diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9d88d863 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +# Ignore dataset folder +/dataset/ +dataset/ + +# Python +__pycache__/ +*.py[cod] +*$py.class + +# Jupyter Notebook +.ipynb_checkpoints + +# Virtual environment +venv/ +env/ +.env + +# IDEs and editors +.vscode/ +.idea/ +*.swp +*.swo + +# Operating System Files +.DS_Store +Thumbs.db + +# Model checkpoints and logs +*.pth +*.pt +*.ckpt +logs/ + +# Temporary files +*.tmp +*.temp + +# Compiled source +*.com +*.class +*.dll +*.exe +*.o +*.so + +# Packages +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases +*.log +*.sql +*.sqlite diff --git a/EDA/EDA.ipynb b/EDA/EDA.ipynb new file mode 100644 index 00000000..4a714464 --- /dev/null +++ b/EDA/EDA.ipynb @@ -0,0 +1,776 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d26a2b99-615a-43d4-ba96-8770bb42d56b", + "metadata": {}, + "source": [ + "# An EDA (Exploratory Data Analysis) for object detection" + ] + }, + { + "cell_type": "markdown", + "id": "f4a79faf-a6e0-447b-97b5-d55fcd8f99a3", + "metadata": {}, + "source": [ + "## 재활용 품목 분류를 위한 Object detection" + ] + }, + { + "cell_type": "markdown", + "id": "d0adbbbe-4aba-4b08-ad73-d0a3df487b14", + "metadata": {}, + "source": [ + "### Importing libraries & prepare DataFrame" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "34734851-74a7-4b3f-9040-be226ef2d08a", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "from PIL import Image\n", + "import matplotlib.patches as patches\n", + "import pandas as pd\n", + "from pycocotools.coco import COCO" + ] + }, + { + "cell_type": "markdown", + "id": "b604d973-5168-448e-837d-eb9af73bfa28", + "metadata": {}, + "source": [ + "### Train image analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "6fbfca5e-3640-42fa-92bb-0037510ef9e1", + "metadata": {}, + "outputs": [], + "source": [ + "# check the json file\n", + "with open('./dataset/train.json', 'r', encoding='utf-8') as f:\n", + "\ttrain = json.load(f)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "a5887e13-eace-441a-bdee-641887a27f1b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dict_keys(['info', 'licenses', 'images', 'categories', 'annotations'])\n" + ] + } + ], + "source": [ + "print(train.keys())" + ] + }, + { + "cell_type": "markdown", + "id": "b0732104-5803-4488-a0d1-19c0eb8d4660", + "metadata": {}, + "source": [ + "- 'info' : metadata\n", + "- 'licenses' : 라이선스 정보\n", + "- 'images' : 이미지 크기, 이름 등\n", + "- 'categories' : 클래스 정보 \n", + "- 'annotations' : bbox 좌표, 박스 크기, 단일 객체만 포함되었는지 여부 등" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "18d97514-3e1c-4261-9e02-44242a18144e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'id': 0, 'name': 'General trash', 'supercategory': 'General trash'}, {'id': 1, 'name': 'Paper', 'supercategory': 'Paper'}, {'id': 2, 'name': 'Paper pack', 'supercategory': 'Paper pack'}, {'id': 3, 'name': 'Metal', 'supercategory': 'Metal'}, {'id': 4, 'name': 'Glass', 'supercategory': 'Glass'}, {'id': 5, 'name': 'Plastic', 'supercategory': 'Plastic'}, {'id': 6, 'name': 'Styrofoam', 'supercategory': 'Styrofoam'}, {'id': 7, 'name': 'Plastic bag', 'supercategory': 'Plastic bag'}, {'id': 8, 'name': 'Battery', 'supercategory': 'Battery'}, {'id': 9, 'name': 'Clothing', 'supercategory': 'Clothing'}]\n" + ] + } + ], + "source": [ + "print(train['categories'])" + ] + }, + { + "cell_type": "markdown", + "id": "bad5df9f-96f5-4090-81d7-9057a5eddabe", + "metadata": {}, + "source": [ + "- 위와 같이 10개 클래스가 있음" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "956c2f3e-769f-4939-85d7-30922b8467ac", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4883" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(train['images']) " + ] + }, + { + "cell_type": "markdown", + "id": "68f7055d-b2d6-4c2f-8671-8b31d4408336", + "metadata": {}, + "source": [ + "- train image는 4883개" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "27da9c0f-9630-4eec-8819-0809f7f1254c", + "metadata": {}, + "outputs": [], + "source": [ + "# 이미지 크기 체크\n", + "heights = [x['height'] for x in train['images']]\n", + "widths = [x['width'] for x in train['images']]" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "708e2b64-6447-4d51-aeae-e692a5af637b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'frequency')" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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73vi448/1r59+7R27Vq98847WrFihZ588skax7Zt21bPPfecxo0bpyNHjujEiRMaNWqUZs2apQ4dOni1DgAA+Fr37t1VUlKiHTt2SJKys7PVpEkTZWdnm2PWrVunHj161Pj4ZcuWadq0afrTn/6krVu3qlmzZnr55ZfN/RkZGRo6dKgGDBig/Px85efnq2vXrub+J554QhkZGcrNzVXr1q11//336/z58x4fxxdffKFly5bpX//6l7KyspSbm6uHHnqoxrFNmzbVG2+8oenTp2vr1q0qLS3Vr3/9a40bN079+vWTJP3mN7/RZ599pqVLl2rXrl361a9+pQEDBujgwYMezw24HlC6gevMrbfeqmnTpik+Pl4jRoxQ586dzd9Hr127Vrt379bf//53de7cWfHx8XruuefUsGFD/eMf/6jx+V566SVNnTpVQ4YMUdu2bbVgwYIaT6Bypddt2rSppAtfSXM4HOZ9T4SEhOiNN97QLbfcokGDBumpp57Siy++aH46cKlx48apW7duSk1N1YgRI9SpUydNnDjR63UAAMDX7Ha7brvtNvNT3HXr1umRRx7Rzp07VVJSooKCAh04cEA9e/as8fHz58/Xgw8+qN/+9rdq06aNnnnmGbVv397c36BBA4WFhSk0NFQOh0MOh8PlK9gZGRkaNGiQWrdurSeffFJHjhzRF1984fFxnDt3Tm+++aZuu+02de/eXS+99JKWLl2qgoKCGscPHDhQo0aN0vDhwzV27FjVq1dPs2bNkiQdOnRI77zzjv7+97/rzjvv1E9+8hNlZGSoW7duWrRokcdzA64HlG7gOnPrrbe63G/WrJkKCwslSdu2bVNpaamioqLM3yg3aNBAeXl5OnToULXncjqdOnnypH72s5+Z24KCgtSpUyePXtcXOnbsqPDwcPN+UlKSSktLdezYscs+5o033tCuXbu0fft288ymkufrAACAVXr27Kl169bJMAx9+umnuvvuu5WQkKD169dr7dq1iomJUdu2bWt87L59+6qdPMyTk4l9P7ubNWsmSV5l90033eRyotGkpCRVVVVp//79l33Mc889p/Pnz2vZsmVasmSJ6tWrJ0navn27DMNQ69atXTI6OzubjEatxYnUgOtM3bp1Xe7bbDbz0+Cqqio1a9asxt9FXenyHxfL6kWGYXj0ula6dG7ft3PnTp05c0Z16tRRQUGBYmNjJXm/DgAA+FrPnj31+uuva+fOnapTp47at2+vHj16KDs7W0VFRZf9arkvfD+7L+apL7L74nNdKaO//PJLnThxQlVVVTpy5Ij5BkBVVZWCgoK0bdu2aicv9feJXgGrULqBWuSOO+5QQUGBgoOD3TqZmd1uV0xMjDZv3qw777xTklRZWakdO3Z4fB3tunXrqrKy0otZX7Bz506dPXvWPGtrTk6OGjRocNlLeH377bd64IEH9MQTT6igoEDDhw/X9u3bFRYW5vE6AABglYu/654/f7569Oghm82mHj16aObMmSoqKjJ/GlWTdu3aKScnRyNGjDC35eTkuIwJCQm5qvx1x9GjR3XixAnzze2NGzeqTp06at26dY3jy8vLNXz4cA0bNkxt27ZVWlqadu/erZiYGN1+++2qrKxUYWGh+W8PoLbj6+VALdKnTx8lJSXpnnvu0YcffqjDhw9rw4YN+sMf/qCtW7fW+Jjx48dr5syZevfdd7V//35NnDhRRUVFV3z3uiYtWrTQxx9/rIKCAhUVFXk89/LycqWlpek///mPPvjgA02bNk0PP/yw6tSp+X9TY8eOVVxcnP7whz9o7ty5MgxDGRkZkrxbBwAArHDxd91vv/22+dvt7t27a/v27Vf8PbckTZw4UW+88YbeeOMNHThwQNOmTdPevXtdxrRo0UK7du3S/v379fXXX6uiosLnx1CvXj2NHDlSO3fu1KeffqoJEyZo6NChcjgcNY5/4okn5HQ69eKLL2ry5Mlq166d0tLSJEmtW7fW8OHDNWLECC1fvlx5eXnasmWLZs+erVWrVvl87kAgoHQDtYjNZtOqVavUvXt3Pfjgg2rdurXuu+8+HT58WDExMTU+ZsqUKbr//vs1YsQIJSUlqUGDBurfv7/52yt3Pf/881qzZo3i4uJ0++23ezz33r17Kz4+Xt27d9fQoUN11113Vbsc2kVvvfWWVq1apcWLFys4OFjh4eFasmSJ/vKXv2jVqlVerQMAAFbp1auXKisrzYLdqFEjtW/fXk2bNlW7du0u+7hhw4bp//2//6cpU6aoU6dOOnLkiH73u9+5jBk1apTatGmjzp07q2nTpvrss898Pv9WrVrp3nvv1cCBA9WvXz8lJCS4nEX9+9atW6f58+dr8eLFioyMVJ06dbR48WKtX79er7zyiqQLVzsZMWKEJk2apDZt2ig5OVmbNm1SXFycz+cOBAKbUdOPNwHcsKqqqtSuXTsNHTpUTz/9tE+f+/Dhw2rZsmW1r68/8MADOn36dI2XKrvWWrRoofT0dPOa4AAA3ChsNptWrFhhXnNbunCd7pUrVyo3N9dv87qoZ8+euu222zR//nx/TwXwCJ90Aze4I0eO6LXXXtOBAwe0e/du/e53v1NeXp5SUlIse82uXbu6XEc0EMyYMUMNGjTQ0aNH/T0VAAD85v7777/s+VT8ZcmSJWrQoIE+/fRTf08F8AonUgNucHXq1FFmZqYyMjJkGIYSEhL00UcfXfHrbt5q3ry5Dh48KEkKDQ31+fNfjbFjx2ro0KGS5NV1xgEAuN5dzOhLzyrub8nJyUpMTJTEVUhwfeLr5QAAAAAAWISvlwMAAAAAYBFKNwAAAAAAFqF0AwAAAABgEUo3AAAAAAAWoXQDAAAAAGARSjcAAAAAABahdAMAAAAAYBFKNwAAAAAAFqF0AwAAAABgkf8PPBBYBgztw+8AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(1, 2, figsize=(10, 4), tight_layout=True)\n", + "ax[0].hist(heights)\n", + "ax[0].set_xlabel('height [pixel]')\n", + "ax[0].set_ylabel('frequency')\n", + "ax[1].hist(widths)\n", + "ax[1].set_xlabel('width [pixel]')\n", + "ax[1].set_ylabel('frequency')" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "6cdaf44c-89df-4604-9f70-376fb15bf40f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{1024} {1024}\n" + ] + } + ], + "source": [ + "width, height = set(widths), set(heights)\n", + "print(width, height)" + ] + }, + { + "cell_type": "markdown", + "id": "c5de797c-053e-4c61-af7c-8fde2e80a016", + "metadata": {}, + "source": [ + "- 4883개 모든 이미지가 1024 $\\times$ 1024 크기이다." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "68eb6302-b68c-45e3-b8ea-bf5b39750db6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "23144\n" + ] + } + ], + "source": [ + "# bounding box의 개수 체크\n", + "print(len(train['annotations']))" + ] + }, + { + "cell_type": "markdown", + "id": "52a47fe1-53ce-490c-97a9-077a5349656b", + "metadata": {}, + "source": [ + "- 4883개의 학습 이미지에는 23144개의 bounding box (=쓰레기 객체)가 포함되어 있다." + ] + }, + { + "cell_type": "markdown", + "id": "74843f0e-8dea-4604-af65-8554dd494b7d", + "metadata": {}, + "source": [ + "### Bounding box analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "e58c9562-ec91-40d2-a5a0-dd6c697db097", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "loading annotations into memory...\n", + "Done (t=0.16s)\n", + "creating index...\n", + "index created!\n" + ] + } + ], + "source": [ + "# convert json dictionary to pandas DataFrame\n", + "coco = COCO('./dataset/train.json')\n", + "\n", + "train_df = pd.DataFrame()\n", + "\n", + "image_ids = []\n", + "class_id = []\n", + "x_min = []\n", + "y_min = []\n", + "x_max = []\n", + "y_max = []\n", + "widths = []\n", + "heights = []\n", + "\n", + "for image_id in coco.getImgIds():\n", + " \n", + " image_info = coco.loadImgs(image_id)[0]\n", + " ann_ids = coco.getAnnIds(imgIds=image_info['id'])\n", + " anns = coco.loadAnns(ann_ids)\n", + " \n", + " file_name = image_info['file_name']\n", + " \n", + " for ann in anns:\n", + " image_ids.append(file_name)\n", + " class_id.append(ann['category_id'])\n", + " x_min.append(float(ann['bbox'][0]))\n", + " y_min.append(float(ann['bbox'][1]))\n", + " x_max.append(float(ann['bbox'][0]) + float(ann['bbox'][2]))\n", + " y_max.append(float(ann['bbox'][1]) + float(ann['bbox'][3]))\n", + " widths.append(float(ann['bbox'][2]))\n", + " heights.append(float(ann['bbox'][3]))\n", + " \n", + "train_df['image_id'] = image_ids\n", + "train_df['class_id'] = class_id\n", + "train_df['x_min'] = x_min\n", + "train_df['y_min'] = y_min\n", + "train_df['x_max'] = x_max\n", + "train_df['y_max'] = y_max\n", + "train_df['width'] = widths\n", + "train_df['height'] = heights" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "f19b0fe6-0332-40ea-b4ee-d7cb622c5fad", + "metadata": {}, + "outputs": [], + "source": [ + "# 이미지와 bbox를 눈으로 확인해보자\n", + "img43 = Image.open('./dataset/train/0043.jpg')\n", + "img43_list = train_df[train_df['image_id']=='train/0043.jpg']" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "a11f6fc6-610b-438f-a199-415ab73a1d23", + "metadata": {}, + "outputs": [], + "source": [ + "def draw_bbox(xmin, ymin, width, height, label=None, color='red', alpha=1):\n", + " #import matplotlib.patches as patches\n", + " bbox = patches.Rectangle((xmin, ymin), width, height, ec=color, fc='none', lw=2, label=label, alpha=alpha)\n", + " plt.gca().add_patch(bbox)\n", + "\n", + "def num_to_label(num):\n", + " return train['categories'][num]['name']" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "9f2581e5-11b8-47ac-9004-1614a224f53b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(img43)\n", + "plt.axis('off')\n", + "colors = ['red', 'green', 'blue']\n", + "for i, (index, x) in enumerate(img43_list.iterrows()):\n", + " draw_bbox(x['x_min'], x['y_min'], x['width'], x['height'], label=num_to_label(x['class_id']), color=colors[i]) \n", + "plt.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "7d5faf48-50e1-4654-8541-c0e6a3233d3c", + "metadata": {}, + "outputs": [], + "source": [ + "# 한 이미지에 얼마나 많은 bounding box들이 있을까?\n", + "count_bbox = train_df.image_id.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "272d197b-dea1-494e-8a93-24a9ba0172d6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'frequency')" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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8omqqqpUVVWljz/+WBkZGRo7dmwwMgIAAJyTgI/oPProo9q1a5cGDhyo0NATP15dXa1x48ZxjQ4AAKhTAi46YWFhWrp0qf785z9rw4YNioyMVOfOnZWUlBSMfAAAAOcs4KJzUrt27dSuXbvazAIAAFCrAi46VVVVWrRokT766CNVVFSourrab/3HH39ca+EAAADOR8BFJyMjQ4sWLdKwYcPUqVMneTyeYOQCAAA4bwEXnSVLluj111/X0KFDg5EHAACg1gR8e3lYWJjatGkTjCwAAAC1KuCiM2PGDD355JMyhq92BwAAdVvAp64+++wzffLJJ3rvvffUsWNH1a9f32/9smXLai0cAADA+Qi46DRs2FCjRo0KRpZzsnv3bt16662qqKhQaGio/vSnP+kPf/iD07EAAEAdEHDRycnJCUaOcxYaGqr58+crJSVFFRUV6t69u4YOHaoGDRo4HQ0AADgs4Gt0JOn48eP68MMP9dxzz+nQoUOSpH379qmy8sI/obl58+ZKSUmRdOKBo40aNdIPP/xwwXMAAIC6J+Cis2vXLnXu3FkjRoxQenq6vvvuO0nSY489ppkzZwYcYM2aNRo+fLgSEhLk8Xi0fPnyGts888wzat26tSIiIpSamqpPP/30lPtat26dqqurlZiYGHAOAABgn4CLTkZGhnr06KF//vOfioyM9C0fNWqUPvroo4ADHD58WF27dtXTTz99yvVLly7VtGnTNHv2bBUUFKhPnz4aMmSISktL/bY7cOCAxo0bp4ULF572s37++Wd5vV6/FwAAsNc53XX1+eefKywszG95UlKS9u7dG3CAIUOGaMiQIaddn5WVpUmTJmny5MmSpPnz52vlypVasGCBMjMzJZ0oMKNGjdIDDzygXr16nXZfmZmZevjhhwPOCAAALk4BH9Gprq5WVVVVjeV79uxRTExMrYQ66ejRo8rPz9fgwYP9lg8ePFhr166VJBljNGHCBF1zzTW69dZbz7i/Bx54QAcPHvS9du/eXat5AQBA3RJw0bn22ms1f/5833uPx6PKykrNmTOn1h8L8f3336uqqkrx8fF+y+Pj41VeXi5J+vzzz7V06VItX75cKSkpSklJ0aZNm065v/DwcMXGxvq9AACAvQI+dfXEE09owIABSk5O1pEjR3TTTTdp+/btatKkiV577bVgZKzx4FBjjG9Z7969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hg0bZlq2bGkqKyt929g8B++884559913zdatW83WrVvNrFmzTP369c3XX39tjLF77L/21VdfmVatWpkuXbqYjIwM33Lb52DOnDmmY8eOpqyszPeqqKjwrbd9/MYY88MPP5ikpCQzYcIE849//MOUlJSYDz/80BQXF/u2sXkeKioq/H7/ubm5RpL55JNPjDHOj52iU8uuvPJKM2XKFL9l7du3N//xH//hUKIL59dFp7q62jRr1szMnTvXt+zIkSMmLi7OPPvssw4kDL6KigojyaxevdoY4845uOSSS8wLL7zgqrEfOnTItG3b1uTm5pp+/fr5io4b5mDOnDmma9eup1znhvEbY8z9999vevfufdr1bpmHkzIyMszll19uqqur68TYOXVVi44ePar8/HwNHjzYb/ngwYO1du1ah1I5p6SkROXl5X7zER4ern79+lk7HwcPHpQkNWrUSJK75qCqqkpLlizR4cOH1bNnT1eNPT09XcOGDdOgQYP8lrtlDrZv366EhAS1bt1aY8eO1c6dOyW5Z/zvvPOOevTooT/84Q9q2rSpunXrpueff9633i3zIJ34d/Dll1/WxIkT5fF46sTYKTq16Pvvv1dVVZXi4+P9lsfHx6u8vNyhVM45OWa3zIcxRtOnT1fv3r3VqVMnSe6Yg02bNik6Olrh4eGaMmWK3n77bSUnJ7ti7JK0ZMkSrV+/XpmZmTXWuWEOfvvb32rx4sVauXKlnn/+eZWXl6tXr146cOCAK8YvSTt37tSCBQvUtm1brVy5UlOmTNHdd9+txYsXS3LHn4OTli9frh9//FETJkyQVDfG7uqnlweLx+Pxe2+MqbHMTdwyH3feeac2btyozz77rMY6m+fgiiuuUGFhoX788Ue99dZbGj9+vFavXu1bb/PYd+/erYyMDH3wwQeKiIg47XY2z8GQIUN8/9+5c2f17NlTl19+uV566SVdddVVkuwevyRVV1erR48e+utf/ypJ6tatmzZv3qwFCxZo3Lhxvu1snwdJevHFFzVkyBAlJCT4LXdy7BzRqUVNmjRRSEhIjZZaUVFRo826wck7L9wwH3fddZfeeecdffLJJ2rRooVvuRvmICwsTG3atFGPHj2UmZmprl276sknn3TF2PPz81VRUaHU1FSFhoYqNDRUq1ev1t///neFhob6xmnzHPxagwYN1LlzZ23fvt0VfwYkqXnz5kpOTvZb1qFDB5WWlkpyx98DkrRr1y59+OGHmjx5sm9ZXRg7RacWhYWFKTU1Vbm5uX7Lc3Nz1atXL4dSOad169Zq1qyZ33wcPXpUq1evtmY+jDG68847tWzZMn388cdq3bq133o3zMGvGWP0888/u2LsAwcO1KZNm1RYWOh79ejRQzfffLMKCwv1m9/8xvo5+LWff/5ZW7ZsUfPmzV3xZ0CSrr766hpfK7Ft2zYlJSVJcs/fAzk5OWratKmGDRvmW1Ynxn5BLnl2kZO3l7/44oumqKjITJs2zTRo0MB8++23TkcLikOHDpmCggJTUFBgJJmsrCxTUFDgu51+7ty5Ji4uzixbtsxs2rTJ3HjjjdbcUmmMMXfccYeJi4szq1at8ru98qeffvJtY/McPPDAA2bNmjWmpKTEbNy40cyaNcvUq1fPfPDBB8YYu8d+Or+868oY++dgxowZZtWqVWbnzp3myy+/NL/73e9MTEyM7+8828dvzImvFggNDTV/+ctfzPbt280rr7xioqKizMsvv+zbxvZ5qKqqMi1btjT3339/jXVOj52iEwTZ2dkmKSnJhIWFme7du/tuNbbRJ598YiTVeI0fP94Yc+K2yjlz5phmzZqZ8PBw07dvX7Np0yZnQ9eiU41dksnJyfFtY/McTJw40fdn/dJLLzUDBw70lRxj7B776fy66Ng+Bye/E6V+/fomISHBjB492mzevNm33vbxn/Q///M/plOnTiY8PNy0b9/eLFy40G+97fOwcuVKI8ls3bq1xjqnx+4xxpgLc+wIAADgwuIaHQAAYC2KDgAAsBZFBwAAWIuiAwAArEXRAQAA1qLoAAAAa1F0AACAtSg6AADAWhQdwHLffPONrrrqKkVERCglJeWU2/Tv31/Tpk07435atWql+fPn13q+YPJ4PFq+fLnTMc7JxTjfQF0U6nQAACd89913SkhI0MGDBxUWFqa4uDht2bJFLVu2PK/9zpkzRw0aNNDWrVsVHR1dS2kvDmVlZbrkkkucjnFO8vLy1KBBA6djABc9ig5QR3zxxRdKSUlRVFSU/vGPf6hRo0bnXXIkaceOHRo2bJjvScpu0qxZM6cjnLNLL73U6QiAFTh1BdQRa9eu1dVXXy1J+uyzz3z/fybV1dV65JFH1KJFC4WHhyslJUXvv/++b73H41F+fr4eeeQReTwePfTQQ6fd1/Hjx3XnnXeqYcOGaty4sR588EH9+lF4hw4d0k033aTo6GglJCToqaee8ltfWlqqESNGKDo6WrGxsfq3f/s37d+/X9KJU2hRUVF69dVXfdsvW7ZMERER2rRpkyTp4MGDuv3229W0aVPFxsbqmmuu0YYNG3zbb9iwQQMGDFBMTIxiY2OVmpqqdevWnXZMvzx19e2338rj8WjZsmUaMGCAoqKi1LVrV33xxRdnnOMzjUmSHnroIaWkpOi///u/1apVK8XFxWns2LE6dOiQbxtjjB577DH95je/UWRkpLp27ao333zzjJ/761NXHo9Hzz33nH73u98pKipKHTp00BdffKHi4mL1799fDRo0UM+ePbVjxw7fz+zYsUMjRoxQfHy8oqOjlZaWpg8//NDvc8rKyjRs2DBFRkaqdevWevXVV2t89r/6vQB12gV7fCiAGnbt2mXi4uJMXFycqV+/vomIiDBxcXEmLCzMhIeHm7i4OHPHHXec9uezsrJMbGysee2118w333xj7rvvPlO/fn2zbds2Y4wxZWVlpmPHjmbGjBmmrKzMHDp06JT76devn4mOjjYZGRnmm2++MS+//LKJioryewJzUlKSiYmJMZmZmWbr1q3m73//uwkJCfE9rby6utp069bN9O7d26xbt858+eWXpnv37qZfv36+fWRnZ5u4uDjz7bffmr1795pGjRqZJ554wvfzV199tRk+fLjJy8sz27ZtMzNmzDCNGzc2Bw4cMMYY07FjR3PLLbeYLVu2mG3btpnXX3/dFBYWnnZ+JJm3337bGGNMSUmJkWTat29v/vd//9ds3brV3HDDDSYpKckcO3bslD9/NmOaM2eOiY6ONqNHjzabNm0ya9asMc2aNTOzZs3ybTNr1izTvn178/7775sdO3aYnJwcEx4eblatWnXa7ElJSb65OTmWyy67zCxdutRs3brVjBw50rRq1cpcc8015v333zdFRUXmqquuMtdff73vZwoLC82zzz5rNm7caLZt22Zmz55tIiIizK5du3zbDBo0yKSkpJgvv/zS5Ofnm379+pnIyMiAfi9AXUbRARx07NgxU1JSYjZs2GDq169vCgsLTXFxsYmOjjarV682JSUl5rvvvjvtzyckJJi//OUvfsvS0tLM1KlTfe+7du1q5syZc8Yc/fr1Mx06dDDV1dW+Zffff7/p0KGD731SUpLfP6LGGDNmzBgzZMgQY4wxH3zwgQkJCTGlpaW+9Zs3bzaSzFdffeVbNmzYMNOnTx8zcOBAc+211/o+86OPPjKxsbHmyJEjfp9x+eWXm+eee84YY0xMTIxZtGjRGcfyS6cqOi+88EKNfFu2bDnlz5/NmObMmWOioqKM1+v1bXPvvfea3/72t8YYYyorK01ERIRZu3at374nTZpkbrzxxtNmP1XRefDBB33vv/jiCyPJvPjii75lr732momIiDjtPo0xJjk52Tz11FPGGGO2bNliJJm8vDzf+u3btxtJvs8+m98LUJdx6gpwUGhoqFq1aqVvvvlGaWlp6tq1q8rLyxUfH6++ffuqVatWatKkySl/1uv1at++fTVOcV199dXasmVLwFmuuuoqeTwe3/uePXtq+/btqqqq8lv2Sz179vR91pYtW5SYmKjExETf+uTkZDVs2NAvz3/9139p48aNWr9+vRYtWuT7zPz8fFVWVqpx48aKjo72vUpKSnynY6ZPn67Jkydr0KBBmjt3rt9pmrPVpUsX3/83b95cklRRUXHKbc92TK1atVJMTIzffk/us6ioSEeOHNG1117rN67FixcHnP+X2ePj4yVJnTt39lt25MgReb1eSdLhw4d13333+TJHR0frm2++UWlpqSRp69atCg0NVffu3X37aNOmjd8F3GfzewHqMi5GBhzUsWNH7dq1S8eOHVN1dbWio6N1/PhxHT9+XNHR0UpKStLmzZvPuI9flhPpxPUgv14WTCc/63Sf++vlGzZs0OHDh1WvXj2Vl5crISFB0onrjZo3b65Vq1bV2EfDhg0lnbge5qabbtK7776r9957T3PmzNGSJUs0atSos85bv379Gtmrq6tPue3ZjumX+zy535P7PPnfd999V5dddpnfduHh4Wed+3TZzzSee++9VytXrtR//ud/qk2bNoqMjNQNN9ygo0eP+sZxKr9cfja/F6Auo+gADlqxYoWOHTumgQMH6rHHHlNqaqrGjh2rCRMm6Prrr6/xD+gvxcbGKiEhQZ999pn69u3rW7527VpdeeWVAWf58ssva7xv27atQkJCzrhN+/btJZ040lFaWqrdu3f7joAUFRXp4MGD6tChgyTphx9+0IQJEzR79myVl5fr5ptv1vr16xUZGanu3burvLzcd5TrdNq1a6d27drpnnvu0Y033qicnJyAik4gzmZMZ7OP8PBwlZaWql+/fkHJeTqffvqpJkyY4JufyspKffvtt7717du31/Hjx1VQUKDU1FRJUnFxsX788UffNmf7ewHqKooO4KCkpCSVl5dr//79GjFihOrVq6eioiKNHj3ad6TjTO69917NmTNHl19+uVJSUpSTk6PCwkK98sorAWfZvXu3pk+frj/+8Y9av369nnrqKT3++ON+23z++ed67LHHNHLkSOXm5uqNN97Qu+++K0kaNGiQunTpoptvvlnz58/X8ePHNXXqVPXr1089evSQJE2ZMkWJiYl68MEHdfToUXXv3l0zZ85Udna2Bg0apJ49e2rkyJH629/+piuuuEL79u3TihUrNHLkSHXs2FH33nuvbrjhBrVu3Vp79uxRXl6efv/73wc81rN1NmP6V2JiYjRz5kzdc889qq6uVu/eveX1erV27VpFR0dr/PjxQcvfpk0bLVu2TMOHD5fH49Gf/vQnv6NX7du316BBg3T77bdrwYIFql+/vmbMmKHIyEjf0aF/9Xs523kAnELRARy2atUqpaWlKSIiQp9++qkuu+yysyo5knT33XfL6/VqxowZqqioUHJyst555x21bds24Bzjxo3T//3f/+nKK69USEiI7rrrLt1+++1+28yYMUP5+fl6+OGHFRMTo8cff1zXXXedpP9/K/ddd92lvn37ql69err++ut9t6AvXrxYK1asUEFBgUJDQxUaGqpXXnlFvXr10rBhwzR06FCtWLFCs2fP1sSJE/Xdd9+pWbNm6tu3r+Lj4xUSEqIDBw5o3Lhx2r9/v5o0aaLRo0fr4YcfDnisZ+tfjels/fnPf1bTpk2VmZmpnTt3qmHDhurevbtmzZoVpOQnPPHEE5o4caJ69eqlJk2a6P777/ddv3PS4sWLNWnSJPXt21fNmjVTZmamNm/erIiICEkn5uBMvxegrvOY052kBQC4zp49e5SYmKgPP/xQAwcOdDoOcN4oOgDgYh9//LEqKyvVuXNnlZWV6b777tPevXu1bdu2M14jBlwsOHUFAC527NgxzZo1Szt37lRMTIx69eqlV155hZIDa3BEBwAAWIsvDAQAANai6AAAAGtRdAAAgLUoOgAAwFoUHQAAYC2KDgAAsBZFBwAAWIuiAwAArPX/ADs/078XjqVzAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.hist(count_bbox, bins=np.arange(0, 80, 10), ec='white')\n", + "plt.yscale('log')\n", + "plt.xlabel('# of bboxes in one image')\n", + "plt.ylabel('frequency')" + ] + }, + { + "cell_type": "markdown", + "id": "ad6d8114-ce77-440f-afeb-a1705d2c6b81", + "metadata": {}, + "source": [ + "- 한 이미지 내에 bbox가 많다!" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "13b52087-74e1-4ecc-8137-079298efebe1", + "metadata": {}, + "outputs": [], + "source": [ + "has_one = count_bbox[count_bbox==1]\n", + "has_two = count_bbox[count_bbox==2]\n", + "has_three = count_bbox[count_bbox==3]\n", + "has_four = count_bbox[count_bbox==4]\n", + "has_five = count_bbox[count_bbox>=5] # over five" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "919cc73e-c05c-4e2f-a440-b448e115daa6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'number of bboxes in one image')" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "labels = ['1', '2', '3', '4', 'over 5']\n", + "num_of_bbox = [len(has_one)/len(train['images'])*100, len(has_two)/len(train['images'])*100, len(has_three)/len(train['images'])*100,\n", + " len(has_four)/len(train['images'])*100, len(has_five)/len(train['images'])*100]\n", + "\n", + "fig, ax = plt.subplots()\n", + "ax.pie(num_of_bbox,labels=labels, autopct='%1.1f%%', startangle=90) \n", + "plt.title('number of bboxes in one image')" + ] + }, + { + "cell_type": "markdown", + "id": "54b9c3dc-d836-4962-8fcf-136f63876ae8", + "metadata": {}, + "source": [ + "- 대부분(40.5%)의 이미지들이 bbox를 1개 포함하고 있다.\n", + "- 5개 이상의 bbox를 가진 이미지도 ~30%나 된다." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f4a4ef4e-2355-4391-8d33-f98b727a8b90", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'frequency')" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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cKZjx45avnz5Tw4HAADQHLwuRPv377/sjL+/v8aOHXtFgQAAAJqb19cQrVixQn/84x/rrf/jH/+oVatWNUooAACA5uR1IXr++efVrl27eusjIiKUkZHRKKEAAACak9eF6NChQxf86Y5OnTrp8OHDjRIKAACgOXldiCIiIvTFF1/UW//5558rPDy8UUIBAAA0J68L0YMPPqjp06frww8/VG1trWpra/XBBx9oxowZevDBB5siIwAAQJPy+i6zZ599VocOHVL//v3l6/vD0+vq6jRmzBiuIQIAAK2S14XI399fb775pn7/+9/r888/V2BgoHr06KFOnTo1RT4AAIAm53Uh+tGNN96oG2+8sTGzAAAAWMLrQlRbW6uVK1fq/fffV1lZmerq6jy2f/DBB40WDgAAoDl4XYhmzJihlStXatiwYYqPj5fD4WiKXAAAAM3G60KUk5Ojt956S/fcc09T5AEAAGh2Xt927+/vr86dOzdFFgAAAEt4XYhmzZqll19+WYZhNEUeAACAZuf1KbNNmzbpww8/1Lvvvqvu3bvLz8/PY/vbb7/daOEAAACag9eF6LrrrtP999/fFFkAAAAs4XUhWrFiRVPkAAAAsIzX1xBJ0rlz57Rx40YtWbJElZWVkqTjx4+rqqqqUcMBAAA0B6+PEB06dEhDhgzR4cOHVV1drYEDByokJETz58/X999/r9dee60pcgIAADQZr48QzZgxQ7169VJ5ebkCAwPN9ffff7/ef//9Rg0HAADQHK7oLrNPP/1U/v7+Hus7deqkY8eONVowAACA5uL1EaK6ujrV1tbWW3/06FGFhIQ0SigAAIDm5HUhGjhwoBYuXGg+djgcqqqq0jPPPMPPeQAAgFbJ61NmCxYs0N13361u3brp+++/V0pKivbv36927drpjTfeaIqMAAAATcrrQhQdHa2ioiK98cYb+uyzz1RXV6fx48fr4Ycf9rjIGgAAoLXwuhBJUmBgoMaNG6dx48Y1dh4AAIBm53UhWr169SW3jxkz5orDAAAAWMHrQjRjxgyPx2fPntV3330nf39/BQUFUYgAAECr4/VdZuXl5R5LVVWV9u7dqz59+nBRNQAAaJWu6LfMzhcXF6fnn3++3tEjAACA1qBRCpEk+fj46Pjx4431cgAAAM3G62uI3nnnHY/HhmGopKRE2dnZuuOOOxotGAAAQHPx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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# class imbalance 체크\n", + "plt.hist(train_df['class_id'], bins=np.arange(0, 11, 1), ec='white')\n", + "plt.xticks(np.arange(0, 11, 1))\n", + "plt.xlabel('class')\n", + "plt.ylabel('frequency')" + ] + }, + { + "cell_type": "markdown", + "id": "beb6653e-b852-4828-80d3-d36752c255f4", + "metadata": {}, + "source": [ + "- class imbalance가 보임\n", + "- 일반 쓰레기, 종이, 플라스틱, 비닐봉지는 상대적으로 많지만 그 외 (종이팩, 철, 유리, 스티로폼, 배터리, 의류)는 수가 적음" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "id": "c2bd9940-011f-4ad4-a149-585c0efd5394", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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c1VWVqbi4mJfRwEAAF4SsEUHAACYj6IDAACMRdEBAADGougAAABjUXQAAICxKDoAAMBYFB0AAGAsig4AADAWRQcAABiLogMAAIxF0QEAAMai6AAAAGNRdAAAgLEoOgAAwFgUHQAAYCyKDgAAMBZFBwAAGIuiAwAAjEXRAQAAxqLoAAAAYwVs0SksLFRqaqoyMzN9HQUAAHhJwBad3NxclZWVqbi42NdRAACAlwRs0QEAAOaj6AAAAGNRdAAAgLEoOgAAwFgUHQAAYCyKDgAAMBZFBwAAGIuiAwAAjEXRAQAAxqLoAAAAY1F04Nd6R4aryWH5OkarukJGAAhEIb4OAFxIdPcQBQfZlLepVOU1db6O06KUuEitmT7M1zEAAC2g6KBLKK+p074qu69jAAC6GE5dAQAAY1F0AACAsSg6AADAWBQdAABgLIoOAAAwFkUHAAAYi6IDAACMRdEBAADGougAAABjuV10KioqvJEDAADA49wuOikpKRo/frz+67/+S6dOnfJGJgAAAI9wu+h88MEHGjZsmO6++2716dNHd9xxh95//31vZAMAAOgQt4tOWlqaCgoK9MUXX2jDhg06evSoRo8erauuukoFBQX6+9//7o2cAAAAbmv3xcghISGaMmWKXnjhBf32t7/VgQMHtHDhQiUmJmrWrFmqrq72ZE4AAAC3tbvolJSUaN68eUpISFBBQYEWLlyoAwcO6M0339QXX3yhn/70p57MCQAA4LYQd99QUFCgDRs26NNPP9WkSZP0zDPPaNKkSQoK+q4z9e/fX0888YSuvPJKj4cFAABwh9tFZ926dbrtttt06623qk+fPi2u069fPz311FMdDtcWtbW1mjBhghobG9XU1KT58+fr9ttv75R9AwAA/+Z20dm/f3+r64SFhWn27NntCuSuHj166J133lGPHj108uRJpaWlaerUqerZs2en7B8AAPgvt6/R2bBhg7Zs2dJs/pYtW/T00097JJQ7goOD1aNHD0nSqVOn1NTUJMuyOj0HAADwP24XnYceeki9evVqNj8uLk6/+c1v3A6wfft2TZ48WX379pXNZtPWrVubrbN27Vr1799f3bp1U3p6unbs2OGy/Ouvv9aQIUOUmJioRYsWtZgPAAAEHreLzqFDh9S/f/9m85OTk1VZWel2gPr6eg0ZMkSPPfZYi8s3b96sBQsWaMmS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i+uHhISoZ8+eXsvqz9ozjmdt3rxZOTk52rJli374wx96M6bfc3cca2trVVJSotLSUt15552SvvvCtixLISEheuONNzRhwoROye5P2vP3mJCQoEsuuUQxMTHOeYMGDZJlWTpy5IiuuOIKr2b2R+0ZxxUrVujaa6/VPffcI0kaPHiwIiIiNGbMGC1fvjxgj0S4i++ZlnFEpx3CwsKUnp6uoqIil/lFRUUaNWpUi+8ZOXJks/XfeOMNZWRkKDQ01GtZ/Vl7xlH67kjOLbfcoueee45z+HJ/HKOjo/XRRx9p7969ztfcuXM1cOBA7d27V9dcc01nRfcr7fl7vPbaa1VVVaW6ujrnvM8++0xBQUFKTEz0al5/1Z5xPHnypIKCXL+OgoODJf3jiARax/fMefjoIugu7+ztk0899ZRVVlZmLViwwIqIiLA+//xzy7Is695777VmzpzpXP/sbX933XWXVVZWZj311FPc9me5P47PPfecFRISYhUWFlrV1dXO19dff+2rj+AX3B3Hc3HX1XfcHcfa2lorMTHR+pd/+Rdr37591jvvvGNdccUV1pw5c3z1EfyCu+O4YcMGKyQkxFq7dq114MAB691337UyMjKsESNG+Ooj+IXa2lqrtLTUKi0ttSRZBQUFVmlpqfM2fb5n2oai0wGFhYVWcnKyFRYWZg0fPtx65513nMtmz55tjR071mX9t99+2xo2bJgVFhZmXXrppda6des6ObF/cmccx44da0lq9po9e3bnB/cz7v49fh9F5x/cHcdPPvnE+uEPf2h1797dSkxMtPLz862TJ092cmr/4+44Pvroo1ZqaqrVvXt3KyEhwZoxY4Z15MiRTk7tX956660L/v+O75m2sVkWxwUBAICZuEYHAAAYi6IDAACMRdEBAADGougAAABjUXQAAICxKDoAAMBYFB0AAGAsig4AADAWRQcAABiLogMAaJOKigqNHz9eqampuvrqq1VfX+/rSECr+AkIAECbjB07VsuXL9eYMWP05ZdfKjo6WiEhIb6OBVwQR3QAnJdlWfrZz36m2NhY2Ww27d27t9k648aN04IFCzo9W1udm6+z8vpqXMaNGyebzXbe/17ttW/fPoWGhmrMmDGSpNjYWGfJueWWW5z73Lp1q8f2CXgCRQfAeb322mvauHGj/vznP6u6ulppaWm+jtRhL730kh588EFf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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# bbox area distribution\n", + "area = train_df['width']*train_df['height']\n", + "areaby106 = [x/10**6 for x in area]\n", + "train_df['area'] = areaby106 # DataFrame에 넓이 정보 추가\n", + "\n", + "plt.hist(areaby106, bins=np.arange(0, 1.2, 0.1), ec='white')\n", + "plt.xlabel('area of bounding box [$10^6$]')\n", + "plt.ylabel('frequency')\n", + "plt.yscale('log')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53cf8e4e-efc3-4463-bb2b-eb180a17d514", + "metadata": {}, + "outputs": [], + "source": [ + "# bbox area distribution by class" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "id": "cce7cab6-1224-4749-9eae-9fedcf6554a5", + "metadata": {}, + "outputs": [], + "source": [ + "# 클래스별로 데이터 분리\n", + "for i in range(10):\n", + " globals()['class{}'.format(i)] = train_df[train_df['class_id']==i]" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "id": "edd7ccce-33bb-43e2-aeb1-50698ce18337", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAACVYAAAMWCAYAAAAQq0+DAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAD2EElEQVR4nOzdfZyVdZ0//vcwA8P9GCIIciPeO6KgMLWSJFjijuUNlovfCtFgN5ZTQlSurG0mubHpLxZ3PaCWim6lbGZs25ciKhPS3XUgMHO6w0gwwFkwHcEcdOb6/eGXGQ93zsC5mTnzfD4e549znWuu63U+0bycM++5rpIkSZIAAAAAAAAAAACgWZdCBwAAAAAAAAAAAGhvDFYBAAAAAAAAAADsw2AVAAAAAAAAAADAPgxWAQAAAAAAAAAA7MNgFQAAAAAAAAAAwD4MVgEAAAAAAAAAAOzDYBUAAAAAAAAAAMA+DFYBAAAAAAAAAADsw2AVAAAAAAAAAADAPgxWQQ784he/iOnTp8eJJ54YPXr0iB49esTJJ58cH//4x2Pt2rWFjpdVS5cujZKSkvjDH/5wyP2eeOKJ+MIXvhAvvfRSXnIdSElJSXziE58o2PkBKD57e3Dvo6ysLIYMGRLXXntt/PGPfyx0PADIK72YPccff3x84AMfKHQMADqpt3b6T3/60/1eT5IkTjrppCgpKYkJEya0+fiLFy+OpUuXHlHG448/Pq655pojOgYAtBet/d3yF77whSgpKSlgUuicygodAIrNXXfdFZ/4xCfi1FNPjdmzZ8cZZ5wRJSUl8atf/SoefPDBqKqqio0bN8aJJ55Y6Kh59cQTT8TNN98c11xzTRx11FGFjgMAWXXffffFaaedFn/+859j9erVsWDBgnjsscfi6aefjl69ehU6HgDklV4EgOLQp0+fuOeee/Ybnnrsscfi2WefjT59+hzWcRcvXhz9+/c3GAUA4XfL0BEYrIIsevzxx2PWrFnx/ve/Px5++OHo1q1b82sXXHBBpFKp+Na3vhU9evQoYMpDe/XVV6Nnz56FjhF//vOf2/U6AcBbjRw5MsaOHRsRERMnTozGxsb44he/GMuXL4+PfOQjBU7XIkmSeO2113QsADnVUXoxQjcCwKFMmTIlvvGNb0Q6nY6+ffs2b7/nnnvi3HPPjfr6+gKmA4COrxh+twydgVsBQhZ96UtfitLS0rjrrrsyiu+trrzyyhg8eHDGtrVr18all14a/fr1i+7du8fZZ58d//7v/56xz97LLz/66KPxt3/7t9G/f/84+uij44orroitW7fud55ly5bFueeeG7169YrevXvHRRddFOvXr8/Y55prronevXvH008/HZMmTYo+ffrEe9/73oiIWLVqVVx22WUxZMiQ6N69e5x00knx8Y9/PHbs2NHmdfnCF74Qn/3sZyMiYsSIEftdRnrvLQ4eeeSROPvss6N79+5x8803R0REOp2O97znPTFgwIDo1atXnHnmmXHrrbfG66+/nnGO9evXxwc+8IEYMGBAlJeXx+DBg+P9739/PP/88/vl+bd/+7c4/fTTo2fPnjFq1Kj43ve+1+b3BACH8hd/8RcREfHcc8/FzTffHO9617uiX79+0bdv3zjnnHPinnvuiSRJMr5mbx9+5zvfibPOOiu6d+8eJ5xwQvzLv/zLfsevr6+Pz3zmMzFixIjo1q1bHHfccTFnzpzYvXt3xn57b4N75513xumnnx7l5eVx//335+6NA8ABvLUXI6JDdWNrM7z22mvx6U9/OkaPHh0VFRXRr1+/OPfcc+M//uM/9jtmU1NT/Ou//muMHj06evToEUcddVT8xV/8RXz3u9895DouXrw4ysrK4qabbjrkfgCQLf/n//yfiIh48MEHm7e9/PLL8e1vfzs+9rGP7bf/nj174pZbbonTTjstysvL45hjjolrr702/vd//7d5n+OPPz6eeeaZeOyxx5o/Jz7++OMjom19CgDF4HB/t/xWy5Yti0mTJsWgQYOiR48ecfrpp8cNN9yw38/Dv//97+Oqq66KwYMHR3l5eQwcODDe+973xoYNG5r3+clPfhITJkyIo48+Onr06BHDhg2LD37wg/Hqq69m5f1CR+WKVZAljY2N8eijj8bYsWNj0KBBrf66Rx99NP7yL/8y3vWud8Wdd94ZFRUV8dBDD8WUKVPi1Vdf3e9yyDNmzIj3v//98c1vfjO2bNkSn/3sZ+OjH/1o/OQnP2ne50tf+lJ87nOfi2uvvTY+97nPxZ49e+K2226L8ePHx5NPPhmVlZXN++7ZsycuvfTS+PjHPx433HBDvPHGGxER8eyzz8a5554bM2bMiIqKivjDH/4QCxcujPPOOy+efvrp6Nq1a6vf44wZM+LFF1+Mf/3Xf41HHnmkeX3emuPnP/95/OpXv4rPfe5zMWLEiObbQzz77LPx4Q9/uPnD8aeeeir+8R//MX7961/HvffeGxERu3fvjgsvvDBGjBgR6XQ6Bg4cGNu3b49HH300XnnllYws//f//t+oqamJ+fPnR+/evePWW2+NyZMnx29+85s44YQTWv2eAOBQNm7cGBERxxxzTDzxxBPx8Y9/PIYNGxYREf/93/8dn/zkJ+OPf/xjfP7zn8/4ug0bNsScOXPiC1/4Qhx77LHxjW98I2bPnh179uyJz3zmMxHx5tUlzz///Hj++efj7//+7+Oss86KZ555Jj7/+c/H008/HT/60Y+ipKSk+ZjLly+PNWvWxOc///k49thjY8CAAXlaBQB401t7MSLiD3/4Q4fqxtZkaGhoiBdffDE+85nPxHHHHRd79uyJH/3oR3HFFVfEfffdF1dffXXz8a655pr4+te/HtOnT4/58+dHt27d4uc//3n84Q9/OOD5kySJz372s/Ev//Iv8bWvfc1tkwDIm759+8aHPvShuPfee+PjH/94RLw5ZNWlS5eYMmVKLFq0qHnfpqamuOyyy2LNmjVx/fXXx7hx4+K5556Lm266KSZMmBBr166NHj16xHe+85340Ic+FBUVFbF48eKIiCgvL4+ItvUpAHR0h/u75X397ne/i4svvjjmzJkTvXr1il//+tfx5S9/OZ588smM3x9ffPHF0djYGLfeemsMGzYsduzYEU888US89NJLEfHmz+rvf//7Y/z48XHvvffGUUcdFX/84x/jBz/4QezZs6dd3PEICiYBsmL79u1JRCRXXXXVfq+98cYbyeuvv978aGpqan7ttNNOS84+++zk9ddfz/iaD3zgA8mgQYOSxsbGJEmS5L777ksiIpk1a1bGfrfeemsSEcm2bduSJEmSzZs3J2VlZcknP/nJjP1eeeWV5Nhjj03+6q/+qnnbtGnTkohI7r333kO+t6ampuT1119PnnvuuSQikv/4j/9ofm1vrk2bNh3yGLfddttB9xs+fHhSWlqa/OY3vznkMRobG5PXX389eeCBB5LS0tLkxRdfTJIkSdauXZtERLJ8+fJDfn1EJAMHDkzq6+ubt23fvj3p0qVLsmDBgkN+LQAcyN4e/O///u/k9ddfT1555ZXke9/7XnLMMcckffr0SbZv356x/94umz9/fnL00Udn/DfB8OHDk5KSkmTDhg0ZX3PhhRcmffv2TXbv3p0kSZIsWLAg6dKlS1JTU5Ox38MPP5xERLJixYrmbRGRVFRUNHcmAORSW3sxSdp/N7Y2w772fg4wffr05Oyzz27evnr16iQikhtvvPFtz/v+978/efXVV5MPfvCDSUVFRfKjH/2oVZkB4Ejt7fSamprk0UcfTSIi+eUvf5kkSZJUVVUl11xzTZIkSXLGGWck559/fpIkSfLggw8mEZF8+9vfzjhWTU1NEhHJ4sWLm7e99esO5WB9miRvduW0adMO/00CQIEdzu+Wb7rppuRQIx57f6f72GOPJRGRPPXUU0mSJMmOHTuSiEgWLVp00K/d+zP0vj//AkniVoCQB2PGjImuXbs2P77yla9ExJt/tfvrX/86PvKRj0RExBtvvNH8uPjii2Pbtm3xm9/8JuNYl156acbzs846KyJabqmwcuXKeOONN+Lqq6/OOF737t3j/PPPb7793lt98IMf3G9bXV1dzJw5M4YOHRplZWXRtWvXGD58eERE/OpXvzqyBTmAs846K0455ZT9tq9fvz4uvfTSOProo6O0tDS6du0aV199dTQ2NsZvf/vbiIg46aST4h3veEf83d/9Xdx5551RW1t70PNMnDgx+vTp0/x84MCBMWDAgOb1A4DD8Rd/8RfRtWvX6NOnT3zgAx+IY489Nr7//e/HwIED4yc/+Um8733vi4qKiuYu+/znPx87d+6Murq6jOOcccYZMWrUqIxtH/7wh6O+vj5+/vOfR0TE9773vRg5cmSMHj06o+svuuiijFvt7nXBBRfEO97xjpy+fwB4q0P1YkR0uG5sTYaIiG9961vx7ne/O3r37t38c/Q999yT8TP097///YiISKVSb3venTt3xgUXXBBPPvlk/OxnP4v3vve9rc4MANly/vnnx4knnhj33ntvPP3001FTU3PA2wB+73vfi6OOOiouueSSjD4ePXp0HHvssQf8XPpAWtOnAFDsDva75QP5/e9/Hx/+8Ifj2GOPbf4Z+/zzz4+Ilt/p9uvXL0488cS47bbbYuHChbF+/fpoamrKOM7o0aOjW7du8Td/8zdx//33x+9///vcvUHoYAxWQZb0798/evToccABnW9+85tRU1MT3/3udzO2v/DCCxER8ZnPfCajHLt27RqzZs2KiIgdO3ZkfM3RRx+d8XzvZZL//Oc/Zxyzqqpqv2MuW7Zsv+P17Nkz+vbtm7GtqakpJk2aFI888khcf/318eMf/ziefPLJ+O///u+Mc2XTgS5xuXnz5hg/fnz88Y9/jNtvvz3WrFkTNTU1kU6nM3JUVFTEY489FqNHj46///u/jzPOOCMGDx4cN910U7z++usZx9x3/SLeXMNcvCcAOo8HHnggampqYv369bF169b4xS9+Ee9+97vjySefjEmTJkVExFe/+tV4/PHHo6amJm688caI2L9Tjz322P2OvXfbzp07I+LNrv/FL36xX8/36dMnkiTZr+uP5DLSAHA4DtaLEdEhu7E1GR555JH4q7/6qzjuuOPi61//evzXf/1X8y+eX3vtteav+9///d8oLS094DH39dvf/jb+53/+J6qrq2PkyJFtygwA2VJSUhLXXnttfP3rX48777wzTjnllBg/fvx++73wwgvx0ksvRbdu3fbr5O3bt+/XxwfS2j4FgGJwOL9b3teuXbti/Pjx8T//8z9xyy23xE9/+tOoqamJRx55JCJafsYuKSmJH//4x3HRRRfFrbfeGuecc04cc8wxcd1118Urr7wSEREnnnhi/OhHP4oBAwZEKpWKE088MU488cS4/fbbs/zOoeMpK3QAKBalpaVxwQUXxA9/+MPYtm1bxge1lZWVEfHmvWnfqn///hERMW/evLjiiisOeNxTTz21TTn2HvPhhx9uvsLUoZSUlOy37Ze//GU89dRTsXTp0pg2bVrz9o0bN7YpS1scKMfy5ctj9+7d8cgjj2S8lw0bNuy375lnnhkPPfRQJEkSv/jFL2Lp0qUxf/786NGjR9xwww05yw0AERGnn356jB07dr/tDz30UHTt2jW+973vRffu3Zu3L1++/IDH2b59+0G37R0O3vsD97333nvAY+z9b4G9DtSxAJBLB+vFiI7Zja3J8PWvfz1GjBgRy5Ytyzh+Q0NDxtcdc8wx0djYGNu3b3/bAa9zzz03rrzyypg+fXpERCxZsiS6dPE3kgDk3zXXXBOf//zn484774x//Md/POA+/fv3j6OPPjp+8IMfHPD1t95F4GBa26cAUAwO53fL+/rJT34SW7dujZ/+9KfNV6mKiHjppZf223f48OFxzz33RMSbf8jz7//+7/GFL3wh9uzZE3feeWdERIwfPz7Gjx8fjY2NsXbt2vjXf/3XmDNnTgwcODCuuuqqI3zH0HEZrIIsmjdvXnz/+9+PmTNnxsMPPxxdu3Y95P6nnnpqnHzyyfHUU0/Fl770paxkuOiii6KsrCyeffbZA97irzX2/tC692pYe911112HnWvfK2sdbo4kSeKrX/3qIb9m1KhR8c///M+xdOnSjNsyAEC+lZSURFlZWZSWljZv+/Of/xz/9m//dsD9n3nmmXjqqacybjf0zW9+M/r06RPnnHNORER84AMfiC996Utx9NFHx4gRI3L7BgAgyzpiN7YmQ0lJSXTr1i3jl8Dbt2+P//iP/8g4VnV1dSxYsCCWLFkS8+fPf9tzT5s2LXr16hUf/vCHY/fu3XH//fdnrB0A5MNxxx0Xn/3sZ+PXv/51xh/ivtUHPvCBeOihh6KxsTHe9a53HfJ4B7uDQGv7FACKRVt/t7yvw/2d7imnnBKf+9zn4tvf/vYBf5daWloa73rXu+K0006Lb3zjG/Hzn//cYBWdmsEqyKJ3v/vdkU6n45Of/GScc8458Td/8zdxxhlnRJcuXWLbtm3x7W9/OyIi49Z7d911V1RXV8dFF10U11xzTRx33HHx4osvxq9+9av4+c9/Ht/61rfalOH444+P+fPnx4033hi///3v4y//8i/jHe94R7zwwgvx5JNPRq9eveLmm28+5DFOO+20OPHEE+OGG26IJEmiX79+8Z//+Z+xatWqti/K/3PmmWdGRMTtt98e06ZNi65du8app556yL9UuvDCC6Nbt27xf/7P/4nrr78+XnvttViyZEn86U9/ytjve9/7XixevDguv/zyOOGEEyJJknjkkUfipZdeigsvvPCwMwPAkXr/+98fCxcujA9/+MPxN3/zN7Fz5874//6//2+/H3T3Gjx4cFx66aXxhS98IQYNGhRf//rXY9WqVfHlL385evbsGRERc+bMiW9/+9vxnve8Jz71qU/FWWedFU1NTbF58+b44Q9/GJ/+9Kff9kNsACiUjtiNrcnwgQ98IB555JGYNWtWfOhDH4otW7bEF7/4xRg0aFD87ne/az7W+PHjY+rUqXHLLbfECy+8EB/4wAeivLw81q9fHz179oxPfvKT+53/Qx/6UPTs2TM+9KEPxZ///Od48MEHo1u3bof9fgDgcPzTP/3TIV+/6qqr4hvf+EZcfPHFMXv27HjnO98ZXbt2jeeffz4effTRuOyyy2Ly5MkR0XL3gWXLlsUJJ5wQ3bt3jzPPPLPVfQoAxeJwfrf8VuPGjYt3vOMdMXPmzLjpppuia9eu8Y1vfCOeeuqpjP1+8YtfxCc+8Ym48sor4+STT45u3brFT37yk/jFL37RfOefO++8M37yk5/E+9///hg2bFi89tprzVeGft/73pfDVYD2z2AVZNnMmTPj3HPPjdtvvz3++Z//ObZu3RolJSUxZMiQGDduXPz4xz+OCy64oHn/iRMnxpNPPhn/+I//GHPmzIk//elPcfTRR0dlZWX81V/91WFlmDdvXlRWVsbtt98eDz74YDQ0NMSxxx4bVVVVMXPmzLf9+q5du8Z//ud/xuzZs+PjH/94lJWVxfve97740Y9+FMOGDTusTBMmTIh58+bF/fffH1/96lejqakpHn300ZgwYcJBv+a0006Lb3/72/G5z30urrjiijj66KPjwx/+cMydOzeqq6ub9zv55JPjqKOOiltvvTW2bt0a3bp1i1NPPXW/WxkCQL5dcMEFce+998aXv/zluOSSS+K4446Lv/7rv44BAwY039bnrUaPHh3XXntt3HTTTfG73/0uBg8eHAsXLoxPfepTzfv06tUr1qxZE//0T/8Ud999d2zatCl69OgRw4YNi/e9731x/PHH5/EdAkDbdMRubE2Ga6+9Nurq6uLOO++Me++9N0444YS44YYb4vnnn9/vj5uWLl0a55xzTtxzzz2xdOnS6NGjR1RWVsbf//3fHzTDxRdfHCtWrIhLLrkkLrvssnjkkUeiR48eR/S+ACCbSktL47vf/W7cfvvt8W//9m+xYMGCKCsriyFDhsT555/f/Ie3ERE333xzbNu2Lf76r/86XnnllRg+fHj84Q9/aFOfAkCxaOvvlt/q6KOPjv/7f/9vfPrTn46PfvSj0atXr7jsssti2bJlzVdYjog49thj48QTT4zFixfHli1boqSkJE444YT4yle+0vwHPqNHj44f/vCHcdNNN8X27dujd+/eMXLkyPjud78bkyZNystaQHtVkiRJUugQAADQ2R1//PExcuTI+N73vlfoKADQLrSHbmwPGQAAAAAonC6FDgAAAAAAAAAAANDeGKwCAAAAAAAAICdeeeWVqKqqitGjR8eZZ54ZX/3qVwsdCQBaza0AAQAAAAAAAMiJxsbGaGhoiJ49e8arr74aI0eOjJqamjj66KMLHQ0A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XRuvv/56zrLm2uGsxb6amprilVdeiX79+uUiYt4c7lrcd9998eyzz8ZNN92U64h5dTjr8d3vfjfGjh0bt956axx33HFxyimnxGc+85n485//nI/IOXM4azFu3Lh4/vnnY8WKFZEkSbzwwgvx8MMPx/vf//58RG5XivX7J62jc1vo3Ex6t4XObaFzj0yxfv+kdXRuC52bSee20LktdO6RKdbvn7SOzm2hczPp3BY6t4XOPTLF+v2T1tG5LXRuJp3bQue20LlHpli/fx5MWaEDdAQ7duyIxsbGGDhwYMb2gQMHxvbt2w/4Ndu3bz/g/m+88Ubs2LEjBg0alLO8uXQ4a7Gvr3zlK7F79+74q7/6q1xEzJvDWYvf/e53ccMNN8SaNWuirKy4/u93OOvx+9//Pn72s59F9+7d4zvf+U7s2LEjZs2aFS+++GKHvi/v4azFuHHj4hvf+EZMmTIlXnvttXjjjTfi0ksvjX/913/NR+R2pVi/f9I6OreFzs2kd1vo3BY698gU6/dPWkfnttC5mXRuC53bQucemWL9/knr6NwWOjeTzm2hc1vo3CNTrN8/aR2d20LnZtK5LXRuC517ZIr1++fBuGJVG5SUlGQ8T5Jkv21vt/+BtndEbV2LvR588MH4whe+EMuWLYsBAwbkKl5etXYtGhsb48Mf/nDcfPPNccopp+QrXt615d9GU1NTlJSUxDe+8Y145zvfGRdffHEsXLgwli5d2uGnnCPatha1tbVx3XXXxec///lYt25d/OAHP4hNmzbFzJkz8xG13Snm75+0js5toXMz6d0WOreFzj18xfz9k9bRuS10biad20LnttC5h6+Yv3/SOjq3hc7NpHNb6NwWOvfwFfP3T1pH57bQuZl0bgud20LnHr5i/v65r+IZr8yh/v37R2lp6X6TiXV1dftN4e117LHHHnD/srKyOProo3OWNdcOZy32WrZsWUyfPj2+9a1vxfve975cxsyLtq7FK6+8EmvXro3169fHJz7xiYh4s4iSJImysrL44Q9/GBdccEFesufC4fzbGDRoUBx33HFRUVHRvO3000+PJEni+eefj5NPPjmnmXPlcNZiwYIF8e53vzs++9nPRkTEWWedFb169Yrx48fHLbfcUnRTvYdSrN8/aR2d20LnZtK7LXRuC517ZIr1+yeto3Nb6NxMOreFzm2hc49MsX7/pHV0bgudm0nnttC5LXTukSnW75+0js5toXMz6dwWOreFzj0yxfr982BcsaoVunXrFmPGjIlVq1ZlbF+1alWMGzfugF9z7rnn7rf/D3/4wxg7dmx07do1Z1lz7XDWIuLNyeZrrrkmvvnNbxbNPUbbuhZ9+/aNp59+OjZs2ND8mDlzZpx66qmxYcOGeNe73pWv6DlxOP823v3ud8fWrVtj165dzdt++9vfRpcuXWLIkCE5zZtLh7MWr776anTpkvktubS0NCJapns7i2L9/knr6NwWOjeT3m2hc1vo3CNTrN8/aR2d20LnZtK5LXRuC517ZIr1+yeto3Nb6NxMOreFzm2hc49MsX7/pHV0bgudm0nnttC5LXTukSnW758HldAqDz30UNK1a9fknnvuSWpra5M5c+YkvXr1Sv7whz8kSZIkN9xwQzJ16tTm/X//+98nPXv2TD71qU8ltbW1yT333JN07do1efjhhwv1FrKmrWvxzW9+MykrK0vS6XSybdu25sdLL71UqLeQNW1di33ddNNNyahRo/KUNvfauh6vvPJKMmTIkORDH/pQ8swzzySPPfZYcvLJJyczZswo1FvImrauxX333ZeUlZUlixcvTp599tnkZz/7WTJ27Njkne98Z6HeQta88soryfr165P169cnEZEsXLgwWb9+ffLcc88lSdK5vn/SOjq3hc7NpHdb6NwWOreFzqWtdG4LnZtJ57bQuS10bgudS1vp3BY6N5PObaFzW+jcFjqXttK5LXRuJp3bQue20LktdO6hGaxqg3Q6nQwfPjzp1q1bcs455ySPPfZY82vTpk1Lzj///Iz9f/rTnyZnn3120q1bt+T4449PlixZkufEudOWtTj//POTiNjvMW3atPwHz4G2/rt4q2Iq4b3auh6/+tWvkve9731Jjx49kiFDhiRz585NXn311Tynzo22rsW//Mu/JJWVlUmPHj2SQYMGJR/5yEeS559/Ps+ps+/RRx895PeAzvb9k9bRuS10bia920LnttC5b9K5HA6d20LnZtK5LXRuC537Jp3L4dC5LXRuJp3bQue20Llv0rkcDp3bQudm0rktdG4LnfsmnXtoJUnSya5JBgAAAAAAAAAA8Da6vP0uAAAAAAAAAAAAnYvBKgAAAAAAAAAAgH0YrAIAAAAAAAAAANiHwSoAAAAAAAAAAIB9GKwCAAAAAAAAAADYh8EqAAAAAAAAAACAfRisAgAAAAAAAAAA2IfBKgAAAAAAAAAAgH0YrAIAAAAAAAAAANiHwSoAAAAAAAAAAIB9GKwCKJBNmzbFxIkTo7KyMs4888zYvXt3oSMBQFHSuQCQHzoXAPJD5wJAfuhcIiJKkiRJCh0CoDM6//zz45Zbbonx48fHiy++GH379o2ysrJCxwKAoqNzASA/dC4A5IfOBYD80LlEuGIV5FWSJPE3f/M30a9fvygpKYkNGzbst8+ECRNizpw5ec/WWvvmy1feQq3LhAkToqSk5KD/ex2uZ555Jrp27Rrjx4+PiIh+/fo1l/A111zTfM7ly5dn7ZwAnYnOzd5580XnAnRMOjd7580XnQvQMenc7J03X3QuQMekc7N33nzRueSawSrIox/84AexdOnS+N73vhfbtm2LkSNHFjrSEXvkkUfii1/8YqFj5NRf//VfZ/zvtXr16rjkkkti8ODBhyzLxYsXx4gRI6J79+4xZsyYWLNmTfNrv/vd76J3795x6aWXxjnnnBNf+tKXml+7/fbbY9u2bTl9TwDFTud2TDoXoOPRuR2TzgXoeHRux6RzAToendsx6VxyyWAVncqePXsKev5nn302Bg0aFOPGjYtjjz22KC4T2K9fv+jTp0+hY+RUz549M/732r17d4waNSruuOOOg37NsmXLYs6cOXHjjTfG+vXrY/z48VFdXR2bN2+OiIjXX3891qxZE+l0Ov7rv/4rVq1aFatWrYqIiIqKijj22GNz/8YAckjnZp/OPTCdC3R2Ojf7dO6B6Vygs9O52adzD0znAp2dzs0+nXtgOpfWMlhFh/aDH/wgzjvvvDjqqKPi6KOPjg984APx7LPPNr8+YcKE+MQnPhFz586N/v37x4UXXhgRb17C8dZbb40TTjghevToEaNGjYqHH3641cc9kIaGhrjuuutiwIAB0b179zjvvPOipqam+fVrrrkmPvnJT8bmzZujpKQkjj/++IMe64033ohPfOITzef/3Oc+F0mStPpcxx9/fCxatCjjmKNHj44vfOELGWtz3XXXxfXXXx/9+vWLY489NuP1iDcL5+qrr47evXvHoEGD4itf+cp+WQ90KclDHfeVV16Jj3zkI9GrV68YNGhQ/PM//3OrLwt5qHV5uzX53//93zj22GMzJon/53/+J7p16xY//OEP3/bcb1VdXR233HJLXHHFFQfdZ+HChTF9+vSYMWNGnH766bFo0aIYOnRoLFmyJCIihgwZElVVVTF06NAoLy+Piy++OKuXpgTINp2rc3UuQH7oXJ2rcwHyQ+fqXJ0LkB86V+fqXDo6g1V0aLt37465c+dGTU1N/PjHP44uXbrE5MmTo6mpqXmf+++/P8rKyuLxxx+Pu+66KyIiPve5z8V9990XS5YsiWeeeSY+9alPxUc/+tF47LHHWn3cfV1//fXx7W9/O+6///74+c9/HieddFJcdNH/z97fx2lV0Pnj/3uYgUHuRpEbQW7EyJsRuRGo8KZAV3RUUiwXt0JU2JXm6iM2WslameRGyYrYeoHRjWSbQvZLdrdoaXZTMelmQLB02gpDQQEnMR2HEnDm/P7wy9DF7QxcM9fcPJ+Px/xxznWuc17XiXzNmXnPORfFa6+9FhHv3A5wzpw5MWDAgNi6dWtGSexrT+Zf/vKX8dWvfjXuueee+MY3vtHgYzXUt7/97ejatWv88pe/jLvuuivmzJlTP2UbEfGpT30qHnvssXj00UfjJz/5STz++OOxdu3ao9pvWVlZPPXUU/Gf//mfUV5eHk8++WQ8/fTTDc57sPNyuHPSu3fv+Na3vhVf+MIXYs2aNVFTUxMf+9jHorS0NCZOnNio83Y4u3btirVr1+6334kTJ8bq1asjImLs2LHxyiuvxJ///Oeoq6uLVatWxemnn57VHADZpHN1rs4FaB46V+fqXIDmoXN1rs4FaB46V+fqXFq9BNqQqqqqJCKS3/zmN0mSJMkHPvCBZOTIkRnb1NTUJJ07d05Wr16dsX769OnJP/zDPzRov/uqqalJOnbsmHz3u9+tX7dr166kf//+yV133VW/7p577kkGDx58yM/wgQ98IDn99NOTurq6+nWf+cxnktNPP73Bxxo8eHByzz33ZOx3xIgRye23355xnHPPPTdjm7Fjxyaf+cxnkiRJkjfffDPp1KlTsnTp0vrXt2/fnhxzzDHJrFmzMvaz7/LB9ltdXZ107NgxeeSRR+pfe/3115MuXbpk7KOx56Wh5z9JkqS0tDQ55ZRTko9+9KPJsGHDkr/+9a+HPe6hskVE8uijj2ase/nll5OISJ566qmM9f/yL/+SnHLKKfXLK1asSIYNG5acccYZySc/+ckG7RugpdC5Ovdg52QPnQuQHTpX5x7snOyhcwGyQ+fq3IOdkz10LkB26Fyde7BzsofOpaVxxypateeffz4+8pGPxMknnxw9evSIIUOGRETUP/c0ImLMmDEZ76msrIy33norLrzwwujWrVv914MPPlh/e8iG7HffHLt3745zzjmnfl3Hjh3jPe95T/z2t79t9Od63/veF3l5efXL48aNiz/84Q9RW1ub1WMNHz48Y7lfv35RVVVV/5l27doV48aNq3+9Z8+eceqppx7xfv/4xz/G7t274z3veU/9a0VFRQ3aZ8TBz8uGDRsafE7+9V//Nd5+++343ve+F9/97nejc+fODTr2kfjbrBHv3LL0b9eVlJTEb37zm3j22Wdj/vz5TZYDIBt07tEdS+fqXICG0rlHdyydq3MBGkrnHt2xdK7OBWgonXt0x9K5OpfcK8h1ADgakyZNioEDB8bXv/716N+/f9TV1cWwYcNi165d9dt07do14z17bv/4ox/9KE488cSM1woLCxu837+V/H/PhT3cf3izoSHH6tChQ8YzfCMidu/evd++OnbsmLGcl5dXf372fX9jHGy/h8qeDQ05/3/84x9jy5YtUVdXFy+++OJ+3zRkQ69evSI/Pz+2bduWsb6qqir69u2b9eMBNAede+Bj6dy9+9W5ANmhcw98LJ27d786FyA7dO6Bj6Vz9+5X5wJkh8498LF07t796lxaOnesotXavn17/Pa3v43PfvazccEFF8Tpp58ef/7znw/7vuLi4igsLIxNmzbF0KFDM74GDhx4RPsdOnRodOrUKX72s5/Vr9u9e3esWbPmiJ6z+otf/GK/5Xe/+92Rn5/foGP17t07tm7dWv96dXV1bNy4sVEZhg4dGh07dszI8uc//zl+//vfN/rz7PGud70rOnbsGL/61a8ysv3hD39o0PsPdl4aev537doVH/3oR2PKlClx5513xvTp0+OVV1454s9zMJ06dYrRo0dnPN84IqK8vDzOPvvsrB8PoKnpXJ2rcwGah87VuToXoHnoXJ2rcwGah87VuTqXtsAdq2i1jjvuuDj++ONj8eLF0a9fv9i0aVPceuuth31f9+7d45ZbbolPfvKTUVdXF+eee25UV1fH6tWro1u3bjF16tRG77dr167x8Y9/PD71qU9Fz549Y9CgQXHXXXfFX/7yl5g+fXqjP9vmzZujrKwsbrjhhnj66afj3/7t3+Luu+9u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b7wxPvrRj8ZZZ52V8QPjiHd+MfrhD384ioqK6m+HXFhYGN26dYvrr78+Fi9eHHfddVcUFRXVv2fhwoXRqVOnuP766zP2df3118ell14a3/nOd2LHjh3RsWPH+PjHPx6LFy+OT3ziE3HZZZfFCy+8EJ/73Ofi8ccfj6effjp69eoVjz76aKTT6fjmN78Z//3f/x1FRUUxYMCAiIgGvT8i4vnnn49x48bFjBkzoqioKF544YWYP39+nHvuufGb3/xmv8/993//9/Gxj30sbrjhhigvL4+77rordu/eHf/zP/8TpaWlccstt8RDDz0Un/nMZ2Lo0KFx5ZVXZv1/HwDarsNdm0a03uvT9evXx0033RRf+MIX4oQTTojvfve7MWvWrNi1a1fccsstERGxc+fOeO211+KWW26JE088MXbt2hX/8z//E1deeWU88MADcc0110TEOwPUkyZNijVr1sQXvvCFOOuss+LnP/95XHzxxQ080wDQcLW1tfH2229HkiTxyiuvxLx582LHjh3xkY98pH6bF154IW644YYYNGhQRET84he/iP/3//5fvPzyy/H5z38+Ig5+Hf2ud70rHnjggbjuuuvis5/9bFx66aUREfXXt4899lhcfPHF8d73vjfuv//+KCoqiqVLl8aUKVPiL3/5y36D0PteY5999tkxatSoSKfT8Q//8A8Z2953330xduzY+sEuAGhqtbW18dOf/jRGjx4dAwcObPT733rrrZgwYUI8//zzcccdd8Tw4cPjySefjLlz58b69evjRz/6UUS8c1es888/PyZMmBCf+9znIiL2e9z9jBkz4tJLL42HHnooNm/eHJ/61KfiYx/7WPz0pz89bI50Oh2nnXZaLFiwICLeuca+5JJLYuPGjfU/E1+8eHHccMMN8aEPfSjuueeeeOONN+KOO+7IGACDNiUB2oQHHnggiYjkF7/4RbJ79+7kzTffTH74wx8mvXv3Trp3755s27Yteeyxx5KISB577LGD7uftt99Oampqkq5duyb33ntv/fphw4YlV1xxxSEzpFKp5GD/WRk8eHAybdq0+uU5c+YkEZGUl5c36nMCwNF49dVXk3PPPTeJiCQiko4dOyZnn312Mnfu3OTNN9+s3+6MM85IPvCBD+z3/ueffz7p0KFDcs8999Sv++tf/5ocf/zxyXXXXVe/bk8vX3PNNRnv/+1vf5tERFJaWpqx/pe//GUSEck///M/16+7/fbbk4hI/vSnPx3R+/9WXV1dsnv37uTFF19MIiL5j//4j/2Oc/fdd2e8Z+TIkUlEJD/4wQ/q1+3evTvp3bt3cuWVVx7wOADQkGvTJEnazPXp4MGDk7y8vGT9+vUZ6y+88MKkR48eyY4dOw74vrfffjvZvXt3Mn369GTUqFH163/0ox8lEZEsWrQoY/u5c+cmEZHcfvvtjc4IAPva09f7fhUWFiYLFy486Ptqa2uT3bt3J3PmzEmOP/74pK6urv61g11HV1RUJBGRPPDAA/u9dtpppyWjRo1Kdu/enbH+sssuS/r165fU1tZm5N33GvtvX1u3bl39ul/96ldJRCTf/va3D3MmACB7tm3blkREcvXVVzdo+w984AMZ3Xn//fcnEZF873vfy9juK1/5ShIRyU9+8pP6dV27ds24rt1jTy/u+/Pju+66K4mIZOvWrQc9/saNG5OISM4888zk7bffrl+/p1cffvjhJEne+X7ghBNOSN773vdmHOPFF19MOnbsmAwePLhBnx9aE48ChDbmfe97X3Ts2DG6d+8el112WZxwwgnx4x//OPr27XvA7WtqaurvPFFQUBAFBQXRrVu32LFjR/z2t7+t3+4973lP/PjHP45bb701Hn/88fjrX/96VDl//OMfxymnnBJ/93d/d1T7AYDGOP744+PJJ5+MioqK+PKXvxyXX355/P73v4/Zs2fHmWeeud9j8vZ18sknx2WXXRYLFy6sf+zBQw89FNu3b49PfOIT+23/oQ99KGP5sccei4jY769u3/Oe98Tpp58e//u//3vI4zfm/VVVVTFz5swYOHBgFBQURMeOHWPw4MERERkdv8dll12WsXz66adHXl5elJSU1K8rKCiIoUOHHvIxTgAQ0fhr04jWe316xhlnxIgRIzLWfeQjH4nq6up4+umn69c98sgjcc4550S3bt3qu/mb3/xmxmd74oknIuKdO0n+rX3vwgEA2fDggw9GRUVFVFRUxI9//OOYNm1apFKpuO++++q3+elPfxp/93d/F0VFRZGfnx8dO3aMz3/+87F9+/aoqqo64mNv2LAh/u///i8++tGPRkTE22+/Xf91ySWXxNatW+N3v/tdxnv2vcaOeKcj+/TpU/8Y4IiIf/u3f4vevXvHlClTjjgfADS3n/70p9G1a9f48Ic/nLF+z8+CD/ez47+17+MGhw8fHhHRoJ/rXnrppZGfn3/Q9/7ud7+Lbdu27XfdOmjQoDjnnHManBFaE48ChDbmwQcfjNNPPz0KCgqib9++B33UwR4f+chH4n//93/jc5/7XIwdOzZ69OgReXl5cckll2T8cPqrX/1qDBgwIJYtWxZf+cpXonPnznHRRRfFvHnz4t3vfnejc/7pT3+qv300ADS3MWPGxJgxYyIiYvfu3fGZz3wm7rnnnrjrrrvirrvuOuR7Z82aFRdccEGUl5fHxIkTI51Ox7hx4+Kss87ab9t9e3jPI4oO1M/9+/c/7IVtQ99fV1cXEydOjC1btsTnPve5OPPMM6Nr165RV1cX73vf+w74C+iePXtmLHfq1Cm6dOkSnTt33m99dXX1IXMCQGOvTSNa7/XpCSeccNB1e7r7Bz/4Qfz93/99XHXVVfGpT30qTjjhhCgoKIhFixbFt771rfr3bd++PQoKCvbr5UMNpAHAkTr99NPrr40jIi6++OJ48cUX49Of/nR87GMfi9///vcxceLEGD9+fHz961+PAQMGRKdOnWL58uXxL//yL0c13PzKK69ERMQtt9xS/+jcfe37x08H+n6isLAwbrjhhrj77rtj3rx5sXv37vje974XZWVlUVhYeMT5AKCxevXqFV26dImNGzce0fu3b98eJ5xwQuTl5WWs79OnTxQUFNRfXzbE8ccfn7G8pxMb0t2He++eHAe6Tu3bt+8Rf35oyQxWQRuz78Xwobzxxhvxwx/+MG6//fa49dZb69fv3LkzXnvttYxtu3btGnfccUfccccd8corr9T/dfCkSZPi//7v/xqds3fv3vHSSy81+n0AkG0dO3aM22+/Pe6555549tlnD7v9+eefH8OGDYv77rsvunXrFk8//XT8+7//+wG33fcieM9F6datW2PAgAEZr23ZsiV69ep1yGM39P3PPvtsPPPMM7FkyZKYNm1a/TYbNmw47OcDgGxozLVpROu+Pt22bdtB1+3p7n//93+PIUOGxLJlyzK+P9i5c2fG+44//vh4++2347XXXssYrjrQMQCgKQwfPjxWrlwZv//972Pp0qXRsWPH+OEPf5jxRzfLly8/6uPsuX6dPXt2XHnllQfc5tRTT81Y3vcae4+Pf/zj8eUvfzm+9a1vxVtvvRVvv/12zJw586gzAkBj5OfnxwUXXBA//vGP46WXXtrv57eHc/zxx8cvf/nLSJIko/Oqqqri7bffPuzPjpvLnuvcPUPSf8u1K22VRwFCO5aXlxdJkuz3lzvf+MY3ora29qDv69u3b1x77bXxD//wD/G73/0u/vKXv0RE46adS0pK4ve//3389Kc/PYpPAACNs3Xr1gOu3/MInv79+0fEO512qD678cYb40c/+lHMnj07+vbtG1dddVWDjn/++edHROw3iFVRURG//e1v44ILLsjK+/dceO/b8V/72tcalBMAmltrvj597rnn4plnnslY99BDD0X37t3r72iZl5cXnTp1yvjh+LZt2+I//uM/Mt73gQ98ICIili1blrF+6dKlR5QNABpr/fr1EfHO4HFeXl4UFBRkPA7or3/9a3znO9/Z730Hu44+WCefeuqp8e53vzueeeaZ+rtK7/vVvXv3BmXu169fXHXVVbFw4cK4//77Y9KkSZ6WAEBOzJ49O5IkiX/8x3+MXbt27ff67t2747/+678O+N4LLrggampq9htgfvDBB+tf3+NwP79uSqeeemqccMIJ8b3vfS9j/aZNm2L16tU5yQRNzR2roB3r0aNHvP/974958+ZFr1694qSTToonnngivvnNb8axxx6bse173/veuOyyy2L48OFx3HHHxW9/+9v4zne+E+PGjYsuXbpERMSZZ54ZERFf+cpXoqSkJPLz82P48OHRqVOn/Y590003xbJly+Lyyy+PW2+9Nd7znvfEX//613jiiSfisssuiwkTJjT55weg/bnoootiwIABMWnSpDjttNOirq4u1q9fH3fffXd069YtZs2aFRHvdNrSpUtj2bJlcfLJJ0fnzp3rey4i4mMf+1jMnj07Vq1aFZ/97GcP2HUHcuqpp8Y//dM/xb/9279Fhw4doqSkJF544YX43Oc+FwMHDoxPfvKTWXn/aaedFu9617vi1ltvjSRJomfPnvFf//VfUV5efoRnDgCaVmu+Pu3fv3988IMfjC984QvRr1+/+Pd///coLy+Pr3zlK/V5LrvssvjBD34QpaWl8eEPfzg2b94cX/ziF6Nfv37xhz/8oX5fF198cZxzzjlx8803R3V1dYwePTp+/vOf1/8gvUMHfyMJQPY8++yz8fbbb0fEO4/1+cEPfhDl5eUxefLkGDJkSFx66aUxf/78+MhHPhL/9E//FNu3b49//dd/PeAj9g52Hf2ud70rjjnmmPjud78bp59+enTr1i369+8f/fv3j6997WtRUlISF110UVx77bVx4oknxmuvvRa//e1v4+mnn45HHnmkwZ9l1qxZ8d73vjciIh544IHsnCAAaKRx48bFokWLorS0NEaPHh0f//jH44wzzojdu3fHunXrYvHixTFs2LCYNGnSfu+95pprIp1Ox7Rp0+KFF16IM888M372s5/Fl770pbjkkkvi7/7u7+q3PfPMM+Pxxx+P//qv/4p+/fpF9+7d97vTY1Pp0KFD3HHHHXHDDTfEhz/84bj++uvj9ddfjzvuuCP69evnupW2KQHahAceeCCJiKSiouKg2zz22GNJRCSPPfZY/bqXXnop+dCHPpQcd9xxSffu3ZOLL744efbZZ5PBgwcn06ZNq9/u1ltvTcaMGZMcd9xxSWFhYXLyyScnn/zkJ5NXX321fpudO3cmM2bMSHr37p3k5eUlEZFs3LgxSZJkv/0lSZL8+c9/TmbNmpUMGjQo6dixY9KnT5/k0ksvTf7v//4vG6cEAPazbNmy5CMf+Ujy7ne/O+nWrVvSsWPHZNCgQcnUqVOTysrK+u1eeOGFZOLEiUn37t2TiEgGDx68376uvfbapKCgIHnppZf2e+1QvVxbW5t85StfSU455ZSkY8eOSa9evZKPfexjyebNmzO2u/3225OISP70pz8d0fsrKyuTCy+8MOnevXty3HHHJVdddVWyadOmJCKS22+//bDHmTZtWtK1a9f98n/gAx9IzjjjjP3WA0CSNOzaNEnazvXp4MGDk0svvTT5/ve/n5xxxhlJp06dkpNOOimZP3/+ftt++ctfTk466aSksLAwOf3005Ovf/3r9T38t1577bXkuuuuS4499tikS5cuyYUXXpj84he/SCIiuffeew+ZBwAaYk9f/+1XUVFRMnLkyGT+/PnJW2+9Vb/tt771reTUU0+t79y5c+cm3/zmNzO6NUkOfR398MMPJ6eddlrSsWPH/a5Jn3nmmeTv//7vkz59+iQdO3ZMTjjhhOT8889P7r///v3yHu77i5NOOik5/fTTj/r8AMDRWr9+fTJt2rRk0KBBSadOnZKuXbsmo0aNSj7/+c8nVVVVSZK883PWD3zgAxnv2759ezJz5sykX79+SUFBQTJ48OBk9uzZGd28Z//nnHNO0qVLlyQi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6ygAcFBtoXMBoDXQuQDQfPQuADQ9fQsA2dPowaorrrgiYzkvLy969+4d559/ftx9993ZytWkUqlUpFKpqK6ujqKiolzHAYADagudCwCtgc4FgOajdwGg6elbAMieRg9W1dXVNUUOAGAfOhcAmofOBYDmo3cBoOnpWwDIng65DgAAAADstXnz5hg/fnwUFxfH8OHD45FHHsl1JAAAAACAdqnRd6wqKytr8Lbz589v7O4BgP+PzgWA5tHSOregoCAWLFgQI0eOjKqqqjjrrLPikksuia5duzb5sQGgqbW03gWAtkjfAkeqd7fCqK1LIr9DXq6jQIvR6MGqdevWxdNPPx1vv/12nHrqqRER8fvf/z7y8/PjrLPOqt8uL8//0QDgaOhcAGgeLa1z+/XrF/369YuIiD59+kTPnj3jtddeM1hFi9FSfsjaEjIAjdfSehcA2iJ9CxypHscURH6HvJi1dF1sqKrJWY7xp/aOT110Ws6OD3+r0YNVkyZNiu7du8e3v/3tOO644yIi4s9//nNcd911cd5558XNN9+c9ZAA0B7pXABoHtnu3FWrVsW8efNi7dq1sXXr1nj00UfjiiuuyNhm4cKFMW/evNi6dWucccYZsWDBgjjvvPP229eaNWuirq4uBg4ceMSfD7KtJfyQdWifbnHv1aNycmzg6LjWBYCmp2+Bo7Whqiae21Kds+O/q7c/MKTlaPRg1d133x0/+clP6ks4IuK4446LO++8MyZOnNgqijidTkc6nY7a2tpcRwGAg2oLnQsArUG2O3fHjh0xYsSIuO666+JDH/rQfq8vW7Ysbrrppli4cGGcc8458bWvfS1KSkqisrIyBg0aVL/d9u3b45prrolvfOMbR/7hoAnl+oesQOvkWhcAmp6+BYDs6dDYN1RXV8crr7yy3/qqqqp48803sxKqqaVSqaisrIyKiopcRwGAg2oLnQsArUG2O7ekpCTuvPPOuPLKKw/4+vz582P69OkxY8aMOP3002PBggUxcODAWLRoUf02O3fujMmTJ8fs2bPj7LPPPuTxdu7cGdXV1RlfANBSudYFgKbXEvt28uTJcdxxx8WHP/zhnBwfAI5UowerJk+eHNddd118//vfj5deeileeuml+P73vx/Tp08/6A+NAYDGawudm06no7i4OMaOHZvrKABwUM3Zubt27Yq1a9fGxIkTM9ZP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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(2, 5, figsize=(24, 8), tight_layout=True)\n", + "\n", + "for i, ax in enumerate(axes.flat):\n", + " ax.hist(globals()['class{}'.format(i)]['area'], bins=np.arange(0, 1.2, 0.1), ec='white')\n", + " ax.set_xlabel('area of bounding box [$10^6$]')\n", + " ax.set_ylabel('frequency')\n", + " ax.set_yscale('log')\n", + " ax.set_title(num_to_label(i))" + ] + }, + { + "cell_type": "markdown", + "id": "8daec77e-9836-4f62-a161-d59852556050", + "metadata": {}, + "source": [ + "- 클래스 별 area 분포가 대체로 비슷하지만 특이사항을 꼽자면 배터리의 area가 작고, 의류가 크다." + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "id": "bdb77983-0ff1-4340-98d0-b4952224cf65", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/var/folders/f6/gvt0g3yj5239r97qmydnwbsc0000gn/T/ipykernel_47732/1858640043.py:4: SettingWithCopyWarning: \n", + "A value is trying to be set on a copy of a slice from a DataFrame.\n", + "Try using .loc[row_indexer,col_indexer] = value instead\n", + "\n", + "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", + " globals()['class{}'.format(i)]['ratio'] = ratio_list\n" + ] + } + ], + "source": [ + "# 클래스 별 bbox의 가로세로 비\n", + "for i in range(10):\n", + " ratio_list = globals()['class{}'.format(i)]['height']/globals()['class{}'.format(i)]['width']\n", + " globals()['class{}'.format(i)]['ratio'] = ratio_list" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "id": "9f5a834a-03da-436a-a0b6-698e1d00b824", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAACVYAAAMWCAYAAAAQq0+DAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzde3iU5Z0//k9IICCHKHKWg1RbbUQBIduCUsFusfFYPBTWXUSLu2WZtlCsXVm7VdluaeuWxa2Dh1bFHray9cD227pLsR5wZbsGBLWmB7EoKmAWrB3BGjR5fn/wIzVAIIHJzCR5va4r19V55p5nPs/cdd5M5pP7LkqSJAkAAAAAAAAAAAAadMp3AQAAAAAAAAAAAIVGYxUAAAAAAAAAAMBeNFYBAAAAAAAAAADsRWMVAAAAAAAAAADAXjRWAQAAAAAAAAAA7EVjFQAAAAAAAAAAwF40VgEAAAAAAAAAAOxFYxUAAAAAAAAAAMBeNFYBAAAAAAAAAADsRWMVtIJnnnkmZs6cGccdd1x069YtunXrFu9///vj05/+dKxZsybf5WXV0qVLo6ioKF588cUDjlu9enVcf/318cYbb+Skrv0pKiqKz3zmM3l7fgDanz05uOenpKQkBg8eHFdccUW8+uqr+S4PAHJKLmbPscceG+eee26+ywCgg3pvpj/66KP73J8kSRx//PFRVFQUEydObPH5lyxZEkuXLj2sGo899ti4/PLLD+scAFAomvvd8vXXXx9FRUV5rBQ6ppJ8FwDtzW233Raf+cxn4oQTTog5c+bESSedFEVFRfGrX/0qfvjDH0ZFRUVs2LAhjjvuuHyXmlOrV6+OG264IS6//PI48sgj810OAGTVXXfdFSeeeGL88Y9/jFWrVsXChQvjsccei2effTa6d++e7/IAIKfkIgC0Dz179ow77rhjn+apxx57LF544YXo2bPnIZ13yZIl0adPH41RABC+W4a2QGMVZNETTzwRs2fPjnPOOSfuvffe6NKlS8N9Z555ZqRSqfjRj34U3bp1y2OVB/bWW2/FEUccke8y4o9//GNBv04A8F4jRoyIsWPHRkTEpEmToq6uLv7xH/8xli9fHn/5l3+Z5+r+JEmSePvtt2UsAK2qreRihGwEgAOZOnVq/OAHP4h0Oh29evVqOH7HHXfEuHHjIpPJ5LE6AGj72sN3y9AR2AoQsuirX/1qFBcXx2233dYo+N7rkksuiUGDBjU6tmbNmjj//POjd+/e0bVr1xg9enT8+7//e6Mxe5ZffuSRR+Jv//Zvo0+fPnH00UfHhRdeGJs3b97neZYtWxbjxo2L7t27R48ePeKss86KdevWNRpz+eWXR48ePeLZZ5+NyZMnR8+ePeOjH/1oRESsXLkyLrjgghg8eHB07do1jj/++Pj0pz8d27Zta/Hrcv3118fVV18dERHDhw/fZxnpPVsc3H///TF69Ojo2rVr3HDDDRERkU6n4yMf+Uj069cvunfvHieffHJ84xvfiHfeeafRc6xbty7OPffc6NevX5SWlsagQYPinHPOiVdeeWWfer73ve/FBz/4wTjiiCNi5MiR8ZOf/KTF1wQAB/LhD384IiJeeumluOGGG+JDH/pQ9O7dO3r16hWnnnpq3HHHHZEkSaPH7MnDBx54IE455ZTo2rVrvO9974t//dd/3ef8mUwmvvCFL8Tw4cOjS5cuccwxx8TcuXNj586djcbt2Qb31ltvjQ9+8INRWload999d+tdOADsx3tzMSLaVDY2t4a33347rrrqqhg1alSUlZVF7969Y9y4cfEf//Ef+5yzvr4+vvWtb8WoUaOiW7duceSRR8aHP/zh+PGPf3zA13HJkiVRUlIS11133QHHAUC2/MVf/EVERPzwhz9sOPaHP/wh7rvvvvjUpz61z/hdu3bFV77ylTjxxBOjtLQ0+vbtG1dccUX83//9X8OYY489Np577rl47LHHGn5PfOyxx0ZEy/IUANqDQ/1u+b2WLVsWkydPjoEDB0a3bt3igx/8YFxzzTX7fB7+3e9+F9OmTYtBgwZFaWlp9O/fPz760Y/G+vXrG8Y8/PDDMXHixDj66KOjW7duMXTo0Ljooovirbfeysr1QltlxSrIkrq6unjkkUdi7NixMXDgwGY/7pFHHomPf/zj8aEPfShuvfXWKCsri3vuuSemTp0ab7311j7LIV955ZVxzjnnxL/927/Fyy+/HFdffXX81V/9VTz88MMNY7761a/Gl770pbjiiiviS1/6UuzatStuvPHGmDBhQjz55JNRXl7eMHbXrl1x/vnnx6c//em45ppr4t13342IiBdeeCHGjRsXV155ZZSVlcWLL74YixYtitNPPz2effbZ6Ny5c7Ov8corr4zXX389vvWtb8X999/f8Pq8t46nnnoqfvWrX8WXvvSlGD58eMP2EC+88EJceumlDb8cf/rpp+Of/umf4te//nXceeedERGxc+fO+NjHPhbDhw+PdDod/fv3j61bt8YjjzwSb775ZqNafvrTn0ZVVVUsWLAgevToEd/4xjdiypQp8Zvf/Cbe9773NfuaAOBANmzYEBERffv2jdWrV8enP/3pGDp0aERE/OIXv4jPfvaz8eqrr8aXv/zlRo9bv359zJ07N66//voYMGBA/OAHP4g5c+bErl274gtf+EJE7F5d8owzzohXXnkl/v7v/z5OOeWUeO655+LLX/5yPPvss/HQQw9FUVFRwzmXL18ejz/+eHz5y1+OAQMGRL9+/XL0KgDAbu/NxYiIF198sU1lY3NqqK2tjddffz2+8IUvxDHHHBO7du2Khx56KC688MK466674rLLLms43+WXXx7f//73Y+bMmbFgwYLo0qVLPPXUU/Hiiy/u9/mTJImrr746/vVf/zW+853v2DYJgJzp1atXXHzxxXHnnXfGpz/96YjY3WTVqVOnmDp1aixevLhhbH19fVxwwQXx+OOPxxe/+MUYP358vPTSS3HdddfFxIkTY82aNdGtW7d44IEH4uKLL46ysrJYsmRJRESUlpZGRMvyFADaukP9bnlvzz//fJx99tkxd+7c6N69e/z617+Or3/96/Hkk082+v747LPPjrq6uvjGN74RQ4cOjW3btsXq1avjjTfeiIjdn9XPOeecmDBhQtx5551x5JFHxquvvhr/9V//Fbt27SqIHY8gbxIgK7Zu3ZpERDJt2rR97nv33XeTd955p+Gnvr6+4b4TTzwxGT16dPLOO+80esy5556bDBw4MKmrq0uSJEnuuuuuJCKS2bNnNxr3jW98I4mIZMuWLUmSJMmmTZuSkpKS5LOf/WyjcW+++WYyYMCA5JOf/GTDsRkzZiQRkdx5550HvLb6+vrknXfeSV566aUkIpL/+I//aLhvT10bN2484DluvPHGJscNGzYsKS4uTn7zm98c8Bx1dXXJO++8k3z3u99NiouLk9dffz1JkiRZs2ZNEhHJ8uXLD/j4iEj69++fZDKZhmNbt25NOnXqlCxcuPCAjwWA/dmTg7/4xS+Sd955J3nzzTeTn/zkJ0nfvn2Tnj17Jlu3bm00fk+WLViwIDn66KMb/Ztg2LBhSVFRUbJ+/fpGj/nYxz6W9OrVK9m5c2eSJEmycOHCpFOnTklVVVWjcffee28SEcmDDz7YcCwikrKysobMBIDW1NJcTJLCz8bm1rC3Pb8HmDlzZjJ69OiG46tWrUoiIrn22msP+rznnHNO8tZbbyUXXXRRUlZWljz00EPNqhkADteeTK+qqkoeeeSRJCKSX/7yl0mSJElFRUVy+eWXJ0mSJCeddFJyxhlnJEmSJD/84Q+TiEjuu+++RueqqqpKIiJZsmRJw7H3Pu5AmsrTJNmdlTNmzDj0iwSAPDuU75avu+665EAtHnu+033ssceSiEiefvrpJEmSZNu2bUlEJIsXL27ysXs+Q+/9+RdIElsBQg6MGTMmOnfu3PDzzW9+MyJ2/9Xur3/96/jLv/zLiIh49913G37OPvvs2LJlS/zmN79pdK7zzz+/0e1TTjklIv60pcKKFSvi3Xffjcsuu6zR+bp27RpnnHFGw/Z773XRRRftc6ympiZmzZoVQ4YMiZKSkujcuXMMGzYsIiJ+9atfHd4Lsh+nnHJKfOADH9jn+Lp16+L888+Po48+OoqLi6Nz585x2WWXRV1dXfz2t7+NiIjjjz8+jjrqqPi7v/u7uPXWW6O6urrJ55k0aVL07Nmz4Xb//v2jX79+Da8fAByKD3/4w9G5c+fo2bNnnHvuuTFgwID4z//8z+jfv388/PDD8ed//udRVlbWkGVf/vKXY/v27VFTU9PoPCeddFKMHDmy0bFLL700MplMPPXUUxER8ZOf/CRGjBgRo0aNapT1Z511VqOtdvc488wz46ijjmrV6weA9zpQLkZEm8vG5tQQEfGjH/0oTjvttOjRo0fD5+g77rij0Wfo//zP/4yIiFQqddDn3b59e5x55pnx5JNPxn//93/HRz/60WbXDADZcsYZZ8Rxxx0Xd955Zzz77LNRVVW1320Af/KTn8SRRx4Z5513XqM8HjVqVAwYMGC/v5fen+bkKQC0d019t7w/v/vd7+LSSy+NAQMGNHzGPuOMMyLiT9/p9u7dO4477ri48cYbY9GiRbFu3bqor69vdJ5Ro0ZFly5d4m/+5m/i7rvvjt/97netd4HQxmisgizp06dPdOvWbb8NOv/2b/8WVVVV8eMf/7jR8ddeey0iIr7whS80CsfOnTvH7NmzIyJi27ZtjR5z9NFHN7q9Z5nkP/7xj43OWVFRsc85ly1bts/5jjjiiOjVq1ejY/X19TF58uS4//7744tf/GL8/Oc/jyeffDJ+8YtfNHqubNrfEpebNm2KCRMmxKuvvho33XRTPP7441FVVRXpdLpRHWVlZfHYY4/FqFGj4u///u/jpJNOikGDBsV1110X77zzTqNz7v36Rex+DVvjmgDoOL773e9GVVVVrFu3LjZv3hzPPPNMnHbaafHkk0/G5MmTIyLi29/+djzxxBNRVVUV1157bUTsm6kDBgzY59x7jm3fvj0idmf9M888s0/O9+zZM5Ik2SfrD2cZaQA4FE3lYkS0yWxsTg33339/fPKTn4xjjjkmvv/978f//M//NHzx/Pbbbzc87v/+7/+iuLh4v+fc229/+9v43//936isrIwRI0a0qGYAyJaioqK44oor4vvf/37ceuut8YEPfCAmTJiwz7jXXnst3njjjejSpcs+mbx169Z98nh/mpunANAeHMp3y3vbsWNHTJgwIf73f/83vvKVr8Sjjz4aVVVVcf/990fEnz5jFxUVxc9//vM466yz4hvf+Eaceuqp0bdv3/jc5z4Xb775ZkREHHfccfHQQw9Fv379IpVKxXHHHRfHHXdc3HTTTVm+cmh7SvJdALQXxcXFceaZZ8bPfvaz2LJlS6Nf1JaXl0fE7r1p36tPnz4RETF//vy48MIL93veE044oUV17Dnnvffe27DC1IEUFRXtc+yXv/xlPP3007F06dKYMWNGw/ENGza0qJaW2F8dy5cvj507d8b999/f6FrWr1+/z9iTTz457rnnnkiSJJ555plYunRpLFiwILp16xbXXHNNq9UNABERH/zgB2Ps2LH7HL/nnnuic+fO8ZOf/CS6du3acHz58uX7Pc/WrVubPLanOXjPB+4777xzv+fY82+BPfaXsQDQmprKxYi2mY3NqeH73/9+DB8+PJYtW9bo/LW1tY0e17dv36irq4utW7cetMFr3Lhxcckll8TMmTMjIuKWW26JTp38jSQAuXf55ZfHl7/85bj11lvjn/7pn/Y7pk+fPnH00UfHf/3Xf+33/vfuItCU5uYpALQHh/Ld8t4efvjh2Lx5czz66KMNq1RFRLzxxhv7jB02bFjccccdEbH7D3n+/d//Pa6//vrYtWtX3HrrrRERMWHChJgwYULU1dXFmjVr4lvf+lbMnTs3+vfvH9OmTTvMK4a2S2MVZNH8+fPjP//zP2PWrFlx7733RufOnQ84/oQTToj3v//98fTTT8dXv/rVrNRw1llnRUlJSbzwwgv73eKvOfZ8aN2zGtYet9122yHXtffKWodaR5Ik8e1vf/uAjxk5cmT8y7/8SyxdurTRtgwAkGtFRUVRUlISxcXFDcf++Mc/xve+9739jn/uuefi6aefbrTd0L/9279Fz54949RTT42IiHPPPTe++tWvxtFHHx3Dhw9v3QsAgCxri9nYnBqKioqiS5cujb4E3rp1a/zHf/xHo3NVVlbGwoUL45ZbbokFCxYc9LlnzJgR3bt3j0svvTR27twZd999d6PXDgBy4Zhjjomrr746fv3rXzf6Q9z3Ovfcc+Oee+6Jurq6+NCHPnTA8zW1g0Bz8xQA2ouWfre8t0P9TvcDH/hAfOlLX4r77rtvv9+lFhcXx4c+9KE48cQT4wc/+EE89dRTGqvo0DRWQRaddtppkU6n47Of/Wyceuqp8Td/8zdx0kknRadOnWLLli1x3333RUQ02nrvtttui8rKyjjrrLPi8ssvj2OOOSZef/31+NWvfhVPPfVU/OhHP2pRDccee2wsWLAgrr322vjd734XH//4x+Ooo46K1157LZ588sno3r173HDDDQc8x4knnhjHHXdcXHPNNZEkSfTu3Tv+3//7f7Fy5cqWvyj/v5NPPjkiIm666aaYMWNGdO7cOU444YQD/qXSxz72sejSpUv8xV/8RXzxi1+Mt99+O2655Zb4/e9/32jcT37yk1iyZEl84hOfiPe9732RJEncf//98cYbb8THPvaxQ64ZAA7XOeecE4sWLYpLL700/uZv/ia2b98e//zP/7zPB909Bg0aFOeff35cf/31MXDgwPj+978fK1eujK9//etxxBFHRETE3Llz47777ouPfOQj8fnPfz5OOeWUqK+vj02bNsXPfvazuOqqqw76S2wAyJe2mI3NqeHcc8+N+++/P2bPnh0XX3xxvPzyy/GP//iPMXDgwHj++ecbzjVhwoSYPn16fOUrX4nXXnstzj333CgtLY1169bFEUccEZ/97Gf3ef6LL744jjjiiLj44ovjj3/8Y/zwhz+MLl26HPL1AMCh+NrXvnbA+6dNmxY/+MEP4uyzz445c+bEn/3Zn0Xnzp3jlVdeiUceeSQuuOCCmDJlSkT8afeBZcuWxfve977o2rVrnHzy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rW99q9H9mzZtigsuuCB69OgRvXr1ik/+f+zdeXiU1d3/8c8kIWEJDIaEABICigIRgRCigqJBazAqIlgFFxaFPmKmD8GordQVapsWaoSWAcUFXKqgVmkfRWla2SouCSYuRNEoEJBgJKhDogaYnN8f/jJ1sjExs2Xm/bquXO19z5n7/s7A5Ycz+c45V12lL774QpL00UcfqXPnznr66add41944QV17NhR77//fpM1OZ1OzZo1SwMGDFCnTp00aNAgLV261G3Mpk2bdMYZZ6hLly7q3r27zj77bO3Zs0fSfztRH3roISUlJalz58668sorG3ULr1q1SkOGDFHHjh01ePBgLV++3O3xffv2aerUqYqLi1OXLl00atQovfXWW1q9erUWLFigd99917Uk4OrVq5t8LTNnztTll1+uvLw89enTR6eeeqok6amnntKoUaPUtWtX9erVS9dcc40qKysl/bDU4Lhx4yRJJ5xwgiwWi2bOnCmpcbfvV199penTp+uEE05Q586dlZWVpU8++aTJWjzx5JNPqn///rJarZo6daoOHz7seswYo0WLFumkk05Sp06dNHz4cD3//POux5vqyn744YddfwaTJk1Sfn5+k8sxNnffmTNnavPmzVq6dKnrvd69e7freX//+9912WWX6f3331dERIQOHjzoel8iIiJ05ZVXusbm5eVp9OjRzda6evVq9evXz1VrVVWV22Mt/ZkfPHhQkyZNUufOnXXKKafoH//4R6vedwDhgcwlc3+MzCVzAfgOmUvm/hiZS+YC8B0yl8z9MTKXzAXgO2QumftjZC6ZG9YM4GfnnXeeiY2NNTk5Oeajjz4yTz31lOncubNZuXKla0xycrLp2rWrycvLMzt37jR//vOfTWRkpPnnP/9pjDGmrq7OpKammnPOOccUFRWZN99804wcOdKcd955rmvY7XZjtVrN7t27zeeff27i4uLMAw880GxdR44cMXfffbd5++23zWeffeaqa+3atcYYY44ePWqsVqu59dZbTVlZmSktLTWrV682e/bsMcYYc88995guXbqY888/3xQXF5vNmzebgQMHmmuuucZ1j5UrV5revXubv/3tb+azzz4zf/vb30xcXJxZvXq1McaYw4cPm5NOOsmMHTvWbN261XzyySdm7dq1Ztu2bebbb781t9xyiznttNNMRUWFqaioMN9++22Tr2XGjBkmNjbWTJs2zXzwwQfm/fffN8YY8+ijj5r169ebTz/91LzxxhvmrLPOMllZWcYYY44dO2b+9re/GUlm586dpqKiwnz99deuP7OcnBzX9S+77DIzZMgQs2XLFlNSUmLGjx9vBg4caI4cOdLs+yvJvPjii27n7rnnHhMbG2smT55s3n//fbNlyxbTq1cv85vf/MY15je/+Y0ZPHiwefXVV82nn35qVq1aZWJiYsymTZuMMcZs3LjRSDJfffWVMcaY//znPyYiIsIsXrzY7Ny509jtdhMXF2esVqvH9/3666/N6NGjzS9+8QvXe33s2DFjjDFfffWV6dChgykvLzd1dXUmPj7ePP/888YYY9atW2fi4+NNz549XffKzMw0v/71r5us9c033zQWi8X193zp0qWme/furlpb+jOXZPr27Wuefvpp88knn5i5c+ea2NhYU1VV1eg9Hj58eLN/LgBCG5lL5tYjc8lcAL5F5pK59chcMheAb5G5ZG49MpfMBeBbZC6ZW4/MJXNhDI1V8LvzzjvPDBkyxNTV1bnO/frXvzZDhgxxHScnJ5uLLrrI7XlTpkxxhcY///lPExkZacrLy12P79ixw0gyb7/9tuvcJZdcYsaOHWsuuOACc+GFF7rd0xPZ2dnmiiuuMMYYU1VVZSS5AqChe+65x0RGRpq9e/e6zr3yyismIiLCVFRUGGOMSUpKMk8//bTb837729+a0aNHG2OMeeihh0zXrl0b/cf0x/fw5D+qM2bMMImJiaa2trbFcW+//baRZA4fPmyMaRwU9X4cxB9//LGRZF5//XXX4wcPHjSdOnUyzz77bLP3ai6IO3fubBwOh+vcbbfdZs4880xjjDHV1dWmY8eOZtu2bW7PmzVrlrn66qubrHnKlCnmkksucRt/7bXXNgrilu7b8DX/2F//+lczcuRI1/HkyZPNL3/5S2OMMfPmzTO33HKLiY+PNzt27DBHjx41sbGx5pVXXmmy1quvvrrJv+cNa23qz1ySufPOO13H1dXVxmKxuO51vOcDCA9kLpn749dD5pK5AHyHzCVzf/x6yFwyF4DvkLlk7o9fD5lL5gLwHTKXzP3x6yFzydxwx1aACIizzjpLFovFdTx69Gh98skncjqdbud+bPTo0frwww8lSR9++KGSkpKUlJTkejwlJUXdu3d3jZGkxx57TO+9957eeecdrV692u2eTXnwwQc1atQoJSQkKDY2Vg8//LDKy8sl/bAv7cyZMzV+/HhNmDBBS5cuVUVFhdvz+/Xrp759+7rVXFdXp507d+rLL7/U3r17NWvWLMXGxrp+7rvvPn366aeSpJKSEqWmpnplD9zTTz+90T68xcXFmjhxopKTk9W1a1dlZGRIkus1euLDDz9UVFSUzjzzTNe5Hj16aNCgQW7vvaf69++vrl27uo579+7tWs6ytLRU33//vS688EK39+yJJ55wvWcN7dy5U2eccYbbuYbHx7tvS+qXjayXkZGhTZs2SZI2b96scePG6dxzz9XmzZtVWFio7777TmeffXaT1/rwww+b/HvuqWHDhrn+f5cuXdS1a1ePXgOA8ELmkrn1yFwyF4Bvkblkbj0yl8wF4FtkLplbj8wlcwH4FplL5tYjc8nccBcV6AKA1qgPUmNMk6Ha8Py7776rmpoaRURE6MCBA+rTp0+z13722Wd188036/7779fo0aPVtWtXLV68WG+99ZZrzKpVqzR37ly9+uqrWrt2re68804VFBTorLPOarFei8Wiuro6ST/sF/vjEJOkyMhISVKnTp08eRs80qVLF7fjmpoaZWZmKjMzU0899ZQSEhJUXl6u8ePH68iRIx5f1zTYO/nH54/3D52mdOjQwe34x+9V/f++/PLLOvHEE93GxcTEeFxHUzW3dN/mHD16VK+++qrmz5/vOpeRkaGcnByVlZXpgw8+0NixY/Xpp59q8+bN+vrrr5WWluYW+MerqzV+ymsAAE+RuZ4jc1uumcwFgJaRuZ4jc1uumcwFgJaRuZ4jc1uumcwFgJaRuZ4jc1uumcxFsKCxCgHx5ptvNjo+5ZRTXIHU3JjBgwdL+qGbuby8XHv37nV1OZeWluqbb77RkCFDJEmHDh3SzJkzdccdd+jAgQO69tpr9c477zQbdlu3btWYMWOUnZ3tOtdUF21qaqpSU1M1f/58jR49Wk8//bQriMvLy7V//35X4L/xxhuKiIjQqaeeqsTERJ144on67LPPdO211zZZw7Bhw/TII4/o0KFDTXY5R0dHu3WBt8ZHH32kgwcP6g9/+IPrPSsqKmp0fUkt3iMlJUXHjh3TW2+9pTFjxkiSqqqq9PHHH7vee29JSUlRTEyMysvLdd5553n0nMGDB+vtt992O9fwdXqiqfd648aN6t69u0aMGOE6N3ToUPXo0UP33Xefhg8frm7duum8885TXl6evvrqqxbrTklJafLv+fHqAIDWIHPJXE+QuWQugLYjc8lcT5C5ZC6AtiNzyVxPkLlkLoC2I3PJXE+QuWRuOGArQATE3r17lZubq507d+qZZ57RX/7yF+Xk5LiNef3117Vo0SJ9/PHHstvteu6551xjfvazn2nYsGGucH377bc1ffp0nXfeeRo1apQkac6cOUpKStKdd96p/Px8GWN06623NlvTwIEDVVRUpA0bNujjjz/WXXfdpcLCQtfju3bt0vz58/XGG29oz549+uc//9kofDp27KgZM2bo3Xff1datWzV37lxdddVV6tWrlyTp3nvvVV5enpYuXaqPP/5Y77//vlatWqX8/HxJ0tVXX61evXrp8ssv1+uvv67PPvtMf/vb3/TGG29I+mG5w127dqmkpEQHDx5UbW2tx+95v379FB0drb/85S/67LPP9I9//EO//e1v3cYkJyfLYrHopZde0pdffqnq6upG1znllFM0ceJE/eIXv9B//vMfvfvuu7ruuut04oknauLEiR7X44muXbvq1ltv1c0336zHH39cn376qYqLi2W32/X44483+Zz//d//1fr165Wfn69PPvlEDz30kF555ZVWd1/3799fb731lnbv3q2DBw+qrq5O//jHP9yWjZR+6Co+99xz9dRTT7mW4hw2bJiOHDmif//7365zTanvlq//e75s2TK9+uqrjer4qX/mACCRuWSuZ8hcMhdA25G5ZK4nyFwyF0DbkblkrifIXDIXQNuRuWSuJ8hcMjcsGMDPzjvvPJOdnW3mzJljunXrZk444QRz++23m7q6OteY5ORks2DBAnPVVVeZzp07m8TERLNkyRK36+zZs8dcdtllpkuXLqZr167myiuvNAcOHDDGGPP444+bLl26mI8//tg1vqioyERHR5uXX365ybq+//57M3PmTGO1Wk337t3NTTfdZG6//XYzfPhwY4wxBw4cMJdffrnp3bu3iY6ONsnJyebuu+82TqfTGGPMPffcY4YPH26WL19u+vTpYzp27GgmT55sDh065Hafv/71r2bEiBEmOjranHDCCebcc881L7zwguvx3bt3myuuuMJ069bNdO7c2YwaNcq89dZbrhqvuOIK0717dyPJrFq1qsnXMmPGDDNx4sRG559++mnTv39/ExMTY0aPHm3+8Y9/GEmmuLjYNWbhwoWmV69exmKxmBkzZrj+zHJyclxjDh06ZKZNm2asVqvp1KmTGT9+vNt73RRJ5sUXX3Q7V/+e/dgDDzxgkpOTXcd1dXVm6dKlZtCgQaZDhw4mISHBjB8/3mzevNkYY8zGjRuNJPPVV1+5nrNy5Upz4oknmk6dOpnLL7/c3HfffaZXr16tuu/OnTvNWWedZTp16mQkmV27dpmkpCRTUFDQ6LX95S9/MZLMSy+95Do3ceJEExkZab755hvXuaZqffTRR03fvn1Np06dzIQJE8yf/vQnY7VaXY8392fe1PtptVob/Z1o6rUCCB9kLplbj8wlcwH4FplL5tYjc8lcAL5F5pK59chcMheAb5G5ZG49MpfMhTEWY9q4KSQAST90L69bt04lJSWBLiUoWSwWvfjii7r88sv9fu9f/OIX+uijj7R169affI133nlH559/vr788stGe+EGM/5eAghF/LetZWRuYPD3EkAo4r9tLSNzA4O/lwBCEf9taxmZGxj8vQQQivhvW8vI3MDg72Xwiwp0AQDCx9VXX60ePXpo3759Pr3Pn/70J1144YXq0qWLXnnlFT3++ONavnx5m6557Ngx/eUvf2k3IVxeXq6UlBQdOXJEKSkpgS4HAOBnZK7/kLkAEN7IXP8hcwEgvJG5/kPmAkB4I3P9h8xtP2isAuAXn3zyiSQpMjLS5/d6++23tWjRIh0+fFgnnXSS/vznP2v27NltuuYZZ5yhM844w0sV+l6fPn1cXc0xMTGBLQYA4Fdkrn+RuQAQvshc/yJzASB8kbn+ReYCQPgic/2LzG0/2AoQAAAAAAAAAAAAAAAAABqICHQBAAAAAAAAAAAAAAAAABBsaKwCAAAAAAAAAAAAAAAAgAZorAIAAAAAAAAAAAAAAACABmisAgAAAAAAAAAAAAAAAIAGaKwCAAAAAAAAAAAAAAAAgAZorAIAAAAAAAAAAAAAAACABmisAgAAAAAAAAAAAAAAAIAGaKwCAAAAAAAAAAAAAAAAgAZorAIAAAAAAAAAAAAAAACABmisAgAAAAAAAAAAAAAAAIAGaKwCAAAAAAAAAAAAAAAAgAZorAIAAAAAAAAAAAAAAACABmisAgAAAAAAAAAAAAAAAIAGaKwCQsTq1atlsVhcP1FRUerbt6+uv/56ff7555KkTZs2yWKxaNOmTT6pYf369br33nubfKx///6aOXOmT+4LAEBrvPXWW5o0aZL69eunmJgYJSYmavTo0brllltcY5YvX67Vq1cHrsj/784771S/fv0UFRWl7t27B7ocAACOy5O5qRQ689P+/fvr0ksv9cq1AADwl4Z5bbFYlJCQoIyMDL300ks/6ZrNzaP379+ve++9VyUlJW0rGgCAduS9997T9ddfrwEDBqhjx46KjY3VyJEjtWjRIh06dEiSlJGRoYyMjJ90/d///vdat25do/P1GV9UVHTca7Tl/kC4iQp0AQC8a9WqVRo8eLC+++47bdmyRXl5edq8ebPef/99n997/fr1stvtTX54/eKLL6pbt24+rwEAgJa8/PLLuuyyy5SRkaFFixapd+/eqqioUFFRkdasWaP7779f0g8fCMfHxwe0Kfjvf/+7fve73+mOO+5QVlaWYmJiAlYLAACt1dLctEuXLj6/P/NTAACOrz6vjTE6cOCAli1bpgkTJugf//iHJkyY0KprNTeP3r9/vxYsWKD+/ftrxIgR3iseAIAg9fDDDys7O1uDBg3SbbfdppSUFB09elRFRUV68MEH9cYbb+jFF19s0z1+//vf6+c//7kuv/zyn3yN5cuXt6kGIJzQWAWEmKFDh2rUqFGSpHHjxsnpdOq3v/2t1q1bpxNPPDFgdaWmpgbs3gAA1Fu0aJEGDBigDRs2KCrqv/8Unjp1qhYtWuTTe3/33Xfq2LGjLBaLR+M/+OADSdLcuXPVs2dPX5YGAIDXtTQ3vfbaawNaG/NTAAB+8OO8lqSLLrpIJ5xwgp555plWN1b5W2vn2AAA+MMbb7yhm266SRdeeKHWrVvn9mXZCy+8ULfccoteffXVAFb4XykpKYEuAWg32AoQCHFnnXWWJGnPnj1NPl5UVKSpU6eqf//+6tSpk/r376+rr7660fhvv/1Wt956q2vJyri4OI0aNUrPPPOMJGnmzJmy2+2S5LaE9O7duyU1vdXC119/rVtuuUUnnXSSYmJi1LNnT1188cX66KOPvPgOAADwX1VVVYqPj3drqqoXEfHDP4379++vHTt2aPPmza4869+/v6qrq9W9e3fdeOONjZ67e/duRUZGavHixZL+u+TyP//5T91www1KSEhQ586dVVtbq7q6Oi1atEiDBw925d/06dO1b98+1/X69++vO++8U5KUmJgoi8XiWnHDk+dLUkFBgSZOnKi+ffuqY8eOGjhwoG688UYdPHjQbdy9994ri8Wi9957T1deeaWsVqvi4uKUm5urY8eOaefOnbrooovUtWtX9e/f3+cNaACA0HS8uanUvuenL774ooYNG6aOHTvqpJNO0p///Ge3x7///nvdcsstGjFihCtrR48erb///e+NrvX1119r1qxZiouLU2xsrC655BJ99tlnbv8eAADAFzp27Kjo6Gh16NDBdW7BggU688wzFRcXp27dumnkyJF69NFHZYxxjWluHr1p0yalp6dLkq6//nrXYz/Os6KiIl122WWKi4tTx44dlZqaqmeffdatrubm2P/5z39ksVhc/wb4sSeeeEIWi0WFhYVefpcAAGje73//e1ksFq1cubLJHQiio6N12WWXNfv8Q4cOKTs7WyeeeKKio6N10kkn6Y477lBtba1rjMViUU1NjR5//HFXtjbc0u/w4cO66aabFB8frx49emjy5Mnav3+/25iGWwHu3r1bFotFf/rTn5Sfn68BAwYoNjZWo0eP1ptvvtmo1ocfflinnnqqYmJilJKSoqefflozZ85U//79PXuzgHaEFauAEFdWViZJSkhIaPLx3bt3a9CgQZo6dari4uJUUVGhFStWKD09XaWlpYqPj5ck5ebm6sknn9R9992n1NRU1dTU6IMPPlBVVZUk6a677lJNTY2ef/55vfHGG67r9+7du8n7Hj58WOecc452796tX//61zrzzDNVXV2tLVu2qKKiQoMHD/bm2wAAgCRp9OjReuSRRzR37lxde+21GjlypNsHxtIPvxj9+c9/LqvV6loOOSYmRrGxsbrhhhu0cuVKLVq0SFar1fWc5cuXKzo6WjfccIPbtW644QZdcsklevLJJ1VTU6MOHTropptu0sqVK/XLX/5Sl156qXbv3q277rpLmzZt0jvvvKP4+Hi9+OKLstvtevTRR/Xqq6/KarWqb9++kuTR8yXp008/1ejRozV79mxZrVbt3r1b+fn5Ouecc/T+++83et1XXXWVrrvuOt14440qKCjQokWLdPToUf3rX/9Sdna2br31Vj399NP69a9/rYEDB2ry5Mle//MBAISu481NpfY7Py0pKdG8efN07733qlevXvrrX/+qnJwcHTlyRLfeeqskqba2VocOHdKtt96qE088UUeOHNG//vUvTZ48WatWrdL06dMl/dBAPWHCBBUVFenee+/VyJEj9cYbb+iiiy7y8J0GAMBzTqdTx44dkzFGX3zxhRYvXqyamhpdc801rjG7d+/WjTfeqH79+kmS3nzzTf3v//6vPv/8c919992Smp9Hn3zyyVq1apWuv/563XnnnbrkkkskyTW/3bhxoy666CKdeeaZevDBB2W1WrVmzRpNmTJF3377baNG6IZz7DFjxig1NVV2u11XX32129hly5YpPT3d1dgFAICvOZ1Ovfbaa0pLS1NSUlKrn//9999r3Lhx+vTTT7VgwQINGzZMW7duVV5enkpKSvTyyy9L+mFVrPPPP1/jxo3TXXfdJUmNtrufPXu2LrnkEj399NPau3evbrvtNl133XV67bXXjluH3W7X4MGDtWTJEkk/zLEvvvhi7dq1y/WZ+MqVK3XjjTfqiiuu0AMPPKBvvvlGCxYscGsAA0KKARASVq1aZSSZN9980xw9etQcPnzYvPTSSyYhIcF07drVHDhwwGzcuNFIMhs3bmz2OseOHTPV1dWmS5cuZunSpa7zQ4cONZdffnmLNdhsNtPcf1aSk5PNjBkzXMcLFy40kkxBQUGrXicAAG1x8OBBc8455xhJRpLp0KGDGTNmjMnLyzOHDx92jTvttNPMeeed1+j5n376qYmIiDAPPPCA69x3331nevToYa6//nrXufpcnj59utvzP/zwQyPJZGdnu51/6623jCTzm9/8xnXunnvuMZLMl19++ZOe/2N1dXXm6NGjZs+ePUaS+fvf/97oPvfff7/bc0aMGGEkmRdeeMF17ujRoyYhIcFMnjy5yfsAAODJ3NQYEzLz0+TkZGOxWExJSYnb+QsvvNB069bN1NTUNPm8Y8eOmaNHj5pZs2aZ1NRU1/mXX37ZSDIrVqxwG5+Xl2ckmXvuuafVNQIA0FB9Xjf8iYmJMcuXL2/2eU6n0xw9etQsXLjQ9OjRw9TV1bkea24eXVhYaCSZVatWNXps8ODBJjU11Rw9etTt/KWXXmp69+5tnE6nW70N59g/fqy4uNh17u233zaSzOOPP36cdwIAAO85cOCAkWSmTp3q0fjzzjvPLTsffPBBI8k8++yzbuP++Mc/Gknmn//8p+tcly5d3Oa19epzseHnx4sWLTKSTEVFRbP337Vrl5FkTj/9dHPs2DHX+fpcfeaZZ4wxP/x7oFevXubMM890u8eePXtMhw4dTHJyskevH2hP2AoQCDFnnXWWOnTooK5du+rSSy9Vr1699MorrygxMbHJ8dXV1a6VJ6KiohQVFaXY2FjV1NToww8/dI0744wz9Morr+j222/Xpk2b9N1337WpzldeeUWnnnqqfvazn7XpOgAAtEaPHj20detWFRYW6g9/+IMmTpyojz/+WPPnz9fpp5/eaJu8hk466SRdeumlWr58uWvbg6efflpVVVX65S9/2Wj8FVdc4Xa8ceNGSWr0rdszzjhDQ4YM0b///e8W79+a51dWVmrOnDlKSkpSVFSUOnTooOTkZElyy/h6l156qdvxkCFDZLFYlJWV5ToXFRWlgQMHtriNEwAAUuvnplL7nZ+edtppGj58uNu5a665Rg6HQ++8847r3HPPPaezzz5bsbGxrmx+9NFH3V7b5s2bJf2wkuSPNVyFAwAAb3jiiSdUWFiowsJCvfLKK5oxY4ZsNpuWLVvmGvPaa6/pZz/7maxWqyIjI9WhQwfdfffdqqqqUmVl5U++d1lZmT766CNde+21kqRjx465fi6++GJVVFRo586dbs9pOMeWfsjInj17urYBlqS//OUvSkhI0JQpU35yfQAA+Ntrr72mLl266Oc//7nb+frPgo/32fGPNdxucNiwYZLk0ee6l1xyiSIjI5t97s6dO3XgwIFG89Z+/frp7LPP9rhGoD1hK0AgxDzxxBMaMmSIoqKilJiY2OxWB/WuueYa/fvf/9Zdd92l9PR0devWTRaLRRdffLHbh9N//vOf1bdvX61du1Z//OMf1bFjR40fP16LFy/WKaec0uo6v/zyS9fy0QAA+NuoUaM0atQoSdLRo0f161//Wg888IAWLVqkRYsWtfjcnJwcXXDBBSooKFBmZqbsdrtGjx6tkSNHNhrbMIfrtyhqKp/79Olz3Imtp8+vq6tTZmam9u/fr7vuukunn366unTporq6Op111llN/gI6Li7O7Tg6OlqdO3dWx44dG513OBwt1gkAQGvnplL7nZ/26tWr2XP12f3CCy/oqquu0pVXXqnbbrtNvXr1UlRUlFasWKHHHnvM9byqqipFRUU1yuWWGtIAAPiphgwZ4pobS9JFF12kPXv26Fe/+pWuu+46ffzxx8rMzFRGRoYefvhh9e3bV9HR0Vq3bp1+97vftam5+YsvvpAk3Xrrra6tcxtq+OWnpv49ERMToxtvvFH333+/Fi9erKNHj+rZZ59Vbm6uYmJifnJ9AAC0Vnx8vDp37qxdu3b9pOdXVVWpV69eslgsbud79uypqKgo1/zSEz169HA7rs9ET7L7eM+tr6OpeWpiYuJPfv1AMKOxCggxDSfDLfnmm2/00ksv6Z577tHtt9/uOl9bW6tDhw65je3SpYsWLFigBQsW6IsvvnB9O3jChAn66KOPWl1nQkKC9u3b1+rnAQDgbR06dNA999yjBx54QB988MFxx59//vkaOnSoli1bptjYWL3zzjt66qmnmhzbcBJcPymtqKhQ37593R7bv3+/4uPjW7y3p8//4IMP9O6772r16tWaMWOGa0xZWdlxXx8AAN7Qmrmp1L7npwcOHGj2XH12P/XUUxowYIDWrl3r9u+D2tpat+f16NFDx44d06FDh9yaq5q6BwAAvjBs2DBt2LBBH3/8sdasWaMOHTropZdecvvSzbp169p8n/r56/z58zV58uQmxwwaNMjtuOEcu95NN92kP/zhD3rsscf0/fff69ixY5ozZ06bawQAoDUiIyN1wQUX6JVXXtG+ffsafX57PD169NBbb70lY4xb5lVWVurYsWPH/ezYX+rnufVN0j/G3BWhiq0AgTBmsVhkjGn0zZ1HHnlETqez2eclJiZq5syZuvrqq7Vz5059++23klrX7ZyVlaWPP/5Yr732WhteAQAArVNRUdHk+fotePr06SPph0xrKc/mzp2rl19+WfPnz1diYqKuvPJKj+5//vnnS1KjRqzCwkJ9+OGHuuCCC7zy/PqJd8OMf+ihhzyqEwAAf2vP89MdO3bo3XffdTv39NNPq2vXrq4VLS0Wi6Kjo90+HD9w4ID+/ve/uz3vvPPOkyStXbvW7fyaNWt+Um0AALRWSUmJpB8ajy0Wi6Kioty2A/ruu+/05JNPNnpec/Po5jJ50KBBOuWUU/Tuu++6VpVu+NO1a1ePau7du7euvPJKLV++XA8++KAmTJjAbgkAgICYP3++jDH6xS9+oSNHjjR6/OjRo/q///u/Jp97wQUXqLq6ulED8xNPPOF6vN7xPr/2pUGDBqlXr1569tln3c6Xl5dr27ZtAakJ8DVWrALCWLdu3XTuuedq8eLFio+PV//+/bV582Y9+uij6t69u9vYM888U5deeqmGDRumE044QR9++KGefPJJjR49Wp07d5YknX766ZKkP/7xj8rKylJkZKSGDRum6OjoRveeN2+e1q5dq4kTJ+r222/XGWecoe+++06bN2/WpZdeqnHjxvn89QMAws/48ePVt29fTZgwQYMHD1ZdXZ1KSkp0//33KzY2Vjk5OZJ+yLQ1a9Zo7dq1Oumkk9SxY0dXzknSddddp/nz52vLli268847m8y6pgwaNEj/8z//o7/85S+KiIhQVlaWdu/erbvuuktJSUm6+eabvfL8wYMH6+STT9btt98uY4zi4uL0f//3fyooKPiJ7xwAAL7Vnuenffr00WWXXaZ7771XvXv31lNPPaWCggL98Y9/dNVz6aWX6oUXXlB2drZ+/vOfa+/evfrtb3+r3r1765NPPnFd66KLLtLZZ5+tW265RQ6HQ2lpaXrjjTdcH6RHRPAdSQCA93zwwQc6duyYpB+29XnhhRdUUFCgSZMmacCAAbrkkkuUn5+va665Rv/zP/+jqqoq/elPf2pyi73m5tEnn3yyOnXqpL/+9a8aMmSIYmNj1adPH/Xp00cPPfSQsrKyNH78eM2cOVMnnniiDh06pA8//FDvvPOOnnvuOY9fS05Ojs4880xJ0qpVq7zzBgEA0EqjR4/WihUrlJ2drbS0NN1000067bTTdPToURUXF2vlypUaOnSoJkyY0Oi506dPl91u14wZM7R7926dfvrp+s9//qPf//73uvjii/Wzn/3MNfb000/Xpk2b9H//93/q3bu3unbt2milR1+JiIjQggULdOONN+rnP/+5brjhBn399ddasGCBevfuzbwVockACAmrVq0ykkxhYWGzYzZu3GgkmY0bN7rO7du3z1xxxRXmhBNOMF27djUXXXSR+eCDD0xycrKZMWOGa9ztt99uRo0aZU444QQTExNjTjrpJHPzzTebgwcPusbU1taa2bNnm4SEBGOxWIwks2vXLmOMaXQ9Y4z56quvTE5OjunXr5/p0KGD6dmzp7nkkkvMRx995I23BACARtauXWuuueYac8opp5jY2FjToUMH069fPzNt2jRTWlrqGrd7926TmZlpunbtaiSZ5OTkRteaOXOmiYqKMvv27Wv0WEu57HQ6zR//+Edz6qmnmg4dOpj4+Hhz3XXXmb1797qNu+eee4wk8+WXX/6k55eWlpoLL7zQdO3a1ZxwwgnmyiuvNOXl5UaSueeee457nxkzZpguXbo0qv+8884zp512WqPzAAAY49nc1JjQmZ8mJyebSy65xDz//PPmtNNOM9HR0aZ///4mPz+/0dg//OEPpn///iYmJsYMGTLEPPzww64c/rFDhw6Z66+/3nTv3t107tzZXHjhhebNN980kszSpUtbrAcAAE/U5/WPf6xWqxkxYoTJz88333//vWvsY489ZgYNGuTK3Ly8PPPoo4+6ZasxLc+jn3nmGTN48GDToUOHRnPSd99911x11VWmZ8+epkOHDqZXr17m/PPPNw8++GCjeo/374v+/fubIUOGtPn9AQCgrUpKSsyMGTNMv379THR0tOnSpYtJTU01d999t6msrDTG/PA563nnnef2vKqqKjNnzhzTu3dvExUVZZKTk838+fPdsrn++meffbbp3LmzkeS6TnOZ2dQcvOH9d+3aZSSZxYsXN3o9DfPbGGNWrlxpBg4caKKjo82pp55qHnvsMTNx4kSTmpraujcLaAcsxhjjryYuAAAAIBQcOXJE/fv31znnnNNoyWMAAABve/rpp3Xttdfq9ddf15gxYwJdDgAAQee9997T8OHDZbfblZ2dHehyAAAIO19//bVOPfVUXX755Vq5cmWgywG8iq0AAQAAAA99+eWX2rlzp1atWqUvvvhCt99+e6BLAgAAIeaZZ57R559/rtNPP10RERF68803tXjxYp177rk0VQEA0MCnn36qPXv26De/+Y169+6tmTNnBrokAABC3oEDB/S73/1O48aNU48ePbRnzx498MADOnz4sHJycgJdHuB1NFYBAAAAHnr55Zd1/fXXq3fv3lq+fLlGjhwZ6JIAAECI6dq1q9asWaP77rtPNTU1rl8S33fffYEuDQCAoPPb3/5WTz75pIYMGaLnnntOnTt3DnRJAACEvJiYGO3evVvZ2dk6dOiQOnfurLPOOksPPvigTjvttECXB3gdWwECAAAAAAAAAAAAAAAAQAMRgS4AAAAAAAAAAAAAAAAAAIINjVUAAAAAAAAAAAAAAAAA0ACNVQAAAAAAAAAAAAAAAADQAI1VAAAAAAAAAAAAAAAAANBAVKALCLS6ujrt379fXbt2lcViCXQ5AIAQYIzR4cOH1adPH0VE0MNcj8wFAHgbmds0MhcA4G1kbtPIXACAt5G5TSNzAQDe1prMDfvGqv379yspKSnQ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Nm7cqLVr1+r8+fMaMWKEvv3224ueW1hYqNLSUu+fa665phlmXD/f+973fOa2c+fOgGNd75EkbdmyxaeetWvXSpLuu+++Os9zqUfffvut+vXrp9dee83v8y+++KLmzZun1157TVu2bFFiYqLuvPNOlZWVBbzmhg0bNGHCBE2ePFk7duzQ5MmTNX78eG3atKmpyvBRV02nT5/Wtm3b9LOf/Uzbtm3T8uXLVVRUpNGjR1/0unFxcT59Ky0tVURERFOUUMvF+iRJd911l8/c3n///Tqv6XKfJNV6rd9++215PB7de++9dV63JfvUVpC533FpPa+JzK3kUo/IXDLXxT5JZK7LyNzvuLSe10TmVnKpR2QumetinyQy12Vk7ndcWs9rInMrudQjMpfMdbFPEpnrMjL3Oy6t5zWRuZVc6hGZS+a62CfJocw11MugQYPskUce8TnWp08fmzlzpt/x//qv/2p9+vTxOfbP//zPNnjw4Cab4+X46quvTJLl5+cHHJObm2uS7NixY803sQaYNWuW9evXr97jg61HZmbTpk2zq666yioqKvw+73qPJNmKFSu8jysqKiwxMdHmzp3rPXbmzBmLj4+3119/PeB1xo8fb3fddZfPsZEjR9rEiRMbfc4XU7MmfzZv3myS7ODBgwHHZGdnW3x8fONO7hL5qykzM9PGjBnToOsEW5/GjBljt99+e51jXOpTa0bmur+ek7nu94jMJXNd7hOZ6w4y1/31nMx1v0dkLpnrcp/IXHeQue6v52Su+z0ic8lcl/tE5rqDzHV/PSdz3e8RmUvmutynlspcPrGqHsrLy/U///M/GjFihM/xESNGaP369X7P2bBhQ63xI0eO1NatW3Xu3Lkmm+ulOnHihCSpY8eOFx3bv39/JSUl6Y477lBubm5TT61BiouLlZycrF69emnixInat29fwLHB1qPy8nItWbJE//RP/ySPx1PnWJd7VN3+/ft1+PBhnz6Eh4crIyMj4M+WFLh3dZ3Tkk6cOCGPx6OEhIQ6x506dUqpqanq3r27fvCDH6igoKB5JlhPeXl56tKli6699lo9/PDD+uqrr+ocH0x9+tvf/qbVq1froYceuuhY1/sU7MhcXy6v52RuJZd7VB2Z68v1tZzMreR6n4IdmevL5fWczK3kco+qI3N9ub6Wk7mVXO9TsCNzfbm8npO5lVzuUXVk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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(2, 5, figsize=(24, 8), tight_layout=True)\n", + "\n", + "for i, ax in enumerate(axes.flat):\n", + " #maxratio = max(globals()['class{}'.format(i)]['ratio'])\n", + " #ax.hist(globals()['class{}'.format(i)]['ratio'], bins=np.arange(0, maxratio+0.5, 0.5), ec='white')\n", + " ax.hist(globals()['class{}'.format(i)]['ratio'], bins=np.arange(0, 18, 0.5), ec='white')\n", + " ax.set_xlabel('bbox aspect ratio [height/width]')\n", + " ax.set_ylabel('frequency')\n", + " ax.set_yscale('log')\n", + " ax.set_title(num_to_label(i))" + ] + }, + { + "cell_type": "markdown", + "id": "b9e97b08-b076-4c62-9d3c-38bddbf8d216", + "metadata": {}, + "source": [ + "- 클래스 불문 정사각형에 가까운 bbox가 가장 많음\n", + "- 유리가 대체로 길쭉한 bbox를 가지는 경우가 많아 보임\n", + "- 일반 쓰레기, 종이, 비닐봉지가 큰 aspect ratio를 가지는 비율이 크며, 배터리, 종이팩이 큰 aspect ratio를 가지는 비율이 적어 보임" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "f1054e48-6f8a-4b35-b16b-fa07451a0e3d", + "metadata": {}, + "outputs": [], + "source": [ + "# 이미지 내에서 bbox의 위치 (클래스 별)\n", + "background = np.ones((1024, 1024), dtype=np.int8)" + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "id": "33ff5092-f2fc-4b6f-9ae1-957df1c9db89", + "metadata": {}, + "outputs": [], + "source": [ + "def draw_bbox1(xmin, ymin, width, height, label=None, color='red', alpha=1):\n", + " #import matplotlib.patches as patches\n", + " bbox = patches.Rectangle((xmin, ymin), width, height, ec=color, fc='none', lw=2, label=label, alpha=alpha)\n", + " ax.add_patch(bbox)" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "af9c5805-bf41-4af1-8ff1-357af997f87e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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bTGbzXDrW98a3/AN34D3ve99Mc3b3/52fN3XfR0uvvhiHDt2DLe//e3xrd/6rbjhhhtOSn0LndlULCIOkfq+x7XXXosv//Ivx21uc5utn7v22mvx0Ic+FPe73/1w9dVX46KLLsLrX/96fPu3fztuuOGGJbPPJz3pSbjyyivxK7/yK/j7v/97PPOZz8R/+k//CW9/+9tjmhe+8IV49rOfjSc84Ql49rOfjfl8jp/4iZ/AFVdcgXe96134N//m38S08/kcD3/4w/HkJz8Zz3rWs9B1HQDgIx/5CO5///vjSU96Ei666CJ87GMfw0te8hL8u3/37/DBD34Qk8lk6zo+6UlPwmc+8xn83M/9HK655prYPr4c73nPe/ChD30Iz372s3GHO9whuiT4yEc+gsc85jHxwPz+978fP/qjP4oPf/jDeOUrXwkA+OIXv4gHPehBuMMd7oCXvvSluPWtb43rr78e1157LT7/+c8nZflf/+t/4brrrsMLXvACXHDBBXjxi1+MRzziEfjLv/xL3PGOd9y6ToUKFTq76G/+5m8AALe85S3xzne+E09+8pNx+9vfHgDwJ3/yJ3ja056GT3ziE3jOc56TPPe+970PT3/60/G85z0Pl1xyCV772tfie77nezCfz/GMZzwDgFiSPeABD8A//MM/4Ad/8Adxz3veE3/+53+O5zznOfjgBz+It73tbYnfzTe/+c34wz/8QzznOc/BJZdcglvd6lanqBUKFSp0JpPnUwDwsY997KziVduUYX9/H5/5zGfwjGc8A7e73e0wn8/xtre9Dd/yLd+CV73qVXjsYx8b83v84x+P17zmNXjiE5+IF7zgBZhOp3jPe96zMi5ZCAHPfOYz8bM/+7P4n//zfxbXKYUKncN0k5vcBI985CPxyle+Ek9+8pMBiFCirmt8+7d/O376p386ph2GAd/8zd+MP/zDP8T3f//346u/+qvxd3/3d3juc5+Lr/u6r8O73/1uHDt2DL/+67+ORz7ykbjooovwspe9DAAwm80A7MbbChUqVOiw6KC4ZE5//dd/jYc97GF4+tOfjvPPPx8f/vCH8eM//uN417velWCPD3vYw9D3PV784hfj9re/Pf7lX/4F73znO2Ns2I997GO48sorccUVV+CVr3wlbnrTm+ITn/gEfvu3fxvz+fyM8MZS6JApFDo0uv766wOA8OhHP3rpXtd1YbFYxM8wDPHeXe5yl3Cf+9wnLBaL5Jmrrroq3OY2twl934cQQnjVq14VAISnPOUpSboXv/jFAUD45Cc/GUII4eMf/3ho2zY87WlPS9J9/vOfD5dcckn4tm/7tnjtcY97XAAQXvnKV66t2zAMYbFYhL/7u78LAMJv/MZvxHss19/+7d+uzeMnfuInVqa79NJLQ9M04S//8i/X5tH3fVgsFuGXfumXQtM04TOf+UwIIYR3v/vdAUB485vfvPZ5AOHWt751+NznPhevXX/99aGu6/CiF71o7bOFChU6O4g86U/+5E/CYrEIn//858Nb3vKWcMtb3jJceOGF4frrr0/Sk6+84AUvCBdffHHCny+99NJQVVV43/velzzzoAc9KNzkJjcJX/ziF0MIIbzoRS8KdV2H6667Lkn3xje+MQAI//t//+94DUC46KKLIv8qVKjQuUe78qkQznxetW0ZcuIe+YlPfGK4z33uE6//wR/8QQAQfuiHfmjje6+88spwww03hG/91m8NF110UXjb2962VZkLFSp09Ij89brrrgvXXnttABD+7M/+LIQQwld8xVeExz/+8SGEEO52t7uFBzzgASGEEF73utcFAOFNb3pTktd1110XAISXvexl8Zp/bh2t4m0hCN963OMed/BKFipUqNAKOggu+dznPjesg4uJB77jHe8IAML73//+EEII//Iv/xIAhJ/+6Z9e+Sz3mPn+sNC5Q8U102mif/tv/y0mk0n8/I//8T8AiObbhz/84egHuOu6+HnYwx6GT37yk/jLv/zLJK+HP/zhye973vOeAMyM///8n/+Druvw2Mc+Nslvb28PD3jAA6I7JE/f+q3funTtn/7pn/Bd3/Vd+P/+v/8PbdtiMpng0ksvBQB86EMfOrEGGaF73vOe+LIv+7Kl6+9973vx8Ic/HBdffDGapsFkMsFjH/tY9H2Pv/qrvwIA3OlOd8LNbnYz/Pf//t9x9dVX4y/+4i9Wvufrv/7rk+Bkt771rXGrW90qtl+hQoWOBn3VV30VJpMJLrzwQlx11VW45JJL8Na3vhW3vvWt8fa3vx3f+I3fiIsuuijylec85zn49Kc/jX/6p39K8rnb3e6Ge93rXsm1xzzmMfjc5z6H97znPQCAt7zlLbj73e+Oe9/73gnffchDHpK4oSM98IEPxM1udrNDrX+hQoXO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8TM/8zN49KMfjXe/+91r83HZZZfh3HPPxX//7/8dgMxBH/SgB+HTn/50WsO//OUvxxOf+ER87/d+L170ohfhP/7jP3DJJZdkChPHMYjgOJB49atfHQCED3zgA2GxWISvfe1r4e1vf3s444wzwsknnxy+8IUvhPe85z0BQHjPe94zms5yuQxf//rXw4knnhh+/dd/PR2/3e1uF777u797ZR5+7Md+LIw1sbPPPjs89rGPTb9/8Rd/MQAI73rXu7Z6T4fDcfDw7//+7+Ee97hHABAAhOl0Gr792789PPe5zw1f+9rX0nW3ve1twz3vec/e/Z/61KdCXdfhRS96UTp29dVXh9NOOy08/vGPT8coJx/zmMdk93/84x8PAMLFF1+cHf+bv/mbACD83M/9XDr2rGc9KwAIX/7yl4/ofouu68JisQif+cxnAoDwtre9rfecF7zgBdk9d7zjHQOA8OY3vzkdWywW4YwzzggPe9jDBp/jcDium9hk7hZCODDzt7PPPjtUVRU+8pGPZMfvd7/7hetf//rhyiuvHLxvuVyGxWIRfuiHfiicf/756fg73vGOACC89KUvza5/7nOfGwCEZz3rWVvn0eFwXPdB2Vl+dnZ2wkte8pLR+9q2DYvFIvziL/5iOO2000LXdenc2Bzzgx/8YAAQXv3qV/fOnXvuueH8888Pi8UiO37RRReFs846K7Rtm+W3nH/ac3/7t3+bjv3v//2/A4Dwu7/7u2tKwuFwHA/4whe+EACERz7ykRtdf8973jOTZy972csCgPCmN70pu+55z3teABD+7M/+LB078cQTs3kfQVlVrncvvfTSACB8/vOfH33+pz/96QAg3P72tw/L5TIdp6x7/etfH0IQGX3mmWeGu971rtkzPvOZz4TpdBrOPvvsjd7fcd2Dh2Y64Ljb3e6G6XSKk08+GRdddBHOPPNMvPOd78SNb3zjweu//vWvJ0vayWSCyWSCk046CVdeeSU+/vGPp+u+7du+De985zvxtKc9De9973tx9dVXH1U+3/nOd+LWt741vvM7v/Oo0nE4HMc+TjvtNPzVX/0VPvjBD+JXf/VX8dCHPhSf/OQn8fSnPx23v/3te2GLStzylrfERRddhJe85CXJ1f51r3sdrrjiCvz4j/947/rv/d7vzX6/5z3vAYCe5dq3fdu34bzzzsNf/MVfrHz+Nvd/6Utfwo/+6I/iG77hGzCZTDCdTnH22WcDQCZziYsuuij7fd5556GqKjzwgQ9MxyaTCW51q1utDOPicDiuu9h27gYcu/O32972tviWb/mW7NijHvUo/Od//ic+/OEPp2N/8Ad/gLvf/e446aSTkqx85Stfmb3b+973PgDiOWZRWhY7HI6Dide85jX44Ac/iA9+8IN45zvficc+9rH4sR/7Mbz4xS9O17z73e/Gd37nd+KUU05B0zSYTqf4hV/4BVxxxRX40pe+dMTP/ud//mf80z/9U9rHZ7lcps+DHvQgfP7zn8cnPvGJ7J5y/gmIvLrRjW6UQuQBwG/+5m/ijDPOwPd///cfcf4cDoeDePe7340TTzwRD3/4w7PjXLuuW+talOGf7nCHOwBYHU6UuPDCC9E0zei9n/jEJ/CFL3yhN6+7+c1vjrvf/e4b59Fx3YOHZjrgeM1rXoPzzjsPk8kEN77xjUdd64lHPepR+Iu/+As885nPxF3uchdc//rXR1VVeNCDHpQtVn/jN34DN7vZzfDGN74Rz3ve83Do0CF813d9F57//Ofjm77pm7bO55e//OXkIutwOBwAcOc73zltOrhYLPCzP/uzeNGLXoRLL7107R4IT37yk3Hf+94X73rXu3D/+98fl112GS644ALc6U536l1bykWGKBmSlze5yU3WTqw2vb/rOtz//vfH5z73OTzzmc/E7W9/e5x44onoug53u9vdBgnCU089Nfs9m81wwgkn4NChQ73j//mf/7kynw6H47qJbeduwLE7fzvzzDNHj1GWvvnNb8b3fd/34RGPeAR+5md+BmeeeSYmkwle+tKX4lWvelW674orrsBkMunJyVUKHIfDcXBw3nnn9Tar/sxnPoP/7//7//DoRz8an/zkJ3H/+98f97rXvfCKV7wCN7vZzTCbzfDWt74Vv/Irv3JUitkvfvGLAICnPvWpKaxcidKQZki27+zs4IlPfCJe8IIX4PnPfz4WiwXe9KY34ad/+qcH48A7HI7jD6effjpOOOEEfPrTnz6i+6+44gqceeaZqKoqO36jG90Ik8kkzb82wWmnnZb9ppzaRJ6uu5f5GJrH3fjGNz7i93dc+3BFxAFHOSFbhf/4j//A29/+djzrWc/C0572tHScsXktTjzxRFxyySW45JJL8MUvfjFZ1z34wQ8+os2lzzjjjN4mrg6H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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# 클래스 별 이미지 내 bbox 위치 분포\n", + "fig, axes = plt.subplots(2, 5, figsize=(20, 8))\n", + "\n", + "for i, ax in enumerate(axes.flat):\n", + " ax.imshow(background)\n", + " ax.set_title(num_to_label(i))\n", + " ax.axis('off')\n", + " for j, x in globals()['class{}'.format(i)].iterrows():\n", + " draw_bbox1(x['x_min'], x['y_min'], x['width'], x['height'], alpha=0.02)" + ] + }, + { + "cell_type": "markdown", + "id": "4d1f907e-c2df-4127-84a2-5f7d99c283f3", + "metadata": {}, + "source": [ + "- 클래스 별 경향성이 보인다기보다 꽤 넓은 범위에 걸쳐 나타나며, 대체로 중간에 위치하는 듯함" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/EDA/heatmap.png b/EDA/heatmap.png new file mode 100644 index 00000000..23ba195b Binary files /dev/null and b/EDA/heatmap.png differ diff --git a/README.md b/README.md new file mode 100644 index 00000000..fefa36ce --- /dev/null +++ b/README.md @@ -0,0 +1,173 @@ +# 재활용 품목 분류를 위한 Object Detection + +## 🥇 팀 구성원 + + + +
+ +## 🗒️ 프로젝트 개요 + +바야흐로 대량 생산, 대량 소비의 시대. 우리는 많은 물건이 대량으로 생산되고, 소비되는 시대를 살고 있습니다. 하지만 이러한 문화는 '쓰레기 대란', '매립지 부족'과 같은 여러 사회 문제를 낳고 있습니다. + +분리수거는 이러한 환경 부담을 줄일 수 있는 방법 중 하나입니다. 잘 분리배출 된 쓰레기는 자원으로서 가치를 인정받아 재활용되지만, 잘못 분리배출 되면 그대로 폐기물로 분류되어 매립 또는 소각되기 때문입니다. + +따라서 우리는 사진에서 쓰레기를 Detection 하는 모델을 만들어 이러한 문제점을 해결해보고자 합니다. 문제 해결을 위한 데이터셋으로는 일반 쓰레기, 플라스틱, 종이, 유리 등 10 종류의 쓰레기가 찍힌 사진 데이터셋이 제공됩니다. + +여러분에 의해 만들어진 우수한 성능의 모델은 쓰레기장에 설치되어 정확한 분리수거를 돕거나, 어린아이들의 분리수거 교육 등에 사용될 수 있을 것입니다. 부디 지구를 위기로부터 구해주세요! 🌎 + + +
+ +## 📅 프로젝트 일정 + +프로젝트 전체 일정 + +- 2024.09.30 (월) 10:00 ~ 2024.10.24 (목) 19:00 + +![image](https://github.com/user-attachments/assets/e6d03619-fe9b-4b14-8266-e169c765f9a0) + + +## 💻 개발 환경 + +```bash +- Language : Python +- Environment + - CPU : Intel(R) Xeon(R) Gold 5120 + - GPU : Tesla V100-SXM2 32GB × 1 +- Framework : PyTorch +- Collaborative Tool : Git, Wandb, Notion +``` + +## 🏆 프로젝트 결과 + +- Public 1등, Private 1등 + + ![image](https://github.com/user-attachments/assets/4956fa94-51b7-498a-b8c8-4cc7dd8cea33) + +## ✏️ Wrap-Up Report + +- 프로젝트의 전반적인 내용은 아래 랩업 리포트를 참고 바랍니다. +- [Wrap-Up Report](https://drive.google.com/file/d/13dfWdaCJQfc2CzF-bT4asWYKytZWTk9m/view?usp=sharing) + +## 📁 데이터셋 구조 + +``` +📦data + ┣ 📜train.json + ┣ 📜test.json + ┃ + ┣ 📂test + ┃ ┣ 📜0000.JPG + ┃ ┣ 📜0001.JPG + ┃ ┣ 📜0002.JPG + ┃ ┗ ... + ┣ 📂train + ┃ ┣ 📜0000.JPG + ┃ ┣ 📜0001.JPG + ┃ ┣ 📜0002.JPG + ┃ ┗ ... +``` + +- 학습에 사용할 이미지는 4,883개, 추론에 사용할 이미지는 4,871개로 각각 data/train/, data/test 아래에 저장되어 있습니다. +- 제공되는 이미지 데이터셋은 10개 클래스의 쓰레기가 찍힌 1024 x 1024 크기의 사진으로 구성되어 있습니다. +- train.json과 test.json은 coco format으로 된 각 이미지에 대한 annotation file 입니다. + +
+ +## 📁 프로젝트 구조 + +``` +📦level2-objectdetection-cv-05 + ┣ 📂.github + ┃ ┗ 📜.keep + ┣ 📂EDA + ┃ ┣ 📜EDA.ipynb + ┃ ┣ 📜heatmap.png + ┣ 📂mmdetection + ┃ ┣ 📜Co-detr + ┃ ┃ ┗ 📜co-detrl.py + ┃ ┣ 📜DiNO + ┃ ┃ ┗ 📜dino_Swin_L_baseline.py + ┃ ┣ 📜_base_ + ┃ ┃ ┗ 📜default_dataset.py + ┃ ┃ ┗ 📜default_multi_dataset.py + ┃ ┃ ┗ 📜simple_augmentation_dataset.py + ┃ ┃ ┗ 📜heavy_augmentation_dataset.py + ┃ ┃ ┗ 📜lsj_mosaic_augmentation_dataset.py + ┃ ┃ ┗ 📜default_runtime.py + ┃ ┃ ┗ 📜default_tta.py + ┃ ┣ 📜cascade-RCNN + ┃ ┃ ┗ 📜cascade-rcbb_convnextV2.py + ┃ ┃ ┗ 📜cascade-rcnn_swin_L.py + ┃ ┣ 📜ddq + ┃ ┃ ┗ 📜ddq_swinl.py + ┣ 📂utils + ┃ ┣ 📜SR_X2_preprocessing + ┃ ┃ ┗ 📜4_crop.ipynb + ┃ ┃ ┗ 📜5_random.ipynb + ┃ ┃ ┗ 📜Update_json.ipynb + ┃ ┣ 📜mmdetection + ┃ ┃ ┗ 📜inference.py + ┃ ┃ ┗ 📜inference_mmdet_v2.py + ┃ ┃ ┗ 📜train.py + ┃ ┣ 📜MultiLabelStratifiedKFold_COCO.py + ┃ ┣ 📜Pascal_to_coco.ipynb + ┃ ┣ 📜StratifiedKFold_COCO.py + ┃ ┣ 📜emsemble.py + ┗ ┗ 📜inference_visualizer.py + +``` +
+ +## 🧱 Structure + +![image](https://github.com/user-attachments/assets/b2e1d2b4-822e-4a39-86b1-97319114f8c8) + +- CO-DETR + DDQ-DETR + Cascade R-CNN + YOLO(emsemble) 모델 사용 + + + + + +
diff --git a/mmdetection/.circleci/config.yml b/mmdetection/.circleci/config.yml new file mode 100644 index 00000000..1a24b82d --- /dev/null +++ b/mmdetection/.circleci/config.yml @@ -0,0 +1,34 @@ +version: 2.1 + +# this allows you to use CircleCI's dynamic configuration feature +setup: true + +# the path-filtering orb is required to continue a pipeline based on +# the path of an updated fileset +orbs: + path-filtering: circleci/path-filtering@0.1.2 + +workflows: + # the always-run workflow is always triggered, regardless of the pipeline parameters. + always-run: + jobs: + # the path-filtering/filter job determines which pipeline + # parameters to update. + - path-filtering/filter: + name: check-updated-files + # 3-column, whitespace-delimited mapping. One mapping per + # line: + # + mapping: | + mmdet/.* lint_only false + requirements/.* lint_only false + tests/.* lint_only false + tools/.* lint_only false + configs/.* lint_only false + .circleci/.* lint_only false + base-revision: dev-3.x + # this is the path of the configuration we should trigger once + # path filtering and pipeline parameter value updates are + # complete. In this case, we are using the parent dynamic + # configuration itself. + config-path: .circleci/test.yml diff --git a/mmdetection/.circleci/docker/Dockerfile b/mmdetection/.circleci/docker/Dockerfile new file mode 100644 index 00000000..d9cf8cc7 --- /dev/null +++ b/mmdetection/.circleci/docker/Dockerfile @@ -0,0 +1,11 @@ +ARG PYTORCH="1.8.1" +ARG CUDA="10.2" +ARG CUDNN="7" + +FROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel + +# To fix GPG key error when running apt-get update +RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/3bf863cc.pub +RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/7fa2af80.pub + +RUN apt-get update && apt-get install -y ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 libgl1-mesa-glx diff --git a/mmdetection/.circleci/test.yml b/mmdetection/.circleci/test.yml new file mode 100644 index 00000000..e5e14d3d --- /dev/null +++ b/mmdetection/.circleci/test.yml @@ -0,0 +1,210 @@ +version: 2.1 + +# the default pipeline parameters, which will be updated according to +# the results of the path-filtering orb +parameters: + lint_only: + type: boolean + default: true + +jobs: + lint: + docker: + - image: cimg/python:3.7.4 + steps: + - checkout + - run: + name: Install pre-commit hook + command: | + pip install pre-commit + pre-commit install + - run: + name: Linting + command: pre-commit run --all-files + - run: + name: Check docstring coverage + command: | + pip install interrogate + interrogate -v --ignore-init-method --ignore-module --ignore-nested-functions --ignore-magic --ignore-regex "__repr__" --fail-under 85 mmdet + + build_cpu: + parameters: + # The python version must match available image tags in + # https://circleci.com/developer/images/image/cimg/python + python: + type: string + torch: + type: string + torchvision: + type: string + docker: + - image: cimg/python:<< parameters.python >> + resource_class: large + steps: + - checkout + - run: + name: Install Libraries + command: | + sudo apt-get update + sudo apt-get install -y ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 libgl1-mesa-glx libjpeg-dev zlib1g-dev libtinfo-dev libncurses5 + - run: + name: Configure Python & pip + command: | + pip install --upgrade pip + pip install wheel + - run: + name: Install PyTorch + command: | + python -V + python -m pip install torch==<< parameters.torch >>+cpu torchvision==<< parameters.torchvision >>+cpu -f https://download.pytorch.org/whl/torch_stable.html + - when: + condition: + equal: ["3.9.0", << parameters.python >>] + steps: + - run: pip install "protobuf <= 3.20.1" && sudo apt-get update && sudo apt-get -y install libprotobuf-dev protobuf-compiler cmake + - run: pip install dsdl + - run: + name: Install mmdet dependencies + # numpy may be downgraded after building pycocotools, which causes `ImportError: numpy.core.multiarray failed to import` + # force reinstall pycocotools to ensure pycocotools being built under the currenct numpy + command: | + python -m pip install git+ssh://git@github.com/open-mmlab/mmengine.git@main + pip install -U openmim + mim install 'mmcv >= 2.0.0rc4' + pip install -r requirements/tests.txt -r requirements/optional.txt + pip install --force-reinstall pycocotools + pip install albumentations>=0.3.2 --no-binary imgaug,albumentations + pip install -r requirements/tracking.txt + pip install git+https://github.com/cocodataset/panopticapi.git + pip install git+https://github.com/JonathonLuiten/TrackEval.git + - run: + name: Build and install + command: | + pip install -e . + - run: + name: Run unittests + command: | + python -m coverage run --branch --source mmdet -m pytest tests/ + python -m coverage xml + python -m coverage report -m + + build_cuda: + parameters: + torch: + type: string + cuda: + type: enum + enum: ["11.1", "11.7", "11.8"] + cudnn: + type: integer + default: 8 + machine: + image: linux-cuda-11:default + # docker_layer_caching: true + resource_class: gpu.nvidia.small.multi + steps: + - checkout + - run: + # CLoning repos in VM since Docker doesn't have access to the private key + name: Clone Repos + command: | + git clone -b main --depth 1 ssh://git@github.com/open-mmlab/mmengine.git /home/circleci/mmengine + - run: + name: Install nvidia-container-toolkit and Restart Docker + command: | + sudo apt-get update + sudo apt-get install -y nvidia-container-toolkit + sudo systemctl restart docker + - run: + name: Build Docker image + command: | + docker build .circleci/docker -t mmdetection:gpu --build-arg PYTORCH=<< parameters.torch >> --build-arg CUDA=<< parameters.cuda >> --build-arg CUDNN=<< parameters.cudnn >> + docker run --gpus all -t -d -v /home/circleci/project:/mmdetection -v /home/circleci/mmengine:/mmengine -w /mmdetection --name mmdetection mmdetection:gpu + docker exec mmdetection apt-get install -y git + - run: + name: Install mmdet dependencies + command: | + docker exec mmdetection pip install -e /mmengine + docker exec mmdetection pip install -U openmim + docker exec mmdetection mim install 'mmcv >= 2.0.0rc4' + docker exec mmdetection pip install -r requirements/tests.txt -r requirements/optional.txt + docker exec mmdetection pip install pycocotools + docker exec mmdetection pip install albumentations>=0.3.2 --no-binary imgaug,albumentations + docker exec mmdetection pip install -r requirements/tracking.txt + docker exec mmdetection pip install git+https://github.com/cocodataset/panopticapi.git + docker exec mmdetection pip install git+https://github.com/JonathonLuiten/TrackEval.git + docker exec mmdetection python -c 'import mmcv; print(mmcv.__version__)' + - run: + name: Build and install + command: | + docker exec mmdetection pip install -e . + - run: + name: Run unittests + command: | + docker exec mmdetection python -m pytest tests/ + +workflows: + pr_stage_lint: + when: << pipeline.parameters.lint_only >> + jobs: + - lint: + name: lint + filters: + branches: + ignore: + - dev-3.x + pr_stage_test: + when: + not: << pipeline.parameters.lint_only >> + jobs: + - lint: + name: lint + filters: + branches: + ignore: + - dev-3.x + - build_cpu: + name: minimum_version_cpu + torch: 1.8.0 + torchvision: 0.9.0 + python: 3.7.16 + requires: + - lint + - build_cpu: + name: maximum_version_cpu + torch: 2.0.0 + torchvision: 0.15.1 + python: 3.9.0 + requires: + - minimum_version_cpu + - hold: + type: approval + requires: + - maximum_version_cpu + - build_cuda: + name: mainstream_version_gpu + torch: 1.8.1 + # Use double quotation mark to explicitly specify its type + # as string instead of number + cuda: "11.1" + requires: + - hold + - build_cuda: + name: maximum_version_gpu + torch: 2.0.0 + cuda: "11.7" + cudnn: 8 + requires: + - hold + merge_stage_test: + when: + not: << pipeline.parameters.lint_only >> + jobs: + - build_cuda: + name: minimum_version_gpu + torch: 1.8.0 + cuda: "11.1" + filters: + branches: + only: + - dev-3.x diff --git a/mmdetection/.dev_scripts/batch_test_list.py b/mmdetection/.dev_scripts/batch_test_list.py new file mode 100644 index 00000000..b28d4034 --- /dev/null +++ b/mmdetection/.dev_scripts/batch_test_list.py @@ -0,0 +1,545 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# missing wider_face/timm_example/strong_baselines/simple_copy_paste/ +# selfsup_pretrain/seesaw_loss/pascal_voc/openimages/lvis/ld/lad/cityscapes/deepfashion + +# yapf: disable +atss = dict( + config='configs/atss/atss_r50_fpn_1x_coco.py', + checkpoint='atss_r50_fpn_1x_coco_20200209-985f7bd0.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/atss/atss_r50_fpn_1x_coco/atss_r50_fpn_1x_coco_20200209-985f7bd0.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=39.4), +) +autoassign = dict( + config='configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py', + checkpoint='auto_assign_r50_fpn_1x_coco_20210413_115540-5e17991f.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/autoassign/auto_assign_r50_fpn_1x_coco/auto_assign_r50_fpn_1x_coco_20210413_115540-5e17991f.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.4), +) +carafe = dict( + config='configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py', + checkpoint='faster_rcnn_r50_fpn_carafe_1x_coco_bbox_mAP-0.386_20200504_175733-385a75b7.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/carafe/faster_rcnn_r50_fpn_carafe_1x_coco/faster_rcnn_r50_fpn_carafe_1x_coco_bbox_mAP-0.386_20200504_175733-385a75b7.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=38.6), +) +cascade_rcnn = [ + dict( + config='configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py', + checkpoint='cascade_rcnn_r50_fpn_1x_coco_20200316-3dc56deb.pth', + eval='bbox', + url='https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco/cascade_rcnn_r50_fpn_1x_coco_20200316-3dc56deb.pth', # noqa + metric=dict(bbox_mAP=40.3), + ), + dict( + config='configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py', + checkpoint='cascade_mask_rcnn_r50_fpn_1x_coco_20200203-9d4dcb24.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco/cascade_mask_rcnn_r50_fpn_1x_coco_20200203-9d4dcb24.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=41.2, segm_mAP=35.9), + ), +] +cascade_rpn = dict( + config='configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py', # noqa + checkpoint='crpn_faster_rcnn_r50_caffe_fpn_1x_coco-c8283cca.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/cascade_rpn/crpn_faster_rcnn_r50_caffe_fpn_1x_coco/crpn_faster_rcnn_r50_caffe_fpn_1x_coco-c8283cca.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.4), +) +centernet = dict( + config='configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py', + checkpoint='centernet_resnet18_dcnv2_140e_coco_20210702_155131-c8cd631f.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/centernet/centernet_resnet18_dcnv2_140e_coco/centernet_resnet18_dcnv2_140e_coco_20210702_155131-c8cd631f.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=29.5), +) +centripetalnet = dict( + config='configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py', # noqa + checkpoint='centripetalnet_hourglass104_mstest_16x6_210e_coco_20200915_204804-3ccc61e5.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/centripetalnet/centripetalnet_hourglass104_mstest_16x6_210e_coco/centripetalnet_hourglass104_mstest_16x6_210e_coco_20200915_204804-3ccc61e5.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=44.7), +) +convnext = dict( + config='configs/convnext/cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py', # noqa + checkpoint='cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco_20220510_201004-3d24f5a4.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/convnext/cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco/cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco_20220510_201004-3d24f5a4.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=51.8, segm_mAP=44.8), +) +cornernet = dict( + config='configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py', + checkpoint='cornernet_hourglass104_mstest_8x6_210e_coco_20200825_150618-79b44c30.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_8x6_210e_coco/cornernet_hourglass104_mstest_8x6_210e_coco_20200825_150618-79b44c30.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=41.2), +) +dcn = dict( + config='configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py', + checkpoint='faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200130-d68aed1e.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200130-d68aed1e.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=41.3), +) +dcnv2 = dict( + config='configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py', + checkpoint='faster_rcnn_r50_fpn_mdpool_1x_coco_20200307-c0df27ff.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdpool_1x_coco/faster_rcnn_r50_fpn_mdpool_1x_coco_20200307-c0df27ff.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=38.7), +) +ddod = dict( + config='configs/ddod/ddod_r50_fpn_1x_coco.py', + checkpoint='ddod_r50_fpn_1x_coco_20220523_223737-29b2fc67.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/ddod/ddod_r50_fpn_1x_coco/ddod_r50_fpn_1x_coco_20220523_223737-29b2fc67.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=41.7), +) +deformable_detr = dict( + config='configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py', + checkpoint='deformable_detr_r50_16x2_50e_coco_20210419_220030-a12b9512.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/deformable_detr/deformable_detr_r50_16x2_50e_coco/deformable_detr_r50_16x2_50e_coco_20210419_220030-a12b9512.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=44.5), +) +detectors = dict( + config='configs/detectors/detectors_htc-r50_1x_coco.py', + checkpoint='detectors_htc_r50_1x_coco-329b1453.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/detectors/detectors_htc_r50_1x_coco/detectors_htc_r50_1x_coco-329b1453.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=49.1, segm_mAP=42.6), +) +detr = dict( + config='configs/detr/detr_r50_8xb2-150e_coco.py', + checkpoint='detr_r50_8x2_150e_coco_20201130_194835-2c4b8974.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/detr/detr_r50_8x2_150e_coco/detr_r50_8x2_150e_coco_20201130_194835-2c4b8974.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.1), +) +double_heads = dict( + config='configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py', + checkpoint='dh_faster_rcnn_r50_fpn_1x_coco_20200130-586b67df.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/double_heads/dh_faster_rcnn_r50_fpn_1x_coco/dh_faster_rcnn_r50_fpn_1x_coco_20200130-586b67df.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.0), +) +dyhead = dict( + config='configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py', + checkpoint='atss_r50_fpn_dyhead_4x4_1x_coco_20211219_023314-eaa620c6.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_r50_fpn_dyhead_4x4_1x_coco/atss_r50_fpn_dyhead_4x4_1x_coco_20211219_023314-eaa620c6.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=43.3), +) +dynamic_rcnn = dict( + config='configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py', + checkpoint='dynamic_rcnn_r50_fpn_1x-62a3f276.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/dynamic_rcnn/dynamic_rcnn_r50_fpn_1x/dynamic_rcnn_r50_fpn_1x-62a3f276.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=38.9), +) +efficientnet = dict( + config='configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py', + checkpoint='retinanet_effb3_fpn_crop896_8x4_1x_coco_20220322_234806-615a0dda.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/efficientnet/retinanet_effb3_fpn_crop896_8x4_1x_coco/retinanet_effb3_fpn_crop896_8x4_1x_coco_20220322_234806-615a0dda.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.5), +) +empirical_attention = dict( + config='configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py', # noqa + checkpoint='faster_rcnn_r50_fpn_attention_1111_1x_coco_20200130-403cccba.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_1111_1x_coco/faster_rcnn_r50_fpn_attention_1111_1x_coco_20200130-403cccba.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.0), +) +faster_rcnn = dict( + config='configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py', + checkpoint='faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.4), +) +fcos = dict( + config='configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py', # noqa + checkpoint='fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_1x_coco-0a0d75a8.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_1x_coco/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_1x_coco-0a0d75a8.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=38.7), +) +foveabox = dict( + config='configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py', + checkpoint='fovea_align_r50_fpn_gn-head_4x4_2x_coco_20200203-8987880d.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r50_fpn_gn-head_4x4_2x_coco/fovea_align_r50_fpn_gn-head_4x4_2x_coco_20200203-8987880d.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.9), +) +fpg = dict( + config='configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py', + checkpoint='mask_rcnn_r50_fpg_crop640_50e_coco_20220311_011857-233b8334.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/fpg/mask_rcnn_r50_fpg_crop640_50e_coco/mask_rcnn_r50_fpg_crop640_50e_coco_20220311_011857-233b8334.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=43.0, segm_mAP=38.1), +) +free_anchor = dict( + config='configs/free_anchor/freeanchor_r50_fpn_1x_coco.py', + checkpoint='retinanet_free_anchor_r50_fpn_1x_coco_20200130-0f67375f.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_r50_fpn_1x_coco/retinanet_free_anchor_r50_fpn_1x_coco_20200130-0f67375f.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=38.7), +) +fsaf = dict( + config='configs/fsaf/fsaf_r50_fpn_1x_coco.py', + checkpoint='fsaf_r50_fpn_1x_coco-94ccc51f.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_r50_fpn_1x_coco/fsaf_r50_fpn_1x_coco-94ccc51f.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.4), +) +gcnet = dict( + config='configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py', # noqa + checkpoint='mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200202-587b99aa.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200202-587b99aa.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=40.4, segm_mAP=36.2), +) +gfl = dict( + config='configs/gfl/gfl_r50_fpn_1x_coco.py', + checkpoint='gfl_r50_fpn_1x_coco_20200629_121244-25944287.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r50_fpn_1x_coco/gfl_r50_fpn_1x_coco_20200629_121244-25944287.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.2), +) +ghm = dict( + config='configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py', + checkpoint='retinanet_ghm_r50_fpn_1x_coco_20200130-a437fda3.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_r50_fpn_1x_coco/retinanet_ghm_r50_fpn_1x_coco_20200130-a437fda3.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.0), +) +gn = dict( + config='configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py', + checkpoint='mask_rcnn_r50_fpn_gn-all_2x_coco_20200206-8eee02a6.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_2x_coco/mask_rcnn_r50_fpn_gn-all_2x_coco_20200206-8eee02a6.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=40.1, segm_mAP=36.4), +) +gn_ws = dict( + config='configs/gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py', + checkpoint='faster_rcnn_r50_fpn_gn_ws-all_1x_coco_20200130-613d9fe2.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_r50_fpn_gn_ws-all_1x_coco/faster_rcnn_r50_fpn_gn_ws-all_1x_coco_20200130-613d9fe2.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=39.7), +) +grid_rcnn = dict( + config='configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py', + checkpoint='grid_rcnn_r50_fpn_gn-head_2x_coco_20200130-6cca8223.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_r50_fpn_gn-head_2x_coco/grid_rcnn_r50_fpn_gn-head_2x_coco_20200130-6cca8223.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.4), +) +groie = dict( + config='configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py', + checkpoint='faster_rcnn_r50_fpn_groie_1x_coco_20200604_211715-66ee9516.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/groie/faster_rcnn_r50_fpn_groie_1x_coco/faster_rcnn_r50_fpn_groie_1x_coco_20200604_211715-66ee9516.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=38.3), +) +guided_anchoring = dict( + config='configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py', # noqa + checkpoint='ga_retinanet_r50_caffe_fpn_1x_coco_20201020-39581c6f.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_r50_caffe_fpn_1x_coco/ga_retinanet_r50_caffe_fpn_1x_coco_20201020-39581c6f.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=36.9), + ) +hrnet = dict( + config='configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py', + checkpoint='faster_rcnn_hrnetv2p_w18_1x_coco_20200130-56651a6d.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w18_1x_coco/faster_rcnn_hrnetv2p_w18_1x_coco_20200130-56651a6d.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=36.9), +) +htc = dict( + config='configs/htc/htc_r50_fpn_1x_coco.py', + checkpoint='htc_r50_fpn_1x_coco_20200317-7332cf16.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r50_fpn_1x_coco/htc_r50_fpn_1x_coco_20200317-7332cf16.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=42.3, segm_mAP=37.4), +) +instaboost = dict( + config='configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py', + checkpoint='mask_rcnn_r50_fpn_instaboost_4x_coco_20200307-d025f83a.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_r50_fpn_instaboost_4x_coco/mask_rcnn_r50_fpn_instaboost_4x_coco_20200307-d025f83a.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=40.6, segm_mAP=36.6), +) +libra_rcnn = dict( + config='configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py', + checkpoint='libra_faster_rcnn_r50_fpn_1x_coco_20200130-3afee3a9.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_r50_fpn_1x_coco/libra_faster_rcnn_r50_fpn_1x_coco_20200130-3afee3a9.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=38.3), +) +mask2former = dict( + config='configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py', + checkpoint='mask2former_r50_lsj_8x2_50e_coco-panoptic_20220326_224516-11a44721.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_r50_lsj_8x2_50e_coco-panoptic/mask2former_r50_lsj_8x2_50e_coco-panoptic_20220326_224516-11a44721.pth', # noqa + eval=['bbox', 'segm', 'PQ'], + metric=dict(PQ=51.9, bbox_mAP=44.8, segm_mAP=41.9), +) +mask_rcnn = dict( + config='configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py', + checkpoint='mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=38.2, segm_mAP=34.7), +) +maskformer = dict( + config='configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py', + checkpoint='maskformer_r50_mstrain_16x1_75e_coco_20220221_141956-bc2699cb.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/maskformer/maskformer_r50_mstrain_16x1_75e_coco/maskformer_r50_mstrain_16x1_75e_coco_20220221_141956-bc2699cb.pth', # noqa + eval='PQ', + metric=dict(PQ=46.9), +) +ms_rcnn = dict( + config='configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py', + checkpoint='ms_rcnn_r50_caffe_fpn_1x_coco_20200702_180848-61c9355e.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r50_caffe_fpn_1x_coco/ms_rcnn_r50_caffe_fpn_1x_coco_20200702_180848-61c9355e.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=38.2, segm_mAP=36.0), +) +nas_fcos = dict( + config='configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py', # noqa + checkpoint='nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco_20200520-1bdba3ce.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/nas_fcos/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco_20200520-1bdba3ce.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=39.4), +) +nas_fpn = dict( + config='configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py', + checkpoint='retinanet_r50_nasfpn_crop640_50e_coco-0ad1f644.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/nas_fpn/retinanet_r50_nasfpn_crop640_50e_coco/retinanet_r50_nasfpn_crop640_50e_coco-0ad1f644.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.5), +) +paa = dict( + config='configs/paa/paa_r50_fpn_1x_coco.py', + checkpoint='paa_r50_fpn_1x_coco_20200821-936edec3.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1x_coco/paa_r50_fpn_1x_coco_20200821-936edec3.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.4), +) +pafpn = dict( + config='configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py', + checkpoint='faster_rcnn_r50_pafpn_1x_coco_bbox_mAP-0.375_20200503_105836-b7b4b9bd.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/pafpn/faster_rcnn_r50_pafpn_1x_coco/faster_rcnn_r50_pafpn_1x_coco_bbox_mAP-0.375_20200503_105836-b7b4b9bd.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.5), +) +panoptic_fpn = dict( + config='configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py', + checkpoint='panoptic_fpn_r50_fpn_1x_coco_20210821_101153-9668fd13.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r50_fpn_1x_coco/panoptic_fpn_r50_fpn_1x_coco_20210821_101153-9668fd13.pth', # noqa + eval='PQ', + metric=dict(PQ=40.2), +) +pisa = dict( + config='configs/pisa/faster-rcnn_r50_fpn_pisa_1x_coco.py', + checkpoint='pisa_faster_rcnn_r50_fpn_1x_coco-dea93523.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_faster_rcnn_r50_fpn_1x_coco/pisa_faster_rcnn_r50_fpn_1x_coco-dea93523.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=38.4), +) +point_rend = dict( + config='configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py', + checkpoint='point_rend_r50_caffe_fpn_mstrain_1x_coco-1bcb5fb4.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/point_rend/point_rend_r50_caffe_fpn_mstrain_1x_coco/point_rend_r50_caffe_fpn_mstrain_1x_coco-1bcb5fb4.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=38.4, segm_mAP=36.3), +) +pvt = dict( + config='configs/pvt/retinanet_pvt-s_fpn_1x_coco.py', + checkpoint='retinanet_pvt-s_fpn_1x_coco_20210906_142921-b6c94a5b.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-s_fpn_1x_coco/retinanet_pvt-s_fpn_1x_coco_20210906_142921-b6c94a5b.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=40.4), +) +queryinst = dict( + config='configs/queryinst/queryinst_r50_fpn_1x_coco.py', + checkpoint='queryinst_r50_fpn_1x_coco_20210907_084916-5a8f1998.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_1x_coco/queryinst_r50_fpn_1x_coco_20210907_084916-5a8f1998.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=42.0, segm_mAP=37.5), +) +regnet = dict( + config='configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py', + checkpoint='mask_rcnn_regnetx-3.2GF_fpn_1x_coco_20200520_163141-2a9d1814.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_1x_coco/mask_rcnn_regnetx-3.2GF_fpn_1x_coco_20200520_163141-2a9d1814.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=40.4, segm_mAP=36.7), +) +reppoints = dict( + config='configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py', + checkpoint='reppoints_moment_r50_fpn_1x_coco_20200330-b73db8d1.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_1x_coco/reppoints_moment_r50_fpn_1x_coco_20200330-b73db8d1.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.0), +) +res2net = dict( + config='configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py', + checkpoint='faster_rcnn_r2_101_fpn_2x_coco-175f1da6.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/res2net/faster_rcnn_r2_101_fpn_2x_coco/faster_rcnn_r2_101_fpn_2x_coco-175f1da6.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=43.0), +) +resnest = dict( + config='configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py', # noqa + checkpoint='faster_rcnn_s50_fpn_syncbn-backbone+head_mstrain-range_1x_coco_20200926_125502-20289c16.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/resnest/faster_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/faster_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco_20200926_125502-20289c16.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=42.0), +) +resnet_strikes_back = dict( + config='configs/resnet_strikes_back/mask-rcnn_r50-rsb-pre_fpn_1x_coco.py', # noqa + checkpoint='mask_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_174054-06ce8ba0.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/mask_rcnn_r50_fpn_rsb-pretrain_1x_coco/mask_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_174054-06ce8ba0.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=41.2, segm_mAP=38.2), +) +retinanet = dict( + config='configs/retinanet/retinanet_r50_fpn_1x_coco.py', + checkpoint='retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_1x_coco/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=36.5), +) +rpn = dict( + config='configs/rpn/rpn_r50_fpn_1x_coco.py', + checkpoint='rpn_r50_fpn_1x_coco_20200218-5525fa2e.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_fpn_1x_coco/rpn_r50_fpn_1x_coco_20200218-5525fa2e.pth', # noqa + eval='proposal_fast', + metric=dict(AR_1000=58.2), +) +sabl = [ + dict( + config='configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py', + checkpoint='sabl_retinanet_r50_fpn_1x_coco-6c54fd4f.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r50_fpn_1x_coco/sabl_retinanet_r50_fpn_1x_coco-6c54fd4f.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.7), + ), + dict( + config='configs/sabl/sabl-faster-rcnn_r50_fpn_1x_coco.py', + checkpoint='sabl_faster_rcnn_r50_fpn_1x_coco-e867595b.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_faster_rcnn_r50_fpn_1x_coco/sabl_faster_rcnn_r50_fpn_1x_coco-e867595b.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=39.9), + ), +] +scnet = dict( + config='configs/scnet/scnet_r50_fpn_1x_coco.py', + checkpoint='scnet_r50_fpn_1x_coco-c3f09857.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r50_fpn_1x_coco/scnet_r50_fpn_1x_coco-c3f09857.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=43.5), +) +scratch = dict( + config='configs/scratch/mask-rcnn_r50-scratch_fpn_gn-all_6x_coco.py', + checkpoint='scratch_mask_rcnn_r50_fpn_gn_6x_bbox_mAP-0.412__segm_mAP-0.374_20200201_193051-1e190a40.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/scratch/mask_rcnn_r50_fpn_gn-all_scratch_6x_coco/scratch_mask_rcnn_r50_fpn_gn_6x_bbox_mAP-0.412__segm_mAP-0.374_20200201_193051-1e190a40.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=41.2, segm_mAP=37.4), +) +solo = dict( + config='configs/solo/decoupled-solo_r50_fpn_1x_coco.py', + checkpoint='decoupled_solo_r50_fpn_1x_coco_20210820_233348-6337c589.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_r50_fpn_1x_coco/decoupled_solo_r50_fpn_1x_coco_20210820_233348-6337c589.pth', # noqa + eval='segm', + metric=dict(segm_mAP=33.9), +) +solov2 = dict( + config='configs/solov2/solov2_r50_fpn_1x_coco.py', + checkpoint='solov2_r50_fpn_1x_coco_20220512_125858-a357fa23.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r50_fpn_1x_coco/solov2_r50_fpn_1x_coco_20220512_125858-a357fa23.pth', # noqa + eval='segm', + metric=dict(segm_mAP=34.8), +) +sparse_rcnn = dict( + config='configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py', + checkpoint='sparse_rcnn_r50_fpn_1x_coco_20201222_214453-dc79b137.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_1x_coco/sparse_rcnn_r50_fpn_1x_coco_20201222_214453-dc79b137.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.9), +) +ssd = [ + dict( + config='configs/ssd/ssd300_coco.py', + checkpoint='ssd300_coco_20210803_015428-d231a06e.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/ssd/ssd300_coco/ssd300_coco_20210803_015428-d231a06e.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=25.5), + ), + dict( + config='configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py', + checkpoint='ssdlite_mobilenetv2_scratch_600e_coco_20210629_110627-974d9307.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/ssd/ssdlite_mobilenetv2_scratch_600e_coco/ssdlite_mobilenetv2_scratch_600e_coco_20210629_110627-974d9307.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=21.3), + ), +] +swin = dict( + config='configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py', + checkpoint='mask_rcnn_swin-t-p4-w7_fpn_1x_coco_20210902_120937-9d6b7cfa.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_1x_coco/mask_rcnn_swin-t-p4-w7_fpn_1x_coco_20210902_120937-9d6b7cfa.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=42.7, segm_mAP=39.3), +) +tood = dict( + config='configs/tood/tood_r50_fpn_1x_coco.py', + checkpoint='tood_r50_fpn_1x_coco_20211210_103425-20e20746.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_1x_coco/tood_r50_fpn_1x_coco_20211210_103425-20e20746.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=42.4), +) +tridentnet = dict( + config='configs/tridentnet/tridentnet_r50-caffe_1x_coco.py', + checkpoint='tridentnet_r50_caffe_1x_coco_20201230_141838-2ec0b530.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_1x_coco/tridentnet_r50_caffe_1x_coco_20201230_141838-2ec0b530.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.6), +) +vfnet = dict( + config='configs/vfnet/vfnet_r50_fpn_1x_coco.py', + checkpoint='vfnet_r50_fpn_1x_coco_20201027-38db6f58.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_1x_coco/vfnet_r50_fpn_1x_coco_20201027-38db6f58.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=41.6), +) +yolact = dict( + config='configs/yolact/yolact_r50_1xb8-55e_coco.py', + checkpoint='yolact_r50_1x8_coco_20200908-f38d58df.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/yolact/yolact_r50_1x8_coco/yolact_r50_1x8_coco_20200908-f38d58df.pth', # noqa + eval=['bbox', 'segm'], + metric=dict(bbox_mAP=31.2, segm_mAP=29.0), +) +yolo = dict( + config='configs/yolo/yolov3_d53_8xb8-320-273e_coco.py', + checkpoint='yolov3_d53_320_273e_coco-421362b6.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_320_273e_coco/yolov3_d53_320_273e_coco-421362b6.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=27.9), +) +yolof = dict( + config='configs/yolof/yolof_r50-c5_8xb8-1x_coco.py', + checkpoint='yolof_r50_c5_8x8_1x_coco_20210425_024427-8e864411.pth', + url='https://download.openmmlab.com/mmdetection/v2.0/yolof/yolof_r50_c5_8x8_1x_coco/yolof_r50_c5_8x8_1x_coco_20210425_024427-8e864411.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=37.5), +) +yolox = dict( + config='configs/yolox/yolox_tiny_8xb8-300e_coco.py', + checkpoint='yolox_tiny_8x8_300e_coco_20211124_171234-b4047906.pth', # noqa + url='https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_tiny_8x8_300e_coco/yolox_tiny_8x8_300e_coco_20211124_171234-b4047906.pth', # noqa + eval='bbox', + metric=dict(bbox_mAP=31.8), +) +# yapf: enable diff --git a/mmdetection/.dev_scripts/batch_train_list.txt b/mmdetection/.dev_scripts/batch_train_list.txt new file mode 100644 index 00000000..9a4f6778 --- /dev/null +++ b/mmdetection/.dev_scripts/batch_train_list.txt @@ -0,0 +1,80 @@ +configs/albu_example/mask-rcnn_r50_fpn_albu_1x_coco.py +configs/atss/atss_r50_fpn_1x_coco.py +configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py +configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py +configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py +configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py +configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py +configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py +configs/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco.py +configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py +configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py +configs/convnext/mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco.py +configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py +configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py +configs/ddod/ddod_r50_fpn_1x_coco.py +configs/detectors/detectors_htc-r50_1x_coco.py +configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py +configs/detr/detr_r50_8xb2-150e_coco.py +configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py +configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py +configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py +configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py +configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py +configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py +configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-1x_coco.py +configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py +configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py +configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py +configs/free_anchor/freeanchor_r50_fpn_1x_coco.py +configs/fsaf/fsaf_r50_fpn_1x_coco.py +configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py +configs/gfl/gfl_r50_fpn_1x_coco.py +configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py +configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py +configs/gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py +configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py +configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py +configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py +configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py +configs/htc/htc_r50_fpn_1x_coco.py +configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py +configs/lad/lad_r50-paa-r101_fpn_2xb8_coco_1x.py +configs/ld/ld_r18-gflv1-r101_fpn_1x_coco.py +configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py +configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py +configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py +configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py +configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py +configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py +configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py +configs/paa/paa_r50_fpn_1x_coco.py +configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py +configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py +configs/pisa/mask-rcnn_r50_fpn_pisa_1x_coco.py +configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py +configs/pvt/retinanet_pvt-t_fpn_1x_coco.py +configs/queryinst/queryinst_r50_fpn_1x_coco.py +configs/regnet/retinanet_regnetx-800MF_fpn_1x_coco.py +configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py +configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py +configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py +configs/resnet_strikes_back/retinanet_r50-rsb-pre_fpn_1x_coco.py +configs/retinanet/retinanet_r50-caffe_fpn_1x_coco.py +configs/rpn/rpn_r50_fpn_1x_coco.py +configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py +configs/scnet/scnet_r50_fpn_1x_coco.py +configs/scratch/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py +configs/solo/solo_r50_fpn_1x_coco.py +configs/solov2/solov2_r50_fpn_1x_coco.py +configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py +configs/ssd/ssd300_coco.py +configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py +configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py +configs/tood/tood_r50_fpn_1x_coco.py +'configs/tridentnet/tridentnet_r50-caffe_1x_coco.py +configs/vfnet/vfnet_r50_fpn_1x_coco.py +configs/yolact/yolact_r50_8xb8-55e_coco.py +configs/yolo/yolov3_d53_8xb8-320-273e_coco.py +configs/yolof/yolof_r50-c5_8xb8-1x_coco.py +configs/yolox/yolox_tiny_8xb8-300e_coco.py diff --git a/mmdetection/.dev_scripts/benchmark_filter.py b/mmdetection/.dev_scripts/benchmark_filter.py new file mode 100644 index 00000000..178cd9cb --- /dev/null +++ b/mmdetection/.dev_scripts/benchmark_filter.py @@ -0,0 +1,167 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp + + +def parse_args(): + parser = argparse.ArgumentParser(description='Filter configs to train') + parser.add_argument( + '--basic-arch', + action='store_true', + help='to train models in basic arch') + parser.add_argument( + '--datasets', action='store_true', help='to train models in dataset') + parser.add_argument( + '--data-pipeline', + action='store_true', + help='to train models related to data pipeline, e.g. augmentations') + parser.add_argument( + '--nn-module', + action='store_true', + help='to train models related to neural network modules') + parser.add_argument( + '--model-options', + nargs='+', + help='custom options to special model benchmark') + parser.add_argument( + '--out', + type=str, + default='batch_train_list.txt', + help='output path of gathered metrics to be stored') + args = parser.parse_args() + return args + + +basic_arch_root = [ + 'atss', 'autoassign', 'cascade_rcnn', 'cascade_rpn', 'centripetalnet', + 'cornernet', 'detectors', 'deformable_detr', 'detr', 'double_heads', + 'dynamic_rcnn', 'faster_rcnn', 'fcos', 'foveabox', 'fp16', 'free_anchor', + 'fsaf', 'gfl', 'ghm', 'grid_rcnn', 'guided_anchoring', 'htc', 'ld', + 'libra_rcnn', 'mask_rcnn', 'ms_rcnn', 'nas_fcos', 'paa', 'pisa', + 'point_rend', 'reppoints', 'retinanet', 'rpn', 'sabl', 'ssd', 'tridentnet', + 'vfnet', 'yolact', 'yolo', 'sparse_rcnn', 'scnet', 'yolof', 'centernet' +] + +datasets_root = [ + 'wider_face', 'pascal_voc', 'cityscapes', 'lvis', 'deepfashion' +] + +data_pipeline_root = ['albu_example', 'instaboost'] + +nn_module_root = [ + 'carafe', 'dcn', 'empirical_attention', 'gcnet', 'gn', 'gn+ws', 'hrnet', + 'pafpn', 'nas_fpn', 'regnet', 'resnest', 'res2net', 'groie' +] + +benchmark_pool = [ + 'configs/albu_example/mask_rcnn_r50_fpn_albu_1x_coco.py', + 'configs/atss/atss_r50_fpn_1x_coco.py', + 'configs/autoassign/autoassign_r50_fpn_8x2_1x_coco.py', + 'configs/carafe/mask_rcnn_r50_fpn_carafe_1x_coco.py', + 'configs/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco.py', + 'configs/cascade_rpn/crpn_faster_rcnn_r50_caffe_fpn_1x_coco.py', + 'configs/centernet/centernet_resnet18_dcnv2_140e_coco.py', + 'configs/centripetalnet/' + 'centripetalnet_hourglass104_mstest_16x6_210e_coco.py', + 'configs/cityscapes/mask_rcnn_r50_fpn_1x_cityscapes.py', + 'configs/cornernet/' + 'cornernet_hourglass104_mstest_8x6_210e_coco.py', + 'configs/dcn/mask_rcnn_r50_fpn_mdconv_c3-c5_1x_coco.py', + 'configs/dcn/faster_rcnn_r50_fpn_dpool_1x_coco.py', + 'configs/dcn/faster_rcnn_r50_fpn_mdpool_1x_coco.py', + 'configs/dcn/mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco.py', + 'configs/deformable_detr/deformable_detr_r50_16x2_50e_coco.py', + 'configs/detectors/detectors_htc_r50_1x_coco.py', + 'configs/detr/detr_r50_8x2_150e_coco.py', + 'configs/double_heads/dh_faster_rcnn_r50_fpn_1x_coco.py', + 'configs/dynamic_rcnn/dynamic_rcnn_r50_fpn_1x_coco.py', + 'configs/empirical_attention/faster_rcnn_r50_fpn_attention_1111_dcn_1x_coco.py', # noqa + 'configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py', + 'configs/faster_rcnn/faster_rcnn_r50_fpn_ohem_1x_coco.py', + 'configs/faster_rcnn/faster_rcnn_r50_caffe_fpn_1x_coco.py', + 'configs/faster_rcnn/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco.py', + 'configs/faster_rcnn/faster_rcnn_r50_caffe_dc5_mstrain_1x_coco.py', + 'configs/fcos/fcos_center_r50_caffe_fpn_gn-head_4x4_1x_coco.py', + 'configs/foveabox/fovea_align_r50_fpn_gn-head_4x4_2x_coco.py', + 'configs/retinanet/retinanet_r50_fpn_fp16_1x_coco.py', + 'configs/mask_rcnn/mask_rcnn_r50_fpn_fp16_1x_coco.py', + 'configs/free_anchor/retinanet_free_anchor_r50_fpn_1x_coco.py', + 'configs/fsaf/fsaf_r50_fpn_1x_coco.py', + 'configs/gcnet/mask_rcnn_r50_fpn_r4_gcb_c3-c5_1x_coco.py', + 'configs/gfl/gfl_r50_fpn_1x_coco.py', + 'configs/ghm/retinanet_ghm_r50_fpn_1x_coco.py', + 'configs/gn/mask_rcnn_r50_fpn_gn-all_2x_coco.py', + 'configs/gn+ws/mask_rcnn_r50_fpn_gn_ws-all_2x_coco.py', + 'configs/grid_rcnn/grid_rcnn_r50_fpn_gn-head_2x_coco.py', + 'configs/groie/faster_rcnn_r50_fpn_groie_1x_coco.py', + 'configs/guided_anchoring/ga_faster_r50_caffe_fpn_1x_coco.py', + 'configs/hrnet/mask_rcnn_hrnetv2p_w18_1x_coco.py', + 'configs/htc/htc_r50_fpn_1x_coco.py', + 'configs/instaboost/mask_rcnn_r50_fpn_instaboost_4x_coco.py', + 'configs/ld/ld_r18_gflv1_r101_fpn_coco_1x.py', + 'configs/libra_rcnn/libra_faster_rcnn_r50_fpn_1x_coco.py', + 'configs/lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_1x_lvis_v1.py', + 'configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco.py', + 'configs/ms_rcnn/ms_rcnn_r50_caffe_fpn_1x_coco.py', + 'configs/nas_fcos/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco.py', + 'configs/nas_fpn/retinanet_r50_nasfpn_crop640_50e_coco.py', + 'configs/paa/paa_r50_fpn_1x_coco.py', + 'configs/pafpn/faster_rcnn_r50_pafpn_1x_coco.py', + 'configs/pisa/pisa_mask_rcnn_r50_fpn_1x_coco.py', + 'configs/point_rend/point_rend_r50_caffe_fpn_mstrain_1x_coco.py', + 'configs/regnet/mask_rcnn_regnetx-3.2GF_fpn_1x_coco.py', + 'configs/reppoints/reppoints_moment_r50_fpn_gn-neck+head_1x_coco.py', + 'configs/res2net/faster_rcnn_r2_101_fpn_2x_coco.py', + 'configs/resnest/' + 'mask_rcnn_s50_fpn_syncbn-backbone+head_mstrain_1x_coco.py', + 'configs/retinanet/retinanet_r50_caffe_fpn_1x_coco.py', + 'configs/rpn/rpn_r50_fpn_1x_coco.py', + 'configs/sabl/sabl_retinanet_r50_fpn_1x_coco.py', + 'configs/ssd/ssd300_coco.py', + 'configs/tridentnet/tridentnet_r50_caffe_1x_coco.py', + 'configs/vfnet/vfnet_r50_fpn_1x_coco.py', + 'configs/yolact/yolact_r50_1x8_coco.py', + 'configs/yolo/yolov3_d53_320_273e_coco.py', + 'configs/sparse_rcnn/sparse_rcnn_r50_fpn_1x_coco.py', + 'configs/scnet/scnet_r50_fpn_1x_coco.py', + 'configs/yolof/yolof_r50_c5_8x8_1x_coco.py', +] + + +def main(): + args = parse_args() + + benchmark_type = [] + if args.basic_arch: + benchmark_type += basic_arch_root + if args.datasets: + benchmark_type += datasets_root + if args.data_pipeline: + benchmark_type += data_pipeline_root + if args.nn_module: + benchmark_type += nn_module_root + + special_model = args.model_options + if special_model is not None: + benchmark_type += special_model + + config_dpath = 'configs/' + benchmark_configs = [] + for cfg_root in benchmark_type: + cfg_dir = osp.join(config_dpath, cfg_root) + configs = os.scandir(cfg_dir) + for cfg in configs: + config_path = osp.join(cfg_dir, cfg.name) + if (config_path in benchmark_pool + and config_path not in benchmark_configs): + benchmark_configs.append(config_path) + + print(f'Totally found {len(benchmark_configs)} configs to benchmark') + with open(args.out, 'w') as f: + for config in benchmark_configs: + f.write(config + '\n') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/.dev_scripts/benchmark_full_models.txt b/mmdetection/.dev_scripts/benchmark_full_models.txt new file mode 100644 index 00000000..eae7235f --- /dev/null +++ b/mmdetection/.dev_scripts/benchmark_full_models.txt @@ -0,0 +1,96 @@ +albu_example/mask-rcnn_r50_fpn_albu_1x_coco.py +atss/atss_r50_fpn_1x_coco.py +autoassign/autoassign_r50-caffe_fpn_1x_coco.py +boxinst/boxinst_r50_fpn_ms-90k_coco.py +carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py +cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py +cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py +cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py +centernet/centernet-update_r50-caffe_fpn_ms-1x_coco.py +centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py +condinst/condinst_r50_fpn_ms-poly-90k_coco_instance.py +conditional_detr/conditional-detr_r50_8xb2-50e_coco.py +convnext/mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco.py +cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py +dab_detr/dab-detr_r50_8xb2-50e_coco.py +dcn/mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py +dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py +ddod/ddod_r50_fpn_1x_coco.py +deformable_detr/deformable-detr_r50_16xb2-50e_coco.py +detectors/detectors_htc-r50_1x_coco.py +detr/detr_r50_8xb2-150e_coco.py +dino/dino-4scale_r50_8xb2-12e_coco.py +double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py +dyhead/atss_r50_fpn_dyhead_1x_coco.py +dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py +efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py +empirical_attention/faster-rcnn_r50-attn0010-dcn_fpn_1x_coco.py +faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py +fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py +foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py +fpg/retinanet_r50_fpg_crop640_50e_coco.py +free_anchor/freeanchor_r50_fpn_1x_coco.py +fsaf/fsaf_r50_fpn_1x_coco.py +gcnet/mask-rcnn_r50-gcb-r4-c3-c5_fpn_1x_coco.py +gfl/gfl_r50_fpn_1x_coco.py +glip/glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py +ghm/retinanet_r50_fpn_ghm-1x_coco.py +gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py +gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py +grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py +groie/faste-rcnn_r50_fpn_groie_1x_coco.py +guided_anchoring/ga-faster-rcnn_r50-caffe_fpn_1x_coco.py +hrnet/htc_hrnetv2p-w18_20e_coco.py +htc/htc_r50_fpn_1x_coco.py +instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py +lad/lad_r50-paa-r101_fpn_2xb8_coco_1x.py +ld/ld_r18-gflv1-r101_fpn_1x_coco.py +libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py +lvis/mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py +mask2former/mask2former_r50_8xb2-lsj-50e_coco.py +mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py +mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py +maskformer/maskformer_r50_ms-16xb1-75e_coco.py +ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py +nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py +nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py +paa/paa_r50_fpn_1x_coco.py +pafpn/faster-rcnn_r50_pafpn_1x_coco.py +panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py +pisa/faster-rcnn_r50_fpn_pisa_1x_coco.py +point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py +pvt/retinanet_pvtv2-b0_fpn_1x_coco.py +queryinst/queryinst_r50_fpn_1x_coco.py +regnet/mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py +reppoints/reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py +res2net/faster-rcnn_res2net-101_fpn_2x_coco.py +resnest/mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py +resnet_strikes_back/faster-rcnn_r50-rsb-pre_fpn_1x_coco.py +retinanet/retinanet_r50_fpn_1x_coco.py +rpn/rpn_r50_fpn_1x_coco.py +rtmdet/rtmdet_s_8xb32-300e_coco.py +rtmdet/rtmdet-ins_s_8xb32-300e_coco.py +sabl/sabl-retinanet_r50_fpn_1x_coco.py +scnet/scnet_r50_fpn_1x_coco.py +scratch/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py +seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss_random-ms-2x_lvis-v1.py +simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-90k_coco.py +soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py +solo/solo_r50_fpn_1x_coco.py +solov2/solov2_r50_fpn_1x_coco.py +sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py +ssd/ssd300_coco.py +swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py +tood/tood_r50_fpn_1x_coco.py +tridentnet/tridentnet_r50-caffe_1x_coco.py +vfnet/vfnet_r50_fpn_1x_coco.py +yolact/yolact_r50_8xb8-55e_coco.py +yolo/yolov3_d53_8xb8-320-273e_coco.py +yolof/yolof_r50-c5_8xb8-1x_coco.py +yolox/yolox_s_8xb8-300e_coco.py +deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py +mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py +masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py +ocsort/ocsort_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py +qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py +strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py diff --git a/mmdetection/.dev_scripts/benchmark_inference_fps.py b/mmdetection/.dev_scripts/benchmark_inference_fps.py new file mode 100644 index 00000000..6099ed13 --- /dev/null +++ b/mmdetection/.dev_scripts/benchmark_inference_fps.py @@ -0,0 +1,171 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp + +from mmengine.config import Config, DictAction +from mmengine.dist import init_dist +from mmengine.fileio import dump +from mmengine.utils import mkdir_or_exist +from terminaltables import GithubFlavoredMarkdownTable + +from tools.analysis_tools.benchmark import repeat_measure_inference_speed + + +def parse_args(): + parser = argparse.ArgumentParser( + description='MMDet benchmark a model of FPS') + parser.add_argument('config', help='test config file path') + parser.add_argument('checkpoint_root', help='Checkpoint file root path') + parser.add_argument( + '--round-num', + type=int, + default=1, + help='round a number to a given precision in decimal digits') + parser.add_argument( + '--repeat-num', + type=int, + default=1, + help='number of repeat times of measurement for averaging the results') + parser.add_argument( + '--out', type=str, help='output path of gathered fps to be stored') + parser.add_argument( + '--max-iter', type=int, default=2000, help='num of max iter') + parser.add_argument( + '--log-interval', type=int, default=50, help='interval of logging') + parser.add_argument( + '--fuse-conv-bn', + action='store_true', + help='Whether to fuse conv and bn, this will slightly increase' + 'the inference speed') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + parser.add_argument( + '--launcher', + choices=['none', 'pytorch', 'slurm', 'mpi'], + default='none', + help='job launcher') + parser.add_argument('--local_rank', type=int, default=0) + args = parser.parse_args() + if 'LOCAL_RANK' not in os.environ: + os.environ['LOCAL_RANK'] = str(args.local_rank) + return args + + +def results2markdown(result_dict): + table_data = [] + is_multiple_results = False + for cfg_name, value in result_dict.items(): + name = cfg_name.replace('configs/', '') + fps = value['fps'] + ms_times_pre_image = value['ms_times_pre_image'] + if isinstance(fps, list): + is_multiple_results = True + mean_fps = value['mean_fps'] + mean_times_pre_image = value['mean_times_pre_image'] + fps_str = ','.join([str(s) for s in fps]) + ms_times_pre_image_str = ','.join( + [str(s) for s in ms_times_pre_image]) + table_data.append([ + name, fps_str, mean_fps, ms_times_pre_image_str, + mean_times_pre_image + ]) + else: + table_data.append([name, fps, ms_times_pre_image]) + + if is_multiple_results: + table_data.insert(0, [ + 'model', 'fps', 'mean_fps', 'times_pre_image(ms)', + 'mean_times_pre_image(ms)' + ]) + + else: + table_data.insert(0, ['model', 'fps', 'times_pre_image(ms)']) + table = GithubFlavoredMarkdownTable(table_data) + print(table.table, flush=True) + + +if __name__ == '__main__': + args = parse_args() + assert args.round_num >= 0 + assert args.repeat_num >= 1 + + config = Config.fromfile(args.config) + + if args.launcher == 'none': + raise NotImplementedError('Only supports distributed mode') + else: + init_dist(args.launcher) + + result_dict = {} + for model_key in config: + model_infos = config[model_key] + if not isinstance(model_infos, list): + model_infos = [model_infos] + for model_info in model_infos: + record_metrics = model_info['metric'] + cfg_path = model_info['config'].strip() + cfg = Config.fromfile(cfg_path) + checkpoint = osp.join(args.checkpoint_root, + model_info['checkpoint'].strip()) + try: + fps = repeat_measure_inference_speed(cfg, checkpoint, + args.max_iter, + args.log_interval, + args.fuse_conv_bn, + args.repeat_num) + if args.repeat_num > 1: + fps_list = [round(fps_, args.round_num) for fps_ in fps] + times_pre_image_list = [ + round(1000 / fps_, args.round_num) for fps_ in fps + ] + mean_fps = round( + sum(fps_list) / len(fps_list), args.round_num) + mean_times_pre_image = round( + sum(times_pre_image_list) / len(times_pre_image_list), + args.round_num) + print( + f'{cfg_path} ' + f'Overall fps: {fps_list}[{mean_fps}] img / s, ' + f'times per image: ' + f'{times_pre_image_list}[{mean_times_pre_image}] ' + f'ms / img', + flush=True) + result_dict[cfg_path] = dict( + fps=fps_list, + mean_fps=mean_fps, + ms_times_pre_image=times_pre_image_list, + mean_times_pre_image=mean_times_pre_image) + else: + print( + f'{cfg_path} fps : {fps:.{args.round_num}f} img / s, ' + f'times per image: {1000 / fps:.{args.round_num}f} ' + f'ms / img', + flush=True) + result_dict[cfg_path] = dict( + fps=round(fps, args.round_num), + ms_times_pre_image=round(1000 / fps, args.round_num)) + except Exception as e: + print(f'{cfg_path} error: {repr(e)}') + if args.repeat_num > 1: + result_dict[cfg_path] = dict( + fps=[0], + mean_fps=0, + ms_times_pre_image=[0], + mean_times_pre_image=0) + else: + result_dict[cfg_path] = dict(fps=0, ms_times_pre_image=0) + + if args.out: + mkdir_or_exist(args.out) + dump(result_dict, osp.join(args.out, 'batch_inference_fps.json')) + + results2markdown(result_dict) diff --git a/mmdetection/.dev_scripts/benchmark_options.py b/mmdetection/.dev_scripts/benchmark_options.py new file mode 100644 index 00000000..cdb1f87d --- /dev/null +++ b/mmdetection/.dev_scripts/benchmark_options.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +third_part_libs = [ + 'pip install -r ../requirements/albu.txt', + 'pip install instaboostfast', + 'pip install git+https://github.com/cocodataset/panopticapi.git', + 'pip install timm', + 'pip install mmpretrain', + 'pip install git+https://github.com/lvis-dataset/lvis-api.git', + 'pip install -r ../requirements/multimodal.txt', + 'pip install -r ../requirements/tracking.txt', + 'pip install git+https://github.com/JonathonLuiten/TrackEval.git', +] + +default_floating_range = 0.5 +model_floating_ranges = {'atss/atss_r50_fpn_1x_coco.py': 0.3} diff --git a/mmdetection/.dev_scripts/benchmark_test.py b/mmdetection/.dev_scripts/benchmark_test.py new file mode 100644 index 00000000..dddfca15 --- /dev/null +++ b/mmdetection/.dev_scripts/benchmark_test.py @@ -0,0 +1,115 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging +import os +import os.path as osp +from argparse import ArgumentParser + +from mmengine.config import Config, DictAction +from mmengine.logging import MMLogger +from mmengine.registry import RUNNERS +from mmengine.runner import Runner + +from mmdet.testing import replace_to_ceph +from mmdet.utils import register_all_modules, replace_cfg_vals + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument('config', help='test config file path') + parser.add_argument('checkpoint_root', help='Checkpoint file root path') + parser.add_argument('--work-dir', help='the dir to save logs') + parser.add_argument('--ceph', action='store_true') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + parser.add_argument( + '--launcher', + choices=['none', 'pytorch', 'slurm', 'mpi'], + default='none', + help='job launcher') + parser.add_argument('--local_rank', type=int, default=0) + args = parser.parse_args() + if 'LOCAL_RANK' not in os.environ: + os.environ['LOCAL_RANK'] = str(args.local_rank) + args = parser.parse_args() + return args + + +# TODO: Need to refactor test.py so that it can be reused. +def fast_test_model(config_name, checkpoint, args, logger=None): + cfg = Config.fromfile(config_name) + cfg = replace_cfg_vals(cfg) + cfg.launcher = args.launcher + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + # work_dir is determined in this priority: CLI > segment in file > filename + if args.work_dir is not None: + # update configs according to CLI args if args.work_dir is not None + cfg.work_dir = osp.join(args.work_dir, + osp.splitext(osp.basename(config_name))[0]) + elif cfg.get('work_dir', None) is None: + # use config filename as default work_dir if cfg.work_dir is None + cfg.work_dir = osp.join('./work_dirs', + osp.splitext(osp.basename(config_name))[0]) + + if args.ceph: + replace_to_ceph(cfg) + + cfg.load_from = checkpoint + + # TODO: temporary plan + if 'visualizer' in cfg: + if 'name' in cfg.visualizer: + del cfg.visualizer.name + + # build the runner from config + if 'runner_type' not in cfg: + # build the default runner + runner = Runner.from_cfg(cfg) + else: + # build customized runner from the registry + # if 'runner_type' is set in the cfg + runner = RUNNERS.build(cfg) + + runner.test() + + +# Sample test whether the inference code is correct +def main(args): + # register all modules in mmdet into the registries + register_all_modules(init_default_scope=False) + + config = Config.fromfile(args.config) + + # test all model + logger = MMLogger.get_instance( + name='MMLogger', + log_file='benchmark_test.log', + log_level=logging.ERROR) + + for model_key in config: + model_infos = config[model_key] + if not isinstance(model_infos, list): + model_infos = [model_infos] + for model_info in model_infos: + print('processing: ', model_info['config'], flush=True) + config_name = model_info['config'].strip() + checkpoint = osp.join(args.checkpoint_root, + model_info['checkpoint'].strip()) + try: + fast_test_model(config_name, checkpoint, args, logger) + except Exception as e: + logger.error(f'{config_name} " : {repr(e)}') + + +if __name__ == '__main__': + args = parse_args() + main(args) diff --git a/mmdetection/.dev_scripts/benchmark_test_image.py b/mmdetection/.dev_scripts/benchmark_test_image.py new file mode 100644 index 00000000..62fa57ee --- /dev/null +++ b/mmdetection/.dev_scripts/benchmark_test_image.py @@ -0,0 +1,134 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging +import os.path as osp +from argparse import ArgumentParser + +import mmcv +from mmengine.config import Config +from mmengine.logging import MMLogger +from mmengine.utils import mkdir_or_exist + +from mmdet.apis import inference_detector, init_detector +from mmdet.registry import VISUALIZERS +from mmdet.utils import register_all_modules + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument('config', help='test config file path') + parser.add_argument('checkpoint_root', help='Checkpoint file root path') + parser.add_argument('--img', default='demo/demo.jpg', help='Image file') + parser.add_argument('--aug', action='store_true', help='aug test') + parser.add_argument('--model-name', help='model name to inference') + parser.add_argument('--show', action='store_true', help='show results') + parser.add_argument('--out-dir', default=None, help='Dir to output file') + parser.add_argument( + '--wait-time', + type=float, + default=1, + help='the interval of show (s), 0 is block') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + parser.add_argument( + '--palette', + default='coco', + choices=['coco', 'voc', 'citys', 'random'], + help='Color palette used for visualization') + parser.add_argument( + '--score-thr', type=float, default=0.3, help='bbox score threshold') + args = parser.parse_args() + return args + + +def inference_model(config_name, checkpoint, visualizer, args, logger=None): + cfg = Config.fromfile(config_name) + if args.aug: + raise NotImplementedError() + + model = init_detector( + cfg, checkpoint, palette=args.palette, device=args.device) + visualizer.dataset_meta = model.dataset_meta + + # test a single image + result = inference_detector(model, args.img) + + # show the results + if args.show or args.out_dir is not None: + img = mmcv.imread(args.img) + img = mmcv.imconvert(img, 'bgr', 'rgb') + out_file = None + if args.out_dir is not None: + out_dir = args.out_dir + mkdir_or_exist(out_dir) + + out_file = osp.join( + out_dir, + config_name.split('/')[-1].replace('py', 'jpg')) + + visualizer.add_datasample( + 'result', + img, + data_sample=result, + draw_gt=False, + show=args.show, + wait_time=args.wait_time, + out_file=out_file, + pred_score_thr=args.score_thr) + + return result + + +# Sample test whether the inference code is correct +def main(args): + # register all modules in mmdet into the registries + register_all_modules() + + config = Config.fromfile(args.config) + + # init visualizer + visualizer_cfg = dict(type='DetLocalVisualizer', name='visualizer') + visualizer = VISUALIZERS.build(visualizer_cfg) + + # test single model + if args.model_name: + if args.model_name in config: + model_infos = config[args.model_name] + if not isinstance(model_infos, list): + model_infos = [model_infos] + model_info = model_infos[0] + config_name = model_info['config'].strip() + print(f'processing: {config_name}', flush=True) + checkpoint = osp.join(args.checkpoint_root, + model_info['checkpoint'].strip()) + # build the model from a config file and a checkpoint file + inference_model(config_name, checkpoint, visualizer, args) + return + else: + raise RuntimeError('model name input error.') + + # test all model + logger = MMLogger.get_instance( + name='MMLogger', + log_file='benchmark_test_image.log', + log_level=logging.ERROR) + + for model_key in config: + model_infos = config[model_key] + if not isinstance(model_infos, list): + model_infos = [model_infos] + for model_info in model_infos: + print('processing: ', model_info['config'], flush=True) + config_name = model_info['config'].strip() + checkpoint = osp.join(args.checkpoint_root, + model_info['checkpoint'].strip()) + try: + # build the model from a config file and a checkpoint file + inference_model(config_name, checkpoint, visualizer, args, + logger) + except Exception as e: + logger.error(f'{config_name} " : {repr(e)}') + + +if __name__ == '__main__': + args = parse_args() + main(args) diff --git a/mmdetection/.dev_scripts/benchmark_train.py b/mmdetection/.dev_scripts/benchmark_train.py new file mode 100644 index 00000000..cd1e70c9 --- /dev/null +++ b/mmdetection/.dev_scripts/benchmark_train.py @@ -0,0 +1,178 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging +import os +import os.path as osp +from argparse import ArgumentParser + +from mmengine.config import Config, DictAction +from mmengine.logging import MMLogger, print_log +from mmengine.registry import RUNNERS +from mmengine.runner import Runner + +from mmdet.testing import replace_to_ceph +from mmdet.utils import register_all_modules, replace_cfg_vals + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument('config', help='test config file path') + parser.add_argument('--work-dir', help='the dir to save logs and models') + parser.add_argument('--ceph', action='store_true') + parser.add_argument('--save-ckpt', action='store_true') + parser.add_argument( + '--amp', + action='store_true', + default=False, + help='enable automatic-mixed-precision training') + parser.add_argument( + '--auto-scale-lr', + action='store_true', + help='enable automatically scaling LR.') + parser.add_argument( + '--resume', + action='store_true', + help='resume from the latest checkpoint in the work_dir automatically') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + parser.add_argument( + '--launcher', + choices=['none', 'pytorch', 'slurm', 'mpi'], + default='none', + help='job launcher') + parser.add_argument('--local_rank', type=int, default=0) + args = parser.parse_args() + if 'LOCAL_RANK' not in os.environ: + os.environ['LOCAL_RANK'] = str(args.local_rank) + args = parser.parse_args() + return args + + +# TODO: Need to refactor train.py so that it can be reused. +def fast_train_model(config_name, args, logger=None): + cfg = Config.fromfile(config_name) + cfg = replace_cfg_vals(cfg) + cfg.launcher = args.launcher + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + # work_dir is determined in this priority: CLI > segment in file > filename + if args.work_dir is not None: + # update configs according to CLI args if args.work_dir is not None + cfg.work_dir = osp.join(args.work_dir, + osp.splitext(osp.basename(config_name))[0]) + elif cfg.get('work_dir', None) is None: + # use config filename as default work_dir if cfg.work_dir is None + cfg.work_dir = osp.join('./work_dirs', + osp.splitext(osp.basename(config_name))[0]) + + ckpt_hook = cfg.default_hooks.checkpoint + by_epoch = ckpt_hook.get('by_epoch', True) + fast_stop_hook = dict(type='FastStopTrainingHook') + fast_stop_hook['by_epoch'] = by_epoch + if args.save_ckpt: + if by_epoch: + interval = 1 + stop_iter_or_epoch = 2 + else: + interval = 4 + stop_iter_or_epoch = 10 + fast_stop_hook['stop_iter_or_epoch'] = stop_iter_or_epoch + fast_stop_hook['save_ckpt'] = True + ckpt_hook.interval = interval + + if 'custom_hooks' in cfg: + cfg.custom_hooks.append(fast_stop_hook) + else: + custom_hooks = [fast_stop_hook] + cfg.custom_hooks = custom_hooks + + # TODO: temporary plan + if 'visualizer' in cfg: + if 'name' in cfg.visualizer: + del cfg.visualizer.name + + # enable automatic-mixed-precision training + if args.amp is True: + optim_wrapper = cfg.optim_wrapper.type + if optim_wrapper == 'AmpOptimWrapper': + print_log( + 'AMP training is already enabled in your config.', + logger='current', + level=logging.WARNING) + else: + assert optim_wrapper == 'OptimWrapper', ( + '`--amp` is only supported when the optimizer wrapper type is ' + f'`OptimWrapper` but got {optim_wrapper}.') + cfg.optim_wrapper.type = 'AmpOptimWrapper' + cfg.optim_wrapper.loss_scale = 'dynamic' + + # enable automatically scaling LR + if args.auto_scale_lr: + if 'auto_scale_lr' in cfg and \ + 'enable' in cfg.auto_scale_lr and \ + 'base_batch_size' in cfg.auto_scale_lr: + cfg.auto_scale_lr.enable = True + else: + raise RuntimeError('Can not find "auto_scale_lr" or ' + '"auto_scale_lr.enable" or ' + '"auto_scale_lr.base_batch_size" in your' + ' configuration file.') + + if args.ceph: + replace_to_ceph(cfg) + + cfg.resume = args.resume + + # build the runner from config + if 'runner_type' not in cfg: + # build the default runner + runner = Runner.from_cfg(cfg) + else: + # build customized runner from the registry + # if 'runner_type' is set in the cfg + runner = RUNNERS.build(cfg) + + runner.train() + + +# Sample test whether the train code is correct +def main(args): + # register all modules in mmdet into the registries + register_all_modules(init_default_scope=False) + + config = Config.fromfile(args.config) + + # test all model + logger = MMLogger.get_instance( + name='MMLogger', + log_file='benchmark_train.log', + log_level=logging.ERROR) + + for model_key in config: + model_infos = config[model_key] + if not isinstance(model_infos, list): + model_infos = [model_infos] + for model_info in model_infos: + print('processing: ', model_info['config'], flush=True) + config_name = model_info['config'].strip() + try: + fast_train_model(config_name, args, logger) + except RuntimeError as e: + # quick exit is the normal exit message + if 'quick exit' not in repr(e): + logger.error(f'{config_name} " : {repr(e)}') + except Exception as e: + logger.error(f'{config_name} " : {repr(e)}') + + +if __name__ == '__main__': + args = parse_args() + main(args) diff --git a/mmdetection/.dev_scripts/benchmark_train_models.txt b/mmdetection/.dev_scripts/benchmark_train_models.txt new file mode 100644 index 00000000..30b53c00 --- /dev/null +++ b/mmdetection/.dev_scripts/benchmark_train_models.txt @@ -0,0 +1,22 @@ +atss/atss_r50_fpn_1x_coco.py +faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py +mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py +cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py +configs/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco.py +configs/glip/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco.py +configs/ddq/ddq-detr-4scale_r50_8xb2-12e_coco.py +panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py +retinanet/retinanet_r50_fpn_1x_coco.py +rtmdet/rtmdet_s_8xb32-300e_coco.py +rtmdet/rtmdet-ins_s_8xb32-300e_coco.py +fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py +centernet/centernet-update_r50-caffe_fpn_ms-1x_coco.py +dino/dino-4scale_r50_8xb2-12e_coco.py +htc/htc_r50_fpn_1x_coco.py +mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py +swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py +condinst/condinst_r50_fpn_ms-poly-90k_coco_instance.py +lvis/mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py +mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py +masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py +qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py diff --git a/mmdetection/.dev_scripts/benchmark_valid_flops.py b/mmdetection/.dev_scripts/benchmark_valid_flops.py new file mode 100644 index 00000000..7dc81f67 --- /dev/null +++ b/mmdetection/.dev_scripts/benchmark_valid_flops.py @@ -0,0 +1,295 @@ +import logging +import re +import tempfile +from argparse import ArgumentParser +from collections import OrderedDict +from functools import partial +from pathlib import Path + +import numpy as np +import pandas as pd +import torch +from mmengine import Config, DictAction +from mmengine.analysis import get_model_complexity_info +from mmengine.analysis.print_helper import _format_size +from mmengine.fileio import FileClient +from mmengine.logging import MMLogger +from mmengine.model import revert_sync_batchnorm +from mmengine.runner import Runner +from modelindex.load_model_index import load +from rich.console import Console +from rich.table import Table +from rich.text import Text +from tqdm import tqdm + +from mmdet.registry import MODELS +from mmdet.utils import register_all_modules + +console = Console() +MMDET_ROOT = Path(__file__).absolute().parents[1] + + +def parse_args(): + parser = ArgumentParser(description='Valid all models in model-index.yml') + parser.add_argument( + '--shape', + type=int, + nargs='+', + default=[1280, 800], + help='input image size') + parser.add_argument( + '--checkpoint_root', + help='Checkpoint file root path. If set, load checkpoint before test.') + parser.add_argument('--img', default='demo/demo.jpg', help='Image file') + parser.add_argument('--models', nargs='+', help='models name to inference') + parser.add_argument( + '--batch-size', + type=int, + default=1, + help='The batch size during the inference.') + parser.add_argument( + '--flops', action='store_true', help='Get Flops and Params of models') + parser.add_argument( + '--flops-str', + action='store_true', + help='Output FLOPs and params counts in a string form.') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + parser.add_argument( + '--size_divisor', + type=int, + default=32, + help='Pad the input image, the minimum size that is divisible ' + 'by size_divisor, -1 means do not pad the image.') + args = parser.parse_args() + return args + + +def inference(config_file, checkpoint, work_dir, args, exp_name): + logger = MMLogger.get_instance(name='MMLogger') + logger.warning('if you want test flops, please make sure torch>=1.12') + cfg = Config.fromfile(config_file) + cfg.work_dir = work_dir + cfg.load_from = checkpoint + cfg.log_level = 'WARN' + cfg.experiment_name = exp_name + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + # forward the model + result = {'model': config_file.stem} + + if args.flops: + + if len(args.shape) == 1: + h = w = args.shape[0] + elif len(args.shape) == 2: + h, w = args.shape + else: + raise ValueError('invalid input shape') + divisor = args.size_divisor + if divisor > 0: + h = int(np.ceil(h / divisor)) * divisor + w = int(np.ceil(w / divisor)) * divisor + + input_shape = (3, h, w) + result['resolution'] = input_shape + + try: + cfg = Config.fromfile(config_file) + if hasattr(cfg, 'head_norm_cfg'): + cfg['head_norm_cfg'] = dict(type='SyncBN', requires_grad=True) + cfg['model']['roi_head']['bbox_head']['norm_cfg'] = dict( + type='SyncBN', requires_grad=True) + cfg['model']['roi_head']['mask_head']['norm_cfg'] = dict( + type='SyncBN', requires_grad=True) + + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + model = MODELS.build(cfg.model) + input = torch.rand(1, *input_shape) + if torch.cuda.is_available(): + model.cuda() + input = input.cuda() + model = revert_sync_batchnorm(model) + inputs = (input, ) + model.eval() + outputs = get_model_complexity_info( + model, input_shape, inputs, show_table=False, show_arch=False) + flops = outputs['flops'] + params = outputs['params'] + activations = outputs['activations'] + result['Get Types'] = 'direct' + except: # noqa 772 + logger = MMLogger.get_instance(name='MMLogger') + logger.warning( + 'Direct get flops failed, try to get flops with data') + cfg = Config.fromfile(config_file) + if hasattr(cfg, 'head_norm_cfg'): + cfg['head_norm_cfg'] = dict(type='SyncBN', requires_grad=True) + cfg['model']['roi_head']['bbox_head']['norm_cfg'] = dict( + type='SyncBN', requires_grad=True) + cfg['model']['roi_head']['mask_head']['norm_cfg'] = dict( + type='SyncBN', requires_grad=True) + data_loader = Runner.build_dataloader(cfg.val_dataloader) + data_batch = next(iter(data_loader)) + model = MODELS.build(cfg.model) + if torch.cuda.is_available(): + model = model.cuda() + model = revert_sync_batchnorm(model) + model.eval() + _forward = model.forward + data = model.data_preprocessor(data_batch) + del data_loader + model.forward = partial( + _forward, data_samples=data['data_samples']) + outputs = get_model_complexity_info( + model, + input_shape, + data['inputs'], + show_table=False, + show_arch=False) + flops = outputs['flops'] + params = outputs['params'] + activations = outputs['activations'] + result['Get Types'] = 'dataloader' + + if args.flops_str: + flops = _format_size(flops) + params = _format_size(params) + activations = _format_size(activations) + + result['flops'] = flops + result['params'] = params + + return result + + +def show_summary(summary_data, args): + table = Table(title='Validation Benchmark Regression Summary') + table.add_column('Model') + table.add_column('Validation') + table.add_column('Resolution (c, h, w)') + if args.flops: + table.add_column('Flops', justify='right', width=11) + table.add_column('Params', justify='right') + + for model_name, summary in summary_data.items(): + row = [model_name] + valid = summary['valid'] + color = 'green' if valid == 'PASS' else 'red' + row.append(f'[{color}]{valid}[/{color}]') + if valid == 'PASS': + row.append(str(summary['resolution'])) + if args.flops: + row.append(str(summary['flops'])) + row.append(str(summary['params'])) + table.add_row(*row) + + console.print(table) + table_data = { + x.header: [Text.from_markup(y).plain for y in x.cells] + for x in table.columns + } + table_pd = pd.DataFrame(table_data) + table_pd.to_csv('./mmdetection_flops.csv') + + +# Sample test whether the inference code is correct +def main(args): + register_all_modules() + model_index_file = MMDET_ROOT / 'model-index.yml' + model_index = load(str(model_index_file)) + model_index.build_models_with_collections() + models = OrderedDict({model.name: model for model in model_index.models}) + + logger = MMLogger( + 'validation', + logger_name='validation', + log_file='benchmark_test_image.log', + log_level=logging.INFO) + + if args.models: + patterns = [ + re.compile(pattern.replace('+', '_')) for pattern in args.models + ] + filter_models = {} + for k, v in models.items(): + k = k.replace('+', '_') + if any([re.match(pattern, k) for pattern in patterns]): + filter_models[k] = v + if len(filter_models) == 0: + print('No model found, please specify models in:') + print('\n'.join(models.keys())) + return + models = filter_models + + summary_data = {} + tmpdir = tempfile.TemporaryDirectory() + for model_name, model_info in tqdm(models.items()): + + if model_info.config is None: + continue + + model_info.config = model_info.config.replace('%2B', '+') + config = Path(model_info.config) + + try: + config.exists() + except: # noqa 722 + logger.error(f'{model_name}: {config} not found.') + continue + + logger.info(f'Processing: {model_name}') + + http_prefix = 'https://download.openmmlab.com/mmdetection/' + if args.checkpoint_root is not None: + root = args.checkpoint_root + if 's3://' in args.checkpoint_root: + from petrel_client.common.exception import AccessDeniedError + file_client = FileClient.infer_client(uri=root) + checkpoint = file_client.join_path( + root, model_info.weights[len(http_prefix):]) + try: + exists = file_client.exists(checkpoint) + except AccessDeniedError: + exists = False + else: + checkpoint = Path(root) / model_info.weights[len(http_prefix):] + exists = checkpoint.exists() + if exists: + checkpoint = str(checkpoint) + else: + print(f'WARNING: {model_name}: {checkpoint} not found.') + checkpoint = None + else: + checkpoint = None + + try: + # build the model from a config file and a checkpoint file + result = inference(MMDET_ROOT / config, checkpoint, tmpdir.name, + args, model_name) + result['valid'] = 'PASS' + except Exception: # noqa 722 + import traceback + logger.error(f'"{config}" :\n{traceback.format_exc()}') + result = {'valid': 'FAIL'} + + summary_data[model_name] = result + + tmpdir.cleanup() + show_summary(summary_data, args) + + +if __name__ == '__main__': + args = parse_args() + main(args) diff --git a/mmdetection/.dev_scripts/check_links.py b/mmdetection/.dev_scripts/check_links.py new file mode 100644 index 00000000..ccf4fad5 --- /dev/null +++ b/mmdetection/.dev_scripts/check_links.py @@ -0,0 +1,157 @@ +# Modified from: +# https://github.com/allenai/allennlp/blob/main/scripts/check_links.py + +import argparse +import logging +import os +import pathlib +import re +import sys +from multiprocessing.dummy import Pool +from typing import NamedTuple, Optional, Tuple + +import requests +from mmengine.logging import MMLogger + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Goes through all the inline-links ' + 'in markdown files and reports the breakages') + parser.add_argument( + '--num-threads', + type=int, + default=100, + help='Number of processes to confirm the link') + parser.add_argument('--https-proxy', type=str, help='https proxy') + parser.add_argument( + '--out', + type=str, + default='link_reports.txt', + help='output path of reports') + args = parser.parse_args() + return args + + +OK_STATUS_CODES = ( + 200, + 401, # the resource exists but may require some sort of login. + 403, # ^ same + 405, # HEAD method not allowed. + # the resource exists, but our default 'Accept-' header may not + # match what the server can provide. + 406, +) + + +class MatchTuple(NamedTuple): + source: str + name: str + link: str + + +def check_link( + match_tuple: MatchTuple, + http_session: requests.Session, + logger: logging = None) -> Tuple[MatchTuple, bool, Optional[str]]: + reason: Optional[str] = None + if match_tuple.link.startswith('http'): + result_ok, reason = check_url(match_tuple, http_session) + else: + result_ok = check_path(match_tuple) + if logger is None: + print(f" {'✓' if result_ok else '✗'} {match_tuple.link}") + else: + logger.info(f" {'✓' if result_ok else '✗'} {match_tuple.link}") + return match_tuple, result_ok, reason + + +def check_url(match_tuple: MatchTuple, + http_session: requests.Session) -> Tuple[bool, str]: + """Check if a URL is reachable.""" + try: + result = http_session.head( + match_tuple.link, timeout=5, allow_redirects=True) + return ( + result.ok or result.status_code in OK_STATUS_CODES, + f'status code = {result.status_code}', + ) + except (requests.ConnectionError, requests.Timeout): + return False, 'connection error' + + +def check_path(match_tuple: MatchTuple) -> bool: + """Check if a file in this repository exists.""" + relative_path = match_tuple.link.split('#')[0] + full_path = os.path.join( + os.path.dirname(str(match_tuple.source)), relative_path) + return os.path.exists(full_path) + + +def main(): + args = parse_args() + + # setup logger + logger = MMLogger.get_instance(name='mmdet', log_file=args.out) + + # setup https_proxy + if args.https_proxy: + os.environ['https_proxy'] = args.https_proxy + + # setup http_session + http_session = requests.Session() + for resource_prefix in ('http://', 'https://'): + http_session.mount( + resource_prefix, + requests.adapters.HTTPAdapter( + max_retries=5, + pool_connections=20, + pool_maxsize=args.num_threads), + ) + + logger.info('Finding all markdown files in the current directory...') + + project_root = (pathlib.Path(__file__).parent / '..').resolve() + markdown_files = project_root.glob('**/*.md') + + all_matches = set() + url_regex = re.compile(r'\[([^!][^\]]+)\]\(([^)(]+)\)') + for markdown_file in markdown_files: + with open(markdown_file) as handle: + for line in handle.readlines(): + matches = url_regex.findall(line) + for name, link in matches: + if 'localhost' not in link: + all_matches.add( + MatchTuple( + source=str(markdown_file), + name=name, + link=link)) + + logger.info(f' {len(all_matches)} markdown files found') + logger.info('Checking to make sure we can retrieve each link...') + + with Pool(processes=args.num_threads) as pool: + results = pool.starmap(check_link, [(match, http_session, logger) + for match in list(all_matches)]) + + # collect unreachable results + unreachable_results = [(match_tuple, reason) + for match_tuple, success, reason in results + if not success] + + if unreachable_results: + logger.info('================================================') + logger.info(f'Unreachable links ({len(unreachable_results)}):') + for match_tuple, reason in unreachable_results: + logger.info(' > Source: ' + match_tuple.source) + logger.info(' Name: ' + match_tuple.name) + logger.info(' Link: ' + match_tuple.link) + if reason is not None: + logger.info(' Reason: ' + reason) + sys.exit(1) + logger.info('No Unreachable link found.') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/.dev_scripts/convert_test_benchmark_script.py b/mmdetection/.dev_scripts/convert_test_benchmark_script.py new file mode 100644 index 00000000..6d7ce8a2 --- /dev/null +++ b/mmdetection/.dev_scripts/convert_test_benchmark_script.py @@ -0,0 +1,114 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp + +from mmengine import Config + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert benchmark model list to script') + parser.add_argument('config', help='test config file path') + parser.add_argument('--port', type=int, default=29666, help='dist port') + parser.add_argument( + '--run', action='store_true', help='run script directly') + parser.add_argument( + '--out', type=str, help='path to save model benchmark script') + + args = parser.parse_args() + return args + + +def process_model_info(model_info, work_dir): + config = model_info['config'].strip() + fname, _ = osp.splitext(osp.basename(config)) + job_name = fname + work_dir = '$WORK_DIR/' + fname + checkpoint = model_info['checkpoint'].strip() + return dict( + config=config, + job_name=job_name, + work_dir=work_dir, + checkpoint=checkpoint) + + +def create_test_bash_info(commands, model_test_dict, port, script_name, + partition): + config = model_test_dict['config'] + job_name = model_test_dict['job_name'] + checkpoint = model_test_dict['checkpoint'] + work_dir = model_test_dict['work_dir'] + + echo_info = f' \necho \'{config}\' &' + commands.append(echo_info) + commands.append('\n') + + command_info = f'GPUS=8 GPUS_PER_NODE=8 ' \ + f'CPUS_PER_TASK=$CPUS_PRE_TASK {script_name} ' + + command_info += f'{partition} ' + command_info += f'{job_name} ' + command_info += f'{config} ' + command_info += f'$CHECKPOINT_DIR/{checkpoint} ' + command_info += f'--work-dir {work_dir} ' + + command_info += f'--cfg-option env_cfg.dist_cfg.port={port} ' + command_info += ' &' + + commands.append(command_info) + + +def main(): + args = parse_args() + if args.out: + out_suffix = args.out.split('.')[-1] + assert args.out.endswith('.sh'), \ + f'Expected out file path suffix is .sh, but get .{out_suffix}' + assert args.out or args.run, \ + ('Please specify at least one operation (save/run/ the ' + 'script) with the argument "--out" or "--run"') + + commands = [] + partition_name = 'PARTITION=$1 ' + commands.append(partition_name) + commands.append('\n') + + checkpoint_root = 'CHECKPOINT_DIR=$2 ' + commands.append(checkpoint_root) + commands.append('\n') + + work_dir = 'WORK_DIR=$3 ' + commands.append(work_dir) + commands.append('\n') + + cpus_pre_task = 'CPUS_PER_TASK=${4:-2} ' + commands.append(cpus_pre_task) + commands.append('\n') + + script_name = osp.join('tools', 'slurm_test.sh') + port = args.port + + cfg = Config.fromfile(args.config) + + for model_key in cfg: + model_infos = cfg[model_key] + if not isinstance(model_infos, list): + model_infos = [model_infos] + for model_info in model_infos: + print('processing: ', model_info['config']) + model_test_dict = process_model_info(model_info, work_dir) + create_test_bash_info(commands, model_test_dict, port, script_name, + '$PARTITION') + port += 1 + + command_str = ''.join(commands) + if args.out: + with open(args.out, 'w') as f: + f.write(command_str) + if args.run: + os.system(command_str) + + +if __name__ == '__main__': + main() diff --git a/mmdetection/.dev_scripts/convert_train_benchmark_script.py b/mmdetection/.dev_scripts/convert_train_benchmark_script.py new file mode 100644 index 00000000..278a76c5 --- /dev/null +++ b/mmdetection/.dev_scripts/convert_train_benchmark_script.py @@ -0,0 +1,104 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert benchmark model json to script') + parser.add_argument( + 'txt_path', type=str, help='txt path output by benchmark_filter') + parser.add_argument( + '--run', action='store_true', help='run script directly') + parser.add_argument( + '--out', type=str, help='path to save model benchmark script') + + args = parser.parse_args() + return args + + +def determine_gpus(cfg_name): + gpus = 8 + gpus_pre_node = 8 + + if cfg_name.find('16x') >= 0: + gpus = 16 + elif cfg_name.find('4xb4') >= 0: + gpus = 4 + gpus_pre_node = 4 + elif 'lad' in cfg_name: + gpus = 2 + gpus_pre_node = 2 + + return gpus, gpus_pre_node + + +def main(): + args = parse_args() + if args.out: + out_suffix = args.out.split('.')[-1] + assert args.out.endswith('.sh'), \ + f'Expected out file path suffix is .sh, but get .{out_suffix}' + assert args.out or args.run, \ + ('Please specify at least one operation (save/run/ the ' + 'script) with the argument "--out" or "--run"') + + root_name = './tools' + train_script_name = osp.join(root_name, 'slurm_train.sh') + + commands = [] + partition_name = 'PARTITION=$1 ' + commands.append(partition_name) + commands.append('\n') + + work_dir = 'WORK_DIR=$2 ' + commands.append(work_dir) + commands.append('\n') + + cpus_pre_task = 'CPUS_PER_TASK=${3:-4} ' + commands.append(cpus_pre_task) + commands.append('\n') + commands.append('\n') + + with open(args.txt_path, 'r') as f: + model_cfgs = f.readlines() + for i, cfg in enumerate(model_cfgs): + cfg = cfg.strip() + if len(cfg) == 0: + continue + # print cfg name + echo_info = f'echo \'{cfg}\' &' + commands.append(echo_info) + commands.append('\n') + + fname, _ = osp.splitext(osp.basename(cfg)) + out_fname = '$WORK_DIR/' + fname + + gpus, gpus_pre_node = determine_gpus(cfg) + command_info = f'GPUS={gpus} GPUS_PER_NODE={gpus_pre_node} ' \ + f'CPUS_PER_TASK=$CPUS_PRE_TASK {train_script_name} ' + command_info += '$PARTITION ' + command_info += f'{fname} ' + command_info += f'{cfg} ' + command_info += f'{out_fname} ' + + command_info += '--cfg-options default_hooks.checkpoint.' \ + 'max_keep_ckpts=1 ' + command_info += '&' + + commands.append(command_info) + + if i < len(model_cfgs): + commands.append('\n') + + command_str = ''.join(commands) + if args.out: + with open(args.out, 'w') as f: + f.write(command_str) + if args.run: + os.system(command_str) + + +if __name__ == '__main__': + main() diff --git a/mmdetection/.dev_scripts/covignore.cfg b/mmdetection/.dev_scripts/covignore.cfg new file mode 100644 index 00000000..a3de535a --- /dev/null +++ b/mmdetection/.dev_scripts/covignore.cfg @@ -0,0 +1,5 @@ +# Each line should be the relative path to the root directory +# of this repo. Support regular expression as well. +# For example: + +.*/__init__.py diff --git a/mmdetection/.dev_scripts/diff_coverage_test.sh b/mmdetection/.dev_scripts/diff_coverage_test.sh new file mode 100644 index 00000000..8dec2452 --- /dev/null +++ b/mmdetection/.dev_scripts/diff_coverage_test.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +readarray -t IGNORED_FILES < $( dirname "$0" )/covignore.cfg +REUSE_COVERAGE_REPORT=${REUSE_COVERAGE_REPORT:-0} +REPO=${1:-"origin"} +BRANCH=${2:-"refactor_dev"} + +git fetch $REPO $BRANCH + +PY_FILES="" +for FILE_NAME in $(git diff --name-only ${REPO}/${BRANCH}); do + # Only test python files in mmdet/ existing in current branch, and not ignored in covignore.cfg + if [ ${FILE_NAME: -3} == ".py" ] && [ ${FILE_NAME:0:6} == "mmdet/" ] && [ -f "$FILE_NAME" ]; then + IGNORED=false + for IGNORED_FILE_NAME in "${IGNORED_FILES[@]}"; do + # Skip blank lines + if [ -z "$IGNORED_FILE_NAME" ]; then + continue + fi + if [ "${IGNORED_FILE_NAME::1}" != "#" ] && [[ "$FILE_NAME" =~ $IGNORED_FILE_NAME ]]; then + echo "Ignoring $FILE_NAME" + IGNORED=true + break + fi + done + if [ "$IGNORED" = false ]; then + PY_FILES="$PY_FILES $FILE_NAME" + fi + fi +done + +# Only test the coverage when PY_FILES are not empty, otherwise they will test the entire project +if [ ! -z "${PY_FILES}" ] +then + if [ "$REUSE_COVERAGE_REPORT" == "0" ]; then + coverage run --branch --source mmdet -m pytest tests/ + fi + coverage report --fail-under 80 -m $PY_FILES + interrogate -v --ignore-init-method --ignore-module --ignore-nested-functions --ignore-magic --ignore-regex "__repr__" --fail-under 95 $PY_FILES +fi diff --git a/mmdetection/.dev_scripts/download_checkpoints.py b/mmdetection/.dev_scripts/download_checkpoints.py new file mode 100644 index 00000000..fa5ef9dc --- /dev/null +++ b/mmdetection/.dev_scripts/download_checkpoints.py @@ -0,0 +1,83 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +import argparse +import math +import os +import os.path as osp +from multiprocessing import Pool + +import torch +from mmengine.config import Config +from mmengine.utils import mkdir_or_exist + + +def download(url, out_file, min_bytes=math.pow(1024, 2), progress=True): + # math.pow(1024, 2) is mean 1 MB + assert_msg = f"Downloaded url '{url}' does not exist " \ + f'or size is < min_bytes={min_bytes}' + try: + print(f'Downloading {url} to {out_file}...') + torch.hub.download_url_to_file(url, str(out_file), progress=progress) + assert osp.exists( + out_file) and osp.getsize(out_file) > min_bytes, assert_msg + except Exception as e: + if osp.exists(out_file): + os.remove(out_file) + print(f'ERROR: {e}\nRe-attempting {url} to {out_file} ...') + os.system(f"curl -L '{url}' -o '{out_file}' --retry 3 -C -" + ) # curl download, retry and resume on fail + finally: + if osp.exists(out_file) and osp.getsize(out_file) < min_bytes: + os.remove(out_file) # remove partial downloads + + if not osp.exists(out_file): + print(f'ERROR: {assert_msg}\n') + print('=========================================\n') + + +def parse_args(): + parser = argparse.ArgumentParser(description='Download checkpoints') + parser.add_argument('config', help='test config file path') + parser.add_argument( + 'out', type=str, help='output dir of checkpoints to be stored') + parser.add_argument( + '--nproc', type=int, default=16, help='num of Processes') + parser.add_argument( + '--intranet', + action='store_true', + help='switch to internal network url') + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + mkdir_or_exist(args.out) + + cfg = Config.fromfile(args.config) + + checkpoint_url_list = [] + checkpoint_out_list = [] + + for model in cfg: + model_infos = cfg[model] + if not isinstance(model_infos, list): + model_infos = [model_infos] + for model_info in model_infos: + checkpoint = model_info['checkpoint'] + out_file = osp.join(args.out, checkpoint) + if not osp.exists(out_file): + + url = model_info['url'] + if args.intranet is True: + url = url.replace('.com', '.sensetime.com') + url = url.replace('https', 'http') + + checkpoint_url_list.append(url) + checkpoint_out_list.append(out_file) + + if len(checkpoint_url_list) > 0: + pool = Pool(min(os.cpu_count(), args.nproc)) + pool.starmap(download, zip(checkpoint_url_list, checkpoint_out_list)) + else: + print('No files to download!') diff --git a/mmdetection/.dev_scripts/gather_models.py b/mmdetection/.dev_scripts/gather_models.py new file mode 100644 index 00000000..52acdc3f --- /dev/null +++ b/mmdetection/.dev_scripts/gather_models.py @@ -0,0 +1,308 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import glob +import os +import os.path as osp +import shutil +import subprocess +import time +from collections import OrderedDict + +import torch +import yaml +from mmengine.config import Config +from mmengine.fileio import dump +from mmengine.utils import digit_version, mkdir_or_exist, scandir + + +def ordered_yaml_dump(data, stream=None, Dumper=yaml.SafeDumper, **kwds): + + class OrderedDumper(Dumper): + pass + + def _dict_representer(dumper, data): + return dumper.represent_mapping( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, data.items()) + + OrderedDumper.add_representer(OrderedDict, _dict_representer) + return yaml.dump(data, stream, OrderedDumper, **kwds) + + +def process_checkpoint(in_file, out_file): + checkpoint = torch.load(in_file, map_location='cpu') + # remove optimizer for smaller file size + if 'optimizer' in checkpoint: + del checkpoint['optimizer'] + if 'ema_state_dict' in checkpoint: + del checkpoint['ema_state_dict'] + + # remove ema state_dict + for key in list(checkpoint['state_dict']): + if key.startswith('ema_'): + checkpoint['state_dict'].pop(key) + elif key.startswith('data_preprocessor'): + checkpoint['state_dict'].pop(key) + + # if it is necessary to remove some sensitive data in checkpoint['meta'], + # add the code here. + if digit_version(torch.__version__) >= digit_version('1.6'): + torch.save(checkpoint, out_file, _use_new_zipfile_serialization=False) + else: + torch.save(checkpoint, out_file) + sha = subprocess.check_output(['sha256sum', out_file]).decode() + final_file = out_file.rstrip('.pth') + '-{}.pth'.format(sha[:8]) + subprocess.Popen(['mv', out_file, final_file]) + return final_file + + +def is_by_epoch(config): + cfg = Config.fromfile('./configs/' + config) + return cfg.train_cfg.type == 'EpochBasedTrainLoop' + + +def get_final_epoch_or_iter(config): + cfg = Config.fromfile('./configs/' + config) + if cfg.train_cfg.type == 'EpochBasedTrainLoop': + return cfg.train_cfg.max_epochs + else: + return cfg.train_cfg.max_iters + + +def get_best_epoch_or_iter(exp_dir): + best_epoch_iter_full_path = list( + sorted(glob.glob(osp.join(exp_dir, 'best_*.pth'))))[-1] + best_epoch_or_iter_model_path = best_epoch_iter_full_path.split('/')[-1] + best_epoch_or_iter = best_epoch_or_iter_model_path.\ + split('_')[-1].split('.')[0] + return best_epoch_or_iter_model_path, int(best_epoch_or_iter) + + +def get_real_epoch_or_iter(config): + cfg = Config.fromfile('./configs/' + config) + if cfg.train_cfg.type == 'EpochBasedTrainLoop': + epoch = cfg.train_cfg.max_epochs + return epoch + else: + return cfg.train_cfg.max_iters + + +def get_final_results(log_json_path, + epoch_or_iter, + results_lut='coco/bbox_mAP', + by_epoch=True): + result_dict = dict() + with open(log_json_path) as f: + r = f.readlines()[-1] + last_metric = r.split(',')[0].split(': ')[-1].strip() + result_dict[results_lut] = last_metric + return result_dict + + +def get_dataset_name(config): + # If there are more dataset, add here. + name_map = dict( + CityscapesDataset='Cityscapes', + CocoDataset='COCO', + CocoPanopticDataset='COCO', + DeepFashionDataset='Deep Fashion', + LVISV05Dataset='LVIS v0.5', + LVISV1Dataset='LVIS v1', + VOCDataset='Pascal VOC', + WIDERFaceDataset='WIDER Face', + OpenImagesDataset='OpenImagesDataset', + OpenImagesChallengeDataset='OpenImagesChallengeDataset', + Objects365V1Dataset='Objects365 v1', + Objects365V2Dataset='Objects365 v2') + cfg = Config.fromfile('./configs/' + config) + return name_map[cfg.dataset_type] + + +def find_last_dir(model_dir): + dst_times = [] + for time_stamp in os.scandir(model_dir): + if osp.isdir(time_stamp): + dst_time = time.mktime( + time.strptime(time_stamp.name, '%Y%m%d_%H%M%S')) + dst_times.append([dst_time, time_stamp.name]) + return max(dst_times, key=lambda x: x[0])[1] + + +def convert_model_info_to_pwc(model_infos): + pwc_files = {} + for model in model_infos: + cfg_folder_name = osp.split(model['config'])[-2] + pwc_model_info = OrderedDict() + pwc_model_info['Name'] = osp.split(model['config'])[-1].split('.')[0] + pwc_model_info['In Collection'] = 'Please fill in Collection name' + pwc_model_info['Config'] = osp.join('configs', model['config']) + + # get metadata + meta_data = OrderedDict() + if 'epochs' in model: + meta_data['Epochs'] = get_real_epoch_or_iter(model['config']) + else: + meta_data['Iterations'] = get_real_epoch_or_iter(model['config']) + pwc_model_info['Metadata'] = meta_data + + # get dataset name + dataset_name = get_dataset_name(model['config']) + + # get results + results = [] + # if there are more metrics, add here. + if 'bbox_mAP' in model['results']: + metric = round(model['results']['bbox_mAP'] * 100, 1) + results.append( + OrderedDict( + Task='Object Detection', + Dataset=dataset_name, + Metrics={'box AP': metric})) + if 'segm_mAP' in model['results']: + metric = round(model['results']['segm_mAP'] * 100, 1) + results.append( + OrderedDict( + Task='Instance Segmentation', + Dataset=dataset_name, + Metrics={'mask AP': metric})) + if 'PQ' in model['results']: + metric = round(model['results']['PQ'], 1) + results.append( + OrderedDict( + Task='Panoptic Segmentation', + Dataset=dataset_name, + Metrics={'PQ': metric})) + pwc_model_info['Results'] = results + + link_string = 'https://download.openmmlab.com/mmdetection/v3.0/' + link_string += '{}/{}'.format(model['config'].rstrip('.py'), + osp.split(model['model_path'])[-1]) + pwc_model_info['Weights'] = link_string + if cfg_folder_name in pwc_files: + pwc_files[cfg_folder_name].append(pwc_model_info) + else: + pwc_files[cfg_folder_name] = [pwc_model_info] + return pwc_files + + +def parse_args(): + parser = argparse.ArgumentParser(description='Gather benchmarked models') + parser.add_argument( + 'root', + type=str, + default='work_dirs', + help='root path of benchmarked models to be gathered') + parser.add_argument( + '--out', + type=str, + default='gather', + help='output path of gathered models to be stored') + parser.add_argument( + '--best', + action='store_true', + help='whether to gather the best model.') + + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + models_root = args.root + models_out = args.out + mkdir_or_exist(models_out) + + # find all models in the root directory to be gathered + raw_configs = list(scandir('./configs', '.py', recursive=True)) + + # filter configs that is not trained in the experiments dir + used_configs = [] + for raw_config in raw_configs: + if osp.exists(osp.join(models_root, raw_config)): + used_configs.append(raw_config) + print(f'Find {len(used_configs)} models to be gathered') + + # find final_ckpt and log file for trained each config + # and parse the best performance + model_infos = [] + for used_config in used_configs: + exp_dir = osp.join(models_root, used_config) + by_epoch = is_by_epoch(used_config) + # check whether the exps is finished + if args.best is True: + final_model, final_epoch_or_iter = get_best_epoch_or_iter(exp_dir) + else: + final_epoch_or_iter = get_final_epoch_or_iter(used_config) + final_model = '{}_{}.pth'.format('epoch' if by_epoch else 'iter', + final_epoch_or_iter) + + model_path = osp.join(exp_dir, final_model) + # skip if the model is still training + if not osp.exists(model_path): + continue + + # get the latest logs + latest_exp_name = find_last_dir(exp_dir) + latest_exp_json = osp.join(exp_dir, latest_exp_name, 'vis_data', + latest_exp_name + '.json') + + model_performance = get_final_results( + latest_exp_json, final_epoch_or_iter, by_epoch=by_epoch) + + if model_performance is None: + continue + + model_info = dict( + config=used_config, + results=model_performance, + final_model=final_model, + latest_exp_json=latest_exp_json, + latest_exp_name=latest_exp_name) + model_info['epochs' if by_epoch else 'iterations'] =\ + final_epoch_or_iter + model_infos.append(model_info) + + # publish model for each checkpoint + publish_model_infos = [] + for model in model_infos: + model_publish_dir = osp.join(models_out, model['config'].rstrip('.py')) + mkdir_or_exist(model_publish_dir) + + model_name = osp.split(model['config'])[-1].split('.')[0] + + model_name += '_' + model['latest_exp_name'] + publish_model_path = osp.join(model_publish_dir, model_name) + trained_model_path = osp.join(models_root, model['config'], + model['final_model']) + + # convert model + final_model_path = process_checkpoint(trained_model_path, + publish_model_path) + + # copy log + shutil.copy(model['latest_exp_json'], + osp.join(model_publish_dir, f'{model_name}.log.json')) + + # copy config to guarantee reproducibility + config_path = model['config'] + config_path = osp.join( + 'configs', + config_path) if 'configs' not in config_path else config_path + target_config_path = osp.split(config_path)[-1] + shutil.copy(config_path, osp.join(model_publish_dir, + target_config_path)) + + model['model_path'] = final_model_path + publish_model_infos.append(model) + + models = dict(models=publish_model_infos) + print(f'Totally gathered {len(publish_model_infos)} models') + dump(models, osp.join(models_out, 'model_info.json')) + + pwc_files = convert_model_info_to_pwc(publish_model_infos) + for name in pwc_files: + with open(osp.join(models_out, name + '_metafile.yml'), 'w') as f: + ordered_yaml_dump(pwc_files[name], f, encoding='utf-8') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/.dev_scripts/gather_test_benchmark_metric.py b/mmdetection/.dev_scripts/gather_test_benchmark_metric.py new file mode 100644 index 00000000..951bfe6f --- /dev/null +++ b/mmdetection/.dev_scripts/gather_test_benchmark_metric.py @@ -0,0 +1,96 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import glob +import os.path as osp + +from mmengine.config import Config +from mmengine.fileio import dump, load +from mmengine.utils import mkdir_or_exist + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Gather benchmarked models metric') + parser.add_argument('config', help='test config file path') + parser.add_argument( + 'root', + type=str, + help='root path of benchmarked models to be gathered') + parser.add_argument( + '--out', type=str, help='output path of gathered metrics to be stored') + parser.add_argument( + '--not-show', action='store_true', help='not show metrics') + parser.add_argument( + '--show-all', action='store_true', help='show all model metrics') + + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + + root_path = args.root + metrics_out = args.out + result_dict = {} + + cfg = Config.fromfile(args.config) + + for model_key in cfg: + model_infos = cfg[model_key] + if not isinstance(model_infos, list): + model_infos = [model_infos] + for model_info in model_infos: + record_metrics = model_info['metric'] + config = model_info['config'].strip() + fname, _ = osp.splitext(osp.basename(config)) + metric_json_dir = osp.join(root_path, fname) + if osp.exists(metric_json_dir): + json_list = glob.glob(osp.join(metric_json_dir, '*.json')) + if len(json_list) > 0: + log_json_path = list(sorted(json_list))[-1] + + metric = load(log_json_path) + if config in metric.get('config', {}): + + new_metrics = dict() + for record_metric_key in record_metrics: + record_metric_key_bk = record_metric_key + old_metric = record_metrics[record_metric_key] + if record_metric_key == 'AR_1000': + record_metric_key = 'AR@1000' + if record_metric_key not in metric['metric']: + raise KeyError( + 'record_metric_key not exist, please ' + 'check your config') + new_metric = round( + metric['metric'][record_metric_key] * 100, 1) + new_metrics[record_metric_key_bk] = new_metric + + if args.show_all: + result_dict[config] = dict( + before=record_metrics, after=new_metrics) + else: + for record_metric_key in record_metrics: + old_metric = record_metrics[record_metric_key] + new_metric = new_metrics[record_metric_key] + if old_metric != new_metric: + result_dict[config] = dict( + before=record_metrics, + after=new_metrics) + break + else: + print(f'{config} not included in: {log_json_path}') + else: + print(f'{config} not exist file: {metric_json_dir}') + else: + print(f'{config} not exist dir: {metric_json_dir}') + + if metrics_out: + mkdir_or_exist(metrics_out) + dump(result_dict, osp.join(metrics_out, 'batch_test_metric_info.json')) + if not args.not_show: + print('===================================') + for config_name, metrics in result_dict.items(): + print(config_name, metrics) + print('===================================') diff --git a/mmdetection/.dev_scripts/gather_train_benchmark_metric.py b/mmdetection/.dev_scripts/gather_train_benchmark_metric.py new file mode 100644 index 00000000..3d4c9cfc --- /dev/null +++ b/mmdetection/.dev_scripts/gather_train_benchmark_metric.py @@ -0,0 +1,151 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import glob +import os.path as osp + +from gather_models import get_final_results +from mmengine.config import Config +from mmengine.fileio import dump +from mmengine.utils import mkdir_or_exist + +try: + import xlrd +except ImportError: + xlrd = None +try: + import xlutils + from xlutils.copy import copy +except ImportError: + xlutils = None + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Gather benchmarked models metric') + parser.add_argument( + 'root', + type=str, + help='root path of benchmarked models to be gathered') + parser.add_argument( + 'txt_path', type=str, help='txt path output by benchmark_filter') + parser.add_argument( + '--out', type=str, help='output path of gathered metrics to be stored') + parser.add_argument( + '--not-show', action='store_true', help='not show metrics') + parser.add_argument( + '--excel', type=str, help='input path of excel to be recorded') + parser.add_argument( + '--ncol', type=int, help='Number of column to be modified or appended') + + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + + if args.excel: + assert args.ncol, 'Please specify "--excel" and "--ncol" ' \ + 'at the same time' + if xlrd is None: + raise RuntimeError( + 'xlrd is not installed,' + 'Please use “pip install xlrd==1.2.0” to install') + if xlutils is None: + raise RuntimeError( + 'xlutils is not installed,' + 'Please use “pip install xlutils==2.0.0” to install') + readbook = xlrd.open_workbook(args.excel) + sheet = readbook.sheet_by_name('Sheet1') + sheet_info = {} + total_nrows = sheet.nrows + for i in range(3, sheet.nrows): + sheet_info[sheet.row_values(i)[0]] = i + xlrw = copy(readbook) + table = xlrw.get_sheet(0) + + root_path = args.root + metrics_out = args.out + + result_dict = {} + with open(args.txt_path, 'r') as f: + model_cfgs = f.readlines() + for i, config in enumerate(model_cfgs): + config = config.strip() + if len(config) == 0: + continue + + config_name = osp.split(config)[-1] + config_name = osp.splitext(config_name)[0] + result_path = osp.join(root_path, config_name) + if osp.exists(result_path): + # 1 read config + cfg = Config.fromfile(config) + total_epochs = cfg.runner.max_epochs + final_results = cfg.evaluation.metric + if not isinstance(final_results, list): + final_results = [final_results] + final_results_out = [] + for key in final_results: + if 'proposal_fast' in key: + final_results_out.append('AR@1000') # RPN + elif 'mAP' not in key: + final_results_out.append(key + '_mAP') + + # 2 determine whether total_epochs ckpt exists + ckpt_path = f'epoch_{total_epochs}.pth' + if osp.exists(osp.join(result_path, ckpt_path)): + log_json_path = list( + sorted(glob.glob(osp.join(result_path, + '*.log.json'))))[-1] + + # 3 read metric + model_performance = get_final_results( + log_json_path, total_epochs, final_results_out) + if model_performance is None: + print(f'log file error: {log_json_path}') + continue + for performance in model_performance: + if performance in ['AR@1000', 'bbox_mAP', 'segm_mAP']: + metric = round( + model_performance[performance] * 100, 1) + model_performance[performance] = metric + result_dict[config] = model_performance + + # update and append excel content + if args.excel: + if 'AR@1000' in model_performance: + metrics = f'{model_performance["AR@1000"]}' \ + f'(AR@1000)' + elif 'segm_mAP' in model_performance: + metrics = f'{model_performance["bbox_mAP"]}/' \ + f'{model_performance["segm_mAP"]}' + else: + metrics = f'{model_performance["bbox_mAP"]}' + + row_num = sheet_info.get(config, None) + if row_num: + table.write(row_num, args.ncol, metrics) + else: + table.write(total_nrows, 0, config) + table.write(total_nrows, args.ncol, metrics) + total_nrows += 1 + + else: + print(f'{config} not exist: {ckpt_path}') + else: + print(f'not exist: {config}') + + # 4 save or print results + if metrics_out: + mkdir_or_exist(metrics_out) + dump(result_dict, osp.join(metrics_out, 'model_metric_info.json')) + if not args.not_show: + print('===================================') + for config_name, metrics in result_dict.items(): + print(config_name, metrics) + print('===================================') + if args.excel: + filename, sufflx = osp.splitext(args.excel) + xlrw.save(f'{filename}_o{sufflx}') + print(f'>>> Output {filename}_o{sufflx}') diff --git a/mmdetection/.dev_scripts/linter.sh b/mmdetection/.dev_scripts/linter.sh new file mode 100644 index 00000000..b0fe0acf --- /dev/null +++ b/mmdetection/.dev_scripts/linter.sh @@ -0,0 +1,3 @@ +yapf -r -i mmdet/ configs/ tests/ tools/ +isort -rc mmdet/ configs/ tests/ tools/ +flake8 . diff --git a/mmdetection/.dev_scripts/test_benchmark.sh b/mmdetection/.dev_scripts/test_benchmark.sh new file mode 100644 index 00000000..8fae1670 --- /dev/null +++ b/mmdetection/.dev_scripts/test_benchmark.sh @@ -0,0 +1,157 @@ +PARTITION=$1 +CHECKPOINT_DIR=$2 +WORK_DIR=$3 +CPUS_PER_TASK=${4:-2} + +echo 'configs/atss/atss_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION atss_r50_fpn_1x_coco configs/atss/atss_r50_fpn_1x_coco.py $CHECKPOINT_DIR/atss_r50_fpn_1x_coco_20200209-985f7bd0.pth --work-dir $WORK_DIR/atss_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29666 & +echo 'configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION autoassign_r50-caffe_fpn_1x_coco configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py $CHECKPOINT_DIR/auto_assign_r50_fpn_1x_coco_20210413_115540-5e17991f.pth --work-dir $WORK_DIR/autoassign_r50-caffe_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29667 & +echo 'configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_r50_fpn-carafe_1x_coco configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py $CHECKPOINT_DIR/faster_rcnn_r50_fpn_carafe_1x_coco_bbox_mAP-0.386_20200504_175733-385a75b7.pth --work-dir $WORK_DIR/faster-rcnn_r50_fpn-carafe_1x_coco --cfg-option env_cfg.dist_cfg.port=29668 & +echo 'configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION cascade-rcnn_r50_fpn_1x_coco configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/cascade_rcnn_r50_fpn_1x_coco_20200316-3dc56deb.pth --work-dir $WORK_DIR/cascade-rcnn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29669 & +echo 'configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION cascade-mask-rcnn_r50_fpn_1x_coco configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/cascade_mask_rcnn_r50_fpn_1x_coco_20200203-9d4dcb24.pth --work-dir $WORK_DIR/cascade-mask-rcnn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29670 & +echo 'configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py $CHECKPOINT_DIR/crpn_faster_rcnn_r50_caffe_fpn_1x_coco-c8283cca.pth --work-dir $WORK_DIR/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29671 & +echo 'configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION centernet_r18-dcnv2_8xb16-crop512-140e_coco configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py $CHECKPOINT_DIR/centernet_resnet18_dcnv2_140e_coco_20210702_155131-c8cd631f.pth --work-dir $WORK_DIR/centernet_r18-dcnv2_8xb16-crop512-140e_coco --cfg-option env_cfg.dist_cfg.port=29672 & +echo 'configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py $CHECKPOINT_DIR/centripetalnet_hourglass104_mstest_16x6_210e_coco_20200915_204804-3ccc61e5.pth --work-dir $WORK_DIR/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco --cfg-option env_cfg.dist_cfg.port=29673 & +echo 'configs/convnext/cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco configs/convnext/cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py $CHECKPOINT_DIR/cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco_20220510_201004-3d24f5a4.pth --work-dir $WORK_DIR/cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco --cfg-option env_cfg.dist_cfg.port=29674 & +echo 'configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION cornernet_hourglass104_8xb6-210e-mstest_coco configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py $CHECKPOINT_DIR/cornernet_hourglass104_mstest_8x6_210e_coco_20200825_150618-79b44c30.pth --work-dir $WORK_DIR/cornernet_hourglass104_8xb6-210e-mstest_coco --cfg-option env_cfg.dist_cfg.port=29675 & +echo 'configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py $CHECKPOINT_DIR/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200130-d68aed1e.pth --work-dir $WORK_DIR/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29676 & +echo 'configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_r50_fpn_mdpool_1x_coco configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py $CHECKPOINT_DIR/faster_rcnn_r50_fpn_mdpool_1x_coco_20200307-c0df27ff.pth --work-dir $WORK_DIR/faster-rcnn_r50_fpn_mdpool_1x_coco --cfg-option env_cfg.dist_cfg.port=29677 & +echo 'configs/ddod/ddod_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION ddod_r50_fpn_1x_coco configs/ddod/ddod_r50_fpn_1x_coco.py $CHECKPOINT_DIR/ddod_r50_fpn_1x_coco_20220523_223737-29b2fc67.pth --work-dir $WORK_DIR/ddod_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29678 & +echo 'configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION deformable-detr_r50_16xb2-50e_coco configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py $CHECKPOINT_DIR/deformable_detr_r50_16x2_50e_coco_20210419_220030-a12b9512.pth --work-dir $WORK_DIR/deformable-detr_r50_16xb2-50e_coco --cfg-option env_cfg.dist_cfg.port=29679 & +echo 'configs/detectors/detectors_htc-r50_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION detectors_htc-r50_1x_coco configs/detectors/detectors_htc-r50_1x_coco.py $CHECKPOINT_DIR/detectors_htc_r50_1x_coco-329b1453.pth --work-dir $WORK_DIR/detectors_htc-r50_1x_coco --cfg-option env_cfg.dist_cfg.port=29680 & +echo 'configs/detr/detr_r50_8xb2-150e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION detr_r50_8xb2-150e_coco configs/detr/detr_r50_8xb2-150e_coco.py $CHECKPOINT_DIR/detr_r50_8x2_150e_coco_20201130_194835-2c4b8974.pth --work-dir $WORK_DIR/detr_r50_8xb2-150e_coco --cfg-option env_cfg.dist_cfg.port=29681 & +echo 'configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION dh-faster-rcnn_r50_fpn_1x_coco configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/dh_faster_rcnn_r50_fpn_1x_coco_20200130-586b67df.pth --work-dir $WORK_DIR/dh-faster-rcnn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29682 & +echo 'configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION atss_r50_fpn_dyhead_1x_coco configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py $CHECKPOINT_DIR/atss_r50_fpn_dyhead_4x4_1x_coco_20211219_023314-eaa620c6.pth --work-dir $WORK_DIR/atss_r50_fpn_dyhead_1x_coco --cfg-option env_cfg.dist_cfg.port=29683 & +echo 'configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION dynamic-rcnn_r50_fpn_1x_coco configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/dynamic_rcnn_r50_fpn_1x-62a3f276.pth --work-dir $WORK_DIR/dynamic-rcnn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29684 & +echo 'configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION retinanet_effb3_fpn_8xb4-crop896-1x_coco configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py $CHECKPOINT_DIR/retinanet_effb3_fpn_crop896_8x4_1x_coco_20220322_234806-615a0dda.pth --work-dir $WORK_DIR/retinanet_effb3_fpn_8xb4-crop896-1x_coco --cfg-option env_cfg.dist_cfg.port=29685 & +echo 'configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_r50-attn1111_fpn_1x_coco configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py $CHECKPOINT_DIR/faster_rcnn_r50_fpn_attention_1111_1x_coco_20200130-403cccba.pth --work-dir $WORK_DIR/faster-rcnn_r50-attn1111_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29686 & +echo 'configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_r50_fpn_1x_coco configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth --work-dir $WORK_DIR/faster-rcnn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29687 & +echo 'configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py $CHECKPOINT_DIR/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_1x_coco-0a0d75a8.pth --work-dir $WORK_DIR/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco --cfg-option env_cfg.dist_cfg.port=29688 & +echo 'configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION fovea_r50_fpn_gn-head-align_4xb4-2x_coco configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py $CHECKPOINT_DIR/fovea_align_r50_fpn_gn-head_4x4_2x_coco_20200203-8987880d.pth --work-dir $WORK_DIR/fovea_r50_fpn_gn-head-align_4xb4-2x_coco --cfg-option env_cfg.dist_cfg.port=29689 & +echo 'configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask-rcnn_r50_fpg_crop640-50e_coco configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py $CHECKPOINT_DIR/mask_rcnn_r50_fpg_crop640_50e_coco_20220311_011857-233b8334.pth --work-dir $WORK_DIR/mask-rcnn_r50_fpg_crop640-50e_coco --cfg-option env_cfg.dist_cfg.port=29690 & +echo 'configs/free_anchor/freeanchor_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION freeanchor_r50_fpn_1x_coco configs/free_anchor/freeanchor_r50_fpn_1x_coco.py $CHECKPOINT_DIR/retinanet_free_anchor_r50_fpn_1x_coco_20200130-0f67375f.pth --work-dir $WORK_DIR/freeanchor_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29691 & +echo 'configs/fsaf/fsaf_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION fsaf_r50_fpn_1x_coco configs/fsaf/fsaf_r50_fpn_1x_coco.py $CHECKPOINT_DIR/fsaf_r50_fpn_1x_coco-94ccc51f.pth --work-dir $WORK_DIR/fsaf_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29692 & +echo 'configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py $CHECKPOINT_DIR/mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200202-587b99aa.pth --work-dir $WORK_DIR/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29693 & +echo 'configs/gfl/gfl_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION gfl_r50_fpn_1x_coco configs/gfl/gfl_r50_fpn_1x_coco.py $CHECKPOINT_DIR/gfl_r50_fpn_1x_coco_20200629_121244-25944287.pth --work-dir $WORK_DIR/gfl_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29694 & +echo 'configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION retinanet_r50_fpn_ghm-1x_coco configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py $CHECKPOINT_DIR/retinanet_ghm_r50_fpn_1x_coco_20200130-a437fda3.pth --work-dir $WORK_DIR/retinanet_r50_fpn_ghm-1x_coco --cfg-option env_cfg.dist_cfg.port=29695 & +echo 'configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask-rcnn_r50_fpn_gn-all_2x_coco configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py $CHECKPOINT_DIR/mask_rcnn_r50_fpn_gn-all_2x_coco_20200206-8eee02a6.pth --work-dir $WORK_DIR/mask-rcnn_r50_fpn_gn-all_2x_coco --cfg-option env_cfg.dist_cfg.port=29696 & +echo 'configs/gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_r50_fpn_gn-ws-all_1x_coco configs/gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py $CHECKPOINT_DIR/faster_rcnn_r50_fpn_gn_ws-all_1x_coco_20200130-613d9fe2.pth --work-dir $WORK_DIR/faster-rcnn_r50_fpn_gn-ws-all_1x_coco --cfg-option env_cfg.dist_cfg.port=29697 & +echo 'configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION grid-rcnn_r50_fpn_gn-head_2x_coco configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py $CHECKPOINT_DIR/grid_rcnn_r50_fpn_gn-head_2x_coco_20200130-6cca8223.pth --work-dir $WORK_DIR/grid-rcnn_r50_fpn_gn-head_2x_coco --cfg-option env_cfg.dist_cfg.port=29698 & +echo 'configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faste-rcnn_r50_fpn_groie_1x_coco configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py $CHECKPOINT_DIR/faster_rcnn_r50_fpn_groie_1x_coco_20200604_211715-66ee9516.pth --work-dir $WORK_DIR/faste-rcnn_r50_fpn_groie_1x_coco --cfg-option env_cfg.dist_cfg.port=29699 & +echo 'configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION ga-retinanet_r50-caffe_fpn_1x_coco configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py $CHECKPOINT_DIR/ga_retinanet_r50_caffe_fpn_1x_coco_20201020-39581c6f.pth --work-dir $WORK_DIR/ga-retinanet_r50-caffe_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29700 & +echo 'configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_hrnetv2p-w18-1x_coco configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py $CHECKPOINT_DIR/faster_rcnn_hrnetv2p_w18_1x_coco_20200130-56651a6d.pth --work-dir $WORK_DIR/faster-rcnn_hrnetv2p-w18-1x_coco --cfg-option env_cfg.dist_cfg.port=29701 & +echo 'configs/htc/htc_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION htc_r50_fpn_1x_coco configs/htc/htc_r50_fpn_1x_coco.py $CHECKPOINT_DIR/htc_r50_fpn_1x_coco_20200317-7332cf16.pth --work-dir $WORK_DIR/htc_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29702 & +echo 'configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask-rcnn_r50_fpn_instaboost-4x_coco configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py $CHECKPOINT_DIR/mask_rcnn_r50_fpn_instaboost_4x_coco_20200307-d025f83a.pth --work-dir $WORK_DIR/mask-rcnn_r50_fpn_instaboost-4x_coco --cfg-option env_cfg.dist_cfg.port=29703 & +echo 'configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION libra-faster-rcnn_r50_fpn_1x_coco configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/libra_faster_rcnn_r50_fpn_1x_coco_20200130-3afee3a9.pth --work-dir $WORK_DIR/libra-faster-rcnn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29704 & +echo 'configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask2former_r50_8xb2-lsj-50e_coco-panoptic configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py $CHECKPOINT_DIR/mask2former_r50_lsj_8x2_50e_coco-panoptic_20220326_224516-11a44721.pth --work-dir $WORK_DIR/mask2former_r50_8xb2-lsj-50e_coco-panoptic --cfg-option env_cfg.dist_cfg.port=29705 & +echo 'configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask-rcnn_r50_fpn_1x_coco configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth --work-dir $WORK_DIR/mask-rcnn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29706 & +echo 'configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION maskformer_r50_ms-16xb1-75e_coco configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py $CHECKPOINT_DIR/maskformer_r50_mstrain_16x1_75e_coco_20220221_141956-bc2699cb.pth --work-dir $WORK_DIR/maskformer_r50_ms-16xb1-75e_coco --cfg-option env_cfg.dist_cfg.port=29707 & +echo 'configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION ms-rcnn_r50-caffe_fpn_1x_coco configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py $CHECKPOINT_DIR/ms_rcnn_r50_caffe_fpn_1x_coco_20200702_180848-61c9355e.pth --work-dir $WORK_DIR/ms-rcnn_r50-caffe_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29708 & +echo 'configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py $CHECKPOINT_DIR/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco_20200520-1bdba3ce.pth --work-dir $WORK_DIR/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco --cfg-option env_cfg.dist_cfg.port=29709 & +echo 'configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION retinanet_r50_nasfpn_crop640-50e_coco configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py $CHECKPOINT_DIR/retinanet_r50_nasfpn_crop640_50e_coco-0ad1f644.pth --work-dir $WORK_DIR/retinanet_r50_nasfpn_crop640-50e_coco --cfg-option env_cfg.dist_cfg.port=29710 & +echo 'configs/paa/paa_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION paa_r50_fpn_1x_coco configs/paa/paa_r50_fpn_1x_coco.py $CHECKPOINT_DIR/paa_r50_fpn_1x_coco_20200821-936edec3.pth --work-dir $WORK_DIR/paa_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29711 & +echo 'configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_r50_pafpn_1x_coco configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py $CHECKPOINT_DIR/faster_rcnn_r50_pafpn_1x_coco_bbox_mAP-0.375_20200503_105836-b7b4b9bd.pth --work-dir $WORK_DIR/faster-rcnn_r50_pafpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29712 & +echo 'configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION panoptic-fpn_r50_fpn_1x_coco configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/panoptic_fpn_r50_fpn_1x_coco_20210821_101153-9668fd13.pth --work-dir $WORK_DIR/panoptic-fpn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29713 & +echo 'configs/pisa/faster-rcnn_r50_fpn_pisa_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_r50_fpn_pisa_1x_coco configs/pisa/faster-rcnn_r50_fpn_pisa_1x_coco.py $CHECKPOINT_DIR/pisa_faster_rcnn_r50_fpn_1x_coco-dea93523.pth --work-dir $WORK_DIR/faster-rcnn_r50_fpn_pisa_1x_coco --cfg-option env_cfg.dist_cfg.port=29714 & +echo 'configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION point-rend_r50-caffe_fpn_ms-1x_coco configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py $CHECKPOINT_DIR/point_rend_r50_caffe_fpn_mstrain_1x_coco-1bcb5fb4.pth --work-dir $WORK_DIR/point-rend_r50-caffe_fpn_ms-1x_coco --cfg-option env_cfg.dist_cfg.port=29715 & +echo 'configs/pvt/retinanet_pvt-s_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION retinanet_pvt-s_fpn_1x_coco configs/pvt/retinanet_pvt-s_fpn_1x_coco.py $CHECKPOINT_DIR/retinanet_pvt-s_fpn_1x_coco_20210906_142921-b6c94a5b.pth --work-dir $WORK_DIR/retinanet_pvt-s_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29716 & +echo 'configs/queryinst/queryinst_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION queryinst_r50_fpn_1x_coco configs/queryinst/queryinst_r50_fpn_1x_coco.py $CHECKPOINT_DIR/queryinst_r50_fpn_1x_coco_20210907_084916-5a8f1998.pth --work-dir $WORK_DIR/queryinst_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29717 & +echo 'configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask-rcnn_regnetx-3.2GF_fpn_1x_coco configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py $CHECKPOINT_DIR/mask_rcnn_regnetx-3.2GF_fpn_1x_coco_20200520_163141-2a9d1814.pth --work-dir $WORK_DIR/mask-rcnn_regnetx-3.2GF_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29718 & +echo 'configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION reppoints-moment_r50_fpn_1x_coco configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py $CHECKPOINT_DIR/reppoints_moment_r50_fpn_1x_coco_20200330-b73db8d1.pth --work-dir $WORK_DIR/reppoints-moment_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29719 & +echo 'configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_res2net-101_fpn_2x_coco configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py $CHECKPOINT_DIR/faster_rcnn_r2_101_fpn_2x_coco-175f1da6.pth --work-dir $WORK_DIR/faster-rcnn_res2net-101_fpn_2x_coco --cfg-option env_cfg.dist_cfg.port=29720 & +echo 'configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py $CHECKPOINT_DIR/faster_rcnn_s50_fpn_syncbn-backbone+head_mstrain-range_1x_coco_20200926_125502-20289c16.pth --work-dir $WORK_DIR/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco --cfg-option env_cfg.dist_cfg.port=29721 & +echo 'configs/resnet_strikes_back/mask-rcnn_r50-rsb-pre_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask-rcnn_r50-rsb-pre_fpn_1x_coco configs/resnet_strikes_back/mask-rcnn_r50-rsb-pre_fpn_1x_coco.py $CHECKPOINT_DIR/mask_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_174054-06ce8ba0.pth --work-dir $WORK_DIR/mask-rcnn_r50-rsb-pre_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29722 & +echo 'configs/retinanet/retinanet_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION retinanet_r50_fpn_1x_coco configs/retinanet/retinanet_r50_fpn_1x_coco.py $CHECKPOINT_DIR/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth --work-dir $WORK_DIR/retinanet_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29723 & +echo 'configs/rpn/rpn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION rpn_r50_fpn_1x_coco configs/rpn/rpn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/rpn_r50_fpn_1x_coco_20200218-5525fa2e.pth --work-dir $WORK_DIR/rpn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29724 & +echo 'configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION sabl-retinanet_r50_fpn_1x_coco configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py $CHECKPOINT_DIR/sabl_retinanet_r50_fpn_1x_coco-6c54fd4f.pth --work-dir $WORK_DIR/sabl-retinanet_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29725 & +echo 'configs/sabl/sabl-faster-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION sabl-faster-rcnn_r50_fpn_1x_coco configs/sabl/sabl-faster-rcnn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/sabl_faster_rcnn_r50_fpn_1x_coco-e867595b.pth --work-dir $WORK_DIR/sabl-faster-rcnn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29726 & +echo 'configs/scnet/scnet_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION scnet_r50_fpn_1x_coco configs/scnet/scnet_r50_fpn_1x_coco.py $CHECKPOINT_DIR/scnet_r50_fpn_1x_coco-c3f09857.pth --work-dir $WORK_DIR/scnet_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29727 & +echo 'configs/scratch/mask-rcnn_r50-scratch_fpn_gn-all_6x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask-rcnn_r50-scratch_fpn_gn-all_6x_coco configs/scratch/mask-rcnn_r50-scratch_fpn_gn-all_6x_coco.py $CHECKPOINT_DIR/scratch_mask_rcnn_r50_fpn_gn_6x_bbox_mAP-0.412__segm_mAP-0.374_20200201_193051-1e190a40.pth --work-dir $WORK_DIR/mask-rcnn_r50-scratch_fpn_gn-all_6x_coco --cfg-option env_cfg.dist_cfg.port=29728 & +echo 'configs/solo/decoupled-solo_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION decoupled-solo_r50_fpn_1x_coco configs/solo/decoupled-solo_r50_fpn_1x_coco.py $CHECKPOINT_DIR/decoupled_solo_r50_fpn_1x_coco_20210820_233348-6337c589.pth --work-dir $WORK_DIR/decoupled-solo_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29729 & +echo 'configs/solov2/solov2_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION solov2_r50_fpn_1x_coco configs/solov2/solov2_r50_fpn_1x_coco.py $CHECKPOINT_DIR/solov2_r50_fpn_1x_coco_20220512_125858-a357fa23.pth --work-dir $WORK_DIR/solov2_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29730 & +echo 'configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION sparse-rcnn_r50_fpn_1x_coco configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py $CHECKPOINT_DIR/sparse_rcnn_r50_fpn_1x_coco_20201222_214453-dc79b137.pth --work-dir $WORK_DIR/sparse-rcnn_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29731 & +echo 'configs/ssd/ssd300_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION ssd300_coco configs/ssd/ssd300_coco.py $CHECKPOINT_DIR/ssd300_coco_20210803_015428-d231a06e.pth --work-dir $WORK_DIR/ssd300_coco --cfg-option env_cfg.dist_cfg.port=29732 & +echo 'configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION ssdlite_mobilenetv2-scratch_8xb24-600e_coco configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py $CHECKPOINT_DIR/ssdlite_mobilenetv2_scratch_600e_coco_20210629_110627-974d9307.pth --work-dir $WORK_DIR/ssdlite_mobilenetv2-scratch_8xb24-600e_coco --cfg-option env_cfg.dist_cfg.port=29733 & +echo 'configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION mask-rcnn_swin-t-p4-w7_fpn_1x_coco configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py $CHECKPOINT_DIR/mask_rcnn_swin-t-p4-w7_fpn_1x_coco_20210902_120937-9d6b7cfa.pth --work-dir $WORK_DIR/mask-rcnn_swin-t-p4-w7_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29734 & +echo 'configs/tood/tood_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION tood_r50_fpn_1x_coco configs/tood/tood_r50_fpn_1x_coco.py $CHECKPOINT_DIR/tood_r50_fpn_1x_coco_20211210_103425-20e20746.pth --work-dir $WORK_DIR/tood_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29735 & +echo 'configs/tridentnet/tridentnet_r50-caffe_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION tridentnet_r50-caffe_1x_coco configs/tridentnet/tridentnet_r50-caffe_1x_coco.py $CHECKPOINT_DIR/tridentnet_r50_caffe_1x_coco_20201230_141838-2ec0b530.pth --work-dir $WORK_DIR/tridentnet_r50-caffe_1x_coco --cfg-option env_cfg.dist_cfg.port=29736 & +echo 'configs/vfnet/vfnet_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION vfnet_r50_fpn_1x_coco configs/vfnet/vfnet_r50_fpn_1x_coco.py $CHECKPOINT_DIR/vfnet_r50_fpn_1x_coco_20201027-38db6f58.pth --work-dir $WORK_DIR/vfnet_r50_fpn_1x_coco --cfg-option env_cfg.dist_cfg.port=29737 & +echo 'configs/yolact/yolact_r50_1xb8-55e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION yolact_r50_1xb8-55e_coco configs/yolact/yolact_r50_1xb8-55e_coco.py $CHECKPOINT_DIR/yolact_r50_1x8_coco_20200908-f38d58df.pth --work-dir $WORK_DIR/yolact_r50_1xb8-55e_coco --cfg-option env_cfg.dist_cfg.port=29738 & +echo 'configs/yolo/yolov3_d53_8xb8-320-273e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION yolov3_d53_8xb8-320-273e_coco configs/yolo/yolov3_d53_8xb8-320-273e_coco.py $CHECKPOINT_DIR/yolov3_d53_320_273e_coco-421362b6.pth --work-dir $WORK_DIR/yolov3_d53_8xb8-320-273e_coco --cfg-option env_cfg.dist_cfg.port=29739 & +echo 'configs/yolof/yolof_r50-c5_8xb8-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION yolof_r50-c5_8xb8-1x_coco configs/yolof/yolof_r50-c5_8xb8-1x_coco.py $CHECKPOINT_DIR/yolof_r50_c5_8x8_1x_coco_20210425_024427-8e864411.pth --work-dir $WORK_DIR/yolof_r50-c5_8xb8-1x_coco --cfg-option env_cfg.dist_cfg.port=29740 & +echo 'configs/yolox/yolox_tiny_8xb8-300e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK tools/slurm_test.sh $PARTITION yolox_tiny_8xb8-300e_coco configs/yolox/yolox_tiny_8xb8-300e_coco.py $CHECKPOINT_DIR/yolox_tiny_8x8_300e_coco_20211124_171234-b4047906.pth --work-dir $WORK_DIR/yolox_tiny_8xb8-300e_coco --cfg-option env_cfg.dist_cfg.port=29741 & diff --git a/mmdetection/.dev_scripts/test_init_backbone.py b/mmdetection/.dev_scripts/test_init_backbone.py new file mode 100644 index 00000000..d38d180b --- /dev/null +++ b/mmdetection/.dev_scripts/test_init_backbone.py @@ -0,0 +1,178 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Check out backbone whether successfully load pretrained checkpoint.""" +import copy +import os +from os.path import dirname, exists, join + +import pytest +from mmengine.config import Config +from mmengine.runner import CheckpointLoader +from mmengine.utils import ProgressBar + +from mmdet.registry import MODELS + + +def _get_config_directory(): + """Find the predefined detector config directory.""" + try: + # Assume we are running in the source mmdetection repo + repo_dpath = dirname(dirname(__file__)) + except NameError: + # For IPython development when this __file__ is not defined + import mmdet + repo_dpath = dirname(dirname(mmdet.__file__)) + config_dpath = join(repo_dpath, 'configs') + if not exists(config_dpath): + raise Exception('Cannot find config path') + return config_dpath + + +def _get_config_module(fname): + """Load a configuration as a python module.""" + config_dpath = _get_config_directory() + config_fpath = join(config_dpath, fname) + config_mod = Config.fromfile(config_fpath) + return config_mod + + +def _get_detector_cfg(fname): + """Grab configs necessary to create a detector. + + These are deep copied to allow for safe modification of parameters without + influencing other tests. + """ + config = _get_config_module(fname) + model = copy.deepcopy(config.model) + return model + + +def _traversed_config_file(): + """We traversed all potential config files under the `config` file. If you + need to print details or debug code, you can use this function. + + If the `backbone.init_cfg` is None (do not use `Pretrained` init way), you + need add the folder name in `ignores_folder` (if the config files in this + folder all set backbone.init_cfg is None) or add config name in + `ignores_file` (if the config file set backbone.init_cfg is None) + """ + config_path = _get_config_directory() + check_cfg_names = [] + + # `base`, `legacy_1.x` and `common` ignored by default. + ignores_folder = ['_base_', 'legacy_1.x', 'common'] + # 'ld' need load teacher model, if want to check 'ld', + # please check teacher_config path first. + ignores_folder += ['ld'] + # `selfsup_pretrain` need convert model, if want to check this model, + # need to convert the model first. + ignores_folder += ['selfsup_pretrain'] + + # the `init_cfg` in 'centripetalnet', 'cornernet', 'cityscapes', + # 'scratch' is None. + # the `init_cfg` in ssdlite(`ssdlite_mobilenetv2_scratch_600e_coco.py`) + # is None + # Please confirm `bockbone.init_cfg` is None first. + ignores_folder += ['centripetalnet', 'cornernet', 'cityscapes', 'scratch'] + ignores_file = ['ssdlite_mobilenetv2_scratch_600e_coco.py'] + + for config_file_name in os.listdir(config_path): + if config_file_name not in ignores_folder: + config_file = join(config_path, config_file_name) + if os.path.isdir(config_file): + for config_sub_file in os.listdir(config_file): + if config_sub_file.endswith('py') and \ + config_sub_file not in ignores_file: + name = join(config_file, config_sub_file) + check_cfg_names.append(name) + return check_cfg_names + + +def _check_backbone(config, print_cfg=True): + """Check out backbone whether successfully load pretrained model, by using + `backbone.init_cfg`. + + First, using `CheckpointLoader.load_checkpoint` to load the checkpoint + without loading models. + Then, using `MODELS.build` to build models, and using + `model.init_weights()` to initialize the parameters. + Finally, assert weights and bias of each layer loaded from pretrained + checkpoint are equal to the weights and bias of original checkpoint. + For the convenience of comparison, we sum up weights and bias of + each loaded layer separately. + + Args: + config (str): Config file path. + print_cfg (bool): Whether print logger and return the result. + + Returns: + results (str or None): If backbone successfully load pretrained + checkpoint, return None; else, return config file path. + """ + if print_cfg: + print('-' * 15 + 'loading ', config) + cfg = Config.fromfile(config) + init_cfg = None + try: + init_cfg = cfg.model.backbone.init_cfg + init_flag = True + except AttributeError: + init_flag = False + if init_cfg is None or init_cfg.get('type') != 'Pretrained': + init_flag = False + if init_flag: + checkpoint = CheckpointLoader.load_checkpoint(init_cfg.checkpoint) + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + else: + state_dict = checkpoint + + model = MODELS.build(cfg.model) + model.init_weights() + + checkpoint_layers = state_dict.keys() + for name, value in model.backbone.state_dict().items(): + if name in checkpoint_layers: + assert value.equal(state_dict[name]) + + if print_cfg: + print('-' * 10 + 'Successfully load checkpoint' + '-' * 10 + + '\n', ) + return None + else: + if print_cfg: + print(config + '\n' + '-' * 10 + + 'config file do not have init_cfg' + '-' * 10 + '\n') + return config + + +@pytest.mark.parametrize('config', _traversed_config_file()) +def test_load_pretrained(config): + """Check out backbone whether successfully load pretrained model by using + `backbone.init_cfg`. + + Details please refer to `_check_backbone` + """ + _check_backbone(config, print_cfg=False) + + +def _test_load_pretrained(): + """We traversed all potential config files under the `config` file. If you + need to print details or debug code, you can use this function. + + Returns: + check_cfg_names (list[str]): Config files that backbone initialized + from pretrained checkpoint might be problematic. Need to recheck + the config file. The output including the config files that the + backbone.init_cfg is None + """ + check_cfg_names = _traversed_config_file() + need_check_cfg = [] + + prog_bar = ProgressBar(len(check_cfg_names)) + for config in check_cfg_names: + init_cfg_name = _check_backbone(config) + if init_cfg_name is not None: + need_check_cfg.append(init_cfg_name) + prog_bar.update() + print('These config files need to be checked again') + print(need_check_cfg) diff --git a/mmdetection/.dev_scripts/train_benchmark.sh b/mmdetection/.dev_scripts/train_benchmark.sh new file mode 100644 index 00000000..13180a6e --- /dev/null +++ b/mmdetection/.dev_scripts/train_benchmark.sh @@ -0,0 +1,164 @@ +PARTITION=$1 +WORK_DIR=$2 +CPUS_PER_TASK=${3:-4} + +echo 'configs/albu_example/mask-rcnn_r50_fpn_albu_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask-rcnn_r50_fpn_albu_1x_coco configs/albu_example/mask-rcnn_r50_fpn_albu_1x_coco.py $WORK_DIR/mask-rcnn_r50_fpn_albu_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/atss/atss_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION atss_r50_fpn_1x_coco configs/atss/atss_r50_fpn_1x_coco.py $WORK_DIR/atss_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION autoassign_r50-caffe_fpn_1x_coco configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py $WORK_DIR/autoassign_r50-caffe_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_r50_fpn-carafe_1x_coco configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py $WORK_DIR/faster-rcnn_r50_fpn-carafe_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION cascade-rcnn_r50_fpn_1x_coco configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py $WORK_DIR/cascade-rcnn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION cascade-mask-rcnn_r50_fpn_1x_coco configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py $WORK_DIR/cascade-mask-rcnn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py $WORK_DIR/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION centernet_r18-dcnv2_8xb16-crop512-140e_coco configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py $WORK_DIR/centernet_r18-dcnv2_8xb16-crop512-140e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION centernet-update_r50-caffe_fpn_ms-1x_coco configs/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco.py $WORK_DIR/centernet-update_r50-caffe_fpn_ms-1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py' & +GPUS=16 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py $WORK_DIR/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION cornernet_hourglass104_8xb6-210e-mstest_coco configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py $WORK_DIR/cornernet_hourglass104_8xb6-210e-mstest_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/convnext/mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco configs/convnext/mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco.py $WORK_DIR/mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py $WORK_DIR/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_r50_fpn_mdpool_1x_coco configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py $WORK_DIR/faster-rcnn_r50_fpn_mdpool_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/ddod/ddod_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION ddod_r50_fpn_1x_coco configs/ddod/ddod_r50_fpn_1x_coco.py $WORK_DIR/ddod_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/detectors/detectors_htc-r50_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION detectors_htc-r50_1x_coco configs/detectors/detectors_htc-r50_1x_coco.py $WORK_DIR/detectors_htc-r50_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py' & +GPUS=16 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION deformable-detr_r50_16xb2-50e_coco configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py $WORK_DIR/deformable-detr_r50_16xb2-50e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/detr/detr_r50_8xb2-150e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION detr_r50_8xb2-150e_coco configs/detr/detr_r50_8xb2-150e_coco.py $WORK_DIR/detr_r50_8xb2-150e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION dh-faster-rcnn_r50_fpn_1x_coco configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py $WORK_DIR/dh-faster-rcnn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION dynamic-rcnn_r50_fpn_1x_coco configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py $WORK_DIR/dynamic-rcnn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION atss_r50_fpn_dyhead_1x_coco configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py $WORK_DIR/atss_r50_fpn_dyhead_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION retinanet_effb3_fpn_8xb4-crop896-1x_coco configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py $WORK_DIR/retinanet_effb3_fpn_8xb4-crop896-1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_r50-attn1111_fpn_1x_coco configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py $WORK_DIR/faster-rcnn_r50-attn1111_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_r50_fpn_1x_coco configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py $WORK_DIR/faster-rcnn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_r50-caffe-dc5_ms-1x_coco configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-1x_coco.py $WORK_DIR/faster-rcnn_r50-caffe-dc5_ms-1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py $WORK_DIR/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py' & +GPUS=4 GPUS_PER_NODE=4 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION fovea_r50_fpn_gn-head-align_4xb4-2x_coco configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py $WORK_DIR/fovea_r50_fpn_gn-head-align_4xb4-2x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask-rcnn_r50_fpg_crop640-50e_coco configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py $WORK_DIR/mask-rcnn_r50_fpg_crop640-50e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/free_anchor/freeanchor_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION freeanchor_r50_fpn_1x_coco configs/free_anchor/freeanchor_r50_fpn_1x_coco.py $WORK_DIR/freeanchor_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/fsaf/fsaf_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION fsaf_r50_fpn_1x_coco configs/fsaf/fsaf_r50_fpn_1x_coco.py $WORK_DIR/fsaf_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py $WORK_DIR/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/gfl/gfl_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION gfl_r50_fpn_1x_coco configs/gfl/gfl_r50_fpn_1x_coco.py $WORK_DIR/gfl_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION retinanet_r50_fpn_ghm-1x_coco configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py $WORK_DIR/retinanet_r50_fpn_ghm-1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask-rcnn_r50_fpn_gn-all_2x_coco configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py $WORK_DIR/mask-rcnn_r50_fpn_gn-all_2x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_r50_fpn_gn-ws-all_1x_coco configs/gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py $WORK_DIR/faster-rcnn_r50_fpn_gn-ws-all_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION grid-rcnn_r50_fpn_gn-head_2x_coco configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py $WORK_DIR/grid-rcnn_r50_fpn_gn-head_2x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faste-rcnn_r50_fpn_groie_1x_coco configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py $WORK_DIR/faste-rcnn_r50_fpn_groie_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION ga-retinanet_r50-caffe_fpn_1x_coco configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py $WORK_DIR/ga-retinanet_r50-caffe_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_hrnetv2p-w18-1x_coco configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py $WORK_DIR/faster-rcnn_hrnetv2p-w18-1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/htc/htc_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION htc_r50_fpn_1x_coco configs/htc/htc_r50_fpn_1x_coco.py $WORK_DIR/htc_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask-rcnn_r50_fpn_instaboost-4x_coco configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py $WORK_DIR/mask-rcnn_r50_fpn_instaboost-4x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/lad/lad_r50-paa-r101_fpn_2xb8_coco_1x.py' & +GPUS=2 GPUS_PER_NODE=2 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION lad_r50-paa-r101_fpn_2xb8_coco_1x configs/lad/lad_r50-paa-r101_fpn_2xb8_coco_1x.py $WORK_DIR/lad_r50-paa-r101_fpn_2xb8_coco_1x --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/ld/ld_r18-gflv1-r101_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION ld_r18-gflv1-r101_fpn_1x_coco configs/ld/ld_r18-gflv1-r101_fpn_1x_coco.py $WORK_DIR/ld_r18-gflv1-r101_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION libra-faster-rcnn_r50_fpn_1x_coco configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py $WORK_DIR/libra-faster-rcnn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask2former_r50_8xb2-lsj-50e_coco-panoptic configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py $WORK_DIR/mask2former_r50_8xb2-lsj-50e_coco-panoptic --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask-rcnn_r50_fpn_1x_coco configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py $WORK_DIR/mask-rcnn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py' & +GPUS=16 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION maskformer_r50_ms-16xb1-75e_coco configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py $WORK_DIR/maskformer_r50_ms-16xb1-75e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION ms-rcnn_r50-caffe_fpn_1x_coco configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py $WORK_DIR/ms-rcnn_r50-caffe_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py' & +GPUS=4 GPUS_PER_NODE=4 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py $WORK_DIR/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION retinanet_r50_nasfpn_crop640-50e_coco configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py $WORK_DIR/retinanet_r50_nasfpn_crop640-50e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/paa/paa_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION paa_r50_fpn_1x_coco configs/paa/paa_r50_fpn_1x_coco.py $WORK_DIR/paa_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_r50_pafpn_1x_coco configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py $WORK_DIR/faster-rcnn_r50_pafpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION panoptic-fpn_r50_fpn_1x_coco configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py $WORK_DIR/panoptic-fpn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/pisa/mask-rcnn_r50_fpn_pisa_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask-rcnn_r50_fpn_pisa_1x_coco configs/pisa/mask-rcnn_r50_fpn_pisa_1x_coco.py $WORK_DIR/mask-rcnn_r50_fpn_pisa_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION point-rend_r50-caffe_fpn_ms-1x_coco configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py $WORK_DIR/point-rend_r50-caffe_fpn_ms-1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/pvt/retinanet_pvt-t_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION retinanet_pvt-t_fpn_1x_coco configs/pvt/retinanet_pvt-t_fpn_1x_coco.py $WORK_DIR/retinanet_pvt-t_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/queryinst/queryinst_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION queryinst_r50_fpn_1x_coco configs/queryinst/queryinst_r50_fpn_1x_coco.py $WORK_DIR/queryinst_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/regnet/retinanet_regnetx-800MF_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION retinanet_regnetx-800MF_fpn_1x_coco configs/regnet/retinanet_regnetx-800MF_fpn_1x_coco.py $WORK_DIR/retinanet_regnetx-800MF_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION reppoints-moment_r50_fpn_1x_coco configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py $WORK_DIR/reppoints-moment_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_res2net-101_fpn_2x_coco configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py $WORK_DIR/faster-rcnn_res2net-101_fpn_2x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py $WORK_DIR/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/resnet_strikes_back/retinanet_r50-rsb-pre_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION retinanet_r50-rsb-pre_fpn_1x_coco configs/resnet_strikes_back/retinanet_r50-rsb-pre_fpn_1x_coco.py $WORK_DIR/retinanet_r50-rsb-pre_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/retinanet/retinanet_r50-caffe_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION retinanet_r50-caffe_fpn_1x_coco configs/retinanet/retinanet_r50-caffe_fpn_1x_coco.py $WORK_DIR/retinanet_r50-caffe_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/rpn/rpn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION rpn_r50_fpn_1x_coco configs/rpn/rpn_r50_fpn_1x_coco.py $WORK_DIR/rpn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION sabl-retinanet_r50_fpn_1x_coco configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py $WORK_DIR/sabl-retinanet_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/scnet/scnet_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION scnet_r50_fpn_1x_coco configs/scnet/scnet_r50_fpn_1x_coco.py $WORK_DIR/scnet_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/scratch/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION faster-rcnn_r50-scratch_fpn_gn-all_6x_coco configs/scratch/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py $WORK_DIR/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/solo/solo_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION solo_r50_fpn_1x_coco configs/solo/solo_r50_fpn_1x_coco.py $WORK_DIR/solo_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/solov2/solov2_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION solov2_r50_fpn_1x_coco configs/solov2/solov2_r50_fpn_1x_coco.py $WORK_DIR/solov2_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION sparse-rcnn_r50_fpn_1x_coco configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py $WORK_DIR/sparse-rcnn_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/ssd/ssd300_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION ssd300_coco configs/ssd/ssd300_coco.py $WORK_DIR/ssd300_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION ssdlite_mobilenetv2-scratch_8xb24-600e_coco configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py $WORK_DIR/ssdlite_mobilenetv2-scratch_8xb24-600e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION mask-rcnn_swin-t-p4-w7_fpn_1x_coco configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py $WORK_DIR/mask-rcnn_swin-t-p4-w7_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/tood/tood_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION tood_r50_fpn_1x_coco configs/tood/tood_r50_fpn_1x_coco.py $WORK_DIR/tood_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo ''configs/tridentnet/tridentnet_r50-caffe_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION tridentnet_r50-caffe_1x_coco 'configs/tridentnet/tridentnet_r50-caffe_1x_coco.py $WORK_DIR/tridentnet_r50-caffe_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/vfnet/vfnet_r50_fpn_1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION vfnet_r50_fpn_1x_coco configs/vfnet/vfnet_r50_fpn_1x_coco.py $WORK_DIR/vfnet_r50_fpn_1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/yolact/yolact_r50_8xb8-55e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION yolact_r50_8xb8-55e_coco configs/yolact/yolact_r50_8xb8-55e_coco.py $WORK_DIR/yolact_r50_8xb8-55e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/yolo/yolov3_d53_8xb8-320-273e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION yolov3_d53_8xb8-320-273e_coco configs/yolo/yolov3_d53_8xb8-320-273e_coco.py $WORK_DIR/yolov3_d53_8xb8-320-273e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/yolof/yolof_r50-c5_8xb8-1x_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION yolof_r50-c5_8xb8-1x_coco configs/yolof/yolof_r50-c5_8xb8-1x_coco.py $WORK_DIR/yolof_r50-c5_8xb8-1x_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & +echo 'configs/yolox/yolox_tiny_8xb8-300e_coco.py' & +GPUS=8 GPUS_PER_NODE=8 CPUS_PER_TASK=$CPUS_PRE_TASK ./tools/slurm_train.sh $PARTITION yolox_tiny_8xb8-300e_coco configs/yolox/yolox_tiny_8xb8-300e_coco.py $WORK_DIR/yolox_tiny_8xb8-300e_coco --cfg-options default_hooks.checkpoint.max_keep_ckpts=1 & diff --git a/mmdetection/.github/CODE_OF_CONDUCT.md b/mmdetection/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..92afad1c --- /dev/null +++ b/mmdetection/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at chenkaidev@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq + +[homepage]: https://www.contributor-covenant.org diff --git a/mmdetection/.github/CONTRIBUTING.md b/mmdetection/.github/CONTRIBUTING.md new file mode 100644 index 00000000..c6696262 --- /dev/null +++ b/mmdetection/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +We appreciate all contributions to improve MMDetection. Please refer to [CONTRIBUTING.md](https://github.com/open-mmlab/mmcv/blob/master/CONTRIBUTING.md) in MMCV for more details about the contributing guideline. diff --git a/mmdetection/.github/ISSUE_TEMPLATE/config.yml b/mmdetection/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..56bbd88f --- /dev/null +++ b/mmdetection/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +blank_issues_enabled: false + +contact_links: + - name: Common Issues + url: https://mmdetection.readthedocs.io/en/latest/faq.html + about: Check if your issue already has solutions + - name: MMDetection Documentation + url: https://mmdetection.readthedocs.io/en/latest/ + about: Check if your question is answered in docs diff --git a/mmdetection/.github/ISSUE_TEMPLATE/error-report.md b/mmdetection/.github/ISSUE_TEMPLATE/error-report.md new file mode 100644 index 00000000..9dbd3ff1 --- /dev/null +++ b/mmdetection/.github/ISSUE_TEMPLATE/error-report.md @@ -0,0 +1,46 @@ +--- +name: Error report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' +--- + +Thanks for your error report and we appreciate it a lot. + +**Checklist** + +1. I have searched related issues but cannot get the expected help. +2. I have read the [FAQ documentation](https://mmdetection.readthedocs.io/en/latest/faq.html) but cannot get the expected help. +3. The bug has not been fixed in the latest version. + +**Describe the bug** +A clear and concise description of what the bug is. + +**Reproduction** + +1. What command or script did you run? + +```none +A placeholder for the command. +``` + +2. Did you make any modifications on the code or config? Did you understand what you have modified? +3. What dataset did you use? + +**Environment** + +1. Please run `python mmdet/utils/collect_env.py` to collect necessary environment information and paste it here. +2. You may add addition that may be helpful for locating the problem, such as + - How you installed PyTorch \[e.g., pip, conda, source\] + - Other environment variables that may be related (such as `$PATH`, `$LD_LIBRARY_PATH`, `$PYTHONPATH`, etc.) + +**Error traceback** +If applicable, paste the error trackback here. + +```none +A placeholder for trackback. +``` + +**Bug fix** +If you have already identified the reason, you can provide the information here. If you are willing to create a PR to fix it, please also leave a comment here and that would be much appreciated! diff --git a/mmdetection/.github/ISSUE_TEMPLATE/feature_request.md b/mmdetection/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..7bf92e8c --- /dev/null +++ b/mmdetection/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' +--- + +**Describe the feature** + +**Motivation** +A clear and concise description of the motivation of the feature. +Ex1. It is inconvenient when \[....\]. +Ex2. There is a recent paper \[....\], which is very helpful for \[....\]. + +**Related resources** +If there is an official code release or third-party implementations, please also provide the information here, which would be very helpful. + +**Additional context** +Add any other context or screenshots about the feature request here. +If you would like to implement the feature and create a PR, please leave a comment here and that would be much appreciated. diff --git a/mmdetection/.github/ISSUE_TEMPLATE/general_questions.md b/mmdetection/.github/ISSUE_TEMPLATE/general_questions.md new file mode 100644 index 00000000..f02dd63a --- /dev/null +++ b/mmdetection/.github/ISSUE_TEMPLATE/general_questions.md @@ -0,0 +1,7 @@ +--- +name: General questions +about: Ask general questions to get help +title: '' +labels: '' +assignees: '' +--- diff --git a/mmdetection/.github/ISSUE_TEMPLATE/reimplementation_questions.md b/mmdetection/.github/ISSUE_TEMPLATE/reimplementation_questions.md new file mode 100644 index 00000000..83607ac3 --- /dev/null +++ b/mmdetection/.github/ISSUE_TEMPLATE/reimplementation_questions.md @@ -0,0 +1,67 @@ +--- +name: Reimplementation Questions +about: Ask about questions during model reimplementation +title: '' +labels: reimplementation +assignees: '' +--- + +**Notice** + +There are several common situations in the reimplementation issues as below + +1. Reimplement a model in the model zoo using the provided configs +2. Reimplement a model in the model zoo on other dataset (e.g., custom datasets) +3. Reimplement a custom model but all the components are implemented in MMDetection +4. Reimplement a custom model with new modules implemented by yourself + +There are several things to do for different cases as below. + +- For case 1 & 3, please follow the steps in the following sections thus we could help to quick identify the issue. +- For case 2 & 4, please understand that we are not able to do much help here because we usually do not know the full code and the users should be responsible to the code they write. +- One suggestion for case 2 & 4 is that the users should first check whether the bug lies in the self-implemented code or the original code. For example, users can first make sure that the same model runs well on supported datasets. If you still need help, please describe what you have done and what you obtain in the issue, and follow the steps in the following sections and try as clear as possible so that we can better help you. + +**Checklist** + +1. I have searched related issues but cannot get the expected help. +2. The issue has not been fixed in the latest version. + +**Describe the issue** + +A clear and concise description of what the problem you meet and what have you done. + +**Reproduction** + +1. What command or script did you run? + +```none +A placeholder for the command. +``` + +2. What config dir you run? + +```none +A placeholder for the config. +``` + +3. Did you make any modifications on the code or config? Did you understand what you have modified? +4. What dataset did you use? + +**Environment** + +1. Please run `python mmdet/utils/collect_env.py` to collect necessary environment information and paste it here. +2. You may add addition that may be helpful for locating the problem, such as + 1. How you installed PyTorch \[e.g., pip, conda, source\] + 2. Other environment variables that may be related (such as `$PATH`, `$LD_LIBRARY_PATH`, `$PYTHONPATH`, etc.) + +**Results** + +If applicable, paste the related results here, e.g., what you expect and what you get. + +```none +A placeholder for results comparison +``` + +**Issue fix** + +If you have already identified the reason, you can provide the information here. If you are willing to create a PR to fix it, please also leave a comment here and that would be much appreciated! diff --git a/mmdetection/.github/pull_request_template.md b/mmdetection/.github/pull_request_template.md new file mode 100644 index 00000000..7a9f0d90 --- /dev/null +++ b/mmdetection/.github/pull_request_template.md @@ -0,0 +1,25 @@ +Thanks for your contribution and we appreciate it a lot. The following instructions would make your pull request more healthy and more easily get feedback. If you do not understand some items, don't worry, just make the pull request and seek help from maintainers. + +## Motivation + +Please describe the motivation of this PR and the goal you want to achieve through this PR. + +## Modification + +Please briefly describe what modification is made in this PR. + +## BC-breaking (Optional) + +Does the modification introduce changes that break the backward-compatibility of the downstream repos? +If so, please describe how it breaks the compatibility and how the downstream projects should modify their code to keep compatibility with this PR. + +## Use cases (Optional) + +If this PR introduces a new feature, it is better to list some use cases here, and update the documentation. + +## Checklist + +1. Pre-commit or other linting tools are used to fix the potential lint issues. +2. The modification is covered by complete unit tests. If not, please add more unit test to ensure the correctness. +3. If the modification has potential influence on downstream projects, this PR should be tested with downstream projects, like MMDet or MMPreTrain. +4. The documentation has been modified accordingly, like docstring or example tutorials. diff --git a/mmdetection/.github/workflows/deploy.yml b/mmdetection/.github/workflows/deploy.yml new file mode 100644 index 00000000..f5750614 --- /dev/null +++ b/mmdetection/.github/workflows/deploy.yml @@ -0,0 +1,28 @@ +name: deploy + +on: push + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-n-publish: + runs-on: ubuntu-latest + if: startsWith(github.event.ref, 'refs/tags') + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.7 + uses: actions/setup-python@v2 + with: + python-version: 3.7 + - name: Install torch + run: pip install torch + - name: Install wheel + run: pip install wheel + - name: Build MMDetection + run: python setup.py sdist bdist_wheel + - name: Publish distribution to PyPI + run: | + pip install twine + twine upload dist/* -u __token__ -p ${{ secrets.pypi_password }} diff --git a/mmdetection/.gitignore b/mmdetection/.gitignore new file mode 100644 index 00000000..55ad113b --- /dev/null +++ b/mmdetection/.gitignore @@ -0,0 +1,123 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/en/_build/ +docs/zh_cn/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +data/ +data +.vscode +.idea +.DS_Store + +# custom +*.pkl +*.pkl.json +*.log.json +docs/modelzoo_statistics.md +mmdet/.mim +work_dirs/ + +# Pytorch +*.pth +*.py~ +*.sh~ diff --git a/mmdetection/.owners.yml b/mmdetection/.owners.yml new file mode 100644 index 00000000..97296aab --- /dev/null +++ b/mmdetection/.owners.yml @@ -0,0 +1,14 @@ +assign: + strategy: + # random + daily-shift-based + scedule: + '*/1 * * * *' + assignees: + - Czm369 + - hhaAndroid + - jbwang1997 + - RangiLyu + - BIGWangYuDong + - chhluo + - ZwwWayne diff --git a/mmdetection/.pre-commit-config-zh-cn.yaml b/mmdetection/.pre-commit-config-zh-cn.yaml new file mode 100644 index 00000000..38591708 --- /dev/null +++ b/mmdetection/.pre-commit-config-zh-cn.yaml @@ -0,0 +1,61 @@ +exclude: ^tests/data/ +repos: + - repo: https://gitee.com/openmmlab/mirrors-flake8 + rev: 5.0.4 + hooks: + - id: flake8 + - repo: https://gitee.com/openmmlab/mirrors-isort + rev: 5.11.5 + hooks: + - id: isort + - repo: https://gitee.com/openmmlab/mirrors-yapf + rev: v0.32.0 + hooks: + - id: yapf + - repo: https://gitee.com/openmmlab/mirrors-pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + - id: check-yaml + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: double-quote-string-fixer + - id: check-merge-conflict + - id: fix-encoding-pragma + args: ["--remove"] + - id: mixed-line-ending + args: ["--fix=lf"] + - repo: https://gitee.com/openmmlab/mirrors-mdformat + rev: 0.7.9 + hooks: + - id: mdformat + args: ["--number"] + additional_dependencies: + - mdformat-openmmlab + - mdformat_frontmatter + - linkify-it-py + - repo: https://gitee.com/openmmlab/mirrors-codespell + rev: v2.2.1 + hooks: + - id: codespell + - repo: https://gitee.com/openmmlab/mirrors-docformatter + rev: v1.3.1 + hooks: + - id: docformatter + args: ["--in-place", "--wrap-descriptions", "79"] + - repo: https://gitee.com/openmmlab/mirrors-pyupgrade + rev: v3.0.0 + hooks: + - id: pyupgrade + args: ["--py36-plus"] + - repo: https://gitee.com/open-mmlab/pre-commit-hooks + rev: v0.2.0 + hooks: + - id: check-algo-readme + - id: check-copyright + args: ["mmdet"] +# - repo: https://gitee.com/openmmlab/mirrors-mypy +# rev: v0.812 +# hooks: +# - id: mypy +# exclude: "docs" diff --git a/mmdetection/.pre-commit-config.yaml b/mmdetection/.pre-commit-config.yaml new file mode 100644 index 00000000..6ea250c8 --- /dev/null +++ b/mmdetection/.pre-commit-config.yaml @@ -0,0 +1,50 @@ +repos: + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + - repo: https://github.com/PyCQA/isort + rev: 5.11.5 + hooks: + - id: isort + - repo: https://github.com/pre-commit/mirrors-yapf + rev: v0.32.0 + hooks: + - id: yapf + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + - id: check-yaml + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: double-quote-string-fixer + - id: check-merge-conflict + - id: fix-encoding-pragma + args: ["--remove"] + - id: mixed-line-ending + args: ["--fix=lf"] + - repo: https://github.com/codespell-project/codespell + rev: v2.2.1 + hooks: + - id: codespell + - repo: https://github.com/executablebooks/mdformat + rev: 0.7.9 + hooks: + - id: mdformat + args: ["--number"] + additional_dependencies: + - mdformat-openmmlab + - mdformat_frontmatter + - linkify-it-py + - repo: https://github.com/myint/docformatter + rev: v1.3.1 + hooks: + - id: docformatter + args: ["--in-place", "--wrap-descriptions", "79"] + - repo: https://github.com/open-mmlab/pre-commit-hooks + rev: v0.2.0 # Use the ref you want to point at + hooks: + - id: check-algo-readme + - id: check-copyright + args: ["mmdet"] # replace the dir_to_check with your expected directory to check diff --git a/mmdetection/.readthedocs.yml b/mmdetection/.readthedocs.yml new file mode 100644 index 00000000..9b597978 --- /dev/null +++ b/mmdetection/.readthedocs.yml @@ -0,0 +1,14 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +formats: + - epub + +python: + install: + - requirements: requirements/docs.txt + - requirements: requirements/readthedocs.txt diff --git a/mmdetection/CITATION.cff b/mmdetection/CITATION.cff new file mode 100644 index 00000000..aac93137 --- /dev/null +++ b/mmdetection/CITATION.cff @@ -0,0 +1,8 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: + - name: "MMDetection Contributors" +title: "OpenMMLab Detection Toolbox and Benchmark" +date-released: 2018-08-22 +url: "https://github.com/open-mmlab/mmdetection" +license: Apache-2.0 diff --git a/mmdetection/CV_05/Co-detr/co-detrl.py b/mmdetection/CV_05/Co-detr/co-detrl.py new file mode 100644 index 00000000..d8b14e0b --- /dev/null +++ b/mmdetection/CV_05/Co-detr/co-detrl.py @@ -0,0 +1,512 @@ +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_lsj_swin_large_1x_coco-3af73af2.pth' +work_dir = "/data/ephemeral/home/mmdetection/work_dir/codetr" + + +custom_imports = dict( + imports=['projects.CO-DETR.codetr'], allow_failed_imports=False) + +# data settings +num_classes = 10 +image_size = (1024, 1024) +classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", + "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing") + + +# dataset settings ( 반드시 확인하기 ) +dataset_type = 'CocoDataset' # data format 정의 +data_root = '/data/ephemeral/home/dataset' # json과 train 모두 위치하는 최상단 폴더 +train_ann_file_name = "train_random/train_kfold_0.json" # train ann file +val_ann_file_name = "train_random/val_kfold_0.json" # val ann file +test_ann_file_name = "test.json" +img_folder = "" # 신경안써도 됨 + + +# model settings +num_dec_layer = 6 +loss_lambda = 2.0 + + + +batch_augments = [ + dict(type='BatchFixedSizePad', size=image_size, pad_mask=True) +] +model = dict( + type='CoDETR', + # If using the lsj augmentation, + # it is recommended to set it to True. + use_lsj=True, + # detr: 52.1 + # one-stage: 49.4 + # two-stage: 47.9 + eval_module='detr', # in ['detr', 'one-stage', 'two-stage'] + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=False, + batch_augments=None), + backbone=dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.5, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True), + neck=dict( + type='ChannelMapper', + in_channels=[192, 384, 768, 1536], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=5), + query_head=dict( + type='CoDINOHead', + num_query=900, + num_classes=num_classes, + in_channels=2048, + as_two_stage=True, + dn_cfg=dict( + label_noise_scale=0.5, + box_noise_scale=1.0, + group_cfg=dict(dynamic=True, num_groups=None, num_dn_queries=100)), + transformer=dict( + type='CoDinoTransformer', + with_coord_feat=False, + num_co_heads=2, # ATSS Aux Head + Faster RCNN Aux Head + num_feature_levels=5, + encoder=dict( + type='DetrTransformerEncoder', + num_layers=6, + # number of layers that use checkpoint. + # The maximum value for the setting is num_layers. + # FairScale must be installed for it to work. + with_cp=6, + transformerlayers=dict( + type='BaseTransformerLayer', + attn_cfgs=dict( + type='MultiScaleDeformableAttention', + embed_dims=256, + num_levels=5, + dropout=0.0), + feedforward_channels=2048, + ffn_dropout=0.0, + operation_order=('self_attn', 'norm', 'ffn', 'norm'))), + decoder=dict( + type='DinoTransformerDecoder', + num_layers=6, + return_intermediate=True, + transformerlayers=dict( + type='DetrTransformerDecoderLayer', + attn_cfgs=[ + dict( + type='MultiheadAttention', + embed_dims=256, + num_heads=8, + dropout=0.0), + dict( + type='MultiScaleDeformableAttention', + embed_dims=256, + num_levels=5, + dropout=0.0), + ], + feedforward_channels=2048, + ffn_dropout=0.0, + operation_order=('self_attn', 'norm', 'cross_attn', 'norm', + 'ffn', 'norm')))), + positional_encoding=dict( + type='SinePositionalEncoding', + num_feats=128, + temperature=20, + normalize=True), + loss_cls=dict( # Different from the DINO + type='QualityFocalLoss', + use_sigmoid=True, + beta=2.0, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0 * num_dec_layer * loss_lambda), + loss_bbox=dict( + type='L1Loss', loss_weight=1.0 * num_dec_layer * loss_lambda)), + roi_head=[ + dict( + type='CoStandardRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32, 64], + finest_scale=56), + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + reg_decoded_bbox=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0 * num_dec_layer * loss_lambda), + loss_bbox=dict( + type='GIoULoss', + loss_weight=10.0 * num_dec_layer * loss_lambda))) + ], + bbox_head=[ + dict( + type='CoATSSHead', + num_classes=num_classes, + in_channels=256, + stacked_convs=1, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[4, 8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0 * num_dec_layer * loss_lambda), + loss_bbox=dict( + type='GIoULoss', + loss_weight=2.0 * num_dec_layer * loss_lambda), + loss_centerness=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0 * num_dec_layer * loss_lambda)), + ], + # model training and testing settings + train_cfg=[ + dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=4000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False)), + dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False) + ], + test_cfg=[ + # Deferent from the DINO, we use the NMS. + dict( + max_per_img=300, + # NMS can improve the mAP by 0.2. + nms=dict(type='soft_nms', iou_threshold=0.8)), + dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100)), + dict( + # atss bbox head: + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100), + # soft-nms is also supported for rcnn testing + # e.g., nms=dict(type='soft_nms', iou_threshold=0.5, min_score=0.05) + ]) + + +# LSJ + CopyPaste +backend_args = None +color_space = [ + [dict(type='ColorTransform')], + [dict(type='AutoContrast')], + [dict(type='Equalize')], + [dict(type='Sharpness')], + [dict(type='Posterize')], + [dict(type='Solarize')], + [dict(type='Color')], + [dict(type='Contrast')], + [dict(type='Brightness')], +] +train_pipeline = [ + dict(type='Mosaic', img_scale=(1024, 1024), prob=1.0), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict(type='Pad', size=image_size, pad_val=dict(img=(114, 114, 114))), + dict(type='PackDetInputs') +] + + +train_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + # use MultiImageMixDataset wrapper to support mosaic and mixup + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), + data_root=data_root, + ann_file=train_ann_file_name, + data_prefix=dict(img=img_folder), + pipeline=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True) + ], + filter_cfg=dict(filter_empty_gt=False, min_size=32)), + pipeline=train_pipeline) +) + + +# follow ViTDet +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=image_size, keep_ratio=True), # diff + dict(type='Pad', size=image_size, pad_val=dict(img=(114, 114, 114))), + dict(type='LoadAnnotations', with_bbox=True, with_mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict( + type='nms', + iou_threshold=0.5), + max_per_img=300)) + +img_scales = [(1333, 800), (666, 400), (2000, 1200)] +tta_pipeline = [ + dict(type='LoadImageFromFile', + backend_args=None), + dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale=s, keep_ratio=True) for s in img_scales + ], [ + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'flip', + 'flip_direction')) + ]])] +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), # 클래스 정보 반드시 적기 + data_root=data_root, # 마찬가지로 data_root + ann_file=val_ann_file_name, # validation_ann_file + data_prefix=dict(img=img_folder), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = ( + dict( + batch_size=1, + num_workers=2, + dataset=dict( + data_root=data_root, + ann_file='test.json', + data_prefix=dict(img=''), + pipeline=tta_pipeline, + metainfo=dict(classes=classes)))) + + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + "/" + val_ann_file_name, + metric='bbox', + format_only=False, + backend_args=backend_args) +test_evaluator = dict( + type='CustomResultDumping', # custom evaluating method를 정의 + img_prefix="/data/ephemeral/home/dataset/", # dataset 최상단 폴더 + outfile_path='/data/ephemeral/home/mmdetection/work_dir/co', + classes=classes # class 정보를 담은 list나 집합 + ) + + + +################################ training setting +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=2e-4, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + accumulative_counts=2, + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.1)})) + + +max_epochs = 12 +train_cfg = dict( + type='EpochBasedTrainLoop', + max_epochs=max_epochs, + val_interval=1) +val_cfg = (dict(type='ValLoop')) +test_cfg = (dict(type='TestLoop')) + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + + +###################################### hooks +default_scope = 'mmdet' +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', + interval=1, + save_best='coco/bbox_mAP_50', + max_keep_ckpts=2), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='DetVisualizationHook')) +log_processor = dict( + type='LogProcessor', + by_epoch=True, + window_size=50) + + + +vis_backends = [ + dict(type='LocalVisBackend', save_dir='visualizations'), + # dict(type='WandbVisBackend', + # init_kwargs={ + # 'project': 'mmdetection', + # 'name': 'Co_DETR' + # }) +] +visualizer = dict( + type='DetLocalVisualizer', + vis_backends=vis_backends, + name='visualizer') + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) +fp16 = dict(loss_scale=512.) + + + +######### seed ######## +seed = 42 +randomness = dict( + seed = seed +) \ No newline at end of file diff --git a/mmdetection/CV_05/DiNO/dino_Swin_L_baseline.py b/mmdetection/CV_05/DiNO/dino_Swin_L_baseline.py new file mode 100644 index 00000000..667f0102 --- /dev/null +++ b/mmdetection/CV_05/DiNO/dino_Swin_L_baseline.py @@ -0,0 +1,281 @@ +_base_ = [ + '../configs/_base_/datasets/coco_detection.py', + '../configs/_base_/default_runtime.py' +] +########################################################################### +#Model +########################################################################### + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/dino/dino-5scale_swin-l_8xb2-12e_coco/dino-5scale_swin-l_8xb2-12e_coco_20230228_072924-a654145f.pth' + +model = dict( + type='DINO', + num_queries=900, # num_matching_queries + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + num_feature_levels=5, + backbone=dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(0, 1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=True, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict( + type='ChannelMapper', + in_channels=[192, 384, 768, 1536], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=5), + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=5, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0))), # 0.1 for DeformDETR + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_heads=8, + dropout=0.0), # 0.1 for DeformDETR + cross_attn_cfg=dict(embed_dims=256, num_levels=5, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0)), # 0.1 for DeformDETR + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, + normalize=True, + offset=0.0, # -0.5 for DeformDETR + temperature=20), # 10000 for DeformDETR + bbox_head=dict( + type='DINOHead', + ##클래스 수 설정해주기 + num_classes=10, + sync_cls_avg_factor=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), # 2.0 in DeformDETR + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( # TODO: Move to model.train_cfg ? + label_noise_scale=0.5, + box_noise_scale=1.0, # 0.4 for DN-DETR + group_cfg=dict(dynamic=True, num_groups=None, + num_dn_queries=100)), # TODO: half num_dn_queries + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) # 100 for DeformDETR + +########################################################################### +#pipline +########################################################################### + +data_root ='/data/ephemeral/home/dataset/' +metainfo = { + 'classes': ('General trash', 'Paper', 'Paper pack', 'Metal', 'Glass', + 'Plastic', 'Styrofoam', 'Plastic bag', 'Battery', 'Clothing',), + 'palette': [ + (220, 20, 60), (119, 11, 32), (0, 0, 230), (106, 0, 228), (60, 20, 220), + (0, 80, 100), (0, 0, 70), (50, 0, 192), (250, 170, 30), (255, 0, 0) + ] +} + +backend_args = None +color_space = [ + [dict(type='ColorTransform')], + [dict(type='AutoContrast')], + [dict(type='Equalize')], + [dict(type='Sharpness')], + [dict(type='Posterize')], + [dict(type='Solarize')], + [dict(type='Color')], + [dict(type='Contrast')], + [dict(type='Brightness')], +] + +image_size = (1024, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=image_size, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + # dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict(type='Resize', scale=image_size, keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + data_root=data_root, + ann_file='train_kfold_0.json', + data_prefix=dict(img=''), + pipeline=train_pipeline, + metainfo=metainfo)) + +val_dataloader = ( + dict( + batch_size=1, + num_workers=4, + dataset=dict( + data_root=data_root, + ann_file='val_kfold_0.json', + data_prefix=dict(img=''), + pipeline=test_pipeline, + metainfo=metainfo))) + +test_dataloader = ( + dict( + batch_size=1, + num_workers=4, + dataset=dict( + data_root=data_root, + ann_file='test.json', + data_prefix=dict(img=''), + pipeline=test_pipeline, + metainfo=metainfo))) + +########################################################################### +#evaluator +########################################################################### + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'train_kfold_0.json', + metric=['bbox'], + format_only=False, + classwise=True) + +test_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'test.json', + metric=['bbox'], + format_only=False, + classwise=True) + +########################################################################### +#schedule & optimize +########################################################################### + +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.1)})) + +max_epochs = 25 +train_cfg = dict( + type='EpochBasedTrainLoop', + max_epochs=max_epochs, + val_interval=1) + +param_scheduler = [ + dict( + type='CosineAnnealingLR', + eta_min=0.0, + begin=0, + T_max=max_epochs, + end=max_epochs, + by_epoch=True, + convert_to_iter_based=True) +] + +val_cfg = (dict(type='ValLoop')) +test_cfg = (dict(type='TestLoop')) + +########################################################################### +#visualizer & hooks +########################################################################### + +vis_backends = [ + dict(type='LocalVisBackend', save_dir='visualizations'), + dict(type='WandbVisBackend', + init_kwargs={ + 'project': 'mmdetection', + 'name': 'DINO_NEW' + }) +] + +visualizer = dict( + type='DetLocalVisualizer', + vis_backends=vis_backends, + name='visualizer') + +default_hooks = dict(checkpoint=dict(by_epoch=True, interval=1, max_keep_ckpts=3)) +log_processor = dict(by_epoch=True) + +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', + interval=1, # 매 에폭마다 + by_epoch=True, # 에폭 기준으로 저장 + max_keep_ckpts=3), # 최근 3개만 유지 + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='DetVisualizationHook')) + +auto_scale_lr = dict(base_batch_size=2) \ No newline at end of file diff --git a/mmdetection/CV_05/_base_/default_dataset.py b/mmdetection/CV_05/_base_/default_dataset.py new file mode 100644 index 00000000..45434c30 --- /dev/null +++ b/mmdetection/CV_05/_base_/default_dataset.py @@ -0,0 +1,114 @@ +num_classes = 10 +image_size = (1024, 1024) +classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", + "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing") + + +# dataset settings ( 반드시 확인하기 ) +dataset_type = 'CocoDataset' # data format 정의 +data_root = '/data/ephemeral/home/dataset' # json과 train 모두 위치하는 최상단 폴더 +train_ann_file_name = "MLSK_split/train_fold_2.json" # train ann file +val_ann_file_name = "MLSK_split/val_fold_2.json" # val ann file +test_ann_file_name = "test.json" +img_folder = "" # 신경안써도 됨 + + + +color_space = [ + [dict(type='ColorTransform')], + [dict(type='AutoContrast')], + [dict(type='Equalize')], + [dict(type='Sharpness')], + [dict(type='Posterize')], + [dict(type='Solarize')], + [dict(type='Color')], + [dict(type='Contrast')], + [dict(type='Brightness')], +] + + +backend_args = None +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + # dict(type='PhotoMetricDistortion'), + # dict(type='Corrupt', corruption='gaussian_blur'), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='RandomFlip', prob=0.5), + dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='PackDetInputs') +] + + +train_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), + data_root=data_root, + ann_file=train_ann_file_name, + pipeline=train_pipeline, + data_prefix=dict(img=img_folder), + filter_cfg=dict(filter_empty_gt=False, min_size=32)) +) + + +# follow ViTDet +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=image_size, keep_ratio=True), # diff + dict(type='LoadAnnotations', with_bbox=True, with_mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), # 클래스 정보 반드시 적기 + data_root=data_root, # 마찬가지로 data_root + ann_file=val_ann_file_name, # validation_ann_file + data_prefix=dict(img=img_folder), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + "/" + val_ann_file_name, + metric='bbox', + format_only=False, + backend_args=backend_args) + + +test_evaluator = dict( + type='CustomResultDumping', # custom evaluating method를 정의 + img_prefix="/data/ephemeral/home/dataset/", # dataset 최상단 폴더 + outfile_path='/data/ephemeral/home/mmdetection/work_dir/co', + classes=classes # class 정보를 담은 list나 집합 + ) \ No newline at end of file diff --git a/mmdetection/CV_05/_base_/default_multi_dataset.py b/mmdetection/CV_05/_base_/default_multi_dataset.py new file mode 100644 index 00000000..7213666b --- /dev/null +++ b/mmdetection/CV_05/_base_/default_multi_dataset.py @@ -0,0 +1,120 @@ +num_classes = 10 +image_size = (1024, 1024) +classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", + "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing") + + +# dataset settings ( 반드시 확인하기 ) +dataset_type = 'CocoDataset' # data format 정의 +data_root = '/data/ephemeral/home/dataset' # json과 train 모두 위치하는 최상단 폴더 +train_ann_file_name = "MLSK_split/train_fold_2.json" # train ann file +val_ann_file_name = "MLSK_split/val_fold_2.json" # val ann file +test_ann_file_name = "test.json" +img_folder = "" # 신경안써도 됨 + + + +color_space = [ + [dict(type='ColorTransform')], + [dict(type='AutoContrast')], + [dict(type='Equalize')], + [dict(type='Sharpness')], + [dict(type='Posterize')], + [dict(type='Solarize')], + [dict(type='Color')], + [dict(type='Contrast')], + [dict(type='Brightness')], +] + + +backend_args = None +train_pipeline = [ + dict(type='Mosaic', img_scale=(1024, 1024), prob=1.0), + dict(type='RandomFlip', prob=0.5), + dict(type='RandAugment', aug_space=color_space, aug_num=1), + # dict(type='PhotoMetricDistortion'), + # dict(type='Corrupt', corruption='gaussian_blur'), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='PackDetInputs') +] + + +train_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + # use MultiImageMixDataset wrapper to support mosaic and mixup + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), + data_root=data_root, + ann_file=train_ann_file_name, + data_prefix=dict(img=img_folder), + pipeline=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True) + ], + filter_cfg=dict(filter_empty_gt=False, min_size=32)), + pipeline=train_pipeline) +) + + +# follow ViTDet +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=image_size, keep_ratio=True), # diff + dict(type='LoadAnnotations', with_bbox=True, with_mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), # 클래스 정보 반드시 적기 + data_root=data_root, # 마찬가지로 data_root + ann_file=val_ann_file_name, # validation_ann_file + data_prefix=dict(img=img_folder), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + "/" + val_ann_file_name, + metric='bbox', + format_only=False, + backend_args=backend_args) + + +test_evaluator = dict( + type='CustomResultDumping', # custom evaluating method를 정의 + img_prefix="/data/ephemeral/home/dataset/", # dataset 최상단 폴더 + outfile_path='/data/ephemeral/home/mmdetection/work_dir/co', + classes=classes # class 정보를 담은 list나 집합 + ) \ No newline at end of file diff --git a/mmdetection/CV_05/_base_/default_runtime.py b/mmdetection/CV_05/_base_/default_runtime.py new file mode 100644 index 00000000..acfd9a41 --- /dev/null +++ b/mmdetection/CV_05/_base_/default_runtime.py @@ -0,0 +1,76 @@ +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=2e-4, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + accumulative_counts=2, + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.1)})) + + +# learning policy +max_epochs = 36 # 에폭 수 증가 +train_cfg = dict( + type='EpochBasedTrainLoop', + max_epochs=max_epochs, + val_interval=1) + +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='CosineAnnealingLR', + eta_min=0.0, + begin=0, + T_max=max_epochs, + end=max_epochs, + by_epoch=True, + convert_to_iter_based=True) +] + + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) + +########################################################################### +#visuallization & hooks +########################################################################### + +vis_backends = [ + dict(type='LocalVisBackend', save_dir='visualizations'), + dict(type='WandbVisBackend', + init_kwargs={ + 'project': 'mmdetection', + 'name': 'cascade_convnext_v2_baseline' + }) +] +default_scope = 'mmdet' +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', + interval=1, + save_best='coco/bbox_mAP_50', + max_keep_ckpts=2), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='DetVisualizationHook')) + +env_cfg = dict( + cudnn_benchmark=False, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) + +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') +visualization=dict( + type='DetVisualizationHook', + draw=True, + interval=1, + show=True) +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=True) + +log_level = 'INFO' \ No newline at end of file diff --git a/mmdetection/CV_05/_base_/default_tta.py b/mmdetection/CV_05/_base_/default_tta.py new file mode 100644 index 00000000..2d94f3b6 --- /dev/null +++ b/mmdetection/CV_05/_base_/default_tta.py @@ -0,0 +1,25 @@ +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict( + type='nms', + iou_threshold=0.5), + max_per_img=300)) + +img_scales = [(1333, 800), (666, 400), (2000, 1200)] +tta_pipeline = [ + dict(type='LoadImageFromFile', + backend_args=None), + dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale=s, keep_ratio=True) for s in img_scales + ], [ + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'flip', + 'flip_direction')) + ]])] \ No newline at end of file diff --git a/mmdetection/CV_05/_base_/heavy_augmentation_dataset.py b/mmdetection/CV_05/_base_/heavy_augmentation_dataset.py new file mode 100644 index 00000000..a74c4612 --- /dev/null +++ b/mmdetection/CV_05/_base_/heavy_augmentation_dataset.py @@ -0,0 +1,94 @@ +num_classes = 10 +image_size = (1024, 1024) +classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", + "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing") + + +# dataset settings ( 반드시 확인하기 ) +dataset_type = 'CocoDataset' # data format 정의 +data_root = '/data/ephemeral/home/dataset' # json과 train 모두 위치하는 최상단 폴더 +train_ann_file_name = "MLSK_split/train_fold_2.json" # train ann file +val_ann_file_name = "MLSK_split/val_fold_2.json" # val ann file +test_ann_file_name = "test.json" +img_folder = "" # 신경안써도 됨 + + +backend_args = None +train_pipeline = [ + dict(type='Mosaic', img_scale=(1024, 1024), prob=1.0), + dict(type='RandomFlip', prob=0.5), + dict(type='RandomAffine'), + dict(type='PhotoMetricDistortion'), + dict(type='Corrupt', corruption='gaussian_blur'), + dict(type='PackDetInputs') +] + + +train_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + # use MultiImageMixDataset wrapper to support mosaic and mixup + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), + data_root=data_root, + ann_file=train_ann_file_name, + data_prefix=dict(img=img_folder), + pipeline=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True) + ], + filter_cfg=dict(filter_empty_gt=False, min_size=32)), + pipeline=train_pipeline) +) + + +# follow ViTDet +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=image_size, keep_ratio=True), # diff + dict(type='LoadAnnotations', with_bbox=True, with_mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), # 클래스 정보 반드시 적기 + data_root=data_root, # 마찬가지로 data_root + ann_file=val_ann_file_name, # validation_ann_file + data_prefix=dict(img=img_folder), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + "/" + val_ann_file_name, + metric='bbox', + format_only=False, + backend_args=backend_args) + + +test_evaluator = dict( + type='CustomResultDumping', # custom evaluating method를 정의 + img_prefix="/data/ephemeral/home/dataset/", # dataset 최상단 폴더 + outfile_path='/data/ephemeral/home/mmdetection/work_dir/co', + classes=classes # class 정보를 담은 list나 집합 + ) \ No newline at end of file diff --git a/mmdetection/CV_05/_base_/lsj_mosaic_augmentation_dataset.py b/mmdetection/CV_05/_base_/lsj_mosaic_augmentation_dataset.py new file mode 100644 index 00000000..380e34be --- /dev/null +++ b/mmdetection/CV_05/_base_/lsj_mosaic_augmentation_dataset.py @@ -0,0 +1,103 @@ +num_classes = 10 +image_size = (1024, 1024) +classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", + "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing") + + +# dataset settings ( 반드시 확인하기 ) +dataset_type = 'CocoDataset' # data format 정의 +data_root = '/data/ephemeral/home/dataset' # json과 train 모두 위치하는 최상단 폴더 +train_ann_file_name = "MLSK_split/train_fold_2.json" # train ann file +val_ann_file_name = "MLSK_split/val_fold_2.json" # val ann file +test_ann_file_name = "test.json" +img_folder = "" # 신경안써도 됨 + + +backend_args = None +train_pipeline = [ + dict(type='Mosaic', img_scale=(1024, 1024), prob=1.0), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=image_size, pad_val=dict(img=(114, 114, 114))), + dict(type='PackDetInputs') +] + + +train_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + # use MultiImageMixDataset wrapper to support mosaic and mixup + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), + data_root=data_root, + ann_file=train_ann_file_name, + data_prefix=dict(img=img_folder), + pipeline=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True) + ], + filter_cfg=dict(filter_empty_gt=False, min_size=32)), + pipeline=train_pipeline) +) + + +# follow ViTDet +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=image_size, keep_ratio=True), # diff + dict(type='LoadAnnotations', with_bbox=True, with_mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), # 클래스 정보 반드시 적기 + data_root=data_root, # 마찬가지로 data_root + ann_file=val_ann_file_name, # validation_ann_file + data_prefix=dict(img=img_folder), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + "/" + val_ann_file_name, + metric='bbox', + format_only=False, + backend_args=backend_args) + + +test_evaluator = dict( + type='CustomResultDumping', # custom evaluating method를 정의 + img_prefix="/data/ephemeral/home/dataset/", # dataset 최상단 폴더 + outfile_path='/data/ephemeral/home/mmdetection/work_dir/co', + classes=classes # class 정보를 담은 list나 집합 + ) \ No newline at end of file diff --git a/mmdetection/CV_05/_base_/simple_augmentation_dataset.py b/mmdetection/CV_05/_base_/simple_augmentation_dataset.py new file mode 100644 index 00000000..0973f2a9 --- /dev/null +++ b/mmdetection/CV_05/_base_/simple_augmentation_dataset.py @@ -0,0 +1,86 @@ +num_classes = 10 +image_size = (1024, 1024) +classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", + "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing") + + +# dataset settings ( 반드시 확인하기 ) +dataset_type = 'CocoDataset' # data format 정의 +data_root = '/data/ephemeral/home/dataset' # json과 train 모두 위치하는 최상단 폴더 +train_ann_file_name = "MLSK_split/train_fold_2.json" # train ann file +val_ann_file_name = "MLSK_split/val_fold_2.json" # val ann file +test_ann_file_name = "test.json" +img_folder = "" # 신경안써도 됨 + + +backend_args = None +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='PackDetInputs') +] + + +train_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), + data_root=data_root, + ann_file=train_ann_file_name, + pipeline=train_pipeline, + data_prefix=dict(img=img_folder), + filter_cfg=dict(filter_empty_gt=False, min_size=32)) +) + + +# follow ViTDet +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=image_size, keep_ratio=True), # diff + dict(type='LoadAnnotations', with_bbox=True, with_mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + metainfo=dict(classes=classes), # 클래스 정보 반드시 적기 + data_root=data_root, # 마찬가지로 data_root + ann_file=val_ann_file_name, # validation_ann_file + data_prefix=dict(img=img_folder), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + "/" + val_ann_file_name, + metric='bbox', + format_only=False, + backend_args=backend_args) + + +test_evaluator = dict( + type='CustomResultDumping', # custom evaluating method를 정의 + img_prefix="/data/ephemeral/home/dataset/", # dataset 최상단 폴더 + outfile_path='/data/ephemeral/home/mmdetection/work_dir/co', + classes=classes # class 정보를 담은 list나 집합 + ) \ No newline at end of file diff --git a/mmdetection/CV_05/cascade-RCNN/cascade-rcnn_convnextV2.py b/mmdetection/CV_05/cascade-RCNN/cascade-rcnn_convnextV2.py new file mode 100644 index 00000000..a0340ecf --- /dev/null +++ b/mmdetection/CV_05/cascade-RCNN/cascade-rcnn_convnextV2.py @@ -0,0 +1,210 @@ +_base_ = [ + '../_base_/default_dataset.py', + '../_base_/default_runtime.py', + '../_base_/default_tta.py' +] + + +# model settings +custom_imports = dict( + imports=['mmpretrain.models'], allow_failed_imports=False) +load_from = None +resume = False +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext-v2/convnext-v2-large_fcmae-in21k-pre_3rdparty_in1k-384px_20230104-9139a1f3.pth' +num_classes = _base_.num_classes + +model = dict( + type='CascadeRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='mmpretrain.ConvNeXt', + arch='large', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.1, + layer_scale_init_value=0., + gap_before_final_norm=False, + use_grn=True, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + neck=[ + dict( + type='FPN', + in_channels=[192, 384, 768, 1536], + start_level=0, + out_channels=256, + num_outs=5), + dict( + type='DyHead', + in_channels=256, + out_channels=256, + num_blocks=6, + # disable zero_init_offset to follow official implementation + zero_init_offset=False) + ], + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + type='CascadeRoIHead', + num_stages=3, + stage_loss_weights=[1, 0.5, 0.25], + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ]), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=[ + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100))) \ No newline at end of file diff --git a/mmdetection/CV_05/cascade-RCNN/cascade-rcnn_swin_L.py b/mmdetection/CV_05/cascade-RCNN/cascade-rcnn_swin_L.py new file mode 100644 index 00000000..cc7e9054 --- /dev/null +++ b/mmdetection/CV_05/cascade-RCNN/cascade-rcnn_swin_L.py @@ -0,0 +1,452 @@ +_base_ = [ + '../configs/_base_/datasets/coco_detection.py', + '../configs/_base_/default_runtime.py' +] + +########################################################################### +#Model +########################################################################### + +pretrained ='https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' + +model = dict( + type='CascadeRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict( + type='FPN', + in_channels=[192, 384, 768, 1536], + out_channels=256, + num_outs=5), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + type='CascadeRoIHead', + num_stages=3, + stage_loss_weights=[1, 0.5, 0.25], + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=10, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=10, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=10, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ]), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=[ + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.00, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100))) + +########################################################################### +#pipeline +########################################################################### + +dataset_type = 'CocoDataset' +data_root ='/data/ephemeral/home/dataset/' +classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", + "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing") +backend_args = None +image_size = (1024,1024) +color_space = [ + [dict(type='ColorTransform')], + [dict(type='AutoContrast')], + [dict(type='Equalize')], + [dict(type='Sharpness')], + [dict(type='Posterize')], + [dict(type='Solarize')], + [dict(type='Color')], + [dict(type='Contrast')], + [dict(type='Brightness')], +] +albu_transform = [ + dict( + type='OneOf', + transforms=[ + dict(type='CLAHE', clip_limit=4.0, tile_grid_size=(8, 8), p=1.0), + dict(type='RandomGamma', p=1.0), + dict(type='HueSaturationValue', p=1.0), + dict(type='ChannelDropout', p=1.0), + dict(type='ChannelShuffle', p=1.0), + dict(type='RGBShift', p=1.0) + ], + p=0.5 + ), + dict( + type='OneOf', + transforms=[ + dict(type='GaussNoise', p=1.0), + dict(type='GaussianBlur', p=1.0), + dict(type='Blur', p=1.0) + ], + p=0.1 + ), + dict( + type='OneOf', + transforms=[ + dict(type='ShiftScaleRotate', p=1.0), + dict(type='RandomRotate90', p=1.0), + ], + p=0.1 + ) +] + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=[0.5, 0.15], direction=['horizontal', 'vertical']), + # dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict( + type='Albu', + transforms=albu_transform, + bbox_params=dict( + type='BboxParams', + format='pascal_voc', + label_fields=['gt_bboxes_labels', 'gt_ignore_flags'], + min_visibility=0.0, + filter_lost_elements=True), + keymap={ + 'img': 'image', + 'gt_bboxes': 'bboxes' + }, + skip_img_without_anno=True + ), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1024, 1024), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='PackDetInputs') # 이 부분이 있는지 확인 +] +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict( + type='nms', + iou_threshold=0.5), + max_per_img=300)) + +img_scales = [(1333, 800), (666, 400), (2000, 1200)] +tta_pipeline = [ + dict(type='LoadImageFromFile', + backend_args=None), + dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale=s, keep_ratio=True) for s in img_scales + ], [ + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'flip', + 'flip_direction')) + ]])] +train_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train_kfold_1.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + metainfo=dict(classes=classes), + backend_args=backend_args) +) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val_kfold_1.json', + data_prefix=dict(img=''), + test_mode=True, + pipeline=test_pipeline, + metainfo=dict(classes=classes), + backend_args=backend_args)) + +test_dataloader = ( + dict( + batch_size=2, + num_workers=4, + dataset=dict( + data_root=data_root, + ann_file='test.json', + data_prefix=dict(img=''), + pipeline=test_pipeline, + metainfo=dict(classes=classes)))) + +########################################################################### +#evaluator +########################################################################### + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'val_kfold_1.json', + metric=['bbox'], + format_only=False, + backend_args=backend_args) + +test_evaluator = dict( + _delete_=True, + type='CustomResultDumping', # custom evaluating method를 정의 + img_prefix="/data/ephemeral/home/dataset/", # dataset 최상단 폴더 + outfile_path='/data/ephemeral/home/mmdetection/work_dir/co', + classes=classes # class 정보를 담은 list나 집합 + ) + +########################################################################### +#schedule & optimize +########################################################################### + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.05), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.05)})) + +# learning policy +max_epochs = 30 # 에폭 수 증가 +train_cfg = dict( + type='EpochBasedTrainLoop', + max_epochs=max_epochs, + val_interval=1) + +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate scheduler +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.001, # 웜업 시작 학습률 증가 + by_epoch=False, + begin=0, + end=1000), + dict( + type='CosineAnnealingLR', + eta_min=1e-6, + begin=1000, + end=max_epochs * 1000, + T_max=max_epochs * 1000, + by_epoch=False) +] + + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) + +########################################################################### +#visuallization & hooks +########################################################################### + +vis_backends = [ + dict(type='LocalVisBackend', save_dir='visualizations'), + dict(type='WandbVisBackend', + init_kwargs={ + 'project': 'mmdetection', + 'name': 'cascade' + }) +] +default_scope = 'mmdet' +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', interval=1, max_keep_ckpts=3), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='DetVisualizationHook')) + +env_cfg = dict( + cudnn_benchmark=False, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) + +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=True) + +log_level = 'INFO' +load_from = None +resume = False \ No newline at end of file diff --git a/mmdetection/CV_05/ddq/ddq_swinl.py b/mmdetection/CV_05/ddq/ddq_swinl.py new file mode 100644 index 00000000..fadddaf5 --- /dev/null +++ b/mmdetection/CV_05/ddq/ddq_swinl.py @@ -0,0 +1,160 @@ +_base_ = [ + '../_base_/default_dataset.py', + '../_base_/default_runtime.py', + '../_base_/default_tta.py' +] + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa: E501 +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq_detr_swinl_30e.pth' + +model = dict( + type='DDQDETR', + num_queries=900, # num_matching_queries + # ratio of num_dense queries to num_queries + dense_topk_ratio=1.5, + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict( + type='ChannelMapper', + in_channels=[384, 768, 1536], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + # encoder class name: DeformableDetrTransformerEncoder + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0))), # 0.1 for DeformDETR + # decoder class name: DDQTransformerDecoder + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_heads=8, + dropout=0.0), # 0.1 for DeformDETR + cross_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0)), # 0.1 for DeformDETR + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, + normalize=True, + offset=0.0, # -0.5 for DeformDETR + temperature=20), # 10000 for DeformDETR + bbox_head=dict( + type='DDQDETRHead', + num_classes=_base_.num_classes, + sync_cls_avg_factor=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( + label_noise_scale=0.5, + box_noise_scale=1.0, + group_cfg=dict(dynamic=True, num_groups=None, num_dn_queries=100)), + dqs_cfg=dict(type='nms', iou_threshold=0.8), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) + + + +backend_args = None +train_pipeline = [ + dict(type='Mosaic', img_scale=(1024, 1024), prob=1.0), + dict(type='RandomFlip', prob=0.5), + dict(type='RandomAffine'), + dict(type='PhotoMetricDistortion'), + dict(type='Corrupt', corruption='gaussian_blur'), + dict(type='PackDetInputs') +] +train_dataloader = dict( + dataset=dict( + pipeline=train_pipeline + ) +) + + + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.05), + clip_grad=dict(max_norm=0.1, norm_type=2), + accumulative_counts=2, + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.05)})) + + +# learning policy +max_epochs = 36 # 에폭 수 증가 +train_cfg = dict( + type='EpochBasedTrainLoop', + max_epochs=max_epochs, + val_interval=1) + +param_scheduler = [ + dict( + type='LinearLR', + begin=0, + end=2000, + by_epoch=False, + start_factor=0.0001 + ), + dict( + type='MultiStepLR', + begin=0, + end=30, + gamma=0.1, + by_epoch=True, + milestones=[ + 20, + 26 + ] + ) +] \ No newline at end of file diff --git a/mmdetection/LICENSE b/mmdetection/LICENSE new file mode 100644 index 00000000..1bfc23e4 --- /dev/null +++ b/mmdetection/LICENSE @@ -0,0 +1,203 @@ +Copyright 2018-2023 OpenMMLab. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2023 OpenMMLab. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/mmdetection/MANIFEST.in b/mmdetection/MANIFEST.in new file mode 100644 index 00000000..7398e6a6 --- /dev/null +++ b/mmdetection/MANIFEST.in @@ -0,0 +1,7 @@ +include requirements/*.txt +include mmdet/VERSION +include mmdet/.mim/model-index.yml +include mmdet/.mim/dataset-index.yml +include mmdet/.mim/demo/*/* +recursive-include mmdet/.mim/configs *.py *.yml +recursive-include mmdet/.mim/tools *.sh *.py diff --git a/mmdetection/README.md b/mmdetection/README.md new file mode 100644 index 00000000..34f7f0b8 --- /dev/null +++ b/mmdetection/README.md @@ -0,0 +1,455 @@ +
+ +
 
+
+ OpenMMLab website + + + HOT + + +      + OpenMMLab platform + + + TRY IT OUT + + +
+
 
+ +[![PyPI](https://img.shields.io/pypi/v/mmdet)](https://pypi.org/project/mmdet) +[![docs](https://img.shields.io/badge/docs-latest-blue)](https://mmdetection.readthedocs.io/en/latest/) +[![badge](https://github.com/open-mmlab/mmdetection/workflows/build/badge.svg)](https://github.com/open-mmlab/mmdetection/actions) +[![codecov](https://codecov.io/gh/open-mmlab/mmdetection/branch/main/graph/badge.svg)](https://codecov.io/gh/open-mmlab/mmdetection) +[![license](https://img.shields.io/github/license/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/blob/main/LICENSE) +[![open issues](https://isitmaintained.com/badge/open/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/issues) +[![issue resolution](https://isitmaintained.com/badge/resolution/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/issues) +[![Open in OpenXLab](https://cdn-static.openxlab.org.cn/app-center/openxlab_demo.svg)](https://openxlab.org.cn/apps?search=mmdet) + +[📘Documentation](https://mmdetection.readthedocs.io/en/latest/) | +[🛠️Installation](https://mmdetection.readthedocs.io/en/latest/get_started.html) | +[👀Model Zoo](https://mmdetection.readthedocs.io/en/latest/model_zoo.html) | +[🆕Update News](https://mmdetection.readthedocs.io/en/latest/notes/changelog.html) | +[🚀Ongoing Projects](https://github.com/open-mmlab/mmdetection/projects) | +[🤔Reporting Issues](https://github.com/open-mmlab/mmdetection/issues/new/choose) + +
+ +
+ +English | [简体中文](README_zh-CN.md) + +
+ +
+ + + + + + + + + + + + + + + + + +
+ +
+ +
+ +## Introduction + +MMDetection is an open source object detection toolbox based on PyTorch. It is +a part of the [OpenMMLab](https://openmmlab.com/) project. + +The main branch works with **PyTorch 1.8+**. + + + +
+Major features + +- **Modular Design** + + We decompose the detection framework into different components and one can easily construct a customized object detection framework by combining different modules. + +- **Support of multiple tasks out of box** + + The toolbox directly supports multiple detection tasks such as **object detection**, **instance segmentation**, **panoptic segmentation**, and **semi-supervised object detection**. + +- **High efficiency** + + All basic bbox and mask operations run on GPUs. The training speed is faster than or comparable to other codebases, including [Detectron2](https://github.com/facebookresearch/detectron2), [maskrcnn-benchmark](https://github.com/facebookresearch/maskrcnn-benchmark) and [SimpleDet](https://github.com/TuSimple/simpledet). + +- **State of the art** + + The toolbox stems from the codebase developed by the *MMDet* team, who won [COCO Detection Challenge](http://cocodataset.org/#detection-leaderboard) in 2018, and we keep pushing it forward. + The newly released [RTMDet](configs/rtmdet) also obtains new state-of-the-art results on real-time instance segmentation and rotated object detection tasks and the best parameter-accuracy trade-off on object detection. + +
+ +Apart from MMDetection, we also released [MMEngine](https://github.com/open-mmlab/mmengine) for model training and [MMCV](https://github.com/open-mmlab/mmcv) for computer vision research, which are heavily depended on by this toolbox. + +## What's New + +💎 **We have released the pre-trained weights for MM-Grounding-DINO Swin-B and Swin-L, welcome to try and give feedback.** + +### Highlight + +**v3.3.0** was released in 5/1/2024: + +**[MM-Grounding-DINO: An Open and Comprehensive Pipeline for Unified Object Grounding and Detection](https://arxiv.org/abs/2401.02361)** + +Grounding DINO is a grounding pre-training model that unifies 2d open vocabulary object detection and phrase grounding, with wide applications. However, its training part has not been open sourced. Therefore, we propose MM-Grounding-DINO, which not only serves as an open source replication version of Grounding DINO, but also achieves significant performance improvement based on reconstructed data types, exploring different dataset combinations and initialization strategies. Moreover, we conduct evaluations from multiple dimensions, including OOD, REC, Phrase Grounding, OVD, and Fine-tune, to fully excavate the advantages and disadvantages of Grounding pre-training, hoping to provide inspiration for future work. + +code: [mm_grounding_dino/README.md](configs/mm_grounding_dino/README.md) + +
+ +
+ +We are excited to announce our latest work on real-time object recognition tasks, **RTMDet**, a family of fully convolutional single-stage detectors. RTMDet not only achieves the best parameter-accuracy trade-off on object detection from tiny to extra-large model sizes but also obtains new state-of-the-art performance on instance segmentation and rotated object detection tasks. Details can be found in the [technical report](https://arxiv.org/abs/2212.07784). Pre-trained models are [here](configs/rtmdet). + +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/real-time-instance-segmentation-on-mscoco)](https://paperswithcode.com/sota/real-time-instance-segmentation-on-mscoco?p=rtmdet-an-empirical-study-of-designing-real) +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/object-detection-in-aerial-images-on-dota-1)](https://paperswithcode.com/sota/object-detection-in-aerial-images-on-dota-1?p=rtmdet-an-empirical-study-of-designing-real) +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/object-detection-in-aerial-images-on-hrsc2016)](https://paperswithcode.com/sota/object-detection-in-aerial-images-on-hrsc2016?p=rtmdet-an-empirical-study-of-designing-real) + +| Task | Dataset | AP | FPS(TRT FP16 BS1 3090) | +| ------------------------ | ------- | ------------------------------------ | ---------------------- | +| Object Detection | COCO | 52.8 | 322 | +| Instance Segmentation | COCO | 44.6 | 188 | +| Rotated Object Detection | DOTA | 78.9(single-scale)/81.3(multi-scale) | 121 | + +
+ +
+ +## Installation + +Please refer to [Installation](https://mmdetection.readthedocs.io/en/latest/get_started.html) for installation instructions. + +## Getting Started + +Please see [Overview](https://mmdetection.readthedocs.io/en/latest/get_started.html) for the general introduction of MMDetection. + +For detailed user guides and advanced guides, please refer to our [documentation](https://mmdetection.readthedocs.io/en/latest/): + +- User Guides + +
+ + - [Train & Test](https://mmdetection.readthedocs.io/en/latest/user_guides/index.html#train-test) + - [Learn about Configs](https://mmdetection.readthedocs.io/en/latest/user_guides/config.html) + - [Inference with existing models](https://mmdetection.readthedocs.io/en/latest/user_guides/inference.html) + - [Dataset Prepare](https://mmdetection.readthedocs.io/en/latest/user_guides/dataset_prepare.html) + - [Test existing models on standard datasets](https://mmdetection.readthedocs.io/en/latest/user_guides/test.html) + - [Train predefined models on standard datasets](https://mmdetection.readthedocs.io/en/latest/user_guides/train.html) + - [Train with customized datasets](https://mmdetection.readthedocs.io/en/latest/user_guides/train.html#train-with-customized-datasets) + - [Train with customized models and standard datasets](https://mmdetection.readthedocs.io/en/latest/user_guides/new_model.html) + - [Finetuning Models](https://mmdetection.readthedocs.io/en/latest/user_guides/finetune.html) + - [Test Results Submission](https://mmdetection.readthedocs.io/en/latest/user_guides/test_results_submission.html) + - [Weight initialization](https://mmdetection.readthedocs.io/en/latest/user_guides/init_cfg.html) + - [Use a single stage detector as RPN](https://mmdetection.readthedocs.io/en/latest/user_guides/single_stage_as_rpn.html) + - [Semi-supervised Object Detection](https://mmdetection.readthedocs.io/en/latest/user_guides/semi_det.html) + - [Useful Tools](https://mmdetection.readthedocs.io/en/latest/user_guides/index.html#useful-tools) + +
+ +- Advanced Guides + +
+ + - [Basic Concepts](https://mmdetection.readthedocs.io/en/latest/advanced_guides/index.html#basic-concepts) + - [Component Customization](https://mmdetection.readthedocs.io/en/latest/advanced_guides/index.html#component-customization) + - [How to](https://mmdetection.readthedocs.io/en/latest/advanced_guides/index.html#how-to) + +
+ +We also provide object detection colab tutorial [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](demo/MMDet_Tutorial.ipynb) and instance segmentation colab tutorial [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](demo/MMDet_InstanceSeg_Tutorial.ipynb). + +To migrate from MMDetection 2.x, please refer to [migration](https://mmdetection.readthedocs.io/en/latest/migration.html). + +## Overview of Benchmark and Model Zoo + +Results and models are available in the [model zoo](docs/en/model_zoo.md). + +
+ Architectures +
+ + + + + + + + + + + + + + + + + +
+ Object Detection + + Instance Segmentation + + Panoptic Segmentation + + Other +
+ + + + + + + +
  • Contrastive Learning
  • + + +
  • Distillation
  • + +
  • Semi-Supervised Object Detection
  • + + +
    + +
    + Components +
    + + + + + + + + + + + + + + + + + +
    + Backbones + + Necks + + Loss + + Common +
    + + + + + + + +
    + +Some other methods are also supported in [projects using MMDetection](./docs/en/notes/projects.md). + +## FAQ + +Please refer to [FAQ](docs/en/notes/faq.md) for frequently asked questions. + +## Contributing + +We appreciate all contributions to improve MMDetection. Ongoing projects can be found in out [GitHub Projects](https://github.com/open-mmlab/mmdetection/projects). Welcome community users to participate in these projects. Please refer to [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the contributing guideline. + +## Acknowledgement + +MMDetection is an open source project that is contributed by researchers and engineers from various colleges and companies. We appreciate all the contributors who implement their methods or add new features, as well as users who give valuable feedbacks. +We wish that the toolbox and benchmark could serve the growing research community by providing a flexible toolkit to reimplement existing methods and develop their own new detectors. + +## Citation + +If you use this toolbox or benchmark in your research, please cite this project. + +``` +@article{mmdetection, + title = {{MMDetection}: Open MMLab Detection Toolbox and Benchmark}, + author = {Chen, Kai and Wang, Jiaqi and Pang, Jiangmiao and Cao, Yuhang and + Xiong, Yu and Li, Xiaoxiao and Sun, Shuyang and Feng, Wansen and + Liu, Ziwei and Xu, Jiarui and Zhang, Zheng and Cheng, Dazhi and + Zhu, Chenchen and Cheng, Tianheng and Zhao, Qijie and Li, Buyu and + Lu, Xin and Zhu, Rui and Wu, Yue and Dai, Jifeng and Wang, Jingdong + and Shi, Jianping and Ouyang, Wanli and Loy, Chen Change and Lin, Dahua}, + journal= {arXiv preprint arXiv:1906.07155}, + year={2019} +} +``` + +## License + +This project is released under the [Apache 2.0 license](LICENSE). + +## Projects in OpenMMLab + +- [MMEngine](https://github.com/open-mmlab/mmengine): OpenMMLab foundational library for training deep learning models. +- [MMCV](https://github.com/open-mmlab/mmcv): OpenMMLab foundational library for computer vision. +- [MMPreTrain](https://github.com/open-mmlab/mmpretrain): OpenMMLab pre-training toolbox and benchmark. +- [MMagic](https://github.com/open-mmlab/mmagic): Open**MM**Lab **A**dvanced, **G**enerative and **I**ntelligent **C**reation toolbox. +- [MMDetection](https://github.com/open-mmlab/mmdetection): OpenMMLab detection toolbox and benchmark. +- [MMDetection3D](https://github.com/open-mmlab/mmdetection3d): OpenMMLab's next-generation platform for general 3D object detection. +- [MMRotate](https://github.com/open-mmlab/mmrotate): OpenMMLab rotated object detection toolbox and benchmark. +- [MMYOLO](https://github.com/open-mmlab/mmyolo): OpenMMLab YOLO series toolbox and benchmark. +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation): OpenMMLab semantic segmentation toolbox and benchmark. +- [MMOCR](https://github.com/open-mmlab/mmocr): OpenMMLab text detection, recognition, and understanding toolbox. +- [MMPose](https://github.com/open-mmlab/mmpose): OpenMMLab pose estimation toolbox and benchmark. +- [MMHuman3D](https://github.com/open-mmlab/mmhuman3d): OpenMMLab 3D human parametric model toolbox and benchmark. +- [MMSelfSup](https://github.com/open-mmlab/mmselfsup): OpenMMLab self-supervised learning toolbox and benchmark. +- [MMRazor](https://github.com/open-mmlab/mmrazor): OpenMMLab model compression toolbox and benchmark. +- [MMFewShot](https://github.com/open-mmlab/mmfewshot): OpenMMLab fewshot learning toolbox and benchmark. +- [MMAction2](https://github.com/open-mmlab/mmaction2): OpenMMLab's next-generation action understanding toolbox and benchmark. +- [MMTracking](https://github.com/open-mmlab/mmtracking): OpenMMLab video perception toolbox and benchmark. +- [MMFlow](https://github.com/open-mmlab/mmflow): OpenMMLab optical flow toolbox and benchmark. +- [MMEditing](https://github.com/open-mmlab/mmediting): OpenMMLab image and video editing toolbox. +- [MMGeneration](https://github.com/open-mmlab/mmgeneration): OpenMMLab image and video generative models toolbox. +- [MMDeploy](https://github.com/open-mmlab/mmdeploy): OpenMMLab model deployment framework. +- [MIM](https://github.com/open-mmlab/mim): MIM installs OpenMMLab packages. +- [MMEval](https://github.com/open-mmlab/mmeval): A unified evaluation library for multiple machine learning libraries. +- [Playground](https://github.com/open-mmlab/playground): A central hub for gathering and showcasing amazing projects built upon OpenMMLab. diff --git a/mmdetection/README_zh-CN.md b/mmdetection/README_zh-CN.md new file mode 100644 index 00000000..8d7c060f --- /dev/null +++ b/mmdetection/README_zh-CN.md @@ -0,0 +1,476 @@ +
    + +
     
    +
    + OpenMMLab 官网 + + + HOT + + +      + OpenMMLab 开放平台 + + + TRY IT OUT + + +
    +
     
    + +[![PyPI](https://img.shields.io/pypi/v/mmdet)](https://pypi.org/project/mmdet) +[![docs](https://img.shields.io/badge/docs-latest-blue)](https://mmdetection.readthedocs.io/en/latest/) +[![badge](https://github.com/open-mmlab/mmdetection/workflows/build/badge.svg)](https://github.com/open-mmlab/mmdetection/actions) +[![codecov](https://codecov.io/gh/open-mmlab/mmdetection/branch/main/graph/badge.svg)](https://codecov.io/gh/open-mmlab/mmdetection) +[![license](https://img.shields.io/github/license/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/blob/main/LICENSE) +[![open issues](https://isitmaintained.com/badge/open/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/issues) +[![issue resolution](https://isitmaintained.com/badge/resolution/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/issues) +[![Open in OpenXLab](https://cdn-static.openxlab.org.cn/app-center/openxlab_demo.svg)](https://openxlab.org.cn/apps?search=mmdet) + +[📘使用文档](https://mmdetection.readthedocs.io/zh_CN/latest/) | +[🛠️安装教程](https://mmdetection.readthedocs.io/zh_CN/latest/get_started.html) | +[👀模型库](https://mmdetection.readthedocs.io/zh_CN/latest/model_zoo.html) | +[🆕更新日志](https://mmdetection.readthedocs.io/en/latest/notes/changelog.html) | +[🚀进行中的项目](https://github.com/open-mmlab/mmdetection/projects) | +[🤔报告问题](https://github.com/open-mmlab/mmdetection/issues/new/choose) + +
    + +
    + +[English](README.md) | 简体中文 + +
    + +
    + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +## 简介 + +MMDetection 是一个基于 PyTorch 的目标检测开源工具箱。它是 [OpenMMLab](https://openmmlab.com/) 项目的一部分。 + +主分支代码目前支持 PyTorch 1.8 及其以上的版本。 + + + +
    +主要特性 + +- **模块化设计** + + MMDetection 将检测框架解耦成不同的模块组件,通过组合不同的模块组件,用户可以便捷地构建自定义的检测模型 + +- **支持多种检测任务** + + MMDetection 支持了各种不同的检测任务,包括**目标检测**,**实例分割**,**全景分割**,以及**半监督目标检测**。 + +- **速度快** + + 基本的框和 mask 操作都实现了 GPU 版本,训练速度比其他代码库更快或者相当,包括 [Detectron2](https://github.com/facebookresearch/detectron2), [maskrcnn-benchmark](https://github.com/facebookresearch/maskrcnn-benchmark) 和 [SimpleDet](https://github.com/TuSimple/simpledet)。 + +- **性能高** + + MMDetection 这个算法库源自于 COCO 2018 目标检测竞赛的冠军团队 *MMDet* 团队开发的代码,我们在之后持续进行了改进和提升。 + 新发布的 [RTMDet](configs/rtmdet) 还在实时实例分割和旋转目标检测任务中取得了最先进的成果,同时也在目标检测模型中取得了最佳的的参数量和精度平衡。 + +
    + +除了 MMDetection 之外,我们还开源了深度学习训练库 [MMEngine](https://github.com/open-mmlab/mmengine) 和计算机视觉基础库 [MMCV](https://github.com/open-mmlab/mmcv),它们是 MMDetection 的主要依赖。 + +## 最新进展 + +💎 **我们已经发布了 MM-Grounding-DINO Swin-B 和 Swin-L 预训练权重,欢迎试用和反馈.** + +### 亮点 + +**v3.3.0** 版本已经在 2024.1.5 发布: + +**MM-Grounding-DINO: 轻松涨点,数据到评测全面开源** + +Grounding DINO 是一个统一了 2d 开放词汇目标检测和 Phrase Grounding 的检测预训练模型,应用广泛,但是其训练部分并未开源,为此提出了 MM-Grounding-DINO。其不仅作为 Grounding DINO 的开源复现版,MM-Grounding-DINO 基于重新构建的数据类型出发,在探索了不同数据集组合和初始化策略基础上实现了 Grounding DINO 的性能极大提升,并且从多个维度包括 OOD、REC、Phrase Grounding、OVD 和 Finetune 等方面进行评测,充分挖掘 Grounding 预训练优缺点,希望能为后续工作提供启发。 + +arxiv 技术报告:https://arxiv.org/abs/2401.02361 + +代码地址: [mm_grounding_dino/README.md](configs/mm_grounding_dino/README.md) + +
    + +
    + +我们很高兴向大家介绍我们在实时目标识别任务方面的最新成果 RTMDet,包含了一系列的全卷积单阶段检测模型。 RTMDet 不仅在从 tiny 到 extra-large 尺寸的目标检测模型上实现了最佳的参数量和精度的平衡,而且在实时实例分割和旋转目标检测任务上取得了最先进的成果。 更多细节请参阅[技术报告](https://arxiv.org/abs/2212.07784)。 预训练模型可以在[这里](configs/rtmdet)找到。 + +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/real-time-instance-segmentation-on-mscoco)](https://paperswithcode.com/sota/real-time-instance-segmentation-on-mscoco?p=rtmdet-an-empirical-study-of-designing-real) +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/object-detection-in-aerial-images-on-dota-1)](https://paperswithcode.com/sota/object-detection-in-aerial-images-on-dota-1?p=rtmdet-an-empirical-study-of-designing-real) +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/object-detection-in-aerial-images-on-hrsc2016)](https://paperswithcode.com/sota/object-detection-in-aerial-images-on-hrsc2016?p=rtmdet-an-empirical-study-of-designing-real) + +| Task | Dataset | AP | FPS(TRT FP16 BS1 3090) | +| ------------------------ | ------- | ------------------------------------ | ---------------------- | +| Object Detection | COCO | 52.8 | 322 | +| Instance Segmentation | COCO | 44.6 | 188 | +| Rotated Object Detection | DOTA | 78.9(single-scale)/81.3(multi-scale) | 121 | + +
    + +
    + +## 安装 + +请参考[快速入门文档](https://mmdetection.readthedocs.io/zh_CN/latest/get_started.html)进行安装。 + +## 教程 + +请阅读[概述](https://mmdetection.readthedocs.io/zh_CN/latest/get_started.html)对 MMDetection 进行初步的了解。 + +为了帮助用户更进一步了解 MMDetection,我们准备了用户指南和进阶指南,请阅读我们的[文档](https://mmdetection.readthedocs.io/zh_CN/latest/): + +- 用户指南 + +
    + + - [训练 & 测试](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/index.html#train-test) + - [学习配置文件](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/config.html) + - [使用已有模型在标准数据集上进行推理](https://mmdetection.readthedocs.io/en/latest/user_guides/inference.html) + - [数据集准备](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/dataset_prepare.html) + - [测试现有模型](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/test.html) + - [在标准数据集上训练预定义的模型](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/train.html) + - [在自定义数据集上进行训练](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/train.html#train-with-customized-datasets) + - [在标准数据集上训练自定义模型](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/new_model.html) + - [模型微调](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/finetune.html) + - [提交测试结果](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/test_results_submission.html) + - [权重初始化](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/init_cfg.html) + - [将单阶段检测器作为 RPN](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/single_stage_as_rpn.html) + - [半监督目标检测](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/semi_det.html) + - [实用工具](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/index.html#useful-tools) + +
    + +- 进阶指南 + +
    + + - [基础概念](https://mmdetection.readthedocs.io/zh_CN/latest/advanced_guides/index.html#basic-concepts) + - [组件定制](https://mmdetection.readthedocs.io/zh_CN/latest/advanced_guides/index.html#component-customization) + - [How to](https://mmdetection.readthedocs.io/zh_CN/latest/advanced_guides/index.html#how-to) + +
    + +我们提供了检测的 colab 教程 [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](demo/MMDet_Tutorial.ipynb) 和 实例分割的 colab 教程 [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](demo/MMDet_Tutorial.ipynb) + +同时,我们还提供了 [MMDetection 中文解读文案汇总](docs/zh_cn/article.md) + +若需要将2.x版本的代码迁移至新版,请参考[迁移文档](https://mmdetection.readthedocs.io/en/latest/migration.html)。 + +## 基准测试和模型库 + +测试结果和模型可以在[模型库](docs/zh_cn/model_zoo.md)中找到。 + +
    + 算法架构 +
    + + + + + + + + + + + + + + + + + +
    + Object Detection + + Instance Segmentation + + Panoptic Segmentation + + Other +
    + + + + + + + +
  • Contrastive Learning
  • + + +
  • Distillation
  • + +
  • Semi-Supervised Object Detection
  • + + +
    + +
    + 模块组件 +
    + + + + + + + + + + + + + + + + + +
    + Backbones + + Necks + + Loss + + Common +
    + + + + + + + +
    + +我们在[基于 MMDetection 的项目](./docs/zh_cn/notes/projects.md)中列举了一些其他的支持的算法。 + +## 常见问题 + +请参考 [FAQ](docs/zh_cn/notes/faq.md) 了解其他用户的常见问题。 + +## 贡献指南 + +我们感谢所有的贡献者为改进和提升 MMDetection 所作出的努力。我们将正在进行中的项目添加进了[GitHub Projects](https://github.com/open-mmlab/mmdetection/projects)页面,非常欢迎社区用户能参与进这些项目中来。请参考[贡献指南](.github/CONTRIBUTING.md)来了解参与项目贡献的相关指引。 + +## 致谢 + +MMDetection 是一款由来自不同高校和企业的研发人员共同参与贡献的开源项目。我们感谢所有为项目提供算法复现和新功能支持的贡献者,以及提供宝贵反馈的用户。 我们希望这个工具箱和基准测试可以为社区提供灵活的代码工具,供用户复现已有算法并开发自己的新模型,从而不断为开源社区提供贡献。 + +## 引用 + +如果你在研究中使用了本项目的代码或者性能基准,请参考如下 bibtex 引用 MMDetection。 + +``` +@article{mmdetection, + title = {{MMDetection}: Open MMLab Detection Toolbox and Benchmark}, + author = {Chen, Kai and Wang, Jiaqi and Pang, Jiangmiao and Cao, Yuhang and + Xiong, Yu and Li, Xiaoxiao and Sun, Shuyang and Feng, Wansen and + Liu, Ziwei and Xu, Jiarui and Zhang, Zheng and Cheng, Dazhi and + Zhu, Chenchen and Cheng, Tianheng and Zhao, Qijie and Li, Buyu and + Lu, Xin and Zhu, Rui and Wu, Yue and Dai, Jifeng and Wang, Jingdong + and Shi, Jianping and Ouyang, Wanli and Loy, Chen Change and Lin, Dahua}, + journal= {arXiv preprint arXiv:1906.07155}, + year={2019} +} +``` + +## 开源许可证 + +该项目采用 [Apache 2.0 开源许可证](LICENSE)。 + +## OpenMMLab 的其他项目 + +- [MMEngine](https://github.com/open-mmlab/mmengine): OpenMMLab 深度学习模型训练基础库 +- [MMCV](https://github.com/open-mmlab/mmcv): OpenMMLab 计算机视觉基础库 +- [MMPreTrain](https://github.com/open-mmlab/mmpretrain): OpenMMLab 深度学习预训练工具箱 +- [MMagic](https://github.com/open-mmlab/mmagic): OpenMMLab 新一代人工智能内容生成(AIGC)工具箱 +- [MMDetection](https://github.com/open-mmlab/mmdetection): OpenMMLab 目标检测工具箱 +- [MMDetection3D](https://github.com/open-mmlab/mmdetection3d): OpenMMLab 新一代通用 3D 目标检测平台 +- [MMRotate](https://github.com/open-mmlab/mmrotate): OpenMMLab 旋转框检测工具箱与测试基准 +- [MMYOLO](https://github.com/open-mmlab/mmyolo): OpenMMLab YOLO 系列工具箱与测试基准 +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation): OpenMMLab 语义分割工具箱 +- [MMOCR](https://github.com/open-mmlab/mmocr): OpenMMLab 全流程文字检测识别理解工具包 +- [MMPose](https://github.com/open-mmlab/mmpose): OpenMMLab 姿态估计工具箱 +- [MMHuman3D](https://github.com/open-mmlab/mmhuman3d): OpenMMLab 人体参数化模型工具箱与测试基准 +- [MMSelfSup](https://github.com/open-mmlab/mmselfsup): OpenMMLab 自监督学习工具箱与测试基准 +- [MMRazor](https://github.com/open-mmlab/mmrazor): OpenMMLab 模型压缩工具箱与测试基准 +- [MMFewShot](https://github.com/open-mmlab/mmfewshot): OpenMMLab 少样本学习工具箱与测试基准 +- [MMAction2](https://github.com/open-mmlab/mmaction2): OpenMMLab 新一代视频理解工具箱 +- [MMTracking](https://github.com/open-mmlab/mmtracking): OpenMMLab 一体化视频目标感知平台 +- [MMFlow](https://github.com/open-mmlab/mmflow): OpenMMLab 光流估计工具箱与测试基准 +- [MMEditing](https://github.com/open-mmlab/mmediting): OpenMMLab 图像视频编辑工具箱 +- [MMGeneration](https://github.com/open-mmlab/mmgeneration): OpenMMLab 图片视频生成模型工具箱 +- [MMDeploy](https://github.com/open-mmlab/mmdeploy): OpenMMLab 模型部署框架 +- [MIM](https://github.com/open-mmlab/mim): OpenMMlab 项目、算法、模型的统一入口 +- [MMEval](https://github.com/open-mmlab/mmeval): 统一开放的跨框架算法评测库 +- [Playground](https://github.com/open-mmlab/playground): 收集和展示 OpenMMLab 相关的前沿、有趣的社区项目 + +## 欢迎加入 OpenMMLab 社区 + +扫描下方的二维码可关注 OpenMMLab 团队的 [知乎官方账号](https://www.zhihu.com/people/openmmlab),扫描下方微信二维码添加喵喵好友,进入 MMDectection 微信交流社群。【加好友申请格式:研究方向+地区+学校/公司+姓名】 + +
    + +
    + +我们会在 OpenMMLab 社区为大家 + +- 📢 分享 AI 框架的前沿核心技术 +- 💻 解读 PyTorch 常用模块源码 +- 📰 发布 OpenMMLab 的相关新闻 +- 🚀 介绍 OpenMMLab 开发的前沿算法 +- 🏃 获取更高效的问题答疑和意见反馈 +- 🔥 提供与各行各业开发者充分交流的平台 + +干货满满 📘,等你来撩 💗,OpenMMLab 社区期待您的加入 👬 diff --git a/mmdetection/configs/_base_/datasets/ade20k_instance.py b/mmdetection/configs/_base_/datasets/ade20k_instance.py new file mode 100644 index 00000000..57f657aa --- /dev/null +++ b/mmdetection/configs/_base_/datasets/ade20k_instance.py @@ -0,0 +1,53 @@ +# dataset settings +dataset_type = 'ADE20KInstanceDataset' +data_root = 'data/ADEChallengeData2016/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/ADEChallengeData2016/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(2560, 640), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='ade20k_instance_val.json', + data_prefix=dict(img='images/validation'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'ade20k_instance_val.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/ade20k_panoptic.py b/mmdetection/configs/_base_/datasets/ade20k_panoptic.py new file mode 100644 index 00000000..7be5ddd7 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/ade20k_panoptic.py @@ -0,0 +1,38 @@ +# dataset settings +dataset_type = 'ADE20KPanopticDataset' +data_root = 'data/ADEChallengeData2016/' + +backend_args = None + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(2560, 640), keep_ratio=True), + dict(type='LoadPanopticAnnotations', backend_args=backend_args), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=0, + persistent_workers=False, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='ade20k_panoptic_val.json', + data_prefix=dict(img='images/validation/', seg='ade20k_panoptic_val/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoPanopticMetric', + ann_file=data_root + 'ade20k_panoptic_val.json', + seg_prefix=data_root + 'ade20k_panoptic_val/', + backend_args=backend_args) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/ade20k_semantic.py b/mmdetection/configs/_base_/datasets/ade20k_semantic.py new file mode 100644 index 00000000..522a7757 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/ade20k_semantic.py @@ -0,0 +1,48 @@ +dataset_type = 'ADE20KSegDataset' +data_root = 'data/ADEChallengeData2016/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/ADEChallengeData2016/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + dict( + type='LoadAnnotations', + with_bbox=False, + with_mask=False, + with_seg=True, + reduce_zero_label=True), + dict( + type='PackDetInputs', meta_keys=('img_path', 'ori_shape', 'img_shape')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='SemSegMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/cityscapes_detection.py b/mmdetection/configs/_base_/datasets/cityscapes_detection.py new file mode 100644 index 00000000..caeba6bf --- /dev/null +++ b/mmdetection/configs/_base_/datasets/cityscapes_detection.py @@ -0,0 +1,84 @@ +# dataset settings +dataset_type = 'CityscapesDataset' +data_root = 'data/cityscapes/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/segmentation/cityscapes/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/segmentation/', +# 'data/': 's3://openmmlab/datasets/segmentation/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=[(2048, 800), (2048, 1024)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='RepeatDataset', + times=8, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instancesonly_filtered_gtFine_train.json', + data_prefix=dict(img='leftImg8bit/train/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instancesonly_filtered_gtFine_val.json', + data_prefix=dict(img='leftImg8bit/val/'), + test_mode=True, + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instancesonly_filtered_gtFine_val.json', + metric='bbox', + backend_args=backend_args) + +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/cityscapes_instance.py b/mmdetection/configs/_base_/datasets/cityscapes_instance.py new file mode 100644 index 00000000..13640313 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/cityscapes_instance.py @@ -0,0 +1,113 @@ +# dataset settings +dataset_type = 'CityscapesDataset' +data_root = 'data/cityscapes/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/segmentation/cityscapes/' + +# Method 2: Use backend_args, file_client_args in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/segmentation/', +# 'data/': 's3://openmmlab/datasets/segmentation/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=[(2048, 800), (2048, 1024)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='RepeatDataset', + times=8, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instancesonly_filtered_gtFine_train.json', + data_prefix=dict(img='leftImg8bit/train/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instancesonly_filtered_gtFine_val.json', + data_prefix=dict(img='leftImg8bit/val/'), + test_mode=True, + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + +val_evaluator = [ + dict( + type='CocoMetric', + ann_file=data_root + + 'annotations/instancesonly_filtered_gtFine_val.json', + metric=['bbox', 'segm'], + backend_args=backend_args), + dict( + type='CityScapesMetric', + seg_prefix=data_root + 'gtFine/val', + outfile_prefix='./work_dirs/cityscapes_metric/instance', + backend_args=backend_args) +] + +test_evaluator = val_evaluator + +# inference on test dataset and +# format the output results for submission. +# test_dataloader = dict( +# batch_size=1, +# num_workers=2, +# persistent_workers=True, +# drop_last=False, +# sampler=dict(type='DefaultSampler', shuffle=False), +# dataset=dict( +# type=dataset_type, +# data_root=data_root, +# ann_file='annotations/instancesonly_filtered_gtFine_test.json', +# data_prefix=dict(img='leftImg8bit/test/'), +# test_mode=True, +# filter_cfg=dict(filter_empty_gt=True, min_size=32), +# pipeline=test_pipeline)) +# test_evaluator = dict( +# type='CityScapesMetric', +# format_only=True, +# outfile_prefix='./work_dirs/cityscapes_metric/test') diff --git a/mmdetection/configs/_base_/datasets/coco_caption.py b/mmdetection/configs/_base_/datasets/coco_caption.py new file mode 100644 index 00000000..a1bd8983 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/coco_caption.py @@ -0,0 +1,60 @@ +# data settings + +dataset_type = 'CocoCaptionDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +test_pipeline = [ + dict( + type='LoadImageFromFile', + imdecode_backend='pillow', + backend_args=backend_args), + dict( + type='Resize', + scale=(224, 224), + interpolation='bicubic', + backend='pillow'), + dict(type='PackInputs', meta_keys=['image_id']), +] + +# ann_file download from +# train dataset: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_train.json # noqa +# val dataset: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_val.json # noqa +# test dataset: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_test.json # noqa +# val evaluator: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_val_gt.json # noqa +# test evaluator: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_test_gt.json # noqa +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/coco_karpathy_val.json', + pipeline=test_pipeline, + )) + +val_evaluator = dict( + type='COCOCaptionMetric', + ann_file=data_root + 'annotations/coco_karpathy_val_gt.json', +) + +# # If you want standard test, please manually configure the test dataset +test_dataloader = val_dataloader +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/coco_detection.py b/mmdetection/configs/_base_/datasets/coco_detection.py new file mode 100644 index 00000000..fdf8dfad --- /dev/null +++ b/mmdetection/configs/_base_/datasets/coco_detection.py @@ -0,0 +1,95 @@ +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +# inference on test dataset and +# format the output results for submission. +# test_dataloader = dict( +# batch_size=1, +# num_workers=2, +# persistent_workers=True, +# drop_last=False, +# sampler=dict(type='DefaultSampler', shuffle=False), +# dataset=dict( +# type=dataset_type, +# data_root=data_root, +# ann_file=data_root + 'annotations/image_info_test-dev2017.json', +# data_prefix=dict(img='test2017/'), +# test_mode=True, +# pipeline=test_pipeline)) +# test_evaluator = dict( +# type='CocoMetric', +# metric='bbox', +# format_only=True, +# ann_file=data_root + 'annotations/image_info_test-dev2017.json', +# outfile_prefix='./work_dirs/coco_detection/test') diff --git a/mmdetection/configs/_base_/datasets/coco_instance.py b/mmdetection/configs/_base_/datasets/coco_instance.py new file mode 100644 index 00000000..e91cb354 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/coco_instance.py @@ -0,0 +1,95 @@ +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +# inference on test dataset and +# format the output results for submission. +# test_dataloader = dict( +# batch_size=1, +# num_workers=2, +# persistent_workers=True, +# drop_last=False, +# sampler=dict(type='DefaultSampler', shuffle=False), +# dataset=dict( +# type=dataset_type, +# data_root=data_root, +# ann_file=data_root + 'annotations/image_info_test-dev2017.json', +# data_prefix=dict(img='test2017/'), +# test_mode=True, +# pipeline=test_pipeline)) +# test_evaluator = dict( +# type='CocoMetric', +# metric=['bbox', 'segm'], +# format_only=True, +# ann_file=data_root + 'annotations/image_info_test-dev2017.json', +# outfile_prefix='./work_dirs/coco_instance/test') diff --git a/mmdetection/configs/_base_/datasets/coco_instance_semantic.py b/mmdetection/configs/_base_/datasets/coco_instance_semantic.py new file mode 100644 index 00000000..cc961863 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/coco_instance_semantic.py @@ -0,0 +1,78 @@ +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict( + type='LoadAnnotations', with_bbox=True, with_mask=True, with_seg=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict( + type='LoadAnnotations', with_bbox=True, with_mask=True, with_seg=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/', seg='stuffthingmaps/train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/coco_panoptic.py b/mmdetection/configs/_base_/datasets/coco_panoptic.py new file mode 100644 index 00000000..0b95b619 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/coco_panoptic.py @@ -0,0 +1,94 @@ +# dataset settings +dataset_type = 'CocoPanopticDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadPanopticAnnotations', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadPanopticAnnotations', backend_args=backend_args), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/panoptic_train2017.json', + data_prefix=dict( + img='train2017/', seg='annotations/panoptic_train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/panoptic_val2017.json', + data_prefix=dict(img='val2017/', seg='annotations/panoptic_val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoPanopticMetric', + ann_file=data_root + 'annotations/panoptic_val2017.json', + seg_prefix=data_root + 'annotations/panoptic_val2017/', + backend_args=backend_args) +test_evaluator = val_evaluator + +# inference on test dataset and +# format the output results for submission. +# test_dataloader = dict( +# batch_size=1, +# num_workers=1, +# persistent_workers=True, +# drop_last=False, +# sampler=dict(type='DefaultSampler', shuffle=False), +# dataset=dict( +# type=dataset_type, +# data_root=data_root, +# ann_file='annotations/panoptic_image_info_test-dev2017.json', +# data_prefix=dict(img='test2017/'), +# test_mode=True, +# pipeline=test_pipeline)) +# test_evaluator = dict( +# type='CocoPanopticMetric', +# format_only=True, +# ann_file=data_root + 'annotations/panoptic_image_info_test-dev2017.json', +# outfile_prefix='./work_dirs/coco_panoptic/test') diff --git a/mmdetection/configs/_base_/datasets/coco_semantic.py b/mmdetection/configs/_base_/datasets/coco_semantic.py new file mode 100644 index 00000000..944bbbae --- /dev/null +++ b/mmdetection/configs/_base_/datasets/coco_semantic.py @@ -0,0 +1,78 @@ +# dataset settings +dataset_type = 'CocoSegDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict( + type='LoadAnnotations', + with_bbox=False, + with_label=False, + with_seg=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='LoadAnnotations', + with_bbox=False, + with_label=False, + with_seg=True), + dict( + type='PackDetInputs', + meta_keys=('img_path', 'ori_shape', 'img_shape', 'scale_factor')) +] + +# For stuffthingmaps_semseg, please refer to +# `docs/en/user_guides/dataset_prepare.md` +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='train2017/', + seg_map_path='stuffthingmaps_semseg/train2017/'), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='val2017/', + seg_map_path='stuffthingmaps_semseg/val2017/'), + pipeline=test_pipeline)) + +test_dataloader = val_dataloader + +val_evaluator = dict(type='SemSegMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/deepfashion.py b/mmdetection/configs/_base_/datasets/deepfashion.py new file mode 100644 index 00000000..a93dc715 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/deepfashion.py @@ -0,0 +1,95 @@ +# dataset settings +dataset_type = 'DeepFashionDataset' +data_root = 'data/DeepFashion/In-shop/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='Resize', scale=(750, 1101), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(750, 1101), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='RepeatDataset', + times=2, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='Anno/segmentation/DeepFashion_segmentation_train.json', + data_prefix=dict(img='Img/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='Anno/segmentation/DeepFashion_segmentation_query.json', + data_prefix=dict(img='Img/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='Anno/segmentation/DeepFashion_segmentation_gallery.json', + data_prefix=dict(img='Img/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + + 'Anno/segmentation/DeepFashion_segmentation_query.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = dict( + type='CocoMetric', + ann_file=data_root + + 'Anno/segmentation/DeepFashion_segmentation_gallery.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) diff --git a/mmdetection/configs/_base_/datasets/dsdl.py b/mmdetection/configs/_base_/datasets/dsdl.py new file mode 100644 index 00000000..1f19e5e4 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/dsdl.py @@ -0,0 +1,62 @@ +dataset_type = 'DSDLDetDataset' +data_root = 'path to dataset folder' +train_ann = 'path to train yaml file' +val_ann = 'path to val yaml file' + +backend_args = None +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': "s3://open_data/", +# 'data/': "s3://open_data/" +# })) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'instances')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file=train_ann, + filter_cfg=dict(filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file=val_ann, + test_mode=True, + pipeline=test_pipeline)) + +test_dataloader = val_dataloader + +val_evaluator = dict(type='CocoMetric', metric='bbox') +# val_evaluator = dict(type='VOCMetric', metric='mAP', eval_mode='11points') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/isaid_instance.py b/mmdetection/configs/_base_/datasets/isaid_instance.py new file mode 100644 index 00000000..09ddcab0 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/isaid_instance.py @@ -0,0 +1,59 @@ +# dataset settings +dataset_type = 'iSAIDDataset' +data_root = 'data/iSAID/' +backend_args = None + +# Please see `projects/iSAID/README.md` for data preparation +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='Resize', scale=(800, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(800, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train/instancesonly_filtered_train.json', + data_prefix=dict(img='train/images/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val/instancesonly_filtered_val.json', + data_prefix=dict(img='val/images/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'val/instancesonly_filtered_val.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/lvis_v0.5_instance.py b/mmdetection/configs/_base_/datasets/lvis_v0.5_instance.py new file mode 100644 index 00000000..d0ca44ef --- /dev/null +++ b/mmdetection/configs/_base_/datasets/lvis_v0.5_instance.py @@ -0,0 +1,79 @@ +# dataset settings +dataset_type = 'LVISV05Dataset' +data_root = 'data/lvis_v0.5/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/lvis_v0.5/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/lvis_v0.5_train.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/lvis_v0.5_val.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='LVISMetric', + ann_file=data_root + 'annotations/lvis_v0.5_val.json', + metric=['bbox', 'segm'], + backend_args=backend_args) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/lvis_v1_instance.py b/mmdetection/configs/_base_/datasets/lvis_v1_instance.py new file mode 100644 index 00000000..0413f370 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/lvis_v1_instance.py @@ -0,0 +1,22 @@ +# dataset settings +_base_ = 'lvis_v0.5_instance.py' +dataset_type = 'LVISV1Dataset' +data_root = 'data/lvis_v1/' + +train_dataloader = dict( + dataset=dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/lvis_v1_train.json', + data_prefix=dict(img='')))) +val_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/lvis_v1_val.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +val_evaluator = dict(ann_file=data_root + 'annotations/lvis_v1_val.json') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/mot_challenge.py b/mmdetection/configs/_base_/datasets/mot_challenge.py new file mode 100644 index 00000000..ce2828ef --- /dev/null +++ b/mmdetection/configs/_base_/datasets/mot_challenge.py @@ -0,0 +1,90 @@ +# dataset settings +dataset_type = 'MOTChallengeDataset' +data_root = 'data/MOT17/' +img_scale = (1088, 1088) + +backend_args = None +# data pipeline +train_pipeline = [ + dict( + type='UniformRefFrameSample', + num_ref_imgs=1, + frame_range=10, + filter_key_img=True), + dict( + type='TransformBroadcaster', + share_random_params=True, + transforms=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadTrackAnnotations'), + dict( + type='RandomResize', + scale=img_scale, + ratio_range=(0.8, 1.2), + keep_ratio=True, + clip_object_border=False), + dict(type='PhotoMetricDistortion') + ]), + dict( + type='TransformBroadcaster', + # different cropped positions for different frames + share_random_params=False, + transforms=[ + dict( + type='RandomCrop', crop_size=img_scale, bbox_clip_border=False) + ]), + dict( + type='TransformBroadcaster', + share_random_params=True, + transforms=[ + dict(type='RandomFlip', prob=0.5), + ]), + dict(type='PackTrackInputs') +] + +test_pipeline = [ + dict( + type='TransformBroadcaster', + transforms=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict(type='LoadTrackAnnotations') + ]), + dict(type='PackTrackInputs') +] + +# dataloader +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='TrackImgSampler'), # image-based sampling + dataset=dict( + type=dataset_type, + data_root=data_root, + visibility_thr=-1, + ann_file='annotations/half-train_cocoformat.json', + data_prefix=dict(img_path='train'), + metainfo=dict(classes=('pedestrian', )), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + # Now we support two ways to test, image_based and video_based + # if you want to use video_based sampling, you can use as follows + # sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), + sampler=dict(type='TrackImgSampler'), # image-based sampling + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/half-val_cocoformat.json', + data_prefix=dict(img_path='train'), + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# evaluator +val_evaluator = dict( + type='MOTChallengeMetric', metric=['HOTA', 'CLEAR', 'Identity']) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/mot_challenge_det.py b/mmdetection/configs/_base_/datasets/mot_challenge_det.py new file mode 100644 index 00000000..a988572c --- /dev/null +++ b/mmdetection/configs/_base_/datasets/mot_challenge_det.py @@ -0,0 +1,66 @@ +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/MOT17/' + +backend_args = None +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args, to_float32=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(1088, 1088), + ratio_range=(0.8, 1.2), + keep_ratio=True, + clip_object_border=False), + dict(type='PhotoMetricDistortion'), + dict(type='RandomCrop', crop_size=(1088, 1088), bbox_clip_border=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1088, 1088), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/half-train_cocoformat.json', + data_prefix=dict(img='train/'), + metainfo=dict(classes=('pedestrian', )), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/half-val_cocoformat.json', + data_prefix=dict(img='train/'), + metainfo=dict(classes=('pedestrian', )), + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/half-val_cocoformat.json', + metric='bbox', + format_only=False) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/mot_challenge_reid.py b/mmdetection/configs/_base_/datasets/mot_challenge_reid.py new file mode 100644 index 00000000..57a95b53 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/mot_challenge_reid.py @@ -0,0 +1,61 @@ +# dataset settings +dataset_type = 'ReIDDataset' +data_root = 'data/MOT17/' + +backend_args = None +# data pipeline +train_pipeline = [ + dict( + type='TransformBroadcaster', + share_random_params=False, + transforms=[ + dict( + type='LoadImageFromFile', + backend_args=backend_args, + to_float32=True), + dict( + type='Resize', + scale=(128, 256), + keep_ratio=False, + clip_object_border=False), + dict(type='RandomFlip', prob=0.5, direction='horizontal'), + ]), + dict(type='PackReIDInputs', meta_keys=('flip', 'flip_direction')) +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args, to_float32=True), + dict(type='Resize', scale=(128, 256), keep_ratio=False), + dict(type='PackReIDInputs') +] + +# dataloader +train_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + triplet_sampler=dict(num_ids=8, ins_per_id=4), + data_prefix=dict(img_path='reid/imgs'), + ann_file='reid/meta/train_80.txt', + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + triplet_sampler=None, + data_prefix=dict(img_path='reid/imgs'), + ann_file='reid/meta/val_20.txt', + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# evaluator +val_evaluator = dict(type='ReIDMetrics', metric=['mAP', 'CMC']) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/objects365v1_detection.py b/mmdetection/configs/_base_/datasets/objects365v1_detection.py new file mode 100644 index 00000000..ee398698 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/objects365v1_detection.py @@ -0,0 +1,74 @@ +# dataset settings +dataset_type = 'Objects365V1Dataset' +data_root = 'data/Objects365/Obj365_v1/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/objects365_train.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/objects365_val.json', + data_prefix=dict(img='val/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/objects365_val.json', + metric='bbox', + sort_categories=True, + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/objects365v2_detection.py b/mmdetection/configs/_base_/datasets/objects365v2_detection.py new file mode 100644 index 00000000..b25a7ba9 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/objects365v2_detection.py @@ -0,0 +1,73 @@ +# dataset settings +dataset_type = 'Objects365V2Dataset' +data_root = 'data/Objects365/Obj365_v2/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/zhiyuan_objv2_train.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/zhiyuan_objv2_val.json', + data_prefix=dict(img='val/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/zhiyuan_objv2_val.json', + metric='bbox', + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/openimages_detection.py b/mmdetection/configs/_base_/datasets/openimages_detection.py new file mode 100644 index 00000000..129661b4 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/openimages_detection.py @@ -0,0 +1,81 @@ +# dataset settings +dataset_type = 'OpenImagesDataset' +data_root = 'data/OpenImages/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1024, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1024, 800), keep_ratio=True), + # avoid bboxes being resized + dict(type='LoadAnnotations', with_bbox=True), + # TODO: find a better way to collect image_level_labels + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'instances', 'image_level_labels')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=0, # workers_per_gpu > 0 may occur out of memory + persistent_workers=False, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/oidv6-train-annotations-bbox.csv', + data_prefix=dict(img='OpenImages/train/'), + label_file='annotations/class-descriptions-boxable.csv', + hierarchy_file='annotations/bbox_labels_600_hierarchy.json', + meta_file='annotations/train-image-metas.pkl', + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=0, + persistent_workers=False, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/validation-annotations-bbox.csv', + data_prefix=dict(img='OpenImages/validation/'), + label_file='annotations/class-descriptions-boxable.csv', + hierarchy_file='annotations/bbox_labels_600_hierarchy.json', + meta_file='annotations/validation-image-metas.pkl', + image_level_ann_file='annotations/validation-' + 'annotations-human-imagelabels-boxable.csv', + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='OpenImagesMetric', + iou_thrs=0.5, + ioa_thrs=0.5, + use_group_of=True, + get_supercategory=True) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/refcoco+.py b/mmdetection/configs/_base_/datasets/refcoco+.py new file mode 100644 index 00000000..ae0278dd --- /dev/null +++ b/mmdetection/configs/_base_/datasets/refcoco+.py @@ -0,0 +1,55 @@ +# dataset settings +dataset_type = 'RefCocoDataset' +data_root = 'data/coco/' + +backend_args = None + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='LoadAnnotations', + with_mask=True, + with_bbox=False, + with_seg=False, + with_label=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'gt_masks', 'text')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='train2014/'), + ann_file='refcoco+/instances.json', + split_file='refcoco+/refs(unc).p', + split='val', + text_mode='select_first', + pipeline=test_pipeline)) + +test_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='train2014/'), + ann_file='refcoco+/instances.json', + split_file='refcoco+/refs(unc).p', + split='testA', # or 'testB' + text_mode='select_first', + pipeline=test_pipeline)) + +val_evaluator = dict(type='RefSegMetric', metric=['cIoU', 'mIoU']) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/refcoco.py b/mmdetection/configs/_base_/datasets/refcoco.py new file mode 100644 index 00000000..7b6caefa --- /dev/null +++ b/mmdetection/configs/_base_/datasets/refcoco.py @@ -0,0 +1,55 @@ +# dataset settings +dataset_type = 'RefCocoDataset' +data_root = 'data/coco/' + +backend_args = None + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='LoadAnnotations', + with_mask=True, + with_bbox=False, + with_seg=False, + with_label=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'gt_masks', 'text')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='train2014/'), + ann_file='refcoco/instances.json', + split_file='refcoco/refs(unc).p', + split='val', + text_mode='select_first', + pipeline=test_pipeline)) + +test_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='train2014/'), + ann_file='refcoco/instances.json', + split_file='refcoco/refs(unc).p', + split='testA', # or 'testB' + text_mode='select_first', + pipeline=test_pipeline)) + +val_evaluator = dict(type='RefSegMetric', metric=['cIoU', 'mIoU']) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/refcocog.py b/mmdetection/configs/_base_/datasets/refcocog.py new file mode 100644 index 00000000..19dbeef1 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/refcocog.py @@ -0,0 +1,55 @@ +# dataset settings +dataset_type = 'RefCocoDataset' +data_root = 'data/coco/' + +backend_args = None + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='LoadAnnotations', + with_mask=True, + with_bbox=False, + with_seg=False, + with_label=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'gt_masks', 'text')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='train2014/'), + ann_file='refcocog/instances.json', + split_file='refcocog/refs(umd).p', + split='val', + text_mode='select_first', + pipeline=test_pipeline)) + +test_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='train2014/'), + ann_file='refcocog/instances.json', + split_file='refcocog/refs(umd).p', + split='test', + text_mode='select_first', + pipeline=test_pipeline)) + +val_evaluator = dict(type='RefSegMetric', metric=['cIoU', 'mIoU']) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/semi_coco_detection.py b/mmdetection/configs/_base_/datasets/semi_coco_detection.py new file mode 100644 index 00000000..694f25f8 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/semi_coco_detection.py @@ -0,0 +1,178 @@ +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +color_space = [ + [dict(type='ColorTransform')], + [dict(type='AutoContrast')], + [dict(type='Equalize')], + [dict(type='Sharpness')], + [dict(type='Posterize')], + [dict(type='Solarize')], + [dict(type='Color')], + [dict(type='Contrast')], + [dict(type='Brightness')], +] + +geometric = [ + [dict(type='Rotate')], + [dict(type='ShearX')], + [dict(type='ShearY')], + [dict(type='TranslateX')], + [dict(type='TranslateY')], +] + +scale = [(1333, 400), (1333, 1200)] + +branch_field = ['sup', 'unsup_teacher', 'unsup_student'] +# pipeline used to augment labeled data, +# which will be sent to student model for supervised training. +sup_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomResize', scale=scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='MultiBranch', + branch_field=branch_field, + sup=dict(type='PackDetInputs')) +] + +# pipeline used to augment unlabeled data weakly, +# which will be sent to teacher model for predicting pseudo instances. +weak_pipeline = [ + dict(type='RandomResize', scale=scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', + 'homography_matrix')), +] + +# pipeline used to augment unlabeled data strongly, +# which will be sent to student model for unsupervised training. +strong_pipeline = [ + dict(type='RandomResize', scale=scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomOrder', + transforms=[ + dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict(type='RandAugment', aug_space=geometric, aug_num=1), + ]), + dict(type='RandomErasing', n_patches=(1, 5), ratio=(0, 0.2)), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', + 'homography_matrix')), +] + +# pipeline used to augment unlabeled data into different views +unsup_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadEmptyAnnotations'), + dict( + type='MultiBranch', + branch_field=branch_field, + unsup_teacher=weak_pipeline, + unsup_student=strong_pipeline, + ) +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +batch_size = 5 +num_workers = 5 +# There are two common semi-supervised learning settings on the coco dataset: +# (1) Divide the train2017 into labeled and unlabeled datasets +# by a fixed percentage, such as 1%, 2%, 5% and 10%. +# The format of labeled_ann_file and unlabeled_ann_file are +# instances_train2017.{fold}@{percent}.json, and +# instances_train2017.{fold}@{percent}-unlabeled.json +# `fold` is used for cross-validation, and `percent` represents +# the proportion of labeled data in the train2017. +# (2) Choose the train2017 as the labeled dataset +# and unlabeled2017 as the unlabeled dataset. +# The labeled_ann_file and unlabeled_ann_file are +# instances_train2017.json and image_info_unlabeled2017.json +# We use this configuration by default. +labeled_dataset = dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=sup_pipeline, + backend_args=backend_args) + +unlabeled_dataset = dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_unlabeled2017.json', + data_prefix=dict(img='unlabeled2017/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=unsup_pipeline, + backend_args=backend_args) + +train_dataloader = dict( + batch_size=batch_size, + num_workers=num_workers, + persistent_workers=True, + sampler=dict( + type='GroupMultiSourceSampler', + batch_size=batch_size, + source_ratio=[1, 4]), + dataset=dict( + type='ConcatDataset', datasets=[labeled_dataset, unlabeled_dataset])) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/v3det.py b/mmdetection/configs/_base_/datasets/v3det.py new file mode 100644 index 00000000..38ccbf86 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/v3det.py @@ -0,0 +1,69 @@ +# dataset settings +dataset_type = 'V3DetDataset' +data_root = 'data/V3Det/' + +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/v3det_2023_v1_train.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=True, min_size=4), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/v3det_2023_v1_val.json', + data_prefix=dict(img=''), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/v3det_2023_v1_val.json', + metric='bbox', + format_only=False, + backend_args=backend_args, + use_mp_eval=True, + proposal_nums=[300]) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/voc0712.py b/mmdetection/configs/_base_/datasets/voc0712.py new file mode 100644 index 00000000..47f5e656 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/voc0712.py @@ -0,0 +1,92 @@ +# dataset settings +dataset_type = 'VOCDataset' +data_root = 'data/VOCdevkit/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically Infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/segmentation/VOCdevkit/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/segmentation/', +# 'data/': 's3://openmmlab/datasets/segmentation/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1000, 600), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1000, 600), keep_ratio=True), + # avoid bboxes being resized + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='RepeatDataset', + times=3, + dataset=dict( + type='ConcatDataset', + # VOCDataset will add different `dataset_type` in dataset.metainfo, + # which will get error if using ConcatDataset. Adding + # `ignore_keys` can avoid this error. + ignore_keys=['dataset_type'], + datasets=[ + dict( + type=dataset_type, + data_root=data_root, + ann_file='VOC2007/ImageSets/Main/trainval.txt', + data_prefix=dict(sub_data_root='VOC2007/'), + filter_cfg=dict( + filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline, + backend_args=backend_args), + dict( + type=dataset_type, + data_root=data_root, + ann_file='VOC2012/ImageSets/Main/trainval.txt', + data_prefix=dict(sub_data_root='VOC2012/'), + filter_cfg=dict( + filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline, + backend_args=backend_args) + ]))) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='VOC2007/ImageSets/Main/test.txt', + data_prefix=dict(sub_data_root='VOC2007/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +# Pascal VOC2007 uses `11points` as default evaluate mode, while PASCAL +# VOC2012 defaults to use 'area'. +val_evaluator = dict(type='VOCMetric', metric='mAP', eval_mode='11points') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/wider_face.py b/mmdetection/configs/_base_/datasets/wider_face.py new file mode 100644 index 00000000..7042bc46 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/wider_face.py @@ -0,0 +1,73 @@ +# dataset settings +dataset_type = 'WIDERFaceDataset' +data_root = 'data/WIDERFace/' +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/cityscapes/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +img_scale = (640, 640) # VGA resolution + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img='WIDER_train'), + filter_cfg=dict(filter_empty_gt=True, bbox_min_size=17, min_size=32), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img='WIDER_val'), + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict( + # TODO: support WiderFace-Evaluation for easy, medium, hard cases + type='VOCMetric', + metric='mAP', + eval_mode='11points') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/_base_/datasets/youtube_vis.py b/mmdetection/configs/_base_/datasets/youtube_vis.py new file mode 100644 index 00000000..ece07cc3 --- /dev/null +++ b/mmdetection/configs/_base_/datasets/youtube_vis.py @@ -0,0 +1,66 @@ +dataset_type = 'YouTubeVISDataset' +data_root = 'data/youtube_vis_2019/' +dataset_version = data_root[-5:-1] # 2019 or 2021 + +backend_args = None + +# dataset settings +train_pipeline = [ + dict( + type='UniformRefFrameSample', + num_ref_imgs=1, + frame_range=100, + filter_key_img=True), + dict( + type='TransformBroadcaster', + share_random_params=True, + transforms=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadTrackAnnotations', with_mask=True), + dict(type='Resize', scale=(640, 360), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + ]), + dict(type='PackTrackInputs') +] + +test_pipeline = [ + dict( + type='TransformBroadcaster', + transforms=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(640, 360), keep_ratio=True), + dict(type='LoadTrackAnnotations', with_mask=True), + ]), + dict(type='PackTrackInputs') +] + +# dataloader +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + # sampler=dict(type='TrackImgSampler'), # image-based sampling + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='TrackAspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2019_train.json', + data_prefix=dict(img_path='train/JPEGImages'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2019_valid.json', + data_prefix=dict(img_path='valid/JPEGImages'), + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/_base_/default_runtime.py b/mmdetection/configs/_base_/default_runtime.py new file mode 100644 index 00000000..870e5614 --- /dev/null +++ b/mmdetection/configs/_base_/default_runtime.py @@ -0,0 +1,24 @@ +default_scope = 'mmdet' + +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', interval=1), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='DetVisualizationHook')) + +env_cfg = dict( + cudnn_benchmark=False, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) + +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=True) + +log_level = 'INFO' +load_from = None +resume = False diff --git a/mmdetection/configs/_base_/models/cascade-mask-rcnn_r50_fpn.py b/mmdetection/configs/_base_/models/cascade-mask-rcnn_r50_fpn.py new file mode 100644 index 00000000..c5167f7a --- /dev/null +++ b/mmdetection/configs/_base_/models/cascade-mask-rcnn_r50_fpn.py @@ -0,0 +1,203 @@ +# model settings +model = dict( + type='CascadeRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + type='CascadeRoIHead', + num_stages=3, + stage_loss_weights=[1, 0.5, 0.25], + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], + mask_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + mask_head=dict( + type='FCNMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=80, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=[ + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100, + mask_thr_binary=0.5))) diff --git a/mmdetection/configs/_base_/models/cascade-rcnn_r50_fpn.py b/mmdetection/configs/_base_/models/cascade-rcnn_r50_fpn.py new file mode 100644 index 00000000..50c57f01 --- /dev/null +++ b/mmdetection/configs/_base_/models/cascade-rcnn_r50_fpn.py @@ -0,0 +1,185 @@ +# model settings +model = dict( + type='CascadeRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + type='CascadeRoIHead', + num_stages=3, + stage_loss_weights=[1, 0.5, 0.25], + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ]), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=[ + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100))) diff --git a/mmdetection/configs/_base_/models/fast-rcnn_r50_fpn.py b/mmdetection/configs/_base_/models/fast-rcnn_r50_fpn.py new file mode 100644 index 00000000..2bd45e92 --- /dev/null +++ b/mmdetection/configs/_base_/models/fast-rcnn_r50_fpn.py @@ -0,0 +1,68 @@ +# model settings +model = dict( + type='FastRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + roi_head=dict( + type='StandardRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False)), + test_cfg=dict( + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100))) diff --git a/mmdetection/configs/_base_/models/faster-rcnn_r50-caffe-c4.py b/mmdetection/configs/_base_/models/faster-rcnn_r50-caffe-c4.py new file mode 100644 index 00000000..15d2db72 --- /dev/null +++ b/mmdetection/configs/_base_/models/faster-rcnn_r50-caffe-c4.py @@ -0,0 +1,123 @@ +# model settings +norm_cfg = dict(type='BN', requires_grad=False) +model = dict( + type='FasterRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=3, + strides=(1, 2, 2), + dilations=(1, 1, 1), + out_indices=(2, ), + frozen_stages=1, + norm_cfg=norm_cfg, + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + rpn_head=dict( + type='RPNHead', + in_channels=1024, + feat_channels=1024, + anchor_generator=dict( + type='AnchorGenerator', + scales=[2, 4, 8, 16, 32], + ratios=[0.5, 1.0, 2.0], + strides=[16]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + roi_head=dict( + type='StandardRoIHead', + shared_head=dict( + type='ResLayer', + depth=50, + stage=3, + stride=2, + dilation=1, + style='caffe', + norm_cfg=norm_cfg, + norm_eval=True, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=1024, + featmap_strides=[16]), + bbox_head=dict( + type='BBoxHead', + with_avg_pool=True, + roi_feat_size=7, + in_channels=2048, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=12000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=6000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100))) diff --git a/mmdetection/configs/_base_/models/faster-rcnn_r50-caffe-dc5.py b/mmdetection/configs/_base_/models/faster-rcnn_r50-caffe-dc5.py new file mode 100644 index 00000000..189915e3 --- /dev/null +++ b/mmdetection/configs/_base_/models/faster-rcnn_r50-caffe-dc5.py @@ -0,0 +1,111 @@ +# model settings +norm_cfg = dict(type='BN', requires_grad=False) +model = dict( + type='FasterRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + strides=(1, 2, 2, 1), + dilations=(1, 1, 1, 2), + out_indices=(3, ), + frozen_stages=1, + norm_cfg=norm_cfg, + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + rpn_head=dict( + type='RPNHead', + in_channels=2048, + feat_channels=2048, + anchor_generator=dict( + type='AnchorGenerator', + scales=[2, 4, 8, 16, 32], + ratios=[0.5, 1.0, 2.0], + strides=[16]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + roi_head=dict( + type='StandardRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=2048, + featmap_strides=[16]), + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=2048, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=12000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms=dict(type='nms', iou_threshold=0.7), + nms_pre=6000, + max_per_img=1000, + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100))) diff --git a/mmdetection/configs/_base_/models/faster-rcnn_r50_fpn.py b/mmdetection/configs/_base_/models/faster-rcnn_r50_fpn.py new file mode 100644 index 00000000..31aa1461 --- /dev/null +++ b/mmdetection/configs/_base_/models/faster-rcnn_r50_fpn.py @@ -0,0 +1,114 @@ +# model settings +model = dict( + type='FasterRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + roi_head=dict( + type='StandardRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100) + # soft-nms is also supported for rcnn testing + # e.g., nms=dict(type='soft_nms', iou_threshold=0.5, min_score=0.05) + )) diff --git a/mmdetection/configs/_base_/models/mask-rcnn_r50-caffe-c4.py b/mmdetection/configs/_base_/models/mask-rcnn_r50-caffe-c4.py new file mode 100644 index 00000000..de1131b2 --- /dev/null +++ b/mmdetection/configs/_base_/models/mask-rcnn_r50-caffe-c4.py @@ -0,0 +1,132 @@ +# model settings +norm_cfg = dict(type='BN', requires_grad=False) +model = dict( + type='MaskRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=3, + strides=(1, 2, 2), + dilations=(1, 1, 1), + out_indices=(2, ), + frozen_stages=1, + norm_cfg=norm_cfg, + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + rpn_head=dict( + type='RPNHead', + in_channels=1024, + feat_channels=1024, + anchor_generator=dict( + type='AnchorGenerator', + scales=[2, 4, 8, 16, 32], + ratios=[0.5, 1.0, 2.0], + strides=[16]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + roi_head=dict( + type='StandardRoIHead', + shared_head=dict( + type='ResLayer', + depth=50, + stage=3, + stride=2, + dilation=1, + style='caffe', + norm_cfg=norm_cfg, + norm_eval=True), + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=1024, + featmap_strides=[16]), + bbox_head=dict( + type='BBoxHead', + with_avg_pool=True, + roi_feat_size=7, + in_channels=2048, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + mask_roi_extractor=None, + mask_head=dict( + type='FCNMaskHead', + num_convs=0, + in_channels=2048, + conv_out_channels=256, + num_classes=80, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=12000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=14, + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=6000, + nms=dict(type='nms', iou_threshold=0.7), + max_per_img=1000, + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100, + mask_thr_binary=0.5))) diff --git a/mmdetection/configs/_base_/models/mask-rcnn_r50_fpn.py b/mmdetection/configs/_base_/models/mask-rcnn_r50_fpn.py new file mode 100644 index 00000000..b4ff7a49 --- /dev/null +++ b/mmdetection/configs/_base_/models/mask-rcnn_r50_fpn.py @@ -0,0 +1,127 @@ +# model settings +model = dict( + type='MaskRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + roi_head=dict( + type='StandardRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + mask_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + mask_head=dict( + type='FCNMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=80, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100, + mask_thr_binary=0.5))) diff --git a/mmdetection/configs/_base_/models/retinanet_r50_fpn.py b/mmdetection/configs/_base_/models/retinanet_r50_fpn.py new file mode 100644 index 00000000..53662c9f --- /dev/null +++ b/mmdetection/configs/_base_/models/retinanet_r50_fpn.py @@ -0,0 +1,68 @@ +# model settings +model = dict( + type='RetinaNet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_input', + num_outs=5), + bbox_head=dict( + type='RetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + # model training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0, + ignore_iof_thr=-1), + sampler=dict( + type='PseudoSampler'), # Focal loss should use PseudoSampler + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100)) diff --git a/mmdetection/configs/_base_/models/rpn_r50-caffe-c4.py b/mmdetection/configs/_base_/models/rpn_r50-caffe-c4.py new file mode 100644 index 00000000..ed1dbe74 --- /dev/null +++ b/mmdetection/configs/_base_/models/rpn_r50-caffe-c4.py @@ -0,0 +1,64 @@ +# model settings +model = dict( + type='RPN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=3, + strides=(1, 2, 2), + dilations=(1, 1, 1), + out_indices=(2, ), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + neck=None, + rpn_head=dict( + type='RPNHead', + in_channels=1024, + feat_channels=1024, + anchor_generator=dict( + type='AnchorGenerator', + scales=[2, 4, 8, 16, 32], + ratios=[0.5, 1.0, 2.0], + strides=[16]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=12000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0))) diff --git a/mmdetection/configs/_base_/models/rpn_r50_fpn.py b/mmdetection/configs/_base_/models/rpn_r50_fpn.py new file mode 100644 index 00000000..6bc47904 --- /dev/null +++ b/mmdetection/configs/_base_/models/rpn_r50_fpn.py @@ -0,0 +1,64 @@ +# model settings +model = dict( + type='RPN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=2000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0))) diff --git a/mmdetection/configs/_base_/models/ssd300.py b/mmdetection/configs/_base_/models/ssd300.py new file mode 100644 index 00000000..fd113c7c --- /dev/null +++ b/mmdetection/configs/_base_/models/ssd300.py @@ -0,0 +1,63 @@ +# model settings +input_size = 300 +model = dict( + type='SingleStageDetector', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[1, 1, 1], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='SSDVGG', + depth=16, + with_last_pool=False, + ceil_mode=True, + out_indices=(3, 4), + out_feature_indices=(22, 34), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://vgg16_caffe')), + neck=dict( + type='SSDNeck', + in_channels=(512, 1024), + out_channels=(512, 1024, 512, 256, 256, 256), + level_strides=(2, 2, 1, 1), + level_paddings=(1, 1, 0, 0), + l2_norm_scale=20), + bbox_head=dict( + type='SSDHead', + in_channels=(512, 1024, 512, 256, 256, 256), + num_classes=80, + anchor_generator=dict( + type='SSDAnchorGenerator', + scale_major=False, + input_size=input_size, + basesize_ratio_range=(0.15, 0.9), + strides=[8, 16, 32, 64, 100, 300], + ratios=[[2], [2, 3], [2, 3], [2, 3], [2], [2]]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2])), + # model training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0., + ignore_iof_thr=-1, + gt_max_assign_all=False), + sampler=dict(type='PseudoSampler'), + smoothl1_beta=1., + allowed_border=-1, + pos_weight=-1, + neg_pos_ratio=3, + debug=False), + test_cfg=dict( + nms_pre=1000, + nms=dict(type='nms', iou_threshold=0.45), + min_bbox_size=0, + score_thr=0.02, + max_per_img=200)) +cudnn_benchmark = True diff --git a/mmdetection/configs/_base_/schedules/schedule_1x.py b/mmdetection/configs/_base_/schedules/schedule_1x.py new file mode 100644 index 00000000..95f30be7 --- /dev/null +++ b/mmdetection/configs/_base_/schedules/schedule_1x.py @@ -0,0 +1,28 @@ +# training schedule for 1x +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/_base_/schedules/schedule_20e.py b/mmdetection/configs/_base_/schedules/schedule_20e.py new file mode 100644 index 00000000..75f958b0 --- /dev/null +++ b/mmdetection/configs/_base_/schedules/schedule_20e.py @@ -0,0 +1,28 @@ +# training schedule for 20e +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=20, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=20, + by_epoch=True, + milestones=[16, 19], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/_base_/schedules/schedule_2x.py b/mmdetection/configs/_base_/schedules/schedule_2x.py new file mode 100644 index 00000000..5b7b241d --- /dev/null +++ b/mmdetection/configs/_base_/schedules/schedule_2x.py @@ -0,0 +1,28 @@ +# training schedule for 2x +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=24, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=24, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/albu_example/README.md b/mmdetection/configs/albu_example/README.md new file mode 100644 index 00000000..fa362f95 --- /dev/null +++ b/mmdetection/configs/albu_example/README.md @@ -0,0 +1,31 @@ +# Albu Example + +> [Albumentations: fast and flexible image augmentations](https://arxiv.org/abs/1809.06839) + + + +## Abstract + +Data augmentation is a commonly used technique for increasing both the size and the diversity of labeled training sets by leveraging input transformations that preserve output labels. In computer vision domain, image augmentations have become a common implicit regularization technique to combat overfitting in deep convolutional neural networks and are ubiquitously used to improve performance. While most deep learning frameworks implement basic image transformations, the list is typically limited to some variations and combinations of flipping, rotating, scaling, and cropping. Moreover, the image processing speed varies in existing tools for image augmentation. We present Albumentations, a fast and flexible library for image augmentations with many various image transform operations available, that is also an easy-to-use wrapper around other augmentation libraries. We provide examples of image augmentations for different computer vision tasks and show that Albumentations is faster than other commonly used image augmentation tools on the most of commonly used image transformations. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :-------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | 1x | 4.4 | 16.6 | 38.0 | 34.5 | [config](./mask-rcnn_r50_fpn_albu-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/albu_example/mask_rcnn_r50_fpn_albu_1x_coco/mask_rcnn_r50_fpn_albu_1x_coco_20200208-ab203bcd.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/albu_example/mask_rcnn_r50_fpn_albu_1x_coco/mask_rcnn_r50_fpn_albu_1x_coco_20200208_225520.log.json) | + +## Citation + +```latex +@article{2018arXiv180906839B, + author = {A. Buslaev, A. Parinov, E. Khvedchenya, V.~I. Iglovikov and A.~A. Kalinin}, + title = "{Albumentations: fast and flexible image augmentations}", + journal = {ArXiv e-prints}, + eprint = {1809.06839}, + year = 2018 +} +``` diff --git a/mmdetection/configs/albu_example/mask-rcnn_r50_fpn_albu-1x_coco.py b/mmdetection/configs/albu_example/mask-rcnn_r50_fpn_albu-1x_coco.py new file mode 100644 index 00000000..b8a2780e --- /dev/null +++ b/mmdetection/configs/albu_example/mask-rcnn_r50_fpn_albu-1x_coco.py @@ -0,0 +1,66 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' + +albu_train_transforms = [ + dict( + type='ShiftScaleRotate', + shift_limit=0.0625, + scale_limit=0.0, + rotate_limit=0, + interpolation=1, + p=0.5), + dict( + type='RandomBrightnessContrast', + brightness_limit=[0.1, 0.3], + contrast_limit=[0.1, 0.3], + p=0.2), + dict( + type='OneOf', + transforms=[ + dict( + type='RGBShift', + r_shift_limit=10, + g_shift_limit=10, + b_shift_limit=10, + p=1.0), + dict( + type='HueSaturationValue', + hue_shift_limit=20, + sat_shift_limit=30, + val_shift_limit=20, + p=1.0) + ], + p=0.1), + dict(type='JpegCompression', quality_lower=85, quality_upper=95, p=0.2), + dict(type='ChannelShuffle', p=0.1), + dict( + type='OneOf', + transforms=[ + dict(type='Blur', blur_limit=3, p=1.0), + dict(type='MedianBlur', blur_limit=3, p=1.0) + ], + p=0.1), +] +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='Albu', + transforms=albu_train_transforms, + bbox_params=dict( + type='BboxParams', + format='pascal_voc', + label_fields=['gt_bboxes_labels', 'gt_ignore_flags'], + min_visibility=0.0, + filter_lost_elements=True), + keymap={ + 'img': 'image', + 'gt_masks': 'masks', + 'gt_bboxes': 'bboxes' + }, + skip_img_without_anno=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/albu_example/metafile.yml b/mmdetection/configs/albu_example/metafile.yml new file mode 100644 index 00000000..3b54bdf1 --- /dev/null +++ b/mmdetection/configs/albu_example/metafile.yml @@ -0,0 +1,17 @@ +Models: + - Name: mask-rcnn_r50_fpn_albu-1x_coco + In Collection: Mask R-CNN + Config: mask-rcnn_r50_fpn_albu-1x_coco.py + Metadata: + Training Memory (GB): 4.4 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 34.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/albu_example/mask_rcnn_r50_fpn_albu_1x_coco/mask_rcnn_r50_fpn_albu_1x_coco_20200208-ab203bcd.pth diff --git a/mmdetection/configs/atss/README.md b/mmdetection/configs/atss/README.md new file mode 100644 index 00000000..1411672e --- /dev/null +++ b/mmdetection/configs/atss/README.md @@ -0,0 +1,31 @@ +# ATSS + +> [Bridging the Gap Between Anchor-based and Anchor-free Detection via Adaptive Training Sample Selection](https://arxiv.org/abs/1912.02424) + + + +## Abstract + +Object detection has been dominated by anchor-based detectors for several years. Recently, anchor-free detectors have become popular due to the proposal of FPN and Focal Loss. In this paper, we first point out that the essential difference between anchor-based and anchor-free detection is actually how to define positive and negative training samples, which leads to the performance gap between them. If they adopt the same definition of positive and negative samples during training, there is no obvious difference in the final performance, no matter regressing from a box or a point. This shows that how to select positive and negative training samples is important for current object detectors. Then, we propose an Adaptive Training Sample Selection (ATSS) to automatically select positive and negative samples according to statistical characteristics of object. It significantly improves the performance of anchor-based and anchor-free detectors and bridges the gap between them. Finally, we discuss the necessity of tiling multiple anchors per location on the image to detect objects. Extensive experiments conducted on MS COCO support our aforementioned analysis and conclusions. With the newly introduced ATSS, we improve state-of-the-art detectors by a large margin to 50.7% AP without introducing any overhead. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :-----: | :-----: | :------: | :------------: | :----: | :----------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | 1x | 3.7 | 19.7 | 39.4 | [config](./atss_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/atss/atss_r50_fpn_1x_coco/atss_r50_fpn_1x_coco_20200209-985f7bd0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/atss/atss_r50_fpn_1x_coco/atss_r50_fpn_1x_coco_20200209_102539.log.json) | +| R-101 | pytorch | 1x | 5.6 | 12.3 | 41.5 | [config](./atss_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/atss/atss_r101_fpn_1x_coco/atss_r101_fpn_1x_20200825-dfcadd6f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/atss/atss_r101_fpn_1x_coco/atss_r101_fpn_1x_20200825-dfcadd6f.log.json) | + +## Citation + +```latex +@article{zhang2019bridging, + title = {Bridging the Gap Between Anchor-based and Anchor-free Detection via Adaptive Training Sample Selection}, + author = {Zhang, Shifeng and Chi, Cheng and Yao, Yongqiang and Lei, Zhen and Li, Stan Z.}, + journal = {arXiv preprint arXiv:1912.02424}, + year = {2019} +} +``` diff --git a/mmdetection/configs/atss/atss_r101_fpn_1x_coco.py b/mmdetection/configs/atss/atss_r101_fpn_1x_coco.py new file mode 100644 index 00000000..5225d2ab --- /dev/null +++ b/mmdetection/configs/atss/atss_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './atss_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/atss/atss_r101_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/atss/atss_r101_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..69999ce4 --- /dev/null +++ b/mmdetection/configs/atss/atss_r101_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './atss_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/atss/atss_r18_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/atss/atss_r18_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..12d9f132 --- /dev/null +++ b/mmdetection/configs/atss/atss_r18_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './atss_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/atss/atss_r50_fpn_1x_coco.py b/mmdetection/configs/atss/atss_r50_fpn_1x_coco.py new file mode 100644 index 00000000..306435d7 --- /dev/null +++ b/mmdetection/configs/atss/atss_r50_fpn_1x_coco.py @@ -0,0 +1,71 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# model settings +model = dict( + type='ATSS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='ATSSHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + # training and testing settings + train_cfg=dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/atss/atss_r50_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/atss/atss_r50_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..e3b3c46f --- /dev/null +++ b/mmdetection/configs/atss/atss_r50_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,81 @@ +_base_ = '../common/lsj-200e_coco-detection.py' + +image_size = (1024, 1024) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +model = dict( + type='ATSS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='ATSSHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + # training and testing settings + train_cfg=dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +train_dataloader = dict(batch_size=8, num_workers=4) + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + optimizer=dict( + type='SGD', lr=0.01 * 4, momentum=0.9, weight_decay=0.00004)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/atss/metafile.yml b/mmdetection/configs/atss/metafile.yml new file mode 100644 index 00000000..f4c567ef --- /dev/null +++ b/mmdetection/configs/atss/metafile.yml @@ -0,0 +1,60 @@ +Collections: + - Name: ATSS + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ATSS + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1912.02424 + Title: 'Bridging the Gap Between Anchor-based and Anchor-free Detection via Adaptive Training Sample Selection' + README: configs/atss/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/atss.py#L6 + Version: v2.0.0 + +Models: + - Name: atss_r50_fpn_1x_coco + In Collection: ATSS + Config: configs/atss/atss_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.7 + inference time (ms/im): + - value: 50.76 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/atss/atss_r50_fpn_1x_coco/atss_r50_fpn_1x_coco_20200209-985f7bd0.pth + + - Name: atss_r101_fpn_1x_coco + In Collection: ATSS + Config: configs/atss/atss_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.6 + inference time (ms/im): + - value: 81.3 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/atss/atss_r101_fpn_1x_coco/atss_r101_fpn_1x_20200825-dfcadd6f.pth diff --git a/mmdetection/configs/autoassign/README.md b/mmdetection/configs/autoassign/README.md new file mode 100644 index 00000000..f6b05738 --- /dev/null +++ b/mmdetection/configs/autoassign/README.md @@ -0,0 +1,35 @@ +# AutoAssign + +> [AutoAssign: Differentiable Label Assignment for Dense Object Detection](https://arxiv.org/abs/2007.03496) + + + +## Abstract + +Determining positive/negative samples for object detection is known as label assignment. Here we present an anchor-free detector named AutoAssign. It requires little human knowledge and achieves appearance-aware through a fully differentiable weighting mechanism. During training, to both satisfy the prior distribution of data and adapt to category characteristics, we present Center Weighting to adjust the category-specific prior distributions. To adapt to object appearances, Confidence Weighting is proposed to adjust the specific assign strategy of each instance. The two weighting modules are then combined to generate positive and negative weights to adjust each location's confidence. Extensive experiments on the MS COCO show that our method steadily surpasses other best sampling strategies by large margins with various backbones. Moreover, our best model achieves 52.1% AP, outperforming all existing one-stage detectors. Besides, experiments on other datasets, e.g., PASCAL VOC, Objects365, and WiderFace, demonstrate the broad applicability of AutoAssign. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | box AP | Config | Download | +| :------: | :---: | :-----: | :------: | :----: | :---------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | caffe | 1x | 4.08 | 40.4 | [config](./autoassign_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/autoassign/auto_assign_r50_fpn_1x_coco/auto_assign_r50_fpn_1x_coco_20210413_115540-5e17991f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/autoassign/auto_assign_r50_fpn_1x_coco/auto_assign_r50_fpn_1x_coco_20210413_115540-5e17991f.log.json) | + +**Note**: + +1. We find that the performance is unstable with 1x setting and may fluctuate by about 0.3 mAP. mAP 40.3 ~ 40.6 is acceptable. Such fluctuation can also be found in the original implementation. +2. You can get a more stable results ~ mAP 40.6 with a schedule total 13 epoch, and learning rate is divided by 10 at 10th and 13th epoch. + +## Citation + +```latex +@article{zhu2020autoassign, + title={AutoAssign: Differentiable Label Assignment for Dense Object Detection}, + author={Zhu, Benjin and Wang, Jianfeng and Jiang, Zhengkai and Zong, Fuhang and Liu, Songtao and Li, Zeming and Sun, Jian}, + journal={arXiv preprint arXiv:2007.03496}, + year={2020} +} +``` diff --git a/mmdetection/configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..76a36195 --- /dev/null +++ b/mmdetection/configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,69 @@ +# We follow the original implementation which +# adopts the Caffe pre-trained backbone. +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + type='AutoAssign', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[102.9801, 115.9465, 122.7717], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs=True, + num_outs=5, + relu_before_extra_convs=True, + init_cfg=dict(type='Caffe2Xavier', layer='Conv2d')), + bbox_head=dict( + type='AutoAssignHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_bbox=dict(type='GIoULoss', loss_weight=5.0)), + train_cfg=None, + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + optimizer=dict(lr=0.01), paramwise_cfg=dict(norm_decay_mult=0.)) diff --git a/mmdetection/configs/autoassign/metafile.yml b/mmdetection/configs/autoassign/metafile.yml new file mode 100644 index 00000000..ab7a4af3 --- /dev/null +++ b/mmdetection/configs/autoassign/metafile.yml @@ -0,0 +1,33 @@ +Collections: + - Name: AutoAssign + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - AutoAssign + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/2007.03496 + Title: 'AutoAssign: Differentiable Label Assignment for Dense Object Detection' + README: configs/autoassign/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.12.0/mmdet/models/detectors/autoassign.py#L6 + Version: v2.12.0 + +Models: + - Name: autoassign_r50-caffe_fpn_1x_coco + In Collection: AutoAssign + Config: configs/autoassign/autoassign_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.08 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/autoassign/auto_assign_r50_fpn_1x_coco/auto_assign_r50_fpn_1x_coco_20210413_115540-5e17991f.pth diff --git a/mmdetection/configs/boxinst/README.md b/mmdetection/configs/boxinst/README.md new file mode 100644 index 00000000..f6f01c5d --- /dev/null +++ b/mmdetection/configs/boxinst/README.md @@ -0,0 +1,32 @@ +# BoxInst + +> [BoxInst: High-Performance Instance Segmentation with Box Annotations](https://arxiv.org/pdf/2012.02310.pdf) + + + +## Abstract + +We present a high-performance method that can achieve mask-level instance segmentation with only bounding-box annotations for training. While this setting has been studied in the literature, here we show significantly stronger performance with a simple design (e.g., dramatically improving previous best reported mask AP of 21.1% to 31.6% on the COCO dataset). Our core idea is to redesign the loss +of learning masks in instance segmentation, with no modification to the segmentation network itself. The new loss functions can supervise the mask training without relying on mask annotations. This is made possible with two loss terms, namely, 1) a surrogate term that minimizes the discrepancy between the projections of the ground-truth box and the predicted mask; 2) a pairwise loss that can exploit the prior that proximal pixels with similar colors are very likely to have the same category label. Experiments demonstrate that the redesigned mask loss can yield surprisingly high-quality instance masks with only box annotations. For example, without using any mask annotations, with a ResNet-101 backbone and 3× training schedule, we achieve 33.2% mask AP on COCO test-dev split (vs. 39.1% of the fully supervised counterpart). Our excellent experiment results on COCO and Pascal VOC indicate that our method dramatically narrows the performance gap between weakly and fully supervised instance segmentation. + +
    + +
    + +## Results and Models + +| Backbone | Style | MS train | Lr schd | bbox AP | mask AP | Config | Download | +| :------: | :-----: | :------: | :-----: | :-----: | :-----: | :-----------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | Y | 1x | 39.6 | 31.1 | [config](./boxinst_r50_fpn_ms-90k_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/boxinst/boxinst_r50_fpn_ms-90k_coco/boxinst_r50_fpn_ms-90k_coco_20221228_163052-6add751a.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/boxinst/boxinst_r50_fpn_ms-90k_coco/boxinst_r50_fpn_ms-90k_coco_20221228_163052.log.json) | +| R-101 | pytorch | Y | 1x | 41.8 | 32.7 | [config](./boxinst_r101_fpn_ms-90k_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/boxinst/boxinst_r101_fpn_ms-90k_coco/boxinst_r101_fpn_ms-90k_coco_20221229_145106-facf375b.pth) \|[log](https://download.openmmlab.com/mmdetection/v3.0/boxinst/boxinst_r101_fpn_ms-90k_coco/boxinst_r101_fpn_ms-90k_coco_20221229_145106.log.json) | + +## Citation + +```latex +@inproceedings{tian2020boxinst, + title = {{BoxInst}: High-Performance Instance Segmentation with Box Annotations}, + author = {Tian, Zhi and Shen, Chunhua and Wang, Xinlong and Chen, Hao}, + booktitle = {Proc. IEEE Conf. Computer Vision and Pattern Recognition (CVPR)}, + year = {2021} +} +``` diff --git a/mmdetection/configs/boxinst/boxinst_r101_fpn_ms-90k_coco.py b/mmdetection/configs/boxinst/boxinst_r101_fpn_ms-90k_coco.py new file mode 100644 index 00000000..ab2b1162 --- /dev/null +++ b/mmdetection/configs/boxinst/boxinst_r101_fpn_ms-90k_coco.py @@ -0,0 +1,8 @@ +_base_ = './boxinst_r50_fpn_ms-90k_coco.py' + +# model settings +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/boxinst/boxinst_r50_fpn_ms-90k_coco.py b/mmdetection/configs/boxinst/boxinst_r50_fpn_ms-90k_coco.py new file mode 100644 index 00000000..371f252a --- /dev/null +++ b/mmdetection/configs/boxinst/boxinst_r50_fpn_ms-90k_coco.py @@ -0,0 +1,93 @@ +_base_ = '../common/ms-90k_coco.py' + +# model settings +model = dict( + type='BoxInst', + data_preprocessor=dict( + type='BoxInstDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + mask_stride=4, + pairwise_size=3, + pairwise_dilation=2, + pairwise_color_thresh=0.3, + bottom_pixels_removed=10), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50'), + style='pytorch'), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', # use P5 + num_outs=5, + relu_before_extra_convs=True), + bbox_head=dict( + type='BoxInstBboxHead', + num_params=593, + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + norm_on_bbox=True, + centerness_on_reg=True, + dcn_on_last_conv=False, + center_sampling=True, + conv_bias=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + mask_head=dict( + type='BoxInstMaskHead', + num_layers=3, + feat_channels=16, + size_of_interest=8, + mask_out_stride=4, + topk_masks_per_img=64, + mask_feature_head=dict( + in_channels=256, + feat_channels=128, + start_level=0, + end_level=2, + out_channels=16, + mask_stride=8, + num_stacked_convs=4, + norm_cfg=dict(type='BN', requires_grad=True)), + loss_mask=dict( + type='DiceLoss', + use_sigmoid=True, + activate=True, + eps=5e-6, + loss_weight=1.0)), + # model training and testing settings + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100, + mask_thr=0.5)) + +# optimizer +optim_wrapper = dict(optimizer=dict(lr=0.01)) + +# evaluator +val_evaluator = dict(metric=['bbox', 'segm']) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/boxinst/metafile.yml b/mmdetection/configs/boxinst/metafile.yml new file mode 100644 index 00000000..c97fcdcd --- /dev/null +++ b/mmdetection/configs/boxinst/metafile.yml @@ -0,0 +1,52 @@ +Collections: + - Name: BoxInst + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x A100 GPUs + Architecture: + - ResNet + - FPN + - CondInst + Paper: + URL: https://arxiv.org/abs/2012.02310 + Title: 'BoxInst: High-Performance Instance Segmentation with Box Annotations' + README: configs/boxinst/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v3.0.0rc6/mmdet/models/detectors/boxinst.py#L8 + Version: v3.0.0rc6 + +Models: + - Name: boxinst_r50_fpn_ms-90k_coco + In Collection: BoxInst + Config: configs/boxinst/boxinst_r50_fpn_ms-90k_coco.py + Metadata: + Iterations: 90000 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 30.8 + Weights: https://download.openmmlab.com/mmdetection/v3.0/boxinst/boxinst_r50_fpn_ms-90k_coco/boxinst_r50_fpn_ms-90k_coco_20221228_163052-6add751a.pth + + - Name: boxinst_r101_fpn_ms-90k_coco + In Collection: BoxInst + Config: configs/boxinst/boxinst_r101_fpn_ms-90k_coco.py + Metadata: + Iterations: 90000 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 32.7 + Weights: https://download.openmmlab.com/mmdetection/v3.0/boxinst/boxinst_r101_fpn_ms-90k_coco/boxinst_r101_fpn_ms-90k_coco_20221229_145106-facf375b.pth diff --git a/mmdetection/configs/bytetrack/README.md b/mmdetection/configs/bytetrack/README.md new file mode 100644 index 00000000..30b96f07 --- /dev/null +++ b/mmdetection/configs/bytetrack/README.md @@ -0,0 +1,132 @@ +# ByteTrack: Multi-Object Tracking by Associating Every Detection Box + +## Abstract + + + +Multi-object tracking (MOT) aims at estimating bounding boxes and identities of objects in videos. Most methods obtain identities by associating detection boxes whose scores are higher than a threshold. The objects with low detection scores, e.g. occluded objects, are simply thrown away, which brings non-negligible true object missing and fragmented trajectories. To solve this problem, we present a simple, effective and generic association method, tracking by associating every detection box instead of only the high score ones. For the low score detection boxes, we utilize their similarities with tracklets to recover true objects and filter out the background detections. When applied to 9 different state-of-the-art trackers, our method achieves consistent improvement on IDF1 score ranging from 1 to 10 points. To put forwards the state-of-the-art performance of MOT, we design a simple and strong tracker, named ByteTrack. For the first time, we achieve 80.3 MOTA, 77.3 IDF1 and 63.1 HOTA on the test set of MOT17 with 30 FPS running speed on a single V100 GPU. + + + +
    + +
    + +## Citation + + + +```latex +@inproceedings{zhang2021bytetrack, + title={ByteTrack: Multi-Object Tracking by Associating Every Detection Box}, + author={Zhang, Yifu and Sun, Peize and Jiang, Yi and Yu, Dongdong and Yuan, Zehuan and Luo, Ping and Liu, Wenyu and Wang, Xinggang}, + journal={arXiv preprint arXiv:2110.06864}, + year={2021} +} +``` + +## Results and models on MOT17 + +Please note that the performance on `MOT17-half-val` is comparable with the performance reported in the manuscript, while the performance on `MOT17-test` is lower than the performance reported in the manuscript. + +The reason is that ByteTrack tunes customized hyper-parameters (e.g., image resolution and the high threshold of detection score) for each video in `MOT17-test` set, while we use unified parameters. + +| Method | Detector | Train Set | Test Set | Public | Inf time (fps) | HOTA | MOTA | IDF1 | FP | FN | IDSw. | Config | Download | +| :-------: | :------: | :---------------------------: | :------------: | :----: | :------------: | :--: | :--: | :--: | :---: | :---: | :---: | :-------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ByteTrack | YOLOX-X | CrowdHuman + MOT17-half-train | MOT17-half-val | N | - | 67.5 | 78.6 | 78.5 | 12852 | 21060 | 672 | [config](bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py) | [model](https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot17-private-half_20211218_205500-1985c9f0.pth) \| [log](https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot17-private-half_20211218_205500.log.json) | +| ByteTrack | YOLOX-X | CrowdHuman + MOT17-half-train | MOT17-test | N | - | 61.7 | 78.1 | 74.8 | 36705 | 85032 | 2049 | [config](bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17test.py) | [model](https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot17-private-half_20211218_205500-1985c9f0.pth) \| [log](https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot17-private-half_20211218_205500.log.json) | + +## Results and models on MOT20 + +Since there are only 4 videos in `MOT20-train`, ByteTrack is validated on `MOT17-train` rather than `MOT20-half-train`. + +Please note that the MOTA on `MOT20-test` is slightly lower than that reported in the manuscript, because we don't tune the threshold for each video. + +| Method | Detector | Train Set | Test Set | Public | Inf time (fps) | HOTA | MOTA | IDF1 | FP | FN | IDSw. | Config | Download | +| :-------: | :------: | :----------------------: | :---------: | :----: | :------------: | :--: | :--: | :--: | :----: | :----: | :---: | :------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ByteTrack | YOLOX-X | CrowdHuman + MOT20-train | MOT17-train | N | - | 57.3 | 64.9 | 71.8 | 33,747 | 83,385 | 1,263 | [config](bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test.py) | [model](https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot20-private_20220506_101040-9ce38a60.pth) \| [log](https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot20-private_20220506_101040.log.json) | +| ByteTrack | YOLOX-X | CrowdHuman + MOT20-train | MOT20-test | N | - | 61.5 | 77.0 | 75.4 | 33,083 | 84,433 | 1,345 | [config](bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test.py) | [model](https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot20-private_20220506_101040-9ce38a60.pth) \| [log](https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot20-private_20220506_101040.log.json) | + +## Get started + +### 1. Development Environment Setup + +Tracking Development Environment Setup can refer to this [document](../../docs/en/get_started.md). + +### 2. Dataset Prepare + +Tracking Dataset Prepare can refer to this [document](../../docs/en/user_guides/tracking_dataset_prepare.md). + +### 3. Training + +Due to the influence of parameters such as learning rate in default configuration file, we recommend using 8 GPUs for training in order to reproduce accuracy. You can use the following command to start the training. + +**3.1 Joint training and tracking** + +Some algorithm like ByteTrack, OCSORT don't need reid model, so we provide joint training and tracking for convenient. + +```shell +# Training Bytetrack on crowdhuman and mot17-half-train dataset with following command +# The number after config file represents the number of GPUs used. Here we use 8 GPUs +bash tools/dist_train.sh configs/bytetrack/bytetrack_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py 8 +``` + +**3.2 Separate training and tracking** + +Of course, we provide train detector independently like SORT, DeepSORT, StrongSORT. Then use this detector to track. + +```shell +# Training Bytetrack on crowdhuman and mot17-half-train dataset with following command +# The number after config file represents the number of GPUs used. Here we use 8 GPUs +bash tools/dist_train.sh configs/bytetrack/yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py 8 +``` + +If you want to know about more detailed usage of `train.py/dist_train.sh/slurm_train.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 4. Testing and evaluation + +### 4.1 Example on MOTxx-halfval dataset + +**4.1.1 use joint trained detector to evaluating and testing** + +```shell +bash tools/dist_test_tracking.sh configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py 8 --checkpoint ${CHECKPOINT_FILE} +``` + +**4.1.2 use separate trained detector to evaluating and testing** + +```shell +bash tools/dist_test_tracking.sh configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py 8 --detector ${CHECKPOINT_FILE} +``` + +**4.1.3 use video_baesd to evaluating and testing** + +we also provide two_ways(img_based or video_based) to evaluating and testing. +if you want to use video_based to evaluating and testing, you can modify config as follows + +``` +val_dataloader = dict( + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False)) +``` + +#### 4.2 Example on MOTxx-test dataset + +If you want to get the results of the [MOT Challenge](https://motchallenge.net/) test set, please use the following command to generate result files that can be used for submission. It will be stored in `./mot_17_test_res`, you can modify the saved path in `test_evaluator` of the config. + +```shell +bash tools/dist_test_tracking.sh configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17test.py 8 --checkpoint ${CHECKPOINT_FILE} +``` + +If you want to know about more detailed usage of `test_tracking.py/dist_test_tracking.sh/slurm_test_tracking.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 5.Inference + +Use a single GPU to predict a video and save it as a video. + +```shell +python demo/mot_demo.py demo/demo_mot.mp4 configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py --checkpoint ${CHECKPOINT_FILE} --out mot.mp4 +``` + +If you want to know about more detailed usage of `mot_demo.py`, please refer to this [document](../../docs/en/user_guides/tracking_inference.md). diff --git a/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..24b3f784 --- /dev/null +++ b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py @@ -0,0 +1,249 @@ +_base_ = ['../yolox/yolox_x_8xb8-300e_coco.py'] + +dataset_type = 'MOTChallengeDataset' +data_root = 'data/MOT17/' + +img_scale = (1440, 800) # weight, height +batch_size = 4 + +detector = _base_.model +detector.pop('data_preprocessor') +detector.bbox_head.update(dict(num_classes=1)) +detector.test_cfg.nms.update(dict(iou_threshold=0.7)) +detector['init_cfg'] = dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_x_8x8_300e_coco/yolox_x_8x8_300e_coco_20211126_140254-1ef88d67.pth' # noqa: E501 +) +del _base_.model + +model = dict( + type='ByteTrack', + data_preprocessor=dict( + type='TrackDataPreprocessor', + pad_size_divisor=32, + # in bytetrack, we provide joint train detector and evaluate tracking + # performance, use_det_processor means use independent detector + # data_preprocessor. of course, you can train detector independently + # like strongsort + use_det_processor=True, + batch_augments=[ + dict( + type='BatchSyncRandomResize', + random_size_range=(576, 1024), + size_divisor=32, + interval=10) + ]), + detector=detector, + tracker=dict( + type='ByteTracker', + motion=dict(type='KalmanFilter'), + obj_score_thrs=dict(high=0.6, low=0.1), + init_track_thr=0.7, + weight_iou_with_det_scores=True, + match_iou_thrs=dict(high=0.1, low=0.5, tentative=0.3), + num_frames_retain=30)) + +train_pipeline = [ + dict( + type='Mosaic', + img_scale=img_scale, + pad_val=114.0, + bbox_clip_border=False), + dict( + type='RandomAffine', + scaling_ratio_range=(0.1, 2), + border=(-img_scale[0] // 2, -img_scale[1] // 2), + bbox_clip_border=False), + dict( + type='MixUp', + img_scale=img_scale, + ratio_range=(0.8, 1.6), + pad_val=114.0, + bbox_clip_border=False), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict( + type='Resize', + scale=img_scale, + keep_ratio=True, + clip_object_border=False), + dict(type='Pad', size_divisor=32, pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1), keep_empty=False), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict( + type='TransformBroadcaster', + transforms=[ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict( + type='Pad', + size_divisor=32, + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='LoadTrackAnnotations'), + ]), + dict(type='PackTrackInputs') +] +train_dataloader = dict( + _delete_=True, + batch_size=batch_size, + num_workers=4, + persistent_workers=True, + pin_memory=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + type='MultiImageMixDataset', + dataset=dict( + type='ConcatDataset', + datasets=[ + dict( + type='CocoDataset', + data_root='data/MOT17', + ann_file='annotations/half-train_cocoformat.json', + data_prefix=dict(img='train'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + dict( + type='CocoDataset', + data_root='data/crowdhuman', + ann_file='annotations/crowdhuman_train.json', + data_prefix=dict(img='train'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + dict( + type='CocoDataset', + data_root='data/crowdhuman', + ann_file='annotations/crowdhuman_val.json', + data_prefix=dict(img='val'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + ]), + pipeline=train_pipeline)) + +val_dataloader = dict( + _delete_=True, + batch_size=1, + num_workers=2, + persistent_workers=True, + pin_memory=True, + drop_last=False, + # video_based + # sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), + sampler=dict(type='TrackImgSampler'), # image_based + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/half-val_cocoformat.json', + data_prefix=dict(img_path='train'), + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# optimizer +# default 8 gpu +base_lr = 0.001 / 8 * batch_size +optim_wrapper = dict(optimizer=dict(lr=base_lr)) + +# some hyper parameters +# training settings +max_epochs = 80 +num_last_epochs = 10 +interval = 5 + +train_cfg = dict( + type='EpochBasedTrainLoop', + max_epochs=max_epochs, + val_begin=70, + val_interval=1) + +# learning policy +param_scheduler = [ + dict( + # use quadratic formula to warm up 1 epochs + type='QuadraticWarmupLR', + by_epoch=True, + begin=0, + end=1, + convert_to_iter_based=True), + dict( + # use cosine lr from 1 to 70 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=1, + T_max=max_epochs - num_last_epochs, + end=max_epochs - num_last_epochs, + by_epoch=True, + convert_to_iter_based=True), + dict( + # use fixed lr during last 10 epochs + type='ConstantLR', + by_epoch=True, + factor=1, + begin=max_epochs - num_last_epochs, + end=max_epochs, + ) +] + +custom_hooks = [ + dict( + type='YOLOXModeSwitchHook', + num_last_epochs=num_last_epochs, + priority=48), + dict(type='SyncNormHook', priority=48), + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0001, + update_buffers=True, + priority=49) +] + +default_hooks = dict( + checkpoint=dict( + _delete_=True, type='CheckpointHook', interval=1, max_keep_ckpts=10), + visualization=dict(type='TrackVisualizationHook', draw=False)) + +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='TrackLocalVisualizer', vis_backends=vis_backends, name='visualizer') + +# evaluator +val_evaluator = dict( + _delete_=True, + type='MOTChallengeMetric', + metric=['HOTA', 'CLEAR', 'Identity'], + postprocess_tracklet_cfg=[ + dict(type='InterpolateTracklets', min_num_frames=5, max_num_frames=20) + ]) +test_evaluator = val_evaluator + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (4 samples per GPU) +auto_scale_lr = dict(base_batch_size=32) + +del detector +del _base_.tta_model +del _base_.tta_pipeline +del _base_.train_dataset diff --git a/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py new file mode 100644 index 00000000..9202f5fb --- /dev/null +++ b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py @@ -0,0 +1,127 @@ +_base_ = [ + './bytetrack_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_' + 'test-mot17halfval.py' +] + +dataset_type = 'MOTChallengeDataset' + +img_scale = (1600, 896) # weight, height + +model = dict( + data_preprocessor=dict( + type='TrackDataPreprocessor', + use_det_processor=True, + pad_size_divisor=32, + batch_augments=[ + dict(type='BatchSyncRandomResize', random_size_range=(640, 1152)) + ]), + tracker=dict( + weight_iou_with_det_scores=False, + match_iou_thrs=dict(high=0.3), + )) + +train_pipeline = [ + dict( + type='Mosaic', + img_scale=img_scale, + pad_val=114.0, + bbox_clip_border=True), + dict( + type='RandomAffine', + scaling_ratio_range=(0.1, 2), + border=(-img_scale[0] // 2, -img_scale[1] // 2), + bbox_clip_border=True), + dict( + type='MixUp', + img_scale=img_scale, + ratio_range=(0.8, 1.6), + pad_val=114.0, + bbox_clip_border=True), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict( + type='Resize', + scale=img_scale, + keep_ratio=True, + clip_object_border=True), + dict(type='Pad', size_divisor=32, pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1), keep_empty=False), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict( + type='TransformBroadcaster', + transforms=[ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict( + type='Pad', + size_divisor=32, + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='LoadTrackAnnotations'), + ]), + dict(type='PackTrackInputs') +] +train_dataloader = dict( + dataset=dict( + type='MultiImageMixDataset', + dataset=dict( + type='ConcatDataset', + datasets=[ + dict( + type='CocoDataset', + data_root='data/MOT20', + ann_file='annotations/train_cocoformat.json', + # TODO: mmdet use img as key, but img_path is needed + data_prefix=dict(img='train'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + dict( + type='CocoDataset', + data_root='data/crowdhuman', + ann_file='annotations/crowdhuman_train.json', + data_prefix=dict(img='train'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + dict( + type='CocoDataset', + data_root='data/crowdhuman', + ann_file='annotations/crowdhuman_val.json', + data_prefix=dict(img='val'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + ]), + pipeline=train_pipeline)) +val_dataloader = dict( + dataset=dict(ann_file='annotations/train_cocoformat.json')) + +test_dataloader = dict( + dataset=dict( + data_root='data/MOT20', ann_file='annotations/test_cocoformat.json')) + +test_evaluator = dict( + type='MOTChallengeMetrics', + postprocess_tracklet_cfg=[ + dict(type='InterpolateTracklets', min_num_frames=5, max_num_frames=20) + ], + format_only=True, + outfile_prefix='./mot_20_test_res') diff --git a/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..9c211920 --- /dev/null +++ b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py @@ -0,0 +1,9 @@ +_base_ = [ + './bytetrack_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_' + 'test-mot17halfval.py' +] + +# fp16 settings +optim_wrapper = dict(type='AmpOptimWrapper', loss_scale='dynamic') +val_cfg = dict(type='ValLoop', fp16=True) +test_cfg = dict(type='TestLoop', fp16=True) diff --git a/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17test.py b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17test.py new file mode 100644 index 00000000..3f4427c1 --- /dev/null +++ b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17test.py @@ -0,0 +1,17 @@ +_base_ = [ + './bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-' + 'mot17halftrain_test-mot17halfval.py' +] + +test_dataloader = dict( + dataset=dict( + data_root='data/MOT17/', + ann_file='annotations/test_cocoformat.json', + data_prefix=dict(img_path='test'))) +test_evaluator = dict( + type='MOTChallengeMetrics', + postprocess_tracklet_cfg=[ + dict(type='InterpolateTracklets', min_num_frames=5, max_num_frames=20) + ], + format_only=True, + outfile_prefix='./mot_17_test_res') diff --git a/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test.py b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test.py new file mode 100644 index 00000000..10169997 --- /dev/null +++ b/mmdetection/configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test.py @@ -0,0 +1,8 @@ +_base_ = [ + './bytetrack_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py' +] + +# fp16 settings +optim_wrapper = dict(type='AmpOptimWrapper', loss_scale='dynamic') +val_cfg = dict(type='ValLoop', fp16=True) +test_cfg = dict(type='TestLoop', fp16=True) diff --git a/mmdetection/configs/bytetrack/metafile.yml b/mmdetection/configs/bytetrack/metafile.yml new file mode 100644 index 00000000..8ed638cf --- /dev/null +++ b/mmdetection/configs/bytetrack/metafile.yml @@ -0,0 +1,53 @@ +Collections: + - Name: ByteTrack + Metadata: + Training Techniques: + - SGD with Momentum + Training Resources: 8x V100 GPUs + Architecture: + - YOLOX + Paper: + URL: https://arxiv.org/abs/2110.06864 + Title: ByteTrack Multi-Object Tracking by Associating Every Detection Box + README: configs/bytetrack/README.md + +Models: + - Name: bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval + In Collection: ByteTrack + Config: configs/bytetrack/bytetrack_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py + Metadata: + Training Data: CrowdHuman + MOT17-half-train + Results: + - Task: Multiple Object Tracking + Dataset: MOT17-half-val + Metrics: + HOTA: 67.5 + MOTA: 78.6 + IDF1: 78.5 + Weights: https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot17-private-half_20211218_205500-1985c9f0.pth + + - Name: bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17test + In Collection: ByteTrack + Config: configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17test.py + Metadata: + Training Data: CrowdHuman + MOT17-half-train + Results: + - Task: Multiple Object Tracking + Dataset: MOT17-test + Metrics: + MOTA: 78.1 + IDF1: 74.8 + Weights: https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot17-private-half_20211218_205500-1985c9f0.pth + + - Name: bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test + In Collection: ByteTrack + Config: configs/bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test.py + Metadata: + Training Data: CrowdHuman + MOT20-train + Results: + - Task: Multiple Object Tracking + Dataset: MOT20-test + Metrics: + MOTA: 77.0 + IDF1: 75.4 + Weights: https://download.openmmlab.com/mmtracking/mot/bytetrack/bytetrack_yolox_x/bytetrack_yolox_x_crowdhuman_mot20-private_20220506_101040-9ce38a60.pth diff --git a/mmdetection/configs/bytetrack/yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py b/mmdetection/configs/bytetrack/yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..8fc3acd4 --- /dev/null +++ b/mmdetection/configs/bytetrack/yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py @@ -0,0 +1,6 @@ +_base_ = [ + '../strongsort/yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py' # noqa: E501 +] + +# fp16 settings +optim_wrapper = dict(type='AmpOptimWrapper', loss_scale='dynamic') diff --git a/mmdetection/configs/carafe/README.md b/mmdetection/configs/carafe/README.md new file mode 100644 index 00000000..61e1fa60 --- /dev/null +++ b/mmdetection/configs/carafe/README.md @@ -0,0 +1,42 @@ +# CARAFE + +> [CARAFE: Content-Aware ReAssembly of FEatures](https://arxiv.org/abs/1905.02188) + + + +## Abstract + +Feature upsampling is a key operation in a number of modern convolutional network architectures, e.g. feature pyramids. Its design is critical for dense prediction tasks such as object detection and semantic/instance segmentation. In this work, we propose Content-Aware ReAssembly of FEatures (CARAFE), a universal, lightweight and highly effective operator to fulfill this goal. CARAFE has several appealing properties: (1) Large field of view. Unlike previous works (e.g. bilinear interpolation) that only exploit sub-pixel neighborhood, CARAFE can aggregate contextual information within a large receptive field. (2) Content-aware handling. Instead of using a fixed kernel for all samples (e.g. deconvolution), CARAFE enables instance-specific content-aware handling, which generates adaptive kernels on-the-fly. (3) Lightweight and fast to compute. CARAFE introduces little computational overhead and can be readily integrated into modern network architectures. We conduct comprehensive evaluations on standard benchmarks in object detection, instance/semantic segmentation and inpainting. CARAFE shows consistent and substantial gains across all the tasks (1.2%, 1.3%, 1.8%, 1.1db respectively) with negligible computational overhead. It has great potential to serve as a strong building block for future research. It has great potential to serve as a strong building block for future research. + +
    + +
    + +## Results and Models + +The results on COCO 2017 val is shown in the below table. + +| Method | Backbone | Style | Lr schd | Test Proposal Num | Inf time (fps) | Box AP | Mask AP | Config | Download | +| :--------------------: | :------: | :-----: | :-----: | :---------------: | :------------: | :----: | :-----: | :-----------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster R-CNN w/ CARAFE | R-50-FPN | pytorch | 1x | 1000 | 16.5 | 38.6 | 38.6 | [config](./faster-rcnn_r50_fpn-carafe_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/carafe/faster_rcnn_r50_fpn_carafe_1x_coco/faster_rcnn_r50_fpn_carafe_1x_coco_bbox_mAP-0.386_20200504_175733-385a75b7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/carafe/faster_rcnn_r50_fpn_carafe_1x_coco/faster_rcnn_r50_fpn_carafe_1x_coco_20200504_175733.log.json) | +| - | - | - | - | 2000 | | | | | | +| Mask R-CNN w/ CARAFE | R-50-FPN | pytorch | 1x | 1000 | 14.0 | 39.3 | 35.8 | [config](./mask-rcnn_r50_fpn-carafe_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/carafe/mask_rcnn_r50_fpn_carafe_1x_coco/mask_rcnn_r50_fpn_carafe_1x_coco_bbox_mAP-0.393__segm_mAP-0.358_20200503_135957-8687f195.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/carafe/mask_rcnn_r50_fpn_carafe_1x_coco/mask_rcnn_r50_fpn_carafe_1x_coco_20200503_135957.log.json) | +| - | - | - | - | 2000 | | | | | | + +## Implementation + +The CUDA implementation of CARAFE can be find at https://github.com/myownskyW7/CARAFE. + +## Citation + +We provide config files to reproduce the object detection & instance segmentation results in the ICCV 2019 Oral paper for [CARAFE: Content-Aware ReAssembly of FEatures](https://arxiv.org/abs/1905.02188). + +```latex +@inproceedings{Wang_2019_ICCV, + title = {CARAFE: Content-Aware ReAssembly of FEatures}, + author = {Wang, Jiaqi and Chen, Kai and Xu, Rui and Liu, Ziwei and Loy, Chen Change and Lin, Dahua}, + booktitle = {The IEEE International Conference on Computer Vision (ICCV)}, + month = {October}, + year = {2019} +} +``` diff --git a/mmdetection/configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py b/mmdetection/configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py new file mode 100644 index 00000000..388305cc --- /dev/null +++ b/mmdetection/configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py @@ -0,0 +1,20 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + data_preprocessor=dict(pad_size_divisor=64), + neck=dict( + type='FPN_CARAFE', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5, + start_level=0, + end_level=-1, + norm_cfg=None, + act_cfg=None, + order=('conv', 'norm', 'act'), + upsample_cfg=dict( + type='carafe', + up_kernel=5, + up_group=1, + encoder_kernel=3, + encoder_dilation=1, + compressed_channels=64))) diff --git a/mmdetection/configs/carafe/mask-rcnn_r50_fpn-carafe_1x_coco.py b/mmdetection/configs/carafe/mask-rcnn_r50_fpn-carafe_1x_coco.py new file mode 100644 index 00000000..6ce621de --- /dev/null +++ b/mmdetection/configs/carafe/mask-rcnn_r50_fpn-carafe_1x_coco.py @@ -0,0 +1,30 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + data_preprocessor=dict(pad_size_divisor=64), + neck=dict( + type='FPN_CARAFE', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5, + start_level=0, + end_level=-1, + norm_cfg=None, + act_cfg=None, + order=('conv', 'norm', 'act'), + upsample_cfg=dict( + type='carafe', + up_kernel=5, + up_group=1, + encoder_kernel=3, + encoder_dilation=1, + compressed_channels=64)), + roi_head=dict( + mask_head=dict( + upsample_cfg=dict( + type='carafe', + scale_factor=2, + up_kernel=5, + up_group=1, + encoder_kernel=3, + encoder_dilation=1, + compressed_channels=64)))) diff --git a/mmdetection/configs/carafe/metafile.yml b/mmdetection/configs/carafe/metafile.yml new file mode 100644 index 00000000..863c0f49 --- /dev/null +++ b/mmdetection/configs/carafe/metafile.yml @@ -0,0 +1,55 @@ +Collections: + - Name: CARAFE + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RPN + - FPN_CARAFE + - ResNet + - RoIPool + Paper: + URL: https://arxiv.org/abs/1905.02188 + Title: 'CARAFE: Content-Aware ReAssembly of FEatures' + README: configs/carafe/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.12.0/mmdet/models/necks/fpn_carafe.py#L11 + Version: v2.12.0 + +Models: + - Name: faster-rcnn_r50_fpn_carafe_1x_coco + In Collection: CARAFE + Config: configs/carafe/faster-rcnn_r50_fpn-carafe_1x_coco.py + Metadata: + Training Memory (GB): 4.26 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/carafe/faster_rcnn_r50_fpn_carafe_1x_coco/faster_rcnn_r50_fpn_carafe_1x_coco_bbox_mAP-0.386_20200504_175733-385a75b7.pth + + - Name: mask-rcnn_r50_fpn_carafe_1x_coco + In Collection: CARAFE + Config: configs/carafe/mask-rcnn_r50_fpn-carafe_1x_coco.py + Metadata: + Training Memory (GB): 4.31 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 35.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/carafe/mask_rcnn_r50_fpn_carafe_1x_coco/mask_rcnn_r50_fpn_carafe_1x_coco_bbox_mAP-0.393__segm_mAP-0.358_20200503_135957-8687f195.pth diff --git a/mmdetection/configs/cascade_rcnn/README.md b/mmdetection/configs/cascade_rcnn/README.md new file mode 100644 index 00000000..81fce448 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/README.md @@ -0,0 +1,79 @@ +# Cascade R-CNN + +> [Cascade R-CNN: High Quality Object Detection and Instance Segmentation](https://arxiv.org/abs/1906.09756) + + + +## Abstract + +In object detection, the intersection over union (IoU) threshold is frequently used to define positives/negatives. The threshold used to train a detector defines its quality. While the commonly used threshold of 0.5 leads to noisy (low-quality) detections, detection performance frequently degrades for larger thresholds. This paradox of high-quality detection has two causes: 1) overfitting, due to vanishing positive samples for large thresholds, and 2) inference-time quality mismatch between detector and test hypotheses. A multi-stage object detection architecture, the Cascade R-CNN, composed of a sequence of detectors trained with increasing IoU thresholds, is proposed to address these problems. The detectors are trained sequentially, using the output of a detector as training set for the next. This resampling progressively improves hypotheses quality, guaranteeing a positive training set of equivalent size for all detectors and minimizing overfitting. The same cascade is applied at inference, to eliminate quality mismatches between hypotheses and detectors. An implementation of the Cascade R-CNN without bells or whistles achieves state-of-the-art performance on the COCO dataset, and significantly improves high-quality detection on generic and specific object detection datasets, including VOC, KITTI, CityPerson, and WiderFace. Finally, the Cascade R-CNN is generalized to instance segmentation, with nontrivial improvements over the Mask R-CNN. + +
    + +
    + +## Results and Models + +### Cascade R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | caffe | 1x | 4.2 | | 40.4 | [config](./cascade-rcnn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_caffe_fpn_1x_coco/cascade_rcnn_r50_caffe_fpn_1x_coco_bbox_mAP-0.404_20200504_174853-b857be87.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_caffe_fpn_1x_coco/cascade_rcnn_r50_caffe_fpn_1x_coco_20200504_174853.log.json) | +| R-50-FPN | pytorch | 1x | 4.4 | 16.1 | 40.3 | [config](./cascade-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco/cascade_rcnn_r50_fpn_1x_coco_20200316-3dc56deb.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco/cascade_rcnn_r50_fpn_1x_coco_20200316_214748.log.json) | +| R-50-FPN | pytorch | 20e | - | - | 41.0 | [config](./cascade-rcnn_r50_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_fpn_20e_coco/cascade_rcnn_r50_fpn_20e_coco_bbox_mAP-0.41_20200504_175131-e9872a90.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_fpn_20e_coco/cascade_rcnn_r50_fpn_20e_coco_20200504_175131.log.json) | +| R-101-FPN | caffe | 1x | 6.2 | | 42.3 | [config](./cascade-rcnn_r101-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r101_caffe_fpn_1x_coco/cascade_rcnn_r101_caffe_fpn_1x_coco_bbox_mAP-0.423_20200504_175649-cab8dbd5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r101_caffe_fpn_1x_coco/cascade_rcnn_r101_caffe_fpn_1x_coco_20200504_175649.log.json) | +| R-101-FPN | pytorch | 1x | 6.4 | 13.5 | 42.0 | [config](./cascade-rcnn_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r101_fpn_1x_coco/cascade_rcnn_r101_fpn_1x_coco_20200317-0b6a2fbf.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r101_fpn_1x_coco/cascade_rcnn_r101_fpn_1x_coco_20200317_101744.log.json) | +| R-101-FPN | pytorch | 20e | - | - | 42.5 | [config](./cascade-rcnn_r101_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r101_fpn_20e_coco/cascade_rcnn_r101_fpn_20e_coco_bbox_mAP-0.425_20200504_231812-5057dcc5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r101_fpn_20e_coco/cascade_rcnn_r101_fpn_20e_coco_20200504_231812.log.json) | +| X-101-32x4d-FPN | pytorch | 1x | 7.6 | 10.9 | 43.7 | [config](./cascade-rcnn_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_32x4d_fpn_1x_coco/cascade_rcnn_x101_32x4d_fpn_1x_coco_20200316-95c2deb6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_32x4d_fpn_1x_coco/cascade_rcnn_x101_32x4d_fpn_1x_coco_20200316_055608.log.json) | +| X-101-32x4d-FPN | pytorch | 20e | 7.6 | | 43.7 | [config](./cascade-rcnn_x101-32x4d_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_32x4d_fpn_20e_coco/cascade_rcnn_x101_32x4d_fpn_20e_coco_20200906_134608-9ae0a720.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_32x4d_fpn_20e_coco/cascade_rcnn_x101_32x4d_fpn_20e_coco_20200906_134608.log.json) | +| X-101-64x4d-FPN | pytorch | 1x | 10.7 | | 44.7 | [config](./cascade-rcnn_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_64x4d_fpn_1x_coco/cascade_rcnn_x101_64x4d_fpn_1x_coco_20200515_075702-43ce6a30.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_64x4d_fpn_1x_coco/cascade_rcnn_x101_64x4d_fpn_1x_coco_20200515_075702.log.json) | +| X-101-64x4d-FPN | pytorch | 20e | 10.7 | | 44.5 | [config](./cascade-rcnn_x101_64x4d_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_64x4d_fpn_20e_coco/cascade_rcnn_x101_64x4d_fpn_20e_coco_20200509_224357-051557b1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_64x4d_fpn_20e_coco/cascade_rcnn_x101_64x4d_fpn_20e_coco_20200509_224357.log.json) | + +### Cascade Mask R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | caffe | 1x | 5.9 | | 41.2 | 36.0 | [config](./cascade-mask-rcnn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_caffe_fpn_1x_coco/cascade_mask_rcnn_r50_caffe_fpn_1x_coco_bbox_mAP-0.412__segm_mAP-0.36_20200504_174659-5004b251.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_caffe_fpn_1x_coco/cascade_mask_rcnn_r50_caffe_fpn_1x_coco_20200504_174659.log.json) | +| R-50-FPN | pytorch | 1x | 6.0 | 11.2 | 41.2 | 35.9 | [config](./cascade-mask-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco/cascade_mask_rcnn_r50_fpn_1x_coco_20200203-9d4dcb24.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco/cascade_mask_rcnn_r50_fpn_1x_coco_20200203_170449.log.json) | +| R-50-FPN | pytorch | 20e | - | - | 41.9 | 36.5 | [config](./cascade-mask-rcnn_r50_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_20e_coco/cascade_mask_rcnn_r50_fpn_20e_coco_bbox_mAP-0.419__segm_mAP-0.365_20200504_174711-4af8e66e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_20e_coco/cascade_mask_rcnn_r50_fpn_20e_coco_20200504_174711.log.json) | +| R-101-FPN | caffe | 1x | 7.8 | | 43.2 | 37.6 | [config](./cascade-mask-rcnn_r101-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_caffe_fpn_1x_coco/cascade_mask_rcnn_r101_caffe_fpn_1x_coco_bbox_mAP-0.432__segm_mAP-0.376_20200504_174813-5c1e9599.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_caffe_fpn_1x_coco/cascade_mask_rcnn_r101_caffe_fpn_1x_coco_20200504_174813.log.json) | +| R-101-FPN | pytorch | 1x | 7.9 | 9.8 | 42.9 | 37.3 | [config](./cascade-mask-rcnn_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_fpn_1x_coco/cascade_mask_rcnn_r101_fpn_1x_coco_20200203-befdf6ee.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_fpn_1x_coco/cascade_mask_rcnn_r101_fpn_1x_coco_20200203_092521.log.json) | +| R-101-FPN | pytorch | 20e | - | - | 43.4 | 37.8 | [config](./cascade-mask-rcnn_r101_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_fpn_20e_coco/cascade_mask_rcnn_r101_fpn_20e_coco_bbox_mAP-0.434__segm_mAP-0.378_20200504_174836-005947da.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_fpn_20e_coco/cascade_mask_rcnn_r101_fpn_20e_coco_20200504_174836.log.json) | +| X-101-32x4d-FPN | pytorch | 1x | 9.2 | 8.6 | 44.3 | 38.3 | [config](./cascade-mask-rcnn_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x4d_fpn_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_1x_coco_20200201-0f411b1f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x4d_fpn_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_1x_coco_20200201_052416.log.json) | +| X-101-32x4d-FPN | pytorch | 20e | 9.2 | - | 45.0 | 39.0 | [config](./cascade-mask-rcnn_x101-32x4d_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x4d_fpn_20e_coco/cascade_mask_rcnn_x101_32x4d_fpn_20e_coco_20200528_083917-ed1f4751.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x4d_fpn_20e_coco/cascade_mask_rcnn_x101_32x4d_fpn_20e_coco_20200528_083917.log.json) | +| X-101-64x4d-FPN | pytorch | 1x | 12.2 | 6.7 | 45.3 | 39.2 | [config](./cascade-mask-rcnn_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_64x4d_fpn_1x_coco/cascade_mask_rcnn_x101_64x4d_fpn_1x_coco_20200203-9a2db89d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_64x4d_fpn_1x_coco/cascade_mask_rcnn_x101_64x4d_fpn_1x_coco_20200203_044059.log.json) | +| X-101-64x4d-FPN | pytorch | 20e | 12.2 | | 45.6 | 39.5 | [config](./cascade-mask-rcnn_x101-64x4d_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_64x4d_fpn_20e_coco/cascade_mask_rcnn_x101_64x4d_fpn_20e_coco_20200512_161033-bdb5126a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_64x4d_fpn_20e_coco/cascade_mask_rcnn_x101_64x4d_fpn_20e_coco_20200512_161033.log.json) | + +**Notes:** + +- The `20e` schedule in Cascade (Mask) R-CNN indicates decreasing the lr at 16 and 19 epochs, with a total of 20 epochs. + +## Pre-trained Models + +We also train some models with longer schedules and multi-scale training for Cascade Mask R-CNN. The users could finetune them for downstream tasks. + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :--------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | caffe | 3x | 5.7 | | 44.0 | 38.1 | [config](./cascade-mask-rcnn_r50-caffe_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_caffe_fpn_mstrain_3x_coco/cascade_mask_rcnn_r50_caffe_fpn_mstrain_3x_coco_20210707_002651-6e29b3a6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_caffe_fpn_mstrain_3x_coco/cascade_mask_rcnn_r50_caffe_fpn_mstrain_3x_coco_20210707_002651.log.json) | +| R-50-FPN | pytorch | 3x | 5.9 | | 44.3 | 38.5 | [config](./cascade-mask-rcnn_r50_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_mstrain_3x_coco/cascade_mask_rcnn_r50_fpn_mstrain_3x_coco_20210628_164719-5bdc3824.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_mstrain_3x_coco/cascade_mask_rcnn_r50_fpn_mstrain_3x_coco_20210628_164719.log.json) | +| R-101-FPN | caffe | 3x | 7.7 | | 45.4 | 39.5 | [config](./cascade-mask-rcnn_r101-caffe_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_caffe_fpn_mstrain_3x_coco/cascade_mask_rcnn_r101_caffe_fpn_mstrain_3x_coco_20210707_002620-a5bd2389.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_caffe_fpn_mstrain_3x_coco/cascade_mask_rcnn_r101_caffe_fpn_mstrain_3x_coco_20210707_002620.log.json) | +| R-101-FPN | pytorch | 3x | 7.8 | | 45.5 | 39.6 | [config](./cascade-mask-rcnn_r101_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_fpn_mstrain_3x_coco/cascade_mask_rcnn_r101_fpn_mstrain_3x_coco_20210628_165236-51a2d363.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_fpn_mstrain_3x_coco/cascade_mask_rcnn_r101_fpn_mstrain_3x_coco_20210628_165236.log.json) | +| X-101-32x4d-FPN | pytorch | 3x | 9.0 | | 46.3 | 40.1 | [config](./cascade-mask-rcnn_x101-32x4d_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x4d_fpn_mstrain_3x_coco/cascade_mask_rcnn_x101_32x4d_fpn_mstrain_3x_coco_20210706_225234-40773067.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x4d_fpn_mstrain_3x_coco/cascade_mask_rcnn_x101_32x4d_fpn_mstrain_3x_coco_20210706_225234.log.json) | +| X-101-32x8d-FPN | pytorch | 3x | 12.1 | | 46.1 | 39.9 | [config](./cascade-mask-rcnn_x101-32x8d_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x8d_fpn_mstrain_3x_coco/cascade_mask_rcnn_x101_32x8d_fpn_mstrain_3x_coco_20210719_180640-9ff7e76f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x8d_fpn_mstrain_3x_coco/cascade_mask_rcnn_x101_32x8d_fpn_mstrain_3x_coco_20210719_180640.log.json) | +| X-101-64x4d-FPN | pytorch | 3x | 12.0 | | 46.6 | 40.3 | [config](./cascade-mask-rcnn_x101-64x4d_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_64x4d_fpn_mstrain_3x_coco/cascade_mask_rcnn_x101_64x4d_fpn_mstrain_3x_coco_20210719_210311-d3e64ba0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_64x4d_fpn_mstrain_3x_coco/cascade_mask_rcnn_x101_64x4d_fpn_mstrain_3x_coco_20210719_210311.log.json) | + +## Citation + +```latex +@article{Cai_2019, + title={Cascade R-CNN: High Quality Object Detection and Instance Segmentation}, + ISSN={1939-3539}, + url={http://dx.doi.org/10.1109/tpami.2019.2956516}, + DOI={10.1109/tpami.2019.2956516}, + journal={IEEE Transactions on Pattern Analysis and Machine Intelligence}, + publisher={Institute of Electrical and Electronics Engineers (IEEE)}, + author={Cai, Zhaowei and Vasconcelos, Nuno}, + year={2019}, + pages={1–1} +} +``` diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..6d85340e --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './cascade-mask-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101-caffe_fpn_ms-3x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101-caffe_fpn_ms-3x_coco.py new file mode 100644 index 00000000..a6855ee8 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101-caffe_fpn_ms-3x_coco.py @@ -0,0 +1,7 @@ +_base_ = './cascade-mask-rcnn_r50-caffe_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..c3d962c2 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_20e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_20e_coco.py new file mode 100644 index 00000000..497148f5 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_20e_coco.py @@ -0,0 +1,6 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_ms-3x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_ms-3x_coco.py new file mode 100644 index 00000000..183b5c50 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_ms-3x_coco.py @@ -0,0 +1,6 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..497f68c4 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = ['./cascade-mask-rcnn_r50_fpn_1x_coco.py'] + +model = dict( + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50-caffe_fpn_ms-3x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50-caffe_fpn_ms-3x_coco.py new file mode 100644 index 00000000..6677a9fe --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50-caffe_fpn_ms-3x_coco.py @@ -0,0 +1,18 @@ +_base_ = [ + '../common/ms_3x_coco-instance.py', + '../_base_/models/cascade-mask-rcnn_r50_fpn.py' +] + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..f59bb94e --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/cascade-mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_20e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_20e_coco.py new file mode 100644 index 00000000..35c8aa67 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_20e_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/cascade-mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_20e.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_ms-3x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_ms-3x_coco.py new file mode 100644 index 00000000..b15006f4 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_ms-3x_coco.py @@ -0,0 +1,4 @@ +_base_ = [ + '../common/ms_3x_coco-instance.py', + '../_base_/models/cascade-mask-rcnn_r50_fpn.py' +] diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..87a4cc32 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_20e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_20e_coco.py new file mode 100644 index 00000000..5e8dcaa6 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_20e_coco.py @@ -0,0 +1,14 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_ms-3x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_ms-3x_coco.py new file mode 100644 index 00000000..3a0f61b9 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_ms-3x_coco.py @@ -0,0 +1,14 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x8d_fpn_ms-3x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x8d_fpn_ms-3x_coco.py new file mode 100644 index 00000000..8cf08306 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-32x8d_fpn_ms-3x_coco.py @@ -0,0 +1,24 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_ms-3x_coco.py' + +model = dict( + # ResNeXt-101-32x8d model trained with Caffe2 at FB, + # so the mean and std need to be changed. + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[57.375, 57.120, 58.395], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=8, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + style='pytorch', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnext101_32x8d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..fb2e6b6b --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_20e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_20e_coco.py new file mode 100644 index 00000000..cc20c171 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_20e_coco.py @@ -0,0 +1,14 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_ms-3x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_ms-3x_coco.py new file mode 100644 index 00000000..f4ecc426 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_ms-3x_coco.py @@ -0,0 +1,14 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..b6eaee2d --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './cascade-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..1cdf5108 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './cascade-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101_fpn_20e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101_fpn_20e_coco.py new file mode 100644 index 00000000..84c285fc --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101_fpn_20e_coco.py @@ -0,0 +1,6 @@ +_base_ = './cascade-rcnn_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..1fc52e9c --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r101_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './cascade-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_r18_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r18_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..aa30a3d0 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r18_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './cascade-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..ad90e259 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = './cascade-rcnn_r50_fpn_1x_coco.py' + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + norm_cfg=dict(requires_grad=False), + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..1a07c8b2 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/cascade-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50_fpn_20e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50_fpn_20e_coco.py new file mode 100644 index 00000000..30f3ff10 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50_fpn_20e_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/cascade-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_20e.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..cd25f026 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,23 @@ +_base_ = [ + '../_base_/models/cascade-rcnn_r50_fpn.py', + '../common/lsj-200e_coco-detection.py' +] +image_size = (1024, 1024) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +# disable allowed_border to avoid potential errors. +model = dict( + data_preprocessor=dict(batch_augments=batch_augments), + train_cfg=dict(rpn=dict(allowed_border=-1))) + +train_dataloader = dict(batch_size=8, num_workers=4) +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + optimizer=dict( + type='SGD', lr=0.02 * 4, momentum=0.9, weight_decay=0.00004)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..50e0b954 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './cascade-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101-32x4d_fpn_20e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101-32x4d_fpn_20e_coco.py new file mode 100644 index 00000000..61201892 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101-32x4d_fpn_20e_coco.py @@ -0,0 +1,14 @@ +_base_ = './cascade-rcnn_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..29475e39 --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,15 @@ +_base_ = './cascade-rcnn_r50_fpn_1x_coco.py' +model = dict( + type='CascadeRCNN', + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101_64x4d_fpn_20e_coco.py b/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101_64x4d_fpn_20e_coco.py new file mode 100644 index 00000000..e2aa57ea --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/cascade-rcnn_x101_64x4d_fpn_20e_coco.py @@ -0,0 +1,15 @@ +_base_ = './cascade-rcnn_r50_fpn_20e_coco.py' +model = dict( + type='CascadeRCNN', + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/cascade_rcnn/metafile.yml b/mmdetection/configs/cascade_rcnn/metafile.yml new file mode 100644 index 00000000..7e0385da --- /dev/null +++ b/mmdetection/configs/cascade_rcnn/metafile.yml @@ -0,0 +1,545 @@ +Collections: + - Name: Cascade R-CNN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Cascade R-CNN + - FPN + - RPN + - ResNet + - RoIAlign + Paper: + URL: http://dx.doi.org/10.1109/tpami.2019.2956516 + Title: 'Cascade R-CNN: Delving into High Quality Object Detection' + README: configs/cascade_rcnn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/cascade_rcnn.py#L6 + Version: v2.0.0 + - Name: Cascade Mask R-CNN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Cascade R-CNN + - FPN + - RPN + - ResNet + - RoIAlign + Paper: + URL: http://dx.doi.org/10.1109/tpami.2019.2956516 + Title: 'Cascade R-CNN: Delving into High Quality Object Detection' + README: configs/cascade_rcnn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/cascade_rcnn.py#L6 + Version: v2.0.0 + +Models: + - Name: cascade-rcnn_r50-caffe_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.2 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_caffe_fpn_1x_coco/cascade_rcnn_r50_caffe_fpn_1x_coco_bbox_mAP-0.404_20200504_174853-b857be87.pth + + - Name: cascade-rcnn_r50_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.4 + inference time (ms/im): + - value: 62.11 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco/cascade_rcnn_r50_fpn_1x_coco_20200316-3dc56deb.pth + + - Name: cascade-rcnn_r50_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_r50_fpn_20e_coco.py + Metadata: + Training Memory (GB): 4.4 + inference time (ms/im): + - value: 62.11 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r50_fpn_20e_coco/cascade_rcnn_r50_fpn_20e_coco_bbox_mAP-0.41_20200504_175131-e9872a90.pth + + - Name: cascade-rcnn_r101-caffe_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_r101-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.2 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r101_caffe_fpn_1x_coco/cascade_rcnn_r101_caffe_fpn_1x_coco_bbox_mAP-0.423_20200504_175649-cab8dbd5.pth + + - Name: cascade-rcnn_r101_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.4 + inference time (ms/im): + - value: 74.07 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r101_fpn_1x_coco/cascade_rcnn_r101_fpn_1x_coco_20200317-0b6a2fbf.pth + + - Name: cascade-rcnn_r101_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_r101_fpn_20e_coco.py + Metadata: + Training Memory (GB): 6.4 + inference time (ms/im): + - value: 74.07 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_r101_fpn_20e_coco/cascade_rcnn_r101_fpn_20e_coco_bbox_mAP-0.425_20200504_231812-5057dcc5.pth + + - Name: cascade-rcnn_x101-32x4d_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.6 + inference time (ms/im): + - value: 91.74 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_32x4d_fpn_1x_coco/cascade_rcnn_x101_32x4d_fpn_1x_coco_20200316-95c2deb6.pth + + - Name: cascade-rcnn_x101-32x4d_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_x101-32x4d_fpn_20e_coco.py + Metadata: + Training Memory (GB): 7.6 + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_32x4d_fpn_20e_coco/cascade_rcnn_x101_32x4d_fpn_20e_coco_20200906_134608-9ae0a720.pth + + - Name: cascade-rcnn_x101-64x4d_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 10.7 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_64x4d_fpn_1x_coco/cascade_rcnn_x101_64x4d_fpn_1x_coco_20200515_075702-43ce6a30.pth + + - Name: cascade-rcnn_x101_64x4d_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-rcnn_x101_64x4d_fpn_20e_coco.py + Metadata: + Training Memory (GB): 10.7 + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_rcnn_x101_64x4d_fpn_20e_coco/cascade_rcnn_x101_64x4d_fpn_20e_coco_20200509_224357-051557b1.pth + + - Name: cascade-mask-rcnn_r50-caffe_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.9 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_caffe_fpn_1x_coco/cascade_mask_rcnn_r50_caffe_fpn_1x_coco_bbox_mAP-0.412__segm_mAP-0.36_20200504_174659-5004b251.pth + + - Name: cascade-mask-rcnn_r50_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.0 + inference time (ms/im): + - value: 89.29 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 35.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco/cascade_mask_rcnn_r50_fpn_1x_coco_20200203-9d4dcb24.pth + + - Name: cascade-mask-rcnn_r50_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_20e_coco.py + Metadata: + Training Memory (GB): 6.0 + inference time (ms/im): + - value: 89.29 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_20e_coco/cascade_mask_rcnn_r50_fpn_20e_coco_bbox_mAP-0.419__segm_mAP-0.365_20200504_174711-4af8e66e.pth + + - Name: cascade-mask-rcnn_r101-caffe_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r101-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.8 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_caffe_fpn_1x_coco/cascade_mask_rcnn_r101_caffe_fpn_1x_coco_bbox_mAP-0.432__segm_mAP-0.376_20200504_174813-5c1e9599.pth + + - Name: cascade-mask-rcnn_r101_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.9 + inference time (ms/im): + - value: 102.04 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_fpn_1x_coco/cascade_mask_rcnn_r101_fpn_1x_coco_20200203-befdf6ee.pth + + - Name: cascade-mask-rcnn_r101_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_20e_coco.py + Metadata: + Training Memory (GB): 7.9 + inference time (ms/im): + - value: 102.04 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_fpn_20e_coco/cascade_mask_rcnn_r101_fpn_20e_coco_bbox_mAP-0.434__segm_mAP-0.378_20200504_174836-005947da.pth + + - Name: cascade-mask-rcnn_x101-32x4d_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 9.2 + inference time (ms/im): + - value: 116.28 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x4d_fpn_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_1x_coco_20200201-0f411b1f.pth + + - Name: cascade-mask-rcnn_x101-32x4d_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_20e_coco.py + Metadata: + Training Memory (GB): 9.2 + inference time (ms/im): + - value: 116.28 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x4d_fpn_20e_coco/cascade_mask_rcnn_x101_32x4d_fpn_20e_coco_20200528_083917-ed1f4751.pth + + - Name: cascade-mask-rcnn_x101-64x4d_fpn_1x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 12.2 + inference time (ms/im): + - value: 149.25 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_64x4d_fpn_1x_coco/cascade_mask_rcnn_x101_64x4d_fpn_1x_coco_20200203-9a2db89d.pth + + - Name: cascade-mask-rcnn_x101-64x4d_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_20e_coco.py + Metadata: + Training Memory (GB): 12.2 + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_64x4d_fpn_20e_coco/cascade_mask_rcnn_x101_64x4d_fpn_20e_coco_20200512_161033-bdb5126a.pth + + - Name: cascade-mask-rcnn_r50-caffe_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r50-caffe_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 5.7 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_caffe_fpn_mstrain_3x_coco/cascade_mask_rcnn_r50_caffe_fpn_mstrain_3x_coco_20210707_002651-6e29b3a6.pth + + - Name: cascade-mask-rcnn_r50_fpn_mstrain_3x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r50_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 5.9 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_mstrain_3x_coco/cascade_mask_rcnn_r50_fpn_mstrain_3x_coco_20210628_164719-5bdc3824.pth + + - Name: cascade-mask-rcnn_r101-caffe_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r101-caffe_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 7.7 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_caffe_fpn_mstrain_3x_coco/cascade_mask_rcnn_r101_caffe_fpn_mstrain_3x_coco_20210707_002620-a5bd2389.pth + + - Name: cascade-mask-rcnn_r101_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_r101_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 7.8 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r101_fpn_mstrain_3x_coco/cascade_mask_rcnn_r101_fpn_mstrain_3x_coco_20210628_165236-51a2d363.pth + + - Name: cascade-mask-rcnn_x101-32x4d_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 9.0 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x4d_fpn_mstrain_3x_coco/cascade_mask_rcnn_x101_32x4d_fpn_mstrain_3x_coco_20210706_225234-40773067.pth + + - Name: cascade-mask-rcnn_x101-32x8d_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_x101-32x8d_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 12.1 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_32x8d_fpn_mstrain_3x_coco/cascade_mask_rcnn_x101_32x8d_fpn_mstrain_3x_coco_20210719_180640-9ff7e76f.pth + + - Name: cascade-mask-rcnn_x101-64x4d_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/cascade_rcnn/cascade-mask-rcnn_x101-64x4d_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 12.0 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_x101_64x4d_fpn_mstrain_3x_coco/cascade_mask_rcnn_x101_64x4d_fpn_mstrain_3x_coco_20210719_210311-d3e64ba0.pth diff --git a/mmdetection/configs/cascade_rpn/README.md b/mmdetection/configs/cascade_rpn/README.md new file mode 100644 index 00000000..868a25ed --- /dev/null +++ b/mmdetection/configs/cascade_rpn/README.md @@ -0,0 +1,41 @@ +# Cascade RPN + +> [Cascade RPN: Delving into High-Quality Region Proposal Network with Adaptive Convolution](https://arxiv.org/abs/1909.06720) + + + +## Abstract + +This paper considers an architecture referred to as Cascade Region Proposal Network (Cascade RPN) for improving the region-proposal quality and detection performance by systematically addressing the limitation of the conventional RPN that heuristically defines the anchors and aligns the features to the anchors. First, instead of using multiple anchors with predefined scales and aspect ratios, Cascade RPN relies on a single anchor per location and performs multi-stage refinement. Each stage is progressively more stringent in defining positive samples by starting out with an anchor-free metric followed by anchor-based metrics in the ensuing stages. Second, to attain alignment between the features and the anchors throughout the stages, adaptive convolution is proposed that takes the anchors in addition to the image features as its input and learns the sampled features guided by the anchors. A simple implementation of a two-stage Cascade RPN achieves AR 13.4 points higher than that of the conventional RPN, surpassing any existing region proposal methods. When adopting to Fast R-CNN and Faster R-CNN, Cascade RPN can improve the detection mAP by 3.1 and 3.5 points, respectively. + +
    + +
    + +## Results and Models + +### Region proposal performance + +| Method | Backbone | Style | Mem (GB) | Train time (s/iter) | Inf time (fps) | AR 1000 | Config | Download | +| :----: | :------: | :---: | :------: | :-----------------: | :------------: | :-----: | :----------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | +| CRPN | R-50-FPN | caffe | - | - | - | 72.0 | [config](./cascade-rpn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rpn/crpn_r50_caffe_fpn_1x_coco/cascade_rpn_r50_caffe_fpn_1x_coco-7aa93cef.pth) | + +### Detection performance + +| Method | Proposal | Backbone | Style | Schedule | Mem (GB) | Train time (s/iter) | Inf time (fps) | box AP | Config | Download | +| :----------: | :---------: | :------: | :---: | :------: | :------: | :-----------------: | :------------: | :----: | :----------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Fast R-CNN | Cascade RPN | R-50-FPN | caffe | 1x | - | - | - | 39.9 | [config](./cascade-rpn_fast-rcnn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rpn/crpn_fast_rcnn_r50_caffe_fpn_1x_coco/crpn_fast_rcnn_r50_caffe_fpn_1x_coco-cb486e66.pth) | +| Faster R-CNN | Cascade RPN | R-50-FPN | caffe | 1x | - | - | - | 40.4 | [config](./cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cascade_rpn/crpn_faster_rcnn_r50_caffe_fpn_1x_coco/crpn_faster_rcnn_r50_caffe_fpn_1x_coco-c8283cca.pth) | + +## Citation + +We provide the code for reproducing experiment results of [Cascade RPN](https://arxiv.org/abs/1909.06720). + +```latex +@inproceedings{vu2019cascade, + title={Cascade RPN: Delving into High-Quality Region Proposal Network with Adaptive Convolution}, + author={Vu, Thang and Jang, Hyunjun and Pham, Trung X and Yoo, Chang D}, + booktitle={Conference on Neural Information Processing Systems (NeurIPS)}, + year={2019} +} +``` diff --git a/mmdetection/configs/cascade_rpn/cascade-rpn_fast-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/cascade_rpn/cascade-rpn_fast-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..ba23ce90 --- /dev/null +++ b/mmdetection/configs/cascade_rpn/cascade-rpn_fast-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,27 @@ +_base_ = '../fast_rcnn/fast-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + roi_head=dict( + bbox_head=dict( + bbox_coder=dict(target_stds=[0.04, 0.04, 0.08, 0.08]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.5), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rcnn=dict( + assigner=dict( + pos_iou_thr=0.65, neg_iou_thr=0.65, min_pos_iou=0.65), + sampler=dict(num=256))), + test_cfg=dict(rcnn=dict(score_thr=1e-3))) + +# MMEngine support the following two ways, users can choose +# according to convenience +# train_dataloader = dict(dataset=dict(proposal_file='proposals/crpn_r50_caffe_fpn_1x_train2017.pkl')) # noqa +_base_.train_dataloader.dataset.proposal_file = 'proposals/crpn_r50_caffe_fpn_1x_train2017.pkl' # noqa + +# val_dataloader = dict(dataset=dict(proposal_file='proposals/crpn_r50_caffe_fpn_1x_val2017.pkl')) # noqa +# test_dataloader = val_dataloader +_base_.val_dataloader.dataset.proposal_file = 'proposals/crpn_r50_caffe_fpn_1x_val2017.pkl' # noqa +test_dataloader = _base_.val_dataloader + +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..2f7eced0 --- /dev/null +++ b/mmdetection/configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,89 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50-caffe_fpn_1x_coco.py' +rpn_weight = 0.7 +model = dict( + rpn_head=dict( + _delete_=True, + type='CascadeRPNHead', + num_stages=2, + stages=[ + dict( + type='StageCascadeRPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[1.0], + strides=[4, 8, 16, 32, 64]), + adapt_cfg=dict(type='dilation', dilation=3), + bridged_feature=True, + with_cls=False, + reg_decoded_bbox=True, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=(.0, .0, .0, .0), + target_stds=(0.1, 0.1, 0.5, 0.5)), + loss_bbox=dict( + type='IoULoss', linear=True, + loss_weight=10.0 * rpn_weight)), + dict( + type='StageCascadeRPNHead', + in_channels=256, + feat_channels=256, + adapt_cfg=dict(type='offset'), + bridged_feature=False, + with_cls=True, + reg_decoded_bbox=True, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=(.0, .0, .0, .0), + target_stds=(0.05, 0.05, 0.1, 0.1)), + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0 * rpn_weight), + loss_bbox=dict( + type='IoULoss', linear=True, + loss_weight=10.0 * rpn_weight)) + ]), + roi_head=dict( + bbox_head=dict( + bbox_coder=dict(target_stds=[0.04, 0.04, 0.08, 0.08]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.5), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=[ + dict( + assigner=dict( + type='RegionAssigner', center_ratio=0.2, ignore_ratio=0.5), + allowed_border=-1, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.3, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False) + ], + rpn_proposal=dict(max_per_img=300, nms=dict(iou_threshold=0.8)), + rcnn=dict( + assigner=dict( + pos_iou_thr=0.65, neg_iou_thr=0.65, min_pos_iou=0.65), + sampler=dict(type='RandomSampler', num=256))), + test_cfg=dict( + rpn=dict(max_per_img=300, nms=dict(iou_threshold=0.8)), + rcnn=dict(score_thr=1e-3))) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/cascade_rpn/cascade-rpn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/cascade_rpn/cascade-rpn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..6eba24d1 --- /dev/null +++ b/mmdetection/configs/cascade_rpn/cascade-rpn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,76 @@ +_base_ = '../rpn/rpn_r50-caffe_fpn_1x_coco.py' +model = dict( + rpn_head=dict( + _delete_=True, + type='CascadeRPNHead', + num_stages=2, + stages=[ + dict( + type='StageCascadeRPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[1.0], + strides=[4, 8, 16, 32, 64]), + adapt_cfg=dict(type='dilation', dilation=3), + bridged_feature=True, + sampling=False, + with_cls=False, + reg_decoded_bbox=True, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=(.0, .0, .0, .0), + target_stds=(0.1, 0.1, 0.5, 0.5)), + loss_bbox=dict(type='IoULoss', linear=True, loss_weight=10.0)), + dict( + type='StageCascadeRPNHead', + in_channels=256, + feat_channels=256, + adapt_cfg=dict(type='offset'), + bridged_feature=False, + sampling=True, + with_cls=True, + reg_decoded_bbox=True, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=(.0, .0, .0, .0), + target_stds=(0.05, 0.05, 0.1, 0.1)), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', linear=True, loss_weight=10.0)) + ]), + train_cfg=dict(rpn=[ + dict( + assigner=dict( + type='RegionAssigner', center_ratio=0.2, ignore_ratio=0.5), + allowed_border=-1, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.3, + ignore_iof_thr=-1, + iou_calculator=dict(type='BboxOverlaps2D')), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.8), + min_bbox_size=0))) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/cascade_rpn/metafile.yml b/mmdetection/configs/cascade_rpn/metafile.yml new file mode 100644 index 00000000..62a88c5d --- /dev/null +++ b/mmdetection/configs/cascade_rpn/metafile.yml @@ -0,0 +1,44 @@ +Collections: + - Name: Cascade RPN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Cascade RPN + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1909.06720 + Title: 'Cascade RPN: Delving into High-Quality Region Proposal Network with Adaptive Convolution' + README: configs/cascade_rpn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.8.0/mmdet/models/dense_heads/cascade_rpn_head.py#L538 + Version: v2.8.0 + +Models: + - Name: cascade-rpn_fast-rcnn_r50-caffe_fpn_1x_coco + In Collection: Cascade RPN + Config: configs/cascade_rpn/cascade-rpn_fast-rcnn_r50-caffe_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rpn/crpn_fast_rcnn_r50_caffe_fpn_1x_coco/crpn_fast_rcnn_r50_caffe_fpn_1x_coco-cb486e66.pth + + - Name: cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco + In Collection: Cascade RPN + Config: configs/cascade_rpn/cascade-rpn_faster-rcnn_r50-caffe_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cascade_rpn/crpn_faster_rcnn_r50_caffe_fpn_1x_coco/crpn_faster_rcnn_r50_caffe_fpn_1x_coco-c8283cca.pth diff --git a/mmdetection/configs/centernet/README.md b/mmdetection/configs/centernet/README.md new file mode 100644 index 00000000..81e229c6 --- /dev/null +++ b/mmdetection/configs/centernet/README.md @@ -0,0 +1,58 @@ +# CenterNet + +> [Objects as Points](https://arxiv.org/abs/1904.07850) + + + +## Abstract + +Detection identifies objects as axis-aligned boxes in an image. Most successful object detectors enumerate a nearly exhaustive list of potential object locations and classify each. This is wasteful, inefficient, and requires additional post-processing. In this paper, we take a different approach. We model an object as a single point --- the center point of its bounding box. Our detector uses keypoint estimation to find center points and regresses to all other object properties, such as size, 3D location, orientation, and even pose. Our center point based approach, CenterNet, is end-to-end differentiable, simpler, faster, and more accurate than corresponding bounding box based detectors. CenterNet achieves the best speed-accuracy trade-off on the MS COCO dataset, with 28.1% AP at 142 FPS, 37.4% AP at 52 FPS, and 45.1% AP with multi-scale testing at 1.4 FPS. We use the same approach to estimate 3D bounding box in the KITTI benchmark and human pose on the COCO keypoint dataset. Our method performs competitively with sophisticated multi-stage methods and runs in real-time. + +
    + +
    + +## Results and Models + +| Backbone | DCN | Mem (GB) | Box AP | Flip box AP | Config | Download | +| :-------: | :-: | :------: | :----: | :---------: | :--------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ResNet-18 | N | 3.45 | 25.9 | 27.3 | [config](./centernet_r18_8xb16-crop512-140e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/centernet/centernet_resnet18_140e_coco/centernet_resnet18_140e_coco_20210705_093630-bb5b3bf7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/centernet/centernet_resnet18_140e_coco/centernet_resnet18_140e_coco_20210705_093630.log.json) | +| ResNet-18 | Y | 3.47 | 29.5 | 30.9 | [config](./centernet_r18-dcnv2_8xb16-crop512-140e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/centernet/centernet_resnet18_dcnv2_140e_coco/centernet_resnet18_dcnv2_140e_coco_20210702_155131-c8cd631f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/centernet/centernet_resnet18_dcnv2_140e_coco/centernet_resnet18_dcnv2_140e_coco_20210702_155131.log.json) | + +Note: + +- Flip box AP setting is single-scale and `flip=True`. +- Due to complex data enhancement, we find that the performance is unstable and may fluctuate by about 0.4 mAP. mAP 29.4 ~ 29.8 is acceptable in ResNet-18-DCNv2. +- Compared to the source code, we refer to [CenterNet-Better](https://github.com/FateScript/CenterNet-better), and make the following changes + - fix wrong image mean and variance in image normalization to be compatible with the pre-trained backbone. + - Use SGD rather than ADAM optimizer and add warmup and grad clip. + - Use DistributedDataParallel as other models in MMDetection rather than using DataParallel. + +## CenterNet Update + +| Backbone | Style | Lr schd | MS train | Mem (GB) | Box AP | Config | Download | +| :-------: | :---: | :-----: | :------: | :------: | :----: | :------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ResNet-50 | caffe | 1x | True | 3.3 | 40.2 | [config](./centernet-update_r50-caffe_fpn_ms-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco/centernet-update_r50-caffe_fpn_ms-1x_coco_20230512_203845-8306baf2.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco/centernet-update_r50-caffe_fpn_ms-1x_coco_20230512_203845.log.json) | + +CenterNet Update from the paper of [Probabilistic two-stage detection](https://arxiv.org/abs/2103.07461). The author has updated CenterNet to greatly improve performance and convergence speed. +The [Details](https://github.com/xingyizhou/CenterNet2/blob/master/docs/MODEL_ZOO.md) are as follows: + +- Using top-left-right-bottom box encoding and GIoU Loss +- Adding regression loss to the center 3x3 region +- Adding more positive pixels for the heatmap loss whose regression loss is small and is within the center3x3 region +- Using RetinaNet-style optimizer (SGD), learning rate rule (0.01 for each batch size 16), and schedule (12 epochs) +- Added FPN neck layers, and assigns objects to FPN levels based on a fixed size range. +- Using standard NMS instead of max pooling + +Note: We found that the performance of the r50 model fluctuates greatly and sometimes it does not converge. If the model does not converge, you can try running it again or reduce the learning rate. + +## Citation + +```latex +@article{zhou2019objects, + title={Objects as Points}, + author={Zhou, Xingyi and Wang, Dequan and Kr{\"a}henb{\"u}hl, Philipp}, + booktitle={arXiv preprint arXiv:1904.07850}, + year={2019} +} +``` diff --git a/mmdetection/configs/centernet/centernet-update_r101_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/centernet/centernet-update_r101_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..4fc65e0f --- /dev/null +++ b/mmdetection/configs/centernet/centernet-update_r101_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './centernet-update_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/centernet/centernet-update_r18_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/centernet/centernet-update_r18_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..ab3ae32e --- /dev/null +++ b/mmdetection/configs/centernet/centernet-update_r18_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './centernet-update_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco.py b/mmdetection/configs/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco.py new file mode 100644 index 00000000..1f6e2b39 --- /dev/null +++ b/mmdetection/configs/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco.py @@ -0,0 +1,105 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + type='CenterNet', + # use caffe img_norm + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5, + # There is a chance to get 40.3 after switching init_cfg, + # otherwise it is about 39.9~40.1 + init_cfg=dict(type='Caffe2Xavier', layer='Conv2d'), + relu_before_extra_convs=True), + bbox_head=dict( + type='CenterNetUpdateHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + hm_min_radius=4, + hm_min_overlap=0.8, + more_pos_thresh=0.2, + more_pos_topk=9, + soft_weight_on_reg=False, + loss_cls=dict( + type='GaussianFocalLoss', + pos_weight=0.25, + neg_weight=0.75, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + ), + train_cfg=None, + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# single-scale training is about 39.3 +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.00025, + by_epoch=False, + begin=0, + end=4000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +optim_wrapper = dict( + optimizer=dict(lr=0.01), + # Experiments show that there is no need to turn on clip_grad. + paramwise_cfg=dict(norm_decay_mult=0.)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/centernet/centernet-update_r50_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/centernet/centernet-update_r50_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..34e0c680 --- /dev/null +++ b/mmdetection/configs/centernet/centernet-update_r50_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,83 @@ +_base_ = '../common/lsj-200e_coco-detection.py' + +image_size = (1024, 1024) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +model = dict( + type='CenterNet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5, + init_cfg=dict(type='Caffe2Xavier', layer='Conv2d'), + relu_before_extra_convs=True), + bbox_head=dict( + type='CenterNetUpdateHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_cls=dict( + type='GaussianFocalLoss', + pos_weight=0.25, + neg_weight=0.75, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + ), + train_cfg=None, + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +train_dataloader = dict(batch_size=8, num_workers=4) +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + optimizer=dict( + type='SGD', lr=0.01 * 4, momentum=0.9, weight_decay=0.00004), + paramwise_cfg=dict(norm_decay_mult=0.)) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.00025, + by_epoch=False, + begin=0, + end=4000), + dict( + type='MultiStepLR', + begin=0, + end=25, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py b/mmdetection/configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py new file mode 100644 index 00000000..732a55d5 --- /dev/null +++ b/mmdetection/configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py @@ -0,0 +1,136 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py', + './centernet_tta.py' +] + +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# model settings +model = dict( + type='CenterNet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True), + backbone=dict( + type='ResNet', + depth=18, + norm_eval=False, + norm_cfg=dict(type='BN'), + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict( + type='CTResNetNeck', + in_channels=512, + num_deconv_filters=(256, 128, 64), + num_deconv_kernels=(4, 4, 4), + use_dcn=True), + bbox_head=dict( + type='CenterNetHead', + num_classes=80, + in_channels=64, + feat_channels=64, + loss_center_heatmap=dict(type='GaussianFocalLoss', loss_weight=1.0), + loss_wh=dict(type='L1Loss', loss_weight=0.1), + loss_offset=dict(type='L1Loss', loss_weight=1.0)), + train_cfg=None, + test_cfg=dict(topk=100, local_maximum_kernel=3, max_per_img=100)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict( + type='RandomCenterCropPad', + # The cropped images are padded into squares during training, + # but may be less than crop_size. + crop_size=(512, 512), + ratios=(0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3), + mean=[0, 0, 0], + std=[1, 1, 1], + to_rgb=True, + test_pad_mode=None), + # Make sure the output is always crop_size. + dict(type='Resize', scale=(512, 512), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args={{_base_.backend_args}}, + to_float32=True), + # don't need Resize + dict( + type='RandomCenterCropPad', + ratios=None, + border=None, + mean=[0, 0, 0], + std=[1, 1, 1], + to_rgb=True, + test_mode=True, + test_pad_mode=['logical_or', 31], + test_pad_add_pix=1), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'border')) +] + +# Use RepeatDataset to speed up training +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + _delete_=True, + type='RepeatDataset', + times=5, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args={{_base_.backend_args}}, + ))) + +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# optimizer +# Based on the default settings of modern detectors, the SGD effect is better +# than the Adam in the source code, so we use SGD default settings and +# if you use adam+lr5e-4, the map is 29.1. +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) + +max_epochs = 28 +# learning policy +# Based on the default settings of modern detectors, we added warmup settings. +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[18, 24], # the real step is [18*5, 24*5] + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) # the real epoch is 28*5=140 + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (16 samples per GPU) +auto_scale_lr = dict(base_batch_size=128) diff --git a/mmdetection/configs/centernet/centernet_r18_8xb16-crop512-140e_coco.py b/mmdetection/configs/centernet/centernet_r18_8xb16-crop512-140e_coco.py new file mode 100644 index 00000000..6094b642 --- /dev/null +++ b/mmdetection/configs/centernet/centernet_r18_8xb16-crop512-140e_coco.py @@ -0,0 +1,3 @@ +_base_ = './centernet_r18-dcnv2_8xb16-crop512-140e_coco.py' + +model = dict(neck=dict(use_dcn=False)) diff --git a/mmdetection/configs/centernet/centernet_tta.py b/mmdetection/configs/centernet/centernet_tta.py new file mode 100644 index 00000000..edd7b03e --- /dev/null +++ b/mmdetection/configs/centernet/centernet_tta.py @@ -0,0 +1,39 @@ +# This is different from the TTA of official CenterNet. + +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict(type='nms', iou_threshold=0.5), max_per_img=100)) + +tta_pipeline = [ + dict(type='LoadImageFromFile', to_float32=True, backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + # ``RandomFlip`` must be placed before ``RandomCenterCropPad``, + # otherwise bounding box coordinates after flipping cannot be + # recovered correctly. + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], + [ + dict( + type='RandomCenterCropPad', + ratios=None, + border=None, + mean=[0, 0, 0], + std=[1, 1, 1], + to_rgb=True, + test_mode=True, + test_pad_mode=['logical_or', 31], + test_pad_add_pix=1), + ], + [dict(type='LoadAnnotations', with_bbox=True)], + [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'flip', 'flip_direction', 'border')) + ] + ]) +] diff --git a/mmdetection/configs/centernet/metafile.yml b/mmdetection/configs/centernet/metafile.yml new file mode 100644 index 00000000..496b8ea2 --- /dev/null +++ b/mmdetection/configs/centernet/metafile.yml @@ -0,0 +1,60 @@ +Collections: + - Name: CenterNet + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x TITANXP GPUs + Architecture: + - ResNet + Paper: + URL: https://arxiv.org/abs/1904.07850 + Title: 'Objects as Points' + README: configs/centernet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.13.0/mmdet/models/detectors/centernet.py#L10 + Version: v2.13.0 + +Models: + - Name: centernet_r18-dcnv2_8xb16-crop512-140e_coco + In Collection: CenterNet + Config: configs/centernet/centernet_r18-dcnv2_8xb16-crop512-140e_coco.py + Metadata: + Batch Size: 128 + Training Memory (GB): 3.47 + Epochs: 140 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 29.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/centernet/centernet_resnet18_dcnv2_140e_coco/centernet_resnet18_dcnv2_140e_coco_20210702_155131-c8cd631f.pth + + - Name: centernet_r18_8xb16-crop512-140e_coco + In Collection: CenterNet + Config: configs/centernet/centernet_r18_8xb16-crop512-140e_coco.py + Metadata: + Batch Size: 128 + Training Memory (GB): 3.45 + Epochs: 140 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 25.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/centernet/centernet_resnet18_140e_coco/centernet_resnet18_140e_coco_20210705_093630-bb5b3bf7.pth + + - Name: centernet-update_r50-caffe_fpn_ms-1x_coco + In Collection: CenterNet + Config: configs/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco.py + Metadata: + Batch Size: 16 + Training Memory (GB): 3.3 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.2 + Weights: https://download.openmmlab.com/mmdetection/v3.0/centernet/centernet-update_r50-caffe_fpn_ms-1x_coco/centernet-update_r50-caffe_fpn_ms-1x_coco_20230512_203845-8306baf2.pth diff --git a/mmdetection/configs/centripetalnet/README.md b/mmdetection/configs/centripetalnet/README.md new file mode 100644 index 00000000..21edbd26 --- /dev/null +++ b/mmdetection/configs/centripetalnet/README.md @@ -0,0 +1,36 @@ +# CentripetalNet + +> [CentripetalNet: Pursuing High-quality Keypoint Pairs for Object Detection](https://arxiv.org/abs/2003.09119) + + + +## Abstract + +Keypoint-based detectors have achieved pretty-well performance. However, incorrect keypoint matching is still widespread and greatly affects the performance of the detector. In this paper, we propose CentripetalNet which uses centripetal shift to pair corner keypoints from the same instance. CentripetalNet predicts the position and the centripetal shift of the corner points and matches corners whose shifted results are aligned. Combining position information, our approach matches corner points more accurately than the conventional embedding approaches do. Corner pooling extracts information inside the bounding boxes onto the border. To make this information more aware at the corners, we design a cross-star deformable convolution network to conduct feature adaption. Furthermore, we explore instance segmentation on anchor-free detectors by equipping our CentripetalNet with a mask prediction module. On MS-COCO test-dev, our CentripetalNet not only outperforms all existing anchor-free detectors with an AP of 48.0% but also achieves comparable performance to the state-of-the-art instance segmentation approaches with a 40.2% MaskAP. + +
    + +
    + +## Results and Models + +| Backbone | Batch Size | Step/Total Epochs | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :--------------: | :-----------------------------------------------------------------------: | :---------------: | :------: | :------------: | :----: | :-----------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| HourglassNet-104 | [16 x 6](./centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py) | 190/210 | 16.7 | 3.7 | 44.8 | [config](./centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/centripetalnet/centripetalnet_hourglass104_mstest_16x6_210e_coco/centripetalnet_hourglass104_mstest_16x6_210e_coco_20200915_204804-3ccc61e5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/centripetalnet/centripetalnet_hourglass104_mstest_16x6_210e_coco/centripetalnet_hourglass104_mstest_16x6_210e_coco_20200915_204804.log.json) | + +Note: + +- TTA setting is single-scale and `flip=True`. If you want to reproduce the TTA performance, please add `--tta` in the test command. +- The model we released is the best checkpoint rather than the latest checkpoint (box AP 44.8 vs 44.6 in our experiment). + +## Citation + +```latex +@InProceedings{Dong_2020_CVPR, +author = {Dong, Zhiwei and Li, Guoxuan and Liao, Yue and Wang, Fei and Ren, Pengju and Qian, Chen}, +title = {CentripetalNet: Pursuing High-Quality Keypoint Pairs for Object Detection}, +booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, +month = {June}, +year = {2020} +} +``` diff --git a/mmdetection/configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py b/mmdetection/configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py new file mode 100644 index 00000000..b757ffd1 --- /dev/null +++ b/mmdetection/configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py @@ -0,0 +1,181 @@ +_base_ = [ + '../_base_/default_runtime.py', '../_base_/datasets/coco_detection.py' +] + +data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True) + +# model settings +model = dict( + type='CornerNet', + data_preprocessor=data_preprocessor, + backbone=dict( + type='HourglassNet', + downsample_times=5, + num_stacks=2, + stage_channels=[256, 256, 384, 384, 384, 512], + stage_blocks=[2, 2, 2, 2, 2, 4], + norm_cfg=dict(type='BN', requires_grad=True)), + neck=None, + bbox_head=dict( + type='CentripetalHead', + num_classes=80, + in_channels=256, + num_feat_levels=2, + corner_emb_channels=0, + loss_heatmap=dict( + type='GaussianFocalLoss', alpha=2.0, gamma=4.0, loss_weight=1), + loss_offset=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1), + loss_guiding_shift=dict( + type='SmoothL1Loss', beta=1.0, loss_weight=0.05), + loss_centripetal_shift=dict( + type='SmoothL1Loss', beta=1.0, loss_weight=1)), + # training and testing settings + train_cfg=None, + test_cfg=dict( + corner_topk=100, + local_maximum_kernel=3, + distance_threshold=0.5, + score_thr=0.05, + max_per_img=100, + nms=dict(type='soft_nms', iou_threshold=0.5, method='gaussian'))) + +# data settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict( + # The cropped images are padded into squares during training, + # but may be smaller than crop_size. + type='RandomCenterCropPad', + crop_size=(511, 511), + ratios=(0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3), + test_mode=False, + test_pad_mode=None, + mean=data_preprocessor['mean'], + std=data_preprocessor['std'], + # Image data is not converted to rgb. + to_rgb=data_preprocessor['bgr_to_rgb']), + dict(type='Resize', scale=(511, 511), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs'), +] + +test_pipeline = [ + dict( + type='LoadImageFromFile', + to_float32=True, + backend_args=_base_.backend_args), + # don't need Resize + dict( + type='RandomCenterCropPad', + crop_size=None, + ratios=None, + border=None, + test_mode=True, + test_pad_mode=['logical_or', 127], + mean=data_preprocessor['mean'], + std=data_preprocessor['std'], + # Image data is not converted to rgb. + to_rgb=data_preprocessor['bgr_to_rgb']), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'border')) +] + +train_dataloader = dict( + batch_size=6, + num_workers=3, + batch_sampler=None, + dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='Adam', lr=0.0005), + clip_grad=dict(max_norm=35, norm_type=2)) + +max_epochs = 210 + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 3, + by_epoch=False, + begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[190], + gamma=0.1) +] + +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (16 GPUs) x (6 samples per GPU) +auto_scale_lr = dict(base_batch_size=96) + +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict( + nms=dict(type='soft_nms', iou_threshold=0.5, method='gaussian'), + max_per_img=100)) + +tta_pipeline = [ + dict( + type='LoadImageFromFile', + to_float32=True, + backend_args=_base_.backend_args), + dict( + type='TestTimeAug', + transforms=[ + [ + # ``RandomFlip`` must be placed before ``RandomCenterCropPad``, + # otherwise bounding box coordinates after flipping cannot be + # recovered correctly. + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], + [ + dict( + type='RandomCenterCropPad', + crop_size=None, + ratios=None, + border=None, + test_mode=True, + test_pad_mode=['logical_or', 127], + mean=data_preprocessor['mean'], + std=data_preprocessor['std'], + # Image data is not converted to rgb. + to_rgb=data_preprocessor['bgr_to_rgb']) + ], + [dict(type='LoadAnnotations', with_bbox=True)], + [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'flip', 'flip_direction', 'border')) + ] + ]) +] diff --git a/mmdetection/configs/centripetalnet/metafile.yml b/mmdetection/configs/centripetalnet/metafile.yml new file mode 100644 index 00000000..526572df --- /dev/null +++ b/mmdetection/configs/centripetalnet/metafile.yml @@ -0,0 +1,39 @@ +Collections: + - Name: CentripetalNet + Metadata: + Training Data: COCO + Training Techniques: + - Adam + Training Resources: 16x V100 GPUs + Architecture: + - Corner Pooling + - Stacked Hourglass Network + Paper: + URL: https://arxiv.org/abs/2003.09119 + Title: 'CentripetalNet: Pursuing High-quality Keypoint Pairs for Object Detection' + README: configs/centripetalnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.5.0/mmdet/models/detectors/cornernet.py#L9 + Version: v2.5.0 + +Models: + - Name: centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco + In Collection: CentripetalNet + Config: configs/centripetalnet/centripetalnet_hourglass104_16xb6-crop511-210e-mstest_coco.py + Metadata: + Batch Size: 96 + Training Memory (GB): 16.7 + inference time (ms/im): + - value: 270.27 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 210 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/centripetalnet/centripetalnet_hourglass104_mstest_16x6_210e_coco/centripetalnet_hourglass104_mstest_16x6_210e_coco_20200915_204804-3ccc61e5.pth diff --git a/mmdetection/configs/cityscapes/README.md b/mmdetection/configs/cityscapes/README.md new file mode 100644 index 00000000..9e37b64e --- /dev/null +++ b/mmdetection/configs/cityscapes/README.md @@ -0,0 +1,46 @@ +# Cityscapes + +> [The Cityscapes Dataset for Semantic Urban Scene Understanding](https://arxiv.org/abs/1604.01685) + + + +## Abstract + +Visual understanding of complex urban street scenes is an enabling factor for a wide range of applications. Object detection has benefited enormously from large-scale datasets, especially in the context of deep learning. For semantic urban scene understanding, however, no current dataset adequately captures the complexity of real-world urban scenes. +To address this, we introduce Cityscapes, a benchmark suite and large-scale dataset to train and test approaches for pixel-level and instance-level semantic labeling. Cityscapes is comprised of a large, diverse set of stereo video sequences recorded in streets from 50 different cities. 5000 of these images have high quality pixel-level annotations; 20000 additional images have coarse annotations to enable methods that leverage large volumes of weakly-labeled data. Crucially, our effort exceeds previous attempts in terms of dataset size, annotation richness, scene variability, and complexity. Our accompanying empirical study provides an in-depth analysis of the dataset characteristics, as well as a performance evaluation of several state-of-the-art approaches based on our benchmark. + +
    + +
    + +## Common settings + +- All baselines were trained using 8 GPU with a batch size of 8 (1 images per GPU) using the [linear scaling rule](https://arxiv.org/abs/1706.02677) to scale the learning rate. +- All models were trained on `cityscapes_train`, and tested on `cityscapes_val`. +- 1x training schedule indicates 64 epochs which corresponds to slightly less than the 24k iterations reported in the original schedule from the [Mask R-CNN paper](https://arxiv.org/abs/1703.06870) +- COCO pre-trained weights are used to initialize. +- A conversion [script](../../tools/dataset_converters/cityscapes.py) is provided to convert Cityscapes into COCO format. Please refer to [install.md](../../docs/1_exist_data_model.md#prepare-datasets) for details. +- `CityscapesDataset` implemented three evaluation methods. `bbox` and `segm` are standard COCO bbox/mask AP. `cityscapes` is the cityscapes dataset official evaluation, which may be slightly higher than COCO. + +### Faster R-CNN + +| Backbone | Style | Lr schd | Scale | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :-----: | :-----: | :------: | :------: | :------------: | :----: | :----------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 1x | 800-1024 | 5.2 | - | 40.3 | [config](./faster-rcnn_r50_fpn_1x_cityscapes.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cityscapes/faster_rcnn_r50_fpn_1x_cityscapes_20200502-829424c0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cityscapes/faster_rcnn_r50_fpn_1x_cityscapes_20200502_114915.log.json) | + +### Mask R-CNN + +| Backbone | Style | Lr schd | Scale | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :------: | :-----: | :-----: | :------: | :------: | :------------: | :----: | :-----: | :--------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 1x | 800-1024 | 5.3 | - | 40.9 | 36.4 | [config](./mask-rcnn_r50_fpn_1x_cityscapes.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cityscapes/mask_rcnn_r50_fpn_1x_cityscapes/mask_rcnn_r50_fpn_1x_cityscapes_20201211_133733-d2858245.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cityscapes/mask_rcnn_r50_fpn_1x_cityscapes/mask_rcnn_r50_fpn_1x_cityscapes_20201211_133733.log.json) | + +## Citation + +```latex +@inproceedings{Cordts2016Cityscapes, + title={The Cityscapes Dataset for Semantic Urban Scene Understanding}, + author={Cordts, Marius and Omran, Mohamed and Ramos, Sebastian and Rehfeld, Timo and Enzweiler, Markus and Benenson, Rodrigo and Franke, Uwe and Roth, Stefan and Schiele, Bernt}, + booktitle={Proc. of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, + year={2016} +} +``` diff --git a/mmdetection/configs/cityscapes/faster-rcnn_r50_fpn_1x_cityscapes.py b/mmdetection/configs/cityscapes/faster-rcnn_r50_fpn_1x_cityscapes.py new file mode 100644 index 00000000..ccd0de2a --- /dev/null +++ b/mmdetection/configs/cityscapes/faster-rcnn_r50_fpn_1x_cityscapes.py @@ -0,0 +1,41 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/cityscapes_detection.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_1x.py' +] +model = dict( + backbone=dict(init_cfg=None), + roi_head=dict( + bbox_head=dict( + num_classes=8, + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)))) + +# optimizer +# lr is set for a batch size of 8 +optim_wrapper = dict(optimizer=dict(lr=0.01)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=8, + by_epoch=True, + # [7] yields higher performance than [6] + milestones=[7], + gamma=0.1) +] + +# actual epoch = 8 * 8 = 64 +train_cfg = dict(max_epochs=8) + +# For better, more stable performance initialize from COCO +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth' # noqa + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (1 samples per GPU) +# TODO: support auto scaling lr +# auto_scale_lr = dict(base_batch_size=8) diff --git a/mmdetection/configs/cityscapes/mask-rcnn_r50_fpn_1x_cityscapes.py b/mmdetection/configs/cityscapes/mask-rcnn_r50_fpn_1x_cityscapes.py new file mode 100644 index 00000000..772268b1 --- /dev/null +++ b/mmdetection/configs/cityscapes/mask-rcnn_r50_fpn_1x_cityscapes.py @@ -0,0 +1,43 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/cityscapes_instance.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_1x.py' +] +model = dict( + backbone=dict(init_cfg=None), + roi_head=dict( + bbox_head=dict( + type='Shared2FCBBoxHead', + num_classes=8, + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), + mask_head=dict(num_classes=8))) + +# optimizer +# lr is set for a batch size of 8 +optim_wrapper = dict(optimizer=dict(lr=0.01)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=8, + by_epoch=True, + # [7] yields higher performance than [6] + milestones=[7], + gamma=0.1) +] + +# actual epoch = 8 * 8 = 64 +train_cfg = dict(max_epochs=8) + +# For better, more stable performance initialize from COCO +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth' # noqa + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (1 samples per GPU) +# TODO: support auto scaling lr +# auto_scale_lr = dict(base_batch_size=8) diff --git a/mmdetection/configs/common/lsj-100e_coco-detection.py b/mmdetection/configs/common/lsj-100e_coco-detection.py new file mode 100644 index 00000000..bb631e5d --- /dev/null +++ b/mmdetection/configs/common/lsj-100e_coco-detection.py @@ -0,0 +1,122 @@ +_base_ = '../_base_/default_runtime.py' +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' +image_size = (1024, 1024) + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +# Use RepeatDataset to speed up training +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + type='RepeatDataset', + times=4, # simply change this from 2 to 16 for 50e - 400e training. + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +max_epochs = 25 + +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=5) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# optimizer assumes bs=64 +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.00004)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.067, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] + +# only keep latest 2 checkpoints +default_hooks = dict(checkpoint=dict(max_keep_ckpts=2)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/common/lsj-100e_coco-instance.py b/mmdetection/configs/common/lsj-100e_coco-instance.py new file mode 100644 index 00000000..6e62729d --- /dev/null +++ b/mmdetection/configs/common/lsj-100e_coco-instance.py @@ -0,0 +1,122 @@ +_base_ = '../_base_/default_runtime.py' +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' +image_size = (1024, 1024) + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +# Use RepeatDataset to speed up training +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + type='RepeatDataset', + times=4, # simply change this from 2 to 16 for 50e - 400e training. + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +max_epochs = 25 + +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=5) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# optimizer assumes bs=64 +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.00004)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.067, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] + +# only keep latest 2 checkpoints +default_hooks = dict(checkpoint=dict(max_keep_ckpts=2)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/common/lsj-200e_coco-detection.py b/mmdetection/configs/common/lsj-200e_coco-detection.py new file mode 100644 index 00000000..83d12947 --- /dev/null +++ b/mmdetection/configs/common/lsj-200e_coco-detection.py @@ -0,0 +1,18 @@ +_base_ = './lsj-100e_coco-detection.py' + +# 8x25=200e +train_dataloader = dict(dataset=dict(times=8)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.067, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=25, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] diff --git a/mmdetection/configs/common/lsj-200e_coco-instance.py b/mmdetection/configs/common/lsj-200e_coco-instance.py new file mode 100644 index 00000000..af3e4bf1 --- /dev/null +++ b/mmdetection/configs/common/lsj-200e_coco-instance.py @@ -0,0 +1,18 @@ +_base_ = './lsj-100e_coco-instance.py' + +# 8x25=200e +train_dataloader = dict(dataset=dict(times=8)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.067, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=25, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] diff --git a/mmdetection/configs/common/ms-90k_coco.py b/mmdetection/configs/common/ms-90k_coco.py new file mode 100644 index 00000000..e2d6c3da --- /dev/null +++ b/mmdetection/configs/common/ms-90k_coco.py @@ -0,0 +1,122 @@ +_base_ = '../_base_/default_runtime.py' + +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# Align with Detectron2 +backend = 'pillow' +train_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=backend_args, + imdecode_backend=backend), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True, + backend=backend), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=backend_args, + imdecode_backend=backend), + dict(type='Resize', scale=(1333, 800), keep_ratio=True, backend=backend), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + pin_memory=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + pin_memory=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +# training schedule for 90k +max_iter = 90000 +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iter, val_interval=10000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[60000, 80000], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) + +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=10000)) +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/common/ms-poly-90k_coco-instance.py b/mmdetection/configs/common/ms-poly-90k_coco-instance.py new file mode 100644 index 00000000..d5566b3c --- /dev/null +++ b/mmdetection/configs/common/ms-poly-90k_coco-instance.py @@ -0,0 +1,130 @@ +_base_ = '../_base_/default_runtime.py' + +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# Align with Detectron2 +backend = 'pillow' +train_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=backend_args, + imdecode_backend=backend), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True, + backend=backend), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=backend_args, + imdecode_backend=backend), + dict(type='Resize', scale=(1333, 800), keep_ratio=True, backend=backend), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + pin_memory=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + pin_memory=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +# training schedule for 90k +max_iter = 90000 +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iter, val_interval=10000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[60000, 80000], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) + +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=10000)) +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/common/ms-poly_3x_coco-instance.py b/mmdetection/configs/common/ms-poly_3x_coco-instance.py new file mode 100644 index 00000000..04072f9b --- /dev/null +++ b/mmdetection/configs/common/ms-poly_3x_coco-instance.py @@ -0,0 +1,118 @@ +_base_ = '../_base_/default_runtime.py' +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# In mstrain 3x config, img_scale=[(1333, 640), (1333, 800)], +# multiscale_mode='range' +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs'), +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='RepeatDataset', + times=3, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + backend_args=backend_args) +test_evaluator = val_evaluator + +# training schedule for 3x with `RepeatDataset` +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +# Experiments show that using milestones=[9, 11] has higher performance +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[9, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/common/ms_3x_coco-instance.py b/mmdetection/configs/common/ms_3x_coco-instance.py new file mode 100644 index 00000000..f80cf88e --- /dev/null +++ b/mmdetection/configs/common/ms_3x_coco-instance.py @@ -0,0 +1,108 @@ +_base_ = '../_base_/default_runtime.py' + +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='RepeatDataset', + times=3, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + backend_args=backend_args) +test_evaluator = val_evaluator + +# training schedule for 3x with `RepeatDataset` +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +# Experiments show that using milestones=[9, 11] has higher performance +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[9, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/common/ms_3x_coco.py b/mmdetection/configs/common/ms_3x_coco.py new file mode 100644 index 00000000..facbb34c --- /dev/null +++ b/mmdetection/configs/common/ms_3x_coco.py @@ -0,0 +1,108 @@ +_base_ = '../_base_/default_runtime.py' + +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='RepeatDataset', + times=3, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + backend_args=backend_args) +test_evaluator = val_evaluator + +# training schedule for 3x with `RepeatDataset` +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +# Experiments show that using milestones=[9, 11] has higher performance +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[9, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/common/ssj_270k_coco-instance.py b/mmdetection/configs/common/ssj_270k_coco-instance.py new file mode 100644 index 00000000..7407644f --- /dev/null +++ b/mmdetection/configs/common/ssj_270k_coco-instance.py @@ -0,0 +1,125 @@ +_base_ = '../_base_/default_runtime.py' +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +image_size = (1024, 1024) + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# Standard Scale Jittering (SSJ) resizes and crops an image +# with a resize range of 0.8 to 1.25 of the original image size. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.8, 1.25), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='InfiniteSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +# The model is trained by 270k iterations with batch_size 64, +# which is roughly equivalent to 144 epochs. + +max_iters = 270000 +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iters, val_interval=10000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# optimizer assumes bs=64 +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.00004)) + +# learning rate policy +# lr steps at [0.9, 0.95, 0.975] of the maximum iterations +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=270000, + by_epoch=False, + milestones=[243000, 256500, 263250], + gamma=0.1) +] + +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=10000)) +log_processor = dict(by_epoch=False) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/common/ssj_scp_270k_coco-instance.py b/mmdetection/configs/common/ssj_scp_270k_coco-instance.py new file mode 100644 index 00000000..06159dd4 --- /dev/null +++ b/mmdetection/configs/common/ssj_scp_270k_coco-instance.py @@ -0,0 +1,60 @@ +_base_ = 'ssj_270k_coco-instance.py' +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +image_size = (1024, 1024) + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# Standard Scale Jittering (SSJ) resizes and crops an image +# with a resize range of 0.8 to 1.25 of the original image size. +load_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.8, 1.25), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=image_size), +] +train_pipeline = [ + dict(type='CopyPaste', max_num_pasted=100), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=load_pipeline, + backend_args=backend_args), + pipeline=train_pipeline)) diff --git a/mmdetection/configs/condinst/README.md b/mmdetection/configs/condinst/README.md new file mode 100644 index 00000000..01deb0ec --- /dev/null +++ b/mmdetection/configs/condinst/README.md @@ -0,0 +1,40 @@ +# CondInst + +> [CondInst: Conditional Convolutions for Instance +> Segmentation](https://arxiv.org/pdf/2003.05664.pdf) + + + +## Abstract + +We propose a simple yet effective instance segmentation framework, termed CondInst (conditional convolutions for instance segmentation). Top-performing instance segmentation methods such as Mask +R-CNN rely on ROI operations (typically ROIPool or ROIAlign) to +obtain the final instance masks. In contrast, we propose to solve instance segmentation from a new perspective. Instead of using instancewise ROIs as inputs to a network of fixed weights, we employ dynamic +instance-aware networks, conditioned on instances. CondInst enjoys two +advantages: 1) Instance segmentation is solved by a fully convolutional +network, eliminating the need for ROI cropping and feature alignment. +2\) Due to the much improved capacity of dynamically-generated conditional convolutions, the mask head can be very compact (e.g., 3 conv. +layers, each having only 8 channels), leading to significantly faster inference. We demonstrate a simpler instance segmentation method that can +achieve improved performance in both accuracy and inference speed. On +the COCO dataset, we outperform a few recent methods including welltuned Mask R-CNN baselines, without longer training schedules needed. + +
    + +
    + +## Results and Models + +| Backbone | Style | MS train | Lr schd | bbox AP | mask AP | Config | Download | +| :------: | :-----: | :------: | :-----: | :-----: | :-----: | :-------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | Y | 1x | 39.8 | 36.0 | [config](./condinst_r50_fpn_ms-poly-90k_coco_instance.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/condinst/condinst_r50_fpn_ms-poly-90k_coco_instance/condinst_r50_fpn_ms-poly-90k_coco_instance_20221129_125223-4c186406.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/condinst/condinst_r50_fpn_ms-poly-90k_coco_instance/condinst_r50_fpn_ms-poly-90k_coco_instance_20221129_125223.json) | + +## Citation + +```latex +@inproceedings{tian2020conditional, + title = {Conditional Convolutions for Instance Segmentation}, + author = {Tian, Zhi and Shen, Chunhua and Chen, Hao}, + booktitle = {Proc. Eur. Conf. Computer Vision (ECCV)}, + year = {2020} +} +``` diff --git a/mmdetection/configs/condinst/condinst_r50_fpn_ms-poly-90k_coco_instance.py b/mmdetection/configs/condinst/condinst_r50_fpn_ms-poly-90k_coco_instance.py new file mode 100644 index 00000000..39639d87 --- /dev/null +++ b/mmdetection/configs/condinst/condinst_r50_fpn_ms-poly-90k_coco_instance.py @@ -0,0 +1,85 @@ +_base_ = '../common/ms-poly-90k_coco-instance.py' + +# model settings +model = dict( + type='CondInst', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50'), + style='pytorch'), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', # use P5 + num_outs=5, + relu_before_extra_convs=True), + bbox_head=dict( + type='CondInstBboxHead', + num_params=169, + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + norm_on_bbox=True, + centerness_on_reg=True, + dcn_on_last_conv=False, + center_sampling=True, + conv_bias=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + mask_head=dict( + type='CondInstMaskHead', + num_layers=3, + feat_channels=8, + size_of_interest=8, + mask_out_stride=4, + max_masks_to_train=300, + mask_feature_head=dict( + in_channels=256, + feat_channels=128, + start_level=0, + end_level=2, + out_channels=8, + mask_stride=8, + num_stacked_convs=4, + norm_cfg=dict(type='BN', requires_grad=True)), + loss_mask=dict( + type='DiceLoss', + use_sigmoid=True, + activate=True, + eps=5e-6, + loss_weight=1.0)), + # model training and testing settings + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100, + mask_thr=0.5)) + +# optimizer +optim_wrapper = dict(optimizer=dict(lr=0.01)) diff --git a/mmdetection/configs/condinst/metafile.yml b/mmdetection/configs/condinst/metafile.yml new file mode 100644 index 00000000..1237b74d --- /dev/null +++ b/mmdetection/configs/condinst/metafile.yml @@ -0,0 +1,32 @@ +Collections: + - Name: CondInst + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x A100 GPUs + Architecture: + - FPN + - FCOS + - ResNet + Paper: https://arxiv.org/abs/2003.05664 + README: configs/condinst/README.md + +Models: + - Name: condinst_r50_fpn_ms-poly-90k_coco_instance + In Collection: CondInst + Config: configs/condinst/condinst_r50_fpn_ms-poly-90k_coco_instance.py + Metadata: + Training Memory (GB): 4.4 + Iterations: 90000 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.0 + Weights: https://download.openmmlab.com/mmdetection/v3.0/condinst/condinst_r50_fpn_ms-poly-90k_coco_instance/condinst_r50_fpn_ms-poly-90k_coco_instance_20221129_125223-4c186406.pth diff --git a/mmdetection/configs/conditional_detr/README.md b/mmdetection/configs/conditional_detr/README.md new file mode 100644 index 00000000..4043571c --- /dev/null +++ b/mmdetection/configs/conditional_detr/README.md @@ -0,0 +1,39 @@ +# Conditional DETR + +> [Conditional DETR for Fast Training Convergence](https://arxiv.org/abs/2108.06152) + + + +## Abstract + +The DETR approach applies the transformer encoder and decoder architecture to object detection and achieves promising performance. In this paper, we handle the critical issue, slow training convergence, and present a conditional cross-attention mechanism for fast DETR training. Our approach is motivated by that the cross-attention in DETR relies highly on the content embeddings and that the spatial embeddings make minor contributions, increasing the need for high-quality content embeddings and thus increasing the training difficulty. + +
    + +
    + +Our conditional DETR learns a conditional spatial query from the decoder embedding for decoder multi-head cross-attention. The benefit is that through the conditional spatial query, each cross-attention head is able to attend to a band containing a distinct region, e.g., one object extremity or a region inside the object box (Figure 1). This narrows down the spatial range for localizing the distinct regions for object classification and box regression, thus relaxing the dependence on the content embeddings and easing the training. Empirical results show that conditional DETR converges 6.7x faster for the backbones R50 and R101 and 10x faster for stronger backbones DC5-R50 and DC5-R101. + +
    + + +
    + +## Results and Models + +We provide the config files and models for Conditional DETR: [Conditional DETR for Fast Training Convergence](https://arxiv.org/abs/2108.06152). + +| Backbone | Model | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :--------------: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | Conditional DETR | 50e | | | 41.1 | [config](./conditional-detr_r50_8xb2-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/conditional_detr/conditional-detr_r50_8xb2-50e_coco/conditional-detr_r50_8xb2-50e_coco_20221121_180202-c83a1dc0.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/conditional_detr/conditional-detr_r50_8xb2-50e_coco/conditional-detr_r50_8xb2-50e_coco_20221121_180202.log.json) | + +## Citation + +```latex +@inproceedings{meng2021-CondDETR, + title = {Conditional DETR for Fast Training Convergence}, + author = {Meng, Depu and Chen, Xiaokang and Fan, Zejia and Zeng, Gang and Li, Houqiang and Yuan, Yuhui and Sun, Lei and Wang, Jingdong}, + booktitle = {Proceedings of the IEEE International Conference on Computer Vision (ICCV)}, + year = {2021} +} +``` diff --git a/mmdetection/configs/conditional_detr/conditional-detr_r50_8xb2-50e_coco.py b/mmdetection/configs/conditional_detr/conditional-detr_r50_8xb2-50e_coco.py new file mode 100644 index 00000000..a2147644 --- /dev/null +++ b/mmdetection/configs/conditional_detr/conditional-detr_r50_8xb2-50e_coco.py @@ -0,0 +1,42 @@ +_base_ = ['../detr/detr_r50_8xb2-150e_coco.py'] +model = dict( + type='ConditionalDETR', + num_queries=300, + decoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict( + _delete_=True, + embed_dims=256, + num_heads=8, + attn_drop=0.1, + cross_attn=False), + cross_attn_cfg=dict( + _delete_=True, + embed_dims=256, + num_heads=8, + attn_drop=0.1, + cross_attn=True))), + bbox_head=dict( + type='ConditionalDETRHead', + loss_cls=dict( + _delete_=True, + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=2.0)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ]))) + +# learning policy +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=50, val_interval=1) + +param_scheduler = [dict(type='MultiStepLR', end=50, milestones=[40])] diff --git a/mmdetection/configs/conditional_detr/metafile.yml b/mmdetection/configs/conditional_detr/metafile.yml new file mode 100644 index 00000000..83f5532c --- /dev/null +++ b/mmdetection/configs/conditional_detr/metafile.yml @@ -0,0 +1,32 @@ +Collections: + - Name: Conditional DETR + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Multi Scale Train + - Gradient Clip + Training Resources: 8x A100 GPUs + Architecture: + - ResNet + - Transformer + Paper: + URL: https://arxiv.org/abs/2108.06152 + Title: 'Conditional DETR for Fast Training Convergence' + README: configs/conditional_detr/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/f4112c9e5611468ffbd57cfba548fd1289264b52/mmdet/models/detectors/conditional_detr.py#L14 + Version: v3.0.0rc6 + +Models: + - Name: conditional-detr_r50_8xb2-50e_coco + In Collection: Conditional DETR + Config: configs/conditional_detr/conditional-detr_r50_8xb2-50e_coco.py + Metadata: + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/conditional_detr/conditional-detr_r50_8xb2-50e_coco/conditional-detr_r50_8xb2-50e_coco_20221121_180202-c83a1dc0.pth diff --git a/mmdetection/configs/convnext/README.md b/mmdetection/configs/convnext/README.md new file mode 100644 index 00000000..33497bb5 --- /dev/null +++ b/mmdetection/configs/convnext/README.md @@ -0,0 +1,42 @@ +# ConvNeXt + +> [A ConvNet for the 2020s](https://arxiv.org/abs/2201.03545) + + + +## Abstract + +The "Roaring 20s" of visual recognition began with the introduction of Vision Transformers (ViTs), which quickly superseded ConvNets as the state-of-the-art image classification model. A vanilla ViT, on the other hand, faces difficulties when applied to general computer vision tasks such as object detection and semantic segmentation. It is the hierarchical Transformers (e.g., Swin Transformers) that reintroduced several ConvNet priors, making Transformers practically viable as a generic vision backbone and demonstrating remarkable performance on a wide variety of vision tasks. However, the effectiveness of such hybrid approaches is still largely credited to the intrinsic superiority of Transformers, rather than the inherent inductive biases of convolutions. In this work, we reexamine the design spaces and test the limits of what a pure ConvNet can achieve. We gradually "modernize" a standard ResNet toward the design of a vision Transformer, and discover several key components that contribute to the performance difference along the way. The outcome of this exploration is a family of pure ConvNet models dubbed ConvNeXt. Constructed entirely from standard ConvNet modules, ConvNeXts compete favorably with Transformers in terms of accuracy and scalability, achieving 87.8% ImageNet top-1 accuracy and outperforming Swin Transformers on COCO detection and ADE20K segmentation, while maintaining the simplicity and efficiency of standard ConvNets. + +
    + +
    + +## Results and models + +| Method | Backbone | Pretrain | Lr schd | Multi-scale crop | FP16 | Mem (GB) | box AP | mask AP | Config | Download | +| :----------------: | :--------: | :---------: | :-----: | :--------------: | :--: | :------: | :----: | :-----: | :-------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Mask R-CNN | ConvNeXt-T | ImageNet-1K | 3x | yes | yes | 7.3 | 46.2 | 41.7 | [config](./mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/convnext/mask_rcnn_convnext-t_p4_w7_fpn_fp16_ms-crop_3x_coco/mask_rcnn_convnext-t_p4_w7_fpn_fp16_ms-crop_3x_coco_20220426_154953-050731f4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/convnext/mask_rcnn_convnext-t_p4_w7_fpn_fp16_ms-crop_3x_coco/mask_rcnn_convnext-t_p4_w7_fpn_fp16_ms-crop_3x_coco_20220426_154953.log.json) | +| Cascade Mask R-CNN | ConvNeXt-T | ImageNet-1K | 3x | yes | yes | 9.0 | 50.3 | 43.6 | [config](./cascade-mask-rcnn_convnext-t-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/convnext/cascade_mask_rcnn_convnext-t_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco/cascade_mask_rcnn_convnext-t_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco_20220509_204200-8f07c40b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/convnext/cascade_mask_rcnn_convnext-t_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco/cascade_mask_rcnn_convnext-t_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco_20220509_204200.log.json) | +| Cascade Mask R-CNN | ConvNeXt-S | ImageNet-1K | 3x | yes | yes | 12.3 | 51.8 | 44.8 | [config](./cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/convnext/cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco/cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco_20220510_201004-3d24f5a4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/convnext/cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco/cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco_20220510_201004.log.json) | + +**Note**: + +- ConvNeXt backbone needs to install [MMPreTrain](https://github.com/open-mmlab/mmpretrain) first, which has abundant backbones for downstream tasks. + +```shell +pip install mmpretrain +``` + +- The performance is unstable. `Cascade Mask R-CNN` may fluctuate about 0.2 mAP. + +## Citation + +```bibtex +@article{liu2022convnet, + title={A ConvNet for the 2020s}, + author={Liu, Zhuang and Mao, Hanzi and Wu, Chao-Yuan and Feichtenhofer, Christoph and Darrell, Trevor and Xie, Saining}, + journal={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, + year={2022} +} +``` diff --git a/mmdetection/configs/convnext/cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py b/mmdetection/configs/convnext/cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py new file mode 100644 index 00000000..9a5fbedc --- /dev/null +++ b/mmdetection/configs/convnext/cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py @@ -0,0 +1,26 @@ +_base_ = './cascade-mask-rcnn_convnext-t-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py' # noqa + +# please install mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict( + imports=['mmpretrain.models'], allow_failed_imports=False) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-small_3rdparty_32xb128-noema_in1k_20220301-303e75e3.pth' # noqa + +model = dict( + backbone=dict( + _delete_=True, + type='mmpretrain.ConvNeXt', + arch='small', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.6, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.'))) + +optim_wrapper = dict(paramwise_cfg={ + 'decay_rate': 0.7, + 'decay_type': 'layer_wise', + 'num_layers': 12 +}) diff --git a/mmdetection/configs/convnext/cascade-mask-rcnn_convnext-t-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py b/mmdetection/configs/convnext/cascade-mask-rcnn_convnext-t-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py new file mode 100644 index 00000000..c92f8683 --- /dev/null +++ b/mmdetection/configs/convnext/cascade-mask-rcnn_convnext-t-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py @@ -0,0 +1,154 @@ +_base_ = [ + '../_base_/models/cascade-mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# please install mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict( + imports=['mmpretrain.models'], allow_failed_imports=False) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-tiny_3rdparty_32xb128-noema_in1k_20220301-795e9634.pth' # noqa + +model = dict( + backbone=dict( + _delete_=True, + type='mmpretrain.ConvNeXt', + arch='tiny', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.4, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + neck=dict(in_channels=[96, 192, 384, 768]), + roi_head=dict(bbox_head=[ + dict( + type='ConvFCBBoxHead', + num_shared_convs=4, + num_shared_fcs=1, + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + reg_decoded_bbox=True, + norm_cfg=dict(type='SyncBN', requires_grad=True), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=10.0)), + dict( + type='ConvFCBBoxHead', + num_shared_convs=4, + num_shared_fcs=1, + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=False, + reg_decoded_bbox=True, + norm_cfg=dict(type='SyncBN', requires_grad=True), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=10.0)), + dict( + type='ConvFCBBoxHead', + num_shared_convs=4, + num_shared_fcs=1, + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=False, + reg_decoded_bbox=True, + norm_cfg=dict(type='SyncBN', requires_grad=True), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=10.0)) + ])) + +# augmentation strategy originates from DETR / Sparse RCNN +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[[ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True) + ]]), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + constructor='LearningRateDecayOptimizerConstructor', + paramwise_cfg={ + 'decay_rate': 0.7, + 'decay_type': 'layer_wise', + 'num_layers': 6 + }, + optimizer=dict( + _delete_=True, + type='AdamW', + lr=0.0002, + betas=(0.9, 0.999), + weight_decay=0.05)) diff --git a/mmdetection/configs/convnext/mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco.py b/mmdetection/configs/convnext/mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco.py new file mode 100644 index 00000000..5792b5b5 --- /dev/null +++ b/mmdetection/configs/convnext/mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco.py @@ -0,0 +1,96 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# please install mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict( + imports=['mmpretrain.models'], allow_failed_imports=False) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-tiny_3rdparty_32xb128-noema_in1k_20220301-795e9634.pth' # noqa + +model = dict( + backbone=dict( + _delete_=True, + type='mmpretrain.ConvNeXt', + arch='tiny', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.4, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + neck=dict(in_channels=[96, 192, 384, 768])) + +# augmentation strategy originates from DETR / Sparse RCNN +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[[ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True) + ]]), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + constructor='LearningRateDecayOptimizerConstructor', + paramwise_cfg={ + 'decay_rate': 0.95, + 'decay_type': 'layer_wise', + 'num_layers': 6 + }, + optimizer=dict( + _delete_=True, + type='AdamW', + lr=0.0001, + betas=(0.9, 0.999), + weight_decay=0.05, + )) diff --git a/mmdetection/configs/convnext/metafile.yml b/mmdetection/configs/convnext/metafile.yml new file mode 100644 index 00000000..b9fd7506 --- /dev/null +++ b/mmdetection/configs/convnext/metafile.yml @@ -0,0 +1,93 @@ +Models: + - Name: mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco + In Collection: Mask R-CNN + Config: configs/convnext/mask-rcnn_convnext-t-p4-w7_fpn_amp-ms-crop-3x_coco.py + Metadata: + Training Memory (GB): 7.3 + Epochs: 36 + Training Data: COCO + Training Techniques: + - AdamW + - Mixed Precision Training + Training Resources: 8x A100 GPUs + Architecture: + - ConvNeXt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/convnext/mask_rcnn_convnext-t_p4_w7_fpn_fp16_ms-crop_3x_coco/mask_rcnn_convnext-t_p4_w7_fpn_fp16_ms-crop_3x_coco_20220426_154953-050731f4.pth + Paper: + URL: https://arxiv.org/abs/2201.03545 + Title: 'A ConvNet for the 2020s' + README: configs/convnext/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.16.0/mmdet/models/backbones/swin.py#L465 + Version: v2.16.0 + + - Name: cascade-mask-rcnn_convnext-t-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco + In Collection: Cascade Mask R-CNN + Config: configs/convnext/cascade-mask-rcnn_convnext-t-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py + Metadata: + Training Memory (GB): 9.0 + Epochs: 36 + Training Data: COCO + Training Techniques: + - AdamW + - Mixed Precision Training + Training Resources: 8x A100 GPUs + Architecture: + - ConvNeXt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 43.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/convnext/cascade_mask_rcnn_convnext-t_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco/cascade_mask_rcnn_convnext-t_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco_20220509_204200-8f07c40b.pth + Paper: + URL: https://arxiv.org/abs/2201.03545 + Title: 'A ConvNet for the 2020s' + README: configs/convnext/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.16.0/mmdet/models/backbones/swin.py#L465 + Version: v2.25.0 + + - Name: cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco + In Collection: Cascade Mask R-CNN + Config: configs/convnext/cascade-mask-rcnn_convnext-s-p4-w7_fpn_4conv1fc-giou_amp-ms-crop-3x_coco.py + Metadata: + Training Memory (GB): 12.3 + Epochs: 36 + Training Data: COCO + Training Techniques: + - AdamW + - Mixed Precision Training + Training Resources: 8x A100 GPUs + Architecture: + - ConvNeXt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 51.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 44.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/convnext/cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco/cascade_mask_rcnn_convnext-s_p4_w7_fpn_giou_4conv1f_fp16_ms-crop_3x_coco_20220510_201004-3d24f5a4.pth + Paper: + URL: https://arxiv.org/abs/2201.03545 + Title: 'A ConvNet for the 2020s' + README: configs/convnext/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.16.0/mmdet/models/backbones/swin.py#L465 + Version: v2.25.0 diff --git a/mmdetection/configs/cornernet/README.md b/mmdetection/configs/cornernet/README.md new file mode 100644 index 00000000..e44964d8 --- /dev/null +++ b/mmdetection/configs/cornernet/README.md @@ -0,0 +1,43 @@ +# CornerNet + +> [Cornernet: Detecting objects as paired keypoints](https://arxiv.org/abs/1808.01244) + + + +## Abstract + +We propose CornerNet, a new approach to object detection where we detect an object bounding box as a pair of keypoints, the top-left corner and the bottom-right corner, using a single convolution neural network. By detecting objects as paired keypoints, we eliminate the need for designing a set of anchor boxes commonly used in prior single-stage detectors. In addition to our novel formulation, we introduce corner pooling, a new type of pooling layer that helps the network better localize corners. Experiments show that CornerNet achieves a 42.2% AP on MS COCO, outperforming all existing one-stage detectors. + +
    + +
    + +## Results and Models + +| Backbone | Batch Size | Step/Total Epochs | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :--------------: | :------------------------------------------------------------------: | :---------------: | :------: | :------------: | :----: | :------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| HourglassNet-104 | [10 x 5](./cornernet_hourglass104_10xb5-crop511-210e-mstest_coco.py) | 180/210 | 13.9 | 4.2 | 41.2 | [config](./cornernet_hourglass104_10xb5-crop511-210e-mstest_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_10x5_210e_coco/cornernet_hourglass104_mstest_10x5_210e_coco_20200824_185720-5fefbf1c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_10x5_210e_coco/cornernet_hourglass104_mstest_10x5_210e_coco_20200824_185720.log.json) | +| HourglassNet-104 | [8 x 6](./cornernet_hourglass104_8xb6-210e-mstest_coco.py) | 180/210 | 15.9 | 4.2 | 41.2 | [config](./cornernet_hourglass104_8xb6-210e-mstest_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_8x6_210e_coco/cornernet_hourglass104_mstest_8x6_210e_coco_20200825_150618-79b44c30.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_8x6_210e_coco/cornernet_hourglass104_mstest_8x6_210e_coco_20200825_150618.log.json) | +| HourglassNet-104 | [32 x 3](./cornernet_hourglass104_32xb3-210e-mstest_coco.py) | 180/210 | 9.5 | 3.9 | 40.4 | [config](./cornernet_hourglass104_32xb3-210e-mstest_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_32x3_210e_coco/cornernet_hourglass104_mstest_32x3_210e_coco_20200819_203110-1efaea91.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_32x3_210e_coco/cornernet_hourglass104_mstest_32x3_210e_coco_20200819_203110.log.json) | + +Note: + +- TTA setting is single-scale and `flip=True`. If you want to reproduce the TTA performance, please add `--tta` in the test command. +- Experiments with `images_per_gpu=6` are conducted on Tesla V100-SXM2-32GB, `images_per_gpu=3` are conducted on GeForce GTX 1080 Ti. +- Here are the descriptions of each experiment setting: + - 10 x 5: 10 GPUs with 5 images per gpu. This is the same setting as that reported in the original paper. + - 8 x 6: 8 GPUs with 6 images per gpu. The total batchsize is similar to paper and only need 1 node to train. + - 32 x 3: 32 GPUs with 3 images per gpu. The default setting for 1080TI and need 4 nodes to train. + +## Citation + +```latex +@inproceedings{law2018cornernet, + title={Cornernet: Detecting objects as paired keypoints}, + author={Law, Hei and Deng, Jia}, + booktitle={15th European Conference on Computer Vision, ECCV 2018}, + pages={765--781}, + year={2018}, + organization={Springer Verlag} +} +``` diff --git a/mmdetection/configs/cornernet/cornernet_hourglass104_10xb5-crop511-210e-mstest_coco.py b/mmdetection/configs/cornernet/cornernet_hourglass104_10xb5-crop511-210e-mstest_coco.py new file mode 100644 index 00000000..76339163 --- /dev/null +++ b/mmdetection/configs/cornernet/cornernet_hourglass104_10xb5-crop511-210e-mstest_coco.py @@ -0,0 +1,8 @@ +_base_ = './cornernet_hourglass104_8xb6-210e-mstest_coco.py' + +train_dataloader = dict(batch_size=5) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (10 GPUs) x (5 samples per GPU) +auto_scale_lr = dict(base_batch_size=50) diff --git a/mmdetection/configs/cornernet/cornernet_hourglass104_32xb3-210e-mstest_coco.py b/mmdetection/configs/cornernet/cornernet_hourglass104_32xb3-210e-mstest_coco.py new file mode 100644 index 00000000..51a47403 --- /dev/null +++ b/mmdetection/configs/cornernet/cornernet_hourglass104_32xb3-210e-mstest_coco.py @@ -0,0 +1,8 @@ +_base_ = './cornernet_hourglass104_8xb6-210e-mstest_coco.py' + +train_dataloader = dict(batch_size=3) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (3 samples per GPU) +auto_scale_lr = dict(base_batch_size=96) diff --git a/mmdetection/configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py b/mmdetection/configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py new file mode 100644 index 00000000..bdb46fff --- /dev/null +++ b/mmdetection/configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py @@ -0,0 +1,183 @@ +_base_ = [ + '../_base_/default_runtime.py', '../_base_/datasets/coco_detection.py' +] + +data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True) + +# model settings +model = dict( + type='CornerNet', + data_preprocessor=data_preprocessor, + backbone=dict( + type='HourglassNet', + downsample_times=5, + num_stacks=2, + stage_channels=[256, 256, 384, 384, 384, 512], + stage_blocks=[2, 2, 2, 2, 2, 4], + norm_cfg=dict(type='BN', requires_grad=True)), + neck=None, + bbox_head=dict( + type='CornerHead', + num_classes=80, + in_channels=256, + num_feat_levels=2, + corner_emb_channels=1, + loss_heatmap=dict( + type='GaussianFocalLoss', alpha=2.0, gamma=4.0, loss_weight=1), + loss_embedding=dict( + type='AssociativeEmbeddingLoss', + pull_weight=0.10, + push_weight=0.10), + loss_offset=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1)), + # training and testing settings + train_cfg=None, + test_cfg=dict( + corner_topk=100, + local_maximum_kernel=3, + distance_threshold=0.5, + score_thr=0.05, + max_per_img=100, + nms=dict(type='soft_nms', iou_threshold=0.5, method='gaussian'))) + +# data settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict( + # The cropped images are padded into squares during training, + # but may be smaller than crop_size. + type='RandomCenterCropPad', + crop_size=(511, 511), + ratios=(0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3), + test_mode=False, + test_pad_mode=None, + mean=data_preprocessor['mean'], + std=data_preprocessor['std'], + # Image data is not converted to rgb. + to_rgb=data_preprocessor['bgr_to_rgb']), + # Make sure the output is always crop_size. + dict(type='Resize', scale=(511, 511), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs'), +] + +test_pipeline = [ + dict( + type='LoadImageFromFile', + to_float32=True, + backend_args=_base_.backend_args, + ), + # don't need Resize + dict( + type='RandomCenterCropPad', + crop_size=None, + ratios=None, + border=None, + test_mode=True, + test_pad_mode=['logical_or', 127], + mean=data_preprocessor['mean'], + std=data_preprocessor['std'], + # Image data is not converted to rgb. + to_rgb=data_preprocessor['bgr_to_rgb']), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'border')) +] + +train_dataloader = dict( + batch_size=6, + num_workers=3, + batch_sampler=None, + dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='Adam', lr=0.0005), + clip_grad=dict(max_norm=35, norm_type=2)) + +max_epochs = 210 + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 3, + by_epoch=False, + begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[180], + gamma=0.1) +] + +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (6 samples per GPU) +auto_scale_lr = dict(base_batch_size=48) + +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict( + nms=dict(type='soft_nms', iou_threshold=0.5, method='gaussian'), + max_per_img=100)) + +tta_pipeline = [ + dict( + type='LoadImageFromFile', + to_float32=True, + backend_args=_base_.backend_args), + dict( + type='TestTimeAug', + transforms=[ + [ + # ``RandomFlip`` must be placed before ``RandomCenterCropPad``, + # otherwise bounding box coordinates after flipping cannot be + # recovered correctly. + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], + [ + dict( + type='RandomCenterCropPad', + crop_size=None, + ratios=None, + border=None, + test_mode=True, + test_pad_mode=['logical_or', 127], + mean=data_preprocessor['mean'], + std=data_preprocessor['std'], + # Image data is not converted to rgb. + to_rgb=data_preprocessor['bgr_to_rgb']) + ], + [dict(type='LoadAnnotations', with_bbox=True)], + [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'flip', 'flip_direction', 'border')) + ] + ]) +] diff --git a/mmdetection/configs/cornernet/metafile.yml b/mmdetection/configs/cornernet/metafile.yml new file mode 100644 index 00000000..f915cf37 --- /dev/null +++ b/mmdetection/configs/cornernet/metafile.yml @@ -0,0 +1,83 @@ +Collections: + - Name: CornerNet + Metadata: + Training Data: COCO + Training Techniques: + - Adam + Training Resources: 8x V100 GPUs + Architecture: + - Corner Pooling + - Stacked Hourglass Network + Paper: + URL: https://arxiv.org/abs/1808.01244 + Title: 'CornerNet: Detecting Objects as Paired Keypoints' + README: configs/cornernet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.3.0/mmdet/models/detectors/cornernet.py#L9 + Version: v2.3.0 + +Models: + - Name: cornernet_hourglass104_10xb5-crop511-210e-mstest_coco + In Collection: CornerNet + Config: configs/cornernet/cornernet_hourglass104_10xb5-crop511-210e-mstest_coco.py + Metadata: + Training Resources: 10x V100 GPUs + Batch Size: 50 + Training Memory (GB): 13.9 + inference time (ms/im): + - value: 238.1 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 210 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_10x5_210e_coco/cornernet_hourglass104_mstest_10x5_210e_coco_20200824_185720-5fefbf1c.pth + + - Name: cornernet_hourglass104_8xb6-210e-mstest_coco + In Collection: CornerNet + Config: configs/cornernet/cornernet_hourglass104_8xb6-210e-mstest_coco.py + Metadata: + Batch Size: 48 + Training Memory (GB): 15.9 + inference time (ms/im): + - value: 238.1 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 210 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_8x6_210e_coco/cornernet_hourglass104_mstest_8x6_210e_coco_20200825_150618-79b44c30.pth + + - Name: cornernet_hourglass104_32xb3-210e-mstest_coco + In Collection: CornerNet + Config: configs/cornernet/cornernet_hourglass104_32xb3-210e-mstest_coco.py + Metadata: + Training Resources: 32x V100 GPUs + Batch Size: 96 + Training Memory (GB): 9.5 + inference time (ms/im): + - value: 256.41 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 210 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/cornernet/cornernet_hourglass104_mstest_32x3_210e_coco/cornernet_hourglass104_mstest_32x3_210e_coco_20200819_203110-1efaea91.pth diff --git a/mmdetection/configs/crowddet/README.md b/mmdetection/configs/crowddet/README.md new file mode 100644 index 00000000..abc0f2d2 --- /dev/null +++ b/mmdetection/configs/crowddet/README.md @@ -0,0 +1,37 @@ +# CrowdDet + +> [Detection in Crowded Scenes: One Proposal, Multiple Predictions](https://arxiv.org/abs/2003.09163) + + + +## Abstract + +We propose a simple yet effective proposal-based object detector, aiming at detecting highly-overlapped instances in crowded scenes. The key of our approach is to let each proposal predict a set of correlated instances rather than a single one in previous proposal-based frameworks. Equipped with new techniques such as EMD Loss and Set NMS, our detector can effectively handle the difficulty of detecting highly overlapped objects. On a FPN-Res50 baseline, our detector can obtain 4.9% AP gains on challenging CrowdHuman dataset and 1.0% MR^−2 improvements on CityPersons dataset, without bells and whistles. Moreover, on less crowed datasets like COCO, our approach can still achieve moderate improvement, suggesting the proposed method is robust to crowdedness. Code and pre-trained models will be released at https://github.com/megvii-model/CrowdDetection. + +
    + +
    + +## Results and Models + +| Backbone | RM | Style | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :---: | :-----: | :------: | :------------: | :----: | :-------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | False | pytorch | 4.4 | - | 90.0 | [config](./crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/crowddet/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman_20221023_174954-dc319c2d.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/crowddet/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman_20221023_174954.log.json) | +| R-50-FPN | True | pytorch | 4.8 | - | 90.32 | [config](./crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/crowddet/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman_20221024_215917-45602806.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/crowddet/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman_20221024_215917.log.json) | + +Note: + +- RM indicates whether to use the refine module. +- The dataset for training and testing this model is `CrowdHuman`, and the metric of `box AP` is calculated by `mmdet/evaluation/metrics/crowdhuman_metric.py`. + +## Citation + +```latex +@inproceedings{Chu_2020_CVPR, + title={Detection in Crowded Scenes: One Proposal, Multiple Predictions}, + author={Chu, Xuangeng and Zheng, Anlin and Zhang, Xiangyu and Sun, Jian}, + booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, + month = {June}, + year = {2020} +} +``` diff --git a/mmdetection/configs/crowddet/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman.py b/mmdetection/configs/crowddet/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman.py new file mode 100644 index 00000000..8815be77 --- /dev/null +++ b/mmdetection/configs/crowddet/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman.py @@ -0,0 +1,227 @@ +_base_ = ['../_base_/default_runtime.py'] + +model = dict( + type='CrowdDet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False, + pad_size_divisor=64, + # This option is set according to https://github.com/Purkialo/CrowdDet/ + # blob/master/lib/data/CrowdHuman.py The images in the entire batch are + # resize together. + batch_augments=[ + dict(type='BatchResize', scale=(1400, 800), pad_size_divisor=64) + ]), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5, + upsample_cfg=dict(mode='bilinear', align_corners=False)), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[1.0, 2.0, 3.0], + strides=[4, 8, 16, 32, 64], + centers=[(8, 8), (8, 8), (8, 8), (8, 8), (8, 8)]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0.0, 0.0, 0.0, 0.0], + target_stds=[1.0, 1.0, 1.0, 1.0], + clip_border=False), + loss_cls=dict(type='CrossEntropyLoss', loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + roi_head=dict( + type='MultiInstanceRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', + output_size=7, + sampling_ratio=-1, + aligned=True, + use_torchvision=True), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=dict( + type='MultiInstanceBBoxHead', + with_refine=False, + num_shared_fcs=2, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=1, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', + loss_weight=1.0, + use_sigmoid=False, + reduction='none'), + loss_bbox=dict( + type='SmoothL1Loss', loss_weight=1.0, reduction='none'))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=(0.3, 0.7), + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2400, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=2), + rcnn=dict( + assigner=dict( + type='MultiInstanceAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.3, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='MultiInsRandomSampler', + num=512, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=1200, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=2), + rcnn=dict( + nms=dict(type='nms', iou_threshold=0.5), + score_thr=0.01, + max_per_img=500))) + +dataset_type = 'CrowdHumanDataset' +data_root = 'data/CrowdHuman/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/tracking/CrowdHuman/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/tracking/', +# 'data/': 's3://openmmlab/datasets/tracking/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', + 'flip_direction')) +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1400, 800), keep_ratio=True), + # avoid bboxes being resized + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=None, # The 'batch_sampler' may decrease the precision + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotation_train.odgt', + data_prefix=dict(img='Images/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotation_val.odgt', + data_prefix=dict(img='Images/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CrowdHumanMetric', + ann_file=data_root + 'annotation_val.odgt', + metric=['AP', 'MR', 'JI'], + backend_args=backend_args) +test_evaluator = val_evaluator + +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=30, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=800), + dict( + type='MultiStepLR', + begin=0, + end=30, + by_epoch=True, + milestones=[24, 27], + gamma=0.1) +] + +# optimizer +auto_scale_lr = dict(base_batch_size=16) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.002, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/crowddet/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman.py b/mmdetection/configs/crowddet/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman.py new file mode 100644 index 00000000..80277ce1 --- /dev/null +++ b/mmdetection/configs/crowddet/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman.py @@ -0,0 +1,3 @@ +_base_ = './crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman.py' + +model = dict(roi_head=dict(bbox_head=dict(with_refine=True))) diff --git a/mmdetection/configs/crowddet/metafile.yml b/mmdetection/configs/crowddet/metafile.yml new file mode 100644 index 00000000..4f191dea --- /dev/null +++ b/mmdetection/configs/crowddet/metafile.yml @@ -0,0 +1,47 @@ +Collections: + - Name: CrowdDet + Metadata: + Training Data: CrowdHuman + Training Techniques: + - SGD + - EMD Loss + Training Resources: 8x A100 GPUs + Architecture: + - FPN + - RPN + - ResNet + - RoIPool + Paper: + URL: https://arxiv.org/abs/2003.09163 + Title: 'Detection in Crowded Scenes: One Proposal, Multiple Predictions' + README: configs/crowddet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v3.0.0rc3/mmdet/models/detectors/crowddet.py + Version: v3.0.0rc3 + +Models: + - Name: crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman + In Collection: CrowdDet + Config: configs/crowddet/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman.py + Metadata: + Training Memory (GB): 4.8 + Epochs: 30 + Results: + - Task: Object Detection + Dataset: CrowdHuman + Metrics: + box AP: 90.32 + Weights: https://download.openmmlab.com/mmdetection/v3.0/crowddet/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman/crowddet-rcnn_refine_r50_fpn_8xb2-30e_crowdhuman_20221024_215917-45602806.pth + + - Name: crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman + In Collection: CrowdDet + Config: configs/crowddet/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman.py + Metadata: + Training Memory (GB): 4.4 + Epochs: 30 + Results: + - Task: Object Detection + Dataset: CrowdHuman + Metrics: + box AP: 90.0 + Weights: https://download.openmmlab.com/mmdetection/v3.0/crowddet/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman/crowddet-rcnn_r50_fpn_8xb2-30e_crowdhuman_20221023_174954-dc319c2d.pth diff --git a/mmdetection/configs/dab_detr/README.md b/mmdetection/configs/dab_detr/README.md new file mode 100644 index 00000000..5661f27a --- /dev/null +++ b/mmdetection/configs/dab_detr/README.md @@ -0,0 +1,40 @@ +# DAB-DETR + +> [DAB-DETR: Dynamic Anchor Boxes are Better Queries for DETR](https://arxiv.org/abs/2201.12329) + + + +## Abstract + +We present in this paper a novel query formulation using dynamic anchor boxes for DETR (DEtection TRansformer) and offer a deeper understanding of the role of queries in DETR. This new formulation directly uses box coordinates as queries in Transformer decoders and dynamically updates them layer-by-layer. Using box coordinates not only helps using explicit positional priors to improve the query-to-feature similarity and eliminate the slow training convergence issue in DETR, but also allows us to modulate the positional attention map using the box width and height information. Such a design makes it clear that queries in DETR can be implemented as performing soft ROI pooling layer-by-layer in a cascade manner. As a result, it leads to the best performance on MS-COCO benchmark among the DETR-like detection models under the same setting, e.g., AP 45.7% using ResNet50-DC5 as backbone trained in 50 epochs. We also conducted extensive experiments to confirm our analysis and verify the effectiveness of our methods. + +
    + +
    +
    + +
    +
    + +
    + +## Results and Models + +We provide the config files and models for DAB-DETR: [DAB-DETR: Dynamic Anchor Boxes are Better Queries for DETR](https://arxiv.org/abs/2201.12329). + +| Backbone | Model | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :------: | :-----: | :------: | :------------: | :----: | :---------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | DAB-DETR | 50e | | | 42.3 | [config](./dab-detr_r50_8xb2-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/dab_detr/dab-detr_r50_8xb2-50e_coco/dab-detr_r50_8xb2-50e_coco_20221122_120837-c1035c8c.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/dab_detr/dab-detr_r50_8xb2-50e_coco/dab-detr_r50_8xb2-50e_coco_20221122_120837.log.json) | + +## Citation + +```latex +@inproceedings{ + liu2022dabdetr, + title={{DAB}-{DETR}: Dynamic Anchor Boxes are Better Queries for {DETR}}, + author={Shilong Liu and Feng Li and Hao Zhang and Xiao Yang and Xianbiao Qi and Hang Su and Jun Zhu and Lei Zhang}, + booktitle={International Conference on Learning Representations}, + year={2022}, + url={https://openreview.net/forum?id=oMI9PjOb9Jl} +} +``` diff --git a/mmdetection/configs/dab_detr/dab-detr_r50_8xb2-50e_coco.py b/mmdetection/configs/dab_detr/dab-detr_r50_8xb2-50e_coco.py new file mode 100644 index 00000000..314ed97e --- /dev/null +++ b/mmdetection/configs/dab_detr/dab-detr_r50_8xb2-50e_coco.py @@ -0,0 +1,159 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', '../_base_/default_runtime.py' +] +model = dict( + type='DABDETR', + num_queries=300, + with_random_refpoints=False, + num_patterns=0, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(3, ), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=None, + num_outs=1), + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict( + embed_dims=256, num_heads=8, dropout=0., batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0., + act_cfg=dict(type='PReLU')))), + decoder=dict( + num_layers=6, + query_dim=4, + query_scale_type='cond_elewise', + with_modulated_hw_attn=True, + layer_cfg=dict( + self_attn_cfg=dict( + embed_dims=256, + num_heads=8, + attn_drop=0., + proj_drop=0., + cross_attn=False), + cross_attn_cfg=dict( + embed_dims=256, + num_heads=8, + attn_drop=0., + proj_drop=0., + cross_attn=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0., + act_cfg=dict(type='PReLU'))), + return_intermediate=True), + positional_encoding=dict(num_feats=128, temperature=20, normalize=True), + bbox_head=dict( + type='DABDETRHead', + num_classes=80, + embed_dims=256, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2., eps=1e-8), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[[ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True) + ]]), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={'backbone': dict(lr_mult=0.1, decay_mult=1.0)})) + +# learning policy +max_epochs = 50 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[40], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16, enable=False) diff --git a/mmdetection/configs/dab_detr/metafile.yml b/mmdetection/configs/dab_detr/metafile.yml new file mode 100644 index 00000000..94383a04 --- /dev/null +++ b/mmdetection/configs/dab_detr/metafile.yml @@ -0,0 +1,32 @@ +Collections: + - Name: DAB-DETR + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Multi Scale Train + - Gradient Clip + Training Resources: 8x A100 GPUs + Architecture: + - ResNet + - Transformer + Paper: + URL: https://arxiv.org/abs/2201.12329 + Title: 'DAB-DETR: Dynamic Anchor Boxes are Better Queries for DETR' + README: configs/dab_detr/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/f4112c9e5611468ffbd57cfba548fd1289264b52/mmdet/models/detectors/dab_detr.py#L15 + Version: v3.0.0rc6 + +Models: + - Name: dab-detr_r50_8xb2-50e_coco + In Collection: DAB-DETR + Config: configs/dab_detr/dab-detr_r50_8xb2-50e_coco.py + Metadata: + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/dab_detr/dab-detr_r50_8xb2-50e_coco/dab-detr_r50_8xb2-50e_coco_20221122_120837-c1035c8c.pth diff --git a/mmdetection/configs/dcn/README.md b/mmdetection/configs/dcn/README.md new file mode 100644 index 00000000..e287e1d5 --- /dev/null +++ b/mmdetection/configs/dcn/README.md @@ -0,0 +1,48 @@ +# DCN + +> [Deformable Convolutional Networks](https://arxiv.org/abs/1703.06211) + + + +## Abstract + +Convolutional neural networks (CNNs) are inherently limited to model geometric transformations due to the fixed geometric structures in its building modules. In this work, we introduce two new modules to enhance the transformation modeling capacity of CNNs, namely, deformable convolution and deformable RoI pooling. Both are based on the idea of augmenting the spatial sampling locations in the modules with additional offsets and learning the offsets from target tasks, without additional supervision. The new modules can readily replace their plain counterparts in existing CNNs and can be easily trained end-to-end by standard back-propagation, giving rise to deformable convolutional networks. Extensive experiments validate the effectiveness of our approach on sophisticated vision tasks of object detection and semantic segmentation. + +
    + +
    + +## Results and Models + +| Backbone | Model | Style | Conv | Pool | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------------: | :----------: | :-----: | :----------: | :---: | :-----: | :------: | :------------: | :----: | :-----: | :-----------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | Faster | pytorch | dconv(c3-c5) | - | 1x | 4.0 | 17.8 | 41.3 | | [config](./faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200130-d68aed1e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200130_212941.log.json) | +| R-50-FPN | Faster | pytorch | - | dpool | 1x | 5.0 | 17.2 | 38.9 | | [config](./faster-rcnn_r50_fpn_dpool_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_dpool_1x_coco/faster_rcnn_r50_fpn_dpool_1x_coco_20200307-90d3c01d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_dpool_1x_coco/faster_rcnn_r50_fpn_dpool_1x_coco_20200307_203250.log.json) | +| R-101-FPN | Faster | pytorch | dconv(c3-c5) | - | 1x | 6.0 | 12.5 | 42.7 | | [config](./faster-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r101_fpn_dconv_c3-c5_1x_coco/faster_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200203-1377f13d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r101_fpn_dconv_c3-c5_1x_coco/faster_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200203_230019.log.json) | +| X-101-32x4d-FPN | Faster | pytorch | dconv(c3-c5) | - | 1x | 7.3 | 10.0 | 44.5 | | [config](./faster-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco/faster_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco_20200203-4f85c69c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco/faster_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco_20200203_001325.log.json) | +| R-50-FPN | Mask | pytorch | dconv(c3-c5) | - | 1x | 4.5 | 15.4 | 41.8 | 37.4 | [config](./mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200203-4d9ad43b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200203_061339.log.json) | +| R-101-FPN | Mask | pytorch | dconv(c3-c5) | - | 1x | 6.5 | 11.7 | 43.5 | 38.9 | [config](./mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco/mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200216-a71f5bce.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco/mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200216_191601.log.json) | +| R-50-FPN | Cascade | pytorch | dconv(c3-c5) | - | 1x | 4.5 | 14.6 | 43.8 | | [config](./cascade-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_rcnn_r50_fpn_dconv_c3-c5_1x_coco/cascade_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200130-2f1fca44.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_rcnn_r50_fpn_dconv_c3-c5_1x_coco/cascade_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200130_220843.log.json) | +| R-101-FPN | Cascade | pytorch | dconv(c3-c5) | - | 1x | 6.4 | 11.0 | 45.0 | | [config](./cascade-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_rcnn_r101_fpn_dconv_c3-c5_1x_coco/cascade_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200203-3b2f0594.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_rcnn_r101_fpn_dconv_c3-c5_1x_coco/cascade_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200203_224829.log.json) | +| R-50-FPN | Cascade Mask | pytorch | dconv(c3-c5) | - | 1x | 6.0 | 10.0 | 44.4 | 38.6 | [config](./cascade-mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco/cascade_mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200202-42e767a2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco/cascade_mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200202_010309.log.json) | +| R-101-FPN | Cascade Mask | pytorch | dconv(c3-c5) | - | 1x | 8.0 | 8.6 | 45.8 | 39.7 | [config](./cascade-mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco/cascade_mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200204-df0c5f10.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco/cascade_mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200204_134006.log.json) | +| X-101-32x4d-FPN | Cascade Mask | pytorch | dconv(c3-c5) | - | 1x | 9.2 | | 47.3 | 41.1 | [config](./cascade-mask-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_mask_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco-e75f90c8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_mask_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco-20200606_183737.log.json) | +| R-50-FPN (FP16) | Mask | pytorch | dconv(c3-c5) | - | 1x | 3.0 | | 41.9 | 37.5 | [config](./mask-rcnn_r50-dconv-c3-c5_fpn_amp-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fp16/mask_rcnn_r50_fpn_fp16_dconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_fp16_dconv_c3-c5_1x_coco_20210520_180247-c06429d2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fp16/mask_rcnn_r50_fpn_fp16_dconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_fp16_dconv_c3-c5_1x_coco_20210520_180247.log.json) | + +**Notes:** + +- `dconv` denotes deformable convolution, `c3-c5` means adding dconv in resnet stage 3 to 5. `dpool` denotes deformable roi pooling. +- The dcn ops are modified from https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch, which should be more memory efficient and slightly faster. +- (\*) For R-50-FPN (dg=4), dg is short for deformable_group. This model is trained and tested on Amazon EC2 p3dn.24xlarge instance. +- **Memory, Train/Inf time is outdated.** + +## Citation + +```latex +@inproceedings{dai2017deformable, + title={Deformable Convolutional Networks}, + author={Dai, Jifeng and Qi, Haozhi and Xiong, Yuwen and Li, Yi and Zhang, Guodong and Hu, Han and Wei, Yichen}, + booktitle={Proceedings of the IEEE international conference on computer vision}, + year={2017} +} +``` diff --git a/mmdetection/configs/dcn/cascade-mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/cascade-mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..8c0ff989 --- /dev/null +++ b/mmdetection/configs/dcn/cascade-mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcn/cascade-mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/cascade-mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..cfcc5e73 --- /dev/null +++ b/mmdetection/configs/dcn/cascade-mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcn/cascade-mask-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/cascade-mask-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..48b25f62 --- /dev/null +++ b/mmdetection/configs/dcn/cascade-mask-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcn/cascade-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/cascade-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..8a942da7 --- /dev/null +++ b/mmdetection/configs/dcn/cascade-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../cascade_rcnn/cascade-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcn/cascade-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/cascade-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..f6bf5b79 --- /dev/null +++ b/mmdetection/configs/dcn/cascade-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcn/faster-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/faster-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..db44e7e8 --- /dev/null +++ b/mmdetection/configs/dcn/faster-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../faster_rcnn/faster-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..95f20467 --- /dev/null +++ b/mmdetection/configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcn/faster-rcnn_r50_fpn_dpool_1x_coco.py b/mmdetection/configs/dcn/faster-rcnn_r50_fpn_dpool_1x_coco.py new file mode 100644 index 00000000..c65ce5fd --- /dev/null +++ b/mmdetection/configs/dcn/faster-rcnn_r50_fpn_dpool_1x_coco.py @@ -0,0 +1,12 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + _delete_=True, + type='DeformRoIPoolPack', + output_size=7, + output_channels=256), + out_channels=256, + featmap_strides=[4, 8, 16, 32]))) diff --git a/mmdetection/configs/dcn/faster-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/faster-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..e4ed832f --- /dev/null +++ b/mmdetection/configs/dcn/faster-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/dcn/mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..3f36714a --- /dev/null +++ b/mmdetection/configs/dcn/mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcn/mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcn/mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..0b281d41 --- /dev/null +++ b/mmdetection/configs/dcn/mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcn/mask-rcnn_r50-dconv-c3-c5_fpn_amp-1x_coco.py b/mmdetection/configs/dcn/mask-rcnn_r50-dconv-c3-c5_fpn_amp-1x_coco.py new file mode 100644 index 00000000..9d015943 --- /dev/null +++ b/mmdetection/configs/dcn/mask-rcnn_r50-dconv-c3-c5_fpn_amp-1x_coco.py @@ -0,0 +1,10 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) + +# MMEngine support the following two ways, users can choose +# according to convenience +# optim_wrapper = dict(type='AmpOptimWrapper') +_base_.optim_wrapper.type = 'AmpOptimWrapper' diff --git a/mmdetection/configs/dcn/metafile.yml b/mmdetection/configs/dcn/metafile.yml new file mode 100644 index 00000000..4aa35b5d --- /dev/null +++ b/mmdetection/configs/dcn/metafile.yml @@ -0,0 +1,272 @@ +Collections: + - Name: Deformable Convolutional Networks + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Deformable Convolution + Paper: + URL: https://arxiv.org/abs/1703.06211 + Title: "Deformable Convolutional Networks" + README: configs/dcn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/ops/dcn/deform_conv.py#L15 + Version: v2.0.0 + +Models: + - Name: faster-rcnn_r50_fpn_dconv_c3-c5_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/faster-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.0 + inference time (ms/im): + - value: 56.18 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200130-d68aed1e.pth + + - Name: faster-rcnn_r50_fpn_dpool_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/faster-rcnn_r50_fpn_dpool_1x_coco.py + Metadata: + Training Memory (GB): 5.0 + inference time (ms/im): + - value: 58.14 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_dpool_1x_coco/faster_rcnn_r50_fpn_dpool_1x_coco_20200307-90d3c01d.pth + + - Name: faster-rcnn_r101-dconv-c3-c5_fpn_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/faster-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.0 + inference time (ms/im): + - value: 80 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r101_fpn_dconv_c3-c5_1x_coco/faster_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200203-1377f13d.pth + + - Name: faster-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/faster-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.3 + inference time (ms/im): + - value: 100 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco/faster_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco_20200203-4f85c69c.pth + + - Name: mask-rcnn_r50_fpn_dconv_c3-c5_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.5 + inference time (ms/im): + - value: 64.94 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200203-4d9ad43b.pth + + - Name: mask-rcnn_r50_fpn_fp16_dconv_c3-c5_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/mask-rcnn_r50-dconv-c3-c5_fpn_amp-1x_coco.py + Metadata: + Training Techniques: + - SGD with Momentum + - Weight Decay + - Mixed Precision Training + Training Memory (GB): 3.0 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fp16/mask_rcnn_r50_fpn_fp16_dconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_fp16_dconv_c3-c5_1x_coco_20210520_180247-c06429d2.pth + + - Name: mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.5 + inference time (ms/im): + - value: 85.47 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco/mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200216-a71f5bce.pth + + - Name: cascade-rcnn_r50_fpn_dconv_c3-c5_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/cascade-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.5 + inference time (ms/im): + - value: 68.49 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_rcnn_r50_fpn_dconv_c3-c5_1x_coco/cascade_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200130-2f1fca44.pth + + - Name: cascade-rcnn_r101-dconv-c3-c5_fpn_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/cascade-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.4 + inference time (ms/im): + - value: 90.91 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_rcnn_r101_fpn_dconv_c3-c5_1x_coco/cascade_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200203-3b2f0594.pth + + - Name: cascade-mask-rcnn_r50_fpn_dconv_c3-c5_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/cascade-mask-rcnn_r50-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.0 + inference time (ms/im): + - value: 100 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco/cascade_mask_rcnn_r50_fpn_dconv_c3-c5_1x_coco_20200202-42e767a2.pth + + - Name: cascade-mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/cascade-mask-rcnn_r101-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.0 + inference time (ms/im): + - value: 116.28 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco/cascade_mask_rcnn_r101_fpn_dconv_c3-c5_1x_coco_20200204-df0c5f10.pth + + - Name: cascade-mask-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco + In Collection: Deformable Convolutional Networks + Config: configs/dcn/cascade-mask-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 9.2 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/cascade_mask_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_dconv_c3-c5_1x_coco-e75f90c8.pth diff --git a/mmdetection/configs/dcnv2/README.md b/mmdetection/configs/dcnv2/README.md new file mode 100644 index 00000000..7f42c934 --- /dev/null +++ b/mmdetection/configs/dcnv2/README.md @@ -0,0 +1,37 @@ +# DCNv2 + +> [Deformable ConvNets v2: More Deformable, Better Results](https://arxiv.org/abs/1811.11168) + + + +## Abstract + +The superior performance of Deformable Convolutional Networks arises from its ability to adapt to the geometric variations of objects. Through an examination of its adaptive behavior, we observe that while the spatial support for its neural features conforms more closely than regular ConvNets to object structure, this support may nevertheless extend well beyond the region of interest, causing features to be influenced by irrelevant image content. To address this problem, we present a reformulation of Deformable ConvNets that improves its ability to focus on pertinent image regions, through increased modeling power and stronger training. The modeling power is enhanced through a more comprehensive integration of deformable convolution within the network, and by introducing a modulation mechanism that expands the scope of deformation modeling. To effectively harness this enriched modeling capability, we guide network training via a proposed feature mimicking scheme that helps the network to learn features that reflect the object focus and classification power of RCNN features. With the proposed contributions, this new version of Deformable ConvNets yields significant performance gains over the original model and produces leading results on the COCO benchmark for object detection and instance segmentation. + +## Results and Models + +| Backbone | Model | Style | Conv | Pool | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :---------------: | :----: | :-----: | :-----------: | :----: | :-----: | :------: | :------------: | :----: | :-----: | :------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | Faster | pytorch | mdconv(c3-c5) | - | 1x | 4.1 | 17.6 | 41.4 | | [config](./faster-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdconv_c3-c5_1x_coco/faster_rcnn_r50_fpn_mdconv_c3-c5_1x_coco_20200130-d099253b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdconv_c3-c5_1x_coco/faster_rcnn_r50_fpn_mdconv_c3-c5_1x_coco_20200130_222144.log.json) | +| \*R-50-FPN (dg=4) | Faster | pytorch | mdconv(c3-c5) | - | 1x | 4.2 | 17.4 | 41.5 | | [config](./faster-rcnn_r50-mdconv-group4-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdconv_c3-c5_group4_1x_coco/faster_rcnn_r50_fpn_mdconv_c3-c5_group4_1x_coco_20200130-01262257.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdconv_c3-c5_group4_1x_coco/faster_rcnn_r50_fpn_mdconv_c3-c5_group4_1x_coco_20200130_222058.log.json) | +| R-50-FPN | Faster | pytorch | - | mdpool | 1x | 5.8 | 16.6 | 38.7 | | [config](./faster-rcnn_r50_fpn_mdpool_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdpool_1x_coco/faster_rcnn_r50_fpn_mdpool_1x_coco_20200307-c0df27ff.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdpool_1x_coco/faster_rcnn_r50_fpn_mdpool_1x_coco_20200307_203304.log.json) | +| R-50-FPN | Mask | pytorch | mdconv(c3-c5) | - | 1x | 4.5 | 15.1 | 41.5 | 37.1 | [config](./mask-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dcn/mask_rcnn_r50_fpn_mdconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_mdconv_c3-c5_1x_coco_20200203-ad97591f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dcn/mask_rcnn_r50_fpn_mdconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_mdconv_c3-c5_1x_coco_20200203_063443.log.json) | +| R-50-FPN (FP16) | Mask | pytorch | mdconv(c3-c5) | - | 1x | 3.1 | | 42.0 | 37.6 | [config](./mask-rcnn_r50-mdconv-c3-c5_fpn_amp-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fp16/mask_rcnn_r50_fpn_fp16_mdconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_fp16_mdconv_c3-c5_1x_coco_20210520_180434-cf8fefa5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fp16/mask_rcnn_r50_fpn_fp16_mdconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_fp16_mdconv_c3-c5_1x_coco_20210520_180434.log.json) | + +**Notes:** + +- `mdconv` denotes modulated deformable convolution, `c3-c5` means adding dconv in resnet stage 3 to 5. `mdpool` denotes modulated deformable roi pooling. +- The dcn ops are modified from https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch, which should be more memory efficient and slightly faster. +- (\*) For R-50-FPN (dg=4), dg is short for deformable_group. This model is trained and tested on Amazon EC2 p3dn.24xlarge instance. +- **Memory, Train/Inf time is outdated.** + +## Citation + +```latex +@article{zhu2018deformable, + title={Deformable ConvNets v2: More Deformable, Better Results}, + author={Zhu, Xizhou and Hu, Han and Lin, Stephen and Dai, Jifeng}, + journal={arXiv preprint arXiv:1811.11168}, + year={2018} +} +``` diff --git a/mmdetection/configs/dcnv2/faster-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcnv2/faster-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..a7f7e4ee --- /dev/null +++ b/mmdetection/configs/dcnv2/faster-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcnv2/faster-rcnn_r50-mdconv-group4-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcnv2/faster-rcnn_r50-mdconv-group4-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..5c58dbed --- /dev/null +++ b/mmdetection/configs/dcnv2/faster-rcnn_r50-mdconv-group4-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCNv2', deform_groups=4, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py b/mmdetection/configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py new file mode 100644 index 00000000..6198d6d7 --- /dev/null +++ b/mmdetection/configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py @@ -0,0 +1,12 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + _delete_=True, + type='ModulatedDeformRoIPoolPack', + output_size=7, + output_channels=256), + out_channels=256, + featmap_strides=[4, 8, 16, 32]))) diff --git a/mmdetection/configs/dcnv2/mask-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/dcnv2/mask-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..f7a90bbf --- /dev/null +++ b/mmdetection/configs/dcnv2/mask-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/dcnv2/mask-rcnn_r50-mdconv-c3-c5_fpn_amp-1x_coco.py b/mmdetection/configs/dcnv2/mask-rcnn_r50-mdconv-c3-c5_fpn_amp-1x_coco.py new file mode 100644 index 00000000..3b3894c2 --- /dev/null +++ b/mmdetection/configs/dcnv2/mask-rcnn_r50-mdconv-c3-c5_fpn_amp-1x_coco.py @@ -0,0 +1,10 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) + +# MMEngine support the following two ways, users can choose +# according to convenience +# optim_wrapper = dict(type='AmpOptimWrapper') +_base_.optim_wrapper.type = 'AmpOptimWrapper' diff --git a/mmdetection/configs/dcnv2/metafile.yml b/mmdetection/configs/dcnv2/metafile.yml new file mode 100644 index 00000000..dea7bfa1 --- /dev/null +++ b/mmdetection/configs/dcnv2/metafile.yml @@ -0,0 +1,123 @@ +Collections: + - Name: Deformable Convolutional Networks v2 + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Deformable Convolution + Paper: + URL: https://arxiv.org/abs/1811.11168 + Title: "Deformable ConvNets v2: More Deformable, Better Results" + README: configs/dcnv2/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/ops/dcn/deform_conv.py#L15 + Version: v2.0.0 + +Models: + - Name: faster-rcnn_r50_fpn_mdconv_c3-c5_1x_coco + In Collection: Deformable Convolutional Networks v2 + Config: configs/dcnv2/faster-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.1 + inference time (ms/im): + - value: 56.82 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdconv_c3-c5_1x_coco/faster_rcnn_r50_fpn_mdconv_c3-c5_1x_coco_20200130-d099253b.pth + + - Name: faster-rcnn_r50_fpn_mdconv_c3-c5_group4_1x_coco + In Collection: Deformable Convolutional Networks v2 + Config: configs/dcnv2/faster-rcnn_r50-mdconv-group4-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.2 + inference time (ms/im): + - value: 57.47 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdconv_c3-c5_group4_1x_coco/faster_rcnn_r50_fpn_mdconv_c3-c5_group4_1x_coco_20200130-01262257.pth + + - Name: faster-rcnn_r50_fpn_mdpool_1x_coco + In Collection: Deformable Convolutional Networks v2 + Config: configs/dcnv2/faster-rcnn_r50_fpn_mdpool_1x_coco.py + Metadata: + Training Memory (GB): 5.8 + inference time (ms/im): + - value: 60.24 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/faster_rcnn_r50_fpn_mdpool_1x_coco/faster_rcnn_r50_fpn_mdpool_1x_coco_20200307-c0df27ff.pth + + - Name: mask-rcnn_r50_fpn_mdconv_c3-c5_1x_coco + In Collection: Deformable Convolutional Networks v2 + Config: configs/dcnv2/mask-rcnn_r50-mdconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.5 + inference time (ms/im): + - value: 66.23 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dcn/mask_rcnn_r50_fpn_mdconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_mdconv_c3-c5_1x_coco_20200203-ad97591f.pth + + - Name: mask-rcnn_r50_fpn_fp16_mdconv_c3-c5_1x_coco + In Collection: Deformable Convolutional Networks v2 + Config: configs/dcnv2/mask-rcnn_r50-mdconv-c3-c5_fpn_amp-1x_coco.py + Metadata: + Training Memory (GB): 3.1 + Training Techniques: + - SGD with Momentum + - Weight Decay + - Mixed Precision Training + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fp16/mask_rcnn_r50_fpn_fp16_mdconv_c3-c5_1x_coco/mask_rcnn_r50_fpn_fp16_mdconv_c3-c5_1x_coco_20210520_180434-cf8fefa5.pth diff --git a/mmdetection/configs/ddod/README.md b/mmdetection/configs/ddod/README.md new file mode 100644 index 00000000..d5ea9cd0 --- /dev/null +++ b/mmdetection/configs/ddod/README.md @@ -0,0 +1,31 @@ +# DDOD + +> [Disentangle Your Dense Object Detector](https://arxiv.org/pdf/2107.02963.pdf) + + + +## Abstract + +Deep learning-based dense object detectors have achieved great success in the past few years and have been applied to numerous multimedia applications such as video understanding. However, the current training pipeline for dense detectors is compromised to lots of conjunctions that may not hold. In this paper, we investigate three such important conjunctions: 1) only samples assigned as positive in classification head are used to train the regression head; 2) classification and regression share the same input feature and computational fields defined by the parallel head architecture; and 3) samples distributed in different feature pyramid layers are treated equally when computing the loss. We first carry out a series of pilot experiments to show disentangling such conjunctions can lead to persistent performance improvement. Then, based on these findings, we propose Disentangled Dense Object Detector(DDOD), in which simple and effective disentanglement mechanisms are designed and integrated into the current state-of-the-art dense object detectors. Extensive experiments on MS COCO benchmark show that our approach can lead to 2.0 mAP, 2.4 mAP and 2.2 mAP absolute improvements on RetinaNet, FCOS, and ATSS baselines with negligible extra overhead. Notably, our best model reaches 55.0 mAP on the COCO test-dev set and 93.5 AP on the hard subset of WIDER FACE, achieving new state-of-the-art performance on these two competitive benchmarks. Code is available at https://github.com/zehuichen123/DDOD. + +
    + +
    + +## Results and Models + +| Model | Backbone | Style | Lr schd | Mem (GB) | box AP | Config | Download | +| :-------: | :------: | :-----: | :-----: | :------: | :----: | :---------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| DDOD-ATSS | R-50 | pytorch | 1x | 3.4 | 41.7 | [config](./ddod_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ddod/ddod_r50_fpn_1x_coco/ddod_r50_fpn_1x_coco_20220523_223737-29b2fc67.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ddod/ddod_r50_fpn_1x_coco/ddod_r50_fpn_1x_coco_20220523_223737.log.json) | + +## Citation + +```latex +@inproceedings{chen2021disentangle, +title={Disentangle Your Dense Object Detector}, +author={Chen, Zehui and Yang, Chenhongyi and Li, Qiaofei and Zhao, Feng and Zha, Zheng-Jun and Wu, Feng}, +booktitle={Proceedings of the 29th ACM International Conference on Multimedia}, +pages={4939--4948}, +year={2021} +} +``` diff --git a/mmdetection/configs/ddod/ddod_r50_fpn_1x_coco.py b/mmdetection/configs/ddod/ddod_r50_fpn_1x_coco.py new file mode 100644 index 00000000..fed1116b --- /dev/null +++ b/mmdetection/configs/ddod/ddod_r50_fpn_1x_coco.py @@ -0,0 +1,72 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + type='DDOD', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='DDODHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + loss_iou=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + train_cfg=dict( + # assigner is mean cls_assigner + assigner=dict(type='ATSSAssigner', topk=9, alpha=0.8), + reg_assigner=dict(type='ATSSAssigner', topk=9, alpha=0.5), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/ddod/metafile.yml b/mmdetection/configs/ddod/metafile.yml new file mode 100644 index 00000000..c2239500 --- /dev/null +++ b/mmdetection/configs/ddod/metafile.yml @@ -0,0 +1,33 @@ +Collections: + - Name: DDOD + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - DDOD + - FPN + - ResNet + Paper: + URL: https://arxiv.org/pdf/2107.02963.pdf + Title: 'Disentangle Your Dense Object Detector' + README: configs/ddod/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.25.0/mmdet/models/detectors/ddod.py#L6 + Version: v2.25.0 + +Models: + - Name: ddod_r50_fpn_1x_coco + In Collection: DDOD + Config: configs/ddod/ddod_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.4 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ddod/ddod_r50_fpn_1x_coco/ddod_r50_fpn_1x_coco_20220523_223737-29b2fc67.pth diff --git a/mmdetection/configs/ddq/README.md b/mmdetection/configs/ddq/README.md new file mode 100644 index 00000000..3f6f459c --- /dev/null +++ b/mmdetection/configs/ddq/README.md @@ -0,0 +1,39 @@ +# DDQ + +> [Dense Distinct Query for End-to-End Object Detection](https://arxiv.org/abs/2303.12776) + + + +## Abstract + + + +One-to-one label assignment in object detection has successfully obviated the need for non-maximum suppression (NMS) as postprocessing and makes the pipeline end-to-end. However, it triggers a new dilemma as the widely used sparse queries cannot guarantee a high recall, while dense queries inevitably bring more similar queries and encounter optimization difficulties. As both sparse and dense queries are problematic, then what are the expected queries in end-to-end object detection? This paper shows that the solution should be Dense Distinct Queries (DDQ). Concretely, we first lay dense queries like traditional detectors and then select distinct ones for one-to-one assignments. DDQ blends the advantages of traditional and recent end-to-end detectors and significantly improves the performance of various detectors including FCN, R-CNN, and DETRs. Most impressively, DDQ-DETR achieves 52.1 AP on MS-COCO dataset within 12 epochs using a ResNet-50 backbone, outperforming all existing detectors in the same setting. DDQ also shares the benefit of end-to-end detectors in crowded scenes and achieves 93.8 AP on CrowdHuman. We hope DDQ can inspire researchers to consider the complementarity between traditional methods and end-to-end detectors. + +![ddq_arch](https://github.com/open-mmlab/mmdetection/assets/33146359/5ca9f11b-b6f3-454f-a2d1-3009ee337bbc) + +## Results and Models + +| Model | Backbone | Lr schd | Augmentation | box AP(val) | Config | Download | +| :---------------: | :------: | :-----: | :----------: | :---------: | :------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| DDQ DETR-4scale | R-50 | 12e | DETR | 51.4 | [config](./ddq-detr-4scale_r50_8xb2-12e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq-detr-4scale_r50_8xb2-12e_coco/ddq-detr-4scale_r50_8xb2-12e_coco_20230809_170711-42528127.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq-detr-4scale_r50_8xb2-12e_coco/ddq-detr-4scale_r50_8xb2-12e_coco_20230809_170711.log.json) | +| DDQ DETR-5scale\* | R-50 | 12e | DETR | 52.1 | [config](./ddq-detr-5scale_r50_8xb2-12e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq_detr_5scale_coco_1x.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq_detr_5scale_coco_1x_20230319_103307.log) | +| DDQ DETR-4scale\* | Swin-L | 30e | DETR | 58.7 | [config](./ddq-detr-4scale_swinl_8xb2-30e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq_detr_swinl_30e.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq_detr_swinl_30e_20230316_221721_20230318_143554.log) | + +**Note** + +- Models labeled * are not trained by us, but from [DDQ official website](https://github.com/jshilong/DDQ). +- We find that the performance is unstable and may fluctuate by about 0.2 mAP. + +## Citation + +```latex +@InProceedings{Zhang_2023_CVPR, + author = {Zhang, Shilong and Wang, Xinjiang and Wang, Jiaqi and Pang, Jiangmiao and Lyu, Chengqi and Zhang, Wenwei and Luo, Ping and Chen, Kai}, + title = {Dense Distinct Query for End-to-End Object Detection}, + booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, + month = {June}, + year = {2023}, + pages = {7329-7338} +} +``` diff --git a/mmdetection/configs/ddq/ddq-detr-4scale_r50_8xb2-12e_coco.py b/mmdetection/configs/ddq/ddq-detr-4scale_r50_8xb2-12e_coco.py new file mode 100644 index 00000000..5e64afc0 --- /dev/null +++ b/mmdetection/configs/ddq/ddq-detr-4scale_r50_8xb2-12e_coco.py @@ -0,0 +1,170 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', '../_base_/default_runtime.py' +] +model = dict( + type='DDQDETR', + num_queries=900, # num_matching_queries + # ratio of num_dense queries to num_queries + dense_topk_ratio=1.5, + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[512, 1024, 2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + # encoder class name: DeformableDetrTransformerEncoder + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0))), # 0.1 for DeformDETR + # decoder class name: DDQTransformerDecoder + decoder=dict( + # `num_layers` >= 2, because attention masks of the last + # `num_layers` - 1 layers are used for distinct query selection + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_heads=8, + dropout=0.0), # 0.1 for DeformDETR + cross_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0)), # 0.1 for DeformDETR + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, + normalize=True, + offset=0.0, # -0.5 for DeformDETR + temperature=20), # 10000 for DeformDETR + bbox_head=dict( + type='DDQDETRHead', + num_classes=80, + sync_cls_avg_factor=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( + label_noise_scale=0.5, + box_noise_scale=1.0, + group_cfg=dict(dynamic=True, num_groups=None, num_dn_queries=100)), + dqs_cfg=dict(type='nms', iou_threshold=0.8), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.05), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.1)})) + +# learning policy +max_epochs = 12 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.0001, + by_epoch=False, + begin=0, + end=2000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/ddq/ddq-detr-4scale_swinl_8xb2-30e_coco.py b/mmdetection/configs/ddq/ddq-detr-4scale_swinl_8xb2-30e_coco.py new file mode 100644 index 00000000..d8636494 --- /dev/null +++ b/mmdetection/configs/ddq/ddq-detr-4scale_swinl_8xb2-30e_coco.py @@ -0,0 +1,177 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', '../_base_/default_runtime.py' +] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa: E501 +model = dict( + type='DDQDETR', + num_queries=900, # num_matching_queries + # ratio of num_dense queries to num_queries + dense_topk_ratio=1.5, + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict( + type='ChannelMapper', + in_channels=[384, 768, 1536], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + # encoder class name: DeformableDetrTransformerEncoder + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0))), # 0.1 for DeformDETR + # decoder class name: DDQTransformerDecoder + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_heads=8, + dropout=0.0), # 0.1 for DeformDETR + cross_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0)), # 0.1 for DeformDETR + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, + normalize=True, + offset=0.0, # -0.5 for DeformDETR + temperature=20), # 10000 for DeformDETR + bbox_head=dict( + type='DDQDETRHead', + num_classes=80, + sync_cls_avg_factor=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( + label_noise_scale=0.5, + box_noise_scale=1.0, + group_cfg=dict(dynamic=True, num_groups=None, num_dn_queries=100)), + dqs_cfg=dict(type='nms', iou_threshold=0.8), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.05), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.05)})) + +# learning policy +max_epochs = 30 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.0001, + by_epoch=False, + begin=0, + end=2000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[20, 26], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/ddq/ddq-detr-5scale_r50_8xb2-12e_coco.py b/mmdetection/configs/ddq/ddq-detr-5scale_r50_8xb2-12e_coco.py new file mode 100644 index 00000000..3c38f553 --- /dev/null +++ b/mmdetection/configs/ddq/ddq-detr-5scale_r50_8xb2-12e_coco.py @@ -0,0 +1,171 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', '../_base_/default_runtime.py' +] +model = dict( + type='DDQDETR', + num_queries=900, # num_matching_queries + # ratio of num_dense queries to num_queries + dense_topk_ratio=1.5, + with_box_refine=True, + as_two_stage=True, + num_feature_levels=5, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[256, 512, 1024, 2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=5), + # encoder class name: DeformableDetrTransformerEncoder + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=5, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0))), # 0.1 for DeformDETR + # decoder class name: DDQTransformerDecoder + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_heads=8, + dropout=0.0), # 0.1 for DeformDETR + cross_attn_cfg=dict(embed_dims=256, num_levels=5, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0)), # 0.1 for DeformDETR + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, + normalize=True, + offset=0.0, # -0.5 for DeformDETR + temperature=20), # 10000 for DeformDETR + bbox_head=dict( + type='DDQDETRHead', + num_classes=80, + sync_cls_avg_factor=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( + label_noise_scale=0.5, + box_noise_scale=1.0, + group_cfg=dict(dynamic=True, num_groups=None, num_dn_queries=100)), + dqs_cfg=dict(type='nms', iou_threshold=0.8), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.05), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.1)})) + +# learning policy +max_epochs = 12 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.0001, + by_epoch=False, + begin=0, + end=2000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/ddq/metafile.yml b/mmdetection/configs/ddq/metafile.yml new file mode 100644 index 00000000..bd33abe1 --- /dev/null +++ b/mmdetection/configs/ddq/metafile.yml @@ -0,0 +1,56 @@ +Collections: + - Name: DDQ + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Multi Scale Train + - Gradient Clip + Training Resources: 8x A100 GPUs + Architecture: + - ResNet + - Transformer + Paper: + URL: https://arxiv.org/abs/2303.12776 + Title: 'Dense Distinct Query for End-to-End Object Detection' + README: configs/ddq/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/dev-3.x/mmdet/models/detectors/ddq_detr.py#L21 + Version: dev-3.x + +Models: + - Name: ddq-detr-4scale_r50_8xb2-12e_coco + In Collection: DDQ + Config: configs/ddq/ddq-detr-4scale_r50_8xb2-12e_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 51.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq-detr-4scale_r50_8xb2-12e_coco/ddq-detr-4scale_r50_8xb2-12e_coco_20230809_170711-42528127.pth + + - Name: ddq-detr-5scale_r50_8xb2-12e_coco + In Collection: DDQ + Config: configs/dino/ddq-detr-5scale_r50_8xb2-12e_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 52.1 + Weights: https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq_detr_5scale_coco_1x.pth + + - Name: ddq-detr-4scale_swinl_8xb2-30e_coco + In Collection: DDQ + Config: configs/dino/ddq-detr-4scale_swinl_8xb2-30e_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 58.7 + Weights: https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq_detr_swinl_30e.pth diff --git a/mmdetection/configs/deepfashion/README.md b/mmdetection/configs/deepfashion/README.md new file mode 100644 index 00000000..844e29d6 --- /dev/null +++ b/mmdetection/configs/deepfashion/README.md @@ -0,0 +1,70 @@ +# DeepFashion + +> [DeepFashion: Powering Robust Clothes Recognition and Retrieval With Rich Annotations](https://openaccess.thecvf.com/content_cvpr_2016/html/Liu_DeepFashion_Powering_Robust_CVPR_2016_paper.html) + + + +## Abstract + +Recent advances in clothes recognition have been driven by the construction of clothes datasets. Existing datasets are limited in the amount of annotations and are difficult to cope with the various challenges in real-world applications. In this work, we introduce DeepFashion, a large-scale clothes dataset with comprehensive annotations. It contains over 800,000 images, which are richly annotated with massive attributes, clothing landmarks, and correspondence of images taken under different scenarios including store, street snapshot, and consumer. Such rich annotations enable the development of powerful algorithms in clothes recognition and facilitating future researches. To demonstrate the advantages of DeepFashion, we propose a new deep model, namely FashionNet, which learns clothing features by jointly predicting clothing attributes and landmarks. The estimated landmarks are then employed to pool or gate the learned features. It is optimized in an iterative manner. Extensive experiments demonstrate the effectiveness of FashionNet and the usefulness of DeepFashion. + +
    + +
    + +## Introduction + +[MMFashion](https://github.com/open-mmlab/mmfashion) develops "fashion parsing and segmentation" module +based on the dataset +[DeepFashion-Inshop](https://drive.google.com/drive/folders/0B7EVK8r0v71pVDZFQXRsMDZCX1E?usp=sharing). +Its annotation follows COCO style. +To use it, you need to first download the data. Note that we only use "img_highres" in this task. +The file tree should be like this: + +```sh +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── DeepFashion +│ │ ├── In-shop +| │ │ ├── Anno +| │ │ │   ├── segmentation +| │ │ │   | ├── DeepFashion_segmentation_train.json +| │ │ │   | ├── DeepFashion_segmentation_query.json +| │ │ │   | ├── DeepFashion_segmentation_gallery.json +| │ │ │   ├── list_bbox_inshop.txt +| │ │ │   ├── list_description_inshop.json +| │ │ │   ├── list_item_inshop.txt +| │ │ │   └── list_landmarks_inshop.txt +| │ │ ├── Eval +| │ │ │ └── list_eval_partition.txt +| │ │ ├── Img +| │ │ │ ├── img +| │ │ │ │ ├──XXX.jpg +| │ │ │ ├── img_highres +| │ │ │ └── ├──XXX.jpg + +``` + +After that you can train the Mask RCNN r50 on DeepFashion-In-shop dataset by launching training with the `mask_rcnn_r50_fpn_1x.py` config +or creating your own config file. + +## Results and Models + +| Backbone | Model type | Dataset | bbox detection Average Precision | segmentation Average Precision | Config | Download (Google) | +| :------: | :--------: | :-----------------: | :------------------------------: | :----------------------------: | :----------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ResNet50 | Mask RCNN | DeepFashion-In-shop | 0.599 | 0.584 | [config](./mask-rcnn_r50_fpn_15e_deepfashion.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/deepfashion/mask_rcnn_r50_fpn_15e_deepfashion/mask_rcnn_r50_fpn_15e_deepfashion_20200329_192752.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/deepfashion/mask_rcnn_r50_fpn_15e_deepfashion/20200329_192752.log.json) | + +## Citation + +```latex +@inproceedings{liuLQWTcvpr16DeepFashion, + author = {Liu, Ziwei and Luo, Ping and Qiu, Shi and Wang, Xiaogang and Tang, Xiaoou}, + title = {DeepFashion: Powering Robust Clothes Recognition and Retrieval with Rich Annotations}, + booktitle = {Proceedings of IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, + month = {June}, + year = {2016} +} +``` diff --git a/mmdetection/configs/deepfashion/mask-rcnn_r50_fpn_15e_deepfashion.py b/mmdetection/configs/deepfashion/mask-rcnn_r50_fpn_15e_deepfashion.py new file mode 100644 index 00000000..403b18a4 --- /dev/null +++ b/mmdetection/configs/deepfashion/mask-rcnn_r50_fpn_15e_deepfashion.py @@ -0,0 +1,23 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/deepfashion.py', '../_base_/schedules/schedule_1x.py', + '../_base_/default_runtime.py' +] +model = dict( + roi_head=dict( + bbox_head=dict(num_classes=15), mask_head=dict(num_classes=15))) +# runtime settings +max_epochs = 15 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] diff --git a/mmdetection/configs/deepsort/README.md b/mmdetection/configs/deepsort/README.md new file mode 100644 index 00000000..e50ec17e --- /dev/null +++ b/mmdetection/configs/deepsort/README.md @@ -0,0 +1,109 @@ +# Simple online and realtime tracking with a deep association metric + +## Abstract + + + +Simple Online and Realtime Tracking (SORT) is a pragmatic approach to multiple object tracking with a focus on simple, effective algorithms. In this paper, we integrate appearance information to improve the performance of SORT. Due to this extension we are able to track objects through longer periods of occlusions, effectively reducing the number of identity switches. In spirit of the original framework we place much of the computational complexity into an offline pre-training stage where we learn a deep association metric on a largescale person re-identification dataset. During online application, we establish measurement-to-track associations using nearest neighbor queries in visual appearance space. Experimental evaluation shows that our extensions reduce the number of identity switches by 45%, achieving overall competitive performance at high frame rates. + + + +
    + +
    + +## Results and models on MOT17 + +Currently we do not support training ReID models for DeepSORT. +We directly use the ReID model from [Tracktor](https://github.com/phil-bergmann/tracking_wo_bnw). These missed features will be supported in the future. + +| Method | Detector | ReID | Train Set | Test Set | Public | Inf time (fps) | HOTA | MOTA | IDF1 | FP | FN | IDSw. | Config | Download | +| :------: | :----------------: | :--: | :--------: | :------: | :----: | :------------: | :--: | :--: | :--: | :---: | :---: | :---: | :--------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| DeepSORT | R50-FasterRCNN-FPN | R50 | half-train | half-val | N | 13.8 | 57.0 | 63.7 | 69.5 | 15063 | 40323 | 3276 | [config](deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py) | [detector](https://download.openmmlab.com/mmtracking/mot/faster_rcnn/faster-rcnn_r50_fpn_4e_mot17-half-64ee2ed4.pth) [reid](https://download.openmmlab.com/mmtracking/mot/reid/tracktor_reid_r50_iter25245-a452f51f.pth) | + +## Get started + +### 1. Development Environment Setup + +Tracking Development Environment Setup can refer to this [document](../../docs/en/get_started.md). + +### 2. Dataset Prepare + +Tracking Dataset Prepare can refer to this [document](../../docs/en/user_guides/tracking_dataset_prepare.md). + +### 3. Training + +We implement DeepSORT with independent detector and ReID models. +Note that, due to the influence of parameters such as learning rate in default configuration file, +we recommend using 8 GPUs for training in order to reproduce accuracy. + +You can train the detector as follows. + +```shell script +# Training Faster R-CNN on mot17-half-train dataset with following command. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_train.sh configs/sort/faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 +``` + +If you want to know about more detailed usage of `train.py/dist_train.sh/slurm_train.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 4. Testing and evaluation + +### 4.1 Example on MOTxx-halfval dataset + +**4.1.1 use separate trained detector and reid model to evaluating and testing** + +```shell +# Example 1: Test on motXX-half-val set. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_test_tracking.sh configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 --detector ${DETECTOR_CHECKPOINT_PATH} --reid ${REID_CHECKPOINT_PATH} +``` + +**4.1.2 use video_baesd to evaluating and testing** + +we also provide two_ways(img_based or video_based) to evaluating and testing. +if you want to use video_based to evaluating and testing, you can modify config as follows + +``` +val_dataloader = dict( + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False)) +``` + +### 4.2 Example on MOTxx-test dataset + +If you want to get the results of the [MOT Challenge](https://motchallenge.net/) test set, +please use the following command to generate result files that can be used for submission. +It will be stored in `./mot_17_test_res`, you can modify the saved path in `test_evaluator` of the config. + +```shell script +# Example 2: Test on motxx-test set +# The number after config file represents the number of GPUs used +bash tools/dist_test_tracking.sh configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17test 8 --detector ${DETECTOR_CHECKPOINT_PATH} --reid ${REID_CHECKPOINT_PATH} +``` + +If you want to know about more detailed usage of `test_tracking.py/dist_test_tracking.sh/slurm_test_tracking.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 5.Inference + +Use a single GPU to predict a video and save it as a video. + +```shell +python demo/mot_demo.py demo/demo_mot.mp4 configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17test --detector ${DETECTOR_CHECKPOINT_PATH} --reid ${REID_CHECKPOINT_PATH} --out mot.mp4 +``` + +## Citation + + + +```latex +@inproceedings{wojke2017simple, + title={Simple online and realtime tracking with a deep association metric}, + author={Wojke, Nicolai and Bewley, Alex and Paulus, Dietrich}, + booktitle={2017 IEEE international conference on image processing (ICIP)}, + pages={3645--3649}, + year={2017}, + organization={IEEE} +} +``` diff --git a/mmdetection/configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py b/mmdetection/configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..70d33938 --- /dev/null +++ b/mmdetection/configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py @@ -0,0 +1,85 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/mot_challenge.py', '../_base_/default_runtime.py' +] + +default_hooks = dict( + logger=dict(type='LoggerHook', interval=1), + visualization=dict(type='TrackVisualizationHook', draw=False)) + +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='TrackLocalVisualizer', vis_backends=vis_backends, name='visualizer') +# custom hooks +custom_hooks = [ + # Synchronize model buffers such as running_mean and running_var in BN + # at the end of each epoch + dict(type='SyncBuffersHook') +] + +detector = _base_.model +detector.pop('data_preprocessor') +detector.rpn_head.bbox_coder.update(dict(clip_border=False)) +detector.roi_head.bbox_head.update(dict(num_classes=1)) +detector.roi_head.bbox_head.bbox_coder.update(dict(clip_border=False)) +detector['init_cfg'] = dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmtracking/mot/faster_rcnn/' + 'faster-rcnn_r50_fpn_4e_mot17-half-64ee2ed4.pth') +del _base_.model + +model = dict( + type='DeepSORT', + data_preprocessor=dict( + type='TrackDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + detector=detector, + reid=dict( + type='BaseReID', + data_preprocessor=dict(type='mmpretrain.ClsDataPreprocessor'), + backbone=dict( + type='mmpretrain.ResNet', + depth=50, + num_stages=4, + out_indices=(3, ), + style='pytorch'), + neck=dict(type='GlobalAveragePooling', kernel_size=(8, 4), stride=1), + head=dict( + type='LinearReIDHead', + num_fcs=1, + in_channels=2048, + fc_channels=1024, + out_channels=128, + num_classes=380, + loss_cls=dict(type='mmpretrain.CrossEntropyLoss', loss_weight=1.0), + loss_triplet=dict(type='TripletLoss', margin=0.3, loss_weight=1.0), + norm_cfg=dict(type='BN1d'), + act_cfg=dict(type='ReLU')), + init_cfg=dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmtracking/mot/reid/tracktor_reid_r50_iter25245-a452f51f.pth' # noqa: E501 + )), + tracker=dict( + type='SORTTracker', + motion=dict(type='KalmanFilter', center_only=False), + obj_score_thr=0.5, + reid=dict( + num_samples=10, + img_scale=(256, 128), + img_norm_cfg=None, + match_score_thr=2.0), + match_iou_thr=0.5, + momentums=None, + num_tentatives=2, + num_frames_retain=100)) + +train_dataloader = None + +train_cfg = None +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') diff --git a/mmdetection/configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17test.py b/mmdetection/configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17test.py new file mode 100644 index 00000000..687ce7ad --- /dev/null +++ b/mmdetection/configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17test.py @@ -0,0 +1,15 @@ +_base_ = [ + './deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain' + '_test-mot17halfval.py' +] + +# dataloader +val_dataloader = dict( + dataset=dict(ann_file='annotations/train_cocoformat.json')) +test_dataloader = dict( + dataset=dict( + ann_file='annotations/test_cocoformat.json', + data_prefix=dict(img_path='test'))) + +# evaluator +test_evaluator = dict(format_only=True, outfile_prefix='./mot_17_test_res') diff --git a/mmdetection/configs/deepsort/metafile.yml b/mmdetection/configs/deepsort/metafile.yml new file mode 100644 index 00000000..2feb358e --- /dev/null +++ b/mmdetection/configs/deepsort/metafile.yml @@ -0,0 +1,37 @@ +Collections: + - Name: DeepSORT + Metadata: + Training Techniques: + - SGD with Momentum + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + - FPN + Paper: + URL: https://arxiv.org/abs/1703.07402 + Title: Simple Online and Realtime Tracking with a Deep Association Metric + README: configs/deepsort/README.md + +Models: + - Name: deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval + In Collection: DeepSORT + Config: configs/deepsort/deepsort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py + Metadata: + Training Data: MOT17-half-train + inference time (ms/im): + - value: 72.5 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (640, 1088) + Results: + - Task: Multiple Object Tracking + Dataset: MOT17-half-val + Metrics: + MOTA: 63.7 + IDF1: 69.5 + HOTA: 57.0 + Weights: + - https://download.openmmlab.com/mmtracking/mot/faster_rcnn/faster-rcnn_r50_fpn_4e_mot17-half-64ee2ed4.pth + - https://download.openmmlab.com/mmtracking/mot/reid/tracktor_reid_r50_iter25245-a452f51f.pth diff --git a/mmdetection/configs/deformable_detr/README.md b/mmdetection/configs/deformable_detr/README.md new file mode 100644 index 00000000..ca897cdb --- /dev/null +++ b/mmdetection/configs/deformable_detr/README.md @@ -0,0 +1,41 @@ +# Deformable DETR + +> [Deformable DETR: Deformable Transformers for End-to-End Object Detection](https://arxiv.org/abs/2010.04159) + + + +## Abstract + +DETR has been recently proposed to eliminate the need for many hand-designed components in object detection while demonstrating good performance. However, it suffers from slow convergence and limited feature spatial resolution, due to the limitation of Transformer attention modules in processing image feature maps. To mitigate these issues, we proposed Deformable DETR, whose attention modules only attend to a small set of key sampling points around a reference. Deformable DETR can achieve better performance than DETR (especially on small objects) with 10 times less training epochs. Extensive experiments on the COCO benchmark demonstrate the effectiveness of our approach. + +
    + +
    + +## Results and Models + +| Backbone | Model | Lr schd | box AP | Config | Download | +| :------: | :---------------------------------: | :-----: | :----: | :---------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | Deformable DETR | 50e | 44.3 | [config](./deformable-detr_r50_16xb2-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/deformable_detr/deformable-detr_r50_16xb2-50e_coco/deformable-detr_r50_16xb2-50e_coco_20221029_210934-6bc7d21b.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/deformable_detr/deformable-detr_r50_16xb2-50e_coco/deformable-detr_r50_16xb2-50e_coco_20221029_210934.log.json) | +| R-50 | + iterative bounding box refinement | 50e | 46.2 | [config](./deformable-detr-refine_r50_16xb2-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/deformable_detr/deformable-detr-refine_r50_16xb2-50e_coco/deformable-detr-refine_r50_16xb2-50e_coco_20221022_225303-844e0f93.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/deformable_detr/deformable-detr-refine_r50_16xb2-50e_coco/deformable-detr-refine_r50_16xb2-50e_coco_20221022_225303.log.json) | +| R-50 | ++ two-stage Deformable DETR | 50e | 47.0 | [config](./deformable-detr-refine-twostage_r50_16xb2-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/deformable_detr/deformable-detr-refine-twostage_r50_16xb2-50e_coco/deformable-detr-refine-twostage_r50_16xb2-50e_coco_20221021_184714-acc8a5ff.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/deformable_detr/deformable-detr-refine-twostage_r50_16xb2-50e_coco/deformable-detr-refine-twostage_r50_16xb2-50e_coco_20221021_184714.log.json) | + +### NOTE + +1. All models are trained with batch size 32. +2. The performance is unstable. `Deformable DETR` and `iterative bounding box refinement` may fluctuate about 0.3 mAP. `two-stage Deformable DETR` may fluctuate about 0.2 mAP. + +## Citation + +We provide the config files for Deformable DETR: [Deformable DETR: Deformable Transformers for End-to-End Object Detection](https://arxiv.org/abs/2010.04159). + +```latex +@inproceedings{ +zhu2021deformable, +title={Deformable DETR: Deformable Transformers for End-to-End Object Detection}, +author={Xizhou Zhu and Weijie Su and Lewei Lu and Bin Li and Xiaogang Wang and Jifeng Dai}, +booktitle={International Conference on Learning Representations}, +year={2021}, +url={https://openreview.net/forum?id=gZ9hCDWe6ke} +} +``` diff --git a/mmdetection/configs/deformable_detr/deformable-detr-refine-twostage_r50_16xb2-50e_coco.py b/mmdetection/configs/deformable_detr/deformable-detr-refine-twostage_r50_16xb2-50e_coco.py new file mode 100644 index 00000000..eeb67fc9 --- /dev/null +++ b/mmdetection/configs/deformable_detr/deformable-detr-refine-twostage_r50_16xb2-50e_coco.py @@ -0,0 +1,2 @@ +_base_ = 'deformable-detr-refine_r50_16xb2-50e_coco.py' +model = dict(as_two_stage=True) diff --git a/mmdetection/configs/deformable_detr/deformable-detr-refine_r50_16xb2-50e_coco.py b/mmdetection/configs/deformable_detr/deformable-detr-refine_r50_16xb2-50e_coco.py new file mode 100644 index 00000000..b968674f --- /dev/null +++ b/mmdetection/configs/deformable_detr/deformable-detr-refine_r50_16xb2-50e_coco.py @@ -0,0 +1,2 @@ +_base_ = 'deformable-detr_r50_16xb2-50e_coco.py' +model = dict(with_box_refine=True) diff --git a/mmdetection/configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py b/mmdetection/configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py new file mode 100644 index 00000000..e0dee411 --- /dev/null +++ b/mmdetection/configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py @@ -0,0 +1,156 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', '../_base_/default_runtime.py' +] +model = dict( + type='DeformableDETR', + num_queries=300, + num_feature_levels=4, + with_box_refine=False, + as_two_stage=False, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[512, 1024, 2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + encoder=dict( # DeformableDetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DeformableDetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=1024, ffn_drop=0.1))), + decoder=dict( # DeformableDetrTransformerDecoder + num_layers=6, + return_intermediate=True, + layer_cfg=dict( # DeformableDetrTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + cross_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=1024, ffn_drop=0.1)), + post_norm_cfg=None), + positional_encoding=dict(num_feats=128, normalize=True, offset=-0.5), + bbox_head=dict( + type='DeformableDETRHead', + num_classes=80, + sync_cls_avg_factor=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=2.0), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=100)) + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] +train_dataloader = dict( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1), + 'sampling_offsets': dict(lr_mult=0.1), + 'reference_points': dict(lr_mult=0.1) + })) + +# learning policy +max_epochs = 50 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[40], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (16 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=32) diff --git a/mmdetection/configs/deformable_detr/metafile.yml b/mmdetection/configs/deformable_detr/metafile.yml new file mode 100644 index 00000000..a30c9791 --- /dev/null +++ b/mmdetection/configs/deformable_detr/metafile.yml @@ -0,0 +1,56 @@ +Collections: + - Name: Deformable DETR + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Multi Scale Train + - Gradient Clip + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + - Transformer + Paper: + URL: https://openreview.net/forum?id=gZ9hCDWe6ke + Title: 'Deformable DETR: Deformable Transformers for End-to-End Object Detection' + README: configs/deformable_detr/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.12.0/mmdet/models/detectors/deformable_detr.py#L6 + Version: v2.12.0 + +Models: + - Name: deformable-detr_r50_16xb2-50e_coco + In Collection: Deformable DETR + Config: configs/deformable_detr/deformable-detr_r50_16xb2-50e_coco.py + Metadata: + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/deformable_detr/deformable-detr_r50_16xb2-50e_coco/deformable-detr_r50_16xb2-50e_coco_20221029_210934-6bc7d21b.pth + + - Name: deformable-detr-refine_r50_16xb2-50e_coco + In Collection: Deformable DETR + Config: configs/deformable_detr/deformable-detr-refine_r50_16xb2-50e_coco.py + Metadata: + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.2 + Weights: https://download.openmmlab.com/mmdetection/v3.0/deformable_detr/deformable-detr-refine_r50_16xb2-50e_coco/deformable-detr-refine_r50_16xb2-50e_coco_20221022_225303-844e0f93.pth + + - Name: deformable-detr-refine-twostage_r50_16xb2-50e_coco + In Collection: Deformable DETR + Config: configs/deformable_detr/deformable-detr-refine-twostage_r50_16xb2-50e_coco.py + Metadata: + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.0 + Weights: https://download.openmmlab.com/mmdetection/v3.0/deformable_detr/deformable-detr-refine-twostage_r50_16xb2-50e_coco/deformable-detr-refine-twostage_r50_16xb2-50e_coco_20221021_184714-acc8a5ff.pth diff --git a/mmdetection/configs/detectors/README.md b/mmdetection/configs/detectors/README.md new file mode 100644 index 00000000..2918d6e4 --- /dev/null +++ b/mmdetection/configs/detectors/README.md @@ -0,0 +1,69 @@ +# DetectoRS + +> [DetectoRS: Detecting Objects with Recursive Feature Pyramid and Switchable Atrous Convolution](https://arxiv.org/abs/2006.02334) + + + +## Abstract + +Many modern object detectors demonstrate outstanding performances by using the mechanism of looking and thinking twice. In this paper, we explore this mechanism in the backbone design for object detection. At the macro level, we propose Recursive Feature Pyramid, which incorporates extra feedback connections from Feature Pyramid Networks into the bottom-up backbone layers. At the micro level, we propose Switchable Atrous Convolution, which convolves the features with different atrous rates and gathers the results using switch functions. Combining them results in DetectoRS, which significantly improves the performances of object detection. On COCO test-dev, DetectoRS achieves state-of-the-art 55.7% box AP for object detection, 48.5% mask AP for instance segmentation, and 50.0% PQ for panoptic segmentation. + +
    + +
    + +## Introduction + +DetectoRS requires COCO and [COCO-stuff](http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip) dataset for training. You need to download and extract it in the COCO dataset path. +The directory should be like this. + +```none +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +| | ├── stuffthingmaps +``` + +## Results and Models + +DetectoRS includes two major components: + +- Recursive Feature Pyramid (RFP). +- Switchable Atrous Convolution (SAC). + +They can be used independently. +Combining them together results in DetectoRS. +The results on COCO 2017 val are shown in the below table. + +| Method | Detector | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------: | :-----------------: | :-----: | :------: | :------------: | :----: | :-----: | :-----------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| RFP | Cascade + ResNet-50 | 1x | 7.5 | - | 44.8 | | [config](./cascade-rcnn_r50-rfp_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/detectors/cascade_rcnn_r50_rfp_1x_coco/cascade_rcnn_r50_rfp_1x_coco-8cf51bfd.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/detectors/cascade_rcnn_r50_rfp_1x_coco/cascade_rcnn_r50_rfp_1x_coco_20200624_104126.log.json) | +| SAC | Cascade + ResNet-50 | 1x | 5.6 | - | 45.0 | | [config](./cascade-rcnn_r50-sac_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/detectors/cascade_rcnn_r50_sac_1x_coco/cascade_rcnn_r50_sac_1x_coco-24bfda62.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/detectors/cascade_rcnn_r50_sac_1x_coco/cascade_rcnn_r50_sac_1x_coco_20200624_104402.log.json) | +| DetectoRS | Cascade + ResNet-50 | 1x | 9.9 | - | 47.4 | | [config](./detectors_cascade-rcnn_r50_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/detectors/detectors_cascade_rcnn_r50_1x_coco/detectors_cascade_rcnn_r50_1x_coco-32a10ba0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/detectors/detectors_cascade_rcnn_r50_1x_coco/detectors_cascade_rcnn_r50_1x_coco_20200706_001203.log.json) | +| RFP | HTC + ResNet-50 | 1x | 11.2 | - | 46.6 | 40.9 | [config](./htc_r50-rfp_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/detectors/htc_r50_rfp_1x_coco/htc_r50_rfp_1x_coco-8ff87c51.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/detectors/htc_r50_rfp_1x_coco/htc_r50_rfp_1x_coco_20200624_103053.log.json) | +| SAC | HTC + ResNet-50 | 1x | 9.3 | - | 46.4 | 40.9 | [config](./htc_r50-sac_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/detectors/htc_r50_sac_1x_coco/htc_r50_sac_1x_coco-bfa60c54.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/detectors/htc_r50_sac_1x_coco/htc_r50_sac_1x_coco_20200624_103111.log.json) | +| DetectoRS | HTC + ResNet-50 | 1x | 13.6 | - | 49.1 | 42.6 | [config](./detectors_htc-r50_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/detectors/detectors_htc_r50_1x_coco/detectors_htc_r50_1x_coco-329b1453.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/detectors/detectors_htc_r50_1x_coco/detectors_htc_r50_1x_coco_20200624_103659.log.json) | +| DetectoRS | HTC + ResNet-101 | 20e | 19.6 | | 50.5 | 43.9 | [config](./detectors_htc-r101_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/detectors/detectors_htc_r101_20e_coco/detectors_htc_r101_20e_coco_20210419_203638-348d533b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/detectors/detectors_htc_r101_20e_coco/detectors_htc_r101_20e_coco_20210419_203638.log.json) | + +*Note*: This is a re-implementation based on MMDetection-V2. +The original implementation is based on MMDetection-V1. + +## Citation + +We provide the config files for [DetectoRS: Detecting Objects with Recursive Feature Pyramid and Switchable Atrous Convolution](https://arxiv.org/pdf/2006.02334.pdf). + +```latex +@article{qiao2020detectors, + title={DetectoRS: Detecting Objects with Recursive Feature Pyramid and Switchable Atrous Convolution}, + author={Qiao, Siyuan and Chen, Liang-Chieh and Yuille, Alan}, + journal={arXiv preprint arXiv:2006.02334}, + year={2020} +} +``` diff --git a/mmdetection/configs/detectors/cascade-rcnn_r50-rfp_1x_coco.py b/mmdetection/configs/detectors/cascade-rcnn_r50-rfp_1x_coco.py new file mode 100644 index 00000000..c30c84d7 --- /dev/null +++ b/mmdetection/configs/detectors/cascade-rcnn_r50-rfp_1x_coco.py @@ -0,0 +1,28 @@ +_base_ = [ + '../_base_/models/cascade-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict( + type='DetectoRS_ResNet', + conv_cfg=dict(type='ConvAWS'), + output_img=True), + neck=dict( + type='RFP', + rfp_steps=2, + aspp_out_channels=64, + aspp_dilations=(1, 3, 6, 1), + rfp_backbone=dict( + rfp_inplanes=256, + type='DetectoRS_ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + conv_cfg=dict(type='ConvAWS'), + pretrained='torchvision://resnet50', + style='pytorch'))) diff --git a/mmdetection/configs/detectors/cascade-rcnn_r50-sac_1x_coco.py b/mmdetection/configs/detectors/cascade-rcnn_r50-sac_1x_coco.py new file mode 100644 index 00000000..24d6cd3a --- /dev/null +++ b/mmdetection/configs/detectors/cascade-rcnn_r50-sac_1x_coco.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/cascade-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict( + type='DetectoRS_ResNet', + conv_cfg=dict(type='ConvAWS'), + sac=dict(type='SAC', use_deform=True), + stage_with_sac=(False, True, True, True))) diff --git a/mmdetection/configs/detectors/detectors_cascade-rcnn_r50_1x_coco.py b/mmdetection/configs/detectors/detectors_cascade-rcnn_r50_1x_coco.py new file mode 100644 index 00000000..19d13d9c --- /dev/null +++ b/mmdetection/configs/detectors/detectors_cascade-rcnn_r50_1x_coco.py @@ -0,0 +1,32 @@ +_base_ = [ + '../_base_/models/cascade-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict( + type='DetectoRS_ResNet', + conv_cfg=dict(type='ConvAWS'), + sac=dict(type='SAC', use_deform=True), + stage_with_sac=(False, True, True, True), + output_img=True), + neck=dict( + type='RFP', + rfp_steps=2, + aspp_out_channels=64, + aspp_dilations=(1, 3, 6, 1), + rfp_backbone=dict( + rfp_inplanes=256, + type='DetectoRS_ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + conv_cfg=dict(type='ConvAWS'), + sac=dict(type='SAC', use_deform=True), + stage_with_sac=(False, True, True, True), + pretrained='torchvision://resnet50', + style='pytorch'))) diff --git a/mmdetection/configs/detectors/detectors_htc-r101_20e_coco.py b/mmdetection/configs/detectors/detectors_htc-r101_20e_coco.py new file mode 100644 index 00000000..93d7d2b1 --- /dev/null +++ b/mmdetection/configs/detectors/detectors_htc-r101_20e_coco.py @@ -0,0 +1,28 @@ +_base_ = '../htc/htc_r101_fpn_20e_coco.py' + +model = dict( + backbone=dict( + type='DetectoRS_ResNet', + conv_cfg=dict(type='ConvAWS'), + sac=dict(type='SAC', use_deform=True), + stage_with_sac=(False, True, True, True), + output_img=True), + neck=dict( + type='RFP', + rfp_steps=2, + aspp_out_channels=64, + aspp_dilations=(1, 3, 6, 1), + rfp_backbone=dict( + rfp_inplanes=256, + type='DetectoRS_ResNet', + depth=101, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + conv_cfg=dict(type='ConvAWS'), + sac=dict(type='SAC', use_deform=True), + stage_with_sac=(False, True, True, True), + pretrained='torchvision://resnet101', + style='pytorch'))) diff --git a/mmdetection/configs/detectors/detectors_htc-r50_1x_coco.py b/mmdetection/configs/detectors/detectors_htc-r50_1x_coco.py new file mode 100644 index 00000000..0d2fc4f7 --- /dev/null +++ b/mmdetection/configs/detectors/detectors_htc-r50_1x_coco.py @@ -0,0 +1,28 @@ +_base_ = '../htc/htc_r50_fpn_1x_coco.py' + +model = dict( + backbone=dict( + type='DetectoRS_ResNet', + conv_cfg=dict(type='ConvAWS'), + sac=dict(type='SAC', use_deform=True), + stage_with_sac=(False, True, True, True), + output_img=True), + neck=dict( + type='RFP', + rfp_steps=2, + aspp_out_channels=64, + aspp_dilations=(1, 3, 6, 1), + rfp_backbone=dict( + rfp_inplanes=256, + type='DetectoRS_ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + conv_cfg=dict(type='ConvAWS'), + sac=dict(type='SAC', use_deform=True), + stage_with_sac=(False, True, True, True), + pretrained='torchvision://resnet50', + style='pytorch'))) diff --git a/mmdetection/configs/detectors/htc_r50-rfp_1x_coco.py b/mmdetection/configs/detectors/htc_r50-rfp_1x_coco.py new file mode 100644 index 00000000..496104e1 --- /dev/null +++ b/mmdetection/configs/detectors/htc_r50-rfp_1x_coco.py @@ -0,0 +1,24 @@ +_base_ = '../htc/htc_r50_fpn_1x_coco.py' + +model = dict( + backbone=dict( + type='DetectoRS_ResNet', + conv_cfg=dict(type='ConvAWS'), + output_img=True), + neck=dict( + type='RFP', + rfp_steps=2, + aspp_out_channels=64, + aspp_dilations=(1, 3, 6, 1), + rfp_backbone=dict( + rfp_inplanes=256, + type='DetectoRS_ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + conv_cfg=dict(type='ConvAWS'), + pretrained='torchvision://resnet50', + style='pytorch'))) diff --git a/mmdetection/configs/detectors/htc_r50-sac_1x_coco.py b/mmdetection/configs/detectors/htc_r50-sac_1x_coco.py new file mode 100644 index 00000000..72d4db96 --- /dev/null +++ b/mmdetection/configs/detectors/htc_r50-sac_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = '../htc/htc_r50_fpn_1x_coco.py' + +model = dict( + backbone=dict( + type='DetectoRS_ResNet', + conv_cfg=dict(type='ConvAWS'), + sac=dict(type='SAC', use_deform=True), + stage_with_sac=(False, True, True, True))) diff --git a/mmdetection/configs/detectors/metafile.yml b/mmdetection/configs/detectors/metafile.yml new file mode 100644 index 00000000..196a1cef --- /dev/null +++ b/mmdetection/configs/detectors/metafile.yml @@ -0,0 +1,114 @@ +Collections: + - Name: DetectoRS + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ASPP + - FPN + - RFP + - RPN + - ResNet + - RoIAlign + - SAC + Paper: + URL: https://arxiv.org/abs/2006.02334 + Title: 'DetectoRS: Detecting Objects with Recursive Feature Pyramid and Switchable Atrous Convolution' + README: configs/detectors/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.2.0/mmdet/models/backbones/detectors_resnet.py#L205 + Version: v2.2.0 + +Models: + - Name: cascade-rcnn_r50-rfp_1x_coco + In Collection: DetectoRS + Config: configs/detectors/cascade-rcnn_r50-rfp_1x_coco.py + Metadata: + Training Memory (GB): 7.5 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/detectors/cascade_rcnn_r50_rfp_1x_coco/cascade_rcnn_r50_rfp_1x_coco-8cf51bfd.pth + + - Name: cascade-rcnn_r50-sac_1x_coco + In Collection: DetectoRS + Config: configs/detectors/cascade-rcnn_r50-sac_1x_coco.py + Metadata: + Training Memory (GB): 5.6 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/detectors/cascade_rcnn_r50_sac_1x_coco/cascade_rcnn_r50_sac_1x_coco-24bfda62.pth + + - Name: detectors_cascade-rcnn_r50_1x_coco + In Collection: DetectoRS + Config: configs/detectors/detectors_cascade-rcnn_r50_1x_coco.py + Metadata: + Training Memory (GB): 9.9 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/detectors/detectors_cascade_rcnn_r50_1x_coco/detectors_cascade_rcnn_r50_1x_coco-32a10ba0.pth + + - Name: htc_r50-rfp_1x_coco + In Collection: DetectoRS + Config: configs/detectors/htc_r50-rfp_1x_coco.py + Metadata: + Training Memory (GB): 11.2 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/detectors/htc_r50_rfp_1x_coco/htc_r50_rfp_1x_coco-8ff87c51.pth + + - Name: htc_r50-sac_1x_coco + In Collection: DetectoRS + Config: configs/detectors/htc_r50-sac_1x_coco.py + Metadata: + Training Memory (GB): 9.3 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/detectors/htc_r50_sac_1x_coco/htc_r50_sac_1x_coco-bfa60c54.pth + + - Name: detectors_htc-r50_1x_coco + In Collection: DetectoRS + Config: configs/detectors/detectors_htc-r50_1x_coco.py + Metadata: + Training Memory (GB): 13.6 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 49.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 42.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/detectors/detectors_htc_r50_1x_coco/detectors_htc_r50_1x_coco-329b1453.pth diff --git a/mmdetection/configs/detr/README.md b/mmdetection/configs/detr/README.md new file mode 100644 index 00000000..8e843f36 --- /dev/null +++ b/mmdetection/configs/detr/README.md @@ -0,0 +1,37 @@ +# DETR + +> [End-to-End Object Detection with Transformers](https://arxiv.org/abs/2005.12872) + + + +## Abstract + +We present a new method that views object detection as a direct set prediction problem. Our approach streamlines the detection pipeline, effectively removing the need for many hand-designed components like a non-maximum suppression procedure or anchor generation that explicitly encode our prior knowledge about the task. The main ingredients of the new framework, called DEtection TRansformer or DETR, are a set-based global loss that forces unique predictions via bipartite matching, and a transformer encoder-decoder architecture. Given a fixed small set of learned object queries, DETR reasons about the relations of the objects and the global image context to directly output the final set of predictions in parallel. The new model is conceptually simple and does not require a specialized library, unlike many other modern detectors. DETR demonstrates accuracy and run-time performance on par with the well-established and highly-optimized Faster RCNN baseline on the challenging COCO object detection dataset. Moreover, DETR can be easily generalized to produce panoptic segmentation in a unified manner. We show that it significantly outperforms competitive baselines. + +
    + +
    + +## Results and Models + +| Backbone | Model | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :---: | :-----: | :------: | :------------: | :----: | :------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | DETR | 150e | 7.9 | | 39.9 | [config](./detr_r50_8xb2-150e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/detr/detr_r50_8xb2-150e_coco/detr_r50_8xb2-150e_coco_20221023_153551-436d03e8.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/detr/detr_r50_8xb2-150e_coco/detr_r50_8xb2-150e_coco_20221023_153551.log.json) | + +## Citation + +We provide the config files for DETR: [End-to-End Object Detection with Transformers](https://arxiv.org/abs/2005.12872). + +```latex +@inproceedings{detr, + author = {Nicolas Carion and + Francisco Massa and + Gabriel Synnaeve and + Nicolas Usunier and + Alexander Kirillov and + Sergey Zagoruyko}, + title = {End-to-End Object Detection with Transformers}, + booktitle = {ECCV}, + year = {2020} +} +``` diff --git a/mmdetection/configs/detr/detr_r101_8xb2-500e_coco.py b/mmdetection/configs/detr/detr_r101_8xb2-500e_coco.py new file mode 100644 index 00000000..6661aacd --- /dev/null +++ b/mmdetection/configs/detr/detr_r101_8xb2-500e_coco.py @@ -0,0 +1,7 @@ +_base_ = './detr_r50_8xb2-500e_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/detr/detr_r18_8xb2-500e_coco.py b/mmdetection/configs/detr/detr_r18_8xb2-500e_coco.py new file mode 100644 index 00000000..305b9d6f --- /dev/null +++ b/mmdetection/configs/detr/detr_r18_8xb2-500e_coco.py @@ -0,0 +1,7 @@ +_base_ = './detr_r50_8xb2-500e_coco.py' + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[512])) diff --git a/mmdetection/configs/detr/detr_r50_8xb2-150e_coco.py b/mmdetection/configs/detr/detr_r50_8xb2-150e_coco.py new file mode 100644 index 00000000..aaa15410 --- /dev/null +++ b/mmdetection/configs/detr/detr_r50_8xb2-150e_coco.py @@ -0,0 +1,155 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', '../_base_/default_runtime.py' +] +model = dict( + type='DETR', + num_queries=100, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(3, ), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=None, + num_outs=1), + encoder=dict( # DetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.1, + act_cfg=dict(type='ReLU', inplace=True)))), + decoder=dict( # DetrTransformerDecoder + num_layers=6, + layer_cfg=dict( # DetrTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.1, + act_cfg=dict(type='ReLU', inplace=True))), + return_intermediate=True), + positional_encoding=dict(num_feats=128, normalize=True), + bbox_head=dict( + type='DETRHead', + num_classes=80, + embed_dims=256, + loss_cls=dict( + type='CrossEntropyLoss', + bg_cls_weight=0.1, + use_sigmoid=False, + loss_weight=1.0, + class_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='ClassificationCost', weight=1.), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=100)) + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[[ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True) + ]]), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={'backbone': dict(lr_mult=0.1, decay_mult=1.0)})) + +# learning policy +max_epochs = 150 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[100], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/detr/detr_r50_8xb2-500e_coco.py b/mmdetection/configs/detr/detr_r50_8xb2-500e_coco.py new file mode 100644 index 00000000..f07d5dce --- /dev/null +++ b/mmdetection/configs/detr/detr_r50_8xb2-500e_coco.py @@ -0,0 +1,24 @@ +_base_ = './detr_r50_8xb2-150e_coco.py' + +# learning policy +max_epochs = 500 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=10) + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[334], + gamma=0.1) +] + +# only keep latest 2 checkpoints +default_hooks = dict(checkpoint=dict(max_keep_ckpts=2)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/detr/metafile.yml b/mmdetection/configs/detr/metafile.yml new file mode 100644 index 00000000..a9132dff --- /dev/null +++ b/mmdetection/configs/detr/metafile.yml @@ -0,0 +1,33 @@ +Collections: + - Name: DETR + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Multi Scale Train + - Gradient Clip + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + - Transformer + Paper: + URL: https://arxiv.org/abs/2005.12872 + Title: 'End-to-End Object Detection with Transformers' + README: configs/detr/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.7.0/mmdet/models/detectors/detr.py#L7 + Version: v2.7.0 + +Models: + - Name: detr_r50_8xb2-150e_coco + In Collection: DETR + Config: configs/detr/detr_r50_8xb2-150e_coco.py + Metadata: + Training Memory (GB): 7.9 + Epochs: 150 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/detr/detr_r50_8xb2-150e_coco/detr_r50_8xb2-150e_coco_20221023_153551-436d03e8.pth diff --git a/mmdetection/configs/dino/README.md b/mmdetection/configs/dino/README.md new file mode 100644 index 00000000..d8a01bde --- /dev/null +++ b/mmdetection/configs/dino/README.md @@ -0,0 +1,40 @@ +# DINO + +> [DINO: DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection](https://arxiv.org/abs/2203.03605) + + + +## Abstract + +We present DINO (DETR with Improved deNoising anchOr boxes), a state-of-the-art end-to-end object detector. DINO improves over previous DETR-like models in performance and efficiency by using a contrastive way for denoising training, a mixed query selection method for anchor initialization, and a look forward twice scheme for box prediction. DINO achieves 49.4AP in 12 epochs and 51.3AP in 24 epochs on COCO with a ResNet-50 backbone and multi-scale features, yielding a significant improvement of +6.0AP and +2.7AP, respectively, compared to DN-DETR, the previous best DETR-like model. DINO scales well in both model size and data size. Without bells and whistles, after pre-training on the Objects365 dataset with a SwinL backbone, DINO obtains the best results on both COCO val2017 (63.2AP) and test-dev (63.3AP). Compared to other models on the leaderboard, DINO significantly reduces its model size and pre-training data size while achieving better results. + +
    + +
    + +## Results and Models + +| Backbone | Model | Lr schd | Better-Hyper | box AP | Config | Download | +| :------: | :---------: | :-----: | :----------: | :----: | :---------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | DINO-4scale | 12e | False | 49.0 | [config](./dino-4scale_r50_8xb2-12e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/dino/dino-4scale_r50_8xb2-12e_coco/dino-4scale_r50_8xb2-12e_coco_20221202_182705-55b2bba2.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/dino/dino-4scale_r50_8xb2-12e_coco/dino-4scale_r50_8xb2-12e_coco_20221202_182705.log.json) | +| R-50 | DINO-4scale | 12e | True | 50.1 | [config](./dino-4scale_r50_improved_8xb2-12e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/dino/dino-4scale_r50_improved_8xb2-12e_coco/dino-4scale_r50_improved_8xb2-12e_coco_20230818_162607-6f47a913.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/dino/dino-4scale_r50_improved_8xb2-12e_coco/dino-4scale_r50_improved_8xb2-12e_coco_20230818_162607.log.json) | +| Swin-L | DINO-5scale | 12e | False | 57.2 | [config](./dino-5scale_swin-l_8xb2-12e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/dino/dino-5scale_swin-l_8xb2-12e_coco/dino-5scale_swin-l_8xb2-12e_coco_20230228_072924-a654145f.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/dino/dino-5scale_swin-l_8xb2-12e_coco/dino-5scale_swin-l_8xb2-12e_coco_20230228_072924.log) | +| Swin-L | DINO-5scale | 36e | False | 58.4 | [config](./dino-5scale_swin-l_8xb2-36e_coco.py) | [model](https://github.com/RistoranteRist/mmlab-weights/releases/download/dino-swinl/dino-5scale_swin-l_8xb2-36e_coco-5486e051.pth) \| [log](https://github.com/RistoranteRist/mmlab-weights/releases/download/dino-swinl/20230307_032359.log) | + +### NOTE + +The performance is unstable. `DINO-4scale` with `R-50` may fluctuate about 0.4 mAP. + +## Citation + +We provide the config files for DINO: [DINO: DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection](https://arxiv.org/abs/2203.03605). + +```latex +@misc{zhang2022dino, + title={DINO: DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection}, + author={Hao Zhang and Feng Li and Shilong Liu and Lei Zhang and Hang Su and Jun Zhu and Lionel M. Ni and Heung-Yeung Shum}, + year={2022}, + eprint={2203.03605}, + archivePrefix={arXiv}, + primaryClass={cs.CV}} +``` diff --git a/mmdetection/configs/dino/dino-4scale_r50_8xb2-12e_coco.py b/mmdetection/configs/dino/dino-4scale_r50_8xb2-12e_coco.py new file mode 100644 index 00000000..5831f898 --- /dev/null +++ b/mmdetection/configs/dino/dino-4scale_r50_8xb2-12e_coco.py @@ -0,0 +1,163 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', '../_base_/default_runtime.py' +] +model = dict( + type='DINO', + num_queries=900, # num_matching_queries + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[512, 1024, 2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0))), # 0.1 for DeformDETR + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_heads=8, + dropout=0.0), # 0.1 for DeformDETR + cross_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0)), # 0.1 for DeformDETR + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, + normalize=True, + offset=0.0, # -0.5 for DeformDETR + temperature=20), # 10000 for DeformDETR + bbox_head=dict( + type='DINOHead', + num_classes=80, + sync_cls_avg_factor=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), # 2.0 in DeformDETR + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( # TODO: Move to model.train_cfg ? + label_noise_scale=0.5, + box_noise_scale=1.0, # 0.4 for DN-DETR + group_cfg=dict(dynamic=True, num_groups=None, + num_dn_queries=100)), # TODO: half num_dn_queries + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) # 100 for DeformDETR + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] +train_dataloader = dict( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + type='AdamW', + lr=0.0001, # 0.0002 for DeformDETR + weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.1)}) +) # custom_keys contains sampling_offsets and reference_points in DeformDETR # noqa + +# learning policy +max_epochs = 12 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/dino/dino-4scale_r50_8xb2-24e_coco.py b/mmdetection/configs/dino/dino-4scale_r50_8xb2-24e_coco.py new file mode 100644 index 00000000..8534ac6a --- /dev/null +++ b/mmdetection/configs/dino/dino-4scale_r50_8xb2-24e_coco.py @@ -0,0 +1,13 @@ +_base_ = './dino-4scale_r50_8xb2-12e_coco.py' +max_epochs = 24 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[20], + gamma=0.1) +] diff --git a/mmdetection/configs/dino/dino-4scale_r50_8xb2-36e_coco.py b/mmdetection/configs/dino/dino-4scale_r50_8xb2-36e_coco.py new file mode 100644 index 00000000..1c2cf460 --- /dev/null +++ b/mmdetection/configs/dino/dino-4scale_r50_8xb2-36e_coco.py @@ -0,0 +1,13 @@ +_base_ = './dino-4scale_r50_8xb2-12e_coco.py' +max_epochs = 36 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[30], + gamma=0.1) +] diff --git a/mmdetection/configs/dino/dino-4scale_r50_improved_8xb2-12e_coco.py b/mmdetection/configs/dino/dino-4scale_r50_improved_8xb2-12e_coco.py new file mode 100644 index 00000000..6a4a82ba --- /dev/null +++ b/mmdetection/configs/dino/dino-4scale_r50_improved_8xb2-12e_coco.py @@ -0,0 +1,18 @@ +_base_ = ['dino-4scale_r50_8xb2-12e_coco.py'] + +# from deformable detr hyper +model = dict( + backbone=dict(frozen_stages=-1), + bbox_head=dict(loss_cls=dict(loss_weight=2.0)), + positional_encoding=dict(offset=-0.5, temperature=10000), + dn_cfg=dict(group_cfg=dict(num_dn_queries=300))) + +# optimizer +optim_wrapper = dict( + optimizer=dict(lr=0.0002), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1), + 'sampling_offsets': dict(lr_mult=0.1), + 'reference_points': dict(lr_mult=0.1) + })) diff --git a/mmdetection/configs/dino/dino-5scale_swin-l_8xb2-12e_coco.py b/mmdetection/configs/dino/dino-5scale_swin-l_8xb2-12e_coco.py new file mode 100644 index 00000000..3d39f22f --- /dev/null +++ b/mmdetection/configs/dino/dino-5scale_swin-l_8xb2-12e_coco.py @@ -0,0 +1,30 @@ +_base_ = './dino-4scale_r50_8xb2-12e_coco.py' + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa +num_levels = 5 +model = dict( + num_feature_levels=num_levels, + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(0, 1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=True, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[192, 384, 768, 1536], num_outs=num_levels), + encoder=dict(layer_cfg=dict(self_attn_cfg=dict(num_levels=num_levels))), + decoder=dict(layer_cfg=dict(cross_attn_cfg=dict(num_levels=num_levels)))) diff --git a/mmdetection/configs/dino/dino-5scale_swin-l_8xb2-36e_coco.py b/mmdetection/configs/dino/dino-5scale_swin-l_8xb2-36e_coco.py new file mode 100644 index 00000000..d55a38e6 --- /dev/null +++ b/mmdetection/configs/dino/dino-5scale_swin-l_8xb2-36e_coco.py @@ -0,0 +1,13 @@ +_base_ = './dino-5scale_swin-l_8xb2-12e_coco.py' +max_epochs = 36 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] diff --git a/mmdetection/configs/dino/metafile.yml b/mmdetection/configs/dino/metafile.yml new file mode 100644 index 00000000..f276a04e --- /dev/null +++ b/mmdetection/configs/dino/metafile.yml @@ -0,0 +1,85 @@ +Collections: + - Name: DINO + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Multi Scale Train + - Gradient Clip + Training Resources: 8x A100 GPUs + Architecture: + - ResNet + - Transformer + Paper: + URL: https://arxiv.org/abs/2203.03605 + Title: 'DINO: DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection' + README: configs/dino/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/f4112c9e5611468ffbd57cfba548fd1289264b52/mmdet/models/detectors/dino.py#L17 + Version: v3.0.0rc6 + +Models: + - Name: dino-4scale_r50_8xb2-12e_coco + In Collection: DINO + Config: configs/dino/dino-4scale_r50_8xb2-12e_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 49.0 + Weights: https://download.openmmlab.com/mmdetection/v3.0/dino/dino-4scale_r50_8xb2-12e_coco/dino-4scale_r50_8xb2-12e_coco_20221202_182705-55b2bba2.pth + + - Name: dino-4scale_r50_8xb2-24e_coco + In Collection: DINO + Config: configs/dino/dino-4scale_r50_8xb2-24e_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + + - Name: dino-4scale_r50_8xb2-36e_coco + In Collection: DINO + Config: configs/dino/dino-4scale_r50_8xb2-36e_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + + - Name: dino-5scale_swin-l_8xb2-12e_coco + In Collection: DINO + Config: configs/dino/dino-5scale_swin-l_8xb2-12e_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 57.2 + Weights: https://download.openmmlab.com/mmdetection/v3.0/dino/dino-5scale_swin-l_8xb2-12e_coco/dino-5scale_swin-l_8xb2-12e_coco_20230228_072924-a654145f.pth + + - Name: dino-5scale_swin-l_8xb2-36e_coco + In Collection: DINO + Config: configs/dino/dino-5scale_swin-l_8xb2-36e_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 58.4 + Weights: https://github.com/RistoranteRist/mmlab-weights/releases/download/dino-swinl/dino-5scale_swin-l_8xb2-36e_coco-5486e051.pth + - Name: dino-4scale_r50_improved_8xb2-12e_coco + In Collection: DINO + Config: configs/dino/dino-4scale_r50_improved_8xb2-12e_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.1 + Weights: https://download.openmmlab.com/mmdetection/v3.0/dino/dino-4scale_r50_improved_8xb2-12e_coco/dino-4scale_r50_improved_8xb2-12e_coco_20230818_162607-6f47a913.pth diff --git a/mmdetection/configs/double_heads/README.md b/mmdetection/configs/double_heads/README.md new file mode 100644 index 00000000..1b97dbc1 --- /dev/null +++ b/mmdetection/configs/double_heads/README.md @@ -0,0 +1,32 @@ +# Double Heads + +> [Rethinking Classification and Localization for Object Detection](https://arxiv.org/abs/1904.06493) + + + +## Abstract + +Two head structures (i.e. fully connected head and convolution head) have been widely used in R-CNN based detectors for classification and localization tasks. However, there is a lack of understanding of how does these two head structures work for these two tasks. To address this issue, we perform a thorough analysis and find an interesting fact that the two head structures have opposite preferences towards the two tasks. Specifically, the fully connected head (fc-head) is more suitable for the classification task, while the convolution head (conv-head) is more suitable for the localization task. Furthermore, we examine the output feature maps of both heads and find that fc-head has more spatial sensitivity than conv-head. Thus, fc-head has more capability to distinguish a complete object from part of an object, but is not robust to regress the whole object. Based upon these findings, we propose a Double-Head method, which has a fully connected head focusing on classification and a convolution head for bounding box regression. Without bells and whistles, our method gains +3.5 and +2.8 AP on MS COCO dataset from Feature Pyramid Network (FPN) baselines with ResNet-50 and ResNet-101 backbones, respectively. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :-----: | :-----: | :------: | :------------: | :----: | :-------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 1x | 6.8 | 9.5 | 40.0 | [config](./dh-faster-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/double_heads/dh_faster_rcnn_r50_fpn_1x_coco/dh_faster_rcnn_r50_fpn_1x_coco_20200130-586b67df.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/double_heads/dh_faster_rcnn_r50_fpn_1x_coco/dh_faster_rcnn_r50_fpn_1x_coco_20200130_220238.log.json) | + +## Citation + +```latex +@article{wu2019rethinking, + title={Rethinking Classification and Localization for Object Detection}, + author={Yue Wu and Yinpeng Chen and Lu Yuan and Zicheng Liu and Lijuan Wang and Hongzhi Li and Yun Fu}, + year={2019}, + eprint={1904.06493}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} +``` diff --git a/mmdetection/configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..6b9b6e69 --- /dev/null +++ b/mmdetection/configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,23 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + type='DoubleHeadRoIHead', + reg_roi_scale_factor=1.3, + bbox_head=dict( + _delete_=True, + type='DoubleConvFCBBoxHead', + num_convs=4, + num_fcs=2, + in_channels=256, + conv_out_channels=1024, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=2.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=2.0)))) diff --git a/mmdetection/configs/double_heads/metafile.yml b/mmdetection/configs/double_heads/metafile.yml new file mode 100644 index 00000000..bb14e796 --- /dev/null +++ b/mmdetection/configs/double_heads/metafile.yml @@ -0,0 +1,41 @@ +Collections: + - Name: Rethinking Classification and Localization for Object Detection + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - RPN + - ResNet + - RoIAlign + Paper: + URL: https://arxiv.org/pdf/1904.06493 + Title: 'Rethinking Classification and Localization for Object Detection' + README: configs/double_heads/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/roi_heads/double_roi_head.py#L6 + Version: v2.0.0 + +Models: + - Name: dh-faster-rcnn_r50_fpn_1x_coco + In Collection: Rethinking Classification and Localization for Object Detection + Config: configs/double_heads/dh-faster-rcnn_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.8 + inference time (ms/im): + - value: 105.26 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/double_heads/dh_faster_rcnn_r50_fpn_1x_coco/dh_faster_rcnn_r50_fpn_1x_coco_20200130-586b67df.pth diff --git a/mmdetection/configs/dsdl/README.md b/mmdetection/configs/dsdl/README.md new file mode 100644 index 00000000..f38c3b65 --- /dev/null +++ b/mmdetection/configs/dsdl/README.md @@ -0,0 +1,63 @@ +# DSDL: Standard Description Language for DataSet + + + +## 1. Abstract + +Data is the cornerstone of artificial intelligence. The efficiency of data acquisition, exchange, and application directly impacts the advances in technologies and applications. Over the long history of AI, a vast quantity of data sets have been developed and distributed. However, these datasets are defined in very different forms, which incurs significant overhead when it comes to exchange, integration, and utilization -- it is often the case that one needs to develop a new customized tool or script in order to incorporate a new dataset into a workflow. + +To overcome such difficulties, we develop **Data Set Description Language (DSDL)**. More details please visit our [official documents](https://opendatalab.github.io/dsdl-docs/getting_started/overview/), dsdl datasets can be downloaded from our platform [OpenDataLab](https://opendatalab.com/). + +## 2. Steps + +- install dsdl: + + install by pip: + + ``` + pip install dsdl + ``` + + install by source code: + + ``` + git clone https://github.com/opendatalab/dsdl-sdk.git -b schema-dsdl + cd dsdl-sdk + python setup.py install + ``` + +- install mmdet and pytorch: + please refer this [installation documents](https://mmdetection.readthedocs.io/en/latest/get_started.html). + +- train: + + - using single gpu: + + ``` + python tools/train.py {config_file} + ``` + + - using slurm: + + ``` + ./tools/slurm_train.sh {partition} {job_name} {config_file} {work_dir} {gpu_nums} + ``` + +## 3. Test Results + +- detection task: + + | Datasets | Model | box AP | Config | + | :--------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----: | :-------------------------: | + | VOC07+12 | [model](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/faster_rcnn_r50_fpn_1x_voc0712/faster_rcnn_r50_fpn_1x_voc0712_20220320_192712-54bef0f3.pth) | 80.3\* | [config](./voc0712.py) | + | COCO | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth) | 37.4 | [config](./coco.py) | + | Objects365 | [model](https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_16x4_1x_obj365v2/faster_rcnn_r50_fpn_16x4_1x_obj365v2_20221220_175040-5910b015.pth) | 19.8 | [config](./objects365v2.py) | + | OpenImages | [model](https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_20220306_202424-98c630e5.pth) | 59.9\* | [config](./openimagesv6.py) | + + \*: box AP in voc metric and openimages metric, actually means AP_50. + +- instance segmentation task: + + | Datasets | Model | box AP | mask AP | Config | + | :------: | :------------------------------------------------------------------------------------------------------------------------------------------: | :----: | :-----: | :--------------------------: | + | COCO | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth) | 38.1 | 34.7 | [config](./coco_instance.py) | diff --git a/mmdetection/configs/dsdl/coco.py b/mmdetection/configs/dsdl/coco.py new file mode 100644 index 00000000..3c9e895e --- /dev/null +++ b/mmdetection/configs/dsdl/coco.py @@ -0,0 +1,33 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py', + '../_base_/datasets/dsdl.py' +] + +# dsdl dataset settings + +# please visit our platform [OpenDataLab](https://opendatalab.com/) +# to downloaded dsdl dataset. +data_root = 'data/COCO2017' +img_prefix = 'original' +train_ann = 'dsdl/set-train/train.yaml' +val_ann = 'dsdl/set-val/val.yaml' +specific_key_path = dict(ignore_flag='./annotations/*/iscrowd') + +train_dataloader = dict( + dataset=dict( + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=train_ann, + data_prefix=dict(img_path=img_prefix), + filter_cfg=dict(filter_empty_gt=True, min_size=32, bbox_min_size=32), + )) + +val_dataloader = dict( + dataset=dict( + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=val_ann, + data_prefix=dict(img_path=img_prefix), + )) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/dsdl/coco_instance.py b/mmdetection/configs/dsdl/coco_instance.py new file mode 100644 index 00000000..e34f93c9 --- /dev/null +++ b/mmdetection/configs/dsdl/coco_instance.py @@ -0,0 +1,62 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py', + '../_base_/datasets/dsdl.py' +] + +# dsdl dataset settings. + +# please visit our platform [OpenDataLab](https://opendatalab.com/) +# to downloaded dsdl dataset. +data_root = 'data/COCO2017' +img_prefix = 'original' +train_ann = 'dsdl/set-train/train.yaml' +val_ann = 'dsdl/set-val/val.yaml' +specific_key_path = dict(ignore_flag='./annotations/*/iscrowd') + +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'instances')) +] + +train_dataloader = dict( + dataset=dict( + with_polygon=True, + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=train_ann, + data_prefix=dict(img_path=img_prefix), + filter_cfg=dict(filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline, + )) + +val_dataloader = dict( + dataset=dict( + with_polygon=True, + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=val_ann, + data_prefix=dict(img_path=img_prefix), + pipeline=test_pipeline, + )) + +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', metric=['bbox', 'segm'], format_only=False) + +test_evaluator = val_evaluator diff --git a/mmdetection/configs/dsdl/objects365v2.py b/mmdetection/configs/dsdl/objects365v2.py new file mode 100644 index 00000000..d25a2323 --- /dev/null +++ b/mmdetection/configs/dsdl/objects365v2.py @@ -0,0 +1,54 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py', + '../_base_/datasets/dsdl.py' +] + +model = dict(roi_head=dict(bbox_head=dict(num_classes=365))) + +# dsdl dataset settings + +# please visit our platform [OpenDataLab](https://opendatalab.com/) +# to downloaded dsdl dataset. +data_root = 'data/Objects365' +img_prefix = 'original' +train_ann = 'dsdl/set-train/train.yaml' +val_ann = 'dsdl/set-val/val.yaml' +specific_key_path = dict(ignore_flag='./annotations/*/iscrowd') + +train_dataloader = dict( + dataset=dict( + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=train_ann, + data_prefix=dict(img_path=img_prefix), + filter_cfg=dict(filter_empty_gt=True, min_size=32, bbox_min_size=32), + )) + +val_dataloader = dict( + dataset=dict( + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=val_ann, + data_prefix=dict(img_path=img_prefix), + test_mode=True, + )) +test_dataloader = val_dataloader + +default_hooks = dict(logger=dict(type='LoggerHook', interval=1000), ) +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=3, val_interval=1) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[1, 2], + gamma=0.1) +] +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/dsdl/openimagesv6.py b/mmdetection/configs/dsdl/openimagesv6.py new file mode 100644 index 00000000..a65f942a --- /dev/null +++ b/mmdetection/configs/dsdl/openimagesv6.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/schedules/schedule_1x.py', + '../_base_/default_runtime.py', +] + +model = dict(roi_head=dict(bbox_head=dict(num_classes=601))) + +# dsdl dataset settings + +# please visit our platform [OpenDataLab](https://opendatalab.com/) +# to downloaded dsdl dataset. +dataset_type = 'DSDLDetDataset' +data_root = 'data/OpenImages' +train_ann = 'dsdl/set-train/train.yaml' +val_ann = 'dsdl/set-val/val.yaml' +specific_key_path = dict( + image_level_labels='./image_labels/*/label', + Label='./objects/*/label', + is_group_of='./objects/*/isgroupof', +) + +backend_args = dict( + backend='petrel', + path_mapping=dict({'data/': 's3://open_dataset_original/'})) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1024, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1024, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'instances', 'image_level_labels')) +] + +train_dataloader = dict( + sampler=dict(type='ClassAwareSampler', num_sample_class=1), + dataset=dict( + type=dataset_type, + with_imagelevel_label=True, + with_hierarchy=True, + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=train_ann, + filter_cfg=dict(filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline)) + +val_dataloader = dict( + dataset=dict( + type=dataset_type, + with_imagelevel_label=True, + with_hierarchy=True, + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=val_ann, + test_mode=True, + pipeline=test_pipeline)) + +test_dataloader = val_dataloader + +default_hooks = dict(logger=dict(type='LoggerHook', interval=1000), ) +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=3, val_interval=1) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[1, 2], + gamma=0.1) +] +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +val_evaluator = dict( + type='OpenImagesMetric', + iou_thrs=0.5, + ioa_thrs=0.5, + use_group_of=True, + get_supercategory=True) + +test_evaluator = val_evaluator diff --git a/mmdetection/configs/dsdl/voc07.py b/mmdetection/configs/dsdl/voc07.py new file mode 100644 index 00000000..b7b86471 --- /dev/null +++ b/mmdetection/configs/dsdl/voc07.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', '../_base_/default_runtime.py' +] + +# model setting +model = dict(roi_head=dict(bbox_head=dict(num_classes=20))) + +# dsdl dataset settings + +# please visit our platform [OpenDataLab](https://opendatalab.com/) +# to downloaded dsdl dataset. +dataset_type = 'DSDLDetDataset' +data_root = 'data/VOC07-det' +img_prefix = 'original' +train_ann = 'dsdl/set-train/train.yaml' +val_ann = 'dsdl/set-test/test.yaml' + +specific_key_path = dict(ignore_flag='./objects/*/difficult') + +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1000, 600), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1000, 600), keep_ratio=True), + # avoid bboxes being resized + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'instances')) +] +train_dataloader = dict( + dataset=dict( + type=dataset_type, + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=train_ann, + data_prefix=dict(img_path=img_prefix), + filter_cfg=dict(filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline)) + +val_dataloader = dict( + dataset=dict( + type=dataset_type, + specific_key_path=specific_key_path, + data_root=data_root, + ann_file=val_ann, + data_prefix=dict(img_path=img_prefix), + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# Pascal VOC2007 uses `11points` as default evaluate mode, while PASCAL +# VOC2012 defaults to use 'area'. +val_evaluator = dict(type='VOCMetric', metric='mAP', eval_mode='11points') +# val_evaluator = dict(type='CocoMetric', metric='bbox') +test_evaluator = val_evaluator + +# training schedule, voc dataset is repeated 3 times, in +# `_base_/datasets/voc0712.py`, so the actual epoch = 4 * 3 = 12 +max_epochs = 12 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=3) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[9], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/dsdl/voc0712.py b/mmdetection/configs/dsdl/voc0712.py new file mode 100644 index 00000000..9ec1bb8f --- /dev/null +++ b/mmdetection/configs/dsdl/voc0712.py @@ -0,0 +1,132 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/schedules/schedule_1x.py', + '../_base_/default_runtime.py', + # '../_base_/datasets/dsdl.py' +] + +# model setting +model = dict(roi_head=dict(bbox_head=dict(num_classes=20))) + +# dsdl dataset settings + +# please visit our platform [OpenDataLab](https://opendatalab.com/) +# to downloaded dsdl dataset. +dataset_type = 'DSDLDetDataset' +data_root_07 = 'data/VOC07-det' +data_root_12 = 'data/VOC12-det' +img_prefix = 'original' + +train_ann = 'dsdl/set-train/train.yaml' +val_ann = 'dsdl/set-val/val.yaml' +test_ann = 'dsdl/set-test/test.yaml' + +backend_args = None +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1000, 600), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(1000, 600), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'instances')) +] + +specific_key_path = dict(ignore_flag='./objects/*/difficult', ) + +train_dataloader = dict( + dataset=dict( + type='RepeatDataset', + times=3, + dataset=dict( + type='ConcatDataset', + datasets=[ + dict( + type=dataset_type, + specific_key_path=specific_key_path, + data_root=data_root_07, + ann_file=train_ann, + data_prefix=dict(img_path=img_prefix), + filter_cfg=dict( + filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline), + dict( + type=dataset_type, + specific_key_path=specific_key_path, + data_root=data_root_07, + ann_file=val_ann, + data_prefix=dict(img_path=img_prefix), + filter_cfg=dict( + filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline), + dict( + type=dataset_type, + specific_key_path=specific_key_path, + data_root=data_root_12, + ann_file=train_ann, + data_prefix=dict(img_path=img_prefix), + filter_cfg=dict( + filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline), + dict( + type=dataset_type, + specific_key_path=specific_key_path, + data_root=data_root_12, + ann_file=val_ann, + data_prefix=dict(img_path=img_prefix), + filter_cfg=dict( + filter_empty_gt=True, min_size=32, bbox_min_size=32), + pipeline=train_pipeline), + ]))) + +val_dataloader = dict( + dataset=dict( + type=dataset_type, + specific_key_path=specific_key_path, + data_root=data_root_07, + ann_file=test_ann, + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='CocoMetric', metric='bbox') +# val_evaluator = dict(type='VOCMetric', metric='mAP', eval_mode='11points') +test_evaluator = val_evaluator + +# training schedule, voc dataset is repeated 3 times, in +# `_base_/datasets/voc0712.py`, so the actual epoch = 4 * 3 = 12 +max_epochs = 4 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[3], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/dyhead/README.md b/mmdetection/configs/dyhead/README.md new file mode 100644 index 00000000..decd4805 --- /dev/null +++ b/mmdetection/configs/dyhead/README.md @@ -0,0 +1,52 @@ +# DyHead + +> [Dynamic Head: Unifying Object Detection Heads with Attentions](https://arxiv.org/abs/2106.08322) + + + +## Abstract + +The complex nature of combining localization and classification in object detection has resulted in the flourished development of methods. Previous works tried to improve the performance in various object detection heads but failed to present a unified view. In this paper, we present a novel dynamic head framework to unify object detection heads with attentions. By coherently combining multiple self-attention mechanisms between feature levels for scale-awareness, among spatial locations for spatial-awareness, and within output channels for task-awareness, the proposed approach significantly improves the representation ability of object detection heads without any computational overhead. Further experiments demonstrate that the effectiveness and efficiency of the proposed dynamic head on the COCO benchmark. With a standard ResNeXt-101-DCN backbone, we largely improve the performance over popular object detectors and achieve a new state-of-the-art at 54.0 AP. Furthermore, with latest transformer backbone and extra data, we can push current best COCO result to a new record at 60.6 AP. + +
    + +
    + +## Results and Models + +| Method | Backbone | Style | Setting | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :----: | :------: | :-----: | :----------: | :-----: | :------: | :------------: | :----: | :----------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ATSS | R-50 | caffe | reproduction | 1x | 5.4 | 13.2 | 42.5 | [config](./atss_r50-caffe_fpn_dyhead_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_r50_fpn_dyhead_for_reproduction_1x_coco/atss_r50_fpn_dyhead_for_reproduction_4x4_1x_coco_20220107_213939-162888e6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_r50_fpn_dyhead_for_reproduction_1x_coco/atss_r50_fpn_dyhead_for_reproduction_4x4_1x_coco_20220107_213939.log.json) | +| ATSS | R-50 | pytorch | simple | 1x | 4.9 | 13.7 | 43.3 | [config](./atss_r50_fpn_dyhead_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_r50_fpn_dyhead_4x4_1x_coco/atss_r50_fpn_dyhead_4x4_1x_coco_20211219_023314-eaa620c6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_r50_fpn_dyhead_4x4_1x_coco/atss_r50_fpn_dyhead_4x4_1x_coco_20211219_023314.log.json) | + +- We trained the above models with 4 GPUs and 4 `samples_per_gpu`. +- The `reproduction` setting aims to reproduce the official implementation based on Detectron2. +- The `simple` setting serves as a minimum example to use DyHead in MMDetection. Specifically, + - it adds `DyHead` to `neck` after `FPN` + - it sets `stacked_convs=0` to `bbox_head` +- The `simple` setting achieves higher AP than the original implementation. + We have not conduct ablation study between the two settings. + `dict(type='Pad', size_divisor=128)` may further improve AP by prefer spatial alignment across pyramid levels, although large padding reduces efficiency. + +We also trained the model with Swin-L backbone. Results are as below. + +| Method | Backbone | Style | Setting | Lr schd | mstrain | box AP | Config | Download | +| :----: | :------: | :---: | :----------: | :-----: | :------: | :----: | :-----------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ATSS | Swin-L | caffe | reproduction | 2x | 480~1200 | 56.2 | [config](./atss_swin-l-p4-w12_fpn_dyhead_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_swin-l-p4-w12_fpn_dyhead_mstrain_2x_coco/atss_swin-l-p4-w12_fpn_dyhead_mstrain_2x_coco_20220509_100315-bc5b6516.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_swin-l-p4-w12_fpn_dyhead_mstrain_2x_coco/atss_swin-l-p4-w12_fpn_dyhead_mstrain_2x_coco_20220509_100315.log.json) | + +## Relation to Other Methods + +- DyHead can be regarded as an improved [SEPC](https://arxiv.org/abs/2005.03101) with [DyReLU modules](https://arxiv.org/abs/2003.10027) and simplified [SE blocks](https://arxiv.org/abs/1709.01507). +- Xiyang Dai et al., the author team of DyHead, adopt it for [Dynamic DETR](https://openaccess.thecvf.com/content/ICCV2021/html/Dai_Dynamic_DETR_End-to-End_Object_Detection_With_Dynamic_Attention_ICCV_2021_paper.html). + The description of Dynamic Encoder in Sec. 3.2 will help you understand DyHead. + +## Citation + +```latex +@inproceedings{DyHead_CVPR2021, + author = {Dai, Xiyang and Chen, Yinpeng and Xiao, Bin and Chen, Dongdong and Liu, Mengchen and Yuan, Lu and Zhang, Lei}, + title = {Dynamic Head: Unifying Object Detection Heads With Attentions}, + booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, + year = {2021} +} +``` diff --git a/mmdetection/configs/dyhead/atss_r50-caffe_fpn_dyhead_1x_coco.py b/mmdetection/configs/dyhead/atss_r50-caffe_fpn_dyhead_1x_coco.py new file mode 100644 index 00000000..8716f122 --- /dev/null +++ b/mmdetection/configs/dyhead/atss_r50-caffe_fpn_dyhead_1x_coco.py @@ -0,0 +1,103 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + type='ATSS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=128), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + neck=[ + dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + dict( + type='DyHead', + in_channels=256, + out_channels=256, + num_blocks=6, + # disable zero_init_offset to follow official implementation + zero_init_offset=False) + ], + bbox_head=dict( + type='ATSSHead', + num_classes=80, + in_channels=256, + pred_kernel_size=1, # follow DyHead official implementation + stacked_convs=0, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128], + center_offset=0.5), # follow DyHead official implementation + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + # training and testing settings + train_cfg=dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# optimizer +optim_wrapper = dict(optimizer=dict(lr=0.01)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True, backend='pillow'), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1333, 800), keep_ratio=True, backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py b/mmdetection/configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py new file mode 100644 index 00000000..89e89b98 --- /dev/null +++ b/mmdetection/configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py @@ -0,0 +1,72 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + type='ATSS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=[ + dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + dict(type='DyHead', in_channels=256, out_channels=256, num_blocks=6) + ], + bbox_head=dict( + type='ATSSHead', + num_classes=80, + in_channels=256, + stacked_convs=0, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + # training and testing settings + train_cfg=dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# optimizer +optim_wrapper = dict(optimizer=dict(lr=0.01)) diff --git a/mmdetection/configs/dyhead/atss_swin-l-p4-w12_fpn_dyhead_ms-2x_coco.py b/mmdetection/configs/dyhead/atss_swin-l-p4-w12_fpn_dyhead_ms-2x_coco.py new file mode 100644 index 00000000..f537b9dc --- /dev/null +++ b/mmdetection/configs/dyhead/atss_swin-l-p4-w12_fpn_dyhead_ms-2x_coco.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa +model = dict( + type='ATSS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=128), + backbone=dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=[ + dict( + type='FPN', + in_channels=[384, 768, 1536], + out_channels=256, + start_level=0, + add_extra_convs='on_output', + num_outs=5), + dict( + type='DyHead', + in_channels=256, + out_channels=256, + num_blocks=6, + # disable zero_init_offset to follow official implementation + zero_init_offset=False) + ], + bbox_head=dict( + type='ATSSHead', + num_classes=80, + in_channels=256, + pred_kernel_size=1, # follow DyHead official implementation + stacked_convs=0, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128], + center_offset=0.5), # follow DyHead official implementation + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + # training and testing settings + train_cfg=dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=[(2000, 480), (2000, 1200)], + keep_ratio=True, + backend='pillow'), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(2000, 1200), keep_ratio=True, backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='RepeatDataset', + times=2, + dataset=dict( + type={{_base_.dataset_type}}, + data_root={{_base_.data_root}}, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args={{_base_.backend_args}}))) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00005, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'relative_position_bias_table': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + }), + clip_grad=None) diff --git a/mmdetection/configs/dyhead/metafile.yml b/mmdetection/configs/dyhead/metafile.yml new file mode 100644 index 00000000..28b5a582 --- /dev/null +++ b/mmdetection/configs/dyhead/metafile.yml @@ -0,0 +1,76 @@ +Collections: + - Name: DyHead + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 4x T4 GPUs + Architecture: + - ATSS + - DyHead + - FPN + - ResNet + - Deformable Convolution + - Pyramid Convolution + Paper: + URL: https://arxiv.org/abs/2106.08322 + Title: 'Dynamic Head: Unifying Object Detection Heads with Attentions' + README: configs/dyhead/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.22.0/mmdet/models/necks/dyhead.py#L130 + Version: v2.22.0 + +Models: + - Name: atss_r50-caffe_fpn_dyhead_1x_coco + In Collection: DyHead + Config: configs/dyhead/atss_r50-caffe_fpn_dyhead_1x_coco.py + Metadata: + Training Memory (GB): 5.4 + inference time (ms/im): + - value: 75.7 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_r50_fpn_dyhead_for_reproduction_1x_coco/atss_r50_fpn_dyhead_for_reproduction_4x4_1x_coco_20220107_213939-162888e6.pth + + - Name: atss_r50_fpn_dyhead_1x_coco + In Collection: DyHead + Config: configs/dyhead/atss_r50_fpn_dyhead_1x_coco.py + Metadata: + Training Memory (GB): 4.9 + inference time (ms/im): + - value: 73.1 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_r50_fpn_dyhead_4x4_1x_coco/atss_r50_fpn_dyhead_4x4_1x_coco_20211219_023314-eaa620c6.pth + + - Name: atss_swin-l-p4-w12_fpn_dyhead_ms-2x_coco + In Collection: DyHead + Config: configs/dyhead/atss_swin-l-p4-w12_fpn_dyhead_ms-2x_coco.py + Metadata: + Training Memory (GB): 58.4 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 56.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dyhead/atss_swin-l-p4-w12_fpn_dyhead_mstrain_2x_coco/atss_swin-l-p4-w12_fpn_dyhead_mstrain_2x_coco_20220509_100315-bc5b6516.pth diff --git a/mmdetection/configs/dynamic_rcnn/README.md b/mmdetection/configs/dynamic_rcnn/README.md new file mode 100644 index 00000000..b5e803a2 --- /dev/null +++ b/mmdetection/configs/dynamic_rcnn/README.md @@ -0,0 +1,30 @@ +# Dynamic R-CNN + +> [Dynamic R-CNN: Towards High Quality Object Detection via Dynamic Training](https://arxiv.org/abs/2004.06002) + + + +## Abstract + +Although two-stage object detectors have continuously advanced the state-of-the-art performance in recent years, the training process itself is far from crystal. In this work, we first point out the inconsistency problem between the fixed network settings and the dynamic training procedure, which greatly affects the performance. For example, the fixed label assignment strategy and regression loss function cannot fit the distribution change of proposals and thus are harmful to training high quality detectors. Consequently, we propose Dynamic R-CNN to adjust the label assignment criteria (IoU threshold) and the shape of regression loss function (parameters of SmoothL1 Loss) automatically based on the statistics of proposals during training. This dynamic design makes better use of the training samples and pushes the detector to fit more high quality samples. Specifically, our method improves upon ResNet-50-FPN baseline with 1.9% AP and 5.5% AP90 on the MS COCO dataset with no extra overhead. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :-----: | :-----: | :------: | :------------: | :----: | :-----------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | 1x | 3.8 | | 38.9 | [config](./dynamic-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/dynamic_rcnn/dynamic_rcnn_r50_fpn_1x/dynamic_rcnn_r50_fpn_1x-62a3f276.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/dynamic_rcnn/dynamic_rcnn_r50_fpn_1x/dynamic_rcnn_r50_fpn_1x_20200618_095048.log.json) | + +## Citation + +```latex +@article{DynamicRCNN, + author = {Hongkai Zhang and Hong Chang and Bingpeng Ma and Naiyan Wang and Xilin Chen}, + title = {Dynamic {R-CNN}: Towards High Quality Object Detection via Dynamic Training}, + journal = {arXiv preprint arXiv:2004.06002}, + year = {2020} +} +``` diff --git a/mmdetection/configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..f64dfa0b --- /dev/null +++ b/mmdetection/configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,28 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + type='DynamicRoIHead', + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))), + train_cfg=dict( + rpn_proposal=dict(nms=dict(iou_threshold=0.85)), + rcnn=dict( + dynamic_rcnn=dict( + iou_topk=75, + beta_topk=10, + update_iter_interval=100, + initial_iou=0.4, + initial_beta=1.0))), + test_cfg=dict(rpn=dict(nms=dict(iou_threshold=0.85)))) diff --git a/mmdetection/configs/dynamic_rcnn/metafile.yml b/mmdetection/configs/dynamic_rcnn/metafile.yml new file mode 100644 index 00000000..64ab3b0c --- /dev/null +++ b/mmdetection/configs/dynamic_rcnn/metafile.yml @@ -0,0 +1,35 @@ +Collections: + - Name: Dynamic R-CNN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Dynamic R-CNN + - FPN + - RPN + - ResNet + - RoIAlign + Paper: + URL: https://arxiv.org/pdf/2004.06002 + Title: 'Dynamic R-CNN: Towards High Quality Object Detection via Dynamic Training' + README: configs/dynamic_rcnn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.2.0/mmdet/models/roi_heads/dynamic_roi_head.py#L11 + Version: v2.2.0 + +Models: + - Name: dynamic-rcnn_r50_fpn_1x_coco + In Collection: Dynamic R-CNN + Config: configs/dynamic_rcnn/dynamic-rcnn_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.8 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/dynamic_rcnn/dynamic_rcnn_r50_fpn_1x/dynamic_rcnn_r50_fpn_1x-62a3f276.pth diff --git a/mmdetection/configs/efficientnet/README.md b/mmdetection/configs/efficientnet/README.md new file mode 100644 index 00000000..941944db --- /dev/null +++ b/mmdetection/configs/efficientnet/README.md @@ -0,0 +1,30 @@ +# EfficientNet + +> [EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks](https://arxiv.org/abs/1905.11946v5) + + + +## Introduction + +Convolutional Neural Networks (ConvNets) are commonly developed at a fixed resource budget, and then scaled up for better accuracy if more resources are available. In this paper, we systematically study model scaling and identify that carefully balancing network depth, width, and resolution can lead to better performance. Based on this observation, we propose a new scaling method that uniformly scales all dimensions of depth/width/resolution using a simple yet highly effective compound coefficient. We demonstrate the effectiveness of this method on scaling up MobileNets and ResNet. + +To go even further, we use neural architecture search to design a new baseline network and scale it up to obtain a family of models, called EfficientNets, which achieve much better accuracy and efficiency than previous ConvNets. In particular, our EfficientNet-B7 achieves state-of-the-art 84.3% top-1 accuracy on ImageNet, while being 8.4x smaller and 6.1x faster on inference than the best existing ConvNet. Our EfficientNets also transfer well and achieve state-of-the-art accuracy on CIFAR-100 (91.7%), Flowers (98.8%), and 3 other transfer learning datasets, with an order of magnitude fewer parameters. + +## Results and Models + +### RetinaNet + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Efficientnet-b3 | pytorch | 1x | - | - | 40.5 | [config](./retinanet_effb3_fpn_8xb4-crop896-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/efficientnet/retinanet_effb3_fpn_crop896_8x4_1x_coco/retinanet_effb3_fpn_crop896_8x4_1x_coco_20220322_234806-615a0dda.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/efficientnet/retinanet_effb3_fpn_crop896_8x4_1x_coco/retinanet_effb3_fpn_crop896_8x4_1x_coco_20220322_234806.log.json) | + +## Citation + +```latex +@article{tan2019efficientnet, + title={Efficientnet: Rethinking model scaling for convolutional neural networks}, + author={Tan, Mingxing and Le, Quoc V}, + journal={arXiv preprint arXiv:1905.11946}, + year={2019} +} +``` diff --git a/mmdetection/configs/efficientnet/metafile.yml b/mmdetection/configs/efficientnet/metafile.yml new file mode 100644 index 00000000..6e220c8a --- /dev/null +++ b/mmdetection/configs/efficientnet/metafile.yml @@ -0,0 +1,19 @@ +Models: + - Name: retinanet_effb3_fpn_8xb4-crop896-1x_coco + In Collection: RetinaNet + Config: configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/efficientnet/retinanet_effb3_fpn_crop896_8x4_1x_coco/retinanet_effb3_fpn_crop896_8x4_1x_coco_20220322_234806-615a0dda.pth + Paper: + URL: https://arxiv.org/abs/1905.11946v5 + Title: 'EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks' + README: configs/efficientnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.23.0/mmdet/models/backbones/efficientnet.py#L159 + Version: v2.23.0 diff --git a/mmdetection/configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py b/mmdetection/configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py new file mode 100644 index 00000000..2d0d9cef --- /dev/null +++ b/mmdetection/configs/efficientnet/retinanet_effb3_fpn_8xb4-crop896-1x_coco.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/schedules/schedule_1x.py', + '../_base_/datasets/coco_detection.py', '../_base_/default_runtime.py' +] + +image_size = (896, 896) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] +norm_cfg = dict(type='BN', requires_grad=True) +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b3_3rdparty_8xb32-aa_in1k_20220119-5b4887a0.pth' # noqa +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + _delete_=True, + type='EfficientNet', + arch='b3', + drop_path_rate=0.2, + out_indices=(3, 4, 5), + frozen_stages=0, + norm_cfg=dict( + type='SyncBN', requires_grad=True, eps=1e-3, momentum=0.01), + norm_eval=False, + init_cfg=dict( + type='Pretrained', prefix='backbone', checkpoint=checkpoint)), + neck=dict( + in_channels=[48, 136, 384], + start_level=0, + out_channels=256, + relu_before_extra_convs=True, + no_norm_on_lateral=True, + norm_cfg=norm_cfg), + bbox_head=dict(type='RetinaSepBNHead', num_ins=5, norm_cfg=norm_cfg), + # training and testing settings + train_cfg=dict(assigner=dict(neg_iou_thr=0.5))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.8, 1.2), + keep_ratio=True), + dict(type='RandomCrop', crop_size=image_size), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=image_size, keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=4, num_workers=4, dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# optimizer +optim_wrapper = dict( + optimizer=dict(lr=0.04), + paramwise_cfg=dict(norm_decay_mult=0, bypass_duplicate=True)) + +# learning policy +max_epochs = 12 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) + +# cudnn_benchmark=True can accelerate fix-size training +env_cfg = dict(cudnn_benchmark=True) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (4 samples per GPU) +auto_scale_lr = dict(base_batch_size=32) diff --git a/mmdetection/configs/empirical_attention/README.md b/mmdetection/configs/empirical_attention/README.md new file mode 100644 index 00000000..c0b4a68b --- /dev/null +++ b/mmdetection/configs/empirical_attention/README.md @@ -0,0 +1,33 @@ +# Empirical Attention + +> [An Empirical Study of Spatial Attention Mechanisms in Deep Networks](https://arxiv.org/abs/1904.05873) + + + +## Abstract + +Attention mechanisms have become a popular component in deep neural networks, yet there has been little examination of how different influencing factors and methods for computing attention from these factors affect performance. Toward a better general understanding of attention mechanisms, we present an empirical study that ablates various spatial attention elements within a generalized attention formulation, encompassing the dominant Transformer attention as well as the prevalent deformable convolution and dynamic convolution modules. Conducted on a variety of applications, the study yields significant findings about spatial attention in deep networks, some of which run counter to conventional understanding. For example, we find that the query and key content comparison in Transformer attention is negligible for self-attention, but vital for encoder-decoder attention. A proper combination of deformable convolution with key content only saliency achieves the best accuracy-efficiency tradeoff in self-attention. Our results suggest that there exists much room for improvement in the design of attention mechanisms. + +
    + +
    + +## Results and Models + +| Backbone | Attention Component | DCN | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :-----------------: | :-: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | 1111 | N | 1x | 8.0 | 13.8 | 40.0 | [config](./faster-rcnn_r50-attn1111_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_1111_1x_coco/faster_rcnn_r50_fpn_attention_1111_1x_coco_20200130-403cccba.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_1111_1x_coco/faster_rcnn_r50_fpn_attention_1111_1x_coco_20200130_210344.log.json) | +| R-50 | 0010 | N | 1x | 4.2 | 18.4 | 39.1 | [config](./faster-rcnn_r50-attn0010_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_0010_1x_coco/faster_rcnn_r50_fpn_attention_0010_1x_coco_20200130-7cb0c14d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_0010_1x_coco/faster_rcnn_r50_fpn_attention_0010_1x_coco_20200130_210125.log.json) | +| R-50 | 1111 | Y | 1x | 8.0 | 12.7 | 42.1 | [config](./faster-rcnn_r50-attn1111-dcn_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_1111_dcn_1x_coco/faster_rcnn_r50_fpn_attention_1111_dcn_1x_coco_20200130-8b2523a6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_1111_dcn_1x_coco/faster_rcnn_r50_fpn_attention_1111_dcn_1x_coco_20200130_204442.log.json) | +| R-50 | 0010 | Y | 1x | 4.2 | 17.1 | 42.0 | [config](./faster-rcnn_r50-attn0010-dcn_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_0010_dcn_1x_coco/faster_rcnn_r50_fpn_attention_0010_dcn_1x_coco_20200130-1a2e831d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_0010_dcn_1x_coco/faster_rcnn_r50_fpn_attention_0010_dcn_1x_coco_20200130_210410.log.json) | + +## Citation + +```latex +@article{zhu2019empirical, + title={An Empirical Study of Spatial Attention Mechanisms in Deep Networks}, + author={Zhu, Xizhou and Cheng, Dazhi and Zhang, Zheng and Lin, Stephen and Dai, Jifeng}, + journal={arXiv preprint arXiv:1904.05873}, + year={2019} +} +``` diff --git a/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn0010-dcn_fpn_1x_coco.py b/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn0010-dcn_fpn_1x_coco.py new file mode 100644 index 00000000..e1ae17a7 --- /dev/null +++ b/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn0010-dcn_fpn_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + plugins=[ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='0010', + kv_stride=2), + stages=(False, False, True, True), + position='after_conv2') + ], + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn0010_fpn_1x_coco.py b/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn0010_fpn_1x_coco.py new file mode 100644 index 00000000..7336d292 --- /dev/null +++ b/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn0010_fpn_1x_coco.py @@ -0,0 +1,13 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict(plugins=[ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='0010', + kv_stride=2), + stages=(False, False, True, True), + position='after_conv2') + ])) diff --git a/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn1111-dcn_fpn_1x_coco.py b/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn1111-dcn_fpn_1x_coco.py new file mode 100644 index 00000000..980e23d4 --- /dev/null +++ b/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn1111-dcn_fpn_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + plugins=[ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='1111', + kv_stride=2), + stages=(False, False, True, True), + position='after_conv2') + ], + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) diff --git a/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py b/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py new file mode 100644 index 00000000..426bc09f --- /dev/null +++ b/mmdetection/configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py @@ -0,0 +1,13 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict(plugins=[ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='1111', + kv_stride=2), + stages=(False, False, True, True), + position='after_conv2') + ])) diff --git a/mmdetection/configs/empirical_attention/metafile.yml b/mmdetection/configs/empirical_attention/metafile.yml new file mode 100644 index 00000000..b488da7d --- /dev/null +++ b/mmdetection/configs/empirical_attention/metafile.yml @@ -0,0 +1,103 @@ +Collections: + - Name: Empirical Attention + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Deformable Convolution + - FPN + - RPN + - ResNet + - RoIAlign + - Spatial Attention + Paper: + URL: https://arxiv.org/pdf/1904.05873 + Title: 'An Empirical Study of Spatial Attention Mechanisms in Deep Networks' + README: configs/empirical_attention/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/ops/generalized_attention.py#L10 + Version: v2.0.0 + +Models: + - Name: faster-rcnn_r50_fpn_attention_1111_1x_coco + In Collection: Empirical Attention + Config: configs/empirical_attention/faster-rcnn_r50-attn1111_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.0 + inference time (ms/im): + - value: 72.46 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_1111_1x_coco/faster_rcnn_r50_fpn_attention_1111_1x_coco_20200130-403cccba.pth + + - Name: faster-rcnn_r50_fpn_attention_0010_1x_coco + In Collection: Empirical Attention + Config: configs/empirical_attention/faster-rcnn_r50-attn0010_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.2 + inference time (ms/im): + - value: 54.35 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_0010_1x_coco/faster_rcnn_r50_fpn_attention_0010_1x_coco_20200130-7cb0c14d.pth + + - Name: faster-rcnn_r50_fpn_attention_1111_dcn_1x_coco + In Collection: Empirical Attention + Config: configs/empirical_attention/faster-rcnn_r50-attn1111-dcn_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.0 + inference time (ms/im): + - value: 78.74 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_1111_dcn_1x_coco/faster_rcnn_r50_fpn_attention_1111_dcn_1x_coco_20200130-8b2523a6.pth + + - Name: faster-rcnn_r50_fpn_attention_0010_dcn_1x_coco + In Collection: Empirical Attention + Config: configs/empirical_attention/faster-rcnn_r50-attn0010-dcn_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.2 + inference time (ms/im): + - value: 58.48 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/empirical_attention/faster_rcnn_r50_fpn_attention_0010_dcn_1x_coco/faster_rcnn_r50_fpn_attention_0010_dcn_1x_coco_20200130-1a2e831d.pth diff --git a/mmdetection/configs/fast_rcnn/README.md b/mmdetection/configs/fast_rcnn/README.md new file mode 100644 index 00000000..0bdc9359 --- /dev/null +++ b/mmdetection/configs/fast_rcnn/README.md @@ -0,0 +1,121 @@ +# Fast R-CNN + +> [Fast R-CNN](https://arxiv.org/abs/1504.08083) + + + +## Abstract + +This paper proposes a Fast Region-based Convolutional Network method (Fast R-CNN) for object detection. Fast R-CNN builds on previous work to efficiently classify object proposals using deep convolutional networks. Compared to previous work, Fast R-CNN employs several innovations to improve training and testing speed while also increasing detection accuracy. Fast R-CNN trains the very deep VGG16 network 9x faster than R-CNN, is 213x faster at test-time, and achieves a higher mAP on PASCAL VOC 2012. Compared to SPPnet, Fast R-CNN trains VGG16 3x faster, tests 10x faster, and is more accurate. + +
    + +
    + +## Introduction + +Before training the Fast R-CNN, users should first train an [RPN](../rpn/README.md), and use the RPN to extract the region proposals. +The region proposals can be obtained by setting `DumpProposals` pseudo metric. The dumped results is a `dict(file_name: pred_instance)`. +The `pred_instance` is an `InstanceData` containing the sorted boxes and scores predicted by RPN. We provide example of dumping proposals in [RPN config](../rpn/rpn_r50_fpn_1x_coco.py). + +- First, it should be obtained the region proposals in both training and validation (or testing) set. + change the type of `test_evaluator` to `DumpProposals` in the RPN config to get the region proposals as below: + + The config of get training image region proposals can be set as below: + + ```python + # For training set + val_dataloader = dict( + dataset=dict( + ann_file='data/coco/annotations/instances_train2017.json', + data_prefix=dict(img='val2017/'))) + val_dataloader = dict( + _delete_=True, + type='DumpProposals', + output_dir='data/coco/proposals/', + proposals_file='rpn_r50_fpn_1x_train2017.pkl') + test_dataloader = val_dataloader + test_evaluator = val_dataloader + ``` + + The config of get validation image region proposals can be set as below: + + ```python + # For validation set + val_dataloader = dict( + _delete_=True, + type='DumpProposals', + output_dir='data/coco/proposals/', + proposals_file='rpn_r50_fpn_1x_val2017.pkl') + test_evaluator = val_dataloader + ``` + + Extract the region proposals command can be set as below: + + ```bash + ./tools/dist_test.sh \ + configs/rpn_r50_fpn_1x_coco.py \ + checkpoints/rpn_r50_fpn_1x_coco_20200218-5525fa2e.pth \ + 8 + ``` + + Users can refer to [test tutorial](https://mmdetection.readthedocs.io/en/latest/user_guides/test.html) for more details. + +- Then, modify the path of `proposal_file` in the dataset and using `ProposalBroadcaster` to process both ground truth bounding boxes and region proposals in pipelines. + An example of Fast R-CNN important setting can be seen as below: + + ```python + train_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args={{_base_.backend_args}}), + dict(type='LoadProposals', num_max_proposals=2000), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='ProposalBroadcaster', + transforms=[ + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + ]), + dict(type='PackDetInputs') + ] + test_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args={{_base_.backend_args}}), + dict(type='LoadProposals', num_max_proposals=None), + dict( + type='ProposalBroadcaster', + transforms=[ + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) + ] + train_dataloader = dict( + dataset=dict( + proposal_file='proposals/rpn_r50_fpn_1x_train2017.pkl', + pipeline=train_pipeline)) + val_dataloader = dict( + dataset=dict( + proposal_file='proposals/rpn_r50_fpn_1x_val2017.pkl', + pipeline=test_pipeline)) + test_dataloader = val_dataloader + ``` + +- Finally, users can start training the Fast R-CNN. + +## Results and Models + +## Citation + +```latex +@inproceedings{girshick2015fast, + title={Fast r-cnn}, + author={Girshick, Ross}, + booktitle={Proceedings of the IEEE international conference on computer vision}, + year={2015} +} +``` diff --git a/mmdetection/configs/fast_rcnn/fast-rcnn_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/fast_rcnn/fast-rcnn_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..02c70296 --- /dev/null +++ b/mmdetection/configs/fast_rcnn/fast-rcnn_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './fast-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/fast_rcnn/fast-rcnn_r101_fpn_1x_coco.py b/mmdetection/configs/fast_rcnn/fast-rcnn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..5af6b223 --- /dev/null +++ b/mmdetection/configs/fast_rcnn/fast-rcnn_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './fast-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/fast_rcnn/fast-rcnn_r101_fpn_2x_coco.py b/mmdetection/configs/fast_rcnn/fast-rcnn_r101_fpn_2x_coco.py new file mode 100644 index 00000000..73425cf1 --- /dev/null +++ b/mmdetection/configs/fast_rcnn/fast-rcnn_r101_fpn_2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './fast-rcnn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/fast_rcnn/fast-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/fast_rcnn/fast-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..3110f9fd --- /dev/null +++ b/mmdetection/configs/fast_rcnn/fast-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = './fast-rcnn_r50_fpn_1x_coco.py' + +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + norm_cfg=dict(type='BN', requires_grad=False), + style='caffe', + norm_eval=True, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/configs/fast_rcnn/fast-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/fast_rcnn/fast-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..daefe2d2 --- /dev/null +++ b/mmdetection/configs/fast_rcnn/fast-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,39 @@ +_base_ = [ + '../_base_/models/fast-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadProposals', num_max_proposals=2000), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='ProposalBroadcaster', + transforms=[ + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + ]), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadProposals', num_max_proposals=None), + dict( + type='ProposalBroadcaster', + transforms=[ + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + dataset=dict( + proposal_file='proposals/rpn_r50_fpn_1x_train2017.pkl', + pipeline=train_pipeline)) +val_dataloader = dict( + dataset=dict( + proposal_file='proposals/rpn_r50_fpn_1x_val2017.pkl', + pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/fast_rcnn/fast-rcnn_r50_fpn_2x_coco.py b/mmdetection/configs/fast_rcnn/fast-rcnn_r50_fpn_2x_coco.py new file mode 100644 index 00000000..d609a7c0 --- /dev/null +++ b/mmdetection/configs/fast_rcnn/fast-rcnn_r50_fpn_2x_coco.py @@ -0,0 +1,14 @@ +_base_ = './fast-rcnn_r50_fpn_1x_coco.py' + +train_cfg = dict(max_epochs=24) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=24, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/faster_rcnn/README.md b/mmdetection/configs/faster_rcnn/README.md new file mode 100644 index 00000000..8bcdcf6d --- /dev/null +++ b/mmdetection/configs/faster_rcnn/README.md @@ -0,0 +1,88 @@ +# Faster R-CNN + +> [Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks](https://arxiv.org/abs/1506.01497) + + + +## Abstract + +State-of-the-art object detection networks depend on region proposal algorithms to hypothesize object locations. Advances like SPPnet and Fast R-CNN have reduced the running time of these detection networks, exposing region proposal computation as a bottleneck. In this work, we introduce a Region Proposal Network (RPN) that shares full-image convolutional features with the detection network, thus enabling nearly cost-free region proposals. An RPN is a fully convolutional network that simultaneously predicts object bounds and objectness scores at each position. The RPN is trained end-to-end to generate high-quality region proposals, which are used by Fast R-CNN for detection. We further merge RPN and Fast R-CNN into a single network by sharing their convolutional features---using the recently popular terminology of neural networks with 'attention' mechanisms, the RPN component tells the unified network where to look. For the very deep VGG-16 model, our detection system has a frame rate of 5fps (including all steps) on a GPU, while achieving state-of-the-art object detection accuracy on PASCAL VOC 2007, 2012, and MS COCO datasets with only 300 proposals per image. In ILSVRC and COCO 2015 competitions, Faster R-CNN and RPN are the foundations of the 1st-place winning entries in several tracks. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-C4 | caffe | 1x | - | - | 35.6 | [config](./faster-rcnn_r50-caffe_c4-1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50-caffe-c4_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_c4_1x_coco/faster_rcnn_r50_caffe_c4_1x_coco_20220316_150152.log.json) | +| R-50-DC5 | caffe | 1x | - | - | 37.2 | [config](./faster-rcnn_r50-caffe-dc5_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50-caffe-dc5_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_dc5_1x_coco/faster_rcnn_r50_caffe_dc5_1x_coco_20201030_151909.log.json) | +| R-50-FPN | caffe | 1x | 3.8 | | 37.8 | [config](./faster-rcnn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50-caffe_fpn_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_fpn_1x_coco/faster_rcnn_r50_caffe_fpn_1x_coco_20200504_180032.log.json) | +| R-50-FPN | pytorch | 1x | 4.0 | 21.4 | 37.4 | [config](./faster-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50_fpn_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130_204655.log.json) | +| R-50-FPN (FP16) | pytorch | 1x | 3.4 | 28.8 | 37.5 | [config](./faster-rcnn_r50_fpn_amp-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fp16/faster_rcnn_r50_fpn_fp16_1x_coco/faster_rcnn_r50_fpn_fp16_1x_coco_20200204-d4dc1471.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fp16/faster_rcnn_r50_fpn_fp16_1x_coco/faster_rcnn_r50_fpn_fp16_1x_coco_20200204_143530.log.json) | +| R-50-FPN | pytorch | 2x | - | - | 38.4 | [config](./faster-rcnn_r50_fpn_2x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50_fpn_2x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_2x_coco/faster_rcnn_r50_fpn_2x_coco_20200504_210434.log.json) | +| R-101-FPN | caffe | 1x | 5.7 | | 39.8 | [config](./faster-rcnn_r101-caffe_fpn_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r101-caffe_fpn_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_caffe_fpn_1x_coco/faster_rcnn_r101_caffe_fpn_1x_coco_20200504_180057.log.json) | +| R-101-FPN | pytorch | 1x | 6.0 | 15.6 | 39.4 | [config](./faster-rcnn_r101_fpn_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r101_fpn_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_fpn_1x_coco/faster_rcnn_r101_fpn_1x_coco_20200130_204655.log.json) | +| R-101-FPN | pytorch | 2x | - | - | 39.8 | [config](./faster-rcnn_r101_fpn_2x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r101_fpn_2x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_fpn_2x_coco/faster_rcnn_r101_fpn_2x_coco_20200504_210455.log.json) | +| X-101-32x4d-FPN | pytorch | 1x | 7.2 | 13.8 | 41.2 | [config](./faster-rcnn_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_x101-32x4d_fpn_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_32x4d_fpn_1x_coco/faster_rcnn_x101_32x4d_fpn_1x_coco_20200203_000520.log.json) | +| X-101-32x4d-FPN | pytorch | 2x | - | - | 41.2 | [config](./faster-rcnn_x101-32x4d_fpn_2x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_x101-32x4d_fpn_2x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_32x4d_fpn_2x_coco/faster_rcnn_x101_32x4d_fpn_2x_coco_20200506_041400.log.json) | +| X-101-64x4d-FPN | pytorch | 1x | 10.3 | 9.4 | 42.1 | [config](./faster-rcnn_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_x101-64x4d_fpn_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_64x4d_fpn_1x_coco/faster_rcnn_x101_64x4d_fpn_1x_coco_20200204_134340.log.json) | +| X-101-64x4d-FPN | pytorch | 2x | - | - | 41.6 | [config](./faster-rcnn_x101-64x4d_fpn_2x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_x101-64x4d_fpn_2x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_64x4d_fpn_2x_coco/faster_rcnn_x101_64x4d_fpn_2x_coco_20200512_161033.log.json) | + +## Different regression loss + +We trained with R-50-FPN pytorch style backbone for 1x schedule. + +| Backbone | Loss type | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :------------: | :------: | :------------: | :----: | :----------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | L1Loss | 4.0 | 21.4 | 37.4 | [config](./faster-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50_fpn_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130_204655.log.json) | +| R-50-FPN | IoULoss | | | 37.9 | [config](./faster-rcnn_r50_fpn_iou_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50_fpn_iou_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_iou_1x_coco/faster_rcnn_r50_fpn_iou_1x_coco_20200506_095954.log.json) | +| R-50-FPN | GIoULoss | | | 37.6 | [config](./faster-rcnn_r50_fpn_giou_1x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50_fpn_giou_1x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_giou_1x_coco_20200505_161120.log.json) | +| R-50-FPN | BoundedIoULoss | | | 37.4 | [config](./faster-rcnn_r50_fpn_bounded-iou_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_bounded_iou_1x_coco-98ad993b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_bounded_iou_1x_coco_20200505_160738.log.json) | + +## Pre-trained Models + +We also train some models with longer schedules and multi-scale training. The users could finetune them for downstream tasks. + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-----------------------------------------------------------: | :-----: | :-----: | :------: | :------------: | :----: | :--------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [R-50-C4](./faster-rcnn_r50-caffe-c4_ms-1x_coco.py) | caffe | 1x | - | | 35.9 | [config](./faster-rcnn_r50-caffe-c4_ms-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_c4_mstrain_1x_coco/faster_rcnn_r50_caffe_c4_mstrain_1x_coco_20220316_150527-db276fed.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_c4_mstrain_1x_coco/faster_rcnn_r50_caffe_c4_mstrain_1x_coco_20220316_150527.log.json) | +| [R-50-DC5](./faster-rcnn_r50-caffe-dc5_ms-1x_coco.py) | caffe | 1x | - | | 37.4 | [config](./faster-rcnn_r50-caffe-dc5_ms-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_dc5_mstrain_1x_coco/faster_rcnn_r50_caffe_dc5_mstrain_1x_coco_20201028_233851-b33d21b9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_dc5_mstrain_1x_coco/faster_rcnn_r50_caffe_dc5_mstrain_1x_coco_20201028_233851.log.json) | +| [R-50-DC5](./faster-rcnn_r50-caffe-dc5_ms-3x_coco.py) | caffe | 3x | - | | 38.7 | [config](./faster-rcnn_r50-caffe-dc5_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_dc5_mstrain_3x_coco/faster_rcnn_r50_caffe_dc5_mstrain_3x_coco_20201028_002107-34a53b2c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_dc5_mstrain_3x_coco/faster_rcnn_r50_caffe_dc5_mstrain_3x_coco_20201028_002107.log.json) | +| [R-50-FPN](./faster-rcnn_r50-caffe_fpn_ms-2x_coco.py) | caffe | 2x | 3.7 | | 39.7 | [config](./faster-rcnn_r50-caffe_fpn_ms-2x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50-caffe_fpn_ms-2x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_fpn_mstrain_2x_coco/faster_rcnn_r50_caffe_fpn_mstrain_2x_coco_20200504_231813.log.json) | +| [R-50-FPN](./faster-rcnn_r50-caffe_fpn_ms-3x_coco.py) | caffe | 3x | 3.7 | | 39.9 | [config](./faster-rcnn_r50-caffe_fpn_ms-3x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r50-caffe_fpn_ms-3x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_fpn_mstrain_3x_coco/faster_rcnn_r50_caffe_fpn_mstrain_3x_coco_20210526_095054.log.json) | +| [R-50-FPN](./faster-rcnn_r50_fpn_ms-3x_coco.py) | pytorch | 3x | 3.9 | | 40.3 | [config](./faster-rcnn_r50_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_mstrain_3x_coco/faster_rcnn_r50_fpn_mstrain_3x_coco_20210524_110822-e10bd31c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_mstrain_3x_coco/faster_rcnn_r50_fpn_mstrain_3x_coco_20210524_110822.log.json) | +| [R-101-FPN](./faster-rcnn_r101-caffe_fpn_ms-3x_coco.py) | caffe | 3x | 5.6 | | 42.0 | [config](./faster-rcnn_r101-caffe_fpn_ms-3x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r101-caffe_fpn_ms-3x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_caffe_fpn_mstrain_3x_coco/faster_rcnn_r101_caffe_fpn_mstrain_3x_coco_20210526_095742.log.json) | +| [R-101-FPN](./faster-rcnn_r101_fpn_ms-3x_coco.py) | pytorch | 3x | 5.8 | | 41.8 | [config](./faster-rcnn_r101_fpn_ms-3x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_r101_fpn_ms-3x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_fpn_mstrain_3x_coco/faster_rcnn_r101_fpn_mstrain_3x_coco_20210524_110822.log.json) | +| [X-101-32x4d-FPN](./faster-rcnn_x101-32x4d_fpn_ms-3x_coco.py) | pytorch | 3x | 7.0 | | 42.5 | [config](./faster-rcnn_x101-32x4d_fpn_ms-3x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_x101-32x4d_fpn_ms-3x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_32x4d_fpn_mstrain_3x_coco/faster_rcnn_x101_32x4d_fpn_mstrain_3x_coco_20210524_124151.log.json) | +| [X-101-32x8d-FPN](./faster-rcnn_x101-32x8d_fpn_ms-3x_coco.py) | pytorch | 3x | 10.1 | | 42.4 | [config](./faster-rcnn_x101-32x8d_fpn_ms-3x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_x101-32x8d_fpn_ms-3x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_32x8d_fpn_mstrain_3x_coco/faster_rcnn_x101_32x8d_fpn_mstrain_3x_coco_20210604_182954.log.json) | +| [X-101-64x4d-FPN](./faster-rcnn_x101-64x4d_fpn_ms-3x_coco.py) | pytorch | 3x | 10.0 | | 43.1 | [config](./faster-rcnn_x101-64x4d_fpn_ms-3x_coco.py) | [model](https://download.openxlab.org.cn/models/mmdetection/FasterR-CNN/weight/faster-rcnn_x101-64x4d_fpn_ms-3x_coco) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_64x4d_fpn_mstrain_3x_coco/faster_rcnn_x101_64x4d_fpn_mstrain_3x_coco_20210524_124528.log.json) | + +We further finetune some pre-trained models on the COCO subsets, which only contain only a few of the 80 categories. + +| Backbone | Style | Class name | Pre-traind model | Mem (GB) | box AP | Config | Download | +| ------------------------------------------------------------------------ | ----- | ------------------ | -------------------------------------------------------------- | -------- | ------ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [R-50-FPN](./faster-rcnn_r50-caffe_fpn_ms-1x_coco-person.py) | caffe | person | [R-50-FPN-Caffe-3x](./faster-rcnn_r50-caffe_fpn_ms-3x_coco.py) | 3.7 | 55.8 | [config](./faster-rcnn_r50-caffe_fpn_ms-1x_coco-person.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco-person/faster_rcnn_r50_fpn_1x_coco-person_20201216_175929-d022e227.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco-person/faster_rcnn_r50_fpn_1x_coco-person_20201216_175929.log.json) | +| [R-50-FPN](./faster-rcnn_r50-caffe_fpn_ms-1x_coco-person-bicycle-car.py) | caffe | person-bicycle-car | [R-50-FPN-Caffe-3x](./faster-rcnn_r50-caffe_fpn_ms-3x_coco.py) | 3.7 | 44.1 | [config](./faster-rcnn_r50-caffe_fpn_ms-1x_coco-person-bicycle-car.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco-person-bicycle-car/faster_rcnn_r50_fpn_1x_coco-person-bicycle-car_20201216_173117-6eda6d92.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco-person-bicycle-car/faster_rcnn_r50_fpn_1x_coco-person-bicycle-car_20201216_173117.log.json) | + +## Torchvision New Receipe (TNR) + +Torchvision released its high-precision ResNet models. The training details can be found on the [Pytorch website](https://pytorch.org/blog/how-to-train-state-of-the-art-models-using-torchvision-latest-primitives/). Here, we have done grid searches on learning rate and weight decay and found the optimal hyper-parameter on the detection task. + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :--------------------------------------------------: | :-----: | :-----: | :------: | :------------: | :----: | :------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [R-50-TNR](./faster-rcnn_r50-tnr-pre_fpn_1x_coco.py) | pytorch | 1x | - | | 40.2 | [config](./faster-rcnn_r50-tnr-pre_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_tnr-pretrain_1x_coco/faster_rcnn_r50_fpn_tnr-pretrain_1x_coco_20220320_085147-efedfda4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_tnr-pretrain_1x_coco/faster_rcnn_r50_fpn_tnr-pretrain_1x_coco_20220320_085147.log.json) | + +## Citation + +```latex +@article{Ren_2017, + title={Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks}, + journal={IEEE Transactions on Pattern Analysis and Machine Intelligence}, + publisher={Institute of Electrical and Electronics Engineers (IEEE)}, + author={Ren, Shaoqing and He, Kaiming and Girshick, Ross and Sun, Jian}, + year={2017}, + month={Jun}, +} +``` diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..a18f1ada --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './faster-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r101-caffe_fpn_ms-3x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r101-caffe_fpn_ms-3x_coco.py new file mode 100644 index 00000000..1cdb4d49 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r101-caffe_fpn_ms-3x_coco.py @@ -0,0 +1,11 @@ +_base_ = 'faster-rcnn_r50_fpn_ms-3x_coco.py' + +model = dict( + backbone=dict( + depth=101, + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..d113ae62 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_2x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_2x_coco.py new file mode 100644 index 00000000..b471fb3c --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './faster-rcnn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..a71d4afd --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './faster-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_ms-3x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_ms-3x_coco.py new file mode 100644 index 00000000..8ef6d1f8 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r101_fpn_ms-3x_coco.py @@ -0,0 +1,7 @@ +_base_ = 'faster-rcnn_r50_fpn_ms-3x_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r18_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r18_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..65515c9a --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r18_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './faster-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-c4_ms-1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-c4_ms-1x_coco.py new file mode 100644 index 00000000..7e231e86 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-c4_ms-1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './faster-rcnn_r50-caffe_c4-1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +_base_.train_dataloader.dataset.pipeline = train_pipeline diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_1x_coco.py new file mode 100644 index 00000000..8952a5c9 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50-caffe-dc5.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-1x_coco.py new file mode 100644 index 00000000..63a68859 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-1x_coco.py @@ -0,0 +1,14 @@ +_base_ = 'faster-rcnn_r50-caffe-dc5_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +_base_.train_dataloader.dataset.pipeline = train_pipeline diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-3x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-3x_coco.py new file mode 100644 index 00000000..27063468 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-3x_coco.py @@ -0,0 +1,18 @@ +_base_ = './faster-rcnn_r50-caffe-dc5_ms-1x_coco.py' + +# MMEngine support the following two ways, users can choose +# according to convenience +# param_scheduler = [ +# dict( +# type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), # noqa +# dict( +# type='MultiStepLR', +# begin=0, +# end=12, +# by_epoch=True, +# milestones=[28, 34], +# gamma=0.1) +# ] +_base_.param_scheduler[1].milestones = [28, 34] + +train_cfg = dict(max_epochs=36) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_c4-1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_c4-1x_coco.py new file mode 100644 index 00000000..0888fc01 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_c4-1x_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50-caffe-c4.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..9129a958 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,15 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_90k_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_90k_coco.py new file mode 100644 index 00000000..27f49355 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_90k_coco.py @@ -0,0 +1,22 @@ +_base_ = 'faster-rcnn_r50-caffe_fpn_1x_coco.py' +max_iter = 90000 + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[60000, 80000], + gamma=0.1) +] + +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=10000) +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=10000)) +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco-person-bicycle-car.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco-person-bicycle-car.py new file mode 100644 index 00000000..f36bb055 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco-person-bicycle-car.py @@ -0,0 +1,16 @@ +_base_ = './faster-rcnn_r50-caffe_fpn_ms-1x_coco.py' +model = dict(roi_head=dict(bbox_head=dict(num_classes=3))) +metainfo = { + 'classes': ('person', 'bicycle', 'car'), + 'palette': [ + (220, 20, 60), + (119, 11, 32), + (0, 0, 142), + ] +} + +train_dataloader = dict(dataset=dict(metainfo=metainfo)) +val_dataloader = dict(dataset=dict(metainfo=metainfo)) +test_dataloader = dict(dataset=dict(metainfo=metainfo)) + +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_fpn_mstrain_3x_coco/faster_rcnn_r50_caffe_fpn_mstrain_3x_coco_bbox_mAP-0.398_20200504_163323-30042637.pth' # noqa diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco-person.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco-person.py new file mode 100644 index 00000000..9528b63f --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco-person.py @@ -0,0 +1,14 @@ +_base_ = './faster-rcnn_r50-caffe_fpn_ms-1x_coco.py' +model = dict(roi_head=dict(bbox_head=dict(num_classes=1))) +metainfo = { + 'classes': ('person', ), + 'palette': [ + (220, 20, 60), + ] +} + +train_dataloader = dict(dataset=dict(metainfo=metainfo)) +val_dataloader = dict(dataset=dict(metainfo=metainfo)) +test_dataloader = dict(dataset=dict(metainfo=metainfo)) + +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_fpn_mstrain_3x_coco/faster_rcnn_r50_caffe_fpn_mstrain_3x_coco_bbox_mAP-0.398_20200504_163323-30042637.pth' # noqa diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco.py new file mode 100644 index 00000000..59f1633c --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco.py @@ -0,0 +1,31 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +# MMEngine support the following two ways, users can choose +# according to convenience +# train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +_base_.train_dataloader.dataset.pipeline = train_pipeline diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-2x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-2x_coco.py new file mode 100644 index 00000000..44d320ea --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-2x_coco.py @@ -0,0 +1,18 @@ +_base_ = './faster-rcnn_r50-caffe_fpn_ms-1x_coco.py' + +# MMEngine support the following two ways, users can choose +# according to convenience +# param_scheduler = [ +# dict( +# type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), # noqa +# dict( +# type='MultiStepLR', +# begin=0, +# end=12, +# by_epoch=True, +# milestones=[16, 23], +# gamma=0.1) +# ] +_base_.param_scheduler[1].milestones = [16, 23] + +train_cfg = dict(max_epochs=24) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-3x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-3x_coco.py new file mode 100644 index 00000000..365f6439 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-3x_coco.py @@ -0,0 +1,15 @@ +_base_ = 'faster-rcnn_r50_fpn_ms-3x_coco.py' +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-90k_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-90k_coco.py new file mode 100644 index 00000000..6b9b3eb0 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-90k_coco.py @@ -0,0 +1,23 @@ +_base_ = 'faster-rcnn_r50-caffe_fpn_ms-1x_coco.py' + +max_iter = 90000 + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[60000, 80000], + gamma=0.1) +] + +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=10000) +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=10000)) +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50-tnr-pre_fpn_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-tnr-pre_fpn_1x_coco.py new file mode 100644 index 00000000..7b3e5ded --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50-tnr-pre_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +checkpoint = 'https://download.pytorch.org/models/resnet50-11ad3fa6.pth' +model = dict( + backbone=dict(init_cfg=dict(type='Pretrained', checkpoint=checkpoint))) + +# `lr` and `weight_decay` have been searched to be optimal. +optim_wrapper = dict( + optimizer=dict(_delete_=True, type='AdamW', lr=0.0001, weight_decay=0.1), + paramwise_cfg=dict(norm_decay_mult=0., bypass_duplicate=True)) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..8a45417f --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_2x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_2x_coco.py new file mode 100644 index 00000000..2981c6fb --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_2x_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..3d366f3b --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,20 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../common/lsj-200e_coco-detection.py' +] +image_size = (1024, 1024) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +model = dict(data_preprocessor=dict(batch_augments=batch_augments)) + +train_dataloader = dict(batch_size=8, num_workers=4) +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + optimizer=dict( + type='SGD', lr=0.02 * 4, momentum=0.9, weight_decay=0.00004)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_amp-1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_amp-1x_coco.py new file mode 100644 index 00000000..f765deae --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_amp-1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' + +# MMEngine support the following two ways, users can choose +# according to convenience +# optim_wrapper = dict(type='AmpOptimWrapper') +_base_.optim_wrapper.type = 'AmpOptimWrapper' diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_bounded-iou_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_bounded-iou_1x_coco.py new file mode 100644 index 00000000..7758ca80 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_bounded-iou_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + bbox_head=dict( + reg_decoded_bbox=True, + loss_bbox=dict(type='BoundedIoULoss', loss_weight=10.0)))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_ciou_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_ciou_1x_coco.py new file mode 100644 index 00000000..e8d8a304 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_ciou_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + bbox_head=dict( + reg_decoded_bbox=True, + loss_bbox=dict(type='CIoULoss', loss_weight=12.0)))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py new file mode 100644 index 00000000..b5a34d9f --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py @@ -0,0 +1,48 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + # copied from configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py + neck=dict( + start_level=1, + add_extra_convs='on_output', # use P5 + relu_before_extra_convs=True), + rpn_head=dict( + _delete_=True, # ignore the unused old settings + type='FCOSHead', + # num_classes = 1 for rpn, + # if num_classes > 1, it will be set to 1 in + # TwoStageDetector automatically + num_classes=1, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + roi_head=dict( # update featmap_strides + bbox_roi_extractor=dict(featmap_strides=[8, 16, 32, 64, 128]))) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), # Slowly increase lr, otherwise loss becomes NAN + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_giou_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_giou_1x_coco.py new file mode 100644 index 00000000..82b71d77 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_giou_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + bbox_head=dict( + reg_decoded_bbox=True, + loss_bbox=dict(type='GIoULoss', loss_weight=10.0)))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_iou_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_iou_1x_coco.py new file mode 100644 index 00000000..e21c4364 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_iou_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + bbox_head=dict( + reg_decoded_bbox=True, + loss_bbox=dict(type='IoULoss', loss_weight=10.0)))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_ms-3x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_ms-3x_coco.py new file mode 100644 index 00000000..75dcfeb7 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_ms-3x_coco.py @@ -0,0 +1 @@ +_base_ = ['../common/ms_3x_coco.py', '../_base_/models/faster-rcnn_r50_fpn.py'] diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_ohem_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_ohem_1x_coco.py new file mode 100644 index 00000000..4f804b9b --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_ohem_1x_coco.py @@ -0,0 +1,2 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict(train_cfg=dict(rcnn=dict(sampler=dict(type='OHEMSampler')))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_soft-nms_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_soft-nms_1x_coco.py new file mode 100644 index 00000000..3775d8e4 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_soft-nms_1x_coco.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + test_cfg=dict( + rcnn=dict( + score_thr=0.05, + nms=dict(type='soft_nms', iou_threshold=0.5), + max_per_img=100))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..395c98cd --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_2x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_2x_coco.py new file mode 100644 index 00000000..6232d0ed --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_2x_coco.py @@ -0,0 +1,14 @@ +_base_ = './faster-rcnn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_ms-3x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_ms-3x_coco.py new file mode 100644 index 00000000..88cb40fd --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_ms-3x_coco.py @@ -0,0 +1,14 @@ +_base_ = ['../common/ms_3x_coco.py', '../_base_/models/faster-rcnn_r50_fpn.py'] +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x8d_fpn_ms-3x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x8d_fpn_ms-3x_coco.py new file mode 100644 index 00000000..28d6290b --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-32x8d_fpn_ms-3x_coco.py @@ -0,0 +1,23 @@ +_base_ = ['../common/ms_3x_coco.py', '../_base_/models/faster-rcnn_r50_fpn.py'] +model = dict( + # ResNeXt-101-32x8d model trained with Caffe2 at FB, + # so the mean and std need to be changed. + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[57.375, 57.120, 58.395], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=8, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + style='pytorch', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnext101_32x8d'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..f39d6322 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_2x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_2x_coco.py new file mode 100644 index 00000000..97a3c133 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_2x_coco.py @@ -0,0 +1,14 @@ +_base_ = './faster-rcnn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_ms-3x_coco.py b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_ms-3x_coco.py new file mode 100644 index 00000000..eeaa218c --- /dev/null +++ b/mmdetection/configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_ms-3x_coco.py @@ -0,0 +1,14 @@ +_base_ = ['../common/ms_3x_coco.py', '../_base_/models/faster-rcnn_r50_fpn.py'] +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/faster_rcnn/metafile.yml b/mmdetection/configs/faster_rcnn/metafile.yml new file mode 100644 index 00000000..6a201e17 --- /dev/null +++ b/mmdetection/configs/faster_rcnn/metafile.yml @@ -0,0 +1,451 @@ +Collections: + - Name: Faster R-CNN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - RPN + - ResNet + - RoIPool + Paper: + URL: https://arxiv.org/abs/1506.01497 + Title: "Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks" + README: configs/faster_rcnn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/faster_rcnn.py#L6 + Version: v2.0.0 + +Models: + - Name: faster-rcnn_r50-caffe-c4_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50-caffe_c4-1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 35.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_c4_1x_coco/faster_rcnn_r50_caffe_c4_1x_coco_20220316_150152-3f885b85.pth + + - Name: faster-rcnn_r50-caffe-c4_mstrain_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50-caffe-c4_ms-1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 35.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_c4_mstrain_1x_coco/faster_rcnn_r50_caffe_c4_mstrain_1x_coco_20220316_150527-db276fed.pth + + - Name: faster-rcnn_r50-caffe-dc5_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_dc5_1x_coco/faster_rcnn_r50_caffe_dc5_1x_coco_20201030_151909-531f0f43.pth + + - Name: faster-rcnn_r50-caffe_fpn_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.8 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_fpn_1x_coco/faster_rcnn_r50_caffe_fpn_1x_coco_bbox_mAP-0.378_20200504_180032-c5925ee5.pth + + - Name: faster-rcnn_r50_fpn_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.0 + inference time (ms/im): + - value: 46.73 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth + + - Name: faster-rcnn_r50_fpn_fp16_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50_fpn_amp-1x_coco.py + Metadata: + Training Memory (GB): 3.4 + Training Techniques: + - SGD with Momentum + - Weight Decay + - Mixed Precision Training + inference time (ms/im): + - value: 34.72 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP16 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fp16/faster_rcnn_r50_fpn_fp16_1x_coco/faster_rcnn_r50_fpn_fp16_1x_coco_20200204-d4dc1471.pth + + - Name: faster-rcnn_r50_fpn_2x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50_fpn_2x_coco.py + Metadata: + Training Memory (GB): 4.0 + inference time (ms/im): + - value: 46.73 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_2x_coco/faster_rcnn_r50_fpn_2x_coco_bbox_mAP-0.384_20200504_210434-a5d8aa15.pth + + - Name: faster-rcnn_r101-caffe_fpn_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r101-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.7 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_caffe_fpn_1x_coco/faster_rcnn_r101_caffe_fpn_1x_coco_bbox_mAP-0.398_20200504_180057-b269e9dd.pth + + - Name: faster-rcnn_r101_fpn_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.0 + inference time (ms/im): + - value: 64.1 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_fpn_1x_coco/faster_rcnn_r101_fpn_1x_coco_20200130-f513f705.pth + + - Name: faster-rcnn_r101_fpn_2x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r101_fpn_2x_coco.py + Metadata: + Training Memory (GB): 6.0 + inference time (ms/im): + - value: 64.1 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_fpn_2x_coco/faster_rcnn_r101_fpn_2x_coco_bbox_mAP-0.398_20200504_210455-1d2dac9c.pth + + - Name: faster-rcnn_x101-32x4d_fpn_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.2 + inference time (ms/im): + - value: 72.46 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_32x4d_fpn_1x_coco/faster_rcnn_x101_32x4d_fpn_1x_coco_20200203-cff10310.pth + + - Name: faster-rcnn_x101-32x4d_fpn_2x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_2x_coco.py + Metadata: + Training Memory (GB): 7.2 + inference time (ms/im): + - value: 72.46 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_32x4d_fpn_2x_coco/faster_rcnn_x101_32x4d_fpn_2x_coco_bbox_mAP-0.412_20200506_041400-64a12c0b.pth + + - Name: faster-rcnn_x101-64x4d_fpn_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 10.3 + inference time (ms/im): + - value: 106.38 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_64x4d_fpn_1x_coco/faster_rcnn_x101_64x4d_fpn_1x_coco_20200204-833ee192.pth + + - Name: faster-rcnn_x101-64x4d_fpn_2x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_2x_coco.py + Metadata: + Training Memory (GB): 10.3 + inference time (ms/im): + - value: 106.38 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_64x4d_fpn_2x_coco/faster_rcnn_x101_64x4d_fpn_2x_coco_20200512_161033-5961fa95.pth + + - Name: faster-rcnn_r50_fpn_iou_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50_fpn_iou_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_iou_1x_coco/faster_rcnn_r50_fpn_iou_1x_coco_20200506_095954-938e81f0.pth + + - Name: faster-rcnn_r50_fpn_giou_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50_fpn_giou_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_giou_1x_coco-0eada910.pth + + - Name: faster-rcnn_r50_fpn_bounded_iou_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50_fpn_bounded-iou_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_bounded_iou_1x_coco-98ad993b.pth + + - Name: faster-rcnn_r50-caffe-dc5_mstrain_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_dc5_mstrain_1x_coco/faster_rcnn_r50_caffe_dc5_mstrain_1x_coco_20201028_233851-b33d21b9.pth + + - Name: faster-rcnn_r50-caffe-dc5_mstrain_3x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50-caffe-dc5_ms-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_dc5_mstrain_3x_coco/faster_rcnn_r50_caffe_dc5_mstrain_3x_coco_20201028_002107-34a53b2c.pth + + - Name: faster-rcnn_r50-caffe_fpn_ms-2x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-2x_coco.py + Metadata: + Training Memory (GB): 4.3 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_fpn_mstrain_2x_coco/faster_rcnn_r50_caffe_fpn_mstrain_2x_coco_bbox_mAP-0.397_20200504_231813-10b2de58.pth + + - Name: faster-rcnn_r50-caffe_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 3.7 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_fpn_mstrain_3x_coco/faster_rcnn_r50_caffe_fpn_mstrain_3x_coco_20210526_095054-1f77628b.pth + + - Name: faster-rcnn_r50_fpn_mstrain_3x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 3.9 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_mstrain_3x_coco/faster_rcnn_r50_fpn_mstrain_3x_coco_20210524_110822-e10bd31c.pth + + - Name: faster-rcnn_r101-caffe_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r101-caffe_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 5.6 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_caffe_fpn_mstrain_3x_coco/faster_rcnn_r101_caffe_fpn_mstrain_3x_coco_20210526_095742-a7ae426d.pth + + - Name: faster-rcnn_r101_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r101_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 5.8 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_fpn_mstrain_3x_coco/faster_rcnn_r101_fpn_mstrain_3x_coco_20210524_110822-4d4d2ca8.pth + + - Name: faster-rcnn_x101-32x4d_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_x101-32x4d_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 7.0 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_32x4d_fpn_mstrain_3x_coco/faster_rcnn_x101_32x4d_fpn_mstrain_3x_coco_20210524_124151-16b9b260.pth + + - Name: faster-rcnn_x101-32x8d_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_x101-32x8d_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 10.1 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_32x8d_fpn_mstrain_3x_coco/faster_rcnn_x101_32x8d_fpn_mstrain_3x_coco_20210604_182954-002e082a.pth + + - Name: faster-rcnn_x101-64x4d_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_x101-64x4d_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 10.0 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_x101_64x4d_fpn_mstrain_3x_coco/faster_rcnn_x101_64x4d_fpn_mstrain_3x_coco_20210524_124528-26c63de6.pth + + - Name: faster-rcnn_r50_fpn_tnr-pretrain_1x_coco + In Collection: Faster R-CNN + Config: configs/faster_rcnn/faster-rcnn_r50-tnr-pre_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.0 + inference time (ms/im): + - value: 46.73 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_tnr-pretrain_1x_coco/faster_rcnn_r50_fpn_tnr-pretrain_1x_coco_20220320_085147-efedfda4.pth diff --git a/mmdetection/configs/fcos/README.md b/mmdetection/configs/fcos/README.md new file mode 100644 index 00000000..8d72237a --- /dev/null +++ b/mmdetection/configs/fcos/README.md @@ -0,0 +1,45 @@ +# FCOS + +> [FCOS: Fully Convolutional One-Stage Object Detection](https://arxiv.org/abs/1904.01355) + + + +## Abstract + +We propose a fully convolutional one-stage object detector (FCOS) to solve object detection in a per-pixel prediction fashion, analogue to semantic segmentation. Almost all state-of-the-art object detectors such as RetinaNet, SSD, YOLOv3, and Faster R-CNN rely on pre-defined anchor boxes. In contrast, our proposed detector FCOS is anchor box free, as well as proposal free. By eliminating the predefined set of anchor boxes, FCOS completely avoids the complicated computation related to anchor boxes such as calculating overlapping during training. More importantly, we also avoid all hyper-parameters related to anchor boxes, which are often very sensitive to the final detection performance. With the only post-processing non-maximum suppression (NMS), FCOS with ResNeXt-64x4d-101 achieves 44.7% in AP with single-model and single-scale testing, surpassing previous one-stage detectors with the advantage of being much simpler. For the first time, we demonstrate a much simpler and flexible detection framework achieving improved detection accuracy. We hope that the proposed FCOS framework can serve as a simple and strong alternative for many other instance-level tasks. + +
    + +
    + +## Results and Models + +| Backbone | Style | GN | MS train | Tricks | DCN | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :---: | :-: | :------: | :----: | :-: | :-----: | :------: | :------------: | :----: | :------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | caffe | Y | N | N | N | 1x | 3.6 | 22.7 | 36.6 | [config](./fcos_r50-caffe_fpn_gn-head_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_1x_coco/fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_1x_coco/20201227_180009.log.json) | +| R-50 | caffe | Y | N | Y | N | 1x | 3.7 | - | 38.7 | [config](./fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_1x_coco/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_1x_coco-0a0d75a8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_1x_coco/20210105_135818.log.json) | +| R-50 | caffe | Y | N | Y | Y | 1x | 3.8 | - | 42.3 | [config](./fcos_r50-dcn-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_dcn_1x_coco/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_dcn_1x_coco-ae4d8b3d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_dcn_1x_coco/20210105_224556.log.json) | +| R-101 | caffe | Y | N | N | N | 1x | 5.5 | 17.3 | 39.1 | [config](./fcos_r101-caffe_fpn_gn-head-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r101_caffe_fpn_gn-head_1x_coco/fcos_r101_caffe_fpn_gn-head_1x_coco-0e37b982.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r101_caffe_fpn_gn-head_1x_coco/20210103_155046.log.json) | + +| Backbone | Style | GN | MS train | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :-----: | :-: | :------: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | caffe | Y | Y | 2x | 2.6 | 22.9 | 38.5 | [config](./fcos_r50-caffe_fpn_gn-head_ms-640-800-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_mstrain_640-800_2x_coco/fcos_r50_caffe_fpn_gn-head_mstrain_640-800_2x_coco-d92ceeea.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_mstrain_640-800_2x_coco/20201227_161900.log.json) | +| R-101 | caffe | Y | Y | 2x | 5.5 | 17.3 | 40.8 | [config](./fcos_r101-caffe_fpn_gn-head_ms-640-800-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r101_caffe_fpn_gn-head_mstrain_640-800_2x_coco/fcos_r101_caffe_fpn_gn-head_mstrain_640-800_2x_coco-511424d6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r101_caffe_fpn_gn-head_mstrain_640-800_2x_coco/20210103_155046.log.json) | +| X-101 | pytorch | Y | Y | 2x | 10.0 | 9.7 | 42.6 | [config](./fcos_x101-64x4d_fpn_gn-head_ms-640-800-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_x101_64x4d_fpn_gn-head_mstrain_640-800_2x_coco/fcos_x101_64x4d_fpn_gn-head_mstrain_640-800_2x_coco-ede514a8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_x101_64x4d_fpn_gn-head_mstrain_640-800_2x_coco/20210114_133041.log.json) | + +**Notes:** + +- The X-101 backbone is X-101-64x4d. +- Tricks means setting `norm_on_bbox`, `centerness_on_reg`, `center_sampling` as `True`. +- DCN means using `DCNv2` in both backbone and head. + +## Citation + +```latex +@article{tian2019fcos, + title={FCOS: Fully Convolutional One-Stage Object Detection}, + author={Tian, Zhi and Shen, Chunhua and Chen, Hao and He, Tong}, + journal={arXiv preprint arXiv:1904.01355}, + year={2019} +} +``` diff --git a/mmdetection/configs/fcos/fcos_r101-caffe_fpn_gn-head-1x_coco.py b/mmdetection/configs/fcos/fcos_r101-caffe_fpn_gn-head-1x_coco.py new file mode 100644 index 00000000..5380e874 --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r101-caffe_fpn_gn-head-1x_coco.py @@ -0,0 +1,9 @@ +_base_ = './fcos_r50-caffe_fpn_gn-head_1x_coco.py' + +# model settings +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron/resnet101_caffe'))) diff --git a/mmdetection/configs/fcos/fcos_r101-caffe_fpn_gn-head_ms-640-800-2x_coco.py b/mmdetection/configs/fcos/fcos_r101-caffe_fpn_gn-head_ms-640-800-2x_coco.py new file mode 100644 index 00000000..286a07a2 --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r101-caffe_fpn_gn-head_ms-640-800-2x_coco.py @@ -0,0 +1,38 @@ +_base_ = './fcos_r50-caffe_fpn_gn-head_1x_coco.py' + +# model settings +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron/resnet101_caffe'))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# training schedule for 2x +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict(type='ConstantLR', factor=1.0 / 3, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/fcos/fcos_r101_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/fcos/fcos_r101_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..77250e69 --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r101_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './fcos_r50_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py' # noqa + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/fcos/fcos_r18_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/fcos/fcos_r18_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..6f001024 --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r18_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './fcos_r50_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py' # noqa + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py new file mode 100644 index 00000000..2a77641d --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py @@ -0,0 +1,43 @@ +_base_ = 'fcos_r50-caffe_fpn_gn-head_1x_coco.py' + +# model setting +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + bbox_head=dict( + norm_on_bbox=True, + centerness_on_reg=True, + dcn_on_last_conv=False, + center_sampling=True, + conv_bias=True, + loss_bbox=dict(type='GIoULoss', loss_weight=1.0)), + # training and testing settings + test_cfg=dict(nms=dict(type='nms', iou_threshold=0.6))) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 3.0, + by_epoch=False, + begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict(clip_grad=None) diff --git a/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head-center_1x_coco.py b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head-center_1x_coco.py new file mode 100644 index 00000000..9e4eb1d5 --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head-center_1x_coco.py @@ -0,0 +1,4 @@ +_base_ = './fcos_r50-caffe_fpn_gn-head_1x_coco.py' + +# model settings +model = dict(bbox_head=dict(center_sampling=True, center_sample_radius=1.5)) diff --git a/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py new file mode 100644 index 00000000..928a9b4c --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py @@ -0,0 +1,75 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# model settings +model = dict( + type='FCOS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[102.9801, 115.9465, 122.7717], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron/resnet50_caffe')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', # use P5 + num_outs=5, + relu_before_extra_convs=True), + bbox_head=dict( + type='FCOSHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + # testing settings + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100)) + +# learning rate +param_scheduler = [ + dict(type='ConstantLR', factor=1.0 / 3, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + optimizer=dict(lr=0.01), + paramwise_cfg=dict(bias_lr_mult=2., bias_decay_mult=0.), + clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head_4xb4-1x_coco.py b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head_4xb4-1x_coco.py new file mode 100644 index 00000000..32358cd3 --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head_4xb4-1x_coco.py @@ -0,0 +1,5 @@ +# TODO: Remove this config after benchmarking all related configs +_base_ = 'fcos_r50-caffe_fpn_gn-head_1x_coco.py' + +# dataset settings +train_dataloader = dict(batch_size=4, num_workers=4) diff --git a/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head_ms-640-800-2x_coco.py b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head_ms-640-800-2x_coco.py new file mode 100644 index 00000000..4d50b4ec --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r50-caffe_fpn_gn-head_ms-640-800-2x_coco.py @@ -0,0 +1,30 @@ +_base_ = './fcos_r50-caffe_fpn_gn-head_1x_coco.py' + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# training schedule for 2x +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict(type='ConstantLR', factor=1.0 / 3, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/fcos/fcos_r50-dcn-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py b/mmdetection/configs/fcos/fcos_r50-dcn-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py new file mode 100644 index 00000000..a6a6c44f --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r50-dcn-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py @@ -0,0 +1,45 @@ +_base_ = 'fcos_r50-caffe_fpn_gn-head_1x_coco.py' + +# model settings +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + bbox_head=dict( + norm_on_bbox=True, + centerness_on_reg=True, + dcn_on_last_conv=True, + center_sampling=True, + conv_bias=True, + loss_bbox=dict(type='GIoULoss', loss_weight=1.0)), + # training and testing settings + test_cfg=dict(nms=dict(type='nms', iou_threshold=0.6))) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 3.0, + by_epoch=False, + begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict(clip_grad=None) diff --git a/mmdetection/configs/fcos/fcos_r50_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/fcos/fcos_r50_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..b51556b8 --- /dev/null +++ b/mmdetection/configs/fcos/fcos_r50_fpn_gn-head-center-normbbox-centeronreg-giou_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,75 @@ +_base_ = '../common/lsj-200e_coco-detection.py' + +image_size = (1024, 1024) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +# model settings +model = dict( + type='FCOS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', # use P5 + num_outs=5, + relu_before_extra_convs=True), + bbox_head=dict( + type='FCOSHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + norm_on_bbox=True, + centerness_on_reg=True, + dcn_on_last_conv=False, + center_sampling=True, + conv_bias=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + # testing settings + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +train_dataloader = dict(batch_size=8, num_workers=4) +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + optimizer=dict( + type='SGD', lr=0.01 * 4, momentum=0.9, weight_decay=0.00004), + paramwise_cfg=dict(bias_lr_mult=2., bias_decay_mult=0.), + clip_grad=dict(max_norm=35, norm_type=2)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/fcos/fcos_x101-64x4d_fpn_gn-head_ms-640-800-2x_coco.py b/mmdetection/configs/fcos/fcos_x101-64x4d_fpn_gn-head_ms-640-800-2x_coco.py new file mode 100644 index 00000000..503c0e1c --- /dev/null +++ b/mmdetection/configs/fcos/fcos_x101-64x4d_fpn_gn-head_ms-640-800-2x_coco.py @@ -0,0 +1,52 @@ +_base_ = './fcos_r50-caffe_fpn_gn-head_1x_coco.py' + +# model settings +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# training schedule for 2x +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict(type='ConstantLR', factor=1.0 / 3, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/fcos/metafile.yml b/mmdetection/configs/fcos/metafile.yml new file mode 100644 index 00000000..fb6527cf --- /dev/null +++ b/mmdetection/configs/fcos/metafile.yml @@ -0,0 +1,146 @@ +Collections: + - Name: FCOS + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - Group Normalization + - ResNet + Paper: + URL: https://arxiv.org/abs/1904.01355 + Title: 'FCOS: Fully Convolutional One-Stage Object Detection' + README: configs/fcos/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/fcos.py#L6 + Version: v2.0.0 + +Models: + - Name: fcos_r50-caffe_fpn_gn-head_1x_coco + In Collection: FCOS + Config: configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py + Metadata: + Training Memory (GB): 3.6 + inference time (ms/im): + - value: 44.05 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_1x_coco/fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth + + - Name: fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco + In Collection: FCOS + Config: configs/fcos/fcos_r50-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py + Metadata: + Training Memory (GB): 3.7 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_1x_coco/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_1x_coco-0a0d75a8.pth + + - Name: fcos_r50-dcn-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco + In Collection: FCOS + Config: configs/fcos/fcos_r50-dcn-caffe_fpn_gn-head-center-normbbox-centeronreg-giou_1x_coco.py + Metadata: + Training Memory (GB): 3.8 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_dcn_1x_coco/fcos_center-normbbox-centeronreg-giou_r50_caffe_fpn_gn-head_dcn_1x_coco-ae4d8b3d.pth + + - Name: fcos_r101-caffe_fpn_gn-head-1x_coco + In Collection: FCOS + Config: configs/fcos/fcos_r101-caffe_fpn_gn-head-1x_coco.py + Metadata: + Training Memory (GB): 5.5 + inference time (ms/im): + - value: 57.8 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r101_caffe_fpn_gn-head_1x_coco/fcos_r101_caffe_fpn_gn-head_1x_coco-0e37b982.pth + + - Name: fcos_r50-caffe_fpn_gn-head_ms-640-800-2x_coco + In Collection: FCOS + Config: configs/fcos/fcos_r50-caffe_fpn_gn-head_ms-640-800-2x_coco.py + Metadata: + Training Memory (GB): 2.6 + inference time (ms/im): + - value: 43.67 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_mstrain_640-800_2x_coco/fcos_r50_caffe_fpn_gn-head_mstrain_640-800_2x_coco-d92ceeea.pth + + - Name: fcos_r101-caffe_fpn_gn-head_ms-640-800-2x_coco + In Collection: FCOS + Config: configs/fcos/fcos_r101-caffe_fpn_gn-head_ms-640-800-2x_coco.py + Metadata: + Training Memory (GB): 5.5 + inference time (ms/im): + - value: 57.8 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r101_caffe_fpn_gn-head_mstrain_640-800_2x_coco/fcos_r101_caffe_fpn_gn-head_mstrain_640-800_2x_coco-511424d6.pth + + - Name: fcos_x101-64x4d_fpn_gn-head_ms-640-800-2x_coco + In Collection: FCOS + Config: configs/fcos/fcos_x101-64x4d_fpn_gn-head_ms-640-800-2x_coco.py + Metadata: + Training Memory (GB): 10.0 + inference time (ms/im): + - value: 103.09 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_x101_64x4d_fpn_gn-head_mstrain_640-800_2x_coco/fcos_x101_64x4d_fpn_gn-head_mstrain_640-800_2x_coco-ede514a8.pth diff --git a/mmdetection/configs/foveabox/README.md b/mmdetection/configs/foveabox/README.md new file mode 100644 index 00000000..96f1358b --- /dev/null +++ b/mmdetection/configs/foveabox/README.md @@ -0,0 +1,53 @@ +# FoveaBox + +> [FoveaBox: Beyond Anchor-based Object Detector](https://arxiv.org/abs/1904.03797) + + + +## Abstract + +We present FoveaBox, an accurate, flexible, and completely anchor-free framework for object detection. While almost all state-of-the-art object detectors utilize predefined anchors to enumerate possible locations, scales and aspect ratios for the search of the objects, their performance and generalization ability are also limited to the design of anchors. Instead, FoveaBox directly learns the object existing possibility and the bounding box coordinates without anchor reference. This is achieved by: (a) predicting category-sensitive semantic maps for the object existing possibility, and (b) producing category-agnostic bounding box for each position that potentially contains an object. The scales of target boxes are naturally associated with feature pyramid representations. In FoveaBox, an instance is assigned to adjacent feature levels to make the model more accurate.We demonstrate its effectiveness on standard benchmarks and report extensive experimental analysis. Without bells and whistles, FoveaBox achieves state-of-the-art single model performance on the standard COCO and Pascal VOC object detection benchmark. More importantly, FoveaBox avoids all computation and hyper-parameters related to anchor boxes, which are often sensitive to the final detection performance. We believe the simple and effective approach will serve as a solid baseline and help ease future research for object detection. + +
    + +
    + +## Introduction + +FoveaBox is an accurate, flexible and completely anchor-free object detection system for object detection framework, as presented in our paper [https://arxiv.org/abs/1904.03797](https://arxiv.org/abs/1904.03797): +Different from previous anchor-based methods, FoveaBox directly learns the object existing possibility and the bounding box coordinates without anchor reference. This is achieved by: (a) predicting category-sensitive semantic maps for the object existing possibility, and (b) producing category-agnostic bounding box for each position that potentially contains an object. + +## Results and Models + +### Results on R50/101-FPN + +| Backbone | Style | align | ms-train | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :-----: | :---: | :------: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | N | N | 1x | 5.6 | 24.1 | 36.5 | [config](./fovea_r50_fpn_4xb4-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r50_fpn_4x4_1x_coco/fovea_r50_fpn_4x4_1x_coco_20200219-ee4d5303.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r50_fpn_4x4_1x_coco/fovea_r50_fpn_4x4_1x_coco_20200219_223025.log.json) | +| R-50 | pytorch | N | N | 2x | 5.6 | - | 37.2 | [config](./fovea_r50_fpn_4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r50_fpn_4x4_2x_coco/fovea_r50_fpn_4x4_2x_coco_20200203-2df792b1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r50_fpn_4x4_2x_coco/fovea_r50_fpn_4x4_2x_coco_20200203_112043.log.json) | +| R-50 | pytorch | Y | N | 2x | 8.1 | 19.4 | 37.9 | [config](./fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r50_fpn_gn-head_4x4_2x_coco/fovea_align_r50_fpn_gn-head_4x4_2x_coco_20200203-8987880d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r50_fpn_gn-head_4x4_2x_coco/fovea_align_r50_fpn_gn-head_4x4_2x_coco_20200203_134252.log.json) | +| R-50 | pytorch | Y | Y | 2x | 8.1 | 18.3 | 40.4 | [config](./fovea_r50_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r50_fpn_gn-head_mstrain_640-800_4x4_2x_coco/fovea_align_r50_fpn_gn-head_mstrain_640-800_4x4_2x_coco_20200205-85ce26cb.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r50_fpn_gn-head_mstrain_640-800_4x4_2x_coco/fovea_align_r50_fpn_gn-head_mstrain_640-800_4x4_2x_coco_20200205_112557.log.json) | +| R-101 | pytorch | N | N | 1x | 9.2 | 17.4 | 38.6 | [config](./fovea_r101_fpn_4xb4-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r101_fpn_4x4_1x_coco/fovea_r101_fpn_4x4_1x_coco_20200219-05e38f1c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r101_fpn_4x4_1x_coco/fovea_r101_fpn_4x4_1x_coco_20200219_011740.log.json) | +| R-101 | pytorch | N | N | 2x | 11.7 | - | 40.0 | [config](./fovea_r101_fpn_4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r101_fpn_4x4_2x_coco/fovea_r101_fpn_4x4_2x_coco_20200208-02320ea4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r101_fpn_4x4_2x_coco/fovea_r101_fpn_4x4_2x_coco_20200208_202059.log.json) | +| R-101 | pytorch | Y | N | 2x | 11.7 | 14.7 | 40.0 | [config](./fovea_r101_fpn_gn-head-align_4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r101_fpn_gn-head_4x4_2x_coco/fovea_align_r101_fpn_gn-head_4x4_2x_coco_20200208-c39a027a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r101_fpn_gn-head_4x4_2x_coco/fovea_align_r101_fpn_gn-head_4x4_2x_coco_20200208_203337.log.json) | +| R-101 | pytorch | Y | Y | 2x | 11.7 | 14.7 | 42.0 | [config](./fovea_r101_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r101_fpn_gn-head_mstrain_640-800_4x4_2x_coco/fovea_align_r101_fpn_gn-head_mstrain_640-800_4x4_2x_coco_20200208-649c5eb6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r101_fpn_gn-head_mstrain_640-800_4x4_2x_coco/fovea_align_r101_fpn_gn-head_mstrain_640-800_4x4_2x_coco_20200208_202124.log.json) | + +\[1\] *1x and 2x mean the model is trained for 12 and 24 epochs, respectively.* \ +\[2\] *Align means utilizing deformable convolution to align the cls branch.* \ +\[3\] *All results are obtained with a single model and without any test time data augmentation.*\ +\[4\] *We use 4 GPUs for training.* + +Any pull requests or issues are welcome. + +## Citation + +Please consider citing our paper in your publications if the project helps your research. BibTeX reference is as follows. + +```latex +@article{kong2019foveabox, + title={FoveaBox: Beyond Anchor-based Object Detector}, + author={Kong, Tao and Sun, Fuchun and Liu, Huaping and Jiang, Yuning and Shi, Jianbo}, + journal={arXiv preprint arXiv:1904.03797}, + year={2019} +} +``` diff --git a/mmdetection/configs/foveabox/fovea_r101_fpn_4xb4-1x_coco.py b/mmdetection/configs/foveabox/fovea_r101_fpn_4xb4-1x_coco.py new file mode 100644 index 00000000..7e8ccf91 --- /dev/null +++ b/mmdetection/configs/foveabox/fovea_r101_fpn_4xb4-1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './fovea_r50_fpn_4xb4-1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/foveabox/fovea_r101_fpn_4xb4-2x_coco.py b/mmdetection/configs/foveabox/fovea_r101_fpn_4xb4-2x_coco.py new file mode 100644 index 00000000..0dc98515 --- /dev/null +++ b/mmdetection/configs/foveabox/fovea_r101_fpn_4xb4-2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './fovea_r50_fpn_4xb4-2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/foveabox/fovea_r101_fpn_gn-head-align_4xb4-2x_coco.py b/mmdetection/configs/foveabox/fovea_r101_fpn_gn-head-align_4xb4-2x_coco.py new file mode 100644 index 00000000..222671d4 --- /dev/null +++ b/mmdetection/configs/foveabox/fovea_r101_fpn_gn-head-align_4xb4-2x_coco.py @@ -0,0 +1,23 @@ +_base_ = './fovea_r50_fpn_4xb4-1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + bbox_head=dict( + with_deform=True, + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True))) +# learning policy +max_epochs = 24 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/foveabox/fovea_r101_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py b/mmdetection/configs/foveabox/fovea_r101_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py new file mode 100644 index 00000000..e1852d58 --- /dev/null +++ b/mmdetection/configs/foveabox/fovea_r101_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py @@ -0,0 +1,34 @@ +_base_ = './fovea_r50_fpn_4xb4-1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + bbox_head=dict( + with_deform=True, + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True))) +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +# learning policy +max_epochs = 24 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/foveabox/fovea_r50_fpn_4xb4-1x_coco.py b/mmdetection/configs/foveabox/fovea_r50_fpn_4xb4-1x_coco.py new file mode 100644 index 00000000..13cf3ae9 --- /dev/null +++ b/mmdetection/configs/foveabox/fovea_r50_fpn_4xb4-1x_coco.py @@ -0,0 +1,59 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + type='FOVEA', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + num_outs=5, + add_extra_convs='on_input'), + bbox_head=dict( + type='FoveaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + base_edge_list=[16, 32, 64, 128, 256], + scale_ranges=((1, 64), (32, 128), (64, 256), (128, 512), (256, 2048)), + sigma=0.4, + with_deform=False, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=1.50, + alpha=0.4, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=0.11, loss_weight=1.0)), + # training and testing settings + train_cfg=dict(), + test_cfg=dict( + nms_pre=1000, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100)) +train_dataloader = dict(batch_size=4, num_workers=4) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/foveabox/fovea_r50_fpn_4xb4-2x_coco.py b/mmdetection/configs/foveabox/fovea_r50_fpn_4xb4-2x_coco.py new file mode 100644 index 00000000..f9d06ef9 --- /dev/null +++ b/mmdetection/configs/foveabox/fovea_r50_fpn_4xb4-2x_coco.py @@ -0,0 +1,15 @@ +_base_ = './fovea_r50_fpn_4xb4-1x_coco.py' +# learning policy +max_epochs = 24 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py b/mmdetection/configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py new file mode 100644 index 00000000..877bb4fa --- /dev/null +++ b/mmdetection/configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py @@ -0,0 +1,20 @@ +_base_ = './fovea_r50_fpn_4xb4-1x_coco.py' +model = dict( + bbox_head=dict( + with_deform=True, + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True))) +# learning policy +max_epochs = 24 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/foveabox/fovea_r50_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py b/mmdetection/configs/foveabox/fovea_r50_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py new file mode 100644 index 00000000..5690bcae --- /dev/null +++ b/mmdetection/configs/foveabox/fovea_r50_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py @@ -0,0 +1,30 @@ +_base_ = './fovea_r50_fpn_4xb4-1x_coco.py' +model = dict( + bbox_head=dict( + with_deform=True, + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True))) +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +# learning policy +max_epochs = 24 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/foveabox/metafile.yml b/mmdetection/configs/foveabox/metafile.yml new file mode 100644 index 00000000..9ab2f542 --- /dev/null +++ b/mmdetection/configs/foveabox/metafile.yml @@ -0,0 +1,172 @@ +Collections: + - Name: FoveaBox + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 4x V100 GPUs + Architecture: + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1904.03797 + Title: 'FoveaBox: Beyond Anchor-based Object Detector' + README: configs/foveabox/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/fovea.py#L6 + Version: v2.0.0 + +Models: + - Name: fovea_r50_fpn_4xb4-1x_coco + In Collection: FoveaBox + Config: configs/foveabox/fovea_r50_fpn_4xb4-1x_coco.py + Metadata: + Training Memory (GB): 5.6 + inference time (ms/im): + - value: 41.49 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r50_fpn_4x4_1x_coco/fovea_r50_fpn_4x4_1x_coco_20200219-ee4d5303.pth + + - Name: fovea_r50_fpn_4xb4-2x_coco + In Collection: FoveaBox + Config: configs/foveabox/fovea_r50_fpn_4xb4-2x_coco.py + Metadata: + Training Memory (GB): 5.6 + inference time (ms/im): + - value: 41.49 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r50_fpn_4x4_2x_coco/fovea_r50_fpn_4x4_2x_coco_20200203-2df792b1.pth + + - Name: fovea_r50_fpn_gn-head-align_4xb4-2x_coco + In Collection: FoveaBox + Config: configs/foveabox/fovea_r50_fpn_gn-head-align_4xb4-2x_coco.py + Metadata: + Training Memory (GB): 8.1 + inference time (ms/im): + - value: 51.55 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r50_fpn_gn-head_4x4_2x_coco/fovea_align_r50_fpn_gn-head_4x4_2x_coco_20200203-8987880d.pth + + - Name: fovea_r50_fpn_gn-head-align_ms-640-800-4xb4-2x_coco + In Collection: FoveaBox + Config: configs/foveabox/fovea_r50_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py + Metadata: + Training Memory (GB): 8.1 + inference time (ms/im): + - value: 54.64 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r50_fpn_gn-head_mstrain_640-800_4x4_2x_coco/fovea_align_r50_fpn_gn-head_mstrain_640-800_4x4_2x_coco_20200205-85ce26cb.pth + + - Name: fovea_r101_fpn_4xb4-1x_coco + In Collection: FoveaBox + Config: configs/foveabox/fovea_r101_fpn_4xb4-1x_coco.py + Metadata: + Training Memory (GB): 9.2 + inference time (ms/im): + - value: 57.47 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r101_fpn_4x4_1x_coco/fovea_r101_fpn_4x4_1x_coco_20200219-05e38f1c.pth + + - Name: fovea_r101_fpn_4xb4-2x_coco + In Collection: FoveaBox + Config: configs/foveabox/fovea_r101_fpn_4xb4-2x_coco.py + Metadata: + Training Memory (GB): 11.7 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_r101_fpn_4x4_2x_coco/fovea_r101_fpn_4x4_2x_coco_20200208-02320ea4.pth + + - Name: fovea_r101_fpn_gn-head-align_4xb4-2x_coco + In Collection: FoveaBox + Config: configs/foveabox/fovea_r101_fpn_gn-head-align_4xb4-2x_coco.py + Metadata: + Training Memory (GB): 11.7 + inference time (ms/im): + - value: 68.03 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r101_fpn_gn-head_4x4_2x_coco/fovea_align_r101_fpn_gn-head_4x4_2x_coco_20200208-c39a027a.pth + + - Name: fovea_r101_fpn_gn-head-align_ms-640-800-4xb4-2x_coco + In Collection: FoveaBox + Config: configs/foveabox/fovea_r101_fpn_gn-head-align_ms-640-800-4xb4-2x_coco.py + Metadata: + Training Memory (GB): 11.7 + inference time (ms/im): + - value: 68.03 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/foveabox/fovea_align_r101_fpn_gn-head_mstrain_640-800_4x4_2x_coco/fovea_align_r101_fpn_gn-head_mstrain_640-800_4x4_2x_coco_20200208-649c5eb6.pth diff --git a/mmdetection/configs/fpg/README.md b/mmdetection/configs/fpg/README.md new file mode 100644 index 00000000..1e2fd400 --- /dev/null +++ b/mmdetection/configs/fpg/README.md @@ -0,0 +1,43 @@ +# FPG + +> [Feature Pyramid Grids](https://arxiv.org/abs/2004.03580) + + + +## Abstract + +Feature pyramid networks have been widely adopted in the object detection literature to improve feature representations for better handling of variations in scale. In this paper, we present Feature Pyramid Grids (FPG), a deep multi-pathway feature pyramid, that represents the feature scale-space as a regular grid of parallel bottom-up pathways which are fused by multi-directional lateral connections. FPG can improve single-pathway feature pyramid networks by significantly increasing its performance at similar computation cost, highlighting importance of deep pyramid representations. In addition to its general and uniform structure, over complicated structures that have been found with neural architecture search, it also compares favorably against such approaches without relying on search. We hope that FPG with its uniform and effective nature can serve as a strong component for future work in object recognition. + +
    + +
    + +## Results and Models + +We benchmark the new training schedule (crop training, large batch, unfrozen BN, 50 epochs) introduced in NAS-FPN. +All backbones are Resnet-50 in pytorch style. + +| Method | Neck | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :----------: | :--------: | :-----: | :------: | :------------: | :----: | :-----: | :--------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster R-CNN | FPG | 50e | 20.0 | - | 42.3 | - | [config](./faster-rcnn_r50_fpg_crop640-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fpg/faster_rcnn_r50_fpg_crop640_50e_coco/faster_rcnn_r50_fpg_crop640_50e_coco_20220311_011856-74109f42.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fpg/faster_rcnn_r50_fpg_crop640_50e_coco/faster_rcnn_r50_fpg_crop640_50e_coco_20220311_011856.log.json) | +| Faster R-CNN | FPG-chn128 | 50e | 11.9 | - | 41.2 | - | [config](./faster-rcnn_r50_fpg-chn128_crop640-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fpg/faster_rcnn_r50_fpg-chn128_crop640_50e_coco/faster_rcnn_r50_fpg-chn128_crop640_50e_coco_20220311_011857-9376aa9d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fpg/faster_rcnn_r50_fpg-chn128_crop640_50e_coco/faster_rcnn_r50_fpg-chn128_crop640_50e_coco_20220311_011857.log.json) | +| Faster R-CNN | FPN | 50e | 20.0 | - | 38.9 | - | [config](./faster-rcnn_r50_fpn_crop640-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fpg/faster_rcnn_r50_fpn_crop640_50e_coco/faster_rcnn_r50_fpn_crop640_50e_coco_20220311_011857-be7c9f42.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fpg/faster_rcnn_r50_fpn_crop640_50e_coco/faster_rcnn_r50_fpn_crop640_50e_coco_20220311_011857.log.json) | +| Mask R-CNN | FPG | 50e | 23.2 | - | 43.0 | 38.1 | [config](./mask-rcnn_r50_fpg_crop640-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fpg/mask_rcnn_r50_fpg_crop640_50e_coco/mask_rcnn_r50_fpg_crop640_50e_coco_20220311_011857-233b8334.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fpg/mask_rcnn_r50_fpg_crop640_50e_coco/mask_rcnn_r50_fpg_crop640_50e_coco_20220311_011857.log.json) | +| Mask R-CNN | FPG-chn128 | 50e | 15.3 | - | 41.7 | 37.1 | [config](./mask-rcnn_r50_fpg-chn128_crop640-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fpg/mask_rcnn_r50_fpg-chn128_crop640_50e_coco/mask_rcnn_r50_fpg-chn128_crop640_50e_coco_20220311_011859-043c9b4e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fpg/mask_rcnn_r50_fpg-chn128_crop640_50e_coco/mask_rcnn_r50_fpg-chn128_crop640_50e_coco_20220311_011859.log.json) | +| Mask R-CNN | FPN | 50e | 23.2 | - | 49.6 | 35.6 | [config](./mask-rcnn_r50_fpn_crop640-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fpg/mask_rcnn_r50_fpn_crop640_50e_coco/mask_rcnn_r50_fpn_crop640_50e_coco_20220311_011855-a756664a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fpg/mask_rcnn_r50_fpn_crop640_50e_coco/mask_rcnn_r50_fpn_crop640_50e_coco_20220311_011855.log.json) | +| RetinaNet | FPG | 50e | 20.8 | - | 40.5 | - | [config](./retinanet_r50_fpg_crop640_50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fpg/retinanet_r50_fpg_crop640_50e_coco/retinanet_r50_fpg_crop640_50e_coco_20220311_110809-b0bcf5f4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fpg/retinanet_r50_fpg_crop640_50e_coco/retinanet_r50_fpg_crop640_50e_coco_20220311_110809.log.json) | +| RetinaNet | FPG-chn128 | 50e | 19.9 | - | 39.9 | - | [config](./retinanet_r50_fpg-chn128_crop640_50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fpg/retinanet_r50_fpg-chn128_crop640_50e_coco/retinanet_r50_fpg-chn128_crop640_50e_coco_20220313_104829-ee99a686.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fpg/retinanet_r50_fpg-chn128_crop640_50e_coco/retinanet_r50_fpg-chn128_crop640_50e_coco_20220313_104829.log.json) | + +**Note**: Chn128 means to decrease the number of channels of features and convs from 256 (default) to 128 in +Neck and BBox Head, which can greatly decrease memory consumption without sacrificing much precision. + +## Citation + +```latex +@article{chen2020feature, + title={Feature pyramid grids}, + author={Chen, Kai and Cao, Yuhang and Loy, Chen Change and Lin, Dahua and Feichtenhofer, Christoph}, + journal={arXiv preprint arXiv:2004.03580}, + year={2020} +} +``` diff --git a/mmdetection/configs/fpg/faster-rcnn_r50_fpg-chn128_crop640-50e_coco.py b/mmdetection/configs/fpg/faster-rcnn_r50_fpg-chn128_crop640-50e_coco.py new file mode 100644 index 00000000..cb9160f5 --- /dev/null +++ b/mmdetection/configs/fpg/faster-rcnn_r50_fpg-chn128_crop640-50e_coco.py @@ -0,0 +1,9 @@ +_base_ = 'faster-rcnn_r50_fpg_crop640-50e_coco.py' + +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + neck=dict(out_channels=128, inter_channels=128), + rpn_head=dict(in_channels=128), + roi_head=dict( + bbox_roi_extractor=dict(out_channels=128), + bbox_head=dict(in_channels=128))) diff --git a/mmdetection/configs/fpg/faster-rcnn_r50_fpg_crop640-50e_coco.py b/mmdetection/configs/fpg/faster-rcnn_r50_fpg_crop640-50e_coco.py new file mode 100644 index 00000000..d0d366f1 --- /dev/null +++ b/mmdetection/configs/fpg/faster-rcnn_r50_fpg_crop640-50e_coco.py @@ -0,0 +1,48 @@ +_base_ = 'faster-rcnn_r50_fpn_crop640-50e_coco.py' + +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + neck=dict( + type='FPG', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + inter_channels=256, + num_outs=5, + stack_times=9, + paths=['bu'] * 9, + same_down_trans=None, + same_up_trans=dict( + type='conv', + kernel_size=3, + stride=2, + padding=1, + norm_cfg=norm_cfg, + inplace=False, + order=('act', 'conv', 'norm')), + across_lateral_trans=dict( + type='conv', + kernel_size=1, + norm_cfg=norm_cfg, + inplace=False, + order=('act', 'conv', 'norm')), + across_down_trans=dict( + type='interpolation_conv', + mode='nearest', + kernel_size=3, + norm_cfg=norm_cfg, + order=('act', 'conv', 'norm'), + inplace=False), + across_up_trans=None, + across_skip_trans=dict( + type='conv', + kernel_size=1, + norm_cfg=norm_cfg, + inplace=False, + order=('act', 'conv', 'norm')), + output_trans=dict( + type='last_conv', + kernel_size=3, + order=('act', 'conv', 'norm'), + inplace=False), + norm_cfg=norm_cfg, + skip_inds=[(0, 1, 2, 3), (0, 1, 2), (0, 1), (0, ), ()])) diff --git a/mmdetection/configs/fpg/faster-rcnn_r50_fpn_crop640-50e_coco.py b/mmdetection/configs/fpg/faster-rcnn_r50_fpn_crop640-50e_coco.py new file mode 100644 index 00000000..46211de0 --- /dev/null +++ b/mmdetection/configs/fpg/faster-rcnn_r50_fpn_crop640-50e_coco.py @@ -0,0 +1,73 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +norm_cfg = dict(type='BN', requires_grad=True) +image_size = (640, 640) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +model = dict( + data_preprocessor=dict(pad_size_divisor=64, batch_augments=batch_augments), + backbone=dict(norm_cfg=norm_cfg, norm_eval=False), + neck=dict(norm_cfg=norm_cfg), + roi_head=dict(bbox_head=dict(norm_cfg=norm_cfg))) +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.8, 1.2), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + allow_negative_crop=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=image_size, keep_ratio=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=8, num_workers=4, dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# learning policy +max_epochs = 50 +train_cfg = dict(max_epochs=max_epochs, val_interval=2) +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[30, 40], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.08, momentum=0.9, weight_decay=0.0001), + paramwise_cfg=dict(norm_decay_mult=0, bypass_duplicate=True), + clip_grad=None) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/fpg/mask-rcnn_r50_fpg-chn128_crop640-50e_coco.py b/mmdetection/configs/fpg/mask-rcnn_r50_fpg-chn128_crop640-50e_coco.py new file mode 100644 index 00000000..80439396 --- /dev/null +++ b/mmdetection/configs/fpg/mask-rcnn_r50_fpg-chn128_crop640-50e_coco.py @@ -0,0 +1,10 @@ +_base_ = 'mask-rcnn_r50_fpg_crop640-50e_coco.py' + +model = dict( + neck=dict(out_channels=128, inter_channels=128), + rpn_head=dict(in_channels=128), + roi_head=dict( + bbox_roi_extractor=dict(out_channels=128), + bbox_head=dict(in_channels=128), + mask_roi_extractor=dict(out_channels=128), + mask_head=dict(in_channels=128))) diff --git a/mmdetection/configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py b/mmdetection/configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py new file mode 100644 index 00000000..135bb60b --- /dev/null +++ b/mmdetection/configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py @@ -0,0 +1,48 @@ +_base_ = 'mask-rcnn_r50_fpn_crop640-50e_coco.py' + +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + neck=dict( + type='FPG', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + inter_channels=256, + num_outs=5, + stack_times=9, + paths=['bu'] * 9, + same_down_trans=None, + same_up_trans=dict( + type='conv', + kernel_size=3, + stride=2, + padding=1, + norm_cfg=norm_cfg, + inplace=False, + order=('act', 'conv', 'norm')), + across_lateral_trans=dict( + type='conv', + kernel_size=1, + norm_cfg=norm_cfg, + inplace=False, + order=('act', 'conv', 'norm')), + across_down_trans=dict( + type='interpolation_conv', + mode='nearest', + kernel_size=3, + norm_cfg=norm_cfg, + order=('act', 'conv', 'norm'), + inplace=False), + across_up_trans=None, + across_skip_trans=dict( + type='conv', + kernel_size=1, + norm_cfg=norm_cfg, + inplace=False, + order=('act', 'conv', 'norm')), + output_trans=dict( + type='last_conv', + kernel_size=3, + order=('act', 'conv', 'norm'), + inplace=False), + norm_cfg=norm_cfg, + skip_inds=[(0, 1, 2, 3), (0, 1, 2), (0, 1), (0, ), ()])) diff --git a/mmdetection/configs/fpg/mask-rcnn_r50_fpn_crop640-50e_coco.py b/mmdetection/configs/fpg/mask-rcnn_r50_fpn_crop640-50e_coco.py new file mode 100644 index 00000000..08ca5b6f --- /dev/null +++ b/mmdetection/configs/fpg/mask-rcnn_r50_fpn_crop640-50e_coco.py @@ -0,0 +1,79 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +norm_cfg = dict(type='BN', requires_grad=True) +image_size = (640, 640) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +model = dict( + data_preprocessor=dict(pad_size_divisor=64, batch_augments=batch_augments), + backbone=dict(norm_cfg=norm_cfg, norm_eval=False), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + norm_cfg=norm_cfg, + num_outs=5), + roi_head=dict( + bbox_head=dict(norm_cfg=norm_cfg), mask_head=dict(norm_cfg=norm_cfg))) +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.8, 1.2), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + allow_negative_crop=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=image_size, keep_ratio=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=8, num_workers=4, dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# learning policy +max_epochs = 50 +train_cfg = dict(max_epochs=max_epochs, val_interval=2) +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[30, 40], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.08, momentum=0.9, weight_decay=0.0001), + paramwise_cfg=dict(norm_decay_mult=0, bypass_duplicate=True), + clip_grad=None) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/fpg/metafile.yml b/mmdetection/configs/fpg/metafile.yml new file mode 100644 index 00000000..7d7634ae --- /dev/null +++ b/mmdetection/configs/fpg/metafile.yml @@ -0,0 +1,104 @@ +Collections: + - Name: Feature Pyramid Grids + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Feature Pyramid Grids + Paper: + URL: https://arxiv.org/abs/2004.03580 + Title: 'Feature Pyramid Grids' + README: configs/fpg/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.10.0/mmdet/models/necks/fpg.py#L101 + Version: v2.10.0 + +Models: + - Name: faster-rcnn_r50_fpg_crop640-50e_coco + In Collection: Feature Pyramid Grids + Config: configs/fpg/faster-rcnn_r50_fpg_crop640-50e_coco.py + Metadata: + Training Memory (GB): 20.0 + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fpg/faster_rcnn_r50_fpg_crop640_50e_coco/faster_rcnn_r50_fpg_crop640_50e_coco_20220311_011856-74109f42.pth + + - Name: faster-rcnn_r50_fpg-chn128_crop640-50e_coco + In Collection: Feature Pyramid Grids + Config: configs/fpg/faster-rcnn_r50_fpg-chn128_crop640-50e_coco.py + Metadata: + Training Memory (GB): 11.9 + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fpg/faster_rcnn_r50_fpg-chn128_crop640_50e_coco/faster_rcnn_r50_fpg-chn128_crop640_50e_coco_20220311_011857-9376aa9d.pth + + - Name: mask-rcnn_r50_fpg_crop640-50e_coco + In Collection: Feature Pyramid Grids + Config: configs/fpg/mask-rcnn_r50_fpg_crop640-50e_coco.py + Metadata: + Training Memory (GB): 23.2 + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fpg/mask_rcnn_r50_fpg_crop640_50e_coco/mask_rcnn_r50_fpg_crop640_50e_coco_20220311_011857-233b8334.pth + + - Name: mask-rcnn_r50_fpg-chn128_crop640-50e_coco + In Collection: Feature Pyramid Grids + Config: configs/fpg/mask-rcnn_r50_fpg-chn128_crop640-50e_coco.py + Metadata: + Training Memory (GB): 15.3 + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fpg/mask_rcnn_r50_fpg-chn128_crop640_50e_coco/mask_rcnn_r50_fpg-chn128_crop640_50e_coco_20220311_011859-043c9b4e.pth + + - Name: retinanet_r50_fpg_crop640_50e_coco + In Collection: Feature Pyramid Grids + Config: configs/fpg/retinanet_r50_fpg_crop640_50e_coco.py + Metadata: + Training Memory (GB): 20.8 + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fpg/retinanet_r50_fpg_crop640_50e_coco/retinanet_r50_fpg_crop640_50e_coco_20220311_110809-b0bcf5f4.pth + + - Name: retinanet_r50_fpg-chn128_crop640_50e_coco + In Collection: Feature Pyramid Grids + Config: configs/fpg/retinanet_r50_fpg-chn128_crop640_50e_coco.py + Metadata: + Training Memory (GB): 19.9 + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fpg/retinanet_r50_fpg-chn128_crop640_50e_coco/retinanet_r50_fpg-chn128_crop640_50e_coco_20220313_104829-ee99a686.pth diff --git a/mmdetection/configs/fpg/retinanet_r50_fpg-chn128_crop640_50e_coco.py b/mmdetection/configs/fpg/retinanet_r50_fpg-chn128_crop640_50e_coco.py new file mode 100644 index 00000000..9a6cf7e5 --- /dev/null +++ b/mmdetection/configs/fpg/retinanet_r50_fpg-chn128_crop640_50e_coco.py @@ -0,0 +1,5 @@ +_base_ = 'retinanet_r50_fpg_crop640_50e_coco.py' + +model = dict( + neck=dict(out_channels=128, inter_channels=128), + bbox_head=dict(in_channels=128)) diff --git a/mmdetection/configs/fpg/retinanet_r50_fpg_crop640_50e_coco.py b/mmdetection/configs/fpg/retinanet_r50_fpg_crop640_50e_coco.py new file mode 100644 index 00000000..e2aac283 --- /dev/null +++ b/mmdetection/configs/fpg/retinanet_r50_fpg_crop640_50e_coco.py @@ -0,0 +1,53 @@ +_base_ = '../nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py' + +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + neck=dict( + _delete_=True, + type='FPG', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + inter_channels=256, + num_outs=5, + add_extra_convs=True, + start_level=1, + stack_times=9, + paths=['bu'] * 9, + same_down_trans=None, + same_up_trans=dict( + type='conv', + kernel_size=3, + stride=2, + padding=1, + norm_cfg=norm_cfg, + inplace=False, + order=('act', 'conv', 'norm')), + across_lateral_trans=dict( + type='conv', + kernel_size=1, + norm_cfg=norm_cfg, + inplace=False, + order=('act', 'conv', 'norm')), + across_down_trans=dict( + type='interpolation_conv', + mode='nearest', + kernel_size=3, + norm_cfg=norm_cfg, + order=('act', 'conv', 'norm'), + inplace=False), + across_up_trans=None, + across_skip_trans=dict( + type='conv', + kernel_size=1, + norm_cfg=norm_cfg, + inplace=False, + order=('act', 'conv', 'norm')), + output_trans=dict( + type='last_conv', + kernel_size=3, + order=('act', 'conv', 'norm'), + inplace=False), + norm_cfg=norm_cfg, + skip_inds=[(0, 1, 2, 3), (0, 1, 2), (0, 1), (0, ), ()])) + +train_cfg = dict(val_interval=2) diff --git a/mmdetection/configs/free_anchor/README.md b/mmdetection/configs/free_anchor/README.md new file mode 100644 index 00000000..03dc8283 --- /dev/null +++ b/mmdetection/configs/free_anchor/README.md @@ -0,0 +1,37 @@ +# FreeAnchor + +> [FreeAnchor: Learning to Match Anchors for Visual Object Detection](https://arxiv.org/abs/1909.02466) + + + +## Abstract + +Modern CNN-based object detectors assign anchors for ground-truth objects under the restriction of object-anchor Intersection-over-Unit (IoU). In this study, we propose a learning-to-match approach to break IoU restriction, allowing objects to match anchors in a flexible manner. Our approach, referred to as FreeAnchor, updates hand-crafted anchor assignment to "free" anchor matching by formulating detector training as a maximum likelihood estimation (MLE) procedure. FreeAnchor targets at learning features which best explain a class of objects in terms of both classification and localization. FreeAnchor is implemented by optimizing detection customized likelihood and can be fused with CNN-based detectors in a plug-and-play manner. Experiments on COCO demonstrate that FreeAnchor consistently outperforms their counterparts with significant margins. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :---------: | :-----: | :-----: | :------: | :------------: | :----: | :----------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | 1x | 4.9 | 18.4 | 38.7 | [config](./freeanchor_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_r50_fpn_1x_coco/retinanet_free_anchor_r50_fpn_1x_coco_20200130-0f67375f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_r50_fpn_1x_coco/retinanet_free_anchor_r50_fpn_1x_coco_20200130_095625.log.json) | +| R-101 | pytorch | 1x | 6.8 | 14.9 | 40.3 | [config](./freeanchor_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_r101_fpn_1x_coco/retinanet_free_anchor_r101_fpn_1x_coco_20200130-358324e6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_r101_fpn_1x_coco/retinanet_free_anchor_r101_fpn_1x_coco_20200130_100723.log.json) | +| X-101-32x4d | pytorch | 1x | 8.1 | 11.1 | 41.9 | [config](./freeanchor_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_x101_32x4d_fpn_1x_coco/retinanet_free_anchor_x101_32x4d_fpn_1x_coco_20200130-d4846968.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_x101_32x4d_fpn_1x_coco/retinanet_free_anchor_x101_32x4d_fpn_1x_coco_20200130_095627.log.json) | + +**Notes:** + +- We use 8 GPUs with 2 images/GPU. +- For more settings and models, please refer to the [official repo](https://github.com/zhangxiaosong18/FreeAnchor). + +## Citation + +```latex +@inproceedings{zhang2019freeanchor, + title = {{FreeAnchor}: Learning to Match Anchors for Visual Object Detection}, + author = {Zhang, Xiaosong and Wan, Fang and Liu, Chang and Ji, Rongrong and Ye, Qixiang}, + booktitle = {Neural Information Processing Systems}, + year = {2019} +} +``` diff --git a/mmdetection/configs/free_anchor/freeanchor_r101_fpn_1x_coco.py b/mmdetection/configs/free_anchor/freeanchor_r101_fpn_1x_coco.py new file mode 100644 index 00000000..dc323d94 --- /dev/null +++ b/mmdetection/configs/free_anchor/freeanchor_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './freeanchor_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/free_anchor/freeanchor_r50_fpn_1x_coco.py b/mmdetection/configs/free_anchor/freeanchor_r50_fpn_1x_coco.py new file mode 100644 index 00000000..13f64d14 --- /dev/null +++ b/mmdetection/configs/free_anchor/freeanchor_r50_fpn_1x_coco.py @@ -0,0 +1,22 @@ +_base_ = '../retinanet/retinanet_r50_fpn_1x_coco.py' +model = dict( + bbox_head=dict( + _delete_=True, + type='FreeAnchorRetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_bbox=dict(type='SmoothL1Loss', beta=0.11, loss_weight=0.75))) + +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/free_anchor/freeanchor_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/free_anchor/freeanchor_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..8e448bc1 --- /dev/null +++ b/mmdetection/configs/free_anchor/freeanchor_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,13 @@ +_base_ = './freeanchor_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/free_anchor/metafile.yml b/mmdetection/configs/free_anchor/metafile.yml new file mode 100644 index 00000000..cff19db6 --- /dev/null +++ b/mmdetection/configs/free_anchor/metafile.yml @@ -0,0 +1,79 @@ +Collections: + - Name: FreeAnchor + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FreeAnchor + - ResNet + Paper: + URL: https://arxiv.org/abs/1909.02466 + Title: 'FreeAnchor: Learning to Match Anchors for Visual Object Detection' + README: configs/free_anchor/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/dense_heads/free_anchor_retina_head.py#L10 + Version: v2.0.0 + +Models: + - Name: freeanchor_r50_fpn_1x_coco + In Collection: FreeAnchor + Config: configs/free_anchor/freeanchor_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.9 + inference time (ms/im): + - value: 54.35 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_r50_fpn_1x_coco/retinanet_free_anchor_r50_fpn_1x_coco_20200130-0f67375f.pth + + - Name: freeanchor_r101_fpn_1x_coco + In Collection: FreeAnchor + Config: configs/free_anchor/freeanchor_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.8 + inference time (ms/im): + - value: 67.11 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_r101_fpn_1x_coco/retinanet_free_anchor_r101_fpn_1x_coco_20200130-358324e6.pth + + - Name: freeanchor_x101-32x4d_fpn_1x_coco + In Collection: FreeAnchor + Config: configs/free_anchor/freeanchor_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.1 + inference time (ms/im): + - value: 90.09 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/free_anchor/retinanet_free_anchor_x101_32x4d_fpn_1x_coco/retinanet_free_anchor_x101_32x4d_fpn_1x_coco_20200130-d4846968.pth diff --git a/mmdetection/configs/fsaf/README.md b/mmdetection/configs/fsaf/README.md new file mode 100644 index 00000000..46f60577 --- /dev/null +++ b/mmdetection/configs/fsaf/README.md @@ -0,0 +1,57 @@ +# FSAF + +> [Feature Selective Anchor-Free Module for Single-Shot Object Detection](https://arxiv.org/abs/1903.00621) + + + +## Abstract + +We motivate and present feature selective anchor-free (FSAF) module, a simple and effective building block for single-shot object detectors. It can be plugged into single-shot detectors with feature pyramid structure. The FSAF module addresses two limitations brought up by the conventional anchor-based detection: 1) heuristic-guided feature selection; 2) overlap-based anchor sampling. The general concept of the FSAF module is online feature selection applied to the training of multi-level anchor-free branches. Specifically, an anchor-free branch is attached to each level of the feature pyramid, allowing box encoding and decoding in the anchor-free manner at an arbitrary level. During training, we dynamically assign each instance to the most suitable feature level. At the time of inference, the FSAF module can work jointly with anchor-based branches by outputting predictions in parallel. We instantiate this concept with simple implementations of anchor-free branches and online feature selection strategy. Experimental results on the COCO detection track show that our FSAF module performs better than anchor-based counterparts while being faster. When working jointly with anchor-based branches, the FSAF module robustly improves the baseline RetinaNet by a large margin under various settings, while introducing nearly free inference overhead. And the resulting best model can achieve a state-of-the-art 44.6% mAP, outperforming all existing single-shot detectors on COCO. + +
    + +
    + +## Introduction + +FSAF is an anchor-free method published in CVPR2019 ([https://arxiv.org/pdf/1903.00621.pdf](https://arxiv.org/pdf/1903.00621.pdf)). +Actually it is equivalent to the anchor-based method with only one anchor at each feature map position in each FPN level. +And this is how we implemented it. +Only the anchor-free branch is released for its better compatibility with the current framework and less computational budget. + +In the original paper, feature maps within the central 0.2-0.5 area of a gt box are tagged as ignored. However, +it is empirically found that a hard threshold (0.2-0.2) gives a further gain on the performance. (see the table below) + +## Results and Models + +### Results on R50/R101/X101-FPN + +| Backbone | ignore range | ms-train | Lr schd | Train Mem (GB) | Train time (s/iter) | Inf time (fps) | box AP | Config | Download | +| :------: | :----------: | :------: | :-----: | :------------: | :-----------------: | :------------: | :---------: | :----------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | 0.2-0.5 | N | 1x | 3.15 | 0.43 | 12.3 | 36.0 (35.9) | | [model](https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_pscale0.2_nscale0.5_r50_fpn_1x_coco/fsaf_pscale0.2_nscale0.5_r50_fpn_1x_coco_20200715-b555b0e0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_pscale0.2_nscale0.5_r50_fpn_1x_coco/fsaf_pscale0.2_nscale0.5_r50_fpn_1x_coco_20200715_094657.log.json) | +| R-50 | 0.2-0.2 | N | 1x | 3.15 | 0.43 | 13.0 | 37.4 | [config](./fsaf_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_r50_fpn_1x_coco/fsaf_r50_fpn_1x_coco-94ccc51f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_r50_fpn_1x_coco/fsaf_r50_fpn_1x_coco_20200428_072327.log.json) | +| R-101 | 0.2-0.2 | N | 1x | 5.08 | 0.58 | 10.8 | 39.3 (37.9) | [config](./fsaf_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_r101_fpn_1x_coco/fsaf_r101_fpn_1x_coco-9e71098f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_r101_fpn_1x_coco/fsaf_r101_fpn_1x_coco_20200428_160348.log.json) | +| X-101 | 0.2-0.2 | N | 1x | 9.38 | 1.23 | 5.6 | 42.4 (41.0) | [config](./fsaf_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_x101_64x4d_fpn_1x_coco/fsaf_x101_64x4d_fpn_1x_coco-e3f6e6fd.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_x101_64x4d_fpn_1x_coco/fsaf_x101_64x4d_fpn_1x_coco_20200428_160424.log.json) | + +**Notes:** + +- *1x means the model is trained for 12 epochs.* +- *AP values in the brackets represent those reported in the original paper.* +- *All results are obtained with a single model and single-scale test.* +- *X-101 backbone represents ResNext-101-64x4d.* +- *All pretrained backbones use pytorch style.* +- *All models are trained on 8 Titan-XP gpus and tested on a single gpu.* + +## Citation + +BibTeX reference is as follows. + +```latex +@inproceedings{zhu2019feature, + title={Feature Selective Anchor-Free Module for Single-Shot Object Detection}, + author={Zhu, Chenchen and He, Yihui and Savvides, Marios}, + booktitle={Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition}, + pages={840--849}, + year={2019} +} +``` diff --git a/mmdetection/configs/fsaf/fsaf_r101_fpn_1x_coco.py b/mmdetection/configs/fsaf/fsaf_r101_fpn_1x_coco.py new file mode 100644 index 00000000..12b49fed --- /dev/null +++ b/mmdetection/configs/fsaf/fsaf_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './fsaf_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/fsaf/fsaf_r50_fpn_1x_coco.py b/mmdetection/configs/fsaf/fsaf_r50_fpn_1x_coco.py new file mode 100644 index 00000000..e7165cd6 --- /dev/null +++ b/mmdetection/configs/fsaf/fsaf_r50_fpn_1x_coco.py @@ -0,0 +1,47 @@ +_base_ = '../retinanet/retinanet_r50_fpn_1x_coco.py' +# model settings +model = dict( + type='FSAF', + bbox_head=dict( + type='FSAFHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + reg_decoded_bbox=True, + # Only anchor-free branch is implemented. The anchor generator only + # generates 1 anchor at each feature point, as a substitute of the + # grid of features. + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=1, + scales_per_octave=1, + ratios=[1.0], + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict(_delete_=True, type='TBLRBBoxCoder', normalizer=4.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0, + reduction='none'), + loss_bbox=dict( + _delete_=True, + type='IoULoss', + eps=1e-6, + loss_weight=1.0, + reduction='none')), + # training and testing settings + train_cfg=dict( + assigner=dict( + _delete_=True, + type='CenterRegionAssigner', + pos_scale=0.2, + neg_scale=0.2, + min_pos_iof=0.01), + allowed_border=-1, + pos_weight=-1, + debug=False)) + +optim_wrapper = dict(clip_grad=dict(max_norm=10, norm_type=2)) diff --git a/mmdetection/configs/fsaf/fsaf_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/fsaf/fsaf_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..89c0c634 --- /dev/null +++ b/mmdetection/configs/fsaf/fsaf_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './fsaf_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/fsaf/metafile.yml b/mmdetection/configs/fsaf/metafile.yml new file mode 100644 index 00000000..daaad0d3 --- /dev/null +++ b/mmdetection/configs/fsaf/metafile.yml @@ -0,0 +1,80 @@ +Collections: + - Name: FSAF + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x Titan-XP GPUs + Architecture: + - FPN + - FSAF + - ResNet + Paper: + URL: https://arxiv.org/abs/1903.00621 + Title: 'Feature Selective Anchor-Free Module for Single-Shot Object Detection' + README: configs/fsaf/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/detectors/fsaf.py#L6 + Version: v2.1.0 + +Models: + - Name: fsaf_r50_fpn_1x_coco + In Collection: FSAF + Config: configs/fsaf/fsaf_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.15 + inference time (ms/im): + - value: 76.92 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_r50_fpn_1x_coco/fsaf_r50_fpn_1x_coco-94ccc51f.pth + + - Name: fsaf_r101_fpn_1x_coco + In Collection: FSAF + Config: configs/fsaf/fsaf_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.08 + inference time (ms/im): + - value: 92.59 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_r101_fpn_1x_coco/fsaf_r101_fpn_1x_coco-9e71098f.pth + + - Name: fsaf_x101-64x4d_fpn_1x_coco + In Collection: FSAF + Config: configs/fsaf/fsaf_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 9.38 + inference time (ms/im): + - value: 178.57 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fsaf/fsaf_x101_64x4d_fpn_1x_coco/fsaf_x101_64x4d_fpn_1x_coco-e3f6e6fd.pth diff --git a/mmdetection/configs/gcnet/README.md b/mmdetection/configs/gcnet/README.md new file mode 100644 index 00000000..1ba6f6f3 --- /dev/null +++ b/mmdetection/configs/gcnet/README.md @@ -0,0 +1,69 @@ +# GCNet + +> [GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond](https://arxiv.org/abs/1904.11492) + + + +## Abstract + +The Non-Local Network (NLNet) presents a pioneering approach for capturing long-range dependencies, via aggregating query-specific global context to each query position. However, through a rigorous empirical analysis, we have found that the global contexts modeled by non-local network are almost the same for different query positions within an image. In this paper, we take advantage of this finding to create a simplified network based on a query-independent formulation, which maintains the accuracy of NLNet but with significantly less computation. We further observe that this simplified design shares similar structure with Squeeze-Excitation Network (SENet). Hence we unify them into a three-step general framework for global context modeling. Within the general framework, we design a better instantiation, called the global context (GC) block, which is lightweight and can effectively model the global context. The lightweight property allows us to apply it for multiple layers in a backbone network to construct a global context network (GCNet), which generally outperforms both simplified NLNet and SENet on major benchmarks for various recognition tasks. + +
    + +
    + +## Introduction + +By [Yue Cao](http://yue-cao.me), [Jiarui Xu](http://jerryxu.net), [Stephen Lin](https://scholar.google.com/citations?user=c3PYmxUAAAAJ&hl=en), Fangyun Wei, [Han Hu](https://sites.google.com/site/hanhushomepage/). + +We provide config files to reproduce the results in the paper for +["GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond"](https://arxiv.org/abs/1904.11492) on COCO object detection. + +**GCNet** is initially described in [arxiv](https://arxiv.org/abs/1904.11492). Via absorbing advantages of Non-Local Networks (NLNet) and Squeeze-Excitation Networks (SENet), GCNet provides a simple, fast and effective approach for global context modeling, which generally outperforms both NLNet and SENet on major benchmarks for various recognition tasks. + +## Results and Models + +The results on COCO 2017val are shown in the below table. + +| Backbone | Model | Context | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------: | :---: | :------------: | :-----: | :------: | :------------: | :----: | :-----: | :-----------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | Mask | GC(c3-c5, r16) | 1x | 5.0 | | 39.7 | 35.9 | [config](./mask-rcnn_r50-gcb-r16-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_r16_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_r16_gcb_c3-c5_1x_coco_20200515_211915-187da160.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_r16_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_r16_gcb_c3-c5_1x_coco_20200515_211915.log.json) | +| R-50-FPN | Mask | GC(c3-c5, r4) | 1x | 5.1 | 15.0 | 39.9 | 36.0 | [config](./mask-rcnn_r50-gcb-r4-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_r4_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_r4_gcb_c3-c5_1x_coco_20200204-17235656.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_r4_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_r4_gcb_c3-c5_1x_coco_20200204_024626.log.json) | +| R-101-FPN | Mask | GC(c3-c5, r16) | 1x | 7.6 | 11.4 | 41.3 | 37.2 | [config](./mask-rcnn_r101-gcb-r16-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_r16_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_r16_gcb_c3-c5_1x_coco_20200205-e58ae947.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_r16_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_r16_gcb_c3-c5_1x_coco_20200205_192835.log.json) | +| R-101-FPN | Mask | GC(c3-c5, r4) | 1x | 7.8 | 11.6 | 42.2 | 37.8 | [config](./mask-rcnn_r101-gcb-r4-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_r4_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_r4_gcb_c3-c5_1x_coco_20200206-af22dc9d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_r4_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_r4_gcb_c3-c5_1x_coco_20200206_112128.log.json) | + +| Backbone | Model | Context | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------: | :--------------: | :------------: | :-----: | :------: | :------------: | :----: | :-----: | :--------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | Mask | - | 1x | 4.4 | 16.6 | 38.4 | 34.6 | [config](./mask-rcnn_r50-syncbn_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_1x_coco_20200202-bb3eb55c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_1x_coco_20200202_214122.log.json) | +| R-50-FPN | Mask | GC(c3-c5, r16) | 1x | 5.0 | 15.5 | 40.4 | 36.2 | [config](./mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200202-587b99aa.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200202_174907.log.json) | +| R-50-FPN | Mask | GC(c3-c5, r4) | 1x | 5.1 | 15.1 | 40.7 | 36.5 | [config](./mask-rcnn_r50-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200202-50b90e5c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200202_085547.log.json) | +| R-101-FPN | Mask | - | 1x | 6.4 | 13.3 | 40.5 | 36.3 | [config](./mask-rcnn_r101-syncbn_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_1x_coco_20200210-81658c8a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_1x_coco_20200210_220422.log.json) | +| R-101-FPN | Mask | GC(c3-c5, r16) | 1x | 7.6 | 12.0 | 42.2 | 37.8 | [config](./mask-rcnn_r101-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200207-945e77ca.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200207_015330.log.json) | +| R-101-FPN | Mask | GC(c3-c5, r4) | 1x | 7.8 | 11.8 | 42.2 | 37.8 | [config](./mask-rcnn_r101-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200206-8407a3f0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200206_142508.log.json) | +| X-101-FPN | Mask | - | 1x | 7.6 | 11.3 | 42.4 | 37.7 | [config](./mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco_20200211-7584841c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco_20200211_054326.log.json) | +| X-101-FPN | Mask | GC(c3-c5, r16) | 1x | 8.8 | 9.8 | 43.5 | 38.6 | [config](./mask-rcnn_x101-32x4d-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200211-cbed3d2c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200211_164715.log.json) | +| X-101-FPN | Mask | GC(c3-c5, r4) | 1x | 9.0 | 9.7 | 43.9 | 39.0 | [config](./mask-rcnn_x101-32x4d-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200212-68164964.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200212_070942.log.json) | +| X-101-FPN | Cascade Mask | - | 1x | 9.2 | 8.4 | 44.7 | 38.6 | [config](./cascade-mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco_20200310-d5ad2a5e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco_20200310_115217.log.json) | +| X-101-FPN | Cascade Mask | GC(c3-c5, r16) | 1x | 10.3 | 7.7 | 46.2 | 39.7 | [config](./cascade-mask-rcnn_x101-32x4d-syncbn-r16-gcb-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200211-10bf2463.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200211_184154.log.json) | +| X-101-FPN | Cascade Mask | GC(c3-c5, r4) | 1x | 10.6 | | 46.4 | 40.1 | [config](./cascade-mask-rcnn_x101-32x4d-syncbn-r4-gcb-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200703_180653-ed035291.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200703_180653.log.json) | +| X-101-FPN | DCN Cascade Mask | - | 1x | | | 47.5 | 40.9 | [config](./cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_1x_coco_20210615_211019-abbc39ea.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_1x_coco_20210615_211019.log.json) | +| X-101-FPN | DCN Cascade Mask | GC(c3-c5, r16) | 1x | | | 48.0 | 41.3 | [config](./cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r16-gcb-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r16_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r16_gcb_c3-c5_1x_coco_20210615_215648-44aa598a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r16_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r16_gcb_c3-c5_1x_coco_20210615_215648.log.json) | +| X-101-FPN | DCN Cascade Mask | GC(c3-c5, r4) | 1x | | | 47.9 | 41.1 | [config](./cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r4-gcb-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r4_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r4_gcb_c3-c5_1x_coco_20210615_161851-720338ec.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r4_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r4_gcb_c3-c5_1x_coco_20210615_161851.log.json) | + +**Notes:** + +- The `SyncBN` is added in the backbone for all models in **Table 2**. +- `GC` denotes Global Context (GC) block is inserted after 1x1 conv of backbone. +- `DCN` denotes replace 3x3 conv with 3x3 Deformable Convolution in `c3-c5` stages of backbone. +- `r4` and `r16` denote ratio 4 and ratio 16 in GC block respectively. + +## Citation + +```latex +@article{cao2019GCNet, + title={GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond}, + author={Cao, Yue and Xu, Jiarui and Lin, Stephen and Wei, Fangyun and Hu, Han}, + journal={arXiv preprint arXiv:1904.11492}, + year={2019} +} +``` diff --git a/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r16-gcb-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r16-gcb-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..6cf605b6 --- /dev/null +++ b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r16-gcb-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../dcn/cascade-mask-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r4-gcb-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r4-gcb-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..95fc687b --- /dev/null +++ b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r4-gcb-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../dcn/cascade-mask-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 4), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..9b77dc93 --- /dev/null +++ b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,4 @@ +_base_ = '../dcn/cascade-mask-rcnn_x101-32x4d-dconv-c3-c5_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), norm_eval=False)) diff --git a/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-r16-gcb-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-r16-gcb-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..8f97972a --- /dev/null +++ b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-r16-gcb-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-r4-gcb-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-r4-gcb-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..8404cfda --- /dev/null +++ b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-r4-gcb-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 4), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py new file mode 100644 index 00000000..87667dee --- /dev/null +++ b/mmdetection/configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py @@ -0,0 +1,4 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_x101-32x4d_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), norm_eval=False)) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r101-gcb-r16-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r101-gcb-r16-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..447e2c6d --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r101-gcb-r16-c3-c5_fpn_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = '../mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict(plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r101-gcb-r4-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r101-gcb-r4-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..9c723a64 --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r101-gcb-r4-c3-c5_fpn_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = '../mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict(plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 4), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r101-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r101-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..6f9d03d3 --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r101-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r101-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r101-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..d07cb0d4 --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r101-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 4), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r101-syncbn_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r101-syncbn_fpn_1x_coco.py new file mode 100644 index 00000000..957bdf55 --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r101-syncbn_fpn_1x_coco.py @@ -0,0 +1,4 @@ +_base_ = '../mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), norm_eval=False)) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r50-gcb-r16-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r50-gcb-r16-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..c9ec5ac3 --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r50-gcb-r16-c3-c5_fpn_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict(plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r50-gcb-r4-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r50-gcb-r4-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..42474d51 --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r50-gcb-r4-c3-c5_fpn_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict(plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 4), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..ac192808 --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r50-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r50-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..ae29f0ce --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r50-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 4), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_r50-syncbn_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_r50-syncbn_fpn_1x_coco.py new file mode 100644 index 00000000..f8ef27ba --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_r50-syncbn_fpn_1x_coco.py @@ -0,0 +1,4 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), norm_eval=False)) diff --git a/mmdetection/configs/gcnet/mask-rcnn_x101-32x4d-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_x101-32x4d-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..1a2e2c9f --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_x101-32x4d-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_x101-32x4d-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_x101-32x4d-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..65d3f9aa --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_x101-32x4d-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = '../mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + plugins=[ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 4), + stages=(False, True, True, True), + position='after_conv3') + ])) diff --git a/mmdetection/configs/gcnet/mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py b/mmdetection/configs/gcnet/mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py new file mode 100644 index 00000000..b5343a6d --- /dev/null +++ b/mmdetection/configs/gcnet/mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py @@ -0,0 +1,4 @@ +_base_ = '../mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py' +model = dict( + backbone=dict( + norm_cfg=dict(type='SyncBN', requires_grad=True), norm_eval=False)) diff --git a/mmdetection/configs/gcnet/metafile.yml b/mmdetection/configs/gcnet/metafile.yml new file mode 100644 index 00000000..075a94c8 --- /dev/null +++ b/mmdetection/configs/gcnet/metafile.yml @@ -0,0 +1,440 @@ +Collections: + - Name: GCNet + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Global Context Block + - FPN + - RPN + - ResNet + - ResNeXt + Paper: + URL: https://arxiv.org/abs/1904.11492 + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + README: configs/gcnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/ops/context_block.py#L13 + Version: v2.0.0 + +Models: + - Name: mask-rcnn_r50_fpn_r16_gcb_c3-c5_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r50-gcb-r16-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.0 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 35.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_r16_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_r16_gcb_c3-c5_1x_coco_20200515_211915-187da160.pth + + - Name: mask-rcnn_r50_fpn_r4_gcb_c3-c5_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r50-gcb-r4-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.1 + inference time (ms/im): + - value: 66.67 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_r4_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_r4_gcb_c3-c5_1x_coco_20200204-17235656.pth + + - Name: mask-rcnn_r101-gcb-r16-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r101-gcb-r16-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.6 + inference time (ms/im): + - value: 87.72 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_r16_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_r16_gcb_c3-c5_1x_coco_20200205-e58ae947.pth + + - Name: mask-rcnn_r101-gcb-r4-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r101-gcb-r4-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.8 + inference time (ms/im): + - value: 86.21 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_r4_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_r4_gcb_c3-c5_1x_coco_20200206-af22dc9d.pth + + - Name: mask-rcnn_r50_fpn_syncbn-backbone_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r50-syncbn_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.4 + inference time (ms/im): + - value: 60.24 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 34.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_1x_coco_20200202-bb3eb55c.pth + + - Name: mask-rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r50-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.0 + inference time (ms/im): + - value: 64.52 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200202-587b99aa.pth + + - Name: mask-rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r50-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.1 + inference time (ms/im): + - value: 66.23 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200202-50b90e5c.pth + + - Name: mask-rcnn_r101-syncbn_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r101-syncbn_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.4 + inference time (ms/im): + - value: 75.19 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_1x_coco_20200210-81658c8a.pth + + - Name: mask-rcnn_r101-syncbn-gcb-r16-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r101-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.6 + inference time (ms/im): + - value: 83.33 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200207-945e77ca.pth + + - Name: mask-rcnn_r101-syncbn-gcb-r4-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_r101-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.8 + inference time (ms/im): + - value: 84.75 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200206-8407a3f0.pth + + - Name: mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.6 + inference time (ms/im): + - value: 88.5 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco_20200211-7584841c.pth + + - Name: mask-rcnn_x101-32x4d-syncbn-gcb-r16-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_x101-32x4d-syncbn-gcb-r16-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.8 + inference time (ms/im): + - value: 102.04 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200211-cbed3d2c.pth + + - Name: mask-rcnn_x101-32x4d-syncbn-gcb-r4-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/mask-rcnn_x101-32x4d-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 9.0 + inference time (ms/im): + - value: 103.09 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200212-68164964.pth + + - Name: cascade-mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn_fpn_1x_coco.py + Metadata: + Training Memory (GB): 9.2 + inference time (ms/im): + - value: 119.05 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_1x_coco_20200310-d5ad2a5e.pth + + - Name: cascade-mask-rcnn_x101-32x4d-syncbn-r16-gcb-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-r16-gcb-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 10.3 + inference time (ms/im): + - value: 129.87 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r16_gcb_c3-c5_1x_coco_20200211-10bf2463.pth + + - Name: cascade-mask-rcnn_x101-32x4d-syncbn-r4-gcb-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-r4-gcb-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 10.6 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200703_180653-ed035291.pth + + - Name: cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_1x_coco_20210615_211019-abbc39ea.pth + + - Name: cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r16-gcb-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r16-gcb-c3-c5_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 48.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r16_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r16_gcb_c3-c5_1x_coco_20210615_215648-44aa598a.pth + + - Name: cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r4-gcb-c3-c5_fpn_1x_coco + In Collection: GCNet + Config: configs/gcnet/cascade-mask-rcnn_x101-32x4d-syncbn-dconv-c3-c5-r4-gcb-c3-c5_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gcnet/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r4_gcb_c3-c5_1x_coco/cascade_mask_rcnn_x101_32x4d_fpn_syncbn-backbone_dconv_c3-c5_r4_gcb_c3-c5_1x_coco_20210615_161851-720338ec.pth diff --git a/mmdetection/configs/gfl/README.md b/mmdetection/configs/gfl/README.md new file mode 100644 index 00000000..123f303a --- /dev/null +++ b/mmdetection/configs/gfl/README.md @@ -0,0 +1,42 @@ +# GFL + +> [Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes for Dense Object Detection](https://arxiv.org/abs/2006.04388) + + + +## Abstract + +One-stage detector basically formulates object detection as dense classification and localization. The classification is usually optimized by Focal Loss and the box location is commonly learned under Dirac delta distribution. A recent trend for one-stage detectors is to introduce an individual prediction branch to estimate the quality of localization, where the predicted quality facilitates the classification to improve detection performance. This paper delves into the representations of the above three fundamental elements: quality estimation, classification and localization. Two problems are discovered in existing practices, including (1) the inconsistent usage of the quality estimation and classification between training and inference and (2) the inflexible Dirac delta distribution for localization when there is ambiguity and uncertainty in complex scenes. To address the problems, we design new representations for these elements. Specifically, we merge the quality estimation into the class prediction vector to form a joint representation of localization quality and classification, and use a vector to represent arbitrary distribution of box locations. The improved representations eliminate the inconsistency risk and accurately depict the flexible distribution in real data, but contain continuous labels, which is beyond the scope of Focal Loss. We then propose Generalized Focal Loss (GFL) that generalizes Focal Loss from its discrete form to the continuous version for successful optimization. On COCO test-dev, GFL achieves 45.0% AP using ResNet-101 backbone, surpassing state-of-the-art SAPD (43.5%) and ATSS (43.6%) with higher or comparable inference speed, under the same backbone and training settings. Notably, our best model can achieve a single-model single-scale AP of 48.2%, at 10 FPS on a single 2080Ti GPU. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Multi-scale Training | Inf time (fps) | box AP | Config | Download | +| :---------------: | :-----: | :-----: | :------------------: | :------------: | :----: | :------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | 1x | No | 19.5 | 40.2 | [config](./gfl_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r50_fpn_1x_coco/gfl_r50_fpn_1x_coco_20200629_121244-25944287.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r50_fpn_1x_coco/gfl_r50_fpn_1x_coco_20200629_121244.log.json) | +| R-50 | pytorch | 2x | Yes | 19.5 | 42.9 | [config](./gfl_r50_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r50_fpn_mstrain_2x_coco/gfl_r50_fpn_mstrain_2x_coco_20200629_213802-37bb1edc.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r50_fpn_mstrain_2x_coco/gfl_r50_fpn_mstrain_2x_coco_20200629_213802.log.json) | +| R-101 | pytorch | 2x | Yes | 14.7 | 44.7 | [config](./gfl_r101_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r101_fpn_mstrain_2x_coco/gfl_r101_fpn_mstrain_2x_coco_20200629_200126-dd12f847.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r101_fpn_mstrain_2x_coco/gfl_r101_fpn_mstrain_2x_coco_20200629_200126.log.json) | +| R-101-dcnv2 | pytorch | 2x | Yes | 12.9 | 47.1 | [config](./gfl_r101-dconv-c3-c5_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r101_fpn_dconv_c3-c5_mstrain_2x_coco/gfl_r101_fpn_dconv_c3-c5_mstrain_2x_coco_20200630_102002-134b07df.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r101_fpn_dconv_c3-c5_mstrain_2x_coco/gfl_r101_fpn_dconv_c3-c5_mstrain_2x_coco_20200630_102002.log.json) | +| X-101-32x4d | pytorch | 2x | Yes | 12.1 | 45.9 | [config](./gfl_x101-32x4d_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_x101_32x4d_fpn_mstrain_2x_coco/gfl_x101_32x4d_fpn_mstrain_2x_coco_20200630_102002-50c1ffdb.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_x101_32x4d_fpn_mstrain_2x_coco/gfl_x101_32x4d_fpn_mstrain_2x_coco_20200630_102002.log.json) | +| X-101-32x4d-dcnv2 | pytorch | 2x | Yes | 10.7 | 48.1 | [config](./gfl_x101-32x4d-dconv-c4-c5_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_x101_32x4d_fpn_dconv_c4-c5_mstrain_2x_coco/gfl_x101_32x4d_fpn_dconv_c4-c5_mstrain_2x_coco_20200630_102002-14a2bf25.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_x101_32x4d_fpn_dconv_c4-c5_mstrain_2x_coco/gfl_x101_32x4d_fpn_dconv_c4-c5_mstrain_2x_coco_20200630_102002.log.json) | + +\[1\] *1x and 2x mean the model is trained for 90K and 180K iterations, respectively.* \ +\[2\] *All results are obtained with a single model and without any test time data augmentation such as multi-scale, flipping and etc..* \ +\[3\] *`dcnv2` denotes deformable convolutional networks v2.* \ +\[4\] *FPS is tested with a single GeForce RTX 2080Ti GPU, using a batch size of 1.* + +## Citation + +We provide config files to reproduce the object detection results in the paper [Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes for Dense Object Detection](https://arxiv.org/abs/2006.04388) + +```latex +@article{li2020generalized, + title={Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes for Dense Object Detection}, + author={Li, Xiang and Wang, Wenhai and Wu, Lijun and Chen, Shuo and Hu, Xiaolin and Li, Jun and Tang, Jinhui and Yang, Jian}, + journal={arXiv preprint arXiv:2006.04388}, + year={2020} +} +``` diff --git a/mmdetection/configs/gfl/gfl_r101-dconv-c3-c5_fpn_ms-2x_coco.py b/mmdetection/configs/gfl/gfl_r101-dconv-c3-c5_fpn_ms-2x_coco.py new file mode 100644 index 00000000..7f748935 --- /dev/null +++ b/mmdetection/configs/gfl/gfl_r101-dconv-c3-c5_fpn_ms-2x_coco.py @@ -0,0 +1,15 @@ +_base_ = './gfl_r50_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + type='ResNet', + depth=101, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/gfl/gfl_r101_fpn_ms-2x_coco.py b/mmdetection/configs/gfl/gfl_r101_fpn_ms-2x_coco.py new file mode 100644 index 00000000..10135f16 --- /dev/null +++ b/mmdetection/configs/gfl/gfl_r101_fpn_ms-2x_coco.py @@ -0,0 +1,13 @@ +_base_ = './gfl_r50_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + type='ResNet', + depth=101, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/gfl/gfl_r50_fpn_1x_coco.py b/mmdetection/configs/gfl/gfl_r50_fpn_1x_coco.py new file mode 100644 index 00000000..90238255 --- /dev/null +++ b/mmdetection/configs/gfl/gfl_r50_fpn_1x_coco.py @@ -0,0 +1,66 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + type='GFL', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='GFLHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + loss_cls=dict( + type='QualityFocalLoss', + use_sigmoid=True, + beta=2.0, + loss_weight=1.0), + loss_dfl=dict(type='DistributionFocalLoss', loss_weight=0.25), + reg_max=16, + loss_bbox=dict(type='GIoULoss', loss_weight=2.0)), + # training and testing settings + train_cfg=dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/gfl/gfl_r50_fpn_ms-2x_coco.py b/mmdetection/configs/gfl/gfl_r50_fpn_ms-2x_coco.py new file mode 100644 index 00000000..22770eb1 --- /dev/null +++ b/mmdetection/configs/gfl/gfl_r50_fpn_ms-2x_coco.py @@ -0,0 +1,28 @@ +_base_ = './gfl_r50_fpn_1x_coco.py' +max_epochs = 24 + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) + +# multi-scale training +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 480), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/gfl/gfl_x101-32x4d-dconv-c4-c5_fpn_ms-2x_coco.py b/mmdetection/configs/gfl/gfl_x101-32x4d-dconv-c4-c5_fpn_ms-2x_coco.py new file mode 100644 index 00000000..6aa98eea --- /dev/null +++ b/mmdetection/configs/gfl/gfl_x101-32x4d-dconv-c4-c5_fpn_ms-2x_coco.py @@ -0,0 +1,18 @@ +_base_ = './gfl_r50_fpn_ms-2x_coco.py' +model = dict( + type='GFL', + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, False, True, True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/gfl/gfl_x101-32x4d_fpn_ms-2x_coco.py b/mmdetection/configs/gfl/gfl_x101-32x4d_fpn_ms-2x_coco.py new file mode 100644 index 00000000..ec629b1f --- /dev/null +++ b/mmdetection/configs/gfl/gfl_x101-32x4d_fpn_ms-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './gfl_r50_fpn_ms-2x_coco.py' +model = dict( + type='GFL', + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/gfl/metafile.yml b/mmdetection/configs/gfl/metafile.yml new file mode 100644 index 00000000..183fc14b --- /dev/null +++ b/mmdetection/configs/gfl/metafile.yml @@ -0,0 +1,134 @@ +Collections: + - Name: Generalized Focal Loss + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Generalized Focal Loss + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/2006.04388 + Title: 'Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes for Dense Object Detection' + README: configs/gfl/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.2.0/mmdet/models/detectors/gfl.py#L6 + Version: v2.2.0 + +Models: + - Name: gfl_r50_fpn_1x_coco + In Collection: Generalized Focal Loss + Config: configs/gfl/gfl_r50_fpn_1x_coco.py + Metadata: + inference time (ms/im): + - value: 51.28 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r50_fpn_1x_coco/gfl_r50_fpn_1x_coco_20200629_121244-25944287.pth + + - Name: gfl_r50_fpn_ms-2x_coco + In Collection: Generalized Focal Loss + Config: configs/gfl/gfl_r50_fpn_ms-2x_coco.py + Metadata: + inference time (ms/im): + - value: 51.28 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r50_fpn_mstrain_2x_coco/gfl_r50_fpn_mstrain_2x_coco_20200629_213802-37bb1edc.pth + + - Name: gfl_r101_fpn_ms-2x_coco + In Collection: Generalized Focal Loss + Config: configs/gfl/gfl_r101_fpn_ms-2x_coco.py + Metadata: + inference time (ms/im): + - value: 68.03 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r101_fpn_mstrain_2x_coco/gfl_r101_fpn_mstrain_2x_coco_20200629_200126-dd12f847.pth + + - Name: gfl_r101-dconv-c3-c5_fpn_ms-2x_coco + In Collection: Generalized Focal Loss + Config: configs/gfl/gfl_r101-dconv-c3-c5_fpn_ms-2x_coco.py + Metadata: + inference time (ms/im): + - value: 77.52 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r101_fpn_dconv_c3-c5_mstrain_2x_coco/gfl_r101_fpn_dconv_c3-c5_mstrain_2x_coco_20200630_102002-134b07df.pth + + - Name: gfl_x101-32x4d_fpn_ms-2x_coco + In Collection: Generalized Focal Loss + Config: configs/gfl/gfl_x101-32x4d_fpn_ms-2x_coco.py + Metadata: + inference time (ms/im): + - value: 82.64 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_x101_32x4d_fpn_mstrain_2x_coco/gfl_x101_32x4d_fpn_mstrain_2x_coco_20200630_102002-50c1ffdb.pth + + - Name: gfl_x101-32x4d-dconv-c4-c5_fpn_ms-2x_coco + In Collection: Generalized Focal Loss + Config: configs/gfl/gfl_x101-32x4d-dconv-c4-c5_fpn_ms-2x_coco.py + Metadata: + inference time (ms/im): + - value: 93.46 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 48.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_x101_32x4d_fpn_dconv_c4-c5_mstrain_2x_coco/gfl_x101_32x4d_fpn_dconv_c4-c5_mstrain_2x_coco_20200630_102002-14a2bf25.pth diff --git a/mmdetection/configs/ghm/README.md b/mmdetection/configs/ghm/README.md new file mode 100644 index 00000000..c245cea5 --- /dev/null +++ b/mmdetection/configs/ghm/README.md @@ -0,0 +1,33 @@ +# GHM + +> [Gradient Harmonized Single-stage Detector](https://arxiv.org/abs/1811.05181) + + + +## Abstract + +Despite the great success of two-stage detectors, single-stage detector is still a more elegant and efficient way, yet suffers from the two well-known disharmonies during training, i.e. the huge difference in quantity between positive and negative examples as well as between easy and hard examples. In this work, we first point out that the essential effect of the two disharmonies can be summarized in term of the gradient. Further, we propose a novel gradient harmonizing mechanism (GHM) to be a hedging for the disharmonies. The philosophy behind GHM can be easily embedded into both classification loss function like cross-entropy (CE) and regression loss function like smooth-L1 (SL1) loss. To this end, two novel loss functions called GHM-C and GHM-R are designed to balancing the gradient flow for anchor classification and bounding box refinement, respectively. Ablation study on MS COCO demonstrates that without laborious hyper-parameter tuning, both GHM-C and GHM-R can bring substantial improvement for single-stage detector. Without any whistles and bells, our model achieves 41.6 mAP on COCO test-dev set which surpasses the state-of-the-art method, Focal Loss (FL) + SL1, by 0.8. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 1x | 4.0 | 3.3 | 37.0 | [config](./retinanet_r50_fpn_ghm-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_r50_fpn_1x_coco/retinanet_ghm_r50_fpn_1x_coco_20200130-a437fda3.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_r50_fpn_1x_coco/retinanet_ghm_r50_fpn_1x_coco_20200130_004213.log.json) | +| R-101-FPN | pytorch | 1x | 6.0 | 4.4 | 39.1 | [config](./retinanet_r101_fpn_ghm-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_r101_fpn_1x_coco/retinanet_ghm_r101_fpn_1x_coco_20200130-c148ee8f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_r101_fpn_1x_coco/retinanet_ghm_r101_fpn_1x_coco_20200130_145259.log.json) | +| X-101-32x4d-FPN | pytorch | 1x | 7.2 | 5.1 | 40.7 | [config](./retinanet_x101-32x4d_fpn_ghm-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_x101_32x4d_fpn_1x_coco/retinanet_ghm_x101_32x4d_fpn_1x_coco_20200131-e4333bd0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_x101_32x4d_fpn_1x_coco/retinanet_ghm_x101_32x4d_fpn_1x_coco_20200131_113653.log.json) | +| X-101-64x4d-FPN | pytorch | 1x | 10.3 | 5.2 | 41.4 | [config](./retinanet_x101-64x4d_fpn_ghm-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_x101_64x4d_fpn_1x_coco/retinanet_ghm_x101_64x4d_fpn_1x_coco_20200131-dd381cef.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_x101_64x4d_fpn_1x_coco/retinanet_ghm_x101_64x4d_fpn_1x_coco_20200131_113723.log.json) | + +## Citation + +```latex +@inproceedings{li2019gradient, + title={Gradient Harmonized Single-stage Detector}, + author={Li, Buyu and Liu, Yu and Wang, Xiaogang}, + booktitle={AAAI Conference on Artificial Intelligence}, + year={2019} +} +``` diff --git a/mmdetection/configs/ghm/metafile.yml b/mmdetection/configs/ghm/metafile.yml new file mode 100644 index 00000000..63cb48ff --- /dev/null +++ b/mmdetection/configs/ghm/metafile.yml @@ -0,0 +1,101 @@ +Collections: + - Name: GHM + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - GHM-C + - GHM-R + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1811.05181 + Title: 'Gradient Harmonized Single-stage Detector' + README: configs/ghm/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/losses/ghm_loss.py#L21 + Version: v2.0.0 + +Models: + - Name: retinanet_r50_fpn_ghm-1x_coco + In Collection: GHM + Config: configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py + Metadata: + Training Memory (GB): 4.0 + inference time (ms/im): + - value: 303.03 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_r50_fpn_1x_coco/retinanet_ghm_r50_fpn_1x_coco_20200130-a437fda3.pth + + - Name: retinanet_r101_fpn_ghm-1x_coco + In Collection: GHM + Config: configs/ghm/retinanet_r101_fpn_ghm-1x_coco.py + Metadata: + Training Memory (GB): 6.0 + inference time (ms/im): + - value: 227.27 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_r101_fpn_1x_coco/retinanet_ghm_r101_fpn_1x_coco_20200130-c148ee8f.pth + + - Name: retinanet_x101-32x4d_fpn_ghm-1x_coco + In Collection: GHM + Config: configs/ghm/retinanet_x101-32x4d_fpn_ghm-1x_coco.py + Metadata: + Training Memory (GB): 7.2 + inference time (ms/im): + - value: 196.08 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_x101_32x4d_fpn_1x_coco/retinanet_ghm_x101_32x4d_fpn_1x_coco_20200131-e4333bd0.pth + + - Name: retinanet_x101-64x4d_fpn_ghm-1x_coco + In Collection: GHM + Config: configs/ghm/retinanet_x101-64x4d_fpn_ghm-1x_coco.py + Metadata: + Training Memory (GB): 10.3 + inference time (ms/im): + - value: 192.31 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ghm/retinanet_ghm_x101_64x4d_fpn_1x_coco/retinanet_ghm_x101_64x4d_fpn_1x_coco_20200131-dd381cef.pth diff --git a/mmdetection/configs/ghm/retinanet_r101_fpn_ghm-1x_coco.py b/mmdetection/configs/ghm/retinanet_r101_fpn_ghm-1x_coco.py new file mode 100644 index 00000000..090221e6 --- /dev/null +++ b/mmdetection/configs/ghm/retinanet_r101_fpn_ghm-1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './retinanet_r50_fpn_ghm-1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py b/mmdetection/configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py new file mode 100644 index 00000000..42b9aa6d --- /dev/null +++ b/mmdetection/configs/ghm/retinanet_r50_fpn_ghm-1x_coco.py @@ -0,0 +1,18 @@ +_base_ = '../retinanet/retinanet_r50_fpn_1x_coco.py' +model = dict( + bbox_head=dict( + loss_cls=dict( + _delete_=True, + type='GHMC', + bins=30, + momentum=0.75, + use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict( + _delete_=True, + type='GHMR', + mu=0.02, + bins=10, + momentum=0.7, + loss_weight=10.0))) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/ghm/retinanet_x101-32x4d_fpn_ghm-1x_coco.py b/mmdetection/configs/ghm/retinanet_x101-32x4d_fpn_ghm-1x_coco.py new file mode 100644 index 00000000..1240545a --- /dev/null +++ b/mmdetection/configs/ghm/retinanet_x101-32x4d_fpn_ghm-1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './retinanet_r50_fpn_ghm-1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/ghm/retinanet_x101-64x4d_fpn_ghm-1x_coco.py b/mmdetection/configs/ghm/retinanet_x101-64x4d_fpn_ghm-1x_coco.py new file mode 100644 index 00000000..689d2edc --- /dev/null +++ b/mmdetection/configs/ghm/retinanet_x101-64x4d_fpn_ghm-1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './retinanet_r50_fpn_ghm-1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/glip/README.md b/mmdetection/configs/glip/README.md new file mode 100644 index 00000000..e74e98d1 --- /dev/null +++ b/mmdetection/configs/glip/README.md @@ -0,0 +1,173 @@ +# GLIP: Grounded Language-Image Pre-training + +> [GLIP: Grounded Language-Image Pre-training](https://arxiv.org/abs/2112.03857) + + + +## Abstract + +This paper presents a grounded language-image pre-training (GLIP) model for learning object-level, language-aware, and semantic-rich visual representations. GLIP unifies object detection and phrase grounding for pre-training. The unification brings two benefits: 1) it allows GLIP to learn from both detection and grounding data to improve both tasks and bootstrap a good grounding model; 2) GLIP can leverage massive image-text pairs by generating grounding boxes in a self-training fashion, making the learned representation semantic-rich. In our experiments, we pre-train GLIP on 27M grounding data, including 3M human-annotated and 24M web-crawled image-text pairs. The learned representations demonstrate strong zero-shot and few-shot transferability to various object-level recognition tasks. 1) When directly evaluated on COCO and LVIS (without seeing any images in COCO during pre-training), GLIP achieves 49.8 AP and 26.9 AP, respectively, surpassing many supervised baselines. 2) After fine-tuned on COCO, GLIP achieves 60.8 AP on val and 61.5 AP on test-dev, surpassing prior SoTA. 3) When transferred to 13 downstream object detection tasks, a 1-shot GLIP rivals with a fully-supervised Dynamic Head. + +
    + +
    + +## Installation + +```shell +cd $MMDETROOT + +# source installation +pip install -r requirements/multimodal.txt + +# or mim installation +mim install mmdet[multimodal] +``` + +```shell +cd $MMDETROOT + +wget https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_a_mmdet-b3654169.pth + +python demo/image_demo.py demo/demo.jpg \ +configs/glip/glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py \ +--weights glip_tiny_a_mmdet-b3654169.pth \ +--texts 'bench. car' +``` + +
    + +
    + +## NOTE + +GLIP utilizes BERT as the language model, which requires access to https://huggingface.co/. If you encounter connection errors due to network access, you can download the required files on a computer with internet access and save them locally. Finally, modify the `lang_model_name` field in the config to the local path. Please refer to the following code: + +```python +from transformers import BertConfig, BertModel +from transformers import AutoTokenizer + +config = BertConfig.from_pretrained("bert-base-uncased") +model = BertModel.from_pretrained("bert-base-uncased", add_pooling_layer=False, config=config) +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") + +config.save_pretrained("your path/bert-base-uncased") +model.save_pretrained("your path/bert-base-uncased") +tokenizer.save_pretrained("your path/bert-base-uncased") +``` + +## COCO Results and Models + +| Model | Zero-shot or Finetune | COCO mAP | Official COCO mAP | Pre-Train Data | Config | Download | +| :--------: | :-------------------: | :------: | ----------------: | :------------------------: | :---------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| GLIP-T (A) | Zero-shot | 43.0 | 42.9 | O365 | [config](glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_a_mmdet-b3654169.pth) | +| GLIP-T (A) | Finetune | 53.3 | 52.9 | O365 | [config](glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230914_180419-e6addd96.pth)\| [log](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230914_180419.log.json) | +| GLIP-T (B) | Zero-shot | 44.9 | 44.9 | O365 | [config](glip_atss_swin-t_b_fpn_dyhead_pretrain_obj365.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_b_mmdet-6dfbd102.pth) | +| GLIP-T (B) | Finetune | 54.1 | 53.8 | O365 | [config](glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230916_163538-650323ba.pth)\| [log](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230916_163538.log.json) | +| GLIP-T (C) | Zero-shot | 46.7 | 46.7 | O365,GoldG | [config](glip_atss_swin-t_c_fpn_dyhead_pretrain_obj365-goldg.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_c_mmdet-2fc427dd.pth) | +| GLIP-T (C) | Finetune | 55.2 | 55.1 | O365,GoldG | [config](glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230914_182935-4ba3fc3b.pth)\| [log](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230914_182935.log.json) | +| GLIP-T | Zero-shot | 46.6 | 46.6 | O365,GoldG,CC3M,SBU | [config](glip_atss_swin-t_fpn_dyhead_pretrain_obj365-goldg-cc3m-sub.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_mmdet-c24ce662.pth) | +| GLIP-T | Finetune | 55.4 | 55.2 | O365,GoldG,CC3M,SBU | [config](glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230914_224410-ba97be24.pth)\| [log](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230914_224410.log.json) | +| GLIP-L | Zero-shot | 51.3 | 51.4 | FourODs,GoldG,CC3M+12M,SBU | [config](glip_atss_swin-l_fpn_dyhead_pretrain_mixeddata.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_l_mmdet-abfe026b.pth) | +| GLIP-L | Finetune | 59.4 | | FourODs,GoldG,CC3M+12M,SBU | [config](glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230910_100800-e9be4274.pth)\| [log](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230910_100800.log.json) | + +Note: + +1. The weights corresponding to the zero-shot model are adopted from the official weights and converted using the [script](../../tools/model_converters/glip_to_mmdet.py). We have not retrained the model for the time being. +2. Finetune refers to fine-tuning on the COCO 2017 dataset. The L model is trained using 16 A100 GPUs, while the remaining models are trained using 16 NVIDIA GeForce 3090 GPUs. +3. Taking the GLIP-T(A) model as an example, I trained it twice using the official code, and the fine-tuning mAP were 52.5 and 52.6. Therefore, the mAP we achieved in our reproduction is higher than the official results. The main reason is that we modified the `weight_decay` parameter. +4. Our experiments revealed that training for 24 epochs leads to overfitting. Therefore, we chose the best-performing model. If users want to train on a custom dataset, it is advisable to shorten the number of epochs and save the best-performing model. +5. Due to the official absence of fine-tuning hyperparameters for the GLIP-L model, we have not yet reproduced the official accuracy. I have found that overfitting can also occur, so it may be necessary to consider custom modifications to data augmentation and model enhancement. Given the high cost of training, we have not conducted any research on this matter at the moment. + +## LVIS Results + +| Model | Official | MiniVal APr | MiniVal APc | MiniVal APf | MiniVal AP | Val1.0 APr | Val1.0 APc | Val1.0 APf | Val1.0 AP | Pre-Train Data | Config | Download | +| :--------: | :------: | :---------: | :---------: | :---------: | :--------: | :--------: | :--------: | :--------: | :-------: | :------------------------: | :---------------------------------------------------------------------: | :------------------------------------------------------------------------------------------: | +| GLIP-T (A) | ✔ | | | | | | | | | O365 | [config](lvis/glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_a_mmdet-b3654169.pth) | +| GLIP-T (A) | | 12.1 | 15.5 | 25.8 | 20.2 | 6.2 | 10.9 | 22.8 | 14.7 | O365 | [config](lvis/glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_a_mmdet-b3654169.pth) | +| GLIP-T (B) | ✔ | | | | | | | | | O365 | [config](lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_b_mmdet-6dfbd102.pth) | +| GLIP-T (B) | | 8.6 | 13.9 | 26.0 | 19.3 | 4.6 | 9.8 | 22.6 | 13.9 | O365 | [config](lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_b_mmdet-6dfbd102.pth) | +| GLIP-T (C) | ✔ | 14.3 | 19.4 | 31.1 | 24.6 | | | | | O365,GoldG | [config](lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_c_mmdet-2fc427dd.pth) | +| GLIP-T (C) | | 14.4 | 19.8 | 31.9 | 25.2 | 8.3 | 13.2 | 28.1 | 18.2 | O365,GoldG | [config](lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_c_mmdet-2fc427dd.pth) | +| GLIP-T | ✔ | | | | | | | | | O365,GoldG,CC3M,SBU | [config](lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_mmdet-c24ce662.pth) | +| GLIP-T | | 18.1 | 21.2 | 33.1 | 26.7 | 10.8 | 14.7 | 29.0 | 19.6 | O365,GoldG,CC3M,SBU | [config](lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_mmdet-c24ce662.pth) | +| GLIP-L | ✔ | 29.2 | 34.9 | 42.1 | 37.9 | | | | | FourODs,GoldG,CC3M+12M,SBU | [config](lvis/glip_atss_swin-l_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_l_mmdet-abfe026b.pth) | +| GLIP-L | | 27.9 | 33.7 | 39.7 | 36.1 | 20.2 | 25.8 | 35.3 | 28.5 | FourODs,GoldG,CC3M+12M,SBU | [config](lvis/glip_atss_swin-l_fpn_dyhead_pretrain_zeroshot_lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/glip/glip_l_mmdet-abfe026b.pth) | + +Note: + +1. The above are zero-shot evaluation results. +2. The evaluation metric we used is LVIS FixAP. For specific details, please refer to [Evaluating Large-Vocabulary Object Detectors: The Devil is in the Details](https://arxiv.org/pdf/2102.01066.pdf). +3. We found that the performance on small models is better than the official results, but it is lower on large models. This is mainly due to the incomplete alignment of the GLIP post-processing. + +## ODinW (Object Detection in the Wild) Results + +Learning visual representations from natural language supervision has recently shown great promise in a number of pioneering works. In general, these language-augmented visual models demonstrate strong transferability to a variety of datasets and tasks. However, it remains challenging to evaluate the transferablity of these models due to the lack of easy-to-use evaluation toolkits and public benchmarks. To tackle this, we build ELEVATER 1 , the first benchmark and toolkit for evaluating (pre-trained) language-augmented visual models. ELEVATER is composed of three components. (i) Datasets. As downstream evaluation suites, it consists of 20 image classification datasets and 35 object detection datasets, each of which is augmented with external knowledge. (ii) Toolkit. An automatic hyper-parameter tuning toolkit is developed to facilitate model evaluation on downstream tasks. (iii) Metrics. A variety of evaluation metrics are used to measure sample-efficiency (zero-shot and few-shot) and parameter-efficiency (linear probing and full model fine-tuning). ELEVATER is platform for Computer Vision in the Wild (CVinW), and is publicly released at https://computer-vision-in-the-wild.github.io/ELEVATER/ + +### Results and models of ODinW13 + +| Method | GLIP-T(A) | Official | GLIP-T(B) | Official | GLIP-T(C) | Official | GroundingDINO-T | GroundingDINO-B | +| --------------------- | --------- | --------- | --------- | --------- | --------- | --------- | --------------- | --------------- | +| AerialMaritimeDrone | 0.123 | 0.122 | 0.110 | 0.110 | 0.130 | 0.130 | 0.173 | 0.281 | +| Aquarium | 0.175 | 0.174 | 0.173 | 0.169 | 0.191 | 0.190 | 0.195 | 0.445 | +| CottontailRabbits | 0.686 | 0.686 | 0.688 | 0.688 | 0.744 | 0.744 | 0.799 | 0.808 | +| EgoHands | 0.013 | 0.013 | 0.003 | 0.004 | 0.314 | 0.315 | 0.608 | 0.764 | +| NorthAmericaMushrooms | 0.502 | 0.502 | 0.367 | 0.367 | 0.297 | 0.296 | 0.507 | 0.675 | +| Packages | 0.589 | 0.589 | 0.083 | 0.083 | 0.699 | 0.699 | 0.687 | 0.670 | +| PascalVOC | 0.512 | 0.512 | 0.541 | 0.540 | 0.565 | 0.565 | 0.563 | 0.711 | +| pistols | 0.339 | 0.339 | 0.502 | 0.501 | 0.503 | 0.504 | 0.726 | 0.771 | +| pothole | 0.007 | 0.007 | 0.030 | 0.030 | 0.058 | 0.058 | 0.215 | 0.478 | +| Raccoon | 0.075 | 0.074 | 0.285 | 0.288 | 0.241 | 0.244 | 0.549 | 0.541 | +| ShellfishOpenImages | 0.253 | 0.253 | 0.337 | 0.338 | 0.300 | 0.302 | 0.393 | 0.650 | +| thermalDogsAndPeople | 0.372 | 0.372 | 0.475 | 0.475 | 0.510 | 0.510 | 0.657 | 0.633 | +| VehiclesOpenImages | 0.574 | 0.566 | 0.562 | 0.547 | 0.549 | 0.534 | 0.613 | 0.647 | +| Average | **0.325** | **0.324** | **0.320** | **0.318** | **0.392** | **0.392** | **0.514** | **0.621** | + +### Results and models of ODinW35 + +| Method | GLIP-T(A) | Official | GLIP-T(B) | Official | GLIP-T(C) | Official | GroundingDINO-T | GroundingDINO-B | +| --------------------------- | --------- | --------- | --------- | --------- | --------- | --------- | --------------- | --------------- | +| AerialMaritimeDrone_large | 0.123 | 0.122 | 0.110 | 0.110 | 0.130 | 0.130 | 0.173 | 0.281 | +| AerialMaritimeDrone_tiled | 0.174 | 0.174 | 0.172 | 0.172 | 0.172 | 0.172 | 0.206 | 0.364 | +| AmericanSignLanguageLetters | 0.001 | 0.001 | 0.003 | 0.003 | 0.009 | 0.009 | 0.002 | 0.096 | +| Aquarium | 0.175 | 0.175 | 0.173 | 0.171 | 0.192 | 0.182 | 0.195 | 0.445 | +| BCCD | 0.016 | 0.016 | 0.001 | 0.001 | 0.000 | 0.000 | 0.161 | 0.584 | +| boggleBoards | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.134 | +| brackishUnderwater | 0.016 | 0..013 | 0.021 | 0.027 | 0.020 | 0.022 | 0.021 | 0.454 | +| ChessPieces | 0.001 | 0.001 | 0.000 | 0.000 | 0.001 | 0.001 | 0.000 | 0.000 | +| CottontailRabbits | 0.710 | 0.709 | 0.683 | 0.683 | 0.752 | 0.752 | 0.806 | 0.797 | +| dice | 0.005 | 0.005 | 0.004 | 0.004 | 0.004 | 0.004 | 0.004 | 0.082 | +| DroneControl | 0.016 | 0.017 | 0.006 | 0.008 | 0.005 | 0.007 | 0.042 | 0.638 | +| EgoHands_generic | 0.009 | 0.010 | 0.005 | 0.006 | 0.510 | 0.508 | 0.608 | 0.764 | +| EgoHands_specific | 0.001 | 0.001 | 0.004 | 0.006 | 0.003 | 0.004 | 0.002 | 0.687 | +| HardHatWorkers | 0.029 | 0.029 | 0.023 | 0.023 | 0.033 | 0.033 | 0.046 | 0.439 | +| MaskWearing | 0.007 | 0.007 | 0.003 | 0.002 | 0.005 | 0.005 | 0.004 | 0.406 | +| MountainDewCommercial | 0.218 | 0.227 | 0.199 | 0.197 | 0.478 | 0.463 | 0.430 | 0.580 | +| NorthAmericaMushrooms | 0.502 | 0.502 | 0.450 | 0.450 | 0.497 | 0.497 | 0.471 | 0.501 | +| openPoetryVision | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.051 | +| OxfordPets_by_breed | 0.001 | 0.002 | 0.002 | 0.004 | 0.001 | 0.002 | 0.003 | 0.799 | +| OxfordPets_by_species | 0.016 | 0.011 | 0.012 | 0.009 | 0.013 | 0.009 | 0.011 | 0.872 | +| PKLot | 0.002 | 0.002 | 0.000 | 0.000 | 0.000 | 0.000 | 0.001 | 0.774 | +| Packages | 0.569 | 0.569 | 0.279 | 0.279 | 0.712 | 0.712 | 0.695 | 0.728 | +| PascalVOC | 0.512 | 0.512 | 0.541 | 0.540 | 0.565 | 0.565 | 0.563 | 0.711 | +| pistols | 0.339 | 0.339 | 0.502 | 0.501 | 0.503 | 0.504 | 0.726 | 0.771 | +| plantdoc | 0.002 | 0.002 | 0.007 | 0.007 | 0.009 | 0.009 | 0.005 | 0.376 | +| pothole | 0.007 | 0.010 | 0.024 | 0.025 | 0.085 | 0.101 | 0.215 | 0.478 | +| Raccoons | 0.075 | 0.074 | 0.285 | 0.288 | 0.241 | 0.244 | 0.549 | 0.541 | +| selfdrivingCar | 0.071 | 0.072 | 0.074 | 0.074 | 0.081 | 0.080 | 0.089 | 0.318 | +| ShellfishOpenImages | 0.253 | 0.253 | 0.337 | 0.338 | 0.300 | 0.302 | 0.393 | 0.650 | +| ThermalCheetah | 0.028 | 0.028 | 0.000 | 0.000 | 0.028 | 0.028 | 0.087 | 0.290 | +| thermalDogsAndPeople | 0.372 | 0.372 | 0.475 | 0.475 | 0.510 | 0.510 | 0.657 | 0.633 | +| UnoCards | 0.000 | 0.000 | 0.000 | 0.001 | 0.002 | 0.003 | 0.006 | 0.754 | +| VehiclesOpenImages | 0.574 | 0.566 | 0.562 | 0.547 | 0.549 | 0.534 | 0.613 | 0.647 | +| WildfireSmoke | 0.000 | 0.000 | 0.000 | 0.000 | 0.017 | 0.017 | 0.134 | 0.410 | +| websiteScreenshots | 0.003 | 0.004 | 0.003 | 0.005 | 0.005 | 0.006 | 0.012 | 0.175 | +| Average | **0.134** | **0.134** | **0.138** | **0.138** | **0.179** | **0.178** | **0.227** | **0.492** | + +### Results on Flickr30k + +| Model | Official | Pre-Train Data | Val R@1 | Val R@5 | Val R@10 | Test R@1 | Test R@5 | Test R@10 | +| ------------- | -------- | ------------------- | ------- | ------- | -------- | -------- | -------- | --------- | +| **GLIP-T(C)** | ✔ | O365, GoldG | 84.8 | 94.9 | 96.3 | 85.5 | 95.4 | 96.6 | +| **GLIP-T(C)** | | O365, GoldG | 84.9 | 94.9 | 96.3 | 85.6 | 95.4 | 96.7 | +| **GLIP-T** | | O365,GoldG,CC3M,SBU | 85.3 | 95.5 | 96.9 | 86.0 | 95.9 | 97.2 | diff --git a/mmdetection/configs/glip/flickr30k/glip_atss_swin-t_c_fpn_dyhead_pretrain_obj365-goldg_zeroshot_flickr30k.py b/mmdetection/configs/glip/flickr30k/glip_atss_swin-t_c_fpn_dyhead_pretrain_obj365-goldg_zeroshot_flickr30k.py new file mode 100644 index 00000000..14d6e8aa --- /dev/null +++ b/mmdetection/configs/glip/flickr30k/glip_atss_swin-t_c_fpn_dyhead_pretrain_obj365-goldg_zeroshot_flickr30k.py @@ -0,0 +1,61 @@ +_base_ = '../glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py' + +lang_model_name = 'bert-base-uncased' + +model = dict(bbox_head=dict(early_fuse=True)) + +dataset_type = 'Flickr30kDataset' +data_root = 'data/flickr30k_entities/' + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive', 'phrase_ids', 'phrases')) +] + +dataset_Flickr30k_val = dict( + type=dataset_type, + data_root=data_root, + ann_file='final_flickr_separateGT_val.json', + data_prefix=dict(img='flickr30k_images/'), + pipeline=test_pipeline, +) + +dataset_Flickr30k_test = dict( + type=dataset_type, + data_root=data_root, + ann_file='final_flickr_separateGT_test.json', + data_prefix=dict(img='flickr30k_images/'), + pipeline=test_pipeline, +) + +val_evaluator_Flickr30k = dict(type='Flickr30kMetric', ) + +test_evaluator_Flickr30k = dict(type='Flickr30kMetric', ) + +# ----------Config---------- # +dataset_prefixes = ['Flickr30kVal', 'Flickr30kTest'] +datasets = [dataset_Flickr30k_val, dataset_Flickr30k_test] +metrics = [val_evaluator_Flickr30k, test_evaluator_Flickr30k] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/glip/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco.py b/mmdetection/configs/glip/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco.py new file mode 100644 index 00000000..92a85a11 --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco.py @@ -0,0 +1,14 @@ +_base_ = './glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco.py' + +model = dict( + backbone=dict( + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + drop_path_rate=0.4, + ), + neck=dict(in_channels=[384, 768, 1536]), + bbox_head=dict(early_fuse=True, num_dyhead_blocks=8, use_checkpoint=True)) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/glip/glip_l_mmdet-abfe026b.pth' # noqa diff --git a/mmdetection/configs/glip/glip_atss_swin-l_fpn_dyhead_pretrain_mixeddata.py b/mmdetection/configs/glip/glip_atss_swin-l_fpn_dyhead_pretrain_mixeddata.py new file mode 100644 index 00000000..546ecfe1 --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-l_fpn_dyhead_pretrain_mixeddata.py @@ -0,0 +1,12 @@ +_base_ = './glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py' + +model = dict( + backbone=dict( + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + drop_path_rate=0.4, + ), + neck=dict(in_channels=[384, 768, 1536]), + bbox_head=dict(early_fuse=True, num_dyhead_blocks=8)) diff --git a/mmdetection/configs/glip/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco.py b/mmdetection/configs/glip/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco.py new file mode 100644 index 00000000..4b280657 --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco.py @@ -0,0 +1,155 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_a_mmdet-b3654169.pth' # noqa +lang_model_name = 'bert-base-uncased' + +model = dict( + type='GLIP', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='SwinTransformer', + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False, + convert_weights=False), + neck=dict( + type='FPN_DropBlock', + in_channels=[192, 384, 768], + out_channels=256, + start_level=0, + relu_before_extra_convs=True, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='ATSSVLFusionHead', + lang_model_name=lang_model_name, + num_classes=80, + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128], + center_offset=0.5), + bbox_coder=dict( + type='DeltaXYWHBBoxCoderForGLIP', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + language_model=dict(type='BertModel', name=lang_model_name), + train_cfg=dict( + assigner=dict( + type='ATSSAssigner', + topk=9, + iou_calculator=dict(type='BboxOverlaps2D_GLIP')), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# dataset settings +train_pipeline = [ + dict( + type='LoadImageFromFile', + imdecode_backend='pillow', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='GTBoxSubOne_GLIP'), + dict( + type='RandomChoiceResize', + scales=[(1333, 480), (1333, 560), (1333, 640), (1333, 720), + (1333, 800)], + keep_ratio=True, + resize_type='FixScaleResize', + backend='pillow'), + dict(type='RandomFlip_GLIP', prob=0.5), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +test_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='RepeatDataset', + times=2, + dataset=dict( + type=_base_.dataset_type, + data_root=_base_.data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + return_classes=True, + backend_args=_base_.backend_args))) + +val_dataloader = dict( + dataset=dict(pipeline=test_pipeline, return_classes=True)) +test_dataloader = val_dataloader + +# We did not adopt the official 24e optimizer strategy +# because the results indicate that the current strategy is superior. +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00002, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'relative_position_bias_table': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + }), + clip_grad=None) diff --git a/mmdetection/configs/glip/glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py b/mmdetection/configs/glip/glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py new file mode 100644 index 00000000..34a818ca --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py @@ -0,0 +1,90 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +lang_model_name = 'bert-base-uncased' + +model = dict( + type='GLIP', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='SwinTransformer', + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False, + convert_weights=False), + neck=dict( + type='FPN', + in_channels=[192, 384, 768], + out_channels=256, + start_level=0, + relu_before_extra_convs=True, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='ATSSVLFusionHead', + lang_model_name=lang_model_name, + num_classes=80, + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128], + center_offset=0.5), + bbox_coder=dict( + type='DeltaXYWHBBoxCoderForGLIP', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + ), + language_model=dict(type='BertModel', name=lang_model_name), + train_cfg=dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +test_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities')) +] + +val_dataloader = dict( + dataset=dict(pipeline=test_pipeline, return_classes=True)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/glip/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco.py b/mmdetection/configs/glip/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco.py new file mode 100644 index 00000000..3487de3f --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco.py @@ -0,0 +1,9 @@ +_base_ = './glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco.py' + +model = dict(bbox_head=dict(early_fuse=True, use_checkpoint=True)) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_b_mmdet-6dfbd102.pth' # noqa + +optim_wrapper = dict( + optimizer=dict(lr=0.00001), + clip_grad=dict(_delete_=True, max_norm=1, norm_type=2)) diff --git a/mmdetection/configs/glip/glip_atss_swin-t_b_fpn_dyhead_pretrain_obj365.py b/mmdetection/configs/glip/glip_atss_swin-t_b_fpn_dyhead_pretrain_obj365.py new file mode 100644 index 00000000..6334e5e3 --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-t_b_fpn_dyhead_pretrain_obj365.py @@ -0,0 +1,3 @@ +_base_ = './glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py' + +model = dict(bbox_head=dict(early_fuse=True)) diff --git a/mmdetection/configs/glip/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco.py b/mmdetection/configs/glip/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco.py new file mode 100644 index 00000000..5c315e49 --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco.py @@ -0,0 +1,3 @@ +_base_ = './glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco.py' + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_c_mmdet-2fc427dd.pth' # noqa diff --git a/mmdetection/configs/glip/glip_atss_swin-t_c_fpn_dyhead_pretrain_obj365-goldg.py b/mmdetection/configs/glip/glip_atss_swin-t_c_fpn_dyhead_pretrain_obj365-goldg.py new file mode 100644 index 00000000..24898f4d --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-t_c_fpn_dyhead_pretrain_obj365-goldg.py @@ -0,0 +1 @@ +_base_ = './glip_atss_swin-t_b_fpn_dyhead_pretrain_obj365.py' diff --git a/mmdetection/configs/glip/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco.py b/mmdetection/configs/glip/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco.py new file mode 100644 index 00000000..3391272e --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco.py @@ -0,0 +1,3 @@ +_base_ = './glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco.py' + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_mmdet-c24ce662.pth' # noqa diff --git a/mmdetection/configs/glip/glip_atss_swin-t_fpn_dyhead_pretrain_obj365-goldg-cc3m-sub.py b/mmdetection/configs/glip/glip_atss_swin-t_fpn_dyhead_pretrain_obj365-goldg-cc3m-sub.py new file mode 100644 index 00000000..24898f4d --- /dev/null +++ b/mmdetection/configs/glip/glip_atss_swin-t_fpn_dyhead_pretrain_obj365-goldg-cc3m-sub.py @@ -0,0 +1 @@ +_base_ = './glip_atss_swin-t_b_fpn_dyhead_pretrain_obj365.py' diff --git a/mmdetection/configs/glip/lvis/glip_atss_swin-l_fpn_dyhead_pretrain_zeroshot_lvis.py b/mmdetection/configs/glip/lvis/glip_atss_swin-l_fpn_dyhead_pretrain_zeroshot_lvis.py new file mode 100644 index 00000000..1f79e447 --- /dev/null +++ b/mmdetection/configs/glip/lvis/glip_atss_swin-l_fpn_dyhead_pretrain_zeroshot_lvis.py @@ -0,0 +1,12 @@ +_base_ = './glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_lvis.py' + +model = dict( + backbone=dict( + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + drop_path_rate=0.4, + ), + neck=dict(in_channels=[384, 768, 1536]), + bbox_head=dict(early_fuse=True, num_dyhead_blocks=8)) diff --git a/mmdetection/configs/glip/lvis/glip_atss_swin-l_fpn_dyhead_pretrain_zeroshot_mini-lvis.py b/mmdetection/configs/glip/lvis/glip_atss_swin-l_fpn_dyhead_pretrain_zeroshot_mini-lvis.py new file mode 100644 index 00000000..13f1a690 --- /dev/null +++ b/mmdetection/configs/glip/lvis/glip_atss_swin-l_fpn_dyhead_pretrain_zeroshot_mini-lvis.py @@ -0,0 +1,12 @@ +_base_ = './glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_mini-lvis.py' + +model = dict( + backbone=dict( + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + drop_path_rate=0.4, + ), + neck=dict(in_channels=[384, 768, 1536]), + bbox_head=dict(early_fuse=True, num_dyhead_blocks=8)) diff --git a/mmdetection/configs/glip/lvis/glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_lvis.py b/mmdetection/configs/glip/lvis/glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_lvis.py new file mode 100644 index 00000000..4d526d59 --- /dev/null +++ b/mmdetection/configs/glip/lvis/glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_lvis.py @@ -0,0 +1,24 @@ +_base_ = '../glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py' + +model = dict(test_cfg=dict( + max_per_img=300, + chunked_size=40, +)) + +dataset_type = 'LVISV1Dataset' +data_root = 'data/coco/' + +val_dataloader = dict( + dataset=dict( + data_root=data_root, + type=dataset_type, + ann_file='annotations/lvis_od_val.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +# numpy < 1.24.0 +val_evaluator = dict( + _delete_=True, + type='LVISFixedAPMetric', + ann_file=data_root + 'annotations/lvis_od_val.json') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/glip/lvis/glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_mini-lvis.py b/mmdetection/configs/glip/lvis/glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_mini-lvis.py new file mode 100644 index 00000000..70a57a3f --- /dev/null +++ b/mmdetection/configs/glip/lvis/glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_mini-lvis.py @@ -0,0 +1,25 @@ +_base_ = '../glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py' + +model = dict(test_cfg=dict( + max_per_img=300, + chunked_size=40, +)) + +dataset_type = 'LVISV1Dataset' +data_root = 'data/coco/' + +val_dataloader = dict( + dataset=dict( + data_root=data_root, + type=dataset_type, + ann_file='annotations/lvis_v1_minival_inserted_image_name.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +# numpy < 1.24.0 +val_evaluator = dict( + _delete_=True, + type='LVISFixedAPMetric', + ann_file=data_root + + 'annotations/lvis_v1_minival_inserted_image_name.json') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/glip/lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_lvis.py b/mmdetection/configs/glip/lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_lvis.py new file mode 100644 index 00000000..6dc712b3 --- /dev/null +++ b/mmdetection/configs/glip/lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_lvis.py @@ -0,0 +1,3 @@ +_base_ = './glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_lvis.py' + +model = dict(bbox_head=dict(early_fuse=True)) diff --git a/mmdetection/configs/glip/lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_mini-lvis.py b/mmdetection/configs/glip/lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_mini-lvis.py new file mode 100644 index 00000000..3babb911 --- /dev/null +++ b/mmdetection/configs/glip/lvis/glip_atss_swin-t_bc_fpn_dyhead_pretrain_zeroshot_mini-lvis.py @@ -0,0 +1,3 @@ +_base_ = './glip_atss_swin-t_a_fpn_dyhead_pretrain_zeroshot_mini-lvis.py' + +model = dict(bbox_head=dict(early_fuse=True)) diff --git a/mmdetection/configs/glip/metafile.yml b/mmdetection/configs/glip/metafile.yml new file mode 100644 index 00000000..fbbf718b --- /dev/null +++ b/mmdetection/configs/glip/metafile.yml @@ -0,0 +1,111 @@ +Collections: + - Name: GLIP + Metadata: + Training Data: Objects365, GoldG, CC3M, SBU and COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: A100 GPUs + Architecture: + - Swin Transformer + - DYHead + - BERT + Paper: + URL: https://arxiv.org/abs/2112.03857 + Title: 'GLIP: Grounded Language-Image Pre-training' + README: configs/glip/README.md + Code: + URL: + Version: v3.0.0 + +Models: + - Name: glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365 + In Collection: GLIP + Config: configs/glip/glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.0 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_a_mmdet-b3654169.pth + - Name: glip_atss_swin-t_b_fpn_dyhead_pretrain_obj365 + In Collection: GLIP + Config: configs/glip/glip_atss_swin-t_b_fpn_dyhead_pretrain_obj365.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_b_mmdet-6dfbd102.pth + - Name: glip_atss_swin-t_c_fpn_dyhead_pretrain_obj365-goldg + In Collection: GLIP + Config: configs/glip/glip_atss_swin-t_c_fpn_dyhead_pretrain_obj365-goldg.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.7 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_c_mmdet-2fc427dd.pth + - Name: glip_atss_swin-t_fpn_dyhead_pretrain_obj365-goldg-cc3m-sub + In Collection: GLIP + Config: configs/glip/glip_atss_swin-t_fpn_dyhead_pretrain_obj365-goldg-cc3m-sub.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_mmdet-c24ce662.pth + - Name: glip_atss_swin-l_fpn_dyhead_pretrain_mixeddata + In Collection: GLIP + Config: configs/glip/glip_atss_swin-l_fpn_dyhead_pretrain_mixeddata.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 51.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_l_mmdet-abfe026b.pth + - Name: glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco + In Collection: GLIP + Config: configs/glip/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 53.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_a_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230914_180419-e6addd96.pth + - Name: glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco + In Collection: GLIP + Config: configs/glip/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 54.1 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_b_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230916_163538-650323ba.pth + - Name: glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco + In Collection: GLIP + Config: configs/glip/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 55.2 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_c_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230914_182935-4ba3fc3b.pth + - Name: glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco + In Collection: GLIP + Config: configs/glip/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 55.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-t_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230914_224410-ba97be24.pth + - Name: glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco + In Collection: GLIP + Config: configs/glip/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 59.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/glip/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco/glip_atss_swin-l_fpn_dyhead_16xb2_ms-2x_funtune_coco_20230910_100800-e9be4274.pth diff --git a/mmdetection/configs/glip/odinw/glip_atss_swin-t_a_fpn_dyhead_pretrain_odinw13.py b/mmdetection/configs/glip/odinw/glip_atss_swin-t_a_fpn_dyhead_pretrain_odinw13.py new file mode 100644 index 00000000..d38effba --- /dev/null +++ b/mmdetection/configs/glip/odinw/glip_atss_swin-t_a_fpn_dyhead_pretrain_odinw13.py @@ -0,0 +1,338 @@ +_base_ = '../glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py' + +dataset_type = 'CocoDataset' +data_root = 'data/odinw/' + +base_test_pipeline = _base_.test_pipeline +base_test_pipeline[-1]['meta_keys'] = ('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'text', + 'custom_entities', 'caption_prompt') + +# ---------------------1 AerialMaritimeDrone---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/large/' +dataset_AerialMaritimeDrone = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + test_mode=True, + pipeline=base_test_pipeline, + return_classes=True) +val_evaluator_AerialMaritimeDrone = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------2 Aquarium---------------------# +class_name = ('fish', 'jellyfish', 'penguin', 'puffin', 'shark', 'starfish', + 'stingray') +metainfo = dict(classes=class_name) +_data_root = data_root + 'Aquarium/Aquarium Combined.v2-raw-1024.coco/' + +caption_prompt = None +# caption_prompt = { +# 'penguin': { +# 'suffix': ', which is black and white' +# }, +# 'puffin': { +# 'suffix': ' with orange beaks' +# }, +# 'stingray': { +# 'suffix': ' which is flat and round' +# }, +# } +dataset_Aquarium = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Aquarium = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------3 CottontailRabbits---------------------# +class_name = ('Cottontail-Rabbit', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'CottontailRabbits/' + +caption_prompt = None +# caption_prompt = {'Cottontail-Rabbit': {'name': 'rabbit'}} + +dataset_CottontailRabbits = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_CottontailRabbits = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------4 EgoHands---------------------# +class_name = ('hand', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/generic/' + +caption_prompt = None +# caption_prompt = {'hand': {'suffix': ' of a person'}} + +dataset_EgoHands = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------5 NorthAmericaMushrooms---------------------# +class_name = ('CoW', 'chanterelle') +metainfo = dict(classes=class_name) +_data_root = data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa + +caption_prompt = None +# caption_prompt = { +# 'CoW': { +# 'name': 'flat mushroom' +# }, +# 'chanterelle': { +# 'name': 'yellow mushroom' +# } +# } + +dataset_NorthAmericaMushrooms = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_NorthAmericaMushrooms = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------6 Packages---------------------# +class_name = ('package', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Packages/Raw/' + +caption_prompt = None +# caption_prompt = { +# 'package': { +# 'prefix': 'there is a ', +# 'suffix': ' on the porch' +# } +# } + +dataset_Packages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Packages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------7 PascalVOC---------------------# +class_name = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', + 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PascalVOC/' +dataset_PascalVOC = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PascalVOC = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------8 pistols---------------------# +class_name = ('pistol', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pistols/export/' +dataset_pistols = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pistols = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------9 pothole---------------------# +class_name = ('pothole', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pothole/' + +caption_prompt = None +# caption_prompt = { +# 'pothole': { +# 'prefix': 'there are some ', +# 'name': 'holes', +# 'suffix': ' on the road' +# } +# } + +dataset_pothole = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pothole = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------10 Raccoon---------------------# +class_name = ('raccoon', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Raccoon/Raccoon.v2-raw.coco/' +dataset_Raccoon = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Raccoon = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------11 ShellfishOpenImages---------------------# +class_name = ('Crab', 'Lobster', 'Shrimp') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ShellfishOpenImages/raw/' +dataset_ShellfishOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ShellfishOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------12 thermalDogsAndPeople---------------------# +class_name = ('dog', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'thermalDogsAndPeople/' +dataset_thermalDogsAndPeople = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_thermalDogsAndPeople = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------13 VehiclesOpenImages---------------------# +class_name = ('Ambulance', 'Bus', 'Car', 'Motorcycle', 'Truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'VehiclesOpenImages/416x416/' +dataset_VehiclesOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_VehiclesOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# --------------------- Config---------------------# +dataset_prefixes = [ + 'AerialMaritimeDrone', 'Aquarium', 'CottontailRabbits', 'EgoHands', + 'NorthAmericaMushrooms', 'Packages', 'PascalVOC', 'pistols', 'pothole', + 'Raccoon', 'ShellfishOpenImages', 'thermalDogsAndPeople', + 'VehiclesOpenImages' +] +datasets = [ + dataset_AerialMaritimeDrone, dataset_Aquarium, dataset_CottontailRabbits, + dataset_EgoHands, dataset_NorthAmericaMushrooms, dataset_Packages, + dataset_PascalVOC, dataset_pistols, dataset_pothole, dataset_Raccoon, + dataset_ShellfishOpenImages, dataset_thermalDogsAndPeople, + dataset_VehiclesOpenImages +] +metrics = [ + val_evaluator_AerialMaritimeDrone, val_evaluator_Aquarium, + val_evaluator_CottontailRabbits, val_evaluator_EgoHands, + val_evaluator_NorthAmericaMushrooms, val_evaluator_Packages, + val_evaluator_PascalVOC, val_evaluator_pistols, val_evaluator_pothole, + val_evaluator_Raccoon, val_evaluator_ShellfishOpenImages, + val_evaluator_thermalDogsAndPeople, val_evaluator_VehiclesOpenImages +] + +# -------------------------------------------------# +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/glip/odinw/glip_atss_swin-t_a_fpn_dyhead_pretrain_odinw35.py b/mmdetection/configs/glip/odinw/glip_atss_swin-t_a_fpn_dyhead_pretrain_odinw35.py new file mode 100644 index 00000000..2eaf09ed --- /dev/null +++ b/mmdetection/configs/glip/odinw/glip_atss_swin-t_a_fpn_dyhead_pretrain_odinw35.py @@ -0,0 +1,794 @@ +_base_ = '../glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365.py' + +dataset_type = 'CocoDataset' +data_root = 'data/odinw/' + +base_test_pipeline = _base_.test_pipeline +base_test_pipeline[-1]['meta_keys'] = ('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'text', + 'custom_entities', 'caption_prompt') + +# ---------------------1 AerialMaritimeDrone_large---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/large/' +dataset_AerialMaritimeDrone_large = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AerialMaritimeDrone_large = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------2 AerialMaritimeDrone_tiled---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/tiled/' +dataset_AerialMaritimeDrone_tiled = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AerialMaritimeDrone_tiled = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------3 AmericanSignLanguageLetters---------------------# +class_name = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AmericanSignLanguageLetters/American Sign Language Letters.v1-v1.coco/' # noqa +dataset_AmericanSignLanguageLetters = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AmericanSignLanguageLetters = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------4 Aquarium---------------------# +class_name = ('fish', 'jellyfish', 'penguin', 'puffin', 'shark', 'starfish', + 'stingray') +metainfo = dict(classes=class_name) +_data_root = data_root + 'Aquarium/Aquarium Combined.v2-raw-1024.coco/' +dataset_Aquarium = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Aquarium = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------5 BCCD---------------------# +class_name = ('Platelets', 'RBC', 'WBC') +metainfo = dict(classes=class_name) +_data_root = data_root + 'BCCD/BCCD.v3-raw.coco/' +dataset_BCCD = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_BCCD = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------6 boggleBoards---------------------# +class_name = ('Q', 'a', 'an', 'b', 'c', 'd', 'e', 'er', 'f', 'g', 'h', 'he', + 'i', 'in', 'j', 'k', 'l', 'm', 'n', 'o', 'o ', 'p', 'q', 'qu', + 'r', 's', 't', 't\\', 'th', 'u', 'v', 'w', 'wild', 'x', 'y', 'z') +metainfo = dict(classes=class_name) +_data_root = data_root + 'boggleBoards/416x416AutoOrient/export/' +dataset_boggleBoards = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_boggleBoards = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------7 brackishUnderwater---------------------# +class_name = ('crab', 'fish', 'jellyfish', 'shrimp', 'small_fish', 'starfish') +metainfo = dict(classes=class_name) +_data_root = data_root + 'brackishUnderwater/960x540/' +dataset_brackishUnderwater = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_brackishUnderwater = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------8 ChessPieces---------------------# +class_name = (' ', 'black bishop', 'black king', 'black knight', 'black pawn', + 'black queen', 'black rook', 'white bishop', 'white king', + 'white knight', 'white pawn', 'white queen', 'white rook') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ChessPieces/Chess Pieces.v23-raw.coco/' +dataset_ChessPieces = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ChessPieces = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------9 CottontailRabbits---------------------# +class_name = ('rabbit', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'CottontailRabbits/' +dataset_CottontailRabbits = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_CottontailRabbits = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------10 dice---------------------# +class_name = ('1', '2', '3', '4', '5', '6') +metainfo = dict(classes=class_name) +_data_root = data_root + 'dice/mediumColor/export/' +dataset_dice = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_dice = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------11 DroneControl---------------------# +class_name = ('follow', 'follow_hand', 'land', 'land_hand', 'null', 'object', + 'takeoff', 'takeoff-hand') +metainfo = dict(classes=class_name) +_data_root = data_root + 'DroneControl/Drone Control.v3-raw.coco/' +dataset_DroneControl = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_DroneControl = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------12 EgoHands_generic---------------------# +class_name = ('hand', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/generic/' +caption_prompt = {'hand': {'suffix': ' of a person'}} +dataset_EgoHands_generic = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands_generic = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------13 EgoHands_specific---------------------# +class_name = ('myleft', 'myright', 'yourleft', 'yourright') +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/specific/' +dataset_EgoHands_specific = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands_specific = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------14 HardHatWorkers---------------------# +class_name = ('head', 'helmet', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'HardHatWorkers/raw/' +dataset_HardHatWorkers = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_HardHatWorkers = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------15 MaskWearing---------------------# +class_name = ('mask', 'no-mask') +metainfo = dict(classes=class_name) +_data_root = data_root + 'MaskWearing/raw/' +dataset_MaskWearing = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_MaskWearing = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------16 MountainDewCommercial---------------------# +class_name = ('bottle', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'MountainDewCommercial/' +dataset_MountainDewCommercial = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_MountainDewCommercial = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------17 NorthAmericaMushrooms---------------------# +class_name = ('flat mushroom', 'yellow mushroom') +metainfo = dict(classes=class_name) +_data_root = data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa +dataset_NorthAmericaMushrooms = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_NorthAmericaMushrooms = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------18 openPoetryVision---------------------# +class_name = ('American Typewriter', 'Andale Mono', 'Apple Chancery', 'Arial', + 'Avenir', 'Baskerville', 'Big Caslon', 'Bradley Hand', + 'Brush Script MT', 'Chalkboard', 'Comic Sans MS', 'Copperplate', + 'Courier', 'Didot', 'Futura', 'Geneva', 'Georgia', 'Gill Sans', + 'Helvetica', 'Herculanum', 'Impact', 'Kefa', 'Lucida Grande', + 'Luminari', 'Marker Felt', 'Menlo', 'Monaco', 'Noteworthy', + 'Optima', 'PT Sans', 'PT Serif', 'Palatino', 'Papyrus', + 'Phosphate', 'Rockwell', 'SF Pro', 'SignPainter', 'Skia', + 'Snell Roundhand', 'Tahoma', 'Times New Roman', 'Trebuchet MS', + 'Verdana') +metainfo = dict(classes=class_name) +_data_root = data_root + 'openPoetryVision/512x512/' +dataset_openPoetryVision = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_openPoetryVision = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------19 OxfordPets_by_breed---------------------# +class_name = ('cat-Abyssinian', 'cat-Bengal', 'cat-Birman', 'cat-Bombay', + 'cat-British_Shorthair', 'cat-Egyptian_Mau', 'cat-Maine_Coon', + 'cat-Persian', 'cat-Ragdoll', 'cat-Russian_Blue', 'cat-Siamese', + 'cat-Sphynx', 'dog-american_bulldog', + 'dog-american_pit_bull_terrier', 'dog-basset_hound', + 'dog-beagle', 'dog-boxer', 'dog-chihuahua', + 'dog-english_cocker_spaniel', 'dog-english_setter', + 'dog-german_shorthaired', 'dog-great_pyrenees', 'dog-havanese', + 'dog-japanese_chin', 'dog-keeshond', 'dog-leonberger', + 'dog-miniature_pinscher', 'dog-newfoundland', 'dog-pomeranian', + 'dog-pug', 'dog-saint_bernard', 'dog-samoyed', + 'dog-scottish_terrier', 'dog-shiba_inu', + 'dog-staffordshire_bull_terrier', 'dog-wheaten_terrier', + 'dog-yorkshire_terrier') +metainfo = dict(classes=class_name) +_data_root = data_root + 'OxfordPets/by-breed/' # noqa +dataset_OxfordPets_by_breed = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_OxfordPets_by_breed = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------20 OxfordPets_by_species---------------------# +class_name = ('cat', 'dog') +metainfo = dict(classes=class_name) +_data_root = data_root + 'OxfordPets/by-species/' # noqa +dataset_OxfordPets_by_species = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_OxfordPets_by_species = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------21 PKLot---------------------# +class_name = ('space-empty', 'space-occupied') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PKLot/640/' # noqa +dataset_PKLot = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PKLot = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------22 Packages---------------------# +class_name = ('package', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Packages/Raw/' +caption_prompt = { + 'package': { + 'prefix': 'there is a ', + 'suffix': ' on the porch' + } +} +dataset_Packages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Packages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------23 PascalVOC---------------------# +class_name = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', + 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PascalVOC/' +dataset_PascalVOC = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PascalVOC = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------24 pistols---------------------# +class_name = ('pistol', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pistols/export/' +dataset_pistols = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pistols = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------25 plantdoc---------------------# +class_name = ('Apple Scab Leaf', 'Apple leaf', 'Apple rust leaf', + 'Bell_pepper leaf', 'Bell_pepper leaf spot', 'Blueberry leaf', + 'Cherry leaf', 'Corn Gray leaf spot', 'Corn leaf blight', + 'Corn rust leaf', 'Peach leaf', 'Potato leaf', + 'Potato leaf early blight', 'Potato leaf late blight', + 'Raspberry leaf', 'Soyabean leaf', 'Soybean leaf', + 'Squash Powdery mildew leaf', 'Strawberry leaf', + 'Tomato Early blight leaf', 'Tomato Septoria leaf spot', + 'Tomato leaf', 'Tomato leaf bacterial spot', + 'Tomato leaf late blight', 'Tomato leaf mosaic virus', + 'Tomato leaf yellow virus', 'Tomato mold leaf', + 'Tomato two spotted spider mites leaf', 'grape leaf', + 'grape leaf black rot') +metainfo = dict(classes=class_name) +_data_root = data_root + 'plantdoc/416x416/' +dataset_plantdoc = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_plantdoc = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------26 pothole---------------------# +class_name = ('pothole', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pothole/' +caption_prompt = { + 'pothole': { + 'name': 'holes', + 'prefix': 'there are some ', + 'suffix': ' on the road' + } +} +dataset_pothole = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + caption_prompt=caption_prompt, + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pothole = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------27 Raccoon---------------------# +class_name = ('raccoon', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Raccoon/Raccoon.v2-raw.coco/' +dataset_Raccoon = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Raccoon = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------28 selfdrivingCar---------------------# +class_name = ('biker', 'car', 'pedestrian', 'trafficLight', + 'trafficLight-Green', 'trafficLight-GreenLeft', + 'trafficLight-Red', 'trafficLight-RedLeft', + 'trafficLight-Yellow', 'trafficLight-YellowLeft', 'truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'selfdrivingCar/fixedLarge/export/' +dataset_selfdrivingCar = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_selfdrivingCar = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------29 ShellfishOpenImages---------------------# +class_name = ('Crab', 'Lobster', 'Shrimp') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ShellfishOpenImages/raw/' +dataset_ShellfishOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ShellfishOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------30 ThermalCheetah---------------------# +class_name = ('cheetah', 'human') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ThermalCheetah/' +dataset_ThermalCheetah = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ThermalCheetah = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------31 thermalDogsAndPeople---------------------# +class_name = ('dog', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'thermalDogsAndPeople/' +dataset_thermalDogsAndPeople = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_thermalDogsAndPeople = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------32 UnoCards---------------------# +class_name = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', + '12', '13', '14') +metainfo = dict(classes=class_name) +_data_root = data_root + 'UnoCards/raw/' +dataset_UnoCards = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_UnoCards = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------33 VehiclesOpenImages---------------------# +class_name = ('Ambulance', 'Bus', 'Car', 'Motorcycle', 'Truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'VehiclesOpenImages/416x416/' +dataset_VehiclesOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_VehiclesOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------34 WildfireSmoke---------------------# +class_name = ('smoke', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'WildfireSmoke/' +dataset_WildfireSmoke = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_WildfireSmoke = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------35 websiteScreenshots---------------------# +class_name = ('button', 'field', 'heading', 'iframe', 'image', 'label', 'link', + 'text') +metainfo = dict(classes=class_name) +_data_root = data_root + 'websiteScreenshots/' +dataset_websiteScreenshots = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_websiteScreenshots = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# --------------------- Config---------------------# + +dataset_prefixes = [ + 'AerialMaritimeDrone_large', + 'AerialMaritimeDrone_tiled', + 'AmericanSignLanguageLetters', + 'Aquarium', + 'BCCD', + 'boggleBoards', + 'brackishUnderwater', + 'ChessPieces', + 'CottontailRabbits', + 'dice', + 'DroneControl', + 'EgoHands_generic', + 'EgoHands_specific', + 'HardHatWorkers', + 'MaskWearing', + 'MountainDewCommercial', + 'NorthAmericaMushrooms', + 'openPoetryVision', + 'OxfordPets_by_breed', + 'OxfordPets_by_species', + 'PKLot', + 'Packages', + 'PascalVOC', + 'pistols', + 'plantdoc', + 'pothole', + 'Raccoons', + 'selfdrivingCar', + 'ShellfishOpenImages', + 'ThermalCheetah', + 'thermalDogsAndPeople', + 'UnoCards', + 'VehiclesOpenImages', + 'WildfireSmoke', + 'websiteScreenshots', +] + +datasets = [ + dataset_AerialMaritimeDrone_large, dataset_AerialMaritimeDrone_tiled, + dataset_AmericanSignLanguageLetters, dataset_Aquarium, dataset_BCCD, + dataset_boggleBoards, dataset_brackishUnderwater, dataset_ChessPieces, + dataset_CottontailRabbits, dataset_dice, dataset_DroneControl, + dataset_EgoHands_generic, dataset_EgoHands_specific, + dataset_HardHatWorkers, dataset_MaskWearing, dataset_MountainDewCommercial, + dataset_NorthAmericaMushrooms, dataset_openPoetryVision, + dataset_OxfordPets_by_breed, dataset_OxfordPets_by_species, dataset_PKLot, + dataset_Packages, dataset_PascalVOC, dataset_pistols, dataset_plantdoc, + dataset_pothole, dataset_Raccoon, dataset_selfdrivingCar, + dataset_ShellfishOpenImages, dataset_ThermalCheetah, + dataset_thermalDogsAndPeople, dataset_UnoCards, dataset_VehiclesOpenImages, + dataset_WildfireSmoke, dataset_websiteScreenshots +] + +metrics = [ + val_evaluator_AerialMaritimeDrone_large, + val_evaluator_AerialMaritimeDrone_tiled, + val_evaluator_AmericanSignLanguageLetters, val_evaluator_Aquarium, + val_evaluator_BCCD, val_evaluator_boggleBoards, + val_evaluator_brackishUnderwater, val_evaluator_ChessPieces, + val_evaluator_CottontailRabbits, val_evaluator_dice, + val_evaluator_DroneControl, val_evaluator_EgoHands_generic, + val_evaluator_EgoHands_specific, val_evaluator_HardHatWorkers, + val_evaluator_MaskWearing, val_evaluator_MountainDewCommercial, + val_evaluator_NorthAmericaMushrooms, val_evaluator_openPoetryVision, + val_evaluator_OxfordPets_by_breed, val_evaluator_OxfordPets_by_species, + val_evaluator_PKLot, val_evaluator_Packages, val_evaluator_PascalVOC, + val_evaluator_pistols, val_evaluator_plantdoc, val_evaluator_pothole, + val_evaluator_Raccoon, val_evaluator_selfdrivingCar, + val_evaluator_ShellfishOpenImages, val_evaluator_ThermalCheetah, + val_evaluator_thermalDogsAndPeople, val_evaluator_UnoCards, + val_evaluator_VehiclesOpenImages, val_evaluator_WildfireSmoke, + val_evaluator_websiteScreenshots +] + +# -------------------------------------------------# +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/glip/odinw/glip_atss_swin-t_bc_fpn_dyhead_pretrain_odinw13.py b/mmdetection/configs/glip/odinw/glip_atss_swin-t_bc_fpn_dyhead_pretrain_odinw13.py new file mode 100644 index 00000000..c3479b62 --- /dev/null +++ b/mmdetection/configs/glip/odinw/glip_atss_swin-t_bc_fpn_dyhead_pretrain_odinw13.py @@ -0,0 +1,3 @@ +_base_ = './glip_atss_swin-t_a_fpn_dyhead_pretrain_odinw13.py' + +model = dict(bbox_head=dict(early_fuse=True)) diff --git a/mmdetection/configs/glip/odinw/glip_atss_swin-t_bc_fpn_dyhead_pretrain_odinw35.py b/mmdetection/configs/glip/odinw/glip_atss_swin-t_bc_fpn_dyhead_pretrain_odinw35.py new file mode 100644 index 00000000..182afc66 --- /dev/null +++ b/mmdetection/configs/glip/odinw/glip_atss_swin-t_bc_fpn_dyhead_pretrain_odinw35.py @@ -0,0 +1,3 @@ +_base_ = './glip_atss_swin-t_a_fpn_dyhead_pretrain_odinw35.py' + +model = dict(bbox_head=dict(early_fuse=True)) diff --git a/mmdetection/configs/glip/odinw/override_category.py b/mmdetection/configs/glip/odinw/override_category.py new file mode 100644 index 00000000..9ff05fc6 --- /dev/null +++ b/mmdetection/configs/glip/odinw/override_category.py @@ -0,0 +1,109 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse + +import mmengine + + +def parse_args(): + parser = argparse.ArgumentParser(description='Override Category') + parser.add_argument('data_root') + return parser.parse_args() + + +def main(): + args = parse_args() + + ChessPieces = [{ + 'id': 1, + 'name': ' ', + 'supercategory': 'pieces' + }, { + 'id': 2, + 'name': 'black bishop', + 'supercategory': 'pieces' + }, { + 'id': 3, + 'name': 'black king', + 'supercategory': 'pieces' + }, { + 'id': 4, + 'name': 'black knight', + 'supercategory': 'pieces' + }, { + 'id': 5, + 'name': 'black pawn', + 'supercategory': 'pieces' + }, { + 'id': 6, + 'name': 'black queen', + 'supercategory': 'pieces' + }, { + 'id': 7, + 'name': 'black rook', + 'supercategory': 'pieces' + }, { + 'id': 8, + 'name': 'white bishop', + 'supercategory': 'pieces' + }, { + 'id': 9, + 'name': 'white king', + 'supercategory': 'pieces' + }, { + 'id': 10, + 'name': 'white knight', + 'supercategory': 'pieces' + }, { + 'id': 11, + 'name': 'white pawn', + 'supercategory': 'pieces' + }, { + 'id': 12, + 'name': 'white queen', + 'supercategory': 'pieces' + }, { + 'id': 13, + 'name': 'white rook', + 'supercategory': 'pieces' + }] + + _data_root = args.data_root + 'ChessPieces/Chess Pieces.v23-raw.coco/' + json_data = mmengine.load(_data_root + + 'valid/annotations_without_background.json') + json_data['categories'] = ChessPieces + mmengine.dump(json_data, + _data_root + 'valid/new_annotations_without_background.json') + + CottontailRabbits = [{ + 'id': 1, + 'name': 'rabbit', + 'supercategory': 'Cottontail-Rabbit' + }] + + _data_root = args.data_root + 'CottontailRabbits/' + json_data = mmengine.load(_data_root + + 'valid/annotations_without_background.json') + json_data['categories'] = CottontailRabbits + mmengine.dump(json_data, + _data_root + 'valid/new_annotations_without_background.json') + + NorthAmericaMushrooms = [{ + 'id': 1, + 'name': 'flat mushroom', + 'supercategory': 'mushroom' + }, { + 'id': 2, + 'name': 'yellow mushroom', + 'supercategory': 'mushroom' + }] + + _data_root = args.data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa + json_data = mmengine.load(_data_root + + 'valid/annotations_without_background.json') + json_data['categories'] = NorthAmericaMushrooms + mmengine.dump(json_data, + _data_root + 'valid/new_annotations_without_background.json') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/configs/gn+ws/README.md b/mmdetection/configs/gn+ws/README.md new file mode 100644 index 00000000..ef8cfc81 --- /dev/null +++ b/mmdetection/configs/gn+ws/README.md @@ -0,0 +1,54 @@ +# GN + WS + +> [Weight Standardization](https://arxiv.org/abs/1903.10520) + + + +## Abstract + +Batch Normalization (BN) has become an out-of-box technique to improve deep network training. However, its effectiveness is limited for micro-batch training, i.e., each GPU typically has only 1-2 images for training, which is inevitable for many computer vision tasks, e.g., object detection and semantic segmentation, constrained by memory consumption. To address this issue, we propose Weight Standardization (WS) and Batch-Channel Normalization (BCN) to bring two success factors of BN into micro-batch training: 1) the smoothing effects on the loss landscape and 2) the ability to avoid harmful elimination singularities along the training trajectory. WS standardizes the weights in convolutional layers to smooth the loss landscape by reducing the Lipschitz constants of the loss and the gradients; BCN combines batch and channel normalizations and leverages estimated statistics of the activations in convolutional layers to keep networks away from elimination singularities. We validate WS and BCN on comprehensive computer vision tasks, including image classification, object detection, instance segmentation, video recognition and semantic segmentation. All experimental results consistently show that WS and BCN improve micro-batch training significantly. Moreover, using WS and BCN with micro-batch training is even able to match or outperform the performances of BN with large-batch training. + +
    + +
    + +## Results and Models + +Faster R-CNN + +| Backbone | Style | Normalization | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------------: | :-----: | :-----------: | :-----: | :------: | :------------: | :----: | :-----: | :---------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | GN+WS | 1x | 5.9 | 11.7 | 39.7 | - | [config](./faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_r50_fpn_gn_ws-all_1x_coco/faster_rcnn_r50_fpn_gn_ws-all_1x_coco_20200130-613d9fe2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_r50_fpn_gn_ws-all_1x_coco/faster_rcnn_r50_fpn_gn_ws-all_1x_coco_20200130_210936.log.json) | +| R-101-FPN | pytorch | GN+WS | 1x | 8.9 | 9.0 | 41.7 | - | [config](./faster-rcnn_r101_fpn_gn-ws-all_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_r101_fpn_gn_ws-all_1x_coco/faster_rcnn_r101_fpn_gn_ws-all_1x_coco_20200205-a93b0d75.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_r101_fpn_gn_ws-all_1x_coco/faster_rcnn_r101_fpn_gn_ws-all_1x_coco_20200205_232146.log.json) | +| X-50-32x4d-FPN | pytorch | GN+WS | 1x | 7.0 | 10.3 | 40.7 | - | [config](./faster-rcnn_x50-32x4d_fpn_gn-ws-all_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_x50_32x4d_fpn_gn_ws-all_1x_coco/faster_rcnn_x50_32x4d_fpn_gn_ws-all_1x_coco_20200203-839c5d9d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_x50_32x4d_fpn_gn_ws-all_1x_coco/faster_rcnn_x50_32x4d_fpn_gn_ws-all_1x_coco_20200203_220113.log.json) | +| X-101-32x4d-FPN | pytorch | GN+WS | 1x | 10.8 | 7.6 | 42.1 | - | [config](./faster-rcnn_x101-32x4d_fpn_gn-ws-all_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_x101_32x4d_fpn_gn_ws-all_1x_coco/faster_rcnn_x101_32x4d_fpn_gn_ws-all_1x_coco_20200212-27da1bc2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_x101_32x4d_fpn_gn_ws-all_1x_coco/faster_rcnn_x101_32x4d_fpn_gn_ws-all_1x_coco_20200212_195302.log.json) | + +Mask R-CNN + +| Backbone | Style | Normalization | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------------: | :-----: | :-----------: | :-------: | :------: | :------------: | :----: | :-----: | :--------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | GN+WS | 2x | 7.3 | 10.5 | 40.6 | 36.6 | [config](./mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r50_fpn_gn_ws-all_2x_coco/mask_rcnn_r50_fpn_gn_ws-all_2x_coco_20200226-16acb762.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r50_fpn_gn_ws-all_2x_coco/mask_rcnn_r50_fpn_gn_ws-all_2x_coco_20200226_062128.log.json) | +| R-101-FPN | pytorch | GN+WS | 2x | 10.3 | 8.6 | 42.0 | 37.7 | [config](./mask-rcnn_r101_fpn_gn-ws-all_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r101_fpn_gn_ws-all_2x_coco/mask_rcnn_r101_fpn_gn_ws-all_2x_coco_20200212-ea357cd9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r101_fpn_gn_ws-all_2x_coco/mask_rcnn_r101_fpn_gn_ws-all_2x_coco_20200212_213627.log.json) | +| X-50-32x4d-FPN | pytorch | GN+WS | 2x | 8.4 | 9.3 | 41.1 | 37.0 | [config](./mask-rcnn_x50-32x4d_fpn_gn-ws-all_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x50_32x4d_fpn_gn_ws-all_2x_coco/mask_rcnn_x50_32x4d_fpn_gn_ws-all_2x_coco_20200216-649fdb6f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x50_32x4d_fpn_gn_ws-all_2x_coco/mask_rcnn_x50_32x4d_fpn_gn_ws-all_2x_coco_20200216_201500.log.json) | +| X-101-32x4d-FPN | pytorch | GN+WS | 2x | 12.2 | 7.1 | 42.1 | 37.9 | [config](./mask-rcnn_x101-32x4d_fpn_gn-ws-all_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x101_32x4d_fpn_gn_ws-all_2x_coco/mask_rcnn_x101_32x4d_fpn_gn_ws-all_2x_coco_20200319-33fb95b5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x101_32x4d_fpn_gn_ws-all_2x_coco/mask_rcnn_x101_32x4d_fpn_gn_ws-all_2x_coco_20200319_104101.log.json) | +| R-50-FPN | pytorch | GN+WS | 20-23-24e | 7.3 | - | 41.1 | 37.1 | [config](./mask-rcnn_r50_fpn_gn-ws-all_20-23-24e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r50_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_r50_fpn_gn_ws-all_20_23_24e_coco_20200213-487d1283.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r50_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_r50_fpn_gn_ws-all_20_23_24e_coco_20200213_035123.log.json) | +| R-101-FPN | pytorch | GN+WS | 20-23-24e | 10.3 | - | 43.1 | 38.6 | [config](./mask-rcnn_r101_fpn_gn-ws-all_20-23-24e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r101_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_r101_fpn_gn_ws-all_20_23_24e_coco_20200213-57b5a50f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r101_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_r101_fpn_gn_ws-all_20_23_24e_coco_20200213_130142.log.json) | +| X-50-32x4d-FPN | pytorch | GN+WS | 20-23-24e | 8.4 | - | 42.1 | 38.0 | [config](./mask-rcnn_x50-32x4d_fpn_gn-ws-all_20-23-24e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x50_32x4d_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_x50_32x4d_fpn_gn_ws-all_20_23_24e_coco_20200226-969bcb2c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x50_32x4d_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_x50_32x4d_fpn_gn_ws-all_20_23_24e_coco_20200226_093732.log.json) | +| X-101-32x4d-FPN | pytorch | GN+WS | 20-23-24e | 12.2 | - | 42.7 | 38.5 | [config](./mask-rcnn_x101-32x4d_fpn_gn-ws-all_20-23-24e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x101_32x4d_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_x101_32x4d_fpn_gn_ws-all_20_23_24e_coco_20200316-e6cd35ef.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x101_32x4d_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_x101_32x4d_fpn_gn_ws-all_20_23_24e_coco_20200316_013741.log.json) | + +Note: + +- GN+WS requires about 5% more memory than GN, and it is only 5% slower than GN. +- In the paper, a 20-23-24e lr schedule is used instead of 2x. +- The X-50-GN and X-101-GN pretrained models are also shared by the authors. + +## Citation + +```latex +@article{weightstandardization, + author = {Siyuan Qiao and Huiyu Wang and Chenxi Liu and Wei Shen and Alan Yuille}, + title = {Weight Standardization}, + journal = {arXiv preprint arXiv:1903.10520}, + year = {2019}, +} +``` diff --git a/mmdetection/configs/gn+ws/faster-rcnn_r101_fpn_gn-ws-all_1x_coco.py b/mmdetection/configs/gn+ws/faster-rcnn_r101_fpn_gn-ws-all_1x_coco.py new file mode 100644 index 00000000..a4cb8281 --- /dev/null +++ b/mmdetection/configs/gn+ws/faster-rcnn_r101_fpn_gn-ws-all_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://jhu/resnet101_gn_ws'))) diff --git a/mmdetection/configs/gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py b/mmdetection/configs/gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py new file mode 100644 index 00000000..1a044c99 --- /dev/null +++ b/mmdetection/configs/gn+ws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +conv_cfg = dict(type='ConvWS') +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://jhu/resnet50_gn_ws')), + neck=dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg), + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg))) diff --git a/mmdetection/configs/gn+ws/faster-rcnn_x101-32x4d_fpn_gn-ws-all_1x_coco.py b/mmdetection/configs/gn+ws/faster-rcnn_x101-32x4d_fpn_gn-ws-all_1x_coco.py new file mode 100644 index 00000000..b2a317d2 --- /dev/null +++ b/mmdetection/configs/gn+ws/faster-rcnn_x101-32x4d_fpn_gn-ws-all_1x_coco.py @@ -0,0 +1,18 @@ +_base_ = './faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py' +conv_cfg = dict(type='ConvWS') +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + style='pytorch', + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://jhu/resnext101_32x4d_gn_ws'))) diff --git a/mmdetection/configs/gn+ws/faster-rcnn_x50-32x4d_fpn_gn-ws-all_1x_coco.py b/mmdetection/configs/gn+ws/faster-rcnn_x50-32x4d_fpn_gn-ws-all_1x_coco.py new file mode 100644 index 00000000..dd75a2c0 --- /dev/null +++ b/mmdetection/configs/gn+ws/faster-rcnn_x50-32x4d_fpn_gn-ws-all_1x_coco.py @@ -0,0 +1,18 @@ +_base_ = './faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py' +conv_cfg = dict(type='ConvWS') +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + type='ResNeXt', + depth=50, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + style='pytorch', + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://jhu/resnext50_32x4d_gn_ws'))) diff --git a/mmdetection/configs/gn+ws/mask-rcnn_r101_fpn_gn-ws-all_20-23-24e_coco.py b/mmdetection/configs/gn+ws/mask-rcnn_r101_fpn_gn-ws-all_20-23-24e_coco.py new file mode 100644 index 00000000..1815e3f8 --- /dev/null +++ b/mmdetection/configs/gn+ws/mask-rcnn_r101_fpn_gn-ws-all_20-23-24e_coco.py @@ -0,0 +1,17 @@ +_base_ = './mask-rcnn_r101_fpn_gn-ws-all_2x_coco.py' +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[20, 23], + gamma=0.1) +] diff --git a/mmdetection/configs/gn+ws/mask-rcnn_r101_fpn_gn-ws-all_2x_coco.py b/mmdetection/configs/gn+ws/mask-rcnn_r101_fpn_gn-ws-all_2x_coco.py new file mode 100644 index 00000000..5de37dee --- /dev/null +++ b/mmdetection/configs/gn+ws/mask-rcnn_r101_fpn_gn-ws-all_2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://jhu/resnet101_gn_ws'))) diff --git a/mmdetection/configs/gn+ws/mask-rcnn_r50_fpn_gn-ws-all_20-23-24e_coco.py b/mmdetection/configs/gn+ws/mask-rcnn_r50_fpn_gn-ws-all_20-23-24e_coco.py new file mode 100644 index 00000000..287c6520 --- /dev/null +++ b/mmdetection/configs/gn+ws/mask-rcnn_r50_fpn_gn-ws-all_20-23-24e_coco.py @@ -0,0 +1,17 @@ +_base_ = './mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py' +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[20, 23], + gamma=0.1) +] diff --git a/mmdetection/configs/gn+ws/mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py b/mmdetection/configs/gn+ws/mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py new file mode 100644 index 00000000..ed8b1b73 --- /dev/null +++ b/mmdetection/configs/gn+ws/mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py @@ -0,0 +1,33 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +conv_cfg = dict(type='ConvWS') +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://jhu/resnet50_gn_ws')), + neck=dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg), + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg), + mask_head=dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg))) +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/gn+ws/mask-rcnn_x101-32x4d_fpn_gn-ws-all_20-23-24e_coco.py b/mmdetection/configs/gn+ws/mask-rcnn_x101-32x4d_fpn_gn-ws-all_20-23-24e_coco.py new file mode 100644 index 00000000..8ce91935 --- /dev/null +++ b/mmdetection/configs/gn+ws/mask-rcnn_x101-32x4d_fpn_gn-ws-all_20-23-24e_coco.py @@ -0,0 +1,17 @@ +_base_ = './mask-rcnn_x101-32x4d_fpn_gn-ws-all_2x_coco.py' +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[20, 23], + gamma=0.1) +] diff --git a/mmdetection/configs/gn+ws/mask-rcnn_x101-32x4d_fpn_gn-ws-all_2x_coco.py b/mmdetection/configs/gn+ws/mask-rcnn_x101-32x4d_fpn_gn-ws-all_2x_coco.py new file mode 100644 index 00000000..bcfc371e --- /dev/null +++ b/mmdetection/configs/gn+ws/mask-rcnn_x101-32x4d_fpn_gn-ws-all_2x_coco.py @@ -0,0 +1,19 @@ +_base_ = './mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py' +# model settings +conv_cfg = dict(type='ConvWS') +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + style='pytorch', + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://jhu/resnext101_32x4d_gn_ws'))) diff --git a/mmdetection/configs/gn+ws/mask-rcnn_x50-32x4d_fpn_gn-ws-all_20-23-24e_coco.py b/mmdetection/configs/gn+ws/mask-rcnn_x50-32x4d_fpn_gn-ws-all_20-23-24e_coco.py new file mode 100644 index 00000000..af9ea5ab --- /dev/null +++ b/mmdetection/configs/gn+ws/mask-rcnn_x50-32x4d_fpn_gn-ws-all_20-23-24e_coco.py @@ -0,0 +1,17 @@ +_base_ = './mask-rcnn_x50-32x4d_fpn_gn-ws-all_2x_coco.py' +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[20, 23], + gamma=0.1) +] diff --git a/mmdetection/configs/gn+ws/mask-rcnn_x50-32x4d_fpn_gn-ws-all_2x_coco.py b/mmdetection/configs/gn+ws/mask-rcnn_x50-32x4d_fpn_gn-ws-all_2x_coco.py new file mode 100644 index 00000000..ab2b1404 --- /dev/null +++ b/mmdetection/configs/gn+ws/mask-rcnn_x50-32x4d_fpn_gn-ws-all_2x_coco.py @@ -0,0 +1,19 @@ +_base_ = './mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py' +# model settings +conv_cfg = dict(type='ConvWS') +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + type='ResNeXt', + depth=50, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + style='pytorch', + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://jhu/resnext50_32x4d_gn_ws'))) diff --git a/mmdetection/configs/gn+ws/metafile.yml b/mmdetection/configs/gn+ws/metafile.yml new file mode 100644 index 00000000..89b91072 --- /dev/null +++ b/mmdetection/configs/gn+ws/metafile.yml @@ -0,0 +1,263 @@ +Collections: + - Name: Weight Standardization + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Group Normalization + - Weight Standardization + Paper: + URL: https://arxiv.org/abs/1903.10520 + Title: 'Weight Standardization' + README: configs/gn+ws/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/configs/gn%2Bws/mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py + Version: v2.0.0 + +Models: + - Name: faster-rcnn_r50_fpn_gn_ws-all_1x_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/faster-rcnn_r50_fpn_gn-ws-all_1x_coco.py + Metadata: + Training Memory (GB): 5.9 + inference time (ms/im): + - value: 85.47 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_r50_fpn_gn_ws-all_1x_coco/faster_rcnn_r50_fpn_gn_ws-all_1x_coco_20200130-613d9fe2.pth + + - Name: faster-rcnn_r101_fpn_gn-ws-all_1x_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/faster-rcnn_r101_fpn_gn-ws-all_1x_coco.py + Metadata: + Training Memory (GB): 8.9 + inference time (ms/im): + - value: 111.11 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_r101_fpn_gn_ws-all_1x_coco/faster_rcnn_r101_fpn_gn_ws-all_1x_coco_20200205-a93b0d75.pth + + - Name: faster-rcnn_x50-32x4d_fpn_gn-ws-all_1x_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/faster-rcnn_x50-32x4d_fpn_gn-ws-all_1x_coco.py + Metadata: + Training Memory (GB): 7.0 + inference time (ms/im): + - value: 97.09 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_x50_32x4d_fpn_gn_ws-all_1x_coco/faster_rcnn_x50_32x4d_fpn_gn_ws-all_1x_coco_20200203-839c5d9d.pth + + - Name: faster-rcnn_x101-32x4d_fpn_gn-ws-all_1x_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/faster-rcnn_x101-32x4d_fpn_gn-ws-all_1x_coco.py + Metadata: + Training Memory (GB): 10.8 + inference time (ms/im): + - value: 131.58 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/faster_rcnn_x101_32x4d_fpn_gn_ws-all_1x_coco/faster_rcnn_x101_32x4d_fpn_gn_ws-all_1x_coco_20200212-27da1bc2.pth + + - Name: mask-rcnn_r50_fpn_gn_ws-all_2x_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/mask-rcnn_r50_fpn_gn-ws-all_2x_coco.py + Metadata: + Training Memory (GB): 7.3 + inference time (ms/im): + - value: 95.24 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r50_fpn_gn_ws-all_2x_coco/mask_rcnn_r50_fpn_gn_ws-all_2x_coco_20200226-16acb762.pth + + - Name: mask-rcnn_r101_fpn_gn-ws-all_2x_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/mask-rcnn_r101_fpn_gn-ws-all_2x_coco.py + Metadata: + Training Memory (GB): 10.3 + inference time (ms/im): + - value: 116.28 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r101_fpn_gn_ws-all_2x_coco/mask_rcnn_r101_fpn_gn_ws-all_2x_coco_20200212-ea357cd9.pth + + - Name: mask-rcnn_x50-32x4d_fpn_gn-ws-all_2x_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/mask-rcnn_x50-32x4d_fpn_gn-ws-all_2x_coco.py + Metadata: + Training Memory (GB): 8.4 + inference time (ms/im): + - value: 107.53 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x50_32x4d_fpn_gn_ws-all_2x_coco/mask_rcnn_x50_32x4d_fpn_gn_ws-all_2x_coco_20200216-649fdb6f.pth + + - Name: mask-rcnn_x101-32x4d_fpn_gn-ws-all_2x_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/mask-rcnn_x101-32x4d_fpn_gn-ws-all_2x_coco.py + Metadata: + Training Memory (GB): 12.2 + inference time (ms/im): + - value: 140.85 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x101_32x4d_fpn_gn_ws-all_2x_coco/mask_rcnn_x101_32x4d_fpn_gn_ws-all_2x_coco_20200319-33fb95b5.pth + + - Name: mask-rcnn_r50_fpn_gn_ws-all_20_23_24e_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/mask-rcnn_r50_fpn_gn-ws-all_20-23-24e_coco.py + Metadata: + Training Memory (GB): 7.3 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r50_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_r50_fpn_gn_ws-all_20_23_24e_coco_20200213-487d1283.pth + + - Name: mask-rcnn_r101_fpn_gn-ws-all_20-23-24e_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/mask-rcnn_r101_fpn_gn-ws-all_20-23-24e_coco.py + Metadata: + Training Memory (GB): 10.3 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_r101_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_r101_fpn_gn_ws-all_20_23_24e_coco_20200213-57b5a50f.pth + + - Name: mask-rcnn_x50-32x4d_fpn_gn-ws-all_20-23-24e_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/mask-rcnn_x50-32x4d_fpn_gn-ws-all_20-23-24e_coco.py + Metadata: + Training Memory (GB): 8.4 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x50_32x4d_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_x50_32x4d_fpn_gn_ws-all_20_23_24e_coco_20200226-969bcb2c.pth + + - Name: mask-rcnn_x101-32x4d_fpn_gn-ws-all_20-23-24e_coco + In Collection: Weight Standardization + Config: configs/gn%2Bws/mask-rcnn_x101-32x4d_fpn_gn-ws-all_20-23-24e_coco.py + Metadata: + Training Memory (GB): 12.2 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn%2Bws/mask_rcnn_x101_32x4d_fpn_gn_ws-all_20_23_24e_coco/mask_rcnn_x101_32x4d_fpn_gn_ws-all_20_23_24e_coco_20200316-e6cd35ef.pth diff --git a/mmdetection/configs/gn/README.md b/mmdetection/configs/gn/README.md new file mode 100644 index 00000000..1bc8192f --- /dev/null +++ b/mmdetection/configs/gn/README.md @@ -0,0 +1,41 @@ +# GN + +> [Group Normalization](https://arxiv.org/abs/1803.08494) + + + +## Abstract + +Batch Normalization (BN) is a milestone technique in the development of deep learning, enabling various networks to train. However, normalizing along the batch dimension introduces problems --- BN's error increases rapidly when the batch size becomes smaller, caused by inaccurate batch statistics estimation. This limits BN's usage for training larger models and transferring features to computer vision tasks including detection, segmentation, and video, which require small batches constrained by memory consumption. In this paper, we present Group Normalization (GN) as a simple alternative to BN. GN divides the channels into groups and computes within each group the mean and variance for normalization. GN's computation is independent of batch sizes, and its accuracy is stable in a wide range of batch sizes. On ResNet-50 trained in ImageNet, GN has 10.6% lower error than its BN counterpart when using a batch size of 2; when using typical batch sizes, GN is comparably good with BN and outperforms other normalization variants. Moreover, GN can be naturally transferred from pre-training to fine-tuning. GN can outperform its BN-based counterparts for object detection and segmentation in COCO, and for video classification in Kinetics, showing that GN can effectively replace the powerful BN in a variety of tasks. GN can be easily implemented by a few lines of code in modern libraries. + +
    + +
    + +## Results and Models + +| Backbone | model | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-----------: | :--------: | :-----: | :------: | :------------: | :----: | :-----: | :-----------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN (d) | Mask R-CNN | 2x | 7.1 | 11.0 | 40.2 | 36.4 | [config](./mask-rcnn_r50_fpn_gn-all_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_2x_coco/mask_rcnn_r50_fpn_gn-all_2x_coco_20200206-8eee02a6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_2x_coco/mask_rcnn_r50_fpn_gn-all_2x_coco_20200206_050355.log.json) | +| R-50-FPN (d) | Mask R-CNN | 3x | 7.1 | - | 40.5 | 36.7 | [config](./mask-rcnn_r50_fpn_gn-all_3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_3x_coco/mask_rcnn_r50_fpn_gn-all_3x_coco_20200214-8b23b1e5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_3x_coco/mask_rcnn_r50_fpn_gn-all_3x_coco_20200214_063512.log.json) | +| R-101-FPN (d) | Mask R-CNN | 2x | 9.9 | 9.0 | 41.9 | 37.6 | [config](./mask-rcnn_r101_fpn_gn-all_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r101_fpn_gn-all_2x_coco/mask_rcnn_r101_fpn_gn-all_2x_coco_20200205-d96b1b50.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r101_fpn_gn-all_2x_coco/mask_rcnn_r101_fpn_gn-all_2x_coco_20200205_234402.log.json) | +| R-101-FPN (d) | Mask R-CNN | 3x | 9.9 | | 42.1 | 38.0 | [config](./mask-rcnn_r101_fpn_gn-all_3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r101_fpn_gn-all_3x_coco/mask_rcnn_r101_fpn_gn-all_3x_coco_20200513_181609-0df864f4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r101_fpn_gn-all_3x_coco/mask_rcnn_r101_fpn_gn-all_3x_coco_20200513_181609.log.json) | +| R-50-FPN (c) | Mask R-CNN | 2x | 7.1 | 10.9 | 40.0 | 36.1 | [config](./mask-rcnn_r50-contrib_fpn_gn-all_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_contrib_2x_coco/mask_rcnn_r50_fpn_gn-all_contrib_2x_coco_20200207-20d3e849.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_contrib_2x_coco/mask_rcnn_r50_fpn_gn-all_contrib_2x_coco_20200207_225832.log.json) | +| R-50-FPN (c) | Mask R-CNN | 3x | 7.1 | - | 40.1 | 36.2 | [config](./mask-rcnn_r50-contrib_fpn_gn-all_3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_contrib_3x_coco/mask_rcnn_r50_fpn_gn-all_contrib_3x_coco_20200225-542aefbc.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_contrib_3x_coco/mask_rcnn_r50_fpn_gn-all_contrib_3x_coco_20200225_235135.log.json) | + +**Notes:** + +- (d) means pretrained model converted from Detectron, and (c) means the contributed model pretrained by [@thangvubk](https://github.com/thangvubk). +- The `3x` schedule is epoch \[28, 34, 36\]. +- **Memory, Train/Inf time is outdated.** + +## Citation + +```latex +@inproceedings{wu2018group, + title={Group Normalization}, + author={Wu, Yuxin and He, Kaiming}, + booktitle={Proceedings of the European Conference on Computer Vision (ECCV)}, + year={2018} +} +``` diff --git a/mmdetection/configs/gn/mask-rcnn_r101_fpn_gn-all_2x_coco.py b/mmdetection/configs/gn/mask-rcnn_r101_fpn_gn-all_2x_coco.py new file mode 100644 index 00000000..54f57d8d --- /dev/null +++ b/mmdetection/configs/gn/mask-rcnn_r101_fpn_gn-all_2x_coco.py @@ -0,0 +1,7 @@ +_base_ = './mask-rcnn_r50_fpn_gn-all_2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron/resnet101_gn'))) diff --git a/mmdetection/configs/gn/mask-rcnn_r101_fpn_gn-all_3x_coco.py b/mmdetection/configs/gn/mask-rcnn_r101_fpn_gn-all_3x_coco.py new file mode 100644 index 00000000..a94e063e --- /dev/null +++ b/mmdetection/configs/gn/mask-rcnn_r101_fpn_gn-all_3x_coco.py @@ -0,0 +1,18 @@ +_base_ = './mask-rcnn_r101_fpn_gn-all_2x_coco.py' + +# learning policy +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] diff --git a/mmdetection/configs/gn/mask-rcnn_r50-contrib_fpn_gn-all_2x_coco.py b/mmdetection/configs/gn/mask-rcnn_r50-contrib_fpn_gn-all_2x_coco.py new file mode 100644 index 00000000..5515ec14 --- /dev/null +++ b/mmdetection/configs/gn/mask-rcnn_r50-contrib_fpn_gn-all_2x_coco.py @@ -0,0 +1,31 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + norm_cfg=norm_cfg, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://contrib/resnet50_gn')), + neck=dict(norm_cfg=norm_cfg), + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=norm_cfg), + mask_head=dict(norm_cfg=norm_cfg))) + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/gn/mask-rcnn_r50-contrib_fpn_gn-all_3x_coco.py b/mmdetection/configs/gn/mask-rcnn_r50-contrib_fpn_gn-all_3x_coco.py new file mode 100644 index 00000000..e6f7a97e --- /dev/null +++ b/mmdetection/configs/gn/mask-rcnn_r50-contrib_fpn_gn-all_3x_coco.py @@ -0,0 +1,18 @@ +_base_ = './mask-rcnn_r50-contrib_fpn_gn-all_2x_coco.py' + +# learning policy +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] diff --git a/mmdetection/configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py b/mmdetection/configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py new file mode 100644 index 00000000..1313b22e --- /dev/null +++ b/mmdetection/configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py @@ -0,0 +1,36 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=norm_cfg, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron/resnet50_gn')), + neck=dict(norm_cfg=norm_cfg), + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=norm_cfg), + mask_head=dict(norm_cfg=norm_cfg))) + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/gn/mask-rcnn_r50_fpn_gn-all_3x_coco.py b/mmdetection/configs/gn/mask-rcnn_r50_fpn_gn-all_3x_coco.py new file mode 100644 index 00000000..e425de95 --- /dev/null +++ b/mmdetection/configs/gn/mask-rcnn_r50_fpn_gn-all_3x_coco.py @@ -0,0 +1,18 @@ +_base_ = './mask-rcnn_r50_fpn_gn-all_2x_coco.py' + +# learning policy +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] diff --git a/mmdetection/configs/gn/metafile.yml b/mmdetection/configs/gn/metafile.yml new file mode 100644 index 00000000..9781dc93 --- /dev/null +++ b/mmdetection/configs/gn/metafile.yml @@ -0,0 +1,162 @@ +Collections: + - Name: Group Normalization + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Group Normalization + Paper: + URL: https://arxiv.org/abs/1803.08494 + Title: 'Group Normalization' + README: configs/gn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py + Version: v2.0.0 + +Models: + - Name: mask-rcnn_r50_fpn_gn-all_2x_coco + In Collection: Group Normalization + Config: configs/gn/mask-rcnn_r50_fpn_gn-all_2x_coco.py + Metadata: + Training Memory (GB): 7.1 + inference time (ms/im): + - value: 90.91 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_2x_coco/mask_rcnn_r50_fpn_gn-all_2x_coco_20200206-8eee02a6.pth + + - Name: mask-rcnn_r50_fpn_gn-all_3x_coco + In Collection: Group Normalization + Config: configs/gn/mask-rcnn_r50_fpn_gn-all_3x_coco.py + Metadata: + Training Memory (GB): 7.1 + inference time (ms/im): + - value: 90.91 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_3x_coco/mask_rcnn_r50_fpn_gn-all_3x_coco_20200214-8b23b1e5.pth + + - Name: mask-rcnn_r101_fpn_gn-all_2x_coco + In Collection: Group Normalization + Config: configs/gn/mask-rcnn_r101_fpn_gn-all_2x_coco.py + Metadata: + Training Memory (GB): 9.9 + inference time (ms/im): + - value: 111.11 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r101_fpn_gn-all_2x_coco/mask_rcnn_r101_fpn_gn-all_2x_coco_20200205-d96b1b50.pth + + - Name: mask-rcnn_r101_fpn_gn-all_3x_coco + In Collection: Group Normalization + Config: configs/gn/mask-rcnn_r101_fpn_gn-all_3x_coco.py + Metadata: + Training Memory (GB): 9.9 + inference time (ms/im): + - value: 111.11 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r101_fpn_gn-all_3x_coco/mask_rcnn_r101_fpn_gn-all_3x_coco_20200513_181609-0df864f4.pth + + - Name: mask-rcnn_r50_fpn_gn-all_contrib_2x_coco + In Collection: Group Normalization + Config: configs/gn/mask-rcnn_r50-contrib_fpn_gn-all_2x_coco.py + Metadata: + Training Memory (GB): 7.1 + inference time (ms/im): + - value: 91.74 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_contrib_2x_coco/mask_rcnn_r50_fpn_gn-all_contrib_2x_coco_20200207-20d3e849.pth + + - Name: mask-rcnn_r50_fpn_gn-all_contrib_3x_coco + In Collection: Group Normalization + Config: configs/gn/mask-rcnn_r50-contrib_fpn_gn-all_3x_coco.py + Metadata: + Training Memory (GB): 7.1 + inference time (ms/im): + - value: 91.74 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/gn/mask_rcnn_r50_fpn_gn-all_contrib_3x_coco/mask_rcnn_r50_fpn_gn-all_contrib_3x_coco_20200225-542aefbc.pth diff --git a/mmdetection/configs/grid_rcnn/README.md b/mmdetection/configs/grid_rcnn/README.md new file mode 100644 index 00000000..3de810af --- /dev/null +++ b/mmdetection/configs/grid_rcnn/README.md @@ -0,0 +1,47 @@ +# Grid R-CNN + +> [Grid R-CNN](https://arxiv.org/abs/1811.12030) + + + +## Abstract + +This paper proposes a novel object detection framework named Grid R-CNN, which adopts a grid guided localization mechanism for accurate object detection. Different from the traditional regression based methods, the Grid R-CNN captures the spatial information explicitly and enjoys the position sensitive property of fully convolutional architecture. Instead of using only two independent points, we design a multi-point supervision formulation to encode more clues in order to reduce the impact of inaccurate prediction of specific points. To take the full advantage of the correlation of points in a grid, we propose a two-stage information fusion strategy to fuse feature maps of neighbor grid points. The grid guided localization approach is easy to be extended to different state-of-the-art detection frameworks. Grid R-CNN leads to high quality object localization, and experiments demonstrate that it achieves a 4.1% AP gain at IoU=0.8 and a 10.0% AP gain at IoU=0.9 on COCO benchmark compared to Faster R-CNN with Res50 backbone and FPN architecture. + +Grid R-CNN is a well-performed objection detection framework. It transforms the traditional box offset regression problem into a grid point estimation problem. With the guidance of the grid points, it can obtain high-quality localization results. However, the speed of Grid R-CNN is not so satisfactory. In this technical report we present Grid R-CNN Plus, a better and faster version of Grid R-CNN. We have made several updates that significantly speed up the framework and simultaneously improve the accuracy. On COCO dataset, the Res50-FPN based Grid R-CNN Plus detector achieves an mAP of 40.4%, outperforming the baseline on the same model by 3.0 points with similar inference time. + +
    + +
    + +## Results and Models + +| Backbone | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :---------: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | 2x | 5.1 | 15.0 | 40.4 | [config](./grid-rcnn_r50_fpn_gn-head_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_r50_fpn_gn-head_2x_coco/grid_rcnn_r50_fpn_gn-head_2x_coco_20200130-6cca8223.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_r50_fpn_gn-head_2x_coco/grid_rcnn_r50_fpn_gn-head_2x_coco_20200130_221140.log.json) | +| R-101 | 2x | 7.0 | 12.6 | 41.5 | [config](./grid-rcnn_r101_fpn_gn-head_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_r101_fpn_gn-head_2x_coco/grid_rcnn_r101_fpn_gn-head_2x_coco_20200309-d6eca030.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_r101_fpn_gn-head_2x_coco/grid_rcnn_r101_fpn_gn-head_2x_coco_20200309_164224.log.json) | +| X-101-32x4d | 2x | 8.3 | 10.8 | 42.9 | [config](./grid-rcnn_x101-32x4d_fpn_gn-head_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_x101_32x4d_fpn_gn-head_2x_coco/grid_rcnn_x101_32x4d_fpn_gn-head_2x_coco_20200130-d8f0e3ff.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_x101_32x4d_fpn_gn-head_2x_coco/grid_rcnn_x101_32x4d_fpn_gn-head_2x_coco_20200130_215413.log.json) | +| X-101-64x4d | 2x | 11.3 | 7.7 | 43.0 | [config](./grid-rcnn_x101-64x4d_fpn_gn-head_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_x101_64x4d_fpn_gn-head_2x_coco/grid_rcnn_x101_64x4d_fpn_gn-head_2x_coco_20200204-ec76a754.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_x101_64x4d_fpn_gn-head_2x_coco/grid_rcnn_x101_64x4d_fpn_gn-head_2x_coco_20200204_080641.log.json) | + +**Notes:** + +- All models are trained with 8 GPUs instead of 32 GPUs in the original paper. +- The warming up lasts for 1 epoch and `2x` here indicates 25 epochs. + +## Citation + +```latex +@inproceedings{lu2019grid, + title={Grid r-cnn}, + author={Lu, Xin and Li, Buyu and Yue, Yuxin and Li, Quanquan and Yan, Junjie}, + booktitle={Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition}, + year={2019} +} + +@article{lu2019grid, + title={Grid R-CNN Plus: Faster and Better}, + author={Lu, Xin and Li, Buyu and Yue, Yuxin and Li, Quanquan and Yan, Junjie}, + journal={arXiv preprint arXiv:1906.05688}, + year={2019} +} +``` diff --git a/mmdetection/configs/grid_rcnn/grid-rcnn_r101_fpn_gn-head_2x_coco.py b/mmdetection/configs/grid_rcnn/grid-rcnn_r101_fpn_gn-head_2x_coco.py new file mode 100644 index 00000000..46d41ed4 --- /dev/null +++ b/mmdetection/configs/grid_rcnn/grid-rcnn_r101_fpn_gn-head_2x_coco.py @@ -0,0 +1,7 @@ +_base_ = './grid-rcnn_r50_fpn_gn-head_2x_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_1x_coco.py b/mmdetection/configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_1x_coco.py new file mode 100644 index 00000000..35828063 --- /dev/null +++ b/mmdetection/configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_1x_coco.py @@ -0,0 +1,19 @@ +_base_ = './grid-rcnn_r50_fpn_gn-head_2x_coco.py' + +# training schedule +max_epochs = 12 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.0001, by_epoch=False, begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] diff --git a/mmdetection/configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py b/mmdetection/configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py new file mode 100644 index 00000000..228fca23 --- /dev/null +++ b/mmdetection/configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py @@ -0,0 +1,160 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + type='GridRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + type='GridRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=dict( + type='Shared2FCBBoxHead', + with_reg=False, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False), + grid_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + grid_head=dict( + type='GridHead', + grid_points=9, + num_convs=8, + in_channels=256, + point_feat_channels=64, + norm_cfg=dict(type='GN', num_groups=36), + loss_grid=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=15))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_radius=1, + pos_weight=-1, + max_num_grid=192, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.03, + nms=dict(type='nms', iou_threshold=0.3), + max_per_img=100))) +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) + +# training schedule +max_epochs = 25 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 80, + by_epoch=False, + begin=0, + end=3665), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[17, 23], + gamma=0.1) +] + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/grid_rcnn/grid-rcnn_x101-32x4d_fpn_gn-head_2x_coco.py b/mmdetection/configs/grid_rcnn/grid-rcnn_x101-32x4d_fpn_gn-head_2x_coco.py new file mode 100644 index 00000000..dddf157b --- /dev/null +++ b/mmdetection/configs/grid_rcnn/grid-rcnn_x101-32x4d_fpn_gn-head_2x_coco.py @@ -0,0 +1,13 @@ +_base_ = './grid-rcnn_r50_fpn_gn-head_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/grid_rcnn/grid-rcnn_x101-64x4d_fpn_gn-head_2x_coco.py b/mmdetection/configs/grid_rcnn/grid-rcnn_x101-64x4d_fpn_gn-head_2x_coco.py new file mode 100644 index 00000000..e4ff50f5 --- /dev/null +++ b/mmdetection/configs/grid_rcnn/grid-rcnn_x101-64x4d_fpn_gn-head_2x_coco.py @@ -0,0 +1,13 @@ +_base_ = './grid-rcnn_x101-32x4d_fpn_gn-head_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/grid_rcnn/metafile.yml b/mmdetection/configs/grid_rcnn/metafile.yml new file mode 100644 index 00000000..cee91e3b --- /dev/null +++ b/mmdetection/configs/grid_rcnn/metafile.yml @@ -0,0 +1,101 @@ +Collections: + - Name: Grid R-CNN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RPN + - Dilated Convolution + - ResNet + - RoIAlign + Paper: + URL: https://arxiv.org/abs/1906.05688 + Title: 'Grid R-CNN' + README: configs/grid_rcnn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/grid_rcnn.py#L6 + Version: v2.0.0 + +Models: + - Name: grid-rcnn_r50_fpn_gn-head_2x_coco + In Collection: Grid R-CNN + Config: configs/grid_rcnn/grid-rcnn_r50_fpn_gn-head_2x_coco.py + Metadata: + Training Memory (GB): 5.1 + inference time (ms/im): + - value: 66.67 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_r50_fpn_gn-head_2x_coco/grid_rcnn_r50_fpn_gn-head_2x_coco_20200130-6cca8223.pth + + - Name: grid-rcnn_r101_fpn_gn-head_2x_coco + In Collection: Grid R-CNN + Config: configs/grid_rcnn/grid-rcnn_r101_fpn_gn-head_2x_coco.py + Metadata: + Training Memory (GB): 7.0 + inference time (ms/im): + - value: 79.37 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_r101_fpn_gn-head_2x_coco/grid_rcnn_r101_fpn_gn-head_2x_coco_20200309-d6eca030.pth + + - Name: grid-rcnn_x101-32x4d_fpn_gn-head_2x_coco + In Collection: Grid R-CNN + Config: configs/grid_rcnn/grid-rcnn_x101-32x4d_fpn_gn-head_2x_coco.py + Metadata: + Training Memory (GB): 8.3 + inference time (ms/im): + - value: 92.59 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_x101_32x4d_fpn_gn-head_2x_coco/grid_rcnn_x101_32x4d_fpn_gn-head_2x_coco_20200130-d8f0e3ff.pth + + - Name: grid-rcnn_x101-64x4d_fpn_gn-head_2x_coco + In Collection: Grid R-CNN + Config: configs/grid_rcnn/grid-rcnn_x101-64x4d_fpn_gn-head_2x_coco.py + Metadata: + Training Memory (GB): 11.3 + inference time (ms/im): + - value: 129.87 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/grid_rcnn/grid_rcnn_x101_64x4d_fpn_gn-head_2x_coco/grid_rcnn_x101_64x4d_fpn_gn-head_2x_coco_20200204-ec76a754.pth diff --git a/mmdetection/configs/groie/README.md b/mmdetection/configs/groie/README.md new file mode 100644 index 00000000..9792df93 --- /dev/null +++ b/mmdetection/configs/groie/README.md @@ -0,0 +1,72 @@ +# GRoIE + +> [A novel Region of Interest Extraction Layer for Instance Segmentation](https://arxiv.org/abs/2004.13665) + + + +## Abstract + +Given the wide diffusion of deep neural network architectures for computer vision tasks, several new applications are nowadays more and more feasible. Among them, a particular attention has been recently given to instance segmentation, by exploiting the results achievable by two-stage networks (such as Mask R-CNN or Faster R-CNN), derived from R-CNN. In these complex architectures, a crucial role is played by the Region of Interest (RoI) extraction layer, devoted to extracting a coherent subset of features from a single Feature Pyramid Network (FPN) layer attached on top of a backbone. +This paper is motivated by the need to overcome the limitations of existing RoI extractors which select only one (the best) layer from FPN. Our intuition is that all the layers of FPN retain useful information. Therefore, the proposed layer (called Generic RoI Extractor - GRoIE) introduces non-local building blocks and attention mechanisms to boost the performance. +A comprehensive ablation study at component level is conducted to find the best set of algorithms and parameters for the GRoIE layer. Moreover, GRoIE can be integrated seamlessly with every two-stage architecture for both object detection and instance segmentation tasks. Therefore, the improvements brought about by the use of GRoIE in different state-of-the-art architectures are also evaluated. The proposed layer leads up to gain a 1.1% AP improvement on bounding box detection and 1.7% AP improvement on instance segmentation. + +
    + +
    + +## Introduction + +By Leonardo Rossi, Akbar Karimi and Andrea Prati from +[IMPLab](http://implab.ce.unipr.it/). + +We provide configs to reproduce the results in the paper for +"*A novel Region of Interest Extraction Layer for Instance Segmentation*" +on COCO object detection. + +This paper is motivated by the need to overcome to the limitations of existing +RoI extractors which select only one (the best) layer from FPN. + +Our intuition is that all the layers of FPN retain useful information. + +Therefore, the proposed layer (called Generic RoI Extractor - **GRoIE**) +introduces non-local building blocks and attention mechanisms to boost the +performance. + +## Results and Models + +The results on COCO 2017 minival (5k images) are shown in the below table. + +### Application of GRoIE to different architectures + +| Backbone | Method | Lr schd | box AP | mask AP | Config | Download | +| :-------: | :-------------: | :-----: | :----: | :-----: | :------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | Faster Original | 1x | 37.4 | | [config](../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130_204655.log.json) | +| R-50-FPN | + GRoIE | 1x | 38.3 | | [config](./faste-rcnn_r50_fpn_groie_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/groie/faster_rcnn_r50_fpn_groie_1x_coco/faster_rcnn_r50_fpn_groie_1x_coco_20200604_211715-66ee9516.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/groie/faster_rcnn_r50_fpn_groie_1x_coco/faster_rcnn_r50_fpn_groie_1x_coco_20200604_211715.log.json) | +| R-50-FPN | Grid R-CNN | 1x | 39.1 | | [config](./grid-rcnn_r50_fpn_gn-head-groie_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/groie/grid_rcnn_r50_fpn_gn-head_groie_1x_coco/grid_rcnn_r50_fpn_gn-head_groie_1x_coco_20200605_202059-4b75d86f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/groie/grid_rcnn_r50_fpn_gn-head_groie_1x_coco/grid_rcnn_r50_fpn_gn-head_groie_1x_coco_20200605_202059.log.json) | +| R-50-FPN | + GRoIE | 1x | | | [config](./grid-rcnn_r50_fpn_gn-head-groie_1x_coco.py) | | +| R-50-FPN | Mask R-CNN | 1x | 38.2 | 34.7 | [config](../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205_050542.log.json) | +| R-50-FPN | + GRoIE | 1x | 39.0 | 36.0 | [config](./mask-rcnn_r50_fpn_groie_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/groie/mask_rcnn_r50_fpn_groie_1x_coco/mask_rcnn_r50_fpn_groie_1x_coco_20200604_211715-50d90c74.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/groie/mask_rcnn_r50_fpn_groie_1x_coco/mask_rcnn_r50_fpn_groie_1x_coco_20200604_211715.log.json) | +| R-50-FPN | GC-Net | 1x | 40.7 | 36.5 | [config](../gcnet/mask-rcnn_r50-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200202-50b90e5c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200202_085547.log.json) | +| R-50-FPN | + GRoIE | 1x | 41.0 | 37.8 | [config](./mask-rcnn_r50_fpn_syncbn-r4-gcb-c3-c5-groie_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/groie/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco_20200604_211715-42eb79e1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/groie/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco_20200604_211715-42eb79e1.pth) | +| R-101-FPN | GC-Net | 1x | 42.2 | 37.8 | [config](../gcnet/mask-rcnn_r101-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200206-8407a3f0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/gcnet/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_1x_coco_20200206_142508.log.json) | +| R-101-FPN | + GRoIE | 1x | 42.6 | 38.7 | [config](./mask-rcnn_r101_fpn_syncbn-r4-gcb_c3-c5-groie_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/groie/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco_20200607_224507-8daae01c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/groie/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco_20200607_224507.log.json) | + +## Citation + +If you use this work or benchmark in your research, please cite this project. + +```latex +@inproceedings{rossi2021novel, + title={A novel region of interest extraction layer for instance segmentation}, + author={Rossi, Leonardo and Karimi, Akbar and Prati, Andrea}, + booktitle={2020 25th International Conference on Pattern Recognition (ICPR)}, + pages={2203--2209}, + year={2021}, + organization={IEEE} +} +``` + +## Contact + +The implementation of GRoIE is currently maintained by +[Leonardo Rossi](https://github.com/hachreak/). diff --git a/mmdetection/configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py b/mmdetection/configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py new file mode 100644 index 00000000..0fbe8a32 --- /dev/null +++ b/mmdetection/configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py @@ -0,0 +1,25 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +# model settings +model = dict( + roi_head=dict( + bbox_roi_extractor=dict( + type='GenericRoIExtractor', + aggregation='sum', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32], + pre_cfg=dict( + type='ConvModule', + in_channels=256, + out_channels=256, + kernel_size=5, + padding=2, + inplace=False, + ), + post_cfg=dict( + type='GeneralizedAttention', + in_channels=256, + spatial_range=-1, + num_heads=6, + attention_type='0100', + kv_stride=2)))) diff --git a/mmdetection/configs/groie/grid-rcnn_r50_fpn_gn-head-groie_1x_coco.py b/mmdetection/configs/groie/grid-rcnn_r50_fpn_gn-head-groie_1x_coco.py new file mode 100644 index 00000000..dadccb79 --- /dev/null +++ b/mmdetection/configs/groie/grid-rcnn_r50_fpn_gn-head-groie_1x_coco.py @@ -0,0 +1,45 @@ +_base_ = '../grid_rcnn/grid-rcnn_r50_fpn_gn-head_1x_coco.py' +# model settings +model = dict( + roi_head=dict( + bbox_roi_extractor=dict( + type='GenericRoIExtractor', + aggregation='sum', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32], + pre_cfg=dict( + type='ConvModule', + in_channels=256, + out_channels=256, + kernel_size=5, + padding=2, + inplace=False, + ), + post_cfg=dict( + type='GeneralizedAttention', + in_channels=256, + spatial_range=-1, + num_heads=6, + attention_type='0100', + kv_stride=2)), + grid_roi_extractor=dict( + type='GenericRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32], + pre_cfg=dict( + type='ConvModule', + in_channels=256, + out_channels=256, + kernel_size=5, + padding=2, + inplace=False, + ), + post_cfg=dict( + type='GeneralizedAttention', + in_channels=256, + spatial_range=-1, + num_heads=6, + attention_type='0100', + kv_stride=2)))) diff --git a/mmdetection/configs/groie/mask-rcnn_r101_fpn_syncbn-r4-gcb_c3-c5-groie_1x_coco.py b/mmdetection/configs/groie/mask-rcnn_r101_fpn_syncbn-r4-gcb_c3-c5-groie_1x_coco.py new file mode 100644 index 00000000..5699b428 --- /dev/null +++ b/mmdetection/configs/groie/mask-rcnn_r101_fpn_syncbn-r4-gcb_c3-c5-groie_1x_coco.py @@ -0,0 +1,45 @@ +_base_ = '../gcnet/mask-rcnn_r101-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py' +# model settings +model = dict( + roi_head=dict( + bbox_roi_extractor=dict( + type='GenericRoIExtractor', + aggregation='sum', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32], + pre_cfg=dict( + type='ConvModule', + in_channels=256, + out_channels=256, + kernel_size=5, + padding=2, + inplace=False, + ), + post_cfg=dict( + type='GeneralizedAttention', + in_channels=256, + spatial_range=-1, + num_heads=6, + attention_type='0100', + kv_stride=2)), + mask_roi_extractor=dict( + type='GenericRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32], + pre_cfg=dict( + type='ConvModule', + in_channels=256, + out_channels=256, + kernel_size=5, + padding=2, + inplace=False, + ), + post_cfg=dict( + type='GeneralizedAttention', + in_channels=256, + spatial_range=-1, + num_heads=6, + attention_type='0100', + kv_stride=2)))) diff --git a/mmdetection/configs/groie/mask-rcnn_r50_fpn_groie_1x_coco.py b/mmdetection/configs/groie/mask-rcnn_r50_fpn_groie_1x_coco.py new file mode 100644 index 00000000..4c9521e2 --- /dev/null +++ b/mmdetection/configs/groie/mask-rcnn_r50_fpn_groie_1x_coco.py @@ -0,0 +1,45 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +# model settings +model = dict( + roi_head=dict( + bbox_roi_extractor=dict( + type='GenericRoIExtractor', + aggregation='sum', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32], + pre_cfg=dict( + type='ConvModule', + in_channels=256, + out_channels=256, + kernel_size=5, + padding=2, + inplace=False, + ), + post_cfg=dict( + type='GeneralizedAttention', + in_channels=256, + spatial_range=-1, + num_heads=6, + attention_type='0100', + kv_stride=2)), + mask_roi_extractor=dict( + type='GenericRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32], + pre_cfg=dict( + type='ConvModule', + in_channels=256, + out_channels=256, + kernel_size=5, + padding=2, + inplace=False, + ), + post_cfg=dict( + type='GeneralizedAttention', + in_channels=256, + spatial_range=-1, + num_heads=6, + attention_type='0100', + kv_stride=2)))) diff --git a/mmdetection/configs/groie/mask-rcnn_r50_fpn_syncbn-r4-gcb-c3-c5-groie_1x_coco.py b/mmdetection/configs/groie/mask-rcnn_r50_fpn_syncbn-r4-gcb-c3-c5-groie_1x_coco.py new file mode 100644 index 00000000..22e97b69 --- /dev/null +++ b/mmdetection/configs/groie/mask-rcnn_r50_fpn_syncbn-r4-gcb-c3-c5-groie_1x_coco.py @@ -0,0 +1,45 @@ +_base_ = '../gcnet/mask-rcnn_r50-syncbn-gcb-r4-c3-c5_fpn_1x_coco.py' +# model settings +model = dict( + roi_head=dict( + bbox_roi_extractor=dict( + type='GenericRoIExtractor', + aggregation='sum', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32], + pre_cfg=dict( + type='ConvModule', + in_channels=256, + out_channels=256, + kernel_size=5, + padding=2, + inplace=False, + ), + post_cfg=dict( + type='GeneralizedAttention', + in_channels=256, + spatial_range=-1, + num_heads=6, + attention_type='0100', + kv_stride=2)), + mask_roi_extractor=dict( + type='GenericRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32], + pre_cfg=dict( + type='ConvModule', + in_channels=256, + out_channels=256, + kernel_size=5, + padding=2, + inplace=False, + ), + post_cfg=dict( + type='GeneralizedAttention', + in_channels=256, + spatial_range=-1, + num_heads=6, + attention_type='0100', + kv_stride=2)))) diff --git a/mmdetection/configs/groie/metafile.yml b/mmdetection/configs/groie/metafile.yml new file mode 100644 index 00000000..ce957004 --- /dev/null +++ b/mmdetection/configs/groie/metafile.yml @@ -0,0 +1,94 @@ +Collections: + - Name: GRoIE + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Generic RoI Extractor + - FPN + - RPN + - ResNet + - RoIAlign + Paper: + URL: https://arxiv.org/abs/2004.13665 + Title: 'A novel Region of Interest Extraction Layer for Instance Segmentation' + README: configs/groie/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/roi_heads/roi_extractors/groie.py#L15 + Version: v2.1.0 + +Models: + - Name: faster-rcnn_r50_fpn_groie_1x_coco + In Collection: GRoIE + Config: configs/groie/faste-rcnn_r50_fpn_groie_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/groie/faster_rcnn_r50_fpn_groie_1x_coco/faster_rcnn_r50_fpn_groie_1x_coco_20200604_211715-66ee9516.pth + + - Name: grid-rcnn_r50_fpn_gn-head-groie_1x_coco + In Collection: GRoIE + Config: configs/groie/grid-rcnn_r50_fpn_gn-head-groie_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/groie/grid_rcnn_r50_fpn_gn-head_groie_1x_coco/grid_rcnn_r50_fpn_gn-head_groie_1x_coco_20200605_202059-4b75d86f.pth + + - Name: mask-rcnn_r50_fpn_groie_1x_coco + In Collection: GRoIE + Config: configs/groie/mask-rcnn_r50_fpn_groie_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/groie/mask_rcnn_r50_fpn_groie_1x_coco/mask_rcnn_r50_fpn_groie_1x_coco_20200604_211715-50d90c74.pth + + - Name: mask-rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco + In Collection: GRoIE + Config: configs/groie/mask-rcnn_r50_fpn_syncbn-r4-gcb-c3-c5-groie_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/groie/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco/mask_rcnn_r50_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco_20200604_211715-42eb79e1.pth + + - Name: mask-rcnn_r101_fpn_syncbn-r4-gcb_c3-c5-groie_1x_coco + In Collection: GRoIE + Config: configs/groie/mask-rcnn_r101_fpn_syncbn-r4-gcb_c3-c5-groie_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/groie/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco/mask_rcnn_r101_fpn_syncbn-backbone_r4_gcb_c3-c5_groie_1x_coco_20200607_224507-8daae01c.pth diff --git a/mmdetection/configs/grounding_dino/README.md b/mmdetection/configs/grounding_dino/README.md new file mode 100644 index 00000000..2a527828 --- /dev/null +++ b/mmdetection/configs/grounding_dino/README.md @@ -0,0 +1,317 @@ +# Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection + +[Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection](https://arxiv.org/abs/2303.05499) + + + +## Abstract + +In this paper, we present an open-set object detector, called Grounding DINO, by marrying Transformer-based detector DINO with grounded pre-training, which can detect arbitrary objects with human inputs such as category names or referring expressions. The key solution of open-set object detection is introducing language to a closed-set detector for open-set concept generalization. To effectively fuse language and vision modalities, we conceptually divide a closed-set detector into three phases and propose a tight fusion solution, which includes a feature enhancer, a language-guided query selection, and a cross-modality decoder for cross-modality fusion. While previous works mainly evaluate open-set object detection on novel categories, we propose to also perform evaluations on referring expression comprehension for objects specified with attributes. Grounding DINO performs remarkably well on all three settings, including benchmarks on COCO, LVIS, ODinW, and RefCOCO/+/g. Grounding DINO achieves a 52.5 AP on the COCO detection zero-shot transfer benchmark, i.e., without any training data from COCO. It sets a new record on the ODinW zero-shot benchmark with a mean 26.1 AP. + +
    + +
    + +## Installation + +```shell +cd $MMDETROOT + +# source installation +pip install -r requirements/multimodal.txt + +# or mim installation +mim install mmdet[multimodal] +``` + +## NOTE + +Grounding DINO utilizes BERT as the language model, which requires access to https://huggingface.co/. If you encounter connection errors due to network access, you can download the required files on a computer with internet access and save them locally. Finally, modify the `lang_model_name` field in the config to the local path. Please refer to the following code: + +```python +from transformers import BertConfig, BertModel +from transformers import AutoTokenizer + +config = BertConfig.from_pretrained("bert-base-uncased") +model = BertModel.from_pretrained("bert-base-uncased", add_pooling_layer=False, config=config) +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") + +config.save_pretrained("your path/bert-base-uncased") +model.save_pretrained("your path/bert-base-uncased") +tokenizer.save_pretrained("your path/bert-base-uncased") +``` + +## Inference + +``` +cd $MMDETROOT + +wget https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swint_ogc_mmdet-822d7e9d.pth + +python demo/image_demo.py \ + demo/demo.jpg \ + configs/grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py \ + --weights groundingdino_swint_ogc_mmdet-822d7e9d.pth \ + --texts 'bench . car .' +``` + +
    + +
    + +## COCO Results and Models + +| Model | Backbone | Style | COCO mAP | Official COCO mAP | Pre-Train Data | Config | Download | +| :----------------: | :------: | :-------: | :--------: | :---------------: | :----------------------------------------------: | :------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Grounding DINO-T | Swin-T | Zero-shot | 48.5 | 48.4 | O365,GoldG,Cap4M | [config](grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swint_ogc_mmdet-822d7e9d.pth) | +| Grounding DINO-T | Swin-T | Finetune | 58.1(+0.9) | 57.2 | O365,GoldG,Cap4M | [config](grounding_dino_swin-t_finetune_16xb2_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco/grounding_dino_swin-t_finetune_16xb2_1x_coco_20230921_152544-5f234b20.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco/grounding_dino_swin-t_finetune_16xb2_1x_coco_20230921_152544.log.json) | +| Grounding DINO-B | Swin-B | Zero-shot | 56.9 | 56.7 | COCO,O365,GoldG,Cap4M,OpenImage,ODinW-35,RefCOCO | [config](grounding_dino_swin-b_pretrain_mixeddata.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swinb_cogcoor_mmdet-55949c9c.pth) | +| Grounding DINO-B | Swin-B | Finetune | 59.7 | | COCO,O365,GoldG,Cap4M,OpenImage,ODinW-35,RefCOCO | [config](grounding_dino_swin-b_finetune_16xb2_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_swin-b_finetune_16xb2_1x_coco/grounding_dino_swin-b_finetune_16xb2_1x_coco_20230921_153201-f219e0c0.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_swin-b_finetune_16xb2_1x_coco/grounding_dino_swin-b_finetune_16xb2_1x_coco_20230921_153201.log.json) | +| Grounding DINO-R50 | R50 | Scratch | 48.9(+0.8) | 48.1 | | [config](grounding_dino_r50_scratch_8xb2_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_r50_scratch_8xb2_1x_coco/grounding_dino_r50_scratch_1x_coco-fe0002f2.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_r50_scratch_8xb2_1x_coco/20230922_114218.json) | + +Note: + +1. The weights corresponding to the zero-shot model are adopted from the official weights and converted using the [script](../../tools/model_converters/groundingdino_to_mmdet.py). We have not retrained the model for the time being. +2. Finetune refers to fine-tuning on the COCO 2017 dataset. The R50 model is trained using 8 NVIDIA GeForce 3090 GPUs, while the remaining models are trained using 16 NVIDIA GeForce 3090 GPUs. The GPU memory usage is approximately 8.5GB. +3. Our performance is higher than the official model due to two reasons: we modified the initialization strategy and introduced a log scaler. + +## LVIS Results + +| Model | MiniVal APr | MiniVal APc | MiniVal APf | MiniVal AP | Val1.0 APr | Val1.0 APc | Val1.0 APf | Val1.0 AP | Pre-Train Data | Config | Download | +| :--------------: | :---------: | :---------: | :---------: | :--------: | :--------: | :--------: | :--------: | :-------: | :----------------------------------------------: | :-----------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------: | +| Grounding DINO-T | 18.8 | 24.2 | 34.7 | 28.8 | 10.1 | 15.3 | 29.9 | 20.1 | O365,GoldG,Cap4M | [config](lvis/grounding_dino_swin-t_pretrain_zeroshot_mini-lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swint_ogc_mmdet-822d7e9d.pth) | +| Grounding DINO-B | 27.9 | 33.4 | 37.2 | 34.7 | 19.0 | 24.1 | 32.9 | 26.7 | COCO,O365,GoldG,Cap4M,OpenImage,ODinW-35,RefCOCO | [config](lvis/grounding_dino_swin-b_pretrain_zeroshot_mini-lvis.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swinb_cogcoor_mmdet-55949c9c.pth) | + +Note: + +1. The above are zero-shot evaluation results. +2. The evaluation metric we used is LVIS FixAP. For specific details, please refer to [Evaluating Large-Vocabulary Object Detectors: The Devil is in the Details](https://arxiv.org/pdf/2102.01066.pdf). + +## ODinW (Object Detection in the Wild) Results + +Learning visual representations from natural language supervision has recently shown great promise in a number of pioneering works. In general, these language-augmented visual models demonstrate strong transferability to a variety of datasets and tasks. However, it remains challenging to evaluate the transferablity of these models due to the lack of easy-to-use evaluation toolkits and public benchmarks. To tackle this, we build ELEVATER 1 , the first benchmark and toolkit for evaluating (pre-trained) language-augmented visual models. ELEVATER is composed of three components. (i) Datasets. As downstream evaluation suites, it consists of 20 image classification datasets and 35 object detection datasets, each of which is augmented with external knowledge. (ii) Toolkit. An automatic hyper-parameter tuning toolkit is developed to facilitate model evaluation on downstream tasks. (iii) Metrics. A variety of evaluation metrics are used to measure sample-efficiency (zero-shot and few-shot) and parameter-efficiency (linear probing and full model fine-tuning). ELEVATER is platform for Computer Vision in the Wild (CVinW), and is publicly released at https://computer-vision-in-the-wild.github.io/ELEVATER/ + +### Results and models of ODinW13 + +| Method | GLIP-T(A) | Official | GLIP-T(B) | Official | GLIP-T(C) | Official | GroundingDINO-T | GroundingDINO-B | +| --------------------- | --------- | --------- | --------- | --------- | --------- | --------- | --------------- | --------------- | +| AerialMaritimeDrone | 0.123 | 0.122 | 0.110 | 0.110 | 0.130 | 0.130 | 0.173 | 0.281 | +| Aquarium | 0.175 | 0.174 | 0.173 | 0.169 | 0.191 | 0.190 | 0.195 | 0.445 | +| CottontailRabbits | 0.686 | 0.686 | 0.688 | 0.688 | 0.744 | 0.744 | 0.799 | 0.808 | +| EgoHands | 0.013 | 0.013 | 0.003 | 0.004 | 0.314 | 0.315 | 0.608 | 0.764 | +| NorthAmericaMushrooms | 0.502 | 0.502 | 0.367 | 0.367 | 0.297 | 0.296 | 0.507 | 0.675 | +| Packages | 0.589 | 0.589 | 0.083 | 0.083 | 0.699 | 0.699 | 0.687 | 0.670 | +| PascalVOC | 0.512 | 0.512 | 0.541 | 0.540 | 0.565 | 0.565 | 0.563 | 0.711 | +| pistols | 0.339 | 0.339 | 0.502 | 0.501 | 0.503 | 0.504 | 0.726 | 0.771 | +| pothole | 0.007 | 0.007 | 0.030 | 0.030 | 0.058 | 0.058 | 0.215 | 0.478 | +| Raccoon | 0.075 | 0.074 | 0.285 | 0.288 | 0.241 | 0.244 | 0.549 | 0.541 | +| ShellfishOpenImages | 0.253 | 0.253 | 0.337 | 0.338 | 0.300 | 0.302 | 0.393 | 0.650 | +| thermalDogsAndPeople | 0.372 | 0.372 | 0.475 | 0.475 | 0.510 | 0.510 | 0.657 | 0.633 | +| VehiclesOpenImages | 0.574 | 0.566 | 0.562 | 0.547 | 0.549 | 0.534 | 0.613 | 0.647 | +| Average | **0.325** | **0.324** | **0.320** | **0.318** | **0.392** | **0.392** | **0.514** | **0.621** | + +### Results and models of ODinW35 + +| Method | GLIP-T(A) | Official | GLIP-T(B) | Official | GLIP-T(C) | Official | GroundingDINO-T | GroundingDINO-B | +| --------------------------- | --------- | --------- | --------- | --------- | --------- | --------- | --------------- | --------------- | +| AerialMaritimeDrone_large | 0.123 | 0.122 | 0.110 | 0.110 | 0.130 | 0.130 | 0.173 | 0.281 | +| AerialMaritimeDrone_tiled | 0.174 | 0.174 | 0.172 | 0.172 | 0.172 | 0.172 | 0.206 | 0.364 | +| AmericanSignLanguageLetters | 0.001 | 0.001 | 0.003 | 0.003 | 0.009 | 0.009 | 0.002 | 0.096 | +| Aquarium | 0.175 | 0.175 | 0.173 | 0.171 | 0.192 | 0.182 | 0.195 | 0.445 | +| BCCD | 0.016 | 0.016 | 0.001 | 0.001 | 0.000 | 0.000 | 0.161 | 0.584 | +| boggleBoards | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.134 | +| brackishUnderwater | 0.016 | 0..013 | 0.021 | 0.027 | 0.020 | 0.022 | 0.021 | 0.454 | +| ChessPieces | 0.001 | 0.001 | 0.000 | 0.000 | 0.001 | 0.001 | 0.000 | 0.000 | +| CottontailRabbits | 0.710 | 0.709 | 0.683 | 0.683 | 0.752 | 0.752 | 0.806 | 0.797 | +| dice | 0.005 | 0.005 | 0.004 | 0.004 | 0.004 | 0.004 | 0.004 | 0.082 | +| DroneControl | 0.016 | 0.017 | 0.006 | 0.008 | 0.005 | 0.007 | 0.042 | 0.638 | +| EgoHands_generic | 0.009 | 0.010 | 0.005 | 0.006 | 0.510 | 0.508 | 0.608 | 0.764 | +| EgoHands_specific | 0.001 | 0.001 | 0.004 | 0.006 | 0.003 | 0.004 | 0.002 | 0.687 | +| HardHatWorkers | 0.029 | 0.029 | 0.023 | 0.023 | 0.033 | 0.033 | 0.046 | 0.439 | +| MaskWearing | 0.007 | 0.007 | 0.003 | 0.002 | 0.005 | 0.005 | 0.004 | 0.406 | +| MountainDewCommercial | 0.218 | 0.227 | 0.199 | 0.197 | 0.478 | 0.463 | 0.430 | 0.580 | +| NorthAmericaMushrooms | 0.502 | 0.502 | 0.450 | 0.450 | 0.497 | 0.497 | 0.471 | 0.501 | +| openPoetryVision | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.051 | +| OxfordPets_by_breed | 0.001 | 0.002 | 0.002 | 0.004 | 0.001 | 0.002 | 0.003 | 0.799 | +| OxfordPets_by_species | 0.016 | 0.011 | 0.012 | 0.009 | 0.013 | 0.009 | 0.011 | 0.872 | +| PKLot | 0.002 | 0.002 | 0.000 | 0.000 | 0.000 | 0.000 | 0.001 | 0.774 | +| Packages | 0.569 | 0.569 | 0.279 | 0.279 | 0.712 | 0.712 | 0.695 | 0.728 | +| PascalVOC | 0.512 | 0.512 | 0.541 | 0.540 | 0.565 | 0.565 | 0.563 | 0.711 | +| pistols | 0.339 | 0.339 | 0.502 | 0.501 | 0.503 | 0.504 | 0.726 | 0.771 | +| plantdoc | 0.002 | 0.002 | 0.007 | 0.007 | 0.009 | 0.009 | 0.005 | 0.376 | +| pothole | 0.007 | 0.010 | 0.024 | 0.025 | 0.085 | 0.101 | 0.215 | 0.478 | +| Raccoons | 0.075 | 0.074 | 0.285 | 0.288 | 0.241 | 0.244 | 0.549 | 0.541 | +| selfdrivingCar | 0.071 | 0.072 | 0.074 | 0.074 | 0.081 | 0.080 | 0.089 | 0.318 | +| ShellfishOpenImages | 0.253 | 0.253 | 0.337 | 0.338 | 0.300 | 0.302 | 0.393 | 0.650 | +| ThermalCheetah | 0.028 | 0.028 | 0.000 | 0.000 | 0.028 | 0.028 | 0.087 | 0.290 | +| thermalDogsAndPeople | 0.372 | 0.372 | 0.475 | 0.475 | 0.510 | 0.510 | 0.657 | 0.633 | +| UnoCards | 0.000 | 0.000 | 0.000 | 0.001 | 0.002 | 0.003 | 0.006 | 0.754 | +| VehiclesOpenImages | 0.574 | 0.566 | 0.562 | 0.547 | 0.549 | 0.534 | 0.613 | 0.647 | +| WildfireSmoke | 0.000 | 0.000 | 0.000 | 0.000 | 0.017 | 0.017 | 0.134 | 0.410 | +| websiteScreenshots | 0.003 | 0.004 | 0.003 | 0.005 | 0.005 | 0.006 | 0.012 | 0.175 | +| Average | **0.134** | **0.134** | **0.138** | **0.138** | **0.179** | **0.178** | **0.227** | **0.492** | + +## Flickr30k Results + +| Model | Pre-Train Data | Val R@1 | Val R@5 | Val R@10 | Tesst R@1 | Test R@5 | Test R@10 | Config | Download | +| :--------------: | :--------------: | ------- | ------- | -------- | --------- | -------- | --------- | :-------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Grounding DINO-T | O365,GoldG,Cap4M | 87.8 | 96.6 | 98.0 | 88.1 | 96.9 | 98.2 | [config](grounding_dino_swin-t_finetune_16xb2_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco/grounding_dino_swin-t_finetune_16xb2_1x_coco_20230921_152544-5f234b20.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco/grounding_dino_swin-t_finetune_16xb2_1x_coco_20230921_152544.log.json) | + +Note: + +1. `@1,5,10` refers to precision at the top 1, 5, and 10 positions in a predicted ranked list. +2. The pretraining data used by Grounding DINO-T is `O365,GoldG,Cap4M`, and the corresponding evaluation configuration is (grounding_dino_swin-t_pretrain_zeroshot_refcoco)\[refcoco/grounding_dino_swin-t_pretrain_zeroshot_refcoco.py\]. + +Test Command + +```shell +cd mmdetection +bash tools/dist_test.sh configs/grounding_dino/flickr30k/grounding_dino_swin-t-pretrain_zeroshot_flickr30k.py checkpoints/groundingdino_swint_ogc_mmdet-822d7e9d.pth 8 +``` + +## Referring Expression Comprehension Results + +| Method | Grounding DINO-T
    (O365,GoldG,Cap4M) | Grounding DINO-B
    (COCO,O365,GoldG,Cap4M,OpenImage,ODinW-35,RefCOCO) | +| --------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------- | +| RefCOCO val @1,5,10 | 50.77/89.45/94.86 | 84.61/97.88/99.10 | +| RefCOCO testA @1,5,10 | 57.45/91.29/95.62 | 88.65/98.89/99.63 | +| RefCOCO testB @1,5,10 | 44.97/86.54/92.88 | 80.51/96.64/98.51 | +| RefCOCO+ val @1,5,10 | 51.64/86.35/92.57 | 73.67/96.60/98.65 | +| RefCOCO+ testA @1,5,10 | 57.25/86.74/92.65 | 82.19/97.92/99.09 | +| RefCOCO+ testB @1,5,10 | 46.35/84.05/90.67 | 64.10/94.25/97.46 | +| RefCOCOg val @1,5,10 | 60.42/92.10/96.18 | 78.33/97.28/98.57 | +| RefCOCOg test @1,5,10 | 59.74/92.08/96.28 | 78.11/97.06/98.65 | +| gRefCOCO val Pr@(F1=1, IoU≥0.5),N-acc | 41.32/91.82 | 46.18/81.44 | +| gRefCOCO testA Pr@(F1=1, IoU≥0.5),N-acc | 27.23/90.24 | 38.60/76.06 | +| gRefCOCO testB Pr@(F1=1, IoU≥0.5),N-acc | 29.70/93.49 | 35.87/80.58 | + +Note: + +1. `@1,5,10` refers to precision at the top 1, 5, and 10 positions in a predicted ranked list. +2. `Pr@(F1=1, IoU≥0.5),N-acc` from the paper [GREC: Generalized Referring Expression Comprehension](https://arxiv.org/pdf/2308.16182.pdf) +3. The pretraining data used by Grounding DINO-T is `O365,GoldG,Cap4M`, and the corresponding evaluation configuration is (grounding_dino_swin-t_pretrain_zeroshot_refcoco)\[refcoco/grounding_dino_swin-t_pretrain_zeroshot_refcoco.py\]. +4. The pretraining data used by Grounding DINO-B is `COCO,O365,GoldG,Cap4M,OpenImage,ODinW-35,RefCOCO`, and the corresponding evaluation configuration is (grounding_dino_swin-t_pretrain_zeroshot_refcoco)\[refcoco/grounding_dino_swin-b_pretrain_zeroshot_refcoco.py\]. + +Test Command + +```shell +cd mmdetection +./tools/dist_test.sh configs/grounding_dino/refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp.py https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swint_ogc_mmdet-822d7e9d.pth 8 +./tools/dist_test.sh configs/grounding_dino/refcoco/grounding_dino_swin-b_pretrain_zeroshot_refexp.py https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swinb_cogcoor_mmdet-55949c9c.pth 8 +``` + +## Description Detection Dataset + +```shell +pip install ddd-dataset +``` + +| Method | mode | Grounding DINO-T
    (O365,GoldG,Cap4M) | Grounding DINO-B
    (COCO,O365,GoldG,Cap4M,OpenImage,ODinW-35,RefCOCO) | +| -------------------------------- | -------- | ----------------------------------------- | ------------------------------------------------------------------------- | +| FULL/short/middle/long/very long | concat | 17.2/18.0/18.7/14.8/16.3 | 20.2/20.4/21.1/18.8/19.8 | +| FULL/short/middle/long/very long | parallel | 22.3/28.2/24.8/19.1/13.9 | 25.0/26.4/27.2/23.5/19.7 | +| PRES/short/middle/long/very long | concat | 17.8/18.3/19.2/15.2/17.3 | 20.7/21.7/21.4/19.1/20.3 | +| PRES/short/middle/long/very long | parallel | 21.0/27.0/22.8/17.5/12.5 | 23.7/25.8/25.1/21.9/19.3 | +| ABS/short/middle/long/very long | concat | 15.4/17.1/16.4/13.6/14.9 | 18.6/16.1/19.7/18.1/19.1 | +| ABS/short/middle/long/very long | parallel | 26.0/32.0/33.0/23.6/15.5 | 28.8/28.1/35.8/28.2/20.2 | + +Note: + +1. Considering that the evaluation time for Inter-scenario is very long and the performance is low, it is temporarily not supported. The mentioned metrics are for Intra-scenario. +2. `concat` is the default inference mode for Grounding DINO, where it concatenates multiple sub-sentences with "." to form a single sentence for inference. On the other hand, "parallel" performs inference on each sub-sentence in a for-loop. + +## Custom Dataset + +To facilitate fine-tuning on custom datasets, we use a simple cat dataset as an example, as shown in the following steps. + +### 1. Dataset Preparation + +```shell +cd mmdetection +wget https://download.openmmlab.com/mmyolo/data/cat_dataset.zip +unzip cat_dataset.zip -d data/cat/ +``` + +cat dataset is a single-category dataset with 144 images, which has been converted to coco format. + +
    +cat dataset +
    + +### 2. Config Preparation + +Due to the simplicity and small number of cat datasets, we use 8 cards to train 20 epochs, scale the learning rate accordingly, and do not train the language model, only the visual model. + +The Details of the configuration can be found in [grounding_dino_swin-t_finetune_8xb2_20e_cat](grounding_dino_swin-t_finetune_8xb2_20e_cat.py) + +### 3. Visualization and Evaluation + +Due to the Grounding DINO is an open detection model, so it can be detected and evaluated even if it is not trained on the cat dataset. + +The single image visualization is as follows: + +```shell +cd mmdetection +python demo/image_demo.py data/cat/images/IMG_20211205_120756.jpg configs/grounding_dino/grounding_dino_swin-t_finetune_8xb2_20e_cat.py --weights https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swint_ogc_mmdet-822d7e9d.pth --texts cat. +``` + +
    +cat dataset +
    + +The test dataset evaluation on single card is as follows: + +```shell +python tools/test.py configs/grounding_dino/grounding_dino_swin-t_finetune_8xb2_20e_cat.py https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swint_ogc_mmdet-822d7e9d.pth +``` + +```text + Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.867 + Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 1.000 + Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.931 + Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.867 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.903 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.907 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.907 + Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.907 +``` + +### 4. Model Training and Visualization + +```shell +./tools/dist_train.sh configs/grounding_dino/grounding_dino_swin-t_finetune_8xb2_20e_cat.py 8 --work-dir cat_work_dir +``` + +The model will be saved based on the best performance on the test set. The performance of the best model (at epoch 16) is as follows: + +```text + Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.905 + Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 1.000 + Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.923 + Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.905 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.927 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.937 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.937 + Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.937 +``` + +We can find that after fine-tuning training, the training of the cat dataset is increased from 86.7 to 90.5. + +If we do single image inference visualization again, the result is as follows: + +```shell +cd mmdetection +python demo/image_demo.py data/cat/images/IMG_20211205_120756.jpg configs/grounding_dino/grounding_dino_swin-t_finetune_8xb2_20e_cat.py --weights cat_work_dir/best_coco_bbox_mAP_epoch_16.pth --texts cat. +``` + +
    +cat dataset +
    diff --git a/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-b_pretrain_zeroshot_concat_dod.py b/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-b_pretrain_zeroshot_concat_dod.py new file mode 100644 index 00000000..ac655b74 --- /dev/null +++ b/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-b_pretrain_zeroshot_concat_dod.py @@ -0,0 +1,14 @@ +_base_ = 'grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py' + +model = dict( + type='GroundingDINO', + backbone=dict( + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12, + drop_path_rate=0.3, + patch_norm=True), + neck=dict(in_channels=[256, 512, 1024]), +) diff --git a/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-b_pretrain_zeroshot_parallel_dod.py b/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-b_pretrain_zeroshot_parallel_dod.py new file mode 100644 index 00000000..9a1c8f2a --- /dev/null +++ b/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-b_pretrain_zeroshot_parallel_dod.py @@ -0,0 +1,3 @@ +_base_ = 'grounding_dino_swin-b_pretrain_zeroshot_concat_dod.py' + +model = dict(test_cfg=dict(chunked_size=1)) diff --git a/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py b/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py new file mode 100644 index 00000000..bb418011 --- /dev/null +++ b/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py @@ -0,0 +1,78 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py' + +data_root = 'data/d3/' + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', 'sent_ids')) +] + +# -------------------------------------------------# +val_dataset_full = dict( + type='DODDataset', + data_root=data_root, + ann_file='d3_json/d3_full_annotations.json', + data_prefix=dict(img='d3_images/', anno='d3_pkl'), + pipeline=test_pipeline, + test_mode=True, + backend_args=None, + return_classes=True) + +val_evaluator_full = dict( + type='DODCocoMetric', + ann_file=data_root + 'd3_json/d3_full_annotations.json') + +# -------------------------------------------------# +val_dataset_pres = dict( + type='DODDataset', + data_root=data_root, + ann_file='d3_json/d3_pres_annotations.json', + data_prefix=dict(img='d3_images/', anno='d3_pkl'), + pipeline=test_pipeline, + test_mode=True, + backend_args=None, + return_classes=True) +val_evaluator_pres = dict( + type='DODCocoMetric', + ann_file=data_root + 'd3_json/d3_pres_annotations.json') + +# -------------------------------------------------# +val_dataset_abs = dict( + type='DODDataset', + data_root=data_root, + ann_file='d3_json/d3_abs_annotations.json', + data_prefix=dict(img='d3_images/', anno='d3_pkl'), + pipeline=test_pipeline, + test_mode=True, + backend_args=None, + return_classes=True) +val_evaluator_abs = dict( + type='DODCocoMetric', + ann_file=data_root + 'd3_json/d3_abs_annotations.json') + +# -------------------------------------------------# +datasets = [val_dataset_full, val_dataset_pres, val_dataset_abs] +dataset_prefixes = ['FULL', 'PRES', 'ABS'] +metrics = [val_evaluator_full, val_evaluator_pres, val_evaluator_abs] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_parallel_dod.py b/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_parallel_dod.py new file mode 100644 index 00000000..3d680091 --- /dev/null +++ b/mmdetection/configs/grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_parallel_dod.py @@ -0,0 +1,3 @@ +_base_ = 'grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py' + +model = dict(test_cfg=dict(chunked_size=1)) diff --git a/mmdetection/configs/grounding_dino/flickr30k/grounding_dino_swin-t-pretrain_zeroshot_flickr30k.py b/mmdetection/configs/grounding_dino/flickr30k/grounding_dino_swin-t-pretrain_zeroshot_flickr30k.py new file mode 100644 index 00000000..c1996567 --- /dev/null +++ b/mmdetection/configs/grounding_dino/flickr30k/grounding_dino_swin-t-pretrain_zeroshot_flickr30k.py @@ -0,0 +1,57 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py' + +dataset_type = 'Flickr30kDataset' +data_root = 'data/flickr30k_entities/' + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive', 'phrase_ids', 'phrases')) +] + +dataset_Flickr30k_val = dict( + type=dataset_type, + data_root=data_root, + ann_file='final_flickr_separateGT_val.json', + data_prefix=dict(img='flickr30k_images/'), + pipeline=test_pipeline, +) + +dataset_Flickr30k_test = dict( + type=dataset_type, + data_root=data_root, + ann_file='final_flickr_separateGT_test.json', + data_prefix=dict(img='flickr30k_images/'), + pipeline=test_pipeline, +) + +val_evaluator_Flickr30k = dict(type='Flickr30kMetric') + +test_evaluator_Flickr30k = dict(type='Flickr30kMetric') + +# ----------Config---------- # +dataset_prefixes = ['Flickr30kVal', 'Flickr30kTest'] +datasets = [dataset_Flickr30k_val, dataset_Flickr30k_test] +metrics = [val_evaluator_Flickr30k, test_evaluator_Flickr30k] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/grounding_dino/grounding_dino_r50_scratch_8xb2_1x_coco.py b/mmdetection/configs/grounding_dino/grounding_dino_r50_scratch_8xb2_1x_coco.py new file mode 100644 index 00000000..623a29b8 --- /dev/null +++ b/mmdetection/configs/grounding_dino/grounding_dino_r50_scratch_8xb2_1x_coco.py @@ -0,0 +1,208 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +lang_model_name = 'bert-base-uncased' + +model = dict( + type='GroundingDINO', + num_queries=900, + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=False, + ), + language_model=dict( + type='BertModel', + name=lang_model_name, + pad_to_max=False, + use_sub_sentence_represent=True, + special_tokens_list=['[CLS]', '[SEP]', '.', '?'], + add_pooling_layer=False, + ), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[512, 1024, 2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + bias=True, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + encoder=dict( + num_layers=6, + num_cp=6, + # visual layer config + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=2048, ffn_drop=0.0)), + # text layer config + text_layer_cfg=dict( + self_attn_cfg=dict(num_heads=4, embed_dims=256, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=1024, ffn_drop=0.0)), + # fusion layer config + fusion_layer_cfg=dict( + v_dim=256, + l_dim=256, + embed_dim=1024, + num_heads=4, + init_values=1e-4), + ), + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + # query self attention layer + self_attn_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + # cross attention layer query to text + cross_attn_text_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + # cross attention layer query to image + cross_attn_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=2048, ffn_drop=0.0)), + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, normalize=True, offset=0.0, temperature=20), + bbox_head=dict( + type='GroundingDINOHead', + num_classes=80, + sync_cls_avg_factor=True, + contrastive_cfg=dict(max_text_len=256, log_scale='auto', bias=True), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), # 2.0 in DeformDETR + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( # TODO: Move to model.train_cfg ? + label_noise_scale=0.5, + box_noise_scale=1.0, # 0.4 for DN-DETR + group_cfg=dict(dynamic=True, num_groups=None, + num_dn_queries=100)), # TODO: half num_dn_queries + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='BinaryFocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='FixScaleResize', scale=(800, 1333), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities')) +] + +train_dataloader = dict( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), + pipeline=train_pipeline, + return_classes=True)) +val_dataloader = dict( + dataset=dict(pipeline=test_pipeline, return_classes=True)) +test_dataloader = val_dataloader + +# We did not adopt the official 24e optimizer strategy +# because the results indicate that the current strategy is superior. +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', + lr=0.0001, # 0.0002 for DeformDETR + weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1) + })) +# learning policy +max_epochs = 12 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/grounding_dino/grounding_dino_swin-b_finetune_16xb2_1x_coco.py b/mmdetection/configs/grounding_dino/grounding_dino_swin-b_finetune_16xb2_1x_coco.py new file mode 100644 index 00000000..3554ee24 --- /dev/null +++ b/mmdetection/configs/grounding_dino/grounding_dino_swin-b_finetune_16xb2_1x_coco.py @@ -0,0 +1,17 @@ +_base_ = [ + './grounding_dino_swin-t_finetune_16xb2_1x_coco.py', +] + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swinb_cogcoor_mmdet-55949c9c.pth' # noqa +model = dict( + type='GroundingDINO', + backbone=dict( + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12, + drop_path_rate=0.3, + patch_norm=True), + neck=dict(in_channels=[256, 512, 1024]), +) diff --git a/mmdetection/configs/grounding_dino/grounding_dino_swin-b_pretrain_mixeddata.py b/mmdetection/configs/grounding_dino/grounding_dino_swin-b_pretrain_mixeddata.py new file mode 100644 index 00000000..92f327fe --- /dev/null +++ b/mmdetection/configs/grounding_dino/grounding_dino_swin-b_pretrain_mixeddata.py @@ -0,0 +1,16 @@ +_base_ = [ + './grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py', +] + +model = dict( + type='GroundingDINO', + backbone=dict( + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12, + drop_path_rate=0.3, + patch_norm=True), + neck=dict(in_channels=[256, 512, 1024]), +) diff --git a/mmdetection/configs/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco.py b/mmdetection/configs/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco.py new file mode 100644 index 00000000..0c6403ee --- /dev/null +++ b/mmdetection/configs/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco.py @@ -0,0 +1,204 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swint_ogc_mmdet-822d7e9d.pth' # noqa +lang_model_name = 'bert-base-uncased' + +model = dict( + type='GroundingDINO', + num_queries=900, + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=False, + ), + language_model=dict( + type='BertModel', + name=lang_model_name, + pad_to_max=False, + use_sub_sentence_represent=True, + special_tokens_list=['[CLS]', '[SEP]', '.', '?'], + add_pooling_layer=False, + ), + backbone=dict( + type='SwinTransformer', + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=True, + convert_weights=False), + neck=dict( + type='ChannelMapper', + in_channels=[192, 384, 768], + kernel_size=1, + out_channels=256, + act_cfg=None, + bias=True, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + encoder=dict( + num_layers=6, + num_cp=6, + # visual layer config + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=2048, ffn_drop=0.0)), + # text layer config + text_layer_cfg=dict( + self_attn_cfg=dict(num_heads=4, embed_dims=256, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=1024, ffn_drop=0.0)), + # fusion layer config + fusion_layer_cfg=dict( + v_dim=256, + l_dim=256, + embed_dim=1024, + num_heads=4, + init_values=1e-4), + ), + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + # query self attention layer + self_attn_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + # cross attention layer query to text + cross_attn_text_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + # cross attention layer query to image + cross_attn_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=2048, ffn_drop=0.0)), + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, normalize=True, offset=0.0, temperature=20), + bbox_head=dict( + type='GroundingDINOHead', + num_classes=80, + sync_cls_avg_factor=True, + contrastive_cfg=dict(max_text_len=256, log_scale=0.0, bias=False), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), # 2.0 in DeformDETR + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( # TODO: Move to model.train_cfg ? + label_noise_scale=0.5, + box_noise_scale=1.0, # 0.4 for DN-DETR + group_cfg=dict(dynamic=True, num_groups=None, + num_dn_queries=100)), # TODO: half num_dn_queries + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='BinaryFocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='FixScaleResize', scale=(800, 1333), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities')) +] + +train_dataloader = dict( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), + pipeline=train_pipeline, + return_classes=True)) +val_dataloader = dict( + dataset=dict(pipeline=test_pipeline, return_classes=True)) +test_dataloader = val_dataloader + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1) + })) +# learning policy +max_epochs = 12 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (16 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=32) diff --git a/mmdetection/configs/grounding_dino/grounding_dino_swin-t_finetune_8xb2_20e_cat.py b/mmdetection/configs/grounding_dino/grounding_dino_swin-t_finetune_8xb2_20e_cat.py new file mode 100644 index 00000000..c2265e86 --- /dev/null +++ b/mmdetection/configs/grounding_dino/grounding_dino_swin-t_finetune_8xb2_20e_cat.py @@ -0,0 +1,56 @@ +_base_ = 'grounding_dino_swin-t_finetune_16xb2_1x_coco.py' + +data_root = 'data/cat/' +class_name = ('cat', ) +num_classes = len(class_name) +metainfo = dict(classes=class_name, palette=[(220, 20, 60)]) + +model = dict(bbox_head=dict(num_classes=num_classes)) + +train_dataloader = dict( + dataset=dict( + data_root=data_root, + metainfo=metainfo, + ann_file='annotations/trainval.json', + data_prefix=dict(img='images/'))) + +val_dataloader = dict( + dataset=dict( + metainfo=metainfo, + data_root=data_root, + ann_file='annotations/test.json', + data_prefix=dict(img='images/'))) + +test_dataloader = val_dataloader + +val_evaluator = dict(ann_file=data_root + 'annotations/test.json') +test_evaluator = val_evaluator + +max_epoch = 20 + +default_hooks = dict( + checkpoint=dict(interval=1, max_keep_ckpts=1, save_best='auto'), + logger=dict(type='LoggerHook', interval=5)) +train_cfg = dict(max_epochs=max_epoch, val_interval=1) + +param_scheduler = [ + dict(type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=30), + dict( + type='MultiStepLR', + begin=0, + end=max_epoch, + by_epoch=True, + milestones=[15], + gamma=0.1) +] + +optim_wrapper = dict( + optimizer=dict(lr=0.00005), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + 'language_model': dict(lr_mult=0), + })) + +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py b/mmdetection/configs/grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py new file mode 100644 index 00000000..7448764e --- /dev/null +++ b/mmdetection/configs/grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py @@ -0,0 +1,128 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +lang_model_name = 'bert-base-uncased' + +model = dict( + type='GroundingDINO', + num_queries=900, + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=False, + ), + language_model=dict( + type='BertModel', + name=lang_model_name, + pad_to_max=False, + use_sub_sentence_represent=True, + special_tokens_list=['[CLS]', '[SEP]', '.', '?'], + add_pooling_layer=True, + ), + backbone=dict( + type='SwinTransformer', + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False, + convert_weights=False), + neck=dict( + type='ChannelMapper', + in_channels=[192, 384, 768], + kernel_size=1, + out_channels=256, + act_cfg=None, + bias=True, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + encoder=dict( + num_layers=6, + # visual layer config + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=2048, ffn_drop=0.0)), + # text layer config + text_layer_cfg=dict( + self_attn_cfg=dict(num_heads=4, embed_dims=256, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=1024, ffn_drop=0.0)), + # fusion layer config + fusion_layer_cfg=dict( + v_dim=256, + l_dim=256, + embed_dim=1024, + num_heads=4, + init_values=1e-4), + ), + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + # query self attention layer + self_attn_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + # cross attention layer query to text + cross_attn_text_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + # cross attention layer query to image + cross_attn_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=2048, ffn_drop=0.0)), + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, normalize=True, offset=0.0, temperature=20), + bbox_head=dict( + type='GroundingDINOHead', + num_classes=80, + sync_cls_avg_factor=True, + contrastive_cfg=dict(max_text_len=256), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), # 2.0 in DeformDETR + loss_bbox=dict(type='L1Loss', loss_weight=5.0)), + dn_cfg=dict( # TODO: Move to model.train_cfg ? + label_noise_scale=0.5, + box_noise_scale=1.0, # 0.4 for DN-DETR + group_cfg=dict(dynamic=True, num_groups=None, + num_dn_queries=100)), # TODO: half num_dn_queries + # training and testing settings + train_cfg=None, + test_cfg=dict(max_per_img=300)) + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive')) +] + +val_dataloader = dict( + dataset=dict(pipeline=test_pipeline, return_classes=True)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-b_pretrain_zeroshot_lvis.py b/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-b_pretrain_zeroshot_lvis.py new file mode 100644 index 00000000..60841590 --- /dev/null +++ b/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-b_pretrain_zeroshot_lvis.py @@ -0,0 +1,14 @@ +_base_ = './grounding_dino_swin-t_pretrain_zeroshot_lvis.py' + +model = dict( + type='GroundingDINO', + backbone=dict( + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12, + drop_path_rate=0.3, + patch_norm=True), + neck=dict(in_channels=[256, 512, 1024]), +) diff --git a/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-b_pretrain_zeroshot_mini-lvis.py b/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-b_pretrain_zeroshot_mini-lvis.py new file mode 100644 index 00000000..68467a72 --- /dev/null +++ b/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-b_pretrain_zeroshot_mini-lvis.py @@ -0,0 +1,14 @@ +_base_ = './grounding_dino_swin-t_pretrain_zeroshot_mini-lvis.py' + +model = dict( + type='GroundingDINO', + backbone=dict( + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12, + drop_path_rate=0.3, + patch_norm=True), + neck=dict(in_channels=[256, 512, 1024]), +) diff --git a/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_lvis.py b/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_lvis.py new file mode 100644 index 00000000..3d05f0ce --- /dev/null +++ b/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_lvis.py @@ -0,0 +1,24 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py' + +model = dict(test_cfg=dict( + max_per_img=300, + chunked_size=40, +)) + +dataset_type = 'LVISV1Dataset' +data_root = 'data/coco/' + +val_dataloader = dict( + dataset=dict( + data_root=data_root, + type=dataset_type, + ann_file='annotations/lvis_od_val.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +# numpy < 1.24.0 +val_evaluator = dict( + _delete_=True, + type='LVISFixedAPMetric', + ann_file=data_root + 'annotations/lvis_od_val.json') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_mini-lvis.py b/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_mini-lvis.py new file mode 100644 index 00000000..0aac6cf3 --- /dev/null +++ b/mmdetection/configs/grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_mini-lvis.py @@ -0,0 +1,25 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py' + +model = dict(test_cfg=dict( + max_per_img=300, + chunked_size=40, +)) + +dataset_type = 'LVISV1Dataset' +data_root = 'data/coco/' + +val_dataloader = dict( + dataset=dict( + data_root=data_root, + type=dataset_type, + ann_file='annotations/lvis_v1_minival_inserted_image_name.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +# numpy < 1.24.0 +val_evaluator = dict( + _delete_=True, + type='LVISFixedAPMetric', + ann_file=data_root + + 'annotations/lvis_v1_minival_inserted_image_name.json') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/grounding_dino/metafile.yml b/mmdetection/configs/grounding_dino/metafile.yml new file mode 100644 index 00000000..dcb5ebf8 --- /dev/null +++ b/mmdetection/configs/grounding_dino/metafile.yml @@ -0,0 +1,67 @@ +Collections: + - Name: Grounding DINO + Metadata: + Training Data: Objects365, GoldG, CC3M and COCO + Training Techniques: + - AdamW + - Multi Scale Train + - Gradient Clip + Training Resources: 3090 GPUs + Architecture: + - Swin Transformer + - BERT + Paper: + URL: https://arxiv.org/abs/2303.05499 + Title: 'Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection +' + README: configs/grounding_dino/README.md + Code: + URL: + Version: v3.0.0 + +Models: + - Name: grounding_dino_swin-t_pretrain_obj365_goldg_cap4m + In Collection: Grounding DINO + Config: configs/grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 48.5 + Weights: https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swint_ogc_mmdet-822d7e9d.pth + - Name: grounding_dino_swin-b_pretrain_mixeddata + In Collection: Grounding DINO + Config: configs/grounding_dino/grounding_dino_swin-b_pretrain_mixeddata.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 56.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swinb_cogcoor_mmdet-55949c9c.pth + - Name: grounding_dino_swin-t_finetune_16xb2_1x_coco + In Collection: Grounding DINO + Config: configs/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 58.1 + Weights: https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_swin-t_finetune_16xb2_1x_coco/grounding_dino_swin-t_finetune_16xb2_1x_coco_20230921_152544-5f234b20.pth + - Name: grounding_dino_swin-b_finetune_16xb2_1x_coco + In Collection: Grounding DINO + Config: configs/grounding_dino/grounding_dino_swin-b_finetune_16xb2_1x_coco.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 59.7 + Weights: https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_swin-b_finetune_16xb2_1x_coco/grounding_dino_swin-b_finetune_16xb2_1x_coco_20230921_153201-f219e0c0.pth + - Name: grounding_dino_r50_scratch_8xb2_1x_coco + In Collection: Grounding DINO + Config: configs/grounding_dino/grounding_dino_r50_scratch_8xb2_1x_coco.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 48.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/grounding_dino_r50_scratch_8xb2_1x_coco/grounding_dino_r50_scratch_1x_coco-fe0002f2.pth diff --git a/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-b_pretrain_odinw13.py b/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-b_pretrain_odinw13.py new file mode 100644 index 00000000..65a6bc2a --- /dev/null +++ b/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-b_pretrain_odinw13.py @@ -0,0 +1,338 @@ +_base_ = '../grounding_dino_swin-b_pretrain_mixeddata.py' + +dataset_type = 'CocoDataset' +data_root = 'data/odinw/' + +base_test_pipeline = _base_.test_pipeline +base_test_pipeline[-1]['meta_keys'] = ('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'text', + 'custom_entities', 'caption_prompt') + +# ---------------------1 AerialMaritimeDrone---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/large/' +dataset_AerialMaritimeDrone = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + test_mode=True, + pipeline=base_test_pipeline, + return_classes=True) +val_evaluator_AerialMaritimeDrone = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------2 Aquarium---------------------# +class_name = ('fish', 'jellyfish', 'penguin', 'puffin', 'shark', 'starfish', + 'stingray') +metainfo = dict(classes=class_name) +_data_root = data_root + 'Aquarium/Aquarium Combined.v2-raw-1024.coco/' + +caption_prompt = None +# caption_prompt = { +# 'penguin': { +# 'suffix': ', which is black and white' +# }, +# 'puffin': { +# 'suffix': ' with orange beaks' +# }, +# 'stingray': { +# 'suffix': ' which is flat and round' +# }, +# } +dataset_Aquarium = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Aquarium = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------3 CottontailRabbits---------------------# +class_name = ('Cottontail-Rabbit', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'CottontailRabbits/' + +caption_prompt = None +# caption_prompt = {'Cottontail-Rabbit': {'name': 'rabbit'}} + +dataset_CottontailRabbits = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_CottontailRabbits = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------4 EgoHands---------------------# +class_name = ('hand', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/generic/' + +caption_prompt = None +# caption_prompt = {'hand': {'suffix': ' of a person'}} + +dataset_EgoHands = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------5 NorthAmericaMushrooms---------------------# +class_name = ('CoW', 'chanterelle') +metainfo = dict(classes=class_name) +_data_root = data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa + +caption_prompt = None +# caption_prompt = { +# 'CoW': { +# 'name': 'flat mushroom' +# }, +# 'chanterelle': { +# 'name': 'yellow mushroom' +# } +# } + +dataset_NorthAmericaMushrooms = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_NorthAmericaMushrooms = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------6 Packages---------------------# +class_name = ('package', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Packages/Raw/' + +caption_prompt = None +# caption_prompt = { +# 'package': { +# 'prefix': 'there is a ', +# 'suffix': ' on the porch' +# } +# } + +dataset_Packages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Packages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------7 PascalVOC---------------------# +class_name = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', + 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PascalVOC/' +dataset_PascalVOC = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PascalVOC = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------8 pistols---------------------# +class_name = ('pistol', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pistols/export/' +dataset_pistols = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pistols = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------9 pothole---------------------# +class_name = ('pothole', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pothole/' + +caption_prompt = None +# caption_prompt = { +# 'pothole': { +# 'prefix': 'there are some ', +# 'name': 'holes', +# 'suffix': ' on the road' +# } +# } + +dataset_pothole = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pothole = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------10 Raccoon---------------------# +class_name = ('raccoon', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Raccoon/Raccoon.v2-raw.coco/' +dataset_Raccoon = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Raccoon = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------11 ShellfishOpenImages---------------------# +class_name = ('Crab', 'Lobster', 'Shrimp') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ShellfishOpenImages/raw/' +dataset_ShellfishOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ShellfishOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------12 thermalDogsAndPeople---------------------# +class_name = ('dog', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'thermalDogsAndPeople/' +dataset_thermalDogsAndPeople = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_thermalDogsAndPeople = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------13 VehiclesOpenImages---------------------# +class_name = ('Ambulance', 'Bus', 'Car', 'Motorcycle', 'Truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'VehiclesOpenImages/416x416/' +dataset_VehiclesOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_VehiclesOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# --------------------- Config---------------------# +dataset_prefixes = [ + 'AerialMaritimeDrone', 'Aquarium', 'CottontailRabbits', 'EgoHands', + 'NorthAmericaMushrooms', 'Packages', 'PascalVOC', 'pistols', 'pothole', + 'Raccoon', 'ShellfishOpenImages', 'thermalDogsAndPeople', + 'VehiclesOpenImages' +] +datasets = [ + dataset_AerialMaritimeDrone, dataset_Aquarium, dataset_CottontailRabbits, + dataset_EgoHands, dataset_NorthAmericaMushrooms, dataset_Packages, + dataset_PascalVOC, dataset_pistols, dataset_pothole, dataset_Raccoon, + dataset_ShellfishOpenImages, dataset_thermalDogsAndPeople, + dataset_VehiclesOpenImages +] +metrics = [ + val_evaluator_AerialMaritimeDrone, val_evaluator_Aquarium, + val_evaluator_CottontailRabbits, val_evaluator_EgoHands, + val_evaluator_NorthAmericaMushrooms, val_evaluator_Packages, + val_evaluator_PascalVOC, val_evaluator_pistols, val_evaluator_pothole, + val_evaluator_Raccoon, val_evaluator_ShellfishOpenImages, + val_evaluator_thermalDogsAndPeople, val_evaluator_VehiclesOpenImages +] + +# -------------------------------------------------# +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-b_pretrain_odinw35.py b/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-b_pretrain_odinw35.py new file mode 100644 index 00000000..e73cd8e6 --- /dev/null +++ b/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-b_pretrain_odinw35.py @@ -0,0 +1,796 @@ +_base_ = '../grounding_dino_swin-b_pretrain_mixeddata.py' + +dataset_type = 'CocoDataset' +data_root = 'data/odinw/' + +base_test_pipeline = _base_.test_pipeline +base_test_pipeline[-1]['meta_keys'] = ('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'text', + 'custom_entities', 'caption_prompt') + +# ---------------------1 AerialMaritimeDrone_large---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/large/' +dataset_AerialMaritimeDrone_large = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AerialMaritimeDrone_large = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------2 AerialMaritimeDrone_tiled---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/tiled/' +dataset_AerialMaritimeDrone_tiled = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AerialMaritimeDrone_tiled = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------3 AmericanSignLanguageLetters---------------------# +class_name = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AmericanSignLanguageLetters/American Sign Language Letters.v1-v1.coco/' # noqa +dataset_AmericanSignLanguageLetters = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AmericanSignLanguageLetters = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------4 Aquarium---------------------# +class_name = ('fish', 'jellyfish', 'penguin', 'puffin', 'shark', 'starfish', + 'stingray') +metainfo = dict(classes=class_name) +_data_root = data_root + 'Aquarium/Aquarium Combined.v2-raw-1024.coco/' +dataset_Aquarium = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Aquarium = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------5 BCCD---------------------# +class_name = ('Platelets', 'RBC', 'WBC') +metainfo = dict(classes=class_name) +_data_root = data_root + 'BCCD/BCCD.v3-raw.coco/' +dataset_BCCD = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_BCCD = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------6 boggleBoards---------------------# +class_name = ('Q', 'a', 'an', 'b', 'c', 'd', 'e', 'er', 'f', 'g', 'h', 'he', + 'i', 'in', 'j', 'k', 'l', 'm', 'n', 'o', 'o ', 'p', 'q', 'qu', + 'r', 's', 't', 't\\', 'th', 'u', 'v', 'w', 'wild', 'x', 'y', 'z') +metainfo = dict(classes=class_name) +_data_root = data_root + 'boggleBoards/416x416AutoOrient/export/' +dataset_boggleBoards = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_boggleBoards = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------7 brackishUnderwater---------------------# +class_name = ('crab', 'fish', 'jellyfish', 'shrimp', 'small_fish', 'starfish') +metainfo = dict(classes=class_name) +_data_root = data_root + 'brackishUnderwater/960x540/' +dataset_brackishUnderwater = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_brackishUnderwater = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------8 ChessPieces---------------------# +class_name = (' ', 'black bishop', 'black king', 'black knight', 'black pawn', + 'black queen', 'black rook', 'white bishop', 'white king', + 'white knight', 'white pawn', 'white queen', 'white rook') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ChessPieces/Chess Pieces.v23-raw.coco/' +dataset_ChessPieces = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ChessPieces = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------9 CottontailRabbits---------------------# +class_name = ('rabbit', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'CottontailRabbits/' +dataset_CottontailRabbits = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_CottontailRabbits = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------10 dice---------------------# +class_name = ('1', '2', '3', '4', '5', '6') +metainfo = dict(classes=class_name) +_data_root = data_root + 'dice/mediumColor/export/' +dataset_dice = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_dice = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------11 DroneControl---------------------# +class_name = ('follow', 'follow_hand', 'land', 'land_hand', 'null', 'object', + 'takeoff', 'takeoff-hand') +metainfo = dict(classes=class_name) +_data_root = data_root + 'DroneControl/Drone Control.v3-raw.coco/' +dataset_DroneControl = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_DroneControl = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------12 EgoHands_generic---------------------# +class_name = ('hand', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/generic/' +caption_prompt = {'hand': {'suffix': ' of a person'}} +dataset_EgoHands_generic = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + # NOTE w. prompt 0.548; wo. prompt 0.764 + # caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands_generic = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------13 EgoHands_specific---------------------# +class_name = ('myleft', 'myright', 'yourleft', 'yourright') +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/specific/' +dataset_EgoHands_specific = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands_specific = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------14 HardHatWorkers---------------------# +class_name = ('head', 'helmet', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'HardHatWorkers/raw/' +dataset_HardHatWorkers = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_HardHatWorkers = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------15 MaskWearing---------------------# +class_name = ('mask', 'no-mask') +metainfo = dict(classes=class_name) +_data_root = data_root + 'MaskWearing/raw/' +dataset_MaskWearing = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_MaskWearing = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------16 MountainDewCommercial---------------------# +class_name = ('bottle', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'MountainDewCommercial/' +dataset_MountainDewCommercial = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_MountainDewCommercial = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------17 NorthAmericaMushrooms---------------------# +class_name = ('flat mushroom', 'yellow mushroom') +metainfo = dict(classes=class_name) +_data_root = data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa +dataset_NorthAmericaMushrooms = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_NorthAmericaMushrooms = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------18 openPoetryVision---------------------# +class_name = ('American Typewriter', 'Andale Mono', 'Apple Chancery', 'Arial', + 'Avenir', 'Baskerville', 'Big Caslon', 'Bradley Hand', + 'Brush Script MT', 'Chalkboard', 'Comic Sans MS', 'Copperplate', + 'Courier', 'Didot', 'Futura', 'Geneva', 'Georgia', 'Gill Sans', + 'Helvetica', 'Herculanum', 'Impact', 'Kefa', 'Lucida Grande', + 'Luminari', 'Marker Felt', 'Menlo', 'Monaco', 'Noteworthy', + 'Optima', 'PT Sans', 'PT Serif', 'Palatino', 'Papyrus', + 'Phosphate', 'Rockwell', 'SF Pro', 'SignPainter', 'Skia', + 'Snell Roundhand', 'Tahoma', 'Times New Roman', 'Trebuchet MS', + 'Verdana') +metainfo = dict(classes=class_name) +_data_root = data_root + 'openPoetryVision/512x512/' +dataset_openPoetryVision = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_openPoetryVision = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------19 OxfordPets_by_breed---------------------# +class_name = ('cat-Abyssinian', 'cat-Bengal', 'cat-Birman', 'cat-Bombay', + 'cat-British_Shorthair', 'cat-Egyptian_Mau', 'cat-Maine_Coon', + 'cat-Persian', 'cat-Ragdoll', 'cat-Russian_Blue', 'cat-Siamese', + 'cat-Sphynx', 'dog-american_bulldog', + 'dog-american_pit_bull_terrier', 'dog-basset_hound', + 'dog-beagle', 'dog-boxer', 'dog-chihuahua', + 'dog-english_cocker_spaniel', 'dog-english_setter', + 'dog-german_shorthaired', 'dog-great_pyrenees', 'dog-havanese', + 'dog-japanese_chin', 'dog-keeshond', 'dog-leonberger', + 'dog-miniature_pinscher', 'dog-newfoundland', 'dog-pomeranian', + 'dog-pug', 'dog-saint_bernard', 'dog-samoyed', + 'dog-scottish_terrier', 'dog-shiba_inu', + 'dog-staffordshire_bull_terrier', 'dog-wheaten_terrier', + 'dog-yorkshire_terrier') +metainfo = dict(classes=class_name) +_data_root = data_root + 'OxfordPets/by-breed/' # noqa +dataset_OxfordPets_by_breed = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_OxfordPets_by_breed = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------20 OxfordPets_by_species---------------------# +class_name = ('cat', 'dog') +metainfo = dict(classes=class_name) +_data_root = data_root + 'OxfordPets/by-species/' # noqa +dataset_OxfordPets_by_species = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_OxfordPets_by_species = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------21 PKLot---------------------# +class_name = ('space-empty', 'space-occupied') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PKLot/640/' # noqa +dataset_PKLot = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PKLot = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------22 Packages---------------------# +class_name = ('package', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Packages/Raw/' +caption_prompt = { + 'package': { + 'prefix': 'there is a ', + 'suffix': ' on the porch' + } +} +dataset_Packages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, # NOTE w. prompt 0.728; wo. prompt 0.670 + test_mode=True, + return_classes=True) +val_evaluator_Packages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------23 PascalVOC---------------------# +class_name = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', + 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PascalVOC/' +dataset_PascalVOC = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PascalVOC = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------24 pistols---------------------# +class_name = ('pistol', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pistols/export/' +dataset_pistols = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pistols = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------25 plantdoc---------------------# +class_name = ('Apple Scab Leaf', 'Apple leaf', 'Apple rust leaf', + 'Bell_pepper leaf', 'Bell_pepper leaf spot', 'Blueberry leaf', + 'Cherry leaf', 'Corn Gray leaf spot', 'Corn leaf blight', + 'Corn rust leaf', 'Peach leaf', 'Potato leaf', + 'Potato leaf early blight', 'Potato leaf late blight', + 'Raspberry leaf', 'Soyabean leaf', 'Soybean leaf', + 'Squash Powdery mildew leaf', 'Strawberry leaf', + 'Tomato Early blight leaf', 'Tomato Septoria leaf spot', + 'Tomato leaf', 'Tomato leaf bacterial spot', + 'Tomato leaf late blight', 'Tomato leaf mosaic virus', + 'Tomato leaf yellow virus', 'Tomato mold leaf', + 'Tomato two spotted spider mites leaf', 'grape leaf', + 'grape leaf black rot') +metainfo = dict(classes=class_name) +_data_root = data_root + 'plantdoc/416x416/' +dataset_plantdoc = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_plantdoc = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------26 pothole---------------------# +class_name = ('pothole', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pothole/' +caption_prompt = { + 'pothole': { + 'name': 'holes', + 'prefix': 'there are some ', + 'suffix': ' on the road' + } +} +dataset_pothole = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + # NOTE w. prompt 0.221; wo. prompt 0.478 + # caption_prompt=caption_prompt, + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pothole = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------27 Raccoon---------------------# +class_name = ('raccoon', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Raccoon/Raccoon.v2-raw.coco/' +dataset_Raccoon = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Raccoon = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------28 selfdrivingCar---------------------# +class_name = ('biker', 'car', 'pedestrian', 'trafficLight', + 'trafficLight-Green', 'trafficLight-GreenLeft', + 'trafficLight-Red', 'trafficLight-RedLeft', + 'trafficLight-Yellow', 'trafficLight-YellowLeft', 'truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'selfdrivingCar/fixedLarge/export/' +dataset_selfdrivingCar = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_selfdrivingCar = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------29 ShellfishOpenImages---------------------# +class_name = ('Crab', 'Lobster', 'Shrimp') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ShellfishOpenImages/raw/' +dataset_ShellfishOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ShellfishOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------30 ThermalCheetah---------------------# +class_name = ('cheetah', 'human') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ThermalCheetah/' +dataset_ThermalCheetah = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ThermalCheetah = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------31 thermalDogsAndPeople---------------------# +class_name = ('dog', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'thermalDogsAndPeople/' +dataset_thermalDogsAndPeople = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_thermalDogsAndPeople = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------32 UnoCards---------------------# +class_name = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', + '12', '13', '14') +metainfo = dict(classes=class_name) +_data_root = data_root + 'UnoCards/raw/' +dataset_UnoCards = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_UnoCards = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------33 VehiclesOpenImages---------------------# +class_name = ('Ambulance', 'Bus', 'Car', 'Motorcycle', 'Truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'VehiclesOpenImages/416x416/' +dataset_VehiclesOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_VehiclesOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------34 WildfireSmoke---------------------# +class_name = ('smoke', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'WildfireSmoke/' +dataset_WildfireSmoke = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_WildfireSmoke = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------35 websiteScreenshots---------------------# +class_name = ('button', 'field', 'heading', 'iframe', 'image', 'label', 'link', + 'text') +metainfo = dict(classes=class_name) +_data_root = data_root + 'websiteScreenshots/' +dataset_websiteScreenshots = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_websiteScreenshots = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# --------------------- Config---------------------# + +dataset_prefixes = [ + 'AerialMaritimeDrone_large', + 'AerialMaritimeDrone_tiled', + 'AmericanSignLanguageLetters', + 'Aquarium', + 'BCCD', + 'boggleBoards', + 'brackishUnderwater', + 'ChessPieces', + 'CottontailRabbits', + 'dice', + 'DroneControl', + 'EgoHands_generic', + 'EgoHands_specific', + 'HardHatWorkers', + 'MaskWearing', + 'MountainDewCommercial', + 'NorthAmericaMushrooms', + 'openPoetryVision', + 'OxfordPets_by_breed', + 'OxfordPets_by_species', + 'PKLot', + 'Packages', + 'PascalVOC', + 'pistols', + 'plantdoc', + 'pothole', + 'Raccoons', + 'selfdrivingCar', + 'ShellfishOpenImages', + 'ThermalCheetah', + 'thermalDogsAndPeople', + 'UnoCards', + 'VehiclesOpenImages', + 'WildfireSmoke', + 'websiteScreenshots', +] + +datasets = [ + dataset_AerialMaritimeDrone_large, dataset_AerialMaritimeDrone_tiled, + dataset_AmericanSignLanguageLetters, dataset_Aquarium, dataset_BCCD, + dataset_boggleBoards, dataset_brackishUnderwater, dataset_ChessPieces, + dataset_CottontailRabbits, dataset_dice, dataset_DroneControl, + dataset_EgoHands_generic, dataset_EgoHands_specific, + dataset_HardHatWorkers, dataset_MaskWearing, dataset_MountainDewCommercial, + dataset_NorthAmericaMushrooms, dataset_openPoetryVision, + dataset_OxfordPets_by_breed, dataset_OxfordPets_by_species, dataset_PKLot, + dataset_Packages, dataset_PascalVOC, dataset_pistols, dataset_plantdoc, + dataset_pothole, dataset_Raccoon, dataset_selfdrivingCar, + dataset_ShellfishOpenImages, dataset_ThermalCheetah, + dataset_thermalDogsAndPeople, dataset_UnoCards, dataset_VehiclesOpenImages, + dataset_WildfireSmoke, dataset_websiteScreenshots +] + +metrics = [ + val_evaluator_AerialMaritimeDrone_large, + val_evaluator_AerialMaritimeDrone_tiled, + val_evaluator_AmericanSignLanguageLetters, val_evaluator_Aquarium, + val_evaluator_BCCD, val_evaluator_boggleBoards, + val_evaluator_brackishUnderwater, val_evaluator_ChessPieces, + val_evaluator_CottontailRabbits, val_evaluator_dice, + val_evaluator_DroneControl, val_evaluator_EgoHands_generic, + val_evaluator_EgoHands_specific, val_evaluator_HardHatWorkers, + val_evaluator_MaskWearing, val_evaluator_MountainDewCommercial, + val_evaluator_NorthAmericaMushrooms, val_evaluator_openPoetryVision, + val_evaluator_OxfordPets_by_breed, val_evaluator_OxfordPets_by_species, + val_evaluator_PKLot, val_evaluator_Packages, val_evaluator_PascalVOC, + val_evaluator_pistols, val_evaluator_plantdoc, val_evaluator_pothole, + val_evaluator_Raccoon, val_evaluator_selfdrivingCar, + val_evaluator_ShellfishOpenImages, val_evaluator_ThermalCheetah, + val_evaluator_thermalDogsAndPeople, val_evaluator_UnoCards, + val_evaluator_VehiclesOpenImages, val_evaluator_WildfireSmoke, + val_evaluator_websiteScreenshots +] + +# -------------------------------------------------# +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py b/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py new file mode 100644 index 00000000..216b8059 --- /dev/null +++ b/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py @@ -0,0 +1,338 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py' # noqa + +dataset_type = 'CocoDataset' +data_root = 'data/odinw/' + +base_test_pipeline = _base_.test_pipeline +base_test_pipeline[-1]['meta_keys'] = ('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'text', + 'custom_entities', 'caption_prompt') + +# ---------------------1 AerialMaritimeDrone---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/large/' +dataset_AerialMaritimeDrone = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + test_mode=True, + pipeline=base_test_pipeline, + return_classes=True) +val_evaluator_AerialMaritimeDrone = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------2 Aquarium---------------------# +class_name = ('fish', 'jellyfish', 'penguin', 'puffin', 'shark', 'starfish', + 'stingray') +metainfo = dict(classes=class_name) +_data_root = data_root + 'Aquarium/Aquarium Combined.v2-raw-1024.coco/' + +caption_prompt = None +# caption_prompt = { +# 'penguin': { +# 'suffix': ', which is black and white' +# }, +# 'puffin': { +# 'suffix': ' with orange beaks' +# }, +# 'stingray': { +# 'suffix': ' which is flat and round' +# }, +# } +dataset_Aquarium = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Aquarium = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------3 CottontailRabbits---------------------# +class_name = ('Cottontail-Rabbit', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'CottontailRabbits/' + +caption_prompt = None +# caption_prompt = {'Cottontail-Rabbit': {'name': 'rabbit'}} + +dataset_CottontailRabbits = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_CottontailRabbits = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------4 EgoHands---------------------# +class_name = ('hand', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/generic/' + +caption_prompt = None +# caption_prompt = {'hand': {'suffix': ' of a person'}} + +dataset_EgoHands = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------5 NorthAmericaMushrooms---------------------# +class_name = ('CoW', 'chanterelle') +metainfo = dict(classes=class_name) +_data_root = data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa + +caption_prompt = None +# caption_prompt = { +# 'CoW': { +# 'name': 'flat mushroom' +# }, +# 'chanterelle': { +# 'name': 'yellow mushroom' +# } +# } + +dataset_NorthAmericaMushrooms = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_NorthAmericaMushrooms = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------6 Packages---------------------# +class_name = ('package', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Packages/Raw/' + +caption_prompt = None +# caption_prompt = { +# 'package': { +# 'prefix': 'there is a ', +# 'suffix': ' on the porch' +# } +# } + +dataset_Packages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Packages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------7 PascalVOC---------------------# +class_name = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', + 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PascalVOC/' +dataset_PascalVOC = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PascalVOC = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------8 pistols---------------------# +class_name = ('pistol', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pistols/export/' +dataset_pistols = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pistols = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------9 pothole---------------------# +class_name = ('pothole', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pothole/' + +caption_prompt = None +# caption_prompt = { +# 'pothole': { +# 'prefix': 'there are some ', +# 'name': 'holes', +# 'suffix': ' on the road' +# } +# } + +dataset_pothole = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pothole = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------10 Raccoon---------------------# +class_name = ('raccoon', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Raccoon/Raccoon.v2-raw.coco/' +dataset_Raccoon = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Raccoon = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------11 ShellfishOpenImages---------------------# +class_name = ('Crab', 'Lobster', 'Shrimp') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ShellfishOpenImages/raw/' +dataset_ShellfishOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ShellfishOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------12 thermalDogsAndPeople---------------------# +class_name = ('dog', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'thermalDogsAndPeople/' +dataset_thermalDogsAndPeople = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_thermalDogsAndPeople = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------13 VehiclesOpenImages---------------------# +class_name = ('Ambulance', 'Bus', 'Car', 'Motorcycle', 'Truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'VehiclesOpenImages/416x416/' +dataset_VehiclesOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_VehiclesOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# --------------------- Config---------------------# +dataset_prefixes = [ + 'AerialMaritimeDrone', 'Aquarium', 'CottontailRabbits', 'EgoHands', + 'NorthAmericaMushrooms', 'Packages', 'PascalVOC', 'pistols', 'pothole', + 'Raccoon', 'ShellfishOpenImages', 'thermalDogsAndPeople', + 'VehiclesOpenImages' +] +datasets = [ + dataset_AerialMaritimeDrone, dataset_Aquarium, dataset_CottontailRabbits, + dataset_EgoHands, dataset_NorthAmericaMushrooms, dataset_Packages, + dataset_PascalVOC, dataset_pistols, dataset_pothole, dataset_Raccoon, + dataset_ShellfishOpenImages, dataset_thermalDogsAndPeople, + dataset_VehiclesOpenImages +] +metrics = [ + val_evaluator_AerialMaritimeDrone, val_evaluator_Aquarium, + val_evaluator_CottontailRabbits, val_evaluator_EgoHands, + val_evaluator_NorthAmericaMushrooms, val_evaluator_Packages, + val_evaluator_PascalVOC, val_evaluator_pistols, val_evaluator_pothole, + val_evaluator_Raccoon, val_evaluator_ShellfishOpenImages, + val_evaluator_thermalDogsAndPeople, val_evaluator_VehiclesOpenImages +] + +# -------------------------------------------------# +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw35.py b/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw35.py new file mode 100644 index 00000000..3df0394a --- /dev/null +++ b/mmdetection/configs/grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw35.py @@ -0,0 +1,796 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py' # noqa + +dataset_type = 'CocoDataset' +data_root = 'data/odinw/' + +base_test_pipeline = _base_.test_pipeline +base_test_pipeline[-1]['meta_keys'] = ('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'text', + 'custom_entities', 'caption_prompt') + +# ---------------------1 AerialMaritimeDrone_large---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/large/' +dataset_AerialMaritimeDrone_large = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AerialMaritimeDrone_large = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------2 AerialMaritimeDrone_tiled---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/tiled/' +dataset_AerialMaritimeDrone_tiled = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AerialMaritimeDrone_tiled = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------3 AmericanSignLanguageLetters---------------------# +class_name = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AmericanSignLanguageLetters/American Sign Language Letters.v1-v1.coco/' # noqa +dataset_AmericanSignLanguageLetters = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AmericanSignLanguageLetters = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------4 Aquarium---------------------# +class_name = ('fish', 'jellyfish', 'penguin', 'puffin', 'shark', 'starfish', + 'stingray') +metainfo = dict(classes=class_name) +_data_root = data_root + 'Aquarium/Aquarium Combined.v2-raw-1024.coco/' +dataset_Aquarium = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Aquarium = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------5 BCCD---------------------# +class_name = ('Platelets', 'RBC', 'WBC') +metainfo = dict(classes=class_name) +_data_root = data_root + 'BCCD/BCCD.v3-raw.coco/' +dataset_BCCD = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_BCCD = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------6 boggleBoards---------------------# +class_name = ('Q', 'a', 'an', 'b', 'c', 'd', 'e', 'er', 'f', 'g', 'h', 'he', + 'i', 'in', 'j', 'k', 'l', 'm', 'n', 'o', 'o ', 'p', 'q', 'qu', + 'r', 's', 't', 't\\', 'th', 'u', 'v', 'w', 'wild', 'x', 'y', 'z') +metainfo = dict(classes=class_name) +_data_root = data_root + 'boggleBoards/416x416AutoOrient/export/' +dataset_boggleBoards = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_boggleBoards = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------7 brackishUnderwater---------------------# +class_name = ('crab', 'fish', 'jellyfish', 'shrimp', 'small_fish', 'starfish') +metainfo = dict(classes=class_name) +_data_root = data_root + 'brackishUnderwater/960x540/' +dataset_brackishUnderwater = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_brackishUnderwater = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------8 ChessPieces---------------------# +class_name = (' ', 'black bishop', 'black king', 'black knight', 'black pawn', + 'black queen', 'black rook', 'white bishop', 'white king', + 'white knight', 'white pawn', 'white queen', 'white rook') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ChessPieces/Chess Pieces.v23-raw.coco/' +dataset_ChessPieces = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ChessPieces = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------9 CottontailRabbits---------------------# +class_name = ('rabbit', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'CottontailRabbits/' +dataset_CottontailRabbits = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_CottontailRabbits = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------10 dice---------------------# +class_name = ('1', '2', '3', '4', '5', '6') +metainfo = dict(classes=class_name) +_data_root = data_root + 'dice/mediumColor/export/' +dataset_dice = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_dice = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------11 DroneControl---------------------# +class_name = ('follow', 'follow_hand', 'land', 'land_hand', 'null', 'object', + 'takeoff', 'takeoff-hand') +metainfo = dict(classes=class_name) +_data_root = data_root + 'DroneControl/Drone Control.v3-raw.coco/' +dataset_DroneControl = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_DroneControl = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------12 EgoHands_generic---------------------# +class_name = ('hand', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/generic/' +caption_prompt = {'hand': {'suffix': ' of a person'}} +dataset_EgoHands_generic = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + # NOTE w. prompt 0.526, wo. prompt 0.608 + # caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands_generic = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------13 EgoHands_specific---------------------# +class_name = ('myleft', 'myright', 'yourleft', 'yourright') +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/specific/' +dataset_EgoHands_specific = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands_specific = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------14 HardHatWorkers---------------------# +class_name = ('head', 'helmet', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'HardHatWorkers/raw/' +dataset_HardHatWorkers = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_HardHatWorkers = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------15 MaskWearing---------------------# +class_name = ('mask', 'no-mask') +metainfo = dict(classes=class_name) +_data_root = data_root + 'MaskWearing/raw/' +dataset_MaskWearing = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_MaskWearing = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------16 MountainDewCommercial---------------------# +class_name = ('bottle', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'MountainDewCommercial/' +dataset_MountainDewCommercial = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_MountainDewCommercial = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------17 NorthAmericaMushrooms---------------------# +class_name = ('flat mushroom', 'yellow mushroom') +metainfo = dict(classes=class_name) +_data_root = data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa +dataset_NorthAmericaMushrooms = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_NorthAmericaMushrooms = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------18 openPoetryVision---------------------# +class_name = ('American Typewriter', 'Andale Mono', 'Apple Chancery', 'Arial', + 'Avenir', 'Baskerville', 'Big Caslon', 'Bradley Hand', + 'Brush Script MT', 'Chalkboard', 'Comic Sans MS', 'Copperplate', + 'Courier', 'Didot', 'Futura', 'Geneva', 'Georgia', 'Gill Sans', + 'Helvetica', 'Herculanum', 'Impact', 'Kefa', 'Lucida Grande', + 'Luminari', 'Marker Felt', 'Menlo', 'Monaco', 'Noteworthy', + 'Optima', 'PT Sans', 'PT Serif', 'Palatino', 'Papyrus', + 'Phosphate', 'Rockwell', 'SF Pro', 'SignPainter', 'Skia', + 'Snell Roundhand', 'Tahoma', 'Times New Roman', 'Trebuchet MS', + 'Verdana') +metainfo = dict(classes=class_name) +_data_root = data_root + 'openPoetryVision/512x512/' +dataset_openPoetryVision = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_openPoetryVision = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------19 OxfordPets_by_breed---------------------# +class_name = ('cat-Abyssinian', 'cat-Bengal', 'cat-Birman', 'cat-Bombay', + 'cat-British_Shorthair', 'cat-Egyptian_Mau', 'cat-Maine_Coon', + 'cat-Persian', 'cat-Ragdoll', 'cat-Russian_Blue', 'cat-Siamese', + 'cat-Sphynx', 'dog-american_bulldog', + 'dog-american_pit_bull_terrier', 'dog-basset_hound', + 'dog-beagle', 'dog-boxer', 'dog-chihuahua', + 'dog-english_cocker_spaniel', 'dog-english_setter', + 'dog-german_shorthaired', 'dog-great_pyrenees', 'dog-havanese', + 'dog-japanese_chin', 'dog-keeshond', 'dog-leonberger', + 'dog-miniature_pinscher', 'dog-newfoundland', 'dog-pomeranian', + 'dog-pug', 'dog-saint_bernard', 'dog-samoyed', + 'dog-scottish_terrier', 'dog-shiba_inu', + 'dog-staffordshire_bull_terrier', 'dog-wheaten_terrier', + 'dog-yorkshire_terrier') +metainfo = dict(classes=class_name) +_data_root = data_root + 'OxfordPets/by-breed/' # noqa +dataset_OxfordPets_by_breed = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_OxfordPets_by_breed = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------20 OxfordPets_by_species---------------------# +class_name = ('cat', 'dog') +metainfo = dict(classes=class_name) +_data_root = data_root + 'OxfordPets/by-species/' # noqa +dataset_OxfordPets_by_species = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_OxfordPets_by_species = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------21 PKLot---------------------# +class_name = ('space-empty', 'space-occupied') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PKLot/640/' # noqa +dataset_PKLot = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PKLot = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------22 Packages---------------------# +class_name = ('package', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Packages/Raw/' +caption_prompt = { + 'package': { + 'prefix': 'there is a ', + 'suffix': ' on the porch' + } +} +dataset_Packages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, # NOTE w. prompt 0.695; wo. prompt 0.687 + test_mode=True, + return_classes=True) +val_evaluator_Packages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------23 PascalVOC---------------------# +class_name = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', + 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PascalVOC/' +dataset_PascalVOC = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PascalVOC = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------24 pistols---------------------# +class_name = ('pistol', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pistols/export/' +dataset_pistols = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pistols = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------25 plantdoc---------------------# +class_name = ('Apple Scab Leaf', 'Apple leaf', 'Apple rust leaf', + 'Bell_pepper leaf', 'Bell_pepper leaf spot', 'Blueberry leaf', + 'Cherry leaf', 'Corn Gray leaf spot', 'Corn leaf blight', + 'Corn rust leaf', 'Peach leaf', 'Potato leaf', + 'Potato leaf early blight', 'Potato leaf late blight', + 'Raspberry leaf', 'Soyabean leaf', 'Soybean leaf', + 'Squash Powdery mildew leaf', 'Strawberry leaf', + 'Tomato Early blight leaf', 'Tomato Septoria leaf spot', + 'Tomato leaf', 'Tomato leaf bacterial spot', + 'Tomato leaf late blight', 'Tomato leaf mosaic virus', + 'Tomato leaf yellow virus', 'Tomato mold leaf', + 'Tomato two spotted spider mites leaf', 'grape leaf', + 'grape leaf black rot') +metainfo = dict(classes=class_name) +_data_root = data_root + 'plantdoc/416x416/' +dataset_plantdoc = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_plantdoc = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------26 pothole---------------------# +class_name = ('pothole', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pothole/' +caption_prompt = { + 'pothole': { + 'name': 'holes', + 'prefix': 'there are some ', + 'suffix': ' on the road' + } +} +dataset_pothole = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + # NOTE w. prompt 0.137; wo. prompt 0.215 + # caption_prompt=caption_prompt, + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pothole = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------27 Raccoon---------------------# +class_name = ('raccoon', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Raccoon/Raccoon.v2-raw.coco/' +dataset_Raccoon = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Raccoon = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------28 selfdrivingCar---------------------# +class_name = ('biker', 'car', 'pedestrian', 'trafficLight', + 'trafficLight-Green', 'trafficLight-GreenLeft', + 'trafficLight-Red', 'trafficLight-RedLeft', + 'trafficLight-Yellow', 'trafficLight-YellowLeft', 'truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'selfdrivingCar/fixedLarge/export/' +dataset_selfdrivingCar = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_selfdrivingCar = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------29 ShellfishOpenImages---------------------# +class_name = ('Crab', 'Lobster', 'Shrimp') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ShellfishOpenImages/raw/' +dataset_ShellfishOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ShellfishOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------30 ThermalCheetah---------------------# +class_name = ('cheetah', 'human') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ThermalCheetah/' +dataset_ThermalCheetah = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ThermalCheetah = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------31 thermalDogsAndPeople---------------------# +class_name = ('dog', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'thermalDogsAndPeople/' +dataset_thermalDogsAndPeople = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_thermalDogsAndPeople = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------32 UnoCards---------------------# +class_name = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', + '12', '13', '14') +metainfo = dict(classes=class_name) +_data_root = data_root + 'UnoCards/raw/' +dataset_UnoCards = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_UnoCards = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------33 VehiclesOpenImages---------------------# +class_name = ('Ambulance', 'Bus', 'Car', 'Motorcycle', 'Truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'VehiclesOpenImages/416x416/' +dataset_VehiclesOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_VehiclesOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------34 WildfireSmoke---------------------# +class_name = ('smoke', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'WildfireSmoke/' +dataset_WildfireSmoke = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_WildfireSmoke = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------35 websiteScreenshots---------------------# +class_name = ('button', 'field', 'heading', 'iframe', 'image', 'label', 'link', + 'text') +metainfo = dict(classes=class_name) +_data_root = data_root + 'websiteScreenshots/' +dataset_websiteScreenshots = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_websiteScreenshots = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# --------------------- Config---------------------# + +dataset_prefixes = [ + 'AerialMaritimeDrone_large', + 'AerialMaritimeDrone_tiled', + 'AmericanSignLanguageLetters', + 'Aquarium', + 'BCCD', + 'boggleBoards', + 'brackishUnderwater', + 'ChessPieces', + 'CottontailRabbits', + 'dice', + 'DroneControl', + 'EgoHands_generic', + 'EgoHands_specific', + 'HardHatWorkers', + 'MaskWearing', + 'MountainDewCommercial', + 'NorthAmericaMushrooms', + 'openPoetryVision', + 'OxfordPets_by_breed', + 'OxfordPets_by_species', + 'PKLot', + 'Packages', + 'PascalVOC', + 'pistols', + 'plantdoc', + 'pothole', + 'Raccoons', + 'selfdrivingCar', + 'ShellfishOpenImages', + 'ThermalCheetah', + 'thermalDogsAndPeople', + 'UnoCards', + 'VehiclesOpenImages', + 'WildfireSmoke', + 'websiteScreenshots', +] + +datasets = [ + dataset_AerialMaritimeDrone_large, dataset_AerialMaritimeDrone_tiled, + dataset_AmericanSignLanguageLetters, dataset_Aquarium, dataset_BCCD, + dataset_boggleBoards, dataset_brackishUnderwater, dataset_ChessPieces, + dataset_CottontailRabbits, dataset_dice, dataset_DroneControl, + dataset_EgoHands_generic, dataset_EgoHands_specific, + dataset_HardHatWorkers, dataset_MaskWearing, dataset_MountainDewCommercial, + dataset_NorthAmericaMushrooms, dataset_openPoetryVision, + dataset_OxfordPets_by_breed, dataset_OxfordPets_by_species, dataset_PKLot, + dataset_Packages, dataset_PascalVOC, dataset_pistols, dataset_plantdoc, + dataset_pothole, dataset_Raccoon, dataset_selfdrivingCar, + dataset_ShellfishOpenImages, dataset_ThermalCheetah, + dataset_thermalDogsAndPeople, dataset_UnoCards, dataset_VehiclesOpenImages, + dataset_WildfireSmoke, dataset_websiteScreenshots +] + +metrics = [ + val_evaluator_AerialMaritimeDrone_large, + val_evaluator_AerialMaritimeDrone_tiled, + val_evaluator_AmericanSignLanguageLetters, val_evaluator_Aquarium, + val_evaluator_BCCD, val_evaluator_boggleBoards, + val_evaluator_brackishUnderwater, val_evaluator_ChessPieces, + val_evaluator_CottontailRabbits, val_evaluator_dice, + val_evaluator_DroneControl, val_evaluator_EgoHands_generic, + val_evaluator_EgoHands_specific, val_evaluator_HardHatWorkers, + val_evaluator_MaskWearing, val_evaluator_MountainDewCommercial, + val_evaluator_NorthAmericaMushrooms, val_evaluator_openPoetryVision, + val_evaluator_OxfordPets_by_breed, val_evaluator_OxfordPets_by_species, + val_evaluator_PKLot, val_evaluator_Packages, val_evaluator_PascalVOC, + val_evaluator_pistols, val_evaluator_plantdoc, val_evaluator_pothole, + val_evaluator_Raccoon, val_evaluator_selfdrivingCar, + val_evaluator_ShellfishOpenImages, val_evaluator_ThermalCheetah, + val_evaluator_thermalDogsAndPeople, val_evaluator_UnoCards, + val_evaluator_VehiclesOpenImages, val_evaluator_WildfireSmoke, + val_evaluator_websiteScreenshots +] + +# -------------------------------------------------# +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/grounding_dino/odinw/override_category.py b/mmdetection/configs/grounding_dino/odinw/override_category.py new file mode 100644 index 00000000..9ff05fc6 --- /dev/null +++ b/mmdetection/configs/grounding_dino/odinw/override_category.py @@ -0,0 +1,109 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse + +import mmengine + + +def parse_args(): + parser = argparse.ArgumentParser(description='Override Category') + parser.add_argument('data_root') + return parser.parse_args() + + +def main(): + args = parse_args() + + ChessPieces = [{ + 'id': 1, + 'name': ' ', + 'supercategory': 'pieces' + }, { + 'id': 2, + 'name': 'black bishop', + 'supercategory': 'pieces' + }, { + 'id': 3, + 'name': 'black king', + 'supercategory': 'pieces' + }, { + 'id': 4, + 'name': 'black knight', + 'supercategory': 'pieces' + }, { + 'id': 5, + 'name': 'black pawn', + 'supercategory': 'pieces' + }, { + 'id': 6, + 'name': 'black queen', + 'supercategory': 'pieces' + }, { + 'id': 7, + 'name': 'black rook', + 'supercategory': 'pieces' + }, { + 'id': 8, + 'name': 'white bishop', + 'supercategory': 'pieces' + }, { + 'id': 9, + 'name': 'white king', + 'supercategory': 'pieces' + }, { + 'id': 10, + 'name': 'white knight', + 'supercategory': 'pieces' + }, { + 'id': 11, + 'name': 'white pawn', + 'supercategory': 'pieces' + }, { + 'id': 12, + 'name': 'white queen', + 'supercategory': 'pieces' + }, { + 'id': 13, + 'name': 'white rook', + 'supercategory': 'pieces' + }] + + _data_root = args.data_root + 'ChessPieces/Chess Pieces.v23-raw.coco/' + json_data = mmengine.load(_data_root + + 'valid/annotations_without_background.json') + json_data['categories'] = ChessPieces + mmengine.dump(json_data, + _data_root + 'valid/new_annotations_without_background.json') + + CottontailRabbits = [{ + 'id': 1, + 'name': 'rabbit', + 'supercategory': 'Cottontail-Rabbit' + }] + + _data_root = args.data_root + 'CottontailRabbits/' + json_data = mmengine.load(_data_root + + 'valid/annotations_without_background.json') + json_data['categories'] = CottontailRabbits + mmengine.dump(json_data, + _data_root + 'valid/new_annotations_without_background.json') + + NorthAmericaMushrooms = [{ + 'id': 1, + 'name': 'flat mushroom', + 'supercategory': 'mushroom' + }, { + 'id': 2, + 'name': 'yellow mushroom', + 'supercategory': 'mushroom' + }] + + _data_root = args.data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa + json_data = mmengine.load(_data_root + + 'valid/annotations_without_background.json') + json_data['categories'] = NorthAmericaMushrooms + mmengine.dump(json_data, + _data_root + 'valid/new_annotations_without_background.json') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/configs/grounding_dino/refcoco/grounding_dino_swin-b_pretrain_zeroshot_refexp.py b/mmdetection/configs/grounding_dino/refcoco/grounding_dino_swin-b_pretrain_zeroshot_refexp.py new file mode 100644 index 00000000..dea0bad0 --- /dev/null +++ b/mmdetection/configs/grounding_dino/refcoco/grounding_dino_swin-b_pretrain_zeroshot_refexp.py @@ -0,0 +1,14 @@ +_base_ = './grounding_dino_swin-t_pretrain_zeroshot_refexp.py' + +model = dict( + type='GroundingDINO', + backbone=dict( + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12, + drop_path_rate=0.3, + patch_norm=True), + neck=dict(in_channels=[256, 512, 1024]), +) diff --git a/mmdetection/configs/grounding_dino/refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp.py b/mmdetection/configs/grounding_dino/refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp.py new file mode 100644 index 00000000..4b5c4657 --- /dev/null +++ b/mmdetection/configs/grounding_dino/refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp.py @@ -0,0 +1,228 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py' + +# 30 is an empirical value, just set it to the maximum value +# without affecting the evaluation result +model = dict(test_cfg=dict(max_per_img=30)) + +data_root = 'data/coco/' + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive')) +] + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/final_refexp_val.json' +val_dataset_all_val = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) +val_evaluator_all_val = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco_testA.json' +val_dataset_refcoco_testA = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testA = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco_testB.json' +val_dataset_refcoco_testB = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testB = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco+_testA.json' +val_dataset_refcoco_plus_testA = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcoco_plus_testA = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco+_testB.json' +val_dataset_refcoco_plus_testB = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcoco_plus_testB = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcocog_test.json' +val_dataset_refcocog_test = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcocog_test = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_grefcoco_val.json' +val_dataset_grefcoco_val = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_grefcoco_val = dict( + type='gRefCOCOMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + thresh_score=0.7, + thresh_f1=1.0) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_grefcoco_testA.json' +val_dataset_grefcoco_testA = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_grefcoco_testA = dict( + type='gRefCOCOMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + thresh_score=0.7, + thresh_f1=1.0) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_grefcoco_testB.json' +val_dataset_grefcoco_testB = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_grefcoco_testB = dict( + type='gRefCOCOMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + thresh_score=0.7, + thresh_f1=1.0) + +# -------------------------------------------------# +datasets = [ + val_dataset_all_val, val_dataset_refcoco_testA, val_dataset_refcoco_testB, + val_dataset_refcoco_plus_testA, val_dataset_refcoco_plus_testB, + val_dataset_refcocog_test, val_dataset_grefcoco_val, + val_dataset_grefcoco_testA, val_dataset_grefcoco_testB +] +dataset_prefixes = [ + 'val', 'refcoco_testA', 'refcoco_testB', 'refcoco+_testA', + 'refcoco+_testB', 'refcocog_test', 'grefcoco_val', 'grefcoco_testA', + 'grefcoco_testB' +] +metrics = [ + val_evaluator_all_val, val_evaluator_refcoco_testA, + val_evaluator_refcoco_testB, val_evaluator_refcoco_plus_testA, + val_evaluator_refcoco_plus_testB, val_evaluator_refcocog_test, + val_evaluator_grefcoco_val, val_evaluator_grefcoco_testA, + val_evaluator_grefcoco_testB +] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/guided_anchoring/README.md b/mmdetection/configs/guided_anchoring/README.md new file mode 100644 index 00000000..1a5e505d --- /dev/null +++ b/mmdetection/configs/guided_anchoring/README.md @@ -0,0 +1,59 @@ +# Guided Anchoring + +> [Region Proposal by Guided Anchoring](https://arxiv.org/abs/1901.03278) + + + +## Abstract + +Region anchors are the cornerstone of modern object detection techniques. State-of-the-art detectors mostly rely on a dense anchoring scheme, where anchors are sampled uniformly over the spatial domain with a predefined set of scales and aspect ratios. In this paper, we revisit this foundational stage. Our study shows that it can be done much more effectively and efficiently. Specifically, we present an alternative scheme, named Guided Anchoring, which leverages semantic features to guide the anchoring. The proposed method jointly predicts the locations where the center of objects of interest are likely to exist as well as the scales and aspect ratios at different locations. On top of predicted anchor shapes, we mitigate the feature inconsistency with a feature adaption module. We also study the use of high-quality proposals to improve detection performance. The anchoring scheme can be seamlessly integrated into proposal methods and detectors. With Guided Anchoring, we achieve 9.1% higher recall on MS COCO with 90% fewer anchors than the RPN baseline. We also adopt Guided Anchoring in Fast R-CNN, Faster R-CNN and RetinaNet, respectively improving the detection mAP by 2.2%, 2.7% and 1.2%. + +
    + +
    + +## Results and Models + +The results on COCO 2017 val is shown in the below table. (results on test-dev are usually slightly higher than val). + +| Method | Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | AR 1000 | Config | Download | +| :----: | :-------------: | :-----: | :-----: | :------: | :------------: | :-----: | :------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| GA-RPN | R-50-FPN | caffe | 1x | 5.3 | 15.8 | 68.4 | [config](./ga-rpn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_r50_caffe_fpn_1x_coco/ga_rpn_r50_caffe_fpn_1x_coco_20200531-899008a6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_r50_caffe_fpn_1x_coco/ga_rpn_r50_caffe_fpn_1x_coco_20200531_011819.log.json) | +| GA-RPN | R-101-FPN | caffe | 1x | 7.3 | 13.0 | 69.5 | [config](./ga-rpn_r101-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_r101_caffe_fpn_1x_coco/ga_rpn_r101_caffe_fpn_1x_coco_20200531-ca9ba8fb.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_r101_caffe_fpn_1x_coco/ga_rpn_r101_caffe_fpn_1x_coco_20200531_011812.log.json) | +| GA-RPN | X-101-32x4d-FPN | pytorch | 1x | 8.5 | 10.0 | 70.6 | [config](./ga-rpn_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_x101_32x4d_fpn_1x_coco/ga_rpn_x101_32x4d_fpn_1x_coco_20200220-c28d1b18.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_x101_32x4d_fpn_1x_coco/ga_rpn_x101_32x4d_fpn_1x_coco_20200220_221326.log.json) | +| GA-RPN | X-101-64x4d-FPN | pytorch | 1x | 7.1 | 7.5 | 71.2 | [config](./ga-rpn_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_x101_64x4d_fpn_1x_coco/ga_rpn_x101_64x4d_fpn_1x_coco_20200225-3c6e1aa2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_x101_64x4d_fpn_1x_coco/ga_rpn_x101_64x4d_fpn_1x_coco_20200225_152704.log.json) | + +| Method | Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------------: | :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :--------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| GA-Faster RCNN | R-50-FPN | caffe | 1x | 5.5 | | 39.6 | [config](./ga-faster-rcnn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_r50_caffe_fpn_1x_coco/ga_faster_r50_caffe_fpn_1x_coco_20200702_000718-a11ccfe6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_r50_caffe_fpn_1x_coco/ga_faster_r50_caffe_fpn_1x_coco_20200702_000718.log.json) | +| GA-Faster RCNN | R-101-FPN | caffe | 1x | 7.5 | | 41.5 | [config](./ga-faster-rcnn_r101-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_r101_caffe_fpn_1x_coco/ga_faster_r101_caffe_fpn_1x_coco_bbox_mAP-0.415_20200505_115528-fb82e499.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_r101_caffe_fpn_1x_coco/ga_faster_r101_caffe_fpn_1x_coco_20200505_115528.log.json) | +| GA-Faster RCNN | X-101-32x4d-FPN | pytorch | 1x | 8.7 | 9.7 | 43.0 | [config](./ga-faster-rcnn_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_x101_32x4d_fpn_1x_coco/ga_faster_x101_32x4d_fpn_1x_coco_20200215-1ded9da3.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_x101_32x4d_fpn_1x_coco/ga_faster_x101_32x4d_fpn_1x_coco_20200215_184547.log.json) | +| GA-Faster RCNN | X-101-64x4d-FPN | pytorch | 1x | 11.8 | 7.3 | 43.9 | [config](./ga-faster-rcnn_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_x101_64x4d_fpn_1x_coco/ga_faster_x101_64x4d_fpn_1x_coco_20200215-0fa7bde7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_x101_64x4d_fpn_1x_coco/ga_faster_x101_64x4d_fpn_1x_coco_20200215_104455.log.json) | +| GA-RetinaNet | R-50-FPN | caffe | 1x | 3.5 | 16.8 | 36.9 | [config](./ga-retinanet_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_r50_caffe_fpn_1x_coco/ga_retinanet_r50_caffe_fpn_1x_coco_20201020-39581c6f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_r50_caffe_fpn_1x_coco/ga_retinanet_r50_caffe_fpn_1x_coco_20201020_225450.log.json) | +| GA-RetinaNet | R-101-FPN | caffe | 1x | 5.5 | 12.9 | 39.0 | [config](./ga-retinanet_r101-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_r101_caffe_fpn_1x_coco/ga_retinanet_r101_caffe_fpn_1x_coco_20200531-6266453c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_r101_caffe_fpn_1x_coco/ga_retinanet_r101_caffe_fpn_1x_coco_20200531_012847.log.json) | +| GA-RetinaNet | X-101-32x4d-FPN | pytorch | 1x | 6.9 | 10.6 | 40.5 | [config](./ga-retinanet_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_x101_32x4d_fpn_1x_coco/ga_retinanet_x101_32x4d_fpn_1x_coco_20200219-40c56caa.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_x101_32x4d_fpn_1x_coco/ga_retinanet_x101_32x4d_fpn_1x_coco_20200219_223025.log.json) | +| GA-RetinaNet | X-101-64x4d-FPN | pytorch | 1x | 9.9 | 7.7 | 41.3 | [config](./ga-retinanet_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_x101_64x4d_fpn_1x_coco/ga_retinanet_x101_64x4d_fpn_1x_coco_20200226-ef9f7f1f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_x101_64x4d_fpn_1x_coco/ga_retinanet_x101_64x4d_fpn_1x_coco_20200226_221123.log.json) | + +- In the Guided Anchoring paper, `score_thr` is set to 0.001 in Fast/Faster RCNN and 0.05 in RetinaNet for both baselines and Guided Anchoring. + +- Performance on COCO test-dev benchmark are shown as follows. + +| Method | Backbone | Style | Lr schd | Aug Train | Score thr | AP | AP_50 | AP_75 | AP_small | AP_medium | AP_large | Download | +| :------------: | :-------: | :---: | :-----: | :-------: | :-------: | :-: | :---: | :---: | :------: | :-------: | :------: | :------: | +| GA-Faster RCNN | R-101-FPN | caffe | 1x | F | 0.05 | | | | | | | | +| GA-Faster RCNN | R-101-FPN | caffe | 1x | F | 0.001 | | | | | | | | +| GA-RetinaNet | R-101-FPN | caffe | 1x | F | 0.05 | | | | | | | | +| GA-RetinaNet | R-101-FPN | caffe | 2x | T | 0.05 | | | | | | | | + +## Citation + +We provide config files to reproduce the results in the CVPR 2019 paper for [Region Proposal by Guided Anchoring](https://arxiv.org/abs/1901.03278). + +```latex +@inproceedings{wang2019region, + title={Region Proposal by Guided Anchoring}, + author={Jiaqi Wang and Kai Chen and Shuo Yang and Chen Change Loy and Dahua Lin}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + year={2019} +} +``` diff --git a/mmdetection/configs/guided_anchoring/ga-fast-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-fast-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..2d0579c5 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-fast-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,66 @@ +_base_ = '../fast_rcnn/fast-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + roi_head=dict( + bbox_head=dict(bbox_coder=dict(target_stds=[0.05, 0.05, 0.1, 0.1]))), + # model training and testing settings + train_cfg=dict( + rcnn=dict( + assigner=dict(pos_iou_thr=0.6, neg_iou_thr=0.6, min_pos_iou=0.6), + sampler=dict(num=256))), + test_cfg=dict(rcnn=dict(score_thr=1e-3))) +dataset_type = 'CocoDataset' +data_root = 'data/coco/' +img_norm_cfg = dict( + mean=[103.530, 116.280, 123.675], std=[1.0, 1.0, 1.0], to_rgb=False) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadProposals', num_max_proposals=300), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', img_scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', flip_ratio=0.5), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='DefaultFormatBundle'), + dict(type='Collect', keys=['img', 'proposals', 'gt_bboxes', 'gt_labels']), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadProposals', num_max_proposals=None), + dict( + type='MultiScaleFlipAug', + img_scale=(1333, 800), + flip=False, + transforms=[ + dict(type='Resize', keep_ratio=True), + dict(type='RandomFlip'), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='ImageToTensor', keys=['img']), + dict(type='Collect', keys=['img', 'proposals']), + ]) +] +# TODO: support loading proposals +data = dict( + train=dict( + proposal_file=data_root + 'proposals/ga_rpn_r50_fpn_1x_train2017.pkl', + pipeline=train_pipeline), + val=dict( + proposal_file=data_root + 'proposals/ga_rpn_r50_fpn_1x_val2017.pkl', + pipeline=test_pipeline), + test=dict( + proposal_file=data_root + 'proposals/ga_rpn_r50_fpn_1x_val2017.pkl', + pipeline=test_pipeline)) +optimizer_config = dict( + _delete_=True, grad_clip=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/guided_anchoring/ga-faster-rcnn_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..f585dc35 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './ga-faster-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/guided_anchoring/ga-faster-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..6cd44de5 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,64 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + rpn_head=dict( + _delete_=True, + type='GARPNHead', + in_channels=256, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=8, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[8], + strides=[4, 8, 16, 32, 64]), + anchor_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.07, 0.07, 0.14, 0.14]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.07, 0.07, 0.11, 0.11]), + loc_filter_thr=0.01, + loss_loc=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_shape=dict(type='BoundedIoULoss', beta=0.2, loss_weight=1.0), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), + roi_head=dict( + bbox_head=dict(bbox_coder=dict(target_stds=[0.05, 0.05, 0.1, 0.1]))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + ga_assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + ignore_iof_thr=-1), + ga_sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + center_ratio=0.2, + ignore_ratio=0.5), + rpn_proposal=dict(nms_post=1000, max_per_img=300), + rcnn=dict( + assigner=dict(pos_iou_thr=0.6, neg_iou_thr=0.6, min_pos_iou=0.6), + sampler=dict(type='RandomSampler', num=256))), + test_cfg=dict( + rpn=dict(nms_post=1000, max_per_img=300), rcnn=dict(score_thr=1e-3))) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/guided_anchoring/ga-faster-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..3007fbec --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,64 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + rpn_head=dict( + _delete_=True, + type='GARPNHead', + in_channels=256, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=8, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[8], + strides=[4, 8, 16, 32, 64]), + anchor_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.07, 0.07, 0.14, 0.14]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.07, 0.07, 0.11, 0.11]), + loc_filter_thr=0.01, + loss_loc=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_shape=dict(type='BoundedIoULoss', beta=0.2, loss_weight=1.0), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), + roi_head=dict( + bbox_head=dict(bbox_coder=dict(target_stds=[0.05, 0.05, 0.1, 0.1]))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + ga_assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + ignore_iof_thr=-1), + ga_sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + center_ratio=0.2, + ignore_ratio=0.5), + rpn_proposal=dict(nms_post=1000, max_per_img=300), + rcnn=dict( + assigner=dict(pos_iou_thr=0.6, neg_iou_thr=0.6, min_pos_iou=0.6), + sampler=dict(type='RandomSampler', num=256))), + test_cfg=dict( + rpn=dict(nms_post=1000, max_per_img=300), rcnn=dict(score_thr=1e-3))) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/guided_anchoring/ga-faster-rcnn_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..8a22a1ec --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './ga-faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/guided_anchoring/ga-faster-rcnn_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..3d6aaeaa --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-faster-rcnn_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './ga-faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/guided_anchoring/ga-retinanet_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-retinanet_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..9adbae55 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-retinanet_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './ga-retinanet_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/guided_anchoring/ga-retinanet_r101-caffe_fpn_ms-2x.py b/mmdetection/configs/guided_anchoring/ga-retinanet_r101-caffe_fpn_ms-2x.py new file mode 100644 index 00000000..012e89b8 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-retinanet_r101-caffe_fpn_ms-2x.py @@ -0,0 +1,34 @@ +_base_ = './ga-retinanet_r101-caffe_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 480), (1333, 960)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# learning policy +max_epochs = 24 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 3.0, + by_epoch=False, + begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..b62aba62 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,61 @@ +_base_ = '../retinanet/retinanet_r50-caffe_fpn_1x_coco.py' +model = dict( + bbox_head=dict( + _delete_=True, + type='GARetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[4], + strides=[8, 16, 32, 64, 128]), + anchor_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loc_filter_thr=0.01, + loss_loc=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_shape=dict(type='BoundedIoULoss', beta=0.2, loss_weight=1.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=0.04, loss_weight=1.0)), + # training and testing settings + train_cfg=dict( + ga_assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0.4, + ignore_iof_thr=-1), + ga_sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + assigner=dict(neg_iou_thr=0.5, min_pos_iou=0.0), + center_ratio=0.2, + ignore_ratio=0.5)) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/guided_anchoring/ga-retinanet_r50_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-retinanet_r50_fpn_1x_coco.py new file mode 100644 index 00000000..da39c700 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-retinanet_r50_fpn_1x_coco.py @@ -0,0 +1,61 @@ +_base_ = '../retinanet/retinanet_r50_fpn_1x_coco.py' +model = dict( + bbox_head=dict( + _delete_=True, + type='GARetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[4], + strides=[8, 16, 32, 64, 128]), + anchor_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loc_filter_thr=0.01, + loss_loc=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_shape=dict(type='BoundedIoULoss', beta=0.2, loss_weight=1.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=0.04, loss_weight=1.0)), + # training and testing settings + train_cfg=dict( + ga_assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0.4, + ignore_iof_thr=-1), + ga_sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + assigner=dict(neg_iou_thr=0.5, min_pos_iou=0.0), + center_ratio=0.2, + ignore_ratio=0.5)) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/guided_anchoring/ga-retinanet_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-retinanet_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..478a8e5e --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-retinanet_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './ga-retinanet_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/guided_anchoring/ga-retinanet_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-retinanet_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..cb7721d3 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-retinanet_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './ga-retinanet_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/guided_anchoring/ga-rpn_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-rpn_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..b375c874 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-rpn_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = './ga-rpn_r50-caffe_fpn_1x_coco.py' +# model settings +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/guided_anchoring/ga-rpn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-rpn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..aa58426e --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-rpn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,57 @@ +_base_ = '../rpn/rpn_r50-caffe_fpn_1x_coco.py' +model = dict( + rpn_head=dict( + _delete_=True, + type='GARPNHead', + in_channels=256, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=8, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[8], + strides=[4, 8, 16, 32, 64]), + anchor_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.07, 0.07, 0.14, 0.14]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.07, 0.07, 0.11, 0.11]), + loc_filter_thr=0.01, + loss_loc=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_shape=dict(type='BoundedIoULoss', beta=0.2, loss_weight=1.0), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict( + rpn=dict( + ga_assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + ignore_iof_thr=-1), + ga_sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + center_ratio=0.2, + ignore_ratio=0.5)), + test_cfg=dict(rpn=dict(nms_post=1000))) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/guided_anchoring/ga-rpn_r50_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-rpn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..2973f272 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-rpn_r50_fpn_1x_coco.py @@ -0,0 +1,57 @@ +_base_ = '../rpn/rpn_r50_fpn_1x_coco.py' +model = dict( + rpn_head=dict( + _delete_=True, + type='GARPNHead', + in_channels=256, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=8, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[8], + strides=[4, 8, 16, 32, 64]), + anchor_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.07, 0.07, 0.14, 0.14]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.07, 0.07, 0.11, 0.11]), + loc_filter_thr=0.01, + loss_loc=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_shape=dict(type='BoundedIoULoss', beta=0.2, loss_weight=1.0), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict( + rpn=dict( + ga_assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + ignore_iof_thr=-1), + ga_sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + center_ratio=0.2, + ignore_ratio=0.5)), + test_cfg=dict(rpn=dict(nms_post=1000))) +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/guided_anchoring/ga-rpn_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-rpn_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..276d45d8 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-rpn_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './ga-rpn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/guided_anchoring/ga-rpn_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/guided_anchoring/ga-rpn_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..f29fe9aa --- /dev/null +++ b/mmdetection/configs/guided_anchoring/ga-rpn_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './ga-rpn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/guided_anchoring/metafile.yml b/mmdetection/configs/guided_anchoring/metafile.yml new file mode 100644 index 00000000..516b3e93 --- /dev/null +++ b/mmdetection/configs/guided_anchoring/metafile.yml @@ -0,0 +1,246 @@ +Collections: + - Name: Guided Anchoring + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - Guided Anchoring + - ResNet + Paper: + URL: https://arxiv.org/abs/1901.03278 + Title: 'Region Proposal by Guided Anchoring' + README: configs/guided_anchoring/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/dense_heads/ga_retina_head.py#L10 + Version: v2.0.0 + +Models: + - Name: ga-rpn_r50-caffe_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-rpn_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.3 + inference time (ms/im): + - value: 63.29 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Region Proposal + Dataset: COCO + Metrics: + AR@1000: 68.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_r50_caffe_fpn_1x_coco/ga_rpn_r50_caffe_fpn_1x_coco_20200531-899008a6.pth + + - Name: ga-rpn_r101-caffe_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-rpn_r101-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.3 + inference time (ms/im): + - value: 76.92 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Region Proposal + Dataset: COCO + Metrics: + AR@1000: 69.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_r101_caffe_fpn_1x_coco/ga_rpn_r101_caffe_fpn_1x_coco_20200531-ca9ba8fb.pth + + - Name: ga-rpn_x101-32x4d_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-rpn_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.5 + inference time (ms/im): + - value: 100 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Region Proposal + Dataset: COCO + Metrics: + AR@1000: 70.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_x101_32x4d_fpn_1x_coco/ga_rpn_x101_32x4d_fpn_1x_coco_20200220-c28d1b18.pth + + - Name: ga-rpn_x101-64x4d_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-rpn_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.1 + inference time (ms/im): + - value: 133.33 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Region Proposal + Dataset: COCO + Metrics: + AR@1000: 70.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_rpn_x101_64x4d_fpn_1x_coco/ga_rpn_x101_64x4d_fpn_1x_coco_20200225-3c6e1aa2.pth + + - Name: ga-faster-rcnn_r50-caffe_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-faster-rcnn_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.5 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_r50_caffe_fpn_1x_coco/ga_faster_r50_caffe_fpn_1x_coco_20200702_000718-a11ccfe6.pth + + - Name: ga-faster-rcnn_r101-caffe_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-faster-rcnn_r101-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.5 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_r101_caffe_fpn_1x_coco/ga_faster_r101_caffe_fpn_1x_coco_bbox_mAP-0.415_20200505_115528-fb82e499.pth + + - Name: ga-faster-rcnn_x101-32x4d_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-faster-rcnn_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.7 + inference time (ms/im): + - value: 103.09 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_x101_32x4d_fpn_1x_coco/ga_faster_x101_32x4d_fpn_1x_coco_20200215-1ded9da3.pth + + - Name: ga-faster-rcnn_x101-64x4d_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-faster-rcnn_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 11.8 + inference time (ms/im): + - value: 136.99 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_faster_x101_64x4d_fpn_1x_coco/ga_faster_x101_64x4d_fpn_1x_coco_20200215-0fa7bde7.pth + + - Name: ga-retinanet_r50-caffe_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-retinanet_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.5 + inference time (ms/im): + - value: 59.52 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_r50_caffe_fpn_1x_coco/ga_retinanet_r50_caffe_fpn_1x_coco_20201020-39581c6f.pth + + - Name: ga-retinanet_r101-caffe_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-retinanet_r101-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.5 + inference time (ms/im): + - value: 77.52 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_r101_caffe_fpn_1x_coco/ga_retinanet_r101_caffe_fpn_1x_coco_20200531-6266453c.pth + + - Name: ga-retinanet_x101-32x4d_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-retinanet_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.9 + inference time (ms/im): + - value: 94.34 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_x101_32x4d_fpn_1x_coco/ga_retinanet_x101_32x4d_fpn_1x_coco_20200219-40c56caa.pth + + - Name: ga-retinanet_x101-64x4d_fpn_1x_coco + In Collection: Guided Anchoring + Config: configs/guided_anchoring/ga-retinanet_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 9.9 + inference time (ms/im): + - value: 129.87 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/guided_anchoring/ga_retinanet_x101_64x4d_fpn_1x_coco/ga_retinanet_x101_64x4d_fpn_1x_coco_20200226-ef9f7f1f.pth diff --git a/mmdetection/configs/hrnet/README.md b/mmdetection/configs/hrnet/README.md new file mode 100644 index 00000000..fc1ed0cc --- /dev/null +++ b/mmdetection/configs/hrnet/README.md @@ -0,0 +1,101 @@ +# HRNet + +> [Deep High-Resolution Representation Learning for Human Pose Estimation](https://arxiv.org/abs/1902.09212) + + + +## Abstract + +This is an official pytorch implementation of Deep High-Resolution Representation Learning for Human Pose Estimation. In this work, we are interested in the human pose estimation problem with a focus on learning reliable high-resolution representations. Most existing methods recover high-resolution representations from low-resolution representations produced by a high-to-low resolution network. Instead, our proposed network maintains high-resolution representations through the whole process. We start from a high-resolution subnetwork as the first stage, gradually add high-to-low resolution subnetworks one by one to form more stages, and connect the mutli-resolution subnetworks in parallel. We conduct repeated multi-scale fusions such that each of the high-to-low resolution representations receives information from other parallel representations over and over, leading to rich high-resolution representations. As a result, the predicted keypoint heatmap is potentially more accurate and spatially more precise. We empirically demonstrate the effectiveness of our network through the superior pose estimation results over two benchmark datasets: the COCO keypoint detection dataset and the MPII Human Pose dataset. + +High-resolution representation learning plays an essential role in many vision problems, e.g., pose estimation and semantic segmentation. The high-resolution network (HRNet), recently developed for human pose estimation, maintains high-resolution representations through the whole process by connecting high-to-low resolution convolutions in parallel and produces strong high-resolution representations by repeatedly conducting fusions across parallel convolutions. +In this paper, we conduct a further study on high-resolution representations by introducing a simple yet effective modification and apply it to a wide range of vision tasks. We augment the high-resolution representation by aggregating the (upsampled) representations from all the parallel convolutions rather than only the representation from the high-resolution convolution as done in HRNet. This simple modification leads to stronger representations, evidenced by superior results. We show top results in semantic segmentation on Cityscapes, LIP, and PASCAL Context, and facial landmark detection on AFLW, COFW, 300W, and WFLW. In addition, we build a multi-level representation from the high-resolution representation and apply it to the Faster R-CNN object detection framework and the extended frameworks. The proposed approach achieves superior results to existing single-model networks on COCO object detection. + +
    + +
    + +## Results and Models + +### Faster R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :----------: | :-----: | :-----: | :------: | :------------: | :----: | :---------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| HRNetV2p-W18 | pytorch | 1x | 6.6 | 13.4 | 36.9 | [config](./faster-rcnn_hrnetv2p-w18-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w18_1x_coco/faster_rcnn_hrnetv2p_w18_1x_coco_20200130-56651a6d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w18_1x_coco/faster_rcnn_hrnetv2p_w18_1x_coco_20200130_211246.log.json) | +| HRNetV2p-W18 | pytorch | 2x | 6.6 | - | 38.9 | [config](./faster-rcnn_hrnetv2p-w18-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w18_2x_coco/faster_rcnn_hrnetv2p_w18_2x_coco_20200702_085731-a4ec0611.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w18_2x_coco/faster_rcnn_hrnetv2p_w18_2x_coco_20200702_085731.log.json) | +| HRNetV2p-W32 | pytorch | 1x | 9.0 | 12.4 | 40.2 | [config](./faster-rcnn_hrnetv2p-w32-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w32_1x_coco/faster_rcnn_hrnetv2p_w32_1x_coco_20200130-6e286425.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w32_1x_coco/faster_rcnn_hrnetv2p_w32_1x_coco_20200130_204442.log.json) | +| HRNetV2p-W32 | pytorch | 2x | 9.0 | - | 41.4 | [config](./faster-rcnn_hrnetv2p-w32_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w32_2x_coco/faster_rcnn_hrnetv2p_w32_2x_coco_20200529_015927-976a9c15.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w32_2x_coco/faster_rcnn_hrnetv2p_w32_2x_coco_20200529_015927.log.json) | +| HRNetV2p-W40 | pytorch | 1x | 10.4 | 10.5 | 41.2 | [config](./faster-rcnn_hrnetv2p-w40-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w40_1x_coco/faster_rcnn_hrnetv2p_w40_1x_coco_20200210-95c1f5ce.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w40_1x_coco/faster_rcnn_hrnetv2p_w40_1x_coco_20200210_125315.log.json) | +| HRNetV2p-W40 | pytorch | 2x | 10.4 | - | 42.1 | [config](./faster-rcnn_hrnetv2p-w40_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w40_2x_coco/faster_rcnn_hrnetv2p_w40_2x_coco_20200512_161033-0f236ef4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w40_2x_coco/faster_rcnn_hrnetv2p_w40_2x_coco_20200512_161033.log.json) | + +### Mask R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :----------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :-------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| HRNetV2p-W18 | pytorch | 1x | 7.0 | 11.7 | 37.7 | 34.2 | [config](./mask-rcnn_hrnetv2p-w18-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w18_1x_coco/mask_rcnn_hrnetv2p_w18_1x_coco_20200205-1c3d78ed.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w18_1x_coco/mask_rcnn_hrnetv2p_w18_1x_coco_20200205_232523.log.json) | +| HRNetV2p-W18 | pytorch | 2x | 7.0 | - | 39.8 | 36.0 | [config](./mask-rcnn_hrnetv2p-w18-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w18_2x_coco/mask_rcnn_hrnetv2p_w18_2x_coco_20200212-b3c825b1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w18_2x_coco/mask_rcnn_hrnetv2p_w18_2x_coco_20200212_134222.log.json) | +| HRNetV2p-W32 | pytorch | 1x | 9.4 | 11.3 | 41.2 | 37.1 | [config](./mask-rcnn_hrnetv2p-w32-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w32_1x_coco/mask_rcnn_hrnetv2p_w32_1x_coco_20200207-b29f616e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w32_1x_coco/mask_rcnn_hrnetv2p_w32_1x_coco_20200207_055017.log.json) | +| HRNetV2p-W32 | pytorch | 2x | 9.4 | - | 42.5 | 37.8 | [config](./mask-rcnn_hrnetv2p-w32-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w32_2x_coco/mask_rcnn_hrnetv2p_w32_2x_coco_20200213-45b75b4d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w32_2x_coco/mask_rcnn_hrnetv2p_w32_2x_coco_20200213_150518.log.json) | +| HRNetV2p-W40 | pytorch | 1x | 10.9 | | 42.1 | 37.5 | [config](./mask-rcnn_hrnetv2p-w40_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w40_1x_coco/mask_rcnn_hrnetv2p_w40_1x_coco_20200511_015646-66738b35.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w40_1x_coco/mask_rcnn_hrnetv2p_w40_1x_coco_20200511_015646.log.json) | +| HRNetV2p-W40 | pytorch | 2x | 10.9 | | 42.8 | 38.2 | [config](./mask-rcnn_hrnetv2p-w40-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w40_2x_coco/mask_rcnn_hrnetv2p_w40_2x_coco_20200512_163732-aed5e4ab.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w40_2x_coco/mask_rcnn_hrnetv2p_w40_2x_coco_20200512_163732.log.json) | + +### Cascade R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :----------: | :-----: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| HRNetV2p-W18 | pytorch | 20e | 7.0 | 11.0 | 41.2 | [config](./cascade-rcnn_hrnetv2p-w18-20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_rcnn_hrnetv2p_w18_20e_coco/cascade_rcnn_hrnetv2p_w18_20e_coco_20200210-434be9d7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_rcnn_hrnetv2p_w18_20e_coco/cascade_rcnn_hrnetv2p_w18_20e_coco_20200210_105632.log.json) | +| HRNetV2p-W32 | pytorch | 20e | 9.4 | 11.0 | 43.3 | [config](./cascade-rcnn_hrnetv2p-w32-20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_rcnn_hrnetv2p_w32_20e_coco/cascade_rcnn_hrnetv2p_w32_20e_coco_20200208-928455a4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_rcnn_hrnetv2p_w32_20e_coco/cascade_rcnn_hrnetv2p_w32_20e_coco_20200208_160511.log.json) | +| HRNetV2p-W40 | pytorch | 20e | 10.8 | | 43.8 | [config](./cascade-rcnn_hrnetv2p-w40-20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_rcnn_hrnetv2p_w40_20e_coco/cascade_rcnn_hrnetv2p_w40_20e_coco_20200512_161112-75e47b04.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_rcnn_hrnetv2p_w40_20e_coco/cascade_rcnn_hrnetv2p_w40_20e_coco_20200512_161112.log.json) | + +### Cascade Mask R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :----------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :----------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| HRNetV2p-W18 | pytorch | 20e | 8.5 | 8.5 | 41.6 | 36.4 | [config](./cascade-mask-rcnn_hrnetv2p-w18_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_mask_rcnn_hrnetv2p_w18_20e_coco/cascade_mask_rcnn_hrnetv2p_w18_20e_coco_20200210-b543cd2b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_mask_rcnn_hrnetv2p_w18_20e_coco/cascade_mask_rcnn_hrnetv2p_w18_20e_coco_20200210_093149.log.json) | +| HRNetV2p-W32 | pytorch | 20e | | 8.3 | 44.3 | 38.6 | [config](./cascade-mask-rcnn_hrnetv2p-w32_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_mask_rcnn_hrnetv2p_w32_20e_coco/cascade_mask_rcnn_hrnetv2p_w32_20e_coco_20200512_154043-39d9cf7b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_mask_rcnn_hrnetv2p_w32_20e_coco/cascade_mask_rcnn_hrnetv2p_w32_20e_coco_20200512_154043.log.json) | +| HRNetV2p-W40 | pytorch | 20e | 12.5 | | 45.1 | 39.3 | [config](./cascade-mask-rcnn_hrnetv2p-w40-20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_mask_rcnn_hrnetv2p_w40_20e_coco/cascade_mask_rcnn_hrnetv2p_w40_20e_coco_20200527_204922-969c4610.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_mask_rcnn_hrnetv2p_w40_20e_coco/cascade_mask_rcnn_hrnetv2p_w40_20e_coco_20200527_204922.log.json) | + +### Hybrid Task Cascade (HTC) + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :----------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :--------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| HRNetV2p-W18 | pytorch | 20e | 10.8 | 4.7 | 42.8 | 37.9 | [config](./htc_hrnetv2p-w18_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/htc_hrnetv2p_w18_20e_coco/htc_hrnetv2p_w18_20e_coco_20200210-b266988c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/htc_hrnetv2p_w18_20e_coco/htc_hrnetv2p_w18_20e_coco_20200210_182735.log.json) | +| HRNetV2p-W32 | pytorch | 20e | 13.1 | 4.9 | 45.4 | 39.9 | [config](./htc_hrnetv2p-w32_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/htc_hrnetv2p_w32_20e_coco/htc_hrnetv2p_w32_20e_coco_20200207-7639fa12.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/htc_hrnetv2p_w32_20e_coco/htc_hrnetv2p_w32_20e_coco_20200207_193153.log.json) | +| HRNetV2p-W40 | pytorch | 20e | 14.6 | | 46.4 | 40.8 | [config](./htc_hrnetv2p-w40_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/htc_hrnetv2p_w40_20e_coco/htc_hrnetv2p_w40_20e_coco_20200529_183411-417c4d5b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/htc_hrnetv2p_w40_20e_coco/htc_hrnetv2p_w40_20e_coco_20200529_183411.log.json) | + +### FCOS + +| Backbone | Style | GN | MS train | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :----------: | :-----: | :-: | :------: | :-----: | :------: | :------------: | :----: | :--------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| HRNetV2p-W18 | pytorch | Y | N | 1x | 13.0 | 12.9 | 35.3 | [config](./fcos_hrnetv2p-w18-gn-head_4xb4-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w18_gn-head_4x4_1x_coco/fcos_hrnetv2p_w18_gn-head_4x4_1x_coco_20201212_100710-4ad151de.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w18_gn-head_4x4_1x_coco/fcos_hrnetv2p_w18_gn-head_4x4_1x_coco_20201212_100710.log.json) | +| HRNetV2p-W18 | pytorch | Y | N | 2x | 13.0 | - | 38.2 | [config](./fcos_hrnetv2p-w18-gn-head_4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w18_gn-head_4x4_2x_coco/fcos_hrnetv2p_w18_gn-head_4x4_2x_coco_20201212_101110-5c575fa5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w18_gn-head_4x4_2x_coco/fcos_hrnetv2p_w18_gn-head_4x4_2x_coco_20201212_101110.log.json) | +| HRNetV2p-W32 | pytorch | Y | N | 1x | 17.5 | 12.9 | 39.5 | [config](./fcos_hrnetv2p-w32-gn-head_4xb4-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w32_gn-head_4x4_1x_coco/fcos_hrnetv2p_w32_gn-head_4x4_1x_coco_20201211_134730-cb8055c0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w32_gn-head_4x4_1x_coco/fcos_hrnetv2p_w32_gn-head_4x4_1x_coco_20201211_134730.log.json) | +| HRNetV2p-W32 | pytorch | Y | N | 2x | 17.5 | - | 40.8 | [config](./fcos_hrnetv2p-w32-gn-head_4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w32_gn-head_4x4_2x_coco/fcos_hrnetv2p_w32_gn-head_4x4_2x_coco_20201212_112133-77b6b9bb.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w32_gn-head_4x4_2x_coco/fcos_hrnetv2p_w32_gn-head_4x4_2x_coco_20201212_112133.log.json) | +| HRNetV2p-W18 | pytorch | Y | Y | 2x | 13.0 | 12.9 | 38.3 | [config](./fcos_hrnetv2p-w18-gn-head_ms-640-800-4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w18_gn-head_mstrain_640-800_4x4_2x_coco/fcos_hrnetv2p_w18_gn-head_mstrain_640-800_4x4_2x_coco_20201212_111651-441e9d9f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w18_gn-head_mstrain_640-800_4x4_2x_coco/fcos_hrnetv2p_w18_gn-head_mstrain_640-800_4x4_2x_coco_20201212_111651.log.json) | +| HRNetV2p-W32 | pytorch | Y | Y | 2x | 17.5 | 12.4 | 41.9 | [config](./fcos_hrnetv2p-w32-gn-head_ms-640-800-4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w32_gn-head_mstrain_640-800_4x4_2x_coco/fcos_hrnetv2p_w32_gn-head_mstrain_640-800_4x4_2x_coco_20201212_090846-b6f2b49f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w32_gn-head_mstrain_640-800_4x4_2x_coco/fcos_hrnetv2p_w32_gn-head_mstrain_640-800_4x4_2x_coco_20201212_090846.log.json) | +| HRNetV2p-W48 | pytorch | Y | Y | 2x | 20.3 | 10.8 | 42.7 | [config](./fcos_hrnetv2p-w40-gn-head_ms-640-800-4xb4-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w40_gn-head_mstrain_640-800_4x4_2x_coco/fcos_hrnetv2p_w40_gn-head_mstrain_640-800_4x4_2x_coco_20201212_124752-f22d2ce5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w40_gn-head_mstrain_640-800_4x4_2x_coco/fcos_hrnetv2p_w40_gn-head_mstrain_640-800_4x4_2x_coco_20201212_124752.log.json) | + +**Note:** + +- The `28e` schedule in HTC indicates decreasing the lr at 24 and 27 epochs, with a total of 28 epochs. +- HRNetV2 ImageNet pretrained models are in [HRNets for Image Classification](https://github.com/HRNet/HRNet-Image-Classification). + +## Citation + +```latex +@inproceedings{SunXLW19, + title={Deep High-Resolution Representation Learning for Human Pose Estimation}, + author={Ke Sun and Bin Xiao and Dong Liu and Jingdong Wang}, + booktitle={CVPR}, + year={2019} +} + +@article{SunZJCXLMWLW19, + title={High-Resolution Representations for Labeling Pixels and Regions}, + author={Ke Sun and Yang Zhao and Borui Jiang and Tianheng Cheng and Bin Xiao + and Dong Liu and Yadong Mu and Xinggang Wang and Wenyu Liu and Jingdong Wang}, + journal = {CoRR}, + volume = {abs/1904.04514}, + year={2019} +} +``` diff --git a/mmdetection/configs/hrnet/cascade-mask-rcnn_hrnetv2p-w18_20e_coco.py b/mmdetection/configs/hrnet/cascade-mask-rcnn_hrnetv2p-w18_20e_coco.py new file mode 100644 index 00000000..5ca0ebfe --- /dev/null +++ b/mmdetection/configs/hrnet/cascade-mask-rcnn_hrnetv2p-w18_20e_coco.py @@ -0,0 +1,11 @@ +_base_ = './cascade-mask-rcnn_hrnetv2p-w32_20e_coco.py' +# model settings +model = dict( + backbone=dict( + extra=dict( + stage2=dict(num_channels=(18, 36)), + stage3=dict(num_channels=(18, 36, 72)), + stage4=dict(num_channels=(18, 36, 72, 144))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w18')), + neck=dict(type='HRFPN', in_channels=[18, 36, 72, 144], out_channels=256)) diff --git a/mmdetection/configs/hrnet/cascade-mask-rcnn_hrnetv2p-w32_20e_coco.py b/mmdetection/configs/hrnet/cascade-mask-rcnn_hrnetv2p-w32_20e_coco.py new file mode 100644 index 00000000..1ffedc39 --- /dev/null +++ b/mmdetection/configs/hrnet/cascade-mask-rcnn_hrnetv2p-w32_20e_coco.py @@ -0,0 +1,51 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + _delete_=True, + type='HRNet', + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w32')), + neck=dict( + _delete_=True, + type='HRFPN', + in_channels=[32, 64, 128, 256], + out_channels=256)) +# learning policy +max_epochs = 20 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 19], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/cascade-mask-rcnn_hrnetv2p-w40-20e_coco.py b/mmdetection/configs/hrnet/cascade-mask-rcnn_hrnetv2p-w40-20e_coco.py new file mode 100644 index 00000000..4a51a024 --- /dev/null +++ b/mmdetection/configs/hrnet/cascade-mask-rcnn_hrnetv2p-w40-20e_coco.py @@ -0,0 +1,12 @@ +_base_ = './cascade-mask-rcnn_hrnetv2p-w32_20e_coco.py' +# model settings +model = dict( + backbone=dict( + type='HRNet', + extra=dict( + stage2=dict(num_channels=(40, 80)), + stage3=dict(num_channels=(40, 80, 160)), + stage4=dict(num_channels=(40, 80, 160, 320))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w40')), + neck=dict(type='HRFPN', in_channels=[40, 80, 160, 320], out_channels=256)) diff --git a/mmdetection/configs/hrnet/cascade-rcnn_hrnetv2p-w18-20e_coco.py b/mmdetection/configs/hrnet/cascade-rcnn_hrnetv2p-w18-20e_coco.py new file mode 100644 index 00000000..8834c1d4 --- /dev/null +++ b/mmdetection/configs/hrnet/cascade-rcnn_hrnetv2p-w18-20e_coco.py @@ -0,0 +1,11 @@ +_base_ = './cascade-rcnn_hrnetv2p-w32-20e_coco.py' +# model settings +model = dict( + backbone=dict( + extra=dict( + stage2=dict(num_channels=(18, 36)), + stage3=dict(num_channels=(18, 36, 72)), + stage4=dict(num_channels=(18, 36, 72, 144))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w18')), + neck=dict(type='HRFPN', in_channels=[18, 36, 72, 144], out_channels=256)) diff --git a/mmdetection/configs/hrnet/cascade-rcnn_hrnetv2p-w32-20e_coco.py b/mmdetection/configs/hrnet/cascade-rcnn_hrnetv2p-w32-20e_coco.py new file mode 100644 index 00000000..afeb75db --- /dev/null +++ b/mmdetection/configs/hrnet/cascade-rcnn_hrnetv2p-w32-20e_coco.py @@ -0,0 +1,51 @@ +_base_ = '../cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + _delete_=True, + type='HRNet', + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w32')), + neck=dict( + _delete_=True, + type='HRFPN', + in_channels=[32, 64, 128, 256], + out_channels=256)) +# learning policy +max_epochs = 20 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 19], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/cascade-rcnn_hrnetv2p-w40-20e_coco.py b/mmdetection/configs/hrnet/cascade-rcnn_hrnetv2p-w40-20e_coco.py new file mode 100644 index 00000000..66f8882a --- /dev/null +++ b/mmdetection/configs/hrnet/cascade-rcnn_hrnetv2p-w40-20e_coco.py @@ -0,0 +1,12 @@ +_base_ = './cascade-rcnn_hrnetv2p-w32-20e_coco.py' +# model settings +model = dict( + backbone=dict( + type='HRNet', + extra=dict( + stage2=dict(num_channels=(40, 80)), + stage3=dict(num_channels=(40, 80, 160)), + stage4=dict(num_channels=(40, 80, 160, 320))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w40')), + neck=dict(type='HRFPN', in_channels=[40, 80, 160, 320], out_channels=256)) diff --git a/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py new file mode 100644 index 00000000..ee9a6986 --- /dev/null +++ b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py @@ -0,0 +1,11 @@ +_base_ = './faster-rcnn_hrnetv2p-w32-1x_coco.py' +# model settings +model = dict( + backbone=dict( + extra=dict( + stage2=dict(num_channels=(18, 36)), + stage3=dict(num_channels=(18, 36, 72)), + stage4=dict(num_channels=(18, 36, 72, 144))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w18')), + neck=dict(type='HRFPN', in_channels=[18, 36, 72, 144], out_channels=256)) diff --git a/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w18-2x_coco.py b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w18-2x_coco.py new file mode 100644 index 00000000..0b72c68f --- /dev/null +++ b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w18-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './faster-rcnn_hrnetv2p-w18-1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w32-1x_coco.py b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w32-1x_coco.py new file mode 100644 index 00000000..a27ad06c --- /dev/null +++ b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w32-1x_coco.py @@ -0,0 +1,37 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + _delete_=True, + type='HRNet', + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w32')), + neck=dict( + _delete_=True, + type='HRFPN', + in_channels=[32, 64, 128, 256], + out_channels=256)) diff --git a/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w32_2x_coco.py b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w32_2x_coco.py new file mode 100644 index 00000000..c9568ce6 --- /dev/null +++ b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w32_2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './faster-rcnn_hrnetv2p-w32-1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w40-1x_coco.py b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w40-1x_coco.py new file mode 100644 index 00000000..b3620023 --- /dev/null +++ b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w40-1x_coco.py @@ -0,0 +1,11 @@ +_base_ = './faster-rcnn_hrnetv2p-w32-1x_coco.py' +model = dict( + backbone=dict( + type='HRNet', + extra=dict( + stage2=dict(num_channels=(40, 80)), + stage3=dict(num_channels=(40, 80, 160)), + stage4=dict(num_channels=(40, 80, 160, 320))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w40')), + neck=dict(type='HRFPN', in_channels=[40, 80, 160, 320], out_channels=256)) diff --git a/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w40_2x_coco.py b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w40_2x_coco.py new file mode 100644 index 00000000..d1b45355 --- /dev/null +++ b/mmdetection/configs/hrnet/faster-rcnn_hrnetv2p-w40_2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './faster-rcnn_hrnetv2p-w40-1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/fcos_hrnetv2p-w18-gn-head_4xb4-1x_coco.py b/mmdetection/configs/hrnet/fcos_hrnetv2p-w18-gn-head_4xb4-1x_coco.py new file mode 100644 index 00000000..c20ca776 --- /dev/null +++ b/mmdetection/configs/hrnet/fcos_hrnetv2p-w18-gn-head_4xb4-1x_coco.py @@ -0,0 +1,10 @@ +_base_ = './fcos_hrnetv2p-w32-gn-head_4xb4-1x_coco.py' +model = dict( + backbone=dict( + extra=dict( + stage2=dict(num_channels=(18, 36)), + stage3=dict(num_channels=(18, 36, 72)), + stage4=dict(num_channels=(18, 36, 72, 144))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w18')), + neck=dict(type='HRFPN', in_channels=[18, 36, 72, 144], out_channels=256)) diff --git a/mmdetection/configs/hrnet/fcos_hrnetv2p-w18-gn-head_4xb4-2x_coco.py b/mmdetection/configs/hrnet/fcos_hrnetv2p-w18-gn-head_4xb4-2x_coco.py new file mode 100644 index 00000000..f5b67f6a --- /dev/null +++ b/mmdetection/configs/hrnet/fcos_hrnetv2p-w18-gn-head_4xb4-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './fcos_hrnetv2p-w18-gn-head_4xb4-1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/fcos_hrnetv2p-w18-gn-head_ms-640-800-4xb4-2x_coco.py b/mmdetection/configs/hrnet/fcos_hrnetv2p-w18-gn-head_ms-640-800-4xb4-2x_coco.py new file mode 100644 index 00000000..c5332d65 --- /dev/null +++ b/mmdetection/configs/hrnet/fcos_hrnetv2p-w18-gn-head_ms-640-800-4xb4-2x_coco.py @@ -0,0 +1,10 @@ +_base_ = './fcos_hrnetv2p-w32-gn-head_ms-640-800-4xb4-2x_coco.py' +model = dict( + backbone=dict( + extra=dict( + stage2=dict(num_channels=(18, 36)), + stage3=dict(num_channels=(18, 36, 72)), + stage4=dict(num_channels=(18, 36, 72, 144))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w18')), + neck=dict(type='HRFPN', in_channels=[18, 36, 72, 144], out_channels=256)) diff --git a/mmdetection/configs/hrnet/fcos_hrnetv2p-w32-gn-head_4xb4-1x_coco.py b/mmdetection/configs/hrnet/fcos_hrnetv2p-w32-gn-head_4xb4-1x_coco.py new file mode 100644 index 00000000..159d96d7 --- /dev/null +++ b/mmdetection/configs/hrnet/fcos_hrnetv2p-w32-gn-head_4xb4-1x_coco.py @@ -0,0 +1,43 @@ +_base_ = '../fcos/fcos_r50-caffe_fpn_gn-head_4xb4-1x_coco.py' +model = dict( + data_preprocessor=dict( + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False), + backbone=dict( + _delete_=True, + type='HRNet', + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w32')), + neck=dict( + _delete_=True, + type='HRFPN', + in_channels=[32, 64, 128, 256], + out_channels=256, + stride=2, + num_outs=5)) diff --git a/mmdetection/configs/hrnet/fcos_hrnetv2p-w32-gn-head_4xb4-2x_coco.py b/mmdetection/configs/hrnet/fcos_hrnetv2p-w32-gn-head_4xb4-2x_coco.py new file mode 100644 index 00000000..73fd80e9 --- /dev/null +++ b/mmdetection/configs/hrnet/fcos_hrnetv2p-w32-gn-head_4xb4-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './fcos_hrnetv2p-w32-gn-head_4xb4-1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/fcos_hrnetv2p-w32-gn-head_ms-640-800-4xb4-2x_coco.py b/mmdetection/configs/hrnet/fcos_hrnetv2p-w32-gn-head_ms-640-800-4xb4-2x_coco.py new file mode 100644 index 00000000..4c977bf3 --- /dev/null +++ b/mmdetection/configs/hrnet/fcos_hrnetv2p-w32-gn-head_ms-640-800-4xb4-2x_coco.py @@ -0,0 +1,35 @@ +_base_ = './fcos_hrnetv2p-w32-gn-head_4xb4-1x_coco.py' + +model = dict( + data_preprocessor=dict( + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/fcos_hrnetv2p-w40-gn-head_ms-640-800-4xb4-2x_coco.py b/mmdetection/configs/hrnet/fcos_hrnetv2p-w40-gn-head_ms-640-800-4xb4-2x_coco.py new file mode 100644 index 00000000..bb0ff6d6 --- /dev/null +++ b/mmdetection/configs/hrnet/fcos_hrnetv2p-w40-gn-head_ms-640-800-4xb4-2x_coco.py @@ -0,0 +1,11 @@ +_base_ = './fcos_hrnetv2p-w32-gn-head_ms-640-800-4xb4-2x_coco.py' +model = dict( + backbone=dict( + type='HRNet', + extra=dict( + stage2=dict(num_channels=(40, 80)), + stage3=dict(num_channels=(40, 80, 160)), + stage4=dict(num_channels=(40, 80, 160, 320))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w40')), + neck=dict(type='HRFPN', in_channels=[40, 80, 160, 320], out_channels=256)) diff --git a/mmdetection/configs/hrnet/htc_hrnetv2p-w18_20e_coco.py b/mmdetection/configs/hrnet/htc_hrnetv2p-w18_20e_coco.py new file mode 100644 index 00000000..55255d52 --- /dev/null +++ b/mmdetection/configs/hrnet/htc_hrnetv2p-w18_20e_coco.py @@ -0,0 +1,10 @@ +_base_ = './htc_hrnetv2p-w32_20e_coco.py' +model = dict( + backbone=dict( + extra=dict( + stage2=dict(num_channels=(18, 36)), + stage3=dict(num_channels=(18, 36, 72)), + stage4=dict(num_channels=(18, 36, 72, 144))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w18')), + neck=dict(type='HRFPN', in_channels=[18, 36, 72, 144], out_channels=256)) diff --git a/mmdetection/configs/hrnet/htc_hrnetv2p-w32_20e_coco.py b/mmdetection/configs/hrnet/htc_hrnetv2p-w32_20e_coco.py new file mode 100644 index 00000000..545cb83e --- /dev/null +++ b/mmdetection/configs/hrnet/htc_hrnetv2p-w32_20e_coco.py @@ -0,0 +1,37 @@ +_base_ = '../htc/htc_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + _delete_=True, + type='HRNet', + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w32')), + neck=dict( + _delete_=True, + type='HRFPN', + in_channels=[32, 64, 128, 256], + out_channels=256)) diff --git a/mmdetection/configs/hrnet/htc_hrnetv2p-w40_20e_coco.py b/mmdetection/configs/hrnet/htc_hrnetv2p-w40_20e_coco.py new file mode 100644 index 00000000..b09256a0 --- /dev/null +++ b/mmdetection/configs/hrnet/htc_hrnetv2p-w40_20e_coco.py @@ -0,0 +1,11 @@ +_base_ = './htc_hrnetv2p-w32_20e_coco.py' +model = dict( + backbone=dict( + type='HRNet', + extra=dict( + stage2=dict(num_channels=(40, 80)), + stage3=dict(num_channels=(40, 80, 160)), + stage4=dict(num_channels=(40, 80, 160, 320))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w40')), + neck=dict(type='HRFPN', in_channels=[40, 80, 160, 320], out_channels=256)) diff --git a/mmdetection/configs/hrnet/htc_hrnetv2p-w40_28e_coco.py b/mmdetection/configs/hrnet/htc_hrnetv2p-w40_28e_coco.py new file mode 100644 index 00000000..1c13b58a --- /dev/null +++ b/mmdetection/configs/hrnet/htc_hrnetv2p-w40_28e_coco.py @@ -0,0 +1,16 @@ +_base_ = './htc_hrnetv2p-w40_20e_coco.py' + +# learning policy +max_epochs = 28 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[24, 27], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/htc_x101-64x4d_fpn_16xb1-28e_coco.py b/mmdetection/configs/hrnet/htc_x101-64x4d_fpn_16xb1-28e_coco.py new file mode 100644 index 00000000..1f1304e5 --- /dev/null +++ b/mmdetection/configs/hrnet/htc_x101-64x4d_fpn_16xb1-28e_coco.py @@ -0,0 +1,16 @@ +_base_ = '../htc/htc_x101-64x4d_fpn_16xb1-20e_coco.py' + +# learning policy +max_epochs = 28 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[24, 27], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w18-1x_coco.py b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w18-1x_coco.py new file mode 100644 index 00000000..5d5a463a --- /dev/null +++ b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w18-1x_coco.py @@ -0,0 +1,10 @@ +_base_ = './mask-rcnn_hrnetv2p-w32-1x_coco.py' +model = dict( + backbone=dict( + extra=dict( + stage2=dict(num_channels=(18, 36)), + stage3=dict(num_channels=(18, 36, 72)), + stage4=dict(num_channels=(18, 36, 72, 144))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w18')), + neck=dict(type='HRFPN', in_channels=[18, 36, 72, 144], out_channels=256)) diff --git a/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w18-2x_coco.py b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w18-2x_coco.py new file mode 100644 index 00000000..8abc5592 --- /dev/null +++ b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w18-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './mask-rcnn_hrnetv2p-w18-1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w32-1x_coco.py b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w32-1x_coco.py new file mode 100644 index 00000000..208b0378 --- /dev/null +++ b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w32-1x_coco.py @@ -0,0 +1,37 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + _delete_=True, + type='HRNet', + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w32')), + neck=dict( + _delete_=True, + type='HRFPN', + in_channels=[32, 64, 128, 256], + out_channels=256)) diff --git a/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w32-2x_coco.py b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w32-2x_coco.py new file mode 100644 index 00000000..d3741c82 --- /dev/null +++ b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w32-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './mask-rcnn_hrnetv2p-w32-1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w40-2x_coco.py b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w40-2x_coco.py new file mode 100644 index 00000000..360420c5 --- /dev/null +++ b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w40-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './mask-rcnn_hrnetv2p-w40_1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w40_1x_coco.py b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w40_1x_coco.py new file mode 100644 index 00000000..36e2305a --- /dev/null +++ b/mmdetection/configs/hrnet/mask-rcnn_hrnetv2p-w40_1x_coco.py @@ -0,0 +1,11 @@ +_base_ = './mask-rcnn_hrnetv2p-w18-1x_coco.py' +model = dict( + backbone=dict( + type='HRNet', + extra=dict( + stage2=dict(num_channels=(40, 80)), + stage3=dict(num_channels=(40, 80, 160)), + stage4=dict(num_channels=(40, 80, 160, 320))), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w40')), + neck=dict(type='HRFPN', in_channels=[40, 80, 160, 320], out_channels=256)) diff --git a/mmdetection/configs/hrnet/metafile.yml b/mmdetection/configs/hrnet/metafile.yml new file mode 100644 index 00000000..54c62479 --- /dev/null +++ b/mmdetection/configs/hrnet/metafile.yml @@ -0,0 +1,971 @@ +Models: + - Name: faster-rcnn_hrnetv2p-w18-1x_coco + In Collection: Faster R-CNN + Config: configs/hrnet/faster-rcnn_hrnetv2p-w18-1x_coco.py + Metadata: + Training Memory (GB): 6.6 + inference time (ms/im): + - value: 74.63 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w18_1x_coco/faster_rcnn_hrnetv2p_w18_1x_coco_20200130-56651a6d.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: faster-rcnn_hrnetv2p-w18-2x_coco + In Collection: Faster R-CNN + Config: configs/hrnet/faster-rcnn_hrnetv2p-w18-2x_coco.py + Metadata: + Training Memory (GB): 6.6 + inference time (ms/im): + - value: 74.63 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w18_2x_coco/faster_rcnn_hrnetv2p_w18_2x_coco_20200702_085731-a4ec0611.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: faster-rcnn_hrnetv2p-w32-1x_coco + In Collection: Faster R-CNN + Config: configs/hrnet/faster-rcnn_hrnetv2p-w32-1x_coco.py + Metadata: + Training Memory (GB): 9.0 + inference time (ms/im): + - value: 80.65 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w32_1x_coco/faster_rcnn_hrnetv2p_w32_1x_coco_20200130-6e286425.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: faster-rcnn_hrnetv2p-w32_2x_coco + In Collection: Faster R-CNN + Config: configs/hrnet/faster-rcnn_hrnetv2p-w32_2x_coco.py + Metadata: + Training Memory (GB): 9.0 + inference time (ms/im): + - value: 80.65 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w32_2x_coco/faster_rcnn_hrnetv2p_w32_2x_coco_20200529_015927-976a9c15.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: faster-rcnn_hrnetv2p-w40-1x_coco + In Collection: Faster R-CNN + Config: configs/hrnet/faster-rcnn_hrnetv2p-w40-1x_coco.py + Metadata: + Training Memory (GB): 10.4 + inference time (ms/im): + - value: 95.24 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w40_1x_coco/faster_rcnn_hrnetv2p_w40_1x_coco_20200210-95c1f5ce.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: faster-rcnn_hrnetv2p-w40_2x_coco + In Collection: Faster R-CNN + Config: configs/hrnet/faster-rcnn_hrnetv2p-w40_2x_coco.py + Metadata: + Training Memory (GB): 10.4 + inference time (ms/im): + - value: 95.24 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/faster_rcnn_hrnetv2p_w40_2x_coco/faster_rcnn_hrnetv2p_w40_2x_coco_20200512_161033-0f236ef4.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: mask-rcnn_hrnetv2p-w18-1x_coco + In Collection: Mask R-CNN + Config: configs/hrnet/mask-rcnn_hrnetv2p-w18-1x_coco.py + Metadata: + Training Memory (GB): 7.0 + inference time (ms/im): + - value: 85.47 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 34.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w18_1x_coco/mask_rcnn_hrnetv2p_w18_1x_coco_20200205-1c3d78ed.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: mask-rcnn_hrnetv2p-w18-2x_coco + In Collection: Mask R-CNN + Config: configs/hrnet/mask-rcnn_hrnetv2p-w18-2x_coco.py + Metadata: + Training Memory (GB): 7.0 + inference time (ms/im): + - value: 85.47 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w18_2x_coco/mask_rcnn_hrnetv2p_w18_2x_coco_20200212-b3c825b1.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: mask-rcnn_hrnetv2p-w32-1x_coco + In Collection: Mask R-CNN + Config: configs/hrnet/mask-rcnn_hrnetv2p-w32-1x_coco.py + Metadata: + Training Memory (GB): 9.4 + inference time (ms/im): + - value: 88.5 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w32_1x_coco/mask_rcnn_hrnetv2p_w32_1x_coco_20200207-b29f616e.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: mask-rcnn_hrnetv2p-w32-2x_coco + In Collection: Mask R-CNN + Config: configs/hrnet/mask-rcnn_hrnetv2p-w32-2x_coco.py + Metadata: + Training Memory (GB): 9.4 + inference time (ms/im): + - value: 88.5 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w32_2x_coco/mask_rcnn_hrnetv2p_w32_2x_coco_20200213-45b75b4d.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: mask-rcnn_hrnetv2p-w40_1x_coco + In Collection: Mask R-CNN + Config: configs/hrnet/mask-rcnn_hrnetv2p-w40_1x_coco.py + Metadata: + Training Memory (GB): 10.9 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w40_1x_coco/mask_rcnn_hrnetv2p_w40_1x_coco_20200511_015646-66738b35.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: mask-rcnn_hrnetv2p-w40-2x_coco + In Collection: Mask R-CNN + Config: configs/hrnet/mask-rcnn_hrnetv2p-w40-2x_coco.py + Metadata: + Training Memory (GB): 10.9 + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/mask_rcnn_hrnetv2p_w40_2x_coco/mask_rcnn_hrnetv2p_w40_2x_coco_20200512_163732-aed5e4ab.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: cascade-rcnn_hrnetv2p-w18-20e_coco + In Collection: Cascade R-CNN + Config: configs/hrnet/cascade-rcnn_hrnetv2p-w18-20e_coco.py + Metadata: + Training Memory (GB): 7.0 + inference time (ms/im): + - value: 90.91 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_rcnn_hrnetv2p_w18_20e_coco/cascade_rcnn_hrnetv2p_w18_20e_coco_20200210-434be9d7.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: cascade-rcnn_hrnetv2p-w32-20e_coco + In Collection: Cascade R-CNN + Config: configs/hrnet/cascade-rcnn_hrnetv2p-w32-20e_coco.py + Metadata: + Training Memory (GB): 9.4 + inference time (ms/im): + - value: 90.91 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_rcnn_hrnetv2p_w32_20e_coco/cascade_rcnn_hrnetv2p_w32_20e_coco_20200208-928455a4.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: cascade-rcnn_hrnetv2p-w40-20e_coco + In Collection: Cascade R-CNN + Config: configs/hrnet/cascade-rcnn_hrnetv2p-w40-20e_coco.py + Metadata: + Training Memory (GB): 10.8 + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_rcnn_hrnetv2p_w40_20e_coco/cascade_rcnn_hrnetv2p_w40_20e_coco_20200512_161112-75e47b04.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: cascade-mask-rcnn_hrnetv2p-w18_20e_coco + In Collection: Cascade R-CNN + Config: configs/hrnet/cascade-mask-rcnn_hrnetv2p-w18_20e_coco.py + Metadata: + Training Memory (GB): 8.5 + inference time (ms/im): + - value: 117.65 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_mask_rcnn_hrnetv2p_w18_20e_coco/cascade_mask_rcnn_hrnetv2p_w18_20e_coco_20200210-b543cd2b.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: cascade-mask-rcnn_hrnetv2p-w32_20e_coco + In Collection: Cascade R-CNN + Config: configs/hrnet/cascade-mask-rcnn_hrnetv2p-w32_20e_coco.py + Metadata: + inference time (ms/im): + - value: 120.48 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_mask_rcnn_hrnetv2p_w32_20e_coco/cascade_mask_rcnn_hrnetv2p_w32_20e_coco_20200512_154043-39d9cf7b.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: cascade-mask-rcnn_hrnetv2p-w40-20e_coco + In Collection: Cascade R-CNN + Config: configs/hrnet/cascade-mask-rcnn_hrnetv2p-w40-20e_coco.py + Metadata: + Training Memory (GB): 12.5 + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/cascade_mask_rcnn_hrnetv2p_w40_20e_coco/cascade_mask_rcnn_hrnetv2p_w40_20e_coco_20200527_204922-969c4610.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: htc_hrnetv2p-w18_20e_coco + In Collection: HTC + Config: configs/hrnet/htc_hrnetv2p-w18_20e_coco.py + Metadata: + Training Memory (GB): 10.8 + inference time (ms/im): + - value: 212.77 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/htc_hrnetv2p_w18_20e_coco/htc_hrnetv2p_w18_20e_coco_20200210-b266988c.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: htc_hrnetv2p-w32_20e_coco + In Collection: HTC + Config: configs/hrnet/htc_hrnetv2p-w32_20e_coco.py + Metadata: + Training Memory (GB): 13.1 + inference time (ms/im): + - value: 204.08 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/htc_hrnetv2p_w32_20e_coco/htc_hrnetv2p_w32_20e_coco_20200207-7639fa12.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: htc_hrnetv2p-w40_20e_coco + In Collection: HTC + Config: configs/hrnet/htc_hrnetv2p-w40_20e_coco.py + Metadata: + Training Memory (GB): 14.6 + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/htc_hrnetv2p_w40_20e_coco/htc_hrnetv2p_w40_20e_coco_20200529_183411-417c4d5b.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: fcos_hrnetv2p-w18-gn-head_4xb4-1x_coco + In Collection: FCOS + Config: configs/hrnet/fcos_hrnetv2p-w18-gn-head_4xb4-1x_coco.py + Metadata: + Training Resources: 4x V100 GPUs + Batch Size: 16 + Training Memory (GB): 13.0 + inference time (ms/im): + - value: 77.52 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 35.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w18_gn-head_4x4_1x_coco/fcos_hrnetv2p_w18_gn-head_4x4_1x_coco_20201212_100710-4ad151de.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: fcos_hrnetv2p-w18-gn-head_4xb4-2x_coco + In Collection: FCOS + Config: configs/hrnet/fcos_hrnetv2p-w18-gn-head_4xb4-2x_coco.py + Metadata: + Training Resources: 4x V100 GPUs + Batch Size: 16 + Training Memory (GB): 13.0 + inference time (ms/im): + - value: 77.52 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w18_gn-head_4x4_2x_coco/fcos_hrnetv2p_w18_gn-head_4x4_2x_coco_20201212_101110-5c575fa5.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: fcos_hrnetv2p-w32-gn-head_4xb4-1x_coco + In Collection: FCOS + Config: configs/hrnet/fcos_hrnetv2p-w32-gn-head_4xb4-1x_coco.py + Metadata: + Training Resources: 4x V100 GPUs + Batch Size: 16 + Training Memory (GB): 17.5 + inference time (ms/im): + - value: 77.52 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w32_gn-head_4x4_1x_coco/fcos_hrnetv2p_w32_gn-head_4x4_1x_coco_20201211_134730-cb8055c0.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: fcos_hrnetv2p-w32-gn-head_4xb4-2x_coco + In Collection: FCOS + Config: configs/hrnet/fcos_hrnetv2p-w32-gn-head_4xb4-2x_coco.py + Metadata: + Training Resources: 4x V100 GPUs + Batch Size: 16 + Training Memory (GB): 17.5 + inference time (ms/im): + - value: 77.52 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w32_gn-head_4x4_2x_coco/fcos_hrnetv2p_w32_gn-head_4x4_2x_coco_20201212_112133-77b6b9bb.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: fcos_hrnetv2p-w18-gn-head_ms-640-800-4xb4-2x_coco + In Collection: FCOS + Config: configs/hrnet/fcos_hrnetv2p-w18-gn-head_ms-640-800-4xb4-2x_coco.py + Metadata: + Training Resources: 4x V100 GPUs + Batch Size: 16 + Training Memory (GB): 13.0 + inference time (ms/im): + - value: 77.52 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w18_gn-head_mstrain_640-800_4x4_2x_coco/fcos_hrnetv2p_w18_gn-head_mstrain_640-800_4x4_2x_coco_20201212_111651-441e9d9f.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: fcos_hrnetv2p-w32-gn-head_ms-640-800-4xb4-2x_coco + In Collection: FCOS + Config: configs/hrnet/fcos_hrnetv2p-w32-gn-head_ms-640-800-4xb4-2x_coco.py + Metadata: + Training Resources: 4x V100 GPUs + Batch Size: 16 + Training Memory (GB): 17.5 + inference time (ms/im): + - value: 80.65 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w32_gn-head_mstrain_640-800_4x4_2x_coco/fcos_hrnetv2p_w32_gn-head_mstrain_640-800_4x4_2x_coco_20201212_090846-b6f2b49f.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 + + - Name: fcos_hrnetv2p-w40-gn-head_ms-640-800-4xb4-2x_coco + In Collection: FCOS + Config: configs/hrnet/fcos_hrnetv2p-w40-gn-head_ms-640-800-4xb4-2x_coco.py + Metadata: + Training Resources: 4x V100 GPUs + Batch Size: 16 + Training Memory (GB): 20.3 + inference time (ms/im): + - value: 92.59 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Architecture: + - HRNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/hrnet/fcos_hrnetv2p_w40_gn-head_mstrain_640-800_4x4_2x_coco/fcos_hrnetv2p_w40_gn-head_mstrain_640-800_4x4_2x_coco_20201212_124752-f22d2ce5.pth + Paper: + URL: https://arxiv.org/abs/1904.04514 + Title: 'Deep High-Resolution Representation Learning for Visual Recognition' + README: configs/hrnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/backbones/hrnet.py#L195 + Version: v2.0.0 diff --git a/mmdetection/configs/htc/README.md b/mmdetection/configs/htc/README.md new file mode 100644 index 00000000..a6b77ce4 --- /dev/null +++ b/mmdetection/configs/htc/README.md @@ -0,0 +1,67 @@ +# HTC + +> [Hybrid Task Cascade for Instance Segmentation](https://arxiv.org/abs/1901.07518) + + + +## Abstract + +Cascade is a classic yet powerful architecture that has boosted performance on various tasks. However, how to introduce cascade to instance segmentation remains an open question. A simple combination of Cascade R-CNN and Mask R-CNN only brings limited gain. In exploring a more effective approach, we find that the key to a successful instance segmentation cascade is to fully leverage the reciprocal relationship between detection and segmentation. In this work, we propose a new framework, Hybrid Task Cascade (HTC), which differs in two important aspects: (1) instead of performing cascaded refinement on these two tasks separately, it interweaves them for a joint multi-stage processing; (2) it adopts a fully convolutional branch to provide spatial context, which can help distinguishing hard foreground from cluttered background. Overall, this framework can learn more discriminative features progressively while integrating complementary features together in each stage. Without bells and whistles, a single HTC obtains 38.4 and 1.5 improvement over a strong Cascade Mask R-CNN baseline on MSCOCO dataset. Moreover, our overall system achieves 48.6 mask AP on the test-challenge split, ranking 1st in the COCO 2018 Challenge Object Detection Task. + +
    + +
    + +## Introduction + +HTC requires COCO and [COCO-stuff](http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip) dataset for training. You need to download and extract it in the COCO dataset path. +The directory should be like this. + +```none +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +| | ├── stuffthingmaps +``` + +## Results and Models + +The results on COCO 2017val are shown in the below table. (results on test-dev are usually slightly higher than val) + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :----------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 1x | 8.2 | 5.8 | 42.3 | 37.4 | [config](./htc_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r50_fpn_1x_coco/htc_r50_fpn_1x_coco_20200317-7332cf16.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r50_fpn_1x_coco/htc_r50_fpn_1x_coco_20200317_070435.log.json) | +| R-50-FPN | pytorch | 20e | 8.2 | - | 43.3 | 38.3 | [config](./htc_r50_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r50_fpn_20e_coco/htc_r50_fpn_20e_coco_20200319-fe28c577.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r50_fpn_20e_coco/htc_r50_fpn_20e_coco_20200319_070313.log.json) | +| R-101-FPN | pytorch | 20e | 10.2 | 5.5 | 44.8 | 39.6 | [config](./htc_r101_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r101_fpn_20e_coco/htc_r101_fpn_20e_coco_20200317-9b41b48f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r101_fpn_20e_coco/htc_r101_fpn_20e_coco_20200317_153107.log.json) | +| X-101-32x4d-FPN | pytorch | 20e | 11.4 | 5.0 | 46.1 | 40.5 | [config](./htc_x101-32x4d_fpn_16xb1-20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_x101_32x4d_fpn_16x1_20e_coco/htc_x101_32x4d_fpn_16x1_20e_coco_20200318-de97ae01.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_x101_32x4d_fpn_16x1_20e_coco/htc_x101_32x4d_fpn_16x1_20e_coco_20200318_034519.log.json) | +| X-101-64x4d-FPN | pytorch | 20e | 14.5 | 4.4 | 47.0 | 41.4 | [config](./htc_x101-64x4d_fpn_16xb1-20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_x101_64x4d_fpn_16x1_20e_coco/htc_x101_64x4d_fpn_16x1_20e_coco_20200318-b181fd7a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_x101_64x4d_fpn_16x1_20e_coco/htc_x101_64x4d_fpn_16x1_20e_coco_20200318_081711.log.json) | + +- In the HTC paper and COCO 2018 Challenge, `score_thr` is set to 0.001 for both baselines and HTC. +- We use 8 GPUs with 2 images/GPU for R-50 and R-101 models, and 16 GPUs with 1 image/GPU for X-101 models. + If you would like to train X-101 HTC with 8 GPUs, you need to change the lr from 0.02 to 0.01. + +We also provide a powerful HTC with DCN and multi-scale training model. No testing augmentation is used. + +| Backbone | Style | DCN | training scales | Lr schd | box AP | mask AP | Config | Download | +| :-------------: | :-----: | :---: | :-------------: | :-----: | :----: | :-----: | :----------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| X-101-64x4d-FPN | pytorch | c3-c5 | 400~1400 | 20e | 50.4 | 43.8 | [config](./htc_x101-64x4d-dconv-c3-c5_fpn_ms-400-1400-16xb1-20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_x101_64x4d_fpn_dconv_c3-c5_mstrain_400_1400_16x1_20e_coco/htc_x101_64x4d_fpn_dconv_c3-c5_mstrain_400_1400_16x1_20e_coco_20200312-946fd751.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/htc/htc_x101_64x4d_fpn_dconv_c3-c5_mstrain_400_1400_16x1_20e_coco/htc_x101_64x4d_fpn_dconv_c3-c5_mstrain_400_1400_16x1_20e_coco_20200312_203410.log.json) | + +## Citation + +We provide config files to reproduce the results in the CVPR 2019 paper for [Hybrid Task Cascade](https://arxiv.org/abs/1901.07518). + +```latex +@inproceedings{chen2019hybrid, + title={Hybrid task cascade for instance segmentation}, + author={Chen, Kai and Pang, Jiangmiao and Wang, Jiaqi and Xiong, Yu and Li, Xiaoxiao and Sun, Shuyang and Feng, Wansen and Liu, Ziwei and Shi, Jianping and Ouyang, Wanli and Chen Change Loy and Dahua Lin}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + year={2019} +} +``` diff --git a/mmdetection/configs/htc/htc-without-semantic_r50_fpn_1x_coco.py b/mmdetection/configs/htc/htc-without-semantic_r50_fpn_1x_coco.py new file mode 100644 index 00000000..791f4eb2 --- /dev/null +++ b/mmdetection/configs/htc/htc-without-semantic_r50_fpn_1x_coco.py @@ -0,0 +1,223 @@ +_base_ = [ + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + type='HybridTaskCascade', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + type='HybridTaskCascadeRoIHead', + interleaved=True, + mask_info_flow=True, + num_stages=3, + stage_loss_weights=[1, 0.5, 0.25], + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], + mask_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + mask_head=[ + dict( + type='HTCMaskHead', + with_conv_res=False, + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=80, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)), + dict( + type='HTCMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=80, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)), + dict( + type='HTCMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=80, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)) + ]), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=[ + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.001, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100, + mask_thr_binary=0.5))) diff --git a/mmdetection/configs/htc/htc_r101_fpn_20e_coco.py b/mmdetection/configs/htc/htc_r101_fpn_20e_coco.py new file mode 100644 index 00000000..28091aad --- /dev/null +++ b/mmdetection/configs/htc/htc_r101_fpn_20e_coco.py @@ -0,0 +1,6 @@ +_base_ = './htc_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/htc/htc_r50_fpn_1x_coco.py b/mmdetection/configs/htc/htc_r50_fpn_1x_coco.py new file mode 100644 index 00000000..3573f1f6 --- /dev/null +++ b/mmdetection/configs/htc/htc_r50_fpn_1x_coco.py @@ -0,0 +1,33 @@ +_base_ = './htc-without-semantic_r50_fpn_1x_coco.py' +model = dict( + data_preprocessor=dict(pad_seg=True), + roi_head=dict( + semantic_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[8]), + semantic_head=dict( + type='FusedSemanticHead', + num_ins=5, + fusion_level=1, + seg_scale_factor=1 / 8, + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=183, + loss_seg=dict( + type='CrossEntropyLoss', ignore_index=255, loss_weight=0.2)))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', with_bbox=True, with_mask=True, with_seg=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict( + dataset=dict( + data_prefix=dict(img='train2017/', seg='stuffthingmaps/train2017/'), + pipeline=train_pipeline)) diff --git a/mmdetection/configs/htc/htc_r50_fpn_20e_coco.py b/mmdetection/configs/htc/htc_r50_fpn_20e_coco.py new file mode 100644 index 00000000..9f510fa6 --- /dev/null +++ b/mmdetection/configs/htc/htc_r50_fpn_20e_coco.py @@ -0,0 +1,16 @@ +_base_ = './htc_r50_fpn_1x_coco.py' + +# learning policy +max_epochs = 20 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 19], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/htc/htc_x101-32x4d_fpn_16xb1-20e_coco.py b/mmdetection/configs/htc/htc_x101-32x4d_fpn_16xb1-20e_coco.py new file mode 100644 index 00000000..396d3a0e --- /dev/null +++ b/mmdetection/configs/htc/htc_x101-32x4d_fpn_16xb1-20e_coco.py @@ -0,0 +1,32 @@ +_base_ = './htc_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) + +train_dataloader = dict(batch_size=1, num_workers=1) + +# learning policy +max_epochs = 20 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 19], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/htc/htc_x101-64x4d-dconv-c3-c5_fpn_ms-400-1400-16xb1-20e_coco.py b/mmdetection/configs/htc/htc_x101-64x4d-dconv-c3-c5_fpn_ms-400-1400-16xb1-20e_coco.py new file mode 100644 index 00000000..26d68e7e --- /dev/null +++ b/mmdetection/configs/htc/htc_x101-64x4d-dconv-c3-c5_fpn_ms-400-1400-16xb1-20e_coco.py @@ -0,0 +1,20 @@ +_base_ = './htc_x101-64x4d_fpn_16xb1-20e_coco.py' + +model = dict( + backbone=dict( + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='LoadAnnotations', with_bbox=True, with_mask=True, with_seg=True), + dict( + type='RandomResize', + scale=[(1600, 400), (1600, 1400)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/htc/htc_x101-64x4d_fpn_16xb1-20e_coco.py b/mmdetection/configs/htc/htc_x101-64x4d_fpn_16xb1-20e_coco.py new file mode 100644 index 00000000..a600ddb0 --- /dev/null +++ b/mmdetection/configs/htc/htc_x101-64x4d_fpn_16xb1-20e_coco.py @@ -0,0 +1,7 @@ +_base_ = './htc_x101-32x4d_fpn_16xb1-20e_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + groups=64, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/htc/metafile.yml b/mmdetection/configs/htc/metafile.yml new file mode 100644 index 00000000..2f0f74d2 --- /dev/null +++ b/mmdetection/configs/htc/metafile.yml @@ -0,0 +1,165 @@ +Collections: + - Name: HTC + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - HTC + - RPN + - ResNet + - ResNeXt + - RoIAlign + Paper: + URL: https://arxiv.org/abs/1901.07518 + Title: 'Hybrid Task Cascade for Instance Segmentation' + README: configs/htc/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/htc.py#L6 + Version: v2.0.0 + +Models: + - Name: htc_r50_fpn_1x_coco + In Collection: HTC + Config: configs/htc/htc_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.2 + inference time (ms/im): + - value: 172.41 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r50_fpn_1x_coco/htc_r50_fpn_1x_coco_20200317-7332cf16.pth + + - Name: htc_r50_fpn_20e_coco + In Collection: HTC + Config: configs/htc/htc_r50_fpn_20e_coco.py + Metadata: + Training Memory (GB): 8.2 + inference time (ms/im): + - value: 172.41 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r50_fpn_20e_coco/htc_r50_fpn_20e_coco_20200319-fe28c577.pth + + - Name: htc_r101_fpn_20e_coco + In Collection: HTC + Config: configs/htc/htc_r101_fpn_20e_coco.py + Metadata: + Training Memory (GB): 10.2 + inference time (ms/im): + - value: 181.82 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/htc/htc_r101_fpn_20e_coco/htc_r101_fpn_20e_coco_20200317-9b41b48f.pth + + - Name: htc_x101-32x4d_fpn_16xb1-20e_coco + In Collection: HTC + Config: configs/htc/htc_x101-32x4d_fpn_16xb1-20e_coco.py + Metadata: + Training Resources: 16x V100 GPUs + Batch Size: 16 + Training Memory (GB): 11.4 + inference time (ms/im): + - value: 200 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/htc/htc_x101_32x4d_fpn_16x1_20e_coco/htc_x101_32x4d_fpn_16x1_20e_coco_20200318-de97ae01.pth + + - Name: htc_x101-64x4d_fpn_16xb1-20e_coco + In Collection: HTC + Config: configs/htc/htc_x101-64x4d_fpn_16xb1-20e_coco.py + Metadata: + Training Resources: 16x V100 GPUs + Batch Size: 16 + Training Memory (GB): 14.5 + inference time (ms/im): + - value: 227.27 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/htc/htc_x101_64x4d_fpn_16x1_20e_coco/htc_x101_64x4d_fpn_16x1_20e_coco_20200318-b181fd7a.pth + + - Name: htc_x101-64x4d-dconv-c3-c5_fpn_ms-400-1400-16xb1-20e_coco + In Collection: HTC + Config: configs/htc/htc_x101-64x4d-dconv-c3-c5_fpn_ms-400-1400-16xb1-20e_coco.py + Metadata: + Training Resources: 16x V100 GPUs + Batch Size: 16 + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 43.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/htc/htc_x101_64x4d_fpn_dconv_c3-c5_mstrain_400_1400_16x1_20e_coco/htc_x101_64x4d_fpn_dconv_c3-c5_mstrain_400_1400_16x1_20e_coco_20200312-946fd751.pth diff --git a/mmdetection/configs/instaboost/README.md b/mmdetection/configs/instaboost/README.md new file mode 100644 index 00000000..34132341 --- /dev/null +++ b/mmdetection/configs/instaboost/README.md @@ -0,0 +1,58 @@ +# Instaboost + +> [Instaboost: Boosting instance segmentation via probability map guided copy-pasting](https://arxiv.org/abs/1908.07801) + + + +## Abstract + +Instance segmentation requires a large number of training samples to achieve satisfactory performance and benefits from proper data augmentation. To enlarge the training set and increase the diversity, previous methods have investigated using data annotation from other domain (e.g. bbox, point) in a weakly supervised mechanism. In this paper, we present a simple, efficient and effective method to augment the training set using the existing instance mask annotations. Exploiting the pixel redundancy of the background, we are able to improve the performance of Mask R-CNN for 1.7 mAP on COCO dataset and 3.3 mAP on Pascal VOC dataset by simply introducing random jittering to objects. Furthermore, we propose a location probability map based approach to explore the feasible locations that objects can be placed based on local appearance similarity. With the guidance of such map, we boost the performance of R101-Mask R-CNN on instance segmentation from 35.7 mAP to 37.9 mAP without modifying the backbone or network structure. Our method is simple to implement and does not increase the computational complexity. It can be integrated into the training pipeline of any instance segmentation model without affecting the training and inference efficiency. + +
    + +
    + +## Introduction + +Configs in this directory is the implementation for ICCV2019 paper "InstaBoost: Boosting Instance Segmentation Via Probability Map Guided Copy-Pasting" and provided by the authors of the paper. InstaBoost is a data augmentation method for object detection and instance segmentation. The paper has been released on [`arXiv`](https://arxiv.org/abs/1908.07801). + +## Usage + +### Requirements + +You need to install `instaboostfast` before using it. + +```shell +pip install instaboostfast +``` + +The code and more details can be found [here](https://github.com/GothicAi/Instaboost). + +### Integration with MMDetection + +InstaBoost have been already integrated in the data pipeline, thus all you need is to add or change **InstaBoost** configurations after **LoadImageFromFile**. We have provided examples like [this](mask_rcnn_r50_fpn_instaboost_4x#L121). You can refer to [`InstaBoostConfig`](https://github.com/GothicAi/InstaBoost-pypi#instaboostconfig) for more details. + +## Results and Models + +- All models were trained on `coco_2017_train` and tested on `coco_2017_val` for convenience of evaluation and comparison. In the paper, the results are obtained from `test-dev`. +- To balance accuracy and training time when using InstaBoost, models released in this page are all trained for 48 Epochs. Other training and testing configs strictly follow the original framework. +- For results and models in MMDetection V1.x, please refer to [Instaboost](https://github.com/GothicAi/Instaboost). + +| Network | Backbone | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-----------: | :-------------: | :-----: | :------: | :------------: | :----: | :-----: | :---------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Mask R-CNN | R-50-FPN | 4x | 4.4 | 17.5 | 40.6 | 36.6 | [config](./mask-rcnn_r50_fpn_instaboost-4x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_r50_fpn_instaboost_4x_coco/mask_rcnn_r50_fpn_instaboost_4x_coco_20200307-d025f83a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_r50_fpn_instaboost_4x_coco/mask_rcnn_r50_fpn_instaboost_4x_coco_20200307_223635.log.json) | +| Mask R-CNN | R-101-FPN | 4x | 6.4 | | 42.5 | 38.0 | [config](./mask-rcnn_r101_fpn_instaboost-4x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_r101_fpn_instaboost_4x_coco/mask_rcnn_r101_fpn_instaboost_4x_coco_20200703_235738-f23f3a5f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_r101_fpn_instaboost_4x_coco/mask_rcnn_r101_fpn_instaboost_4x_coco_20200703_235738.log.json) | +| Mask R-CNN | X-101-64x4d-FPN | 4x | 10.7 | | 44.7 | 39.7 | [config](./mask-rcnn_x101-64x4d_fpn_instaboost-4x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_x101_64x4d_fpn_instaboost_4x_coco/mask_rcnn_x101_64x4d_fpn_instaboost_4x_coco_20200515_080947-8ed58c1b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_x101_64x4d_fpn_instaboost_4x_coco/mask_rcnn_x101_64x4d_fpn_instaboost_4x_coco_20200515_080947.log.json) | +| Cascade R-CNN | R-101-FPN | 4x | 6.0 | 12.0 | 43.7 | 38.0 | [config](./cascade-mask-rcnn_r50_fpn_instaboost-4x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/instaboost/cascade_mask_rcnn_r50_fpn_instaboost_4x_coco/cascade_mask_rcnn_r50_fpn_instaboost_4x_coco_20200307-c19d98d9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/instaboost/cascade_mask_rcnn_r50_fpn_instaboost_4x_coco/cascade_mask_rcnn_r50_fpn_instaboost_4x_coco_20200307_223646.log.json) | + +## Citation + +```latex +@inproceedings{fang2019instaboost, + title={Instaboost: Boosting instance segmentation via probability map guided copy-pasting}, + author={Fang, Hao-Shu and Sun, Jianhua and Wang, Runzhong and Gou, Minghao and Li, Yong-Lu and Lu, Cewu}, + booktitle={Proceedings of the IEEE International Conference on Computer Vision}, + pages={682--691}, + year={2019} +} +``` diff --git a/mmdetection/configs/instaboost/cascade-mask-rcnn_r101_fpn_instaboost-4x_coco.py b/mmdetection/configs/instaboost/cascade-mask-rcnn_r101_fpn_instaboost-4x_coco.py new file mode 100644 index 00000000..53e33b89 --- /dev/null +++ b/mmdetection/configs/instaboost/cascade-mask-rcnn_r101_fpn_instaboost-4x_coco.py @@ -0,0 +1,7 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_instaboost-4x_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/instaboost/cascade-mask-rcnn_r50_fpn_instaboost-4x_coco.py b/mmdetection/configs/instaboost/cascade-mask-rcnn_r50_fpn_instaboost-4x_coco.py new file mode 100644 index 00000000..f7736cf5 --- /dev/null +++ b/mmdetection/configs/instaboost/cascade-mask-rcnn_r50_fpn_instaboost-4x_coco.py @@ -0,0 +1,40 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='InstaBoost', + action_candidate=('normal', 'horizontal', 'skip'), + action_prob=(1, 0, 0), + scale=(0.8, 1.2), + dx=15, + dy=15, + theta=(-1, 1), + color_prob=0.5, + hflag=False, + aug_ratio=0.5), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +max_epochs = 48 + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[32, 44], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) + +# only keep latest 3 checkpoints +default_hooks = dict(checkpoint=dict(max_keep_ckpts=3)) diff --git a/mmdetection/configs/instaboost/cascade-mask-rcnn_x101-64x4d_fpn_instaboost-4x_coco.py b/mmdetection/configs/instaboost/cascade-mask-rcnn_x101-64x4d_fpn_instaboost-4x_coco.py new file mode 100644 index 00000000..c7938d9e --- /dev/null +++ b/mmdetection/configs/instaboost/cascade-mask-rcnn_x101-64x4d_fpn_instaboost-4x_coco.py @@ -0,0 +1,14 @@ +_base_ = './cascade-mask-rcnn_r50_fpn_instaboost-4x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/instaboost/mask-rcnn_r101_fpn_instaboost-4x_coco.py b/mmdetection/configs/instaboost/mask-rcnn_r101_fpn_instaboost-4x_coco.py new file mode 100644 index 00000000..55bfa9fe --- /dev/null +++ b/mmdetection/configs/instaboost/mask-rcnn_r101_fpn_instaboost-4x_coco.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_instaboost-4x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py b/mmdetection/configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py new file mode 100644 index 00000000..0a8c9be8 --- /dev/null +++ b/mmdetection/configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py @@ -0,0 +1,40 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='InstaBoost', + action_candidate=('normal', 'horizontal', 'skip'), + action_prob=(1, 0, 0), + scale=(0.8, 1.2), + dx=15, + dy=15, + theta=(-1, 1), + color_prob=0.5, + hflag=False, + aug_ratio=0.5), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +max_epochs = 48 + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[32, 44], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) + +# only keep latest 3 checkpoints +default_hooks = dict(checkpoint=dict(max_keep_ckpts=3)) diff --git a/mmdetection/configs/instaboost/mask-rcnn_x101-64x4d_fpn_instaboost-4x_coco.py b/mmdetection/configs/instaboost/mask-rcnn_x101-64x4d_fpn_instaboost-4x_coco.py new file mode 100644 index 00000000..9ba2ada6 --- /dev/null +++ b/mmdetection/configs/instaboost/mask-rcnn_x101-64x4d_fpn_instaboost-4x_coco.py @@ -0,0 +1,14 @@ +_base_ = './mask-rcnn_r50_fpn_instaboost-4x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/instaboost/metafile.yml b/mmdetection/configs/instaboost/metafile.yml new file mode 100644 index 00000000..228f31b7 --- /dev/null +++ b/mmdetection/configs/instaboost/metafile.yml @@ -0,0 +1,99 @@ +Collections: + - Name: InstaBoost + Metadata: + Training Data: COCO + Training Techniques: + - InstaBoost + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Paper: + URL: https://arxiv.org/abs/1908.07801 + Title: 'Instaboost: Boosting instance segmentation via probability map guided copy-pasting' + README: configs/instaboost/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/datasets/pipelines/instaboost.py#L7 + Version: v2.0.0 + +Models: + - Name: mask-rcnn_r50_fpn_instaboost_4x_coco + In Collection: InstaBoost + Config: configs/instaboost/mask-rcnn_r50_fpn_instaboost-4x_coco.py + Metadata: + Training Memory (GB): 4.4 + inference time (ms/im): + - value: 57.14 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 48 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_r50_fpn_instaboost_4x_coco/mask_rcnn_r50_fpn_instaboost_4x_coco_20200307-d025f83a.pth + + - Name: mask-rcnn_r101_fpn_instaboost-4x_coco + In Collection: InstaBoost + Config: configs/instaboost/mask-rcnn_r101_fpn_instaboost-4x_coco.py + Metadata: + Training Memory (GB): 6.4 + Epochs: 48 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_r101_fpn_instaboost_4x_coco/mask_rcnn_r101_fpn_instaboost_4x_coco_20200703_235738-f23f3a5f.pth + + - Name: mask-rcnn_x101-64x4d_fpn_instaboost-4x_coco + In Collection: InstaBoost + Config: configs/instaboost/mask-rcnn_x101-64x4d_fpn_instaboost-4x_coco.py + Metadata: + Training Memory (GB): 10.7 + Epochs: 48 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/instaboost/mask_rcnn_x101_64x4d_fpn_instaboost_4x_coco/mask_rcnn_x101_64x4d_fpn_instaboost_4x_coco_20200515_080947-8ed58c1b.pth + + - Name: cascade-mask-rcnn_r50_fpn_instaboost_4x_coco + In Collection: InstaBoost + Config: configs/instaboost/cascade-mask-rcnn_r50_fpn_instaboost-4x_coco.py + Metadata: + Training Memory (GB): 6.0 + inference time (ms/im): + - value: 83.33 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 48 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/instaboost/cascade_mask_rcnn_r50_fpn_instaboost_4x_coco/cascade_mask_rcnn_r50_fpn_instaboost_4x_coco_20200307-c19d98d9.pth diff --git a/mmdetection/configs/lad/README.md b/mmdetection/configs/lad/README.md new file mode 100644 index 00000000..3c3b6b4b --- /dev/null +++ b/mmdetection/configs/lad/README.md @@ -0,0 +1,45 @@ +# LAD + +> [Improving Object Detection by Label Assignment Distillation](https://arxiv.org/abs/2108.10520) + + + +## Abstract + +Label assignment in object detection aims to assign targets, foreground or background, to sampled regions in an image. Unlike labeling for image classification, this problem is not well defined due to the object's bounding box. In this paper, we investigate the problem from a perspective of distillation, hence we call Label Assignment Distillation (LAD). Our initial motivation is very simple, we use a teacher network to generate labels for the student. This can be achieved in two ways: either using the teacher's prediction as the direct targets (soft label), or through the hard labels dynamically assigned by the teacher (LAD). Our experiments reveal that: (i) LAD is more effective than soft-label, but they are complementary. (ii) Using LAD, a smaller teacher can also improve a larger student significantly, while soft-label can't. We then introduce Co-learning LAD, in which two networks simultaneously learn from scratch and the role of teacher and student are dynamically interchanged. Using PAA-ResNet50 as a teacher, our LAD techniques can improve detectors PAA-ResNet101 and PAA-ResNeXt101 to 46AP and 47.5AP on the COCO test-dev set. With a stronger teacher PAA-SwinB, we improve the students PAA-ResNet50 to 43.7AP by only 1x schedule training and standard setting, and PAA-ResNet101 to 47.9AP, significantly surpassing the current methods. + +
    + +
    + +## Results and Models + +We provide config files to reproduce the object detection results in the +WACV 2022 paper for Improving Object Detection by Label Assignment +Distillation. + +### PAA with LAD + +| Teacher | Student | Training schedule | AP (val) | Config | Download | +| :-----: | :-----: | :---------------: | :------: | :----------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| -- | R-50 | 1x | 40.4 | [config](../paa/paa_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1x_coco/paa_r50_fpn_1x_coco_20200821-936edec3.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1x_coco/paa_r50_fpn_1x_coco_20200821-936edec3.log.json) | +| -- | R-101 | 1x | 42.6 | [config](../paa/paa_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_1x_coco/paa_r101_fpn_1x_coco_20200821-0a1825a4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_1x_coco/paa_r101_fpn_1x_coco_20200821-0a1825a4.log.json) | +| R-101 | R-50 | 1x | 41.4 | [config](./lad_r50-paa-r101_fpn_2xb8_coco_1x.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lad/lad_r50_paa_r101_fpn_coco_1x/lad_r50_paa_r101_fpn_coco_1x_20220708_124246-74c76ff0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lad/lad_r50_paa_r101_fpn_coco_1x/lad_r50_paa_r101_fpn_coco_1x_20220708_124246.log.json) | +| R-50 | R-101 | 1x | 43.2 | [config](./lad_r101-paa-r50_fpn_2xb8_coco_1x.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lad/lad_r101_paa_r50_fpn_coco_1x/lad_r101_paa_r50_fpn_coco_1x_20220708_124357-9407ac54.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lad/lad_r101_paa_r50_fpn_coco_1x/lad_r101_paa_r50_fpn_coco_1x_20220708_124357.log.json) | + +## Note + +- Meaning of Config name: lad_r50(student model)\_paa(based on paa)\_r101(teacher model)\_fpn(neck)\_coco(dataset)\_1x(12 epoch).py +- Results may fluctuate by about 0.2 mAP. +- 2 GPUs are used, 8 samples per GPU. + +## Citation + +```latex +@inproceedings{nguyen2021improving, + title={Improving Object Detection by Label Assignment Distillation}, + author={Chuong H. Nguyen and Thuy C. Nguyen and Tuan N. Tang and Nam L. H. Phan}, + booktitle = {WACV}, + year={2022} +} +``` diff --git a/mmdetection/configs/lad/lad_r101-paa-r50_fpn_2xb8_coco_1x.py b/mmdetection/configs/lad/lad_r101-paa-r50_fpn_2xb8_coco_1x.py new file mode 100644 index 00000000..d61d0863 --- /dev/null +++ b/mmdetection/configs/lad/lad_r101-paa-r50_fpn_2xb8_coco_1x.py @@ -0,0 +1,127 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +teacher_ckpt = 'https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1x_coco/paa_r50_fpn_1x_coco_20200821-936edec3.pth' # noqa + +model = dict( + type='LAD', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + # student + backbone=dict( + type='ResNet', + depth=101, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='LADHead', + reg_decoded_bbox=True, + score_voting=True, + topk=9, + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.3), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.5)), + # teacher + teacher_ckpt=teacher_ckpt, + teacher_backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch'), + teacher_neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + teacher_bbox_head=dict( + type='LADHead', + reg_decoded_bbox=True, + score_voting=True, + topk=9, + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.3), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.5)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.1, + neg_iou_thr=0.1, + min_pos_iou=0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + score_voting=True, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) +train_dataloader = dict(batch_size=8, num_workers=4) +optim_wrapper = dict(type='AmpOptimWrapper', optimizer=dict(lr=0.01)) diff --git a/mmdetection/configs/lad/lad_r50-paa-r101_fpn_2xb8_coco_1x.py b/mmdetection/configs/lad/lad_r50-paa-r101_fpn_2xb8_coco_1x.py new file mode 100644 index 00000000..f7eaf2bf --- /dev/null +++ b/mmdetection/configs/lad/lad_r50-paa-r101_fpn_2xb8_coco_1x.py @@ -0,0 +1,126 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +teacher_ckpt = 'http://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_1x_coco/paa_r101_fpn_1x_coco_20200821-0a1825a4.pth' # noqa + +model = dict( + type='LAD', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + # student + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='LADHead', + reg_decoded_bbox=True, + score_voting=True, + topk=9, + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.3), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.5)), + # teacher + teacher_ckpt=teacher_ckpt, + teacher_backbone=dict( + type='ResNet', + depth=101, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch'), + teacher_neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + teacher_bbox_head=dict( + type='LADHead', + reg_decoded_bbox=True, + score_voting=True, + topk=9, + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.3), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.5)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.1, + neg_iou_thr=0.1, + min_pos_iou=0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + score_voting=True, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) +train_dataloader = dict(batch_size=8, num_workers=4) +optim_wrapper = dict(type='AmpOptimWrapper', optimizer=dict(lr=0.01)) diff --git a/mmdetection/configs/lad/metafile.yml b/mmdetection/configs/lad/metafile.yml new file mode 100644 index 00000000..230132e6 --- /dev/null +++ b/mmdetection/configs/lad/metafile.yml @@ -0,0 +1,45 @@ +Collections: + - Name: Label Assignment Distillation + Metadata: + Training Data: COCO + Training Techniques: + - Label Assignment Distillation + - SGD with Momentum + - Weight Decay + Training Resources: 2x V100 GPUs + Architecture: + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/2108.10520 + Title: 'Improving Object Detection by Label Assignment Distillation' + README: configs/lad/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.19.0/mmdet/models/detectors/lad.py#L10 + Version: v2.19.0 + +Models: + - Name: lad_r101-paa-r50_fpn_2xb8_coco_1x + In Collection: Label Assignment Distillation + Config: configs/lad/lad_r101-paa-r50_fpn_2xb8_coco_1x.py + Metadata: + Training Memory (GB): 12.4 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lad/lad_r101_paa_r50_fpn_coco_1x/lad_r101_paa_r50_fpn_coco_1x_20220708_124357-9407ac54.pth + - Name: lad_r50-paa-r101_fpn_2xb8_coco_1x + In Collection: Label Assignment Distillation + Config: configs/lad/lad_r50-paa-r101_fpn_2xb8_coco_1x.py + Metadata: + Training Memory (GB): 8.9 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lad/lad_r50_paa_r101_fpn_coco_1x/lad_r50_paa_r101_fpn_coco_1x_20220708_124246-74c76ff0.pth diff --git a/mmdetection/configs/ld/README.md b/mmdetection/configs/ld/README.md new file mode 100644 index 00000000..65e16c79 --- /dev/null +++ b/mmdetection/configs/ld/README.md @@ -0,0 +1,43 @@ +# LD + +> [Localization Distillation for Dense Object Detection](https://arxiv.org/abs/2102.12252) + + + +## Abstract + +Knowledge distillation (KD) has witnessed its powerful capability in learning compact models in object detection. Previous KD methods for object detection mostly focus on imitating deep features within the imitation regions instead of mimicking classification logits due to its inefficiency in distilling localization information. In this paper, by reformulating the knowledge distillation process on localization, we present a novel localization distillation (LD) method which can efficiently transfer the localization knowledge from the teacher to the student. Moreover, we also heuristically introduce the concept of valuable localization region that can aid to selectively distill the semantic and localization knowledge for a certain region. Combining these two new components, for the first time, we show that logit mimicking can outperform feature imitation and localization knowledge distillation is more important and efficient than semantic knowledge for distilling object detectors. Our distillation scheme is simple as well as effective and can be easily applied to different dense object detectors. Experiments show that our LD can boost the AP score of GFocal-ResNet-50 with a single-scale 1× training schedule from 40.1 to 42.1 on the COCO benchmark without any sacrifice on the inference speed. + +
    + +
    + +## Results and Models + +### GFocalV1 with LD + +| Teacher | Student | Training schedule | Mini-batch size | AP (val) | Config | Download | +| :-------: | :-----: | :---------------: | :-------------: | :------: | :-----------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| -- | R-18 | 1x | 6 | 35.8 | | | +| R-101 | R-18 | 1x | 6 | 36.5 | [config](./ld_r18-gflv1-r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r18_gflv1_r101_fpn_coco_1x/ld_r18_gflv1_r101_fpn_coco_1x_20220702_062206-330e6332.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r18_gflv1_r101_fpn_coco_1x/ld_r18_gflv1_r101_fpn_coco_1x_20220702_062206.log.json) | +| -- | R-34 | 1x | 6 | 38.9 | | | +| R-101 | R-34 | 1x | 6 | 39.9 | [config](./ld_r34-gflv1-r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r34_gflv1_r101_fpn_coco_1x/ld_r34_gflv1_r101_fpn_coco_1x_20220630_134007-9bc69413.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r34_gflv1_r101_fpn_coco_1x/ld_r34_gflv1_r101_fpn_coco_1x_20220630_134007.log.json) | +| -- | R-50 | 1x | 6 | 40.1 | | | +| R-101 | R-50 | 1x | 6 | 41.0 | [config](./ld_r50-gflv1-r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r50_gflv1_r101_fpn_coco_1x/ld_r50_gflv1_r101_fpn_coco_1x_20220629_145355-8dc5bad8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r50_gflv1_r101_fpn_coco_1x/ld_r50_gflv1_r101_fpn_coco_1x_20220629_145355.log.json) | +| -- | R-101 | 2x | 6 | 44.6 | | | +| R-101-DCN | R-101 | 2x | 6 | 45.5 | [config](./ld_r101-gflv1-r101-dcn_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r101_gflv1_r101dcn_fpn_coco_2x/ld_r101_gflv1_r101dcn_fpn_coco_2x_20220629_185920-9e658426.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r101_gflv1_r101dcn_fpn_coco_2x/ld_r101_gflv1_r101dcn_fpn_coco_2x_20220629_185920.log.json) | + +## Note + +- Meaning of Config name: ld_r18(student model)\_gflv1(based on gflv1)\_r101(teacher model)\_fpn(neck)\_coco(dataset)\_1x(12 epoch).py + +## Citation + +```latex +@Inproceedings{zheng2022LD, + title={Localization Distillation for Dense Object Detection}, + author= {Zheng, Zhaohui and Ye, Rongguang and Wang, Ping and Ren, Dongwei and Zuo, Wangmeng and Hou, Qibin and Cheng, Mingming}, + booktitle={CVPR}, + year={2022} +} +``` diff --git a/mmdetection/configs/ld/ld_r101-gflv1-r101-dcn_fpn_2x_coco.py b/mmdetection/configs/ld/ld_r101-gflv1-r101-dcn_fpn_2x_coco.py new file mode 100644 index 00000000..a7e928bd --- /dev/null +++ b/mmdetection/configs/ld/ld_r101-gflv1-r101-dcn_fpn_2x_coco.py @@ -0,0 +1,49 @@ +_base_ = ['./ld_r18-gflv1-r101_fpn_1x_coco.py'] +teacher_ckpt = 'https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r101_fpn_dconv_c3-c5_mstrain_2x_coco/gfl_r101_fpn_dconv_c3-c5_mstrain_2x_coco_20200630_102002-134b07df.pth' # noqa +model = dict( + teacher_config='configs/gfl/gfl_r101-dconv-c3-c5_fpn_ms-2x_coco.py', + teacher_ckpt=teacher_ckpt, + backbone=dict( + type='ResNet', + depth=101, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5)) + +max_epochs = 24 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) + +# multi-scale training +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 480), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/ld/ld_r18-gflv1-r101_fpn_1x_coco.py b/mmdetection/configs/ld/ld_r18-gflv1-r101_fpn_1x_coco.py new file mode 100644 index 00000000..f18bb1d3 --- /dev/null +++ b/mmdetection/configs/ld/ld_r18-gflv1-r101_fpn_1x_coco.py @@ -0,0 +1,70 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +teacher_ckpt = 'https://download.openmmlab.com/mmdetection/v2.0/gfl/gfl_r101_fpn_mstrain_2x_coco/gfl_r101_fpn_mstrain_2x_coco_20200629_200126-dd12f847.pth' # noqa +model = dict( + type='KnowledgeDistillationSingleStageDetector', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + teacher_config='configs/gfl/gfl_r101_fpn_ms-2x_coco.py', + teacher_ckpt=teacher_ckpt, + backbone=dict( + type='ResNet', + depth=18, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict( + type='FPN', + in_channels=[64, 128, 256, 512], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='LDHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + loss_cls=dict( + type='QualityFocalLoss', + use_sigmoid=True, + beta=2.0, + loss_weight=1.0), + loss_dfl=dict(type='DistributionFocalLoss', loss_weight=0.25), + loss_ld=dict( + type='KnowledgeDistillationKLDivLoss', loss_weight=0.25, T=10), + reg_max=16, + loss_bbox=dict(type='GIoULoss', loss_weight=2.0)), + # training and testing settings + train_cfg=dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/ld/ld_r34-gflv1-r101_fpn_1x_coco.py b/mmdetection/configs/ld/ld_r34-gflv1-r101_fpn_1x_coco.py new file mode 100644 index 00000000..2198adc8 --- /dev/null +++ b/mmdetection/configs/ld/ld_r34-gflv1-r101_fpn_1x_coco.py @@ -0,0 +1,19 @@ +_base_ = ['./ld_r18-gflv1-r101_fpn_1x_coco.py'] +model = dict( + backbone=dict( + type='ResNet', + depth=34, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet34')), + neck=dict( + type='FPN', + in_channels=[64, 128, 256, 512], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5)) diff --git a/mmdetection/configs/ld/ld_r50-gflv1-r101_fpn_1x_coco.py b/mmdetection/configs/ld/ld_r50-gflv1-r101_fpn_1x_coco.py new file mode 100644 index 00000000..89ab5796 --- /dev/null +++ b/mmdetection/configs/ld/ld_r50-gflv1-r101_fpn_1x_coco.py @@ -0,0 +1,19 @@ +_base_ = ['./ld_r18-gflv1-r101_fpn_1x_coco.py'] +model = dict( + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5)) diff --git a/mmdetection/configs/ld/metafile.yml b/mmdetection/configs/ld/metafile.yml new file mode 100644 index 00000000..a807d1b8 --- /dev/null +++ b/mmdetection/configs/ld/metafile.yml @@ -0,0 +1,69 @@ +Collections: + - Name: Localization Distillation + Metadata: + Training Data: COCO + Training Techniques: + - Localization Distillation + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/2102.12252 + Title: 'Localization Distillation for Dense Object Detection' + README: configs/ld/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.11.0/mmdet/models/dense_heads/ld_head.py#L11 + Version: v2.11.0 + +Models: + - Name: ld_r18-gflv1-r101_fpn_1x_coco + In Collection: Localization Distillation + Config: configs/ld/ld_r18-gflv1-r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 1.8 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r18_gflv1_r101_fpn_coco_1x/ld_r18_gflv1_r101_fpn_coco_1x_20220702_062206-330e6332.pth + - Name: ld_r34-gflv1-r101_fpn_1x_coco + In Collection: Localization Distillation + Config: configs/ld/ld_r34-gflv1-r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 2.2 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r34_gflv1_r101_fpn_coco_1x/ld_r34_gflv1_r101_fpn_coco_1x_20220630_134007-9bc69413.pth + - Name: ld_r50-gflv1-r101_fpn_1x_coco + In Collection: Localization Distillation + Config: configs/ld/ld_r50-gflv1-r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.6 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r50_gflv1_r101_fpn_coco_1x/ld_r50_gflv1_r101_fpn_coco_1x_20220629_145355-8dc5bad8.pth + - Name: ld_r101-gflv1-r101-dcn_fpn_2x_coco + In Collection: Localization Distillation + Config: configs/ld/ld_r101-gflv1-r101-dcn_fpn_2x_coco.py + Metadata: + Training Memory (GB): 5.5 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ld/ld_r101_gflv1_r101dcn_fpn_coco_2x/ld_r101_gflv1_r101dcn_fpn_coco_2x_20220629_185920-9e658426.pth diff --git a/mmdetection/configs/legacy_1.x/README.md b/mmdetection/configs/legacy_1.x/README.md new file mode 100644 index 00000000..443a0a71 --- /dev/null +++ b/mmdetection/configs/legacy_1.x/README.md @@ -0,0 +1,54 @@ +# Legacy Configs in MMDetection V1.x + + + +Configs in this directory implement the legacy configs used by MMDetection V1.x and its model zoos. + +To help users convert their models from V1.x to MMDetection V2.0, we provide v1.x configs to inference the converted v1.x models. +Due to the BC-breaking changes in MMDetection V2.0 from MMDetection V1.x, running inference with the same model weights in these two version will produce different results. The difference will cause within 1% AP absolute difference as can be found in the following table. + +## Usage + +To upgrade the model version, the users need to do the following steps. + +### 1. Convert model weights + +There are three main difference in the model weights between V1.x and V2.0 codebases. + +1. Since the class order in all the detector's classification branch is reordered, all the legacy model weights need to go through the conversion process. +2. The regression and segmentation head no longer contain the background channel. Weights in these background channels should be removed to fix in the current codebase. +3. For two-stage detectors, their wegihts need to be upgraded since MMDetection V2.0 refactors all the two-stage detectors with `RoIHead`. + +The users can do the same modification as mentioned above for the self-implemented +detectors. We provide a scripts `tools/model_converters/upgrade_model_version.py` to convert the model weights in the V1.x model zoo. + +```bash +python tools/model_converters/upgrade_model_version.py ${OLD_MODEL_PATH} ${NEW_MODEL_PATH} --num-classes ${NUM_CLASSES} + +``` + +- OLD_MODEL_PATH: the path to load the model weights in 1.x version. +- NEW_MODEL_PATH: the path to save the converted model weights in 2.0 version. +- NUM_CLASSES: number of classes of the original model weights. Usually it is 81 for COCO dataset, 21 for VOC dataset. + The number of classes in V2.0 models should be equal to that in V1.x models - 1. + +### 2. Use configs with legacy settings + +After converting the model weights, checkout to the v1.2 release to find the corresponding config file that uses the legacy settings. +The V1.x models usually need these three legacy modules: `LegacyAnchorGenerator`, `LegacyDeltaXYWHBBoxCoder`, and `RoIAlign(align=False)`. +For models using ResNet Caffe backbones, they also need to change the pretrain name and the corresponding `img_norm_cfg`. +An example is in [`retinanet_r50-caffe_fpn_1x_coco_v1.py`](retinanet_r50-caffe_fpn_1x_coco_v1.py) +Then use the config to test the model weights. For most models, the obtained results should be close to that in V1.x. +We provide configs of some common structures in this directory. + +## Performance + +The performance change after converting the models in this directory are listed as the following. + +| Method | Style | Lr schd | V1.x box AP | V1.x mask AP | V2.0 box AP | V2.0 mask AP | Config | Download | +| :-------------------------: | :-----: | :-----: | :---------: | :----------: | :---------: | :----------: | :-------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------: | +| Mask R-CNN R-50-FPN | pytorch | 1x | 37.3 | 34.2 | 36.8 | 33.9 | [config](./mask-rcnn_r50_fpn_1x_coco_v1.py) | [model](https://s3.ap-northeast-2.amazonaws.com/open-mmlab/mmdetection/models/mask_rcnn_r50_fpn_1x_20181010-069fa190.pth) | +| RetinaNet R-50-FPN | caffe | 1x | 35.8 | - | 35.4 | - | [config](./retinanet_r50-caffe_fpn_1x_coco_v1.py) | | +| RetinaNet R-50-FPN | pytorch | 1x | 35.6 | - | 35.2 | - | [config](./retinanet_r50_fpn_1x_coco_v1.py) | [model](https://s3.ap-northeast-2.amazonaws.com/open-mmlab/mmdetection/models/retinanet_r50_fpn_1x_20181125-7b0c2548.pth) | +| Cascade Mask R-CNN R-50-FPN | pytorch | 1x | 41.2 | 35.7 | 40.8 | 35.6 | [config](./cascade-mask-rcnn_r50_fpn_1x_coco_v1.py) | [model](https://s3.ap-northeast-2.amazonaws.com/open-mmlab/mmdetection/models/cascade_mask_rcnn_r50_fpn_1x_20181123-88b170c9.pth) | +| SSD300-VGG16 | caffe | 120e | 25.7 | - | 25.4 | - | [config](./ssd300_coco_v1.py) | [model](https://s3.ap-northeast-2.amazonaws.com/open-mmlab/mmdetection/models/ssd300_coco_vgg16_caffe_120e_20181221-84d7110b.pth) | diff --git a/mmdetection/configs/legacy_1.x/cascade-mask-rcnn_r50_fpn_1x_coco_v1.py b/mmdetection/configs/legacy_1.x/cascade-mask-rcnn_r50_fpn_1x_coco_v1.py new file mode 100644 index 00000000..f948a7a9 --- /dev/null +++ b/mmdetection/configs/legacy_1.x/cascade-mask-rcnn_r50_fpn_1x_coco_v1.py @@ -0,0 +1,78 @@ +_base_ = [ + '../_base_/models/cascade-mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + type='CascadeRCNN', + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + anchor_generator=dict(type='LegacyAnchorGenerator', center_offset=0.5), + bbox_coder=dict( + type='LegacyDeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0])), + roi_head=dict( + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', + output_size=7, + sampling_ratio=2, + aligned=False)), + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + reg_class_agnostic=True, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='LegacyDeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2])), + dict( + type='Shared2FCBBoxHead', + reg_class_agnostic=True, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='LegacyDeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1])), + dict( + type='Shared2FCBBoxHead', + reg_class_agnostic=True, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='LegacyDeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067])), + ], + mask_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', + output_size=14, + sampling_ratio=2, + aligned=False)))) diff --git a/mmdetection/configs/legacy_1.x/faster-rcnn_r50_fpn_1x_coco_v1.py b/mmdetection/configs/legacy_1.x/faster-rcnn_r50_fpn_1x_coco_v1.py new file mode 100644 index 00000000..66bf9713 --- /dev/null +++ b/mmdetection/configs/legacy_1.x/faster-rcnn_r50_fpn_1x_coco_v1.py @@ -0,0 +1,38 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + type='FasterRCNN', + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + rpn_head=dict( + type='RPNHead', + anchor_generator=dict( + type='LegacyAnchorGenerator', + center_offset=0.5, + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict(type='LegacyDeltaXYWHBBoxCoder'), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + type='StandardRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', + output_size=7, + sampling_ratio=2, + aligned=False), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=dict( + bbox_coder=dict(type='LegacyDeltaXYWHBBoxCoder'), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn_proposal=dict(max_per_img=2000), + rcnn=dict(assigner=dict(match_low_quality=True)))) diff --git a/mmdetection/configs/legacy_1.x/mask-rcnn_r50_fpn_1x_coco_v1.py b/mmdetection/configs/legacy_1.x/mask-rcnn_r50_fpn_1x_coco_v1.py new file mode 100644 index 00000000..69080259 --- /dev/null +++ b/mmdetection/configs/legacy_1.x/mask-rcnn_r50_fpn_1x_coco_v1.py @@ -0,0 +1,34 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + rpn_head=dict( + anchor_generator=dict(type='LegacyAnchorGenerator', center_offset=0.5), + bbox_coder=dict(type='LegacyDeltaXYWHBBoxCoder'), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', + output_size=7, + sampling_ratio=2, + aligned=False)), + mask_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', + output_size=14, + sampling_ratio=2, + aligned=False)), + bbox_head=dict( + bbox_coder=dict(type='LegacyDeltaXYWHBBoxCoder'), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))), + + # model training and testing settings + train_cfg=dict( + rpn_proposal=dict(max_per_img=2000), + rcnn=dict(assigner=dict(match_low_quality=True)))) diff --git a/mmdetection/configs/legacy_1.x/retinanet_r50-caffe_fpn_1x_coco_v1.py b/mmdetection/configs/legacy_1.x/retinanet_r50-caffe_fpn_1x_coco_v1.py new file mode 100644 index 00000000..49abc31a --- /dev/null +++ b/mmdetection/configs/legacy_1.x/retinanet_r50-caffe_fpn_1x_coco_v1.py @@ -0,0 +1,16 @@ +_base_ = './retinanet_r50_fpn_1x_coco_v1.py' +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + # use caffe img_norm + mean=[102.9801, 115.9465, 122.7717], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron/resnet50_caffe'))) diff --git a/mmdetection/configs/legacy_1.x/retinanet_r50_fpn_1x_coco_v1.py b/mmdetection/configs/legacy_1.x/retinanet_r50_fpn_1x_coco_v1.py new file mode 100644 index 00000000..6198b971 --- /dev/null +++ b/mmdetection/configs/legacy_1.x/retinanet_r50_fpn_1x_coco_v1.py @@ -0,0 +1,17 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + bbox_head=dict( + type='RetinaHead', + anchor_generator=dict( + type='LegacyAnchorGenerator', + center_offset=0.5, + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict(type='LegacyDeltaXYWHBBoxCoder'), + loss_bbox=dict(type='SmoothL1Loss', beta=0.11, loss_weight=1.0))) diff --git a/mmdetection/configs/legacy_1.x/ssd300_coco_v1.py b/mmdetection/configs/legacy_1.x/ssd300_coco_v1.py new file mode 100644 index 00000000..e5ffc633 --- /dev/null +++ b/mmdetection/configs/legacy_1.x/ssd300_coco_v1.py @@ -0,0 +1,20 @@ +_base_ = [ + '../_base_/models/ssd300.py', '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] +# model settings +input_size = 300 +model = dict( + bbox_head=dict( + type='SSDHead', + anchor_generator=dict( + type='LegacySSDAnchorGenerator', + scale_major=False, + input_size=input_size, + basesize_ratio_range=(0.15, 0.9), + strides=[8, 16, 32, 64, 100, 300], + ratios=[[2], [2, 3], [2, 3], [2, 3], [2], [2]]), + bbox_coder=dict( + type='LegacyDeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]))) diff --git a/mmdetection/configs/libra_rcnn/README.md b/mmdetection/configs/libra_rcnn/README.md new file mode 100644 index 00000000..ee8015ba --- /dev/null +++ b/mmdetection/configs/libra_rcnn/README.md @@ -0,0 +1,53 @@ +# Libra R-CNN + +> [Libra R-CNN: Towards Balanced Learning for Object Detection](https://arxiv.org/abs/1904.02701) + + + +## Abstract + +Compared with model architectures, the training process, which is also crucial to the success of detectors, has received relatively less attention in object detection. In this work, we carefully revisit the standard training practice of detectors, and find that the detection performance is often limited by the imbalance during the training process, which generally consists in three levels - sample level, feature level, and objective level. To mitigate the adverse effects caused thereby, we propose Libra R-CNN, a simple but effective framework towards balanced learning for object detection. It integrates three novel components: IoU-balanced sampling, balanced feature pyramid, and balanced L1 loss, respectively for reducing the imbalance at sample, feature, and objective level. Benefitted from the overall balanced design, Libra R-CNN significantly improves the detection performance. Without bells and whistles, it achieves 2.5 points and 2.0 points higher Average Precision (AP) than FPN Faster R-CNN and RetinaNet respectively on MSCOCO. + +Instance recognition is rapidly advanced along with the developments of various deep convolutional neural networks. Compared to the architectures of networks, the training process, which is also crucial to the success of detectors, has received relatively less attention. In this work, we carefully revisit the standard training practice of detectors, and find that the detection performance is often limited by the imbalance during the training process, which generally consists in three levels - sample level, feature level, and objective level. To mitigate the adverse effects caused thereby, we propose Libra R-CNN, a simple yet effective framework towards balanced learning for instance recognition. It integrates IoU-balanced sampling, balanced feature pyramid, and objective re-weighting, respectively for reducing the imbalance at sample, feature, and objective level. Extensive experiments conducted on MS COCO, LVIS and Pascal VOC datasets prove the effectiveness of the overall balanced design. + +
    + +
    + +## Results and Models + +The results on COCO 2017val are shown in the below table. (results on test-dev are usually slightly higher than val) + +| Architecture | Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :----------: | :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster R-CNN | R-50-FPN | pytorch | 1x | 4.6 | 19.0 | 38.3 | [config](./libra-faster-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_r50_fpn_1x_coco/libra_faster_rcnn_r50_fpn_1x_coco_20200130-3afee3a9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_r50_fpn_1x_coco/libra_faster_rcnn_r50_fpn_1x_coco_20200130_204655.log.json) | +| Fast R-CNN | R-50-FPN | pytorch | 1x | | | | | | +| Faster R-CNN | R-101-FPN | pytorch | 1x | 6.5 | 14.4 | 40.1 | [config](./libra-faster-rcnn_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_r101_fpn_1x_coco/libra_faster_rcnn_r101_fpn_1x_coco_20200203-8dba6a5a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_r101_fpn_1x_coco/libra_faster_rcnn_r101_fpn_1x_coco_20200203_001405.log.json) | +| Faster R-CNN | X-101-64x4d-FPN | pytorch | 1x | 10.8 | 8.5 | 42.7 | [config](./libra-faster-rcnn_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_x101_64x4d_fpn_1x_coco/libra_faster_rcnn_x101_64x4d_fpn_1x_coco_20200315-3a7d0488.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_x101_64x4d_fpn_1x_coco/libra_faster_rcnn_x101_64x4d_fpn_1x_coco_20200315_231625.log.json) | +| RetinaNet | R-50-FPN | pytorch | 1x | 4.2 | 17.7 | 37.6 | [config](./libra-retinanet_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_retinanet_r50_fpn_1x_coco/libra_retinanet_r50_fpn_1x_coco_20200205-804d94ce.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_retinanet_r50_fpn_1x_coco/libra_retinanet_r50_fpn_1x_coco_20200205_112757.log.json) | + +## Citation + +We provide config files to reproduce the results in the CVPR 2019 paper [Libra R-CNN](https://arxiv.org/pdf/1904.02701.pdf). + +The extended version of [Libra R-CNN](https://arxiv.org/pdf/2108.10175.pdf) is accpeted by IJCV. + +```latex +@inproceedings{pang2019libra, + title={Libra R-CNN: Towards Balanced Learning for Object Detection}, + author={Pang, Jiangmiao and Chen, Kai and Shi, Jianping and Feng, Huajun and Ouyang, Wanli and Dahua Lin}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + year={2019} +} + +@article{pang2021towards, + title={Towards Balanced Learning for Instance Recognition}, + author={Pang, Jiangmiao and Chen, Kai and Li, Qi and Xu, Zhihai and Feng, Huajun and Shi, Jianping and Ouyang, Wanli and Lin, Dahua}, + journal={International Journal of Computer Vision}, + volume={129}, + number={5}, + pages={1376--1393}, + year={2021}, + publisher={Springer} +} +``` diff --git a/mmdetection/configs/libra_rcnn/libra-fast-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/libra_rcnn/libra-fast-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..2efe440c --- /dev/null +++ b/mmdetection/configs/libra_rcnn/libra-fast-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,52 @@ +_base_ = '../fast_rcnn/fast-rcnn_r50_fpn_1x_coco.py' +# model settings +model = dict( + neck=[ + dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + dict( + type='BFP', + in_channels=256, + num_levels=5, + refine_level=2, + refine_type='non_local') + ], + roi_head=dict( + bbox_head=dict( + loss_bbox=dict( + _delete_=True, + type='BalancedL1Loss', + alpha=0.5, + gamma=1.5, + beta=1.0, + loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rcnn=dict( + sampler=dict( + _delete_=True, + type='CombinedSampler', + num=512, + pos_fraction=0.25, + add_gt_as_proposals=True, + pos_sampler=dict(type='InstanceBalancedPosSampler'), + neg_sampler=dict( + type='IoUBalancedNegSampler', + floor_thr=-1, + floor_fraction=0, + num_bins=3))))) + +# MMEngine support the following two ways, users can choose +# according to convenience +# _base_.train_dataloader.dataset.proposal_file = 'libra_proposals/rpn_r50_fpn_1x_train2017.pkl' # noqa +train_dataloader = dict( + dataset=dict(proposal_file='libra_proposals/rpn_r50_fpn_1x_train2017.pkl')) + +# _base_.val_dataloader.dataset.proposal_file = 'libra_proposals/rpn_r50_fpn_1x_val2017.pkl' # noqa +# test_dataloader = _base_.val_dataloader +val_dataloader = dict( + dataset=dict(proposal_file='libra_proposals/rpn_r50_fpn_1x_val2017.pkl')) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/libra_rcnn/libra-faster-rcnn_r101_fpn_1x_coco.py b/mmdetection/configs/libra_rcnn/libra-faster-rcnn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..985df64c --- /dev/null +++ b/mmdetection/configs/libra_rcnn/libra-faster-rcnn_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './libra-faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..f9ee507d --- /dev/null +++ b/mmdetection/configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,41 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +# model settings +model = dict( + neck=[ + dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + dict( + type='BFP', + in_channels=256, + num_levels=5, + refine_level=2, + refine_type='non_local') + ], + roi_head=dict( + bbox_head=dict( + loss_bbox=dict( + _delete_=True, + type='BalancedL1Loss', + alpha=0.5, + gamma=1.5, + beta=1.0, + loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict(sampler=dict(neg_pos_ub=5), allowed_border=-1), + rcnn=dict( + sampler=dict( + _delete_=True, + type='CombinedSampler', + num=512, + pos_fraction=0.25, + add_gt_as_proposals=True, + pos_sampler=dict(type='InstanceBalancedPosSampler'), + neg_sampler=dict( + type='IoUBalancedNegSampler', + floor_thr=-1, + floor_fraction=0, + num_bins=3))))) diff --git a/mmdetection/configs/libra_rcnn/libra-faster-rcnn_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/libra_rcnn/libra-faster-rcnn_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..158e238e --- /dev/null +++ b/mmdetection/configs/libra_rcnn/libra-faster-rcnn_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './libra-faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/libra_rcnn/libra-retinanet_r50_fpn_1x_coco.py b/mmdetection/configs/libra_rcnn/libra-retinanet_r50_fpn_1x_coco.py new file mode 100644 index 00000000..be274209 --- /dev/null +++ b/mmdetection/configs/libra_rcnn/libra-retinanet_r50_fpn_1x_coco.py @@ -0,0 +1,26 @@ +_base_ = '../retinanet/retinanet_r50_fpn_1x_coco.py' +# model settings +model = dict( + neck=[ + dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_input', + num_outs=5), + dict( + type='BFP', + in_channels=256, + num_levels=5, + refine_level=1, + refine_type='non_local') + ], + bbox_head=dict( + loss_bbox=dict( + _delete_=True, + type='BalancedL1Loss', + alpha=0.5, + gamma=1.5, + beta=0.11, + loss_weight=1.0))) diff --git a/mmdetection/configs/libra_rcnn/metafile.yml b/mmdetection/configs/libra_rcnn/metafile.yml new file mode 100644 index 00000000..f01bd02b --- /dev/null +++ b/mmdetection/configs/libra_rcnn/metafile.yml @@ -0,0 +1,99 @@ +Collections: + - Name: Libra R-CNN + Metadata: + Training Data: COCO + Training Techniques: + - IoU-Balanced Sampling + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Balanced Feature Pyramid + Paper: + URL: https://arxiv.org/abs/1904.02701 + Title: 'Libra R-CNN: Towards Balanced Learning for Object Detection' + README: configs/libra_rcnn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/necks/bfp.py#L10 + Version: v2.0.0 + +Models: + - Name: libra-faster-rcnn_r50_fpn_1x_coco + In Collection: Libra R-CNN + Config: configs/libra_rcnn/libra-faster-rcnn_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.6 + inference time (ms/im): + - value: 52.63 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_r50_fpn_1x_coco/libra_faster_rcnn_r50_fpn_1x_coco_20200130-3afee3a9.pth + + - Name: libra-faster-rcnn_r101_fpn_1x_coco + In Collection: Libra R-CNN + Config: configs/libra_rcnn/libra-faster-rcnn_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.5 + inference time (ms/im): + - value: 69.44 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_r101_fpn_1x_coco/libra_faster_rcnn_r101_fpn_1x_coco_20200203-8dba6a5a.pth + + - Name: libra-faster-rcnn_x101-64x4d_fpn_1x_coco + In Collection: Libra R-CNN + Config: configs/libra_rcnn/libra-faster-rcnn_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 10.8 + inference time (ms/im): + - value: 117.65 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_faster_rcnn_x101_64x4d_fpn_1x_coco/libra_faster_rcnn_x101_64x4d_fpn_1x_coco_20200315-3a7d0488.pth + + - Name: libra-retinanet_r50_fpn_1x_coco + In Collection: Libra R-CNN + Config: configs/libra_rcnn/libra-retinanet_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.2 + inference time (ms/im): + - value: 56.5 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/libra_rcnn/libra_retinanet_r50_fpn_1x_coco/libra_retinanet_r50_fpn_1x_coco_20200205-804d94ce.pth diff --git a/mmdetection/configs/lvis/README.md b/mmdetection/configs/lvis/README.md new file mode 100644 index 00000000..57aeda43 --- /dev/null +++ b/mmdetection/configs/lvis/README.md @@ -0,0 +1,56 @@ +# LVIS + +> [LVIS: A Dataset for Large Vocabulary Instance Segmentation](https://arxiv.org/abs/1908.03195) + + + +## Abstract + +Progress on object detection is enabled by datasets that focus the research community's attention on open challenges. This process led us from simple images to complex scenes and from bounding boxes to segmentation masks. In this work, we introduce LVIS (pronounced \`el-vis'): a new dataset for Large Vocabulary Instance Segmentation. We plan to collect ~2 million high-quality instance segmentation masks for over 1000 entry-level object categories in 164k images. Due to the Zipfian distribution of categories in natural images, LVIS naturally has a long tail of categories with few training samples. Given that state-of-the-art deep learning methods for object detection perform poorly in the low-sample regime, we believe that our dataset poses an important and exciting new scientific challenge. + +
    + +
    + +## Common Setting + +- Please follow [install guide](../../docs/get_started.md#install-mmdetection) to install open-mmlab forked cocoapi first. + +- Run following scripts to install our forked lvis-api. + + ```shell + pip install git+https://github.com/lvis-dataset/lvis-api.git + ``` + +- All experiments use oversample strategy [here](../../docs/tutorials/customize_dataset.md#class-balanced-dataset) with oversample threshold `1e-3`. + +- The size of LVIS v0.5 is half of COCO, so schedule `2x` in LVIS is roughly the same iterations as `1x` in COCO. + +## Results and models of LVIS v0.5 + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :----------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 2x | - | - | 26.1 | 25.9 | [config](./mask-rcnn_r50_fpn_sample1e-3_ms-2x_lvis-v0.5.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_2x_lvis-dbd06831.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_2x_lvis_20200531_160435.log.json) | +| R-101-FPN | pytorch | 2x | - | - | 27.1 | 27.0 | [config](./mask-rcnn_r101_fpn_sample1e-3_ms-2x_lvis-v0.5.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r101_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_r101_fpn_sample1e-3_mstrain_2x_lvis-54582ee2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r101_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_r101_fpn_sample1e-3_mstrain_2x_lvis_20200601_134748.log.json) | +| X-101-32x4d-FPN | pytorch | 2x | - | - | 26.7 | 26.9 | [config](./mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_2x_lvis-3cf55ea2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_2x_lvis_20200531_221749.log.json) | +| X-101-64x4d-FPN | pytorch | 2x | - | - | 26.4 | 26.0 | [config](./mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_2x_lvis-1c99a5ad.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_2x_lvis_20200601_194651.log.json) | + +## Results and models of LVIS v1 + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :--------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 1x | 9.1 | - | 22.5 | 21.7 | [config](./mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_r50_fpn_sample1e-3_mstrain_1x_lvis_v1-aa78ac3d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_r50_fpn_sample1e-3_mstrain_1x_lvis_v1-20200829_061305.log.json) | +| R-101-FPN | pytorch | 1x | 10.8 | - | 24.6 | 23.6 | [config](./mask-rcnn_r101_fpn_sample1e-3_ms-1x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r101_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_r101_fpn_sample1e-3_mstrain_1x_lvis_v1-ec55ce32.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r101_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_r101_fpn_sample1e-3_mstrain_1x_lvis_v1-20200829_070959.log.json) | +| X-101-32x4d-FPN | pytorch | 1x | 11.8 | - | 26.7 | 25.5 | [config](./mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-1x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_1x_lvis_v1-ebbc5c81.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_1x_lvis_v1-20200829_071317.log.json) | +| X-101-64x4d-FPN | pytorch | 1x | 14.6 | - | 27.2 | 25.8 | [config](./mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-1x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_1x_lvis_v1-43d9edfe.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_1x_lvis_v1-20200830_060206.log.json) | + +## Citation + +```latex +@inproceedings{gupta2019lvis, + title={{LVIS}: A Dataset for Large Vocabulary Instance Segmentation}, + author={Gupta, Agrim and Dollar, Piotr and Girshick, Ross}, + booktitle={Proceedings of the {IEEE} Conference on Computer Vision and Pattern Recognition}, + year={2019} +} +``` diff --git a/mmdetection/configs/lvis/mask-rcnn_r101_fpn_sample1e-3_ms-1x_lvis-v1.py b/mmdetection/configs/lvis/mask-rcnn_r101_fpn_sample1e-3_ms-1x_lvis-v1.py new file mode 100644 index 00000000..3994d75a --- /dev/null +++ b/mmdetection/configs/lvis/mask-rcnn_r101_fpn_sample1e-3_ms-1x_lvis-v1.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/lvis/mask-rcnn_r101_fpn_sample1e-3_ms-2x_lvis-v0.5.py b/mmdetection/configs/lvis/mask-rcnn_r101_fpn_sample1e-3_ms-2x_lvis-v0.5.py new file mode 100644 index 00000000..ed8b3639 --- /dev/null +++ b/mmdetection/configs/lvis/mask-rcnn_r101_fpn_sample1e-3_ms-2x_lvis-v0.5.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_sample1e-3_ms-2x_lvis-v0.5.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/lvis/mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py b/mmdetection/configs/lvis/mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py new file mode 100644 index 00000000..cdd3683e --- /dev/null +++ b/mmdetection/configs/lvis/mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py @@ -0,0 +1,13 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/lvis_v1_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + roi_head=dict( + bbox_head=dict(num_classes=1203), mask_head=dict(num_classes=1203)), + test_cfg=dict( + rcnn=dict( + score_thr=0.0001, + # LVIS allows up to 300 + max_per_img=300))) diff --git a/mmdetection/configs/lvis/mask-rcnn_r50_fpn_sample1e-3_ms-2x_lvis-v0.5.py b/mmdetection/configs/lvis/mask-rcnn_r50_fpn_sample1e-3_ms-2x_lvis-v0.5.py new file mode 100644 index 00000000..b36b6c17 --- /dev/null +++ b/mmdetection/configs/lvis/mask-rcnn_r50_fpn_sample1e-3_ms-2x_lvis-v0.5.py @@ -0,0 +1,13 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/lvis_v0.5_instance.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] +model = dict( + roi_head=dict( + bbox_head=dict(num_classes=1230), mask_head=dict(num_classes=1230)), + test_cfg=dict( + rcnn=dict( + score_thr=0.0001, + # LVIS allows up to 300 + max_per_img=300))) diff --git a/mmdetection/configs/lvis/mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-1x_lvis-v1.py b/mmdetection/configs/lvis/mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-1x_lvis-v1.py new file mode 100644 index 00000000..9da3ab6d --- /dev/null +++ b/mmdetection/configs/lvis/mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-1x_lvis-v1.py @@ -0,0 +1,14 @@ +_base_ = './mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/lvis/mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py b/mmdetection/configs/lvis/mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py new file mode 100644 index 00000000..9a097c94 --- /dev/null +++ b/mmdetection/configs/lvis/mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py @@ -0,0 +1,14 @@ +_base_ = './mask-rcnn_r50_fpn_sample1e-3_ms-2x_lvis-v0.5.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/lvis/mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-1x_lvis-v1.py b/mmdetection/configs/lvis/mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-1x_lvis-v1.py new file mode 100644 index 00000000..b0819b3e --- /dev/null +++ b/mmdetection/configs/lvis/mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-1x_lvis-v1.py @@ -0,0 +1,14 @@ +_base_ = './mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/lvis/mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py b/mmdetection/configs/lvis/mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py new file mode 100644 index 00000000..9d272008 --- /dev/null +++ b/mmdetection/configs/lvis/mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py @@ -0,0 +1,14 @@ +_base_ = './mask-rcnn_r50_fpn_sample1e-3_ms-2x_lvis-v0.5.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/lvis/metafile.yml b/mmdetection/configs/lvis/metafile.yml new file mode 100644 index 00000000..f8def96c --- /dev/null +++ b/mmdetection/configs/lvis/metafile.yml @@ -0,0 +1,128 @@ +Models: + - Name: mask-rcnn_r50_fpn_sample1e-3_ms-2x_lvis-v0.5 + In Collection: Mask R-CNN + Config: configs/lvis/mask-rcnn_r50_fpn_sample1e-3_ms-2x_lvis-v0.5.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v0.5 + Metrics: + box AP: 26.1 + - Task: Instance Segmentation + Dataset: LVIS v0.5 + Metrics: + mask AP: 25.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_2x_lvis-dbd06831.pth + + - Name: mask-rcnn_r101_fpn_sample1e-3_ms-2x_lvis-v0.5 + In Collection: Mask R-CNN + Config: configs/lvis/mask-rcnn_r101_fpn_sample1e-3_ms-2x_lvis-v0.5.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v0.5 + Metrics: + box AP: 27.1 + - Task: Instance Segmentation + Dataset: LVIS v0.5 + Metrics: + mask AP: 27.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r101_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_r101_fpn_sample1e-3_mstrain_2x_lvis-54582ee2.pth + + - Name: mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-2x_lvis-v0.5 + In Collection: Mask R-CNN + Config: configs/lvis/mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v0.5 + Metrics: + box AP: 26.7 + - Task: Instance Segmentation + Dataset: LVIS v0.5 + Metrics: + mask AP: 26.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_2x_lvis-3cf55ea2.pth + + - Name: mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-2x_lvis-v0.5 + In Collection: Mask R-CNN + Config: configs/lvis/mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-2x_lvis-v0.5.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v0.5 + Metrics: + box AP: 26.4 + - Task: Instance Segmentation + Dataset: LVIS v0.5 + Metrics: + mask AP: 26.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_2x_lvis/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_2x_lvis-1c99a5ad.pth + + - Name: mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1 + In Collection: Mask R-CNN + Config: configs/lvis/mask-rcnn_r50_fpn_sample1e-3_ms-1x_lvis-v1.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 22.5 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 21.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r50_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_r50_fpn_sample1e-3_mstrain_1x_lvis_v1-aa78ac3d.pth + + - Name: mask-rcnn_r101_fpn_sample1e-3_ms-1x_lvis-v1 + In Collection: Mask R-CNN + Config: configs/lvis/mask-rcnn_r101_fpn_sample1e-3_ms-1x_lvis-v1.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 24.6 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 23.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_r101_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_r101_fpn_sample1e-3_mstrain_1x_lvis_v1-ec55ce32.pth + + - Name: mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-1x_lvis-v1 + In Collection: Mask R-CNN + Config: configs/lvis/mask-rcnn_x101-32x4d_fpn_sample1e-3_ms-1x_lvis-v1.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 26.7 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 25.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_x101_32x4d_fpn_sample1e-3_mstrain_1x_lvis_v1-ebbc5c81.pth + + - Name: mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-1x_lvis-v1 + In Collection: Mask R-CNN + Config: configs/lvis/mask-rcnn_x101-64x4d_fpn_sample1e-3_ms-1x_lvis-v1.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 27.2 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 25.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/lvis/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_1x_lvis_v1/mask_rcnn_x101_64x4d_fpn_sample1e-3_mstrain_1x_lvis_v1-43d9edfe.pth diff --git a/mmdetection/configs/mask2former/README.md b/mmdetection/configs/mask2former/README.md new file mode 100644 index 00000000..94b0821e --- /dev/null +++ b/mmdetection/configs/mask2former/README.md @@ -0,0 +1,76 @@ +# Mask2Former + +> [Masked-attention Mask Transformer for Universal Image Segmentation](http://arxiv.org/abs/2112.01527) + + + +## Abstract + +Image segmentation is about grouping pixels with different semantics, e.g., category or instance membership, where each choice of semantics defines a task. While only the semantics of each task differ, current research focuses on designing specialized architectures for each task. We present Masked-attention Mask Transformer (Mask2Former), a new architecture capable of addressing any image segmentation task (panoptic, instance or semantic). Its key components include masked attention, which extracts localized features by constraining cross-attention within predicted mask regions. In addition to reducing the research effort by at least three times, it outperforms the best specialized architectures by a significant margin on four popular datasets. Most notably, Mask2Former sets a new state-of-the-art for panoptic segmentation (57.8 PQ on COCO), instance segmentation (50.1 AP on COCO) and semantic segmentation (57.7 mIoU on ADE20K). + +
    + +
    + +## Introduction + +Mask2Former requires COCO and [COCO-panoptic](http://images.cocodataset.org/annotations/panoptic_annotations_trainval2017.zip) dataset for training and evaluation. You need to download and extract it in the COCO dataset path. +The directory should be like this. + +```none +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +| | | ├── instances_train2017.json +| | | ├── instances_val2017.json +│ │ │ ├── panoptic_train2017.json +│ │ │ ├── panoptic_train2017 +│ │ │ ├── panoptic_val2017.json +│ │ │ ├── panoptic_val2017 +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +``` + +## Results and Models + +### Panoptic segmentation + +| Backbone | style | Pretrain | Lr schd | Mem (GB) | Inf time (fps) | PQ | box mAP | mask mAP | Config | Download | +| -------- | ------- | ------------ | ------- | -------- | -------------- | ---- | ------- | -------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| R-50 | pytorch | ImageNet-1K | 50e | 13.9 | - | 52.0 | 44.5 | 41.8 | [config](./mask2former_r50_8xb2-lsj-50e_coco-panoptic.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic/mask2former_r50_8xb2-lsj-50e_coco-panoptic_20230118_125535-54df384a.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic/mask2former_r50_8xb2-lsj-50e_coco-panoptic_20230118_125535.log.json) | +| R-101 | pytorch | ImageNet-1K | 50e | 16.1 | - | 52.4 | 45.3 | 42.4 | [config](./mask2former_r101_8xb2-lsj-50e_coco-panoptic.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_r101_8xb2-lsj-50e_coco-panoptic/mask2former_r101_8xb2-lsj-50e_coco-panoptic_20220329_225104-c74d4d71.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_r101_lsj_8x2_50e_coco-panoptic/mask2former_r101_lsj_8x2_50e_coco-panoptic_20220329_225104.log.json) | +| Swin-T | - | ImageNet-1K | 50e | 15.9 | - | 53.4 | 46.3 | 43.4 | [config](./mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic_20220326_224553-3ec9e0ae.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_swin-t-p4-w7-224_lsj_8x2_50e_coco-panoptic/mask2former_swin-t-p4-w7-224_lsj_8x2_50e_coco-panoptic_20220326_224553.log.json) | +| Swin-S | - | ImageNet-1K | 50e | 19.1 | - | 54.5 | 47.8 | 44.5 | [config](./mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic_20220329_225200-4a16ded7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_swin-s-p4-w7-224_lsj_8x2_50e_coco-panoptic/mask2former_swin-s-p4-w7-224_lsj_8x2_50e_coco-panoptic_20220329_225200.log.json) | +| Swin-B | - | ImageNet-1K | 50e | 26.0 | - | 55.1 | 48.2 | 44.9 | [config](./mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic/mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic_20220331_002244-8a651d82.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_swin-b-p4-w12-384_lsj_8x2_50e_coco-panoptic/mask2former_swin-b-p4-w12-384_lsj_8x2_50e_coco-panoptic_20220331_002244.log.json) | +| Swin-B | - | ImageNet-21K | 50e | 25.8 | - | 56.3 | 50.0 | 46.3 | [config](./mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic/mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic_20220329_230021-05ec7315.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_swin-b-p4-w12-384-in21k_lsj_8x2_50e_coco-panoptic/mask2former_swin-b-p4-w12-384-in21k_lsj_8x2_50e_coco-panoptic_20220329_230021.log.json) | +| Swin-L | - | ImageNet-21K | 100e | 21.1 | - | 57.6 | 52.2 | 48.5 | [config](./mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic/mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic_20220407_104949-82f8d28d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_swin-l-p4-w12-384-in21k_lsj_16x1_100e_coco-panoptic/mask2former_swin-l-p4-w12-384-in21k_lsj_16x1_100e_coco-panoptic_20220407_104949.log.json) | + +### Instance segmentation + +| Backbone | style | Pretrain | Lr schd | Mem (GB) | Inf time (fps) | box mAP | mask mAP | Config | Download | +| -------- | ------- | ----------- | ------- | -------- | -------------- | ------- | -------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| R-50 | pytorch | ImageNet-1K | 50e | 13.7 | - | 45.7 | 42.9 | [config](./mask2former_r50_8xb2-lsj-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_r50_8xb2-lsj-50e_coco/mask2former_r50_8xb2-lsj-50e_coco_20220506_191028-41b088b6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_r50_lsj_8x2_50e_coco/mask2former_r50_lsj_8x2_50e_coco_20220506_191028.log.json) | +| R-101 | pytorch | ImageNet-1K | 50e | 15.5 | - | 46.7 | 44.0 | [config](./mask2former_r101_8xb2-lsj-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_r101_8xb2-lsj-50e_coco/mask2former_r101_8xb2-lsj-50e_coco_20220426_100250-ecf181e2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_r101_lsj_8x2_50e_coco/mask2former_r101_lsj_8x2_50e_coco_20220426_100250.log.json) | +| Swin-T | - | ImageNet-1K | 50e | 15.3 | - | 47.7 | 44.7 | [config](./mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco_20220508_091649-01b0f990.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_swin-t-p4-w7-224_lsj_8x2_50e_coco/mask2former_swin-t-p4-w7-224_lsj_8x2_50e_coco_20220508_091649.log.json) | +| Swin-S | - | ImageNet-1K | 50e | 18.8 | - | 49.3 | 46.1 | [config](./mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco_20220504_001756-c9d0c4f2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask2former/mask2former_swin-s-p4-w7-224_lsj_8x2_50e_coco/mask2former_swin-s-p4-w7-224_lsj_8x2_50e_coco_20220504_001756.log.json) | + +### Note + +1. The performance is unstable. The `Mask2Former-R50-coco-panoptic` may fluctuate about 0.2 PQ. The models other than `Mask2Former-R50-coco-panoptic` were trained with mmdet 2.x and have been converted for mmdet 3.x. +2. We have trained the instance segmentation models many times (see more details in [PR 7571](https://github.com/open-mmlab/mmdetection/pull/7571)). The results of the trained models are relatively stable (+- 0.2), and have a certain gap (about 0.2 AP) in comparison with the results in the [paper](http://arxiv.org/abs/2112.01527). However, the performance of the model trained with the official code is unstable and may also be slightly lower than the reported results as mentioned in the [issue](https://github.com/facebookresearch/Mask2Former/issues/46). + +## Citation + +```latex +@article{cheng2021mask2former, + title={Masked-attention Mask Transformer for Universal Image Segmentation}, + author={Bowen Cheng and Ishan Misra and Alexander G. Schwing and Alexander Kirillov and Rohit Girdhar}, + journal={arXiv}, + year={2021} +} +``` diff --git a/mmdetection/configs/mask2former/mask2former_r101_8xb2-lsj-50e_coco-panoptic.py b/mmdetection/configs/mask2former/mask2former_r101_8xb2-lsj-50e_coco-panoptic.py new file mode 100644 index 00000000..66685a2f --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_r101_8xb2-lsj-50e_coco-panoptic.py @@ -0,0 +1,7 @@ +_base_ = './mask2former_r50_8xb2-lsj-50e_coco-panoptic.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/mask2former/mask2former_r101_8xb2-lsj-50e_coco.py b/mmdetection/configs/mask2former/mask2former_r101_8xb2-lsj-50e_coco.py new file mode 100644 index 00000000..f4c29906 --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_r101_8xb2-lsj-50e_coco.py @@ -0,0 +1,7 @@ +_base_ = ['./mask2former_r50_8xb2-lsj-50e_coco.py'] + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py b/mmdetection/configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py new file mode 100644 index 00000000..c53e981b --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py @@ -0,0 +1,251 @@ +_base_ = [ + '../_base_/datasets/coco_panoptic.py', '../_base_/default_runtime.py' +] +image_size = (1024, 1024) +batch_augments = [ + dict( + type='BatchFixedSizePad', + size=image_size, + img_pad_value=0, + pad_mask=True, + mask_pad_value=0, + pad_seg=True, + seg_pad_value=255) +] +data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + pad_mask=True, + mask_pad_value=0, + pad_seg=True, + seg_pad_value=255, + batch_augments=batch_augments) + +num_things_classes = 80 +num_stuff_classes = 53 +num_classes = num_things_classes + num_stuff_classes +model = dict( + type='Mask2Former', + data_preprocessor=data_preprocessor, + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=-1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + panoptic_head=dict( + type='Mask2FormerHead', + in_channels=[256, 512, 1024, 2048], # pass to pixel_decoder inside + strides=[4, 8, 16, 32], + feat_channels=256, + out_channels=256, + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes, + num_queries=100, + num_transformer_feat_level=3, + pixel_decoder=dict( + type='MSDeformAttnPixelDecoder', + num_outs=3, + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU'), + encoder=dict( # DeformableDetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DeformableDetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + num_heads=8, + num_levels=3, + num_points=4, + dropout=0.0, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0.0, + act_cfg=dict(type='ReLU', inplace=True)))), + positional_encoding=dict(num_feats=128, normalize=True)), + enforce_decoder_input_project=False, + positional_encoding=dict(num_feats=128, normalize=True), + transformer_decoder=dict( # Mask2FormerTransformerDecoder + return_intermediate=True, + num_layers=9, + layer_cfg=dict( # Mask2FormerTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.0, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.0, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.0, + act_cfg=dict(type='ReLU', inplace=True))), + init_cfg=None), + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=2.0, + reduction='mean', + class_weight=[1.0] * num_classes + [0.1]), + loss_mask=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='mean', + loss_weight=5.0), + loss_dice=dict( + type='DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=5.0)), + panoptic_fusion_head=dict( + type='MaskFormerFusionHead', + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes, + loss_panoptic=None, + init_cfg=None), + train_cfg=dict( + num_points=12544, + oversample_ratio=3.0, + importance_sample_ratio=0.75, + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='ClassificationCost', weight=2.0), + dict( + type='CrossEntropyLossCost', weight=5.0, use_sigmoid=True), + dict(type='DiceCost', weight=5.0, pred_act=True, eps=1.0) + ]), + sampler=dict(type='MaskPseudoSampler')), + test_cfg=dict( + panoptic_on=True, + # For now, the dataset does not support + # evaluating semantic segmentation metric. + semantic_on=False, + instance_on=True, + # max_per_image is for instance segmentation. + max_per_image=100, + iou_thr=0.8, + # In Mask2Former's panoptic postprocessing, + # it will filter mask area where score is less than 0.5 . + filter_low_score=True), + init_cfg=None) + +# dataset settings +data_root = 'data/coco/' +train_pipeline = [ + dict( + type='LoadImageFromFile', + to_float32=True, + backend_args={{_base_.backend_args}}), + dict( + type='LoadPanopticAnnotations', + with_bbox=True, + with_mask=True, + with_seg=True, + backend_args={{_base_.backend_args}}), + dict(type='RandomFlip', prob=0.5), + # large scale jittering + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_size=image_size, + crop_type='absolute', + recompute_bbox=True, + allow_negative_crop=True), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +val_evaluator = [ + dict( + type='CocoPanopticMetric', + ann_file=data_root + 'annotations/panoptic_val2017.json', + seg_prefix=data_root + 'annotations/panoptic_val2017/', + backend_args={{_base_.backend_args}}), + dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + backend_args={{_base_.backend_args}}) +] +test_evaluator = val_evaluator + +# optimizer +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.05, + eps=1e-8, + betas=(0.9, 0.999)), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi, + }, + norm_decay_mult=0.0), + clip_grad=dict(max_norm=0.01, norm_type=2)) + +# learning policy +max_iters = 368750 +param_scheduler = dict( + type='MultiStepLR', + begin=0, + end=max_iters, + by_epoch=False, + milestones=[327778, 355092], + gamma=0.1) + +# Before 365001th iteration, we do evaluation every 5000 iterations. +# After 365000th iteration, we do evaluation every 368750 iterations, +# which means that we do evaluation at the end of training. +interval = 5000 +dynamic_intervals = [(max_iters // interval * interval + 1, max_iters)] +train_cfg = dict( + type='IterBasedTrainLoop', + max_iters=max_iters, + val_interval=interval, + dynamic_intervals=dynamic_intervals) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + by_epoch=False, + save_last=True, + max_keep_ckpts=3, + interval=interval)) +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=False) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco.py b/mmdetection/configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco.py new file mode 100644 index 00000000..24a17f58 --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco.py @@ -0,0 +1,100 @@ +_base_ = ['./mask2former_r50_8xb2-lsj-50e_coco-panoptic.py'] + +num_things_classes = 80 +num_stuff_classes = 0 +num_classes = num_things_classes + num_stuff_classes +image_size = (1024, 1024) +batch_augments = [ + dict( + type='BatchFixedSizePad', + size=image_size, + img_pad_value=0, + pad_mask=True, + mask_pad_value=0, + pad_seg=False) +] +data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + pad_mask=True, + mask_pad_value=0, + pad_seg=False, + batch_augments=batch_augments) +model = dict( + data_preprocessor=data_preprocessor, + panoptic_head=dict( + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes, + loss_cls=dict(class_weight=[1.0] * num_classes + [0.1])), + panoptic_fusion_head=dict( + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes), + test_cfg=dict(panoptic_on=False)) + +# dataset settings +train_pipeline = [ + dict( + type='LoadImageFromFile', + to_float32=True, + backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='RandomFlip', prob=0.5), + # large scale jittering + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + resize_type='Resize', + keep_ratio=True), + dict( + type='RandomCrop', + crop_size=image_size, + crop_type='absolute', + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-5, 1e-5), by_mask=True), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict( + type='LoadImageFromFile', + to_float32=True, + backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +train_dataloader = dict( + dataset=dict( + type=dataset_type, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + pipeline=train_pipeline)) +val_dataloader = dict( + dataset=dict( + type=dataset_type, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args={{_base_.backend_args}}) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mask2former/mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic.py b/mmdetection/configs/mask2former/mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic.py new file mode 100644 index 00000000..b275f231 --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic.py @@ -0,0 +1,5 @@ +_base_ = ['./mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic.py'] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window12_384_22k.pth' # noqa + +model = dict( + backbone=dict(init_cfg=dict(type='Pretrained', checkpoint=pretrained))) diff --git a/mmdetection/configs/mask2former/mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic.py b/mmdetection/configs/mask2former/mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic.py new file mode 100644 index 00000000..bd59400b --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic.py @@ -0,0 +1,42 @@ +_base_ = ['./mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py'] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window12_384.pth' # noqa + +depths = [2, 2, 18, 2] +model = dict( + backbone=dict( + pretrain_img_size=384, + embed_dims=128, + depths=depths, + num_heads=[4, 8, 16, 32], + window_size=12, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + panoptic_head=dict(in_channels=[128, 256, 512, 1024])) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/mmdetection/configs/mask2former/mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic.py b/mmdetection/configs/mask2former/mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic.py new file mode 100644 index 00000000..e203ffc9 --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic.py @@ -0,0 +1,25 @@ +_base_ = ['./mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic.py'] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa + +model = dict( + backbone=dict( + embed_dims=192, + num_heads=[6, 12, 24, 48], + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + panoptic_head=dict(num_queries=200, in_channels=[192, 384, 768, 1536])) + +train_dataloader = dict(batch_size=1, num_workers=1) + +# learning policy +max_iters = 737500 +param_scheduler = dict(end=max_iters, milestones=[655556, 710184]) + +# Before 735001th iteration, we do evaluation every 5000 iterations. +# After 735000th iteration, we do evaluation every 737500 iterations, +# which means that we do evaluation at the end of training.' +interval = 5000 +dynamic_intervals = [(max_iters // interval * interval + 1, max_iters)] +train_cfg = dict( + max_iters=max_iters, + val_interval=interval, + dynamic_intervals=dynamic_intervals) diff --git a/mmdetection/configs/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py b/mmdetection/configs/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py new file mode 100644 index 00000000..f9d081db --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py @@ -0,0 +1,37 @@ +_base_ = ['./mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py'] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_small_patch4_window7_224.pth' # noqa + +depths = [2, 2, 18, 2] +model = dict( + backbone=dict( + depths=depths, init_cfg=dict(type='Pretrained', + checkpoint=pretrained))) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/mmdetection/configs/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco.py b/mmdetection/configs/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco.py new file mode 100644 index 00000000..69d5e8c6 --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco.py @@ -0,0 +1,37 @@ +_base_ = ['./mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco.py'] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_small_patch4_window7_224.pth' # noqa + +depths = [2, 2, 18, 2] +model = dict( + backbone=dict( + depths=depths, init_cfg=dict(type='Pretrained', + checkpoint=pretrained))) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/mmdetection/configs/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py b/mmdetection/configs/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py new file mode 100644 index 00000000..1c00d7a6 --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py @@ -0,0 +1,58 @@ +_base_ = ['./mask2former_r50_8xb2-lsj-50e_coco-panoptic.py'] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_tiny_patch4_window7_224.pth' # noqa + +depths = [2, 2, 6, 2] +model = dict( + type='Mask2Former', + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=96, + depths=depths, + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + panoptic_head=dict( + type='Mask2FormerHead', in_channels=[96, 192, 384, 768]), + init_cfg=None) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) + +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/mmdetection/configs/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco.py b/mmdetection/configs/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco.py new file mode 100644 index 00000000..5bb9c218 --- /dev/null +++ b/mmdetection/configs/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco.py @@ -0,0 +1,56 @@ +_base_ = ['./mask2former_r50_8xb2-lsj-50e_coco.py'] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_tiny_patch4_window7_224.pth' # noqa +depths = [2, 2, 6, 2] +model = dict( + type='Mask2Former', + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=96, + depths=depths, + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + panoptic_head=dict( + type='Mask2FormerHead', in_channels=[96, 192, 384, 768]), + init_cfg=None) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/mmdetection/configs/mask2former/metafile.yml b/mmdetection/configs/mask2former/metafile.yml new file mode 100644 index 00000000..33212392 --- /dev/null +++ b/mmdetection/configs/mask2former/metafile.yml @@ -0,0 +1,223 @@ +Collections: + - Name: Mask2Former + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Weight Decay + Training Resources: 8x A100 GPUs + Architecture: + - Mask2Former + Paper: + URL: https://arxiv.org/pdf/2112.01527 + Title: 'Masked-attention Mask Transformer for Universal Image Segmentation' + README: configs/mask2former/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.23.0/mmdet/models/detectors/mask2former.py#L7 + Version: v2.23.0 + +Models: +- Name: mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic + In Collection: Mask2Former + Config: configs/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py + Metadata: + Training Memory (GB): 19.1 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 44.5 + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 54.5 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco-panoptic_20220329_225200-4a16ded7.pth +- Name: mask2former_r101_8xb2-lsj-50e_coco + In Collection: Mask2Former + Config: configs/mask2former/mask2former_r101_8xb2-lsj-50e_coco.py + Metadata: + Training Memory (GB): 15.5 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 44.0 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_r101_8xb2-lsj-50e_coco/mask2former_r101_8xb2-lsj-50e_coco_20220426_100250-ecf181e2.pth +- Name: mask2former_r101_8xb2-lsj-50e_coco-panoptic + In Collection: Mask2Former + Config: configs/mask2former/mask2former_r101_8xb2-lsj-50e_coco-panoptic.py + Metadata: + Training Memory (GB): 16.1 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 42.4 + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 52.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_r101_8xb2-lsj-50e_coco-panoptic/mask2former_r101_8xb2-lsj-50e_coco-panoptic_20220329_225104-c74d4d71.pth +- Name: mask2former_r50_8xb2-lsj-50e_coco-panoptic + In Collection: Mask2Former + Config: configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic.py + Metadata: + Training Memory (GB): 13.9 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.8 + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 52.0 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_r50_8xb2-lsj-50e_coco-panoptic/mask2former_r50_8xb2-lsj-50e_coco-panoptic_20230118_125535-54df384a.pth +- Name: mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic + In Collection: Mask2Former + Config: configs/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic.py + Metadata: + Training Memory (GB): 15.9 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 43.4 + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 53.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco-panoptic_20220326_224553-3ec9e0ae.pth +- Name: mask2former_r50_8xb2-lsj-50e_coco + In Collection: Mask2Former + Config: configs/mask2former/mask2former_r50_8xb2-lsj-50e_coco.py + Metadata: + Training Memory (GB): 13.7 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 42.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_r50_8xb2-lsj-50e_coco/mask2former_r50_8xb2-lsj-50e_coco_20220506_191028-41b088b6.pth +- Name: mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic + In Collection: Mask2Former + Config: configs/mask2former/mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic.py + Metadata: + Training Memory (GB): 21.1 + Iterations: 737500 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 52.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 48.5 + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 57.6 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic/mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic_20220407_104949-82f8d28d.pth +- Name: mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic + In Collection: Mask2Former + Config: configs/mask2former/mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic.py + Metadata: + Training Memory (GB): 25.8 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 46.3 + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 56.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic/mask2former_swin-b-p4-w12-384-in21k_8xb2-lsj-50e_coco-panoptic_20220329_230021-05ec7315.pth +- Name: mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic + In Collection: Mask2Former + Config: configs/mask2former/mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic.py + Metadata: + Training Memory (GB): 26.0 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 48.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 44.9 + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 55.1 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic/mask2former_swin-b-p4-w12-384_8xb2-lsj-50e_coco-panoptic_20220331_002244-8a651d82.pth +- Name: mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco + In Collection: Mask2Former + Config: configs/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco.py + Metadata: + Training Memory (GB): 15.3 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 44.7 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco/mask2former_swin-t-p4-w7-224_8xb2-lsj-50e_coco_20220508_091649-01b0f990.pth +- Name: mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco + In Collection: Mask2Former + Config: configs/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco.py + Metadata: + Training Memory (GB): 18.8 + Iterations: 368750 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 49.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 46.1 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco/mask2former_swin-s-p4-w7-224_8xb2-lsj-50e_coco_20220504_001756-c9d0c4f2.pth diff --git a/mmdetection/configs/mask2former_vis/README.md b/mmdetection/configs/mask2former_vis/README.md new file mode 100644 index 00000000..69965729 --- /dev/null +++ b/mmdetection/configs/mask2former_vis/README.md @@ -0,0 +1,81 @@ +# Mask2Former for Video Instance Segmentation + +## Abstract + + + +We find Mask2Former also achieves state-of-the-art performance on video instance segmentation without modifying the architecture, the loss or even the training pipeline. In this report, we show universal image segmentation architectures trivially generalize to video segmentation by directly predicting 3D segmentation volumes. Specifically, Mask2Former sets a new state-of-the-art of 60.4 AP on YouTubeVIS-2019 and 52.6 AP on YouTubeVIS-2021. We believe Mask2Former is also capable of handling video semantic and panoptic segmentation, given its versatility in image segmentation. We hope this will make state-of-theart video segmentation research more accessible and bring more attention to designing universal image and video segmentation architectures. + + + +
    + +
    + +## Citation + + + +```latex +@inproceedings{cheng2021mask2former, + title={Masked-attention Mask Transformer for Universal Image Segmentation}, + author={Bowen Cheng and Ishan Misra and Alexander G. Schwing and Alexander Kirillov and Rohit Girdhar}, + journal={CVPR}, + year={2022} +} +``` + +## Results and models of Mask2Former on YouTube-VIS 2021 validation dataset + +Note: Codalab has closed the evaluation portal of `YouTube-VIS 2019`, so we do not provide the results of `YouTube-VIS 2019` at present. If you want to evaluate the results of `YouTube-VIS 2021`, at present, you can submit the result to the evaluation portal of `YouTube-VIS 2022`. The value of `AP_S` is the result of `YouTube-VIS 2021`. + +| Method | Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | AP | Config | Download | +| :----------------------: | :------: | :-----: | :-----: | :------: | :------------: | :--: | :---------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Mask2Former | R-50 | pytorch | 8e | 6.0 | - | 41.3 | [config](mask2former_r50_8xb2-8e_youtubevis2021.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021/mask2former_r50_8xb2-8e_youtubevis2021_20230426_131833-5d215283.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021/mask2former_r50_8xb2-8e_youtubevis2021_20230426_131833.json) | +| Mask2Former | R-101 | pytorch | 8e | 7.5 | - | 42.3 | [config](mask2former_r101_8xb2-8e_youtubevis2021.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former_vis/mask2former_r101_8xb2-8e_youtubevis2021/mask2former_r101_8xb2-8e_youtubevis2021_20220823_092747-8077d115.pth) \| [log](https://download.openmmlab.com/mmtracking/vis/mask2former/mask2former_r101_8xb2-8e_youtubevis2021_20220823_092747.json) | +| Mask2Former(200 queries) | Swin-L | pytorch | 8e | 18.5 | - | 52.3 | [config](mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mask2former_vis/mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021/mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021_20220907_124752-48252603.pth) \| [log](https://download.openmmlab.com/mmtracking/vis/mask2former/mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021_20220907_124752.json) | + +## Get started + +### 1. Development Environment Setup + +Tracking Development Environment Setup can refer to this [document](../../docs/en/get_started.md). + +### 2. Dataset Prepare + +Tracking Dataset Prepare can refer to this [document](../../docs/en/user_guides/tracking_dataset_prepare.md). + +### 3. Training + +Due to the influence of parameters such as learning rate in default configuration file, we recommend using 8 GPUs for training in order to reproduce accuracy. You can use the following command to start the training. + +```shell +# Training Mask2Former on YouTube-VIS-2021 dataset with following command. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_train.sh configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py 8 +``` + +If you want to know about more detailed usage of `train.py/dist_train.sh/slurm_train.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 4. Testing and evaluation + +If you want to get the results of the [YouTube-VOS](https://youtube-vos.org/dataset/vis/) val/test set, please use the following command to generate result files that can be used for submission. It will be stored in `./youtube_vis_results.submission_file.zip`, you can modify the saved path in `test_evaluator` of the config. + +```shell +# The number after config file represents the number of GPUs used. +bash tools/dist_test_tracking.sh configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py --checkpoint ${CHECKPOINT_PATH} +``` + +If you want to know about more detailed usage of `test_tracking.py/dist_test_tracking.sh/slurm_test_tracking.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 5.Inference + +Use a single GPU to predict a video and save it as a video. + +```shell +python demo/mot_demo.py demo/demo_mot.mp4 configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py --checkpoint {CHECKPOINT_PATH} --out vis.mp4 +``` + +If you want to know about more detailed usage of `mot_demo.py`, please refer to this [document](../../docs/en/user_guides/tracking_inference.md). diff --git a/mmdetection/configs/mask2former_vis/mask2former_r101_8xb2-8e_youtubevis2019.py b/mmdetection/configs/mask2former_vis/mask2former_r101_8xb2-8e_youtubevis2019.py new file mode 100644 index 00000000..3ba4aea8 --- /dev/null +++ b/mmdetection/configs/mask2former_vis/mask2former_r101_8xb2-8e_youtubevis2019.py @@ -0,0 +1,12 @@ +_base_ = './mask2former_r50_8xb2-8e_youtubevis2019.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + init_cfg=dict( + type='Pretrained', + checkpoint='https://download.openmmlab.com/mmdetection/v3.0/' + 'mask2former/mask2former_r101_8xb2-lsj-50e_coco/' + 'mask2former_r101_8xb2-lsj-50e_coco_20220426_100250-ecf181e2.pth')) diff --git a/mmdetection/configs/mask2former_vis/mask2former_r101_8xb2-8e_youtubevis2021.py b/mmdetection/configs/mask2former_vis/mask2former_r101_8xb2-8e_youtubevis2021.py new file mode 100644 index 00000000..95f9ceeb --- /dev/null +++ b/mmdetection/configs/mask2former_vis/mask2former_r101_8xb2-8e_youtubevis2021.py @@ -0,0 +1,12 @@ +_base_ = './mask2former_r50_8xb2-8e_youtubevis2021.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + init_cfg=dict( + type='Pretrained', + checkpoint='https://download.openmmlab.com/mmdetection/v3.0/' + 'mask2former/mask2former_r101_8xb2-lsj-50e_coco/' + 'mask2former_r101_8xb2-lsj-50e_coco_20220426_100250-ecf181e2.pth')) diff --git a/mmdetection/configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2019.py b/mmdetection/configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2019.py new file mode 100644 index 00000000..8dc03bf9 --- /dev/null +++ b/mmdetection/configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2019.py @@ -0,0 +1,174 @@ +_base_ = ['../_base_/datasets/youtube_vis.py', '../_base_/default_runtime.py'] + +num_classes = 40 +num_frames = 2 +model = dict( + type='Mask2FormerVideo', + data_preprocessor=dict( + type='TrackDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=-1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + track_head=dict( + type='Mask2FormerTrackHead', + in_channels=[256, 512, 1024, 2048], # pass to pixel_decoder inside + strides=[4, 8, 16, 32], + feat_channels=256, + out_channels=256, + num_classes=num_classes, + num_queries=100, + num_frames=num_frames, + num_transformer_feat_level=3, + pixel_decoder=dict( + type='MSDeformAttnPixelDecoder', + num_outs=3, + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU'), + encoder=dict( # DeformableDetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DeformableDetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + num_heads=8, + num_levels=3, + num_points=4, + im2col_step=128, + dropout=0.0, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0.0, + act_cfg=dict(type='ReLU', inplace=True)))), + positional_encoding=dict(num_feats=128, normalize=True)), + enforce_decoder_input_project=False, + positional_encoding=dict( + type='SinePositionalEncoding3D', num_feats=128, normalize=True), + transformer_decoder=dict( # Mask2FormerTransformerDecoder + return_intermediate=True, + num_layers=9, + layer_cfg=dict( # Mask2FormerTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.0, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.0, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.0, + act_cfg=dict(type='ReLU', inplace=True))), + init_cfg=None), + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=2.0, + reduction='mean', + class_weight=[1.0] * num_classes + [0.1]), + loss_mask=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='mean', + loss_weight=5.0), + loss_dice=dict( + type='DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=5.0), + train_cfg=dict( + num_points=12544, + oversample_ratio=3.0, + importance_sample_ratio=0.75, + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='ClassificationCost', weight=2.0), + dict( + type='CrossEntropyLossCost', + weight=5.0, + use_sigmoid=True), + dict(type='DiceCost', weight=5.0, pred_act=True, eps=1.0) + ]), + sampler=dict(type='MaskPseudoSampler'))), + init_cfg=dict( + type='Pretrained', + checkpoint='https://download.openmmlab.com/mmdetection/v3.0/' + 'mask2former/mask2former_r50_8xb2-lsj-50e_coco/' + 'mask2former_r50_8xb2-lsj-50e_coco_20220506_191028-41b088b6.pth')) + +# optimizer +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.05, + eps=1e-8, + betas=(0.9, 0.999)), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi, + }, + norm_decay_mult=0.0), + clip_grad=dict(max_norm=0.01, norm_type=2)) + +# learning policy +max_iters = 6000 +param_scheduler = dict( + type='MultiStepLR', + begin=0, + end=max_iters, + by_epoch=False, + milestones=[ + 4000, + ], + gamma=0.1) +# runtime settings +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iters, val_interval=6001) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='TrackLocalVisualizer', vis_backends=vis_backends, name='visualizer') + +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', by_epoch=False, save_last=True, interval=2000), + visualization=dict(type='TrackVisualizationHook', draw=False)) +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=False) + +# evaluator +val_evaluator = dict( + type='YouTubeVISMetric', + metric='youtube_vis_ap', + outfile_prefix='./youtube_vis_results', + format_only=True) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py b/mmdetection/configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py new file mode 100644 index 00000000..158fe52d --- /dev/null +++ b/mmdetection/configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py @@ -0,0 +1,37 @@ +_base_ = './mask2former_r50_8xb2-8e_youtubevis2019.py' + +dataset_type = 'YouTubeVISDataset' +data_root = 'data/youtube_vis_2021/' +dataset_version = data_root[-5:-1] # 2019 or 2021 + +train_dataloader = dict( + dataset=dict( + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2021_train.json')) + +val_dataloader = dict( + dataset=dict( + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2021_valid.json')) +test_dataloader = val_dataloader + +# learning policy +max_iters = 8000 +param_scheduler = dict( + type='MultiStepLR', + begin=0, + end=max_iters, + by_epoch=False, + milestones=[ + 5500, + ], + gamma=0.1) +# runtime settings +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iters, val_interval=8001) + +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', by_epoch=False, save_last=True, interval=500)) diff --git a/mmdetection/configs/mask2former_vis/mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021.py b/mmdetection/configs/mask2former_vis/mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021.py new file mode 100644 index 00000000..94dcccf4 --- /dev/null +++ b/mmdetection/configs/mask2former_vis/mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021.py @@ -0,0 +1,64 @@ +_base_ = ['./mask2former_r50_8xb2-8e_youtubevis2021.py'] +depths = [2, 2, 18, 2] +model = dict( + type='Mask2FormerVideo', + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=depths, + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + frozen_stages=-1, + init_cfg=None), + track_head=dict( + type='Mask2FormerTrackHead', + in_channels=[192, 384, 768, 1536], + num_queries=200), + init_cfg=dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v3.0/mask2former/' + 'mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic/' + 'mask2former_swin-l-p4-w12-384-in21k_16xb1-lsj-100e_coco-panoptic_' + '20220407_104949-82f8d28d.pth')) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/mmdetection/configs/mask2former_vis/metafile.yml b/mmdetection/configs/mask2former_vis/metafile.yml new file mode 100644 index 00000000..f5f4bd7c --- /dev/null +++ b/mmdetection/configs/mask2former_vis/metafile.yml @@ -0,0 +1,53 @@ +Collections: + - Name: Mask2Former + Metadata: + Training Techniques: + - AdamW + - Weight Decay + Training Resources: 8x A100 GPUs + Architecture: + - Mask2Former + Paper: + URL: https://arxiv.org/pdf/2112.10764.pdf + Title: Mask2Former for Video Instance Segmentation + README: configs/mask2former/README.md + +Models: + - Name: mask2former_r50_8xb2-8e_youtubevis2021 + In Collection: Mask2Former + Config: configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py + Metadata: + Training Data: YouTube-VIS 2021 + Training Memory (GB): 6.0 + Results: + - Task: Video Instance Segmentation + Dataset: YouTube-VIS 2021 + Metrics: + AP: 41.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021/mask2former_r50_8xb2-8e_youtubevis2021_20230426_131833-5d215283.pth + + - Name: mask2former_r101_8xb2-8e_youtubevis2021 + In Collection: Mask2Former + Config: configs/mask2former_vis/mask2former_r101_8xb2-8e_youtubevis2021.py + Metadata: + Training Data: YouTube-VIS 2021 + Training Memory (GB): 7.5 + Results: + - Task: Video Instance Segmentation + Dataset: YouTube-VIS 2021 + Metrics: + AP: 42.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former_vis/mask2former_r101_8xb2-8e_youtubevis2021/mask2former_r101_8xb2-8e_youtubevis2021_20220823_092747-8077d115.pth + + - Name: mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021.py + In Collection: Mask2Former + Config: configs/mask2former_vis/mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021.py + Metadata: + Training Data: YouTube-VIS 2021 + Training Memory (GB): 18.5 + Results: + - Task: Video Instance Segmentation + Dataset: YouTube-VIS 2021 + Metrics: + AP: 52.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mask2former_vis/mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021/mask2former_swin-l-p4-w12-384-in21k_8xb2-8e_youtubevis2021_20220907_124752-48252603.pth diff --git a/mmdetection/configs/mask_rcnn/README.md b/mmdetection/configs/mask_rcnn/README.md new file mode 100644 index 00000000..afc5c3c9 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/README.md @@ -0,0 +1,59 @@ +# Mask R-CNN + +> [Mask R-CNN](https://arxiv.org/abs/1703.06870) + + + +## Abstract + +We present a conceptually simple, flexible, and general framework for object instance segmentation. Our approach efficiently detects objects in an image while simultaneously generating a high-quality segmentation mask for each instance. The method, called Mask R-CNN, extends Faster R-CNN by adding a branch for predicting an object mask in parallel with the existing branch for bounding box recognition. Mask R-CNN is simple to train and adds only a small overhead to Faster R-CNN, running at 5 fps. Moreover, Mask R-CNN is easy to generalize to other tasks, e.g., allowing us to estimate human poses in the same framework. We show top results in all three tracks of the COCO suite of challenges, including instance segmentation, bounding-box object detection, and person keypoint detection. Without bells and whistles, Mask R-CNN outperforms all existing, single-model entries on every task, including the COCO 2016 challenge winners. We hope our simple and effective approach will serve as a solid baseline and help ease future research in instance-level recognition. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :---------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | caffe | 1x | 4.3 | | 38.0 | 34.4 | [config](./mask-rcnn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_1x_coco/mask_rcnn_r50_caffe_fpn_1x_coco_bbox_mAP-0.38__segm_mAP-0.344_20200504_231812-0ebd1859.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_1x_coco/mask_rcnn_r50_caffe_fpn_1x_coco_20200504_231812.log.json) | +| R-50-FPN | pytorch | 1x | 4.4 | 16.1 | 38.2 | 34.7 | [config](./mask-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205_050542.log.json) | +| R-50-FPN (FP16) | pytorch | 1x | 3.6 | 24.1 | 38.1 | 34.7 | [config](./mask-rcnn_r50_fpn_amp-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fp16/mask_rcnn_r50_fpn_fp16_1x_coco/mask_rcnn_r50_fpn_fp16_1x_coco_20200205-59faf7e4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fp16/mask_rcnn_r50_fpn_fp16_1x_coco/mask_rcnn_r50_fpn_fp16_1x_coco_20200205_130539.log.json) | +| R-50-FPN | pytorch | 2x | - | - | 39.2 | 35.4 | [config](./mask-rcnn_r50_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_2x_coco/mask_rcnn_r50_fpn_2x_coco_bbox_mAP-0.392__segm_mAP-0.354_20200505_003907-3e542a40.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_2x_coco/mask_rcnn_r50_fpn_2x_coco_20200505_003907.log.json) | +| R-101-FPN | caffe | 1x | | | 40.4 | 36.4 | [config](./mask-rcnn_r101-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_caffe_fpn_1x_coco/mask_rcnn_r101_caffe_fpn_1x_coco_20200601_095758-805e06c1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_caffe_fpn_1x_coco/mask_rcnn_r101_caffe_fpn_1x_coco_20200601_095758.log.json) | +| R-101-FPN | pytorch | 1x | 6.4 | 13.5 | 40.0 | 36.1 | [config](./mask-rcnn_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_1x_coco/mask_rcnn_r101_fpn_1x_coco_20200204-1efe0ed5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_1x_coco/mask_rcnn_r101_fpn_1x_coco_20200204_144809.log.json) | +| R-101-FPN | pytorch | 2x | - | - | 40.8 | 36.6 | [config](./mask-rcnn_r101_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_2x_coco/mask_rcnn_r101_fpn_2x_coco_bbox_mAP-0.408__segm_mAP-0.366_20200505_071027-14b391c7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_2x_coco/mask_rcnn_r101_fpn_2x_coco_20200505_071027.log.json) | +| X-101-32x4d-FPN | pytorch | 1x | 7.6 | 11.3 | 41.9 | 37.5 | [config](./mask-rcnn_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_1x_coco/mask_rcnn_x101_32x4d_fpn_1x_coco_20200205-478d0b67.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_1x_coco/mask_rcnn_x101_32x4d_fpn_1x_coco_20200205_034906.log.json) | +| X-101-32x4d-FPN | pytorch | 2x | - | - | 42.2 | 37.8 | [config](./mask-rcnn_x101-32x4d_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_2x_coco/mask_rcnn_x101_32x4d_fpn_2x_coco_bbox_mAP-0.422__segm_mAP-0.378_20200506_004702-faef898c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_2x_coco/mask_rcnn_x101_32x4d_fpn_2x_coco_20200506_004702.log.json) | +| X-101-64x4d-FPN | pytorch | 1x | 10.7 | 8.0 | 42.8 | 38.4 | [config](./mask-rcnn_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_1x_coco/mask_rcnn_x101_64x4d_fpn_1x_coco_20200201-9352eb0d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_1x_coco/mask_rcnn_x101_64x4d_fpn_1x_coco_20200201_124310.log.json) | +| X-101-64x4d-FPN | pytorch | 2x | - | - | 42.7 | 38.1 | [config](./mask-rcnn_x101-64x4d_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_2x_coco/mask_rcnn_x101_64x4d_fpn_2x_coco_20200509_224208-39d6f70c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_2x_coco/mask_rcnn_x101_64x4d_fpn_2x_coco_20200509_224208.log.json) | +| X-101-32x8d-FPN | pytorch | 1x | 10.6 | - | 42.8 | 38.3 | [config](./mask-rcnn_x101-32x8d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x8d_fpn_1x_coco/mask_rcnn_x101_32x8d_fpn_1x_coco_20220630_173841-0aaf329e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x8d_fpn_1x_coco/mask_rcnn_x101_32x8d_fpn_1x_coco_20220630_173841.log.json) | + +## Pre-trained Models + +We also train some models with longer schedules and multi-scale training. The users could finetune them for downstream tasks. + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :--------------------------------------------------------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :-----------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [R-50-FPN](./mask-rcnn_r50-caffe_fpn_ms-poly-2x_coco.py) | caffe | 2x | 4.3 | | 40.3 | 36.5 | [config](./mask-rcnn_r50-caffe_fpn_ms-poly-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_2x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_2x_coco_bbox_mAP-0.403__segm_mAP-0.365_20200504_231822-a75c98ce.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_2x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_2x_coco_20200504_231822.log.json) | +| [R-50-FPN](./mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco.py) | caffe | 3x | 4.3 | | 40.8 | 37.0 | [config](./mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_20200504_163245.log.json) | +| [R-50-FPN](./mask-rcnn_r50_fpn_ms-poly-3x_coco.py) | pytorch | 3x | 4.1 | | 40.9 | 37.1 | [config](./mask-rcnn_r50_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_fpn_mstrain-poly_3x_coco_20210524_201154-21b550bb.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_fpn_mstrain-poly_3x_coco_20210524_201154.log.json) | +| [R-101-FPN](./mask-rcnn_r101-caffe_fpn_ms-poly-3x_coco.py) | caffe | 3x | 5.9 | | 42.9 | 38.5 | [config](./mask-rcnn_r101-caffe_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r101_caffe_fpn_mstrain-poly_3x_coco_20210526_132339-3c33ce02.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn_r101_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r101_caffe_fpn_mstrain-poly_3x_coco_20210526_132339.log.json) | +| [R-101-FPN](./mask-rcnn_r101_fpn_ms-poly-3x_coco.py) | pytorch | 3x | 6.1 | | 42.7 | 38.5 | [config](./mask-rcnn_r101_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_mstrain-poly_3x_coco/mask_rcnn_r101_fpn_mstrain-poly_3x_coco_20210524_200244-5675c317.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_mstrain-poly_3x_coco/mask_rcnn_r101_fpn_mstrain-poly_3x_coco_20210524_200244.log.json) | +| [x101-32x4d-FPN](./mask-rcnn_x101-32x4d_fpn_ms-poly-3x_coco.py) | pytorch | 3x | 7.3 | | 43.6 | 39.0 | [config](./mask-rcnn_x101-32x4d_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_mstrain-poly_3x_coco/mask_rcnn_x101_32x4d_fpn_mstrain-poly_3x_coco_20210524_201410-abcd7859.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_mstrain-poly_3x_coco/mask_rcnn_x101_32x4d_fpn_mstrain-poly_3x_coco_20210524_201410.log.json) | +| [X-101-32x8d-FPN](./mask-rcnn_x101-32x8d_fpn_ms-poly-3x_coco.py) | pytorch | 1x | 10.4 | | 43.4 | 39.0 | [config](./mask-rcnn_x101-32x8d_fpn_ms-poly-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x8d_fpn_mstrain-poly_1x_coco/mask_rcnn_x101_32x8d_fpn_mstrain-poly_1x_coco_20220630_170346-b4637974.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x8d_fpn_mstrain-poly_1x_coco/mask_rcnn_x101_32x8d_fpn_mstrain-poly_1x_coco_20220630_170346.log.json) | +| [X-101-32x8d-FPN](./mask-rcnn_x101-32x8d_fpn_ms-poly-3x_coco.py) | pytorch | 3x | 10.3 | | 44.3 | 39.5 | [config](./mask-rcnn_x101-32x8d_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x8d_fpn_mstrain-poly_3x_coco/mask_rcnn_x101_32x8d_fpn_mstrain-poly_3x_coco_20210607_161042-8bd2c639.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x8d_fpn_mstrain-poly_3x_coco/mask_rcnn_x101_32x8d_fpn_mstrain-poly_3x_coco_20210607_161042.log.json) | +| [X-101-64x4d-FPN](./mask-rcnn_x101-64x4d_fpn_ms-poly_3x_coco.py) | pytorch | 3x | 10.4 | | 44.5 | 39.7 | [config](./mask-rcnn_x101-64x4d_fpn_ms-poly_3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_mstrain-poly_3x_coco/mask_rcnn_x101_64x4d_fpn_mstrain-poly_3x_coco_20210526_120447-c376f129.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_mstrain-poly_3x_coco/mask_rcnn_x101_64x4d_fpn_mstrain-poly_3x_coco_20210526_120447.log.json) | + +## Citation + +```latex +@article{He_2017, + title={Mask R-CNN}, + journal={2017 IEEE International Conference on Computer Vision (ICCV)}, + publisher={IEEE}, + author={He, Kaiming and Gkioxari, Georgia and Dollar, Piotr and Girshick, Ross}, + year={2017}, + month={Oct} +} +``` diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..09808e4b --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './mask-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r101-caffe_fpn_ms-poly-3x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r101-caffe_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..e723aea8 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r101-caffe_fpn_ms-poly-3x_coco.py @@ -0,0 +1,19 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + depth=101, + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..af91ff0b --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_2x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_2x_coco.py new file mode 100644 index 00000000..a5599e7c --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..45235105 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './mask-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_ms-poly-3x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..384f6dcd --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r101_fpn_ms-poly-3x_coco.py @@ -0,0 +1,10 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r18_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r18_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..5b9219c9 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r18_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './mask-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe-c4_1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe-c4_1x_coco.py new file mode 100644 index 00000000..9919f11c --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe-c4_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50-caffe-c4.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..4124f138 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,13 @@ +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-1x_coco.py new file mode 100644 index 00000000..7702ae14 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-1x_coco.py @@ -0,0 +1,28 @@ +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs'), +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py new file mode 100644 index 00000000..94d94dd3 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py @@ -0,0 +1,31 @@ +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-2x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-2x_coco.py new file mode 100644 index 00000000..dbf87bb8 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-2x_coco.py @@ -0,0 +1,15 @@ +_base_ = './mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py' + +train_cfg = dict(max_epochs=24) +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=24, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..45260e2e --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco.py @@ -0,0 +1,15 @@ +_base_ = './mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py' + +train_cfg = dict(max_epochs=36) +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=24, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_poly-1x_coco_v1.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_poly-1x_coco_v1.py new file mode 100644 index 00000000..3baf0014 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_poly-1x_coco_v1.py @@ -0,0 +1,31 @@ +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + rpn_head=dict( + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + bbox_roi_extractor=dict( + roi_layer=dict( + type='RoIAlign', + output_size=7, + sampling_ratio=2, + aligned=False)), + bbox_head=dict( + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), + mask_roi_extractor=dict( + roi_layer=dict( + type='RoIAlign', + output_size=14, + sampling_ratio=2, + aligned=False)))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_1x-wandb_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_1x-wandb_coco.py new file mode 100644 index 00000000..28b125cc --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_1x-wandb_coco.py @@ -0,0 +1,16 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +vis_backends = [dict(type='LocalVisBackend'), dict(type='WandbVisBackend')] +visualizer = dict(vis_backends=vis_backends) + +# MMEngine support the following two ways, users can choose +# according to convenience +# default_hooks = dict(checkpoint=dict(interval=4)) +_base_.default_hooks.checkpoint.interval = 4 + +# train_cfg = dict(val_interval=2) +_base_.train_cfg.val_interval = 2 diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..0fc6b91a --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_2x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_2x_coco.py new file mode 100644 index 00000000..87cb8b4b --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_2x_coco.py @@ -0,0 +1,5 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..7371b364 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,22 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../common/lsj-100e_coco-instance.py' +] +image_size = (1024, 1024) +batch_augments = [ + dict(type='BatchFixedSizePad', size=image_size, pad_mask=True) +] + +model = dict(data_preprocessor=dict(batch_augments=batch_augments)) + +train_dataloader = dict(batch_size=8, num_workers=4) +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + optimizer=dict( + type='SGD', lr=0.02 * 4, momentum=0.9, weight_decay=0.00004)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_amp-1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_amp-1x_coco.py new file mode 100644 index 00000000..a139c48b --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_amp-1x_coco.py @@ -0,0 +1,4 @@ +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict(type='AmpOptimWrapper') diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_ms-poly-3x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..417adc3c --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_ms-poly-3x_coco.py @@ -0,0 +1,4 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_poly-1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_poly-1x_coco.py new file mode 100644 index 00000000..826180ce --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_r50_fpn_poly-1x_coco.py @@ -0,0 +1,18 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs'), +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..921ade81 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './mask-rcnn_r101_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_2x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_2x_coco.py new file mode 100644 index 00000000..db8157f8 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_2x_coco.py @@ -0,0 +1,14 @@ +_base_ = './mask-rcnn_r101_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_ms-poly-3x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..83e5451f --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_ms-poly-3x_coco.py @@ -0,0 +1,18 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] + +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_1x_coco.py new file mode 100644 index 00000000..3e9b1b6f --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_1x_coco.py @@ -0,0 +1,22 @@ +_base_ = './mask-rcnn_r101_fpn_1x_coco.py' + +model = dict( + # ResNeXt-101-32x8d model trained with Caffe2 at FB, + # so the mean and std need to be changed. + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[57.375, 57.120, 58.395], + bgr_to_rgb=False), + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=8, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + style='pytorch', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnext101_32x8d'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_ms-poly-1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_ms-poly-1x_coco.py new file mode 100644 index 00000000..6ee204d9 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_ms-poly-1x_coco.py @@ -0,0 +1,40 @@ +_base_ = './mask-rcnn_r101_fpn_1x_coco.py' + +model = dict( + # ResNeXt-101-32x8d model trained with Caffe2 at FB, + # so the mean and std need to be changed. + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[57.375, 57.120, 58.395], + bgr_to_rgb=False), + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=8, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + style='pytorch', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnext101_32x8d'))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs'), +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_ms-poly-3x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..999a30c3 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_ms-poly-3x_coco.py @@ -0,0 +1,25 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] + +model = dict( + # ResNeXt-101-32x8d model trained with Caffe2 at FB, + # so the mean and std need to be changed. + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[57.375, 57.120, 58.395], + bgr_to_rgb=False), + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=8, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + style='pytorch', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnext101_32x8d'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..2cbb658c --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './mask-rcnn_x101-32x4d_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_2x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_2x_coco.py new file mode 100644 index 00000000..f21a55b0 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_2x_coco.py @@ -0,0 +1,14 @@ +_base_ = './mask-rcnn_x101-32x4d_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_ms-poly_3x_coco.py b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_ms-poly_3x_coco.py new file mode 100644 index 00000000..09b49d47 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_ms-poly_3x_coco.py @@ -0,0 +1,18 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] + +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/mask_rcnn/metafile.yml b/mmdetection/configs/mask_rcnn/metafile.yml new file mode 100644 index 00000000..ddf85c87 --- /dev/null +++ b/mmdetection/configs/mask_rcnn/metafile.yml @@ -0,0 +1,443 @@ +Collections: + - Name: Mask R-CNN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Softmax + - RPN + - Convolution + - Dense Connections + - FPN + - ResNet + - RoIAlign + Paper: + URL: https://arxiv.org/abs/1703.06870v3 + Title: "Mask R-CNN" + README: configs/mask_rcnn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/mask_rcnn.py#L6 + Version: v2.0.0 + +Models: + - Name: mask-rcnn_r50-caffe_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.3 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 34.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_1x_coco/mask_rcnn_r50_caffe_fpn_1x_coco_bbox_mAP-0.38__segm_mAP-0.344_20200504_231812-0ebd1859.pth + + - Name: mask-rcnn_r50_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.4 + inference time (ms/im): + - value: 62.11 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 34.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth + + - Name: mask-rcnn_r50_fpn_fp16_1x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r50_fpn_amp-1x_coco.py + Metadata: + Training Memory (GB): 3.6 + Training Techniques: + - SGD with Momentum + - Weight Decay + - Mixed Precision Training + inference time (ms/im): + - value: 41.49 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP16 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 34.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fp16/mask_rcnn_r50_fpn_fp16_1x_coco/mask_rcnn_r50_fpn_fp16_1x_coco_20200205-59faf7e4.pth + + - Name: mask-rcnn_r50_fpn_2x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r50_fpn_2x_coco.py + Metadata: + Training Memory (GB): 4.4 + inference time (ms/im): + - value: 62.11 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 35.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_2x_coco/mask_rcnn_r50_fpn_2x_coco_bbox_mAP-0.392__segm_mAP-0.354_20200505_003907-3e542a40.pth + + - Name: mask-rcnn_r101-caffe_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r101-caffe_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_caffe_fpn_1x_coco/mask_rcnn_r101_caffe_fpn_1x_coco_20200601_095758-805e06c1.pth + + - Name: mask-rcnn_r101_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.4 + inference time (ms/im): + - value: 74.07 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_1x_coco/mask_rcnn_r101_fpn_1x_coco_20200204-1efe0ed5.pth + + - Name: mask-rcnn_r101_fpn_2x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r101_fpn_2x_coco.py + Metadata: + Training Memory (GB): 6.4 + inference time (ms/im): + - value: 74.07 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_2x_coco/mask_rcnn_r101_fpn_2x_coco_bbox_mAP-0.408__segm_mAP-0.366_20200505_071027-14b391c7.pth + + - Name: mask-rcnn_x101-32x4d_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.6 + inference time (ms/im): + - value: 88.5 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_1x_coco/mask_rcnn_x101_32x4d_fpn_1x_coco_20200205-478d0b67.pth + + - Name: mask-rcnn_x101-32x4d_fpn_2x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_2x_coco.py + Metadata: + Training Memory (GB): 7.6 + inference time (ms/im): + - value: 88.5 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_2x_coco/mask_rcnn_x101_32x4d_fpn_2x_coco_bbox_mAP-0.422__segm_mAP-0.378_20200506_004702-faef898c.pth + + - Name: mask-rcnn_x101-64x4d_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 10.7 + inference time (ms/im): + - value: 125 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_1x_coco/mask_rcnn_x101_64x4d_fpn_1x_coco_20200201-9352eb0d.pth + + - Name: mask-rcnn_x101-64x4d_fpn_2x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_2x_coco.py + Metadata: + Training Memory (GB): 10.7 + inference time (ms/im): + - value: 125 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_2x_coco/mask_rcnn_x101_64x4d_fpn_2x_coco_20200509_224208-39d6f70c.pth + + - Name: mask-rcnn_x101-32x8d_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 10.6 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x8d_fpn_1x_coco/mask_rcnn_x101_32x8d_fpn_1x_coco_20220630_173841-0aaf329e.pth + + - Name: mask-rcnn_r50-caffe_fpn_ms-poly-2x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-2x_coco.py + Metadata: + Training Memory (GB): 4.3 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_2x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_2x_coco_bbox_mAP-0.403__segm_mAP-0.365_20200504_231822-a75c98ce.pth + + - Name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 4.3 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth + + - Name: mask-rcnn_r50_fpn_mstrain-poly_3x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r50_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 4.1 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_fpn_mstrain-poly_3x_coco_20210524_201154-21b550bb.pth + + - Name: mask-rcnn_r101_fpn_ms-poly-3x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r101_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 6.1 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_mstrain-poly_3x_coco/mask_rcnn_r101_fpn_mstrain-poly_3x_coco_20210524_200244-5675c317.pth + + - Name: mask-rcnn_r101-caffe_fpn_ms-poly-3x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_r101-caffe_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 5.9 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r101_caffe_fpn_mstrain-poly_3x_coco_20210526_132339-3c33ce02.pth + + - Name: mask-rcnn_x101-32x4d_fpn_ms-poly-3x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_x101-32x4d_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 7.3 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_mstrain-poly_3x_coco/mask_rcnn_x101_32x4d_fpn_mstrain-poly_3x_coco_20210524_201410-abcd7859.pth + + - Name: mask-rcnn_x101-32x8d_fpn_ms-poly-1x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_ms-poly-1x_coco.py + Metadata: + Training Memory (GB): 10.4 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x8d_fpn_mstrain-poly_1x_coco/mask_rcnn_x101_32x8d_fpn_mstrain-poly_1x_coco_20220630_170346-b4637974.pth + + - Name: mask-rcnn_x101-32x8d_fpn_ms-poly-3x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_x101-32x8d_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 10.3 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x8d_fpn_mstrain-poly_3x_coco/mask_rcnn_x101_32x8d_fpn_mstrain-poly_3x_coco_20210607_161042-8bd2c639.pth + + - Name: mask-rcnn_x101-64x4d_fpn_ms-poly_3x_coco + In Collection: Mask R-CNN + Config: configs/mask_rcnn/mask-rcnn_x101-64x4d_fpn_ms-poly_3x_coco.py + Metadata: + Epochs: 36 + Training Memory (GB): 10.4 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_mstrain-poly_3x_coco/mask_rcnn_x101_64x4d_fpn_mstrain-poly_3x_coco_20210526_120447-c376f129.pth diff --git a/mmdetection/configs/maskformer/README.md b/mmdetection/configs/maskformer/README.md new file mode 100644 index 00000000..ca5ce320 --- /dev/null +++ b/mmdetection/configs/maskformer/README.md @@ -0,0 +1,58 @@ +# MaskFormer + +> [Per-Pixel Classification is Not All You Need for Semantic Segmentation](https://arxiv.org/abs/2107.06278) + + + +## Abstract + +Modern approaches typically formulate semantic segmentation as a per-pixel classification task, while instance-level segmentation is handled with an alternative mask classification. Our key insight: mask classification is sufficiently general to solve both semantic- and instance-level segmentation tasks in a unified manner using the exact same model, loss, and training procedure. Following this observation, we propose MaskFormer, a simple mask classification model which predicts a set of binary masks, each associated with a single global class label prediction. Overall, the proposed mask classification-based method simplifies the landscape of effective approaches to semantic and panoptic segmentation tasks and shows excellent empirical results. In particular, we observe that MaskFormer outperforms per-pixel classification baselines when the number of classes is large. Our mask classification-based method outperforms both current state-of-the-art semantic (55.6 mIoU on ADE20K) and panoptic segmentation (52.7 PQ on COCO) models. + +
    + +
    + +## Introduction + +MaskFormer requires COCO and [COCO-panoptic](http://images.cocodataset.org/annotations/panoptic_annotations_trainval2017.zip) dataset for training and evaluation. You need to download and extract it in the COCO dataset path. +The directory should be like this. + +```none +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── panoptic_train2017.json +│ │ │ ├── panoptic_train2017 +│ │ │ ├── panoptic_val2017.json +│ │ │ ├── panoptic_val2017 +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +``` + +## Results and Models + +| Backbone | style | Lr schd | Mem (GB) | Inf time (fps) | PQ | SQ | RQ | PQ_th | SQ_th | RQ_th | PQ_st | SQ_st | RQ_st | Config | Download | +| :------: | :-----: | :-----: | :------: | :------------: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | :--------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | 75e | 16.2 | - | 46.757 | 80.297 | 57.176 | 50.829 | 81.125 | 61.798 | 40.610 | 79.048 | 50.199 | [config](./maskformer_r50_ms-16xb1-75e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/maskformer/maskformer_r50_ms-16xb1-75e_coco/maskformer_r50_ms-16xb1-75e_coco_20230116_095226-baacd858.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/maskformer/maskformer_r50_ms-16xb1-75e_coco/maskformer_r50_ms-16xb1-75e_coco_20230116_095226.log.json) | +| Swin-L | pytorch | 300e | 27.2 | - | 53.249 | 81.704 | 64.231 | 58.798 | 82.923 | 70.282 | 44.874 | 79.863 | 55.097 | [config](./maskformer_swin-l-p4-w12_64xb1-ms-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/maskformer/maskformer_swin-l-p4-w12_64xb1-ms-300e_coco/maskformer_swin-l-p4-w12_64xb1-ms-300e_coco_20220326_221612-c63ab967.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/maskformer/maskformer_swin-l-p4-w12_mstrain_64x1_300e_coco/maskformer_swin-l-p4-w12_mstrain_64x1_300e_coco_20220326_221612.log.json) | + +### Note + +1. The `R-50` version was mentioned in Table XI, in paper [Masked-attention Mask Transformer for Universal Image Segmentation](https://arxiv.org/abs/2112.01527). +2. The models were trained with mmdet 2.x and have been converted for mmdet 3.x. + +## Citation + +```latex +@inproceedings{cheng2021maskformer, + title={Per-Pixel Classification is Not All You Need for Semantic Segmentation}, + author={Bowen Cheng and Alexander G. Schwing and Alexander Kirillov}, + journal={NeurIPS}, + year={2021} +} +``` diff --git a/mmdetection/configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py b/mmdetection/configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py new file mode 100644 index 00000000..784ee776 --- /dev/null +++ b/mmdetection/configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py @@ -0,0 +1,216 @@ +_base_ = [ + '../_base_/datasets/coco_panoptic.py', '../_base_/default_runtime.py' +] + +data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1, + pad_mask=True, + mask_pad_value=0, + pad_seg=True, + seg_pad_value=255) + +num_things_classes = 80 +num_stuff_classes = 53 +num_classes = num_things_classes + num_stuff_classes +model = dict( + type='MaskFormer', + data_preprocessor=data_preprocessor, + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=-1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + panoptic_head=dict( + type='MaskFormerHead', + in_channels=[256, 512, 1024, 2048], # pass to pixel_decoder inside + feat_channels=256, + out_channels=256, + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes, + num_queries=100, + pixel_decoder=dict( + type='TransformerEncoderPixelDecoder', + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU'), + encoder=dict( # DetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.1, + act_cfg=dict(type='ReLU', inplace=True)))), + positional_encoding=dict(num_feats=128, normalize=True)), + enforce_decoder_input_project=False, + positional_encoding=dict(num_feats=128, normalize=True), + transformer_decoder=dict( # DetrTransformerDecoder + num_layers=6, + layer_cfg=dict( # DetrTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.1, + act_cfg=dict(type='ReLU', inplace=True))), + return_intermediate=True), + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + reduction='mean', + class_weight=[1.0] * num_classes + [0.1]), + loss_mask=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + reduction='mean', + loss_weight=20.0), + loss_dice=dict( + type='DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=1.0)), + panoptic_fusion_head=dict( + type='MaskFormerFusionHead', + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes, + loss_panoptic=None, + init_cfg=None), + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='ClassificationCost', weight=1.0), + dict(type='FocalLossCost', weight=20.0, binary_input=True), + dict(type='DiceCost', weight=1.0, pred_act=True, eps=1.0) + ]), + sampler=dict(type='MaskPseudoSampler')), + test_cfg=dict( + panoptic_on=True, + # For now, the dataset does not support + # evaluating semantic segmentation metric. + semantic_on=False, + instance_on=False, + # max_per_image is for instance segmentation. + max_per_image=100, + object_mask_thr=0.8, + iou_thr=0.8, + # In MaskFormer's panoptic postprocessing, + # it will not filter masks whose score is smaller than 0.5 . + filter_low_score=False), + init_cfg=None) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='LoadPanopticAnnotations', + with_bbox=True, + with_mask=True, + with_seg=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[[ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True) + ]]), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + batch_size=1, num_workers=1, dataset=dict(pipeline=train_pipeline)) + +val_dataloader = dict(batch_size=1, num_workers=1) + +test_dataloader = val_dataloader + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0001, + eps=1e-8, + betas=(0.9, 0.999)), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'query_embed': dict(lr_mult=1.0, decay_mult=0.0) + }, + norm_decay_mult=0.0), + clip_grad=dict(max_norm=0.01, norm_type=2)) + +max_epochs = 75 + +# learning rate +param_scheduler = dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[50], + gamma=0.1) + +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (16 GPUs) x (1 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/maskformer/maskformer_swin-l-p4-w12_64xb1-ms-300e_coco.py b/mmdetection/configs/maskformer/maskformer_swin-l-p4-w12_64xb1-ms-300e_coco.py new file mode 100644 index 00000000..9e4897f2 --- /dev/null +++ b/mmdetection/configs/maskformer/maskformer_swin-l-p4-w12_64xb1-ms-300e_coco.py @@ -0,0 +1,73 @@ +_base_ = './maskformer_r50_ms-16xb1-75e_coco.py' + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa +depths = [2, 2, 18, 2] +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + patch_size=4, + window_size=12, + mlp_ratio=4, + depths=depths, + num_heads=[6, 12, 24, 48], + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + panoptic_head=dict( + in_channels=[192, 384, 768, 1536], # pass to pixel_decoder inside + pixel_decoder=dict( + _delete_=True, + type='PixelDecoder', + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU')), + enforce_decoder_input_project=True)) + +# optimizer + +# weight_decay = 0.01 +# norm_weight_decay = 0.0 +# embed_weight_decay = 0.0 +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +norm_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'norm': norm_multi, + 'absolute_pos_embed': embed_multi, + 'relative_position_bias_table': embed_multi, + 'query_embed': embed_multi +} + +optim_wrapper = dict( + optimizer=dict(lr=6e-5, weight_decay=0.01), + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) + +max_epochs = 300 + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[250], + gamma=0.1) +] + +train_cfg = dict(max_epochs=max_epochs) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (64 GPUs) x (1 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/maskformer/metafile.yml b/mmdetection/configs/maskformer/metafile.yml new file mode 100644 index 00000000..fa58269d --- /dev/null +++ b/mmdetection/configs/maskformer/metafile.yml @@ -0,0 +1,43 @@ +Collections: + - Name: MaskFormer + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Weight Decay + Training Resources: 16x V100 GPUs + Architecture: + - MaskFormer + Paper: + URL: https://arxiv.org/pdf/2107.06278 + Title: 'Per-Pixel Classification is Not All You Need for Semantic Segmentation' + README: configs/maskformer/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.22.0/mmdet/models/detectors/maskformer.py#L7 + Version: v2.22.0 + +Models: + - Name: maskformer_r50_ms-16xb1-75e_coco + In Collection: MaskFormer + Config: configs/maskformer/maskformer_r50_ms-16xb1-75e_coco.py + Metadata: + Training Memory (GB): 16.2 + Epochs: 75 + Results: + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 46.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/maskformer/maskformer_r50_ms-16xb1-75e_coco/maskformer_r50_ms-16xb1-75e_coco_20230116_095226-baacd858.pth + - Name: maskformer_swin-l-p4-w12_64xb1-ms-300e_coco + In Collection: MaskFormer + Config: configs/maskformer/maskformer_swin-l-p4-w12_64xb1-ms-300e_coco.py + Metadata: + Training Memory (GB): 27.2 + Epochs: 300 + Results: + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 53.2 + Weights: https://download.openmmlab.com/mmdetection/v3.0/maskformer/maskformer_swin-l-p4-w12_64xb1-ms-300e_coco/maskformer_swin-l-p4-w12_64xb1-ms-300e_coco_20220326_221612-c63ab967.pth diff --git a/mmdetection/configs/masktrack_rcnn/README.md b/mmdetection/configs/masktrack_rcnn/README.md new file mode 100644 index 00000000..5cef692a --- /dev/null +++ b/mmdetection/configs/masktrack_rcnn/README.md @@ -0,0 +1,93 @@ +# Video Instance Segmentation + +## Abstract + + + +In this paper we present a new computer vision task, named video instance segmentation. The goal of this new task is simultaneous detection, segmentation and tracking of instances in videos. In words, it is the first time that the image instance segmentation problem is extended to the video domain. To facilitate research on this new task, we propose a large-scale benchmark called YouTube-VIS, which consists of 2883 high-resolution YouTube videos, a 40-category label set and 131k high-quality instance masks. In addition, we propose a novel algorithm called MaskTrack R-CNN for this task. Our new method introduces a new tracking branch to Mask R-CNN to jointly perform the detection, segmentation and tracking tasks simultaneously. Finally, we evaluate the proposed method and several strong baselines on our new dataset. Experimental results clearly demonstrate the advantages of the proposed algorithm and reveal insight for future improvement. We believe the video instance segmentation task will motivate the community along the line of research for video understanding. + + + +
    + +
    + +## Citation + + + +```latex +@inproceedings{yang2019video, + title={Video instance segmentation}, + author={Yang, Linjie and Fan, Yuchen and Xu, Ning}, + booktitle={Proceedings of the IEEE/CVF International Conference on Computer Vision}, + pages={5188--5197}, + year={2019} +} +``` + +## Results and models of MaskTrack R-CNN on YouTube-VIS 2019 validation dataset + +As mentioned in [Issues #6](https://github.com/youtubevos/MaskTrackRCNN/issues/6#issuecomment-502503505) in MaskTrack R-CNN, the result is kind of unstable for different trials, which ranges from 28 AP to 31 AP when using R-50-FPN as backbone. +The checkpoint provided below is the best one from two experiments. + +| Method | Base detector | Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | AP | Config | Download | +| :-------------: | :-----------: | :-------: | :-----: | :-----: | :------: | :------------: | :--: | :--------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| MaskTrack R-CNN | Mask R-CNN | R-50-FPN | pytorch | 12e | 1.61 | - | 30.2 | [config](masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py) | [model](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r50_fpn_12e_youtubevis2019/masktrack_rcnn_r50_fpn_12e_youtubevis2019_20211022_194830-6ca6b91e.pth) \| [log](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r50_fpn_12e_youtubevis2019/masktrack_rcnn_r50_fpn_12e_youtubevis2019_20211022_194830.log.json) | +| MaskTrack R-CNN | Mask R-CNN | R-101-FPN | pytorch | 12e | 2.27 | - | 32.2 | [config](masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2019.py) | [model](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r101_fpn_12e_youtubevis2019/masktrack_rcnn_r101_fpn_12e_youtubevis2019_20211023_150038-454dc48b.pth) \| [log](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r101_fpn_12e_youtubevis2019/masktrack_rcnn_r101_fpn_12e_youtubevis2019_20211023_150038.log.json) | +| MaskTrack R-CNN | Mask R-CNN | X-101-FPN | pytorch | 12e | 3.69 | - | 34.7 | [config](masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2019.py) | [model](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_x101_fpn_12e_youtubevis2019/masktrack_rcnn_x101_fpn_12e_youtubevis2019_20211023_153205-fff7a102.pth) \| [log](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_x101_fpn_12e_youtubevis2019/masktrack_rcnn_x101_fpn_12e_youtubevis2019_20211023_153205.log.json) | + +## Results and models of MaskTrack R-CNN on YouTube-VIS 2021 validation dataset + +The checkpoint provided below is the best one from two experiments. + +| Method | Base detector | Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | AP | Config | Download | +| :-------------: | :-----------: | :-------: | :-----: | :-----: | :------: | :------------: | :--: | :--------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| MaskTrack R-CNN | Mask R-CNN | R-50-FPN | pytorch | 12e | 1.61 | - | 28.7 | [config](masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py) | [model](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r50_fpn_12e_youtubevis2021/masktrack_rcnn_r50_fpn_12e_youtubevis2021_20211026_044948-10da90d9.pth) \| [log](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r50_fpn_12e_youtubevis2021/masktrack_rcnn_r50_fpn_12e_youtubevis2021_20211026_044948.log.json) | +| MaskTrack R-CNN | Mask R-CNN | R-101-FPN | pytorch | 12e | 2.27 | - | 31.3 | [config](masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2021.py) | [model](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r101_fpn_12e_youtubevis2021/masktrack_rcnn_r101_fpn_12e_youtubevis2021_20211026_045509-3c49e4f3.pth) \| [log](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r101_fpn_12e_youtubevis2021/masktrack_rcnn_r101_fpn_12e_youtubevis2021_20211026_045509.log.json) | +| MaskTrack R-CNN | Mask R-CNN | X-101-FPN | pytorch | 12e | 3.69 | - | 33.5 | [config](masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2021.py) | [model](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_x101_fpn_12e_youtubevis2021/masktrack_rcnn_x101_fpn_12e_youtubevis2021_20211026_095943-90831df4.pth) \| [log](https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_x101_fpn_12e_youtubevis2021/masktrack_rcnn_x101_fpn_12e_youtubevis2021_20211026_095943.log.json) | + +## Get started + +### 1. Development Environment Setup + +Tracking Development Environment Setup can refer to this [document](../../docs/en/get_started.md). + +### 2. Dataset Prepare + +Tracking Dataset Prepare can refer to this [document](../../docs/en/user_guides/tracking_dataset_prepare.md). + +### 3. Training + +Due to the influence of parameters such as learning rate in default configuration file, we recommend using 8 GPUs for training in order to reproduce accuracy. You can use the following command to start the training. + +```shell +# Training MaskTrack R-CNN on YouTube-VIS-2021 dataset with following command. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_train.sh configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py 8 +``` + +If you want to know about more detailed usage of `train.py/dist_train.sh/slurm_train.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 4. Testing and evaluation + +If you want to get the results of the [YouTube-VOS](https://youtube-vos.org/dataset/vis/) val/test set, please use the following command to generate result files that can be used for submission. It will be stored in `./youtube_vis_results.submission_file.zip`, you can modify the saved path in `test_evaluator` of the config. + +```shell +# The number after config file represents the number of GPUs used. +bash tools/dist_test_tracking.sh configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py 8 --checkpoint ${CHECKPOINT_PATH} +``` + +If you want to know about more detailed usage of `train.py/dist_train.sh/slurm_train.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 5.Inference + +Use a single GPU to predict a video and save it as a video. + +```shell +python demo/mot_demo.py demo/demo_mot.mp4 configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py --checkpoint {CHECKPOINT_PATH} --out vis.mp4 +``` + +If you want to know about more detailed usage of `mot_demo.py`, please refer to this [document](../../docs/en/user_guides/tracking_inference.md). diff --git a/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2019.py b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2019.py new file mode 100644 index 00000000..4be492d5 --- /dev/null +++ b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2019.py @@ -0,0 +1,12 @@ +_base_ = ['./masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py'] +model = dict( + detector=dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', checkpoint='torchvision://resnet101')), + init_cfg=dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_1x_coco/mask_rcnn_r101_fpn_1x_coco_20200204-1efe0ed5.pth' # noqa: E501 + ))) diff --git a/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2021.py b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2021.py new file mode 100644 index 00000000..81bae4af --- /dev/null +++ b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2021.py @@ -0,0 +1,28 @@ +_base_ = ['./masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py'] +model = dict( + detector=dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', checkpoint='torchvision://resnet101')), + init_cfg=dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_1x_coco/mask_rcnn_r101_fpn_1x_coco_20200204-1efe0ed5.pth' # noqa: E501 + ))) + +data_root = 'data/youtube_vis_2021/' +dataset_version = data_root[-5:-1] + +# dataloader +train_dataloader = dict( + dataset=dict( + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2021_train.json')) +val_dataloader = dict( + dataset=dict( + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2021_valid.json')) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py new file mode 100644 index 00000000..db1be7b0 --- /dev/null +++ b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py @@ -0,0 +1,130 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/youtube_vis.py', '../_base_/default_runtime.py' +] + +detector = _base_.model +detector.pop('data_preprocessor') +detector.roi_head.bbox_head.update(dict(num_classes=40)) +detector.roi_head.mask_head.update(dict(num_classes=40)) +detector.train_cfg.rpn.sampler.update(dict(num=64)) +detector.train_cfg.rpn_proposal.update(dict(nms_pre=200, max_per_img=200)) +detector.train_cfg.rcnn.sampler.update(dict(num=128)) +detector.test_cfg.rpn.update(dict(nms_pre=200, max_per_img=200)) +detector.test_cfg.rcnn.update(dict(score_thr=0.01)) +detector['init_cfg'] = dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth' # noqa: E501 +) +del _base_.model + +model = dict( + type='MaskTrackRCNN', + data_preprocessor=dict( + type='TrackDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + detector=detector, + track_head=dict( + type='RoITrackHead', + roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + embed_head=dict( + type='RoIEmbedHead', + num_fcs=2, + roi_feat_size=7, + in_channels=256, + fc_out_channels=1024), + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=128, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False)), + tracker=dict( + type='MaskTrackRCNNTracker', + match_weights=dict(det_score=1.0, iou=2.0, det_label=10.0), + num_frames_retain=20)) + +dataset_type = 'YouTubeVISDataset' +data_root = 'data/youtube_vis_2019/' +dataset_version = data_root[-5:-1] # 2019 or 2021 + +# train_dataloader +train_dataloader = dict( + _delete_=True, + batch_size=1, + num_workers=2, + persistent_workers=True, + sampler=dict(type='TrackImgSampler'), # image-based sampling + batch_sampler=dict(type='TrackAspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2019_train.json', + data_prefix=dict(img_path='train/JPEGImages'), + pipeline=_base_.train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.00125, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 3.0, + by_epoch=False, + begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# visualizer +default_hooks = dict( + visualization=dict(type='TrackVisualizationHook', draw=False)) + +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='TrackLocalVisualizer', vis_backends=vis_backends, name='visualizer') + +# runtime settings +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_begin=13) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# evaluator +val_evaluator = dict( + type='YouTubeVISMetric', + metric='youtube_vis_ap', + outfile_prefix='./youtube_vis_results', + format_only=True) +test_evaluator = val_evaluator + +del detector diff --git a/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py new file mode 100644 index 00000000..47263d50 --- /dev/null +++ b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py @@ -0,0 +1,17 @@ +_base_ = ['./masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py'] + +data_root = 'data/youtube_vis_2021/' +dataset_version = data_root[-5:-1] + +# dataloader +train_dataloader = dict( + dataset=dict( + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2021_train.json')) +val_dataloader = dict( + dataset=dict( + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2021_valid.json')) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2019.py b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2019.py new file mode 100644 index 00000000..e7e3f11e --- /dev/null +++ b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2019.py @@ -0,0 +1,16 @@ +_base_ = ['./masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py'] +model = dict( + detector=dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://resnext101_64x4d')), + init_cfg=dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_1x_coco/mask_rcnn_x101_64x4d_fpn_1x_coco_20200201-9352eb0d.pth' # noqa: E501 + ))) diff --git a/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2021.py b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2021.py new file mode 100644 index 00000000..ea4c8b92 --- /dev/null +++ b/mmdetection/configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2021.py @@ -0,0 +1,32 @@ +_base_ = ['./masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py'] +model = dict( + detector=dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://resnext101_64x4d')), + init_cfg=dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_64x4d_fpn_1x_coco/mask_rcnn_x101_64x4d_fpn_1x_coco_20200201-9352eb0d.pth' # noqa: E501 + ))) + +data_root = 'data/youtube_vis_2021/' +dataset_version = data_root[-5:-1] + +# dataloader +train_dataloader = dict( + dataset=dict( + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2021_train.json')) +val_dataloader = dict( + dataset=dict( + data_root=data_root, + dataset_version=dataset_version, + ann_file='annotations/youtube_vis_2021_valid.json')) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/masktrack_rcnn/metafile.yml b/mmdetection/configs/masktrack_rcnn/metafile.yml new file mode 100644 index 00000000..7a1d71d5 --- /dev/null +++ b/mmdetection/configs/masktrack_rcnn/metafile.yml @@ -0,0 +1,91 @@ +Collections: + - Name: MaskTrack R-CNN + Metadata: + Training Techniques: + - SGD with Momentum + Training Resources: 8x TiTanXP GPUs + Architecture: + - ResNet + Paper: + URL: https://arxiv.org/pdf/1905.04804.pdf + Title: Video Instance Segmentation + README: configs/masktrack_rcnn/README.md + +Models: + - Name: masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019 + In Collection: MaskTrack R-CNN + Config: configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2019.py + Metadata: + Training Data: YouTube-VIS 2019 + Training Memory (GB): 1.16 + Results: + - Task: Video Instance Segmentation + Dataset: YouTube-VIS 2019 + Metrics: + AP: 30.2 + Weights: https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r50_fpn_12e_youtubevis2019/masktrack_rcnn_r50_fpn_12e_youtubevis2019_20211022_194830-6ca6b91e.pth + + - Name: masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2019 + In Collection: MaskTrack R-CNN + Config: configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2019.py + Metadata: + Training Data: YouTube-VIS 2019 + Training Memory (GB): 2.27 + Results: + - Task: Video Instance Segmentation + Dataset: YouTube-VIS 2019 + Metrics: + AP: 32.2 + Weights: https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r101_fpn_12e_youtubevis2019/masktrack_rcnn_r101_fpn_12e_youtubevis2019_20211023_150038-454dc48b.pth + + - Name: masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2019 + In Collection: MaskTrack R-CNN + Config: configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2019.py + Metadata: + Training Data: YouTube-VIS 2019 + Training Memory (GB): 3.69 + Results: + - Task: Video Instance Segmentation + Dataset: YouTube-VIS 2019 + Metrics: + AP: 34.7 + Weights: https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_x101_fpn_12e_youtubevis2019/masktrack_rcnn_x101_fpn_12e_youtubevis2019_20211023_153205-fff7a102.pth + + - Name: masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021 + In Collection: MaskTrack R-CNN + Config: configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r50_fpn_8xb1-12e_youtubevis2021.py + Metadata: + Training Data: YouTube-VIS 2021 + Training Memory (GB): 1.16 + Results: + - Task: Video Instance Segmentation + Dataset: YouTube-VIS 2021 + Metrics: + AP: 28.7 + Weights: https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r50_fpn_12e_youtubevis2021/masktrack_rcnn_r50_fpn_12e_youtubevis2021_20211026_044948-10da90d9.pth + + - Name: masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2021 + In Collection: MaskTrack R-CNN + Config: configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_r101_fpn_8xb1-12e_youtubevis2021.py + Metadata: + Training Data: YouTube-VIS 2021 + Training Memory (GB): 2.27 + Results: + - Task: Video Instance Segmentation + Dataset: YouTube-VIS 2021 + Metrics: + AP: 31.3 + Weights: https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_r101_fpn_12e_youtubevis2021/masktrack_rcnn_r101_fpn_12e_youtubevis2021_20211026_045509-3c49e4f3.pth + + - Name: masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2021 + In Collection: MaskTrack R-CNN + Config: configs/masktrack_rcnn/masktrack-rcnn_mask-rcnn_x101_fpn_8xb1-12e_youtubevis2021.py + Metadata: + Training Data: YouTube-VIS 2021 + Training Memory (GB): 3.69 + Results: + - Task: Video Instance Segmentation + Dataset: YouTube-VIS 2021 + Metrics: + AP: 33.5 + Weights: https://download.openmmlab.com/mmtracking/vis/masktrack_rcnn/masktrack_rcnn_x101_fpn_12e_youtubevis2021/masktrack_rcnn_x101_fpn_12e_youtubevis2021_20211026_095943-90831df4.pth diff --git a/mmdetection/configs/misc/d2_faster-rcnn_r50-caffe_fpn_ms-90k_coco.py b/mmdetection/configs/misc/d2_faster-rcnn_r50-caffe_fpn_ms-90k_coco.py new file mode 100644 index 00000000..d93e1562 --- /dev/null +++ b/mmdetection/configs/misc/d2_faster-rcnn_r50-caffe_fpn_ms-90k_coco.py @@ -0,0 +1,75 @@ +_base_ = '../common/ms-90k_coco.py' + +# model settings +model = dict( + type='Detectron2Wrapper', + bgr_to_rgb=False, + detector=dict( + # The settings in `d2_detector` will merged into default settings + # in detectron2. More details please refer to + # https://github.com/facebookresearch/detectron2/blob/main/detectron2/config/defaults.py # noqa + meta_architecture='GeneralizedRCNN', + # If you want to finetune the detector, you can use the + # checkpoint released by detectron2, for example: + # weights='detectron2://COCO-Detection/faster_rcnn_R_50_FPN_1x/137257794/model_final_b275ba.pkl' # noqa + weights='detectron2://ImageNetPretrained/MSRA/R-50.pkl', + mask_on=False, + pixel_mean=[103.530, 116.280, 123.675], + pixel_std=[1.0, 1.0, 1.0], + backbone=dict(name='build_resnet_fpn_backbone', freeze_at=2), + resnets=dict( + depth=50, + out_features=['res2', 'res3', 'res4', 'res5'], + num_groups=1, + norm='FrozenBN'), + fpn=dict( + in_features=['res2', 'res3', 'res4', 'res5'], out_channels=256), + anchor_generator=dict( + name='DefaultAnchorGenerator', + sizes=[[32], [64], [128], [256], [512]], + aspect_ratios=[[0.5, 1.0, 2.0]], + angles=[[-90, 0, 90]]), + proposal_generator=dict(name='RPN'), + rpn=dict( + head_name='StandardRPNHead', + in_features=['p2', 'p3', 'p4', 'p5', 'p6'], + iou_thresholds=[0.3, 0.7], + iou_labels=[0, -1, 1], + batch_size_per_image=256, + positive_fraction=0.5, + bbox_reg_loss_type='smooth_l1', + bbox_reg_loss_weight=1.0, + bbox_reg_weights=(1.0, 1.0, 1.0, 1.0), + smooth_l1_beta=0.0, + loss_weight=1.0, + boundary_thresh=-1, + pre_nms_topk_train=2000, + post_nms_topk_train=1000, + pre_nms_topk_test=1000, + post_nms_topk_test=1000, + nms_thresh=0.7, + conv_dims=[-1]), + roi_heads=dict( + name='StandardROIHeads', + num_classes=80, + in_features=['p2', 'p3', 'p4', 'p5'], + iou_thresholds=[0.5], + iou_labels=[0, 1], + batch_size_per_image=512, + positive_fraction=0.25, + score_thresh_test=0.05, + nms_thresh_test=0.5, + proposal_append_gt=True), + roi_box_head=dict( + name='FastRCNNConvFCHead', + num_fc=2, + fc_dim=1024, + conv_dim=256, + pooler_type='ROIAlignV2', + pooler_resolution=7, + pooler_sampling_ratio=0, + bbox_reg_loss_type='smooth_l1', + bbox_reg_loss_weight=1.0, + bbox_reg_weights=(10.0, 10.0, 5.0, 5.0), + smooth_l1_beta=0.0, + cls_agnostic_bbox_reg=False))) diff --git a/mmdetection/configs/misc/d2_mask-rcnn_r50-caffe_fpn_ms-90k_coco.py b/mmdetection/configs/misc/d2_mask-rcnn_r50-caffe_fpn_ms-90k_coco.py new file mode 100644 index 00000000..c0919c45 --- /dev/null +++ b/mmdetection/configs/misc/d2_mask-rcnn_r50-caffe_fpn_ms-90k_coco.py @@ -0,0 +1,83 @@ +_base_ = '../common/ms-poly-90k_coco-instance.py' + +# model settings +model = dict( + type='Detectron2Wrapper', + bgr_to_rgb=False, + detector=dict( + # The settings in `d2_detector` will merged into default settings + # in detectron2. More details please refer to + # https://github.com/facebookresearch/detectron2/blob/main/detectron2/config/defaults.py # noqa + meta_architecture='GeneralizedRCNN', + # If you want to finetune the detector, you can use the + # checkpoint released by detectron2, for example: + # weights='detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x/137260431/model_final_a54504.pkl' # noqa + weights='detectron2://ImageNetPretrained/MSRA/R-50.pkl', + mask_on=True, + pixel_mean=[103.530, 116.280, 123.675], + pixel_std=[1.0, 1.0, 1.0], + backbone=dict(name='build_resnet_fpn_backbone', freeze_at=2), + resnets=dict( + depth=50, + out_features=['res2', 'res3', 'res4', 'res5'], + num_groups=1, + norm='FrozenBN'), + fpn=dict( + in_features=['res2', 'res3', 'res4', 'res5'], out_channels=256), + anchor_generator=dict( + name='DefaultAnchorGenerator', + sizes=[[32], [64], [128], [256], [512]], + aspect_ratios=[[0.5, 1.0, 2.0]], + angles=[[-90, 0, 90]]), + proposal_generator=dict(name='RPN'), + rpn=dict( + head_name='StandardRPNHead', + in_features=['p2', 'p3', 'p4', 'p5', 'p6'], + iou_thresholds=[0.3, 0.7], + iou_labels=[0, -1, 1], + batch_size_per_image=256, + positive_fraction=0.5, + bbox_reg_loss_type='smooth_l1', + bbox_reg_loss_weight=1.0, + bbox_reg_weights=(1.0, 1.0, 1.0, 1.0), + smooth_l1_beta=0.0, + loss_weight=1.0, + boundary_thresh=-1, + pre_nms_topk_train=2000, + post_nms_topk_train=1000, + pre_nms_topk_test=1000, + post_nms_topk_test=1000, + nms_thresh=0.7, + conv_dims=[-1]), + roi_heads=dict( + name='StandardROIHeads', + num_classes=80, + in_features=['p2', 'p3', 'p4', 'p5'], + iou_thresholds=[0.5], + iou_labels=[0, 1], + batch_size_per_image=512, + positive_fraction=0.25, + score_thresh_test=0.05, + nms_thresh_test=0.5, + proposal_append_gt=True), + roi_box_head=dict( + name='FastRCNNConvFCHead', + num_fc=2, + fc_dim=1024, + conv_dim=256, + pooler_type='ROIAlignV2', + pooler_resolution=7, + pooler_sampling_ratio=0, + bbox_reg_loss_type='smooth_l1', + bbox_reg_loss_weight=1.0, + bbox_reg_weights=(10.0, 10.0, 5.0, 5.0), + smooth_l1_beta=0.0, + cls_agnostic_bbox_reg=False), + roi_mask_head=dict( + name='MaskRCNNConvUpsampleHead', + conv_dim=256, + num_conv=4, + pooler_type='ROIAlignV2', + pooler_resolution=14, + pooler_sampling_ratio=0, + cls_agnostic_mask=False))) diff --git a/mmdetection/configs/misc/d2_retinanet_r50-caffe_fpn_ms-90k_coco.py b/mmdetection/configs/misc/d2_retinanet_r50-caffe_fpn_ms-90k_coco.py new file mode 100644 index 00000000..d3f75876 --- /dev/null +++ b/mmdetection/configs/misc/d2_retinanet_r50-caffe_fpn_ms-90k_coco.py @@ -0,0 +1,48 @@ +_base_ = '../common/ms-90k_coco.py' + +# model settings +model = dict( + type='Detectron2Wrapper', + bgr_to_rgb=False, + detector=dict( + # The settings in `d2_detector` will merged into default settings + # in detectron2. More details please refer to + # https://github.com/facebookresearch/detectron2/blob/main/detectron2/config/defaults.py # noqa + meta_architecture='RetinaNet', + # If you want to finetune the detector, you can use the + # checkpoint released by detectron2, for example: + # weights='detectron2://COCO-Detection/retinanet_R_50_FPN_1x/190397773/model_final_bfca0b.pkl' # noqa + weights='detectron2://ImageNetPretrained/MSRA/R-50.pkl', + mask_on=False, + pixel_mean=[103.530, 116.280, 123.675], + pixel_std=[1.0, 1.0, 1.0], + backbone=dict(name='build_retinanet_resnet_fpn_backbone', freeze_at=2), + resnets=dict( + depth=50, + out_features=['res3', 'res4', 'res5'], + num_groups=1, + norm='FrozenBN'), + fpn=dict(in_features=['res3', 'res4', 'res5'], out_channels=256), + anchor_generator=dict( + name='DefaultAnchorGenerator', + sizes=[[x, x * 2**(1.0 / 3), x * 2**(2.0 / 3)] + for x in [32, 64, 128, 256, 512]], + aspect_ratios=[[0.5, 1.0, 2.0]], + angles=[[-90, 0, 90]]), + retinanet=dict( + num_classes=80, + in_features=['p3', 'p4', 'p5', 'p6', 'p7'], + num_convs=4, + iou_thresholds=[0.4, 0.5], + iou_labels=[0, -1, 1], + bbox_reg_weights=(1.0, 1.0, 1.0, 1.0), + bbox_reg_loss_type='smooth_l1', + smooth_l1_loss_beta=0.0, + focal_loss_gamma=2.0, + focal_loss_alpha=0.25, + prior_prob=0.01, + score_thresh_test=0.05, + topk_candidates_test=1000, + nms_thresh_test=0.5))) + +optim_wrapper = dict(optimizer=dict(lr=0.01)) diff --git a/mmdetection/configs/mm_grounding_dino/README.md b/mmdetection/configs/mm_grounding_dino/README.md new file mode 100644 index 00000000..c88cb1c9 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/README.md @@ -0,0 +1,387 @@ +# MM Grounding DINO + +> [An Open and Comprehensive Pipeline for Unified Object Grounding and Detection](https://arxiv.org/abs/2401.02361) + + + +## Abstract + +Grounding-DINO is a state-of-the-art open-set detection model that tackles multiple vision tasks including Open-Vocabulary Detection (OVD), Phrase Grounding (PG), and Referring Expression Comprehension (REC). Its effectiveness has led to its widespread adoption as a mainstream architecture for various downstream applications. However, despite its significance, the original Grounding-DINO model lacks comprehensive public technical details due to the unavailability of its training code. To bridge this gap, we present MM-Grounding-DINO, an open-source, comprehensive, and user-friendly baseline, which is built with the MMDetection toolbox. It adopts abundant vision datasets for pre-training and various detection and grounding datasets for fine-tuning. We give a comprehensive analysis of each reported result and detailed settings for reproduction. The extensive experiments on the benchmarks mentioned demonstrate that our MM-Grounding-DINO-Tiny outperforms the Grounding-DINO-Tiny baseline. We release all our models to the research community. + +
    + +
    + +
    + +
    + +## Dataset Preparation + +Please refer to [dataset_prepare.md](dataset_prepare.md) or [中文版数据准备](dataset_prepare_zh-CN.md) + +## ✨ What's New + +💎 **We have released the pre-trained weights for Swin-B and Swin-L, welcome to try and give feedback.** + +## Usage + +Please refer to [usage.md](usage.md) or [中文版用法说明](usage_zh-CN.md) + +## Zero-Shot COCO Results and Models + +| Model | Backbone | Style | COCO mAP | Pre-Train Data | Config | Download | +| :----------: | :------: | :-------: | :--------: | :----------------------: | :------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| GDINO-T | Swin-T | Zero-shot | 46.7 | O365 | | | +| GDINO-T | Swin-T | Zero-shot | 48.1 | O365,GoldG | | | +| GDINO-T | Swin-T | Zero-shot | 48.4 | O365,GoldG,Cap4M | [config](../grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_cap4m.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/grounding_dino/groundingdino_swint_ogc_mmdet-822d7e9d.pth) | +| MM-GDINO-T | Swin-T | Zero-shot | 48.5(+1.8) | O365 | [config](grounding_dino_swin-t_pretrain_obj365.py) | | +| MM-GDINO-T | Swin-T | Zero-shot | 50.4(+2.3) | O365,GoldG | [config](grounding_dino_swin-t_pretrain_obj365_goldg.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg/grounding_dino_swin-t_pretrain_obj365_goldg_20231122_132602-4ea751ce.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg/grounding_dino_swin-t_pretrain_obj365_goldg_20231122_132602.log.json) | +| MM-GDINO-T | Swin-T | Zero-shot | 50.5(+2.1) | O365,GoldG,GRIT | [config](grounding_dino_swin-t_pretrain_obj365_goldg_grit9m.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_20231128_200818-169cc352.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_20231128_200818.log.json) | +| MM-GDINO-T | Swin-T | Zero-shot | 50.6(+2.2) | O365,GoldG,V3Det | [config](grounding_dino_swin-t_pretrain_obj365_goldg_v3det.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_v3det_20231218_095741-e316e297.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_v3det_20231218_095741.log.json) | +| MM-GDINO-T | Swin-T | Zero-shot | 50.4(+2.0) | O365,GoldG,GRIT,V3Det | [config](grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047.log.json) | +| MM-GDINO-B | Swin-B | Zero-shot | 52.5 | O365,GoldG,V3Det | [config](grounding_dino_swin-b_pretrain_obj365_goldg_v3det.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-b_pretrain_obj365_goldg_v3det/grounding_dino_swin-b_pretrain_obj365_goldg_v3de-f83eef00.pth) \| [log](<>) | +| MM-GDINO-B\* | Swin-B | - | 59.5 | O365,ALL | [config](grounding_dino_swin-b_pretrain_all.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-b_pretrain_all/grounding_dino_swin-b_pretrain_all-f9818a7c.pth) \| [log](<>) | +| MM-GDINO-L | Swin-L | Zero-shot | 53.0 | O365V2,OpenImageV6,GoldG | [config](grounding_dino_swin-l_pretrain_obj365_goldg.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-l_pretrain_obj365_goldg/grounding_dino_swin-l_pretrain_obj365_goldg-34dcdc53.pth) \| [log](<>) | +| MM-GDINO-L\* | Swin-L | - | 60.3 | O365V2,OpenImageV6,ALL | [config](grounding_dino_swin-l_pretrain_all.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-l_pretrain_all/grounding_dino_swin-l_pretrain_all-56d69e78.pth) \| [log](<>) | + +- This * indicates that the model has not been fully trained yet. We will release the final weights in the future. +- ALL: GoldG,V3det,COCO2017,LVISV1,COCO2014,GRIT,RefCOCO,RefCOCO+,RefCOCOg,gRefCOCO. + +## Zero-Shot LVIS Results + +| Model | MiniVal APr | MiniVal APc | MiniVal APf | MiniVal AP | Val1.0 APr | Val1.0 APc | Val1.0 APf | Val1.0 AP | Pre-Train Data | +| :--------: | :---------: | :---------: | :---------: | :---------: | :--------: | :--------: | :--------: | :---------: | :-------------------: | +| GDINO-T | 18.8 | 24.2 | 34.7 | 28.8 | 10.1 | 15.3 | 29.9 | 20.1 | O365,GoldG,Cap4M | +| MM-GDINO-T | 28.1 | 30.2 | 42.0 | 35.7(+6.9) | 17.1 | 22.4 | 36.5 | 27.0(+6.9) | O365,GoldG | +| MM-GDINO-T | 26.6 | 32.4 | 41.8 | 36.5(+7.7) | 17.3 | 22.6 | 36.4 | 27.1(+7.0) | O365,GoldG,GRIT | +| MM-GDINO-T | 33.0 | 36.0 | 45.9 | 40.5(+11.7) | 21.5 | 25.5 | 40.2 | 30.6(+10.5) | O365,GoldG,V3Det | +| MM-GDINO-T | 34.2 | 37.4 | 46.2 | 41.4(+12.6) | 23.6 | 27.6 | 40.5 | 31.9(+11.8) | O365,GoldG,GRIT,V3Det | + +- The MM-GDINO-T config file is [mini-lvis](lvis/grounding_dino_swin-t_pretrain_zeroshot_mini-lvis.py) and [lvis 1.0](lvis/grounding_dino_swin-t_pretrain_zeroshot_lvis.py) + +## Zero-Shot ODinW (Object Detection in the Wild) Results + +### Results and models of ODinW13 + +| Method | GDINO-T
    (O365,GoldG,Cap4M) | MM-GDINO-T
    (O365,GoldG) | MM-GDINO-T
    (O365,GoldG,GRIT) | MM-GDINO-T
    (O365,GoldG,V3Det) | MM-GDINO-T
    (O365,GoldG,GRIT,V3Det) | +| --------------------- | -------------------------------- | ----------------------------- | ---------------------------------- | ----------------------------------- | ---------------------------------------- | +| AerialMaritimeDrone | 0.173 | 0.133 | 0.155 | 0.177 | 0.151 | +| Aquarium | 0.195 | 0.252 | 0.261 | 0.266 | 0.283 | +| CottontailRabbits | 0.799 | 0.771 | 0.810 | 0.778 | 0.786 | +| EgoHands | 0.608 | 0.499 | 0.537 | 0.506 | 0.519 | +| NorthAmericaMushrooms | 0.507 | 0.331 | 0.462 | 0.669 | 0.767 | +| Packages | 0.687 | 0.707 | 0.687 | 0.710 | 0.706 | +| PascalVOC | 0.563 | 0.565 | 0.580 | 0.556 | 0.566 | +| pistols | 0.726 | 0.585 | 0.709 | 0.671 | 0.729 | +| pothole | 0.215 | 0.136 | 0.285 | 0.199 | 0.243 | +| Raccoon | 0.549 | 0.469 | 0.511 | 0.553 | 0.535 | +| ShellfishOpenImages | 0.393 | 0.321 | 0.437 | 0.519 | 0.488 | +| thermalDogsAndPeople | 0.657 | 0.556 | 0.603 | 0.493 | 0.542 | +| VehiclesOpenImages | 0.613 | 0.566 | 0.603 | 0.614 | 0.615 | +| Average | **0.514** | **0.453** | **0.511** | **0.516** | **0.533** | + +- The MM-GDINO-T config file is [odinw13](odinw/grounding_dino_swin-t_pretrain_odinw13.py) + +### Results and models of ODinW35 + +| Method | GDINO-T
    (O365,GoldG,Cap4M) | MM-GDINO-T
    (O365,GoldG) | MM-GDINO-T
    (O365,GoldG,GRIT) | MM-GDINO-T
    (O365,GoldG,V3Det) | MM-GDINO-T
    (O365,GoldG,GRIT,V3Det) | +| --------------------------- | -------------------------------- | ----------------------------- | ---------------------------------- | ----------------------------------- | ---------------------------------------- | +| AerialMaritimeDrone_large | 0.173 | 0.133 | 0.155 | 0.177 | 0.151 | +| AerialMaritimeDrone_tiled | 0.206 | 0.170 | 0.225 | 0.184 | 0.206 | +| AmericanSignLanguageLetters | 0.002 | 0.016 | 0.020 | 0.011 | 0.007 | +| Aquarium | 0.195 | 0.252 | 0.261 | 0.266 | 0.283 | +| BCCD | 0.161 | 0.069 | 0.118 | 0.083 | 0.077 | +| boggleBoards | 0.000 | 0.002 | 0.001 | 0.001 | 0.002 | +| brackishUnderwater | 0.021 | 0.033 | 0.021 | 0.025 | 0.025 | +| ChessPieces | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | +| CottontailRabbits | 0.806 | 0.771 | 0.810 | 0.778 | 0.786 | +| dice | 0.004 | 0.002 | 0.005 | 0.001 | 0.001 | +| DroneControl | 0.042 | 0.047 | 0.097 | 0.088 | 0.074 | +| EgoHands_generic | 0.608 | 0.527 | 0.537 | 0.506 | 0.519 | +| EgoHands_specific | 0.002 | 0.001 | 0.005 | 0.007 | 0.003 | +| HardHatWorkers | 0.046 | 0.048 | 0.070 | 0.070 | 0.108 | +| MaskWearing | 0.004 | 0.009 | 0.004 | 0.011 | 0.009 | +| MountainDewCommercial | 0.430 | 0.453 | 0.465 | 0.194 | 0.430 | +| NorthAmericaMushrooms | 0.471 | 0.331 | 0.462 | 0.669 | 0.767 | +| openPoetryVision | 0.000 | 0.001 | 0.000 | 0.000 | 0.000 | +| OxfordPets_by_breed | 0.003 | 0.002 | 0.004 | 0.006 | 0.004 | +| OxfordPets_by_species | 0.011 | 0.019 | 0.016 | 0.020 | 0.015 | +| PKLot | 0.001 | 0.004 | 0.002 | 0.008 | 0.007 | +| Packages | 0.695 | 0.707 | 0.687 | 0.710 | 0.706 | +| PascalVOC | 0.563 | 0.565 | 0.580 | 0.566 | 0.566 | +| pistols | 0.726 | 0.585 | 0.709 | 0.671 | 0.729 | +| plantdoc | 0.005 | 0.005 | 0.007 | 0.008 | 0.011 | +| pothole | 0.215 | 0.136 | 0.219 | 0.077 | 0.168 | +| Raccoons | 0.549 | 0.469 | 0.511 | 0.553 | 0.535 | +| selfdrivingCar | 0.089 | 0.091 | 0.076 | 0.094 | 0.083 | +| ShellfishOpenImages | 0.393 | 0.321 | 0.437 | 0.519 | 0.488 | +| ThermalCheetah | 0.087 | 0.063 | 0.081 | 0.030 | 0.045 | +| thermalDogsAndPeople | 0.657 | 0.556 | 0.603 | 0.493 | 0.543 | +| UnoCards | 0.006 | 0.012 | 0.010 | 0.009 | 0.005 | +| VehiclesOpenImages | 0.613 | 0.566 | 0.603 | 0.614 | 0.615 | +| WildfireSmoke | 0.134 | 0.106 | 0.154 | 0.042 | 0.127 | +| websiteScreenshots | 0.012 | 0.02 | 0.016 | 0.016 | 0.016 | +| Average | **0.227** | **0.202** | **0.228** | **0.214** | **0.284** | + +- The MM-GDINO-T config file is [odinw35](odinw/grounding_dino_swin-t_pretrain_odinw35.py) + +## Zero-Shot Referring Expression Comprehension Results + +| Method | GDINO-T
    (O365,GoldG,Cap4M) | MM-GDINO-T
    (O365,GoldG) | MM-GDINO-T
    (O365,GoldG,GRIT) | MM-GDINO-T
    (O365,GoldG,V3Det) | MM-GDINO-T
    (O365,GoldG,GRIT,V3Det) | +| ---------------------- | -------------------------------- | ----------------------------- | ---------------------------------- | ----------------------------------- | ---------------------------------------- | +| RefCOCO val @1,5,10 | 50.8/89.5/94.9 | 53.1/89.9/94.7 | 53.4/90.3/95.5 | 52.1/89.8/95.0 | 53.1/89.7/95.1 | +| RefCOCO testA @1,5,10 | 57.4/91.3/95.6 | 59.7/91.5/95.9 | 58.8/91.70/96.2 | 58.4/86.8/95.6 | 59.1/91.0/95.5 | +| RefCOCO testB @1,5,10 | 45.0/86.5/92.9 | 46.4/86.9/92.2 | 46.8/87.7/93.3 | 45.4/86.2/92.6 | 46.8/87.8/93.6 | +| RefCOCO+ val @1,5,10 | 51.6/86.4/92.6 | 53.1/87.0/92.8 | 53.5/88.0/93.7 | 52.5/86.8/93.2 | 52.7/87.7/93.5 | +| RefCOCO+ testA @1,5,10 | 57.3/86.7/92.7 | 58.9/87.3/92.9 | 59.0/88.1/93.7 | 58.1/86.7/93.5 | 58.7/87.2/93.1 | +| RefCOCO+ testB @1,5,10 | 46.4/84.1/90.7 | 47.9/84.3/91.0 | 47.9/85.5/92.7 | 46.9/83.7/91.5 | 48.4/85.8/92.1 | +| RefCOCOg val @1,5,10 | 60.4/92.1/96.2 | 61.2/92.6/96.1 | 62.7/93.3/97.0 | 61.7/92.9/96.6 | 62.9/93.3/97.2 | +| RefCOCOg test @1,5,10 | 59.7/92.1/96.3 | 61.1/93.3/96.7 | 62.6/94.9/97.1 | 61.0/93.1/96.8 | 62.9/93.9/97.4 | + +| Method | thresh_score | GDINO-T
    (O365,GoldG,Cap4M) | MM-GDINO-T
    (O365,GoldG) | MM-GDINO-T
    (O365,GoldG,GRIT) | MM-GDINO-T
    (O365,GoldG,V3Det) | MM-GDINO-T
    (O365,GoldG,GRIT,V3Det) | +| --------------------------------------- | ------------ | -------------------------------- | ----------------------------- | ---------------------------------- | ----------------------------------- | ---------------------------------------- | +| gRefCOCO val Pr@(F1=1, IoU≥0.5),N-acc | 0.5 | 39.3/70.4 | | | | 39.4/67.5 | +| gRefCOCO val Pr@(F1=1, IoU≥0.5),N-acc | 0.6 | 40.5/83.8 | | | | 40.6/83.1 | +| gRefCOCO val Pr@(F1=1, IoU≥0.5),N-acc | 0.7 | 41.3/91.8 | 39.8/84.7 | 40.7/89.7 | 40.3/88.8 | 41.0/91.3 | +| gRefCOCO val Pr@(F1=1, IoU≥0.5),N-acc | 0.8 | 41.5/96.8 | | | | 41.1/96.4 | +| gRefCOCO testA Pr@(F1=1, IoU≥0.5),N-acc | 0.5 | 31.9/70.4 | | | | 33.1/69.5 | +| gRefCOCO testA Pr@(F1=1, IoU≥0.5),N-acc | 0.6 | 29.3/82.9 | | | | 29.2/84.3 | +| gRefCOCO testA Pr@(F1=1, IoU≥0.5),N-acc | 0.7 | 27.2/90.2 | 26.3/89.0 | 26.0/91.9 | 25.4/91.8 | 26.1/93.0 | +| gRefCOCO testA Pr@(F1=1, IoU≥0.5),N-acc | 0.8 | 25.1/96.3 | | | | 23.8/97.2 | +| gRefCOCO testB Pr@(F1=1, IoU≥0.5),N-acc | 0.5 | 30.9/72.5 | | | | 33.0/69.6 | +| gRefCOCO testB Pr@(F1=1, IoU≥0.5),N-acc | 0.6 | 30.0/86.1 | | | | 31.6/96.7 | +| gRefCOCO testB Pr@(F1=1, IoU≥0.5),N-acc | 0.7 | 29.7/93.5 | 31.3/84.8 | 30.6/90.2 | 30.7/89.9 | 30.4/92.3 | +| gRefCOCO testB Pr@(F1=1, IoU≥0.5),N-acc | 0.8 | 29.1/97.4 | | | | 29.5/84.2 | + +- The MM-GDINO-T config file is [here](refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp.py) + +## Zero-Shot Description Detection Dataset(DOD) + +```shell +pip install ddd-dataset +``` + +| Method | mode | GDINO-T
    (O365,GoldG,Cap4M) | MM-GDINO-T
    (O365,GoldG) | MM-GDINO-T
    (O365,GoldG,GRIT) | MM-GDINO-T
    (O365,GoldG,V3Det) | MM-GDINO-T
    (O365,GoldG,GRIT,V3Det) | +| -------------------------------- | -------- | -------------------------------- | ----------------------------- | ---------------------------------- | ----------------------------------- | ---------------------------------------- | +| FULL/short/middle/long/very long | concat | 17.2/18.0/18.7/14.8/16.3 | 15.6/17.3/16.7/14.3/13.1 | 17.0/17.7/18.0/15.7/15.7 | 16.2/17.4/16.8/14.9/15.4 | 17.5/23.4/18.3/14.7/13.8 | +| FULL/short/middle/long/very long | parallel | 22.3/28.2/24.8/19.1/13.9 | 21.7/24.7/24.0/20.2/13.7 | 22.5/25.6/25.1/20.5/14.9 | 22.3/25.6/24.5/20.6/14.7 | 22.9/28.1/25.4/20.4/14.4 | +| PRES/short/middle/long/very long | concat | 17.8/18.3/19.2/15.2/17.3 | 16.4/18.4/17.3/14.5/14.2 | 17.9/19.0/18.3/16.5/17.5 | 16.6/18.8/17.1/15.1/15.0 | 18.0/23.7/18.6/15.4/13.3 | +| PRES/short/middle/long/very long | parallel | 21.0/27.0/22.8/17.5/12.5 | 21.3/25.5/22.8/19.2/12.9 | 21.5/25.2/23.0/19.0/15.0 | 21.6/25.7/23.0/19.5/14.8 | 21.9/27.4/23.2/19.1/14.2 | +| ABS/short/middle/long/very long | concat | 15.4/17.1/16.4/13.6/14.9 | 13.4/13.4/14.5/13.5/11.9 | 14.5/13.1/16.7/13.6/13.3 | 14.8/12.5/15.6/14.3/15.8 | 15.9/22.2/17.1/12.5/14.4 | +| ABS/short/middle/long/very long | parallel | 26.0/32.0/33.0/23.6/15.5 | 22.8/22.2/28.7/22.9/14.7 | 25.6/26.8/33.9/24.5/14.7 | 24.1/24.9/30.7/23.8/14.7 | 26.0/30.3/34.1/23.9/14.6 | + +Note: + +1. Considering that the evaluation time for Inter-scenario is very long and the performance is low, it is temporarily not supported. The mentioned metrics are for Intra-scenario. +2. `concat` is the default inference mode for Grounding DINO, where it concatenates multiple sub-sentences with "." to form a single sentence for inference. On the other hand, "parallel" performs inference on each sub-sentence in a for-loop. +3. The MM-GDINO-T config file is [concat_dod](dod/grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py) and [parallel_dod](dod/grounding_dino_swin-t_pretrain_zeroshot_parallel_dod.py) + +## Pretrain Flickr30k Results + +| Model | Pre-Train Data | Val R@1 | Val R@5 | Val R@10 | Test R@1 | Test R@5 | Test R@10 | +| :--------: | :-------------------: | ------- | ------- | -------- | -------- | -------- | --------- | +| GLIP-T | O365,GoldG | 84.9 | 94.9 | 96.3 | 85.6 | 95.4 | 96.7 | +| GLIP-T | O365,GoldG,CC3M,SBU | 85.3 | 95.5 | 96.9 | 86.0 | 95.9 | 97.2 | +| GDINO-T | O365,GoldG,Cap4M | 87.8 | 96.6 | 98.0 | 88.1 | 96.9 | 98.2 | +| MM-GDINO-T | O365,GoldG | 85.5 | 95.6 | 97.2 | 86.2 | 95.7 | 97.4 | +| MM-GDINO-T | O365,GoldG,GRIT | 86.7 | 95.8 | 97.6 | 87.0 | 96.2 | 97.7 | +| MM-GDINO-T | O365,GoldG,V3Det | 85.9 | 95.7 | 97.4 | 86.3 | 95.7 | 97.4 | +| MM-GDINO-T | O365,GoldG,GRIT,V3Det | 86.7 | 96.0 | 97.6 | 87.2 | 96.2 | 97.7 | + +Note: + +1. `@1,5,10` refers to precision at the top 1, 5, and 10 positions in a predicted ranked list. +2. The MM-GDINO-T config file is [here](flickr30k/grounding_dino_swin-t-pretrain_flickr30k.py) + +## Validating the generalization of a pre-trained model through fine-tuning + +### RTTS + +| Architecture | Backbone | Lr schd | box AP | +| :-----------------: | :------: | ------- | -------- | +| Faster R-CNN | R-50 | 1x | 48.1 | +| Cascade R-CNN | R-50 | 1x | 50.8 | +| ATSS | R-50 | 1x | 48.2 | +| TOOD | R-50 | 1X | 50.8 | +| MM-GDINO(zero-shot) | Swin-T | | 49.8 | +| MM-GDINO | Swin-T | 1x | **69.1** | + +- The reference metrics come from https://github.com/BIGWangYuDong/lqit/tree/main/configs/detection/rtts_dataset +- The MM-GDINO-T config file is [here](rtts/grounding_dino_swin-t_finetune_8xb4_1x_rtts.py) + +### RUOD + +| Architecture | Backbone | Lr schd | box AP | +| :-----------------: | :------: | ------- | -------- | +| Faster R-CNN | R-50 | 1x | 52.4 | +| Cascade R-CNN | R-50 | 1x | 55.3 | +| ATSS | R-50 | 1x | 55.7 | +| TOOD | R-50 | 1X | 57.4 | +| MM-GDINO(zero-shot) | Swin-T | | 29.8 | +| MM-GDINO | Swin-T | 1x | **65.5** | + +- The reference metrics come from https://github.com/BIGWangYuDong/lqit/tree/main/configs/detection/ruod_dataset +- The MM-GDINO-T config file is [here](ruod/grounding_dino_swin-t_finetune_8xb4_1x_ruod.py) + +### Brain Tumor + +| Architecture | Backbone | Lr schd | box AP | +| :-----------: | :------: | ------- | ------ | +| Faster R-CNN | R-50 | 50e | 43.5 | +| Cascade R-CNN | R-50 | 50e | 46.2 | +| DINO | R-50 | 50e | 46.4 | +| Cascade-DINO | R-50 | 50e | 48.6 | +| MM-GDINO | Swin-T | 50e | 47.5 | + +- The reference metrics come from https://arxiv.org/abs/2307.11035 +- The MM-GDINO-T config file is [here](brain_tumor/grounding_dino_swin-t_finetune_8xb4_50e_brain_tumor.py) + +### Cityscapes + +| Architecture | Backbone | Lr schd | box AP | +| :-----------------: | :------: | ------- | -------- | +| Faster R-CNN | R-50 | 50e | 30.1 | +| Cascade R-CNN | R-50 | 50e | 31.8 | +| DINO | R-50 | 50e | 34.5 | +| Cascade-DINO | R-50 | 50e | 34.8 | +| MM-GDINO(zero-shot) | Swin-T | | 34.2 | +| MM-GDINO | Swin-T | 50e | **51.5** | + +- The reference metrics come from https://arxiv.org/abs/2307.11035 +- The MM-GDINO-T config file is [here](cityscapes/grounding_dino_swin-t_finetune_8xb4_50e_cityscapes.py) + +### People in Painting + +| Architecture | Backbone | Lr schd | box AP | +| :-----------------: | :------: | ------- | -------- | +| Faster R-CNN | R-50 | 50e | 17.0 | +| Cascade R-CNN | R-50 | 50e | 18.0 | +| DINO | R-50 | 50e | 12.0 | +| Cascade-DINO | R-50 | 50e | 13.4 | +| MM-GDINO(zero-shot) | Swin-T | | 23.1 | +| MM-GDINO | Swin-T | 50e | **38.9** | + +- The reference metrics come from https://arxiv.org/abs/2307.11035 +- The MM-GDINO-T config file is [here](people_in_painting/grounding_dino_swin-t_finetune_8xb4_50e_people_in_painting.py) + +### COCO + +**(1) Closed-set performance** + +| Architecture | Backbone | Lr schd | box AP | +| :-----------------: | :------: | ------- | ------ | +| Faster R-CNN | R-50 | 1x | 37.4 | +| Cascade R-CNN | R-50 | 1x | 40.3 | +| ATSS | R-50 | 1x | 39.4 | +| TOOD | R-50 | 1X | 42.4 | +| DINO | R-50 | 1X | 50.1 | +| GLIP(zero-shot) | Swin-T | | 46.6 | +| GDINO(zero-shot) | Swin-T | | 48.5 | +| MM-GDINO(zero-shot) | Swin-T | | 50.4 | +| GLIP | Swin-T | 1x | 55.4 | +| GDINO | Swin-T | 1x | 58.1 | +| MM-GDINO | Swin-T | 1x | 58.2 | + +- The MM-GDINO-T config file is [here](coco/grounding_dino_swin-t_finetune_16xb4_1x_coco.py) + +**(2) Open-set continuing pretraining performance** + +| Architecture | Backbone | Lr schd | box AP | +| :-----------------: | :------: | :-----: | :----: | +| GLIP(zero-shot) | Swin-T | | 46.7 | +| GDINO(zero-shot) | Swin-T | | 48.5 | +| MM-GDINO(zero-shot) | Swin-T | | 50.4 | +| MM-GDINO | Swin-T | 1x | 54.7 | + +- The MM-GDINO-T config file is [here](coco/grounding_dino_swin-t_finetune_16xb4_1x_sft_coco.py) +- Due to the small size of the COCO dataset, continuing pretraining solely on COCO can easily lead to overfitting. The results shown above are from the third epoch. I do not recommend you train using this approach. + +**(3) Open vocabulary performance** + +| Architecture | Backbone | Lr schd | box AP | Base box AP | Novel box AP | box AP@50 | Base box AP@50 | Novel box AP@50 | +| :-----------------: | :------: | :-----: | :----: | :---------: | :----------: | :-------: | :------------: | :-------------: | +| MM-GDINO(zero-shot) | Swin-T | | 51.1 | 48.4 | 58.9 | 66.7 | 64.0 | 74.2 | +| MM-GDINO | Swin-T | 1x | 57.2 | 56.1 | 60.4 | 73.6 | 73.0 | 75.3 | + +- The MM-GDINO-T config file is [here](coco/grounding_dino_swin-t_finetune_16xb4_1x_coco_48_17.py) + +### LVIS 1.0 + +**(1) Open-set continuing pretraining performance** + +| Architecture | Backbone | Lr schd | MiniVal APr | MiniVal APc | MiniVal APf | MiniVal AP | Val1.0 APr | Val1.0 APc | Val1.0 APf | Val1.0 AP | +| :-----------------: | :------: | :-----: | :---------: | :---------: | :---------: | :--------: | :--------: | :--------: | :--------: | :-------: | +| GLIP(zero-shot) | Swin-T | | 18.1 | 21.2 | 33.1 | 26.7 | 10.8 | 14.7 | 29.0 | 19.6 | +| GDINO(zero-shot) | Swin-T | | 18.8 | 24.2 | 34.7 | 28.8 | 10.1 | 15.3 | 29.9 | 20.1 | +| MM-GDINO(zero-shot) | Swin-T | | 34.2 | 37.4 | 46.2 | 41.4 | 23.6 | 27.6 | 40.5 | 31.9 | +| MM-GDINO | Swin-T | 1x | 50.7 | 58.8 | 60.1 | 58.7 | 45.2 | 50.2 | 56.1 | 51.7 | + +- The MM-GDINO-T config file is [here](lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis.py) + +**(2) Open vocabulary performance** + +| Architecture | Backbone | Lr schd | MiniVal APr | MiniVal APc | MiniVal APf | MiniVal AP | +| :-----------------: | :------: | :-----: | :---------: | :---------: | :---------: | :--------: | +| MM-GDINO(zero-shot) | Swin-T | | 34.2 | 37.4 | 46.2 | 41.4 | +| MM-GDINO | Swin-T | 1x | 43.2 | 57.4 | 59.3 | 57.1 | + +- The MM-GDINO-T config file is [here](lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis_866_337.py) + +### RefEXP + +#### RefCOCO + +| Architecture | Backbone | Lr schd | val @1 | val @5 | val @10 | testA @1 | testA @5 | testA @10 | testB @1 | testB @5 | testB @10 | +| :-----------------: | :------: | :-----: | :----: | :----: | :-----: | :------: | :------: | :-------: | :------: | :------: | :-------: | +| GDINO(zero-shot) | Swin-T | | 50.8 | 89.5 | 94.9 | 57.5 | 91.3 | 95.6 | 45.0 | 86.5 | 92.9 | +| MM-GDINO(zero-shot) | Swin-T | | 53.1 | 89.7 | 95.1 | 59.1 | 91.0 | 95.5 | 46.8 | 87.8 | 93.6 | +| GDINO | Swin-T | UNK | 89.2 | | | 91.9 | | | 86.0 | | | +| MM-GDINO | Swin-T | 5e | 89.5 | 98.6 | 99.4 | 91.4 | 99.2 | 99.8 | 86.6 | 97.9 | 99.1 | + +- The MM-GDINO-T config file is [here](refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcoco.py) + +#### RefCOCO+ + +| Architecture | Backbone | Lr schd | val @1 | val @5 | val @10 | testA @1 | testA @5 | testA @10 | testB @1 | testB @5 | testB @10 | +| :-----------------: | :------: | :-----: | :----: | :----: | :-----: | :------: | :------: | :-------: | :------: | :------: | :-------: | +| GDINO(zero-shot) | Swin-T | | 51.6 | 86.4 | 92.6 | 57.3 | 86.7 | 92.7 | 46.4 | 84.1 | 90.7 | +| MM-GDINO(zero-shot) | Swin-T | | 52.7 | 87.7 | 93.5 | 58.7 | 87.2 | 93.1 | 48.4 | 85.8 | 92.1 | +| GDINO | Swin-T | UNK | 81.1 | | | 87.4 | | | 74.7 | | | +| MM-GDINO | Swin-T | 5e | 82.1 | 97.8 | 99.2 | 87.5 | 99.2 | 99.7 | 74.0 | 96.3 | 96.4 | + +- The MM-GDINO-T config file is [here](refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcoco_plus.py) + +#### RefCOCOg + +| Architecture | Backbone | Lr schd | val @1 | val @5 | val @10 | test @1 | test @5 | test @10 | +| :-----------------: | :------: | :-----: | :----: | :----: | :-----: | :-----: | :-----: | :------: | +| GDINO(zero-shot) | Swin-T | | 60.4 | 92.1 | 96.2 | 59.7 | 92.1 | 96.3 | +| MM-GDINO(zero-shot) | Swin-T | | 62.9 | 93.3 | 97.2 | 62.9 | 93.9 | 97.4 | +| GDINO | Swin-T | UNK | 84.2 | | | 84.9 | | | +| MM-GDINO | Swin-T | 5e | 85.5 | 98.4 | 99.4 | 85.8 | 98.6 | 99.4 | + +- The MM-GDINO-T config file is [here](refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcocog.py) + +#### gRefCOCO + +| Architecture | Backbone | Lr schd | val Pr@(F1=1, IoU≥0.5) | val N-acc | testA Pr@(F1=1, IoU≥0.5) | testA N-acc | testB Pr@(F1=1, IoU≥0.5) | testB N-acc | +| :-----------------: | :------: | :-----: | :--------------------: | :-------: | :----------------------: | :---------: | :----------------------: | :---------: | +| GDINO(zero-shot) | Swin-T | | 41.3 | 91.8 | 27.2 | 90.2 | 29.7 | 93.5 | +| MM-GDINO(zero-shot) | Swin-T | | 41.0 | 91.3 | 26.1 | 93.0 | 30.4 | 92.3 | +| MM-GDINO | Swin-T | 5e | 45.1 | 64.7 | 42.5 | 65.5 | 40.3 | 63.2 | + +- The MM-GDINO-T config file is [here](refcoco/grounding_dino_swin-t_finetune_8xb4_5e_grefcoco.py) + +## Citation + +If you find this project useful in your research, please consider citing: + +```latex +@article{zhao2024open, + title={An Open and Comprehensive Pipeline for Unified Object Grounding and Detection}, + author={Zhao, Xiangyu and Chen, Yicheng and Xu, Shilin and Li, Xiangtai and Wang, Xinjiang and Li, Yining and Huang, Haian}, + journal={arXiv preprint arXiv:2401.02361}, + year={2024} +} +``` diff --git a/mmdetection/configs/mm_grounding_dino/brain_tumor/grounding_dino_swin-t_finetune_8xb4_50e_brain_tumor.py b/mmdetection/configs/mm_grounding_dino/brain_tumor/grounding_dino_swin-t_finetune_8xb4_50e_brain_tumor.py new file mode 100644 index 00000000..1172da5b --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/brain_tumor/grounding_dino_swin-t_finetune_8xb4_50e_brain_tumor.py @@ -0,0 +1,112 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +# https://universe.roboflow.com/roboflow-100/brain-tumor-m2pbp/dataset/2 +data_root = 'data/brain_tumor_v2/' +class_name = ('label0', 'label1', 'label2') +label_name = '_annotations.coco.json' + +palette = [(220, 20, 60), (255, 0, 0), (0, 0, 142)] + +metainfo = dict(classes=class_name, palette=palette) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +train_dataloader = dict( + sampler=dict(_delete_=True, type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + _delete_=True, + type='RepeatDataset', + times=10, + dataset=dict( + type='CocoDataset', + data_root=data_root, + metainfo=metainfo, + filter_cfg=dict(filter_empty_gt=False, min_size=32), + pipeline=train_pipeline, + return_classes=True, + data_prefix=dict(img='train/'), + ann_file='train/' + label_name))) + +val_dataloader = dict( + dataset=dict( + metainfo=metainfo, + data_root=data_root, + return_classes=True, + ann_file='valid/' + label_name, + data_prefix=dict(img='valid/'))) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'valid/' + label_name, + metric='bbox', + format_only=False) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1) + })) + +# learning policy +max_epochs = 5 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[4], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +default_hooks = dict(checkpoint=dict(max_keep_ckpts=1, save_best='auto')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/cityscapes/grounding_dino_swin-t_finetune_8xb4_50e_cityscapes.py b/mmdetection/configs/mm_grounding_dino/cityscapes/grounding_dino_swin-t_finetune_8xb4_50e_cityscapes.py new file mode 100644 index 00000000..c4283413 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/cityscapes/grounding_dino_swin-t_finetune_8xb4_50e_cityscapes.py @@ -0,0 +1,110 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/cityscapes/' +class_name = ('person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle') +palette = [(220, 20, 60), (255, 0, 0), (0, 0, 142), (0, 0, 70), (0, 60, 100), + (0, 80, 100), (0, 0, 230), (119, 11, 32)] + +metainfo = dict(classes=class_name, palette=palette) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +train_dataloader = dict( + sampler=dict(_delete_=True, type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + _delete_=True, + type='RepeatDataset', + times=10, + dataset=dict( + type='CocoDataset', + data_root=data_root, + metainfo=metainfo, + filter_cfg=dict(filter_empty_gt=False, min_size=32), + pipeline=train_pipeline, + return_classes=True, + data_prefix=dict(img='leftImg8bit/train/'), + ann_file='annotations/instancesonly_filtered_gtFine_train.json'))) + +val_dataloader = dict( + dataset=dict( + metainfo=metainfo, + data_root=data_root, + return_classes=True, + ann_file='annotations/instancesonly_filtered_gtFine_val.json', + data_prefix=dict(img='leftImg8bit/val/'))) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instancesonly_filtered_gtFine_val.json', + metric='bbox', + format_only=False) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1) + })) + +# learning policy +max_epochs = 5 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[4], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) +default_hooks = dict(checkpoint=dict(max_keep_ckpts=1, save_best='auto')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/coco/grounding_dino_swin-t_finetune_16xb4_1x_coco.py b/mmdetection/configs/mm_grounding_dino/coco/grounding_dino_swin-t_finetune_16xb4_1x_coco.py new file mode 100644 index 00000000..792297ac --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/coco/grounding_dino_swin-t_finetune_16xb4_1x_coco.py @@ -0,0 +1,85 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/coco/' + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='CocoDataset', + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + return_classes=True, + filter_cfg=dict(filter_empty_gt=False, min_size=32), + pipeline=train_pipeline)) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + 'language_model': dict(lr_mult=0.1), + })) + +# learning policy +max_epochs = 12 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +default_hooks = dict(checkpoint=dict(max_keep_ckpts=1, save_best='auto')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/coco/grounding_dino_swin-t_finetune_16xb4_1x_coco_48_17.py b/mmdetection/configs/mm_grounding_dino/coco/grounding_dino_swin-t_finetune_16xb4_1x_coco_48_17.py new file mode 100644 index 00000000..e68afbb4 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/coco/grounding_dino_swin-t_finetune_16xb4_1x_coco_48_17.py @@ -0,0 +1,157 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/coco/' +base_classes = ('person', 'bicycle', 'car', 'motorcycle', 'train', 'truck', + 'boat', 'bench', 'bird', 'horse', 'sheep', 'bear', 'zebra', + 'giraffe', 'backpack', 'handbag', 'suitcase', 'frisbee', + 'skis', 'kite', 'surfboard', 'bottle', 'fork', 'spoon', 'bowl', + 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', + 'pizza', 'donut', 'chair', 'bed', 'toilet', 'tv', 'laptop', + 'mouse', 'remote', 'microwave', 'oven', 'toaster', + 'refrigerator', 'book', 'clock', 'vase', 'toothbrush') # 48 +novel_classes = ('airplane', 'bus', 'cat', 'dog', 'cow', 'elephant', + 'umbrella', 'tie', 'snowboard', 'skateboard', 'cup', 'knife', + 'cake', 'couch', 'keyboard', 'sink', 'scissors') # 17 +all_classes = ( + 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', + 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', + 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'kite', 'skateboard', + 'surfboard', 'bottle', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', + 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'pizza', 'donut', + 'cake', 'chair', 'couch', 'bed', 'toilet', 'tv', 'laptop', 'mouse', + 'remote', 'keyboard', 'microwave', 'oven', 'toaster', 'sink', + 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'toothbrush') # 65 + +train_metainfo = dict(classes=base_classes) +test_metainfo = dict( + classes=all_classes, + base_classes=base_classes, + novel_classes=novel_classes) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='CocoDataset', + metainfo=train_metainfo, + data_root=data_root, + ann_file='annotations/instances_train2017_seen_2.json', + data_prefix=dict(img='train2017/'), + return_classes=True, + filter_cfg=dict(filter_empty_gt=False, min_size=32), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type='CocoDataset', + metainfo=test_metainfo, + data_root=data_root, + ann_file='annotations/instances_val2017_all_2.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + return_classes=True, + )) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='OVCocoMetric', + ann_file=data_root + 'annotations/instances_val2017_all_2.json', + metric='bbox', + format_only=False) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.00005, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + # 'language_model': dict(lr_mult=0), + })) + +# learning policy +max_epochs = 12 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +default_hooks = dict( + checkpoint=dict( + max_keep_ckpts=1, save_best='coco/novel_ap50', rule='greater')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/coco/grounding_dino_swin-t_finetune_16xb4_1x_sft_coco.py b/mmdetection/configs/mm_grounding_dino/coco/grounding_dino_swin-t_finetune_16xb4_1x_sft_coco.py new file mode 100644 index 00000000..5505df58 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/coco/grounding_dino_swin-t_finetune_16xb4_1x_sft_coco.py @@ -0,0 +1,93 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/coco/' + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=20, # ======= important ===== + label_map_file='data/coco/annotations/coco2017_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='ODVGDataset', + need_text=False, + data_root=data_root, + ann_file='annotations/instances_train2017_od.json', + label_map_file='annotations/coco2017_label_map.json', + data_prefix=dict(img='train2017/'), + return_classes=True, + filter_cfg=dict(filter_empty_gt=False, min_size=32), + pipeline=train_pipeline)) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.00005, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + 'language_model': dict(lr_mult=0.0), + })) + +# learning policy +max_epochs = 12 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +default_hooks = dict(checkpoint=dict(max_keep_ckpts=1, save_best='auto')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/dataset_prepare.md b/mmdetection/configs/mm_grounding_dino/dataset_prepare.md new file mode 100644 index 00000000..af60a8bf --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/dataset_prepare.md @@ -0,0 +1,1193 @@ +# Data Prepare and Process + +## MM-GDINO-T Pre-train Dataset + +For the MM-GDINO-T model, we provide a total of 5 different data combination pre-training configurations. The data is trained in a progressive accumulation manner, so users can prepare it according to their actual needs. + +### 1 Objects365v1 + +The corresponding training config is [grounding_dino_swin-t_pretrain_obj365](./grounding_dino_swin-t_pretrain_obj365.py) + +Objects365v1 can be downloaded from [opendatalab](https://opendatalab.com/OpenDataLab/Objects365_v1). It offers two methods of download: CLI and SDK. + +After downloading and unzipping, place the dataset or create a symbolic link to the `data/objects365v1` directory. The directory structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── objects365v1 +│ │ ├── objects365_train.json +│ │ ├── objects365_val.json +│ │ ├── train +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── test +``` + +Then, use [coco2odvg.py](../../tools/dataset_converters/coco2odvg.py) to convert it into the ODVG format required for training. + +```shell +python tools/dataset_converters/coco2odvg.py data/objects365v1/objects365_train.json -d o365v1 +``` + +After the program runs successfully, it will create two new files, `o365v1_train_od.json` and `o365v1_label_map.json`, in the `data/objects365v1` directory. The complete structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── objects365v1 +│ │ ├── objects365_train.json +│ │ ├── objects365_val.json +│ │ ├── o365v1_train_od.json +│ │ ├── o365v1_label_map.json +│ │ ├── train +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── test +``` + +### 2 COCO 2017 + +The above configuration will evaluate the performance on the COCO 2017 dataset during the training process. Therefore, it is necessary to prepare the COCO 2017 dataset. You can download it from the [COCO](https://cocodataset.org/) official website or from [opendatalab](https://opendatalab.com/OpenDataLab/COCO_2017). + +After downloading and unzipping, place the dataset or create a symbolic link to the `data/coco` directory. The directory structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +### 3 GoldG + +After downloading the dataset, you can start training with the [grounding_dino_swin-t_pretrain_obj365_goldg](./grounding_dino_swin-t_pretrain_obj365_goldg.py) configuration. + +The GoldG dataset includes the `GQA` and `Flickr30k` datasets, which are part of the MixedGrounding dataset mentioned in the GLIP paper, excluding the COCO dataset. The download links are [mdetr_annotations](https://huggingface.co/GLIPModel/GLIP/tree/main/mdetr_annotations), and the specific files currently needed are `mdetr_annotations/final_mixed_train_no_coco.json` and `mdetr_annotations/final_flickr_separateGT_train.json`. + +Then download the [GQA images](https://nlp.stanford.edu/data/gqa/images.zip). After downloading and unzipping, place the dataset or create a symbolic link to them in the `data/gqa` directory, with the following directory structure: + +```text +mmdetection +├── configs +├── data +│ ├── gqa +| | ├── final_mixed_train_no_coco.json +│ │ ├── images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +Then download the [Flickr30k images](http://shannon.cs.illinois.edu/DenotationGraph/). You need to apply for access to this dataset and then download it using the provided link. After downloading and unzipping, place the dataset or create a symbolic link to them in the `data/flickr30k_entities` directory, with the following directory structure: + +```text +mmdetection +├── configs +├── data +│ ├── flickr30k_entities +│ │ ├── final_flickr_separateGT_train.json +│ │ ├── flickr30k_images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +For the GQA dataset, you need to use [goldg2odvg.py](../../tools/dataset_converters/goldg2odvg.py) to convert it into the ODVG format required for training: + +```shell +python tools/dataset_converters/goldg2odvg.py data/gqa/final_mixed_train_no_coco.json +``` + +After the program has run, a new file `final_mixed_train_no_coco_vg.json` will be created in the `data/gqa` directory, with the complete structure as follows: + +```text +mmdetection +├── configs +├── data +│ ├── gqa +| | ├── final_mixed_train_no_coco.json +| | ├── final_mixed_train_no_coco_vg.json +│ │ ├── images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +For the Flickr30k dataset, you need to use [goldg2odvg.py](../../tools/dataset_converters/goldg2odvg.py) to convert it into the ODVG format required for training: + +```shell +python tools/dataset_converters/goldg2odvg.py data/flickr30k_entities/final_flickr_separateGT_train.json +``` + +After the program has run, a new file `final_flickr_separateGT_train_vg.json` will be created in the `data/flickr30k_entities` directory, with the complete structure as follows: + +```text +mmdetection +├── configs +├── data +│ ├── flickr30k_entities +│ │ ├── final_flickr_separateGT_train.json +│ │ ├── final_flickr_separateGT_train_vg.json +│ │ ├── flickr30k_images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 4 GRIT-20M + +The corresponding training configuration is [grounding_dino_swin-t_pretrain_obj365_goldg_grit9m](./grounding_dino_swin-t_pretrain_obj365_goldg_grit9m.py). + +The GRIT dataset can be downloaded using the img2dataset package from [GRIT](https://huggingface.co/datasets/zzliang/GRIT#download-image). By default, the dataset size is 1.1T, and downloading and processing it may require at least 2T of disk space, depending on your available storage capacity. After downloading, the dataset is in its original format, which includes: + +```text +mmdetection +├── configs +├── data +│ ├── grit_raw +│ │ ├── 00000_stats.json +│ │ ├── 00000.parquet +│ │ ├── 00000.tar +│ │ ├── 00001_stats.json +│ │ ├── 00001.parquet +│ │ ├── 00001.tar +│ │ ├── ... +``` + +After downloading, further format processing is required: + +```shell +python tools/dataset_converters/grit_processing.py data/grit_raw data/grit_processed +``` + +The processed format is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── grit_processed +│ │ ├── annotations +│ │ │ ├── 00000.json +│ │ │ ├── 00001.json +│ │ │ ├── ... +│ │ ├── images +│ │ │ ├── 00000 +│ │ │ │ ├── 000000000.jpg +│ │ │ │ ├── 000000003.jpg +│ │ │ │ ├── 000000004.jpg +│ │ │ │ ├── ... +│ │ │ ├── 00001 +│ │ │ ├── ... +``` + +As for the GRIT dataset, you need to use [grit2odvg.py](../../tools/dataset_converters/grit2odvg.py) to convert it to the format of ODVG: + +```shell +python tools/dataset_converters/grit2odvg.py data/grit_processed/ +``` + +After the program has run, a new file `grit20m_vg.json` will be created in the `data/grit_processed` directory, which has about 9M data, with the complete structure as follows: + +```text +mmdetection +├── configs +├── data +│ ├── grit_processed +| | ├── grit20m_vg.json +│ │ ├── annotations +│ │ │ ├── 00000.json +│ │ │ ├── 00001.json +│ │ │ ├── ... +│ │ ├── images +│ │ │ ├── 00000 +│ │ │ │ ├── 000000000.jpg +│ │ │ │ ├── 000000003.jpg +│ │ │ │ ├── 000000004.jpg +│ │ │ │ ├── ... +│ │ │ ├── 00001 +│ │ │ ├── ... +``` + +### 5 V3Det + +The corresponding training configurations are: + +- [grounding_dino_swin-t_pretrain_obj365_goldg_v3det](./grounding_dino_swin-t_pretrain_obj365_goldg_v3det.py) +- [grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det](./grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det.py) + +The V3Det dataset can be downloaded from [opendatalab](https://opendatalab.com/V3Det/V3Det). After downloading and unzipping, place the dataset or create a symbolic link to it in the `data/v3det` directory, with the following directory structure: + +```text +mmdetection +├── configs +├── data +│ ├── v3det +│ │ ├── annotations +│ │ | ├── v3det_2023_v1_train.json +│ │ ├── images +│ │ │ ├── a00000066 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +Then use [coco2odvg.py](../../tools/dataset_converters/coco2odvg.py) to convert it into the ODVG format required for training: + +```shell +python tools/dataset_converters/coco2odvg.py data/v3det/annotations/v3det_2023_v1_train.json -d v3det +``` + +After the program has run, two new files `v3det_2023_v1_train_od.json` and `v3det_2023_v1_label_map.json` will be created in the `data/v3det/annotations` directory, with the complete structure as follows: + +```text +mmdetection +├── configs +├── data +│ ├── v3det +│ │ ├── annotations +│ │ | ├── v3det_2023_v1_train.json +│ │ | ├── v3det_2023_v1_train_od.json +│ │ | ├── v3det_2023_v1_label_map.json +│ │ ├── images +│ │ │ ├── a00000066 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 6 Data Splitting and Visualization + +Considering that users need to prepare many datasets, which is inconvenient for confirming images and annotations before training, we provide a data splitting and visualization tool. This tool can split the dataset into a tiny version and then use a visualization script to check the correctness of the images and labels. + +1. Splitting the Dataset + +The script is located [here](../../tools/misc/split_odvg.py). Taking `Object365 v1` as an example, the command to split the dataset is as follows: + +```shell +python tools/misc/split_odvg.py data/object365_v1/ o365v1_train_od.json train your_output_dir --label-map-file o365v1_label_map.json -n 200 +``` + +After running the above script, it will create a folder structure in the `your_output_dir` directory identical to `data/object365_v1/`, but it will only save 200 training images and their corresponding json files for convenient user review. + +2. Visualizing the Original Dataset + +The script is located [here](../../tools/analysis_tools/browse_grounding_raw.py). Taking `Object365 v1` as an example, the command to visualize the dataset is as follows: + +```shell +python tools/analysis_tools/browse_grounding_raw.py data/object365_v1/ o365v1_train_od.json train --label-map-file o365v1_label_map.json -o your_output_dir --not-show +``` + +After running the above script, it will generate images in the `your_output_dir` directory that include both the pictures and their labels, making it convenient for users to review. + +3. Visualizing the Output Dataset + +The script is located [here](../../tools/analysis_tools/browse_grounding_dataset.py). Users can use this script to view the results of the dataset output, including the results of data augmentation. Taking `Object365 v1` as an example, the command to visualize the dataset is as follows: + +```shell +python tools/analysis_tools/browse_grounding_dataset.py configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py -o your_output_dir --not-show +``` + +After running the above script, it will generate images in the `your_output_dir` directory that include both the pictures and their labels, making it convenient for users to review. + +## MM-GDINO-L Pre-training Data Preparation and Processing + +### 1 Object365 v2 + +Objects365_v2 can be downloaded from [opendatalab](https://opendatalab.com/OpenDataLab/Objects365). It offers two download methods: CLI and SDK. + +After downloading and unzipping, place the dataset or create a symbolic link to it in the `data/objects365v2` directory, with the following directory structure: + +```text +mmdetection +├── configs +├── data +│ ├── objects365v2 +│ │ ├── annotations +│ │ │ ├── zhiyuan_objv2_train.json +│ │ ├── train +│ │ │ ├── patch0 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +Since some category names in Objects365v2 are incorrect, it is necessary to correct them first. + +```shell +python tools/dataset_converters/fix_o365_names.py +``` + +A new annotation file `zhiyuan_objv2_train_fixname.json` will be generated in the `data/objects365v2/annotations` directory. + +Then use [coco2odvg.py](../../tools/dataset_converters/coco2odvg.py) to convert it into the ODVG format required for training: + +```shell +python tools/dataset_converters/coco2odvg.py data/objects365v2/annotations/zhiyuan_objv2_train_fixname.json -d o365v2 +``` + +After the program has run, two new files `zhiyuan_objv2_train_fixname_od.json` and `o365v2_label_map.json` will be created in the `data/objects365v2` directory, with the complete structure as follows: + +```text +mmdetection +├── configs +├── data +│ ├── objects365v2 +│ │ ├── annotations +│ │ │ ├── zhiyuan_objv2_train.json +│ │ │ ├── zhiyuan_objv2_train_fixname.json +│ │ │ ├── zhiyuan_objv2_train_fixname_od.json +│ │ │ ├── o365v2_label_map.json +│ │ ├── train +│ │ │ ├── patch0 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 2 OpenImages v6 + +OpenImages v6 can be downloaded from the [official website](https://storage.googleapis.com/openimages/web/download_v6.html). Due to the large size of the dataset, it may take some time to download. After completion, the file structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── OpenImages +│ │ ├── annotations +| │ │ ├── oidv6-train-annotations-bbox.csv +| │ │ ├── class-descriptions-boxable.csv +│ │ ├── OpenImages +│ │ │ ├── train +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +Then use [openimages2odvg.py](../../tools/dataset_converters/openimages2odvg.py) to convert it into the ODVG format required for training: + +```shell +python tools/dataset_converters/openimages2odvg.py data/OpenImages/annotations +``` + +After the program has run, two new files `oidv6-train-annotation_od.json` and `openimages_label_map.json` will be created in the `data/OpenImages/annotations` directory, with the complete structure as follows: + +```text +mmdetection +├── configs +├── data +│ ├── OpenImages +│ │ ├── annotations +| │ │ ├── oidv6-train-annotations-bbox.csv +| │ │ ├── class-descriptions-boxable.csv +| │ │ ├── oidv6-train-annotations_od.json +| │ │ ├── openimages_label_map.json +│ │ ├── OpenImages +│ │ │ ├── train +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 3 V3Det + +Referring to the data preparation section of the previously mentioned MM-GDINO-T pre-training data preparation and processing, the complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── v3det +│ │ ├── annotations +│ │ | ├── v3det_2023_v1_train.json +│ │ | ├── v3det_2023_v1_train_od.json +│ │ | ├── v3det_2023_v1_label_map.json +│ │ ├── images +│ │ │ ├── a00000066 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 4 LVIS 1.0 + +Please refer to the `2 LVIS 1.0` section of the later `Fine-tuning Dataset Preparation`. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── lvis_v1_train.json +│ │ │ ├── lvis_v1_val.json +│ │ │ ├── lvis_v1_train_od.json +│ │ │ ├── lvis_v1_label_map.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +### 5 COCO2017 OD + +You can refer to the earlier section `MM-GDINO-T Pre-training Data Preparation and Processing` for data preparation. For convenience in subsequent processing, please create a symbolic link or move the downloaded [mdetr_annotations](https://huggingface.co/GLIPModel/GLIP/tree/main/mdetr_annotations) folder to the `data/coco` path. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── ... +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +Due to some overlap between COCO2017 train and RefCOCO/RefCOCO+/RefCOCOg/gRefCOCO val, if not removed in advance, there will be data leakage when evaluating RefExp. + +```shell +python tools/dataset_converters/remove_cocotrain2017_from_refcoco.py data/coco/mdetr_annotations data/coco/annotations/instances_train2017.json +``` + +A new file `instances_train2017_norefval.json` will be created in the `data/coco/annotations` directory. Finally, use [coco2odvg.py](../../tools/dataset_converters/coco2odvg.py) to convert it into the ODVG format required for training: + +```shell +python tools/dataset_converters/coco2odvg.py data/coco/annotations/instances_train2017_norefval.json -d coco +``` + +Two new files `instances_train2017_norefval_od.json` and `coco_label_map.json` will be created in the `data/coco/annotations` directory, with the complete structure as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2017_norefval_od.json +│ │ │ ├── coco_label_map.json +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── ... +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +Note: There are 15,000 images that overlap between the COCO2017 train and LVIS 1.0 val datasets. Therefore, if the COCO2017 train dataset is used in training, the evaluation results of LVIS 1.0 val will have a data leakage issue. However, LVIS 1.0 minival does not have this problem. + +### 6 GoldG + +Please refer to the section on `MM-GDINO-T Pre-training Data Preparation and Processing`. + +```text +mmdetection +├── configs +├── data +│ ├── flickr30k_entities +│ │ ├── final_flickr_separateGT_train.json +│ │ ├── final_flickr_separateGT_train_vg.json +│ │ ├── flickr30k_images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ ├── gqa +| | ├── final_mixed_train_no_coco.json +| | ├── final_mixed_train_no_coco_vg.json +│ │ ├── images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 7 COCO2014 VG + +MDetr provides a Phrase Grounding version of the COCO2014 train annotations. The original annotation file is named `final_mixed_train.json`, and similar to the previous structure, the file structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── mdetr_annotations +│ │ │ ├── final_mixed_train.json +│ │ │ ├── ... +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +We can extract the COCO portion of the data from `final_mixed_train.json`. + +```shell +python tools/dataset_converters/extract_coco_from_mixed.py data/coco/mdetr_annotations/final_mixed_train.json +``` + +A new file named `final_mixed_train_only_coco.json` will be created in the `data/coco/mdetr_annotations` directory. Finally, use [goldg2odvg.py](../../tools/dataset_converters/goldg2odvg.py) to convert it into the ODVG format required for training: + +```shell +python tools/dataset_converters/goldg2odvg.py data/coco/mdetr_annotations/final_mixed_train_only_coco.json +``` + +A new file named `final_mixed_train_only_coco_vg.json` will be created in the `data/coco/mdetr_annotations` directory, with the complete structure as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── mdetr_annotations +│ │ │ ├── final_mixed_train.json +│ │ │ ├── final_mixed_train_only_coco.json +│ │ │ ├── final_mixed_train_only_coco_vg.json +│ │ │ ├── ... +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +Note: COCO2014 train and COCO2017 val do not have duplicate images, so there is no need to worry about data leakage issues in COCO evaluation. + +### 8 Referring Expression Comprehension + +There are a total of 4 datasets included. For data preparation, please refer to the `Fine-tuning Dataset Preparation` section. + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_refcoco+_testA.json +│ │ │ ├── finetune_refcoco+_testB.json +│ │ │ ├── finetune_refcocog_test.json +│ │ │ ├── finetune_refcoco_train_vg.json +│ │ │ ├── finetune_refcoco+_train_vg.json +│ │ │ ├── finetune_refcocog_train_vg.json +│ │ │ ├── finetune_grefcoco_train_vg.json +``` + +### 9 GRIT-20M + +Please refer to the `MM-GDINO-T Pre-training Data Preparation and Processing` section. + +## Preparation of Evaluation Dataset + +### 1 COCO 2017 + +The data preparation process is consistent with the previous descriptions, and the final structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +### 2 LVIS 1.0 + +The LVIS 1.0 val dataset includes both mini and full versions. The significance of the mini version is: + +1. The full LVIS val evaluation dataset is quite large, and conducting an evaluation with it can take a significant amount of time. +2. In the full LVIS val dataset, there are 15,000 images from the COCO2017 train dataset. If a user has used the COCO2017 data for training, there can be a data leakage issue when evaluating on the full LVIS val dataset + +The LVIS 1.0 dataset contains images that are exactly the same as the COCO2017 dataset, with the addition of new annotations. You can download the minival annotation file from [here](https://huggingface.co/GLIPModel/GLIP/blob/main/lvis_v1_minival_inserted_image_name.json), and the val 1.0 annotation file from [here](https://huggingface.co/GLIPModel/GLIP/blob/main/lvis_od_val.json). The final structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +### 3 ODinW + +ODinW, which stands for Object Detection in the Wild, is a dataset used to evaluate the generalization capability of grounding pre-trained models in different real-world scenarios. It consists of two subsets, ODinW13 and ODinW35, representing datasets composed of 13 and 35 different datasets, respectively. You can download it from [here](https://huggingface.co/GLIPModel/GLIP/tree/main/odinw_35), and then unzip each file. The final structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── odinw +│ │ ├── AerialMaritimeDrone +│ │ | |── large +│ │ | | ├── test +│ │ | | ├── train +│ │ | | ├── valid +│ │ | |── tiled +│ │ ├── AmericanSignLanguageLetters +│ │ ├── Aquarium +│ │ ├── BCCD +│ │ ├── ... +``` + +When evaluating ODinW35, custom prompts are required. Therefore, it's necessary to preprocess the annotated JSON files in advance. You can use the [override_category.py](./odinw/override_category.py) script for this purpose. After processing, it will generate new annotation files without overwriting the original ones. + +```shell +python configs/mm_grounding_dino/odinw/override_category.py data/odinw/ +``` + +### 4 DOD + +DOD stands for Described Object Detection, and it is introduced in the paper titled [Described Object Detection: Liberating Object Detection with Flexible Expressions](https://arxiv.org/abs/2307.12813). You can download the dataset from [here](https://github.com/shikras/d-cube?tab=readme-ov-file). The final structure of the dataset is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── d3 +│ │ ├── d3_images +│ │ ├── d3_json +│ │ ├── d3_pkl +``` + +### 5 Flickr30k Entities + +In the previous GoldG data preparation section, we downloaded the necessary files for training with Flickr30k. For evaluation, you will need 2 JSON files, which you can download from [here](https://huggingface.co/GLIPModel/GLIP/blob/main/mdetr_annotations/final_flickr_separateGT_val.json) and [here](https://huggingface.co/GLIPModel/GLIP/blob/main/mdetr_annotations/final_flickr_separateGT_test.json). The final structure of the dataset is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── flickr30k_entities +│ │ ├── final_flickr_separateGT_train.json +│ │ ├── final_flickr_separateGT_val.json +│ │ ├── final_flickr_separateGT_test.json +│ │ ├── final_flickr_separateGT_train_vg.json +│ │ ├── flickr30k_images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 6 Referring Expression Comprehension + +Referential Expression Comprehension includes 4 datasets: RefCOCO, RefCOCO+, RefCOCOg, and gRefCOCO. The images used in these 4 datasets are from COCO2014 train, similar to COCO2017. You can download the images from the official COCO website or opendatalab. The annotations can be directly downloaded from [here](https://huggingface.co/GLIPModel/GLIP/tree/main/mdetr_annotations). The mdetr_annotations folder contains a large number of annotations, so you can choose to download only the JSON files you need. The final structure of the dataset is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_refcoco+_testA.json +│ │ │ ├── finetune_refcoco+_testB.json +│ │ │ ├── finetune_refcocog_test.json +│ │ │ ├── finetune_refcocog_test.json +``` + +Please note that gRefCOCO is introduced in [GREC: Generalized Referring Expression Comprehension](https://arxiv.org/abs/2308.16182) and is not available in the `mdetr_annotations` folder. You will need to handle it separately. Here are the specific steps: + +1. Download [gRefCOCO](https://github.com/henghuiding/gRefCOCO?tab=readme-ov-file) and unzip it into the `data/coco/` folder. + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ ├── grefs +│ │ │ ├── grefs(unc).json +│ │ │ ├── instances.json +``` + +2. Convert to COCO format + +You can use the official [conversion script](https://github.com/henghuiding/gRefCOCO/blob/b4b1e55b4d3a41df26d6b7d843ea011d581127d4/mdetr/scripts/fine-tuning/grefexp_coco_format.py) provided by gRefCOCO. Please note that you need to uncomment line 161 and comment out line 160 in the script to obtain the full JSON file. + +```shell +# you need to clone the official repo +git clone https://github.com/henghuiding/gRefCOCO.git +cd gRefCOCO/mdetr +python scripts/fine-tuning/grefexp_coco_format.py --data_path ../../data/coco/grefs --out_path ../../data/coco/mdetr_annotations/ --coco_path ../../data/coco +``` + +Four JSON files will be generated in the `data/coco/mdetr_annotations/` folder. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_grefcoco_train.json +│ │ │ ├── finetune_grefcoco_val.json +│ │ │ ├── finetune_grefcoco_testA.json +│ │ │ ├── finetune_grefcoco_testB.json +``` + +## Fine-Tuning Dataset Preparation + +### 1 COCO 2017 + +COCO is the most commonly used dataset in the field of object detection, and we aim to explore its fine-tuning modes more comprehensively. From current developments, there are a total of three fine-tuning modes: + +1. Closed-set fine-tuning, where the description on the text side cannot be modified after fine-tuning, transforms into a closed-set algorithm. This approach maximizes performance on COCO but loses generality. +2. Open-set continued pretraining fine-tuning involves using pretraining methods consistent with the COCO dataset. There are two approaches to this: the first is to reduce the learning rate and fix certain modules, fine-tuning only on the COCO dataset; the second is to mix COCO data with some of the pre-trained data. The goal of both approaches is to improve performance on the COCO dataset as much as possible without compromising generalization. +3. Open-vocabulary fine-tuning involves adopting a common practice in the OVD (Open-Vocabulary Detection) domain. It divides COCO categories into base classes and novel classes. During training, fine-tuning is performed only on the base classes, while evaluation is conducted on both base and novel classes. This approach allows for the assessment of COCO OVD capabilities, with the goal of improving COCO dataset performance without compromising generalization as much as possible. + +\*\*(1) Closed-set Fine-tuning \*\* + +This section does not require data preparation; you can directly use the data you have prepared previously. + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +**(2) Open-set Continued Pretraining Fine-tuning** +To use this approach, you need to convert the COCO training data into ODVG format. You can use the following command for conversion: + +```shell +python tools/dataset_converters/coco2odvg.py data/coco/annotations/instances_train2017.json -d coco +``` + +This will generate new files, `instances_train2017_od.json` and `coco2017_label_map.json`, in the `data/coco/annotations/` directory. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_train2017_od.json +│ │ │ ├── coco2017_label_map.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +Once you have obtained the data, you can choose whether to perform individual pretraining or mixed pretraining. + +**(3) Open-vocabulary Fine-tuning** +For this approach, you need to convert the COCO training data into OVD (Open-Vocabulary Detection) format. You can use the following command for conversion: + +```shell +python tools/dataset_converters/coco2ovd.py data/coco/ +``` + +This will generate new files, `instances_val2017_all_2.json` and `instances_val2017_seen_2.json`, in the `data/coco/annotations/` directory. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_train2017_od.json +│ │ │ ├── instances_val2017_all_2.json +│ │ │ ├── instances_val2017_seen_2.json +│ │ │ ├── coco2017_label_map.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +You can then proceed to train and test directly using the [configuration](coco/grounding_dino_swin-t_finetune_16xb4_1x_coco_48_17.py). + +### 2 LVIS 1.0 + +LVIS is a dataset that includes 1,203 classes, making it a valuable dataset for fine-tuning. Due to its large number of classes, it's not feasible to perform closed-set fine-tuning. Therefore, we can only use open-set continued pretraining fine-tuning and open-vocabulary fine-tuning on LVIS. + +You need to prepare the LVIS training JSON files first, which you can download from [here](https://www.lvisdataset.org/dataset). We only need `lvis_v1_train.json` and `lvis_v1_val.json`. After downloading them, place them in the `data/coco/annotations/` directory, and then run the following command: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── lvis_v1_train.json +│ │ │ ├── lvis_v1_val.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +(1) Open-set continued pretraining fine-tuning + +Convert to ODVG format using the following command: + +```shell +python tools/dataset_converters/lvis2odvg.py data/coco/annotations/lvis_v1_train.json +``` + +It will generate new files, `lvis_v1_train_od.json` and `lvis_v1_label_map.json`, in the `data/coco/annotations/` directory, and the complete dataset structure will look like this: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── lvis_v1_train.json +│ │ │ ├── lvis_v1_val.json +│ │ │ ├── lvis_v1_train_od.json +│ │ │ ├── lvis_v1_label_map.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +You can directly use the provided [configuration](lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis.py) for training and testing, or you can modify the configuration to mix it with some of the pretraining datasets as needed. + +**(2) Open Vocabulary Fine-tuning** + +Convert to OVD format using the following command: + +```shell +python tools/dataset_converters/lvis2ovd.py data/coco/ +``` + +New `lvis_v1_train_od_norare.json` and `lvis_v1_label_map_norare.json` will be generated under `data/coco/annotations/`, and the complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── lvis_v1_train.json +│ │ │ ├── lvis_v1_val.json +│ │ │ ├── lvis_v1_train_od.json +│ │ │ ├── lvis_v1_label_map.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ │ ├── lvis_v1_train_od_norare.json +│ │ │ ├── lvis_v1_label_map_norare.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +然Then you can directly use the [configuration](lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis_866_337.py) for training and testing. + +### 3 RTTS + +RTTS is a foggy weather dataset, which contains 4,322 foggy images, including five classes: bicycle, bus, car, motorbike, and person. It can be downloaded from [here](https://drive.google.com/file/d/15Ei1cHGVqR1mXFep43BO7nkHq1IEGh1e/view), and then extracted to the `data/RTTS/` folder. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── RTTS +│ │ ├── annotations_json +│ │ ├── annotations_xml +│ │ ├── ImageSets +│ │ ├── JPEGImages +``` + +### 4 RUOD + +RUOD is an underwater object detection dataset. You can download it from [here](https://drive.google.com/file/d/1hxtbdgfVveUm_DJk5QXkNLokSCTa_E5o/view), and then extract it to the `data/RUOD/` folder. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── RUOD +│ │ ├── Environment_pic +│ │ ├── Environmet_ANN +│ │ ├── RUOD_ANN +│ │ ├── RUOD_pic +``` + +### 5 Brain Tumor + +Brain Tumor is a 2D detection dataset in the medical field. You can download it from [here](https://universe.roboflow.com/roboflow-100/brain-tumor-m2pbp/dataset/2), please make sure to choose the `COCO JSON` format. Then extract it to the `data/brain_tumor_v2/` folder. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── brain_tumor_v2 +│ │ ├── test +│ │ ├── train +│ │ ├── valid +``` + +### 6 Cityscapes + +Cityscapes is an urban street scene dataset. You can download it from [here](https://www.cityscapes-dataset.com/) or from opendatalab, and then extract it to the `data/cityscapes/` folder. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── cityscapes +│ │ ├── annotations +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +``` + +After downloading, you can use the [cityscapes.py](../../tools/dataset_converters/cityscapes.py) script to generate the required JSON format. + +```shell +python tools/dataset_converters/cityscapes.py data/cityscapes/ +``` + +Three new JSON files will be generated in the annotations directory. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── cityscapes +│ │ ├── annotations +│ │ │ ├── instancesonly_filtered_gtFine_train.json +│ │ │ ├── instancesonly_filtered_gtFine_val.json +│ │ │ ├── instancesonly_filtered_gtFine_test.json +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +``` + +### 7 People in Painting + +People in Painting is an oil painting dataset that you can download from [here](https://universe.roboflow.com/roboflow-100/people-in-paintings/dataset/2). Please make sure to choose the `COCO JSON` format. After downloading, unzip the dataset to the `data/people_in_painting_v2/` folder. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── people_in_painting_v2 +│ │ ├── test +│ │ ├── train +│ │ ├── valid +``` + +### 8 Referring Expression Comprehension + +Fine-tuning for Referential Expression Comprehension is similar to what was described earlier and includes four datasets. The dataset preparation for evaluation has already been organized. The complete dataset structure is as follows: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_refcoco+_testA.json +│ │ │ ├── finetune_refcoco+_testB.json +│ │ │ ├── finetune_refcocog_test.json +│ │ │ ├── finetune_refcocog_test.json +``` + +Then we need to convert it to the required ODVG format. Please use the [refcoco2odvg.py](../../tools/dataset_converters/refcoco2odvg.py) script to perform the conversion. + +```shell +python tools/dataset_converters/refcoco2odvg.py data/coco/mdetr_annotations +``` + +The converted dataset structure will include 4 new JSON files in the `data/coco/mdetr_annotations` directory. Here is the structure of the converted dataset: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_refcoco+_testA.json +│ │ │ ├── finetune_refcoco+_testB.json +│ │ │ ├── finetune_refcocog_test.json +│ │ │ ├── finetune_refcoco_train_vg.json +│ │ │ ├── finetune_refcoco+_train_vg.json +│ │ │ ├── finetune_refcocog_train_vg.json +│ │ │ ├── finetune_grefcoco_train_vg.json +``` diff --git a/mmdetection/configs/mm_grounding_dino/dataset_prepare_zh-CN.md b/mmdetection/configs/mm_grounding_dino/dataset_prepare_zh-CN.md new file mode 100644 index 00000000..10520b02 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/dataset_prepare_zh-CN.md @@ -0,0 +1,1194 @@ +# 数据准备和处理 + +## MM-GDINO-T 预训练数据准备和处理 + +MM-GDINO-T 模型中我们一共提供了 5 种不同数据组合的预训练配置,数据采用逐步累加的方式进行训练,因此用户可以根据自己的实际需求准备数据。 + +### 1 Objects365 v1 + +对应的训练配置为 [grounding_dino_swin-t_pretrain_obj365](./grounding_dino_swin-t_pretrain_obj365.py) + +Objects365_v1 可以从 [opendatalab](https://opendatalab.com/OpenDataLab/Objects365_v1) 下载,其提供了 CLI 和 SDK 两者下载方式。 + +下载并解压后,将其放置或者软链接到 `data/objects365v1` 目录下,目录结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── objects365v1 +│ │ ├── objects365_train.json +│ │ ├── objects365_val.json +│ │ ├── train +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── test +``` + +然后使用 [coco2odvg.py](../../tools/dataset_converters/coco2odvg.py) 转换为训练所需的 ODVG 格式: + +```shell +python tools/dataset_converters/coco2odvg.py data/objects365v1/objects365_train.json -d o365v1 +``` + +程序运行完成后会在 `data/objects365v1` 目录下创建 `o365v1_train_od.json` 和 `o365v1_label_map.json` 两个新文件,完整结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── objects365v1 +│ │ ├── objects365_train.json +│ │ ├── objects365_val.json +│ │ ├── o365v1_train_od.json +│ │ ├── o365v1_label_map.json +│ │ ├── train +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── test +``` + +### 2 COCO 2017 + +上述配置在训练过程中会评估 COCO 2017 数据集的性能,因此需要准备 COCO 2017 数据集。你可以从 [COCO](https://cocodataset.org/) 官网下载或者从 [opendatalab](https://opendatalab.com/OpenDataLab/COCO_2017) 下载 + +下载并解压后,将其放置或者软链接到 `data/coco` 目录下,目录结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +### 3 GoldG + +下载该数据集后就可以训练 [grounding_dino_swin-t_pretrain_obj365_goldg](./grounding_dino_swin-t_pretrain_obj365_goldg.py) 配置了。 + +GoldG 数据集包括 `GQA` 和 `Flickr30k` 两个数据集,来自 GLIP 论文中提到的 MixedGrounding 数据集,其排除了 COCO 数据集。下载链接为 [mdetr_annotations](https://huggingface.co/GLIPModel/GLIP/tree/main/mdetr_annotations),我们目前需要的是 `mdetr_annotations/final_mixed_train_no_coco.json` 和 `mdetr_annotations/final_flickr_separateGT_train.json` 文件。 + +然后下载 [GQA images](https://nlp.stanford.edu/data/gqa/images.zip) 图片。下载并解压后,将其放置或者软链接到 `data/gqa` 目录下,目录结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── gqa +| | ├── final_mixed_train_no_coco.json +│ │ ├── images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +然后下载 [Flickr30k images](http://shannon.cs.illinois.edu/DenotationGraph/) 图片。这个数据下载需要先申请,再获得下载链接后才可以下载。下载并解压后,将其放置或者软链接到 `data/flickr30k_entities` 目录下,目录结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── flickr30k_entities +│ │ ├── final_flickr_separateGT_train.json +│ │ ├── flickr30k_images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +对于 GQA 数据集,你需要使用 [goldg2odvg.py](../../tools/dataset_converters/goldg2odvg.py) 转换为训练所需的 ODVG 格式: + +```shell +python tools/dataset_converters/goldg2odvg.py data/gqa/final_mixed_train_no_coco.json +``` + +程序运行完成后会在 `data/gqa` 目录下创建 `final_mixed_train_no_coco_vg.json` 新文件,完整结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── gqa +| | ├── final_mixed_train_no_coco.json +| | ├── final_mixed_train_no_coco_vg.json +│ │ ├── images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +对于 Flickr30k 数据集,你需要使用 [goldg2odvg.py](../../tools/dataset_converters/goldg2odvg.py) 转换为训练所需的 ODVG 格式: + +```shell +python tools/dataset_converters/goldg2odvg.py data/flickr30k_entities/final_flickr_separateGT_train.json +``` + +程序运行完成后会在 `data/flickr30k_entities` 目录下创建 `final_flickr_separateGT_train_vg.json` 新文件,完整结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── flickr30k_entities +│ │ ├── final_flickr_separateGT_train.json +│ │ ├── final_flickr_separateGT_train_vg.json +│ │ ├── flickr30k_images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 4 GRIT-20M + +对应的训练配置为 [grounding_dino_swin-t_pretrain_obj365_goldg_grit9m](./grounding_dino_swin-t_pretrain_obj365_goldg_grit9m.py) + +GRIT数据集可以从 [GRIT](https://huggingface.co/datasets/zzliang/GRIT#download-image) 中使用 img2dataset 包下载,默认指令下载后数据集大小为 1.1T,下载和处理预估需要至少 2T 硬盘空间,可根据硬盘容量酌情下载。下载后原始格式为: + +```text +mmdetection +├── configs +├── data +│ ├── grit_raw +│ │ ├── 00000_stats.json +│ │ ├── 00000.parquet +│ │ ├── 00000.tar +│ │ ├── 00001_stats.json +│ │ ├── 00001.parquet +│ │ ├── 00001.tar +│ │ ├── ... +``` + +下载后需要对格式进行进一步处理: + +```shell +python tools/dataset_converters/grit_processing.py data/grit_raw data/grit_processed +``` + +处理后的格式为: + +```text +mmdetection +├── configs +├── data +│ ├── grit_processed +│ │ ├── annotations +│ │ │ ├── 00000.json +│ │ │ ├── 00001.json +│ │ │ ├── ... +│ │ ├── images +│ │ │ ├── 00000 +│ │ │ │ ├── 000000000.jpg +│ │ │ │ ├── 000000003.jpg +│ │ │ │ ├── 000000004.jpg +│ │ │ │ ├── ... +│ │ │ ├── 00001 +│ │ │ ├── ... +``` + +对于 GRIT 数据集,你需要使用 [grit2odvg.py](../../tools/dataset_converters/grit2odvg.py) 转化成需要的 ODVG 格式: + +```shell +python tools/dataset_converters/grit2odvg.py data/grit_processed/ +``` + +程序运行完成后会在 `data/grit_processed` 目录下创建 `grit20m_vg.json` 新文件,大概包含 9M 条数据,完整结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── grit_processed +| | ├── grit20m_vg.json +│ │ ├── annotations +│ │ │ ├── 00000.json +│ │ │ ├── 00001.json +│ │ │ ├── ... +│ │ ├── images +│ │ │ ├── 00000 +│ │ │ │ ├── 000000000.jpg +│ │ │ │ ├── 000000003.jpg +│ │ │ │ ├── 000000004.jpg +│ │ │ │ ├── ... +│ │ │ ├── 00001 +│ │ │ ├── ... +``` + +### 5 V3Det + +对应的训练配置为 + +- [grounding_dino_swin-t_pretrain_obj365_goldg_v3det](./grounding_dino_swin-t_pretrain_obj365_goldg_v3det.py) +- [grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det](./grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det.py) + +V3Det 数据集下载可以从 [opendatalab](https://opendatalab.com/V3Det/V3Det) 下载,下载并解压后,将其放置或者软链接到 `data/v3det` 目录下,目录结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── v3det +│ │ ├── annotations +│ │ | ├── v3det_2023_v1_train.json +│ │ ├── images +│ │ │ ├── a00000066 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +然后使用 [coco2odvg.py](../../tools/dataset_converters/coco2odvg.py) 转换为训练所需的 ODVG 格式: + +```shell +python tools/dataset_converters/coco2odvg.py data/v3det/annotations/v3det_2023_v1_train.json -d v3det +``` + +程序运行完成后会在 `data/v3det/annotations` 目录下创建目录下创建 `v3det_2023_v1_train_od.json` 和 `v3det_2023_v1_label_map.json` 两个新文件,完整结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── v3det +│ │ ├── annotations +│ │ | ├── v3det_2023_v1_train.json +│ │ | ├── v3det_2023_v1_train_od.json +│ │ | ├── v3det_2023_v1_label_map.json +│ │ ├── images +│ │ │ ├── a00000066 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 6 数据切分和可视化 + +考虑到用户需要准备的数据集过多,不方便对图片和标注进行训练前确认,因此我们提供了一个数据切分和可视化的工具,可以将数据集切分为 tiny 版本,然后使用可视化脚本查看图片和标签正确性。 + +1. 切分数据集 + +脚本位于 [这里](../../tools/misc/split_odvg.py), 以 `Object365 v1` 为例,切分数据集的命令如下: + +```shell +python tools/misc/split_odvg.py data/object365_v1/ o365v1_train_od.json train your_output_dir --label-map-file o365v1_label_map.json -n 200 +``` + +上述脚本运行后会在 `your_output_dir` 目录下创建和 `data/object365_v1/` 一样的文件夹结构,但是只会保存 200 张训练图片和对应的 json,方便用户查看。 + +2. 可视化原始数据集 + +脚本位于 [这里](../../tools/analysis_tools/browse_grounding_raw.py), 以 `Object365 v1` 为例,可视化数据集的命令如下: + +```shell +python tools/analysis_tools/browse_grounding_raw.py data/object365_v1/ o365v1_train_od.json train --label-map-file o365v1_label_map.json -o your_output_dir --not-show +``` + +上述脚本运行后会在 `your_output_dir` 目录下生成同时包括图片和标签的图片,方便用户查看。 + +3. 可视化 dataset 输出的数据集 + +脚本位于 [这里](../../tools/analysis_tools/browse_grounding_dataset.py), 用户可以通过该脚本查看 dataset 输出的结果即包括了数据增强的结果。 以 `Object365 v1` 为例,可视化数据集的命令如下: + +```shell +python tools/analysis_tools/browse_grounding_dataset.py configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py -o your_output_dir --not-show +``` + +上述脚本运行后会在 `your_output_dir` 目录下生成同时包括图片和标签的图片,方便用户查看。 + +## MM-GDINO-L 预训练数据准备和处理 + +### 1 Object365 v2 + +Objects365_v2 可以从 [opendatalab](https://opendatalab.com/OpenDataLab/Objects365) 下载,其提供了 CLI 和 SDK 两者下载方式。 + +下载并解压后,将其放置或者软链接到 `data/objects365v2` 目录下,目录结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── objects365v2 +│ │ ├── annotations +│ │ │ ├── zhiyuan_objv2_train.json +│ │ ├── train +│ │ │ ├── patch0 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +由于 objects365v2 类别中有部分类名是错误的,因此需要先进行修正。 + +```shell +python tools/dataset_converters/fix_o365_names.py +``` + +会在 `data/objects365v2/annotations` 下生成新的标注文件 `zhiyuan_objv2_train_fixname.json`。 + +然后使用 [coco2odvg.py](../../tools/dataset_converters/coco2odvg.py) 转换为训练所需的 ODVG 格式: + +```shell +python tools/dataset_converters/coco2odvg.py data/objects365v2/annotations/zhiyuan_objv2_train_fixname.json -d o365v2 +``` + +程序运行完成后会在 `data/objects365v2` 目录下创建 `zhiyuan_objv2_train_fixname_od.json` 和 `o365v2_label_map.json` 两个新文件,完整结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── objects365v2 +│ │ ├── annotations +│ │ │ ├── zhiyuan_objv2_train.json +│ │ │ ├── zhiyuan_objv2_train_fixname.json +│ │ │ ├── zhiyuan_objv2_train_fixname_od.json +│ │ │ ├── o365v2_label_map.json +│ │ ├── train +│ │ │ ├── patch0 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 2 OpenImages v6 + +OpenImages v6 可以从 [官网](https://storage.googleapis.com/openimages/web/download_v6.html) 下载,由于数据集比较大,需要花费一定的时间,下载完成后文件结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── OpenImages +│ │ ├── annotations +| │ │ ├── oidv6-train-annotations-bbox.csv +| │ │ ├── class-descriptions-boxable.csv +│ │ ├── OpenImages +│ │ │ ├── train +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +然后使用 [openimages2odvg.py](../../tools/dataset_converters/openimages2odvg.py) 转换为训练所需的 ODVG 格式: + +```shell +python tools/dataset_converters/openimages2odvg.py data/OpenImages/annotations +``` + +程序运行完成后会在 `data/OpenImages/annotations` 目录下创建 `oidv6-train-annotation_od.json` 和 `openimages_label_map.json` 两个新文件,完整结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── OpenImages +│ │ ├── annotations +| │ │ ├── oidv6-train-annotations-bbox.csv +| │ │ ├── class-descriptions-boxable.csv +| │ │ ├── oidv6-train-annotations_od.json +| │ │ ├── openimages_label_map.json +│ │ ├── OpenImages +│ │ │ ├── train +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 3 V3Det + +参见前面的 MM-GDINO-T 预训练数据准备和处理 数据准备部分,完整数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── v3det +│ │ ├── annotations +│ │ | ├── v3det_2023_v1_train.json +│ │ | ├── v3det_2023_v1_train_od.json +│ │ | ├── v3det_2023_v1_label_map.json +│ │ ├── images +│ │ │ ├── a00000066 +│ │ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 4 LVIS 1.0 + +参见后面的 `微调数据集准备` 的 `2 LVIS 1.0` 部分。完整数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── lvis_v1_train.json +│ │ │ ├── lvis_v1_val.json +│ │ │ ├── lvis_v1_train_od.json +│ │ │ ├── lvis_v1_label_map.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +### 5 COCO2017 OD + +数据准备可以参考前面的 `MM-GDINO-T 预训练数据准备和处理` 部分。为了方便后续处理,请将下载的 [mdetr_annotations](https://huggingface.co/GLIPModel/GLIP/tree/main/mdetr_annotations) 文件夹软链接或者移动到 `data/coco` 路径下 +完整数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── ... +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +由于 COCO2017 train 和 RefCOCO/RefCOCO+/RefCOCOg/gRefCOCO val 中存在部分重叠,如果不提前移除,在评测 RefExp 时候会存在数据泄露。 + +```shell +python tools/dataset_converters/remove_cocotrain2017_from_refcoco.py data/coco/mdetr_annotations data/coco/annotations/instances_train2017.json +``` + +会在 `data/coco/annotations` 目录下创建 `instances_train2017_norefval.json` 新文件。最后使用 [coco2odvg.py](../../tools/dataset_converters/coco2odvg.py) 转换为训练所需的 ODVG 格式: + +```shell +python tools/dataset_converters/coco2odvg.py data/coco/annotations/instances_train2017_norefval.json -d coco +``` + +会在 `data/coco/annotations` 目录下创建 `instances_train2017_norefval_od.json` 和 `coco_label_map.json` 两个新文件,完整结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2017_norefval_od.json +│ │ │ ├── coco_label_map.json +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── ... +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +注意: COCO2017 train 和 LVIS 1.0 val 数据集有 15000 张图片重复,因此一旦在训练中使用了 COCO2017 train,那么 LVIS 1.0 val 的评测结果就存在数据泄露问题,LVIS 1.0 minival 没有这个问题。 + +### 6 GoldG + +参见 MM-GDINO-T 预训练数据准备和处理 部分 + +```text +mmdetection +├── configs +├── data +│ ├── flickr30k_entities +│ │ ├── final_flickr_separateGT_train.json +│ │ ├── final_flickr_separateGT_train_vg.json +│ │ ├── flickr30k_images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ ├── gqa +| | ├── final_mixed_train_no_coco.json +| | ├── final_mixed_train_no_coco_vg.json +│ │ ├── images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 7 COCO2014 VG + +MDetr 中提供了 COCO2014 train 的 Phrase Grounding 版本标注, 最原始标注文件为 `final_mixed_train.json`,和之前类似,文件结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── mdetr_annotations +│ │ │ ├── final_mixed_train.json +│ │ │ ├── ... +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +我们可以从 `final_mixed_train.json` 中提取出 COCO 部分数据 + +```shell +python tools/dataset_converters/extract_coco_from_mixed.py data/coco/mdetr_annotations/final_mixed_train.json +``` + +会在 `data/coco/mdetr_annotations` 目录下创建 `final_mixed_train_only_coco.json` 新文件,最后使用 [goldg2odvg.py](../../tools/dataset_converters/goldg2odvg.py) 转换为训练所需的 ODVG 格式: + +```shell +python tools/dataset_converters/goldg2odvg.py data/coco/mdetr_annotations/final_mixed_train_only_coco.json +``` + +会在 `data/coco/mdetr_annotations` 目录下创建 `final_mixed_train_only_coco_vg.json` 新文件,完整结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── mdetr_annotations +│ │ │ ├── final_mixed_train.json +│ │ │ ├── final_mixed_train_only_coco.json +│ │ │ ├── final_mixed_train_only_coco_vg.json +│ │ │ ├── ... +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +注意: COCO2014 train 和 COCO2017 val 没有重复图片,因此不用担心 COCO 评测的数据泄露问题。 + +### 8 Referring Expression Comprehension + +其一共包括 4 个数据集。数据准备部分请参见 微调数据集准备 部分。 + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_refcoco+_testA.json +│ │ │ ├── finetune_refcoco+_testB.json +│ │ │ ├── finetune_refcocog_test.json +│ │ │ ├── finetune_refcoco_train_vg.json +│ │ │ ├── finetune_refcoco+_train_vg.json +│ │ │ ├── finetune_refcocog_train_vg.json +│ │ │ ├── finetune_grefcoco_train_vg.json +``` + +### 9 GRIT-20M + +参见 MM-GDINO-T 预训练数据准备和处理 部分 + +## 评测数据集准备 + +### 1 COCO 2017 + +数据准备流程和前面描述一致,最终结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +### 2 LVIS 1.0 + +LVIS 1.0 val 数据集包括 mini 和全量两个版本,mini 版本存在的意义是: + +1. LVIS val 全量评测数据集比较大,评测一次需要比较久的时间 +2. LVIS val 全量数据集中包括了 15000 张 COCO2017 train, 如果用户使用了 COCO2017 数据进行训练,那么将存在数据泄露问题 + +LVIS 1.0 图片和 COCO2017 数据集图片完全一样,只是提供了新的标注而已,minival 标注文件可以从 [这里](https://huggingface.co/GLIPModel/GLIP/blob/main/lvis_v1_minival_inserted_image_name.json)下载, val 1.0 标注文件可以从 [这里](https://huggingface.co/GLIPModel/GLIP/blob/main/lvis_od_val.json) 下载。 最终结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +### 3 ODinW + +ODinw 全称为 Object Detection in the Wild,是用于验证 grounding 预训练模型在不同实际场景中的泛化能力的数据集,其包括两个子集,分别是 ODinW13 和 ODinW35,代表是由 13 和 35 个数据集组成的。你可以从 [这里](https://huggingface.co/GLIPModel/GLIP/tree/main/odinw_35)下载,然后对每个文件进行解压,最终结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── odinw +│ │ ├── AerialMaritimeDrone +│ │ | |── large +│ │ | | ├── test +│ │ | | ├── train +│ │ | | ├── valid +│ │ | |── tiled +│ │ ├── AmericanSignLanguageLetters +│ │ ├── Aquarium +│ │ ├── BCCD +│ │ ├── ... +``` + +在评测 ODinW3535 时候由于需要自定义 prompt,因此需要提前对标注的 json 文件进行处理,你可以使用 [override_category.py](./odinw/override_category.py) 脚本进行处理,处理后会生成新的标注文件,不会覆盖原先的标注文件。 + +```shell +python configs/mm_grounding_dino/odinw/override_category.py data/odinw/ +``` + +### 4 DOD + +DOD 来自 [Described Object Detection: Liberating Object Detection with Flexible Expressions](https://arxiv.org/abs/2307.12813)。其数据集可以从 [这里](https://github.com/shikras/d-cube?tab=readme-ov-file#download)下载,最终的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── d3 +│ │ ├── d3_images +│ │ ├── d3_json +│ │ ├── d3_pkl +``` + +### 5 Flickr30k Entities + +在前面 GoldG 数据准备章节中我们已经下载了 Flickr30k 训练所需文件,评估所需的文件是 2 个 json 文件,你可以从 [这里](https://huggingface.co/GLIPModel/GLIP/blob/main/mdetr_annotations/final_flickr_separateGT_val.json) 和 [这里](https://huggingface.co/GLIPModel/GLIP/blob/main/mdetr_annotations/final_flickr_separateGT_test.json)下载,最终的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── flickr30k_entities +│ │ ├── final_flickr_separateGT_train.json +│ │ ├── final_flickr_separateGT_val.json +│ │ ├── final_flickr_separateGT_test.json +│ │ ├── final_flickr_separateGT_train_vg.json +│ │ ├── flickr30k_images +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +``` + +### 6 Referring Expression Comprehension + +指代性表达式理解包括 4 个数据集: RefCOCO, RefCOCO+, RefCOCOg, gRefCOCO。这 4 个数据集所采用的图片都来自于 COCO2014 train,和 COCO2017 类似,你可以从 COCO 官方或者 opendatalab 中下载,而标注可以直接从 [这里](https://huggingface.co/GLIPModel/GLIP/tree/main/mdetr_annotations) 下载,mdetr_annotations 文件夹里面包括了其他大量的标注,你如果觉得数量过多,可以只下载所需要的几个 json 文件即可。最终的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_refcoco+_testA.json +│ │ │ ├── finetune_refcoco+_testB.json +│ │ │ ├── finetune_refcocog_test.json +│ │ │ ├── finetune_refcocog_test.json +``` + +注意 gRefCOCO 是在 [GREC: Generalized Referring Expression Comprehension](https://arxiv.org/abs/2308.16182) 被提出,并不在 `mdetr_annotations` 文件夹中,需要自行处理。具体步骤为: + +1. 下载 [gRefCOCO](https://github.com/henghuiding/gRefCOCO?tab=readme-ov-file#grefcoco-dataset-download),并解压到 data/coco/ 文件夹中 + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ ├── grefs +│ │ │ ├── grefs(unc).json +│ │ │ ├── instances.json +``` + +2. 转换为 coco 格式 + +你可以使用 gRefCOCO 官方提供的[转换脚本](https://github.com/henghuiding/gRefCOCO/blob/b4b1e55b4d3a41df26d6b7d843ea011d581127d4/mdetr/scripts/fine-tuning/grefexp_coco_format.py)。注意需要将被注释的 161 行打开,并注释 160 行才可以得到全量的 json 文件。 + +```shell +# 需要克隆官方 repo +git clone https://github.com/henghuiding/gRefCOCO.git +cd gRefCOCO/mdetr +python scripts/fine-tuning/grefexp_coco_format.py --data_path ../../data/coco/grefs --out_path ../../data/coco/mdetr_annotations/ --coco_path ../../data/coco +``` + +会在 `data/coco/mdetr_annotations/` 文件夹中生成 4 个 json 文件,完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_grefcoco_train.json +│ │ │ ├── finetune_grefcoco_val.json +│ │ │ ├── finetune_grefcoco_testA.json +│ │ │ ├── finetune_grefcoco_testB.json +``` + +## 微调数据集准备 + +### 1 COCO 2017 + +COCO 是检测领域最常用的数据集,我们希望能够更充分探索其微调模式。从目前发展来看,一共有 3 种微调方式: + +1. 闭集微调,即微调后文本端将无法修改描述,转变为闭集算法,在 COCO 上性能能够最大化,但是失去了通用性。 +2. 开集继续预训练微调,即对 COCO 数据集采用和预训练一致的预训练手段。此时有两种做法,第一种是降低学习率并固定某些模块,仅仅在 COCO 数据上预训练,第二种是将 COCO 数据和部分预训练数据混合一起训练,两种方式的目的都是在尽可能不降低泛化性时提高 COCO 数据集性能 +3. 开放词汇微调,即采用 OVD 领域常用做法,将 COCO 类别分成 base 类和 novel 类,训练时候仅仅在 base 类上进行,评测在 base 和 novel 类上进行。这种方式可以验证 COCO OVD 能力,目的也是在尽可能不降低泛化性时提高 COCO 数据集性能 + +**(1) 闭集微调** + +这个部分无需准备数据,直接用之前的数据即可。 + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +**(2) 开集继续预训练微调** +这种方式需要将 COCO 训练数据转换为 ODVG 格式,你可以使用如下命令转换: + +```shell +python tools/dataset_converters/coco2odvg.py data/coco/annotations/instances_train2017.json -d coco +``` + +会在 `data/coco/annotations/` 下生成新的 `instances_train2017_od.json` 和 `coco2017_label_map.json`,完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_train2017_od.json +│ │ │ ├── coco2017_label_map.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +在得到数据后,你可以自行选择单独预习还是混合预训练方式。 + +**(3) 开放词汇微调** +这种方式需要将 COCO 训练数据转换为 OVD 格式,你可以使用如下命令转换: + +```shell +python tools/dataset_converters/coco2ovd.py data/coco/ +``` + +会在 `data/coco/annotations/` 下生成新的 `instances_val2017_all_2.json` 和 `instances_val2017_seen_2.json`,完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_train2017_od.json +│ │ │ ├── instances_val2017_all_2.json +│ │ │ ├── instances_val2017_seen_2.json +│ │ │ ├── coco2017_label_map.json +│ │ │ ├── instances_val2017.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +然后可以直接使用 [配置](coco/grounding_dino_swin-t_finetune_16xb4_1x_coco_48_17.py) 进行训练和测试。 + +### 2 LVIS 1.0 + +LVIS 是一个包括 1203 类的数据集,同时也是一个长尾联邦数据集,对其进行微调很有意义。 由于其类别过多,我们无法对其进行闭集微调,因此只能采用开集继续预训练微调和开放词汇微调。 + +你需要先准备好 LVIS 训练 JSON 文件,你可以从 [这里](https://www.lvisdataset.org/dataset) 下载,我们只需要 `lvis_v1_train.json` 和 `lvis_v1_val.json`,然后将其放到 `data/coco/annotations/` 下,然后运行如下命令: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── lvis_v1_train.json +│ │ │ ├── lvis_v1_val.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +(1) 开集继续预训练微调 + +使用如下命令转换为 ODVG 格式: + +```shell +python tools/dataset_converters/lvis2odvg.py data/coco/annotations/lvis_v1_train.json +``` + +会在 `data/coco/annotations/` 下生成新的 `lvis_v1_train_od.json` 和 `lvis_v1_label_map.json`,完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── lvis_v1_train.json +│ │ │ ├── lvis_v1_val.json +│ │ │ ├── lvis_v1_train_od.json +│ │ │ ├── lvis_v1_label_map.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +然后可以直接使用 [配置](lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis.py) 进行训练测试,或者你修改配置将其和部分预训练数据集混合使用。 + +**(2) 开放词汇微调** + +使用如下命令转换为 OVD 格式: + +```shell +python tools/dataset_converters/lvis2ovd.py data/coco/ +``` + +会在 `data/coco/annotations/` 下生成新的 `lvis_v1_train_od_norare.json` 和 `lvis_v1_label_map_norare.json`,完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── lvis_v1_train.json +│ │ │ ├── lvis_v1_val.json +│ │ │ ├── lvis_v1_train_od.json +│ │ │ ├── lvis_v1_label_map.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── lvis_v1_minival_inserted_image_name.json +│ │ │ ├── lvis_od_val.json +│ │ │ ├── lvis_v1_train_od_norare.json +│ │ │ ├── lvis_v1_label_map_norare.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +``` + +然后可以直接使用 [配置](lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis_866_337.py) 进行训练测试 + +### 3 RTTS + +RTTS 是一个浓雾天气数据集,该数据集包含 4,322 张雾天图像,包含五个类:自行车 (bicycle)、公共汽车 (bus)、汽车 (car)、摩托车 (motorbike) 和人 (person)。可以从 [这里](https://drive.google.com/file/d/15Ei1cHGVqR1mXFep43BO7nkHq1IEGh1e/view)下载, 然后解压到 `data/RTTS/` 文件夹中。完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── RTTS +│ │ ├── annotations_json +│ │ ├── annotations_xml +│ │ ├── ImageSets +│ │ ├── JPEGImages +``` + +### 4 RUOD + +RUOD 是一个水下目标检测数据集,你可以从 [这里](https://drive.google.com/file/d/1hxtbdgfVveUm_DJk5QXkNLokSCTa_E5o/view)下载, 然后解压到 `data/RUOD/` 文件夹中。完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── RUOD +│ │ ├── Environment_pic +│ │ ├── Environmet_ANN +│ │ ├── RUOD_ANN +│ │ ├── RUOD_pic +``` + +### 5 Brain Tumor + +Brain Tumor 是一个医学领域的 2d 检测数据集,你可以从 [这里](https://universe.roboflow.com/roboflow-100/brain-tumor-m2pbp/dataset/2)下载, 请注意选择 `COCO JSON` 格式。然后解压到 `data/brain_tumor_v2/` 文件夹中。完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── brain_tumor_v2 +│ │ ├── test +│ │ ├── train +│ │ ├── valid +``` + +### 6 Cityscapes + +Cityscapes 是一个城市街景数据集,你可以从 [这里](https://www.cityscapes-dataset.com/) 或者 opendatalab 中下载, 然后解压到 `data/cityscapes/` 文件夹中。完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── cityscapes +│ │ ├── annotations +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +``` + +在下载后,然后使用 [cityscapes.py](../../tools/dataset_converters/cityscapes.py) 脚本生成我们所需要的 json 格式 + +```shell +python tools/dataset_converters/cityscapes.py data/cityscapes/ +``` + +会在 annotations 中生成 3 个新的 json 文件。完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── cityscapes +│ │ ├── annotations +│ │ │ ├── instancesonly_filtered_gtFine_train.json +│ │ │ ├── instancesonly_filtered_gtFine_val.json +│ │ │ ├── instancesonly_filtered_gtFine_test.json +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +``` + +### 7 People in Painting + +People in Painting 是一个油画数据集,你可以从 [这里](https://universe.roboflow.com/roboflow-100/people-in-paintings/dataset/2), 请注意选择 `COCO JSON` 格式。然后解压到 `data/people_in_painting_v2/` 文件夹中。完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── people_in_painting_v2 +│ │ ├── test +│ │ ├── train +│ │ ├── valid +``` + +### 8 Referring Expression Comprehension + +指代性表达式理解的微调和前面一样,也是包括 4 个数据集,在评测数据准备阶段已经全部整理好了,完整的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_refcoco+_testA.json +│ │ │ ├── finetune_refcoco+_testB.json +│ │ │ ├── finetune_refcocog_test.json +│ │ │ ├── finetune_refcocog_test.json +``` + +然后我们需要将其转换为所需的 ODVG 格式,请使用 [refcoco2odvg.py](../../tools/dataset_converters/refcoco2odvg.py) 脚本转换, + +```shell +python tools/dataset_converters/refcoco2odvg.py data/coco/mdetr_annotations +``` + +会在 `data/coco/mdetr_annotations` 中生成新的 4 个 json 文件。 转换后的数据集结构如下: + +```text +mmdetection +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ │ ├── instances_train2014.json +│ │ ├── train2017 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── val2017 +│ │ │ ├── xxxx.jpg +│ │ │ ├── ... +│ │ ├── train2014 +│ │ │ ├── xxx.jpg +│ │ │ ├── ... +│ │ ├── mdetr_annotations +│ │ │ ├── final_refexp_val.json +│ │ │ ├── finetune_refcoco_testA.json +│ │ │ ├── finetune_refcoco_testB.json +│ │ │ ├── finetune_refcoco+_testA.json +│ │ │ ├── finetune_refcoco+_testB.json +│ │ │ ├── finetune_refcocog_test.json +│ │ │ ├── finetune_refcoco_train_vg.json +│ │ │ ├── finetune_refcoco+_train_vg.json +│ │ │ ├── finetune_refcocog_train_vg.json +│ │ │ ├── finetune_grefcoco_train_vg.json +``` diff --git a/mmdetection/configs/mm_grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py b/mmdetection/configs/mm_grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py new file mode 100644 index 00000000..e59a0a52 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py @@ -0,0 +1,78 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/d3/' + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', 'sent_ids')) +] + +# -------------------------------------------------# +val_dataset_full = dict( + type='DODDataset', + data_root=data_root, + ann_file='d3_json/d3_full_annotations.json', + data_prefix=dict(img='d3_images/', anno='d3_pkl'), + pipeline=test_pipeline, + test_mode=True, + backend_args=None, + return_classes=True) + +val_evaluator_full = dict( + type='DODCocoMetric', + ann_file=data_root + 'd3_json/d3_full_annotations.json') + +# -------------------------------------------------# +val_dataset_pres = dict( + type='DODDataset', + data_root=data_root, + ann_file='d3_json/d3_pres_annotations.json', + data_prefix=dict(img='d3_images/', anno='d3_pkl'), + pipeline=test_pipeline, + test_mode=True, + backend_args=None, + return_classes=True) +val_evaluator_pres = dict( + type='DODCocoMetric', + ann_file=data_root + 'd3_json/d3_pres_annotations.json') + +# -------------------------------------------------# +val_dataset_abs = dict( + type='DODDataset', + data_root=data_root, + ann_file='d3_json/d3_abs_annotations.json', + data_prefix=dict(img='d3_images/', anno='d3_pkl'), + pipeline=test_pipeline, + test_mode=True, + backend_args=None, + return_classes=True) +val_evaluator_abs = dict( + type='DODCocoMetric', + ann_file=data_root + 'd3_json/d3_abs_annotations.json') + +# -------------------------------------------------# +datasets = [val_dataset_full, val_dataset_pres, val_dataset_abs] +dataset_prefixes = ['FULL', 'PRES', 'ABS'] +metrics = [val_evaluator_full, val_evaluator_pres, val_evaluator_abs] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mm_grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_parallel_dod.py b/mmdetection/configs/mm_grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_parallel_dod.py new file mode 100644 index 00000000..3d680091 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/dod/grounding_dino_swin-t_pretrain_zeroshot_parallel_dod.py @@ -0,0 +1,3 @@ +_base_ = 'grounding_dino_swin-t_pretrain_zeroshot_concat_dod.py' + +model = dict(test_cfg=dict(chunked_size=1)) diff --git a/mmdetection/configs/mm_grounding_dino/flickr30k/grounding_dino_swin-t-pretrain_flickr30k.py b/mmdetection/configs/mm_grounding_dino/flickr30k/grounding_dino_swin-t-pretrain_flickr30k.py new file mode 100644 index 00000000..e9eb783d --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/flickr30k/grounding_dino_swin-t-pretrain_flickr30k.py @@ -0,0 +1,57 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +dataset_type = 'Flickr30kDataset' +data_root = 'data/flickr30k_entities/' + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive', 'phrase_ids', 'phrases')) +] + +dataset_Flickr30k_val = dict( + type=dataset_type, + data_root=data_root, + ann_file='final_flickr_separateGT_val.json', + data_prefix=dict(img='flickr30k_images/'), + pipeline=test_pipeline, +) + +dataset_Flickr30k_test = dict( + type=dataset_type, + data_root=data_root, + ann_file='final_flickr_separateGT_test.json', + data_prefix=dict(img='flickr30k_images/'), + pipeline=test_pipeline, +) + +val_evaluator_Flickr30k = dict(type='Flickr30kMetric') + +test_evaluator_Flickr30k = dict(type='Flickr30kMetric') + +# ----------Config---------- # +dataset_prefixes = ['Flickr30kVal', 'Flickr30kTest'] +datasets = [dataset_Flickr30k_val, dataset_Flickr30k_test] +metrics = [val_evaluator_Flickr30k, test_evaluator_Flickr30k] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-b_pretrain_all.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-b_pretrain_all.py new file mode 100644 index 00000000..eff58bba --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-b_pretrain_all.py @@ -0,0 +1,335 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-b_pretrain_obj365_goldg_v3det/grounding_dino_swin-b_pretrain_obj365_goldg_v3de-f83eef00.pth' # noqa + +model = dict( + use_autocast=True, + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=True, + convert_weights=True, + frozen_stages=-1, + init_cfg=None), + neck=dict(in_channels=[256, 512, 1024]), +) + +o365v1_od_dataset = dict( + type='ODVGDataset', + data_root='data/objects365v1/', + ann_file='o365v1_train_odvg.json', + label_map_file='o365v1_label_map.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None, +) + +flickr30k_dataset = dict( + type='ODVGDataset', + data_root='data/flickr30k_entities/', + ann_file='final_flickr_separateGT_train_vg.json', + label_map_file=None, + data_prefix=dict(img='flickr30k_images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +gqa_dataset = dict( + type='ODVGDataset', + data_root='data/gqa/', + ann_file='final_mixed_train_no_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +v3d_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/V3Det/annotations/v3det_2023_v1_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] +v3det_dataset = dict( + type='ODVGDataset', + data_root='data/V3Det/', + ann_file='annotations/v3det_2023_v1_train_od.json', + label_map_file='annotations/v3det_2023_v1_label_map.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + need_text=False, # change this + pipeline=v3d_train_pipeline, + return_classes=True, + backend_args=None) + +grit_dataset = dict( + type='ODVGDataset', + data_root='grit_processed/', + ann_file='grit20m_vg.json', + label_map_file=None, + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +# --------------------------- lvis od dataset--------------------------- +lvis_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/coco/annotations/lvis_v1_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] +lvis_dataset = dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='annotations/lvis_v1_train_od.json', + label_map_file='annotations/lvis_v1_label_map.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + need_text=False, # change this + pipeline=lvis_train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- coco2017 od dataset--------------------------- +coco2017_train_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='annotations/instance_train2017_norefval_od.json', + label_map_file='annotations/coco2017_label_map.json', + data_prefix=dict(img='train2017'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- coco2014 vg dataset--------------------------- +coco2014_vg_dataset = dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/final_mixed_train_only_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +# --------------------------- refcoco vg dataset--------------------------- +refcoco_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/finetune_refcoco_train_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- refcoco+ vg dataset--------------------------- +refcoco_plus_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/finetune_refcoco+_train_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- refcocog vg dataset--------------------------- +refcocog_dataset = dict( + type='RepeatDataset', + times=3, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/finetune_refcocog_train_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- grefcoco vg dataset--------------------------- +grefcoco_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/finetune_grefcoco_train_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- dataloader--------------------------- +train_dataloader = dict( + batch_size=4, + num_workers=4, + sampler=dict( + _delete_=True, + type='CustomSampleSizeSampler', + ratio_mode=True, + dataset_size=[-1, -1, 0.07, -1, -1, -1, -1, -1, -1, -1, -1, -1]), + dataset=dict(datasets=[ + o365v1_od_dataset, # 1.74M + v3det_dataset, # + grit_dataset, + lvis_dataset, + coco2017_train_dataset, # 0.12M + flickr30k_dataset, # 0.15M + gqa_dataset, # 0.62M + coco2014_vg_dataset, # 0.49M + refcoco_dataset, # 0.12M + refcoco_plus_dataset, # 0.12M + refcocog_dataset, # 0.08M + grefcoco_dataset, # 0.19M + ])) + +optim_wrapper = dict(optimizer=dict(lr=0.0001)) + +# learning policy +max_iter = 304680 +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=10000) + +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[228510], + gamma=0.1) +] + +default_hooks = dict( + checkpoint=dict(by_epoch=False, interval=10000, max_keep_ckpts=20)) +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-b_pretrain_obj365_goldg_v3det.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-b_pretrain_obj365_goldg_v3det.py new file mode 100644 index 00000000..743d02cf --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-b_pretrain_obj365_goldg_v3det.py @@ -0,0 +1,143 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window12_384_22k.pth' # noqa +model = dict( + use_autocast=True, + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=True, + convert_weights=True, + frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[256, 512, 1024]), +) + +o365v1_od_dataset = dict( + type='ODVGDataset', + data_root='data/objects365v1/', + ann_file='o365v1_train_odvg.json', + label_map_file='o365v1_label_map.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None, +) + +flickr30k_dataset = dict( + type='ODVGDataset', + data_root='data/flickr30k_entities/', + ann_file='final_flickr_separateGT_train_vg.json', + label_map_file=None, + data_prefix=dict(img='flickr30k_images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +gqa_dataset = dict( + type='ODVGDataset', + data_root='data/gqa/', + ann_file='final_mixed_train_no_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +v3d_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/V3Det/annotations/v3det_2023_v1_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] +v3det_dataset = dict( + type='ODVGDataset', + data_root='data/V3Det/', + ann_file='annotations/v3det_2023_v1_train_od.json', + label_map_file='annotations/v3det_2023_v1_label_map.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + need_text=False, # change this + pipeline=v3d_train_pipeline, + return_classes=True, + backend_args=None) + +train_dataloader = dict( + dataset=dict(datasets=[ + o365v1_od_dataset, flickr30k_dataset, gqa_dataset, v3det_dataset + ])) + +# learning policy +max_epochs = 18 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[13, 16], + gamma=0.1) +] + +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-l_pretrain_all.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-l_pretrain_all.py new file mode 100644 index 00000000..a17f2344 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-l_pretrain_all.py @@ -0,0 +1,540 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-l_pretrain_obj365_goldg/grounding_dino_swin-l_pretrain_obj365_goldg-34dcdc53.pth' # noqa + +num_levels = 5 +model = dict( + use_autocast=True, + num_feature_levels=num_levels, + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(0, 1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=True, + convert_weights=True, + frozen_stages=-1, + init_cfg=None), + neck=dict(in_channels=[192, 384, 768, 1536], num_outs=num_levels), + encoder=dict(layer_cfg=dict(self_attn_cfg=dict(num_levels=num_levels))), + decoder=dict(layer_cfg=dict(cross_attn_cfg=dict(num_levels=num_levels)))) + +# --------------------------- object365v2 od dataset--------------------------- +# objv2_backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/objects365v2/': 'yudong:s3://wangyudong/obj365_v2/', +# 'data/objects365v2/': 'yudong:s3://wangyudong/obj365_v2/' +# })) +objv2_backend_args = None + +objv2_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=objv2_backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/objects365v2/annotations/o365v2_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +o365v2_dataset = dict( + type='ODVGDataset', + data_root='data/objects365v2/', + ann_file='annotations/zhiyuan_objv2_train_od.json', + label_map_file='annotations/o365v2_label_map.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=objv2_train_pipeline, + return_classes=True, + need_text=False, + backend_args=None, +) + +# --------------------------- openimagev6 od dataset--------------------------- +# oi_backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +oi_backend_args = None + +oi_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=oi_backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/OpenImages/annotations/openimages_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +oiv6_dataset = dict( + type='ODVGDataset', + data_root='data/OpenImages/', + ann_file='annotations/oidv6-train-annotations_od.json', + label_map_file='annotations/openimages_label_map.json', + data_prefix=dict(img='OpenImages/train/'), + filter_cfg=dict(filter_empty_gt=False), + need_text=False, + pipeline=oi_train_pipeline, + return_classes=True, + backend_args=None) + +# --------------------------- v3det od dataset--------------------------- +v3d_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/V3Det/annotations/v3det_2023_v1_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] +v3det_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/V3Det/', + ann_file='annotations/v3det_2023_v1_train_od.json', + label_map_file='annotations/v3det_2023_v1_label_map.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + need_text=False, + pipeline=v3d_train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- lvis od dataset--------------------------- +lvis_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/coco/annotations/lvis_v1_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] +lvis_dataset = dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='annotations/lvis_v1_train_od.json', + label_map_file='annotations/lvis_v1_label_map.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + need_text=False, # change this + pipeline=lvis_train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- coco2017 od dataset--------------------------- +coco2017_train_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='annotations/instance_train2017_norefval_od.json', + label_map_file='annotations/coco2017_label_map.json', + data_prefix=dict(img='train2017'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- flickr30k vg dataset--------------------------- +flickr30k_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/flickr30k_entities/', + ann_file='final_flickr_separateGT_train_vg.json', + label_map_file=None, + data_prefix=dict(img='flickr30k_images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- gqa vg dataset--------------------------- +gqa_dataset = dict( + type='ODVGDataset', + data_root='data/gqa/', + ann_file='final_mixed_train_no_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +# --------------------------- coco2014 vg dataset--------------------------- +coco2014_vg_dataset = dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/final_mixed_train_only_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +# --------------------------- refcoco vg dataset--------------------------- +refcoco_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/finetune_refcoco_train_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- refcoco+ vg dataset--------------------------- +refcoco_plus_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/finetune_refcoco+_train_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- refcocog vg dataset--------------------------- +refcocog_dataset = dict( + type='RepeatDataset', + times=3, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/finetune_refcocog_train_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- grefcoco vg dataset--------------------------- +grefcoco_dataset = dict( + type='RepeatDataset', + times=2, + dataset=dict( + type='ODVGDataset', + data_root='data/coco/', + ann_file='mdetr_annotations/finetune_grefcoco_train_vg.json', + label_map_file=None, + data_prefix=dict(img='train2014'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None)) + +# --------------------------- grit vg dataset--------------------------- +# grit_backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/grit/': 'yichen:s3://chenyicheng/grit/', +# 'data/grit/': 'yichen:s3://chenyicheng/grit/' +# })) +grit_backend_args = None + +grit_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=grit_backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +grit_dataset = dict( + type='ODVGDataset', + data_root='data/grit/', + ann_file='grit20m_vg.json', + label_map_file=None, + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + pipeline=grit_train_pipeline, + return_classes=True, + backend_args=None) + +# --------------------------- dataloader--------------------------- +train_dataloader = dict( + batch_size=4, + num_workers=4, + sampler=dict( + _delete_=True, + type='CustomSampleSizeSampler', + ratio_mode=True, + # OD ~ 1.74+1.67*0.5+0.18*2+0.12*2+0.1=3.2 + # vg ~ 0.15*2+0.62*1+0.49*1+0.12*2+0.12*2+0.08*3+0.19*2+9*0.09=3.3 + dataset_size=[-1, 0.5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0.09]), + dataset=dict(datasets=[ + o365v2_dataset, # 1.74M + oiv6_dataset, # 1.67M + v3det_dataset, # 0.18M + coco2017_train_dataset, # 0.12M + lvis_dataset, # 0.1M + flickr30k_dataset, # 0.15M + gqa_dataset, # 0.62M + coco2014_vg_dataset, # 0.49M + refcoco_dataset, # 0.12M + refcoco_plus_dataset, # 0.12M + refcocog_dataset, # 0.08M + grefcoco_dataset, # 0.19M + grit_dataset # 9M + ])) + +# 4NODES * 8GPU +optim_wrapper = dict(optimizer=dict(lr=0.0001)) + +max_iter = 250000 +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=13000) + +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[210000], + gamma=0.1) +] + +default_hooks = dict( + checkpoint=dict(by_epoch=False, interval=13000, max_keep_ckpts=30)) +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-l_pretrain_obj365_goldg.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-l_pretrain_obj365_goldg.py new file mode 100644 index 00000000..85d43f96 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-l_pretrain_obj365_goldg.py @@ -0,0 +1,227 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa +num_levels = 5 +model = dict( + use_autocast=True, + num_feature_levels=num_levels, + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(0, 1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=True, + convert_weights=True, + frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[192, 384, 768, 1536], num_outs=num_levels), + encoder=dict(layer_cfg=dict(self_attn_cfg=dict(num_levels=num_levels))), + decoder=dict(layer_cfg=dict(cross_attn_cfg=dict(num_levels=num_levels)))) + +# --------------------------- object365v2 od dataset--------------------------- +# objv2_backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/objects365v2/': 'yudong:s3://wangyudong/obj365_v2/', +# 'data/objects365v2/': 'yudong:s3://wangyudong/obj365_v2/' +# })) +objv2_backend_args = None + +objv2_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=objv2_backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/objects365v2/annotations/o365v2_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +o365v2_dataset = dict( + type='ODVGDataset', + data_root='data/objects365v2/', + ann_file='annotations/zhiyuan_objv2_train_od.json', + label_map_file='annotations/o365v2_label_map.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=objv2_train_pipeline, + return_classes=True, + need_text=False, + backend_args=None, +) + +# --------------------------- openimagev6 od dataset--------------------------- +# oi_backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +oi_backend_args = None + +oi_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=oi_backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/OpenImages/annotations/openimages_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +oiv6_dataset = dict( + type='ODVGDataset', + data_root='data/OpenImages/', + ann_file='annotations/oidv6-train-annotations_od.json', + label_map_file='annotations/openimages_label_map.json', + data_prefix=dict(img='OpenImages/train/'), + filter_cfg=dict(filter_empty_gt=False), + need_text=False, + pipeline=oi_train_pipeline, + return_classes=True, + backend_args=None) + +flickr30k_dataset = dict( + type='ODVGDataset', + data_root='data/flickr30k_entities/', + ann_file='final_flickr_separateGT_train_vg.json', + label_map_file=None, + data_prefix=dict(img='flickr30k_images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +gqa_dataset = dict( + type='ODVGDataset', + data_root='data/gqa/', + ann_file='final_mixed_train_no_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +train_dataloader = dict( + dataset=dict(datasets=[ + o365v2_dataset, oiv6_dataset, flickr30k_dataset, gqa_dataset + ])) + +# 4Nodex8GPU +optim_wrapper = dict(optimizer=dict(lr=0.0002)) + +max_iter = 200000 +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=13000) + +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[156100], + gamma=0.5) +] + +default_hooks = dict( + checkpoint=dict(by_epoch=False, interval=13000, max_keep_ckpts=30)) +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_finetune_8xb4_20e_cat.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_finetune_8xb4_20e_cat.py new file mode 100644 index 00000000..bf3b3589 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_finetune_8xb4_20e_cat.py @@ -0,0 +1,102 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/cat/' +class_name = ('cat', ) +num_classes = len(class_name) +metainfo = dict(classes=class_name, palette=[(220, 20, 60)]) + +model = dict(bbox_head=dict(num_classes=num_classes)) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='CocoDataset', + data_root=data_root, + metainfo=metainfo, + return_classes=True, + pipeline=train_pipeline, + filter_cfg=dict(filter_empty_gt=False, min_size=32), + ann_file='annotations/trainval.json', + data_prefix=dict(img='images/'))) + +val_dataloader = dict( + dataset=dict( + metainfo=metainfo, + data_root=data_root, + ann_file='annotations/test.json', + data_prefix=dict(img='images/'))) + +test_dataloader = val_dataloader + +val_evaluator = dict(ann_file=data_root + 'annotations/test.json') +test_evaluator = val_evaluator + +max_epoch = 20 + +default_hooks = dict( + checkpoint=dict(interval=1, max_keep_ckpts=1, save_best='auto'), + logger=dict(type='LoggerHook', interval=5)) +train_cfg = dict(max_epochs=max_epoch, val_interval=1) + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epoch, + by_epoch=True, + milestones=[15], + gamma=0.1) +] + +optim_wrapper = dict( + optimizer=dict(lr=0.0001), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.0), + 'language_model': dict(lr_mult=0.0) + })) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py new file mode 100644 index 00000000..66060f45 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py @@ -0,0 +1,247 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_tiny_patch4_window7_224.pth' # noqa +lang_model_name = 'bert-base-uncased' + +model = dict( + type='GroundingDINO', + num_queries=900, + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=False, + ), + language_model=dict( + type='BertModel', + name=lang_model_name, + max_tokens=256, + pad_to_max=False, + use_sub_sentence_represent=True, + special_tokens_list=['[CLS]', '[SEP]', '.', '?'], + add_pooling_layer=False, + ), + backbone=dict( + type='SwinTransformer', + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=True, + convert_weights=True, + frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict( + type='ChannelMapper', + in_channels=[192, 384, 768], + kernel_size=1, + out_channels=256, + act_cfg=None, + bias=True, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + encoder=dict( + num_layers=6, + num_cp=6, + # visual layer config + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=2048, ffn_drop=0.0)), + # text layer config + text_layer_cfg=dict( + self_attn_cfg=dict(num_heads=4, embed_dims=256, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=1024, ffn_drop=0.0)), + # fusion layer config + fusion_layer_cfg=dict( + v_dim=256, + l_dim=256, + embed_dim=1024, + num_heads=4, + init_values=1e-4), + ), + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + # query self attention layer + self_attn_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + # cross attention layer query to text + cross_attn_text_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + # cross attention layer query to image + cross_attn_cfg=dict(embed_dims=256, num_heads=8, dropout=0.0), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=2048, ffn_drop=0.0)), + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, normalize=True, offset=0.0, temperature=20), + bbox_head=dict( + type='GroundingDINOHead', + num_classes=256, + sync_cls_avg_factor=True, + contrastive_cfg=dict(max_text_len=256, log_scale='auto', bias=True), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), # 2.0 in DeformDETR + loss_bbox=dict(type='L1Loss', loss_weight=5.0)), + dn_cfg=dict( # TODO: Move to model.train_cfg ? + label_noise_scale=0.5, + box_noise_scale=1.0, # 0.4 for DN-DETR + group_cfg=dict(dynamic=True, num_groups=None, + num_dn_queries=100)), # TODO: half num_dn_queries + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='BinaryFocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=lang_model_name, + num_sample_negative=85, + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive')) +] + +dataset_type = 'ODVGDataset' +data_root = 'data/objects365v1/' + +coco_od_dataset = dict( + type=dataset_type, + data_root=data_root, + ann_file='o365v1_train_odvg.json', + label_map_file='o365v1_label_map.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=train_pipeline, + return_classes=True, + backend_args=None) + +train_dataloader = dict( + _delete_=True, + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict(type='ConcatDataset', datasets=[coco_od_dataset])) + +val_dataloader = dict( + dataset=dict(pipeline=test_pipeline, return_classes=True)) +test_dataloader = val_dataloader + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0004, + weight_decay=0.0001), # bs=16 0.0001 + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + 'language_model': dict(lr_mult=0.1), + })) + +# learning policy +max_epochs = 30 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[19, 26], + gamma=0.1) +] + +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (16 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) + +default_hooks = dict(visualization=dict(type='GroundingVisualizationHook')) diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg.py new file mode 100644 index 00000000..b7f388bd --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg.py @@ -0,0 +1,38 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +o365v1_od_dataset = dict( + type='ODVGDataset', + data_root='data/objects365v1/', + ann_file='o365v1_train_odvg.json', + label_map_file='o365v1_label_map.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None, +) + +flickr30k_dataset = dict( + type='ODVGDataset', + data_root='data/flickr30k_entities/', + ann_file='final_flickr_separateGT_train_vg.json', + label_map_file=None, + data_prefix=dict(img='flickr30k_images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +gqa_dataset = dict( + type='ODVGDataset', + data_root='data/gqa/', + ann_file='final_mixed_train_no_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +train_dataloader = dict( + dataset=dict(datasets=[o365v1_od_dataset, flickr30k_dataset, gqa_dataset])) diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m.py new file mode 100644 index 00000000..8e9f5ca4 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m.py @@ -0,0 +1,55 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +o365v1_od_dataset = dict( + type='ODVGDataset', + data_root='data/objects365v1/', + ann_file='o365v1_train_odvg.json', + label_map_file='o365v1_label_map.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None, +) + +flickr30k_dataset = dict( + type='ODVGDataset', + data_root='data/flickr30k_entities/', + ann_file='final_flickr_separateGT_train_vg.json', + label_map_file=None, + data_prefix=dict(img='flickr30k_images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +gqa_dataset = dict( + type='ODVGDataset', + data_root='data/gqa/', + ann_file='final_mixed_train_no_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +grit_dataset = dict( + type='ODVGDataset', + data_root='grit_processed/', + ann_file='grit20m_vg.json', + label_map_file=None, + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +train_dataloader = dict( + sampler=dict( + _delete_=True, + type='CustomSampleSizeSampler', + dataset_size=[-1, -1, -1, 500000]), + dataset=dict(datasets=[ + o365v1_od_dataset, flickr30k_dataset, gqa_dataset, grit_dataset + ])) diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det.py new file mode 100644 index 00000000..56e500c8 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det.py @@ -0,0 +1,117 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +o365v1_od_dataset = dict( + type='ODVGDataset', + data_root='data/objects365v1/', + ann_file='o365v1_train_odvg.json', + label_map_file='o365v1_label_map.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None, +) + +flickr30k_dataset = dict( + type='ODVGDataset', + data_root='data/flickr30k_entities/', + ann_file='final_flickr_separateGT_train_vg.json', + label_map_file=None, + data_prefix=dict(img='flickr30k_images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +gqa_dataset = dict( + type='ODVGDataset', + data_root='data/gqa/', + ann_file='final_mixed_train_no_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +v3d_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/V3Det/annotations/v3det_2023_v1_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] +v3det_dataset = dict( + type='ODVGDataset', + data_root='data/V3Det/', + ann_file='annotations/v3det_2023_v1_train_od.json', + label_map_file='annotations/v3det_2023_v1_label_map.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + need_text=False, # change this + pipeline=v3d_train_pipeline, + return_classes=True, + backend_args=None) + +grit_dataset = dict( + type='ODVGDataset', + data_root='grit_processed/', + ann_file='grit20m_vg.json', + label_map_file=None, + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +train_dataloader = dict( + sampler=dict( + _delete_=True, + type='CustomSampleSizeSampler', + dataset_size=[-1, -1, -1, -1, 500000]), + dataset=dict(datasets=[ + o365v1_od_dataset, flickr30k_dataset, gqa_dataset, v3det_dataset, + grit_dataset + ])) diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_v3det.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_v3det.py new file mode 100644 index 00000000..c89014fb --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_v3det.py @@ -0,0 +1,101 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +o365v1_od_dataset = dict( + type='ODVGDataset', + data_root='data/objects365v1/', + ann_file='o365v1_train_odvg.json', + label_map_file='o365v1_label_map.json', + data_prefix=dict(img='train/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None, +) + +flickr30k_dataset = dict( + type='ODVGDataset', + data_root='data/flickr30k_entities/', + ann_file='final_flickr_separateGT_train_vg.json', + label_map_file=None, + data_prefix=dict(img='flickr30k_images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +gqa_dataset = dict( + type='ODVGDataset', + data_root='data/gqa/', + ann_file='final_mixed_train_no_coco_vg.json', + label_map_file=None, + data_prefix=dict(img='images/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=_base_.train_pipeline, + return_classes=True, + backend_args=None) + +v3d_train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/V3Det/annotations/v3det_2023_v1_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] +v3det_dataset = dict( + type='ODVGDataset', + data_root='data/V3Det/', + ann_file='annotations/v3det_2023_v1_train_od.json', + label_map_file='annotations/v3det_2023_v1_label_map.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + need_text=False, # change this + pipeline=v3d_train_pipeline, + return_classes=True, + backend_args=None) + +train_dataloader = dict( + dataset=dict(datasets=[ + o365v1_od_dataset, flickr30k_dataset, gqa_dataset, v3det_dataset + ])) diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_cat.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_cat.py new file mode 100644 index 00000000..6dc8dcd8 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_cat.py @@ -0,0 +1,43 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadTextAnnotations'), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive')) +] + +data_root = 'data/cat/' + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=False, + dataset=dict( + type='ODVGDataset', + data_root=data_root, + label_map_file='cat_label_map.json', + ann_file='cat_train_od.json', + data_prefix=dict(img='images/'), + pipeline=test_pipeline, + return_classes=True)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + outfile_path=data_root + 'cat_train_od_v1.json', + img_prefix=data_root + 'images/', + score_thr=0.7, + nms_thr=0.5, + type='DumpODVGResults') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_flickr30k.py b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_flickr30k.py new file mode 100644 index 00000000..78bf1c34 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_flickr30k.py @@ -0,0 +1,42 @@ +_base_ = 'grounding_dino_swin-t_pretrain_obj365.py' + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadTextAnnotations'), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive')) +] + +data_root = 'data/flickr30k_entities/' + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=False, + dataset=dict( + type='ODVGDataset', + data_root=data_root, + ann_file='flickr_simple_train_vg.json', + data_prefix=dict(img='flickr30k_images/'), + pipeline=test_pipeline, + return_classes=True)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + outfile_path=data_root + 'flickr_simple_train_vg_v1.json', + img_prefix=data_root + 'flickr30k_images/', + score_thr=0.4, + nms_thr=0.5, + type='DumpODVGResults') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis.py b/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis.py new file mode 100644 index 00000000..3ba12c90 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis.py @@ -0,0 +1,120 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/coco/' + +model = dict(test_cfg=dict( + max_per_img=300, + chunked_size=40, +)) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/coco/annotations/lvis_v1_label_map.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='ODVGDataset', + data_root=data_root, + need_text=False, + label_map_file='annotations/lvis_v1_label_map.json', + ann_file='annotations/lvis_v1_train_od.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False, min_size=32), + return_classes=True, + pipeline=train_pipeline))) + +val_dataloader = dict( + dataset=dict( + data_root=data_root, + type='LVISV1Dataset', + ann_file='annotations/lvis_v1_minival_inserted_image_name.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='LVISFixedAPMetric', + ann_file=data_root + + 'annotations/lvis_v1_minival_inserted_image_name.json') +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + # 'language_model': dict(lr_mult=0), + })) + +# learning policy +max_epochs = 12 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=3) + +default_hooks = dict( + checkpoint=dict( + max_keep_ckpts=1, save_best='lvis_fixed_ap/AP', rule='greater')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis_866_337.py b/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis_866_337.py new file mode 100644 index 00000000..28d0141d --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_finetune_16xb4_1x_lvis_866_337.py @@ -0,0 +1,120 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/coco/' + +model = dict(test_cfg=dict( + max_per_img=300, + chunked_size=40, +)) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + # change this + label_map_file='data/coco/annotations/lvis_v1_label_map_norare.json', + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='ODVGDataset', + data_root=data_root, + need_text=False, + label_map_file='annotations/lvis_v1_label_map_norare.json', + ann_file='annotations/lvis_v1_train_od_norare.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False, min_size=32), + return_classes=True, + pipeline=train_pipeline))) + +val_dataloader = dict( + dataset=dict( + data_root=data_root, + type='LVISV1Dataset', + ann_file='annotations/lvis_v1_minival_inserted_image_name.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='LVISFixedAPMetric', + ann_file=data_root + + 'annotations/lvis_v1_minival_inserted_image_name.json') +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.00005, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + # 'language_model': dict(lr_mult=0), + })) + +# learning policy +max_epochs = 12 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=3) + +default_hooks = dict( + checkpoint=dict( + max_keep_ckpts=3, save_best='lvis_fixed_ap/AP', rule='greater')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_lvis.py b/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_lvis.py new file mode 100644 index 00000000..fb4ed438 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_lvis.py @@ -0,0 +1,24 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +model = dict(test_cfg=dict( + max_per_img=300, + chunked_size=40, +)) + +dataset_type = 'LVISV1Dataset' +data_root = 'data/coco/' + +val_dataloader = dict( + dataset=dict( + data_root=data_root, + type=dataset_type, + ann_file='annotations/lvis_od_val.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +# numpy < 1.24.0 +val_evaluator = dict( + _delete_=True, + type='LVISFixedAPMetric', + ann_file=data_root + 'annotations/lvis_od_val.json') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_mini-lvis.py b/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_mini-lvis.py new file mode 100644 index 00000000..406a39a4 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/lvis/grounding_dino_swin-t_pretrain_zeroshot_mini-lvis.py @@ -0,0 +1,25 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +model = dict(test_cfg=dict( + max_per_img=300, + chunked_size=40, +)) + +dataset_type = 'LVISV1Dataset' +data_root = 'data/coco/' + +val_dataloader = dict( + dataset=dict( + data_root=data_root, + type=dataset_type, + ann_file='annotations/lvis_v1_minival_inserted_image_name.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +# numpy < 1.24.0 +val_evaluator = dict( + _delete_=True, + type='LVISFixedAPMetric', + ann_file=data_root + + 'annotations/lvis_v1_minival_inserted_image_name.json') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mm_grounding_dino/metafile.yml b/mmdetection/configs/mm_grounding_dino/metafile.yml new file mode 100644 index 00000000..c104ac05 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/metafile.yml @@ -0,0 +1,90 @@ +Collections: + - Name: MM Grounding DINO + Metadata: + Training Data: Objects365, GoldG, GRIT and V3Det + Training Techniques: + - AdamW + - Multi Scale Train + - Gradient Clip + Training Resources: 3090 GPUs + Architecture: + - Swin Transformer + - BERT + README: configs/mm_grounding_dino/README.md + Code: + URL: + Version: v3.0.0 + +Models: + - Name: grounding_dino_swin-t_pretrain_obj365_goldg + In Collection: MM Grounding DINO + Config: configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg/grounding_dino_swin-t_pretrain_obj365_goldg_20231122_132602-4ea751ce.pth + - Name: grounding_dino_swin-t_pretrain_obj365_goldg_grit9m + In Collection: MM Grounding DINO + Config: configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.5 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_20231128_200818-169cc352.pth + - Name: grounding_dino_swin-t_pretrain_obj365_goldg_v3det + In Collection: MM Grounding DINO + Config: configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_v3det.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.6 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_v3det_20231218_095741-e316e297.pth + - Name: grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det + In Collection: MM Grounding DINO + Config: configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth + - Name: grounding_dino_swin-b_pretrain_obj365_goldg_v3det + In Collection: MM Grounding DINO + Config: configs/mm_grounding_dino/grounding_dino_swin-b_pretrain_obj365_goldg_v3det.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 52.5 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-b_pretrain_obj365_goldg_v3det/grounding_dino_swin-b_pretrain_obj365_goldg_v3de-f83eef00.pth + - Name: grounding_dino_swin-b_pretrain_all + In Collection: MM Grounding DINO + Config: configs/mm_grounding_dino/grounding_dino_swin-b_pretrain_all.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 59.5 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-b_pretrain_all/grounding_dino_swin-b_pretrain_all-f9818a7c.pth + - Name: grounding_dino_swin-l_pretrain_obj365_goldg + In Collection: MM Grounding DINO + Config: configs/mm_grounding_dino/grounding_dino_swin-l_pretrain_obj365_goldg.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 53.0 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-l_pretrain_obj365_goldg/grounding_dino_swin-l_pretrain_obj365_goldg-34dcdc53.pth + - Name: grounding_dino_swin-l_pretrain_all + In Collection: MM Grounding DINO + Config: configs/mm_grounding_dino/grounding_dino_swin-l_pretrain_all.py + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 60.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-l_pretrain_all/grounding_dino_swin-l_pretrain_all-56d69e78.pth diff --git a/mmdetection/configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py b/mmdetection/configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py new file mode 100644 index 00000000..d87ca7ca --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py @@ -0,0 +1,338 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' # noqa + +dataset_type = 'CocoDataset' +data_root = 'data/odinw/' + +base_test_pipeline = _base_.test_pipeline +base_test_pipeline[-1]['meta_keys'] = ('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'text', + 'custom_entities', 'caption_prompt') + +# ---------------------1 AerialMaritimeDrone---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/large/' +dataset_AerialMaritimeDrone = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + test_mode=True, + pipeline=base_test_pipeline, + return_classes=True) +val_evaluator_AerialMaritimeDrone = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------2 Aquarium---------------------# +class_name = ('fish', 'jellyfish', 'penguin', 'puffin', 'shark', 'starfish', + 'stingray') +metainfo = dict(classes=class_name) +_data_root = data_root + 'Aquarium/Aquarium Combined.v2-raw-1024.coco/' + +caption_prompt = None +# caption_prompt = { +# 'penguin': { +# 'suffix': ', which is black and white' +# }, +# 'puffin': { +# 'suffix': ' with orange beaks' +# }, +# 'stingray': { +# 'suffix': ' which is flat and round' +# }, +# } +dataset_Aquarium = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Aquarium = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------3 CottontailRabbits---------------------# +class_name = ('Cottontail-Rabbit', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'CottontailRabbits/' + +# caption_prompt = None +caption_prompt = {'Cottontail-Rabbit': {'name': 'rabbit'}} + +dataset_CottontailRabbits = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_CottontailRabbits = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------4 EgoHands---------------------# +class_name = ('hand', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/generic/' + +# caption_prompt = None +caption_prompt = {'hand': {'suffix': ' of a person'}} + +dataset_EgoHands = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------5 NorthAmericaMushrooms---------------------# +class_name = ('CoW', 'chanterelle') +metainfo = dict(classes=class_name) +_data_root = data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa + +# caption_prompt = None +caption_prompt = { + 'CoW': { + 'name': 'flat mushroom' + }, + 'chanterelle': { + 'name': 'yellow mushroom' + } +} + +dataset_NorthAmericaMushrooms = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_NorthAmericaMushrooms = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------6 Packages---------------------# +class_name = ('package', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Packages/Raw/' + +# caption_prompt = None +caption_prompt = { + 'package': { + 'prefix': 'there is a ', + 'suffix': ' on the porch' + } +} + +dataset_Packages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Packages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------7 PascalVOC---------------------# +class_name = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', + 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PascalVOC/' +dataset_PascalVOC = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PascalVOC = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------8 pistols---------------------# +class_name = ('pistol', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pistols/export/' +dataset_pistols = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pistols = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------9 pothole---------------------# +class_name = ('pothole', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pothole/' + +# caption_prompt = None +caption_prompt = { + 'pothole': { + 'prefix': 'there are some ', + 'name': 'holes', + 'suffix': ' on the road' + } +} + +dataset_pothole = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pothole = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------10 Raccoon---------------------# +class_name = ('raccoon', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Raccoon/Raccoon.v2-raw.coco/' +dataset_Raccoon = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Raccoon = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------11 ShellfishOpenImages---------------------# +class_name = ('Crab', 'Lobster', 'Shrimp') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ShellfishOpenImages/raw/' +dataset_ShellfishOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ShellfishOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------12 thermalDogsAndPeople---------------------# +class_name = ('dog', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'thermalDogsAndPeople/' +dataset_thermalDogsAndPeople = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_thermalDogsAndPeople = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------13 VehiclesOpenImages---------------------# +class_name = ('Ambulance', 'Bus', 'Car', 'Motorcycle', 'Truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'VehiclesOpenImages/416x416/' +dataset_VehiclesOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_VehiclesOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# --------------------- Config---------------------# +dataset_prefixes = [ + 'AerialMaritimeDrone', 'Aquarium', 'CottontailRabbits', 'EgoHands', + 'NorthAmericaMushrooms', 'Packages', 'PascalVOC', 'pistols', 'pothole', + 'Raccoon', 'ShellfishOpenImages', 'thermalDogsAndPeople', + 'VehiclesOpenImages' +] +datasets = [ + dataset_AerialMaritimeDrone, dataset_Aquarium, dataset_CottontailRabbits, + dataset_EgoHands, dataset_NorthAmericaMushrooms, dataset_Packages, + dataset_PascalVOC, dataset_pistols, dataset_pothole, dataset_Raccoon, + dataset_ShellfishOpenImages, dataset_thermalDogsAndPeople, + dataset_VehiclesOpenImages +] +metrics = [ + val_evaluator_AerialMaritimeDrone, val_evaluator_Aquarium, + val_evaluator_CottontailRabbits, val_evaluator_EgoHands, + val_evaluator_NorthAmericaMushrooms, val_evaluator_Packages, + val_evaluator_PascalVOC, val_evaluator_pistols, val_evaluator_pothole, + val_evaluator_Raccoon, val_evaluator_ShellfishOpenImages, + val_evaluator_thermalDogsAndPeople, val_evaluator_VehiclesOpenImages +] + +# -------------------------------------------------# +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw35.py b/mmdetection/configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw35.py new file mode 100644 index 00000000..a6b8566a --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw35.py @@ -0,0 +1,794 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' # noqa + +dataset_type = 'CocoDataset' +data_root = 'data/odinw/' + +base_test_pipeline = _base_.test_pipeline +base_test_pipeline[-1]['meta_keys'] = ('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'text', + 'custom_entities', 'caption_prompt') + +# ---------------------1 AerialMaritimeDrone_large---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/large/' +dataset_AerialMaritimeDrone_large = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AerialMaritimeDrone_large = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------2 AerialMaritimeDrone_tiled---------------------# +class_name = ('boat', 'car', 'dock', 'jetski', 'lift') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AerialMaritimeDrone/tiled/' +dataset_AerialMaritimeDrone_tiled = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AerialMaritimeDrone_tiled = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------3 AmericanSignLanguageLetters---------------------# +class_name = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z') +metainfo = dict(classes=class_name) +_data_root = data_root + 'AmericanSignLanguageLetters/American Sign Language Letters.v1-v1.coco/' # noqa +dataset_AmericanSignLanguageLetters = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_AmericanSignLanguageLetters = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------4 Aquarium---------------------# +class_name = ('fish', 'jellyfish', 'penguin', 'puffin', 'shark', 'starfish', + 'stingray') +metainfo = dict(classes=class_name) +_data_root = data_root + 'Aquarium/Aquarium Combined.v2-raw-1024.coco/' +dataset_Aquarium = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Aquarium = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------5 BCCD---------------------# +class_name = ('Platelets', 'RBC', 'WBC') +metainfo = dict(classes=class_name) +_data_root = data_root + 'BCCD/BCCD.v3-raw.coco/' +dataset_BCCD = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_BCCD = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------6 boggleBoards---------------------# +class_name = ('Q', 'a', 'an', 'b', 'c', 'd', 'e', 'er', 'f', 'g', 'h', 'he', + 'i', 'in', 'j', 'k', 'l', 'm', 'n', 'o', 'o ', 'p', 'q', 'qu', + 'r', 's', 't', 't\\', 'th', 'u', 'v', 'w', 'wild', 'x', 'y', 'z') +metainfo = dict(classes=class_name) +_data_root = data_root + 'boggleBoards/416x416AutoOrient/export/' +dataset_boggleBoards = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_boggleBoards = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------7 brackishUnderwater---------------------# +class_name = ('crab', 'fish', 'jellyfish', 'shrimp', 'small_fish', 'starfish') +metainfo = dict(classes=class_name) +_data_root = data_root + 'brackishUnderwater/960x540/' +dataset_brackishUnderwater = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_brackishUnderwater = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------8 ChessPieces---------------------# +class_name = (' ', 'black bishop', 'black king', 'black knight', 'black pawn', + 'black queen', 'black rook', 'white bishop', 'white king', + 'white knight', 'white pawn', 'white queen', 'white rook') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ChessPieces/Chess Pieces.v23-raw.coco/' +dataset_ChessPieces = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ChessPieces = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------9 CottontailRabbits---------------------# +class_name = ('rabbit', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'CottontailRabbits/' +dataset_CottontailRabbits = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_CottontailRabbits = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------10 dice---------------------# +class_name = ('1', '2', '3', '4', '5', '6') +metainfo = dict(classes=class_name) +_data_root = data_root + 'dice/mediumColor/export/' +dataset_dice = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_dice = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------11 DroneControl---------------------# +class_name = ('follow', 'follow_hand', 'land', 'land_hand', 'null', 'object', + 'takeoff', 'takeoff-hand') +metainfo = dict(classes=class_name) +_data_root = data_root + 'DroneControl/Drone Control.v3-raw.coco/' +dataset_DroneControl = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_DroneControl = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------12 EgoHands_generic---------------------# +class_name = ('hand', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/generic/' +caption_prompt = {'hand': {'suffix': ' of a person'}} +dataset_EgoHands_generic = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands_generic = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------13 EgoHands_specific---------------------# +class_name = ('myleft', 'myright', 'yourleft', 'yourright') +metainfo = dict(classes=class_name) +_data_root = data_root + 'EgoHands/specific/' +dataset_EgoHands_specific = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_EgoHands_specific = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------14 HardHatWorkers---------------------# +class_name = ('head', 'helmet', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'HardHatWorkers/raw/' +dataset_HardHatWorkers = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_HardHatWorkers = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------15 MaskWearing---------------------# +class_name = ('mask', 'no-mask') +metainfo = dict(classes=class_name) +_data_root = data_root + 'MaskWearing/raw/' +dataset_MaskWearing = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_MaskWearing = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------16 MountainDewCommercial---------------------# +class_name = ('bottle', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'MountainDewCommercial/' +dataset_MountainDewCommercial = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_MountainDewCommercial = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------17 NorthAmericaMushrooms---------------------# +class_name = ('flat mushroom', 'yellow mushroom') +metainfo = dict(classes=class_name) +_data_root = data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa +dataset_NorthAmericaMushrooms = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/new_annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_NorthAmericaMushrooms = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/new_annotations_without_background.json', + metric='bbox') + +# ---------------------18 openPoetryVision---------------------# +class_name = ('American Typewriter', 'Andale Mono', 'Apple Chancery', 'Arial', + 'Avenir', 'Baskerville', 'Big Caslon', 'Bradley Hand', + 'Brush Script MT', 'Chalkboard', 'Comic Sans MS', 'Copperplate', + 'Courier', 'Didot', 'Futura', 'Geneva', 'Georgia', 'Gill Sans', + 'Helvetica', 'Herculanum', 'Impact', 'Kefa', 'Lucida Grande', + 'Luminari', 'Marker Felt', 'Menlo', 'Monaco', 'Noteworthy', + 'Optima', 'PT Sans', 'PT Serif', 'Palatino', 'Papyrus', + 'Phosphate', 'Rockwell', 'SF Pro', 'SignPainter', 'Skia', + 'Snell Roundhand', 'Tahoma', 'Times New Roman', 'Trebuchet MS', + 'Verdana') +metainfo = dict(classes=class_name) +_data_root = data_root + 'openPoetryVision/512x512/' +dataset_openPoetryVision = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_openPoetryVision = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------19 OxfordPets_by_breed---------------------# +class_name = ('cat-Abyssinian', 'cat-Bengal', 'cat-Birman', 'cat-Bombay', + 'cat-British_Shorthair', 'cat-Egyptian_Mau', 'cat-Maine_Coon', + 'cat-Persian', 'cat-Ragdoll', 'cat-Russian_Blue', 'cat-Siamese', + 'cat-Sphynx', 'dog-american_bulldog', + 'dog-american_pit_bull_terrier', 'dog-basset_hound', + 'dog-beagle', 'dog-boxer', 'dog-chihuahua', + 'dog-english_cocker_spaniel', 'dog-english_setter', + 'dog-german_shorthaired', 'dog-great_pyrenees', 'dog-havanese', + 'dog-japanese_chin', 'dog-keeshond', 'dog-leonberger', + 'dog-miniature_pinscher', 'dog-newfoundland', 'dog-pomeranian', + 'dog-pug', 'dog-saint_bernard', 'dog-samoyed', + 'dog-scottish_terrier', 'dog-shiba_inu', + 'dog-staffordshire_bull_terrier', 'dog-wheaten_terrier', + 'dog-yorkshire_terrier') +metainfo = dict(classes=class_name) +_data_root = data_root + 'OxfordPets/by-breed/' # noqa +dataset_OxfordPets_by_breed = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_OxfordPets_by_breed = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------20 OxfordPets_by_species---------------------# +class_name = ('cat', 'dog') +metainfo = dict(classes=class_name) +_data_root = data_root + 'OxfordPets/by-species/' # noqa +dataset_OxfordPets_by_species = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_OxfordPets_by_species = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------21 PKLot---------------------# +class_name = ('space-empty', 'space-occupied') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PKLot/640/' # noqa +dataset_PKLot = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PKLot = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------22 Packages---------------------# +class_name = ('package', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Packages/Raw/' +caption_prompt = { + 'package': { + 'prefix': 'there is a ', + 'suffix': ' on the porch' + } +} +dataset_Packages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=base_test_pipeline, + caption_prompt=caption_prompt, + test_mode=True, + return_classes=True) +val_evaluator_Packages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------23 PascalVOC---------------------# +class_name = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', + 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor') +metainfo = dict(classes=class_name) +_data_root = data_root + 'PascalVOC/' +dataset_PascalVOC = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_PascalVOC = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------24 pistols---------------------# +class_name = ('pistol', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pistols/export/' +dataset_pistols = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pistols = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------25 plantdoc---------------------# +class_name = ('Apple Scab Leaf', 'Apple leaf', 'Apple rust leaf', + 'Bell_pepper leaf', 'Bell_pepper leaf spot', 'Blueberry leaf', + 'Cherry leaf', 'Corn Gray leaf spot', 'Corn leaf blight', + 'Corn rust leaf', 'Peach leaf', 'Potato leaf', + 'Potato leaf early blight', 'Potato leaf late blight', + 'Raspberry leaf', 'Soyabean leaf', 'Soybean leaf', + 'Squash Powdery mildew leaf', 'Strawberry leaf', + 'Tomato Early blight leaf', 'Tomato Septoria leaf spot', + 'Tomato leaf', 'Tomato leaf bacterial spot', + 'Tomato leaf late blight', 'Tomato leaf mosaic virus', + 'Tomato leaf yellow virus', 'Tomato mold leaf', + 'Tomato two spotted spider mites leaf', 'grape leaf', + 'grape leaf black rot') +metainfo = dict(classes=class_name) +_data_root = data_root + 'plantdoc/416x416/' +dataset_plantdoc = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_plantdoc = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------26 pothole---------------------# +class_name = ('pothole', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'pothole/' +caption_prompt = { + 'pothole': { + 'name': 'holes', + 'prefix': 'there are some ', + 'suffix': ' on the road' + } +} +dataset_pothole = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + caption_prompt=caption_prompt, + pipeline=base_test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_pothole = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------27 Raccoon---------------------# +class_name = ('raccoon', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'Raccoon/Raccoon.v2-raw.coco/' +dataset_Raccoon = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_Raccoon = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------28 selfdrivingCar---------------------# +class_name = ('biker', 'car', 'pedestrian', 'trafficLight', + 'trafficLight-Green', 'trafficLight-GreenLeft', + 'trafficLight-Red', 'trafficLight-RedLeft', + 'trafficLight-Yellow', 'trafficLight-YellowLeft', 'truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'selfdrivingCar/fixedLarge/export/' +dataset_selfdrivingCar = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='val_annotations_without_background.json', + data_prefix=dict(img=''), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_selfdrivingCar = dict( + type='CocoMetric', + ann_file=_data_root + 'val_annotations_without_background.json', + metric='bbox') + +# ---------------------29 ShellfishOpenImages---------------------# +class_name = ('Crab', 'Lobster', 'Shrimp') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ShellfishOpenImages/raw/' +dataset_ShellfishOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ShellfishOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------30 ThermalCheetah---------------------# +class_name = ('cheetah', 'human') +metainfo = dict(classes=class_name) +_data_root = data_root + 'ThermalCheetah/' +dataset_ThermalCheetah = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_ThermalCheetah = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------31 thermalDogsAndPeople---------------------# +class_name = ('dog', 'person') +metainfo = dict(classes=class_name) +_data_root = data_root + 'thermalDogsAndPeople/' +dataset_thermalDogsAndPeople = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_thermalDogsAndPeople = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------32 UnoCards---------------------# +class_name = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', + '12', '13', '14') +metainfo = dict(classes=class_name) +_data_root = data_root + 'UnoCards/raw/' +dataset_UnoCards = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_UnoCards = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------33 VehiclesOpenImages---------------------# +class_name = ('Ambulance', 'Bus', 'Car', 'Motorcycle', 'Truck') +metainfo = dict(classes=class_name) +_data_root = data_root + 'VehiclesOpenImages/416x416/' +dataset_VehiclesOpenImages = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_VehiclesOpenImages = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------34 WildfireSmoke---------------------# +class_name = ('smoke', ) +metainfo = dict(classes=class_name) +_data_root = data_root + 'WildfireSmoke/' +dataset_WildfireSmoke = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_WildfireSmoke = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# ---------------------35 websiteScreenshots---------------------# +class_name = ('button', 'field', 'heading', 'iframe', 'image', 'label', 'link', + 'text') +metainfo = dict(classes=class_name) +_data_root = data_root + 'websiteScreenshots/' +dataset_websiteScreenshots = dict( + type=dataset_type, + metainfo=metainfo, + data_root=_data_root, + ann_file='valid/annotations_without_background.json', + data_prefix=dict(img='valid/'), + pipeline=_base_.test_pipeline, + test_mode=True, + return_classes=True) +val_evaluator_websiteScreenshots = dict( + type='CocoMetric', + ann_file=_data_root + 'valid/annotations_without_background.json', + metric='bbox') + +# --------------------- Config---------------------# + +dataset_prefixes = [ + 'AerialMaritimeDrone_large', + 'AerialMaritimeDrone_tiled', + 'AmericanSignLanguageLetters', + 'Aquarium', + 'BCCD', + 'boggleBoards', + 'brackishUnderwater', + 'ChessPieces', + 'CottontailRabbits', + 'dice', + 'DroneControl', + 'EgoHands_generic', + 'EgoHands_specific', + 'HardHatWorkers', + 'MaskWearing', + 'MountainDewCommercial', + 'NorthAmericaMushrooms', + 'openPoetryVision', + 'OxfordPets_by_breed', + 'OxfordPets_by_species', + 'PKLot', + 'Packages', + 'PascalVOC', + 'pistols', + 'plantdoc', + 'pothole', + 'Raccoons', + 'selfdrivingCar', + 'ShellfishOpenImages', + 'ThermalCheetah', + 'thermalDogsAndPeople', + 'UnoCards', + 'VehiclesOpenImages', + 'WildfireSmoke', + 'websiteScreenshots', +] + +datasets = [ + dataset_AerialMaritimeDrone_large, dataset_AerialMaritimeDrone_tiled, + dataset_AmericanSignLanguageLetters, dataset_Aquarium, dataset_BCCD, + dataset_boggleBoards, dataset_brackishUnderwater, dataset_ChessPieces, + dataset_CottontailRabbits, dataset_dice, dataset_DroneControl, + dataset_EgoHands_generic, dataset_EgoHands_specific, + dataset_HardHatWorkers, dataset_MaskWearing, dataset_MountainDewCommercial, + dataset_NorthAmericaMushrooms, dataset_openPoetryVision, + dataset_OxfordPets_by_breed, dataset_OxfordPets_by_species, dataset_PKLot, + dataset_Packages, dataset_PascalVOC, dataset_pistols, dataset_plantdoc, + dataset_pothole, dataset_Raccoon, dataset_selfdrivingCar, + dataset_ShellfishOpenImages, dataset_ThermalCheetah, + dataset_thermalDogsAndPeople, dataset_UnoCards, dataset_VehiclesOpenImages, + dataset_WildfireSmoke, dataset_websiteScreenshots +] + +metrics = [ + val_evaluator_AerialMaritimeDrone_large, + val_evaluator_AerialMaritimeDrone_tiled, + val_evaluator_AmericanSignLanguageLetters, val_evaluator_Aquarium, + val_evaluator_BCCD, val_evaluator_boggleBoards, + val_evaluator_brackishUnderwater, val_evaluator_ChessPieces, + val_evaluator_CottontailRabbits, val_evaluator_dice, + val_evaluator_DroneControl, val_evaluator_EgoHands_generic, + val_evaluator_EgoHands_specific, val_evaluator_HardHatWorkers, + val_evaluator_MaskWearing, val_evaluator_MountainDewCommercial, + val_evaluator_NorthAmericaMushrooms, val_evaluator_openPoetryVision, + val_evaluator_OxfordPets_by_breed, val_evaluator_OxfordPets_by_species, + val_evaluator_PKLot, val_evaluator_Packages, val_evaluator_PascalVOC, + val_evaluator_pistols, val_evaluator_plantdoc, val_evaluator_pothole, + val_evaluator_Raccoon, val_evaluator_selfdrivingCar, + val_evaluator_ShellfishOpenImages, val_evaluator_ThermalCheetah, + val_evaluator_thermalDogsAndPeople, val_evaluator_UnoCards, + val_evaluator_VehiclesOpenImages, val_evaluator_WildfireSmoke, + val_evaluator_websiteScreenshots +] + +# -------------------------------------------------# +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mm_grounding_dino/odinw/override_category.py b/mmdetection/configs/mm_grounding_dino/odinw/override_category.py new file mode 100644 index 00000000..9ff05fc6 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/odinw/override_category.py @@ -0,0 +1,109 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse + +import mmengine + + +def parse_args(): + parser = argparse.ArgumentParser(description='Override Category') + parser.add_argument('data_root') + return parser.parse_args() + + +def main(): + args = parse_args() + + ChessPieces = [{ + 'id': 1, + 'name': ' ', + 'supercategory': 'pieces' + }, { + 'id': 2, + 'name': 'black bishop', + 'supercategory': 'pieces' + }, { + 'id': 3, + 'name': 'black king', + 'supercategory': 'pieces' + }, { + 'id': 4, + 'name': 'black knight', + 'supercategory': 'pieces' + }, { + 'id': 5, + 'name': 'black pawn', + 'supercategory': 'pieces' + }, { + 'id': 6, + 'name': 'black queen', + 'supercategory': 'pieces' + }, { + 'id': 7, + 'name': 'black rook', + 'supercategory': 'pieces' + }, { + 'id': 8, + 'name': 'white bishop', + 'supercategory': 'pieces' + }, { + 'id': 9, + 'name': 'white king', + 'supercategory': 'pieces' + }, { + 'id': 10, + 'name': 'white knight', + 'supercategory': 'pieces' + }, { + 'id': 11, + 'name': 'white pawn', + 'supercategory': 'pieces' + }, { + 'id': 12, + 'name': 'white queen', + 'supercategory': 'pieces' + }, { + 'id': 13, + 'name': 'white rook', + 'supercategory': 'pieces' + }] + + _data_root = args.data_root + 'ChessPieces/Chess Pieces.v23-raw.coco/' + json_data = mmengine.load(_data_root + + 'valid/annotations_without_background.json') + json_data['categories'] = ChessPieces + mmengine.dump(json_data, + _data_root + 'valid/new_annotations_without_background.json') + + CottontailRabbits = [{ + 'id': 1, + 'name': 'rabbit', + 'supercategory': 'Cottontail-Rabbit' + }] + + _data_root = args.data_root + 'CottontailRabbits/' + json_data = mmengine.load(_data_root + + 'valid/annotations_without_background.json') + json_data['categories'] = CottontailRabbits + mmengine.dump(json_data, + _data_root + 'valid/new_annotations_without_background.json') + + NorthAmericaMushrooms = [{ + 'id': 1, + 'name': 'flat mushroom', + 'supercategory': 'mushroom' + }, { + 'id': 2, + 'name': 'yellow mushroom', + 'supercategory': 'mushroom' + }] + + _data_root = args.data_root + 'NorthAmericaMushrooms/North American Mushrooms.v1-416x416.coco/' # noqa + json_data = mmengine.load(_data_root + + 'valid/annotations_without_background.json') + json_data['categories'] = NorthAmericaMushrooms + mmengine.dump(json_data, + _data_root + 'valid/new_annotations_without_background.json') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/configs/mm_grounding_dino/people_in_painting/grounding_dino_swin-t_finetune_8xb4_50e_people_in_painting.py b/mmdetection/configs/mm_grounding_dino/people_in_painting/grounding_dino_swin-t_finetune_8xb4_50e_people_in_painting.py new file mode 100644 index 00000000..449d8682 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/people_in_painting/grounding_dino_swin-t_finetune_8xb4_50e_people_in_painting.py @@ -0,0 +1,109 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +# https://universe.roboflow.com/roboflow-100/people-in-paintings/dataset/2 +data_root = 'data/people_in_painting_v2/' +class_name = ('Human', ) +palette = [(220, 20, 60)] + +metainfo = dict(classes=class_name, palette=palette) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +train_dataloader = dict( + sampler=dict(_delete_=True, type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + _delete_=True, + type='RepeatDataset', + times=10, + dataset=dict( + type='CocoDataset', + data_root=data_root, + metainfo=metainfo, + filter_cfg=dict(filter_empty_gt=False, min_size=32), + pipeline=train_pipeline, + return_classes=True, + data_prefix=dict(img='train/'), + ann_file='train/_annotations.coco.json'))) + +val_dataloader = dict( + dataset=dict( + metainfo=metainfo, + data_root=data_root, + return_classes=True, + ann_file='valid/_annotations.coco.json', + data_prefix=dict(img='valid/'))) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'valid/_annotations.coco.json', + metric='bbox', + format_only=False) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1) + })) + +# learning policy +max_epochs = 5 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[4], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) +default_hooks = dict(checkpoint=dict(max_keep_ckpts=1, save_best='auto')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_grefcoco.py b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_grefcoco.py new file mode 100644 index 00000000..983ffe5c --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_grefcoco.py @@ -0,0 +1,170 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/coco/' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + # change this + dict(type='RandomFlip', prob=0.0), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='ODVGDataset', + data_root=data_root, + ann_file='mdetr_annotations/finetune_grefcoco_train_vg.json', + data_prefix=dict(img='train2014/'), + filter_cfg=dict(filter_empty_gt=False, min_size=32), + return_classes=True, + pipeline=train_pipeline)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_grefcoco_val.json' +val_dataset_all_val = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) +val_evaluator_all_val = dict( + type='gRefCOCOMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + thresh_score=0.7, + thresh_f1=1.0) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_grefcoco_testA.json' +val_dataset_refcoco_testA = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testA = dict( + type='gRefCOCOMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + thresh_score=0.7, + thresh_f1=1.0) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_grefcoco_testB.json' +val_dataset_refcoco_testB = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testB = dict( + type='gRefCOCOMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + thresh_score=0.7, + thresh_f1=1.0) + +# -------------------------------------------------# +datasets = [ + val_dataset_all_val, val_dataset_refcoco_testA, val_dataset_refcoco_testB +] +dataset_prefixes = ['grefcoco_val', 'grefcoco_testA', 'grefcoco_testB'] +metrics = [ + val_evaluator_all_val, val_evaluator_refcoco_testA, + val_evaluator_refcoco_testB +] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + # 'language_model': dict(lr_mult=0), + })) + +# learning policy +max_epochs = 5 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[3], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcoco.py b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcoco.py new file mode 100644 index 00000000..d91af473 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcoco.py @@ -0,0 +1,167 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/coco/' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + # change this + dict(type='RandomFlip', prob=0.0), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='ODVGDataset', + data_root=data_root, + ann_file='mdetr_annotations/finetune_refcoco_train_vg.json', + data_prefix=dict(img='train2014/'), + filter_cfg=dict(filter_empty_gt=False, min_size=32), + return_classes=True, + pipeline=train_pipeline)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco_val.json' +val_dataset_all_val = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) +val_evaluator_all_val = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco_testA.json' +val_dataset_refcoco_testA = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testA = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco_testB.json' +val_dataset_refcoco_testB = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testB = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +datasets = [ + val_dataset_all_val, val_dataset_refcoco_testA, val_dataset_refcoco_testB +] +dataset_prefixes = ['refcoco_val', 'refcoco_testA', 'refcoco_testB'] +metrics = [ + val_evaluator_all_val, val_evaluator_refcoco_testA, + val_evaluator_refcoco_testB +] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + # 'language_model': dict(lr_mult=0), + })) + +# learning policy +max_epochs = 5 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[3], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcoco_plus.py b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcoco_plus.py new file mode 100644 index 00000000..871adc8e --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcoco_plus.py @@ -0,0 +1,167 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/coco/' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + # change this + dict(type='RandomFlip', prob=0.0), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='ODVGDataset', + data_root=data_root, + ann_file='mdetr_annotations/finetune_refcoco+_train_vg.json', + data_prefix=dict(img='train2014/'), + filter_cfg=dict(filter_empty_gt=False, min_size=32), + return_classes=True, + pipeline=train_pipeline)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco+_val.json' +val_dataset_all_val = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) +val_evaluator_all_val = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco+_testA.json' +val_dataset_refcoco_testA = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testA = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco+_testB.json' +val_dataset_refcoco_testB = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testB = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +datasets = [ + val_dataset_all_val, val_dataset_refcoco_testA, val_dataset_refcoco_testB +] +dataset_prefixes = ['refcoco+_val', 'refcoco+_testA', 'refcoco+_testB'] +metrics = [ + val_evaluator_all_val, val_evaluator_refcoco_testA, + val_evaluator_refcoco_testB +] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + # 'language_model': dict(lr_mult=0), + })) + +# learning policy +max_epochs = 5 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[3], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcocog.py b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcocog.py new file mode 100644 index 00000000..a351d6f9 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_finetune_8xb4_5e_refcocog.py @@ -0,0 +1,145 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/coco/' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + # change this + dict(type='RandomFlip', prob=0.0), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='RandomSamplingNegPos', + tokenizer_name=_base_.lang_model_name, + num_sample_negative=85, + max_tokens=256), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities', 'tokens_positive', 'dataset_mode')) +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type='ODVGDataset', + data_root=data_root, + ann_file='mdetr_annotations/finetune_refcocog_train_vg.json', + data_prefix=dict(img='train2014/'), + filter_cfg=dict(filter_empty_gt=False, min_size=32), + return_classes=True, + pipeline=train_pipeline)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcocog_val.json' +val_dataset_all_val = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) +val_evaluator_all_val = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcocog_test.json' +val_dataset_refcoco_test = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=_base_.test_pipeline, + backend_args=None) + +val_evaluator_refcoco_test = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +datasets = [val_dataset_all_val, val_dataset_refcoco_test] +dataset_prefixes = ['refcocog_val', 'refcocog_test'] +metrics = [val_evaluator_all_val, val_evaluator_refcoco_test] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1), + # 'language_model': dict(lr_mult=0), + })) + +# learning policy +max_epochs = 5 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[3], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +default_hooks = dict(checkpoint=dict(max_keep_ckpts=1, save_best='auto')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp.py b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp.py new file mode 100644 index 00000000..437d71c6 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp.py @@ -0,0 +1,228 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +# 30 is an empirical value, just set it to the maximum value +# without affecting the evaluation result +model = dict(test_cfg=dict(max_per_img=30)) + +data_root = 'data/coco/' + +test_pipeline = [ + dict( + type='LoadImageFromFile', backend_args=None, + imdecode_backend='pillow'), + dict( + type='FixScaleResize', + scale=(800, 1333), + keep_ratio=True, + backend='pillow'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities', + 'tokens_positive')) +] + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/final_refexp_val.json' +val_dataset_all_val = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) +val_evaluator_all_val = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco_testA.json' +val_dataset_refcoco_testA = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testA = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco_testB.json' +val_dataset_refcoco_testB = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcoco_testB = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco+_testA.json' +val_dataset_refcoco_plus_testA = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcoco_plus_testA = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcoco+_testB.json' +val_dataset_refcoco_plus_testB = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcoco_plus_testB = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_refcocog_test.json' +val_dataset_refcocog_test = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_refcocog_test = dict( + type='RefExpMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + topk=(1, 5, 10)) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_grefcoco_val.json' +val_dataset_grefcoco_val = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_grefcoco_val = dict( + type='gRefCOCOMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + thresh_score=0.7, + thresh_f1=1.0) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_grefcoco_testA.json' +val_dataset_grefcoco_testA = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_grefcoco_testA = dict( + type='gRefCOCOMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + thresh_score=0.7, + thresh_f1=1.0) + +# -------------------------------------------------# +ann_file = 'mdetr_annotations/finetune_grefcoco_testB.json' +val_dataset_grefcoco_testB = dict( + type='MDETRStyleRefCocoDataset', + data_root=data_root, + ann_file=ann_file, + data_prefix=dict(img='train2014/'), + test_mode=True, + return_classes=True, + pipeline=test_pipeline, + backend_args=None) + +val_evaluator_grefcoco_testB = dict( + type='gRefCOCOMetric', + ann_file=data_root + ann_file, + metric='bbox', + iou_thrs=0.5, + thresh_score=0.7, + thresh_f1=1.0) + +# -------------------------------------------------# +datasets = [ + val_dataset_all_val, val_dataset_refcoco_testA, val_dataset_refcoco_testB, + val_dataset_refcoco_plus_testA, val_dataset_refcoco_plus_testB, + val_dataset_refcocog_test, val_dataset_grefcoco_val, + val_dataset_grefcoco_testA, val_dataset_grefcoco_testB +] +dataset_prefixes = [ + 'val', 'refcoco_testA', 'refcoco_testB', 'refcoco+_testA', + 'refcoco+_testB', 'refcocog_test', 'grefcoco_val', 'grefcoco_testA', + 'grefcoco_testB' +] +metrics = [ + val_evaluator_all_val, val_evaluator_refcoco_testA, + val_evaluator_refcoco_testB, val_evaluator_refcoco_plus_testA, + val_evaluator_refcoco_plus_testB, val_evaluator_refcocog_test, + val_evaluator_grefcoco_val, val_evaluator_grefcoco_testA, + val_evaluator_grefcoco_testB +] + +val_dataloader = dict( + dataset=dict(_delete_=True, type='ConcatDataset', datasets=datasets)) +test_dataloader = val_dataloader + +val_evaluator = dict( + _delete_=True, + type='MultiDatasetsEvaluator', + metrics=metrics, + dataset_prefixes=dataset_prefixes) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/mm_grounding_dino/rtts/grounding_dino_swin-t_finetune_8xb4_1x_rtts.py b/mmdetection/configs/mm_grounding_dino/rtts/grounding_dino_swin-t_finetune_8xb4_1x_rtts.py new file mode 100644 index 00000000..95c2be05 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/rtts/grounding_dino_swin-t_finetune_8xb4_1x_rtts.py @@ -0,0 +1,106 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/RTTS/' +class_name = ('bicycle', 'bus', 'car', 'motorbike', 'person') +palette = [(255, 97, 0), (0, 201, 87), (176, 23, 31), (138, 43, 226), + (30, 144, 255)] + +metainfo = dict(classes=class_name, palette=palette) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +train_dataloader = dict( + sampler=dict(_delete_=True, type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + _delete_=True, + type='CocoDataset', + data_root=data_root, + metainfo=metainfo, + filter_cfg=dict(filter_empty_gt=False, min_size=32), + pipeline=train_pipeline, + return_classes=True, + ann_file='annotations_json/rtts_train.json', + data_prefix=dict(img=''))) + +val_dataloader = dict( + dataset=dict( + metainfo=metainfo, + data_root=data_root, + return_classes=True, + ann_file='annotations_json/rtts_val.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations_json/rtts_val.json', + metric='bbox', + format_only=False) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1) + })) + +# learning policy +max_epochs = 12 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) +default_hooks = dict(checkpoint=dict(max_keep_ckpts=1, save_best='auto')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/ruod/grounding_dino_swin-t_finetune_8xb4_1x_ruod.py b/mmdetection/configs/mm_grounding_dino/ruod/grounding_dino_swin-t_finetune_8xb4_1x_ruod.py new file mode 100644 index 00000000..f57682b2 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/ruod/grounding_dino_swin-t_finetune_8xb4_1x_ruod.py @@ -0,0 +1,108 @@ +_base_ = '../grounding_dino_swin-t_pretrain_obj365.py' + +data_root = 'data/RUOD/' +class_name = ('holothurian', 'echinus', 'scallop', 'starfish', 'fish', + 'corals', 'diver', 'cuttlefish', 'turtle', 'jellyfish') +palette = [(235, 211, 70), (106, 90, 205), (160, 32, 240), (176, 23, 31), + (142, 0, 0), (230, 0, 0), (106, 0, 228), (60, 100, 0), (80, 100, 0), + (70, 0, 0)] + +metainfo = dict(classes=class_name, palette=palette) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', 'text', + 'custom_entities')) +] + +train_dataloader = dict( + sampler=dict(_delete_=True, type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + _delete_=True, + type='CocoDataset', + data_root=data_root, + metainfo=metainfo, + filter_cfg=dict(filter_empty_gt=False, min_size=32), + pipeline=train_pipeline, + return_classes=True, + ann_file='RUOD_ANN/instances_train.json', + data_prefix=dict(img='RUOD_pic/train/'))) + +val_dataloader = dict( + dataset=dict( + metainfo=metainfo, + data_root=data_root, + return_classes=True, + ann_file='RUOD_ANN/instances_test.json', + data_prefix=dict(img='RUOD_pic/test/'))) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'RUOD_ANN/instances_test.json', + metric='bbox', + format_only=False) +test_evaluator = val_evaluator + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'backbone': dict(lr_mult=0.1) + })) + +# learning policy +max_epochs = 12 +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) +default_hooks = dict(checkpoint=dict(max_keep_ckpts=1, save_best='auto')) + +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa diff --git a/mmdetection/configs/mm_grounding_dino/usage.md b/mmdetection/configs/mm_grounding_dino/usage.md new file mode 100644 index 00000000..123c6638 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/usage.md @@ -0,0 +1,491 @@ +# Usage + +## Install + +After installing MMDet according to the instructions in the [get_started](../../docs/zh_cn/get_started.md) section, you need to install additional dependency packages: + +```shell +cd $MMDETROOT + +pip install -r requirements/multimodal.txt +pip install emoji ddd-dataset +pip install git+https://github.com/lvis-dataset/lvis-api.git" +``` + +Please note that since the LVIS third-party library does not currently support numpy 1.24, ensure that your numpy version meets the requirements. It is recommended to install numpy version 1.23. + +## Instructions + +### Download BERT Weight + +MM Grounding DINO uses BERT as its language model and requires access to https://huggingface.co/. If you encounter connection errors due to network access issues, you can download the necessary files on a computer with network access and save them locally. Finally, modify the `lang_model_name` field in the configuration file to the local path. For specific instructions, please refer to the following code: + +```python +from transformers import BertConfig, BertModel +from transformers import AutoTokenizer + +config = BertConfig.from_pretrained("bert-base-uncased") +model = BertModel.from_pretrained("bert-base-uncased", add_pooling_layer=False, config=config) +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") + +config.save_pretrained("your path/bert-base-uncased") +model.save_pretrained("your path/bert-base-uncased") +tokenizer.save_pretrained("your path/bert-base-uncased") +``` + +### Download NLTK Weight + +When MM Grounding DINO performs Phrase Grounding inference, it may extract noun phrases. Although it downloads specific models at runtime, considering that some users' running environments cannot connect to the internet, it is possible to download them in advance to the `~/nltk_data` path. + +```python +import nltk +nltk.download('punkt', download_dir='~/nltk_data') +nltk.download('averaged_perceptron_tagger', download_dir='~/nltk_data') +``` + +### Download MM Grounding DINO-T Weight + +For convenience in demonstration, you can download the MM Grounding DINO-T model weights in advance to the current path. + +```shell +wget load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa +``` + +## Inference + +Before inference, for a better experience of the inference effects on different images, it is recommended that you first download [these images](https://github.com/microsoft/X-Decoder/tree/main/inference_demo/images) to the current path. + +MM Grounding DINO supports four types of inference methods: Closed-Set Object Detection, Open Vocabulary Object Detection, Phrase Grounding, and Referential Expression Comprehension. The details are explained below. + +**(1) Closed-Set Object Detection** + +Since MM Grounding DINO is a pretrained model, it can theoretically be applied to any closed-set detection dataset. Currently, we support commonly used datasets such as coco/voc/cityscapes/objects365v1/lvis, etc. Below, we will use coco as an example. + +```shell +python demo/image_demo.py images/animals.png \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts '$: coco' +``` + +The predictions for `outputs/vis/animals.png` will be generated in the current directory, as shown in the following image. + +
    + +
    + +Since ostrich is not one of the 80 classes in COCO, it will not be detected. + +It's important to note that Objects365v1 and LVIS have a large number of categories. If you try to input all category names directly into the network, it may exceed 256 tokens, leading to poor model predictions. In such cases, you can use the `--chunked-size` parameter to perform chunked predictions. However, please be aware that chunked predictions may take longer to complete due to the large number of categories. + +```shell +python demo/image_demo.py images/animals.png \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts '$: lvis' --chunked-size 70 \ + --palette random +``` + +
    + +
    + +Different `--chunked-size` values can lead to different prediction results. You can experiment with different chunked sizes to find the one that works best for your specific task and dataset. + +**(2) Open Vocabulary Object Detection** + +Open vocabulary object detection refers to the ability to input arbitrary class names during inference. + +```shell +python demo/image_demo.py images/animals.png \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts 'zebra. giraffe' -c +``` + +
    + +
    + +**(3) Phrase Grounding** + +Phrase Grounding refers to the process where a user inputs a natural language description, and the model automatically detects the corresponding bounding boxes for the mentioned noun phrases. It can be used in two ways: + +1. Automatically extracting noun phrases using the NLTK library and then performing detection. + +```shell +python demo/image_demo.py images/apples.jpg \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts 'There are many apples here.' +``` + +
    + +
    + +The program will automatically split `many apples` as a noun phrase and then detect the corresponding objects. Different input descriptions can have a significant impact on the prediction results. + +2. Users can manually specify which parts of the sentence are noun phrases to avoid errors in NLTK extraction. + +```shell +python demo/image_demo.py images/fruit.jpg \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts 'The picture contains watermelon, flower, and a white bottle.' \ + --tokens-positive "[[[21,31]], [[45,59]]]" --pred-score-thr 0.12 +``` + +The noun phrase corresponding to positions 21-31 is `watermelon`, and the noun phrase corresponding to positions 45-59 is `a white bottle`. + +
    + +
    + +**(4) Referential Expression Comprehension** + +Referential expression understanding refers to the model automatically comprehending the referential expressions involved in a user's language description without the need for noun phrase extraction. + +```shell +python demo/image_demo.py images/apples.jpg \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts 'red apple.' \ + --tokens-positive -1 +``` + +
    + +
    + +## Evaluation + +Our provided evaluation scripts are unified, and you only need to prepare the data in advance and then run the relevant configuration. + +(1) Zero-Shot COCO2017 val + +```shell +# single GPU +python tools/test.py configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth + +# 8 GPUs +./tools/dist_test.sh configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth 8 +``` + +(2) Zero-Shot ODinW13 + +```shell +# single GPU +python tools/test.py configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth + +# 8 GPUs +./tools/dist_test.sh configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth 8 +``` + +## Visualization of Evaluation Results + +For the convenience of visualizing and analyzing model prediction results, we provide support for visualizing evaluation dataset prediction results. Taking referential expression understanding as an example, the usage is as follows: + +```shell +python tools/test.py configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth --work-dir refcoco_result --show-dir save_path +``` + +During the inference process, it will save the visualization results to the `refcoco_result/{current_timestamp}/save_path` directory. For other evaluation dataset visualizations, you only need to replace the configuration file. + +Here are some visualization results for various datasets. The left image represents the Ground Truth (GT). The right image represents the Predicted Result. + +1. COCO2017 val Results: + +
    + +
    + +2. Flickr30k Entities Results: + +
    + +
    + +3. DOD Results: + +
    + +
    + +4. RefCOCO val Results: + +
    + +
    + +5. RefCOCO testA Results: + +
    + +
    + +6. gRefCOCO val Results: + +
    + +
    + +## Training + +If you want to reproduce our results, you can train the model by using the following command after preparing the dataset: + +```shell +# Training on a single machine with 8 GPUs for obj365v1 dataset +./tools/dist_train.sh configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py 8 +# Training on a single machine with 8 GPUs for datasets like obj365v1, goldg, grit, v3det, and other datasets is similar. +./tools/dist_train.sh configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det.py 8 +``` + +For multi-machine training, please refer to [train.md](../../docs/zh_cn/user_guides/train.md). The MM-Grounding-DINO T model is designed to work with 32 GPUs (specifically, 3090Ti GPUs). If your total batch size is not 32x4=128, you will need to manually adjust the learning rate accordingly. + +### Pretraining Custom Format Explanation + +In order to standardize the pretraining formats for different datasets, we refer to the format design proposed by [Open-GroundingDino](https://github.com/longzw1997/Open-GroundingDino). Specifically, it is divided into two formats. + +**(1) Object Detection Format (OD)** + +```text +{"filename": "obj365_train_000000734304.jpg", + "height": 512, + "width": 769, + "detection": { + "instances": [ + {"bbox": [109.4768676992, 346.0190429696, 135.1918335098, 365.3641967616], "label": 2, "category": "chair"}, + {"bbox": [58.612365705900004, 323.2281494016, 242.6005859067, 451.4166870016], "label": 8, "category": "car"} + ] + } +} +``` + +The numerical values corresponding to labels in the label dictionary should match the respective label_map. Each item in the instances list corresponds to a bounding box (in the format x1y1x2y2). + +**(2) Phrase Grounding Format (VG)** + +```text +{"filename": "2405116.jpg", + "height": 375, + "width": 500, + "grounding": + {"caption": "Two surfers walking down the shore. sand on the beach.", + "regions": [ + {"bbox": [206, 156, 282, 248], "phrase": "Two surfers", "tokens_positive": [[0, 3], [4, 11]]}, + {"bbox": [303, 338, 443, 343], "phrase": "sand", "tokens_positive": [[36, 40]]}, + {"bbox": [[327, 223, 421, 282], [300, 200, 400, 210]], "phrase": "beach", "tokens_positive": [[48, 53]]} + ] + } +``` + +The `tokens_positive` field indicates the character positions of the current phrase within the caption. + +## Example of Fine-tuning Custom Dataset + +In order to facilitate downstream fine-tuning on custom datasets, we have provided a fine-tuning example using the simple "cat" dataset as an illustration. + +### 1 Data Preparation + +```shell +cd mmdetection +wget https://download.openmmlab.com/mmyolo/data/cat_dataset.zip +unzip cat_dataset.zip -d data/cat/ +``` + +The "cat" dataset is a single-category dataset consisting of 144 images, already converted to the COCO format. + +
    +cat dataset +
    + +### 2 Configuration Preparation + +Due to the simplicity and small size of the "cat" dataset, we trained it for 20 epochs using 8 GPUs, with corresponding learning rate scaling. We did not train the language model, only the visual model. + +Detailed configuration information can be found in [grounding_dino_swin-t_finetune_8xb4_20e_cat](grounding_dino_swin-t_finetune_8xb4_20e_cat.py). + +### 3 Visualization and Evaluation of Zero-Shot Results + +Due to MM Grounding DINO being an open-set detection model, you can perform detection and evaluation even if it was not trained on the cat dataset. + +Visualization of a single image: + +```shell +cd mmdetection +python demo/image_demo.py data/cat/images/IMG_20211205_120756.jpg configs/mm_grounding_dino/grounding_dino_swin-t_finetune_8xb4_20e_cat.py --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth --texts cat. +``` + +Evaluation results of Zero-shot on test dataset: + +```shell +python tools/test.py configs/mm_grounding_dino/grounding_dino_swin-t_finetune_8xb4_20e_cat.py grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth +``` + +```text + Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.881 + Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 1.000 + Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.929 + Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.881 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.913 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.913 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.913 + Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.913 +``` + +### 4 Fine-tuning + +```shell +./tools/dist_train.sh configs/mm_grounding_dino/grounding_dino_swin-t_finetune_8xb4_20e_cat.py 8 --work-dir cat_work_dir +``` + +The model will save the best-performing checkpoint. It achieved its best performance at the 16th epoch, with the following results: + +```text + Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.901 + Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 1.000 + Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.930 + Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.901 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.967 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.967 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.967 + Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.967 +``` + +We can observe that after fine-tuning, the training performance on the cat dataset improved from 88.1 to 90.1. However, due to the small dataset size, the evaluation metrics show some fluctuations. + +## Iterative Generation and Optimization Pipeline of Model Self-training Pseduo Label + +To facilitate users in creating their own datasets from scratch or those who want to leverage the model's inference capabilities for iterative pseudo-label generation and optimization, continuously modifying pseudo-labels to improve model performance, we have provided relevant pipelines. + +Since we have defined two data formats, we will provide separate explanations for demonstration purposes. + +### 1 Object Detection Format + +Here, we continue to use the aforementioned cat dataset as an example. Let's assume that we currently have a series of images and predefined categories but no annotations. + +1. Generate initial `odvg` format file + +```python +import os +import cv2 +import json +import jsonlines + +data_root = 'data/cat' +images_path = os.path.join(data_root, 'images') +out_path = os.path.join(data_root, 'cat_train_od.json') +metas = [] +for files in os.listdir(images_path): + img = cv2.imread(os.path.join(images_path, files)) + height, width, _ = img.shape + metas.append({"filename": files, "height": height, "width": width}) + +with jsonlines.open(out_path, mode='w') as writer: + writer.write_all(metas) + +# 生成 label_map.json,由于只有一个类别,所以只需要写一个 cat 即可 +label_map_path = os.path.join(data_root, 'cat_label_map.json') +with open(label_map_path, 'w') as f: + json.dump({'0': 'cat'}, f) +``` + +Two files, `cat_train_od.json` and `cat_label_map.json`, will be generated in the `data/cat` directory. + +2. Inference with pre-trained model and save the results + +We provide a readily usable [configuration](grounding_dino_swin-t_pretrain_pseudo-labeling_cat.py). If you are using a different dataset, you can refer to this configuration for modifications. + +```shell +python tools/test.py configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_cat.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth +``` + +A new file `cat_train_od_v1.json` will be generated in the `data/cat` directory. You can manually open it to confirm or use the provided [script](../../tools/analysis_tools/browse_grounding_raw.py) to visualize the results. + +```shell +python tools/analysis_tools/browse_grounding_raw.py data/cat/ cat_train_od_v1.json images --label-map-file cat_label_map.json -o your_output_dir --not-show +``` + +The visualization results will be generated in the `your_output_dir` directory. + +3. Continue training to boost performance + +After obtaining pseudo-labels, you can mix them with some pre-training data for further pre-training to improve the model's performance on the current dataset. Then, you can repeat step 2 to obtain more accurate pseudo-labels, and continue this iterative process. + +### 2 Phrase Grounding Format + +1. Generate initial `odvg` format file + +The bootstrapping process of Phrase Grounding requires providing captions corresponding to each image and pre-segmented phrase information initially. Taking flickr30k entities images as an example, the generated typical file should look like this: + +```text +[ +{"filename": "3028766968.jpg", + "height": 375, + "width": 500, + "grounding": + {"caption": "Man with a black shirt on sit behind a desk sorting threw a giant stack of people work with a smirk on his face .", + "regions": [ + {"bbox": [0, 0, 1, 1], "phrase": "a giant stack of people", "tokens_positive": [[58, 81]]}, + {"bbox": [0, 0, 1, 1], "phrase": "a black shirt", "tokens_positive": [[9, 22]]}, + {"bbox": [0, 0, 1, 1], "phrase": "a desk", "tokens_positive": [[37, 43]]}, + {"bbox": [0, 0, 1, 1], "phrase": "his face", "tokens_positive": [[103, 111]]}, + {"bbox": [0, 0, 1, 1], "phrase": "Man", "tokens_positive": [[0, 3]]}]}} +{"filename": "6944134083.jpg", + "height": 319, + "width": 500, + "grounding": + {"caption": "Two men are competing in a horse race .", + "regions": [ + {"bbox": [0, 0, 1, 1], "phrase": "Two men", "tokens_positive": [[0, 7]]}]}} +] +``` + +Bbox needs to be set to `[0, 0, 1, 1]` for initialization to make sure the programme could run, but this value would not be utilized. + +```text +{"filename": "3028766968.jpg", "height": 375, "width": 500, "grounding": {"caption": "Man with a black shirt on sit behind a desk sorting threw a giant stack of people work with a smirk on his face .", "regions": [{"bbox": [0, 0, 1, 1], "phrase": "a giant stack of people", "tokens_positive": [[58, 81]]}, {"bbox": [0, 0, 1, 1], "phrase": "a black shirt", "tokens_positive": [[9, 22]]}, {"bbox": [0, 0, 1, 1], "phrase": "a desk", "tokens_positive": [[37, 43]]}, {"bbox": [0, 0, 1, 1], "phrase": "his face", "tokens_positive": [[103, 111]]}, {"bbox": [0, 0, 1, 1], "phrase": "Man", "tokens_positive": [[0, 3]]}]}} +{"filename": "6944134083.jpg", "height": 319, "width": 500, "grounding": {"caption": "Two men are competing in a horse race .", "regions": [{"bbox": [0, 0, 1, 1], "phrase": "Two men", "tokens_positive": [[0, 7]]}]}} +``` + +You can directly copy the text above, and assume that the text content is pasted into a file named `flickr_simple_train_vg.json`, which is placed in the pre-prepared `data/flickr30k_entities` dataset directory, as detailed in the data preparation document. + +2. Inference with pre-trained model and save the results + +We provide a directly usable [configuration](https://chat.openai.com/c/grounding_dino_swin-t_pretrain_pseudo-labeling_flickr30k.py). If you are using a different dataset, you can refer to this configuration for modifications. + +```shell +python tools/test.py configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_flickr30k.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth +``` + +The translation of your text from Chinese to English is: "A new file `flickr_simple_train_vg_v1.json` will be generated in the `data/flickr30k_entities` directory. You can manually open it to confirm or use the [script](../../tools/analysis_tools/browse_grounding_raw.py) to visualize the effects + +```shell +python tools/analysis_tools/browse_grounding_raw.py data/flickr30k_entities/ flickr_simple_train_vg_v1.json flickr30k_images -o your_output_dir --not-show +``` + +The visualization results will be generated in the `your_output_dir` directory, as shown in the following image: + +
    + +
    + +3. Continue training to boost performance + +After obtaining the pseudo-labels, you can mix some pre-training data to continue pre-training jointly, which enhances the model's performance on the current dataset. Then, rerun step 2 to obtain more accurate pseudo-labels, and repeat this cycle iteratively. diff --git a/mmdetection/configs/mm_grounding_dino/usage_zh-CN.md b/mmdetection/configs/mm_grounding_dino/usage_zh-CN.md new file mode 100644 index 00000000..5f625ea6 --- /dev/null +++ b/mmdetection/configs/mm_grounding_dino/usage_zh-CN.md @@ -0,0 +1,491 @@ +# 用法说明 + +## 安装 + +在按照 [get_started](../../docs/zh_cn/get_started.md) 一节的说明安装好 MMDet 之后,需要安装额外的依赖包: + +```shell +cd $MMDETROOT + +pip install -r requirements/multimodal.txt +pip install emoji ddd-dataset +pip install git+https://github.com/lvis-dataset/lvis-api.git" +``` + +请注意由于 LVIS 第三方库暂时不支持 numpy 1.24,因此请确保您的 numpy 版本符合要求。建议安装 numpy 1.23 版本。 + +## 说明 + +### BERT 权重下载 + +MM Grounding DINO 采用了 BERT 作为语言模型,需要访问 https://huggingface.co/, 如果您因为网络访问问题遇到连接错误,可以在有网络访问权限的电脑上下载所需文件并保存在本地。最后,修改配置文件中的 `lang_model_name` 字段为本地路径即可。具体请参考以下代码: + +```python +from transformers import BertConfig, BertModel +from transformers import AutoTokenizer + +config = BertConfig.from_pretrained("bert-base-uncased") +model = BertModel.from_pretrained("bert-base-uncased", add_pooling_layer=False, config=config) +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") + +config.save_pretrained("your path/bert-base-uncased") +model.save_pretrained("your path/bert-base-uncased") +tokenizer.save_pretrained("your path/bert-base-uncased") +``` + +### NLTK 权重下载 + +MM Grounding DINO 在进行 Phrase Grounding 推理时候可能会进行名词短语提取,虽然会在运行时候下载特定的模型,但是考虑到有些用户运行环境无法联网,因此可以提前下载到 `~/nltk_data` 路径下 + +```python +import nltk +nltk.download('punkt', download_dir='~/nltk_data') +nltk.download('averaged_perceptron_tagger', download_dir='~/nltk_data') +``` + +### MM Grounding DINO-T 模型权重下载 + +为了方便演示,您可以提前下载 MM Grounding DINO-T 模型权重到当前路径下 + +```shell +wget load_from = 'https://download.openmmlab.com/mmdetection/v3.0/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth' # noqa +``` + +## 推理 + +在推理前,为了更好的体验不同图片的推理效果,建议您先下载 [这些图片](https://github.com/microsoft/X-Decoder/tree/main/inference_demo/images) 到当前路径下 + +MM Grounding DINO 支持了闭集目标检测,开放词汇目标检测,Phrase Grounding 和指代性表达式理解 4 种推理方式,下面详细说明。 + +**(1) 闭集目标检测** + +由于 MM Grounding DINO 是预训练模型,理论上可以应用于任何闭集检测数据集,目前我们支持了常用的 coco/voc/cityscapes/objects365v1/lvis 等,下面以 coco 为例 + +```shell +python demo/image_demo.py images/animals.png \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts '$: coco' +``` + +会在当前路径下生成 `outputs/vis/animals.png` 的预测结果,如下图所示 + +
    + +
    + +由于鸵鸟并不在 COCO 80 类中, 因此不会检测出来。 + +需要注意,由于 objects365v1 和 lvis 类别很多,如果直接将类别名全部输入到网络中,会超过 256 个 token 导致模型预测效果极差,此时我们需要通过 `--chunked-size` 参数进行截断预测, 同时预测时间会比较长。 + +```shell +python demo/image_demo.py images/animals.png \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts '$: lvis' --chunked-size 70 \ + --palette random +``` + +
    + +
    + +不同的 `--chunked-size` 会导致不同的预测效果,您可以自行尝试。 + +**(2) 开放词汇目标检测** + +开放词汇目标检测是指在推理时候,可以输入任意的类别名 + +```shell +python demo/image_demo.py images/animals.png \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts 'zebra. giraffe' -c +``` + +
    + +
    + +**(3) Phrase Grounding** + +Phrase Grounding 是指的用户输入一句语言描述,模型自动对其涉及到的名词短语想对应的 bbox 进行检测,有两种用法 + +1. 通过 NLTK 库自动提取名词短语,然后进行检测 + +```shell +python demo/image_demo.py images/apples.jpg \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts 'There are many apples here.' +``` + +
    + +
    + +程序内部会自动切分出 `many apples` 作为名词短语,然后检测出对应物体。不同的输入描述对预测结果影响很大。 + +2. 用户自己指定句子中哪些为名词短语,避免 NLTK 提取错误的情况 + +```shell +python demo/image_demo.py images/fruit.jpg \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts 'The picture contains watermelon, flower, and a white bottle.' \ + --tokens-positive "[[[21,31]], [[45,59]]]" --pred-score-thr 0.12 +``` + +21,31 对应的名词短语为 `watermelon`,45,59 对应的名词短语为 `a white bottle`。 + +
    + +
    + +**(4) 指代性表达式理解** + +指代性表达式理解是指的用户输入一句语言描述,模型自动对其涉及到的指代性表达式进行理解, 不需要进行名词短语提取。 + +```shell +python demo/image_demo.py images/apples.jpg \ + configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth \ + --texts 'red apple.' \ + --tokens-positive -1 +``` + +
    + +
    + +## 评测 + +我们所提供的评测脚本都是统一的,你只需要提前准备好数据,然后运行相关配置就可以了 + +(1) Zero-Shot COCO2017 val + +```shell +# 单卡 +python tools/test.py configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth + +# 8 卡 +./tools/dist_test.sh configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth 8 +``` + +(2) Zero-Shot ODinW13 + +```shell +# 单卡 +python tools/test.py configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth + +# 8 卡 +./tools/dist_test.sh configs/mm_grounding_dino/odinw/grounding_dino_swin-t_pretrain_odinw13.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth 8 +``` + +## 评测数据集结果可视化 + +为了方便大家对模型预测结果进行可视化和分析,我们支持了评测数据集预测结果可视化,以指代性表达式理解为例用法如下: + +```shell +python tools/test.py configs/mm_grounding_dino/refcoco/grounding_dino_swin-t_pretrain_zeroshot_refexp \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth --work-dir refcoco_result --show-dir save_path +``` + +模型在推理过程中会将可视化结果保存到 `refcoco_result/{当前时间戳}/save_path` 路径下。其余评测数据集可视化只需要替换配置文件即可。 + +下面展示一些数据集的可视化结果: 左图为 GT,右图为预测结果 + +1. COCO2017 val 结果: + +
    + +
    + +2. Flickr30k Entities 结果: + +
    + +
    + +3. DOD 结果: + +
    + +
    + +4. RefCOCO val 结果: + +
    + +
    + +5. RefCOCO testA 结果: + +
    + +
    + +6. gRefCOCO val 结果: + +
    + +
    + +## 模型训练 + +如果想复现我们的结果,你可以在准备好数据集后,直接通过如下命令进行训练 + +```shell +# 单机 8 卡训练仅包括 obj365v1 数据集 +./tools/dist_train.sh configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365.py 8 +# 单机 8 卡训练包括 obj365v1/goldg/grit/v3det 数据集,其余数据集类似 +./tools/dist_train.sh configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det.py 8 +``` + +多机训练的用法请参考 [train.md](../../docs/zh_cn/user_guides/train.md)。MM-Grounding-DINO T 模型默认采用的是 32 张 3090Ti,如果你的总 bs 数不是 32x4=128,那么你需要手动的线性调整学习率。 + +### 预训练自定义格式说明 + +为了统一不同数据集的预训练格式,我们参考 [Open-GroundingDino](https://github.com/longzw1997/Open-GroundingDino) 所设计的格式。具体来说分成 2 种格式 + +**(1) 目标检测数据格式 OD** + +```text +{"filename": "obj365_train_000000734304.jpg", + "height": 512, + "width": 769, + "detection": { + "instances": [ + {"bbox": [109.4768676992, 346.0190429696, 135.1918335098, 365.3641967616], "label": 2, "category": "chair"}, + {"bbox": [58.612365705900004, 323.2281494016, 242.6005859067, 451.4166870016], "label": 8, "category": "car"} + ] + } +} +``` + +label字典中所对应的数值需要和相应的 label_map 一致。 instances 列表中的每一项都对应一个 bbox (x1y1x2y2 格式)。 + +**(2) phrase grounding 数据格式 VG** + +```text +{"filename": "2405116.jpg", + "height": 375, + "width": 500, + "grounding": + {"caption": "Two surfers walking down the shore. sand on the beach.", + "regions": [ + {"bbox": [206, 156, 282, 248], "phrase": "Two surfers", "tokens_positive": [[0, 3], [4, 11]]}, + {"bbox": [303, 338, 443, 343], "phrase": "sand", "tokens_positive": [[36, 40]]}, + {"bbox": [[327, 223, 421, 282], [300, 200, 400, 210]], "phrase": "beach", "tokens_positive": [[48, 53]]} + ] + } +``` + +tokens_positive 表示当前 phrase 在 caption 中的字符位置。 + +## 自定义数据集微调训练案例 + +为了方便用户针对自定义数据集进行下游微调,我们特意提供了以简单的 cat 数据集为例的微调训练案例。 + +### 1 数据准备 + +```shell +cd mmdetection +wget https://download.openmmlab.com/mmyolo/data/cat_dataset.zip +unzip cat_dataset.zip -d data/cat/ +``` + +cat 数据集是一个单类别数据集,包含 144 张图片,已经转换为 coco 格式。 + +
    +cat dataset +
    + +### 2 配置准备 + +由于 cat 数据集的简单性和数量较少,我们使用 8 卡训练 20 个 epoch,相应的缩放学习率,不训练语言模型,只训练视觉模型。 + +详细的配置信息可以在 [grounding_dino_swin-t_finetune_8xb4_20e_cat](grounding_dino_swin-t_finetune_8xb4_20e_cat.py) 中找到。 + +### 3 可视化和 Zero-Shot 评估 + +由于 MM Grounding DINO 是一个开放的检测模型,所以即使没有在 cat 数据集上训练,也可以进行检测和评估。 + +单张图片的可视化结果如下: + +```shell +cd mmdetection +python demo/image_demo.py data/cat/images/IMG_20211205_120756.jpg configs/mm_grounding_dino/grounding_dino_swin-t_finetune_8xb4_20e_cat.py --weights grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth --texts cat. +``` + +测试集上的 Zero-Shot 评估结果如下: + +```shell +python tools/test.py configs/mm_grounding_dino/grounding_dino_swin-t_finetune_8xb4_20e_cat.py grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth +``` + +```text + Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.881 + Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 1.000 + Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.929 + Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.881 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.913 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.913 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.913 + Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.913 +``` + +### 4 模型训练 + +```shell +./tools/dist_train.sh configs/mm_grounding_dino/grounding_dino_swin-t_finetune_8xb4_20e_cat.py 8 --work-dir cat_work_dir +``` + +模型将会保存性能最佳的模型。在第 16 epoch 时候达到最佳,性能如下所示: + +```text + Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.901 + Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 1.000 + Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.930 + Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.901 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.967 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.967 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.967 + Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = -1.000 + Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.967 +``` + +我们可以发现,经过微调训练后,cat 数据集的训练性能从 88.1 提升到了 90.1。同时由于数据集比较小,评估指标波动比较大。 + +## 模型自训练伪标签迭代生成和优化 pipeline + +为了方便用户从头构建自己的数据集或者希望利用模型推理能力进行自举式伪标签迭代生成和优化,不断修改伪标签来提升模型性能,我们特意提供了相关的 pipeline。 + +由于我们定义了两种数据格式,为了演示我们也将分别进行说明。 + +### 1 目标检测格式 + +此处我们依然采用上述的 cat 数据集为例,假设我们目前只有一系列图片和预定义的类别,并不存在标注。 + +1. 生成初始 odvg 格式文件 + +```python +import os +import cv2 +import json +import jsonlines + +data_root = 'data/cat' +images_path = os.path.join(data_root, 'images') +out_path = os.path.join(data_root, 'cat_train_od.json') +metas = [] +for files in os.listdir(images_path): + img = cv2.imread(os.path.join(images_path, files)) + height, width, _ = img.shape + metas.append({"filename": files, "height": height, "width": width}) + +with jsonlines.open(out_path, mode='w') as writer: + writer.write_all(metas) + +# 生成 label_map.json,由于只有一个类别,所以只需要写一个 cat 即可 +label_map_path = os.path.join(data_root, 'cat_label_map.json') +with open(label_map_path, 'w') as f: + json.dump({'0': 'cat'}, f) +``` + +会在 `data/cat` 目录下生成 `cat_train_od.json` 和 `cat_label_map.json` 两个文件。 + +2. 使用预训练模型进行推理,并保存结果 + +我们提供了直接可用的 [配置](grounding_dino_swin-t_pretrain_pseudo-labeling_cat.py), 如果你是其他数据集可以参考这个配置进行修改。 + +```shell +python tools/test.py configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_cat.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth +``` + +会在 `data/cat` 目录下新生成 `cat_train_od_v1.json` 文件,你可以手动打开确认或者使用 [脚本](../../tools/analysis_tools/browse_grounding_raw.py) 可视化效果 + +```shell +python tools/analysis_tools/browse_grounding_raw.py data/cat/ cat_train_od_v1.json images --label-map-file cat_label_map.json -o your_output_dir --not-show +``` + +会在 your_output_dir 目录下生成可视化结果 + +3. 继续训练提高性能 + +在得到伪标签后,你可以混合一些预训练数据联合进行继续预训练,提升模型在当前数据集上的性能,然后重新运行 2 步骤,得到更准确的伪标签,如此循环迭代即可。 + +### 2 Phrase Grounding 格式 + +1. 生成初始 odvg 格式文件 + +Phrase Grounding 的自举流程要求初始时候提供每张图片对应的 caption 和提前切割好的 phrase 信息。以 flickr30k entities 图片为例,生成的典型的文件应该如下所示: + +```text +[ +{"filename": "3028766968.jpg", + "height": 375, + "width": 500, + "grounding": + {"caption": "Man with a black shirt on sit behind a desk sorting threw a giant stack of people work with a smirk on his face .", + "regions": [ + {"bbox": [0, 0, 1, 1], "phrase": "a giant stack of people", "tokens_positive": [[58, 81]]}, + {"bbox": [0, 0, 1, 1], "phrase": "a black shirt", "tokens_positive": [[9, 22]]}, + {"bbox": [0, 0, 1, 1], "phrase": "a desk", "tokens_positive": [[37, 43]]}, + {"bbox": [0, 0, 1, 1], "phrase": "his face", "tokens_positive": [[103, 111]]}, + {"bbox": [0, 0, 1, 1], "phrase": "Man", "tokens_positive": [[0, 3]]}]}} +{"filename": "6944134083.jpg", + "height": 319, + "width": 500, + "grounding": + {"caption": "Two men are competing in a horse race .", + "regions": [ + {"bbox": [0, 0, 1, 1], "phrase": "Two men", "tokens_positive": [[0, 7]]}]}} +] +``` + +初始时候 bbox 必须要设置为 `[0, 0, 1, 1]`,因为这能确保程序正常运行,但是 bbox 的值并不会被使用。 + +```text +{"filename": "3028766968.jpg", "height": 375, "width": 500, "grounding": {"caption": "Man with a black shirt on sit behind a desk sorting threw a giant stack of people work with a smirk on his face .", "regions": [{"bbox": [0, 0, 1, 1], "phrase": "a giant stack of people", "tokens_positive": [[58, 81]]}, {"bbox": [0, 0, 1, 1], "phrase": "a black shirt", "tokens_positive": [[9, 22]]}, {"bbox": [0, 0, 1, 1], "phrase": "a desk", "tokens_positive": [[37, 43]]}, {"bbox": [0, 0, 1, 1], "phrase": "his face", "tokens_positive": [[103, 111]]}, {"bbox": [0, 0, 1, 1], "phrase": "Man", "tokens_positive": [[0, 3]]}]}} +{"filename": "6944134083.jpg", "height": 319, "width": 500, "grounding": {"caption": "Two men are competing in a horse race .", "regions": [{"bbox": [0, 0, 1, 1], "phrase": "Two men", "tokens_positive": [[0, 7]]}]}} +``` + +你可直接复制上面的文本,并假设将文本内容粘贴到命名为 `flickr_simple_train_vg.json` 文件中,并放置于提前准备好的 `data/flickr30k_entities` 数据集目录下,具体见数据准备文档。 + +2. 使用预训练模型进行推理,并保存结果 + +我们提供了直接可用的 [配置](grounding_dino_swin-t_pretrain_pseudo-labeling_flickr30k.py), 如果你是其他数据集可以参考这个配置进行修改。 + +```shell +python tools/test.py configs/mm_grounding_dino/grounding_dino_swin-t_pretrain_pseudo-labeling_flickr30k.py \ + grounding_dino_swin-t_pretrain_obj365_goldg_grit9m_v3det_20231204_095047-b448804b.pth +``` + +会在 `data/flickr30k_entities` 目录下新生成 `flickr_simple_train_vg_v1.json` 文件,你可以手动打开确认或者使用 [脚本](../../tools/analysis_tools/browse_grounding_raw.py) 可视化效果 + +```shell +python tools/analysis_tools/browse_grounding_raw.py data/flickr30k_entities/ flickr_simple_train_vg_v1.json flickr30k_images -o your_output_dir --not-show +``` + +会在 `your_output_dir` 目录下生成可视化结果,如下图所示: + +
    + +
    + +3. 继续训练提高性能 + +在得到伪标签后,你可以混合一些预训练数据联合进行继续预训练,提升模型在当前数据集上的性能,然后重新运行 2 步骤,得到更准确的伪标签,如此循环迭代即可。 diff --git a/mmdetection/configs/ms_rcnn/README.md b/mmdetection/configs/ms_rcnn/README.md new file mode 100644 index 00000000..abbec9b6 --- /dev/null +++ b/mmdetection/configs/ms_rcnn/README.md @@ -0,0 +1,36 @@ +# MS R-CNN + +> [Mask Scoring R-CNN](https://arxiv.org/abs/1903.00241) + + + +## Abstract + +Letting a deep network be aware of the quality of its own predictions is an interesting yet important problem. In the task of instance segmentation, the confidence of instance classification is used as mask quality score in most instance segmentation frameworks. However, the mask quality, quantified as the IoU between the instance mask and its ground truth, is usually not well correlated with classification score. In this paper, we study this problem and propose Mask Scoring R-CNN which contains a network block to learn the quality of the predicted instance masks. The proposed network block takes the instance feature and the corresponding predicted mask together to regress the mask IoU. The mask scoring strategy calibrates the misalignment between mask quality and mask score, and improves instance segmentation performance by prioritizing more accurate mask predictions during COCO AP evaluation. By extensive evaluations on the COCO dataset, Mask Scoring R-CNN brings consistent and noticeable gain with different models, and outperforms the state-of-the-art Mask R-CNN. We hope our simple and effective approach will provide a new direction for improving instance segmentation. + +
    + +
    + +## Results and Models + +| Backbone | style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :----------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :-------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | caffe | 1x | 4.5 | | 38.2 | 36.0 | [config](./ms-rcnn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r50_caffe_fpn_1x_coco/ms_rcnn_r50_caffe_fpn_1x_coco_20200702_180848-61c9355e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r50_caffe_fpn_1x_coco/ms_rcnn_r50_caffe_fpn_1x_coco_20200702_180848.log.json) | +| R-50-FPN | caffe | 2x | - | - | 38.8 | 36.3 | [config](./ms-rcnn_r50-caffe_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r50_caffe_fpn_2x_coco/ms_rcnn_r50_caffe_fpn_2x_coco_bbox_mAP-0.388__segm_mAP-0.363_20200506_004738-ee87b137.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r50_caffe_fpn_2x_coco/ms_rcnn_r50_caffe_fpn_2x_coco_20200506_004738.log.json) | +| R-101-FPN | caffe | 1x | 6.5 | | 40.4 | 37.6 | [config](./ms-rcnn_r101-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r101_caffe_fpn_1x_coco/ms_rcnn_r101_caffe_fpn_1x_coco_bbox_mAP-0.404__segm_mAP-0.376_20200506_004755-b9b12a37.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r101_caffe_fpn_1x_coco/ms_rcnn_r101_caffe_fpn_1x_coco_20200506_004755.log.json) | +| R-101-FPN | caffe | 2x | - | - | 41.1 | 38.1 | [config](./ms-rcnn_r101-caffe_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r101_caffe_fpn_2x_coco/ms_rcnn_r101_caffe_fpn_2x_coco_bbox_mAP-0.411__segm_mAP-0.381_20200506_011134-5f3cc74f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r101_caffe_fpn_2x_coco/ms_rcnn_r101_caffe_fpn_2x_coco_20200506_011134.log.json) | +| R-X101-32x4d | pytorch | 2x | 7.9 | 11.0 | 41.8 | 38.7 | [config](./ms-rcnn_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_x101_32x4d_fpn_1x_coco/ms_rcnn_x101_32x4d_fpn_1x_coco_20200206-81fd1740.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_x101_32x4d_fpn_1x_coco/ms_rcnn_x101_32x4d_fpn_1x_coco_20200206_100113.log.json) | +| R-X101-64x4d | pytorch | 1x | 11.0 | 8.0 | 43.0 | 39.5 | [config](./ms-rcnn_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_x101_64x4d_fpn_1x_coco/ms_rcnn_x101_64x4d_fpn_1x_coco_20200206-86ba88d2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_x101_64x4d_fpn_1x_coco/ms_rcnn_x101_64x4d_fpn_1x_coco_20200206_091744.log.json) | +| R-X101-64x4d | pytorch | 2x | 11.0 | 8.0 | 42.6 | 39.5 | [config](./ms-rcnn_x101-64x4d_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_x101_64x4d_fpn_2x_coco/ms_rcnn_x101_64x4d_fpn_2x_coco_20200308-02a445e2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_x101_64x4d_fpn_2x_coco/ms_rcnn_x101_64x4d_fpn_2x_coco_20200308_012247.log.json) | + +## Citation + +```latex +@inproceedings{huang2019msrcnn, + title={Mask Scoring R-CNN}, + author={Zhaojin Huang and Lichao Huang and Yongchao Gong and Chang Huang and Xinggang Wang}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + year={2019}, +} +``` diff --git a/mmdetection/configs/ms_rcnn/metafile.yml b/mmdetection/configs/ms_rcnn/metafile.yml new file mode 100644 index 00000000..290f0543 --- /dev/null +++ b/mmdetection/configs/ms_rcnn/metafile.yml @@ -0,0 +1,159 @@ +Collections: + - Name: Mask Scoring R-CNN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RPN + - FPN + - ResNet + - RoIAlign + Paper: + URL: https://arxiv.org/abs/1903.00241 + Title: 'Mask Scoring R-CNN' + README: configs/ms_rcnn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/mask_scoring_rcnn.py#L6 + Version: v2.0.0 + +Models: + - Name: ms-rcnn_r50-caffe_fpn_1x_coco + In Collection: Mask Scoring R-CNN + Config: configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.5 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r50_caffe_fpn_1x_coco/ms_rcnn_r50_caffe_fpn_1x_coco_20200702_180848-61c9355e.pth + + - Name: ms-rcnn_r50-caffe_fpn_2x_coco + In Collection: Mask Scoring R-CNN + Config: configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r50_caffe_fpn_2x_coco/ms_rcnn_r50_caffe_fpn_2x_coco_bbox_mAP-0.388__segm_mAP-0.363_20200506_004738-ee87b137.pth + + - Name: ms-rcnn_r101-caffe_fpn_1x_coco + In Collection: Mask Scoring R-CNN + Config: configs/ms_rcnn/ms-rcnn_r101-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.5 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r101_caffe_fpn_1x_coco/ms_rcnn_r101_caffe_fpn_1x_coco_bbox_mAP-0.404__segm_mAP-0.376_20200506_004755-b9b12a37.pth + + - Name: ms-rcnn_r101-caffe_fpn_2x_coco + In Collection: Mask Scoring R-CNN + Config: configs/ms_rcnn/ms-rcnn_r101-caffe_fpn_2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_r101_caffe_fpn_2x_coco/ms_rcnn_r101_caffe_fpn_2x_coco_bbox_mAP-0.411__segm_mAP-0.381_20200506_011134-5f3cc74f.pth + + - Name: ms-rcnn_x101-32x4d_fpn_1x_coco + In Collection: Mask Scoring R-CNN + Config: configs/ms_rcnn/ms-rcnn_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.9 + inference time (ms/im): + - value: 90.91 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_x101_32x4d_fpn_1x_coco/ms_rcnn_x101_32x4d_fpn_1x_coco_20200206-81fd1740.pth + + - Name: ms-rcnn_x101-64x4d_fpn_1x_coco + In Collection: Mask Scoring R-CNN + Config: configs/ms_rcnn/ms-rcnn_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 11.0 + inference time (ms/im): + - value: 125 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_x101_64x4d_fpn_1x_coco/ms_rcnn_x101_64x4d_fpn_1x_coco_20200206-86ba88d2.pth + + - Name: ms-rcnn_x101-64x4d_fpn_2x_coco + In Collection: Mask Scoring R-CNN + Config: configs/ms_rcnn/ms-rcnn_x101-64x4d_fpn_2x_coco.py + Metadata: + Training Memory (GB): 11.0 + inference time (ms/im): + - value: 125 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ms_rcnn/ms_rcnn_x101_64x4d_fpn_2x_coco/ms_rcnn_x101_64x4d_fpn_2x_coco_20200308-02a445e2.pth diff --git a/mmdetection/configs/ms_rcnn/ms-rcnn_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/ms_rcnn/ms-rcnn_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..2ff4f2d6 --- /dev/null +++ b/mmdetection/configs/ms_rcnn/ms-rcnn_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './ms-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/ms_rcnn/ms-rcnn_r101-caffe_fpn_2x_coco.py b/mmdetection/configs/ms_rcnn/ms-rcnn_r101-caffe_fpn_2x_coco.py new file mode 100644 index 00000000..54b29e4f --- /dev/null +++ b/mmdetection/configs/ms_rcnn/ms-rcnn_r101-caffe_fpn_2x_coco.py @@ -0,0 +1,17 @@ +_base_ = './ms-rcnn_r101-caffe_fpn_1x_coco.py' +# learning policy +max_epochs = 24 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..e7fbc51f --- /dev/null +++ b/mmdetection/configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50-caffe_fpn_1x_coco.py' +model = dict( + type='MaskScoringRCNN', + roi_head=dict( + type='MaskScoringRoIHead', + mask_iou_head=dict( + type='MaskIoUHead', + num_convs=4, + num_fcs=2, + roi_feat_size=14, + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + num_classes=80)), + # model training and testing settings + train_cfg=dict(rcnn=dict(mask_thr_binary=0.5))) diff --git a/mmdetection/configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_2x_coco.py b/mmdetection/configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_2x_coco.py new file mode 100644 index 00000000..03348822 --- /dev/null +++ b/mmdetection/configs/ms_rcnn/ms-rcnn_r50-caffe_fpn_2x_coco.py @@ -0,0 +1,17 @@ +_base_ = './ms-rcnn_r50-caffe_fpn_1x_coco.py' +# learning policy +max_epochs = 24 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/ms_rcnn/ms-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/ms_rcnn/ms-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..0ae47d1c --- /dev/null +++ b/mmdetection/configs/ms_rcnn/ms-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + type='MaskScoringRCNN', + roi_head=dict( + type='MaskScoringRoIHead', + mask_iou_head=dict( + type='MaskIoUHead', + num_convs=4, + num_fcs=2, + roi_feat_size=14, + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + num_classes=80)), + # model training and testing settings + train_cfg=dict(rcnn=dict(mask_thr_binary=0.5))) diff --git a/mmdetection/configs/ms_rcnn/ms-rcnn_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/ms_rcnn/ms-rcnn_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..1a5d0d0f --- /dev/null +++ b/mmdetection/configs/ms_rcnn/ms-rcnn_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './ms-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/ms_rcnn/ms-rcnn_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/ms_rcnn/ms-rcnn_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..16290076 --- /dev/null +++ b/mmdetection/configs/ms_rcnn/ms-rcnn_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './ms-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/ms_rcnn/ms-rcnn_x101-64x4d_fpn_2x_coco.py b/mmdetection/configs/ms_rcnn/ms-rcnn_x101-64x4d_fpn_2x_coco.py new file mode 100644 index 00000000..7aec1874 --- /dev/null +++ b/mmdetection/configs/ms_rcnn/ms-rcnn_x101-64x4d_fpn_2x_coco.py @@ -0,0 +1,17 @@ +_base_ = './ms-rcnn_x101-64x4d_fpn_1x_coco.py' +# learning policy +max_epochs = 24 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/nas_fcos/README.md b/mmdetection/configs/nas_fcos/README.md new file mode 100644 index 00000000..a0ec77c8 --- /dev/null +++ b/mmdetection/configs/nas_fcos/README.md @@ -0,0 +1,35 @@ +# NAS-FCOS + +> [NAS-FCOS: Fast Neural Architecture Search for Object Detection](https://arxiv.org/abs/1906.04423) + + + +## Abstract + +The success of deep neural networks relies on significant architecture engineering. Recently neural architecture search (NAS) has emerged as a promise to greatly reduce manual effort in network design by automatically searching for optimal architectures, although typically such algorithms need an excessive amount of computational resources, e.g., a few thousand GPU-days. To date, on challenging vision tasks such as object detection, NAS, especially fast versions of NAS, is less studied. Here we propose to search for the decoder structure of object detectors with search efficiency being taken into consideration. To be more specific, we aim to efficiently search for the feature pyramid network (FPN) as well as the prediction head of a simple anchor-free object detector, namely FCOS, using a tailored reinforcement learning paradigm. With carefully designed search space, search algorithms and strategies for evaluating network quality, we are able to efficiently search a top-performing detection architecture within 4 days using 8 V100 GPUs. The discovered architecture surpasses state-of-the-art object detection models (such as Faster R-CNN, RetinaNet and FCOS) by 1.5 to 3.5 points in AP on the COCO dataset, with comparable computation complexity and memory footprint, demonstrating the efficacy of the proposed NAS for object detection. + +
    + +
    + +## Results and Models + +| Head | Backbone | Style | GN-head | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :----------: | :------: | :---: | :-----: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| NAS-FCOSHead | R-50 | caffe | Y | 1x | | | 39.4 | [config](./nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/nas_fcos/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco_20200520-1bdba3ce.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/nas_fcos/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco_20200520.log.json) | +| FCOSHead | R-50 | caffe | Y | 1x | | | 38.5 | [config](./nas-fcos_r50-caffe_fpn_fcoshead-gn-head_4xb4-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/nas_fcos/nas_fcos_fcoshead_r50_caffe_fpn_gn-head_4x4_1x_coco/nas_fcos_fcoshead_r50_caffe_fpn_gn-head_4x4_1x_coco_20200521-7fdcbce0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/nas_fcos/nas_fcos_fcoshead_r50_caffe_fpn_gn-head_4x4_1x_coco/nas_fcos_fcoshead_r50_caffe_fpn_gn-head_4x4_1x_coco_20200521.log.json) | + +**Notes:** + +- To be consistent with the author's implementation, we use 4 GPUs with 4 images/GPU. + +## Citation + +```latex +@article{wang2019fcos, + title={Nas-fcos: Fast neural architecture search for object detection}, + author={Wang, Ning and Gao, Yang and Chen, Hao and Wang, Peng and Tian, Zhi and Shen, Chunhua}, + journal={arXiv preprint arXiv:1906.04423}, + year={2019} +} +``` diff --git a/mmdetection/configs/nas_fcos/metafile.yml b/mmdetection/configs/nas_fcos/metafile.yml new file mode 100644 index 00000000..02292a41 --- /dev/null +++ b/mmdetection/configs/nas_fcos/metafile.yml @@ -0,0 +1,44 @@ +Collections: + - Name: NAS-FCOS + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 4x V100 GPUs + Architecture: + - FPN + - NAS-FCOS + - ResNet + Paper: + URL: https://arxiv.org/abs/1906.04423 + Title: 'NAS-FCOS: Fast Neural Architecture Search for Object Detection' + README: configs/nas_fcos/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/detectors/nasfcos.py#L6 + Version: v2.1.0 + +Models: + - Name: nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco + In Collection: NAS-FCOS + Config: configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/nas_fcos/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco/nas_fcos_nashead_r50_caffe_fpn_gn-head_4x4_1x_coco_20200520-1bdba3ce.pth + + - Name: nas-fcos_r50-caffe_fpn_fcoshead-gn-head_4xb4-1x_coco + In Collection: NAS-FCOS + Config: configs/nas_fcos/nas-fcos_r50-caffe_fpn_fcoshead-gn-head_4xb4-1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/nas_fcos/nas_fcos_fcoshead_r50_caffe_fpn_gn-head_4x4_1x_coco/nas_fcos_fcoshead_r50_caffe_fpn_gn-head_4x4_1x_coco_20200521-7fdcbce0.pth diff --git a/mmdetection/configs/nas_fcos/nas-fcos_r50-caffe_fpn_fcoshead-gn-head_4xb4-1x_coco.py b/mmdetection/configs/nas_fcos/nas-fcos_r50-caffe_fpn_fcoshead-gn-head_4xb4-1x_coco.py new file mode 100644 index 00000000..ba207c9f --- /dev/null +++ b/mmdetection/configs/nas_fcos/nas-fcos_r50-caffe_fpn_fcoshead-gn-head_4xb4-1x_coco.py @@ -0,0 +1,75 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# model settings +model = dict( + type='NASFCOS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False, eps=0), + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + neck=dict( + type='NASFCOS_FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs=True, + num_outs=5, + norm_cfg=dict(type='BN'), + conv_cfg=dict(type='DCNv2', deform_groups=2)), + bbox_head=dict( + type='FCOSHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + norm_cfg=dict(type='GN', num_groups=32), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# dataset settings +train_dataloader = dict(batch_size=4, num_workers=2) + +# optimizer +optim_wrapper = dict( + optimizer=dict(lr=0.01), + paramwise_cfg=dict(bias_lr_mult=2., bias_decay_mult=0.)) diff --git a/mmdetection/configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py b/mmdetection/configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py new file mode 100644 index 00000000..329f34c4 --- /dev/null +++ b/mmdetection/configs/nas_fcos/nas-fcos_r50-caffe_fpn_nashead-gn-head_4xb4-1x_coco.py @@ -0,0 +1,74 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# model settings +model = dict( + type='NASFCOS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False, eps=0), + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + neck=dict( + type='NASFCOS_FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs=True, + num_outs=5, + norm_cfg=dict(type='BN'), + conv_cfg=dict(type='DCNv2', deform_groups=2)), + bbox_head=dict( + type='NASFCOSHead', + num_classes=80, + in_channels=256, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + norm_cfg=dict(type='GN', num_groups=32), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# dataset settings +train_dataloader = dict(batch_size=4, num_workers=2) + +# optimizer +optim_wrapper = dict( + optimizer=dict(lr=0.01), + paramwise_cfg=dict(bias_lr_mult=2., bias_decay_mult=0.)) diff --git a/mmdetection/configs/nas_fpn/README.md b/mmdetection/configs/nas_fpn/README.md new file mode 100644 index 00000000..260ec470 --- /dev/null +++ b/mmdetection/configs/nas_fpn/README.md @@ -0,0 +1,36 @@ +# NAS-FPN + +> [NAS-FPN: Learning Scalable Feature Pyramid Architecture for Object Detection](https://arxiv.org/abs/1904.07392) + + + +## Abstract + +Current state-of-the-art convolutional architectures for object detection are manually designed. Here we aim to learn a better architecture of feature pyramid network for object detection. We adopt Neural Architecture Search and discover a new feature pyramid architecture in a novel scalable search space covering all cross-scale connections. The discovered architecture, named NAS-FPN, consists of a combination of top-down and bottom-up connections to fuse features across scales. NAS-FPN, combined with various backbone models in the RetinaNet framework, achieves better accuracy and latency tradeoff compared to state-of-the-art object detection models. NAS-FPN improves mobile detection accuracy by 2 AP compared to state-of-the-art SSDLite with MobileNetV2 model in \[32\] and achieves 48.3 AP which surpasses Mask R-CNN \[10\] detection accuracy with less computation time. + +
    + +
    + +## Results and Models + +We benchmark the new training schedule (crop training, large batch, unfrozen BN, 50 epochs) introduced in NAS-FPN. RetinaNet is used in the paper. + +| Backbone | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :---------: | :-----: | :------: | :------------: | :----: | :--------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | 50e | 12.9 | 22.9 | 37.9 | [config](./retinanet_r50_fpn_crop640-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/nas_fpn/retinanet_r50_fpn_crop640_50e_coco/retinanet_r50_fpn_crop640_50e_coco-9b953d76.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/nas_fpn/retinanet_r50_fpn_crop640_50e_coco/retinanet_r50_fpn_crop640_50e_coco_20200529_095329.log.json) | +| R-50-NASFPN | 50e | 13.2 | 23.0 | 40.5 | [config](./retinanet_r50_nasfpn_crop640-50e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/nas_fpn/retinanet_r50_nasfpn_crop640_50e_coco/retinanet_r50_nasfpn_crop640_50e_coco-0ad1f644.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/nas_fpn/retinanet_r50_nasfpn_crop640_50e_coco/retinanet_r50_nasfpn_crop640_50e_coco_20200528_230008.log.json) | + +**Note**: We find that it is unstable to train NAS-FPN and there is a small chance that results can be 3% mAP lower. + +## Citation + +```latex +@inproceedings{ghiasi2019fpn, + title={Nas-fpn: Learning scalable feature pyramid architecture for object detection}, + author={Ghiasi, Golnaz and Lin, Tsung-Yi and Le, Quoc V}, + booktitle={Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition}, + pages={7036--7045}, + year={2019} +} +``` diff --git a/mmdetection/configs/nas_fpn/metafile.yml b/mmdetection/configs/nas_fpn/metafile.yml new file mode 100644 index 00000000..aef0df6d --- /dev/null +++ b/mmdetection/configs/nas_fpn/metafile.yml @@ -0,0 +1,59 @@ +Collections: + - Name: NAS-FPN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - NAS-FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1904.07392 + Title: 'NAS-FPN: Learning Scalable Feature Pyramid Architecture for Object Detection' + README: configs/nas_fpn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/necks/nas_fpn.py#L67 + Version: v2.0.0 + +Models: + - Name: retinanet_r50_fpn_crop640-50e_coco + In Collection: NAS-FPN + Config: configs/nas_fpn/retinanet_r50_fpn_crop640-50e_coco.py + Metadata: + Training Memory (GB): 12.9 + inference time (ms/im): + - value: 43.67 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/nas_fpn/retinanet_r50_fpn_crop640_50e_coco/retinanet_r50_fpn_crop640_50e_coco-9b953d76.pth + + - Name: retinanet_r50_nasfpn_crop640-50e_coco + In Collection: NAS-FPN + Config: configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py + Metadata: + Training Memory (GB): 13.2 + inference time (ms/im): + - value: 43.48 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 50 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/nas_fpn/retinanet_r50_nasfpn_crop640_50e_coco/retinanet_r50_nasfpn_crop640_50e_coco-0ad1f644.pth diff --git a/mmdetection/configs/nas_fpn/retinanet_r50_fpn_crop640-50e_coco.py b/mmdetection/configs/nas_fpn/retinanet_r50_fpn_crop640-50e_coco.py new file mode 100644 index 00000000..11c34f67 --- /dev/null +++ b/mmdetection/configs/nas_fpn/retinanet_r50_fpn_crop640-50e_coco.py @@ -0,0 +1,78 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=64, + batch_augments=[dict(type='BatchFixedSizePad', size=(640, 640))]), + backbone=dict(norm_eval=False), + neck=dict( + relu_before_extra_convs=True, + no_norm_on_lateral=True, + norm_cfg=norm_cfg), + bbox_head=dict(type='RetinaSepBNHead', num_ins=5, norm_cfg=norm_cfg), + # training and testing settings + train_cfg=dict(assigner=dict(neg_iou_thr=0.5))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(640, 640), + ratio_range=(0.8, 1.2), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(640, 640)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(640, 640), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=8, num_workers=4, dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# training schedule for 50e +max_epochs = 50 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[30, 40], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.08, momentum=0.9, weight_decay=0.0001), + paramwise_cfg=dict(norm_decay_mult=0, bypass_duplicate=True)) + +env_cfg = dict(cudnn_benchmark=True) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py b/mmdetection/configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py new file mode 100644 index 00000000..a851b745 --- /dev/null +++ b/mmdetection/configs/nas_fpn/retinanet_r50_nasfpn_crop640-50e_coco.py @@ -0,0 +1,16 @@ +_base_ = './retinanet_r50_fpn_crop640-50e_coco.py' + +# model settings +model = dict( + # `pad_size_divisor=128` ensures the feature maps sizes + # in `NAS_FPN` won't mismatch. + data_preprocessor=dict(pad_size_divisor=128), + neck=dict( + _delete_=True, + type='NASFPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5, + stack_times=7, + start_level=1, + norm_cfg=dict(type='BN', requires_grad=True))) diff --git a/mmdetection/configs/objects365/README.md b/mmdetection/configs/objects365/README.md new file mode 100644 index 00000000..fca0dbfc --- /dev/null +++ b/mmdetection/configs/objects365/README.md @@ -0,0 +1,102 @@ +# Objects365 Dataset + +> [Objects365 Dataset](https://openaccess.thecvf.com/content_ICCV_2019/papers/Shao_Objects365_A_Large-Scale_High-Quality_Dataset_for_Object_Detection_ICCV_2019_paper.pdf) + + + +## Abstract + + + +#### Objects365 Dataset V1 + +[Objects365 Dataset V1](http://www.objects365.org/overview.html) is a brand new dataset, +designed to spur object detection research with a focus on diverse objects in the Wild. +It has 365 object categories over 600K training images. More than 10 million, high-quality bounding boxes are manually labeled through a three-step, carefully designed annotation pipeline. It is the largest object detection dataset (with full annotation) so far and establishes a more challenging benchmark for the community. Objects365 can serve as a better feature learning dataset for localization-sensitive tasks like object detection +and semantic segmentation. + + + +
    + +
    + +#### Objects365 Dataset V2 + +[Objects365 Dataset V2](http://www.objects365.org/overview.html) is based on the V1 release of the Objects365 dataset. +Objects 365 annotated 365 object classes on more than 1800k images, with more than 29 million bounding boxes in the training set, surpassing PASCAL VOC, ImageNet, and COCO datasets. +Objects 365 includes 11 categories of people, clothing, living room, bathroom, kitchen, office/medical, electrical appliances, transportation, food, animals, sports/musical instruments, and each category has dozens of subcategories. + +## Citation + +``` +@inproceedings{shao2019objects365, + title={Objects365: A large-scale, high-quality dataset for object detection}, + author={Shao, Shuai and Li, Zeming and Zhang, Tianyuan and Peng, Chao and Yu, Gang and Zhang, Xiangyu and Li, Jing and Sun, Jian}, + booktitle={Proceedings of the IEEE/CVF international conference on computer vision}, + pages={8430--8439}, + year={2019} +} +``` + +## Prepare Dataset + +1. You need to download and extract Objects365 dataset. Users can download Objects365 V2 by using `tools/misc/download_dataset.py`. + + **Usage** + + ```shell + python tools/misc/download_dataset.py --dataset-name objects365v2 \ + --save-dir ${SAVING PATH} \ + --unzip \ + --delete # Optional, delete the download zip file + ``` + + **Note:** There is no download link for Objects365 V1 right now. If you would like to download Objects365-V1, please visit [official website](http://www.objects365.org/) to concat the author. + +2. The directory should be like this: + + ```none + mmdetection + ├── mmdet + ├── tools + ├── configs + ├── data + │ ├── Objects365 + │ │ ├── Obj365_v1 + │ │ │ ├── annotations + │ │ │ │ ├── objects365_train.json + │ │ │ │ ├── objects365_val.json + │ │ │ ├── train # training images + │ │ │ ├── val # validation images + │ │ ├── Obj365_v2 + │ │ │ ├── annotations + │ │ │ │ ├── zhiyuan_objv2_train.json + │ │ │ │ ├── zhiyuan_objv2_val.json + │ │ │ ├── train # training images + │ │ │ │ ├── patch0 + │ │ │ │ ├── patch1 + │ │ │ │ ├── ... + │ │ │ ├── val # validation images + │ │ │ │ ├── patch0 + │ │ │ │ ├── patch1 + │ │ │ │ ├── ... + ``` + +## Results and Models + +### Objects365 V1 + +| Architecture | Backbone | Style | Lr schd | Mem (GB) | box AP | Config | Download | +| :----------: | :------: | :-----: | :-----: | :------: | :----: | :-------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster R-CNN | R-50 | pytorch | 1x | - | 19.6 | [config](https://github.com/open-mmlab/mmdetection/tree/main/configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_16x4_1x_obj365v1/faster_rcnn_r50_fpn_16x4_1x_obj365v1_20221219_181226-9ff10f95.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_16x4_1x_obj365v1/faster_rcnn_r50_fpn_16x4_1x_obj365v1_20221219_181226.log.json) | +| Faster R-CNN | R-50 | pytorch | 1350K | - | 22.3 | [config](https://github.com/open-mmlab/mmdetection/tree/main/configs/objects365/faster-rcnn_r50-syncbn_fpn_1350k_objects365v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_syncbn_1350k_obj365v1/faster_rcnn_r50_fpn_syncbn_1350k_obj365v1_20220510_142457-337d8965.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_syncbn_1350k_obj365v1/faster_rcnn_r50_fpn_syncbn_1350k_obj365v1_20220510_142457.log.json) | +| Retinanet | R-50 | pytorch | 1x | - | 14.8 | [config](https://github.com/open-mmlab/mmdetection/tree/main/configs/objects365/retinanet_r50_fpn_1x_objects365v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/objects365/retinanet_r50_fpn_1x_obj365v1/retinanet_r50_fpn_1x_obj365v1_20221219_181859-ba3e3dd5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/objects365/retinanet_r50_fpn_1x_obj365v1/retinanet_r50_fpn_1x_obj365v1_20221219_181859.log.json) | +| Retinanet | R-50 | pytorch | 1350K | - | 18.0 | [config](https://github.com/open-mmlab/mmdetection/tree/main/configs/objects365/retinanet_r50-syncbn_fpn_1350k_objects365v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/objects365/retinanet_r50_fpn_syncbn_1350k_obj365v1/retinanet_r50_fpn_syncbn_1350k_obj365v1_20220513_111237-7517c576.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/objects365/retinanet_r50_fpn_syncbn_1350k_obj365v1/retinanet_r50_fpn_syncbn_1350k_obj365v1_20220513_111237.log.json) | + +### Objects365 V2 + +| Architecture | Backbone | Style | Lr schd | Mem (GB) | box AP | Config | Download | +| :----------: | :------: | :-----: | :-----: | :------: | :----: | :---------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster R-CNN | R-50 | pytorch | 1x | - | 19.8 | [config](https://github.com/open-mmlab/mmdetection/tree/main/configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v2.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_16x4_1x_obj365v2/faster_rcnn_r50_fpn_16x4_1x_obj365v2_20221220_175040-5910b015.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_16x4_1x_obj365v2/faster_rcnn_r50_fpn_16x4_1x_obj365v2_20221220_175040.log.json) | +| Retinanet | R-50 | pytorch | 1x | - | 16.7 | [config](https://github.com/open-mmlab/mmdetection/tree/main/configs/objects365/retinanet_r50_fpn_1x_objects365v2.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/objects365/retinanet_r50_fpn_1x_obj365v2/retinanet_r50_fpn_1x_obj365v2_20221223_122105-d9b191f1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/objects365/retinanet_r50_fpn_1x_obj365v2/retinanet_r50_fpn_1x_obj365v2_20221223_122105.log.json) | diff --git a/mmdetection/configs/objects365/faster-rcnn_r50-syncbn_fpn_1350k_objects365v1.py b/mmdetection/configs/objects365/faster-rcnn_r50-syncbn_fpn_1350k_objects365v1.py new file mode 100644 index 00000000..ff7d0a36 --- /dev/null +++ b/mmdetection/configs/objects365/faster-rcnn_r50-syncbn_fpn_1350k_objects365v1.py @@ -0,0 +1,49 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/objects365v2_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict(norm_cfg=dict(type='SyncBN', requires_grad=True)), + roi_head=dict(bbox_head=dict(num_classes=365))) + +# training schedule for 1350K +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=1350000, # 36 epochs + val_interval=150000) + +# Using 8 GPUS while training +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning rate policy +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 1000, + by_epoch=False, + begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=1350000, + by_epoch=False, + milestones=[900000, 1200000], + gamma=0.1) +] + +train_dataloader = dict(sampler=dict(type='InfiniteSampler')) +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=150000)) + +log_processor = dict(by_epoch=False) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v1.py b/mmdetection/configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v1.py new file mode 100644 index 00000000..bc0d96fa --- /dev/null +++ b/mmdetection/configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v1.py @@ -0,0 +1,39 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/objects365v1_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict(roi_head=dict(bbox_head=dict(num_classes=365))) + +train_dataloader = dict( + batch_size=4, # using 16 GPUS while training. total batch size is 16 x 4) +) + +# Using 32 GPUS while training +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.08, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 1000, + by_epoch=False, + begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v2.py b/mmdetection/configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v2.py new file mode 100644 index 00000000..1090678f --- /dev/null +++ b/mmdetection/configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v2.py @@ -0,0 +1,39 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/objects365v2_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict(roi_head=dict(bbox_head=dict(num_classes=365))) + +train_dataloader = dict( + batch_size=4, # using 16 GPUS while training. total batch size is 16 x 4) +) + +# Using 32 GPUS while training +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.08, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 1000, + by_epoch=False, + begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/objects365/metafile.yml b/mmdetection/configs/objects365/metafile.yml new file mode 100644 index 00000000..d43e8bde --- /dev/null +++ b/mmdetection/configs/objects365/metafile.yml @@ -0,0 +1,101 @@ +- Name: retinanet_r50_fpn_1x_objects365v1 + In Collection: RetinaNet + Config: configs/objects365/retinanet_r50_fpn_1x_objects365v1.py + Metadata: + Training Memory (GB): 7.4 + Epochs: 12 + Training Data: Objects365 v1 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Objects365 v1 + Metrics: + box AP: 14.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/objects365/retinanet_r50_fpn_1x_obj365v1/retinanet_r50_fpn_1x_obj365v1_20221219_181859-ba3e3dd5.pth + +- Name: retinanet_r50-syncbn_fpn_1350k_objects365v1 + In Collection: RetinaNet + Config: configs/objects365/retinanet_r50-syncbn_fpn_1350k_objects365v1.py + Metadata: + Training Memory (GB): 7.6 + Iterations: 1350000 + Training Data: Objects365 v1 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Objects365 v1 + Metrics: + box AP: 18.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/objects365/retinanet_r50_fpn_syncbn_1350k_obj365v1/retinanet_r50_fpn_syncbn_1350k_obj365v1_20220513_111237-7517c576.pth + +- Name: retinanet_r50_fpn_1x_objects365v2 + In Collection: RetinaNet + Config: configs/objects365/retinanet_r50_fpn_1x_objects365v2.py + Metadata: + Training Memory (GB): 7.2 + Epochs: 12 + Training Data: Objects365 v2 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Objects365 v2 + Metrics: + box AP: 16.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/objects365/retinanet_r50_fpn_1x_obj365v2/retinanet_r50_fpn_1x_obj365v2_20221223_122105-d9b191f1.pth + +- Name: faster-rcnn_r50_fpn_16xb4-1x_objects365v1 + In Collection: Faster R-CNN + Config: configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v1.py + Metadata: + Training Memory (GB): 11.4 + Epochs: 12 + Training Data: Objects365 v1 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Objects365 v1 + Metrics: + box AP: 19.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_16x4_1x_obj365v1/faster_rcnn_r50_fpn_16x4_1x_obj365v1_20221219_181226-9ff10f95.pth + +- Name: faster-rcnn_r50-syncbn_fpn_1350k_objects365v1 + In Collection: Faster R-CNN + Config: configs/objects365/faster-rcnn_r50-syncbn_fpn_1350k_objects365v1.py + Metadata: + Training Memory (GB): 8.6 + Iterations: 1350000 + Training Data: Objects365 v1 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Objects365 v1 + Metrics: + box AP: 22.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_syncbn_1350k_obj365v1/faster_rcnn_r50_fpn_syncbn_1350k_obj365v1_20220510_142457-337d8965.pth + +- Name: faster-rcnn_r50_fpn_16xb4-1x_objects365v2 + In Collection: Faster R-CNN + Config: configs/objects365/faster-rcnn_r50_fpn_16xb4-1x_objects365v2.py + Metadata: + Training Memory (GB): 10.8 + Epochs: 12 + Training Data: Objects365 v1 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Objects365 v2 + Metrics: + box AP: 19.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/objects365/faster_rcnn_r50_fpn_16x4_1x_obj365v2/faster_rcnn_r50_fpn_16x4_1x_obj365v2_20221220_175040-5910b015.pth diff --git a/mmdetection/configs/objects365/retinanet_r50-syncbn_fpn_1350k_objects365v1.py b/mmdetection/configs/objects365/retinanet_r50-syncbn_fpn_1350k_objects365v1.py new file mode 100644 index 00000000..c41dfce8 --- /dev/null +++ b/mmdetection/configs/objects365/retinanet_r50-syncbn_fpn_1350k_objects365v1.py @@ -0,0 +1,49 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/objects365v2_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict(norm_cfg=dict(type='SyncBN', requires_grad=True)), + bbox_head=dict(num_classes=365)) + +# training schedule for 1350K +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=1350000, # 36 epochs + val_interval=150000) + +# Using 8 GPUS while training +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning rate policy +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 1000, + by_epoch=False, + begin=0, + end=10000), + dict( + type='MultiStepLR', + begin=0, + end=1350000, + by_epoch=False, + milestones=[900000, 1200000], + gamma=0.1) +] + +train_dataloader = dict(sampler=dict(type='InfiniteSampler')) +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=150000)) + +log_processor = dict(by_epoch=False) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/objects365/retinanet_r50_fpn_1x_objects365v1.py b/mmdetection/configs/objects365/retinanet_r50_fpn_1x_objects365v1.py new file mode 100644 index 00000000..72144192 --- /dev/null +++ b/mmdetection/configs/objects365/retinanet_r50_fpn_1x_objects365v1.py @@ -0,0 +1,35 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/objects365v1_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict(bbox_head=dict(num_classes=365)) + +# Using 8 GPUS while training +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 1000, + by_epoch=False, + begin=0, + end=10000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/objects365/retinanet_r50_fpn_1x_objects365v2.py b/mmdetection/configs/objects365/retinanet_r50_fpn_1x_objects365v2.py new file mode 100644 index 00000000..21954412 --- /dev/null +++ b/mmdetection/configs/objects365/retinanet_r50_fpn_1x_objects365v2.py @@ -0,0 +1,35 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/objects365v2_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict(bbox_head=dict(num_classes=365)) + +# Using 8 GPUS while training +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 1000, + by_epoch=False, + begin=0, + end=10000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/ocsort/README.md b/mmdetection/configs/ocsort/README.md new file mode 100644 index 00000000..e9b86c6c --- /dev/null +++ b/mmdetection/configs/ocsort/README.md @@ -0,0 +1,56 @@ +# Observation-Centric SORT: Rethinking SORT for Robust Multi-Object Tracking + +## Abstract + + + +Multi-Object Tracking (MOT) has rapidly progressed with the development of object detection and re-identification. However, motion modeling, which facilitates object association by forecasting short-term trajec- tories with past observations, has been relatively under-explored in recent years. Current motion models in MOT typically assume that the object motion is linear in a small time window and needs continuous observations, so these methods are sensitive to occlusions and non-linear motion and require high frame-rate videos. In this work, we show that a simple motion model can obtain state-of-the-art tracking performance without other cues like appearance. We emphasize the role of “observation” when recovering tracks from being lost and reducing the error accumulated by linear motion models during the lost period. We thus name the proposed method as Observation-Centric SORT, OC-SORT for short. It remains simple, online, and real-time but improves robustness over occlusion and non-linear motion. It achieves 63.2 and 62.1 HOTA on MOT17 and MOT20, respectively, surpassing all published methods. It also sets new states of the art on KITTI Pedestrian Tracking and DanceTrack where the object motion is highly non-linear + + + +
    + +
    + +## Citation + + + +```latex +@article{cao2022observation, + title={Observation-Centric SORT: Rethinking SORT for Robust Multi-Object Tracking}, + author={Cao, Jinkun and Weng, Xinshuo and Khirodkar, Rawal and Pang, Jiangmiao and Kitani, Kris}, + journal={arXiv preprint arXiv:2203.14360}, + year={2022} +} +``` + +## Results and models on MOT17 + +The performance on `MOT17-half-val` is comparable with the performance from [the OC-SORT official implementation](https://github.com/noahcao/OC_SORT). We use the same YOLO-X detector weights as in [ByteTrack](https://github.com/open-mmlab/mmtracking/tree/master/configs/mot/bytetrack). + +| Method | Detector | Train Set | Test Set | Public | Inf time (fps) | HOTA | MOTA | IDF1 | FP | FN | IDSw. | Config | Download | +| :-----: | :------: | :---------------------: | :------: | :----: | :------------: | :--: | :--: | :--: | :---: | :---: | :---: | :-------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| OC-SORT | YOLOX-X | CrowdHuman + half-train | half-val | N | - | 67.5 | 77.5 | 78.2 | 15987 | 19590 | 855 | [config](ocsort_yolox_x_crowdhuman_mot17-private-half.py) | [model](https://download.openmmlab.com/mmtracking/mot/ocsort/mot_dataset/ocsort_yolox_x_crowdhuman_mot17-private-half_20220813_101618-fe150582.pth) \| [log](https://download.openmmlab.com/mmtracking/mot/ocsort/mot_dataset/ocsort_yolox_x_crowdhuman_mot17-private-half_20220813_101618.log.json) | + +## Get started + +### 1. Development Environment Setup + +Tracking Development Environment Setup can refer to this [document](../../docs/en/get_started.md). + +### 2. Dataset Prepare + +Tracking Dataset Prepare can refer to this [document](../../docs/en/user_guides/tracking_dataset_prepare.md). + +### 3. Training + +OCSORT training is same as Bytetrack, please refer to [document](../../configs/bytetrack/README.md). + +### 4. Testing and evaluation + +OCSORT evaluation and test are same as Bytetrack, please refer to [document](../../configs/bytetrack/README.md). + +### 5.Inference + +OCSORT inference is same as Bytetrack, please refer to [document](../../configs/bytetrack/README.md). diff --git a/mmdetection/configs/ocsort/metafile.yml b/mmdetection/configs/ocsort/metafile.yml new file mode 100644 index 00000000..0a31ef10 --- /dev/null +++ b/mmdetection/configs/ocsort/metafile.yml @@ -0,0 +1,27 @@ +Collections: + - Name: OCSORT + Metadata: + Training Techniques: + - SGD with Momentum + Training Resources: 8x V100 GPUs + Architecture: + - YOLOX + Paper: + URL: https://arxiv.org/abs/2203.14360 + Title: Observation-Centric SORT Rethinking SORT for Robust Multi-Object Tracking + README: configs/ocsort/README.md + +Models: + - Name: ocsort_yolox_x_crowdhuman_mot17-private-half + In Collection: OCSORT + Config: configs/ocsort/ocsort_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py + Metadata: + Training Data: CrowdHuman + MOT17-half-train + Results: + - Task: Multiple Object Tracking + Dataset: MOT17-half-val + Metrics: + HOTA: 67.5 + MOTA: 77.5 + IDF1: 78.2 + Weights: https://download.openmmlab.com/mmtracking/mot/ocsort/mot_dataset/ocsort_yolox_x_crowdhuman_mot17-private-half_20220813_101618-fe150582.pth diff --git a/mmdetection/configs/ocsort/ocsort_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py b/mmdetection/configs/ocsort/ocsort_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..ea04923d --- /dev/null +++ b/mmdetection/configs/ocsort/ocsort_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py @@ -0,0 +1,18 @@ +_base_ = [ + '../bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py', # noqa: E501 +] + +model = dict( + type='OCSORT', + tracker=dict( + _delete_=True, + type='OCSORTTracker', + motion=dict(type='KalmanFilter'), + obj_score_thr=0.3, + init_track_thr=0.7, + weight_iou_with_det_scores=True, + match_iou_thr=0.3, + num_tentatives=3, + vel_consist_weight=0.2, + vel_delta_t=3, + num_frames_retain=30)) diff --git a/mmdetection/configs/ocsort/ocsort_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test.py b/mmdetection/configs/ocsort/ocsort_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test.py new file mode 100644 index 00000000..ea04923d --- /dev/null +++ b/mmdetection/configs/ocsort/ocsort_yolox_x_8xb4-amp-80e_crowdhuman-mot20train_test-mot20test.py @@ -0,0 +1,18 @@ +_base_ = [ + '../bytetrack/bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py', # noqa: E501 +] + +model = dict( + type='OCSORT', + tracker=dict( + _delete_=True, + type='OCSORTTracker', + motion=dict(type='KalmanFilter'), + obj_score_thr=0.3, + init_track_thr=0.7, + weight_iou_with_det_scores=True, + match_iou_thr=0.3, + num_tentatives=3, + vel_consist_weight=0.2, + vel_delta_t=3, + num_frames_retain=30)) diff --git a/mmdetection/configs/openimages/README.md b/mmdetection/configs/openimages/README.md new file mode 100644 index 00000000..ccfc721d --- /dev/null +++ b/mmdetection/configs/openimages/README.md @@ -0,0 +1,149 @@ +# Open Images Dataset + +> [Open Images Dataset](https://arxiv.org/abs/1811.00982) + + + +## Abstract + + + +#### Open Images v6 + +[Open Images](https://storage.googleapis.com/openimages/web/index.html) is a dataset of ~9M images annotated with image-level labels, +object bounding boxes, object segmentation masks, visual relationships, +and localized narratives: + +- It contains a total of 16M bounding boxes for 600 object classes on + 1.9M images, making it the largest existing dataset with object location + annotations. The boxes have been largely manually drawn by professional + annotators to ensure accuracy and consistency. The images are very diverse + and often contain complex scenes with several objects (8.3 per image on + average). + +- Open Images also offers visual relationship annotations, indicating pairs + of objects in particular relations (e.g. "woman playing guitar", "beer on + table"), object properties (e.g. "table is wooden"), and human actions (e.g. + "woman is jumping"). In total it has 3.3M annotations from 1,466 distinct + relationship triplets. + +- In V5 we added segmentation masks for 2.8M object instances in 350 classes. + Segmentation masks mark the outline of objects, which characterizes their + spatial extent to a much higher level of detail. + +- In V6 we added 675k localized narratives: multimodal descriptions of images + consisting of synchronized voice, text, and mouse traces over the objects being + described. (Note we originally launched localized narratives only on train in V6, + but since July 2020 we also have validation and test covered.) + +- Finally, the dataset is annotated with 59.9M image-level labels spanning 19,957 + classes. + +We believe that having a single dataset with unified annotations for image +classification, object detection, visual relationship detection, instance +segmentation, and multimodal image descriptions will enable to study these +tasks jointly and stimulate progress towards genuine scene understanding. + + + +
    + +
    + +#### Open Images Challenge 2019 + +[Open Images Challenges 2019](https://storage.googleapis.com/openimages/web/challenge2019.html) is based on the V5 release of the Open +Images dataset. The images of the dataset are very varied and +often contain complex scenes with several objects (explore the dataset). + +## Citation + +``` +@article{OpenImages, + author = {Alina Kuznetsova and Hassan Rom and Neil Alldrin and Jasper Uijlings and Ivan Krasin and Jordi Pont-Tuset and Shahab Kamali and Stefan Popov and Matteo Malloci and Alexander Kolesnikov and Tom Duerig and Vittorio Ferrari}, + title = {The Open Images Dataset V4: Unified image classification, object detection, and visual relationship detection at scale}, + year = {2020}, + journal = {IJCV} +} +``` + +## Prepare Dataset + +1. You need to download and extract Open Images dataset. + +2. The Open Images dataset does not have image metas (width and height of the image), + which will be used during training and testing (evaluation). We suggest to get test image metas before + training/testing by using `tools/misc/get_image_metas.py`. + + **Usage** + + ```shell + python tools/misc/get_image_metas.py ${CONFIG} \ + --dataset ${DATASET TYPE} \ # train or val or test + --out ${OUTPUT FILE NAME} + ``` + +3. The directory should be like this: + + ```none + mmdetection + ├── mmdet + ├── tools + ├── configs + ├── data + │ ├── OpenImages + │ │ ├── annotations + │ │ │ ├── bbox_labels_600_hierarchy.json + │ │ │ ├── class-descriptions-boxable.csv + │ │ │ ├── oidv6-train-annotations-bbox.scv + │ │ │ ├── validation-annotations-bbox.csv + │ │ │ ├── validation-annotations-human-imagelabels-boxable.csv + │ │ │ ├── validation-image-metas.pkl # get from script + │ │ ├── challenge2019 + │ │ │ ├── challenge-2019-train-detection-bbox.txt + │ │ │ ├── challenge-2019-validation-detection-bbox.txt + │ │ │ ├── class_label_tree.np + │ │ │ ├── class_sample_train.pkl + │ │ │ ├── challenge-2019-validation-detection-human-imagelabels.csv # download from official website + │ │ │ ├── challenge-2019-validation-metas.pkl # get from script + │ │ ├── OpenImages + │ │ │ ├── train # training images + │ │ │ ├── test # testing images + │ │ │ ├── validation # validation images + ``` + +**Note**: + +1. The training and validation images of Open Images Challenge dataset are based on + Open Images v6, but the test images are different. +2. The Open Images Challenges annotations are obtained from [TSD](https://github.com/Sense-X/TSD). + You can also download the annotations from [official website](https://storage.googleapis.com/openimages/web/challenge2019_downloads.html), + and set data.train.type=OpenImagesDataset, data.val.type=OpenImagesDataset, and data.test.type=OpenImagesDataset in the config +3. If users do not want to use `validation-annotations-human-imagelabels-boxable.csv` and `challenge-2019-validation-detection-human-imagelabels.csv` + users can set `test_dataloader.dataset.image_level_ann_file=None` and `test_dataloader.dataset.image_level_ann_file=None` in the config. + Please note that loading image-levels label is the default of Open Images evaluation metric. + More details please refer to the [official website](https://storage.googleapis.com/openimages/web/evaluation.html) + +## Results and Models + +| Architecture | Backbone | Style | Lr schd | Sampler | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :---------------------------: | :------: | :-----: | :-----: | :-----------------: | :------: | :------------: | :----: | :------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster R-CNN | R-50 | pytorch | 1x | Group Sampler | 7.7 | - | 51.6 | [config](./faster-rcnn_r50_fpn_32xb2-1x_openimages.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_1x_openimages/faster_rcnn_r50_fpn_32x2_1x_openimages_20211130_231159-e87ab7ce.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_1x_openimages/faster_rcnn_r50_fpn_32x2_1x_openimages_20211130_231159.log.json) | +| Faster R-CNN | R-50 | pytorch | 1x | Class Aware Sampler | 7.7 | - | 60.0 | [config](./faster-rcnn_r50_fpn_32xb2-cas-1x_openimages.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_20220306_202424-98c630e5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_20220306_202424.log.json) | +| Faster R-CNN (Challenge 2019) | R-50 | pytorch | 1x | Group Sampler | 7.7 | - | 54.9 | [config](./faster-rcnn_r50_fpn_32xb2-1x_openimages-challenge.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_1x_openimages_challenge/faster_rcnn_r50_fpn_32x2_1x_openimages_challenge_20220114_045100-0e79e5df.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_1x_openimages_challenge/faster_rcnn_r50_fpn_32x2_1x_openimages_challenge_20220114_045100.log.json) | +| Faster R-CNN (Challenge 2019) | R-50 | pytorch | 1x | Class Aware Sampler | 7.1 | - | 65.0 | [config](./faster-rcnn_r50_fpn_32xb2-cas-1x_openimages-challenge.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_challenge/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_challenge_20220221_192021-34c402d9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_challenge/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_challenge_20220221_192021.log.json) | +| Retinanet | R-50 | pytorch | 1x | Group Sampler | 6.6 | - | 61.5 | [config](./retinanet_r50_fpn_32xb2-1x_openimages.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/openimages/retinanet_r50_fpn_32x2_1x_openimages/retinanet_r50_fpn_32x2_1x_openimages_20211223_071954-d2ae5462.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/openimages/retinanet_r50_fpn_32x2_1x_openimages/retinanet_r50_fpn_32x2_1x_openimages_20211223_071954.log.json) | +| SSD | VGG16 | pytorch | 36e | Group Sampler | 10.8 | - | 35.4 | [config](./ssd300_32xb8-36e_openimages.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/openimages/ssd300_32x8_36e_openimages/ssd300_32x8_36e_openimages_20211224_000232-dce93846.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/openimages/ssd300_32x8_36e_openimages/ssd300_32x8_36e_openimages_20211224_000232.log.json) | + +**Notes:** + +- 'cas' is short for 'Class Aware Sampler' + +### Results of consider image level labels + +| Architecture | Sampler | Consider Image Level Labels | box AP | +| :-------------------------------: | :-----------------: | :-------------------------: | :----: | +| Faster R-CNN r50 (Challenge 2019) | Group Sampler | w/o | 62.19 | +| Faster R-CNN r50 (Challenge 2019) | Group Sampler | w/ | 54.87 | +| Faster R-CNN r50 (Challenge 2019) | Class Aware Sampler | w/o | 71.77 | +| Faster R-CNN r50 (Challenge 2019) | Class Aware Sampler | w/ | 64.98 | diff --git a/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-1x_openimages-challenge.py b/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-1x_openimages-challenge.py new file mode 100644 index 00000000..e79a92cc --- /dev/null +++ b/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-1x_openimages-challenge.py @@ -0,0 +1,39 @@ +_base_ = ['faster-rcnn_r50_fpn_32xb2-1x_openimages.py'] + +model = dict( + roi_head=dict(bbox_head=dict(num_classes=500)), + test_cfg=dict(rcnn=dict(score_thr=0.01))) + +# dataset settings +dataset_type = 'OpenImagesChallengeDataset' +train_dataloader = dict( + dataset=dict( + type=dataset_type, + ann_file='challenge2019/challenge-2019-train-detection-bbox.txt', + label_file='challenge2019/cls-label-description.csv', + hierarchy_file='challenge2019/class_label_tree.np', + meta_file='challenge2019/challenge-2019-train-metas.pkl')) +val_dataloader = dict( + dataset=dict( + type=dataset_type, + ann_file='challenge2019/challenge-2019-validation-detection-bbox.txt', + data_prefix=dict(img='OpenImages/'), + label_file='challenge2019/cls-label-description.csv', + hierarchy_file='challenge2019/class_label_tree.np', + meta_file='challenge2019/challenge-2019-validation-metas.pkl', + image_level_ann_file='challenge2019/challenge-2019-validation-' + 'detection-human-imagelabels.csv')) +test_dataloader = dict( + dataset=dict( + type=dataset_type, + ann_file='challenge2019/challenge-2019-validation-detection-bbox.txt', + label_file='challenge2019/cls-label-description.csv', + hierarchy_file='challenge2019/class_label_tree.np', + meta_file='challenge2019/challenge-2019-validation-metas.pkl', + image_level_ann_file='challenge2019/challenge-2019-validation-' + 'detection-human-imagelabels.csv')) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-1x_openimages.py b/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-1x_openimages.py new file mode 100644 index 00000000..f3f0aa0a --- /dev/null +++ b/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-1x_openimages.py @@ -0,0 +1,35 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/openimages_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict(roi_head=dict(bbox_head=dict(num_classes=601))) + +# Using 32 GPUS while training +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.08, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 64, + by_epoch=False, + begin=0, + end=26000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-cas-1x_openimages-challenge.py b/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-cas-1x_openimages-challenge.py new file mode 100644 index 00000000..9e428725 --- /dev/null +++ b/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-cas-1x_openimages-challenge.py @@ -0,0 +1,5 @@ +_base_ = ['faster-rcnn_r50_fpn_32xb2-1x_openimages-challenge.py'] + +# Use ClassAwareSampler +train_dataloader = dict( + sampler=dict(_delete_=True, type='ClassAwareSampler', num_sample_class=1)) diff --git a/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-cas-1x_openimages.py b/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-cas-1x_openimages.py new file mode 100644 index 00000000..803190ab --- /dev/null +++ b/mmdetection/configs/openimages/faster-rcnn_r50_fpn_32xb2-cas-1x_openimages.py @@ -0,0 +1,5 @@ +_base_ = ['faster-rcnn_r50_fpn_32xb2-1x_openimages.py'] + +# Use ClassAwareSampler +train_dataloader = dict( + sampler=dict(_delete_=True, type='ClassAwareSampler', num_sample_class=1)) diff --git a/mmdetection/configs/openimages/metafile.yml b/mmdetection/configs/openimages/metafile.yml new file mode 100644 index 00000000..76c12094 --- /dev/null +++ b/mmdetection/configs/openimages/metafile.yml @@ -0,0 +1,102 @@ +Models: + - Name: faster-rcnn_r50_fpn_32x2_1x_openimages + In Collection: Faster R-CNN + Config: configs/openimages/faster-rcnn_r50_fpn_32xb2-1x_openimages.py + Metadata: + Training Memory (GB): 7.7 + Epochs: 12 + Training Data: Open Images v6 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Open Images v6 + Metrics: + box AP: 51.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_1x_openimages/faster_rcnn_r50_fpn_32x2_1x_openimages_20211130_231159-e87ab7ce.pth + + - Name: retinanet_r50_fpn_32xb2-1x_openimages + In Collection: RetinaNet + Config: configs/openimages/retinanet_r50_fpn_32xb2-1x_openimages.py + Metadata: + Training Memory (GB): 6.6 + Epochs: 12 + Training Data: Open Images v6 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Open Images v6 + Metrics: + box AP: 61.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/openimages/retinanet_r50_fpn_32x2_1x_openimages/retinanet_r50_fpn_32x2_1x_openimages_20211223_071954-d2ae5462.pth + + - Name: ssd300_32xb8-36e_openimages + In Collection: SSD + Config: configs/openimages/ssd300_32xb8-36e_openimages.py + Metadata: + Training Memory (GB): 10.8 + Epochs: 36 + Training Data: Open Images v6 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Open Images v6 + Metrics: + box AP: 35.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/openimages/ssd300_32x8_36e_openimages/ssd300_32x8_36e_openimages_20211224_000232-dce93846.pth + + - Name: faster-rcnn_r50_fpn_32x2_1x_openimages_challenge + In Collection: Faster R-CNN + Config: configs/openimages/faster-rcnn_r50_fpn_32xb2-1x_openimages-challenge.py + Metadata: + Training Memory (GB): 7.7 + Epochs: 12 + Training Data: Open Images Challenge 2019 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Open Images Challenge 2019 + Metrics: + box AP: 54.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_1x_openimages_challenge/faster_rcnn_r50_fpn_32x2_1x_openimages_challenge_20220114_045100-0e79e5df.pth + + - Name: faster-rcnn_r50_fpn_32x2_cas_1x_openimages + In Collection: Faster R-CNN + Config: configs/openimages/faster-rcnn_r50_fpn_32xb2-cas-1x_openimages.py + Metadata: + Training Memory (GB): 7.7 + Epochs: 12 + Training Data: Open Images Challenge 2019 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Open Images Challenge 2019 + Metrics: + box AP: 60.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_20220306_202424-98c630e5.pth + + - Name: faster-rcnn_r50_fpn_32x2_cas_1x_openimages_challenge + In Collection: Faster R-CNN + Config: configs/openimages/faster-rcnn_r50_fpn_32xb2-cas-1x_openimages-challenge.py + Metadata: + Training Memory (GB): 7.1 + Epochs: 12 + Training Data: Open Images Challenge 2019 + Training Techniques: + - SGD with Momentum + - Weight Decay + Results: + - Task: Object Detection + Dataset: Open Images Challenge 2019 + Metrics: + box AP: 65.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_challenge/faster_rcnn_r50_fpn_32x2_cas_1x_openimages_challenge_20220221_192021-34c402d9.pth diff --git a/mmdetection/configs/openimages/retinanet_r50_fpn_32xb2-1x_openimages.py b/mmdetection/configs/openimages/retinanet_r50_fpn_32xb2-1x_openimages.py new file mode 100644 index 00000000..97a0eb07 --- /dev/null +++ b/mmdetection/configs/openimages/retinanet_r50_fpn_32xb2-1x_openimages.py @@ -0,0 +1,35 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/openimages_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict(bbox_head=dict(num_classes=601)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 64, + by_epoch=False, + begin=0, + end=26000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.08, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/openimages/ssd300_32xb8-36e_openimages.py b/mmdetection/configs/openimages/ssd300_32xb8-36e_openimages.py new file mode 100644 index 00000000..9cb51cae --- /dev/null +++ b/mmdetection/configs/openimages/ssd300_32xb8-36e_openimages.py @@ -0,0 +1,88 @@ +_base_ = [ + '../_base_/models/ssd300.py', '../_base_/datasets/openimages_detection.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_1x.py' +] +model = dict( + bbox_head=dict( + num_classes=601, + anchor_generator=dict(basesize_ratio_range=(0.2, 0.9)))) +# dataset settings +dataset_type = 'OpenImagesDataset' +data_root = 'data/OpenImages/' +input_size = 300 +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict( + type='Expand', + mean={{_base_.model.data_preprocessor.mean}}, + to_rgb={{_base_.model.data_preprocessor.bgr_to_rgb}}, + ratio_range=(1, 4)), + dict( + type='MinIoURandomCrop', + min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + # avoid bboxes being resized + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'instances')) +] + +train_dataloader = dict( + batch_size=8, # using 32 GPUS while training. total batch size is 32 x 8 + batch_sampler=None, + dataset=dict( + _delete_=True, + type='RepeatDataset', + times=3, # repeat 3 times, total epochs are 12 x 3 + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/oidv6-train-annotations-bbox.csv', + data_prefix=dict(img='OpenImages/train/'), + label_file='annotations/class-descriptions-boxable.csv', + hierarchy_file='annotations/bbox_labels_600_hierarchy.json', + meta_file='annotations/train-image-metas.pkl', + pipeline=train_pipeline))) +val_dataloader = dict(batch_size=8, dataset=dict(pipeline=test_pipeline)) +test_dataloader = dict(batch_size=8, dataset=dict(pipeline=test_pipeline)) + +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.04, momentum=0.9, weight_decay=5e-4)) +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.001, + by_epoch=False, + begin=0, + end=20000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=256) diff --git a/mmdetection/configs/paa/README.md b/mmdetection/configs/paa/README.md new file mode 100644 index 00000000..625aacf2 --- /dev/null +++ b/mmdetection/configs/paa/README.md @@ -0,0 +1,47 @@ +# PAA + +> [Probabilistic Anchor Assignment with IoU Prediction for Object Detection](https://arxiv.org/abs/2007.08103) + + + +## Abstract + +In object detection, determining which anchors to assign as positive or negative samples, known as anchor assignment, has been revealed as a core procedure that can significantly affect a model's performance. In this paper we propose a novel anchor assignment strategy that adaptively separates anchors into positive and negative samples for a ground truth bounding box according to the model's learning status such that it is able to reason about the separation in a probabilistic manner. To do so we first calculate the scores of anchors conditioned on the model and fit a probability distribution to these scores. The model is then trained with anchors separated into positive and negative samples according to their probabilities. Moreover, we investigate the gap between the training and testing objectives and propose to predict the Intersection-over-Unions of detected boxes as a measure of localization quality to reduce the discrepancy. The combined score of classification and localization qualities serving as a box selection metric in non-maximum suppression well aligns with the proposed anchor assignment strategy and leads significant performance improvements. The proposed methods only add a single convolutional layer to RetinaNet baseline and does not require multiple anchors per location, so are efficient. Experimental results verify the effectiveness of the proposed methods. Especially, our models set new records for single-stage detectors on MS COCO test-dev dataset with various backbones. + +
    + +
    + +## Results and Models + +We provide config files to reproduce the object detection results in the +ECCV 2020 paper for Probabilistic Anchor Assignment with IoU +Prediction for Object Detection. + +| Backbone | Lr schd | Mem (GB) | Score voting | box AP | Config | Download | +| :-------: | :-----: | :------: | :----------: | :----: | :------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | 12e | 3.7 | True | 40.4 | [config](./paa_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1x_coco/paa_r50_fpn_1x_coco_20200821-936edec3.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1x_coco/paa_r50_fpn_1x_coco_20200821-936edec3.log.json) | +| R-50-FPN | 12e | 3.7 | False | 40.2 | - | | +| R-50-FPN | 18e | 3.7 | True | 41.4 | [config](./paa_r50_fpn_1.5x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1.5x_coco/paa_r50_fpn_1.5x_coco_20200823-805d6078.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1.5x_coco/paa_r50_fpn_1.5x_coco_20200823-805d6078.log.json) | +| R-50-FPN | 18e | 3.7 | False | 41.2 | - | | +| R-50-FPN | 24e | 3.7 | True | 41.6 | [config](./paa_r50_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_2x_coco/paa_r50_fpn_2x_coco_20200821-c98bfc4e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_2x_coco/paa_r50_fpn_2x_coco_20200821-c98bfc4e.log.json) | +| R-50-FPN | 36e | 3.7 | True | 43.3 | [config](./paa_r50_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_mstrain_3x_coco/paa_r50_fpn_mstrain_3x_coco_20210121_145722-06a6880b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_mstrain_3x_coco/paa_r50_fpn_mstrain_3x_coco_20210121_145722.log.json) | +| R-101-FPN | 12e | 6.2 | True | 42.6 | [config](./paa_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_1x_coco/paa_r101_fpn_1x_coco_20200821-0a1825a4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_1x_coco/paa_r101_fpn_1x_coco_20200821-0a1825a4.log.json) | +| R-101-FPN | 12e | 6.2 | False | 42.4 | - | | +| R-101-FPN | 24e | 6.2 | True | 43.5 | [config](./paa_r101_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_2x_coco/paa_r101_fpn_2x_coco_20200821-6829f96b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_2x_coco/paa_r101_fpn_2x_coco_20200821-6829f96b.log.json) | +| R-101-FPN | 36e | 6.2 | True | 45.1 | [config](./paa_r101_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_mstrain_3x_coco/paa_r101_fpn_mstrain_3x_coco_20210122_084202-83250d22.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_mstrain_3x_coco/paa_r101_fpn_mstrain_3x_coco_20210122_084202.log.json) | + +**Note**: + +1. We find that the performance is unstable with 1x setting and may fluctuate by about 0.2 mAP. We report the best results. + +## Citation + +```latex +@inproceedings{paa-eccv2020, + title={Probabilistic Anchor Assignment with IoU Prediction for Object Detection}, + author={Kim, Kang and Lee, Hee Seok}, + booktitle = {ECCV}, + year={2020} +} +``` diff --git a/mmdetection/configs/paa/metafile.yml b/mmdetection/configs/paa/metafile.yml new file mode 100644 index 00000000..078b9749 --- /dev/null +++ b/mmdetection/configs/paa/metafile.yml @@ -0,0 +1,111 @@ +Collections: + - Name: PAA + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - Probabilistic Anchor Assignment + - ResNet + Paper: + URL: https://arxiv.org/abs/2007.08103 + Title: 'Probabilistic Anchor Assignment with IoU Prediction for Object Detection' + README: configs/paa/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.4.0/mmdet/models/detectors/paa.py#L6 + Version: v2.4.0 + +Models: + - Name: paa_r50_fpn_1x_coco + In Collection: PAA + Config: configs/paa/paa_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.7 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1x_coco/paa_r50_fpn_1x_coco_20200821-936edec3.pth + + - Name: paa_r50_fpn_1.5x_coco + In Collection: PAA + Config: configs/paa/paa_r50_fpn_1.5x_coco.py + Metadata: + Training Memory (GB): 3.7 + Epochs: 18 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_1.5x_coco/paa_r50_fpn_1.5x_coco_20200823-805d6078.pth + + - Name: paa_r50_fpn_2x_coco + In Collection: PAA + Config: configs/paa/paa_r50_fpn_2x_coco.py + Metadata: + Training Memory (GB): 3.7 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_2x_coco/paa_r50_fpn_2x_coco_20200821-c98bfc4e.pth + + - Name: paa_r50_fpn_mstrain_3x_coco + In Collection: PAA + Config: configs/paa/paa_r50_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 3.7 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r50_fpn_mstrain_3x_coco/paa_r50_fpn_mstrain_3x_coco_20210121_145722-06a6880b.pth + + - Name: paa_r101_fpn_1x_coco + In Collection: PAA + Config: configs/paa/paa_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.2 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_1x_coco/paa_r101_fpn_1x_coco_20200821-0a1825a4.pth + + - Name: paa_r101_fpn_2x_coco + In Collection: PAA + Config: configs/paa/paa_r101_fpn_2x_coco.py + Metadata: + Training Memory (GB): 6.2 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_2x_coco/paa_r101_fpn_2x_coco_20200821-6829f96b.pth + + - Name: paa_r101_fpn_mstrain_3x_coco + In Collection: PAA + Config: configs/paa/paa_r101_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 6.2 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/paa/paa_r101_fpn_mstrain_3x_coco/paa_r101_fpn_mstrain_3x_coco_20210122_084202-83250d22.pth diff --git a/mmdetection/configs/paa/paa_r101_fpn_1x_coco.py b/mmdetection/configs/paa/paa_r101_fpn_1x_coco.py new file mode 100644 index 00000000..94f1c278 --- /dev/null +++ b/mmdetection/configs/paa/paa_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './paa_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/paa/paa_r101_fpn_2x_coco.py b/mmdetection/configs/paa/paa_r101_fpn_2x_coco.py new file mode 100644 index 00000000..c6136f3b --- /dev/null +++ b/mmdetection/configs/paa/paa_r101_fpn_2x_coco.py @@ -0,0 +1,18 @@ +_base_ = './paa_r101_fpn_1x_coco.py' +max_epochs = 24 + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] + +# training schedule for 2x +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/paa/paa_r101_fpn_ms-3x_coco.py b/mmdetection/configs/paa/paa_r101_fpn_ms-3x_coco.py new file mode 100644 index 00000000..8529dcdb --- /dev/null +++ b/mmdetection/configs/paa/paa_r101_fpn_ms-3x_coco.py @@ -0,0 +1,6 @@ +_base_ = './paa_r50_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/paa/paa_r50_fpn_1.5x_coco.py b/mmdetection/configs/paa/paa_r50_fpn_1.5x_coco.py new file mode 100644 index 00000000..ae993b5c --- /dev/null +++ b/mmdetection/configs/paa/paa_r50_fpn_1.5x_coco.py @@ -0,0 +1,18 @@ +_base_ = './paa_r50_fpn_1x_coco.py' +max_epochs = 18 + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[12, 16], + gamma=0.1) +] + +# training schedule for 1.5x +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/paa/paa_r50_fpn_1x_coco.py b/mmdetection/configs/paa/paa_r50_fpn_1x_coco.py new file mode 100644 index 00000000..f806a3ea --- /dev/null +++ b/mmdetection/configs/paa/paa_r50_fpn_1x_coco.py @@ -0,0 +1,80 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# model settings +model = dict( + type='PAA', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='PAAHead', + reg_decoded_bbox=True, + score_voting=True, + topk=9, + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.3), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.5)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.1, + neg_iou_thr=0.1, + min_pos_iou=0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/paa/paa_r50_fpn_2x_coco.py b/mmdetection/configs/paa/paa_r50_fpn_2x_coco.py new file mode 100644 index 00000000..6908e4eb --- /dev/null +++ b/mmdetection/configs/paa/paa_r50_fpn_2x_coco.py @@ -0,0 +1,18 @@ +_base_ = './paa_r50_fpn_1x_coco.py' +max_epochs = 24 + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] + +# training schedule for 2x +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/paa/paa_r50_fpn_ms-3x_coco.py b/mmdetection/configs/paa/paa_r50_fpn_ms-3x_coco.py new file mode 100644 index 00000000..fed8b90a --- /dev/null +++ b/mmdetection/configs/paa/paa_r50_fpn_ms-3x_coco.py @@ -0,0 +1,29 @@ +_base_ = './paa_r50_fpn_1x_coco.py' +max_epochs = 36 + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] + +# training schedule for 3x +train_cfg = dict(max_epochs=max_epochs) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/pafpn/README.md b/mmdetection/configs/pafpn/README.md new file mode 100644 index 00000000..36cd6e9f --- /dev/null +++ b/mmdetection/configs/pafpn/README.md @@ -0,0 +1,34 @@ +# PAFPN + +> [Path Aggregation Network for Instance Segmentation](https://arxiv.org/abs/1803.01534) + + + +## Abstract + +The way that information propagates in neural networks is of great importance. In this paper, we propose Path Aggregation Network (PANet) aiming at boosting information flow in proposal-based instance segmentation framework. Specifically, we enhance the entire feature hierarchy with accurate localization signals in lower layers by bottom-up path augmentation, which shortens the information path between lower layers and topmost feature. We present adaptive feature pooling, which links feature grid and all feature levels to make useful information in each feature level propagate directly to following proposal subnetworks. A complementary branch capturing different views for each proposal is created to further improve mask prediction. These improvements are simple to implement, with subtle extra computational overhead. Our PANet reaches the 1st place in the COCO 2017 Challenge Instance Segmentation task and the 2nd place in Object Detection task without large-batch training. It is also state-of-the-art on MVD and Cityscapes. + +
    + +
    + +## Results and Models + +| Backbone | style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 1x | 4.0 | 17.2 | 37.5 | | [config](./faster-rcnn_r50_pafpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pafpn/faster_rcnn_r50_pafpn_1x_coco/faster_rcnn_r50_pafpn_1x_coco_bbox_mAP-0.375_20200503_105836-b7b4b9bd.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pafpn/faster_rcnn_r50_pafpn_1x_coco/faster_rcnn_r50_pafpn_1x_coco_20200503_105836.log.json) | + +## Citation + +```latex +@inproceedings{liu2018path, + author = {Shu Liu and + Lu Qi and + Haifang Qin and + Jianping Shi and + Jiaya Jia}, + title = {Path Aggregation Network for Instance Segmentation}, + booktitle = {Proceedings of IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, + year = {2018} +} +``` diff --git a/mmdetection/configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py b/mmdetection/configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py new file mode 100644 index 00000000..1452baec --- /dev/null +++ b/mmdetection/configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' + +model = dict( + neck=dict( + type='PAFPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/pafpn/metafile.yml b/mmdetection/configs/pafpn/metafile.yml new file mode 100644 index 00000000..7772d276 --- /dev/null +++ b/mmdetection/configs/pafpn/metafile.yml @@ -0,0 +1,38 @@ +Collections: + - Name: PAFPN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - PAFPN + Paper: + URL: https://arxiv.org/abs/1803.01534 + Title: 'Path Aggregation Network for Instance Segmentation' + README: configs/pafpn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/necks/pafpn.py#L11 + Version: v2.0.0 + +Models: + - Name: faster-rcnn_r50_pafpn_1x_coco + In Collection: PAFPN + Config: configs/pafpn/faster-rcnn_r50_pafpn_1x_coco.py + Metadata: + Training Memory (GB): 4.0 + inference time (ms/im): + - value: 58.14 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pafpn/faster_rcnn_r50_pafpn_1x_coco/faster_rcnn_r50_pafpn_1x_coco_bbox_mAP-0.375_20200503_105836-b7b4b9bd.pth diff --git a/mmdetection/configs/panoptic_fpn/README.md b/mmdetection/configs/panoptic_fpn/README.md new file mode 100644 index 00000000..0321fb7c --- /dev/null +++ b/mmdetection/configs/panoptic_fpn/README.md @@ -0,0 +1,62 @@ +# Panoptic FPN + +> [Panoptic feature pyramid networks](https://arxiv.org/abs/1901.02446) + + + +## Abstract + +The recently introduced panoptic segmentation task has renewed our community's interest in unifying the tasks of instance segmentation (for thing classes) and semantic segmentation (for stuff classes). However, current state-of-the-art methods for this joint task use separate and dissimilar networks for instance and semantic segmentation, without performing any shared computation. In this work, we aim to unify these methods at the architectural level, designing a single network for both tasks. Our approach is to endow Mask R-CNN, a popular instance segmentation method, with a semantic segmentation branch using a shared Feature Pyramid Network (FPN) backbone. Surprisingly, this simple baseline not only remains effective for instance segmentation, but also yields a lightweight, top-performing method for semantic segmentation. In this work, we perform a detailed study of this minimally extended version of Mask R-CNN with FPN, which we refer to as Panoptic FPN, and show it is a robust and accurate baseline for both tasks. Given its effectiveness and conceptual simplicity, we hope our method can serve as a strong baseline and aid future research in panoptic segmentation. + +
    + +
    + +## Dataset + +PanopticFPN requires COCO and [COCO-panoptic](http://images.cocodataset.org/annotations/panoptic_annotations_trainval2017.zip) dataset for training and evaluation. You need to download and extract it in the COCO dataset path. +The directory should be like this. + +```none +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── panoptic_train2017.json +│ │ │ ├── panoptic_train2017 +│ │ │ ├── panoptic_val2017.json +│ │ │ ├── panoptic_val2017 +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +``` + +## Results and Models + +| Backbone | style | Lr schd | Mem (GB) | Inf time (fps) | PQ | SQ | RQ | PQ_th | SQ_th | RQ_th | PQ_st | SQ_st | RQ_st | Config | Download | +| :-------: | :-----: | :-----: | :------: | :------------: | :--: | :--: | :--: | :---: | :---: | :---: | :---: | :---: | :---: | :---------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 1x | 4.7 | | 40.2 | 77.8 | 49.3 | 47.8 | 80.9 | 57.5 | 28.9 | 73.1 | 37.0 | [config](./panoptic-fpn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r50_fpn_1x_coco/panoptic_fpn_r50_fpn_1x_coco_20210821_101153-9668fd13.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r50_fpn_1x_coco/panoptic_fpn_r50_fpn_1x_coco_20210821_101153.log.json) | +| R-50-FPN | pytorch | 3x | - | - | 42.5 | 78.1 | 51.7 | 50.3 | 81.5 | 60.3 | 30.7 | 73.0 | 38.8 | [config](./panoptic-fpn_r50_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r50_fpn_mstrain_3x_coco/panoptic_fpn_r50_fpn_mstrain_3x_coco_20210824_171155-5650f98b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r50_fpn_mstrain_3x_coco/panoptic_fpn_r50_fpn_mstrain_3x_coco_20210824_171155.log.json) | +| R-101-FPN | pytorch | 1x | 6.7 | | 42.2 | 78.3 | 51.4 | 50.1 | 81.4 | 59.9 | 30.3 | 73.6 | 38.5 | [config](./panoptic-fpn_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r101_fpn_1x_coco/panoptic_fpn_r101_fpn_1x_coco_20210820_193950-ab9157a2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r101_fpn_1x_coco/panoptic_fpn_r101_fpn_1x_coco_20210820_193950.log.json) | +| R-101-FPN | pytorch | 3x | - | - | 44.1 | 78.9 | 53.6 | 52.1 | 81.7 | 62.3 | 32.0 | 74.6 | 40.3 | [config](./panoptic-fpn_r101_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r101_fpn_mstrain_3x_coco/panoptic_fpn_r101_fpn_mstrain_3x_coco_20210823_114712-9c99acc4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r101_fpn_mstrain_3x_coco/panoptic_fpn_r101_fpn_mstrain_3x_coco_20210823_114712.log.json) | + +## Citation + +The base method for panoptic segmentation task. + +```latex +@inproceedings{kirillov2018panopticfpn, + author = { + Alexander Kirillov, + Ross Girshick, + Kaiming He, + Piotr Dollar, + }, + title = {Panoptic Feature Pyramid Networks}, + booktitle = {Proceedings of IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, + year = {2019} +} +``` diff --git a/mmdetection/configs/panoptic_fpn/metafile.yml b/mmdetection/configs/panoptic_fpn/metafile.yml new file mode 100644 index 00000000..c99275ec --- /dev/null +++ b/mmdetection/configs/panoptic_fpn/metafile.yml @@ -0,0 +1,70 @@ +Collections: + - Name: PanopticFPN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - PanopticFPN + Paper: + URL: https://arxiv.org/pdf/1901.02446 + Title: 'Panoptic feature pyramid networks' + README: configs/panoptic_fpn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.16.0/mmdet/models/detectors/panoptic_fpn.py#L7 + Version: v2.16.0 + +Models: + - Name: panoptic_fpn_r50_fpn_1x_coco + In Collection: PanopticFPN + Config: configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.6 + Epochs: 12 + Results: + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 40.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r50_fpn_1x_coco/panoptic_fpn_r50_fpn_1x_coco_20210821_101153-9668fd13.pth + + - Name: panoptic_fpn_r50_fpn_mstrain_3x_coco + In Collection: PanopticFPN + Config: configs/panoptic_fpn/panoptic-fpn_r50_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 4.6 + Epochs: 36 + Results: + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 42.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r50_fpn_mstrain_3x_coco/panoptic_fpn_r50_fpn_mstrain_3x_coco_20210824_171155-5650f98b.pth + + - Name: panoptic_fpn_r101_fpn_1x_coco + In Collection: PanopticFPN + Config: configs/panoptic_fpn/panoptic-fpn_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.5 + Epochs: 12 + Results: + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 42.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r101_fpn_1x_coco/panoptic_fpn_r101_fpn_1x_coco_20210820_193950-ab9157a2.pth + + - Name: panoptic_fpn_r101_fpn_mstrain_3x_coco + In Collection: PanopticFPN + Config: configs/panoptic_fpn/panoptic-fpn_r101_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 6.5 + Epochs: 36 + Results: + - Task: Panoptic Segmentation + Dataset: COCO + Metrics: + PQ: 44.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/panoptic_fpn/panoptic_fpn_r101_fpn_mstrain_3x_coco/panoptic_fpn_r101_fpn_mstrain_3x_coco_20210823_114712-9c99acc4.pth diff --git a/mmdetection/configs/panoptic_fpn/panoptic-fpn_r101_fpn_1x_coco.py b/mmdetection/configs/panoptic_fpn/panoptic-fpn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..b960254e --- /dev/null +++ b/mmdetection/configs/panoptic_fpn/panoptic-fpn_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './panoptic-fpn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/panoptic_fpn/panoptic-fpn_r101_fpn_ms-3x_coco.py b/mmdetection/configs/panoptic_fpn/panoptic-fpn_r101_fpn_ms-3x_coco.py new file mode 100644 index 00000000..268782ee --- /dev/null +++ b/mmdetection/configs/panoptic_fpn/panoptic-fpn_r101_fpn_ms-3x_coco.py @@ -0,0 +1,6 @@ +_base_ = './panoptic-fpn_r50_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py b/mmdetection/configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..c2c89ef5 --- /dev/null +++ b/mmdetection/configs/panoptic_fpn/panoptic-fpn_r50_fpn_1x_coco.py @@ -0,0 +1,45 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_panoptic.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + type='PanopticFPN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + pad_mask=True, + mask_pad_value=0, + pad_seg=True, + seg_pad_value=255), + semantic_head=dict( + type='PanopticFPNHead', + num_things_classes=80, + num_stuff_classes=53, + in_channels=256, + inner_channels=128, + start_level=0, + end_level=4, + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True), + conv_cfg=None, + loss_seg=dict( + type='CrossEntropyLoss', ignore_index=255, loss_weight=0.5)), + panoptic_fusion_head=dict( + type='HeuristicFusionHead', + num_things_classes=80, + num_stuff_classes=53), + test_cfg=dict( + rcnn=dict( + score_thr=0.6, + nms=dict(type='nms', iou_threshold=0.5, class_agnostic=True), + max_per_img=100, + mask_thr_binary=0.5), + # used in HeuristicFusionHead + panoptic=dict(mask_overlap=0.5, stuff_area_limit=4096))) + +# Forced to remove NumClassCheckHook +custom_hooks = [] diff --git a/mmdetection/configs/panoptic_fpn/panoptic-fpn_r50_fpn_ms-3x_coco.py b/mmdetection/configs/panoptic_fpn/panoptic-fpn_r50_fpn_ms-3x_coco.py new file mode 100644 index 00000000..b18a8f8d --- /dev/null +++ b/mmdetection/configs/panoptic_fpn/panoptic-fpn_r50_fpn_ms-3x_coco.py @@ -0,0 +1,35 @@ +_base_ = './panoptic-fpn_r50_fpn_1x_coco.py' + +# In mstrain 3x config, img_scale=[(1333, 640), (1333, 800)], +# multiscale_mode='range' +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='LoadPanopticAnnotations', + with_bbox=True, + with_mask=True, + with_seg=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# TODO: Use RepeatDataset to speed up training +# training schedule for 3x +train_cfg = dict(max_epochs=36, val_interval=3) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=36, + by_epoch=True, + milestones=[24, 33], + gamma=0.1) +] diff --git a/mmdetection/configs/pascal_voc/README.md b/mmdetection/configs/pascal_voc/README.md new file mode 100644 index 00000000..2ead3add --- /dev/null +++ b/mmdetection/configs/pascal_voc/README.md @@ -0,0 +1,40 @@ +# Pascal VOC + +> [The Pascal Visual Object Classes (VOC) Challenge](https://link.springer.com/article/10.1007/s11263-009-0275-4) + + + +## Abstract + +The Pascal Visual Object Classes (VOC) challenge is a benchmark in visual object category recognition and detection, providing the vision and machine learning communities with a standard dataset of images and annotation, and standard evaluation procedures. Organised annually from 2005 to present, the challenge and its associated dataset has become accepted as the benchmark for object detection. + +This paper describes the dataset and evaluation procedure. We review the state-of-the-art in evaluated methods for both classification and detection, analyse whether the methods are statistically different, what they are learning from the images (e.g. the object or its context), and what the methods find easy or confuse. The paper concludes with lessons learnt in the three year history of the challenge, and proposes directions for future improvement and extension. + +
    + +
    + +## Results and Models + +| Architecture | Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------------: | :------: | :-----: | :-----: | :------: | :------------: | :----: | :----------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster R-CNN C4 | R-50 | caffe | 18k | | - | 80.9 | [config](./faster-rcnn_r50-caffe-c4_ms-18k_voc0712.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/faster_rcnn_r50_caffe_c4_mstrain_18k_voc0712//home/dong/code_sensetime/2022Q1/mmdetection/work_dirs/prepare_voc/gather/pascal_voc/faster_rcnn_r50_caffe_c4_mstrain_18k_voc0712/faster_rcnn_r50_caffe_c4_mstrain_18k_voc0712_20220314_234327-847a14d2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/faster_rcnn_r50_caffe_c4_mstrain_18k_voc0712/faster_rcnn_r50_caffe_c4_mstrain_18k_voc0712_20220314_234327.log.json) | +| Faster R-CNN | R-50 | pytorch | 1x | 2.6 | - | 80.4 | [config](./faster-rcnn_r50_fpn_1x_voc0712.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/faster_rcnn_r50_fpn_1x_voc0712/faster_rcnn_r50_fpn_1x_voc0712_20220320_192712-54bef0f3.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/faster_rcnn_r50_fpn_1x_voc0712/faster_rcnn_r50_fpn_1x_voc0712_20220320_192712.log.json) | +| Retinanet | R-50 | pytorch | 1x | 2.1 | - | 77.3 | [config](./retinanet_r50_fpn_1x_voc0712.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/retinanet_r50_fpn_1x_voc0712/retinanet_r50_fpn_1x_voc0712_20200617-47cbdd0e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/retinanet_r50_fpn_1x_voc0712/retinanet_r50_fpn_1x_voc0712_20200616_014642.log.json) | +| SSD300 | VGG16 | - | 120e | - | - | 76.5 | [config](./ssd300_voc0712.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/ssd300_voc0712/ssd300_voc0712_20220320_194658-17edda1b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/ssd300_voc0712/ssd300_voc0712_20220320_194658.log.json) | +| SSD512 | VGG16 | - | 120e | - | - | 79.5 | [config](./ssd512_voc0712.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/ssd512_voc0712/ssd512_voc0712_20220320_194717-03cefefe.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/ssd512_voc0712/ssd512_voc0712_20220320_194717.log.json) | + +## Citation + +```latex +@Article{Everingham10, + author = "Everingham, M. and Van~Gool, L. and Williams, C. K. I. and Winn, J. and Zisserman, A.", + title = "The Pascal Visual Object Classes (VOC) Challenge", + journal = "International Journal of Computer Vision", + volume = "88", + year = "2010", + number = "2", + month = jun, + pages = "303--338", +} +``` diff --git a/mmdetection/configs/pascal_voc/faster-rcnn_r50-caffe-c4_ms-18k_voc0712.py b/mmdetection/configs/pascal_voc/faster-rcnn_r50-caffe-c4_ms-18k_voc0712.py new file mode 100644 index 00000000..dddc0bbd --- /dev/null +++ b/mmdetection/configs/pascal_voc/faster-rcnn_r50-caffe-c4_ms-18k_voc0712.py @@ -0,0 +1,86 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50-caffe-c4.py', + '../_base_/schedules/schedule_1x.py', '../_base_/datasets/voc0712.py', + '../_base_/default_runtime.py' +] +model = dict(roi_head=dict(bbox_head=dict(num_classes=20))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 480), (1333, 512), (1333, 544), (1333, 576), + (1333, 608), (1333, 640), (1333, 672), (1333, 704), + (1333, 736), (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + # avoid bboxes being resized + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + _delete_=True, + type='ConcatDataset', + datasets=[ + dict( + type='VOCDataset', + data_root={{_base_.data_root}}, + ann_file='VOC2007/ImageSets/Main/trainval.txt', + data_prefix=dict(sub_data_root='VOC2007/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args={{_base_.backend_args}}), + dict( + type='VOCDataset', + data_root={{_base_.data_root}}, + ann_file='VOC2012/ImageSets/Main/trainval.txt', + data_prefix=dict(sub_data_root='VOC2012/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args={{_base_.backend_args}}) + ])) + +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# training schedule for 18k +max_iter = 18000 +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=3000) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=100), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[12000, 16000], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) + +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=3000)) +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/pascal_voc/faster-rcnn_r50_fpn_1x_voc0712-cocofmt.py b/mmdetection/configs/pascal_voc/faster-rcnn_r50_fpn_1x_voc0712-cocofmt.py new file mode 100644 index 00000000..0b0aa41d --- /dev/null +++ b/mmdetection/configs/pascal_voc/faster-rcnn_r50_fpn_1x_voc0712-cocofmt.py @@ -0,0 +1,100 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', '../_base_/datasets/voc0712.py', + '../_base_/default_runtime.py' +] +model = dict(roi_head=dict(bbox_head=dict(num_classes=20))) + +METAINFO = { + 'classes': + ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', + 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', + 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'), + # palette is a list of color tuples, which is used for visualization. + 'palette': [(106, 0, 228), (119, 11, 32), (165, 42, 42), (0, 0, 192), + (197, 226, 255), (0, 60, 100), (0, 0, 142), (255, 77, 255), + (153, 69, 1), (120, 166, 157), (0, 182, 199), (0, 226, 252), + (182, 182, 255), (0, 0, 230), (220, 20, 60), (163, 255, 0), + (0, 82, 0), (3, 95, 161), (0, 80, 100), (183, 130, 88)] +} + +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/VOCdevkit/' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1000, 600), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1000, 600), keep_ratio=True), + # avoid bboxes being resized + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + dataset=dict( + type='RepeatDataset', + times=3, + dataset=dict( + _delete_=True, + type=dataset_type, + data_root=data_root, + ann_file='annotations/voc0712_trainval.json', + data_prefix=dict(img=''), + metainfo=METAINFO, + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args={{_base_.backend_args}}))) +val_dataloader = dict( + dataset=dict( + type=dataset_type, + ann_file='annotations/voc07_test.json', + data_prefix=dict(img=''), + metainfo=METAINFO, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/voc07_test.json', + metric='bbox', + format_only=False, + backend_args={{_base_.backend_args}}) +test_evaluator = val_evaluator + +# training schedule, the dataset is repeated 3 times, so the +# actual epoch = 4 * 3 = 12 +max_epochs = 4 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[3], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/pascal_voc/faster-rcnn_r50_fpn_1x_voc0712.py b/mmdetection/configs/pascal_voc/faster-rcnn_r50_fpn_1x_voc0712.py new file mode 100644 index 00000000..07391667 --- /dev/null +++ b/mmdetection/configs/pascal_voc/faster-rcnn_r50_fpn_1x_voc0712.py @@ -0,0 +1,35 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', '../_base_/datasets/voc0712.py', + '../_base_/default_runtime.py' +] +model = dict(roi_head=dict(bbox_head=dict(num_classes=20))) + +# training schedule, voc dataset is repeated 3 times, in +# `_base_/datasets/voc0712.py`, so the actual epoch = 4 * 3 = 12 +max_epochs = 4 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[3], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/pascal_voc/retinanet_r50_fpn_1x_voc0712.py b/mmdetection/configs/pascal_voc/retinanet_r50_fpn_1x_voc0712.py new file mode 100644 index 00000000..c86a6f19 --- /dev/null +++ b/mmdetection/configs/pascal_voc/retinanet_r50_fpn_1x_voc0712.py @@ -0,0 +1,34 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', '../_base_/datasets/voc0712.py', + '../_base_/default_runtime.py' +] +model = dict(bbox_head=dict(num_classes=20)) + +# training schedule, voc dataset is repeated 3 times, in +# `_base_/datasets/voc0712.py`, so the actual epoch = 4 * 3 = 12 +max_epochs = 4 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[3], + gamma=0.1) +] +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/configs/pascal_voc/ssd300_voc0712.py b/mmdetection/configs/pascal_voc/ssd300_voc0712.py new file mode 100644 index 00000000..ff7a1368 --- /dev/null +++ b/mmdetection/configs/pascal_voc/ssd300_voc0712.py @@ -0,0 +1,102 @@ +_base_ = [ + '../_base_/models/ssd300.py', '../_base_/datasets/voc0712.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] +model = dict( + bbox_head=dict( + num_classes=20, anchor_generator=dict(basesize_ratio_range=(0.2, + 0.9)))) +# dataset settings +dataset_type = 'VOCDataset' +data_root = 'data/VOCdevkit/' +input_size = 300 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='Expand', + mean={{_base_.model.data_preprocessor.mean}}, + to_rgb={{_base_.model.data_preprocessor.bgr_to_rgb}}, + ratio_range=(1, 4)), + dict( + type='MinIoURandomCrop', + min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + # avoid bboxes being resized + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=8, + num_workers=3, + dataset=dict( # RepeatDataset + # the dataset is repeated 10 times, and the training schedule is 2x, + # so the actual epoch = 12 * 10 = 120. + times=10, + dataset=dict( # ConcatDataset + # VOCDataset will add different `dataset_type` in dataset.metainfo, + # which will get error if using ConcatDataset. Adding + # `ignore_keys` can avoid this error. + ignore_keys=['dataset_type'], + datasets=[ + dict( + type=dataset_type, + data_root=data_root, + ann_file='VOC2007/ImageSets/Main/trainval.txt', + data_prefix=dict(sub_data_root='VOC2007/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline), + dict( + type=dataset_type, + data_root=data_root, + ann_file='VOC2012/ImageSets/Main/trainval.txt', + data_prefix=dict(sub_data_root='VOC2012/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline) + ]))) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +custom_hooks = [ + dict(type='NumClassCheckHook'), + dict(type='CheckInvalidLossHook', interval=50, priority='VERY_LOW') +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=1e-3, momentum=0.9, weight_decay=5e-4)) + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=24, + by_epoch=True, + milestones=[16, 20], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/pascal_voc/ssd512_voc0712.py b/mmdetection/configs/pascal_voc/ssd512_voc0712.py new file mode 100644 index 00000000..6c4dc8a3 --- /dev/null +++ b/mmdetection/configs/pascal_voc/ssd512_voc0712.py @@ -0,0 +1,82 @@ +_base_ = 'ssd300_voc0712.py' + +input_size = 512 +model = dict( + neck=dict( + out_channels=(512, 1024, 512, 256, 256, 256, 256), + level_strides=(2, 2, 2, 2, 1), + level_paddings=(1, 1, 1, 1, 1), + last_kernel_size=4), + bbox_head=dict( + in_channels=(512, 1024, 512, 256, 256, 256, 256), + anchor_generator=dict( + input_size=input_size, + strides=[8, 16, 32, 64, 128, 256, 512], + basesize_ratio_range=(0.15, 0.9), + ratios=([2], [2, 3], [2, 3], [2, 3], [2, 3], [2], [2])))) + +# dataset settings +dataset_type = 'VOCDataset' +data_root = 'data/VOCdevkit/' +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='Expand', + mean={{_base_.model.data_preprocessor.mean}}, + to_rgb={{_base_.model.data_preprocessor.bgr_to_rgb}}, + ratio_range=(1, 4)), + dict( + type='MinIoURandomCrop', + min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + # avoid bboxes being resized + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=8, + num_workers=3, + dataset=dict( # RepeatDataset + # the dataset is repeated 10 times, and the training schedule is 2x, + # so the actual epoch = 12 * 10 = 120. + times=10, + dataset=dict( # ConcatDataset + # VOCDataset will add different `dataset_type` in dataset.metainfo, + # which will get error if using ConcatDataset. Adding + # `ignore_keys` can avoid this error. + ignore_keys=['dataset_type'], + datasets=[ + dict( + type=dataset_type, + data_root=data_root, + ann_file='VOC2007/ImageSets/Main/trainval.txt', + data_prefix=dict(sub_data_root='VOC2007/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline), + dict( + type=dataset_type, + data_root=data_root, + ann_file='VOC2012/ImageSets/Main/trainval.txt', + data_prefix=dict(sub_data_root='VOC2012/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline) + ]))) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/pisa/README.md b/mmdetection/configs/pisa/README.md new file mode 100644 index 00000000..39f79ecd --- /dev/null +++ b/mmdetection/configs/pisa/README.md @@ -0,0 +1,50 @@ +# PISA + +> [Prime Sample Attention in Object Detection](https://arxiv.org/abs/1904.04821) + + + +## Abstract + +It is a common paradigm in object detection frameworks to treat all samples equally and target at maximizing the performance on average. In this work, we revisit this paradigm through a careful study on how different samples contribute to the overall performance measured in terms of mAP. Our study suggests that the samples in each mini-batch are neither independent nor equally important, and therefore a better classifier on average does not necessarily mean higher mAP. Motivated by this study, we propose the notion of Prime Samples, those that play a key role in driving the detection performance. We further develop a simple yet effective sampling and learning strategy called PrIme Sample Attention (PISA) that directs the focus of the training process towards such samples. Our experiments demonstrate that it is often more effective to focus on prime samples than hard samples when training a detector. Particularly, On the MSCOCO dataset, PISA outperforms the random sampling baseline and hard mining schemes, e.g., OHEM and Focal Loss, consistently by around 2% on both single-stage and two-stage detectors, even with a strong backbone ResNeXt-101. + +
    + +
    + +## Results and Models + +| PISA | Network | Backbone | Lr schd | box AP | mask AP | Config | Download | +| :--: | :----------: | :------------: | :-----: | :----: | :-----: | :----------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| × | Faster R-CNN | R-50-FPN | 1x | 36.4 | | - | | +| √ | Faster R-CNN | R-50-FPN | 1x | 38.4 | | [config](./faster-rcnn_r50_fpn_pisa_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_faster_rcnn_r50_fpn_1x_coco/pisa_faster_rcnn_r50_fpn_1x_coco-dea93523.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_faster_rcnn_r50_fpn_1x_coco/pisa_faster_rcnn_r50_fpn_1x_coco_20200506_185619.log.json) | +| × | Faster R-CNN | X101-32x4d-FPN | 1x | 40.1 | | - | | +| √ | Faster R-CNN | X101-32x4d-FPN | 1x | 41.9 | | [config](./faster-rcnn_x101-32x4d_fpn_pisa_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_faster_rcnn_x101_32x4d_fpn_1x_coco/pisa_faster_rcnn_x101_32x4d_fpn_1x_coco-e4accec4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_faster_rcnn_x101_32x4d_fpn_1x_coco/pisa_faster_rcnn_x101_32x4d_fpn_1x_coco_20200505_181503.log.json) | +| × | Mask R-CNN | R-50-FPN | 1x | 37.3 | 34.2 | - | | +| √ | Mask R-CNN | R-50-FPN | 1x | 39.1 | 35.2 | [config](./mask-rcnn_r50_fpn_pisa_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_mask_rcnn_r50_fpn_1x_coco/pisa_mask_rcnn_r50_fpn_1x_coco-dfcedba6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_mask_rcnn_r50_fpn_1x_coco/pisa_mask_rcnn_r50_fpn_1x_coco_20200508_150500.log.json) | +| × | Mask R-CNN | X101-32x4d-FPN | 1x | 41.1 | 37.1 | - | | +| √ | Mask R-CNN | X101-32x4d-FPN | 1x | | | | | +| × | RetinaNet | R-50-FPN | 1x | 35.6 | | - | | +| √ | RetinaNet | R-50-FPN | 1x | 36.9 | | [config](./retinanet-r50_fpn_pisa_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_retinanet_r50_fpn_1x_coco/pisa_retinanet_r50_fpn_1x_coco-76409952.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_retinanet_r50_fpn_1x_coco/pisa_retinanet_r50_fpn_1x_coco_20200504_014311.log.json) | +| × | RetinaNet | X101-32x4d-FPN | 1x | 39.0 | | - | | +| √ | RetinaNet | X101-32x4d-FPN | 1x | 40.7 | | [config](./retinanet_x101-32x4d_fpn_pisa_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_retinanet_x101_32x4d_fpn_1x_coco/pisa_retinanet_x101_32x4d_fpn_1x_coco-a0c13c73.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_retinanet_x101_32x4d_fpn_1x_coco/pisa_retinanet_x101_32x4d_fpn_1x_coco_20200505_001404.log.json) | +| × | SSD300 | VGG16 | 1x | 25.6 | | - | | +| √ | SSD300 | VGG16 | 1x | 27.6 | | [config](./ssd300_pisa_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_ssd300_coco/pisa_ssd300_coco-710e3ac9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_ssd300_coco/pisa_ssd300_coco_20200504_144325.log.json) | +| × | SSD512 | VGG16 | 1x | 29.3 | | - | | +| √ | SSD512 | VGG16 | 1x | 31.8 | | [config](./ssd512_pisa_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_ssd512_coco/pisa_ssd512_coco-247addee.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_ssd512_coco/pisa_ssd512_coco_20200508_131030.log.json) | + +**Notes:** + +- In the original paper, all models are trained and tested on mmdet v1.x, thus results may not be exactly the same with this release on v2.0. +- It is noted PISA only modifies the training pipeline so the inference time remains the same with the baseline. + +## Citation + +```latex +@inproceedings{cao2019prime, + title={Prime sample attention in object detection}, + author={Cao, Yuhang and Chen, Kai and Loy, Chen Change and Lin, Dahua}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + year={2020} +} +``` diff --git a/mmdetection/configs/pisa/faster-rcnn_r50_fpn_pisa_1x_coco.py b/mmdetection/configs/pisa/faster-rcnn_r50_fpn_pisa_1x_coco.py new file mode 100644 index 00000000..237a3b13 --- /dev/null +++ b/mmdetection/configs/pisa/faster-rcnn_r50_fpn_pisa_1x_coco.py @@ -0,0 +1,30 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' + +model = dict( + roi_head=dict( + type='PISARoIHead', + bbox_head=dict( + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))), + train_cfg=dict( + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + sampler=dict( + type='ScoreHLRSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True, + k=0.5, + bias=0.), + isr=dict(k=2, bias=0), + carl=dict(k=1, bias=0.2))), + test_cfg=dict( + rpn=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0))) diff --git a/mmdetection/configs/pisa/faster-rcnn_x101-32x4d_fpn_pisa_1x_coco.py b/mmdetection/configs/pisa/faster-rcnn_x101-32x4d_fpn_pisa_1x_coco.py new file mode 100644 index 00000000..4b2c8d9a --- /dev/null +++ b/mmdetection/configs/pisa/faster-rcnn_x101-32x4d_fpn_pisa_1x_coco.py @@ -0,0 +1,30 @@ +_base_ = '../faster_rcnn/faster-rcnn_x101-32x4d_fpn_1x_coco.py' + +model = dict( + roi_head=dict( + type='PISARoIHead', + bbox_head=dict( + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))), + train_cfg=dict( + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + sampler=dict( + type='ScoreHLRSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True, + k=0.5, + bias=0.), + isr=dict(k=2, bias=0), + carl=dict(k=1, bias=0.2))), + test_cfg=dict( + rpn=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0))) diff --git a/mmdetection/configs/pisa/mask-rcnn_r50_fpn_pisa_1x_coco.py b/mmdetection/configs/pisa/mask-rcnn_r50_fpn_pisa_1x_coco.py new file mode 100644 index 00000000..d6a68235 --- /dev/null +++ b/mmdetection/configs/pisa/mask-rcnn_r50_fpn_pisa_1x_coco.py @@ -0,0 +1,30 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' + +model = dict( + roi_head=dict( + type='PISARoIHead', + bbox_head=dict( + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))), + train_cfg=dict( + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + sampler=dict( + type='ScoreHLRSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True, + k=0.5, + bias=0.), + isr=dict(k=2, bias=0), + carl=dict(k=1, bias=0.2))), + test_cfg=dict( + rpn=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0))) diff --git a/mmdetection/configs/pisa/mask-rcnn_x101-32x4d_fpn_pisa_1x_coco.py b/mmdetection/configs/pisa/mask-rcnn_x101-32x4d_fpn_pisa_1x_coco.py new file mode 100644 index 00000000..f2ac19fe --- /dev/null +++ b/mmdetection/configs/pisa/mask-rcnn_x101-32x4d_fpn_pisa_1x_coco.py @@ -0,0 +1,30 @@ +_base_ = '../mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py' + +model = dict( + roi_head=dict( + type='PISARoIHead', + bbox_head=dict( + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))), + train_cfg=dict( + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + sampler=dict( + type='ScoreHLRSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True, + k=0.5, + bias=0.), + isr=dict(k=2, bias=0), + carl=dict(k=1, bias=0.2))), + test_cfg=dict( + rpn=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0))) diff --git a/mmdetection/configs/pisa/metafile.yml b/mmdetection/configs/pisa/metafile.yml new file mode 100644 index 00000000..3be5c3ba --- /dev/null +++ b/mmdetection/configs/pisa/metafile.yml @@ -0,0 +1,110 @@ +Collections: + - Name: PISA + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - PISA + - RPN + - ResNet + - RoIPool + Paper: + URL: https://arxiv.org/abs/1904.04821 + Title: 'Prime Sample Attention in Object Detection' + README: configs/pisa/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/roi_heads/pisa_roi_head.py#L8 + Version: v2.1.0 + +Models: + - Name: pisa_faster_rcnn_r50_fpn_1x_coco + In Collection: PISA + Config: configs/pisa/faster-rcnn_r50_fpn_pisa_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_faster_rcnn_r50_fpn_1x_coco/pisa_faster_rcnn_r50_fpn_1x_coco-dea93523.pth + + - Name: pisa_faster_rcnn_x101_32x4d_fpn_1x_coco + In Collection: PISA + Config: configs/pisa/faster-rcnn_x101-32x4d_fpn_pisa_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_faster_rcnn_x101_32x4d_fpn_1x_coco/pisa_faster_rcnn_x101_32x4d_fpn_1x_coco-e4accec4.pth + + - Name: pisa_mask_rcnn_r50_fpn_1x_coco + In Collection: PISA + Config: configs/pisa/mask-rcnn_r50_fpn_pisa_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 35.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_mask_rcnn_r50_fpn_1x_coco/pisa_mask_rcnn_r50_fpn_1x_coco-dfcedba6.pth + + - Name: pisa_retinanet_r50_fpn_1x_coco + In Collection: PISA + Config: configs/pisa/retinanet-r50_fpn_pisa_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_retinanet_r50_fpn_1x_coco/pisa_retinanet_r50_fpn_1x_coco-76409952.pth + + - Name: pisa_retinanet_x101_32x4d_fpn_1x_coco + In Collection: PISA + Config: configs/pisa/retinanet_x101-32x4d_fpn_pisa_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_retinanet_x101_32x4d_fpn_1x_coco/pisa_retinanet_x101_32x4d_fpn_1x_coco-a0c13c73.pth + + - Name: pisa_ssd300_coco + In Collection: PISA + Config: configs/pisa/ssd300_pisa_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 27.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_ssd300_coco/pisa_ssd300_coco-710e3ac9.pth + + - Name: pisa_ssd512_coco + In Collection: PISA + Config: configs/pisa/ssd512_pisa_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 31.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pisa/pisa_ssd512_coco/pisa_ssd512_coco-247addee.pth diff --git a/mmdetection/configs/pisa/retinanet-r50_fpn_pisa_1x_coco.py b/mmdetection/configs/pisa/retinanet-r50_fpn_pisa_1x_coco.py new file mode 100644 index 00000000..70f89e22 --- /dev/null +++ b/mmdetection/configs/pisa/retinanet-r50_fpn_pisa_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = '../retinanet/retinanet_r50_fpn_1x_coco.py' + +model = dict( + bbox_head=dict( + type='PISARetinaHead', + loss_bbox=dict(type='SmoothL1Loss', beta=0.11, loss_weight=1.0)), + train_cfg=dict(isr=dict(k=2., bias=0.), carl=dict(k=1., bias=0.2))) diff --git a/mmdetection/configs/pisa/retinanet_x101-32x4d_fpn_pisa_1x_coco.py b/mmdetection/configs/pisa/retinanet_x101-32x4d_fpn_pisa_1x_coco.py new file mode 100644 index 00000000..9caad45d --- /dev/null +++ b/mmdetection/configs/pisa/retinanet_x101-32x4d_fpn_pisa_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = '../retinanet/retinanet_x101-32x4d_fpn_1x_coco.py' + +model = dict( + bbox_head=dict( + type='PISARetinaHead', + loss_bbox=dict(type='SmoothL1Loss', beta=0.11, loss_weight=1.0)), + train_cfg=dict(isr=dict(k=2., bias=0.), carl=dict(k=1., bias=0.2))) diff --git a/mmdetection/configs/pisa/ssd300_pisa_coco.py b/mmdetection/configs/pisa/ssd300_pisa_coco.py new file mode 100644 index 00000000..b10236ba --- /dev/null +++ b/mmdetection/configs/pisa/ssd300_pisa_coco.py @@ -0,0 +1,7 @@ +_base_ = '../ssd/ssd300_coco.py' + +model = dict( + bbox_head=dict(type='PISASSDHead'), + train_cfg=dict(isr=dict(k=2., bias=0.), carl=dict(k=1., bias=0.2))) + +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/pisa/ssd512_pisa_coco.py b/mmdetection/configs/pisa/ssd512_pisa_coco.py new file mode 100644 index 00000000..939c7f45 --- /dev/null +++ b/mmdetection/configs/pisa/ssd512_pisa_coco.py @@ -0,0 +1,7 @@ +_base_ = '../ssd/ssd512_coco.py' + +model = dict( + bbox_head=dict(type='PISASSDHead'), + train_cfg=dict(isr=dict(k=2., bias=0.), carl=dict(k=1., bias=0.2))) + +optim_wrapper = dict(clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/point_rend/README.md b/mmdetection/configs/point_rend/README.md new file mode 100644 index 00000000..efa1dcac --- /dev/null +++ b/mmdetection/configs/point_rend/README.md @@ -0,0 +1,33 @@ +# PointRend + +> [PointRend: Image Segmentation as Rendering](https://arxiv.org/abs/1912.08193) + + + +## Abstract + +We present a new method for efficient high-quality image segmentation of objects and scenes. By analogizing classical computer graphics methods for efficient rendering with over- and undersampling challenges faced in pixel labeling tasks, we develop a unique perspective of image segmentation as a rendering problem. From this vantage, we present the PointRend (Point-based Rendering) neural network module: a module that performs point-based segmentation predictions at adaptively selected locations based on an iterative subdivision algorithm. PointRend can be flexibly applied to both instance and semantic segmentation tasks by building on top of existing state-of-the-art models. While many concrete implementations of the general idea are possible, we show that a simple design already achieves excellent results. Qualitatively, PointRend outputs crisp object boundaries in regions that are over-smoothed by previous methods. Quantitatively, PointRend yields significant gains on COCO and Cityscapes, for both instance and semantic segmentation. PointRend's efficiency enables output resolutions that are otherwise impractical in terms of memory or computation compared to existing approaches. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :------: | :---: | :-----: | :------: | :------------: | :----: | :-----: | :------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | caffe | 1x | 4.6 | | 38.4 | 36.3 | [config](./point-rend_r50-caffe_fpn_ms-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/point_rend/point_rend_r50_caffe_fpn_mstrain_1x_coco/point_rend_r50_caffe_fpn_mstrain_1x_coco-1bcb5fb4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/point_rend/point_rend_r50_caffe_fpn_mstrain_1x_coco/point_rend_r50_caffe_fpn_mstrain_1x_coco_20200612_161407.log.json) | +| R-50-FPN | caffe | 3x | 4.6 | | 41.0 | 38.0 | [config](./point-rend_r50-caffe_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/point_rend/point_rend_r50_caffe_fpn_mstrain_3x_coco/point_rend_r50_caffe_fpn_mstrain_3x_coco-e0ebb6b7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/point_rend/point_rend_r50_caffe_fpn_mstrain_3x_coco/point_rend_r50_caffe_fpn_mstrain_3x_coco_20200614_002632.log.json) | + +Note: All models are trained with multi-scale, the input image shorter side is randomly scaled to one of (640, 672, 704, 736, 768, 800). + +## Citation + +```latex +@InProceedings{kirillov2019pointrend, + title={{PointRend}: Image Segmentation as Rendering}, + author={Alexander Kirillov and Yuxin Wu and Kaiming He and Ross Girshick}, + journal={ArXiv:1912.08193}, + year={2019} +} +``` diff --git a/mmdetection/configs/point_rend/metafile.yml b/mmdetection/configs/point_rend/metafile.yml new file mode 100644 index 00000000..f54f8a86 --- /dev/null +++ b/mmdetection/configs/point_rend/metafile.yml @@ -0,0 +1,54 @@ +Collections: + - Name: PointRend + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - PointRend + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1912.08193 + Title: 'PointRend: Image Segmentation as Rendering' + README: configs/point_rend/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.2.0/mmdet/models/detectors/point_rend.py#L6 + Version: v2.2.0 + +Models: + - Name: point_rend_r50_caffe_fpn_mstrain_1x_coco + In Collection: PointRend + Config: configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py + Metadata: + Training Memory (GB): 4.6 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/point_rend/point_rend_r50_caffe_fpn_mstrain_1x_coco/point_rend_r50_caffe_fpn_mstrain_1x_coco-1bcb5fb4.pth + + - Name: point_rend_r50_caffe_fpn_mstrain_3x_coco + In Collection: PointRend + Config: configs/point_rend/point-rend_r50-caffe_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 4.6 + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/point_rend/point_rend_r50_caffe_fpn_mstrain_3x_coco/point_rend_r50_caffe_fpn_mstrain_3x_coco-e0ebb6b7.pth diff --git a/mmdetection/configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py b/mmdetection/configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py new file mode 100644 index 00000000..8b17f5a3 --- /dev/null +++ b/mmdetection/configs/point_rend/point-rend_r50-caffe_fpn_ms-1x_coco.py @@ -0,0 +1,44 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-1x_coco.py' +# model settings +model = dict( + type='PointRend', + roi_head=dict( + type='PointRendRoIHead', + mask_roi_extractor=dict( + type='GenericRoIExtractor', + aggregation='concat', + roi_layer=dict( + _delete_=True, type='SimpleRoIAlign', output_size=14), + out_channels=256, + featmap_strides=[4]), + mask_head=dict( + _delete_=True, + type='CoarseMaskHead', + num_fcs=2, + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + num_classes=80, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)), + point_head=dict( + type='MaskPointHead', + num_fcs=3, + in_channels=256, + fc_channels=256, + num_classes=80, + coarse_pred_each_layer=True, + loss_point=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rcnn=dict( + mask_size=7, + num_points=14 * 14, + oversample_ratio=3, + importance_sample_ratio=0.75)), + test_cfg=dict( + rcnn=dict( + subdivision_steps=5, + subdivision_num_points=28 * 28, + scale_factor=2))) diff --git a/mmdetection/configs/point_rend/point-rend_r50-caffe_fpn_ms-3x_coco.py b/mmdetection/configs/point_rend/point-rend_r50-caffe_fpn_ms-3x_coco.py new file mode 100644 index 00000000..b11faaa9 --- /dev/null +++ b/mmdetection/configs/point_rend/point-rend_r50-caffe_fpn_ms-3x_coco.py @@ -0,0 +1,18 @@ +_base_ = './point-rend_r50-caffe_fpn_ms-1x_coco.py' + +max_epochs = 36 + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] + +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/pvt/README.md b/mmdetection/configs/pvt/README.md new file mode 100644 index 00000000..fccad4f6 --- /dev/null +++ b/mmdetection/configs/pvt/README.md @@ -0,0 +1,57 @@ +# PVT + +> [Pyramid vision transformer: A versatile backbone for dense prediction without convolutions](https://arxiv.org/abs/2102.12122) + + + +## Abstract + +Although using convolutional neural networks (CNNs) as backbones achieves great successes in computer vision, this work investigates a simple backbone network useful for many dense prediction tasks without convolutions. Unlike the recently-proposed Transformer model (e.g., ViT) that is specially designed for image classification, we propose Pyramid Vision Transformer~(PVT), which overcomes the difficulties of porting Transformer to various dense prediction tasks. PVT has several merits compared to prior arts. (1) Different from ViT that typically has low-resolution outputs and high computational and memory cost, PVT can be not only trained on dense partitions of the image to achieve high output resolution, which is important for dense predictions but also using a progressive shrinking pyramid to reduce computations of large feature maps. (2) PVT inherits the advantages from both CNN and Transformer, making it a unified backbone in various vision tasks without convolutions by simply replacing CNN backbones. (3) We validate PVT by conducting extensive experiments, showing that it boosts the performance of many downstream tasks, e.g., object detection, semantic, and instance segmentation. For example, with a comparable number of parameters, RetinaNet+PVT achieves 40.4 AP on the COCO dataset, surpassing RetinNet+ResNet50 (36.3 AP) by 4.1 absolute AP. We hope PVT could serve as an alternative and useful backbone for pixel-level predictions and facilitate future researches. + +Transformer recently has shown encouraging progresses in computer vision. In this work, we present new baselines by improving the original Pyramid Vision Transformer (abbreviated as PVTv1) by adding three designs, including (1) overlapping patch embedding, (2) convolutional feed-forward networks, and (3) linear complexity attention layers. +With these modifications, our PVTv2 significantly improves PVTv1 on three tasks e.g., classification, detection, and segmentation. Moreover, PVTv2 achieves comparable or better performances than recent works such as Swin Transformer. We hope this work will facilitate state-of-the-art Transformer researches in computer vision. + +
    + +
    + +## Results and Models + +### RetinaNet (PVTv1) + +| Backbone | Lr schd | Mem (GB) | box AP | Config | Download | +| :--------: | :-----: | :------: | :----: | :----------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| PVT-Tiny | 12e | 8.5 | 36.6 | [config](./retinanet_pvt-t_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-t_fpn_1x_coco/retinanet_pvt-t_fpn_1x_coco_20210831_103110-17b566bd.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-t_fpn_1x_coco/retinanet_pvt-t_fpn_1x_coco_20210831_103110.log.json) | +| PVT-Small | 12e | 14.5 | 40.4 | [config](./retinanet_pvt-s_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-s_fpn_1x_coco/retinanet_pvt-s_fpn_1x_coco_20210906_142921-b6c94a5b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-s_fpn_1x_coco/retinanet_pvt-s_fpn_1x_coco_20210906_142921.log.json) | +| PVT-Medium | 12e | 20.9 | 41.7 | [config](./retinanet_pvt-m_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-m_fpn_1x_coco/retinanet_pvt-m_fpn_1x_coco_20210831_103243-55effa1b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-m_fpn_1x_coco/retinanet_pvt-m_fpn_1x_coco_20210831_103243.log.json) | + +### RetinaNet (PVTv2) + +| Backbone | Lr schd | Mem (GB) | box AP | Config | Download | +| :------: | :-----: | :------: | :----: | :-------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| PVTv2-B0 | 12e | 7.4 | 37.1 | [config](./retinanet_pvtv2-b0_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b0_fpn_1x_coco/retinanet_pvtv2-b0_fpn_1x_coco_20210831_103157-13e9aabe.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b0_fpn_1x_coco/retinanet_pvtv2-b0_fpn_1x_coco_20210831_103157.log.json) | +| PVTv2-B1 | 12e | 9.5 | 41.2 | [config](./retinanet_pvtv2-b1_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b1_fpn_1x_coco/retinanet_pvtv2-b1_fpn_1x_coco_20210831_103318-7e169a7d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b1_fpn_1x_coco/retinanet_pvtv2-b1_fpn_1x_coco_20210831_103318.log.json) | +| PVTv2-B2 | 12e | 16.2 | 44.6 | [config](./retinanet_pvtv2-b2_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b2_fpn_1x_coco/retinanet_pvtv2-b2_fpn_1x_coco_20210901_174843-529f0b9a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b2_fpn_1x_coco/retinanet_pvtv2-b2_fpn_1x_coco_20210901_174843.log.json) | +| PVTv2-B3 | 12e | 23.0 | 46.0 | [config](./retinanet_pvtv2-b3_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b3_fpn_1x_coco/retinanet_pvtv2-b3_fpn_1x_coco_20210903_151512-8357deff.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b3_fpn_1x_coco/retinanet_pvtv2-b3_fpn_1x_coco_20210903_151512.log.json) | +| PVTv2-B4 | 12e | 17.0 | 46.3 | [config](./retinanet_pvtv2-b4_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b4_fpn_1x_coco/retinanet_pvtv2-b4_fpn_1x_coco_20210901_170151-83795c86.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b4_fpn_1x_coco/retinanet_pvtv2-b4_fpn_1x_coco_20210901_170151.log.json) | +| PVTv2-B5 | 12e | 18.7 | 46.1 | [config](./retinanet_pvtv2-b5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b5_fpn_1x_coco/retinanet_pvtv2-b5_fpn_1x_coco_20210902_201800-3420eb57.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b5_fpn_1x_coco/retinanet_pvtv2-b5_fpn_1x_coco_20210902_201800.log.json) | + +## Citation + +```latex +@article{wang2021pyramid, + title={Pyramid vision transformer: A versatile backbone for dense prediction without convolutions}, + author={Wang, Wenhai and Xie, Enze and Li, Xiang and Fan, Deng-Ping and Song, Kaitao and Liang, Ding and Lu, Tong and Luo, Ping and Shao, Ling}, + journal={arXiv preprint arXiv:2102.12122}, + year={2021} +} +``` + +```latex +@article{wang2021pvtv2, + title={PVTv2: Improved Baselines with Pyramid Vision Transformer}, + author={Wang, Wenhai and Xie, Enze and Li, Xiang and Fan, Deng-Ping and Song, Kaitao and Liang, Ding and Lu, Tong and Luo, Ping and Shao, Ling}, + journal={arXiv preprint arXiv:2106.13797}, + year={2021} +} +``` diff --git a/mmdetection/configs/pvt/metafile.yml b/mmdetection/configs/pvt/metafile.yml new file mode 100644 index 00000000..58843784 --- /dev/null +++ b/mmdetection/configs/pvt/metafile.yml @@ -0,0 +1,243 @@ +Models: + - Name: retinanet_pvt-t_fpn_1x_coco + In Collection: RetinaNet + Config: configs/pvt/retinanet_pvt-t_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.5 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x NVIDIA V100 GPUs + Architecture: + - PyramidVisionTransformer + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-t_fpn_1x_coco/retinanet_pvt-t_fpn_1x_coco_20210831_103110-17b566bd.pth + Paper: + URL: https://arxiv.org/abs/2102.12122 + Title: "Pyramid Vision Transformer: A Versatile Backbone for Dense Prediction without Convolutions" + README: configs/pvt/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.17.0/mmdet/models/backbones/pvt.py#L315 + Version: 2.17.0 + + - Name: retinanet_pvt-s_fpn_1x_coco + In Collection: RetinaNet + Config: configs/pvt/retinanet_pvt-s_fpn_1x_coco.py + Metadata: + Training Memory (GB): 14.5 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x NVIDIA V100 GPUs + Architecture: + - PyramidVisionTransformer + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-s_fpn_1x_coco/retinanet_pvt-s_fpn_1x_coco_20210906_142921-b6c94a5b.pth + Paper: + URL: https://arxiv.org/abs/2102.12122 + Title: "Pyramid Vision Transformer: A Versatile Backbone for Dense Prediction without Convolutions" + README: configs/pvt/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.17.0/mmdet/models/backbones/pvt.py#L315 + Version: 2.17.0 + + - Name: retinanet_pvt-m_fpn_1x_coco + In Collection: RetinaNet + Config: configs/pvt/retinanet_pvt-m_fpn_1x_coco.py + Metadata: + Training Memory (GB): 20.9 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x NVIDIA V100 GPUs + Architecture: + - PyramidVisionTransformer + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvt-m_fpn_1x_coco/retinanet_pvt-m_fpn_1x_coco_20210831_103243-55effa1b.pth + Paper: + URL: https://arxiv.org/abs/2102.12122 + Title: "Pyramid Vision Transformer: A Versatile Backbone for Dense Prediction without Convolutions" + README: configs/pvt/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.17.0/mmdet/models/backbones/pvt.py#L315 + Version: 2.17.0 + + - Name: retinanet_pvtv2-b0_fpn_1x_coco + In Collection: RetinaNet + Config: configs/pvt/retinanet_pvtv2-b0_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.4 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x NVIDIA V100 GPUs + Architecture: + - PyramidVisionTransformerV2 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b0_fpn_1x_coco/retinanet_pvtv2-b0_fpn_1x_coco_20210831_103157-13e9aabe.pth + Paper: + URL: https://arxiv.org/abs/2106.13797 + Title: "PVTv2: Improved Baselines with Pyramid Vision Transformer" + README: configs/pvt/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.17.0/mmdet/models/backbones/pvt.py#L543 + Version: 2.17.0 + + - Name: retinanet_pvtv2-b1_fpn_1x_coco + In Collection: RetinaNet + Config: configs/pvt/retinanet_pvtv2-b1_fpn_1x_coco.py + Metadata: + Training Memory (GB): 9.5 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x NVIDIA V100 GPUs + Architecture: + - PyramidVisionTransformerV2 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b1_fpn_1x_coco/retinanet_pvtv2-b1_fpn_1x_coco_20210831_103318-7e169a7d.pth + Paper: + URL: https://arxiv.org/abs/2106.13797 + Title: "PVTv2: Improved Baselines with Pyramid Vision Transformer" + README: configs/pvt/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.17.0/mmdet/models/backbones/pvt.py#L543 + Version: 2.17.0 + + - Name: retinanet_pvtv2-b2_fpn_1x_coco + In Collection: RetinaNet + Config: configs/pvt/retinanet_pvtv2-b2_fpn_1x_coco.py + Metadata: + Training Memory (GB): 16.2 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x NVIDIA V100 GPUs + Architecture: + - PyramidVisionTransformerV2 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b2_fpn_1x_coco/retinanet_pvtv2-b2_fpn_1x_coco_20210901_174843-529f0b9a.pth + Paper: + URL: https://arxiv.org/abs/2106.13797 + Title: "PVTv2: Improved Baselines with Pyramid Vision Transformer" + README: configs/pvt/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.17.0/mmdet/models/backbones/pvt.py#L543 + Version: 2.17.0 + + - Name: retinanet_pvtv2-b3_fpn_1x_coco + In Collection: RetinaNet + Config: configs/pvt/retinanet_pvtv2-b3_fpn_1x_coco.py + Metadata: + Training Memory (GB): 23.0 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x NVIDIA V100 GPUs + Architecture: + - PyramidVisionTransformerV2 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b3_fpn_1x_coco/retinanet_pvtv2-b3_fpn_1x_coco_20210903_151512-8357deff.pth + Paper: + URL: https://arxiv.org/abs/2106.13797 + Title: "PVTv2: Improved Baselines with Pyramid Vision Transformer" + README: configs/pvt/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.17.0/mmdet/models/backbones/pvt.py#L543 + Version: 2.17.0 + + - Name: retinanet_pvtv2-b4_fpn_1x_coco + In Collection: RetinaNet + Config: configs/pvt/retinanet_pvtv2-b4_fpn_1x_coco.py + Metadata: + Training Memory (GB): 17.0 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x NVIDIA V100 GPUs + Architecture: + - PyramidVisionTransformerV2 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b4_fpn_1x_coco/retinanet_pvtv2-b4_fpn_1x_coco_20210901_170151-83795c86.pth + Paper: + URL: https://arxiv.org/abs/2106.13797 + Title: "PVTv2: Improved Baselines with Pyramid Vision Transformer" + README: configs/pvt/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.17.0/mmdet/models/backbones/pvt.py#L543 + Version: 2.17.0 + + - Name: retinanet_pvtv2-b5_fpn_1x_coco + In Collection: RetinaNet + Config: configs/pvt/retinanet_pvtv2-b5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 18.7 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x NVIDIA V100 GPUs + Architecture: + - PyramidVisionTransformerV2 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/pvt/retinanet_pvtv2-b5_fpn_1x_coco/retinanet_pvtv2-b5_fpn_1x_coco_20210902_201800-3420eb57.pth + Paper: + URL: https://arxiv.org/abs/2106.13797 + Title: "PVTv2: Improved Baselines with Pyramid Vision Transformer" + README: configs/pvt/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.17.0/mmdet/models/backbones/pvt.py#L543 + Version: 2.17.0 diff --git a/mmdetection/configs/pvt/retinanet_pvt-l_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvt-l_fpn_1x_coco.py new file mode 100644 index 00000000..1a6f604b --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvt-l_fpn_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = 'retinanet_pvt-t_fpn_1x_coco.py' +model = dict( + backbone=dict( + num_layers=[3, 8, 27, 3], + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_large.pth'))) +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict(type='AmpOptimWrapper') diff --git a/mmdetection/configs/pvt/retinanet_pvt-m_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvt-m_fpn_1x_coco.py new file mode 100644 index 00000000..b888f788 --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvt-m_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = 'retinanet_pvt-t_fpn_1x_coco.py' +model = dict( + backbone=dict( + num_layers=[3, 4, 18, 3], + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_medium.pth'))) diff --git a/mmdetection/configs/pvt/retinanet_pvt-s_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvt-s_fpn_1x_coco.py new file mode 100644 index 00000000..46603488 --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvt-s_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = 'retinanet_pvt-t_fpn_1x_coco.py' +model = dict( + backbone=dict( + num_layers=[3, 4, 6, 3], + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_small.pth'))) diff --git a/mmdetection/configs/pvt/retinanet_pvt-t_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvt-t_fpn_1x_coco.py new file mode 100644 index 00000000..5f67c444 --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvt-t_fpn_1x_coco.py @@ -0,0 +1,18 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + type='RetinaNet', + backbone=dict( + _delete_=True, + type='PyramidVisionTransformer', + num_layers=[2, 2, 2, 2], + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_tiny.pth')), + neck=dict(in_channels=[64, 128, 320, 512])) +# optimizer +optim_wrapper = dict( + optimizer=dict( + _delete_=True, type='AdamW', lr=0.0001, weight_decay=0.0001)) diff --git a/mmdetection/configs/pvt/retinanet_pvtv2-b0_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvtv2-b0_fpn_1x_coco.py new file mode 100644 index 00000000..cbebf90f --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvtv2-b0_fpn_1x_coco.py @@ -0,0 +1,19 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + type='RetinaNet', + backbone=dict( + _delete_=True, + type='PyramidVisionTransformerV2', + embed_dims=32, + num_layers=[2, 2, 2, 2], + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_v2_b0.pth')), + neck=dict(in_channels=[32, 64, 160, 256])) +# optimizer +optim_wrapper = dict( + optimizer=dict( + _delete_=True, type='AdamW', lr=0.0001, weight_decay=0.0001)) diff --git a/mmdetection/configs/pvt/retinanet_pvtv2-b1_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvtv2-b1_fpn_1x_coco.py new file mode 100644 index 00000000..5374c509 --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvtv2-b1_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = 'retinanet_pvtv2-b0_fpn_1x_coco.py' +model = dict( + backbone=dict( + embed_dims=64, + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_v2_b1.pth')), + neck=dict(in_channels=[64, 128, 320, 512])) diff --git a/mmdetection/configs/pvt/retinanet_pvtv2-b2_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvtv2-b2_fpn_1x_coco.py new file mode 100644 index 00000000..cf9a18de --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvtv2-b2_fpn_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = 'retinanet_pvtv2-b0_fpn_1x_coco.py' +model = dict( + backbone=dict( + embed_dims=64, + num_layers=[3, 4, 6, 3], + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_v2_b2.pth')), + neck=dict(in_channels=[64, 128, 320, 512])) diff --git a/mmdetection/configs/pvt/retinanet_pvtv2-b3_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvtv2-b3_fpn_1x_coco.py new file mode 100644 index 00000000..7a47f820 --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvtv2-b3_fpn_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = 'retinanet_pvtv2-b0_fpn_1x_coco.py' +model = dict( + backbone=dict( + embed_dims=64, + num_layers=[3, 4, 18, 3], + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_v2_b3.pth')), + neck=dict(in_channels=[64, 128, 320, 512])) diff --git a/mmdetection/configs/pvt/retinanet_pvtv2-b4_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvtv2-b4_fpn_1x_coco.py new file mode 100644 index 00000000..5faf4c50 --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvtv2-b4_fpn_1x_coco.py @@ -0,0 +1,20 @@ +_base_ = 'retinanet_pvtv2-b0_fpn_1x_coco.py' +model = dict( + backbone=dict( + embed_dims=64, + num_layers=[3, 8, 27, 3], + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_v2_b4.pth')), + neck=dict(in_channels=[64, 128, 320, 512])) +# optimizer +optim_wrapper = dict( + optimizer=dict( + _delete_=True, type='AdamW', lr=0.0001 / 1.4, weight_decay=0.0001)) + +# dataset settings +train_dataloader = dict(batch_size=1, num_workers=1) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (1 samples per GPU) +auto_scale_lr = dict(base_batch_size=8) diff --git a/mmdetection/configs/pvt/retinanet_pvtv2-b5_fpn_1x_coco.py b/mmdetection/configs/pvt/retinanet_pvtv2-b5_fpn_1x_coco.py new file mode 100644 index 00000000..afff8719 --- /dev/null +++ b/mmdetection/configs/pvt/retinanet_pvtv2-b5_fpn_1x_coco.py @@ -0,0 +1,21 @@ +_base_ = 'retinanet_pvtv2-b0_fpn_1x_coco.py' +model = dict( + backbone=dict( + embed_dims=64, + num_layers=[3, 6, 40, 3], + mlp_ratios=(4, 4, 4, 4), + init_cfg=dict(checkpoint='https://github.com/whai362/PVT/' + 'releases/download/v2/pvt_v2_b5.pth')), + neck=dict(in_channels=[64, 128, 320, 512])) +# optimizer +optim_wrapper = dict( + optimizer=dict( + _delete_=True, type='AdamW', lr=0.0001 / 1.4, weight_decay=0.0001)) + +# dataset settings +train_dataloader = dict(batch_size=1, num_workers=1) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (1 samples per GPU) +auto_scale_lr = dict(base_batch_size=8) diff --git a/mmdetection/configs/qdtrack/README.md b/mmdetection/configs/qdtrack/README.md new file mode 100644 index 00000000..5a6efe7d --- /dev/null +++ b/mmdetection/configs/qdtrack/README.md @@ -0,0 +1,89 @@ +# Quasi-Dense Similarity Learning for Multiple Object Tracking + +## Abstract + + + +Similarity learning has been recognized as a crucial step for object tracking. However, existing multiple object tracking methods only use sparse ground truth matching as the training objective, while ignoring the majority of the informative regions on the images. In this paper, we present Quasi-Dense Similarity Learning, which densely samples hundreds of region proposals on a pair of images for contrastive learning. We can directly combine this similarity learning with existing detection methods to build Quasi-Dense Tracking (QDTrack) without turning to displacementregression or motion priors. We also find that the resulting distinctive feature space admits a simple nearest neighbor search at the inference time. Despite its simplicity, QD-Track outperforms all existing methods on MOT, BDD100K, Waymo, and TAO tracking benchmarks. It achieves 68.7 MOTA at 20.3 FPS on MOT17 without using external training data. Compared to methods with similar detectors, it boosts almost 10 points of MOTA and significantly decreases the number of ID switches on BDD100K and Waymo datasets. + + + +
    + + +
    + +## Results and models on MOT17 + +| Method | Detector | Train Set | Test Set | Public | Inf time (fps) | HOTA | MOTA | IDF1 | FP | FN | IDSw. | Config | Download | +| :-----: | :----------: | :--------: | :------: | :----: | :------------: | :--: | :--: | :--: | :--: | :---: | :---: | :-------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| QDTrack | Faster R-CNN | half-train | half-val | N | - | 57.1 | 68.1 | 68.6 | 7707 | 42732 | 1083 | [config](qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py) | [model](https://download.openmmlab.com/mmtracking/mot/qdtrack/mot_dataset/qdtrack_faster-rcnn_r50_fpn_4e_mot17_20220315_145635-76f295ef.pth) \| [log](https://download.openmmlab.com/mmtracking/mot/qdtrack/mot_dataset/qdtrack_faster-rcnn_r50_fpn_4e_mot17_20220315_145635.log.json) | + +## Get started + +### 1. Development Environment Setup + +Tracking Development Environment Setup can refer to this [document](../../docs/en/get_started.md). + +### 2. Dataset Prepare + +Tracking Dataset Prepare can refer to this [document](../../docs/en/user_guides/tracking_dataset_prepare.md). + +### 3. Training + +Due to the influence of parameters such as learning rate in default configuration file, we recommend using 8 GPUs for training in order to reproduce accuracy. You can use the following command to start the training. + +```shell +# Training QDTrack on mot17-half-train dataset with following command. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_train.sh configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 +``` + +If you want to know about more detailed usage of `train.py/dist_train.sh/slurm_train.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 4. Testing and evaluation + +**4.1 Example on MOTxx-halfval dataset** + +```shell +# Example 1: Test on motXX-half-val set +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_test_tracking.sh configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 --checkpoint ${CHECKPOINT_PATH} +``` + +**4.2 use video_baesd to evaluating and testing** +we also provide two_ways(img_based or video_based) to evaluating and testing. +if you want to use video_based to evaluating and testing, you can modify config as follows + +``` +val_dataloader = dict( + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False)) +``` + +If you want to know about more detailed usage of `test_tracking.py/dist_test_tracking.sh/slurm_test_tracking.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 5.Inference + +Use a single GPU to predict a video and save it as a video. + +```shell +python demo/mot_demo.py demo/demo_mot.mp4 configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py --checkpoint ${CHECKPOINT_PATH} --out mot.mp4 +``` + +If you want to know about more detailed usage of `mot_demo.py`, please refer to this [document](../../docs/en/user_guides/tracking_inference.md). + +## Citation + + + +```latex +@inproceedings{pang2021quasi, + title={Quasi-dense similarity learning for multiple object tracking}, + author={Pang, Jiangmiao and Qiu, Linlu and Li, Xia and Chen, Haofeng and Li, Qi and Darrell, Trevor and Yu, Fisher}, + booktitle={Proceedings of the IEEE/CVF conference on computer vision and pattern recognition}, + pages={164--173}, + year={2021} +} +``` diff --git a/mmdetection/configs/qdtrack/metafile.yml b/mmdetection/configs/qdtrack/metafile.yml new file mode 100644 index 00000000..e5c5504d --- /dev/null +++ b/mmdetection/configs/qdtrack/metafile.yml @@ -0,0 +1,30 @@ +Collections: + - Name: QDTrack + Metadata: + Training Data: MOT17, crowdhuman + Training Techniques: + - SGD + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + Paper: + URL: https://arxiv.org/pdf/2006.06664.pdf + Title: Quasi-Dense Similarity Learning for Multiple Object Tracking + README: configs/qdtrack/README.md + +Models: + - Name: qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval + In Collection: QDTrack + Config: configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py + Metadata: + Training Data: MOT17 + Training Memory (GB): 5.83 + Epochs: 4 + Results: + - Task: Multi-object Tracking + Dataset: MOT17 + Metrics: + HOTA: 57.1 + MOTA: 68.1 + IDF1: 68.6 + Weights: https://download.openmmlab.com/mmtracking/mot/qdtrack/mot_dataset/qdtrack_faster-rcnn_r50_fpn_4e_mot17_20220315_145635-76f295ef.pth diff --git a/mmdetection/configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_4e_base.py b/mmdetection/configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_4e_base.py new file mode 100644 index 00000000..e3c17c3e --- /dev/null +++ b/mmdetection/configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_4e_base.py @@ -0,0 +1,118 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', '../_base_/default_runtime.py' +] + +detector = _base_.model +detector.pop('data_preprocessor') + +detector['backbone'].update( + dict( + norm_cfg=dict(type='BN', requires_grad=False), + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) +detector.rpn_head.loss_bbox.update( + dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)) +detector.rpn_head.bbox_coder.update(dict(clip_border=False)) +detector.roi_head.bbox_head.update(dict(num_classes=1)) +detector.roi_head.bbox_head.bbox_coder.update(dict(clip_border=False)) +detector['init_cfg'] = dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/' + 'faster_rcnn_r50_fpn_1x_coco-person/' + 'faster_rcnn_r50_fpn_1x_coco-person_20201216_175929-d022e227.pth' + # noqa: E501 +) +del _base_.model + +model = dict( + type='QDTrack', + data_preprocessor=dict( + type='TrackDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + detector=detector, + track_head=dict( + type='QuasiDenseTrackHead', + roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + embed_head=dict( + type='QuasiDenseEmbedHead', + num_convs=4, + num_fcs=1, + embed_channels=256, + norm_cfg=dict(type='GN', num_groups=32), + loss_track=dict(type='MultiPosCrossEntropyLoss', loss_weight=0.25), + loss_track_aux=dict( + type='MarginL2Loss', + neg_pos_ub=3, + pos_margin=0, + neg_margin=0.1, + hard_mining=True, + loss_weight=1.0)), + loss_bbox=dict(type='L1Loss', loss_weight=1.0), + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='CombinedSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=3, + add_gt_as_proposals=True, + pos_sampler=dict(type='InstanceBalancedPosSampler'), + neg_sampler=dict(type='RandomSampler')))), + tracker=dict( + type='QuasiDenseTracker', + init_score_thr=0.9, + obj_score_thr=0.5, + match_score_thr=0.5, + memo_tracklet_frames=30, + memo_backdrop_frames=1, + memo_momentum=0.8, + nms_conf_thr=0.5, + nms_backdrop_iou_thr=0.3, + nms_class_iou_thr=0.7, + with_cats=True, + match_metric='bisoftmax')) +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) +# learning policy +param_scheduler = [ + dict(type='MultiStepLR', begin=0, end=4, by_epoch=True, milestones=[3]) +] + +# runtime settings +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=4, val_interval=4) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +default_hooks = dict( + logger=dict(type='LoggerHook', interval=50), + visualization=dict(type='TrackVisualizationHook', draw=False)) + +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='TrackLocalVisualizer', vis_backends=vis_backends, name='visualizer') + +# custom hooks +custom_hooks = [ + # Synchronize model buffers such as running_mean and running_var in BN + # at the end of each epoch + dict(type='SyncBuffersHook') +] diff --git a/mmdetection/configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py b/mmdetection/configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..d87604da --- /dev/null +++ b/mmdetection/configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py @@ -0,0 +1,14 @@ +_base_ = [ + './qdtrack_faster-rcnn_r50_fpn_4e_base.py', + '../_base_/datasets/mot_challenge.py', +] + +# evaluator +val_evaluator = [ + dict(type='CocoVideoMetric', metric=['bbox'], classwise=True), + dict(type='MOTChallengeMetric', metric=['HOTA', 'CLEAR', 'Identity']) +] + +test_evaluator = val_evaluator +# The fluctuation of HOTA is about +-1. +randomness = dict(seed=6) diff --git a/mmdetection/configs/queryinst/README.md b/mmdetection/configs/queryinst/README.md new file mode 100644 index 00000000..ee62ccbf --- /dev/null +++ b/mmdetection/configs/queryinst/README.md @@ -0,0 +1,36 @@ +# QueryInst + +> [Instances as Queries](https://openaccess.thecvf.com/content/ICCV2021/html/Fang_Instances_As_Queries_ICCV_2021_paper.html) + + + +## Abstract + +We present QueryInst, a new perspective for instance segmentation. QueryInst is a multi-stage end-to-end system that treats instances of interest as learnable queries, enabling query based object detectors, e.g., Sparse R-CNN, to have strong instance segmentation performance. The attributes of instances such as categories, bounding boxes, instance masks, and instance association embeddings are represented by queries in a unified manner. In QueryInst, a query is shared by both detection and segmentation via dynamic convolutions and driven by parallelly-supervised multi-stage learning. We conduct extensive experiments on three challenging benchmarks, i.e., COCO, CityScapes, and YouTube-VIS to evaluate the effectiveness of QueryInst in object detection, instance segmentation, and video instance segmentation tasks. For the first time, we demonstrate that a simple end-to-end query based framework can achieve the state-of-the-art performance in various instance-level recognition tasks. + +
    + +
    + +## Results and Models + +| Model | Backbone | Style | Lr schd | Number of Proposals | Multi-Scale | RandomCrop | box AP | mask AP | Config | Download | +| :-------: | :-------: | :-----: | :-----: | :-----------------: | :---------: | :--------: | :----: | :-----: | :---------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| QueryInst | R-50-FPN | pytorch | 1x | 100 | False | False | 42.0 | 37.5 | [config](./queryinst_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_1x_coco/queryinst_r50_fpn_1x_coco_20210907_084916-5a8f1998.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_1x_coco/queryinst_r50_fpn_1x_coco_20210907_084916.log.json) | +| QueryInst | R-50-FPN | pytorch | 3x | 100 | True | False | 44.8 | 39.8 | [config](./queryinst_r50_fpn_ms-480-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_mstrain_480-800_3x_coco/queryinst_r50_fpn_mstrain_480-800_3x_coco_20210901_103643-7837af86.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_mstrain_480-800_3x_coco/queryinst_r50_fpn_mstrain_480-800_3x_coco_20210901_103643.log.json) | +| QueryInst | R-50-FPN | pytorch | 3x | 300 | True | True | 47.5 | 41.7 | [config](./queryinst_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco/queryinst_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20210904_101802-85cffbd8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco/queryinst_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20210904_101802.log.json) | +| QueryInst | R-101-FPN | pytorch | 3x | 100 | True | False | 46.4 | 41.0 | [config](./queryinst_r101_fpn_ms-480-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r101_fpn_mstrain_480-800_3x_coco/queryinst_r101_fpn_mstrain_480-800_3x_coco_20210904_104048-91f9995b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r101_fpn_mstrain_480-800_3x_coco/queryinst_r101_fpn_mstrain_480-800_3x_coco_20210904_104048.log.json) | +| QueryInst | R-101-FPN | pytorch | 3x | 300 | True | True | 49.0 | 42.9 | [config](./queryinst_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco/queryinst_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20210904_153621-76cce59f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco/queryinst_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20210904_153621.log.json) | + +## Citation + +```latex +@InProceedings{Fang_2021_ICCV, + author = {Fang, Yuxin and Yang, Shusheng and Wang, Xinggang and Li, Yu and Fang, Chen and Shan, Ying and Feng, Bin and Liu, Wenyu}, + title = {Instances As Queries}, + booktitle = {Proceedings of the IEEE/CVF International Conference on Computer Vision (ICCV)}, + month = {October}, + year = {2021}, + pages = {6910-6919} +} +``` diff --git a/mmdetection/configs/queryinst/metafile.yml b/mmdetection/configs/queryinst/metafile.yml new file mode 100644 index 00000000..3ea3b00a --- /dev/null +++ b/mmdetection/configs/queryinst/metafile.yml @@ -0,0 +1,100 @@ +Collections: + - Name: QueryInst + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - ResNet + - QueryInst + Paper: + URL: https://openaccess.thecvf.com/content/ICCV2021/papers/Fang_Instances_As_Queries_ICCV_2021_paper.pdf + Title: 'Instances as Queries' + README: configs/queryinst/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/detectors/queryinst.py + Version: v2.18.0 + +Models: + - Name: queryinst_r50_fpn_1x_coco + In Collection: QueryInst + Config: configs/queryinst/queryinst_r50_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_1x_coco/queryinst_r50_fpn_1x_coco_20210907_084916-5a8f1998.pth + + - Name: queryinst_r50_fpn_ms-480-800-3x_coco + In Collection: QueryInst + Config: configs/queryinst/queryinst_r50_fpn_ms-480-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_mstrain_480-800_3x_coco/queryinst_r50_fpn_mstrain_480-800_3x_coco_20210901_103643-7837af86.pth + + - Name: queryinst_r50_fpn_300-proposals_crop-ms-480-800-3x_coco + In Collection: QueryInst + Config: configs/queryinst/queryinst_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco/queryinst_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20210904_101802-85cffbd8.pth + + - Name: queryinst_r101_fpn_ms-480-800-3x_coco + In Collection: QueryInst + Config: configs/queryinst/queryinst_r101_fpn_ms-480-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r101_fpn_mstrain_480-800_3x_coco/queryinst_r101_fpn_mstrain_480-800_3x_coco_20210904_104048-91f9995b.pth + + - Name: queryinst_r101_fpn_300-proposals_crop-ms-480-800-3x_coco + In Collection: QueryInst + Config: configs/queryinst/queryinst_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 49.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 42.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/queryinst/queryinst_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco/queryinst_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20210904_153621-76cce59f.pth diff --git a/mmdetection/configs/queryinst/queryinst_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py b/mmdetection/configs/queryinst/queryinst_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py new file mode 100644 index 00000000..1692c134 --- /dev/null +++ b/mmdetection/configs/queryinst/queryinst_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py @@ -0,0 +1,7 @@ +_base_ = './queryinst_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/queryinst/queryinst_r101_fpn_ms-480-800-3x_coco.py b/mmdetection/configs/queryinst/queryinst_r101_fpn_ms-480-800-3x_coco.py new file mode 100644 index 00000000..dd5b7f45 --- /dev/null +++ b/mmdetection/configs/queryinst/queryinst_r101_fpn_ms-480-800-3x_coco.py @@ -0,0 +1,7 @@ +_base_ = './queryinst_r50_fpn_ms-480-800-3x_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/queryinst/queryinst_r50_fpn_1x_coco.py b/mmdetection/configs/queryinst/queryinst_r50_fpn_1x_coco.py new file mode 100644 index 00000000..63d61d78 --- /dev/null +++ b/mmdetection/configs/queryinst/queryinst_r50_fpn_1x_coco.py @@ -0,0 +1,155 @@ +_base_ = [ + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +num_stages = 6 +num_proposals = 100 +model = dict( + type='QueryInst', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=0, + add_extra_convs='on_input', + num_outs=4), + rpn_head=dict( + type='EmbeddingRPNHead', + num_proposals=num_proposals, + proposal_feature_channel=256), + roi_head=dict( + type='SparseRoIHead', + num_stages=num_stages, + stage_loss_weights=[1] * num_stages, + proposal_feature_channel=256, + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + mask_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type='DIIHead', + num_classes=80, + num_ffn_fcs=2, + num_heads=8, + num_cls_fcs=1, + num_reg_fcs=3, + feedforward_channels=2048, + in_channels=256, + dropout=0.0, + ffn_act_cfg=dict(type='ReLU', inplace=True), + dynamic_conv_cfg=dict( + type='DynamicConv', + in_channels=256, + feat_channels=64, + out_channels=256, + input_feat_shape=7, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN')), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=2.0), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + clip_border=False, + target_means=[0., 0., 0., 0.], + target_stds=[0.5, 0.5, 1., 1.])) for _ in range(num_stages) + ], + mask_head=[ + dict( + type='DynamicMaskHead', + dynamic_conv_cfg=dict( + type='DynamicConv', + in_channels=256, + feat_channels=64, + out_channels=256, + input_feat_shape=14, + with_proj=False, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN')), + num_convs=4, + num_classes=80, + roi_feat_size=14, + in_channels=256, + conv_kernel_size=3, + conv_out_channels=256, + class_agnostic=False, + norm_cfg=dict(type='BN'), + upsample_cfg=dict(type='deconv', scale_factor=2), + loss_mask=dict( + type='DiceLoss', + loss_weight=8.0, + use_sigmoid=True, + activate=False, + eps=1e-5)) for _ in range(num_stages) + ]), + # training and testing settings + train_cfg=dict( + rpn=None, + rcnn=[ + dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xyxy'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ]), + sampler=dict(type='PseudoSampler'), + pos_weight=1, + mask_size=28, + ) for _ in range(num_stages) + ]), + test_cfg=dict( + rpn=None, rcnn=dict(max_per_img=num_proposals, mask_thr_binary=0.5))) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + _delete_=True, type='AdamW', lr=0.0001, weight_decay=0.0001), + paramwise_cfg=dict( + custom_keys={'backbone': dict(lr_mult=0.1, decay_mult=1.0)}), + clip_grad=dict(max_norm=0.1, norm_type=2)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] diff --git a/mmdetection/configs/queryinst/queryinst_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py b/mmdetection/configs/queryinst/queryinst_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py new file mode 100644 index 00000000..33ab0612 --- /dev/null +++ b/mmdetection/configs/queryinst/queryinst_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py @@ -0,0 +1,45 @@ +_base_ = './queryinst_r50_fpn_ms-480-800-3x_coco.py' +num_proposals = 300 +model = dict( + rpn_head=dict(num_proposals=num_proposals), + test_cfg=dict( + _delete_=True, + rpn=None, + rcnn=dict(max_per_img=num_proposals, mask_thr_binary=0.5))) + +# augmentation strategy originates from DETR. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[[ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True) + ]]), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/queryinst/queryinst_r50_fpn_ms-480-800-3x_coco.py b/mmdetection/configs/queryinst/queryinst_r50_fpn_ms-480-800-3x_coco.py new file mode 100644 index 00000000..6b99374e --- /dev/null +++ b/mmdetection/configs/queryinst/queryinst_r50_fpn_ms-480-800-3x_coco.py @@ -0,0 +1,32 @@ +_base_ = './queryinst_r50_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# learning policy +max_epochs = 36 +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=max_epochs) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] diff --git a/mmdetection/configs/regnet/README.md b/mmdetection/configs/regnet/README.md new file mode 100644 index 00000000..0bfcec18 --- /dev/null +++ b/mmdetection/configs/regnet/README.md @@ -0,0 +1,121 @@ +# RegNet + +> [Designing Network Design Spaces](https://arxiv.org/abs/2003.13678) + + + +## Abstract + +In this work, we present a new network design paradigm. Our goal is to help advance the understanding of network design and discover design principles that generalize across settings. Instead of focusing on designing individual network instances, we design network design spaces that parametrize populations of networks. The overall process is analogous to classic manual design of networks, but elevated to the design space level. Using our methodology we explore the structure aspect of network design and arrive at a low-dimensional design space consisting of simple, regular networks that we call RegNet. The core insight of the RegNet parametrization is surprisingly simple: widths and depths of good networks can be explained by a quantized linear function. We analyze the RegNet design space and arrive at interesting findings that do not match the current practice of network design. The RegNet design space provides simple and fast networks that work well across a wide range of flop regimes. Under comparable training settings and flops, the RegNet models outperform the popular EfficientNet models while being up to 5x faster on GPUs. + +
    + +
    + +## Introduction + +We implement RegNetX and RegNetY models in detection systems and provide their first results on Mask R-CNN, Faster R-CNN and RetinaNet. + +The pre-trained models are converted from [model zoo of pycls](https://github.com/facebookresearch/pycls/blob/master/MODEL_ZOO.md). + +## Usage + +To use a regnet model, there are two steps to do: + +1. Convert the model to ResNet-style supported by MMDetection +2. Modify backbone and neck in config accordingly + +### Convert model + +We already prepare models of FLOPs from 400M to 12G in our model zoo. + +For more general usage, we also provide script `regnet2mmdet.py` in the tools directory to convert the key of models pretrained by [pycls](https://github.com/facebookresearch/pycls/) to +ResNet-style checkpoints used in MMDetection. + +```bash +python -u tools/model_converters/regnet2mmdet.py ${PRETRAIN_PATH} ${STORE_PATH} +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +### Modify config + +The users can modify the config's `depth` of backbone and corresponding keys in `arch` according to the configs in the [pycls model zoo](https://github.com/facebookresearch/pycls/blob/master/MODEL_ZOO.md). +The parameter `in_channels` in FPN can be found in the Figure 15 & 16 of the paper (`wi` in the legend). +This directory already provides some configs with their performance, using RegNetX from 800MF to 12GF level. +For other pre-trained models or self-implemented regnet models, the users are responsible to check these parameters by themselves. + +**Note**: Although Fig. 15 & 16 also provide `w0`, `wa`, `wm`, `group_w`, and `bot_mul` for `arch`, they are quantized thus inaccurate, using them sometimes produces different backbone that does not match the key in the pre-trained model. + +## Results and Models + +### Mask R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :----------------------------------------------------------------------------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :-------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [R-50-FPN](../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py) | pytorch | 1x | 4.4 | 12.0 | 38.2 | 34.7 | [config](../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205_050542.log.json) | +| [RegNetX-3.2GF-FPN](./mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py) | pytorch | 1x | 5.0 | | 40.3 | 36.6 | [config](./mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_1x_coco/mask_rcnn_regnetx-3.2GF_fpn_1x_coco_20200520_163141-2a9d1814.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_1x_coco/mask_rcnn_regnetx-3.2GF_fpn_1x_coco_20200520_163141.log.json) | +| [RegNetX-4.0GF-FPN](./mask-rcnn_regnetx-4GF_fpn_1x_coco.py) | pytorch | 1x | 5.5 | | 41.5 | 37.4 | [config](./mask-rcnn_regnetx-4GF_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-4GF_fpn_1x_coco/mask_rcnn_regnetx-4GF_fpn_1x_coco_20200517_180217-32e9c92d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-4GF_fpn_1x_coco/mask_rcnn_regnetx-4GF_fpn_1x_coco_20200517_180217.log.json) | +| [R-101-FPN](../mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py) | pytorch | 1x | 6.4 | 10.3 | 40.0 | 36.1 | [config](../mask_rcnn/mask-rcnn_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_1x_coco/mask_rcnn_r101_fpn_1x_coco_20200204-1efe0ed5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_1x_coco/mask_rcnn_r101_fpn_1x_coco_20200204_144809.log.json) | +| [RegNetX-6.4GF-FPN](./mask-rcnn_regnetx-6.4GF_fpn_1x_coco.py) | pytorch | 1x | 6.1 | | 41.0 | 37.1 | [config](./mask-rcnn_regnetx-6.4GF_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-6.4GF_fpn_1x_coco/mask_rcnn_regnetx-6.4GF_fpn_1x_coco_20200517_180439-3a7aae83.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-6.4GF_fpn_1x_coco/mask_rcnn_regnetx-6.4GF_fpn_1x_coco_20200517_180439.log.json) | +| [X-101-32x4d-FPN](../mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py) | pytorch | 1x | 7.6 | 9.4 | 41.9 | 37.5 | [config](../mask_rcnn/mask-rcnn_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_1x_coco/mask_rcnn_x101_32x4d_fpn_1x_coco_20200205-478d0b67.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_x101_32x4d_fpn_1x_coco/mask_rcnn_x101_32x4d_fpn_1x_coco_20200205_034906.log.json) | +| [RegNetX-8.0GF-FPN](./mask-rcnn_regnetx-8GF_fpn_1x_coco.py) | pytorch | 1x | 6.4 | | 41.7 | 37.5 | [config](./mask-rcnn_regnetx-8GF_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-8GF_fpn_1x_coco/mask_rcnn_regnetx-8GF_fpn_1x_coco_20200517_180515-09daa87e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-8GF_fpn_1x_coco/mask_rcnn_regnetx-8GF_fpn_1x_coco_20200517_180515.log.json) | +| [RegNetX-12GF-FPN](./mask-rcnn_regnetx-12GF_fpn_1x_coco.py) | pytorch | 1x | 7.4 | | 42.2 | 38 | [config](./mask-rcnn_regnetx-12GF_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-12GF_fpn_1x_coco/mask_rcnn_regnetx-12GF_fpn_1x_coco_20200517_180552-b538bd8b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-12GF_fpn_1x_coco/mask_rcnn_regnetx-12GF_fpn_1x_coco_20200517_180552.log.json) | +| [RegNetX-3.2GF-FPN-DCN-C3-C5](./mask-rcnn_regnetx-3.2GF-mdconv-c3-c5_fpn_1x_coco.py) | pytorch | 1x | 5.0 | | 40.3 | 36.6 | [config](./mask-rcnn_regnetx-3.2GF-mdconv-c3-c5_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_mdconv_c3-c5_1x_coco/mask_rcnn_regnetx-3.2GF_fpn_mdconv_c3-c5_1x_coco_20200520_172726-75f40794.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_mdconv_c3-c5_1x_coco/mask_rcnn_regnetx-3.2GF_fpn_mdconv_c3-c5_1x_coco_20200520_172726.log.json) | + +### Faster R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------------------------------------------------------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [R-50-FPN](../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py) | pytorch | 1x | 4.0 | 18.2 | 37.4 | [config](../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130_204655.log.json) | +| [RegNetX-3.2GF-FPN](./faster-rcnn_regnetx-3.2GF_fpn_1x_coco.py) | pytorch | 1x | 4.5 | | 39.9 | [config](./faster-rcnn_regnetx-3.2GF_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-3.2GF_fpn_1x_coco/faster_rcnn_regnetx-3.2GF_fpn_1x_coco_20200517_175927-126fd9bf.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-3.2GF_fpn_1x_coco/faster_rcnn_regnetx-3.2GF_fpn_1x_coco_20200517_175927.log.json) | +| [RegNetX-3.2GF-FPN](./faster-rcnn_regnetx-3.2GF_fpn_2x_coco.py) | pytorch | 2x | 4.5 | | 41.1 | [config](./faster-rcnn_regnetx-3.2GF_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-3.2GF_fpn_2x_coco/faster_rcnn_regnetx-3.2GF_fpn_2x_coco_20200520_223955-e2081918.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-3.2GF_fpn_2x_coco/faster_rcnn_regnetx-3.2GF_fpn_2x_coco_20200520_223955.log.json) | + +### RetinaNet + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-----------------------------------------------------------: | :-----: | :-----: | :------: | :------------: | :----: | :-------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [R-50-FPN](../retinanet/retinanet_r50_fpn_1x_coco.py) | pytorch | 1x | 3.8 | 16.6 | 36.5 | [config](../retinanet/retinanet_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_1x_coco/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_1x_coco/retinanet_r50_fpn_1x_coco_20200130_002941.log.json) | +| [RegNetX-800MF-FPN](./retinanet_regnetx-800MF_fpn_1x_coco.py) | pytorch | 1x | 2.5 | | 35.6 | [config](./retinanet_regnetx-800MF_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/retinanet_regnetx-800MF_fpn_1x_coco/retinanet_regnetx-800MF_fpn_1x_coco_20200517_191403-f6f91d10.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/retinanet_regnetx-800MF_fpn_1x_coco/retinanet_regnetx-800MF_fpn_1x_coco_20200517_191403.log.json) | +| [RegNetX-1.6GF-FPN](./retinanet_regnetx-1.6GF_fpn_1x_coco.py) | pytorch | 1x | 3.3 | | 37.3 | [config](./retinanet_regnetx-1.6GF_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/retinanet_regnetx-1.6GF_fpn_1x_coco/retinanet_regnetx-1.6GF_fpn_1x_coco_20200517_191403-37009a9d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/retinanet_regnetx-1.6GF_fpn_1x_coco/retinanet_regnetx-1.6GF_fpn_1x_coco_20200517_191403.log.json) | +| [RegNetX-3.2GF-FPN](./retinanet_regnetx-3.2GF_fpn_1x_coco.py) | pytorch | 1x | 4.2 | | 39.1 | [config](./retinanet_regnetx-3.2GF_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/retinanet_regnetx-3.2GF_fpn_1x_coco/retinanet_regnetx-3.2GF_fpn_1x_coco_20200520_163141-cb1509e8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/retinanet_regnetx-3.2GF_fpn_1x_coco/retinanet_regnetx-3.2GF_fpn_1x_coco_20200520_163141.log.json) | + +### Pre-trained models + +We also train some models with longer schedules and multi-scale training. The users could finetune them for downstream tasks. + +| Method | Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :---------------: | :----------------------------------------------------------------------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :-----------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster RCNN | [RegNetX-400MF-FPN](./faster-rcnn_regnetx-400MF_fpn_ms-3x_coco.py) | pytorch | 3x | 2.3 | | 37.1 | - | [config](./faster-rcnn_regnetx-400MF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-400MF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-400MF_fpn_mstrain_3x_coco_20210526_095112-e1967c37.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-400MF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-400MF_fpn_mstrain_3x_coco_20210526_095112.log.json) | +| Faster RCNN | [RegNetX-800MF-FPN](./faster-rcnn_regnetx-800MF_fpn_ms-3x_coco.py) | pytorch | 3x | 2.8 | | 38.8 | - | [config](./faster-rcnn_regnetx-800MF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-800MF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-800MF_fpn_mstrain_3x_coco_20210526_095118-a2c70b20.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-800MF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-800MF_fpn_mstrain_3x_coco_20210526_095118.log.json) | +| Faster RCNN | [RegNetX-1.6GF-FPN](./faster-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py) | pytorch | 3x | 3.4 | | 40.5 | - | [config](./faster-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-1.6GF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-1_20210526_095325-94aa46cc.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-1.6GF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-1_20210526_095325.log.json) | +| Faster RCNN | [RegNetX-3.2GF-FPN](./faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py) | pytorch | 3x | 4.4 | | 42.3 | - | [config](./faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-3_20210526_095152-e16a5227.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-3_20210526_095152.log.json) | +| Faster RCNN | [RegNetX-4GF-FPN](./faster-rcnn_regnetx-4GF_fpn_ms-3x_coco.py) | pytorch | 3x | 4.9 | | 42.8 | - | [config](./faster-rcnn_regnetx-4GF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-4GF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-4GF_fpn_mstrain_3x_coco_20210526_095201-65eaf841.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-4GF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-4GF_fpn_mstrain_3x_coco_20210526_095201.log.json) | +| Mask RCNN | [RegNetX-400MF-FPN](./mask-rcnn_regnetx-400MF_fpn_ms-poly-3x_coco.py) | pytorch | 3x | 2.5 | | 37.6 | 34.4 | [config](./mask-rcnn_regnetx-400MF_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-400MF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-400MF_fpn_mstrain-poly_3x_coco_20210601_235443-8aac57a4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-400MF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-400MF_fpn_mstrain-poly_3x_coco_20210601_235443.log.json) | +| Mask RCNN | [RegNetX-800MF-FPN](./mask-rcnn_regnetx-800MF_fpn_ms-poly-3x_coco.py) | pytorch | 3x | 2.9 | | 39.5 | 36.1 | [config](./mask-rcnn_regnetx-800MF_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-800MF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-800MF_fpn_mstrain-poly_3x_coco_20210602_210641-715d51f5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-800MF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-800MF_fpn_mstrain-poly_3x_coco_20210602_210641.log.json) | +| Mask RCNN | [RegNetX-1.6GF-FPN](./mask-rcnn_regnetx-1.6GF_fpn_ms-poly-3x_coco.py) | pytorch | 3x | 3.6 | | 40.9 | 37.5 | [config](./mask-rcnn_regnetx-1.6GF_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-1.6GF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-1_20210602_210641-6764cff5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-1.6GF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-1_20210602_210641.log.json) | +| Mask RCNN | [RegNetX-3.2GF-FPN](./mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py) | pytorch | 3x | 5.0 | | 43.1 | 38.7 | [config](./mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco_20200521_202221-99879813.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco_20200521_202221.log.json) | +| Mask RCNN | [RegNetX-4GF-FPN](./mask-rcnn_regnetx-4GF_fpn_ms-poly-3x_coco.py) | pytorch | 3x | 5.1 | | 43.4 | 39.2 | [config](./mask-rcnn_regnetx-4GF_fpn_ms-poly-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-4GF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-4GF_fpn_mstrain-poly_3x_coco_20210602_032621-00f0331c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-4GF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-4GF_fpn_mstrain-poly_3x_coco_20210602_032621.log.json) | +| Cascade Mask RCNN | [RegNetX-400MF-FPN](./cascade-mask-rcnn_regnetx-400MF_fpn_ms-3x_coco.py) | pytorch | 3x | 4.3 | | 41.6 | 36.4 | [config](./cascade-mask-rcnn_regnetx-400MF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-400MF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-400MF_fpn_mstrain_3x_coco_20210715_211619-5142f449.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-400MF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-400MF_fpn_mstrain_3x_coco_20210715_211619.log.json) | +| Cascade Mask RCNN | [RegNetX-800MF-FPN](./cascade-mask-rcnn_regnetx-800MF_fpn_ms-3x_coco.py) | pytorch | 3x | 4.8 | | 42.8 | 37.6 | [config](./cascade-mask-rcnn_regnetx-800MF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-800MF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-800MF_fpn_mstrain_3x_coco_20210715_211616-dcbd13f4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-800MF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-800MF_fpn_mstrain_3x_coco_20210715_211616.log.json) | +| Cascade Mask RCNN | [RegNetX-1.6GF-FPN](./cascade-mask-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py) | pytorch | 3x | 5.4 | | 44.5 | 39.0 | [config](./cascade-mask-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-1.6GF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-1_20210715_211616-75f29a61.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-1.6GF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-1_20210715_211616.log.json) | +| Cascade Mask RCNN | [RegNetX-3.2GF-FPN](./cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py) | pytorch | 3x | 6.4 | | 45.8 | 40.0 | [config](./cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-3_20210715_211616-b9c2c58b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-3_20210715_211616.log.json) | +| Cascade Mask RCNN | [RegNetX-4GF-FPN](./cascade-mask-rcnn_regnetx-4GF_fpn_ms-3x_coco.py) | pytorch | 3x | 6.9 | | 45.8 | 40.0 | [config](./cascade-mask-rcnn_regnetx-4GF_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-4GF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-4GF_fpn_mstrain_3x_coco_20210715_212034-cbb1be4c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-4GF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-4GF_fpn_mstrain_3x_coco_20210715_212034.log.json) | + +### Notice + +1. The models are trained using a different weight decay, i.e., `weight_decay=5e-5` according to the setting in ImageNet training. This brings improvement of at least 0.7 AP absolute but does not improve the model using ResNet-50. +2. RetinaNets using RegNets are trained with learning rate 0.02 with gradient clip. We find that using learning rate 0.02 could improve the results by at least 0.7 AP absolute and gradient clip is necessary to stabilize the training. However, this does not improve the performance of ResNet-50-FPN RetinaNet. + +## Citation + +```latex +@article{radosavovic2020designing, + title={Designing Network Design Spaces}, + author={Ilija Radosavovic and Raj Prateek Kosaraju and Ross Girshick and Kaiming He and Piotr Dollár}, + year={2020}, + eprint={2003.13678}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} +``` diff --git a/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..74e6adab --- /dev/null +++ b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = 'cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_1.6gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_1.6gf')), + neck=dict( + type='FPN', + in_channels=[72, 168, 408, 912], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..ea219021 --- /dev/null +++ b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py @@ -0,0 +1,28 @@ +_base_ = [ + '../common/ms_3x_coco-instance.py', + '../_base_/models/cascade-mask-rcnn_r50_fpn.py' +] +model = dict( + data_preprocessor=dict( + # The mean and std are used in PyCls when training RegNets + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False), + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_3.2gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_3.2gf')), + neck=dict( + type='FPN', + in_channels=[96, 192, 432, 1008], + out_channels=256, + num_outs=5)) + +optim_wrapper = dict(optimizer=dict(weight_decay=0.00005)) diff --git a/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-400MF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-400MF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..3fe47f83 --- /dev/null +++ b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-400MF_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = 'cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_400mf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_400mf')), + neck=dict( + type='FPN', + in_channels=[32, 64, 160, 384], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-4GF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-4GF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..e22886a8 --- /dev/null +++ b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-4GF_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = 'cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_4.0gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_4.0gf')), + neck=dict( + type='FPN', + in_channels=[80, 240, 560, 1360], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-800MF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-800MF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..655bdc60 --- /dev/null +++ b/mmdetection/configs/regnet/cascade-mask-rcnn_regnetx-800MF_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = 'cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_800mf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_800mf')), + neck=dict( + type='FPN', + in_channels=[64, 128, 288, 672], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/faster-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/faster-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..e9e8302b --- /dev/null +++ b/mmdetection/configs/regnet/faster-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = 'faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_1.6gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_1.6gf')), + neck=dict( + type='FPN', + in_channels=[72, 168, 408, 912], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_1x_coco.py b/mmdetection/configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_1x_coco.py new file mode 100644 index 00000000..db49092e --- /dev/null +++ b/mmdetection/configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_1x_coco.py @@ -0,0 +1,30 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + data_preprocessor=dict( + # The mean and std are used in PyCls when training RegNets + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False), + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_3.2gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_3.2gf')), + neck=dict( + type='FPN', + in_channels=[96, 192, 432, 1008], + out_channels=256, + num_outs=5)) + +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.00005)) diff --git a/mmdetection/configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_2x_coco.py b/mmdetection/configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_2x_coco.py new file mode 100644 index 00000000..be533603 --- /dev/null +++ b/mmdetection/configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './faster-rcnn_regnetx-3.2GF_fpn_1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..d3d5d5d6 --- /dev/null +++ b/mmdetection/configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py @@ -0,0 +1,25 @@ +_base_ = ['../common/ms_3x_coco.py', '../_base_/models/faster-rcnn_r50_fpn.py'] +model = dict( + data_preprocessor=dict( + # The mean and std are used in PyCls when training RegNets + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False), + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_3.2gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_3.2gf')), + neck=dict( + type='FPN', + in_channels=[96, 192, 432, 1008], + out_channels=256, + num_outs=5)) + +optim_wrapper = dict(optimizer=dict(weight_decay=0.00005)) diff --git a/mmdetection/configs/regnet/faster-rcnn_regnetx-400MF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/faster-rcnn_regnetx-400MF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..2edeff9c --- /dev/null +++ b/mmdetection/configs/regnet/faster-rcnn_regnetx-400MF_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = 'faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_400mf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_400mf')), + neck=dict( + type='FPN', + in_channels=[32, 64, 160, 384], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/faster-rcnn_regnetx-4GF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/faster-rcnn_regnetx-4GF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..afcbb5d5 --- /dev/null +++ b/mmdetection/configs/regnet/faster-rcnn_regnetx-4GF_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = 'faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_4.0gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_4.0gf')), + neck=dict( + type='FPN', + in_channels=[80, 240, 560, 1360], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/faster-rcnn_regnetx-800MF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/faster-rcnn_regnetx-800MF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..f659ec96 --- /dev/null +++ b/mmdetection/configs/regnet/faster-rcnn_regnetx-800MF_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = 'faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_800mf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_800mf')), + neck=dict( + type='FPN', + in_channels=[64, 128, 288, 672], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-1.6GF_fpn_ms-poly-3x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-1.6GF_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..60874c66 --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-1.6GF_fpn_ms-poly-3x_coco.py @@ -0,0 +1,26 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] + +model = dict( + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_1.6gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_1.6gf')), + neck=dict( + type='FPN', + in_channels=[72, 168, 408, 912], + out_channels=256, + num_outs=5)) + +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.00005), + clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-12GF_fpn_1x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-12GF_fpn_1x_coco.py new file mode 100644 index 00000000..e82cecea --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-12GF_fpn_1x_coco.py @@ -0,0 +1,17 @@ +_base_ = './mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_12gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_12gf')), + neck=dict( + type='FPN', + in_channels=[224, 448, 896, 2240], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-3.2GF-mdconv-c3-c5_fpn_1x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-3.2GF-mdconv-c3-c5_fpn_1x_coco.py new file mode 100644 index 00000000..c7c1d1ac --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-3.2GF-mdconv-c3-c5_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = 'mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_3.2gf'))) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py new file mode 100644 index 00000000..c52bf13f --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py @@ -0,0 +1,30 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + data_preprocessor=dict( + # The mean and std are used in PyCls when training RegNets + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False), + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_3.2gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_3.2gf')), + neck=dict( + type='FPN', + in_channels=[96, 192, 432, 1008], + out_channels=256, + num_outs=5)) + +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.00005)) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py new file mode 100644 index 00000000..36482c93 --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py @@ -0,0 +1,60 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + data_preprocessor=dict( + # The mean and std are used in PyCls when training RegNets + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False), + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_3.2gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_3.2gf')), + neck=dict( + type='FPN', + in_channels=[96, 192, 432, 1008], + out_channels=256, + num_outs=5)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.00005), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning policy +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-400MF_fpn_ms-poly-3x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-400MF_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..b96e1921 --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-400MF_fpn_ms-poly-3x_coco.py @@ -0,0 +1,26 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] + +model = dict( + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_400mf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_400mf')), + neck=dict( + type='FPN', + in_channels=[32, 64, 160, 384], + out_channels=256, + num_outs=5)) + +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.00005), + clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-4GF_fpn_1x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-4GF_fpn_1x_coco.py new file mode 100644 index 00000000..ce9f8ef4 --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-4GF_fpn_1x_coco.py @@ -0,0 +1,17 @@ +_base_ = './mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_4.0gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_4.0gf')), + neck=dict( + type='FPN', + in_channels=[80, 240, 560, 1360], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-4GF_fpn_ms-poly-3x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-4GF_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..f160ccf6 --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-4GF_fpn_ms-poly-3x_coco.py @@ -0,0 +1,26 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] + +model = dict( + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_4.0gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_4.0gf')), + neck=dict( + type='FPN', + in_channels=[80, 240, 560, 1360], + out_channels=256, + num_outs=5)) + +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.00005), + clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-6.4GF_fpn_1x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-6.4GF_fpn_1x_coco.py new file mode 100644 index 00000000..e17a3d76 --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-6.4GF_fpn_1x_coco.py @@ -0,0 +1,17 @@ +_base_ = './mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_6.4gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_6.4gf')), + neck=dict( + type='FPN', + in_channels=[168, 392, 784, 1624], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-800MF_fpn_ms-poly-3x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-800MF_fpn_ms-poly-3x_coco.py new file mode 100644 index 00000000..93851fdb --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-800MF_fpn_ms-poly-3x_coco.py @@ -0,0 +1,26 @@ +_base_ = [ + '../common/ms-poly_3x_coco-instance.py', + '../_base_/models/mask-rcnn_r50_fpn.py' +] + +model = dict( + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_800mf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_800mf')), + neck=dict( + type='FPN', + in_channels=[64, 128, 288, 672], + out_channels=256, + num_outs=5)) + +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.00005), + clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/regnet/mask-rcnn_regnetx-8GF_fpn_1x_coco.py b/mmdetection/configs/regnet/mask-rcnn_regnetx-8GF_fpn_1x_coco.py new file mode 100644 index 00000000..62a4c931 --- /dev/null +++ b/mmdetection/configs/regnet/mask-rcnn_regnetx-8GF_fpn_1x_coco.py @@ -0,0 +1,17 @@ +_base_ = './mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_8.0gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_8.0gf')), + neck=dict( + type='FPN', + in_channels=[80, 240, 720, 1920], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/metafile.yml b/mmdetection/configs/regnet/metafile.yml new file mode 100644 index 00000000..19fbba80 --- /dev/null +++ b/mmdetection/configs/regnet/metafile.yml @@ -0,0 +1,797 @@ +Models: + - Name: mask-rcnn_regnetx-3.2GF_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.0 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_1x_coco/mask_rcnn_regnetx-3.2GF_fpn_1x_coco_20200520_163141-2a9d1814.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-4GF_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-4GF_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.5 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-4GF_fpn_1x_coco/mask_rcnn_regnetx-4GF_fpn_1x_coco_20200517_180217-32e9c92d.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-6.4GF_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-6.4GF_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.1 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-6.4GF_fpn_1x_coco/mask_rcnn_regnetx-6.4GF_fpn_1x_coco_20200517_180439-3a7aae83.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-8GF_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-8GF_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.4 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-8GF_fpn_1x_coco/mask_rcnn_regnetx-8GF_fpn_1x_coco_20200517_180515-09daa87e.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-12GF_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-12GF_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.4 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-12GF_fpn_1x_coco/mask_rcnn_regnetx-12GF_fpn_1x_coco_20200517_180552-b538bd8b.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-3.2GF-mdconv-c3-c5_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-3.2GF-mdconv-c3-c5_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.0 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_mdconv_c3-c5_1x_coco/mask_rcnn_regnetx-3.2GF_fpn_mdconv_c3-c5_1x_coco_20200520_172726-75f40794.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: faster-rcnn_regnetx-3.2GF_fpn_1x_coco + In Collection: Faster R-CNN + Config: configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.5 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-3.2GF_fpn_1x_coco/faster_rcnn_regnetx-3.2GF_fpn_1x_coco_20200517_175927-126fd9bf.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: faster-rcnn_regnetx-3.2GF_fpn_2x_coco + In Collection: Faster R-CNN + Config: configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_2x_coco.py + Metadata: + Training Memory (GB): 4.5 + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-3.2GF_fpn_2x_coco/faster_rcnn_regnetx-3.2GF_fpn_2x_coco_20200520_223955-e2081918.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: retinanet_regnetx-800MF_fpn_1x_coco + In Collection: RetinaNet + Config: configs/regnet/retinanet_regnetx-800MF_fpn_1x_coco.py + Metadata: + Training Memory (GB): 2.5 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 35.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/retinanet_regnetx-800MF_fpn_1x_coco/retinanet_regnetx-800MF_fpn_1x_coco_20200517_191403-f6f91d10.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: retinanet_regnetx-1.6GF_fpn_1x_coco + In Collection: RetinaNet + Config: configs/regnet/retinanet_regnetx-1.6GF_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.3 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/retinanet_regnetx-1.6GF_fpn_1x_coco/retinanet_regnetx-1.6GF_fpn_1x_coco_20200517_191403-37009a9d.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: retinanet_regnetx-3.2GF_fpn_1x_coco + In Collection: RetinaNet + Config: configs/regnet/retinanet_regnetx-3.2GF_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.2 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/retinanet_regnetx-3.2GF_fpn_1x_coco/retinanet_regnetx-3.2GF_fpn_1x_coco_20200520_163141-cb1509e8.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: faster-rcnn_regnetx-400MF_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/regnet/faster-rcnn_regnetx-400MF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 2.3 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-400MF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-400MF_fpn_mstrain_3x_coco_20210526_095112-e1967c37.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: faster-rcnn_regnetx-800MF_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/regnet/faster-rcnn_regnetx-800MF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 2.8 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-800MF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-800MF_fpn_mstrain_3x_coco_20210526_095118-a2c70b20.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: faster-rcnn_regnetx-1.6GF_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/regnet/faster-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 3.4 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-1.6GF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-1_20210526_095325-94aa46cc.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/regnet/faster-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 4.4 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-3_20210526_095152-e16a5227.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: faster-rcnn_regnetx-4GF_fpn_ms-3x_coco + In Collection: Faster R-CNN + Config: configs/regnet/faster-rcnn_regnetx-4GF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 4.9 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/faster_rcnn_regnetx-4GF_fpn_mstrain_3x_coco/faster_rcnn_regnetx-4GF_fpn_mstrain_3x_coco_20210526_095201-65eaf841.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 5.0 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco_20200521_202221-99879813.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-400MF_fpn_ms-poly-3x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-400MF_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 2.5 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 34.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-400MF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-400MF_fpn_mstrain-poly_3x_coco_20210601_235443-8aac57a4.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-800MF_fpn_ms-poly-3x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-800MF_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 2.9 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-800MF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-800MF_fpn_mstrain-poly_3x_coco_20210602_210641-715d51f5.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-1.6GF_fpn_ms-poly-3x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-1.6GF_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 3.6 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.9 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-1.6GF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-1_20210602_210641-6764cff5.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 5.0 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco_20200521_202221-99879813.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: mask-rcnn_regnetx-4GF_fpn_ms-poly-3x_coco + In Collection: Mask R-CNN + Config: configs/regnet/mask-rcnn_regnetx-4GF_fpn_ms-poly-3x_coco.py + Metadata: + Training Memory (GB): 5.1 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/mask_rcnn_regnetx-4GF_fpn_mstrain-poly_3x_coco/mask_rcnn_regnetx-4GF_fpn_mstrain-poly_3x_coco_20210602_032621-00f0331c.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: cascade-mask-rcnn_regnetx-400MF_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/regnet/cascade-mask-rcnn_regnetx-400MF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 4.3 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-400MF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-400MF_fpn_mstrain_3x_coco_20210715_211619-5142f449.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: cascade-mask-rcnn_regnetx-800MF_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/regnet/cascade-mask-rcnn_regnetx-800MF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 4.8 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-800MF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-800MF_fpn_mstrain_3x_coco_20210715_211616-dcbd13f4.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: cascade-mask-rcnn_regnetx-1.6GF_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/regnet/cascade-mask-rcnn_regnetx-1.6GF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 5.4 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-1.6GF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-1_20210715_211616-75f29a61.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/regnet/cascade-mask-rcnn_regnetx-3.2GF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 6.4 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-3.2GF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-3_20210715_211616-b9c2c58b.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 + + - Name: cascade-mask-rcnn_regnetx-4GF_fpn_ms-3x_coco + In Collection: Cascade R-CNN + Config: configs/regnet/cascade-mask-rcnn_regnetx-4GF_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 6.9 + Epochs: 36 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - RegNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/regnet/cascade_mask_rcnn_regnetx-4GF_fpn_mstrain_3x_coco/cascade_mask_rcnn_regnetx-4GF_fpn_mstrain_3x_coco_20210715_212034-cbb1be4c.pth + Paper: + URL: https://arxiv.org/abs/2003.13678 + Title: 'Designing Network Design Spaces' + README: configs/regnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/regnet.py#L11 + Version: v2.1.0 diff --git a/mmdetection/configs/regnet/retinanet_regnetx-1.6GF_fpn_1x_coco.py b/mmdetection/configs/regnet/retinanet_regnetx-1.6GF_fpn_1x_coco.py new file mode 100644 index 00000000..7395c1bf --- /dev/null +++ b/mmdetection/configs/regnet/retinanet_regnetx-1.6GF_fpn_1x_coco.py @@ -0,0 +1,17 @@ +_base_ = './retinanet_regnetx-3.2GF_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_1.6gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_1.6gf')), + neck=dict( + type='FPN', + in_channels=[72, 168, 408, 912], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/regnet/retinanet_regnetx-3.2GF_fpn_1x_coco.py b/mmdetection/configs/regnet/retinanet_regnetx-3.2GF_fpn_1x_coco.py new file mode 100644 index 00000000..8b8a32ce --- /dev/null +++ b/mmdetection/configs/regnet/retinanet_regnetx-3.2GF_fpn_1x_coco.py @@ -0,0 +1,31 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + data_preprocessor=dict( + # The mean and std are used in PyCls when training RegNets + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False), + backbone=dict( + _delete_=True, + type='RegNet', + arch='regnetx_3.2gf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_3.2gf')), + neck=dict( + type='FPN', + in_channels=[96, 192, 432, 1008], + out_channels=256, + num_outs=5)) + +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.00005), + clip_grad=dict(max_norm=35, norm_type=2)) diff --git a/mmdetection/configs/regnet/retinanet_regnetx-800MF_fpn_1x_coco.py b/mmdetection/configs/regnet/retinanet_regnetx-800MF_fpn_1x_coco.py new file mode 100644 index 00000000..f6f89893 --- /dev/null +++ b/mmdetection/configs/regnet/retinanet_regnetx-800MF_fpn_1x_coco.py @@ -0,0 +1,17 @@ +_base_ = './retinanet_regnetx-3.2GF_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='RegNet', + arch='regnetx_800mf', + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://regnetx_800mf')), + neck=dict( + type='FPN', + in_channels=[64, 128, 288, 672], + out_channels=256, + num_outs=5)) diff --git a/mmdetection/configs/reid/README.md b/mmdetection/configs/reid/README.md new file mode 100644 index 00000000..a5bfe5ec --- /dev/null +++ b/mmdetection/configs/reid/README.md @@ -0,0 +1,135 @@ +# Training a ReID Model + +You may want to train a ReID model for multiple object tracking or other applications. We support ReID model training in MMDetection, which is built upon [MMPretrain](https://github.com/open-mmlab/mmpretrain). + +### 1. Development Environment Setup + +Tracking Development Environment Setup can refer to this [document](../../docs/en/get_started.md). + +### 2. Dataset Preparation + +This section will show how to train a ReID model on standard datasets i.e. MOT17. + +We need to download datasets following docs. We use [ReIDDataset](mmdet/datasets/reid_dataset.py) to maintain standard datasets. In this case, you need to convert the official dataset to this style. We provide scripts and the usages as follow: + +```python +python tools/dataset_converters/mot2reid.py -i ./data/MOT17/ -o ./data/MOT17/reid --val-split 0.2 --vis-threshold 0.3 +``` + +Arguments: + +- `--val-split`: Proportion of the validation dataset to the whole ReID dataset. +- `--vis-threshold`: Threshold of visibility for each person. + +The directory of the converted datasets is as follows: + +``` +MOT17 +├── train +├── test +├── reid +│ ├── imgs +│ │ ├── MOT17-02-FRCNN_000002 +│ │ │ ├── 000000.jpg +│ │ │ ├── 000001.jpg +│ │ │ ├── ... +│ │ ├── MOT17-02-FRCNN_000003 +│ │ │ ├── 000000.jpg +│ │ │ ├── 000001.jpg +│ │ │ ├── ... +│ ├── meta +│ │ ├── train_80.txt +│ │ ├── val_20.txt +``` + +Note: `80` in `train_80.txt` means the proportion of the training dataset to the whole ReID dataset is eighty percent. While the proportion of the validation dataset is twenty percent. + +For training, we provide a annotation list `train_80.txt`. Each line of the list constraints a filename and its corresponding ground-truth labels. The format is as follows: + +``` +MOT17-05-FRCNN_000110/000018.jpg 0 +MOT17-13-FRCNN_000146/000014.jpg 1 +MOT17-05-FRCNN_000088/000004.jpg 2 +MOT17-02-FRCNN_000009/000081.jpg 3 +``` + +For validation, The annotation list `val_20.txt` remains the same as format above. + +Note: Images in `MOT17/reid/imgs` are cropped from raw images in `MOT17/train` by the corresponding `gt.txt`. The value of ground-truth labels should fall in range `[0, num_classes - 1]`. + +### 3. Training + +#### Training on a single GPU + +```shell +python tools/train.py configs/reid/reid_r50_8xb32-6e_mot17train80_test-mot17val20.py +``` + +#### Training on multiple GPUs + +We provide `tools/dist_train.sh` to launch training on multiple GPUs. +The basic usage is as follows. + +```shell +bash tools/dist_train.sh configs/reid/reid_r50_8xb32-6e_mot17train80_test-mot17val20.py 8 +``` + +### 4. Customize Dataset + +This section will show how to train a ReID model on customize datasets. + +### 4.1 Dataset Preparation + +You need to convert your customize datasets to existing dataset format. + +#### An example of customized dataset + +Assume we are going to implement a `Filelist` dataset, which takes filelists for both training and testing. The directory of the dataset is as follows: + +``` +Filelist +├── imgs +│ ├── person1 +│ │ ├── 000000.jpg +│ │ ├── 000001.jpg +│ │ ├── ... +│ ├── person2 +│ │ ├── 000000.jpg +│ │ ├── 000001.jpg +│ │ ├── ... +├── meta +│ ├── train.txt +│ ├── val.txt +``` + +The format of annotation list is as follows: + +``` +person1/000000.jpg 0 +person1/000001.jpg 0 +person2/000000.jpg 1 +person2/000001.jpg 1 +``` + +You can directly use [ReIDDataset](mmdet/datasets/reid_dataset.py). In this case, you only need to modify the config as follows: + +```python +# modify the path of annotation files and the image path prefix +data = dict( + train=dict( + data_prefix='data/Filelist/imgs', + ann_file='data/Filelist/meta/train.txt'), + val=dict( + data_prefix='data/Filelist/imgs', + ann_file='data/Filelist/meta/val.txt'), + test=dict( + data_prefix='data/Filelist/imgs', + ann_file='data/Filelist/meta/val.txt'), +) +# modify the number of classes, assume your training set has 100 classes +model = dict(reid=dict(head=dict(num_classes=100))) +``` + +### 4.2 Training + +The training stage is the same as `Standard Dataset`. diff --git a/mmdetection/configs/reid/reid_r50_8xb32-6e_mot15train80_test-mot15val20.py b/mmdetection/configs/reid/reid_r50_8xb32-6e_mot15train80_test-mot15val20.py new file mode 100644 index 00000000..4e30b229 --- /dev/null +++ b/mmdetection/configs/reid/reid_r50_8xb32-6e_mot15train80_test-mot15val20.py @@ -0,0 +1,7 @@ +_base_ = ['./reid_r50_8xb32-6e_mot17train80_test-mot17val20.py'] +model = dict(head=dict(num_classes=368)) +# data +data_root = 'data/MOT15/' +train_dataloader = dict(dataset=dict(data_root=data_root)) +val_dataloader = dict(dataset=dict(data_root=data_root)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/reid/reid_r50_8xb32-6e_mot16train80_test-mot16val20.py b/mmdetection/configs/reid/reid_r50_8xb32-6e_mot16train80_test-mot16val20.py new file mode 100644 index 00000000..468b9bfb --- /dev/null +++ b/mmdetection/configs/reid/reid_r50_8xb32-6e_mot16train80_test-mot16val20.py @@ -0,0 +1,7 @@ +_base_ = ['./reid_r50_8xb32-6e_mot17train80_test-mot17val20.py'] +model = dict(head=dict(num_classes=371)) +# data +data_root = 'data/MOT16/' +train_dataloader = dict(dataset=dict(data_root=data_root)) +val_dataloader = dict(dataset=dict(data_root=data_root)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/reid/reid_r50_8xb32-6e_mot17train80_test-mot17val20.py b/mmdetection/configs/reid/reid_r50_8xb32-6e_mot17train80_test-mot17val20.py new file mode 100644 index 00000000..83669de7 --- /dev/null +++ b/mmdetection/configs/reid/reid_r50_8xb32-6e_mot17train80_test-mot17val20.py @@ -0,0 +1,61 @@ +_base_ = [ + '../_base_/datasets/mot_challenge_reid.py', '../_base_/default_runtime.py' +] +model = dict( + type='BaseReID', + data_preprocessor=dict( + type='ReIDDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + to_rgb=True), + backbone=dict( + type='mmpretrain.ResNet', + depth=50, + num_stages=4, + out_indices=(3, ), + style='pytorch'), + neck=dict(type='GlobalAveragePooling', kernel_size=(8, 4), stride=1), + head=dict( + type='LinearReIDHead', + num_fcs=1, + in_channels=2048, + fc_channels=1024, + out_channels=128, + num_classes=380, + loss_cls=dict(type='mmpretrain.CrossEntropyLoss', loss_weight=1.0), + loss_triplet=dict(type='TripletLoss', margin=0.3, loss_weight=1.0), + norm_cfg=dict(type='BN1d'), + act_cfg=dict(type='ReLU')), + init_cfg=dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_batch256_imagenet_20200708-cfb998bf.pth' # noqa: E501 + )) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + clip_grad=None, + optimizer=dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.0001)) + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 1000, + by_epoch=False, + begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=6, + by_epoch=True, + milestones=[5], + gamma=0.1) +] + +# train, val, test setting +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=6, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') diff --git a/mmdetection/configs/reid/reid_r50_8xb32-6e_mot20train80_test-mot20val20.py b/mmdetection/configs/reid/reid_r50_8xb32-6e_mot20train80_test-mot20val20.py new file mode 100644 index 00000000..8a807996 --- /dev/null +++ b/mmdetection/configs/reid/reid_r50_8xb32-6e_mot20train80_test-mot20val20.py @@ -0,0 +1,10 @@ +_base_ = ['./reid_r50_8xb32-6e_mot17train80_test-mot17val20.py'] +model = dict(head=dict(num_classes=1701)) +# data +data_root = 'data/MOT20/' +train_dataloader = dict(dataset=dict(data_root=data_root)) +val_dataloader = dict(dataset=dict(data_root=data_root)) +test_dataloader = val_dataloader + +# train, val, test setting +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=6, val_interval=7) diff --git a/mmdetection/configs/reppoints/README.md b/mmdetection/configs/reppoints/README.md new file mode 100644 index 00000000..03cb86be --- /dev/null +++ b/mmdetection/configs/reppoints/README.md @@ -0,0 +1,59 @@ +# RepPoints + +> [RepPoints: Point Set Representation for Object Detection](https://arxiv.org/abs/1904.11490) + + + +## Abstract + +Modern object detectors rely heavily on rectangular bounding boxes, such as anchors, proposals and the final predictions, to represent objects at various recognition stages. The bounding box is convenient to use but provides only a coarse localization of objects and leads to a correspondingly coarse extraction of object features. In this paper, we present RepPoints(representative points), a new finer representation of objects as a set of sample points useful for both localization and recognition. Given ground truth localization and recognition targets for training, RepPoints learn to automatically arrange themselves in a manner that bounds the spatial extent of an object and indicates semantically significant local areas. They furthermore do not require the use of anchors to sample a space of bounding boxes. We show that an anchor-free object detector based on RepPoints can be as effective as the state-of-the-art anchor-based detection methods, with 46.5 AP and 67.4 AP50 on the COCO test-dev detection benchmark, using ResNet-101 model. + +
    + +
    + +## Introdution + +By [Ze Yang](https://yangze.tech/), [Shaohui Liu](http://b1ueber2y.me/), and [Han Hu](https://ancientmooner.github.io/). + +We provide code support and configuration files to reproduce the results in the paper for +["RepPoints: Point Set Representation for Object Detection"](https://arxiv.org/abs/1904.11490) on COCO object detection. + +**RepPoints**, initially described in [arXiv](https://arxiv.org/abs/1904.11490), is a new representation method for visual objects, on which visual understanding tasks are typically centered. Visual object representation, aiming at both geometric description and appearance feature extraction, is conventionally achieved by `bounding box + RoIPool (RoIAlign)`. The bounding box representation is convenient to use; however, it provides only a rectangular localization of objects that lacks geometric precision and may consequently degrade feature quality. Our new representation, RepPoints, models objects by a `point set` instead of a `bounding box`, which learns to adaptively position themselves over an object in a manner that circumscribes the object’s `spatial extent` and enables `semantically aligned feature extraction`. This richer and more flexible representation maintains the convenience of bounding boxes while facilitating various visual understanding applications. This repo demonstrated the effectiveness of RepPoints for COCO object detection. + +Another feature of this repo is the demonstration of an `anchor-free detector`, which can be as effective as state-of-the-art anchor-based detection methods. The anchor-free detector can utilize either `bounding box` or `RepPoints` as the basic object representation. + +## Results and Models + +The results on COCO 2017val are shown in the table below. + +| Method | Backbone | GN | Anchor | convert func | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------: | :-----------: | :-: | :----: | :----------: | :-----: | :------: | :------------: | :----: | :---------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| BBox | R-50-FPN | Y | single | - | 1x | 3.9 | 15.9 | 36.4 | [config](./reppoints-bbox_r50_fpn-gn_head-gn-grid_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/reppoints/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco_20200329_145916-0eedf8d1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/reppoints/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco_20200329_145916.log.json) | +| BBox | R-50-FPN | Y | none | - | 1x | 3.9 | 15.4 | 37.4 | [config](./reppoints-bbox_r50-center_fpn-gn_head-gn-grid_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/reppoints/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco_20200329_145916-0eedf8d1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/reppoints/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco_20200329_145916.log.json) | +| RepPoints | R-50-FPN | N | none | moment | 1x | 3.3 | 18.5 | 37.0 | [config](./reppoints-moment_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_1x_coco/reppoints_moment_r50_fpn_1x_coco_20200330-b73db8d1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_1x_coco/reppoints_moment_r50_fpn_1x_coco_20200330_233609.log.json) | +| RepPoints | R-50-FPN | Y | none | moment | 1x | 3.9 | 17.5 | 38.1 | [config](./reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_gn-neck%2Bhead_1x_coco/reppoints_moment_r50_fpn_gn-neck%2Bhead_1x_coco_20200329_145952-3e51b550.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_gn-neck%2Bhead_1x_coco/reppoints_moment_r50_fpn_gn-neck%2Bhead_1x_coco_20200329_145952.log.json) | +| RepPoints | R-50-FPN | Y | none | moment | 2x | 3.9 | - | 38.6 | [config](./reppoints-moment_r50_fpn-gn_head-gn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_gn-neck%2Bhead_2x_coco/reppoints_moment_r50_fpn_gn-neck%2Bhead_2x_coco_20200329-91babaa2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_gn-neck%2Bhead_2x_coco/reppoints_moment_r50_fpn_gn-neck%2Bhead_2x_coco_20200329_150020.log.json) | +| RepPoints | R-101-FPN | Y | none | moment | 2x | 5.8 | 13.7 | 40.5 | [config](./reppoints-moment_r101_fpn-gn_head-gn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r101_fpn_gn-neck%2Bhead_2x_coco/reppoints_moment_r101_fpn_gn-neck%2Bhead_2x_coco_20200329-4fbc7310.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r101_fpn_gn-neck%2Bhead_2x_coco/reppoints_moment_r101_fpn_gn-neck%2Bhead_2x_coco_20200329_132205.log.json) | +| RepPoints | R-101-FPN-DCN | Y | none | moment | 2x | 5.9 | 12.1 | 42.9 | [config](./reppoints-moment_r101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco/reppoints_moment_r101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco_20200329-3309fbf2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco/reppoints_moment_r101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco_20200329_132134.log.json) | +| RepPoints | X-101-FPN-DCN | Y | none | moment | 2x | 7.1 | 9.3 | 44.2 | [config](./reppoints-moment_x101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_x101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco/reppoints_moment_x101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco_20200329-f87da1ea.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_x101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco/reppoints_moment_x101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco_20200329_132201.log.json) | + +**Notes:** + +- `R-xx`, `X-xx` denote the ResNet and ResNeXt architectures, respectively. +- `DCN` denotes replacing 3x3 conv with the 3x3 deformable convolution in `c3-c5` stages of backbone. +- `none` in the `anchor` column means 2-d `center point` (x,y) is used to represent the initial object hypothesis. `single` denotes one 4-d anchor box (x,y,w,h) with IoU based label assign criterion is adopted. +- `moment`, `partial MinMax`, `MinMax` in the `convert func` column are three functions to convert a point set to a pseudo box. +- Note the results here are slightly different from those reported in the paper, due to framework change. While the original paper uses an [MXNet](https://mxnet.apache.org/) implementation, we re-implement the method in [PyTorch](https://pytorch.org/) based on mmdetection. + +## Citation + +```latex +@inproceedings{yang2019reppoints, + title={RepPoints: Point Set Representation for Object Detection}, + author={Yang, Ze and Liu, Shaohui and Hu, Han and Wang, Liwei and Lin, Stephen}, + booktitle={The IEEE International Conference on Computer Vision (ICCV)}, + month={Oct}, + year={2019} +} +``` diff --git a/mmdetection/configs/reppoints/metafile.yml b/mmdetection/configs/reppoints/metafile.yml new file mode 100644 index 00000000..732d541f --- /dev/null +++ b/mmdetection/configs/reppoints/metafile.yml @@ -0,0 +1,181 @@ +Collections: + - Name: RepPoints + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Group Normalization + - FPN + - RepPoints + - ResNet + Paper: + URL: https://arxiv.org/abs/1904.11490 + Title: 'RepPoints: Point Set Representation for Object Detection' + README: configs/reppoints/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/reppoints_detector.py#L9 + Version: v2.0.0 + +Models: + - Name: reppoints-bbox_r50_fpn-gn_head-gn-grid_1x_coco + In Collection: RepPoints + Config: configs/reppoints/reppoints-bbox_r50_fpn-gn_head-gn-grid_1x_coco.py + Metadata: + Training Memory (GB): 3.9 + inference time (ms/im): + - value: 62.89 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/reppoints/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco_20200329_145916-0eedf8d1.pth + + - Name: reppoints-bbox_r50-center_fpn-gn_head-gn-grid_1x_coco + In Collection: RepPoints + Config: configs/reppoints/reppoints-bbox_r50-center_fpn-gn_head-gn-grid_1x_coco.py + Metadata: + Training Memory (GB): 3.9 + inference time (ms/im): + - value: 64.94 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/reppoints/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco/bbox_r50_grid_fpn_gn-neck%2Bhead_1x_coco_20200329_145916-0eedf8d1.pth + + - Name: reppoints-moment_r50_fpn_1x_coco + In Collection: RepPoints + Config: configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.3 + inference time (ms/im): + - value: 54.05 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_1x_coco/reppoints_moment_r50_fpn_1x_coco_20200330-b73db8d1.pth + + - Name: reppoints-moment_r50_fpn-gn_head-gn_1x_coco + In Collection: RepPoints + Config: configs/reppoints/reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py + Metadata: + Training Memory (GB): 3.9 + inference time (ms/im): + - value: 57.14 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_gn-neck%2Bhead_1x_coco/reppoints_moment_r50_fpn_gn-neck%2Bhead_1x_coco_20200329_145952-3e51b550.pth + + - Name: reppoints-moment_r50_fpn-gn_head-gn_2x_coco + In Collection: RepPoints + Config: configs/reppoints/reppoints-moment_r50_fpn-gn_head-gn_2x_coco.py + Metadata: + Training Memory (GB): 3.9 + inference time (ms/im): + - value: 57.14 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r50_fpn_gn-neck%2Bhead_2x_coco/reppoints_moment_r50_fpn_gn-neck%2Bhead_2x_coco_20200329-91babaa2.pth + + - Name: reppoints-moment_r101_fpn-gn_head-gn_2x_coco + In Collection: RepPoints + Config: configs/reppoints/reppoints-moment_r101_fpn-gn_head-gn_2x_coco.py + Metadata: + Training Memory (GB): 5.8 + inference time (ms/im): + - value: 72.99 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r101_fpn_gn-neck%2Bhead_2x_coco/reppoints_moment_r101_fpn_gn-neck%2Bhead_2x_coco_20200329-4fbc7310.pth + + - Name: reppoints-moment_r101-dconv-c3-c5_fpn-gn_head-gn_2x_coco + In Collection: RepPoints + Config: configs/reppoints/reppoints-moment_r101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py + Metadata: + Training Memory (GB): 5.9 + inference time (ms/im): + - value: 82.64 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_r101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco/reppoints_moment_r101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco_20200329-3309fbf2.pth + + - Name: reppoints-moment_x101-dconv-c3-c5_fpn-gn_head-gn_2x_coco + In Collection: RepPoints + Config: configs/reppoints/reppoints-moment_x101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py + Metadata: + Training Memory (GB): 7.1 + inference time (ms/im): + - value: 107.53 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/reppoints/reppoints_moment_x101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco/reppoints_moment_x101_fpn_dconv_c3-c5_gn-neck%2Bhead_2x_coco_20200329-f87da1ea.pth diff --git a/mmdetection/configs/reppoints/reppoints-bbox_r50-center_fpn-gn_head-gn-grid_1x_coco.py b/mmdetection/configs/reppoints/reppoints-bbox_r50-center_fpn-gn_head-gn-grid_1x_coco.py new file mode 100644 index 00000000..f116e53f --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-bbox_r50-center_fpn-gn_head-gn-grid_1x_coco.py @@ -0,0 +1,2 @@ +_base_ = './reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py' +model = dict(bbox_head=dict(transform_method='minmax', use_grid_points=True)) diff --git a/mmdetection/configs/reppoints/reppoints-bbox_r50_fpn-gn_head-gn-grid_1x_coco.py b/mmdetection/configs/reppoints/reppoints-bbox_r50_fpn-gn_head-gn-grid_1x_coco.py new file mode 100644 index 00000000..76be39b8 --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-bbox_r50_fpn-gn_head-gn-grid_1x_coco.py @@ -0,0 +1,13 @@ +_base_ = './reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py' +model = dict( + bbox_head=dict(transform_method='minmax', use_grid_points=True), + # training and testing settings + train_cfg=dict( + init=dict( + assigner=dict( + _delete_=True, + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0, + ignore_iof_thr=-1)))) diff --git a/mmdetection/configs/reppoints/reppoints-minmax_r50_fpn-gn_head-gn_1x_coco.py b/mmdetection/configs/reppoints/reppoints-minmax_r50_fpn-gn_head-gn_1x_coco.py new file mode 100644 index 00000000..0e7dffe7 --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-minmax_r50_fpn-gn_head-gn_1x_coco.py @@ -0,0 +1,2 @@ +_base_ = './reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py' +model = dict(bbox_head=dict(transform_method='minmax')) diff --git a/mmdetection/configs/reppoints/reppoints-moment_r101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py b/mmdetection/configs/reppoints/reppoints-moment_r101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py new file mode 100644 index 00000000..5c2bfab4 --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-moment_r101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py @@ -0,0 +1,8 @@ +_base_ = './reppoints-moment_r50_fpn-gn_head-gn_2x_coco.py' +model = dict( + backbone=dict( + depth=101, + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/reppoints/reppoints-moment_r101_fpn-gn_head-gn_2x_coco.py b/mmdetection/configs/reppoints/reppoints-moment_r101_fpn-gn_head-gn_2x_coco.py new file mode 100644 index 00000000..02c447ad --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-moment_r101_fpn-gn_head-gn_2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './reppoints-moment_r50_fpn-gn_head-gn_2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/reppoints/reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py b/mmdetection/configs/reppoints/reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py new file mode 100644 index 00000000..cedf2226 --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py @@ -0,0 +1,3 @@ +_base_ = './reppoints-moment_r50_fpn_1x_coco.py' +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict(neck=dict(norm_cfg=norm_cfg), bbox_head=dict(norm_cfg=norm_cfg)) diff --git a/mmdetection/configs/reppoints/reppoints-moment_r50_fpn-gn_head-gn_2x_coco.py b/mmdetection/configs/reppoints/reppoints-moment_r50_fpn-gn_head-gn_2x_coco.py new file mode 100644 index 00000000..4490d449 --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-moment_r50_fpn-gn_head-gn_2x_coco.py @@ -0,0 +1,17 @@ +_base_ = './reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py' + +max_epochs = 24 + +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py b/mmdetection/configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py new file mode 100644 index 00000000..df7e72a8 --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-moment_r50_fpn_1x_coco.py @@ -0,0 +1,74 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + type='RepPointsDetector', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_input', + num_outs=5), + bbox_head=dict( + type='RepPointsHead', + num_classes=80, + in_channels=256, + feat_channels=256, + point_feat_channels=256, + stacked_convs=3, + num_points=9, + gradient_mul=0.1, + point_strides=[8, 16, 32, 64, 128], + point_base_scale=4, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox_init=dict(type='SmoothL1Loss', beta=0.11, loss_weight=0.5), + loss_bbox_refine=dict(type='SmoothL1Loss', beta=0.11, loss_weight=1.0), + transform_method='moment'), + # training and testing settings + train_cfg=dict( + init=dict( + assigner=dict(type='PointAssigner', scale=4, pos_num=1), + allowed_border=-1, + pos_weight=-1, + debug=False), + refine=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False)), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100)) + +optim_wrapper = dict(optimizer=dict(lr=0.01)) diff --git a/mmdetection/configs/reppoints/reppoints-moment_x101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py b/mmdetection/configs/reppoints/reppoints-moment_x101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py new file mode 100644 index 00000000..a9909efe --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-moment_x101-dconv-c3-c5_fpn-gn_head-gn_2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './reppoints-moment_r50_fpn-gn_head-gn_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + dcn=dict(type='DCN', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/reppoints/reppoints-partial-minmax_r50_fpn-gn_head-gn_1x_coco.py b/mmdetection/configs/reppoints/reppoints-partial-minmax_r50_fpn-gn_head-gn_1x_coco.py new file mode 100644 index 00000000..30f7844b --- /dev/null +++ b/mmdetection/configs/reppoints/reppoints-partial-minmax_r50_fpn-gn_head-gn_1x_coco.py @@ -0,0 +1,2 @@ +_base_ = './reppoints-moment_r50_fpn-gn_head-gn_1x_coco.py' +model = dict(bbox_head=dict(transform_method='partial_minmax')) diff --git a/mmdetection/configs/reppoints/reppoints.png b/mmdetection/configs/reppoints/reppoints.png new file mode 100644 index 00000000..a9306d9b Binary files /dev/null and b/mmdetection/configs/reppoints/reppoints.png differ diff --git a/mmdetection/configs/res2net/README.md b/mmdetection/configs/res2net/README.md new file mode 100644 index 00000000..cd6732b6 --- /dev/null +++ b/mmdetection/configs/res2net/README.md @@ -0,0 +1,77 @@ +# Res2Net + +> [Res2Net: A New Multi-scale Backbone Architecture](https://arxiv.org/abs/1904.01169) + + + +## Abstract + +Representing features at multiple scales is of great importance for numerous vision tasks. Recent advances in backbone convolutional neural networks (CNNs) continually demonstrate stronger multi-scale representation ability, leading to consistent performance gains on a wide range of applications. However, most existing methods represent the multi-scale features in a layer-wise manner. In this paper, we propose a novel building block for CNNs, namely Res2Net, by constructing hierarchical residual-like connections within one single residual block. The Res2Net represents multi-scale features at a granular level and increases the range of receptive fields for each network layer. The proposed Res2Net block can be plugged into the state-of-the-art backbone CNN models, e.g., ResNet, ResNeXt, and DLA. We evaluate the Res2Net block on all these models and demonstrate consistent performance gains over baseline models on widely-used datasets, e.g., CIFAR-100 and ImageNet. Further ablation studies and experimental results on representative computer vision tasks, i.e., object detection, class activation mapping, and salient object detection, further verify the superiority of the Res2Net over the state-of-the-art baseline methods. + +
    + +
    + +## Introduction + +We propose a novel building block for CNNs, namely Res2Net, by constructing hierarchical residual-like connections within one single residual block. The Res2Net represents multi-scale features at a granular level and increases the range of receptive fields for each network layer. + +| Backbone | Params. | GFLOPs | top-1 err. | top-5 err. | +| :---------------: | :-----: | :----: | :--------: | :--------: | +| ResNet-101 | 44.6 M | 7.8 | 22.63 | 6.44 | +| ResNeXt-101-64x4d | 83.5M | 15.5 | 20.40 | - | +| HRNetV2p-W48 | 77.5M | 16.1 | 20.70 | 5.50 | +| Res2Net-101 | 45.2M | 8.3 | 18.77 | 4.64 | + +Compared with other backbone networks, Res2Net requires fewer parameters and FLOPs. + +**Note:** + +- GFLOPs for classification are calculated with image size (224x224). + +## Results and Models + +### Faster R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :--------: | :-----: | :-----: | :------: | :------------: | :----: | :------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R2-101-FPN | pytorch | 2x | 7.4 | - | 43.0 | [config](./faster-rcnn_res2net-101_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/res2net/faster_rcnn_r2_101_fpn_2x_coco/faster_rcnn_r2_101_fpn_2x_coco-175f1da6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/res2net/faster_rcnn_r2_101_fpn_2x_coco/faster_rcnn_r2_101_fpn_2x_coco_20200514_231734.log.json) | + +### Mask R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :--------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :----------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R2-101-FPN | pytorch | 2x | 7.9 | - | 43.6 | 38.7 | [config](./mask-rcnn_res2net-101_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/res2net/mask_rcnn_r2_101_fpn_2x_coco/mask_rcnn_r2_101_fpn_2x_coco-17f061e8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/res2net/mask_rcnn_r2_101_fpn_2x_coco/mask_rcnn_r2_101_fpn_2x_coco_20200515_002413.log.json) | + +### Cascade R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :--------: | :-----: | :-----: | :------: | :------------: | :----: | :--------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R2-101-FPN | pytorch | 20e | 7.8 | - | 45.7 | [config](./cascade-rcnn_res2net-101_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/res2net/cascade_rcnn_r2_101_fpn_20e_coco/cascade_rcnn_r2_101_fpn_20e_coco-f4b7b7db.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/res2net/cascade_rcnn_r2_101_fpn_20e_coco/cascade_rcnn_r2_101_fpn_20e_coco_20200515_091644.log.json) | + +### Cascade Mask R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :--------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :-------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R2-101-FPN | pytorch | 20e | 9.5 | - | 46.4 | 40.0 | [config](./cascade-mask-rcnn_res2net-101_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/res2net/cascade_mask_rcnn_r2_101_fpn_20e_coco/cascade_mask_rcnn_r2_101_fpn_20e_coco-8a7b41e1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/res2net/cascade_mask_rcnn_r2_101_fpn_20e_coco/cascade_mask_rcnn_r2_101_fpn_20e_coco_20200515_091645.log.json) | + +### Hybrid Task Cascade (HTC) + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :--------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :-----------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R2-101-FPN | pytorch | 20e | - | - | 47.5 | 41.6 | [config](./htc_res2net-101_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/res2net/htc_r2_101_fpn_20e_coco/htc_r2_101_fpn_20e_coco-3a8d2112.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/res2net/htc_r2_101_fpn_20e_coco/htc_r2_101_fpn_20e_coco_20200515_150029.log.json) | + +- Res2Net ImageNet pretrained models are in [Res2Net-PretrainedModels](https://github.com/Res2Net/Res2Net-PretrainedModels). +- More applications of Res2Net are in [Res2Net-Github](https://github.com/Res2Net/). + +## Citation + +```latex +@article{gao2019res2net, + title={Res2Net: A New Multi-scale Backbone Architecture}, + author={Gao, Shang-Hua and Cheng, Ming-Ming and Zhao, Kai and Zhang, Xin-Yu and Yang, Ming-Hsuan and Torr, Philip}, + journal={IEEE TPAMI}, + year={2020}, + doi={10.1109/TPAMI.2019.2938758}, +} +``` diff --git a/mmdetection/configs/res2net/cascade-mask-rcnn_res2net-101_fpn_20e_coco.py b/mmdetection/configs/res2net/cascade-mask-rcnn_res2net-101_fpn_20e_coco.py new file mode 100644 index 00000000..21b6d2ea --- /dev/null +++ b/mmdetection/configs/res2net/cascade-mask-rcnn_res2net-101_fpn_20e_coco.py @@ -0,0 +1,10 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + type='Res2Net', + depth=101, + scales=4, + base_width=26, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://res2net101_v1d_26w_4s'))) diff --git a/mmdetection/configs/res2net/cascade-rcnn_res2net-101_fpn_20e_coco.py b/mmdetection/configs/res2net/cascade-rcnn_res2net-101_fpn_20e_coco.py new file mode 100644 index 00000000..670a7745 --- /dev/null +++ b/mmdetection/configs/res2net/cascade-rcnn_res2net-101_fpn_20e_coco.py @@ -0,0 +1,10 @@ +_base_ = '../cascade_rcnn/cascade-rcnn_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + type='Res2Net', + depth=101, + scales=4, + base_width=26, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://res2net101_v1d_26w_4s'))) diff --git a/mmdetection/configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py b/mmdetection/configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py new file mode 100644 index 00000000..033cf574 --- /dev/null +++ b/mmdetection/configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py @@ -0,0 +1,10 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='Res2Net', + depth=101, + scales=4, + base_width=26, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://res2net101_v1d_26w_4s'))) diff --git a/mmdetection/configs/res2net/htc_res2net-101_fpn_20e_coco.py b/mmdetection/configs/res2net/htc_res2net-101_fpn_20e_coco.py new file mode 100644 index 00000000..d5542fda --- /dev/null +++ b/mmdetection/configs/res2net/htc_res2net-101_fpn_20e_coco.py @@ -0,0 +1,10 @@ +_base_ = '../htc/htc_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + type='Res2Net', + depth=101, + scales=4, + base_width=26, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://res2net101_v1d_26w_4s'))) diff --git a/mmdetection/configs/res2net/mask-rcnn_res2net-101_fpn_2x_coco.py b/mmdetection/configs/res2net/mask-rcnn_res2net-101_fpn_2x_coco.py new file mode 100644 index 00000000..3a2d5730 --- /dev/null +++ b/mmdetection/configs/res2net/mask-rcnn_res2net-101_fpn_2x_coco.py @@ -0,0 +1,10 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='Res2Net', + depth=101, + scales=4, + base_width=26, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://res2net101_v1d_26w_4s'))) diff --git a/mmdetection/configs/res2net/metafile.yml b/mmdetection/configs/res2net/metafile.yml new file mode 100644 index 00000000..1d9f9ea0 --- /dev/null +++ b/mmdetection/configs/res2net/metafile.yml @@ -0,0 +1,146 @@ +Models: + - Name: faster-rcnn_res2net-101_fpn_2x_coco + In Collection: Faster R-CNN + Config: configs/res2net/faster-rcnn_res2net-101_fpn_2x_coco.py + Metadata: + Training Memory (GB): 7.4 + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Res2Net + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/res2net/faster_rcnn_r2_101_fpn_2x_coco/faster_rcnn_r2_101_fpn_2x_coco-175f1da6.pth + Paper: + URL: https://arxiv.org/abs/1904.01169 + Title: 'Res2Net for object detection and instance segmentation' + README: configs/res2net/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/res2net.py#L239 + Version: v2.1.0 + + - Name: mask-rcnn_res2net-101_fpn_2x_coco + In Collection: Mask R-CNN + Config: configs/res2net/mask-rcnn_res2net-101_fpn_2x_coco.py + Metadata: + Training Memory (GB): 7.9 + Epochs: 24 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Res2Net + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/res2net/mask_rcnn_r2_101_fpn_2x_coco/mask_rcnn_r2_101_fpn_2x_coco-17f061e8.pth + Paper: + URL: https://arxiv.org/abs/1904.01169 + Title: 'Res2Net for object detection and instance segmentation' + README: configs/res2net/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/res2net.py#L239 + Version: v2.1.0 + + - Name: cascade-rcnn_res2net-101_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/res2net/cascade-rcnn_res2net-101_fpn_20e_coco.py + Metadata: + Training Memory (GB): 7.8 + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Res2Net + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/res2net/cascade_rcnn_r2_101_fpn_20e_coco/cascade_rcnn_r2_101_fpn_20e_coco-f4b7b7db.pth + Paper: + URL: https://arxiv.org/abs/1904.01169 + Title: 'Res2Net for object detection and instance segmentation' + README: configs/res2net/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/res2net.py#L239 + Version: v2.1.0 + + - Name: cascade-mask-rcnn_res2net-101_fpn_20e_coco + In Collection: Cascade R-CNN + Config: configs/res2net/cascade-mask-rcnn_res2net-101_fpn_20e_coco.py + Metadata: + Training Memory (GB): 9.5 + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Res2Net + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/res2net/cascade_mask_rcnn_r2_101_fpn_20e_coco/cascade_mask_rcnn_r2_101_fpn_20e_coco-8a7b41e1.pth + Paper: + URL: https://arxiv.org/abs/1904.01169 + Title: 'Res2Net for object detection and instance segmentation' + README: configs/res2net/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/res2net.py#L239 + Version: v2.1.0 + + - Name: htc_res2net-101_fpn_20e_coco + In Collection: HTC + Config: configs/res2net/htc_res2net-101_fpn_20e_coco.py + Metadata: + Epochs: 20 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Res2Net + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/res2net/htc_r2_101_fpn_20e_coco/htc_r2_101_fpn_20e_coco-3a8d2112.pth + Paper: + URL: https://arxiv.org/abs/1904.01169 + Title: 'Res2Net for object detection and instance segmentation' + README: configs/res2net/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.1.0/mmdet/models/backbones/res2net.py#L239 + Version: v2.1.0 diff --git a/mmdetection/configs/resnest/README.md b/mmdetection/configs/resnest/README.md new file mode 100644 index 00000000..a72f8423 --- /dev/null +++ b/mmdetection/configs/resnest/README.md @@ -0,0 +1,54 @@ +# ResNeSt + +> [ResNeSt: Split-Attention Networks](https://arxiv.org/abs/2004.08955) + + + +## Abstract + +It is well known that featuremap attention and multi-path representation are important for visual recognition. In this paper, we present a modularized architecture, which applies the channel-wise attention on different network branches to leverage their success in capturing cross-feature interactions and learning diverse representations. Our design results in a simple and unified computation block, which can be parameterized using only a few variables. Our model, named ResNeSt, outperforms EfficientNet in accuracy and latency trade-off on image classification. In addition, ResNeSt has achieved superior transfer learning results on several public benchmarks serving as the backbone, and has been adopted by the winning entries of COCO-LVIS challenge. + +
    + +
    + +## Results and Models + +### Faster R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------: | :-----: | :-----: | :------: | :------------: | :----: | :-----------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| S-50-FPN | pytorch | 1x | 4.8 | - | 42.0 | [config](./faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnest/faster_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/faster_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco_20200926_125502-20289c16.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnest/faster_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/faster_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco-20200926_125502.log.json) | +| S-101-FPN | pytorch | 1x | 7.1 | - | 44.5 | [config](./faster-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnest/faster_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/faster_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco_20201006_021058-421517f1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnest/faster_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/faster_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco-20201006_021058.log.json) | + +### Mask R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :---------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| S-50-FPN | pytorch | 1x | 5.5 | - | 42.6 | 38.1 | [config](./mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnest/mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco_20200926_125503-8a2c3d47.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnest/mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco-20200926_125503.log.json) | +| S-101-FPN | pytorch | 1x | 7.8 | - | 45.2 | 40.2 | [config](./mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnest/mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco_20201005_215831-af60cdf9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnest/mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco-20201005_215831.log.json) | + +### Cascade R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------: | :-----: | :-----: | :------: | :------------: | :----: | :------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| S-50-FPN | pytorch | 1x | - | - | 44.5 | [config](./cascade-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/cascade_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco_20201122_213640-763cc7b5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/cascade_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco-20201005_113242.log.json) | +| S-101-FPN | pytorch | 1x | 8.4 | - | 46.8 | [config](./cascade-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/cascade_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco_20201005_113242-b9459f8f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/cascade_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco-20201122_213640.log.json) | + +### Cascade Mask R-CNN + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :-----------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| S-50-FPN | pytorch | 1x | - | - | 45.4 | 39.5 | [config](./cascade-mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/cascade_mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco_20201122_104428-99eca4c7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/cascade_mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco-20201122_104428.log.json) | +| S-101-FPN | pytorch | 1x | 10.5 | - | 47.7 | 41.4 | [config](./cascade-mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/cascade_mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco_20201005_113243-42607475.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/cascade_mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco-20201005_113243.log.json) | + +## Citation + +```latex +@article{zhang2020resnest, +title={ResNeSt: Split-Attention Networks}, +author={Zhang, Hang and Wu, Chongruo and Zhang, Zhongyue and Zhu, Yi and Zhang, Zhi and Lin, Haibin and Sun, Yue and He, Tong and Muller, Jonas and Manmatha, R. and Li, Mu and Smola, Alexander}, +journal={arXiv preprint arXiv:2004.08955}, +year={2020} +} +``` diff --git a/mmdetection/configs/resnest/cascade-mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py b/mmdetection/configs/resnest/cascade-mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py new file mode 100644 index 00000000..f4f19925 --- /dev/null +++ b/mmdetection/configs/resnest/cascade-mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './cascade-mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py' +model = dict( + backbone=dict( + stem_channels=128, + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='open-mmlab://resnest101'))) diff --git a/mmdetection/configs/resnest/cascade-mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py b/mmdetection/configs/resnest/cascade-mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py new file mode 100644 index 00000000..c6ef41c0 --- /dev/null +++ b/mmdetection/configs/resnest/cascade-mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py @@ -0,0 +1,101 @@ +_base_ = '../cascade_rcnn/cascade-mask-rcnn_r50_fpn_1x_coco.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) + +model = dict( + # use ResNeSt img_norm + data_preprocessor=dict( + mean=[123.68, 116.779, 103.939], + std=[58.393, 57.12, 57.375], + bgr_to_rgb=True), + backbone=dict( + type='ResNeSt', + stem_channels=64, + depth=50, + radix=2, + reduction_factor=4, + avg_down_stride=True, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://resnest50')), + roi_head=dict( + bbox_head=[ + dict( + type='Shared4Conv1FCBBoxHead', + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + norm_cfg=norm_cfg, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared4Conv1FCBBoxHead', + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + norm_cfg=norm_cfg, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared4Conv1FCBBoxHead', + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + norm_cfg=norm_cfg, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], + mask_head=dict(norm_cfg=norm_cfg))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/resnest/cascade-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py b/mmdetection/configs/resnest/cascade-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py new file mode 100644 index 00000000..9dbf3fae --- /dev/null +++ b/mmdetection/configs/resnest/cascade-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './cascade-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py' +model = dict( + backbone=dict( + stem_channels=128, + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='open-mmlab://resnest101'))) diff --git a/mmdetection/configs/resnest/cascade-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py b/mmdetection/configs/resnest/cascade-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py new file mode 100644 index 00000000..7ce7b563 --- /dev/null +++ b/mmdetection/configs/resnest/cascade-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py @@ -0,0 +1,93 @@ +_base_ = '../cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + # use ResNeSt img_norm + data_preprocessor=dict( + mean=[123.68, 116.779, 103.939], + std=[58.393, 57.12, 57.375], + bgr_to_rgb=True), + backbone=dict( + type='ResNeSt', + stem_channels=64, + depth=50, + radix=2, + reduction_factor=4, + avg_down_stride=True, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://resnest50')), + roi_head=dict( + bbox_head=[ + dict( + type='Shared4Conv1FCBBoxHead', + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + norm_cfg=norm_cfg, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared4Conv1FCBBoxHead', + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + norm_cfg=norm_cfg, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared4Conv1FCBBoxHead', + in_channels=256, + conv_out_channels=256, + fc_out_channels=1024, + norm_cfg=norm_cfg, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], )) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/resnest/faster-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py b/mmdetection/configs/resnest/faster-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py new file mode 100644 index 00000000..f1e16321 --- /dev/null +++ b/mmdetection/configs/resnest/faster-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py' +model = dict( + backbone=dict( + stem_channels=128, + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='open-mmlab://resnest101'))) diff --git a/mmdetection/configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py b/mmdetection/configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py new file mode 100644 index 00000000..8f0ec6e0 --- /dev/null +++ b/mmdetection/configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py @@ -0,0 +1,39 @@ +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + # use ResNeSt img_norm + data_preprocessor=dict( + mean=[123.68, 116.779, 103.939], + std=[58.393, 57.12, 57.375], + bgr_to_rgb=True), + backbone=dict( + type='ResNeSt', + stem_channels=64, + depth=50, + radix=2, + reduction_factor=4, + avg_down_stride=True, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://resnest50')), + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=norm_cfg))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/resnest/mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py b/mmdetection/configs/resnest/mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py new file mode 100644 index 00000000..3edf49f0 --- /dev/null +++ b/mmdetection/configs/resnest/mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py' +model = dict( + backbone=dict( + stem_channels=128, + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='open-mmlab://resnest101'))) diff --git a/mmdetection/configs/resnest/mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py b/mmdetection/configs/resnest/mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py new file mode 100644 index 00000000..c6f27000 --- /dev/null +++ b/mmdetection/configs/resnest/mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py @@ -0,0 +1,46 @@ +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + # use ResNeSt img_norm + data_preprocessor=dict( + mean=[123.68, 116.779, 103.939], + std=[58.393, 57.12, 57.375], + bgr_to_rgb=True), + backbone=dict( + type='ResNeSt', + stem_channels=64, + depth=50, + radix=2, + reduction_factor=4, + avg_down_stride=True, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://resnest50')), + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=norm_cfg), + mask_head=dict(norm_cfg=norm_cfg))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/resnest/metafile.yml b/mmdetection/configs/resnest/metafile.yml new file mode 100644 index 00000000..265c9409 --- /dev/null +++ b/mmdetection/configs/resnest/metafile.yml @@ -0,0 +1,230 @@ +Models: + - Name: faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco + In Collection: Faster R-CNN + Config: configs/resnest/faster-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py + Metadata: + Training Memory (GB): 4.8 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNeSt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnest/faster_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/faster_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco_20200926_125502-20289c16.pth + Paper: + URL: https://arxiv.org/abs/2004.08955 + Title: 'ResNeSt: Split-Attention Networks' + README: configs/resnest/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.7.0/mmdet/models/backbones/resnest.py#L273 + Version: v2.7.0 + + - Name: faster-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco + In Collection: Faster R-CNN + Config: configs/resnest/faster-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py + Metadata: + Training Memory (GB): 7.1 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNeSt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnest/faster_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/faster_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco_20201006_021058-421517f1.pth + Paper: + URL: https://arxiv.org/abs/2004.08955 + Title: 'ResNeSt: Split-Attention Networks' + README: configs/resnest/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.7.0/mmdet/models/backbones/resnest.py#L273 + Version: v2.7.0 + + - Name: mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco + In Collection: Mask R-CNN + Config: configs/resnest/mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py + Metadata: + Training Memory (GB): 5.5 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNeSt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.6 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnest/mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco_20200926_125503-8a2c3d47.pth + Paper: + URL: https://arxiv.org/abs/2004.08955 + Title: 'ResNeSt: Split-Attention Networks' + README: configs/resnest/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.7.0/mmdet/models/backbones/resnest.py#L273 + Version: v2.7.0 + + - Name: mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco + In Collection: Mask R-CNN + Config: configs/resnest/mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py + Metadata: + Training Memory (GB): 7.8 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNeSt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnest/mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco_20201005_215831-af60cdf9.pth + Paper: + URL: https://arxiv.org/abs/2004.08955 + Title: 'ResNeSt: Split-Attention Networks' + README: configs/resnest/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.7.0/mmdet/models/backbones/resnest.py#L273 + Version: v2.7.0 + + - Name: cascade-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco + In Collection: Cascade R-CNN + Config: configs/resnest/cascade-rcnn_s50_fpn_syncbn-backbone+head_ms-range-1x_coco.py + Metadata: + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNeSt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/cascade_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco_20201122_213640-763cc7b5.pth + Paper: + URL: https://arxiv.org/abs/2004.08955 + Title: 'ResNeSt: Split-Attention Networks' + README: configs/resnest/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.7.0/mmdet/models/backbones/resnest.py#L273 + Version: v2.7.0 + + - Name: cascade-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco + In Collection: Cascade R-CNN + Config: configs/resnest/cascade-rcnn_s101_fpn_syncbn-backbone+head_ms-range-1x_coco.py + Metadata: + Training Memory (GB): 8.4 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNeSt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco/cascade_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain-range_1x_coco_20201005_113242-b9459f8f.pth + Paper: + URL: https://arxiv.org/abs/2004.08955 + Title: 'ResNeSt: Split-Attention Networks' + README: configs/resnest/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.7.0/mmdet/models/backbones/resnest.py#L273 + Version: v2.7.0 + + - Name: cascade-mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco + In Collection: Cascade R-CNN + Config: configs/resnest/cascade-mask-rcnn_s50_fpn_syncbn-backbone+head_ms-1x_coco.py + Metadata: + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNeSt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/cascade_mask_rcnn_s50_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco_20201122_104428-99eca4c7.pth + Paper: + URL: https://arxiv.org/abs/2004.08955 + Title: 'ResNeSt: Split-Attention Networks' + README: configs/resnest/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.7.0/mmdet/models/backbones/resnest.py#L273 + Version: v2.7.0 + + - Name: cascade-mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco + In Collection: Cascade R-CNN + Config: configs/resnest/cascade-mask-rcnn_s101_fpn_syncbn-backbone+head_ms-1x_coco.py + Metadata: + Training Memory (GB): 10.5 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNeSt + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnest/cascade_mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco/cascade_mask_rcnn_s101_fpn_syncbn-backbone%2Bhead_mstrain_1x_coco_20201005_113243-42607475.pth + Paper: + URL: https://arxiv.org/abs/2004.08955 + Title: 'ResNeSt: Split-Attention Networks' + README: configs/resnest/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.7.0/mmdet/models/backbones/resnest.py#L273 + Version: v2.7.0 diff --git a/mmdetection/configs/resnet_strikes_back/README.md b/mmdetection/configs/resnet_strikes_back/README.md new file mode 100644 index 00000000..f015729a --- /dev/null +++ b/mmdetection/configs/resnet_strikes_back/README.md @@ -0,0 +1,40 @@ +# ResNet strikes back + +> [ResNet strikes back: An improved training procedure in timm](https://arxiv.org/abs/2110.00476) + + + +## Abstract + +The influential Residual Networks designed by He et al. remain the gold-standard architecture in numerous scientific publications. They typically serve as the default architecture in studies, or as baselines when new architectures are proposed. Yet there has been significant progress on best practices for training neural networks since the inception of the ResNet architecture in 2015. Novel optimization & dataaugmentation have increased the effectiveness of the training recipes. + +In this paper, we re-evaluate the performance of the vanilla ResNet-50 when trained with a procedure that integrates such advances. We share competitive training settings and pre-trained models in the timm open-source library, with the hope that they will serve as better baselines for future work. For instance, with our more demanding training setting, a vanilla ResNet-50 reaches 80.4% top-1 accuracy at resolution 224×224 on ImageNet-val without extra data or distillation. We also report the performance achieved with popular models with our training procedure. + +
    + +
    + +## Results and Models + +| Method | Backbone | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :----------------: | :------: | :-----: | :------: | :------------: | :---------: | :---------: | :------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster R-CNN | R-50 rsb | 1x | 3.9 | - | 40.8 (+3.4) | - | [Config](./faster-rcnn_r50-rsb-pre_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/faster_rcnn_r50_fpn_rsb-pretrain_1x_coco/faster_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_162229-32ae82a9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/faster_rcnn_r50_fpn_rsb-pretrain_1x_coco/faster_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_162229.log.json) | +| Mask R-CNN | R-50 rsb | 1x | 4.5 | - | 41.2 (+3.0) | 38.2 (+3.0) | [Config](./mask-rcnn_r50-rsb-pre_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/mask_rcnn_r50_fpn_rsb-pretrain_1x_coco/mask_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_174054-06ce8ba0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/mask_rcnn_r50_fpn_rsb-pretrain_1x_coco/mask_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_174054.log.json) | +| Cascade Mask R-CNN | R-50 rsb | 1x | 6.2 | - | 44.8 (+3.6) | 39.9 (+3.6) | [Config](./cascade-mask-rcnn_r50-rsb-pre_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/cascade_mask_rcnn_r50_fpn_rsb-pretrain_1x_coco/cascade_mask_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_193636-8b9ad50f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/cascade_mask_rcnn_r50_fpn_rsb-pretrain_1x_coco/cascade_mask_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_193636.log.json) | +| RetinaNet | R-50 rsb | 1x | 3.8 | - | 39.0 (+2.5) | - | [Config](./retinanet_r50-rsb-pre_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/retinanet_r50_fpn_rsb-pretrain_1x_coco/retinanet_r50_fpn_rsb-pretrain_1x_coco_20220113_175432-bd24aae9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/retinanet_r50_fpn_rsb-pretrain_1x_coco/retinanet_r50_fpn_rsb-pretrain_1x_coco_20220113_175432.log.json) | + +**Notes:** + +- 'rsb' is short for 'resnet strikes back' +- We have done some grid searches on learning rate and weight decay and get these optimal hyper-parameters. + +## Citation + +```latex +@article{wightman2021resnet, +title={Resnet strikes back: An improved training procedure in timm}, +author={Ross Wightman, Hugo Touvron, Hervé Jégou}, +journal={arXiv preprint arXiv:2110.00476}, +year={2021} +} +``` diff --git a/mmdetection/configs/resnet_strikes_back/cascade-mask-rcnn_r50-rsb-pre_fpn_1x_coco.py b/mmdetection/configs/resnet_strikes_back/cascade-mask-rcnn_r50-rsb-pre_fpn_1x_coco.py new file mode 100644 index 00000000..de7b95b0 --- /dev/null +++ b/mmdetection/configs/resnet_strikes_back/cascade-mask-rcnn_r50-rsb-pre_fpn_1x_coco.py @@ -0,0 +1,15 @@ +_base_ = [ + '../_base_/models/cascade-mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb256-rsb-a1-600e_in1k_20211228-20e21305.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint))) + +optim_wrapper = dict( + optimizer=dict(_delete_=True, type='AdamW', lr=0.0002, weight_decay=0.05), + paramwise_cfg=dict(norm_decay_mult=0., bypass_duplicate=True)) diff --git a/mmdetection/configs/resnet_strikes_back/faster-rcnn_r50-rsb-pre_fpn_1x_coco.py b/mmdetection/configs/resnet_strikes_back/faster-rcnn_r50-rsb-pre_fpn_1x_coco.py new file mode 100644 index 00000000..8c60f66a --- /dev/null +++ b/mmdetection/configs/resnet_strikes_back/faster-rcnn_r50-rsb-pre_fpn_1x_coco.py @@ -0,0 +1,15 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb256-rsb-a1-600e_in1k_20211228-20e21305.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint))) + +optim_wrapper = dict( + optimizer=dict(_delete_=True, type='AdamW', lr=0.0002, weight_decay=0.05), + paramwise_cfg=dict(norm_decay_mult=0., bypass_duplicate=True)) diff --git a/mmdetection/configs/resnet_strikes_back/mask-rcnn_r50-rsb-pre_fpn_1x_coco.py b/mmdetection/configs/resnet_strikes_back/mask-rcnn_r50-rsb-pre_fpn_1x_coco.py new file mode 100644 index 00000000..85e25d39 --- /dev/null +++ b/mmdetection/configs/resnet_strikes_back/mask-rcnn_r50-rsb-pre_fpn_1x_coco.py @@ -0,0 +1,15 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb256-rsb-a1-600e_in1k_20211228-20e21305.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint))) + +optim_wrapper = dict( + optimizer=dict(_delete_=True, type='AdamW', lr=0.0002, weight_decay=0.05), + paramwise_cfg=dict(norm_decay_mult=0., bypass_duplicate=True)) diff --git a/mmdetection/configs/resnet_strikes_back/metafile.yml b/mmdetection/configs/resnet_strikes_back/metafile.yml new file mode 100644 index 00000000..74b15210 --- /dev/null +++ b/mmdetection/configs/resnet_strikes_back/metafile.yml @@ -0,0 +1,116 @@ +Models: + - Name: faster-rcnn_r50_fpn_rsb-pretrain_1x_coco + In Collection: Faster R-CNN + Config: configs/resnet_strikes_back/faster-rcnn_r50-rsb-pre_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.9 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/faster_rcnn_r50_fpn_rsb-pretrain_1x_coco/faster_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_162229-32ae82a9.pth + Paper: + URL: https://arxiv.org/abs/2110.00476 + Title: 'ResNet strikes back: An improved training procedure in timm' + README: configs/resnet_strikes_back/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.22.0/configs/resnet_strikes_back/README.md + Version: v2.22.0 + + - Name: cascade-mask-rcnn_r50_fpn_rsb-pretrain_1x_coco + In Collection: Cascade R-CNN + Config: configs/resnet_strikes_back/cascade-mask-rcnn_r50-rsb-pre_fpn_1x_coco.py + Metadata: + Training Memory (GB): 6.2 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/cascade_mask_rcnn_r50_fpn_rsb-pretrain_1x_coco/cascade_mask_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_193636-8b9ad50f.pth + Paper: + URL: https://arxiv.org/abs/2110.00476 + Title: 'ResNet strikes back: An improved training procedure in timm' + README: configs/resnet_strikes_back/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.22.0/configs/resnet_strikes_back/README.md + Version: v2.22.0 + + - Name: retinanet_r50-rsb-pre_fpn_1x_coco + In Collection: RetinaNet + Config: configs/resnet_strikes_back/retinanet_r50-rsb-pre_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.8 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/retinanet_r50_fpn_rsb-pretrain_1x_coco/retinanet_r50_fpn_rsb-pretrain_1x_coco_20220113_175432-bd24aae9.pth + Paper: + URL: https://arxiv.org/abs/2110.00476 + Title: 'ResNet strikes back: An improved training procedure in timm' + README: configs/resnet_strikes_back/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.22.0/configs/resnet_strikes_back/README.md + Version: v2.22.0 + + - Name: mask-rcnn_r50_fpn_rsb-pretrain_1x_coco + In Collection: Mask R-CNN + Config: configs/resnet_strikes_back/mask-rcnn_r50-rsb-pre_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.5 + Epochs: 12 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/resnet_strikes_back/mask_rcnn_r50_fpn_rsb-pretrain_1x_coco/mask_rcnn_r50_fpn_rsb-pretrain_1x_coco_20220113_174054-06ce8ba0.pth + Paper: + URL: https://arxiv.org/abs/2110.00476 + Title: 'ResNet strikes back: An improved training procedure in timm' + README: configs/resnet_strikes_back/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.22.0/configs/resnet_strikes_back/README.md + Version: v2.22.0 diff --git a/mmdetection/configs/resnet_strikes_back/retinanet_r50-rsb-pre_fpn_1x_coco.py b/mmdetection/configs/resnet_strikes_back/retinanet_r50-rsb-pre_fpn_1x_coco.py new file mode 100644 index 00000000..7ce7bfd8 --- /dev/null +++ b/mmdetection/configs/resnet_strikes_back/retinanet_r50-rsb-pre_fpn_1x_coco.py @@ -0,0 +1,15 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb256-rsb-a1-600e_in1k_20211228-20e21305.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint))) + +optim_wrapper = dict( + optimizer=dict(_delete_=True, type='AdamW', lr=0.0001, weight_decay=0.05), + paramwise_cfg=dict(norm_decay_mult=0., bypass_duplicate=True)) diff --git a/mmdetection/configs/retinanet/README.md b/mmdetection/configs/retinanet/README.md new file mode 100644 index 00000000..b38335a3 --- /dev/null +++ b/mmdetection/configs/retinanet/README.md @@ -0,0 +1,53 @@ +# RetinaNet + +> [Focal Loss for Dense Object Detection](https://arxiv.org/abs/1708.02002) + + + +## Abstract + +The highest accuracy object detectors to date are based on a two-stage approach popularized by R-CNN, where a classifier is applied to a sparse set of candidate object locations. In contrast, one-stage detectors that are applied over a regular, dense sampling of possible object locations have the potential to be faster and simpler, but have trailed the accuracy of two-stage detectors thus far. In this paper, we investigate why this is the case. We discover that the extreme foreground-background class imbalance encountered during training of dense detectors is the central cause. We propose to address this class imbalance by reshaping the standard cross entropy loss such that it down-weights the loss assigned to well-classified examples. Our novel Focal Loss focuses training on a sparse set of hard examples and prevents the vast number of easy negatives from overwhelming the detector during training. To evaluate the effectiveness of our loss, we design and train a simple dense detector we call RetinaNet. Our results show that when trained with the focal loss, RetinaNet is able to match the speed of previous one-stage detectors while surpassing the accuracy of all existing state-of-the-art two-stage detectors. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------------: | :-----: | :----------: | :------: | :------------: | :----: | :---------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-18-FPN | pytorch | 1x | 1.7 | | 31.7 | [config](./retinanet_r18_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r18_fpn_1x_coco/retinanet_r18_fpn_1x_coco_20220407_171055-614fd399.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r18_fpn_1x_coco/retinanet_r18_fpn_1x_coco_20220407_171055.log.json) | +| R-18-FPN | pytorch | 1x(1 x 8 BS) | 5.0 | | 31.7 | [config](./retinanet_r18_fpn_1xb8-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r18_fpn_1x8_1x_coco/retinanet_r18_fpn_1x8_1x_coco_20220407_171255-4ea310d7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r18_fpn_1x8_1x_coco/retinanet_r18_fpn_1x8_1x_coco_20220407_171255.log.json) | +| R-50-FPN | caffe | 1x | 3.5 | 18.6 | 36.3 | [config](./retinanet_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_caffe_fpn_1x_coco/retinanet_r50_caffe_fpn_1x_coco_20200531-f11027c5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_caffe_fpn_1x_coco/retinanet_r50_caffe_fpn_1x_coco_20200531_012518.log.json) | +| R-50-FPN | pytorch | 1x | 3.8 | 19.0 | 36.5 | [config](./retinanet_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_1x_coco/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_1x_coco/retinanet_r50_fpn_1x_coco_20200130_002941.log.json) | +| R-50-FPN (FP16) | pytorch | 1x | 2.8 | 31.6 | 36.4 | [config](./retinanet_r50_fpn_amp-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/fp16/retinanet_r50_fpn_fp16_1x_coco/retinanet_r50_fpn_fp16_1x_coco_20200702-0dbfb212.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/fp16/retinanet_r50_fpn_fp16_1x_coco/retinanet_r50_fpn_fp16_1x_coco_20200702_020127.log.json) | +| R-50-FPN | pytorch | 2x | - | - | 37.4 | [config](./retinanet_r50_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_2x_coco/retinanet_r50_fpn_2x_coco_20200131-fdb43119.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_2x_coco/retinanet_r50_fpn_2x_coco_20200131_114738.log.json) | +| R-101-FPN | caffe | 1x | 5.5 | 14.7 | 38.5 | [config](./retinanet_r101-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_caffe_fpn_1x_coco/retinanet_r101_caffe_fpn_1x_coco_20200531-b428fa0f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_caffe_fpn_1x_coco/retinanet_r101_caffe_fpn_1x_coco_20200531_012536.log.json) | +| R-101-FPN | pytorch | 1x | 5.7 | 15.0 | 38.5 | [config](./retinanet_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_fpn_1x_coco/retinanet_r101_fpn_1x_coco_20200130-7a93545f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_fpn_1x_coco/retinanet_r101_fpn_1x_coco_20200130_003055.log.json) | +| R-101-FPN | pytorch | 2x | - | - | 38.9 | [config](./retinanet_r101_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_fpn_2x_coco/retinanet_r101_fpn_2x_coco_20200131-5560aee8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_fpn_2x_coco/retinanet_r101_fpn_2x_coco_20200131_114859.log.json) | +| X-101-32x4d-FPN | pytorch | 1x | 7.0 | 12.1 | 39.9 | [config](./retinanet_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_32x4d_fpn_1x_coco/retinanet_x101_32x4d_fpn_1x_coco_20200130-5c8b7ec4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_32x4d_fpn_1x_coco/retinanet_x101_32x4d_fpn_1x_coco_20200130_003004.log.json) | +| X-101-32x4d-FPN | pytorch | 2x | - | - | 40.1 | [config](./retinanet_x101-32x4d_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_32x4d_fpn_2x_coco/retinanet_x101_32x4d_fpn_2x_coco_20200131-237fc5e1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_32x4d_fpn_2x_coco/retinanet_x101_32x4d_fpn_2x_coco_20200131_114812.log.json) | +| X-101-64x4d-FPN | pytorch | 1x | 10.0 | 8.7 | 41.0 | [config](./retinanet_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_64x4d_fpn_1x_coco/retinanet_x101_64x4d_fpn_1x_coco_20200130-366f5af1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_64x4d_fpn_1x_coco/retinanet_x101_64x4d_fpn_1x_coco_20200130_003008.log.json) | +| X-101-64x4d-FPN | pytorch | 2x | - | - | 40.8 | [config](./retinanet_x101-64x4d_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_64x4d_fpn_2x_coco/retinanet_x101_64x4d_fpn_2x_coco_20200131-bca068ab.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_64x4d_fpn_2x_coco/retinanet_x101_64x4d_fpn_2x_coco_20200131_114833.log.json) | + +## Pre-trained Models + +We also train some models with longer schedules and multi-scale training. The users could finetune them for downstream tasks. + +| Backbone | Style | Lr schd | Mem (GB) | box AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :----: | :--------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 3x | 3.5 | 39.5 | [config](./retinanet_r50_fpn_ms-640-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_mstrain_3x_coco/retinanet_r50_fpn_mstrain_3x_coco_20210718_220633-88476508.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_mstrain_3x_coco/retinanet_r50_fpn_mstrain_3x_coco_20210718_220633-88476508.log.json) | +| R-101-FPN | caffe | 3x | 5.4 | 40.7 | [config](./retinanet_r101-caffe_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_caffe_fpn_mstrain_3x_coco/retinanet_r101_caffe_fpn_mstrain_3x_coco_20210721_063439-88a8a944.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_caffe_fpn_mstrain_3x_coco/retinanet_r101_caffe_fpn_mstrain_3x_coco_20210721_063439-88a8a944.log.json) | +| R-101-FPN | pytorch | 3x | 5.4 | 41 | [config](./retinanet_r101_fpn_ms-640-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_fpn_mstrain_3x_coco/retinanet_r101_fpn_mstrain_3x_coco_20210720_214650-7ee888e0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_fpn_mstrain_3x_coco/retinanet_r101_fpn_mstrain_3x_coco_20210720_214650-7ee888e0.log.json) | +| X-101-64x4d-FPN | pytorch | 3x | 9.8 | 41.6 | [config](./retinanet_x101-64x4d_fpn_ms-640-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_64x4d_fpn_mstrain_3x_coco/retinanet_x101_64x4d_fpn_mstrain_3x_coco_20210719_051838-022c2187.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_64x4d_fpn_mstrain_3x_coco/retinanet_x101_64x4d_fpn_mstrain_3x_coco_20210719_051838-022c2187.log.json) | + +## Citation + +```latex +@inproceedings{lin2017focal, + title={Focal loss for dense object detection}, + author={Lin, Tsung-Yi and Goyal, Priya and Girshick, Ross and He, Kaiming and Doll{\'a}r, Piotr}, + booktitle={Proceedings of the IEEE international conference on computer vision}, + year={2017} +} +``` diff --git a/mmdetection/configs/retinanet/metafile.yml b/mmdetection/configs/retinanet/metafile.yml new file mode 100644 index 00000000..0551541c --- /dev/null +++ b/mmdetection/configs/retinanet/metafile.yml @@ -0,0 +1,312 @@ +Collections: + - Name: RetinaNet + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Focal Loss + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1708.02002 + Title: "Focal Loss for Dense Object Detection" + README: configs/retinanet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/retinanet.py#L6 + Version: v2.0.0 + +Models: + - Name: retinanet_r18_fpn_1x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r18_fpn_1x_coco.py + Metadata: + Training Memory (GB): 1.7 + Training Resources: 8x V100 GPUs + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 31.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r18_fpn_1x_coco/retinanet_r18_fpn_1x_coco_20220407_171055-614fd399.pth + + - Name: retinanet_r18_fpn_1xb8-1x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r18_fpn_1xb8-1x_coco.py + Metadata: + Training Memory (GB): 5.0 + Training Resources: 1x V100 GPUs + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 31.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r18_fpn_1x8_1x_coco/retinanet_r18_fpn_1x8_1x_coco_20220407_171255-4ea310d7.pth + + - Name: retinanet_r50-caffe_fpn_1x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.5 + inference time (ms/im): + - value: 53.76 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_caffe_fpn_1x_coco/retinanet_r50_caffe_fpn_1x_coco_20200531-f11027c5.pth + + - Name: retinanet_r50_fpn_1x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.8 + inference time (ms/im): + - value: 52.63 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_1x_coco/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth + + - Name: retinanet_r50_fpn_amp-1x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r50_fpn_amp-1x_coco.py + Metadata: + Training Memory (GB): 2.8 + Training Techniques: + - SGD with Momentum + - Weight Decay + - Mixed Precision Training + inference time (ms/im): + - value: 31.65 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP16 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 36.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/fp16/retinanet_r50_fpn_fp16_1x_coco/retinanet_r50_fpn_fp16_1x_coco_20200702-0dbfb212.pth + + - Name: retinanet_r50_fpn_2x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r50_fpn_2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_2x_coco/retinanet_r50_fpn_2x_coco_20200131-fdb43119.pth + + - Name: retinanet_r50_fpn_ms-640-800-3x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r50_fpn_ms-640-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_mstrain_3x_coco/retinanet_r50_fpn_mstrain_3x_coco_20210718_220633-88476508.pth + + - Name: retinanet_r101-caffe_fpn_1x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r101-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.5 + inference time (ms/im): + - value: 68.03 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_caffe_fpn_1x_coco/retinanet_r101_caffe_fpn_1x_coco_20200531-b428fa0f.pth + + - Name: retinanet_r101-caffe_fpn_ms-3x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r101-caffe_fpn_ms-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_caffe_fpn_mstrain_3x_coco/retinanet_r101_caffe_fpn_mstrain_3x_coco_20210721_063439-88a8a944.pth + + - Name: retinanet_r101_fpn_1x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r101_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.7 + inference time (ms/im): + - value: 66.67 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_fpn_1x_coco/retinanet_r101_fpn_1x_coco_20200130-7a93545f.pth + + - Name: retinanet_r101_fpn_2x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r101_fpn_2x_coco.py + Metadata: + Training Memory (GB): 5.7 + inference time (ms/im): + - value: 66.67 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_fpn_2x_coco/retinanet_r101_fpn_2x_coco_20200131-5560aee8.pth + + - Name: retinanet_r101_fpn_ms-640-800-3x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_r101_fpn_ms-640-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r101_fpn_mstrain_3x_coco/retinanet_r101_fpn_mstrain_3x_coco_20210720_214650-7ee888e0.pth + + - Name: retinanet_x101-32x4d_fpn_1x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.0 + inference time (ms/im): + - value: 82.64 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_32x4d_fpn_1x_coco/retinanet_x101_32x4d_fpn_1x_coco_20200130-5c8b7ec4.pth + + - Name: retinanet_x101-32x4d_fpn_2x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_x101-32x4d_fpn_2x_coco.py + Metadata: + Training Memory (GB): 7.0 + inference time (ms/im): + - value: 82.64 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_32x4d_fpn_2x_coco/retinanet_x101_32x4d_fpn_2x_coco_20200131-237fc5e1.pth + + - Name: retinanet_x101-64x4d_fpn_1x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 10.0 + inference time (ms/im): + - value: 114.94 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_64x4d_fpn_1x_coco/retinanet_x101_64x4d_fpn_1x_coco_20200130-366f5af1.pth + + - Name: retinanet_x101-64x4d_fpn_2x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_x101-64x4d_fpn_2x_coco.py + Metadata: + Training Memory (GB): 10.0 + inference time (ms/im): + - value: 114.94 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_64x4d_fpn_2x_coco/retinanet_x101_64x4d_fpn_2x_coco_20200131-bca068ab.pth + + - Name: retinanet_x101-64x4d_fpn_ms-640-800-3x_coco + In Collection: RetinaNet + Config: configs/retinanet/retinanet_x101-64x4d_fpn_ms-640-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_x101_64x4d_fpn_mstrain_3x_coco/retinanet_x101_64x4d_fpn_mstrain_3x_coco_20210719_051838-022c2187.pth diff --git a/mmdetection/configs/retinanet/retinanet_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/retinanet/retinanet_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..1f3a4487 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './retinanet_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/retinanet/retinanet_r101-caffe_fpn_ms-3x_coco.py b/mmdetection/configs/retinanet/retinanet_r101-caffe_fpn_ms-3x_coco.py new file mode 100644 index 00000000..cfe77345 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r101-caffe_fpn_ms-3x_coco.py @@ -0,0 +1,8 @@ +_base_ = './retinanet_r50-caffe_fpn_ms-3x_coco.py' +# learning policy +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/retinanet/retinanet_r101_fpn_1x_coco.py b/mmdetection/configs/retinanet/retinanet_r101_fpn_1x_coco.py new file mode 100644 index 00000000..a7f06002 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './retinanet_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/retinanet/retinanet_r101_fpn_2x_coco.py b/mmdetection/configs/retinanet/retinanet_r101_fpn_2x_coco.py new file mode 100644 index 00000000..721112a2 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r101_fpn_2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './retinanet_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/retinanet/retinanet_r101_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/retinanet/retinanet_r101_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..be018eaa --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r101_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './retinanet_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/retinanet/retinanet_r101_fpn_ms-640-800-3x_coco.py b/mmdetection/configs/retinanet/retinanet_r101_fpn_ms-640-800-3x_coco.py new file mode 100644 index 00000000..56639722 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r101_fpn_ms-640-800-3x_coco.py @@ -0,0 +1,9 @@ +_base_ = ['../_base_/models/retinanet_r50_fpn.py', '../common/ms_3x_coco.py'] +# optimizer +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/retinanet/retinanet_r18_fpn_1x_coco.py b/mmdetection/configs/retinanet/retinanet_r18_fpn_1x_coco.py new file mode 100644 index 00000000..96021180 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r18_fpn_1x_coco.py @@ -0,0 +1,20 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# model +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +# TODO: support auto scaling lr +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +# auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/configs/retinanet/retinanet_r18_fpn_1xb8-1x_coco.py b/mmdetection/configs/retinanet/retinanet_r18_fpn_1xb8-1x_coco.py new file mode 100644 index 00000000..d2e88d68 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r18_fpn_1xb8-1x_coco.py @@ -0,0 +1,24 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# data +train_dataloader = dict(batch_size=8) + +# model +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) + +# Note: If the learning rate is set to 0.0025, the mAP will be 32.4. +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.005, momentum=0.9, weight_decay=0.0001)) +# TODO: support auto scaling lr +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (1 GPUs) x (8 samples per GPU) +# auto_scale_lr = dict(base_batch_size=8) diff --git a/mmdetection/configs/retinanet/retinanet_r18_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/retinanet/retinanet_r18_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..d6833f3f --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r18_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './retinanet_r50_fpn_8xb8-amp-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..6ba1cddd --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = './retinanet_r50_fpn_1x_coco.py' +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + # use caffe img_norm + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_ms-1x_coco.py b/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_ms-1x_coco.py new file mode 100644 index 00000000..93687d8c --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_ms-1x_coco.py @@ -0,0 +1,15 @@ +_base_ = './retinanet_r50-caffe_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_ms-2x_coco.py b/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_ms-2x_coco.py new file mode 100644 index 00000000..6d1604fb --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_ms-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './retinanet_r50-caffe_fpn_ms-1x_coco.py' +# training schedule for 2x +train_cfg = dict(max_epochs=24) + +# learning rate policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=24, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_ms-3x_coco.py b/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_ms-3x_coco.py new file mode 100644 index 00000000..5a6d42a1 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50-caffe_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = './retinanet_r50-caffe_fpn_ms-1x_coco.py' + +# training schedule for 2x +train_cfg = dict(max_epochs=36) + +# learning rate policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=36, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] diff --git a/mmdetection/configs/retinanet/retinanet_r50_fpn_1x_coco.py b/mmdetection/configs/retinanet/retinanet_r50_fpn_1x_coco.py new file mode 100644 index 00000000..00d2567b --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50_fpn_1x_coco.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py', + './retinanet_tta.py' +] + +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/retinanet/retinanet_r50_fpn_2x_coco.py b/mmdetection/configs/retinanet/retinanet_r50_fpn_2x_coco.py new file mode 100644 index 00000000..47511b78 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50_fpn_2x_coco.py @@ -0,0 +1,25 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# training schedule for 2x +train_cfg = dict(max_epochs=24) + +# learning rate policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=24, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/retinanet/retinanet_r50_fpn_8xb8-amp-lsj-200e_coco.py b/mmdetection/configs/retinanet/retinanet_r50_fpn_8xb8-amp-lsj-200e_coco.py new file mode 100644 index 00000000..2f10db2f --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50_fpn_8xb8-amp-lsj-200e_coco.py @@ -0,0 +1,21 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../common/lsj-200e_coco-detection.py' +] + +image_size = (1024, 1024) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +model = dict(data_preprocessor=dict(batch_augments=batch_augments)) + +train_dataloader = dict(batch_size=8, num_workers=4) +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + optimizer=dict( + type='SGD', lr=0.01 * 4, momentum=0.9, weight_decay=0.00004)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/retinanet/retinanet_r50_fpn_90k_coco.py b/mmdetection/configs/retinanet/retinanet_r50_fpn_90k_coco.py new file mode 100644 index 00000000..1e1b2fd9 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50_fpn_90k_coco.py @@ -0,0 +1,24 @@ +_base_ = 'retinanet_r50_fpn_1x_coco.py' + +# training schedule for 90k +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=90000, + val_interval=10000) +# learning rate policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=90000, + by_epoch=False, + milestones=[60000, 80000], + gamma=0.1) +] +train_dataloader = dict(sampler=dict(type='InfiniteSampler')) +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=10000)) + +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/retinanet/retinanet_r50_fpn_amp-1x_coco.py b/mmdetection/configs/retinanet/retinanet_r50_fpn_amp-1x_coco.py new file mode 100644 index 00000000..acf52663 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50_fpn_amp-1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './retinanet_r50_fpn_1x_coco.py' + +# MMEngine support the following two ways, users can choose +# according to convenience +# optim_wrapper = dict(type='AmpOptimWrapper') +_base_.optim_wrapper.type = 'AmpOptimWrapper' diff --git a/mmdetection/configs/retinanet/retinanet_r50_fpn_ms-640-800-3x_coco.py b/mmdetection/configs/retinanet/retinanet_r50_fpn_ms-640-800-3x_coco.py new file mode 100644 index 00000000..d91cf8ce --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_r50_fpn_ms-640-800-3x_coco.py @@ -0,0 +1,4 @@ +_base_ = ['../_base_/models/retinanet_r50_fpn.py', '../common/ms_3x_coco.py'] +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/retinanet/retinanet_tta.py b/mmdetection/configs/retinanet/retinanet_tta.py new file mode 100644 index 00000000..d0f37e0a --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_tta.py @@ -0,0 +1,23 @@ +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict(type='nms', iou_threshold=0.5), max_per_img=100)) + +img_scales = [(1333, 800), (666, 400), (2000, 1200)] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale=s, keep_ratio=True) for s in img_scales + ], [ + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], [dict(type='LoadAnnotations', with_bbox=True)], + [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'flip', + 'flip_direction')) + ]]) +] diff --git a/mmdetection/configs/retinanet/retinanet_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/retinanet/retinanet_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..765a4c2c --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './retinanet_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/retinanet/retinanet_x101-32x4d_fpn_2x_coco.py b/mmdetection/configs/retinanet/retinanet_x101-32x4d_fpn_2x_coco.py new file mode 100644 index 00000000..14de96fa --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_x101-32x4d_fpn_2x_coco.py @@ -0,0 +1,14 @@ +_base_ = './retinanet_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/retinanet/retinanet_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/retinanet/retinanet_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..948cd18e --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './retinanet_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/retinanet/retinanet_x101-64x4d_fpn_2x_coco.py b/mmdetection/configs/retinanet/retinanet_x101-64x4d_fpn_2x_coco.py new file mode 100644 index 00000000..ad04b6ee --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_x101-64x4d_fpn_2x_coco.py @@ -0,0 +1,14 @@ +_base_ = './retinanet_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/retinanet/retinanet_x101-64x4d_fpn_ms-640-800-3x_coco.py b/mmdetection/configs/retinanet/retinanet_x101-64x4d_fpn_ms-640-800-3x_coco.py new file mode 100644 index 00000000..85313416 --- /dev/null +++ b/mmdetection/configs/retinanet/retinanet_x101-64x4d_fpn_ms-640-800-3x_coco.py @@ -0,0 +1,11 @@ +_base_ = ['../_base_/models/retinanet_r50_fpn.py', '../common/ms_3x_coco.py'] +# optimizer +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) +optim_wrapper = dict(optimizer=dict(type='SGD', lr=0.01)) diff --git a/mmdetection/configs/rpn/README.md b/mmdetection/configs/rpn/README.md new file mode 100644 index 00000000..bd328b47 --- /dev/null +++ b/mmdetection/configs/rpn/README.md @@ -0,0 +1,39 @@ +# RPN + +> [Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks](https://arxiv.org/abs/1506.01497) + + + +## Abstract + +State-of-the-art object detection networks depend on region proposal algorithms to hypothesize object locations. Advances like SPPnet and Fast R-CNN have reduced the running time of these detection networks, exposing region proposal computation as a bottleneck. In this work, we introduce a Region Proposal Network (RPN) that shares full-image convolutional features with the detection network, thus enabling nearly cost-free region proposals. An RPN is a fully convolutional network that simultaneously predicts object bounds and objectness scores at each position. The RPN is trained end-to-end to generate high-quality region proposals, which are used by Fast R-CNN for detection. We further merge RPN and Fast R-CNN into a single network by sharing their convolutional features---using the recently popular terminology of neural networks with 'attention' mechanisms, the RPN component tells the unified network where to look. For the very deep VGG-16 model, our detection system has a frame rate of 5fps (including all steps) on a GPU, while achieving state-of-the-art object detection accuracy on PASCAL VOC 2007, 2012, and MS COCO datasets with only 300 proposals per image. In ILSVRC and COCO 2015 competitions, Faster R-CNN and RPN are the foundations of the 1st-place winning entries in several tracks. + +
    + +
    + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | AR1000 | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :---------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | caffe | 1x | 3.5 | 22.6 | 58.7 | [config](./rpn_r50-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_caffe_fpn_1x_coco/rpn_r50_caffe_fpn_1x_coco_20200531-5b903a37.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_caffe_fpn_1x_coco/rpn_r50_caffe_fpn_1x_coco_20200531_012334.log.json) | +| R-50-FPN | pytorch | 1x | 3.8 | 22.3 | 58.2 | [config](./rpn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_fpn_1x_coco/rpn_r50_fpn_1x_coco_20200218-5525fa2e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_fpn_1x_coco/rpn_r50_fpn_1x_coco_20200218_151240.log.json) | +| R-50-FPN | pytorch | 2x | - | - | 58.6 | [config](./rpn_r50_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_fpn_2x_coco/rpn_r50_fpn_2x_coco_20200131-0728c9b3.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_fpn_2x_coco/rpn_r50_fpn_2x_coco_20200131_190631.log.json) | +| R-101-FPN | caffe | 1x | 5.4 | 17.3 | 60.0 | [config](./rpn_r101-caffe_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r101_caffe_fpn_1x_coco/rpn_r101_caffe_fpn_1x_coco_20200531-0629a2e2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r101_caffe_fpn_1x_coco/rpn_r101_caffe_fpn_1x_coco_20200531_012345.log.json) | +| R-101-FPN | pytorch | 1x | 5.8 | 16.5 | 59.7 | [config](./rpn_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r101_fpn_1x_coco/rpn_r101_fpn_1x_coco_20200131-2ace2249.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r101_fpn_1x_coco/rpn_r101_fpn_1x_coco_20200131_191000.log.json) | +| R-101-FPN | pytorch | 2x | - | - | 60.2 | [config](./rpn_r101_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r101_fpn_2x_coco/rpn_r101_fpn_2x_coco_20200131-24e3db1a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r101_fpn_2x_coco/rpn_r101_fpn_2x_coco_20200131_191106.log.json) | +| X-101-32x4d-FPN | pytorch | 1x | 7.0 | 13.0 | 60.6 | [config](./rpn_x101-32x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_32x4d_fpn_1x_coco/rpn_x101_32x4d_fpn_1x_coco_20200219-b02646c6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_32x4d_fpn_1x_coco/rpn_x101_32x4d_fpn_1x_coco_20200219_012037.log.json) | +| X-101-32x4d-FPN | pytorch | 2x | - | - | 61.1 | [config](./rpn_x101-32x4d_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_32x4d_fpn_2x_coco/rpn_x101_32x4d_fpn_2x_coco_20200208-d22bd0bb.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_32x4d_fpn_2x_coco/rpn_x101_32x4d_fpn_2x_coco_20200208_200752.log.json) | +| X-101-64x4d-FPN | pytorch | 1x | 10.1 | 9.1 | 61.0 | [config](./rpn_x101-64x4d_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_64x4d_fpn_1x_coco/rpn_x101_64x4d_fpn_1x_coco_20200208-cde6f7dd.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_64x4d_fpn_1x_coco/rpn_x101_64x4d_fpn_1x_coco_20200208_200752.log.json) | +| X-101-64x4d-FPN | pytorch | 2x | - | - | 61.5 | [config](./rpn_x101-64x4d_fpn_2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_64x4d_fpn_2x_coco/rpn_x101_64x4d_fpn_2x_coco_20200208-c65f524f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_64x4d_fpn_2x_coco/rpn_x101_64x4d_fpn_2x_coco_20200208_200752.log.json) | + +## Citation + +```latex +@inproceedings{ren2015faster, + title={Faster r-cnn: Towards real-time object detection with region proposal networks}, + author={Ren, Shaoqing and He, Kaiming and Girshick, Ross and Sun, Jian}, + booktitle={Advances in neural information processing systems}, + year={2015} +} +``` diff --git a/mmdetection/configs/rpn/metafile.yml b/mmdetection/configs/rpn/metafile.yml new file mode 100644 index 00000000..9796ead6 --- /dev/null +++ b/mmdetection/configs/rpn/metafile.yml @@ -0,0 +1,127 @@ +Collections: + - Name: RPN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1506.01497 + Title: "Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks" + README: configs/rpn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/mmdet/models/detectors/rpn.py#L6 + Version: v2.0.0 + +Models: + - Name: rpn_r50-caffe_fpn_1x_coco + In Collection: RPN + Config: configs/rpn/rpn_r50-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.5 + Training Resources: 8x V100 GPUs + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + AR@1000: 58.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_caffe_fpn_1x_coco/rpn_r50_caffe_fpn_1x_coco_20200531-5b903a37.pth + + - Name: rpn_r50_fpn_1x_coco + In Collection: RPN + Config: configs/rpn/rpn_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 3.8 + Training Resources: 8x V100 GPUs + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + AR@1000: 58.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_fpn_1x_coco/rpn_r50_fpn_1x_coco_20200218-5525fa2e.pth + + - Name: rpn_r50_fpn_2x_coco + In Collection: RPN + Config: rpn_r50_fpn_2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + AR@1000: 58.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r50_fpn_2x_coco/rpn_r50_fpn_2x_coco_20200131-0728c9b3.pth + + - Name: rpn_r101-caffe_fpn_1x_coco + In Collection: RPN + Config: configs/rpn/rpn_r101-caffe_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.4 + Training Resources: 8x V100 GPUs + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + AR@1000: 60.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_r101_caffe_fpn_1x_coco/rpn_r101_caffe_fpn_1x_coco_20200531-0629a2e2.pth + + - Name: rpn_x101-32x4d_fpn_1x_coco + In Collection: RPN + Config: configs/rpn/rpn_x101-32x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.0 + Training Resources: 8x V100 GPUs + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + AR@1000: 60.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_32x4d_fpn_1x_coco/rpn_x101_32x4d_fpn_1x_coco_20200219-b02646c6.pth + + - Name: rpn_x101-32x4d_fpn_2x_coco + In Collection: RPN + Config: configs/rpn/rpn_x101-32x4d_fpn_2x_coco.py + Metadata: + Training Resources: 8x V100 GPUs + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + AR@1000: 61.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_32x4d_fpn_2x_coco/rpn_x101_32x4d_fpn_2x_coco_20200208-d22bd0bb.pth + + - Name: rpn_x101-64x4d_fpn_1x_coco + In Collection: RPN + Config: configs/rpn/rpn_x101-64x4d_fpn_1x_coco.py + Metadata: + Training Memory (GB): 10.1 + Training Resources: 8x V100 GPUs + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + AR@1000: 61.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_64x4d_fpn_1x_coco/rpn_x101_64x4d_fpn_1x_coco_20200208-cde6f7dd.pth + + - Name: rpn_x101-64x4d_fpn_2x_coco + In Collection: RPN + Config: configs/rpn/rpn_x101-64x4d_fpn_2x_coco.py + Metadata: + Training Resources: 8x V100 GPUs + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + AR@1000: 61.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/rpn/rpn_x101_64x4d_fpn_2x_coco/rpn_x101_64x4d_fpn_2x_coco_20200208-c65f524f.pth diff --git a/mmdetection/configs/rpn/rpn_r101-caffe_fpn_1x_coco.py b/mmdetection/configs/rpn/rpn_r101-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..22977af8 --- /dev/null +++ b/mmdetection/configs/rpn/rpn_r101-caffe_fpn_1x_coco.py @@ -0,0 +1,7 @@ +_base_ = './rpn_r50-caffe_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/configs/rpn/rpn_r101_fpn_1x_coco.py b/mmdetection/configs/rpn/rpn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..962728ff --- /dev/null +++ b/mmdetection/configs/rpn/rpn_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './rpn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/rpn/rpn_r101_fpn_2x_coco.py b/mmdetection/configs/rpn/rpn_r101_fpn_2x_coco.py new file mode 100644 index 00000000..ac7671c1 --- /dev/null +++ b/mmdetection/configs/rpn/rpn_r101_fpn_2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './rpn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/rpn/rpn_r50-caffe-c4_1x_coco.py b/mmdetection/configs/rpn/rpn_r50-caffe-c4_1x_coco.py new file mode 100644 index 00000000..76b878c8 --- /dev/null +++ b/mmdetection/configs/rpn/rpn_r50-caffe-c4_1x_coco.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/rpn_r50-caffe-c4.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +val_evaluator = dict(metric='proposal_fast') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/rpn/rpn_r50-caffe_fpn_1x_coco.py b/mmdetection/configs/rpn/rpn_r50-caffe_fpn_1x_coco.py new file mode 100644 index 00000000..530f3652 --- /dev/null +++ b/mmdetection/configs/rpn/rpn_r50-caffe_fpn_1x_coco.py @@ -0,0 +1,16 @@ +_base_ = './rpn_r50_fpn_1x_coco.py' +# use caffe img_norm +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/configs/rpn/rpn_r50_fpn_1x_coco.py b/mmdetection/configs/rpn/rpn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..7fe88d39 --- /dev/null +++ b/mmdetection/configs/rpn/rpn_r50_fpn_1x_coco.py @@ -0,0 +1,36 @@ +_base_ = [ + '../_base_/models/rpn_r50_fpn.py', '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +val_evaluator = dict(metric='proposal_fast') +test_evaluator = val_evaluator + +# inference on val dataset and dump the proposals with evaluate metric +# data_root = 'data/coco/' +# test_evaluator = [ +# dict( +# type='DumpProposals', +# output_dir=data_root + 'proposals/', +# proposals_file='rpn_r50_fpn_1x_val2017.pkl'), +# dict( +# type='CocoMetric', +# ann_file=data_root + 'annotations/instances_val2017.json', +# metric='proposal_fast', +# backend_args={{_base_.backend_args}}, +# format_only=False) +# ] + +# inference on training dataset and dump the proposals without evaluate metric +# data_root = 'data/coco/' +# test_dataloader = dict( +# dataset=dict( +# ann_file='annotations/instances_train2017.json', +# data_prefix=dict(img='train2017/'))) +# +# test_evaluator = [ +# dict( +# type='DumpProposals', +# output_dir=data_root + 'proposals/', +# proposals_file='rpn_r50_fpn_1x_train2017.pkl'), +# ] diff --git a/mmdetection/configs/rpn/rpn_r50_fpn_2x_coco.py b/mmdetection/configs/rpn/rpn_r50_fpn_2x_coco.py new file mode 100644 index 00000000..0ebccbcf --- /dev/null +++ b/mmdetection/configs/rpn/rpn_r50_fpn_2x_coco.py @@ -0,0 +1,17 @@ +_base_ = './rpn_r50_fpn_1x_coco.py' + +# learning policy +max_epochs = 24 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/configs/rpn/rpn_x101-32x4d_fpn_1x_coco.py b/mmdetection/configs/rpn/rpn_x101-32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..d0c73948 --- /dev/null +++ b/mmdetection/configs/rpn/rpn_x101-32x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './rpn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/rpn/rpn_x101-32x4d_fpn_2x_coco.py b/mmdetection/configs/rpn/rpn_x101-32x4d_fpn_2x_coco.py new file mode 100644 index 00000000..c6880b76 --- /dev/null +++ b/mmdetection/configs/rpn/rpn_x101-32x4d_fpn_2x_coco.py @@ -0,0 +1,14 @@ +_base_ = './rpn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/rpn/rpn_x101-64x4d_fpn_1x_coco.py b/mmdetection/configs/rpn/rpn_x101-64x4d_fpn_1x_coco.py new file mode 100644 index 00000000..96e691a9 --- /dev/null +++ b/mmdetection/configs/rpn/rpn_x101-64x4d_fpn_1x_coco.py @@ -0,0 +1,14 @@ +_base_ = './rpn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/rpn/rpn_x101-64x4d_fpn_2x_coco.py b/mmdetection/configs/rpn/rpn_x101-64x4d_fpn_2x_coco.py new file mode 100644 index 00000000..4182a396 --- /dev/null +++ b/mmdetection/configs/rpn/rpn_x101-64x4d_fpn_2x_coco.py @@ -0,0 +1,14 @@ +_base_ = './rpn_r50_fpn_2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/rtmdet/README.md b/mmdetection/configs/rtmdet/README.md new file mode 100644 index 00000000..1677184a --- /dev/null +++ b/mmdetection/configs/rtmdet/README.md @@ -0,0 +1,457 @@ +# RTMDet: An Empirical Study of Designing Real-Time Object Detectors + +> [RTMDet: An Empirical Study of Designing Real-Time Object Detectors](https://arxiv.org/abs/2212.07784) + +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/real-time-instance-segmentation-on-mscoco)](https://paperswithcode.com/sota/real-time-instance-segmentation-on-mscoco?p=rtmdet-an-empirical-study-of-designing-real) +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/object-detection-in-aerial-images-on-dota-1)](https://paperswithcode.com/sota/object-detection-in-aerial-images-on-dota-1?p=rtmdet-an-empirical-study-of-designing-real) +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/object-detection-in-aerial-images-on-hrsc2016)](https://paperswithcode.com/sota/object-detection-in-aerial-images-on-hrsc2016?p=rtmdet-an-empirical-study-of-designing-real) + + + +## Abstract + +In this paper, we aim to design an efficient real-time object detector that exceeds the YOLO series and is easily extensible for many object recognition tasks such as instance segmentation and rotated object detection. To obtain a more efficient model architecture, we explore an architecture that has compatible capacities in the backbone and neck, constructed by a basic building block that consists of large-kernel depth-wise convolutions. We further introduce soft labels when calculating matching costs in the dynamic label assignment to improve accuracy. Together with better training techniques, the resulting object detector, named RTMDet, achieves 52.8% AP on COCO with 300+ FPS on an NVIDIA 3090 GPU, outperforming the current mainstream industrial detectors. RTMDet achieves the best parameter-accuracy trade-off with tiny/small/medium/large/extra-large model sizes for various application scenarios, and obtains new state-of-the-art performance on real-time instance segmentation and rotated object detection. We hope the experimental results can provide new insights into designing versatile real-time object detectors for many object recognition tasks. + +
    + +
    + +## Results and Models + +### Object Detection + +| Model | size | box AP | Params(M) | FLOPS(G) | TRT-FP16-Latency(ms)
    RTX3090 | TRT-FP16-Latency(ms)
    T4 | Config | Download | +| :-----------------: | :--: | :----: | :-------: | :------: | :-----------------------------: | :------------------------: | :------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| RTMDet-tiny | 640 | 41.1 | 4.8 | 8.1 | 0.98 | 2.34 | [config](./rtmdet_tiny_8xb32-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_tiny_8xb32-300e_coco/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_tiny_8xb32-300e_coco/rtmdet_tiny_8xb32-300e_coco_20220902_112414.log.json) | +| RTMDet-s | 640 | 44.6 | 8.89 | 14.8 | 1.22 | 2.96 | [config](./rtmdet_s_8xb32-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_s_8xb32-300e_coco/rtmdet_s_8xb32-300e_coco_20220905_161602-387a891e.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_s_8xb32-300e_coco/rtmdet_s_8xb32-300e_coco_20220905_161602.log.json) | +| RTMDet-m | 640 | 49.4 | 24.71 | 39.27 | 1.62 | 6.41 | [config](./rtmdet_m_8xb32-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_m_8xb32-300e_coco/rtmdet_m_8xb32-300e_coco_20220719_112220-229f527c.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_m_8xb32-300e_coco/rtmdet_m_8xb32-300e_coco_20220719_112220.log.json) | +| RTMDet-l | 640 | 51.5 | 52.3 | 80.23 | 2.44 | 10.32 | [config](./rtmdet_l_8xb32-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_l_8xb32-300e_coco/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_l_8xb32-300e_coco/rtmdet_l_8xb32-300e_coco_20220719_112030.log.json) | +| RTMDet-x | 640 | 52.8 | 94.86 | 141.67 | 3.10 | 18.80 | [config](./rtmdet_x_8xb32-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_x_8xb32-300e_coco/rtmdet_x_8xb32-300e_coco_20220715_230555-cc79b9ae.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_x_8xb32-300e_coco/rtmdet_x_8xb32-300e_coco_20220715_230555.log.json) | +| RTMDet-x-P6 | 1280 | 54.9 | | | | | [config](./rtmdet_x_p6_4xb8-300e_coco.py) | [model](https://github.com/orange0-jp/orange-weights/releases/download/v0.1.0rtmdet-p6/rtmdet_x_p6_4xb8-300e_coco-bf32be58.pth) | +| RTMDet-l-ConvNeXt-B | 640 | 53.1 | | | | | [config](./rtmdet_l_convnext_b_4xb32-100e_coco.py) | [model](https://github.com/orange0-jp/orange-weights/releases/download/v0.1.0rtmdet-swin-convnext/rtmdet_l_convnext_b_4xb32-100e_coco-d4731b3d.pth) | +| RTMDet-l-Swin-B | 640 | 52.4 | | | | | [config](./rtmdet_l_swin_b_4xb32-100e_coco.py) | [model](https://github.com/orange0-jp/orange-weights/releases/download/v0.1.0rtmdet-swin-convnext/rtmdet_l_swin_b_4xb32-100e_coco-0828ce5d.pth) | +| RTMDet-l-Swin-B-P6 | 1280 | 56.4 | | | | | [config](./rtmdet_l_swin_b_p6_4xb16-100e_coco.py) | [model](https://github.com/orange0-jp/orange-weights/releases/download/v0.1.0rtmdet-swin-convnext/rtmdet_l_swin_b_p6_4xb16-100e_coco-a1486b6f.pth) | + +**Note**: + +1. We implement a fast training version of RTMDet in [MMYOLO](https://github.com/open-mmlab/mmyolo). Its training speed is **2.6 times faster** and memory requirement is lower! Try it [here](https://github.com/open-mmlab/mmyolo/tree/main/configs/rtmdet)! +2. The inference speed of RTMDet is measured with TensorRT 8.4.3, cuDNN 8.2.0, FP16, batch size=1, and without NMS. +3. For a fair comparison, the config of bbox postprocessing is changed to be consistent with YOLOv5/6/7 after [PR#9494](https://github.com/open-mmlab/mmdetection/pull/9494), bringing about 0.1~0.3% AP improvement. + +### Instance Segmentation + +RTMDet-Ins is the state-of-the-art real-time instance segmentation on coco dataset: + +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/real-time-instance-segmentation-on-mscoco)](https://paperswithcode.com/sota/real-time-instance-segmentation-on-mscoco?p=rtmdet-an-empirical-study-of-designing-real) + +| Model | size | box AP | mask AP | Params(M) | FLOPS(G) | TRT-FP16-Latency(ms) | Config | Download | +| :-------------: | :--: | :----: | :-----: | :-------: | :------: | :------------------: | :--------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| RTMDet-Ins-tiny | 640 | 40.5 | 35.4 | 5.6 | 11.8 | 1.70 | [config](./rtmdet-ins_tiny_8xb32-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_tiny_8xb32-300e_coco/rtmdet-ins_tiny_8xb32-300e_coco_20221130_151727-ec670f7e.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_tiny_8xb32-300e_coco/rtmdet-ins_tiny_8xb32-300e_coco_20221130_151727.log.json) | +| RTMDet-Ins-s | 640 | 44.0 | 38.7 | 10.18 | 21.5 | 1.93 | [config](./rtmdet-ins_s_8xb32-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_s_8xb32-300e_coco/rtmdet-ins_s_8xb32-300e_coco_20221121_212604-fdc5d7ec.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_s_8xb32-300e_coco/rtmdet-ins_s_8xb32-300e_coco_20221121_212604.log.json) | +| RTMDet-Ins-m | 640 | 48.8 | 42.1 | 27.58 | 54.13 | 2.69 | [config](./rtmdet-ins_m_8xb32-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_m_8xb32-300e_coco/rtmdet-ins_m_8xb32-300e_coco_20221123_001039-6eba602e.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_m_8xb32-300e_coco/rtmdet-ins_m_8xb32-300e_coco_20221123_001039.log.json) | +| RTMDet-Ins-l | 640 | 51.2 | 43.7 | 57.37 | 106.56 | 3.68 | [config](./rtmdet-ins_l_8xb32-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_l_8xb32-300e_coco/rtmdet-ins_l_8xb32-300e_coco_20221124_103237-78d1d652.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_l_8xb32-300e_coco/rtmdet-ins_l_8xb32-300e_coco_20221124_103237.log.json) | +| RTMDet-Ins-x | 640 | 52.4 | 44.6 | 102.7 | 182.7 | 5.31 | [config](./rtmdet-ins_x_8xb16-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_x_8xb16-300e_coco/rtmdet-ins_x_8xb16-300e_coco_20221124_111313-33d4595b.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_x_8xb16-300e_coco/rtmdet-ins_x_8xb16-300e_coco_20221124_111313.log.json) | + +**Note**: + +1. The inference speed of RTMDet-Ins is measured on an NVIDIA 3090 GPU with TensorRT 8.4.3, cuDNN 8.2.0, FP16, batch size=1. Top 100 masks are kept and the post process latency is included. + +### Rotated Object Detection + +RTMDet-R achieves state-of-the-art on various remote sensing datasets. + +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/object-detection-in-aerial-images-on-dota-1)](https://paperswithcode.com/sota/object-detection-in-aerial-images-on-dota-1?p=rtmdet-an-empirical-study-of-designing-real) + +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/one-stage-anchor-free-oriented-object-1)](https://paperswithcode.com/sota/one-stage-anchor-free-oriented-object-1?p=rtmdet-an-empirical-study-of-designing-real) + +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/object-detection-in-aerial-images-on-hrsc2016)](https://paperswithcode.com/sota/object-detection-in-aerial-images-on-hrsc2016?p=rtmdet-an-empirical-study-of-designing-real) + +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/rtmdet-an-empirical-study-of-designing-real/one-stage-anchor-free-oriented-object-3)](https://paperswithcode.com/sota/one-stage-anchor-free-oriented-object-3?p=rtmdet-an-empirical-study-of-designing-real) + +Models and configs of RTMDet-R are available in [MMRotate](https://github.com/open-mmlab/mmrotate/tree/1.x/configs/rotated_rtmdet). + +| Backbone | pretrain | Aug | mmAP | mAP50 | mAP75 | Params(M) | FLOPS(G) | TRT-FP16-Latency(ms) | Config | Download | +| :---------: | :------: | :---: | :---: | :---: | :---: | :-------: | :------: | :------------------: | :---------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| RTMDet-tiny | IN | RR | 47.37 | 75.36 | 50.64 | 4.88 | 20.45 | 4.40 | [config](https://github.com/open-mmlab/mmrotate/edit/1.x/configs/rotated_rtmdet/rotated_rtmdet_tiny-3x-dota.py) | [model](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_tiny-3x-dota/rotated_rtmdet_tiny-3x-dota-9d821076.pth) \| [log](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_tiny-3x-dota/rotated_rtmdet_tiny-3x-dota_20221201_120814.json) | +| RTMDet-tiny | IN | MS+RR | 53.59 | 79.82 | 58.87 | 4.88 | 20.45 | 4.40 | [config](https://github.com/open-mmlab/mmrotate/edit/1.x/configs/rotated_rtmdet/rotated_rtmdet_tiny-3x-dota_ms.py) | [model](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_tiny-3x-dota_ms/rotated_rtmdet_tiny-3x-dota_ms-f12286ff.pth) \| [log](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_tiny-3x-dota_ms/rotated_rtmdet_tiny-3x-dota_ms_20221113_201235.log) | +| RTMDet-s | IN | RR | 48.16 | 76.93 | 50.59 | 8.86 | 37.62 | 4.86 | [config](https://github.com/open-mmlab/mmrotate/edit/1.x/configs/rotated_rtmdet/rotated_rtmdet_s-3x-dota.py) | [model](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_s-3x-dota/rotated_rtmdet_s-3x-dota-11f6ccf5.pth) \| [log](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_s-3x-dota/rotated_rtmdet_s-3x-dota_20221124_081442.json) | +| RTMDet-s | IN | MS+RR | 54.43 | 79.98 | 60.07 | 8.86 | 37.62 | 4.86 | [config](https://github.com/open-mmlab/mmrotate/edit/1.x/configs/rotated_rtmdet/rotated_rtmdet_s-3x-dota_ms.py) | [model](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_s-3x-dota_ms/rotated_rtmdet_s-3x-dota_ms-20ead048.pth) \| [log](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_s-3x-dota_ms/rotated_rtmdet_s-3x-dota_ms_20221113_201055.json) | +| RTMDet-m | IN | RR | 50.56 | 78.24 | 54.47 | 24.67 | 99.76 | 7.82 | [config](https://github.com/open-mmlab/mmrotate/edit/1.x/configs/rotated_rtmdet/rotated_rtmdet_m-3x-dota.py) | [model](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_m-3x-dota/rotated_rtmdet_m-3x-dota-beeadda6.pth) \| [log](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_m-3x-dota/rotated_rtmdet_m-3x-dota_20221122_011234.json) | +| RTMDet-m | IN | MS+RR | 55.00 | 80.26 | 61.26 | 24.67 | 99.76 | 7.82 | [config](https://github.com/open-mmlab/mmrotate/edit/1.x/configs/rotated_rtmdet/rotated_rtmdet_m-3x-dota_ms.py) | [model](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_m-3x-dota_ms/rotated_rtmdet_m-3x-dota_ms-c71eb375.pth) \| [log](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_m-3x-dota_ms/rotated_rtmdet_m-3x-dota_ms_20221122_011234.json) | +| RTMDet-l | IN | RR | 51.01 | 78.85 | 55.21 | 52.27 | 204.21 | 10.82 | [config](https://github.com/open-mmlab/mmrotate/edit/1.x/configs/rotated_rtmdet/rotated_rtmdet_l-3x-dota.py) | [model](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_l-3x-dota/rotated_rtmdet_l-3x-dota-23992372.pth) \| [log](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_l-3x-dota/rotated_rtmdet_l-3x-dota_20221122_011241.json) | +| RTMDet-l | IN | MS+RR | 55.52 | 80.54 | 61.47 | 52.27 | 204.21 | 10.82 | [config](https://github.com/open-mmlab/mmrotate/edit/1.x/configs/rotated_rtmdet/rotated_rtmdet_l-3x-dota_ms.py) | [model](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_l-3x-dota_ms/rotated_rtmdet_l-3x-dota_ms-2738da34.pth) \| [log](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_l-3x-dota_ms/rotated_rtmdet_l-3x-dota_ms_20221122_011241.json) | +| RTMDet-l | COCO | MS+RR | 56.74 | 81.33 | 63.45 | 52.27 | 204.21 | 10.82 | [config](https://github.com/open-mmlab/mmrotate/edit/1.x/configs/rotated_rtmdet/rotated_rtmdet_l-coco_pretrain-3x-dota_ms.py) | [model](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_l-coco_pretrain-3x-dota_ms/rotated_rtmdet_l-coco_pretrain-3x-dota_ms-06d248a2.pth) \| [log](https://download.openmmlab.com/mmrotate/v1.0/rotated_rtmdet/rotated_rtmdet_l-coco_pretrain-3x-dota_ms/rotated_rtmdet_l-coco_pretrain-3x-dota_ms_20221113_202010.json) | + +### Classification + +We also provide the imagenet classification configs of the RTMDet backbone. Find more details in the [classification folder](./classification). + +| Model | resolution | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Download | +| :----------: | :--------: | :-------: | :------: | :-------: | :-------: | :---------------------------------------------------------------------------------------------------------------------------------: | +| CSPNeXt-tiny | 224x224 | 2.73 | 0.34 | 69.44 | 89.45 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e-3a2dd350.pth) | +| CSPNeXt-s | 224x224 | 4.89 | 0.66 | 74.41 | 92.23 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-s_imagenet_600e-ea671761.pth) | +| CSPNeXt-m | 224x224 | 13.05 | 1.93 | 79.27 | 94.79 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-m_8xb256-rsb-a1-600e_in1k-ecb3bbd9.pth) | +| CSPNeXt-l | 224x224 | 27.16 | 4.19 | 81.30 | 95.62 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-l_8xb256-rsb-a1-600e_in1k-6a760974.pth) | +| CSPNeXt-x | 224x224 | 48.85 | 7.76 | 82.10 | 95.69 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-x_8xb256-rsb-a1-600e_in1k-b3f78edd.pth) | + +## Citation + +```latex +@misc{lyu2022rtmdet, + title={RTMDet: An Empirical Study of Designing Real-Time Object Detectors}, + author={Chengqi Lyu and Wenwei Zhang and Haian Huang and Yue Zhou and Yudong Wang and Yanyi Liu and Shilong Zhang and Kai Chen}, + year={2022}, + eprint={2212.07784}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} +``` + +## Visualization + +
    + +
    + +## Deployment Tutorial + +Here is a basic example of deploy RTMDet with [MMDeploy-1.x](https://github.com/open-mmlab/mmdeploy/tree/1.x). + +### Step1. Install MMDeploy + +Before starting the deployment, please make sure you install MMDetection and MMDeploy-1.x correctly. + +- Install MMDetection, please refer to the [MMDetection installation guide](https://mmdetection.readthedocs.io/en/latest/get_started.html). +- Install MMDeploy-1.x, please refer to the [MMDeploy-1.x installation guide](https://mmdeploy.readthedocs.io/en/1.x/get_started.html#installation). + +If you want to deploy RTMDet with ONNXRuntime, TensorRT, or other inference engine, +please make sure you have installed the corresponding dependencies and MMDeploy precompiled packages. + +### Step2. Convert Model + +After the installation, you can enjoy the model deployment journey starting from converting PyTorch model to backend model by running MMDeploy's `tools/deploy.py`. + +The detailed model conversion tutorial please refer to the [MMDeploy document](https://mmdeploy.readthedocs.io/en/1.x/02-how-to-run/convert_model.html). +Here we only give the example of converting RTMDet. + +MMDeploy supports converting dynamic and static models. Dynamic models support different input shape, but the inference speed is slower than static models. +To achieve the best performance, we suggest converting RTMDet with static setting. + +- If you only want to use ONNX, please use [`configs/mmdet/detection/detection_onnxruntime_static.py`](https://github.com/open-mmlab/mmdeploy/blob/1.x/configs/mmdet/detection/detection_onnxruntime_static.py) as the deployment config. +- If you want to use TensorRT, please use [`configs/mmdet/detection/detection_tensorrt_static-640x640.py`](https://github.com/open-mmlab/mmdeploy/blob/1.x/configs/mmdet/detection/detection_tensorrt_static-640x640.py). + +If you want to customize the settings in the deployment config for your requirements, please refer to [MMDeploy config tutorial](https://mmdeploy.readthedocs.io/en/1.x/02-how-to-run/write_config.html). + +After preparing the deployment config, you can run the `tools/deploy.py` script to convert your model. +Here we take converting RTMDet-s to TensorRT as an example: + +```shell +# go to the mmdeploy folder +cd ${PATH_TO_MMDEPLOY} + +# download RTMDet-s checkpoint +wget -P checkpoint https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_s_8xb32-300e_coco/rtmdet_s_8xb32-300e_coco_20220905_161602-387a891e.pth + +# run the command to start model conversion +python tools/deploy.py \ + configs/mmdet/detection/detection_tensorrt_static-640x640.py \ + ${PATH_TO_MMDET}/configs/rtmdet/rtmdet_s_8xb32-300e_coco.py \ + checkpoint/rtmdet_s_8xb32-300e_coco_20220905_161602-387a891e.pth \ + demo/resources/det.jpg \ + --work-dir ./work_dirs/rtmdet \ + --device cuda:0 \ + --show +``` + +If the script runs successfully, you will see the following files: + +``` +|----work_dirs + |----rtmdet + |----end2end.onnx # ONNX model + |----end2end.engine # TensorRT engine file +``` + +After this, you can check the inference results with MMDeploy Model Converter API: + +```python +from mmdeploy.apis import inference_model + +result = inference_model( + model_cfg='${PATH_TO_MMDET}/configs/rtmdet/rtmdet_s_8xb32-300e_coco.py', + deploy_cfg='${PATH_TO_MMDEPLOY}/configs/mmdet/detection/detection_tensorrt_static-640x640.py', + backend_files=['work_dirs/rtmdet/end2end.engine'], + img='demo/resources/det.jpg', + device='cuda:0') +``` + +#### Advanced Setting + +To convert the model with TRT-FP16, you can enable the fp16 mode in your deploy config: + +```python +# in MMDeploy config +backend_config = dict( + type='tensorrt', + common_config=dict( + fp16_mode=True # enable fp16 + )) +``` + +To reduce the end to end inference speed with the inference engine, we suggest you to adjust the post-processing setting of the model. +We set a very low score threshold during training and testing to achieve better COCO mAP. +However, in actual usage scenarios, a relatively high score threshold (e.g. 0.3) is usually used. + +You can adjust the score threshold and the number of detection boxes in your model config according to the actual usage to reduce the time-consuming of post-processing. + +```python +# in MMDetection config +model = dict( + test_cfg=dict( + nms_pre=1000, # keep top-k score bboxes before nms + min_bbox_size=0, + score_thr=0.3, # score threshold to filter bboxes + nms=dict(type='nms', iou_threshold=0.65), + max_per_img=100) # only keep top-100 as the final results. +) +``` + +### Step3. Inference with SDK + +We provide both Python and C++ inference API with MMDeploy SDK. + +To use SDK, you need to dump the required info during converting the model. Just add `--dump-info` to the model conversion command: + +```shell +python tools/deploy.py \ + configs/mmdet/detection/detection_tensorrt_static-640x640.py \ + ${PATH_TO_MMDET}/configs/rtmdet/rtmdet_s_8xb32-300e_coco.py \ + checkpoint/rtmdet_s_8xb32-300e_coco_20220905_161602-387a891e.pth \ + demo/resources/det.jpg \ + --work-dir ./work_dirs/rtmdet-sdk \ + --device cuda:0 \ + --show \ + --dump-info # dump sdk info +``` + +After running the command, it will dump 3 json files additionally for the SDK: + +``` +|----work_dirs + |----rtmdet-sdk + |----end2end.onnx # ONNX model + |----end2end.engine # TensorRT engine file + # json files for the SDK + |----pipeline.json + |----deploy.json + |----detail.json +``` + +#### Python API + +Here is a basic example of SDK Python API: + +```python +from mmdeploy_python import Detector +import cv2 + +img = cv2.imread('demo/resources/det.jpg') + +# create a detector +detector = Detector(model_path='work_dirs/rtmdet-sdk', device_name='cuda', device_id=0) +# run the inference +bboxes, labels, _ = detector(img) +# Filter the result according to threshold +indices = [i for i in range(len(bboxes))] +for index, bbox, label_id in zip(indices, bboxes, labels): + [left, top, right, bottom], score = bbox[0:4].astype(int), bbox[4] + if score < 0.3: + continue + # draw bbox + cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0)) + +cv2.imwrite('output_detection.png', img) +``` + +#### C++ API + +Here is a basic example of SDK C++ API: + +```C++ +#include +#include +#include "mmdeploy/detector.hpp" + +int main() { + const char* device_name = "cuda"; + int device_id = 0; + std::string model_path = "work_dirs/rtmdet-sdk"; + std::string image_path = "demo/resources/det.jpg"; + + // 1. load model + mmdeploy::Model model(model_path); + // 2. create predictor + mmdeploy::Detector detector(model, mmdeploy::Device{device_name, device_id}); + // 3. read image + cv::Mat img = cv::imread(image_path); + // 4. inference + auto dets = detector.Apply(img); + // 5. deal with the result. Here we choose to visualize it + for (int i = 0; i < dets.size(); ++i) { + const auto& box = dets[i].bbox; + fprintf(stdout, "box %d, left=%.2f, top=%.2f, right=%.2f, bottom=%.2f, label=%d, score=%.4f\n", + i, box.left, box.top, box.right, box.bottom, dets[i].label_id, dets[i].score); + if (bboxes[i].score < 0.3) { + continue; + } + cv::rectangle(img, cv::Point{(int)box.left, (int)box.top}, + cv::Point{(int)box.right, (int)box.bottom}, cv::Scalar{0, 255, 0}); + } + cv::imwrite("output_detection.png", img); + return 0; +} +``` + +To build C++ example, please add MMDeploy package in your CMake project as following: + +```cmake +find_package(MMDeploy REQUIRED) +target_link_libraries(${name} PRIVATE mmdeploy ${OpenCV_LIBS}) +``` + +#### Other languages + +- [C# API Examples](https://github.com/open-mmlab/mmdeploy/tree/1.x/demo/csharp) +- [JAVA API Examples](https://github.com/open-mmlab/mmdeploy/tree/1.x/demo/java) + +### Deploy RTMDet Instance Segmentation Model + +We support RTMDet-Ins ONNXRuntime and TensorRT deployment after [MMDeploy v1.0.0rc2](https://github.com/open-mmlab/mmdeploy/tree/v1.0.0rc2). And its deployment process is almost consistent with the detection model. + +#### Step1. Install MMDeploy >= v1.0.0rc2 + +Please refer to the [MMDeploy-1.x installation guide](https://mmdeploy.readthedocs.io/en/1.x/get_started.html#installation) to install the latest version. +Please remember to replace the pre-built package with the latest version. +The v1.0.0rc2 package can be downloaded from [v1.0.0rc2 release page](https://github.com/open-mmlab/mmdeploy/releases/tag/v1.0.0rc2). + +Step2. Convert Model + +This step has no difference with the previous tutorial. The only thing you need to change is switching to the RTMDet-Ins deploy config: + +- If you want to use ONNXRuntime, please use [`configs/mmdet/instance-seg/instance-seg_rtmdet-ins_onnxruntime_static-640x640.py`](https://github.com/open-mmlab/mmdeploy/blob/dev-1.x/configs/mmdet/instance-seg/instance-seg_rtmdet-ins_onnxruntime_static-640x640.py) as the deployment config. +- If you want to use TensorRT, please use [`configs/mmdet/instance-seg/instance-seg_rtmdet-ins_tensorrt_static-640x640.py`](https://github.com/open-mmlab/mmdeploy/blob/dev-1.x/configs/mmdet/instance-seg/instance-seg_rtmdet-ins_tensorrt_static-640x640.py). + +Here we take converting RTMDet-Ins-s to TensorRT as an example: + +```shell +# go to the mmdeploy folder +cd ${PATH_TO_MMDEPLOY} + +# download RTMDet-s checkpoint +wget -P checkpoint https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_s_8xb32-300e_coco/rtmdet-ins_s_8xb32-300e_coco_20221121_212604-fdc5d7ec.pth + +# run the command to start model conversion +python tools/deploy.py \ + configs/mmdet/instance-seg/instance-seg_rtmdet-ins_tensorrt_static-640x640.py \ + ${PATH_TO_MMDET}/configs/rtmdet/rtmdet-ins_s_8xb32-300e_coco.py \ + checkpoint/rtmdet-ins_s_8xb32-300e_coco_20221121_212604-fdc5d7ec.pth \ + demo/resources/det.jpg \ + --work-dir ./work_dirs/rtmdet-ins \ + --device cuda:0 \ + --show +``` + +If the script runs successfully, you will see the following files: + +``` +|----work_dirs + |----rtmdet-ins + |----end2end.onnx # ONNX model + |----end2end.engine # TensorRT engine file +``` + +After this, you can check the inference results with MMDeploy Model Converter API: + +```python +from mmdeploy.apis import inference_model + +result = inference_model( + model_cfg='${PATH_TO_MMDET}/configs/rtmdet/rtmdet-ins_s_8xb32-300e_coco.py', + deploy_cfg='${PATH_TO_MMDEPLOY}/configs/mmdet/instance-seg/instance-seg_rtmdet-ins_tensorrt_static-640x640.py', + backend_files=['work_dirs/rtmdet-ins/end2end.engine'], + img='demo/resources/det.jpg', + device='cuda:0') +``` + +### Model Config + +In MMDetection's config, we use `model` to set up detection algorithm components. In addition to neural network components such as `backbone`, `neck`, etc, it also requires `data_preprocessor`, `train_cfg`, and `test_cfg`. `data_preprocessor` is responsible for processing a batch of data output by dataloader. `train_cfg`, and `test_cfg` in the model config are for training and testing hyperparameters of the components.Taking RTMDet as an example, we will introduce each field in the config according to different function modules: + +```python +model = dict( + type='RTMDet', # The name of detector + data_preprocessor=dict( # The config of data preprocessor, usually includes image normalization and padding + type='DetDataPreprocessor', # The type of the data preprocessor. Refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.data_preprocessors.DetDataPreprocessor + mean=[103.53, 116.28, 123.675], # Mean values used to pre-training the pre-trained backbone models, ordered in R, G, B + std=[57.375, 57.12, 58.395], # Standard variance used to pre-training the pre-trained backbone models, ordered in R, G, B + bgr_to_rgb=False, # whether to convert image from BGR to RGB + batch_augments=None), # Batch-level augmentations + backbone=dict( # The config of backbone + type='CSPNeXt', # The type of backbone network. Refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.backbones.CSPNeXt + arch='P5', # Architecture of CSPNeXt, from {P5, P6}. Defaults to P5 + expand_ratio=0.5, # Ratio to adjust the number of channels of the hidden layer. Defaults to 0.5 + deepen_factor=1, # Depth multiplier, multiply number of blocks in CSP layer by this amount. Defaults to 1.0 + widen_factor=1, # Width multiplier, multiply number of channels in each layer by this amount. Defaults to 1.0 + channel_attention=True, # Whether to add channel attention in each stage. Defaults to True + norm_cfg=dict(type='SyncBN'), # Dictionary to construct and config norm layer. Defaults to dict(type=’BN’, requires_grad=True) + act_cfg=dict(type='SiLU', inplace=True)), # Config dict for activation layer. Defaults to dict(type=’SiLU’) + neck=dict( + type='CSPNeXtPAFPN', # The type of neck is CSPNeXtPAFPN. Refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.necks.CSPNeXtPAFPN + in_channels=[256, 512, 1024], # Number of input channels per scale + out_channels=256, # Number of output channels (used at each scale) + num_csp_blocks=3, # Number of bottlenecks in CSPLayer. Defaults to 3 + expand_ratio=0.5, # Ratio to adjust the number of channels of the hidden layer. Default: 0.5 + norm_cfg=dict(type='SyncBN'), # Config dict for normalization layer. Default: dict(type=’BN’) + act_cfg=dict(type='SiLU', inplace=True)), # Config dict for activation layer. Default: dict(type=’Swish’) + bbox_head=dict( + type='RTMDetSepBNHead', # The type of bbox_head is RTMDetSepBNHead. RTMDetHead with separated BN layers and shared conv layers. Refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.dense_heads.RTMDetSepBNHead + num_classes=80, # Number of categories excluding the background category + in_channels=256, # Number of channels in the input feature map + stacked_convs=2, # Whether to share conv layers between stages. Defaults to True + feat_channels=256, # Feature channels of convolutional layers in the head + anchor_generator=dict( # The config of anchor generator + type='MlvlPointGenerator', # The methods use MlvlPointGenerator. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/prior_generators/point_generator.py#L92 + offset=0, # The offset of points, the value is normalized with corresponding stride. Defaults to 0.5 + strides=[8, 16, 32]), # Strides of anchors in multiple feature levels in order (w, h) + bbox_coder=dict(type='DistancePointBBoxCoder'), # Distance Point BBox coder.This coder encodes gt bboxes (x1, y1, x2, y2) into (top, bottom, left,right) and decode it back to the original. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/coders/distance_point_bbox_coder.py#L9 + loss_cls=dict( # Config of loss function for the classification branch + type='QualityFocalLoss', # Type of loss for classification branch. Refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.losses.QualityFocalLoss + use_sigmoid=True, # Whether sigmoid operation is conducted in QFL. Defaults to True + beta=2.0, # The beta parameter for calculating the modulating factor. Defaults to 2.0 + loss_weight=1.0), # Loss weight of current loss + loss_bbox=dict( # Config of loss function for the regression branch + type='GIoULoss', # Type of loss. Refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.losses.GIoULoss + loss_weight=2.0), # Loss weight of the regression branch + with_objectness=False, # Whether to add an objectness branch. Defaults to True + exp_on_reg=True, # Whether to use .exp() in regression + share_conv=True, # Whether to share conv layers between stages. Defaults to True + pred_kernel_size=1, # Kernel size of prediction layer. Defaults to 1 + norm_cfg=dict(type='SyncBN'), # Config dict for normalization layer. Defaults to dict(type='BN', momentum=0.03, eps=0.001) + act_cfg=dict(type='SiLU', inplace=True)), # Config dict for activation layer. Defaults to dict(type='SiLU') + train_cfg=dict( # Config of training hyperparameters for ATSS + assigner=dict( # Config of assigner + type='DynamicSoftLabelAssigner', # Type of assigner. DynamicSoftLabelAssigner computes matching between predictions and ground truth with dynamic soft label assignment. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/assigners/dynamic_soft_label_assigner.py#L40 + topk=13), # Select top-k predictions to calculate dynamic k best matches for each gt. Defaults to 13 + allowed_border=-1, # The border allowed after padding for valid anchors + pos_weight=-1, # The weight of positive samples during training + debug=False), # Whether to set the debug mode + test_cfg=dict( # Config for testing hyperparameters for ATSS + nms_pre=30000, # The number of boxes before NMS + min_bbox_size=0, # The allowed minimal box size + score_thr=0.001, # Threshold to filter out boxes + nms=dict( # Config of NMS in the second stage + type='nms', # Type of NMS + iou_threshold=0.65), # NMS threshold + max_per_img=300), # Max number of detections of each image +) +``` diff --git a/mmdetection/configs/rtmdet/classification/README.md b/mmdetection/configs/rtmdet/classification/README.md new file mode 100644 index 00000000..acc127db --- /dev/null +++ b/mmdetection/configs/rtmdet/classification/README.md @@ -0,0 +1,56 @@ +# CSPNeXt ImageNet Pre-training + +In this folder, we provide the imagenet pre-training config of RTMDet's backbone CSPNeXt. + +## Requirements + +To train with these configs, please install [MMPreTrain](https://github.com/open-mmlab/mmpretrain) first. + +Install by MIM: + +```shell +mim install mmpretrain +``` + +or install by pip: + +```shell +pip install mmpretrain +``` + +## Prepare Dataset + +To pre-train on ImageNet, you need to prepare the dataset first. Please refer to the [guide](https://mmpretrain.readthedocs.io/en/latest/user_guides/dataset_prepare.html#imagenet). + +## How to Train + +You can use the classification config in the same way as the detection config. + +For single-GPU training, run: + +```shell +python tools/train.py \ + ${CONFIG_FILE} \ + [optional arguments] +``` + +For multi-GPU training, run: + +```shell +bash ./tools/dist_train.sh \ + ${CONFIG_FILE} \ + ${GPU_NUM} \ + [optional arguments] +``` + +More details can be found in [user guides](https://mmdetection.readthedocs.io/en/latest/user_guides/train.html). + +## Results and Models + +| Model | resolution | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Download | +| :----------: | :--------: | :-------: | :------: | :-------: | :-------: | :---------------------------------------------------------------------------------------------------------------------------------: | +| CSPNeXt-tiny | 224x224 | 2.73 | 0.34 | 69.44 | 89.45 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e-3a2dd350.pth) | +| CSPNeXt-s | 224x224 | 4.89 | 0.66 | 74.41 | 92.23 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-s_imagenet_600e-ea671761.pth) | +| CSPNeXt-m | 224x224 | 13.05 | 1.93 | 79.27 | 94.79 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-m_8xb256-rsb-a1-600e_in1k-ecb3bbd9.pth) | +| CSPNeXt-l | 224x224 | 27.16 | 4.19 | 81.30 | 95.62 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-l_8xb256-rsb-a1-600e_in1k-6a760974.pth) | +| CSPNeXt-x | 224x224 | 48.85 | 7.76 | 82.10 | 95.69 | [model](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-x_8xb256-rsb-a1-600e_in1k-b3f78edd.pth) | diff --git a/mmdetection/configs/rtmdet/classification/cspnext-l_8xb256-rsb-a1-600e_in1k.py b/mmdetection/configs/rtmdet/classification/cspnext-l_8xb256-rsb-a1-600e_in1k.py new file mode 100644 index 00000000..d2e70539 --- /dev/null +++ b/mmdetection/configs/rtmdet/classification/cspnext-l_8xb256-rsb-a1-600e_in1k.py @@ -0,0 +1,5 @@ +_base_ = './cspnext-s_8xb256-rsb-a1-600e_in1k.py' + +model = dict( + backbone=dict(deepen_factor=1, widen_factor=1), + head=dict(in_channels=1024)) diff --git a/mmdetection/configs/rtmdet/classification/cspnext-m_8xb256-rsb-a1-600e_in1k.py b/mmdetection/configs/rtmdet/classification/cspnext-m_8xb256-rsb-a1-600e_in1k.py new file mode 100644 index 00000000..e1b1352d --- /dev/null +++ b/mmdetection/configs/rtmdet/classification/cspnext-m_8xb256-rsb-a1-600e_in1k.py @@ -0,0 +1,5 @@ +_base_ = './cspnext-s_8xb256-rsb-a1-600e_in1k.py' + +model = dict( + backbone=dict(deepen_factor=0.67, widen_factor=0.75), + head=dict(in_channels=768)) diff --git a/mmdetection/configs/rtmdet/classification/cspnext-s_8xb256-rsb-a1-600e_in1k.py b/mmdetection/configs/rtmdet/classification/cspnext-s_8xb256-rsb-a1-600e_in1k.py new file mode 100644 index 00000000..dcfd2ea4 --- /dev/null +++ b/mmdetection/configs/rtmdet/classification/cspnext-s_8xb256-rsb-a1-600e_in1k.py @@ -0,0 +1,64 @@ +_base_ = [ + 'mmpretrain::_base_/datasets/imagenet_bs256_rsb_a12.py', + 'mmpretrain::_base_/schedules/imagenet_bs2048_rsb.py', + 'mmpretrain::_base_/default_runtime.py' +] + +model = dict( + type='ImageClassifier', + backbone=dict( + type='mmdet.CSPNeXt', + arch='P5', + out_indices=(4, ), + expand_ratio=0.5, + deepen_factor=0.33, + widen_factor=0.5, + channel_attention=True, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='mmdet.SiLU')), + neck=dict(type='GlobalAveragePooling'), + head=dict( + type='LinearClsHead', + num_classes=1000, + in_channels=512, + loss=dict( + type='LabelSmoothLoss', + label_smooth_val=0.1, + mode='original', + loss_weight=1.0), + topk=(1, 5)), + train_cfg=dict(augments=[ + dict(type='Mixup', alpha=0.2), + dict(type='CutMix', alpha=1.0) + ])) + +# dataset settings +train_dataloader = dict(sampler=dict(type='RepeatAugSampler', shuffle=True)) + +# schedule settings +optim_wrapper = dict( + optimizer=dict(weight_decay=0.01), + paramwise_cfg=dict(bias_decay_mult=0., norm_decay_mult=0.), +) + +param_scheduler = [ + # warm up learning rate scheduler + dict( + type='LinearLR', + start_factor=0.0001, + by_epoch=True, + begin=0, + end=5, + # update by iter + convert_to_iter_based=True), + # main learning rate scheduler + dict( + type='CosineAnnealingLR', + T_max=595, + eta_min=1.0e-6, + by_epoch=True, + begin=5, + end=600) +] + +train_cfg = dict(by_epoch=True, max_epochs=600) diff --git a/mmdetection/configs/rtmdet/classification/cspnext-tiny_8xb256-rsb-a1-600e_in1k.py b/mmdetection/configs/rtmdet/classification/cspnext-tiny_8xb256-rsb-a1-600e_in1k.py new file mode 100644 index 00000000..af3170bd --- /dev/null +++ b/mmdetection/configs/rtmdet/classification/cspnext-tiny_8xb256-rsb-a1-600e_in1k.py @@ -0,0 +1,5 @@ +_base_ = './cspnext-s_8xb256-rsb-a1-600e_in1k.py' + +model = dict( + backbone=dict(deepen_factor=0.167, widen_factor=0.375), + head=dict(in_channels=384)) diff --git a/mmdetection/configs/rtmdet/classification/cspnext-x_8xb256-rsb-a1-600e_in1k.py b/mmdetection/configs/rtmdet/classification/cspnext-x_8xb256-rsb-a1-600e_in1k.py new file mode 100644 index 00000000..edec48d7 --- /dev/null +++ b/mmdetection/configs/rtmdet/classification/cspnext-x_8xb256-rsb-a1-600e_in1k.py @@ -0,0 +1,5 @@ +_base_ = './cspnext-s_8xb256-rsb-a1-600e_in1k.py' + +model = dict( + backbone=dict(deepen_factor=1.33, widen_factor=1.25), + head=dict(in_channels=1280)) diff --git a/mmdetection/configs/rtmdet/metafile.yml b/mmdetection/configs/rtmdet/metafile.yml new file mode 100644 index 00000000..a62abcb2 --- /dev/null +++ b/mmdetection/configs/rtmdet/metafile.yml @@ -0,0 +1,242 @@ +Collections: + - Name: RTMDet + Metadata: + Training Data: COCO + Training Techniques: + - AdamW + - Flat Cosine Annealing + Training Resources: 8x A100 GPUs + Architecture: + - CSPNeXt + - CSPNeXtPAFPN + README: configs/rtmdet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v3.0.0rc1/mmdet/models/detectors/rtmdet.py#L6 + Version: v3.0.0rc1 + +Models: + - Name: rtmdet_tiny_8xb32-300e_coco + Alias: + - rtmdet-t + In Collection: RTMDet + Config: configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py + Metadata: + Training Memory (GB): 11.7 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_tiny_8xb32-300e_coco/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth + + - Name: rtmdet_s_8xb32-300e_coco + Alias: + - rtmdet-s + In Collection: RTMDet + Config: configs/rtmdet/rtmdet_s_8xb32-300e_coco.py + Metadata: + Training Memory (GB): 15.9 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_s_8xb32-300e_coco/rtmdet_s_8xb32-300e_coco_20220905_161602-387a891e.pth + + - Name: rtmdet_m_8xb32-300e_coco + Alias: + - rtmdet-m + In Collection: RTMDet + Config: configs/rtmdet/rtmdet_m_8xb32-300e_coco.py + Metadata: + Training Memory (GB): 27.8 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 49.1 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_m_8xb32-300e_coco/rtmdet_m_8xb32-300e_coco_20220719_112220-229f527c.pth + + - Name: rtmdet_l_8xb32-300e_coco + Alias: + - rtmdet-l + In Collection: RTMDet + Config: configs/rtmdet/rtmdet_l_8xb32-300e_coco.py + Metadata: + Training Memory (GB): 43.2 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 51.3 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_l_8xb32-300e_coco/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth + + - Name: rtmdet_x_8xb32-300e_coco + Alias: + - rtmdet-x + In Collection: RTMDet + Config: configs/rtmdet/rtmdet_x_8xb32-300e_coco.py + Metadata: + Training Memory (GB): 61.1 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 52.6 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_x_8xb32-300e_coco/rtmdet_x_8xb32-300e_coco_20220715_230555-cc79b9ae.pth + + - Name: rtmdet_x_p6_4xb8-300e_coco + Alias: + - rtmdet-x_p6 + In Collection: RTMDet + Config: configs/rtmdet/rtmdet_x_p6_4xb8-300e_coco.py + Metadata: + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 54.9 + Weights: https://github.com/orange0-jp/orange-weights/releases/download/v0.1.0rtmdet-p6/rtmdet_x_p6_4xb8-300e_coco-bf32be58.pth + + - Name: rtmdet_l_convnext_b_4xb32-100e_coco + Alias: + - rtmdet-l_convnext_b + In Collection: RTMDet + Config: configs/rtmdet/rtmdet_l_convnext_b_4xb32-100e_coco.py + Metadata: + Epochs: 100 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 53.1 + Weights: https://github.com/orange0-jp/orange-weights/releases/download/v0.1.0rtmdet-swin-convnext/rtmdet_l_convnext_b_4xb32-100e_coco-d4731b3d.pth + + - Name: rtmdet_l_swin_b_4xb32-100e_coco + Alias: + - rtmdet-l_swin_b + In Collection: RTMDet + Config: configs/rtmdet/rtmdet_l_swin_b_4xb32-100e_coco.py + Metadata: + Epochs: 100 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 52.4 + Weights: https://github.com/orange0-jp/orange-weights/releases/download/v0.1.0rtmdet-swin-convnext/rtmdet_l_swin_b_4xb32-100e_coco-0828ce5d.pth + + - Name: rtmdet_l_swin_b_p6_4xb16-100e_coco + Alias: + - rtmdet-l_swin_b_p6 + In Collection: RTMDet + Config: configs/rtmdet/rtmdet_l_swin_b_p6_4xb16-100e_coco.py + Metadata: + Epochs: 100 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 56.4 + Weights: https://github.com/orange0-jp/orange-weights/releases/download/v0.1.0rtmdet-swin-convnext/rtmdet_l_swin_b_p6_4xb16-100e_coco-a1486b6f.pth + + - Name: rtmdet-ins_tiny_8xb32-300e_coco + Alias: + - rtmdet-ins-t + In Collection: RTMDet + Config: configs/rtmdet/rtmdet-ins_tiny_8xb32-300e_coco.py + Metadata: + Training Memory (GB): 18.4 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 35.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_tiny_8xb32-300e_coco/rtmdet-ins_tiny_8xb32-300e_coco_20221130_151727-ec670f7e.pth + + - Name: rtmdet-ins_s_8xb32-300e_coco + Alias: + - rtmdet-ins-s + In Collection: RTMDet + Config: configs/rtmdet/rtmdet-ins_s_8xb32-300e_coco.py + Metadata: + Training Memory (GB): 27.6 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 38.7 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_s_8xb32-300e_coco/rtmdet-ins_s_8xb32-300e_coco_20221121_212604-fdc5d7ec.pth + + - Name: rtmdet-ins_m_8xb32-300e_coco + Alias: + - rtmdet-ins-m + In Collection: RTMDet + Config: configs/rtmdet/rtmdet-ins_m_8xb32-300e_coco.py + Metadata: + Training Memory (GB): 42.5 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 48.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 42.1 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_m_8xb32-300e_coco/rtmdet-ins_m_8xb32-300e_coco_20221123_001039-6eba602e.pth + + - Name: rtmdet-ins_l_8xb32-300e_coco + Alias: + - rtmdet-ins-l + In Collection: RTMDet + Config: configs/rtmdet/rtmdet-ins_l_8xb32-300e_coco.py + Metadata: + Training Memory (GB): 59.8 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 51.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 43.7 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_l_8xb32-300e_coco/rtmdet-ins_l_8xb32-300e_coco_20221124_103237-78d1d652.pth + + - Name: rtmdet-ins_x_8xb16-300e_coco + Alias: + - rtmdet-ins-x + In Collection: RTMDet + Config: configs/rtmdet/rtmdet-ins_x_8xb16-300e_coco.py + Metadata: + Training Memory (GB): 33.7 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 52.4 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 44.6 + Weights: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet-ins_x_8xb16-300e_coco/rtmdet-ins_x_8xb16-300e_coco_20221124_111313-33d4595b.pth diff --git a/mmdetection/configs/rtmdet/rtmdet-ins_l_8xb32-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet-ins_l_8xb32-300e_coco.py new file mode 100644 index 00000000..6b4b9240 --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet-ins_l_8xb32-300e_coco.py @@ -0,0 +1,104 @@ +_base_ = './rtmdet_l_8xb32-300e_coco.py' +model = dict( + bbox_head=dict( + _delete_=True, + type='RTMDetInsSepBNHead', + num_classes=80, + in_channels=256, + stacked_convs=2, + share_conv=True, + pred_kernel_size=1, + feat_channels=256, + act_cfg=dict(type='SiLU', inplace=True), + norm_cfg=dict(type='SyncBN', requires_grad=True), + anchor_generator=dict( + type='MlvlPointGenerator', offset=0, strides=[8, 16, 32]), + bbox_coder=dict(type='DistancePointBBoxCoder'), + loss_cls=dict( + type='QualityFocalLoss', + use_sigmoid=True, + beta=2.0, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + loss_mask=dict( + type='DiceLoss', loss_weight=2.0, eps=5e-6, reduction='mean')), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100, + mask_thr_binary=0.5), +) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict(type='CachedMosaic', img_scale=(640, 640), pad_val=114.0), + dict( + type='RandomResize', + scale=(1280, 1280), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_size=(640, 640), + recompute_bbox=True, + allow_negative_crop=True), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type='CachedMixUp', + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)), + dict(type='PackDetInputs') +] + +train_dataloader = dict(pin_memory=True, dataset=dict(pipeline=train_pipeline)) + +train_pipeline_stage2 = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='RandomResize', + scale=(640, 640), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_size=(640, 640), + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type='PackDetInputs') +] +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='PipelineSwitchHook', + switch_epoch=280, + switch_pipeline=train_pipeline_stage2) +] + +val_evaluator = dict(metric=['bbox', 'segm']) +test_evaluator = val_evaluator diff --git a/mmdetection/configs/rtmdet/rtmdet-ins_m_8xb32-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet-ins_m_8xb32-300e_coco.py new file mode 100644 index 00000000..66da9148 --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet-ins_m_8xb32-300e_coco.py @@ -0,0 +1,6 @@ +_base_ = './rtmdet-ins_l_8xb32-300e_coco.py' + +model = dict( + backbone=dict(deepen_factor=0.67, widen_factor=0.75), + neck=dict(in_channels=[192, 384, 768], out_channels=192, num_csp_blocks=2), + bbox_head=dict(in_channels=192, feat_channels=192)) diff --git a/mmdetection/configs/rtmdet/rtmdet-ins_s_8xb32-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet-ins_s_8xb32-300e_coco.py new file mode 100644 index 00000000..28bc21cc --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet-ins_s_8xb32-300e_coco.py @@ -0,0 +1,80 @@ +_base_ = './rtmdet-ins_l_8xb32-300e_coco.py' +checkpoint = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-s_imagenet_600e.pth' # noqa +model = dict( + backbone=dict( + deepen_factor=0.33, + widen_factor=0.5, + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint)), + neck=dict(in_channels=[128, 256, 512], out_channels=128, num_csp_blocks=1), + bbox_head=dict(in_channels=128, feat_channels=128)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict(type='CachedMosaic', img_scale=(640, 640), pad_val=114.0), + dict( + type='RandomResize', + scale=(1280, 1280), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_size=(640, 640), + recompute_bbox=True, + allow_negative_crop=True), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type='CachedMixUp', + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)), + dict(type='PackDetInputs') +] + +train_pipeline_stage2 = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='RandomResize', + scale=(640, 640), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_size=(640, 640), + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='PipelineSwitchHook', + switch_epoch=280, + switch_pipeline=train_pipeline_stage2) +] diff --git a/mmdetection/configs/rtmdet/rtmdet-ins_tiny_8xb32-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet-ins_tiny_8xb32-300e_coco.py new file mode 100644 index 00000000..954f9116 --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet-ins_tiny_8xb32-300e_coco.py @@ -0,0 +1,48 @@ +_base_ = './rtmdet-ins_s_8xb32-300e_coco.py' + +checkpoint = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e.pth' # noqa + +model = dict( + backbone=dict( + deepen_factor=0.167, + widen_factor=0.375, + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint)), + neck=dict(in_channels=[96, 192, 384], out_channels=96, num_csp_blocks=1), + bbox_head=dict(in_channels=96, feat_channels=96)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='CachedMosaic', + img_scale=(640, 640), + pad_val=114.0, + max_cached_images=20, + random_pop=False), + dict( + type='RandomResize', + scale=(1280, 1280), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(640, 640)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type='CachedMixUp', + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=10, + random_pop=False, + pad_val=(114, 114, 114), + prob=0.5), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1)), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/rtmdet/rtmdet-ins_x_8xb16-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet-ins_x_8xb16-300e_coco.py new file mode 100644 index 00000000..daaa640e --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet-ins_x_8xb16-300e_coco.py @@ -0,0 +1,31 @@ +_base_ = './rtmdet-ins_l_8xb32-300e_coco.py' + +model = dict( + backbone=dict(deepen_factor=1.33, widen_factor=1.25), + neck=dict( + in_channels=[320, 640, 1280], out_channels=320, num_csp_blocks=4), + bbox_head=dict(in_channels=320, feat_channels=320)) + +base_lr = 0.002 + +# optimizer +optim_wrapper = dict(optimizer=dict(lr=base_lr)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0e-5, + by_epoch=False, + begin=0, + end=1000), + dict( + # use cosine lr from 150 to 300 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=_base_.max_epochs // 2, + end=_base_.max_epochs, + T_max=_base_.max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] diff --git a/mmdetection/configs/rtmdet/rtmdet_l_8xb32-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet_l_8xb32-300e_coco.py new file mode 100644 index 00000000..1cce4d89 --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_l_8xb32-300e_coco.py @@ -0,0 +1,179 @@ +_base_ = [ + '../_base_/default_runtime.py', '../_base_/schedules/schedule_1x.py', + '../_base_/datasets/coco_detection.py', './rtmdet_tta.py' +] +model = dict( + type='RTMDet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False, + batch_augments=None), + backbone=dict( + type='CSPNeXt', + arch='P5', + expand_ratio=0.5, + deepen_factor=1, + widen_factor=1, + channel_attention=True, + norm_cfg=dict(type='SyncBN'), + act_cfg=dict(type='SiLU', inplace=True)), + neck=dict( + type='CSPNeXtPAFPN', + in_channels=[256, 512, 1024], + out_channels=256, + num_csp_blocks=3, + expand_ratio=0.5, + norm_cfg=dict(type='SyncBN'), + act_cfg=dict(type='SiLU', inplace=True)), + bbox_head=dict( + type='RTMDetSepBNHead', + num_classes=80, + in_channels=256, + stacked_convs=2, + feat_channels=256, + anchor_generator=dict( + type='MlvlPointGenerator', offset=0, strides=[8, 16, 32]), + bbox_coder=dict(type='DistancePointBBoxCoder'), + loss_cls=dict( + type='QualityFocalLoss', + use_sigmoid=True, + beta=2.0, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + with_objectness=False, + exp_on_reg=True, + share_conv=True, + pred_kernel_size=1, + norm_cfg=dict(type='SyncBN'), + act_cfg=dict(type='SiLU', inplace=True)), + train_cfg=dict( + assigner=dict(type='DynamicSoftLabelAssigner', topk=13), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=30000, + min_bbox_size=0, + score_thr=0.001, + nms=dict(type='nms', iou_threshold=0.65), + max_per_img=300), +) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='CachedMosaic', img_scale=(640, 640), pad_val=114.0), + dict( + type='RandomResize', + scale=(1280, 1280), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(640, 640)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type='CachedMixUp', + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type='PackDetInputs') +] + +train_pipeline_stage2 = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(640, 640), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(640, 640)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(640, 640), keep_ratio=True), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=32, + num_workers=10, + batch_sampler=None, + pin_memory=True, + dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=5, num_workers=10, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +max_epochs = 300 +stage2_num_epochs = 20 +base_lr = 0.004 +interval = 10 + +train_cfg = dict( + max_epochs=max_epochs, + val_interval=interval, + dynamic_intervals=[(max_epochs - stage2_num_epochs, 1)]) + +val_evaluator = dict(proposal_nums=(100, 1, 10)) +test_evaluator = val_evaluator + +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=base_lr, weight_decay=0.05), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0e-5, + by_epoch=False, + begin=0, + end=1000), + dict( + # use cosine lr from 150 to 300 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] + +# hooks +default_hooks = dict( + checkpoint=dict( + interval=interval, + max_keep_ckpts=3 # only keep latest 3 checkpoints + )) +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='PipelineSwitchHook', + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline=train_pipeline_stage2) +] diff --git a/mmdetection/configs/rtmdet/rtmdet_l_convnext_b_4xb32-100e_coco.py b/mmdetection/configs/rtmdet/rtmdet_l_convnext_b_4xb32-100e_coco.py new file mode 100644 index 00000000..85af292b --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_l_convnext_b_4xb32-100e_coco.py @@ -0,0 +1,81 @@ +_base_ = './rtmdet_l_8xb32-300e_coco.py' + +custom_imports = dict( + imports=['mmpretrain.models'], allow_failed_imports=False) + +norm_cfg = dict(type='GN', num_groups=32) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_in21k-pre-3rdparty_in1k-384px_20221219-4570f792.pth' # noqa +model = dict( + type='RTMDet', + data_preprocessor=dict( + _delete_=True, + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + batch_augments=None), + backbone=dict( + _delete_=True, + type='mmpretrain.ConvNeXt', + arch='base', + out_indices=[1, 2, 3], + drop_path_rate=0.7, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + with_cp=True, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + neck=dict(in_channels=[256, 512, 1024], norm_cfg=norm_cfg), + bbox_head=dict(norm_cfg=norm_cfg)) + +max_epochs = 100 +stage2_num_epochs = 10 +interval = 10 +base_lr = 0.001 + +train_cfg = dict( + max_epochs=max_epochs, + val_interval=interval, + dynamic_intervals=[(max_epochs - stage2_num_epochs, 1)]) + +optim_wrapper = dict( + constructor='LearningRateDecayOptimizerConstructor', + paramwise_cfg={ + 'decay_rate': 0.8, + 'decay_type': 'layer_wise', + 'num_layers': 12 + }, + optimizer=dict(lr=base_lr)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0e-5, + by_epoch=False, + begin=0, + end=1000), + dict( + # use cosine lr from 50 to 100 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='PipelineSwitchHook', + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline={{_base_.train_pipeline_stage2}}) +] diff --git a/mmdetection/configs/rtmdet/rtmdet_l_swin_b_4xb32-100e_coco.py b/mmdetection/configs/rtmdet/rtmdet_l_swin_b_4xb32-100e_coco.py new file mode 100644 index 00000000..84b0e0fa --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_l_swin_b_4xb32-100e_coco.py @@ -0,0 +1,78 @@ +_base_ = './rtmdet_l_8xb32-300e_coco.py' + +norm_cfg = dict(type='GN', num_groups=32) +checkpoint = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window12_384_22k.pth' # noqa +model = dict( + type='RTMDet', + data_preprocessor=dict( + _delete_=True, + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + batch_augments=None), + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=True, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=checkpoint)), + neck=dict(in_channels=[256, 512, 1024], norm_cfg=norm_cfg), + bbox_head=dict(norm_cfg=norm_cfg)) + +max_epochs = 100 +stage2_num_epochs = 10 +interval = 10 +base_lr = 0.001 + +train_cfg = dict( + max_epochs=max_epochs, + val_interval=interval, + dynamic_intervals=[(max_epochs - stage2_num_epochs, 1)]) + +optim_wrapper = dict(optimizer=dict(lr=base_lr)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0e-5, + by_epoch=False, + begin=0, + end=1000), + dict( + # use cosine lr from 50 to 100 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='PipelineSwitchHook', + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline={{_base_.train_pipeline_stage2}}) +] diff --git a/mmdetection/configs/rtmdet/rtmdet_l_swin_b_p6_4xb16-100e_coco.py b/mmdetection/configs/rtmdet/rtmdet_l_swin_b_p6_4xb16-100e_coco.py new file mode 100644 index 00000000..37d4215c --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_l_swin_b_p6_4xb16-100e_coco.py @@ -0,0 +1,114 @@ +_base_ = './rtmdet_l_swin_b_4xb32-100e_coco.py' + +model = dict( + backbone=dict( + depths=[2, 2, 18, 2, 1], + num_heads=[4, 8, 16, 32, 64], + strides=(4, 2, 2, 2, 2), + out_indices=(1, 2, 3, 4)), + neck=dict(in_channels=[256, 512, 1024, 2048]), + bbox_head=dict( + anchor_generator=dict( + type='MlvlPointGenerator', offset=0, strides=[8, 16, 32, 64]))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='CachedMosaic', img_scale=(1280, 1280), pad_val=114.0), + dict( + type='RandomResize', + scale=(2560, 2560), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(1280, 1280)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(1280, 1280), pad_val=dict(img=(114, 114, 114))), + dict( + type='CachedMixUp', + img_scale=(1280, 1280), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type='PackDetInputs') +] + +train_pipeline_stage2 = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(1280, 1280), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(1280, 1280)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(1280, 1280), pad_val=dict(img=(114, 114, 114))), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1280, 1280), keep_ratio=True), + dict(type='Pad', size=(1280, 1280), pad_val=dict(img=(114, 114, 114))), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=16, num_workers=20, dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(num_workers=20, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +max_epochs = 100 +stage2_num_epochs = 10 + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='PipelineSwitchHook', + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline=train_pipeline_stage2) +] + +img_scales = [(1280, 1280), (640, 640), (1920, 1920)] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale=s, keep_ratio=True) + for s in img_scales + ], + [ + # ``RandomFlip`` must be placed before ``Pad``, otherwise + # bounding box coordinates after flipping cannot be + # recovered correctly. + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], + [ + dict( + type='Pad', + size=(1920, 1920), + pad_val=dict(img=(114, 114, 114))), + ], + [dict(type='LoadAnnotations', with_bbox=True)], + [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction')) + ] + ]) +] diff --git a/mmdetection/configs/rtmdet/rtmdet_m_8xb32-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet_m_8xb32-300e_coco.py new file mode 100644 index 00000000..c83f5a60 --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_m_8xb32-300e_coco.py @@ -0,0 +1,6 @@ +_base_ = './rtmdet_l_8xb32-300e_coco.py' + +model = dict( + backbone=dict(deepen_factor=0.67, widen_factor=0.75), + neck=dict(in_channels=[192, 384, 768], out_channels=192, num_csp_blocks=2), + bbox_head=dict(in_channels=192, feat_channels=192)) diff --git a/mmdetection/configs/rtmdet/rtmdet_s_8xb32-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet_s_8xb32-300e_coco.py new file mode 100644 index 00000000..cbf76247 --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_s_8xb32-300e_coco.py @@ -0,0 +1,62 @@ +_base_ = './rtmdet_l_8xb32-300e_coco.py' +checkpoint = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-s_imagenet_600e.pth' # noqa +model = dict( + backbone=dict( + deepen_factor=0.33, + widen_factor=0.5, + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint)), + neck=dict(in_channels=[128, 256, 512], out_channels=128, num_csp_blocks=1), + bbox_head=dict(in_channels=128, feat_channels=128, exp_on_reg=False)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='CachedMosaic', img_scale=(640, 640), pad_val=114.0), + dict( + type='RandomResize', + scale=(1280, 1280), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(640, 640)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type='CachedMixUp', + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type='PackDetInputs') +] + +train_pipeline_stage2 = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(640, 640), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(640, 640)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='PipelineSwitchHook', + switch_epoch=280, + switch_pipeline=train_pipeline_stage2) +] diff --git a/mmdetection/configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py new file mode 100644 index 00000000..a686f4a7 --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py @@ -0,0 +1,43 @@ +_base_ = './rtmdet_s_8xb32-300e_coco.py' + +checkpoint = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e.pth' # noqa + +model = dict( + backbone=dict( + deepen_factor=0.167, + widen_factor=0.375, + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint)), + neck=dict(in_channels=[96, 192, 384], out_channels=96, num_csp_blocks=1), + bbox_head=dict(in_channels=96, feat_channels=96, exp_on_reg=False)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='CachedMosaic', + img_scale=(640, 640), + pad_val=114.0, + max_cached_images=20, + random_pop=False), + dict( + type='RandomResize', + scale=(1280, 1280), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(640, 640)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type='CachedMixUp', + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=10, + random_pop=False, + pad_val=(114, 114, 114), + prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/rtmdet/rtmdet_tta.py b/mmdetection/configs/rtmdet/rtmdet_tta.py new file mode 100644 index 00000000..6dde36de --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_tta.py @@ -0,0 +1,36 @@ +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict(type='nms', iou_threshold=0.6), max_per_img=100)) + +img_scales = [(640, 640), (320, 320), (960, 960)] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale=s, keep_ratio=True) + for s in img_scales + ], + [ + # ``RandomFlip`` must be placed before ``Pad``, otherwise + # bounding box coordinates after flipping cannot be + # recovered correctly. + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], + [ + dict( + type='Pad', + size=(960, 960), + pad_val=dict(img=(114, 114, 114))), + ], + [dict(type='LoadAnnotations', with_bbox=True)], + [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction')) + ] + ]) +] diff --git a/mmdetection/configs/rtmdet/rtmdet_x_8xb32-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet_x_8xb32-300e_coco.py new file mode 100644 index 00000000..16a33632 --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_x_8xb32-300e_coco.py @@ -0,0 +1,7 @@ +_base_ = './rtmdet_l_8xb32-300e_coco.py' + +model = dict( + backbone=dict(deepen_factor=1.33, widen_factor=1.25), + neck=dict( + in_channels=[320, 640, 1280], out_channels=320, num_csp_blocks=4), + bbox_head=dict(in_channels=320, feat_channels=320)) diff --git a/mmdetection/configs/rtmdet/rtmdet_x_p6_4xb8-300e_coco.py b/mmdetection/configs/rtmdet/rtmdet_x_p6_4xb8-300e_coco.py new file mode 100644 index 00000000..d1bb7fa6 --- /dev/null +++ b/mmdetection/configs/rtmdet/rtmdet_x_p6_4xb8-300e_coco.py @@ -0,0 +1,132 @@ +_base_ = './rtmdet_x_8xb32-300e_coco.py' + +model = dict( + backbone=dict(arch='P6', out_indices=(2, 3, 4, 5)), + neck=dict(in_channels=[320, 640, 960, 1280]), + bbox_head=dict( + anchor_generator=dict( + type='MlvlPointGenerator', offset=0, strides=[8, 16, 32, 64]))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='CachedMosaic', img_scale=(1280, 1280), pad_val=114.0), + dict( + type='RandomResize', + scale=(2560, 2560), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(1280, 1280)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(1280, 1280), pad_val=dict(img=(114, 114, 114))), + dict( + type='CachedMixUp', + img_scale=(1280, 1280), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type='PackDetInputs') +] + +train_pipeline_stage2 = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(1280, 1280), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(1280, 1280)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=(1280, 1280), pad_val=dict(img=(114, 114, 114))), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1280, 1280), keep_ratio=True), + dict(type='Pad', size=(1280, 1280), pad_val=dict(img=(114, 114, 114))), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=8, num_workers=20, dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=5, num_workers=20, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +max_epochs = 300 +stage2_num_epochs = 20 + +base_lr = 0.004 * 32 / 256 +optim_wrapper = dict(optimizer=dict(lr=base_lr)) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0e-5, + by_epoch=False, + begin=0, + end=1000), + dict( + # use cosine lr from 150 to 300 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='PipelineSwitchHook', + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline=train_pipeline_stage2) +] + +img_scales = [(1280, 1280), (640, 640), (1920, 1920)] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale=s, keep_ratio=True) + for s in img_scales + ], + [ + # ``RandomFlip`` must be placed before ``Pad``, otherwise + # bounding box coordinates after flipping cannot be + # recovered correctly. + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], + [ + dict( + type='Pad', + size=(1920, 1920), + pad_val=dict(img=(114, 114, 114))), + ], + [dict(type='LoadAnnotations', with_bbox=True)], + [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction')) + ] + ]) +] diff --git a/mmdetection/configs/sabl/README.md b/mmdetection/configs/sabl/README.md new file mode 100644 index 00000000..c730729c --- /dev/null +++ b/mmdetection/configs/sabl/README.md @@ -0,0 +1,47 @@ +# SABL + +> [Side-Aware Boundary Localization for More Precise Object Detection](https://arxiv.org/abs/1912.04260) + + + +## Abstract + +Current object detection frameworks mainly rely on bounding box regression to localize objects. Despite the remarkable progress in recent years, the precision of bounding box regression remains unsatisfactory, hence limiting performance in object detection. We observe that precise localization requires careful placement of each side of the bounding box. However, the mainstream approach, which focuses on predicting centers and sizes, is not the most effective way to accomplish this task, especially when there exists displacements with large variance between the anchors and the targets. In this paper, we propose an alternative approach, named as Side-Aware Boundary Localization (SABL), where each side of the bounding box is respectively localized with a dedicated network branch. To tackle the difficulty of precise localization in the presence of displacements with large variance, we further propose a two-step localization scheme, which first predicts a range of movement through bucket prediction and then pinpoints the precise position within the predicted bucket. We test the proposed method on both two-stage and single-stage detection frameworks. Replacing the standard bounding box regression branch with the proposed design leads to significant improvements on Faster R-CNN, RetinaNet, and Cascade R-CNN, by 3.0%, 1.7%, and 0.9%, respectively. + +
    + +
    + +## Results and Models + +The results on COCO 2017 val is shown in the below table. (results on test-dev are usually slightly higher than val). +Single-scale testing (1333x800) is adopted in all results. + +| Method | Backbone | Lr schd | ms-train | box AP | Config | Download | +| :----------------: | :-------: | :-----: | :------: | :----: | :-----------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| SABL Faster R-CNN | R-50-FPN | 1x | N | 39.9 | [config](./sabl-faster-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_faster_rcnn_r50_fpn_1x_coco/sabl_faster_rcnn_r50_fpn_1x_coco-e867595b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_faster_rcnn_r50_fpn_1x_coco/20200830_130324.log.json) | +| SABL Faster R-CNN | R-101-FPN | 1x | N | 41.7 | [config](./sabl-faster-rcnn_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_faster_rcnn_r101_fpn_1x_coco/sabl_faster_rcnn_r101_fpn_1x_coco-f804c6c1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_faster_rcnn_r101_fpn_1x_coco/20200830_183949.log.json) | +| SABL Cascade R-CNN | R-50-FPN | 1x | N | 41.6 | [config](./sabl-cascade-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_cascade_rcnn_r50_fpn_1x_coco/sabl_cascade_rcnn_r50_fpn_1x_coco-e1748e5e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_cascade_rcnn_r50_fpn_1x_coco/20200831_033726.log.json) | +| SABL Cascade R-CNN | R-101-FPN | 1x | N | 43.0 | [config](./sabl-cascade-rcnn_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_cascade_rcnn_r101_fpn_1x_coco/sabl_cascade_rcnn_r101_fpn_1x_coco-2b83e87c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_cascade_rcnn_r101_fpn_1x_coco/20200831_141745.log.json) | + +| Method | Backbone | GN | Lr schd | ms-train | box AP | Config | Download | +| :------------: | :-------: | :-: | :-----: | :---------: | :----: | :----------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| SABL RetinaNet | R-50-FPN | N | 1x | N | 37.7 | [config](./sabl-retinanet_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r50_fpn_1x_coco/sabl_retinanet_r50_fpn_1x_coco-6c54fd4f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r50_fpn_1x_coco/20200830_053451.log.json) | +| SABL RetinaNet | R-50-FPN | Y | 1x | N | 38.8 | [config](./sabl-retinanet_r50-gn_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r50_fpn_gn_1x_coco/sabl_retinanet_r50_fpn_gn_1x_coco-e16dfcf1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r50_fpn_gn_1x_coco/20200831_141955.log.json) | +| SABL RetinaNet | R-101-FPN | N | 1x | N | 39.7 | [config](./sabl-retinanet_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_1x_coco/sabl_retinanet_r101_fpn_1x_coco-42026904.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_1x_coco/20200831_034256.log.json) | +| SABL RetinaNet | R-101-FPN | Y | 1x | N | 40.5 | [config](./sabl-retinanet_r101-gn_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_gn_1x_coco/sabl_retinanet_r101_fpn_gn_1x_coco-40a893e8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_gn_1x_coco/20200830_201422.log.json) | +| SABL RetinaNet | R-101-FPN | Y | 2x | Y (640~800) | 42.9 | [config](./sabl-retinanet_r101-gn_fpn_ms-640-800-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_gn_2x_ms_640_800_coco/sabl_retinanet_r101_fpn_gn_2x_ms_640_800_coco-1e63382c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_gn_2x_ms_640_800_coco/20200830_144807.log.json) | +| SABL RetinaNet | R-101-FPN | Y | 2x | Y (480~960) | 43.6 | [config](./sabl-retinanet_r101-gn_fpn_ms-480-960-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_gn_2x_ms_480_960_coco/sabl_retinanet_r101_fpn_gn_2x_ms_480_960_coco-5342f857.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_gn_2x_ms_480_960_coco/20200830_164537.log.json) | + +## Citation + +We provide config files to reproduce the object detection results in the ECCV 2020 Spotlight paper for [Side-Aware Boundary Localization for More Precise Object Detection](https://arxiv.org/abs/1912.04260). + +```latex +@inproceedings{Wang_2020_ECCV, + title = {Side-Aware Boundary Localization for More Precise Object Detection}, + author = {Jiaqi Wang and Wenwei Zhang and Yuhang Cao and Kai Chen and Jiangmiao Pang and Tao Gong and Jianping Shi and Chen Change Loy and Dahua Lin}, + booktitle = {ECCV}, + year = {2020} +} +``` diff --git a/mmdetection/configs/sabl/metafile.yml b/mmdetection/configs/sabl/metafile.yml new file mode 100644 index 00000000..632b869c --- /dev/null +++ b/mmdetection/configs/sabl/metafile.yml @@ -0,0 +1,140 @@ +Collections: + - Name: SABL + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - ResNet + - SABL + Paper: + URL: https://arxiv.org/abs/1912.04260 + Title: 'Side-Aware Boundary Localization for More Precise Object Detection' + README: configs/sabl/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.4.0/mmdet/models/roi_heads/bbox_heads/sabl_head.py#L14 + Version: v2.4.0 + +Models: + - Name: sabl-faster-rcnn_r50_fpn_1x_coco + In Collection: SABL + Config: configs/sabl/sabl-faster-rcnn_r50_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_faster_rcnn_r50_fpn_1x_coco/sabl_faster_rcnn_r50_fpn_1x_coco-e867595b.pth + + - Name: sabl-faster-rcnn_r101_fpn_1x_coco + In Collection: SABL + Config: configs/sabl/sabl-faster-rcnn_r101_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_faster_rcnn_r101_fpn_1x_coco/sabl_faster_rcnn_r101_fpn_1x_coco-f804c6c1.pth + + - Name: sabl-cascade-rcnn_r50_fpn_1x_coco + In Collection: SABL + Config: configs/sabl/sabl-cascade-rcnn_r50_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_cascade_rcnn_r50_fpn_1x_coco/sabl_cascade_rcnn_r50_fpn_1x_coco-e1748e5e.pth + + - Name: sabl-cascade-rcnn_r101_fpn_1x_coco + In Collection: SABL + Config: configs/sabl/sabl-cascade-rcnn_r101_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_cascade_rcnn_r101_fpn_1x_coco/sabl_cascade_rcnn_r101_fpn_1x_coco-2b83e87c.pth + + - Name: sabl-retinanet_r50_fpn_1x_coco + In Collection: SABL + Config: configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r50_fpn_1x_coco/sabl_retinanet_r50_fpn_1x_coco-6c54fd4f.pth + + - Name: sabl-retinanet_r50-gn_fpn_1x_coco + In Collection: SABL + Config: configs/sabl/sabl-retinanet_r50-gn_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 38.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r50_fpn_gn_1x_coco/sabl_retinanet_r50_fpn_gn_1x_coco-e16dfcf1.pth + + - Name: sabl-retinanet_r101_fpn_1x_coco + In Collection: SABL + Config: configs/sabl/sabl-retinanet_r101_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 39.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_1x_coco/sabl_retinanet_r101_fpn_1x_coco-42026904.pth + + - Name: sabl-retinanet_r101-gn_fpn_1x_coco + In Collection: SABL + Config: configs/sabl/sabl-retinanet_r101-gn_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_gn_1x_coco/sabl_retinanet_r101_fpn_gn_1x_coco-40a893e8.pth + + - Name: sabl-retinanet_r101-gn_fpn_ms-640-800-2x_coco + In Collection: SABL + Config: configs/sabl/sabl-retinanet_r101-gn_fpn_ms-640-800-2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_gn_2x_ms_640_800_coco/sabl_retinanet_r101_fpn_gn_2x_ms_640_800_coco-1e63382c.pth + + - Name: sabl-retinanet_r101-gn_fpn_ms-480-960-2x_coco + In Collection: SABL + Config: configs/sabl/sabl-retinanet_r101-gn_fpn_ms-480-960-2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sabl/sabl_retinanet_r101_fpn_gn_2x_ms_480_960_coco/sabl_retinanet_r101_fpn_gn_2x_ms_480_960_coco-5342f857.pth diff --git a/mmdetection/configs/sabl/sabl-cascade-rcnn_r101_fpn_1x_coco.py b/mmdetection/configs/sabl/sabl-cascade-rcnn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..404e7fcb --- /dev/null +++ b/mmdetection/configs/sabl/sabl-cascade-rcnn_r101_fpn_1x_coco.py @@ -0,0 +1,90 @@ +_base_ = [ + '../_base_/models/cascade-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + roi_head=dict(bbox_head=[ + dict( + type='SABLHead', + num_classes=80, + cls_in_channels=256, + reg_in_channels=256, + roi_feat_size=7, + reg_feat_up_ratio=2, + reg_pre_kernel=3, + reg_post_kernel=3, + reg_pre_num=2, + reg_post_num=1, + cls_out_channels=1024, + reg_offset_out_channels=256, + reg_cls_out_channels=256, + num_cls_fcs=1, + num_reg_fcs=0, + reg_class_agnostic=True, + norm_cfg=None, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=1.7), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox_reg=dict(type='SmoothL1Loss', beta=0.1, + loss_weight=1.0)), + dict( + type='SABLHead', + num_classes=80, + cls_in_channels=256, + reg_in_channels=256, + roi_feat_size=7, + reg_feat_up_ratio=2, + reg_pre_kernel=3, + reg_post_kernel=3, + reg_pre_num=2, + reg_post_num=1, + cls_out_channels=1024, + reg_offset_out_channels=256, + reg_cls_out_channels=256, + num_cls_fcs=1, + num_reg_fcs=0, + reg_class_agnostic=True, + norm_cfg=None, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=1.5), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox_reg=dict(type='SmoothL1Loss', beta=0.1, + loss_weight=1.0)), + dict( + type='SABLHead', + num_classes=80, + cls_in_channels=256, + reg_in_channels=256, + roi_feat_size=7, + reg_feat_up_ratio=2, + reg_pre_kernel=3, + reg_post_kernel=3, + reg_pre_num=2, + reg_post_num=1, + cls_out_channels=1024, + reg_offset_out_channels=256, + reg_cls_out_channels=256, + num_cls_fcs=1, + num_reg_fcs=0, + reg_class_agnostic=True, + norm_cfg=None, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=1.3), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox_reg=dict(type='SmoothL1Loss', beta=0.1, loss_weight=1.0)) + ])) diff --git a/mmdetection/configs/sabl/sabl-cascade-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/sabl/sabl-cascade-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..69c59ca2 --- /dev/null +++ b/mmdetection/configs/sabl/sabl-cascade-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,86 @@ +_base_ = [ + '../_base_/models/cascade-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + roi_head=dict(bbox_head=[ + dict( + type='SABLHead', + num_classes=80, + cls_in_channels=256, + reg_in_channels=256, + roi_feat_size=7, + reg_feat_up_ratio=2, + reg_pre_kernel=3, + reg_post_kernel=3, + reg_pre_num=2, + reg_post_num=1, + cls_out_channels=1024, + reg_offset_out_channels=256, + reg_cls_out_channels=256, + num_cls_fcs=1, + num_reg_fcs=0, + reg_class_agnostic=True, + norm_cfg=None, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=1.7), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox_reg=dict(type='SmoothL1Loss', beta=0.1, + loss_weight=1.0)), + dict( + type='SABLHead', + num_classes=80, + cls_in_channels=256, + reg_in_channels=256, + roi_feat_size=7, + reg_feat_up_ratio=2, + reg_pre_kernel=3, + reg_post_kernel=3, + reg_pre_num=2, + reg_post_num=1, + cls_out_channels=1024, + reg_offset_out_channels=256, + reg_cls_out_channels=256, + num_cls_fcs=1, + num_reg_fcs=0, + reg_class_agnostic=True, + norm_cfg=None, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=1.5), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox_reg=dict(type='SmoothL1Loss', beta=0.1, + loss_weight=1.0)), + dict( + type='SABLHead', + num_classes=80, + cls_in_channels=256, + reg_in_channels=256, + roi_feat_size=7, + reg_feat_up_ratio=2, + reg_pre_kernel=3, + reg_post_kernel=3, + reg_pre_num=2, + reg_post_num=1, + cls_out_channels=1024, + reg_offset_out_channels=256, + reg_cls_out_channels=256, + num_cls_fcs=1, + num_reg_fcs=0, + reg_class_agnostic=True, + norm_cfg=None, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=1.3), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox_reg=dict(type='SmoothL1Loss', beta=0.1, loss_weight=1.0)) + ])) diff --git a/mmdetection/configs/sabl/sabl-faster-rcnn_r101_fpn_1x_coco.py b/mmdetection/configs/sabl/sabl-faster-rcnn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..d1bf8b9c --- /dev/null +++ b/mmdetection/configs/sabl/sabl-faster-rcnn_r101_fpn_1x_coco.py @@ -0,0 +1,38 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + roi_head=dict( + bbox_head=dict( + _delete_=True, + type='SABLHead', + num_classes=80, + cls_in_channels=256, + reg_in_channels=256, + roi_feat_size=7, + reg_feat_up_ratio=2, + reg_pre_kernel=3, + reg_post_kernel=3, + reg_pre_num=2, + reg_post_num=1, + cls_out_channels=1024, + reg_offset_out_channels=256, + reg_cls_out_channels=256, + num_cls_fcs=1, + num_reg_fcs=0, + reg_class_agnostic=True, + norm_cfg=None, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=1.7), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox_reg=dict(type='SmoothL1Loss', beta=0.1, + loss_weight=1.0)))) diff --git a/mmdetection/configs/sabl/sabl-faster-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/sabl/sabl-faster-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..a727bd6d --- /dev/null +++ b/mmdetection/configs/sabl/sabl-faster-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,34 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + roi_head=dict( + bbox_head=dict( + _delete_=True, + type='SABLHead', + num_classes=80, + cls_in_channels=256, + reg_in_channels=256, + roi_feat_size=7, + reg_feat_up_ratio=2, + reg_pre_kernel=3, + reg_post_kernel=3, + reg_pre_num=2, + reg_post_num=1, + cls_out_channels=1024, + reg_offset_out_channels=256, + reg_cls_out_channels=256, + num_cls_fcs=1, + num_reg_fcs=0, + reg_class_agnostic=True, + norm_cfg=None, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=1.7), + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox_reg=dict(type='SmoothL1Loss', beta=0.1, + loss_weight=1.0)))) diff --git a/mmdetection/configs/sabl/sabl-retinanet_r101-gn_fpn_1x_coco.py b/mmdetection/configs/sabl/sabl-retinanet_r101-gn_fpn_1x_coco.py new file mode 100644 index 00000000..f181ad68 --- /dev/null +++ b/mmdetection/configs/sabl/sabl-retinanet_r101-gn_fpn_1x_coco.py @@ -0,0 +1,57 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + bbox_head=dict( + _delete_=True, + type='SABLRetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[4], + strides=[8, 16, 32, 64, 128]), + norm_cfg=norm_cfg, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.5), + loss_bbox_reg=dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.5)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0.0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/sabl/sabl-retinanet_r101-gn_fpn_ms-480-960-2x_coco.py b/mmdetection/configs/sabl/sabl-retinanet_r101-gn_fpn_ms-480-960-2x_coco.py new file mode 100644 index 00000000..dc7209ae --- /dev/null +++ b/mmdetection/configs/sabl/sabl-retinanet_r101-gn_fpn_ms-480-960-2x_coco.py @@ -0,0 +1,68 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] +# model settings +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + bbox_head=dict( + _delete_=True, + type='SABLRetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[4], + strides=[8, 16, 32, 64, 128]), + norm_cfg=norm_cfg, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.5), + loss_bbox_reg=dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.5)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0.0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False)) +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 480), (1333, 960)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/sabl/sabl-retinanet_r101-gn_fpn_ms-640-800-2x_coco.py b/mmdetection/configs/sabl/sabl-retinanet_r101-gn_fpn_ms-640-800-2x_coco.py new file mode 100644 index 00000000..ac5f6d98 --- /dev/null +++ b/mmdetection/configs/sabl/sabl-retinanet_r101-gn_fpn_ms-640-800-2x_coco.py @@ -0,0 +1,68 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] +# model settings +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + bbox_head=dict( + _delete_=True, + type='SABLRetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[4], + strides=[8, 16, 32, 64, 128]), + norm_cfg=norm_cfg, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.5), + loss_bbox_reg=dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.5)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0.0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False)) +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 480), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/sabl/sabl-retinanet_r101_fpn_1x_coco.py b/mmdetection/configs/sabl/sabl-retinanet_r101_fpn_1x_coco.py new file mode 100644 index 00000000..409695b5 --- /dev/null +++ b/mmdetection/configs/sabl/sabl-retinanet_r101_fpn_1x_coco.py @@ -0,0 +1,55 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + bbox_head=dict( + _delete_=True, + type='SABLRetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[4], + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.5), + loss_bbox_reg=dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.5)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0.0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/sabl/sabl-retinanet_r50-gn_fpn_1x_coco.py b/mmdetection/configs/sabl/sabl-retinanet_r50-gn_fpn_1x_coco.py new file mode 100644 index 00000000..4facdb6a --- /dev/null +++ b/mmdetection/configs/sabl/sabl-retinanet_r50-gn_fpn_1x_coco.py @@ -0,0 +1,53 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + bbox_head=dict( + _delete_=True, + type='SABLRetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[4], + strides=[8, 16, 32, 64, 128]), + norm_cfg=norm_cfg, + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.5), + loss_bbox_reg=dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.5)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0.0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py b/mmdetection/configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py new file mode 100644 index 00000000..9073d6f0 --- /dev/null +++ b/mmdetection/configs/sabl/sabl-retinanet_r50_fpn_1x_coco.py @@ -0,0 +1,51 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + bbox_head=dict( + _delete_=True, + type='SABLRetinaHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + approx_anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + square_anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[4], + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.5), + loss_bbox_reg=dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.5)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='ApproxMaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0.0, + ignore_iof_thr=-1), + allowed_border=-1, + pos_weight=-1, + debug=False)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/scnet/README.md b/mmdetection/configs/scnet/README.md new file mode 100644 index 00000000..08dbfa87 --- /dev/null +++ b/mmdetection/configs/scnet/README.md @@ -0,0 +1,63 @@ +# SCNet + +> [SCNet: Training Inference Sample Consistency for Instance Segmentation](https://arxiv.org/abs/2012.10150) + + + +## Abstract + + + +Cascaded architectures have brought significant performance improvement in object detection and instance segmentation. However, there are lingering issues regarding the disparity in the Intersection-over-Union (IoU) distribution of the samples between training and inference. This disparity can potentially exacerbate detection accuracy. This paper proposes an architecture referred to as Sample Consistency Network (SCNet) to ensure that the IoU distribution of the samples at training time is close to that at inference time. Furthermore, SCNet incorporates feature relay and utilizes global contextual information to further reinforce the reciprocal relationships among classifying, detecting, and segmenting sub-tasks. Extensive experiments on the standard COCO dataset reveal the effectiveness of the proposed method over multiple evaluation metrics, including box AP, mask AP, and inference speed. In particular, while running 38% faster, the proposed SCNet improves the AP of the box and mask predictions by respectively 1.3 and 2.3 points compared to the strong Cascade Mask R-CNN baseline. + +
    + +
    + +## Dataset + +SCNet requires COCO and [COCO-stuff](http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip) dataset for training. You need to download and extract it in the COCO dataset path. +The directory should be like this. + +```none +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +| | ├── stuffthingmaps +``` + +## Results and Models + +The results on COCO 2017val are shown in the below table. (results on test-dev are usually slightly higher than val) + +| Backbone | Style | Lr schd | Mem (GB) | Inf speed (fps) | box AP | mask AP | TTA box AP | TTA mask AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :-------------: | :----: | :-----: | :--------: | :---------: | :------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-FPN | pytorch | 1x | 7.0 | 6.2 | 43.5 | 39.2 | 44.8 | 40.9 | [config](./scnet_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r50_fpn_1x_coco/scnet_r50_fpn_1x_coco-c3f09857.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r50_fpn_1x_coco/scnet_r50_fpn_1x_coco_20210117_192725.log.json) | +| R-50-FPN | pytorch | 20e | 7.0 | 6.2 | 44.5 | 40.0 | 45.8 | 41.5 | [config](./scnet_r50_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r50_fpn_20e_coco/scnet_r50_fpn_20e_coco-a569f645.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r50_fpn_20e_coco/scnet_r50_fpn_20e_coco_20210116_060148.log.json) | +| R-101-FPN | pytorch | 20e | 8.9 | 5.8 | 45.8 | 40.9 | 47.3 | 42.7 | [config](./scnet_r101_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r101_fpn_20e_coco/scnet_r101_fpn_20e_coco-294e312c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r101_fpn_20e_coco/scnet_r101_fpn_20e_coco_20210118_175824.log.json) | +| X-101-64x4d-FPN | pytorch | 20e | 13.2 | 4.9 | 47.5 | 42.3 | 48.9 | 44.0 | [config](./scnet_x101-64x4d_fpn_20e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_x101_64x4d_fpn_20e_coco/scnet_x101_64x4d_fpn_20e_coco-fb09dec9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_x101_64x4d_fpn_20e_coco/scnet_x101_64x4d_fpn_20e_coco_20210120_045959.log.json) | + +### Notes + +- Training hyper-parameters are identical to those of [HTC](https://github.com/open-mmlab/mmdetection/tree/main/configs/htc). +- TTA means Test Time Augmentation, which applies horizontal flip and multi-scale testing. Refer to [config](./scnet_r50_fpn_1x_coco.py). + +## Citation + +We provide the code for reproducing experiment results of [SCNet](https://arxiv.org/abs/2012.10150). + +```latex +@inproceedings{vu2019cascade, + title={SCNet: Training Inference Sample Consistency for Instance Segmentation}, + author={Vu, Thang and Haeyong, Kang and Yoo, Chang D}, + booktitle={AAAI}, + year={2021} +} +``` diff --git a/mmdetection/configs/scnet/metafile.yml b/mmdetection/configs/scnet/metafile.yml new file mode 100644 index 00000000..936d3896 --- /dev/null +++ b/mmdetection/configs/scnet/metafile.yml @@ -0,0 +1,116 @@ +Collections: + - Name: SCNet + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - ResNet + - SCNet + Paper: + URL: https://arxiv.org/abs/2012.10150 + Title: 'SCNet: Training Inference Sample Consistency for Instance Segmentation' + README: configs/scnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.9.0/mmdet/models/detectors/scnet.py#L6 + Version: v2.9.0 + +Models: + - Name: scnet_r50_fpn_1x_coco + In Collection: SCNet + Config: configs/scnet/scnet_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.0 + inference time (ms/im): + - value: 161.29 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r50_fpn_1x_coco/scnet_r50_fpn_1x_coco-c3f09857.pth + + - Name: scnet_r50_fpn_20e_coco + In Collection: SCNet + Config: configs/scnet/scnet_r50_fpn_20e_coco.py + Metadata: + Training Memory (GB): 7.0 + inference time (ms/im): + - value: 161.29 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r50_fpn_20e_coco/scnet_r50_fpn_20e_coco-a569f645.pth + + - Name: scnet_r101_fpn_20e_coco + In Collection: SCNet + Config: configs/scnet/scnet_r101_fpn_20e_coco.py + Metadata: + Training Memory (GB): 8.9 + inference time (ms/im): + - value: 172.41 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_r101_fpn_20e_coco/scnet_r101_fpn_20e_coco-294e312c.pth + + - Name: scnet_x101-64x4d_fpn_20e_coco + In Collection: SCNet + Config: configs/scnet/scnet_x101-64x4d_fpn_20e_coco.py + Metadata: + Training Memory (GB): 13.2 + inference time (ms/im): + - value: 204.08 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (800, 1333) + Epochs: 20 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 42.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/scnet/scnet_x101_64x4d_fpn_20e_coco/scnet_x101_64x4d_fpn_20e_coco-fb09dec9.pth diff --git a/mmdetection/configs/scnet/scnet_r101_fpn_20e_coco.py b/mmdetection/configs/scnet/scnet_r101_fpn_20e_coco.py new file mode 100644 index 00000000..ebba5297 --- /dev/null +++ b/mmdetection/configs/scnet/scnet_r101_fpn_20e_coco.py @@ -0,0 +1,6 @@ +_base_ = './scnet_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/scnet/scnet_r50_fpn_1x_coco.py b/mmdetection/configs/scnet/scnet_r50_fpn_1x_coco.py new file mode 100644 index 00000000..a0210fdb --- /dev/null +++ b/mmdetection/configs/scnet/scnet_r50_fpn_1x_coco.py @@ -0,0 +1,138 @@ +_base_ = '../htc/htc_r50_fpn_1x_coco.py' +# model settings +model = dict( + type='SCNet', + roi_head=dict( + _delete_=True, + type='SCNetRoIHead', + num_stages=3, + stage_loss_weights=[1, 0.5, 0.25], + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type='SCNetBBoxHead', + num_shared_fcs=2, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='SCNetBBoxHead', + num_shared_fcs=2, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='SCNetBBoxHead', + num_shared_fcs=2, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], + mask_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + mask_head=dict( + type='SCNetMaskHead', + num_convs=12, + in_channels=256, + conv_out_channels=256, + num_classes=80, + conv_to_res=True, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)), + semantic_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[8]), + semantic_head=dict( + type='SCNetSemanticHead', + num_ins=5, + fusion_level=1, + seg_scale_factor=1 / 8, + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=183, + loss_seg=dict( + type='CrossEntropyLoss', ignore_index=255, loss_weight=0.2), + conv_to_res=True), + glbctx_head=dict( + type='GlobalContextHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=80, + loss_weight=3.0, + conv_to_res=True), + feat_relay_head=dict( + type='FeatureRelayHead', + in_channels=1024, + out_conv_channels=256, + roi_feat_size=7, + scale_factor=2))) + +# TODO +# uncomment below code to enable test time augmentations +# img_norm_cfg = dict( +# mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +# test_pipeline = [ +# dict(type='LoadImageFromFile'), +# dict( +# type='MultiScaleFlipAug', +# img_scale=[(600, 900), (800, 1200), (1000, 1500), (1200, 1800), +# (1400, 2100)], +# flip=True, +# transforms=[ +# dict(type='Resize', keep_ratio=True), +# dict(type='RandomFlip', flip_ratio=0.5), +# dict(type='Normalize', **img_norm_cfg), +# dict(type='Pad', size_divisor=32), +# dict(type='ImageToTensor', keys=['img']), +# dict(type='Collect', keys=['img']), +# ]) +# ] +# data = dict( +# val=dict(pipeline=test_pipeline), +# test=dict(pipeline=test_pipeline)) diff --git a/mmdetection/configs/scnet/scnet_r50_fpn_20e_coco.py b/mmdetection/configs/scnet/scnet_r50_fpn_20e_coco.py new file mode 100644 index 00000000..533e1b5f --- /dev/null +++ b/mmdetection/configs/scnet/scnet_r50_fpn_20e_coco.py @@ -0,0 +1,15 @@ +_base_ = './scnet_r50_fpn_1x_coco.py' +# learning policy +max_epochs = 20 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 19], + gamma=0.1) +] +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/scnet/scnet_x101-64x4d_fpn_20e_coco.py b/mmdetection/configs/scnet/scnet_x101-64x4d_fpn_20e_coco.py new file mode 100644 index 00000000..1e54b030 --- /dev/null +++ b/mmdetection/configs/scnet/scnet_x101-64x4d_fpn_20e_coco.py @@ -0,0 +1,15 @@ +_base_ = './scnet_r50_fpn_20e_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/scnet/scnet_x101-64x4d_fpn_8xb1-20e_coco.py b/mmdetection/configs/scnet/scnet_x101-64x4d_fpn_8xb1-20e_coco.py new file mode 100644 index 00000000..3cdce7d5 --- /dev/null +++ b/mmdetection/configs/scnet/scnet_x101-64x4d_fpn_8xb1-20e_coco.py @@ -0,0 +1,8 @@ +_base_ = './scnet_x101-64x4d_fpn_20e_coco.py' +train_dataloader = dict(batch_size=1, num_workers=1) + +optim_wrapper = dict(optimizer=dict(lr=0.01)) +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (1 samples per GPU) +auto_scale_lr = dict(base_batch_size=8) diff --git a/mmdetection/configs/scratch/README.md b/mmdetection/configs/scratch/README.md new file mode 100644 index 00000000..7bdd8ff9 --- /dev/null +++ b/mmdetection/configs/scratch/README.md @@ -0,0 +1,35 @@ +# Scratch + +> [Rethinking ImageNet Pre-training](https://arxiv.org/abs/1811.08883) + + + +## Abstract + +We report competitive results on object detection and instance segmentation on the COCO dataset using standard models trained from random initialization. The results are no worse than their ImageNet pre-training counterparts even when using the hyper-parameters of the baseline system (Mask R-CNN) that were optimized for fine-tuning pre-trained models, with the sole exception of increasing the number of training iterations so the randomly initialized models may converge. Training from random initialization is surprisingly robust; our results hold even when: (i) using only 10% of the training data, (ii) for deeper and wider models, and (iii) for multiple tasks and metrics. Experiments show that ImageNet pre-training speeds up convergence early in training, but does not necessarily provide regularization or improve final target task accuracy. To push the envelope we demonstrate 50.9 AP on COCO object detection without using any external data---a result on par with the top COCO 2017 competition results that used ImageNet pre-training. These observations challenge the conventional wisdom of ImageNet pre-training for dependent tasks and we expect these discoveries will encourage people to rethink the current de facto paradigm of \`pre-training and fine-tuning' in computer vision. + +
    + +
    + +## Results and Models + +| Model | Backbone | Style | Lr schd | box AP | mask AP | Config | Download | +| :----------: | :------: | :-----: | :-----: | :----: | :-----: | :-------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Faster R-CNN | R-50-FPN | pytorch | 6x | 40.7 | | [config](./faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/scratch/faster_rcnn_r50_fpn_gn-all_scratch_6x_coco/scratch_faster_rcnn_r50_fpn_gn_6x_bbox_mAP-0.407_20200201_193013-90813d01.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/scratch/faster_rcnn_r50_fpn_gn-all_scratch_6x_coco/scratch_faster_rcnn_r50_fpn_gn_6x_20200201_193013.log.json) | +| Mask R-CNN | R-50-FPN | pytorch | 6x | 41.2 | 37.4 | [config](./mask-rcnn_r50-scratch_fpn_gn-all_6x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/scratch/mask_rcnn_r50_fpn_gn-all_scratch_6x_coco/scratch_mask_rcnn_r50_fpn_gn_6x_bbox_mAP-0.412__segm_mAP-0.374_20200201_193051-1e190a40.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/scratch/mask_rcnn_r50_fpn_gn-all_scratch_6x_coco/scratch_mask_rcnn_r50_fpn_gn_6x_20200201_193051.log.json) | + +Note: + +- The above models are trained with 16 GPUs. + +## Citation + +```latex +@article{he2018rethinking, + title={Rethinking imagenet pre-training}, + author={He, Kaiming and Girshick, Ross and Doll{\'a}r, Piotr}, + journal={arXiv preprint arXiv:1811.08883}, + year={2018} +} +``` diff --git a/mmdetection/configs/scratch/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py b/mmdetection/configs/scratch/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py new file mode 100644 index 00000000..6e632b9a --- /dev/null +++ b/mmdetection/configs/scratch/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py @@ -0,0 +1,39 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + frozen_stages=-1, + zero_init_residual=False, + norm_cfg=norm_cfg, + init_cfg=None), + neck=dict(norm_cfg=norm_cfg), + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=norm_cfg))) + +optim_wrapper = dict(paramwise_cfg=dict(norm_decay_mult=0.)) + +max_epochs = 73 + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[65, 71], + gamma=0.1) +] + +train_cfg = dict(max_epochs=max_epochs) + +# only keep latest 3 checkpoints +default_hooks = dict(checkpoint=dict(max_keep_ckpts=3)) diff --git a/mmdetection/configs/scratch/mask-rcnn_r50-scratch_fpn_gn-all_6x_coco.py b/mmdetection/configs/scratch/mask-rcnn_r50-scratch_fpn_gn-all_6x_coco.py new file mode 100644 index 00000000..9796f504 --- /dev/null +++ b/mmdetection/configs/scratch/mask-rcnn_r50-scratch_fpn_gn-all_6x_coco.py @@ -0,0 +1,40 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + backbone=dict( + frozen_stages=-1, + zero_init_residual=False, + norm_cfg=norm_cfg, + init_cfg=None), + neck=dict(norm_cfg=norm_cfg), + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=norm_cfg), + mask_head=dict(norm_cfg=norm_cfg))) + +optim_wrapper = dict(paramwise_cfg=dict(norm_decay_mult=0.)) + +max_epochs = 73 + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[65, 71], + gamma=0.1) +] + +train_cfg = dict(max_epochs=max_epochs) + +# only keep latest 3 checkpoints +default_hooks = dict(checkpoint=dict(max_keep_ckpts=3)) diff --git a/mmdetection/configs/scratch/metafile.yml b/mmdetection/configs/scratch/metafile.yml new file mode 100644 index 00000000..977b8e5b --- /dev/null +++ b/mmdetection/configs/scratch/metafile.yml @@ -0,0 +1,48 @@ +Collections: + - Name: Rethinking ImageNet Pre-training + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - RPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1811.08883 + Title: 'Rethinking ImageNet Pre-training' + README: configs/scratch/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.0.0/configs/scratch/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py + Version: v2.0.0 + +Models: + - Name: faster-rcnn_r50_fpn_gn-all_scratch_6x_coco + In Collection: Rethinking ImageNet Pre-training + Config: configs/scratch/faster-rcnn_r50-scratch_fpn_gn-all_6x_coco.py + Metadata: + Epochs: 72 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/scratch/faster_rcnn_r50_fpn_gn-all_scratch_6x_coco/scratch_faster_rcnn_r50_fpn_gn_6x_bbox_mAP-0.407_20200201_193013-90813d01.pth + + - Name: mask-rcnn_r50_fpn_gn-all_scratch_6x_coco + In Collection: Rethinking ImageNet Pre-training + Config: configs/scratch/mask-rcnn_r50-scratch_fpn_gn-all_6x_coco.py + Metadata: + Epochs: 72 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/scratch/mask_rcnn_r50_fpn_gn-all_scratch_6x_coco/scratch_mask_rcnn_r50_fpn_gn_6x_bbox_mAP-0.412__segm_mAP-0.374_20200201_193051-1e190a40.pth diff --git a/mmdetection/configs/seesaw_loss/README.md b/mmdetection/configs/seesaw_loss/README.md new file mode 100644 index 00000000..7077d753 --- /dev/null +++ b/mmdetection/configs/seesaw_loss/README.md @@ -0,0 +1,47 @@ +# Seesaw Loss + +> [Seesaw Loss for Long-Tailed Instance Segmentation](https://arxiv.org/abs/2008.10032) + + + +## Abstract + +Instance segmentation has witnessed a remarkable progress on class-balanced benchmarks. However, they fail to perform as accurately in real-world scenarios, where the category distribution of objects naturally comes with a long tail. Instances of head classes dominate a long-tailed dataset and they serve as negative samples of tail categories. The overwhelming gradients of negative samples on tail classes lead to a biased learning process for classifiers. Consequently, objects of tail categories are more likely to be misclassified as backgrounds or head categories. To tackle this problem, we propose Seesaw Loss to dynamically re-balance gradients of positive and negative samples for each category, with two complementary factors, i.e., mitigation factor and compensation factor. The mitigation factor reduces punishments to tail categories w.r.t. the ratio of cumulative training instances between different categories. Meanwhile, the compensation factor increases the penalty of misclassified instances to avoid false positives of tail categories. We conduct extensive experiments on Seesaw Loss with mainstream frameworks and different data sampling strategies. With a simple end-to-end training pipeline, Seesaw Loss obtains significant gains over Cross-Entropy Loss, and achieves state-of-the-art performance on LVIS dataset without bells and whistles. + +
    + +
    + +- Please setup [LVIS dataset](../lvis/README.md) for MMDetection. + +- RFS indicates to use oversample strategy [here](../../docs/tutorials/customipredataset.md#class-balanced-dataset) with oversample threshold `1e-3`. + +## Results and models of Seasaw Loss on LVIS v1 dataset + +| Method | Backbone | Style | Lr schd | Data Sampler | Norm Mask | box AP | mask AP | Config | Download | +| :----------------: | :-------: | :-----: | :-----: | :----------: | :-------: | :----: | :-----: | :----------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Mask R-CNN | R-50-FPN | pytorch | 2x | random | N | 25.6 | 25.0 | [config](./mask-rcnn_r50_fpn_seesaw-loss_random-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_random_seesaw_loss_mstrain_2x_lvis_v1-a698dd3d.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_random_seesaw_loss_mstrain_2x_lvis_v1.log.json) | +| Mask R-CNN | R-50-FPN | pytorch | 2x | random | Y | 25.6 | 25.4 | [config](./mask-rcnn_r50_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-a1c11314.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1.log.json) | +| Mask R-CNN | R-101-FPN | pytorch | 2x | random | N | 27.4 | 26.7 | [config](./mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_random_seesaw_loss_mstrain_2x_lvis_v1-8e6e6dd5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_random_seesaw_loss_mstrain_2x_lvis_v1.log.json) | +| Mask R-CNN | R-101-FPN | pytorch | 2x | random | Y | 27.2 | 27.3 | [config](./mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-a0b59c42.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1.log.json) | +| Mask R-CNN | R-50-FPN | pytorch | 2x | RFS | N | 27.6 | 26.4 | [config](./mask-rcnn_r50_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1-392a804b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1.log.json) | +| Mask R-CNN | R-50-FPN | pytorch | 2x | RFS | Y | 27.6 | 26.8 | [config](./mask-rcnn_r50_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-cd0f6a12.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1.log.json) | +| Mask R-CNN | R-101-FPN | pytorch | 2x | RFS | N | 28.9 | 27.6 | [config](./mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1-e68eb464.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1.log.json) | +| Mask R-CNN | R-101-FPN | pytorch | 2x | RFS | Y | 28.9 | 28.2 | [config](./mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-1d817139.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1.log.json) | +| Cascade Mask R-CNN | R-101-FPN | pytorch | 2x | random | N | 33.1 | 29.2 | [config](./cascade-mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_random_seesaw_loss_mstrain_2x_lvis_v1-71e2215e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_random_seesaw_loss_mstrain_2x_lvis_v1.log.json) | +| Cascade Mask R-CNN | R-101-FPN | pytorch | 2x | random | Y | 33.0 | 30.0 | [config](./cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-8b5a6745.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1.log.json) | +| Cascade Mask R-CNN | R-101-FPN | pytorch | 2x | RFS | N | 30.0 | 29.3 | [config](./cascade-mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1-5d8ca2a4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1.log.json) | +| Cascade Mask R-CNN | R-101-FPN | pytorch | 2x | RFS | Y | 32.8 | 30.1 | [config](./cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-c8551505.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1.log.json) | + +## Citation + +We provide config files to reproduce the instance segmentation performance in the CVPR 2021 paper for [Seesaw Loss for Long-Tailed Instance Segmentation](https://arxiv.org/abs/2008.10032). + +```latex +@inproceedings{wang2021seesaw, + title={Seesaw Loss for Long-Tailed Instance Segmentation}, + author={Jiaqi Wang and Wenwei Zhang and Yuhang Zang and Yuhang Cao and Jiangmiao Pang and Tao Gong and Kai Chen and Ziwei Liu and Chen Change Loy and Dahua Lin}, + booktitle={Proceedings of the {IEEE} Conference on Computer Vision and Pattern Recognition}, + year={2021} +} +``` diff --git a/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py new file mode 100644 index 00000000..2de87dcc --- /dev/null +++ b/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py @@ -0,0 +1,5 @@ +_base_ = './cascade-mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py' # noqa: E501 +model = dict( + roi_head=dict( + mask_head=dict( + predictor_cfg=dict(type='NormedConv2d', tempearture=20)))) diff --git a/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py new file mode 100644 index 00000000..4d67ad7d --- /dev/null +++ b/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py @@ -0,0 +1,5 @@ +_base_ = './cascade-mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py' # noqa: E501 +model = dict( + roi_head=dict( + mask_head=dict( + predictor_cfg=dict(type='NormedConv2d', tempearture=20)))) diff --git a/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py new file mode 100644 index 00000000..2a1a87d4 --- /dev/null +++ b/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py @@ -0,0 +1,116 @@ +_base_ = [ + '../_base_/models/cascade-mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + roi_head=dict( + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=1203, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + cls_predictor_cfg=dict(type='NormedLinear', tempearture=20), + loss_cls=dict( + type='SeesawLoss', + p=0.8, + q=2.0, + num_classes=1203, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=1203, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + cls_predictor_cfg=dict(type='NormedLinear', tempearture=20), + loss_cls=dict( + type='SeesawLoss', + p=0.8, + q=2.0, + num_classes=1203, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=1203, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + cls_predictor_cfg=dict(type='NormedLinear', tempearture=20), + loss_cls=dict( + type='SeesawLoss', + p=0.8, + q=2.0, + num_classes=1203, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], + mask_head=dict(num_classes=1203)), + test_cfg=dict( + rcnn=dict( + score_thr=0.0001, + # LVIS allows up to 300 + max_per_img=300))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +dataset_type = 'LVISV1Dataset' +data_root = 'data/lvis_v1/' +train_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/lvis_v1_train.json', + data_prefix=dict(img=''), + pipeline=train_pipeline)) +val_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/lvis_v1_val.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='LVISMetric', + ann_file=data_root + 'annotations/lvis_v1_val.json', + metric=['bbox', 'segm']) +test_evaluator = val_evaluator + +train_cfg = dict(val_interval=24) diff --git a/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py new file mode 100644 index 00000000..0e7b4df9 --- /dev/null +++ b/mmdetection/configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py @@ -0,0 +1,95 @@ +_base_ = [ + '../_base_/models/cascade-mask-rcnn_r50_fpn.py', + '../_base_/datasets/lvis_v1_instance.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101')), + roi_head=dict( + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=1203, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + cls_predictor_cfg=dict(type='NormedLinear', tempearture=20), + loss_cls=dict( + type='SeesawLoss', + p=0.8, + q=2.0, + num_classes=1203, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=1203, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + cls_predictor_cfg=dict(type='NormedLinear', tempearture=20), + loss_cls=dict( + type='SeesawLoss', + p=0.8, + q=2.0, + num_classes=1203, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=1203, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + cls_predictor_cfg=dict(type='NormedLinear', tempearture=20), + loss_cls=dict( + type='SeesawLoss', + p=0.8, + q=2.0, + num_classes=1203, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], + mask_head=dict(num_classes=1203)), + test_cfg=dict( + rcnn=dict( + score_thr=0.0001, + # LVIS allows up to 300 + max_per_img=300))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(dataset=dict(pipeline=train_pipeline))) + +train_cfg = dict(val_interval=24) diff --git a/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py new file mode 100644 index 00000000..b518c213 --- /dev/null +++ b/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py' # noqa: E501 +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py new file mode 100644 index 00000000..008bbcae --- /dev/null +++ b/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py' # noqa: E501 +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py new file mode 100644 index 00000000..8a0b6755 --- /dev/null +++ b/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_seesaw-loss_random-ms-2x_lvis-v1.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py new file mode 100644 index 00000000..61432319 --- /dev/null +++ b/mmdetection/configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_r50_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py new file mode 100644 index 00000000..06d2438c --- /dev/null +++ b/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py @@ -0,0 +1,5 @@ +_base_ = './mask-rcnn_r50_fpn_seesaw-loss_random-ms-2x_lvis-v1.py' +model = dict( + roi_head=dict( + mask_head=dict( + predictor_cfg=dict(type='NormedConv2d', tempearture=20)))) diff --git a/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py new file mode 100644 index 00000000..5fc68d3d --- /dev/null +++ b/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py @@ -0,0 +1,5 @@ +_base_ = './mask-rcnn_r50_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py' +model = dict( + roi_head=dict( + mask_head=dict( + predictor_cfg=dict(type='NormedConv2d', tempearture=20)))) diff --git a/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss_random-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss_random-ms-2x_lvis-v1.py new file mode 100644 index 00000000..25c646c9 --- /dev/null +++ b/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss_random-ms-2x_lvis-v1.py @@ -0,0 +1,59 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] +model = dict( + roi_head=dict( + bbox_head=dict( + num_classes=1203, + cls_predictor_cfg=dict(type='NormedLinear', tempearture=20), + loss_cls=dict( + type='SeesawLoss', + p=0.8, + q=2.0, + num_classes=1203, + loss_weight=1.0)), + mask_head=dict(num_classes=1203)), + test_cfg=dict( + rcnn=dict( + score_thr=0.0001, + # LVIS allows up to 300 + max_per_img=300))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +dataset_type = 'LVISV1Dataset' +data_root = 'data/lvis_v1/' +train_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/lvis_v1_train.json', + data_prefix=dict(img=''), + pipeline=train_pipeline)) +val_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/lvis_v1_val.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='LVISMetric', + ann_file=data_root + 'annotations/lvis_v1_val.json', + metric=['bbox', 'segm']) +test_evaluator = val_evaluator + +train_cfg = dict(val_interval=24) diff --git a/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py b/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py new file mode 100644 index 00000000..d60320e0 --- /dev/null +++ b/mmdetection/configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py @@ -0,0 +1,38 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/lvis_v1_instance.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] +model = dict( + roi_head=dict( + bbox_head=dict( + num_classes=1203, + cls_predictor_cfg=dict(type='NormedLinear', tempearture=20), + loss_cls=dict( + type='SeesawLoss', + p=0.8, + q=2.0, + num_classes=1203, + loss_weight=1.0)), + mask_head=dict(num_classes=1203)), + test_cfg=dict( + rcnn=dict( + score_thr=0.0001, + # LVIS allows up to 300 + max_per_img=300))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(dataset=dict(pipeline=train_pipeline))) + +train_cfg = dict(val_interval=24) diff --git a/mmdetection/configs/seesaw_loss/metafile.yml b/mmdetection/configs/seesaw_loss/metafile.yml new file mode 100644 index 00000000..374b9cde --- /dev/null +++ b/mmdetection/configs/seesaw_loss/metafile.yml @@ -0,0 +1,203 @@ +Collections: + - Name: Seesaw Loss + Metadata: + Training Data: LVIS + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Softmax + - RPN + - Convolution + - Dense Connections + - FPN + - ResNet + - RoIAlign + - Seesaw Loss + Paper: + URL: https://arxiv.org/abs/2008.10032 + Title: 'Seesaw Loss for Long-Tailed Instance Segmentation' + README: configs/seesaw_loss/README.md + +Models: + - Name: mask-rcnn_r50_fpn_random_seesaw_loss_mstrain_2x_lvis_v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss_random-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 25.6 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 25.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_random_seesaw_loss_mstrain_2x_lvis_v1-a698dd3d.pth + - Name: mask-rcnn_r50_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 25.6 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 25.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-a1c11314.pth + - Name: mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 27.4 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 26.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_random_seesaw_loss_mstrain_2x_lvis_v1-8e6e6dd5.pth + - Name: mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 27.2 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 27.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-a0b59c42.pth + - Name: mask-rcnn_r50_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 27.6 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 26.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1-392a804b.pth + - Name: mask-rcnn_r50_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/mask-rcnn_r50_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 27.6 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 26.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r50_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-cd0f6a12.pth + - Name: mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 28.9 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 27.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1-e68eb464.pth + - Name: mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 28.9 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 28.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-1d817139.pth + - Name: cascade-mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss_random-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 33.1 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 29.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_random_seesaw_loss_mstrain_2x_lvis_v1-71e2215e.pth + - Name: cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_random-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 33.0 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 30.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_random_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-8b5a6745.pth + - Name: cascade-mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss_sample1e-3-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 30.0 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 29.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_mstrain_2x_lvis_v1-5d8ca2a4.pth + - Name: cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1 + In Collection: Seesaw Loss + Config: configs/seesaw_loss/cascade-mask-rcnn_r101_fpn_seesaw-loss-normed-mask_sample1e-3-ms-2x_lvis-v1.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: LVIS v1 + Metrics: + box AP: 32.8 + - Task: Instance Segmentation + Dataset: LVIS v1 + Metrics: + mask AP: 30.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/seesaw_loss/cascade_mask_rcnn_r101_fpn_sample1e-3_seesaw_loss_normed_mask_mstrain_2x_lvis_v1-c8551505.pth diff --git a/mmdetection/configs/selfsup_pretrain/README.md b/mmdetection/configs/selfsup_pretrain/README.md new file mode 100644 index 00000000..57537ddd --- /dev/null +++ b/mmdetection/configs/selfsup_pretrain/README.md @@ -0,0 +1,109 @@ +# Backbones Trained by Self-Supervise Algorithms + + + +## Abstract + +Unsupervised image representations have significantly reduced the gap with supervised pretraining, notably with the recent achievements of contrastive learning methods. These contrastive methods typically work online and rely on a large number of explicit pairwise feature comparisons, which is computationally challenging. In this paper, we propose an online algorithm, SwAV, that takes advantage of contrastive methods without requiring to compute pairwise comparisons. Specifically, our method simultaneously clusters the data while enforcing consistency between cluster assignments produced for different augmentations (or views) of the same image, instead of comparing features directly as in contrastive learning. Simply put, we use a swapped prediction mechanism where we predict the cluster assignment of a view from the representation of another view. Our method can be trained with large and small batches and can scale to unlimited amounts of data. Compared to previous contrastive methods, our method is more memory efficient since it does not require a large memory bank or a special momentum network. In addition, we also propose a new data augmentation strategy, multi-crop, that uses a mix of views with different resolutions in place of two full-resolution views, without increasing the memory or compute requirements much. We validate our findings by achieving 75.3% top-1 accuracy on ImageNet with ResNet-50, as well as surpassing supervised pretraining on all the considered transfer tasks. + +
    + +
    + +We present Momentum Contrast (MoCo) for unsupervised visual representation learning. From a perspective on contrastive learning as dictionary look-up, we build a dynamic dictionary with a queue and a moving-averaged encoder. This enables building a large and consistent dictionary on-the-fly that facilitates contrastive unsupervised learning. MoCo provides competitive results under the common linear protocol on ImageNet classification. More importantly, the representations learned by MoCo transfer well to downstream tasks. MoCo can outperform its supervised pre-training counterpart in 7 detection/segmentation tasks on PASCAL VOC, COCO, and other datasets, sometimes surpassing it by large margins. This suggests that the gap between unsupervised and supervised representation learning has been largely closed in many vision tasks. + +
    + +
    + +## Usage + +To use a self-supervisely pretrained backbone, there are two steps to do: + +1. Download and convert the model to PyTorch-style supported by MMDetection +2. Modify the config and change the training setting accordingly + +### Convert model + +For more general usage, we also provide script `selfsup2mmdet.py` in the tools directory to convert the key of models pretrained by different self-supervised methods to PyTorch-style checkpoints used in MMDetection. + +```bash +python -u tools/model_converters/selfsup2mmdet.py ${PRETRAIN_PATH} ${STORE_PATH} --selfsup ${method} +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +For example, to use a ResNet-50 backbone released by MoCo, you can download it from [here](https://dl.fbaipublicfiles.com/moco/moco_checkpoints/moco_v2_800ep/moco_v2_800ep_pretrain.pth.tar) and use the following command + +```bash +python -u tools/model_converters/selfsup2mmdet.py ./moco_v2_800ep_pretrain.pth.tar mocov2_r50_800ep_pretrain.pth --selfsup moco +``` + +To use the ResNet-50 backbone released by SwAV, you can download it from [here](https://dl.fbaipublicfiles.com/deepcluster/swav_800ep_pretrain.pth.tar) + +### Modify config + +The backbone requires SyncBN and the `frozen_stages` need to be changed. A config that use the moco backbone is as below + +```python +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + pretrained='./mocov2_r50_800ep_pretrain.pth', + backbone=dict( + frozen_stages=0, + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False)) + +``` + +## Results and Models + +| Method | Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :-------: | :------------------------------------------------------------: | :-----: | :------------: | :------: | :------------: | :----: | :-----: | :----------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Mask RCNN | [R50 by MoCo v2](./mask-rcnn_r50-mocov2-pre_fpn_1x_coco.py) | pytorch | 1x | | | 38.0 | 34.3 | [config](./mask-rcnn_r50-mocov2-pre_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/selfsup_pretrain/mask_rcnn_r50_fpn_mocov2-pretrain_1x_coco/mask_rcnn_r50_fpn_mocov2-pretrain_1x_coco_20210604_114614-a8b63483.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/selfsup_pretrain/mask_rcnn_r50_fpn_mocov2-pretrain_1x_coco/mask_rcnn_r50_fpn_mocov2-pretrain_1x_coco_20210604_114614.log.json) | +| Mask RCNN | [R50 by MoCo v2](./mask-rcnn_r50-mocov2-pre_fpn_ms-2x_coco.py) | pytorch | multi-scale 2x | | | 40.8 | 36.8 | [config](./mask-rcnn_r50-mocov2-pre_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/selfsup_pretrain/mask_rcnn_r50_fpn_mocov2-pretrain_ms-2x_coco/mask_rcnn_r50_fpn_mocov2-pretrain_ms-2x_coco_20210605_163717-d95df20a.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/selfsup_pretrain/mask_rcnn_r50_fpn_mocov2-pretrain_ms-2x_coco/mask_rcnn_r50_fpn_mocov2-pretrain_ms-2x_coco_20210605_163717.log.json) | +| Mask RCNN | [R50 by SwAV](./mask-rcnn_r50-swav-pre_fpn_1x_coco.py) | pytorch | 1x | | | 39.1 | 35.7 | [config](./mask-rcnn_r50-swav-pre_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/selfsup_pretrain/mask_rcnn_r50_fpn_swav-pretrain_1x_coco/mask_rcnn_r50_fpn_swav-pretrain_1x_coco_20210604_114640-7b9baf28.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/selfsup_pretrain/mask_rcnn_r50_fpn_swav-pretrain_1x_coco/mask_rcnn_r50_fpn_swav-pretrain_1x_coco_20210604_114640.log.json) | +| Mask RCNN | [R50 by SwAV](./mask-rcnn_r50-swav-pre_fpn_ms-2x_coco.py) | pytorch | multi-scale 2x | | | 41.3 | 37.3 | [config](./mask-rcnn_r50-swav-pre_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/selfsup_pretrain/mask_rcnn_r50_fpn_swav-pretrain_ms-2x_coco/mask_rcnn_r50_fpn_swav-pretrain_ms-2x_coco_20210605_163717-08e26fca.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/selfsup_pretrain/mask_rcnn_r50_fpn_swav-pretrain_ms-2x_coco/mask_rcnn_r50_fpn_swav-pretrain_ms-2x_coco_20210605_163717.log.json) | + +### Notice + +1. We only provide single-scale 1x and multi-scale 2x configs as examples to show how to use backbones trained by self-supervised algorithms. We will try to reproduce the results in their corresponding paper using the released backbone in the future. Please stay tuned. + +## Citation + +We support to apply the backbone models pre-trained by different self-supervised methods in detection systems and provide their results on Mask R-CNN. + +The pre-trained models are converted from [MoCo](https://github.com/facebookresearch/moco) and downloaded from [SwAV](https://github.com/facebookresearch/swav). + +For SwAV, please cite + +```latex +@article{caron2020unsupervised, + title={Unsupervised Learning of Visual Features by Contrasting Cluster Assignments}, + author={Caron, Mathilde and Misra, Ishan and Mairal, Julien and Goyal, Priya and Bojanowski, Piotr and Joulin, Armand}, + booktitle={Proceedings of Advances in Neural Information Processing Systems (NeurIPS)}, + year={2020} +} +``` + +For MoCo, please cite + +```latex +@Article{he2019moco, + author = {Kaiming He and Haoqi Fan and Yuxin Wu and Saining Xie and Ross Girshick}, + title = {Momentum Contrast for Unsupervised Visual Representation Learning}, + journal = {arXiv preprint arXiv:1911.05722}, + year = {2019}, +} +@Article{chen2020mocov2, + author = {Xinlei Chen and Haoqi Fan and Ross Girshick and Kaiming He}, + title = {Improved Baselines with Momentum Contrastive Learning}, + journal = {arXiv preprint arXiv:2003.04297}, + year = {2020}, +} +``` diff --git a/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-mocov2-pre_fpn_1x_coco.py b/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-mocov2-pre_fpn_1x_coco.py new file mode 100644 index 00000000..91d45add --- /dev/null +++ b/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-mocov2-pre_fpn_1x_coco.py @@ -0,0 +1,13 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict( + frozen_stages=0, + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + init_cfg=dict( + type='Pretrained', checkpoint='./mocov2_r50_800ep_pretrain.pth'))) diff --git a/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-mocov2-pre_fpn_ms-2x_coco.py b/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-mocov2-pre_fpn_ms-2x_coco.py new file mode 100644 index 00000000..ddaebf55 --- /dev/null +++ b/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-mocov2-pre_fpn_ms-2x_coco.py @@ -0,0 +1,25 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict( + frozen_stages=0, + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + init_cfg=dict( + type='Pretrained', checkpoint='./mocov2_r50_800ep_pretrain.pth'))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-swav-pre_fpn_1x_coco.py b/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-swav-pre_fpn_1x_coco.py new file mode 100644 index 00000000..785c80ec --- /dev/null +++ b/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-swav-pre_fpn_1x_coco.py @@ -0,0 +1,13 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict( + frozen_stages=0, + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + init_cfg=dict( + type='Pretrained', checkpoint='./swav_800ep_pretrain.pth.tar'))) diff --git a/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-swav-pre_fpn_ms-2x_coco.py b/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-swav-pre_fpn_ms-2x_coco.py new file mode 100644 index 00000000..c393e0b3 --- /dev/null +++ b/mmdetection/configs/selfsup_pretrain/mask-rcnn_r50-swav-pre_fpn_ms-2x_coco.py @@ -0,0 +1,25 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] + +model = dict( + backbone=dict( + frozen_stages=0, + norm_cfg=dict(type='SyncBN', requires_grad=True), + norm_eval=False, + init_cfg=dict( + type='Pretrained', checkpoint='./swav_800ep_pretrain.pth.tar'))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/simple_copy_paste/README.md b/mmdetection/configs/simple_copy_paste/README.md new file mode 100644 index 00000000..23b09ce5 --- /dev/null +++ b/mmdetection/configs/simple_copy_paste/README.md @@ -0,0 +1,38 @@ +# SimpleCopyPaste + +> [Simple Copy-Paste is a Strong Data Augmentation Method for Instance Segmentation](https://arxiv.org/abs/2012.07177) + + + +## Abstract + +Building instance segmentation models that are data-efficient and can handle rare object categories is an important challenge in computer vision. Leveraging data augmentations is a promising direction towards addressing this challenge. Here, we perform a systematic study of the Copy-Paste augmentation (\[13, 12\]) for instance segmentation where we randomly paste objects onto an image. Prior studies on Copy-Paste relied on modeling the surrounding visual context for pasting the objects. However, we find that the simple mechanism of pasting objects randomly is good enough and can provide solid gains on top of strong baselines. Furthermore, we show Copy-Paste is additive with semi-supervised methods that leverage extra data through pseudo labeling (e.g. self-training). On COCO instance segmentation, we achieve 49.1 mask AP and 57.3 box AP, an improvement of +0.6 mask AP and +1.5 box AP over the previous state-of-the-art. We further demonstrate that Copy-Paste can lead to significant improvements on the LVIS benchmark. Our baseline model outperforms the LVIS 2020 Challenge winning entry by +3.6 mask AP on rare categories. + +
    + +
    + +## Results and Models + +### Mask R-CNN with Standard Scale Jittering (SSJ) and Simple Copy-Paste(SCP) + +Standard Scale Jittering(SSJ) resizes and crops an image with a resize range of 0.8 to 1.25 of the original image size, and Simple Copy-Paste(SCP) selects a random subset of objects from one of the images and pastes them onto the other image. + +| Backbone | Training schedule | Augmentation | batch size | box AP | mask AP | Config | Download | +| :------: | :---------------: | :----------: | :--------: | :----: | :-----: | :------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | 90k | SSJ | 64 | 43.3 | 39.0 | [config](./mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-90k_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_90k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_90k_coco_20220316_181409-f79c84c5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_90k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_90k_coco_20220316_181409.log.json) | +| R-50 | 90k | SSJ+SCP | 64 | 43.8 | 39.2 | [config](./mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-90k_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_90k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_90k_coco_20220316_181307-6bc5726f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_90k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_90k_coco_20220316_181307.log.json) | +| R-50 | 270k | SSJ | 64 | 43.5 | 39.1 | [config](./mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-270k_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_270k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_270k_coco_20220324_182940-33a100c5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_270k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_270k_coco_20220324_182940.log.json) | +| R-50 | 270k | SSJ+SCP | 64 | 45.1 | 40.3 | [config](./mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-270k_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_270k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_270k_coco_20220324_201229-80ee90b7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_270k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_270k_coco_20220324_201229.log.json) | + +## Citation + +```latex +@inproceedings{ghiasi2021simple, + title={Simple copy-paste is a strong data augmentation method for instance segmentation}, + author={Ghiasi, Golnaz and Cui, Yin and Srinivas, Aravind and Qian, Rui and Lin, Tsung-Yi and Cubuk, Ekin D and Le, Quoc V and Zoph, Barret}, + booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition}, + pages={2918--2928}, + year={2021} +} +``` diff --git a/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-270k_coco.py b/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-270k_coco.py new file mode 100644 index 00000000..0c6e081e --- /dev/null +++ b/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-270k_coco.py @@ -0,0 +1,31 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + # 270k iterations with batch_size 64 is roughly equivalent to 144 epochs + '../common/ssj_270k_coco-instance.py', +] + +image_size = (1024, 1024) +batch_augments = [ + dict(type='BatchFixedSizePad', size=image_size, pad_mask=True) +] +norm_cfg = dict(type='SyncBN', requires_grad=True) +# Use MMSyncBN that handles empty tensor in head. It can be changed to +# SyncBN after https://github.com/pytorch/pytorch/issues/36530 is fixed +head_norm_cfg = dict(type='MMSyncBN', requires_grad=True) +model = dict( + # the model is trained from scratch, so init_cfg is None + data_preprocessor=dict( + # pad_size_divisor=32 is unnecessary in training but necessary + # in testing. + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + frozen_stages=-1, norm_eval=False, norm_cfg=norm_cfg, init_cfg=None), + neck=dict(norm_cfg=norm_cfg), + rpn_head=dict(num_convs=2), # leads to 0.1+ mAP + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=head_norm_cfg), + mask_head=dict(norm_cfg=head_norm_cfg))) diff --git a/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-90k_coco.py b/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-90k_coco.py new file mode 100644 index 00000000..abe8962a --- /dev/null +++ b/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-90k_coco.py @@ -0,0 +1,18 @@ +_base_ = 'mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-270k_coco.py' # noqa + +# training schedule for 90k +max_iters = 90000 + +# learning rate policy +# lr steps at [0.9, 0.95, 0.975] of the maximum iterations +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.067, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=90000, + by_epoch=False, + milestones=[81000, 85500, 87750], + gamma=0.1) +] diff --git a/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-270k_coco.py b/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-270k_coco.py new file mode 100644 index 00000000..f0ea57d1 --- /dev/null +++ b/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-270k_coco.py @@ -0,0 +1,31 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + # 270k iterations with batch_size 64 is roughly equivalent to 144 epochs + '../common/ssj_scp_270k_coco-instance.py' +] + +image_size = (1024, 1024) +batch_augments = [ + dict(type='BatchFixedSizePad', size=image_size, pad_mask=True) +] +norm_cfg = dict(type='SyncBN', requires_grad=True) +# Use MMSyncBN that handles empty tensor in head. It can be changed to +# SyncBN after https://github.com/pytorch/pytorch/issues/36530 is fixed +head_norm_cfg = dict(type='MMSyncBN', requires_grad=True) +model = dict( + # the model is trained from scratch, so init_cfg is None + data_preprocessor=dict( + # pad_size_divisor=32 is unnecessary in training but necessary + # in testing. + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + frozen_stages=-1, norm_eval=False, norm_cfg=norm_cfg, init_cfg=None), + neck=dict(norm_cfg=norm_cfg), + rpn_head=dict(num_convs=2), # leads to 0.1+ mAP + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=head_norm_cfg), + mask_head=dict(norm_cfg=head_norm_cfg))) diff --git a/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-90k_coco.py b/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-90k_coco.py new file mode 100644 index 00000000..e158b5c0 --- /dev/null +++ b/mmdetection/configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-90k_coco.py @@ -0,0 +1,18 @@ +_base_ = 'mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-270k_coco.py' # noqa + +# training schedule for 90k +max_iters = 90000 + +# learning rate policy +# lr steps at [0.9, 0.95, 0.975] of the maximum iterations +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.067, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=90000, + by_epoch=False, + milestones=[81000, 85500, 87750], + gamma=0.1) +] diff --git a/mmdetection/configs/simple_copy_paste/metafile.yml b/mmdetection/configs/simple_copy_paste/metafile.yml new file mode 100644 index 00000000..8a40b658 --- /dev/null +++ b/mmdetection/configs/simple_copy_paste/metafile.yml @@ -0,0 +1,92 @@ +Collections: + - Name: SimpleCopyPaste + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 32x A100 GPUs + Architecture: + - Softmax + - RPN + - Convolution + - Dense Connections + - FPN + - ResNet + - RoIAlign + Paper: + URL: https://arxiv.org/abs/2012.07177 + Title: "Simple Copy-Paste is a Strong Data Augmentation Method for Instance Segmentation" + README: configs/simple_copy_paste/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.25.0/mmdet/datasets/pipelines/transforms.py#L2762 + Version: v2.25.0 + +Models: + - Name: mask-rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_270k_coco + In Collection: SimpleCopyPaste + Config: configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-270k_coco.py + Metadata: + Training Memory (GB): 7.2 + Iterations: 270000 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.5 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_270k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_270k_coco_20220324_182940-33a100c5.pth + + - Name: mask-rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_90k_coco + In Collection: SimpleCopyPaste + Config: configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-90k_coco.py + Metadata: + Training Memory (GB): 7.2 + Iterations: 90000 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.3 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_90k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_32x2_90k_coco_20220316_181409-f79c84c5.pth + + - Name: mask-rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_270k_coco + In Collection: SimpleCopyPaste + Config: configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-270k_coco.py + Metadata: + Training Memory (GB): 7.2 + Iterations: 270000 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.1 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 40.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_270k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_270k_coco_20220324_201229-80ee90b7.pth + + - Name: mask-rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_90k_coco + In Collection: SimpleCopyPaste + Config: configs/simple_copy_paste/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_32xb2-ssj-scp-90k_coco.py + Metadata: + Training Memory (GB): 7.2 + Iterations: 90000 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.8 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_90k_coco/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_90k_coco_20220316_181307-6bc5726f.pth diff --git a/mmdetection/configs/soft_teacher/README.md b/mmdetection/configs/soft_teacher/README.md new file mode 100644 index 00000000..1fd3d84d --- /dev/null +++ b/mmdetection/configs/soft_teacher/README.md @@ -0,0 +1,33 @@ +# SoftTeacher + +> [End-to-End Semi-Supervised Object Detection with Soft Teacher](https://arxiv.org/abs/2106.09018) + + + +## Abstract + +This paper presents an end-to-end semi-supervised object detection approach, in contrast to previous more complex multi-stage methods. The end-to-end training gradually improves pseudo label qualities during the curriculum, and the more and more accurate pseudo labels in turn benefit object detection training. We also propose two simple yet effective techniques within this framework: a soft teacher mechanism where the classification loss of each unlabeled bounding box is weighed by the classification score produced by the teacher network; a box jittering approach to select reliable pseudo boxes for the learning of box regression. On the COCO benchmark, the proposed approach outperforms previous methods by a large margin under various labeling ratios, i.e. 1%, 5% and 10%. Moreover, our approach proves to perform also well when the amount of labeled data is relatively large. For example, it can improve a 40.9 mAP baseline detector trained using the full COCO training set by +3.6 mAP, reaching 44.5 mAP, by leveraging the 123K unlabeled images of COCO. On the state-of-the-art Swin Transformer based object detector (58.9 mAP on test-dev), it can still significantly improve the detection accuracy by +1.5 mAP, reaching 60.4 mAP, and improve the instance segmentation accuracy by +1.2 mAP, reaching 52.4 mAP. Further incorporating with the Object365 pre-trained model, the detection accuracy reaches 61.3 mAP and the instance segmentation accuracy reaches 53.0 mAP, pushing the new state-of-the-art. + +
    + +
    + +## Results and Models + +| Model | Detector | Labeled Dataset | Iteration | box AP | Config | Download | +| :---------: | :----------: | :-------------: | :-------: | :----: | :-----------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| SoftTeacher | Faster R-CNN | COCO-1% | 180k | 19.9 | [config](./soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.01-coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.01-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230330_233412-3c8f6d4a.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.01-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230330_233412.log.json) | +| SoftTeacher | Faster R-CNN | COCO-2% | 180k | 24.9 | [config](./soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.02-coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.02-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230331_020244-c0d2c3aa.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.02-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230331_020244.log.json) | +| SoftTeacher | Faster R-CNN | COCO-5% | 180k | 30.4 | [config](./soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.05-coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.05-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230331_070656-308798ad.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.05-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230331_070656.log.json) | +| SoftTeacher | Faster R-CNN | COCO-10% | 180k | 33.8 | [config](./soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230330_232113-b46f78d0.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230330_232113.log.json) | + +## Citation + +```latex +@article{xu2021end, + title={End-to-End Semi-Supervised Object Detection with Soft Teacher}, + author={Xu, Mengde and Zhang, Zheng and Hu, Han and Wang, Jianfeng and Wang, Lijuan and Wei, Fangyun and Bai, Xiang and Liu, Zicheng}, + journal={Proceedings of the IEEE/CVF International Conference on Computer Vision (ICCV)}, + year={2021} +} +``` diff --git a/mmdetection/configs/soft_teacher/metafile.yml b/mmdetection/configs/soft_teacher/metafile.yml new file mode 100644 index 00000000..9622acec --- /dev/null +++ b/mmdetection/configs/soft_teacher/metafile.yml @@ -0,0 +1,67 @@ +Collections: + - Name: SoftTeacher + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x A100 GPUs + Architecture: + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/2106.09018 + Title: "End-to-End Semi-Supervised Object Detection with Soft Teacher" + README: configs/soft_teacher/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v3.0.0rc1/mmdet/models/detectors/soft_teacher.py#L20 + Version: v3.0.0rc1 + +Models: + - Name: soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.01-coco.py + In Collection: SoftTeacher + Config: configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.01-coco.py + Metadata: + Iterations: 180000 + Results: + - Task: Semi-Supervised Object Detection + Dataset: COCO + Metrics: + box AP: 19.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.01-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230330_233412-3c8f6d4a.pth + + - Name: soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.02-coco.py + In Collection: SoftTeacher + Config: configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.02-coco.py + Metadata: + Iterations: 180000 + Results: + - Task: Semi-Supervised Object Detection + Dataset: COCO + Metrics: + box AP: 24.9 + Weights: https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.02-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230331_020244-c0d2c3aa.pth + + - Name: soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.05-coco.py + In Collection: SoftTeacher + Config: configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.05-coco.py + Metadata: + Iterations: 180000 + Results: + - Task: Semi-Supervised Object Detection + Dataset: COCO + Metrics: + box AP: 30.4 + Weights: https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.05-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230331_070656-308798ad.pth + + - Name: soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py + In Collection: SoftTeacher + Config: configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py + Metadata: + Iterations: 180000 + Results: + - Task: Semi-Supervised Object Detection + Dataset: COCO + Metrics: + box AP: 33.8 + Weights: https://download.openmmlab.com/mmdetection/v3.0/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0_20230330_232113-b46f78d0.pth diff --git a/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.01-coco.py b/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.01-coco.py new file mode 100644 index 00000000..2bd09645 --- /dev/null +++ b/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.01-coco.py @@ -0,0 +1,9 @@ +_base_ = ['soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py'] + +# 1% coco train2017 is set as labeled dataset +labeled_dataset = _base_.labeled_dataset +unlabeled_dataset = _base_.unlabeled_dataset +labeled_dataset.ann_file = 'semi_anns/instances_train2017.1@1.json' +unlabeled_dataset.ann_file = 'semi_anns/instances_train2017.1@1-unlabeled.json' +train_dataloader = dict( + dataset=dict(datasets=[labeled_dataset, unlabeled_dataset])) diff --git a/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.02-coco.py b/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.02-coco.py new file mode 100644 index 00000000..8ca38c93 --- /dev/null +++ b/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.02-coco.py @@ -0,0 +1,9 @@ +_base_ = ['soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py'] + +# 2% coco train2017 is set as labeled dataset +labeled_dataset = _base_.labeled_dataset +unlabeled_dataset = _base_.unlabeled_dataset +labeled_dataset.ann_file = 'semi_anns/instances_train2017.1@2.json' +unlabeled_dataset.ann_file = 'semi_anns/instances_train2017.1@2-unlabeled.json' +train_dataloader = dict( + dataset=dict(datasets=[labeled_dataset, unlabeled_dataset])) diff --git a/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.05-coco.py b/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.05-coco.py new file mode 100644 index 00000000..750b7ed6 --- /dev/null +++ b/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.05-coco.py @@ -0,0 +1,9 @@ +_base_ = ['soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py'] + +# 5% coco train2017 is set as labeled dataset +labeled_dataset = _base_.labeled_dataset +unlabeled_dataset = _base_.unlabeled_dataset +labeled_dataset.ann_file = 'semi_anns/instances_train2017.1@5.json' +unlabeled_dataset.ann_file = 'semi_anns/instances_train2017.1@5-unlabeled.json' +train_dataloader = dict( + dataset=dict(datasets=[labeled_dataset, unlabeled_dataset])) diff --git a/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py b/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py new file mode 100644 index 00000000..3713aef4 --- /dev/null +++ b/mmdetection/configs/soft_teacher/soft-teacher_faster-rcnn_r50-caffe_fpn_180k_semi-0.1-coco.py @@ -0,0 +1,84 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', '../_base_/default_runtime.py', + '../_base_/datasets/semi_coco_detection.py' +] + +detector = _base_.model +detector.data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32) +detector.backbone = dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')) + +model = dict( + _delete_=True, + type='SoftTeacher', + detector=detector, + data_preprocessor=dict( + type='MultiBranchDataPreprocessor', + data_preprocessor=detector.data_preprocessor), + semi_train_cfg=dict( + freeze_teacher=True, + sup_weight=1.0, + unsup_weight=4.0, + pseudo_label_initial_score_thr=0.5, + rpn_pseudo_thr=0.9, + cls_pseudo_thr=0.9, + reg_pseudo_thr=0.02, + jitter_times=10, + jitter_scale=0.06, + min_pseudo_bbox_wh=(1e-2, 1e-2)), + semi_test_cfg=dict(predict_on='teacher')) + +# 10% coco train2017 is set as labeled dataset +labeled_dataset = _base_.labeled_dataset +unlabeled_dataset = _base_.unlabeled_dataset +labeled_dataset.ann_file = 'semi_anns/instances_train2017.1@10.json' +unlabeled_dataset.ann_file = 'semi_anns/' \ + 'instances_train2017.1@10-unlabeled.json' +unlabeled_dataset.data_prefix = dict(img='train2017/') +train_dataloader = dict( + dataset=dict(datasets=[labeled_dataset, unlabeled_dataset])) + +# training schedule for 180k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=180000, val_interval=5000) +val_cfg = dict(type='TeacherStudentValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=180000, + by_epoch=False, + milestones=[120000, 160000], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +default_hooks = dict( + checkpoint=dict(by_epoch=False, interval=10000, max_keep_ckpts=2)) +log_processor = dict(by_epoch=False) + +custom_hooks = [dict(type='MeanTeacherHook')] diff --git a/mmdetection/configs/solo/README.md b/mmdetection/configs/solo/README.md new file mode 100644 index 00000000..4a36676b --- /dev/null +++ b/mmdetection/configs/solo/README.md @@ -0,0 +1,54 @@ +# SOLO + +> [SOLO: Segmenting Objects by Locations](https://arxiv.org/abs/1912.04488) + + + +## Abstract + +We present a new, embarrassingly simple approach to instance segmentation in images. Compared to many other dense prediction tasks, e.g., semantic segmentation, it is the arbitrary number of instances that have made instance segmentation much more challenging. In order to predict a mask for each instance, mainstream approaches either follow the 'detect-thensegment' strategy as used by Mask R-CNN, or predict category masks first then use clustering techniques to group pixels into individual instances. We view the task of instance segmentation from a completely new perspective by introducing the notion of "instance categories", which assigns categories to each pixel within an instance according to the instance's location and size, thus nicely converting instance mask segmentation into a classification-solvable problem. Now instance segmentation is decomposed into two classification tasks. We demonstrate a much simpler and flexible instance segmentation framework with strong performance, achieving on par accuracy with Mask R-CNN and outperforming recent singleshot instance segmenters in accuracy. We hope that this very simple and strong framework can serve as a baseline for many instance-level recognition tasks besides instance segmentation. + +
    + +
    + +## Results and Models + +### SOLO + +| Backbone | Style | MS train | Lr schd | Mem (GB) | Inf time (fps) | mask AP | Download | +| :------: | :-----: | :------: | :-----: | :------: | :------------: | :-----: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | N | 1x | 8.0 | 14.0 | 33.1 | [model](https://download.openmmlab.com/mmdetection/v2.0/solo/solo_r50_fpn_1x_coco/solo_r50_fpn_1x_coco_20210821_035055-2290a6b8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solo/solo_r50_fpn_1x_coco/solo_r50_fpn_1x_coco_20210821_035055.log.json) | +| R-50 | pytorch | Y | 3x | 7.4 | 14.0 | 35.9 | [model](https://download.openmmlab.com/mmdetection/v2.0/solo/solo_r50_fpn_3x_coco/solo_r50_fpn_3x_coco_20210901_012353-11d224d7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solo/solo_r50_fpn_3x_coco/solo_r50_fpn_3x_coco_20210901_012353.log.json) | + +### Decoupled SOLO + +| Backbone | Style | MS train | Lr schd | Mem (GB) | Inf time (fps) | mask AP | Download | +| :------: | :-----: | :------: | :-----: | :------: | :------------: | :-----: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | N | 1x | 7.8 | 12.5 | 33.9 | [model](https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_r50_fpn_1x_coco/decoupled_solo_r50_fpn_1x_coco_20210820_233348-6337c589.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_r50_fpn_1x_coco/decoupled_solo_r50_fpn_1x_coco_20210820_233348.log.json) | +| R-50 | pytorch | Y | 3x | 7.9 | 12.5 | 36.7 | [model](https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_r50_fpn_3x_coco/decoupled_solo_r50_fpn_3x_coco_20210821_042504-7b3301ec.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_r50_fpn_3x_coco/decoupled_solo_r50_fpn_3x_coco_20210821_042504.log.json) | + +- Decoupled SOLO has a decoupled head which is different from SOLO head. + Decoupled SOLO serves as an efficient and equivalent variant in accuracy + of SOLO. Please refer to the corresponding config files for details. + +### Decoupled Light SOLO + +| Backbone | Style | MS train | Lr schd | Mem (GB) | Inf time (fps) | mask AP | Download | +| :------: | :-----: | :------: | :-----: | :------: | :------------: | :-----: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | Y | 3x | 2.2 | 31.2 | 32.9 | [model](https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_light_r50_fpn_3x_coco/decoupled_solo_light_r50_fpn_3x_coco_20210906_142703-e70e226f.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_light_r50_fpn_3x_coco/decoupled_solo_light_r50_fpn_3x_coco_20210906_142703.log.json) | + +- Decoupled Light SOLO using decoupled structure similar to Decoupled + SOLO head, with light-weight head and smaller input size, Please refer + to the corresponding config files for details. + +## Citation + +```latex +@inproceedings{wang2020solo, + title = {{SOLO}: Segmenting Objects by Locations}, + author = {Wang, Xinlong and Kong, Tao and Shen, Chunhua and Jiang, Yuning and Li, Lei}, + booktitle = {Proc. Eur. Conf. Computer Vision (ECCV)}, + year = {2020} +} +``` diff --git a/mmdetection/configs/solo/decoupled-solo-light_r50_fpn_3x_coco.py b/mmdetection/configs/solo/decoupled-solo-light_r50_fpn_3x_coco.py new file mode 100644 index 00000000..fc35df3c --- /dev/null +++ b/mmdetection/configs/solo/decoupled-solo-light_r50_fpn_3x_coco.py @@ -0,0 +1,50 @@ +_base_ = './decoupled-solo_r50_fpn_3x_coco.py' + +# model settings +model = dict( + mask_head=dict( + type='DecoupledSOLOLightHead', + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 8, 16, 32, 32], + scale_ranges=((1, 64), (32, 128), (64, 256), (128, 512), (256, 2048)), + pos_scale=0.2, + num_grids=[40, 36, 24, 16, 12], + cls_down_index=0, + loss_mask=dict( + type='DiceLoss', use_sigmoid=True, activate=False, + loss_weight=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(852, 512), (852, 480), (852, 448), (852, 416), (852, 384), + (852, 352)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(852, 512), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/solo/decoupled-solo_r50_fpn_1x_coco.py b/mmdetection/configs/solo/decoupled-solo_r50_fpn_1x_coco.py new file mode 100644 index 00000000..6d7f4b90 --- /dev/null +++ b/mmdetection/configs/solo/decoupled-solo_r50_fpn_1x_coco.py @@ -0,0 +1,24 @@ +_base_ = './solo_r50_fpn_1x_coco.py' +# model settings +model = dict( + mask_head=dict( + type='DecoupledSOLOHead', + num_classes=80, + in_channels=256, + stacked_convs=7, + feat_channels=256, + strides=[8, 8, 16, 32, 32], + scale_ranges=((1, 96), (48, 192), (96, 384), (192, 768), (384, 2048)), + pos_scale=0.2, + num_grids=[40, 36, 24, 16, 12], + cls_down_index=0, + loss_mask=dict( + type='DiceLoss', use_sigmoid=True, activate=False, + loss_weight=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True))) diff --git a/mmdetection/configs/solo/decoupled-solo_r50_fpn_3x_coco.py b/mmdetection/configs/solo/decoupled-solo_r50_fpn_3x_coco.py new file mode 100644 index 00000000..4a8c19de --- /dev/null +++ b/mmdetection/configs/solo/decoupled-solo_r50_fpn_3x_coco.py @@ -0,0 +1,25 @@ +_base_ = './solo_r50_fpn_3x_coco.py' + +# model settings +model = dict( + mask_head=dict( + type='DecoupledSOLOHead', + num_classes=80, + in_channels=256, + stacked_convs=7, + feat_channels=256, + strides=[8, 8, 16, 32, 32], + scale_ranges=((1, 96), (48, 192), (96, 384), (192, 768), (384, 2048)), + pos_scale=0.2, + num_grids=[40, 36, 24, 16, 12], + cls_down_index=0, + loss_mask=dict( + type='DiceLoss', use_sigmoid=True, activate=False, + loss_weight=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True))) diff --git a/mmdetection/configs/solo/metafile.yml b/mmdetection/configs/solo/metafile.yml new file mode 100644 index 00000000..aa38b8c0 --- /dev/null +++ b/mmdetection/configs/solo/metafile.yml @@ -0,0 +1,115 @@ +Collections: + - Name: SOLO + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - Convolution + - ResNet + Paper: https://arxiv.org/abs/1912.04488 + README: configs/solo/README.md + +Models: + - Name: decoupled-solo_r50_fpn_1x_coco + In Collection: SOLO + Config: configs/solo/decoupled-solo_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.8 + Epochs: 12 + inference time (ms/im): + - value: 116.4 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (1333, 800) + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 33.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_r50_fpn_1x_coco/decoupled_solo_r50_fpn_1x_coco_20210820_233348-6337c589.pth + + - Name: decoupled-solo_r50_fpn_3x_coco + In Collection: SOLO + Config: configs/solo/decoupled-solo_r50_fpn_3x_coco.py + Metadata: + Training Memory (GB): 7.9 + Epochs: 36 + inference time (ms/im): + - value: 117.2 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (1333, 800) + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 36.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_r50_fpn_3x_coco/decoupled_solo_r50_fpn_3x_coco_20210821_042504-7b3301ec.pth + + - Name: decoupled-solo-light_r50_fpn_3x_coco + In Collection: SOLO + Config: configs/solo/decoupled-solo-light_r50_fpn_3x_coco.py + Metadata: + Training Memory (GB): 2.2 + Epochs: 36 + inference time (ms/im): + - value: 35.0 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (852, 512) + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 32.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solo/decoupled_solo_light_r50_fpn_3x_coco/decoupled_solo_light_r50_fpn_3x_coco_20210906_142703-e70e226f.pth + + - Name: solo_r50_fpn_3x_coco + In Collection: SOLO + Config: configs/solo/solo_r50_fpn_3x_coco.py + Metadata: + Training Memory (GB): 7.4 + Epochs: 36 + inference time (ms/im): + - value: 94.2 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (1333, 800) + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 35.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solo/solo_r50_fpn_3x_coco/solo_r50_fpn_3x_coco_20210901_012353-11d224d7.pth + + - Name: solo_r50_fpn_1x_coco + In Collection: SOLO + Config: configs/solo/solo_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 8.0 + Epochs: 12 + inference time (ms/im): + - value: 95.1 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (1333, 800) + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 33.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solo/solo_r50_fpn_1x_coco/solo_r50_fpn_1x_coco_20210821_035055-2290a6b8.pth diff --git a/mmdetection/configs/solo/solo_r101_fpn_8xb8-lsj-200e_coco.py b/mmdetection/configs/solo/solo_r101_fpn_8xb8-lsj-200e_coco.py new file mode 100644 index 00000000..0f49c5c1 --- /dev/null +++ b/mmdetection/configs/solo/solo_r101_fpn_8xb8-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './solo_r50_fpn_8xb8-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/solo/solo_r18_fpn_8xb8-lsj-200e_coco.py b/mmdetection/configs/solo/solo_r18_fpn_8xb8-lsj-200e_coco.py new file mode 100644 index 00000000..977ae54d --- /dev/null +++ b/mmdetection/configs/solo/solo_r18_fpn_8xb8-lsj-200e_coco.py @@ -0,0 +1,7 @@ +_base_ = './solo_r50_fpn_8xb8-lsj-200e_coco.py' + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/solo/solo_r50_fpn_1x_coco.py b/mmdetection/configs/solo/solo_r50_fpn_1x_coco.py new file mode 100644 index 00000000..595e9ffe --- /dev/null +++ b/mmdetection/configs/solo/solo_r50_fpn_1x_coco.py @@ -0,0 +1,62 @@ +_base_ = [ + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + type='SOLO', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50'), + style='pytorch'), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=0, + num_outs=5), + mask_head=dict( + type='SOLOHead', + num_classes=80, + in_channels=256, + stacked_convs=7, + feat_channels=256, + strides=[8, 8, 16, 32, 32], + scale_ranges=((1, 96), (48, 192), (96, 384), (192, 768), (384, 2048)), + pos_scale=0.2, + num_grids=[40, 36, 24, 16, 12], + cls_down_index=0, + loss_mask=dict(type='DiceLoss', use_sigmoid=True, loss_weight=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)), + # model training and testing settings + test_cfg=dict( + nms_pre=500, + score_thr=0.1, + mask_thr=0.5, + filter_thr=0.05, + kernel='gaussian', # gaussian/linear + sigma=2.0, + max_per_img=100)) + +# optimizer +optim_wrapper = dict(optimizer=dict(lr=0.01)) + +val_evaluator = dict(metric='segm') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/solo/solo_r50_fpn_3x_coco.py b/mmdetection/configs/solo/solo_r50_fpn_3x_coco.py new file mode 100644 index 00000000..0d5abbd2 --- /dev/null +++ b/mmdetection/configs/solo/solo_r50_fpn_3x_coco.py @@ -0,0 +1,35 @@ +_base_ = './solo_r50_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 800), (1333, 768), (1333, 736), (1333, 704), + (1333, 672), (1333, 640)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# training schedule for 3x +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 3, + by_epoch=False, + begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=36, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] diff --git a/mmdetection/configs/solo/solo_r50_fpn_8xb8-lsj-200e_coco.py b/mmdetection/configs/solo/solo_r50_fpn_8xb8-lsj-200e_coco.py new file mode 100644 index 00000000..d46bf391 --- /dev/null +++ b/mmdetection/configs/solo/solo_r50_fpn_8xb8-lsj-200e_coco.py @@ -0,0 +1,71 @@ +_base_ = '../common/lsj-200e_coco-instance.py' + +image_size = (1024, 1024) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +# model settings +model = dict( + type='SOLO', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50'), + style='pytorch'), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=0, + num_outs=5), + mask_head=dict( + type='SOLOHead', + num_classes=80, + in_channels=256, + stacked_convs=7, + feat_channels=256, + strides=[8, 8, 16, 32, 32], + scale_ranges=((1, 96), (48, 192), (96, 384), (192, 768), (384, 2048)), + pos_scale=0.2, + num_grids=[40, 36, 24, 16, 12], + cls_down_index=0, + loss_mask=dict(type='DiceLoss', use_sigmoid=True, loss_weight=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)), + # model training and testing settings + test_cfg=dict( + nms_pre=500, + score_thr=0.1, + mask_thr=0.5, + filter_thr=0.05, + kernel='gaussian', # gaussian/linear + sigma=2.0, + max_per_img=100)) + +train_dataloader = dict(batch_size=8, num_workers=4) + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + optimizer=dict( + type='SGD', lr=0.01 * 4, momentum=0.9, weight_decay=0.00004), + clip_grad=dict(max_norm=35, norm_type=2)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/solov2/README.md b/mmdetection/configs/solov2/README.md new file mode 100644 index 00000000..b2169131 --- /dev/null +++ b/mmdetection/configs/solov2/README.md @@ -0,0 +1,59 @@ +# SOLOv2 + +> [SOLOv2: Dynamic and Fast Instance Segmentation](https://arxiv.org/abs/2003.10152) + + + +## Abstract + +In this work, we aim at building a simple, direct, and fast instance segmentation +framework with strong performance. We follow the principle of the SOLO method of +Wang et al. "SOLO: segmenting objects by locations". Importantly, we take one +step further by dynamically learning the mask head of the object segmenter such +that the mask head is conditioned on the location. Specifically, the mask branch +is decoupled into a mask kernel branch and mask feature branch, which are +responsible for learning the convolution kernel and the convolved features +respectively. Moreover, we propose Matrix NMS (non maximum suppression) to +significantly reduce the inference time overhead due to NMS of masks. Our +Matrix NMS performs NMS with parallel matrix operations in one shot, and +yields better results. We demonstrate a simple direct instance segmentation +system, outperforming a few state-of-the-art methods in both speed and accuracy. +A light-weight version of SOLOv2 executes at 31.3 FPS and yields 37.1% AP. +Moreover, our state-of-the-art results in object detection (from our mask byproduct) +and panoptic segmentation show the potential to serve as a new strong baseline +for many instance-level recognition tasks besides instance segmentation. + +
    + +
    + +## Results and Models + +### SOLOv2 + +| Backbone | Style | MS train | Lr schd | Mem (GB) | mask AP | Config | Download | +| :--------: | :-----: | :------: | :-----: | :------: | :-----: | :-------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | N | 1x | 5.1 | 34.8 | [config](./solov2_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r50_fpn_1x_coco/solov2_r50_fpn_1x_coco_20220512_125858-a357fa23.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r50_fpn_1x_coco/solov2_r50_fpn_1x_coco_20220512_125858.log.json) | +| R-50 | pytorch | Y | 3x | 5.1 | 37.5 | [config](./solov2_r50_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r50_fpn_3x_coco/solov2_r50_fpn_3x_coco_20220512_125856-fed092d4.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r50_fpn_3x_coco/solov2_r50_fpn_3x_coco_20220512_125856.log.json) | +| R-101 | pytorch | Y | 3x | 6.9 | 39.1 | [config](./solov2_r101_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r101_fpn_3x_coco/solov2_r101_fpn_3x_coco_20220511_095119-c559a076.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r101_fpn_3x_coco/solov2_r101_fpn_3x_coco_20220511_095119.log.json) | +| R-101(DCN) | pytorch | Y | 3x | 7.1 | 41.2 | [config](./solov2_r101-dcn_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r101_dcn_fpn_3x_coco/solov2_r101_dcn_fpn_3x_coco_20220513_214734-16c966cb.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r101_dcn_fpn_3x_coco/solov2_r101_dcn_fpn_3x_coco_20220513_214734.log.json) | +| X-101(DCN) | pytorch | Y | 3x | 11.3 | 42.4 | [config](./solov2_x101-dcn_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_x101_dcn_fpn_3x_coco/solov2_x101_dcn_fpn_3x_coco_20220513_214337-aef41095.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_x101_dcn_fpn_3x_coco/solov2_x101_dcn_fpn_3x_coco_20220513_214337.log.json) | + +### Light SOLOv2 + +| Backbone | Style | MS train | Lr schd | Mem (GB) | mask AP | Config | Download | +| :------: | :-----: | :------: | :-----: | :------: | :-----: | :--------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-18 | pytorch | Y | 3x | 9.1 | 29.7 | [config](./solov2-light_r18_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_light_r18_fpn_3x_coco/solov2_light_r18_fpn_3x_coco_20220511_083717-75fa355b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_light_r18_fpn_3x_coco/solov2_light_r18_fpn_3x_coco_20220511_083717.log.json) | +| R-34 | pytorch | Y | 3x | 9.3 | 31.9 | [config](./solov2-light_r34_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_light_r34_fpn_3x_coco/solov2_light_r34_fpn_3x_coco_20220511_091839-e51659d3.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_light_r34_fpn_3x_coco/solov2_light_r34_fpn_3x_coco_20220511_091839.log.json) | +| R-50 | pytorch | Y | 3x | 9.9 | 33.7 | [config](./solov2-light_r50_fpn_ms-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_light_r50_fpn_3x_coco/solov2_light_r50_fpn_3x_coco_20220512_165256-c93a6074.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_light_r50_fpn_3x_coco/solov2_light_r50_fpn_3x_coco_20220512_165256.log.json) | + +## Citation + +```latex +@article{wang2020solov2, + title={SOLOv2: Dynamic and Fast Instance Segmentation}, + author={Wang, Xinlong and Zhang, Rufeng and Kong, Tao and Li, Lei and Shen, Chunhua}, + journal={Proc. Advances in Neural Information Processing Systems (NeurIPS)}, + year={2020} +} +``` diff --git a/mmdetection/configs/solov2/metafile.yml b/mmdetection/configs/solov2/metafile.yml new file mode 100644 index 00000000..d0156b2b --- /dev/null +++ b/mmdetection/configs/solov2/metafile.yml @@ -0,0 +1,93 @@ +Collections: + - Name: SOLOv2 + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x A100 GPUs + Architecture: + - FPN + - Convolution + - ResNet + Paper: https://arxiv.org/abs/2003.10152 + README: configs/solov2/README.md + +Models: + - Name: solov2_r50_fpn_1x_coco + In Collection: SOLOv2 + Config: configs/solov2/solov2_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 5.1 + Epochs: 12 + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 34.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r50_fpn_1x_coco/solov2_r50_fpn_1x_coco_20220512_125858-a357fa23.pth + + - Name: solov2_r50_fpn_ms-3x_coco + In Collection: SOLOv2 + Config: configs/solov2/solov2_r50_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 5.1 + Epochs: 36 + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r50_fpn_3x_coco/solov2_r50_fpn_3x_coco_20220512_125856-fed092d4.pth + + - Name: solov2_r101-dcn_fpn_ms-3x_coco + In Collection: SOLOv2 + Config: configs/solov2/solov2_r101-dcn_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 7.1 + Epochs: 36 + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_r101_dcn_fpn_3x_coco/solov2_r101_dcn_fpn_3x_coco_20220513_214734-16c966cb.pth + + - Name: solov2_x101-dcn_fpn_ms-3x_coco + In Collection: SOLOv2 + Config: configs/solov2/solov2_x101-dcn_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 11.3 + Epochs: 36 + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 42.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_x101_dcn_fpn_3x_coco/solov2_x101_dcn_fpn_3x_coco_20220513_214337-aef41095.pth + + - Name: solov2-light_r18_fpn_ms-3x_coco + In Collection: SOLOv2 + Config: configs/solov2/solov2-light_r18_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 9.1 + Epochs: 36 + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 29.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_light_r18_fpn_3x_coco/solov2_light_r18_fpn_3x_coco_20220511_083717-75fa355b.pth + + - Name: solov2-light_r50_fpn_ms-3x_coco + In Collection: SOLOv2 + Config: configs/solov2/solov2-light_r50_fpn_ms-3x_coco.py + Metadata: + Training Memory (GB): 9.9 + Epochs: 36 + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 33.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/solov2/solov2_light_r50_fpn_3x_coco/solov2_light_r50_fpn_3x_coco_20220512_165256-c93a6074.pth diff --git a/mmdetection/configs/solov2/solov2-light_r18_fpn_ms-3x_coco.py b/mmdetection/configs/solov2/solov2-light_r18_fpn_ms-3x_coco.py new file mode 100644 index 00000000..f8fc53e0 --- /dev/null +++ b/mmdetection/configs/solov2/solov2-light_r18_fpn_ms-3x_coco.py @@ -0,0 +1,7 @@ +_base_ = './solov2-light_r50_fpn_ms-3x_coco.py' + +# model settings +model = dict( + backbone=dict( + depth=18, init_cfg=dict(checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/solov2/solov2-light_r34_fpn_ms-3x_coco.py b/mmdetection/configs/solov2/solov2-light_r34_fpn_ms-3x_coco.py new file mode 100644 index 00000000..149b3366 --- /dev/null +++ b/mmdetection/configs/solov2/solov2-light_r34_fpn_ms-3x_coco.py @@ -0,0 +1,7 @@ +_base_ = './solov2-light_r50_fpn_ms-3x_coco.py' + +# model settings +model = dict( + backbone=dict( + depth=34, init_cfg=dict(checkpoint='torchvision://resnet34')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/configs/solov2/solov2-light_r50-dcn_fpn_ms-3x_coco.py b/mmdetection/configs/solov2/solov2-light_r50-dcn_fpn_ms-3x_coco.py new file mode 100644 index 00000000..05391944 --- /dev/null +++ b/mmdetection/configs/solov2/solov2-light_r50-dcn_fpn_ms-3x_coco.py @@ -0,0 +1,14 @@ +_base_ = './solov2-light_r50_fpn_ms-3x_coco.py' + +# model settings +model = dict( + backbone=dict( + dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True)), + mask_head=dict( + feat_channels=256, + stacked_convs=3, + scale_ranges=((1, 64), (32, 128), (64, 256), (128, 512), (256, 2048)), + mask_feature_head=dict(out_channels=128), + dcn_cfg=dict(type='DCNv2'), + dcn_apply_to_all_conv=False)) # light solov2 head diff --git a/mmdetection/configs/solov2/solov2-light_r50_fpn_ms-3x_coco.py b/mmdetection/configs/solov2/solov2-light_r50_fpn_ms-3x_coco.py new file mode 100644 index 00000000..cf0a7f77 --- /dev/null +++ b/mmdetection/configs/solov2/solov2-light_r50_fpn_ms-3x_coco.py @@ -0,0 +1,56 @@ +_base_ = './solov2_r50_fpn_1x_coco.py' + +# model settings +model = dict( + mask_head=dict( + stacked_convs=2, + feat_channels=256, + scale_ranges=((1, 56), (28, 112), (56, 224), (112, 448), (224, 896)), + mask_feature_head=dict(out_channels=128))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(768, 512), (768, 480), (768, 448), (768, 416), (768, 384), + (768, 352)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(448, 768), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# training schedule for 3x +max_epochs = 36 +train_cfg = dict(by_epoch=True, max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 3, + by_epoch=False, + begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=36, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] diff --git a/mmdetection/configs/solov2/solov2_r101-dcn_fpn_ms-3x_coco.py b/mmdetection/configs/solov2/solov2_r101-dcn_fpn_ms-3x_coco.py new file mode 100644 index 00000000..370a4eb7 --- /dev/null +++ b/mmdetection/configs/solov2/solov2_r101-dcn_fpn_ms-3x_coco.py @@ -0,0 +1,13 @@ +_base_ = './solov2_r50_fpn_ms-3x_coco.py' + +# model settings +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(checkpoint='torchvision://resnet101'), + dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True)), + mask_head=dict( + mask_feature_head=dict(conv_cfg=dict(type='DCNv2')), + dcn_cfg=dict(type='DCNv2'), + dcn_apply_to_all_conv=True)) diff --git a/mmdetection/configs/solov2/solov2_r101_fpn_ms-3x_coco.py b/mmdetection/configs/solov2/solov2_r101_fpn_ms-3x_coco.py new file mode 100644 index 00000000..96aaac0a --- /dev/null +++ b/mmdetection/configs/solov2/solov2_r101_fpn_ms-3x_coco.py @@ -0,0 +1,6 @@ +_base_ = './solov2_r50_fpn_ms-3x_coco.py' + +# model settings +model = dict( + backbone=dict( + depth=101, init_cfg=dict(checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/solov2/solov2_r50_fpn_1x_coco.py b/mmdetection/configs/solov2/solov2_r50_fpn_1x_coco.py new file mode 100644 index 00000000..138ca010 --- /dev/null +++ b/mmdetection/configs/solov2/solov2_r50_fpn_1x_coco.py @@ -0,0 +1,70 @@ +_base_ = [ + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# model settings +model = dict( + type='SOLOv2', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50'), + style='pytorch'), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=0, + num_outs=5), + mask_head=dict( + type='SOLOV2Head', + num_classes=80, + in_channels=256, + feat_channels=512, + stacked_convs=4, + strides=[8, 8, 16, 32, 32], + scale_ranges=((1, 96), (48, 192), (96, 384), (192, 768), (384, 2048)), + pos_scale=0.2, + num_grids=[40, 36, 24, 16, 12], + cls_down_index=0, + mask_feature_head=dict( + feat_channels=128, + start_level=0, + end_level=3, + out_channels=256, + mask_stride=4, + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)), + loss_mask=dict(type='DiceLoss', use_sigmoid=True, loss_weight=3.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0)), + # model training and testing settings + test_cfg=dict( + nms_pre=500, + score_thr=0.1, + mask_thr=0.5, + filter_thr=0.05, + kernel='gaussian', # gaussian/linear + sigma=2.0, + max_per_img=100)) + +# optimizer +optim_wrapper = dict( + optimizer=dict(lr=0.01), clip_grad=dict(max_norm=35, norm_type=2)) + +val_evaluator = dict(metric='segm') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/solov2/solov2_r50_fpn_ms-3x_coco.py b/mmdetection/configs/solov2/solov2_r50_fpn_ms-3x_coco.py new file mode 100644 index 00000000..d6f09827 --- /dev/null +++ b/mmdetection/configs/solov2/solov2_r50_fpn_ms-3x_coco.py @@ -0,0 +1,35 @@ +_base_ = './solov2_r50_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 800), (1333, 768), (1333, 736), (1333, 704), + (1333, 672), (1333, 640)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# training schedule for 3x +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 3, + by_epoch=False, + begin=0, + end=500), + dict( + type='MultiStepLR', + begin=0, + end=36, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] diff --git a/mmdetection/configs/solov2/solov2_x101-dcn_fpn_ms-3x_coco.py b/mmdetection/configs/solov2/solov2_x101-dcn_fpn_ms-3x_coco.py new file mode 100644 index 00000000..612c45eb --- /dev/null +++ b/mmdetection/configs/solov2/solov2_x101-dcn_fpn_ms-3x_coco.py @@ -0,0 +1,17 @@ +_base_ = './solov2_r50_fpn_ms-3x_coco.py' + +# model settings +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d')), + mask_head=dict( + mask_feature_head=dict(conv_cfg=dict(type='DCNv2')), + dcn_cfg=dict(type='DCNv2'), + dcn_apply_to_all_conv=True)) diff --git a/mmdetection/configs/sort/README.md b/mmdetection/configs/sort/README.md new file mode 100644 index 00000000..8f035fde --- /dev/null +++ b/mmdetection/configs/sort/README.md @@ -0,0 +1,108 @@ +# Simple online and realtime tracking + +## Abstract + + + +This paper explores a pragmatic approach to multiple object tracking where the main focus is to associate objects efficiently for online and realtime applications. To this end, detection quality is identified as a key factor influencing tracking performance, where changing the detector can improve tracking by up to 18.9%. Despite only using a rudimentary combination of familiar techniques such as the Kalman Filter and Hungarian algorithm for the tracking components, this approach achieves an accuracy comparable to state-of-the-art online trackers. Furthermore, due to the simplicity of our tracking method, the tracker updates at a rate of 260 Hz which is over 20x faster than other state-of-the-art trackers. + + + +
    + +
    + +## Citation + + + +```latex +@inproceedings{bewley2016simple, + title={Simple online and realtime tracking}, + author={Bewley, Alex and Ge, Zongyuan and Ott, Lionel and Ramos, Fabio and Upcroft, Ben}, + booktitle={2016 IEEE International Conference on Image Processing (ICIP)}, + pages={3464--3468}, + year={2016}, + organization={IEEE} +} +``` + +## Results and models on MOT17 + +| Method | Detector | ReID | Train Set | Test Set | Public | Inf time (fps) | HOTA | MOTA | IDF1 | FP | FN | IDSw. | Config | Download | +| :----: | :----------------: | :--: | :--------: | :------: | :----: | :------------: | :--: | :--: | :--: | :---: | :---: | :---: | :----------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------: | +| SORT | R50-FasterRCNN-FPN | - | half-train | half-val | N | 18.6 | 52.0 | 62.0 | 57.8 | 15150 | 40410 | 5847 | [config](sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py) | [detector](https://download.openmmlab.com/mmtracking/mot/faster_rcnn/faster-rcnn_r50_fpn_4e_mot17-half-64ee2ed4.pth) | + +## Get started + +### 1. Development Environment Setup + +Tracking Development Environment Setup can refer to this [document](../../docs/en/get_started.md). + +### 2. Dataset Prepare + +Tracking Dataset Prepare can refer to this [document](../../docs/en/user_guides/tracking_dataset_prepare.md). + +### 3. Training + +We implement SORT with independent detector models. +Note that, due to the influence of parameters such as learning rate in default configuration file, +we recommend using 8 GPUs for training in order to reproduce accuracy. + +You can train the detector as follows. + +```shell script +# Training Faster R-CNN on mot17-half-train dataset with following command. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_train.sh configs/sort/faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 +``` + +If you want to know about more detailed usage of `train.py/dist_train.sh/slurm_train.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 4. Testing and evaluation + +### 4.1 Example on MOTxx-halfval dataset + +**4.1.1 use separate trained detector model to evaluating and testing**\* + +```shell script +# Example 1: Test on motXX-half-val set. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_test_tracking.sh configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 --detector ${DETECTOR_CHECKPOINT_PATH} +``` + +**4.1.2 use video_baesd to evaluating and testing** + +we also provide two_ways(img_based or video_based) to evaluating and testing. +if you want to use video_based to evaluating and testing, you can modify config as follows + +``` +val_dataloader = dict( + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False)) +``` + +### 4.2 Example on MOTxx-test dataset + +If you want to get the results of the [MOT Challenge](https://motchallenge.net/) test set, +please use the following command to generate result files that can be used for submission. +It will be stored in `./mot_17_test_res`, you can modify the saved path in `test_evaluator` of the config. + +```shell script +# Example 2: Test on motxx-test set +# The number after config file represents the number of GPUs used +bash tools/dist_test_tracking.sh configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17test.py 8 --detector ${DETECTOR_CHECKPOINT_PATH} +``` + +If you want to know about more detailed usage of `test_tracking.py/dist_test_tracking.sh/slurm_test_tracking.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 5.Inference + +Use a single GPU to predict a video and save it as a video. + +```shell +python demo/mot_demo.py demo/demo_mot.mp4 configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py --detector ${DETECTOR_CHECKPOINT_PATH} --out mot.mp4 +``` + +If you want to know about more detailed usage of `mot_demo.py`, please refer to this [document](../../docs/en/user_guides/tracking_inference.md). diff --git a/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py b/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..f1d5b72c --- /dev/null +++ b/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py @@ -0,0 +1,41 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/mot_challenge_det.py', '../_base_/default_runtime.py' +] + +model = dict( + rpn_head=dict( + bbox_coder=dict(clip_border=False), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + bbox_head=dict( + num_classes=1, + bbox_coder=dict(clip_border=False), + loss_bbox=dict(type='SmoothL1Loss', loss_weight=1.0))), + init_cfg=dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'http://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_2x_coco/faster_rcnn_r50_fpn_2x_coco_bbox_mAP-0.384_20200504_210434-a5d8aa15.pth' # noqa: E501 + )) + +# training schedule for 4e +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=4, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict(type='LinearLR', start_factor=0.01, by_epoch=False, begin=0, end=100), + dict( + type='MultiStepLR', + begin=0, + end=4, + by_epoch=True, + milestones=[3], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17train.py b/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17train.py new file mode 100644 index 00000000..83647061 --- /dev/null +++ b/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17train.py @@ -0,0 +1,11 @@ +_base_ = ['./faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval'] +# data +data_root = 'data/MOT17/' +train_dataloader = dict( + dataset=dict(ann_file='annotations/train_cocoformat.json')) +val_dataloader = dict( + dataset=dict(ann_file='annotations/train_cocoformat.json')) +test_dataloader = val_dataloader + +val_evaluator = dict(ann_file=data_root + 'annotations/train_cocoformat.json') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-8e_mot20halftrain_test-mot20halfval.py b/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-8e_mot20halftrain_test-mot20halfval.py new file mode 100644 index 00000000..a6d14ad8 --- /dev/null +++ b/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-8e_mot20halftrain_test-mot20halfval.py @@ -0,0 +1,29 @@ +_base_ = ['./faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval'] +model = dict( + rpn_head=dict(bbox_coder=dict(clip_border=True)), + roi_head=dict( + bbox_head=dict(bbox_coder=dict(clip_border=True), num_classes=1))) +# data +data_root = 'data/MOT20/' +train_dataloader = dict(dataset=dict(data_root=data_root)) +val_dataloader = dict(dataset=dict(data_root=data_root)) +test_dataloader = val_dataloader + +val_evaluator = dict(ann_file=data_root + + 'annotations/half-val_cocoformat.json') +test_evaluator = val_evaluator + +# training schedule for 8e +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=8, val_interval=1) + +# learning rate +param_scheduler = [ + dict(type='LinearLR', start_factor=0.01, by_epoch=False, begin=0, end=100), + dict( + type='MultiStepLR', + begin=0, + end=8, + by_epoch=True, + milestones=[6], + gamma=0.1) +] diff --git a/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-8e_mot20train_test-mot20train.py b/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-8e_mot20train_test-mot20train.py new file mode 100644 index 00000000..85c85973 --- /dev/null +++ b/mmdetection/configs/sort/faster-rcnn_r50_fpn_8xb2-8e_mot20train_test-mot20train.py @@ -0,0 +1,32 @@ +_base_ = ['./faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval'] +model = dict( + rpn_head=dict(bbox_coder=dict(clip_border=True)), + roi_head=dict( + bbox_head=dict(bbox_coder=dict(clip_border=True), num_classes=1))) +# data +data_root = 'data/MOT20/' +train_dataloader = dict( + dataset=dict( + data_root=data_root, ann_file='annotations/train_cocoformat.json')) +val_dataloader = dict( + dataset=dict( + data_root=data_root, ann_file='annotations/train_cocoformat.json')) +test_dataloader = val_dataloader + +val_evaluator = dict(ann_file=data_root + 'annotations/train_cocoformat.json') +test_evaluator = val_evaluator + +# training schedule for 8e +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=8, val_interval=1) + +# learning rate +param_scheduler = [ + dict(type='LinearLR', start_factor=0.01, by_epoch=False, begin=0, end=100), + dict( + type='MultiStepLR', + begin=0, + end=8, + by_epoch=True, + milestones=[6], + gamma=0.1) +] diff --git a/mmdetection/configs/sort/metafile.yml b/mmdetection/configs/sort/metafile.yml new file mode 100644 index 00000000..c582ce35 --- /dev/null +++ b/mmdetection/configs/sort/metafile.yml @@ -0,0 +1,35 @@ +Collections: + - Name: SORT + Metadata: + Training Techniques: + - SGD with Momentum + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + - FPN + Paper: + URL: https://arxiv.org/abs/1602.00763 + Title: Simple Online and Realtime Tracking + README: configs/sort/README.md + +Models: + - Name: sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval + In Collection: SORT + Config: configs/mot/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py + Metadata: + Training Data: MOT17-half-train + inference time (ms/im): + - value: 53.8 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (640, 1088) + Results: + - Task: Multiple Object Tracking + Dataset: MOT17-half-val + Metrics: + MOTA: 62.0 + IDF1: 57.8 + HOTA: 52.0 + Weights: https://download.openmmlab.com/mmtracking/mot/faster_rcnn/faster-rcnn_r50_fpn_4e_mot17-half-64ee2ed4.pth diff --git a/mmdetection/configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py b/mmdetection/configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..78acb774 --- /dev/null +++ b/mmdetection/configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py @@ -0,0 +1,54 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/mot_challenge.py', '../_base_/default_runtime.py' +] + +default_hooks = dict( + logger=dict(type='LoggerHook', interval=1), + visualization=dict(type='TrackVisualizationHook', draw=False)) + +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='TrackLocalVisualizer', vis_backends=vis_backends, name='visualizer') + +# custom hooks +custom_hooks = [ + # Synchronize model buffers such as running_mean and running_var in BN + # at the end of each epoch + dict(type='SyncBuffersHook') +] + +detector = _base_.model +detector.pop('data_preprocessor') +detector.rpn_head.bbox_coder.update(dict(clip_border=False)) +detector.roi_head.bbox_head.update(dict(num_classes=1)) +detector.roi_head.bbox_head.bbox_coder.update(dict(clip_border=False)) +detector['init_cfg'] = dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmtracking/mot/' + 'faster_rcnn/faster-rcnn_r50_fpn_4e_mot17-half-64ee2ed4.pth') # noqa: E501 +del _base_.model + +model = dict( + type='DeepSORT', + data_preprocessor=dict( + type='TrackDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + rgb_to_bgr=False, + pad_size_divisor=32), + detector=detector, + tracker=dict( + type='SORTTracker', + motion=dict(type='KalmanFilter', center_only=False), + obj_score_thr=0.5, + match_iou_thr=0.5, + reid=None)) + +train_dataloader = None + +train_cfg = None +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') diff --git a/mmdetection/configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17test.py b/mmdetection/configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17test.py new file mode 100644 index 00000000..921652c4 --- /dev/null +++ b/mmdetection/configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17train_test-mot17test.py @@ -0,0 +1,15 @@ +_base_ = [ + './sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain' + '_test-mot17halfval.py' +] + +# dataloader +val_dataloader = dict( + dataset=dict(ann_file='annotations/train_cocoformat.json')) +test_dataloader = dict( + dataset=dict( + ann_file='annotations/test_cocoformat.json', + data_prefix=dict(img_path='test'))) + +# evaluator +test_evaluator = dict(format_only=True, outfile_prefix='./mot_17_test_res') diff --git a/mmdetection/configs/sparse_rcnn/README.md b/mmdetection/configs/sparse_rcnn/README.md new file mode 100644 index 00000000..2e8e365b --- /dev/null +++ b/mmdetection/configs/sparse_rcnn/README.md @@ -0,0 +1,38 @@ +# Sparse R-CNN + +> [Sparse R-CNN: End-to-End Object Detection with Learnable Proposals](https://arxiv.org/abs/2011.12450) + + + +## Abstract + +We present Sparse R-CNN, a purely sparse method for object detection in images. Existing works on object detection heavily rely on dense object candidates, such as k anchor boxes pre-defined on all grids of image feature map of size H×W. In our method, however, a fixed sparse set of learned object proposals, total length of N, are provided to object recognition head to perform classification and location. By eliminating HWk (up to hundreds of thousands) hand-designed object candidates to N (e.g. 100) learnable proposals, Sparse R-CNN completely avoids all efforts related to object candidates design and many-to-one label assignment. More importantly, final predictions are directly output without non-maximum suppression post-procedure. Sparse R-CNN demonstrates accuracy, run-time and training convergence performance on par with the well-established detector baselines on the challenging COCO dataset, e.g., achieving 45.0 AP in standard 3× training schedule and running at 22 fps using ResNet-50 FPN model. We hope our work could inspire re-thinking the convention of dense prior in object detectors. + +
    + +
    + +## Results and Models + +| Model | Backbone | Style | Lr schd | Number of Proposals | Multi-Scale | RandomCrop | box AP | Config | Download | +| :----------: | :-------: | :-----: | :-----: | :-----------------: | :---------: | :--------: | :----: | :-----------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Sparse R-CNN | R-50-FPN | pytorch | 1x | 100 | False | False | 37.9 | [config](./sparse-rcnn_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_1x_coco/sparse_rcnn_r50_fpn_1x_coco_20201222_214453-dc79b137.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_1x_coco/sparse_rcnn_r50_fpn_1x_coco_20201222_214453-dc79b137.log.json) | +| Sparse R-CNN | R-50-FPN | pytorch | 3x | 100 | True | False | 42.8 | [config](./sparse-rcnn_r50_fpn_ms-480-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_mstrain_480-800_3x_coco/sparse_rcnn_r50_fpn_mstrain_480-800_3x_coco_20201218_154234-7bc5c054.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_mstrain_480-800_3x_coco/sparse_rcnn_r50_fpn_mstrain_480-800_3x_coco_20201218_154234-7bc5c054.log.json) | +| Sparse R-CNN | R-50-FPN | pytorch | 3x | 300 | True | True | 45.0 | [config](./sparse-rcnn_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco/sparse_rcnn_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20201223_024605-9fe92701.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco/sparse_rcnn_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20201223_024605-9fe92701.log.json) | +| Sparse R-CNN | R-101-FPN | pytorch | 3x | 100 | True | False | 44.2 | [config](./sparse-rcnn_r101_fpn_ms-480-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r101_fpn_mstrain_480-800_3x_coco/sparse_rcnn_r101_fpn_mstrain_480-800_3x_coco_20201223_121552-6c46c9d6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r101_fpn_mstrain_480-800_3x_coco/sparse_rcnn_r101_fpn_mstrain_480-800_3x_coco_20201223_121552-6c46c9d6.log.json) | +| Sparse R-CNN | R-101-FPN | pytorch | 3x | 300 | True | True | 46.2 | [config](./sparse-rcnn_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco/sparse_rcnn_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20201223_023452-c23c3564.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco/sparse_rcnn_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20201223_023452-c23c3564.log.json) | + +### Notes + +We observe about 0.3 AP noise especially when using ResNet-101 as the backbone. + +## Citation + +```latex +@article{peize2020sparse, + title = {{SparseR-CNN}: End-to-End Object Detection with Learnable Proposals}, + author = {Peize Sun and Rufeng Zhang and Yi Jiang and Tao Kong and Chenfeng Xu and Wei Zhan and Masayoshi Tomizuka and Lei Li and Zehuan Yuan and Changhu Wang and Ping Luo}, + journal = {arXiv preprint arXiv:2011.12450}, + year = {2020} +} +``` diff --git a/mmdetection/configs/sparse_rcnn/metafile.yml b/mmdetection/configs/sparse_rcnn/metafile.yml new file mode 100644 index 00000000..8fe25318 --- /dev/null +++ b/mmdetection/configs/sparse_rcnn/metafile.yml @@ -0,0 +1,80 @@ +Collections: + - Name: Sparse R-CNN + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - ResNet + - Sparse R-CNN + Paper: + URL: https://arxiv.org/abs/2011.12450 + Title: 'Sparse R-CNN: End-to-End Object Detection with Learnable Proposals' + README: configs/sparse_rcnn/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.9.0/mmdet/models/detectors/sparse_rcnn.py#L6 + Version: v2.9.0 + +Models: + - Name: sparse-rcnn_r50_fpn_1x_coco + In Collection: Sparse R-CNN + Config: configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_1x_coco/sparse_rcnn_r50_fpn_1x_coco_20201222_214453-dc79b137.pth + + - Name: sparse-rcnn_r50_fpn_ms-480-800-3x_coco + In Collection: Sparse R-CNN + Config: configs/sparse_rcnn/sparse-rcnn_r50_fpn_ms-480-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_mstrain_480-800_3x_coco/sparse_rcnn_r50_fpn_mstrain_480-800_3x_coco_20201218_154234-7bc5c054.pth + + - Name: sparse-rcnn_r50_fpn_300-proposals_crop-ms-480-800-3x_coco + In Collection: Sparse R-CNN + Config: configs/sparse_rcnn/sparse-rcnn_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 45.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco/sparse_rcnn_r50_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20201223_024605-9fe92701.pth + + - Name: sparse-rcnn_r101_fpn_ms-480-800-3x_coco + In Collection: Sparse R-CNN + Config: configs/sparse_rcnn/sparse-rcnn_r101_fpn_ms-480-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r101_fpn_mstrain_480-800_3x_coco/sparse_rcnn_r101_fpn_mstrain_480-800_3x_coco_20201223_121552-6c46c9d6.pth + + - Name: sparse-rcnn_r101_fpn_300-proposals_crop-ms-480-800-3x_coco + In Collection: Sparse R-CNN + Config: configs/sparse_rcnn/sparse-rcnn_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/sparse_rcnn/sparse_rcnn_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco/sparse_rcnn_r101_fpn_300_proposals_crop_mstrain_480-800_3x_coco_20201223_023452-c23c3564.pth diff --git a/mmdetection/configs/sparse_rcnn/sparse-rcnn_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py new file mode 100644 index 00000000..09c11c65 --- /dev/null +++ b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r101_fpn_300-proposals_crop-ms-480-800-3x_coco.py @@ -0,0 +1,7 @@ +_base_ = './sparse-rcnn_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/sparse_rcnn/sparse-rcnn_r101_fpn_ms-480-800-3x_coco.py b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r101_fpn_ms-480-800-3x_coco.py new file mode 100644 index 00000000..a51f11ce --- /dev/null +++ b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r101_fpn_ms-480-800-3x_coco.py @@ -0,0 +1,7 @@ +_base_ = './sparse-rcnn_r50_fpn_ms-480-800-3x_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..88354427 --- /dev/null +++ b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,101 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +num_stages = 6 +num_proposals = 100 +model = dict( + type='SparseRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=0, + add_extra_convs='on_input', + num_outs=4), + rpn_head=dict( + type='EmbeddingRPNHead', + num_proposals=num_proposals, + proposal_feature_channel=256), + roi_head=dict( + type='SparseRoIHead', + num_stages=num_stages, + stage_loss_weights=[1] * num_stages, + proposal_feature_channel=256, + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type='DIIHead', + num_classes=80, + num_ffn_fcs=2, + num_heads=8, + num_cls_fcs=1, + num_reg_fcs=3, + feedforward_channels=2048, + in_channels=256, + dropout=0.0, + ffn_act_cfg=dict(type='ReLU', inplace=True), + dynamic_conv_cfg=dict( + type='DynamicConv', + in_channels=256, + feat_channels=64, + out_channels=256, + input_feat_shape=7, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN')), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=2.0), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + clip_border=False, + target_means=[0., 0., 0., 0.], + target_stds=[0.5, 0.5, 1., 1.])) for _ in range(num_stages) + ]), + # training and testing settings + train_cfg=dict( + rpn=None, + rcnn=[ + dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xyxy'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ]), + sampler=dict(type='PseudoSampler'), + pos_weight=1) for _ in range(num_stages) + ]), + test_cfg=dict(rpn=None, rcnn=dict(max_per_img=num_proposals))) + +# optimizer +optim_wrapper = dict( + optimizer=dict( + _delete_=True, type='AdamW', lr=0.000025, weight_decay=0.0001), + clip_grad=dict(max_norm=1, norm_type=2)) diff --git a/mmdetection/configs/sparse_rcnn/sparse-rcnn_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py new file mode 100644 index 00000000..93edc031 --- /dev/null +++ b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r50_fpn_300-proposals_crop-ms-480-800-3x_coco.py @@ -0,0 +1,43 @@ +_base_ = './sparse-rcnn_r50_fpn_ms-480-800-3x_coco.py' +num_proposals = 300 +model = dict( + rpn_head=dict(num_proposals=num_proposals), + test_cfg=dict( + _delete_=True, rpn=None, rcnn=dict(max_per_img=num_proposals))) + +# augmentation strategy originates from DETR. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[[ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True) + ]]), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/sparse_rcnn/sparse-rcnn_r50_fpn_ms-480-800-3x_coco.py b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r50_fpn_ms-480-800-3x_coco.py new file mode 100644 index 00000000..156028d7 --- /dev/null +++ b/mmdetection/configs/sparse_rcnn/sparse-rcnn_r50_fpn_ms-480-800-3x_coco.py @@ -0,0 +1,32 @@ +_base_ = './sparse-rcnn_r50_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# learning policy +max_epochs = 36 +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=max_epochs) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] diff --git a/mmdetection/configs/ssd/README.md b/mmdetection/configs/ssd/README.md new file mode 100644 index 00000000..8b3ca912 --- /dev/null +++ b/mmdetection/configs/ssd/README.md @@ -0,0 +1,62 @@ +# SSD + +> [SSD: Single Shot MultiBox Detector](https://arxiv.org/abs/1512.02325) + + + +## Abstract + +We present a method for detecting objects in images using a single deep neural network. Our approach, named SSD, discretizes the output space of bounding boxes into a set of default boxes over different aspect ratios and scales per feature map location. At prediction time, the network generates scores for the presence of each object category in each default box and produces adjustments to the box to better match the object shape. Additionally, the network combines predictions from multiple feature maps with different resolutions to naturally handle objects of various sizes. Our SSD model is simple relative to methods that require object proposals because it completely eliminates proposal generation and subsequent pixel or feature resampling stage and encapsulates all computation in a single network. This makes SSD easy to train and straightforward to integrate into systems that require a detection component. Experimental results on the PASCAL VOC, MS COCO, and ILSVRC datasets confirm that SSD has comparable accuracy to methods that utilize an additional object proposal step and is much faster, while providing a unified framework for both training and inference. Compared to other single stage methods, SSD has much better accuracy, even with a smaller input image size. For 300×300 input, SSD achieves 72.1% mAP on VOC2007 test at 58 FPS on a Nvidia Titan X and for 500×500 input, SSD achieves 75.1% mAP, outperforming a comparable state of the art Faster R-CNN model. + +
    + +
    + +## Results and models of SSD + +| Backbone | Size | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :------: | :--: | :---: | :-----: | :------: | :------------: | :----: | :------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| VGG16 | 300 | caffe | 120e | 9.9 | 43.7 | 25.5 | [config](./ssd300_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ssd/ssd300_coco/ssd300_coco_20210803_015428-d231a06e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ssd/ssd300_coco/ssd300_coco_20210803_015428.log.json) | +| VGG16 | 512 | caffe | 120e | 19.4 | 30.7 | 29.5 | [config](./ssd512_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ssd/ssd512_coco/ssd512_coco_20210803_022849-0a47a1ca.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ssd/ssd512_coco/ssd512_coco_20210803_022849.log.json) | + +## Results and models of SSD-Lite + +| Backbone | Size | Training from scratch | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :---------: | :--: | :-------------------: | :-----: | :------: | :------------: | :----: | :--------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| MobileNetV2 | 320 | yes | 600e | 4.0 | 69.9 | 21.3 | [config](./ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/ssd/ssdlite_mobilenetv2_scratch_600e_coco/ssdlite_mobilenetv2_scratch_600e_coco_20210629_110627-974d9307.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/ssd/ssdlite_mobilenetv2_scratch_600e_coco/ssdlite_mobilenetv2_scratch_600e_coco_20210629_110627.log.json) | + +## Notice + +### Compatibility + +In v2.14.0, [PR5291](https://github.com/open-mmlab/mmdetection/pull/5291) refactored SSD neck and head for more +flexible usage. If users want to use the SSD checkpoint trained in the older versions, we provide a scripts +`tools/model_converters/upgrade_ssd_version.py` to convert the model weights. + +```bash +python tools/model_converters/upgrade_ssd_version.py ${OLD_MODEL_PATH} ${NEW_MODEL_PATH} + +``` + +- OLD_MODEL_PATH: the path to load the old version SSD model. +- NEW_MODEL_PATH: the path to save the converted model weights. + +### SSD-Lite training settings + +There are some differences between our implementation of MobileNetV2 SSD-Lite and the one in [TensorFlow 1.x detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf1_detection_zoo.md) . + +1. Use 320x320 as input size instead of 300x300. +2. The anchor sizes are different. +3. The C4 feature map is taken from the last layer of stage 4 instead of the middle of the block. +4. The model in TensorFlow1.x is trained on coco 2014 and validated on coco minival2014, but we trained and validated the model on coco 2017. The mAP on val2017 is usually a little lower than minival2014 (refer to the results in TensorFlow Object Detection API, e.g., MobileNetV2 SSD gets 22 mAP on minival2014 but 20.2 mAP on val2017). + +## Citation + +```latex +@article{Liu_2016, + title={SSD: Single Shot MultiBox Detector}, + journal={ECCV}, + author={Liu, Wei and Anguelov, Dragomir and Erhan, Dumitru and Szegedy, Christian and Reed, Scott and Fu, Cheng-Yang and Berg, Alexander C.}, + year={2016}, +} +``` diff --git a/mmdetection/configs/ssd/metafile.yml b/mmdetection/configs/ssd/metafile.yml new file mode 100644 index 00000000..190a207c --- /dev/null +++ b/mmdetection/configs/ssd/metafile.yml @@ -0,0 +1,78 @@ +Collections: + - Name: SSD + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - VGG + Paper: + URL: https://arxiv.org/abs/1512.02325 + Title: 'SSD: Single Shot MultiBox Detector' + README: configs/ssd/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.14.0/mmdet/models/dense_heads/ssd_head.py#L16 + Version: v2.14.0 + +Models: + - Name: ssd300_coco + In Collection: SSD + Config: configs/ssd/ssd300_coco.py + Metadata: + Training Memory (GB): 9.9 + inference time (ms/im): + - value: 22.88 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (300, 300) + Epochs: 120 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 25.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ssd/ssd300_coco/ssd300_coco_20210803_015428-d231a06e.pth + + - Name: ssd512_coco + In Collection: SSD + Config: configs/ssd/ssd512_coco.py + Metadata: + Training Memory (GB): 19.4 + inference time (ms/im): + - value: 32.57 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (512, 512) + Epochs: 120 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 29.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ssd/ssd512_coco/ssd512_coco_20210803_022849-0a47a1ca.pth + + - Name: ssdlite_mobilenetv2-scratch_8xb24-600e_coco + In Collection: SSD + Config: configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py + Metadata: + Training Memory (GB): 4.0 + inference time (ms/im): + - value: 14.3 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (320, 320) + Epochs: 600 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 21.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/ssd/ssdlite_mobilenetv2_scratch_600e_coco/ssdlite_mobilenetv2_scratch_600e_coco_20210629_110627-974d9307.pth diff --git a/mmdetection/configs/ssd/ssd300_coco.py b/mmdetection/configs/ssd/ssd300_coco.py new file mode 100644 index 00000000..796d25c9 --- /dev/null +++ b/mmdetection/configs/ssd/ssd300_coco.py @@ -0,0 +1,71 @@ +_base_ = [ + '../_base_/models/ssd300.py', '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] + +# dataset settings +input_size = 300 +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='Expand', + mean={{_base_.model.data_preprocessor.mean}}, + to_rgb={{_base_.model.data_preprocessor.bgr_to_rgb}}, + ratio_range=(1, 4)), + dict( + type='MinIoURandomCrop', + min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=8, + num_workers=2, + batch_sampler=None, + dataset=dict( + _delete_=True, + type='RepeatDataset', + times=5, + dataset=dict( + type={{_base_.dataset_type}}, + data_root={{_base_.data_root}}, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args={{_base_.backend_args}}))) +val_dataloader = dict(batch_size=8, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=2e-3, momentum=0.9, weight_decay=5e-4)) + +custom_hooks = [ + dict(type='NumClassCheckHook'), + dict(type='CheckInvalidLossHook', interval=50, priority='VERY_LOW') +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/ssd/ssd512_coco.py b/mmdetection/configs/ssd/ssd512_coco.py new file mode 100644 index 00000000..7acd6144 --- /dev/null +++ b/mmdetection/configs/ssd/ssd512_coco.py @@ -0,0 +1,60 @@ +_base_ = 'ssd300_coco.py' + +# model settings +input_size = 512 +model = dict( + neck=dict( + out_channels=(512, 1024, 512, 256, 256, 256, 256), + level_strides=(2, 2, 2, 2, 1), + level_paddings=(1, 1, 1, 1, 1), + last_kernel_size=4), + bbox_head=dict( + in_channels=(512, 1024, 512, 256, 256, 256, 256), + anchor_generator=dict( + type='SSDAnchorGenerator', + scale_major=False, + input_size=input_size, + basesize_ratio_range=(0.1, 0.9), + strides=[8, 16, 32, 64, 128, 256, 512], + ratios=[[2], [2, 3], [2, 3], [2, 3], [2, 3], [2], [2]]))) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='Expand', + mean={{_base_.model.data_preprocessor.mean}}, + to_rgb={{_base_.model.data_preprocessor.bgr_to_rgb}}, + ratio_range=(1, 4)), + dict( + type='MinIoURandomCrop', + min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict(dataset=dict(dataset=dict(pipeline=train_pipeline))) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py b/mmdetection/configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py new file mode 100644 index 00000000..4e508f20 --- /dev/null +++ b/mmdetection/configs/ssd/ssdlite_mobilenetv2-scratch_8xb24-600e_coco.py @@ -0,0 +1,158 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# model settings +data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1) +model = dict( + type='SingleStageDetector', + data_preprocessor=data_preprocessor, + backbone=dict( + type='MobileNetV2', + out_indices=(4, 7), + norm_cfg=dict(type='BN', eps=0.001, momentum=0.03), + init_cfg=dict(type='TruncNormal', layer='Conv2d', std=0.03)), + neck=dict( + type='SSDNeck', + in_channels=(96, 1280), + out_channels=(96, 1280, 512, 256, 256, 128), + level_strides=(2, 2, 2, 2), + level_paddings=(1, 1, 1, 1), + l2_norm_scale=None, + use_depthwise=True, + norm_cfg=dict(type='BN', eps=0.001, momentum=0.03), + act_cfg=dict(type='ReLU6'), + init_cfg=dict(type='TruncNormal', layer='Conv2d', std=0.03)), + bbox_head=dict( + type='SSDHead', + in_channels=(96, 1280, 512, 256, 256, 128), + num_classes=80, + use_depthwise=True, + norm_cfg=dict(type='BN', eps=0.001, momentum=0.03), + act_cfg=dict(type='ReLU6'), + init_cfg=dict(type='Normal', layer='Conv2d', std=0.001), + + # set anchor size manually instead of using the predefined + # SSD300 setting. + anchor_generator=dict( + type='SSDAnchorGenerator', + scale_major=False, + strides=[16, 32, 64, 107, 160, 320], + ratios=[[2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3]], + min_sizes=[48, 100, 150, 202, 253, 304], + max_sizes=[100, 150, 202, 253, 304, 320]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2])), + # model training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0., + ignore_iof_thr=-1, + gt_max_assign_all=False), + sampler=dict(type='PseudoSampler'), + smoothl1_beta=1., + allowed_border=-1, + pos_weight=-1, + neg_pos_ratio=3, + debug=False), + test_cfg=dict( + nms_pre=1000, + nms=dict(type='nms', iou_threshold=0.45), + min_bbox_size=0, + score_thr=0.02, + max_per_img=200)) +env_cfg = dict(cudnn_benchmark=True) + +# dataset settings +input_size = 320 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='Expand', + mean=data_preprocessor['mean'], + to_rgb=data_preprocessor['bgr_to_rgb'], + ratio_range=(1, 4)), + dict( + type='MinIoURandomCrop', + min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=24, + num_workers=4, + batch_sampler=None, + dataset=dict( + _delete_=True, + type='RepeatDataset', + times=5, + dataset=dict( + type={{_base_.dataset_type}}, + data_root={{_base_.data_root}}, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline))) +val_dataloader = dict(batch_size=8, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# training schedule +max_epochs = 120 +train_cfg = dict(max_epochs=max_epochs, val_interval=5) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='CosineAnnealingLR', + begin=0, + T_max=max_epochs, + end=max_epochs, + by_epoch=True, + eta_min=0) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.015, momentum=0.9, weight_decay=4.0e-5)) + +custom_hooks = [ + dict(type='NumClassCheckHook'), + dict(type='CheckInvalidLossHook', interval=50, priority='VERY_LOW') +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (24 samples per GPU) +auto_scale_lr = dict(base_batch_size=192) diff --git a/mmdetection/configs/strong_baselines/README.md b/mmdetection/configs/strong_baselines/README.md new file mode 100644 index 00000000..e5db3e08 --- /dev/null +++ b/mmdetection/configs/strong_baselines/README.md @@ -0,0 +1,20 @@ +# Strong Baselines + + + +We train Mask R-CNN with large-scale jitter and longer schedule as strong baselines. +The modifications follow those in [Detectron2](https://github.com/facebookresearch/detectron2/tree/master/configs/new_baselines). + +## Results and Models + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :------: | :-----: | :-----: | :------: | :------------: | :----: | :-----: | :--------------------------------------------------------------------------------: | :----------------------: | +| R-50-FPN | pytorch | 50e | | | | | [config](./mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-50e_coco.py) | [model](<>) \| [log](<>) | +| R-50-FPN | pytorch | 100e | | | | | [config](./mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py) | [model](<>) \| [log](<>) | +| R-50-FPN | caffe | 100e | | | 44.7 | 40.4 | [config](./mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py) | [model](<>) \| [log](<>) | +| R-50-FPN | caffe | 400e | | | | | [config](./mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-400e_coco.py) | [model](<>) \| [log](<>) | + +## Notice + +When using large-scale jittering, there are sometimes empty proposals in the box and mask heads during training. +This requires MMSyncBN that allows empty tensors. Therefore, please use mmcv-full>=1.3.14 to train models supported in this directory. diff --git a/mmdetection/configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_amp-lsj-100e_coco.py b/mmdetection/configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_amp-lsj-100e_coco.py new file mode 100644 index 00000000..b004d740 --- /dev/null +++ b/mmdetection/configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_amp-lsj-100e_coco.py @@ -0,0 +1,4 @@ +_base_ = 'mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py' # noqa + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict(type='AmpOptimWrapper') diff --git a/mmdetection/configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py b/mmdetection/configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py new file mode 100644 index 00000000..70e92a82 --- /dev/null +++ b/mmdetection/configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py @@ -0,0 +1,68 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../common/lsj-100e_coco-instance.py' +] +image_size = (1024, 1024) +batch_augments = [ + dict(type='BatchFixedSizePad', size=image_size, pad_mask=True) +] +norm_cfg = dict(type='SyncBN', requires_grad=True) +# Use MMSyncBN that handles empty tensor in head. It can be changed to +# SyncBN after https://github.com/pytorch/pytorch/issues/36530 is fixed +head_norm_cfg = dict(type='MMSyncBN', requires_grad=True) +model = dict( + # use caffe norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + + # pad_size_divisor=32 is unnecessary in training but necessary + # in testing. + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + frozen_stages=-1, + norm_eval=False, + norm_cfg=norm_cfg, + init_cfg=None, + style='caffe'), + neck=dict(norm_cfg=norm_cfg), + rpn_head=dict(num_convs=2), + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=head_norm_cfg), + mask_head=dict(norm_cfg=head_norm_cfg))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +# Use RepeatDataset to speed up training +train_dataloader = dict(dataset=dict(dataset=dict(pipeline=train_pipeline))) diff --git a/mmdetection/configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-400e_coco.py b/mmdetection/configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-400e_coco.py new file mode 100644 index 00000000..cb64c9b6 --- /dev/null +++ b/mmdetection/configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-400e_coco.py @@ -0,0 +1,20 @@ +_base_ = './mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py' # noqa + +# Use RepeatDataset to speed up training +# change repeat time from 4 (for 100 epochs) to 16 (for 400 epochs) +train_dataloader = dict(dataset=dict(times=4 * 4)) +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.067, + by_epoch=False, + begin=0, + end=500 * 4), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] diff --git a/mmdetection/configs/strong_baselines/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_amp-lsj-100e_coco.py b/mmdetection/configs/strong_baselines/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_amp-lsj-100e_coco.py new file mode 100644 index 00000000..7fab2c72 --- /dev/null +++ b/mmdetection/configs/strong_baselines/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_amp-lsj-100e_coco.py @@ -0,0 +1,4 @@ +_base_ = 'mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py' + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict(type='AmpOptimWrapper') diff --git a/mmdetection/configs/strong_baselines/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py b/mmdetection/configs/strong_baselines/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py new file mode 100644 index 00000000..8e06587f --- /dev/null +++ b/mmdetection/configs/strong_baselines/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py @@ -0,0 +1,30 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../common/lsj-100e_coco-instance.py' +] + +image_size = (1024, 1024) +batch_augments = [ + dict(type='BatchFixedSizePad', size=image_size, pad_mask=True) +] +norm_cfg = dict(type='SyncBN', requires_grad=True) +# Use MMSyncBN that handles empty tensor in head. It can be changed to +# SyncBN after https://github.com/pytorch/pytorch/issues/36530 is fixed +head_norm_cfg = dict(type='MMSyncBN', requires_grad=True) +model = dict( + # the model is trained from scratch, so init_cfg is None + data_preprocessor=dict( + # pad_size_divisor=32 is unnecessary in training but necessary + # in testing. + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + frozen_stages=-1, norm_eval=False, norm_cfg=norm_cfg, init_cfg=None), + neck=dict(norm_cfg=norm_cfg), + rpn_head=dict(num_convs=2), # leads to 0.1+ mAP + roi_head=dict( + bbox_head=dict( + type='Shared4Conv1FCBBoxHead', + conv_out_channels=256, + norm_cfg=head_norm_cfg), + mask_head=dict(norm_cfg=head_norm_cfg))) diff --git a/mmdetection/configs/strong_baselines/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-50e_coco.py b/mmdetection/configs/strong_baselines/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-50e_coco.py new file mode 100644 index 00000000..6621d28c --- /dev/null +++ b/mmdetection/configs/strong_baselines/mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-50e_coco.py @@ -0,0 +1,5 @@ +_base_ = 'mask-rcnn_r50_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py' + +# Use RepeatDataset to speed up training +# change repeat time from 4 (for 100 epochs) to 2 (for 50 epochs) +train_dataloader = dict(dataset=dict(times=2)) diff --git a/mmdetection/configs/strong_baselines/metafile.yml b/mmdetection/configs/strong_baselines/metafile.yml new file mode 100644 index 00000000..f72c07e6 --- /dev/null +++ b/mmdetection/configs/strong_baselines/metafile.yml @@ -0,0 +1,24 @@ +Models: + - Name: mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco + In Collection: Mask R-CNN + Config: configs/strong_baselines/mask-rcnn_r50-caffe_fpn_rpn-2conv_4conv1fc_syncbn-all_lsj-100e_coco.py + Metadata: + Epochs: 100 + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + - LSJ + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + - FPN + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + box AP: 40.4 diff --git a/mmdetection/configs/strongsort/README.md b/mmdetection/configs/strongsort/README.md new file mode 100644 index 00000000..8e08413c --- /dev/null +++ b/mmdetection/configs/strongsort/README.md @@ -0,0 +1,108 @@ +# StrongSORT: Make DeepSORT Great Again + +## Abstract + + + +Existing Multi-Object Tracking (MOT) methods can be roughly classified as tracking-by-detection and joint-detection-association paradigms. Although the latter has elicited more attention and demonstrates comparable performance relative to the former, we claim that the tracking-by-detection paradigm is still the optimal solution in terms of tracking accuracy. In this paper, we revisit the classic tracker DeepSORT and upgrade it from various aspects, i.e., detection, embedding and association. The resulting tracker, called StrongSORT, sets new HOTA and IDF1 records on MOT17 and MOT20. We also present two lightweight and plug-and-play algorithms to further refine the tracking results. Firstly, an appearance-free link model (AFLink) is proposed to associate short tracklets into complete trajectories. To the best of our knowledge, this is the first global link model without appearance information. Secondly, we propose Gaussian-smoothed interpolation (GSI) to compensate for missing detections. Instead of ignoring motion information like linear interpolation, GSI is based on the Gaussian process regression algorithm and can achieve more accurate localizations. Moreover, AFLink and GSI can be plugged into various trackers with a negligible extra computational cost (591.9 and 140.9 Hz, respectively, on MOT17). By integrating StrongSORT with the two algorithms, the final tracker StrongSORT++ ranks first on MOT17 and MOT20 in terms of HOTA and IDF1 metrics and surpasses the second-place one by 1.3 - 2.2. Code will be released soon. + + + +
    + +
    + +## Citation + + + +```latex +@article{du2022strongsort, + title={Strongsort: Make deepsort great again}, + author={Du, Yunhao and Song, Yang and Yang, Bo and Zhao, Yanyun}, + journal={arXiv preprint arXiv:2202.13514}, + year={2022} +} +``` + +## Results and models on MOT17 + +| Method | Detector | ReID | Train Set | Test Set | Public | Inf time (fps) | HOTA | MOTA | IDF1 | FP | FN | IDSw. | Config | Download | +| :----------: | :------: | :--: | :---------------------------: | :------------: | :----: | :------------: | :--: | :--: | :--: | :---: | :---: | :---: | :----------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| StrongSORT++ | YOLOX-X | R50 | CrowdHuman + MOT17-half-train | MOT17-half-val | N | - | 70.9 | 78.4 | 83.3 | 15237 | 19035 | 582 | [config](strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py) | [detector](https://download.openmmlab.com/mmtracking/mot/strongsort/mot_dataset/yolox_x_crowdhuman_mot17-private-half_20220812_192036-b6c9ce9a.pth) [reid](https://download.openmmlab.com/mmtracking/mot/reid/reid_r50_6e_mot17-4bf6b63d.pth) [AFLink](https://download.openmmlab.com/mmtracking/mot/strongsort/mot_dataset/aflink_motchallenge_20220812_190310-a7578ad3.pth) | + +## Results and models on MOT20 + +| Method | Detector | ReID | Train Set | Test Set | Public | Inf time (fps) | HOTA | MOTA | IDF1 | FP | FN | IDSw. | Config | Download | +| :----------: | :------: | :--: | :----------------------: | :--------: | :----: | :------------: | :--: | :--: | :--: | :---: | :---: | :---: | :---------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| StrongSORT++ | YOLOX-X | R50 | CrowdHuman + MOT20-train | MOT20-test | N | - | 62.9 | 75.5 | 77.3 | 29043 | 96155 | 1640 | [config](strongsort_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py) | [detector](https://download.openmmlab.com/mmtracking/mot/strongsort/mot_dataset/yolox_x_crowdhuman_mot20-private_20220812_192123-77c014de.pth) [reid](https://download.openmmlab.com/mmtracking/mot/reid/reid_r50_6e_mot20_20210803_212426-c83b1c01.pth) [AFLink](https://download.openmmlab.com/mmtracking/mot/strongsort/mot_dataset/aflink_motchallenge_20220812_190310-a7578ad3.pth) | + +## Get started + +### 1. Development Environment Setup + +Tracking Development Environment Setup can refer to this [document](../../docs/en/get_started.md). + +### 2. Dataset Prepare + +Tracking Dataset Prepare can refer to this [document](../../docs/en/user_guides/tracking_dataset_prepare.md). + +### 3. Training + +We implement StrongSORT with independent detector and ReID models. +Note that, due to the influence of parameters such as learning rate in default configuration file, +we recommend using 8 GPUs for training in order to reproduce accuracy. + +You can train the detector as follows. + +```shell script +# Training YOLOX-X on crowdhuman and mot17-half-train dataset with following command. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_train.sh configs/det/yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py 8 +``` + +And you can train the ReID model as follows. + +```shell script +# Training ReID model on mot17-train80 dataset with following command. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_train.sh configs/reid/reid_r50_8xb32-6e_mot17train80_test-mot17val20.py 8 +``` + +If you want to know about more detailed usage of `train.py/dist_train.sh/slurm_train.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 4. Testing and evaluation + +**2.1 Example on MOTxx-halfval dataset** + +```shell script +# Example 1: Test on motXX-half-val set. +# The number after config file represents the number of GPUs used. Here we use 8 GPUs. +bash tools/dist_test_tracking.sh configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py 8 --detector ${CHECKPOINT_PATH} --reid ${CHECKPOINT_PATH} +``` + +**2.2 Example on MOTxx-test dataset** + +If you want to get the results of the [MOT Challenge](https://motchallenge.net/) test set, +please use the following command to generate result files that can be used for submission. +It will be stored in `./mot_20_test_res`, you can modify the saved path in `test_evaluator` of the config. + +```shell script +# Example 2: Test on motxx-test set +# The number after config file represents the number of GPUs used +bash tools/dist_test_tracking.sh configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py 8 --detector ${CHECKPOINT_PATH} --reid ${CHECKPOINT_PATH} +``` + +If you want to know about more detailed usage of `test_tracking.py/dist_test_tracking.sh/slurm_test_tracking.sh`, +please refer to this [document](../../docs/en/user_guides/tracking_train_test.md). + +### 3.Inference + +Use a single GPU to predict a video and save it as a video. + +```shell +python demo/mot_demo.py demo/demo_mot.mp4 configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py --detector ${CHECKPOINT_FILE} --reid ${CHECKPOINT_PATH} --out mot.mp4 +``` + +If you want to know about more detailed usage of `mot_demo.py`, please refer to this [document](../../docs/en/user_guides/tracking_inference.md). diff --git a/mmdetection/configs/strongsort/metafile.yml b/mmdetection/configs/strongsort/metafile.yml new file mode 100644 index 00000000..08a564b7 --- /dev/null +++ b/mmdetection/configs/strongsort/metafile.yml @@ -0,0 +1,48 @@ +Collections: + - Name: StrongSORT++ + Metadata: + Training Techniques: + - SGD with Momentum + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + - YOLOX + Paper: + URL: https://arxiv.org/abs/2202.13514 + Title: "StrongSORT: Make DeepSORT Great Again" + README: configs/strongsort/README.md + +Models: + - Name: strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval + In Collection: StrongSORT++ + Config: configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py + Metadata: + Training Data: CrowdHuman + MOT17-half-train + Results: + - Task: Multiple Object Tracking + Dataset: MOT17-half-val + Metrics: + MOTA: 78.3 + IDF1: 83.2 + HOTA: 70.9 + Weights: + - https://download.openmmlab.com/mmtracking/mot/strongsort/mot_dataset/yolox_x_crowdhuman_mot17-private-half_20220812_192036-b6c9ce9a.pth + - https://download.openmmlab.com/mmtracking/mot/reid/reid_r50_6e_mot17-4bf6b63d.pth + - https://download.openmmlab.com/mmtracking/mot/strongsort/mot_dataset/aflink_motchallenge_20220812_190310-a7578ad3.pth + + - Name: strongsort_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test + In Collection: StrongSORT++ + Config: configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py + Metadata: + Training Data: CrowdHuman + MOT20-train + Results: + - Task: Multiple Object Tracking + Dataset: MOT20-test + Metrics: + MOTA: 75.5 + IDF1: 77.3 + HOTA: 62.9 + Weights: + - https://download.openmmlab.com/mmtracking/mot/strongsort/mot_dataset/yolox_x_crowdhuman_mot20-private_20220812_192123-77c014de.pth + - https://download.openmmlab.com/mmtracking/mot/reid/reid_r50_6e_mot20_20210803_212426-c83b1c01.pth + - https://download.openmmlab.com/mmtracking/mot/strongsort/mot_dataset/aflink_motchallenge_20220812_190310-a7578ad3.pth diff --git a/mmdetection/configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py b/mmdetection/configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..532e2aee --- /dev/null +++ b/mmdetection/configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py @@ -0,0 +1,130 @@ +_base_ = [ + './yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py', # noqa: E501 +] + +dataset_type = 'MOTChallengeDataset' +detector = _base_.model +detector.pop('data_preprocessor') +del _base_.model + +model = dict( + type='StrongSORT', + data_preprocessor=dict( + type='TrackDataPreprocessor', + pad_size_divisor=32, + batch_augments=[ + dict( + type='BatchSyncRandomResize', + random_size_range=(576, 1024), + size_divisor=32, + interval=10) + ]), + detector=detector, + reid=dict( + type='BaseReID', + data_preprocessor=dict(type='mmpretrain.ClsDataPreprocessor'), + backbone=dict( + type='mmpretrain.ResNet', + depth=50, + num_stages=4, + out_indices=(3, ), + style='pytorch'), + neck=dict(type='GlobalAveragePooling', kernel_size=(8, 4), stride=1), + head=dict( + type='LinearReIDHead', + num_fcs=1, + in_channels=2048, + fc_channels=1024, + out_channels=128, + num_classes=380, + loss_cls=dict(type='mmpretrain.CrossEntropyLoss', loss_weight=1.0), + loss_triplet=dict(type='TripletLoss', margin=0.3, loss_weight=1.0), + norm_cfg=dict(type='BN1d'), + act_cfg=dict(type='ReLU'))), + cmc=dict( + type='CameraMotionCompensation', + warp_mode='cv2.MOTION_EUCLIDEAN', + num_iters=100, + stop_eps=0.00001), + tracker=dict( + type='StrongSORTTracker', + motion=dict(type='KalmanFilter', center_only=False, use_nsa=True), + obj_score_thr=0.6, + reid=dict( + num_samples=None, + img_scale=(256, 128), + img_norm_cfg=dict( + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + to_rgb=True), + match_score_thr=0.3, + motion_weight=0.02, + ), + match_iou_thr=0.7, + momentums=dict(embeds=0.1, ), + num_tentatives=2, + num_frames_retain=100), + postprocess_model=dict( + type='AppearanceFreeLink', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmtracking/mot/strongsort/mot_dataset/aflink_motchallenge_20220812_190310-a7578ad3.pth', # noqa: E501 + temporal_threshold=(0, 30), + spatial_threshold=50, + confidence_threshold=0.95, + )) + +train_pipeline = None +test_pipeline = [ + dict( + type='TransformBroadcaster', + transforms=[ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='Resize', scale=_base_.img_scale, keep_ratio=True), + dict( + type='Pad', + size_divisor=32, + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='LoadTrackAnnotations'), + ]), + dict(type='PackTrackInputs') +] + +train_dataloader = None +val_dataloader = dict( + # Now StrongSORT only support video_based sampling + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), + dataset=dict( + _delete_=True, + type=dataset_type, + data_root=_base_.data_root, + ann_file='annotations/half-val_cocoformat.json', + data_prefix=dict(img_path='train'), + # when you evaluate track performance, you need to remove metainfo + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +train_cfg = None +optim_wrapper = None + +# evaluator +val_evaluator = dict( + _delete_=True, + type='MOTChallengeMetric', + metric=['HOTA', 'CLEAR', 'Identity'], + # use_postprocess to support AppearanceFreeLink in val_evaluator + use_postprocess=True, + postprocess_tracklet_cfg=[ + dict( + type='InterpolateTracklets', + min_num_frames=5, + max_num_frames=20, + use_gsi=True, + smooth_tau=10) + ]) +test_evaluator = val_evaluator + +default_hooks = dict(logger=dict(type='LoggerHook', interval=1)) + +del _base_.param_scheduler +del _base_.custom_hooks diff --git a/mmdetection/configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py b/mmdetection/configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py new file mode 100644 index 00000000..eab97063 --- /dev/null +++ b/mmdetection/configs/strongsort/strongsort_yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py @@ -0,0 +1,44 @@ +_base_ = [ + './strongsort_yolox_x_8xb4-80e_crowdhuman-mot17halftrain' + '_test-mot17halfval.py' +] + +img_scale = (1600, 896) # width, height + +model = dict( + data_preprocessor=dict( + type='TrackDataPreprocessor', + pad_size_divisor=32, + batch_augments=[ + dict(type='BatchSyncRandomResize', random_size_range=(640, 1152)) + ])) + +test_pipeline = [ + dict( + type='TransformBroadcaster', + transforms=[ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict( + type='Pad', + size_divisor=32, + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='LoadTrackAnnotations'), + ]), + dict(type='PackTrackInputs') +] + +val_dataloader = dict( + dataset=dict( + data_root='data/MOT17', + ann_file='annotations/train_cocoformat.json', + data_prefix=dict(img_path='train'), + pipeline=test_pipeline)) +test_dataloader = dict( + dataset=dict( + data_root='data/MOT20', + ann_file='annotations/test_cocoformat.json', + data_prefix=dict(img_path='test'), + pipeline=test_pipeline)) + +test_evaluator = dict(format_only=True, outfile_prefix='./mot_20_test_res') diff --git a/mmdetection/configs/strongsort/yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py b/mmdetection/configs/strongsort/yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..59a52e43 --- /dev/null +++ b/mmdetection/configs/strongsort/yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py @@ -0,0 +1,188 @@ +_base_ = ['../yolox/yolox_x_8xb8-300e_coco.py'] + +data_root = 'data/MOT17/' + +img_scale = (1440, 800) # width, height +batch_size = 4 + +# model settings +model = dict( + bbox_head=dict(num_classes=1), + test_cfg=dict(nms=dict(iou_threshold=0.7)), + init_cfg=dict( + type='Pretrained', + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_x_8x8_300e_coco/yolox_x_8x8_300e_coco_20211126_140254-1ef88d67.pth' # noqa: E501 + )) + +train_pipeline = [ + dict( + type='Mosaic', + img_scale=img_scale, + pad_val=114.0, + bbox_clip_border=False), + dict( + type='RandomAffine', + scaling_ratio_range=(0.1, 2), + border=(-img_scale[0] // 2, -img_scale[1] // 2), + bbox_clip_border=False), + dict( + type='MixUp', + img_scale=img_scale, + ratio_range=(0.8, 1.6), + pad_val=114.0, + bbox_clip_border=False), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict( + type='Resize', + scale=img_scale, + keep_ratio=True, + clip_object_border=False), + dict(type='Pad', size_divisor=32, pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1), keep_empty=False), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict(type='Pad', size_divisor=32, pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + _delete_=True, + batch_size=batch_size, + num_workers=4, + persistent_workers=True, + pin_memory=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + type='MultiImageMixDataset', + dataset=dict( + type='ConcatDataset', + datasets=[ + dict( + type='CocoDataset', + data_root=data_root, + ann_file='annotations/half-train_cocoformat.json', + data_prefix=dict(img='train'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + dict( + type='CocoDataset', + data_root='data/crowdhuman', + ann_file='annotations/crowdhuman_train.json', + data_prefix=dict(img='train'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + dict( + type='CocoDataset', + data_root='data/crowdhuman', + ann_file='annotations/crowdhuman_val.json', + data_prefix=dict(img='val'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + ]), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + dataset=dict( + data_root=data_root, + ann_file='annotations/half-val_cocoformat.json', + data_prefix=dict(img='train'), + metainfo=dict(classes=('pedestrian', )), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# training settings +max_epochs = 80 +num_last_epochs = 10 +interval = 5 + +train_cfg = dict(max_epochs=max_epochs, val_begin=75, val_interval=1) + +# optimizer +# default 8 gpu +base_lr = 0.001 / 8 * batch_size +optim_wrapper = dict(optimizer=dict(lr=base_lr)) + +# learning rate +param_scheduler = [ + dict( + type='QuadraticWarmupLR', + by_epoch=True, + begin=0, + end=1, + convert_to_iter_based=True), + dict( + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=1, + T_max=max_epochs - num_last_epochs, + end=max_epochs - num_last_epochs, + by_epoch=True, + convert_to_iter_based=True), + dict( + type='ConstantLR', + by_epoch=True, + factor=1, + begin=max_epochs - num_last_epochs, + end=max_epochs, + ) +] + +default_hooks = dict( + checkpoint=dict( + interval=1, + max_keep_ckpts=5 # only keep latest 5 checkpoints + )) + +custom_hooks = [ + dict( + type='YOLOXModeSwitchHook', + num_last_epochs=num_last_epochs, + priority=48), + dict(type='SyncNormHook', priority=48), + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0001, + update_buffers=True, + priority=49) +] + +# evaluator +val_evaluator = dict( + ann_file=data_root + 'annotations/half-val_cocoformat.json', + format_only=False) +test_evaluator = val_evaluator + +del _base_.tta_model +del _base_.tta_pipeline +del _base_.train_dataset diff --git a/mmdetection/configs/strongsort/yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py b/mmdetection/configs/strongsort/yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py new file mode 100644 index 00000000..d4eb3cb2 --- /dev/null +++ b/mmdetection/configs/strongsort/yolox_x_8xb4-80e_crowdhuman-mot20train_test-mot20test.py @@ -0,0 +1,108 @@ +_base_ = ['./yolox_x_8xb4-80e_crowdhuman-mot17halftrain_test-mot17halfval.py'] + +data_root = 'data/MOT20/' + +img_scale = (1600, 896) # width, height + +# model settings +model = dict( + data_preprocessor=dict(batch_augments=[ + dict(type='BatchSyncRandomResize', random_size_range=(640, 1152)) + ])) + +train_pipeline = [ + dict( + type='Mosaic', + img_scale=img_scale, + pad_val=114.0, + bbox_clip_border=True), + dict( + type='RandomAffine', + scaling_ratio_range=(0.1, 2), + border=(-img_scale[0] // 2, -img_scale[1] // 2), + bbox_clip_border=True), + dict( + type='MixUp', + img_scale=img_scale, + ratio_range=(0.8, 1.6), + pad_val=114.0, + bbox_clip_border=True), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + dict( + type='Resize', + scale=img_scale, + keep_ratio=True, + clip_object_border=True), + dict(type='Pad', size_divisor=32, pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1), keep_empty=False), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict(type='Pad', size_divisor=32, pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + dataset=dict( + type='MultiImageMixDataset', + dataset=dict( + type='ConcatDataset', + datasets=[ + dict( + type='CocoDataset', + data_root=data_root, + ann_file='annotations/train_cocoformat.json', + data_prefix=dict(img='train'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + dict( + type='CocoDataset', + data_root='data/crowdhuman', + ann_file='annotations/crowdhuman_train.json', + data_prefix=dict(img='train'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + dict( + type='CocoDataset', + data_root='data/crowdhuman', + ann_file='annotations/crowdhuman_val.json', + data_prefix=dict(img='val'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + metainfo=dict(classes=('pedestrian', )), + pipeline=[ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + ]), + ]), + pipeline=train_pipeline)) + +val_dataloader = dict( + dataset=dict( + data_root='data/MOT17', ann_file='annotations/train_cocoformat.json')) +test_dataloader = val_dataloader + +# evaluator +val_evaluator = dict(ann_file='data/MOT17/annotations/train_cocoformat.json') +test_evaluator = val_evaluator diff --git a/mmdetection/configs/swin/README.md b/mmdetection/configs/swin/README.md new file mode 100644 index 00000000..99bcf6ed --- /dev/null +++ b/mmdetection/configs/swin/README.md @@ -0,0 +1,41 @@ +# Swin + +> [Swin Transformer: Hierarchical Vision Transformer using Shifted Windows](https://arxiv.org/abs/2103.14030) + + + +## Abstract + +This paper presents a new vision Transformer, called Swin Transformer, that capably serves as a general-purpose backbone for computer vision. Challenges in adapting Transformer from language to vision arise from differences between the two domains, such as large variations in the scale of visual entities and the high resolution of pixels in images compared to words in text. To address these differences, we propose a hierarchical Transformer whose representation is computed with Shifted windows. The shifted windowing scheme brings greater efficiency by limiting self-attention computation to non-overlapping local windows while also allowing for cross-window connection. This hierarchical architecture has the flexibility to model at various scales and has linear computational complexity with respect to image size. These qualities of Swin Transformer make it compatible with a broad range of vision tasks, including image classification (87.3 top-1 accuracy on ImageNet-1K) and dense prediction tasks such as object detection (58.7 box AP and 51.1 mask AP on COCO test-dev) and semantic segmentation (53.5 mIoU on ADE20K val). Its performance surpasses the previous state-of-the-art by a large margin of +2.7 box AP and +2.6 mask AP on COCO, and +3.2 mIoU on ADE20K, demonstrating the potential of Transformer-based models as vision backbones. The hierarchical design and the shifted window approach also prove beneficial for all-MLP architectures. + +
    + +
    + +## Results and Models + +### Mask R-CNN + +| Backbone | Pretrain | Lr schd | Multi-scale crop | FP16 | Mem (GB) | Inf time (fps) | box AP | mask AP | Config | Download | +| :------: | :---------: | :-----: | :--------------: | :--: | :------: | :------------: | :----: | :-----: | :-----------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Swin-T | ImageNet-1K | 1x | no | no | 7.6 | | 42.7 | 39.3 | [config](./mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_1x_coco/mask_rcnn_swin-t-p4-w7_fpn_1x_coco_20210902_120937-9d6b7cfa.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_1x_coco/mask_rcnn_swin-t-p4-w7_fpn_1x_coco_20210902_120937.log.json) | +| Swin-T | ImageNet-1K | 3x | yes | no | 10.2 | | 46.0 | 41.6 | [config](./mask-rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco/mask_rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco_20210906_131725-bacf6f7b.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco/mask_rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco_20210906_131725.log.json) | +| Swin-T | ImageNet-1K | 3x | yes | yes | 7.8 | | 46.0 | 41.7 | [config](./mask-rcnn_swin-t-p4-w7_fpn_amp-ms-crop-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_fp16_ms-crop-3x_coco/mask_rcnn_swin-t-p4-w7_fpn_fp16_ms-crop-3x_coco_20210908_165006-90a4008c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_fp16_ms-crop-3x_coco/mask_rcnn_swin-t-p4-w7_fpn_fp16_ms-crop-3x_coco_20210908_165006.log.json) | +| Swin-S | ImageNet-1K | 3x | yes | yes | 11.9 | | 48.2 | 43.2 | [config](./mask-rcnn_swin-s-p4-w7_fpn_amp-ms-crop-3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-s-p4-w7_fpn_fp16_ms-crop-3x_coco/mask_rcnn_swin-s-p4-w7_fpn_fp16_ms-crop-3x_coco_20210903_104808-b92c91f1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-s-p4-w7_fpn_fp16_ms-crop-3x_coco/mask_rcnn_swin-s-p4-w7_fpn_fp16_ms-crop-3x_coco_20210903_104808.log.json) | + +### Notice + +Please follow the example +of `retinanet_swin-t-p4-w7_fpn_1x_coco.py` when you want to combine Swin Transformer with +the one-stage detector. Because there is a layer norm at the outs of Swin Transformer, you must set `start_level` as 0 in FPN, so we have to set the `out_indices` of backbone as `[1,2,3]`. + +## Citation + +```latex +@article{liu2021Swin, + title={Swin Transformer: Hierarchical Vision Transformer using Shifted Windows}, + author={Liu, Ze and Lin, Yutong and Cao, Yue and Hu, Han and Wei, Yixuan and Zhang, Zheng and Lin, Stephen and Guo, Baining}, + journal={arXiv preprint arXiv:2103.14030}, + year={2021} +} +``` diff --git a/mmdetection/configs/swin/mask-rcnn_swin-s-p4-w7_fpn_amp-ms-crop-3x_coco.py b/mmdetection/configs/swin/mask-rcnn_swin-s-p4-w7_fpn_amp-ms-crop-3x_coco.py new file mode 100644 index 00000000..4a3e8ad9 --- /dev/null +++ b/mmdetection/configs/swin/mask-rcnn_swin-s-p4-w7_fpn_amp-ms-crop-3x_coco.py @@ -0,0 +1,6 @@ +_base_ = './mask-rcnn_swin-t-p4-w7_fpn_amp-ms-crop-3x_coco.py' +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_small_patch4_window7_224.pth' # noqa +model = dict( + backbone=dict( + depths=[2, 2, 18, 2], + init_cfg=dict(type='Pretrained', checkpoint=pretrained))) diff --git a/mmdetection/configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py b/mmdetection/configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py new file mode 100644 index 00000000..5471caa1 --- /dev/null +++ b/mmdetection/configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py @@ -0,0 +1,60 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_tiny_patch4_window7_224.pth' # noqa +model = dict( + type='MaskRCNN', + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[96, 192, 384, 768])) + +max_epochs = 12 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'relative_position_bias_table': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + }), + optimizer=dict( + _delete_=True, + type='AdamW', + lr=0.0001, + betas=(0.9, 0.999), + weight_decay=0.05)) diff --git a/mmdetection/configs/swin/mask-rcnn_swin-t-p4-w7_fpn_amp-ms-crop-3x_coco.py b/mmdetection/configs/swin/mask-rcnn_swin-t-p4-w7_fpn_amp-ms-crop-3x_coco.py new file mode 100644 index 00000000..622087ba --- /dev/null +++ b/mmdetection/configs/swin/mask-rcnn_swin-t-p4-w7_fpn_amp-ms-crop-3x_coco.py @@ -0,0 +1,3 @@ +_base_ = './mask-rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco.py' +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict(type='AmpOptimWrapper') diff --git a/mmdetection/configs/swin/mask-rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco.py b/mmdetection/configs/swin/mask-rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco.py new file mode 100644 index 00000000..7024b732 --- /dev/null +++ b/mmdetection/configs/swin/mask-rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco.py @@ -0,0 +1,99 @@ +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_tiny_patch4_window7_224.pth' # noqa + +model = dict( + type='MaskRCNN', + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[96, 192, 384, 768])) + +# augmentation strategy originates from DETR / Sparse RCNN +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[[ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True) + ]]), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'relative_position_bias_table': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + }), + optimizer=dict( + _delete_=True, + type='AdamW', + lr=0.0001, + betas=(0.9, 0.999), + weight_decay=0.05)) diff --git a/mmdetection/configs/swin/metafile.yml b/mmdetection/configs/swin/metafile.yml new file mode 100644 index 00000000..763f9300 --- /dev/null +++ b/mmdetection/configs/swin/metafile.yml @@ -0,0 +1,120 @@ +Models: + - Name: mask-rcnn_swin-s-p4-w7_fpn_amp-ms-crop-3x_coco + In Collection: Mask R-CNN + Config: configs/swin/mask-rcnn_swin-s-p4-w7_fpn_amp-ms-crop-3x_coco.py + Metadata: + Training Memory (GB): 11.9 + Epochs: 36 + Training Data: COCO + Training Techniques: + - AdamW + Training Resources: 8x V100 GPUs + Architecture: + - Swin Transformer + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 48.2 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 43.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-s-p4-w7_fpn_fp16_ms-crop-3x_coco/mask_rcnn_swin-s-p4-w7_fpn_fp16_ms-crop-3x_coco_20210903_104808-b92c91f1.pth + Paper: + URL: https://arxiv.org/abs/2107.08430 + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + README: configs/swin/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.16.0/mmdet/models/backbones/swin.py#L465 + Version: v2.16.0 + + - Name: mask-rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco + In Collection: Mask R-CNN + Config: configs/swin/mask-rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco.py + Metadata: + Training Memory (GB): 10.2 + Epochs: 36 + Training Data: COCO + Training Techniques: + - AdamW + Training Resources: 8x V100 GPUs + Architecture: + - Swin Transformer + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco/mask_rcnn_swin-t-p4-w7_fpn_ms-crop-3x_coco_20210906_131725-bacf6f7b.pth + Paper: + URL: https://arxiv.org/abs/2107.08430 + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + README: configs/swin/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.16.0/mmdet/models/backbones/swin.py#L465 + Version: v2.16.0 + + - Name: mask-rcnn_swin-t-p4-w7_fpn_1x_coco + In Collection: Mask R-CNN + Config: configs/swin/mask-rcnn_swin-t-p4-w7_fpn_1x_coco.py + Metadata: + Training Memory (GB): 7.6 + Epochs: 12 + Training Data: COCO + Training Techniques: + - AdamW + Training Resources: 8x V100 GPUs + Architecture: + - Swin Transformer + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.7 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 39.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_1x_coco/mask_rcnn_swin-t-p4-w7_fpn_1x_coco_20210902_120937-9d6b7cfa.pth + Paper: + URL: https://arxiv.org/abs/2107.08430 + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + README: configs/swin/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.16.0/mmdet/models/backbones/swin.py#L465 + Version: v2.16.0 + + - Name: mask-rcnn_swin-t-p4-w7_fpn_amp-ms-crop-3x_coco + In Collection: Mask R-CNN + Config: configs/swin/mask-rcnn_swin-t-p4-w7_fpn_amp-ms-crop-3x_coco.py + Metadata: + Training Memory (GB): 7.8 + Epochs: 36 + Training Data: COCO + Training Techniques: + - AdamW + Training Resources: 8x V100 GPUs + Architecture: + - Swin Transformer + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.0 + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 41.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_fp16_ms-crop-3x_coco/mask_rcnn_swin-t-p4-w7_fpn_fp16_ms-crop-3x_coco_20210908_165006-90a4008c.pth + Paper: + URL: https://arxiv.org/abs/2107.08430 + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + README: configs/swin/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.16.0/mmdet/models/backbones/swin.py#L465 + Version: v2.16.0 diff --git a/mmdetection/configs/swin/retinanet_swin-t-p4-w7_fpn_1x_coco.py b/mmdetection/configs/swin/retinanet_swin-t-p4-w7_fpn_1x_coco.py new file mode 100644 index 00000000..2f40a87e --- /dev/null +++ b/mmdetection/configs/swin/retinanet_swin-t-p4-w7_fpn_1x_coco.py @@ -0,0 +1,31 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_tiny_patch4_window7_224.pth' # noqa +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[192, 384, 768], start_level=0, num_outs=5)) + +# optimizer +optim_wrapper = dict(optimizer=dict(lr=0.01)) diff --git a/mmdetection/configs/timm_example/README.md b/mmdetection/configs/timm_example/README.md new file mode 100644 index 00000000..848f8d3c --- /dev/null +++ b/mmdetection/configs/timm_example/README.md @@ -0,0 +1,62 @@ +# Timm Example + +> [PyTorch Image Models](https://github.com/rwightman/pytorch-image-models) + + + +## Abstract + +Py**T**orch **Im**age **M**odels (`timm`) is a collection of image models, layers, utilities, optimizers, schedulers, data-loaders / augmentations, and reference training / validation scripts that aim to pull together a wide variety of SOTA models with ability to reproduce ImageNet training results. + + + +## Results and Models + +### RetinaNet + +| Backbone | Style | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :-------------: | :-----: | :-----: | :------: | :------------: | :----: | :-------------------------------------------------------: | :------: | +| R-50 | pytorch | 1x | | | | [config](./retinanet_timm-tv-resnet50_fpn_1x_coco.py) | | +| EfficientNet-B1 | - | 1x | | | | [config](./retinanet_timm-efficientnet-b1_fpn_1x_coco.py) | | + +## Usage + +### Install additional requirements + +MMDetection supports timm backbones via `TIMMBackbone`, a wrapper class in MMPretrain. +Thus, you need to install `mmpretrain` in addition to timm. +If you have already installed requirements for mmdet, run + +```shell +pip install 'dataclasses; python_version<"3.7"' +pip install timm +pip install mmpretrain +``` + +See [this document](https://mmpretrain.readthedocs.io/en/latest/get_started.html#installation) for the details of MMPretrain installation. + +### Edit config + +- See example configs for basic usage. +- See the documents of [timm feature extraction](https://rwightman.github.io/pytorch-image-models/feature_extraction/#multi-scale-feature-maps-feature-pyramid) and [TIMMBackbone](https://mmpretrain.readthedocs.io/en/latest/api/generated/mmpretrain.models.backbones.TIMMBackbone.html#mmpretrain.models.backbones.TIMMBackbone) for details. +- Which feature map is output depends on the backbone. + Please check `backbone out_channels` and `backbone out_strides` in your log, and modify `model.neck.in_channels` and `model.backbone.out_indices` if necessary. +- If you use Vision Transformer models that do not support `features_only=True`, add `custom_hooks = []` to your config to disable `NumClassCheckHook`. + +## Citation + +```latex +@misc{rw2019timm, + author = {Ross Wightman}, + title = {PyTorch Image Models}, + year = {2019}, + publisher = {GitHub}, + journal = {GitHub repository}, + doi = {10.5281/zenodo.4414861}, + howpublished = {\url{https://github.com/rwightman/pytorch-image-models}} +} +``` diff --git a/mmdetection/configs/timm_example/retinanet_timm-efficientnet-b1_fpn_1x_coco.py b/mmdetection/configs/timm_example/retinanet_timm-efficientnet-b1_fpn_1x_coco.py new file mode 100644 index 00000000..b87dddf5 --- /dev/null +++ b/mmdetection/configs/timm_example/retinanet_timm-efficientnet-b1_fpn_1x_coco.py @@ -0,0 +1,23 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# please install mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict( + imports=['mmpretrain.models'], allow_failed_imports=False) + +model = dict( + backbone=dict( + _delete_=True, + type='mmpretrain.TIMMBackbone', + model_name='efficientnet_b1', + features_only=True, + pretrained=True, + out_indices=(1, 2, 3, 4)), + neck=dict(in_channels=[24, 40, 112, 320])) + +# optimizer +optim_wrapper = dict(optimizer=dict(lr=0.01)) diff --git a/mmdetection/configs/timm_example/retinanet_timm-tv-resnet50_fpn_1x_coco.py b/mmdetection/configs/timm_example/retinanet_timm-tv-resnet50_fpn_1x_coco.py new file mode 100644 index 00000000..74e43506 --- /dev/null +++ b/mmdetection/configs/timm_example/retinanet_timm-tv-resnet50_fpn_1x_coco.py @@ -0,0 +1,22 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# please install mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict( + imports=['mmpretrain.models'], allow_failed_imports=False) + +model = dict( + backbone=dict( + _delete_=True, + type='mmpretrain.TIMMBackbone', + model_name='tv_resnet50', # ResNet-50 with torchvision weights + features_only=True, + pretrained=True, + out_indices=(1, 2, 3, 4))) + +# optimizer +optim_wrapper = dict(optimizer=dict(lr=0.01)) diff --git a/mmdetection/configs/tood/README.md b/mmdetection/configs/tood/README.md new file mode 100644 index 00000000..9371d9d7 --- /dev/null +++ b/mmdetection/configs/tood/README.md @@ -0,0 +1,40 @@ +# TOOD + +> [TOOD: Task-aligned One-stage Object Detection](https://arxiv.org/abs/2108.07755) + + + +## Abstract + +One-stage object detection is commonly implemented by optimizing two sub-tasks: object classification and localization, using heads with two parallel branches, which might lead to a certain level of spatial misalignment in predictions between the two tasks. In this work, we propose a Task-aligned One-stage Object Detection (TOOD) that explicitly aligns the two tasks in a learning-based manner. First, we design a novel Task-aligned Head (T-Head) which offers a better balance between learning task-interactive and task-specific features, as well as a greater flexibility to learn the alignment via a task-aligned predictor. Second, we propose Task Alignment Learning (TAL) to explicitly pull closer (or even unify) the optimal anchors for the two tasks during training via a designed sample assignment scheme and a task-aligned loss. Extensive experiments are conducted on MS-COCO, where TOOD achieves a 51.1 AP at single-model single-scale testing. This surpasses the recent one-stage detectors by a large margin, such as ATSS (47.7 AP), GFL (48.2 AP), and PAA (49.0 AP), with fewer parameters and FLOPs. Qualitative results also demonstrate the effectiveness of TOOD for better aligning the tasks of object classification and localization. + +
    + +
    + +## Results and Models + +| Backbone | Style | Anchor Type | Lr schd | Multi-scale Training | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :---------------: | :-----: | :----------: | :-----: | :------------------: | :------: | :------------: | :----: | :-------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | Anchor-free | 1x | N | 4.1 | | 42.4 | [config](./tood_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_1x_coco/tood_r50_fpn_1x_coco_20211210_103425-20e20746.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_1x_coco/tood_r50_fpn_1x_coco_20211210_103425.log) | +| R-50 | pytorch | Anchor-based | 1x | N | 4.1 | | 42.4 | [config](./tood_r50_fpn_anchor-based_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_anchor_based_1x_coco/tood_r50_fpn_anchor_based_1x_coco_20211214_100105-b776c134.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_anchor_based_1x_coco/tood_r50_fpn_anchor_based_1x_coco_20211214_100105.log) | +| R-50 | pytorch | Anchor-free | 2x | Y | 4.1 | | 44.5 | [config](./tood_r50_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_mstrain_2x_coco/tood_r50_fpn_mstrain_2x_coco_20211210_144231-3b23174c.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_mstrain_2x_coco/tood_r50_fpn_mstrain_2x_coco_20211210_144231.log) | +| R-101 | pytorch | Anchor-free | 2x | Y | 6.0 | | 46.1 | [config](./tood_r101_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r101_fpn_mstrain_2x_coco/tood_r101_fpn_mstrain_2x_coco_20211210_144232-a18f53c8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r101_fpn_mstrain_2x_coco/tood_r101_fpn_mstrain_2x_coco_20211210_144232.log) | +| R-101-dcnv2 | pytorch | Anchor-free | 2x | Y | 6.2 | | 49.3 | [config](./tood_r101-dconv-c3-c5_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r101_fpn_dconv_c3-c5_mstrain_2x_coco/tood_r101_fpn_dconv_c3-c5_mstrain_2x_coco_20211210_213728-4a824142.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r101_fpn_dconv_c3-c5_mstrain_2x_coco/tood_r101_fpn_dconv_c3-c5_mstrain_2x_coco_20211210_213728.log) | +| X-101-64x4d | pytorch | Anchor-free | 2x | Y | 10.2 | | 47.6 | [config](./tood_x101-64x4d_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_x101_64x4d_fpn_mstrain_2x_coco/tood_x101_64x4d_fpn_mstrain_2x_coco_20211211_003519-a4f36113.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/tood/tood_x101_64x4d_fpn_mstrain_2x_coco/tood_x101_64x4d_fpn_mstrain_2x_coco_20211211_003519.log) | +| X-101-64x4d-dcnv2 | pytorch | Anchor-free | 2x | Y | | | | [config](./tood_x101-64x4d-dconv-c4-c5_fpn_ms-2x_coco.py) | [model](<>) \| [log](<>) | + +\[1\] *1x and 2x mean the model is trained for 90K and 180K iterations, respectively.* \ +\[2\] *All results are obtained with a single model and without any test time data augmentation such as multi-scale, flipping and etc..* \ +\[3\] *`dcnv2` denotes deformable convolutional networks v2.* \\ + +## Citation + +```latex +@inproceedings{feng2021tood, + title={TOOD: Task-aligned One-stage Object Detection}, + author={Feng, Chengjian and Zhong, Yujie and Gao, Yu and Scott, Matthew R and Huang, Weilin}, + booktitle={ICCV}, + year={2021} +} +``` diff --git a/mmdetection/configs/tood/metafile.yml b/mmdetection/configs/tood/metafile.yml new file mode 100644 index 00000000..d2bc0807 --- /dev/null +++ b/mmdetection/configs/tood/metafile.yml @@ -0,0 +1,95 @@ +Collections: + - Name: TOOD + Metadata: + Training Data: COCO + Training Techniques: + - SGD + Training Resources: 8x V100 GPUs + Architecture: + - TOOD + Paper: + URL: https://arxiv.org/abs/2108.07755 + Title: 'TOOD: Task-aligned One-stage Object Detection' + README: configs/tood/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.20.0/mmdet/models/detectors/tood.py#L7 + Version: v2.20.0 + +Models: + - Name: tood_r101_fpn_ms-2x_coco + In Collection: TOOD + Config: configs/tood/tood_r101_fpn_ms-2x_coco.py + Metadata: + Training Memory (GB): 6.0 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.1 + Weights: https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r101_fpn_mstrain_2x_coco/tood_r101_fpn_mstrain_2x_coco_20211210_144232-a18f53c8.pth + + - Name: tood_x101-64x4d_fpn_ms-2x_coco + In Collection: TOOD + Config: configs/tood/tood_x101-64x4d_fpn_ms-2x_coco.py + Metadata: + Training Memory (GB): 10.2 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 47.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/tood/tood_x101_64x4d_fpn_mstrain_2x_coco/tood_x101_64x4d_fpn_mstrain_2x_coco_20211211_003519-a4f36113.pth + + - Name: tood_r101-dconv-c3-c5_fpn_ms-2x_coco + In Collection: TOOD + Config: configs/tood/tood_r101-dconv-c3-c5_fpn_ms-2x_coco.py + Metadata: + Training Memory (GB): 6.2 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 49.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r101_fpn_dconv_c3-c5_mstrain_2x_coco/tood_r101_fpn_dconv_c3-c5_mstrain_2x_coco_20211210_213728-4a824142.pth + + - Name: tood_r50_fpn_anchor-based_1x_coco + In Collection: TOOD + Config: configs/tood/tood_r50_fpn_anchor-based_1x_coco.py + Metadata: + Training Memory (GB): 4.1 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_anchor_based_1x_coco/tood_r50_fpn_anchor_based_1x_coco_20211214_100105-b776c134.pth + + - Name: tood_r50_fpn_1x_coco + In Collection: TOOD + Config: configs/tood/tood_r50_fpn_1x_coco.py + Metadata: + Training Memory (GB): 4.1 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 42.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_1x_coco/tood_r50_fpn_1x_coco_20211210_103425-20e20746.pth + + - Name: tood_r50_fpn_ms-2x_coco + In Collection: TOOD + Config: configs/tood/tood_r50_fpn_ms-2x_coco.py + Metadata: + Training Memory (GB): 4.1 + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/tood/tood_r50_fpn_mstrain_2x_coco/tood_r50_fpn_mstrain_2x_coco_20211210_144231-3b23174c.pth diff --git a/mmdetection/configs/tood/tood_r101-dconv-c3-c5_fpn_ms-2x_coco.py b/mmdetection/configs/tood/tood_r101-dconv-c3-c5_fpn_ms-2x_coco.py new file mode 100644 index 00000000..45030a68 --- /dev/null +++ b/mmdetection/configs/tood/tood_r101-dconv-c3-c5_fpn_ms-2x_coco.py @@ -0,0 +1,7 @@ +_base_ = './tood_r101_fpn_ms-2x_coco.py' + +model = dict( + backbone=dict( + dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True)), + bbox_head=dict(num_dcn=2)) diff --git a/mmdetection/configs/tood/tood_r101_fpn_ms-2x_coco.py b/mmdetection/configs/tood/tood_r101_fpn_ms-2x_coco.py new file mode 100644 index 00000000..fc6ae5d9 --- /dev/null +++ b/mmdetection/configs/tood/tood_r101_fpn_ms-2x_coco.py @@ -0,0 +1,7 @@ +_base_ = './tood_r50_fpn_ms-2x_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/tood/tood_r50_fpn_1x_coco.py b/mmdetection/configs/tood/tood_r50_fpn_1x_coco.py new file mode 100644 index 00000000..e4839d9d --- /dev/null +++ b/mmdetection/configs/tood/tood_r50_fpn_1x_coco.py @@ -0,0 +1,80 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# model settings +model = dict( + type='TOOD', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', + num_outs=5), + bbox_head=dict( + type='TOODHead', + num_classes=80, + in_channels=256, + stacked_convs=6, + feat_channels=256, + anchor_type='anchor_free', + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + initial_loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + activated=True, # use probability instead of logit as input + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_cls=dict( + type='QualityFocalLoss', + use_sigmoid=True, + activated=True, # use probability instead of logit as input + beta=2.0, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0)), + train_cfg=dict( + initial_epoch=4, + initial_assigner=dict(type='ATSSAssigner', topk=9), + assigner=dict(type='TaskAlignedAssigner', topk=13), + alpha=1, + beta=6, + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/tood/tood_r50_fpn_anchor-based_1x_coco.py b/mmdetection/configs/tood/tood_r50_fpn_anchor-based_1x_coco.py new file mode 100644 index 00000000..c7fbf6af --- /dev/null +++ b/mmdetection/configs/tood/tood_r50_fpn_anchor-based_1x_coco.py @@ -0,0 +1,2 @@ +_base_ = './tood_r50_fpn_1x_coco.py' +model = dict(bbox_head=dict(anchor_type='anchor_based')) diff --git a/mmdetection/configs/tood/tood_r50_fpn_ms-2x_coco.py b/mmdetection/configs/tood/tood_r50_fpn_ms-2x_coco.py new file mode 100644 index 00000000..ffb296dc --- /dev/null +++ b/mmdetection/configs/tood/tood_r50_fpn_ms-2x_coco.py @@ -0,0 +1,30 @@ +_base_ = './tood_r50_fpn_1x_coco.py' +max_epochs = 24 + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] + +# training schedule for 2x +train_cfg = dict(max_epochs=max_epochs) + +# multi-scale training +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 480), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/tood/tood_x101-64x4d-dconv-c4-c5_fpn_ms-2x_coco.py b/mmdetection/configs/tood/tood_x101-64x4d-dconv-c4-c5_fpn_ms-2x_coco.py new file mode 100644 index 00000000..43405196 --- /dev/null +++ b/mmdetection/configs/tood/tood_x101-64x4d-dconv-c4-c5_fpn_ms-2x_coco.py @@ -0,0 +1,7 @@ +_base_ = './tood_x101-64x4d_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCNv2', deformable_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, False, True, True), + ), + bbox_head=dict(num_dcn=2)) diff --git a/mmdetection/configs/tood/tood_x101-64x4d_fpn_ms-2x_coco.py b/mmdetection/configs/tood/tood_x101-64x4d_fpn_ms-2x_coco.py new file mode 100644 index 00000000..1651542c --- /dev/null +++ b/mmdetection/configs/tood/tood_x101-64x4d_fpn_ms-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './tood_r50_fpn_ms-2x_coco.py' + +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/tridentnet/README.md b/mmdetection/configs/tridentnet/README.md new file mode 100644 index 00000000..b972b3a3 --- /dev/null +++ b/mmdetection/configs/tridentnet/README.md @@ -0,0 +1,38 @@ +# TridentNet + +> [Scale-Aware Trident Networks for Object Detection](https://arxiv.org/abs/1901.01892) + + + +## Abstract + +Scale variation is one of the key challenges in object detection. In this work, we first present a controlled experiment to investigate the effect of receptive fields for scale variation in object detection. Based on the findings from the exploration experiments, we propose a novel Trident Network (TridentNet) aiming to generate scale-specific feature maps with a uniform representational power. We construct a parallel multi-branch architecture in which each branch shares the same transformation parameters but with different receptive fields. Then, we adopt a scale-aware training scheme to specialize each branch by sampling object instances of proper scales for training. As a bonus, a fast approximation version of TridentNet could achieve significant improvements without any additional parameters and computational cost compared with the vanilla detector. On the COCO dataset, our TridentNet with ResNet-101 backbone achieves state-of-the-art single-model results of 48.4 mAP. + +
    + +
    + +## Results and Models + +We reports the test results using only one branch for inference. + +| Backbone | Style | mstrain | Lr schd | Mem (GB) | Inf time (fps) | box AP | Download | +| :------: | :---: | :-----: | :-----: | :------: | :------------: | :----: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | caffe | N | 1x | | | 37.7 | [model](https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_1x_coco/tridentnet_r50_caffe_1x_coco_20201230_141838-2ec0b530.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_1x_coco/tridentnet_r50_caffe_1x_coco_20201230_141838.log.json) | +| R-50 | caffe | Y | 1x | | | 37.6 | [model](https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_mstrain_1x_coco/tridentnet_r50_caffe_mstrain_1x_coco_20201230_141839-6ce55ccb.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_mstrain_1x_coco/tridentnet_r50_caffe_mstrain_1x_coco_20201230_141839.log.json) | +| R-50 | caffe | Y | 3x | | | 40.3 | [model](https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_mstrain_3x_coco/tridentnet_r50_caffe_mstrain_3x_coco_20201130_100539-46d227ba.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_mstrain_3x_coco/tridentnet_r50_caffe_mstrain_3x_coco_20201130_100539.log.json) | + +**Note** + +Similar to [Detectron2](https://github.com/facebookresearch/detectron2/tree/master/projects/TridentNet), we haven't implemented the Scale-aware Training Scheme in section 4.2 of the paper. + +## Citation + +```latex +@InProceedings{li2019scale, + title={Scale-Aware Trident Networks for Object Detection}, + author={Li, Yanghao and Chen, Yuntao and Wang, Naiyan and Zhang, Zhaoxiang}, + journal={The International Conference on Computer Vision (ICCV)}, + year={2019} +} +``` diff --git a/mmdetection/configs/tridentnet/metafile.yml b/mmdetection/configs/tridentnet/metafile.yml new file mode 100644 index 00000000..c0081c5b --- /dev/null +++ b/mmdetection/configs/tridentnet/metafile.yml @@ -0,0 +1,55 @@ +Collections: + - Name: TridentNet + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - ResNet + - TridentNet Block + Paper: + URL: https://arxiv.org/abs/1901.01892 + Title: 'Scale-Aware Trident Networks for Object Detection' + README: configs/tridentnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.8.0/mmdet/models/detectors/trident_faster_rcnn.py#L6 + Version: v2.8.0 + +Models: + - Name: tridentnet_r50-caffe_1x_coco + In Collection: TridentNet + Config: configs/tridentnet/tridentnet_r50-caffe_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_1x_coco/tridentnet_r50_caffe_1x_coco_20201230_141838-2ec0b530.pth + + - Name: tridentnet_r50-caffe_ms-1x_coco + In Collection: TridentNet + Config: configs/tridentnet/tridentnet_r50-caffe_ms-1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_mstrain_1x_coco/tridentnet_r50_caffe_mstrain_1x_coco_20201230_141839-6ce55ccb.pth + + - Name: tridentnet_r50-caffe_ms-3x_coco + In Collection: TridentNet + Config: configs/tridentnet/tridentnet_r50-caffe_ms-3x_coco.py + Metadata: + Epochs: 36 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.3 + Weights: https://download.openmmlab.com/mmdetection/v2.0/tridentnet/tridentnet_r50_caffe_mstrain_3x_coco/tridentnet_r50_caffe_mstrain_3x_coco_20201130_100539-46d227ba.pth diff --git a/mmdetection/configs/tridentnet/tridentnet_r50-caffe_1x_coco.py b/mmdetection/configs/tridentnet/tridentnet_r50-caffe_1x_coco.py new file mode 100644 index 00000000..26a4c123 --- /dev/null +++ b/mmdetection/configs/tridentnet/tridentnet_r50-caffe_1x_coco.py @@ -0,0 +1,22 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50-caffe-c4.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + type='TridentFasterRCNN', + backbone=dict( + type='TridentResNet', + trident_dilations=(1, 2, 3), + num_branch=3, + test_branch_idx=1, + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + roi_head=dict(type='TridentRoIHead', num_branch=3, test_branch_idx=1), + train_cfg=dict( + rpn_proposal=dict(max_per_img=500), + rcnn=dict( + sampler=dict(num=128, pos_fraction=0.5, + add_gt_as_proposals=False)))) diff --git a/mmdetection/configs/tridentnet/tridentnet_r50-caffe_ms-1x_coco.py b/mmdetection/configs/tridentnet/tridentnet_r50-caffe_ms-1x_coco.py new file mode 100644 index 00000000..806d20b9 --- /dev/null +++ b/mmdetection/configs/tridentnet/tridentnet_r50-caffe_ms-1x_coco.py @@ -0,0 +1,15 @@ +_base_ = 'tridentnet_r50-caffe_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomChoiceResize', + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/configs/tridentnet/tridentnet_r50-caffe_ms-3x_coco.py b/mmdetection/configs/tridentnet/tridentnet_r50-caffe_ms-3x_coco.py new file mode 100644 index 00000000..4de249c6 --- /dev/null +++ b/mmdetection/configs/tridentnet/tridentnet_r50-caffe_ms-3x_coco.py @@ -0,0 +1,18 @@ +_base_ = 'tridentnet_r50-caffe_ms-1x_coco.py' + +# learning rate +max_epochs = 36 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] diff --git a/mmdetection/configs/v3det/README.md b/mmdetection/configs/v3det/README.md new file mode 100644 index 00000000..36879316 --- /dev/null +++ b/mmdetection/configs/v3det/README.md @@ -0,0 +1,86 @@ +

    +

    + +# V3Det: Vast Vocabulary Visual Detection Dataset + +
    + Jiaqi Wang*, + Pan Zhang*, + Tao Chu*, + Yuhang Cao*,
    + Yujie Zhou, + Tong Wu, + Bin Wang, + Conghui He, + Dahua Lin
    + (* equal contribution)
    + Accepted to ICCV 2023 (Oral) +
    +

    +

    +

    + + Paper, + Dataset
    +
    +
    +
    +

    + +
    + +
    + + + +## Abstract + +Recent advances in detecting arbitrary objects in the real world are trained and evaluated on object detection datasets with a relatively restricted vocabulary. To facilitate the development of more general visual object detection, we propose V3Det, a vast vocabulary visual detection dataset with precisely annotated bounding boxes on massive images. V3Det has several appealing properties: 1) Vast Vocabulary: It contains bounding boxes of objects from 13,204 categories on real-world images, which is 10 times larger than the existing large vocabulary object detection dataset, e.g., LVIS. 2) Hierarchical Category Organization: The vast vocabulary of V3Det is organized by a hierarchical category tree which annotates the inclusion relationship among categories, encouraging the exploration of category relationships in vast and open vocabulary object detection. 3) Rich Annotations: V3Det comprises precisely annotated objects in 243k images and professional descriptions of each category written by human experts and a powerful chatbot. By offering a vast exploration space, V3Det enables extensive benchmarks on both vast and open vocabulary object detection, leading to new observations, practices, and insights for future research. It has the potential to serve as a cornerstone dataset for developing more general visual perception systems. V3Det is available at https://v3det.openxlab.org.cn/. + +## Prepare Dataset + +Please download and prepare V3Det Dataset at [V3Det Homepage](https://v3det.openxlab.org.cn/) and [V3Det Github](https://github.com/V3Det/V3Det). + +The data includes a training set, a validation set, comprising 13,204 categories. The training set consists of 183,354 images, while the validation set has 29,821 images. The data organization is: + +``` +data/ + V3Det/ + images/ + / + |────.png + ... + ... + annotations/ + |────v3det_2023_v1_category_tree.json # Category tree + |────category_name_13204_v3det_2023_v1.txt # Category name + |────v3det_2023_v1_train.json # Train set + |────v3det_2023_v1_val.json # Validation set +``` + +## Results and Models + +| Backbone | Model | Lr schd | box AP | Config | Download | +| :------: | :-------------: | :-----: | :----: | :----------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------: | +| R-50 | Faster R-CNN | 2x | 25.4 | [config](./faster_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight//faster_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x) | +| R-50 | Cascade R-CNN | 2x | 31.6 | [config](./cascade_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight//cascade_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x) | +| R-50 | FCOS | 2x | 9.4 | [config](./fcos_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight//fcos_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x) | +| R-50 | Deformable-DETR | 50e | 34.4 | [config](./deformable-detr-refine-twostage_r50_8xb4_sample1e-3_v3det_50e.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight/Deformable_DETR_V3Det_R50) | +| R-50 | DINO | 36e | 33.5 | [config](./dino-4scale_r50_8xb2_sample1e-3_v3det_36e.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight/DINO_V3Det_R50) | +| Swin-B | Faster R-CNN | 2x | 37.6 | [config](./faster_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight//faster_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x) | +| Swin-B | Cascade R-CNN | 2x | 42.5 | [config](./cascade_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight//cascade_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x) | +| Swin-B | FCOS | 2x | 21.0 | [config](./fcos_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight//fcos_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x) | +| Swin-B | Deformable-DETR | 50e | 42.5 | [config](./deformable-detr-refine-twostage_swin_16xb2_sample1e-3_v3det_50e.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight/Deformable_DETR_V3Det_SwinB) | +| Swin-B | DINO | 36e | 42.0 | [config](./dino-4scale_swin_16xb1_sample1e-3_v3det_36e.py) | [model](https://download.openxlab.org.cn/models/V3Det/V3Det/weight/DINO_V3Det_SwinB) | + +## Citation + +```latex +@inproceedings{wang2023v3det, + title = {V3Det: Vast Vocabulary Visual Detection Dataset}, + author = {Wang, Jiaqi and Zhang, Pan and Chu, Tao and Cao, Yuhang and Zhou, Yujie and Wu, Tong and Wang, Bin and He, Conghui and Lin, Dahua}, + booktitle = {The IEEE International Conference on Computer Vision (ICCV)}, + month = {October}, + year = {2023} +} +``` diff --git a/mmdetection/configs/v3det/cascade_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py b/mmdetection/configs/v3det/cascade_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py new file mode 100644 index 00000000..567c31bd --- /dev/null +++ b/mmdetection/configs/v3det/cascade_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py @@ -0,0 +1,171 @@ +_base_ = [ + '../_base_/models/cascade-rcnn_r50_fpn.py', '../_base_/datasets/v3det.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + rpn_head=dict( + loss_bbox=dict(_delete_=True, type='L1Loss', loss_weight=1.0)), + roi_head=dict(bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=13204, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + cls_predictor_cfg=dict( + type='NormedLinear', tempearture=50, bias=True), + loss_cls=dict( + type='CrossEntropyCustomLoss', + num_classes=13204, + use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=13204, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + cls_predictor_cfg=dict( + type='NormedLinear', tempearture=50, bias=True), + loss_cls=dict( + type='CrossEntropyCustomLoss', + num_classes=13204, + use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=13204, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + cls_predictor_cfg=dict( + type='NormedLinear', tempearture=50, bias=True), + loss_cls=dict( + type='CrossEntropyCustomLoss', + num_classes=13204, + use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0)) + ]), + # model training and testing settings + train_cfg=dict( + rpn_proposal=dict(nms_pre=4000, max_per_img=2000), + rcnn=[ + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1, + perm_repeat_gt_cfg=dict(iou_thr=0.7, perm_range=0.01)), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + match_low_quality=False, + ignore_iof_thr=-1, + perm_repeat_gt_cfg=dict(iou_thr=0.7, perm_range=0.01)), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + match_low_quality=False, + ignore_iof_thr=-1, + perm_repeat_gt_cfg=dict(iou_thr=0.7, perm_range=0.01)), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rcnn=dict( + score_thr=0.0001, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=300))) +# dataset settings +train_dataloader = dict(batch_size=4, num_workers=8) + +# training schedule for 1x +max_iter = 68760 * 2 +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=max_iter) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 2048, + by_epoch=False, + begin=0, + end=5000), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[45840 * 2, 63030 * 2], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(_delete_=True, type='AdamW', lr=1e-4 * 1, weight_decay=0.1), + clip_grad=dict(max_norm=35, norm_type=2)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=32) + +default_hooks = dict( + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=5730 * 2)) +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=False) diff --git a/mmdetection/configs/v3det/cascade_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py b/mmdetection/configs/v3det/cascade_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py new file mode 100644 index 00000000..f6493323 --- /dev/null +++ b/mmdetection/configs/v3det/cascade_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py @@ -0,0 +1,27 @@ +_base_ = [ + './cascade_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py', +] + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224.pth' # noqa + +# model settings +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[128, 256, 512, 1024])) diff --git a/mmdetection/configs/v3det/deformable-detr-refine-twostage_r50_8xb4_sample1e-3_v3det_50e.py b/mmdetection/configs/v3det/deformable-detr-refine-twostage_r50_8xb4_sample1e-3_v3det_50e.py new file mode 100644 index 00000000..97544a27 --- /dev/null +++ b/mmdetection/configs/v3det/deformable-detr-refine-twostage_r50_8xb4_sample1e-3_v3det_50e.py @@ -0,0 +1,108 @@ +_base_ = '../deformable_detr/deformable-detr-refine-twostage_r50_16xb2-50e_coco.py' # noqa + +model = dict( + bbox_head=dict(num_classes=13204), + test_cfg=dict(max_per_img=300), +) + +data_root = 'data/V3Det/' +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + _delete_=True, + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='V3DetDataset', + data_root=data_root, + ann_file='annotations/v3det_2023_v1_train.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + pipeline=train_pipeline, + backend_args=None))) +val_dataloader = dict( + dataset=dict( + type='V3DetDataset', + data_root=data_root, + ann_file='annotations/v3det_2023_v1_val.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +val_evaluator = dict( + ann_file=data_root + 'annotations/v3det_2023_v1_val.json', + use_mp_eval=True, + proposal_nums=[300]) +test_evaluator = val_evaluator + +# training schedule for 50e +# when using RFS, bs32, each epoch ~ 5730 iter +max_iter = 286500 +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=max_iter / 5) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[229200], # 40e + gamma=0.1) +] + +default_hooks = dict( + timer=dict(type='IterTimerHook'), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict( + type='CheckpointHook', by_epoch=False, interval=5730, + max_keep_ckpts=3)) + +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=False) diff --git a/mmdetection/configs/v3det/deformable-detr-refine-twostage_swin_16xb2_sample1e-3_v3det_50e.py b/mmdetection/configs/v3det/deformable-detr-refine-twostage_swin_16xb2_sample1e-3_v3det_50e.py new file mode 100644 index 00000000..e640cd60 --- /dev/null +++ b/mmdetection/configs/v3det/deformable-detr-refine-twostage_swin_16xb2_sample1e-3_v3det_50e.py @@ -0,0 +1,27 @@ +_base_ = 'deformable-detr-refine-twostage_r50_8xb4_sample1e-3_v3det_50e.py' + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224.pth' # noqa + +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[256, 512, 1024]), +) + +train_dataloader = dict(batch_size=2, num_workers=2) diff --git a/mmdetection/configs/v3det/dino-4scale_r50_8xb2_sample1e-3_v3det_36e.py b/mmdetection/configs/v3det/dino-4scale_r50_8xb2_sample1e-3_v3det_36e.py new file mode 100644 index 00000000..d9e6e6be --- /dev/null +++ b/mmdetection/configs/v3det/dino-4scale_r50_8xb2_sample1e-3_v3det_36e.py @@ -0,0 +1,109 @@ +_base_ = '../dino/dino-4scale_r50_8xb2-36e_coco.py' + +model = dict( + bbox_head=dict(num_classes=13204), + test_cfg=dict(max_per_img=300), +) + +data_root = 'data/V3Det/' +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] +train_dataloader = dict( + _delete_=True, + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='V3DetDataset', + data_root=data_root, + ann_file='annotations/v3det_2023_v1_train.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=False), + pipeline=train_pipeline, + backend_args=None))) +val_dataloader = dict( + dataset=dict( + type='V3DetDataset', + data_root=data_root, + ann_file='annotations/v3det_2023_v1_val.json', + data_prefix=dict(img=''))) +test_dataloader = val_dataloader + +val_evaluator = dict( + ann_file=data_root + 'annotations/v3det_2023_v1_val.json', + use_mp_eval=True, + proposal_nums=[300]) +test_evaluator = val_evaluator + +# training schedule for 36e +# when using RFS, bs16, each epoch ~ 11460 iter +max_iter = 412560 +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=max_iter / 5) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[343800], # 30e + gamma=0.1) +] + +default_hooks = dict( + timer=dict(type='IterTimerHook'), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict( + type='CheckpointHook', + by_epoch=False, + interval=11460, + max_keep_ckpts=3)) + +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=False) diff --git a/mmdetection/configs/v3det/dino-4scale_swin_16xb1_sample1e-3_v3det_36e.py b/mmdetection/configs/v3det/dino-4scale_swin_16xb1_sample1e-3_v3det_36e.py new file mode 100644 index 00000000..100c4ba4 --- /dev/null +++ b/mmdetection/configs/v3det/dino-4scale_swin_16xb1_sample1e-3_v3det_36e.py @@ -0,0 +1,27 @@ +_base_ = 'dino-4scale_r50_8xb2_sample1e-3_v3det_36e.py' + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224.pth' # noqa + +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[256, 512, 1024]), +) + +train_dataloader = dict(batch_size=1) diff --git a/mmdetection/configs/v3det/faster_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py b/mmdetection/configs/v3det/faster_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py new file mode 100644 index 00000000..3d306fb0 --- /dev/null +++ b/mmdetection/configs/v3det/faster_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py @@ -0,0 +1,72 @@ +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', '../_base_/datasets/v3det.py', + '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + roi_head=dict( + bbox_head=dict( + num_classes=13204, + reg_class_agnostic=True, + cls_predictor_cfg=dict( + type='NormedLinear', tempearture=50, bias=True), + loss_cls=dict( + type='CrossEntropyCustomLoss', + num_classes=13204, + use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn_proposal=dict(nms_pre=4000, max_per_img=2000), + rcnn=dict( + assigner=dict( + perm_repeat_gt_cfg=dict(iou_thr=0.7, perm_range=0.01)))), + test_cfg=dict( + rcnn=dict( + score_thr=0.0001, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=300))) +# dataset settings +train_dataloader = dict(batch_size=4, num_workers=8) + +# training schedule for 2x +max_iter = 68760 * 2 +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=max_iter) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 2048, + by_epoch=False, + begin=0, + end=5000), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[45840 * 2, 63030 * 2], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(_delete_=True, type='AdamW', lr=1e-4 * 1, weight_decay=0.1), + clip_grad=dict(max_norm=35, norm_type=2)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=32) + +default_hooks = dict( + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=5730 * 2)) +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=False) diff --git a/mmdetection/configs/v3det/faster_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py b/mmdetection/configs/v3det/faster_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py new file mode 100644 index 00000000..b0b11108 --- /dev/null +++ b/mmdetection/configs/v3det/faster_rcnn_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py @@ -0,0 +1,27 @@ +_base_ = [ + './faster_rcnn_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py', +] + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224.pth' # noqa + +# model settings +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[128, 256, 512, 1024])) diff --git a/mmdetection/configs/v3det/fcos_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py b/mmdetection/configs/v3det/fcos_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py new file mode 100644 index 00000000..b78e38c9 --- /dev/null +++ b/mmdetection/configs/v3det/fcos_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py @@ -0,0 +1,116 @@ +_base_ = [ + '../_base_/datasets/v3det.py', '../_base_/schedules/schedule_2x.py', + '../_base_/default_runtime.py' +] +# model settings +model = dict( + type='FCOS', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', # use P5 + num_outs=5, + relu_before_extra_convs=True), + bbox_head=dict( + type='FCOSHead', + num_classes=13204, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + cls_predictor_cfg=dict(type='NormedLinear', tempearture=50, bias=True), + loss_cls=dict( + type='FocalCustomLoss', + use_sigmoid=True, + num_classes=13204, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0, + ignore_iof_thr=-1, + perm_repeat_gt_cfg=dict(iou_thr=0.7, perm_range=0.01)), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.0001, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=300)) +# dataset settings + +backend_args = None + +train_dataloader = dict(batch_size=2, num_workers=8) + +# training schedule for 2x +max_iter = 68760 * 2 * 2 +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=max_iter, + val_interval=max_iter) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0 / 2048, + by_epoch=False, + begin=0, + end=5000 * 2), + dict( + type='MultiStepLR', + begin=0, + end=max_iter, + by_epoch=False, + milestones=[45840 * 2 * 2, 63030 * 2 * 2], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + _delete_=True, type='AdamW', lr=1e-4 * 0.25, weight_decay=0.1), + clip_grad=dict(max_norm=35, norm_type=2)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=32) + +default_hooks = dict( + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=5730 * 2)) +log_processor = dict(type='LogProcessor', window_size=50, by_epoch=False) + +find_unused_parameters = True diff --git a/mmdetection/configs/v3det/fcos_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py b/mmdetection/configs/v3det/fcos_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py new file mode 100644 index 00000000..6ca952a2 --- /dev/null +++ b/mmdetection/configs/v3det/fcos_swinb_fpn_8x4_sample1e-3_mstrain_v3det_2x.py @@ -0,0 +1,27 @@ +_base_ = [ + './fcos_r50_fpn_8x4_sample1e-3_mstrain_v3det_2x.py', +] + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224.pth' # noqa + +# model settings +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[128, 256, 512, 1024], force_grad_on_level=True)) diff --git a/mmdetection/configs/v3det/v3det_icon.jpg b/mmdetection/configs/v3det/v3det_icon.jpg new file mode 100644 index 00000000..b25be6fd Binary files /dev/null and b/mmdetection/configs/v3det/v3det_icon.jpg differ diff --git a/mmdetection/configs/vfnet/README.md b/mmdetection/configs/vfnet/README.md new file mode 100644 index 00000000..73b5c07b --- /dev/null +++ b/mmdetection/configs/vfnet/README.md @@ -0,0 +1,48 @@ +# VarifocalNet + +> [VarifocalNet: An IoU-aware Dense Object Detector](https://arxiv.org/abs/2008.13367) + + + +## Abstract + +Accurately ranking the vast number of candidate detections is crucial for dense object detectors to achieve high performance. Prior work uses the classification score or a combination of classification and predicted localization scores to rank candidates. However, neither option results in a reliable ranking, thus degrading detection performance. In this paper, we propose to learn an Iou-aware Classification Score (IACS) as a joint representation of object presence confidence and localization accuracy. We show that dense object detectors can achieve a more accurate ranking of candidate detections based on the IACS. We design a new loss function, named Varifocal Loss, to train a dense object detector to predict the IACS, and propose a new star-shaped bounding box feature representation for IACS prediction and bounding box refinement. Combining these two new components and a bounding box refinement branch, we build an IoU-aware dense object detector based on the FCOS+ATSS architecture, that we call VarifocalNet or VFNet for short. Extensive experiments on MS COCO show that our VFNet consistently surpasses the strong baseline by ∼2.0 AP with different backbones. Our best model VFNet-X-1200 with Res2Net-101-DCN achieves a single-model single-scale AP of 55.1 on COCO test-dev, which is state-of-the-art among various object detectors. + +
    + +
    + +## Introduction + +**VarifocalNet (VFNet)** learns to predict the IoU-aware classification score which mixes the object presence confidence and localization accuracy together as the detection score for a bounding box. The learning is supervised by the proposed Varifocal Loss (VFL), based on a new star-shaped bounding box feature representation (the features at nine yellow sampling points). Given the new representation, the object localization accuracy is further improved by refining the initially regressed bounding box. The full paper is available at: [https://arxiv.org/abs/2008.13367](https://arxiv.org/abs/2008.13367). + +## Results and Models + +| Backbone | Style | DCN | MS train | Lr schd | Inf time (fps) | box AP (val) | box AP (test-dev) | Config | Download | +| :---------: | :-----: | :-: | :------: | :-----: | :------------: | :----------: | :---------------: | :---------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | pytorch | N | N | 1x | - | 41.6 | 41.6 | [config](./vfnet_r50_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_1x_coco/vfnet_r50_fpn_1x_coco_20201027-38db6f58.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_1x_coco/vfnet_r50_fpn_1x_coco.json) | +| R-50 | pytorch | N | Y | 2x | - | 44.5 | 44.8 | [config](./vfnet_r50_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_mstrain_2x_coco/vfnet_r50_fpn_mstrain_2x_coco_20201027-7cc75bd2.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_mstrain_2x_coco/vfnet_r50_fpn_mstrain_2x_coco.json) | +| R-50 | pytorch | Y | Y | 2x | - | 47.8 | 48.0 | [config](./vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_r50_fpn_mdconv_c3-c5_mstrain_2x_coco_20201027pth-6879c318.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_r50_fpn_mdconv_c3-c5_mstrain_2x_coco.json) | +| R-101 | pytorch | N | N | 1x | - | 43.0 | 43.6 | [config](./vfnet_r101_fpn_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r101_fpn_1x_coco/vfnet_r101_fpn_1x_coco_20201027pth-c831ece7.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r101_fpn_1x_coco/vfnet_r101_fpn_1x_coco.json) | +| R-101 | pytorch | N | Y | 2x | - | 46.2 | 46.7 | [config](./vfnet_r101_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r101_fpn_mstrain_2x_coco/vfnet_r101_fpn_mstrain_2x_coco_20201027pth-4a5d53f1.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r101_fpn_mstrain_2x_coco/vfnet_r101_fpn_mstrain_2x_coco.json) | +| R-101 | pytorch | Y | Y | 2x | - | 49.0 | 49.2 | [config](./vfnet_r101-mdconv-c3-c5_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r101_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_r101_fpn_mdconv_c3-c5_mstrain_2x_coco_20201027pth-7729adb5.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r101_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_r101_fpn_mdconv_c3-c5_mstrain_2x_coco.json) | +| X-101-32x4d | pytorch | Y | Y | 2x | - | 49.7 | 50.0 | [config](./vfnet_x101-32x4d-mdconv-c3-c5_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_x101_32x4d_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_x101_32x4d_fpn_mdconv_c3-c5_mstrain_2x_coco_20201027pth-d300a6fc.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_x101_32x4d_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_x101_32x4d_fpn_mdconv_c3-c5_mstrain_2x_coco.json) | +| X-101-64x4d | pytorch | Y | Y | 2x | - | 50.4 | 50.8 | [config](./vfnet_x101-64x4d-mdconv-c3-c5_fpn_ms-2x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_x101_64x4d_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_x101_64x4d_fpn_mdconv_c3-c5_mstrain_2x_coco_20201027pth-b5f6da5e.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_x101_64x4d_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_x101_64x4d_fpn_mdconv_c3-c5_mstrain_2x_coco.json) | + +**Notes:** + +- The MS-train scale range is 1333x\[480:960\] (`range` mode) and the inference scale keeps 1333x800. +- DCN means using `DCNv2` in both backbone and head. +- Inference time will be updated soon. +- More results and pre-trained models can be found in [VarifocalNet-Github](https://github.com/hyz-xmaster/VarifocalNet) + +## Citation + +```latex +@article{zhang2020varifocalnet, + title={VarifocalNet: An IoU-aware Dense Object Detector}, + author={Zhang, Haoyang and Wang, Ying and Dayoub, Feras and S{\"u}nderhauf, Niko}, + journal={arXiv preprint arXiv:2008.13367}, + year={2020} +} +``` diff --git a/mmdetection/configs/vfnet/metafile.yml b/mmdetection/configs/vfnet/metafile.yml new file mode 100644 index 00000000..1b791d01 --- /dev/null +++ b/mmdetection/configs/vfnet/metafile.yml @@ -0,0 +1,116 @@ +Collections: + - Name: VFNet + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - ResNet + - Varifocal Loss + Paper: + URL: https://arxiv.org/abs/2008.13367 + Title: 'VarifocalNet: An IoU-aware Dense Object Detector' + README: configs/vfnet/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.6.0/mmdet/models/detectors/vfnet.py#L6 + Version: v2.6.0 + +Models: + - Name: vfnet_r50_fpn_1x_coco + In Collection: VFNet + Config: configs/vfnet/vfnet_r50_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 41.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_1x_coco/vfnet_r50_fpn_1x_coco_20201027-38db6f58.pth + + - Name: vfnet_r50_fpn_ms-2x_coco + In Collection: VFNet + Config: configs/vfnet/vfnet_r50_fpn_ms-2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 44.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_mstrain_2x_coco/vfnet_r50_fpn_mstrain_2x_coco_20201027-7cc75bd2.pth + + - Name: vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco + In Collection: VFNet + Config: configs/vfnet/vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 48.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r50_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_r50_fpn_mdconv_c3-c5_mstrain_2x_coco_20201027pth-6879c318.pth + + - Name: vfnet_r101_fpn_1x_coco + In Collection: VFNet + Config: configs/vfnet/vfnet_r101_fpn_1x_coco.py + Metadata: + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 43.6 + Weights: https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r101_fpn_1x_coco/vfnet_r101_fpn_1x_coco_20201027pth-c831ece7.pth + + - Name: vfnet_r101_fpn_ms-2x_coco + In Collection: VFNet + Config: configs/vfnet/vfnet_r101_fpn_ms-2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 46.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r101_fpn_mstrain_2x_coco/vfnet_r101_fpn_mstrain_2x_coco_20201027pth-4a5d53f1.pth + + - Name: vfnet_r101-mdconv-c3-c5_fpn_ms-2x_coco + In Collection: VFNet + Config: configs/vfnet/vfnet_r101-mdconv-c3-c5_fpn_ms-2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 49.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_r101_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_r101_fpn_mdconv_c3-c5_mstrain_2x_coco_20201027pth-7729adb5.pth + + - Name: vfnet_x101-32x4d-mdconv-c3-c5_fpn_ms-2x_coco + In Collection: VFNet + Config: configs/vfnet/vfnet_x101-32x4d-mdconv-c3-c5_fpn_ms-2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_x101_32x4d_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_x101_32x4d_fpn_mdconv_c3-c5_mstrain_2x_coco_20201027pth-d300a6fc.pth + + - Name: vfnet_x101-64x4d-mdconv-c3-c5_fpn_ms-2x_coco + In Collection: VFNet + Config: configs/vfnet/vfnet_x101-64x4d-mdconv-c3-c5_fpn_ms-2x_coco.py + Metadata: + Epochs: 24 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/vfnet/vfnet_x101_64x4d_fpn_mdconv_c3-c5_mstrain_2x_coco/vfnet_x101_64x4d_fpn_mdconv_c3-c5_mstrain_2x_coco_20201027pth-b5f6da5e.pth diff --git a/mmdetection/configs/vfnet/vfnet_r101-mdconv-c3-c5_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_r101-mdconv-c3-c5_fpn_ms-2x_coco.py new file mode 100644 index 00000000..2dd67a3b --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_r101-mdconv-c3-c5_fpn_ms-2x_coco.py @@ -0,0 +1,15 @@ +_base_ = './vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + type='ResNet', + depth=101, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/vfnet/vfnet_r101_fpn_1x_coco.py b/mmdetection/configs/vfnet/vfnet_r101_fpn_1x_coco.py new file mode 100644 index 00000000..b296a079 --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_r101_fpn_1x_coco.py @@ -0,0 +1,6 @@ +_base_ = './vfnet_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/vfnet/vfnet_r101_fpn_2x_coco.py b/mmdetection/configs/vfnet/vfnet_r101_fpn_2x_coco.py new file mode 100644 index 00000000..37a7bacb --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_r101_fpn_2x_coco.py @@ -0,0 +1,20 @@ +_base_ = './vfnet_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) +# learning policy +max_epochs = 24 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] + +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/vfnet/vfnet_r101_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_r101_fpn_ms-2x_coco.py new file mode 100644 index 00000000..62f064b7 --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_r101_fpn_ms-2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './vfnet_r50_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/vfnet/vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco.py new file mode 100644 index 00000000..08adf927 --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco.py @@ -0,0 +1,6 @@ +_base_ = './vfnet_r50_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True)), + bbox_head=dict(dcn_on_last_conv=True)) diff --git a/mmdetection/configs/vfnet/vfnet_r50_fpn_1x_coco.py b/mmdetection/configs/vfnet/vfnet_r50_fpn_1x_coco.py new file mode 100644 index 00000000..99bc3b5f --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_r50_fpn_1x_coco.py @@ -0,0 +1,104 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# model settings +model = dict( + type='VFNet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_output', # use P5 + num_outs=5, + relu_before_extra_convs=True), + bbox_head=dict( + type='VFNetHead', + num_classes=80, + in_channels=256, + stacked_convs=3, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + center_sampling=False, + dcn_on_last_conv=False, + use_atss=True, + use_vfl=True, + loss_cls=dict( + type='VarifocalLoss', + use_sigmoid=True, + alpha=0.75, + gamma=2.0, + iou_weighted=True, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.5), + loss_bbox_refine=dict(type='GIoULoss', loss_weight=2.0)), + # training and testing settings + train_cfg=dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) + +# data setting +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# optimizer +optim_wrapper = dict( + optimizer=dict(lr=0.01), + paramwise_cfg=dict(bias_lr_mult=2., bias_decay_mult=0.), + clip_grad=None) +# learning rate +max_epochs = 12 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/vfnet/vfnet_r50_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_r50_fpn_ms-2x_coco.py new file mode 100644 index 00000000..0f8eed29 --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_r50_fpn_ms-2x_coco.py @@ -0,0 +1,36 @@ +_base_ = './vfnet_r50_fpn_1x_coco.py' +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', scale=[(1333, 480), (1333, 960)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader +# learning policy +max_epochs = 24 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] + +train_cfg = dict(max_epochs=max_epochs) diff --git a/mmdetection/configs/vfnet/vfnet_res2net-101_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_res2net-101_fpn_ms-2x_coco.py new file mode 100644 index 00000000..94288e8e --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_res2net-101_fpn_ms-2x_coco.py @@ -0,0 +1,16 @@ +_base_ = './vfnet_r50_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + type='Res2Net', + depth=101, + scales=4, + base_width=26, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://res2net101_v1d_26w_4s'))) diff --git a/mmdetection/configs/vfnet/vfnet_res2net101-mdconv-c3-c5_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_res2net101-mdconv-c3-c5_fpn_ms-2x_coco.py new file mode 100644 index 00000000..269330d3 --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_res2net101-mdconv-c3-c5_fpn_ms-2x_coco.py @@ -0,0 +1,18 @@ +_base_ = './vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + type='Res2Net', + depth=101, + scales=4, + base_width=26, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://res2net101_v1d_26w_4s'))) diff --git a/mmdetection/configs/vfnet/vfnet_x101-32x4d-mdconv-c3-c5_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_x101-32x4d-mdconv-c3-c5_fpn_ms-2x_coco.py new file mode 100644 index 00000000..465da0cb --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_x101-32x4d-mdconv-c3-c5_fpn_ms-2x_coco.py @@ -0,0 +1,17 @@ +_base_ = './vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/vfnet/vfnet_x101-32x4d_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_x101-32x4d_fpn_ms-2x_coco.py new file mode 100644 index 00000000..486bcfe5 --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_x101-32x4d_fpn_ms-2x_coco.py @@ -0,0 +1,15 @@ +_base_ = './vfnet_r50_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/configs/vfnet/vfnet_x101-64x4d-mdconv-c3-c5_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_x101-64x4d-mdconv-c3-c5_fpn_ms-2x_coco.py new file mode 100644 index 00000000..14a070e7 --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_x101-64x4d-mdconv-c3-c5_fpn_ms-2x_coco.py @@ -0,0 +1,17 @@ +_base_ = './vfnet_r50-mdconv-c3-c5_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), + stage_with_dcn=(False, True, True, True), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/vfnet/vfnet_x101-64x4d_fpn_ms-2x_coco.py b/mmdetection/configs/vfnet/vfnet_x101-64x4d_fpn_ms-2x_coco.py new file mode 100644 index 00000000..92e3f71d --- /dev/null +++ b/mmdetection/configs/vfnet/vfnet_x101-64x4d_fpn_ms-2x_coco.py @@ -0,0 +1,15 @@ +_base_ = './vfnet_r50_fpn_ms-2x_coco.py' +model = dict( + backbone=dict( + type='ResNeXt', + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/configs/wider_face/README.md b/mmdetection/configs/wider_face/README.md new file mode 100644 index 00000000..1904506c --- /dev/null +++ b/mmdetection/configs/wider_face/README.md @@ -0,0 +1,57 @@ +# WIDER FACE + +> [WIDER FACE: A Face Detection Benchmark](https://arxiv.org/abs/1511.06523) + + + +## Abstract + +Face detection is one of the most studied topics in the computer vision community. Much of the progresses have been made by the availability of face detection benchmark datasets. We show that there is a gap between current face detection performance and the real world requirements. To facilitate future face detection research, we introduce the WIDER FACE dataset, which is 10 times larger than existing datasets. The dataset contains rich annotations, including occlusions, poses, event categories, and face bounding boxes. Faces in the proposed dataset are extremely challenging due to large variations in scale, pose and occlusion, as shown in Fig. 1. Furthermore, we show that WIDER FACE dataset is an effective training source for face detection. We benchmark several representative detection systems, providing an overview of state-of-the-art performance and propose a solution to deal with large scale variation. Finally, we discuss common failure cases that worth to be further investigated. + +
    + +
    + +## Introduction + +To use the WIDER Face dataset you need to download it +and extract to the `data/WIDERFace` folder. Annotation in the VOC format +can be found in this [repo](https://github.com/sovrasov/wider-face-pascal-voc-annotations.git). +You should move the annotation files from `WIDER_train_annotations` and `WIDER_val_annotations` folders +to the `Annotation` folders inside the corresponding directories `WIDER_train` and `WIDER_val`. +Also annotation lists `val.txt` and `train.txt` should be copied to `data/WIDERFace` from `WIDER_train_annotations` and `WIDER_val_annotations`. +The directory should be like this: + +``` +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── WIDERFace +│ │ ├── WIDER_train +│ | │ ├──0--Parade +│ | │ ├── ... +│ | │ ├── Annotations +│ │ ├── WIDER_val +│ | │ ├──0--Parade +│ | │ ├── ... +│ | │ ├── Annotations +│ │ ├── val.txt +│ │ ├── train.txt + +``` + +After that you can train the SSD300 on WIDER by launching training with the `ssd300_wider_face.py` config or +create your own config based on the presented one. + +## Citation + +```latex +@inproceedings{yang2016wider, + Author = {Yang, Shuo and Luo, Ping and Loy, Chen Change and Tang, Xiaoou}, + Booktitle = {IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, + Title = {WIDER FACE: A Face Detection Benchmark}, + Year = {2016} +} +``` diff --git a/mmdetection/configs/wider_face/retinanet_r50_fpn_1x_widerface.py b/mmdetection/configs/wider_face/retinanet_r50_fpn_1x_widerface.py new file mode 100644 index 00000000..78067255 --- /dev/null +++ b/mmdetection/configs/wider_face/retinanet_r50_fpn_1x_widerface.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/wider_face.py', '../_base_/schedules/schedule_1x.py', + '../_base_/default_runtime.py' +] +# model settings +model = dict(bbox_head=dict(num_classes=1)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) diff --git a/mmdetection/configs/wider_face/ssd300_8xb32-24e_widerface.py b/mmdetection/configs/wider_face/ssd300_8xb32-24e_widerface.py new file mode 100644 index 00000000..02c3c927 --- /dev/null +++ b/mmdetection/configs/wider_face/ssd300_8xb32-24e_widerface.py @@ -0,0 +1,64 @@ +_base_ = [ + '../_base_/models/ssd300.py', '../_base_/datasets/wider_face.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_2x.py' +] +model = dict(bbox_head=dict(num_classes=1)) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict( + type='Expand', + mean={{_base_.model.data_preprocessor.mean}}, + to_rgb={{_base_.model.data_preprocessor.bgr_to_rgb}}, + ratio_range=(1, 4)), + dict( + type='MinIoURandomCrop', + min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=(300, 300), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='Resize', scale=(300, 300), keep_ratio=False), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +dataset_type = 'WIDERFaceDataset' +data_root = 'data/WIDERFace/' +train_dataloader = dict( + batch_size=32, num_workers=8, dataset=dict(pipeline=train_pipeline)) + +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict(type='MultiStepLR', by_epoch=True, milestones=[16, 20], gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + optimizer=dict(lr=0.012, momentum=0.9, weight_decay=5e-4), + clip_grad=dict(max_norm=35, norm_type=2)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (32 samples per GPU) +auto_scale_lr = dict(base_batch_size=256) diff --git a/mmdetection/configs/yolact/README.md b/mmdetection/configs/yolact/README.md new file mode 100644 index 00000000..e884ad65 --- /dev/null +++ b/mmdetection/configs/yolact/README.md @@ -0,0 +1,75 @@ +# YOLACT + +> [YOLACT: Real-time Instance Segmentation](https://arxiv.org/abs/1904.02689) + + + +## Abstract + +We present a simple, fully-convolutional model for real-time instance segmentation that achieves 29.8 mAP on MS COCO at 33.5 fps evaluated on a single Titan Xp, which is significantly faster than any previous competitive approach. Moreover, we obtain this result after training on only one GPU. We accomplish this by breaking instance segmentation into two parallel subtasks: (1) generating a set of prototype masks and (2) predicting per-instance mask coefficients. Then we produce instance masks by linearly combining the prototypes with the mask coefficients. We find that because this process doesn't depend on repooling, this approach produces very high-quality masks and exhibits temporal stability for free. Furthermore, we analyze the emergent behavior of our prototypes and show they learn to localize instances on their own in a translation variant manner, despite being fully-convolutional. Finally, we also propose Fast NMS, a drop-in 12 ms faster replacement for standard NMS that only has a marginal performance penalty. + +
    + +
    + +## Introduction + +A simple, fully convolutional model for real-time instance segmentation. This is the code for our paper: + +- [YOLACT: Real-time Instance Segmentation](https://arxiv.org/abs/1904.02689) + + + +For a real-time demo, check out our ICCV video: +[![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/0pMfmo8qfpQ/0.jpg)](https://www.youtube.com/watch?v=0pMfmo8qfpQ) + +## Evaluation + +Here are our YOLACT models along with their FPS on a Titan Xp and mAP on COCO's `val`: + +| Image Size | GPU x BS | Backbone | \*FPS | mAP | Weights | Configs | Download | +| :--------: | :------: | :-----------: | :---: | :--: | :-----: | :--------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | +| 550 | 1x8 | Resnet50-FPN | 42.5 | 29.0 | | [config](./yolact_r50_1xb8-55e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolact/yolact_r50_1x8_coco/yolact_r50_1x8_coco_20200908-f38d58df.pth) | +| 550 | 8x8 | Resnet50-FPN | 42.5 | 28.4 | | [config](./yolact_r50_8xb8-55e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolact/yolact_r50_8x8_coco/yolact_r50_8x8_coco_20200908-ca34f5db.pth) | +| 550 | 1x8 | Resnet101-FPN | 33.5 | 30.4 | | [config](./yolact_r101_1xb8-55e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolact/yolact_r101_1x8_coco/yolact_r101_1x8_coco_20200908-4cbe9101.pth) | + +\*Note: The FPS is evaluated by the [original implementation](https://github.com/dbolya/yolact). When calculating FPS, only the model inference time is taken into account. Data loading and post-processing operations such as converting masks to RLE code, generating COCO JSON results, image rendering are not included. + +## Training + +All the aforementioned models are trained with a single GPU. It typically takes ~12GB VRAM when using resnet-101 as the backbone. If you want to try multiple GPUs training, you may have to modify the configuration files accordingly, such as adjusting the training schedule and freezing batch norm. + +```Shell +# Trains using the resnet-101 backbone with a batch size of 8 on a single GPU. +./tools/dist_train.sh configs/yolact/yolact_r101.py 1 +``` + +## Testing + +Please refer to [mmdetection/docs/getting_started.md](https://mmdetection.readthedocs.io/en/latest/1_exist_data_model.html#test-existing-models). + +## Citation + +If you use YOLACT or this code base in your work, please cite + +```latex +@inproceedings{yolact-iccv2019, + author = {Daniel Bolya and Chong Zhou and Fanyi Xiao and Yong Jae Lee}, + title = {YOLACT: {Real-time} Instance Segmentation}, + booktitle = {ICCV}, + year = {2019}, +} +``` + + diff --git a/mmdetection/configs/yolact/metafile.yml b/mmdetection/configs/yolact/metafile.yml new file mode 100644 index 00000000..9ca76b3d --- /dev/null +++ b/mmdetection/configs/yolact/metafile.yml @@ -0,0 +1,81 @@ +Collections: + - Name: YOLACT + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - FPN + - ResNet + Paper: + URL: https://arxiv.org/abs/1904.02689 + Title: 'YOLACT: Real-time Instance Segmentation' + README: configs/yolact/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.5.0/mmdet/models/detectors/yolact.py#L9 + Version: v2.5.0 + +Models: + - Name: yolact_r50_1x8_coco + In Collection: YOLACT + Config: configs/yolact/yolact_r50_1xb8-55e_coco.py + Metadata: + Training Resources: 1x V100 GPU + Batch Size: 8 + Epochs: 55 + inference time (ms/im): + - value: 23.53 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (550, 550) + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 29.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolact/yolact_r50_1x8_coco/yolact_r50_1x8_coco_20200908-f38d58df.pth + + - Name: yolact_r50_8x8_coco + In Collection: YOLACT + Config: configs/yolact/yolact_r50_8xb8-55e_coco.py + Metadata: + Batch Size: 64 + Epochs: 55 + inference time (ms/im): + - value: 23.53 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (550, 550) + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 28.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolact/yolact_r50_8x8_coco/yolact_r50_8x8_coco_20200908-ca34f5db.pth + + - Name: yolact_r101_1x8_coco + In Collection: YOLACT + Config: configs/yolact/yolact_r101_1xb8-55e_coco.py + Metadata: + Training Resources: 1x V100 GPU + Batch Size: 8 + Epochs: 55 + inference time (ms/im): + - value: 29.85 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (550, 550) + Results: + - Task: Instance Segmentation + Dataset: COCO + Metrics: + mask AP: 30.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolact/yolact_r101_1x8_coco/yolact_r101_1x8_coco_20200908-4cbe9101.pth diff --git a/mmdetection/configs/yolact/yolact_r101_1xb8-55e_coco.py b/mmdetection/configs/yolact/yolact_r101_1xb8-55e_coco.py new file mode 100644 index 00000000..e6ffe296 --- /dev/null +++ b/mmdetection/configs/yolact/yolact_r101_1xb8-55e_coco.py @@ -0,0 +1,7 @@ +_base_ = './yolact_r50_1xb8-55e_coco.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/configs/yolact/yolact_r50_1xb8-55e_coco.py b/mmdetection/configs/yolact/yolact_r50_1xb8-55e_coco.py new file mode 100644 index 00000000..b7dabf15 --- /dev/null +++ b/mmdetection/configs/yolact/yolact_r50_1xb8-55e_coco.py @@ -0,0 +1,170 @@ +_base_ = [ + '../_base_/datasets/coco_instance.py', '../_base_/default_runtime.py' +] +img_norm_cfg = dict( + mean=[123.68, 116.78, 103.94], std=[58.40, 57.12, 57.38], to_rgb=True) +# model settings +input_size = 550 +model = dict( + type='YOLACT', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=img_norm_cfg['mean'], + std=img_norm_cfg['std'], + bgr_to_rgb=img_norm_cfg['to_rgb'], + pad_mask=True), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=-1, # do not freeze stem + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=False, # update the statistics of bn + zero_init_residual=False, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_input', + num_outs=5, + upsample_cfg=dict(mode='bilinear')), + bbox_head=dict( + type='YOLACTHead', + num_classes=80, + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=3, + scales_per_octave=1, + base_sizes=[8, 16, 32, 64, 128], + ratios=[0.5, 1.0, 2.0], + strides=[550.0 / x for x in [69, 35, 18, 9, 5]], + centers=[(550 * 0.5 / x, 550 * 0.5 / x) + for x in [69, 35, 18, 9, 5]]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + reduction='none', + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.5), + num_head_convs=1, + num_protos=32, + use_ohem=True), + mask_head=dict( + type='YOLACTProtonet', + in_channels=256, + num_protos=32, + num_classes=80, + max_masks_to_train=100, + loss_mask_weight=6.125, + with_seg_branch=True, + loss_segm=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0., + ignore_iof_thr=-1, + gt_max_assign_all=False), + sampler=dict(type='PseudoSampler'), # YOLACT should use PseudoSampler + # smoothl1_beta=1., + allowed_border=-1, + pos_weight=-1, + neg_pos_ratio=3, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + mask_thr=0.5, + iou_thr=0.5, + top_k=200, + max_per_img=100, + mask_thr_binary=0.5)) +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(4.0, 4.0)), + dict( + type='Expand', + mean=img_norm_cfg['mean'], + to_rgb=img_norm_cfg['to_rgb'], + ratio_range=(1, 4)), + dict( + type='MinIoURandomCrop', + min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict( + type='PhotoMetricDistortion', + brightness_delta=32, + contrast_range=(0.5, 1.5), + saturation_range=(0.5, 1.5), + hue_delta=18), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(input_size, input_size), keep_ratio=False), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=8, + num_workers=4, + batch_sampler=None, + dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +max_epochs = 55 +# training schedule for 55e +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# learning rate +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[20, 42, 49, 52], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=1e-3, momentum=0.9, weight_decay=5e-4)) + +custom_hooks = [ + dict(type='CheckInvalidLossHook', interval=50, priority='VERY_LOW') +] + +env_cfg = dict(cudnn_benchmark=True) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (1 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=8) diff --git a/mmdetection/configs/yolact/yolact_r50_8xb8-55e_coco.py b/mmdetection/configs/yolact/yolact_r50_8xb8-55e_coco.py new file mode 100644 index 00000000..e39c285d --- /dev/null +++ b/mmdetection/configs/yolact/yolact_r50_8xb8-55e_coco.py @@ -0,0 +1,23 @@ +_base_ = 'yolact_r50_1xb8-55e_coco.py' + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(lr=8e-3), + clip_grad=dict(max_norm=35, norm_type=2)) +# learning rate +max_epochs = 55 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[20, 42, 49, 52], + gamma=0.1) +] +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/yolo/README.md b/mmdetection/configs/yolo/README.md new file mode 100644 index 00000000..9cb47bcc --- /dev/null +++ b/mmdetection/configs/yolo/README.md @@ -0,0 +1,55 @@ +# YOLOv3 + +> [YOLOv3: An Incremental Improvement](https://arxiv.org/abs/1804.02767) + + + +## Abstract + +We present some updates to YOLO! We made a bunch of little design changes to make it better. We also trained this new network that's pretty swell. It's a little bigger than last time but more accurate. It's still fast though, don't worry. At 320x320 YOLOv3 runs in 22 ms at 28.2 mAP, as accurate as SSD but three times faster. When we look at the old .5 IOU mAP detection metric YOLOv3 is quite good. It achieves 57.9 mAP@50 in 51 ms on a Titan X, compared to 57.5 mAP@50 in 198 ms by RetinaNet, similar performance but 3.8x faster. + +
    + +
    + +## Results and Models + +| Backbone | Scale | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :--------: | :---: | :-----: | :------: | :------------: | :----: | :---------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| DarkNet-53 | 320 | 273e | 2.7 | 63.9 | 27.9 | [config](./yolov3_d53_8xb8-320-273e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_320_273e_coco/yolov3_d53_320_273e_coco-421362b6.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_320_273e_coco/yolov3_d53_320_273e_coco-20200819_172101.log.json) | +| DarkNet-53 | 416 | 273e | 3.8 | 61.2 | 30.9 | [config](./yolov3_d53_8xb8-ms-416-273e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_mstrain-416_273e_coco/yolov3_d53_mstrain-416_273e_coco-2b60fcd9.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_mstrain-416_273e_coco/yolov3_d53_mstrain-416_273e_coco-20200819_173424.log.json) | +| DarkNet-53 | 608 | 273e | 7.4 | 48.1 | 33.7 | [config](./yolov3_d53_8xb8-ms-608-273e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_mstrain-608_273e_coco/yolov3_d53_mstrain-608_273e_coco_20210518_115020-a2c3acb8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_mstrain-608_273e_coco/yolov3_d53_mstrain-608_273e_coco_20210518_115020.log.json) | + +## Mixed Precision Training + +We also train YOLOv3 with mixed precision training. + +| Backbone | Scale | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :--------: | :---: | :-----: | :------: | :------------: | :----: | :-------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| DarkNet-53 | 608 | 273e | 4.7 | 48.1 | 33.8 | [config](./yolov3_d53_8xb8-amp-ms-608-273e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_fp16_mstrain-608_273e_coco/yolov3_d53_fp16_mstrain-608_273e_coco_20210517_213542-4bc34944.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_fp16_mstrain-608_273e_coco/yolov3_d53_fp16_mstrain-608_273e_coco_20210517_213542.log.json) | + +## Lightweight models + +| Backbone | Scale | Lr schd | Mem (GB) | Inf time (fps) | box AP | Config | Download | +| :---------: | :---: | :-----: | :------: | :------------: | :----: | :------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| MobileNetV2 | 416 | 300e | 5.3 | | 23.9 | [config](./yolov3_mobilenetv2_8xb24-ms-416-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_mobilenetv2_mstrain-416_300e_coco/yolov3_mobilenetv2_mstrain-416_300e_coco_20210718_010823-f68a07b3.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_mobilenetv2_mstrain-416_300e_coco/yolov3_mobilenetv2_mstrain-416_300e_coco_20210718_010823.log.json) | +| MobileNetV2 | 320 | 300e | 3.2 | | 22.2 | [config](./yolov3_mobilenetv2_8xb24-320-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_mobilenetv2_320_300e_coco/yolov3_mobilenetv2_320_300e_coco_20210719_215349-d18dff72.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_mobilenetv2_320_300e_coco/yolov3_mobilenetv2_320_300e_coco_20210719_215349.log.json) | + +Notice: We reduce the number of channels to 96 in both head and neck. It can reduce the flops and parameters, which makes these models more suitable for edge devices. + +## Credit + +This implementation originates from the project of Haoyu Wu(@wuhy08) at Western Digital. + +## Citation + +```latex +@misc{redmon2018yolov3, + title={YOLOv3: An Incremental Improvement}, + author={Joseph Redmon and Ali Farhadi}, + year={2018}, + eprint={1804.02767}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} +``` diff --git a/mmdetection/configs/yolo/metafile.yml b/mmdetection/configs/yolo/metafile.yml new file mode 100644 index 00000000..627e70c4 --- /dev/null +++ b/mmdetection/configs/yolo/metafile.yml @@ -0,0 +1,124 @@ +Collections: + - Name: YOLOv3 + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - DarkNet + Paper: + URL: https://arxiv.org/abs/1804.02767 + Title: 'YOLOv3: An Incremental Improvement' + README: configs/yolo/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.4.0/mmdet/models/detectors/yolo.py#L8 + Version: v2.4.0 + +Models: + - Name: yolov3_d53_320_273e_coco + In Collection: YOLOv3 + Config: configs/yolo/yolov3_d53_8xb8-320-273e_coco.py + Metadata: + Training Memory (GB): 2.7 + inference time (ms/im): + - value: 15.65 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (320, 320) + Epochs: 273 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 27.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_320_273e_coco/yolov3_d53_320_273e_coco-421362b6.pth + + - Name: yolov3_d53_mstrain-416_273e_coco + In Collection: YOLOv3 + Config: configs/yolo/yolov3_d53_8xb8-ms-416-273e_coco.py + Metadata: + Training Memory (GB): 3.8 + inference time (ms/im): + - value: 16.34 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (416, 416) + Epochs: 273 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 30.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_mstrain-416_273e_coco/yolov3_d53_mstrain-416_273e_coco-2b60fcd9.pth + + - Name: yolov3_d53_mstrain-608_273e_coco + In Collection: YOLOv3 + Config: configs/yolo/yolov3_d53_8xb8-ms-608-273e_coco.py + Metadata: + Training Memory (GB): 7.4 + inference time (ms/im): + - value: 20.79 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP32 + resolution: (608, 608) + Epochs: 273 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 33.7 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_mstrain-608_273e_coco/yolov3_d53_mstrain-608_273e_coco_20210518_115020-a2c3acb8.pth + + - Name: yolov3_d53_fp16_mstrain-608_273e_coco + In Collection: YOLOv3 + Config: configs/yolo/yolov3_d53_8xb8-amp-ms-608-273e_coco.py + Metadata: + Training Memory (GB): 4.7 + inference time (ms/im): + - value: 20.79 + hardware: V100 + backend: PyTorch + batch size: 1 + mode: FP16 + resolution: (608, 608) + Epochs: 273 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 33.8 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_fp16_mstrain-608_273e_coco/yolov3_d53_fp16_mstrain-608_273e_coco_20210517_213542-4bc34944.pth + + - Name: yolov3_mobilenetv2_8xb24-320-300e_coco + In Collection: YOLOv3 + Config: configs/yolo/yolov3_mobilenetv2_8xb24-320-300e_coco.py + Metadata: + Training Memory (GB): 3.2 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 22.2 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_mobilenetv2_320_300e_coco/yolov3_mobilenetv2_320_300e_coco_20210719_215349-d18dff72.pth + + - Name: yolov3_mobilenetv2_8xb24-ms-416-300e_coco + In Collection: YOLOv3 + Config: configs/yolo/yolov3_mobilenetv2_8xb24-ms-416-300e_coco.py + Metadata: + Training Memory (GB): 5.3 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 23.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_mobilenetv2_mstrain-416_300e_coco/yolov3_mobilenetv2_mstrain-416_300e_coco_20210718_010823-f68a07b3.pth diff --git a/mmdetection/configs/yolo/yolov3_d53_8xb8-320-273e_coco.py b/mmdetection/configs/yolo/yolov3_d53_8xb8-320-273e_coco.py new file mode 100644 index 00000000..a3d08dd7 --- /dev/null +++ b/mmdetection/configs/yolo/yolov3_d53_8xb8-320-273e_coco.py @@ -0,0 +1,29 @@ +_base_ = './yolov3_d53_8xb8-ms-608-273e_coco.py' + +input_size = (320, 320) +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + # `mean` and `to_rgb` should be the same with the `preprocess_cfg` + dict(type='Expand', mean=[0, 0, 0], to_rgb=True, ratio_range=(1, 2)), + dict( + type='MinIoURandomCrop', + min_ious=(0.4, 0.5, 0.6, 0.7, 0.8, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=input_size, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=input_size, keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/yolo/yolov3_d53_8xb8-amp-ms-608-273e_coco.py b/mmdetection/configs/yolo/yolov3_d53_8xb8-amp-ms-608-273e_coco.py new file mode 100644 index 00000000..173d8ee2 --- /dev/null +++ b/mmdetection/configs/yolo/yolov3_d53_8xb8-amp-ms-608-273e_coco.py @@ -0,0 +1,3 @@ +_base_ = './yolov3_d53_8xb8-ms-608-273e_coco.py' +# fp16 settings +optim_wrapper = dict(type='AmpOptimWrapper', loss_scale='dynamic') diff --git a/mmdetection/configs/yolo/yolov3_d53_8xb8-ms-416-273e_coco.py b/mmdetection/configs/yolo/yolov3_d53_8xb8-ms-416-273e_coco.py new file mode 100644 index 00000000..ca0127e8 --- /dev/null +++ b/mmdetection/configs/yolo/yolov3_d53_8xb8-ms-416-273e_coco.py @@ -0,0 +1,28 @@ +_base_ = './yolov3_d53_8xb8-ms-608-273e_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + # `mean` and `to_rgb` should be the same with the `preprocess_cfg` + dict(type='Expand', mean=[0, 0, 0], to_rgb=True, ratio_range=(1, 2)), + dict( + type='MinIoURandomCrop', + min_ious=(0.4, 0.5, 0.6, 0.7, 0.8, 0.9), + min_crop_size=0.3), + dict(type='RandomResize', scale=[(320, 320), (416, 416)], keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(416, 416), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/yolo/yolov3_d53_8xb8-ms-608-273e_coco.py b/mmdetection/configs/yolo/yolov3_d53_8xb8-ms-608-273e_coco.py new file mode 100644 index 00000000..d4a36dfd --- /dev/null +++ b/mmdetection/configs/yolo/yolov3_d53_8xb8-ms-608-273e_coco.py @@ -0,0 +1,167 @@ +_base_ = ['../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'] +# model settings +data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[0, 0, 0], + std=[255., 255., 255.], + bgr_to_rgb=True, + pad_size_divisor=32) +model = dict( + type='YOLOV3', + data_preprocessor=data_preprocessor, + backbone=dict( + type='Darknet', + depth=53, + out_indices=(3, 4, 5), + init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://darknet53')), + neck=dict( + type='YOLOV3Neck', + num_scales=3, + in_channels=[1024, 512, 256], + out_channels=[512, 256, 128]), + bbox_head=dict( + type='YOLOV3Head', + num_classes=80, + in_channels=[512, 256, 128], + out_channels=[1024, 512, 256], + anchor_generator=dict( + type='YOLOAnchorGenerator', + base_sizes=[[(116, 90), (156, 198), (373, 326)], + [(30, 61), (62, 45), (59, 119)], + [(10, 13), (16, 30), (33, 23)]], + strides=[32, 16, 8]), + bbox_coder=dict(type='YOLOBBoxCoder'), + featmap_strides=[32, 16, 8], + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0, + reduction='sum'), + loss_conf=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0, + reduction='sum'), + loss_xy=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=2.0, + reduction='sum'), + loss_wh=dict(type='MSELoss', loss_weight=2.0, reduction='sum')), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='GridAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0)), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + conf_thr=0.005, + nms=dict(type='nms', iou_threshold=0.45), + max_per_img=100)) +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='Expand', + mean=data_preprocessor['mean'], + to_rgb=data_preprocessor['bgr_to_rgb'], + ratio_range=(1, 2)), + dict( + type='MinIoURandomCrop', + min_ious=(0.4, 0.5, 0.6, 0.7, 0.8, 0.9), + min_crop_size=0.3), + dict(type='RandomResize', scale=[(320, 320), (608, 608)], keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(608, 608), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=8, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + backend_args=backend_args) +test_evaluator = val_evaluator + +train_cfg = dict(max_epochs=273, val_interval=7) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.001, momentum=0.9, weight_decay=0.0005), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning policy +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=2000), + dict(type='MultiStepLR', by_epoch=True, milestones=[218, 246], gamma=0.1) +] + +default_hooks = dict(checkpoint=dict(type='CheckpointHook', interval=7)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/yolo/yolov3_mobilenetv2_8xb24-320-300e_coco.py b/mmdetection/configs/yolo/yolov3_mobilenetv2_8xb24-320-300e_coco.py new file mode 100644 index 00000000..07b39373 --- /dev/null +++ b/mmdetection/configs/yolo/yolov3_mobilenetv2_8xb24-320-300e_coco.py @@ -0,0 +1,42 @@ +_base_ = ['./yolov3_mobilenetv2_8xb24-ms-416-300e_coco.py'] + +# yapf:disable +model = dict( + bbox_head=dict( + anchor_generator=dict( + base_sizes=[[(220, 125), (128, 222), (264, 266)], + [(35, 87), (102, 96), (60, 170)], + [(10, 15), (24, 36), (72, 42)]]))) +# yapf:enable + +input_size = (320, 320) +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + # `mean` and `to_rgb` should be the same with the `preprocess_cfg` + dict( + type='Expand', + mean=[123.675, 116.28, 103.53], + to_rgb=True, + ratio_range=(1, 2)), + dict( + type='MinIoURandomCrop', + min_ious=(0.4, 0.5, 0.6, 0.7, 0.8, 0.9), + min_crop_size=0.3), + dict(type='Resize', scale=input_size, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=input_size, keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict(dataset=dict(dataset=dict(pipeline=train_pipeline))) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/yolo/yolov3_mobilenetv2_8xb24-ms-416-300e_coco.py b/mmdetection/configs/yolo/yolov3_mobilenetv2_8xb24-ms-416-300e_coco.py new file mode 100644 index 00000000..9a161b66 --- /dev/null +++ b/mmdetection/configs/yolo/yolov3_mobilenetv2_8xb24-ms-416-300e_coco.py @@ -0,0 +1,176 @@ +_base_ = ['../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'] +# model settings +data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32) +model = dict( + type='YOLOV3', + data_preprocessor=data_preprocessor, + backbone=dict( + type='MobileNetV2', + out_indices=(2, 4, 6), + act_cfg=dict(type='LeakyReLU', negative_slope=0.1), + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://mmdet/mobilenet_v2')), + neck=dict( + type='YOLOV3Neck', + num_scales=3, + in_channels=[320, 96, 32], + out_channels=[96, 96, 96]), + bbox_head=dict( + type='YOLOV3Head', + num_classes=80, + in_channels=[96, 96, 96], + out_channels=[96, 96, 96], + anchor_generator=dict( + type='YOLOAnchorGenerator', + base_sizes=[[(116, 90), (156, 198), (373, 326)], + [(30, 61), (62, 45), (59, 119)], + [(10, 13), (16, 30), (33, 23)]], + strides=[32, 16, 8]), + bbox_coder=dict(type='YOLOBBoxCoder'), + featmap_strides=[32, 16, 8], + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0, + reduction='sum'), + loss_conf=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0, + reduction='sum'), + loss_xy=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=2.0, + reduction='sum'), + loss_wh=dict(type='MSELoss', loss_weight=2.0, reduction='sum')), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='GridAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0)), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + conf_thr=0.005, + nms=dict(type='nms', iou_threshold=0.45), + max_per_img=100)) +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='Expand', + mean=data_preprocessor['mean'], + to_rgb=data_preprocessor['bgr_to_rgb'], + ratio_range=(1, 2)), + dict( + type='MinIoURandomCrop', + min_ious=(0.4, 0.5, 0.6, 0.7, 0.8, 0.9), + min_crop_size=0.3), + dict(type='RandomResize', scale=[(320, 320), (416, 416)], keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=(416, 416), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=24, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='RepeatDataset', # use RepeatDataset to speed up training + times=10, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=24, + num_workers=4, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + backend_args=backend_args) +test_evaluator = val_evaluator + +train_cfg = dict(max_epochs=30) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.003, momentum=0.9, weight_decay=0.0005), + clip_grad=dict(max_norm=35, norm_type=2)) + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.0001, + by_epoch=False, + begin=0, + end=4000), + dict(type='MultiStepLR', by_epoch=True, milestones=[24, 28], gamma=0.1) +] + +find_unused_parameters = True + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (24 samples per GPU) +auto_scale_lr = dict(base_batch_size=192) diff --git a/mmdetection/configs/yolof/README.md b/mmdetection/configs/yolof/README.md new file mode 100644 index 00000000..b9167f6e --- /dev/null +++ b/mmdetection/configs/yolof/README.md @@ -0,0 +1,35 @@ +# YOLOF + +> [You Only Look One-level Feature](https://arxiv.org/abs/2103.09460) + + + +## Abstract + +This paper revisits feature pyramids networks (FPN) for one-stage detectors and points out that the success of FPN is due to its divide-and-conquer solution to the optimization problem in object detection rather than multi-scale feature fusion. From the perspective of optimization, we introduce an alternative way to address the problem instead of adopting the complex feature pyramids - {\\em utilizing only one-level feature for detection}. Based on the simple and efficient solution, we present You Only Look One-level Feature (YOLOF). In our method, two key components, Dilated Encoder and Uniform Matching, are proposed and bring considerable improvements. Extensive experiments on the COCO benchmark prove the effectiveness of the proposed model. Our YOLOF achieves comparable results with its feature pyramids counterpart RetinaNet while being 2.5× faster. Without transformer layers, YOLOF can match the performance of DETR in a single-level feature manner with 7× less training epochs. With an image size of 608×608, YOLOF achieves 44.3 mAP running at 60 fps on 2080Ti, which is 13% faster than YOLOv4. + +
    + +
    + +## Results and Models + +| Backbone | Style | Epoch | Lr schd | Mem (GB) | box AP | Config | Download | +| :------: | :---: | :---: | :-----: | :------: | :----: | :--------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50-C5 | caffe | Y | 1x | 8.3 | 37.5 | [config](./yolof_r50-c5_8xb8-1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolof/yolof_r50_c5_8x8_1x_coco/yolof_r50_c5_8x8_1x_coco_20210425_024427-8e864411.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolof/yolof_r50_c5_8x8_1x_coco/yolof_r50_c5_8x8_1x_coco_20210425_024427.log.json) | + +**Note**: + +1. We find that the performance is unstable and may fluctuate by about 0.3 mAP. mAP 37.4 ~ 37.7 is acceptable in YOLOF_R_50_C5_1x. Such fluctuation can also be found in the [original implementation](https://github.com/chensnathan/YOLOF). +2. In addition to instability issues, sometimes there are large loss fluctuations and NAN, so there may still be problems with this project, which will be improved subsequently. + +## Citation + +```latex +@inproceedings{chen2021you, + title={You Only Look One-level Feature}, + author={Chen, Qiang and Wang, Yingming and Yang, Tong and Zhang, Xiangyu and Cheng, Jian and Sun, Jian}, + booktitle={IEEE Conference on Computer Vision and Pattern Recognition}, + year={2021} +} +``` diff --git a/mmdetection/configs/yolof/metafile.yml b/mmdetection/configs/yolof/metafile.yml new file mode 100644 index 00000000..b3b7b7f8 --- /dev/null +++ b/mmdetection/configs/yolof/metafile.yml @@ -0,0 +1,32 @@ +Collections: + - Name: YOLOF + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Momentum + - Weight Decay + Training Resources: 8x V100 GPUs + Architecture: + - Dilated Encoder + - ResNet + Paper: + URL: https://arxiv.org/abs/2103.09460 + Title: 'You Only Look One-level Feature' + README: configs/yolof/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.12.0/mmdet/models/detectors/yolof.py#L6 + Version: v2.12.0 + +Models: + - Name: yolof_r50_c5_8x8_1x_coco + In Collection: YOLOF + Config: configs/yolof/yolof_r50-c5_8xb8-1x_coco.py + Metadata: + Training Memory (GB): 8.3 + Epochs: 12 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 37.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolof/yolof_r50_c5_8x8_1x_coco/yolof_r50_c5_8x8_1x_coco_20210425_024427-8e864411.pth diff --git a/mmdetection/configs/yolof/yolof_r50-c5_8xb8-1x_coco.py b/mmdetection/configs/yolof/yolof_r50-c5_8xb8-1x_coco.py new file mode 100644 index 00000000..5ea228e3 --- /dev/null +++ b/mmdetection/configs/yolof/yolof_r50-c5_8xb8-1x_coco.py @@ -0,0 +1,116 @@ +_base_ = [ + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + type='YOLOF', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(3, ), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron/resnet50_caffe')), + neck=dict( + type='DilatedEncoder', + in_channels=2048, + out_channels=512, + block_mid_channels=128, + num_residual_blocks=4, + block_dilations=[2, 4, 6, 8]), + bbox_head=dict( + type='YOLOFHead', + num_classes=80, + in_channels=512, + reg_decoded_bbox=True, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[1, 2, 4, 8, 16], + strides=[32]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1., 1., 1., 1.], + add_ctr_clamp=True, + ctr_clamp=32), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=1.0)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='UniformAssigner', pos_ignore_thr=0.15, neg_ignore_thr=0.7), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100)) +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.12, momentum=0.9, weight_decay=0.0001), + paramwise_cfg=dict( + norm_decay_mult=0., custom_keys={'backbone': dict(lr_mult=1. / 3)})) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.00066667, + by_epoch=False, + begin=0, + end=1500), + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='RandomShift', prob=0.5, max_shift_px=32), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=8, num_workers=8, dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/yolof/yolof_r50-c5_8xb8-iter-1x_coco.py b/mmdetection/configs/yolof/yolof_r50-c5_8xb8-iter-1x_coco.py new file mode 100644 index 00000000..466a8200 --- /dev/null +++ b/mmdetection/configs/yolof/yolof_r50-c5_8xb8-iter-1x_coco.py @@ -0,0 +1,32 @@ +_base_ = './yolof_r50-c5_8xb8-1x_coco.py' + +# We implemented the iter-based config according to the source code. +# COCO dataset has 117266 images after filtering. We use 8 gpu and +# 8 batch size training, so 22500 is equivalent to +# 22500/(117266/(8x8))=12.3 epoch, 15000 is equivalent to 8.2 epoch, +# 20000 is equivalent to 10.9 epoch. Due to lr(0.12) is large, +# the iter-based and epoch-based setting have about 0.2 difference on +# the mAP evaluation value. + +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=22500, + val_interval=4500) + +# learning rate policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=22500, + by_epoch=False, + milestones=[15000, 20000], + gamma=0.1) +] +train_dataloader = dict(sampler=dict(type='InfiniteSampler')) +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=2500)) + +log_processor = dict(by_epoch=False) diff --git a/mmdetection/configs/yolox/README.md b/mmdetection/configs/yolox/README.md new file mode 100644 index 00000000..0cde1926 --- /dev/null +++ b/mmdetection/configs/yolox/README.md @@ -0,0 +1,39 @@ +# YOLOX + +> [YOLOX: Exceeding YOLO Series in 2021](https://arxiv.org/abs/2107.08430) + + + +## Abstract + +In this report, we present some experienced improvements to YOLO series, forming a new high-performance detector -- YOLOX. We switch the YOLO detector to an anchor-free manner and conduct other advanced detection techniques, i.e., a decoupled head and the leading label assignment strategy SimOTA to achieve state-of-the-art results across a large scale range of models: For YOLO-Nano with only 0.91M parameters and 1.08G FLOPs, we get 25.3% AP on COCO, surpassing NanoDet by 1.8% AP; for YOLOv3, one of the most widely used detectors in industry, we boost it to 47.3% AP on COCO, outperforming the current best practice by 3.0% AP; for YOLOX-L with roughly the same amount of parameters as YOLOv4-CSP, YOLOv5-L, we achieve 50.0% AP on COCO at a speed of 68.9 FPS on Tesla V100, exceeding YOLOv5-L by 1.8% AP. Further, we won the 1st Place on Streaming Perception Challenge (Workshop on Autonomous Driving at CVPR 2021) using a single YOLOX-L model. We hope this report can provide useful experience for developers and researchers in practical scenes, and we also provide deploy versions with ONNX, TensorRT, NCNN, and Openvino supported. + +
    + +
    + +## Results and Models + +| Backbone | size | Mem (GB) | box AP | Config | Download | +| :--------: | :--: | :------: | :----: | :--------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| YOLOX-tiny | 416 | 3.5 | 32.0 | [config](./yolox_tiny_8xb8-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_tiny_8x8_300e_coco/yolox_tiny_8x8_300e_coco_20211124_171234-b4047906.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_tiny_8x8_300e_coco/yolox_tiny_8x8_300e_coco_20211124_171234.log.json) | +| YOLOX-s | 640 | 7.6 | 40.5 | [config](./yolox_s_8xb8-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_s_8x8_300e_coco/yolox_s_8x8_300e_coco_20211121_095711-4592a793.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_s_8x8_300e_coco/yolox_s_8x8_300e_coco_20211121_095711.log.json) | +| YOLOX-l | 640 | 19.9 | 49.4 | [config](./yolox_l_8xb8-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_l_8x8_300e_coco/yolox_l_8x8_300e_coco_20211126_140236-d3bd2b23.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_l_8x8_300e_coco/yolox_l_8x8_300e_coco_20211126_140236.log.json) | +| YOLOX-x | 640 | 28.1 | 50.9 | [config](./yolox_x_8xb8-300e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_x_8x8_300e_coco/yolox_x_8x8_300e_coco_20211126_140254-1ef88d67.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_x_8x8_300e_coco/yolox_x_8x8_300e_coco_20211126_140254.log.json) | + +**Note**: + +1. The test score threshold is 0.001, and the box AP indicates the best AP. +2. Due to the need for pre-training weights, we cannot reproduce the performance of the `yolox-nano` model. Please refer to https://github.com/Megvii-BaseDetection/YOLOX/issues/674 for more information. +3. We also trained the model by the official release of YOLOX based on [Megvii-BaseDetection/YOLOX#735](https://github.com/Megvii-BaseDetection/YOLOX/issues/735) with commit ID [38c633](https://github.com/Megvii-BaseDetection/YOLOX/tree/38c633bf176462ee42b110c70e4ffe17b5753208). We found that the best AP of `YOLOX-tiny`, `YOLOX-s`, `YOLOX-l`, and `YOLOX-x` is 31.8, 40.3, 49.2, and 50.9, respectively. The performance is consistent with that of our re-implementation (see Table above) but still has a gap (0.3~0.8 AP) in comparison with the reported performance in their [README](https://github.com/Megvii-BaseDetection/YOLOX/blob/38c633bf176462ee42b110c70e4ffe17b5753208/README.md#benchmark). + +## Citation + +```latex +@article{yolox2021, + title={{YOLOX}: Exceeding YOLO Series in 2021}, + author={Ge, Zheng and Liu, Songtao and Wang, Feng and Li, Zeming and Sun, Jian}, + journal={arXiv preprint arXiv:2107.08430}, + year={2021} +} +``` diff --git a/mmdetection/configs/yolox/metafile.yml b/mmdetection/configs/yolox/metafile.yml new file mode 100644 index 00000000..2f64450e --- /dev/null +++ b/mmdetection/configs/yolox/metafile.yml @@ -0,0 +1,70 @@ +Collections: + - Name: YOLOX + Metadata: + Training Data: COCO + Training Techniques: + - SGD with Nesterov + - Weight Decay + - Cosine Annealing Lr Updater + Training Resources: 8x TITANXp GPUs + Architecture: + - CSPDarkNet + - PAFPN + Paper: + URL: https://arxiv.org/abs/2107.08430 + Title: 'YOLOX: Exceeding YOLO Series in 2021' + README: configs/yolox/README.md + Code: + URL: https://github.com/open-mmlab/mmdetection/blob/v2.15.1/mmdet/models/detectors/yolox.py#L6 + Version: v2.15.1 + + +Models: + - Name: yolox_s_8x8_300e_coco + In Collection: YOLOX + Config: configs/yolox/yolox_s_8xb8-300e_coco.py + Metadata: + Training Memory (GB): 7.6 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 40.5 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_s_8x8_300e_coco/yolox_s_8x8_300e_coco_20211121_095711-4592a793.pth + - Name: yolox_l_8x8_300e_coco + In Collection: YOLOX + Config: configs/yolox/yolox_l_8xb8-300e_coco.py + Metadata: + Training Memory (GB): 19.9 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 49.4 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_l_8x8_300e_coco/yolox_l_8x8_300e_coco_20211126_140236-d3bd2b23.pth + - Name: yolox_x_8x8_300e_coco + In Collection: YOLOX + Config: configs/yolox/yolox_x_8xb8-300e_coco.py + Metadata: + Training Memory (GB): 28.1 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 50.9 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_x_8x8_300e_coco/yolox_x_8x8_300e_coco_20211126_140254-1ef88d67.pth + - Name: yolox_tiny_8x8_300e_coco + In Collection: YOLOX + Config: configs/yolox/yolox_tiny_8xb8-300e_coco.py + Metadata: + Training Memory (GB): 3.5 + Epochs: 300 + Results: + - Task: Object Detection + Dataset: COCO + Metrics: + box AP: 32.0 + Weights: https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_tiny_8x8_300e_coco/yolox_tiny_8x8_300e_coco_20211124_171234-b4047906.pth diff --git a/mmdetection/configs/yolox/yolox_l_8xb8-300e_coco.py b/mmdetection/configs/yolox/yolox_l_8xb8-300e_coco.py new file mode 100644 index 00000000..2a4b287b --- /dev/null +++ b/mmdetection/configs/yolox/yolox_l_8xb8-300e_coco.py @@ -0,0 +1,8 @@ +_base_ = './yolox_s_8xb8-300e_coco.py' + +# model settings +model = dict( + backbone=dict(deepen_factor=1.0, widen_factor=1.0), + neck=dict( + in_channels=[256, 512, 1024], out_channels=256, num_csp_blocks=3), + bbox_head=dict(in_channels=256, feat_channels=256)) diff --git a/mmdetection/configs/yolox/yolox_m_8xb8-300e_coco.py b/mmdetection/configs/yolox/yolox_m_8xb8-300e_coco.py new file mode 100644 index 00000000..d82f9e98 --- /dev/null +++ b/mmdetection/configs/yolox/yolox_m_8xb8-300e_coco.py @@ -0,0 +1,8 @@ +_base_ = './yolox_s_8xb8-300e_coco.py' + +# model settings +model = dict( + backbone=dict(deepen_factor=0.67, widen_factor=0.75), + neck=dict(in_channels=[192, 384, 768], out_channels=192, num_csp_blocks=2), + bbox_head=dict(in_channels=192, feat_channels=192), +) diff --git a/mmdetection/configs/yolox/yolox_nano_8xb8-300e_coco.py b/mmdetection/configs/yolox/yolox_nano_8xb8-300e_coco.py new file mode 100644 index 00000000..3f7a1c5a --- /dev/null +++ b/mmdetection/configs/yolox/yolox_nano_8xb8-300e_coco.py @@ -0,0 +1,11 @@ +_base_ = './yolox_tiny_8xb8-300e_coco.py' + +# model settings +model = dict( + backbone=dict(deepen_factor=0.33, widen_factor=0.25, use_depthwise=True), + neck=dict( + in_channels=[64, 128, 256], + out_channels=64, + num_csp_blocks=1, + use_depthwise=True), + bbox_head=dict(in_channels=64, feat_channels=64, use_depthwise=True)) diff --git a/mmdetection/configs/yolox/yolox_s_8xb8-300e_coco.py b/mmdetection/configs/yolox/yolox_s_8xb8-300e_coco.py new file mode 100644 index 00000000..3e324eb5 --- /dev/null +++ b/mmdetection/configs/yolox/yolox_s_8xb8-300e_coco.py @@ -0,0 +1,250 @@ +_base_ = [ + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py', + './yolox_tta.py' +] + +img_scale = (640, 640) # width, height + +# model settings +model = dict( + type='YOLOX', + data_preprocessor=dict( + type='DetDataPreprocessor', + pad_size_divisor=32, + batch_augments=[ + dict( + type='BatchSyncRandomResize', + random_size_range=(480, 800), + size_divisor=32, + interval=10) + ]), + backbone=dict( + type='CSPDarknet', + deepen_factor=0.33, + widen_factor=0.5, + out_indices=(2, 3, 4), + use_depthwise=False, + spp_kernal_sizes=(5, 9, 13), + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish'), + ), + neck=dict( + type='YOLOXPAFPN', + in_channels=[128, 256, 512], + out_channels=128, + num_csp_blocks=1, + use_depthwise=False, + upsample_cfg=dict(scale_factor=2, mode='nearest'), + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish')), + bbox_head=dict( + type='YOLOXHead', + num_classes=80, + in_channels=128, + feat_channels=128, + stacked_convs=2, + strides=(8, 16, 32), + use_depthwise=False, + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish'), + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='sum', + loss_weight=1.0), + loss_bbox=dict( + type='IoULoss', + mode='square', + eps=1e-16, + reduction='sum', + loss_weight=5.0), + loss_obj=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='sum', + loss_weight=1.0), + loss_l1=dict(type='L1Loss', reduction='sum', loss_weight=1.0)), + train_cfg=dict(assigner=dict(type='SimOTAAssigner', center_radius=2.5)), + # In order to align the source code, the threshold of the val phase is + # 0.01, and the threshold of the test phase is 0.001. + test_cfg=dict(score_thr=0.01, nms=dict(type='nms', iou_threshold=0.65))) + +# dataset settings +data_root = 'data/coco/' +dataset_type = 'CocoDataset' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='Mosaic', img_scale=img_scale, pad_val=114.0), + dict( + type='RandomAffine', + scaling_ratio_range=(0.1, 2), + # img_scale is (width, height) + border=(-img_scale[0] // 2, -img_scale[1] // 2)), + dict( + type='MixUp', + img_scale=img_scale, + ratio_range=(0.8, 1.6), + pad_val=114.0), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + # According to the official implementation, multi-scale + # training is not considered here but in the + # 'mmdet/models/detectors/yolox.py'. + # Resize and Pad are for the last 15 epochs when Mosaic, + # RandomAffine, and MixUp are closed by YOLOXModeSwitchHook. + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict( + type='Pad', + pad_to_square=True, + # If the image is three-channel, the pad value needs + # to be set separately for each channel. + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1), keep_empty=False), + dict(type='PackDetInputs') +] + +train_dataset = dict( + # use MultiImageMixDataset wrapper to support mosaic and mixup + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + pipeline=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True) + ], + filter_cfg=dict(filter_empty_gt=False, min_size=32), + backend_args=backend_args), + pipeline=train_pipeline) + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict( + type='Pad', + pad_to_square=True, + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=8, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=train_dataset) +val_dataloader = dict( + batch_size=8, + num_workers=4, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + backend_args=backend_args) +test_evaluator = val_evaluator + +# training settings +max_epochs = 300 +num_last_epochs = 15 +interval = 10 + +train_cfg = dict(max_epochs=max_epochs, val_interval=interval) + +# optimizer +# default 8 gpu +base_lr = 0.01 +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + type='SGD', lr=base_lr, momentum=0.9, weight_decay=5e-4, + nesterov=True), + paramwise_cfg=dict(norm_decay_mult=0., bias_decay_mult=0.)) + +# learning rate +param_scheduler = [ + dict( + # use quadratic formula to warm up 5 epochs + # and lr is updated by iteration + # TODO: fix default scope in get function + type='mmdet.QuadraticWarmupLR', + by_epoch=True, + begin=0, + end=5, + convert_to_iter_based=True), + dict( + # use cosine lr from 5 to 285 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=5, + T_max=max_epochs - num_last_epochs, + end=max_epochs - num_last_epochs, + by_epoch=True, + convert_to_iter_based=True), + dict( + # use fixed lr during last 15 epochs + type='ConstantLR', + by_epoch=True, + factor=1, + begin=max_epochs - num_last_epochs, + end=max_epochs, + ) +] + +default_hooks = dict( + checkpoint=dict( + interval=interval, + max_keep_ckpts=3 # only keep latest 3 checkpoints + )) + +custom_hooks = [ + dict( + type='YOLOXModeSwitchHook', + num_last_epochs=num_last_epochs, + priority=48), + dict(type='SyncNormHook', priority=48), + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0001, + update_buffers=True, + priority=49) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/configs/yolox/yolox_tiny_8xb8-300e_coco.py b/mmdetection/configs/yolox/yolox_tiny_8xb8-300e_coco.py new file mode 100644 index 00000000..86f7e9a6 --- /dev/null +++ b/mmdetection/configs/yolox/yolox_tiny_8xb8-300e_coco.py @@ -0,0 +1,54 @@ +_base_ = './yolox_s_8xb8-300e_coco.py' + +# model settings +model = dict( + data_preprocessor=dict(batch_augments=[ + dict( + type='BatchSyncRandomResize', + random_size_range=(320, 640), + size_divisor=32, + interval=10) + ]), + backbone=dict(deepen_factor=0.33, widen_factor=0.375), + neck=dict(in_channels=[96, 192, 384], out_channels=96), + bbox_head=dict(in_channels=96, feat_channels=96)) + +img_scale = (640, 640) # width, height + +train_pipeline = [ + dict(type='Mosaic', img_scale=img_scale, pad_val=114.0), + dict( + type='RandomAffine', + scaling_ratio_range=(0.5, 1.5), + # img_scale is (width, height) + border=(-img_scale[0] // 2, -img_scale[1] // 2)), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + # Resize and Pad are for the last 15 epochs when Mosaic and + # RandomAffine are closed by YOLOXModeSwitchHook. + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict( + type='Pad', + pad_to_square=True, + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1), keep_empty=False), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(416, 416), keep_ratio=True), + dict( + type='Pad', + pad_to_square=True, + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/configs/yolox/yolox_tta.py b/mmdetection/configs/yolox/yolox_tta.py new file mode 100644 index 00000000..e65244be --- /dev/null +++ b/mmdetection/configs/yolox/yolox_tta.py @@ -0,0 +1,36 @@ +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict(type='nms', iou_threshold=0.65), max_per_img=100)) + +img_scales = [(640, 640), (320, 320), (960, 960)] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale=s, keep_ratio=True) + for s in img_scales + ], + [ + # ``RandomFlip`` must be placed before ``Pad``, otherwise + # bounding box coordinates after flipping cannot be + # recovered correctly. + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], + [ + dict( + type='Pad', + pad_to_square=True, + pad_val=dict(img=(114.0, 114.0, 114.0))), + ], + [dict(type='LoadAnnotations', with_bbox=True)], + [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction')) + ] + ]) +] diff --git a/mmdetection/configs/yolox/yolox_x_8xb8-300e_coco.py b/mmdetection/configs/yolox/yolox_x_8xb8-300e_coco.py new file mode 100644 index 00000000..34828e03 --- /dev/null +++ b/mmdetection/configs/yolox/yolox_x_8xb8-300e_coco.py @@ -0,0 +1,8 @@ +_base_ = './yolox_s_8xb8-300e_coco.py' + +# model settings +model = dict( + backbone=dict(deepen_factor=1.33, widen_factor=1.25), + neck=dict( + in_channels=[320, 640, 1280], out_channels=320, num_csp_blocks=4), + bbox_head=dict(in_channels=320, feat_channels=320)) diff --git a/mmdetection/dataset-index.yml b/mmdetection/dataset-index.yml new file mode 100644 index 00000000..116412e1 --- /dev/null +++ b/mmdetection/dataset-index.yml @@ -0,0 +1,18 @@ +openxlab: true +voc2007: + dataset: OpenDataLab/PASCAL_VOC2007 + download_root: data + data_root: data + script: tools/dataset_converters/scripts/preprocess_voc2007.sh + +voc2012: + dataset: OpenDataLab/PASCAL_VOC2012 + download_root: data + data_root: data + script: tools/dataset_converters/scripts/preprocess_voc2012.sh + +coco2017: + dataset: OpenDataLab/COCO_2017 + download_root: data + data_root: data/coco + script: tools/dataset_converters/scripts/preprocess_coco2017.sh diff --git a/mmdetection/demo/MMDet_InstanceSeg_Tutorial.ipynb b/mmdetection/demo/MMDet_InstanceSeg_Tutorial.ipynb new file mode 100644 index 00000000..4b63ba34 --- /dev/null +++ b/mmdetection/demo/MMDet_InstanceSeg_Tutorial.ipynb @@ -0,0 +1,2167 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "aGYwt_UjIrqp" + }, + "source": [ + "# Instance Segmentation\n", + "\n", + "In this tutorial, you will learn:\n", + "- the basic structure of Mask R-CNN.\n", + "- to perform inference with a MMDetection detector.\n", + "- to train a new instance segmentation model with a new dataset.\n", + "\n", + "Let's start!\n", + "\n", + "\"Open" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cCk6uTQrdUUn" + }, + "source": [ + "If you are running the tutorial files on the colab platform or a new virtual environment, please run the following code first to configure the runtime environment.\n", + "```python\n", + "!pip install -U openmim\n", + "!mim install \"mmengine>=0.7.0\"\n", + "!mim install \"mmcv>=2.0.0rc4\"\n", + "\n", + "# Install mmdetection\n", + "!rm -rf mmdetection\n", + "!git clone https://github.com/open-mmlab/mmdetection.git\n", + "%cd mmdetection\n", + "\n", + "!pip install -e .\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "6hD0mmMixT0p", + "outputId": "221dad3c-5ef8-4094-e07e-289f333f7bb9" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "torch version: 2.0.1+cu118 cuda: True\n", + "mmdetection: 3.1.0\n", + "mmcv: 2.0.1\n", + "mmengine: 0.8.4\n" + ] + } + ], + "source": [ + "# Check Pytorch installation\n", + "import torch, torchvision\n", + "print(\"torch version:\",torch.__version__, \"cuda:\",torch.cuda.is_available())\n", + "\n", + "# Check MMDetection installation\n", + "import mmdet\n", + "print(\"mmdetection:\",mmdet.__version__)\n", + "\n", + "# Check mmcv installation\n", + "import mmcv\n", + "print(\"mmcv:\",mmcv.__version__)\n", + "\n", + "# Check mmengine installation\n", + "import mmengine\n", + "print(\"mmengine:\",mmengine.__version__)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gi9zw03oM4CH" + }, + "source": [ + "## Perform Inference with An MMDetection Detector" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3pFYLerc0we1" + }, + "source": [ + "### A two-stage detector\n", + "\n", + "In this tutorial, we use Mask R-CNN, a simple two-stage detector as an example.\n", + "\n", + "The high-level architecture of Mask R-CNN is shown in the following picture. More details can be found in the [paper](https://arxiv.org/abs/1703.06870).\n", + "\n", + "\"mask\n", + "\n", + "Mask R-CNN adds a mask branch based on the original Faster R-CNN. It also uses RoIAlign, a more precise version of RoIPooling for RoI feature extraction to improve the performance.\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "sWI-nX5yRYYQ", + "outputId": "fd91e337-27cb-492c-a948-98adcbcfca27" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "processing mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco...\n", + "\u001b[2Kdownloading \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m169.6/169.6 MiB\u001b[0m \u001b[31m9.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[32mSuccessfully downloaded mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth to /content/mmdetection/checkpoints\u001b[0m\n", + "\u001b[32mSuccessfully dumped mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco.py to /content/mmdetection/checkpoints\u001b[0m\n" + ] + } + ], + "source": [ + "!mim download mmdet --config mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco --dest ./checkpoints" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8M5KUnX7Np3h", + "outputId": "71de79c0-9f7e-4cae-f810-5c0a20fe9be8" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loads checkpoint by local backend from path: checkpoints/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth\n" + ] + } + ], + "source": [ + "import mmcv\n", + "import mmengine\n", + "from mmdet.apis import init_detector, inference_detector\n", + "from mmdet.utils import register_all_modules\n", + "# Choose to use a config and initialize the detector\n", + "config_file = 'configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-3x_coco.py'\n", + "# Setup a checkpoint file to load\n", + "checkpoint_file = 'checkpoints/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth'\n", + "\n", + "# register all modules in mmdet into the registries\n", + "register_all_modules()\n", + "\n", + "# build the model from a config file and a checkpoint file\n", + "model = init_detector(config_file, checkpoint_file, device='cuda:0') # or device='cuda:0'\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "pVqDQAOiKkJK" + }, + "source": [ + "From the printed model, we will find that the model does consist of the components that we described earlier. It uses ResNet as its CNN backbone, and has a RPN head and RoI Head.\n", + "The RoI Head includes box head and mask head. In addition, the model has a neural network module, named neck, directly after the CNN backbone. It is a [feature pyramid network (FPN)](https://arxiv.org/abs/1612.03144) for enhancing the multi-scale features.\n", + "\n", + "\n", + "### Inference with the detector\n", + "\n", + "The model is successfully created and loaded, let's see how good it is. We use the high-level API `inference_detector` implemented in the MMDetection. This API is created to ease the inference process. The details of the codes can be found [here](https://github.com/open-mmlab/mmdetection/blob/master/mmdet/apis/inference.py#L15)." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Wi6DRpsQPEmV", + "outputId": "42a9dd39-edcb-49f1-e318-a3cd77f89eee" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " ignored_instances: \n", + " pred_instances: \n", + ") at 0x79a3c999fc10>\n" + ] + } + ], + "source": [ + "# Use the detector to do inference\n", + "image = mmcv.imread('demo/demo.jpg',channel_order='rgb')\n", + "result = inference_detector(model, image)\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4pFVhKeQRYYS" + }, + "source": [ + "### Let's plot the result" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "YinmJV1dRYYT", + "outputId": "e6c9059f-55b3-481b-edef-b21befcbcf2e" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/mmengine/visualization/visualizer.py:196: UserWarning: Failed to add , please provide the `save_dir` argument.\n", + " warnings.warn(f'Failed to add {vis_backend.__class__}, '\n" + ] + } + ], + "source": [ + "from mmdet.registry import VISUALIZERS\n", + "# init visualizer(run the block only once in jupyter notebook)\n", + "visualizer = VISUALIZERS.build(model.cfg.visualizer)\n", + "# the dataset_meta is loaded from the checkpoint and\n", + "# then pass to the model in init_detector\n", + "visualizer.dataset_meta = model.dataset_meta" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 464 + }, + "id": "z6qT6pG1RYYT", + "outputId": "089b652b-061f-480d-f9de-ffa06b7d385a" + }, + "outputs": [ + { + "data": { + "image/png": "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q6++wne/+z323ntvVq5cievGrhnJZIJisRxnt7QYkRYC/CAkjJgU4O6angZoa2ujt7cXpRQ9PT1s2xYHlLG/tV579uqBczxYc7kcf/rTn8jn83R3d3PFFVfw17/+lUWLFvHBDx7PD374Q1zHJZlMMXNmDzuvQf+qve+A0vVj3kfV9WJF7r9ijk4JGkOE8OPdazSBRNY5lMcc/UE+/vFTCcKAe+/5K48++igf/ODRHH98rCyaN28ev/jFz3nmmWcbnTJr1gzOP//8hqXNxRdfzMjIMFL2kkzGSryqU8I0NQJfUiq6fPr0kzjuhP14+vF3KJZ3cMgH9kUa7RTLHmYiw6lfWsCmt/L85aZ/kExZfPq042mePsyyp2dzxx8m4OXOPVro3tfkoI8sZGRsnGoJypXY7xERIyKu40AkiHw/VmULnVALiMIA3QLHd5BYVDwH29BJJHQKbgkV1wxAjyRWwkATsepPavECZBgGUkqSySSO4+AGcSpYKIiiMpomKRaLRKZNEAowFIYeYZsa6ebplMoO0tcYGagyMtpPNl1gW98YTa0ZnJKDUgojbcQTp4q96aKYh4Ch23hOMabehRpSajHHR4Xouo20JH++9R/kx0+ipzvH9HktDIwWqQoPU9fxRYiyNEpBlcj10XULHx8vdAl8EHVTeh0MFH4ULzi6YcaorKdQmkDqEEQhbuQSYjfuS11+0t09jTVr3qm9FidxjzrqaI499liEEHR2dpJMpDAMg61bt9LT04Oq2ygArhdiWxpKwejoGB/60L9hmibnnnsumUyGKIrYvn07f//7gySTCa699lreeustmpqaGB8f59e//jXNzc1cdtlljbT4ZPuab/7nNzju2GP52/33M62rk7HREb7xn/9Jb28fv7nictatXc9rr71OS0uOXDbLeH58yqOVLxTI54v0Dwxz9NGLefzx5xtQvtAFaHpsOO164Pu18SEII4VmaGimQVBfzIVE0zVMXSMIPVQUB3RC1hceP06TGyamGdudmMk0YajYPlziinse4K7nX+aTRx7G0fvFu11N08AOufDCxVN2+0OjJS659HdTvsuNt97FjbfeVb+BIOD6m/5CHKjE5y694spdppdb/nI3t/zlbmKysQAl+Ou9j/HXex+DRr3tGGle+ss/YtkWba1JHvvHw3F6vTPXeK+kYWLq2k7ZkKlzWzJpkErp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", + "text/plain": [ + "
    " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# show the results\n", + "visualizer.add_datasample(\n", + " 'result',\n", + " image,\n", + " data_sample=result,\n", + " draw_gt = None,\n", + " wait_time=0,\n", + ")\n", + "visualizer.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7GrWIJywLV-V" + }, + "source": [ + "## Train a Detector on A Customized Dataset\n", + "\n", + "To train a new detector, there are usually three things to do:\n", + "1. Support a new dataset\n", + "2. Modify the config\n", + "3. Train a new detector\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "E73y5Lru-wBx" + }, + "source": [ + "### Support a new dataset\n", + "\n", + "There are three ways to support a new dataset in MMDetection:\n", + " 1. Reorganize the dataset into a COCO format\n", + " 2. Reorganize the dataset into a middle format\n", + " 3. Implement a new dataset\n", + "\n", + "We recommend the first two methods, as they are usually easier than the third.\n", + "\n", + "In this tutorial, we give an example that converts the data into COCO format because MMDetection **only support evaluating mask AP of dataset in COCO format for now**. Other methods and more advanced usages can be found in the [doc](https://mmdetection.readthedocs.io/en/latest/advanced_guides/customize_dataset.html).\n", + "\n", + "First, let's download the [the balloon dataset](https://github.com/matterport/Mask_RCNN/tree/master/samples/balloon)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "rHnw5Q_nARXq" + }, + "outputs": [], + "source": [ + "# download and unzip the data\n", + "!wget -c https://github.com/matterport/Mask_RCNN/releases/download/v2.1/balloon_dataset.zip" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ucSfn1U_RYYW" + }, + "outputs": [], + "source": [ + "!unzip balloon_dataset.zip -d ./ballondatasets/" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ro6JhfBVRYYX" + }, + "source": [ + "# Check the directory structure of the tiny data\n", + "\n", + "# Install tree first in your terminal(linux)\n", + "sudo apt-get -q install tree\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Wuwxw1oZRtVZ", + "outputId": "2e472cfa-2e2f-41ea-ddec-5a9d49fe71cf" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/bin/bash: line 1: tree: command not found\n" + ] + } + ], + "source": [ + "!tree ballondatasets" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 836 + }, + "id": "YnQQqzOWzE91", + "outputId": "ff7d3804-638c-461f-aef6-8c496a4b69c8" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Let's take a look at the dataset image\n", + "import mmcv\n", + "import matplotlib.pyplot as plt\n", + "\n", + "img = mmcv.imread('ballondatasets/balloon/train/10464445726_6f1e3bbe6a_k.jpg')\n", + "plt.figure(figsize=(15, 10))\n", + "plt.imshow(mmcv.bgr2rgb(img))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PMZvtSIl71qi" + }, + "source": [ + "After downloading the data, we need to implement a function to convert the annotation format into the COCO format. Then we can use implemented `COCODataset` to load the data and perform training and evaluation.\n", + "Let's take a look at the annotation json file.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "id": "n7rwalnPd6e1" + }, + "outputs": [], + "source": [ + "# Check the label of a single image\n", + "import mmengine\n", + "\n", + "annotation = mmengine.load('./ballondatasets/balloon/train/via_region_data.json')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "keLW7uqJM54Y", + "outputId": "8bdf087e-5ec0-4f8a-ee1d-5692986ac87d" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'fileref': '',\n", + " 'size': 1115004,\n", + " 'filename': '34020010494_e5cb88e1c4_k.jpg',\n", + " 'base64_img_data': '',\n", + " 'file_attributes': {},\n", + " 'regions': {'0': {'shape_attributes': {'name': 'polygon',\n", + " 'all_points_x': [1020,\n", + " 1000,\n", + " 994,\n", + " 1003,\n", + " 1023,\n", + " 1050,\n", + " 1089,\n", + " 1134,\n", + " 1190,\n", + " 1265,\n", + " 1321,\n", + " 1361,\n", + " 1403,\n", + " 1428,\n", + " 1442,\n", + " 1445,\n", + " 1441,\n", + " 1427,\n", + " 1400,\n", + " 1361,\n", + " 1316,\n", + " 1269,\n", + " 1228,\n", + " 1198,\n", + " 1207,\n", + " 1210,\n", + " 1190,\n", + " 1177,\n", + " 1172,\n", + " 1174,\n", + " 1170,\n", + " 1153,\n", + " 1127,\n", + " 1104,\n", + " 1061,\n", + " 1032,\n", + " 1020],\n", + " 'all_points_y': [963,\n", + " 899,\n", + " 841,\n", + " 787,\n", + " 738,\n", + " 700,\n", + " 663,\n", + " 638,\n", + " 621,\n", + " 619,\n", + " 643,\n", + " 672,\n", + " 720,\n", + " 765,\n", + " 800,\n", + " 860,\n", + " 896,\n", + " 942,\n", + " 990,\n", + " 1035,\n", + " 1079,\n", + " 1112,\n", + " 1129,\n", + " 1134,\n", + " 1144,\n", + " 1153,\n", + " 1166,\n", + " 1166,\n", + " 1150,\n", + " 1136,\n", + " 1129,\n", + " 1122,\n", + " 1112,\n", + " 1084,\n", + " 1037,\n", + " 989,\n", + " 963]},\n", + " 'region_attributes': {}}}}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The annotation is a dict, and its values looks like the following\n", + "annotation['34020010494_e5cb88e1c4_k.jpg1115004']" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QA1pFg-FeO3l" + }, + "source": [ + "According to the above observation, each single image has a corresponding annotation containing keys `filename` and `regions` that are necessary for training.\n", + "We need to read annotations of each image and convert them into COCO format as below:\n", + "\n", + "```python\n", + "{\n", + " \"images\": [image],\n", + " \"annotations\": [annotation],\n", + " \"categories\": [category]\n", + "}\n", + "\n", + "\n", + "image = {\n", + " \"id\": int,\n", + " \"width\": int,\n", + " \"height\": int,\n", + " \"file_name\": str,\n", + "}\n", + "\n", + "annotation = {\n", + " \"id\": int,\n", + " \"image_id\": int,\n", + " \"category_id\": int,\n", + " \"segmentation\": RLE or [polygon],\n", + " \"area\": float,\n", + " \"bbox\": [x,y,width,height],\n", + " \"iscrowd\": 0 or 1,\n", + "}\n", + "\n", + "categories = [{\n", + " \"id\": int,\n", + " \"name\": str,\n", + " \"supercategory\": str,\n", + "}]\n", + "```\n", + "**Note**: We only list the necessary keys for training, as shown above. For a full COCO format, please see [here](https://cocodataset.org/#format-data)." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "id": "GdSaB2ad0EdX" + }, + "outputs": [], + "source": [ + "import os.path as osp\n", + "\n", + "def convert_balloon_to_coco(ann_file, out_file, image_prefix):\n", + " data_infos = mmengine.load(ann_file)\n", + "\n", + " annotations = []\n", + " images = []\n", + " obj_count = 0\n", + " for idx, v in enumerate(mmengine.track_iter_progress(list(data_infos.values()))):\n", + " filename = v['filename']\n", + " img_path = osp.join(image_prefix, filename)\n", + " height, width = mmcv.imread(img_path).shape[:2]\n", + "\n", + " images.append(dict(\n", + " id=idx,\n", + " file_name=filename,\n", + " height=height,\n", + " width=width))\n", + "\n", + " bboxes = []\n", + " labels = []\n", + " masks = []\n", + " for _, obj in v['regions'].items():\n", + " assert not obj['region_attributes']\n", + " obj = obj['shape_attributes']\n", + " px = obj['all_points_x']\n", + " py = obj['all_points_y']\n", + " poly = [(x + 0.5, y + 0.5) for x, y in zip(px, py)]\n", + " poly = [p for x in poly for p in x]\n", + "\n", + " x_min, y_min, x_max, y_max = (\n", + " min(px), min(py), max(px), max(py))\n", + "\n", + "\n", + " data_anno = dict(\n", + " image_id=idx,\n", + " id=obj_count,\n", + " category_id=0,\n", + " bbox=[x_min, y_min, x_max - x_min, y_max - y_min],\n", + " area=(x_max - x_min) * (y_max - y_min),\n", + " segmentation=[poly],\n", + " iscrowd=0)\n", + " annotations.append(data_anno)\n", + " obj_count += 1\n", + "\n", + " coco_format_json = dict(\n", + " images=images,\n", + " annotations=annotations,\n", + " categories=[{'id':0, 'name': 'balloon'}])\n", + " mmengine.dump(coco_format_json, out_file)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "G3xV5ktqlpFu", + "outputId": "2d97137b-34e6-42e5-c8d6-0a4fe7d2c7cf" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 61/61, 19.7 task/s, elapsed: 3s, ETA: 0s\n", + "[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 13/13, 20.1 task/s, elapsed: 1s, ETA: 0s\n" + ] + } + ], + "source": [ + "convert_balloon_to_coco(\n", + " './ballondatasets/balloon/train/via_region_data.json',\n", + " './ballondatasets/balloon/train/annotation_coco.json',\n", + " './ballondatasets/balloon/train/')\n", + "convert_balloon_to_coco(\n", + " './ballondatasets/balloon/val/via_region_data.json',\n", + " './ballondatasets/balloon/val/annotation_coco.json',\n", + " './ballondatasets/balloon/val/')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "h85AtunjRvx4" + }, + "source": [ + "Checking the label corresponding to the instance split ID after the data format conversion is complete" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "zaYkWbxORwZq", + "outputId": "02ad1ff6-f138-49af-b733-1d23c51557f5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "loading annotations into memory...\n", + "Done (t=0.01s)\n", + "creating index...\n", + "index created!\n", + "Category ID: 0, Category Name: balloon\n" + ] + } + ], + "source": [ + "from pycocotools.coco import COCO\n", + "\n", + "# Path to load the COCO annotation file\n", + "annotation_file = './ballondatasets/balloon/train/annotation_coco.json'\n", + "\n", + "# Initialise the COCO object\n", + "coco = COCO(annotation_file)\n", + "\n", + "# Get all category tags and corresponding category IDs\n", + "categories = coco.loadCats(coco.getCatIds())\n", + "category_id_to_name = {cat['id']: cat['name'] for cat in categories}\n", + "\n", + "# Print all category IDs and corresponding category names\n", + "for category_id, category_name in category_id_to_name.items():\n", + " print(f\"Category ID: {category_id}, Category Name: {category_name}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PwqJOpBe-bMj" + }, + "source": [ + "### Modify the config\n", + "\n", + "In the next step, we need to modify the config for the training.\n", + "To accelerate the process, we finetune a detector using a pre-trained detector." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "id": "hamZrlnH-YDD" + }, + "outputs": [], + "source": [ + "from mmengine import Config\n", + "cfg = Config.fromfile('./configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HntziLGq-92Z" + }, + "source": [ + "Given a config that trains a Mask R-CNN on COCO dataset, we need to modify some values to use it for training on the balloon dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "id": "pUbwD8uV0PR8" + }, + "outputs": [], + "source": [ + "from mmengine.runner import set_random_seed\n", + "\n", + "# Modify dataset classes and color\n", + "cfg.metainfo = {\n", + " 'classes': ('balloon', ),\n", + " 'palette': [\n", + " (220, 20, 60),\n", + " ]\n", + "}\n", + "\n", + "# Modify dataset type and path\n", + "cfg.data_root = './ballondatasets/balloon'\n", + "\n", + "cfg.train_dataloader.dataset.ann_file = 'train/annotation_coco.json'\n", + "cfg.train_dataloader.dataset.data_root = cfg.data_root\n", + "cfg.train_dataloader.dataset.data_prefix.img = 'train/'\n", + "cfg.train_dataloader.dataset.metainfo = cfg.metainfo\n", + "\n", + "cfg.val_dataloader.dataset.ann_file = 'val/annotation_coco.json'\n", + "cfg.val_dataloader.dataset.data_root = cfg.data_root\n", + "cfg.val_dataloader.dataset.data_prefix.img = 'val/'\n", + "cfg.val_dataloader.dataset.metainfo = cfg.metainfo\n", + "\n", + "cfg.test_dataloader = cfg.val_dataloader\n", + "\n", + "# Modify metric config\n", + "cfg.val_evaluator.ann_file = cfg.data_root+'/'+'val/annotation_coco.json'\n", + "cfg.test_evaluator = cfg.val_evaluator\n", + "\n", + "# Modify num classes of the model in box head and mask head\n", + "cfg.model.roi_head.bbox_head.num_classes = 1\n", + "cfg.model.roi_head.mask_head.num_classes = 1\n", + "\n", + "# We can still the pre-trained Mask RCNN model to obtain a higher performance\n", + "cfg.load_from = 'checkpoints/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth'\n", + "\n", + "# Set up working dir to save files and logs.\n", + "cfg.work_dir = './tutorial_exps'\n", + "\n", + "\n", + "# We can set the evaluation interval to reduce the evaluation times\n", + "cfg.train_cfg.val_interval = 3\n", + "# We can set the checkpoint saving interval to reduce the storage cost\n", + "cfg.default_hooks.checkpoint.interval = 3\n", + "\n", + "# The original learning rate (LR) is set for 8-GPU training.\n", + "# We divide it by 8 since we only use one GPU.\n", + "cfg.optim_wrapper.optimizer.lr = 0.02 / 8\n", + "cfg.default_hooks.logger.interval = 10\n", + "\n", + "\n", + "# Set seed thus the results are more reproducible\n", + "# cfg.seed = 0\n", + "set_random_seed(0, deterministic=False)\n", + "\n", + "# We can also use tensorboard to log the training process\n", + "cfg.visualizer.vis_backends.append({\"type\":'TensorboardVisBackend'})\n", + "\n", + "#------------------------------------------------------\n", + "config=f'./configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon.py'\n", + "with open(config, 'w') as f:\n", + " f.write(cfg.pretty_text)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "111W_oZV_3wa" + }, + "source": [ + "### Train a new detector\n", + "\n", + "Finally, lets initialize the dataset and detector, then train a new detector!" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "JiqDnPdAMGyg", + "outputId": "0de25679-3541-488e-eceb-5b5400f92745" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "08/15 04:31:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - \n", + "------------------------------------------------------------\n", + "System environment:\n", + " sys.platform: linux\n", + " Python: 3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0]\n", + " CUDA available: True\n", + " numpy_random_seed: 1186080067\n", + " GPU 0: Tesla T4\n", + " CUDA_HOME: /usr/local/cuda\n", + " NVCC: Cuda compilation tools, release 11.8, V11.8.89\n", + " GCC: x86_64-linux-gnu-gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0\n", + " PyTorch: 2.0.1+cu118\n", + " PyTorch compiling details: PyTorch built with:\n", + " - GCC 9.3\n", + " - C++ Version: 201703\n", + " - Intel(R) oneAPI Math Kernel Library Version 2022.2-Product Build 20220804 for Intel(R) 64 architecture applications\n", + " - Intel(R) MKL-DNN v2.7.3 (Git Hash 6dbeffbae1f23cbbeae17adb7b5b13f1f37c080e)\n", + " - OpenMP 201511 (a.k.a. OpenMP 4.5)\n", + " - LAPACK is enabled (usually provided by MKL)\n", + " - NNPACK is enabled\n", + " - CPU capability usage: AVX2\n", + " - CUDA Runtime 11.8\n", + " - NVCC architecture flags: -gencode;arch=compute_37,code=sm_37;-gencode;arch=compute_50,code=sm_50;-gencode;arch=compute_60,code=sm_60;-gencode;arch=compute_70,code=sm_70;-gencode;arch=compute_75,code=sm_75;-gencode;arch=compute_80,code=sm_80;-gencode;arch=compute_86,code=sm_86;-gencode;arch=compute_90,code=sm_90\n", + " - CuDNN 8.7\n", + " - Magma 2.6.1\n", + " - Build settings: BLAS_INFO=mkl, BUILD_TYPE=Release, CUDA_VERSION=11.8, CUDNN_VERSION=8.7.0, CXX_COMPILER=/opt/rh/devtoolset-9/root/usr/bin/c++, CXX_FLAGS= -D_GLIBCXX_USE_CXX11_ABI=0 -fabi-version=11 -Wno-deprecated -fvisibility-inlines-hidden -DUSE_PTHREADPOOL -DNDEBUG -DUSE_KINETO -DLIBKINETO_NOROCTRACER -DUSE_FBGEMM -DUSE_QNNPACK -DUSE_PYTORCH_QNNPACK -DUSE_XNNPACK -DSYMBOLICATE_MOBILE_DEBUG_HANDLE -O2 -fPIC -Wall -Wextra -Werror=return-type -Werror=non-virtual-dtor -Werror=bool-operation -Wnarrowing -Wno-missing-field-initializers -Wno-type-limits -Wno-array-bounds -Wno-unknown-pragmas -Wunused-local-typedefs -Wno-unused-parameter -Wno-unused-function -Wno-unused-result -Wno-strict-overflow -Wno-strict-aliasing -Wno-error=deprecated-declarations -Wno-stringop-overflow -Wno-psabi -Wno-error=pedantic -Wno-error=redundant-decls -Wno-error=old-style-cast -fdiagnostics-color=always -faligned-new -Wno-unused-but-set-variable -Wno-maybe-uninitialized -fno-math-errno -fno-trapping-math -Werror=format -Werror=cast-function-type -Wno-stringop-overflow, LAPACK_INFO=mkl, PERF_WITH_AVX=1, PERF_WITH_AVX2=1, PERF_WITH_AVX512=1, TORCH_DISABLE_GPU_ASSERTS=ON, TORCH_VERSION=2.0.1, USE_CUDA=ON, USE_CUDNN=ON, USE_EXCEPTION_PTR=1, USE_GFLAGS=OFF, USE_GLOG=OFF, USE_MKL=ON, USE_MKLDNN=ON, USE_MPI=OFF, USE_NCCL=1, USE_NNPACK=ON, USE_OPENMP=ON, USE_ROCM=OFF, \n", + "\n", + " TorchVision: 0.15.2+cu118\n", + " OpenCV: 4.8.0\n", + " MMEngine: 0.8.4\n", + "\n", + "Runtime environment:\n", + " cudnn_benchmark: False\n", + " dist_cfg: {'backend': 'nccl'}\n", + " mp_cfg: {'mp_start_method': 'fork', 'opencv_num_threads': 0}\n", + " seed: 1186080067\n", + " Distributed launcher: none\n", + " Distributed training: False\n", + " GPU number: 1\n", + "------------------------------------------------------------\n", + "\n", + "08/15 04:31:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Config:\n", + "auto_scale_lr = dict(base_batch_size=16, enable=False)\n", + "backend_args = None\n", + "data_root = './ballondatasets/balloon'\n", + "dataset_type = 'CocoDataset'\n", + "default_hooks = dict(\n", + " checkpoint=dict(interval=3, type='CheckpointHook'),\n", + " logger=dict(interval=10, type='LoggerHook'),\n", + " param_scheduler=dict(type='ParamSchedulerHook'),\n", + " sampler_seed=dict(type='DistSamplerSeedHook'),\n", + " timer=dict(type='IterTimerHook'),\n", + " visualization=dict(type='DetVisualizationHook'))\n", + "default_scope = 'mmdet'\n", + "env_cfg = dict(\n", + " cudnn_benchmark=False,\n", + " dist_cfg=dict(backend='nccl'),\n", + " mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0))\n", + "launcher = 'none'\n", + "load_from = 'checkpoints/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth'\n", + "log_level = 'INFO'\n", + "log_processor = dict(by_epoch=True, type='LogProcessor', window_size=50)\n", + "metainfo = dict(\n", + " classes=('balloon', ), palette=[\n", + " (\n", + " 220,\n", + " 20,\n", + " 60,\n", + " ),\n", + " ])\n", + "model = dict(\n", + " backbone=dict(\n", + " depth=50,\n", + " frozen_stages=1,\n", + " init_cfg=dict(\n", + " checkpoint='open-mmlab://detectron2/resnet50_caffe',\n", + " type='Pretrained'),\n", + " norm_cfg=dict(requires_grad=False, type='BN'),\n", + " norm_eval=True,\n", + " num_stages=4,\n", + " out_indices=(\n", + " 0,\n", + " 1,\n", + " 2,\n", + " 3,\n", + " ),\n", + " style='caffe',\n", + " type='ResNet'),\n", + " data_preprocessor=dict(\n", + " bgr_to_rgb=False,\n", + " mean=[\n", + " 103.53,\n", + " 116.28,\n", + " 123.675,\n", + " ],\n", + " pad_mask=True,\n", + " pad_size_divisor=32,\n", + " std=[\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " ],\n", + " type='DetDataPreprocessor'),\n", + " neck=dict(\n", + " in_channels=[\n", + " 256,\n", + " 512,\n", + " 1024,\n", + " 2048,\n", + " ],\n", + " num_outs=5,\n", + " out_channels=256,\n", + " type='FPN'),\n", + " roi_head=dict(\n", + " bbox_head=dict(\n", + " bbox_coder=dict(\n", + " target_means=[\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " ],\n", + " target_stds=[\n", + " 0.1,\n", + " 0.1,\n", + " 0.2,\n", + " 0.2,\n", + " ],\n", + " type='DeltaXYWHBBoxCoder'),\n", + " fc_out_channels=1024,\n", + " in_channels=256,\n", + " loss_bbox=dict(loss_weight=1.0, type='L1Loss'),\n", + " loss_cls=dict(\n", + " loss_weight=1.0, type='CrossEntropyLoss', use_sigmoid=False),\n", + " num_classes=1,\n", + " reg_class_agnostic=False,\n", + " roi_feat_size=7,\n", + " type='Shared2FCBBoxHead'),\n", + " bbox_roi_extractor=dict(\n", + " featmap_strides=[\n", + " 4,\n", + " 8,\n", + " 16,\n", + " 32,\n", + " ],\n", + " out_channels=256,\n", + " roi_layer=dict(output_size=7, sampling_ratio=0, type='RoIAlign'),\n", + " type='SingleRoIExtractor'),\n", + " mask_head=dict(\n", + " conv_out_channels=256,\n", + " in_channels=256,\n", + " loss_mask=dict(\n", + " loss_weight=1.0, type='CrossEntropyLoss', use_mask=True),\n", + " num_classes=1,\n", + " num_convs=4,\n", + " type='FCNMaskHead'),\n", + " mask_roi_extractor=dict(\n", + " featmap_strides=[\n", + " 4,\n", + " 8,\n", + " 16,\n", + " 32,\n", + " ],\n", + " out_channels=256,\n", + " roi_layer=dict(output_size=14, sampling_ratio=0, type='RoIAlign'),\n", + " type='SingleRoIExtractor'),\n", + " type='StandardRoIHead'),\n", + " rpn_head=dict(\n", + " anchor_generator=dict(\n", + " ratios=[\n", + " 0.5,\n", + " 1.0,\n", + " 2.0,\n", + " ],\n", + " scales=[\n", + " 8,\n", + " ],\n", + " strides=[\n", + " 4,\n", + " 8,\n", + " 16,\n", + " 32,\n", + " 64,\n", + " ],\n", + " type='AnchorGenerator'),\n", + " bbox_coder=dict(\n", + " target_means=[\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " ],\n", + " target_stds=[\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " ],\n", + " type='DeltaXYWHBBoxCoder'),\n", + " feat_channels=256,\n", + " in_channels=256,\n", + " loss_bbox=dict(loss_weight=1.0, type='L1Loss'),\n", + " loss_cls=dict(\n", + " loss_weight=1.0, type='CrossEntropyLoss', use_sigmoid=True),\n", + " type='RPNHead'),\n", + " test_cfg=dict(\n", + " rcnn=dict(\n", + " mask_thr_binary=0.5,\n", + " max_per_img=100,\n", + " nms=dict(iou_threshold=0.5, type='nms'),\n", + " score_thr=0.05),\n", + " rpn=dict(\n", + " max_per_img=1000,\n", + " min_bbox_size=0,\n", + " nms=dict(iou_threshold=0.7, type='nms'),\n", + " nms_pre=1000)),\n", + " train_cfg=dict(\n", + " rcnn=dict(\n", + " assigner=dict(\n", + " ignore_iof_thr=-1,\n", + " match_low_quality=True,\n", + " min_pos_iou=0.5,\n", + " neg_iou_thr=0.5,\n", + " pos_iou_thr=0.5,\n", + " type='MaxIoUAssigner'),\n", + " debug=False,\n", + " mask_size=28,\n", + " pos_weight=-1,\n", + " sampler=dict(\n", + " add_gt_as_proposals=True,\n", + " neg_pos_ub=-1,\n", + " num=512,\n", + " pos_fraction=0.25,\n", + " type='RandomSampler')),\n", + " rpn=dict(\n", + " allowed_border=-1,\n", + " assigner=dict(\n", + " ignore_iof_thr=-1,\n", + " match_low_quality=True,\n", + " min_pos_iou=0.3,\n", + " neg_iou_thr=0.3,\n", + " pos_iou_thr=0.7,\n", + " type='MaxIoUAssigner'),\n", + " debug=False,\n", + " pos_weight=-1,\n", + " sampler=dict(\n", + " add_gt_as_proposals=False,\n", + " neg_pos_ub=-1,\n", + " num=256,\n", + " pos_fraction=0.5,\n", + " type='RandomSampler')),\n", + " rpn_proposal=dict(\n", + " max_per_img=1000,\n", + " min_bbox_size=0,\n", + " nms=dict(iou_threshold=0.7, type='nms'),\n", + " nms_pre=2000)),\n", + " type='MaskRCNN')\n", + "optim_wrapper = dict(\n", + " optimizer=dict(lr=0.0025, momentum=0.9, type='SGD', weight_decay=0.0001),\n", + " type='OptimWrapper')\n", + "param_scheduler = [\n", + " dict(\n", + " begin=0, by_epoch=False, end=500, start_factor=0.001, type='LinearLR'),\n", + " dict(\n", + " begin=0,\n", + " by_epoch=True,\n", + " end=12,\n", + " gamma=0.1,\n", + " milestones=[\n", + " 8,\n", + " 11,\n", + " ],\n", + " type='MultiStepLR'),\n", + "]\n", + "resume = False\n", + "test_cfg = dict(type='TestLoop')\n", + "test_dataloader = dict(\n", + " batch_size=1,\n", + " dataset=dict(\n", + " ann_file='val/annotation_coco.json',\n", + " backend_args=None,\n", + " data_prefix=dict(img='val/'),\n", + " data_root='./ballondatasets/balloon',\n", + " metainfo=dict(classes=('balloon', ), palette=[\n", + " (\n", + " 220,\n", + " 20,\n", + " 60,\n", + " ),\n", + " ]),\n", + " pipeline=[\n", + " dict(backend_args=None, type='LoadImageFromFile'),\n", + " dict(keep_ratio=True, scale=(\n", + " 1333,\n", + " 800,\n", + " ), type='Resize'),\n", + " dict(type='LoadAnnotations', with_bbox=True, with_mask=True),\n", + " dict(\n", + " meta_keys=(\n", + " 'img_id',\n", + " 'img_path',\n", + " 'ori_shape',\n", + " 'img_shape',\n", + " 'scale_factor',\n", + " ),\n", + " type='PackDetInputs'),\n", + " ],\n", + " test_mode=True,\n", + " type='CocoDataset'),\n", + " drop_last=False,\n", + " num_workers=2,\n", + " persistent_workers=True,\n", + " sampler=dict(shuffle=False, type='DefaultSampler'))\n", + "test_evaluator = dict(\n", + " ann_file='./ballondatasets/balloon/val/annotation_coco.json',\n", + " backend_args=None,\n", + " format_only=False,\n", + " metric=[\n", + " 'bbox',\n", + " 'segm',\n", + " ],\n", + " type='CocoMetric')\n", + "test_pipeline = [\n", + " dict(backend_args=None, type='LoadImageFromFile'),\n", + " dict(keep_ratio=True, scale=(\n", + " 1333,\n", + " 800,\n", + " ), type='Resize'),\n", + " dict(type='LoadAnnotations', with_bbox=True, with_mask=True),\n", + " dict(\n", + " meta_keys=(\n", + " 'img_id',\n", + " 'img_path',\n", + " 'ori_shape',\n", + " 'img_shape',\n", + " 'scale_factor',\n", + " ),\n", + " type='PackDetInputs'),\n", + "]\n", + "train_cfg = dict(max_epochs=12, type='EpochBasedTrainLoop', val_interval=3)\n", + "train_dataloader = dict(\n", + " batch_sampler=dict(type='AspectRatioBatchSampler'),\n", + " batch_size=2,\n", + " dataset=dict(\n", + " ann_file='train/annotation_coco.json',\n", + " backend_args=None,\n", + " data_prefix=dict(img='train/'),\n", + " data_root='./ballondatasets/balloon',\n", + " filter_cfg=dict(filter_empty_gt=True, min_size=32),\n", + " metainfo=dict(classes=('balloon', ), palette=[\n", + " (\n", + " 220,\n", + " 20,\n", + " 60,\n", + " ),\n", + " ]),\n", + " pipeline=[\n", + " dict(backend_args=None, type='LoadImageFromFile'),\n", + " dict(\n", + " poly2mask=False,\n", + " type='LoadAnnotations',\n", + " with_bbox=True,\n", + " with_mask=True),\n", + " dict(\n", + " keep_ratio=True,\n", + " scales=[\n", + " (\n", + " 1333,\n", + " 640,\n", + " ),\n", + " (\n", + " 1333,\n", + " 672,\n", + " ),\n", + " (\n", + " 1333,\n", + " 704,\n", + " ),\n", + " (\n", + " 1333,\n", + " 736,\n", + " ),\n", + " (\n", + " 1333,\n", + " 768,\n", + " ),\n", + " (\n", + " 1333,\n", + " 800,\n", + " ),\n", + " ],\n", + " type='RandomChoiceResize'),\n", + " dict(prob=0.5, type='RandomFlip'),\n", + " dict(type='PackDetInputs'),\n", + " ],\n", + " type='CocoDataset'),\n", + " num_workers=2,\n", + " persistent_workers=True,\n", + " sampler=dict(shuffle=True, type='DefaultSampler'))\n", + "train_pipeline = [\n", + " dict(backend_args=None, type='LoadImageFromFile'),\n", + " dict(\n", + " poly2mask=False,\n", + " type='LoadAnnotations',\n", + " with_bbox=True,\n", + " with_mask=True),\n", + " dict(\n", + " keep_ratio=True,\n", + " scales=[\n", + " (\n", + " 1333,\n", + " 640,\n", + " ),\n", + " (\n", + " 1333,\n", + " 672,\n", + " ),\n", + " (\n", + " 1333,\n", + " 704,\n", + " ),\n", + " (\n", + " 1333,\n", + " 736,\n", + " ),\n", + " (\n", + " 1333,\n", + " 768,\n", + " ),\n", + " (\n", + " 1333,\n", + " 800,\n", + " ),\n", + " ],\n", + " type='RandomChoiceResize'),\n", + " dict(prob=0.5, type='RandomFlip'),\n", + " dict(type='PackDetInputs'),\n", + "]\n", + "val_cfg = dict(type='ValLoop')\n", + "val_dataloader = dict(\n", + " batch_size=1,\n", + " dataset=dict(\n", + " ann_file='val/annotation_coco.json',\n", + " backend_args=None,\n", + " data_prefix=dict(img='val/'),\n", + " data_root='./ballondatasets/balloon',\n", + " metainfo=dict(classes=('balloon', ), palette=[\n", + " (\n", + " 220,\n", + " 20,\n", + " 60,\n", + " ),\n", + " ]),\n", + " pipeline=[\n", + " dict(backend_args=None, type='LoadImageFromFile'),\n", + " dict(keep_ratio=True, scale=(\n", + " 1333,\n", + " 800,\n", + " ), type='Resize'),\n", + " dict(type='LoadAnnotations', with_bbox=True, with_mask=True),\n", + " dict(\n", + " meta_keys=(\n", + " 'img_id',\n", + " 'img_path',\n", + " 'ori_shape',\n", + " 'img_shape',\n", + " 'scale_factor',\n", + " ),\n", + " type='PackDetInputs'),\n", + " ],\n", + " test_mode=True,\n", + " type='CocoDataset'),\n", + " drop_last=False,\n", + " num_workers=2,\n", + " persistent_workers=True,\n", + " sampler=dict(shuffle=False, type='DefaultSampler'))\n", + "val_evaluator = dict(\n", + " ann_file='./ballondatasets/balloon/val/annotation_coco.json',\n", + " backend_args=None,\n", + " format_only=False,\n", + " metric=[\n", + " 'bbox',\n", + " 'segm',\n", + " ],\n", + " type='CocoMetric')\n", + "vis_backends = [\n", + " dict(type='LocalVisBackend'),\n", + "]\n", + "visualizer = dict(\n", + " name='visualizer',\n", + " type='DetLocalVisualizer',\n", + " vis_backends=[\n", + " dict(type='LocalVisBackend'),\n", + " dict(type='TensorboardVisBackend'),\n", + " ])\n", + "work_dir = './tutorial_exps'\n", + "\n", + "2023-08-15 04:31:59.033157: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n", + "To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n", + "2023-08-15 04:32:00.371943: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT\n", + "08/15 04:32:04 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Distributed training is not used, all SyncBatchNorm (SyncBN) layers in the model will be automatically reverted to BatchNormXd layers if they are used.\n", + "08/15 04:32:04 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Hooks will be executed in the following order:\n", + "before_run:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "before_train:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "before_train_epoch:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(NORMAL ) DistSamplerSeedHook \n", + " -------------------- \n", + "before_train_iter:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "after_train_iter:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(BELOW_NORMAL) LoggerHook \n", + "(LOW ) ParamSchedulerHook \n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "after_train_epoch:\n", + "(NORMAL ) IterTimerHook \n", + "(LOW ) ParamSchedulerHook \n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "before_val:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + " -------------------- \n", + "before_val_epoch:\n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "before_val_iter:\n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "after_val_iter:\n", + "(NORMAL ) IterTimerHook \n", + "(NORMAL ) DetVisualizationHook \n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "after_val_epoch:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(BELOW_NORMAL) LoggerHook \n", + "(LOW ) ParamSchedulerHook \n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "after_val:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + " -------------------- \n", + "after_train:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "before_test:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + " -------------------- \n", + "before_test_epoch:\n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "before_test_iter:\n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "after_test_iter:\n", + "(NORMAL ) IterTimerHook \n", + "(NORMAL ) DetVisualizationHook \n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "after_test_epoch:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "after_test:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + " -------------------- \n", + "after_run:\n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "loading annotations into memory...\n", + "Done (t=0.01s)\n", + "creating index...\n", + "index created!\n", + "loading annotations into memory...\n", + "Done (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "loading annotations into memory...\n", + "Done (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "08/15 04:32:05 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - load model from: open-mmlab://detectron2/resnet50_caffe\n", + "08/15 04:32:05 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Loads checkpoint by openmmlab backend from path: open-mmlab://detectron2/resnet50_caffe\n", + "Downloading: \"https://download.openmmlab.com/pretrain/third_party/resnet50_msra-5891d200.pth\" to /root/.cache/torch/hub/checkpoints/resnet50_msra-5891d200.pth\n", + "100% 89.9M/89.9M [00:12<00:00, 7.43MB/s]\n", + "08/15 04:32:19 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - The model and loaded state dict do not match exactly\n", + "\n", + "unexpected key in source state_dict: conv1.bias\n", + "\n", + "Loads checkpoint by local backend from path: checkpoints/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth\n", + "The model and loaded state dict do not match exactly\n", + "\n", + "size mismatch for roi_head.bbox_head.fc_cls.weight: copying a param with shape torch.Size([81, 1024]) from checkpoint, the shape in current model is torch.Size([2, 1024]).\n", + "size mismatch for roi_head.bbox_head.fc_cls.bias: copying a param with shape torch.Size([81]) from checkpoint, the shape in current model is torch.Size([2]).\n", + "size mismatch for roi_head.bbox_head.fc_reg.weight: copying a param with shape torch.Size([320, 1024]) from checkpoint, the shape in current model is torch.Size([4, 1024]).\n", + "size mismatch for roi_head.bbox_head.fc_reg.bias: copying a param with shape torch.Size([320]) from checkpoint, the shape in current model is torch.Size([4]).\n", + "size mismatch for roi_head.mask_head.conv_logits.weight: copying a param with shape torch.Size([80, 256, 1, 1]) from checkpoint, the shape in current model is torch.Size([1, 256, 1, 1]).\n", + "size mismatch for roi_head.mask_head.conv_logits.bias: copying a param with shape torch.Size([80]) from checkpoint, the shape in current model is torch.Size([1]).\n", + "08/15 04:32:19 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Load checkpoint from checkpoints/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth\n", + "08/15 04:32:19 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - \"FileClient\" will be deprecated in future. Please use io functions in https://mmengine.readthedocs.io/en/latest/api/fileio.html#file-io\n", + "08/15 04:32:19 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - \"HardDiskBackend\" is the alias of \"LocalBackend\" and the former will be deprecated in future.\n", + "08/15 04:32:19 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Checkpoints will be saved to /content/mmdetection/tutorial_exps.\n", + "08/15 04:32:25 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [1][10/31] lr: 4.7545e-05 eta: 0:03:33 time: 0.5898 data_time: 0.0359 memory: 3283 loss: 16.7169 loss_rpn_cls: 0.1373 loss_rpn_bbox: 0.0201 loss_cls: 0.6155 acc: 83.3984 loss_bbox: 0.3727 loss_mask: 15.5713\n", + "08/15 04:32:30 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [1][20/31] lr: 9.7595e-05 eta: 0:03:05 time: 0.5271 data_time: 0.0214 memory: 3283 loss: 10.8499 loss_rpn_cls: 0.0836 loss_rpn_bbox: 0.0148 loss_cls: 0.5401 acc: 84.6680 loss_bbox: 0.2949 loss_mask: 9.9164\n", + "08/15 04:32:35 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [1][30/31] lr: 1.4765e-04 eta: 0:03:02 time: 0.5348 data_time: 0.0180 memory: 3283 loss: 7.7485 loss_rpn_cls: 0.0823 loss_rpn_bbox: 0.0183 loss_cls: 0.4752 acc: 95.7031 loss_bbox: 0.2886 loss_mask: 6.8839\n", + "08/15 04:32:35 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:32:41 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [2][10/31] lr: 2.0270e-04 eta: 0:02:53 time: 0.5254 data_time: 0.0198 memory: 3449 loss: 5.9555 loss_rpn_cls: 0.0705 loss_rpn_bbox: 0.0176 loss_cls: 0.4270 acc: 93.2617 loss_bbox: 0.3093 loss_mask: 5.1312\n", + "08/15 04:32:46 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [2][20/31] lr: 2.5275e-04 eta: 0:02:49 time: 0.5077 data_time: 0.0126 memory: 3282 loss: 4.6321 loss_rpn_cls: 0.0661 loss_rpn_bbox: 0.0176 loss_cls: 0.3947 acc: 84.6680 loss_bbox: 0.3221 loss_mask: 3.8315\n", + "08/15 04:32:52 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [2][30/31] lr: 3.0280e-04 eta: 0:02:45 time: 0.5237 data_time: 0.0137 memory: 3283 loss: 1.8118 loss_rpn_cls: 0.0470 loss_rpn_bbox: 0.0193 loss_cls: 0.3295 acc: 85.7422 loss_bbox: 0.3360 loss_mask: 1.0801\n", + "08/15 04:32:52 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:32:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [3][10/31] lr: 3.5786e-04 eta: 0:02:37 time: 0.5224 data_time: 0.0146 memory: 3282 loss: 1.1179 loss_rpn_cls: 0.0411 loss_rpn_bbox: 0.0203 loss_cls: 0.2903 acc: 96.0938 loss_bbox: 0.3861 loss_mask: 0.3801\n", + "08/15 04:33:02 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [3][20/31] lr: 4.0791e-04 eta: 0:02:31 time: 0.5148 data_time: 0.0105 memory: 3283 loss: 0.9559 loss_rpn_cls: 0.0353 loss_rpn_bbox: 0.0189 loss_cls: 0.2542 acc: 95.4102 loss_bbox: 0.3753 loss_mask: 0.2723\n", + "08/15 04:33:08 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [3][30/31] lr: 4.5796e-04 eta: 0:02:27 time: 0.5319 data_time: 0.0118 memory: 3283 loss: 0.9498 loss_rpn_cls: 0.0316 loss_rpn_bbox: 0.0194 loss_cls: 0.2459 acc: 86.4258 loss_bbox: 0.4030 loss_mask: 0.2500\n", + "08/15 04:33:08 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:33:08 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Saving checkpoint at 3 epochs\n", + "08/15 04:33:20 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [3][10/13] eta: 0:00:03 time: 1.0153 data_time: 0.0759 memory: 2785 \n", + "08/15 04:33:21 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.16s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.06s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.515\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 0.736\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.597\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.266\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.621\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.644\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.644\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.644\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.525\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.719\n", + "08/15 04:33:21 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.515 0.736 0.597 0.000 0.266 0.621\n", + "08/15 04:33:21 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating segm...\n", + "Loading and preparing results...\n", + "DONE (t=0.04s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *segm*\n", + "DONE (t=0.21s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.06s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.622\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 0.733\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.729\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.001\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.331\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.734\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.764\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.764\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.764\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.150\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.750\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.803\n", + "08/15 04:33:22 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - segm_mAP_copypaste: 0.622 0.733 0.729 0.001 0.331 0.734\n", + "08/15 04:33:22 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [3][13/13] coco/bbox_mAP: 0.5150 coco/bbox_mAP_50: 0.7360 coco/bbox_mAP_75: 0.5970 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2660 coco/bbox_mAP_l: 0.6210 coco/segm_mAP: 0.6220 coco/segm_mAP_50: 0.7330 coco/segm_mAP_75: 0.7290 coco/segm_mAP_s: 0.0010 coco/segm_mAP_m: 0.3310 coco/segm_mAP_l: 0.7340 data_time: 0.0597 time: 0.8604\n", + "08/15 04:33:27 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [4][10/31] lr: 5.1301e-04 eta: 0:02:22 time: 0.5251 data_time: 0.0119 memory: 3283 loss: 0.8807 loss_rpn_cls: 0.0273 loss_rpn_bbox: 0.0187 loss_cls: 0.2192 acc: 95.1172 loss_bbox: 0.4036 loss_mask: 0.2119\n", + "08/15 04:33:32 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [4][20/31] lr: 5.6306e-04 eta: 0:02:16 time: 0.5211 data_time: 0.0099 memory: 3004 loss: 0.8362 loss_rpn_cls: 0.0217 loss_rpn_bbox: 0.0173 loss_cls: 0.2001 acc: 97.4609 loss_bbox: 0.4115 loss_mask: 0.1856\n", + "08/15 04:33:38 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [4][30/31] lr: 6.1311e-04 eta: 0:02:11 time: 0.5334 data_time: 0.0108 memory: 3284 loss: 0.7356 loss_rpn_cls: 0.0198 loss_rpn_bbox: 0.0148 loss_cls: 0.1705 acc: 99.1211 loss_bbox: 0.3685 loss_mask: 0.1621\n", + "08/15 04:33:38 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:33:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [5][10/31] lr: 6.6817e-04 eta: 0:02:05 time: 0.5305 data_time: 0.0137 memory: 3283 loss: 0.6874 loss_rpn_cls: 0.0174 loss_rpn_bbox: 0.0127 loss_cls: 0.1545 acc: 95.1172 loss_bbox: 0.3607 loss_mask: 0.1421\n", + "08/15 04:33:49 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [5][20/31] lr: 7.1822e-04 eta: 0:01:59 time: 0.5216 data_time: 0.0115 memory: 3283 loss: 0.6122 loss_rpn_cls: 0.0162 loss_rpn_bbox: 0.0123 loss_cls: 0.1331 acc: 93.5547 loss_bbox: 0.3217 loss_mask: 0.1289\n", + "08/15 04:33:54 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [5][30/31] lr: 7.6827e-04 eta: 0:01:55 time: 0.5289 data_time: 0.0125 memory: 3283 loss: 0.5432 loss_rpn_cls: 0.0145 loss_rpn_bbox: 0.0122 loss_cls: 0.1175 acc: 93.7500 loss_bbox: 0.2771 loss_mask: 0.1219\n", + "08/15 04:33:55 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:34:00 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [6][10/31] lr: 8.2332e-04 eta: 0:01:49 time: 0.5335 data_time: 0.0137 memory: 3284 loss: 0.4541 loss_rpn_cls: 0.0139 loss_rpn_bbox: 0.0115 loss_cls: 0.0988 acc: 94.2383 loss_bbox: 0.2158 loss_mask: 0.1141\n", + "08/15 04:34:06 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [6][20/31] lr: 8.7337e-04 eta: 0:01:44 time: 0.5318 data_time: 0.0095 memory: 3283 loss: 0.4245 loss_rpn_cls: 0.0130 loss_rpn_bbox: 0.0122 loss_cls: 0.0944 acc: 99.4141 loss_bbox: 0.1922 loss_mask: 0.1127\n", + "08/15 04:34:11 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [6][30/31] lr: 9.2342e-04 eta: 0:01:39 time: 0.5465 data_time: 0.0102 memory: 3497 loss: 0.3810 loss_rpn_cls: 0.0133 loss_rpn_bbox: 0.0126 loss_cls: 0.0883 acc: 98.1445 loss_bbox: 0.1617 loss_mask: 0.1052\n", + "08/15 04:34:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:34:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Saving checkpoint at 6 epochs\n", + "08/15 04:34:17 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [6][10/13] eta: 0:00:01 time: 0.6382 data_time: 0.0735 memory: 1810 \n", + "08/15 04:34:18 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.03s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.709\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 0.854\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.799\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.474\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.795\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.776\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.776\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.776\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.733\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.833\n", + "08/15 04:34:18 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.709 0.854 0.799 0.000 0.474 0.795\n", + "08/15 04:34:18 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating segm...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *segm*\n", + "DONE (t=0.03s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.01s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.762\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 0.834\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.834\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.453\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.860\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.822\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.822\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.822\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.758\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.889\n", + "08/15 04:34:18 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - segm_mAP_copypaste: 0.762 0.834 0.834 0.000 0.453 0.860\n", + "08/15 04:34:18 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [6][13/13] coco/bbox_mAP: 0.7090 coco/bbox_mAP_50: 0.8540 coco/bbox_mAP_75: 0.7990 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.4740 coco/bbox_mAP_l: 0.7950 coco/segm_mAP: 0.7620 coco/segm_mAP_50: 0.8340 coco/segm_mAP_75: 0.8340 coco/segm_mAP_s: 0.0000 coco/segm_mAP_m: 0.4530 coco/segm_mAP_l: 0.8600 data_time: 0.0661 time: 0.3035\n", + "08/15 04:34:23 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [7][10/31] lr: 9.7848e-04 eta: 0:01:33 time: 0.5453 data_time: 0.0117 memory: 3283 loss: 0.3427 loss_rpn_cls: 0.0123 loss_rpn_bbox: 0.0119 loss_cls: 0.0819 acc: 98.7305 loss_bbox: 0.1376 loss_mask: 0.0989\n", + "08/15 04:34:29 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [7][20/31] lr: 1.0285e-03 eta: 0:01:28 time: 0.5473 data_time: 0.0103 memory: 3282 loss: 0.2958 loss_rpn_cls: 0.0098 loss_rpn_bbox: 0.0095 loss_cls: 0.0710 acc: 97.1680 loss_bbox: 0.1132 loss_mask: 0.0922\n", + "08/15 04:34:35 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [7][30/31] lr: 1.0786e-03 eta: 0:01:23 time: 0.5543 data_time: 0.0105 memory: 3448 loss: 0.2984 loss_rpn_cls: 0.0092 loss_rpn_bbox: 0.0104 loss_cls: 0.0723 acc: 91.1133 loss_bbox: 0.1114 loss_mask: 0.0952\n", + "08/15 04:34:35 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:34:41 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [8][10/31] lr: 1.1336e-03 eta: 0:01:17 time: 0.5499 data_time: 0.0125 memory: 3283 loss: 0.2648 loss_rpn_cls: 0.0083 loss_rpn_bbox: 0.0094 loss_cls: 0.0634 acc: 97.7539 loss_bbox: 0.0944 loss_mask: 0.0894\n", + "08/15 04:34:47 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [8][20/31] lr: 1.1837e-03 eta: 0:01:12 time: 0.5652 data_time: 0.0119 memory: 3283 loss: 0.2537 loss_rpn_cls: 0.0073 loss_rpn_bbox: 0.0086 loss_cls: 0.0605 acc: 99.2188 loss_bbox: 0.0873 loss_mask: 0.0900\n", + "08/15 04:34:52 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [8][30/31] lr: 1.2337e-03 eta: 0:01:07 time: 0.5620 data_time: 0.0117 memory: 3283 loss: 0.2575 loss_rpn_cls: 0.0073 loss_rpn_bbox: 0.0099 loss_cls: 0.0642 acc: 95.4102 loss_bbox: 0.0891 loss_mask: 0.0870\n", + "08/15 04:34:52 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:34:58 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [9][10/31] lr: 1.2888e-04 eta: 0:01:01 time: 0.5621 data_time: 0.0160 memory: 3283 loss: 0.2680 loss_rpn_cls: 0.0079 loss_rpn_bbox: 0.0108 loss_cls: 0.0709 acc: 95.8008 loss_bbox: 0.0934 loss_mask: 0.0851\n", + "08/15 04:35:04 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [9][20/31] lr: 1.3388e-04 eta: 0:00:56 time: 0.5631 data_time: 0.0150 memory: 3283 loss: 0.2295 loss_rpn_cls: 0.0063 loss_rpn_bbox: 0.0082 loss_cls: 0.0603 acc: 99.1211 loss_bbox: 0.0787 loss_mask: 0.0760\n", + "08/15 04:35:09 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [9][30/31] lr: 1.3889e-04 eta: 0:00:50 time: 0.5553 data_time: 0.0133 memory: 3091 loss: 0.2444 loss_rpn_cls: 0.0064 loss_rpn_bbox: 0.0094 loss_cls: 0.0650 acc: 96.5820 loss_bbox: 0.0847 loss_mask: 0.0789\n", + "08/15 04:35:09 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:35:09 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Saving checkpoint at 9 epochs\n", + "08/15 04:35:16 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [9][10/13] eta: 0:00:01 time: 0.5437 data_time: 0.0917 memory: 1693 \n", + "08/15 04:35:16 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.04s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.741\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 0.869\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.807\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.473\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.833\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.794\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.794\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.794\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.717\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.864\n", + "08/15 04:35:16 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.741 0.869 0.807 0.000 0.473 0.833\n", + "08/15 04:35:16 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating segm...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *segm*\n", + "DONE (t=0.04s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.779\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 0.847\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.847\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.476\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.877\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.830\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.830\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.830\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.767\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.897\n", + "08/15 04:35:16 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - segm_mAP_copypaste: 0.779 0.847 0.847 0.000 0.476 0.877\n", + "08/15 04:35:16 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [9][13/13] coco/bbox_mAP: 0.7410 coco/bbox_mAP_50: 0.8690 coco/bbox_mAP_75: 0.8070 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.4730 coco/bbox_mAP_l: 0.8330 coco/segm_mAP: 0.7790 coco/segm_mAP_50: 0.8470 coco/segm_mAP_75: 0.8470 coco/segm_mAP_s: 0.0000 coco/segm_mAP_m: 0.4760 coco/segm_mAP_l: 0.8770 data_time: 0.1157 time: 0.3601\n", + "08/15 04:35:22 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [10][10/31] lr: 1.4439e-04 eta: 0:00:44 time: 0.5374 data_time: 0.0169 memory: 3284 loss: 0.2415 loss_rpn_cls: 0.0063 loss_rpn_bbox: 0.0093 loss_cls: 0.0644 acc: 98.3398 loss_bbox: 0.0832 loss_mask: 0.0784\n", + "08/15 04:35:27 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [10][20/31] lr: 1.4940e-04 eta: 0:00:39 time: 0.5342 data_time: 0.0130 memory: 3283 loss: 0.2161 loss_rpn_cls: 0.0048 loss_rpn_bbox: 0.0080 loss_cls: 0.0573 acc: 99.4141 loss_bbox: 0.0721 loss_mask: 0.0738\n", + "08/15 04:35:33 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [10][30/31] lr: 1.5440e-04 eta: 0:00:33 time: 0.5374 data_time: 0.0129 memory: 3428 loss: 0.2210 loss_rpn_cls: 0.0054 loss_rpn_bbox: 0.0086 loss_cls: 0.0584 acc: 99.8047 loss_bbox: 0.0737 loss_mask: 0.0749\n", + "08/15 04:35:33 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:35:38 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [11][10/31] lr: 1.5991e-04 eta: 0:00:27 time: 0.5344 data_time: 0.0149 memory: 3067 loss: 0.2405 loss_rpn_cls: 0.0052 loss_rpn_bbox: 0.0099 loss_cls: 0.0639 acc: 98.8281 loss_bbox: 0.0807 loss_mask: 0.0808\n", + "08/15 04:35:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [11][20/31] lr: 1.6491e-04 eta: 0:00:22 time: 0.5326 data_time: 0.0105 memory: 3283 loss: 0.2297 loss_rpn_cls: 0.0047 loss_rpn_bbox: 0.0094 loss_cls: 0.0606 acc: 96.7773 loss_bbox: 0.0768 loss_mask: 0.0781\n", + "08/15 04:35:49 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [11][30/31] lr: 1.6992e-04 eta: 0:00:17 time: 0.5409 data_time: 0.0115 memory: 3283 loss: 0.2221 loss_rpn_cls: 0.0048 loss_rpn_bbox: 0.0092 loss_cls: 0.0595 acc: 95.3125 loss_bbox: 0.0753 loss_mask: 0.0733\n", + "08/15 04:35:50 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:35:55 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [12][10/31] lr: 1.7543e-05 eta: 0:00:11 time: 0.5311 data_time: 0.0129 memory: 3200 loss: 0.2094 loss_rpn_cls: 0.0054 loss_rpn_bbox: 0.0084 loss_cls: 0.0555 acc: 96.9727 loss_bbox: 0.0689 loss_mask: 0.0711\n", + "08/15 04:36:00 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [12][20/31] lr: 1.8043e-05 eta: 0:00:05 time: 0.5275 data_time: 0.0098 memory: 3448 loss: 0.2108 loss_rpn_cls: 0.0047 loss_rpn_bbox: 0.0089 loss_cls: 0.0560 acc: 99.5117 loss_bbox: 0.0698 loss_mask: 0.0713\n", + "08/15 04:36:06 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [12][30/31] lr: 1.8544e-05 eta: 0:00:00 time: 0.5425 data_time: 0.0111 memory: 3282 loss: 0.2048 loss_rpn_cls: 0.0043 loss_rpn_bbox: 0.0080 loss_cls: 0.0540 acc: 98.2422 loss_bbox: 0.0695 loss_mask: 0.0690\n", + "08/15 04:36:06 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: mask-rcnn_r50-caffe_fpn_ms-poly-3x_balloon_20230815_043154\n", + "08/15 04:36:06 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Saving checkpoint at 12 epochs\n", + "08/15 04:36:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [12][10/13] eta: 0:00:01 time: 0.4801 data_time: 0.0931 memory: 1589 \n", + "08/15 04:36:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.02s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.01s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.740\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 0.868\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.804\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.506\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.828\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.798\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.798\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.798\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.742\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.861\n", + "08/15 04:36:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.740 0.868 0.804 0.000 0.506 0.828\n", + "08/15 04:36:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating segm...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *segm*\n", + "DONE (t=0.02s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.01s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.776\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=1000 ] = 0.845\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=1000 ] = 0.845\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.481\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.868\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.832\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=300 ] = 0.832\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.832\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.775\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.897\n", + "08/15 04:36:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - segm_mAP_copypaste: 0.776 0.845 0.845 0.000 0.481 0.868\n", + "08/15 04:36:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [12][13/13] coco/bbox_mAP: 0.7400 coco/bbox_mAP_50: 0.8680 coco/bbox_mAP_75: 0.8040 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.5060 coco/bbox_mAP_l: 0.8280 coco/segm_mAP: 0.7760 coco/segm_mAP_50: 0.8450 coco/segm_mAP_75: 0.8450 coco/segm_mAP_s: 0.0000 coco/segm_mAP_m: 0.4810 coco/segm_mAP_l: 0.8680 data_time: 0.0903 time: 0.2892\n" + ] + } + ], + "source": [ + "!python tools/train.py {config}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_vYQF5K2NqqI" + }, + "source": [ + "### Understand the log\n", + "From the log, we can have a basic understanding on the training process and know how well the detector is trained.\n", + "\n", + "First, since the dataset we are using is small, we loaded a Mask R-CNN model and finetune it for detection. Because the original Mask R-CNN is trained on COCO dataset that contains 80 classes but KITTI Tiny dataset only have 3 classes. Therefore, the last FC layers of the pre-trained Mask R-CNN for classification and regression have different weight shape and are not used. The pre-trained weights of mask prediction layer `mask_head.conv_logits` also does not matches the current model and is not used due to similar reason.\n", + "\n", + "Third, after training, the detector is evaluated by the default COCO-style evaluation. The results show that the detector achieves 79.6 bbox AP and 81.5 mask AP on the val dataset, not bad!\n", + "\n", + " We can also check the tensorboard to see the curves." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "gbLNlJR-RYYd" + }, + "outputs": [], + "source": [ + "%pip install tensorboard -i https://mirrors.ustc.edu.cn/pypi/web/simple" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "id": "PW2NAam_7irv" + }, + "outputs": [], + "source": [ + "# load tensorboard in jupyter notebook\n", + "%load_ext tensorboard" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "4G9MCbL2RYYd" + }, + "outputs": [], + "source": [ + "# see curves in tensorboard\n", + "# if you see please run it again\n", + "%tensorboard --logdir tutorial_exps/" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MfQ-yspZLuuI" + }, + "source": [ + "## Test the Trained Detector\n", + "\n", + "After finetuning the detector, let's visualize the prediction results!" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "_MuZurfGLq0p", + "outputId": "4b25759c-8e22-405e-a061-3abc44e38043" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loads checkpoint by local backend from path: tutorial_exps/epoch_12.pth\n", + "\n", + " ignored_instances: \n", + " pred_instances: \n", + ") at 0x79a3a96e3130>\n" + ] + } + ], + "source": [ + "import mmcv\n", + "from mmdet.apis import init_detector, inference_detector\n", + "img = mmcv.imread('./ballondatasets/balloon/train/7178882742_f090f3ce56_k.jpg',channel_order='rgb')\n", + "checkpoint_file = 'tutorial_exps/epoch_12.pth'\n", + "model = init_detector(cfg, checkpoint_file, device='cpu')\n", + "new_result = inference_detector(model, img)\n", + "print(new_result)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 461 + }, + "id": "7SSTauCURYYe", + "outputId": "3becb5ea-cb4e-44f6-d93d-c10194a2263b" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
    " + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from mmengine.visualization import Visualizer\n", + "# get built visualizer\n", + "visualizer_now = Visualizer.get_current_instance()\n", + "# the dataset_meta is loaded from the checkpoint and\n", + "# then pass to the model in init_detector\n", + "visualizer_now.dataset_meta = model.dataset_meta\n", + "# show the results\n", + "visualizer_now.add_datasample(\n", + " 'new_result',\n", + " img,\n", + " data_sample=new_result,\n", + " draw_gt=False,\n", + " wait_time=0,\n", + " out_file=None,\n", + " pred_score_thr=0.5\n", + ")\n", + "visualizer_now.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6rzruCwFgPXm" + }, + "source": [ + "## What to Do Next?\n", + "\n", + "So far, we have learnt how to test and train Mask R-CNN. To further explore the segmentation task, you could do several other things as shown below:\n", + "\n", + "- Try cascade methods, e.g., [Cascade Mask R-CNN](https://github.com/open-mmlab/mmdetection/tree/master/configs/cascade_rcnn) and [HTC](https://github.com/open-mmlab/mmdetection/tree/master/configs/htc) in [MMDetection model zoo](https://github.com/open-mmlab/mmdetection/blob/master/docs/en/model_zoo.md). They are powerful detectors that are ranked high in many benchmarks, e.g., COCO dataset.\n", + "- Try single-stage methods, e.g., [K-Net](https://github.com/ZwwWayne/K-Net) and [Dense-RepPoints](https://github.com/justimyhxu/Dense-RepPoints). These two algorithms are based on MMDetection. Box-free instance segmentation is a new trend in the instance segmentation community.\n", + "- Try semantic segmentation. Semantic segmentation is also a popular task with wide applications. You can explore [MMSegmentation](https://github.com/open-mmlab/mmsegmentation/); we also provide a [colab tutorial](https://github.com/open-mmlab/mmsegmentation/blob/master/demo/MMSegmentation_Tutorial.ipynb) for semantic segmentation using MMSegmentation.\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "8868640c17582ff5a3e06365ba2fb344ce697cf42d4745ae8b85a9738303c037" + } + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/mmdetection/demo/MMDet_Tutorial.ipynb b/mmdetection/demo/MMDet_Tutorial.ipynb new file mode 100644 index 00000000..47785506 --- /dev/null +++ b/mmdetection/demo/MMDet_Tutorial.ipynb @@ -0,0 +1,3013 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "gCMycQ_2U8SA" + }, + "source": [ + "
    \n", + " \n", + "
     
    \n", + "
    \n", + " OpenMMLab website\n", + " \n", + " \n", + " HOT\n", + " \n", + " \n", + "     \n", + " OpenMMLab platform\n", + " \n", + " \n", + " TRY IT OUT\n", + " \n", + " \n", + "
    \n", + "
     
    \n", + "\n", + "\"Open\n", + "\n", + "[![PyPI](https://img.shields.io/pypi/v/mmdet)](https://pypi.org/project/mmdet)\n", + "[![docs](https://img.shields.io/badge/docs-latest-blue)](https://mmdetection.readthedocs.io/en/latest/)\n", + "[![badge](https://github.com/open-mmlab/mmdetection/workflows/build/badge.svg)](https://github.com/open-mmlab/mmdetection/actions)\n", + "[![codecov](https://codecov.io/gh/open-mmlab/mmdetection/branch/master/graph/badge.svg)](https://codecov.io/gh/open-mmlab/mmdetection)\n", + "[![license](https://img.shields.io/github/license/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/blob/master/LICENSE)\n", + "[![open issues](https://isitmaintained.com/badge/open/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/issues)\n", + "[![issue resolution](https://isitmaintained.com/badge/resolution/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/issues)\n", + "\n", + "[📘Documentation](https://mmdetection.readthedocs.io/en/3.x/) |\n", + "[🛠️Installation](https://mmdetection.readthedocs.io/en/3.x/get_started.html) |\n", + "[👀Model Zoo](https://mmdetection.readthedocs.io/en/3.x/model_zoo.html) |\n", + "[🆕Update News](https://mmdetection.readthedocs.io/en/3.x/notes/changelog.html) |\n", + "[🚀Ongoing Projects](https://github.com/open-mmlab/mmdetection/projects) |\n", + "[🤔Reporting Issues](https://github.com/open-mmlab/mmdetection/issues/new/choose)\n", + "\n", + "
    \n", + "\n", + "
    \n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + "
    " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aGYwt_UjIrqp" + }, + "source": [ + "# Object Detection\n", + "\n", + "In this tutorial, you will learn:\n", + "- the basic structure of RTMDet.\n", + "- to perform inference with a MMDetection detector.\n", + "- to train a new detector with a new dataset.\n", + "\n", + "Let's start!\n", + "\n", + "```{note}\n", + "The commands in this tutorial are mainly for Colab.\n", + "You can click the button above, `Open in Colab`, to run this notebook in Colab.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tJxJHruNLb7Y" + }, + "source": [ + "## Install MMDetection" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Wi4LPmsR66sy", + "outputId": "13704ca1-3e1f-4bfc-8638-86458b1effb1" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "nvcc: NVIDIA (R) Cuda compiler driver\n", + "Copyright (c) 2005-2022 NVIDIA Corporation\n", + "Built on Wed_Sep_21_10:33:58_PDT_2022\n", + "Cuda compilation tools, release 11.8, V11.8.89\n", + "Build cuda_11.8.r11.8/compiler.31833905_0\n", + "gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0\n", + "Copyright (C) 2019 Free Software Foundation, Inc.\n", + "This is free software; see the source for copying conditions. There is NO\n", + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", + "\n" + ] + } + ], + "source": [ + "# Check nvcc version\n", + "!nvcc -V\n", + "# Check GCC version\n", + "!gcc --version" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gkGnB9WyHSXB", + "outputId": "781c6870-be3d-4162-cae1-017ebf0c6043" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n", + "Collecting openmim\n", + " Downloading openmim-0.3.7-py2.py3-none-any.whl (51 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m51.3/51.3 kB\u001b[0m \u001b[31m536.0 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: requests in /usr/local/lib/python3.9/dist-packages (from openmim) (2.27.1)\n", + "Collecting model-index\n", + " Downloading model_index-0.1.11-py3-none-any.whl (34 kB)\n", + "Requirement already satisfied: rich in /usr/local/lib/python3.9/dist-packages (from openmim) (13.3.3)\n", + "Requirement already satisfied: tabulate in /usr/local/lib/python3.9/dist-packages (from openmim) (0.8.10)\n", + "Requirement already satisfied: pandas in /usr/local/lib/python3.9/dist-packages (from openmim) (1.5.3)\n", + "Requirement already satisfied: Click in /usr/local/lib/python3.9/dist-packages (from openmim) (8.1.3)\n", + "Collecting colorama\n", + " Downloading colorama-0.4.6-py2.py3-none-any.whl (25 kB)\n", + "Requirement already satisfied: pip>=19.3 in /usr/local/lib/python3.9/dist-packages (from openmim) (23.0.1)\n", + "Collecting ordered-set\n", + " Downloading ordered_set-4.1.0-py3-none-any.whl (7.6 kB)\n", + "Requirement already satisfied: markdown in /usr/local/lib/python3.9/dist-packages (from model-index->openmim) (3.4.3)\n", + "Requirement already satisfied: pyyaml in /usr/local/lib/python3.9/dist-packages (from model-index->openmim) (6.0)\n", + "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.9/dist-packages (from pandas->openmim) (2022.7.1)\n", + "Requirement already satisfied: numpy>=1.20.3 in /usr/local/lib/python3.9/dist-packages (from pandas->openmim) (1.22.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.9/dist-packages (from pandas->openmim) (2.8.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.9/dist-packages (from requests->openmim) (2022.12.7)\n", + "Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.9/dist-packages (from requests->openmim) (2.0.12)\n", + "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.9/dist-packages (from requests->openmim) (1.26.15)\n", + "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.9/dist-packages (from requests->openmim) (3.4)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.9/dist-packages (from rich->openmim) (2.14.0)\n", + "Requirement already satisfied: markdown-it-py<3.0.0,>=2.2.0 in /usr/local/lib/python3.9/dist-packages (from rich->openmim) (2.2.0)\n", + "Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.9/dist-packages (from markdown-it-py<3.0.0,>=2.2.0->rich->openmim) (0.1.2)\n", + "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.9/dist-packages (from python-dateutil>=2.8.1->pandas->openmim) (1.16.0)\n", + "Requirement already satisfied: importlib-metadata>=4.4 in /usr/local/lib/python3.9/dist-packages (from markdown->model-index->openmim) (6.3.0)\n", + "Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.9/dist-packages (from importlib-metadata>=4.4->markdown->model-index->openmim) (3.15.0)\n", + "Installing collected packages: ordered-set, colorama, model-index, openmim\n", + "Successfully installed colorama-0.4.6 model-index-0.1.11 openmim-0.3.7 ordered-set-4.1.0\n", + "/usr/local/lib/python3.9/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n", + "Looking in links: https://download.openmmlab.com/mmcv/dist/cu118/torch2.0.0/index.html\n", + "Collecting mmengine>=0.7.0\n", + " Downloading mmengine-0.7.2-py3-none-any.whl (366 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m366.9/366.9 kB\u001b[0m \u001b[31m26.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: pyyaml in /usr/local/lib/python3.9/dist-packages (from mmengine>=0.7.0) (6.0)\n", + "Requirement already satisfied: rich in /usr/local/lib/python3.9/dist-packages (from mmengine>=0.7.0) (13.3.3)\n", + "Requirement already satisfied: opencv-python>=3 in /usr/local/lib/python3.9/dist-packages (from mmengine>=0.7.0) (4.7.0.72)\n", + "Collecting addict\n", + " Downloading addict-2.4.0-py3-none-any.whl (3.8 kB)\n", + "Requirement already satisfied: matplotlib in /usr/local/lib/python3.9/dist-packages (from mmengine>=0.7.0) (3.7.1)\n", + "Collecting yapf\n", + " Downloading yapf-0.32.0-py2.py3-none-any.whl (190 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m190.2/190.2 kB\u001b[0m \u001b[31m25.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: numpy in /usr/local/lib/python3.9/dist-packages (from mmengine>=0.7.0) (1.22.4)\n", + "Requirement already satisfied: termcolor in /usr/local/lib/python3.9/dist-packages (from mmengine>=0.7.0) (2.2.0)\n", + "Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.7.0) (3.0.9)\n", + "Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.7.0) (4.39.3)\n", + "Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.7.0) (1.0.7)\n", + "Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.7.0) (8.4.0)\n", + "Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.7.0) (0.11.0)\n", + "Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.7.0) (1.4.4)\n", + "Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.7.0) (23.0)\n", + "Requirement already satisfied: importlib-resources>=3.2.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.7.0) (5.12.0)\n", + "Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.7.0) (2.8.2)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.9/dist-packages (from rich->mmengine>=0.7.0) (2.14.0)\n", + "Requirement already satisfied: markdown-it-py<3.0.0,>=2.2.0 in /usr/local/lib/python3.9/dist-packages (from rich->mmengine>=0.7.0) (2.2.0)\n", + "Requirement already satisfied: zipp>=3.1.0 in /usr/local/lib/python3.9/dist-packages (from importlib-resources>=3.2.0->matplotlib->mmengine>=0.7.0) (3.15.0)\n", + "Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.9/dist-packages (from markdown-it-py<3.0.0,>=2.2.0->rich->mmengine>=0.7.0) (0.1.2)\n", + "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.9/dist-packages (from python-dateutil>=2.7->matplotlib->mmengine>=0.7.0) (1.16.0)\n", + "Installing collected packages: yapf, addict, mmengine\n", + "/usr/local/lib/python3.9/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "/usr/local/lib/python3.9/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "/usr/local/lib/python3.9/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "/usr/local/lib/python3.9/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "Successfully installed addict-2.4.0 mmengine-0.7.2 yapf-0.32.0\n", + "/usr/local/lib/python3.9/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n", + "Looking in links: https://download.openmmlab.com/mmcv/dist/cu118/torch2.0.0/index.html\n", + "Collecting mmcv>=2.0.0rc4\n", + " Downloading https://download.openmmlab.com/mmcv/dist/cu118/torch2.0.0/mmcv-2.0.0-cp39-cp39-manylinux1_x86_64.whl (74.4 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m74.4/74.4 MB\u001b[0m \u001b[31m12.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: mmengine>=0.2.0 in /usr/local/lib/python3.9/dist-packages (from mmcv>=2.0.0rc4) (0.7.2)\n", + "Requirement already satisfied: Pillow in /usr/local/lib/python3.9/dist-packages (from mmcv>=2.0.0rc4) (8.4.0)\n", + "Requirement already satisfied: addict in /usr/local/lib/python3.9/dist-packages (from mmcv>=2.0.0rc4) (2.4.0)\n", + "Requirement already satisfied: pyyaml in /usr/local/lib/python3.9/dist-packages (from mmcv>=2.0.0rc4) (6.0)\n", + "Requirement already satisfied: numpy in /usr/local/lib/python3.9/dist-packages (from mmcv>=2.0.0rc4) (1.22.4)\n", + "Requirement already satisfied: packaging in /usr/local/lib/python3.9/dist-packages (from mmcv>=2.0.0rc4) (23.0)\n", + "Requirement already satisfied: yapf in /usr/local/lib/python3.9/dist-packages (from mmcv>=2.0.0rc4) (0.32.0)\n", + "Requirement already satisfied: opencv-python>=3 in /usr/local/lib/python3.9/dist-packages (from mmcv>=2.0.0rc4) (4.7.0.72)\n", + "Requirement already satisfied: termcolor in /usr/local/lib/python3.9/dist-packages (from mmengine>=0.2.0->mmcv>=2.0.0rc4) (2.2.0)\n", + "Requirement already satisfied: matplotlib in /usr/local/lib/python3.9/dist-packages (from mmengine>=0.2.0->mmcv>=2.0.0rc4) (3.7.1)\n", + "Requirement already satisfied: rich in /usr/local/lib/python3.9/dist-packages (from mmengine>=0.2.0->mmcv>=2.0.0rc4) (13.3.3)\n", + "Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.2.0->mmcv>=2.0.0rc4) (3.0.9)\n", + "Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.2.0->mmcv>=2.0.0rc4) (1.0.7)\n", + "Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.2.0->mmcv>=2.0.0rc4) (4.39.3)\n", + "Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.2.0->mmcv>=2.0.0rc4) (0.11.0)\n", + "Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.2.0->mmcv>=2.0.0rc4) (2.8.2)\n", + "Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.2.0->mmcv>=2.0.0rc4) (1.4.4)\n", + "Requirement already satisfied: importlib-resources>=3.2.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmengine>=0.2.0->mmcv>=2.0.0rc4) (5.12.0)\n", + "Requirement already satisfied: markdown-it-py<3.0.0,>=2.2.0 in /usr/local/lib/python3.9/dist-packages (from rich->mmengine>=0.2.0->mmcv>=2.0.0rc4) (2.2.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.9/dist-packages (from rich->mmengine>=0.2.0->mmcv>=2.0.0rc4) (2.14.0)\n", + "Requirement already satisfied: zipp>=3.1.0 in /usr/local/lib/python3.9/dist-packages (from importlib-resources>=3.2.0->matplotlib->mmengine>=0.2.0->mmcv>=2.0.0rc4) (3.15.0)\n", + "Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.9/dist-packages (from markdown-it-py<3.0.0,>=2.2.0->rich->mmengine>=0.2.0->mmcv>=2.0.0rc4) (0.1.2)\n", + "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.9/dist-packages (from python-dateutil>=2.7->matplotlib->mmengine>=0.2.0->mmcv>=2.0.0rc4) (1.16.0)\n", + "Installing collected packages: mmcv\n", + "/usr/local/lib/python3.9/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "/usr/local/lib/python3.9/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n", + " warnings.warn(\n", + "Successfully installed mmcv-2.0.0\n", + "Cloning into 'mmdetection'...\n", + "remote: Enumerating objects: 35338, done.\u001b[K\n", + "remote: Counting objects: 100% (31/31), done.\u001b[K\n", + "remote: Compressing objects: 100% (31/31), done.\u001b[K\n", + "remote: Total 35338 (delta 2), reused 8 (delta 0), pack-reused 35307\u001b[K\n", + "Receiving objects: 100% (35338/35338), 47.30 MiB | 16.88 MiB/s, done.\n", + "Resolving deltas: 100% (24919/24919), done.\n", + "/content/mmdetection\n", + "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n", + "Obtaining file:///content/mmdetection\n", + " Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n", + "Requirement already satisfied: matplotlib in /usr/local/lib/python3.9/dist-packages (from mmdet==3.0.0) (3.7.1)\n", + "Requirement already satisfied: numpy in /usr/local/lib/python3.9/dist-packages (from mmdet==3.0.0) (1.22.4)\n", + "Requirement already satisfied: pycocotools in /usr/local/lib/python3.9/dist-packages (from mmdet==3.0.0) (2.0.6)\n", + "Requirement already satisfied: scipy in /usr/local/lib/python3.9/dist-packages (from mmdet==3.0.0) (1.10.1)\n", + "Requirement already satisfied: shapely in /usr/local/lib/python3.9/dist-packages (from mmdet==3.0.0) (2.0.1)\n", + "Requirement already satisfied: six in /usr/local/lib/python3.9/dist-packages (from mmdet==3.0.0) (1.16.0)\n", + "Collecting terminaltables\n", + " Downloading terminaltables-3.1.10-py2.py3-none-any.whl (15 kB)\n", + "Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmdet==3.0.0) (1.4.4)\n", + "Requirement already satisfied: importlib-resources>=3.2.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmdet==3.0.0) (5.12.0)\n", + "Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmdet==3.0.0) (3.0.9)\n", + "Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmdet==3.0.0) (1.0.7)\n", + "Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmdet==3.0.0) (0.11.0)\n", + "Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmdet==3.0.0) (2.8.2)\n", + "Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmdet==3.0.0) (8.4.0)\n", + "Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmdet==3.0.0) (23.0)\n", + "Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.9/dist-packages (from matplotlib->mmdet==3.0.0) (4.39.3)\n", + "Requirement already satisfied: zipp>=3.1.0 in /usr/local/lib/python3.9/dist-packages (from importlib-resources>=3.2.0->matplotlib->mmdet==3.0.0) (3.15.0)\n", + "Installing collected packages: terminaltables, mmdet\n", + " Running setup.py develop for mmdet\n", + "Successfully installed mmdet-3.0.0 terminaltables-3.1.10\n" + ] + } + ], + "source": [ + "# install dependencies: (use cu111 because colab has CUDA 11.1)\n", + "%pip install -U openmim\n", + "!mim install \"mmengine>=0.7.0\"\n", + "!mim install \"mmcv>=2.0.0rc4\"\n", + "\n", + "# Install mmdetection\n", + "!rm -rf mmdetection\n", + "!git clone https://github.com/open-mmlab/mmdetection.git\n", + "%cd mmdetection\n", + "\n", + "%pip install -e ." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "_YeUiqAoCaoV", + "outputId": "98b02135-08f8-4142-9b59-80056f29192d" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "sys.platform: linux\n", + "Python: 3.9.16 (main, Dec 7 2022, 01:11:51) [GCC 9.4.0]\n", + "CUDA available: True\n", + "numpy_random_seed: 2147483648\n", + "GPU 0: Tesla T4\n", + "CUDA_HOME: /usr/local/cuda\n", + "NVCC: Cuda compilation tools, release 11.8, V11.8.89\n", + "GCC: x86_64-linux-gnu-gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0\n", + "PyTorch: 2.0.0+cu118\n", + "PyTorch compiling details: PyTorch built with:\n", + " - GCC 9.3\n", + " - C++ Version: 201703\n", + " - Intel(R) oneAPI Math Kernel Library Version 2022.2-Product Build 20220804 for Intel(R) 64 architecture applications\n", + " - Intel(R) MKL-DNN v2.7.3 (Git Hash 6dbeffbae1f23cbbeae17adb7b5b13f1f37c080e)\n", + " - OpenMP 201511 (a.k.a. OpenMP 4.5)\n", + " - LAPACK is enabled (usually provided by MKL)\n", + " - NNPACK is enabled\n", + " - CPU capability usage: AVX2\n", + " - CUDA Runtime 11.8\n", + " - NVCC architecture flags: -gencode;arch=compute_37,code=sm_37;-gencode;arch=compute_50,code=sm_50;-gencode;arch=compute_60,code=sm_60;-gencode;arch=compute_70,code=sm_70;-gencode;arch=compute_75,code=sm_75;-gencode;arch=compute_80,code=sm_80;-gencode;arch=compute_86,code=sm_86;-gencode;arch=compute_90,code=sm_90\n", + " - CuDNN 8.7\n", + " - Magma 2.6.1\n", + " - Build settings: BLAS_INFO=mkl, BUILD_TYPE=Release, CUDA_VERSION=11.8, CUDNN_VERSION=8.7.0, CXX_COMPILER=/opt/rh/devtoolset-9/root/usr/bin/c++, CXX_FLAGS= -D_GLIBCXX_USE_CXX11_ABI=0 -fabi-version=11 -Wno-deprecated -fvisibility-inlines-hidden -DUSE_PTHREADPOOL -DNDEBUG -DUSE_KINETO -DLIBKINETO_NOROCTRACER -DUSE_FBGEMM -DUSE_QNNPACK -DUSE_PYTORCH_QNNPACK -DUSE_XNNPACK -DSYMBOLICATE_MOBILE_DEBUG_HANDLE -O2 -fPIC -Wall -Wextra -Werror=return-type -Werror=non-virtual-dtor -Werror=bool-operation -Wnarrowing -Wno-missing-field-initializers -Wno-type-limits -Wno-array-bounds -Wno-unknown-pragmas -Wunused-local-typedefs -Wno-unused-parameter -Wno-unused-function -Wno-unused-result -Wno-strict-overflow -Wno-strict-aliasing -Wno-error=deprecated-declarations -Wno-stringop-overflow -Wno-psabi -Wno-error=pedantic -Wno-error=redundant-decls -Wno-error=old-style-cast -fdiagnostics-color=always -faligned-new -Wno-unused-but-set-variable -Wno-maybe-uninitialized -fno-math-errno -fno-trapping-math -Werror=format -Werror=cast-function-type -Wno-stringop-overflow, LAPACK_INFO=mkl, PERF_WITH_AVX=1, PERF_WITH_AVX2=1, PERF_WITH_AVX512=1, TORCH_DISABLE_GPU_ASSERTS=ON, TORCH_VERSION=2.0.0, USE_CUDA=ON, USE_CUDNN=ON, USE_EXCEPTION_PTR=1, USE_GFLAGS=OFF, USE_GLOG=OFF, USE_MKL=ON, USE_MKLDNN=ON, USE_MPI=OFF, USE_NCCL=1, USE_NNPACK=ON, USE_OPENMP=ON, USE_ROCM=OFF, \n", + "\n", + "TorchVision: 0.15.1+cu118\n", + "OpenCV: 4.7.0\n", + "MMEngine: 0.7.2\n", + "MMDetection: 3.0.0+ecac3a7\n" + ] + } + ], + "source": [ + "from mmengine.utils import get_git_hash\n", + "from mmengine.utils.dl_utils import collect_env as collect_base_env\n", + "\n", + "import mmdet\n", + "\n", + "\n", + "def collect_env():\n", + " \"\"\"Collect the information of the running environments.\"\"\"\n", + " env_info = collect_base_env()\n", + " env_info['MMDetection'] = f'{mmdet.__version__}+{get_git_hash()[:7]}'\n", + " return env_info\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " for name, val in collect_env().items():\n", + " print(f'{name}: {val}')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gi9zw03oM4CH" + }, + "source": [ + "## Perform Inference with An MMDet detector\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "s99mDGBG1S1z" + }, + "source": [ + "### An efficient Real-Time one-stage detector\n", + "\n", + "In this tutorial, we use RTMDet, an efficient Real-Time one-stage detector as an example.\n", + "\n", + "The high-level architecture of RTMDet is shown in the following picture. More details can be found in the [paper](https://arxiv.org/abs/2212.07784).\n", + "\n", + "![RTMDet](https://user-images.githubusercontent.com/27466624/225922103-404064c1-3cb0-4ab5-9388-79f9517dcdb0.jpg)\n", + "\n", + "To obtain a more efficient model architecture, MMDetection explore an architecture that has compatible capacities in the backbone and neck, constructed by a basic building block that consists of large-kernel depth-wise convolutions. MMDetection further introduce soft labels when calculating matching costs in the dynamic label assignment to improve accuracy. Together with better training techniques, the resulting object detector, named RTMDet, achieves 52.8% AP on COCO with 300+ FPS on an NVIDIA 3090 GPU, outperforming the current mainstream industrial detectors. RTMDet achieves the best parameter-accuracy trade-off with tiny/small/medium/large/extra-large model sizes for various application scenarios, and obtains new state-of-the-art performance on real-time instance segmentation and rotated object detection. We hope the experimental results can provide new insights into designing versatile real-time object detectors for many object recognition tasks.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "j4doHX4exvS1", + "outputId": "9eb9d460-7e3f-4bbc-f9e6-5823773375d3" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "processing rtmdet_tiny_8xb32-300e_coco...\n", + "\u001b[2Kdownloading \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m54.9/54.9 MiB\u001b[0m \u001b[31m54.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[32mSuccessfully downloaded rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth to /content/mmdetection/checkpoints\u001b[0m\n", + "\u001b[32mSuccessfully dumped rtmdet_tiny_8xb32-300e_coco.py to /content/mmdetection/checkpoints\u001b[0m\n" + ] + } + ], + "source": [ + "# We download the pre-trained checkpoints for inference and finetuning.\n", + "!mkdir ./checkpoints\n", + "!mim download mmdet --config rtmdet_tiny_8xb32-300e_coco --dest ./checkpoints" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fLgFRMtP91ue" + }, + "source": [ + "### Inference the detector\n", + "\n", + "Since the model is successfully created and loaded, let's see how good it is. We use the high-level API `DetInferencer` implemented in the MMDetection. This API is created to ease the inference process. The details of the codes can be found [here](https://github.com/open-mmlab/mmdetection/blob/dev-3.x/mmdet/apis/det_inferencer.py)." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 244, + "referenced_widgets": [ + "b1188048a1f04c2fa77c0d3829da39bd", + "534561e3c4804bae96a30d44493d701d" + ] + }, + "id": "Wi6DRpsQPEmV", + "outputId": "0b0a14bc-fd10-4ec4-a585-e1ddefc33973" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Loads checkpoint by local backend from path: ./checkpoints/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth\n", + "The model and loaded state dict do not match exactly\n", + "\n", + "unexpected key in source state_dict: data_preprocessor.mean, data_preprocessor.std\n", + "\n", + "04/17 10:28:19 - mmengine - WARNING - Failed to search registry with scope \"mmdet\" in the \"function\" registry tree. As a workaround, the current \"function\" registry in \"mmengine\" is used to build instance. This may cause unexpected failure when running the built modules. Please check whether \"mmdet\" is a correct scope, or whether the registry is initialized.\n", + "04/17 10:28:19 - mmengine - WARNING - `Visualizer` backend is not initialized because save_dir is None.\n" + ] + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "Output()" + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "b1188048a1f04c2fa77c0d3829da39bd" + } + }, + "metadata": {} + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "/usr/local/lib/python3.9/dist-packages/torch/functional.py:504: UserWarning: torch.meshgrid: in an upcoming \n", + "release, it will be required to pass the indexing argument. (Triggered internally at \n", + "../aten/src/ATen/native/TensorShape.cpp:3483.)\n", + " return _VF.meshgrid(tensors, **kwargs) # type: ignore[attr-defined]\n" + ], + "text/html": [ + "
    /usr/local/lib/python3.9/dist-packages/torch/functional.py:504: UserWarning: torch.meshgrid: in an upcoming \n",
    +              "release, it will be required to pass the indexing argument. (Triggered internally at \n",
    +              "../aten/src/ATen/native/TensorShape.cpp:3483.)\n",
    +              "  return _VF.meshgrid(tensors, **kwargs)  # type: ignore[attr-defined]\n",
    +              "
    \n" + ] + }, + "metadata": {} + }, + { + "output_type": "display_data", + "data": { + "text/plain": [], + "text/html": [ + "
    \n"
    +            ]
    +          },
    +          "metadata": {}
    +        },
    +        {
    +          "output_type": "display_data",
    +          "data": {
    +            "text/plain": [
    +              "\n"
    +            ],
    +            "text/html": [
    +              "
    \n",
    +              "
    \n" + ] + }, + "metadata": {} + } + ], + "source": [ + "from mmdet.apis import DetInferencer\n", + "\n", + "# Choose to use a config\n", + "model_name = 'rtmdet_tiny_8xb32-300e_coco'\n", + "# Setup a checkpoint file to load\n", + "checkpoint = './checkpoints/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth'\n", + "\n", + "# Set the device to be used for evaluation\n", + "device = 'cuda:0'\n", + "\n", + "# Initialize the DetInferencer\n", + "inferencer = DetInferencer(model_name, checkpoint, device)\n", + "\n", + "# Use the detector to do inference\n", + "img = './demo/demo.jpg'\n", + "result = inferencer(img, out_dir='./output')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "m6a8T4goU8Sq", + "outputId": "68005045-d741-4f53-b59b-16881337bebf" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "\u001b[1m{\u001b[0m\n", + "\u001b[2;32m│ \u001b[0m\u001b[32m'predictions'\u001b[0m: \u001b[1m[\u001b[0m\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m{\u001b[0m\n", + "\u001b[2;32m│ │ │ \u001b[0m\u001b[32m'bboxes'\u001b[0m: \u001b[1m[\u001b[0m\n", + "\u001b[2;32m│ │ │ │ \u001b[0m\u001b[1m[\u001b[0m\u001b[1;36m221.37188720703125\u001b[0m, \u001b[1;36m176.12808227539062\u001b[0m, \u001b[1;36m456.25811767578125\u001b[0m, \u001b[1;36m383.2401428222656\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ │ │ \u001b[0m\u001b[1m[\u001b[0m\u001b[1;36m295.3505859375\u001b[0m, \u001b[1;36m117.18350219726562\u001b[0m, \u001b[1;36m378.571533203125\u001b[0m, \u001b[1;36m150.27117919921875\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ │ │ \u001b[0m\u001b[1m[\u001b[0m\u001b[1;36m190.57350158691406\u001b[0m, \u001b[1;36m109.70985412597656\u001b[0m, \u001b[1;36m299.52215576171875\u001b[0m, \u001b[1;36m155.0396270751953\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ │ │ \u001b[0m\u001b[1m[\u001b[0m\u001b[1;36m431.36944580078125\u001b[0m, \u001b[1;36m104.98468780517578\u001b[0m, \u001b[1;36m484.879150390625\u001b[0m, \u001b[1;36m131.94033813476562\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ │ │ \u001b[0m\u001b[33m...\u001b[0m +\u001b[1;36m296\u001b[0m\n", + "\u001b[2;32m│ │ │ \u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ │ \u001b[0m\u001b[32m'labels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[1;36m13\u001b[0m, \u001b[1;36m2\u001b[0m, \u001b[1;36m2\u001b[0m, \u001b[1;36m2\u001b[0m, \u001b[33m...\u001b[0m +\u001b[1;36m296\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ │ \u001b[0m\u001b[32m'scores'\u001b[0m: \u001b[1m[\u001b[0m\u001b[1;36m0.8703235387802124\u001b[0m, \u001b[1;36m0.7677358984947205\u001b[0m, \u001b[1;36m0.7427828311920166\u001b[0m, \u001b[1;36m0.6994596123695374\u001b[0m, \u001b[33m...\u001b[0m +\u001b[1;36m296\u001b[0m\u001b[1m]\u001b[0m\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m}\u001b[0m\n", + "\u001b[2;32m│ \u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ \u001b[0m\u001b[32m'visualization'\u001b[0m: \u001b[1m[\u001b[0m\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1;35marray\u001b[0m\u001b[1m(\u001b[0m\u001b[1m[\u001b[0m\u001b[1m[\u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m28\u001b[0m, \u001b[1;36m48\u001b[0m, \u001b[1;36m13\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m37\u001b[0m, \u001b[1;36m63\u001b[0m, \u001b[1;36m28\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m30\u001b[0m, \u001b[1;36m64\u001b[0m, \u001b[1;36m27\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[33m...\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m23\u001b[0m, \u001b[1;36m47\u001b[0m, \u001b[1;36m31\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m31\u001b[0m, \u001b[1;36m67\u001b[0m, \u001b[1;36m31\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m54\u001b[0m, \u001b[1;36m92\u001b[0m, \u001b[1;36m17\u001b[0m\u001b[1m]\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ \u001b[0m\n", + "\u001b[2;32m│ \u001b[0m\u001b[1m[\u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m23\u001b[0m, \u001b[1;36m42\u001b[0m, \u001b[1;36m0\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m25\u001b[0m, \u001b[1;36m50\u001b[0m, \u001b[1;36m8\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m30\u001b[0m, \u001b[1;36m62\u001b[0m, \u001b[1;36m21\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[33m...\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m92\u001b[0m, \u001b[1;36m114\u001b[0m, \u001b[1;36m102\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m16\u001b[0m, \u001b[1;36m53\u001b[0m, \u001b[1;36m12\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m44\u001b[0m, \u001b[1;36m75\u001b[0m, \u001b[1;36m16\u001b[0m\u001b[1m]\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ \u001b[0m\n", + "\u001b[2;32m│ \u001b[0m\u001b[1m[\u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m20\u001b[0m, \u001b[1;36m50\u001b[0m, \u001b[1;36m0\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m25\u001b[0m, \u001b[1;36m59\u001b[0m, \u001b[1;36m8\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m29\u001b[0m, \u001b[1;36m66\u001b[0m, \u001b[1;36m23\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[33m...\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m47\u001b[0m, \u001b[1;36m70\u001b[0m, \u001b[1;36m44\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m29\u001b[0m, \u001b[1;36m60\u001b[0m, \u001b[1;36m18\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m54\u001b[0m, \u001b[1;36m77\u001b[0m, \u001b[1;36m31\u001b[0m\u001b[1m]\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ \u001b[0m\n", + "\u001b[2;32m│ \u001b[0m\u001b[33m...\u001b[0m,\n", + "\u001b[2;32m│ \u001b[0m\n", + "\u001b[2;32m│ \u001b[0m\u001b[1m[\u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m63\u001b[0m, \u001b[1;36m68\u001b[0m, \u001b[1;36m45\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m58\u001b[0m, \u001b[1;36m66\u001b[0m, \u001b[1;36m27\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m74\u001b[0m, \u001b[1;36m84\u001b[0m, \u001b[1;36m49\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[33m...\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m32\u001b[0m, \u001b[1;36m46\u001b[0m, \u001b[1;36m23\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m56\u001b[0m, \u001b[1;36m76\u001b[0m, \u001b[1;36m39\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m31\u001b[0m, \u001b[1;36m47\u001b[0m, \u001b[1;36m18\u001b[0m\u001b[1m]\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ \u001b[0m\n", + "\u001b[2;32m│ \u001b[0m\u001b[1m[\u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m60\u001b[0m, \u001b[1;36m66\u001b[0m, \u001b[1;36m40\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m45\u001b[0m, \u001b[1;36m55\u001b[0m, \u001b[1;36m18\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m83\u001b[0m, \u001b[1;36m92\u001b[0m, \u001b[1;36m61\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[33m...\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m63\u001b[0m, \u001b[1;36m77\u001b[0m, \u001b[1;36m54\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m47\u001b[0m, \u001b[1;36m67\u001b[0m, \u001b[1;36m30\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m35\u001b[0m, \u001b[1;36m52\u001b[0m, \u001b[1;36m20\u001b[0m\u001b[1m]\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ \u001b[0m\n", + "\u001b[2;32m│ \u001b[0m\u001b[1m[\u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m64\u001b[0m, \u001b[1;36m70\u001b[0m, \u001b[1;36m42\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m85\u001b[0m, \u001b[1;36m95\u001b[0m, \u001b[1;36m60\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m66\u001b[0m, \u001b[1;36m75\u001b[0m, \u001b[1;36m48\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[33m...\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m37\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m28\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m59\u001b[0m, \u001b[1;36m79\u001b[0m, \u001b[1;36m42\u001b[0m\u001b[1m]\u001b[0m,\n", + "\u001b[2;32m│ │ \u001b[0m\u001b[1m[\u001b[0m \u001b[1;36m44\u001b[0m, \u001b[1;36m61\u001b[0m, \u001b[1;36m29\u001b[0m\u001b[1m]\u001b[0m\u001b[1m]\u001b[0m\u001b[1m]\u001b[0m, \u001b[33mdtype\u001b[0m=\u001b[35muint8\u001b[0m\u001b[1m)\u001b[0m\n", + "\u001b[2;32m│ \u001b[0m\u001b[1m]\u001b[0m\n", + "\u001b[1m}\u001b[0m\n" + ], + "text/html": [ + "
    {\n",
    +              "'predictions': [\n",
    +              "│   │   {\n",
    +              "│   │   │   'bboxes': [\n",
    +              "│   │   │   │   [221.37188720703125, 176.12808227539062, 456.25811767578125, 383.2401428222656],\n",
    +              "│   │   │   │   [295.3505859375, 117.18350219726562, 378.571533203125, 150.27117919921875],\n",
    +              "│   │   │   │   [190.57350158691406, 109.70985412597656, 299.52215576171875, 155.0396270751953],\n",
    +              "│   │   │   │   [431.36944580078125, 104.98468780517578, 484.879150390625, 131.94033813476562],\n",
    +              "│   │   │   │   ... +296\n",
    +              "│   │   │   ],\n",
    +              "│   │   │   'labels': [13, 2, 2, 2, ... +296],\n",
    +              "│   │   │   'scores': [0.8703235387802124, 0.7677358984947205, 0.7427828311920166, 0.6994596123695374, ... +296]\n",
    +              "│   │   }\n",
    +              "],\n",
    +              "'visualization': [\n",
    +              "│   │   array([[[ 28,  48,  13],\n",
    +              "│   │   [ 37,  63,  28],\n",
    +              "│   │   [ 30,  64,  27],\n",
    +              "│   │   ...,\n",
    +              "│   │   [ 23,  47,  31],\n",
    +              "│   │   [ 31,  67,  31],\n",
    +              "│   │   [ 54,  92,  17]],\n",
    +              "\n",
    +              "[[ 23,  42,   0],\n",
    +              "│   │   [ 25,  50,   8],\n",
    +              "│   │   [ 30,  62,  21],\n",
    +              "│   │   ...,\n",
    +              "│   │   [ 92, 114, 102],\n",
    +              "│   │   [ 16,  53,  12],\n",
    +              "│   │   [ 44,  75,  16]],\n",
    +              "\n",
    +              "[[ 20,  50,   0],\n",
    +              "│   │   [ 25,  59,   8],\n",
    +              "│   │   [ 29,  66,  23],\n",
    +              "│   │   ...,\n",
    +              "│   │   [ 47,  70,  44],\n",
    +              "│   │   [ 29,  60,  18],\n",
    +              "│   │   [ 54,  77,  31]],\n",
    +              "\n",
    +              "...,\n",
    +              "\n",
    +              "[[ 63,  68,  45],\n",
    +              "│   │   [ 58,  66,  27],\n",
    +              "│   │   [ 74,  84,  49],\n",
    +              "│   │   ...,\n",
    +              "│   │   [ 32,  46,  23],\n",
    +              "│   │   [ 56,  76,  39],\n",
    +              "│   │   [ 31,  47,  18]],\n",
    +              "\n",
    +              "[[ 60,  66,  40],\n",
    +              "│   │   [ 45,  55,  18],\n",
    +              "│   │   [ 83,  92,  61],\n",
    +              "│   │   ...,\n",
    +              "│   │   [ 63,  77,  54],\n",
    +              "│   │   [ 47,  67,  30],\n",
    +              "│   │   [ 35,  52,  20]],\n",
    +              "\n",
    +              "[[ 64,  70,  42],\n",
    +              "│   │   [ 85,  95,  60],\n",
    +              "│   │   [ 66,  75,  48],\n",
    +              "│   │   ...,\n",
    +              "│   │   [ 37,  51,  28],\n",
    +              "│   │   [ 59,  79,  42],\n",
    +              "│   │   [ 44,  61,  29]]], dtype=uint8)\n",
    +              "]\n",
    +              "}\n",
    +              "
    \n" + ] + }, + "metadata": {} + } + ], + "source": [ + "# Show the structure of result dict\n", + "from rich.pretty import pprint\n", + "pprint(result, max_length=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 444 + }, + "id": "UsJU5D-QPX8L", + "outputId": "766f3211-301b-4a89-ae9d-898a92181c67" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "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PC65ydJHW8N6IyiKqh63MGYsIMJgRgzQ1ogCYSgywRTQVZHavahBG3EwMLbM2ZXLF+IgeP2brzsNUE2GP/D/+NE4pmFsMEa//3tffnh758pjTw6GB8ls6Pg4HReXLz3x7rU3prMeIUqcWlpMRkNr9Yuf+IwixGBna+vhctOvBc0KZ8RgABBXGkKojSZMIASApQh6xigANYAKEwipnY60MmXcRecfi/e+ciNwQ0CsrdqYzaiffvzjj0OgF9ud472jr37xjcBpfu2r12ttFDRR7/ignArPpTxLJA54VQUx2t4/3O8dvvxdL3LFl08t7588ure9femxc624u320/2M/+hOvfv0bntfQ08QaCACw2lj4YRH3HAPrMDZP8VPGsEuJ744Go/67b2/vbjsQYoIqo+JWe3qw77puJaQFmqu8qqo4jjmX+7t7m2fPRKuLezv7o15/6dQKN8ogwKVwXR9jnMyyKIq01uPJjDHmMG/u20ySxGGh1awSFhm9uNgVWkSBz8vcj5jRcjaeMEzLShCCG36nUnp5La41G1mWDU8mRVHcubulIfxzf+G/oucuyULevX4rSQcGINetWwyX108dnoyeePqFna2sXV/f3d1FwlTjIvB913fneRhr9cry0rW33/na6+85LsGUAAAa9U5VmskohZYYiV3XwQAqXWGMubQnxxOCvVoH9fvpkEyVVVrLS1cu904mWpuVtQuPtm+Nxsn62sY7b7wHLanX4t7uEVp2z54782DrPsY4SfIwDMbTaeDTTr3Z700w8iBmghtjZVlVccNVFhDlrq2eWjnb2dv9wPZn77z9zunNx1975auPHtyLm2Gu9MEgtdgBqtpY2jgcHY3SZHmpNa2ElCZLq8CFuyeJos3jQYloOMvknb3t8089VkmepGmalNDAwCVGK0ggQLbMEmC1NIDFobW6KMZ114FSJELUlgKRe9BKqVNhwK07O7UoliJdXV3M00JKef/REZfl537qx+/fv1uUhsD2rUe31zc333v/zunTp+vJ7A+/tL+42ppNp2UOPvbCRQzw2urFgqvBLJ0ORlcvXBmOehpIREFWpK6CGKKF5aWy0oUaM9c3ACCIZCUwonleMo/FIUV49u61G52Ov3lm8eBgd6G7Ljn5lk9/7w//8A//zb/1Uz/w/Z/9xV/6vNUmDJxkmu7uPigLWavFBui19cWDg70wcCpeMEodhvv9ycapU1HYyIuUFyBeWWAMBYGzs7Pz/vvva221VAjZ6azY3nlQi8PAj1qt1rVr18JGKBXf3t46Oep5nosQWlpYnNfREMKqMseIUIp5lbsOGvb7y8vLtSBWXHNejoYTSt1mp33uwrnJZASR3d05qMXB4lJHaXm8P0SEQcQQQq5HgCau6yqurbFKaaUgIQQCpOScrO5BCIu84sLU6sxxiAEAIVLkFaVEKWWs5Zwzh1itIcRCqrLkmBI/9KtKZGXRaCDG3CiK0jwri6rTCfOqbLQ7WZbBPHcc5+uvvh7X/DzPl5eXfccdT2fjyQhjXJalMWruzCrLknNdr/sYwHa7qbVOktmHTEFhIMT1RpNznuZ5FAXUA2ENaWMgMjiRoVdn1IMIhAEZTY7SSWmBLAqzsLzY6/VarVYQOlJV48EwCKGyVVgLy7KUGklhVle609mgzKTjIoL8oiwsEFEIw4CWRdXqLh/sn1iDKKXHoOcHDmKCegASa4D2wmA8Hm596aS7jFlAeF4lSQKg2tnKP/Hxjx/tnhwf7Uc1N5tp4gbQIoxhoSZSKa2AA1l/OgtDPykmbgTaTa93UhICRAXyIqMUUupwiO7ffXDv3h2IALLAoY612FrpBKyz0AWInAyOABajUVlx67okzUrrATekm6dXYSUPD8bQWmCQBLZ3OKYQSl61mj6pdSaTCQCgzAuIiZTSYe6c7m6txhju7e07DvMDVlWV6zqcG6lVJwq11S5ysiR1MPZ8ZhGGhlhjXNdJstwAAAlSQChdBKEHAEjTWRAyRCCvCgzgwmJn5+B+RDFGbHAynU1OHMcpBa9yx/UdJbLFpdBhkNoyG9rWYmP1VHgweDAccaUBomFcX5xOjp949ool4sHdk+WVhW/51pf++t/40ZPjKSM0bjXffuv9/+Hn/rvf+73ffvutOwurnVdffRVADqBFPkX1yHEZoq7jOv6jre2vfu2VBw8efe3L38hS4VJfSwMMEKWoCh4EHuclJNZAYZE0UAFAIHSNpRCiSpRFkd269SDPq8997i90F4P++E5YM73xbRbmUYAunDl/eHi8sN751s9+8mR6sriy/F3f/dknnnyaEVeX1mrGDbLWOixwWHiwN/n85/+DArzZCd9857V33rkzOMkRpIPBaDqd/vy//rfX3novdBpKSmvMPI8OLJoHyeek9aIoEMGYkoyXxHcHs0lzsRm16yfjYcKLXMjRbJYWhcUYEpIXVVRvLC4u+45vlS7zollvHB8ejYZ9YwV1yDidPNjfAhRZCHhZtBpN16H1Rs0LfG1NmmdFUWKEKCFamyIXt28/dDy31ekcnfS5MH4YamQm6Zi5rN5pMS+I4thCMMnTrJgBK5HRVoHhpMilNQD6bvClP/rSydHhcHL06e968YnnTqtqZsGkSlU6Km+8d5cX6vz5s7sHDxGWxIG1ZiPjZpLwSpNRWixtrB9Nj9+48V6r1SSEGGUxdga9yd5Oj5cqCuMknUpVScUxplWpRKERxFJK3w02N9YhxFUmIXAf3N2fjctsqh7c2W34a8vt84ODvFtffe6JF11cg9xPZpXReNSfNpsNz2NVxRe6K1yYqio8L8gLUxXQ8/yKJ2HoI+jkhdHW3rr9/p07NyBynbDp1/1J0ktm/SpJHYBrjgesMVCzKDieDEvBscN645lF6Ed++M+14xaBrjTs7sNhJSjnqN5cuP3g4VH/5M6de91WPEo5Y8yBgGhOgdKwor7WyAgpeZX5AXUiWkrR7KxcvnglGwsKCIZIKUU9sn5qQwhBGVlbXfnMZ741y3mr2yC+96WvfUNopjSeTCaNBtawaC7V379z41d//dcWWyuzXh6w6MmnNy5ceWx9c/PWzRuiKl966aXVc5v9sugdn8RR6HqUUhrXmwAgLpSGJq+4hDrlBQupX/MsUI5DHccpiwIBHAWk1WxCoAEQJ8f7vuf963/9r37uZ3+myGdf+MIfxXH94oXHn3j82Xfefh9Zt1VfiGvtZtxYW16dB2SrymgjIYShz8q82ts77Pcmly4+fuH81YXF9uNPXG21WozS7/nu79rf39vbP261CRfVcDjs9/t7ewdRFItKKiF7Jz2CAcHA99zRaKi1RgBiiLSWEGqjeS32XZcpoe7fuQ8UWmivVHmBAcozfvnKY5UsiINancbHPvaxjY3TVVXF9fri6pIFqBLcQKt0SYlHCJuvr+b4zzljwXEc13XTbDYXel0Hcy4gxJQSCC2ARgn5YeGg4zDGpFbzCD5znbkjYZalcRzP4SFcSEpYrVZDlBkDkiShlFZSpWne7baMttaA6XR66eoVoYWFgLmOARYh1GzWtdYU4VoYNuI4CsOqKCejsRKSlxWwmjJw6tRanueYOt2lLvWQ1JUxquK6dzKejNRw3AdY5MXk6ORYSogg9b1YWTCZjjwft7oRc3C/P1QGnFo/DSyWAlDHxxhfvnqGy7LZagECqceIa2lgMDOYEqFxKcFkOnV9D1OECEyLbPfgiLmu6zMujcUGQgMwWFmNPS9K05RSkuf5dFxGtdZr33jLIKABnM14VULPrbea3X5/2G6t1KMVUeDJqJJSljzxQ/CjP/pnn3jiiaiGCcKuwxxH80qKStaiII68bqu+ttxutcIoZq4H/ShEkJZc9kdHncXouRee/dGf+LGnX3hmkmdORBEFjuMMBqOj46HnUoe5CDHfcatSGivWVtt5OZ6zt3wvnEMULl26VFZVt9s9e/ZsWYg5EdZaCwFyXVdKefnK+Weee7riXEnNCCMQC6kQIrwoEQCEojmTx3ddY8x0NkOUQWiFKOr1muDaaGiM0iarxKS70Op01rShAIFLly8Di5REcYykmkghn3r62X/4M//4e3/gz7qteFiMlGNOXz3bWnG66wuNbvzN175w+8aNG7dvzdJc2mIw3f5Pv/a/NxqNn/5vfkoouX5qaXVt8bd+87eNsfUGGU8HFpRxPTx7dhOee/ExZGwymQEAOusriRDTNAMGMiVBngbIAqmF0HmZBTFDVJWVcigRUvk+0woKQRD0KqG5rEpZtrrNWrNRSH7p6unKDJPiGKTtj336sazoLbY3WvHGf/rV34SO/LG/9Be//uXXb75/hxEImHjmmaeKsfzKb32lHXRzPQUAWYAxI5bp7mpzcW3x6PiEUi+ZFUdHx4xR3/UvnLm0t30yHaVqOLTIYowBshACTOCcKocRncPZISPUYVmVG2C5tEABVgvPrK4bKY72D6qqCuJaqYTSttFuJZPksceuHB8fTyYjN3CbzaZSU8bYs89/5Atf+iIFuB7EJzuHHnEdTNK8wJQUJbcIEoiUlARhTABEtOIaMwyxob6jjbUWp0VqrHr+2ceAgfs7Rwvt7nQyLsoEODZgvlEgy0TKZa3dKUQBAJAVh1o5Ho7b/mNPXth+9PCJC0+sL29k6cr+0Y28OhwMjx57/On33nvv/oNtP3TSKXe8OsKu49CsmnUXo86CV/KhmCBKnLWN0/fuP8yyLAg8x6Wj0aDZiJe6S1uP9hCko+HMcT5k2bs1sra2MhpNKPEn46zTXHQcRwnx7NPPGmmuvfNep9XMkkm72aKU5Ul5ONtNi+zCxdM7+w+73cWTo4kUoKwS10WU+Fo6eSZdDyqbJbPcDWrIrelygkEpkSXYjYm7udCVRTVK8/7xsMnI6lJ9WI2H2pSaMOwjDWWV60o999SzVy8+9bu//QcImaqclaJodDpZKbTWzUZEgDFWSMlL3LLFxMfSKlUKzeqoUMYYoAVABi8tdxgjZ8899vbr130/LPMsjJxpPukPJ14QQcB4mapKMAokh8wJADFB5E0m05df+sTTT1xOkxEJskLoikNe4t/+T7//7GMvpLPZD/3ID/z7X/h30yyvcnHx/IVB/+D02bVROotb3ezkSBl10h8Ggbu0sDQdT+rNem88dCLvsDdY7HbObmzubW33DvrNdl1JY7Sg1D05nlIGFpcD12XJrKwKHIVNpcR0NtjY2HjhhY+ur501Gpw6tTEd7gshLl66AoD5n/+//3Rr+353oXFwcOD7LqVOWQhjUBTWpVYf/ehHX3311Wk66nabL7/08t7eQb1eOzjYV7o6Pj48d+b8zZt3A7+2urLRbLdu3PhgeaV7cnJclanneUEQcc6l1HNrzJy9jBAy2kptEKbjUXbh6oVv+9bvePP9VxB2T4aTU5srJ/3dk4ODpx9/bvPU+Tffeh0i4Yf++tq5L/zhK5bzT3/iSWMmQtdu33o0HmQEM0wMJsYCXZWSUd8Yo42klGZpgTFGBDIHR35QcSGlFlzFjXpZ5pQhwUtCiDVQKe26vrGw0W7sHmzPKyXmFOg8zwEmjLE5c3gOWrLWllmBMajX4slk1mjE3EjGmKgqAExViFotsMoihOIonq/T0jQFABCCAQBZnjsOwMQtCl6r19sLzXEyKHkRBrW8kErOmz2A1hIhYg1BlvAiJdjRwEBctBd9SpwitwTR0WjgOF425d3FFYiUsunG6fU7t+8vrp7inE/HfUpRGDij0UxJ4DnB3HNXlvmcPA8gzvMCYxIylhcFdQGlwPFCiChEZJxOCq4YdXmlm80GMDLLEgwpzw2RUVAnUQMfHh4Z5Xq0oSruMKVtwjlodP3zly6Ox/27dw86cZzPhNal7xGjKCIYYSO4cl2fOUjIVBtkrGsBA0SWevrE0xcXFhbOXXpCZfzzP/+/Yow1MgAD3/NEKjzqK2WSMgEEtBp+mRa1KOScq8qZG9eNMRcuXSSEvf/++88///zW1tbB/uHchz9PAM9F4p/4yR/f2zu4c/ve3vZ+rVbnlex0OkfHBwhYC/T6qVMnJycAANcNp5PEDXxjlFGVBcZhrtZQKxvEzNiKK/78Rz7W648fPny4sbE6ngzzpCLYW12zjh/v7B7VGgsXL1395Cc//eZbr733wWsG5x/7xMuTqWjUu/2T/cnoJJ8lawtrr3z9Pa3AxStLTzzxhFb26qXH33332ptvvO26Ya/XA0BTBldWF4wxuzv9zc11dPnxq8R1KslHo9H+1h4ohUjz0ytr2BDXDQhlJa+U1hbivKiCIHAcCjEC6EPNVWtttEWQuK4DNHAcJ/DbFLTv3hwXif/U1U+xGnz48H4jbv7+H/zuz/zM/7T1YP/px1567WvvvPnOm6PJIJsV2biCyKZiUALNHB8gCxECACCI/+pf/avIweNkjClZXd0c9BPPjZNZPhyPgiB49umny7QkhGCA5yZ1A6yx0AKAELLGIAg/rH1GyAl94NC4G7vdJnWd/eOjk0EfYMT8AFMGEYOYTsYzrfXtm3eS6cz3vDIrd7a2ELCMseF44HhuZ3kxLdKgHq6trwRBYLUySlOGCQIAGquNtRohxMvKdZlSFdcVoiqse4jhMIprcbx3OLj/aK8S0PXriDHX84zmeVWWwhhEu92uqDJgJbBSiHLt9Mbiyur62hkrnWJmuYRJXrIQvfbma1/5+jePhsNvvPXGOJ3EbSequwurTS5mtTpVJm01XWgrJQqGGaV0PB73eseuQ+ck/SxLjNW+78RxWBQFAtR3Q0qp4+Ja7Mwz79YobIAplUzzvbsP/uKf+aFnLl599Q++Wo5yF9LHLl384L23D3YPjg77o9GkXvMrnvo+IdQQCuJ6yJh37uzlKIoIEytrUcHH1lo/Cq21RZYjRBgNPdqQklrsbO0f7Z8cra0sxy70mGK0qsU+JtiNPDd2hJV+GC202vtbe//lt36jLFOfEWztUruDLIijUAveCAKPEoqJ1NYChD2nlAJj7EEYkzDAxGiAoeOzQKRVmZZbO9vIc7KqZC4dDmZZkrgeBMhyoRYX1pudurUgjBzKoLFwllaI+F/50td//dd+6+joZDrSK4unTq2txJH7ub/+VwETChW//Gu/lGZVLWrGzWCaHhlY+aFXZOmDux+ks1m304mjAAG0t3dQlrLfH6wsLkyGg9hn0OjbN9+vhWxltdE7mUpRYYizWfWjf/HHrl55fDYtjo8nxijK7GB4RKjFBI5Go//867/5cz/7P967d2975+Hn//df+mf/7F/9k3/8z/7J//zPz1+8+PKnPmkBog75sEDXaoxhlqXHh729vb3v+Z7v8V2KIfrd3/2dmzc/uPbe25hYBC0lZGtrq16vRVGws/twf2+rFgVZkuZpThGVUo/HY6WEFzDqINclQlQIgbmlnyAMLWg0o3u371179910lkMI19dXIYF+GJw5e7bZrL/15jdD3z977gJh3sOdnYWVRdenXuBijKpKIEgghBZobSTnHAKMMZVSzu1XeV4QQuZTMucCIeAwCgCgDCfJFEDDORdSK62zrAIQS2Wk0Yhg3wviRmP+jErT1CI8p1isr6+7njevQoqiKIrCbndRKdXttuetAHP6B0IoDD2XMmutlmo+/UsuEIBzRqyU0vMYgC6ExHXdsixvXH/0+NXnT61cPDlKrGEQOEtLa/W4A6zjOXXPDblUiEipCmMsQqhWi7a3J0rgqgKdToeLElHAOT/pH5dVdtI/lFqVYoYpwJQCDAQQcSPEhLpeSBkTqtRWaqsAghUvEMKnT29uXjhDPVxUAGLGqJfM+GxaIeu06jFjBGMwmYyMFZ7LtIIQOBAXSdZzXBDUMMRVkR0TxgHkjLF6M0pnam+n9+STz/7nX/0Pvh+uLG9+53d+nDHEXEsJ4JXEmBqjqqoAAFFKIQQQas65qADnfJoOv/LqF3/pV3+x1EZjhCjLM22AqoxggbGsXN9cdjwU1uJaM56mmR+FFgIuxcLS4t/66b/9xGOPT8ejIqvev/be4f6BNbbiJaF4br6bl0G9/sZro0n/0d62FwYYEqPs/v6h0QAj5Lve0fFhVuZBECBgHZdaCKJaYC0k2AGAVFVFKGi32x/72EutZvsrX/wa56W1WimTJvm582fW1hdn2cT1sO/7Vc6/8Edf/q1f/7XAtRQXG2uL77zxzmyYjocDqRO/ZobjYa8/fvaFK08+t8Fl6fj4yaevXLv+1tkLZ0ajZG/7KAgCP3BOnTrVaS/NJlUtqgmu0FsfvD+aTQCCnhfwhB9v7fPp7HhrlwAMLLIQWAK4ko7jKQUqwZlDIIQUuko4wHgEEQNKCApjBWF0Nq2AxoQgXvLhcbHzoNTIZaQ+7hdGg8eeOhtFwe137n7591/hhYSATQaFh+MrV69CTwZ1kBQjgKGxinNOHIIpns5ms7wI4ua5s1eSWWkNqUUtXuhXXnll6+Ejq+QctmwhMMAqazSwACAECaU0y/J5aAFiBCBeWFl68tlnHI+VVV4KnpfcIgwR0XaOVrEUEd/1FRdKiNk0xQjV42ZVqYP9o/ffvwEsLAvuBaGQ+ui4V0kBALRaVxmHFjTiOiFIa1NVnFKKEHBcRimE0CZZOk2nGc9zLo57fUIdgNHte3fHk1lRqdgJA78GMFUI1ZtNK4VHELAKGD0tk8F4evP6g9vvP/qub/kzn/zE93/zrXulzZ5+8dnv+zM/AIg7nCbSwlrcCGuBsqUfEl6OFxfCVswubK6b0uzc65epdZ2wd3yYpEPKbFEmjLFWvQUAvn37HmMsSabGaGDsnMsYRswatdBaHI9Ga8udeuyeP7Oqq+oXf+EXjOYI8fv3rn/z9VcxgX7geBGFEAdBMJ4Mjbb7e8fLy8tay7jWOD4aaq3H00EhjppNn1KXUqpNARxRZqlWyHX9esP3AicM4uX2ekDJk4+fPXd+YePCpqW+sRQpXEwzwIhQsuSV5zsYmnYriOseRkornqYjaHgcOONhbzweTrOcW5iViYbACWOlUOhEq/XVGoltCc9vXlleOpVPS4/4ViqpisHsuDc5ZMy/fPUqwhZjCZHtDye8MgACA6WyUinRaMSB67Q7rTyrZhN+dKiPd3NT4Glv2l1oLm92V863to92lc6z2WBlYYFSunH6TD1uI2Nlqj2XZsk4T3LNS0aokNIYc3y0V3OZTEQ2GIuMH+wffNd3f2e77aWpmE5zpczCwlJcaxlF46ie5Yoy98y5s8PxxA8igCDCIK57X/7KH379tS/P8mTjzIYTMurhpEyiRu35j70QRL5QXCmhtYTQej6Nak6/37t9+5bWoCwrxlin29jcXJ9ORru7+wSzKIqVUmWVhZFT8ZQ5uNFocC4xZkoZjDEiWIiq4kUlSkyRMQYjQgizFgqhoAVxLXj37TdqQT2O6lKVBwd7eVZS162q6srVCxunVzmXSVo6XoAIjDvR8kq3qniWVvNz0VqjlBJCz7tp53ssxojjMCmVlJIxdx7XhhDOOwfnnxNCXJdprcOa5zgOomR5efno6CiMa/OmQiEEdR3Pc2q1WhhFUinXdTvd7jw6vLq66vtukuR/2poVR5Hv+51Wm2FSlqVRmjGWp5kQwhijrfnwK4HdOH366uXnskRaQ5VEruOWidnf62NMp5PRSy89L4SYTvLQb2mFEIKUGeoA5sN6PfICP8+LRt2ZTNLZLB1PR3GdAWgabd8PKEBgHp2aZMOsmjme64cRZU4QxdJoRJCynHkIYlBxyRzPDyJEMETo4fZdTDFlAEHv+HimJdESEIijwPcZdhleaLUcyjDAoix1UXqhPnN2PQyDJ5688P0/9OJHP3OhKHMIbVUahwXtdhthTaj9X/7dvxFC1OPm5ulzAEoLKtdnShnHcREGSilGAwhYmc8IswBAUAHPrS0vLZ09t7G4thw1A+Y4eV5GMYpi0lqAwzTBnm524+7CUpbzSuhGp8WNIoRACM+cOfPw4cOiKE5OThYXW7VaWK/XFhY6LnP+JGb2YQr8+vW7r3/jGgBAympluYuwQRQwh0ADZSWlUEEUJUmSZjMpy6xM8nTMGOOVrUXNT33q5bLK9nd3b9641+2sfvYHvyvPJmc214ssr6bCdV3mWuIEFji1uMEcuLwY7j66cbh9b63VrkYTxItscry/e282HT16sJNlYPfweJIdnru08d/8nb/puuSDm693lvzj3s6pzeXVzcVSpBXnW1s7e7vH3/1dP7i2upHnOZEaZlkGhQHcutQVsrCKj06Oa1GzKme+Y3zPkyIHEFoDCGbWKGAgpU5VWkoYJUSLmQECWEYgLlJ+dLDXXIgfe+xMxe3eo0Gwqt54/Z2LZ05tnrkYxe2dO9+4f+tmWHNSnmlhiXE7zZX337tTCOFFSKUJFyb0I0i1KsuvffWV6Xj2sSeeHJ7Mfvu3frcTd/Ik01ZigAjA77/1lk8DKZWyhgAMMcbwwxY5Y4HQ2nWdeXabMSa0UEq9f/0DxgLHcTSXzXrbSDObzaQxAEJkwXynMYesYgjnNNc8E/VGm7peb9CviGrU6gCSMxcuVklmhJ1NpxRCwzW0KI4b4/HYIQQRIoTIOPdqtKisMLrZaUKEPM+DACghZSXSLPMdhqgLMUnTPCsrC9Gt2zcIQQ5wjTFxp1kUhRbaSA6t6Cw2f+5/+kdr6+esISfHo8cev+i5Nc+tVXk+HqVxPZQVaDZbnuMKWUJrtrd3CHbb9YXeUf74kxdu3Hm33vLq9SjLBYJunpVVmQeuPx1NXddhFBdFiQkBBuVZNh5WzZqNwqbUwtiK+tEb774dNutBlb300otvv/PWyXFvcXkFUHiwveOFjWQmV1bP3753+9nnXsCY3b97GNf8NJlKPQ0CWFUVNHg2LeJ6FIQejf0Rr6QWVXJci4NklqzXVrDUt967FgTQQrvdS7eGBfKxlRYramsmExXXQE/HTz12tUzTZDYiLlw71Z3dTggUbo0hxrKK+UE0TlKMcSWq5VbH9YzMip3tPYGBi5gsCkYYRm5U6wyTKSFsdXU9DP39ByenT585Huz2h1NKUZEJgInFTFvQaEazWdqshSa0Ww92PMd98413f+gHf2xlYf36rWue1/rKl199+rnHCD1dC+P7d7ePD3pbW1txVJsMZtff+aOPf/wZdhF/cOOtvErqdRDH7byyrjGVLDFGUOpWwGZT0V5oHA8nN27c+9t/6++26gu/8iu/vH9w/C/+xb8IAs/zQi40BFRJ6DrRxz76iXfefdP32ekza/v7+7NJcf78uZ/9uZ9zXLa8uqos/+qrX7137+5k1Fs9td4/PhkPJ77vcy51qTGGAKrd3e2ykFpr5kCEgJLi4sULW1vbx0cDhMjq6vLe/k5ZqWazKWV5eDSlFHc7y5vnNq/feG93//jy1Q0LbVEUAFijLUYQIWQNxMZgDAnGWaa0sGEYXr/3weLagpRyHkm4f/9uu92epsXhcX+FuK1ua3Aw8wKfMZZl6byvfg6msBYAAKTQcxqo1pIxhjGaY64BtBDC+RcrbULPgxhWvNRauy6L6nGaFL7naWsMQAgRbbkXBvM+0CAIpsms5AWlFFgkpcQIAGDKMp9Op/NWSqWUEkpzYa0NPZcQ4rluwjOtlJKaEMKlsNYqY2RRQQJ2D/Zf+shVpYDkyPUbxphXvvQV4uHF1dZoXF6/8e6wn26cOndwcBRGXl5MHMe4HiEoHg0nrk/rcSfNjjCT9Vqz16/aHW/jTGN/b8f3XUwp89h0Ol1Y8WvhwsHeKBunjgf7yZRzcOnKlWvvfiMIqdLGdbw0TeenUaPRmCQEI48SMxykvhcbA4wWCKKTwxNMQLPRXFtdvX/3QZGU9aiRQ9Fq1YCl1z948Jc/9+dc1+xuHTIGtGJSFCWfNtrB6sbim299rUiE77f2D7beemfih0GSZJAo5mKIIYGeNRBBZqAMw7DMC5d5rM3SaUmBf+v2Tc91yypv1ePJBPgelSXf2DjtXQyVBl975Xp3sS54hTA47mXGgpYTW6uvX39/OBwCaymlxKJeb6K1WVpaJIRoqeZMbyFEKfjVxy4d9E/KspR5defudW2AF0W84AwQoxFzQyW1BdB3qB96Uae+d/8RALa70C2LxChdr9XzonBd/86tOweHOyvrKxcvXppOE/o8NlZ2uxdo+MLO3l4p93g68j1WVNX2zv75C2eYsUSOnQBYSDzfbzfbw8YUGgr9EaH61W9++aR3EAWMVFoD+uQzZ8uSP3ggEMTnzl55+GD3Fz7/H1qdqFarodkkQZA5bkgdTynlMepS6rtOms7ystDIAgK8wAcAQAiqSiilADBaS14WRZ5qJeY7KIYRJRACxavs6PBg2D8OA7q5sYIAf/nlF19++eWXX/qOg/2k1eqeOrUqZcoo9APmuOjm7Vu3bm9fPP9C4DcRtLEX6JJTY4CsXvvKN198+nlVqCItHt25N+0PqYUMkMV6FwvoYeoCII2e0+gBAPP1BVcfdnoopRBARV7WarVWvVGL6isra81a1G23oLFlnvOqCoJwvr5WSgFrKcKMUpc5rutKoT3PC4NGnsjD3ZOARlATAj1e2es37570RhZSreDa8trKwjLPqjhuxI06BB8WMDSbTUZjz6mvr52BGFggDRJpPvZCIm3x1LNXAIaD8WT3aJZmhcdw6FACkbI0EzJuNB2CL6yfXmjX24v+S9/+xM2t11pdBmQegHBykPynz//Hk92jk92D4XEvneSKg6tXnioLHde7cb2VliLnolJgkgmE6HAw6bYXq0pyruJaK8+qMKhrDZIkD0MPAkyIQ7AruFUS6goT4BhFe71J3OqsnD693+/vDXuaYRiyV9/+Jodq9cype48e7h0cLK2sJbNiNlLvvb23snjpuadfOtgb8ZLOpqLdbiOEXNZY6lzSigW+n2UpMmQ2TtvteqPldDoRgKqqKoBMreEBC1RlJwMwGqtWp66M9jyv3VogCHq1wO82OYVHw37cjJNkmmRiMhmtLUVnTi9xnedGzDTvzVILKIeVFrLh1gLqlDw7ffG0MIJioHgyHfca7dZkWjx29WnNzWJnOU0qQ+QH129niSYQuY6GpCIutBBzbafJrFGLzm+e6R/2AtcRPKcM/Oav/uLd23fOX3oM+k6z3Xrz1bf/5T/5X00R5Jnqriy5vrOy0DVZ+viF9aPDQ+LU1s8vBHUgDUCO1KBsLrfrncbCwkKZqLX26sbS8tHB5OVPvPQjf+5HFxc3arXF7/+BPyuE8n1Wi30DTFFUBDuTWf7uO9du373fbLSErA4Ot12Ptrut3/yN37/6+NMbZy5O0nyW8eEkwcwF2NfAmcxKz40IYZ7nEYqt1VmWOC6u1WoLCwsQYK11GIa3b98uisJaO51OZ7NZo9F49tlnxuPRcNQvyzyqheNpEseNJ596WhtQq9Wjer3Z6VRSU+Yigo0xEFlCEcJAyCquu3fv3u2f9Jr1xubm5tLSkuZ6d3cXY3x4fHTc78VxLJUihBRVaa09ODjIsizPc2OUBVJKRSnJskIpYy2UUmKMylKYP/mY98LOt9+u6/wJsUE7Dp03FhBCZmmSpcXi4qKQMooiIThj1FpzeHg4//YkSbgoKcPz6XkOA49qIcKQOTR0vFoQthtNBCFGSErpOo5SSmpVVCWX0vHcsBb9g5/9Bz/wZ36QS/GHf/wbjaYfBIEUKkmmm+c24oY7GfWjqNY7GiEIT0724gYFKO90a0rz8UCNRiNlZbMVN9uNspKdRbKwEoQhhMAxRl26ckpqJQWyFkIMhFDHx8eUkmQm8pxTB58+vZ5luet6ruvXar6xak63LfPsm9943UIcRZEX+PO2KAS157rptMDQqYW1bqvBiF1sd6DBhqtOrQYsvX9vS+fgxgcP/8tvffX9d/c8d9FqBqC5dGW93sRllcxm6WRSAGC6i3FWpACStfV1QGx7KcqrVCqkNNFWYWKl1LWoIYTAEGw/2PqPv/g7R1u9nQfbq8uNx69unt+MfIQ8HD+4dbz3aPbNV69DAIb9qQVqPKocF3ge0ED7ka+0aDab86ap6XRmrLEWnJyczHtCq6rSWnMll5eXl5cX251mUVbGAEoxYyzNM0QJ5xwTWFY55+X8DZMk0063u7ZxGkCRFb246d25cwdBtrl5fjIcVFU5GWe9w/G1t99/9ZUvb2/d++C99+/eenjv4d2tvbskUM2lyGv5S6fXaWNhf1Bk1kkkKKR1ozpAMMvSIIDLy6EfuPsHW5PpycJiHWBwctx/8PCO60tIZs8+dwUj5+23rm1tbwMA4ro/naTERcz3fQdR65jxqF/IAmErZIkIo4jMkZumslohx3GSadXqYGCA0BJTiyDQRiKEMGTAQKs5o1iKyqXh7fdubG9vP/Hklc21jScff/zunftv/eaXwmih3V0eHW2luWAWYgQ4rxaWO6c31774+19Vo8qjbDKrAt/VRiEAWu3g5o3bkDIpzfJCl2IXASylSpLElCp0gyJJqe8aoJU11hpgrDGGoA9dlFXJhZKu644Go0QUrcXuSnO9NzmWXLiOY7iWUjsWUoSl0Q5j0FhrtIFASkkdhjFOkgwhQhCmmBmuy7zYT3bDMJxOJk8/8eS9G7cgtJcvXjo+PtzeO8AuqarCdz1IIEMkL7gXxdbQ3Z1DNwRuQILQVYI7Ht08t37/wR0DEEQI2BDYPMsKx6EY01Z3MWqEw+ERzzK4BFxGwsBfXI59L/yRH/pLr/zRu6PhAALjuWxhsa6ttNaOx2Ni8eHOXuS61z94N4yc5ZXW4eGRVdRI7FBzcnL80idefPDofl7xRr0WhjpJp77v8qJ0HK+x0OW55LmsR3VlhFHIKDCdjYjDdvcPO7LVWmxbIIErWeSMBunSQqsossX17lJnNUsrSh2HeePhoe/79XrD87xut3NwsLe4GEhhBYQ5khg5QsziWiikpNCRZUUolNJoC4nnJSrtbe9CAJYaKwznklhAmGm0FcDDdGgVNw7LkKCh++BwjxcFIeyJq6eHvYPl5e6wfzyYcafDUqkYdpFBrksclw0H07rnEN93w6jTWdjd3bdIQw8vra6NR9kHb7/N00n/GDY7ncnsaPdg3yXe8kJDgdwN1GxWWqID13v+6ecmvdnOowNZFtoo18cGGA3Eo507tbUwLY+igHziE8+3/dab33w3FUkQE0qsljPflesb7T/40jVWj3/kJ773g3ffETkX3N748t1wSda7zenx0dXHz5GKFEB/9vu+79xjl99489rXX3lrubu2urL09/7eP/hrP/X/xMzOta6oXlOTyeLS6mAwAEB/67d8/L3336bEHYzLz/3kTyeJuXbt/Xevv2OhOXvhHMLR93/fX/iX/+Kfzwb53s62QyBCABNEGdFGJ1liLc6yjDE2Gs3y9G5eFLUo8jxnNisfPeqHIajX6xDher25urp65869KHRee+21x564+mM/+hdOBifTtNo8s4bJw/39fY95QvF54iAvsw8LiBC5ffPm1aevVgXf3tq6cvHSvZvTJMmU0YyxwXQAZhNRFoEfHfd7ypqyFIQSrSCEYI46RpA4vi+lMFJobShFWkGtLQAmCHwpudYWY4wAKgXHGHW7zbLM5/ZvqSVCKK/KSla1esQYLctyfsOu1aKiyIUQQmjPg6KqfN8Xgo/GQ89xHccxUnU6ndlkCiFMZ4njOKLiWmulrVKKEIoItpUECEEEX3/99ceffPK7v+97f/1XfkuZqdRZJTTEdndvh1CgNJAip8QxxoSRkxdjABTnJsusR52FpchY/r2f/Z4bN24AAABSFhYGWMac3mB/lo48L8pTDQByPTAaCmiFg0Gr45zaWJmOZ9BaCMFkPHOcjpIQWBKGLkIAEwjyIkvlqfUGxuniSr1/MmaMyAJT4gMFRWkGw+NG7HXa9ZO9sRawd9xvItcPSBTX3n77bWshc4LZeACgXlkNj44Oiop/27d/780P9pUAs2Ra5RULoIFokiSQAM93FpeDk6PcYe5g1F9ZboWhX1UlI0SWVRQ0sOGgMIDadq2ZjoaXz57t7Y+mUyFTcJQMKAkQKJlLamFo9cyhLAxrIpdKKUgoMMYALaXAGDFCGXNnSZLnuVIKYUQcFlKitU7ysVL8zNmNZJjoQioNXEylEGHkJ0na6MQaAi0VRVgBcnzYDz0axkFZZUqXBa/yTAzHIwit47ucy8loFke1Z5585o1vvg4Bqvsd6lZBzLTlQeTzCmAaAAC1EgASxtoHB9NGc1XrAqD81MraqD+jNDIajUeF78WUhJ12J/Ar3/fHk8H+/v721kGztvTRFy4onb77zl0tGYxPrTPXq9UbQojRqA9hRShMk4JRv1kPkSmtKHgqrIZSVbV64Pgl0EQIo5RCCFhtMXGkINpALoRFWAGEsc9cR5hcg3J5YcPxIXHRpcuPX3v3xu72FnWMUrxWqzkUapusbi6HUWNwmE4e9kILJxxTigBUla7cWphLHtTj0XDSarQd4kBDq0qOB2OsLLIKGm2gsdZCAgmjQnJgtO+40CKttcvcyWzW7LYLyadFBhlutlvJdBZ6gUfcZJpSzBAmeVlYax3fA1pDACSvIISIkdlkGndaQRDMMbtKyLn2MJvOAARnNjewNLLI1xeW9w/2AIKTImGuAwTyItcCkJa8FJBbGzSc1VPtrEw8zxuPJo1mvVmPKSHJlO88PFSpT6gWOsEMW2u73cXJbIgJ6Cx3y7LUhlMqMVRVqQUPH7/60rkLG7/9O785HByurC7mSQoAklwAqOLIxURfuHj+0aN7zMFZyntHZRytULe0EGZZ4bghxARTApHRqhSyCD2fFxojv8pUEASYwMHgmDhsXiSnDdDaRPUaZWaWjDDG9bhTFFWWJXHkrqysjEf5oJctxKuD0cFTz16pt+rnz138yiuv7O7uXDh3fmdnB2hICBsMBlEUKVUhBo3lkMZlMoZaYeLS0OeyiEKvwQIxrNpO2IpdGrC37t3JIY0WWyfDg9i4qVZuXKeug7iy4+R0c2GxVj/YfZBUaYEBbbmDTBKnHrJaOS3jmqkyU49ajVrwwbVrTz92OY7qb998p7W+QANP5Lb/8CDE2gmN9EGw2PVI83CvxzOhpaq3Ag3VNBsbCM5sbFju3f9gF1n9fd/3iRu33hiNC619KWXYCD/1HR8JYv3U4+eYDd786q3eccE539m/T4k5vb40HJ0Ugh+PMr/W/KG//B0P7tz+6HMv3r//6Pf++KuIep4XnF1ae+fLr2921yB2K2BHeWoweuaJF3oHvU9/8lOLS63f+M1f2dp55DjOcDiuNWrHR6O/8pM//uD+9ttvvtloRFWZpUn18//6l773u/9svY2mCXj9rdcN0jfv3Dx79uwf/N7v3Ll5o1mrffwjz9Qi/x/97D+kHoEQVpwjgqGFZVnOTUMfeeH5ra2HxqggCB5uHXkeCvyoVqtJKYPQazbrb73zXjNqAIDqjdbCyuo0SS4/dvXWrZue59y5dUOqqiqE78zL4TGECEEihMhK8dFPfhy76J333lhfXfGdsMw5pYSbKq0Kz4slNyHFj19amU0ObrzfE0JZbRrNeGvrOAqZkrCqZBgG2pTaKIywklAIhSlYWV20ileVUBrkRWUgoBQHtcAYJYTAmAKLHcdTBhij/NAbjQe+685lqY2NjfF0kqZps9msqmo0GBpjKMZCCGhBLQjPnDnT6/Wmw4njsrnJVmvNhSCEKGOiKHL9kHM+S5O1jTXXdcbj8c7eYeCCLAEf/djZIIi/+Y13lYC1qBmG0f7hflQLIFJa85xrCAGhwALguRRTWav7StKyMsy1i8u1IuHt5qnd7R3MCojU0aHynAhRA3Beb9Zm02rYE0vLjSCi6TTjXC8uLE1Go3rcnE2LTmuxqqqKzzAxg+GQevHm6TU/oJ6Dd7YfDQfTxe6Zxe5mp700Sw43z7aLfHLtrdsP7xwh64qK11bUj/74j2/v7tSbjVde+UqWpAutbuD5R0cnBzsFdajjBQjrTjs8PjwmFjc67ZIniBgATVFqoylGHgEUQAOsiut+kWZaAYAcrSDGWIvcALm41Dpzeq2aJFXKDcAP9/aw5xhkIYQGWIxpKXg6zQLPgUQDAEI/sMoqpRhhAKAiy/OcE4K0AQghbQ11HGNsvV5/4RNXLURVqfcfHW3f36ZOcPXxx4b93sN7d9bWltMqK6ocQyIrian70U+8FEb82rX3eiczz2GiFJ4bIujwSpdCGiMg0labTqcFLYQQ1sJo4WnvpNcjhMRxzEvlIj+dTFeWFx883Gk2lx0/Nrgk7ozSfKG5sP2g59Rbs4lKZ3mzFXQXYwwwgjTPZhWfHez3TBWOhzwvZq2OL7hyWJ0AxZVAk8lkPBoCJMNG4Pme68dZViBKJr2EGhm5QTZL52hsWGFCKEZGQw2AQQggYIwRnAOEnaLirhcZAJQSymgISTkFZaUyMSbIoxg889zTg9Hg4Pggyyvt6Oc/9vjJYO/ocNr2llIIoCVZVdXdUGhuCOosL+IkGYwnru81Og0tzaP7253mEkKIeCSd5BRjRuC8+MIajSEiBGmtEQCu60qplAGu6xoMSVnU600tDc8rBjGXuZbGYqBUUavHUimjNUbAKA2t1cYETgha7SzLKYP1OLZSHe4fSKmb7cZjT15+8ODBzt5OQGknjj/2qRf/z1+4RxwSeDQrSyshIBpTZIHxfI8BKAUv8wpYBCwdDTPXqe1OjzCBk2EWBjE27jSfVZoCKagD93YexHHNZcFgMEIEnju/ceu9d7//+37g9q1H7753X1qprOCyqtXj0WQMpE2n2bkz5+Oas79/z/GAi9BCo7W9s92od1Tka4Eby+F4NG026/3BxI9qLnM9jxqDx+Miy6fI0rIqXBYQamq1+vbDrdXNRlnlEFqXMin1dDDwPBK5DqVUJDMtVBwESohkViilNBQnvb3zlxbOXWxwJY97D+MGjROyu397Mi27zRVr7bd/67e89tprhDDPZWWlGNNRt9uI2v3+kIYMFBYA7LjxuEhcWKWDcaACGgR1L+wNBo049gRjxnCliYWD2dQHuMiVtOLU4qleeTKCRS8vXFqzHCglXNe1yVRbkhndO9qvteI0KXsnI4NJv0ybkZNmk7BGAwA0FkKAOqp2dh/IymLiuNQ3ykHMsyCPYpTk0+HRgRO4G2uLa5t17F36wz94yypXQS11dfbsRrNDBM+mSe/G3a8nY1vzO6HrHR7tnD13avPC1ZPR7MKziwaZG3cfeiz80muvdzqLn/62b3/vxs3NU5tHj/YUAo9ODi6ev/jw5h3M6PLaajbrv/vu6++//3qzFf7ET/yl7Z1HWttWqzWe9OMaeeObrw0HyaWLl48ODxYWln2neO3rr/z8v/r33/7t37Z2eg1RBCjoH++lyRAjcPHihXoYHB0d/cG7b0VRJBXnUiCElbQEW8YYxpRg22h2aqNxWWXK6KWl5uHB2OgiyxXGsCjFSa93+vSaylXJRZqmxfbe0888h6C7uLgxHJ1sbJ69d+dGFAcEQSOVEApjBKwFVsVx9M5b11ZOLV69cCnNZtPxxGgMQ6SN1logCKyyw8nkuM7WVlqXLtevX79eleXKyvnZbJQmgmCfUVyWFWUWQmCMQRgjBFyPYQzzXNZq9ZNeXymNGQlr0f+fqP+Mtm07yzPRHkceY+a58tpr7ZzPPjkrHQFCSCJICIxsMBinMgVlKOqWzbWxfZ3q1i0XLpqhbBAYsAGTJJCQQDmdoJP22TmuHGeeI4+e68fC7c5fs7XZ2vg1W+vj+/r7Po/tWJxXCKEsK4xGaZqHtcapUye3djY551pKhBBjfHt7u91u5yjd39tpt9tRLcjzHGrjOe6R37rX6/UPe67tlkVFKFZKQYQQQkxwrc14OnW4CMOwqqrNzc1Op/PCC88z9hWt8vPnF//6j/4oRva3v31NMB3HycLCAleNyXRoWQRC3G75QhaYSGqBiomlY3O93iBJS9sKqsLEY+Xa/v379zkrLz9y6vqNu5QAbSrFNLbRdMxarW6rgQHUSTIdjQqlAAQ9hKx7N7cWji1CRBgr8zwnlgoCurx6PMsSAIXreOcunCqKSnJ7dWVBcDMTzCRZsre7v7yycrg/TSbZcy8+PSwfvPbtV8aTiR/ZTz5zRitGgfX6Kzd7ByXCBJNIcD2/2OUsDTw7S9igH4d1hDAoKu24YVFUQueAOFHQHPYPxDh1HSqEhgwi6Fg2ifxWWhbZlFUxqDlz2cH2pUcuxkl2EB9KoBuN2nRS2E5NMxx6dc92KjAghHDJXNuL40RZCgAkpW63m81Ge2tn+yg6MByPEcGIoMFgsra+ebg3ajZb1LME5/fv3wUAHD996sKFc5yzv/ziF2wKDET1en3UH5SKh42mkCQeT2zPLsqMYCM1cRwHQqp0RRHJYk6J7dhkPJ66KWg154VQnuN5hI96hyzLDjYrVbJ2FDGjkyqNalacjmHbC4LmwSA9vnxxT/c9l9jY2djYOLl6bjo+FIqHXm1aStuhjtvqbw9IALptlzTbfl6ZaVl67frsXOvs6VXBxPXrtzG1NUTd+YW6a+89XPejgLGSEEtLKo0iFAChjUEAUKEAwtpxKMIWl6Yois58Ny8zrbVje5VOsCZQk/v316FWWoNnnnsxKxNK9Ohwd9wbSW7X/Nbh3sh2vWpcuZQArQghSqrBYV9qAJhGFDWj5sc+9rE/+m9//I0vfqsW1oBQvuthApQoMcFCCMmVbRFKaFmWlgW5FAhjA4GBAGLkum6R5YhgP/B830+niRDCsT1lUJwk9UaU5zkEsBYGcSxlxY/045TSNE20lMuLi23WHvYHCMEsTYxW9XpAIUrLdG/QMwQc1fY9m5bAHOmgAYCCZ8iymo06UCCOs4P9cbc9Z1O3yHOe8fn5ed8L7vXvK418y9fQDmtkPCkLwYqxUUAFIT3c255bnPPD4OTpE0XJapFSFZn0i2bdI5oqKVq1TjYp00kMNPWt2svfuFFvNX1/Pk15Z3Zp7UHPigvXs6fJtNOtV0LEyUBIz3Mcx/F4BaqitB0qWPLkU++6fu0egFgZaVHHJpYUosryKxcuCSaBMsPJSBaxbZF8UlpO2N/P/dCjiLihTQi5c+dOo1P3vXqSjge9ISWOa9lxHEsGhoO02eym6SRwo9BrDLMH84tn4qSsteoH/QOLECRpFpfzi3P9w55fC3uFjGY7FS9bsqYrOJiO242mYAXTqevZ1aSsLbR0Jt9++8bSY8cgMpApyzg29bhUTJbdWkcWeSUKpXitHRloJukUNdxJFlsE1Tw/y6fKsblSi/Od73zPd77yzlvXr94FBlHb5tJETuDJhuQsbLZe+pHv6O3veTZQqOqPplyA0HUaHpmUw7ev3Xz86Uc0A29/6+pwMIHG3tjfTjP27HNPvPzy1cuXnnZq8wr6/eQgEwlCSEuz27snpZzrzhllhuPJ/PGVIsmH2fSRRy9kSfbUE4+VefHiM5eQY0bDyR//8R91O7O93kADDgzyPD/PyizJbk9uNev1pYXFt/ffHPS2rr3zLSN6P/YTP3bz3i2JdFSvPbh/e9AfyVJ0Gs2ttYd37zxYXu5krDIQGYS0VgoKCP6qyPGnn/4sgMC2UZrqZ565tLJytlFvX79+czSdjKdZoxkeP3n25S992XX9ZqcdRi3XCe/eeSC16nS6Vx696Pv+K9/6ZqsZGQMty66K0rbdIPTStKLI3niwEfhn5mbmWaX6g+l0OoVUSs6nfBi4bTeqNRvtvIgHg1Gn00qsqeNSQjHCgHMOgaWUIgYgDKUwhCCEkOPYAGpK6TSOCSEYq26363i2kPzIeUepPRxMMMZLSwvz8/MHvX1jTLfbTdOUcz6ZxEeQB8dxESSc5UaaigmCZKfZStO0yPL/7o1XSimEcVFVxgCAQXumOxgMpJS94YA6drNZ37y3Z5HXnnjssY3tjbX1w/W16VtvXS0yU6tHBvD1nevtdivUblkwSPB0mkQ117IwQBogkWXV0tKxh2s7ly9fmIyn6+vrjVotTQpKyBvfvkst6NgEUyKEAMZQXD88GEQ11w9sQtHy8gxnandnSDDArmvbdpIOW91wdG+3KEGzGW1uPVhdOa603N7dYzw7d+4CIujN6y9Tag2HA4vS5YUlJ/CffP7xZDJ94qnLL7+ShX5zOq2kUPE0D3z3G994a39dU4sAqL1Quq5LCAbKq3eD+Tm29rBf5FIkcnl1PsnSLGF+jXJVJfmgNVvTimEILAuUuRZlUZUAS9Zo16VSk0kigDLChTJwrIYUPa/mjMcxIbAop5gcwcBRxZSNgGs5eZH6vsMrEQS1yWj8j//Rz+zu7v6HX/0123Wm0ylCSEoJALh69ZZgEiEIgG60m0qp/kEPQWJZ1mc++xc2tQh0AICu5ybJ9PjxFY2wAWx+afnU6fPTcXzjzetuqA2qsoxBhKIoIpD4rgM0ZKy0id0IasdWzt+4cWtne29mxg8anu/Vtjb6jWZb2drACgEZ59wP2hjZx48f337lzeF479zF4w8f3h2Pc4TIgwf3Vo4vrD8cMqYYF0uLy7arZ+fRdJrYDiCTctLuLl85ebnRbVXVJI/jq29cNxofO7Y6Gvf8yLcwwo6FjHIcB0AMoCdVgomBGEFtGW0BYyyKNDBpmhNiaWMsiwDki8QYg+0mKLPScTzfaRtjxqP826+82ZyJDKwafthp1Hr7vc0HW816zbPoYLrvIShEZTR0LMcylihKLKBj6NXXr67fW7cQphDYGigplGQQYq21lsqxbIIhY4wgHHi+gQBAPE1SyyFJXqRZ5ocegsBxnDSPj9Icgkkuhes6TPA8zwlBFOEkmSKAwtDnFdNSAYwJJkVRrq9thGFYbzarqhr0xq1WveIMEISo+5VvvaKxE4VBmeVKSNumAADJgRf4SZEbDSM/Ouj3FuYW17c2x+O+1vUiLxFCSZw+fPDAohBDx3OwlHzQ2zMUSAVCNyySdPnE4sFw7UM/8MGXX//WrZtr73vX84+cX717s6q5rdHBfhgRBA3FuEwzISpC9W4+1IpqrRHV9WbtwcYmUxhOEtd1y5IZGEujbcdRilccYmQ7NvJdL89T33e5qOI49vxw0O+HQd0OPNexXNsLg/bJS6ddywdA/9f/9ttKiMCrMwYxRIPDuN4IGnUPI29l+eyrr31tbnEuj4WWDrHDOBlGoYO0ffP6WqMeCqa3t/dPnjiDIbhx+wbCLkIEAIAMKnnaWWwsLixWEitsK5UVUrIqG/SmkbtAvLA/GEWhi4y0KZUWnBbZmVPndyY7JdQJY6snT4+2UpnoaTxZPLF4MEwsl4QOnrLptJgSBzFXzrSCej2a7A+Eg716lDJWMEtsld3g5Exzb3Y2G/SSNMuklqXMEcII2sNBeu3m1SDQjz39/PAwO3fhmSSmb799q8NppcTDjYP6zPLZY4sE1efaSxvbh5NCfN8Pf/x7P/Q9/+oX/mWndWxa8bgQ/SwLXXsSJ47jxVnpO7S3v1dlzHX8jAvsO1EU1YPG3/qRH/2D3/u9zfX7jz/2SGKKrTzRigRe5DgWgJpgmKdlnnBoAEZGyfzVb36dEPKFz3/hp/6Hn0yGW5/59G915joGAyEH7WYNaqfbOLb+cKPdDl567xPEtl5/4y1qO9IgqYXREhNUlnkQRFrrTqdz4sSJ7/nwRyxq27Z79dqN7/zAR/7wD/9wdr5z2Nv/s0998fhC3Q+iJI5r9Y7jWq1Wa/X4yQcP7wluPvLhj25vbE8nA5sSXjFKqVJCa+I4TlXJwHFvXr27vLowMzeXpnFQ86UqZmZmppOi5vn93gEAME5LAMxjjz26tbk+Gg2MUUdap6pUFnUAqI44/AAAA/TR88uytB1PiMIY47ou45UBOkkSz/OqqhJCeF5w9/bt4XDIRWFZFgLgyKJjWVYYhoyxMAyTJAFaW5alhGw0GmVZAm0IIVLKqqo0MFJKIwUkSCtdq9XDeu2gP9CCAwAcasXjmLpg7cH2/bvbTp1Q5P/H//g76Ti2fQ9bsKiKwMdZPoXAgoQWBWu1gtE0m50NPc/b3e4hmGuARwO+s7OFIOWcH10KTEaFMWCpOSNVPhmnXoCV0pbtFAUTinFhIejUWhEA0rFLgl2lzDRJTp9asB1kIKjVa5Np5kV4a2tLCEEtcP7yye7MXMVVrV13PCLlUlXAquB3Ht4xqqxF0ac+9yfzrVPJlC0tnmx1akWZdptzqytkf+11gp0wkthKpS4XFk8d7oxu37q9erw2P9cdjWPfc7Ks8nz7yReX8ozH8WR+YabM08P9uB41FGcBRomWRpi8zHwT1TstnjLFwIXzjzAOyxK2OvPcJGEdVqy0LSy1oJSkxXR2vs4YSya5a1tAAoSQECII6P3797/1rW9RCqWUJWOU0tm5bqvVivPcJlgjPR5OTp87f/nSpbdee+PWzTujZPq+7/yuezfv9vb2pTGebydJsrO5hRoB43waj5589FS7ubK3M06yPkFibrmxMHd8cBj39ntW3R6P+r4fxnG6uy08qz3TWWy2ozjbTdMUaT/sdpmRg6wf1G1jAWWQVPDOvbuPP/rYs88+u7F5/423v6i4CPxWPM5d135w7zpExnE8zyHD8U69SaTJTp1e3doYkJOXjp8+/cTGxuDhGw+xqXYeblSprLVnt7d3NJCTyejU0qLr+SybUkw4lwCn2nDGjdEQIaONhIAojQEAhJhKcGFEUead7owytCq5cl0fA50Jy3KYMJ5FeS7Hw7S70On3cjbMxr2B324IpMbZGFrKVJJYRAHAOXfdMPLrFuYQkFpgj0dTXpSB6xVlYhFKLQgxsKFl25ZFqWdbUzWFRlHLKVkFIBTKNJthlheWY6dpTikOPb/ZaUd+NB6OOZcIYKmV53llmXPOpQE2oZILxhiybEqp0BoDaru+1jpOmWvZENi1yJICPPf0u0eTcX8wwAj1BzvPPvOuN195zaWRokxKYFE7T6UyBCO0tb4ngN7e3jVARpE9nR46bkiwnZfFqbNn9jfXMYZxceh6zuqp5SwvKwYVAxb1t3b6Uqvf+50/qQoACNjfnT6MxpZbS/Jxe7alVW4T7FLXIlQqrFXOhDIAD3t9bIM4GdiOR5HBmHpeWDKtJJJaGVBhApVSjuWJSru21WzMS8a//KWvYuRgjKCCBsg0G87Ozo8H09v31t+6es/3vJMnj2cV73ZmGVNKMQD0bGsGYT2dTi9euLL+4DBJiDeG9+5uH1te7fV6FrUF4whC32tMJynE1HNwUQx9LyjK1HE9gCyEEEDKNhTY9K1r9y+df3xrZ3c62PO5YZxFnVZZ5dQJqVIQgzyNfeR6Dh3mk1fvXRehlRSTxnybWo7jKKmBB0TFS0mdKk9QmjfaDQ4UQ/zMlUu33rxes2oO8rIsl7gA2CI0INj6j5/8rUzEjudqoOqtoOSlMqoqCmAItZ3RaDKKq7sP91q1la9/49VnnrrQG9+q9oSDyfBgYFG0vffw2OljYegvnjofCz1KhrVG54X3vr+/NwS2CesuyZGWoigqhG2t9bDXrztu6EZpIUsuNdQ2K7th04a4RlxLmrdfeXVKACV+VeZFvgOgEoKFQd0idpZlBqh2yx2PspOrnfEoXTq7dObUabMQLs63mWGGwjjP9nbWEbSUjDqNgFfEdZ3XXnut22mM4kxKaNuOqBgA0HFsxgoDpJTyzt0H/cF/+cV/+v/Z2N46d+7Sf/6t337p/e+dm5u7ev3af/qNn/iT//xLr7z25vzy8vd8z3ePJuntu/cRQk8+8XSn2+q0Wj/y137s3/0f/5vv+HZo5XnGGddaWZZtUciFcG3nYLd/2BufPX9KI6U1QohkSVL364HrjCbj8WjiWu6f/tlnLl86NZmOpOSUEi0BAAgAo7UGECAEjFGUEtumUpVHV7NFVXmen+e5MrJipec5cRwPBlmrWcuy4syZM8PJMEkSAEy/N7Asq15vOI7jeW4cx8aYmZmZ0WAghGg2m6wohZAIQM4F0EYYCQAgFoUELS8v52VRVGx7e9u2bYyx53l5ktq2TRAEWkspklIqmr/rxWfeeuutIk80QFqBw77yfdBq+hVPw9CDmDTq/niUskqtHl9Iy2lRFOcvrj54sCVK3Wi7jBVFoY9MD8NhPwgdz7URoEEYSCAchwSRNR0XnEleWvVGaLlISaGlnk6rG7cTx0K1qHGE8mBMcACllOOxas30soLVGx1l2Mm5hb/4iy8sL55du7fF+bQeEkPdZrdDkFBSHBxMbt2+jRA5eVwcHvSpDRFWvu++66UX33jjjb39zcP9qW1b42EZBpgzJoRhSTGz1GKMNdpBrWVvbq6fOnGyLHNeZYIxxw4whgAZXsD17R1nND67cqYRNYURB/2DWteNxwcC8HrkwxxP09LzXKEYtIUyKIx8z/aqggkjj8D+juN89atfjeOk0+kUR6k6pF3XPXHqVG88Ge4ftpttCUDJ5IXzl3fX96+r227grBw/9uSjV/7NP/9XJ86cTqsCIetgbyiSfGl5uUwrIR2Iyez8fL4xqgqugHrs8ctR0Pn3/+evHBz0fvZnfxoa8B9++d8Lpn3fdd2W0uGwtzfuTbstR4m81Qxzke5u9fyoFkUBxKjZaX7rlW/mOa01rEajlkwSjLHr+M8998ztm9dGw7GWSilFKcYYz82u9HtjKRjR2Hzz1W9OY5XH06ZnS867rdmK65n5hUoXyOg4zRirtOJVVRFkK5wSRJTCACAENESKYMorIBWSBiJibIKGw57jBrblxtMiGXCLi7mwVhUlcXzOhUtdBPDebhw5oWdZ82e7O5MNp2E7DXsvG9m+W0mFLAtKmJcVBjTPSjcMhdKIUCdA2BigsRAMQayNJBq5jpNMpsqxMAKccc4k05I4ruNQZbTjuVrrZrOZ53m/3186dezw8LDdbDvUOQpuZFkShiFjJUWYZYXW2rZtprQ2ykDAKkUsFwBIMBUKBF6kpWFF+fDBFrboeJoTQmwn/PKXvoGkrrm+oYXgBgALI8sCCmKiDYRGuo41GMekZT33/JOvvvI2dHHFyoODPShxKUriYmOr/mAPKmKkrY2rDVLI7s6sHO6tz3UiaIJbrzyc6z7VXeXAYZOyjDwnZ4VW6NjSEgal6wOLUAyjzc19aWR/uMfKHAG3qth0kiiJlEIaobDmllXa7bbHgxRqMhpmWicWwb5fYxUHUDrUUqLANphmA7/eSmPp1cKiTG8+uGF7ru2FAHIDQJ4lGHkWdeKEf/7zn+/OLF659OJXvvKVuYXloszCyNVCU0wsSgk2kzFzHCdN+wCPm+2G5wWuHZWVGE36xFWLq8cmSV5p67XXrhPEiOFYOs1ovjCFBOOqtD1KDVaEAILhNEtdDDZHqRUgiITFis31m3yMan4TYVywijhtxwGyUphYSTZtzLQmw+SJy8+MdifD4ZD4PitTCwLXshhjLzz1LFTOJO0lfDzN43Q/hsj4AVHcIGQazZndg5033ngwN6MuXLr87LsuNTrTL/z+/QcbO2ISzzTd85dO3Lx+7Xjn5Cc/+Xvve+k7HS9Ks7jWbu71D5NiSFXTIqZKcwwgl2JhaTHpoY+89B0P7m1+47W3Vy9cuPfw3sFweHrmmEucJy49ujI786Wvfi5HjuDK92tcZAryRjPwvWAyqubm5rod//Bw/d3vOffsUy8MD7Pf/71PvfPW9UdPz852ZiXgiho0IVoDYrm1oHb6+Il4PPr2a6+9610v/Nff/xzAoN6qU9vKE4mxsm0LEwgAybIMYSeJi1dee+PkyZN7ezvffvnlaTz+/o/+oG15g/74G994M6q7nu28+10v/uqvfTKM/Gar3m51KbXv31srcv70U8+/c/XbtcgTnHueLRUoisKyKCVEKug6blImG5vrSysLk8nEslg9qhd5qrTc2NiAxkzllDEwHA6FYEoBjHGRVa5TU4oBhBFWCBHOFKU2QqjKK4QoY4xSYhDUWhdlsXxsqSiy6XRKKSjLcnZ21nGcer2utZxOJ1UlXNcVQti2PR5PfN8DAIxGI8uyMMZ5mmmhgDZCSS01IRgjrIw2xiQx29rZVsZobcJahBFlVQUhrNVqSsiyKo9U04sLzb39vY3dW888f/7mjbsY0kajOewPoprPeUEpRJiOR4nnOQCgsBblGSvLfG5+edCLgXGJrbKsNBpYFGBiGE9rNVtpoBWRHCkhJY4rLuVEYhhSZGepyosdiKXnhhaxAXCrqqCBLzhjvDSAu06Ikas0I05se3a90RqOYsszf/nFv4BE37x9fWFucXZmVuuMFVhL4vhcFpXroxZqHR6Mt7Y3IGKnL8xrAUtWPXywM52m08m05tUrQVilNEuUlpUU7dk2MPb1N9fay5BzY1Frb7e3tDx/sL8dx9JzqWVhACBQHoAmK/jNOw9Pd1dSe7K8Ors+HuV8FEQu0xIi13FpGIVFNcEIWDZxXRe6uKo4QsgYKYSg2ErTVAgh07RWrwMMIQGe533ta1977KnHwicef3B3zQqibmcxTqqrb9+AEOd5/uef+8y7n30WYj03N5OsbRpNHMsTpsoyaZRr27U0HXRn23MLLwDIL54/b9t2sxaurCwe7g3e/9J3//zP/4/aqBdeeKHbnj84yBbnl5pPBq9+68ueg2igKzHtBLWaF6UVV1xN8yklyAujbneW8UzyYn7u2Mb6Trsx+8br1zzL912rzIszl5aTdAiBXl48fbD3bQgxmezmGOKWb3koSpLMEKefjC5dfGRlZXljZ2swHqT5lFUcSYSNsbTCBUC2dB0pBBAMGG0j2xBaAFyJCmBsS45s6nJWAFDYFndyzpk5SPnK6jxn43pYg8Z2iUPY+HDvRq3ZNqQTtU8QacpsKs2SjSsoS2wItWGeZwXnzU5nPO57bqik0Rxyg4y2NcBQG2QM88RUM2FRiGA9CKvRVApjWw7nrNudTXPMJIYYjNJJs9WYTJPhZENqIFFQQi4Rdmndr7WPrXRtuzSGzXXm52cX1jce3rl9L/Rn7tzZM2TKktJxHEKwZDzOxo1GiwHQ2z+wLMsBUFSVbVnAoQihQZZZyDEIE2IZLRGBRZW6nk8tq2QVRnYyJnevDbCqWdq2bMiqQqCo0Z3J86wetrIiH0+G1BaiSgAAUdQEFFmBp6ldpFNvBgV+Tw06K+35SiSyKBraf27lCZSPuEzrYbA9kqQ53+hEC2fgKANvvrGlealjv8ilZVmWDVlptCYN9/jobimVrXTJRWU5fpnDEnBESog5thDC2nYsaErBp67jZWl8bGFZCJFOqzLhSTpttH0N4Djt2VbQaJ8yvKIaPn35UYTBpOzfuPn28dnVdL/0YJimCeqiuUvdg53d0AsCjB/u9/zAKjQDxl5YXB2Ph1TazVZ4Y+deUZZLiwszM2fOnjl39eo1Ni7qxioR8+06VrYBNTmufOARimBgKwiWlxcyNjx3eR5Ivb/Wjw+VS9toRgNsA7s5NUwYHY/G6ThZfn7lwpWLvd3Dt157c2FugYtqttv6qZ/+qbt3bz/35Mdu3f70cPwWdLq//V+2i9Lmhget2mgyPV8//cy5+YON67OzsYLwrZd5vbUUXpz8wHe98Pbbb//51/7i+lqHpqAhR+E4Sx+84x8763B7cFhWCCofJVNJp3O1GdkI6/tb8WRadhaOv3n3fr1JH3tqRXLedANvplEhOCrHza6fa//EE0/Em8PR7oOzlxYH+7HS7v4wr9dPZcM7P/eP/vbO/savvnXvJ3/0+/7sv/zBbM27fGz28OB2cur0YLI2YQcMuVnl1v2V0EM37t4UZt1vNschtvTwkWeOAWEZhDJZzNbnDnvjcVwhx6MWsKAZ9Ycf+ehHLr/7KQHMcO1aq2Pz4jCsIRjO394f/MTf+ZlP/fEnR4P1L33xz5uNZb9GskSU07WZmYV+L+7MzH/v9/3g22+8qqVCxGiqhKRKIwMwBAADhRRzDGBT9vDmVrs7YzvhERLHdbxhf2hTS1caY1KUtmAAAFKWnFCjQQqQUcpAaDGhtAbNWpBlJUa+kgWACkENtBBChGHjhz/+Y7/yK7/iu22CGIAaQrOzvzkYDR2XGKTCtq+R8TzHQJNkacWEFML3Qs50mqY2tbgWjmVprrUG2EYGKwuTohSOB7zQq5iAEGV57rq+kBJhPE1jKeVspyuEMFqhCgcw3L7f69YXGUdCCZ6UGlGbEK/WQizNkrEVGj8whKHpZDDKSbs5T1BnOulpwWyCKMSQGik1NirOpQHSC1yNpZJFWhpUHJGBgCapQakywLNpmVuFJJAAY7jWMIlRFM32hxM/avIyRRhAhKhxrr26vXo8+e6PvP/2nWunF4+lSXliLsKUasCJRZtz9jQ+6N9rlCWrh10CgIfDpD958vGLjgevvn0NI2ftxuHs3DEieraNVAGTXImASKOiFhBoGNrewnKjN8hnlusXH5s5HO4VoJgkYH5uXqQTyDgSruMwWAlqYYDl3d5VTK2rA8vzHAKbecxqNVebqh65UehhqJVSE5XZ0F2dXTV6a23jvhfaLGFCKWjZRkCiHB5rLuHF5x5/8X3fmSVi6MRawNX50z6yXah3hzuPv/fC+vaD3Z39p5575NOf/kPHgvevvcmk8gOrUOXTjz49MzP36ONPlqzgusklwxg7dshLoDSGsBHV2rvbm3/v7/3o4HDwse//OLI797fGp8+cILaJx/zsUxfv3r4505krJ0ln9sTrr7/R6bRdSOfnVwFAeVYejO63/CbUztZ2/4nnXzq+dPorf/YFpKpKH86d70o8DC3JefzOvb+49NgjN99ZJyeOndjZ2RlP4jwvyqz07TDn6ea9BzZGa7fvQWSiyEtVoaSm2hjJFcYAKAyB41DJBcLGGGWMVgo2m9FwnGltNNBZknZnZoHBpTS1toUh2t3dX5pfioIaRNpoYRhe6V4wRkDGKK7KrPQJ4fEQufCIeiO59v2w3fGyslqYXxoOJ0fEHAghhJoggxCACFgCK1G5xLIxjScJ55xSLEGpKdBW7FGLp5xSB8kiS/i506fGvYFX97I4R1C6GI/724HXGe4ZQnhVJsPt0TenXy/KmDEuxbprtfxmgDG2bbcqyp3Rge/7B4Nd17Jdz2ashBBigpSujDEEkpXVhSRJJuOMFYralADk2q6WyhiDILQsi1dFrJSWWlRACk6IA5Uu0xQjMextAwAD2xVC+W63YozC5uHOQGtdc4O4rJCxZ1tnxlV1GCeznfrqqfMN6Nedxua9veWV050T8zfeeBOhvdwp7mxneZbXa7MWjAZ8oKjlun6Wx1wxlvWH075je1qDeiOYqzX2Dw6iZigF8IO2MTJngoucSOK6vk38eFp5npdmU8dxbAfu7R1GUZAmEz9wWClZLhZnLexYGw/XtC5fes+zv/k7/5lI24KuH+iqSDFFk3Fy5dQjtcCeb9SLadIk7el0PB5PBefT6ZggfLDf2yp3Hr38xP37a3lWWEvWN7/1jTiOH7lydntvLT0QAlZpNnQpoRQDAIU0yyuLW73Nvf116hLGmEe9qLFQDykvRF5yg2VRTE6fWc19a3Nz58VnX9zbPGhe6NRrzXa3lZbx+1567/Wbt4gd/L//+f9WjHCnW03jmVdef8NIE7rOYBrnMWgE9Rtv3bn0kfc9ceX5vYODtEiz8eHGXtXvAV7emF9s1LttIVR7Phg+XDv96IX1vcMSxxxoP8SBdAY9NbMY3bvx7YWGu7x4bHN3IllOM8SyJKmsTq1WMW6oOujtdnyPEkcZdmz+JAdOv/f2J378Jz/9R7+TTTk33NIg3l/Xorp+9Sa2rdCjX/na5xVm93f6P/Dx7wvaUcWLsqp6w6lCJQCOwHprOLEsmg6T6Wjo2JwnulnrrC6e+vrXvpwl/Rdf+qHr4HqWPjBGak2U0s3Q+b3f+p3O4klk+64T/ZN/9r8jALcODuJysrN+v750uju/NB5s/+av/+bH/vo/+MsvfON9L73r9MqxdDJ97Mrl++trx08s/ew//sf/4hf/ycJcTUiGELI9m1cMI3QUaKKUSgMAhIeH+8v2alQLGGNAK8G4qJgFoTGmqiotpVbgaOV4lBFDCCmlIITNVh1jbNt2HMcY4jRlUc2DmBCCtJb//F/8YhRFtm0T6moto0YdY5hkme/5jDEMMQRQcskUC/1AKaUklLxSSvmuVxQFpbQsS6VMEHiWZZUiI9TmsbBcBCB2XQoRanc6cZwCBCmlJZMYAgWM7/u2Y6VJNbvYmsajt955bWHpmAFWXlaOS4XOHcem1JvrdjbWNqeTEmEANPBtW4liPD6shV4GNYKmqiQAIAwdRGizRYqi8L16mue1qEZJxXIMUEmoqUqOIEEIV6Wi1FJSQqEIxQBDiMTh/sbxU4tKZ0G7u7Ozh5HleV6jDSTUX/7qF4+fWBSCG6SuXr351JNPIUAf3t+58uj5bhQFy3RnZ28wPBj2knrQAlC8ff2NyShzHGAgicKGZdGTJ4/fvXvXAFhvBE4YDYaHM925oG5zWZ44eYy/c2Nvp3/u/Inzx5+9c/3WeJD4BF155IqN7c0HGzvbfYyo0YZV0rVcoYziIuUCYQChmY5i26ZM89wUyoCTJ8/d2VsrheW25i91F3bWdkgFDbSYUUoyy7NkXkAXYqy2p2t/+cpn69FcZIetTlMo7lCLF6LVbC3MKYjxU08875BwZfWSKor+wW6SVcfPrLz/uz7yn379l86cv3LliSeLSiHi2nawtrbW7ZDlxWOCc41Bc6bT7M2lcY6t2vmLz4Tnwnv37o3H47t33zl+YuHRRx8JguDB3XuTyeT8Ob/Vatq2bXuubbtZWgZBTR6iPKuyaTw7P7+2tvbaN9567Mwjvf0tUVgP7vSjoBbY9onjlzw7HRyofELRg7v3Jv1xMpyIpGz7dV1xz/KyJL325ju64rpgHsAn5hYtYVxILACAsZWkjCGLRhhjQiFECgIcBs0i557tuLYFjZJSxpMEAFQK5QT+idOrrXpjOk6yrEAIQAijsINEhFjgAh9Uqua6WnDftgiFUjEpueKiyKvxOOGFSKcZZ1JLrrUCRgIoEVYQSQSlo/0Qe0SbKsuRpq4TQEJLCaKm59f9meUa8TMBJhcvnTp9erlex889824tcZnmkecbxTyLIC2TcXK43x/1p2v3t+Jx2qjVT59aeezR888+dxlaqDXbJi5uzTQuXjkfNsLubAtQk/OMupg4CBJtkDRIMlns93YQkAtzM2FYAxppaYxUCAIIDC8rxTjBEBuJgMII+W6oBUJaibJgeQ4Uqwde4NhIAVmKut8yDFWZcGnQbnQjvy6FmUySKU8lhGleMS455Nc2rj8YH5rOTLC01DnT6Z6Gj7w4u3p+zvGjRmu2NxgYq7j85JmZpdbKqSXLR8QBrdmAesgJrGMnli9eOffdH3qp5FPbw61Oy3Y916u324uOXQv99mRcZHGRTONf+IV/9IHvemkyHVo2UKJcmJsJneD40krdr7Vcq+557Ua9d7B9uLu1NLNom6BTnx0O+8pUCiheiVe/9XKaDG/ceUcRGQW+Y7laIN8NlJCO47FcXjx3BQAwPztXVfzWzbvxNOG8un33DjCo5kWS8WbDdz2CsJbKYIzzIhWq4KJ0bB8afzSU0lhu5JQmblhejTo1avW394a7PRvQh7fWLpy98kMf/WtJkly6cunH//bfXFpd+dgPf2IwLR6u96aZvHN3s91cmeucHB1W3Xq3E7V5Xsii6Nbb3/jKm3dvHjpOWyvgBvZjVy68+8V3zXVradLf2z7Y2hy+9s6NoUznL5+krfbOaOKGNrU5QtJwRQiqd6xKoG+/fU0hHbYbGa9cv77fm+4ejA0Ex48fs2x8bHEJaAgldrDfrc9+/Lu+p2vZZly9+OiVH/qeD//0T/7Nn/s7P/Gdzz959c1rH/zu7/2hH/z4j/zQD3zsr33/leeeLIFClJQqPhgNSw6UUhcurFZicDje0RjOdeeeu/JoNZy6yJubO33v3sFofwgysL836R/2OJNaymatjYCLkIW0+dM//OPD3b2iFMaqRTOnj516vHfQi/ubb965VWvPJgmvNTpJnswszi6fOjXKM2ihyWRYluXazo7A6IkXnhpOEwxsapO/EnIDcFQlQAgdHaUUk82NtdFgWGT51uYmMAZqw7nSGpQlE1wd5VqPvIQAgCPys+d5juMURXH0KM5M4DucCyllUZXzS/PnLp4pWJbkU0ggk2IwGNRqjXNnL0kBIbCQsSQDgklWcl5WigvXogAqjIxSwhgFNeTM1GvNdmvWaIywjbEdRJ5tecYAiJDnBRoiPwqbnbYGZnFprjPTBgAkeXbQ6xEbViz1AmthsTMYHk4mIy3kZDIhGCKkGc8gUkVRhfVICqA0IJDXQ6vMp0c/ua5dbwZhzcfUcoPaaJxh4vX6SZaK8bDE0ItqTccNpAKuV/PdjmIOMi6CUIrKokQwzqsSQYkt1W03tADbu3vLx465vje/fCxqtjXAXJqsqLBl5XkZeu7Bbv/OtbXZ2vGDtfy1L97Z2togCEOoMQZ+4FRVNplmXgRsj3qeNxwOu3Ozv/zL/8H3wyThGOPIt6LQX3+4t7MZs5LcvHn97Pljdd/50h+9euf1/fF+6lCAMOjMzVshDeet0G8DRaDGvhM5jmeMwRBZFLOyggZpqapCpHFxsD/aXt+//vYtlYHFpVO01qq152dbizJXBFsGI2hBaKSBTJms07axSPYf3Gna5IMXnnenVXGwHzpkfn4hz9XuQZJkyvFaUWP+p/7hL7z0wY85zc7/6xf/iRs0zl188of/5o8fDIY3bj+Ynz/ueW1q1U6dvtzuzl+7fbM3OqxU+dJ3f9dP/N2/b6g9u7S6sX/ARTae9Pf3tiA0mxsP3nnn6uHhIed8dXV1Z2enKAqulDHmrbfe6vV68TSf6x4Pg5Zt+cPBdDpKCXEazU6zPhe6s8dnL/t0zncWNh+mvKhpVjMyJMiyZxttITZdy83TQilloHHDIAgCURZG8GZY05LVgzBPYoKxgpZSUBrNObIsR6oKAoowrUqhJQBIwqPZ1ACEkEVovekxxjY3N9uzdagRQZaUjjLAQCSNolZQVsxApETFjTR2DaMRpRhhSwpQMtntNi3HfXB/7YjPDoECACBgjNEIagCghT2pCkyQY/tcQiZEpXV3fqnWbhZloiHwnEYqcoRBGNolG271yt3+/aXFE9IAywk93zMasTL37BCBqNWaIZhO44Ff9whBG3tbluceDgdBEJRlCbXxaxFG6NiJk72Dg72dXUKITS0IDcFYCSGEGE8GWsa1qONYUAKhYIkh8fyQUpyXmdIcY+rYFCithRQVM1g7jgWxJRXnXDkO8XysoYEoTTJu2RAivbW9ro22fOlFpmSm7gZUg+31tbRhN9udPAVbWf/rn/qK7bM689fXYy7V8VOnN9b2WwtIC5yK3UJzKfWHvveD3/j6qzaNKpZ6nnft1s3X3qyiCNfb7Xqt3Rsdcl4uzB8HQG+s9W3sOTSwIt8iYH9760tf+gLjOYVOp9N56T3vf/Xllwm0cqtyESk4y3lSSn6s1h4fvikzOR3GysCYFYrltuufPHNqEu9Pk8m1O2OKAymQVjSvKsZYFhetRhshVKuFVx55ZHd3v92a3T/YVhJIAeOJFKIsiyIesU5nFkLb8jGmwBANEcLI2tnYrzfnMaJKiMk0E7IcDPuYGAN1WI+w4wKDlMRvvf7m1TfebLfrWT51XWd3f7+o9NLSqU9/6s8ffeyp/fUbLnnxtz/5J08//tRzL77/Gy9/6X0vvft3fvuPOgF9/NJzTz321K2168320g98/Pt+6zd+Y7u/mcSjoqjCrmP7dccJmm3LbnvcqspUZ/nEQajhuC4EgMvQjfaGY6ixQ20CUeDVBodj126JwoyyHNKCF2UymdgYlVLoqnIJchwHS/A//8//U73TUoRORtPZ1syxpWNfevmNBxu3r1w8z/MiCKL3fff7Oa/ScZ/USGthZuPqvjTC37yXF5NHnnjUMFfmBiC4uno+zsTBIZtmMKzNenhS6waLZfuZ58/1x+xbr9wIgxoA0A/DLMs+8pEPJWU+nRb9rPC9xsz86pOPnhlmMLLwhbPn8jzPiuzchTNBLdrb3TBVlkzjR6486dajT332Dzg0jh8ICbCNq6qyXAcoLTg3xmilEIQUEy6FY9mH+wdAa8dxLIi5EkoBjIEQwqGW1toYc6RXOZp9AYRKqbIs4zjVWmOMhVCEUgSxMQBhaNvW7bu3qU3Lsqx4Wa9H02lSFsKmbllwzwu04BRRYHTgBmWeEYIgMEAbIQTUxiglNWw2GwCgPKss6hLbRgg13EAIIbUyCAuttDJHSywv9LIsIxhjjA0ExCJpnjWb9cFwP6rX6vUgTTiAFkU0jatz508OB2xt40GrCykxzdpKVRrJ4yyNbcs1SgMAao06QOjwsN9uNC3HO376dJ6VnMssK6SQ00luucYAQbHFKmlbyLaCLJ1wIgmCklcYIA1AVWRAy4cP1jzPazTdNJtMkqRedhw33NzprazOprnsdNuj4YOluZX+QVwk7O71h6yqmrUoBVW95qVxbln2ZDI5trrS6da1ERvr21LgldVTP/cP/3F/ED9y+Yluc3j17ZsFG0EIIYBaKMuyp0mlWOlZtvDsYsw8Gs6drbU7jVe+/c20GDsBOLbwtOiKrd2tSZYQgz33r7YarUbIGMMIa60RIkyqwPOLLKv6/ajWaHXaluceP358b2MdIyKBUYLb2CYQ+hA1C+Nj9/zJp8otgJvVU8uXDuTsULCSMKdRX/DO+oFlgOpPMq4PvE7nb/zdn262mz/wIz92OBpdOP/MubPPTKbpOMnLsgxrngZS6Gp2vguBlkanZUU95+/+T/9D4PmHh4f7h+sPH9w8fvz4idNn+r3tt6++1my2Tx4/ubtzYFu+lIpSalnWxYtnK6YwssrCchx3pkuSgs/On4TIXtvYagd+u97IqwpCkRYVhrh3MLSJq7UmC6dODA76jdnZeBRzAzgErW7bDwOEkBaui1AhWDoaSqOhRbKSY4sZA4EmWcrCiDCuEIQYWQCgIAiSbKqUsCjNMkZw2W60BSgY09i1+6MD28LN+oLUUCsEEeOw0sBjEoZhBJFTFIWh1EjFGDNaWLbv2d7szOLm5hYCCGgADYLIIGCMURgBCDEAIJUpoVpoBYCiNFCSB0Hw9NPP9vqjNCkpxU899uKrr746Gk7oXGQ7cH5p7u7DB/3JyLaCMKhxpRjPkmJkcVCvNfvTsRKkLFlaSYhAnpe2506nmWV5imvOuUWo67rTUdxudGc781ubm5JxrbWWGgNqINRUeoGfl7FS0ouIkVJokeaAWq5tUwCR77vTcYqhIZAIVfiRTy17PIrDMMxzPhz1/cgBULciT0qGAMWQjMfjMHLzIq5kDAUtJqkF0NJ8w/bp7fVNZvRrN18LfNluzfERzqfFyQvLEKR+jbm+u702krG0ib+wtOQEUZIqJUpKvNEk7XZbQmbGaNf1GS+1FtQ2TEzHwwnFBmkUOq5NrSQd/e7v/c7pc6eLotjc2H//I5cdz+0d9su8mu3OYdtzPfrBj37o3KVH97eHjMETJ054YYBtn6kSEWU75GB7n/E8DJvDZKiZicLGTHf+7oPbUS1gZYWw3Ny4V822337rFdcLoyhI05qGTrc72zsc5dnuxz768U576dd+/TfbnUiCQuJKcCmU9oPQaoVVlrOiJCIkCJ9dvTCxcurY29vbAHhaMurYGrJWoz47251ORpyrGzdu245HbOfO7WvEsq5vXG3b0c2331ma60bNzvtf+sBoNBqO73/4ey/mcb66euzJp943KKrNwcN//3/9rg1mzp+qXX29dJy6Bf0w8jWxg3pYSR21It8CQKgqZqhUDjSi4JMhw0bbtp2PE+PIWqPhYd8l/vHFxetX36AenesupHFsJKNICFlZFimlAlx6jrPV26UO1RWqSjU7t/DhD7+/1rDyLEnTvJAVO6gW5ruHh33dcRFFfhRILTZ3d+fm2+PR5MGt6zWvrqrq3PkzNJHxVHNeQYfYQYQDMbvgry61fvij76vy37z/YB3ZRAu9t7/1y//XL33wQx9MK07cjszQhfOPlcNNSqus4s+/9IFr77zxtW98/UqtEY8GhKK3rt3I0/Tm/XuPP/1MLQwoXrx44uTnPvU5rARCACEghIAIIQC01gYAQrBj2UIIbAAmFAhFEZVGaAgAQEoZaGGMqVLqSHUDodZaY0LKsoTIuK6tlNJaE2wxVtmuQx3LILO7v6eM9h1qu45gomKCcVmr1Qb9MYYWhkQD4VIrz3NjIAYQQygqDoAxUhkAtQKWY3leUOSlMUYIRWwLIowxsV2PCQEhjNNprVbjnBescF3X8VxoQFmWVVGEYcjyame3Ryjp9yaO77iuFyeZbUVVybIJcxwnaoClhcY7b49llbrW7LHZzqj3IJpvAoAB5yVTeZE6rs+UlmWFMW522skkiePYsmxWVpWYKi1taiEItWEVk0CaIKhBKIxRjAmHeghDxkpeicDDZVmmSVlr1NI0NxrYljcapq6HEdr3/FqalI7t0pq9v7d3+sTxJJ4YjYqsyjMmRBn50ebmZsVaQehxpR999ClZmb/393/qve958duvXTUSPnL5cextPHh4mFXGwFLzyqPuwXYPCl33KVCFMWrj4T6TbDDpGwyQxp3ZxtramtKsFjoKqKLKlVIGAghsYJTRMAzCNE0dy8IYCqGbvr1z5+7+9s7Fc2exFDkQrSDiJbchJYAaqLpBc55Yy+6stSnPLZzeevO+jhBY8t2FcCwZsYmGRBurqrLRdLJzsDPTnltaWNnan9q2bbRWMQEAuH6DsbLWiCqWUxcyJuvtCGpYFsxzPANM2LC1Ya252rfe+NzcQn1z+95BH4QhDULXGMUYIwT7ke/4bhxP02wIsGRMnDl1GWMUp+Pe/lbgdYq0RBSVrHjr/vWaZ5U8Va7CDmGVDkijVVsEKCcPtraySSoqxtMSI3r8/Nmo2egN+ghBSu3RcFSzqYIoqEWDYWkFtuAlgBQhXFU8iFyEkDFKawkhTdMEY6ANIBh5rl0WhZSS2lhhs7SyCpFEiBsuWTaykJ3nmZSltqnGaDg2FkGC5eloJ/SB74dVKaqCEQqzJBkOh8bAIwwkgQhADQ2GACGIAQDGYwBCAICRmpusZPnpcycn44cbGw9qtcbq6vGa71IICICdxuzrb7w8GkfN+pk8UZ7ta6WFEEUuLBJKXu3ujKglLFKjNNDaTaZThFoo4/OtE57tZSLDSGpuqB3yig3TgiDkwHrUjcaDIbGJlFJjVaGyLCUiDsY6zys/7GomKHGTNJmZqxflxPZsjxmgKSurZrtdiLQsS0qpVohgZ6bT5Jyzqmh4s6oYxkXOpZzvLArBMlkQVZPKVFzVan6RSqkor6xoxtU4Q0CNh1SXdqN2FnJ6MH7YaC8qCS5dWDnYH8XTot8TGw9fcb1QMhOGYV5IY4xS0rIJF/lkHB87dqwos+koW5hZHB6OCSTZJG4sLWQF6B32hTFc6dljs2u7D7/69S/zSjaCtoYkY/zk5ZOxLI9feOTewy+ef+TS+t3bD9buCw0dP2IiEazMcuYHYejV06rAzLAyv33zGrYNRqbZ8ofDYbMe1up4OJR5OlnbKLTWAKn9noKQNBoLYdT2otBgDqzKgCJJhhoYBEhVFovdxWjRf/6p5/70jz/dac7Fh/n3/eD3Pny4vrO3W8nK9h2puNTs7KUzP/M//rTWIM9LAxDnIi9zrXXBij5LIu188Q/+9O6tdwZXr55/5PF/+HP/8Pn3nfvBH35f90J71JvuDQebe/2vv/mWLMxS59QoWff94GB3YjI+mCbQQe9cvbG4PJ/GRTeYwcZysC2oAzWsBXUhzerijBDCAUpK1dvbas3MGy1ef+21Zr117MRyf7Rfr9cBFFLltmcXsACCeLV2xSSxA9cCGMO1zXtOVL9w8SIrWL3exLZz4+o3F1Y667vbANIiyyzL67ba2KLbB1usMLAs2r5vUWA36/fX7xTTqlubqVlxL+v/5E//1Nr64K37b15e7va3rn33e86//tq17oJnUasZ+V//y885wLznO77bcx1Cnf7m/uHmfvfkQonwW7duPVxff/zxR4s0+fyn/thvRtN0NDc3O9Ne+NY3v9ZuBkJmidGnz55ZW79jWRYrK8aU5zrAGAAAFwIhjQA6CrpYmLCyqoT0PDfnlVJKKaMshRAy//2DMFVKCCGOAHyNRmM8HiOEEISWTYoiO750whA0no7rzWae5xgb27I0MFprSi1jDKXYdd1KlIyVQjApuTHmCG+llNYKSilt26WUFkXh+R7GOE1TZCxRceghVWlpJEBQa82kkFoQy8rL0nMcA4BlWUqpSnDH8SrOCSGOFwpRaa2VkBKIRq259mDD8tIrj5+qN/zJSN18JVZBdT8eCwGTmCGMCfHTpMiKvNluJ3EKMSIITyYTx7KC0NFaE0wLXSKDXd+zKSmznFAQdVpZkjsuRsi4rqskQsCiFurMNI0ReQ84jq05yKrMIJOn6YVHVx0XS8kRAtN42gjapSotC23vbGitNYBCKK2B7/rjyWh+qe1F7t7BrusEvf5evxdjABFCg8HApvY7195sL4rufDAzj0fDrEyTuZklTMxwcMi5zLJ+WcmwHvZ702a744VOs924f+POcDw+Krw5viM1kRJoDQAAwCCEYFHkhGAmSo86toNUAXxKWcauvvJtauN6MxKMA86VMqWR1LIY5PvJtBO1CcDXb9+GDjGlXesENnExBJzzwKvJkhOITx5f3j/cdXyr4ozYDsYEEqMlVEoBIY62KbblsLIgmLJcQEgcWtOSSK0F467vVrx89/se7x+mo/Hw+OqKlGw4mHAGkqwcjEdbu7v1Wpsx5vkWk6KqxGjca9Rntja3TpyanQwYxgIidvb8Ko93PKggECViyLY7UTMbVmWVaMNInha2bWOAF7rzWVZIrbIsr9VqjDGWZ1G9VkxGaZk7bkNJA6ACGEAkELQl15xLy3YFr4xWCGLLJhAqY5RSikCCMdzZ3sRhMLM8c9DfI4772CMXF2dqJ+Zb/a39MGgzpRM2qXSqhdYl6IT1//yffjVnQCteVcy2XQP0+vpDI5XvBZxXABhjIDAQAogQRogaY4wuEXa0QtBIxsvFRWt21k7zvZUVJ457D+71P//5ammxhoH91qt3bLJ863WMkWPbdqoFgJyS0PcWFDdAyZqDq5IbYbHKTkdC8Y6UUutEdsKNaUIIRYY6jpOOjDEYGgQAkJxD1dbCK0ophEjTlCMvDEMtsZRSSi2RJyWTQlq0PolHeZ5AjDgnihloqFTMGIgxphhQBCHEmgPIiYdCU8Ga5YctmzHRrjf29w+psH1Q79OY+FQAzgXUOW/Xw6QYc11pCe0mDSLfYDUYqrwKCLYert1p15coDqqCQ4nSuLIciKlQBkLD47hwPYKAKfIJRiCexJNJ2q0t8RwaDjWSFGICkUPcWtRlDAljkGVu3L3RbXVeePE93/7W1a29g+58vd5sffub3+gPR6NpXxvu+q7ve2U6dVyfWH42Gi7Nzwmh9nZ7QX2mrLaiWq3R7A6HfddzRqPRyeOraRqPx+NTp05ce+euVqA719nv7QtWUeJQMv+VL38dEKFhlWaikkUQhFLKPMkhFLubG61G9/wnLmxe2p2Mkg9/9/e3Zpt/+Id/KEEFhdSwElLNzs6/8vpbz169df7c5Ywjajscolq3CSFsQhho9vSZM1/7zOdXj59ehvj//rVf/xt/6xM/8zM//59+/ZefevzZZx5791df/ubXvv2qhFbJs0bbzUEw6k8UhsDYhlEAtO+2gfYmo1hOpxphDhnXlTIwLTMntLZ37jPGIIS2a/kRYvpwpruote8Qurd/uHe4GwCALMvyXK4F8V0bSMartALYUF0knZb/cPPWlIG8Ui+++N6l422K8ov2hXduXquH3bgoGj6FBhzs7ikATpw61W12Ht64vTK7mGZjhFWn6T713hchE1ce+YGgabUWG2+8+gc/+aN/Z+PGq8l45/FHn/jhjz/x53/xJrENxcQjcn994+zSqVsPt2/c/cZsy+dVPB1b99Y2csbTyaDm2hdOnj2xsnp/d73WOffWW6+XefnY5XNb2+tpMk3Gk9AJojCYjKcAAIIRQshobTTEkEguAIKu62qty7yglBJCqqoyAAIA0NH0SQgAyBh5pE8AAEipbRsxzoQQ6sh4ZphFLSewp/FkZmFBCmURighBkBiEbeq4ruKcYQIhMmWVallwzjEimGAptTFAa8QYN8ZYjk9tWwOFEBBalDyvNSMJaVmWUsosyxBFBmpCSFnmlmWVZen7/tEkkGaF0ToIAoQMokgrILiw7YBS23UBQtAADiGc7a4S2PrqF799sCftGjVKA+gS6ozHGSJYQ6O0smx7cNj3owgZUJQ5xtgisCwzi1Jq0dlm53BvTAgBUFY8NwYCGLquK1WRx3Gz3dYaCiUlEFu7G5IpjIAbeuPxxA1sDfjKieaZ08fW1u8WRVHkvKx45EeQoKDuEwwHg0mjVp+Ops1mRAjhIp+ZbZ47f6q+5fYOR3E2euSxS88+9cLagweWS6BRnk/SQmiUHVueUVwbYTXC9v7B1uL8gjL69TduNttNZcDS4klqk7JK1m5t67HO8rzdaeZlmaQ5xjgIG1XFKaYAoCMBV15mHnWyqvB9F/uRLAU0qB7VNJBAGgS0BSm0DAIYAH3Ih9Gs87nBm08e82r+fK203nh487nH5rlWWhnPDTQXyMivfvVLSysLTz79xDQpgFYYQowxr5jtQGKAUlxKg7GtAabIBUARgrSGRmBKXICA0jDPYsuybt+5erAzne0e29k+iKLIKCfwmoNe6rnhxYsXb9++Qy1okBCyLIpiPJ5WZX7h0vHQI9223TtM9g92D/ZBp9WY9d0HawXx3Eyr0SQzOcxhIZgkRACgdGC7RhqHWiwrJ6OJ5Vij0Qiwilo0chxKKYTQtiwtFQcKQKAhA1BUlXFcXwJhgEFYI4iP/tkIGmEkxQQCAjQdHQ4bC20u2eFg7NvWe3/kA6cWl+7f3Yhz2c8n0NWMZ6pkMGVRY3aytw+A5hxEkc2Z1EogjDivAADGGGM0BBIidJTUMBq6xpUlpsQCmAvI/9E//vn2TDfL80kyAQBtbR/UotbNG3f3dkcHO33PnnHIadd2MMYYIqNUnlWDEVASEIq4rILQp75nNKYUAgosQhUQUspmnfyVptQAAwxE0BgDtXE8MokVAhBC6Pq25SitzWQyMRoagyEgVYy5KqBVaLwfdUlU51mRAeFAYAee3z8Yui6xCLEcpygzijGhEADu2Cj0ygpnyCDfgRSlMy1aFRLBXIuh0aUBjhM6BBUPt7abM67IqqIw800ax3dd1x2OS0P9SQa7c63pYI9VACD/sD/yfb8o8sB3uCgRQr5Xy7NEUBOGETA8jmPP806tLF9/500L4yoZ1sMm0lIzFnrRbn+siMZa1RvB7FwricdLCzNlnfcPNq69TTtRxzDWiqxRNg5DC4BGpRLL0qNhaRHLcS0/gF4YjmMWBjWMUBRFvhe0Wi3fOixzUa/NOQ7Z2hgabRFq1aL65vZ2lfOohViVWxQ9dvkKBviwt2sBBAsCpJqJZpaWVl584b2/9H/+8r/81/+H7TiEEEHV22/d+ms//GNf+tpf3Lx1dWFpvij5rXfu/K//9N8srzwyijVEfpEriLxsqiDEGOFxLtat3WmaTEbJ3/57P9/7tV/5xX/xby5eOhMEp1nRunVn52svf+2Z972fcTUdHEiZHcQpY1JoYGOohY6Tcb3jrz/ccKjPFU/ZRFgV0FpiWapSQnHq0mqnM9PrHTz11JNu5Lz85qv7O71KciFU2JzXgFh2SKibVtL1/bTivoGjPMFRZ649O964x7gkIY1qAcohM3qzt3t//97cibnazPzbb97odubH23uBE7mOhwhV3GQpm186bSoWhfiFdz2eJNNPfOITsmLj8YDa+pd/6d9x3nr0iXdHvtOZcW6tr+2PY+LYmNKCJfWmc/P2O7/+679e685v7T7s94RtZDXtc216/b5vW3du3jGV1Ahv9ff8RkghLZIpnJ+dJpP+cJCOxrQN6vX63u5BLQyAhTjnWgNozJEeFEBdFplje5RSpRQhljEQQQAhhIQc6bchhBBihIDWGkLsuqQsS2rhoiiOchhSKSgZpIRLeXh4QB1bKKkVoJQcqSAAAEEQbG1tpdOp7/sEaUSQEMxAjRAxBuZlqbXxPK/RaHDJpTLUIhBq3/U0lFxoy6ZMVI5rUYcyVhZVgRDUFPuBV5WVRUiWFlmW1Ws1hBCxdZKlWhrPreV5RbCmiCjNqzLzfXd3c/D2Gw+bzfZc147jie1TlmvBOcREKO35jlKKUIIxxgArLtqNVlEUaZpXOY9maxDCZJoFQYQhsAmJAns8YlVVaGmkKt3QKqrc8cIsndQa1qlzZ+v1KB8Vr3z12mPPnjp56njJ89m5VpJPTxw7nsSp1uj+vQ3OZV5m2nDfshdWW2wEa1EdI2yMrtejtbUHjkfn5ueGo7hiHABz7/6due7cxYsXR4Oe5GKYA4KIkgQCmxCbWiiquXlZ7B8MT5w6JbVCCMSjZGFhaTQdxqNpgOvYorYfINuRk4mGiAkNAckLSSn1/FqaFbbnEgot382yRIPcsSgmRIjCtqlgDCACANLGEAClEq2VFpiH1jF/YpW0KsvdUeZMdtPNJVLTmpZMB5alFX/88cc7szPTSQkJwQQKwTiHlh1wHmMCEcLYxgRjxZXWhlAENYIaWMQWghnAbZcNBw8th0vBVlZWtPR7h+OJzBbmV9utuWvXr3Je3b17m1LKRVEkeVTzwqDJSpJM4ztZfPH08aWF7q2bV69cedq13Ovf/MaS13JxbZjkTqdjJHNqpNua7feHxDBhe54oeGu+VeaFRSybWkmWeNQushRYtDM/y9O0t7unOYv8wCjJmKQOkBAYowSXGkBMoVQSI4AxlVJqrTEkxgAuZLcRGMeM+gOvFSjA7t3b+ZX/8Mf/7t/+63G6+Vu/+ylp17vLi9qwuXZwZuVMOHOZ9geu61HKGSuFUGEYxtP8r1YWCCBkMMEQGmWUMYYQQiTByGEVL3X26NNnD0bp57/yDrFaly4/pQEUxjU0uvTkwldf/rULV96/tyVA0gDIsS1PcRP6Tc1Lh+BGo5UVMcKq3goFVxBiKaUQTMgSIMd28JFQxfGwUQBjfGQnNcYghDCERxA7BSEgOgTEt48lSTadpAoAZDBFoVQxgqKIE6/uebZmCmihheSQQoosoLDQ2qKelBwbI41cWll64qkzSTqxcICAO01Sz3POP8rdCOJh7kG3KuBO1j93Ye5vfufHvvi5b80uHL908fi1m1989ulziwvLGgWvvnOnsTi7PzoQZXny5PLeztD3fQzs4VBjZEdRtL+z/+QTz7z73e/9pV/6JWhcm/IohH7gREF+8WxzOppCRWUVQ6EJnG49XLPDtqpUWeVeAG+98c4Tlx+1ISrYaDzabzTa1JN33no9tPE7Gw+2Hm7WarWTK4uTeNo4tiSEcF08GPQtO7IQ1srNEsXL1HaiYa9Xb9RDH3/f937Edd0sybMsK8ryN37jN1ZOXDy2uvjg4d3F9jyF9tXX35ZSnVu52O/vnzyxtLOz9aEPfWhn7/Czn/7LH/0bP/nok48LI9+58c6ffumzTTr/43/7Jx954rHf/4P/+pM/+ZOvfvutd67dft/7PzCeFElaRmFNK6CkMsY4jsVKThHVTJxcXfnqg1euXbv2/R/96Kc+87tpnrlWu2JoGhdPPv206/gYg6fee/nVr/w5pRENa7kuRcWELDuNkFcscrwg8LVkVoBKXWgC8jI9M3P59u2H+/3pxSuPI2IJBXYebIxHSZ6Xy4sro8Oyt7sPJPLsiJdAc8yxhMggxymyabsW3Hh4t411y28Y4BaV1JgYKF5/83XSdCH2OEenTp+9ffv24nx9MBj8yMc/YaS5e/fug/Wt5WOnseOlo96dm7uujf/1//dX652ZzZ2tzfu3NCu5Ef/LP/3foxBfevJsb1r4MycYOIxsCyOQySpsu994/Wvv/cCHo45fCyyWxNt7/Uaref7cmb3N3RdeeL6scqb582deePhg/cTiappOv/yVLxLPoa7X6VKKSZ6kp0+eePhwzaK26/pCCCmlMUYr7bhWmuaZymzbURIQBM1f5Z2Pjlt49AVjDAA0xiilEMJHK2jP8xBChBAEpe+7BRdCsna3M81So7RlE60kNFAr02rUb968fri/7/mOVFwjLqTyfC/PSwi0EAoTa7bbVUpxIy3X8qk/mY6IhVmVY4wp8RDShKCqqgxSEALHsbXWRwlqpbRGyHEc13HKghkNEUL1en08ngKooyhilZC8gkgjBHq9kU2DwOlAEwRerdlsDkf79XpTKdXv941glBLf9+I4xgBSSoBRw4ORMYbaNkJaCM0Yq7c7lFIEVJwMJEcWsqWUACoFpCxA2PCzctyYsZ5/1xXqwPW1rblO4zs/emlmZlZKHhBa5cX9m/cXF1dUiW039OwatSwIIYC83qopzUZbSeCH1CJZlhCCo6jZbHb3dgeHB8Njy8dff/2NR688eenio/3e+PzZc9s7m4VGQRhVpQmiYDI6PBjdD4KgjMHS8smNjR3XI91uO8/zva2NNJs0gnqaVO3ZtiGAMaaxcV1naenY3u6hNHnFKwBAGPpSC+J4AMpuOIuAmg7GWhoNmRe6OZBaAa0RhggrQImTTRMcYhK6XKRx0SvxEM6Izcl2VJ50w1leAs6l6wYdzxdCGYgxREpxgi1EaMkYsajWCiIFoRZSAYQIxghBCAwCAGEJVRVERqNRb/xWb3hvfmal1Zx79ZWrx5ZXa/WQVWp2rvVw3TrsHdq2xVjWnWnyaaq1JNQEIW3VLtoOXF090d87bNQWdjdHjz3y5GLnBMsSYkieVo7PuaiwRtO4f/r0aUIxwRifv3BpNJ1k43IwGC7Ozc/Pz4dhOJwMS1aM0rjme4unjldJtre+EQYN2+IIScsCnOujU7Aocsuyj9IThFhaS60BUsgiNE77509cqcnmKBvubm4h6fzat25VGf33v/zPrzz11NV78Tdfe3s0Gt6/Nxwf3L+/OaaUYgwZEwghSvHRJuqoFGigAlAboCAEEGIIoYEGUiNlQX1acSKQ9fkvvfr62+sf/ejfP3H2A3/+uS+cOvNYs0vv3b/5zLu+r127dOvuyzYJKZLajDEFSuUQirKqICwWlyLGSyaniEApNCaEWkDIMkt7WhMhBISQG+37PsY48v04jiGEjDGtNRPwqDKBMZZATuNpLWpwmWZZgiiVBliESKAp9GziAcg0UpoYDATBSgOjjLYdX2otIYWqVjL9zHN/K4hgXm6Pxqwq4aOPfaDWdGcXZ27evAkz9blP/dHf/amfbc+GKyuNZJR2QjAZpR59/D0vnJud8TGmClBZ5DffHhcSI3ESyuONiD28+wAiYKTPud7a2WVlFWf5a6+/Mzu3Oh0Pnnn2+YXF7sd/6GPJ1jck5+dOX/jLz37pcP/goz/4Pbv72yXHe4f50uppRMUbr7+8ub7zoz/8N7/98is13xHGgxaZJMXBwS6xrfNnzl65eCH0ozwvb929E2dxzvKSUSEhBNymBIEZA49Un5ZtuUZRIeSn/uQzjz7y+OXLl1eOnQzD8LOf+eI/+Pv/S70R/O7v/3bXC/qDw4vnlwfD/SefONbvux//+Mf39/q//snf/ie/+C+uPLF9bOV4bzQep9PRZOoHdQ307//pHy0tLDzzwnv+/C+/PDPT/cRf//j16y/3h9PlpdXxaEKpjTHEBFZxRSnOGUd65uH9G1m5++df+I13vf999Qa8cePVZmM2L3tG2R/74A+9+vYbGICttTv721vMsbK0oBgDpZtBYCGcTKfUtqJG0I9jxgQmdjbJPBpgRs4unfG78tVvvTUZjlZWVsbxlFqNmuUeboyKlPtBY5iMQKUiO6RhXcLSDz0LgMiqzTePHa5t11tRNokJp5BLDcqyylYWj11fe/B2dX2vt/X8M8/P1Gfu37vmWN71t68CKWdmZvb2NnKuHbt+enHhC3/51R//sU9cf+ute9++pg20FSzH+alz51aPHd/rHf7x599moFqamSllVUOWVgAhDCGdxuOvfe2zP/H3fvLVb37j5MpqVMi5hXlesW53zq+1cs4qno+nSVWJJx9/btDf3j7YEgYurZ6cqzfWbt/0MN3b23v8yafW1jbyrIQQAoCM0bbrVIwBCAjBSilq21JrDSA0Ry/X///2kdYaAKS1PJKaYIwhgJzzWhiVeYEwiZPcjwKm5e7uNrUtRCxWloSQLCvbrdZkOorHcRRFWZICgyzPAATzslAGGC3CZhRFdSllWRUQIUxdxWVQCzjnXuBlWcaL2LIsalmEQgwBxCSdTCzLCqNQCAGhSuPkCMtVq9WEELAinu/PzXlZkto2CQJ/2OsbgDEm7VZIsB1P8+n4QMnEsiEkYPtws91s+TXPFVaWpEaqehhNxuN4Eh/xD5rNZsUZUDCblrwsGIMAKqO5ZWEhtENCSCDj05e+69m0mH77W3dWz8+ev7TC4Nh2akHLmltpbqxtvvb2/QtnLvFK728faglvXrsdBHWCC496eZGFYVRU6Xgw6Y+mtvSKogAF8APPGJFnee+gXzEGANjYXLMtf2dn6/r16+Pp5O6D+xBpBFkyKjmXfsNgu9oexPoA1P15E7PxeOjmtB4GRubT+AAjIio3akXQggYqhVS9XUuTTENZ8Jg6mNhESi10SR1bGek4lmXb3UaohNze6Le6fqkrDjkhLqg0ENpABDDKR8LCYv5YYESJcY20ZX6Yn15ZdZ1Qlgwr5LsBKzkmloGCUmikQJpSRDQACEiIoYYGaIMgJohCiI0BWiuEIEDAAMlE0tvY3T28JsG4KJO97bS/f4dVqmLF5gt4sGQAAQAASURBVPV79Xp08/YbZcFc1+VcJEkaBEGz0U3SoWBJ4KOKsSLRA79cmD/XaBpjXFWqdFzUGm6aTMLAQTYoqoIrJSR9++23SVbkfi1859a10XhKKD1x6kSR5diiJWdMS+J5zKiYsZlGvRhPnnzXC++8fNNxiVKM2BBAA9FRXQ8iiI2BWv0VOV0pZQyBADNdDAY9J6x3owUD5bGTiy+98G5q9Fe/+pbQ6ktf+cvFYyuOqZQWzaAdeIBzAKHRRlmUGo04+6u7HwAMQAoCBYABGCFMIKBao0zEfhCkZemENandk2cvxqW3vHJSa9uiIcHOeDJxnMbx4yvr9xjGLSwarjMajq9rc6BEjgCVleoPapjOD6eDssypTQQ3juUIoULfJUZNR1OMsVLKDwPfakkpy0RjYDDCxDnKmDCltVIKATIshpCQ3QH3vMBYSV4VAHiMtRy3DaQCAmoggJYWokZypAGxKRMich3FNC+IQSe0aP3FZ4HjAcFaCLiYBtvbxSTZCOvb40mc9+hP/MT/PUpmb9ycnj0ZOjhSZd21rK9/HTCNIGZlMbKwlZer09ynvoGysXWjIMQB5gJ1ckiHXA8wdKKoHk8NNmw6FtNYP/LIS4P+5Iuf33jq4sob77xMrLJStUG8s9tPuIH9yfTmnQerp84m+eSRR0+/9J7ntx+unz69/PD+rZMXv6PeCvb3d5mWW1u9559/HgJ99fU3D/eHo96UusihTpwxglwMABJVWgI/cDAhRiOjcFFmpsgti3z2s3/61S9/WRtObatRa3zqj/7EIBCEVjpWTz72+JPPriT5tmXDolocZdt+q/7z//Tn/uwLf1JvtDd31+7cexiGjbWHG1obu6m/8q1dyXQ8ySI/uPzI2cFw7dq1t5997unRKJXMQIAtSo3mFjGM5wmXrNdYnA+ygiKX37z1GcEBY1maTSwcjFPbQToZ73Rmo817d/NkB4L5syeWi6KIR2MHkWJaBVbEKrV97xA52CI1x2rZjfb95AAyMtkfRa322ePn/uD136faimqt61fv1cN6OhqFgaeqpBFZFoVpmgrOpKkqOV0IfItTPuSh1YCKdRtRhKxSA67Z8ZVVCCxMvIN8Hy7MDg+HqHIvn3828q183HNtvLa55tpo/+DBuVMXazXaatcYL/Z21s+ePZ+XVTwYOLAOpPP8sy+Ns2R/evBHn/rdMPAunjt5/9adWr2dZxWB1IeKjQaf/5M/fOzxJyejcRjUlVIrx1fX7+3EeekH9UIUBKInn3h6dmaGOvjKlceo7wZRa67dDaL6W1/98olTZ46fPLW4tPrpT/8pIcR2nDLPj/pFSkEplTEKKIMQhhhBZY6OXgiPQtNAKWWMQQhCCJWS5Mg3qrRlWQghYIDjWIwJAwGAwLII51U9CqbTxKbWaDDAAEWBLxjXCtg2LRk/OtfDMMCUKKUwhcSyKlkiBLlkUsqaXXN8L01TqXSrUS/LMi8y13WNMbZtd7tdAEBRFEf3VseOHSuKqqoqoCU05uBgYNu02YoQgo4HbQsrzbK0wpiEYZhnU6GYF1hx0q/VajZ0fd+HGA0PDz3bAQBUrBCc16N6HMdGGN8NgIbQIIu4ECFiIaM0wpAix0hlYwcYBAAkthXVw1LFz7zv7PGTxwoeh0Gj1ztotJtvXX8HGNJodz7/l28uzIaGAwwoNKhIYghwrdniFXMcJ3LrSUZCBzgUlyXDmBZFhrBuNeppGgMEF+a7h/1eVHOLMq6qot6qRw2/d7jtuoRXmjouolVnsT2ZDhT3J8PMQpRi2GnMQA2NrrqzfpYZmzRLncmqlEq5rosJsVyysDzT6+8aYxRXkAKMiIGmLEvX94pKPlgbPvfsd8wu7V+//kbgUwCVkooYhICmFigM8yPXJRAmEHk2d/BgOII47DSWbONpXiKgWJpSKxJKayMhRlAbYiwjMYTaIZiZEgCIsAMNVYZAiSAyCEIAtVGcQFRW/Pr1+5ZLGPOa0WN5InrDKYTm+rXb9Ybr+972zkaeV4wJwbXrhIPBMEmI41oFY5zFPnXqtRkAUJwW2lihZ/O8mA7HZxeWZrrN7XyYp9PZ2YZvOcO9ol7rEMdzD/t9L/BnZzrj8TRL0uXl5dDzR9PJEQVbKVHkeW8wmqbZh7/vo/HBeHdvLay5FUsphZxzfNSKM9BoBCHACBuAEDIAAa2B1vrgYK8lVKM1qw3UUrz4nkvr61svv/lnvCqaTYDNvdBGx04tBIFaXrBvHOQG2EdJSAghhAAhQghlosTQGGwAhgBaADhGu0oijREzbpLnAWpcufhd125ey8bV4cbuO9m3L6ycTCYH79x8e3HpxNe/+hoCC8mo9FPb6LwRqUYLWJQCBRB0IJTSbK5GhEslVQkMApphTASLHQpbDTWdTouicIgXj+5mWUFs65Of/ORnPvOZGzdvjsYDyYuf/umf9n3/3/7bfzu/5GNkTyflM09f/qEf/liWTX/ix38WCEhxAyiomNKGQ2MoIVJprDCUKHTcIss1sAOvO+mFNjoj8hOirIpyIjhE0NXACurn8qks01Dr+mBILa/eaqyO+9SCSkvAhVQwUthGVLsOYQxko7FNlvl4BHSDGAgqRSyu1GG9U88YtK0mNs7OQ0VWIigXq9T81q999Uc/8Q962+krBXTDH7h5X7785h0pTly/H21sHnY7K2lB+pPG+vZtCHtRuIuVjRTmCK/tjyZ3bm1sPLQdn3ju/Y2dP/xvfzTZP5ydmUXAwAxKAJkCvgunvb7vImDBwXCiNcgyiSCFSC6vdIyW3U4DaoixDSEcTYZpmne77e94/weTQ/Zw89Zz77u0/aAvDet053vD0Ts3XptO492D/VoQ9g8HolDPP/mus8tL48GUeVMlTVKVvge0zna37qfJtlHJxtpVz/NWV060Gm3JeZpMLpw/G8cTQwwowWM/9v2AfCjTUy61Q3yESJrGimPNvcODnRefOdeYsb77veebbvh7n/76J37sJx6ub6STEdJmMsmz3BDiYAwBNQASDYIszRG0gAI/9VM//edf/a+KVR/7/g/xqtrfHb3w9JNQoWTSJ4TnppKDuN71iE8RDzvd2WE51mVSFEWz2d082BJYl4KXZY4hMRD1JtPpIL7wyGUS45c/++qTl54Z9UdWvTWdHrq4jLOstdA9aSO4sTEZbxC8unJqeXNvazoZdMf9E8srN3v7G/s7Vx75yO7u7tb+Rtj04mFPFrM/8OHv/2ev34GQeY5PpaEQCUi2bt9XlXzyyScfHkxXj18O6g1NDx9ubXfqQSOqOb5z7ea1B+vBeNIDhmdZsbN5kB8/deXJZ65/+9Wt/V2DyfMvvPvH//bf+c1P/kaZZIHnaGOU4kdEDoSQ1BohDAwCR83+o/MWQgihMgYCgOEREgtIKSGECqCqqjDGUmolDcRISB7WQyH44sJCo1F7eH+NECIgyuKCUtvCBLkoz3Mc4FqtZlkWE1WWZY7jlCyzLMv33YJVruMhhKhlC6EwpoyJ4XhEKT06s4uq4lJSSrXWeZ5TaimlDg4OgiBoROF4PLVtG0MgeDEZMwBkXkxarZkw9IVQgivGCym44Lwe1RoLzSRJbGpVXFFsrSytDHp9C2NlJNBgNBoFrmcwrMrKtWyLUGxjxpgEGiElmLL9QGuUZ4Xj+Eyy02ePeV6QbSetTu3h2p2F5aV7dza4KInltdvLcZwuzCyd/eHH3nz1TSY4Z7zTahuliqLI00mjHk4mk/mFRWQQBRYmqlb3ESRZlkklq6qCDCqgwlrQatSLIjPGwhRNJhNq61anlU7Gtm0MAWmRRqJtcMSUKVkeRAFF3vBwUuUZtPOg5mR5Fc22pBLtdjdN06IoqionhEqh0zTVWouKt1pNqSHnqlZvKUkgxKdOXKbB8iPPPpILuP/gOtUUQoxtbGQpdEFdKpXZ3y3f89LjccXcmdnVunPv/rCsjOMZy3I31x/UosbsfKQ0gBArIDEGQButlDEAQGCMItBBEANgaY0BMMAYhAkAHCCklOp25r/jpe+r1cPpdKyUsOjg2rV39vf3y0Lwil575x4XzLF9LWCnMddo1jivtre3Lei1awtFUWRZKrjRAmJiUzs8czxwqZ7r1jfW1idF7IaeFRAMwGg0QTAqy5RoKWbbrWmSTuOUV1VJ6eH27tR1p0k8uzCbl4UsuCwqz/WBAjeuXesuqFv3mR8pBIlNbcYYhForw6SAwBzFtRBCGAOjoDZcVBxjHE8GwBjqeutr5ee/9KXzF49tb1/rdmrHz57L0yxJkl48ubNZbh7eO7KOEEKU1MYorRFj7Oi9FRKDMQYQG+UKGRkZKmVDavcn2eVLHzhx/PzBBu9vWv09dQOsffnwptGkVvfCmpMfDPXEe/bFF/rrf5FOoJKClanNOKuEYwXIaGRpZaSSZnf3sGSMIhJFkVbSojBJZZqmtm03O11KqdFQqP5Tzzy9t38opN7Y2Jqfn63XmmFUN8ZARNJpahQL/M7t6w//1zd/4V/+239OEJEAY2NrIIGEBhmgDQZYGwsCS3NAbSo1xJgwoR3qKCYODh9gPFEgFpVBKIIQxikjFpLKWBJUsUljiYnDOafYICAQIkoPuNCY4nhQYWAjQ6DRGFRS9bTWlBLJGbVBOgRSNwXgSiJKGtv3lRA0cE/vr4P/37/6L0D5JZq1HH7u/NLD+wZCa/cgThLXtYGWC/3R5o/8+HcKfWM62WEC1ILW+59/6c2bg7iS73r/hcHg4OHGeq07+1M/87+MDw9/55P/8fjqMQOo0CQri4O9je966b13b1wflYltWUlSfPADH9QavfXWG8TQZqM+GY3jOG626lVVubbX7XRKXv3u7/w2VzII6mvr/MnHf+JLX/3ib/zmfwnqbsGzOI6XlhaTbKqBfPSx8wd7966/fisKmqaLZ2dntx7cC7yAV+xgW3Vnmlke375999Klc1LKdDbe3d4RFTPGUEqrctvSbm97W0JjKFRKebYjBAdQWpbjkQBDBokaDYb+wvznv/A1gKrdvQdf++bXzp2Zb9Vpy3XynWJzZ/3DH35xNB0oXayt74m8jNrewWgrqIenz6weP7FU5eMiS1963/NxUo2H05XF50o2Ppj2sB/wqX775tsNKyp7LEP8VNtfuHD62s6t/WoEfauo8trCHNFQqFJZ+MHu2pXnnzzevXjh3IbtBrWWPnH58sH2tWlvqLS8ee/ee9//Egmtwf7+KB/fuHd3YWHJD6KZ1iw2VjzNsOcDz+wON7lOsrysBcFsd7XTPvmPfuGf/f4f//7mw3tz9ZrlIM00xfa9uw8NJB/6yF/DDplOp7VmjeHS9/1Rf7dgSXNm7vNf+LJtWYFltOJZxVOmRpU8c/5clhUf+K4P/odf/U9Xrjz2Mz/7c7/9W7816O83azUtuVLKaKCBtqktlAYGKqURgkcFJK3/+zoaGYARUBpjdBS+RAilaUoplUJbDhVcAAAIJAvLC5Zl9Q76jagxGY21MggAqE3JcsZkvd6wW7bWev/w8PTpE47v5XlObCvNc4wppbTX63W73SzLhFBHFpaqSGuNBiE0TVPHcTHGfzX7YuJ5HqW0qqqDg4PA9RBCQrCobnNuEDZBEBW5SOLMtr1utx3Hk6PhBAJLcsqMYaXolweG2LYhCGGgoBTyaMp3HEdD4DhUGzmNx77vh36kRGVjWElBCVFCCqE83zdG1RvR/XsPDWJRy02zuKz426/fGI6TldW57bVtLW3PC/Z2B7LqF5nWkgguR8NMKZFmSaMR1h2r2YwGw33fD7XCk8G0KrnjBNAA1/V4KTUwtm2XmfAjh3MtOa1K5Xq1LBGHxUiWqt0lxDIVU3fvHCwszHE2RQgYgwVHrUaz3XIPRiWXblBr2EEARA1B0mrOQDA5kvjG4xIjR/EiCCLOVVkIanuOFfpBDQLaiLoaOpkA73r3+/9s/X7FC4NJBbiB3MKaAgW0jaGDYevc6SW/0Ujzsr1oURpWWji2vbS6AoHO+FRBAzEWQtiYQKMhxgZALSFRSEINgYSAIYQAMgAoqRQ0CiFUMVkBYRF6sDvAmGLsHY4OOp2WlLJRbxsDR+N94xWU0ixlcZxhjFvt2rHl06E/S5FLOiTOD7K49PwozgvkKAw5MmXkUgHC3f4+dR2Wl3FWhv8PT+8Zd9lZ1f2vq+16+jl3LzP39JnMJJn0hEAIVSAiIB0eFEVEBAUBsf4fQUUFUQEFpImgFMHQCYT0nkzKlEyvdy+nn312u9r6v7j1OS/3u/PZ+7PWda31+/2+hWq3mypleBAE/W5PZZkvHGugu7Sal+Ox8cl0GK8vriKaQb9fr1STfjxarXfave37dGbc40fyUhhay4xBx2VCiDTRaNEYTcEabRHJ5r626PsANEsioDYk1SgVv/jZw6nse8Gw2Y2OHFflUkNJWa0Vzj71zNL6+ghxlNTGWGstAcIYF0IARcoAKCIwYn1jS6hHUDUQi2nsFsNQDrccPjTknHGzc2Z0a2dNctOIBumgmfmBkWbRWOfi2W/nCjlpKGN0lA6z3gfe+4GdWy7zA/6nf/6+l7z8xdddf4Pvle+86957fnHXa1/7q1OTNc9n3/jmD172spd9/vOfH8S6WCymaVod2f7KV/3aoUOHKrU57owMhuLtb3/7d797z8tf/nKAGrU2TXWKLmeO0l7UQ609AF8bQalAShhjSmutrUWCSBggoCX/U4RAmWHgJ8a2+sOnuds31hJTRGQIaa4RrLXWJdKVoAEUgNaMEkJRIwXLGEgpEYgFF4AAaAACQAGskRLAUdoBYJslDkCYvA9gAewwZwBEQlJwZxgp5+ng/LmWEBPD4UDK0eaaBsvB2OXlBeGvh+Vz42PBnp1XTo5tndt+1fiWwskTZ4LQPawPTSv9s5/9dKQyNlquGWMI5fXq+Kmzl65/znUv/vD7XvKCF77qtld7NiXACbBapTI1OZMMe74nPI+/+52/+ZWvfGV5cWVtZaNWa9iaLocFVFoVk2tvuuXQ4eXPfu4XviOYne53OrnWperoylq3VPAcr3ji1Kkbr7725pueaxX0AB975JFXv/JXK+Vyp9PKk7Raq7S7XSSWUMxTcvrUxYmxca/Omxvddrt94LJab2PAGRInsJqApVQZSghhNMkHUdZJkiwslyUxvX5yaXHj8mu3Ly5dnJycPHf+WPHK6bWNxELDYHdl7Wlj88ktuyqF0tpgQFiSgz21cOL4yWcLJZIOW8WClyRrWS4LAeu1l4QDhcDVHEVBlN1SAUqxMk8++sDkjZedPXdias+VM5XZE08/UiOsvdqyzMUyN0R2s+4XvvbVUnV8dHI0jgcS00NHHk27y0VuCXDfL995//1btk5MbdteCEdFaXlhbUU7zko/3V6f82tTYw3n9NKR8caoL8TNNz3nBz+548KFtQN7bnz+C29b7q7ML55qx72SE+ZgpMXx6fELFy58+5vf+Ju//5unjh15+IF7Xvbil3XXVrO4PzVR4x697WW3fuMb//XyN71htF565vixBw+fqkxtT41e2Wg++MTjlpGFleW5bTt+/wN/8KlP/v3GylK1UkoGOSJQBABgwBRu6jk2vbnm/029OGeUUgKEc47/K7dUSgkBQKzWmnNOgG3dMsc5HwwG+/dccejxJ5IoNcb4rqeUYhTCQDgOBQbGmvGpMcJZsVjs9/utZo8xhpgxxur1BmM8TYetVisIgk2113AY57lSSlVrNYuEC9cYo7W1FrIsC8MwCHw0ZmNjDQBKQeAUAosmTbTMLec2zweIplouGWM6zQFjAixBJMQQKU255hWDwrmzZ13PQ0uK5WK72USgiIox5ga+EHx0dDRJEmsUWh36hTRNgWjGqNXG8USaJqVSaWlxbcQUtsyNeg5srFzyRam11r/1hTecPHrx0ulLlUKt3e6WwoqUNvTCleWVMAxL5VqSJs12q1ItuB7VmCiMueP4IAphKU+l0WTTsON7FWWzjdWOF4S+VxI8LBVHxsZr7c6KUdpAihzqtXGDLOoPSkXXI0ygKxydZGmzm4blRqlc2+h0T587NjZWHAwGSZIz6npeMDE2vv+yg8ePnhhrTOcyttYmqIklWZJxGrq+M0jW3KHxSUmaQbu1VvOCRCsUVIIVDshcC1EcrU3s3nmdcByjFK+MIUWFhhBk1DBtwyCw1iCnGpD4nuv4jBBtgQqHcOallhK+6XND0Eg0EAsAgHzTEQoARslatZSmqecFa83hQw/fu3fvZVunti0urAiHhcXyoD8sl0YAGXdMq9Up+iNZLDvJcGysXilNBU5eKtZro6EhxlpLTe45zKFF4RQKYTHq99yip6TlPCBEc0IwCD1u0OVC84w4btIbXIwi4M6g2xOMo1E9abTWbrUa+gUjzt98656zp08Rwij1KJN5nvu+73mOzC1lQCmllBuDlAIXqFRqkXLHRaCUCYdy4bjHnjp98OD2lY2Fi2pty+y2Sql67vjSkUePjwQVSDNCCKUMEIyx1hhKQVtNCEUAQhgQn5ga6jErJ8CUwWqdhvMXM40KAYQQUoLnBoxa4jqeV1XK5kYicKMp457WLigLxDz3uc8BgPe+9/dr9YLB7K677nz4kUcBnT/5s/977113EwJra2vf+vbXhVf+8r99iTBQmR4MBsPh8Nprr/3xj39cKBSstZTSWrniO8LlrFYuVYoFpKxQ5IiUEFJyRaEcEIIICJtLfgLcEVqDBUI4EGY1zTUIZYAwbhlaFKXyaK9/iXBpYDUoEp13BOXSppQCIpGS+p4jLBLClFW5ylEb4XigDAHLuGQOy3MNRnz3O9/+r2/+x3du/8HrXvert9zyXES2bW7nx//uH+578F5KXWvhV375DW94w+v7w+ba+tJH/u9fMRijHJglDtdlr5gkWcBV4Dq+s+Txhid8P3Su3T9XHakCSkcFzYXe733pA5OzB/rRYHJ6XPhw4fTFgsNWF091lp0wDIfD9IqDW77ytX8fG69+//s/PHL4WaWtoEwZgoYcO3L40sUzy8uLwmE7ts0o3fvwH73n8//ylXar1aiXidHFoPCi5790z8tvMRL++s8/trh2Lo16voPVuk8RNxZXGuOjw0TmiS4XG0fPLB05fqEQ+n5tLLNw6vw8tRj4DhegQfmhBwBaa0AhaBj1hnuvvfLpJx9dXjj/Sy95w6B9wQ2KQxUzhgSsRYdRyrgpV6o2J1lqMpNba7kbCr9GFKs0qoBJa4UFoirQisDZvWOLSy13CiTL5bDvEtYbptUxzy3mURyvrzUnx0vLi+dGGyOomOeWBLIszjIp3bID2oac+Ug21ns7t+7aPTrS3li5ePzCtddfXtyzzzX6eIyxwkQOly5c+N33/v499x9aWlw5dvjpeqPq+/5g/uzuLbNPPvQo0WT/tVcWyJBx5/SFxQPba1JmhYrbHWaLyfD8U0dcl5058dQVVxzcaDV3btu3vNadmtkyu3VuY7VdLYaoNCHg+oV+pJGhU6Bx1Cr7/vra8sc++pfXXHf13NRkr7ex3lqaHqmViu6dd/7kt9/9/sv/4iNG5c888wigztNMxur4wim05OnDzyDwVqf7w5/8+Ir9B/71i1/4tbe+Jc8SQojgxHEcREoIGEQm2P+TWxpjEIBQ5Jxba+n/uoE3HUqu6+a5pBS0yj2/GA+GaOgVB686cuTIYw8/lQxjYjlBlFIzRgkBrbXn0Uinrue6rtfpdhuNUUBGKXNdjzFGKQUgm/Otbdvm+v2+47I0VYhSa+t5HiLJcxWGYZIkrhf0B4NqpdTpdMIwqFRLbt+N42Ga5NYCo55BHvglQhCIVUqtbbQrxUqWZfv27et1umDs6OjoxYvzk7WRbre76bC3HDgjlx04sLS0hGj2XXZZkiRJPBz0+sNB5HDKuFDGFcICkTJXnltWyhhU2pgszxYvprt27XnwwQd37NgxjJI06y9cWL50YmXX7n3PPHN8fLzKGLVUD6LWnn072+22BcN9l7kOcDFIW9bqcrmcD4e1ejkeSsdxNuVvruP3uz0v5L7v5zJjRBuDly4uDIf9OO2UatXx8alz5y54AfVc6wQEdVoIC8N2QgWxRAfVsgGz0W0iNb2o64hkz+7LVleby0sbV15xrVXm6UPPWI1Rb5DnCYLhTEiZDVZXHLfHmeeMELYYNsojedTmApFqioRYwgQb5LrsQJLmbugur7WfffZoo1qIM+KGnsJ8OOysLV1qFKpMCc8tGkRF8kKlkGeaMJ8yLgIhPOLkKITLhU8pRWoYJ0wwSgQBh1JOCFpUlFrGqdGgtR2ZYdt2jJ0882il6q82T6ZZLzd065a9cd8F64VFfvbsRjGw5XIwGDYZ5743Ug4qUZKLQuA5YnSkplWOSmqDSAkjNvA55Qx4sdvM0XhcWwPGEkKq1apKMp1rz/clGsq4w4XVmlHHamSUaq0tYKqhUofZLWz+TFYslIRwkzTzCTFotVWcUYe7AGCttmgRteCgETOtMXGpJ4fx6o5tsy4LW/P53p3XEZqsrs3LtlxeaPIhccD0lXFdlzEQnGeZUtJwztFqSilSpIQR8JCGxpasKmusBEFGmC8NMuFQRyhNLSeiGEjZsSYWjFrKHdfJcsO4r3PGoAjEN5hPThWeevoeyiJAW63wHbu2vOo1v4rgjI/6Mmsa1T117mQad7JsuOk76nc7ruuGQXDrLQf/8i//8tZbb0Xlp9HKG373bT/8wX8KIYzuOiwZSDoYDAghSqdMaKnbSGMgORBpUKLOGaBBRAqMEkuNtrknXGvR8Ug6QMIKSezmWQFtCQhP4o7vhFIqxtFokNKCyxPdAakEBg4rEYKa55RYZL7WFoXVJgGg5Vrt/b//LhU3KeTf+c7Xvn/7N5XJ5uamLi7MUwHWAqFwz32ffeDRz3Z7MRAAAUYNBnEfYIERoOWpZNC1Vnea5zgdtNsalGOh8/UvJcLtAHJQZbBeNBzk9hh1rERJmCVMcp5TzExus5jGA/Wtb3ztez/89sz0luXltXKxsrK4QFiiJDuw/8qp6fGN5mKueuPjU/W6O7944pFH7nz37779oQcfqZSL7/ztd37xC18bbYwvr8LX//0zTx/5xUjDDQsyi/J+j3i+Xym6SdyNc0tpgbDa/OpannSNzYLKarVcnT9zjlpAnW6bm8jlIE0GSqnAL2kJxBLPJQf2bK1WnI3VPMtNpTY6TFLhK+LGJtMOqwpgIKyxMo4sgSCTmVtwDdo4VSO1mTzJmuvrHMN6uC0p+NKS7jBmeTjYGIzX0GdUAgfJh4PB+JTn+8Xx8elOc3G0PuFSmmiVpEPIGCIxloS1Uqngtc4s++WZtYVFb2RULi8cmNj55dvvjxZXrtlXt9bGGx1RrFCrrr7+YOiV8kwdOfoUmLhau7IxOjNYml+I0l+64YXMssXWysWlk854cXWjH2+kYyVPyrZbDEW11u7j8vKJ8elSu7nqwsiBvddsdNcpZ65Di4Fz4thRNcyv2H/N6eOXtFUGhsQaaoFaqNTGzp44tTx/6ZrnXMMZZio7e+HUlsnrxkerSxfP9yN7/Y03nLtwtlgvZ8lwdWllcnQ0TfMwKJ06e05Q8YbXvjFwxKmTZ17/+td/8XOfK/l+plKjNWWOMYYRyjmnlDLGCFHWGmDkf6/BBCi1Um3eVYwxlUolTVNK0fVdQkie4dzcnJLm/LlLg25fCIFgOQVj1CBSGmFyMmSMcM7CMNDaGGOiKPKDAhqwFlwnKBaLUmaDwSDLMq0DbWSep0FQR0Sl1HA4NAZ9399MCDHGjI+PK5kVCuHy8rI20g3cKBkqhWg5UCeVSquUUF1vVGWujeJJrCilcdqanRt55tCzLitddcV1Km03N9bDMEBC0zzzPG/79rlLCxd933/myOFapaqVEoy7rjscDLZt27a6llGhpUoDz09TxQT3Ao9yZYc4s3Wm6NV2bdsFhEopOefxMCfSiTrSF2I4iEZGq37gt1pxq7eKlMRxPLt1S6NR67Y79dpomsZJnAORykqt86Dgpql2OLMoHYcjqsmpsY1WM/T9NIt0nqw3U4CUFx2LfGJie7ezViwwQm2WaM5Iseh32muEM2ULSMzIeJlQTb1k2B5cvHRG8KBWq+RJIqV68Qtf9PQTj3mNsusJRKqUAqSlQqHbj8oVpytbngy3NUYSAUiNIYBIbKaRGmSQU0AOfqkwMjF+7PZv1qpBmHntbv/Ka6/YMTFG++u1wLEDgQkDSiw3ocEjJ8/2+4YKD0We6hYjQMCxKBA3LTYI1oIlQB1gDugUiAKOYAAMAR686u1zw6RdqrAL888USmL77lkgili1ttYmWHS7xBqMov6g2xsmzZX1NYrIECZnth168rDU5pFhZ1/dnywGG61emsbYS/2qyImVORYLje5KmzMFEuzOq/Ysr62KqarbonG3HzoiKHndJCnUqwwEI6TTXuMFTG2LISgrduzac/7McSdsqr4R4MuYlkoAOiYIMuOUOPg/emjXgkJrPUKzbFgi1cuvuGmYRDfccN3a+vKhp5+J44Hn8JEGLK0tAMcklSyvGRM5BdQGtUEkVlsJNuB2lBKtTIc5caoypblm1pKeYV/nnGoVEUVAWUSDNkdZVbEk4GgmCNUWs4LvOaLcaaUGELQCUPNn1q+/9qp777p/oxW1e+ZP//RPfvu3f9sa9r3bf9pe78VREseDTi9GkgNYQEsYZCr3MSyX6n/50b+uVCqO45y/sFCujL7hjW8tFArjEzOXH3zenQ/crSFDLSlleWqNcsA6gJRQy4Xgrm/0kDNuFLgeK3rBQLoEXZBdsEPXMmk8bSjhClRXUDVSm+YOZ15vcX019NxRf4cQG91OHmkYGa0CsVzwbi8pFAprqyvcIWiZkZpSUgzyV9z20muvvR5Y9vOf3/Gd7/7w+uuv+JM//rOVlXUlzfve9z4gVCs6NjqLslOrV7RJo6jbH5yxZsxgLAp5oNvdbrfVDQEYMmos54IadCmOWm0IEcyB1731dVdcc1kQBPfff//tt99+/fXXv/GNb7TWnjx58pOf+MTkaLFcLqdpum/Xzj/+0Adrtdptt91mZNH3TIZkoeNefePb2nd+dWp2arHDTvTdfrd42fkLW2qDhWOPPOeGu0hwrYTMB71xYcFLeaHLOusJFMb7QRViBpkDwGng22iFV3Kpu6PMHRWTpyAqcZcIS3naNgmGKvAqUScrFmop42N7tg5bzVEXhpGO8qk8qBVLkko9GpQr5V2duJOHnULo5BF6Tk0ijbzVRq2+t3oD5IGy6saDdvuBV6ysz6fzzcbctgdPnE1iUw7Gb7j5LWkShRN94/rMt7KXeJVqpmnSYWM7G/cdf0A4wOMgz6BSrMeDFYfJ2fHJ9cVuviFKhdL0FXvdYq1o8vHR7QObuq6z84rizbde9+nP/tNlVx7g+4vnlhe3bd9xqrNy5388tra29o53vP7MmVO9bufeX3zz8t0zK/PPandE07LM5cC091SEMKgyP1HjifSvu3zrsaceff4VN4XXvPKr//nN6yYY+PEwOdfrrsn+hknWf37/I7/82lcfWc537t71mle85jOf+WxfudShMuqPBMV+Eo1UmFLJY3c/uHJhbWa2zFmcNC9NlEY9KD787MOiSFhBGO4Ao4vzJ82SqFXTy2++TAy9Iyda//GFr1Tr7vZdM8994Uund+9dOHGqWvClMpa4wAC0BsgIRYUaKRImgAjOiQVrQeW5YUyEYSEe9pCb3KbcpxlVyuZo3Fq5fscPfpGkA+GAVwjiRGpLGCdMwHU37+YQ7d4xc+zoU6sLxEba8b3p8al2v+N5oNIkzfNMmdCvtQZdBdzmNk2GRhDwQ4w8a+XV11x94fzJQdTLopgj1dJEUTxsx67vuK4zWpuqhJVeFHGqCWd5lhlMtmydLRQKJ46dUCl0N/qhHwgDaYJ+Gu4o78ZprITFibGRCxvR5OiIYs4glWCpVOa+e+8lEnOV18J6vNGpVopFz1uNhkGx1pja3ul20nxDK3R4EW3COTUyQ82L/kjczeYvnBytuWdOzzeXBpdfOVstkksy6qxHW6ZHNzr9bmdoKCpj4ihhNJDSBeKPT84+e+rs6OjoUOeJ7DtpiXIHrG41I5cVnCBAlMrmmPNzJ5quE/rM01oCVwiMELfISiZLKOSFkOUZdruZ4MJl5TSz1oCgOumljAYXl/tMSIUplbsQSTttFUrl1bWoUqycOXmmUQuLBToYKGOdSm26O+gbUKMjRWNi0UOXOp2VYa8/dEXVWk05o8Qa7RR8TnKGipddmkfLRceM1yo6dEYjtzoWMi0dSQX3MmMI76OTAZW5CZkrR6a4kl1jTENUh1YVnMCkOVC0zAK1WhqHcMLACDvUjBCOSnJGUpWOTHvdXqJUuGPr3jNnznabKutWyoWp06fPxmlWKOUjoyXBC0lsLpy55HulU0+ti7IjXHWDRxeOP9bbGFAF/v4949dcGcue0FVo+54oeqHKWLah11m5z7v9HnXY0sKycJ1hEg36Q0aYQYjTvFAogEVjVH8QAYHNXFbPg0x2LctECFpLxwmzRIHNCPeBgtaMABrMgEhKXcdx0yzlgqMFx+HLy8tvfdtvPO/5z7/hhhs8h6dZqnX25KGHP/yH78+yxBHcDYglAwQg4AlOlUhSKQGAc61hzRV0rFpeWc2lNAjaWtsYmcmV4IKW3fr/2HC1tlZZa4MwyFIdhEVCLGVmMIiGwwGCw0lAhDI4fOTB+25+ztWf/ZfPCO7+7d997J577v/Sl7586uS5XCazW6YoBUpcRqjR5I//5E/+8z+/DmCjKOp3h+965+86jvOyl7/cdd1jR0996Jk/9QO3UCq9+3fe98Mf/6JWL772Vbd9+StfvHzvwd/4jXfOTm355898+k//5AsqdYTrGGOcIDdACAZKGqBM6TzPbcH3DGKeZ65wCmE1bjGg+ubnXX76xPHFpR5CzgNIhiTubBjS3Dq7I03aSnHh0FarmaQ2igaMcWuF0ZYLJwzDQSR/8IOff/ObPxhm7a9+9Wvf/a+f+974Qw8e//jH/w7AAnAANNSeO3/Od8OF+SVtzZatU91uDACO5w+jyBW8UasSwrTCKJOCCUZRmaEAEC4DhDxPvvf9b33ju5nrup/61Ke+973vnT979sMf+lCe53/8x3+8f//++fn5Zms9CILHHn/k/vvv/+QnP8koDatuwW1ev7d/8uz35kq19Rn30XvvJqKw2Oyhlqs3QHF05OxC86N//Z5te7dmdv3ndzwQtVqFkdraINp53TX10RkSjs7tuXykXu2uL4JODFOPPXU/rex50wt+ad/Y7Ov+7A8OXHfljuv3MJHrkD/yzMnOhrzu4HX9aNAHVSiKbJAjkesLJ9JU7J7YWS1ORmuXGtX6iWfPjG+ZCMNilndr1cnF+awyMjtSLzPGHnrwicmRnc9/wa3zq4uNkfHbv/8dafW5C5c458eOnon60hhTqxa7vY2rrromz43WxhqslmoP3P94i1xqN/trq63Z6ck0GV5KThqtXeEefuLkzPTU9OzMuYXzrW78u7/9/jgitVpt79RWP3APn3z27JlLkxNbjaYbS+3hMD918pyyhhASBsXxsclLly5Fw3j/ZQesxj2XXdvqDYyNi5UGpzSP7ez0lmOHF7UdlCplnYHLpp5+amn3FRXq+E5YpY5duHj6xJnl9Y2+74gCJBCtkbifId06WRipqUa5vtLuTTdGu+fmsVSLokHRdUPXriycPH82f97N1wWluR17Zrbu2PmTe35wx4++NbtlUg6jsig+94Wv/o03vuXpI3c6QXTfoX9/01veuHvvtcdPHXvkkTuF1SMFd4UAorDaUEgsIHqUIdHKArFgKANmEQARjeVMWEI4ETo3DuOASJEy4CpXgcu0zAUAJbQQeoTZLEmNIZT5jHJGyc3Ped51V2/7wff/7ZdfeeveNfP4E88sr7eLptzrDXzXG6mPljXGed5bXeFKlQWrVMcB6cL6UhbHXDgqT488/bDnc50NPc+Ler0szYTw0JB4EPWyfHxmNB72fMGVQxNpZqZmBt3egX2Xbdu2LYvirVu3cgKLC/NOsey4xPNZp7s+NT0ik6TZWhK+1+q33XK1WCmIjFIKpVJxY3WNCjGzdbzfFoKS0XrNDfzl1Q1UWTw4jXTgexgnPdcFgkWjuUbquNgZtOacklvmja2F7Qe2PfvsScknr33uFaur68IL/MD0o4FwXASfWuu5jsMwjfTdP3u81YnKxXKlUHOFkw71MImVUdwJjIVMmyDwB71EcNcRQRQNfd8nyEG6wguMVo7LXN85f/zC2Ng4EFoq1tGaNE1hM+2RUkqFkpsaIMaZUw75MI0497JUD7q9udnZLI+yLGOMOE5ZJXpx6QIRuljyolQx6uQ2YxjNL50mgEGBoFWUAdqcOzbN8qIXxEnuhV5YLBDOhHCNUZxBtVxqLl5EhrlJkJJUpa6DGk2axNpkQDiiNUpmCqnD0mTAkfieG+c5MkIpTbIUSWYVBSEcEcaxsZR6bri6sD5sRknaPeNfosy4XHTV6dGyeNHzdxK6vdPfkCoP/NB1w8t37QyDksMFiDHhSsT4qr3bSn617JUeu/+u9bULjiut7VTr40a18kiG1bKMkrSb8Upt1KDutQcAEHcjRpnn+0As5YSAKATFQS8q+AUEOT46KqVChCTtjkz6O/fA2WPYqBS1bjJujUZGfQvUIhpMCQGL1hpDGAFAA8bxAqnyf/zHf7riyuu/8R/fazU7wmG97sajj9yzML9aKgZKRgjoBtTagrUuodYQxTgQwhkBJjI3YMO4y3kt9Av9nqrVqpy6kmqlLKXUGEDLhBCUCUI0IVAuF7NMUUqMlI7juY7T76WI4DhMxSl3zCf+/u+KQalaGU3T9Dv/9YNvffO7lEK9UbVW/8c3vq4VFbzkMfXt//hG1OulaTw+Pt7vDCxnlXL1vnvvJZQGga+MrlTKa82Nj3z0zxl1u821//zqv7mEXDx3/q/+4i/7w/YwzhhsC/wpwWhutFIZJRzRKEtdR1TKvs6T4TB3vJBzGvUjlW1wxrSWk9PVw4ebvuf/3u//4fZdM1kqv/C5rx84+IKXvPg2Svwf/fCnd/zsx6973etmZsfq9frnP/+FX77tNZ/61KeUlKVScX19nRBgnHNKm2tNa6jnlK+95qbPfe4LDz547ze+8TUALZVmjKX5wBH+hz/4we3b55CYP/7gJ266+eCvvPrFrs9vv/37P/3Jz97+9ndMjG8pFetf/OLn3vLWV33hi58yNgPkjsva7R6hLnNZZ6MHBtqtLufcEZ6SxhpQuUbENM6sRiEEIBJCKuNh3el9+L0vGzTP5pCefLq3dWpmfKz4gWtvTvKNcp3+sNm8/JqZG64rLa49Wh8v/Z9fvWm56z15ujPI7Na56WptvFbd3m5mP/z2j/PuBQp9UhSdJB2rNTbag86lR7ft3HX82LHH5s+/4fW33v6de5Ub3HDdC+ulEb9cJCXvrsfu27J18uq5mTpzCqJ2+OilI4/r6cktQTB57uIjszt2lQq1iV1XbSwPv/rv//jzux588LEH/vNrX7/hmufFidq3f8/9j9z/yKFHzy2cafYWkrTPmdsfpo4jHnzs55VqoVau7c33u55n0AiHA6XtVv9C/3zo1hqF8Ysn5jnLCahsqC6/8oaRnSOEZfc++AuPlZ9ZPTVS2bJtdsphWgReq9cRrvf6N71l4uG5v/uHj2/fvdNout7taNRxHDdG6l/60pcQsVwp5kpfOt+iOKiXOaXEssVuK3rJrS/2XWehsLTRXZZ50m42ts7uP3lm8YknjxEGWaog7Tz58LltB245fuRi3G2XSN90LzQXzmpLDx/6qcPW3XLIayP7p+oLdP7JlX6p6Ko4d12fCuBO4bEnTzz9zLmf/fQnSd6L4lbBxbLLL621XWS+Fd/5/o+dsszbLeWU0AnPLjWvveXlNz3/+V/8p4911puB728KD10GitmBlkwLRIOAlAAhlhFCEBkgQcsJJYjEWEo5gKFAKTCPgsmsQwkBQ9AqmVsCymigwnEoY2xqciYeDo89e6RYDA5etX+byoSr7/j54Wyoik7BFTyNMmU0Gih6IvQ9q7HeaCyvrNXDQj+yVseCQZYlWQyCMaIpR3CpT6wwyjDmVBulgheur68K4e7fvz9J2Ic+9KE8zy+cPffSX3rx29/8m3Nzc2984+sXz80DEJUbIVxE7A8GvnC4y6I0Bi6yLBNe8NIXv2R9dWOsUb/22muvuPqKI0cO/+H7P1Avl4b9weT0bKVcrpdLxOtfd8POagN933vg/mcXzkXbth2U2ixdOnb1LQff8OZX/OiObzFPdrOVoFIcn93DszWvVDh3fhXRABDBHc6Y7wgu6NpqZ9BK+r2MGJFFOUNnaaEZUpcQ4vp+qVxOE805y6RC5khEZjJD8mq1TA0SQ4qimBnJhCWE5Jm9cPySXy0zbkpFz3MYIYQgGE1kbjkTQSEcxu2wGAJNLGYEheP6lQpXOi2GjpR6bLRurYNU0gA09KRJgkLAWcAFkx195cHLz545hSgpJ3maUEo0as8ngBYoesWw1esrrZEiRdtqtjwvyHNjkeQ6F8LBTaca80ycE8JQIwNCuOMy33COSnoOzVUmhNDWIhIExh2aoaEIcZRaIxzqGKlNZhuN6aHx06QXFlyXUDNUp48cfdYoIHZkdNQYjIeZ0bQQlKMo4pwzZ8TxjLWJMcZnxUD4w+56vep6HIDzwTAqlsNiWIx68S03vajVyjgQzggnWiZJUi03GGOtbocQLJWLgCyOkl5rMNJoGMS1tfWdu7c5nEujN5qtmbnK8nwvyxIELnMVx8i5R5kBlEoBZxwsaJMbiwSo47jRcCiEZ4x+97vfFQ+zPE7BGBDU90ipFKZJRChzXSdXynWEzBRB1MoSSghBYxUoGPYNguGc9PoDz58h1PT668gjxsnvvvv3t2/frST83d9+Yv+BPbf98ks9z/vud77/0EOPvvFNb56YGC0Wi1/+8lff9a5XffJv/jlPYy5QayQApVIhSWJrUebKcUW5XEzTYaHoS5UBOpy4xihCTZz0gUGm42LZi+NMuNYvhACQ5bHNUqCM0WGc5taCg0w4YKwBm1vrAbLQrcc5QRtTSjhnxrqOGygJxspcG+EwpNQaFgb1i+faY6OFQlBdXpsHypdW1jzPOXDZfgLqve9518zM9PzFxQsLjz76yMPra50vffGrd/zsu65ru93Vj33sr4HAJ//hYwDABDAh6yOe63pvfetbb7zhmu98+4e+x0+efOYtb3mVxewf/+ETR49uPXHiHOOglSEAN95wkDP9kY98eGV1g2Ltvvt/cfe9PxBCfPazn//5HXdSgNXV5b/9m08gms/882eFQxGFEG6SDCml737v+2+++ebvf//7m69Ka7tz585qtX7i+MnGyIhSSuWSMweQEGDWQJR0815+510no43HSyNTzX53dPpgCGsHd/DTZ1aGfRYPYHay78Cykabdnhqf3l7z/Zduqbe7/SgeMm/ix/deuLTQHSnsTDHduXW35DLpto+fvnDT/kLn0poYn262O7/0K7964NrrL/To/YeevOehh9UwpYIOUN38olvnL16af/L0m1/6qgcee5Dx4Oobrzx44EWE+MPk+1u27C8Wi2j83TvHiuX/Vmh379l37TXPffzRIy95yUtyrTY6C8+eXOhlTUOTkemalSyVeTzs0WGyuNbfNre3UA4q9cpac8OgFJ67fGm54JYC5iGHqw5ckcaDJI6OLJ/Jhv1nlxeEQ4wxtYlQlena2jnfx9GxA8RV6Nj6ZOPCwvyPfnZHY2T80sJytV4lhDFKXNetVCrdbjdPs0KhtNpuXly+VCu6kxOzCGpqZvv2XVesLQ8R0y1bZ9r9Zc+zI2PFtD949sTjI1tGo2GHlr29O2rF2pZHT19Ebp88/Pjrb922sXxqcfXS9NbtiFG1CCzEbr/LULzqxXvhkfbRwxvjI+Otft7PEyfkDqMySt/0xjeOjlV3bd/z+OP3Tk/Mlgrl1vKxH3z3W0na3XJgdmpubGxs+9C4R5985ls/+eFVl+975+/9+b9+8h8vXbyzXqUcEAzKXLsCKPyP8ZewzWBKQwkQsGiQUWYNCpdqLTdDO8AiR04548BkqqlwCThKpW4gDMXBoFOphdt3XNnr96+68mClyFut4cgUK/t+3iPlai2LVma31dc7G77jJjK/4cBVMk/SPBmZmujEfeDuWH12fuUipcwhxSzJOeM205Q6oLXgvu/wbr+9dWacEZQppFHWayVxL//w732QMSal/PynPyeEAEKiKCoENaKEJ8pGOQ4vZxqSXLuug8bjzEeAuBMdPXQUDPTWeufOXLzvwQdSGV9+9cF8mHQ2WlmWa60dlz3v5S+sNihlUb0+sv9gsL5xNCyJaL0JLrl47tIf/t5f7b18ojpaUHkyOTLZWWtZNewP0izXpWLFGkXRUbns9COH8XpQ1ZLN7duf6djgYM+eOU6gtb62fdueldXmRnOJCeZCwYDHPUFAej7LWtLQbJh2EdJOd93loTRCaqvjBPyS5xazPEoTa2S2ZXoujmSapsViud3pEIqVeg2I7rba3PEZZUCMReV5HlpaKDS0IpaYTCaFWtESEQ27vX5Cac5K4/v377l8175nn32WcVBGEgaUMkBrFSHcTbKhRspc1y8FcdYVrFApVKNeHEdaGea4PMsTxohMresFRKFLAotW6ZwiM0jQCEKRc9CAW7bPnT27SIQT53azQKF1Aq9cbtTzNOn3V4uuu7x2khMeeHWblwBqAoRME+4QrXV7mQvP1QqUpIPYtdYKXwyHYGzCRRGRdBUjCKVwe38IG/EwTb0iL0nNk35kiX/27PKlS2s8GiZWyXq5VPaCXJtcK6CECqaN4ZxLqQqhnyRD5mAlDJTMKBGU0IXl/Ip99IqDYw/dux6G1TzzZW4YQyQZpUgNRcsIIYwrhwbWWqVU4PnKaOHQbneRCSes+p7rIaJRKaIlwAV3VY7GUHAZ48oAEkIoIhBDCHWdEFEpo3MZF0uzWQqtjTYXZZ2lL3jxC6xRH3j/+5RCQPJ43L333jt83//Mp//lvnvvtUZurK9+7K//khD2iY9/DI1HOWhlgJJ6bWTQzygDIRgXxGhLCEsz6XiO6zrWMKVyxnmmjVcI4jQJikGSZ5bYOMt1lBujHJcWyxVr9djoqNaac4q6xBmmeYcLQ5k76NJO02VQsMRQjoDEGt9QzjhBC8Zaa0HwkPHicKBK5dH+oOO6PYsJ5eSRhw/VK+HVVz3n/PnzlNhuZ2N6evLyqy571a+8LorSsYlxygkCHD5yrDFS2djocUa0QiQwP78EAJVK+PnPf/4TH+997rNfeOSRJ9bW1oxNEfGee+88ePDg8ePnyiWvUCisrbV279l5+vTptbUNALAgr7/+Oa969cs556Ojo2mac8c5/uxZg4RQpi3hRAABZQgTvrTxZ/7l0//6xc9/+tOfvvveu9bX1ycnJ3/rXb/10Y9+FBho1OVyUUpprc3zTFvFHQZSa5xsbHsdKWwdnSjED/y46AWddnT20oU0M9Wx0ZESmGyQpIPE7H700TFVVfUSK/F5Rw1MDkfOX1q3jXBqT3/Y2nPFi6q+Ob9ymgYcxNr+y28szl1/6eJPRw1fU4V//e/Hi/Wtja1mfXnRLxV7vY5fKmWx9ERptdV6wUvfFKXFHZft/T9v/ZUnnjxx9fVXEscLy+WCX81i51P//G9hpXri3HGHMSnlHXf89OixZ/77h19hjjzZX2NCjoyWTp06xVkYeuVao9puraA1Y2NjY+OTyjytjLaocqmdgO2aufyRxx+o1sJnl+YB3Vp1ZGxy1BLZGCkuzW/ccvML5ucvbZ2bkKR/5NRhRdiuy6fue+Lndz9439f/66tceKVq7czFM8rko6ONsxfPVKvlM2dODYfDUqniDgZRNORMh2Gp2e4Qgr3hmXJldGZmptdLZCrHJmZ73UGrOU+Rv+51Nz381OG0H9V2D0aLwdLy6fXl3vTWuekSTWxem5kIxspDzDMLWQZbKrW13poe9LiHv3zTrt6FjaWlNVoqjYyPL6+tjFSKVEGvvbK+Or+20VBKjE/uTM4vKUxlug4mbV082V+/ODKz91defVk/jZRuHXryscmR6Z/ec8d73/6Ob/37lyslbqngjIKWlAtCAKneTLmyFhAMIhBCKOFAgRACBAFwUwspmJvnORJkjFtrpdHcdXq9zCnDC37pmiTNm531kV07X/TSV54/+7TMB7UJamxrsrFD6HD7yPQV27Ydk1lQKo9cNpoMe5zzzqA/tmPrc2695Z//4V9f/uJXbdu1fXFx9bGHHgsLZQJ0M2nL9bjKE6nAcZnjcIvSdUUOSgh3vO5sRosI4WZZluV5sVi+MJ8lidJKpmmaJFm/HxltKTBrqUxigdYL3G63m/Sbgjpxr7nRb4K7d3x6dK42eubEOa/E3AJunx2fmC2m9cYjj909PdV49tyx5vLgzb/52lIQXDgbnHhWXzy7TtHxcUp1MploTZPFhYVitdbrxcWgkiW66JVlnGqpuOEqIYNhZkwU9VoKDXdJvVGKel2X+2mqrIZSqeL5rNuLavXa2mrLouSePzZZK9dLOge/WhQ8mJyYzkjT9b36lmmXB/1+v1gsUkCjbK06Mn9hmQC1VluIueC9QVvw0GiKgNrk5brnUToxOTJoR5yFw1gVy54fCAKOMc4wyp7znFt37d52yyt+s3Vp/sQTjyiFlWqx20s5Y0rm1lLX9fLEeG4hKJUG6YCI1C3Sghg/s3KeMcYc1wyMynNBLafUGk4V2MwwA5tRKoQQjToUDgjTGa5u3zWndUYpzbWhjkjMgFJSDPzQC7fv2PH4Yw9TChQ0BHmc6qI/FrdH+50RhxYYA2kztIRyAsymSe57JaM5IlKiEYEJokEicECGiHGmV1sJMXVqvW5ELRk4Ad26K4jlRmIzrnTqCh5HPc9xu+1OUC1v37O91+s0ahWBZH1pbRAnxWIhU1HoFyioJEmNZVccAM81M3Pl0mHIemmaQiH0LGYWJQVKKVcSCUHGmJSZ5wagtUbDBSVEC9cixlLFaIExpqXhxCcgiHWsRco0IgLRYBRBYwEYAc/1tNacE845GGdycubsKdJoTKMNs3x0emz7s4dPgYHA9SmlVx+86ld/9dWIONaYpJYxoOfPnPWEYzSVBhEcCjzwXZlnH/7QR/7sz/741a+5LSy4P7vjrt9+53tuv/320bGxt//GWz7+8b9H63AaAsV6o/KHH/5T4bEsy775re+2msMPf+ijCAEgHZ8Y7XQ2hoOBENzzuTEKTMmiGsZtwqTn+s317L++fe/Cxb5BCyCNZkhwGA/KYVUIgdIoSQM3VHmKgK7ryhzyPBcOyjxFq5obg8NPnzlw+R6ZWyWTfgf/9C/e+tvvei8B8e1v/9egpy2GWgfNZg7W0cgAiEUAsIAmGoLOhwAsS/nqat8YxxgHwF5/w0u/8Z/fQnS7PWi1Wpw77Xa8ZXaPsa7gTGvytre97b3ve6fnef/xH9+ijGVSWyCEcKMl59xgTggBINqi54e5zgkXg7iX5MNSKfijP/rgpz/9T63OOmFW6TSRYK3mnGOeU6alGtZMLQP++LEVTUrB7N6wcQopccoNWt556Vwy7dE8By7oeteP2a6BNzvEoFwtbttVTeafPfbIY0m7P5T9FNtWDR8+txESyDA2YQKmv3j++C9fdcuz3z8shvxS1PREYZw5ORnZvXdubnS622k9dfzhjdXWi59342Kh8Zbf/E2nWhpdOUOond05/dXvfeHs2ulDzzwxWa9OTE1UxqKVJ89PTs5NjDTu/dk99RFv196tu/Zuvf/huwhLZZKdP9uaHJ+ev7jeWlnklM1MT+zavv3c6YVzZy7KWBXcYrMzDHwvj4cPPPCAJZJ7DU36/UG/G8fVWjg5O5smMXPbmZZ7r7jMUtnudu568JGTC5dEcaUfDXftnxZF6jpFi3RypgEAluVc2F6/FQQBEJ2kA993uSuKoV8ul4uBSxlrjE9cWFx46MkLWsUzM1PDgfJEtdVqFQJSqTkuQQ89a9YpKUfDXqnksBI9cupZm9bYaqst8wPb915a7dfrs9nQqDie3LfFqG6iLvzmr1338KGzZ5eitWG2dWJsdaUTOoy50iu5rVbLIn3kkcPc5bf+0gsOXneTl9t+a6UzaP/k3ru+9M9/fsXVBy/bWh0vOg/c95N77rvlta993fe/+XVgQW6UBgUIAhlQJJQCQQQCFIEwIEAot4ZQSrUxhFLOKVpAJEZZrTV3RFDwev0o08pouPz6vWOTNQ3oF4I4SrtD+ff/+C9gB08//VB939azR/ujjR3bR2duvmbvnXf84KrLrk3zBJgMG8FDjz85v9pdaA7qI40tW7dMTjW8UtCL499492/fe/e9Fy+cKha9DIcCiKIoHFfpHNxEycwJ0A28JO+VQlEqlawFKbPAZ9I4xZK7sBSneadQCLmSXmAqDX84GDBCKNXjfjiIbBDay/bs54R5bmEQD3b5Y7sP7rrn4buanfbuHbt37NvOKFjMnzz1i67rNsbqa62NQtnJljvPHP956BT67c7Ccv/q6/bEA/b0Y0fAqKAaCseNBirqda1JbGiCoDTsp64gRDFHBIjMGCM8hiAZYVy4SawY97XhS4vrpWKl3WqXK8Vaeby9vuFQNyzU+sNBninudLbM7Lnxlpc6vmNs1myjtvbgNZcnw+GFc5FMh2AEGuI6oTbE8fwk7xdrvDFaO/zMufpIwfXD/iB1/dAPglzJUtk9ffLUMEomJ6eUjZMkC52CVd7/92efufWW5z362AM/+PHdW2rByVPHHEGkyjzfiaPcdTm3VEvlOgWvGF5cuMCLbM/+bQx6OjU3Pe/Kya3Vp59ac30EzKxNhAgYDxmxMdOMSAvUQGIIGpChwxPdn5irler+6vKAcqa0SnFQGWXT03WV4tLChXOLGkQuPGYlqY1d7fEGqOmNFeHAlkwy7ijKwaDNpHZc4L5JJIDhQSiUGbokJAyM0WgFMmGstJBRkhskRLm+W8zzdXScVOlO1KOu4loroqSxptduhtXazt07OsNBoRTmadIdRINBjwlRa5STFIbRoBC4WQLrK2bX7vDsmT4n/XoNViPjua5FuUkLNEgpCLCSUK61QWMpQWOMNej4nlKKI6FcGKsEp8aYgu9ZiwRsnue+61sijTJGk1xKAsAoEARCUAiCoJNUVavVUycvAu41lnLmCRqePzt/03Ou/tkddwBEhOCb3/S6d73rnYSIb3/ru0pKgpgmA6MlAcca47BQmwgB642J0dGZT/7DZwoFLgQ79PiJRn3qN97+O9xRW7fNXnPNzb/4+SNGp2CyjZXOn/7RRwmlf/4XHzn81KKUwaf+6Q7EqjVCaQtgmfA250VSJQ4WEDShhlKOiISgVtNKMmMzJVPGuRAw1BkhyLibJIkrPKkyAAmEI4CWWuXWWgpIRVCQg/59D951w41XfOYz/4wAf/5nH3vwgYf+5V/+ZX5+sT/oACObJzu0Fgj70B/+0Sc+/nEwGggCoe9///tnZ2c94f7sjruGw+ErXvGKX/mVXxZCPP74oWNHTwG4H/iDD37iE39TrdUOHz1y+RVXf/Wr/9nvDT7ykY/c/+CD//iPn1pb22hudAGIMYhINjE173jHO/79659HRK0MQfjdd79remaGc3733Xenw/gNb3jD6Ojou3/ndwDgc5/73KVLl971W+/8zGc+NT0x+Tv/3++Mj4//82c+880v/PupxYtpvkTdwj9/5msba+vb50YcaovjN55ZPbW4cZFw3uy7z563q/nCddc+T/Pyem9jsW9/cfeTvfOnn3ft5TfNjLSyltKe7FVd4JrVWAVbSyyKFpmIx6g7VOB4I51+liz0ALK1i718ujQ3t/fmW8YffvwbzfaS1t1It2YmJ051TjUHz3/6rh8eO3ekmfS/cfu3Wb62ZXupMjbbGSwnsfGnwquuPvCv/xonWfyrr33zo4cO+ZAS7oCmaVdNj060bKffjTAX64udqB0XHG98ZPTimQWfF8pB6Xx8sq87pWItyTAMKsb2pMwmJsaVdRZXFsAVl5rnj5wbrK23KXG4w175qhcePXHoiSeenNu6Y3xsphBWnj78rF90l5eXC5VZIUSxWPQ8TymFSNbX1xljxDjdTlwIAmXNRmdjMOy1uhsTkw2pDaGuzNjiuQs7d42ZUayXa4uQgm+NXwzr08O1+W7zdK8dXXS8kdlynmOam4AVe0k6OVUpl8tepZ4notNZpPnqVQdrNz9/90OPLT70WLPMakYr68pE58wvcOMff/YcEdItl6a27XnhFTfN3vLiYbR62ytvffrYo9Kaxuj2Xk9Slf/eu950wzU387Ae5yk4hAdMW6MzSzbZ2psTaEKAgiUELABQY4ELwjitVIvRICMMOHUNWsJoKnOgxA2Dd73nPXv2H/jGd77tesGO7Xsef+wx7tQW15rzF49sbLSOazYRbh3E0Upn/okTSSvO6UbmclC2B4FkHp/eusMS78Ceq57z689ZXli90Fy+/jkvmpna8spXv+nDH3rfuQvHCuVymqaU0TTThDrcK6bKiqAUDyXyIFMYrfV93wcAa1WcZt2hBO4J11dAqeuttbuN8UkJVEnFgFBrx8e3aZTtXiootyaTRo4UR85fWiU8nN1a5144yPO9u3fefd8v4jTSbrhtakcK8fTEOLEpSug0e3Nbt0n57L6D45fONWtN77J9N504eSrwS44rdaa5CIDoPB+OjowkSSK8GgB1mAMAhuQWNDBgLlva2ChXGqWSo6Xy/bBUrA8GwyyWnigyxtOhNZlQBMholbuV5qBp5aBS8zOZJkm31V1Nkl407LoiCNyqyenqxqrWGrWqlFzqZszNJ7dWrYa4HxfKRWVMtVFaW2sqzBi307NTRlljkBGPE78+tvXwk2eeeuzMlQcPTG8ZfeTOH60tn3cdBkitIa4n8kwxoA4VRmeW88nJybWoeebcuVrRMGpqDf/oqUMs1HGSENPdPjuSR0mnNQxETdrIsFxaQ1ziuOAWQ0yU8KE0WmpGnY1Wf7Qxl3Vb1HW37xvxXbU4v1oZDZZWTgleYcAnZ7efubgdtaDa07JOwCEwtNixKrWaMcdVNkcqiRCO72nIQMQmD5TUuULhlfPEMEGMGhCmwVLOS8M0ch0pdWQ0oFVCIPGmpxiiztLA871SyS+XiOAETHt9TSDpdHqN+milVu10lmfGa7u2T4fjR/v9bqNR6PY2UInli/T8cZmnrpRZIfQQ0WgQrqO1IuDEQ8lYDpRx5gBQBCKl3nQJM045p5QCZyzLEk7Zps+PCQ7IrYU8SxwXXd/mmTLaIeBIMyxXC4Xy7n07X/+Lny1rU3V9Lx18cmS08pvveNvM7Bii/eQnP3HrC5570003nz+3sG/vle95z3te/ZpXHD321IMPPuk53h+8/4//7m++wB2DILVCi5QAec1rbhNC/OiHd8ocuICJqfI7futtf/WXH6+WZl7x8td++2tffPEv3fqil75IavWVf//GkWPzlDS2b7tFmwaj5cwoLlytCFDHGMUFCsOstZuCIwBLiU2zqLm+QEWnUG6J4JKFtTjqFoO675X7nT4yRmnmetzqchxNpcO5QmHXMG0peRLIs0CXKdVoABCQAGOAFjYdXowBIiAApWANIALnYAwQAoiACIQCAFBLLVpC/ue5BeAcjCaIDIBQsAgGyP/QLhhl2jqUoeMIwT2tSC4z1xWEMK0IpbRSDijTnHNE1CYdxl0kBUqpcDixuAmecxwnjRMppeM4YcG32hBCsiwTQvT7/VKpwIR8/Zte86Of3QO0xJF32mtXXX35DTfeeOTJQyeePhR6XDNnNSJXXn/bH3zwo+fPnP6jj39eBqOFYliy3T9815sy2VPCINH1wA2BKquti0ka7Z2e+95Xv/Wjp560skTZ5FAxr0jSbM1Ew6QpS9WJkVlf2bMsW5oIitt27j+6eqnLhgdLVz5y+Kkrb7z+wpkz+6ZqIWv9wR+848ljZ//qb7/65OPn6oXSmePHX/faNyrr/vXfffwf/ukTrcGpJJZeGKyvrwahx5kHmgnmUUoLofvRj3703rsfOXXyohf49UbhoUd+QUqW09BjQRoPR0eKR48e9oISgoiz2JKMOUZrqBRHo15843UHr7/hqmOnzuzbt//JJ546eeJ0q9UeHR2dnp49efJkmqZBEBQKhdnZ2cXFxSyVUkpltMPL/dZKfbREGVLucEfsO7B7YfH8xtoq0cAsq5QL3e56oRAMhnm5OnrgMm4k6XQyL6xdurhSCEqNak0S3UuHBc+FQe46PJJ9t8znJke6yytXXVGamx4ftpeb663x8cv/6Z8e6HWpEwS8RFMkSe5RdByeUxpl2sRDILn0fN/Y7Hd/+83XXbOvNjkpWaExspVTsbK03Or2/u///Uh7o+lQBCYTk3JVINQQasEiIAUAIBYRASnnrtayWGCEyrltW06fOsdZmKYpJdxxPKCi2erd9srX/MprXvvUkaOHnz3huP7U1Myzx57Zf9kORpLTJw/t3rXloh7SyJktzVw8fUKbZNfuy03M007HcYbgqfUopk6ZULcUNsBW222MVKYyu21utxCiVPK+893/CH0Wx2meaCEcbc3s7EycRIg4GKYExNZqQ2utlCoWi8YYwoAKfvHixSgeEkbQmHq9Nj4+Hg165UIREal2kJJUZ5SSPE0ppRq1V/KIR9e6a5Rhr9OtlovFYtiPe0rrYiksl6ozsxN5FjEqz58/WymHxg4aDRoEttXqTDS2C1p76MFDjEKr1RdAGHW464QFx/V5tTLW7STdblfKbNuOuUqlhoQtLJ9P1DAslgph+cL5o1tmds1MbEVlFy4t9jq9arEeJ/1hmnheWRp3bm5PZTSIZLMXdzqdTqXEVaZGRupaJdbkvfZgY2VQDuoM/H43sdbO7RoNynFQpp2+3FiVgSkPk76BdHbbVJYMd8ztfeKhp6fHplUuo3jAvbBUncwzXiiOce6sLC24O9hcPXzm7js5oZR6Bq3MY9dhVlkKJJOyPDp125t/baXXPnni4ebqaaWZ6/IwLDLqVCuVYbQ6PV7aWF4PRc2B8sry+mAwiDPllwqlmjs2WV45Pwhqrleiy/OrTPljI9uWm+vaiWe3l9sbi8SySmV6ZS1eX+ls3bJbGDJ/9tWImqIA6Woz5LyV61MAAwAHgFJhECQBTomrdQQYAwoCHMEBoAAOAAGIgWoACtYD2Fyo9BjrUjE0NiFT+w4Mo34aD7njuGHolUoWcH1xnjNKLXLhCtfLZVYpeVsmqhP18sj2s7V6hVANNmtuDCYbB372o2OnjnesAsehpVJF5grBKpUT4EmsGUchBCIBQoxBrTVj3Fjw3QDAArEAhhLDBWHEIFoghc1gMGOssRljMVJQmaOUoE46t7cRhDMcb3rmCWps0ZAE0694nkMoaq04p0A0ZUgpReMQcBARiMrVAC1wVpQZyXJBmUIwaBxKPEKVsbHrOWgdQE6Z0TZiDAEdozlnPjUp4QbRaCCUBZkUjDZ2775VySrlobIZ5Qytg+ApnTJuORK03GrNBWqdBV7RakiSlY3OoUKpA7QthOl1NsrFkucW8tzkeqBMn1Ieejttvn1t1a/XL0uGvrHK9RcH8V3MOSeAahMyKnLdcym11gpOATapxOC6rpKGUor4P8lBm70fEYFYozwhiEVFCBJqfN9zPCGlGUbSGsYpyWREKAABtEAIoK0gaNfxtKKIlHNibOq6rlEWkdSqRcYt55xTyPL+MG4Dr4FFx3GyLOOcE0KklARBqtzzPNRm8+qslHIcJ89zUmWeJZfv2/fsqePv//CHf3D7T4bduFAKL7/6wNNPPH7uyMkt0zOxyS4sLk9N7gxJuRAMxNiBlbwAXGwsndm3e6soVHqW5DotQkrjts8pCqbAXnPgyp9970d0pCoz16iypa60nSxdeNHzrim69e/96J7dl+/d2DgxV/VZLNfWW7FrIi+/dsuVv/7Ov/j7T/+HStenygMcrr7geS9e7aQ/+sWdv/7rb6sXpx079uUvfW+jPXzX7/3Ow48/2G5eOHXyjF/wLOZTU2P9fhQPZTTIpicnJ6dGX/KSlzx56MjqSlc4/uho/cnDj3UHzWq5lg4ThwtPOIUwzPLcDfxM5qVqMY7jQjFAjXE0dLmYHJ+QrDAxMbG8vChVtnV2SxxHrVZLax3HMSJ2Op1msymltHnCgsAYA44jOKWIFMH3gzAMXY/2B2v9/nyjzgoBkVIWC9XuYEA9qI+Vut1umqh6fZQjG6k0lheXgZJ+HGmTgVYj1VqhXIzyaGp2vNVqNjc29sy4L3ju9YGwJ46d2b3z4De+9v1rr7zuzjue6EbAgiIJ6tIao7qBC0oawQtcpLmyaHnUHgBAUBEZ4fsO3HDTdbdsm5u96to9H/nLP3no7gdGyyNRPNRUUnQQNRcUANCSTQoLpZQi0dqGBS9OurWGs3ff7iceOyZ4kOUJJYSAR5gXRfLyq6/asWdvNxpcml98xS/f9sQTT1BmapWg01rttzca9UpxGmTbURFrt1vliVI/i+vFsaw9GK+Xpc4W1lejPKWc+d7sySMEzB4EyZkbBCWdS+EQo9MkGaIBzvzN6AbOwYIFAEJ4nqlQuJu4NkIIY4wQRAKIaAkw9j8Z167gcRwLxolFSgLqCKTMglFKOoIJwXIlNQVggGg5BZ3FSFFqFRbL8aDpuj5aIqUUDlI6ZN5GfWy4bbe7un6cU9gyvV0maBVZXLiwZeukS6utzvDJp47Pbp3gLk2ynDvh2trKZQf2TkxMRIMMiGh2V/2Arjc3CmElSVdVIuqVqSSOuhst0G7gVpK4Y3BAqK+y2t4DB8NqvtI5NkwSa4OpqXKaGKW01angkAxzohhqZiQb9FSe6auu26PomrRt7hRXV+LOQj4yGU7MFCzKxYvtbVMH2qt9K3NAyQQtVkeQlNOUCO6Vy8ULF8+acdw/M3r0gbsDFgArGItZOqTEUECCRBk9OrONlGvVidFe51I6XI4Tu8nh23f5wXa31+osj48Vom6v5DdcKKKE9dX21i07gBLCkjjf6K+7fkUojHSapz3t8WpQqVTHK9IkVsrlhSXmBmudbmN0vFqoQA7PHnohBUUJqkQ6LGPuWhQ/BSTmwhVCSNXnApWkYDxrNaGSowZKtEFKXAscgQKRlBm0VhD3zW/+Pzdcd60f8J/89Pu3f++7AMDjpCdVIm0SFgojE42ltVVG6PjESL1c2lhbb3a6ngBkxqI6euyZ4Kr92drSqfNLlEKpBIMubDTvpx6UapAlkMY2STuMcWOs67pRlBZKAtCTMqOcKi2NBcrBWOV5jrGDTTIrICIaBG2pJQQISqVzQG4t4YIQRo2xwsNBGt9y644X3XbN4lLcXGbBKQ0mNBC0k5JKKQGOyAhBIMbaFNAAOoAuICXcILpgLWEhasGIY0xMiYcAFgmYLgsgt20gHFAAIqM2V8goIcxmOmEOMyajjFpEMMYJihYHjXEdeNjrXwQndj3KaYjWCcPA4JAwEDzwfR9IThkySAc9ecWBq+648/jF86ccwYxmDi9luXI90ForJf0wRMLTLPF4xh0CNre6zrCRDULArtEFAwqgrIEBpDkwAFRKAmgCFAGkYhT4ZpoLACAQAAQwAGQza1zlejMRGkBrK7i0SRKDJQBEAQUYos03zcGIHCAEYhQa4bLN8kFMQUrjCpHLPnOQ0NxCooERgpTyTMWFQsGYPFPDQARGaQS0gNqm2ljGmSuEEEJogohG5jI2CIUDl13/zNGTnX6nUPPOnj55697n33TjdTt3bNv6/tm//ouP7tw++al//YdL55f1kJTC7JOf+Up7tW+Met/vveehp55da+rSlt3jlfCp+35apUamg0KluLQ8r9udgOiF1qVAFFS8MDUxHaetrjzv2epIRU3WOpdtD9KuDXx3ZnL65uc97/6nHiNl5yUveuHBK69NB19t1Kr7Lpsos20rS4P/+v492y/b3evhpZOrJ56+uLY00x1mH/2LHzIXHJgYDn2Vc2OzQQcBG9YSSvilC/bCeXXqxKEsVcp4gEzbtrXb64UrSOJUQpZnBo0f9ZlFpTJNCOlFlBKn29L9qJUkA+GGC+ddi5zAOmMOoezkk6ub74Uxl5ACAM3zekh3MZqiZ41WDI2UkljPGsqJI1OrumAxSxQ6Loau6ndPAtFc+OvNfrlRuGxiJomFxWRhaX3Y7V154Ir+MMoyyV2OyACw2e/GVqZK4moPrWOwcPJ8jzpnBQdQpYVH5jdicKvpW3/tqjMnmkdOrs+vXRIBFAIPlWOttSQZyJgZx3fCamPCoGYO07k+cujYkUNnfZ/cdMNlg431khdkUrlOyC1XSqFFgwYtEMI450CAEqqVcbiPxlICDhe9dhS4xTSxm9+4tXkhCHr9+AUvvPLq669+9tTJ8SknzZemZ92V1UUq9NhYWPBHhXAdtZopCApjiXUi1Wln7fKYt7a8CMMaaBsN2yIA5NT1PccZkYPtjLeJ5b127jleMkw9t0DRo5QT624ecFEhIdYCGgOecBVKg4ZTZhHR8DzPHccxaCmlWlOtNSM0tpaC7/oBoYQ7PuUiN0gZK4UOqizLMkI51cQaSq3hgnIqASDwnaifB7qqE7TgcEJ1aggfEFoqFPtPPX3n7j3jgvrVypbUbder7sxsuVwcb631p2a3lOvl8xfnU62vvPagQbn7ii3Vak1Kefzpp1zXlVLyPqNMLy6vT0yMl4oFDtpDPjIxiYrXq43FhX59ZKpSmuj3RViENGkOB00u/EKhkOSDbjfSigbeJkOaUsFK1UpnI0qzyHUKCwsLPIjLo+7i0opgpUojnN4yOszXh8PhyNgoEqhUajqLtUoM6sDzlSWNxsgNN94YBt6nPv30c29+XufiKdcHK2POhdEWaEooamPAQqEYtrtL1x3cuT5Y7w4umqyfp24/zrfuqK+1FuaXlsemx7TL1ocd4vrtYeTT0K94zxx7qlwujo4XhmlLBKMrG/OT03VBmIrl9PRYGFZ76XBpaWN8ZBx0od3vNsYqSg/9oHFh8QJ3Mpl30aYWY60j0IvAIsBIS0DDgaZ5oil1rZUAjFijYEg2WwVSQgVYALQEKAGQZvC1r//TV7+mAezc3ByiRgSexH3HE9TS6mh5EPfGx0e1Uiod9vrtTCUjE/UsN74f5GmvWq/s3rcbG8cYAymBWEgyCMqwvcgXFrXjg5QgNfjCgKXGUosgXC4zSrjQVjHOCDNaA+WAVFoEjYogJ4QQBoRwBGutIRB7vhsPMwRIJZSKBUphaWU4txeuvnnLuYVjRldRtBQMiC0zMo04AtogeIQG1moAS7hBmxHmod10vxBClUUD6BLiGlSEVIEQRqixQ+oNyg0OjLBNMpWlBDxGSmA8xojFXFFhbE4poZRRYGmaDjrri0uPjjRGr75upxuYJFu1hhHrW6uRJIrqYrHc7nQpJcAACfcquNE7PRyekrIveNlq6Qdur5eWSpsE1DBNNWNECKNxTTiVNF9RSK3RBJDBzsCZBqsQRWYI555GhqAI1ZRZRIPIjEa0hFFKKbXWIm4SmmETqgo0tpYAEYCUcUKoNiQvVrnMjTFICDE6JVQTQtBytFyQ1JKuF2ZMKADLiaDEK4U1pfLRcfHy267gXh+sGnTycnH8jjt+MTrbiOM49HzOeRzH/X6/Uqm02+2zZ89OjI/fdNNNlNLJycmjR4/u2rXr9ttv15w876qbd26/fG77lSfOrKWaT8zMTkxN+16l018pV1H7vgKaSOVUQ9EQjfKut/7Wb1CTUp286AXPO3r4cEt2vWz96H3P+irhjrf34NXCJyrvTFac6cmt60X30MP3v+01t+2c20YYfOnL/5I3V7oqef8737hr37WP3XPHS974qt3btvl+2JXRFddcR4i49xc/HPTO2kzd+t739NcuXHVwpDY5e88Dzx57Kp4/a9YXfaUCJBWtM1DS6IY1tSSXVIBVhgnfaE0oZYwhYq8pkKAxBqjQEij3WuuxMQqpJcIDwlEZYFxQAdqAYRSoQqCkTlg9GiIQHlIEsK7HgSjGkTFQ1ihjGOOA1Pc8C8hd67pCG5WmMeaxjFPHL2gNNleEUKTguhWlIEs2ZmZmL1w6EyfdaqXR7w6PPnXJ6pwxpjPNPWdhfRkAcmWGSUYFep5LEAZJRihN0pyjiwnzxmZ7GRFUt9c6b37Dbx56+hnrBrwIfbn4ylddEafRydOXHnk480PiV2t93SJQAEWzJLdsQFyjMiywRiEQOUgtBw/f+TOf+yCE9VCRVKnU5661zFpr0BIACmRzCE2BELQAyBhkWZZl0mhKwRckTjKoVvx+1N6xt1qq6Qcfvl34wfRMJU5loUjs8hAtk9qUKn4cJRvNXrOTlmsQjgTUiGv3XNkbRCMz1WpYXD6/sGNucqO7kQGkiRLgMfAliY2RQihg2g+tMZujI0cwgZYYY6RWjnAoAHAQwnVD2uv1GHcdzrXWfsEhhBiDSJi11iNsEzuBxgahtkobmyYpIhNa4mAoKRrXdY1ihPhSg8N4muSUWKDUaOoHhWx1mTtCSckct1ScVKoS9ztnTy+Vxnmz28kivr6WEd70/XSkug0vUoKJXFrcsn1rqsc6veEwiYCrVquV5zlqs23LuDEy8EbX11uU8mtu2b00r4ZJ2wmtRZNmrN/tWhaPbCkBgGLQiTbcIgI3nVa+ZXZyeXGNhsPxsZleN4njtFIqGrRZpvKkSywnlDoer9drCqhM0sCtaM2FK1dX1pvdZhiG1JeTs34n7ltri4VGnA77/cjSLIqH3/zGWWttoVA4dvjJABMuMMsNgdSiFR5qZUCAF4hhEtfGx06eOuyU2ORkdWOlH/eVw4BTNuwPyqUSSly6sGIVXVpakQMMhD9amwiC4G1ve9uDD//84ny32qCVmpAy8VkxHuaLCytbtwRWWdDY6/TTLLaYt1urVIhhMd+z99pHlgkw6jCTZS2AJvABmA6AHhufdhwXMFtdWw79wkhjRkns91q9OClXyr7vOi5fXVmv12prqxu//uu/fu2111JK77r7zv/+7+/Mbp396499ZGJ88mUvewV3nFpYKE5vqSi0gc+txkGrHQ+VSuPG2JjvlQjmjFCH8WrF07bYqMwFPs/z3Ci3Vg4YsFp15OlDP1+6AMVgNk1zzw8o4RvrXT+oR8OYgIdotNauKyixgBkl1BgLYAGoBUYJI4QDIWgJItGmoxQH4nueazRI6Xf7vcuv2fO2d7xskKzX6ThhtXIxHJk62V/vqLQwvnWKc66kQUuF8BENggQiEQmgMJoQgoRaRCSEUuIxk1GKyioAIMwrVBsvecUtbqCyLANkLneTIY419s1f6A56feFaiRxACyFG6qO9Xu+/v/PNMBTd7vLqyslLSz//vQ++hnM0GophOYq6XhAS4eR5PjZRLITlJE8JVcSSWrHxzJN0nVCXszgaMsZdV0htmcuYLFKWMa6VTgMvMHbpjW+67emjixvNDTBF0IQDIYZbyypeJYotQyoc12BCmdYShXAE9zfbLdks/UA5F8Si2fzzQlHionEIIdwlAEiA59Kh3LVaUKQWNSEIAMRyAA6k5RfyvfvHctX1HHd6ai4eqHKpQQFHxlmUng+ZxznlLn325PrSStiXSkool51qtRrHXrcHlNUYr0zPjMVx/P0fHmGMKfXU6Ojo6vrC9Te+MZN6ba3N9k/91m985NTCxeNnj135wpd2Vpa//Nnv77vqmmeeXfn13/nwlsnRKOonmuUIyYqZ3n69zyXK6L5Hjr7i5a9pDtTS6to1c5Pt9bUkM/NrGxlmeWK2Tu24cc9+OzHtRfrXX/OWxYuXKvXJFz73TcJjr3vtqwZR/PiTx5pL8eTYrtVme25rY305Ok4uzc7sXmmdfddvvv4H3/tuFjnFwmyztTY6Nr66dEhQ0W8XAR0uGNIU6cDQITPWFZnFnDBmkBLMKQNrjcOE1pYiJ8QYmzDGhedJxZiTUKCMiVz3rDFu4OSpMsgZYRolpeC7jjJIGKWYc4eZPEE0LnGBYa4zCoxSKhyhLBLCcsOBMEpplOaUUsJt1eF9kknTotS4QWB1oLCgck6dInf1tu0TFxbOEGrzbFgpVeN2wp3UDYqh68XaDrOYKsYNC50whyzXqhQWtNbAzDDue+CPVBtipLSyfK4gCAKN4r7jV/Zf/pLFC0/3c/DKuOOyXaXKWBhEjz4xv95sgW8dBb7wHSE1zZkDGgFUopRmPAsCYoFRcBKrtE0U1TwEKimhyIAQjQQspdoYZayljDuel8kh5ZBL0+12OS+qzMllOQwDrcEq/cpXvDnqa8Fq6UB2W/1yeWTYGdaK0y71Tl08EXqh7wXIZt1SKoHp2GLO1pM4TnWh1Fhc7ITe2NrqoFSfqfjljbXQIY40iRMOht1lELGyEaghWANCEM1SKYFzyhgAzSS1m6KMWENsQWnJWKw1ER5qDWgANvUXnDvu5kSdUzZYzX3fN6qXa+ReyIWj88ThQkueSTh4zXMUMsYMWINGO74nlamP+W42dff9t0/OFQqF2fNnIkG3AqP79++f3jNy4eKRC+dW5mbdkheONkZPHF0o+d7uHbUTp8+vNpemt8y22svNznIYhpft228MXlg4s2P7VKUctjeahenpQlBsrzX7awPuK1CWE6AMvILlvl5pLvT7tl6JkgxHaTks+MKl/ShKM1UMRDpMA89Ted5ud0uFkswtReCEAdA0GV642I/jYVgWnl9URpaLkKVmenTP0urKzrnJeNgZxn2fVXvd1AIWKwUN9OzZZUQ3y7Jtc7Np1GVMMDpSLFKpWC4zrXLHJdronPjaYtT34p6+bGK3Vkm9VI6aPZUPa+EBSyBK0iySnfnhSG283+voWLHSSJFvj2Xz2/95X6VRKwaXUbAM6bAzHGRIcPJVr/yNa665pt1rfu3r/94fREpGjFECQKR/2Y5fIqaqhjlhaEUGkADJQHeYo33XJaCXF9YBNALEZjAYnAaA7XNbenFTuJ7WdnVlmQBZXV1nlH39a1/98le+6Ajv61//+j333us4wYc++Id/+v/9GTJNtu5/veOJc5cuOp4ruG+15YQCWqlz13V7rV6hXC2EfjLolMvimmuvWNy4t9NbmJkeLRXGKRQ7nVahwPOUP3r/ItGTgjlS94WDSpnNICaDehN2/b/SIWZNLhxmreKCck4BAJGgpZRySrjSqVQxZeg6BZU7eaaJSJ/7ggMKh8YSICF3BOcUwcmGIbH16tiqsUmxzI2VQgiZaz8QFiWipZShZdZaQpAQgkgIYYIqY3IAIIRxgdUGVabreiKJte/7QpD1lehnPzpcK+0yUgFNfa9ImUWrC2HlxIkTMo85Z44ItclSdem33vsS4aVSUpm5VFCgMdWBlBIIswQ4p/V6NYmyoj/6za/9tLshBfe0yh3PbbVawg0LhRLmYWqahGbWuC4tR8Pmr77lRdv2bjt7sU1Jw2Vc532Pi1wG5eq246cXKqE3MzuqbUSoJASNIWiBIGWcMLYJVUVAaoxBMJQSLRMLwhpAkJZkggcyKT9y/3pnPSC2igb+B8wFSEAjGEMFd3LCMsYBLUFtKeWAnBIiHG2xa20fEYl146G0BpRNjTFaa8/zOOebOqzNokMIcRzHdV1r7SbtlVKqmXbBN0MTDWLroig4DjchVfu2zcSY5gyAYXtp6dL5c7M7dkBQ8LWKU0k58wM+t3XaSFxd7gRBgCAJNWFYDEu1sBxWiwHVeunc/FpvOFJyII0oMHDLGeEKJWN5wRfxMJealMtFyiUjptuKhgMzv9ZeW52/9oarEHFyfNIRNB70mSgcemJB63ElyypT2rSJ25LZRYA22JAAIigAAHABOAAHUBQoALNgCBgkEgjC5oGGaUAOdrMo5wCUUWH+32UPNYAFIAAcwAIoIBzIJp4SKWMHrrwCgBZLlcXl9dnZLYMoVUoZg5zzcrnsum57ZWNqbvQnP/sW6IHjVgTdEUdjQEKga+X6Yqk4n2ZLhVJxoz2slUamJifPzZ+bm9oZRfH8xrzhtubVTR+Vyq2TG2GqhaqU0itxS1XaS5l0dt6w/+KZS9xaBqpQKMyfXyqK8Lord2uztm3PtOuP/P57PvJf3/wpZV6s7We/+G966aKhECcxAbAIzAHhB5QIoxJBDQJDKwAsCJsYSX1g6SaOkBtjCVJC0FhtLfieSwhFqnKpuQCtmcen8sQ3WEJEKeUb3/j6UqWQ5RETNFNSWywWShcvXUiSYRB4GxutOEp8L3CKbnfYczxBLVa8ok6sVCYn1gtch7urCwthpYiU5ll1+azD1SzzjsbxMvAm2nVgKWXESsOYEIJLlQGA4G6eKQAGBAAsGAQEwjlaSwg6jL/vfe+bmJ6SMvujP/qj6enpD3zgA0KIc2fOfOlLX/JdzxPQGwzKtZEPf/jDnuPee++9P/7xHZqI577gpYoIpYzruipLPc8DxjVCtVi+uHJ3ZQQb5eu/87Wmjia8ajw+t9IZPlavs7XVZVfwkl+emdiGmrXb3cZomKRplA4mpmu7d+9eWmk9/ujRqcnZOOoZnaGR01MTnU5rcnxq0M9Xlzsg+vv3X25QrreXDNo4I8LxhG9dH06fWs5a4sZbXpjky3GyKFjh3OmW49hNMSAA9Lu9arUmE5vF6LJip9VGUG/7tTdV65Ukzr781a8KIShJvKDsusVo2NuzZ2vSj0wCmLnGAOWkUKpJ7T/99AJqB2w+t2tLHuS7pmcuHDuZJpqJQp4Za7WyKXFASeA2EDxEQUojFdf10ygdDHrlcrlcLkdRpJSJeoMXvehFw8EgjvrPPHOEM7dYqMVZWq1XhSDDdOj5rNNepcRwwvMUbrz+5iwfLi1fVFoLt7C+0UJmhBCBO6bSSpoEebQTYAPIeYMngSx/+A/fvnVrgxL8q7/428svv/KVr7ytUPR++MMfP/rIoTe+8fXjE6PIxTe+8a0Xv+iX/u5vP84INZhzRrSRAOC53l997G8++IEPc86np6c+8MHf//tP/i3fWKpZRMEPqMhmmSIgAABRA8GUC8gnh+uUBH6SFlSGD9zXVdlBJvY256NCWFGZ1tYykXPm62wfw9E0p9zJUUtiDSCVyiJFQNSGWgOEMDQUAGSGFrWVRDOwAIi4eWaklNmcUxEZk8VpYEzoenx00j19OuZ0Mk05EAY0RUgoTY1dJ/RssRu95GXXrLWfIW63nyWMOZwVjFHCpYQR1/HI5p5CiE3WN6C2KBkTAA6lXIJ0fZGm0vULaZ4hpYTZleXlTtMxSoclHDTPIuQIGgzs2L5jqbcKrp/ExhgTlEue15CkbZnjV2oGjTLU5SOez9I8YS4wRuKUUVrkrOF6pSRfqLlFo0Dl2nEEEEzTtOh60sqwFA47YJTNZXcQnSjWwjEVlUpumrU46wrmWF1TmO8IVH/pbFCZYCKzkBJiESlnDkG0VhNqAYAAQ4T//aHnBIx6SingKWGJ1mFrqZ7EFwNnLwW0mHGBYCyARSIRssw2KEM0FpRgVFBBEXKlIgIiTbDfHeR5DkAcxvPNPTRxEBkhXqqJtRmAIIQjbjYVBNhsMAaAUUqtlcA0GA7gcKjpNMszC8iGgM3lk8wziVVAHYHag8nuPEuVYXTNQkHR0BJ98vhJ0IY6BWsiwgFtBmSdWbDGEMqt1uAGAEiVERoppZltoeOCIKCHYBMA5vKiyhYQ88ABq9H3SrFFjrOHH+kIjx5WLQsuMYySNcJA2VU0XQAJMA/pRaArwIFqsACAQCkgACKA/R+FOSUgKGgDlIG1/0teRmAURkYrBddnhHS73U4/ZQBjEw3q8CRJOu0Bo2A1CALF0PEaDSHEpfPzbrFkkUzM7PbDkjbRUmteQj46XV1fbxb9EBH7w/n92/bPzpYuLZx862+8iPHhv3/uK8QV3Clp5VPhuq7XavartWIQVkyrF5TpmQvP5NZdWVmxivo8iOUwNVkeScfhUkoCGA9iV3gms4lJPD8ol6sqzVDRNENE7QZm67YtaTt58slnyw0vhqzcCH5417lPf+lb73zHW5prre0T29Ad/tpv/ZY2ePHc/MZ67/Cp88+cPA/gOE5RagXMGDAuUJYQToyysPmtMsbRACKh1BorAQBJKLVxXIYwSHOgWIuSBrWNVJY457VK/Z672lo3GedSSsqsshLIsrWaMEKpydIwS0SpVNKYWKjmee5xzyMCtHU8N9FaE4KUMbGj2csIIVpyJLVUQ0FyNARMAo72HJIliRCBtdaqVBATBEG/P2DMJYRolXPBDHJCCFgpODfKvPb1r33s0YcefOh+4Ti1avjyl73wvnt/fuedP3v/+99/xeW7z507Jw0a1P/nba/71rf+7fixEx/9vx998IEHNrr9Tnc1A2IQKDCOJIrB930q6NGVdqlRXt1oH33yrFW7uNMwKo97PtU741a+c2a374rzpzYeOTEUHC3pXbioOXccv9FuykvnLwV+tVG4WQ6ETnoyj0ql0qDjlMJdwwgYCWoVKFbXkqja6fS45zMnS5RVspANhj3Wrwb7k3JZJ9NJtEE9GhTIZQd2NJc2CMH5i0tC0JnJqU6rjYZWwjEjCVrqueELn/+yQ088JagDiiibuMLzBde6OzISdFttatxGZTzuJVmeEOK2W7LT4dXSFb5XSdL+xloEQeXEBth0Z5ooYwlngUVtqTQWrGHUlmQiJLVxBMZyl4SWddcjtnIBOQs5Aasn7/pB02HcWIebq1QG7ZgixTgyiEhZw1pgZISRmAsCzDn01ICg5mLKWsjykIstQCxROk29ZOgrXQRIPWYYJ3Ge3vycG7Q07/6d3y+HYSEoPvjgg7/4xc8pg3/7t3/77+99rzfssA7+zd/9faPe+Oy/fIYCMWgEYdYoSuH3f/+9L3zhi7/9X98FQKWN1cCpaxXjWTJhjd7U6FDgBBkCICAIArkh4HjA80g6dFSnaTtJHDoCxGqTqiGWq4KwLuMWjEOpNtoz1hDlK6sJIUA0EFSaCM61MpuLSaVRCCGVdhg3FrVG2IwRRYNgUCMBD7XHuFWKcxZqo1fXEsoDgoHSThB4FgsqT8GicLW1fZni3T97ZtflFuxgbtuoYAUlKaWUUGWsFIIDUmstpWZTNEyZxzhX0gChFtFYI+PUGsGorzEn1A0KJQBeCGvG5kj61Uaj3VxkDvVLfrO7zj1q0AgRMOYnaWzAVdpxvGqSkkxmjs9za5ROqUBCCKEEAS1kyGFlpeO7I2gdYzIEaYx1KOPcy1VHCMdKYmxe8r0kFasLSRbxPEszr5NmvYLP0jzXNj555pjvb+23mn6R1UccoDljCEANGmMMZ6jznFJKKQOglHDKQGuZD2OHl5WyTORegSpp4xgI1ggJLWQKVoHlQJASF4iloDxrjZUGjOOEgFzplDLjudRapIBaLwsHAJlWIDggSKM52ZR+EcooEEKstYxy3OxIAJvX380VKaVggVKOaDyNEiAH5ABCA7iiQJhyuUUEj5WkkUpzV3jIigwKoLw8HwDjrusTFMCFtIo6DkEk0hZcR+kssSkhFLggwBhjnCEh0tD/n6f3jJKsqt7/9z7hpspVnXtyHoack4AiZhSzYkKMGMiCEfULKllRJCiCgiRBRSTnnGFyDj0zPZ27K9544v9F+/vXi1p3Va2qdW+ts07dvffzfB4QCgmpAC0AWKkRmcPA01ZnJpMxakpcktNKZ4nRYAEJZQ6gskZYrRj3wWRKT3I7PdvC0TB7WWAMAAAgAIKdfdHOomr/92wsIAGHgFJw0w2//9f9/7zrrv8cdNCyP/744lKp9JnPfa4zqfr6ai4HIeCDHzrhtNNOo5SvXvvOg/99mFDIuY7QtFyodqI4kaparU5NTbY67f/9jAZy+eDFl144YL/F3KvUpymlcMZXv/vX6//r5ZmynBrSmJzmDkkTvW+kwZ2iMKRnYFBpN2tlPnOSRph3c1kqLTPgWsa457lpR1gpdCStQ1zqNDqxmkYtk6VLl+7YsW1yvFWpVAYW9Xq8N0qytiCHLD/gnw8+AGA2b9764rMv9/Uu6Cjx2ttrkijL5yqHHn7yEcd+8cXXN2/ZuWfPvp31qX1onJxbkJkSUgRBYDJhGbEGjaHGAFpjCFibIto0cTh3O83I9eebzEhdpnqe1rWenn4hUiGVVCilNhoZc7SWlmhETSnVEjThvuvlA9Rae4hay1zOhmGcIUMLaVsgQ2E18RyVWiOkzzySkUxRQNTSAGhAPPec7yxdOmgtufTnvzns0CM/euqHghy77/57H3v0qa+d+bXe3t5KtXDjTdd/5NTPXnP11QRQSgHGvutd79qwYcPnTv/s888//5+H/tvV0/fCS68E+fLuvaMr9ztww8btmlESFAtdPTuGhqWCoV37Vu13cP2N17oH+rRDGfdc5pX8guewfM6tdpU74eK/33nb1EQumsqBLaMFtKXpvQmQ+ZSZxohkjLt8ca2ay+UdQoS0HoBBYj3P0VqH05oCtxYIQMGjcSOM0MwYI5S24DDmTU7mtOKUzAnjKaUjsI7IkDsWaUogMCrY1gESlBQju9XwQQfl+nsHNmxYV87l00w2Gq1iIWDMSdrh9ERqJCGef93VN7TDzHGYEjJfsssXLRW2gdwG+WDX1pmVi5ZWC7WN4+9QSpVkDPrijimWe5MIrXEK/vxQKmlpFsXGKkKp1NYobdjs0NIjpgsN5QQNUVqCMoFG6XoVoATBaiMJoUJAZpFS0NYAoAUgzBLQKgOtfQDU0K5U+xyfGea0O5EUMRNaaQQn0EDBZD4HmRoAn9ActZPSTALOALTmLai8/c4rCLYVRblc4b3vf+8HPvABpKRvztzB+YO5Ym770A4AINQIGRsQAFCuddfrdW30tb/9w/V/vPnWv972zjtrojDptNvGqJmZGUawwl2qjeSuazJLkSIlwmiFBhhQRSgwAsqYTCuObk5KAOsC+MZEUdLS0GbMcXlutvE5u81a61lLKNPaRITkhdSUcm0UABBKpbKEulLNCqEoAhijLWgAg2gtGCSeFIZQR2oVFDzHD4Dwmek2Qc25G3Zs3AryfneWJIT2RM1thSC3bP5CcKtJNq2QUChoaRm3WnRQEUKYVtYSMtsalaQYdTLX9ZQEJFrqzPVda4xQibUyyXTYzgDdcqmmTToxM2ElBqXa4NyumckJbTIjFIAFsFIby2i7FbMAG40WQJ45LFMdqWeQaUQrBAToGRNrISmyOLV5VtZKACpEE7iFJNWcoQVhhGcUKeQ52HYpX9u8tvPu95aDnGq36w7pjurcKO3nu20Whsk8aztadynFo2QmCBxrMUkyz3OMFVpllFIEIqUmhHFOtZac2NjkjXKViZzIgC6n8Twh6qi7pB3vZJMkbWiFaAtomAXp2UnAjFDV0RqRUsaMVQBAgAoZKTsNSgAgEN8YsEYCcAAEAG0sAAJoREcZDYCgDQABsECo1hbAzmLNglyPsNQQilabTBMbKOtK5kmLhBgjtDTMcscSI6Gl0KKSjkWGmlPJTAaaEsIYxUxqiw5nnshSTmTRV9LMJDagNECLWraZkxFDUeUIKRrqSJMSahwXlJSUUEpdpZAyq0XCiZcZBQ4BjDU2rVUEXUprSkoCGQNFwHJwlfZrvTnHLWvFxsfHgxyvVAOKtj7T7HTC3q4uQhglTn0myhdKU/Vxq6UC84H3fmBsDLi/oG/+ih3Dnc9/+YLbbvlLp+Vpa9M4xwjNbPz4U2sfefRNAPjj7695pbxxeHcUdxxEd2hHQxvQYLlbJgAyhTiOmyaklJVLQV/3kr376ovmr3zrre0M6yefeOAJ7/7YKy90KHArIci7fpBLs7Zf7gKP7941VCj5gS8nhydKXhGsJa5TLPphEqYiIYxqC1pZjigTbSVGVqRSzXRa8wZKWza9Hng1woudJBttjsXpTKMuC/muMBw7/pgV2cxQX617zvz5bpCfjHP7psKZsYmtW59GeLKaP7jdyPNCkfg1r4w29rO2TwiTGIWxcaxjLTPaADIAYmE2zEUgATA0EWCtFQmAYSAqBuYj9E5ONZEwJMaYFAljjCWhBDLbP+PGAADn3EsibUyG1FrhOJwLHVO/ICy1mXRoTqcxOEbGApCBzJk4R4AQEIZqQAkgjzv+cELVeed/3yo0yn3+uefefut1xs1vLv/VE48/BVZPTo3+8tKbCIHfXnclEG0AKKVW6XKtsGNo6z/+fe+ll/7qnfVrt27fdtTRxz7wwAMr9ztgYmJCGKPAtSYdGhnZ76AD3nnlzf0OPGh0qgmcB8WccVknij3m1ZszVgrELFrTLrD3ZeMDTqeilNWgjZVSe5TMRSYBCGAun+8tlPPSxBZcwIKLQJltt5vtGQmWckosZOViiVGvPjkZi44xygK11GGMxUnmwnytNRLrsQMQbKtZ97iFVGuNlPgUDGI7yTqDiw8zbEezUadZQAERaT7wjFW+77caU45T+uqZX/rHXQ/6Tu74405+5eU3Laieni5DZnYNbRtcWPADu2f3DikLnps3VgDJCA0606qvZ67LoyTWUawo5jntoiQhFhAASSK0cCgzxiKjAADEl4l1iW8x01pz1zUCmB9kseC8aIyyqIU2jLtgiTCSuY4xoLXQVoOWwHyQFEAihSTLMq0yIxQwxympLHZzngSKjPjccamynm02lUljIHWjRnU6DBgODW84+oiDn3vhKQQcnRr56GkfO+s739Na//OBf+7Zs7cZtYfH9wGByak6AgABa2G6PmEMMO5ba4UUzU5z776hdqsDFpBoCxlD3YfsDWMeVWovZ8xaX1ltbMoMQYvG6JRYRRCQUgJECktyWlvGiIHMGAtWUs6lNIRHWlmtOQABMpsJ6FvtEttkQMFwgvj/CBXaWkMoKgUu9QhQoQUSrY3q6SnHMkKoSMW7+yoKJj/zmQ8KpetN8dDDT3iMf/hD7/McduONdx3xvm/t3ubJpLczY+b2k65iuGNkK8/5qbFgs7zvoBGImUYnE8TFHBHI0VLUTZzJ+24ah67jIFCGnugY5vNYhDmPlWiOUUuhuW/yFaOsQ5BCLtPJ7tFtQFxmcx7JYZYpKYlVCNRDX+kw0R3kaV7TglEtXqYCszT1vZwxXpblAuJNDcfEhppMCev4XnfabjvMCtnk1CrJDCLnrjAMBeYpj1p7d+/ds/iYJa0mMmlzDk2pVk7K8mHAG2NDRYtIvI5LWkp7BH3XM9xtShW7blFk6HCH8hggtcbJ57pENuLxULO2z8ASC5ISOqVo24LnsI6ntvrBbmE0QJWjw0xquRSJ8XgAWqHVShLK8xaIJso4CYiYEAe0IabR3V0L3EAqHkVRvTVVLvNatytVohQdGW4RdADAWEUArSFzB+cLkU1MjVqESDjWKgCFlFqN2joUkGWSESWUYUCQcwPKAgNwHKKsNkCI61EppZDa446QymSGMWaMkcYQQjIDRhhKKSijCBHagqUFXiaInFvQyqGUWSCUSw2f+MynqZMvV3oKpQrhrouEU54ZOllvbFj//I6tr5x44olDu2DrGg6ditXjLT2i2PTZ53/vgKUHGhJdcMEFxx133E9O/XahULj33nsfe+yx8847LwiCcrn6j3/844QTTrjqqmuSzrRlFgA018d/8IiHnvq3x4OpsdH+ga60M0PBchYwUJYk3DOQgBYWaQIUojDttJuIkVBWK2/9us2EBkIp7tsoauXzRWtIPl/0OC0EesmisrFzAo+e9/2PGpr89vd/OPaYz8GaHTpMgemUogXr+IwoQVNS5BUVqdZ4Ugq6qUNVlqbttNBXaySpVgEKYjMn8NxQxMIj3PXjSBZ4IJ14dDy2xs/XHEyFA74K2cH7LZ1ubvvQBz6Gqvv5JzaOz7B6YoAHYUi8sHr4wcf0f7SUJMm6deNPPD3uzT0wC3N57eVAYDWIC1qqtDE1BcSNDP/fXRohYCUQDQZAEwAP0bFgwKZADFJqwQIrgcqKZEtbbwM9BTCBgCqlYAkaQKtRUwqUEq5TTdBQtEYoDRYgR6yELENICaFCMEKYFQJRIIIxOmPUGAQToM4pqHNQ29ePrX3jCdCuVsqA5oF76WVXZDrunzsnMzIBu2PzHsDAoKbSBG4uTiNjFCEkilobNq9DojZtXj040PXof/910UU//M1lvxgdGQ8bM9yCUe3ADf55+10//MFFp73voxMTozPNCSWjoc2bkTvc9XeMbwQgFBAMOpSF0eYw017geljPsimKNMvQdYiETpB3a7WFUoZSJkBibYyULAaggMZqcC2jCMZqJeohWKullV7Bo0iU0YwxKaVHiK89S4wlOgzDYq7oOarTmWIszVRGaS2VVcqqBPNx1Ilkp9qVKW4Gl3aN7JlgADbzpyMpslJtwaKuSr+GjAXB8688liWZw2jZDVas2P+IU05q1MffefOVwa7yWNJo1pvlfHdXZYkxIevHRnOnFN0OL3cVJ5vR3QJ2GYkSHGENQxeNEiZDakEBtb6xrkUnM8iRGRkBSYBkVjhApLbEGA0WAIgBlwBakymhLQFAAMXAUrAICARToyGKPAsWwQCa1FKCXpKghQxQCydoJwoAkAo00uL/FHUA8OoL08ccftxNN/1Zm+wnP/rxM8889aebb9y8ZW2nNUwIUK2Z4qAJAFx88Y+vuOIqAohgPAfPPe978+YvdBz20MMPdtqdrq7yLy+5bOmS5dde9TtmjEQrjAi9HMgslUoCWAChgTDiGKusMYz7WmktBRJtIQHjqowAZEpK7kAaAaPMWqOULZedSqkLkXY6nampGYeTuXMXMOpMTjUazSZlVCsJaJCANva00047/XNf0srccNMNr772IueotSbM1dYuW7bgwBVL856uBb3a9f/531u/9Y1v7tqwOZpKGpZ8/ktnvfb6vk48n2NQ7JqzfserJ9sFS1adGKeSu8WZmRklOtIKoAXm+oJkKVqmLQBREvM26IQZcXKEcKOlyIQT5LSBAAokzTIPQgDNfY4FlwKxkkllcm5qhGOZDzRVkaTKdQJPcSVTgi6yQo0UAlvQScZdQ1QWRu2y6/ocRByWveKc/kU3PnBbb7VPZSTsJCxg2qRCI6WUSMwHpbjRKTlUgpRo0jSs5YJXn3xq5SEDjogpANGkSEir0TSJzABRh6hM2JhO4hnfLbsO1TpLs4QQo4lEhZzDbIEQx2knnCJcMw6oCWc0SVPQYKSlhipNjLFEM5TUd70kRaW0w1mSCUpdrQA0Mu44HheZBorGAAOPoDECXe4ZG2utP/iRD976l78JoQgYY0x9plNvpmCBMUcpC6AdhyhlXO4yRsIoYQyk4pwwrQ1hqJT+4/V/uP++/z773LMEbaYMJbRS6y4Wi1IljHNCWCeMp6enCWe/u+463/dncVo/v+SSycnJVMvLLrvswAMP7Onp+dSnPsUYswCEqqmpmU4nAqs7zSlAZIzdfvvtWZaceeaZ1iAgf+75x7/17bOn65277vwLqxGu2by+uUsXL1q8eP5nPnZOffJLd959/+YtW3OFQy0rtJpToOUxJxzqYfyzi86PTSuN45dfeObFZ54sFArXXnvtlvVriBb1ieYtN/+x047eeeNVhyAiGgOU0pM/cOqzz77ouw7BROq2hcKss4WwNE7aeVLWEgAMpXj6F7/8sY+/982XNxFmDYLDpOcHH/jg8aVStxvwfIG7Hi2Xy0mo87lysZBL0qbRkdWQC8pTU8MW5QGL+8d2bKoFuY5g2ugSo2gzI2aA1olxjz9y/6efe6tWqbbCGeb6UoUAUK9nlJKcy5VIrY2zRHCHCCuligLXYURLGTPteJ5XH9vnB2AJ14p+5NRPT9V7CyW1fvVrqa7ngqzZ2Dk1uam/Z26d7tPO6Ey8zxpeqOQRqZTS80LQuynrGGTMdYICE+lIkgjOcw7BVApKCWE2zWJCiBfklUSjLCHAOGYiooSj78uMEMdXeoxhpOw0cYSRirHAWtQqI5yisYx6SqWEgrGKUEQ0jLhKJxY0QWNBc84v+sGF/f2DSdq55JKfWNDnX3z+vHnzdmzffe3VN1kQAESBEjo+/cuf3v+A5YzzX//qKofnL774B57v/PmWG3wv71EXjASU3AGUrtLgOjlKqeOwzVuHDtj/sNdee23J4lX3/eNBA841V/9BKfX975/9yMOPA2AQ5FUmRJr98Ic/LBcrP/nJT958800pped5yB3CeH9/fz6ft8rmXI9z3u4sfKc1mbRJ4OdanTcBUwBPpSTIB0DDsclJbTxEJFRqGSMSaQkhBACMkohotXRdlxLMsoxz3mykFNAS/P9FkR0NhKC2ChHjFF3KrUnbrWkAC9AGSIJ8Lg3DgjFoII1T7rOZyRaxHBG5T4xKgPHevq6tW7eGYULQ7a7aXN7ZvPntUsHvJLXVb+3pqrmlUq1SKuZzVWJdqdOp6dFK2ZfSSCldNygVuilVEnKFMhNZgTPf6HYaS0op5X6axYRSrRQAm829RQKgDefcglGWc2Rf+fKXDjv8EM/zHnvssf888F/G2CU//2WxGGzevPWmG2/RAEaDsRIQjIEf/egngZ+/7bbbZuoTSdr5+GkfO+mk9yDiggXzrrjiihdffJlxlxDi++VLL/s5oHP5b/5vZHSftQCg/3j9NZmQiGAN3H3X/bfffh+CIkQCwF/+fAclFNAAwJVXX4GIxkpOSCrVNb+9mqAjFRijKHWmp9rf/f7ZCECQMMqs6zKpSSY6YCmAw1wOyIgFLQVlIKVUCsEicsuIMVZbI4z1KKHGSKPga2ecfshBJxSLxcef+O+LLz5njfjlL3+9YMGij3zkQ52wMzycVitdgOZDH/zgGWd+dWJirNma/tlPL2GUnH766d/6xjc8L7j+hj+8/vmXjVEGrFEybIdKDw7v2Zw0pl980ap83s8FO7ZvHN8zIlNeb0YGtg6PaNetOQ5nTqgMPvnoawsWBkKpTAOgTNLW8qWLgTAQxCFa6BAcKhQYigWNXZVaLLQBQhkWK47ItGMdx/XDREpQbi4PkhLwKQENklhhrAIgBCgDQ1AqIgA5BRqG7SjpcM+CsCoVRoEFizoNCDUy6cRtl+eVSnZu2TU8NFqrzImixHcdyoByMGiNIqlUIBUiFVHoBsygZfnAuu7w8Mj6N9Yd/e5Dtmxan6+URZq5rFLw3FRZBxNuaZ7zoFDIUiWj0HEcRA7GWGPAqExHUoeMEQeYH7hCa6us0aCERmXQILOUWGAWHcIBOTXkjC+eMX/BwRycq391yXHHnHjySe8tFEoP/uefL7zw7Cc+9clKuberu/vvd//95JNPueLy33HOASxYQpn7hS9+5YmnH9qze59SYIF0dQ/kC2mnnTUabc64NlpIBQCVam58ajSXc7U13GFKCIej0eBTYrRGsAQJz3laZIBsptOZaTYQhcv5QG9/O4kkGGv12RecRwjxXe/Tn/70ORec/8Mf/AAQ7/v3v6689ppHHnlkqlHPsswaQ1EtWDBPK5GlUmvrud73vvfdLZvXrtxvueuCBcyybHJy78aNbx//rpM8D4jbUgk8+egLb1X9sFnPOdVTT/3Kpz/55WNPsNde82CrlWaAwNiieYNr3nwNpSrk8iLKjj/quI9//ONBEAz2DY6PjLvMfXPj21ZD4PtKSm0MIiUEGGPvPuGUH//4xye/+3iaM9wFgkYbtNY6nhYacnmv1WwBWK3l3Xfec+cdd11xxRUDcwa3btve1dUjBKvValKY0X0jBqIobBJL5gwu3LF1755dQ91dJe5Aqz5Giae1Rm4Kpb6o46oW9U25k6jExI6rEYC41neJTwik0IEoEzpNQ87B5VxlxglygIQ5HJUGrZW0LuW+H5hUWQFoSDFfi0PR0zUw3RiVUvYNBBPjjUxjqz2+eMm8DWt3L16yNI46XeVSX09t6452YtsiE0T7GqrWFESGyKbrrec8d4JQN0pYLvCMmTQqMSpvQBqwVqGRs3o9EncAgTHKjDKJSC2ABceiR8BXAjWghRlgHaNTIESpDIAgQ60EABhggIiIloLQulqrRR1JGa2Ua0nWVJp95jOfePX159at3ZiJWOn4fe97nxDpxT+88Kxvf2/V/ss2bRxSVlBGqj2VWm/1qt9eAQAz9YbvlX9/3R+HhoaiTlzOF2QSugw8TpTMLvrBT6+55hrXdS1inKb/+ud/zjvvvE9/6nNvvfXW5MTMihUrvvPtb0spn3/++ThNCOBXv/rVG6//4+GHH/75z3zWKPvPf//LWhu4XqVUtpR1ohjAhq12EsfTGtI4GZkcSZpzZFS1JgFogjMGyi2XFvr5RNomde0swk/LjBHJOEVEpZRREigwSl2X5/2g2apblX7/u99ftGBhFEWXXHJJoVQ877zzCoWCUOra3/220+kwh4tULpy/6JKfXjY+tldpe+55v5ZxEzFjHGamp8tdPkJKDCu4VSefrzdHLMpSNa+Ev3NoR3e7x3cCtCzNYqmbH/v0MUKkDs/d84+nPvSB46q1vr17N5VK5V3bJ8rFbi9w3YClQhsghHmZUMAypJBKhUC0Nlprx3WNVUIIxog21nV5JqWxhjlcG8MdYozSxiAAILn9r3//yy23OI5z221/+c+/H/jExz/+ysvPP/zww+eec+HypSu3bdshdQagEU1vz5xarYZoM9EBYrUx995/3z/+eR9jeMuf//LaG68DsVqnSuPHP37aE48/+/BDT5XKXrlUogzz+TwYOzo64vt+rdqnJG234jBq12o1QMG5Oz3VLFX9mem60XJWmclcIhOQyvb318ZGJkvVrsBzEdEaNTU1hWiZMcoaYbPQMKhW8oVCrwFodSZb9WatXC4Wi4AopB0dn7Iq7ZvbY0zs0J5mXTU7U9wBY+GO2+++yzwklLr/vrsff+TRJIm+/92zrrzycqMTiiCl/n9QCHL33Xc/+OADWgsA8AP3pZde6HQ6rVZr69atq1at3LBxLaVUZOHXvv6FBx58uLp0/vHvOXJsZsardLXj1jNPPLp0/qp1azYXHW/pvMHuebWhEdWOBSkNrVxQ2/Ls42akir5pqtZHPv4h3+03yqZKoxEeZsZqUJgRlI4VqFTUpIYpAtrFREYBUCtFBl5Khc+IJ0XeWJZp6wPxUVmrhfQ4pdYkKqJE5QlVEgwjJIeOmwaBjDCLSBq4eSQmqetiuQSEuK5vBFYr/ffd+1AnSr1AWEoUJUppQ2gYd3K5nFIqShOHU8O4ACstSVLL3cDr7vrXA8/6pcrhhx6za+9WzR0A0wnDotfVMW3XLRRyRcfNZZmUgjDCpcwopUYTQijjKCUD1CLTSmeUWFDG8xyNys8HWrDA5xQNoOUUUp0dcfihgef+7Cc/Ioa5kL7x8hsvPPuy67o//8WPn372cYWqnTRu/M0Ngevce/ffwCSEgjHGWPWVM84EdH/5fz8B63z3O+efe875SdpZuHB+tdb7xS9+5ZlnnvnAB09O0rbvOn+/82/f+MY34zieM6f/nHMvGBycS5H99z8P/ePuu1ynIDIJSI868vivfePrF1104ejYsMdRCpvzAoosardcz8vSlBGsVrtHRkZyOV9riYxYpdauXW2VslZTilYJL5fTqaJIjbKzBehBBx3U1VW95+47lyw+H8BmmQACVql169459LDDjjnqkGdf/c8p7/3Q/K6+5YvmOmDXb95+x11/vuPu+4498YtE+75T1KIpjTcx0jji0GNeem5TKV+ZM3/ZGV/7zne/+91CoXDjjTeUqn1xZhnLGXAymSnDtIXuavfkxEShXK6Vu6++/Lfd3SXuZq1m/bWX1xD0hNL5fD5fcKJ2pqRxuFOrdY+Nj+f8GiAj6M2Zt0RktD4lHn9one+Vg8BDwpcuO7Kvu6+nNuDKkRVz9batGxbMn9t1VDVLlOd52gpl6NAu2Z5qWVvwAnbYEUcH3gyYdhh2uvu6O3H75JP227evFcWdKAmzJEk7MRDeaEkwFgjjCBQt81mmZRixopfXYebknLG9Hc/PTaRy5apDlVG1ru4dWwrcC4wVIu61evnmjaI+FR579AkicdN0wehIV6FE0LCJvUymXQSqAB3KEkpnjMV8ruiwNstPy0RzGgupHAb4Pw8coIVZv4JVYAEcDlrDGV85/bDDj8/nuv/70KP/eOBfg3Nql172q4UL55751W9LQRqNFiOmWu2uVCrf/Oa3jTFvvfXGA/99MAjyXbWeU0/9+K9/dVUm20LE3DWHHnpgV6V2+mc/8+JLzz/44H8WL1zwzltvlgv57Vs3HXTgii0bt2qqlM5Oft/JqZAX//DnO7dv/8Pvr8+azfPO/3673bRGIcL9991tLVJLc0H1t7+/WhqJGhDRWD08PHzxxRfP6j211hvWrTvn/HNdxl2Xc06UFjfffDOldN26dRvWbgBj4zQxgEjJtm3bGp2wWC61Wp00TmrlKlqI45izwVABow7CLDA2I0xKNdKcaDuetGiUAka4zDLGfdTc2sh1Xd/3GSOB5wkR12dGZCY+99nPvv7yo9de9aK11uHekYceOrJn/R133HH0ice87/1H/f3vfzepDTy/WFQvvvTQX//6F869/v45e3cJmYVaKs/xW1OdYlWLOGSklIVxX99gnE6uOGDFto37PvrhUyulrjdefX3+vJrLzeHHrlh2QGV8cqRUrCxbq3bt2TF37hEHrDq61Qp7B9x2vZMKwXjJ8VAqa8ACM5ZIaSTn8LUvfmPxoiVpLH/5y18eccQRH/zQKZ7n/uuBfz/26ONfPfMb/X1zyuXqn//8l0987KNXXPGrwcGB/wFwGm3f4729ve1my+G0r7d329atXZXe+nTj0EMOnp6anJgaR2IQ4ctnnHHHnbefccYXoyR0HL+vtzw5NZ7LuQcffPD0dNP3SiJrFMtBLueHcaenZ7CrqwKoVu63cseOHXuGxjnjAG6ayqHdQwh80cLFSdYkBJDQkZExAJbOJDArzzRIOSaZ6u2vlMvV6akGoKEMkNnx0eF8Pt/VVR4fn2QUWdiJACCfY+d8/+z+/qWxSC7/zS+P/9BRH3jf+7u6uv5+592vvb76u2edVesqDg7Ubv3LzSedcOqVV15fq9SEmslEIjLLqSoUC/tGxwjzqt05xq2lSmiFFIiBLMso48aYT37ykx/4wPvuu//uZ55+2nXdRr1OCBDCOp1WrVbTGmZmWu85efnK5Uv+KdV4fXLOqg/Mo4NRnPh+/0c+cuzGTUOTjamywz79iRN11vfXO6bzaVdWNB/48DFriqP9PeT4Dx413NmT2s5Uo+PxnAbju1QkEdc0iRTkfOaSEGSBWMeC0iY0SpmIAcvnKu0skVQxqW2qi3k3FTaxEjADdClxHMI9zxVZR6aJYzytuQSijeqEdfCiyGjrdQF3uLW1vnlhmmlJR8bb9al2KWhv37EvV6m0wxYxrJxjWltmWeDkbSY4asqcNMukFHnXB2nRQqYNMh+wcO/fHo5m5JzF/b3zajONDuUZwUxY6QS5VGVhlhBCQNs000ZbYxSllHMurRFCILHc4a7jCRUBIcioliLTmVFM6sygAdTKKkScN2/O2++8iWgdl6CCAw444LTTPqW0nr9wkeMHhNG1a9dkWVzIOWG7Q5GhJUZnhNjLr/r1Rz720W9+89td1cFGvS2U2m/VAd/81hm+n0M0ANYalJnt7a0IIWbzG66++ppbb//r22+vaUx1yqUa8/KEUsLN5z//yRNOfM+53/1+u9PgVt98059+/MMfce5ONzucUCsVQ9JdqV547nnLli3LsuxrX/ualQoA6Kxmj3HUppAvdHV1+a43NTWTZZoQx/f875x19g8uumDZsiVIGAIDqxGZ4zjj4+NTU1M9PT2L+/bvtMx+hxzbaTS65889ZdXhR5922tOPv/TKAw/ng5N9t5tqkNq8/OJLRxx+4HW3/Ell6c9+9rPHHn/ymmt/t23btqlGa7rZ7iRJlGUzjabW6gcXX3zVVVdNNevA6fT05NfP/AJB/v73v9/P02efeW3e3AUX/eBnAwN9F1/045tuviHqTH/j69+49dZbPvih9x99xPFG89UbXn36qaeB5ClUOJlTcFdqlTNpQClu36C3mkbcGXNZYIyOo9LoaCMzLYIOEmutFEoz1jVeB2sSh7M33p4uBEhMwZrqlm0AzEOGDqBHwM9bkjfGgNTWUJ5kwgolohCsCqMol3ctkGgmreXLkczKJUREobKp8RqjTqsOmzdElAJSX8k2pcumpydLpaVrVufDtkzCA15+kiNRBJM00q1mF1iCYLXmkiEQq03iO7ZWzaFtK0mYQ4QwzAGlgHPQGqQCxoBwUAq0BcLgrnvu+8tt91ES3H7HPf9++B/N5sTFF1982WWX1eutpJNJqQFVq9n81EWfefDBhx5//PFrrrnipZdeGhraE3bSm2/+o+cxxki1XDI2nTvY/9zTz915552XXvp/69at7e7u6XTCTqczPT150IGLCCHaKiCkWOoCa7531gXf/ta333X8KS88/2y73UZiKWPGmCRLEJgUaKyTqA53XaEyRGSOYzQoowGAMMqslUY5DuMM0yxkjGVZolVgpNJKedxRSjnO/+A4AJDGoZSyv79/YL+BPXv2yEzOHZyzb9wBNJzOejUdUPycC89etGAhZeqSn//oyCOP+shHPlooBP/69z+effKVL57+9d7BcrVUvvXWWz/4wQ/eeNMftVS5XI4i2X///SuVymc/+9mXXnrpoYceGhkZWb58uZSyq9rVnmlR4JyTLE6SODz+mGNXLFv83Asv3nf/c0D7A9/VJkNLysWK0eNhJ/Fcp1As18q1dKL15hvvBH7ljTfeGNq5t1KptFpT49NTn/jctzvRlAbp5GFgHt+wevvIvnm1cq8WwdDOUUaM67r7Ridcp4cyR1mBTEuRhlF28GEHIbD/+79fp2nabrdffPH5p55+zHHo76//w2OPPYaIk5P1y39zHQW84oqrS8WyEKLVntAa0NALLvjBMccc8+CDDwwODu4e2nv4Ycfu3P6PhQuWTEyOjo0PIwVrYGBwcBarnmUZISxLdbMxYwzpdJL99jvwsUef8v2g3W5zztM0fe75Z/7w+5tOPvldP/zhRXPmzAHLRvbVCXFTEb7vfe/5/OmflkIvXLjwlFPef+rHTp+cnHzs0WeE0NYqsLNgR9vX01NvTE9PNhr18LZb7zjnnPM+fOqpMk3uvueOFSuWfeRDp15yyf+xTEa5XD7V/lFHLdNaf+ubZxlQlOvnnn3hpRdeLpfLV1x59abNuxDo+OjEX275Y6s989prGwj6041x6ihrgTjwvbPPOvGEk59++mlkfO/IPse1zGNeLmg3YwIwO5N49bWXH3n0Ie44N918/dtvvqW1RkQkVohs9tgYOOigVUed+N7r//TXYndfhunvr79xv/0WaasrPdWu3q577vjn0Ycd7gfsintv/MhHzhrO6jQdpxUypZMjPv3+X/3mp70nLZABdLSmc8uxsNRCU6QkcDPjBpWySAVJpV/wKRqZaM/PSyPylaJIwk4mHC/vEJc5mvr5KEDqu67hyIzWjPtuKkWe5jyPT8dJxqgmju/m0hYlkOvvmRMKncTexHC7PjOT1feMTE4nwrYboQcOB0KkKefz1lGu5aIdu66vCKVWE8KYRhknjucpMDkOUmqhhWNk4EDRd61ynnnwGaT25A+eWOvt8bnX0XGmSKOduVI6HC0IrXCWRUwoUUqGScw4RUKsgkRmuhMTAgQoGqFBGisZ8mY70gYAWZopQ/jufRMHHHzQcy9tlknqgv785z7z81/8mlL/t7//bRB0ubxYrfX09vZRoDk/mFENQl1GWKpmgCrKU4dVlCSMMQLw3LNPSyG6arU4jAhqsNr3fIYeJUGp2D1ncJ7j8GcefwEJUUo0OnVEm+j2GV87vTFTv/D8s2QqGSEW8OtnfoM73sJFfXvHNjMLRuvu7u4sSc895xzO+LnnnnvmGV+9+pqrOeNSSQAQaUYA251OlmVWk3nz53fCJM3SCy/8wd/uuGN6qr5okUGkSZpxx5FSB4V8Y6YxtHvn/gceXOzuydAbi2x1cMnOTlOEzVbcOPr9J/Z3HfrALdujaDpgQGxKeHTFNVcYmycmo5Tefc/tf7v9FsYYY0yJ9Na/3IiIYK3D6TXX/IpS1CIBgkDAWquMePjRfzHOjYHde3Z/9+yvARBrZu3D5M+33ABgbrv1lr/ddrux3GKEFKxOkYLRNoql7wVxRDVYxlzGOHPLGggSXemtMgYWPZUJLRXjmGZCKMZJE7hVMnWdslVEKgqWW4LWaJnIRBvGWJrGjKLD3EzpzCJ3Kxag1uVaJQOV8HzQiUIHRKlUozpWVsRpxDlPs5zDXa2k6/UYaYmkDJFR5JZTWwZTyLIWzQZoupBzrk2dasHRyyRnNG9UAWjh3LPPmbdwhVbRDX/41Qc/fOKpH/4CD8x9993z8KMPff1b36hWewYGBq6//vpPfvzUK668nFFQCjjSOLHMzTOSGx2fAoVRGruMG4EiVkpZAKhUKvl8fvGi5U89+aKWMDw8tmTJim1bhvI9hShrFnK5LBVaGiS60Wi99tprSZRsWLfec7ywlQRebpZqUq/XAQhiYLVKO3L9+tVgYM2atYcffvizzzyjtaaMWYuIxBJrtGWunyl0vDxjjBI+S4FVRiFQa61WljHXDxyRiSyTWggkGSFEpTFzXUqp1ppSlqYpcxxtjZZq+dJl2oLrumPjI2mSlIslqURvb1dnWmbtkEMMhLzrhHcToFdccRWnplqq7dszet01f3Rc+PGPLty6fqfn0ka9fdONf46i6I83/okQt1itGKksmDlzFz//wmt//dvdv/71r998a93OXfvO+OrSm//0V0Dx/e9dKFOk0sRJtn3z0Bc+d4aF7NeX/2bt6n3r14VWSS2lsUmQM2h0OejSWoed1vq1067PvFyhVq5t2rRJZciBa9Bd1e580C9JwGjJ5fnlK/JbN27atmV72N5MiDN33uC+vdu1Fp7vuJwVS/mJiShNJkoVp7trzqGHHrR7aF+5VG1jGHXiI4444rTTPkqonTMwaC0w5Js37zbKJQQoUZRSY4w2s6Fw+vob/vif/z7405/+eN2G9f99+JGLL/rp/136m+F9Q+OTY65P01RTyr94+jfuuOu2/v5erS0A4Zy22ynjEOTck9593GOPPZLLeYSAtaiUOf3zX7z33nsef+zBQjHXas8cfsTBzz3/vAHmeu7nTv/sZb/+v7HR8fvu+6fj+oz7mQBCHSQajbbWMsqRGM6IUcpqkAriUPT2zFu/esupH31/V7V30YKlrUa4cN5SRsFHRJ1mixctXbdugwFDqaNVfOTxR37pi1/UWnd1dYVhaC1u3rzZ4VwoAwSNMZSRWTckY/C731170003/fnPf37gP/cDWKFAG0cZROZYKRhjcZp1Oh0kREr59turly1btnHjxt7eXq01Y6S3t3dqaooyEkdZrLq++p0fRXEniRtEh/XGRCNpznSyTdvWdJV6mlMN0eXvf/RhL23YFNqqj6mYJE+/8EZpoNG3vO+Bx1/knoOeUSTurtVqQQUVQc7a8UwpUCzRNa/YakWez8Kw3W6NAxKDgnG7YM6C1nTbd1ztokTRitqUOq4irpY5RhIdCaFaLeuDRaCWasoyArRaqUUzdue69vDeyfHdnaittJFBTnquS4ytdvXpVIC1zMlLpRQDbiHvu5xzhQRITkuNFjhay4jSWmlR7a6GcYhEE2vQMCFpzalxxt5+/s1CoXDk+z8cGlkpDQztHA2T8VxgtJGz0WyEECSaMTJ7Z0eQKaUclwEYg8DRBYOOT61VDmH1hieVJzOR84Mo1K+/9s6BBx/x68uvIdr+9ebr3lmz+rJLf717z3i7lVnF0XAlcDYb48tfOvOqy29SylCuHeLUuqtoTU/X3H0ju42Vjguc0zmDC9rtME2FBdndUx7aNTy8d5yA32xEs6TugBRSGXoOkSbWSIwxGzduPObow/sHqrt3jhqtwVIgXlDomm62LDc6BcYZ5cz1vYX5vOM4zz333FVXXfW73/1OqFkcFVDOgOD/CNho2p1mqVJKx5MDDz5w/wP3/973znJcVijk7rv/H5/7wulAIRMJGLFv395lK5fRqrtz72iFsV3Tw0LHfiG/dPmySFG/Ulu6ikzu9pK4o6wxmSEcjOLEploJoBSsBotJnCICWgLGGm2tUcYAmU2+MJax2fMDC1RKSTloS60iAAwogkkpQ600IQAAVmsEYnFWy5kq3STojk6uB7sXSaC1NjAbMo0IFND4HiPE5lhQyAVSxBSNUBjHQJFkqaBMtxrjHqNSaACSioR6TBlpIdBacYoizQhhlnBFiOlg4AczM7HDAKiVoc0V8tQnkY6ska3GtCFoCW02EzfnzBnsNZnOuS4xVsio6BenzahjhcPCfL7Zbo8V3DJSqnWTEzIzJSl6DkGDcMwxh6UyPOe889Dq3m7/+ZdeffChZz0fb/7THx9+7EFtZLvdvPKKKwDtFZdfTjkYDYyB0ppy9v3vfuvEd59y/33/BsvBulIQl+U5dYSVAKpUDPbs3rN545aVy/fbvHHTimUrx8ZGkEKz2bCQxDLlLCAUlE42bNgwZ3Bw3bp1CxYsePLJZ9at23DYoUes3/DO4Ycf/q/7H0OgVLsG6MZ1W1eu2O/1115eumThrl3bGefzFixMEikEUIdTx86Z29duZ75X7O8tGmPGxydnZmY4dxGIECLwvK6uLmvtzMx04PkTk2O9vd1CiKjTspQAQBxGSinfdQkhFtEiRFHUaDWTJOHctdYSwL3btgJ1QLdKpWM5ZYQAEjlvfv/bb7/ZmgkdFwkRRx+938c++gkAO9A/f9++fcZmQ3tGoyQulIpKqZmZmXbYyeVycRzXW8033n6rE0fvrFtb7qqtOujAN1evvuuuu046/rDTP/e5P9/yN6GVQ50wjDhlhLhvv/nOksWLN65bBwBobc73onbbz1tQymhBGaWEcsKzMBtLx/NBIZJCpsYCpIn+y5/vDEqsVPWa7frHP/qVY4856vUXVi9auDyORNhqdnV1Kd3UMlE60Vq4blGmXuDnjSns3rnvoFVHvPXW6qmpqSRJPv/5z1904Q8SEd533z2EoJTCSMMJZ9xk0iid5TxXayAUGOU9fd3bt2+PoijNwjSNbrzp90mcffs7Z730youp0EhAa9s/OOeiiy4sFgoDAwMf/cjHnn/+2f7+6oYNW448/LCd27cN7RpavHgBWM0IT2VGCel0WsYCpfSFF15YsXy/P//5JiXNxT/8wYvPv3DpLy/dsnlbqxGKTCaJoJQpnWmd/eiHP/7N5VdoZYKACyHOO/vc+QuXM+o+89Sz27duMcYcesiqSy/9pRTpz356aacdMwLEGAOIu4Z2HHPkux555LlMSeayr5zxpauuuHJ4ePiee+/zPNdxmLVaa0mBgyEArlYZd0BbIICAjshUsznNHSAE0XKHBEYaqwRForWezRLvdDqM8/1WrPzX/felaXrUUUf5vl8oFObMGdi0ZavWVhl44vE3/VKut6+HW+yr9ixasl+xJ0hEM6p3sk6yZ9fmsX3ru/K0Z0y3RaGV8Srp627Xc2ZmeU9vGJq0wZzAMFeTRkPaJJOkHbclJHvStsgM44GlrLeQJxYyyhppUnY92263N+5JQRvGHE244Ytcx89zlNREYEtRMcly3dVEY6ZMv1cjMlIAiQl9z133wgtJO8z3DAArVHMsZ2idyRyzTpI4HgzsP394fDRsh4HvuG4OfNTUhmFoBIjM+rWuWBowUWpEopRC1UgTN+ckaZpzHKos+jykWhlZnFelxlOpdjJujNdpxmOTI0GOUEIIcq3rgFLIMMj5Wapcp+C5vtLS910AwxxudaKVUTozNkU7o0Irs36jIQkTo2wcpXfccWehOgekdiB59LEnHn74GUo8QkjgsScee4xS4ruutfbe++9Q0EaOCiJtsskxFTZts9kMo5YFrY0I29Ge3SMUXQJ8z9Du+fMG9gwNH3/sccV8SUuzdvWaLI3fddwxz7zwpNKyWi7P1EOO+Tdf3/zkEy9cfuV1P/7RD/fs2asEM5qV87XJ6WErNFKqjEyyOGtnYSdyXP61r31t686t0ijC0FprDWirgKO2GgiC1sVyYXx8HFB94tOnEUKMkocfftjZZ3/vzG98HRGQAGPEyQdbt2067oRjLYjjjjnooEOP2js27Pq80YnmLVimIvri1k3K4ZIpTTmYsrF9PtVd5d44nTHGZFlWyHuU0v7+/omxkWaziQQ5Ac651hrQEEIYY0mSBDlXG55mGnkKDCH1AHoAPNANoC2tIgButEZEC7OJdACAYNENUGQNZZHSRppJQANWgwTCPSMkAJHKWp02LANIKQgKloIHUFDgATCppDUmMiEBQxko0EISDWiMwxiRYN0cQ6QK6Kmf+OQba9YY2XQtdFp1x3dinYZpIlshZ/5hS1Yec9S8WGQvvPpiqcIPPWzliSesbM90iLEERBza3u6eO25/7ZBD5xeqTrMjm6POgiWEOZTSnrCVf/zxKW3yhLZiYVes7N669XWpQ45OkiRHHHnIxz/5WaLtnIF+0OAQ2LJhLRqJaGdzrykAGEAC1qjfXnvFn/508x9+f8PTjz0zPjkch4kxpqe3S4j81ORIuzW9YP6cRx97+HvfPeeYY46cnp6cGB8FY6SIz/3B2ddedZ1UmREawNx7770/uODCb3zja2+99damTRv27tl3wknHXXn1Fbt27dq0aZPRhR/+4IIrrrrilZdePf64I6+55powal962WXIUFs9OxtSFhYsWjw5OX7woUcvWrRfdxHiON66devwnr1vv/02Y6yvr88S2L17yPO8hQvnD+3apbUeHR231gIYqQVjbFYW0+p0/kds9VzOsb+3L8nSqakpArTT6Th+gIhZKoRIOXNcrxQ29fDw8GGHHvLGK6ubzZgS+YlPfOKC8y8yxtx51+25fBXR11oRgmHYybLMWkMQRZZyRjdt2jRnzpyZmZnFixc//PDD8+bNEyLTWrVb04Wib61mBLTWhVwpiWWWJsuWrXjo4ZeNsZwEaNpK6EK+YG1CeFjKF5qtkDGWhikAeAUXtAGDjPEkzYqBu3jpnHY8yahyKHFpsP+KFTIKA9dvd9jmjXtzuYLITCayXF4nceg7c03mtOqNJJt46+3Row5710UX/UCK9Gc/+9ErL7947bXX7ty5s9lsW20ZByRSmQi0ueDC71x33RWeX5w3b74x6gtf+PzChYs9L3jxxedbndaylfPPP/9CJe3jjz85Njre01s744wzrrz6d+eefxZa1dvbf8455zz44IOMESmzwA8O2P/It95cPzgwx2gwRn3rW9+45prf/utf9//sZz/5+hmfv+SSSyYmJn971TUa2JyB+ZVi7blnXvzn/fd3OlGtVlm8cN7TTz0aRk0pI8rhz7dcj2As0GKh0qjXr7z6egtAECkHoEppc+tfb/zLbQAWCBADnGmQHAxY/eyzLx1z5PE33nyDBvOby3/+3AvP/fDHPxwbGU2zOMsSY6XWkhBCbHDR+edde+0furrLcToFNrngvB/Mm7uEc3ziyUfCZueAlQd897vnLJg779orr73rrrsmJ8fPOPOrt9566yc+8Ylly1cSCm+89vr4+CQA3HXX3//611u1NTf88UYpJSCt1XqrvT0JqGajU2ClNbv3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\n" + }, + "metadata": {}, + "execution_count": 7 + } + ], + "source": [ + "# Show the output image\n", + "from PIL import Image\n", + "Image.open('./output/vis/demo.jpg')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7GrWIJywLV-V" + }, + "source": [ + "## Train with customized datasets\n", + "\n", + "In this part, you will know how to train predefined models with customized datasets and then test it. We use the [balloon dataset](https://github.com/matterport/Mask_RCNN/tree/master/samples/balloon) as an example to describe the whole process.\n", + "\n", + "The basic steps are as below:\n", + "\n", + "1. Prepare the customized dataset\n", + "2. Prepare a config\n", + "3. Train, test, and infer models on the customized dataset.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "E73y5Lru-wBx" + }, + "source": [ + "### Prepare the customized dataset\n", + "\n", + "There are three ways to support a new dataset in MMDetection:\n", + "\n", + "1. Reorganize the dataset into COCO format.\n", + "2. Reorganize the dataset into a middle format.\n", + "3. Implement a new dataset.\n", + "\n", + "Usually, we recommend using the first two methods which are usually easier than the third.\n", + "\n", + "In this tutorial, we use the ballon dataset an example of converting the data into COCO format.\n", + "\n", + "**Note**: Datasets and metrics have been decoupled except CityScapes since MMDetection 3.0. Therefore, users can use any kind of evaluation metrics for any format of datasets during validation. For example: evaluate on COCO dataset with VOC metric, or evaluate on OpenImages dataset with both VOC and COCO metrics." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "sbJhEsckU8UX", + "outputId": "b9fdd1b1-5591-41b9-b27c-cadccac9aafa" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Downloading https://download.openmmlab.com/mmyolo/data/balloon_dataset.zip to data/balloon_dataset.zip\n", + "100% 36.9M/36.9M [00:01<00:00, 25.0MB/s]\n", + "Unzipping balloon_dataset.zip\n" + ] + } + ], + "source": [ + "# Download the data and unzip it\n", + "!python tools/misc/download_dataset.py --dataset-name balloon --save-dir data --unzip" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gmMpkzs2U8UY" + }, + "source": [ + "#### COCO annotation format\n", + "The necessary keys of COCO format for instance segmentation are as below, for the complete details, please refer [here](https://cocodataset.org/#format-data).\n", + "\n", + "```json\n", + "{\n", + " \"images\": [image],\n", + " \"annotations\": [annotation],\n", + " \"categories\": [category]\n", + "}\n", + "image = {\n", + " \"id\": int,\n", + " \"width\": int,\n", + " \"height\": int,\n", + " \"file_name\": str,\n", + "}\n", + "annotation = {\n", + " \"id\": int,\n", + " \"image_id\": int,\n", + " \"category_id\": int,\n", + " \"segmentation\": RLE or [polygon],\n", + " \"area\": float,\n", + " \"bbox\": [x,y,width,height], # (x, y) are the coordinates of the upper left corner of the bbox\n", + " \"iscrowd\": 0 or 1,\n", + "}\n", + "categories = [{\n", + " \"id\": int,\n", + " \"name\": str,\n", + " \"supercategory\": str,\n", + "}]\n", + "```\n", + "\n", + "Assume we use the balloon dataset.\n", + "After downloading the data, we need to implement a function to convert the annotation format into the COCO format. Then we can use implemented `CocoDataset` to load the data and perform training and evaluation.\n", + "\n", + "If you take a look at the dataset, you will find the dataset format is as below:\n", + "\n", + "```json\n", + "{'base64_img_data': '',\n", + " 'file_attributes': {},\n", + " 'filename': '34020010494_e5cb88e1c4_k.jpg',\n", + " 'fileref': '',\n", + " 'regions': {'0': {'region_attributes': {},\n", + " 'shape_attributes': {'all_points_x': [1020,\n", + " 1000,\n", + " 994,\n", + " 1003,\n", + " 1023,\n", + " 1050,\n", + " 1089,\n", + " 1134,\n", + " 1190,\n", + " 1265,\n", + " 1321,\n", + " 1361,\n", + " 1403,\n", + " 1428,\n", + " 1442,\n", + " 1445,\n", + " 1441,\n", + " 1427,\n", + " 1400,\n", + " 1361,\n", + " 1316,\n", + " 1269,\n", + " 1228,\n", + " 1198,\n", + " 1207,\n", + " 1210,\n", + " 1190,\n", + " 1177,\n", + " 1172,\n", + " 1174,\n", + " 1170,\n", + " 1153,\n", + " 1127,\n", + " 1104,\n", + " 1061,\n", + " 1032,\n", + " 1020],\n", + " 'all_points_y': [963,\n", + " 899,\n", + " 841,\n", + " 787,\n", + " 738,\n", + " 700,\n", + " 663,\n", + " 638,\n", + " 621,\n", + " 619,\n", + " 643,\n", + " 672,\n", + " 720,\n", + " 765,\n", + " 800,\n", + " 860,\n", + " 896,\n", + " 942,\n", + " 990,\n", + " 1035,\n", + " 1079,\n", + " 1112,\n", + " 1129,\n", + " 1134,\n", + " 1144,\n", + " 1153,\n", + " 1166,\n", + " 1166,\n", + " 1150,\n", + " 1136,\n", + " 1129,\n", + " 1122,\n", + " 1112,\n", + " 1084,\n", + " 1037,\n", + " 989,\n", + " 963],\n", + " 'name': 'polygon'}}},\n", + " 'size': 1115004}\n", + "```\n", + "\n", + "The annotation is a JSON file where each key indicates an image's all annotations.\n", + "The code to convert the balloon dataset into coco format is as below.\n", + "\n", + "Using the function below, users can successfully convert the annotation file into json format, then we can use `CocoDataset` to train and evaluate the model with `CocoMetric`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "rHnw5Q_nARXq", + "outputId": "4b1efb44-f81d-486f-80e6-461a57fe84bc" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 61/61, 34.8 task/s, elapsed: 2s, ETA: 0s\n", + "[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 13/13, 22.2 task/s, elapsed: 1s, ETA: 0s\n" + ] + } + ], + "source": [ + "import os.path as osp\n", + "import mmcv\n", + "from mmengine.fileio import dump, load\n", + "from mmengine.utils import track_iter_progress\n", + "\n", + "def convert_balloon_to_coco(ann_file, out_file, image_prefix):\n", + " data_infos = load(ann_file)\n", + "\n", + " annotations = []\n", + " images = []\n", + " obj_count = 0\n", + " for idx, v in enumerate(track_iter_progress(data_infos.values())):\n", + " filename = v['filename']\n", + " img_path = osp.join(image_prefix, filename)\n", + " height, width = mmcv.imread(img_path).shape[:2]\n", + "\n", + " images.append(\n", + " dict(id=idx, file_name=filename, height=height, width=width))\n", + " \n", + " for _, obj in v['regions'].items():\n", + " assert not obj['region_attributes']\n", + " obj = obj['shape_attributes']\n", + " px = obj['all_points_x']\n", + " py = obj['all_points_y']\n", + " poly = [(x + 0.5, y + 0.5) for x, y in zip(px, py)]\n", + " poly = [p for x in poly for p in x]\n", + "\n", + " x_min, y_min, x_max, y_max = (min(px), min(py), max(px), max(py))\n", + "\n", + " data_anno = dict(\n", + " image_id=idx,\n", + " id=obj_count,\n", + " category_id=0,\n", + " bbox=[x_min, y_min, x_max - x_min, y_max - y_min],\n", + " area=(x_max - x_min) * (y_max - y_min),\n", + " segmentation=[poly],\n", + " iscrowd=0)\n", + " annotations.append(data_anno)\n", + " obj_count += 1\n", + "\n", + " coco_format_json = dict(\n", + " images=images,\n", + " annotations=annotations,\n", + " categories=[{\n", + " 'id': 0,\n", + " 'name': 'balloon'\n", + " }])\n", + " dump(coco_format_json, out_file)\n", + "\n", + "if __name__ == '__main__':\n", + " convert_balloon_to_coco(ann_file='data/balloon/train/via_region_data.json',\n", + " out_file='data/balloon/train.json',\n", + " image_prefix='data/balloon/train')\n", + " convert_balloon_to_coco(ann_file='data/balloon/val/via_region_data.json',\n", + " out_file='data/balloon/val.json',\n", + " image_prefix='data/balloon/val')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yc9UDp1vU8UZ" + }, + "source": [ + "## Prepare a config\n", + "\n", + "The second step is to prepare a config thus the dataset could be successfully loaded. Assume that we want to use RTMDet-tiny, the config to train the detector on balloon dataset is as below. Assume the config is under directory `configs/rtmdet/` and named as `rtmdet_tiny_1xb4-20e_balloon.py`, the config is as below.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "id": "XjTW6XydU8Ua" + }, + "outputs": [], + "source": [ + "config_balloon = \"\"\"\n", + "# Inherit and overwrite part of the config based on this config\n", + "_base_ = './rtmdet_tiny_8xb32-300e_coco.py'\n", + "\n", + "data_root = 'data/balloon/' # dataset root\n", + "\n", + "train_batch_size_per_gpu = 4\n", + "train_num_workers = 2\n", + "\n", + "max_epochs = 20\n", + "stage2_num_epochs = 1\n", + "base_lr = 0.00008\n", + "\n", + "\n", + "metainfo = {\n", + " 'classes': ('balloon', ),\n", + " 'palette': [\n", + " (220, 20, 60),\n", + " ]\n", + "}\n", + "\n", + "train_dataloader = dict(\n", + " batch_size=train_batch_size_per_gpu,\n", + " num_workers=train_num_workers,\n", + " dataset=dict(\n", + " data_root=data_root,\n", + " metainfo=metainfo,\n", + " data_prefix=dict(img='train/'),\n", + " ann_file='train.json'))\n", + "\n", + "val_dataloader = dict(\n", + " dataset=dict(\n", + " data_root=data_root,\n", + " metainfo=metainfo,\n", + " data_prefix=dict(img='val/'),\n", + " ann_file='val.json'))\n", + "\n", + "test_dataloader = val_dataloader\n", + "\n", + "val_evaluator = dict(ann_file=data_root + 'val.json')\n", + "\n", + "test_evaluator = val_evaluator\n", + "\n", + "model = dict(bbox_head=dict(num_classes=1))\n", + "\n", + "# learning rate\n", + "param_scheduler = [\n", + " dict(\n", + " type='LinearLR',\n", + " start_factor=1.0e-5,\n", + " by_epoch=False,\n", + " begin=0,\n", + " end=10),\n", + " dict(\n", + " # use cosine lr from 10 to 20 epoch\n", + " type='CosineAnnealingLR',\n", + " eta_min=base_lr * 0.05,\n", + " begin=max_epochs // 2,\n", + " end=max_epochs,\n", + " T_max=max_epochs // 2,\n", + " by_epoch=True,\n", + " convert_to_iter_based=True),\n", + "]\n", + "\n", + "train_pipeline_stage2 = [\n", + " dict(type='LoadImageFromFile', backend_args=None),\n", + " dict(type='LoadAnnotations', with_bbox=True),\n", + " dict(\n", + " type='RandomResize',\n", + " scale=(640, 640),\n", + " ratio_range=(0.1, 2.0),\n", + " keep_ratio=True),\n", + " dict(type='RandomCrop', crop_size=(640, 640)),\n", + " dict(type='YOLOXHSVRandomAug'),\n", + " dict(type='RandomFlip', prob=0.5),\n", + " dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),\n", + " dict(type='PackDetInputs')\n", + "]\n", + "\n", + "# optimizer\n", + "optim_wrapper = dict(\n", + " _delete_=True,\n", + " type='OptimWrapper',\n", + " optimizer=dict(type='AdamW', lr=base_lr, weight_decay=0.05),\n", + " paramwise_cfg=dict(\n", + " norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True))\n", + "\n", + "default_hooks = dict(\n", + " checkpoint=dict(\n", + " interval=5,\n", + " max_keep_ckpts=2, # only keep latest 2 checkpoints\n", + " save_best='auto'\n", + " ),\n", + " logger=dict(type='LoggerHook', interval=5))\n", + "\n", + "custom_hooks = [\n", + " dict(\n", + " type='PipelineSwitchHook',\n", + " switch_epoch=max_epochs - stage2_num_epochs,\n", + " switch_pipeline=train_pipeline_stage2)\n", + "]\n", + "\n", + "# load COCO pre-trained weight\n", + "load_from = './checkpoints/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth'\n", + "\n", + "train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1)\n", + "visualizer = dict(vis_backends=[dict(type='LocalVisBackend'),dict(type='TensorboardVisBackend')])\n", + "\"\"\"\n", + "\n", + "with open('./configs/rtmdet/rtmdet_tiny_1xb4-20e_balloon.py', 'w') as f:\n", + " f.write(config_balloon)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "5LNm7LxwZG2w", + "outputId": "aac457c5-f915-433d-e98a-85ddd9005543" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "04/17 10:28:35 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - \n", + "------------------------------------------------------------\n", + "System environment:\n", + " sys.platform: linux\n", + " Python: 3.9.16 (main, Dec 7 2022, 01:11:51) [GCC 9.4.0]\n", + " CUDA available: True\n", + " numpy_random_seed: 904036445\n", + " GPU 0: Tesla T4\n", + " CUDA_HOME: /usr/local/cuda\n", + " NVCC: Cuda compilation tools, release 11.8, V11.8.89\n", + " GCC: x86_64-linux-gnu-gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0\n", + " PyTorch: 2.0.0+cu118\n", + " PyTorch compiling details: PyTorch built with:\n", + " - GCC 9.3\n", + " - C++ Version: 201703\n", + " - Intel(R) oneAPI Math Kernel Library Version 2022.2-Product Build 20220804 for Intel(R) 64 architecture applications\n", + " - Intel(R) MKL-DNN v2.7.3 (Git Hash 6dbeffbae1f23cbbeae17adb7b5b13f1f37c080e)\n", + " - OpenMP 201511 (a.k.a. OpenMP 4.5)\n", + " - LAPACK is enabled (usually provided by MKL)\n", + " - NNPACK is enabled\n", + " - CPU capability usage: AVX2\n", + " - CUDA Runtime 11.8\n", + " - NVCC architecture flags: -gencode;arch=compute_37,code=sm_37;-gencode;arch=compute_50,code=sm_50;-gencode;arch=compute_60,code=sm_60;-gencode;arch=compute_70,code=sm_70;-gencode;arch=compute_75,code=sm_75;-gencode;arch=compute_80,code=sm_80;-gencode;arch=compute_86,code=sm_86;-gencode;arch=compute_90,code=sm_90\n", + " - CuDNN 8.7\n", + " - Magma 2.6.1\n", + " - Build settings: BLAS_INFO=mkl, BUILD_TYPE=Release, CUDA_VERSION=11.8, CUDNN_VERSION=8.7.0, CXX_COMPILER=/opt/rh/devtoolset-9/root/usr/bin/c++, CXX_FLAGS= -D_GLIBCXX_USE_CXX11_ABI=0 -fabi-version=11 -Wno-deprecated -fvisibility-inlines-hidden -DUSE_PTHREADPOOL -DNDEBUG -DUSE_KINETO -DLIBKINETO_NOROCTRACER -DUSE_FBGEMM -DUSE_QNNPACK -DUSE_PYTORCH_QNNPACK -DUSE_XNNPACK -DSYMBOLICATE_MOBILE_DEBUG_HANDLE -O2 -fPIC -Wall -Wextra -Werror=return-type -Werror=non-virtual-dtor -Werror=bool-operation -Wnarrowing -Wno-missing-field-initializers -Wno-type-limits -Wno-array-bounds -Wno-unknown-pragmas -Wunused-local-typedefs -Wno-unused-parameter -Wno-unused-function -Wno-unused-result -Wno-strict-overflow -Wno-strict-aliasing -Wno-error=deprecated-declarations -Wno-stringop-overflow -Wno-psabi -Wno-error=pedantic -Wno-error=redundant-decls -Wno-error=old-style-cast -fdiagnostics-color=always -faligned-new -Wno-unused-but-set-variable -Wno-maybe-uninitialized -fno-math-errno -fno-trapping-math -Werror=format -Werror=cast-function-type -Wno-stringop-overflow, LAPACK_INFO=mkl, PERF_WITH_AVX=1, PERF_WITH_AVX2=1, PERF_WITH_AVX512=1, TORCH_DISABLE_GPU_ASSERTS=ON, TORCH_VERSION=2.0.0, USE_CUDA=ON, USE_CUDNN=ON, USE_EXCEPTION_PTR=1, USE_GFLAGS=OFF, USE_GLOG=OFF, USE_MKL=ON, USE_MKLDNN=ON, USE_MPI=OFF, USE_NCCL=1, USE_NNPACK=ON, USE_OPENMP=ON, USE_ROCM=OFF, \n", + "\n", + " TorchVision: 0.15.1+cu118\n", + " OpenCV: 4.7.0\n", + " MMEngine: 0.7.2\n", + "\n", + "Runtime environment:\n", + " cudnn_benchmark: False\n", + " mp_cfg: {'mp_start_method': 'fork', 'opencv_num_threads': 0}\n", + " dist_cfg: {'backend': 'nccl'}\n", + " seed: None\n", + " Distributed launcher: none\n", + " Distributed training: False\n", + " GPU number: 1\n", + "------------------------------------------------------------\n", + "\n", + "04/17 10:28:37 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Config:\n", + "default_scope = 'mmdet'\n", + "default_hooks = dict(\n", + " timer=dict(type='IterTimerHook'),\n", + " logger=dict(type='LoggerHook', interval=5),\n", + " param_scheduler=dict(type='ParamSchedulerHook'),\n", + " checkpoint=dict(\n", + " type='CheckpointHook', interval=5, max_keep_ckpts=2, save_best='auto'),\n", + " sampler_seed=dict(type='DistSamplerSeedHook'),\n", + " visualization=dict(type='DetVisualizationHook'))\n", + "env_cfg = dict(\n", + " cudnn_benchmark=False,\n", + " mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0),\n", + " dist_cfg=dict(backend='nccl'))\n", + "vis_backends = [dict(type='LocalVisBackend')]\n", + "visualizer = dict(\n", + " type='DetLocalVisualizer',\n", + " vis_backends=[\n", + " dict(type='LocalVisBackend'),\n", + " dict(type='TensorboardVisBackend')\n", + " ],\n", + " name='visualizer')\n", + "log_processor = dict(type='LogProcessor', window_size=50, by_epoch=True)\n", + "log_level = 'INFO'\n", + "load_from = './checkpoints/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth'\n", + "resume = False\n", + "train_cfg = dict(\n", + " type='EpochBasedTrainLoop',\n", + " max_epochs=20,\n", + " val_interval=1,\n", + " dynamic_intervals=[(280, 1)])\n", + "val_cfg = dict(type='ValLoop')\n", + "test_cfg = dict(type='TestLoop')\n", + "param_scheduler = [\n", + " dict(type='LinearLR', start_factor=1e-05, by_epoch=False, begin=0, end=10),\n", + " dict(\n", + " type='CosineAnnealingLR',\n", + " eta_min=4.000000000000001e-06,\n", + " begin=10,\n", + " end=20,\n", + " T_max=10,\n", + " by_epoch=True,\n", + " convert_to_iter_based=True)\n", + "]\n", + "optim_wrapper = dict(\n", + " type='OptimWrapper',\n", + " optimizer=dict(type='AdamW', lr=8e-05, weight_decay=0.05),\n", + " paramwise_cfg=dict(\n", + " norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True))\n", + "auto_scale_lr = dict(enable=False, base_batch_size=16)\n", + "dataset_type = 'CocoDataset'\n", + "data_root = 'data/balloon/'\n", + "backend_args = None\n", + "train_pipeline = [\n", + " dict(type='LoadImageFromFile', backend_args=None),\n", + " dict(type='LoadAnnotations', with_bbox=True),\n", + " dict(\n", + " type='CachedMosaic',\n", + " img_scale=(640, 640),\n", + " pad_val=114.0,\n", + " max_cached_images=20,\n", + " random_pop=False),\n", + " dict(\n", + " type='RandomResize',\n", + " scale=(1280, 1280),\n", + " ratio_range=(0.5, 2.0),\n", + " keep_ratio=True),\n", + " dict(type='RandomCrop', crop_size=(640, 640)),\n", + " dict(type='YOLOXHSVRandomAug'),\n", + " dict(type='RandomFlip', prob=0.5),\n", + " dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),\n", + " dict(\n", + " type='CachedMixUp',\n", + " img_scale=(640, 640),\n", + " ratio_range=(1.0, 1.0),\n", + " max_cached_images=10,\n", + " random_pop=False,\n", + " pad_val=(114, 114, 114),\n", + " prob=0.5),\n", + " dict(type='PackDetInputs')\n", + "]\n", + "test_pipeline = [\n", + " dict(type='LoadImageFromFile', backend_args=None),\n", + " dict(type='Resize', scale=(640, 640), keep_ratio=True),\n", + " dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),\n", + " dict(\n", + " type='PackDetInputs',\n", + " meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',\n", + " 'scale_factor'))\n", + "]\n", + "train_dataloader = dict(\n", + " batch_size=4,\n", + " num_workers=2,\n", + " persistent_workers=True,\n", + " sampler=dict(type='DefaultSampler', shuffle=True),\n", + " batch_sampler=None,\n", + " dataset=dict(\n", + " type='CocoDataset',\n", + " data_root='data/balloon/',\n", + " ann_file='train.json',\n", + " data_prefix=dict(img='train/'),\n", + " filter_cfg=dict(filter_empty_gt=True, min_size=32),\n", + " pipeline=[\n", + " dict(type='LoadImageFromFile', backend_args=None),\n", + " dict(type='LoadAnnotations', with_bbox=True),\n", + " dict(\n", + " type='CachedMosaic',\n", + " img_scale=(640, 640),\n", + " pad_val=114.0,\n", + " max_cached_images=20,\n", + " random_pop=False),\n", + " dict(\n", + " type='RandomResize',\n", + " scale=(1280, 1280),\n", + " ratio_range=(0.5, 2.0),\n", + " keep_ratio=True),\n", + " dict(type='RandomCrop', crop_size=(640, 640)),\n", + " dict(type='YOLOXHSVRandomAug'),\n", + " dict(type='RandomFlip', prob=0.5),\n", + " dict(\n", + " type='Pad', size=(640, 640),\n", + " pad_val=dict(img=(114, 114, 114))),\n", + " dict(\n", + " type='CachedMixUp',\n", + " img_scale=(640, 640),\n", + " ratio_range=(1.0, 1.0),\n", + " max_cached_images=10,\n", + " random_pop=False,\n", + " pad_val=(114, 114, 114),\n", + " prob=0.5),\n", + " dict(type='PackDetInputs')\n", + " ],\n", + " backend_args=None,\n", + " metainfo=dict(classes=('balloon', ), palette=[(220, 20, 60)])),\n", + " pin_memory=True)\n", + "val_dataloader = dict(\n", + " batch_size=5,\n", + " num_workers=10,\n", + " persistent_workers=True,\n", + " drop_last=False,\n", + " sampler=dict(type='DefaultSampler', shuffle=False),\n", + " dataset=dict(\n", + " type='CocoDataset',\n", + " data_root='data/balloon/',\n", + " ann_file='val.json',\n", + " data_prefix=dict(img='val/'),\n", + " test_mode=True,\n", + " pipeline=[\n", + " dict(type='LoadImageFromFile', backend_args=None),\n", + " dict(type='Resize', scale=(640, 640), keep_ratio=True),\n", + " dict(\n", + " type='Pad', size=(640, 640),\n", + " pad_val=dict(img=(114, 114, 114))),\n", + " dict(\n", + " type='PackDetInputs',\n", + " meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',\n", + " 'scale_factor'))\n", + " ],\n", + " backend_args=None,\n", + " metainfo=dict(classes=('balloon', ), palette=[(220, 20, 60)])))\n", + "test_dataloader = dict(\n", + " batch_size=5,\n", + " num_workers=10,\n", + " persistent_workers=True,\n", + " drop_last=False,\n", + " sampler=dict(type='DefaultSampler', shuffle=False),\n", + " dataset=dict(\n", + " type='CocoDataset',\n", + " data_root='data/balloon/',\n", + " ann_file='val.json',\n", + " data_prefix=dict(img='val/'),\n", + " test_mode=True,\n", + " pipeline=[\n", + " dict(type='LoadImageFromFile', backend_args=None),\n", + " dict(type='Resize', scale=(640, 640), keep_ratio=True),\n", + " dict(\n", + " type='Pad', size=(640, 640),\n", + " pad_val=dict(img=(114, 114, 114))),\n", + " dict(\n", + " type='PackDetInputs',\n", + " meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',\n", + " 'scale_factor'))\n", + " ],\n", + " backend_args=None,\n", + " metainfo=dict(classes=('balloon', ), palette=[(220, 20, 60)])))\n", + "val_evaluator = dict(\n", + " type='CocoMetric',\n", + " ann_file='data/balloon/val.json',\n", + " metric='bbox',\n", + " format_only=False,\n", + " backend_args=None,\n", + " proposal_nums=(100, 1, 10))\n", + "test_evaluator = dict(\n", + " type='CocoMetric',\n", + " ann_file='data/balloon/val.json',\n", + " metric='bbox',\n", + " format_only=False,\n", + " backend_args=None,\n", + " proposal_nums=(100, 1, 10))\n", + "tta_model = dict(\n", + " type='DetTTAModel',\n", + " tta_cfg=dict(nms=dict(type='nms', iou_threshold=0.6), max_per_img=100))\n", + "img_scales = [(640, 640), (320, 320), (960, 960)]\n", + "tta_pipeline = [\n", + " dict(type='LoadImageFromFile', backend_args=None),\n", + " dict(\n", + " type='TestTimeAug',\n", + " transforms=[[{\n", + " 'type': 'Resize',\n", + " 'scale': (640, 640),\n", + " 'keep_ratio': True\n", + " }, {\n", + " 'type': 'Resize',\n", + " 'scale': (320, 320),\n", + " 'keep_ratio': True\n", + " }, {\n", + " 'type': 'Resize',\n", + " 'scale': (960, 960),\n", + " 'keep_ratio': True\n", + " }],\n", + " [{\n", + " 'type': 'RandomFlip',\n", + " 'prob': 1.0\n", + " }, {\n", + " 'type': 'RandomFlip',\n", + " 'prob': 0.0\n", + " }],\n", + " [{\n", + " 'type': 'Pad',\n", + " 'size': (960, 960),\n", + " 'pad_val': {\n", + " 'img': (114, 114, 114)\n", + " }\n", + " }],\n", + " [{\n", + " 'type':\n", + " 'PackDetInputs',\n", + " 'meta_keys':\n", + " ('img_id', 'img_path', 'ori_shape', 'img_shape',\n", + " 'scale_factor', 'flip', 'flip_direction')\n", + " }]])\n", + "]\n", + "model = dict(\n", + " type='RTMDet',\n", + " data_preprocessor=dict(\n", + " type='DetDataPreprocessor',\n", + " mean=[103.53, 116.28, 123.675],\n", + " std=[57.375, 57.12, 58.395],\n", + " bgr_to_rgb=False,\n", + " batch_augments=None),\n", + " backbone=dict(\n", + " type='CSPNeXt',\n", + " arch='P5',\n", + " expand_ratio=0.5,\n", + " deepen_factor=0.167,\n", + " widen_factor=0.375,\n", + " channel_attention=True,\n", + " norm_cfg=dict(type='SyncBN'),\n", + " act_cfg=dict(type='SiLU', inplace=True),\n", + " init_cfg=dict(\n", + " type='Pretrained',\n", + " prefix='backbone.',\n", + " checkpoint=\n", + " 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e.pth'\n", + " )),\n", + " neck=dict(\n", + " type='CSPNeXtPAFPN',\n", + " in_channels=[96, 192, 384],\n", + " out_channels=96,\n", + " num_csp_blocks=1,\n", + " expand_ratio=0.5,\n", + " norm_cfg=dict(type='SyncBN'),\n", + " act_cfg=dict(type='SiLU', inplace=True)),\n", + " bbox_head=dict(\n", + " type='RTMDetSepBNHead',\n", + " num_classes=1,\n", + " in_channels=96,\n", + " stacked_convs=2,\n", + " feat_channels=96,\n", + " anchor_generator=dict(\n", + " type='MlvlPointGenerator', offset=0, strides=[8, 16, 32]),\n", + " bbox_coder=dict(type='DistancePointBBoxCoder'),\n", + " loss_cls=dict(\n", + " type='QualityFocalLoss',\n", + " use_sigmoid=True,\n", + " beta=2.0,\n", + " loss_weight=1.0),\n", + " loss_bbox=dict(type='GIoULoss', loss_weight=2.0),\n", + " with_objectness=False,\n", + " exp_on_reg=False,\n", + " share_conv=True,\n", + " pred_kernel_size=1,\n", + " norm_cfg=dict(type='SyncBN'),\n", + " act_cfg=dict(type='SiLU', inplace=True)),\n", + " train_cfg=dict(\n", + " assigner=dict(type='DynamicSoftLabelAssigner', topk=13),\n", + " allowed_border=-1,\n", + " pos_weight=-1,\n", + " debug=False),\n", + " test_cfg=dict(\n", + " nms_pre=30000,\n", + " min_bbox_size=0,\n", + " score_thr=0.001,\n", + " nms=dict(type='nms', iou_threshold=0.65),\n", + " max_per_img=300))\n", + "train_pipeline_stage2 = [\n", + " dict(type='LoadImageFromFile', backend_args=None),\n", + " dict(type='LoadAnnotations', with_bbox=True),\n", + " dict(\n", + " type='RandomResize',\n", + " scale=(640, 640),\n", + " ratio_range=(0.1, 2.0),\n", + " keep_ratio=True),\n", + " dict(type='RandomCrop', crop_size=(640, 640)),\n", + " dict(type='YOLOXHSVRandomAug'),\n", + " dict(type='RandomFlip', prob=0.5),\n", + " dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),\n", + " dict(type='PackDetInputs')\n", + "]\n", + "max_epochs = 20\n", + "stage2_num_epochs = 1\n", + "base_lr = 8e-05\n", + "interval = 10\n", + "custom_hooks = [\n", + " dict(\n", + " type='PipelineSwitchHook',\n", + " switch_epoch=19,\n", + " switch_pipeline=[\n", + " dict(type='LoadImageFromFile', backend_args=None),\n", + " dict(type='LoadAnnotations', with_bbox=True),\n", + " dict(\n", + " type='RandomResize',\n", + " scale=(640, 640),\n", + " ratio_range=(0.1, 2.0),\n", + " keep_ratio=True),\n", + " dict(type='RandomCrop', crop_size=(640, 640)),\n", + " dict(type='YOLOXHSVRandomAug'),\n", + " dict(type='RandomFlip', prob=0.5),\n", + " dict(\n", + " type='Pad', size=(640, 640),\n", + " pad_val=dict(img=(114, 114, 114))),\n", + " dict(type='PackDetInputs')\n", + " ])\n", + "]\n", + "checkpoint = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e.pth'\n", + "train_batch_size_per_gpu = 4\n", + "train_num_workers = 2\n", + "metainfo = dict(classes=('balloon', ), palette=[(220, 20, 60)])\n", + "launcher = 'none'\n", + "work_dir = './work_dirs/rtmdet_tiny_1xb4-20e_balloon'\n", + "\n", + "2023-04-17 10:28:39.429834: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n", + "To enable the following instructions: AVX2 AVX512F FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n", + "2023-04-17 10:28:40.271799: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT\n", + "04/17 10:28:43 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Distributed training is not used, all SyncBatchNorm (SyncBN) layers in the model will be automatically reverted to BatchNormXd layers if they are used.\n", + "04/17 10:28:43 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Hooks will be executed in the following order:\n", + "before_run:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "before_train:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "before_train_epoch:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(NORMAL ) DistSamplerSeedHook \n", + "(NORMAL ) PipelineSwitchHook \n", + " -------------------- \n", + "before_train_iter:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "after_train_iter:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(BELOW_NORMAL) LoggerHook \n", + "(LOW ) ParamSchedulerHook \n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "after_train_epoch:\n", + "(NORMAL ) IterTimerHook \n", + "(LOW ) ParamSchedulerHook \n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "before_val_epoch:\n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "before_val_iter:\n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "after_val_iter:\n", + "(NORMAL ) IterTimerHook \n", + "(NORMAL ) DetVisualizationHook \n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "after_val_epoch:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(BELOW_NORMAL) LoggerHook \n", + "(LOW ) ParamSchedulerHook \n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "after_train:\n", + "(VERY_LOW ) CheckpointHook \n", + " -------------------- \n", + "before_test_epoch:\n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "before_test_iter:\n", + "(NORMAL ) IterTimerHook \n", + " -------------------- \n", + "after_test_iter:\n", + "(NORMAL ) IterTimerHook \n", + "(NORMAL ) DetVisualizationHook \n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "after_test_epoch:\n", + "(VERY_HIGH ) RuntimeInfoHook \n", + "(NORMAL ) IterTimerHook \n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "after_run:\n", + "(BELOW_NORMAL) LoggerHook \n", + " -------------------- \n", + "loading annotations into memory...\n", + "Done (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stem.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stem.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stem.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stem.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stem.2.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stem.2.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.main_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.main_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.short_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.short_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.final_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.final_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.blocks.0.conv1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.blocks.0.conv1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.blocks.0.conv2.depthwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.blocks.0.conv2.depthwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.blocks.0.conv2.pointwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.blocks.0.conv2.pointwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage1.1.attention.fc.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.main_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.main_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.short_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.short_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.final_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.final_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.blocks.0.conv1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.blocks.0.conv1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.blocks.0.conv2.depthwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.blocks.0.conv2.depthwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.blocks.0.conv2.pointwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.blocks.0.conv2.pointwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage2.1.attention.fc.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.main_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.main_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.short_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.short_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.final_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.final_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.blocks.0.conv1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.blocks.0.conv1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.blocks.0.conv2.depthwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.blocks.0.conv2.depthwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.blocks.0.conv2.pointwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.blocks.0.conv2.pointwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage3.1.attention.fc.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.1.conv1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.1.conv1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.1.conv2.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.1.conv2.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.main_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.main_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.short_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.short_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.final_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.final_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.blocks.0.conv1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.blocks.0.conv1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.blocks.0.conv2.depthwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.blocks.0.conv2.depthwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.blocks.0.conv2.pointwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.blocks.0.conv2.pointwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- backbone.stage4.2.attention.fc.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.reduce_layers.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.reduce_layers.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.reduce_layers.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.reduce_layers.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.main_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.main_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.short_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.short_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.final_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.final_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.blocks.0.conv1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.blocks.0.conv1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.blocks.0.conv2.depthwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.blocks.0.conv2.depthwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.blocks.0.conv2.pointwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.0.blocks.0.conv2.pointwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.main_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.main_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.short_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.short_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.final_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.final_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.blocks.0.conv1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.blocks.0.conv1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.blocks.0.conv2.depthwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.blocks.0.conv2.depthwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.blocks.0.conv2.pointwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.top_down_blocks.1.blocks.0.conv2.pointwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.downsamples.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.downsamples.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.downsamples.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.downsamples.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.main_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.main_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.short_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.short_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.final_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.final_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.blocks.0.conv1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.blocks.0.conv1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.blocks.0.conv2.depthwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.blocks.0.conv2.depthwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.blocks.0.conv2.pointwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.0.blocks.0.conv2.pointwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.main_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.main_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.short_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.short_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.final_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.final_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.blocks.0.conv1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.blocks.0.conv1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.blocks.0.conv2.depthwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.blocks.0.conv2.depthwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.blocks.0.conv2.pointwise_conv.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.bottom_up_blocks.1.blocks.0.conv2.pointwise_conv.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.out_convs.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.out_convs.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.out_convs.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.out_convs.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.out_convs.2.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- neck.out_convs.2.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.0.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.0.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.0.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.0.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - bbox_head.cls_convs.1.0.conv is duplicate. It is skipped since bypass_duplicate=True\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.1.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.1.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - bbox_head.cls_convs.1.1.conv is duplicate. It is skipped since bypass_duplicate=True\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.1.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.1.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - bbox_head.cls_convs.2.0.conv is duplicate. It is skipped since bypass_duplicate=True\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.2.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.2.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - bbox_head.cls_convs.2.1.conv is duplicate. It is skipped since bypass_duplicate=True\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.2.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.cls_convs.2.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.0.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.0.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.0.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.0.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - bbox_head.reg_convs.1.0.conv is duplicate. It is skipped since bypass_duplicate=True\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.1.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.1.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - bbox_head.reg_convs.1.1.conv is duplicate. It is skipped since bypass_duplicate=True\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.1.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.1.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - bbox_head.reg_convs.2.0.conv is duplicate. It is skipped since bypass_duplicate=True\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.2.0.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.2.0.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - bbox_head.reg_convs.2.1.conv is duplicate. It is skipped since bypass_duplicate=True\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.2.1.bn.weight:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.reg_convs.2.1.bn.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.rtm_cls.0.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.rtm_cls.1.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.rtm_cls.2.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.rtm_reg.0.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.rtm_reg.1.bias:weight_decay=0.0\n", + "04/17 10:28:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - paramwise_options -- bbox_head.rtm_reg.2.bias:weight_decay=0.0\n", + "loading annotations into memory...\n", + "Done (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "/usr/local/lib/python3.9/dist-packages/torch/utils/data/dataloader.py:561: UserWarning: This DataLoader will create 10 worker processes in total. Our suggested max number of worker in current system is 2, which is smaller than what this DataLoader is going to create. Please be aware that excessive worker creation might get DataLoader running slow or even freeze, lower the worker number to avoid potential slowness/freeze if necessary.\n", + " warnings.warn(_create_warning_msg(\n", + "loading annotations into memory...\n", + "Done (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "04/17 10:28:46 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - load backbone. in model from: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e.pth\n", + "Loads checkpoint by http backend from path: https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e.pth\n", + "Downloading: \"https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e.pth\" to /root/.cache/torch/hub/checkpoints/cspnext-tiny_imagenet_600e.pth\n", + "100% 31.5M/31.5M [00:01<00:00, 27.2MB/s]\n", + "Loads checkpoint by local backend from path: ./checkpoints/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth\n", + "The model and loaded state dict do not match exactly\n", + "\n", + "size mismatch for bbox_head.rtm_cls.0.weight: copying a param with shape torch.Size([80, 96, 1, 1]) from checkpoint, the shape in current model is torch.Size([1, 96, 1, 1]).\n", + "size mismatch for bbox_head.rtm_cls.0.bias: copying a param with shape torch.Size([80]) from checkpoint, the shape in current model is torch.Size([1]).\n", + "size mismatch for bbox_head.rtm_cls.1.weight: copying a param with shape torch.Size([80, 96, 1, 1]) from checkpoint, the shape in current model is torch.Size([1, 96, 1, 1]).\n", + "size mismatch for bbox_head.rtm_cls.1.bias: copying a param with shape torch.Size([80]) from checkpoint, the shape in current model is torch.Size([1]).\n", + "size mismatch for bbox_head.rtm_cls.2.weight: copying a param with shape torch.Size([80, 96, 1, 1]) from checkpoint, the shape in current model is torch.Size([1, 96, 1, 1]).\n", + "size mismatch for bbox_head.rtm_cls.2.bias: copying a param with shape torch.Size([80]) from checkpoint, the shape in current model is torch.Size([1]).\n", + "unexpected key in source state_dict: data_preprocessor.mean, data_preprocessor.std\n", + "\n", + "04/17 10:28:48 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Load checkpoint from ./checkpoints/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth\n", + "04/17 10:28:48 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - \"FileClient\" will be deprecated in future. Please use io functions in https://mmengine.readthedocs.io/en/latest/api/fileio.html#file-io\n", + "04/17 10:28:48 - mmengine - \u001b[5m\u001b[4m\u001b[33mWARNING\u001b[0m - \"HardDiskBackend\" is the alias of \"LocalBackend\" and the former will be deprecated in future.\n", + "04/17 10:28:48 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Checkpoints will be saved to /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon.\n", + "/usr/local/lib/python3.9/dist-packages/torch/functional.py:504: UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument. (Triggered internally at ../aten/src/ATen/native/TensorShape.cpp:3483.)\n", + " return _VF.meshgrid(tensors, **kwargs) # type: ignore[attr-defined]\n", + "04/17 10:28:51 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [1][ 5/16] lr: 3.5556e-05 eta: 0:03:33 time: 0.6778 data_time: 0.2107 memory: 1422 loss: 2.7197 loss_cls: 2.0437 loss_bbox: 0.6761\n", + "04/17 10:28:52 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [1][10/16] lr: 8.0000e-05 eta: 0:02:31 time: 0.4890 data_time: 0.1117 memory: 1422 loss: 2.7312 loss_cls: 2.0638 loss_bbox: 0.6673\n", + "04/17 10:28:54 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [1][15/16] lr: 8.0000e-05 eta: 0:02:05 time: 0.4111 data_time: 0.0774 memory: 1422 loss: 2.7183 loss_cls: 2.0497 loss_bbox: 0.6687\n", + "04/17 10:28:54 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "/usr/local/lib/python3.9/dist-packages/torch/utils/data/dataloader.py:561: UserWarning: This DataLoader will create 10 worker processes in total. Our suggested max number of worker in current system is 2, which is smaller than what this DataLoader is going to create. Please be aware that excessive worker creation might get DataLoader running slow or even freeze, lower the worker number to avoid potential slowness/freeze if necessary.\n", + " warnings.warn(_create_warning_msg(\n", + "04/17 10:28:55 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.01s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.13s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.044\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.059\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.051\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.076\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.054\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.190\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.488\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.208\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.608\n", + "04/17 10:28:55 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.044 0.059 0.051 0.000 0.076 0.054\n", + "04/17 10:28:55 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [1][3/3] coco/bbox_mAP: 0.0440 coco/bbox_mAP_50: 0.0590 coco/bbox_mAP_75: 0.0510 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.0760 coco/bbox_mAP_l: 0.0540 data_time: 0.1730 time: 0.2618\n", + "04/17 10:28:56 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.0440 coco/bbox_mAP at 1 epoch is saved to best_coco_bbox_mAP_epoch_1.pth.\n", + "04/17 10:28:59 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [2][ 5/16] lr: 8.0000e-05 eta: 0:02:09 time: 0.4335 data_time: 0.0945 memory: 1422 loss: 2.7141 loss_cls: 2.0473 loss_bbox: 0.6668\n", + "04/17 10:29:02 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [2][10/16] lr: 8.0000e-05 eta: 0:02:20 time: 0.4791 data_time: 0.1165 memory: 1422 loss: 2.7160 loss_cls: 2.0560 loss_bbox: 0.6600\n", + "04/17 10:29:04 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [2][15/16] lr: 8.0000e-05 eta: 0:02:13 time: 0.4614 data_time: 0.0997 memory: 1422 loss: 2.7240 loss_cls: 2.0732 loss_bbox: 0.6507\n", + "04/17 10:29:04 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:29:05 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.12s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.128\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.165\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.147\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.081\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.164\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.056\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.320\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.570\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.325\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.683\n", + "04/17 10:29:05 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.128 0.165 0.147 0.000 0.081 0.164\n", + "04/17 10:29:05 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [2][3/3] coco/bbox_mAP: 0.1280 coco/bbox_mAP_50: 0.1650 coco/bbox_mAP_75: 0.1470 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.0810 coco/bbox_mAP_l: 0.1640 data_time: 0.1576 time: 0.2254\n", + "04/17 10:29:05 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_1.pth is removed\n", + "04/17 10:29:06 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.1280 coco/bbox_mAP at 2 epoch is saved to best_coco_bbox_mAP_epoch_2.pth.\n", + "04/17 10:29:08 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [3][ 5/16] lr: 8.0000e-05 eta: 0:02:06 time: 0.4462 data_time: 0.1025 memory: 1422 loss: 2.7154 loss_cls: 2.0725 loss_bbox: 0.6429\n", + "04/17 10:29:10 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [3][10/16] lr: 8.0000e-05 eta: 0:02:01 time: 0.4378 data_time: 0.1003 memory: 1422 loss: 2.7268 loss_cls: 2.0980 loss_bbox: 0.6288\n", + "04/17 10:29:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [3][15/16] lr: 8.0000e-05 eta: 0:01:54 time: 0.4192 data_time: 0.0922 memory: 1422 loss: 2.7336 loss_cls: 2.1177 loss_bbox: 0.6159\n", + "04/17 10:29:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:29:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.13s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.308\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.381\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.356\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.098\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.409\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.146\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.370\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.646\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.292\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.800\n", + "04/17 10:29:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.308 0.381 0.356 0.000 0.098 0.409\n", + "04/17 10:29:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [3][3/3] coco/bbox_mAP: 0.3080 coco/bbox_mAP_50: 0.3810 coco/bbox_mAP_75: 0.3560 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.0980 coco/bbox_mAP_l: 0.4090 data_time: 0.1456 time: 0.2063\n", + "04/17 10:29:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_2.pth is removed\n", + "04/17 10:29:14 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.3080 coco/bbox_mAP at 3 epoch is saved to best_coco_bbox_mAP_epoch_3.pth.\n", + "04/17 10:29:17 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [4][ 5/16] lr: 8.0000e-05 eta: 0:01:54 time: 0.3957 data_time: 0.0830 memory: 1422 loss: 2.7228 loss_cls: 2.1246 loss_bbox: 0.5982\n", + "04/17 10:29:19 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [4][10/16] lr: 8.0000e-05 eta: 0:01:54 time: 0.4205 data_time: 0.0934 memory: 1422 loss: 2.7104 loss_cls: 2.1374 loss_bbox: 0.5730\n", + "04/17 10:29:21 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [4][15/16] lr: 8.0000e-05 eta: 0:01:51 time: 0.4289 data_time: 0.1029 memory: 1422 loss: 2.6786 loss_cls: 2.1263 loss_bbox: 0.5524\n", + "04/17 10:29:21 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:29:22 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.12s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.406\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.531\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.449\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.116\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.525\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.166\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.496\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.694\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.533\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.786\n", + "04/17 10:29:22 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.406 0.531 0.449 0.000 0.116 0.525\n", + "04/17 10:29:22 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [4][3/3] coco/bbox_mAP: 0.4060 coco/bbox_mAP_50: 0.5310 coco/bbox_mAP_75: 0.4490 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.1160 coco/bbox_mAP_l: 0.5250 data_time: 0.1390 time: 0.1964\n", + "04/17 10:29:22 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_3.pth is removed\n", + "04/17 10:29:23 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.4060 coco/bbox_mAP at 4 epoch is saved to best_coco_bbox_mAP_epoch_4.pth.\n", + "04/17 10:29:26 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [5][ 5/16] lr: 8.0000e-05 eta: 0:01:46 time: 0.4261 data_time: 0.1005 memory: 1422 loss: 2.6337 loss_cls: 2.1077 loss_bbox: 0.5260\n", + "04/17 10:29:27 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [5][10/16] lr: 8.0000e-05 eta: 0:01:43 time: 0.4038 data_time: 0.0969 memory: 1422 loss: 2.5711 loss_cls: 2.0661 loss_bbox: 0.5050\n", + "04/17 10:29:29 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [5][15/16] lr: 8.0000e-05 eta: 0:01:39 time: 0.3730 data_time: 0.0887 memory: 1422 loss: 2.4938 loss_cls: 2.0094 loss_bbox: 0.4844\n", + "04/17 10:29:29 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:29:29 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Saving checkpoint at 5 epochs\n", + "04/17 10:29:31 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.01s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.25s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.03s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.426\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.547\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.476\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.110\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.556\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.170\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.508\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.678\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.417\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.803\n", + "04/17 10:29:31 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.426 0.547 0.476 0.000 0.110 0.556\n", + "04/17 10:29:31 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [5][3/3] coco/bbox_mAP: 0.4260 coco/bbox_mAP_50: 0.5470 coco/bbox_mAP_75: 0.4760 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.1100 coco/bbox_mAP_l: 0.5560 data_time: 0.1361 time: 0.1959\n", + "04/17 10:29:31 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_4.pth is removed\n", + "04/17 10:29:33 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.4260 coco/bbox_mAP at 5 epoch is saved to best_coco_bbox_mAP_epoch_5.pth.\n", + "04/17 10:29:37 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [6][ 5/16] lr: 8.0000e-05 eta: 0:01:40 time: 0.4070 data_time: 0.1114 memory: 1422 loss: 2.4067 loss_cls: 1.9395 loss_bbox: 0.4673\n", + "04/17 10:29:39 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [6][10/16] lr: 8.0000e-05 eta: 0:01:37 time: 0.4020 data_time: 0.1043 memory: 1422 loss: 2.3103 loss_cls: 1.8537 loss_bbox: 0.4566\n", + "04/17 10:29:40 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [6][15/16] lr: 8.0000e-05 eta: 0:01:33 time: 0.4037 data_time: 0.1089 memory: 1422 loss: 2.2055 loss_cls: 1.7604 loss_bbox: 0.4451\n", + "04/17 10:29:40 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:29:41 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.12s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.455\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.579\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.492\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.173\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.584\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.160\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.582\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.702\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.517\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.803\n", + "04/17 10:29:41 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.455 0.579 0.492 0.000 0.173 0.584\n", + "04/17 10:29:41 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [6][3/3] coco/bbox_mAP: 0.4550 coco/bbox_mAP_50: 0.5790 coco/bbox_mAP_75: 0.4920 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.1730 coco/bbox_mAP_l: 0.5840 data_time: 0.1338 time: 0.1915\n", + "04/17 10:29:41 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_5.pth is removed\n", + "04/17 10:29:42 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.4550 coco/bbox_mAP at 6 epoch is saved to best_coco_bbox_mAP_epoch_6.pth.\n", + "04/17 10:29:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [7][ 5/16] lr: 8.0000e-05 eta: 0:01:30 time: 0.4004 data_time: 0.1114 memory: 1422 loss: 2.0828 loss_cls: 1.6540 loss_bbox: 0.4288\n", + "04/17 10:29:46 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [7][10/16] lr: 8.0000e-05 eta: 0:01:27 time: 0.3826 data_time: 0.1077 memory: 1422 loss: 1.9577 loss_cls: 1.5354 loss_bbox: 0.4222\n", + "04/17 10:29:48 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [7][15/16] lr: 8.0000e-05 eta: 0:01:24 time: 0.3629 data_time: 0.0973 memory: 1422 loss: 1.8775 loss_cls: 1.4601 loss_bbox: 0.4174\n", + "04/17 10:29:48 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:29:49 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.01s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.25s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.03s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.494\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.658\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.558\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.186\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.613\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.174\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.578\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.726\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.575\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.817\n", + "04/17 10:29:49 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.494 0.658 0.558 0.000 0.186 0.613\n", + "04/17 10:29:49 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [7][3/3] coco/bbox_mAP: 0.4940 coco/bbox_mAP_50: 0.6580 coco/bbox_mAP_75: 0.5580 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.1860 coco/bbox_mAP_l: 0.6130 data_time: 0.1454 time: 0.2026\n", + "04/17 10:29:49 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_6.pth is removed\n", + "04/17 10:29:51 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.4940 coco/bbox_mAP at 7 epoch is saved to best_coco_bbox_mAP_epoch_7.pth.\n", + "04/17 10:29:54 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [8][ 5/16] lr: 8.0000e-05 eta: 0:01:22 time: 0.3740 data_time: 0.1034 memory: 1422 loss: 1.7607 loss_cls: 1.3465 loss_bbox: 0.4142\n", + "04/17 10:29:55 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [8][10/16] lr: 8.0000e-05 eta: 0:01:19 time: 0.3734 data_time: 0.1042 memory: 1422 loss: 1.6590 loss_cls: 1.2527 loss_bbox: 0.4064\n", + "04/17 10:29:56 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [8][15/16] lr: 8.0000e-05 eta: 0:01:16 time: 0.3658 data_time: 0.0988 memory: 1422 loss: 1.5863 loss_cls: 1.1838 loss_bbox: 0.4025\n", + "04/17 10:29:56 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:29:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.11s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.542\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.693\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.620\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.231\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.666\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.190\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.618\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.740\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.625\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.819\n", + "04/17 10:29:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.542 0.693 0.620 0.000 0.231 0.666\n", + "04/17 10:29:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [8][3/3] coco/bbox_mAP: 0.5420 coco/bbox_mAP_50: 0.6930 coco/bbox_mAP_75: 0.6200 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2310 coco/bbox_mAP_l: 0.6660 data_time: 0.1422 time: 0.1982\n", + "04/17 10:29:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_7.pth is removed\n", + "04/17 10:29:59 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.5420 coco/bbox_mAP at 8 epoch is saved to best_coco_bbox_mAP_epoch_8.pth.\n", + "04/17 10:30:01 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [9][ 5/16] lr: 8.0000e-05 eta: 0:01:14 time: 0.3413 data_time: 0.0825 memory: 1422 loss: 1.4943 loss_cls: 1.1003 loss_bbox: 0.3940\n", + "04/17 10:30:03 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [9][10/16] lr: 8.0000e-05 eta: 0:01:12 time: 0.3545 data_time: 0.0888 memory: 1422 loss: 1.4314 loss_cls: 1.0468 loss_bbox: 0.3846\n", + "04/17 10:30:05 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [9][15/16] lr: 8.0000e-05 eta: 0:01:10 time: 0.3613 data_time: 0.0938 memory: 1422 loss: 1.3590 loss_cls: 0.9855 loss_bbox: 0.3735\n", + "04/17 10:30:05 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:30:06 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.01s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.24s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.03s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.611\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.725\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.689\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.227\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.751\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.214\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.696\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.774\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.633\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.864\n", + "04/17 10:30:07 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.611 0.725 0.689 0.000 0.227 0.751\n", + "04/17 10:30:07 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [9][3/3] coco/bbox_mAP: 0.6110 coco/bbox_mAP_50: 0.7250 coco/bbox_mAP_75: 0.6890 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2270 coco/bbox_mAP_l: 0.7510 data_time: 0.1471 time: 0.2029\n", + "04/17 10:30:07 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_8.pth is removed\n", + "04/17 10:30:08 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.6110 coco/bbox_mAP at 9 epoch is saved to best_coco_bbox_mAP_epoch_9.pth.\n", + "04/17 10:30:10 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [10][ 5/16] lr: 8.0000e-05 eta: 0:01:08 time: 0.3742 data_time: 0.0985 memory: 1422 loss: 1.2606 loss_cls: 0.8955 loss_bbox: 0.3651\n", + "04/17 10:30:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [10][10/16] lr: 8.0000e-05 eta: 0:01:05 time: 0.3687 data_time: 0.0901 memory: 1422 loss: 1.2161 loss_cls: 0.8550 loss_bbox: 0.3611\n", + "04/17 10:30:13 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [10][15/16] lr: 8.0000e-05 eta: 0:01:03 time: 0.3582 data_time: 0.0862 memory: 1422 loss: 1.1586 loss_cls: 0.8081 loss_bbox: 0.3505\n", + "04/17 10:30:13 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:30:13 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Saving checkpoint at 10 epochs\n", + "04/17 10:30:15 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.12s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.623\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.741\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.698\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.273\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.761\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.226\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.670\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.768\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.642\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.853\n", + "04/17 10:30:16 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.623 0.741 0.698 0.000 0.273 0.761\n", + "04/17 10:30:16 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [10][3/3] coco/bbox_mAP: 0.6230 coco/bbox_mAP_50: 0.7410 coco/bbox_mAP_75: 0.6980 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2730 coco/bbox_mAP_l: 0.7610 data_time: 0.1447 time: 0.2024\n", + "04/17 10:30:16 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_9.pth is removed\n", + "04/17 10:30:17 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.6230 coco/bbox_mAP at 10 epoch is saved to best_coco_bbox_mAP_epoch_10.pth.\n", + "04/17 10:30:20 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [11][ 5/16] lr: 7.9883e-05 eta: 0:01:01 time: 0.3772 data_time: 0.0894 memory: 1422 loss: 1.0756 loss_cls: 0.7460 loss_bbox: 0.3297\n", + "04/17 10:30:22 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [11][10/16] lr: 7.9408e-05 eta: 0:01:00 time: 0.3923 data_time: 0.0905 memory: 1422 loss: 1.0549 loss_cls: 0.7295 loss_bbox: 0.3254\n", + "04/17 10:30:24 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [11][15/16] lr: 7.8573e-05 eta: 0:00:58 time: 0.4015 data_time: 0.1017 memory: 1422 loss: 1.0355 loss_cls: 0.7114 loss_bbox: 0.3241\n", + "04/17 10:30:24 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:30:25 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.11s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.645\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.772\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.709\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.231\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.778\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.226\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.686\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.782\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.692\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.856\n", + "04/17 10:30:25 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.645 0.772 0.709 0.000 0.231 0.778\n", + "04/17 10:30:25 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [11][3/3] coco/bbox_mAP: 0.6450 coco/bbox_mAP_50: 0.7720 coco/bbox_mAP_75: 0.7090 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2310 coco/bbox_mAP_l: 0.7780 data_time: 0.1423 time: 0.1990\n", + "04/17 10:30:25 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_10.pth is removed\n", + "04/17 10:30:26 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.6450 coco/bbox_mAP at 11 epoch is saved to best_coco_bbox_mAP_epoch_11.pth.\n", + "04/17 10:30:29 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [12][ 5/16] lr: 7.7107e-05 eta: 0:00:55 time: 0.4071 data_time: 0.1000 memory: 1422 loss: 0.9773 loss_cls: 0.6681 loss_bbox: 0.3092\n", + "04/17 10:30:30 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [12][10/16] lr: 7.5513e-05 eta: 0:00:53 time: 0.4009 data_time: 0.0986 memory: 1422 loss: 0.9503 loss_cls: 0.6470 loss_bbox: 0.3033\n", + "04/17 10:30:32 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [12][15/16] lr: 7.3596e-05 eta: 0:00:50 time: 0.3808 data_time: 0.0966 memory: 1422 loss: 0.9250 loss_cls: 0.6268 loss_bbox: 0.2982\n", + "04/17 10:30:32 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:30:32 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.12s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.617\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.751\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.696\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.218\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.750\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.210\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.662\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.760\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.633\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.844\n", + "04/17 10:30:33 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.617 0.751 0.696 0.000 0.218 0.750\n", + "04/17 10:30:33 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [12][3/3] coco/bbox_mAP: 0.6170 coco/bbox_mAP_50: 0.7510 coco/bbox_mAP_75: 0.6960 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2180 coco/bbox_mAP_l: 0.7500 data_time: 0.1405 time: 0.1964\n", + "04/17 10:30:36 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [13][ 5/16] lr: 7.0895e-05 eta: 0:00:49 time: 0.4001 data_time: 0.1021 memory: 1422 loss: 0.9386 loss_cls: 0.6415 loss_bbox: 0.2971\n", + "04/17 10:30:39 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [13][10/16] lr: 6.8337e-05 eta: 0:00:47 time: 0.4275 data_time: 0.1168 memory: 1422 loss: 0.9174 loss_cls: 0.6254 loss_bbox: 0.2920\n", + "04/17 10:30:41 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [13][15/16] lr: 6.5526e-05 eta: 0:00:45 time: 0.4284 data_time: 0.1216 memory: 1422 loss: 0.9057 loss_cls: 0.6122 loss_bbox: 0.2935\n", + "04/17 10:30:41 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:30:41 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.11s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.635\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.755\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.718\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.242\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.771\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.210\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.680\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.774\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.642\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.861\n", + "04/17 10:30:42 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.635 0.755 0.718 0.000 0.242 0.771\n", + "04/17 10:30:42 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [13][3/3] coco/bbox_mAP: 0.6350 coco/bbox_mAP_50: 0.7550 coco/bbox_mAP_75: 0.7180 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2420 coco/bbox_mAP_l: 0.7710 data_time: 0.1386 time: 0.1939\n", + "04/17 10:30:44 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [14][ 5/16] lr: 6.1855e-05 eta: 0:00:43 time: 0.4110 data_time: 0.1137 memory: 1422 loss: 0.9190 loss_cls: 0.6193 loss_bbox: 0.2997\n", + "04/17 10:30:45 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [14][10/16] lr: 5.8584e-05 eta: 0:00:40 time: 0.4015 data_time: 0.1135 memory: 1422 loss: 0.9312 loss_cls: 0.6258 loss_bbox: 0.3054\n", + "04/17 10:30:47 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [14][15/16] lr: 5.5152e-05 eta: 0:00:38 time: 0.3801 data_time: 0.1030 memory: 1422 loss: 0.9122 loss_cls: 0.6111 loss_bbox: 0.3011\n", + "04/17 10:30:47 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:30:47 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.11s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.636\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.739\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.708\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.220\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.782\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.222\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.690\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.788\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.683\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.867\n", + "04/17 10:30:47 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.636 0.739 0.708 0.000 0.220 0.782\n", + "04/17 10:30:47 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [14][3/3] coco/bbox_mAP: 0.6360 coco/bbox_mAP_50: 0.7390 coco/bbox_mAP_75: 0.7080 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2200 coco/bbox_mAP_l: 0.7820 data_time: 0.1371 time: 0.1918\n", + "04/17 10:30:50 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [15][ 5/16] lr: 5.0871e-05 eta: 0:00:36 time: 0.3939 data_time: 0.1054 memory: 1422 loss: 0.8896 loss_cls: 0.5903 loss_bbox: 0.2993\n", + "04/17 10:30:53 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [15][10/16] lr: 4.7206e-05 eta: 0:00:34 time: 0.4165 data_time: 0.1144 memory: 1422 loss: 0.8796 loss_cls: 0.5829 loss_bbox: 0.2967\n", + "04/17 10:30:55 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [15][15/16] lr: 4.3492e-05 eta: 0:00:32 time: 0.4306 data_time: 0.1161 memory: 1422 loss: 0.8680 loss_cls: 0.5699 loss_bbox: 0.2981\n", + "04/17 10:30:55 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:30:55 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Saving checkpoint at 15 epochs\n", + "04/17 10:30:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.11s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.01s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.644\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.750\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.701\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.232\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.788\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.224\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.686\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.790\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.692\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.867\n", + "04/17 10:30:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.644 0.750 0.701 0.000 0.232 0.788\n", + "04/17 10:30:57 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [15][3/3] coco/bbox_mAP: 0.6440 coco/bbox_mAP_50: 0.7500 coco/bbox_mAP_75: 0.7010 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2320 coco/bbox_mAP_l: 0.7880 data_time: 0.1364 time: 0.1922\n", + "04/17 10:30:59 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [16][ 5/16] lr: 3.9019e-05 eta: 0:00:30 time: 0.4257 data_time: 0.1152 memory: 1422 loss: 0.8383 loss_cls: 0.5478 loss_bbox: 0.2904\n", + "04/17 10:31:01 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [16][10/16] lr: 3.5320e-05 eta: 0:00:28 time: 0.3921 data_time: 0.0945 memory: 1422 loss: 0.8302 loss_cls: 0.5349 loss_bbox: 0.2953\n", + "04/17 10:31:03 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [16][15/16] lr: 3.1685e-05 eta: 0:00:25 time: 0.3721 data_time: 0.0851 memory: 1422 loss: 0.8413 loss_cls: 0.5464 loss_bbox: 0.2949\n", + "04/17 10:31:03 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:31:03 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.12s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.01s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.667\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.769\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.741\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.264\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.800\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.218\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.714\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.802\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.708\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.878\n", + "04/17 10:31:03 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.667 0.769 0.741 0.000 0.264 0.800\n", + "04/17 10:31:03 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [16][3/3] coco/bbox_mAP: 0.6670 coco/bbox_mAP_50: 0.7690 coco/bbox_mAP_75: 0.7410 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2640 coco/bbox_mAP_l: 0.8000 data_time: 0.1355 time: 0.1909\n", + "04/17 10:31:03 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_11.pth is removed\n", + "04/17 10:31:05 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.6670 coco/bbox_mAP at 16 epoch is saved to best_coco_bbox_mAP_epoch_16.pth.\n", + "04/17 10:31:08 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [17][ 5/16] lr: 2.7458e-05 eta: 0:00:23 time: 0.3927 data_time: 0.0868 memory: 1422 loss: 0.8335 loss_cls: 0.5406 loss_bbox: 0.2929\n", + "04/17 10:31:10 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [17][10/16] lr: 2.4087e-05 eta: 0:00:21 time: 0.4168 data_time: 0.0975 memory: 1422 loss: 0.8255 loss_cls: 0.5369 loss_bbox: 0.2886\n", + "04/17 10:31:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [17][15/16] lr: 2.0888e-05 eta: 0:00:19 time: 0.4215 data_time: 0.0995 memory: 1422 loss: 0.7952 loss_cls: 0.5153 loss_bbox: 0.2799\n", + "04/17 10:31:12 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:31:13 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.11s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.670\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.769\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.736\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.262\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.811\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.220\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.702\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.796\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.667\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.883\n", + "04/17 10:31:13 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.670 0.769 0.736 0.000 0.262 0.811\n", + "04/17 10:31:13 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [17][3/3] coco/bbox_mAP: 0.6700 coco/bbox_mAP_50: 0.7690 coco/bbox_mAP_75: 0.7360 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2620 coco/bbox_mAP_l: 0.8110 data_time: 0.1274 time: 0.1807\n", + "04/17 10:31:13 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The previous best checkpoint /content/mmdetection/work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_16.pth is removed\n", + "04/17 10:31:14 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - The best checkpoint with 0.6700 coco/bbox_mAP at 17 epoch is saved to best_coco_bbox_mAP_epoch_17.pth.\n", + "04/17 10:31:17 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [18][ 5/16] lr: 1.7321e-05 eta: 0:00:17 time: 0.4258 data_time: 0.0986 memory: 1422 loss: 0.8050 loss_cls: 0.5228 loss_bbox: 0.2821\n", + "04/17 10:31:18 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [18][10/16] lr: 1.4608e-05 eta: 0:00:15 time: 0.3996 data_time: 0.0886 memory: 1422 loss: 0.8288 loss_cls: 0.5427 loss_bbox: 0.2861\n", + "04/17 10:31:19 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [18][15/16] lr: 1.2158e-05 eta: 0:00:13 time: 0.3710 data_time: 0.0774 memory: 1422 loss: 0.8460 loss_cls: 0.5555 loss_bbox: 0.2906\n", + "04/17 10:31:19 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:31:20 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.11s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.657\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.755\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.720\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.243\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.802\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.222\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.696\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.788\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.658\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.875\n", + "04/17 10:31:20 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.657 0.755 0.720 0.000 0.243 0.802\n", + "04/17 10:31:20 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [18][3/3] coco/bbox_mAP: 0.6570 coco/bbox_mAP_50: 0.7550 coco/bbox_mAP_75: 0.7200 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2430 coco/bbox_mAP_l: 0.8020 data_time: 0.1262 time: 0.1788\n", + "04/17 10:31:23 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [19][ 5/16] lr: 9.5997e-06 eta: 0:00:10 time: 0.3893 data_time: 0.0942 memory: 1422 loss: 0.8529 loss_cls: 0.5586 loss_bbox: 0.2943\n", + "04/17 10:31:25 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [19][10/16] lr: 7.8096e-06 eta: 0:00:08 time: 0.3976 data_time: 0.0917 memory: 1422 loss: 0.8404 loss_cls: 0.5487 loss_bbox: 0.2917\n", + "04/17 10:31:28 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [19][15/16] lr: 6.3487e-06 eta: 0:00:06 time: 0.4112 data_time: 0.0973 memory: 1422 loss: 0.8332 loss_cls: 0.5454 loss_bbox: 0.2878\n", + "04/17 10:31:28 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:31:28 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.11s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.02s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.648\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.758\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.714\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.278\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.786\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.220\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.682\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.788\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.692\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.864\n", + "04/17 10:31:28 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.648 0.758 0.714 0.000 0.278 0.786\n", + "04/17 10:31:28 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [19][3/3] coco/bbox_mAP: 0.6480 coco/bbox_mAP_50: 0.7580 coco/bbox_mAP_75: 0.7140 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2780 coco/bbox_mAP_l: 0.7860 data_time: 0.1265 time: 0.1790\n", + "04/17 10:31:28 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Switch pipeline now!\n", + "04/17 10:31:30 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [20][ 5/16] lr: 5.0499e-06 eta: 0:00:04 time: 0.3946 data_time: 0.0911 memory: 1422 loss: 0.8398 loss_cls: 0.5569 loss_bbox: 0.2829\n", + "04/17 10:31:31 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [20][10/16] lr: 4.3584e-06 eta: 0:00:02 time: 0.3541 data_time: 0.0729 memory: 1422 loss: 0.8475 loss_cls: 0.5686 loss_bbox: 0.2789\n", + "04/17 10:31:32 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(train) [20][15/16] lr: 4.0293e-06 eta: 0:00:00 time: 0.3273 data_time: 0.0671 memory: 1422 loss: 0.8943 loss_cls: 0.6161 loss_bbox: 0.2783\n", + "04/17 10:31:32 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Exp name: rtmdet_tiny_1xb4-20e_balloon_20230417_102835\n", + "04/17 10:31:32 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Saving checkpoint at 20 epochs\n", + "04/17 10:31:34 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Evaluating bbox...\n", + "Loading and preparing results...\n", + "DONE (t=0.00s)\n", + "creating index...\n", + "index created!\n", + "Running per image evaluation...\n", + "Evaluate annotation type *bbox*\n", + "DONE (t=0.11s).\n", + "Accumulating evaluation results...\n", + "DONE (t=0.01s).\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.652\n", + " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.794\n", + " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.750\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.241\n", + " Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.785\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.222\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.706\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.782\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.000\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.675\n", + " Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.861\n", + "04/17 10:31:34 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - bbox_mAP_copypaste: 0.652 0.794 0.750 0.000 0.241 0.785\n", + "04/17 10:31:34 - mmengine - \u001b[4m\u001b[97mINFO\u001b[0m - Epoch(val) [20][3/3] coco/bbox_mAP: 0.6520 coco/bbox_mAP_50: 0.7940 coco/bbox_mAP_75: 0.7500 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.2410 coco/bbox_mAP_l: 0.7850 data_time: 0.1265 time: 0.1808\n" + ] + } + ], + "source": [ + "!python tools/train.py configs/rtmdet/rtmdet_tiny_1xb4-20e_balloon.py" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_vYQF5K2NqqI" + }, + "source": [ + "### Understand the log\n", + "From the log, we can have a basic understanding on the training process and know how well the detector is trained.\n", + "\n", + "First, since the dataset we are using is small, we loaded a pre-trained Faster R-CNN model and fine-tune it for detection. \n", + "The original Faster R-CNN is trained on COCO dataset that contains 80 classes but KITTI Tiny dataset only have 3 classes. Therefore, the last FC layers of the pre-trained Faster R-CNN for classification and regression have different weight shape and are not used.\n", + "\n", + "Second, after training, the detector is evaluated by the default VOC-style evaluation. The results show that the detector achieves 58.1 mAP on the val dataset, not bad!\n", + "\n", + "We can also check the tensorboard to see the curves." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 821, + "resources": { + "https://localhost:6006/?tensorboardColab=true": { + "data": "PCFkb2N0eXBlIGh0bWw+PG1ldGEgbmFtZT0idGItcmVsYXRpdmUtcm9vdCIgY29udGVudD0iLi8iPjwhZG9jdHlwZSBodG1sPjwhLS0KQGxpY2Vuc2UKQ29weXJpZ2h0IDIwMTkgVGhlIFRlbnNvckZsb3cgQXV0aG9ycy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC4KCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOwp5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCllvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAoKVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQpkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLApXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZApsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KLS0+PGh0bWw+PGhlYWQ+PG1ldGEgY2hhcnNldD0idXRmLTgiPgo8dGl0bGU+VGVuc29yQm9hcmQ8L3RpdGxlPgo8c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCIgbm9uY2U9ImFmZGZlZWY2NDc1YTQxZjBhY2M4MjBhYzdiNCIgc3JjPSIvL2luamVjdGlvbnMuYWRndWFyZC5vcmc/dHM9MTY4MTcyNDU4ODU5NyZhbXA7dHlwZT1jb250ZW50LXNjcmlwdCZhbXA7ZG1uPWNvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20mYW1wO3B0aD0lMkZ0dW4lMkZtJTJGZ3B1LXQ0LXMtMzlzeTg5NTIyMnB4eSUyRl9wcm94eSUyRjYwMDYlMkYlM0Z0ZW5zb3Jib2FyZENvbGFiJTNEdHJ1ZSUyNmF1dGh1c2VyJTNEMCZhbXA7YXBwPWNocm9tZS5leGUmYW1wO2Nzcz0zJmFtcDtqcz0xJmFtcDtyZWw9MSZhbXA7cmppPTEmYW1wO3NiZT0xJmFtcDtzdGVhbHRoPTEmYW1wO3VhZz0iPjwvc2NyaXB0Pgo8c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCIgbm9uY2U9ImFmZGZlZWY2NDc1YTQxZjBhY2M4MjBhYzdiNCIgc3JjPSIvL2luamVjdGlvbnMuYWRndWFyZC5vcmc/dHM9MTY4MTcyNDU4ODU5NyZhbXA7bmFtZT1BZEd1YXJkJTIwQXNzaXN0YW50JmFtcDtuYW1lPUFkR3VhcmQlMjBFeHRyYSZhbXA7dHlwZT11c2VyLXNjcmlwdCI+PC9zY3JpcHQ+PGxpbmsgcmVsPSJzaG9ydGN1dCBpY29uIiBocmVmPSJkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQU1RQUFBREVDQVlBQUFEQXBvNXJBQUFBQkhOQ1NWUUlDQWdJZkFoa2lBQUFBQWx3U0ZsekFBQmFiZ0FBV200QnhXc2pPQUFBQUJsMFJWaDBVMjltZEhkaGNtVUFkM2QzTG1sdWEzTmpZWEJsTG05eVo1dnVQQm9BQUJsMFNVUkJWSGljN1oxNWVGVFYzY2MvdjVzRkNWUnhxVnRwdFZXMlZseGY2MXF0aXEwTDlHbDlOWFVCRkVwRlJUSWhoQkFEQ1RkQU1BRWtDWXNLN3FCOStrRDcycWZCV2l2cVc3VmE2NzRpcmU5cmF4WDFiWlZXWlRITC9ONC81ZzRHU0dEdXpMMXo3NTA1bjM4NHpNdzU1OHZNL1hLMjN6a0hESUdqaXpoUEYzRmUwRG9NSUVFTHlHZDBQb093YUFBdWNWNWFoMFc1VlBCNmtMcnlHV09JQUZDYi92U2pFcWdHK3V6d0t3Z2R3QzEwVUNmVi9Ec1loZm1MTVVRV1VSdUxFa1lEOHhFTzJ2N0dqb1p3UHN4SFdNeGhJRXVsbEs1czZzeG5qQ0d5aEM3Z0pKUlc0Q1NnWnhQMG5INEppNWlVODdqZkdnM0dFTDZqVFF3RTVnR2prVzdmZCtxR1NMS1dJc3BrTW0vN0lOUGdZQXpoRTJwVFFoK3FzS2dDK2dMdVRiQnJlaXV3bUhZYVpEcWZlaXJZQUJoRGVJNHFRaU1YQXdzUUR2UEFCRDJsTnlMVXM0bmJ4U2FldVdwREVtTUlEOUViT1FGb1JUaHQrNHYrR0NMNTU3TkF1Y1I0S2kzQmhsMHdodkFBYmVBUUxHeGdBbUQ1YW9MdTZjU2ZpdkFMbEVxSjhZNUw2WWFkTUliSUFMVXBwb2hyRVdZajdMMzlqZXdhSXBuZWpMS1F2V21VY1d4TDZSOWcyQVZqaURUUnVZekNvZ1g0QnBBOUUzUlA5L3orMzRHWlVzYktYcVFiZG9NeGhFdTBnYU1SbWxITzl2QUIvNk9UT3RrRFF5VFN5aU1vNVJManRaNytIWWFlc1lJV0VCWFVaaitkU3l2d1BNclpIaFc3RVdFaW4zRWFVemtWb1JUNG15Y2xDK2RnOGJJdVphWGV3b0dlbEprSG1CWmlENmhOSVVXTVIya0FEdkNvVmRpS3NKajRydXNKdXB3U1BxTUtxRUtjOVl2ZXlrbTl6azFBRXdmU0xLVzBZK2dWWTRqZG9QV2NnMFVMd2xIYlg4emNFR3NSeW1UYTdsZWN0WW1CRkRrcjNPeXd4cDJPSVpMcERRaFQ1VG9lMkYzZCtZd3hSQS9vSEFaQnQ3QnNiMXFGRnltZ1hDcmR4U1RwSXM0RVdoQ08zYVhNOUxXc280Q1lYTU1iYnJUa0E4WVEzVkNiL2dpVnlFNWgyWmtaNGlPRU9SeWVmdFNxMmxnTVlEVEtmT0FnajNSMW9OeENJWFV5MFlTWkp6R0d3SG5nTE9lQjZ5a3NPNzBITC9IQWlYZjdHblFaL1dudndiRHBhd1NjTVBQOVRKZzVHRU9nczFNSXkzYi9zSzBqVGt4cS9PbVNhQ3VEVUJxUTdUdnR2T2pXdlloUUx0ZmtkNWg1M2hwQ2JSZGgyYWsvVkJ0UUtxU0czM2dxdGhlMGxYT0FGdUFvRDlkRTFsSkFtZncwUDhQTTg4NFFhbE1DenJSbXFtSFplMzZRTmlFMDhUbk5ZbWQzV2xOdEN0bVg4WWd6TGR5N1JqZnBSSmg1RVEzeWsvd0tNODhiUXlnSWRWeU14UUxVWlZoMjcrOTNvdHlKTUZOcStJZlhtdDJnaTlpUFFtWUIxeUVVYm44ak0zTzhoMUxEUkZhSm9KNEtEaWw1WVFpdDVRUWtnN0Rzbmw5N0ZJdHlxZVpWVDhWbWlDNWhLRW96NGh4cjQwMVg2bG1VbUZ6RDB4NUtEU1U1YlFpdDRSQ0tzTkVNdzdKM2ZPMHRsQnFaeVJxUDVYcUtMbVlVUWd2aUJCOUNwdVpRNEY0NnFKSkpmT0NwMkJDUms0WlFtMkk2UFF6TFR2eVpDSy9laHh1bGpNODlsdXdMYWxQTS9zNzNBSHQ3MUZwc1JsaElZVzZHbWVlY0lYU0c1MkhaY1lUN0VLYkpERDcwVm0xMjBPVWNRZ2MyNHJTVTRNWDM4aFpDalV3SWQwdnBscHd4aE43QVVBcG9Cczd6Y0FyeUdaU1kxUEdNaDFJRFEyOTJ0cmdxcDNuMkhWazhTcHdwOGxOZThWQnFZRVRlRUdxekh4M01RcmdPbk5tVnpIL3NkNEVaMU9iZTdJb3F3aTNPSVFod0dPREY5OVdKT0xOdDQ0T2RiY3VVeUJwQ2JRcnBZRHg0R3BhOUJWakFsMmlTQ3JaNktEZDA2SEpLNktRS1NTSE1QTlcweFNiaU5MRjNkTVBNSTJrSXJYRldhTDBMeTFiZ0Z4UXdUV1o2dEVFbkl1amlGTUxNM2FZVFllWVZNaTQ3Sy9aZUVpbEQ2RXdHRWZjOExQczVMTXFsamo5NHB6UjY2TTJjNlV4RzdCcG03amI5eFd2cktLUk14ckxlTzZYK0VnbERxTzFFZVNyVkNIMjJ2NUdaSVRZQzlXQU8rMHFpTmhZSE80Y3hkdzh6aDB6V2JSSlJ2eDNSQ0RNUHRTSFV4bUlybzdHY0h3aThhQlhhZ1ZzcFlhWTVEckpuZEJuOXNhakVjc0xNSVJOREpFbUVtZmNOZDVoNWFBMmhOM1FMeS9adUduVXRRa3hzL3RkRHFUbUwzc29neE9taVptNklaUG9GbEhJWnh4TmVhdldLMEJsQ3F4aUkxVzJRQjE2MENpK2hsTXNjZnUrcDJEeEJWekRDaVk5S1RHSmtab2hrZWkxeEpzczQvdXFsMWt3SmpTSFVwb1F0empRZzlQV29WVWhzMzF6UFVsa1QzbVk2Q3FoTklWOXhUaDhSSjh3Y01qRUVKS2E1bDlDWHVWTEtaOTRxVG8vQURhRWdUT05peEZrb2N2K2w5cFJPWEV2VlRwMDBoWDhnRnlYMGR2WkRtUVZNQWdveU5FUXkvUjVDRGFPRFh3Z04xQkE2bFJPd2VnbkxUdDhRNnhES1pZNjV1TkJQOURhR0FZdVFicmVuWnY3Yi9RbUxtSXplZnBKaDFnbkVFRnJ1aEdYdjdyUnM5MS9xQnVKTWxYbm16S0Zzb3JjekNweGdTbTlhZDBXY01QTngyUTh6ejZvaG5PN1JOSlJhaFA0OXFuRC9wWDRNMUZQRXpXTFQ2YTFpUXlyb1hleEZuQXJnQnFDL1IrTy9UN0NZUXhHTHN4a0drdlVXUWlzNHpqa043NHdlVmFSdWlFUllkaEdWWXZOL2ZtZzF1RU9YY3dpRlBvV1pYNUdkTVBQQXhoQmF5U2lFVnVEcnJnMmhQSW93UmVibFJzaHhycUYzY3dKZHp0alF1eldrUjFIS1piUy9XM2FESFZUYkZMT1pheEhtQUYvYVFWSFBYOHhiV05SSVEyNXRTc2xGVkJIdTlpSE1ITzZraUpsUzZrK1llZURUcmdCYXhhSEFMR0FDMG1OVG05aTJXRXlqMkxtM2JUR1gwZVdVVUp6aWFlYXBwemVoMUhNb3krUXNiOGVOb1RCRUVxM2lQNXh1MUttT3NzVEc5aTZxWkVIdWJtelBCL1EyQmxMTVBOVERNSE40RTRzS3VaUUh2ZElaS2tPQU14TTFuVEVJRjJOaHl6eGVDRnFUd1R2MExyN3JoSmtmcy8zRnpNMXhQM0VxNVlyTVk5UkNad2hEN3FNMkZvYzdZZWFTUVpqNWp1a09MRzZoZzFvWnpTZnBhak9HTUFTR3JxWS9XN3VkWmc2WnR4YkMreWcyRm5la0UyWnVER0VJSEwyUFFYU2xFV2ErdXpROGowVzVsUEtrR3kzR0VJYlFvUGR3UHNJaWhLRkFwb2FBeEtYMkUrVEgzSm1xQnRlM2tHbzVFN1NjSDdyTlp6RHNDYm1TQjluSzBjQkU0SjllRkFrYzZpYUQrMnQ1bFNOUjd0Y1k2L1I2eDhrR2cwZklSRHBrREN2b3d4QmdNV1IzSDBzNmhpaHlVdWRRd0NzYW8xV3Y2eGFvWnpCNGdKVHlzWXdtUnB6aHdHK3pWYTk3UTFqYkRRRlFCSlJSeEhxTk1kWXpWUWFEZzR4bHZZem1mSVFmZ1A5NzRUTnBJYm96RUxoSHkzallkS01NZmlDWDAwWVJ3NEJ5U0grZFlVKzROd1E5R2lLQk1JSUNYdFlwTktyTlh1bkxNaGgyUlVwcGx5dG9SUmdHL3B5bDVhMGhFaFNqVE9kZnZLNHhScVlqeW1EWUhYSVpHOEdmdmRkK0dDTEpOeERhZEFwdFdzblgwNmpIWU1nNlhvMGhkc2RJdW5oZEs3Qk5OOG9RZGpLZFpVcVZ2aWl6K0lUWHRKSUwwOGh2TUdTRmJMUVEzVG1DT0d1MWdqWXQ1L0FNeWpFWWZNSFBNY1R1R0VrQmIyaWw2VVlad2tWUWhvQmtOK3BUWHRWcFhPQlJtUVpEUmdScGlDUkhvanlnVTJuVGF0T05NZ1JMdHNjUXZTT01wSlBYdFFwYkozZTdGTVZneUNMdURTRStHU0pCQ2Nvcyt2Q2FUdXQyWnFqQmtDWEMwR1hhRmVGSTRFR3RvazJuT21mNkdBeFpJSnlHK0lMRWJKVHBSaG15Uk5nTkFWQUN6S0tFVjNVNjM4OXkzWVk4SXdxR1NESUkrSzFXMDZiVCtWcEFHZ3c1VHBRTWtVQVppYkJlcTdIVnBqaFFMWWFjSTNxR1NKRG9SbTNqVmEzbWUwR0xNZVFPVVRWRWtzRUlEK2tOdE9rTXZocTBHRVAwaWJvaGtvd2t6bnF0TWQwb1EyYmtpaUVBK2dHemFPY1ZyZUhjb01VWW9va3JRMmppNEtkQ243UjR4UkRnZHpyRGRLTU03bkhYUWx3ZGVqTjBaeVN3WG1lYWJsUmVFM2QzR0lIYkxsTll1MHU5MFE5bEZsMjhyRE1aRWJRWVExYlpqRkxQTmhhNXllVE9FUHRHemhBSmxLSEE3M1FtSzdXR2c0S1dZL0FWQlZiUndTQzVERnZHdWJ1Q3paMGhPaU5xaUFRQ2pLR0FEVHFUbU5xUjZ2NFpVa0g1RXhhbnlhV01sVEc4bjA0UjdnelJGV2xESk5rSG9ZVXVudE5hVGd0YWpNRVQza1c0a2tzNVdVcDVPcE9DM0JuQ3I4MUJ3WEFNd2hOYVo3cFJrVVhaQXRSVHdHQzVsSlVpbVI5ZTVzNFE4Wnd5QkNTN1VZVzhxWFhFOUJJS2doWmtTQkZoRlVVTWxzdXdwWlN0M2hYckFyMmVvUlN3M2xXcDBVcS9oREJKYko3Q2tKZTRheUdLY3E2RjJKbGpnU2ZWWnFYYUhCaTBHRVAyeWVjeFJNK28wNDJDRFZwUFRGZWJibFErWVF6Uk93TlFXbGpQc3pxYlU0SVdZOGdPdWI1UzdRWEhvZnhCNjFtcDgvaHkwR0lNL21KYWlOUklkS002MmFCelREY3FsM0ZuQ01uN0lMbDlVVnJZd0o5MExpY0hMY2JnUGFiTGxBN0s4U2hQNlJ6VGpjbzFUSmNwZlJMZHFDN1RqY29sVEF1Uk9mc2l0UEFYbnRIWm5CUzBHRU5tdURORWVyY0g1UWZLQ1ZnOHBYTlpxVFlIQkMzSGtCNm1oZkFXQzJFTVJXelFCbUpxcDdWblBWUm9Nd09DMXBBSnVwcUQzWHplN1ErMjV6MEVycUtqY3BiOWdCYUtlVkliT1Nab01lbWdTemhVbDdLY1l1WUVyU1VkZEJWRDlHYzhRQWRUM2VSek82aDJOKzFxekhFS1NsblFJdHlnTnNXNmhCZ1dieUpjVFhvbnN3U0czc2UrZWgrTldMd0M3bSttY3JkclRDbEsreUhmTWQ5bTRER2dqUzZlb0lqSDBCemRreENuUFdnSnFhTExHSVhTQ3RHN1YxeHRMSTVnTkxBQTBnL01kTHVOTXBNeHhOc29Ed05yMlllSHhQN2lRZEZLS29EN01pZzd2QWdkUVV2WUU3cU1ZNG5UZ25KbTBGclNRZS9qTE9LMElCeWRhVm51REdGUjVHSlBVaGVKL1FWckVkcmtKcDd2N1lPeWtKOXBKWmRERHQ1aExlRnRJWFFwK3dOMXhKa0UwVnRIMFpWOERZdTVLR084NnA2Nzd6THRuczJJMHhVcTVOZXlnQTljS0xtZUxyNUw0Z1MrM0VIRDEwTG9jb3BvNXpyaTFDUHNFN1FldCtoSytpRk1BNmFqN09YbFdOV0xMdFBiQ0E4anUzYUYzQ0NOL0ZXcmFFQ1psM2doblZKQ1NNZ01vWXNad2VlMEFOOEtXb3RiVkJIdVpReEtFN2liVGsyVmRBenhSVmNvVHBzczdyMHI1Sm9TRnJDWlVoSTcxM0lES3h5RzBLVU1wb3RGS0JkRzhUOGJ2WXNUV1VVcitMczN4WjBodXJpYkl1WkxNeC83SVVac09yV1M2eENlcFB0MFgyLzdvS05Cb0dNSWJXWUFRalZkVElIb1JTdnJQWHdGdUJFWVRSWitmVmVHa0tXODZaZVE3WFVzNUdtZHhuTGdXci9yeWdvQmRablV4bUpBNXRPUVFhR3I2Y3RXeW9BWndKZXlWVzg0RjEyMlVZM3czaDQvRjRYV0lvQlpKcjJKczlpSEY0QjdpS0laN21JVVczZ0RwWkVzbWdGQ2FnaFp3aWRBaGJ0TS9takptQ3kyRUhvVFg5Vm1WbUx4S0VRdlpFVHY1SGk5aThjUmZnMGNIb1NHMEo1dktrMnMxbW1NUmhqbFByTVBndElsQzRiUUJmVERZaHJDZEdBdnYrdnpHcjJIL2VtaURnbCtQU1MwaGdEQTRucVVzNEQrYVpjUnZEbDg2ektwSWl6aVlvU0ZFTDJyaW5VNVJSUnpIVjNoV1E4SlpaY3BpVFR4RGtLOWR3VjZWbExxK0RUdHFnczVrVVU4Q2F3bWltYTRreEVVOFJKS0M0VERESkJsUTZoTm9icDlMUHZTQXJ6b3VaanNtR01UeXF0ZUZxaE5IS29MV1k3d1IrQlVMOHZPQm5vSFEvUjJIbkRpMnI0WnRKNmQ4ZDBRT3BrK09vVVJXa0Vybi9BdVU3aktUWDZ4NlVTNW1zU0NvRC80WVE3bEF5ek9raHJlOEt6SWhWeEVBWCtHNklWbEEranRqQWRlUTl5SFpXY0xYNzVVblVKZm5jSW9uY0pLQ3ZrUWVCZ29BdzVDYU5ZcUJyb3BUK2J6SEhDekgxcDNyY3lUVXQ2bWdPOUlOUzk3VXRvWEhFTzBZNzJHRWZKeHEyZml0SndCS09jaWpFTDVFZEMvbDRkckg3cTRCVnpPSGlrekVINEU3c3lVRWVtWjR6VUsrYjVNWjZQSGFneFpJS01XUWlleHY4WVlxekhhVUQ0a01jQWJ3NTVuaFVacUJUOTJVNWZNNTFNazlMdlBIcWVBMDQwWm9vdHJRK2hrQm1vWlYydU1OZ3A1bjhScTZFamN4c2tJUzdYUzNTcXFOSEkveXE5YzFlTVZlMjR0MWxMQ2VWTE52N09neHVBVDdpNXVMK2NpTE41QldFN0NCSm5zb0R1QU9NMnVjeW1USU9DSGJtZHpLUGZ5WlM2U0N1OXVzakVFZzlzcnRmcmg1WnlNY0xsTzVZZXVzc3huSThvc3p6Umtpc1VTdXJoU0pvWWp6TnVRR2NGUDNRazNhelg3dXNyVGx5WEFILzBSbERJSzFNc015c1FtSHJBV2cwY0Vid2c0aEU2YTNHUVFtemdXRXlHdy81VzdVSzZSV3V5QTZqZjRSQmdNQVRCQnAvRTlOeGxrSHE4QVMzelNzenZhRVM2VE9sWUVVTGZCWjhKaUNDSE9jclZkQnZIMW9SWjQyeDlKUGJJWllaVE1aRTBXNnpSa2tiQVlBb1REMmN4Y1YxbHN0amdodzluZ1l5eEdTQzIveTFKOWhnQUlqeUVTVE5icG5PNG1nOHpqUWVBWFB1bEo4ZzdDcVRJejhJRzh3V2ZDWmdpTE9MZXI3WEtUU3llVGdYLzVJNG4xRkhDNjFMTEJwL0lOSVNKc2hnQVl3bVpxM1dSd0RrU2I2WU9XNXlqaVRKbkozOU10UUJzNHlrdEJCbjhKb3lFQXFyU2FFMXpsS09ZVzRDa1BOVHpHWHB3dE5md2ozUUwwUnFZalZIcW95ZUF6WVRWRUlYSHUwS3RURHcwUm16Z0ZYSU0zYXhPL0FpNlE2WHlhYmdIYXdHem4xQWhEaEFpcklRQ09ZUUJWYmpMSUhGNUYwb2lQMnBHN2dVdkVabHM2bVZVUmJhQUZjZGZ0TTRTRE1Cc0NvRmFudXp5RGRBczI4RDlwMVNZMGljMDRzZWxNSjd1dXBvQjUzQTdFMHFyZkVEaGhOMFFmNEE2OUpQV2pTYVNaclU1RWJPb0lpbElwTnRWdUJTYlIxUlR3Wis0Q3hxZGJoaUY0d200SVVFN2k2MHgyazBYbThSREN6MVA4ZUJjd1FXWnprM3R4Q2RTbW1EK3pCbUZNdW1VWXdrSDREUUVnTkdnMVI3cktFNmNjMkxTSFQzMU9Zcnh3WjdyUzFLYUVRdHFBSDZWYmhpRThSTU1RVUlKeW01c2piR1FlSDhKdXUwRC93bUtFek9iK2RFWHBBdm81Wm5BVm1HZ0lMMUV4Qk1CM3FlYW5ybkkwY0J2d1pBL3ZKSTZJc1h0OEx5WFVaZ0RiV0FlY25XNFpodkFSSlVPQXNrQm44TlZVUHk2SlRUd1RTSFNOa2lTT2lMRjVLVzBaTmdkU3dIOERKNmRiaGlHY1JNc1FzRGRkM09vbWc4eGxBN0RRK2V2cnhQbU8yTHlWcmdDMU9aZ0NIaUdDcDJzYjlrelVEQUZ3Z1Zaemhhc2NoY3hGdVpzQ1RwZUdGTzZkNkFXZHkyRllQQUVtUGlsWENmVXBhcnVoVld0WTV3eWM5NGl6Nmp3dWt3clZaakJ4MWtIcVhUWkQ5SWhpQ3dHd1A4cmliRldtY3hpR3hXTVlNK1E4VVRVRUtLVjZBeGY1WG8zTjhjUjVIRGpVNzdvTXdSTmRReVJZcGpiNytWVzR6dVpFaEllQkEveXF3eEF1b202SWc5bVdmc2pGN3REWm5JbnlDUGhuT0VQNGlMb2hRTGhLcXpuUHl5SzFudk9KOHlCWnZnSFRFRHpSTndTQXNGeXJ2SGw0MWVZSEtQY0RmYjBvenhBdGNzTVE4RFVLdVRIVFFyU095NEJma2dnN04rUWh1V0lJZ0d0MUJtZWttMWx0cmthNGwraXV6Umc4SUpjTVlhSGNwbFBjZDNXMGprbkFyZVRXOTJGSWcxeDdBQVpUNHU0QVlxMWpPc0pTTkFRM1doc0NKOWNNQWNwVXJlWEVsRDQ2aTNvd0oyUGtNRytnL05KTkJuZUc2T0p4SlBSbm14WVE1dzYxZTcvaVMwRjBGcTBvZGRrVVpzZ2FtNEJxQ2psT1JyczdmdFRWQUZLVzhUZmcrenFGRWNScEFaY25ZbVNQNGJSekExQy84eHRxWTlISkNwU2ZCS0RMNEM5eDRENkttQ3FsNlIwd2wxYVhTWnBaeDFhT1F5a242UHZlZWtPbzBWcUdkMzlKTDZHQVR1NUVqQmx5RHVGUkxJNlRLeGlicmhrZ2d6R0VyS0JERnROS0owY0FpMG1jWGhFbWlvbHpqOXFKVmxCdGlobkthb1FyZ3habThKUzNFRXJsQ3M2Unkza2wwOEl5SGxUTE1qNlNWbUlJdzRHSE1pM1BZNDZqZzNLZFRCODZXUVArUjhjYXNzWm1vSjVPaHNzVjNsMWc0OWtpbExTd0hqaFBZMXlDTUI4NDNLdXlNMEtZelQ1Y0JKd1N0QlNESnlod0w1MVV5VGcrOExwd3o2ZGRwWlUxREdBSVFqbndpZGZscDBGZmpCbHloV2RSVHBPeGpQWERET0RUT29UWXRFc3pyUlF4REZnQjV0cGFRMGE4aDNJbFl6aEpydVJwUHl2eWRXRk81ck5SbXBrSW5BVDh3Yys2RERuSlZxQ0p2Z3lWcTFncGd2cGRZVllDMmFTWjV4UytRd1VYQXd1QXc3SlJyeUhTckVXWkxGZngxMnhXbXJYUURRR1ZSYXpoTTc2SlVFL0MvUWJEenJ5QWNvWmN4U2dabDEwelFBQ3hUTEtDTGJJUW13SUdBNnZBLzJiUUVBaytBc3JweDdkbEhFOEVKU0t3NEQ2Wno3dHlFMk9CVXhCejNXMGUwd0VzcHAwalpCeXRVaHJzQW0vZzBhNnlrR2ZveDJuT0NuSktCNDhaY29aMXdMRXlucGhNREVjSVVPQ0dnTVNGaWJLQWxTaEhPT09Mei9lWXlSQmxObUJ4b1l6blhCblBHMEdMNlU0b0RKRkVGckpaNW1OVHdGR0lkOHZ4aHREd01jcGsvczVSTW83ZkJDMm1KMEs1ZjFnYWVRc28xV3JPZHNMTWgrOHBqeUg4eUlUTUQ0THdtMUMxRURzampUeEtDY2NqVElUMFEzb05obFFKdFNFQXhLWlRHbG1CTUFTbENXZ1BXcE1oZHdtOUlaSklJNXRrUHRYRU9SckMyZjgwUkovSUdDS0p6R2VETkhJaGNDNkVhNGJDRUgwaVo0Z2swc2c2UHVKWUo4dzhGSFBZaHVnVFdVT0FzNDExSHEzQUVjNEZLbUhieG1xSUdKRTJSQks1a1kva1JtTEVPUkY0UEdnOWh1aVNFNFpJSW8yOEtQTTRFL2dCOEhiUWVnelJJNmNNa1VRYWFHTUwzMEtwQmo0TldvOGhPdVNrSVFDa21hMHlqeVlLR1lxYWJheUcxTWhaUXlRUm00M1N3RVNVa3hDZUNscFB4UGd2NHRzdnZjOExjdDRRU2FTQjU1ak42UWlsd0R0QjZ3azVid0xueXlUK1V5Ym4xMWdzYnd3QnpqYlcyYXloZ0dFa3puM2RGclNta1BFeFNqbi9aTGhNNHJkQml3bUN2REpFRXJIWklyT3hzUmdFckNJTHB6bUVuRTVnQlVVTWtjbTBpazFuMElLQ0lpOE5rVVJzM3BYWmpLV0xzNENYZ3RZVENNb2p4RGxPSmpOUkp2TFBvT1VFVFY0Yklvbk00ZmNJSitUWk50YS9JSlJLR1NNa3htdEJpd2tMeGhBT1loTVhtNVhBa1pEVDIxZy9RNmtIaHN0a3N5dHhaNHdoZGtKc1BoTWJtd0tHUTA0OU1IRmdGY3FSRXNPV3NwdzFmRWFFY2d0cEdKQmEvZ0tVYWozbkFNMUVleHZyTXlneEtlZVpvSVdFSGROQzdBR1p4U01veDBNa3Q3RytDMXhKakZPTUdWTERHQ0lGeEtaVDZsaEJuS0VrYmtzSys3VGtGcUNlT0lPbFBEdUhCT2NLeGhBdUVKdVBwWTRZQlF4SGVUQm9QVDJnd0JxNitLWk13WllLYzM2dVc4d1lJZzJraGplQkMzUU9JMGkwR01NQ2xnVEM4MEJNcHBockJ6TEJ0QkFaSUxXczQ4c2NFL0J0ck84alRPVGZmTnVZSVhPTUlUSkVKdElodGJSaWNRU1MxVzJzN2NCaWloZ3FVMWdodGdsdjl3SmpDSStRR2o2U0dtTEF0OEgzNDl6WFlqRk1waEtUc2xEYzQ1Y3ptREdFeDhnTVhnRE8wTG1Nd3FMVjQrTGZSRGhicHZLWXgrVWFEUDZqTmlYYXlCbEI2ekNrenY4RFFkN1FyTWJMUjFBQUFBQUFTVVZPUks1Q1lJST0iPgo8bGluayByZWw9ImFwcGxlLXRvdWNoLWljb24iIGhyZWY9ImRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBTVFBQUFERUNBWUFBQURBcG81ckFBQUFCSE5DU1ZRSUNBZ0lmQWhraUFBQUFBbHdTRmx6QUFCYWJnQUFXbTRCeFdzak9BQUFBQmwwUlZoMFUyOW1kSGRoY21VQWQzZDNMbWx1YTNOallYQmxMbTl5WjV2dVBCb0FBQmwwU1VSQlZIaWM3WjE1ZUZUVjNjYy92NXNGQ1ZSeHFWdHB0VlcyVmx4ZjYxcXRpcTBMOUdsOU5YVUJGRXBGUlRJaGhCQURDVGRBTUFFa0NZc0s3cUI5K2tENzJxZkJXaXZxVzdWYTY3NGlyZTlyYXhYMWJaVldaVEhML040LzVnNEdTR0R1ekwxejc1MDVuMzg0ek13NTU4dk0vWEsyM3prSERJR2ppemhQRjNGZTBEb01JRUVMeUdkMFBvT3dhQUF1Y1Y1YWgwVzVWUEI2a0xyeUdXT0lBRkNiL3ZTakVxZ0crdXp3S3dnZHdDMTBVQ2ZWL0RzWWhmbUxNVVFXVVJ1TEVrWUQ4eEVPMnY3R2pvWndQc3hIV014aElFdWxsSzVzNnN4bmpDR3loQzdnSkpSVzRDU2daeFAwbkg0Smk1aVU4N2pmR2czR0VMNmpUUXdFNWdHamtXN2ZkK3FHU0xLV0lzcGtNbS83SU5QZ1lBemhFMnBUUWgrcXNLZ0MrZ0x1VGJCcmVpdXdtSFlhWkRxZmVpcllBQmhEZUk0cVFpTVhBd3NRRHZQQUJEMmxOeUxVczRuYnhTYWV1V3BERW1NSUQ5RWJPUUZvUlRodCs0ditHQ0w1NTdOQXVjUjRLaTNCaGwwd2h2QUFiZUFRTEd4Z0FtRDVhb0x1NmNTZml2QUxsRXFKOFk1TDZZYWRNSWJJQUxVcHBvaHJFV1lqN0wzOWpld2FJcG5lakxLUXZXbVVjV3hMNlI5ZzJBVmppRFRSdVl6Q29nWDRCcEE5RTNSUDkveiszNEdaVXNiS1hxUWJkb014aEV1MGdhTVJtbEhPOXZBQi82T1RPdGtEUXlUU3lpTW81UkxqdFo3K0hZYWVzWUlXRUJYVVpqK2RTeXZ3UE1yWkhoVzdFV0VpbjNFYVV6a1ZvUlQ0bXljbEMrZGc4Ykl1WmFYZXdvR2VsSmtIbUJaaUQ2aE5JVVdNUjJrQUR2Q29WZGlLc0pqNHJ1c0p1cHdTUHFNS3FFS2M5WXZleWttOXprMUFFd2ZTTEtXMFkrZ1ZZNGpkb1BXY2cwVUx3bEhiWDh6Y0VHc1J5bVRhN2xlY3RZbUJGRGtyM095d3hwMk9JWkxwRFFoVDVUb2UyRjNkK1l3eFJBL29IQVpCdDdCc2IxcUZGeW1nWENyZHhTVHBJczRFV2hDTzNhWE05TFdzbzRDWVhNTWJiclRrQThZUTNWQ2IvZ2lWeUU1aDJaa1o0aU9FT1J5ZWZ0U3EybGdNWURUS2ZPQWdqM1Ixb054Q0lYVXkwWVNaSnpHR3dIbmdMT2VCNnlrc083MEhML0hBaVhmN0duUVovV252d2JEcGF3U2NNUFA5VEpnNUdFT2dzMU1JeTNiL3NLMGpUa3hxL09tU2FDdURVQnFRN1R2dHZPald2WWhRTHRma2Q1aDUzaHBDYlJkaDJhay9WQnRRS3FTRzMzZ3F0aGUwbFhPQUZ1QW9EOWRFMWxKQW1mdzBQOFBNODg0UWFsTUN6clJtcW1IWmUzNlFOaUUwOFRuTlltZDNXbE50Q3RtWDhZZ3pMZHk3UmpmcFJKaDVFUTN5ay93S004OGJReWdJZFZ5TXhRTFVaVmgyNys5M290eUpNRk5xK0lmWG10MmdpOWlQUW1ZQjF5RVVibjhqTTNPOGgxTERSRmFKb0o0S0RpbDVZUWl0NVFRa2c3RHNubDk3Rkl0eXFlWlZUOFZtaUM1aEtFb3o0aHhyNDAxWDZsbVVtRnpEMHg1S0RTVTViUWl0NFJDS3NORU13N0ozZk8wdGxCcVp5UnFQNVhxS0xtWVVRZ3ZpQkI5Q3B1WlE0RjQ2cUpKSmZPQ3AyQkNSazRaUW0ySTZQUXpMVHZ5WkNLL2VoeHVsak04OWx1d0xhbFBNL3M3M0FIdDcxRnBzUmxoSVlXNkdtZWVjSVhTRzUySFpjWVQ3RUtiSkRENzBWbTEyME9VY1FnYzI0clNVNE1YMzhoWkNqVXdJZDB2cGxwd3hoTjdBVUFwb0JzN3pjQXJ5R1pTWTFQR01oMUlEUTI5MnRyZ3FwM24ySFZrOFNwd3A4bE5lOFZCcVlFVGVFR3F6SHgzTVFyZ09uTm1Wekgvc2Q0RVoxT2JlN0lvcXdpM09JUWh3R09ERjk5V0pPTE50NDRPZGJjdVV5QnBDYlFycFlEeDRHcGE5QlZqQWwyaVNDclo2S0RkMDZISks2S1FLU1NITVBOVzB4U2JpTkxGM2RNUE1JMmtJclhGV2FMMEx5MWJnRnhRd1RXWjZ0RUVuSXVqaUZNTE0zYVlUWWVZVk1pNDdLL1plRWlsRDZFd0dFZmM4TFBzNUxNcWxqajk0cHpSNjZNMmM2VXhHN0JwbTdqYjl4V3ZyS0tSTXhyTGVPNlgrRWdsRHFPMUVlU3JWQ0gyMnY1R1pJVFlDOVdBTyswcWlOaFlITzRjeGR3OHpoMHpXYlJKUnZ4M1JDRE1QdFNIVXhtSXJvN0djSHdpOGFCWGFnVnNwWWFZNURySm5kQm45c2FqRWNzTE1JUk5ESkVtRW1mY05kNWg1YUEyaE4zUUx5L1p1R25VdFFreHMvdGREcVRtTDNzb2d4T21pWm02SVpQb0ZsSElaeHhOZWF2V0swQmxDcXhpSTFXMlFCMTYwQ2kraGxNc2NmdStwMkR4QlZ6RENpWTlLVEdKa1pvaGtlaTF4SnNzNC91cWwxa3dKalNIVXBvUXR6alFnOVBXb1ZVaHMzMXpQVWxrVDNtWTZDcWhOSVY5eFRoOFJKOHdjTWpFRUpLYTVsOUNYdVZMS1o5NHFUby9BRGFFZ1RPTml4RmtvY3YrbDlwUk9YRXZWVHAwMGhYOGdGeVgwZHZaRG1RVk1BZ295TkVReS9SNUNEYU9EWHdnTjFCQTZsUk93ZWduTFR0OFE2eERLWlk2NXVOQlA5RGFHQVl1UWJyZW5adjdiL1FtTG1JemVmcEpoMWduRUVGcnVoR1h2N3JSczkxL3FCdUpNbFhubXpLRnNvcmN6Q3B4Z1NtOWFkMFdjTVBOeDJROHp6Nm9obk83Uk5KUmFoUDQ5cW5EL3BYNE0xRlBFeldMVDZhMWlReXJvWGV4Rm5BcmdCcUMvUitPL1Q3Q1lReEdMc3hrR2t2VVdRaXM0emprTjc0d2VWYVJ1aUVSWWRoR1ZZdk4vZm1nMXVFT1hjd2lGUG9XWlg1R2RNUFBBeGhCYXlTaUVWdURycmcyaFBJb3dSZWJsUnNoeHJxRjNjd0pkenRqUXV6V2tSMUhLWmJTL1czYURIVlRiRkxPWmF4SG1BRi9hUVZIUFg4eGJXTlJJUTI1dFNzbEZWQkh1OWlITUhPNmtpSmxTNmsrWWVlRFRyZ0JheGFIQUxHQUMwbU5UbTlpMldFeWoyTG0zYlRHWDBlV1VVSnppYWVhcHB6ZWgxSE1veStRc2I4ZU5vVEJFRXEzaVA1eHUxS21Pc3NURzlpNnFaRUh1Ym16UEIvUTJCbExNUE5URE1ITjRFNHNLdVpRSHZkSVpLa09BTXhNMW5URUlGMk5oeXp4ZUNGcVR3VHYwTHI3cmhKa2ZzLzNGek0xeFAzRXE1WXJNWTlSQ1p3aEQ3cU0yRm9jN1llYVNRWmo1anVrT0xHNmhnMW9aelNmcGFqT0dNQVNHcnFZL1c3dWRaZzZadHhiQyt5ZzJGbmVrRTJadURHRUlITDJQUVhTbEVXYSt1elE4ajBXNWxQS2tHeTNHRUliUW9QZHdQc0lpaEtGQXBvYUF4S1gyRStUSDNKbXFCdGUza0dvNUU3U2NIN3JOWnpEc0NibVNCOW5LMGNCRTRKOWVGQWtjNmlhRCsydDVsU05SN3RjWTYvUjZ4OGtHZzBmSVJEcGtEQ3Zvd3hCZ01XUjNIMHM2aGloeVV1ZFF3Q3NhbzFXdjZ4YW9aekI0Z0pUeXNZd21ScHpod0crelZhOTdRMWpiRFFGUUJKUlJ4SHFOTWRZelZRYURnNHhsdll6bWZJUWZnUDk3NFROcElib3pFTGhIeTNqWWRLTU1maUNYMDBZUnc0QnlTSCtkWVUrNE53UTlHaUtCTUlJQ1h0WXBOS3JOWHVuTE1oaDJSVXBwbHl0b1JSZ0cvcHlsNWEwaEVoU2pUT2Rmdks0eFJxWWp5bURZSFhJWkc4R2Z2ZGQrR0NMSk54RGFkQXB0V3NuWDA2akhZTWc2WG8waGRzZEl1bmhkSzdCTk44b1FkaktkWlVxVnZpaXorSVRYdEpJTDA4aHZNR1NGYkxRUTNUbUNPR3UxZ2pZdDUvQU15akVZZk1IUE1jVHVHRWtCYjJpbDZVWVp3a1ZRaG9Ca04rcFRYdFZwWE9CUm1RWkRSZ1JwaUNSSG9qeWdVMm5UYXRPTk1nUkx0c2NRdlNPTXBKUFh0UXBiSjNlN0ZNVmd5Q0x1RFNFK0dTSkJDY29zK3ZDYVR1dDJacWpCa0NYQzBHWGFGZUZJNEVHdG9rMm5PbWY2R0F4WklKeUcrSUxFYkpUcFJobXlSTmdOQVZBQ3pLS0VWM1U2Mzg5eTNZWThJd3FHU0RJSStLMVcwNmJUK1ZwQUdndzVUcFFNa1VBWmliQmVxN0hWcGpoUUxZYWNJM3FHU0pEb1JtM2pWYTNtZTBHTE1lUU9VVFZFa3NFSUQra050T2tNdmhxMEdFUDBpYm9oa293a3pucXRNZDBvUTJia2lpRUErZ0d6YU9jVnJlSGNvTVVZb29rclEyamk0S2RDbjdSNHhSRGdkenJEZEtNTTduSFhRbHdkZWpOMFp5U3dYbWVhYmxSZUUzZDNHSUhiTGxOWXUwdTkwUTlsRmwyOHJETVpFYlFZUTFiWmpGTFBOaGE1eWVUT0VQdEd6aEFKbEtIQTczUW1LN1dHZzRLV1kvQVZCVmJSd1NDNURGdkd1YnVDelowaE9pTnFpQVFDaktHQURUcVRtTnFSNnY0WlVrSDVFeGFueWFXTWxURzhuMDRSN2d6UkZXbERKTmtIb1lVdW50TmFUZ3Rhak1FVDNrVzRra3M1V1VwNU9wT0MzQm5DcjgxQndYQU13aE5hWjdwUmtVWFpBdFJUd0dDNWxKVWltUjllNXM0UThad3lCQ1M3VVlXOHFYWEU5QklLZ2haa1NCRmhGVVVNbHN1d3BaU3QzaFhyQXIyZW9SU3czbFdwMFVxL2hEQkpiSjdDa0plNGF5R0tjcTZGMkpsamdTZlZacVhhSEJpMEdFUDJ5ZWN4Uk0rbzA0MkNEVnBQVEZlYmJsUStZUXpST3dOUVdsalBzenFiVTRJV1k4Z091YjVTN1FYSG9meEI2MW1wOC9oeTBHSU0vbUphaU5SSWRLTTYyYUJ6VERjcWwzRm5DTW43SUxsOVVWcll3SjkwTGljSExjYmdQYWJMbEE3SzhTaFA2UnpUamNvMVRKY3BmUkxkcUM3VGpjb2xUQXVST2ZzaXRQQVhudEhabkJTMEdFTm11RE5FZXJjSDVRZktDVmc4cFhOWnFUWUhCQzNIa0I2bWhmQVdDMkVNUld6UUJtSnFwN1ZuUFZSb013T0MxcEFKdXBxRDNYemU3USsyNXowRXJxS2pjcGI5Z0JhS2VWSWJPU1pvTWVtZ1N6aFVsN0tjWXVZRXJTVWRkQlZEOUdjOFFBZFQzZVJ6TzZoMk4rMXF6SEVLU2xuUUl0eWdOc1c2aEJnV2J5SmNUWG9uc3dTRzNzZStlaCtOV0x3QzdtK21jcmRyVENsSyt5SGZNZDltNERHZ2pTNmVvSWpIMEJ6ZGt4Q25QV2dKcWFMTEdJWFNDdEc3VjF4dExJNWdOTEFBMGcvTWRMdU5NcE14eE5zb0R3TnIyWWVIeFA3aVFkRktLb0Q3TWlnN3ZBZ2RRVXZZRTdxTVk0blRnbkptMEZyU1FlL2pMT0swSUJ5ZGFWbnVER0ZSNUdKUFVoZUovUVZyRWRya0pwN3Y3WU95a0o5cEpaZEREdDVoTGVGdElYUXArd04xeEprRTBWdEgwWlY4RFl1NUtHTzg2cDY3N3pMdG5zMkkweFVxNU5leWdBOWNLTG1lTHI1TDRnUyszRUhEMTBMb2NvcG81enJpMUNQc0U3UWV0K2hLK2lGTUE2YWo3T1hsV05XTEx0UGJDQThqdTNhRjNDQ04vRldyYUVDWmwzZ2huVkpDU01nTW9Zc1p3ZWUwQU44S1dvdGJWQkh1WlF4S0U3aWJUazJWZEF6eFJWY29UcHNzN3IwcjVKb1NGckNaVWhJNzEzSURLeHlHMEtVTXBvdEZLQmRHOFQ4YnZZc1RXVVVyK0xzM3haMGh1cmliSXVaTE14LzdJVVpzT3JXUzZ4Q2VwUHQwWDIvN29LTkJvR01JYldZQVFqVmRUSUhvUlN2clBYd0Z1QkVZVFJaK2ZWZUdrS1c4NlplUTdYVXM1R21keG5MZ1dyL3J5Z29CZFpuVXhtSkE1dE9RUWFHcjZjdFd5b0Fad0pleVZXODRGMTIyVVkzdzNoNC9GNFhXSW9CWkpyMkpzOWlIRjRCN2lLSVo3bUlVVzNnRHBaRXNtZ0ZDYWdoWndpZEFoYnRNL21qSm1DeTJFSG9UWDlWbVZtTHhLRVF2WkVUdjVIaTlpOGNSZmcwY0hvU0cwSjV2S2syczFtbU1SaGpsUHJNUGd0SWxDNGJRQmZURFlockNkR0F2dit2ekdyMkgvZW1pRGdsK1BTUzBoZ0RBNG5xVXM0RCthWmNSdkRsODZ6S3BJaXppWW9TRkVMMnJpblU1UlJSekhWM2hXUThKWlpjcGlUVHhEa0s5ZHdWNlZsTHErRFR0cWdzNWtVVThDYXdtaW1hNGt4RVU4UkpLQzRURERKQmxRNmhOb2JwOUxQdlNBcnpvdVpqc21HTVR5cXRlRnFoTkhLb0xXWTd3UitCVUw4dk9Cbm9IUS9SMkhuRGkycjRadEo2ZDhkMFFPcGsrT29VUldrRXJuL0F1VTdqS1RYNng2VVM1bXNTQ29ELzRZUTdsQXl6T2tocmU4S3pJaFZ4RUFYK0c2SVZsQStqdGpBZGVROXlIWldjTFg3NVVuVUpmbmNJb25jSktDdmtRZUJnb0F3NUNhTllxQnJvcFQrYnpISEN6SDFwM3JjeVRVdDZtZ085SU5TOTdVdG9YSEVPMFk3MkdFZkp4cTJmaXRKd0JLT2NpakVMNUVkQy9sNGRySDdxNEJWek9IaWt6RUg0RTdzeVVFZW1aNHpVSytiNU1aNlBIYWd4WklLTVdRaWV4djhZWXF6SGFVRDRrTWNBYnc1NW5oVVpxQlQ5MlU1Zk01MU1rOUx2UEhxZUEwNDBab290clEraGtCbW9aVjJ1TU5ncDVuOFJxNkVqY3hza0lTN1hTM1NxcU5ISS95cTljMWVNVmUyNHQxbExDZVZMTnY3T2d4dUFUN2k1dUwrY2lMTjVCV0U3Q0JKbnNvRHVBT00ydWN5bVRJT0NIYm1kektQZnlaUzZTQ3U5dXNqRUVnOXNydGZyaDVaeU1jTGxPNVlldXNzeG5JOG9zenpSa2lzVVN1cmhTSm9ZanpOdVFHY0ZQM1FrM2F6WDd1c3JUbHlYQUgvMFJsRElLMU1zTXlzUW1IckFXZzBjRWJ3ZzRoRTZhM0dRUW16Z1dFeUd3LzVXN1VLNlJXdXlBNmpmNFJCZ01BVEJCcC9FOU54bGtIcThBUzN6U3N6dmFFUzZUT2xZRVVMZkJaOEppQ0NIT2NyVmRCdkgxb1JaNDJ4OUpQYklaWVpUTVpFMFc2elJra2JBWUFvVEQyY3hjVjFsc3RqZ2h3OW5nWXl4R1NDMi95MUo5aGdBSWp5RVNUTmJwbk80bWc4empRZUFYUHVsSjhnN0NxVEl6OElHOHdXZkNaZ2lMT0xlcjdYS1RTeWVUZ1gvNUk0bjFGSEM2MUxMQnAvSU5JU0pzaGdBWXdtWnEzV1J3RGtTYjZZT1c1eWppVEpuSjM5TXRRQnM0eWt0QkJuOEpveUVBcXJTYUUxemxLT1lXNENrUE5UekdYcHd0TmZ3ajNRTDBScVlqVkhxb3llQXpZVFZFSVhIdTBLdFREdzBSbXpnRlhJTTNheE8vQWk2UTZYeWFiZ0hhd0d6bjFBaERoQWlySVFDT1lRQlZiakxJSEY1RjBvaVAycEc3Z1V2RVpsczZtVlVSYmFBRmNkZnRNNFNETUJzQ29GYW51enlEZEFzMjhEOXAxU1kwaWMwNHNlbE1KN3V1cG9CNTNBN0UwcXJmRURoaE4wUWY0QTY5SlBXalNhU1pyVTVFYk9vSWlsSXBOdFZ1QlNiUjFSVHdaKzRDeHFkYmhpRjR3bTRJVUU3aTYweDJrMFhtOFJEQ3oxUDhlQmN3UVdaemszdHhDZFNtbUQrekJtRk11bVVZd2tINERRRWdOR2cxUjdyS0U2Y2MyTFNIVDMxT1lyeHdaN3JTMUthRVF0cUFINlZiaGlFOFJNTVFVSUp5bTVzamJHUWVIOEp1dTBEL3dtS0V6T2IrZEVYcEF2bzVabkFWbUdnSUwxRXhCTUIzcWVhbnJuSTBjQnZ3WkEvdkpJNklzWHQ4THlYVVpnRGJXQWVjblc0Wmh2QVJKVU9Bc2tCbjhOVlVQeTZKVFR3VFNIU05raVNPaUxGNUtXMFpOZ2RTd0g4REo2ZGJoaUdjUk1zUXNEZGQzT29tZzh4bEE3RFErZXZyeFBtTzJMeVZyZ0MxT1pnQ0hpR0NwMnNiOWt6VURBRndnVlp6aGFzY2hjeEZ1WnNDVHBlR0ZPNmQ2QVdkeTJGWVBBRW1QaWxYQ2ZVcGFydWhWV3RZNXd5Yzk0aXo2and1a3dyVlpqQngxa0hxWFRaRDlJaGlDd0d3UDhyaWJGV21jeGlHeFdNWU0rUThVVFVFS0tWNkF4ZjVYbzNOOGNSNUhEalU3N29Nd1JOZFF5UllwamI3K1ZXNHp1WkVoSWVCQS95cXd4QXVvbTZJZzltV2ZzakY3dERabklueUNQaG5PRVA0aUxvaFFMaEtxem5QeXlLMW52T0o4eUJadmdIVEVEelJOd1NBc0Z5cnZIbDQxZVlIS1BjRGZiMG96eEF0Y3NNUThEVUt1VEhUUXJTT3k0QmZrZ2c3TitRaHVXSUlnR3QxQm1la20xbHRya2E0bCtpdXpSZzhJSmNNWWFIY3BsUGNkM1cwamtuQXJlVFc5MkZJZzF4N0FBWlQ0dTRBWXExak9zSlNOQVEzV2hzQ0o5Y01BY3BVcmVYRWxENDZpM293SjJQa01HK2cvTkpOQm5lRzZPSnhKUFJubXhZUTV3NjFlNy9pUzBGMEZxMG9kZGtVWnNnYW00QnFDamxPUnJzN2Z0VFZBRktXOFRmZyt6cUZFY1JwQVpjblltU1A0YlJ6QTFDLzh4dHFZOUhKQ3BTZkJLREw0Qzl4NEQ2S21DcWw2UjB3bDFhWFNacFp4MWFPUXlrbjZQdmVla09vMFZxR2QzOUpMNkdBVHU1RWpCbHlEdUZSTEk2VEt4aWJyaGtnZ3pHRXJLQkRGdE5LSjBjQWkwbWNYaEVtaW9semo5cUpWbEJ0aWhuS2FvUXJneFptOEpTM0VFcmxDczZSeTNrbDA4SXlIbFRMTWo2U1ZtSUl3NEdITWkzUFk0NmpnM0tkVEI4NldRUCtSOGNhc3NabW9KNU9oc3NWM2wxZzQ5a2lsTFN3SGpoUFkxeUNNQjg0M0t1eU0wS1l6VDVjQkp3U3RCU0RKeWh3TDUxVXlUZys4THB3ejZkZHBaVTFER0FJUWpud2lkZmxwMEZmakJseWhXZFJUcE94alBYRERPRFRPb1RZdEVzenJSUXhERmdCNXRwYVEwYThoM0lsWXpoSnJ1UnBQeXZ5ZFdGTzVyTlJtcGtJbkFUOHdjKzZERG5KVnFDSnZneVZxMWdwZ3ZwZFlWWUMyYVNaNXhTK1F3VVhBd3VBdzdKUnJ5SFNyRVdaTEZmeDEyeFdtclhRRFFHVlJhemhNNzZKVUUvQy9RYkR6cnlBY29aY3hTZ1psMTB6UUFDeFRMS0NMYklRbXdJR0E2dkEvMmJRRUFrK0FzcnB4N2RsSEU4RUpTS3c0RDZaejd0eUUyT0JVeEJ6M1cwZTB3RXNwcDBqWkJ5dFVocnNBbS9nMGE2eWtHZm94Mm5PQ25KS0I0OFpjb1oxd0xFeW5waE1ERWNJVU9DR2dNU0ZpYktBbFNoSE9PT0x6L2VZeVJCbE5tQnhvWXpuWEJuUEcwR0w2VTRvREpGRUZySlo1bU5Ud0ZHSWQ4dnhodER3TWNway9zNVJNbzdmQkMybUowSzVmMWdhZVFzbzFXck9kc0xNaCs4cGp5SDh5SVRNRDRMd20xQzFFRHNqalR4S0NjY2pUSVQwUTNvTmhsUUp0U0VBeEtaVEdsbUJNQVNsQ1dnUFdwTWhkd205SVpKSUk1dGtQdFhFT1JyQzJmODBSSi9JR0NLSnpHZUROSEloY0M2RWE0YkNFSDBpWjRnazBzZzZQdUpZSjh3OEZIUFlodWdUV1VPQXM0MTFIcTNBRWM0RkttSGJ4bXFJR0pFMlJCSzVrWS9rUm1MRU9SRjRQR2c5aHVpU0U0WklJbzI4S1BNNEUvZ0I4SGJRZWd6Ukk2Y01rVVFhYUdNTDMwS3BCajROV284aE91U2tJUUNrbWEweWp5WUtHWXFhYmF5RzFNaFpReVFSbTQzU3dFU1VreENlQ2xwUHhQZ3Y0dHN2dmM4TGN0NFFTYVNCNTVqTjZRaWx3RHRCNndrNWJ3TG55eVQrVXlibjExZ3Nid3dCempiVzJheWhnR0Vrem4zZEZyU21rUEV4U2puL1pMaE00cmRCaXdtQ3ZESkVFckhaSXJPeHNSZ0VyQ0lMcHptRW5FNWdCVVVNa2NtMGlrMW4wSUtDSWk4TmtVUnMzcFhaaktXTHM0Q1hndFlUQ01vanhEbE9Kak5SSnZMUG9PVUVUVjRiSW9uTTRmY0lKK1RaTnRhL0lKUktHU01reG10Qml3a0x4aEFPWWhNWG01WEFrWkRUMjFnL1E2a0hoc3Rrc3l0eFo0d2hka0pzUGhNYm13S0dRMDQ5TUhGZ0ZjcVJFc09Xc3B3MWZFYUVjZ3RwR0pCYS9nS1VhajNuQU0xRWV4dnJNeWd4S2VlWm9JV0VIZE5DN0FHWnhTTW94ME1rdDdHK0MxeEpqRk9NR1ZMREdDSUZ4S1pUNmxoQm5LRWtia3NLKzdUa0ZxQ2VPSU9sUER1SEJPY0t4aEF1RUp1UHBZNFlCUXhIZVRCb1BUMmd3QnE2K0taTXdaWUtjMzZ1Vzh3WUlnMmtoamVCQzNRT0kwaTBHTU1DbGdUQzgwQk1wcGhyQnpMQnRCQVpJTFdzNDhzY0UvQnRyTzhqVE9UZmZOdVlJWE9NSVRKRUp0SWh0YlJpY1FTUzFXMnM3Y0JpaWhncVUxZ2h0Z2x2OXdKakNJK1FHajZTR21MQXQ4SDM0OXpYWWpGTXBoS1RzbERjNDVjem1ER0V4OGdNWGdETzBMbU13cUxWNCtMZlJEaGJwdktZeCtVYURQNmpOaVhheUJsQjZ6Q2t6djhEUWQ3UXJNYkxSMUFBQUFBQVNVVk9SSzVDWUlJPSI+Cgo8c3R5bGU+CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBub3JtYWw7CiAgZm9udC13ZWlnaHQ6IDQwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8nKSwgbG9jYWwoJ1JvYm90by1SZWd1bGFyJyksIHVybCgvZm9udC1yb2JvdG8vdVlFQ01Lb0hjTzl4MXdkbWJ5SEltMy1fa2Y2QnlZTzZDTFlkQjRIUUUtWS53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDQwMC0wNDVGLCBVKzA0OTAtMDQ5MSwgVSswNEIwLTA0QjEsIFUrMjExNjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvJyksIGxvY2FsKCdSb2JvdG8tUmVndWxhcicpLCB1cmwoL2ZvbnQtcm9ib3RvL3NUZGFBNmowUHNiOTIwVmp2LW1yekgtX2tmNkJ5WU82Q0xZZEI0SFFFLVkud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzA0NjAtMDUyRiwgVSsyMEI0LCBVKzJERTAtMkRGRiwgVStBNjQwLUE2OUY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNDAwOwogIHNyYzogbG9jYWwoJ1JvYm90bycpLCBsb2NhbCgnUm9ib3RvLVJlZ3VsYXInKSwgdXJsKC9mb250LXJvYm90by9fVllGeC1zODI0a1hxX1VsMkJIcVlILV9rZjZCeVlPNkNMWWRCNEhRRS1ZLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMzcwLTAzRkY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNDAwOwogIHNyYzogbG9jYWwoJ1JvYm90bycpLCBsb2NhbCgnUm9ib3RvLVJlZ3VsYXInKSwgdXJsKC9mb250LXJvYm90by90bmo0U0I2RE5iZGFRbnNNOENGcUJYLV9rZjZCeVlPNkNMWWRCNEhRRS1ZLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSsxRjAwLTFGRkY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNDAwOwogIHNyYzogbG9jYWwoJ1JvYm90bycpLCBsb2NhbCgnUm9ib3RvLVJlZ3VsYXInKSwgdXJsKC9mb250LXJvYm90by9vTU1nZlpNUXRoT3J5UW85bjIyZGN1dnZEaW4xcEs4YUt0ZUxwZVo1YzBBLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMDAwLTAwRkYsIFUrMDEzMSwgVSswMTUyLTAxNTMsIFUrMDJDNiwgVSswMkRBLCBVKzAyREMsIFUrMjAwMC0yMDZGLCBVKzIwNzQsIFUrMjBBQywgVSsyMjEyLCBVKzIyMTU7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNDAwOwogIHNyYzogbG9jYWwoJ1JvYm90bycpLCBsb2NhbCgnUm9ib3RvLVJlZ3VsYXInKSwgdXJsKC9mb250LXJvYm90by9Lc19jVnhpQ2l3VVdWc0ZXRkEzQmpuLV9rZjZCeVlPNkNMWWRCNEhRRS1ZLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMTAwLTAyNEYsIFUrMUUwMC0xRUZGLCBVKzIwQTAtMjBBQiwgVSsyMEFELTIwQ0YsIFUrMkM2MC0yQzdGLCBVK0E3MjAtQTdGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvJyksIGxvY2FsKCdSb2JvdG8tUmVndWxhcicpLCB1cmwoL2ZvbnQtcm9ib3RvL05KNHZ4bGdXd1diRXN2MThkQWhxbm4tX2tmNkJ5WU82Q0xZZEI0SFFFLVkud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAxMDItMDEwMywgVSsxRUEwLTFFRjksIFUrMjBBQjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA3MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIEJvbGQnKSwgbG9jYWwoJ1JvYm90by1Cb2xkJyksIHVybCgvZm9udC1yb2JvdG8vaXNaLXdiQ1hOS0FibmpvNl9Ud0hUb1gwaFZnelpRVWZSRHVaclB2SDNEOC53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDQwMC0wNDVGLCBVKzA0OTAtMDQ5MSwgVSswNEIwLTA0QjEsIFUrMjExNjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA3MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIEJvbGQnKSwgbG9jYWwoJ1JvYm90by1Cb2xkJyksIHVybCgvZm9udC1yb2JvdG8vNzdGWEZqUmJHek40YUNyU0ZobGgzb1gwaFZnelpRVWZSRHVaclB2SDNEOC53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDQ2MC0wNTJGLCBVKzIwQjQsIFUrMkRFMC0yREZGLCBVK0E2NDAtQTY5RjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA3MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIEJvbGQnKSwgbG9jYWwoJ1JvYm90by1Cb2xkJyksIHVybCgvZm9udC1yb2JvdG8valNOMkNHVkRiY1Z5Q25mSmZqU2RmSVgwaFZnelpRVWZSRHVaclB2SDNEOC53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDM3MC0wM0ZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBub3JtYWw7CiAgZm9udC13ZWlnaHQ6IDcwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gQm9sZCcpLCBsb2NhbCgnUm9ib3RvLUJvbGQnKSwgdXJsKC9mb250LXJvYm90by9VWDZpNEp4UURtM2ZWVGMxQ1B1d3FvWDBoVmd6WlFVZlJEdVpyUHZIM0Q4LndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSsxRjAwLTFGRkY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBCb2xkJyksIGxvY2FsKCdSb2JvdG8tQm9sZCcpLCB1cmwoL2ZvbnQtcm9ib3RvL2QtNklZcGxPRm9jQ2FjS3p4d1hTT0pCdzF4VTFyS3B0SmpfMGphbnM5MjAud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAwMDAtMDBGRiwgVSswMTMxLCBVKzAxNTItMDE1MywgVSswMkM2LCBVKzAyREEsIFUrMDJEQywgVSsyMDAwLTIwNkYsIFUrMjA3NCwgVSsyMEFDLCBVKzIyMTIsIFUrMjIxNTsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA3MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIEJvbGQnKSwgbG9jYWwoJ1JvYm90by1Cb2xkJyksIHVybCgvZm9udC1yb2JvdG8vOTd1YWh4aXFaUm9uY0JhQ0VJM2FXNFgwaFZnelpRVWZSRHVaclB2SDNEOC53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDEwMC0wMjRGLCBVKzFFMDAtMUVGRiwgVSsyMEEwLTIwQUIsIFUrMjBBRC0yMENGLCBVKzJDNjAtMkM3RiwgVStBNzIwLUE3RkY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBCb2xkJyksIGxvY2FsKCdSb2JvdG8tQm9sZCcpLCB1cmwoL2ZvbnQtcm9ib3RvL1B3WmMtWWJJTDQxNHdCOXJCMUlBUFlYMGhWZ3paUVVmUkR1WnJQdkgzRDgud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAxMDItMDEwMywgVSsxRUEwLTFFRjksIFUrMjBBQjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogaXRhbGljOwogIGZvbnQtd2VpZ2h0OiA3MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIEJvbGQgSXRhbGljJyksIGxvY2FsKCdSb2JvdG8tQm9sZEl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvL3Q2TmQ0Y2ZQUmhaUDQ0UTVRQWpjQzE0c1lZZEpnNWRVMnF6SkVWU3V0YTAud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzA0MDAtMDQ1RiwgVSswNDkwLTA0OTEsIFUrMDRCMC0wNEIxLCBVKzIxMTY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKICBmb250LXdlaWdodDogNzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBCb2xkIEl0YWxpYycpLCBsb2NhbCgnUm9ib3RvLUJvbGRJdGFsaWMnKSwgdXJsKC9mb250LXJvYm90by90Nk5kNGNmUFJoWlA0NFE1UUFqY0NfWnJhUjJUZzh3Mmx6bTdrTE5MMC13LndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswNDYwLTA1MkYsIFUrMjBCNCwgVSsyREUwLTJERkYsIFUrQTY0MC1BNjlGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDcwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gQm9sZCBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1Cb2xkSXRhbGljJyksIHVybCgvZm9udC1yb2JvdG8vdDZOZDRjZlBSaFpQNDRRNVFBamNDd3RfUm02OTFMVGViS2ZZMlprS1NtSS53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDM3MC0wM0ZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDcwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gQm9sZCBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1Cb2xkSXRhbGljJyksIHVybCgvZm9udC1yb2JvdG8vdDZOZDRjZlBSaFpQNDRRNVFBamNDMUJXMjZReHBTai1fWkttX3hUNGhXdy53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMUYwMC0xRkZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDcwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gQm9sZCBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1Cb2xkSXRhbGljJyksIHVybCgvZm9udC1yb2JvdG8vdDZOZDRjZlBSaFpQNDRRNVFBamNDNGdwOVE4Z2JZcmhxR2xSYXZfSVhmay53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDAwMC0wMEZGLCBVKzAxMzEsIFUrMDE1Mi0wMTUzLCBVKzAyQzYsIFUrMDJEQSwgVSswMkRDLCBVKzIwMDAtMjA2RiwgVSsyMDc0LCBVKzIwQUMsIFUrMjIxMiwgVSsyMjE1Owp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDcwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gQm9sZCBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1Cb2xkSXRhbGljJyksIHVybCgvZm9udC1yb2JvdG8vdDZOZDRjZlBSaFpQNDRRNVFBamNDNkU4a000eFdSMV8xYllVUlJvalJHYy53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDEwMC0wMjRGLCBVKzFFMDAtMUVGRiwgVSsyMEEwLTIwQUIsIFUrMjBBRC0yMENGLCBVKzJDNjAtMkM3RiwgVStBNzIwLUE3RkY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKICBmb250LXdlaWdodDogNzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBCb2xkIEl0YWxpYycpLCBsb2NhbCgnUm9ib3RvLUJvbGRJdGFsaWMnKSwgdXJsKC9mb250LXJvYm90by90Nk5kNGNmUFJoWlA0NFE1UUFqY0M5RGlOc1I1YS05T2VfSXZwdThYV2xZLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMTAyLTAxMDMsIFUrMUVBMC0xRUY5LCBVKzIwQUI7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKICBmb250LXdlaWdodDogNDAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1JdGFsaWMnKSwgdXJsKC9mb250LXJvYm90by9PcFhVcVRvMFVnUVFoR2pfU0ZkTFdCa0F6NHJZbjQ3WnkycnZpZ1dRZjZ3LndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswNDAwLTA0NUYsIFUrMDQ5MC0wNDkxLCBVKzA0QjAtMDRCMSwgVSsyMTE2Owp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDQwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gSXRhbGljJyksIGxvY2FsKCdSb2JvdG8tSXRhbGljJyksIHVybCgvZm9udC1yb2JvdG8vV3hyWEphMEMzS2R0QzdsTWFmRzRkUmtBejRyWW40N1p5MnJ2aWdXUWY2dy53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDQ2MC0wNTJGLCBVKzIwQjQsIFUrMkRFMC0yREZGLCBVK0E2NDAtQTY5RjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogaXRhbGljOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIEl0YWxpYycpLCBsb2NhbCgnUm9ib3RvLUl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvL2NES2hSYVhuUVRPVmJhb3h3ZE9yOXhrQXo0clluNDdaeTJydmlnV1FmNncud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAzNzAtMDNGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogaXRhbGljOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIEl0YWxpYycpLCBsb2NhbCgnUm9ib3RvLUl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvLzFoWmYwMlBPQU5oMzJrMlZrZ0VvVUJrQXo0clluNDdaeTJydmlnV1FmNncud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzFGMDAtMUZGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogaXRhbGljOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIEl0YWxpYycpLCBsb2NhbCgnUm9ib3RvLUl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvL3ZQY3luU0wwcUhxXzZkWDdsS1ZCeVhZaGpiU3B2YzQ3ZWU2eFJfODBIbncud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAwMDAtMDBGRiwgVSswMTMxLCBVKzAxNTItMDE1MywgVSswMkM2LCBVKzAyREEsIFUrMDJEQywgVSsyMDAwLTIwNkYsIFUrMjA3NCwgVSsyMEFDLCBVKzIyMTIsIFUrMjIxNTsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogaXRhbGljOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIEl0YWxpYycpLCBsb2NhbCgnUm9ib3RvLUl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvL3ZTenVsZktTSzBMTGpqZmVheGNSRWhrQXo0clluNDdaeTJydmlnV1FmNncud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAxMDAtMDI0RiwgVSsxRTAwLTFFRkYsIFUrMjBBMC0yMEFCLCBVKzIwQUQtMjBDRiwgVSsyQzYwLTJDN0YsIFUrQTcyMC1BN0ZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDQwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gSXRhbGljJyksIGxvY2FsKCdSb2JvdG8tSXRhbGljJyksIHVybCgvZm9udC1yb2JvdG8vSzIzY3hXVlRySUZENkRKc0VWaTA3UmtBejRyWW40N1p5MnJ2aWdXUWY2dy53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDEwMi0wMTAzLCBVKzFFQTAtMUVGOSwgVSsyMEFCOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBub3JtYWw7CiAgZm9udC13ZWlnaHQ6IDMwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTGlnaHQnKSwgbG9jYWwoJ1JvYm90by1MaWdodCcpLCB1cmwoL2ZvbnQtcm9ib3RvL0ZsNHkwUWRPeHl5VEhFR01YWDhrY1lYMGhWZ3paUVVmUkR1WnJQdkgzRDgud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzA0MDAtMDQ1RiwgVSswNDkwLTA0OTEsIFUrMDRCMC0wNEIxLCBVKzIxMTY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogMzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBMaWdodCcpLCBsb2NhbCgnUm9ib3RvLUxpZ2h0JyksIHVybCgvZm9udC1yb2JvdG8vMGVDNmZsMDZsdVhFWVdwQlNKdlhDSVgwaFZnelpRVWZSRHVaclB2SDNEOC53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDQ2MC0wNTJGLCBVKzIwQjQsIFUrMkRFMC0yREZGLCBVK0E2NDAtQTY5RjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiAzMDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIExpZ2h0JyksIGxvY2FsKCdSb2JvdG8tTGlnaHQnKSwgdXJsKC9mb250LXJvYm90by9JM1Mxd3NnU2c5WUN1clY2UFVrVE9ZWDBoVmd6WlFVZlJEdVpyUHZIM0Q4LndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMzcwLTAzRkY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogMzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBMaWdodCcpLCBsb2NhbCgnUm9ib3RvLUxpZ2h0JyksIHVybCgvZm9udC1yb2JvdG8vLUwxNEprMDZtNnBVSEItNW1YUVFuWVgwaFZnelpRVWZSRHVaclB2SDNEOC53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMUYwMC0xRkZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBub3JtYWw7CiAgZm9udC13ZWlnaHQ6IDMwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTGlnaHQnKSwgbG9jYWwoJ1JvYm90by1MaWdodCcpLCB1cmwoL2ZvbnQtcm9ib3RvL0hnbzEzay10ZlNwbjBxaTFTRmRVZlpCdzF4VTFyS3B0SmpfMGphbnM5MjAud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAwMDAtMDBGRiwgVSswMTMxLCBVKzAxNTItMDE1MywgVSswMkM2LCBVKzAyREEsIFUrMDJEQywgVSsyMDAwLTIwNkYsIFUrMjA3NCwgVSsyMEFDLCBVKzIyMTIsIFUrMjIxNTsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiAzMDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIExpZ2h0JyksIGxvY2FsKCdSb2JvdG8tTGlnaHQnKSwgdXJsKC9mb250LXJvYm90by9QcnUzM3FqU2hwWlNtRzN6NlZZd25ZWDBoVmd6WlFVZlJEdVpyUHZIM0Q4LndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMTAwLTAyNEYsIFUrMUUwMC0xRUZGLCBVKzIwQTAtMjBBQiwgVSsyMEFELTIwQ0YsIFUrMkM2MC0yQzdGLCBVK0E3MjAtQTdGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiAzMDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIExpZ2h0JyksIGxvY2FsKCdSb2JvdG8tTGlnaHQnKSwgdXJsKC9mb250LXJvYm90by9OWURXQmRENGdJcTI2RzVYWWJIc0ZJWDBoVmd6WlFVZlJEdVpyUHZIM0Q4LndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMTAyLTAxMDMsIFUrMUVBMC0xRUY5LCBVKzIwQUI7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKICBmb250LXdlaWdodDogMzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBMaWdodCBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1MaWdodEl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvLzdtOGw3VGxGTy1TM1ZraEh1UjBhdDE0c1lZZEpnNWRVMnF6SkVWU3V0YTAud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzA0MDAtMDQ1RiwgVSswNDkwLTA0OTEsIFUrMDRCMC0wNEIxLCBVKzIxMTY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKICBmb250LXdlaWdodDogMzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBMaWdodCBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1MaWdodEl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvLzdtOGw3VGxGTy1TM1ZraEh1UjBhdF9acmFSMlRnOHcybHptN2tMTkwwLXcud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzA0NjAtMDUyRiwgVSsyMEI0LCBVKzJERTAtMkRGRiwgVStBNjQwLUE2OUY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKICBmb250LXdlaWdodDogMzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBMaWdodCBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1MaWdodEl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvLzdtOGw3VGxGTy1TM1ZraEh1UjBhdHd0X1JtNjkxTFRlYktmWTJaa0tTbUkud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAzNzAtMDNGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogaXRhbGljOwogIGZvbnQtd2VpZ2h0OiAzMDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIExpZ2h0IEl0YWxpYycpLCBsb2NhbCgnUm9ib3RvLUxpZ2h0SXRhbGljJyksIHVybCgvZm9udC1yb2JvdG8vN204bDdUbEZPLVMzVmtoSHVSMGF0MUJXMjZReHBTai1fWkttX3hUNGhXdy53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMUYwMC0xRkZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDMwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTGlnaHQgSXRhbGljJyksIGxvY2FsKCdSb2JvdG8tTGlnaHRJdGFsaWMnKSwgdXJsKC9mb250LXJvYm90by83bThsN1RsRk8tUzNWa2hIdVIwYXQ0Z3A5UThnYllyaHFHbFJhdl9JWGZrLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMDAwLTAwRkYsIFUrMDEzMSwgVSswMTUyLTAxNTMsIFUrMDJDNiwgVSswMkRBLCBVKzAyREMsIFUrMjAwMC0yMDZGLCBVKzIwNzQsIFUrMjBBQywgVSsyMjEyLCBVKzIyMTU7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKICBmb250LXdlaWdodDogMzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBMaWdodCBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1MaWdodEl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvLzdtOGw3VGxGTy1TM1ZraEh1UjBhdDZFOGtNNHhXUjFfMWJZVVJSb2pSR2Mud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAxMDAtMDI0RiwgVSsxRTAwLTFFRkYsIFUrMjBBMC0yMEFCLCBVKzIwQUQtMjBDRiwgVSsyQzYwLTJDN0YsIFUrQTcyMC1BN0ZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDMwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTGlnaHQgSXRhbGljJyksIGxvY2FsKCdSb2JvdG8tTGlnaHRJdGFsaWMnKSwgdXJsKC9mb250LXJvYm90by83bThsN1RsRk8tUzNWa2hIdVIwYXQ5RGlOc1I1YS05T2VfSXZwdThYV2xZLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMTAyLTAxMDMsIFUrMUVBMC0xRUY5LCBVKzIwQUI7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNTAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBNZWRpdW0nKSwgbG9jYWwoJ1JvYm90by1NZWRpdW0nKSwgdXJsKC9mb250LXJvYm90by9vSGkzMGt3UVd2cENXcUFoekhjQ1NJWDBoVmd6WlFVZlJEdVpyUHZIM0Q4LndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswNDAwLTA0NUYsIFUrMDQ5MC0wNDkxLCBVKzA0QjAtMDRCMSwgVSsyMTE2Owp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBub3JtYWw7CiAgZm9udC13ZWlnaHQ6IDUwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTWVkaXVtJyksIGxvY2FsKCdSb2JvdG8tTWVkaXVtJyksIHVybCgvZm9udC1yb2JvdG8vWkxxS2VlbFliQVRHNjBFcFpCU0R5NFgwaFZnelpRVWZSRHVaclB2SDNEOC53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDQ2MC0wNTJGLCBVKzIwQjQsIFUrMkRFMC0yREZGLCBVK0E2NDAtQTY5RjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA1MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1lZGl1bScpLCBsb2NhbCgnUm9ib3RvLU1lZGl1bScpLCB1cmwoL2ZvbnQtcm9ib3RvL214OVVjazZ1QjYzVklLRlluRU1YcllYMGhWZ3paUVVmUkR1WnJQdkgzRDgud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAzNzAtMDNGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA1MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1lZGl1bScpLCBsb2NhbCgnUm9ib3RvLU1lZGl1bScpLCB1cmwoL2ZvbnQtcm9ib3RvL3JHdkhkSm5yMmw3NXFiMFlORDlOeUlYMGhWZ3paUVVmUkR1WnJQdkgzRDgud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzFGMDAtMUZGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA1MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1lZGl1bScpLCBsb2NhbCgnUm9ib3RvLU1lZGl1bScpLCB1cmwoL2ZvbnQtcm9ib3RvL1J4WkpkbnplbzNSNXpTZXhnZThVVVpCdzF4VTFyS3B0SmpfMGphbnM5MjAud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAwMDAtMDBGRiwgVSswMTMxLCBVKzAxNTItMDE1MywgVSswMkM2LCBVKzAyREEsIFUrMDJEQywgVSsyMDAwLTIwNkYsIFUrMjA3NCwgVSsyMEFDLCBVKzIyMTIsIFUrMjIxNTsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA1MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1lZGl1bScpLCBsb2NhbCgnUm9ib3RvLU1lZGl1bScpLCB1cmwoL2ZvbnQtcm9ib3RvL29PZUZ3Wk5sclRlZnpMWW1sVlYxVUlYMGhWZ3paUVVmUkR1WnJQdkgzRDgud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAxMDAtMDI0RiwgVSsxRTAwLTFFRkYsIFUrMjBBMC0yMEFCLCBVKzIwQUQtMjBDRiwgVSsyQzYwLTJDN0YsIFUrQTcyMC1BN0ZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBub3JtYWw7CiAgZm9udC13ZWlnaHQ6IDUwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTWVkaXVtJyksIGxvY2FsKCdSb2JvdG8tTWVkaXVtJyksIHVybCgvZm9udC1yb2JvdG8vbWJtaHByTUg2OVppNmVFUEJZVkZoWVgwaFZnelpRVWZSRHVaclB2SDNEOC53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDEwMi0wMTAzLCBVKzFFQTAtMUVGOSwgVSsyMEFCOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDUwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTWVkaXVtIEl0YWxpYycpLCBsb2NhbCgnUm9ib3RvLU1lZGl1bUl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvL09MZmZHQlRhRjBYRk9XMWdudUhGMFY0c1lZZEpnNWRVMnF6SkVWU3V0YTAud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzA0MDAtMDQ1RiwgVSswNDkwLTA0OTEsIFUrMDRCMC0wNEIxLCBVKzIxMTY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKICBmb250LXdlaWdodDogNTAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBNZWRpdW0gSXRhbGljJyksIGxvY2FsKCdSb2JvdG8tTWVkaXVtSXRhbGljJyksIHVybCgvZm9udC1yb2JvdG8vT0xmZkdCVGFGMFhGT1cxZ251SEYwZlpyYVIyVGc4dzJsem03a0xOTDAtdy53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDQ2MC0wNTJGLCBVKzIwQjQsIFUrMkRFMC0yREZGLCBVK0E2NDAtQTY5RjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogaXRhbGljOwogIGZvbnQtd2VpZ2h0OiA1MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1lZGl1bSBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1NZWRpdW1JdGFsaWMnKSwgdXJsKC9mb250LXJvYm90by9PTGZmR0JUYUYwWEZPVzFnbnVIRjBRdF9SbTY5MUxUZWJLZlkyWmtLU21JLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMzcwLTAzRkY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKICBmb250LXdlaWdodDogNTAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBNZWRpdW0gSXRhbGljJyksIGxvY2FsKCdSb2JvdG8tTWVkaXVtSXRhbGljJyksIHVybCgvZm9udC1yb2JvdG8vT0xmZkdCVGFGMFhGT1cxZ251SEYwVkJXMjZReHBTai1fWkttX3hUNGhXdy53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMUYwMC0xRkZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvJzsKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgZm9udC13ZWlnaHQ6IDUwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTWVkaXVtIEl0YWxpYycpLCBsb2NhbCgnUm9ib3RvLU1lZGl1bUl0YWxpYycpLCB1cmwoL2ZvbnQtcm9ib3RvL09MZmZHQlRhRjBYRk9XMWdudUhGMFlncDlROGdiWXJocUdsUmF2X0lYZmsud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAwMDAtMDBGRiwgVSswMTMxLCBVKzAxNTItMDE1MywgVSswMkM2LCBVKzAyREEsIFUrMDJEQywgVSsyMDAwLTIwNkYsIFUrMjA3NCwgVSsyMEFDLCBVKzIyMTIsIFUrMjIxNTsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogaXRhbGljOwogIGZvbnQtd2VpZ2h0OiA1MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1lZGl1bSBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1NZWRpdW1JdGFsaWMnKSwgdXJsKC9mb250LXJvYm90by9PTGZmR0JUYUYwWEZPVzFnbnVIRjBhRThrTTR4V1IxXzFiWVVSUm9qUkdjLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMTAwLTAyNEYsIFUrMUUwMC0xRUZGLCBVKzIwQTAtMjBBQiwgVSsyMEFELTIwQ0YsIFUrMkM2MC0yQzdGLCBVK0E3MjAtQTdGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byc7CiAgZm9udC1zdHlsZTogaXRhbGljOwogIGZvbnQtd2VpZ2h0OiA1MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1lZGl1bSBJdGFsaWMnKSwgbG9jYWwoJ1JvYm90by1NZWRpdW1JdGFsaWMnKSwgdXJsKC9mb250LXJvYm90by9PTGZmR0JUYUYwWEZPVzFnbnVIRjBkRGlOc1I1YS05T2VfSXZwdThYV2xZLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMTAyLTAxMDMsIFUrMUVBMC0xRUY5LCBVKzIwQUI7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8gTW9ubyc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1vbm8nKSwgbG9jYWwoJ1JvYm90b01vbm8tUmVndWxhcicpLCB1cmwoL2ZvbnQtcm9ib3RvL2hNcVBOTHN1X2R5d01hNENfREVwWTE0c1lZZEpnNWRVMnF6SkVWU3V0YTAud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzA0MDAtMDQ1RiwgVSswNDkwLTA0OTEsIFUrMDRCMC0wNEIxLCBVKzIxMTY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8gTW9ubyc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1vbm8nKSwgbG9jYWwoJ1JvYm90b01vbm8tUmVndWxhcicpLCB1cmwoL2ZvbnQtcm9ib3RvL2hNcVBOTHN1X2R5d01hNENfREVwWV9acmFSMlRnOHcybHptN2tMTkwwLXcud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzA0NjAtMDUyRiwgVSsyMEI0LCBVKzJERTAtMkRGRiwgVStBNjQwLUE2OUY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8gTW9ubyc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1vbm8nKSwgbG9jYWwoJ1JvYm90b01vbm8tUmVndWxhcicpLCB1cmwoL2ZvbnQtcm9ib3RvL2hNcVBOTHN1X2R5d01hNENfREVwWXd0X1JtNjkxTFRlYktmWTJaa0tTbUkud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAzNzAtMDNGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byBNb25vJzsKICBmb250LXN0eWxlOiBub3JtYWw7CiAgZm9udC13ZWlnaHQ6IDQwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTW9ubycpLCBsb2NhbCgnUm9ib3RvTW9uby1SZWd1bGFyJyksIHVybCgvZm9udC1yb2JvdG8vaE1xUE5Mc3VfZHl3TWE0Q19ERXBZMUJXMjZReHBTai1fWkttX3hUNGhXdy53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMUYwMC0xRkZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvIE1vbm8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNDAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBNb25vJyksIGxvY2FsKCdSb2JvdG9Nb25vLVJlZ3VsYXInKSwgdXJsKC9mb250LXJvYm90by9oTXFQTkxzdV9keXdNYTRDX0RFcFk0Z3A5UThnYllyaHFHbFJhdl9JWGZrLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMDAwLTAwRkYsIFUrMDEzMSwgVSswMTUyLTAxNTMsIFUrMDJDNiwgVSswMkRBLCBVKzAyREMsIFUrMjAwMC0yMDZGLCBVKzIwNzQsIFUrMjBBQywgVSsyMjEyLCBVKzIyMTU7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8gTW9ubyc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1vbm8nKSwgbG9jYWwoJ1JvYm90b01vbm8tUmVndWxhcicpLCB1cmwoL2ZvbnQtcm9ib3RvL2hNcVBOTHN1X2R5d01hNENfREVwWTZFOGtNNHhXUjFfMWJZVVJSb2pSR2Mud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAxMDAtMDI0RiwgVSsxRTAwLTFFRkYsIFUrMjBBMC0yMEFCLCBVKzIwQUQtMjBDRiwgVSsyQzYwLTJDN0YsIFUrQTcyMC1BN0ZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvIE1vbm8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNDAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBNb25vJyksIGxvY2FsKCdSb2JvdG9Nb25vLVJlZ3VsYXInKSwgdXJsKC9mb250LXJvYm90by9oTXFQTkxzdV9keXdNYTRDX0RFcFk5RGlOc1I1YS05T2VfSXZwdThYV2xZLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswMTAyLTAxMDMsIFUrMUVBMC0xRUY5LCBVKzIwQUI7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8gTW9ubyc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA3MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1vbm8gQm9sZCcpLCBsb2NhbCgnUm9ib3RvTW9uby1Cb2xkJyksIHVybCgvZm9udC1yb2JvdG8vTjRkdVZjOUM1OHV3UGlZOF81OUZ6MXgtTTFJMXc1T01pcW5WRjh4QkxoVS53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDQwMC0wNDVGLCBVKzA0OTAtMDQ5MSwgVSswNEIwLTA0QjEsIFUrMjExNjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byBNb25vJzsKICBmb250LXN0eWxlOiBub3JtYWw7CiAgZm9udC13ZWlnaHQ6IDcwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTW9ubyBCb2xkJyksIGxvY2FsKCdSb2JvdG9Nb25vLUJvbGQnKSwgdXJsKC9mb250LXJvYm90by9ONGR1VmM5QzU4dXdQaVk4XzU5Rnp3WGFBWHVwNW1abGZLNnhSTHJoc2NvLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSswNDYwLTA1MkYsIFUrMjBCNCwgVSsyREUwLTJERkYsIFUrQTY0MC1BNjlGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvIE1vbm8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBNb25vIEJvbGQnKSwgbG9jYWwoJ1JvYm90b01vbm8tQm9sZCcpLCB1cmwoL2ZvbnQtcm9ib3RvL040ZHVWYzlDNTh1d1BpWThfNTlGenduNldxeG8teHd4aWxEWFBVOGNoVlUud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAzNzAtMDNGRjsKfQpAZm9udC1mYWNlIHsKICBmb250LWZhbWlseTogJ1JvYm90byBNb25vJzsKICBmb250LXN0eWxlOiBub3JtYWw7CiAgZm9udC13ZWlnaHQ6IDcwMDsKICBzcmM6IGxvY2FsKCdSb2JvdG8gTW9ubyBCb2xkJyksIGxvY2FsKCdSb2JvdG9Nb25vLUJvbGQnKSwgdXJsKC9mb250LXJvYm90by9ONGR1VmM5QzU4dXdQaVk4XzU5RnoxVDdhSkxLNm5LcG4zNklNd1RjTU1jLndvZmYyKSBmb3JtYXQoJ3dvZmYyJyk7CiAgdW5pY29kZS1yYW5nZTogVSsxRjAwLTFGRkY7Cn0KQGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdSb2JvdG8gTW9ubyc7CiAgZm9udC1zdHlsZTogbm9ybWFsOwogIGZvbnQtd2VpZ2h0OiA3MDA7CiAgc3JjOiBsb2NhbCgnUm9ib3RvIE1vbm8gQm9sZCcpLCBsb2NhbCgnUm9ib3RvTW9uby1Cb2xkJyksIHVybCgvZm9udC1yb2JvdG8vTjRkdVZjOUM1OHV3UGlZOF81OUZ6Xzc5X1p1VXhDaWdNMkRlc3BUbkZhdy53b2ZmMikgZm9ybWF0KCd3b2ZmMicpOwogIHVuaWNvZGUtcmFuZ2U6IFUrMDAwMC0wMEZGLCBVKzAxMzEsIFUrMDE1Mi0wMTUzLCBVKzAyQzYsIFUrMDJEQSwgVSswMkRDLCBVKzIwMDAtMjA2RiwgVSsyMDc0LCBVKzIwQUMsIFUrMjIxMiwgVSsyMjE1Owp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvIE1vbm8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBNb25vIEJvbGQnKSwgbG9jYWwoJ1JvYm90b01vbm8tQm9sZCcpLCB1cmwoL2ZvbnQtcm9ib3RvL040ZHVWYzlDNTh1d1BpWThfNTlGejRnZDlPRVBVQ04zQWRZVzBlOHRhdDQud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAxMDAtMDI0RiwgVSsxRTAwLTFFRkYsIFUrMjBBMC0yMEFCLCBVKzIwQUQtMjBDRiwgVSsyQzYwLTJDN0YsIFUrQTcyMC1BN0ZGOwp9CkBmb250LWZhY2UgewogIGZvbnQtZmFtaWx5OiAnUm9ib3RvIE1vbm8nOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNzAwOwogIHNyYzogbG9jYWwoJ1JvYm90byBNb25vIEJvbGQnKSwgbG9jYWwoJ1JvYm90b01vbm8tQm9sZCcpLCB1cmwoL2ZvbnQtcm9ib3RvL040ZHVWYzlDNTh1d1BpWThfNTlGejhiSVFTWVpuV0xhV0M5UU5DcFRLX1Uud29mZjIpIGZvcm1hdCgnd29mZjInKTsKICB1bmljb2RlLXJhbmdlOiBVKzAxMDItMDEwMywgVSsxRUEwLTFFRjksIFUrMjBBQjsKfQo8L3N0eWxlPgoKCgo8c3R5bGU+Lm1hdC1iYWRnZS1jb250ZW50e2ZvbnQtd2VpZ2h0OjYwMDtmb250LXNpemU6MTJweDtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1iYWRnZS1zbWFsbCAubWF0LWJhZGdlLWNvbnRlbnR7Zm9udC1zaXplOjlweH0ubWF0LWJhZGdlLWxhcmdlIC5tYXQtYmFkZ2UtY29udGVudHtmb250LXNpemU6MjRweH0ubWF0LWgxLC5tYXQtaGVhZGxpbmUsLm1hdC10eXBvZ3JhcGh5IC5tYXQtaDEsLm1hdC10eXBvZ3JhcGh5IC5tYXQtaGVhZGxpbmUsLm1hdC10eXBvZ3JhcGh5IGgxe2ZvbnQ6NDAwIDI0cHgvMzJweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsO21hcmdpbjowIDAgMTZweH0ubWF0LWgyLC5tYXQtdGl0bGUsLm1hdC10eXBvZ3JhcGh5IC5tYXQtaDIsLm1hdC10eXBvZ3JhcGh5IC5tYXQtdGl0bGUsLm1hdC10eXBvZ3JhcGh5IGgye2ZvbnQ6NTAwIDIwcHgvMzJweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsO21hcmdpbjowIDAgMTZweH0ubWF0LWgzLC5tYXQtc3ViaGVhZGluZy0yLC5tYXQtdHlwb2dyYXBoeSAubWF0LWgzLC5tYXQtdHlwb2dyYXBoeSAubWF0LXN1YmhlYWRpbmctMiwubWF0LXR5cG9ncmFwaHkgaDN7Zm9udDo0MDAgMTZweC8yOHB4IFJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtsZXR0ZXItc3BhY2luZzpub3JtYWw7bWFyZ2luOjAgMCAxNnB4fS5tYXQtaDQsLm1hdC1zdWJoZWFkaW5nLTEsLm1hdC10eXBvZ3JhcGh5IC5tYXQtaDQsLm1hdC10eXBvZ3JhcGh5IC5tYXQtc3ViaGVhZGluZy0xLC5tYXQtdHlwb2dyYXBoeSBoNHtmb250OjQwMCAxNXB4LzI0cHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbDttYXJnaW46MCAwIDE2cHh9Lm1hdC1oNSwubWF0LXR5cG9ncmFwaHkgLm1hdC1oNSwubWF0LXR5cG9ncmFwaHkgaDV7Zm9udDo0MDAgY2FsYygxNHB4ICogMC44MykvMjBweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bWFyZ2luOjAgMCAxMnB4fS5tYXQtaDYsLm1hdC10eXBvZ3JhcGh5IC5tYXQtaDYsLm1hdC10eXBvZ3JhcGh5IGg2e2ZvbnQ6NDAwIGNhbGMoMTRweCAqIDAuNjcpLzIwcHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO21hcmdpbjowIDAgMTJweH0ubWF0LWJvZHktc3Ryb25nLC5tYXQtYm9keS0yLC5tYXQtdHlwb2dyYXBoeSAubWF0LWJvZHktc3Ryb25nLC5tYXQtdHlwb2dyYXBoeSAubWF0LWJvZHktMntmb250OjUwMCAxNHB4LzI0cHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbH0ubWF0LWJvZHksLm1hdC1ib2R5LTEsLm1hdC10eXBvZ3JhcGh5IC5tYXQtYm9keSwubWF0LXR5cG9ncmFwaHkgLm1hdC1ib2R5LTEsLm1hdC10eXBvZ3JhcGh5e2ZvbnQ6NDAwIDE0cHgvMjBweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsfS5tYXQtYm9keSBwLC5tYXQtYm9keS0xIHAsLm1hdC10eXBvZ3JhcGh5IC5tYXQtYm9keSBwLC5tYXQtdHlwb2dyYXBoeSAubWF0LWJvZHktMSBwLC5tYXQtdHlwb2dyYXBoeSBwe21hcmdpbjowIDAgMTJweH0ubWF0LXNtYWxsLC5tYXQtY2FwdGlvbiwubWF0LXR5cG9ncmFwaHkgLm1hdC1zbWFsbCwubWF0LXR5cG9ncmFwaHkgLm1hdC1jYXB0aW9ue2ZvbnQ6NDAwIDEycHgvMjBweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsfS5tYXQtZGlzcGxheS00LC5tYXQtdHlwb2dyYXBoeSAubWF0LWRpc3BsYXktNHtmb250OjMwMCAxMTJweC8xMTJweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6LTAuMDVlbTttYXJnaW46MCAwIDU2cHh9Lm1hdC1kaXNwbGF5LTMsLm1hdC10eXBvZ3JhcGh5IC5tYXQtZGlzcGxheS0ze2ZvbnQ6NDAwIDU2cHgvNTZweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6LTAuMDJlbTttYXJnaW46MCAwIDY0cHh9Lm1hdC1kaXNwbGF5LTIsLm1hdC10eXBvZ3JhcGh5IC5tYXQtZGlzcGxheS0ye2ZvbnQ6NDAwIDQ1cHgvNDhweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6LTAuMDA1ZW07bWFyZ2luOjAgMCA2NHB4fS5tYXQtZGlzcGxheS0xLC5tYXQtdHlwb2dyYXBoeSAubWF0LWRpc3BsYXktMXtmb250OjQwMCAzNHB4LzQwcHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbDttYXJnaW46MCAwIDY0cHh9Lm1hdC1ib3R0b20tc2hlZXQtY29udGFpbmVye2ZvbnQ6NDAwIDE0cHgvMjBweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsfS5tYXQtYnV0dG9uLC5tYXQtcmFpc2VkLWJ1dHRvbiwubWF0LWljb24tYnV0dG9uLC5tYXQtc3Ryb2tlZC1idXR0b24sLm1hdC1mbGF0LWJ1dHRvbiwubWF0LWZhYiwubWF0LW1pbmktZmFie2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtmb250LXNpemU6MTRweDtmb250LXdlaWdodDo1MDB9Lm1hdC1idXR0b24tdG9nZ2xle2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LWNhcmR7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmfS5tYXQtY2FyZC10aXRsZXtmb250LXNpemU6MjRweDtmb250LXdlaWdodDo1MDB9Lm1hdC1jYXJkLWhlYWRlciAubWF0LWNhcmQtdGl0bGV7Zm9udC1zaXplOjIwcHh9Lm1hdC1jYXJkLXN1YnRpdGxlLC5tYXQtY2FyZC1jb250ZW50e2ZvbnQtc2l6ZToxNHB4fS5tYXQtY2hlY2tib3h7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmfS5tYXQtY2hlY2tib3gtbGF5b3V0IC5tYXQtY2hlY2tib3gtbGFiZWx7bGluZS1oZWlnaHQ6MjRweH0ubWF0LWNoaXB7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwfS5tYXQtY2hpcCAubWF0LWNoaXAtdHJhaWxpbmctaWNvbi5tYXQtaWNvbiwubWF0LWNoaXAgLm1hdC1jaGlwLXJlbW92ZS5tYXQtaWNvbntmb250LXNpemU6MThweH0ubWF0LXRhYmxle2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LWhlYWRlci1jZWxse2ZvbnQtc2l6ZToxMnB4O2ZvbnQtd2VpZ2h0OjUwMH0ubWF0LWNlbGwsLm1hdC1mb290ZXItY2VsbHtmb250LXNpemU6MTRweH0ubWF0LWNhbGVuZGFye2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LWNhbGVuZGFyLWJvZHl7Zm9udC1zaXplOjEzcHh9Lm1hdC1jYWxlbmRhci1ib2R5LWxhYmVsLC5tYXQtY2FsZW5kYXItcGVyaW9kLWJ1dHRvbntmb250LXNpemU6MTRweDtmb250LXdlaWdodDo1MDB9Lm1hdC1jYWxlbmRhci10YWJsZS1oZWFkZXIgdGh7Zm9udC1zaXplOjExcHg7Zm9udC13ZWlnaHQ6NDAwfS5tYXQtZGlhbG9nLXRpdGxle2ZvbnQ6NTAwIDIwcHgvMzJweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsfS5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlcntmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE1cHg7Zm9udC13ZWlnaHQ6NDAwfS5tYXQtZXhwYW5zaW9uLXBhbmVsLWNvbnRlbnR7Zm9udDo0MDAgMTRweC8yMHB4IFJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtsZXR0ZXItc3BhY2luZzpub3JtYWx9Lm1hdC1mb3JtLWZpZWxke2ZvbnQtc2l6ZTppbmhlcml0O2ZvbnQtd2VpZ2h0OjQwMDtsaW5lLWhlaWdodDoxLjEyNTtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsfS5tYXQtZm9ybS1maWVsZC13cmFwcGVye3BhZGRpbmctYm90dG9tOjEuMzQzNzVlbX0ubWF0LWZvcm0tZmllbGQtcHJlZml4IC5tYXQtaWNvbiwubWF0LWZvcm0tZmllbGQtc3VmZml4IC5tYXQtaWNvbntmb250LXNpemU6MTUwJTtsaW5lLWhlaWdodDoxLjEyNX0ubWF0LWZvcm0tZmllbGQtcHJlZml4IC5tYXQtaWNvbi1idXR0b24sLm1hdC1mb3JtLWZpZWxkLXN1ZmZpeCAubWF0LWljb24tYnV0dG9ue2hlaWdodDoxLjVlbTt3aWR0aDoxLjVlbX0ubWF0LWZvcm0tZmllbGQtcHJlZml4IC5tYXQtaWNvbi1idXR0b24gLm1hdC1pY29uLC5tYXQtZm9ybS1maWVsZC1zdWZmaXggLm1hdC1pY29uLWJ1dHRvbiAubWF0LWljb257aGVpZ2h0OjEuMTI1ZW07bGluZS1oZWlnaHQ6MS4xMjV9Lm1hdC1mb3JtLWZpZWxkLWluZml4e3BhZGRpbmc6LjVlbSAwO2JvcmRlci10b3A6Ljg0Mzc1ZW0gc29saWQgcmdiYSgwLDAsMCwwKX0ubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0Lm1hdC1mb3JtLWZpZWxkLXNob3VsZC1mbG9hdCAubWF0LWZvcm0tZmllbGQtbGFiZWwsLm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdCAubWF0LWlucHV0LXNlcnZlcjpmb2N1cysubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlciAubWF0LWZvcm0tZmllbGQtbGFiZWx7dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTEuMzQzNzVlbSkgc2NhbGUoMC43NSk7d2lkdGg6MTMzLjMzMzMzMzMzMzMlfS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXJbbGFiZWxdOm5vdCg6bGFiZWwtc2hvd24pKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0cmFuc2Zvcm06dHJhbnNsYXRlWSgtMS4zNDM3NGVtKSBzY2FsZSgwLjc1KTt3aWR0aDoxMzMuMzMzMzQzMzMzMyV9Lm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXJ7dG9wOi0wLjg0Mzc1ZW07cGFkZGluZy10b3A6Ljg0Mzc1ZW19Lm1hdC1mb3JtLWZpZWxkLWxhYmVse3RvcDoxLjM0Mzc1ZW19Lm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtib3R0b206MS4zNDM3NWVtfS5tYXQtZm9ybS1maWVsZC1zdWJzY3JpcHQtd3JhcHBlcntmb250LXNpemU6NzUlO21hcmdpbi10b3A6LjY2NjY2NjY2NjdlbTt0b3A6Y2FsYygxMDAlIC0gMS43OTE2NjY2NjY3ZW0pfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSAubWF0LWZvcm0tZmllbGQtd3JhcHBlcntwYWRkaW5nLWJvdHRvbToxLjI1ZW19Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5IC5tYXQtZm9ybS1maWVsZC1pbmZpeHtwYWRkaW5nOi40Mzc1ZW0gMH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0Lm1hdC1mb3JtLWZpZWxkLXNob3VsZC1mbG9hdCAubWF0LWZvcm0tZmllbGQtbGFiZWwsLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5Lm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdCAubWF0LWlucHV0LXNlcnZlcjpmb2N1cysubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlciAubWF0LWZvcm0tZmllbGQtbGFiZWx7dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTEuMjgxMjVlbSkgc2NhbGUoMC43NSkgcGVyc3BlY3RpdmUoMTAwcHgpIHRyYW5zbGF0ZVooMC4wMDFweCk7d2lkdGg6MTMzLjMzMzMzMzMzMzMlfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1mb3JtLWZpZWxkLWF1dG9maWxsLWNvbnRyb2w6LXdlYmtpdC1hdXRvZmlsbCsubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlciAubWF0LWZvcm0tZmllbGQtbGFiZWx7dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTEuMjgxMjVlbSkgc2NhbGUoMC43NSkgcGVyc3BlY3RpdmUoMTAwcHgpIHRyYW5zbGF0ZVooMC4wMDEwMXB4KTt3aWR0aDoxMzMuMzMzMzQzMzMzMyV9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5Lm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdCAubWF0LWlucHV0LXNlcnZlcltsYWJlbF06bm90KDpsYWJlbC1zaG93bikrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0xLjI4MTI1ZW0pIHNjYWxlKDAuNzUpIHBlcnNwZWN0aXZlKDEwMHB4KSB0cmFuc2xhdGVaKDAuMDAxMDJweCk7d2lkdGg6MTMzLjMzMzM1MzMzMzMlfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSAubWF0LWZvcm0tZmllbGQtbGFiZWx7dG9wOjEuMjgxMjVlbX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtib3R0b206MS4yNWVtfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSAubWF0LWZvcm0tZmllbGQtc3Vic2NyaXB0LXdyYXBwZXJ7bWFyZ2luLXRvcDouNTQxNjY2NjY2N2VtO3RvcDpjYWxjKDEwMCUgLSAxLjY2NjY2NjY2NjdlbSl9QG1lZGlhIHByaW50ey5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQubWF0LWZvcm0tZmllbGQtc2hvdWxkLWZsb2F0IC5tYXQtZm9ybS1maWVsZC1sYWJlbCwubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0IC5tYXQtaW5wdXQtc2VydmVyOmZvY3VzKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0cmFuc2Zvcm06dHJhbnNsYXRlWSgtMS4yODEyMmVtKSBzY2FsZSgwLjc1KX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0IC5tYXQtZm9ybS1maWVsZC1hdXRvZmlsbC1jb250cm9sOi13ZWJraXQtYXV0b2ZpbGwrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0xLjI4MTIxZW0pIHNjYWxlKDAuNzUpfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXJbbGFiZWxdOm5vdCg6bGFiZWwtc2hvd24pKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0cmFuc2Zvcm06dHJhbnNsYXRlWSgtMS4yODEyZW0pIHNjYWxlKDAuNzUpfX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsIC5tYXQtZm9ybS1maWVsZC1pbmZpeHtwYWRkaW5nOi4yNWVtIDAgLjc1ZW0gMH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0b3A6MS4wOTM3NWVtO21hcmdpbi10b3A6LTAuNWVtfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGwubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0Lm1hdC1mb3JtLWZpZWxkLXNob3VsZC1mbG9hdCAubWF0LWZvcm0tZmllbGQtbGFiZWwsLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbC5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXI6Zm9jdXMrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0wLjU5Mzc1ZW0pIHNjYWxlKDAuNzUpO3dpZHRoOjEzMy4zMzMzMzMzMzMzJX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsLm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdCAubWF0LWlucHV0LXNlcnZlcltsYWJlbF06bm90KDpsYWJlbC1zaG93bikrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0wLjU5Mzc0ZW0pIHNjYWxlKDAuNzUpO3dpZHRoOjEzMy4zMzMzNDMzMzMzJX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1pbmZpeHtwYWRkaW5nOjFlbSAwIDFlbSAwfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RvcDoxLjg0Mzc1ZW07bWFyZ2luLXRvcDotMC4yNWVtfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0Lm1hdC1mb3JtLWZpZWxkLXNob3VsZC1mbG9hdCAubWF0LWZvcm0tZmllbGQtbGFiZWwsLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXI6Zm9jdXMrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0xLjU5Mzc1ZW0pIHNjYWxlKDAuNzUpO3dpZHRoOjEzMy4zMzMzMzMzMzMzJX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lLm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdCAubWF0LWlucHV0LXNlcnZlcltsYWJlbF06bm90KDpsYWJlbC1zaG93bikrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0xLjU5Mzc0ZW0pIHNjYWxlKDAuNzUpO3dpZHRoOjEzMy4zMzMzNDMzMzMzJX0ubWF0LWdyaWQtdGlsZS1oZWFkZXIsLm1hdC1ncmlkLXRpbGUtZm9vdGVye2ZvbnQtc2l6ZToxNHB4fS5tYXQtZ3JpZC10aWxlLWhlYWRlciAubWF0LWxpbmUsLm1hdC1ncmlkLXRpbGUtZm9vdGVyIC5tYXQtbGluZXt3aGl0ZS1zcGFjZTpub3dyYXA7b3ZlcmZsb3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXM7ZGlzcGxheTpibG9jaztib3gtc2l6aW5nOmJvcmRlci1ib3h9Lm1hdC1ncmlkLXRpbGUtaGVhZGVyIC5tYXQtbGluZTpudGgtY2hpbGQobisyKSwubWF0LWdyaWQtdGlsZS1mb290ZXIgLm1hdC1saW5lOm50aC1jaGlsZChuKzIpe2ZvbnQtc2l6ZToxMnB4fWlucHV0Lm1hdC1pbnB1dC1lbGVtZW50e21hcmdpbi10b3A6LTAuMDYyNWVtfS5tYXQtbWVudS1pdGVte2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtmb250LXNpemU6MTRweDtmb250LXdlaWdodDo0MDB9Lm1hdC1wYWdpbmF0b3IsLm1hdC1wYWdpbmF0b3ItcGFnZS1zaXplIC5tYXQtc2VsZWN0LXRyaWdnZXJ7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMnB4fS5tYXQtcmFkaW8tYnV0dG9ue2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LXNlbGVjdHtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1zZWxlY3QtdHJpZ2dlcntoZWlnaHQ6MS4xMjVlbX0ubWF0LXNsaWRlLXRvZ2dsZS1jb250ZW50e2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LXNsaWRlci10aHVtYi1sYWJlbC10ZXh0e2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtmb250LXdlaWdodDo1MDB9Lm1hdC1zdGVwcGVyLXZlcnRpY2FsLC5tYXQtc3RlcHBlci1ob3Jpem9udGFse2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LXN0ZXAtbGFiZWx7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NDAwfS5tYXQtc3RlcC1zdWItbGFiZWwtZXJyb3J7Zm9udC13ZWlnaHQ6bm9ybWFsfS5tYXQtc3RlcC1sYWJlbC1lcnJvcntmb250LXNpemU6MTRweH0ubWF0LXN0ZXAtbGFiZWwtc2VsZWN0ZWR7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwfS5tYXQtdGFiLWdyb3Vwe2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LXRhYi1sYWJlbCwubWF0LXRhYi1saW5re2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtmb250LXNpemU6MTRweDtmb250LXdlaWdodDo1MDB9Lm1hdC10b29sYmFyLC5tYXQtdG9vbGJhciBoMSwubWF0LXRvb2xiYXIgaDIsLm1hdC10b29sYmFyIGgzLC5tYXQtdG9vbGJhciBoNCwubWF0LXRvb2xiYXIgaDUsLm1hdC10b29sYmFyIGg2e2ZvbnQ6NTAwIDIwcHgvMzJweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsO21hcmdpbjowfS5tYXQtdG9vbHRpcHtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjEwcHg7cGFkZGluZy10b3A6NnB4O3BhZGRpbmctYm90dG9tOjZweH0ubWF0LXRvb2x0aXAtaGFuZHNldHtmb250LXNpemU6MTRweDtwYWRkaW5nLXRvcDo4cHg7cGFkZGluZy1ib3R0b206OHB4fS5tYXQtbGlzdC1pdGVte2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LWxpc3Qtb3B0aW9ue2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LWxpc3QtYmFzZSAubWF0LWxpc3QtaXRlbXtmb250LXNpemU6MTZweH0ubWF0LWxpc3QtYmFzZSAubWF0LWxpc3QtaXRlbSAubWF0LWxpbmV7d2hpdGUtc3BhY2U6bm93cmFwO292ZXJmbG93OmhpZGRlbjt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzO2Rpc3BsYXk6YmxvY2s7Ym94LXNpemluZzpib3JkZXItYm94fS5tYXQtbGlzdC1iYXNlIC5tYXQtbGlzdC1pdGVtIC5tYXQtbGluZTpudGgtY2hpbGQobisyKXtmb250LXNpemU6MTRweH0ubWF0LWxpc3QtYmFzZSAubWF0LWxpc3Qtb3B0aW9ue2ZvbnQtc2l6ZToxNnB4fS5tYXQtbGlzdC1iYXNlIC5tYXQtbGlzdC1vcHRpb24gLm1hdC1saW5le3doaXRlLXNwYWNlOm5vd3JhcDtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpcztkaXNwbGF5OmJsb2NrO2JveC1zaXppbmc6Ym9yZGVyLWJveH0ubWF0LWxpc3QtYmFzZSAubWF0LWxpc3Qtb3B0aW9uIC5tYXQtbGluZTpudGgtY2hpbGQobisyKXtmb250LXNpemU6MTRweH0ubWF0LWxpc3QtYmFzZSAubWF0LXN1YmhlYWRlcntmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwfS5tYXQtbGlzdC1iYXNlW2RlbnNlXSAubWF0LWxpc3QtaXRlbXtmb250LXNpemU6MTJweH0ubWF0LWxpc3QtYmFzZVtkZW5zZV0gLm1hdC1saXN0LWl0ZW0gLm1hdC1saW5le3doaXRlLXNwYWNlOm5vd3JhcDtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpcztkaXNwbGF5OmJsb2NrO2JveC1zaXppbmc6Ym9yZGVyLWJveH0ubWF0LWxpc3QtYmFzZVtkZW5zZV0gLm1hdC1saXN0LWl0ZW0gLm1hdC1saW5lOm50aC1jaGlsZChuKzIpe2ZvbnQtc2l6ZToxMnB4fS5tYXQtbGlzdC1iYXNlW2RlbnNlXSAubWF0LWxpc3Qtb3B0aW9ue2ZvbnQtc2l6ZToxMnB4fS5tYXQtbGlzdC1iYXNlW2RlbnNlXSAubWF0LWxpc3Qtb3B0aW9uIC5tYXQtbGluZXt3aGl0ZS1zcGFjZTpub3dyYXA7b3ZlcmZsb3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXM7ZGlzcGxheTpibG9jaztib3gtc2l6aW5nOmJvcmRlci1ib3h9Lm1hdC1saXN0LWJhc2VbZGVuc2VdIC5tYXQtbGlzdC1vcHRpb24gLm1hdC1saW5lOm50aC1jaGlsZChuKzIpe2ZvbnQtc2l6ZToxMnB4fS5tYXQtbGlzdC1iYXNlW2RlbnNlXSAubWF0LXN1YmhlYWRlcntmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7Zm9udC13ZWlnaHQ6NTAwfS5tYXQtb3B0aW9ue2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtmb250LXNpemU6MTZweH0ubWF0LW9wdGdyb3VwLWxhYmVse2ZvbnQ6NTAwIDE0cHgvMjRweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsfS5tYXQtc2ltcGxlLXNuYWNrYmFye2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtmb250LXNpemU6MTRweH0ubWF0LXNpbXBsZS1zbmFja2Jhci1hY3Rpb257bGluZS1oZWlnaHQ6MTtmb250LWZhbWlseTppbmhlcml0O2ZvbnQtc2l6ZTppbmhlcml0O2ZvbnQtd2VpZ2h0OjUwMH0ubWF0LXRyZWV7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmfS5tYXQtdHJlZS1ub2RlLC5tYXQtbmVzdGVkLXRyZWUtbm9kZXtmb250LXdlaWdodDo0MDA7Zm9udC1zaXplOjE0cHh9Lm1hdC1yaXBwbGV7b3ZlcmZsb3c6aGlkZGVuO3Bvc2l0aW9uOnJlbGF0aXZlfS5tYXQtcmlwcGxlOm5vdCg6ZW1wdHkpe3RyYW5zZm9ybTp0cmFuc2xhdGVaKDApfS5tYXQtcmlwcGxlLm1hdC1yaXBwbGUtdW5ib3VuZGVke292ZXJmbG93OnZpc2libGV9Lm1hdC1yaXBwbGUtZWxlbWVudHtwb3NpdGlvbjphYnNvbHV0ZTtib3JkZXItcmFkaXVzOjUwJTtwb2ludGVyLWV2ZW50czpub25lO3RyYW5zaXRpb246b3BhY2l0eSx0cmFuc2Zvcm0gMG1zIGN1YmljLWJlemllcigwLCAwLCAwLjIsIDEpO3RyYW5zZm9ybTpzY2FsZTNkKDAsIDAsIDApfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1yaXBwbGUtZWxlbWVudHtkaXNwbGF5Om5vbmV9LmNkay12aXN1YWxseS1oaWRkZW57Ym9yZGVyOjA7Y2xpcDpyZWN0KDAgMCAwIDApO2hlaWdodDoxcHg7bWFyZ2luOi0xcHg7b3ZlcmZsb3c6aGlkZGVuO3BhZGRpbmc6MDtwb3NpdGlvbjphYnNvbHV0ZTt3aWR0aDoxcHg7d2hpdGUtc3BhY2U6bm93cmFwO291dGxpbmU6MDstd2Via2l0LWFwcGVhcmFuY2U6bm9uZTstbW96LWFwcGVhcmFuY2U6bm9uZTtsZWZ0OjB9W2Rpcj1ydGxdIC5jZGstdmlzdWFsbHktaGlkZGVue2xlZnQ6YXV0bztyaWdodDowfS5jZGstb3ZlcmxheS1jb250YWluZXIsLmNkay1nbG9iYWwtb3ZlcmxheS13cmFwcGVye3BvaW50ZXItZXZlbnRzOm5vbmU7dG9wOjA7bGVmdDowO2hlaWdodDoxMDAlO3dpZHRoOjEwMCV9LmNkay1vdmVybGF5LWNvbnRhaW5lcntwb3NpdGlvbjpmaXhlZDt6LWluZGV4OjEwMDB9LmNkay1vdmVybGF5LWNvbnRhaW5lcjplbXB0eXtkaXNwbGF5Om5vbmV9LmNkay1nbG9iYWwtb3ZlcmxheS13cmFwcGVye2Rpc3BsYXk6ZmxleDtwb3NpdGlvbjphYnNvbHV0ZTt6LWluZGV4OjEwMDB9LmNkay1vdmVybGF5LXBhbmV7cG9zaXRpb246YWJzb2x1dGU7cG9pbnRlci1ldmVudHM6YXV0bztib3gtc2l6aW5nOmJvcmRlci1ib3g7ei1pbmRleDoxMDAwO2Rpc3BsYXk6ZmxleDttYXgtd2lkdGg6MTAwJTttYXgtaGVpZ2h0OjEwMCV9LmNkay1vdmVybGF5LWJhY2tkcm9we3Bvc2l0aW9uOmFic29sdXRlO3RvcDowO2JvdHRvbTowO2xlZnQ6MDtyaWdodDowO3otaW5kZXg6MTAwMDtwb2ludGVyLWV2ZW50czphdXRvOy13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjpyZ2JhKDAsMCwwLDApO3RyYW5zaXRpb246b3BhY2l0eSA0MDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKTtvcGFjaXR5OjB9LmNkay1vdmVybGF5LWJhY2tkcm9wLmNkay1vdmVybGF5LWJhY2tkcm9wLXNob3dpbmd7b3BhY2l0eToxfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLmNkay1vdmVybGF5LWJhY2tkcm9wLmNkay1vdmVybGF5LWJhY2tkcm9wLXNob3dpbmd7b3BhY2l0eTouNn0uY2RrLW92ZXJsYXktZGFyay1iYWNrZHJvcHtiYWNrZ3JvdW5kOnJnYmEoMCwwLDAsLjMyKX0uY2RrLW92ZXJsYXktdHJhbnNwYXJlbnQtYmFja2Ryb3B7dHJhbnNpdGlvbjp2aXNpYmlsaXR5IDFtcyBsaW5lYXIsb3BhY2l0eSAxbXMgbGluZWFyO3Zpc2liaWxpdHk6aGlkZGVuO29wYWNpdHk6MX0uY2RrLW92ZXJsYXktdHJhbnNwYXJlbnQtYmFja2Ryb3AuY2RrLW92ZXJsYXktYmFja2Ryb3Atc2hvd2luZ3tvcGFjaXR5OjA7dmlzaWJpbGl0eTp2aXNpYmxlfS5jZGstb3ZlcmxheS1iYWNrZHJvcC1ub29wLWFuaW1hdGlvbnt0cmFuc2l0aW9uOm5vbmV9LmNkay1vdmVybGF5LWNvbm5lY3RlZC1wb3NpdGlvbi1ib3VuZGluZy1ib3h7cG9zaXRpb246YWJzb2x1dGU7ei1pbmRleDoxMDAwO2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47bWluLXdpZHRoOjFweDttaW4taGVpZ2h0OjFweH0uY2RrLWdsb2JhbC1zY3JvbGxibG9ja3twb3NpdGlvbjpmaXhlZDt3aWR0aDoxMDAlO292ZXJmbG93LXk6c2Nyb2xsfXRleHRhcmVhLmNkay10ZXh0YXJlYS1hdXRvc2l6ZXtyZXNpemU6bm9uZX10ZXh0YXJlYS5jZGstdGV4dGFyZWEtYXV0b3NpemUtbWVhc3VyaW5ne3BhZGRpbmc6MnB4IDAgIWltcG9ydGFudDtib3gtc2l6aW5nOmNvbnRlbnQtYm94ICFpbXBvcnRhbnQ7aGVpZ2h0OmF1dG8gIWltcG9ydGFudDtvdmVyZmxvdzpoaWRkZW4gIWltcG9ydGFudH10ZXh0YXJlYS5jZGstdGV4dGFyZWEtYXV0b3NpemUtbWVhc3VyaW5nLWZpcmVmb3h7cGFkZGluZzoycHggMCAhaW1wb3J0YW50O2JveC1zaXppbmc6Y29udGVudC1ib3ggIWltcG9ydGFudDtoZWlnaHQ6MCAhaW1wb3J0YW50fUBrZXlmcmFtZXMgY2RrLXRleHQtZmllbGQtYXV0b2ZpbGwtc3RhcnR7LyohKi99QGtleWZyYW1lcyBjZGstdGV4dC1maWVsZC1hdXRvZmlsbC1lbmR7LyohKi99LmNkay10ZXh0LWZpZWxkLWF1dG9maWxsLW1vbml0b3JlZDotd2Via2l0LWF1dG9maWxse2FuaW1hdGlvbjpjZGstdGV4dC1maWVsZC1hdXRvZmlsbC1zdGFydCAwcyAxbXN9LmNkay10ZXh0LWZpZWxkLWF1dG9maWxsLW1vbml0b3JlZDpub3QoOi13ZWJraXQtYXV0b2ZpbGwpe2FuaW1hdGlvbjpjZGstdGV4dC1maWVsZC1hdXRvZmlsbC1lbmQgMHMgMW1zfS5tYXQtZm9jdXMtaW5kaWNhdG9ye3Bvc2l0aW9uOnJlbGF0aXZlfS5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmV7dG9wOjA7bGVmdDowO3JpZ2h0OjA7Ym90dG9tOjA7cG9zaXRpb246YWJzb2x1dGU7Ym94LXNpemluZzpib3JkZXItYm94O3BvaW50ZXItZXZlbnRzOm5vbmU7ZGlzcGxheTp2YXIoLS1tYXQtZm9jdXMtaW5kaWNhdG9yLWRpc3BsYXksIG5vbmUpO2JvcmRlcjp2YXIoLS1tYXQtZm9jdXMtaW5kaWNhdG9yLWJvcmRlci13aWR0aCwgM3B4KSB2YXIoLS1tYXQtZm9jdXMtaW5kaWNhdG9yLWJvcmRlci1zdHlsZSwgc29saWQpIHZhcigtLW1hdC1mb2N1cy1pbmRpY2F0b3ItYm9yZGVyLWNvbG9yLCB0cmFuc3BhcmVudCk7Ym9yZGVyLXJhZGl1czp2YXIoLS1tYXQtZm9jdXMtaW5kaWNhdG9yLWJvcmRlci1yYWRpdXMsIDRweCl9Lm1hdC1mb2N1cy1pbmRpY2F0b3I6Zm9jdXM6OmJlZm9yZXtjb250ZW50OiIifS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmV7LS1tYXQtZm9jdXMtaW5kaWNhdG9yLWRpc3BsYXk6IGJsb2NrfS5tYXQtbWRjLWZvY3VzLWluZGljYXRvcntwb3NpdGlvbjpyZWxhdGl2ZX0ubWF0LW1kYy1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZXt0b3A6MDtsZWZ0OjA7cmlnaHQ6MDtib3R0b206MDtwb3NpdGlvbjphYnNvbHV0ZTtib3gtc2l6aW5nOmJvcmRlci1ib3g7cG9pbnRlci1ldmVudHM6bm9uZTtkaXNwbGF5OnZhcigtLW1hdC1tZGMtZm9jdXMtaW5kaWNhdG9yLWRpc3BsYXksIG5vbmUpO2JvcmRlcjp2YXIoLS1tYXQtbWRjLWZvY3VzLWluZGljYXRvci1ib3JkZXItd2lkdGgsIDNweCkgdmFyKC0tbWF0LW1kYy1mb2N1cy1pbmRpY2F0b3ItYm9yZGVyLXN0eWxlLCBzb2xpZCkgdmFyKC0tbWF0LW1kYy1mb2N1cy1pbmRpY2F0b3ItYm9yZGVyLWNvbG9yLCB0cmFuc3BhcmVudCk7Ym9yZGVyLXJhZGl1czp2YXIoLS1tYXQtbWRjLWZvY3VzLWluZGljYXRvci1ib3JkZXItcmFkaXVzLCA0cHgpfS5tYXQtbWRjLWZvY3VzLWluZGljYXRvcjpmb2N1czo6YmVmb3Jle2NvbnRlbnQ6IiJ9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZXstLW1hdC1tZGMtZm9jdXMtaW5kaWNhdG9yLWRpc3BsYXk6IGJsb2NrfS5tYXQtYmFkZ2UtY29udGVudHtmb250LXdlaWdodDo2MDA7Zm9udC1zaXplOjEycHg7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmfS5tYXQtYmFkZ2Utc21hbGwgLm1hdC1iYWRnZS1jb250ZW50e2ZvbnQtc2l6ZTo5cHh9Lm1hdC1iYWRnZS1sYXJnZSAubWF0LWJhZGdlLWNvbnRlbnR7Zm9udC1zaXplOjI0cHh9Lm1hdC1oMSwubWF0LWhlYWRsaW5lLC5tYXQtdHlwb2dyYXBoeSAubWF0LWgxLC5tYXQtdHlwb2dyYXBoeSAubWF0LWhlYWRsaW5lLC5tYXQtdHlwb2dyYXBoeSBoMXtmb250OjQwMCAyNHB4LzMycHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbDttYXJnaW46MCAwIDE2cHh9Lm1hdC1oMiwubWF0LXRpdGxlLC5tYXQtdHlwb2dyYXBoeSAubWF0LWgyLC5tYXQtdHlwb2dyYXBoeSAubWF0LXRpdGxlLC5tYXQtdHlwb2dyYXBoeSBoMntmb250OjUwMCAyMHB4LzMycHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbDttYXJnaW46MCAwIDE2cHh9Lm1hdC1oMywubWF0LXN1YmhlYWRpbmctMiwubWF0LXR5cG9ncmFwaHkgLm1hdC1oMywubWF0LXR5cG9ncmFwaHkgLm1hdC1zdWJoZWFkaW5nLTIsLm1hdC10eXBvZ3JhcGh5IGgze2ZvbnQ6NDAwIDE2cHgvMjhweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsO21hcmdpbjowIDAgMTZweH0ubWF0LWg0LC5tYXQtc3ViaGVhZGluZy0xLC5tYXQtdHlwb2dyYXBoeSAubWF0LWg0LC5tYXQtdHlwb2dyYXBoeSAubWF0LXN1YmhlYWRpbmctMSwubWF0LXR5cG9ncmFwaHkgaDR7Zm9udDo0MDAgMTVweC8yNHB4IFJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtsZXR0ZXItc3BhY2luZzpub3JtYWw7bWFyZ2luOjAgMCAxNnB4fS5tYXQtaDUsLm1hdC10eXBvZ3JhcGh5IC5tYXQtaDUsLm1hdC10eXBvZ3JhcGh5IGg1e2ZvbnQ6NDAwIGNhbGMoMTRweCAqIDAuODMpLzIwcHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO21hcmdpbjowIDAgMTJweH0ubWF0LWg2LC5tYXQtdHlwb2dyYXBoeSAubWF0LWg2LC5tYXQtdHlwb2dyYXBoeSBoNntmb250OjQwMCBjYWxjKDE0cHggKiAwLjY3KS8yMHB4IFJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjttYXJnaW46MCAwIDEycHh9Lm1hdC1ib2R5LXN0cm9uZywubWF0LWJvZHktMiwubWF0LXR5cG9ncmFwaHkgLm1hdC1ib2R5LXN0cm9uZywubWF0LXR5cG9ncmFwaHkgLm1hdC1ib2R5LTJ7Zm9udDo1MDAgMTRweC8yNHB4IFJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtsZXR0ZXItc3BhY2luZzpub3JtYWx9Lm1hdC1ib2R5LC5tYXQtYm9keS0xLC5tYXQtdHlwb2dyYXBoeSAubWF0LWJvZHksLm1hdC10eXBvZ3JhcGh5IC5tYXQtYm9keS0xLC5tYXQtdHlwb2dyYXBoeXtmb250OjQwMCAxNHB4LzIwcHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbH0ubWF0LWJvZHkgcCwubWF0LWJvZHktMSBwLC5tYXQtdHlwb2dyYXBoeSAubWF0LWJvZHkgcCwubWF0LXR5cG9ncmFwaHkgLm1hdC1ib2R5LTEgcCwubWF0LXR5cG9ncmFwaHkgcHttYXJnaW46MCAwIDEycHh9Lm1hdC1zbWFsbCwubWF0LWNhcHRpb24sLm1hdC10eXBvZ3JhcGh5IC5tYXQtc21hbGwsLm1hdC10eXBvZ3JhcGh5IC5tYXQtY2FwdGlvbntmb250OjQwMCAxMnB4LzIwcHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbH0ubWF0LWRpc3BsYXktNCwubWF0LXR5cG9ncmFwaHkgLm1hdC1kaXNwbGF5LTR7Zm9udDozMDAgMTEycHgvMTEycHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOi0wLjA1ZW07bWFyZ2luOjAgMCA1NnB4fS5tYXQtZGlzcGxheS0zLC5tYXQtdHlwb2dyYXBoeSAubWF0LWRpc3BsYXktM3tmb250OjQwMCA1NnB4LzU2cHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOi0wLjAyZW07bWFyZ2luOjAgMCA2NHB4fS5tYXQtZGlzcGxheS0yLC5tYXQtdHlwb2dyYXBoeSAubWF0LWRpc3BsYXktMntmb250OjQwMCA0NXB4LzQ4cHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOi0wLjAwNWVtO21hcmdpbjowIDAgNjRweH0ubWF0LWRpc3BsYXktMSwubWF0LXR5cG9ncmFwaHkgLm1hdC1kaXNwbGF5LTF7Zm9udDo0MDAgMzRweC80MHB4IFJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtsZXR0ZXItc3BhY2luZzpub3JtYWw7bWFyZ2luOjAgMCA2NHB4fS5tYXQtYm90dG9tLXNoZWV0LWNvbnRhaW5lcntmb250OjQwMCAxNHB4LzIwcHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbH0ubWF0LWJ1dHRvbiwubWF0LXJhaXNlZC1idXR0b24sLm1hdC1pY29uLWJ1dHRvbiwubWF0LXN0cm9rZWQtYnV0dG9uLC5tYXQtZmxhdC1idXR0b24sLm1hdC1mYWIsLm1hdC1taW5pLWZhYntmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwfS5tYXQtYnV0dG9uLXRvZ2dsZXtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1jYXJke2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LWNhcmQtdGl0bGV7Zm9udC1zaXplOjI0cHg7Zm9udC13ZWlnaHQ6NTAwfS5tYXQtY2FyZC1oZWFkZXIgLm1hdC1jYXJkLXRpdGxle2ZvbnQtc2l6ZToyMHB4fS5tYXQtY2FyZC1zdWJ0aXRsZSwubWF0LWNhcmQtY29udGVudHtmb250LXNpemU6MTRweH0ubWF0LWNoZWNrYm94e2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LWNoZWNrYm94LWxheW91dCAubWF0LWNoZWNrYm94LWxhYmVse2xpbmUtaGVpZ2h0OjI0cHh9Lm1hdC1jaGlwe2ZvbnQtc2l6ZToxNHB4O2ZvbnQtd2VpZ2h0OjUwMH0ubWF0LWNoaXAgLm1hdC1jaGlwLXRyYWlsaW5nLWljb24ubWF0LWljb24sLm1hdC1jaGlwIC5tYXQtY2hpcC1yZW1vdmUubWF0LWljb257Zm9udC1zaXplOjE4cHh9Lm1hdC10YWJsZXtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1oZWFkZXItY2VsbHtmb250LXNpemU6MTJweDtmb250LXdlaWdodDo1MDB9Lm1hdC1jZWxsLC5tYXQtZm9vdGVyLWNlbGx7Zm9udC1zaXplOjE0cHh9Lm1hdC1jYWxlbmRhcntmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1jYWxlbmRhci1ib2R5e2ZvbnQtc2l6ZToxM3B4fS5tYXQtY2FsZW5kYXItYm9keS1sYWJlbCwubWF0LWNhbGVuZGFyLXBlcmlvZC1idXR0b257Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwfS5tYXQtY2FsZW5kYXItdGFibGUtaGVhZGVyIHRoe2ZvbnQtc2l6ZToxMXB4O2ZvbnQtd2VpZ2h0OjQwMH0ubWF0LWRpYWxvZy10aXRsZXtmb250OjUwMCAyMHB4LzMycHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbH0ubWF0LWV4cGFuc2lvbi1wYW5lbC1oZWFkZXJ7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNXB4O2ZvbnQtd2VpZ2h0OjQwMH0ubWF0LWV4cGFuc2lvbi1wYW5lbC1jb250ZW50e2ZvbnQ6NDAwIDE0cHgvMjBweCBSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7bGV0dGVyLXNwYWNpbmc6bm9ybWFsfS5tYXQtZm9ybS1maWVsZHtmb250LXNpemU6aW5oZXJpdDtmb250LXdlaWdodDo0MDA7bGluZS1oZWlnaHQ6MS4xMjU7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbH0ubWF0LWZvcm0tZmllbGQtd3JhcHBlcntwYWRkaW5nLWJvdHRvbToxLjM0Mzc1ZW19Lm1hdC1mb3JtLWZpZWxkLXByZWZpeCAubWF0LWljb24sLm1hdC1mb3JtLWZpZWxkLXN1ZmZpeCAubWF0LWljb257Zm9udC1zaXplOjE1MCU7bGluZS1oZWlnaHQ6MS4xMjV9Lm1hdC1mb3JtLWZpZWxkLXByZWZpeCAubWF0LWljb24tYnV0dG9uLC5tYXQtZm9ybS1maWVsZC1zdWZmaXggLm1hdC1pY29uLWJ1dHRvbntoZWlnaHQ6MS41ZW07d2lkdGg6MS41ZW19Lm1hdC1mb3JtLWZpZWxkLXByZWZpeCAubWF0LWljb24tYnV0dG9uIC5tYXQtaWNvbiwubWF0LWZvcm0tZmllbGQtc3VmZml4IC5tYXQtaWNvbi1idXR0b24gLm1hdC1pY29ue2hlaWdodDoxLjEyNWVtO2xpbmUtaGVpZ2h0OjEuMTI1fS5tYXQtZm9ybS1maWVsZC1pbmZpeHtwYWRkaW5nOi41ZW0gMDtib3JkZXItdG9wOi44NDM3NWVtIHNvbGlkIHJnYmEoMCwwLDAsMCl9Lm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdC5tYXQtZm9ybS1maWVsZC1zaG91bGQtZmxvYXQgLm1hdC1mb3JtLWZpZWxkLWxhYmVsLC5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXI6Zm9jdXMrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0xLjM0MzczZW0pIHNjYWxlKDAuNzUpO3dpZHRoOjEzMy4zMzMzNTMzMzMzJX0ubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0IC5tYXQtaW5wdXQtc2VydmVyW2xhYmVsXTpub3QoOmxhYmVsLXNob3duKSsubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlciAubWF0LWZvcm0tZmllbGQtbGFiZWx7dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTEuMzQzNzJlbSkgc2NhbGUoMC43NSk7d2lkdGg6MTMzLjMzMzM2MzMzMzMlfS5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVye3RvcDotMC44NDM3NWVtO3BhZGRpbmctdG9wOi44NDM3NWVtfS5tYXQtZm9ybS1maWVsZC1sYWJlbHt0b3A6MS4zNDM3NWVtfS5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmV7Ym90dG9tOjEuMzQzNzVlbX0ubWF0LWZvcm0tZmllbGQtc3Vic2NyaXB0LXdyYXBwZXJ7Zm9udC1zaXplOjc1JTttYXJnaW4tdG9wOi42NjY2NjY2NjY3ZW07dG9wOmNhbGMoMTAwJSAtIDEuNzkxNjY2NjY2N2VtKX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLXdyYXBwZXJ7cGFkZGluZy1ib3R0b206MS4yNWVtfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSAubWF0LWZvcm0tZmllbGQtaW5maXh7cGFkZGluZzouNDM3NWVtIDB9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5Lm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdC5tYXQtZm9ybS1maWVsZC1zaG91bGQtZmxvYXQgLm1hdC1mb3JtLWZpZWxkLWxhYmVsLC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXI6Zm9jdXMrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0xLjI4MTI1ZW0pIHNjYWxlKDAuNzUpIHBlcnNwZWN0aXZlKDEwMHB4KSB0cmFuc2xhdGVaKDAuMDAxMDZweCk7d2lkdGg6MTMzLjMzMzM5MzMzMzMlfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1mb3JtLWZpZWxkLWF1dG9maWxsLWNvbnRyb2w6LXdlYmtpdC1hdXRvZmlsbCsubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlciAubWF0LWZvcm0tZmllbGQtbGFiZWx7dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTEuMjgxMjVlbSkgc2NhbGUoMC43NSkgcGVyc3BlY3RpdmUoMTAwcHgpIHRyYW5zbGF0ZVooMC4wMDEwN3B4KTt3aWR0aDoxMzMuMzMzNDAzMzMzMyV9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5Lm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdCAubWF0LWlucHV0LXNlcnZlcltsYWJlbF06bm90KDpsYWJlbC1zaG93bikrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0xLjI4MTI1ZW0pIHNjYWxlKDAuNzUpIHBlcnNwZWN0aXZlKDEwMHB4KSB0cmFuc2xhdGVaKDAuMDAxMDhweCk7d2lkdGg6MTMzLjMzMzQxMzMzMzMlfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSAubWF0LWZvcm0tZmllbGQtbGFiZWx7dG9wOjEuMjgxMjVlbX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtib3R0b206MS4yNWVtfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSAubWF0LWZvcm0tZmllbGQtc3Vic2NyaXB0LXdyYXBwZXJ7bWFyZ2luLXRvcDouNTQxNjY2NjY2N2VtO3RvcDpjYWxjKDEwMCUgLSAxLjY2NjY2NjY2NjdlbSl9QG1lZGlhIHByaW50ey5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQubWF0LWZvcm0tZmllbGQtc2hvdWxkLWZsb2F0IC5tYXQtZm9ybS1maWVsZC1sYWJlbCwubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0IC5tYXQtaW5wdXQtc2VydmVyOmZvY3VzKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0cmFuc2Zvcm06dHJhbnNsYXRlWSgtMS4yODExNmVtKSBzY2FsZSgwLjc1KX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0IC5tYXQtZm9ybS1maWVsZC1hdXRvZmlsbC1jb250cm9sOi13ZWJraXQtYXV0b2ZpbGwrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0xLjI4MTE1ZW0pIHNjYWxlKDAuNzUpfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXJbbGFiZWxdOm5vdCg6bGFiZWwtc2hvd24pKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0cmFuc2Zvcm06dHJhbnNsYXRlWSgtMS4yODExNGVtKSBzY2FsZSgwLjc1KX19Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbCAubWF0LWZvcm0tZmllbGQtaW5maXh7cGFkZGluZzouMjVlbSAwIC43NWVtIDB9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbCAubWF0LWZvcm0tZmllbGQtbGFiZWx7dG9wOjEuMDkzNzVlbTttYXJnaW4tdG9wOi0wLjVlbX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsLm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdC5tYXQtZm9ybS1maWVsZC1zaG91bGQtZmxvYXQgLm1hdC1mb3JtLWZpZWxkLWxhYmVsLC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGwubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0IC5tYXQtaW5wdXQtc2VydmVyOmZvY3VzKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0cmFuc2Zvcm06dHJhbnNsYXRlWSgtMC41OTM3M2VtKSBzY2FsZSgwLjc1KTt3aWR0aDoxMzMuMzMzMzUzMzMzMyV9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbC5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXJbbGFiZWxdOm5vdCg6bGFiZWwtc2hvd24pKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0cmFuc2Zvcm06dHJhbnNsYXRlWSgtMC41OTM3MmVtKSBzY2FsZSgwLjc1KTt3aWR0aDoxMzMuMzMzMzYzMzMzMyV9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtaW5maXh7cGFkZGluZzoxZW0gMCAxZW0gMH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0b3A6MS44NDM3NWVtO21hcmdpbi10b3A6LTAuMjVlbX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lLm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdC5tYXQtZm9ybS1maWVsZC1zaG91bGQtZmxvYXQgLm1hdC1mb3JtLWZpZWxkLWxhYmVsLC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0IC5tYXQtaW5wdXQtc2VydmVyOmZvY3VzKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0cmFuc2Zvcm06dHJhbnNsYXRlWSgtMS41OTM3M2VtKSBzY2FsZSgwLjc1KTt3aWR0aDoxMzMuMzMzMzUzMzMzMyV9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXJbbGFiZWxdOm5vdCg6bGFiZWwtc2hvd24pKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHt0cmFuc2Zvcm06dHJhbnNsYXRlWSgtMS41OTM3MmVtKSBzY2FsZSgwLjc1KTt3aWR0aDoxMzMuMzMzMzYzMzMzMyV9Lm1hdC1ncmlkLXRpbGUtaGVhZGVyLC5tYXQtZ3JpZC10aWxlLWZvb3Rlcntmb250LXNpemU6MTRweH0ubWF0LWdyaWQtdGlsZS1oZWFkZXIgLm1hdC1saW5lLC5tYXQtZ3JpZC10aWxlLWZvb3RlciAubWF0LWxpbmV7d2hpdGUtc3BhY2U6bm93cmFwO292ZXJmbG93OmhpZGRlbjt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzO2Rpc3BsYXk6YmxvY2s7Ym94LXNpemluZzpib3JkZXItYm94fS5tYXQtZ3JpZC10aWxlLWhlYWRlciAubWF0LWxpbmU6bnRoLWNoaWxkKG4rMiksLm1hdC1ncmlkLXRpbGUtZm9vdGVyIC5tYXQtbGluZTpudGgtY2hpbGQobisyKXtmb250LXNpemU6MTJweH1pbnB1dC5tYXQtaW5wdXQtZWxlbWVudHttYXJnaW4tdG9wOi0wLjA2MjVlbX0ubWF0LW1lbnUtaXRlbXtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NDAwfS5tYXQtcGFnaW5hdG9yLC5tYXQtcGFnaW5hdG9yLXBhZ2Utc2l6ZSAubWF0LXNlbGVjdC10cmlnZ2Vye2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjtmb250LXNpemU6MTJweH0ubWF0LXJhZGlvLWJ1dHRvbntmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1zZWxlY3R7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmfS5tYXQtc2VsZWN0LXRyaWdnZXJ7aGVpZ2h0OjEuMTI1ZW19Lm1hdC1zbGlkZS10b2dnbGUtY29udGVudHtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1zbGlkZXItdGh1bWItbGFiZWwtdGV4dHtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7Zm9udC13ZWlnaHQ6NTAwfS5tYXQtc3RlcHBlci12ZXJ0aWNhbCwubWF0LXN0ZXBwZXItaG9yaXpvbnRhbHtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1zdGVwLWxhYmVse2ZvbnQtc2l6ZToxNHB4O2ZvbnQtd2VpZ2h0OjQwMH0ubWF0LXN0ZXAtc3ViLWxhYmVsLWVycm9ye2ZvbnQtd2VpZ2h0Om5vcm1hbH0ubWF0LXN0ZXAtbGFiZWwtZXJyb3J7Zm9udC1zaXplOjE0cHh9Lm1hdC1zdGVwLWxhYmVsLXNlbGVjdGVke2ZvbnQtc2l6ZToxNHB4O2ZvbnQtd2VpZ2h0OjUwMH0ubWF0LXRhYi1ncm91cHtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC10YWItbGFiZWwsLm1hdC10YWItbGlua3tmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwfS5tYXQtdG9vbGJhciwubWF0LXRvb2xiYXIgaDEsLm1hdC10b29sYmFyIGgyLC5tYXQtdG9vbGJhciBoMywubWF0LXRvb2xiYXIgaDQsLm1hdC10b29sYmFyIGg1LC5tYXQtdG9vbGJhciBoNntmb250OjUwMCAyMHB4LzMycHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbDttYXJnaW46MH0ubWF0LXRvb2x0aXB7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMHB4O3BhZGRpbmctdG9wOjZweDtwYWRkaW5nLWJvdHRvbTo2cHh9Lm1hdC10b29sdGlwLWhhbmRzZXR7Zm9udC1zaXplOjE0cHg7cGFkZGluZy10b3A6OHB4O3BhZGRpbmctYm90dG9tOjhweH0ubWF0LWxpc3QtaXRlbXtmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1saXN0LW9wdGlvbntmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWZ9Lm1hdC1saXN0LWJhc2UgLm1hdC1saXN0LWl0ZW17Zm9udC1zaXplOjE2cHh9Lm1hdC1saXN0LWJhc2UgLm1hdC1saXN0LWl0ZW0gLm1hdC1saW5le3doaXRlLXNwYWNlOm5vd3JhcDtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpcztkaXNwbGF5OmJsb2NrO2JveC1zaXppbmc6Ym9yZGVyLWJveH0ubWF0LWxpc3QtYmFzZSAubWF0LWxpc3QtaXRlbSAubWF0LWxpbmU6bnRoLWNoaWxkKG4rMil7Zm9udC1zaXplOjE0cHh9Lm1hdC1saXN0LWJhc2UgLm1hdC1saXN0LW9wdGlvbntmb250LXNpemU6MTZweH0ubWF0LWxpc3QtYmFzZSAubWF0LWxpc3Qtb3B0aW9uIC5tYXQtbGluZXt3aGl0ZS1zcGFjZTpub3dyYXA7b3ZlcmZsb3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXM7ZGlzcGxheTpibG9jaztib3gtc2l6aW5nOmJvcmRlci1ib3h9Lm1hdC1saXN0LWJhc2UgLm1hdC1saXN0LW9wdGlvbiAubWF0LWxpbmU6bnRoLWNoaWxkKG4rMil7Zm9udC1zaXplOjE0cHh9Lm1hdC1saXN0LWJhc2UgLm1hdC1zdWJoZWFkZXJ7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNHB4O2ZvbnQtd2VpZ2h0OjUwMH0ubWF0LWxpc3QtYmFzZVtkZW5zZV0gLm1hdC1saXN0LWl0ZW17Zm9udC1zaXplOjEycHh9Lm1hdC1saXN0LWJhc2VbZGVuc2VdIC5tYXQtbGlzdC1pdGVtIC5tYXQtbGluZXt3aGl0ZS1zcGFjZTpub3dyYXA7b3ZlcmZsb3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXM7ZGlzcGxheTpibG9jaztib3gtc2l6aW5nOmJvcmRlci1ib3h9Lm1hdC1saXN0LWJhc2VbZGVuc2VdIC5tYXQtbGlzdC1pdGVtIC5tYXQtbGluZTpudGgtY2hpbGQobisyKXtmb250LXNpemU6MTJweH0ubWF0LWxpc3QtYmFzZVtkZW5zZV0gLm1hdC1saXN0LW9wdGlvbntmb250LXNpemU6MTJweH0ubWF0LWxpc3QtYmFzZVtkZW5zZV0gLm1hdC1saXN0LW9wdGlvbiAubWF0LWxpbmV7d2hpdGUtc3BhY2U6bm93cmFwO292ZXJmbG93OmhpZGRlbjt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzO2Rpc3BsYXk6YmxvY2s7Ym94LXNpemluZzpib3JkZXItYm94fS5tYXQtbGlzdC1iYXNlW2RlbnNlXSAubWF0LWxpc3Qtb3B0aW9uIC5tYXQtbGluZTpudGgtY2hpbGQobisyKXtmb250LXNpemU6MTJweH0ubWF0LWxpc3QtYmFzZVtkZW5zZV0gLm1hdC1zdWJoZWFkZXJ7Zm9udC1mYW1pbHk6Um9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMnB4O2ZvbnQtd2VpZ2h0OjUwMH0ubWF0LW9wdGlvbntmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHh9Lm1hdC1vcHRncm91cC1sYWJlbHtmb250OjUwMCAxNHB4LzI0cHggUm9ib3RvLCAiSGVsdmV0aWNhIE5ldWUiLCBzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOm5vcm1hbH0ubWF0LXNpbXBsZS1zbmFja2Jhcntmb250LWZhbWlseTpSb2JvdG8sICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHh9Lm1hdC1zaW1wbGUtc25hY2tiYXItYWN0aW9ue2xpbmUtaGVpZ2h0OjE7Zm9udC1mYW1pbHk6aW5oZXJpdDtmb250LXNpemU6aW5oZXJpdDtmb250LXdlaWdodDo1MDB9Lm1hdC10cmVle2ZvbnQtZmFtaWx5OlJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZn0ubWF0LXRyZWUtbm9kZSwubWF0LW5lc3RlZC10cmVlLW5vZGV7Zm9udC13ZWlnaHQ6NDAwO2ZvbnQtc2l6ZToxNHB4fS5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xKX0ubWF0LW9wdGlvbntjb2xvcjojMjEyMTIxfS5tYXQtb3B0aW9uOmhvdmVyOm5vdCgubWF0LW9wdGlvbi1kaXNhYmxlZCksLm1hdC1vcHRpb246Zm9jdXM6bm90KC5tYXQtb3B0aW9uLWRpc2FibGVkKXtiYWNrZ3JvdW5kOnJnYmEoMCwwLDAsLjA0KX0ubWF0LW9wdGlvbi5tYXQtc2VsZWN0ZWQ6bm90KC5tYXQtb3B0aW9uLW11bHRpcGxlKTpub3QoLm1hdC1vcHRpb24tZGlzYWJsZWQpe2JhY2tncm91bmQ6cmdiYSgwLDAsMCwuMDQpfS5tYXQtb3B0aW9uLm1hdC1hY3RpdmV7YmFja2dyb3VuZDpyZ2JhKDAsMCwwLC4wNCk7Y29sb3I6IzIxMjEyMX0ubWF0LW9wdGlvbi5tYXQtb3B0aW9uLWRpc2FibGVke2NvbG9yOnJnYmEoMCwwLDAsLjM4KX0ubWF0LXByaW1hcnkgLm1hdC1vcHRpb24ubWF0LXNlbGVjdGVkOm5vdCgubWF0LW9wdGlvbi1kaXNhYmxlZCl7Y29sb3I6I2Y1N2MwMH0ubWF0LWFjY2VudCAubWF0LW9wdGlvbi5tYXQtc2VsZWN0ZWQ6bm90KC5tYXQtb3B0aW9uLWRpc2FibGVkKXtjb2xvcjojZmY5ODAwfS5tYXQtd2FybiAubWF0LW9wdGlvbi5tYXQtc2VsZWN0ZWQ6bm90KC5tYXQtb3B0aW9uLWRpc2FibGVkKXtjb2xvcjojZjQ0MzM2fS5tYXQtb3B0Z3JvdXAtbGFiZWx7Y29sb3I6IzYxNjE2MX0ubWF0LW9wdGdyb3VwLWRpc2FibGVkIC5tYXQtb3B0Z3JvdXAtbGFiZWx7Y29sb3I6cmdiYSgwLDAsMCwuMzgpfS5tYXQtcHNldWRvLWNoZWNrYm94e2NvbG9yOiM2MTYxNjF9Lm1hdC1wc2V1ZG8tY2hlY2tib3g6OmFmdGVye2NvbG9yOiNmZmZ9Lm1hdC1wc2V1ZG8tY2hlY2tib3gtZGlzYWJsZWR7Y29sb3I6I2IwYjBiMH0ubWF0LXByaW1hcnkgLm1hdC1wc2V1ZG8tY2hlY2tib3gtY2hlY2tlZCwubWF0LXByaW1hcnkgLm1hdC1wc2V1ZG8tY2hlY2tib3gtaW5kZXRlcm1pbmF0ZXtiYWNrZ3JvdW5kOiNmNTdjMDB9Lm1hdC1wc2V1ZG8tY2hlY2tib3gtY2hlY2tlZCwubWF0LXBzZXVkby1jaGVja2JveC1pbmRldGVybWluYXRlLC5tYXQtYWNjZW50IC5tYXQtcHNldWRvLWNoZWNrYm94LWNoZWNrZWQsLm1hdC1hY2NlbnQgLm1hdC1wc2V1ZG8tY2hlY2tib3gtaW5kZXRlcm1pbmF0ZXtiYWNrZ3JvdW5kOiNmZjk4MDB9Lm1hdC13YXJuIC5tYXQtcHNldWRvLWNoZWNrYm94LWNoZWNrZWQsLm1hdC13YXJuIC5tYXQtcHNldWRvLWNoZWNrYm94LWluZGV0ZXJtaW5hdGV7YmFja2dyb3VuZDojZjQ0MzM2fS5tYXQtcHNldWRvLWNoZWNrYm94LWNoZWNrZWQubWF0LXBzZXVkby1jaGVja2JveC1kaXNhYmxlZCwubWF0LXBzZXVkby1jaGVja2JveC1pbmRldGVybWluYXRlLm1hdC1wc2V1ZG8tY2hlY2tib3gtZGlzYWJsZWR7YmFja2dyb3VuZDojYjBiMGIwfS5tYXQtYXBwLWJhY2tncm91bmR7YmFja2dyb3VuZC1jb2xvcjojZmZmO2NvbG9yOiMyMTIxMjF9Lm1hdC1lbGV2YXRpb24tejB7Ym94LXNoYWRvdzowcHggMHB4IDBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtZWxldmF0aW9uLXoxe2JveC1zaGFkb3c6MHB4IDJweCAxcHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDFweCAxcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAzcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1lbGV2YXRpb24tejJ7Ym94LXNoYWRvdzowcHggM3B4IDFweCAtMnB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMnB4IDJweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16M3tib3gtc2hhZG93OjBweCAzcHggM3B4IC0ycHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAzcHggNHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggOHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtZWxldmF0aW9uLXo0e2JveC1zaGFkb3c6MHB4IDJweCA0cHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDRweCA1cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAxMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtZWxldmF0aW9uLXo1e2JveC1zaGFkb3c6MHB4IDNweCA1cHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDVweCA4cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAxNHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtZWxldmF0aW9uLXo2e2JveC1zaGFkb3c6MHB4IDNweCA1cHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDZweCAxMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggMThweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16N3tib3gtc2hhZG93OjBweCA0cHggNXB4IC0ycHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCA3cHggMTBweCAxcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMnB4IDE2cHggMXB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1lbGV2YXRpb24tejh7Ym94LXNoYWRvdzowcHggNXB4IDVweCAtM3B4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggOHB4IDEwcHggMXB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDNweCAxNHB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtZWxldmF0aW9uLXo5e2JveC1zaGFkb3c6MHB4IDVweCA2cHggLTNweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDlweCAxMnB4IDFweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAzcHggMTZweCAycHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MTB7Ym94LXNoYWRvdzowcHggNnB4IDZweCAtM3B4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTBweCAxNHB4IDFweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA0cHggMThweCAzcHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MTF7Ym94LXNoYWRvdzowcHggNnB4IDdweCAtNHB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTFweCAxNXB4IDFweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA0cHggMjBweCAzcHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MTJ7Ym94LXNoYWRvdzowcHggN3B4IDhweCAtNHB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTJweCAxN3B4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA1cHggMjJweCA0cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MTN7Ym94LXNoYWRvdzowcHggN3B4IDhweCAtNHB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTNweCAxOXB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA1cHggMjRweCA0cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MTR7Ym94LXNoYWRvdzowcHggN3B4IDlweCAtNHB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTRweCAyMXB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA1cHggMjZweCA0cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MTV7Ym94LXNoYWRvdzowcHggOHB4IDlweCAtNXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTVweCAyMnB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA2cHggMjhweCA1cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MTZ7Ym94LXNoYWRvdzowcHggOHB4IDEwcHggLTVweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDE2cHggMjRweCAycHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggNnB4IDMwcHggNXB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1lbGV2YXRpb24tejE3e2JveC1zaGFkb3c6MHB4IDhweCAxMXB4IC01cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAxN3B4IDI2cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDZweCAzMnB4IDVweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtZWxldmF0aW9uLXoxOHtib3gtc2hhZG93OjBweCA5cHggMTFweCAtNXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMThweCAyOHB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA3cHggMzRweCA2cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MTl7Ym94LXNoYWRvdzowcHggOXB4IDEycHggLTZweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDE5cHggMjlweCAycHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggN3B4IDM2cHggNnB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1lbGV2YXRpb24tejIwe2JveC1zaGFkb3c6MHB4IDEwcHggMTNweCAtNnB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMjBweCAzMXB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA4cHggMzhweCA3cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MjF7Ym94LXNoYWRvdzowcHggMTBweCAxM3B4IC02cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAyMXB4IDMzcHggM3B4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDhweCA0MHB4IDdweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtZWxldmF0aW9uLXoyMntib3gtc2hhZG93OjBweCAxMHB4IDE0cHggLTZweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDIycHggMzVweCAzcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggOHB4IDQycHggN3B4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1lbGV2YXRpb24tejIze2JveC1zaGFkb3c6MHB4IDExcHggMTRweCAtN3B4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMjNweCAzNnB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA5cHggNDRweCA4cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWVsZXZhdGlvbi16MjR7Ym94LXNoYWRvdzowcHggMTFweCAxNXB4IC03cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAyNHB4IDM4cHggM3B4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDlweCA0NnB4IDhweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtdGhlbWUtbG9hZGVkLW1hcmtlcntkaXNwbGF5Om5vbmV9Lm1hdC1hdXRvY29tcGxldGUtcGFuZWx7YmFja2dyb3VuZDojZmZmO2NvbG9yOiMyMTIxMjF9Lm1hdC1hdXRvY29tcGxldGUtcGFuZWw6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggMnB4IDRweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggNHB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDEwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1hdXRvY29tcGxldGUtcGFuZWwgLm1hdC1vcHRpb24ubWF0LXNlbGVjdGVkOm5vdCgubWF0LWFjdGl2ZSk6bm90KDpob3Zlcil7YmFja2dyb3VuZDojZmZmfS5tYXQtYXV0b2NvbXBsZXRlLXBhbmVsIC5tYXQtb3B0aW9uLm1hdC1zZWxlY3RlZDpub3QoLm1hdC1hY3RpdmUpOm5vdCg6aG92ZXIpOm5vdCgubWF0LW9wdGlvbi1kaXNhYmxlZCl7Y29sb3I6IzIxMjEyMX0ubWF0LWJhZGdle3Bvc2l0aW9uOnJlbGF0aXZlfS5tYXQtYmFkZ2UubWF0LWJhZGdle292ZXJmbG93OnZpc2libGV9Lm1hdC1iYWRnZS1oaWRkZW4gLm1hdC1iYWRnZS1jb250ZW50e2Rpc3BsYXk6bm9uZX0ubWF0LWJhZGdlLWNvbnRlbnR7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7ZGlzcGxheTppbmxpbmUtYmxvY2s7Ym9yZGVyLXJhZGl1czo1MCU7dHJhbnNpdGlvbjp0cmFuc2Zvcm0gMjAwbXMgZWFzZS1pbi1vdXQ7dHJhbnNmb3JtOnNjYWxlKDAuNik7b3ZlcmZsb3c6aGlkZGVuO3doaXRlLXNwYWNlOm5vd3JhcDt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzO3BvaW50ZXItZXZlbnRzOm5vbmV9Lm5nLWFuaW1hdGUtZGlzYWJsZWQgLm1hdC1iYWRnZS1jb250ZW50LC5tYXQtYmFkZ2UtY29udGVudC5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZXt0cmFuc2l0aW9uOm5vbmV9Lm1hdC1iYWRnZS1jb250ZW50Lm1hdC1iYWRnZS1hY3RpdmV7dHJhbnNmb3JtOm5vbmV9Lm1hdC1iYWRnZS1zbWFsbCAubWF0LWJhZGdlLWNvbnRlbnR7d2lkdGg6MTZweDtoZWlnaHQ6MTZweDtsaW5lLWhlaWdodDoxNnB4fS5tYXQtYmFkZ2Utc21hbGwubWF0LWJhZGdlLWFib3ZlIC5tYXQtYmFkZ2UtY29udGVudHt0b3A6LThweH0ubWF0LWJhZGdlLXNtYWxsLm1hdC1iYWRnZS1iZWxvdyAubWF0LWJhZGdlLWNvbnRlbnR7Ym90dG9tOi04cHh9Lm1hdC1iYWRnZS1zbWFsbC5tYXQtYmFkZ2UtYmVmb3JlIC5tYXQtYmFkZ2UtY29udGVudHtsZWZ0Oi0xNnB4fVtkaXI9cnRsXSAubWF0LWJhZGdlLXNtYWxsLm1hdC1iYWRnZS1iZWZvcmUgLm1hdC1iYWRnZS1jb250ZW50e2xlZnQ6YXV0bztyaWdodDotMTZweH0ubWF0LWJhZGdlLXNtYWxsLm1hdC1iYWRnZS1hZnRlciAubWF0LWJhZGdlLWNvbnRlbnR7cmlnaHQ6LTE2cHh9W2Rpcj1ydGxdIC5tYXQtYmFkZ2Utc21hbGwubWF0LWJhZGdlLWFmdGVyIC5tYXQtYmFkZ2UtY29udGVudHtyaWdodDphdXRvO2xlZnQ6LTE2cHh9Lm1hdC1iYWRnZS1zbWFsbC5tYXQtYmFkZ2Utb3ZlcmxhcC5tYXQtYmFkZ2UtYmVmb3JlIC5tYXQtYmFkZ2UtY29udGVudHtsZWZ0Oi04cHh9W2Rpcj1ydGxdIC5tYXQtYmFkZ2Utc21hbGwubWF0LWJhZGdlLW92ZXJsYXAubWF0LWJhZGdlLWJlZm9yZSAubWF0LWJhZGdlLWNvbnRlbnR7bGVmdDphdXRvO3JpZ2h0Oi04cHh9Lm1hdC1iYWRnZS1zbWFsbC5tYXQtYmFkZ2Utb3ZlcmxhcC5tYXQtYmFkZ2UtYWZ0ZXIgLm1hdC1iYWRnZS1jb250ZW50e3JpZ2h0Oi04cHh9W2Rpcj1ydGxdIC5tYXQtYmFkZ2Utc21hbGwubWF0LWJhZGdlLW92ZXJsYXAubWF0LWJhZGdlLWFmdGVyIC5tYXQtYmFkZ2UtY29udGVudHtyaWdodDphdXRvO2xlZnQ6LThweH0ubWF0LWJhZGdlLW1lZGl1bSAubWF0LWJhZGdlLWNvbnRlbnR7d2lkdGg6MjJweDtoZWlnaHQ6MjJweDtsaW5lLWhlaWdodDoyMnB4fS5tYXQtYmFkZ2UtbWVkaXVtLm1hdC1iYWRnZS1hYm92ZSAubWF0LWJhZGdlLWNvbnRlbnR7dG9wOi0xMXB4fS5tYXQtYmFkZ2UtbWVkaXVtLm1hdC1iYWRnZS1iZWxvdyAubWF0LWJhZGdlLWNvbnRlbnR7Ym90dG9tOi0xMXB4fS5tYXQtYmFkZ2UtbWVkaXVtLm1hdC1iYWRnZS1iZWZvcmUgLm1hdC1iYWRnZS1jb250ZW50e2xlZnQ6LTIycHh9W2Rpcj1ydGxdIC5tYXQtYmFkZ2UtbWVkaXVtLm1hdC1iYWRnZS1iZWZvcmUgLm1hdC1iYWRnZS1jb250ZW50e2xlZnQ6YXV0bztyaWdodDotMjJweH0ubWF0LWJhZGdlLW1lZGl1bS5tYXQtYmFkZ2UtYWZ0ZXIgLm1hdC1iYWRnZS1jb250ZW50e3JpZ2h0Oi0yMnB4fVtkaXI9cnRsXSAubWF0LWJhZGdlLW1lZGl1bS5tYXQtYmFkZ2UtYWZ0ZXIgLm1hdC1iYWRnZS1jb250ZW50e3JpZ2h0OmF1dG87bGVmdDotMjJweH0ubWF0LWJhZGdlLW1lZGl1bS5tYXQtYmFkZ2Utb3ZlcmxhcC5tYXQtYmFkZ2UtYmVmb3JlIC5tYXQtYmFkZ2UtY29udGVudHtsZWZ0Oi0xMXB4fVtkaXI9cnRsXSAubWF0LWJhZGdlLW1lZGl1bS5tYXQtYmFkZ2Utb3ZlcmxhcC5tYXQtYmFkZ2UtYmVmb3JlIC5tYXQtYmFkZ2UtY29udGVudHtsZWZ0OmF1dG87cmlnaHQ6LTExcHh9Lm1hdC1iYWRnZS1tZWRpdW0ubWF0LWJhZGdlLW92ZXJsYXAubWF0LWJhZGdlLWFmdGVyIC5tYXQtYmFkZ2UtY29udGVudHtyaWdodDotMTFweH1bZGlyPXJ0bF0gLm1hdC1iYWRnZS1tZWRpdW0ubWF0LWJhZGdlLW92ZXJsYXAubWF0LWJhZGdlLWFmdGVyIC5tYXQtYmFkZ2UtY29udGVudHtyaWdodDphdXRvO2xlZnQ6LTExcHh9Lm1hdC1iYWRnZS1sYXJnZSAubWF0LWJhZGdlLWNvbnRlbnR7d2lkdGg6MjhweDtoZWlnaHQ6MjhweDtsaW5lLWhlaWdodDoyOHB4fS5tYXQtYmFkZ2UtbGFyZ2UubWF0LWJhZGdlLWFib3ZlIC5tYXQtYmFkZ2UtY29udGVudHt0b3A6LTE0cHh9Lm1hdC1iYWRnZS1sYXJnZS5tYXQtYmFkZ2UtYmVsb3cgLm1hdC1iYWRnZS1jb250ZW50e2JvdHRvbTotMTRweH0ubWF0LWJhZGdlLWxhcmdlLm1hdC1iYWRnZS1iZWZvcmUgLm1hdC1iYWRnZS1jb250ZW50e2xlZnQ6LTI4cHh9W2Rpcj1ydGxdIC5tYXQtYmFkZ2UtbGFyZ2UubWF0LWJhZGdlLWJlZm9yZSAubWF0LWJhZGdlLWNvbnRlbnR7bGVmdDphdXRvO3JpZ2h0Oi0yOHB4fS5tYXQtYmFkZ2UtbGFyZ2UubWF0LWJhZGdlLWFmdGVyIC5tYXQtYmFkZ2UtY29udGVudHtyaWdodDotMjhweH1bZGlyPXJ0bF0gLm1hdC1iYWRnZS1sYXJnZS5tYXQtYmFkZ2UtYWZ0ZXIgLm1hdC1iYWRnZS1jb250ZW50e3JpZ2h0OmF1dG87bGVmdDotMjhweH0ubWF0LWJhZGdlLWxhcmdlLm1hdC1iYWRnZS1vdmVybGFwLm1hdC1iYWRnZS1iZWZvcmUgLm1hdC1iYWRnZS1jb250ZW50e2xlZnQ6LTE0cHh9W2Rpcj1ydGxdIC5tYXQtYmFkZ2UtbGFyZ2UubWF0LWJhZGdlLW92ZXJsYXAubWF0LWJhZGdlLWJlZm9yZSAubWF0LWJhZGdlLWNvbnRlbnR7bGVmdDphdXRvO3JpZ2h0Oi0xNHB4fS5tYXQtYmFkZ2UtbGFyZ2UubWF0LWJhZGdlLW92ZXJsYXAubWF0LWJhZGdlLWFmdGVyIC5tYXQtYmFkZ2UtY29udGVudHtyaWdodDotMTRweH1bZGlyPXJ0bF0gLm1hdC1iYWRnZS1sYXJnZS5tYXQtYmFkZ2Utb3ZlcmxhcC5tYXQtYmFkZ2UtYWZ0ZXIgLm1hdC1iYWRnZS1jb250ZW50e3JpZ2h0OmF1dG87bGVmdDotMTRweH0ubWF0LWJhZGdlLWNvbnRlbnR7Y29sb3I6I2ZmZjtiYWNrZ3JvdW5kOiNmNTdjMDB9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWJhZGdlLWNvbnRlbnR7b3V0bGluZTpzb2xpZCAxcHg7Ym9yZGVyLXJhZGl1czowfS5tYXQtYmFkZ2UtYWNjZW50IC5tYXQtYmFkZ2UtY29udGVudHtiYWNrZ3JvdW5kOiNmZjk4MDA7Y29sb3I6I2ZmZn0ubWF0LWJhZGdlLXdhcm4gLm1hdC1iYWRnZS1jb250ZW50e2NvbG9yOiNmZmY7YmFja2dyb3VuZDojZjQ0MzM2fS5tYXQtYmFkZ2UtZGlzYWJsZWQgLm1hdC1iYWRnZS1jb250ZW50e2JhY2tncm91bmQ6I2JkYmRiZDtjb2xvcjojNzU3NTc1fS5tYXQtYm90dG9tLXNoZWV0LWNvbnRhaW5lcntib3gtc2hhZG93OjBweCA4cHggMTBweCAtNXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTZweCAyNHB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA2cHggMzBweCA1cHggcmdiYSgwLCAwLCAwLCAwLjEyKTtiYWNrZ3JvdW5kOiNmZmY7Y29sb3I6IzIxMjEyMX0ubWF0LWJ1dHRvbiwubWF0LWljb24tYnV0dG9uLC5tYXQtc3Ryb2tlZC1idXR0b257Y29sb3I6aW5oZXJpdDtiYWNrZ3JvdW5kOnJnYmEoMCwwLDAsMCl9Lm1hdC1idXR0b24ubWF0LXByaW1hcnksLm1hdC1pY29uLWJ1dHRvbi5tYXQtcHJpbWFyeSwubWF0LXN0cm9rZWQtYnV0dG9uLm1hdC1wcmltYXJ5e2NvbG9yOiNmNTdjMDB9Lm1hdC1idXR0b24ubWF0LWFjY2VudCwubWF0LWljb24tYnV0dG9uLm1hdC1hY2NlbnQsLm1hdC1zdHJva2VkLWJ1dHRvbi5tYXQtYWNjZW50e2NvbG9yOiNmZjk4MDB9Lm1hdC1idXR0b24ubWF0LXdhcm4sLm1hdC1pY29uLWJ1dHRvbi5tYXQtd2FybiwubWF0LXN0cm9rZWQtYnV0dG9uLm1hdC13YXJue2NvbG9yOiNmNDQzMzZ9Lm1hdC1idXR0b24ubWF0LXByaW1hcnkubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LWJ1dHRvbi5tYXQtYWNjZW50Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1idXR0b24ubWF0LXdhcm4ubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkLm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1pY29uLWJ1dHRvbi5tYXQtcHJpbWFyeS5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtaWNvbi1idXR0b24ubWF0LWFjY2VudC5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtaWNvbi1idXR0b24ubWF0LXdhcm4ubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LWljb24tYnV0dG9uLm1hdC1idXR0b24tZGlzYWJsZWQubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LXN0cm9rZWQtYnV0dG9uLm1hdC1wcmltYXJ5Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1zdHJva2VkLWJ1dHRvbi5tYXQtYWNjZW50Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1zdHJva2VkLWJ1dHRvbi5tYXQtd2Fybi5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtc3Ryb2tlZC1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZC5tYXQtYnV0dG9uLWRpc2FibGVke2NvbG9yOnJnYmEoMCwwLDAsLjI2KX0ubWF0LWJ1dHRvbi5tYXQtcHJpbWFyeSAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LC5tYXQtaWNvbi1idXR0b24ubWF0LXByaW1hcnkgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LXN0cm9rZWQtYnV0dG9uLm1hdC1wcmltYXJ5IC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7YmFja2dyb3VuZC1jb2xvcjojZjU3YzAwfS5tYXQtYnV0dG9uLm1hdC1hY2NlbnQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LWljb24tYnV0dG9uLm1hdC1hY2NlbnQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LXN0cm9rZWQtYnV0dG9uLm1hdC1hY2NlbnQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXtiYWNrZ3JvdW5kLWNvbG9yOiNmZjk4MDB9Lm1hdC1idXR0b24ubWF0LXdhcm4gLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LWljb24tYnV0dG9uLm1hdC13YXJuIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1zdHJva2VkLWJ1dHRvbi5tYXQtd2FybiAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNn0ubWF0LWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1pY29uLWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1zdHJva2VkLWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLDApfS5tYXQtYnV0dG9uIC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC1pY29uLWJ1dHRvbiAubWF0LXJpcHBsZS1lbGVtZW50LC5tYXQtc3Ryb2tlZC1idXR0b24gLm1hdC1yaXBwbGUtZWxlbWVudHtvcGFjaXR5Oi4xO2JhY2tncm91bmQtY29sb3I6Y3VycmVudENvbG9yfS5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7YmFja2dyb3VuZDojMDAwfS5tYXQtc3Ryb2tlZC1idXR0b246bm90KC5tYXQtYnV0dG9uLWRpc2FibGVkKXtib3JkZXItY29sb3I6cmdiYSgwLDAsMCwuMTIpfS5tYXQtZmxhdC1idXR0b24sLm1hdC1yYWlzZWQtYnV0dG9uLC5tYXQtZmFiLC5tYXQtbWluaS1mYWJ7Y29sb3I6IzIxMjEyMTtiYWNrZ3JvdW5kLWNvbG9yOiNmZmZ9Lm1hdC1mbGF0LWJ1dHRvbi5tYXQtcHJpbWFyeSwubWF0LXJhaXNlZC1idXR0b24ubWF0LXByaW1hcnksLm1hdC1mYWIubWF0LXByaW1hcnksLm1hdC1taW5pLWZhYi5tYXQtcHJpbWFyeXtjb2xvcjojZmZmfS5tYXQtZmxhdC1idXR0b24ubWF0LWFjY2VudCwubWF0LXJhaXNlZC1idXR0b24ubWF0LWFjY2VudCwubWF0LWZhYi5tYXQtYWNjZW50LC5tYXQtbWluaS1mYWIubWF0LWFjY2VudHtjb2xvcjojZmZmfS5tYXQtZmxhdC1idXR0b24ubWF0LXdhcm4sLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC13YXJuLC5tYXQtZmFiLm1hdC13YXJuLC5tYXQtbWluaS1mYWIubWF0LXdhcm57Y29sb3I6I2ZmZn0ubWF0LWZsYXQtYnV0dG9uLm1hdC1wcmltYXJ5Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1mbGF0LWJ1dHRvbi5tYXQtYWNjZW50Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1mbGF0LWJ1dHRvbi5tYXQtd2Fybi5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtZmxhdC1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZC5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtcHJpbWFyeS5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtYWNjZW50Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC13YXJuLm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1idXR0b24tZGlzYWJsZWQubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LWZhYi5tYXQtcHJpbWFyeS5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtZmFiLm1hdC1hY2NlbnQubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LWZhYi5tYXQtd2Fybi5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtZmFiLm1hdC1idXR0b24tZGlzYWJsZWQubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LW1pbmktZmFiLm1hdC1wcmltYXJ5Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1taW5pLWZhYi5tYXQtYWNjZW50Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1taW5pLWZhYi5tYXQtd2Fybi5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtbWluaS1mYWIubWF0LWJ1dHRvbi1kaXNhYmxlZC5tYXQtYnV0dG9uLWRpc2FibGVke2NvbG9yOnJnYmEoMCwwLDAsLjI2KX0ubWF0LWZsYXQtYnV0dG9uLm1hdC1wcmltYXJ5LC5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtcHJpbWFyeSwubWF0LWZhYi5tYXQtcHJpbWFyeSwubWF0LW1pbmktZmFiLm1hdC1wcmltYXJ5e2JhY2tncm91bmQtY29sb3I6I2Y1N2MwMH0ubWF0LWZsYXQtYnV0dG9uLm1hdC1hY2NlbnQsLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1hY2NlbnQsLm1hdC1mYWIubWF0LWFjY2VudCwubWF0LW1pbmktZmFiLm1hdC1hY2NlbnR7YmFja2dyb3VuZC1jb2xvcjojZmY5ODAwfS5tYXQtZmxhdC1idXR0b24ubWF0LXdhcm4sLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC13YXJuLC5tYXQtZmFiLm1hdC13YXJuLC5tYXQtbWluaS1mYWIubWF0LXdhcm57YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fS5tYXQtZmxhdC1idXR0b24ubWF0LXByaW1hcnkubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LWZsYXQtYnV0dG9uLm1hdC1hY2NlbnQubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LWZsYXQtYnV0dG9uLm1hdC13YXJuLm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1mbGF0LWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkLm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1wcmltYXJ5Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1hY2NlbnQubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LXJhaXNlZC1idXR0b24ubWF0LXdhcm4ubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LXJhaXNlZC1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZC5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtZmFiLm1hdC1wcmltYXJ5Lm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1mYWIubWF0LWFjY2VudC5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtZmFiLm1hdC13YXJuLm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1mYWIubWF0LWJ1dHRvbi1kaXNhYmxlZC5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtbWluaS1mYWIubWF0LXByaW1hcnkubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LW1pbmktZmFiLm1hdC1hY2NlbnQubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LW1pbmktZmFiLm1hdC13YXJuLm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1taW5pLWZhYi5tYXQtYnV0dG9uLWRpc2FibGVkLm1hdC1idXR0b24tZGlzYWJsZWR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xMil9Lm1hdC1mbGF0LWJ1dHRvbi5tYXQtcHJpbWFyeSAubWF0LXJpcHBsZS1lbGVtZW50LC5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtcHJpbWFyeSAubWF0LXJpcHBsZS1lbGVtZW50LC5tYXQtZmFiLm1hdC1wcmltYXJ5IC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC1taW5pLWZhYi5tYXQtcHJpbWFyeSAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMSl9Lm1hdC1mbGF0LWJ1dHRvbi5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1hY2NlbnQgLm1hdC1yaXBwbGUtZWxlbWVudCwubWF0LWZhYi5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC1taW5pLWZhYi5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xKX0ubWF0LWZsYXQtYnV0dG9uLm1hdC13YXJuIC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC13YXJuIC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC1mYWIubWF0LXdhcm4gLm1hdC1yaXBwbGUtZWxlbWVudCwubWF0LW1pbmktZmFiLm1hdC13YXJuIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xKX0ubWF0LXN0cm9rZWQtYnV0dG9uOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pLC5tYXQtZmxhdC1idXR0b246bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggMHB4IDBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtcmFpc2VkLWJ1dHRvbjpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCAzcHggMXB4IC0ycHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggNXB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtcmFpc2VkLWJ1dHRvbjpub3QoLm1hdC1idXR0b24tZGlzYWJsZWQpOmFjdGl2ZTpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCA1cHggNXB4IC0zcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCA4cHggMTBweCAxcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggM3B4IDE0cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1idXR0b24tZGlzYWJsZWQ6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggMHB4IDBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtZmFiOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pLC5tYXQtbWluaS1mYWI6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggM3B4IDVweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggNnB4IDEwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAxOHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtZmFiOm5vdCgubWF0LWJ1dHRvbi1kaXNhYmxlZCk6YWN0aXZlOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pLC5tYXQtbWluaS1mYWI6bm90KC5tYXQtYnV0dG9uLWRpc2FibGVkKTphY3RpdmU6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggN3B4IDhweCAtNHB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTJweCAxN3B4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA1cHggMjJweCA0cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWZhYi5tYXQtYnV0dG9uLWRpc2FibGVkOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pLC5tYXQtbWluaS1mYWIubWF0LWJ1dHRvbi1kaXNhYmxlZDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDBweCAwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDBweCAwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1idXR0b24tdG9nZ2xlLXN0YW5kYWxvbmU6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSksLm1hdC1idXR0b24tdG9nZ2xlLWdyb3VwOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pe2JveC1zaGFkb3c6MHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDJweCAycHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCA1cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1idXR0b24tdG9nZ2xlLXN0YW5kYWxvbmUubWF0LWJ1dHRvbi10b2dnbGUtYXBwZWFyYW5jZS1zdGFuZGFyZDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKSwubWF0LWJ1dHRvbi10b2dnbGUtZ3JvdXAtYXBwZWFyYW5jZS1zdGFuZGFyZDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93Om5vbmV9Lm1hdC1idXR0b24tdG9nZ2xle2NvbG9yOnJnYmEoMCwwLDAsLjM4KX0ubWF0LWJ1dHRvbi10b2dnbGUgLm1hdC1idXR0b24tdG9nZ2xlLWZvY3VzLW92ZXJsYXl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xMil9Lm1hdC1idXR0b24tdG9nZ2xlLWFwcGVhcmFuY2Utc3RhbmRhcmR7Y29sb3I6IzIxMjEyMTtiYWNrZ3JvdW5kOiNmZmZ9Lm1hdC1idXR0b24tdG9nZ2xlLWFwcGVhcmFuY2Utc3RhbmRhcmQgLm1hdC1idXR0b24tdG9nZ2xlLWZvY3VzLW92ZXJsYXl7YmFja2dyb3VuZC1jb2xvcjojMDAwfS5tYXQtYnV0dG9uLXRvZ2dsZS1ncm91cC1hcHBlYXJhbmNlLXN0YW5kYXJkIC5tYXQtYnV0dG9uLXRvZ2dsZSsubWF0LWJ1dHRvbi10b2dnbGV7Ym9yZGVyLWxlZnQ6c29saWQgMXB4ICNlMGUwZTB9W2Rpcj1ydGxdIC5tYXQtYnV0dG9uLXRvZ2dsZS1ncm91cC1hcHBlYXJhbmNlLXN0YW5kYXJkIC5tYXQtYnV0dG9uLXRvZ2dsZSsubWF0LWJ1dHRvbi10b2dnbGV7Ym9yZGVyLWxlZnQ6bm9uZTtib3JkZXItcmlnaHQ6c29saWQgMXB4ICNlMGUwZTB9Lm1hdC1idXR0b24tdG9nZ2xlLWdyb3VwLWFwcGVhcmFuY2Utc3RhbmRhcmQubWF0LWJ1dHRvbi10b2dnbGUtdmVydGljYWwgLm1hdC1idXR0b24tdG9nZ2xlKy5tYXQtYnV0dG9uLXRvZ2dsZXtib3JkZXItbGVmdDpub25lO2JvcmRlci1yaWdodDpub25lO2JvcmRlci10b3A6c29saWQgMXB4ICNlMGUwZTB9Lm1hdC1idXR0b24tdG9nZ2xlLWNoZWNrZWR7YmFja2dyb3VuZC1jb2xvcjojZTBlMGUwO2NvbG9yOiM2MTYxNjF9Lm1hdC1idXR0b24tdG9nZ2xlLWNoZWNrZWQubWF0LWJ1dHRvbi10b2dnbGUtYXBwZWFyYW5jZS1zdGFuZGFyZHtjb2xvcjojMjEyMTIxfS5tYXQtYnV0dG9uLXRvZ2dsZS1kaXNhYmxlZHtjb2xvcjpyZ2JhKDAsMCwwLC4yNik7YmFja2dyb3VuZC1jb2xvcjojZWVlfS5tYXQtYnV0dG9uLXRvZ2dsZS1kaXNhYmxlZC5tYXQtYnV0dG9uLXRvZ2dsZS1hcHBlYXJhbmNlLXN0YW5kYXJke2JhY2tncm91bmQ6I2ZmZn0ubWF0LWJ1dHRvbi10b2dnbGUtZGlzYWJsZWQubWF0LWJ1dHRvbi10b2dnbGUtY2hlY2tlZHtiYWNrZ3JvdW5kLWNvbG9yOiNiZGJkYmR9Lm1hdC1idXR0b24tdG9nZ2xlLXN0YW5kYWxvbmUubWF0LWJ1dHRvbi10b2dnbGUtYXBwZWFyYW5jZS1zdGFuZGFyZCwubWF0LWJ1dHRvbi10b2dnbGUtZ3JvdXAtYXBwZWFyYW5jZS1zdGFuZGFyZHtib3JkZXI6c29saWQgMXB4ICNlMGUwZTB9Lm1hdC1idXR0b24tdG9nZ2xlLWFwcGVhcmFuY2Utc3RhbmRhcmQgLm1hdC1idXR0b24tdG9nZ2xlLWxhYmVsLWNvbnRlbnR7bGluZS1oZWlnaHQ6NDhweH0ubWF0LWNhcmR7YmFja2dyb3VuZDojZmZmO2NvbG9yOiMyMTIxMjF9Lm1hdC1jYXJkOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pe2JveC1zaGFkb3c6MHB4IDJweCAxcHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDFweCAxcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAzcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC1jYXJkLm1hdC1jYXJkLWZsYXQ6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggMHB4IDBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtY2FyZC1zdWJ0aXRsZXtjb2xvcjojNjE2MTYxfS5tYXQtY2hlY2tib3gtZnJhbWV7Ym9yZGVyLWNvbG9yOiM2MTYxNjF9Lm1hdC1jaGVja2JveC1jaGVja21hcmt7ZmlsbDojZmZmfS5tYXQtY2hlY2tib3gtY2hlY2ttYXJrLXBhdGh7c3Ryb2tlOiNmZmYgIWltcG9ydGFudH0ubWF0LWNoZWNrYm94LW1peGVkbWFya3tiYWNrZ3JvdW5kLWNvbG9yOiNmZmZ9Lm1hdC1jaGVja2JveC1pbmRldGVybWluYXRlLm1hdC1wcmltYXJ5IC5tYXQtY2hlY2tib3gtYmFja2dyb3VuZCwubWF0LWNoZWNrYm94LWNoZWNrZWQubWF0LXByaW1hcnkgLm1hdC1jaGVja2JveC1iYWNrZ3JvdW5ke2JhY2tncm91bmQtY29sb3I6I2Y1N2MwMH0ubWF0LWNoZWNrYm94LWluZGV0ZXJtaW5hdGUubWF0LWFjY2VudCAubWF0LWNoZWNrYm94LWJhY2tncm91bmQsLm1hdC1jaGVja2JveC1jaGVja2VkLm1hdC1hY2NlbnQgLm1hdC1jaGVja2JveC1iYWNrZ3JvdW5ke2JhY2tncm91bmQtY29sb3I6I2ZmOTgwMH0ubWF0LWNoZWNrYm94LWluZGV0ZXJtaW5hdGUubWF0LXdhcm4gLm1hdC1jaGVja2JveC1iYWNrZ3JvdW5kLC5tYXQtY2hlY2tib3gtY2hlY2tlZC5tYXQtd2FybiAubWF0LWNoZWNrYm94LWJhY2tncm91bmR7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fS5tYXQtY2hlY2tib3gtZGlzYWJsZWQubWF0LWNoZWNrYm94LWNoZWNrZWQgLm1hdC1jaGVja2JveC1iYWNrZ3JvdW5kLC5tYXQtY2hlY2tib3gtZGlzYWJsZWQubWF0LWNoZWNrYm94LWluZGV0ZXJtaW5hdGUgLm1hdC1jaGVja2JveC1iYWNrZ3JvdW5ke2JhY2tncm91bmQtY29sb3I6I2IwYjBiMH0ubWF0LWNoZWNrYm94LWRpc2FibGVkOm5vdCgubWF0LWNoZWNrYm94LWNoZWNrZWQpIC5tYXQtY2hlY2tib3gtZnJhbWV7Ym9yZGVyLWNvbG9yOiNiMGIwYjB9Lm1hdC1jaGVja2JveC1kaXNhYmxlZCAubWF0LWNoZWNrYm94LWxhYmVse2NvbG9yOnJnYmEoMCwwLDAsLjM4KX0ubWF0LWNoZWNrYm94IC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojMDAwfS5tYXQtY2hlY2tib3gtY2hlY2tlZDpub3QoLm1hdC1jaGVja2JveC1kaXNhYmxlZCkubWF0LXByaW1hcnkgLm1hdC1yaXBwbGUtZWxlbWVudCwubWF0LWNoZWNrYm94OmFjdGl2ZTpub3QoLm1hdC1jaGVja2JveC1kaXNhYmxlZCkubWF0LXByaW1hcnkgLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kOiNmNTdjMDB9Lm1hdC1jaGVja2JveC1jaGVja2VkOm5vdCgubWF0LWNoZWNrYm94LWRpc2FibGVkKS5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC1jaGVja2JveDphY3RpdmU6bm90KC5tYXQtY2hlY2tib3gtZGlzYWJsZWQpLm1hdC1hY2NlbnQgLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kOiNmZjk4MDB9Lm1hdC1jaGVja2JveC1jaGVja2VkOm5vdCgubWF0LWNoZWNrYm94LWRpc2FibGVkKS5tYXQtd2FybiAubWF0LXJpcHBsZS1lbGVtZW50LC5tYXQtY2hlY2tib3g6YWN0aXZlOm5vdCgubWF0LWNoZWNrYm94LWRpc2FibGVkKS5tYXQtd2FybiAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQ6I2Y0NDMzNn0ubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXB7YmFja2dyb3VuZC1jb2xvcjojZTBlMGUwO2NvbG9yOiMyMTIxMjF9Lm1hdC1jaGlwLm1hdC1zdGFuZGFyZC1jaGlwIC5tYXQtY2hpcC1yZW1vdmV7Y29sb3I6IzIxMjEyMTtvcGFjaXR5Oi40fS5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcDpub3QoLm1hdC1jaGlwLWRpc2FibGVkKTphY3RpdmV7Ym94LXNoYWRvdzowcHggM3B4IDNweCAtMnB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggM3B4IDRweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDhweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXA6bm90KC5tYXQtY2hpcC1kaXNhYmxlZCkgLm1hdC1jaGlwLXJlbW92ZTpob3ZlcntvcGFjaXR5Oi41NH0ubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtZGlzYWJsZWR7b3BhY2l0eTouNH0ubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXA6OmFmdGVye2JhY2tncm91bmQ6IzAwMH0ubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtc2VsZWN0ZWQubWF0LXByaW1hcnl7YmFja2dyb3VuZC1jb2xvcjojZjU3YzAwO2NvbG9yOiNmZmZ9Lm1hdC1jaGlwLm1hdC1zdGFuZGFyZC1jaGlwLm1hdC1jaGlwLXNlbGVjdGVkLm1hdC1wcmltYXJ5IC5tYXQtY2hpcC1yZW1vdmV7Y29sb3I6I2ZmZjtvcGFjaXR5Oi40fS5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtcHJpbWFyeSAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMSl9Lm1hdC1jaGlwLm1hdC1zdGFuZGFyZC1jaGlwLm1hdC1jaGlwLXNlbGVjdGVkLm1hdC13YXJue2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNjtjb2xvcjojZmZmfS5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtd2FybiAubWF0LWNoaXAtcmVtb3Zle2NvbG9yOiNmZmY7b3BhY2l0eTouNH0ubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtc2VsZWN0ZWQubWF0LXdhcm4gLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjEpfS5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtYWNjZW50e2JhY2tncm91bmQtY29sb3I6I2ZmOTgwMDtjb2xvcjojZmZmfS5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtYWNjZW50IC5tYXQtY2hpcC1yZW1vdmV7Y29sb3I6I2ZmZjtvcGFjaXR5Oi40fS5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xKX0ubWF0LXRhYmxle2JhY2tncm91bmQ6I2ZmZn0ubWF0LXRhYmxlIHRoZWFkLC5tYXQtdGFibGUgdGJvZHksLm1hdC10YWJsZSB0Zm9vdCxtYXQtaGVhZGVyLXJvdyxtYXQtcm93LG1hdC1mb290ZXItcm93LFttYXQtaGVhZGVyLXJvd10sW21hdC1yb3ddLFttYXQtZm9vdGVyLXJvd10sLm1hdC10YWJsZS1zdGlja3l7YmFja2dyb3VuZDppbmhlcml0fW1hdC1yb3csbWF0LWhlYWRlci1yb3csbWF0LWZvb3Rlci1yb3csdGgubWF0LWhlYWRlci1jZWxsLHRkLm1hdC1jZWxsLHRkLm1hdC1mb290ZXItY2VsbHtib3JkZXItYm90dG9tLWNvbG9yOnJnYmEoMCwwLDAsLjEyKX0ubWF0LWhlYWRlci1jZWxse2NvbG9yOiM2MTYxNjF9Lm1hdC1jZWxsLC5tYXQtZm9vdGVyLWNlbGx7Y29sb3I6IzIxMjEyMX0ubWF0LWNhbGVuZGFyLWFycm93e2ZpbGw6cmdiYSgwLDAsMCwuNTQpfS5tYXQtZGF0ZXBpY2tlci10b2dnbGUsLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQgLm1hdC1jYWxlbmRhci1uZXh0LWJ1dHRvbiwubWF0LWRhdGVwaWNrZXItY29udGVudCAubWF0LWNhbGVuZGFyLXByZXZpb3VzLWJ1dHRvbntjb2xvcjpyZ2JhKDAsMCwwLC41NCl9Lm1hdC1jYWxlbmRhci10YWJsZS1oZWFkZXItZGl2aWRlcjo6YWZ0ZXJ7YmFja2dyb3VuZDpyZ2JhKDAsMCwwLC4xMil9Lm1hdC1jYWxlbmRhci10YWJsZS1oZWFkZXIsLm1hdC1jYWxlbmRhci1ib2R5LWxhYmVse2NvbG9yOiM2MTYxNjF9Lm1hdC1jYWxlbmRhci1ib2R5LWNlbGwtY29udGVudCwubWF0LWRhdGUtcmFuZ2UtaW5wdXQtc2VwYXJhdG9ye2NvbG9yOiMyMTIxMjE7Ym9yZGVyLWNvbG9yOnJnYmEoMCwwLDAsMCl9Lm1hdC1jYWxlbmRhci1ib2R5LWRpc2FibGVkPi5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQ6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCl7Y29sb3I6Izc1NzU3NX0ubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQgLm1hdC1kYXRlLXJhbmdlLWlucHV0LXNlcGFyYXRvcntjb2xvcjojNzU3NTc1fS5tYXQtY2FsZW5kYXItYm9keS1pbi1wcmV2aWV3e2NvbG9yOnJnYmEoMCwwLDAsLjI0KX0ubWF0LWNhbGVuZGFyLWJvZHktdG9kYXk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCl7Ym9yZGVyLWNvbG9yOnJnYmEoMCwwLDAsLjM4KX0ubWF0LWNhbGVuZGFyLWJvZHktZGlzYWJsZWQ+Lm1hdC1jYWxlbmRhci1ib2R5LXRvZGF5Om5vdCgubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWQpOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwpe2JvcmRlci1jb2xvcjpyZ2JhKDAsMCwwLC4xOCl9Lm1hdC1jYWxlbmRhci1ib2R5LWluLXJhbmdlOjpiZWZvcmV7YmFja2dyb3VuZDpyZ2JhKDI0NSwxMjQsMCwuMil9Lm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsLC5tYXQtY2FsZW5kYXItYm9keS1pbi1jb21wYXJpc29uLXJhbmdlOjpiZWZvcmV7YmFja2dyb3VuZDpyZ2JhKDI0OSwxNzEsMCwuMil9Lm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLXN0YXJ0OjpiZWZvcmUsW2Rpcj1ydGxdIC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWJyaWRnZS1lbmQ6OmJlZm9yZXtiYWNrZ3JvdW5kOmxpbmVhci1ncmFkaWVudCh0byByaWdodCwgcmdiYSgyNDUsIDEyNCwgMCwgMC4yKSA1MCUsIHJnYmEoMjQ5LCAxNzEsIDAsIDAuMikgNTAlKX0ubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1icmlkZ2UtZW5kOjpiZWZvcmUsW2Rpcj1ydGxdIC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWJyaWRnZS1zdGFydDo6YmVmb3Jle2JhY2tncm91bmQ6bGluZWFyLWdyYWRpZW50KHRvIGxlZnQsIHJnYmEoMjQ1LCAxMjQsIDAsIDAuMikgNTAlLCByZ2JhKDI0OSwgMTcxLCAwLCAwLjIpIDUwJSl9Lm1hdC1jYWxlbmRhci1ib2R5LWluLXJhbmdlPi5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCwubWF0LWNhbGVuZGFyLWJvZHktaW4tY29tcGFyaXNvbi1yYW5nZS5tYXQtY2FsZW5kYXItYm9keS1pbi1yYW5nZTo6YWZ0ZXJ7YmFja2dyb3VuZDojYThkYWI1fS5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCwubWF0LWNhbGVuZGFyLWJvZHktaW4tY29tcGFyaXNvbi1yYW5nZT4ubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWR7YmFja2dyb3VuZDojNDZhMzVlfS5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZHtiYWNrZ3JvdW5kLWNvbG9yOiNmNTdjMDA7Y29sb3I6I2ZmZn0ubWF0LWNhbGVuZGFyLWJvZHktZGlzYWJsZWQ+Lm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVke2JhY2tncm91bmQtY29sb3I6cmdiYSgyNDUsMTI0LDAsLjQpfS5tYXQtY2FsZW5kYXItYm9keS10b2RheS5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZHtib3gtc2hhZG93Omluc2V0IDAgMCAwIDFweCAjZmZmfS5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWNhbGVuZGFyLWJvZHktYWN0aXZlPi5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQ6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCksLmNkay1wcm9ncmFtLWZvY3VzZWQgLm1hdC1jYWxlbmRhci1ib2R5LWFjdGl2ZT4ubWF0LWNhbGVuZGFyLWJvZHktY2VsbC1jb250ZW50Om5vdCgubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWQpOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNDUsMTI0LDAsLjMpfUBtZWRpYShob3ZlcjogaG92ZXIpey5tYXQtY2FsZW5kYXItYm9keS1jZWxsOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktZGlzYWJsZWQpOmhvdmVyPi5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQ6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI0NSwxMjQsMCwuMyl9fS5tYXQtZGF0ZXBpY2tlci1jb250ZW50e2JveC1zaGFkb3c6MHB4IDJweCA0cHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDRweCA1cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAxMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpO2JhY2tncm91bmQtY29sb3I6I2ZmZjtjb2xvcjojMjEyMTIxfS5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgLm1hdC1jYWxlbmRhci1ib2R5LWluLXJhbmdlOjpiZWZvcmV7YmFja2dyb3VuZDpyZ2JhKDI1NSwxNTIsMCwuMil9Lm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwsLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktaW4tY29tcGFyaXNvbi1yYW5nZTo6YmVmb3Jle2JhY2tncm91bmQ6cmdiYSgyNDksMTcxLDAsLjIpfS5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLXN0YXJ0OjpiZWZvcmUsLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCBbZGlyPXJ0bF0gLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLWVuZDo6YmVmb3Jle2JhY2tncm91bmQ6bGluZWFyLWdyYWRpZW50KHRvIHJpZ2h0LCByZ2JhKDI1NSwgMTUyLCAwLCAwLjIpIDUwJSwgcmdiYSgyNDksIDE3MSwgMCwgMC4yKSA1MCUpfS5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLWVuZDo6YmVmb3JlLC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgW2Rpcj1ydGxdIC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWJyaWRnZS1zdGFydDo6YmVmb3Jle2JhY2tncm91bmQ6bGluZWFyLWdyYWRpZW50KHRvIGxlZnQsIHJnYmEoMjU1LCAxNTIsIDAsIDAuMikgNTAlLCByZ2JhKDI0OSwgMTcxLCAwLCAwLjIpIDUwJSl9Lm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktaW4tcmFuZ2U+Lm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsLC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgLm1hdC1jYWxlbmRhci1ib2R5LWluLWNvbXBhcmlzb24tcmFuZ2UubWF0LWNhbGVuZGFyLWJvZHktaW4tcmFuZ2U6OmFmdGVye2JhY2tncm91bmQ6I2E4ZGFiNX0ubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtYWNjZW50IC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCwubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtYWNjZW50IC5tYXQtY2FsZW5kYXItYm9keS1pbi1jb21wYXJpc29uLXJhbmdlPi5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZHtiYWNrZ3JvdW5kOiM0NmEzNWV9Lm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWR7YmFja2dyb3VuZC1jb2xvcjojZmY5ODAwO2NvbG9yOiNmZmZ9Lm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktZGlzYWJsZWQ+Lm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVke2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMTUyLDAsLjQpfS5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgLm1hdC1jYWxlbmRhci1ib2R5LXRvZGF5Lm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVke2JveC1zaGFkb3c6aW5zZXQgMCAwIDAgMXB4ICNmZmZ9Lm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAuY2RrLWtleWJvYXJkLWZvY3VzZWQgLm1hdC1jYWxlbmRhci1ib2R5LWFjdGl2ZT4ubWF0LWNhbGVuZGFyLWJvZHktY2VsbC1jb250ZW50Om5vdCgubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWQpOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwpLC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgLmNkay1wcm9ncmFtLWZvY3VzZWQgLm1hdC1jYWxlbmRhci1ib2R5LWFjdGl2ZT4ubWF0LWNhbGVuZGFyLWJvZHktY2VsbC1jb250ZW50Om5vdCgubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWQpOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMTUyLDAsLjMpfUBtZWRpYShob3ZlcjogaG92ZXIpey5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgLm1hdC1jYWxlbmRhci1ib2R5LWNlbGw6bm90KC5tYXQtY2FsZW5kYXItYm9keS1kaXNhYmxlZCk6aG92ZXI+Lm1hdC1jYWxlbmRhci1ib2R5LWNlbGwtY29udGVudDpub3QoLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVkKTpub3QoLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsKXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDE1MiwwLC4zKX19Lm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLm1hdC1jYWxlbmRhci1ib2R5LWluLXJhbmdlOjpiZWZvcmV7YmFja2dyb3VuZDpyZ2JhKDI0NCw2Nyw1NCwuMil9Lm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsLC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5tYXQtY2FsZW5kYXItYm9keS1pbi1jb21wYXJpc29uLXJhbmdlOjpiZWZvcmV7YmFja2dyb3VuZDpyZ2JhKDI0OSwxNzEsMCwuMil9Lm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLXN0YXJ0OjpiZWZvcmUsLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gW2Rpcj1ydGxdIC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWJyaWRnZS1lbmQ6OmJlZm9yZXtiYWNrZ3JvdW5kOmxpbmVhci1ncmFkaWVudCh0byByaWdodCwgcmdiYSgyNDQsIDY3LCA1NCwgMC4yKSA1MCUsIHJnYmEoMjQ5LCAxNzEsIDAsIDAuMikgNTAlKX0ubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiAubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1icmlkZ2UtZW5kOjpiZWZvcmUsLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gW2Rpcj1ydGxdIC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWJyaWRnZS1zdGFydDo6YmVmb3Jle2JhY2tncm91bmQ6bGluZWFyLWdyYWRpZW50KHRvIGxlZnQsIHJnYmEoMjQ0LCA2NywgNTQsIDAuMikgNTAlLCByZ2JhKDI0OSwgMTcxLCAwLCAwLjIpIDUwJSl9Lm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLm1hdC1jYWxlbmRhci1ib2R5LWluLXJhbmdlPi5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCwubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiAubWF0LWNhbGVuZGFyLWJvZHktaW4tY29tcGFyaXNvbi1yYW5nZS5tYXQtY2FsZW5kYXItYm9keS1pbi1yYW5nZTo6YWZ0ZXJ7YmFja2dyb3VuZDojYThkYWI1fS5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCwubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiAubWF0LWNhbGVuZGFyLWJvZHktaW4tY29tcGFyaXNvbi1yYW5nZT4ubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWR7YmFja2dyb3VuZDojNDZhMzVlfS5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZHtiYWNrZ3JvdW5kLWNvbG9yOiNmNDQzMzY7Y29sb3I6I2ZmZn0ubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiAubWF0LWNhbGVuZGFyLWJvZHktZGlzYWJsZWQ+Lm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVke2JhY2tncm91bmQtY29sb3I6cmdiYSgyNDQsNjcsNTQsLjQpfS5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5tYXQtY2FsZW5kYXItYm9keS10b2RheS5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZHtib3gtc2hhZG93Omluc2V0IDAgMCAwIDFweCAjZmZmfS5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWNhbGVuZGFyLWJvZHktYWN0aXZlPi5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQ6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCksLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLmNkay1wcm9ncmFtLWZvY3VzZWQgLm1hdC1jYWxlbmRhci1ib2R5LWFjdGl2ZT4ubWF0LWNhbGVuZGFyLWJvZHktY2VsbC1jb250ZW50Om5vdCgubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWQpOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNDQsNjcsNTQsLjMpfUBtZWRpYShob3ZlcjogaG92ZXIpey5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5tYXQtY2FsZW5kYXItYm9keS1jZWxsOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktZGlzYWJsZWQpOmhvdmVyPi5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQ6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI0NCw2Nyw1NCwuMyl9fS5tYXQtZGF0ZXBpY2tlci1jb250ZW50LXRvdWNoe2JveC1zaGFkb3c6MHB4IDExcHggMTVweCAtN3B4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMjRweCAzOHB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA5cHggNDZweCA4cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWRhdGVwaWNrZXItdG9nZ2xlLWFjdGl2ZXtjb2xvcjojZjU3YzAwfS5tYXQtZGF0ZXBpY2tlci10b2dnbGUtYWN0aXZlLm1hdC1hY2NlbnR7Y29sb3I6I2ZmOTgwMH0ubWF0LWRhdGVwaWNrZXItdG9nZ2xlLWFjdGl2ZS5tYXQtd2Fybntjb2xvcjojZjQ0MzM2fS5tYXQtZGF0ZS1yYW5nZS1pbnB1dC1pbm5lcltkaXNhYmxlZF17Y29sb3I6Izc1NzU3NX0ubWF0LWRpYWxvZy1jb250YWluZXJ7Ym94LXNoYWRvdzowcHggMTFweCAxNXB4IC03cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAyNHB4IDM4cHggM3B4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDlweCA0NnB4IDhweCByZ2JhKDAsIDAsIDAsIDAuMTIpO2JhY2tncm91bmQ6I2ZmZjtjb2xvcjojMjEyMTIxfS5tYXQtZGl2aWRlcntib3JkZXItdG9wLWNvbG9yOnJnYmEoMCwwLDAsLjEyKX0ubWF0LWRpdmlkZXItdmVydGljYWx7Ym9yZGVyLXJpZ2h0LWNvbG9yOnJnYmEoMCwwLDAsLjEyKX0ubWF0LWV4cGFuc2lvbi1wYW5lbHtiYWNrZ3JvdW5kOiNmZmY7Y29sb3I6IzIxMjEyMX0ubWF0LWV4cGFuc2lvbi1wYW5lbDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCAzcHggMXB4IC0ycHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggNXB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtYWN0aW9uLXJvd3tib3JkZXItdG9wLWNvbG9yOnJnYmEoMCwwLDAsLjEyKX0ubWF0LWV4cGFuc2lvbi1wYW5lbCAubWF0LWV4cGFuc2lvbi1wYW5lbC1oZWFkZXIuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KFthcmlhLWRpc2FibGVkPXRydWVdKSwubWF0LWV4cGFuc2lvbi1wYW5lbCAubWF0LWV4cGFuc2lvbi1wYW5lbC1oZWFkZXIuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoW2FyaWEtZGlzYWJsZWQ9dHJ1ZV0pLC5tYXQtZXhwYW5zaW9uLXBhbmVsOm5vdCgubWF0LWV4cGFuZGVkKSAubWF0LWV4cGFuc2lvbi1wYW5lbC1oZWFkZXI6aG92ZXI6bm90KFthcmlhLWRpc2FibGVkPXRydWVdKXtiYWNrZ3JvdW5kOnJnYmEoMCwwLDAsLjA0KX1AbWVkaWEoaG92ZXI6IG5vbmUpey5tYXQtZXhwYW5zaW9uLXBhbmVsOm5vdCgubWF0LWV4cGFuZGVkKTpub3QoW2FyaWEtZGlzYWJsZWQ9dHJ1ZV0pIC5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlcjpob3ZlcntiYWNrZ3JvdW5kOiNmZmZ9fS5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlci10aXRsZXtjb2xvcjojMjEyMTIxfS5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlci1kZXNjcmlwdGlvbiwubWF0LWV4cGFuc2lvbi1pbmRpY2F0b3I6OmFmdGVye2NvbG9yOiM2MTYxNjF9Lm1hdC1leHBhbnNpb24tcGFuZWwtaGVhZGVyW2FyaWEtZGlzYWJsZWQ9dHJ1ZV17Y29sb3I6cmdiYSgwLDAsMCwuMjYpfS5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlclthcmlhLWRpc2FibGVkPXRydWVdIC5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlci10aXRsZSwubWF0LWV4cGFuc2lvbi1wYW5lbC1oZWFkZXJbYXJpYS1kaXNhYmxlZD10cnVlXSAubWF0LWV4cGFuc2lvbi1wYW5lbC1oZWFkZXItZGVzY3JpcHRpb257Y29sb3I6aW5oZXJpdH0ubWF0LWV4cGFuc2lvbi1wYW5lbC1oZWFkZXJ7aGVpZ2h0OjQ4cHh9Lm1hdC1leHBhbnNpb24tcGFuZWwtaGVhZGVyLm1hdC1leHBhbmRlZHtoZWlnaHQ6NjRweH0ubWF0LWZvcm0tZmllbGQtbGFiZWx7Y29sb3I6cmdiYSg5Nyw5Nyw5NywuNil9Lm1hdC1oaW50e2NvbG9yOnJnYmEoOTcsOTcsOTcsLjYpfS5tYXQtZm9ybS1maWVsZC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtbGFiZWx7Y29sb3I6I2Y1N2MwMH0ubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQgLm1hdC1mb3JtLWZpZWxkLWxhYmVsLm1hdC1hY2NlbnR7Y29sb3I6I2ZmOTgwMH0ubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQgLm1hdC1mb3JtLWZpZWxkLWxhYmVsLm1hdC13YXJue2NvbG9yOiNmNDQzMzZ9Lm1hdC1mb2N1c2VkIC5tYXQtZm9ybS1maWVsZC1yZXF1aXJlZC1tYXJrZXJ7Y29sb3I6I2ZmOTgwMH0ubWF0LWZvcm0tZmllbGQtcmlwcGxle2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuODcpfS5tYXQtZm9ybS1maWVsZC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtcmlwcGxle2JhY2tncm91bmQtY29sb3I6I2Y1N2MwMH0ubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZS5tYXQtYWNjZW50e2JhY2tncm91bmQtY29sb3I6I2ZmOTgwMH0ubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZS5tYXQtd2FybntiYWNrZ3JvdW5kLWNvbG9yOiNmNDQzMzZ9Lm1hdC1mb3JtLWZpZWxkLXR5cGUtbWF0LW5hdGl2ZS1zZWxlY3QubWF0LWZvY3VzZWQ6bm90KC5tYXQtZm9ybS1maWVsZC1pbnZhbGlkKSAubWF0LWZvcm0tZmllbGQtaW5maXg6OmFmdGVye2NvbG9yOiNmNTdjMDB9Lm1hdC1mb3JtLWZpZWxkLXR5cGUtbWF0LW5hdGl2ZS1zZWxlY3QubWF0LWZvY3VzZWQ6bm90KC5tYXQtZm9ybS1maWVsZC1pbnZhbGlkKS5tYXQtYWNjZW50IC5tYXQtZm9ybS1maWVsZC1pbmZpeDo6YWZ0ZXJ7Y29sb3I6I2ZmOTgwMH0ubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtbmF0aXZlLXNlbGVjdC5tYXQtZm9jdXNlZDpub3QoLm1hdC1mb3JtLWZpZWxkLWludmFsaWQpLm1hdC13YXJuIC5tYXQtZm9ybS1maWVsZC1pbmZpeDo6YWZ0ZXJ7Y29sb3I6I2Y0NDMzNn0ubWF0LWZvcm0tZmllbGQubWF0LWZvcm0tZmllbGQtaW52YWxpZCAubWF0LWZvcm0tZmllbGQtbGFiZWx7Y29sb3I6I2Y0NDMzNn0ubWF0LWZvcm0tZmllbGQubWF0LWZvcm0tZmllbGQtaW52YWxpZCAubWF0LWZvcm0tZmllbGQtbGFiZWwubWF0LWFjY2VudCwubWF0LWZvcm0tZmllbGQubWF0LWZvcm0tZmllbGQtaW52YWxpZCAubWF0LWZvcm0tZmllbGQtbGFiZWwgLm1hdC1mb3JtLWZpZWxkLXJlcXVpcmVkLW1hcmtlcntjb2xvcjojZjQ0MzM2fS5tYXQtZm9ybS1maWVsZC5tYXQtZm9ybS1maWVsZC1pbnZhbGlkIC5tYXQtZm9ybS1maWVsZC1yaXBwbGUsLm1hdC1mb3JtLWZpZWxkLm1hdC1mb3JtLWZpZWxkLWludmFsaWQgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZS5tYXQtYWNjZW50e2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNn0ubWF0LWVycm9ye2NvbG9yOiNmNDQzMzZ9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5IC5tYXQtZm9ybS1maWVsZC1sYWJlbHtjb2xvcjojNjE2MTYxfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSAubWF0LWhpbnR7Y29sb3I6IzYxNjE2MX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjQyKX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQgLm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtiYWNrZ3JvdW5kLWltYWdlOmxpbmVhci1ncmFkaWVudCh0byByaWdodCwgcmdiYSgwLCAwLCAwLCAwLjQyKSAwJSwgcmdiYSgwLCAwLCAwLCAwLjQyKSAzMyUsIHRyYW5zcGFyZW50IDAlKTtiYWNrZ3JvdW5kLXNpemU6NHB4IDEwMCU7YmFja2dyb3VuZC1yZXBlYXQ6cmVwZWF0LXh9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utc3RhbmRhcmQgLm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjQyKX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1zdGFuZGFyZC5tYXQtZm9ybS1maWVsZC1kaXNhYmxlZCAubWF0LWZvcm0tZmllbGQtdW5kZXJsaW5le2JhY2tncm91bmQtaW1hZ2U6bGluZWFyLWdyYWRpZW50KHRvIHJpZ2h0LCByZ2JhKDAsIDAsIDAsIDAuNDIpIDAlLCByZ2JhKDAsIDAsIDAsIDAuNDIpIDMzJSwgdHJhbnNwYXJlbnQgMCUpO2JhY2tncm91bmQtc2l6ZTo0cHggMTAwJTtiYWNrZ3JvdW5kLXJlcGVhdDpyZXBlYXQteH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsIC5tYXQtZm9ybS1maWVsZC1mbGV4e2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMDQpfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGwubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQgLm1hdC1mb3JtLWZpZWxkLWZsZXh7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4wMil9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbCAubWF0LWZvcm0tZmllbGQtdW5kZXJsaW5lOjpiZWZvcmV7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC40Mil9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbC5tYXQtZm9ybS1maWVsZC1kaXNhYmxlZCAubWF0LWZvcm0tZmllbGQtbGFiZWx7Y29sb3I6Izc1NzU3NX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsLm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkIC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmU6OmJlZm9yZXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCl9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtb3V0bGluZXtjb2xvcjpyZ2JhKDAsMCwwLC4xMil9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tjb2xvcjpyZ2JhKDAsMCwwLC44Nyl9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tjb2xvcjojZjU3YzAwfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvY3VzZWQubWF0LWFjY2VudCAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tjb2xvcjojZmY5ODAwfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvY3VzZWQubWF0LXdhcm4gLm1hdC1mb3JtLWZpZWxkLW91dGxpbmUtdGhpY2t7Y29sb3I6I2Y0NDMzNn0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lLm1hdC1mb3JtLWZpZWxkLWludmFsaWQubWF0LWZvcm0tZmllbGQtaW52YWxpZCAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tjb2xvcjojZjQ0MzM2fS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQgLm1hdC1mb3JtLWZpZWxkLWxhYmVse2NvbG9yOiM3NTc1NzV9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9ybS1maWVsZC1kaXNhYmxlZCAubWF0LWZvcm0tZmllbGQtb3V0bGluZXtjb2xvcjpyZ2JhKDAsMCwwLC4wNil9Lm1hdC1pY29uLm1hdC1wcmltYXJ5e2NvbG9yOiNmNTdjMDB9Lm1hdC1pY29uLm1hdC1hY2NlbnR7Y29sb3I6I2ZmOTgwMH0ubWF0LWljb24ubWF0LXdhcm57Y29sb3I6I2Y0NDMzNn0ubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtbmF0aXZlLXNlbGVjdCAubWF0LWZvcm0tZmllbGQtaW5maXg6OmFmdGVye2NvbG9yOiM2MTYxNjF9Lm1hdC1pbnB1dC1lbGVtZW50OmRpc2FibGVkLC5tYXQtZm9ybS1maWVsZC10eXBlLW1hdC1uYXRpdmUtc2VsZWN0Lm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkIC5tYXQtZm9ybS1maWVsZC1pbmZpeDo6YWZ0ZXJ7Y29sb3I6Izc1NzU3NX0ubWF0LWlucHV0LWVsZW1lbnR7Y2FyZXQtY29sb3I6I2Y1N2MwMH0ubWF0LWlucHV0LWVsZW1lbnQ6OnBsYWNlaG9sZGVye2NvbG9yOnJnYmEoOTcsOTcsOTcsLjQyKX0ubWF0LWlucHV0LWVsZW1lbnQ6Oi1tb3otcGxhY2Vob2xkZXJ7Y29sb3I6cmdiYSg5Nyw5Nyw5NywuNDIpfS5tYXQtaW5wdXQtZWxlbWVudDo6LXdlYmtpdC1pbnB1dC1wbGFjZWhvbGRlcntjb2xvcjpyZ2JhKDk3LDk3LDk3LC40Mil9Lm1hdC1pbnB1dC1lbGVtZW50Oi1tcy1pbnB1dC1wbGFjZWhvbGRlcntjb2xvcjpyZ2JhKDk3LDk3LDk3LC40Mil9Lm1hdC1mb3JtLWZpZWxkLm1hdC1hY2NlbnQgLm1hdC1pbnB1dC1lbGVtZW50e2NhcmV0LWNvbG9yOiNmZjk4MDB9Lm1hdC1mb3JtLWZpZWxkLm1hdC13YXJuIC5tYXQtaW5wdXQtZWxlbWVudCwubWF0LWZvcm0tZmllbGQtaW52YWxpZCAubWF0LWlucHV0LWVsZW1lbnR7Y2FyZXQtY29sb3I6I2Y0NDMzNn0ubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtbmF0aXZlLXNlbGVjdC5tYXQtZm9ybS1maWVsZC1pbnZhbGlkIC5tYXQtZm9ybS1maWVsZC1pbmZpeDo6YWZ0ZXJ7Y29sb3I6I2Y0NDMzNn0ubWF0LWxpc3QtYmFzZSAubWF0LWxpc3QtaXRlbXtjb2xvcjojMjEyMTIxfS5tYXQtbGlzdC1iYXNlIC5tYXQtbGlzdC1vcHRpb257Y29sb3I6IzIxMjEyMX0ubWF0LWxpc3QtYmFzZSAubWF0LXN1YmhlYWRlcntjb2xvcjojNjE2MTYxfS5tYXQtbGlzdC1iYXNlIC5tYXQtbGlzdC1pdGVtLWRpc2FibGVke2JhY2tncm91bmQtY29sb3I6I2VlZTtjb2xvcjojNzU3NTc1fS5tYXQtbGlzdC1vcHRpb246aG92ZXIsLm1hdC1saXN0LW9wdGlvbjpmb2N1cywubWF0LW5hdi1saXN0IC5tYXQtbGlzdC1pdGVtOmhvdmVyLC5tYXQtbmF2LWxpc3QgLm1hdC1saXN0LWl0ZW06Zm9jdXMsLm1hdC1hY3Rpb24tbGlzdCAubWF0LWxpc3QtaXRlbTpob3ZlciwubWF0LWFjdGlvbi1saXN0IC5tYXQtbGlzdC1pdGVtOmZvY3Vze2JhY2tncm91bmQ6cmdiYSgwLDAsMCwuMDQpfS5tYXQtbGlzdC1zaW5nbGUtc2VsZWN0ZWQtb3B0aW9uLC5tYXQtbGlzdC1zaW5nbGUtc2VsZWN0ZWQtb3B0aW9uOmhvdmVyLC5tYXQtbGlzdC1zaW5nbGUtc2VsZWN0ZWQtb3B0aW9uOmZvY3Vze2JhY2tncm91bmQ6cmdiYSgwLDAsMCwuMTIpfS5tYXQtbWVudS1wYW5lbHtiYWNrZ3JvdW5kOiNmZmZ9Lm1hdC1tZW51LXBhbmVsOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pe2JveC1zaGFkb3c6MHB4IDJweCA0cHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDRweCA1cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAxMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfS5tYXQtbWVudS1pdGVte2JhY2tncm91bmQ6cmdiYSgwLDAsMCwwKTtjb2xvcjojMjEyMTIxfS5tYXQtbWVudS1pdGVtW2Rpc2FibGVkXSwubWF0LW1lbnUtaXRlbVtkaXNhYmxlZF0gLm1hdC1tZW51LXN1Ym1lbnUtaWNvbiwubWF0LW1lbnUtaXRlbVtkaXNhYmxlZF0gLm1hdC1pY29uLW5vLWNvbG9ye2NvbG9yOnJnYmEoMCwwLDAsLjM4KX0ubWF0LW1lbnUtaXRlbSAubWF0LWljb24tbm8tY29sb3IsLm1hdC1tZW51LXN1Ym1lbnUtaWNvbntjb2xvcjpyZ2JhKDAsMCwwLC41NCl9Lm1hdC1tZW51LWl0ZW06aG92ZXI6bm90KFtkaXNhYmxlZF0pLC5tYXQtbWVudS1pdGVtLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KFtkaXNhYmxlZF0pLC5tYXQtbWVudS1pdGVtLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdChbZGlzYWJsZWRdKSwubWF0LW1lbnUtaXRlbS1oaWdobGlnaHRlZDpub3QoW2Rpc2FibGVkXSl7YmFja2dyb3VuZDpyZ2JhKDAsMCwwLC4wNCl9Lm1hdC1wYWdpbmF0b3J7YmFja2dyb3VuZDojZmZmfS5tYXQtcGFnaW5hdG9yLC5tYXQtcGFnaW5hdG9yLXBhZ2Utc2l6ZSAubWF0LXNlbGVjdC10cmlnZ2Vye2NvbG9yOiM2MTYxNjF9Lm1hdC1wYWdpbmF0b3ItZGVjcmVtZW50LC5tYXQtcGFnaW5hdG9yLWluY3JlbWVudHtib3JkZXItdG9wOjJweCBzb2xpZCByZ2JhKDAsMCwwLC41NCk7Ym9yZGVyLXJpZ2h0OjJweCBzb2xpZCByZ2JhKDAsMCwwLC41NCl9Lm1hdC1wYWdpbmF0b3ItZmlyc3QsLm1hdC1wYWdpbmF0b3ItbGFzdHtib3JkZXItdG9wOjJweCBzb2xpZCByZ2JhKDAsMCwwLC41NCl9Lm1hdC1pY29uLWJ1dHRvbltkaXNhYmxlZF0gLm1hdC1wYWdpbmF0b3ItZGVjcmVtZW50LC5tYXQtaWNvbi1idXR0b25bZGlzYWJsZWRdIC5tYXQtcGFnaW5hdG9yLWluY3JlbWVudCwubWF0LWljb24tYnV0dG9uW2Rpc2FibGVkXSAubWF0LXBhZ2luYXRvci1maXJzdCwubWF0LWljb24tYnV0dG9uW2Rpc2FibGVkXSAubWF0LXBhZ2luYXRvci1sYXN0e2JvcmRlci1jb2xvcjpyZ2JhKDAsMCwwLC4zOCl9Lm1hdC1wYWdpbmF0b3ItY29udGFpbmVye21pbi1oZWlnaHQ6NTZweH0ubWF0LXByb2dyZXNzLWJhci1iYWNrZ3JvdW5ke2ZpbGw6I2ZkZGViZn0ubWF0LXByb2dyZXNzLWJhci1idWZmZXJ7YmFja2dyb3VuZC1jb2xvcjojZmRkZWJmfS5tYXQtcHJvZ3Jlc3MtYmFyLWZpbGw6OmFmdGVye2JhY2tncm91bmQtY29sb3I6I2Y1N2MwMH0ubWF0LXByb2dyZXNzLWJhci5tYXQtYWNjZW50IC5tYXQtcHJvZ3Jlc3MtYmFyLWJhY2tncm91bmR7ZmlsbDojZmZlNWJmfS5tYXQtcHJvZ3Jlc3MtYmFyLm1hdC1hY2NlbnQgLm1hdC1wcm9ncmVzcy1iYXItYnVmZmVye2JhY2tncm91bmQtY29sb3I6I2ZmZTViZn0ubWF0LXByb2dyZXNzLWJhci5tYXQtYWNjZW50IC5tYXQtcHJvZ3Jlc3MtYmFyLWZpbGw6OmFmdGVye2JhY2tncm91bmQtY29sb3I6I2ZmOTgwMH0ubWF0LXByb2dyZXNzLWJhci5tYXQtd2FybiAubWF0LXByb2dyZXNzLWJhci1iYWNrZ3JvdW5ke2ZpbGw6I2ZjZDBjZH0ubWF0LXByb2dyZXNzLWJhci5tYXQtd2FybiAubWF0LXByb2dyZXNzLWJhci1idWZmZXJ7YmFja2dyb3VuZC1jb2xvcjojZmNkMGNkfS5tYXQtcHJvZ3Jlc3MtYmFyLm1hdC13YXJuIC5tYXQtcHJvZ3Jlc3MtYmFyLWZpbGw6OmFmdGVye2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNn0ubWF0LXByb2dyZXNzLXNwaW5uZXIgY2lyY2xlLC5tYXQtc3Bpbm5lciBjaXJjbGV7c3Ryb2tlOiNmNTdjMDB9Lm1hdC1wcm9ncmVzcy1zcGlubmVyLm1hdC1hY2NlbnQgY2lyY2xlLC5tYXQtc3Bpbm5lci5tYXQtYWNjZW50IGNpcmNsZXtzdHJva2U6I2ZmOTgwMH0ubWF0LXByb2dyZXNzLXNwaW5uZXIubWF0LXdhcm4gY2lyY2xlLC5tYXQtc3Bpbm5lci5tYXQtd2FybiBjaXJjbGV7c3Ryb2tlOiNmNDQzMzZ9Lm1hdC1yYWRpby1vdXRlci1jaXJjbGV7Ym9yZGVyLWNvbG9yOiM2MTYxNjF9Lm1hdC1yYWRpby1idXR0b24ubWF0LXByaW1hcnkubWF0LXJhZGlvLWNoZWNrZWQgLm1hdC1yYWRpby1vdXRlci1jaXJjbGV7Ym9yZGVyLWNvbG9yOiNmNTdjMDB9Lm1hdC1yYWRpby1idXR0b24ubWF0LXByaW1hcnkgLm1hdC1yYWRpby1pbm5lci1jaXJjbGUsLm1hdC1yYWRpby1idXR0b24ubWF0LXByaW1hcnkgLm1hdC1yYWRpby1yaXBwbGUgLm1hdC1yaXBwbGUtZWxlbWVudDpub3QoLm1hdC1yYWRpby1wZXJzaXN0ZW50LXJpcHBsZSksLm1hdC1yYWRpby1idXR0b24ubWF0LXByaW1hcnkubWF0LXJhZGlvLWNoZWNrZWQgLm1hdC1yYWRpby1wZXJzaXN0ZW50LXJpcHBsZSwubWF0LXJhZGlvLWJ1dHRvbi5tYXQtcHJpbWFyeTphY3RpdmUgLm1hdC1yYWRpby1wZXJzaXN0ZW50LXJpcHBsZXtiYWNrZ3JvdW5kLWNvbG9yOiNmNTdjMDB9Lm1hdC1yYWRpby1idXR0b24ubWF0LWFjY2VudC5tYXQtcmFkaW8tY2hlY2tlZCAubWF0LXJhZGlvLW91dGVyLWNpcmNsZXtib3JkZXItY29sb3I6I2ZmOTgwMH0ubWF0LXJhZGlvLWJ1dHRvbi5tYXQtYWNjZW50IC5tYXQtcmFkaW8taW5uZXItY2lyY2xlLC5tYXQtcmFkaW8tYnV0dG9uLm1hdC1hY2NlbnQgLm1hdC1yYWRpby1yaXBwbGUgLm1hdC1yaXBwbGUtZWxlbWVudDpub3QoLm1hdC1yYWRpby1wZXJzaXN0ZW50LXJpcHBsZSksLm1hdC1yYWRpby1idXR0b24ubWF0LWFjY2VudC5tYXQtcmFkaW8tY2hlY2tlZCAubWF0LXJhZGlvLXBlcnNpc3RlbnQtcmlwcGxlLC5tYXQtcmFkaW8tYnV0dG9uLm1hdC1hY2NlbnQ6YWN0aXZlIC5tYXQtcmFkaW8tcGVyc2lzdGVudC1yaXBwbGV7YmFja2dyb3VuZC1jb2xvcjojZmY5ODAwfS5tYXQtcmFkaW8tYnV0dG9uLm1hdC13YXJuLm1hdC1yYWRpby1jaGVja2VkIC5tYXQtcmFkaW8tb3V0ZXItY2lyY2xle2JvcmRlci1jb2xvcjojZjQ0MzM2fS5tYXQtcmFkaW8tYnV0dG9uLm1hdC13YXJuIC5tYXQtcmFkaW8taW5uZXItY2lyY2xlLC5tYXQtcmFkaW8tYnV0dG9uLm1hdC13YXJuIC5tYXQtcmFkaW8tcmlwcGxlIC5tYXQtcmlwcGxlLWVsZW1lbnQ6bm90KC5tYXQtcmFkaW8tcGVyc2lzdGVudC1yaXBwbGUpLC5tYXQtcmFkaW8tYnV0dG9uLm1hdC13YXJuLm1hdC1yYWRpby1jaGVja2VkIC5tYXQtcmFkaW8tcGVyc2lzdGVudC1yaXBwbGUsLm1hdC1yYWRpby1idXR0b24ubWF0LXdhcm46YWN0aXZlIC5tYXQtcmFkaW8tcGVyc2lzdGVudC1yaXBwbGV7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fS5tYXQtcmFkaW8tYnV0dG9uLm1hdC1yYWRpby1kaXNhYmxlZC5tYXQtcmFkaW8tY2hlY2tlZCAubWF0LXJhZGlvLW91dGVyLWNpcmNsZSwubWF0LXJhZGlvLWJ1dHRvbi5tYXQtcmFkaW8tZGlzYWJsZWQgLm1hdC1yYWRpby1vdXRlci1jaXJjbGV7Ym9yZGVyLWNvbG9yOnJnYmEoMCwwLDAsLjM4KX0ubWF0LXJhZGlvLWJ1dHRvbi5tYXQtcmFkaW8tZGlzYWJsZWQgLm1hdC1yYWRpby1yaXBwbGUgLm1hdC1yaXBwbGUtZWxlbWVudCwubWF0LXJhZGlvLWJ1dHRvbi5tYXQtcmFkaW8tZGlzYWJsZWQgLm1hdC1yYWRpby1pbm5lci1jaXJjbGV7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4zOCl9Lm1hdC1yYWRpby1idXR0b24ubWF0LXJhZGlvLWRpc2FibGVkIC5tYXQtcmFkaW8tbGFiZWwtY29udGVudHtjb2xvcjpyZ2JhKDAsMCwwLC4zOCl9Lm1hdC1yYWRpby1idXR0b24gLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDB9Lm1hdC1zZWxlY3QtdmFsdWV7Y29sb3I6IzIxMjEyMX0ubWF0LXNlbGVjdC1wbGFjZWhvbGRlcntjb2xvcjpyZ2JhKDk3LDk3LDk3LC40Mil9Lm1hdC1zZWxlY3QtZGlzYWJsZWQgLm1hdC1zZWxlY3QtdmFsdWV7Y29sb3I6Izc1NzU3NX0ubWF0LXNlbGVjdC1hcnJvd3tjb2xvcjojNjE2MTYxfS5tYXQtc2VsZWN0LXBhbmVse2JhY2tncm91bmQ6I2ZmZn0ubWF0LXNlbGVjdC1wYW5lbDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCAycHggNHB4IC0xcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCA0cHggNXB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggMTBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LXNlbGVjdC1wYW5lbCAubWF0LW9wdGlvbi5tYXQtc2VsZWN0ZWQ6bm90KC5tYXQtb3B0aW9uLW11bHRpcGxlKXtiYWNrZ3JvdW5kOnJnYmEoMCwwLDAsLjEyKX0ubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQubWF0LXByaW1hcnkgLm1hdC1zZWxlY3QtYXJyb3d7Y29sb3I6I2Y1N2MwMH0ubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQubWF0LWFjY2VudCAubWF0LXNlbGVjdC1hcnJvd3tjb2xvcjojZmY5ODAwfS5tYXQtZm9ybS1maWVsZC5tYXQtZm9jdXNlZC5tYXQtd2FybiAubWF0LXNlbGVjdC1hcnJvd3tjb2xvcjojZjQ0MzM2fS5tYXQtZm9ybS1maWVsZCAubWF0LXNlbGVjdC5tYXQtc2VsZWN0LWludmFsaWQgLm1hdC1zZWxlY3QtYXJyb3d7Y29sb3I6I2Y0NDMzNn0ubWF0LWZvcm0tZmllbGQgLm1hdC1zZWxlY3QubWF0LXNlbGVjdC1kaXNhYmxlZCAubWF0LXNlbGVjdC1hcnJvd3tjb2xvcjojNzU3NTc1fS5tYXQtZHJhd2VyLWNvbnRhaW5lcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7Y29sb3I6IzIxMjEyMX0ubWF0LWRyYXdlcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7Y29sb3I6IzIxMjEyMX0ubWF0LWRyYXdlci5tYXQtZHJhd2VyLXB1c2h7YmFja2dyb3VuZC1jb2xvcjojZmZmfS5tYXQtZHJhd2VyOm5vdCgubWF0LWRyYXdlci1zaWRlKXtib3gtc2hhZG93OjBweCA4cHggMTBweCAtNXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTZweCAyNHB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA2cHggMzBweCA1cHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LWRyYXdlci1zaWRle2JvcmRlci1yaWdodDpzb2xpZCAxcHggcmdiYSgwLDAsMCwuMTIpfS5tYXQtZHJhd2VyLXNpZGUubWF0LWRyYXdlci1lbmR7Ym9yZGVyLWxlZnQ6c29saWQgMXB4IHJnYmEoMCwwLDAsLjEyKTtib3JkZXItcmlnaHQ6bm9uZX1bZGlyPXJ0bF0gLm1hdC1kcmF3ZXItc2lkZXtib3JkZXItbGVmdDpzb2xpZCAxcHggcmdiYSgwLDAsMCwuMTIpO2JvcmRlci1yaWdodDpub25lfVtkaXI9cnRsXSAubWF0LWRyYXdlci1zaWRlLm1hdC1kcmF3ZXItZW5ke2JvcmRlci1sZWZ0Om5vbmU7Ym9yZGVyLXJpZ2h0OnNvbGlkIDFweCByZ2JhKDAsMCwwLC4xMil9Lm1hdC1kcmF3ZXItYmFja2Ryb3AubWF0LWRyYXdlci1zaG93bntiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjYpfS5tYXQtc2xpZGUtdG9nZ2xlLm1hdC1jaGVja2VkIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1ie2JhY2tncm91bmQtY29sb3I6I2ZmOTgwMH0ubWF0LXNsaWRlLXRvZ2dsZS5tYXQtY2hlY2tlZCAubWF0LXNsaWRlLXRvZ2dsZS1iYXJ7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwxNTIsMCwuNTQpfS5tYXQtc2xpZGUtdG9nZ2xlLm1hdC1jaGVja2VkIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojZmY5ODAwfS5tYXQtc2xpZGUtdG9nZ2xlLm1hdC1wcmltYXJ5Lm1hdC1jaGVja2VkIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1ie2JhY2tncm91bmQtY29sb3I6I2Y1N2MwMH0ubWF0LXNsaWRlLXRvZ2dsZS5tYXQtcHJpbWFyeS5tYXQtY2hlY2tlZCAubWF0LXNsaWRlLXRvZ2dsZS1iYXJ7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI0NSwxMjQsMCwuNTQpfS5tYXQtc2xpZGUtdG9nZ2xlLm1hdC1wcmltYXJ5Lm1hdC1jaGVja2VkIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojZjU3YzAwfS5tYXQtc2xpZGUtdG9nZ2xlLm1hdC13YXJuLm1hdC1jaGVja2VkIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1ie2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNn0ubWF0LXNsaWRlLXRvZ2dsZS5tYXQtd2Fybi5tYXQtY2hlY2tlZCAubWF0LXNsaWRlLXRvZ2dsZS1iYXJ7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI0NCw2Nyw1NCwuNTQpfS5tYXQtc2xpZGUtdG9nZ2xlLm1hdC13YXJuLm1hdC1jaGVja2VkIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fS5tYXQtc2xpZGUtdG9nZ2xlOm5vdCgubWF0LWNoZWNrZWQpIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojMDAwfS5tYXQtc2xpZGUtdG9nZ2xlLXRodW1ie2JveC1zaGFkb3c6MHB4IDJweCAxcHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDFweCAxcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAzcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMik7YmFja2dyb3VuZC1jb2xvcjojZmFmYWZhfS5tYXQtc2xpZGUtdG9nZ2xlLWJhcntiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjM4KX0ubWF0LXNsaWRlci10cmFjay1iYWNrZ3JvdW5ke2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMjYpfS5tYXQtc2xpZGVyLm1hdC1wcmltYXJ5IC5tYXQtc2xpZGVyLXRyYWNrLWZpbGwsLm1hdC1zbGlkZXIubWF0LXByaW1hcnkgLm1hdC1zbGlkZXItdGh1bWIsLm1hdC1zbGlkZXIubWF0LXByaW1hcnkgLm1hdC1zbGlkZXItdGh1bWItbGFiZWx7YmFja2dyb3VuZC1jb2xvcjojZjU3YzAwfS5tYXQtc2xpZGVyLm1hdC1wcmltYXJ5IC5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXRleHR7Y29sb3I6I2ZmZn0ubWF0LXNsaWRlci5tYXQtcHJpbWFyeSAubWF0LXNsaWRlci1mb2N1cy1yaW5ne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNDUsMTI0LDAsLjIpfS5tYXQtc2xpZGVyLm1hdC1hY2NlbnQgLm1hdC1zbGlkZXItdHJhY2stZmlsbCwubWF0LXNsaWRlci5tYXQtYWNjZW50IC5tYXQtc2xpZGVyLXRodW1iLC5tYXQtc2xpZGVyLm1hdC1hY2NlbnQgLm1hdC1zbGlkZXItdGh1bWItbGFiZWx7YmFja2dyb3VuZC1jb2xvcjojZmY5ODAwfS5tYXQtc2xpZGVyLm1hdC1hY2NlbnQgLm1hdC1zbGlkZXItdGh1bWItbGFiZWwtdGV4dHtjb2xvcjojZmZmfS5tYXQtc2xpZGVyLm1hdC1hY2NlbnQgLm1hdC1zbGlkZXItZm9jdXMtcmluZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDE1MiwwLC4yKX0ubWF0LXNsaWRlci5tYXQtd2FybiAubWF0LXNsaWRlci10cmFjay1maWxsLC5tYXQtc2xpZGVyLm1hdC13YXJuIC5tYXQtc2xpZGVyLXRodW1iLC5tYXQtc2xpZGVyLm1hdC13YXJuIC5tYXQtc2xpZGVyLXRodW1iLWxhYmVse2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNn0ubWF0LXNsaWRlci5tYXQtd2FybiAubWF0LXNsaWRlci10aHVtYi1sYWJlbC10ZXh0e2NvbG9yOiNmZmZ9Lm1hdC1zbGlkZXIubWF0LXdhcm4gLm1hdC1zbGlkZXItZm9jdXMtcmluZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjQ0LDY3LDU0LC4yKX0ubWF0LXNsaWRlcjpob3ZlciAubWF0LXNsaWRlci10cmFjay1iYWNrZ3JvdW5kLC5tYXQtc2xpZGVyLmNkay1mb2N1c2VkIC5tYXQtc2xpZGVyLXRyYWNrLWJhY2tncm91bmR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4zOCl9Lm1hdC1zbGlkZXIubWF0LXNsaWRlci1kaXNhYmxlZCAubWF0LXNsaWRlci10cmFjay1iYWNrZ3JvdW5kLC5tYXQtc2xpZGVyLm1hdC1zbGlkZXItZGlzYWJsZWQgLm1hdC1zbGlkZXItdHJhY2stZmlsbCwubWF0LXNsaWRlci5tYXQtc2xpZGVyLWRpc2FibGVkIC5tYXQtc2xpZGVyLXRodW1ie2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMjYpfS5tYXQtc2xpZGVyLm1hdC1zbGlkZXItZGlzYWJsZWQ6aG92ZXIgLm1hdC1zbGlkZXItdHJhY2stYmFja2dyb3VuZHtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjI2KX0ubWF0LXNsaWRlci5tYXQtc2xpZGVyLW1pbi12YWx1ZSAubWF0LXNsaWRlci1mb2N1cy1yaW5ne2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMTIpfS5tYXQtc2xpZGVyLm1hdC1zbGlkZXItbWluLXZhbHVlLm1hdC1zbGlkZXItdGh1bWItbGFiZWwtc2hvd2luZyAubWF0LXNsaWRlci10aHVtYiwubWF0LXNsaWRlci5tYXQtc2xpZGVyLW1pbi12YWx1ZS5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXNob3dpbmcgLm1hdC1zbGlkZXItdGh1bWItbGFiZWx7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC44Nyl9Lm1hdC1zbGlkZXIubWF0LXNsaWRlci1taW4tdmFsdWUubWF0LXNsaWRlci10aHVtYi1sYWJlbC1zaG93aW5nLmNkay1mb2N1c2VkIC5tYXQtc2xpZGVyLXRodW1iLC5tYXQtc2xpZGVyLm1hdC1zbGlkZXItbWluLXZhbHVlLm1hdC1zbGlkZXItdGh1bWItbGFiZWwtc2hvd2luZy5jZGstZm9jdXNlZCAubWF0LXNsaWRlci10aHVtYi1sYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjI2KX0ubWF0LXNsaWRlci5tYXQtc2xpZGVyLW1pbi12YWx1ZTpub3QoLm1hdC1zbGlkZXItdGh1bWItbGFiZWwtc2hvd2luZykgLm1hdC1zbGlkZXItdGh1bWJ7Ym9yZGVyLWNvbG9yOnJnYmEoMCwwLDAsLjI2KTtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCl9Lm1hdC1zbGlkZXIubWF0LXNsaWRlci1taW4tdmFsdWU6bm90KC5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXNob3dpbmcpOmhvdmVyIC5tYXQtc2xpZGVyLXRodW1iLC5tYXQtc2xpZGVyLm1hdC1zbGlkZXItbWluLXZhbHVlOm5vdCgubWF0LXNsaWRlci10aHVtYi1sYWJlbC1zaG93aW5nKS5jZGstZm9jdXNlZCAubWF0LXNsaWRlci10aHVtYntib3JkZXItY29sb3I6cmdiYSgwLDAsMCwuMzgpfS5tYXQtc2xpZGVyLm1hdC1zbGlkZXItbWluLXZhbHVlOm5vdCgubWF0LXNsaWRlci10aHVtYi1sYWJlbC1zaG93aW5nKTpob3Zlci5tYXQtc2xpZGVyLWRpc2FibGVkIC5tYXQtc2xpZGVyLXRodW1iLC5tYXQtc2xpZGVyLm1hdC1zbGlkZXItbWluLXZhbHVlOm5vdCgubWF0LXNsaWRlci10aHVtYi1sYWJlbC1zaG93aW5nKS5jZGstZm9jdXNlZC5tYXQtc2xpZGVyLWRpc2FibGVkIC5tYXQtc2xpZGVyLXRodW1ie2JvcmRlci1jb2xvcjpyZ2JhKDAsMCwwLC4yNil9Lm1hdC1zbGlkZXItaGFzLXRpY2tzIC5tYXQtc2xpZGVyLXdyYXBwZXI6OmFmdGVye2JvcmRlci1jb2xvcjpyZ2JhKDAsMCwwLC43KX0ubWF0LXNsaWRlci1ob3Jpem9udGFsIC5tYXQtc2xpZGVyLXRpY2tze2JhY2tncm91bmQtaW1hZ2U6cmVwZWF0aW5nLWxpbmVhci1ncmFkaWVudCh0byByaWdodCwgcmdiYSgwLCAwLCAwLCAwLjcpLCByZ2JhKDAsIDAsIDAsIDAuNykgMnB4LCB0cmFuc3BhcmVudCAwLCB0cmFuc3BhcmVudCk7YmFja2dyb3VuZC1pbWFnZTotbW96LXJlcGVhdGluZy1saW5lYXItZ3JhZGllbnQoMC4wMDAxZGVnLCByZ2JhKDAsIDAsIDAsIDAuNyksIHJnYmEoMCwgMCwgMCwgMC43KSAycHgsIHRyYW5zcGFyZW50IDAsIHRyYW5zcGFyZW50KX0ubWF0LXNsaWRlci12ZXJ0aWNhbCAubWF0LXNsaWRlci10aWNrc3tiYWNrZ3JvdW5kLWltYWdlOnJlcGVhdGluZy1saW5lYXItZ3JhZGllbnQodG8gYm90dG9tLCByZ2JhKDAsIDAsIDAsIDAuNyksIHJnYmEoMCwgMCwgMCwgMC43KSAycHgsIHRyYW5zcGFyZW50IDAsIHRyYW5zcGFyZW50KX0ubWF0LXN0ZXAtaGVhZGVyLmNkay1rZXlib2FyZC1mb2N1c2VkLC5tYXQtc3RlcC1oZWFkZXIuY2RrLXByb2dyYW0tZm9jdXNlZCwubWF0LXN0ZXAtaGVhZGVyOmhvdmVyOm5vdChbYXJpYS1kaXNhYmxlZF0pLC5tYXQtc3RlcC1oZWFkZXI6aG92ZXJbYXJpYS1kaXNhYmxlZD1mYWxzZV17YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4wNCl9Lm1hdC1zdGVwLWhlYWRlcjpob3ZlclthcmlhLWRpc2FibGVkPXRydWVde2N1cnNvcjpkZWZhdWx0fUBtZWRpYShob3Zlcjogbm9uZSl7Lm1hdC1zdGVwLWhlYWRlcjpob3ZlcntiYWNrZ3JvdW5kOm5vbmV9fS5tYXQtc3RlcC1oZWFkZXIgLm1hdC1zdGVwLWxhYmVsLC5tYXQtc3RlcC1oZWFkZXIgLm1hdC1zdGVwLW9wdGlvbmFse2NvbG9yOiM2MTYxNjF9Lm1hdC1zdGVwLWhlYWRlciAubWF0LXN0ZXAtaWNvbntiYWNrZ3JvdW5kLWNvbG9yOiM2MTYxNjE7Y29sb3I6I2ZmZn0ubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1pY29uLXNlbGVjdGVkLC5tYXQtc3RlcC1oZWFkZXIgLm1hdC1zdGVwLWljb24tc3RhdGUtZG9uZSwubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1pY29uLXN0YXRlLWVkaXR7YmFja2dyb3VuZC1jb2xvcjojZjU3YzAwO2NvbG9yOiNmZmZ9Lm1hdC1zdGVwLWhlYWRlci5tYXQtYWNjZW50IC5tYXQtc3RlcC1pY29ue2NvbG9yOiNmZmZ9Lm1hdC1zdGVwLWhlYWRlci5tYXQtYWNjZW50IC5tYXQtc3RlcC1pY29uLXNlbGVjdGVkLC5tYXQtc3RlcC1oZWFkZXIubWF0LWFjY2VudCAubWF0LXN0ZXAtaWNvbi1zdGF0ZS1kb25lLC5tYXQtc3RlcC1oZWFkZXIubWF0LWFjY2VudCAubWF0LXN0ZXAtaWNvbi1zdGF0ZS1lZGl0e2JhY2tncm91bmQtY29sb3I6I2ZmOTgwMDtjb2xvcjojZmZmfS5tYXQtc3RlcC1oZWFkZXIubWF0LXdhcm4gLm1hdC1zdGVwLWljb257Y29sb3I6I2ZmZn0ubWF0LXN0ZXAtaGVhZGVyLm1hdC13YXJuIC5tYXQtc3RlcC1pY29uLXNlbGVjdGVkLC5tYXQtc3RlcC1oZWFkZXIubWF0LXdhcm4gLm1hdC1zdGVwLWljb24tc3RhdGUtZG9uZSwubWF0LXN0ZXAtaGVhZGVyLm1hdC13YXJuIC5tYXQtc3RlcC1pY29uLXN0YXRlLWVkaXR7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2O2NvbG9yOiNmZmZ9Lm1hdC1zdGVwLWhlYWRlciAubWF0LXN0ZXAtaWNvbi1zdGF0ZS1lcnJvcntiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCk7Y29sb3I6I2Y0NDMzNn0ubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1sYWJlbC5tYXQtc3RlcC1sYWJlbC1hY3RpdmV7Y29sb3I6IzIxMjEyMX0ubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1sYWJlbC5tYXQtc3RlcC1sYWJlbC1lcnJvcntjb2xvcjojZjQ0MzM2fS5tYXQtc3RlcHBlci1ob3Jpem9udGFsLC5tYXQtc3RlcHBlci12ZXJ0aWNhbHtiYWNrZ3JvdW5kLWNvbG9yOiNmZmZ9Lm1hdC1zdGVwcGVyLXZlcnRpY2FsLWxpbmU6OmJlZm9yZXtib3JkZXItbGVmdC1jb2xvcjpyZ2JhKDAsMCwwLC4xMil9Lm1hdC1ob3Jpem9udGFsLXN0ZXBwZXItaGVhZGVyOjpiZWZvcmUsLm1hdC1ob3Jpem9udGFsLXN0ZXBwZXItaGVhZGVyOjphZnRlciwubWF0LXN0ZXBwZXItaG9yaXpvbnRhbC1saW5le2JvcmRlci10b3AtY29sb3I6cmdiYSgwLDAsMCwuMTIpfS5tYXQtaG9yaXpvbnRhbC1zdGVwcGVyLWhlYWRlcntoZWlnaHQ6NzJweH0ubWF0LXN0ZXBwZXItbGFiZWwtcG9zaXRpb24tYm90dG9tIC5tYXQtaG9yaXpvbnRhbC1zdGVwcGVyLWhlYWRlciwubWF0LXZlcnRpY2FsLXN0ZXBwZXItaGVhZGVye3BhZGRpbmc6MjRweCAyNHB4fS5tYXQtc3RlcHBlci12ZXJ0aWNhbC1saW5lOjpiZWZvcmV7dG9wOi0xNnB4O2JvdHRvbTotMTZweH0ubWF0LXN0ZXBwZXItbGFiZWwtcG9zaXRpb24tYm90dG9tIC5tYXQtaG9yaXpvbnRhbC1zdGVwcGVyLWhlYWRlcjo6YWZ0ZXIsLm1hdC1zdGVwcGVyLWxhYmVsLXBvc2l0aW9uLWJvdHRvbSAubWF0LWhvcml6b250YWwtc3RlcHBlci1oZWFkZXI6OmJlZm9yZXt0b3A6MzZweH0ubWF0LXN0ZXBwZXItbGFiZWwtcG9zaXRpb24tYm90dG9tIC5tYXQtc3RlcHBlci1ob3Jpem9udGFsLWxpbmV7dG9wOjM2cHh9Lm1hdC1zb3J0LWhlYWRlci1hcnJvd3tjb2xvcjojNjE2MTYxfS5tYXQtdGFiLW5hdi1iYXIsLm1hdC10YWItaGVhZGVye2JvcmRlci1ib3R0b206MXB4IHNvbGlkIHJnYmEoMCwwLDAsLjEyKX0ubWF0LXRhYi1ncm91cC1pbnZlcnRlZC1oZWFkZXIgLm1hdC10YWItbmF2LWJhciwubWF0LXRhYi1ncm91cC1pbnZlcnRlZC1oZWFkZXIgLm1hdC10YWItaGVhZGVye2JvcmRlci10b3A6MXB4IHNvbGlkIHJnYmEoMCwwLDAsLjEyKTtib3JkZXItYm90dG9tOm5vbmV9Lm1hdC10YWItbGFiZWwsLm1hdC10YWItbGlua3tjb2xvcjojMjEyMTIxfS5tYXQtdGFiLWxhYmVsLm1hdC10YWItZGlzYWJsZWQsLm1hdC10YWItbGluay5tYXQtdGFiLWRpc2FibGVke2NvbG9yOiM3NTc1NzV9Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbntib3JkZXItY29sb3I6IzIxMjEyMX0ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9ue2JvcmRlci1jb2xvcjojNzU3NTc1fS5tYXQtdGFiLWdyb3VwW2NsYXNzKj1tYXQtYmFja2dyb3VuZC1dPi5tYXQtdGFiLWhlYWRlciwubWF0LXRhYi1uYXYtYmFyW2NsYXNzKj1tYXQtYmFja2dyb3VuZC1de2JvcmRlci1ib3R0b206bm9uZTtib3JkZXItdG9wOm5vbmV9Lm1hdC10YWItZ3JvdXAubWF0LXByaW1hcnkgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1ncm91cC5tYXQtcHJpbWFyeSAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LXByaW1hcnkgLm1hdC10YWItbGluay5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLWdyb3VwLm1hdC1wcmltYXJ5IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLW5hdi1iYXIubWF0LXByaW1hcnkgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1wcmltYXJ5IC5tYXQtdGFiLWxhYmVsLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1wcmltYXJ5IC5tYXQtdGFiLWxpbmsuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1wcmltYXJ5IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMTY3LDM4LC4zKX0ubWF0LXRhYi1ncm91cC5tYXQtcHJpbWFyeSAubWF0LWluay1iYXIsLm1hdC10YWItbmF2LWJhci5tYXQtcHJpbWFyeSAubWF0LWluay1iYXJ7YmFja2dyb3VuZC1jb2xvcjojZjU3YzAwfS5tYXQtdGFiLWdyb3VwLm1hdC1wcmltYXJ5Lm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtaW5rLWJhciwubWF0LXRhYi1ncm91cC5tYXQtcHJpbWFyeS5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtaW5rLWJhciwubWF0LXRhYi1uYXYtYmFyLm1hdC1wcmltYXJ5Lm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtaW5rLWJhciwubWF0LXRhYi1uYXYtYmFyLm1hdC1wcmltYXJ5Lm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1pbmstYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZn0ubWF0LXRhYi1ncm91cC5tYXQtYWNjZW50IC5tYXQtdGFiLWxhYmVsLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWFjY2VudCAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWFjY2VudCAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWFjY2VudCAubWF0LXRhYi1saW5rLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1hY2NlbnQgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1hY2NlbnQgLm1hdC10YWItbGFiZWwuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLW5hdi1iYXIubWF0LWFjY2VudCAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItbmF2LWJhci5tYXQtYWNjZW50IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjI0LDE3OCwuMyl9Lm1hdC10YWItZ3JvdXAubWF0LWFjY2VudCAubWF0LWluay1iYXIsLm1hdC10YWItbmF2LWJhci5tYXQtYWNjZW50IC5tYXQtaW5rLWJhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZjk4MDB9Lm1hdC10YWItZ3JvdXAubWF0LWFjY2VudC5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtaW5rLWJhciwubWF0LXRhYi1ncm91cC5tYXQtYWNjZW50Lm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LWluay1iYXIsLm1hdC10YWItbmF2LWJhci5tYXQtYWNjZW50Lm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC1pbmstYmFyLC5tYXQtdGFiLW5hdi1iYXIubWF0LWFjY2VudC5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1pbmstYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZn0ubWF0LXRhYi1ncm91cC5tYXQtd2FybiAubWF0LXRhYi1sYWJlbC5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLWdyb3VwLm1hdC13YXJuIC5tYXQtdGFiLWxhYmVsLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1ncm91cC5tYXQtd2FybiAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LXdhcm4gLm1hdC10YWItbGluay5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItbmF2LWJhci5tYXQtd2FybiAubWF0LXRhYi1sYWJlbC5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLW5hdi1iYXIubWF0LXdhcm4gLm1hdC10YWItbGFiZWwuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLW5hdi1iYXIubWF0LXdhcm4gLm1hdC10YWItbGluay5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLW5hdi1iYXIubWF0LXdhcm4gLm1hdC10YWItbGluay5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyMDUsMjEwLC4zKX0ubWF0LXRhYi1ncm91cC5tYXQtd2FybiAubWF0LWluay1iYXIsLm1hdC10YWItbmF2LWJhci5tYXQtd2FybiAubWF0LWluay1iYXJ7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fS5tYXQtdGFiLWdyb3VwLm1hdC13YXJuLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyIC5tYXQtaW5rLWJhciwubWF0LXRhYi1ncm91cC5tYXQtd2Fybi5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtaW5rLWJhciwubWF0LXRhYi1uYXYtYmFyLm1hdC13YXJuLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyIC5tYXQtaW5rLWJhciwubWF0LXRhYi1uYXYtYmFyLm1hdC13YXJuLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1pbmstYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZn0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5IC5tYXQtdGFiLWxhYmVsLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeSAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeSAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeSAubWF0LXRhYi1saW5rLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnkgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnkgLm1hdC10YWItbGFiZWwuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeSAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMTY3LDM4LC4zKX0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciwubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24sLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItbGluay1jb250YWluZXIsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9ue2JhY2tncm91bmQtY29sb3I6I2Y1N2MwMH0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1sYWJlbCwubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmssLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1sYWJlbCwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC10YWItbGlua3tjb2xvcjojZmZmfS5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWxhYmVsLm1hdC10YWItZGlzYWJsZWQsLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LXRhYi1saW5rLm1hdC10YWItZGlzYWJsZWQsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1sYWJlbC5tYXQtdGFiLWRpc2FibGVkLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LXRhYi1saW5rLm1hdC10YWItZGlzYWJsZWR7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNCl9Lm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbiwubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb24sLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LWZvY3VzLWluZGljYXRvcjo6YmVmb3JlLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmUsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LWZvY3VzLWluZGljYXRvcjo6YmVmb3JlLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXIgLm1hdC1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZXtib3JkZXItY29sb3I6I2ZmZn0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tZGlzYWJsZWQgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbiwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWRpc2FibGVkIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb24sLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWRpc2FibGVkIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb257Ym9yZGVyLWNvbG9yOiNmZmY7b3BhY2l0eTouNH0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciAubWF0LXJpcHBsZS1lbGVtZW50LC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1yaXBwbGUtZWxlbWVudCwubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uIC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciAubWF0LXJpcHBsZS1lbGVtZW50LC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LXJpcHBsZS1lbGVtZW50LC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQtY29sb3I6I2ZmZjtvcGFjaXR5Oi4xMn0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1hY2NlbnQgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1hY2NlbnQgLm1hdC10YWItbGFiZWwuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudCAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50IC5tYXQtdGFiLWxhYmVsLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQgLm1hdC10YWItbGFiZWwuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50IC5tYXQtdGFiLWxpbmsuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudCAubWF0LXRhYi1saW5rLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDIyNCwxNzgsLjMpfS5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIsLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItbGluay1jb250YWluZXIsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb257YmFja2dyb3VuZC1jb2xvcjojZmY5ODAwfS5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwsLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmssLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWxhYmVsLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmt7Y29sb3I6I2ZmZn0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWxhYmVsLm1hdC10YWItZGlzYWJsZWQsLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmsubWF0LXRhYi1kaXNhYmxlZCwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwubWF0LXRhYi1kaXNhYmxlZCwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LXRhYi1saW5rLm1hdC10YWItZGlzYWJsZWR7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNCl9Lm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LWZvY3VzLWluZGljYXRvcjo6YmVmb3JlLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZSwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbiwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmUsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmV7Ym9yZGVyLWNvbG9yOiNmZmZ9Lm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWRpc2FibGVkIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb257Ym9yZGVyLWNvbG9yOiNmZmY7b3BhY2l0eTouNH0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uIC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1yaXBwbGUtZWxlbWVudCwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQtY29sb3I6I2ZmZjtvcGFjaXR5Oi4xMn0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuIC5tYXQtdGFiLWxhYmVsLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2FybiAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2FybiAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2FybiAubWF0LXRhYi1saW5rLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4gLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4gLm1hdC10YWItbGFiZWwuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2FybiAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC13YXJuIC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjA1LDIxMCwuMyl9Lm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIsLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciwubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWxpbmstY29udGFpbmVyLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbntiYWNrZ3JvdW5kLWNvbG9yOiNmNDQzMzZ9Lm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwsLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LXRhYi1saW5rLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwsLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmt7Y29sb3I6I2ZmZn0ubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1sYWJlbC5tYXQtdGFiLWRpc2FibGVkLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC10YWItbGluay5tYXQtdGFiLWRpc2FibGVkLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwubWF0LXRhYi1kaXNhYmxlZCwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC10YWItbGluay5tYXQtdGFiLWRpc2FibGVke2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjQpfS5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb24sLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZSwubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlciAubWF0LWZvY3VzLWluZGljYXRvcjo6YmVmb3JlLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbiwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24gLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbiwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZSwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyIC5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmV7Ym9yZGVyLWNvbG9yOiNmZmZ9Lm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tZGlzYWJsZWQgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbiwubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWRpc2FibGVkIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb24sLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9ue2JvcmRlci1jb2xvcjojZmZmO29wYWNpdHk6LjR9Lm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC1yaXBwbGUtZWxlbWVudCwubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXJpcHBsZS1lbGVtZW50LC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC1yaXBwbGUtZWxlbWVudCwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1yaXBwbGUtZWxlbWVudCwubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24gLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7b3BhY2l0eTouMTJ9Lm1hdC10b29sYmFye2JhY2tncm91bmQ6I2Y1N2MwMDtjb2xvcjojMjEyMTIxfS5tYXQtdG9vbGJhci5tYXQtcHJpbWFyeXtiYWNrZ3JvdW5kOiNmNTdjMDA7Y29sb3I6I2ZmZn0ubWF0LXRvb2xiYXIubWF0LWFjY2VudHtiYWNrZ3JvdW5kOiNmZjk4MDA7Y29sb3I6I2ZmZn0ubWF0LXRvb2xiYXIubWF0LXdhcm57YmFja2dyb3VuZDojZjQ0MzM2O2NvbG9yOiNmZmZ9Lm1hdC10b29sYmFyIC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmUsLm1hdC10b29sYmFyIC5tYXQtZm9ybS1maWVsZC1yaXBwbGUsLm1hdC10b29sYmFyIC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtcmlwcGxle2JhY2tncm91bmQtY29sb3I6Y3VycmVudENvbG9yfS5tYXQtdG9vbGJhciAubWF0LWZvcm0tZmllbGQtbGFiZWwsLm1hdC10b29sYmFyIC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtbGFiZWwsLm1hdC10b29sYmFyIC5tYXQtc2VsZWN0LXZhbHVlLC5tYXQtdG9vbGJhciAubWF0LXNlbGVjdC1hcnJvdywubWF0LXRvb2xiYXIgLm1hdC1mb3JtLWZpZWxkLm1hdC1mb2N1c2VkIC5tYXQtc2VsZWN0LWFycm93e2NvbG9yOmluaGVyaXR9Lm1hdC10b29sYmFyIC5tYXQtaW5wdXQtZWxlbWVudHtjYXJldC1jb2xvcjpjdXJyZW50Q29sb3J9Lm1hdC10b29sYmFyLW11bHRpcGxlLXJvd3N7bWluLWhlaWdodDo2NHB4fS5tYXQtdG9vbGJhci1yb3csLm1hdC10b29sYmFyLXNpbmdsZS1yb3d7aGVpZ2h0OjY0cHh9QG1lZGlhKG1heC13aWR0aDogNTk5cHgpey5tYXQtdG9vbGJhci1tdWx0aXBsZS1yb3dze21pbi1oZWlnaHQ6NTZweH0ubWF0LXRvb2xiYXItcm93LC5tYXQtdG9vbGJhci1zaW5nbGUtcm93e2hlaWdodDo1NnB4fX0ubWF0LXRvb2x0aXB7YmFja2dyb3VuZDpyZ2JhKDk3LDk3LDk3LC45KX0ubWF0LXRyZWV7YmFja2dyb3VuZDojZmZmfS5tYXQtdHJlZS1ub2RlLC5tYXQtbmVzdGVkLXRyZWUtbm9kZXtjb2xvcjojMjEyMTIxfS5tYXQtdHJlZS1ub2Rle21pbi1oZWlnaHQ6NDhweH0ubWF0LXNuYWNrLWJhci1jb250YWluZXJ7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyk7YmFja2dyb3VuZDojMzIzMjMyO2JveC1zaGFkb3c6MHB4IDNweCA1cHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDZweCAxMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggMThweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX0ubWF0LXNpbXBsZS1zbmFja2Jhci1hY3Rpb257Y29sb3I6I2ZmOTgwMH1ib2R5e292ZXJmbG93OmhpZGRlbn0uY2RrLW92ZXJsYXktY29udGFpbmVye2NvbnRhaW46c3RyaWN0fWE6bm90KC5tYXQtYnV0dG9uLC5tYXQtaWNvbi1idXR0b24pe2NvbG9yOiMxOTc2ZDJ9YTpub3QoLm1hdC1idXR0b24sLm1hdC1pY29uLWJ1dHRvbik6dmlzaXRlZHtjb2xvcjojN2IxZmEyfWJvZHkuZGFyay1tb2Rle2JhY2tncm91bmQtY29sb3I6IzMwMzAzMH1ib2R5LmRhcmstbW9kZSBhOm5vdCgubWF0LWJ1dHRvbiwubWF0LWljb24tYnV0dG9uKXtjb2xvcjojNDJhNWY1fWJvZHkuZGFyay1tb2RlIGE6bm90KC5tYXQtYnV0dG9uLC5tYXQtaWNvbi1idXR0b24pOnZpc2l0ZWR7Y29sb3I6I2JhNjhjOH1ib2R5LmRhcmstbW9kZSAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1vcHRpb257Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LW9wdGlvbjpob3Zlcjpub3QoLm1hdC1vcHRpb24tZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtb3B0aW9uOmZvY3VzOm5vdCgubWF0LW9wdGlvbi1kaXNhYmxlZCl7YmFja2dyb3VuZDpyZ2JhKDI1NSwyNTUsMjU1LC4wNCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1vcHRpb24ubWF0LXNlbGVjdGVkOm5vdCgubWF0LW9wdGlvbi1tdWx0aXBsZSk6bm90KC5tYXQtb3B0aW9uLWRpc2FibGVkKXtiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA0KX1ib2R5LmRhcmstbW9kZSAubWF0LW9wdGlvbi5tYXQtYWN0aXZle2JhY2tncm91bmQ6cmdiYSgyNTUsMjU1LDI1NSwuMDQpO2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1vcHRpb24ubWF0LW9wdGlvbi1kaXNhYmxlZHtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC41KX1ib2R5LmRhcmstbW9kZSAubWF0LXByaW1hcnkgLm1hdC1vcHRpb24ubWF0LXNlbGVjdGVkOm5vdCgubWF0LW9wdGlvbi1kaXNhYmxlZCl7Y29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWFjY2VudCAubWF0LW9wdGlvbi5tYXQtc2VsZWN0ZWQ6bm90KC5tYXQtb3B0aW9uLWRpc2FibGVkKXtjb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtd2FybiAubWF0LW9wdGlvbi5tYXQtc2VsZWN0ZWQ6bm90KC5tYXQtb3B0aW9uLWRpc2FibGVkKXtjb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtb3B0Z3JvdXAtbGFiZWx7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1vcHRncm91cC1kaXNhYmxlZCAubWF0LW9wdGdyb3VwLWxhYmVse2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjUpfWJvZHkuZGFyay1tb2RlIC5tYXQtcHNldWRvLWNoZWNrYm94e2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlIC5tYXQtcHNldWRvLWNoZWNrYm94OjphZnRlcntjb2xvcjojMzAzMDMwfWJvZHkuZGFyay1tb2RlIC5tYXQtcHNldWRvLWNoZWNrYm94LWRpc2FibGVke2NvbG9yOiM2ODY4Njh9Ym9keS5kYXJrLW1vZGUgLm1hdC1wcmltYXJ5IC5tYXQtcHNldWRvLWNoZWNrYm94LWNoZWNrZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1wcmltYXJ5IC5tYXQtcHNldWRvLWNoZWNrYm94LWluZGV0ZXJtaW5hdGV7YmFja2dyb3VuZDojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtcHNldWRvLWNoZWNrYm94LWNoZWNrZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1wc2V1ZG8tY2hlY2tib3gtaW5kZXRlcm1pbmF0ZSxib2R5LmRhcmstbW9kZSAubWF0LWFjY2VudCAubWF0LXBzZXVkby1jaGVja2JveC1jaGVja2VkLGJvZHkuZGFyay1tb2RlIC5tYXQtYWNjZW50IC5tYXQtcHNldWRvLWNoZWNrYm94LWluZGV0ZXJtaW5hdGV7YmFja2dyb3VuZDojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtd2FybiAubWF0LXBzZXVkby1jaGVja2JveC1jaGVja2VkLGJvZHkuZGFyay1tb2RlIC5tYXQtd2FybiAubWF0LXBzZXVkby1jaGVja2JveC1pbmRldGVybWluYXRle2JhY2tncm91bmQ6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LXBzZXVkby1jaGVja2JveC1jaGVja2VkLm1hdC1wc2V1ZG8tY2hlY2tib3gtZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1wc2V1ZG8tY2hlY2tib3gtaW5kZXRlcm1pbmF0ZS5tYXQtcHNldWRvLWNoZWNrYm94LWRpc2FibGVke2JhY2tncm91bmQ6IzY4Njg2OH1ib2R5LmRhcmstbW9kZSAubWF0LWFwcC1iYWNrZ3JvdW5kLGJvZHkuZGFyay1tb2RlLm1hdC1hcHAtYmFja2dyb3VuZHtiYWNrZ3JvdW5kLWNvbG9yOiMzMDMwMzA7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWVsZXZhdGlvbi16MHtib3gtc2hhZG93OjBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDBweCAwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDBweCAwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1lbGV2YXRpb24tejF7Ym94LXNoYWRvdzowcHggMnB4IDFweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMXB4IDFweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDNweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWVsZXZhdGlvbi16Mntib3gtc2hhZG93OjBweCAzcHggMXB4IC0ycHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggNXB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoze2JveC1zaGFkb3c6MHB4IDNweCAzcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDNweCA0cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCA4cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1lbGV2YXRpb24tejR7Ym94LXNoYWRvdzowcHggMnB4IDRweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggNHB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDEwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1lbGV2YXRpb24tejV7Ym94LXNoYWRvdzowcHggM3B4IDVweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggNXB4IDhweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDE0cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1lbGV2YXRpb24tejZ7Ym94LXNoYWRvdzowcHggM3B4IDVweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggNnB4IDEwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAxOHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXo3e2JveC1zaGFkb3c6MHB4IDRweCA1cHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDdweCAxMHB4IDFweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAycHggMTZweCAxcHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWVsZXZhdGlvbi16OHtib3gtc2hhZG93OjBweCA1cHggNXB4IC0zcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCA4cHggMTBweCAxcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggM3B4IDE0cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1lbGV2YXRpb24tejl7Ym94LXNoYWRvdzowcHggNXB4IDZweCAtM3B4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggOXB4IDEycHggMXB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDNweCAxNnB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoxMHtib3gtc2hhZG93OjBweCA2cHggNnB4IC0zcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAxMHB4IDE0cHggMXB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDRweCAxOHB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoxMXtib3gtc2hhZG93OjBweCA2cHggN3B4IC00cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAxMXB4IDE1cHggMXB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDRweCAyMHB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoxMntib3gtc2hhZG93OjBweCA3cHggOHB4IC00cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAxMnB4IDE3cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDVweCAyMnB4IDRweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoxM3tib3gtc2hhZG93OjBweCA3cHggOHB4IC00cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAxM3B4IDE5cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDVweCAyNHB4IDRweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoxNHtib3gtc2hhZG93OjBweCA3cHggOXB4IC00cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAxNHB4IDIxcHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDVweCAyNnB4IDRweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoxNXtib3gtc2hhZG93OjBweCA4cHggOXB4IC01cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAxNXB4IDIycHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDZweCAyOHB4IDVweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoxNntib3gtc2hhZG93OjBweCA4cHggMTBweCAtNXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTZweCAyNHB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA2cHggMzBweCA1cHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWVsZXZhdGlvbi16MTd7Ym94LXNoYWRvdzowcHggOHB4IDExcHggLTVweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDE3cHggMjZweCAycHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggNnB4IDMycHggNXB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1lbGV2YXRpb24tejE4e2JveC1zaGFkb3c6MHB4IDlweCAxMXB4IC01cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAxOHB4IDI4cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDdweCAzNHB4IDZweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoxOXtib3gtc2hhZG93OjBweCA5cHggMTJweCAtNnB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTlweCAyOXB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA3cHggMzZweCA2cHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWVsZXZhdGlvbi16MjB7Ym94LXNoYWRvdzowcHggMTBweCAxM3B4IC02cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAyMHB4IDMxcHggM3B4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDhweCAzOHB4IDdweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoyMXtib3gtc2hhZG93OjBweCAxMHB4IDEzcHggLTZweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDIxcHggMzNweCAzcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggOHB4IDQwcHggN3B4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1lbGV2YXRpb24tejIye2JveC1zaGFkb3c6MHB4IDEwcHggMTRweCAtNnB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMjJweCAzNXB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA4cHggNDJweCA3cHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWVsZXZhdGlvbi16MjN7Ym94LXNoYWRvdzowcHggMTFweCAxNHB4IC03cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAyM3B4IDM2cHggM3B4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDlweCA0NHB4IDhweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZWxldmF0aW9uLXoyNHtib3gtc2hhZG93OjBweCAxMXB4IDE1cHggLTdweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDI0cHggMzhweCAzcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggOXB4IDQ2cHggOHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Lm1hdC10aGVtZS1sb2FkZWQtbWFya2Vye2Rpc3BsYXk6bm9uZX1ib2R5LmRhcmstbW9kZSAubWF0LWF1dG9jb21wbGV0ZS1wYW5lbHtiYWNrZ3JvdW5kOiM0MjQyNDI7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWF1dG9jb21wbGV0ZS1wYW5lbDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCAycHggNHB4IC0xcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCA0cHggNXB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggMTBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWF1dG9jb21wbGV0ZS1wYW5lbCAubWF0LW9wdGlvbi5tYXQtc2VsZWN0ZWQ6bm90KC5tYXQtYWN0aXZlKTpub3QoOmhvdmVyKXtiYWNrZ3JvdW5kOiM0MjQyNDJ9Ym9keS5kYXJrLW1vZGUgLm1hdC1hdXRvY29tcGxldGUtcGFuZWwgLm1hdC1vcHRpb24ubWF0LXNlbGVjdGVkOm5vdCgubWF0LWFjdGl2ZSk6bm90KDpob3Zlcik6bm90KC5tYXQtb3B0aW9uLWRpc2FibGVkKXtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtYmFkZ2UtY29udGVudHtjb2xvcjojZmZmO2JhY2tncm91bmQ6I2VmNmMwMH0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIGJvZHkuZGFyay1tb2RlIC5tYXQtYmFkZ2UtY29udGVudHtvdXRsaW5lOnNvbGlkIDFweDtib3JkZXItcmFkaXVzOjB9Ym9keS5kYXJrLW1vZGUgLm1hdC1iYWRnZS1hY2NlbnQgLm1hdC1iYWRnZS1jb250ZW50e2JhY2tncm91bmQ6I2VmNmMwMDtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtYmFkZ2Utd2FybiAubWF0LWJhZGdlLWNvbnRlbnR7Y29sb3I6I2ZmZjtiYWNrZ3JvdW5kOiNmNDQzMzZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1iYWRnZS1kaXNhYmxlZCAubWF0LWJhZGdlLWNvbnRlbnR7YmFja2dyb3VuZDojNmU2ZTZlO2NvbG9yOiM2MTYxNjF9Ym9keS5kYXJrLW1vZGUgLm1hdC1ib3R0b20tc2hlZXQtY29udGFpbmVye2JveC1zaGFkb3c6MHB4IDhweCAxMHB4IC01cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAxNnB4IDI0cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDZweCAzMHB4IDVweCByZ2JhKDAsIDAsIDAsIDAuMTIpO2JhY2tncm91bmQ6IzQyNDI0Mjtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtYnV0dG9uLGJvZHkuZGFyay1tb2RlIC5tYXQtaWNvbi1idXR0b24sYm9keS5kYXJrLW1vZGUgLm1hdC1zdHJva2VkLWJ1dHRvbntjb2xvcjppbmhlcml0O2JhY2tncm91bmQ6cmdiYSgwLDAsMCwwKX1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi5tYXQtcHJpbWFyeSxib2R5LmRhcmstbW9kZSAubWF0LWljb24tYnV0dG9uLm1hdC1wcmltYXJ5LGJvZHkuZGFyay1tb2RlIC5tYXQtc3Ryb2tlZC1idXR0b24ubWF0LXByaW1hcnl7Y29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi5tYXQtYWNjZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtaWNvbi1idXR0b24ubWF0LWFjY2VudCxib2R5LmRhcmstbW9kZSAubWF0LXN0cm9rZWQtYnV0dG9uLm1hdC1hY2NlbnR7Y29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi5tYXQtd2Fybixib2R5LmRhcmstbW9kZSAubWF0LWljb24tYnV0dG9uLm1hdC13YXJuLGJvZHkuZGFyay1tb2RlIC5tYXQtc3Ryb2tlZC1idXR0b24ubWF0LXdhcm57Y29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi5tYXQtcHJpbWFyeS5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtYnV0dG9uLm1hdC1hY2NlbnQubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi5tYXQtd2Fybi5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtYnV0dG9uLm1hdC1idXR0b24tZGlzYWJsZWQubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LWljb24tYnV0dG9uLm1hdC1wcmltYXJ5Lm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1pY29uLWJ1dHRvbi5tYXQtYWNjZW50Lm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1pY29uLWJ1dHRvbi5tYXQtd2Fybi5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtaWNvbi1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZC5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtc3Ryb2tlZC1idXR0b24ubWF0LXByaW1hcnkubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXN0cm9rZWQtYnV0dG9uLm1hdC1hY2NlbnQubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXN0cm9rZWQtYnV0dG9uLm1hdC13YXJuLm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1zdHJva2VkLWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkLm1hdC1idXR0b24tZGlzYWJsZWR7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1idXR0b24ubWF0LXByaW1hcnkgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSxib2R5LmRhcmstbW9kZSAubWF0LWljb24tYnV0dG9uLm1hdC1wcmltYXJ5IC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksYm9keS5kYXJrLW1vZGUgLm1hdC1zdHJva2VkLWJ1dHRvbi5tYXQtcHJpbWFyeSAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e2JhY2tncm91bmQtY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi5tYXQtYWNjZW50IC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksYm9keS5kYXJrLW1vZGUgLm1hdC1pY29uLWJ1dHRvbi5tYXQtYWNjZW50IC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksYm9keS5kYXJrLW1vZGUgLm1hdC1zdHJva2VkLWJ1dHRvbi5tYXQtYWNjZW50IC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtYnV0dG9uLm1hdC13YXJuIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksYm9keS5kYXJrLW1vZGUgLm1hdC1pY29uLWJ1dHRvbi5tYXQtd2FybiAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LGJvZHkuZGFyay1tb2RlIC5tYXQtc3Ryb2tlZC1idXR0b24ubWF0LXdhcm4gLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXtiYWNrZ3JvdW5kLWNvbG9yOiNmNDQzMzZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LGJvZHkuZGFyay1tb2RlIC5tYXQtaWNvbi1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LGJvZHkuZGFyay1tb2RlIC5tYXQtc3Ryb2tlZC1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwwKX1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbiAubWF0LXJpcHBsZS1lbGVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtaWNvbi1idXR0b24gLm1hdC1yaXBwbGUtZWxlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LXN0cm9rZWQtYnV0dG9uIC5tYXQtcmlwcGxlLWVsZW1lbnR7b3BhY2l0eTouMTtiYWNrZ3JvdW5kLWNvbG9yOmN1cnJlbnRDb2xvcn1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e2JhY2tncm91bmQ6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXN0cm9rZWQtYnV0dG9uOm5vdCgubWF0LWJ1dHRvbi1kaXNhYmxlZCl7Ym9yZGVyLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWZsYXQtYnV0dG9uLGJvZHkuZGFyay1tb2RlIC5tYXQtcmFpc2VkLWJ1dHRvbixib2R5LmRhcmstbW9kZSAubWF0LWZhYixib2R5LmRhcmstbW9kZSAubWF0LW1pbmktZmFie2NvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjojNDI0MjQyfWJvZHkuZGFyay1tb2RlIC5tYXQtZmxhdC1idXR0b24ubWF0LXByaW1hcnksYm9keS5kYXJrLW1vZGUgLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1wcmltYXJ5LGJvZHkuZGFyay1tb2RlIC5tYXQtZmFiLm1hdC1wcmltYXJ5LGJvZHkuZGFyay1tb2RlIC5tYXQtbWluaS1mYWIubWF0LXByaW1hcnl7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWZsYXQtYnV0dG9uLm1hdC1hY2NlbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1hY2NlbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1mYWIubWF0LWFjY2VudCxib2R5LmRhcmstbW9kZSAubWF0LW1pbmktZmFiLm1hdC1hY2NlbnR7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWZsYXQtYnV0dG9uLm1hdC13YXJuLGJvZHkuZGFyay1tb2RlIC5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtd2Fybixib2R5LmRhcmstbW9kZSAubWF0LWZhYi5tYXQtd2Fybixib2R5LmRhcmstbW9kZSAubWF0LW1pbmktZmFiLm1hdC13YXJue2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1mbGF0LWJ1dHRvbi5tYXQtcHJpbWFyeS5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtZmxhdC1idXR0b24ubWF0LWFjY2VudC5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtZmxhdC1idXR0b24ubWF0LXdhcm4ubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LWZsYXQtYnV0dG9uLm1hdC1idXR0b24tZGlzYWJsZWQubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXJhaXNlZC1idXR0b24ubWF0LXByaW1hcnkubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXJhaXNlZC1idXR0b24ubWF0LWFjY2VudC5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtd2Fybi5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkLm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1mYWIubWF0LXByaW1hcnkubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LWZhYi5tYXQtYWNjZW50Lm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1mYWIubWF0LXdhcm4ubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LWZhYi5tYXQtYnV0dG9uLWRpc2FibGVkLm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1taW5pLWZhYi5tYXQtcHJpbWFyeS5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtbWluaS1mYWIubWF0LWFjY2VudC5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtbWluaS1mYWIubWF0LXdhcm4ubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LW1pbmktZmFiLm1hdC1idXR0b24tZGlzYWJsZWQubWF0LWJ1dHRvbi1kaXNhYmxlZHtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4zKX1ib2R5LmRhcmstbW9kZSAubWF0LWZsYXQtYnV0dG9uLm1hdC1wcmltYXJ5LGJvZHkuZGFyay1tb2RlIC5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtcHJpbWFyeSxib2R5LmRhcmstbW9kZSAubWF0LWZhYi5tYXQtcHJpbWFyeSxib2R5LmRhcmstbW9kZSAubWF0LW1pbmktZmFiLm1hdC1wcmltYXJ5e2JhY2tncm91bmQtY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWZsYXQtYnV0dG9uLm1hdC1hY2NlbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1hY2NlbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1mYWIubWF0LWFjY2VudCxib2R5LmRhcmstbW9kZSAubWF0LW1pbmktZmFiLm1hdC1hY2NlbnR7YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtZmxhdC1idXR0b24ubWF0LXdhcm4sYm9keS5kYXJrLW1vZGUgLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC13YXJuLGJvZHkuZGFyay1tb2RlIC5tYXQtZmFiLm1hdC13YXJuLGJvZHkuZGFyay1tb2RlIC5tYXQtbWluaS1mYWIubWF0LXdhcm57YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtZmxhdC1idXR0b24ubWF0LXByaW1hcnkubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LWZsYXQtYnV0dG9uLm1hdC1hY2NlbnQubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LWZsYXQtYnV0dG9uLm1hdC13YXJuLm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1mbGF0LWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkLm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1wcmltYXJ5Lm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1hY2NlbnQubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXJhaXNlZC1idXR0b24ubWF0LXdhcm4ubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXJhaXNlZC1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZC5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtZmFiLm1hdC1wcmltYXJ5Lm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1mYWIubWF0LWFjY2VudC5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtZmFiLm1hdC13YXJuLm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1mYWIubWF0LWJ1dHRvbi1kaXNhYmxlZC5tYXQtYnV0dG9uLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtbWluaS1mYWIubWF0LXByaW1hcnkubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LW1pbmktZmFiLm1hdC1hY2NlbnQubWF0LWJ1dHRvbi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LW1pbmktZmFiLm1hdC13YXJuLm1hdC1idXR0b24tZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1taW5pLWZhYi5tYXQtYnV0dG9uLWRpc2FibGVkLm1hdC1idXR0b24tZGlzYWJsZWR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1mbGF0LWJ1dHRvbi5tYXQtcHJpbWFyeSAubWF0LXJpcHBsZS1lbGVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtcHJpbWFyeSAubWF0LXJpcHBsZS1lbGVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtZmFiLm1hdC1wcmltYXJ5IC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1taW5pLWZhYi5tYXQtcHJpbWFyeSAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1mbGF0LWJ1dHRvbi5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1hY2NlbnQgLm1hdC1yaXBwbGUtZWxlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LWZhYi5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1taW5pLWZhYi5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xKX1ib2R5LmRhcmstbW9kZSAubWF0LWZsYXQtYnV0dG9uLm1hdC13YXJuIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC13YXJuIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1mYWIubWF0LXdhcm4gLm1hdC1yaXBwbGUtZWxlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LW1pbmktZmFiLm1hdC13YXJuIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xKX1ib2R5LmRhcmstbW9kZSAubWF0LXN0cm9rZWQtYnV0dG9uOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pLGJvZHkuZGFyay1tb2RlIC5tYXQtZmxhdC1idXR0b246bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggMHB4IDBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtcmFpc2VkLWJ1dHRvbjpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCAzcHggMXB4IC0ycHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggNXB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtcmFpc2VkLWJ1dHRvbjpub3QoLm1hdC1idXR0b24tZGlzYWJsZWQpOmFjdGl2ZTpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCA1cHggNXB4IC0zcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCA4cHggMTBweCAxcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggM3B4IDE0cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1yYWlzZWQtYnV0dG9uLm1hdC1idXR0b24tZGlzYWJsZWQ6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggMHB4IDBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZmFiOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pLGJvZHkuZGFyay1tb2RlIC5tYXQtbWluaS1mYWI6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggM3B4IDVweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggNnB4IDEwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAxOHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZmFiOm5vdCgubWF0LWJ1dHRvbi1kaXNhYmxlZCk6YWN0aXZlOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pLGJvZHkuZGFyay1tb2RlIC5tYXQtbWluaS1mYWI6bm90KC5tYXQtYnV0dG9uLWRpc2FibGVkKTphY3RpdmU6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggN3B4IDhweCAtNHB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTJweCAxN3B4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA1cHggMjJweCA0cHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWZhYi5tYXQtYnV0dG9uLWRpc2FibGVkOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pLGJvZHkuZGFyay1tb2RlIC5tYXQtbWluaS1mYWIubWF0LWJ1dHRvbi1kaXNhYmxlZDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDBweCAwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDBweCAwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1idXR0b24tdG9nZ2xlLXN0YW5kYWxvbmU6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSksYm9keS5kYXJrLW1vZGUgLm1hdC1idXR0b24tdG9nZ2xlLWdyb3VwOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pe2JveC1zaGFkb3c6MHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDJweCAycHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCA1cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1idXR0b24tdG9nZ2xlLXN0YW5kYWxvbmUubWF0LWJ1dHRvbi10b2dnbGUtYXBwZWFyYW5jZS1zdGFuZGFyZDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKSxib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi10b2dnbGUtZ3JvdXAtYXBwZWFyYW5jZS1zdGFuZGFyZDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93Om5vbmV9Ym9keS5kYXJrLW1vZGUgLm1hdC1idXR0b24tdG9nZ2xle2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjUpfWJvZHkuZGFyay1tb2RlIC5tYXQtYnV0dG9uLXRvZ2dsZSAubWF0LWJ1dHRvbi10b2dnbGUtZm9jdXMtb3ZlcmxheXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi10b2dnbGUtYXBwZWFyYW5jZS1zdGFuZGFyZHtjb2xvcjojZmZmO2JhY2tncm91bmQ6IzQyNDI0Mn1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi10b2dnbGUtYXBwZWFyYW5jZS1zdGFuZGFyZCAubWF0LWJ1dHRvbi10b2dnbGUtZm9jdXMtb3ZlcmxheXtiYWNrZ3JvdW5kLWNvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1idXR0b24tdG9nZ2xlLWdyb3VwLWFwcGVhcmFuY2Utc3RhbmRhcmQgLm1hdC1idXR0b24tdG9nZ2xlKy5tYXQtYnV0dG9uLXRvZ2dsZXtib3JkZXItbGVmdDpzb2xpZCAxcHggIzU5NTk1OX1ib2R5LmRhcmstbW9kZSBbZGlyPXJ0bF0gLm1hdC1idXR0b24tdG9nZ2xlLWdyb3VwLWFwcGVhcmFuY2Utc3RhbmRhcmQgLm1hdC1idXR0b24tdG9nZ2xlKy5tYXQtYnV0dG9uLXRvZ2dsZXtib3JkZXItbGVmdDpub25lO2JvcmRlci1yaWdodDpzb2xpZCAxcHggIzU5NTk1OX1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi10b2dnbGUtZ3JvdXAtYXBwZWFyYW5jZS1zdGFuZGFyZC5tYXQtYnV0dG9uLXRvZ2dsZS12ZXJ0aWNhbCAubWF0LWJ1dHRvbi10b2dnbGUrLm1hdC1idXR0b24tdG9nZ2xle2JvcmRlci1sZWZ0Om5vbmU7Ym9yZGVyLXJpZ2h0Om5vbmU7Ym9yZGVyLXRvcDpzb2xpZCAxcHggIzU5NTk1OX1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi10b2dnbGUtY2hlY2tlZHtiYWNrZ3JvdW5kLWNvbG9yOiMyMTIxMjE7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1idXR0b24tdG9nZ2xlLWNoZWNrZWQubWF0LWJ1dHRvbi10b2dnbGUtYXBwZWFyYW5jZS1zdGFuZGFyZHtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtYnV0dG9uLXRvZ2dsZS1kaXNhYmxlZHtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4zKTtiYWNrZ3JvdW5kLWNvbG9yOiMwMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1idXR0b24tdG9nZ2xlLWRpc2FibGVkLm1hdC1idXR0b24tdG9nZ2xlLWFwcGVhcmFuY2Utc3RhbmRhcmR7YmFja2dyb3VuZDojNDI0MjQyfWJvZHkuZGFyay1tb2RlIC5tYXQtYnV0dG9uLXRvZ2dsZS1kaXNhYmxlZC5tYXQtYnV0dG9uLXRvZ2dsZS1jaGVja2Vke2JhY2tncm91bmQtY29sb3I6IzQyNDI0Mn1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi10b2dnbGUtc3RhbmRhbG9uZS5tYXQtYnV0dG9uLXRvZ2dsZS1hcHBlYXJhbmNlLXN0YW5kYXJkLGJvZHkuZGFyay1tb2RlIC5tYXQtYnV0dG9uLXRvZ2dsZS1ncm91cC1hcHBlYXJhbmNlLXN0YW5kYXJke2JvcmRlcjpzb2xpZCAxcHggIzU5NTk1OX1ib2R5LmRhcmstbW9kZSAubWF0LWJ1dHRvbi10b2dnbGUtYXBwZWFyYW5jZS1zdGFuZGFyZCAubWF0LWJ1dHRvbi10b2dnbGUtbGFiZWwtY29udGVudHtsaW5lLWhlaWdodDo0OHB4fWJvZHkuZGFyay1tb2RlIC5tYXQtY2FyZHtiYWNrZ3JvdW5kOiM0MjQyNDI7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWNhcmQ6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggMnB4IDFweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMXB4IDFweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDNweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWNhcmQubWF0LWNhcmQtZmxhdDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCAwcHggMHB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDBweCAwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDBweCAwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1jYXJkLXN1YnRpdGxle2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlIC5tYXQtY2hlY2tib3gtZnJhbWV7Ym9yZGVyLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlIC5tYXQtY2hlY2tib3gtY2hlY2ttYXJre2ZpbGw6IzMwMzAzMH1ib2R5LmRhcmstbW9kZSAubWF0LWNoZWNrYm94LWNoZWNrbWFyay1wYXRoe3N0cm9rZTojMzAzMDMwICFpbXBvcnRhbnR9Ym9keS5kYXJrLW1vZGUgLm1hdC1jaGVja2JveC1taXhlZG1hcmt7YmFja2dyb3VuZC1jb2xvcjojMzAzMDMwfWJvZHkuZGFyay1tb2RlIC5tYXQtY2hlY2tib3gtaW5kZXRlcm1pbmF0ZS5tYXQtcHJpbWFyeSAubWF0LWNoZWNrYm94LWJhY2tncm91bmQsYm9keS5kYXJrLW1vZGUgLm1hdC1jaGVja2JveC1jaGVja2VkLm1hdC1wcmltYXJ5IC5tYXQtY2hlY2tib3gtYmFja2dyb3VuZHtiYWNrZ3JvdW5kLWNvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1jaGVja2JveC1pbmRldGVybWluYXRlLm1hdC1hY2NlbnQgLm1hdC1jaGVja2JveC1iYWNrZ3JvdW5kLGJvZHkuZGFyay1tb2RlIC5tYXQtY2hlY2tib3gtY2hlY2tlZC5tYXQtYWNjZW50IC5tYXQtY2hlY2tib3gtYmFja2dyb3VuZHtiYWNrZ3JvdW5kLWNvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1jaGVja2JveC1pbmRldGVybWluYXRlLm1hdC13YXJuIC5tYXQtY2hlY2tib3gtYmFja2dyb3VuZCxib2R5LmRhcmstbW9kZSAubWF0LWNoZWNrYm94LWNoZWNrZWQubWF0LXdhcm4gLm1hdC1jaGVja2JveC1iYWNrZ3JvdW5ke2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWNoZWNrYm94LWRpc2FibGVkLm1hdC1jaGVja2JveC1jaGVja2VkIC5tYXQtY2hlY2tib3gtYmFja2dyb3VuZCxib2R5LmRhcmstbW9kZSAubWF0LWNoZWNrYm94LWRpc2FibGVkLm1hdC1jaGVja2JveC1pbmRldGVybWluYXRlIC5tYXQtY2hlY2tib3gtYmFja2dyb3VuZHtiYWNrZ3JvdW5kLWNvbG9yOiM2ODY4Njh9Ym9keS5kYXJrLW1vZGUgLm1hdC1jaGVja2JveC1kaXNhYmxlZDpub3QoLm1hdC1jaGVja2JveC1jaGVja2VkKSAubWF0LWNoZWNrYm94LWZyYW1le2JvcmRlci1jb2xvcjojNjg2ODY4fWJvZHkuZGFyay1tb2RlIC5tYXQtY2hlY2tib3gtZGlzYWJsZWQgLm1hdC1jaGVja2JveC1sYWJlbHtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC41KX1ib2R5LmRhcmstbW9kZSAubWF0LWNoZWNrYm94IC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtY2hlY2tib3gtY2hlY2tlZDpub3QoLm1hdC1jaGVja2JveC1kaXNhYmxlZCkubWF0LXByaW1hcnkgLm1hdC1yaXBwbGUtZWxlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LWNoZWNrYm94OmFjdGl2ZTpub3QoLm1hdC1jaGVja2JveC1kaXNhYmxlZCkubWF0LXByaW1hcnkgLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1jaGVja2JveC1jaGVja2VkOm5vdCgubWF0LWNoZWNrYm94LWRpc2FibGVkKS5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1jaGVja2JveDphY3RpdmU6bm90KC5tYXQtY2hlY2tib3gtZGlzYWJsZWQpLm1hdC1hY2NlbnQgLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1jaGVja2JveC1jaGVja2VkOm5vdCgubWF0LWNoZWNrYm94LWRpc2FibGVkKS5tYXQtd2FybiAubWF0LXJpcHBsZS1lbGVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtY2hlY2tib3g6YWN0aXZlOm5vdCgubWF0LWNoZWNrYm94LWRpc2FibGVkKS5tYXQtd2FybiAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQ6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXB7YmFja2dyb3VuZC1jb2xvcjojNjE2MTYxO2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1jaGlwLm1hdC1zdGFuZGFyZC1jaGlwIC5tYXQtY2hpcC1yZW1vdmV7Y29sb3I6I2ZmZjtvcGFjaXR5Oi40fWJvZHkuZGFyay1tb2RlIC5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcDpub3QoLm1hdC1jaGlwLWRpc2FibGVkKTphY3RpdmV7Ym94LXNoYWRvdzowcHggM3B4IDNweCAtMnB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggM3B4IDRweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDhweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXA6bm90KC5tYXQtY2hpcC1kaXNhYmxlZCkgLm1hdC1jaGlwLXJlbW92ZTpob3ZlcntvcGFjaXR5Oi41NH1ib2R5LmRhcmstbW9kZSAubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtZGlzYWJsZWR7b3BhY2l0eTouNH1ib2R5LmRhcmstbW9kZSAubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXA6OmFmdGVye2JhY2tncm91bmQ6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtc2VsZWN0ZWQubWF0LXByaW1hcnl7YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwO2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1jaGlwLm1hdC1zdGFuZGFyZC1jaGlwLm1hdC1jaGlwLXNlbGVjdGVkLm1hdC1wcmltYXJ5IC5tYXQtY2hpcC1yZW1vdmV7Y29sb3I6I2ZmZjtvcGFjaXR5Oi40fWJvZHkuZGFyay1tb2RlIC5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtcHJpbWFyeSAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1jaGlwLm1hdC1zdGFuZGFyZC1jaGlwLm1hdC1jaGlwLXNlbGVjdGVkLm1hdC13YXJue2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNjtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtd2FybiAubWF0LWNoaXAtcmVtb3Zle2NvbG9yOiNmZmY7b3BhY2l0eTouNH1ib2R5LmRhcmstbW9kZSAubWF0LWNoaXAubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtc2VsZWN0ZWQubWF0LXdhcm4gLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjEpfWJvZHkuZGFyay1tb2RlIC5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtYWNjZW50e2JhY2tncm91bmQtY29sb3I6I2VmNmMwMDtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtYWNjZW50IC5tYXQtY2hpcC1yZW1vdmV7Y29sb3I6I2ZmZjtvcGFjaXR5Oi40fWJvZHkuZGFyay1tb2RlIC5tYXQtY2hpcC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZC5tYXQtYWNjZW50IC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xKX1ib2R5LmRhcmstbW9kZSAubWF0LXRhYmxle2JhY2tncm91bmQ6IzQyNDI0Mn1ib2R5LmRhcmstbW9kZSAubWF0LXRhYmxlIHRoZWFkLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFibGUgdGJvZHksYm9keS5kYXJrLW1vZGUgLm1hdC10YWJsZSB0Zm9vdCxib2R5LmRhcmstbW9kZSBtYXQtaGVhZGVyLXJvdyxib2R5LmRhcmstbW9kZSBtYXQtcm93LGJvZHkuZGFyay1tb2RlIG1hdC1mb290ZXItcm93LGJvZHkuZGFyay1tb2RlIFttYXQtaGVhZGVyLXJvd10sYm9keS5kYXJrLW1vZGUgW21hdC1yb3ddLGJvZHkuZGFyay1tb2RlIFttYXQtZm9vdGVyLXJvd10sYm9keS5kYXJrLW1vZGUgLm1hdC10YWJsZS1zdGlja3l7YmFja2dyb3VuZDppbmhlcml0fWJvZHkuZGFyay1tb2RlIG1hdC1yb3csYm9keS5kYXJrLW1vZGUgbWF0LWhlYWRlci1yb3csYm9keS5kYXJrLW1vZGUgbWF0LWZvb3Rlci1yb3csYm9keS5kYXJrLW1vZGUgdGgubWF0LWhlYWRlci1jZWxsLGJvZHkuZGFyay1tb2RlIHRkLm1hdC1jZWxsLGJvZHkuZGFyay1tb2RlIHRkLm1hdC1mb290ZXItY2VsbHtib3JkZXItYm90dG9tLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWhlYWRlci1jZWxse2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlIC5tYXQtY2VsbCxib2R5LmRhcmstbW9kZSAubWF0LWZvb3Rlci1jZWxse2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci1hcnJvd3tmaWxsOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLXRvZ2dsZSxib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudCAubWF0LWNhbGVuZGFyLW5leHQtYnV0dG9uLGJvZHkuZGFyay1tb2RlIC5tYXQtZGF0ZXBpY2tlci1jb250ZW50IC5tYXQtY2FsZW5kYXItcHJldmlvdXMtYnV0dG9ue2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci10YWJsZS1oZWFkZXItZGl2aWRlcjo6YWZ0ZXJ7YmFja2dyb3VuZDpyZ2JhKDI1NSwyNTUsMjU1LC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci10YWJsZS1oZWFkZXIsYm9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci1ib2R5LWxhYmVse2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlIC5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQsYm9keS5kYXJrLW1vZGUgLm1hdC1kYXRlLXJhbmdlLWlucHV0LXNlcGFyYXRvcntjb2xvcjojZmZmO2JvcmRlci1jb2xvcjpyZ2JhKDAsMCwwLDApfWJvZHkuZGFyay1tb2RlIC5tYXQtY2FsZW5kYXItYm9keS1kaXNhYmxlZD4ubWF0LWNhbGVuZGFyLWJvZHktY2VsbC1jb250ZW50Om5vdCgubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWQpOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwpe2NvbG9yOiM2MTYxNjF9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkIC5tYXQtZGF0ZS1yYW5nZS1pbnB1dC1zZXBhcmF0b3J7Y29sb3I6IzYxNjE2MX1ib2R5LmRhcmstbW9kZSAubWF0LWNhbGVuZGFyLWJvZHktaW4tcHJldmlld3tjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4yNCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci1ib2R5LXRvZGF5Om5vdCgubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWQpOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwpe2JvcmRlci1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC41KX1ib2R5LmRhcmstbW9kZSAubWF0LWNhbGVuZGFyLWJvZHktZGlzYWJsZWQ+Lm1hdC1jYWxlbmRhci1ib2R5LXRvZGF5Om5vdCgubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWQpOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwpe2JvcmRlci1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4zKX1ib2R5LmRhcmstbW9kZSAubWF0LWNhbGVuZGFyLWJvZHktaW4tcmFuZ2U6OmJlZm9yZXtiYWNrZ3JvdW5kOnJnYmEoMjM5LDEwOCwwLC4yKX1ib2R5LmRhcmstbW9kZSAubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwsYm9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci1ib2R5LWluLWNvbXBhcmlzb24tcmFuZ2U6OmJlZm9yZXtiYWNrZ3JvdW5kOnJnYmEoMjQ5LDE3MSwwLC4yKX1ib2R5LmRhcmstbW9kZSAubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1icmlkZ2Utc3RhcnQ6OmJlZm9yZSxib2R5LmRhcmstbW9kZSBbZGlyPXJ0bF0gLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLWVuZDo6YmVmb3Jle2JhY2tncm91bmQ6bGluZWFyLWdyYWRpZW50KHRvIHJpZ2h0LCByZ2JhKDIzOSwgMTA4LCAwLCAwLjIpIDUwJSwgcmdiYSgyNDksIDE3MSwgMCwgMC4yKSA1MCUpfWJvZHkuZGFyay1tb2RlIC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWJyaWRnZS1lbmQ6OmJlZm9yZSxib2R5LmRhcmstbW9kZSBbZGlyPXJ0bF0gLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLXN0YXJ0OjpiZWZvcmV7YmFja2dyb3VuZDpsaW5lYXItZ3JhZGllbnQodG8gbGVmdCwgcmdiYSgyMzksIDEwOCwgMCwgMC4yKSA1MCUsIHJnYmEoMjQ5LCAxNzEsIDAsIDAuMikgNTAlKX1ib2R5LmRhcmstbW9kZSAubWF0LWNhbGVuZGFyLWJvZHktaW4tcmFuZ2U+Lm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsLGJvZHkuZGFyay1tb2RlIC5tYXQtY2FsZW5kYXItYm9keS1pbi1jb21wYXJpc29uLXJhbmdlLm1hdC1jYWxlbmRhci1ib2R5LWluLXJhbmdlOjphZnRlcntiYWNrZ3JvdW5kOiNhOGRhYjV9Ym9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtY2FsZW5kYXItYm9keS1pbi1jb21wYXJpc29uLXJhbmdlPi5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZHtiYWNrZ3JvdW5kOiM0NmEzNWV9Ym9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVke2JhY2tncm91bmQtY29sb3I6I2VmNmMwMDtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtY2FsZW5kYXItYm9keS1kaXNhYmxlZD4ubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDIzOSwxMDgsMCwuNCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci1ib2R5LXRvZGF5Lm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVke2JveC1zaGFkb3c6aW5zZXQgMCAwIDAgMXB4ICNmZmZ9Ym9keS5kYXJrLW1vZGUgLmNkay1rZXlib2FyZC1mb2N1c2VkIC5tYXQtY2FsZW5kYXItYm9keS1hY3RpdmU+Lm1hdC1jYWxlbmRhci1ib2R5LWNlbGwtY29udGVudDpub3QoLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVkKTpub3QoLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsKSxib2R5LmRhcmstbW9kZSAuY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LWNhbGVuZGFyLWJvZHktYWN0aXZlPi5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQ6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDIzOSwxMDgsMCwuMyl9QG1lZGlhKGhvdmVyOiBob3Zlcil7Ym9keS5kYXJrLW1vZGUgLm1hdC1jYWxlbmRhci1ib2R5LWNlbGw6bm90KC5tYXQtY2FsZW5kYXItYm9keS1kaXNhYmxlZCk6aG92ZXI+Lm1hdC1jYWxlbmRhci1ib2R5LWNlbGwtY29udGVudDpub3QoLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVkKTpub3QoLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsKXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjM5LDEwOCwwLC4zKX19Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnR7Ym94LXNoYWRvdzowcHggMnB4IDRweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggNHB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDEwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMik7YmFja2dyb3VuZC1jb2xvcjojNDI0MjQyO2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktaW4tcmFuZ2U6OmJlZm9yZXtiYWNrZ3JvdW5kOnJnYmEoMjM5LDEwOCwwLC4yKX1ib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtYWNjZW50IC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCxib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtYWNjZW50IC5tYXQtY2FsZW5kYXItYm9keS1pbi1jb21wYXJpc29uLXJhbmdlOjpiZWZvcmV7YmFja2dyb3VuZDpyZ2JhKDI0OSwxNzEsMCwuMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1icmlkZ2Utc3RhcnQ6OmJlZm9yZSxib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtYWNjZW50IFtkaXI9cnRsXSAubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1icmlkZ2UtZW5kOjpiZWZvcmV7YmFja2dyb3VuZDpsaW5lYXItZ3JhZGllbnQodG8gcmlnaHQsIHJnYmEoMjM5LCAxMDgsIDAsIDAuMikgNTAlLCByZ2JhKDI0OSwgMTcxLCAwLCAwLjIpIDUwJSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1icmlkZ2UtZW5kOjpiZWZvcmUsYm9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCBbZGlyPXJ0bF0gLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLXN0YXJ0OjpiZWZvcmV7YmFja2dyb3VuZDpsaW5lYXItZ3JhZGllbnQodG8gbGVmdCwgcmdiYSgyMzksIDEwOCwgMCwgMC4yKSA1MCUsIHJnYmEoMjQ5LCAxNzEsIDAsIDAuMikgNTAlKX1ib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtYWNjZW50IC5tYXQtY2FsZW5kYXItYm9keS1pbi1yYW5nZT4ubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwsYm9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktaW4tY29tcGFyaXNvbi1yYW5nZS5tYXQtY2FsZW5kYXItYm9keS1pbi1yYW5nZTo6YWZ0ZXJ7YmFja2dyb3VuZDojYThkYWI1fWJvZHkuZGFyay1tb2RlIC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC1hY2NlbnQgLm1hdC1jYWxlbmRhci1ib2R5LWluLWNvbXBhcmlzb24tcmFuZ2U+Lm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVke2JhY2tncm91bmQ6IzQ2YTM1ZX1ib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtYWNjZW50IC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZHtiYWNrZ3JvdW5kLWNvbG9yOiNlZjZjMDA7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtYWNjZW50IC5tYXQtY2FsZW5kYXItYm9keS1kaXNhYmxlZD4ubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDIzOSwxMDgsMCwuNCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktdG9kYXkubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWR7Ym94LXNoYWRvdzppbnNldCAwIDAgMCAxcHggI2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtYWNjZW50IC5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWNhbGVuZGFyLWJvZHktYWN0aXZlPi5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQ6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCksYm9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAuY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LWNhbGVuZGFyLWJvZHktYWN0aXZlPi5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQ6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDIzOSwxMDgsMCwuMyl9QG1lZGlhKGhvdmVyOiBob3Zlcil7Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LWFjY2VudCAubWF0LWNhbGVuZGFyLWJvZHktY2VsbDpub3QoLm1hdC1jYWxlbmRhci1ib2R5LWRpc2FibGVkKTpob3Zlcj4ubWF0LWNhbGVuZGFyLWJvZHktY2VsbC1jb250ZW50Om5vdCgubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWQpOm5vdCgubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyMzksMTA4LDAsLjMpfX1ib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiAubWF0LWNhbGVuZGFyLWJvZHktaW4tcmFuZ2U6OmJlZm9yZXtiYWNrZ3JvdW5kOnJnYmEoMjQ0LDY3LDU0LC4yKX1ib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiAubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1pZGVudGljYWwsYm9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLm1hdC1jYWxlbmRhci1ib2R5LWluLWNvbXBhcmlzb24tcmFuZ2U6OmJlZm9yZXtiYWNrZ3JvdW5kOnJnYmEoMjQ5LDE3MSwwLC4yKX1ib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiAubWF0LWNhbGVuZGFyLWJvZHktY29tcGFyaXNvbi1icmlkZ2Utc3RhcnQ6OmJlZm9yZSxib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiBbZGlyPXJ0bF0gLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLWVuZDo6YmVmb3Jle2JhY2tncm91bmQ6bGluZWFyLWdyYWRpZW50KHRvIHJpZ2h0LCByZ2JhKDI0NCwgNjcsIDU0LCAwLjIpIDUwJSwgcmdiYSgyNDksIDE3MSwgMCwgMC4yKSA1MCUpfWJvZHkuZGFyay1tb2RlIC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWJyaWRnZS1lbmQ6OmJlZm9yZSxib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiBbZGlyPXJ0bF0gLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24tYnJpZGdlLXN0YXJ0OjpiZWZvcmV7YmFja2dyb3VuZDpsaW5lYXItZ3JhZGllbnQodG8gbGVmdCwgcmdiYSgyNDQsIDY3LCA1NCwgMC4yKSA1MCUsIHJnYmEoMjQ5LCAxNzEsIDAsIDAuMikgNTAlKX1ib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiAubWF0LWNhbGVuZGFyLWJvZHktaW4tcmFuZ2U+Lm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsLGJvZHkuZGFyay1tb2RlIC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5tYXQtY2FsZW5kYXItYm9keS1pbi1jb21wYXJpc29uLXJhbmdlLm1hdC1jYWxlbmRhci1ib2R5LWluLXJhbmdlOjphZnRlcntiYWNrZ3JvdW5kOiNhOGRhYjV9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5tYXQtY2FsZW5kYXItYm9keS1pbi1jb21wYXJpc29uLXJhbmdlPi5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZHtiYWNrZ3JvdW5kOiM0NmEzNWV9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVke2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNjtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtZGF0ZXBpY2tlci1jb250ZW50Lm1hdC13YXJuIC5tYXQtY2FsZW5kYXItYm9keS1kaXNhYmxlZD4ubWF0LWNhbGVuZGFyLWJvZHktc2VsZWN0ZWR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI0NCw2Nyw1NCwuNCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLm1hdC1jYWxlbmRhci1ib2R5LXRvZGF5Lm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVke2JveC1zaGFkb3c6aW5zZXQgMCAwIDAgMXB4ICNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLmNkay1rZXlib2FyZC1mb2N1c2VkIC5tYXQtY2FsZW5kYXItYm9keS1hY3RpdmU+Lm1hdC1jYWxlbmRhci1ib2R5LWNlbGwtY29udGVudDpub3QoLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVkKTpub3QoLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsKSxib2R5LmRhcmstbW9kZSAubWF0LWRhdGVwaWNrZXItY29udGVudC5tYXQtd2FybiAuY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LWNhbGVuZGFyLWJvZHktYWN0aXZlPi5tYXQtY2FsZW5kYXItYm9keS1jZWxsLWNvbnRlbnQ6bm90KC5tYXQtY2FsZW5kYXItYm9keS1zZWxlY3RlZCk6bm90KC5tYXQtY2FsZW5kYXItYm9keS1jb21wYXJpc29uLWlkZW50aWNhbCl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI0NCw2Nyw1NCwuMyl9QG1lZGlhKGhvdmVyOiBob3Zlcil7Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQubWF0LXdhcm4gLm1hdC1jYWxlbmRhci1ib2R5LWNlbGw6bm90KC5tYXQtY2FsZW5kYXItYm9keS1kaXNhYmxlZCk6aG92ZXI+Lm1hdC1jYWxlbmRhci1ib2R5LWNlbGwtY29udGVudDpub3QoLm1hdC1jYWxlbmRhci1ib2R5LXNlbGVjdGVkKTpub3QoLm1hdC1jYWxlbmRhci1ib2R5LWNvbXBhcmlzb24taWRlbnRpY2FsKXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjQ0LDY3LDU0LC4zKX19Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLWNvbnRlbnQtdG91Y2h7Ym94LXNoYWRvdzowcHggMTFweCAxNXB4IC03cHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCAyNHB4IDM4cHggM3B4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDlweCA0NnB4IDhweCByZ2JhKDAsIDAsIDAsIDAuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZGF0ZXBpY2tlci10b2dnbGUtYWN0aXZle2NvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlcGlja2VyLXRvZ2dsZS1hY3RpdmUubWF0LWFjY2VudHtjb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtZGF0ZXBpY2tlci10b2dnbGUtYWN0aXZlLm1hdC13YXJue2NvbG9yOiNmNDQzMzZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1kYXRlLXJhbmdlLWlucHV0LWlubmVyW2Rpc2FibGVkXXtjb2xvcjojNjE2MTYxfWJvZHkuZGFyay1tb2RlIC5tYXQtZGlhbG9nLWNvbnRhaW5lcntib3gtc2hhZG93OjBweCAxMXB4IDE1cHggLTdweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDI0cHggMzhweCAzcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggOXB4IDQ2cHggOHB4IHJnYmEoMCwgMCwgMCwgMC4xMik7YmFja2dyb3VuZDojNDI0MjQyO2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1kaXZpZGVye2JvcmRlci10b3AtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZGl2aWRlci12ZXJ0aWNhbHtib3JkZXItcmlnaHQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZXhwYW5zaW9uLXBhbmVse2JhY2tncm91bmQ6IzQyNDI0Mjtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtZXhwYW5zaW9uLXBhbmVsOm5vdChbY2xhc3MqPW1hdC1lbGV2YXRpb24tel0pe2JveC1zaGFkb3c6MHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDJweCAycHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCA1cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1hY3Rpb24tcm93e2JvcmRlci10b3AtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZXhwYW5zaW9uLXBhbmVsIC5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlci5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoW2FyaWEtZGlzYWJsZWQ9dHJ1ZV0pLGJvZHkuZGFyay1tb2RlIC5tYXQtZXhwYW5zaW9uLXBhbmVsIC5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlci5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdChbYXJpYS1kaXNhYmxlZD10cnVlXSksYm9keS5kYXJrLW1vZGUgLm1hdC1leHBhbnNpb24tcGFuZWw6bm90KC5tYXQtZXhwYW5kZWQpIC5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlcjpob3Zlcjpub3QoW2FyaWEtZGlzYWJsZWQ9dHJ1ZV0pe2JhY2tncm91bmQ6cmdiYSgyNTUsMjU1LDI1NSwuMDQpfUBtZWRpYShob3Zlcjogbm9uZSl7Ym9keS5kYXJrLW1vZGUgLm1hdC1leHBhbnNpb24tcGFuZWw6bm90KC5tYXQtZXhwYW5kZWQpOm5vdChbYXJpYS1kaXNhYmxlZD10cnVlXSkgLm1hdC1leHBhbnNpb24tcGFuZWwtaGVhZGVyOmhvdmVye2JhY2tncm91bmQ6IzQyNDI0Mn19Ym9keS5kYXJrLW1vZGUgLm1hdC1leHBhbnNpb24tcGFuZWwtaGVhZGVyLXRpdGxle2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1leHBhbnNpb24tcGFuZWwtaGVhZGVyLWRlc2NyaXB0aW9uLGJvZHkuZGFyay1tb2RlIC5tYXQtZXhwYW5zaW9uLWluZGljYXRvcjo6YWZ0ZXJ7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1leHBhbnNpb24tcGFuZWwtaGVhZGVyW2FyaWEtZGlzYWJsZWQ9dHJ1ZV17Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1leHBhbnNpb24tcGFuZWwtaGVhZGVyW2FyaWEtZGlzYWJsZWQ9dHJ1ZV0gLm1hdC1leHBhbnNpb24tcGFuZWwtaGVhZGVyLXRpdGxlLGJvZHkuZGFyay1tb2RlIC5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlclthcmlhLWRpc2FibGVkPXRydWVdIC5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlci1kZXNjcmlwdGlvbntjb2xvcjppbmhlcml0fWJvZHkuZGFyay1tb2RlIC5tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlcntoZWlnaHQ6NDhweH1ib2R5LmRhcmstbW9kZSAubWF0LWV4cGFuc2lvbi1wYW5lbC1oZWFkZXIubWF0LWV4cGFuZGVke2hlaWdodDo2NHB4fWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC1sYWJlbHtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX1ib2R5LmRhcmstbW9kZSAubWF0LWhpbnR7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLm1hdC1mb2N1c2VkIC5tYXQtZm9ybS1maWVsZC1sYWJlbHtjb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtbGFiZWwubWF0LWFjY2VudHtjb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtbGFiZWwubWF0LXdhcm57Y29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWZvY3VzZWQgLm1hdC1mb3JtLWZpZWxkLXJlcXVpcmVkLW1hcmtlcntjb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC1yaXBwbGV7YmFja2dyb3VuZC1jb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtcmlwcGxle2JhY2tncm91bmQtY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZS5tYXQtYWNjZW50e2JhY2tncm91bmQtY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZS5tYXQtd2FybntiYWNrZ3JvdW5kLWNvbG9yOiNmNDQzMzZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLXR5cGUtbWF0LW5hdGl2ZS1zZWxlY3QubWF0LWZvY3VzZWQ6bm90KC5tYXQtZm9ybS1maWVsZC1pbnZhbGlkKSAubWF0LWZvcm0tZmllbGQtaW5maXg6OmFmdGVye2NvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLXR5cGUtbWF0LW5hdGl2ZS1zZWxlY3QubWF0LWZvY3VzZWQ6bm90KC5tYXQtZm9ybS1maWVsZC1pbnZhbGlkKS5tYXQtYWNjZW50IC5tYXQtZm9ybS1maWVsZC1pbmZpeDo6YWZ0ZXJ7Y29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtbmF0aXZlLXNlbGVjdC5tYXQtZm9jdXNlZDpub3QoLm1hdC1mb3JtLWZpZWxkLWludmFsaWQpLm1hdC13YXJuIC5tYXQtZm9ybS1maWVsZC1pbmZpeDo6YWZ0ZXJ7Y29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQubWF0LWZvcm0tZmllbGQtaW52YWxpZCAubWF0LWZvcm0tZmllbGQtbGFiZWx7Y29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQubWF0LWZvcm0tZmllbGQtaW52YWxpZCAubWF0LWZvcm0tZmllbGQtbGFiZWwubWF0LWFjY2VudCxib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQubWF0LWZvcm0tZmllbGQtaW52YWxpZCAubWF0LWZvcm0tZmllbGQtbGFiZWwgLm1hdC1mb3JtLWZpZWxkLXJlcXVpcmVkLW1hcmtlcntjb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC5tYXQtZm9ybS1maWVsZC1pbnZhbGlkIC5tYXQtZm9ybS1maWVsZC1yaXBwbGUsYm9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLm1hdC1mb3JtLWZpZWxkLWludmFsaWQgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZS5tYXQtYWNjZW50e2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWVycm9ye2NvbG9yOiNmNDQzMzZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5IC5tYXQtZm9ybS1maWVsZC1sYWJlbHtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1oaW50e2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSAubWF0LWZvcm0tZmllbGQtdW5kZXJsaW5le2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5Lm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkIC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmV7YmFja2dyb3VuZC1pbWFnZTpsaW5lYXItZ3JhZGllbnQodG8gcmlnaHQsIHJnYmEoMjU1LCAyNTUsIDI1NSwgMC43KSAwJSwgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjcpIDMzJSwgdHJhbnNwYXJlbnQgMCUpO2JhY2tncm91bmQtc2l6ZTo0cHggMTAwJTtiYWNrZ3JvdW5kLXJlcGVhdDpyZXBlYXQteH1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1zdGFuZGFyZCAubWF0LWZvcm0tZmllbGQtdW5kZXJsaW5le2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utc3RhbmRhcmQubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQgLm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtiYWNrZ3JvdW5kLWltYWdlOmxpbmVhci1ncmFkaWVudCh0byByaWdodCwgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjcpIDAlLCByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNykgMzMlLCB0cmFuc3BhcmVudCAwJSk7YmFja2dyb3VuZC1zaXplOjRweCAxMDAlO2JhY2tncm91bmQtcmVwZWF0OnJlcGVhdC14fWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGwgLm1hdC1mb3JtLWZpZWxkLWZsZXh7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xKX1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsLm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkIC5tYXQtZm9ybS1maWVsZC1mbGV4e2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMDUpfWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGwgLm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZTo6YmVmb3Jle2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbC5tYXQtZm9ybS1maWVsZC1kaXNhYmxlZCAubWF0LWZvcm0tZmllbGQtbGFiZWx7Y29sb3I6IzYxNjE2MX1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsLm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkIC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmU6OmJlZm9yZXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtb3V0bGluZXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4zKX1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLXRoaWNre2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tjb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvY3VzZWQubWF0LWFjY2VudCAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tjb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvY3VzZWQubWF0LXdhcm4gLm1hdC1mb3JtLWZpZWxkLW91dGxpbmUtdGhpY2t7Y29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lLm1hdC1mb3JtLWZpZWxkLWludmFsaWQubWF0LWZvcm0tZmllbGQtaW52YWxpZCAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tjb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQgLm1hdC1mb3JtLWZpZWxkLWxhYmVse2NvbG9yOiM2MTYxNjF9Ym9keS5kYXJrLW1vZGUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9ybS1maWVsZC1kaXNhYmxlZCAubWF0LWZvcm0tZmllbGQtb3V0bGluZXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xNSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1pY29uLm1hdC1wcmltYXJ5e2NvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1pY29uLm1hdC1hY2NlbnR7Y29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWljb24ubWF0LXdhcm57Y29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtbmF0aXZlLXNlbGVjdCAubWF0LWZvcm0tZmllbGQtaW5maXg6OmFmdGVye2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlIC5tYXQtaW5wdXQtZWxlbWVudDpkaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtbmF0aXZlLXNlbGVjdC5tYXQtZm9ybS1maWVsZC1kaXNhYmxlZCAubWF0LWZvcm0tZmllbGQtaW5maXg6OmFmdGVye2NvbG9yOiM2MTYxNjF9Ym9keS5kYXJrLW1vZGUgLm1hdC1pbnB1dC1lbGVtZW50e2NhcmV0LWNvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1pbnB1dC1lbGVtZW50OjpwbGFjZWhvbGRlcntjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC41KX1ib2R5LmRhcmstbW9kZSAubWF0LWlucHV0LWVsZW1lbnQ6Oi1tb3otcGxhY2Vob2xkZXJ7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1pbnB1dC1lbGVtZW50Ojotd2Via2l0LWlucHV0LXBsYWNlaG9sZGVye2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjUpfWJvZHkuZGFyay1tb2RlIC5tYXQtaW5wdXQtZWxlbWVudDotbXMtaW5wdXQtcGxhY2Vob2xkZXJ7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1pbnB1dC1lbGVtZW50Om5vdCgubWF0LW5hdGl2ZS1zZWxlY3QtaW5saW5lKSBvcHRpb257Y29sb3I6cmdiYSgwLDAsMCwuODcpfWJvZHkuZGFyay1tb2RlIC5tYXQtaW5wdXQtZWxlbWVudDpub3QoLm1hdC1uYXRpdmUtc2VsZWN0LWlubGluZSkgb3B0aW9uOmRpc2FibGVke2NvbG9yOnJnYmEoMCwwLDAsLjM4KX1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQubWF0LWFjY2VudCAubWF0LWlucHV0LWVsZW1lbnR7Y2FyZXQtY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQubWF0LXdhcm4gLm1hdC1pbnB1dC1lbGVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC1pbnZhbGlkIC5tYXQtaW5wdXQtZWxlbWVudHtjYXJldC1jb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC10eXBlLW1hdC1uYXRpdmUtc2VsZWN0Lm1hdC1mb3JtLWZpZWxkLWludmFsaWQgLm1hdC1mb3JtLWZpZWxkLWluZml4OjphZnRlcntjb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtbGlzdC1iYXNlIC5tYXQtbGlzdC1pdGVte2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1saXN0LWJhc2UgLm1hdC1saXN0LW9wdGlvbntjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtbGlzdC1iYXNlIC5tYXQtc3ViaGVhZGVye2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlIC5tYXQtbGlzdC1iYXNlIC5tYXQtbGlzdC1pdGVtLWRpc2FibGVke2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMTIpO2NvbG9yOiM2MTYxNjF9Ym9keS5kYXJrLW1vZGUgLm1hdC1saXN0LW9wdGlvbjpob3Zlcixib2R5LmRhcmstbW9kZSAubWF0LWxpc3Qtb3B0aW9uOmZvY3VzLGJvZHkuZGFyay1tb2RlIC5tYXQtbmF2LWxpc3QgLm1hdC1saXN0LWl0ZW06aG92ZXIsYm9keS5kYXJrLW1vZGUgLm1hdC1uYXYtbGlzdCAubWF0LWxpc3QtaXRlbTpmb2N1cyxib2R5LmRhcmstbW9kZSAubWF0LWFjdGlvbi1saXN0IC5tYXQtbGlzdC1pdGVtOmhvdmVyLGJvZHkuZGFyay1tb2RlIC5tYXQtYWN0aW9uLWxpc3QgLm1hdC1saXN0LWl0ZW06Zm9jdXN7YmFja2dyb3VuZDpyZ2JhKDI1NSwyNTUsMjU1LC4wNCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1saXN0LXNpbmdsZS1zZWxlY3RlZC1vcHRpb24sYm9keS5kYXJrLW1vZGUgLm1hdC1saXN0LXNpbmdsZS1zZWxlY3RlZC1vcHRpb246aG92ZXIsYm9keS5kYXJrLW1vZGUgLm1hdC1saXN0LXNpbmdsZS1zZWxlY3RlZC1vcHRpb246Zm9jdXN7YmFja2dyb3VuZDpyZ2JhKDI1NSwyNTUsMjU1LC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1tZW51LXBhbmVse2JhY2tncm91bmQ6IzQyNDI0Mn1ib2R5LmRhcmstbW9kZSAubWF0LW1lbnUtcGFuZWw6bm90KFtjbGFzcyo9bWF0LWVsZXZhdGlvbi16XSl7Ym94LXNoYWRvdzowcHggMnB4IDRweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggNHB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDEwcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1tZW51LWl0ZW17YmFja2dyb3VuZDpyZ2JhKDAsMCwwLDApO2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1tZW51LWl0ZW1bZGlzYWJsZWRdLGJvZHkuZGFyay1tb2RlIC5tYXQtbWVudS1pdGVtW2Rpc2FibGVkXSAubWF0LW1lbnUtc3VibWVudS1pY29uLGJvZHkuZGFyay1tb2RlIC5tYXQtbWVudS1pdGVtW2Rpc2FibGVkXSAubWF0LWljb24tbm8tY29sb3J7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1tZW51LWl0ZW0gLm1hdC1pY29uLW5vLWNvbG9yLGJvZHkuZGFyay1tb2RlIC5tYXQtbWVudS1zdWJtZW51LWljb257Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LW1lbnUtaXRlbTpob3Zlcjpub3QoW2Rpc2FibGVkXSksYm9keS5kYXJrLW1vZGUgLm1hdC1tZW51LWl0ZW0uY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoW2Rpc2FibGVkXSksYm9keS5kYXJrLW1vZGUgLm1hdC1tZW51LWl0ZW0uY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KFtkaXNhYmxlZF0pLGJvZHkuZGFyay1tb2RlIC5tYXQtbWVudS1pdGVtLWhpZ2hsaWdodGVkOm5vdChbZGlzYWJsZWRdKXtiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA0KX1ib2R5LmRhcmstbW9kZSAubWF0LXBhZ2luYXRvcntiYWNrZ3JvdW5kOiM0MjQyNDJ9Ym9keS5kYXJrLW1vZGUgLm1hdC1wYWdpbmF0b3IsYm9keS5kYXJrLW1vZGUgLm1hdC1wYWdpbmF0b3ItcGFnZS1zaXplIC5tYXQtc2VsZWN0LXRyaWdnZXJ7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1wYWdpbmF0b3ItZGVjcmVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtcGFnaW5hdG9yLWluY3JlbWVudHtib3JkZXItdG9wOjJweCBzb2xpZCAjZmZmO2JvcmRlci1yaWdodDoycHggc29saWQgI2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXBhZ2luYXRvci1maXJzdCxib2R5LmRhcmstbW9kZSAubWF0LXBhZ2luYXRvci1sYXN0e2JvcmRlci10b3A6MnB4IHNvbGlkICNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1pY29uLWJ1dHRvbltkaXNhYmxlZF0gLm1hdC1wYWdpbmF0b3ItZGVjcmVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtaWNvbi1idXR0b25bZGlzYWJsZWRdIC5tYXQtcGFnaW5hdG9yLWluY3JlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LWljb24tYnV0dG9uW2Rpc2FibGVkXSAubWF0LXBhZ2luYXRvci1maXJzdCxib2R5LmRhcmstbW9kZSAubWF0LWljb24tYnV0dG9uW2Rpc2FibGVkXSAubWF0LXBhZ2luYXRvci1sYXN0e2JvcmRlci1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC41KX1ib2R5LmRhcmstbW9kZSAubWF0LXBhZ2luYXRvci1jb250YWluZXJ7bWluLWhlaWdodDo1NnB4fWJvZHkuZGFyay1tb2RlIC5tYXQtcHJvZ3Jlc3MtYmFyLWJhY2tncm91bmR7ZmlsbDojNjAzZjI0fWJvZHkuZGFyay1tb2RlIC5tYXQtcHJvZ3Jlc3MtYmFyLWJ1ZmZlcntiYWNrZ3JvdW5kLWNvbG9yOiM2MDNmMjR9Ym9keS5kYXJrLW1vZGUgLm1hdC1wcm9ncmVzcy1iYXItZmlsbDo6YWZ0ZXJ7YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtcHJvZ3Jlc3MtYmFyLm1hdC1hY2NlbnQgLm1hdC1wcm9ncmVzcy1iYXItYmFja2dyb3VuZHtmaWxsOiM2MDNmMjR9Ym9keS5kYXJrLW1vZGUgLm1hdC1wcm9ncmVzcy1iYXIubWF0LWFjY2VudCAubWF0LXByb2dyZXNzLWJhci1idWZmZXJ7YmFja2dyb3VuZC1jb2xvcjojNjAzZjI0fWJvZHkuZGFyay1tb2RlIC5tYXQtcHJvZ3Jlc3MtYmFyLm1hdC1hY2NlbnQgLm1hdC1wcm9ncmVzcy1iYXItZmlsbDo6YWZ0ZXJ7YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtcHJvZ3Jlc3MtYmFyLm1hdC13YXJuIC5tYXQtcHJvZ3Jlc3MtYmFyLWJhY2tncm91bmR7ZmlsbDojNjEzNTMyfWJvZHkuZGFyay1tb2RlIC5tYXQtcHJvZ3Jlc3MtYmFyLm1hdC13YXJuIC5tYXQtcHJvZ3Jlc3MtYmFyLWJ1ZmZlcntiYWNrZ3JvdW5kLWNvbG9yOiM2MTM1MzJ9Ym9keS5kYXJrLW1vZGUgLm1hdC1wcm9ncmVzcy1iYXIubWF0LXdhcm4gLm1hdC1wcm9ncmVzcy1iYXItZmlsbDo6YWZ0ZXJ7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtcHJvZ3Jlc3Mtc3Bpbm5lciBjaXJjbGUsYm9keS5kYXJrLW1vZGUgLm1hdC1zcGlubmVyIGNpcmNsZXtzdHJva2U6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LXByb2dyZXNzLXNwaW5uZXIubWF0LWFjY2VudCBjaXJjbGUsYm9keS5kYXJrLW1vZGUgLm1hdC1zcGlubmVyLm1hdC1hY2NlbnQgY2lyY2xle3N0cm9rZTojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtcHJvZ3Jlc3Mtc3Bpbm5lci5tYXQtd2FybiBjaXJjbGUsYm9keS5kYXJrLW1vZGUgLm1hdC1zcGlubmVyLm1hdC13YXJuIGNpcmNsZXtzdHJva2U6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LXJhZGlvLW91dGVyLWNpcmNsZXtib3JkZXItY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1yYWRpby1idXR0b24ubWF0LXByaW1hcnkubWF0LXJhZGlvLWNoZWNrZWQgLm1hdC1yYWRpby1vdXRlci1jaXJjbGV7Ym9yZGVyLWNvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1yYWRpby1idXR0b24ubWF0LXByaW1hcnkgLm1hdC1yYWRpby1pbm5lci1jaXJjbGUsYm9keS5kYXJrLW1vZGUgLm1hdC1yYWRpby1idXR0b24ubWF0LXByaW1hcnkgLm1hdC1yYWRpby1yaXBwbGUgLm1hdC1yaXBwbGUtZWxlbWVudDpub3QoLm1hdC1yYWRpby1wZXJzaXN0ZW50LXJpcHBsZSksYm9keS5kYXJrLW1vZGUgLm1hdC1yYWRpby1idXR0b24ubWF0LXByaW1hcnkubWF0LXJhZGlvLWNoZWNrZWQgLm1hdC1yYWRpby1wZXJzaXN0ZW50LXJpcHBsZSxib2R5LmRhcmstbW9kZSAubWF0LXJhZGlvLWJ1dHRvbi5tYXQtcHJpbWFyeTphY3RpdmUgLm1hdC1yYWRpby1wZXJzaXN0ZW50LXJpcHBsZXtiYWNrZ3JvdW5kLWNvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC1yYWRpby1idXR0b24ubWF0LWFjY2VudC5tYXQtcmFkaW8tY2hlY2tlZCAubWF0LXJhZGlvLW91dGVyLWNpcmNsZXtib3JkZXItY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LXJhZGlvLWJ1dHRvbi5tYXQtYWNjZW50IC5tYXQtcmFkaW8taW5uZXItY2lyY2xlLGJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC1hY2NlbnQgLm1hdC1yYWRpby1yaXBwbGUgLm1hdC1yaXBwbGUtZWxlbWVudDpub3QoLm1hdC1yYWRpby1wZXJzaXN0ZW50LXJpcHBsZSksYm9keS5kYXJrLW1vZGUgLm1hdC1yYWRpby1idXR0b24ubWF0LWFjY2VudC5tYXQtcmFkaW8tY2hlY2tlZCAubWF0LXJhZGlvLXBlcnNpc3RlbnQtcmlwcGxlLGJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC1hY2NlbnQ6YWN0aXZlIC5tYXQtcmFkaW8tcGVyc2lzdGVudC1yaXBwbGV7YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC13YXJuLm1hdC1yYWRpby1jaGVja2VkIC5tYXQtcmFkaW8tb3V0ZXItY2lyY2xle2JvcmRlci1jb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC13YXJuIC5tYXQtcmFkaW8taW5uZXItY2lyY2xlLGJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC13YXJuIC5tYXQtcmFkaW8tcmlwcGxlIC5tYXQtcmlwcGxlLWVsZW1lbnQ6bm90KC5tYXQtcmFkaW8tcGVyc2lzdGVudC1yaXBwbGUpLGJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC13YXJuLm1hdC1yYWRpby1jaGVja2VkIC5tYXQtcmFkaW8tcGVyc2lzdGVudC1yaXBwbGUsYm9keS5kYXJrLW1vZGUgLm1hdC1yYWRpby1idXR0b24ubWF0LXdhcm46YWN0aXZlIC5tYXQtcmFkaW8tcGVyc2lzdGVudC1yaXBwbGV7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC1yYWRpby1kaXNhYmxlZC5tYXQtcmFkaW8tY2hlY2tlZCAubWF0LXJhZGlvLW91dGVyLWNpcmNsZSxib2R5LmRhcmstbW9kZSAubWF0LXJhZGlvLWJ1dHRvbi5tYXQtcmFkaW8tZGlzYWJsZWQgLm1hdC1yYWRpby1vdXRlci1jaXJjbGV7Ym9yZGVyLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjUpfWJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC1yYWRpby1kaXNhYmxlZCAubWF0LXJhZGlvLXJpcHBsZSAubWF0LXJpcHBsZS1lbGVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC1yYWRpby1kaXNhYmxlZCAubWF0LXJhZGlvLWlubmVyLWNpcmNsZXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjUpfWJvZHkuZGFyay1tb2RlIC5tYXQtcmFkaW8tYnV0dG9uLm1hdC1yYWRpby1kaXNhYmxlZCAubWF0LXJhZGlvLWxhYmVsLWNvbnRlbnR7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNSl9Ym9keS5kYXJrLW1vZGUgLm1hdC1yYWRpby1idXR0b24gLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kLWNvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1zZWxlY3QtdmFsdWV7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXNlbGVjdC1wbGFjZWhvbGRlcntjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC41KX1ib2R5LmRhcmstbW9kZSAubWF0LXNlbGVjdC1kaXNhYmxlZCAubWF0LXNlbGVjdC12YWx1ZXtjb2xvcjojNjE2MTYxfWJvZHkuZGFyay1tb2RlIC5tYXQtc2VsZWN0LWFycm93e2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlIC5tYXQtc2VsZWN0LXBhbmVse2JhY2tncm91bmQ6IzQyNDI0Mn1ib2R5LmRhcmstbW9kZSAubWF0LXNlbGVjdC1wYW5lbDpub3QoW2NsYXNzKj1tYXQtZWxldmF0aW9uLXpdKXtib3gtc2hhZG93OjBweCAycHggNHB4IC0xcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCA0cHggNXB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCAxcHggMTBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LXNlbGVjdC1wYW5lbCAubWF0LW9wdGlvbi5tYXQtc2VsZWN0ZWQ6bm90KC5tYXQtb3B0aW9uLW11bHRpcGxlKXtiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQubWF0LXByaW1hcnkgLm1hdC1zZWxlY3QtYXJyb3d7Y29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQubWF0LWZvY3VzZWQubWF0LWFjY2VudCAubWF0LXNlbGVjdC1hcnJvd3tjb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZC5tYXQtZm9jdXNlZC5tYXQtd2FybiAubWF0LXNlbGVjdC1hcnJvd3tjb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtZm9ybS1maWVsZCAubWF0LXNlbGVjdC5tYXQtc2VsZWN0LWludmFsaWQgLm1hdC1zZWxlY3QtYXJyb3d7Y29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LWZvcm0tZmllbGQgLm1hdC1zZWxlY3QubWF0LXNlbGVjdC1kaXNhYmxlZCAubWF0LXNlbGVjdC1hcnJvd3tjb2xvcjojNjE2MTYxfWJvZHkuZGFyay1tb2RlIC5tYXQtZHJhd2VyLWNvbnRhaW5lcntiYWNrZ3JvdW5kLWNvbG9yOiMzMDMwMzA7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWRyYXdlcntiYWNrZ3JvdW5kLWNvbG9yOiM0MjQyNDI7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LWRyYXdlci5tYXQtZHJhd2VyLXB1c2h7YmFja2dyb3VuZC1jb2xvcjojNDI0MjQyfWJvZHkuZGFyay1tb2RlIC5tYXQtZHJhd2VyOm5vdCgubWF0LWRyYXdlci1zaWRlKXtib3gtc2hhZG93OjBweCA4cHggMTBweCAtNXB4IHJnYmEoMCwgMCwgMCwgMC4yKSwwcHggMTZweCAyNHB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMTQpLDBweCA2cHggMzBweCA1cHggcmdiYSgwLCAwLCAwLCAwLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LWRyYXdlci1zaWRle2JvcmRlci1yaWdodDpzb2xpZCAxcHggcmdiYSgyNTUsMjU1LDI1NSwuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtZHJhd2VyLXNpZGUubWF0LWRyYXdlci1lbmR7Ym9yZGVyLWxlZnQ6c29saWQgMXB4IHJnYmEoMjU1LDI1NSwyNTUsLjEyKTtib3JkZXItcmlnaHQ6bm9uZX1ib2R5LmRhcmstbW9kZSBbZGlyPXJ0bF0gLm1hdC1kcmF3ZXItc2lkZXtib3JkZXItbGVmdDpzb2xpZCAxcHggcmdiYSgyNTUsMjU1LDI1NSwuMTIpO2JvcmRlci1yaWdodDpub25lfWJvZHkuZGFyay1tb2RlIFtkaXI9cnRsXSAubWF0LWRyYXdlci1zaWRlLm1hdC1kcmF3ZXItZW5ke2JvcmRlci1sZWZ0Om5vbmU7Ym9yZGVyLXJpZ2h0OnNvbGlkIDFweCByZ2JhKDI1NSwyNTUsMjU1LC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1kcmF3ZXItYmFja2Ryb3AubWF0LWRyYXdlci1zaG93bntiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMTg5LDE4OSwxODksLjYpfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGUtdG9nZ2xlLm1hdC1jaGVja2VkIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1ie2JhY2tncm91bmQtY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlLXRvZ2dsZS5tYXQtY2hlY2tlZCAubWF0LXNsaWRlLXRvZ2dsZS1iYXJ7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDIzOSwxMDgsMCwuNTQpfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGUtdG9nZ2xlLm1hdC1jaGVja2VkIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGUtdG9nZ2xlLm1hdC1wcmltYXJ5Lm1hdC1jaGVja2VkIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1ie2JhY2tncm91bmQtY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlLXRvZ2dsZS5tYXQtcHJpbWFyeS5tYXQtY2hlY2tlZCAubWF0LXNsaWRlLXRvZ2dsZS1iYXJ7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDIzOSwxMDgsMCwuNTQpfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGUtdG9nZ2xlLm1hdC1wcmltYXJ5Lm1hdC1jaGVja2VkIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGUtdG9nZ2xlLm1hdC13YXJuLm1hdC1jaGVja2VkIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1ie2JhY2tncm91bmQtY29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlLXRvZ2dsZS5tYXQtd2Fybi5tYXQtY2hlY2tlZCAubWF0LXNsaWRlLXRvZ2dsZS1iYXJ7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI0NCw2Nyw1NCwuNTQpfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGUtdG9nZ2xlLm1hdC13YXJuLm1hdC1jaGVja2VkIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGUtdG9nZ2xlOm5vdCgubWF0LWNoZWNrZWQpIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1ie2JveC1zaGFkb3c6MHB4IDJweCAxcHggLTFweCByZ2JhKDAsIDAsIDAsIDAuMiksMHB4IDFweCAxcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xNCksMHB4IDFweCAzcHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMik7YmFja2dyb3VuZC1jb2xvcjojYmRiZGJkfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGUtdG9nZ2xlLWJhcntiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjUpfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGVyLXRyYWNrLWJhY2tncm91bmR7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4zKX1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtcHJpbWFyeSAubWF0LXNsaWRlci10cmFjay1maWxsLGJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGVyLm1hdC1wcmltYXJ5IC5tYXQtc2xpZGVyLXRodW1iLGJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGVyLm1hdC1wcmltYXJ5IC5tYXQtc2xpZGVyLXRodW1iLWxhYmVse2JhY2tncm91bmQtY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtcHJpbWFyeSAubWF0LXNsaWRlci10aHVtYi1sYWJlbC10ZXh0e2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXByaW1hcnkgLm1hdC1zbGlkZXItZm9jdXMtcmluZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjM5LDEwOCwwLC4yKX1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtYWNjZW50IC5tYXQtc2xpZGVyLXRyYWNrLWZpbGwsYm9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LWFjY2VudCAubWF0LXNsaWRlci10aHVtYixib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtYWNjZW50IC5tYXQtc2xpZGVyLXRodW1iLWxhYmVse2JhY2tncm91bmQtY29sb3I6I2VmNmMwMH1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtYWNjZW50IC5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXRleHR7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtYWNjZW50IC5tYXQtc2xpZGVyLWZvY3VzLXJpbmd7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDIzOSwxMDgsMCwuMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXdhcm4gLm1hdC1zbGlkZXItdHJhY2stZmlsbCxib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtd2FybiAubWF0LXNsaWRlci10aHVtYixib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtd2FybiAubWF0LXNsaWRlci10aHVtYi1sYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOiNmNDQzMzZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXdhcm4gLm1hdC1zbGlkZXItdGh1bWItbGFiZWwtdGV4dHtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGVyLm1hdC13YXJuIC5tYXQtc2xpZGVyLWZvY3VzLXJpbmd7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI0NCw2Nyw1NCwuMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXI6aG92ZXIgLm1hdC1zbGlkZXItdHJhY2stYmFja2dyb3VuZCxib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5jZGstZm9jdXNlZCAubWF0LXNsaWRlci10cmFjay1iYWNrZ3JvdW5ke2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXNsaWRlci1kaXNhYmxlZCAubWF0LXNsaWRlci10cmFjay1iYWNrZ3JvdW5kLGJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGVyLm1hdC1zbGlkZXItZGlzYWJsZWQgLm1hdC1zbGlkZXItdHJhY2stZmlsbCxib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtc2xpZGVyLWRpc2FibGVkIC5tYXQtc2xpZGVyLXRodW1ie2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXNsaWRlci1kaXNhYmxlZDpob3ZlciAubWF0LXNsaWRlci10cmFjay1iYWNrZ3JvdW5ke2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXNsaWRlci1taW4tdmFsdWUgLm1hdC1zbGlkZXItZm9jdXMtcmluZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtc2xpZGVyLW1pbi12YWx1ZS5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXNob3dpbmcgLm1hdC1zbGlkZXItdGh1bWIsYm9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXNsaWRlci1taW4tdmFsdWUubWF0LXNsaWRlci10aHVtYi1sYWJlbC1zaG93aW5nIC5tYXQtc2xpZGVyLXRodW1iLWxhYmVse2JhY2tncm91bmQtY29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXNsaWRlci5tYXQtc2xpZGVyLW1pbi12YWx1ZS5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXNob3dpbmcuY2RrLWZvY3VzZWQgLm1hdC1zbGlkZXItdGh1bWIsYm9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXNsaWRlci1taW4tdmFsdWUubWF0LXNsaWRlci10aHVtYi1sYWJlbC1zaG93aW5nLmNkay1mb2N1c2VkIC5tYXQtc2xpZGVyLXRodW1iLWxhYmVse2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXNsaWRlci1taW4tdmFsdWU6bm90KC5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXNob3dpbmcpIC5tYXQtc2xpZGVyLXRodW1ie2JvcmRlci1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4zKTtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXNsaWRlci1taW4tdmFsdWU6bm90KC5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXNob3dpbmcpOmhvdmVyIC5tYXQtc2xpZGVyLXRodW1iLGJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGVyLm1hdC1zbGlkZXItbWluLXZhbHVlOm5vdCgubWF0LXNsaWRlci10aHVtYi1sYWJlbC1zaG93aW5nKS5jZGstZm9jdXNlZCAubWF0LXNsaWRlci10aHVtYntib3JkZXItY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXNsaWRlci1taW4tdmFsdWU6bm90KC5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXNob3dpbmcpOmhvdmVyLm1hdC1zbGlkZXItZGlzYWJsZWQgLm1hdC1zbGlkZXItdGh1bWIsYm9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXIubWF0LXNsaWRlci1taW4tdmFsdWU6bm90KC5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXNob3dpbmcpLmNkay1mb2N1c2VkLm1hdC1zbGlkZXItZGlzYWJsZWQgLm1hdC1zbGlkZXItdGh1bWJ7Ym9yZGVyLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjMpfWJvZHkuZGFyay1tb2RlIC5tYXQtc2xpZGVyLWhhcy10aWNrcyAubWF0LXNsaWRlci13cmFwcGVyOjphZnRlcntib3JkZXItY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXItaG9yaXpvbnRhbCAubWF0LXNsaWRlci10aWNrc3tiYWNrZ3JvdW5kLWltYWdlOnJlcGVhdGluZy1saW5lYXItZ3JhZGllbnQodG8gcmlnaHQsIHJnYmEoMjU1LCAyNTUsIDI1NSwgMC43KSwgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjcpIDJweCwgdHJhbnNwYXJlbnQgMCwgdHJhbnNwYXJlbnQpO2JhY2tncm91bmQtaW1hZ2U6LW1vei1yZXBlYXRpbmctbGluZWFyLWdyYWRpZW50KDAuMDAwMWRlZywgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjcpLCByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNykgMnB4LCB0cmFuc3BhcmVudCAwLCB0cmFuc3BhcmVudCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1zbGlkZXItdmVydGljYWwgLm1hdC1zbGlkZXItdGlja3N7YmFja2dyb3VuZC1pbWFnZTpyZXBlYXRpbmctbGluZWFyLWdyYWRpZW50KHRvIGJvdHRvbSwgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjcpLCByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNykgMnB4LCB0cmFuc3BhcmVudCAwLCB0cmFuc3BhcmVudCl9Ym9keS5kYXJrLW1vZGUgLm1hdC1zdGVwLWhlYWRlci5jZGsta2V5Ym9hcmQtZm9jdXNlZCxib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyLmNkay1wcm9ncmFtLWZvY3VzZWQsYm9keS5kYXJrLW1vZGUgLm1hdC1zdGVwLWhlYWRlcjpob3Zlcjpub3QoW2FyaWEtZGlzYWJsZWRdKSxib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyOmhvdmVyW2FyaWEtZGlzYWJsZWQ9ZmFsc2Vde2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMDQpfWJvZHkuZGFyay1tb2RlIC5tYXQtc3RlcC1oZWFkZXI6aG92ZXJbYXJpYS1kaXNhYmxlZD10cnVlXXtjdXJzb3I6ZGVmYXVsdH1AbWVkaWEoaG92ZXI6IG5vbmUpe2JvZHkuZGFyay1tb2RlIC5tYXQtc3RlcC1oZWFkZXI6aG92ZXJ7YmFja2dyb3VuZDpub25lfX1ib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1sYWJlbCxib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1vcHRpb25hbHtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX1ib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1pY29ue2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyk7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1pY29uLXNlbGVjdGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtc3RlcC1oZWFkZXIgLm1hdC1zdGVwLWljb24tc3RhdGUtZG9uZSxib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1pY29uLXN0YXRlLWVkaXR7YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwO2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1zdGVwLWhlYWRlci5tYXQtYWNjZW50IC5tYXQtc3RlcC1pY29ue2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1zdGVwLWhlYWRlci5tYXQtYWNjZW50IC5tYXQtc3RlcC1pY29uLXNlbGVjdGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtc3RlcC1oZWFkZXIubWF0LWFjY2VudCAubWF0LXN0ZXAtaWNvbi1zdGF0ZS1kb25lLGJvZHkuZGFyay1tb2RlIC5tYXQtc3RlcC1oZWFkZXIubWF0LWFjY2VudCAubWF0LXN0ZXAtaWNvbi1zdGF0ZS1lZGl0e2JhY2tncm91bmQtY29sb3I6I2VmNmMwMDtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtc3RlcC1oZWFkZXIubWF0LXdhcm4gLm1hdC1zdGVwLWljb257Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyLm1hdC13YXJuIC5tYXQtc3RlcC1pY29uLXNlbGVjdGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtc3RlcC1oZWFkZXIubWF0LXdhcm4gLm1hdC1zdGVwLWljb24tc3RhdGUtZG9uZSxib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyLm1hdC13YXJuIC5tYXQtc3RlcC1pY29uLXN0YXRlLWVkaXR7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2O2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC1zdGVwLWhlYWRlciAubWF0LXN0ZXAtaWNvbi1zdGF0ZS1lcnJvcntiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCk7Y29sb3I6I2Y0NDMzNn1ib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1sYWJlbC5tYXQtc3RlcC1sYWJlbC1hY3RpdmV7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXN0ZXAtaGVhZGVyIC5tYXQtc3RlcC1sYWJlbC5tYXQtc3RlcC1sYWJlbC1lcnJvcntjb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtc3RlcHBlci1ob3Jpem9udGFsLGJvZHkuZGFyay1tb2RlIC5tYXQtc3RlcHBlci12ZXJ0aWNhbHtiYWNrZ3JvdW5kLWNvbG9yOiM0MjQyNDJ9Ym9keS5kYXJrLW1vZGUgLm1hdC1zdGVwcGVyLXZlcnRpY2FsLWxpbmU6OmJlZm9yZXtib3JkZXItbGVmdC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1ob3Jpem9udGFsLXN0ZXBwZXItaGVhZGVyOjpiZWZvcmUsYm9keS5kYXJrLW1vZGUgLm1hdC1ob3Jpem9udGFsLXN0ZXBwZXItaGVhZGVyOjphZnRlcixib2R5LmRhcmstbW9kZSAubWF0LXN0ZXBwZXItaG9yaXpvbnRhbC1saW5le2JvcmRlci10b3AtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMTIpfWJvZHkuZGFyay1tb2RlIC5tYXQtaG9yaXpvbnRhbC1zdGVwcGVyLWhlYWRlcntoZWlnaHQ6NzJweH1ib2R5LmRhcmstbW9kZSAubWF0LXN0ZXBwZXItbGFiZWwtcG9zaXRpb24tYm90dG9tIC5tYXQtaG9yaXpvbnRhbC1zdGVwcGVyLWhlYWRlcixib2R5LmRhcmstbW9kZSAubWF0LXZlcnRpY2FsLXN0ZXBwZXItaGVhZGVye3BhZGRpbmc6MjRweCAyNHB4fWJvZHkuZGFyay1tb2RlIC5tYXQtc3RlcHBlci12ZXJ0aWNhbC1saW5lOjpiZWZvcmV7dG9wOi0xNnB4O2JvdHRvbTotMTZweH1ib2R5LmRhcmstbW9kZSAubWF0LXN0ZXBwZXItbGFiZWwtcG9zaXRpb24tYm90dG9tIC5tYXQtaG9yaXpvbnRhbC1zdGVwcGVyLWhlYWRlcjo6YWZ0ZXIsYm9keS5kYXJrLW1vZGUgLm1hdC1zdGVwcGVyLWxhYmVsLXBvc2l0aW9uLWJvdHRvbSAubWF0LWhvcml6b250YWwtc3RlcHBlci1oZWFkZXI6OmJlZm9yZXt0b3A6MzZweH1ib2R5LmRhcmstbW9kZSAubWF0LXN0ZXBwZXItbGFiZWwtcG9zaXRpb24tYm90dG9tIC5tYXQtc3RlcHBlci1ob3Jpem9udGFsLWxpbmV7dG9wOjM2cHh9Ym9keS5kYXJrLW1vZGUgLm1hdC1zb3J0LWhlYWRlci1hcnJvd3tjb2xvcjojYzZjNmM2fWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItaGVhZGVye2JvcmRlci1ib3R0b206MXB4IHNvbGlkIHJnYmEoMjU1LDI1NSwyNTUsLjEyKX1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC1pbnZlcnRlZC1oZWFkZXIgLm1hdC10YWItbmF2LWJhcixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC1pbnZlcnRlZC1oZWFkZXIgLm1hdC10YWItaGVhZGVye2JvcmRlci10b3A6MXB4IHNvbGlkIHJnYmEoMjU1LDI1NSwyNTUsLjEyKTtib3JkZXItYm90dG9tOm5vbmV9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItbGFiZWwsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbGlua3tjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWxhYmVsLm1hdC10YWItZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbGluay5tYXQtdGFiLWRpc2FibGVke2NvbG9yOiM2MTYxNjF9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbntib3JkZXItY29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9ue2JvcmRlci1jb2xvcjojNjE2MTYxfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwW2NsYXNzKj1tYXQtYmFja2dyb3VuZC1dPi5tYXQtdGFiLWhlYWRlcixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyW2NsYXNzKj1tYXQtYmFja2dyb3VuZC1de2JvcmRlci1ib3R0b206bm9uZTtib3JkZXItdG9wOm5vbmV9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LXByaW1hcnkgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtcHJpbWFyeSAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LXByaW1hcnkgLm1hdC10YWItbGluay5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1wcmltYXJ5IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LXByaW1hcnkgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1wcmltYXJ5IC5tYXQtdGFiLWxhYmVsLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1wcmltYXJ5IC5tYXQtdGFiLWxpbmsuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1wcmltYXJ5IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTEsMTQwLDAsLjMpfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1wcmltYXJ5IC5tYXQtaW5rLWJhcixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1wcmltYXJ5IC5tYXQtaW5rLWJhcntiYWNrZ3JvdW5kLWNvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LXByaW1hcnkubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXIgLm1hdC1pbmstYmFyLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1wcmltYXJ5Lm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1pbmstYmFyLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LXByaW1hcnkubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXIgLm1hdC1pbmstYmFyLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LXByaW1hcnkubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LWluay1iYXJ7YmFja2dyb3VuZC1jb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1hY2NlbnQgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYWNjZW50IC5tYXQtdGFiLWxhYmVsLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYWNjZW50IC5tYXQtdGFiLWxpbmsuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYWNjZW50IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWFjY2VudCAubWF0LXRhYi1sYWJlbC5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWFjY2VudCAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYWNjZW50IC5tYXQtdGFiLWxpbmsuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1hY2NlbnQgLm1hdC10YWItbGluay5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1MSwxNDAsMCwuMyl9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWFjY2VudCAubWF0LWluay1iYXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYWNjZW50IC5tYXQtaW5rLWJhcntiYWNrZ3JvdW5kLWNvbG9yOiNlZjZjMDB9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWFjY2VudC5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtaW5rLWJhcixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYWNjZW50Lm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LWluay1iYXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYWNjZW50Lm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC1pbmstYmFyLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWFjY2VudC5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1pbmstYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtd2FybiAubWF0LXRhYi1sYWJlbC5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC13YXJuIC5tYXQtdGFiLWxhYmVsLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtd2FybiAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LXdhcm4gLm1hdC10YWItbGluay5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtd2FybiAubWF0LXRhYi1sYWJlbC5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LXdhcm4gLm1hdC10YWItbGFiZWwuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LXdhcm4gLm1hdC10YWItbGluay5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LXdhcm4gLm1hdC10YWItbGluay5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCl7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyMDUsMjEwLC4zKX1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtd2FybiAubWF0LWluay1iYXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtd2FybiAubWF0LWluay1iYXJ7YmFja2dyb3VuZC1jb2xvcjojZjQ0MzM2fWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC13YXJuLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyIC5tYXQtaW5rLWJhcixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtd2Fybi5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtaW5rLWJhcixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC13YXJuLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyIC5tYXQtaW5rLWJhcixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC13YXJuLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1pbmstYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5IC5tYXQtdGFiLWxhYmVsLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeSAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeSAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeSAubWF0LXRhYi1saW5rLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnkgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnkgLm1hdC10YWItbGFiZWwuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeSAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTEsMTQwLDAsLjMpfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItbGluay1jb250YWluZXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1saW5rLWNvbnRhaW5lcixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb257YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWxhYmVsLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC10YWItbGluayxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWxhYmVsLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LXRhYi1saW5re2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwubWF0LXRhYi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmsubWF0LXRhYi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWxhYmVsLm1hdC10YWItZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmsubWF0LXRhYi1kaXNhYmxlZHtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC40KX1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24gLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmUsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXIgLm1hdC1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb24sYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb24sYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmUsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlciAubWF0LWZvY3VzLWluZGljYXRvcjo6YmVmb3Jle2JvcmRlci1jb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWRpc2FibGVkIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb24sYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tZGlzYWJsZWQgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tZGlzYWJsZWQgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbntib3JkZXItY29sb3I6I2ZmZjtvcGFjaXR5Oi40fWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtcHJpbWFyeT4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LXJpcHBsZS1lbGVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24gLm1hdC1yaXBwbGUtZWxlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXByaW1hcnk+Lm1hdC10YWItaGVhZGVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1wcmltYXJ5Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uIC5tYXQtcmlwcGxlLWVsZW1lbnR7YmFja2dyb3VuZC1jb2xvcjojZmZmO29wYWNpdHk6LjEyfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudCAubWF0LXRhYi1sYWJlbC5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudCAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50IC5tYXQtdGFiLWxpbmsuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1hY2NlbnQgLm1hdC10YWItbGluay5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQgLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudCAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQgLm1hdC10YWItbGluay5jZGsta2V5Ym9hcmQtZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50IC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTEsMTQwLDAsLjMpfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItbGluay1jb250YWluZXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb257YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmssYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWxhYmVsLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmt7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWxhYmVsLm1hdC10YWItZGlzYWJsZWQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmsubWF0LXRhYi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwubWF0LXRhYi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LXRhYi1saW5rLm1hdC10YWItZGlzYWJsZWR7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNCl9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LWZvY3VzLWluZGljYXRvcjo6YmVmb3JlLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmUsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmV7Ym9yZGVyLWNvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWRpc2FibGVkIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb257Ym9yZGVyLWNvbG9yOiNmZmY7b3BhY2l0eTouNH1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtYWNjZW50Pi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItaGVhZGVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC1hY2NlbnQ+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1yaXBwbGUtZWxlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLWFjY2VudD4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXJpcHBsZS1lbGVtZW50e2JhY2tncm91bmQtY29sb3I6I2ZmZjtvcGFjaXR5Oi4xMn1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuIC5tYXQtdGFiLWxhYmVsLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2FybiAubWF0LXRhYi1sYWJlbC5jZGstcHJvZ3JhbS1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2FybiAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2FybiAubWF0LXRhYi1saW5rLmNkay1wcm9ncmFtLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4gLm1hdC10YWItbGFiZWwuY2RrLWtleWJvYXJkLWZvY3VzZWQ6bm90KC5tYXQtdGFiLWRpc2FibGVkKSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4gLm1hdC10YWItbGFiZWwuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2FybiAubWF0LXRhYi1saW5rLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LXRhYi1kaXNhYmxlZCksYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC13YXJuIC5tYXQtdGFiLWxpbmsuY2RrLXByb2dyYW0tZm9jdXNlZDpub3QoLm1hdC10YWItZGlzYWJsZWQpe2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjA1LDIxMCwuMyl9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1saW5rLWNvbnRhaW5lcixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWxpbmstY29udGFpbmVyLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbntiYWNrZ3JvdW5kLWNvbG9yOiNmNDQzMzZ9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1saW5rLWNvbnRhaW5lciAubWF0LXRhYi1saW5rLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtdGFiLWxpbmt7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1sYWJlbC5tYXQtdGFiLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC10YWItbGluay5tYXQtdGFiLWRpc2FibGVkLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItbGFiZWwubWF0LXRhYi1kaXNhYmxlZCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC10YWItbGluay5tYXQtdGFiLWRpc2FibGVke2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjQpfWJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb24sYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLWdyb3VwLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlciAubWF0LWZvY3VzLWluZGljYXRvcjo6YmVmb3JlLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24gLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZSxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyIC5tYXQtZm9jdXMtaW5kaWNhdG9yOjpiZWZvcmV7Ym9yZGVyLWNvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tZGlzYWJsZWQgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbixib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWRpc2FibGVkIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb24sYm9keS5kYXJrLW1vZGUgLm1hdC10YWItbmF2LWJhci5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9ue2JvcmRlci1jb2xvcjojZmZmO29wYWNpdHk6LjR9Ym9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC1yaXBwbGUtZWxlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1ncm91cC5tYXQtYmFja2dyb3VuZC13YXJuPi5tYXQtdGFiLWxpbmstY29udGFpbmVyIC5tYXQtcmlwcGxlLWVsZW1lbnQsYm9keS5kYXJrLW1vZGUgLm1hdC10YWItZ3JvdXAubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbiAubWF0LXJpcHBsZS1lbGVtZW50LGJvZHkuZGFyay1tb2RlIC5tYXQtdGFiLW5hdi1iYXIubWF0LWJhY2tncm91bmQtd2Fybj4ubWF0LXRhYi1oZWFkZXIgLm1hdC1yaXBwbGUtZWxlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItbGluay1jb250YWluZXIgLm1hdC1yaXBwbGUtZWxlbWVudCxib2R5LmRhcmstbW9kZSAubWF0LXRhYi1uYXYtYmFyLm1hdC1iYWNrZ3JvdW5kLXdhcm4+Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24gLm1hdC1yaXBwbGUtZWxlbWVudHtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7b3BhY2l0eTouMTJ9Ym9keS5kYXJrLW1vZGUgLm1hdC10b29sYmFye2JhY2tncm91bmQ6I2VmNmMwMDtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtdG9vbGJhci5tYXQtcHJpbWFyeXtiYWNrZ3JvdW5kOiNlZjZjMDA7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXRvb2xiYXIubWF0LWFjY2VudHtiYWNrZ3JvdW5kOiNlZjZjMDA7Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAubWF0LXRvb2xiYXIubWF0LXdhcm57YmFja2dyb3VuZDojZjQ0MzM2O2NvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGUgLm1hdC10b29sYmFyIC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmUsYm9keS5kYXJrLW1vZGUgLm1hdC10b29sYmFyIC5tYXQtZm9ybS1maWVsZC1yaXBwbGUsYm9keS5kYXJrLW1vZGUgLm1hdC10b29sYmFyIC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtcmlwcGxle2JhY2tncm91bmQtY29sb3I6Y3VycmVudENvbG9yfWJvZHkuZGFyay1tb2RlIC5tYXQtdG9vbGJhciAubWF0LWZvcm0tZmllbGQtbGFiZWwsYm9keS5kYXJrLW1vZGUgLm1hdC10b29sYmFyIC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtbGFiZWwsYm9keS5kYXJrLW1vZGUgLm1hdC10b29sYmFyIC5tYXQtc2VsZWN0LXZhbHVlLGJvZHkuZGFyay1tb2RlIC5tYXQtdG9vbGJhciAubWF0LXNlbGVjdC1hcnJvdyxib2R5LmRhcmstbW9kZSAubWF0LXRvb2xiYXIgLm1hdC1mb3JtLWZpZWxkLm1hdC1mb2N1c2VkIC5tYXQtc2VsZWN0LWFycm93e2NvbG9yOmluaGVyaXR9Ym9keS5kYXJrLW1vZGUgLm1hdC10b29sYmFyIC5tYXQtaW5wdXQtZWxlbWVudHtjYXJldC1jb2xvcjpjdXJyZW50Q29sb3J9Ym9keS5kYXJrLW1vZGUgLm1hdC10b29sYmFyLW11bHRpcGxlLXJvd3N7bWluLWhlaWdodDo2NHB4fWJvZHkuZGFyay1tb2RlIC5tYXQtdG9vbGJhci1yb3csYm9keS5kYXJrLW1vZGUgLm1hdC10b29sYmFyLXNpbmdsZS1yb3d7aGVpZ2h0OjY0cHh9QG1lZGlhKG1heC13aWR0aDogNTk5cHgpe2JvZHkuZGFyay1tb2RlIC5tYXQtdG9vbGJhci1tdWx0aXBsZS1yb3dze21pbi1oZWlnaHQ6NTZweH1ib2R5LmRhcmstbW9kZSAubWF0LXRvb2xiYXItcm93LGJvZHkuZGFyay1tb2RlIC5tYXQtdG9vbGJhci1zaW5nbGUtcm93e2hlaWdodDo1NnB4fX1ib2R5LmRhcmstbW9kZSAubWF0LXRvb2x0aXB7YmFja2dyb3VuZDpyZ2JhKDk3LDk3LDk3LC45KX1ib2R5LmRhcmstbW9kZSAubWF0LXRyZWV7YmFja2dyb3VuZDojNDI0MjQyfWJvZHkuZGFyay1tb2RlIC5tYXQtdHJlZS1ub2RlLGJvZHkuZGFyay1tb2RlIC5tYXQtbmVzdGVkLXRyZWUtbm9kZXtjb2xvcjojZmZmfWJvZHkuZGFyay1tb2RlIC5tYXQtdHJlZS1ub2Rle21pbi1oZWlnaHQ6NDhweH1ib2R5LmRhcmstbW9kZSAubWF0LXNuYWNrLWJhci1jb250YWluZXJ7Y29sb3I6cmdiYSgwLDAsMCwuODcpO2JhY2tncm91bmQ6I2ZhZmFmYTtib3gtc2hhZG93OjBweCAzcHggNXB4IC0xcHggcmdiYSgwLCAwLCAwLCAwLjIpLDBweCA2cHggMTBweCAwcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwwcHggMXB4IDE4cHggMHB4IHJnYmEoMCwgMCwgMCwgMC4xMil9Ym9keS5kYXJrLW1vZGUgLm1hdC1zaW1wbGUtc25hY2tiYXItYWN0aW9ue2NvbG9yOmluaGVyaXR9Cjwvc3R5bGU+Cgo8c3R5bGU+CiAgaHRtbCwKICBib2R5IHsKICAgIG1hcmdpbjogMDsKICAgIHBhZGRpbmc6IDA7CiAgICBoZWlnaHQ6IDEwMCU7CiAgICBmb250LWZhbWlseTogUm9ib3RvLCBzYW5zLXNlcmlmOwogICAgY29sb3I6IHZhcigtLXByaW1hcnktdGV4dC1jb2xvcik7CgogICAgLyogTGVnYWN5IG1lY2hhbmlzbSB0byBhdm9pZCBpc3N1ZXMgd2l0aCBzdWJwaXhlbCBhbnRpLWFsaWFzaW5nIG9uIG1hY09TLgogICAgICoKICAgICAqIEluIHRoZSBwYXN0IFsxXSwgbWFjT1Mgc3VicGl4ZWwgQUEgY2F1c2VkIGV4Y2Vzc2l2ZSBib2xkaW5nIGZvciBsaWdodC1vbi1kYXJrIHRleHQ7IHRoaXMgcnVsZQogICAgICogYXZvaWRzIHRoYXQgYnkgcmVxdWVzdGluZyBub24tc3VicGl4ZWwgQUEgYWx3YXlzLCByYXRoZXIgdGhhbiB0aGUgZGVmYXVsdCBiZWhhdmlvciwgd2hpY2ggaXMKICAgICAqIHRvIHVzZSBzdWJwaXhlbCBBQSB3aGVuIGF2YWlsYWJsZS4gVGhlIG9yaWdpbmFsIGlzc3VlIHdhcyAiZml4ZWQiIGJ5IHJlbW92aW5nIHN1YnBpeGVsIEFBIGluCiAgICAgKiBtYWNPUyAxNCAoTW9qYXZlKSwgYnV0IGZvciBsZWdhY3kgcmVhc29ucyB0aGV5IHByZXNlcnZlZCB0aGUgYm9sZGluZyBlZmZlY3QgYXMgYW4gb3B0aW9uLgogICAgICogQ2hyb21lIHRoZW4gaW4gdHVybiB1cGRhdGVkIGl0cyBmb250IHJlbmRlcmluZyB0byBhcHBseSB0aGF0IGJvbGRpbmcgZWZmZWN0IFsyXSwgd2hpY2ggbWVhbnMKICAgICAqIHRoYXQgZXZlbiB0aG91Z2ggdGhlIGAtd2Via2l0LWZvbnQtc21vb3RoaW5nYCBkb2NzIFszXSBzdWdnZXN0IHRoYXQgc2V0dGluZyBgYW50aWFsaWFzZWRgCiAgICAgKiB3b3VsZCBoYXZlIG5vIGVmZmVjdCBmb3IgcmVjZW50IHZlcnNpb25zIG9mIG1hY09TLCBpdCBzdGlsbCBpcyBuZWVkZWQgdG8gYXZvaWQgdGhlIGJvbGRpbmcuCiAgICAgKgogICAgICogWzFdOiBodHRwOi8vd3d3LmxpZ2h0ZXJyYS5jb20vYXJ0aWNsZXMvbWFjb3N4dGV4dGFhYnVnLwogICAgICogWzJdOiBodHRwczovL2J1Z3MuY2hyb21pdW0ub3JnL3AvY2hyb21pdW0vaXNzdWVzL2RldGFpbD9pZD04NTg4NjEKICAgICAqIFszXTogaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9XZWIvQ1NTL2ZvbnQtc21vb3RoCiAgICAgKgogICAgICovCgogICAgLXdlYmtpdC1mb250LXNtb290aGluZzogYW50aWFsaWFzZWQ7CiAgfQogIG5vc2NyaXB0IHsKICAgIGRpc3BsYXk6IGJsb2NrOwogICAgbWFyZ2luOiAwIGF1dG87CiAgICBtYXgtd2lkdGg6IDYwMHB4OwogICAgcGFkZGluZzogMTBweDsKICB9Cjwvc3R5bGU+Cgo8L2hlYWQ+PGJvZHk+PG5vc2NyaXB0PgogICAgPGgxPlRlbnNvckJvYXJkIHJlcXVpcmVzIEphdmFTY3JpcHQ8L2gxPgogICAgPHA+UGxlYXNlIGVuYWJsZSBKYXZhU2NyaXB0IGFuZCByZWxvYWQgdGhpcyBwYWdlLjwvcD4KICA8L25vc2NyaXB0Pjx0Yi13ZWJhcHA+PC90Yi13ZWJhcHA+PHNjcmlwdCBzcmM9ImluZGV4LmpzP19maWxlX2hhc2g9NDg2ZjM0ZDIiPjwvc2NyaXB0PjwvYm9keT48L2h0bWw+", + "headers": [ + [ + "content-type", + "text/html; charset=utf-8" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/chart_worker.js?_file_hash=c4417681": { + "data": "KCgpPT57dmFyIEgwPU9iamVjdC5kZWZpbmVQcm9wZXJ0eSxWMD1PYmplY3QuZGVmaW5lUHJvcGVydGllczt2YXIgRzA9T2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcnM7dmFyIGFmPU9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHM7dmFyIFcwPU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkscTA9T2JqZWN0LnByb3RvdHlwZS5wcm9wZXJ0eUlzRW51bWVyYWJsZTt2YXIgY2Y9TWF0aC5wb3csbGY9KG4sdCxlKT0+dCBpbiBuP0gwKG4sdCx7ZW51bWVyYWJsZTohMCxjb25maWd1cmFibGU6ITAsd3JpdGFibGU6ITAsdmFsdWU6ZX0pOm5bdF09ZSxObz0obix0KT0+e2Zvcih2YXIgZSBpbiB0fHwodD17fSkpVzAuY2FsbCh0LGUpJiZsZihuLGUsdFtlXSk7aWYoYWYpZm9yKHZhciBlIG9mIGFmKHQpKXEwLmNhbGwodCxlKSYmbGYobixlLHRbZV0pO3JldHVybiBufSx1Zj0obix0KT0+VjAobixHMCh0KSk7dmFyIGhmPShuLHQsZSk9Pm5ldyBQcm9taXNlKChpLHIpPT57dmFyIHM9bD0+e3RyeXthKGUubmV4dChsKSl9Y2F0Y2goYyl7cihjKX19LG89bD0+e3RyeXthKGUudGhyb3cobCkpfWNhdGNoKGMpe3IoYyl9fSxhPWw9PmwuZG9uZT9pKGwudmFsdWUpOlByb21pc2UucmVzb2x2ZShsLnZhbHVlKS50aGVuKHMsbyk7YSgoZT1lLmFwcGx5KG4sdCkpLm5leHQoKSl9KTt2YXIgb247KGZ1bmN0aW9uKG4pe25bbi5MSU5FQVI9MF09IkxJTkVBUiIsbltuLkxPRzEwPTFdPSJMT0cxMCIsbltuLlRJTUU9Ml09IlRJTUUifSkob258fChvbj17fSkpO2Z1bmN0aW9uIGFuKG4sdCl7cmV0dXJuIG48dD8tMTpuPnQ/MTpuPj10PzA6TmFOfWZ1bmN0aW9uIGlzKG4pe3JldHVybiBuLmxlbmd0aD09PTEmJihuPVgwKG4pKSx7bGVmdDpmdW5jdGlvbih0LGUsaSxyKXtmb3IoaT09bnVsbCYmKGk9MCkscj09bnVsbCYmKHI9dC5sZW5ndGgpO2k8cjspe3ZhciBzPWkrcj4+PjE7bih0W3NdLGUpPDA/aT1zKzE6cj1zfXJldHVybiBpfSxyaWdodDpmdW5jdGlvbih0LGUsaSxyKXtmb3IoaT09bnVsbCYmKGk9MCkscj09bnVsbCYmKHI9dC5sZW5ndGgpO2k8cjspe3ZhciBzPWkrcj4+PjE7bih0W3NdLGUpPjA/cj1zOmk9cysxfXJldHVybiBpfX19ZnVuY3Rpb24gWDAobil7cmV0dXJuIGZ1bmN0aW9uKHQsZSl7cmV0dXJuIGFuKG4odCksZSl9fXZhciBmZj1pcyhhbiksZGY9ZmYucmlnaHQsWTA9ZmYubGVmdCxCbj1kZjt2YXIgcGY9QXJyYXkucHJvdG90eXBlLEowPXBmLnNsaWNlLCQwPXBmLm1hcDt2YXIga2w9TWF0aC5zcXJ0KDUwKSxIbD1NYXRoLnNxcnQoMTApLFZsPU1hdGguc3FydCgyKTtmdW5jdGlvbiBycyhuLHQsZSl7dmFyIGkscj0tMSxzLG8sYTtpZih0PSt0LG49K24sZT0rZSxuPT09dCYmZT4wKXJldHVybltuXTtpZigoaT10PG4pJiYocz1uLG49dCx0PXMpLChhPUppKG4sdCxlKSk9PT0wfHwhaXNGaW5pdGUoYSkpcmV0dXJuW107aWYoYT4wKWZvcihuPU1hdGguY2VpbChuL2EpLHQ9TWF0aC5mbG9vcih0L2EpLG89bmV3IEFycmF5KHM9TWF0aC5jZWlsKHQtbisxKSk7KytyPHM7KW9bcl09KG4rcikqYTtlbHNlIGZvcihuPU1hdGguZmxvb3IobiphKSx0PU1hdGguY2VpbCh0KmEpLG89bmV3IEFycmF5KHM9TWF0aC5jZWlsKG4tdCsxKSk7KytyPHM7KW9bcl09KG4tcikvYTtyZXR1cm4gaSYmby5yZXZlcnNlKCksb31mdW5jdGlvbiBKaShuLHQsZSl7dmFyIGk9KHQtbikvTWF0aC5tYXgoMCxlKSxyPU1hdGguZmxvb3IoTWF0aC5sb2coaSkvTWF0aC5MTjEwKSxzPWkvTWF0aC5wb3coMTAscik7cmV0dXJuIHI+PTA/KHM+PWtsPzEwOnM+PUhsPzU6cz49Vmw/MjoxKSpNYXRoLnBvdygxMCxyKTotTWF0aC5wb3coMTAsLXIpLyhzPj1rbD8xMDpzPj1IbD81OnM+PVZsPzI6MSl9ZnVuY3Rpb24gX24obix0LGUpe3ZhciBpPU1hdGguYWJzKHQtbikvTWF0aC5tYXgoMCxlKSxyPU1hdGgucG93KDEwLE1hdGguZmxvb3IoTWF0aC5sb2coaSkvTWF0aC5MTjEwKSkscz1pL3I7cmV0dXJuIHM+PWtsP3IqPTEwOnM+PUhsP3IqPTU6cz49VmwmJihyKj0yKSx0PG4/LXI6cn12YXIgZXg9QXJyYXkucHJvdG90eXBlLnNsaWNlO3ZhciBueD17dmFsdWU6ZnVuY3Rpb24oKXt9fTtmdW5jdGlvbiB5Zigpe2Zvcih2YXIgbj0wLHQ9YXJndW1lbnRzLmxlbmd0aCxlPXt9LGk7bjx0Oysrbil7aWYoIShpPWFyZ3VtZW50c1tuXSsiIil8fGkgaW4gZXx8L1tccy5dLy50ZXN0KGkpKXRocm93IG5ldyBFcnJvcigiaWxsZWdhbCB0eXBlOiAiK2kpO2VbaV09W119cmV0dXJuIG5ldyB6byhlKX1mdW5jdGlvbiB6byhuKXt0aGlzLl89bn1mdW5jdGlvbiBpeChuLHQpe3JldHVybiBuLnRyaW0oKS5zcGxpdCgvXnxccysvKS5tYXAoZnVuY3Rpb24oZSl7dmFyIGk9IiIscj1lLmluZGV4T2YoIi4iKTtpZihyPj0wJiYoaT1lLnNsaWNlKHIrMSksZT1lLnNsaWNlKDAscikpLGUmJiF0Lmhhc093blByb3BlcnR5KGUpKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK2UpO3JldHVybnt0eXBlOmUsbmFtZTppfX0pfXpvLnByb3RvdHlwZT15Zi5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOnpvLG9uOmZ1bmN0aW9uKG4sdCl7dmFyIGU9dGhpcy5fLGk9aXgobisiIixlKSxyLHM9LTEsbz1pLmxlbmd0aDtpZihhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcig7KytzPG87KWlmKChyPShuPWlbc10pLnR5cGUpJiYocj1yeChlW3JdLG4ubmFtZSkpKXJldHVybiByO3JldHVybn1pZih0IT1udWxsJiZ0eXBlb2YgdCE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgY2FsbGJhY2s6ICIrdCk7Zm9yKDsrK3M8bzspaWYocj0obj1pW3NdKS50eXBlKWVbcl09eGYoZVtyXSxuLm5hbWUsdCk7ZWxzZSBpZih0PT1udWxsKWZvcihyIGluIGUpZVtyXT14ZihlW3JdLG4ubmFtZSxudWxsKTtyZXR1cm4gdGhpc30sY29weTpmdW5jdGlvbigpe3ZhciBuPXt9LHQ9dGhpcy5fO2Zvcih2YXIgZSBpbiB0KW5bZV09dFtlXS5zbGljZSgpO3JldHVybiBuZXcgem8obil9LGNhbGw6ZnVuY3Rpb24obix0KXtpZigocj1hcmd1bWVudHMubGVuZ3RoLTIpPjApZm9yKHZhciBlPW5ldyBBcnJheShyKSxpPTAscixzO2k8cjsrK2kpZVtpXT1hcmd1bWVudHNbaSsyXTtpZighdGhpcy5fLmhhc093blByb3BlcnR5KG4pKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK24pO2ZvcihzPXRoaXMuX1tuXSxpPTAscj1zLmxlbmd0aDtpPHI7KytpKXNbaV0udmFsdWUuYXBwbHkodCxlKX0sYXBwbHk6ZnVuY3Rpb24obix0LGUpe2lmKCF0aGlzLl8uaGFzT3duUHJvcGVydHkobikpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrbik7Zm9yKHZhciBpPXRoaXMuX1tuXSxyPTAscz1pLmxlbmd0aDtyPHM7KytyKWlbcl0udmFsdWUuYXBwbHkodCxlKX19O2Z1bmN0aW9uIHJ4KG4sdCl7Zm9yKHZhciBlPTAsaT1uLmxlbmd0aCxyO2U8aTsrK2UpaWYoKHI9bltlXSkubmFtZT09PXQpcmV0dXJuIHIudmFsdWV9ZnVuY3Rpb24geGYobix0LGUpe2Zvcih2YXIgaT0wLHI9bi5sZW5ndGg7aTxyOysraSlpZihuW2ldLm5hbWU9PT10KXtuW2ldPW54LG49bi5zbGljZSgwLGkpLmNvbmNhdChuLnNsaWNlKGkrMSkpO2JyZWFrfXJldHVybiBlIT1udWxsJiZuLnB1c2goe25hbWU6dCx2YWx1ZTplfSksbn12YXIgV2w9eWY7dmFyIFVvPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIixxbD17c3ZnOiJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIseGh0bWw6VW8seGxpbms6Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiLHhtbDoiaHR0cDovL3d3dy53My5vcmcvWE1MLzE5OTgvbmFtZXNwYWNlIix4bWxuczoiaHR0cDovL3d3dy53My5vcmcvMjAwMC94bWxucy8ifTtmdW5jdGlvbiB3bihuKXt2YXIgdD1uKz0iIixlPXQuaW5kZXhPZigiOiIpO3JldHVybiBlPj0wJiYodD1uLnNsaWNlKDAsZSkpIT09InhtbG5zIiYmKG49bi5zbGljZShlKzEpKSxxbC5oYXNPd25Qcm9wZXJ0eSh0KT97c3BhY2U6cWxbdF0sbG9jYWw6bn06bn1mdW5jdGlvbiBzeChuKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD10aGlzLm93bmVyRG9jdW1lbnQsZT10aGlzLm5hbWVzcGFjZVVSSTtyZXR1cm4gZT09PVVvJiZ0LmRvY3VtZW50RWxlbWVudC5uYW1lc3BhY2VVUkk9PT1Vbz90LmNyZWF0ZUVsZW1lbnQobik6dC5jcmVhdGVFbGVtZW50TlMoZSxuKX19ZnVuY3Rpb24gb3gobil7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMub3duZXJEb2N1bWVudC5jcmVhdGVFbGVtZW50TlMobi5zcGFjZSxuLmxvY2FsKX19ZnVuY3Rpb24gQm8obil7dmFyIHQ9d24obik7cmV0dXJuKHQubG9jYWw/b3g6c3gpKHQpfWZ1bmN0aW9uIGF4KCl7fWZ1bmN0aW9uIGRpKG4pe3JldHVybiBuPT1udWxsP2F4OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMucXVlcnlTZWxlY3RvcihuKX19ZnVuY3Rpb24gdmYobil7dHlwZW9mIG4hPSJmdW5jdGlvbiImJihuPWRpKG4pKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLGU9dC5sZW5ndGgsaT1uZXcgQXJyYXkoZSkscj0wO3I8ZTsrK3IpZm9yKHZhciBzPXRbcl0sbz1zLmxlbmd0aCxhPWlbcl09bmV3IEFycmF5KG8pLGwsYyx1PTA7dTxvOysrdSkobD1zW3VdKSYmKGM9bi5jYWxsKGwsbC5fX2RhdGFfXyx1LHMpKSYmKCJfX2RhdGFfXyJpbiBsJiYoYy5fX2RhdGFfXz1sLl9fZGF0YV9fKSxhW3VdPWMpO3JldHVybiBuZXcgY2UoaSx0aGlzLl9wYXJlbnRzKX1mdW5jdGlvbiBseCgpe3JldHVybltdfWZ1bmN0aW9uIG9zKG4pe3JldHVybiBuPT1udWxsP2x4OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMucXVlcnlTZWxlY3RvckFsbChuKX19ZnVuY3Rpb24gX2Yobil7dHlwZW9mIG4hPSJmdW5jdGlvbiImJihuPW9zKG4pKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLGU9dC5sZW5ndGgsaT1bXSxyPVtdLHM9MDtzPGU7KytzKWZvcih2YXIgbz10W3NdLGE9by5sZW5ndGgsbCxjPTA7YzxhOysrYykobD1vW2NdKSYmKGkucHVzaChuLmNhbGwobCxsLl9fZGF0YV9fLGMsbykpLHIucHVzaChsKSk7cmV0dXJuIG5ldyBjZShpLHIpfWZ1bmN0aW9uIGFzKG4pe3JldHVybiBmdW5jdGlvbigpe3JldHVybiB0aGlzLm1hdGNoZXMobil9fWZ1bmN0aW9uIHdmKG4pe3R5cGVvZiBuIT0iZnVuY3Rpb24iJiYobj1hcyhuKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxlPXQubGVuZ3RoLGk9bmV3IEFycmF5KGUpLHI9MDtyPGU7KytyKWZvcih2YXIgcz10W3JdLG89cy5sZW5ndGgsYT1pW3JdPVtdLGwsYz0wO2M8bzsrK2MpKGw9c1tjXSkmJm4uY2FsbChsLGwuX19kYXRhX18sYyxzKSYmYS5wdXNoKGwpO3JldHVybiBuZXcgY2UoaSx0aGlzLl9wYXJlbnRzKX1mdW5jdGlvbiBPbyhuKXtyZXR1cm4gbmV3IEFycmF5KG4ubGVuZ3RoKX1mdW5jdGlvbiBNZigpe3JldHVybiBuZXcgY2UodGhpcy5fZW50ZXJ8fHRoaXMuX2dyb3Vwcy5tYXAoT28pLHRoaXMuX3BhcmVudHMpfWZ1bmN0aW9uIGxzKG4sdCl7dGhpcy5vd25lckRvY3VtZW50PW4ub3duZXJEb2N1bWVudCx0aGlzLm5hbWVzcGFjZVVSST1uLm5hbWVzcGFjZVVSSSx0aGlzLl9uZXh0PW51bGwsdGhpcy5fcGFyZW50PW4sdGhpcy5fX2RhdGFfXz10fWxzLnByb3RvdHlwZT17Y29uc3RydWN0b3I6bHMsYXBwZW5kQ2hpbGQ6ZnVuY3Rpb24obil7cmV0dXJuIHRoaXMuX3BhcmVudC5pbnNlcnRCZWZvcmUobix0aGlzLl9uZXh0KX0saW5zZXJ0QmVmb3JlOmZ1bmN0aW9uKG4sdCl7cmV0dXJuIHRoaXMuX3BhcmVudC5pbnNlcnRCZWZvcmUobix0KX0scXVlcnlTZWxlY3RvcjpmdW5jdGlvbihuKXtyZXR1cm4gdGhpcy5fcGFyZW50LnF1ZXJ5U2VsZWN0b3Iobil9LHF1ZXJ5U2VsZWN0b3JBbGw6ZnVuY3Rpb24obil7cmV0dXJuIHRoaXMuX3BhcmVudC5xdWVyeVNlbGVjdG9yQWxsKG4pfX07ZnVuY3Rpb24gYmYobil7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIG59fXZhciBTZj0iJCI7ZnVuY3Rpb24gY3gobix0LGUsaSxyLHMpe2Zvcih2YXIgbz0wLGEsbD10Lmxlbmd0aCxjPXMubGVuZ3RoO288YzsrK28pKGE9dFtvXSk/KGEuX19kYXRhX189c1tvXSxpW29dPWEpOmVbb109bmV3IGxzKG4sc1tvXSk7Zm9yKDtvPGw7KytvKShhPXRbb10pJiYocltvXT1hKX1mdW5jdGlvbiB1eChuLHQsZSxpLHIscyxvKXt2YXIgYSxsLGM9e30sdT10Lmxlbmd0aCxoPXMubGVuZ3RoLGY9bmV3IEFycmF5KHUpLGQ7Zm9yKGE9MDthPHU7KythKShsPXRbYV0pJiYoZlthXT1kPVNmK28uY2FsbChsLGwuX19kYXRhX18sYSx0KSxkIGluIGM/clthXT1sOmNbZF09bCk7Zm9yKGE9MDthPGg7KythKWQ9U2Yrby5jYWxsKG4sc1thXSxhLHMpLChsPWNbZF0pPyhpW2FdPWwsbC5fX2RhdGFfXz1zW2FdLGNbZF09bnVsbCk6ZVthXT1uZXcgbHMobixzW2FdKTtmb3IoYT0wO2E8dTsrK2EpKGw9dFthXSkmJmNbZlthXV09PT1sJiYoclthXT1sKX1mdW5jdGlvbiBFZihuLHQpe2lmKCFuKXJldHVybiBkPW5ldyBBcnJheSh0aGlzLnNpemUoKSksYz0tMSx0aGlzLmVhY2goZnVuY3Rpb24oTCl7ZFsrK2NdPUx9KSxkO3ZhciBlPXQ/dXg6Y3gsaT10aGlzLl9wYXJlbnRzLHI9dGhpcy5fZ3JvdXBzO3R5cGVvZiBuIT0iZnVuY3Rpb24iJiYobj1iZihuKSk7Zm9yKHZhciBzPXIubGVuZ3RoLG89bmV3IEFycmF5KHMpLGE9bmV3IEFycmF5KHMpLGw9bmV3IEFycmF5KHMpLGM9MDtjPHM7KytjKXt2YXIgdT1pW2NdLGg9cltjXSxmPWgubGVuZ3RoLGQ9bi5jYWxsKHUsdSYmdS5fX2RhdGFfXyxjLGkpLGc9ZC5sZW5ndGgseD1hW2NdPW5ldyBBcnJheShnKSx2PW9bY109bmV3IEFycmF5KGcpLG09bFtjXT1uZXcgQXJyYXkoZik7ZSh1LGgseCx2LG0sZCx0KTtmb3IodmFyIHA9MCxiPTAsXyxTO3A8ZzsrK3ApaWYoXz14W3BdKXtmb3IocD49YiYmKGI9cCsxKTshKFM9dltiXSkmJisrYjxnOyk7Xy5fbmV4dD1TfHxudWxsfX1yZXR1cm4gbz1uZXcgY2UobyxpKSxvLl9lbnRlcj1hLG8uX2V4aXQ9bCxvfWZ1bmN0aW9uIFRmKCl7cmV0dXJuIG5ldyBjZSh0aGlzLl9leGl0fHx0aGlzLl9ncm91cHMubWFwKE9vKSx0aGlzLl9wYXJlbnRzKX1mdW5jdGlvbiBBZihuLHQsZSl7dmFyIGk9dGhpcy5lbnRlcigpLHI9dGhpcyxzPXRoaXMuZXhpdCgpO3JldHVybiBpPXR5cGVvZiBuPT0iZnVuY3Rpb24iP24oaSk6aS5hcHBlbmQobisiIiksdCE9bnVsbCYmKHI9dChyKSksZT09bnVsbD9zLnJlbW92ZSgpOmUocyksaSYmcj9pLm1lcmdlKHIpLm9yZGVyKCk6cn1mdW5jdGlvbiBDZihuKXtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLGU9bi5fZ3JvdXBzLGk9dC5sZW5ndGgscj1lLmxlbmd0aCxzPU1hdGgubWluKGksciksbz1uZXcgQXJyYXkoaSksYT0wO2E8czsrK2EpZm9yKHZhciBsPXRbYV0sYz1lW2FdLHU9bC5sZW5ndGgsaD1vW2FdPW5ldyBBcnJheSh1KSxmLGQ9MDtkPHU7KytkKShmPWxbZF18fGNbZF0pJiYoaFtkXT1mKTtmb3IoO2E8aTsrK2Epb1thXT10W2FdO3JldHVybiBuZXcgY2Uobyx0aGlzLl9wYXJlbnRzKX1mdW5jdGlvbiBSZigpe2Zvcih2YXIgbj10aGlzLl9ncm91cHMsdD0tMSxlPW4ubGVuZ3RoOysrdDxlOylmb3IodmFyIGk9blt0XSxyPWkubGVuZ3RoLTEscz1pW3JdLG87LS1yPj0wOykobz1pW3JdKSYmKHMmJm8uY29tcGFyZURvY3VtZW50UG9zaXRpb24ocyleNCYmcy5wYXJlbnROb2RlLmluc2VydEJlZm9yZShvLHMpLHM9byk7cmV0dXJuIHRoaXN9ZnVuY3Rpb24gTGYobil7bnx8KG49aHgpO2Z1bmN0aW9uIHQoaCxmKXtyZXR1cm4gaCYmZj9uKGguX19kYXRhX18sZi5fX2RhdGFfXyk6IWgtIWZ9Zm9yKHZhciBlPXRoaXMuX2dyb3VwcyxpPWUubGVuZ3RoLHI9bmV3IEFycmF5KGkpLHM9MDtzPGk7KytzKXtmb3IodmFyIG89ZVtzXSxhPW8ubGVuZ3RoLGw9cltzXT1uZXcgQXJyYXkoYSksYyx1PTA7dTxhOysrdSkoYz1vW3VdKSYmKGxbdV09Yyk7bC5zb3J0KHQpfXJldHVybiBuZXcgY2Uocix0aGlzLl9wYXJlbnRzKS5vcmRlcigpfWZ1bmN0aW9uIGh4KG4sdCl7cmV0dXJuIG48dD8tMTpuPnQ/MTpuPj10PzA6TmFOfWZ1bmN0aW9uIFBmKCl7dmFyIG49YXJndW1lbnRzWzBdO3JldHVybiBhcmd1bWVudHNbMF09dGhpcyxuLmFwcGx5KG51bGwsYXJndW1lbnRzKSx0aGlzfWZ1bmN0aW9uIERmKCl7dmFyIG49bmV3IEFycmF5KHRoaXMuc2l6ZSgpKSx0PS0xO3JldHVybiB0aGlzLmVhY2goZnVuY3Rpb24oKXtuWysrdF09dGhpc30pLG59ZnVuY3Rpb24gSWYoKXtmb3IodmFyIG49dGhpcy5fZ3JvdXBzLHQ9MCxlPW4ubGVuZ3RoO3Q8ZTsrK3QpZm9yKHZhciBpPW5bdF0scj0wLHM9aS5sZW5ndGg7cjxzOysrcil7dmFyIG89aVtyXTtpZihvKXJldHVybiBvfXJldHVybiBudWxsfWZ1bmN0aW9uIE5mKCl7dmFyIG49MDtyZXR1cm4gdGhpcy5lYWNoKGZ1bmN0aW9uKCl7KytufSksbn1mdW5jdGlvbiBGZigpe3JldHVybiF0aGlzLm5vZGUoKX1mdW5jdGlvbiB6ZihuKXtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLGU9MCxpPXQubGVuZ3RoO2U8aTsrK2UpZm9yKHZhciByPXRbZV0scz0wLG89ci5sZW5ndGgsYTtzPG87KytzKShhPXJbc10pJiZuLmNhbGwoYSxhLl9fZGF0YV9fLHMscik7cmV0dXJuIHRoaXN9ZnVuY3Rpb24gZngobil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGUobil9fWZ1bmN0aW9uIGR4KG4pe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlTlMobi5zcGFjZSxuLmxvY2FsKX19ZnVuY3Rpb24gcHgobix0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZShuLHQpfX1mdW5jdGlvbiBteChuLHQpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc2V0QXR0cmlidXRlTlMobi5zcGFjZSxuLmxvY2FsLHQpfX1mdW5jdGlvbiBneChuLHQpe3JldHVybiBmdW5jdGlvbigpe3ZhciBlPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO2U9PW51bGw/dGhpcy5yZW1vdmVBdHRyaWJ1dGUobik6dGhpcy5zZXRBdHRyaWJ1dGUobixlKX19ZnVuY3Rpb24geHgobix0KXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgZT10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtlPT1udWxsP3RoaXMucmVtb3ZlQXR0cmlidXRlTlMobi5zcGFjZSxuLmxvY2FsKTp0aGlzLnNldEF0dHJpYnV0ZU5TKG4uc3BhY2Usbi5sb2NhbCxlKX19ZnVuY3Rpb24gVWYobix0KXt2YXIgZT13bihuKTtpZihhcmd1bWVudHMubGVuZ3RoPDIpe3ZhciBpPXRoaXMubm9kZSgpO3JldHVybiBlLmxvY2FsP2kuZ2V0QXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKTppLmdldEF0dHJpYnV0ZShlKX1yZXR1cm4gdGhpcy5lYWNoKCh0PT1udWxsP2UubG9jYWw/ZHg6Zng6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/ZS5sb2NhbD94eDpneDplLmxvY2FsP214OnB4KShlLHQpKX1mdW5jdGlvbiBrbyhuKXtyZXR1cm4gbi5vd25lckRvY3VtZW50JiZuLm93bmVyRG9jdW1lbnQuZGVmYXVsdFZpZXd8fG4uZG9jdW1lbnQmJm58fG4uZGVmYXVsdFZpZXd9ZnVuY3Rpb24geXgobil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShuKX19ZnVuY3Rpb24gdngobix0LGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc3R5bGUuc2V0UHJvcGVydHkobix0LGUpfX1mdW5jdGlvbiBfeChuLHQsZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGk9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7aT09bnVsbD90aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KG4pOnRoaXMuc3R5bGUuc2V0UHJvcGVydHkobixpLGUpfX1mdW5jdGlvbiBCZihuLHQsZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg+MT90aGlzLmVhY2goKHQ9PW51bGw/eXg6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/X3g6dngpKG4sdCxlPT1udWxsPyIiOmUpKTpPbih0aGlzLm5vZGUoKSxuKX1mdW5jdGlvbiBPbihuLHQpe3JldHVybiBuLnN0eWxlLmdldFByb3BlcnR5VmFsdWUodCl8fGtvKG4pLmdldENvbXB1dGVkU3R5bGUobixudWxsKS5nZXRQcm9wZXJ0eVZhbHVlKHQpfWZ1bmN0aW9uIHd4KG4pe3JldHVybiBmdW5jdGlvbigpe2RlbGV0ZSB0aGlzW25dfX1mdW5jdGlvbiBNeChuLHQpe3JldHVybiBmdW5jdGlvbigpe3RoaXNbbl09dH19ZnVuY3Rpb24gYngobix0KXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgZT10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtlPT1udWxsP2RlbGV0ZSB0aGlzW25dOnRoaXNbbl09ZX19ZnVuY3Rpb24gT2Yobix0KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD4xP3RoaXMuZWFjaCgodD09bnVsbD93eDp0eXBlb2YgdD09ImZ1bmN0aW9uIj9ieDpNeCkobix0KSk6dGhpcy5ub2RlKClbbl19ZnVuY3Rpb24ga2Yobil7cmV0dXJuIG4udHJpbSgpLnNwbGl0KC9efFxzKy8pfWZ1bmN0aW9uIFhsKG4pe3JldHVybiBuLmNsYXNzTGlzdHx8bmV3IEhmKG4pfWZ1bmN0aW9uIEhmKG4pe3RoaXMuX25vZGU9bix0aGlzLl9uYW1lcz1rZihuLmdldEF0dHJpYnV0ZSgiY2xhc3MiKXx8IiIpfUhmLnByb3RvdHlwZT17YWRkOmZ1bmN0aW9uKG4pe3ZhciB0PXRoaXMuX25hbWVzLmluZGV4T2Yobik7dDwwJiYodGhpcy5fbmFtZXMucHVzaChuKSx0aGlzLl9ub2RlLnNldEF0dHJpYnV0ZSgiY2xhc3MiLHRoaXMuX25hbWVzLmpvaW4oIiAiKSkpfSxyZW1vdmU6ZnVuY3Rpb24obil7dmFyIHQ9dGhpcy5fbmFtZXMuaW5kZXhPZihuKTt0Pj0wJiYodGhpcy5fbmFtZXMuc3BsaWNlKHQsMSksdGhpcy5fbm9kZS5zZXRBdHRyaWJ1dGUoImNsYXNzIix0aGlzLl9uYW1lcy5qb2luKCIgIikpKX0sY29udGFpbnM6ZnVuY3Rpb24obil7cmV0dXJuIHRoaXMuX25hbWVzLmluZGV4T2Yobik+PTB9fTtmdW5jdGlvbiBWZihuLHQpe2Zvcih2YXIgZT1YbChuKSxpPS0xLHI9dC5sZW5ndGg7KytpPHI7KWUuYWRkKHRbaV0pfWZ1bmN0aW9uIEdmKG4sdCl7Zm9yKHZhciBlPVhsKG4pLGk9LTEscj10Lmxlbmd0aDsrK2k8cjspZS5yZW1vdmUodFtpXSl9ZnVuY3Rpb24gU3gobil7cmV0dXJuIGZ1bmN0aW9uKCl7VmYodGhpcyxuKX19ZnVuY3Rpb24gRXgobil7cmV0dXJuIGZ1bmN0aW9uKCl7R2YodGhpcyxuKX19ZnVuY3Rpb24gVHgobix0KXtyZXR1cm4gZnVuY3Rpb24oKXsodC5hcHBseSh0aGlzLGFyZ3VtZW50cyk/VmY6R2YpKHRoaXMsbil9fWZ1bmN0aW9uIFdmKG4sdCl7dmFyIGU9a2YobisiIik7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXtmb3IodmFyIGk9WGwodGhpcy5ub2RlKCkpLHI9LTEscz1lLmxlbmd0aDsrK3I8czspaWYoIWkuY29udGFpbnMoZVtyXSkpcmV0dXJuITE7cmV0dXJuITB9cmV0dXJuIHRoaXMuZWFjaCgodHlwZW9mIHQ9PSJmdW5jdGlvbiI/VHg6dD9TeDpFeCkoZSx0KSl9ZnVuY3Rpb24gQXgoKXt0aGlzLnRleHRDb250ZW50PSIifWZ1bmN0aW9uIEN4KG4pe3JldHVybiBmdW5jdGlvbigpe3RoaXMudGV4dENvbnRlbnQ9bn19ZnVuY3Rpb24gUngobil7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9bi5hcHBseSh0aGlzLGFyZ3VtZW50cyk7dGhpcy50ZXh0Q29udGVudD10PT1udWxsPyIiOnR9fWZ1bmN0aW9uIHFmKG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaChuPT1udWxsP0F4Oih0eXBlb2Ygbj09ImZ1bmN0aW9uIj9SeDpDeCkobikpOnRoaXMubm9kZSgpLnRleHRDb250ZW50fWZ1bmN0aW9uIEx4KCl7dGhpcy5pbm5lckhUTUw9IiJ9ZnVuY3Rpb24gUHgobil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5pbm5lckhUTUw9bn19ZnVuY3Rpb24gRHgobil7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9bi5hcHBseSh0aGlzLGFyZ3VtZW50cyk7dGhpcy5pbm5lckhUTUw9dD09bnVsbD8iIjp0fX1mdW5jdGlvbiBYZihuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2gobj09bnVsbD9MeDoodHlwZW9mIG49PSJmdW5jdGlvbiI/RHg6UHgpKG4pKTp0aGlzLm5vZGUoKS5pbm5lckhUTUx9ZnVuY3Rpb24gSXgoKXt0aGlzLm5leHRTaWJsaW5nJiZ0aGlzLnBhcmVudE5vZGUuYXBwZW5kQ2hpbGQodGhpcyl9ZnVuY3Rpb24gWWYoKXtyZXR1cm4gdGhpcy5lYWNoKEl4KX1mdW5jdGlvbiBOeCgpe3RoaXMucHJldmlvdXNTaWJsaW5nJiZ0aGlzLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKHRoaXMsdGhpcy5wYXJlbnROb2RlLmZpcnN0Q2hpbGQpfWZ1bmN0aW9uIFpmKCl7cmV0dXJuIHRoaXMuZWFjaChOeCl9ZnVuY3Rpb24gSmYobil7dmFyIHQ9dHlwZW9mIG49PSJmdW5jdGlvbiI/bjpCbyhuKTtyZXR1cm4gdGhpcy5zZWxlY3QoZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5hcHBlbmRDaGlsZCh0LmFwcGx5KHRoaXMsYXJndW1lbnRzKSl9KX1mdW5jdGlvbiBGeCgpe3JldHVybiBudWxsfWZ1bmN0aW9uICRmKG4sdCl7dmFyIGU9dHlwZW9mIG49PSJmdW5jdGlvbiI/bjpCbyhuKSxpPXQ9PW51bGw/Rng6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/dDpkaSh0KTtyZXR1cm4gdGhpcy5zZWxlY3QoZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5pbnNlcnRCZWZvcmUoZS5hcHBseSh0aGlzLGFyZ3VtZW50cyksaS5hcHBseSh0aGlzLGFyZ3VtZW50cyl8fG51bGwpfSl9ZnVuY3Rpb24gengoKXt2YXIgbj10aGlzLnBhcmVudE5vZGU7biYmbi5yZW1vdmVDaGlsZCh0aGlzKX1mdW5jdGlvbiBLZigpe3JldHVybiB0aGlzLmVhY2goengpfWZ1bmN0aW9uIFV4KCl7dmFyIG49dGhpcy5jbG9uZU5vZGUoITEpLHQ9dGhpcy5wYXJlbnROb2RlO3JldHVybiB0P3QuaW5zZXJ0QmVmb3JlKG4sdGhpcy5uZXh0U2libGluZyk6bn1mdW5jdGlvbiBCeCgpe3ZhciBuPXRoaXMuY2xvbmVOb2RlKCEwKSx0PXRoaXMucGFyZW50Tm9kZTtyZXR1cm4gdD90Lmluc2VydEJlZm9yZShuLHRoaXMubmV4dFNpYmxpbmcpOm59ZnVuY3Rpb24gUWYobil7cmV0dXJuIHRoaXMuc2VsZWN0KG4/Qng6VXgpfWZ1bmN0aW9uIGpmKG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMucHJvcGVydHkoIl9fZGF0YV9fIixuKTp0aGlzLm5vZGUoKS5fX2RhdGFfX312YXIgZWQ9e30sWWw9bnVsbDt0eXBlb2YgZG9jdW1lbnQhPSJ1bmRlZmluZWQiJiYodGQ9ZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LCJvbm1vdXNlZW50ZXIiaW4gdGR8fChlZD17bW91c2VlbnRlcjoibW91c2VvdmVyIixtb3VzZWxlYXZlOiJtb3VzZW91dCJ9KSk7dmFyIHRkO2Z1bmN0aW9uIE94KG4sdCxlKXtyZXR1cm4gbj1uZChuLHQsZSksZnVuY3Rpb24oaSl7dmFyIHI9aS5yZWxhdGVkVGFyZ2V0Oyghcnx8ciE9PXRoaXMmJiEoci5jb21wYXJlRG9jdW1lbnRQb3NpdGlvbih0aGlzKSY4KSkmJm4uY2FsbCh0aGlzLGkpfX1mdW5jdGlvbiBuZChuLHQsZSl7cmV0dXJuIGZ1bmN0aW9uKGkpe3ZhciByPVlsO1lsPWk7dHJ5e24uY2FsbCh0aGlzLHRoaXMuX19kYXRhX18sdCxlKX1maW5hbGx5e1lsPXJ9fX1mdW5jdGlvbiBreChuKXtyZXR1cm4gbi50cmltKCkuc3BsaXQoL158XHMrLykubWFwKGZ1bmN0aW9uKHQpe3ZhciBlPSIiLGk9dC5pbmRleE9mKCIuIik7cmV0dXJuIGk+PTAmJihlPXQuc2xpY2UoaSsxKSx0PXQuc2xpY2UoMCxpKSkse3R5cGU6dCxuYW1lOmV9fSl9ZnVuY3Rpb24gSHgobil7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5fX29uO2lmKCEhdCl7Zm9yKHZhciBlPTAsaT0tMSxyPXQubGVuZ3RoLHM7ZTxyOysrZSlzPXRbZV0sKCFuLnR5cGV8fHMudHlwZT09PW4udHlwZSkmJnMubmFtZT09PW4ubmFtZT90aGlzLnJlbW92ZUV2ZW50TGlzdGVuZXIocy50eXBlLHMubGlzdGVuZXIscy5jYXB0dXJlKTp0WysraV09czsrK2k/dC5sZW5ndGg9aTpkZWxldGUgdGhpcy5fX29ufX19ZnVuY3Rpb24gVngobix0LGUpe3ZhciBpPWVkLmhhc093blByb3BlcnR5KG4udHlwZSk/T3g6bmQ7cmV0dXJuIGZ1bmN0aW9uKHIscyxvKXt2YXIgYT10aGlzLl9fb24sbCxjPWkodCxzLG8pO2lmKGEpe2Zvcih2YXIgdT0wLGg9YS5sZW5ndGg7dTxoOysrdSlpZigobD1hW3VdKS50eXBlPT09bi50eXBlJiZsLm5hbWU9PT1uLm5hbWUpe3RoaXMucmVtb3ZlRXZlbnRMaXN0ZW5lcihsLnR5cGUsbC5saXN0ZW5lcixsLmNhcHR1cmUpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcihsLnR5cGUsbC5saXN0ZW5lcj1jLGwuY2FwdHVyZT1lKSxsLnZhbHVlPXQ7cmV0dXJufX10aGlzLmFkZEV2ZW50TGlzdGVuZXIobi50eXBlLGMsZSksbD17dHlwZTpuLnR5cGUsbmFtZTpuLm5hbWUsdmFsdWU6dCxsaXN0ZW5lcjpjLGNhcHR1cmU6ZX0sYT9hLnB1c2gobCk6dGhpcy5fX29uPVtsXX19ZnVuY3Rpb24gaWQobix0LGUpe3ZhciBpPWt4KG4rIiIpLHIscz1pLmxlbmd0aCxvO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7dmFyIGE9dGhpcy5ub2RlKCkuX19vbjtpZihhKXtmb3IodmFyIGw9MCxjPWEubGVuZ3RoLHU7bDxjOysrbClmb3Iocj0wLHU9YVtsXTtyPHM7KytyKWlmKChvPWlbcl0pLnR5cGU9PT11LnR5cGUmJm8ubmFtZT09PXUubmFtZSlyZXR1cm4gdS52YWx1ZX1yZXR1cm59Zm9yKGE9dD9WeDpIeCxlPT1udWxsJiYoZT0hMSkscj0wO3I8czsrK3IpdGhpcy5lYWNoKGEoaVtyXSx0LGUpKTtyZXR1cm4gdGhpc31mdW5jdGlvbiByZChuLHQsZSl7dmFyIGk9a28obikscj1pLkN1c3RvbUV2ZW50O3R5cGVvZiByPT0iZnVuY3Rpb24iP3I9bmV3IHIodCxlKToocj1pLmRvY3VtZW50LmNyZWF0ZUV2ZW50KCJFdmVudCIpLGU/KHIuaW5pdEV2ZW50KHQsZS5idWJibGVzLGUuY2FuY2VsYWJsZSksci5kZXRhaWw9ZS5kZXRhaWwpOnIuaW5pdEV2ZW50KHQsITEsITEpKSxuLmRpc3BhdGNoRXZlbnQocil9ZnVuY3Rpb24gR3gobix0KXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gcmQodGhpcyxuLHQpfX1mdW5jdGlvbiBXeChuLHQpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiByZCh0aGlzLG4sdC5hcHBseSh0aGlzLGFyZ3VtZW50cykpfX1mdW5jdGlvbiBzZChuLHQpe3JldHVybiB0aGlzLmVhY2goKHR5cGVvZiB0PT0iZnVuY3Rpb24iP1d4Okd4KShuLHQpKX12YXIgcXg9W251bGxdO2Z1bmN0aW9uIGNlKG4sdCl7dGhpcy5fZ3JvdXBzPW4sdGhpcy5fcGFyZW50cz10fWZ1bmN0aW9uIG9kKCl7cmV0dXJuIG5ldyBjZShbW2RvY3VtZW50LmRvY3VtZW50RWxlbWVudF1dLHF4KX1jZS5wcm90b3R5cGU9b2QucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpjZSxzZWxlY3Q6dmYsc2VsZWN0QWxsOl9mLGZpbHRlcjp3ZixkYXRhOkVmLGVudGVyOk1mLGV4aXQ6VGYsam9pbjpBZixtZXJnZTpDZixvcmRlcjpSZixzb3J0OkxmLGNhbGw6UGYsbm9kZXM6RGYsbm9kZTpJZixzaXplOk5mLGVtcHR5OkZmLGVhY2g6emYsYXR0cjpVZixzdHlsZTpCZixwcm9wZXJ0eTpPZixjbGFzc2VkOldmLHRleHQ6cWYsaHRtbDpYZixyYWlzZTpZZixsb3dlcjpaZixhcHBlbmQ6SmYsaW5zZXJ0OiRmLHJlbW92ZTpLZixjbG9uZTpRZixkYXR1bTpqZixvbjppZCxkaXNwYXRjaDpzZH07dmFyIE1uPW9kO2Z1bmN0aW9uIEhvKG4sdCxlKXtuLnByb3RvdHlwZT10LnByb3RvdHlwZT1lLGUuY29uc3RydWN0b3I9bn1mdW5jdGlvbiBabChuLHQpe3ZhciBlPU9iamVjdC5jcmVhdGUobi5wcm90b3R5cGUpO2Zvcih2YXIgaSBpbiB0KWVbaV09dFtpXTtyZXR1cm4gZX1mdW5jdGlvbiBocygpe312YXIgY3M9LjcsR289MS9jcywkaT0iXFxzKihbKy1dP1xcZCspXFxzKiIsdXM9IlxccyooWystXT9cXGQqXFwuP1xcZCsoPzpbZUVdWystXT9cXGQrKT8pXFxzKiIsY249IlxccyooWystXT9cXGQqXFwuP1xcZCsoPzpbZUVdWystXT9cXGQrKT8pJVxccyoiLFh4PS9eIyhbMC05YS1mXXszLDh9KSQvLFl4PW5ldyBSZWdFeHAoIl5yZ2JcXCgiK1skaSwkaSwkaV0rIlxcKSQiKSxaeD1uZXcgUmVnRXhwKCJecmdiXFwoIitbY24sY24sY25dKyJcXCkkIiksSng9bmV3IFJlZ0V4cCgiXnJnYmFcXCgiK1skaSwkaSwkaSx1c10rIlxcKSQiKSwkeD1uZXcgUmVnRXhwKCJecmdiYVxcKCIrW2NuLGNuLGNuLHVzXSsiXFwpJCIpLEt4PW5ldyBSZWdFeHAoIl5oc2xcXCgiK1t1cyxjbixjbl0rIlxcKSQiKSxReD1uZXcgUmVnRXhwKCJeaHNsYVxcKCIrW3VzLGNuLGNuLHVzXSsiXFwpJCIpLGFkPXthbGljZWJsdWU6MTU3OTIzODMsYW50aXF1ZXdoaXRlOjE2NDQ0Mzc1LGFxdWE6NjU1MzUsYXF1YW1hcmluZTo4Mzg4NTY0LGF6dXJlOjE1Nzk0MTc1LGJlaWdlOjE2MTE5MjYwLGJpc3F1ZToxNjc3MDI0NCxibGFjazowLGJsYW5jaGVkYWxtb25kOjE2NzcyMDQ1LGJsdWU6MjU1LGJsdWV2aW9sZXQ6OTA1NTIwMixicm93bjoxMDgyNDIzNCxidXJseXdvb2Q6MTQ1OTYyMzEsY2FkZXRibHVlOjYyNjY1MjgsY2hhcnRyZXVzZTo4Mzg4MzUyLGNob2NvbGF0ZToxMzc4OTQ3MCxjb3JhbDoxNjc0NDI3Mixjb3JuZmxvd2VyYmx1ZTo2NTkxOTgxLGNvcm5zaWxrOjE2Nzc1Mzg4LGNyaW1zb246MTQ0MjMxMDAsY3lhbjo2NTUzNSxkYXJrYmx1ZToxMzksZGFya2N5YW46MzU3MjMsZGFya2dvbGRlbnJvZDoxMjA5MjkzOSxkYXJrZ3JheToxMTExOTAxNyxkYXJrZ3JlZW46MjU2MDAsZGFya2dyZXk6MTExMTkwMTcsZGFya2toYWtpOjEyNDMzMjU5LGRhcmttYWdlbnRhOjkxMDk2NDMsZGFya29saXZlZ3JlZW46NTU5Nzk5OSxkYXJrb3JhbmdlOjE2NzQ3NTIwLGRhcmtvcmNoaWQ6MTAwNDAwMTIsZGFya3JlZDo5MTA5NTA0LGRhcmtzYWxtb246MTUzMDg0MTAsZGFya3NlYWdyZWVuOjk0MTk5MTksZGFya3NsYXRlYmx1ZTo0NzM0MzQ3LGRhcmtzbGF0ZWdyYXk6MzEwMDQ5NSxkYXJrc2xhdGVncmV5OjMxMDA0OTUsZGFya3R1cnF1b2lzZTo1Mjk0NSxkYXJrdmlvbGV0Ojk2OTk1MzksZGVlcHBpbms6MTY3MTY5NDcsZGVlcHNreWJsdWU6NDkxNTEsZGltZ3JheTo2OTA4MjY1LGRpbWdyZXk6NjkwODI2NSxkb2RnZXJibHVlOjIwMDMxOTksZmlyZWJyaWNrOjExNjc0MTQ2LGZsb3JhbHdoaXRlOjE2Nzc1OTIwLGZvcmVzdGdyZWVuOjIyNjM4NDIsZnVjaHNpYToxNjcxMTkzNSxnYWluc2Jvcm86MTQ0NzQ0NjAsZ2hvc3R3aGl0ZToxNjMxNjY3MSxnb2xkOjE2NzY2NzIwLGdvbGRlbnJvZDoxNDMyOTEyMCxncmF5Ojg0MjE1MDQsZ3JlZW46MzI3NjgsZ3JlZW55ZWxsb3c6MTE0MDMwNTUsZ3JleTo4NDIxNTA0LGhvbmV5ZGV3OjE1Nzk0MTYwLGhvdHBpbms6MTY3Mzg3NDAsaW5kaWFucmVkOjEzNDU4NTI0LGluZGlnbzo0OTE1MzMwLGl2b3J5OjE2Nzc3MjAwLGtoYWtpOjE1Nzg3NjYwLGxhdmVuZGVyOjE1MTMyNDEwLGxhdmVuZGVyYmx1c2g6MTY3NzMzNjUsbGF3bmdyZWVuOjgxOTA5NzYsbGVtb25jaGlmZm9uOjE2Nzc1ODg1LGxpZ2h0Ymx1ZToxMTM5MzI1NCxsaWdodGNvcmFsOjE1NzYxNTM2LGxpZ2h0Y3lhbjoxNDc0NTU5OSxsaWdodGdvbGRlbnJvZHllbGxvdzoxNjQ0ODIxMCxsaWdodGdyYXk6MTM4ODIzMjMsbGlnaHRncmVlbjo5NDk4MjU2LGxpZ2h0Z3JleToxMzg4MjMyMyxsaWdodHBpbms6MTY3NTg0NjUsbGlnaHRzYWxtb246MTY3NTI3NjIsbGlnaHRzZWFncmVlbjoyMTQyODkwLGxpZ2h0c2t5Ymx1ZTo4OTAwMzQ2LGxpZ2h0c2xhdGVncmF5Ojc4MzM3NTMsbGlnaHRzbGF0ZWdyZXk6NzgzMzc1MyxsaWdodHN0ZWVsYmx1ZToxMTU4NDczNCxsaWdodHllbGxvdzoxNjc3NzE4NCxsaW1lOjY1MjgwLGxpbWVncmVlbjozMzI5MzMwLGxpbmVuOjE2NDQ1NjcwLG1hZ2VudGE6MTY3MTE5MzUsbWFyb29uOjgzODg2MDgsbWVkaXVtYXF1YW1hcmluZTo2NzM3MzIyLG1lZGl1bWJsdWU6MjA1LG1lZGl1bW9yY2hpZDoxMjIxMTY2NyxtZWRpdW1wdXJwbGU6OTY2MjY4MyxtZWRpdW1zZWFncmVlbjozOTc4MDk3LG1lZGl1bXNsYXRlYmx1ZTo4MDg3NzkwLG1lZGl1bXNwcmluZ2dyZWVuOjY0MTU0LG1lZGl1bXR1cnF1b2lzZTo0NzcyMzAwLG1lZGl1bXZpb2xldHJlZDoxMzA0NzE3MyxtaWRuaWdodGJsdWU6MTY0NDkxMixtaW50Y3JlYW06MTYxMjE4NTAsbWlzdHlyb3NlOjE2NzcwMjczLG1vY2Nhc2luOjE2NzcwMjI5LG5hdmFqb3doaXRlOjE2NzY4Njg1LG5hdnk6MTI4LG9sZGxhY2U6MTY2NDM1NTgsb2xpdmU6ODQyMTM3NixvbGl2ZWRyYWI6NzA0ODczOSxvcmFuZ2U6MTY3NTM5MjAsb3JhbmdlcmVkOjE2NzI5MzQ0LG9yY2hpZDoxNDMxNTczNCxwYWxlZ29sZGVucm9kOjE1NjU3MTMwLHBhbGVncmVlbjoxMDAyNTg4MCxwYWxldHVycXVvaXNlOjExNTI5OTY2LHBhbGV2aW9sZXRyZWQ6MTQzODEyMDMscGFwYXlhd2hpcDoxNjc3MzA3NyxwZWFjaHB1ZmY6MTY3Njc2NzMscGVydToxMzQ2ODk5MSxwaW5rOjE2NzYxMDM1LHBsdW06MTQ1MjQ2MzcscG93ZGVyYmx1ZToxMTU5MTkxMCxwdXJwbGU6ODM4ODczNixyZWJlY2NhcHVycGxlOjY2OTc4ODEscmVkOjE2NzExNjgwLHJvc3licm93bjoxMjM1NzUxOSxyb3lhbGJsdWU6NDI4Njk0NSxzYWRkbGVicm93bjo5MTI3MTg3LHNhbG1vbjoxNjQxNjg4MixzYW5keWJyb3duOjE2MDMyODY0LHNlYWdyZWVuOjMwNTAzMjcsc2Vhc2hlbGw6MTY3NzQ2Mzgsc2llbm5hOjEwNTA2Nzk3LHNpbHZlcjoxMjYzMjI1Nixza3libHVlOjg5MDAzMzEsc2xhdGVibHVlOjY5NzAwNjEsc2xhdGVncmF5OjczNzI5NDQsc2xhdGVncmV5OjczNzI5NDQsc25vdzoxNjc3NTkzMCxzcHJpbmdncmVlbjo2NTQwNyxzdGVlbGJsdWU6NDYyMDk4MCx0YW46MTM4MDg3ODAsdGVhbDozMjg5Nix0aGlzdGxlOjE0MjA0ODg4LHRvbWF0bzoxNjczNzA5NSx0dXJxdW9pc2U6NDI1MTg1Nix2aW9sZXQ6MTU2MzEwODYsd2hlYXQ6MTYxMTMzMzEsd2hpdGU6MTY3NzcyMTUsd2hpdGVzbW9rZToxNjExOTI4NSx5ZWxsb3c6MTY3NzY5NjAseWVsbG93Z3JlZW46MTAxNDUwNzR9O0hvKGhzLEplLHtjb3B5OmZ1bmN0aW9uKG4pe3JldHVybiBPYmplY3QuYXNzaWduKG5ldyB0aGlzLmNvbnN0cnVjdG9yLHRoaXMsbil9LGRpc3BsYXlhYmxlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMucmdiKCkuZGlzcGxheWFibGUoKX0saGV4OmxkLGZvcm1hdEhleDpsZCxmb3JtYXRIc2w6angsZm9ybWF0UmdiOmNkLHRvU3RyaW5nOmNkfSk7ZnVuY3Rpb24gbGQoKXtyZXR1cm4gdGhpcy5yZ2IoKS5mb3JtYXRIZXgoKX1mdW5jdGlvbiBqeCgpe3JldHVybiBwZCh0aGlzKS5mb3JtYXRIc2woKX1mdW5jdGlvbiBjZCgpe3JldHVybiB0aGlzLnJnYigpLmZvcm1hdFJnYigpfWZ1bmN0aW9uIEplKG4pe3ZhciB0LGU7cmV0dXJuIG49KG4rIiIpLnRyaW0oKS50b0xvd2VyQ2FzZSgpLCh0PVh4LmV4ZWMobikpPyhlPXRbMV0ubGVuZ3RoLHQ9cGFyc2VJbnQodFsxXSwxNiksZT09PTY/dWQodCk6ZT09PTM/bmV3IFVlKHQ+PjgmMTV8dD4+NCYyNDAsdD4+NCYxNXx0JjI0MCwodCYxNSk8PDR8dCYxNSwxKTplPT09OD9Wbyh0Pj4yNCYyNTUsdD4+MTYmMjU1LHQ+PjgmMjU1LCh0JjI1NSkvMjU1KTplPT09ND9Wbyh0Pj4xMiYxNXx0Pj44JjI0MCx0Pj44JjE1fHQ+PjQmMjQwLHQ+PjQmMTV8dCYyNDAsKCh0JjE1KTw8NHx0JjE1KS8yNTUpOm51bGwpOih0PVl4LmV4ZWMobikpP25ldyBVZSh0WzFdLHRbMl0sdFszXSwxKToodD1aeC5leGVjKG4pKT9uZXcgVWUodFsxXSoyNTUvMTAwLHRbMl0qMjU1LzEwMCx0WzNdKjI1NS8xMDAsMSk6KHQ9SnguZXhlYyhuKSk/Vm8odFsxXSx0WzJdLHRbM10sdFs0XSk6KHQ9JHguZXhlYyhuKSk/Vm8odFsxXSoyNTUvMTAwLHRbMl0qMjU1LzEwMCx0WzNdKjI1NS8xMDAsdFs0XSk6KHQ9S3guZXhlYyhuKSk/ZGQodFsxXSx0WzJdLzEwMCx0WzNdLzEwMCwxKToodD1ReC5leGVjKG4pKT9kZCh0WzFdLHRbMl0vMTAwLHRbM10vMTAwLHRbNF0pOmFkLmhhc093blByb3BlcnR5KG4pP3VkKGFkW25dKTpuPT09InRyYW5zcGFyZW50Ij9uZXcgVWUoTmFOLE5hTixOYU4sMCk6bnVsbH1mdW5jdGlvbiB1ZChuKXtyZXR1cm4gbmV3IFVlKG4+PjE2JjI1NSxuPj44JjI1NSxuJjI1NSwxKX1mdW5jdGlvbiBWbyhuLHQsZSxpKXtyZXR1cm4gaTw9MCYmKG49dD1lPU5hTiksbmV3IFVlKG4sdCxlLGkpfWZ1bmN0aW9uIHR5KG4pe3JldHVybiBuIGluc3RhbmNlb2YgaHN8fChuPUplKG4pKSxuPyhuPW4ucmdiKCksbmV3IFVlKG4ucixuLmcsbi5iLG4ub3BhY2l0eSkpOm5ldyBVZX1mdW5jdGlvbiBLaShuLHQsZSxpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTE/dHkobik6bmV3IFVlKG4sdCxlLGk9PW51bGw/MTppKX1mdW5jdGlvbiBVZShuLHQsZSxpKXt0aGlzLnI9K24sdGhpcy5nPSt0LHRoaXMuYj0rZSx0aGlzLm9wYWNpdHk9K2l9SG8oVWUsS2ksWmwoaHMse2JyaWdodGVyOmZ1bmN0aW9uKG4pe3JldHVybiBuPW49PW51bGw/R286TWF0aC5wb3coR28sbiksbmV3IFVlKHRoaXMucipuLHRoaXMuZypuLHRoaXMuYipuLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihuKXtyZXR1cm4gbj1uPT1udWxsP2NzOk1hdGgucG93KGNzLG4pLG5ldyBVZSh0aGlzLnIqbix0aGlzLmcqbix0aGlzLmIqbix0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpc30sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4tLjU8PXRoaXMuciYmdGhpcy5yPDI1NS41JiYtLjU8PXRoaXMuZyYmdGhpcy5nPDI1NS41JiYtLjU8PXRoaXMuYiYmdGhpcy5iPDI1NS41JiYwPD10aGlzLm9wYWNpdHkmJnRoaXMub3BhY2l0eTw9MX0saGV4OmhkLGZvcm1hdEhleDpoZCxmb3JtYXRSZ2I6ZmQsdG9TdHJpbmc6ZmR9KSk7ZnVuY3Rpb24gaGQoKXtyZXR1cm4iIyIrSmwodGhpcy5yKStKbCh0aGlzLmcpK0psKHRoaXMuYil9ZnVuY3Rpb24gZmQoKXt2YXIgbj10aGlzLm9wYWNpdHk7cmV0dXJuIG49aXNOYU4obik/MTpNYXRoLm1heCgwLE1hdGgubWluKDEsbikpLChuPT09MT8icmdiKCI6InJnYmEoIikrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCh0aGlzLnIpfHwwKSkrIiwgIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKHRoaXMuZyl8fDApKSsiLCAiK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQodGhpcy5iKXx8MCkpKyhuPT09MT8iKSI6IiwgIituKyIpIil9ZnVuY3Rpb24gSmwobil7cmV0dXJuIG49TWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZChuKXx8MCkpLChuPDE2PyIwIjoiIikrbi50b1N0cmluZygxNil9ZnVuY3Rpb24gZGQobix0LGUsaSl7cmV0dXJuIGk8PTA/bj10PWU9TmFOOmU8PTB8fGU+PTE/bj10PU5hTjp0PD0wJiYobj1OYU4pLG5ldyBsbihuLHQsZSxpKX1mdW5jdGlvbiBwZChuKXtpZihuIGluc3RhbmNlb2YgbG4pcmV0dXJuIG5ldyBsbihuLmgsbi5zLG4ubCxuLm9wYWNpdHkpO2lmKG4gaW5zdGFuY2VvZiBoc3x8KG49SmUobikpLCFuKXJldHVybiBuZXcgbG47aWYobiBpbnN0YW5jZW9mIGxuKXJldHVybiBuO249bi5yZ2IoKTt2YXIgdD1uLnIvMjU1LGU9bi5nLzI1NSxpPW4uYi8yNTUscj1NYXRoLm1pbih0LGUsaSkscz1NYXRoLm1heCh0LGUsaSksbz1OYU4sYT1zLXIsbD0ocytyKS8yO3JldHVybiBhPyh0PT09cz9vPShlLWkpL2ErKGU8aSkqNjplPT09cz9vPShpLXQpL2ErMjpvPSh0LWUpL2ErNCxhLz1sPC41P3MrcjoyLXMtcixvKj02MCk6YT1sPjAmJmw8MT8wOm8sbmV3IGxuKG8sYSxsLG4ub3BhY2l0eSl9ZnVuY3Rpb24gcGkobix0LGUsaSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP3BkKG4pOm5ldyBsbihuLHQsZSxpPT1udWxsPzE6aSl9ZnVuY3Rpb24gbG4obix0LGUsaSl7dGhpcy5oPStuLHRoaXMucz0rdCx0aGlzLmw9K2UsdGhpcy5vcGFjaXR5PStpfUhvKGxuLHBpLFpsKGhzLHticmlnaHRlcjpmdW5jdGlvbihuKXtyZXR1cm4gbj1uPT1udWxsP0dvOk1hdGgucG93KEdvLG4pLG5ldyBsbih0aGlzLmgsdGhpcy5zLHRoaXMubCpuLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihuKXtyZXR1cm4gbj1uPT1udWxsP2NzOk1hdGgucG93KGNzLG4pLG5ldyBsbih0aGlzLmgsdGhpcy5zLHRoaXMubCpuLHRoaXMub3BhY2l0eSl9LHJnYjpmdW5jdGlvbigpe3ZhciBuPXRoaXMuaCUzNjArKHRoaXMuaDwwKSozNjAsdD1pc05hTihuKXx8aXNOYU4odGhpcy5zKT8wOnRoaXMucyxlPXRoaXMubCxpPWUrKGU8LjU/ZToxLWUpKnQscj0yKmUtaTtyZXR1cm4gbmV3IFVlKCRsKG4+PTI0MD9uLTI0MDpuKzEyMCxyLGkpLCRsKG4scixpKSwkbChuPDEyMD9uKzI0MDpuLTEyMCxyLGkpLHRoaXMub3BhY2l0eSl9LGRpc3BsYXlhYmxlOmZ1bmN0aW9uKCl7cmV0dXJuKDA8PXRoaXMucyYmdGhpcy5zPD0xfHxpc05hTih0aGlzLnMpKSYmMDw9dGhpcy5sJiZ0aGlzLmw8PTEmJjA8PXRoaXMub3BhY2l0eSYmdGhpcy5vcGFjaXR5PD0xfSxmb3JtYXRIc2w6ZnVuY3Rpb24oKXt2YXIgbj10aGlzLm9wYWNpdHk7cmV0dXJuIG49aXNOYU4obik/MTpNYXRoLm1heCgwLE1hdGgubWluKDEsbikpLChuPT09MT8iaHNsKCI6ImhzbGEoIikrKHRoaXMuaHx8MCkrIiwgIisodGhpcy5zfHwwKSoxMDArIiUsICIrKHRoaXMubHx8MCkqMTAwKyIlIisobj09PTE/IikiOiIsICIrbisiKSIpfX0pKTtmdW5jdGlvbiAkbChuLHQsZSl7cmV0dXJuKG48NjA/dCsoZS10KSpuLzYwOm48MTgwP2U6bjwyNDA/dCsoZS10KSooMjQwLW4pLzYwOnQpKjI1NX1mdW5jdGlvbiBLbChuLHQsZSxpLHIpe3ZhciBzPW4qbixvPXMqbjtyZXR1cm4oKDEtMypuKzMqcy1vKSp0Kyg0LTYqcyszKm8pKmUrKDErMypuKzMqcy0zKm8pKmkrbypyKS82fWZ1bmN0aW9uIG1kKG4pe3ZhciB0PW4ubGVuZ3RoLTE7cmV0dXJuIGZ1bmN0aW9uKGUpe3ZhciBpPWU8PTA/ZT0wOmU+PTE/KGU9MSx0LTEpOk1hdGguZmxvb3IoZSp0KSxyPW5baV0scz1uW2krMV0sbz1pPjA/bltpLTFdOjIqci1zLGE9aTx0LTE/bltpKzJdOjIqcy1yO3JldHVybiBLbCgoZS1pL3QpKnQsbyxyLHMsYSl9fWZ1bmN0aW9uIGdkKG4pe3ZhciB0PW4ubGVuZ3RoO3JldHVybiBmdW5jdGlvbihlKXt2YXIgaT1NYXRoLmZsb29yKCgoZSU9MSk8MD8rK2U6ZSkqdCkscj1uWyhpK3QtMSkldF0scz1uW2kldF0sbz1uWyhpKzEpJXRdLGE9blsoaSsyKSV0XTtyZXR1cm4gS2woKGUtaS90KSp0LHIscyxvLGEpfX1mdW5jdGlvbiBRaShuKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gbn19ZnVuY3Rpb24geGQobix0KXtyZXR1cm4gZnVuY3Rpb24oZSl7cmV0dXJuIG4rZSp0fX1mdW5jdGlvbiBleShuLHQsZSl7cmV0dXJuIG49TWF0aC5wb3cobixlKSx0PU1hdGgucG93KHQsZSktbixlPTEvZSxmdW5jdGlvbihpKXtyZXR1cm4gTWF0aC5wb3cobitpKnQsZSl9fWZ1bmN0aW9uIHlkKG4sdCl7dmFyIGU9dC1uO3JldHVybiBlP3hkKG4sZT4xODB8fGU8LTE4MD9lLTM2MCpNYXRoLnJvdW5kKGUvMzYwKTplKTpRaShpc05hTihuKT90Om4pfWZ1bmN0aW9uIHZkKG4pe3JldHVybihuPStuKT09MT9ibjpmdW5jdGlvbih0LGUpe3JldHVybiBlLXQ/ZXkodCxlLG4pOlFpKGlzTmFOKHQpP2U6dCl9fWZ1bmN0aW9uIGJuKG4sdCl7dmFyIGU9dC1uO3JldHVybiBlP3hkKG4sZSk6UWkoaXNOYU4obik/dDpuKX12YXIgbWk9ZnVuY3Rpb24gbih0KXt2YXIgZT12ZCh0KTtmdW5jdGlvbiBpKHIscyl7dmFyIG89ZSgocj1LaShyKSkuciwocz1LaShzKSkuciksYT1lKHIuZyxzLmcpLGw9ZShyLmIscy5iKSxjPWJuKHIub3BhY2l0eSxzLm9wYWNpdHkpO3JldHVybiBmdW5jdGlvbih1KXtyZXR1cm4gci5yPW8odSksci5nPWEodSksci5iPWwodSksci5vcGFjaXR5PWModSkscisiIn19cmV0dXJuIGkuZ2FtbWE9bixpfSgxKTtmdW5jdGlvbiBfZChuKXtyZXR1cm4gZnVuY3Rpb24odCl7dmFyIGU9dC5sZW5ndGgsaT1uZXcgQXJyYXkoZSkscj1uZXcgQXJyYXkoZSkscz1uZXcgQXJyYXkoZSksbyxhO2ZvcihvPTA7bzxlOysrbylhPUtpKHRbb10pLGlbb109YS5yfHwwLHJbb109YS5nfHwwLHNbb109YS5ifHwwO3JldHVybiBpPW4oaSkscj1uKHIpLHM9bihzKSxhLm9wYWNpdHk9MSxmdW5jdGlvbihsKXtyZXR1cm4gYS5yPWkobCksYS5nPXIobCksYS5iPXMobCksYSsiIn19fXZhciBueT1fZChtZCksaXk9X2QoZ2QpO2Z1bmN0aW9uIHdkKG4sdCl7dHx8KHQ9W10pO3ZhciBlPW4/TWF0aC5taW4odC5sZW5ndGgsbi5sZW5ndGgpOjAsaT10LnNsaWNlKCkscjtyZXR1cm4gZnVuY3Rpb24ocyl7Zm9yKHI9MDtyPGU7KytyKWlbcl09bltyXSooMS1zKSt0W3JdKnM7cmV0dXJuIGl9fWZ1bmN0aW9uIE1kKG4pe3JldHVybiBBcnJheUJ1ZmZlci5pc1ZpZXcobikmJiEobiBpbnN0YW5jZW9mIERhdGFWaWV3KX1mdW5jdGlvbiBiZChuLHQpe3ZhciBlPXQ/dC5sZW5ndGg6MCxpPW4/TWF0aC5taW4oZSxuLmxlbmd0aCk6MCxyPW5ldyBBcnJheShpKSxzPW5ldyBBcnJheShlKSxvO2ZvcihvPTA7bzxpOysrbylyW29dPWdpKG5bb10sdFtvXSk7Zm9yKDtvPGU7KytvKXNbb109dFtvXTtyZXR1cm4gZnVuY3Rpb24oYSl7Zm9yKG89MDtvPGk7KytvKXNbb109cltvXShhKTtyZXR1cm4gc319ZnVuY3Rpb24gU2Qobix0KXt2YXIgZT1uZXcgRGF0ZTtyZXR1cm4gbj0rbix0PSt0LGZ1bmN0aW9uKGkpe3JldHVybiBlLnNldFRpbWUobiooMS1pKSt0KmkpLGV9fWZ1bmN0aW9uIHllKG4sdCl7cmV0dXJuIG49K24sdD0rdCxmdW5jdGlvbihlKXtyZXR1cm4gbiooMS1lKSt0KmV9fWZ1bmN0aW9uIEVkKG4sdCl7dmFyIGU9e30saT17fSxyOyhuPT09bnVsbHx8dHlwZW9mIG4hPSJvYmplY3QiKSYmKG49e30pLCh0PT09bnVsbHx8dHlwZW9mIHQhPSJvYmplY3QiKSYmKHQ9e30pO2ZvcihyIGluIHQpciBpbiBuP2Vbcl09Z2kobltyXSx0W3JdKTppW3JdPXRbcl07cmV0dXJuIGZ1bmN0aW9uKHMpe2ZvcihyIGluIGUpaVtyXT1lW3JdKHMpO3JldHVybiBpfX12YXIgamw9L1stK10/KD86XGQrXC4/XGQqfFwuP1xkKykoPzpbZUVdWy0rXT9cZCspPy9nLFFsPW5ldyBSZWdFeHAoamwuc291cmNlLCJnIik7ZnVuY3Rpb24gcnkobil7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIG59fWZ1bmN0aW9uIHN5KG4pe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4gbih0KSsiIn19ZnVuY3Rpb24gZnMobix0KXt2YXIgZT1qbC5sYXN0SW5kZXg9UWwubGFzdEluZGV4PTAsaSxyLHMsbz0tMSxhPVtdLGw9W107Zm9yKG49bisiIix0PXQrIiI7KGk9amwuZXhlYyhuKSkmJihyPVFsLmV4ZWModCkpOykocz1yLmluZGV4KT5lJiYocz10LnNsaWNlKGUscyksYVtvXT9hW29dKz1zOmFbKytvXT1zKSwoaT1pWzBdKT09PShyPXJbMF0pP2Fbb10/YVtvXSs9cjphWysrb109cjooYVsrK29dPW51bGwsbC5wdXNoKHtpOm8seDp5ZShpLHIpfSkpLGU9UWwubGFzdEluZGV4O3JldHVybiBlPHQubGVuZ3RoJiYocz10LnNsaWNlKGUpLGFbb10/YVtvXSs9czphWysrb109cyksYS5sZW5ndGg8Mj9sWzBdP3N5KGxbMF0ueCk6cnkodCk6KHQ9bC5sZW5ndGgsZnVuY3Rpb24oYyl7Zm9yKHZhciB1PTAsaDt1PHQ7Kyt1KWFbKGg9bFt1XSkuaV09aC54KGMpO3JldHVybiBhLmpvaW4oIiIpfSl9ZnVuY3Rpb24gZ2kobix0KXt2YXIgZT10eXBlb2YgdCxpO3JldHVybiB0PT1udWxsfHxlPT09ImJvb2xlYW4iP1FpKHQpOihlPT09Im51bWJlciI/eWU6ZT09PSJzdHJpbmciPyhpPUplKHQpKT8odD1pLG1pKTpmczp0IGluc3RhbmNlb2YgSmU/bWk6dCBpbnN0YW5jZW9mIERhdGU/U2Q6TWQodCk/d2Q6QXJyYXkuaXNBcnJheSh0KT9iZDp0eXBlb2YgdC52YWx1ZU9mIT0iZnVuY3Rpb24iJiZ0eXBlb2YgdC50b1N0cmluZyE9ImZ1bmN0aW9uInx8aXNOYU4odCk/RWQ6eWUpKG4sdCl9ZnVuY3Rpb24gdGMobix0KXtyZXR1cm4gbj0rbix0PSt0LGZ1bmN0aW9uKGUpe3JldHVybiBNYXRoLnJvdW5kKG4qKDEtZSkrdCplKX19dmFyIFRkPTE4MC9NYXRoLlBJLFdvPXt0cmFuc2xhdGVYOjAsdHJhbnNsYXRlWTowLHJvdGF0ZTowLHNrZXdYOjAsc2NhbGVYOjEsc2NhbGVZOjF9O2Z1bmN0aW9uIGVjKG4sdCxlLGkscixzKXt2YXIgbyxhLGw7cmV0dXJuKG89TWF0aC5zcXJ0KG4qbit0KnQpKSYmKG4vPW8sdC89byksKGw9biplK3QqaSkmJihlLT1uKmwsaS09dCpsKSwoYT1NYXRoLnNxcnQoZSplK2kqaSkpJiYoZS89YSxpLz1hLGwvPWEpLG4qaTx0KmUmJihuPS1uLHQ9LXQsbD0tbCxvPS1vKSx7dHJhbnNsYXRlWDpyLHRyYW5zbGF0ZVk6cyxyb3RhdGU6TWF0aC5hdGFuMih0LG4pKlRkLHNrZXdYOk1hdGguYXRhbihsKSpUZCxzY2FsZVg6byxzY2FsZVk6YX19dmFyIGRzLG5jLEFkLHFvO2Z1bmN0aW9uIENkKG4pe3JldHVybiBuPT09Im5vbmUiP1dvOihkc3x8KGRzPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkRJViIpLG5jPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudCxBZD1kb2N1bWVudC5kZWZhdWx0VmlldyksZHMuc3R5bGUudHJhbnNmb3JtPW4sbj1BZC5nZXRDb21wdXRlZFN0eWxlKG5jLmFwcGVuZENoaWxkKGRzKSxudWxsKS5nZXRQcm9wZXJ0eVZhbHVlKCJ0cmFuc2Zvcm0iKSxuYy5yZW1vdmVDaGlsZChkcyksbj1uLnNsaWNlKDcsLTEpLnNwbGl0KCIsIiksZWMoK25bMF0sK25bMV0sK25bMl0sK25bM10sK25bNF0sK25bNV0pKX1mdW5jdGlvbiBSZChuKXtyZXR1cm4gbj09bnVsbD9XbzoocW98fChxbz1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwiZyIpKSxxby5zZXRBdHRyaWJ1dGUoInRyYW5zZm9ybSIsbiksKG49cW8udHJhbnNmb3JtLmJhc2VWYWwuY29uc29saWRhdGUoKSk/KG49bi5tYXRyaXgsZWMobi5hLG4uYixuLmMsbi5kLG4uZSxuLmYpKTpXbyl9ZnVuY3Rpb24gTGQobix0LGUsaSl7ZnVuY3Rpb24gcihjKXtyZXR1cm4gYy5sZW5ndGg/Yy5wb3AoKSsiICI6IiJ9ZnVuY3Rpb24gcyhjLHUsaCxmLGQsZyl7aWYoYyE9PWh8fHUhPT1mKXt2YXIgeD1kLnB1c2goInRyYW5zbGF0ZSgiLG51bGwsdCxudWxsLGUpO2cucHVzaCh7aTp4LTQseDp5ZShjLGgpfSx7aTp4LTIseDp5ZSh1LGYpfSl9ZWxzZShofHxmKSYmZC5wdXNoKCJ0cmFuc2xhdGUoIitoK3QrZitlKX1mdW5jdGlvbiBvKGMsdSxoLGYpe2MhPT11PyhjLXU+MTgwP3UrPTM2MDp1LWM+MTgwJiYoYys9MzYwKSxmLnB1c2goe2k6aC5wdXNoKHIoaCkrInJvdGF0ZSgiLG51bGwsaSktMix4OnllKGMsdSl9KSk6dSYmaC5wdXNoKHIoaCkrInJvdGF0ZSgiK3UraSl9ZnVuY3Rpb24gYShjLHUsaCxmKXtjIT09dT9mLnB1c2goe2k6aC5wdXNoKHIoaCkrInNrZXdYKCIsbnVsbCxpKS0yLHg6eWUoYyx1KX0pOnUmJmgucHVzaChyKGgpKyJza2V3WCgiK3UraSl9ZnVuY3Rpb24gbChjLHUsaCxmLGQsZyl7aWYoYyE9PWh8fHUhPT1mKXt2YXIgeD1kLnB1c2gocihkKSsic2NhbGUoIixudWxsLCIsIixudWxsLCIpIik7Zy5wdXNoKHtpOngtNCx4OnllKGMsaCl9LHtpOngtMix4OnllKHUsZil9KX1lbHNlKGghPT0xfHxmIT09MSkmJmQucHVzaChyKGQpKyJzY2FsZSgiK2grIiwiK2YrIikiKX1yZXR1cm4gZnVuY3Rpb24oYyx1KXt2YXIgaD1bXSxmPVtdO3JldHVybiBjPW4oYyksdT1uKHUpLHMoYy50cmFuc2xhdGVYLGMudHJhbnNsYXRlWSx1LnRyYW5zbGF0ZVgsdS50cmFuc2xhdGVZLGgsZiksbyhjLnJvdGF0ZSx1LnJvdGF0ZSxoLGYpLGEoYy5za2V3WCx1LnNrZXdYLGgsZiksbChjLnNjYWxlWCxjLnNjYWxlWSx1LnNjYWxlWCx1LnNjYWxlWSxoLGYpLGM9dT1udWxsLGZ1bmN0aW9uKGQpe2Zvcih2YXIgZz0tMSx4PWYubGVuZ3RoLHY7KytnPHg7KWhbKHY9ZltnXSkuaV09di54KGQpO3JldHVybiBoLmpvaW4oIiIpfX19dmFyIGljPUxkKENkLCJweCwgIiwicHgpIiwiZGVnKSIpLHJjPUxkKFJkLCIsICIsIikiLCIpIik7ZnVuY3Rpb24gUGQobil7cmV0dXJuIGZ1bmN0aW9uKHQsZSl7dmFyIGk9bigodD1waSh0KSkuaCwoZT1waShlKSkuaCkscj1ibih0LnMsZS5zKSxzPWJuKHQubCxlLmwpLG89Ym4odC5vcGFjaXR5LGUub3BhY2l0eSk7cmV0dXJuIGZ1bmN0aW9uKGEpe3JldHVybiB0Lmg9aShhKSx0LnM9cihhKSx0Lmw9cyhhKSx0Lm9wYWNpdHk9byhhKSx0KyIifX19dmFyIHNjPVBkKHlkKSxveT1QZChibik7dmFyIGppPTAsbXM9MCxwcz0wLElkPTFlMyxYbyxncyxZbz0wLHhpPTAsWm89MCx4cz10eXBlb2YgcGVyZm9ybWFuY2U9PSJvYmplY3QiJiZwZXJmb3JtYW5jZS5ub3c/cGVyZm9ybWFuY2U6RGF0ZSxOZD10eXBlb2Ygd2luZG93PT0ib2JqZWN0IiYmd2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZT93aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lLmJpbmQod2luZG93KTpmdW5jdGlvbihuKXtzZXRUaW1lb3V0KG4sMTcpfTtmdW5jdGlvbiB0cigpe3JldHVybiB4aXx8KE5kKGF5KSx4aT14cy5ub3coKStabyl9ZnVuY3Rpb24gYXkoKXt4aT0wfWZ1bmN0aW9uIHlzKCl7dGhpcy5fY2FsbD10aGlzLl90aW1lPXRoaXMuX25leHQ9bnVsbH15cy5wcm90b3R5cGU9Sm8ucHJvdG90eXBlPXtjb25zdHJ1Y3Rvcjp5cyxyZXN0YXJ0OmZ1bmN0aW9uKG4sdCxlKXtpZih0eXBlb2YgbiE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgVHlwZUVycm9yKCJjYWxsYmFjayBpcyBub3QgYSBmdW5jdGlvbiIpO2U9KGU9PW51bGw/dHIoKTorZSkrKHQ9PW51bGw/MDordCksIXRoaXMuX25leHQmJmdzIT09dGhpcyYmKGdzP2dzLl9uZXh0PXRoaXM6WG89dGhpcyxncz10aGlzKSx0aGlzLl9jYWxsPW4sdGhpcy5fdGltZT1lLG9jKCl9LHN0b3A6ZnVuY3Rpb24oKXt0aGlzLl9jYWxsJiYodGhpcy5fY2FsbD1udWxsLHRoaXMuX3RpbWU9MS8wLG9jKCkpfX07ZnVuY3Rpb24gSm8obix0LGUpe3ZhciBpPW5ldyB5cztyZXR1cm4gaS5yZXN0YXJ0KG4sdCxlKSxpfWZ1bmN0aW9uIEZkKCl7dHIoKSwrK2ppO2Zvcih2YXIgbj1Ybyx0O247KSh0PXhpLW4uX3RpbWUpPj0wJiZuLl9jYWxsLmNhbGwobnVsbCx0KSxuPW4uX25leHQ7LS1qaX1mdW5jdGlvbiBEZCgpe3hpPShZbz14cy5ub3coKSkrWm8samk9bXM9MDt0cnl7RmQoKX1maW5hbGx5e2ppPTAsY3koKSx4aT0wfX1mdW5jdGlvbiBseSgpe3ZhciBuPXhzLm5vdygpLHQ9bi1Zbzt0PklkJiYoWm8tPXQsWW89bil9ZnVuY3Rpb24gY3koKXtmb3IodmFyIG4sdD1YbyxlLGk9MS8wO3Q7KXQuX2NhbGw/KGk+dC5fdGltZSYmKGk9dC5fdGltZSksbj10LHQ9dC5fbmV4dCk6KGU9dC5fbmV4dCx0Ll9uZXh0PW51bGwsdD1uP24uX25leHQ9ZTpYbz1lKTtncz1uLG9jKGkpfWZ1bmN0aW9uIG9jKG4pe2lmKCFqaSl7bXMmJihtcz1jbGVhclRpbWVvdXQobXMpKTt2YXIgdD1uLXhpO3Q+MjQ/KG48MS8wJiYobXM9c2V0VGltZW91dChEZCxuLXhzLm5vdygpLVpvKSkscHMmJihwcz1jbGVhckludGVydmFsKHBzKSkpOihwc3x8KFlvPXhzLm5vdygpLHBzPXNldEludGVydmFsKGx5LElkKSksamk9MSxOZChEZCkpfX1mdW5jdGlvbiAkbyhuLHQsZSl7dmFyIGk9bmV3IHlzO3JldHVybiB0PXQ9PW51bGw/MDordCxpLnJlc3RhcnQoZnVuY3Rpb24ocil7aS5zdG9wKCksbihyK3QpfSx0LGUpLGl9dmFyIHV5PVdsKCJzdGFydCIsImVuZCIsImNhbmNlbCIsImludGVycnVwdCIpLGh5PVtdLFVkPTAsYWM9MSxRbz0yLEtvPTMsemQ9NCxqbz01LHZzPTY7ZnVuY3Rpb24ga24obix0LGUsaSxyLHMpe3ZhciBvPW4uX190cmFuc2l0aW9uO2lmKCFvKW4uX190cmFuc2l0aW9uPXt9O2Vsc2UgaWYoZSBpbiBvKXJldHVybjtmeShuLGUse25hbWU6dCxpbmRleDppLGdyb3VwOnIsb246dXksdHdlZW46aHksdGltZTpzLnRpbWUsZGVsYXk6cy5kZWxheSxkdXJhdGlvbjpzLmR1cmF0aW9uLGVhc2U6cy5lYXNlLHRpbWVyOm51bGwsc3RhdGU6VWR9KX1mdW5jdGlvbiBfcyhuLHQpe3ZhciBlPXVlKG4sdCk7aWYoZS5zdGF0ZT5VZCl0aHJvdyBuZXcgRXJyb3IoInRvbyBsYXRlOyBhbHJlYWR5IHNjaGVkdWxlZCIpO3JldHVybiBlfWZ1bmN0aW9uIEFlKG4sdCl7dmFyIGU9dWUobix0KTtpZihlLnN0YXRlPktvKXRocm93IG5ldyBFcnJvcigidG9vIGxhdGU7IGFscmVhZHkgcnVubmluZyIpO3JldHVybiBlfWZ1bmN0aW9uIHVlKG4sdCl7dmFyIGU9bi5fX3RyYW5zaXRpb247aWYoIWV8fCEoZT1lW3RdKSl0aHJvdyBuZXcgRXJyb3IoInRyYW5zaXRpb24gbm90IGZvdW5kIik7cmV0dXJuIGV9ZnVuY3Rpb24gZnkobix0LGUpe3ZhciBpPW4uX190cmFuc2l0aW9uLHI7aVt0XT1lLGUudGltZXI9Sm8ocywwLGUudGltZSk7ZnVuY3Rpb24gcyhjKXtlLnN0YXRlPWFjLGUudGltZXIucmVzdGFydChvLGUuZGVsYXksZS50aW1lKSxlLmRlbGF5PD1jJiZvKGMtZS5kZWxheSl9ZnVuY3Rpb24gbyhjKXt2YXIgdSxoLGYsZDtpZihlLnN0YXRlIT09YWMpcmV0dXJuIGwoKTtmb3IodSBpbiBpKWlmKGQ9aVt1XSxkLm5hbWU9PT1lLm5hbWUpe2lmKGQuc3RhdGU9PT1LbylyZXR1cm4gJG8obyk7ZC5zdGF0ZT09PXpkPyhkLnN0YXRlPXZzLGQudGltZXIuc3RvcCgpLGQub24uY2FsbCgiaW50ZXJydXB0IixuLG4uX19kYXRhX18sZC5pbmRleCxkLmdyb3VwKSxkZWxldGUgaVt1XSk6K3U8dCYmKGQuc3RhdGU9dnMsZC50aW1lci5zdG9wKCksZC5vbi5jYWxsKCJjYW5jZWwiLG4sbi5fX2RhdGFfXyxkLmluZGV4LGQuZ3JvdXApLGRlbGV0ZSBpW3VdKX1pZigkbyhmdW5jdGlvbigpe2Uuc3RhdGU9PT1LbyYmKGUuc3RhdGU9emQsZS50aW1lci5yZXN0YXJ0KGEsZS5kZWxheSxlLnRpbWUpLGEoYykpfSksZS5zdGF0ZT1RbyxlLm9uLmNhbGwoInN0YXJ0IixuLG4uX19kYXRhX18sZS5pbmRleCxlLmdyb3VwKSxlLnN0YXRlPT09UW8pe2ZvcihlLnN0YXRlPUtvLHI9bmV3IEFycmF5KGY9ZS50d2Vlbi5sZW5ndGgpLHU9MCxoPS0xO3U8ZjsrK3UpKGQ9ZS50d2Vlblt1XS52YWx1ZS5jYWxsKG4sbi5fX2RhdGFfXyxlLmluZGV4LGUuZ3JvdXApKSYmKHJbKytoXT1kKTtyLmxlbmd0aD1oKzF9fWZ1bmN0aW9uIGEoYyl7Zm9yKHZhciB1PWM8ZS5kdXJhdGlvbj9lLmVhc2UuY2FsbChudWxsLGMvZS5kdXJhdGlvbik6KGUudGltZXIucmVzdGFydChsKSxlLnN0YXRlPWpvLDEpLGg9LTEsZj1yLmxlbmd0aDsrK2g8ZjspcltoXS5jYWxsKG4sdSk7ZS5zdGF0ZT09PWpvJiYoZS5vbi5jYWxsKCJlbmQiLG4sbi5fX2RhdGFfXyxlLmluZGV4LGUuZ3JvdXApLGwoKSl9ZnVuY3Rpb24gbCgpe2Uuc3RhdGU9dnMsZS50aW1lci5zdG9wKCksZGVsZXRlIGlbdF07Zm9yKHZhciBjIGluIGkpcmV0dXJuO2RlbGV0ZSBuLl9fdHJhbnNpdGlvbn19ZnVuY3Rpb24gd3Mobix0KXt2YXIgZT1uLl9fdHJhbnNpdGlvbixpLHIscz0hMCxvO2lmKCEhZSl7dD10PT1udWxsP251bGw6dCsiIjtmb3IobyBpbiBlKXtpZigoaT1lW29dKS5uYW1lIT09dCl7cz0hMTtjb250aW51ZX1yPWkuc3RhdGU+UW8mJmkuc3RhdGU8am8saS5zdGF0ZT12cyxpLnRpbWVyLnN0b3AoKSxpLm9uLmNhbGwocj8iaW50ZXJydXB0IjoiY2FuY2VsIixuLG4uX19kYXRhX18saS5pbmRleCxpLmdyb3VwKSxkZWxldGUgZVtvXX1zJiZkZWxldGUgbi5fX3RyYW5zaXRpb259fWZ1bmN0aW9uIEJkKG4pe3JldHVybiB0aGlzLmVhY2goZnVuY3Rpb24oKXt3cyh0aGlzLG4pfSl9ZnVuY3Rpb24gZHkobix0KXt2YXIgZSxpO3JldHVybiBmdW5jdGlvbigpe3ZhciByPUFlKHRoaXMsbikscz1yLnR3ZWVuO2lmKHMhPT1lKXtpPWU9cztmb3IodmFyIG89MCxhPWkubGVuZ3RoO288YTsrK28paWYoaVtvXS5uYW1lPT09dCl7aT1pLnNsaWNlKCksaS5zcGxpY2UobywxKTticmVha319ci50d2Vlbj1pfX1mdW5jdGlvbiBweShuLHQsZSl7dmFyIGkscjtpZih0eXBlb2YgZSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3I7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHM9QWUodGhpcyxuKSxvPXMudHdlZW47aWYobyE9PWkpe3I9KGk9bykuc2xpY2UoKTtmb3IodmFyIGE9e25hbWU6dCx2YWx1ZTplfSxsPTAsYz1yLmxlbmd0aDtsPGM7KytsKWlmKHJbbF0ubmFtZT09PXQpe3JbbF09YTticmVha31sPT09YyYmci5wdXNoKGEpfXMudHdlZW49cn19ZnVuY3Rpb24gT2Qobix0KXt2YXIgZT10aGlzLl9pZDtpZihuKz0iIixhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcih2YXIgaT11ZSh0aGlzLm5vZGUoKSxlKS50d2VlbixyPTAscz1pLmxlbmd0aCxvO3I8czsrK3IpaWYoKG89aVtyXSkubmFtZT09PW4pcmV0dXJuIG8udmFsdWU7cmV0dXJuIG51bGx9cmV0dXJuIHRoaXMuZWFjaCgodD09bnVsbD9keTpweSkoZSxuLHQpKX1mdW5jdGlvbiBlcihuLHQsZSl7dmFyIGk9bi5faWQ7cmV0dXJuIG4uZWFjaChmdW5jdGlvbigpe3ZhciByPUFlKHRoaXMsaSk7KHIudmFsdWV8fChyLnZhbHVlPXt9KSlbdF09ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyl9KSxmdW5jdGlvbihyKXtyZXR1cm4gdWUocixpKS52YWx1ZVt0XX19ZnVuY3Rpb24gdGEobix0KXt2YXIgZTtyZXR1cm4odHlwZW9mIHQ9PSJudW1iZXIiP3llOnQgaW5zdGFuY2VvZiBKZT9taTooZT1KZSh0KSk/KHQ9ZSxtaSk6ZnMpKG4sdCl9ZnVuY3Rpb24gbXkobil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGUobil9fWZ1bmN0aW9uIGd5KG4pe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlTlMobi5zcGFjZSxuLmxvY2FsKX19ZnVuY3Rpb24geHkobix0LGUpe3ZhciBpLHI9ZSsiIixzO3JldHVybiBmdW5jdGlvbigpe3ZhciBvPXRoaXMuZ2V0QXR0cmlidXRlKG4pO3JldHVybiBvPT09cj9udWxsOm89PT1pP3M6cz10KGk9byxlKX19ZnVuY3Rpb24geXkobix0LGUpe3ZhciBpLHI9ZSsiIixzO3JldHVybiBmdW5jdGlvbigpe3ZhciBvPXRoaXMuZ2V0QXR0cmlidXRlTlMobi5zcGFjZSxuLmxvY2FsKTtyZXR1cm4gbz09PXI/bnVsbDpvPT09aT9zOnM9dChpPW8sZSl9fWZ1bmN0aW9uIHZ5KG4sdCxlKXt2YXIgaSxyLHM7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIG8sYT1lKHRoaXMpLGw7cmV0dXJuIGE9PW51bGw/dm9pZCB0aGlzLnJlbW92ZUF0dHJpYnV0ZShuKToobz10aGlzLmdldEF0dHJpYnV0ZShuKSxsPWErIiIsbz09PWw/bnVsbDpvPT09aSYmbD09PXI/czoocj1sLHM9dChpPW8sYSkpKX19ZnVuY3Rpb24gX3kobix0LGUpe3ZhciBpLHIscztyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbyxhPWUodGhpcyksbDtyZXR1cm4gYT09bnVsbD92b2lkIHRoaXMucmVtb3ZlQXR0cmlidXRlTlMobi5zcGFjZSxuLmxvY2FsKToobz10aGlzLmdldEF0dHJpYnV0ZU5TKG4uc3BhY2Usbi5sb2NhbCksbD1hKyIiLG89PT1sP251bGw6bz09PWkmJmw9PT1yP3M6KHI9bCxzPXQoaT1vLGEpKSl9fWZ1bmN0aW9uIGtkKG4sdCl7dmFyIGU9d24obiksaT1lPT09InRyYW5zZm9ybSI/cmM6dGE7cmV0dXJuIHRoaXMuYXR0clR3ZWVuKG4sdHlwZW9mIHQ9PSJmdW5jdGlvbiI/KGUubG9jYWw/X3k6dnkpKGUsaSxlcih0aGlzLCJhdHRyLiIrbix0KSk6dD09bnVsbD8oZS5sb2NhbD9neTpteSkoZSk6KGUubG9jYWw/eXk6eHkpKGUsaSx0KSl9ZnVuY3Rpb24gd3kobix0KXtyZXR1cm4gZnVuY3Rpb24oZSl7dGhpcy5zZXRBdHRyaWJ1dGUobix0LmNhbGwodGhpcyxlKSl9fWZ1bmN0aW9uIE15KG4sdCl7cmV0dXJuIGZ1bmN0aW9uKGUpe3RoaXMuc2V0QXR0cmlidXRlTlMobi5zcGFjZSxuLmxvY2FsLHQuY2FsbCh0aGlzLGUpKX19ZnVuY3Rpb24gYnkobix0KXt2YXIgZSxpO2Z1bmN0aW9uIHIoKXt2YXIgcz10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyZXR1cm4gcyE9PWkmJihlPShpPXMpJiZNeShuLHMpKSxlfXJldHVybiByLl92YWx1ZT10LHJ9ZnVuY3Rpb24gU3kobix0KXt2YXIgZSxpO2Z1bmN0aW9uIHIoKXt2YXIgcz10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyZXR1cm4gcyE9PWkmJihlPShpPXMpJiZ3eShuLHMpKSxlfXJldHVybiByLl92YWx1ZT10LHJ9ZnVuY3Rpb24gSGQobix0KXt2YXIgZT0iYXR0ci4iK247aWYoYXJndW1lbnRzLmxlbmd0aDwyKXJldHVybihlPXRoaXMudHdlZW4oZSkpJiZlLl92YWx1ZTtpZih0PT1udWxsKXJldHVybiB0aGlzLnR3ZWVuKGUsbnVsbCk7aWYodHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3ZhciBpPXduKG4pO3JldHVybiB0aGlzLnR3ZWVuKGUsKGkubG9jYWw/Ynk6U3kpKGksdCkpfWZ1bmN0aW9uIEV5KG4sdCl7cmV0dXJuIGZ1bmN0aW9uKCl7X3ModGhpcyxuKS5kZWxheT0rdC5hcHBseSh0aGlzLGFyZ3VtZW50cyl9fWZ1bmN0aW9uIFR5KG4sdCl7cmV0dXJuIHQ9K3QsZnVuY3Rpb24oKXtfcyh0aGlzLG4pLmRlbGF5PXR9fWZ1bmN0aW9uIFZkKG4pe3ZhciB0PXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaCgodHlwZW9mIG49PSJmdW5jdGlvbiI/RXk6VHkpKHQsbikpOnVlKHRoaXMubm9kZSgpLHQpLmRlbGF5fWZ1bmN0aW9uIEF5KG4sdCl7cmV0dXJuIGZ1bmN0aW9uKCl7QWUodGhpcyxuKS5kdXJhdGlvbj0rdC5hcHBseSh0aGlzLGFyZ3VtZW50cyl9fWZ1bmN0aW9uIEN5KG4sdCl7cmV0dXJuIHQ9K3QsZnVuY3Rpb24oKXtBZSh0aGlzLG4pLmR1cmF0aW9uPXR9fWZ1bmN0aW9uIEdkKG4pe3ZhciB0PXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaCgodHlwZW9mIG49PSJmdW5jdGlvbiI/QXk6Q3kpKHQsbikpOnVlKHRoaXMubm9kZSgpLHQpLmR1cmF0aW9ufWZ1bmN0aW9uIFJ5KG4sdCl7aWYodHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3JldHVybiBmdW5jdGlvbigpe0FlKHRoaXMsbikuZWFzZT10fX1mdW5jdGlvbiBXZChuKXt2YXIgdD10aGlzLl9pZDtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2goUnkodCxuKSk6dWUodGhpcy5ub2RlKCksdCkuZWFzZX1mdW5jdGlvbiBxZChuKXt0eXBlb2YgbiE9ImZ1bmN0aW9uIiYmKG49YXMobikpO2Zvcih2YXIgdD10aGlzLl9ncm91cHMsZT10Lmxlbmd0aCxpPW5ldyBBcnJheShlKSxyPTA7cjxlOysrcilmb3IodmFyIHM9dFtyXSxvPXMubGVuZ3RoLGE9aVtyXT1bXSxsLGM9MDtjPG87KytjKShsPXNbY10pJiZuLmNhbGwobCxsLl9fZGF0YV9fLGMscykmJmEucHVzaChsKTtyZXR1cm4gbmV3IHdlKGksdGhpcy5fcGFyZW50cyx0aGlzLl9uYW1lLHRoaXMuX2lkKX1mdW5jdGlvbiBYZChuKXtpZihuLl9pZCE9PXRoaXMuX2lkKXRocm93IG5ldyBFcnJvcjtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLGU9bi5fZ3JvdXBzLGk9dC5sZW5ndGgscj1lLmxlbmd0aCxzPU1hdGgubWluKGksciksbz1uZXcgQXJyYXkoaSksYT0wO2E8czsrK2EpZm9yKHZhciBsPXRbYV0sYz1lW2FdLHU9bC5sZW5ndGgsaD1vW2FdPW5ldyBBcnJheSh1KSxmLGQ9MDtkPHU7KytkKShmPWxbZF18fGNbZF0pJiYoaFtkXT1mKTtmb3IoO2E8aTsrK2Epb1thXT10W2FdO3JldHVybiBuZXcgd2Uobyx0aGlzLl9wYXJlbnRzLHRoaXMuX25hbWUsdGhpcy5faWQpfWZ1bmN0aW9uIEx5KG4pe3JldHVybihuKyIiKS50cmltKCkuc3BsaXQoL158XHMrLykuZXZlcnkoZnVuY3Rpb24odCl7dmFyIGU9dC5pbmRleE9mKCIuIik7cmV0dXJuIGU+PTAmJih0PXQuc2xpY2UoMCxlKSksIXR8fHQ9PT0ic3RhcnQifSl9ZnVuY3Rpb24gUHkobix0LGUpe3ZhciBpLHIscz1MeSh0KT9fczpBZTtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbz1zKHRoaXMsbiksYT1vLm9uO2EhPT1pJiYocj0oaT1hKS5jb3B5KCkpLm9uKHQsZSksby5vbj1yfX1mdW5jdGlvbiBZZChuLHQpe3ZhciBlPXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoPDI/dWUodGhpcy5ub2RlKCksZSkub24ub24obik6dGhpcy5lYWNoKFB5KGUsbix0KSl9ZnVuY3Rpb24gRHkobil7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5wYXJlbnROb2RlO2Zvcih2YXIgZSBpbiB0aGlzLl9fdHJhbnNpdGlvbilpZigrZSE9PW4pcmV0dXJuO3QmJnQucmVtb3ZlQ2hpbGQodGhpcyl9fWZ1bmN0aW9uIFpkKCl7cmV0dXJuIHRoaXMub24oImVuZC5yZW1vdmUiLER5KHRoaXMuX2lkKSl9ZnVuY3Rpb24gSmQobil7dmFyIHQ9dGhpcy5fbmFtZSxlPXRoaXMuX2lkO3R5cGVvZiBuIT0iZnVuY3Rpb24iJiYobj1kaShuKSk7Zm9yKHZhciBpPXRoaXMuX2dyb3VwcyxyPWkubGVuZ3RoLHM9bmV3IEFycmF5KHIpLG89MDtvPHI7KytvKWZvcih2YXIgYT1pW29dLGw9YS5sZW5ndGgsYz1zW29dPW5ldyBBcnJheShsKSx1LGgsZj0wO2Y8bDsrK2YpKHU9YVtmXSkmJihoPW4uY2FsbCh1LHUuX19kYXRhX18sZixhKSkmJigiX19kYXRhX18iaW4gdSYmKGguX19kYXRhX189dS5fX2RhdGFfXyksY1tmXT1oLGtuKGNbZl0sdCxlLGYsYyx1ZSh1LGUpKSk7cmV0dXJuIG5ldyB3ZShzLHRoaXMuX3BhcmVudHMsdCxlKX1mdW5jdGlvbiAkZChuKXt2YXIgdD10aGlzLl9uYW1lLGU9dGhpcy5faWQ7dHlwZW9mIG4hPSJmdW5jdGlvbiImJihuPW9zKG4pKTtmb3IodmFyIGk9dGhpcy5fZ3JvdXBzLHI9aS5sZW5ndGgscz1bXSxvPVtdLGE9MDthPHI7KythKWZvcih2YXIgbD1pW2FdLGM9bC5sZW5ndGgsdSxoPTA7aDxjOysraClpZih1PWxbaF0pe2Zvcih2YXIgZj1uLmNhbGwodSx1Ll9fZGF0YV9fLGgsbCksZCxnPXVlKHUsZSkseD0wLHY9Zi5sZW5ndGg7eDx2OysreCkoZD1mW3hdKSYma24oZCx0LGUseCxmLGcpO3MucHVzaChmKSxvLnB1c2godSl9cmV0dXJuIG5ldyB3ZShzLG8sdCxlKX12YXIgSXk9TW4ucHJvdG90eXBlLmNvbnN0cnVjdG9yO2Z1bmN0aW9uIEtkKCl7cmV0dXJuIG5ldyBJeSh0aGlzLl9ncm91cHMsdGhpcy5fcGFyZW50cyl9ZnVuY3Rpb24gTnkobix0KXt2YXIgZSxpLHI7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHM9T24odGhpcyxuKSxvPSh0aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KG4pLE9uKHRoaXMsbikpO3JldHVybiBzPT09bz9udWxsOnM9PT1lJiZvPT09aT9yOnI9dChlPXMsaT1vKX19ZnVuY3Rpb24gUWQobil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShuKX19ZnVuY3Rpb24gRnkobix0LGUpe3ZhciBpLHI9ZSsiIixzO3JldHVybiBmdW5jdGlvbigpe3ZhciBvPU9uKHRoaXMsbik7cmV0dXJuIG89PT1yP251bGw6bz09PWk/czpzPXQoaT1vLGUpfX1mdW5jdGlvbiB6eShuLHQsZSl7dmFyIGkscixzO3JldHVybiBmdW5jdGlvbigpe3ZhciBvPU9uKHRoaXMsbiksYT1lKHRoaXMpLGw9YSsiIjtyZXR1cm4gYT09bnVsbCYmKGw9YT0odGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShuKSxPbih0aGlzLG4pKSksbz09PWw/bnVsbDpvPT09aSYmbD09PXI/czoocj1sLHM9dChpPW8sYSkpfX1mdW5jdGlvbiBVeShuLHQpe3ZhciBlLGkscixzPSJzdHlsZS4iK3Qsbz0iZW5kLiIrcyxhO3JldHVybiBmdW5jdGlvbigpe3ZhciBsPUFlKHRoaXMsbiksYz1sLm9uLHU9bC52YWx1ZVtzXT09bnVsbD9hfHwoYT1RZCh0KSk6dm9pZCAwOyhjIT09ZXx8ciE9PXUpJiYoaT0oZT1jKS5jb3B5KCkpLm9uKG8scj11KSxsLm9uPWl9fWZ1bmN0aW9uIGpkKG4sdCxlKXt2YXIgaT0obis9IiIpPT0idHJhbnNmb3JtIj9pYzp0YTtyZXR1cm4gdD09bnVsbD90aGlzLnN0eWxlVHdlZW4obixOeShuLGkpKS5vbigiZW5kLnN0eWxlLiIrbixRZChuKSk6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/dGhpcy5zdHlsZVR3ZWVuKG4senkobixpLGVyKHRoaXMsInN0eWxlLiIrbix0KSkpLmVhY2goVXkodGhpcy5faWQsbikpOnRoaXMuc3R5bGVUd2VlbihuLEZ5KG4saSx0KSxlKS5vbigiZW5kLnN0eWxlLiIrbixudWxsKX1mdW5jdGlvbiBCeShuLHQsZSl7cmV0dXJuIGZ1bmN0aW9uKGkpe3RoaXMuc3R5bGUuc2V0UHJvcGVydHkobix0LmNhbGwodGhpcyxpKSxlKX19ZnVuY3Rpb24gT3kobix0LGUpe3ZhciBpLHI7ZnVuY3Rpb24gcygpe3ZhciBvPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiBvIT09ciYmKGk9KHI9bykmJkJ5KG4sbyxlKSksaX1yZXR1cm4gcy5fdmFsdWU9dCxzfWZ1bmN0aW9uIHRwKG4sdCxlKXt2YXIgaT0ic3R5bGUuIisobis9IiIpO2lmKGFyZ3VtZW50cy5sZW5ndGg8MilyZXR1cm4oaT10aGlzLnR3ZWVuKGkpKSYmaS5fdmFsdWU7aWYodD09bnVsbClyZXR1cm4gdGhpcy50d2VlbihpLG51bGwpO2lmKHR5cGVvZiB0IT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gdGhpcy50d2VlbihpLE95KG4sdCxlPT1udWxsPyIiOmUpKX1mdW5jdGlvbiBreShuKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnRleHRDb250ZW50PW59fWZ1bmN0aW9uIEh5KG4pe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PW4odGhpcyk7dGhpcy50ZXh0Q29udGVudD10PT1udWxsPyIiOnR9fWZ1bmN0aW9uIGVwKG4pe3JldHVybiB0aGlzLnR3ZWVuKCJ0ZXh0Iix0eXBlb2Ygbj09ImZ1bmN0aW9uIj9IeShlcih0aGlzLCJ0ZXh0IixuKSk6a3kobj09bnVsbD8iIjpuKyIiKSl9ZnVuY3Rpb24gVnkobil7cmV0dXJuIGZ1bmN0aW9uKHQpe3RoaXMudGV4dENvbnRlbnQ9bi5jYWxsKHRoaXMsdCl9fWZ1bmN0aW9uIEd5KG4pe3ZhciB0LGU7ZnVuY3Rpb24gaSgpe3ZhciByPW4uYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiByIT09ZSYmKHQ9KGU9cikmJlZ5KHIpKSx0fXJldHVybiBpLl92YWx1ZT1uLGl9ZnVuY3Rpb24gbnAobil7dmFyIHQ9InRleHQiO2lmKGFyZ3VtZW50cy5sZW5ndGg8MSlyZXR1cm4odD10aGlzLnR3ZWVuKHQpKSYmdC5fdmFsdWU7aWYobj09bnVsbClyZXR1cm4gdGhpcy50d2Vlbih0LG51bGwpO2lmKHR5cGVvZiBuIT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gdGhpcy50d2Vlbih0LEd5KG4pKX1mdW5jdGlvbiBpcCgpe2Zvcih2YXIgbj10aGlzLl9uYW1lLHQ9dGhpcy5faWQsZT1lYSgpLGk9dGhpcy5fZ3JvdXBzLHI9aS5sZW5ndGgscz0wO3M8cjsrK3MpZm9yKHZhciBvPWlbc10sYT1vLmxlbmd0aCxsLGM9MDtjPGE7KytjKWlmKGw9b1tjXSl7dmFyIHU9dWUobCx0KTtrbihsLG4sZSxjLG8se3RpbWU6dS50aW1lK3UuZGVsYXkrdS5kdXJhdGlvbixkZWxheTowLGR1cmF0aW9uOnUuZHVyYXRpb24sZWFzZTp1LmVhc2V9KX1yZXR1cm4gbmV3IHdlKGksdGhpcy5fcGFyZW50cyxuLGUpfWZ1bmN0aW9uIHJwKCl7dmFyIG4sdCxlPXRoaXMsaT1lLl9pZCxyPWUuc2l6ZSgpO3JldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbihzLG8pe3ZhciBhPXt2YWx1ZTpvfSxsPXt2YWx1ZTpmdW5jdGlvbigpey0tcj09PTAmJnMoKX19O2UuZWFjaChmdW5jdGlvbigpe3ZhciBjPUFlKHRoaXMsaSksdT1jLm9uO3UhPT1uJiYodD0obj11KS5jb3B5KCksdC5fLmNhbmNlbC5wdXNoKGEpLHQuXy5pbnRlcnJ1cHQucHVzaChhKSx0Ll8uZW5kLnB1c2gobCkpLGMub249dH0pfSl9dmFyIFd5PTA7ZnVuY3Rpb24gd2Uobix0LGUsaSl7dGhpcy5fZ3JvdXBzPW4sdGhpcy5fcGFyZW50cz10LHRoaXMuX25hbWU9ZSx0aGlzLl9pZD1pfWZ1bmN0aW9uIGxjKG4pe3JldHVybiBNbigpLnRyYW5zaXRpb24obil9ZnVuY3Rpb24gZWEoKXtyZXR1cm4rK1d5fXZhciBucj1Nbi5wcm90b3R5cGU7d2UucHJvdG90eXBlPWxjLnByb3RvdHlwZT17Y29uc3RydWN0b3I6d2Usc2VsZWN0OkpkLHNlbGVjdEFsbDokZCxmaWx0ZXI6cWQsbWVyZ2U6WGQsc2VsZWN0aW9uOktkLHRyYW5zaXRpb246aXAsY2FsbDpuci5jYWxsLG5vZGVzOm5yLm5vZGVzLG5vZGU6bnIubm9kZSxzaXplOm5yLnNpemUsZW1wdHk6bnIuZW1wdHksZWFjaDpuci5lYWNoLG9uOllkLGF0dHI6a2QsYXR0clR3ZWVuOkhkLHN0eWxlOmpkLHN0eWxlVHdlZW46dHAsdGV4dDplcCx0ZXh0VHdlZW46bnAscmVtb3ZlOlpkLHR3ZWVuOk9kLGRlbGF5OlZkLGR1cmF0aW9uOkdkLGVhc2U6V2QsZW5kOnJwfTtmdW5jdGlvbiBuYShuKXtyZXR1cm4oKG4qPTIpPD0xP24qbipuOihuLT0yKSpuKm4rMikvMn12YXIgY2M9e3RpbWU6bnVsbCxkZWxheTowLGR1cmF0aW9uOjI1MCxlYXNlOm5hfTtmdW5jdGlvbiBxeShuLHQpe2Zvcih2YXIgZTshKGU9bi5fX3RyYW5zaXRpb24pfHwhKGU9ZVt0XSk7KWlmKCEobj1uLnBhcmVudE5vZGUpKXJldHVybiBjYy50aW1lPXRyKCksY2M7cmV0dXJuIGV9ZnVuY3Rpb24gc3Aobil7dmFyIHQsZTtuIGluc3RhbmNlb2Ygd2U/KHQ9bi5faWQsbj1uLl9uYW1lKToodD1lYSgpLChlPWNjKS50aW1lPXRyKCksbj1uPT1udWxsP251bGw6bisiIik7Zm9yKHZhciBpPXRoaXMuX2dyb3VwcyxyPWkubGVuZ3RoLHM9MDtzPHI7KytzKWZvcih2YXIgbz1pW3NdLGE9by5sZW5ndGgsbCxjPTA7YzxhOysrYykobD1vW2NdKSYma24obCxuLHQsYyxvLGV8fHF5KGwsdCkpO3JldHVybiBuZXcgd2UoaSx0aGlzLl9wYXJlbnRzLG4sdCl9TW4ucHJvdG90eXBlLmludGVycnVwdD1CZDtNbi5wcm90b3R5cGUudHJhbnNpdGlvbj1zcDtmdW5jdGlvbiBvcChuKXtyZXR1cm5bK25bMF0sK25bMV1dfWZ1bmN0aW9uIFh5KG4pe3JldHVybltvcChuWzBdKSxvcChuWzFdKV19dmFyIEdQPXtuYW1lOiJ4IixoYW5kbGVzOlsidyIsImUiXS5tYXAodWMpLGlucHV0OmZ1bmN0aW9uKG4sdCl7cmV0dXJuIG49PW51bGw/bnVsbDpbWytuWzBdLHRbMF1bMV1dLFsrblsxXSx0WzFdWzFdXV19LG91dHB1dDpmdW5jdGlvbihuKXtyZXR1cm4gbiYmW25bMF1bMF0sblsxXVswXV19fSxXUD17bmFtZToieSIsaGFuZGxlczpbIm4iLCJzIl0ubWFwKHVjKSxpbnB1dDpmdW5jdGlvbihuLHQpe3JldHVybiBuPT1udWxsP251bGw6W1t0WzBdWzBdLCtuWzBdXSxbdFsxXVswXSwrblsxXV1dfSxvdXRwdXQ6ZnVuY3Rpb24obil7cmV0dXJuIG4mJltuWzBdWzFdLG5bMV1bMV1dfX0scVA9e25hbWU6Inh5IixoYW5kbGVzOlsibiIsInciLCJlIiwicyIsIm53IiwibmUiLCJzdyIsInNlIl0ubWFwKHVjKSxpbnB1dDpmdW5jdGlvbihuKXtyZXR1cm4gbj09bnVsbD9udWxsOlh5KG4pfSxvdXRwdXQ6ZnVuY3Rpb24obil7cmV0dXJuIG59fTtmdW5jdGlvbiB1YyhuKXtyZXR1cm57dHlwZTpufX12YXIgYXA9TWF0aC5QSSxZeT1hcC8yLFp5PWFwKjI7dmFyIEp5PUFycmF5LnByb3RvdHlwZS5zbGljZTt2YXIgQmU9IiQiO2Z1bmN0aW9uIGlhKCl7fWlhLnByb3RvdHlwZT1scC5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOmlhLGhhczpmdW5jdGlvbihuKXtyZXR1cm4gQmUrbiBpbiB0aGlzfSxnZXQ6ZnVuY3Rpb24obil7cmV0dXJuIHRoaXNbQmUrbl19LHNldDpmdW5jdGlvbihuLHQpe3JldHVybiB0aGlzW0JlK25dPXQsdGhpc30scmVtb3ZlOmZ1bmN0aW9uKG4pe3ZhciB0PUJlK247cmV0dXJuIHQgaW4gdGhpcyYmZGVsZXRlIHRoaXNbdF19LGNsZWFyOmZ1bmN0aW9uKCl7Zm9yKHZhciBuIGluIHRoaXMpblswXT09PUJlJiZkZWxldGUgdGhpc1tuXX0sa2V5czpmdW5jdGlvbigpe3ZhciBuPVtdO2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1CZSYmbi5wdXNoKHQuc2xpY2UoMSkpO3JldHVybiBufSx2YWx1ZXM6ZnVuY3Rpb24oKXt2YXIgbj1bXTtmb3IodmFyIHQgaW4gdGhpcyl0WzBdPT09QmUmJm4ucHVzaCh0aGlzW3RdKTtyZXR1cm4gbn0sZW50cmllczpmdW5jdGlvbigpe3ZhciBuPVtdO2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1CZSYmbi5wdXNoKHtrZXk6dC5zbGljZSgxKSx2YWx1ZTp0aGlzW3RdfSk7cmV0dXJuIG59LHNpemU6ZnVuY3Rpb24oKXt2YXIgbj0wO2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1CZSYmKytuO3JldHVybiBufSxlbXB0eTpmdW5jdGlvbigpe2Zvcih2YXIgbiBpbiB0aGlzKWlmKG5bMF09PT1CZSlyZXR1cm4hMTtyZXR1cm4hMH0sZWFjaDpmdW5jdGlvbihuKXtmb3IodmFyIHQgaW4gdGhpcyl0WzBdPT09QmUmJm4odGhpc1t0XSx0LnNsaWNlKDEpLHRoaXMpfX07ZnVuY3Rpb24gbHAobix0KXt2YXIgZT1uZXcgaWE7aWYobiBpbnN0YW5jZW9mIGlhKW4uZWFjaChmdW5jdGlvbihhLGwpe2Uuc2V0KGwsYSl9KTtlbHNlIGlmKEFycmF5LmlzQXJyYXkobikpe3ZhciBpPS0xLHI9bi5sZW5ndGgscztpZih0PT1udWxsKWZvcig7KytpPHI7KWUuc2V0KGksbltpXSk7ZWxzZSBmb3IoOysraTxyOyllLnNldCh0KHM9bltpXSxpLG4pLHMpfWVsc2UgaWYobilmb3IodmFyIG8gaW4gbillLnNldChvLG5bb10pO3JldHVybiBlfXZhciB5aT1scDtmdW5jdGlvbiByYSgpe312YXIgdmk9eWkucHJvdG90eXBlO3JhLnByb3RvdHlwZT0keS5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOnJhLGhhczp2aS5oYXMsYWRkOmZ1bmN0aW9uKG4pe3JldHVybiBuKz0iIix0aGlzW0JlK25dPW4sdGhpc30scmVtb3ZlOnZpLnJlbW92ZSxjbGVhcjp2aS5jbGVhcix2YWx1ZXM6dmkua2V5cyxzaXplOnZpLnNpemUsZW1wdHk6dmkuZW1wdHksZWFjaDp2aS5lYWNofTtmdW5jdGlvbiAkeShuLHQpe3ZhciBlPW5ldyByYTtpZihuIGluc3RhbmNlb2YgcmEpbi5lYWNoKGZ1bmN0aW9uKHMpe2UuYWRkKHMpfSk7ZWxzZSBpZihuKXt2YXIgaT0tMSxyPW4ubGVuZ3RoO2lmKHQ9PW51bGwpZm9yKDsrK2k8cjspZS5hZGQobltpXSk7ZWxzZSBmb3IoOysraTxyOyllLmFkZCh0KG5baV0saSxuKSl9cmV0dXJuIGV9dmFyIEt5PUFycmF5LnByb3RvdHlwZSxjcD1LeS5zbGljZTt2YXIgVEk9TWF0aC5QSSooMy1NYXRoLnNxcnQoNSkpO2Z1bmN0aW9uIGhwKG4pe3JldHVybiBNYXRoLmFicyhuPU1hdGgucm91bmQobikpPj0xZTIxP24udG9Mb2NhbGVTdHJpbmcoImVuIikucmVwbGFjZSgvLC9nLCIiKTpuLnRvU3RyaW5nKDEwKX1mdW5jdGlvbiBfaShuLHQpe2lmKChlPShuPXQ/bi50b0V4cG9uZW50aWFsKHQtMSk6bi50b0V4cG9uZW50aWFsKCkpLmluZGV4T2YoImUiKSk8MClyZXR1cm4gbnVsbDt2YXIgZSxpPW4uc2xpY2UoMCxlKTtyZXR1cm5baS5sZW5ndGg+MT9pWzBdK2kuc2xpY2UoMik6aSwrbi5zbGljZShlKzEpXX1mdW5jdGlvbiB1bihuKXtyZXR1cm4gbj1faShNYXRoLmFicyhuKSksbj9uWzFdOk5hTn1mdW5jdGlvbiBmcChuLHQpe3JldHVybiBmdW5jdGlvbihlLGkpe2Zvcih2YXIgcj1lLmxlbmd0aCxzPVtdLG89MCxhPW5bMF0sbD0wO3I+MCYmYT4wJiYobCthKzE+aSYmKGE9TWF0aC5tYXgoMSxpLWwpKSxzLnB1c2goZS5zdWJzdHJpbmcoci09YSxyK2EpKSwhKChsKz1hKzEpPmkpKTspYT1uW289KG8rMSklbi5sZW5ndGhdO3JldHVybiBzLnJldmVyc2UoKS5qb2luKHQpfX1mdW5jdGlvbiBkcChuKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuIHQucmVwbGFjZSgvWzAtOV0vZyxmdW5jdGlvbihlKXtyZXR1cm4gblsrZV19KX19dmFyIHR2PS9eKD86KC4pPyhbPD49Xl0pKT8oWytcLSggXSk/KFskI10pPygwKT8oXGQrKT8oLCk/KFwuXGQrKT8ofik/KFthLXolXSk/JC9pO2Z1bmN0aW9uIEhuKG4pe2lmKCEodD10di5leGVjKG4pKSl0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgZm9ybWF0OiAiK24pO3ZhciB0O3JldHVybiBuZXcgc2Eoe2ZpbGw6dFsxXSxhbGlnbjp0WzJdLHNpZ246dFszXSxzeW1ib2w6dFs0XSx6ZXJvOnRbNV0sd2lkdGg6dFs2XSxjb21tYTp0WzddLHByZWNpc2lvbjp0WzhdJiZ0WzhdLnNsaWNlKDEpLHRyaW06dFs5XSx0eXBlOnRbMTBdfSl9SG4ucHJvdG90eXBlPXNhLnByb3RvdHlwZTtmdW5jdGlvbiBzYShuKXt0aGlzLmZpbGw9bi5maWxsPT09dm9pZCAwPyIgIjpuLmZpbGwrIiIsdGhpcy5hbGlnbj1uLmFsaWduPT09dm9pZCAwPyI+IjpuLmFsaWduKyIiLHRoaXMuc2lnbj1uLnNpZ249PT12b2lkIDA/Ii0iOm4uc2lnbisiIix0aGlzLnN5bWJvbD1uLnN5bWJvbD09PXZvaWQgMD8iIjpuLnN5bWJvbCsiIix0aGlzLnplcm89ISFuLnplcm8sdGhpcy53aWR0aD1uLndpZHRoPT09dm9pZCAwP3ZvaWQgMDorbi53aWR0aCx0aGlzLmNvbW1hPSEhbi5jb21tYSx0aGlzLnByZWNpc2lvbj1uLnByZWNpc2lvbj09PXZvaWQgMD92b2lkIDA6K24ucHJlY2lzaW9uLHRoaXMudHJpbT0hIW4udHJpbSx0aGlzLnR5cGU9bi50eXBlPT09dm9pZCAwPyIiOm4udHlwZSsiIn1zYS5wcm90b3R5cGUudG9TdHJpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5maWxsK3RoaXMuYWxpZ24rdGhpcy5zaWduK3RoaXMuc3ltYm9sKyh0aGlzLnplcm8/IjAiOiIiKSsodGhpcy53aWR0aD09PXZvaWQgMD8iIjpNYXRoLm1heCgxLHRoaXMud2lkdGh8MCkpKyh0aGlzLmNvbW1hPyIsIjoiIikrKHRoaXMucHJlY2lzaW9uPT09dm9pZCAwPyIiOiIuIitNYXRoLm1heCgwLHRoaXMucHJlY2lzaW9ufDApKSsodGhpcy50cmltPyJ+IjoiIikrdGhpcy50eXBlfTtmdW5jdGlvbiBwcChuKXt0OmZvcih2YXIgdD1uLmxlbmd0aCxlPTEsaT0tMSxyO2U8dDsrK2Upc3dpdGNoKG5bZV0pe2Nhc2UiLiI6aT1yPWU7YnJlYWs7Y2FzZSIwIjppPT09MCYmKGk9ZSkscj1lO2JyZWFrO2RlZmF1bHQ6aWYoIStuW2VdKWJyZWFrIHQ7aT4wJiYoaT0wKTticmVha31yZXR1cm4gaT4wP24uc2xpY2UoMCxpKStuLnNsaWNlKHIrMSk6bn12YXIgaGM7ZnVuY3Rpb24gbXAobix0KXt2YXIgZT1faShuLHQpO2lmKCFlKXJldHVybiBuKyIiO3ZhciBpPWVbMF0scj1lWzFdLHM9ci0oaGM9TWF0aC5tYXgoLTgsTWF0aC5taW4oOCxNYXRoLmZsb29yKHIvMykpKSozKSsxLG89aS5sZW5ndGg7cmV0dXJuIHM9PT1vP2k6cz5vP2krbmV3IEFycmF5KHMtbysxKS5qb2luKCIwIik6cz4wP2kuc2xpY2UoMCxzKSsiLiIraS5zbGljZShzKToiMC4iK25ldyBBcnJheSgxLXMpLmpvaW4oIjAiKStfaShuLE1hdGgubWF4KDAsdCtzLTEpKVswXX1mdW5jdGlvbiBmYyhuLHQpe3ZhciBlPV9pKG4sdCk7aWYoIWUpcmV0dXJuIG4rIiI7dmFyIGk9ZVswXSxyPWVbMV07cmV0dXJuIHI8MD8iMC4iK25ldyBBcnJheSgtcikuam9pbigiMCIpK2k6aS5sZW5ndGg+cisxP2kuc2xpY2UoMCxyKzEpKyIuIitpLnNsaWNlKHIrMSk6aStuZXcgQXJyYXkoci1pLmxlbmd0aCsyKS5qb2luKCIwIil9dmFyIGRjPXsiJSI6ZnVuY3Rpb24obix0KXtyZXR1cm4obioxMDApLnRvRml4ZWQodCl9LGI6ZnVuY3Rpb24obil7cmV0dXJuIE1hdGgucm91bmQobikudG9TdHJpbmcoMil9LGM6ZnVuY3Rpb24obil7cmV0dXJuIG4rIiJ9LGQ6aHAsZTpmdW5jdGlvbihuLHQpe3JldHVybiBuLnRvRXhwb25lbnRpYWwodCl9LGY6ZnVuY3Rpb24obix0KXtyZXR1cm4gbi50b0ZpeGVkKHQpfSxnOmZ1bmN0aW9uKG4sdCl7cmV0dXJuIG4udG9QcmVjaXNpb24odCl9LG86ZnVuY3Rpb24obil7cmV0dXJuIE1hdGgucm91bmQobikudG9TdHJpbmcoOCl9LHA6ZnVuY3Rpb24obix0KXtyZXR1cm4gZmMobioxMDAsdCl9LHI6ZmMsczptcCxYOmZ1bmN0aW9uKG4pe3JldHVybiBNYXRoLnJvdW5kKG4pLnRvU3RyaW5nKDE2KS50b1VwcGVyQ2FzZSgpfSx4OmZ1bmN0aW9uKG4pe3JldHVybiBNYXRoLnJvdW5kKG4pLnRvU3RyaW5nKDE2KX19O2Z1bmN0aW9uIHBjKG4pe3JldHVybiBufXZhciBncD1BcnJheS5wcm90b3R5cGUubWFwLHhwPVsieSIsInoiLCJhIiwiZiIsInAiLCJuIiwiXHhCNSIsIm0iLCIiLCJrIiwiTSIsIkciLCJUIiwiUCIsIkUiLCJaIiwiWSJdO2Z1bmN0aW9uIHlwKG4pe3ZhciB0PW4uZ3JvdXBpbmc9PT12b2lkIDB8fG4udGhvdXNhbmRzPT09dm9pZCAwP3BjOmZwKGdwLmNhbGwobi5ncm91cGluZyxOdW1iZXIpLG4udGhvdXNhbmRzKyIiKSxlPW4uY3VycmVuY3k9PT12b2lkIDA/IiI6bi5jdXJyZW5jeVswXSsiIixpPW4uY3VycmVuY3k9PT12b2lkIDA/IiI6bi5jdXJyZW5jeVsxXSsiIixyPW4uZGVjaW1hbD09PXZvaWQgMD8iLiI6bi5kZWNpbWFsKyIiLHM9bi5udW1lcmFscz09PXZvaWQgMD9wYzpkcChncC5jYWxsKG4ubnVtZXJhbHMsU3RyaW5nKSksbz1uLnBlcmNlbnQ9PT12b2lkIDA/IiUiOm4ucGVyY2VudCsiIixhPW4ubWludXM9PT12b2lkIDA/Ii0iOm4ubWludXMrIiIsbD1uLm5hbj09PXZvaWQgMD8iTmFOIjpuLm5hbisiIjtmdW5jdGlvbiBjKGgpe2g9SG4oaCk7dmFyIGY9aC5maWxsLGQ9aC5hbGlnbixnPWguc2lnbix4PWguc3ltYm9sLHY9aC56ZXJvLG09aC53aWR0aCxwPWguY29tbWEsYj1oLnByZWNpc2lvbixfPWgudHJpbSxTPWgudHlwZTtTPT09Im4iPyhwPSEwLFM9ImciKTpkY1tTXXx8KGI9PT12b2lkIDAmJihiPTEyKSxfPSEwLFM9ImciKSwodnx8Zj09PSIwIiYmZD09PSI9IikmJih2PSEwLGY9IjAiLGQ9Ij0iKTt2YXIgTD14PT09IiQiP2U6eD09PSIjIiYmL1tib3hYXS8udGVzdChTKT8iMCIrUy50b0xvd2VyQ2FzZSgpOiIiLEE9eD09PSIkIj9pOi9bJXBdLy50ZXN0KFMpP286IiIsSD1kY1tTXSx0dD0vW2RlZmdwcnMlXS8udGVzdChTKTtiPWI9PT12b2lkIDA/NjovW2dwcnNdLy50ZXN0KFMpP01hdGgubWF4KDEsTWF0aC5taW4oMjEsYikpOk1hdGgubWF4KDAsTWF0aC5taW4oMjAsYikpO2Z1bmN0aW9uIFgoeSl7dmFyIFI9TCxEPUEsRix6LE47aWYoUz09PSJjIilEPUgoeSkrRCx5PSIiO2Vsc2V7eT0reTt2YXIgVj15PDB8fDEveTwwO2lmKHk9aXNOYU4oeSk/bDpIKE1hdGguYWJzKHkpLGIpLF8mJih5PXBwKHkpKSxWJiYreT09MCYmZyE9PSIrIiYmKFY9ITEpLFI9KFY/Zz09PSIoIj9nOmE6Zz09PSItInx8Zz09PSIoIj8iIjpnKStSLEQ9KFM9PT0icyI/eHBbOCtoYy8zXToiIikrRCsoViYmZz09PSIoIj8iKSI6IiIpLHR0KXtmb3IoRj0tMSx6PXkubGVuZ3RoOysrRjx6OylpZihOPXkuY2hhckNvZGVBdChGKSw0OD5OfHxOPjU3KXtEPShOPT09NDY/cit5LnNsaWNlKEYrMSk6eS5zbGljZShGKSkrRCx5PXkuc2xpY2UoMCxGKTticmVha319fXAmJiF2JiYoeT10KHksMS8wKSk7dmFyIFE9Ui5sZW5ndGgreS5sZW5ndGgrRC5sZW5ndGgsYXQ9UTxtP25ldyBBcnJheShtLVErMSkuam9pbihmKToiIjtzd2l0Y2gocCYmdiYmKHk9dChhdCt5LGF0Lmxlbmd0aD9tLUQubGVuZ3RoOjEvMCksYXQ9IiIpLGQpe2Nhc2UiPCI6eT1SK3krRCthdDticmVhaztjYXNlIj0iOnk9UithdCt5K0Q7YnJlYWs7Y2FzZSJeIjp5PWF0LnNsaWNlKDAsUT1hdC5sZW5ndGg+PjEpK1IreStEK2F0LnNsaWNlKFEpO2JyZWFrO2RlZmF1bHQ6eT1hdCtSK3krRDticmVha31yZXR1cm4gcyh5KX1yZXR1cm4gWC50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiBoKyIifSxYfWZ1bmN0aW9uIHUoaCxmKXt2YXIgZD1jKChoPUhuKGgpLGgudHlwZT0iZiIsaCkpLGc9TWF0aC5tYXgoLTgsTWF0aC5taW4oOCxNYXRoLmZsb29yKHVuKGYpLzMpKSkqMyx4PU1hdGgucG93KDEwLC1nKSx2PXhwWzgrZy8zXTtyZXR1cm4gZnVuY3Rpb24obSl7cmV0dXJuIGQoeCptKSt2fX1yZXR1cm57Zm9ybWF0OmMsZm9ybWF0UHJlZml4OnV9fXZhciBvYSxPZSxhYTttYyh7ZGVjaW1hbDoiLiIsdGhvdXNhbmRzOiIsIixncm91cGluZzpbM10sY3VycmVuY3k6WyIkIiwiIl0sbWludXM6Ii0ifSk7ZnVuY3Rpb24gbWMobil7cmV0dXJuIG9hPXlwKG4pLE9lPW9hLmZvcm1hdCxhYT1vYS5mb3JtYXRQcmVmaXgsb2F9ZnVuY3Rpb24gZ2Mobil7cmV0dXJuIE1hdGgubWF4KDAsLXVuKE1hdGguYWJzKG4pKSl9ZnVuY3Rpb24geGMobix0KXtyZXR1cm4gTWF0aC5tYXgoMCxNYXRoLm1heCgtOCxNYXRoLm1pbig4LE1hdGguZmxvb3IodW4odCkvMykpKSozLXVuKE1hdGguYWJzKG4pKSl9ZnVuY3Rpb24geWMobix0KXtyZXR1cm4gbj1NYXRoLmFicyhuKSx0PU1hdGguYWJzKHQpLW4sTWF0aC5tYXgoMCx1bih0KS11bihuKSkrMX1mdW5jdGlvbiBXZSgpe3JldHVybiBNYXRoLnJhbmRvbSgpfXZhciBldj1mdW5jdGlvbiBuKHQpe2Z1bmN0aW9uIGUoaSxyKXtyZXR1cm4gaT1pPT1udWxsPzA6K2kscj1yPT1udWxsPzE6K3IsYXJndW1lbnRzLmxlbmd0aD09PTE/KHI9aSxpPTApOnItPWksZnVuY3Rpb24oKXtyZXR1cm4gdCgpKnIraX19cmV0dXJuIGUuc291cmNlPW4sZX0oV2UpO3ZhciB2Yz1mdW5jdGlvbiBuKHQpe2Z1bmN0aW9uIGUoaSxyKXt2YXIgcyxvO3JldHVybiBpPWk9PW51bGw/MDoraSxyPXI9PW51bGw/MTorcixmdW5jdGlvbigpe3ZhciBhO2lmKHMhPW51bGwpYT1zLHM9bnVsbDtlbHNlIGRvIHM9dCgpKjItMSxhPXQoKSoyLTEsbz1zKnMrYSphO3doaWxlKCFvfHxvPjEpO3JldHVybiBpK3IqYSpNYXRoLnNxcnQoLTIqTWF0aC5sb2cobykvbyl9fXJldHVybiBlLnNvdXJjZT1uLGV9KFdlKTt2YXIgbnY9ZnVuY3Rpb24gbih0KXtmdW5jdGlvbiBlKCl7dmFyIGk9dmMuc291cmNlKHQpLmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gTWF0aC5leHAoaSgpKX19cmV0dXJuIGUuc291cmNlPW4sZX0oV2UpO3ZhciBfYz1mdW5jdGlvbiBuKHQpe2Z1bmN0aW9uIGUoaSl7cmV0dXJuIGZ1bmN0aW9uKCl7Zm9yKHZhciByPTAscz0wO3M8aTsrK3Mpcis9dCgpO3JldHVybiByfX1yZXR1cm4gZS5zb3VyY2U9bixlfShXZSk7dmFyIGl2PWZ1bmN0aW9uIG4odCl7ZnVuY3Rpb24gZShpKXt2YXIgcj1fYy5zb3VyY2UodCkoaSk7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIHIoKS9pfX1yZXR1cm4gZS5zb3VyY2U9bixlfShXZSk7dmFyIHJ2PWZ1bmN0aW9uIG4odCl7ZnVuY3Rpb24gZShpKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4tTWF0aC5sb2coMS10KCkpL2l9fXJldHVybiBlLnNvdXJjZT1uLGV9KFdlKTtmdW5jdGlvbiBQZShuLHQpe3N3aXRjaChhcmd1bWVudHMubGVuZ3RoKXtjYXNlIDA6YnJlYWs7Y2FzZSAxOnRoaXMucmFuZ2Uobik7YnJlYWs7ZGVmYXVsdDp0aGlzLnJhbmdlKHQpLmRvbWFpbihuKTticmVha31yZXR1cm4gdGhpc312YXIgdnA9QXJyYXkucHJvdG90eXBlLGJzPXZwLm1hcCx3aT12cC5zbGljZTtmdW5jdGlvbiBfcChuKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gbn19ZnVuY3Rpb24gd2Mobil7cmV0dXJuK259dmFyIHdwPVswLDFdO2Z1bmN0aW9uIERlKG4pe3JldHVybiBufWZ1bmN0aW9uIE1jKG4sdCl7cmV0dXJuKHQtPW49K24pP2Z1bmN0aW9uKGUpe3JldHVybihlLW4pL3R9Ol9wKGlzTmFOKHQpP05hTjouNSl9ZnVuY3Rpb24gTXAobil7dmFyIHQ9blswXSxlPW5bbi5sZW5ndGgtMV0saTtyZXR1cm4gdD5lJiYoaT10LHQ9ZSxlPWkpLGZ1bmN0aW9uKHIpe3JldHVybiBNYXRoLm1heCh0LE1hdGgubWluKGUscikpfX1mdW5jdGlvbiBvdihuLHQsZSl7dmFyIGk9blswXSxyPW5bMV0scz10WzBdLG89dFsxXTtyZXR1cm4gcjxpPyhpPU1jKHIsaSkscz1lKG8scykpOihpPU1jKGkscikscz1lKHMsbykpLGZ1bmN0aW9uKGEpe3JldHVybiBzKGkoYSkpfX1mdW5jdGlvbiBhdihuLHQsZSl7dmFyIGk9TWF0aC5taW4obi5sZW5ndGgsdC5sZW5ndGgpLTEscj1uZXcgQXJyYXkoaSkscz1uZXcgQXJyYXkoaSksbz0tMTtmb3IobltpXTxuWzBdJiYobj1uLnNsaWNlKCkucmV2ZXJzZSgpLHQ9dC5zbGljZSgpLnJldmVyc2UoKSk7KytvPGk7KXJbb109TWMobltvXSxuW28rMV0pLHNbb109ZSh0W29dLHRbbysxXSk7cmV0dXJuIGZ1bmN0aW9uKGEpe3ZhciBsPUJuKG4sYSwxLGkpLTE7cmV0dXJuIHNbbF0ocltsXShhKSl9fWZ1bmN0aW9uIFZuKG4sdCl7cmV0dXJuIHQuZG9tYWluKG4uZG9tYWluKCkpLnJhbmdlKG4ucmFuZ2UoKSkuaW50ZXJwb2xhdGUobi5pbnRlcnBvbGF0ZSgpKS5jbGFtcChuLmNsYW1wKCkpLnVua25vd24obi51bmtub3duKCkpfWZ1bmN0aW9uIFNzKCl7dmFyIG49d3AsdD13cCxlPWdpLGkscixzLG89RGUsYSxsLGM7ZnVuY3Rpb24gdSgpe3JldHVybiBhPU1hdGgubWluKG4ubGVuZ3RoLHQubGVuZ3RoKT4yP2F2Om92LGw9Yz1udWxsLGh9ZnVuY3Rpb24gaChmKXtyZXR1cm4gaXNOYU4oZj0rZik/czoobHx8KGw9YShuLm1hcChpKSx0LGUpKSkoaShvKGYpKSl9cmV0dXJuIGguaW52ZXJ0PWZ1bmN0aW9uKGYpe3JldHVybiBvKHIoKGN8fChjPWEodCxuLm1hcChpKSx5ZSkpKShmKSkpfSxoLmRvbWFpbj1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj1icy5jYWxsKGYsd2MpLG89PT1EZXx8KG89TXAobikpLHUoKSk6bi5zbGljZSgpfSxoLnJhbmdlPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXdpLmNhbGwoZiksdSgpKTp0LnNsaWNlKCl9LGgucmFuZ2VSb3VuZD1mdW5jdGlvbihmKXtyZXR1cm4gdD13aS5jYWxsKGYpLGU9dGMsdSgpfSxoLmNsYW1wPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPWY/TXAobik6RGUsaCk6byE9PURlfSxoLmludGVycG9sYXRlPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPWYsdSgpKTplfSxoLnVua25vd249ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHM9ZixoKTpzfSxmdW5jdGlvbihmLGQpe3JldHVybiBpPWYscj1kLHUoKX19ZnVuY3Rpb24gRXMobix0KXtyZXR1cm4gU3MoKShuLHQpfWZ1bmN0aW9uIGJjKG4sdCxlLGkpe3ZhciByPV9uKG4sdCxlKSxzO3N3aXRjaChpPUhuKGk9PW51bGw/IixmIjppKSxpLnR5cGUpe2Nhc2UicyI6e3ZhciBvPU1hdGgubWF4KE1hdGguYWJzKG4pLE1hdGguYWJzKHQpKTtyZXR1cm4gaS5wcmVjaXNpb249PW51bGwmJiFpc05hTihzPXhjKHIsbykpJiYoaS5wcmVjaXNpb249cyksYWEoaSxvKX1jYXNlIiI6Y2FzZSJlIjpjYXNlImciOmNhc2UicCI6Y2FzZSJyIjp7aS5wcmVjaXNpb249PW51bGwmJiFpc05hTihzPXljKHIsTWF0aC5tYXgoTWF0aC5hYnMobiksTWF0aC5hYnModCkpKSkmJihpLnByZWNpc2lvbj1zLShpLnR5cGU9PT0iZSIpKTticmVha31jYXNlImYiOmNhc2UiJSI6e2kucHJlY2lzaW9uPT1udWxsJiYhaXNOYU4ocz1nYyhyKSkmJihpLnByZWNpc2lvbj1zLShpLnR5cGU9PT0iJSIpKjIpO2JyZWFrfX1yZXR1cm4gT2UoaSl9ZnVuY3Rpb24gTWkobil7dmFyIHQ9bi5kb21haW47cmV0dXJuIG4udGlja3M9ZnVuY3Rpb24oZSl7dmFyIGk9dCgpO3JldHVybiBycyhpWzBdLGlbaS5sZW5ndGgtMV0sZT09bnVsbD8xMDplKX0sbi50aWNrRm9ybWF0PWZ1bmN0aW9uKGUsaSl7dmFyIHI9dCgpO3JldHVybiBiYyhyWzBdLHJbci5sZW5ndGgtMV0sZT09bnVsbD8xMDplLGkpfSxuLm5pY2U9ZnVuY3Rpb24oZSl7ZT09bnVsbCYmKGU9MTApO3ZhciBpPXQoKSxyPTAscz1pLmxlbmd0aC0xLG89aVtyXSxhPWlbc10sbDtyZXR1cm4gYTxvJiYobD1vLG89YSxhPWwsbD1yLHI9cyxzPWwpLGw9SmkobyxhLGUpLGw+MD8obz1NYXRoLmZsb29yKG8vbCkqbCxhPU1hdGguY2VpbChhL2wpKmwsbD1KaShvLGEsZSkpOmw8MCYmKG89TWF0aC5jZWlsKG8qbCkvbCxhPU1hdGguZmxvb3IoYSpsKS9sLGw9SmkobyxhLGUpKSxsPjA/KGlbcl09TWF0aC5mbG9vcihvL2wpKmwsaVtzXT1NYXRoLmNlaWwoYS9sKSpsLHQoaSkpOmw8MCYmKGlbcl09TWF0aC5jZWlsKG8qbCkvbCxpW3NdPU1hdGguZmxvb3IoYSpsKS9sLHQoaSkpLG59LG59ZnVuY3Rpb24gaXIoKXt2YXIgbj1FcyhEZSxEZSk7cmV0dXJuIG4uY29weT1mdW5jdGlvbigpe3JldHVybiBWbihuLGlyKCkpfSxQZS5hcHBseShuLGFyZ3VtZW50cyksTWkobil9ZnVuY3Rpb24gbGEobix0KXtuPW4uc2xpY2UoKTt2YXIgZT0wLGk9bi5sZW5ndGgtMSxyPW5bZV0scz1uW2ldLG87cmV0dXJuIHM8ciYmKG89ZSxlPWksaT1vLG89cixyPXMscz1vKSxuW2VdPXQuZmxvb3IociksbltpXT10LmNlaWwocyksbn1mdW5jdGlvbiBicChuKXtyZXR1cm4gTWF0aC5sb2cobil9ZnVuY3Rpb24gU3Aobil7cmV0dXJuIE1hdGguZXhwKG4pfWZ1bmN0aW9uIGx2KG4pe3JldHVybi1NYXRoLmxvZygtbil9ZnVuY3Rpb24gY3Yobil7cmV0dXJuLU1hdGguZXhwKC1uKX1mdW5jdGlvbiB1dihuKXtyZXR1cm4gaXNGaW5pdGUobik/KygiMWUiK24pOm48MD8wOm59ZnVuY3Rpb24gaHYobil7cmV0dXJuIG49PT0xMD91djpuPT09TWF0aC5FP01hdGguZXhwOmZ1bmN0aW9uKHQpe3JldHVybiBNYXRoLnBvdyhuLHQpfX1mdW5jdGlvbiBmdihuKXtyZXR1cm4gbj09PU1hdGguRT9NYXRoLmxvZzpuPT09MTAmJk1hdGgubG9nMTB8fG49PT0yJiZNYXRoLmxvZzJ8fChuPU1hdGgubG9nKG4pLGZ1bmN0aW9uKHQpe3JldHVybiBNYXRoLmxvZyh0KS9ufSl9ZnVuY3Rpb24gRXAobil7cmV0dXJuIGZ1bmN0aW9uKHQpe3JldHVybi1uKC10KX19ZnVuY3Rpb24gU2Mobil7dmFyIHQ9bihicCxTcCksZT10LmRvbWFpbixpPTEwLHIscztmdW5jdGlvbiBvKCl7cmV0dXJuIHI9ZnYoaSkscz1odihpKSxlKClbMF08MD8ocj1FcChyKSxzPUVwKHMpLG4obHYsY3YpKTpuKGJwLFNwKSx0fXJldHVybiB0LmJhc2U9ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9K2EsbygpKTppfSx0LmRvbWFpbj1mdW5jdGlvbihhKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZShhKSxvKCkpOmUoKX0sdC50aWNrcz1mdW5jdGlvbihhKXt2YXIgbD1lKCksYz1sWzBdLHU9bFtsLmxlbmd0aC0xXSxoOyhoPXU8YykmJihmPWMsYz11LHU9Zik7dmFyIGY9cihjKSxkPXIodSksZyx4LHYsbT1hPT1udWxsPzEwOithLHA9W107aWYoIShpJTEpJiZkLWY8bSl7aWYoZj1NYXRoLnJvdW5kKGYpLTEsZD1NYXRoLnJvdW5kKGQpKzEsYz4wKXtmb3IoO2Y8ZDsrK2YpZm9yKHg9MSxnPXMoZik7eDxpOysreClpZih2PWcqeCwhKHY8Yykpe2lmKHY+dSlicmVhaztwLnB1c2godil9fWVsc2UgZm9yKDtmPGQ7KytmKWZvcih4PWktMSxnPXMoZik7eD49MTstLXgpaWYodj1nKngsISh2PGMpKXtpZih2PnUpYnJlYWs7cC5wdXNoKHYpfX1lbHNlIHA9cnMoZixkLE1hdGgubWluKGQtZixtKSkubWFwKHMpO3JldHVybiBoP3AucmV2ZXJzZSgpOnB9LHQudGlja0Zvcm1hdD1mdW5jdGlvbihhLGwpe2lmKGw9PW51bGwmJihsPWk9PT0xMD8iLjBlIjoiLCIpLHR5cGVvZiBsIT0iZnVuY3Rpb24iJiYobD1PZShsKSksYT09PTEvMClyZXR1cm4gbDthPT1udWxsJiYoYT0xMCk7dmFyIGM9TWF0aC5tYXgoMSxpKmEvdC50aWNrcygpLmxlbmd0aCk7cmV0dXJuIGZ1bmN0aW9uKHUpe3ZhciBoPXUvcyhNYXRoLnJvdW5kKHIodSkpKTtyZXR1cm4gaCppPGktLjUmJihoKj1pKSxoPD1jP2wodSk6IiJ9fSx0Lm5pY2U9ZnVuY3Rpb24oKXtyZXR1cm4gZShsYShlKCkse2Zsb29yOmZ1bmN0aW9uKGEpe3JldHVybiBzKE1hdGguZmxvb3IocihhKSkpfSxjZWlsOmZ1bmN0aW9uKGEpe3JldHVybiBzKE1hdGguY2VpbChyKGEpKSl9fSkpfSx0fWZ1bmN0aW9uIFRzKCl7dmFyIG49U2MoU3MoKSkuZG9tYWluKFsxLDEwXSk7cmV0dXJuIG4uY29weT1mdW5jdGlvbigpe3JldHVybiBWbihuLFRzKCkpLmJhc2Uobi5iYXNlKCkpfSxQZS5hcHBseShuLGFyZ3VtZW50cyksbn12YXIgRWM9bmV3IERhdGUsVGM9bmV3IERhdGU7ZnVuY3Rpb24gWXQobix0LGUsaSl7ZnVuY3Rpb24gcihzKXtyZXR1cm4gbihzPWFyZ3VtZW50cy5sZW5ndGg9PT0wP25ldyBEYXRlOm5ldyBEYXRlKCtzKSksc31yZXR1cm4gci5mbG9vcj1mdW5jdGlvbihzKXtyZXR1cm4gbihzPW5ldyBEYXRlKCtzKSksc30sci5jZWlsPWZ1bmN0aW9uKHMpe3JldHVybiBuKHM9bmV3IERhdGUocy0xKSksdChzLDEpLG4ocyksc30sci5yb3VuZD1mdW5jdGlvbihzKXt2YXIgbz1yKHMpLGE9ci5jZWlsKHMpO3JldHVybiBzLW88YS1zP286YX0sci5vZmZzZXQ9ZnVuY3Rpb24ocyxvKXtyZXR1cm4gdChzPW5ldyBEYXRlKCtzKSxvPT1udWxsPzE6TWF0aC5mbG9vcihvKSksc30sci5yYW5nZT1mdW5jdGlvbihzLG8sYSl7dmFyIGw9W10sYztpZihzPXIuY2VpbChzKSxhPWE9PW51bGw/MTpNYXRoLmZsb29yKGEpLCEoczxvKXx8IShhPjApKXJldHVybiBsO2RvIGwucHVzaChjPW5ldyBEYXRlKCtzKSksdChzLGEpLG4ocyk7d2hpbGUoYzxzJiZzPG8pO3JldHVybiBsfSxyLmZpbHRlcj1mdW5jdGlvbihzKXtyZXR1cm4gWXQoZnVuY3Rpb24obyl7aWYobz49bylmb3IoO24obyksIXMobyk7KW8uc2V0VGltZShvLTEpfSxmdW5jdGlvbihvLGEpe2lmKG8+PW8paWYoYTwwKWZvcig7KythPD0wOylmb3IoO3QobywtMSksIXMobyk7KTtlbHNlIGZvcig7LS1hPj0wOylmb3IoO3QobywxKSwhcyhvKTspO30pfSxlJiYoci5jb3VudD1mdW5jdGlvbihzLG8pe3JldHVybiBFYy5zZXRUaW1lKCtzKSxUYy5zZXRUaW1lKCtvKSxuKEVjKSxuKFRjKSxNYXRoLmZsb29yKGUoRWMsVGMpKX0sci5ldmVyeT1mdW5jdGlvbihzKXtyZXR1cm4gcz1NYXRoLmZsb29yKHMpLCFpc0Zpbml0ZShzKXx8IShzPjApP251bGw6cz4xP3IuZmlsdGVyKGk/ZnVuY3Rpb24obyl7cmV0dXJuIGkobyklcz09PTB9OmZ1bmN0aW9uKG8pe3JldHVybiByLmNvdW50KDAsbyklcz09PTB9KTpyfSkscn12YXIgY2E9WXQoZnVuY3Rpb24oKXt9LGZ1bmN0aW9uKG4sdCl7bi5zZXRUaW1lKCtuK3QpfSxmdW5jdGlvbihuLHQpe3JldHVybiB0LW59KTtjYS5ldmVyeT1mdW5jdGlvbihuKXtyZXR1cm4gbj1NYXRoLmZsb29yKG4pLCFpc0Zpbml0ZShuKXx8IShuPjApP251bGw6bj4xP1l0KGZ1bmN0aW9uKHQpe3Quc2V0VGltZShNYXRoLmZsb29yKHQvbikqbil9LGZ1bmN0aW9uKHQsZSl7dC5zZXRUaW1lKCt0K2Uqbil9LGZ1bmN0aW9uKHQsZSl7cmV0dXJuKGUtdCkvbn0pOmNhfTt2YXIgdWE9Y2EsVHA9Y2EucmFuZ2U7dmFyIGJpPTFlMyxTbj02ZTQsQWM9MzZlNSxoYT04NjRlNSxmYT02MDQ4ZTU7dmFyIEFwPVl0KGZ1bmN0aW9uKG4pe24uc2V0VGltZShuLW4uZ2V0TWlsbGlzZWNvbmRzKCkpfSxmdW5jdGlvbihuLHQpe24uc2V0VGltZSgrbit0KmJpKX0sZnVuY3Rpb24obix0KXtyZXR1cm4odC1uKS9iaX0sZnVuY3Rpb24obil7cmV0dXJuIG4uZ2V0VVRDU2Vjb25kcygpfSksZGE9QXAsQ3A9QXAucmFuZ2U7dmFyIFJwPVl0KGZ1bmN0aW9uKG4pe24uc2V0VGltZShuLW4uZ2V0TWlsbGlzZWNvbmRzKCktbi5nZXRTZWNvbmRzKCkqYmkpfSxmdW5jdGlvbihuLHQpe24uc2V0VGltZSgrbit0KlNuKX0sZnVuY3Rpb24obix0KXtyZXR1cm4odC1uKS9Tbn0sZnVuY3Rpb24obil7cmV0dXJuIG4uZ2V0TWludXRlcygpfSksQ2M9UnAsZHY9UnAucmFuZ2U7dmFyIExwPVl0KGZ1bmN0aW9uKG4pe24uc2V0VGltZShuLW4uZ2V0TWlsbGlzZWNvbmRzKCktbi5nZXRTZWNvbmRzKCkqYmktbi5nZXRNaW51dGVzKCkqU24pfSxmdW5jdGlvbihuLHQpe24uc2V0VGltZSgrbit0KkFjKX0sZnVuY3Rpb24obix0KXtyZXR1cm4odC1uKS9BY30sZnVuY3Rpb24obil7cmV0dXJuIG4uZ2V0SG91cnMoKX0pLFJjPUxwLHB2PUxwLnJhbmdlO3ZhciBQcD1ZdChmdW5jdGlvbihuKXtuLnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihuLHQpe24uc2V0RGF0ZShuLmdldERhdGUoKSt0KX0sZnVuY3Rpb24obix0KXtyZXR1cm4odC1uLSh0LmdldFRpbWV6b25lT2Zmc2V0KCktbi5nZXRUaW1lem9uZU9mZnNldCgpKSpTbikvaGF9LGZ1bmN0aW9uKG4pe3JldHVybiBuLmdldERhdGUoKS0xfSkscnI9UHAsbXY9UHAucmFuZ2U7ZnVuY3Rpb24gU2kobil7cmV0dXJuIFl0KGZ1bmN0aW9uKHQpe3Quc2V0RGF0ZSh0LmdldERhdGUoKS0odC5nZXREYXkoKSs3LW4pJTcpLHQuc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKHQsZSl7dC5zZXREYXRlKHQuZ2V0RGF0ZSgpK2UqNyl9LGZ1bmN0aW9uKHQsZSl7cmV0dXJuKGUtdC0oZS5nZXRUaW1lem9uZU9mZnNldCgpLXQuZ2V0VGltZXpvbmVPZmZzZXQoKSkqU24pL2ZhfSl9dmFyIEVpPVNpKDApLHNyPVNpKDEpLERwPVNpKDIpLElwPVNpKDMpLEduPVNpKDQpLE5wPVNpKDUpLEZwPVNpKDYpLHpwPUVpLnJhbmdlLGd2PXNyLnJhbmdlLHh2PURwLnJhbmdlLHl2PUlwLnJhbmdlLHZ2PUduLnJhbmdlLF92PU5wLnJhbmdlLHd2PUZwLnJhbmdlO3ZhciBVcD1ZdChmdW5jdGlvbihuKXtuLnNldERhdGUoMSksbi5zZXRIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24obix0KXtuLnNldE1vbnRoKG4uZ2V0TW9udGgoKSt0KX0sZnVuY3Rpb24obix0KXtyZXR1cm4gdC5nZXRNb250aCgpLW4uZ2V0TW9udGgoKSsodC5nZXRGdWxsWWVhcigpLW4uZ2V0RnVsbFllYXIoKSkqMTJ9LGZ1bmN0aW9uKG4pe3JldHVybiBuLmdldE1vbnRoKCl9KSxMYz1VcCxNdj1VcC5yYW5nZTt2YXIgUGM9WXQoZnVuY3Rpb24obil7bi5zZXRNb250aCgwLDEpLG4uc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKG4sdCl7bi5zZXRGdWxsWWVhcihuLmdldEZ1bGxZZWFyKCkrdCl9LGZ1bmN0aW9uKG4sdCl7cmV0dXJuIHQuZ2V0RnVsbFllYXIoKS1uLmdldEZ1bGxZZWFyKCl9LGZ1bmN0aW9uKG4pe3JldHVybiBuLmdldEZ1bGxZZWFyKCl9KTtQYy5ldmVyeT1mdW5jdGlvbihuKXtyZXR1cm4haXNGaW5pdGUobj1NYXRoLmZsb29yKG4pKXx8IShuPjApP251bGw6WXQoZnVuY3Rpb24odCl7dC5zZXRGdWxsWWVhcihNYXRoLmZsb29yKHQuZ2V0RnVsbFllYXIoKS9uKSpuKSx0LnNldE1vbnRoKDAsMSksdC5zZXRIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24odCxlKXt0LnNldEZ1bGxZZWFyKHQuZ2V0RnVsbFllYXIoKStlKm4pfSl9O3ZhciBFbj1QYyxidj1QYy5yYW5nZTt2YXIgQnA9WXQoZnVuY3Rpb24obil7bi5zZXRVVENIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24obix0KXtuLnNldFVUQ0RhdGUobi5nZXRVVENEYXRlKCkrdCl9LGZ1bmN0aW9uKG4sdCl7cmV0dXJuKHQtbikvaGF9LGZ1bmN0aW9uKG4pe3JldHVybiBuLmdldFVUQ0RhdGUoKS0xfSkscGE9QnAsU3Y9QnAucmFuZ2U7ZnVuY3Rpb24gVGkobil7cmV0dXJuIFl0KGZ1bmN0aW9uKHQpe3Quc2V0VVRDRGF0ZSh0LmdldFVUQ0RhdGUoKS0odC5nZXRVVENEYXkoKSs3LW4pJTcpLHQuc2V0VVRDSG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKHQsZSl7dC5zZXRVVENEYXRlKHQuZ2V0VVRDRGF0ZSgpK2UqNyl9LGZ1bmN0aW9uKHQsZSl7cmV0dXJuKGUtdCkvZmF9KX12YXIgQXM9VGkoMCksb3I9VGkoMSksT3A9VGkoMiksa3A9VGkoMyksV249VGkoNCksSHA9VGkoNSksVnA9VGkoNiksR3A9QXMucmFuZ2UsRXY9b3IucmFuZ2UsVHY9T3AucmFuZ2UsQXY9a3AucmFuZ2UsQ3Y9V24ucmFuZ2UsUnY9SHAucmFuZ2UsTHY9VnAucmFuZ2U7dmFyIERjPVl0KGZ1bmN0aW9uKG4pe24uc2V0VVRDTW9udGgoMCwxKSxuLnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihuLHQpe24uc2V0VVRDRnVsbFllYXIobi5nZXRVVENGdWxsWWVhcigpK3QpfSxmdW5jdGlvbihuLHQpe3JldHVybiB0LmdldFVUQ0Z1bGxZZWFyKCktbi5nZXRVVENGdWxsWWVhcigpfSxmdW5jdGlvbihuKXtyZXR1cm4gbi5nZXRVVENGdWxsWWVhcigpfSk7RGMuZXZlcnk9ZnVuY3Rpb24obil7cmV0dXJuIWlzRmluaXRlKG49TWF0aC5mbG9vcihuKSl8fCEobj4wKT9udWxsOll0KGZ1bmN0aW9uKHQpe3Quc2V0VVRDRnVsbFllYXIoTWF0aC5mbG9vcih0LmdldFVUQ0Z1bGxZZWFyKCkvbikqbiksdC5zZXRVVENNb250aCgwLDEpLHQuc2V0VVRDSG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKHQsZSl7dC5zZXRVVENGdWxsWWVhcih0LmdldFVUQ0Z1bGxZZWFyKCkrZSpuKX0pfTt2YXIgQWk9RGMsUHY9RGMucmFuZ2U7ZnVuY3Rpb24gSWMobil7aWYoMDw9bi55JiZuLnk8MTAwKXt2YXIgdD1uZXcgRGF0ZSgtMSxuLm0sbi5kLG4uSCxuLk0sbi5TLG4uTCk7cmV0dXJuIHQuc2V0RnVsbFllYXIobi55KSx0fXJldHVybiBuZXcgRGF0ZShuLnksbi5tLG4uZCxuLkgsbi5NLG4uUyxuLkwpfWZ1bmN0aW9uIE5jKG4pe2lmKDA8PW4ueSYmbi55PDEwMCl7dmFyIHQ9bmV3IERhdGUoRGF0ZS5VVEMoLTEsbi5tLG4uZCxuLkgsbi5NLG4uUyxuLkwpKTtyZXR1cm4gdC5zZXRVVENGdWxsWWVhcihuLnkpLHR9cmV0dXJuIG5ldyBEYXRlKERhdGUuVVRDKG4ueSxuLm0sbi5kLG4uSCxuLk0sbi5TLG4uTCkpfWZ1bmN0aW9uIENzKG4sdCxlKXtyZXR1cm57eTpuLG06dCxkOmUsSDowLE06MCxTOjAsTDowfX1mdW5jdGlvbiBGYyhuKXt2YXIgdD1uLmRhdGVUaW1lLGU9bi5kYXRlLGk9bi50aW1lLHI9bi5wZXJpb2RzLHM9bi5kYXlzLG89bi5zaG9ydERheXMsYT1uLm1vbnRocyxsPW4uc2hvcnRNb250aHMsYz1ScyhyKSx1PUxzKHIpLGg9UnMocyksZj1McyhzKSxkPVJzKG8pLGc9THMobykseD1ScyhhKSx2PUxzKGEpLG09UnMobCkscD1McyhsKSxiPXthOlYsQTpRLGI6YXQsQjpHLGM6bnVsbCxkOkpwLGU6SnAsZjp0XyxnOnVfLEc6Zl8sSDpLdixJOlF2LGo6anYsTDp0bSxtOmVfLE06bl8scDokLHE6bHQsUTpRcCxzOmpwLFM6aV8sdTpyXyxVOnNfLFY6b18sdzphXyxXOmxfLHg6bnVsbCxYOm51bGwseTpjXyxZOmhfLFo6ZF8sIiUiOktwfSxfPXthOmR0LEE6eHQsYjprLEI6RnQsYzpudWxsLGQ6JHAsZTokcCxmOnhfLGc6QV8sRzpSXyxIOnBfLEk6bV8sajpnXyxMOm5tLG06eV8sTTp2XyxwOm10LHE6U3QsUTpRcCxzOmpwLFM6X18sdTp3XyxVOk1fLFY6Yl8sdzpTXyxXOkVfLHg6bnVsbCxYOm51bGwseTpUXyxZOkNfLFo6TF8sIiUiOktwfSxTPXthOlgsQTp5LGI6UixCOkQsYzpGLGQ6WXAsZTpZcCxmOll2LGc6WHAsRzpxcCxIOlpwLEk6WnAsajpHdixMOlh2LG06VnYsTTpXdixwOnR0LHE6SHYsUTpKdixzOiR2LFM6cXYsdTp6dixVOlV2LFY6QnYsdzpGdixXOk92LHg6eixYOk4seTpYcCxZOnFwLFo6a3YsIiUiOlp2fTtiLng9TChlLGIpLGIuWD1MKGksYiksYi5jPUwodCxiKSxfLng9TChlLF8pLF8uWD1MKGksXyksXy5jPUwodCxfKTtmdW5jdGlvbiBMKEIsc3Qpe3JldHVybiBmdW5jdGlvbihudCl7dmFyIEM9W10saj0tMSxKPTAsaXQ9Qi5sZW5ndGgsZXQsdnQsYnQ7Zm9yKG50IGluc3RhbmNlb2YgRGF0ZXx8KG50PW5ldyBEYXRlKCtudCkpOysrajxpdDspQi5jaGFyQ29kZUF0KGopPT09MzcmJihDLnB1c2goQi5zbGljZShKLGopKSwodnQ9V3BbZXQ9Qi5jaGFyQXQoKytqKV0pIT1udWxsP2V0PUIuY2hhckF0KCsraik6dnQ9ZXQ9PT0iZSI/IiAiOiIwIiwoYnQ9c3RbZXRdKSYmKGV0PWJ0KG50LHZ0KSksQy5wdXNoKGV0KSxKPWorMSk7cmV0dXJuIEMucHVzaChCLnNsaWNlKEosaikpLEMuam9pbigiIil9fWZ1bmN0aW9uIEEoQixzdCl7cmV0dXJuIGZ1bmN0aW9uKG50KXt2YXIgQz1DcygxOTAwLHZvaWQgMCwxKSxqPUgoQyxCLG50Kz0iIiwwKSxKLGl0O2lmKGohPW50Lmxlbmd0aClyZXR1cm4gbnVsbDtpZigiUSJpbiBDKXJldHVybiBuZXcgRGF0ZShDLlEpO2lmKCJzImluIEMpcmV0dXJuIG5ldyBEYXRlKEMucyoxZTMrKCJMImluIEM/Qy5MOjApKTtpZihzdCYmISgiWiJpbiBDKSYmKEMuWj0wKSwicCJpbiBDJiYoQy5IPUMuSCUxMitDLnAqMTIpLEMubT09PXZvaWQgMCYmKEMubT0icSJpbiBDP0MucTowKSwiViJpbiBDKXtpZihDLlY8MXx8Qy5WPjUzKXJldHVybiBudWxsOyJ3ImluIEN8fChDLnc9MSksIloiaW4gQz8oSj1OYyhDcyhDLnksMCwxKSksaXQ9Si5nZXRVVENEYXkoKSxKPWl0PjR8fGl0PT09MD9vci5jZWlsKEopOm9yKEopLEo9cGEub2Zmc2V0KEosKEMuVi0xKSo3KSxDLnk9Si5nZXRVVENGdWxsWWVhcigpLEMubT1KLmdldFVUQ01vbnRoKCksQy5kPUouZ2V0VVRDRGF0ZSgpKyhDLncrNiklNyk6KEo9SWMoQ3MoQy55LDAsMSkpLGl0PUouZ2V0RGF5KCksSj1pdD40fHxpdD09PTA/c3IuY2VpbChKKTpzcihKKSxKPXJyLm9mZnNldChKLChDLlYtMSkqNyksQy55PUouZ2V0RnVsbFllYXIoKSxDLm09Si5nZXRNb250aCgpLEMuZD1KLmdldERhdGUoKSsoQy53KzYpJTcpfWVsc2UoIlciaW4gQ3x8IlUiaW4gQykmJigidyJpbiBDfHwoQy53PSJ1ImluIEM/Qy51JTc6IlciaW4gQz8xOjApLGl0PSJaImluIEM/TmMoQ3MoQy55LDAsMSkpLmdldFVUQ0RheSgpOkljKENzKEMueSwwLDEpKS5nZXREYXkoKSxDLm09MCxDLmQ9IlciaW4gQz8oQy53KzYpJTcrQy5XKjctKGl0KzUpJTc6Qy53K0MuVSo3LShpdCs2KSU3KTtyZXR1cm4iWiJpbiBDPyhDLkgrPUMuWi8xMDB8MCxDLk0rPUMuWiUxMDAsTmMoQykpOkljKEMpfX1mdW5jdGlvbiBIKEIsc3QsbnQsQyl7Zm9yKHZhciBqPTAsSj1zdC5sZW5ndGgsaXQ9bnQubGVuZ3RoLGV0LHZ0O2o8Sjspe2lmKEM+PWl0KXJldHVybi0xO2lmKGV0PXN0LmNoYXJDb2RlQXQoaisrKSxldD09PTM3KXtpZihldD1zdC5jaGFyQXQoaisrKSx2dD1TW2V0IGluIFdwP3N0LmNoYXJBdChqKyspOmV0XSwhdnR8fChDPXZ0KEIsbnQsQykpPDApcmV0dXJuLTF9ZWxzZSBpZihldCE9bnQuY2hhckNvZGVBdChDKyspKXJldHVybi0xfXJldHVybiBDfWZ1bmN0aW9uIHR0KEIsc3QsbnQpe3ZhciBDPWMuZXhlYyhzdC5zbGljZShudCkpO3JldHVybiBDPyhCLnA9dVtDWzBdLnRvTG93ZXJDYXNlKCldLG50K0NbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBYKEIsc3QsbnQpe3ZhciBDPWQuZXhlYyhzdC5zbGljZShudCkpO3JldHVybiBDPyhCLnc9Z1tDWzBdLnRvTG93ZXJDYXNlKCldLG50K0NbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiB5KEIsc3QsbnQpe3ZhciBDPWguZXhlYyhzdC5zbGljZShudCkpO3JldHVybiBDPyhCLnc9ZltDWzBdLnRvTG93ZXJDYXNlKCldLG50K0NbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBSKEIsc3QsbnQpe3ZhciBDPW0uZXhlYyhzdC5zbGljZShudCkpO3JldHVybiBDPyhCLm09cFtDWzBdLnRvTG93ZXJDYXNlKCldLG50K0NbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBEKEIsc3QsbnQpe3ZhciBDPXguZXhlYyhzdC5zbGljZShudCkpO3JldHVybiBDPyhCLm09dltDWzBdLnRvTG93ZXJDYXNlKCldLG50K0NbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBGKEIsc3QsbnQpe3JldHVybiBIKEIsdCxzdCxudCl9ZnVuY3Rpb24geihCLHN0LG50KXtyZXR1cm4gSChCLGUsc3QsbnQpfWZ1bmN0aW9uIE4oQixzdCxudCl7cmV0dXJuIEgoQixpLHN0LG50KX1mdW5jdGlvbiBWKEIpe3JldHVybiBvW0IuZ2V0RGF5KCldfWZ1bmN0aW9uIFEoQil7cmV0dXJuIHNbQi5nZXREYXkoKV19ZnVuY3Rpb24gYXQoQil7cmV0dXJuIGxbQi5nZXRNb250aCgpXX1mdW5jdGlvbiBHKEIpe3JldHVybiBhW0IuZ2V0TW9udGgoKV19ZnVuY3Rpb24gJChCKXtyZXR1cm4gclsrKEIuZ2V0SG91cnMoKT49MTIpXX1mdW5jdGlvbiBsdChCKXtyZXR1cm4gMSt+fihCLmdldE1vbnRoKCkvMyl9ZnVuY3Rpb24gZHQoQil7cmV0dXJuIG9bQi5nZXRVVENEYXkoKV19ZnVuY3Rpb24geHQoQil7cmV0dXJuIHNbQi5nZXRVVENEYXkoKV19ZnVuY3Rpb24gayhCKXtyZXR1cm4gbFtCLmdldFVUQ01vbnRoKCldfWZ1bmN0aW9uIEZ0KEIpe3JldHVybiBhW0IuZ2V0VVRDTW9udGgoKV19ZnVuY3Rpb24gbXQoQil7cmV0dXJuIHJbKyhCLmdldFVUQ0hvdXJzKCk+PTEyKV19ZnVuY3Rpb24gU3QoQil7cmV0dXJuIDErfn4oQi5nZXRVVENNb250aCgpLzMpfXJldHVybntmb3JtYXQ6ZnVuY3Rpb24oQil7dmFyIHN0PUwoQis9IiIsYik7cmV0dXJuIHN0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIEJ9LHN0fSxwYXJzZTpmdW5jdGlvbihCKXt2YXIgc3Q9QShCKz0iIiwhMSk7cmV0dXJuIHN0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIEJ9LHN0fSx1dGNGb3JtYXQ6ZnVuY3Rpb24oQil7dmFyIHN0PUwoQis9IiIsXyk7cmV0dXJuIHN0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIEJ9LHN0fSx1dGNQYXJzZTpmdW5jdGlvbihCKXt2YXIgc3Q9QShCKz0iIiwhMCk7cmV0dXJuIHN0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIEJ9LHN0fX19dmFyIFdwPXsiLSI6IiIsXzoiICIsMDoiMCJ9LHBlPS9eXHMqXGQrLyxEdj0vXiUvLEl2PS9bXFxeJCorP3xbXF0oKS57fV0vZztmdW5jdGlvbiBCdChuLHQsZSl7dmFyIGk9bjwwPyItIjoiIixyPShpPy1uOm4pKyIiLHM9ci5sZW5ndGg7cmV0dXJuIGkrKHM8ZT9uZXcgQXJyYXkoZS1zKzEpLmpvaW4odCkrcjpyKX1mdW5jdGlvbiBOdihuKXtyZXR1cm4gbi5yZXBsYWNlKEl2LCJcXCQmIil9ZnVuY3Rpb24gUnMobil7cmV0dXJuIG5ldyBSZWdFeHAoIl4oPzoiK24ubWFwKE52KS5qb2luKCJ8IikrIikiLCJpIil9ZnVuY3Rpb24gTHMobil7Zm9yKHZhciB0PXt9LGU9LTEsaT1uLmxlbmd0aDsrK2U8aTspdFtuW2VdLnRvTG93ZXJDYXNlKCldPWU7cmV0dXJuIHR9ZnVuY3Rpb24gRnYobix0LGUpe3ZhciBpPXBlLmV4ZWModC5zbGljZShlLGUrMSkpO3JldHVybiBpPyhuLnc9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24genYobix0LGUpe3ZhciBpPXBlLmV4ZWModC5zbGljZShlLGUrMSkpO3JldHVybiBpPyhuLnU9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gVXYobix0LGUpe3ZhciBpPXBlLmV4ZWModC5zbGljZShlLGUrMikpO3JldHVybiBpPyhuLlU9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gQnYobix0LGUpe3ZhciBpPXBlLmV4ZWModC5zbGljZShlLGUrMikpO3JldHVybiBpPyhuLlY9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gT3Yobix0LGUpe3ZhciBpPXBlLmV4ZWModC5zbGljZShlLGUrMikpO3JldHVybiBpPyhuLlc9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gcXAobix0LGUpe3ZhciBpPXBlLmV4ZWModC5zbGljZShlLGUrNCkpO3JldHVybiBpPyhuLnk9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gWHAobix0LGUpe3ZhciBpPXBlLmV4ZWModC5zbGljZShlLGUrMikpO3JldHVybiBpPyhuLnk9K2lbMF0rKCtpWzBdPjY4PzE5MDA6MmUzKSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBrdihuLHQsZSl7dmFyIGk9L14oWil8KFsrLV1cZFxkKSg/Ojo/KFxkXGQpKT8vLmV4ZWModC5zbGljZShlLGUrNikpO3JldHVybiBpPyhuLlo9aVsxXT8wOi0oaVsyXSsoaVszXXx8IjAwIikpLGUraVswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIEh2KG4sdCxlKXt2YXIgaT1wZS5leGVjKHQuc2xpY2UoZSxlKzEpKTtyZXR1cm4gaT8obi5xPWlbMF0qMy0zLGUraVswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIFZ2KG4sdCxlKXt2YXIgaT1wZS5leGVjKHQuc2xpY2UoZSxlKzIpKTtyZXR1cm4gaT8obi5tPWlbMF0tMSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBZcChuLHQsZSl7dmFyIGk9cGUuZXhlYyh0LnNsaWNlKGUsZSsyKSk7cmV0dXJuIGk/KG4uZD0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBHdihuLHQsZSl7dmFyIGk9cGUuZXhlYyh0LnNsaWNlKGUsZSszKSk7cmV0dXJuIGk/KG4ubT0wLG4uZD0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBacChuLHQsZSl7dmFyIGk9cGUuZXhlYyh0LnNsaWNlKGUsZSsyKSk7cmV0dXJuIGk/KG4uSD0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBXdihuLHQsZSl7dmFyIGk9cGUuZXhlYyh0LnNsaWNlKGUsZSsyKSk7cmV0dXJuIGk/KG4uTT0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBxdihuLHQsZSl7dmFyIGk9cGUuZXhlYyh0LnNsaWNlKGUsZSsyKSk7cmV0dXJuIGk/KG4uUz0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBYdihuLHQsZSl7dmFyIGk9cGUuZXhlYyh0LnNsaWNlKGUsZSszKSk7cmV0dXJuIGk/KG4uTD0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBZdihuLHQsZSl7dmFyIGk9cGUuZXhlYyh0LnNsaWNlKGUsZSs2KSk7cmV0dXJuIGk/KG4uTD1NYXRoLmZsb29yKGlbMF0vMWUzKSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBadihuLHQsZSl7dmFyIGk9RHYuZXhlYyh0LnNsaWNlKGUsZSsxKSk7cmV0dXJuIGk/ZStpWzBdLmxlbmd0aDotMX1mdW5jdGlvbiBKdihuLHQsZSl7dmFyIGk9cGUuZXhlYyh0LnNsaWNlKGUpKTtyZXR1cm4gaT8obi5RPStpWzBdLGUraVswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uICR2KG4sdCxlKXt2YXIgaT1wZS5leGVjKHQuc2xpY2UoZSkpO3JldHVybiBpPyhuLnM9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gSnAobix0KXtyZXR1cm4gQnQobi5nZXREYXRlKCksdCwyKX1mdW5jdGlvbiBLdihuLHQpe3JldHVybiBCdChuLmdldEhvdXJzKCksdCwyKX1mdW5jdGlvbiBRdihuLHQpe3JldHVybiBCdChuLmdldEhvdXJzKCklMTJ8fDEyLHQsMil9ZnVuY3Rpb24ganYobix0KXtyZXR1cm4gQnQoMStyci5jb3VudChFbihuKSxuKSx0LDMpfWZ1bmN0aW9uIHRtKG4sdCl7cmV0dXJuIEJ0KG4uZ2V0TWlsbGlzZWNvbmRzKCksdCwzKX1mdW5jdGlvbiB0XyhuLHQpe3JldHVybiB0bShuLHQpKyIwMDAifWZ1bmN0aW9uIGVfKG4sdCl7cmV0dXJuIEJ0KG4uZ2V0TW9udGgoKSsxLHQsMil9ZnVuY3Rpb24gbl8obix0KXtyZXR1cm4gQnQobi5nZXRNaW51dGVzKCksdCwyKX1mdW5jdGlvbiBpXyhuLHQpe3JldHVybiBCdChuLmdldFNlY29uZHMoKSx0LDIpfWZ1bmN0aW9uIHJfKG4pe3ZhciB0PW4uZ2V0RGF5KCk7cmV0dXJuIHQ9PT0wPzc6dH1mdW5jdGlvbiBzXyhuLHQpe3JldHVybiBCdChFaS5jb3VudChFbihuKS0xLG4pLHQsMil9ZnVuY3Rpb24gZW0obil7dmFyIHQ9bi5nZXREYXkoKTtyZXR1cm4gdD49NHx8dD09PTA/R24obik6R24uY2VpbChuKX1mdW5jdGlvbiBvXyhuLHQpe3JldHVybiBuPWVtKG4pLEJ0KEduLmNvdW50KEVuKG4pLG4pKyhFbihuKS5nZXREYXkoKT09PTQpLHQsMil9ZnVuY3Rpb24gYV8obil7cmV0dXJuIG4uZ2V0RGF5KCl9ZnVuY3Rpb24gbF8obix0KXtyZXR1cm4gQnQoc3IuY291bnQoRW4obiktMSxuKSx0LDIpfWZ1bmN0aW9uIGNfKG4sdCl7cmV0dXJuIEJ0KG4uZ2V0RnVsbFllYXIoKSUxMDAsdCwyKX1mdW5jdGlvbiB1XyhuLHQpe3JldHVybiBuPWVtKG4pLEJ0KG4uZ2V0RnVsbFllYXIoKSUxMDAsdCwyKX1mdW5jdGlvbiBoXyhuLHQpe3JldHVybiBCdChuLmdldEZ1bGxZZWFyKCklMWU0LHQsNCl9ZnVuY3Rpb24gZl8obix0KXt2YXIgZT1uLmdldERheSgpO3JldHVybiBuPWU+PTR8fGU9PT0wP0duKG4pOkduLmNlaWwobiksQnQobi5nZXRGdWxsWWVhcigpJTFlNCx0LDQpfWZ1bmN0aW9uIGRfKG4pe3ZhciB0PW4uZ2V0VGltZXpvbmVPZmZzZXQoKTtyZXR1cm4odD4wPyItIjoodCo9LTEsIisiKSkrQnQodC82MHwwLCIwIiwyKStCdCh0JTYwLCIwIiwyKX1mdW5jdGlvbiAkcChuLHQpe3JldHVybiBCdChuLmdldFVUQ0RhdGUoKSx0LDIpfWZ1bmN0aW9uIHBfKG4sdCl7cmV0dXJuIEJ0KG4uZ2V0VVRDSG91cnMoKSx0LDIpfWZ1bmN0aW9uIG1fKG4sdCl7cmV0dXJuIEJ0KG4uZ2V0VVRDSG91cnMoKSUxMnx8MTIsdCwyKX1mdW5jdGlvbiBnXyhuLHQpe3JldHVybiBCdCgxK3BhLmNvdW50KEFpKG4pLG4pLHQsMyl9ZnVuY3Rpb24gbm0obix0KXtyZXR1cm4gQnQobi5nZXRVVENNaWxsaXNlY29uZHMoKSx0LDMpfWZ1bmN0aW9uIHhfKG4sdCl7cmV0dXJuIG5tKG4sdCkrIjAwMCJ9ZnVuY3Rpb24geV8obix0KXtyZXR1cm4gQnQobi5nZXRVVENNb250aCgpKzEsdCwyKX1mdW5jdGlvbiB2XyhuLHQpe3JldHVybiBCdChuLmdldFVUQ01pbnV0ZXMoKSx0LDIpfWZ1bmN0aW9uIF9fKG4sdCl7cmV0dXJuIEJ0KG4uZ2V0VVRDU2Vjb25kcygpLHQsMil9ZnVuY3Rpb24gd18obil7dmFyIHQ9bi5nZXRVVENEYXkoKTtyZXR1cm4gdD09PTA/Nzp0fWZ1bmN0aW9uIE1fKG4sdCl7cmV0dXJuIEJ0KEFzLmNvdW50KEFpKG4pLTEsbiksdCwyKX1mdW5jdGlvbiBpbShuKXt2YXIgdD1uLmdldFVUQ0RheSgpO3JldHVybiB0Pj00fHx0PT09MD9XbihuKTpXbi5jZWlsKG4pfWZ1bmN0aW9uIGJfKG4sdCl7cmV0dXJuIG49aW0obiksQnQoV24uY291bnQoQWkobiksbikrKEFpKG4pLmdldFVUQ0RheSgpPT09NCksdCwyKX1mdW5jdGlvbiBTXyhuKXtyZXR1cm4gbi5nZXRVVENEYXkoKX1mdW5jdGlvbiBFXyhuLHQpe3JldHVybiBCdChvci5jb3VudChBaShuKS0xLG4pLHQsMil9ZnVuY3Rpb24gVF8obix0KXtyZXR1cm4gQnQobi5nZXRVVENGdWxsWWVhcigpJTEwMCx0LDIpfWZ1bmN0aW9uIEFfKG4sdCl7cmV0dXJuIG49aW0obiksQnQobi5nZXRVVENGdWxsWWVhcigpJTEwMCx0LDIpfWZ1bmN0aW9uIENfKG4sdCl7cmV0dXJuIEJ0KG4uZ2V0VVRDRnVsbFllYXIoKSUxZTQsdCw0KX1mdW5jdGlvbiBSXyhuLHQpe3ZhciBlPW4uZ2V0VVRDRGF5KCk7cmV0dXJuIG49ZT49NHx8ZT09PTA/V24obik6V24uY2VpbChuKSxCdChuLmdldFVUQ0Z1bGxZZWFyKCklMWU0LHQsNCl9ZnVuY3Rpb24gTF8oKXtyZXR1cm4iKzAwMDAifWZ1bmN0aW9uIEtwKCl7cmV0dXJuIiUifWZ1bmN0aW9uIFFwKG4pe3JldHVybitufWZ1bmN0aW9uIGpwKG4pe3JldHVybiBNYXRoLmZsb29yKCtuLzFlMyl9dmFyIGFyLG1hLHJtLHNtLG9tO3pjKHtkYXRlVGltZToiJXgsICVYIixkYXRlOiIlLW0vJS1kLyVZIix0aW1lOiIlLUk6JU06JVMgJXAiLHBlcmlvZHM6WyJBTSIsIlBNIl0sZGF5czpbIlN1bmRheSIsIk1vbmRheSIsIlR1ZXNkYXkiLCJXZWRuZXNkYXkiLCJUaHVyc2RheSIsIkZyaWRheSIsIlNhdHVyZGF5Il0sc2hvcnREYXlzOlsiU3VuIiwiTW9uIiwiVHVlIiwiV2VkIiwiVGh1IiwiRnJpIiwiU2F0Il0sbW9udGhzOlsiSmFudWFyeSIsIkZlYnJ1YXJ5IiwiTWFyY2giLCJBcHJpbCIsIk1heSIsIkp1bmUiLCJKdWx5IiwiQXVndXN0IiwiU2VwdGVtYmVyIiwiT2N0b2JlciIsIk5vdmVtYmVyIiwiRGVjZW1iZXIiXSxzaG9ydE1vbnRoczpbIkphbiIsIkZlYiIsIk1hciIsIkFwciIsIk1heSIsIkp1biIsIkp1bCIsIkF1ZyIsIlNlcCIsIk9jdCIsIk5vdiIsIkRlYyJdfSk7ZnVuY3Rpb24gemMobil7cmV0dXJuIGFyPUZjKG4pLG1hPWFyLmZvcm1hdCxybT1hci5wYXJzZSxzbT1hci51dGNGb3JtYXQsb209YXIudXRjUGFyc2UsYXJ9dmFyIFBzPTFlMyxEcz1Qcyo2MCxJcz1Ecyo2MCxOcz1JcyoyNCxQXz1Ocyo3LGFtPU5zKjMwLFVjPU5zKjM2NTtmdW5jdGlvbiBEXyhuKXtyZXR1cm4gbmV3IERhdGUobil9ZnVuY3Rpb24gSV8obil7cmV0dXJuIG4gaW5zdGFuY2VvZiBEYXRlPytuOituZXcgRGF0ZSgrbil9ZnVuY3Rpb24gQmMobix0LGUsaSxyLHMsbyxhLGwpe3ZhciBjPUVzKERlLERlKSx1PWMuaW52ZXJ0LGg9Yy5kb21haW4sZj1sKCIuJUwiKSxkPWwoIjolUyIpLGc9bCgiJUk6JU0iKSx4PWwoIiVJICVwIiksdj1sKCIlYSAlZCIpLG09bCgiJWIgJWQiKSxwPWwoIiVCIiksYj1sKCIlWSIpLF89W1tvLDEsUHNdLFtvLDUsNSpQc10sW28sMTUsMTUqUHNdLFtvLDMwLDMwKlBzXSxbcywxLERzXSxbcyw1LDUqRHNdLFtzLDE1LDE1KkRzXSxbcywzMCwzMCpEc10sW3IsMSxJc10sW3IsMywzKklzXSxbciw2LDYqSXNdLFtyLDEyLDEyKklzXSxbaSwxLE5zXSxbaSwyLDIqTnNdLFtlLDEsUF9dLFt0LDEsYW1dLFt0LDMsMyphbV0sW24sMSxVY11dO2Z1bmN0aW9uIFMoQSl7cmV0dXJuKG8oQSk8QT9mOnMoQSk8QT9kOnIoQSk8QT9nOmkoQSk8QT94OnQoQSk8QT9lKEEpPEE/djptOm4oQSk8QT9wOmIpKEEpfWZ1bmN0aW9uIEwoQSxILHR0LFgpe2lmKEE9PW51bGwmJihBPTEwKSx0eXBlb2YgQT09Im51bWJlciIpe3ZhciB5PU1hdGguYWJzKHR0LUgpL0EsUj1pcyhmdW5jdGlvbihEKXtyZXR1cm4gRFsyXX0pLnJpZ2h0KF8seSk7Uj09PV8ubGVuZ3RoPyhYPV9uKEgvVWMsdHQvVWMsQSksQT1uKTpSPyhSPV9beS9fW1ItMV1bMl08X1tSXVsyXS95P1ItMTpSXSxYPVJbMV0sQT1SWzBdKTooWD1NYXRoLm1heChfbihILHR0LEEpLDEpLEE9YSl9cmV0dXJuIFg9PW51bGw/QTpBLmV2ZXJ5KFgpfXJldHVybiBjLmludmVydD1mdW5jdGlvbihBKXtyZXR1cm4gbmV3IERhdGUodShBKSl9LGMuZG9tYWluPWZ1bmN0aW9uKEEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP2goYnMuY2FsbChBLElfKSk6aCgpLm1hcChEXyl9LGMudGlja3M9ZnVuY3Rpb24oQSxIKXt2YXIgdHQ9aCgpLFg9dHRbMF0seT10dFt0dC5sZW5ndGgtMV0sUj15PFgsRDtyZXR1cm4gUiYmKEQ9WCxYPXkseT1EKSxEPUwoQSxYLHksSCksRD1EP0QucmFuZ2UoWCx5KzEpOltdLFI/RC5yZXZlcnNlKCk6RH0sYy50aWNrRm9ybWF0PWZ1bmN0aW9uKEEsSCl7cmV0dXJuIEg9PW51bGw/UzpsKEgpfSxjLm5pY2U9ZnVuY3Rpb24oQSxIKXt2YXIgdHQ9aCgpO3JldHVybihBPUwoQSx0dFswXSx0dFt0dC5sZW5ndGgtMV0sSCkpP2gobGEodHQsQSkpOmN9LGMuY29weT1mdW5jdGlvbigpe3JldHVybiBWbihjLEJjKG4sdCxlLGkscixzLG8sYSxsKSl9LGN9ZnVuY3Rpb24gRnMoKXtyZXR1cm4gUGUuYXBwbHkoQmMoRW4sTGMsRWkscnIsUmMsQ2MsZGEsdWEsbWEpLmRvbWFpbihbbmV3IERhdGUoMmUzLDAsMSksbmV3IERhdGUoMmUzLDAsMildKSxhcmd1bWVudHMpfWZ1bmN0aW9uIE9jKCl7dGhpcy5fPW51bGx9ZnVuY3Rpb24gbHIobil7bi5VPW4uQz1uLkw9bi5SPW4uUD1uLk49bnVsbH1PYy5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOk9jLGluc2VydDpmdW5jdGlvbihuLHQpe3ZhciBlLGkscjtpZihuKXtpZih0LlA9bix0Lk49bi5OLG4uTiYmKG4uTi5QPXQpLG4uTj10LG4uUil7Zm9yKG49bi5SO24uTDspbj1uLkw7bi5MPXR9ZWxzZSBuLlI9dDtlPW59ZWxzZSB0aGlzLl8/KG49Y20odGhpcy5fKSx0LlA9bnVsbCx0Lk49bixuLlA9bi5MPXQsZT1uKToodC5QPXQuTj1udWxsLHRoaXMuXz10LGU9bnVsbCk7Zm9yKHQuTD10LlI9bnVsbCx0LlU9ZSx0LkM9ITAsbj10O2UmJmUuQzspaT1lLlUsZT09PWkuTD8ocj1pLlIsciYmci5DPyhlLkM9ci5DPSExLGkuQz0hMCxuPWkpOihuPT09ZS5SJiYoenModGhpcyxlKSxuPWUsZT1uLlUpLGUuQz0hMSxpLkM9ITAsVXModGhpcyxpKSkpOihyPWkuTCxyJiZyLkM/KGUuQz1yLkM9ITEsaS5DPSEwLG49aSk6KG49PT1lLkwmJihVcyh0aGlzLGUpLG49ZSxlPW4uVSksZS5DPSExLGkuQz0hMCx6cyh0aGlzLGkpKSksZT1uLlU7dGhpcy5fLkM9ITF9LHJlbW92ZTpmdW5jdGlvbihuKXtuLk4mJihuLk4uUD1uLlApLG4uUCYmKG4uUC5OPW4uTiksbi5OPW4uUD1udWxsO3ZhciB0PW4uVSxlLGk9bi5MLHI9bi5SLHMsbztpZihpP3I/cz1jbShyKTpzPWk6cz1yLHQ/dC5MPT09bj90Lkw9czp0LlI9czp0aGlzLl89cyxpJiZyPyhvPXMuQyxzLkM9bi5DLHMuTD1pLGkuVT1zLHMhPT1yPyh0PXMuVSxzLlU9bi5VLG49cy5SLHQuTD1uLHMuUj1yLHIuVT1zKToocy5VPXQsdD1zLG49cy5SKSk6KG89bi5DLG49cyksbiYmKG4uVT10KSwhbyl7aWYobiYmbi5DKXtuLkM9ITE7cmV0dXJufWRve2lmKG49PT10aGlzLl8pYnJlYWs7aWYobj09PXQuTCl7aWYoZT10LlIsZS5DJiYoZS5DPSExLHQuQz0hMCx6cyh0aGlzLHQpLGU9dC5SKSxlLkwmJmUuTC5DfHxlLlImJmUuUi5DKXsoIWUuUnx8IWUuUi5DKSYmKGUuTC5DPSExLGUuQz0hMCxVcyh0aGlzLGUpLGU9dC5SKSxlLkM9dC5DLHQuQz1lLlIuQz0hMSx6cyh0aGlzLHQpLG49dGhpcy5fO2JyZWFrfX1lbHNlIGlmKGU9dC5MLGUuQyYmKGUuQz0hMSx0LkM9ITAsVXModGhpcyx0KSxlPXQuTCksZS5MJiZlLkwuQ3x8ZS5SJiZlLlIuQyl7KCFlLkx8fCFlLkwuQykmJihlLlIuQz0hMSxlLkM9ITAsenModGhpcyxlKSxlPXQuTCksZS5DPXQuQyx0LkM9ZS5MLkM9ITEsVXModGhpcyx0KSxuPXRoaXMuXzticmVha31lLkM9ITAsbj10LHQ9dC5VfXdoaWxlKCFuLkMpO24mJihuLkM9ITEpfX19O2Z1bmN0aW9uIHpzKG4sdCl7dmFyIGU9dCxpPXQuUixyPWUuVTtyP3IuTD09PWU/ci5MPWk6ci5SPWk6bi5fPWksaS5VPXIsZS5VPWksZS5SPWkuTCxlLlImJihlLlIuVT1lKSxpLkw9ZX1mdW5jdGlvbiBVcyhuLHQpe3ZhciBlPXQsaT10Lkwscj1lLlU7cj9yLkw9PT1lP3IuTD1pOnIuUj1pOm4uXz1pLGkuVT1yLGUuVT1pLGUuTD1pLlIsZS5MJiYoZS5MLlU9ZSksaS5SPWV9ZnVuY3Rpb24gY20obil7Zm9yKDtuLkw7KW49bi5MO3JldHVybiBufXZhciBrYz1PYztmdW5jdGlvbiBjcihuLHQsZSxpKXt2YXIgcj1bbnVsbCxudWxsXSxzPW1lLnB1c2gociktMTtyZXR1cm4gci5sZWZ0PW4sci5yaWdodD10LGUmJkJzKHIsbix0LGUpLGkmJkJzKHIsdCxuLGkpLENlW24uaW5kZXhdLmhhbGZlZGdlcy5wdXNoKHMpLENlW3QuaW5kZXhdLmhhbGZlZGdlcy5wdXNoKHMpLHJ9ZnVuY3Rpb24gdXIobix0LGUpe3ZhciBpPVt0LGVdO3JldHVybiBpLmxlZnQ9bixpfWZ1bmN0aW9uIEJzKG4sdCxlLGkpeyFuWzBdJiYhblsxXT8oblswXT1pLG4ubGVmdD10LG4ucmlnaHQ9ZSk6bi5sZWZ0PT09ZT9uWzFdPWk6blswXT1pfWZ1bmN0aW9uIHpfKG4sdCxlLGkscil7dmFyIHM9blswXSxvPW5bMV0sYT1zWzBdLGw9c1sxXSxjPW9bMF0sdT1vWzFdLGg9MCxmPTEsZD1jLWEsZz11LWwseDtpZih4PXQtYSwhKCFkJiZ4PjApKXtpZih4Lz1kLGQ8MCl7aWYoeDxoKXJldHVybjt4PGYmJihmPXgpfWVsc2UgaWYoZD4wKXtpZih4PmYpcmV0dXJuO3g+aCYmKGg9eCl9aWYoeD1pLWEsISghZCYmeDwwKSl7aWYoeC89ZCxkPDApe2lmKHg+ZilyZXR1cm47eD5oJiYoaD14KX1lbHNlIGlmKGQ+MCl7aWYoeDxoKXJldHVybjt4PGYmJihmPXgpfWlmKHg9ZS1sLCEoIWcmJng+MCkpe2lmKHgvPWcsZzwwKXtpZih4PGgpcmV0dXJuO3g8ZiYmKGY9eCl9ZWxzZSBpZihnPjApe2lmKHg+ZilyZXR1cm47eD5oJiYoaD14KX1pZih4PXItbCwhKCFnJiZ4PDApKXtpZih4Lz1nLGc8MCl7aWYoeD5mKXJldHVybjt4PmgmJihoPXgpfWVsc2UgaWYoZz4wKXtpZih4PGgpcmV0dXJuO3g8ZiYmKGY9eCl9cmV0dXJuIShoPjApJiYhKGY8MSl8fChoPjAmJihuWzBdPVthK2gqZCxsK2gqZ10pLGY8MSYmKG5bMV09W2ErZipkLGwrZipnXSkpLCEwfX19fX1mdW5jdGlvbiBVXyhuLHQsZSxpLHIpe3ZhciBzPW5bMV07aWYocylyZXR1cm4hMDt2YXIgbz1uWzBdLGE9bi5sZWZ0LGw9bi5yaWdodCxjPWFbMF0sdT1hWzFdLGg9bFswXSxmPWxbMV0sZD0oYytoKS8yLGc9KHUrZikvMix4LHY7aWYoZj09PXUpe2lmKGQ8dHx8ZD49aSlyZXR1cm47aWYoYz5oKXtpZighbylvPVtkLGVdO2Vsc2UgaWYob1sxXT49cilyZXR1cm47cz1bZCxyXX1lbHNle2lmKCFvKW89W2Qscl07ZWxzZSBpZihvWzFdPGUpcmV0dXJuO3M9W2QsZV19fWVsc2UgaWYoeD0oYy1oKS8oZi11KSx2PWcteCpkLHg8LTF8fHg+MSlpZihjPmgpe2lmKCFvKW89WyhlLXYpL3gsZV07ZWxzZSBpZihvWzFdPj1yKXJldHVybjtzPVsoci12KS94LHJdfWVsc2V7aWYoIW8pbz1bKHItdikveCxyXTtlbHNlIGlmKG9bMV08ZSlyZXR1cm47cz1bKGUtdikveCxlXX1lbHNlIGlmKHU8Zil7aWYoIW8pbz1bdCx4KnQrdl07ZWxzZSBpZihvWzBdPj1pKXJldHVybjtzPVtpLHgqaSt2XX1lbHNle2lmKCFvKW89W2kseCppK3ZdO2Vsc2UgaWYob1swXTx0KXJldHVybjtzPVt0LHgqdCt2XX1yZXR1cm4gblswXT1vLG5bMV09cywhMH1mdW5jdGlvbiB1bShuLHQsZSxpKXtmb3IodmFyIHI9bWUubGVuZ3RoLHM7ci0tOykoIVVfKHM9bWVbcl0sbix0LGUsaSl8fCF6XyhzLG4sdCxlLGkpfHwhKE1hdGguYWJzKHNbMF1bMF0tc1sxXVswXSk+WHR8fE1hdGguYWJzKHNbMF1bMV0tc1sxXVsxXSk+WHQpKSYmZGVsZXRlIG1lW3JdfWZ1bmN0aW9uIGhtKG4pe3JldHVybiBDZVtuLmluZGV4XT17c2l0ZTpuLGhhbGZlZGdlczpbXX19ZnVuY3Rpb24gQl8obix0KXt2YXIgZT1uLnNpdGUsaT10LmxlZnQscj10LnJpZ2h0O3JldHVybiBlPT09ciYmKHI9aSxpPWUpLHI/TWF0aC5hdGFuMihyWzFdLWlbMV0sclswXS1pWzBdKTooZT09PWk/KGk9dFsxXSxyPXRbMF0pOihpPXRbMF0scj10WzFdKSxNYXRoLmF0YW4yKGlbMF0tclswXSxyWzFdLWlbMV0pKX1mdW5jdGlvbiBIYyhuLHQpe3JldHVybiB0WysodC5sZWZ0IT09bi5zaXRlKV19ZnVuY3Rpb24gT18obix0KXtyZXR1cm4gdFsrKHQubGVmdD09PW4uc2l0ZSldfWZ1bmN0aW9uIGZtKCl7Zm9yKHZhciBuPTAsdD1DZS5sZW5ndGgsZSxpLHIscztuPHQ7KytuKWlmKChlPUNlW25dKSYmKHM9KGk9ZS5oYWxmZWRnZXMpLmxlbmd0aCkpe3ZhciBvPW5ldyBBcnJheShzKSxhPW5ldyBBcnJheShzKTtmb3Iocj0wO3I8czsrK3Ipb1tyXT1yLGFbcl09Ql8oZSxtZVtpW3JdXSk7Zm9yKG8uc29ydChmdW5jdGlvbihsLGMpe3JldHVybiBhW2NdLWFbbF19KSxyPTA7cjxzOysrcilhW3JdPWlbb1tyXV07Zm9yKHI9MDtyPHM7KytyKWlbcl09YVtyXX19ZnVuY3Rpb24gZG0obix0LGUsaSl7dmFyIHI9Q2UubGVuZ3RoLHMsbyxhLGwsYyx1LGgsZixkLGcseCx2LG09ITA7Zm9yKHM9MDtzPHI7KytzKWlmKG89Q2Vbc10pe2ZvcihhPW8uc2l0ZSxjPW8uaGFsZmVkZ2VzLGw9Yy5sZW5ndGg7bC0tOyltZVtjW2xdXXx8Yy5zcGxpY2UobCwxKTtmb3IobD0wLHU9Yy5sZW5ndGg7bDx1OylnPU9fKG8sbWVbY1tsXV0pLHg9Z1swXSx2PWdbMV0saD1IYyhvLG1lW2NbKytsJXVdXSksZj1oWzBdLGQ9aFsxXSwoTWF0aC5hYnMoeC1mKT5YdHx8TWF0aC5hYnModi1kKT5YdCkmJihjLnNwbGljZShsLDAsbWUucHVzaCh1cihhLGcsTWF0aC5hYnMoeC1uKTxYdCYmaS12Plh0P1tuLE1hdGguYWJzKGYtbik8WHQ/ZDppXTpNYXRoLmFicyh2LWkpPFh0JiZlLXg+WHQ/W01hdGguYWJzKGQtaSk8WHQ/ZjplLGldOk1hdGguYWJzKHgtZSk8WHQmJnYtdD5YdD9bZSxNYXRoLmFicyhmLWUpPFh0P2Q6dF06TWF0aC5hYnModi10KTxYdCYmeC1uPlh0P1tNYXRoLmFicyhkLXQpPFh0P2Y6bix0XTpudWxsKSktMSksKyt1KTt1JiYobT0hMSl9aWYobSl7dmFyIHAsYixfLFM9MS8wO2ZvcihzPTAsbT1udWxsO3M8cjsrK3MpKG89Q2Vbc10pJiYoYT1vLnNpdGUscD1hWzBdLW4sYj1hWzFdLXQsXz1wKnArYipiLF88UyYmKFM9XyxtPW8pKTtpZihtKXt2YXIgTD1bbix0XSxBPVtuLGldLEg9W2UsaV0sdHQ9W2UsdF07bS5oYWxmZWRnZXMucHVzaChtZS5wdXNoKHVyKGE9bS5zaXRlLEwsQSkpLTEsbWUucHVzaCh1cihhLEEsSCkpLTEsbWUucHVzaCh1cihhLEgsdHQpKS0xLG1lLnB1c2godXIoYSx0dCxMKSktMSl9fWZvcihzPTA7czxyOysrcykobz1DZVtzXSkmJihvLmhhbGZlZGdlcy5sZW5ndGh8fGRlbGV0ZSBDZVtzXSl9dmFyIHBtPVtdLGdhO2Z1bmN0aW9uIGtfKCl7bHIodGhpcyksdGhpcy54PXRoaXMueT10aGlzLmFyYz10aGlzLnNpdGU9dGhpcy5jeT1udWxsfWZ1bmN0aW9uIENpKG4pe3ZhciB0PW4uUCxlPW4uTjtpZighKCF0fHwhZSkpe3ZhciBpPXQuc2l0ZSxyPW4uc2l0ZSxzPWUuc2l0ZTtpZihpIT09cyl7dmFyIG89clswXSxhPXJbMV0sbD1pWzBdLW8sYz1pWzFdLWEsdT1zWzBdLW8saD1zWzFdLWEsZj0yKihsKmgtYyp1KTtpZighKGY+PS1tbSkpe3ZhciBkPWwqbCtjKmMsZz11KnUraCpoLHg9KGgqZC1jKmcpL2Ysdj0obCpnLXUqZCkvZixtPXBtLnBvcCgpfHxuZXcga187bS5hcmM9bixtLnNpdGU9cixtLng9eCtvLG0ueT0obS5jeT12K2EpK01hdGguc3FydCh4Kngrdip2KSxuLmNpcmNsZT1tO2Zvcih2YXIgcD1udWxsLGI9aHIuXztiOylpZihtLnk8Yi55fHxtLnk9PT1iLnkmJm0ueDw9Yi54KWlmKGIuTCliPWIuTDtlbHNle3A9Yi5QO2JyZWFrfWVsc2UgaWYoYi5SKWI9Yi5SO2Vsc2V7cD1iO2JyZWFrfWhyLmluc2VydChwLG0pLHB8fChnYT1tKX19fX1mdW5jdGlvbiBSaShuKXt2YXIgdD1uLmNpcmNsZTt0JiYodC5QfHwoZ2E9dC5OKSxoci5yZW1vdmUodCkscG0ucHVzaCh0KSxscih0KSxuLmNpcmNsZT1udWxsKX12YXIgeG09W107ZnVuY3Rpb24gSF8oKXtscih0aGlzKSx0aGlzLmVkZ2U9dGhpcy5zaXRlPXRoaXMuY2lyY2xlPW51bGx9ZnVuY3Rpb24gZ20obil7dmFyIHQ9eG0ucG9wKCl8fG5ldyBIXztyZXR1cm4gdC5zaXRlPW4sdH1mdW5jdGlvbiBWYyhuKXtSaShuKSxMaS5yZW1vdmUobikseG0ucHVzaChuKSxscihuKX1mdW5jdGlvbiB5bShuKXt2YXIgdD1uLmNpcmNsZSxlPXQueCxpPXQuY3kscj1bZSxpXSxzPW4uUCxvPW4uTixhPVtuXTtWYyhuKTtmb3IodmFyIGw9cztsLmNpcmNsZSYmTWF0aC5hYnMoZS1sLmNpcmNsZS54KTxYdCYmTWF0aC5hYnMoaS1sLmNpcmNsZS5jeSk8WHQ7KXM9bC5QLGEudW5zaGlmdChsKSxWYyhsKSxsPXM7YS51bnNoaWZ0KGwpLFJpKGwpO2Zvcih2YXIgYz1vO2MuY2lyY2xlJiZNYXRoLmFicyhlLWMuY2lyY2xlLngpPFh0JiZNYXRoLmFicyhpLWMuY2lyY2xlLmN5KTxYdDspbz1jLk4sYS5wdXNoKGMpLFZjKGMpLGM9bzthLnB1c2goYyksUmkoYyk7dmFyIHU9YS5sZW5ndGgsaDtmb3IoaD0xO2g8dTsrK2gpYz1hW2hdLGw9YVtoLTFdLEJzKGMuZWRnZSxsLnNpdGUsYy5zaXRlLHIpO2w9YVswXSxjPWFbdS0xXSxjLmVkZ2U9Y3IobC5zaXRlLGMuc2l0ZSxudWxsLHIpLENpKGwpLENpKGMpfWZ1bmN0aW9uIHZtKG4pe2Zvcih2YXIgdD1uWzBdLGU9blsxXSxpLHIscyxvLGE9TGkuXzthOylpZihzPV9tKGEsZSktdCxzPlh0KWE9YS5MO2Vsc2UgaWYobz10LVZfKGEsZSksbz5YdCl7aWYoIWEuUil7aT1hO2JyZWFrfWE9YS5SfWVsc2V7cz4tWHQ/KGk9YS5QLHI9YSk6bz4tWHQ/KGk9YSxyPWEuTik6aT1yPWE7YnJlYWt9aG0obik7dmFyIGw9Z20obik7aWYoTGkuaW5zZXJ0KGksbCksISghaSYmIXIpKXtpZihpPT09cil7UmkoaSkscj1nbShpLnNpdGUpLExpLmluc2VydChsLHIpLGwuZWRnZT1yLmVkZ2U9Y3IoaS5zaXRlLGwuc2l0ZSksQ2koaSksQ2kocik7cmV0dXJufWlmKCFyKXtsLmVkZ2U9Y3IoaS5zaXRlLGwuc2l0ZSk7cmV0dXJufVJpKGkpLFJpKHIpO3ZhciBjPWkuc2l0ZSx1PWNbMF0saD1jWzFdLGY9blswXS11LGQ9blsxXS1oLGc9ci5zaXRlLHg9Z1swXS11LHY9Z1sxXS1oLG09MiooZip2LWQqeCkscD1mKmYrZCpkLGI9eCp4K3YqdixfPVsodipwLWQqYikvbSt1LChmKmIteCpwKS9tK2hdO0JzKHIuZWRnZSxjLGcsXyksbC5lZGdlPWNyKGMsbixudWxsLF8pLHIuZWRnZT1jcihuLGcsbnVsbCxfKSxDaShpKSxDaShyKX19ZnVuY3Rpb24gX20obix0KXt2YXIgZT1uLnNpdGUsaT1lWzBdLHI9ZVsxXSxzPXItdDtpZighcylyZXR1cm4gaTt2YXIgbz1uLlA7aWYoIW8pcmV0dXJuLTEvMDtlPW8uc2l0ZTt2YXIgYT1lWzBdLGw9ZVsxXSxjPWwtdDtpZighYylyZXR1cm4gYTt2YXIgdT1hLWksaD0xL3MtMS9jLGY9dS9jO3JldHVybiBoPygtZitNYXRoLnNxcnQoZipmLTIqaCoodSp1LygtMipjKS1sK2MvMityLXMvMikpKS9oK2k6KGkrYSkvMn1mdW5jdGlvbiBWXyhuLHQpe3ZhciBlPW4uTjtpZihlKXJldHVybiBfbShlLHQpO3ZhciBpPW4uc2l0ZTtyZXR1cm4gaVsxXT09PXQ/aVswXToxLzB9dmFyIFh0PTFlLTYsbW09MWUtMTIsTGksQ2UsaHIsbWU7ZnVuY3Rpb24gR18obix0LGUpe3JldHVybihuWzBdLWVbMF0pKih0WzFdLW5bMV0pLShuWzBdLXRbMF0pKihlWzFdLW5bMV0pfWZ1bmN0aW9uIFdfKG4sdCl7cmV0dXJuIHRbMV0tblsxXXx8dFswXS1uWzBdfWZ1bmN0aW9uIHhhKG4sdCl7dmFyIGU9bi5zb3J0KFdfKS5wb3AoKSxpLHIscztmb3IobWU9W10sQ2U9bmV3IEFycmF5KG4ubGVuZ3RoKSxMaT1uZXcga2MsaHI9bmV3IGtjOzspaWYocz1nYSxlJiYoIXN8fGVbMV08cy55fHxlWzFdPT09cy55JiZlWzBdPHMueCkpKGVbMF0hPT1pfHxlWzFdIT09cikmJih2bShlKSxpPWVbMF0scj1lWzFdKSxlPW4ucG9wKCk7ZWxzZSBpZihzKXltKHMuYXJjKTtlbHNlIGJyZWFrO2lmKGZtKCksdCl7dmFyIG89K3RbMF1bMF0sYT0rdFswXVsxXSxsPSt0WzFdWzBdLGM9K3RbMV1bMV07dW0obyxhLGwsYyksZG0obyxhLGwsYyl9dGhpcy5lZGdlcz1tZSx0aGlzLmNlbGxzPUNlLExpPWhyPW1lPUNlPW51bGx9eGEucHJvdG90eXBlPXtjb25zdHJ1Y3Rvcjp4YSxwb2x5Z29uczpmdW5jdGlvbigpe3ZhciBuPXRoaXMuZWRnZXM7cmV0dXJuIHRoaXMuY2VsbHMubWFwKGZ1bmN0aW9uKHQpe3ZhciBlPXQuaGFsZmVkZ2VzLm1hcChmdW5jdGlvbihpKXtyZXR1cm4gSGModCxuW2ldKX0pO3JldHVybiBlLmRhdGE9dC5zaXRlLmRhdGEsZX0pfSx0cmlhbmdsZXM6ZnVuY3Rpb24oKXt2YXIgbj1bXSx0PXRoaXMuZWRnZXM7cmV0dXJuIHRoaXMuY2VsbHMuZm9yRWFjaChmdW5jdGlvbihlLGkpe2lmKCEhKGE9KHM9ZS5oYWxmZWRnZXMpLmxlbmd0aCkpZm9yKHZhciByPWUuc2l0ZSxzLG89LTEsYSxsLGM9dFtzW2EtMV1dLHU9Yy5sZWZ0PT09cj9jLnJpZ2h0OmMubGVmdDsrK288YTspbD11LGM9dFtzW29dXSx1PWMubGVmdD09PXI/Yy5yaWdodDpjLmxlZnQsbCYmdSYmaTxsLmluZGV4JiZpPHUuaW5kZXgmJkdfKHIsbCx1KTwwJiZuLnB1c2goW3IuZGF0YSxsLmRhdGEsdS5kYXRhXSl9KSxufSxsaW5rczpmdW5jdGlvbigpe3JldHVybiB0aGlzLmVkZ2VzLmZpbHRlcihmdW5jdGlvbihuKXtyZXR1cm4gbi5yaWdodH0pLm1hcChmdW5jdGlvbihuKXtyZXR1cm57c291cmNlOm4ubGVmdC5kYXRhLHRhcmdldDpuLnJpZ2h0LmRhdGF9fSl9LGZpbmQ6ZnVuY3Rpb24obix0LGUpe2Zvcih2YXIgaT10aGlzLHIscz1pLl9mb3VuZHx8MCxvPWkuY2VsbHMubGVuZ3RoLGE7IShhPWkuY2VsbHNbc10pOylpZigrK3M+PW8pcmV0dXJuIG51bGw7dmFyIGw9bi1hLnNpdGVbMF0sYz10LWEuc2l0ZVsxXSx1PWwqbCtjKmM7ZG8gYT1pLmNlbGxzW3I9c10scz1udWxsLGEuaGFsZmVkZ2VzLmZvckVhY2goZnVuY3Rpb24oaCl7dmFyIGY9aS5lZGdlc1toXSxkPWYubGVmdDtpZighKChkPT09YS5zaXRlfHwhZCkmJiEoZD1mLnJpZ2h0KSkpe3ZhciBnPW4tZFswXSx4PXQtZFsxXSx2PWcqZyt4Kng7djx1JiYodT12LHM9ZC5pbmRleCl9fSk7d2hpbGUocyE9PW51bGwpO3JldHVybiBpLl9mb3VuZD1yLGU9PW51bGx8fHU8PWUqZT9hLnNpdGU6bnVsbH19O2Z1bmN0aW9uIFBpKG4sdCxlKXt0aGlzLms9bix0aGlzLng9dCx0aGlzLnk9ZX1QaS5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOlBpLHNjYWxlOmZ1bmN0aW9uKG4pe3JldHVybiBuPT09MT90aGlzOm5ldyBQaSh0aGlzLmsqbix0aGlzLngsdGhpcy55KX0sdHJhbnNsYXRlOmZ1bmN0aW9uKG4sdCl7cmV0dXJuIG49PT0wJnQ9PT0wP3RoaXM6bmV3IFBpKHRoaXMuayx0aGlzLngrdGhpcy5rKm4sdGhpcy55K3RoaXMuayp0KX0sYXBwbHk6ZnVuY3Rpb24obil7cmV0dXJuW25bMF0qdGhpcy5rK3RoaXMueCxuWzFdKnRoaXMuayt0aGlzLnldfSxhcHBseVg6ZnVuY3Rpb24obil7cmV0dXJuIG4qdGhpcy5rK3RoaXMueH0sYXBwbHlZOmZ1bmN0aW9uKG4pe3JldHVybiBuKnRoaXMuayt0aGlzLnl9LGludmVydDpmdW5jdGlvbihuKXtyZXR1cm5bKG5bMF0tdGhpcy54KS90aGlzLmssKG5bMV0tdGhpcy55KS90aGlzLmtdfSxpbnZlcnRYOmZ1bmN0aW9uKG4pe3JldHVybihuLXRoaXMueCkvdGhpcy5rfSxpbnZlcnRZOmZ1bmN0aW9uKG4pe3JldHVybihuLXRoaXMueSkvdGhpcy5rfSxyZXNjYWxlWDpmdW5jdGlvbihuKXtyZXR1cm4gbi5jb3B5KCkuZG9tYWluKG4ucmFuZ2UoKS5tYXAodGhpcy5pbnZlcnRYLHRoaXMpLm1hcChuLmludmVydCxuKSl9LHJlc2NhbGVZOmZ1bmN0aW9uKG4pe3JldHVybiBuLmNvcHkoKS5kb21haW4obi5yYW5nZSgpLm1hcCh0aGlzLmludmVydFksdGhpcykubWFwKG4uaW52ZXJ0LG4pKX0sdG9TdHJpbmc6ZnVuY3Rpb24oKXtyZXR1cm4idHJhbnNsYXRlKCIrdGhpcy54KyIsIit0aGlzLnkrIikgc2NhbGUoIit0aGlzLmsrIikifX07dmFyIEdjPW5ldyBQaSgxLDAsMCk7V2MucHJvdG90eXBlPVBpLnByb3RvdHlwZTtmdW5jdGlvbiBXYyhuKXtmb3IoOyFuLl9fem9vbTspaWYoIShuPW4ucGFyZW50Tm9kZSkpcmV0dXJuIEdjO3JldHVybiBuLl9fem9vbX12YXIgYm09MWU0LFNtPS4wMDEsRW09T2UoIi4yfmUiKSxxXz1PZSgiLjR+ciIpLHdtPU9lKCIsfiIpO2Z1bmN0aW9uIE1tKG4pe2lmKG49PT0wKXJldHVybiIwIjtsZXQgdD1NYXRoLmFicyhuKTtyZXR1cm4gdD49Ym18fHQ8U20/RW0obik6cV8obil9dmFyIFhjPXtmb3JtYXRUaWNrOk1tLGZvcm1hdFNob3J0Ok1tLGZvcm1hdFJlYWRhYmxlKG4pe2xldCB0PU1hdGguYWJzKG4pO3JldHVybiB0Pj1ibXx8dDxTbT9FbShuKTp3bShuKX0sZm9ybWF0TG9uZzp3bX0sc2s9bmV3IEludGwuTnVtYmVyRm9ybWF0KHZvaWQgMCx7bWF4aW11bUZyYWN0aW9uRGlnaXRzOjN9KTt2YXIgb2s9T2UoIjAuM35zIiksYWs9T2UoIiwuM35mIik7dmFyIFhfPTFlMyxZXz02MCpYXyxaXz02MCpZXyxKXz0yNCpaXyxsaz0zNjUqSl8sY2s9T2UoIi40fiIpO3ZhciAkXz1GcygpLnRpY2tGb3JtYXQoKSxxYyxUbT17Zm9ybWF0VGljayhuKXtyZXR1cm4gJF8obmV3IERhdGUobikpfSxmb3JtYXRTaG9ydChuKXtyZXR1cm4gbmV3IERhdGUobikudG9Mb2NhbGVTdHJpbmcocWMse3llYXI6Im51bWVyaWMiLG1vbnRoOiJzaG9ydCIsZGF5OiJudW1lcmljIixob3VyOiJudW1lcmljIixtaW51dGU6Im51bWVyaWMiLHNlY29uZDoibnVtZXJpYyJ9KX0sZm9ybWF0UmVhZGFibGUobil7cmV0dXJuIG5ldyBEYXRlKG4pLnRvTG9jYWxlU3RyaW5nKHFjLHt5ZWFyOiJudW1lcmljIixtb250aDoic2hvcnQiLGRheToibnVtZXJpYyIsaG91cjoibnVtZXJpYyIsbWludXRlOiJudW1lcmljIixzZWNvbmQ6Im51bWVyaWMiLHRpbWVab25lTmFtZToic2hvcnQifSl9LGZvcm1hdExvbmcobil7cmV0dXJuIG5ldyBEYXRlKG4pLnRvTG9jYWxlU3RyaW5nKHFjLHt5ZWFyOiJudW1lcmljIixtb250aDoibG9uZyIsZGF5OiJudW1lcmljIixob3VyOiJudW1lcmljIixtaW51dGU6Im51bWVyaWMiLHNlY29uZDoibnVtZXJpYyIsdGltZVpvbmVOYW1lOiJzaG9ydCIsZnJhY3Rpb25hbFNlY29uZERpZ2l0czozfSl9fTtmdW5jdGlvbiBmcihuKXtzd2l0Y2gobil7Y2FzZSBvbi5MSU5FQVI6cmV0dXJuIG5ldyBZYztjYXNlIG9uLkxPRzEwOnJldHVybiBuZXcgWmM7Y2FzZSBvbi5USU1FOnJldHVybiBuZXcgSmM7ZGVmYXVsdDpsZXQgdD1uO3Rocm93IG5ldyBSYW5nZUVycm9yKGBTY2FsZVR5cGUgJHt0fSBub3Qgc3VwcG9ydGVkLmApfX12YXIgS189LjA1LFljPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5kZWZhdWx0Rm9ybWF0dGVyPVhjfXRyYW5zZm9ybSh0LGUsaSl7bGV0W3Isc109dCxvPXMtcixbYSxsXT1lLGM9bC1hO3JldHVybiBvPT09MD9hOmMvbyooaS1yKSthfWZvcndhcmQodCxlLGkpe3JldHVybiB0aGlzLnRyYW5zZm9ybSh0LGUsaSl9cmV2ZXJzZSh0LGUsaSl7cmV0dXJuIHRoaXMudHJhbnNmb3JtKGUsdCxpKX1uaWNlRG9tYWluKHQpe2xldFtlLGldPXQ7aWYoaTxlKXRocm93IG5ldyBFcnJvcigiVW5leHBlY3RlZCBpbnB1dDogbWluIGlzIGxhcmdlciB0aGFuIG1heCIpO2lmKGk9PT1lKXJldHVybiBlPT09MD9bLTEsMV06ZTwwP1syKmUsMF06WzAsMiplXTtsZXQgcj1pcigpLHM9KGktZStOdW1iZXIuRVBTSUxPTikqS18sW28sYV09ci5kb21haW4oW2UtcyxpK3NdKS5uaWNlKCkuZG9tYWluKCk7cmV0dXJuW28sYV19dGlja3ModCxlKXtyZXR1cm4gaXIoKS5kb21haW4odCkudGlja3MoZSl9aXNTYWZlTnVtYmVyKHQpe3JldHVybiBOdW1iZXIuaXNGaW5pdGUodCl9fSxaYz1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuZGVmYXVsdEZvcm1hdHRlcj1YY310cmFuc2Zvcm0odCl7cmV0dXJuIE1hdGgubG9nMTAodD4wP3Q6TnVtYmVyLk1JTl9WQUxVRSl9dW50cmFuc2Zvcm0odCl7cmV0dXJuIE1hdGguZXhwKHQvTWF0aC5MT0cxMEUpfWZvcndhcmQodCxlLGkpe2lmKGk8PTApcmV0dXJuIGVbMF07bGV0W3Isc109dCxbbyxhXT1lLGw9dGhpcy50cmFuc2Zvcm0ociksdT10aGlzLnRyYW5zZm9ybShzKS1sLGg9YS1vO3JldHVybiBpPXRoaXMudHJhbnNmb3JtKGkpLGgvKHUrTnVtYmVyLkVQU0lMT04pKihpLWwpK299cmV2ZXJzZSh0LGUsaSl7bGV0W3Isc109dCxbbyxhXT1lLGw9dGhpcy50cmFuc2Zvcm0ociksdT10aGlzLnRyYW5zZm9ybShzKS1sLGg9YS1vLGY9dS8oaCtOdW1iZXIuRVBTSUxPTikqKGktbykrbDtyZXR1cm4gdGhpcy51bnRyYW5zZm9ybShmKX1uaWNlRG9tYWluKHQpe2xldFtlLGldPXQ7aWYoZT5pKXRocm93IG5ldyBFcnJvcigiVW5leHBlY3RlZCBpbnB1dDogbWluIGlzIGxhcmdlciB0aGFuIG1heCIpO2xldCByPU1hdGgubWF4KGUsTnVtYmVyLk1JTl9WQUxVRSkscz1NYXRoLm1heChpLE51bWJlci5NSU5fVkFMVUUpO3JldHVybiBpPD0wP1tOdW1iZXIuTUlOX1ZBTFVFLDFdOltNYXRoLm1heChOdW1iZXIuTUlOX1ZBTFVFLHIqLjUpLHMqMl19dGlja3ModCxlKXtsZXQgaT10WzBdPD0wP051bWJlci5NSU5fVkFMVUU6dFswXSxyPXRbMV08PTA/TnVtYmVyLk1JTl9WQUxVRTp0WzFdLHM9VHMoKS5kb21haW4oW2kscl0pLnRpY2tzKGUpO3JldHVybiBzLmxlbmd0aD9zOnR9aXNTYWZlTnVtYmVyKHQpe3JldHVybiBOdW1iZXIuaXNGaW5pdGUodCkmJnQ+MH19LEpjPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5zY2FsZT1GcygpLHRoaXMuZGVmYXVsdEZvcm1hdHRlcj1UbX1mb3J3YXJkKHQsZSxpKXtyZXR1cm4gdGhpcy5zY2FsZS5kb21haW4odCkucmFuZ2UoZSkoaSl9cmV2ZXJzZSh0LGUsaSl7cmV0dXJuIHRoaXMuc2NhbGUuZG9tYWluKHQpLnJhbmdlKGUpLmludmVydChpKS5nZXRUaW1lKCl9bmljZURvbWFpbih0KXtsZXRbZSxpXT10aGlzLnNjYWxlLmRvbWFpbih0KS5uaWNlKCkuZG9tYWluKCk7cmV0dXJuW2UuZ2V0VGltZSgpLGkuZ2V0VGltZSgpXX10aWNrcyh0LGUpe3JldHVybiB0aGlzLnNjYWxlLmRvbWFpbih0KS50aWNrcyhlKS5tYXAoaT0+aS5nZXRUaW1lKCkpfWlzU2FmZU51bWJlcih0KXtyZXR1cm4gTnVtYmVyLmlzRmluaXRlKHQpfX07ZnVuY3Rpb24gUV8obil7cmV0dXJue3g6W24ueCxuLngrbi53aWR0aF0seTpbbi55LG4ueStuLmhlaWdodF19fXZhciAkYz0hMTtpZihzZWxmLmhhc093blByb3BlcnR5KCJXZWJHTDJSZW5kZXJpbmdDb250ZXh0IikmJnNlbGYuaGFzT3duUHJvcGVydHkoImRvY3VtZW50Iikpe2xldCBuPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImNhbnZhcyIpO24uYWRkRXZlbnRMaXN0ZW5lcigid2ViZ2xjb250ZXh0Y3JlYXRpb25lcnJvciIsKCk9PnskYz0hMX0pO2xldCB0PW4uZ2V0Q29udGV4dCgid2ViZ2wyIik7JGM9Qm9vbGVhbih0KX1mdW5jdGlvbiBqXygpe3JldHVybiAkY31mdW5jdGlvbiB0dygpe3JldHVybiBzZWxmLmhhc093blByb3BlcnR5KCJPZmZzY3JlZW5DYW52YXMiKX1mdW5jdGlvbiBldyhuLHQpe2lmKG4ubGVuZ3RoIT09dC5sZW5ndGgpcmV0dXJuITE7Zm9yKGxldCBlPTA7ZTxuLmxlbmd0aDtlKyspaWYobltlXSE9PXRbZV0pcmV0dXJuITE7cmV0dXJuITB9ZnVuY3Rpb24gbncobix0KXtyZXR1cm4gbi54WzBdPT09dC54WzBdJiZuLnhbMV09PT10LnhbMV0mJm4ueVswXT09PXQueVswXSYmbi55WzFdPT09dC55WzFdfXZhciBEaT17Y29udmVydFJlY3RUb0V4dGVudDpRXyxpc1dlYkdsMlN1cHBvcnRlZDpqXyxpc09mZnNjcmVlbkNhbnZhc1N1cHBvcnRlZDp0dyxhcmVQb2x5bGluZXNFcXVhbDpldyxhcmVFeHRlbnRzRXF1YWw6bnd9O3ZhciBkcj1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMueFNjYWxlPWZyKG9uLkxJTkVBUiksdGhpcy55U2NhbGU9ZnIob24uTElORUFSKSx0aGlzLmRvbUNvbnRhaW5lclJlY3Q9e3g6MCx3aWR0aDoxLHk6MCxoZWlnaHQ6MX0sdGhpcy5sYXN0VXBkYXRlZD0wLHRoaXMuY3VycmVudFZpZXdCb3hSZWN0PXt4OjAsd2lkdGg6MSx5OjAsaGVpZ2h0OjF9fWdldFVwZGF0ZUlkZW50aWZpZXIoKXtyZXR1cm4gdGhpcy5sYXN0VXBkYXRlZH11cGRhdGVJZGVudGlmaWVyKCl7dGhpcy5sYXN0VXBkYXRlZCsrfWlzWUF4aXNQb2ludGVkRG93bigpe3JldHVybiEwfXNldFhTY2FsZSh0KXt0aGlzLnhTY2FsZT10LHRoaXMudXBkYXRlSWRlbnRpZmllcigpfXNldFlTY2FsZSh0KXt0aGlzLnlTY2FsZT10LHRoaXMudXBkYXRlSWRlbnRpZmllcigpfWdldEN1cnJlbnRWaWV3Qm94UmVjdCgpe3JldHVybiB0aGlzLmN1cnJlbnRWaWV3Qm94UmVjdH1zZXRWaWV3Qm94UmVjdCh0KXt0aGlzLmN1cnJlbnRWaWV3Qm94UmVjdD10LHRoaXMudXBkYXRlSWRlbnRpZmllcigpfXNldERvbUNvbnRhaW5lclJlY3QodCl7dGhpcy5kb21Db250YWluZXJSZWN0PXQsdGhpcy51cGRhdGVJZGVudGlmaWVyKCl9dHJhbnNmb3JtRGF0YVRvVWlDb29yZCh0LGUpe2xldCBpPXQscj1EaS5jb252ZXJ0UmVjdFRvRXh0ZW50KHRoaXMuY3VycmVudFZpZXdCb3hSZWN0KTtyZXR1cm5bdGhpcy54U2NhbGUuZm9yd2FyZChyLngsW2kueCxpLngraS53aWR0aF0sZVswXSksdGhpcy55U2NhbGUuZm9yd2FyZChyLnksdGhpcy5pc1lBeGlzUG9pbnRlZERvd24oKT9baS55K2kuaGVpZ2h0LGkueV06W2kueSxpLnkraS5oZWlnaHRdLGVbMV0pXX19O3ZhciBUbjsoZnVuY3Rpb24obil7bltuLlNWRz0wXT0iU1ZHIixuW24uV0VCR0w9MV09IldFQkdMIn0pKFRufHwoVG49e30pKTtmdW5jdGlvbiB5YShuLHQsZSxpKXtsZXR7Y29sb3I6cix2aXNpYmxlOnMsb3BhY2l0eTpvfT1pLGE9bjtyZXR1cm4hYSYmIXM/bnVsbDooYT1hIT1udWxsP2E6dCgpLGE9ZShhKSxhLnN0eWxlLmRpc3BsYXk9cz8iIjoibm9uZSIsYS5zdHlsZS5zdHJva2U9cixhLnN0eWxlLm9wYWNpdHk9U3RyaW5nKG8hPW51bGw/bzoxKSxhKX12YXIgT3M9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5zdmc9dH1mbHVzaCgpe31vblJlc2l6ZSh0KXt9ZGVzdHJveU9iamVjdCh0KXt0aGlzLnN2Zy5yZW1vdmVDaGlsZCh0LmRvbSl9c2V0VXNlRGFya01vZGUodCl7fWNyZWF0ZVBhdGhEU3RyaW5nKHQpe2lmKCF0Lmxlbmd0aClyZXR1cm4iIjtsZXQgZT1uZXcgQXJyYXkodC5sZW5ndGgvMik7ZVswXT1gTSR7dFswXX0sJHt0WzFdfWA7Zm9yKGxldCBpPTE7aTx0Lmxlbmd0aC8yO2krKyllW2ldPWBMJHt0W2kqMl19LCR7dFtpKjIrMV19YDtyZXR1cm4gZS5qb2luKCIiKX1jcmVhdGVPclVwZGF0ZUxpbmVPYmplY3QodCxlLGkpe2xldCByPXlhKHQ9PW51bGw/dm9pZCAwOnQuZG9tLCgpPT57bGV0IHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIsInBhdGgiKTtzLnN0eWxlLmZpbGw9Im5vbmUiO2xldCBvPXRoaXMuY3JlYXRlUGF0aERTdHJpbmcoZSk7cmV0dXJuIHMuc2V0QXR0cmlidXRlKCJkIixvKSx0aGlzLnN2Zy5hcHBlbmRDaGlsZChzKSxzfSxzPT57aWYoISh0IT1udWxsJiZ0LmRhdGEpfHwhRGkuYXJlUG9seWxpbmVzRXF1YWwoZSx0PT1udWxsP3ZvaWQgMDp0LmRhdGEpKXtsZXQgbz10aGlzLmNyZWF0ZVBhdGhEU3RyaW5nKGUpO3Muc2V0QXR0cmlidXRlKCJkIixvKX1yZXR1cm4gc30saSk7cmV0dXJuIHI9PT1udWxsP251bGw6KHIuc3R5bGUuc3Ryb2tlV2lkdGg9U3RyaW5nKGkud2lkdGgpLHtkb206cixkYXRhOmV9KX1jcmVhdGVPclVwZGF0ZVRyaWFuZ2xlT2JqZWN0KHQsZSxpKXtsZXR7c2l6ZTpyLGNvbG9yOnN9PWksbz1yKk1hdGguc3FydCgzKS8yLGE9bmV3IEZsb2F0MzJBcnJheShbZS54LXIvMixlLnkrby8zLGUueCtyLzIsZS55K28vMyxlLngsZS55LW8qMi8zXSksbD15YSh0PT1udWxsP3ZvaWQgMDp0LmRvbSwoKT0+e2xldCBjPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUygiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLCJwYXRoIik7Yy5jbGFzc0xpc3QuYWRkKCJ0cmlhbmdsZSIpLGMuc3R5bGUuZmlsbD0ibm9uZSI7bGV0IHU9dGhpcy5jcmVhdGVQYXRoRFN0cmluZyhhKTtyZXR1cm4gYy5zZXRBdHRyaWJ1dGUoImQiLHUrIloiKSx0aGlzLnN2Zy5hcHBlbmRDaGlsZChjKSxjfSxjPT57bGV0IHU9dGhpcy5jcmVhdGVQYXRoRFN0cmluZyhhKTtyZXR1cm4gYy5zZXRBdHRyaWJ1dGUoImQiLHUrIloiKSxjfSxpKTtyZXR1cm4gbD09PW51bGw/bnVsbDoobC5zdHlsZS5maWxsPXMse2RvbTpsLGRhdGE6YX0pfWNyZWF0ZU9yVXBkYXRlQ2lyY2xlT2JqZWN0KHQsZSxpKXtsZXR7Y29sb3I6cixyYWRpdXM6c309aSxvPXlhKHQ9PW51bGw/dm9pZCAwOnQuZG9tLCgpPT57bGV0IGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIsImNpcmNsZSIpO3JldHVybiBhLnN0eWxlLmZpbGw9cixhLnNldEF0dHJpYnV0ZSgiY3giLFN0cmluZyhlLngpKSxhLnNldEF0dHJpYnV0ZSgiY3kiLFN0cmluZyhlLnkpKSxhLnNldEF0dHJpYnV0ZSgiciIsU3RyaW5nKHMpKSx0aGlzLnN2Zy5hcHBlbmRDaGlsZChhKSxhfSxhPT4oYS5zdHlsZS5maWxsPXIsYS5zZXRBdHRyaWJ1dGUoImN4IixTdHJpbmcoZS54KSksYS5zZXRBdHRyaWJ1dGUoImN5IixTdHJpbmcoZS55KSksYS5zZXRBdHRyaWJ1dGUoInIiLFN0cmluZyhzKSksYSksaSk7cmV0dXJuIG89PT1udWxsP251bGw6e2RvbTpvLGRhdGE6ZX19Y3JlYXRlT3JVcGRhdGVUcmFwZXpvaWRPYmplY3QodCxlLGkscil7aWYoZS55IT09aS55KXRocm93IG5ldyBSYW5nZUVycm9yKCJJbnB1dCBlcnJvcjogc3RhcnQueSAhPSBlbmQueS4iKTtsZXR7YWx0aXR1ZGU6cyxjb2xvcjpvfT1yLGE9Mi9NYXRoLnNxcnQoMykqcyxsPW5ldyBGbG9hdDMyQXJyYXkoW2UueC1hLzIsZS55K3MvMixlLngsZS55LXMvMixpLngsaS55LXMvMixpLngrYS8yLGkueStzLzJdKSxjPXlhKHQ9PW51bGw/dm9pZCAwOnQuZG9tLCgpPT57bGV0IHU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIsInBhdGgiKTt1LmNsYXNzTGlzdC5hZGQoInRyYXBlem9pZCIpLHUuc3R5bGUuZmlsbD0ibm9uZSI7bGV0IGg9dGhpcy5jcmVhdGVQYXRoRFN0cmluZyhsKTtyZXR1cm4gdS5zZXRBdHRyaWJ1dGUoImQiLGgrIloiKSx0aGlzLnN2Zy5hcHBlbmRDaGlsZCh1KSx1fSx1PT57bGV0IGg9dGhpcy5jcmVhdGVQYXRoRFN0cmluZyhsKTtyZXR1cm4gdS5zZXRBdHRyaWJ1dGUoImQiLGgrIloiKSx1fSxyKTtyZXR1cm4gYz09PW51bGw/bnVsbDooYy5zdHlsZS5maWxsPW8se2RvbTpjLGRhdGE6bH0pfWRpc3Bvc2UoKXt9fTt2YXIgJGg9IjEzNyI7dmFyIGl3PTAsQW09MSxydz0yO3ZhciBsMD0xLHN3PTIsSnM9Myxlbz0wLGhlPTEsSHI9MixjMD0xO3ZhciBqbj0wLEtzPTEsQ209MixSbT0zLExtPTQsb3c9NSxJcj0xMDAsYXc9MTAxLGx3PTEwMixQbT0xMDMsRG09MTA0LGN3PTIwMCx1dz0yMDEsaHc9MjAyLGZ3PTIwMyx1MD0yMDQsaDA9MjA1LGR3PTIwNixwdz0yMDcsbXc9MjA4LGd3PTIwOSx4dz0yMTAseXc9MCx2dz0xLF93PTIsenU9Myx3dz00LE13PTUsYnc9NixTdz03LENsPTAsRXc9MSxUdz0yLHRpPTAsQXc9MSxDdz0yLFJ3PTMsTHc9NCxQdz01LGYwPTMwMCxBbz0zMDEsQ289MzAyLFV1PTMwMyxCdT0zMDQsUmw9MzA2LEtoPTMwNyxPdT0xZTMsVmU9MTAwMSxrdT0xMDAyLGZlPTEwMDMsSW09MTAwNDt2YXIgTm09MTAwNTt2YXIgYmU9MTAwNixEdz0xMDA3O3ZhciBMbD0xMDA4O3ZhciBlaT0xMDA5LEl3PTEwMTAsTnc9MTAxMSxubz0xMDEyLEZ3PTEwMTMsUWE9MTAxNCxVaT0xMDE1LFVyPTEwMTYsenc9MTAxNyxVdz0xMDE4LEJyPTEwMjAsQnc9MTAyMSxSZT0xMDIzLE93PTEwMjQsa3c9MTAyNSxPaT0xMDI2LFZyPTEwMjcsSHc9MTAyOCxWdz0xMDI5LEd3PTEwMzAsV3c9MTAzMSxxdz0xMDMzLEtjPTMzNzc2LFFjPTMzNzc3LGpjPTMzNzc4LHR1PTMzNzc5LEZtPTM1ODQwLHptPTM1ODQxLFVtPTM1ODQyLEJtPTM1ODQzLFh3PTM2MTk2LE9tPTM3NDkyLGttPTM3NDk2LEhtPTM3ODA4LFZtPTM3ODA5LEdtPTM3ODEwLFdtPTM3ODExLHFtPTM3ODEyLFhtPTM3ODEzLFltPTM3ODE0LFptPTM3ODE1LEptPTM3ODE2LCRtPTM3ODE3LEttPTM3ODE4LFFtPTM3ODE5LGptPTM3ODIwLHRnPTM3ODIxLGVnPTM2NDkyLFl3PTIyMDAsWnc9MjIwMSxKdz0yMjAyLGphPTIzMDAsdGw9MjMwMSxldT0yMzAyLE5yPTI0MDAsRnI9MjQwMSxlbD0yNDAyLFFoPTI1MDAsZDA9MjUwMSwkdz0wO3ZhciByaT0zZTMsJHQ9MzAwMSxLdz0zMjAwLFF3PTMyMDEsdHM9MCxqdz0xO3ZhciBudT03NjgwO3ZhciB0TT01MTksaW89MzUwNDQsbmw9MzUwNDg7dmFyIG5nPSIzMDAgZXMiLEh1PTEwMzUsSW49Y2xhc3N7YWRkRXZlbnRMaXN0ZW5lcih0LGUpe3RoaXMuX2xpc3RlbmVycz09PXZvaWQgMCYmKHRoaXMuX2xpc3RlbmVycz17fSk7bGV0IGk9dGhpcy5fbGlzdGVuZXJzO2lbdF09PT12b2lkIDAmJihpW3RdPVtdKSxpW3RdLmluZGV4T2YoZSk9PT0tMSYmaVt0XS5wdXNoKGUpfWhhc0V2ZW50TGlzdGVuZXIodCxlKXtpZih0aGlzLl9saXN0ZW5lcnM9PT12b2lkIDApcmV0dXJuITE7bGV0IGk9dGhpcy5fbGlzdGVuZXJzO3JldHVybiBpW3RdIT09dm9pZCAwJiZpW3RdLmluZGV4T2YoZSkhPT0tMX1yZW1vdmVFdmVudExpc3RlbmVyKHQsZSl7aWYodGhpcy5fbGlzdGVuZXJzPT09dm9pZCAwKXJldHVybjtsZXQgcj10aGlzLl9saXN0ZW5lcnNbdF07aWYociE9PXZvaWQgMCl7bGV0IHM9ci5pbmRleE9mKGUpO3MhPT0tMSYmci5zcGxpY2UocywxKX19ZGlzcGF0Y2hFdmVudCh0KXtpZih0aGlzLl9saXN0ZW5lcnM9PT12b2lkIDApcmV0dXJuO2xldCBpPXRoaXMuX2xpc3RlbmVyc1t0LnR5cGVdO2lmKGkhPT12b2lkIDApe3QudGFyZ2V0PXRoaXM7bGV0IHI9aS5zbGljZSgwKTtmb3IobGV0IHM9MCxvPXIubGVuZ3RoO3M8bztzKyspcltzXS5jYWxsKHRoaXMsdCk7dC50YXJnZXQ9bnVsbH19fSx2ZT1bXTtmb3IobGV0IG49MDtuPDI1NjtuKyspdmVbbl09KG48MTY/IjAiOiIiKStuLnRvU3RyaW5nKDE2KTt2YXIgaXU9TWF0aC5QSS8xODAsVnU9MTgwL01hdGguUEk7ZnVuY3Rpb24gdG4oKXtsZXQgbj1NYXRoLnJhbmRvbSgpKjQyOTQ5NjcyOTV8MCx0PU1hdGgucmFuZG9tKCkqNDI5NDk2NzI5NXwwLGU9TWF0aC5yYW5kb20oKSo0Mjk0OTY3Mjk1fDAsaT1NYXRoLnJhbmRvbSgpKjQyOTQ5NjcyOTV8MDtyZXR1cm4odmVbbiYyNTVdK3ZlW24+PjgmMjU1XSt2ZVtuPj4xNiYyNTVdK3ZlW24+PjI0JjI1NV0rIi0iK3ZlW3QmMjU1XSt2ZVt0Pj44JjI1NV0rIi0iK3ZlW3Q+PjE2JjE1fDY0XSt2ZVt0Pj4yNCYyNTVdKyItIit2ZVtlJjYzfDEyOF0rdmVbZT4+OCYyNTVdKyItIit2ZVtlPj4xNiYyNTVdK3ZlW2U+PjI0JjI1NV0rdmVbaSYyNTVdK3ZlW2k+PjgmMjU1XSt2ZVtpPj4xNiYyNTVdK3ZlW2k+PjI0JjI1NV0pLnRvVXBwZXJDYXNlKCl9ZnVuY3Rpb24gSWUobix0LGUpe3JldHVybiBNYXRoLm1heCh0LE1hdGgubWluKGUsbikpfWZ1bmN0aW9uIGVNKG4sdCl7cmV0dXJuKG4ldCt0KSV0fWZ1bmN0aW9uIHJ1KG4sdCxlKXtyZXR1cm4oMS1lKSpuK2UqdH1mdW5jdGlvbiBpZyhuKXtyZXR1cm4obiZuLTEpPT09MCYmbiE9PTB9ZnVuY3Rpb24gbk0obil7cmV0dXJuIE1hdGgucG93KDIsTWF0aC5mbG9vcihNYXRoLmxvZyhuKS9NYXRoLkxOMikpfXZhciBLPWNsYXNze2NvbnN0cnVjdG9yKHQ9MCxlPTApe3RoaXMueD10LHRoaXMueT1lfWdldCB3aWR0aCgpe3JldHVybiB0aGlzLnh9c2V0IHdpZHRoKHQpe3RoaXMueD10fWdldCBoZWlnaHQoKXtyZXR1cm4gdGhpcy55fXNldCBoZWlnaHQodCl7dGhpcy55PXR9c2V0KHQsZSl7cmV0dXJuIHRoaXMueD10LHRoaXMueT1lLHRoaXN9c2V0U2NhbGFyKHQpe3JldHVybiB0aGlzLng9dCx0aGlzLnk9dCx0aGlzfXNldFgodCl7cmV0dXJuIHRoaXMueD10LHRoaXN9c2V0WSh0KXtyZXR1cm4gdGhpcy55PXQsdGhpc31zZXRDb21wb25lbnQodCxlKXtzd2l0Y2godCl7Y2FzZSAwOnRoaXMueD1lO2JyZWFrO2Nhc2UgMTp0aGlzLnk9ZTticmVhaztkZWZhdWx0OnRocm93IG5ldyBFcnJvcigiaW5kZXggaXMgb3V0IG9mIHJhbmdlOiAiK3QpfXJldHVybiB0aGlzfWdldENvbXBvbmVudCh0KXtzd2l0Y2godCl7Y2FzZSAwOnJldHVybiB0aGlzLng7Y2FzZSAxOnJldHVybiB0aGlzLnk7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoImluZGV4IGlzIG91dCBvZiByYW5nZTogIit0KX19Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy54LHRoaXMueSl9Y29weSh0KXtyZXR1cm4gdGhpcy54PXQueCx0aGlzLnk9dC55LHRoaXN9YWRkKHQsZSl7cmV0dXJuIGUhPT12b2lkIDA/KGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMjogLmFkZCgpIG5vdyBvbmx5IGFjY2VwdHMgb25lIGFyZ3VtZW50LiBVc2UgLmFkZFZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMuYWRkVmVjdG9ycyh0LGUpKToodGhpcy54Kz10LngsdGhpcy55Kz10LnksdGhpcyl9YWRkU2NhbGFyKHQpe3JldHVybiB0aGlzLngrPXQsdGhpcy55Kz10LHRoaXN9YWRkVmVjdG9ycyh0LGUpe3JldHVybiB0aGlzLng9dC54K2UueCx0aGlzLnk9dC55K2UueSx0aGlzfWFkZFNjYWxlZFZlY3Rvcih0LGUpe3JldHVybiB0aGlzLngrPXQueCplLHRoaXMueSs9dC55KmUsdGhpc31zdWIodCxlKXtyZXR1cm4gZSE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IyOiAuc3ViKCkgbm93IG9ubHkgYWNjZXB0cyBvbmUgYXJndW1lbnQuIFVzZSAuc3ViVmVjdG9ycyggYSwgYiApIGluc3RlYWQuIiksdGhpcy5zdWJWZWN0b3JzKHQsZSkpOih0aGlzLngtPXQueCx0aGlzLnktPXQueSx0aGlzKX1zdWJTY2FsYXIodCl7cmV0dXJuIHRoaXMueC09dCx0aGlzLnktPXQsdGhpc31zdWJWZWN0b3JzKHQsZSl7cmV0dXJuIHRoaXMueD10LngtZS54LHRoaXMueT10LnktZS55LHRoaXN9bXVsdGlwbHkodCl7cmV0dXJuIHRoaXMueCo9dC54LHRoaXMueSo9dC55LHRoaXN9bXVsdGlwbHlTY2FsYXIodCl7cmV0dXJuIHRoaXMueCo9dCx0aGlzLnkqPXQsdGhpc31kaXZpZGUodCl7cmV0dXJuIHRoaXMueC89dC54LHRoaXMueS89dC55LHRoaXN9ZGl2aWRlU2NhbGFyKHQpe3JldHVybiB0aGlzLm11bHRpcGx5U2NhbGFyKDEvdCl9YXBwbHlNYXRyaXgzKHQpe2xldCBlPXRoaXMueCxpPXRoaXMueSxyPXQuZWxlbWVudHM7cmV0dXJuIHRoaXMueD1yWzBdKmUrclszXSppK3JbNl0sdGhpcy55PXJbMV0qZStyWzRdKmkrcls3XSx0aGlzfW1pbih0KXtyZXR1cm4gdGhpcy54PU1hdGgubWluKHRoaXMueCx0LngpLHRoaXMueT1NYXRoLm1pbih0aGlzLnksdC55KSx0aGlzfW1heCh0KXtyZXR1cm4gdGhpcy54PU1hdGgubWF4KHRoaXMueCx0LngpLHRoaXMueT1NYXRoLm1heCh0aGlzLnksdC55KSx0aGlzfWNsYW1wKHQsZSl7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0LngsTWF0aC5taW4oZS54LHRoaXMueCkpLHRoaXMueT1NYXRoLm1heCh0LnksTWF0aC5taW4oZS55LHRoaXMueSkpLHRoaXN9Y2xhbXBTY2FsYXIodCxlKXtyZXR1cm4gdGhpcy54PU1hdGgubWF4KHQsTWF0aC5taW4oZSx0aGlzLngpKSx0aGlzLnk9TWF0aC5tYXgodCxNYXRoLm1pbihlLHRoaXMueSkpLHRoaXN9Y2xhbXBMZW5ndGgodCxlKXtsZXQgaT10aGlzLmxlbmd0aCgpO3JldHVybiB0aGlzLmRpdmlkZVNjYWxhcihpfHwxKS5tdWx0aXBseVNjYWxhcihNYXRoLm1heCh0LE1hdGgubWluKGUsaSkpKX1mbG9vcigpe3JldHVybiB0aGlzLng9TWF0aC5mbG9vcih0aGlzLngpLHRoaXMueT1NYXRoLmZsb29yKHRoaXMueSksdGhpc31jZWlsKCl7cmV0dXJuIHRoaXMueD1NYXRoLmNlaWwodGhpcy54KSx0aGlzLnk9TWF0aC5jZWlsKHRoaXMueSksdGhpc31yb3VuZCgpe3JldHVybiB0aGlzLng9TWF0aC5yb3VuZCh0aGlzLngpLHRoaXMueT1NYXRoLnJvdW5kKHRoaXMueSksdGhpc31yb3VuZFRvWmVybygpe3JldHVybiB0aGlzLng9dGhpcy54PDA/TWF0aC5jZWlsKHRoaXMueCk6TWF0aC5mbG9vcih0aGlzLngpLHRoaXMueT10aGlzLnk8MD9NYXRoLmNlaWwodGhpcy55KTpNYXRoLmZsb29yKHRoaXMueSksdGhpc31uZWdhdGUoKXtyZXR1cm4gdGhpcy54PS10aGlzLngsdGhpcy55PS10aGlzLnksdGhpc31kb3QodCl7cmV0dXJuIHRoaXMueCp0LngrdGhpcy55KnQueX1jcm9zcyh0KXtyZXR1cm4gdGhpcy54KnQueS10aGlzLnkqdC54fWxlbmd0aFNxKCl7cmV0dXJuIHRoaXMueCp0aGlzLngrdGhpcy55KnRoaXMueX1sZW5ndGgoKXtyZXR1cm4gTWF0aC5zcXJ0KHRoaXMueCp0aGlzLngrdGhpcy55KnRoaXMueSl9bWFuaGF0dGFuTGVuZ3RoKCl7cmV0dXJuIE1hdGguYWJzKHRoaXMueCkrTWF0aC5hYnModGhpcy55KX1ub3JtYWxpemUoKXtyZXR1cm4gdGhpcy5kaXZpZGVTY2FsYXIodGhpcy5sZW5ndGgoKXx8MSl9YW5nbGUoKXtyZXR1cm4gTWF0aC5hdGFuMigtdGhpcy55LC10aGlzLngpK01hdGguUEl9ZGlzdGFuY2VUbyh0KXtyZXR1cm4gTWF0aC5zcXJ0KHRoaXMuZGlzdGFuY2VUb1NxdWFyZWQodCkpfWRpc3RhbmNlVG9TcXVhcmVkKHQpe2xldCBlPXRoaXMueC10LngsaT10aGlzLnktdC55O3JldHVybiBlKmUraSppfW1hbmhhdHRhbkRpc3RhbmNlVG8odCl7cmV0dXJuIE1hdGguYWJzKHRoaXMueC10LngpK01hdGguYWJzKHRoaXMueS10LnkpfXNldExlbmd0aCh0KXtyZXR1cm4gdGhpcy5ub3JtYWxpemUoKS5tdWx0aXBseVNjYWxhcih0KX1sZXJwKHQsZSl7cmV0dXJuIHRoaXMueCs9KHQueC10aGlzLngpKmUsdGhpcy55Kz0odC55LXRoaXMueSkqZSx0aGlzfWxlcnBWZWN0b3JzKHQsZSxpKXtyZXR1cm4gdGhpcy54PXQueCsoZS54LXQueCkqaSx0aGlzLnk9dC55KyhlLnktdC55KSppLHRoaXN9ZXF1YWxzKHQpe3JldHVybiB0Lng9PT10aGlzLngmJnQueT09PXRoaXMueX1mcm9tQXJyYXkodCxlPTApe3JldHVybiB0aGlzLng9dFtlXSx0aGlzLnk9dFtlKzFdLHRoaXN9dG9BcnJheSh0PVtdLGU9MCl7cmV0dXJuIHRbZV09dGhpcy54LHRbZSsxXT10aGlzLnksdH1mcm9tQnVmZmVyQXR0cmlidXRlKHQsZSxpKXtyZXR1cm4gaSE9PXZvaWQgMCYmY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IyOiBvZmZzZXQgaGFzIGJlZW4gcmVtb3ZlZCBmcm9tIC5mcm9tQnVmZmVyQXR0cmlidXRlKCkuIiksdGhpcy54PXQuZ2V0WChlKSx0aGlzLnk9dC5nZXRZKGUpLHRoaXN9cm90YXRlQXJvdW5kKHQsZSl7bGV0IGk9TWF0aC5jb3MoZSkscj1NYXRoLnNpbihlKSxzPXRoaXMueC10Lngsbz10aGlzLnktdC55O3JldHVybiB0aGlzLng9cyppLW8qcit0LngsdGhpcy55PXMqcitvKmkrdC55LHRoaXN9cmFuZG9tKCl7cmV0dXJuIHRoaXMueD1NYXRoLnJhbmRvbSgpLHRoaXMueT1NYXRoLnJhbmRvbSgpLHRoaXN9KltTeW1ib2wuaXRlcmF0b3JdKCl7eWllbGQgdGhpcy54LHlpZWxkIHRoaXMueX19O0sucHJvdG90eXBlLmlzVmVjdG9yMj0hMDt2YXIgZGU9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmVsZW1lbnRzPVsxLDAsMCwwLDEsMCwwLDAsMV0sYXJndW1lbnRzLmxlbmd0aD4wJiZjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXgzOiB0aGUgY29uc3RydWN0b3Igbm8gbG9uZ2VyIHJlYWRzIGFyZ3VtZW50cy4gdXNlIC5zZXQoKSBpbnN0ZWFkLiIpfXNldCh0LGUsaSxyLHMsbyxhLGwsYyl7bGV0IHU9dGhpcy5lbGVtZW50cztyZXR1cm4gdVswXT10LHVbMV09cix1WzJdPWEsdVszXT1lLHVbNF09cyx1WzVdPWwsdVs2XT1pLHVbN109byx1WzhdPWMsdGhpc31pZGVudGl0eSgpe3JldHVybiB0aGlzLnNldCgxLDAsMCwwLDEsMCwwLDAsMSksdGhpc31jb3B5KHQpe2xldCBlPXRoaXMuZWxlbWVudHMsaT10LmVsZW1lbnRzO3JldHVybiBlWzBdPWlbMF0sZVsxXT1pWzFdLGVbMl09aVsyXSxlWzNdPWlbM10sZVs0XT1pWzRdLGVbNV09aVs1XSxlWzZdPWlbNl0sZVs3XT1pWzddLGVbOF09aVs4XSx0aGlzfWV4dHJhY3RCYXNpcyh0LGUsaSl7cmV0dXJuIHQuc2V0RnJvbU1hdHJpeDNDb2x1bW4odGhpcywwKSxlLnNldEZyb21NYXRyaXgzQ29sdW1uKHRoaXMsMSksaS5zZXRGcm9tTWF0cml4M0NvbHVtbih0aGlzLDIpLHRoaXN9c2V0RnJvbU1hdHJpeDQodCl7bGV0IGU9dC5lbGVtZW50cztyZXR1cm4gdGhpcy5zZXQoZVswXSxlWzRdLGVbOF0sZVsxXSxlWzVdLGVbOV0sZVsyXSxlWzZdLGVbMTBdKSx0aGlzfW11bHRpcGx5KHQpe3JldHVybiB0aGlzLm11bHRpcGx5TWF0cmljZXModGhpcyx0KX1wcmVtdWx0aXBseSh0KXtyZXR1cm4gdGhpcy5tdWx0aXBseU1hdHJpY2VzKHQsdGhpcyl9bXVsdGlwbHlNYXRyaWNlcyh0LGUpe2xldCBpPXQuZWxlbWVudHMscj1lLmVsZW1lbnRzLHM9dGhpcy5lbGVtZW50cyxvPWlbMF0sYT1pWzNdLGw9aVs2XSxjPWlbMV0sdT1pWzRdLGg9aVs3XSxmPWlbMl0sZD1pWzVdLGc9aVs4XSx4PXJbMF0sdj1yWzNdLG09cls2XSxwPXJbMV0sYj1yWzRdLF89cls3XSxTPXJbMl0sTD1yWzVdLEE9cls4XTtyZXR1cm4gc1swXT1vKngrYSpwK2wqUyxzWzNdPW8qdithKmIrbCpMLHNbNl09byptK2EqXytsKkEsc1sxXT1jKngrdSpwK2gqUyxzWzRdPWMqdit1KmIraCpMLHNbN109YyptK3UqXytoKkEsc1syXT1mKngrZCpwK2cqUyxzWzVdPWYqditkKmIrZypMLHNbOF09ZiptK2QqXytnKkEsdGhpc31tdWx0aXBseVNjYWxhcih0KXtsZXQgZT10aGlzLmVsZW1lbnRzO3JldHVybiBlWzBdKj10LGVbM10qPXQsZVs2XSo9dCxlWzFdKj10LGVbNF0qPXQsZVs3XSo9dCxlWzJdKj10LGVbNV0qPXQsZVs4XSo9dCx0aGlzfWRldGVybWluYW50KCl7bGV0IHQ9dGhpcy5lbGVtZW50cyxlPXRbMF0saT10WzFdLHI9dFsyXSxzPXRbM10sbz10WzRdLGE9dFs1XSxsPXRbNl0sYz10WzddLHU9dFs4XTtyZXR1cm4gZSpvKnUtZSphKmMtaSpzKnUraSphKmwrcipzKmMtcipvKmx9aW52ZXJ0KCl7bGV0IHQ9dGhpcy5lbGVtZW50cyxlPXRbMF0saT10WzFdLHI9dFsyXSxzPXRbM10sbz10WzRdLGE9dFs1XSxsPXRbNl0sYz10WzddLHU9dFs4XSxoPXUqby1hKmMsZj1hKmwtdSpzLGQ9YypzLW8qbCxnPWUqaCtpKmYrcipkO2lmKGc9PT0wKXJldHVybiB0aGlzLnNldCgwLDAsMCwwLDAsMCwwLDAsMCk7bGV0IHg9MS9nO3JldHVybiB0WzBdPWgqeCx0WzFdPShyKmMtdSppKSp4LHRbMl09KGEqaS1yKm8pKngsdFszXT1mKngsdFs0XT0odSplLXIqbCkqeCx0WzVdPShyKnMtYSplKSp4LHRbNl09ZCp4LHRbN109KGkqbC1jKmUpKngsdFs4XT0obyplLWkqcykqeCx0aGlzfXRyYW5zcG9zZSgpe2xldCB0LGU9dGhpcy5lbGVtZW50cztyZXR1cm4gdD1lWzFdLGVbMV09ZVszXSxlWzNdPXQsdD1lWzJdLGVbMl09ZVs2XSxlWzZdPXQsdD1lWzVdLGVbNV09ZVs3XSxlWzddPXQsdGhpc31nZXROb3JtYWxNYXRyaXgodCl7cmV0dXJuIHRoaXMuc2V0RnJvbU1hdHJpeDQodCkuaW52ZXJ0KCkudHJhbnNwb3NlKCl9dHJhbnNwb3NlSW50b0FycmF5KHQpe2xldCBlPXRoaXMuZWxlbWVudHM7cmV0dXJuIHRbMF09ZVswXSx0WzFdPWVbM10sdFsyXT1lWzZdLHRbM109ZVsxXSx0WzRdPWVbNF0sdFs1XT1lWzddLHRbNl09ZVsyXSx0WzddPWVbNV0sdFs4XT1lWzhdLHRoaXN9c2V0VXZUcmFuc2Zvcm0odCxlLGkscixzLG8sYSl7bGV0IGw9TWF0aC5jb3MocyksYz1NYXRoLnNpbihzKTtyZXR1cm4gdGhpcy5zZXQoaSpsLGkqYywtaSoobCpvK2MqYSkrbyt0LC1yKmMscipsLC1yKigtYypvK2wqYSkrYStlLDAsMCwxKSx0aGlzfXNjYWxlKHQsZSl7bGV0IGk9dGhpcy5lbGVtZW50cztyZXR1cm4gaVswXSo9dCxpWzNdKj10LGlbNl0qPXQsaVsxXSo9ZSxpWzRdKj1lLGlbN10qPWUsdGhpc31yb3RhdGUodCl7bGV0IGU9TWF0aC5jb3ModCksaT1NYXRoLnNpbih0KSxyPXRoaXMuZWxlbWVudHMscz1yWzBdLG89clszXSxhPXJbNl0sbD1yWzFdLGM9cls0XSx1PXJbN107cmV0dXJuIHJbMF09ZSpzK2kqbCxyWzNdPWUqbytpKmMscls2XT1lKmEraSp1LHJbMV09LWkqcytlKmwscls0XT0taSpvK2UqYyxyWzddPS1pKmErZSp1LHRoaXN9dHJhbnNsYXRlKHQsZSl7bGV0IGk9dGhpcy5lbGVtZW50cztyZXR1cm4gaVswXSs9dCppWzJdLGlbM10rPXQqaVs1XSxpWzZdKz10KmlbOF0saVsxXSs9ZSppWzJdLGlbNF0rPWUqaVs1XSxpWzddKz1lKmlbOF0sdGhpc31lcXVhbHModCl7bGV0IGU9dGhpcy5lbGVtZW50cyxpPXQuZWxlbWVudHM7Zm9yKGxldCByPTA7cjw5O3IrKylpZihlW3JdIT09aVtyXSlyZXR1cm4hMTtyZXR1cm4hMH1mcm9tQXJyYXkodCxlPTApe2ZvcihsZXQgaT0wO2k8OTtpKyspdGhpcy5lbGVtZW50c1tpXT10W2krZV07cmV0dXJuIHRoaXN9dG9BcnJheSh0PVtdLGU9MCl7bGV0IGk9dGhpcy5lbGVtZW50cztyZXR1cm4gdFtlXT1pWzBdLHRbZSsxXT1pWzFdLHRbZSsyXT1pWzJdLHRbZSszXT1pWzNdLHRbZSs0XT1pWzRdLHRbZSs1XT1pWzVdLHRbZSs2XT1pWzZdLHRbZSs3XT1pWzddLHRbZSs4XT1pWzhdLHR9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5mcm9tQXJyYXkodGhpcy5lbGVtZW50cyl9fTtkZS5wcm90b3R5cGUuaXNNYXRyaXgzPSEwO2Z1bmN0aW9uIHAwKG4pe2ZvcihsZXQgdD1uLmxlbmd0aC0xO3Q+PTA7LS10KWlmKG5bdF0+NjU1MzUpcmV0dXJuITA7cmV0dXJuITF9ZnVuY3Rpb24gcm8obil7cmV0dXJuIGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUygiaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIsbil9dmFyIG0wPXthbGljZWJsdWU6MTU3OTIzODMsYW50aXF1ZXdoaXRlOjE2NDQ0Mzc1LGFxdWE6NjU1MzUsYXF1YW1hcmluZTo4Mzg4NTY0LGF6dXJlOjE1Nzk0MTc1LGJlaWdlOjE2MTE5MjYwLGJpc3F1ZToxNjc3MDI0NCxibGFjazowLGJsYW5jaGVkYWxtb25kOjE2NzcyMDQ1LGJsdWU6MjU1LGJsdWV2aW9sZXQ6OTA1NTIwMixicm93bjoxMDgyNDIzNCxidXJseXdvb2Q6MTQ1OTYyMzEsY2FkZXRibHVlOjYyNjY1MjgsY2hhcnRyZXVzZTo4Mzg4MzUyLGNob2NvbGF0ZToxMzc4OTQ3MCxjb3JhbDoxNjc0NDI3Mixjb3JuZmxvd2VyYmx1ZTo2NTkxOTgxLGNvcm5zaWxrOjE2Nzc1Mzg4LGNyaW1zb246MTQ0MjMxMDAsY3lhbjo2NTUzNSxkYXJrYmx1ZToxMzksZGFya2N5YW46MzU3MjMsZGFya2dvbGRlbnJvZDoxMjA5MjkzOSxkYXJrZ3JheToxMTExOTAxNyxkYXJrZ3JlZW46MjU2MDAsZGFya2dyZXk6MTExMTkwMTcsZGFya2toYWtpOjEyNDMzMjU5LGRhcmttYWdlbnRhOjkxMDk2NDMsZGFya29saXZlZ3JlZW46NTU5Nzk5OSxkYXJrb3JhbmdlOjE2NzQ3NTIwLGRhcmtvcmNoaWQ6MTAwNDAwMTIsZGFya3JlZDo5MTA5NTA0LGRhcmtzYWxtb246MTUzMDg0MTAsZGFya3NlYWdyZWVuOjk0MTk5MTksZGFya3NsYXRlYmx1ZTo0NzM0MzQ3LGRhcmtzbGF0ZWdyYXk6MzEwMDQ5NSxkYXJrc2xhdGVncmV5OjMxMDA0OTUsZGFya3R1cnF1b2lzZTo1Mjk0NSxkYXJrdmlvbGV0Ojk2OTk1MzksZGVlcHBpbms6MTY3MTY5NDcsZGVlcHNreWJsdWU6NDkxNTEsZGltZ3JheTo2OTA4MjY1LGRpbWdyZXk6NjkwODI2NSxkb2RnZXJibHVlOjIwMDMxOTksZmlyZWJyaWNrOjExNjc0MTQ2LGZsb3JhbHdoaXRlOjE2Nzc1OTIwLGZvcmVzdGdyZWVuOjIyNjM4NDIsZnVjaHNpYToxNjcxMTkzNSxnYWluc2Jvcm86MTQ0NzQ0NjAsZ2hvc3R3aGl0ZToxNjMxNjY3MSxnb2xkOjE2NzY2NzIwLGdvbGRlbnJvZDoxNDMyOTEyMCxncmF5Ojg0MjE1MDQsZ3JlZW46MzI3NjgsZ3JlZW55ZWxsb3c6MTE0MDMwNTUsZ3JleTo4NDIxNTA0LGhvbmV5ZGV3OjE1Nzk0MTYwLGhvdHBpbms6MTY3Mzg3NDAsaW5kaWFucmVkOjEzNDU4NTI0LGluZGlnbzo0OTE1MzMwLGl2b3J5OjE2Nzc3MjAwLGtoYWtpOjE1Nzg3NjYwLGxhdmVuZGVyOjE1MTMyNDEwLGxhdmVuZGVyYmx1c2g6MTY3NzMzNjUsbGF3bmdyZWVuOjgxOTA5NzYsbGVtb25jaGlmZm9uOjE2Nzc1ODg1LGxpZ2h0Ymx1ZToxMTM5MzI1NCxsaWdodGNvcmFsOjE1NzYxNTM2LGxpZ2h0Y3lhbjoxNDc0NTU5OSxsaWdodGdvbGRlbnJvZHllbGxvdzoxNjQ0ODIxMCxsaWdodGdyYXk6MTM4ODIzMjMsbGlnaHRncmVlbjo5NDk4MjU2LGxpZ2h0Z3JleToxMzg4MjMyMyxsaWdodHBpbms6MTY3NTg0NjUsbGlnaHRzYWxtb246MTY3NTI3NjIsbGlnaHRzZWFncmVlbjoyMTQyODkwLGxpZ2h0c2t5Ymx1ZTo4OTAwMzQ2LGxpZ2h0c2xhdGVncmF5Ojc4MzM3NTMsbGlnaHRzbGF0ZWdyZXk6NzgzMzc1MyxsaWdodHN0ZWVsYmx1ZToxMTU4NDczNCxsaWdodHllbGxvdzoxNjc3NzE4NCxsaW1lOjY1MjgwLGxpbWVncmVlbjozMzI5MzMwLGxpbmVuOjE2NDQ1NjcwLG1hZ2VudGE6MTY3MTE5MzUsbWFyb29uOjgzODg2MDgsbWVkaXVtYXF1YW1hcmluZTo2NzM3MzIyLG1lZGl1bWJsdWU6MjA1LG1lZGl1bW9yY2hpZDoxMjIxMTY2NyxtZWRpdW1wdXJwbGU6OTY2MjY4MyxtZWRpdW1zZWFncmVlbjozOTc4MDk3LG1lZGl1bXNsYXRlYmx1ZTo4MDg3NzkwLG1lZGl1bXNwcmluZ2dyZWVuOjY0MTU0LG1lZGl1bXR1cnF1b2lzZTo0NzcyMzAwLG1lZGl1bXZpb2xldHJlZDoxMzA0NzE3MyxtaWRuaWdodGJsdWU6MTY0NDkxMixtaW50Y3JlYW06MTYxMjE4NTAsbWlzdHlyb3NlOjE2NzcwMjczLG1vY2Nhc2luOjE2NzcwMjI5LG5hdmFqb3doaXRlOjE2NzY4Njg1LG5hdnk6MTI4LG9sZGxhY2U6MTY2NDM1NTgsb2xpdmU6ODQyMTM3NixvbGl2ZWRyYWI6NzA0ODczOSxvcmFuZ2U6MTY3NTM5MjAsb3JhbmdlcmVkOjE2NzI5MzQ0LG9yY2hpZDoxNDMxNTczNCxwYWxlZ29sZGVucm9kOjE1NjU3MTMwLHBhbGVncmVlbjoxMDAyNTg4MCxwYWxldHVycXVvaXNlOjExNTI5OTY2LHBhbGV2aW9sZXRyZWQ6MTQzODEyMDMscGFwYXlhd2hpcDoxNjc3MzA3NyxwZWFjaHB1ZmY6MTY3Njc2NzMscGVydToxMzQ2ODk5MSxwaW5rOjE2NzYxMDM1LHBsdW06MTQ1MjQ2MzcscG93ZGVyYmx1ZToxMTU5MTkxMCxwdXJwbGU6ODM4ODczNixyZWJlY2NhcHVycGxlOjY2OTc4ODEscmVkOjE2NzExNjgwLHJvc3licm93bjoxMjM1NzUxOSxyb3lhbGJsdWU6NDI4Njk0NSxzYWRkbGVicm93bjo5MTI3MTg3LHNhbG1vbjoxNjQxNjg4MixzYW5keWJyb3duOjE2MDMyODY0LHNlYWdyZWVuOjMwNTAzMjcsc2Vhc2hlbGw6MTY3NzQ2Mzgsc2llbm5hOjEwNTA2Nzk3LHNpbHZlcjoxMjYzMjI1Nixza3libHVlOjg5MDAzMzEsc2xhdGVibHVlOjY5NzAwNjEsc2xhdGVncmF5OjczNzI5NDQsc2xhdGVncmV5OjczNzI5NDQsc25vdzoxNjc3NTkzMCxzcHJpbmdncmVlbjo2NTQwNyxzdGVlbGJsdWU6NDYyMDk4MCx0YW46MTM4MDg3ODAsdGVhbDozMjg5Nix0aGlzdGxlOjE0MjA0ODg4LHRvbWF0bzoxNjczNzA5NSx0dXJxdW9pc2U6NDI1MTg1Nix2aW9sZXQ6MTU2MzEwODYsd2hlYXQ6MTYxMTMzMzEsd2hpdGU6MTY3NzcyMTUsd2hpdGVzbW9rZToxNjExOTI4NSx5ZWxsb3c6MTY3NzY5NjAseWVsbG93Z3JlZW46MTAxNDUwNzR9LCRlPXtoOjAsczowLGw6MH0sdmE9e2g6MCxzOjAsbDowfTtmdW5jdGlvbiBzdShuLHQsZSl7cmV0dXJuIGU8MCYmKGUrPTEpLGU+MSYmKGUtPTEpLGU8MS82P24rKHQtbikqNiplOmU8MS8yP3Q6ZTwyLzM/bisodC1uKSo2KigyLzMtZSk6bn1mdW5jdGlvbiBPcihuKXtyZXR1cm4gbjwuMDQwNDU/biouMDc3Mzk5MzgwODpNYXRoLnBvdyhuKi45NDc4NjcyOTg2Ky4wNTIxMzI3MDE0LDIuNCl9ZnVuY3Rpb24gb3Uobil7cmV0dXJuIG48LjAwMzEzMDg/bioxMi45MjoxLjA1NSpNYXRoLnBvdyhuLC40MTY2NiktLjA1NX12YXIgZnQ9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3JldHVybiBlPT09dm9pZCAwJiZpPT09dm9pZCAwP3RoaXMuc2V0KHQpOnRoaXMuc2V0UkdCKHQsZSxpKX1zZXQodCl7cmV0dXJuIHQmJnQuaXNDb2xvcj90aGlzLmNvcHkodCk6dHlwZW9mIHQ9PSJudW1iZXIiP3RoaXMuc2V0SGV4KHQpOnR5cGVvZiB0PT0ic3RyaW5nIiYmdGhpcy5zZXRTdHlsZSh0KSx0aGlzfXNldFNjYWxhcih0KXtyZXR1cm4gdGhpcy5yPXQsdGhpcy5nPXQsdGhpcy5iPXQsdGhpc31zZXRIZXgodCl7cmV0dXJuIHQ9TWF0aC5mbG9vcih0KSx0aGlzLnI9KHQ+PjE2JjI1NSkvMjU1LHRoaXMuZz0odD4+OCYyNTUpLzI1NSx0aGlzLmI9KHQmMjU1KS8yNTUsdGhpc31zZXRSR0IodCxlLGkpe3JldHVybiB0aGlzLnI9dCx0aGlzLmc9ZSx0aGlzLmI9aSx0aGlzfXNldEhTTCh0LGUsaSl7aWYodD1lTSh0LDEpLGU9SWUoZSwwLDEpLGk9SWUoaSwwLDEpLGU9PT0wKXRoaXMucj10aGlzLmc9dGhpcy5iPWk7ZWxzZXtsZXQgcj1pPD0uNT9pKigxK2UpOmkrZS1pKmUscz0yKmktcjt0aGlzLnI9c3UocyxyLHQrMS8zKSx0aGlzLmc9c3UocyxyLHQpLHRoaXMuYj1zdShzLHIsdC0xLzMpfXJldHVybiB0aGlzfXNldFN0eWxlKHQpe2Z1bmN0aW9uIGUocil7ciE9PXZvaWQgMCYmcGFyc2VGbG9hdChyKTwxJiZjb25zb2xlLndhcm4oIlRIUkVFLkNvbG9yOiBBbHBoYSBjb21wb25lbnQgb2YgIit0KyIgd2lsbCBiZSBpZ25vcmVkLiIpfWxldCBpO2lmKGk9L14oKD86cmdifGhzbClhPylcKChbXlwpXSopXCkvLmV4ZWModCkpe2xldCByLHM9aVsxXSxvPWlbMl07c3dpdGNoKHMpe2Nhc2UicmdiIjpjYXNlInJnYmEiOmlmKHI9L15ccyooXGQrKVxzKixccyooXGQrKVxzKixccyooXGQrKVxzKig/OixccyooXGQqXC4/XGQrKVxzKik/JC8uZXhlYyhvKSlyZXR1cm4gdGhpcy5yPU1hdGgubWluKDI1NSxwYXJzZUludChyWzFdLDEwKSkvMjU1LHRoaXMuZz1NYXRoLm1pbigyNTUscGFyc2VJbnQoclsyXSwxMCkpLzI1NSx0aGlzLmI9TWF0aC5taW4oMjU1LHBhcnNlSW50KHJbM10sMTApKS8yNTUsZShyWzRdKSx0aGlzO2lmKHI9L15ccyooXGQrKVwlXHMqLFxzKihcZCspXCVccyosXHMqKFxkKylcJVxzKig/OixccyooXGQqXC4/XGQrKVxzKik/JC8uZXhlYyhvKSlyZXR1cm4gdGhpcy5yPU1hdGgubWluKDEwMCxwYXJzZUludChyWzFdLDEwKSkvMTAwLHRoaXMuZz1NYXRoLm1pbigxMDAscGFyc2VJbnQoclsyXSwxMCkpLzEwMCx0aGlzLmI9TWF0aC5taW4oMTAwLHBhcnNlSW50KHJbM10sMTApKS8xMDAsZShyWzRdKSx0aGlzO2JyZWFrO2Nhc2UiaHNsIjpjYXNlImhzbGEiOmlmKHI9L15ccyooXGQqXC4/XGQrKVxzKixccyooXGQrKVwlXHMqLFxzKihcZCspXCVccyooPzosXHMqKFxkKlwuP1xkKylccyopPyQvLmV4ZWMobykpe2xldCBhPXBhcnNlRmxvYXQoclsxXSkvMzYwLGw9cGFyc2VJbnQoclsyXSwxMCkvMTAwLGM9cGFyc2VJbnQoclszXSwxMCkvMTAwO3JldHVybiBlKHJbNF0pLHRoaXMuc2V0SFNMKGEsbCxjKX1icmVha319ZWxzZSBpZihpPS9eXCMoW0EtRmEtZlxkXSspJC8uZXhlYyh0KSl7bGV0IHI9aVsxXSxzPXIubGVuZ3RoO2lmKHM9PT0zKXJldHVybiB0aGlzLnI9cGFyc2VJbnQoci5jaGFyQXQoMCkrci5jaGFyQXQoMCksMTYpLzI1NSx0aGlzLmc9cGFyc2VJbnQoci5jaGFyQXQoMSkrci5jaGFyQXQoMSksMTYpLzI1NSx0aGlzLmI9cGFyc2VJbnQoci5jaGFyQXQoMikrci5jaGFyQXQoMiksMTYpLzI1NSx0aGlzO2lmKHM9PT02KXJldHVybiB0aGlzLnI9cGFyc2VJbnQoci5jaGFyQXQoMCkrci5jaGFyQXQoMSksMTYpLzI1NSx0aGlzLmc9cGFyc2VJbnQoci5jaGFyQXQoMikrci5jaGFyQXQoMyksMTYpLzI1NSx0aGlzLmI9cGFyc2VJbnQoci5jaGFyQXQoNCkrci5jaGFyQXQoNSksMTYpLzI1NSx0aGlzfXJldHVybiB0JiZ0Lmxlbmd0aD4wP3RoaXMuc2V0Q29sb3JOYW1lKHQpOnRoaXN9c2V0Q29sb3JOYW1lKHQpe2xldCBlPW0wW3QudG9Mb3dlckNhc2UoKV07cmV0dXJuIGUhPT12b2lkIDA/dGhpcy5zZXRIZXgoZSk6Y29uc29sZS53YXJuKCJUSFJFRS5Db2xvcjogVW5rbm93biBjb2xvciAiK3QpLHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5yLHRoaXMuZyx0aGlzLmIpfWNvcHkodCl7cmV0dXJuIHRoaXMucj10LnIsdGhpcy5nPXQuZyx0aGlzLmI9dC5iLHRoaXN9Y29weVNSR0JUb0xpbmVhcih0KXtyZXR1cm4gdGhpcy5yPU9yKHQuciksdGhpcy5nPU9yKHQuZyksdGhpcy5iPU9yKHQuYiksdGhpc31jb3B5TGluZWFyVG9TUkdCKHQpe3JldHVybiB0aGlzLnI9b3UodC5yKSx0aGlzLmc9b3UodC5nKSx0aGlzLmI9b3UodC5iKSx0aGlzfWNvbnZlcnRTUkdCVG9MaW5lYXIoKXtyZXR1cm4gdGhpcy5jb3B5U1JHQlRvTGluZWFyKHRoaXMpLHRoaXN9Y29udmVydExpbmVhclRvU1JHQigpe3JldHVybiB0aGlzLmNvcHlMaW5lYXJUb1NSR0IodGhpcyksdGhpc31nZXRIZXgoKXtyZXR1cm4gdGhpcy5yKjI1NTw8MTZedGhpcy5nKjI1NTw8OF50aGlzLmIqMjU1PDwwfWdldEhleFN0cmluZygpe3JldHVybigiMDAwMDAwIit0aGlzLmdldEhleCgpLnRvU3RyaW5nKDE2KSkuc2xpY2UoLTYpfWdldEhTTCh0KXtsZXQgZT10aGlzLnIsaT10aGlzLmcscj10aGlzLmIscz1NYXRoLm1heChlLGksciksbz1NYXRoLm1pbihlLGksciksYSxsLGM9KG8rcykvMjtpZihvPT09cylhPTAsbD0wO2Vsc2V7bGV0IHU9cy1vO3N3aXRjaChsPWM8PS41P3UvKHMrbyk6dS8oMi1zLW8pLHMpe2Nhc2UgZTphPShpLXIpL3UrKGk8cj82OjApO2JyZWFrO2Nhc2UgaTphPShyLWUpL3UrMjticmVhaztjYXNlIHI6YT0oZS1pKS91KzQ7YnJlYWt9YS89Nn1yZXR1cm4gdC5oPWEsdC5zPWwsdC5sPWMsdH1nZXRTdHlsZSgpe3JldHVybiJyZ2IoIisodGhpcy5yKjI1NXwwKSsiLCIrKHRoaXMuZyoyNTV8MCkrIiwiKyh0aGlzLmIqMjU1fDApKyIpIn1vZmZzZXRIU0wodCxlLGkpe3JldHVybiB0aGlzLmdldEhTTCgkZSksJGUuaCs9dCwkZS5zKz1lLCRlLmwrPWksdGhpcy5zZXRIU0woJGUuaCwkZS5zLCRlLmwpLHRoaXN9YWRkKHQpe3JldHVybiB0aGlzLnIrPXQucix0aGlzLmcrPXQuZyx0aGlzLmIrPXQuYix0aGlzfWFkZENvbG9ycyh0LGUpe3JldHVybiB0aGlzLnI9dC5yK2Uucix0aGlzLmc9dC5nK2UuZyx0aGlzLmI9dC5iK2UuYix0aGlzfWFkZFNjYWxhcih0KXtyZXR1cm4gdGhpcy5yKz10LHRoaXMuZys9dCx0aGlzLmIrPXQsdGhpc31zdWIodCl7cmV0dXJuIHRoaXMucj1NYXRoLm1heCgwLHRoaXMuci10LnIpLHRoaXMuZz1NYXRoLm1heCgwLHRoaXMuZy10LmcpLHRoaXMuYj1NYXRoLm1heCgwLHRoaXMuYi10LmIpLHRoaXN9bXVsdGlwbHkodCl7cmV0dXJuIHRoaXMucio9dC5yLHRoaXMuZyo9dC5nLHRoaXMuYio9dC5iLHRoaXN9bXVsdGlwbHlTY2FsYXIodCl7cmV0dXJuIHRoaXMucio9dCx0aGlzLmcqPXQsdGhpcy5iKj10LHRoaXN9bGVycCh0LGUpe3JldHVybiB0aGlzLnIrPSh0LnItdGhpcy5yKSplLHRoaXMuZys9KHQuZy10aGlzLmcpKmUsdGhpcy5iKz0odC5iLXRoaXMuYikqZSx0aGlzfWxlcnBDb2xvcnModCxlLGkpe3JldHVybiB0aGlzLnI9dC5yKyhlLnItdC5yKSppLHRoaXMuZz10LmcrKGUuZy10LmcpKmksdGhpcy5iPXQuYisoZS5iLXQuYikqaSx0aGlzfWxlcnBIU0wodCxlKXt0aGlzLmdldEhTTCgkZSksdC5nZXRIU0wodmEpO2xldCBpPXJ1KCRlLmgsdmEuaCxlKSxyPXJ1KCRlLnMsdmEucyxlKSxzPXJ1KCRlLmwsdmEubCxlKTtyZXR1cm4gdGhpcy5zZXRIU0woaSxyLHMpLHRoaXN9ZXF1YWxzKHQpe3JldHVybiB0LnI9PT10aGlzLnImJnQuZz09PXRoaXMuZyYmdC5iPT09dGhpcy5ifWZyb21BcnJheSh0LGU9MCl7cmV0dXJuIHRoaXMucj10W2VdLHRoaXMuZz10W2UrMV0sdGhpcy5iPXRbZSsyXSx0aGlzfXRvQXJyYXkodD1bXSxlPTApe3JldHVybiB0W2VdPXRoaXMucix0W2UrMV09dGhpcy5nLHRbZSsyXT10aGlzLmIsdH1mcm9tQnVmZmVyQXR0cmlidXRlKHQsZSl7cmV0dXJuIHRoaXMucj10LmdldFgoZSksdGhpcy5nPXQuZ2V0WShlKSx0aGlzLmI9dC5nZXRaKGUpLHQubm9ybWFsaXplZD09PSEwJiYodGhpcy5yLz0yNTUsdGhpcy5nLz0yNTUsdGhpcy5iLz0yNTUpLHRoaXN9dG9KU09OKCl7cmV0dXJuIHRoaXMuZ2V0SGV4KCl9fTtmdC5OQU1FUz1tMDtmdC5wcm90b3R5cGUuaXNDb2xvcj0hMDtmdC5wcm90b3R5cGUucj0xO2Z0LnByb3RvdHlwZS5nPTE7ZnQucHJvdG90eXBlLmI9MTt2YXIgcHIsTm49Y2xhc3N7c3RhdGljIGdldERhdGFVUkwodCl7aWYoL15kYXRhOi9pLnRlc3QodC5zcmMpfHx0eXBlb2YgSFRNTENhbnZhc0VsZW1lbnQ9PSJ1bmRlZmluZWQiKXJldHVybiB0LnNyYztsZXQgZTtpZih0IGluc3RhbmNlb2YgSFRNTENhbnZhc0VsZW1lbnQpZT10O2Vsc2V7cHI9PT12b2lkIDAmJihwcj1ybygiY2FudmFzIikpLHByLndpZHRoPXQud2lkdGgscHIuaGVpZ2h0PXQuaGVpZ2h0O2xldCBpPXByLmdldENvbnRleHQoIjJkIik7dCBpbnN0YW5jZW9mIEltYWdlRGF0YT9pLnB1dEltYWdlRGF0YSh0LDAsMCk6aS5kcmF3SW1hZ2UodCwwLDAsdC53aWR0aCx0LmhlaWdodCksZT1wcn1yZXR1cm4gZS53aWR0aD4yMDQ4fHxlLmhlaWdodD4yMDQ4Pyhjb25zb2xlLndhcm4oIlRIUkVFLkltYWdlVXRpbHMuZ2V0RGF0YVVSTDogSW1hZ2UgY29udmVydGVkIHRvIGpwZyBmb3IgcGVyZm9ybWFuY2UgcmVhc29ucyIsdCksZS50b0RhdGFVUkwoImltYWdlL2pwZWciLC42KSk6ZS50b0RhdGFVUkwoImltYWdlL3BuZyIpfXN0YXRpYyBzUkdCVG9MaW5lYXIodCl7aWYodHlwZW9mIEhUTUxJbWFnZUVsZW1lbnQhPSJ1bmRlZmluZWQiJiZ0IGluc3RhbmNlb2YgSFRNTEltYWdlRWxlbWVudHx8dHlwZW9mIEhUTUxDYW52YXNFbGVtZW50IT0idW5kZWZpbmVkIiYmdCBpbnN0YW5jZW9mIEhUTUxDYW52YXNFbGVtZW50fHx0eXBlb2YgSW1hZ2VCaXRtYXAhPSJ1bmRlZmluZWQiJiZ0IGluc3RhbmNlb2YgSW1hZ2VCaXRtYXApe2xldCBlPXJvKCJjYW52YXMiKTtlLndpZHRoPXQud2lkdGgsZS5oZWlnaHQ9dC5oZWlnaHQ7bGV0IGk9ZS5nZXRDb250ZXh0KCIyZCIpO2kuZHJhd0ltYWdlKHQsMCwwLHQud2lkdGgsdC5oZWlnaHQpO2xldCByPWkuZ2V0SW1hZ2VEYXRhKDAsMCx0LndpZHRoLHQuaGVpZ2h0KSxzPXIuZGF0YTtmb3IobGV0IG89MDtvPHMubGVuZ3RoO28rKylzW29dPU9yKHNbb10vMjU1KSoyNTU7cmV0dXJuIGkucHV0SW1hZ2VEYXRhKHIsMCwwKSxlfWVsc2UgaWYodC5kYXRhKXtsZXQgZT10LmRhdGEuc2xpY2UoMCk7Zm9yKGxldCBpPTA7aTxlLmxlbmd0aDtpKyspZSBpbnN0YW5jZW9mIFVpbnQ4QXJyYXl8fGUgaW5zdGFuY2VvZiBVaW50OENsYW1wZWRBcnJheT9lW2ldPU1hdGguZmxvb3IoT3IoZVtpXS8yNTUpKjI1NSk6ZVtpXT1PcihlW2ldKTtyZXR1cm57ZGF0YTplLHdpZHRoOnQud2lkdGgsaGVpZ2h0OnQuaGVpZ2h0fX1lbHNlIHJldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkltYWdlVXRpbHMuc1JHQlRvTGluZWFyKCk6IFVuc3VwcG9ydGVkIGltYWdlIHR5cGUuIE5vIGNvbG9yIHNwYWNlIGNvbnZlcnNpb24gYXBwbGllZC4iKSx0fX0saU09MCxhZT1jbGFzcyBleHRlbmRzIElue2NvbnN0cnVjdG9yKHQ9YWUuREVGQVVMVF9JTUFHRSxlPWFlLkRFRkFVTFRfTUFQUElORyxpPVZlLHI9VmUscz1iZSxvPUxsLGE9UmUsbD1laSxjPTEsdT1yaSl7c3VwZXIoKSxPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywiaWQiLHt2YWx1ZTppTSsrfSksdGhpcy51dWlkPXRuKCksdGhpcy5uYW1lPSIiLHRoaXMuaW1hZ2U9dCx0aGlzLm1pcG1hcHM9W10sdGhpcy5tYXBwaW5nPWUsdGhpcy53cmFwUz1pLHRoaXMud3JhcFQ9cix0aGlzLm1hZ0ZpbHRlcj1zLHRoaXMubWluRmlsdGVyPW8sdGhpcy5hbmlzb3Ryb3B5PWMsdGhpcy5mb3JtYXQ9YSx0aGlzLmludGVybmFsRm9ybWF0PW51bGwsdGhpcy50eXBlPWwsdGhpcy5vZmZzZXQ9bmV3IEsoMCwwKSx0aGlzLnJlcGVhdD1uZXcgSygxLDEpLHRoaXMuY2VudGVyPW5ldyBLKDAsMCksdGhpcy5yb3RhdGlvbj0wLHRoaXMubWF0cml4QXV0b1VwZGF0ZT0hMCx0aGlzLm1hdHJpeD1uZXcgZGUsdGhpcy5nZW5lcmF0ZU1pcG1hcHM9ITAsdGhpcy5wcmVtdWx0aXBseUFscGhhPSExLHRoaXMuZmxpcFk9ITAsdGhpcy51bnBhY2tBbGlnbm1lbnQ9NCx0aGlzLmVuY29kaW5nPXUsdGhpcy51c2VyRGF0YT17fSx0aGlzLnZlcnNpb249MCx0aGlzLm9uVXBkYXRlPW51bGwsdGhpcy5pc1JlbmRlclRhcmdldFRleHR1cmU9ITEsdGhpcy5uZWVkc1BNUkVNVXBkYXRlPSExfXVwZGF0ZU1hdHJpeCgpe3RoaXMubWF0cml4LnNldFV2VHJhbnNmb3JtKHRoaXMub2Zmc2V0LngsdGhpcy5vZmZzZXQueSx0aGlzLnJlcGVhdC54LHRoaXMucmVwZWF0LnksdGhpcy5yb3RhdGlvbix0aGlzLmNlbnRlci54LHRoaXMuY2VudGVyLnkpfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX1jb3B5KHQpe3JldHVybiB0aGlzLm5hbWU9dC5uYW1lLHRoaXMuaW1hZ2U9dC5pbWFnZSx0aGlzLm1pcG1hcHM9dC5taXBtYXBzLnNsaWNlKDApLHRoaXMubWFwcGluZz10Lm1hcHBpbmcsdGhpcy53cmFwUz10LndyYXBTLHRoaXMud3JhcFQ9dC53cmFwVCx0aGlzLm1hZ0ZpbHRlcj10Lm1hZ0ZpbHRlcix0aGlzLm1pbkZpbHRlcj10Lm1pbkZpbHRlcix0aGlzLmFuaXNvdHJvcHk9dC5hbmlzb3Ryb3B5LHRoaXMuZm9ybWF0PXQuZm9ybWF0LHRoaXMuaW50ZXJuYWxGb3JtYXQ9dC5pbnRlcm5hbEZvcm1hdCx0aGlzLnR5cGU9dC50eXBlLHRoaXMub2Zmc2V0LmNvcHkodC5vZmZzZXQpLHRoaXMucmVwZWF0LmNvcHkodC5yZXBlYXQpLHRoaXMuY2VudGVyLmNvcHkodC5jZW50ZXIpLHRoaXMucm90YXRpb249dC5yb3RhdGlvbix0aGlzLm1hdHJpeEF1dG9VcGRhdGU9dC5tYXRyaXhBdXRvVXBkYXRlLHRoaXMubWF0cml4LmNvcHkodC5tYXRyaXgpLHRoaXMuZ2VuZXJhdGVNaXBtYXBzPXQuZ2VuZXJhdGVNaXBtYXBzLHRoaXMucHJlbXVsdGlwbHlBbHBoYT10LnByZW11bHRpcGx5QWxwaGEsdGhpcy5mbGlwWT10LmZsaXBZLHRoaXMudW5wYWNrQWxpZ25tZW50PXQudW5wYWNrQWxpZ25tZW50LHRoaXMuZW5jb2Rpbmc9dC5lbmNvZGluZyx0aGlzLnVzZXJEYXRhPUpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkodC51c2VyRGF0YSkpLHRoaXN9dG9KU09OKHQpe2xldCBlPXQ9PT12b2lkIDB8fHR5cGVvZiB0PT0ic3RyaW5nIjtpZighZSYmdC50ZXh0dXJlc1t0aGlzLnV1aWRdIT09dm9pZCAwKXJldHVybiB0LnRleHR1cmVzW3RoaXMudXVpZF07bGV0IGk9e21ldGFkYXRhOnt2ZXJzaW9uOjQuNSx0eXBlOiJUZXh0dXJlIixnZW5lcmF0b3I6IlRleHR1cmUudG9KU09OIn0sdXVpZDp0aGlzLnV1aWQsbmFtZTp0aGlzLm5hbWUsbWFwcGluZzp0aGlzLm1hcHBpbmcscmVwZWF0Olt0aGlzLnJlcGVhdC54LHRoaXMucmVwZWF0LnldLG9mZnNldDpbdGhpcy5vZmZzZXQueCx0aGlzLm9mZnNldC55XSxjZW50ZXI6W3RoaXMuY2VudGVyLngsdGhpcy5jZW50ZXIueV0scm90YXRpb246dGhpcy5yb3RhdGlvbix3cmFwOlt0aGlzLndyYXBTLHRoaXMud3JhcFRdLGZvcm1hdDp0aGlzLmZvcm1hdCx0eXBlOnRoaXMudHlwZSxlbmNvZGluZzp0aGlzLmVuY29kaW5nLG1pbkZpbHRlcjp0aGlzLm1pbkZpbHRlcixtYWdGaWx0ZXI6dGhpcy5tYWdGaWx0ZXIsYW5pc290cm9weTp0aGlzLmFuaXNvdHJvcHksZmxpcFk6dGhpcy5mbGlwWSxwcmVtdWx0aXBseUFscGhhOnRoaXMucHJlbXVsdGlwbHlBbHBoYSx1bnBhY2tBbGlnbm1lbnQ6dGhpcy51bnBhY2tBbGlnbm1lbnR9O2lmKHRoaXMuaW1hZ2UhPT12b2lkIDApe2xldCByPXRoaXMuaW1hZ2U7aWYoci51dWlkPT09dm9pZCAwJiYoci51dWlkPXRuKCkpLCFlJiZ0LmltYWdlc1tyLnV1aWRdPT09dm9pZCAwKXtsZXQgcztpZihBcnJheS5pc0FycmF5KHIpKXtzPVtdO2ZvcihsZXQgbz0wLGE9ci5sZW5ndGg7bzxhO28rKylyW29dLmlzRGF0YVRleHR1cmU/cy5wdXNoKGF1KHJbb10uaW1hZ2UpKTpzLnB1c2goYXUocltvXSkpfWVsc2Ugcz1hdShyKTt0LmltYWdlc1tyLnV1aWRdPXt1dWlkOnIudXVpZCx1cmw6c319aS5pbWFnZT1yLnV1aWR9cmV0dXJuIEpTT04uc3RyaW5naWZ5KHRoaXMudXNlckRhdGEpIT09Int9IiYmKGkudXNlckRhdGE9dGhpcy51c2VyRGF0YSksZXx8KHQudGV4dHVyZXNbdGhpcy51dWlkXT1pKSxpfWRpc3Bvc2UoKXt0aGlzLmRpc3BhdGNoRXZlbnQoe3R5cGU6ImRpc3Bvc2UifSl9dHJhbnNmb3JtVXYodCl7aWYodGhpcy5tYXBwaW5nIT09ZjApcmV0dXJuIHQ7aWYodC5hcHBseU1hdHJpeDModGhpcy5tYXRyaXgpLHQueDwwfHx0Lng+MSlzd2l0Y2godGhpcy53cmFwUyl7Y2FzZSBPdTp0Lng9dC54LU1hdGguZmxvb3IodC54KTticmVhaztjYXNlIFZlOnQueD10Lng8MD8wOjE7YnJlYWs7Y2FzZSBrdTpNYXRoLmFicyhNYXRoLmZsb29yKHQueCklMik9PT0xP3QueD1NYXRoLmNlaWwodC54KS10Lng6dC54PXQueC1NYXRoLmZsb29yKHQueCk7YnJlYWt9aWYodC55PDB8fHQueT4xKXN3aXRjaCh0aGlzLndyYXBUKXtjYXNlIE91OnQueT10LnktTWF0aC5mbG9vcih0LnkpO2JyZWFrO2Nhc2UgVmU6dC55PXQueTwwPzA6MTticmVhaztjYXNlIGt1Ok1hdGguYWJzKE1hdGguZmxvb3IodC55KSUyKT09PTE/dC55PU1hdGguY2VpbCh0LnkpLXQueTp0Lnk9dC55LU1hdGguZmxvb3IodC55KTticmVha31yZXR1cm4gdGhpcy5mbGlwWSYmKHQueT0xLXQueSksdH1zZXQgbmVlZHNVcGRhdGUodCl7dD09PSEwJiZ0aGlzLnZlcnNpb24rK319O2FlLkRFRkFVTFRfSU1BR0U9dm9pZCAwO2FlLkRFRkFVTFRfTUFQUElORz1mMDthZS5wcm90b3R5cGUuaXNUZXh0dXJlPSEwO2Z1bmN0aW9uIGF1KG4pe3JldHVybiB0eXBlb2YgSFRNTEltYWdlRWxlbWVudCE9InVuZGVmaW5lZCImJm4gaW5zdGFuY2VvZiBIVE1MSW1hZ2VFbGVtZW50fHx0eXBlb2YgSFRNTENhbnZhc0VsZW1lbnQhPSJ1bmRlZmluZWQiJiZuIGluc3RhbmNlb2YgSFRNTENhbnZhc0VsZW1lbnR8fHR5cGVvZiBJbWFnZUJpdG1hcCE9InVuZGVmaW5lZCImJm4gaW5zdGFuY2VvZiBJbWFnZUJpdG1hcD9Obi5nZXREYXRhVVJMKG4pOm4uZGF0YT97ZGF0YTpBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChuLmRhdGEpLHdpZHRoOm4ud2lkdGgsaGVpZ2h0Om4uaGVpZ2h0LHR5cGU6bi5kYXRhLmNvbnN0cnVjdG9yLm5hbWV9Oihjb25zb2xlLndhcm4oIlRIUkVFLlRleHR1cmU6IFVuYWJsZSB0byBzZXJpYWxpemUgVGV4dHVyZS4iKSx7fSl9dmFyIFd0PWNsYXNze2NvbnN0cnVjdG9yKHQ9MCxlPTAsaT0wLHI9MSl7dGhpcy54PXQsdGhpcy55PWUsdGhpcy56PWksdGhpcy53PXJ9Z2V0IHdpZHRoKCl7cmV0dXJuIHRoaXMuen1zZXQgd2lkdGgodCl7dGhpcy56PXR9Z2V0IGhlaWdodCgpe3JldHVybiB0aGlzLnd9c2V0IGhlaWdodCh0KXt0aGlzLnc9dH1zZXQodCxlLGkscil7cmV0dXJuIHRoaXMueD10LHRoaXMueT1lLHRoaXMuej1pLHRoaXMudz1yLHRoaXN9c2V0U2NhbGFyKHQpe3JldHVybiB0aGlzLng9dCx0aGlzLnk9dCx0aGlzLno9dCx0aGlzLnc9dCx0aGlzfXNldFgodCl7cmV0dXJuIHRoaXMueD10LHRoaXN9c2V0WSh0KXtyZXR1cm4gdGhpcy55PXQsdGhpc31zZXRaKHQpe3JldHVybiB0aGlzLno9dCx0aGlzfXNldFcodCl7cmV0dXJuIHRoaXMudz10LHRoaXN9c2V0Q29tcG9uZW50KHQsZSl7c3dpdGNoKHQpe2Nhc2UgMDp0aGlzLng9ZTticmVhaztjYXNlIDE6dGhpcy55PWU7YnJlYWs7Y2FzZSAyOnRoaXMuej1lO2JyZWFrO2Nhc2UgMzp0aGlzLnc9ZTticmVhaztkZWZhdWx0OnRocm93IG5ldyBFcnJvcigiaW5kZXggaXMgb3V0IG9mIHJhbmdlOiAiK3QpfXJldHVybiB0aGlzfWdldENvbXBvbmVudCh0KXtzd2l0Y2godCl7Y2FzZSAwOnJldHVybiB0aGlzLng7Y2FzZSAxOnJldHVybiB0aGlzLnk7Y2FzZSAyOnJldHVybiB0aGlzLno7Y2FzZSAzOnJldHVybiB0aGlzLnc7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoImluZGV4IGlzIG91dCBvZiByYW5nZTogIit0KX19Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy54LHRoaXMueSx0aGlzLnosdGhpcy53KX1jb3B5KHQpe3JldHVybiB0aGlzLng9dC54LHRoaXMueT10LnksdGhpcy56PXQueix0aGlzLnc9dC53IT09dm9pZCAwP3QudzoxLHRoaXN9YWRkKHQsZSl7cmV0dXJuIGUhPT12b2lkIDA/KGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yNDogLmFkZCgpIG5vdyBvbmx5IGFjY2VwdHMgb25lIGFyZ3VtZW50LiBVc2UgLmFkZFZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMuYWRkVmVjdG9ycyh0LGUpKToodGhpcy54Kz10LngsdGhpcy55Kz10LnksdGhpcy56Kz10LnosdGhpcy53Kz10LncsdGhpcyl9YWRkU2NhbGFyKHQpe3JldHVybiB0aGlzLngrPXQsdGhpcy55Kz10LHRoaXMueis9dCx0aGlzLncrPXQsdGhpc31hZGRWZWN0b3JzKHQsZSl7cmV0dXJuIHRoaXMueD10LngrZS54LHRoaXMueT10LnkrZS55LHRoaXMuej10LnorZS56LHRoaXMudz10LncrZS53LHRoaXN9YWRkU2NhbGVkVmVjdG9yKHQsZSl7cmV0dXJuIHRoaXMueCs9dC54KmUsdGhpcy55Kz10LnkqZSx0aGlzLnorPXQueiplLHRoaXMudys9dC53KmUsdGhpc31zdWIodCxlKXtyZXR1cm4gZSE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3I0OiAuc3ViKCkgbm93IG9ubHkgYWNjZXB0cyBvbmUgYXJndW1lbnQuIFVzZSAuc3ViVmVjdG9ycyggYSwgYiApIGluc3RlYWQuIiksdGhpcy5zdWJWZWN0b3JzKHQsZSkpOih0aGlzLngtPXQueCx0aGlzLnktPXQueSx0aGlzLnotPXQueix0aGlzLnctPXQudyx0aGlzKX1zdWJTY2FsYXIodCl7cmV0dXJuIHRoaXMueC09dCx0aGlzLnktPXQsdGhpcy56LT10LHRoaXMudy09dCx0aGlzfXN1YlZlY3RvcnModCxlKXtyZXR1cm4gdGhpcy54PXQueC1lLngsdGhpcy55PXQueS1lLnksdGhpcy56PXQuei1lLnosdGhpcy53PXQudy1lLncsdGhpc31tdWx0aXBseSh0KXtyZXR1cm4gdGhpcy54Kj10LngsdGhpcy55Kj10LnksdGhpcy56Kj10LnosdGhpcy53Kj10LncsdGhpc31tdWx0aXBseVNjYWxhcih0KXtyZXR1cm4gdGhpcy54Kj10LHRoaXMueSo9dCx0aGlzLnoqPXQsdGhpcy53Kj10LHRoaXN9YXBwbHlNYXRyaXg0KHQpe2xldCBlPXRoaXMueCxpPXRoaXMueSxyPXRoaXMueixzPXRoaXMudyxvPXQuZWxlbWVudHM7cmV0dXJuIHRoaXMueD1vWzBdKmUrb1s0XSppK29bOF0qcitvWzEyXSpzLHRoaXMueT1vWzFdKmUrb1s1XSppK29bOV0qcitvWzEzXSpzLHRoaXMuej1vWzJdKmUrb1s2XSppK29bMTBdKnIrb1sxNF0qcyx0aGlzLnc9b1szXSplK29bN10qaStvWzExXSpyK29bMTVdKnMsdGhpc31kaXZpZGVTY2FsYXIodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlTY2FsYXIoMS90KX1zZXRBeGlzQW5nbGVGcm9tUXVhdGVybmlvbih0KXt0aGlzLnc9MipNYXRoLmFjb3ModC53KTtsZXQgZT1NYXRoLnNxcnQoMS10LncqdC53KTtyZXR1cm4gZTwxZS00Pyh0aGlzLng9MSx0aGlzLnk9MCx0aGlzLno9MCk6KHRoaXMueD10LngvZSx0aGlzLnk9dC55L2UsdGhpcy56PXQuei9lKSx0aGlzfXNldEF4aXNBbmdsZUZyb21Sb3RhdGlvbk1hdHJpeCh0KXtsZXQgZSxpLHIscyxsPXQuZWxlbWVudHMsYz1sWzBdLHU9bFs0XSxoPWxbOF0sZj1sWzFdLGQ9bFs1XSxnPWxbOV0seD1sWzJdLHY9bFs2XSxtPWxbMTBdO2lmKE1hdGguYWJzKHUtZik8LjAxJiZNYXRoLmFicyhoLXgpPC4wMSYmTWF0aC5hYnMoZy12KTwuMDEpe2lmKE1hdGguYWJzKHUrZik8LjEmJk1hdGguYWJzKGgreCk8LjEmJk1hdGguYWJzKGcrdik8LjEmJk1hdGguYWJzKGMrZCttLTMpPC4xKXJldHVybiB0aGlzLnNldCgxLDAsMCwwKSx0aGlzO2U9TWF0aC5QSTtsZXQgYj0oYysxKS8yLF89KGQrMSkvMixTPShtKzEpLzIsTD0odStmKS80LEE9KGgreCkvNCxIPShnK3YpLzQ7cmV0dXJuIGI+XyYmYj5TP2I8LjAxPyhpPTAscj0uNzA3MTA2NzgxLHM9LjcwNzEwNjc4MSk6KGk9TWF0aC5zcXJ0KGIpLHI9TC9pLHM9QS9pKTpfPlM/XzwuMDE/KGk9LjcwNzEwNjc4MSxyPTAscz0uNzA3MTA2NzgxKToocj1NYXRoLnNxcnQoXyksaT1ML3Iscz1IL3IpOlM8LjAxPyhpPS43MDcxMDY3ODEscj0uNzA3MTA2NzgxLHM9MCk6KHM9TWF0aC5zcXJ0KFMpLGk9QS9zLHI9SC9zKSx0aGlzLnNldChpLHIscyxlKSx0aGlzfWxldCBwPU1hdGguc3FydCgodi1nKSoodi1nKSsoaC14KSooaC14KSsoZi11KSooZi11KSk7cmV0dXJuIE1hdGguYWJzKHApPC4wMDEmJihwPTEpLHRoaXMueD0odi1nKS9wLHRoaXMueT0oaC14KS9wLHRoaXMuej0oZi11KS9wLHRoaXMudz1NYXRoLmFjb3MoKGMrZCttLTEpLzIpLHRoaXN9bWluKHQpe3JldHVybiB0aGlzLng9TWF0aC5taW4odGhpcy54LHQueCksdGhpcy55PU1hdGgubWluKHRoaXMueSx0LnkpLHRoaXMuej1NYXRoLm1pbih0aGlzLnosdC56KSx0aGlzLnc9TWF0aC5taW4odGhpcy53LHQudyksdGhpc31tYXgodCl7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0aGlzLngsdC54KSx0aGlzLnk9TWF0aC5tYXgodGhpcy55LHQueSksdGhpcy56PU1hdGgubWF4KHRoaXMueix0LnopLHRoaXMudz1NYXRoLm1heCh0aGlzLncsdC53KSx0aGlzfWNsYW1wKHQsZSl7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0LngsTWF0aC5taW4oZS54LHRoaXMueCkpLHRoaXMueT1NYXRoLm1heCh0LnksTWF0aC5taW4oZS55LHRoaXMueSkpLHRoaXMuej1NYXRoLm1heCh0LnosTWF0aC5taW4oZS56LHRoaXMueikpLHRoaXMudz1NYXRoLm1heCh0LncsTWF0aC5taW4oZS53LHRoaXMudykpLHRoaXN9Y2xhbXBTY2FsYXIodCxlKXtyZXR1cm4gdGhpcy54PU1hdGgubWF4KHQsTWF0aC5taW4oZSx0aGlzLngpKSx0aGlzLnk9TWF0aC5tYXgodCxNYXRoLm1pbihlLHRoaXMueSkpLHRoaXMuej1NYXRoLm1heCh0LE1hdGgubWluKGUsdGhpcy56KSksdGhpcy53PU1hdGgubWF4KHQsTWF0aC5taW4oZSx0aGlzLncpKSx0aGlzfWNsYW1wTGVuZ3RoKHQsZSl7bGV0IGk9dGhpcy5sZW5ndGgoKTtyZXR1cm4gdGhpcy5kaXZpZGVTY2FsYXIoaXx8MSkubXVsdGlwbHlTY2FsYXIoTWF0aC5tYXgodCxNYXRoLm1pbihlLGkpKSl9Zmxvb3IoKXtyZXR1cm4gdGhpcy54PU1hdGguZmxvb3IodGhpcy54KSx0aGlzLnk9TWF0aC5mbG9vcih0aGlzLnkpLHRoaXMuej1NYXRoLmZsb29yKHRoaXMueiksdGhpcy53PU1hdGguZmxvb3IodGhpcy53KSx0aGlzfWNlaWwoKXtyZXR1cm4gdGhpcy54PU1hdGguY2VpbCh0aGlzLngpLHRoaXMueT1NYXRoLmNlaWwodGhpcy55KSx0aGlzLno9TWF0aC5jZWlsKHRoaXMueiksdGhpcy53PU1hdGguY2VpbCh0aGlzLncpLHRoaXN9cm91bmQoKXtyZXR1cm4gdGhpcy54PU1hdGgucm91bmQodGhpcy54KSx0aGlzLnk9TWF0aC5yb3VuZCh0aGlzLnkpLHRoaXMuej1NYXRoLnJvdW5kKHRoaXMueiksdGhpcy53PU1hdGgucm91bmQodGhpcy53KSx0aGlzfXJvdW5kVG9aZXJvKCl7cmV0dXJuIHRoaXMueD10aGlzLng8MD9NYXRoLmNlaWwodGhpcy54KTpNYXRoLmZsb29yKHRoaXMueCksdGhpcy55PXRoaXMueTwwP01hdGguY2VpbCh0aGlzLnkpOk1hdGguZmxvb3IodGhpcy55KSx0aGlzLno9dGhpcy56PDA/TWF0aC5jZWlsKHRoaXMueik6TWF0aC5mbG9vcih0aGlzLnopLHRoaXMudz10aGlzLnc8MD9NYXRoLmNlaWwodGhpcy53KTpNYXRoLmZsb29yKHRoaXMudyksdGhpc31uZWdhdGUoKXtyZXR1cm4gdGhpcy54PS10aGlzLngsdGhpcy55PS10aGlzLnksdGhpcy56PS10aGlzLnosdGhpcy53PS10aGlzLncsdGhpc31kb3QodCl7cmV0dXJuIHRoaXMueCp0LngrdGhpcy55KnQueSt0aGlzLnoqdC56K3RoaXMudyp0Lnd9bGVuZ3RoU3EoKXtyZXR1cm4gdGhpcy54KnRoaXMueCt0aGlzLnkqdGhpcy55K3RoaXMueip0aGlzLnordGhpcy53KnRoaXMud31sZW5ndGgoKXtyZXR1cm4gTWF0aC5zcXJ0KHRoaXMueCp0aGlzLngrdGhpcy55KnRoaXMueSt0aGlzLnoqdGhpcy56K3RoaXMudyp0aGlzLncpfW1hbmhhdHRhbkxlbmd0aCgpe3JldHVybiBNYXRoLmFicyh0aGlzLngpK01hdGguYWJzKHRoaXMueSkrTWF0aC5hYnModGhpcy56KStNYXRoLmFicyh0aGlzLncpfW5vcm1hbGl6ZSgpe3JldHVybiB0aGlzLmRpdmlkZVNjYWxhcih0aGlzLmxlbmd0aCgpfHwxKX1zZXRMZW5ndGgodCl7cmV0dXJuIHRoaXMubm9ybWFsaXplKCkubXVsdGlwbHlTY2FsYXIodCl9bGVycCh0LGUpe3JldHVybiB0aGlzLngrPSh0LngtdGhpcy54KSplLHRoaXMueSs9KHQueS10aGlzLnkpKmUsdGhpcy56Kz0odC56LXRoaXMueikqZSx0aGlzLncrPSh0LnctdGhpcy53KSplLHRoaXN9bGVycFZlY3RvcnModCxlLGkpe3JldHVybiB0aGlzLng9dC54KyhlLngtdC54KSppLHRoaXMueT10LnkrKGUueS10LnkpKmksdGhpcy56PXQueisoZS56LXQueikqaSx0aGlzLnc9dC53KyhlLnctdC53KSppLHRoaXN9ZXF1YWxzKHQpe3JldHVybiB0Lng9PT10aGlzLngmJnQueT09PXRoaXMueSYmdC56PT09dGhpcy56JiZ0Lnc9PT10aGlzLnd9ZnJvbUFycmF5KHQsZT0wKXtyZXR1cm4gdGhpcy54PXRbZV0sdGhpcy55PXRbZSsxXSx0aGlzLno9dFtlKzJdLHRoaXMudz10W2UrM10sdGhpc310b0FycmF5KHQ9W10sZT0wKXtyZXR1cm4gdFtlXT10aGlzLngsdFtlKzFdPXRoaXMueSx0W2UrMl09dGhpcy56LHRbZSszXT10aGlzLncsdH1mcm9tQnVmZmVyQXR0cmlidXRlKHQsZSxpKXtyZXR1cm4gaSE9PXZvaWQgMCYmY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3I0OiBvZmZzZXQgaGFzIGJlZW4gcmVtb3ZlZCBmcm9tIC5mcm9tQnVmZmVyQXR0cmlidXRlKCkuIiksdGhpcy54PXQuZ2V0WChlKSx0aGlzLnk9dC5nZXRZKGUpLHRoaXMuej10LmdldFooZSksdGhpcy53PXQuZ2V0VyhlKSx0aGlzfXJhbmRvbSgpe3JldHVybiB0aGlzLng9TWF0aC5yYW5kb20oKSx0aGlzLnk9TWF0aC5yYW5kb20oKSx0aGlzLno9TWF0aC5yYW5kb20oKSx0aGlzLnc9TWF0aC5yYW5kb20oKSx0aGlzfSpbU3ltYm9sLml0ZXJhdG9yXSgpe3lpZWxkIHRoaXMueCx5aWVsZCB0aGlzLnkseWllbGQgdGhpcy56LHlpZWxkIHRoaXMud319O1d0LnByb3RvdHlwZS5pc1ZlY3RvcjQ9ITA7dmFyIE5lPWNsYXNzIGV4dGVuZHMgSW57Y29uc3RydWN0b3IodCxlLGk9e30pe3N1cGVyKCksdGhpcy53aWR0aD10LHRoaXMuaGVpZ2h0PWUsdGhpcy5kZXB0aD0xLHRoaXMuc2Npc3Nvcj1uZXcgV3QoMCwwLHQsZSksdGhpcy5zY2lzc29yVGVzdD0hMSx0aGlzLnZpZXdwb3J0PW5ldyBXdCgwLDAsdCxlKSx0aGlzLnRleHR1cmU9bmV3IGFlKHZvaWQgMCxpLm1hcHBpbmcsaS53cmFwUyxpLndyYXBULGkubWFnRmlsdGVyLGkubWluRmlsdGVyLGkuZm9ybWF0LGkudHlwZSxpLmFuaXNvdHJvcHksaS5lbmNvZGluZyksdGhpcy50ZXh0dXJlLmlzUmVuZGVyVGFyZ2V0VGV4dHVyZT0hMCx0aGlzLnRleHR1cmUuaW1hZ2U9e3dpZHRoOnQsaGVpZ2h0OmUsZGVwdGg6MX0sdGhpcy50ZXh0dXJlLmdlbmVyYXRlTWlwbWFwcz1pLmdlbmVyYXRlTWlwbWFwcyE9PXZvaWQgMD9pLmdlbmVyYXRlTWlwbWFwczohMSx0aGlzLnRleHR1cmUuaW50ZXJuYWxGb3JtYXQ9aS5pbnRlcm5hbEZvcm1hdCE9PXZvaWQgMD9pLmludGVybmFsRm9ybWF0Om51bGwsdGhpcy50ZXh0dXJlLm1pbkZpbHRlcj1pLm1pbkZpbHRlciE9PXZvaWQgMD9pLm1pbkZpbHRlcjpiZSx0aGlzLmRlcHRoQnVmZmVyPWkuZGVwdGhCdWZmZXIhPT12b2lkIDA/aS5kZXB0aEJ1ZmZlcjohMCx0aGlzLnN0ZW5jaWxCdWZmZXI9aS5zdGVuY2lsQnVmZmVyIT09dm9pZCAwP2kuc3RlbmNpbEJ1ZmZlcjohMSx0aGlzLmRlcHRoVGV4dHVyZT1pLmRlcHRoVGV4dHVyZSE9PXZvaWQgMD9pLmRlcHRoVGV4dHVyZTpudWxsfXNldFRleHR1cmUodCl7dC5pbWFnZT17d2lkdGg6dGhpcy53aWR0aCxoZWlnaHQ6dGhpcy5oZWlnaHQsZGVwdGg6dGhpcy5kZXB0aH0sdGhpcy50ZXh0dXJlPXR9c2V0U2l6ZSh0LGUsaT0xKXsodGhpcy53aWR0aCE9PXR8fHRoaXMuaGVpZ2h0IT09ZXx8dGhpcy5kZXB0aCE9PWkpJiYodGhpcy53aWR0aD10LHRoaXMuaGVpZ2h0PWUsdGhpcy5kZXB0aD1pLHRoaXMudGV4dHVyZS5pbWFnZS53aWR0aD10LHRoaXMudGV4dHVyZS5pbWFnZS5oZWlnaHQ9ZSx0aGlzLnRleHR1cmUuaW1hZ2UuZGVwdGg9aSx0aGlzLmRpc3Bvc2UoKSksdGhpcy52aWV3cG9ydC5zZXQoMCwwLHQsZSksdGhpcy5zY2lzc29yLnNldCgwLDAsdCxlKX1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyl9Y29weSh0KXtyZXR1cm4gdGhpcy53aWR0aD10LndpZHRoLHRoaXMuaGVpZ2h0PXQuaGVpZ2h0LHRoaXMuZGVwdGg9dC5kZXB0aCx0aGlzLnZpZXdwb3J0LmNvcHkodC52aWV3cG9ydCksdGhpcy50ZXh0dXJlPXQudGV4dHVyZS5jbG9uZSgpLHRoaXMudGV4dHVyZS5pbWFnZT1PYmplY3QuYXNzaWduKHt9LHQudGV4dHVyZS5pbWFnZSksdGhpcy5kZXB0aEJ1ZmZlcj10LmRlcHRoQnVmZmVyLHRoaXMuc3RlbmNpbEJ1ZmZlcj10LnN0ZW5jaWxCdWZmZXIsdGhpcy5kZXB0aFRleHR1cmU9dC5kZXB0aFRleHR1cmUsdGhpc31kaXNwb3NlKCl7dGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJkaXNwb3NlIn0pfX07TmUucHJvdG90eXBlLmlzV2ViR0xSZW5kZXJUYXJnZXQ9ITA7dmFyIEd1PWNsYXNzIGV4dGVuZHMgTmV7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKHQsZSk7bGV0IHI9dGhpcy50ZXh0dXJlO3RoaXMudGV4dHVyZT1bXTtmb3IobGV0IHM9MDtzPGk7cysrKXRoaXMudGV4dHVyZVtzXT1yLmNsb25lKCl9c2V0U2l6ZSh0LGUsaT0xKXtpZih0aGlzLndpZHRoIT09dHx8dGhpcy5oZWlnaHQhPT1lfHx0aGlzLmRlcHRoIT09aSl7dGhpcy53aWR0aD10LHRoaXMuaGVpZ2h0PWUsdGhpcy5kZXB0aD1pO2ZvcihsZXQgcj0wLHM9dGhpcy50ZXh0dXJlLmxlbmd0aDtyPHM7cisrKXRoaXMudGV4dHVyZVtyXS5pbWFnZS53aWR0aD10LHRoaXMudGV4dHVyZVtyXS5pbWFnZS5oZWlnaHQ9ZSx0aGlzLnRleHR1cmVbcl0uaW1hZ2UuZGVwdGg9aTt0aGlzLmRpc3Bvc2UoKX1yZXR1cm4gdGhpcy52aWV3cG9ydC5zZXQoMCwwLHQsZSksdGhpcy5zY2lzc29yLnNldCgwLDAsdCxlKSx0aGlzfWNvcHkodCl7dGhpcy5kaXNwb3NlKCksdGhpcy53aWR0aD10LndpZHRoLHRoaXMuaGVpZ2h0PXQuaGVpZ2h0LHRoaXMuZGVwdGg9dC5kZXB0aCx0aGlzLnZpZXdwb3J0LnNldCgwLDAsdGhpcy53aWR0aCx0aGlzLmhlaWdodCksdGhpcy5zY2lzc29yLnNldCgwLDAsdGhpcy53aWR0aCx0aGlzLmhlaWdodCksdGhpcy5kZXB0aEJ1ZmZlcj10LmRlcHRoQnVmZmVyLHRoaXMuc3RlbmNpbEJ1ZmZlcj10LnN0ZW5jaWxCdWZmZXIsdGhpcy5kZXB0aFRleHR1cmU9dC5kZXB0aFRleHR1cmUsdGhpcy50ZXh0dXJlLmxlbmd0aD0wO2ZvcihsZXQgZT0wLGk9dC50ZXh0dXJlLmxlbmd0aDtlPGk7ZSsrKXRoaXMudGV4dHVyZVtlXT10LnRleHR1cmVbZV0uY2xvbmUoKTtyZXR1cm4gdGhpc319O0d1LnByb3RvdHlwZS5pc1dlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzPSEwO3ZhciBzbz1jbGFzcyBleHRlbmRzIE5le2NvbnN0cnVjdG9yKHQsZSxpPXt9KXtzdXBlcih0LGUsaSksdGhpcy5zYW1wbGVzPTQsdGhpcy5pZ25vcmVEZXB0aEZvck11bHRpc2FtcGxlQ29weT1pLmlnbm9yZURlcHRoIT09dm9pZCAwP2kuaWdub3JlRGVwdGg6ITAsdGhpcy51c2VSZW5kZXJUb1RleHR1cmU9aS51c2VSZW5kZXJUb1RleHR1cmUhPT12b2lkIDA/aS51c2VSZW5kZXJUb1RleHR1cmU6ITEsdGhpcy51c2VSZW5kZXJidWZmZXI9dGhpcy51c2VSZW5kZXJUb1RleHR1cmU9PT0hMX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5LmNhbGwodGhpcyx0KSx0aGlzLnNhbXBsZXM9dC5zYW1wbGVzLHRoaXMudXNlUmVuZGVyVG9UZXh0dXJlPXQudXNlUmVuZGVyVG9UZXh0dXJlLHRoaXMudXNlUmVuZGVyYnVmZmVyPXQudXNlUmVuZGVyYnVmZmVyLHRoaXN9fTtzby5wcm90b3R5cGUuaXNXZWJHTE11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0PSEwO3ZhciBFZT1jbGFzc3tjb25zdHJ1Y3Rvcih0PTAsZT0wLGk9MCxyPTEpe3RoaXMuX3g9dCx0aGlzLl95PWUsdGhpcy5fej1pLHRoaXMuX3c9cn1zdGF0aWMgc2xlcnAodCxlLGkscil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUXVhdGVybmlvbjogU3RhdGljIC5zbGVycCgpIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSBxbS5zbGVycFF1YXRlcm5pb25zKCBxYSwgcWIsIHQgKSBpbnN0ZWFkLiIpLGkuc2xlcnBRdWF0ZXJuaW9ucyh0LGUscil9c3RhdGljIHNsZXJwRmxhdCh0LGUsaSxyLHMsbyxhKXtsZXQgbD1pW3IrMF0sYz1pW3IrMV0sdT1pW3IrMl0saD1pW3IrM10sZj1zW28rMF0sZD1zW28rMV0sZz1zW28rMl0seD1zW28rM107aWYoYT09PTApe3RbZSswXT1sLHRbZSsxXT1jLHRbZSsyXT11LHRbZSszXT1oO3JldHVybn1pZihhPT09MSl7dFtlKzBdPWYsdFtlKzFdPWQsdFtlKzJdPWcsdFtlKzNdPXg7cmV0dXJufWlmKGghPT14fHxsIT09Znx8YyE9PWR8fHUhPT1nKXtsZXQgdj0xLWEsbT1sKmYrYypkK3UqZytoKngscD1tPj0wPzE6LTEsYj0xLW0qbTtpZihiPk51bWJlci5FUFNJTE9OKXtsZXQgUz1NYXRoLnNxcnQoYiksTD1NYXRoLmF0YW4yKFMsbSpwKTt2PU1hdGguc2luKHYqTCkvUyxhPU1hdGguc2luKGEqTCkvU31sZXQgXz1hKnA7aWYobD1sKnYrZipfLGM9Yyp2K2QqXyx1PXUqditnKl8saD1oKnYreCpfLHY9PT0xLWEpe2xldCBTPTEvTWF0aC5zcXJ0KGwqbCtjKmMrdSp1K2gqaCk7bCo9UyxjKj1TLHUqPVMsaCo9U319dFtlXT1sLHRbZSsxXT1jLHRbZSsyXT11LHRbZSszXT1ofXN0YXRpYyBtdWx0aXBseVF1YXRlcm5pb25zRmxhdCh0LGUsaSxyLHMsbyl7bGV0IGE9aVtyXSxsPWlbcisxXSxjPWlbcisyXSx1PWlbciszXSxoPXNbb10sZj1zW28rMV0sZD1zW28rMl0sZz1zW28rM107cmV0dXJuIHRbZV09YSpnK3UqaCtsKmQtYypmLHRbZSsxXT1sKmcrdSpmK2MqaC1hKmQsdFtlKzJdPWMqZyt1KmQrYSpmLWwqaCx0W2UrM109dSpnLWEqaC1sKmYtYypkLHR9Z2V0IHgoKXtyZXR1cm4gdGhpcy5feH1zZXQgeCh0KXt0aGlzLl94PXQsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpfWdldCB5KCl7cmV0dXJuIHRoaXMuX3l9c2V0IHkodCl7dGhpcy5feT10LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKX1nZXQgeigpe3JldHVybiB0aGlzLl96fXNldCB6KHQpe3RoaXMuX3o9dCx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCl9Z2V0IHcoKXtyZXR1cm4gdGhpcy5fd31zZXQgdyh0KXt0aGlzLl93PXQsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpfXNldCh0LGUsaSxyKXtyZXR1cm4gdGhpcy5feD10LHRoaXMuX3k9ZSx0aGlzLl96PWksdGhpcy5fdz1yLHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKHRoaXMuX3gsdGhpcy5feSx0aGlzLl96LHRoaXMuX3cpfWNvcHkodCl7cmV0dXJuIHRoaXMuX3g9dC54LHRoaXMuX3k9dC55LHRoaXMuX3o9dC56LHRoaXMuX3c9dC53LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21FdWxlcih0LGUpe2lmKCEodCYmdC5pc0V1bGVyKSl0aHJvdyBuZXcgRXJyb3IoIlRIUkVFLlF1YXRlcm5pb246IC5zZXRGcm9tRXVsZXIoKSBub3cgZXhwZWN0cyBhbiBFdWxlciByb3RhdGlvbiByYXRoZXIgdGhhbiBhIFZlY3RvcjMgYW5kIG9yZGVyLiIpO2xldCBpPXQuX3gscj10Ll95LHM9dC5feixvPXQuX29yZGVyLGE9TWF0aC5jb3MsbD1NYXRoLnNpbixjPWEoaS8yKSx1PWEoci8yKSxoPWEocy8yKSxmPWwoaS8yKSxkPWwoci8yKSxnPWwocy8yKTtzd2l0Y2gobyl7Y2FzZSJYWVoiOnRoaXMuX3g9Zip1KmgrYypkKmcsdGhpcy5feT1jKmQqaC1mKnUqZyx0aGlzLl96PWMqdSpnK2YqZCpoLHRoaXMuX3c9Yyp1KmgtZipkKmc7YnJlYWs7Y2FzZSJZWFoiOnRoaXMuX3g9Zip1KmgrYypkKmcsdGhpcy5feT1jKmQqaC1mKnUqZyx0aGlzLl96PWMqdSpnLWYqZCpoLHRoaXMuX3c9Yyp1KmgrZipkKmc7YnJlYWs7Y2FzZSJaWFkiOnRoaXMuX3g9Zip1KmgtYypkKmcsdGhpcy5feT1jKmQqaCtmKnUqZyx0aGlzLl96PWMqdSpnK2YqZCpoLHRoaXMuX3c9Yyp1KmgtZipkKmc7YnJlYWs7Y2FzZSJaWVgiOnRoaXMuX3g9Zip1KmgtYypkKmcsdGhpcy5feT1jKmQqaCtmKnUqZyx0aGlzLl96PWMqdSpnLWYqZCpoLHRoaXMuX3c9Yyp1KmgrZipkKmc7YnJlYWs7Y2FzZSJZWlgiOnRoaXMuX3g9Zip1KmgrYypkKmcsdGhpcy5feT1jKmQqaCtmKnUqZyx0aGlzLl96PWMqdSpnLWYqZCpoLHRoaXMuX3c9Yyp1KmgtZipkKmc7YnJlYWs7Y2FzZSJYWlkiOnRoaXMuX3g9Zip1KmgtYypkKmcsdGhpcy5feT1jKmQqaC1mKnUqZyx0aGlzLl96PWMqdSpnK2YqZCpoLHRoaXMuX3c9Yyp1KmgrZipkKmc7YnJlYWs7ZGVmYXVsdDpjb25zb2xlLndhcm4oIlRIUkVFLlF1YXRlcm5pb246IC5zZXRGcm9tRXVsZXIoKSBlbmNvdW50ZXJlZCBhbiB1bmtub3duIG9yZGVyOiAiK28pfXJldHVybiBlIT09ITEmJnRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21BeGlzQW5nbGUodCxlKXtsZXQgaT1lLzIscj1NYXRoLnNpbihpKTtyZXR1cm4gdGhpcy5feD10Lngqcix0aGlzLl95PXQueSpyLHRoaXMuX3o9dC56KnIsdGhpcy5fdz1NYXRoLmNvcyhpKSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31zZXRGcm9tUm90YXRpb25NYXRyaXgodCl7bGV0IGU9dC5lbGVtZW50cyxpPWVbMF0scj1lWzRdLHM9ZVs4XSxvPWVbMV0sYT1lWzVdLGw9ZVs5XSxjPWVbMl0sdT1lWzZdLGg9ZVsxMF0sZj1pK2EraDtpZihmPjApe2xldCBkPS41L01hdGguc3FydChmKzEpO3RoaXMuX3c9LjI1L2QsdGhpcy5feD0odS1sKSpkLHRoaXMuX3k9KHMtYykqZCx0aGlzLl96PShvLXIpKmR9ZWxzZSBpZihpPmEmJmk+aCl7bGV0IGQ9MipNYXRoLnNxcnQoMStpLWEtaCk7dGhpcy5fdz0odS1sKS9kLHRoaXMuX3g9LjI1KmQsdGhpcy5feT0ocitvKS9kLHRoaXMuX3o9KHMrYykvZH1lbHNlIGlmKGE+aCl7bGV0IGQ9MipNYXRoLnNxcnQoMSthLWktaCk7dGhpcy5fdz0ocy1jKS9kLHRoaXMuX3g9KHIrbykvZCx0aGlzLl95PS4yNSpkLHRoaXMuX3o9KGwrdSkvZH1lbHNle2xldCBkPTIqTWF0aC5zcXJ0KDEraC1pLWEpO3RoaXMuX3c9KG8tcikvZCx0aGlzLl94PShzK2MpL2QsdGhpcy5feT0obCt1KS9kLHRoaXMuX3o9LjI1KmR9cmV0dXJuIHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21Vbml0VmVjdG9ycyh0LGUpe2xldCBpPXQuZG90KGUpKzE7cmV0dXJuIGk8TnVtYmVyLkVQU0lMT04/KGk9MCxNYXRoLmFicyh0LngpPk1hdGguYWJzKHQueik/KHRoaXMuX3g9LXQueSx0aGlzLl95PXQueCx0aGlzLl96PTAsdGhpcy5fdz1pKToodGhpcy5feD0wLHRoaXMuX3k9LXQueix0aGlzLl96PXQueSx0aGlzLl93PWkpKToodGhpcy5feD10LnkqZS56LXQueiplLnksdGhpcy5feT10LnoqZS54LXQueCplLnosdGhpcy5fej10LngqZS55LXQueSplLngsdGhpcy5fdz1pKSx0aGlzLm5vcm1hbGl6ZSgpfWFuZ2xlVG8odCl7cmV0dXJuIDIqTWF0aC5hY29zKE1hdGguYWJzKEllKHRoaXMuZG90KHQpLC0xLDEpKSl9cm90YXRlVG93YXJkcyh0LGUpe2xldCBpPXRoaXMuYW5nbGVUbyh0KTtpZihpPT09MClyZXR1cm4gdGhpcztsZXQgcj1NYXRoLm1pbigxLGUvaSk7cmV0dXJuIHRoaXMuc2xlcnAodCxyKSx0aGlzfWlkZW50aXR5KCl7cmV0dXJuIHRoaXMuc2V0KDAsMCwwLDEpfWludmVydCgpe3JldHVybiB0aGlzLmNvbmp1Z2F0ZSgpfWNvbmp1Z2F0ZSgpe3JldHVybiB0aGlzLl94Kj0tMSx0aGlzLl95Kj0tMSx0aGlzLl96Kj0tMSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31kb3QodCl7cmV0dXJuIHRoaXMuX3gqdC5feCt0aGlzLl95KnQuX3krdGhpcy5feip0Ll96K3RoaXMuX3cqdC5fd31sZW5ndGhTcSgpe3JldHVybiB0aGlzLl94KnRoaXMuX3grdGhpcy5feSp0aGlzLl95K3RoaXMuX3oqdGhpcy5feit0aGlzLl93KnRoaXMuX3d9bGVuZ3RoKCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLl94KnRoaXMuX3grdGhpcy5feSp0aGlzLl95K3RoaXMuX3oqdGhpcy5feit0aGlzLl93KnRoaXMuX3cpfW5vcm1hbGl6ZSgpe2xldCB0PXRoaXMubGVuZ3RoKCk7cmV0dXJuIHQ9PT0wPyh0aGlzLl94PTAsdGhpcy5feT0wLHRoaXMuX3o9MCx0aGlzLl93PTEpOih0PTEvdCx0aGlzLl94PXRoaXMuX3gqdCx0aGlzLl95PXRoaXMuX3kqdCx0aGlzLl96PXRoaXMuX3oqdCx0aGlzLl93PXRoaXMuX3cqdCksdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9bXVsdGlwbHkodCxlKXtyZXR1cm4gZSE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5RdWF0ZXJuaW9uOiAubXVsdGlwbHkoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5tdWx0aXBseVF1YXRlcm5pb25zKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLm11bHRpcGx5UXVhdGVybmlvbnModCxlKSk6dGhpcy5tdWx0aXBseVF1YXRlcm5pb25zKHRoaXMsdCl9cHJlbXVsdGlwbHkodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlRdWF0ZXJuaW9ucyh0LHRoaXMpfW11bHRpcGx5UXVhdGVybmlvbnModCxlKXtsZXQgaT10Ll94LHI9dC5feSxzPXQuX3osbz10Ll93LGE9ZS5feCxsPWUuX3ksYz1lLl96LHU9ZS5fdztyZXR1cm4gdGhpcy5feD1pKnUrbyphK3IqYy1zKmwsdGhpcy5feT1yKnUrbypsK3MqYS1pKmMsdGhpcy5fej1zKnUrbypjK2kqbC1yKmEsdGhpcy5fdz1vKnUtaSphLXIqbC1zKmMsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9c2xlcnAodCxlKXtpZihlPT09MClyZXR1cm4gdGhpcztpZihlPT09MSlyZXR1cm4gdGhpcy5jb3B5KHQpO2xldCBpPXRoaXMuX3gscj10aGlzLl95LHM9dGhpcy5feixvPXRoaXMuX3csYT1vKnQuX3craSp0Ll94K3IqdC5feStzKnQuX3o7aWYoYTwwPyh0aGlzLl93PS10Ll93LHRoaXMuX3g9LXQuX3gsdGhpcy5feT0tdC5feSx0aGlzLl96PS10Ll96LGE9LWEpOnRoaXMuY29weSh0KSxhPj0xKXJldHVybiB0aGlzLl93PW8sdGhpcy5feD1pLHRoaXMuX3k9cix0aGlzLl96PXMsdGhpcztsZXQgbD0xLWEqYTtpZihsPD1OdW1iZXIuRVBTSUxPTil7bGV0IGQ9MS1lO3JldHVybiB0aGlzLl93PWQqbytlKnRoaXMuX3csdGhpcy5feD1kKmkrZSp0aGlzLl94LHRoaXMuX3k9ZCpyK2UqdGhpcy5feSx0aGlzLl96PWQqcytlKnRoaXMuX3osdGhpcy5ub3JtYWxpemUoKSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31sZXQgYz1NYXRoLnNxcnQobCksdT1NYXRoLmF0YW4yKGMsYSksaD1NYXRoLnNpbigoMS1lKSp1KS9jLGY9TWF0aC5zaW4oZSp1KS9jO3JldHVybiB0aGlzLl93PW8qaCt0aGlzLl93KmYsdGhpcy5feD1pKmgrdGhpcy5feCpmLHRoaXMuX3k9cipoK3RoaXMuX3kqZix0aGlzLl96PXMqaCt0aGlzLl96KmYsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9c2xlcnBRdWF0ZXJuaW9ucyh0LGUsaSl7cmV0dXJuIHRoaXMuY29weSh0KS5zbGVycChlLGkpfXJhbmRvbSgpe2xldCB0PU1hdGgucmFuZG9tKCksZT1NYXRoLnNxcnQoMS10KSxpPU1hdGguc3FydCh0KSxyPTIqTWF0aC5QSSpNYXRoLnJhbmRvbSgpLHM9MipNYXRoLlBJKk1hdGgucmFuZG9tKCk7cmV0dXJuIHRoaXMuc2V0KGUqTWF0aC5jb3MociksaSpNYXRoLnNpbihzKSxpKk1hdGguY29zKHMpLGUqTWF0aC5zaW4ocikpfWVxdWFscyh0KXtyZXR1cm4gdC5feD09PXRoaXMuX3gmJnQuX3k9PT10aGlzLl95JiZ0Ll96PT09dGhpcy5feiYmdC5fdz09PXRoaXMuX3d9ZnJvbUFycmF5KHQsZT0wKXtyZXR1cm4gdGhpcy5feD10W2VdLHRoaXMuX3k9dFtlKzFdLHRoaXMuX3o9dFtlKzJdLHRoaXMuX3c9dFtlKzNdLHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXRvQXJyYXkodD1bXSxlPTApe3JldHVybiB0W2VdPXRoaXMuX3gsdFtlKzFdPXRoaXMuX3ksdFtlKzJdPXRoaXMuX3osdFtlKzNdPXRoaXMuX3csdH1mcm9tQnVmZmVyQXR0cmlidXRlKHQsZSl7cmV0dXJuIHRoaXMuX3g9dC5nZXRYKGUpLHRoaXMuX3k9dC5nZXRZKGUpLHRoaXMuX3o9dC5nZXRaKGUpLHRoaXMuX3c9dC5nZXRXKGUpLHRoaXN9X29uQ2hhbmdlKHQpe3JldHVybiB0aGlzLl9vbkNoYW5nZUNhbGxiYWNrPXQsdGhpc31fb25DaGFuZ2VDYWxsYmFjaygpe319O0VlLnByb3RvdHlwZS5pc1F1YXRlcm5pb249ITA7dmFyIFQ9Y2xhc3N7Y29uc3RydWN0b3IodD0wLGU9MCxpPTApe3RoaXMueD10LHRoaXMueT1lLHRoaXMuej1pfXNldCh0LGUsaSl7cmV0dXJuIGk9PT12b2lkIDAmJihpPXRoaXMueiksdGhpcy54PXQsdGhpcy55PWUsdGhpcy56PWksdGhpc31zZXRTY2FsYXIodCl7cmV0dXJuIHRoaXMueD10LHRoaXMueT10LHRoaXMuej10LHRoaXN9c2V0WCh0KXtyZXR1cm4gdGhpcy54PXQsdGhpc31zZXRZKHQpe3JldHVybiB0aGlzLnk9dCx0aGlzfXNldFoodCl7cmV0dXJuIHRoaXMuej10LHRoaXN9c2V0Q29tcG9uZW50KHQsZSl7c3dpdGNoKHQpe2Nhc2UgMDp0aGlzLng9ZTticmVhaztjYXNlIDE6dGhpcy55PWU7YnJlYWs7Y2FzZSAyOnRoaXMuej1lO2JyZWFrO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKCJpbmRleCBpcyBvdXQgb2YgcmFuZ2U6ICIrdCl9cmV0dXJuIHRoaXN9Z2V0Q29tcG9uZW50KHQpe3N3aXRjaCh0KXtjYXNlIDA6cmV0dXJuIHRoaXMueDtjYXNlIDE6cmV0dXJuIHRoaXMueTtjYXNlIDI6cmV0dXJuIHRoaXMuejtkZWZhdWx0OnRocm93IG5ldyBFcnJvcigiaW5kZXggaXMgb3V0IG9mIHJhbmdlOiAiK3QpfX1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3Rvcih0aGlzLngsdGhpcy55LHRoaXMueil9Y29weSh0KXtyZXR1cm4gdGhpcy54PXQueCx0aGlzLnk9dC55LHRoaXMuej10LnosdGhpc31hZGQodCxlKXtyZXR1cm4gZSE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuYWRkKCkgbm93IG9ubHkgYWNjZXB0cyBvbmUgYXJndW1lbnQuIFVzZSAuYWRkVmVjdG9ycyggYSwgYiApIGluc3RlYWQuIiksdGhpcy5hZGRWZWN0b3JzKHQsZSkpOih0aGlzLngrPXQueCx0aGlzLnkrPXQueSx0aGlzLnorPXQueix0aGlzKX1hZGRTY2FsYXIodCl7cmV0dXJuIHRoaXMueCs9dCx0aGlzLnkrPXQsdGhpcy56Kz10LHRoaXN9YWRkVmVjdG9ycyh0LGUpe3JldHVybiB0aGlzLng9dC54K2UueCx0aGlzLnk9dC55K2UueSx0aGlzLno9dC56K2Uueix0aGlzfWFkZFNjYWxlZFZlY3Rvcih0LGUpe3JldHVybiB0aGlzLngrPXQueCplLHRoaXMueSs9dC55KmUsdGhpcy56Kz10LnoqZSx0aGlzfXN1Yih0LGUpe3JldHVybiBlIT09dm9pZCAwPyhjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5zdWIoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5zdWJWZWN0b3JzKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLnN1YlZlY3RvcnModCxlKSk6KHRoaXMueC09dC54LHRoaXMueS09dC55LHRoaXMuei09dC56LHRoaXMpfXN1YlNjYWxhcih0KXtyZXR1cm4gdGhpcy54LT10LHRoaXMueS09dCx0aGlzLnotPXQsdGhpc31zdWJWZWN0b3JzKHQsZSl7cmV0dXJuIHRoaXMueD10LngtZS54LHRoaXMueT10LnktZS55LHRoaXMuej10LnotZS56LHRoaXN9bXVsdGlwbHkodCxlKXtyZXR1cm4gZSE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAubXVsdGlwbHkoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5tdWx0aXBseVZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMubXVsdGlwbHlWZWN0b3JzKHQsZSkpOih0aGlzLngqPXQueCx0aGlzLnkqPXQueSx0aGlzLnoqPXQueix0aGlzKX1tdWx0aXBseVNjYWxhcih0KXtyZXR1cm4gdGhpcy54Kj10LHRoaXMueSo9dCx0aGlzLnoqPXQsdGhpc31tdWx0aXBseVZlY3RvcnModCxlKXtyZXR1cm4gdGhpcy54PXQueCplLngsdGhpcy55PXQueSplLnksdGhpcy56PXQueiplLnosdGhpc31hcHBseUV1bGVyKHQpe3JldHVybiB0JiZ0LmlzRXVsZXJ8fGNvbnNvbGUuZXJyb3IoIlRIUkVFLlZlY3RvcjM6IC5hcHBseUV1bGVyKCkgbm93IGV4cGVjdHMgYW4gRXVsZXIgcm90YXRpb24gcmF0aGVyIHRoYW4gYSBWZWN0b3IzIGFuZCBvcmRlci4iKSx0aGlzLmFwcGx5UXVhdGVybmlvbihyZy5zZXRGcm9tRXVsZXIodCkpfWFwcGx5QXhpc0FuZ2xlKHQsZSl7cmV0dXJuIHRoaXMuYXBwbHlRdWF0ZXJuaW9uKHJnLnNldEZyb21BeGlzQW5nbGUodCxlKSl9YXBwbHlNYXRyaXgzKHQpe2xldCBlPXRoaXMueCxpPXRoaXMueSxyPXRoaXMueixzPXQuZWxlbWVudHM7cmV0dXJuIHRoaXMueD1zWzBdKmUrc1szXSppK3NbNl0qcix0aGlzLnk9c1sxXSplK3NbNF0qaStzWzddKnIsdGhpcy56PXNbMl0qZStzWzVdKmkrc1s4XSpyLHRoaXN9YXBwbHlOb3JtYWxNYXRyaXgodCl7cmV0dXJuIHRoaXMuYXBwbHlNYXRyaXgzKHQpLm5vcm1hbGl6ZSgpfWFwcGx5TWF0cml4NCh0KXtsZXQgZT10aGlzLngsaT10aGlzLnkscj10aGlzLnoscz10LmVsZW1lbnRzLG89MS8oc1szXSplK3NbN10qaStzWzExXSpyK3NbMTVdKTtyZXR1cm4gdGhpcy54PShzWzBdKmUrc1s0XSppK3NbOF0qcitzWzEyXSkqbyx0aGlzLnk9KHNbMV0qZStzWzVdKmkrc1s5XSpyK3NbMTNdKSpvLHRoaXMuej0oc1syXSplK3NbNl0qaStzWzEwXSpyK3NbMTRdKSpvLHRoaXN9YXBwbHlRdWF0ZXJuaW9uKHQpe2xldCBlPXRoaXMueCxpPXRoaXMueSxyPXRoaXMueixzPXQueCxvPXQueSxhPXQueixsPXQudyxjPWwqZStvKnItYSppLHU9bCppK2EqZS1zKnIsaD1sKnIrcyppLW8qZSxmPS1zKmUtbyppLWEqcjtyZXR1cm4gdGhpcy54PWMqbCtmKi1zK3UqLWEtaCotbyx0aGlzLnk9dSpsK2YqLW8raCotcy1jKi1hLHRoaXMuej1oKmwrZiotYStjKi1vLXUqLXMsdGhpc31wcm9qZWN0KHQpe3JldHVybiB0aGlzLmFwcGx5TWF0cml4NCh0Lm1hdHJpeFdvcmxkSW52ZXJzZSkuYXBwbHlNYXRyaXg0KHQucHJvamVjdGlvbk1hdHJpeCl9dW5wcm9qZWN0KHQpe3JldHVybiB0aGlzLmFwcGx5TWF0cml4NCh0LnByb2plY3Rpb25NYXRyaXhJbnZlcnNlKS5hcHBseU1hdHJpeDQodC5tYXRyaXhXb3JsZCl9dHJhbnNmb3JtRGlyZWN0aW9uKHQpe2xldCBlPXRoaXMueCxpPXRoaXMueSxyPXRoaXMueixzPXQuZWxlbWVudHM7cmV0dXJuIHRoaXMueD1zWzBdKmUrc1s0XSppK3NbOF0qcix0aGlzLnk9c1sxXSplK3NbNV0qaStzWzldKnIsdGhpcy56PXNbMl0qZStzWzZdKmkrc1sxMF0qcix0aGlzLm5vcm1hbGl6ZSgpfWRpdmlkZSh0KXtyZXR1cm4gdGhpcy54Lz10LngsdGhpcy55Lz10LnksdGhpcy56Lz10LnosdGhpc31kaXZpZGVTY2FsYXIodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlTY2FsYXIoMS90KX1taW4odCl7cmV0dXJuIHRoaXMueD1NYXRoLm1pbih0aGlzLngsdC54KSx0aGlzLnk9TWF0aC5taW4odGhpcy55LHQueSksdGhpcy56PU1hdGgubWluKHRoaXMueix0LnopLHRoaXN9bWF4KHQpe3JldHVybiB0aGlzLng9TWF0aC5tYXgodGhpcy54LHQueCksdGhpcy55PU1hdGgubWF4KHRoaXMueSx0LnkpLHRoaXMuej1NYXRoLm1heCh0aGlzLnosdC56KSx0aGlzfWNsYW1wKHQsZSl7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0LngsTWF0aC5taW4oZS54LHRoaXMueCkpLHRoaXMueT1NYXRoLm1heCh0LnksTWF0aC5taW4oZS55LHRoaXMueSkpLHRoaXMuej1NYXRoLm1heCh0LnosTWF0aC5taW4oZS56LHRoaXMueikpLHRoaXN9Y2xhbXBTY2FsYXIodCxlKXtyZXR1cm4gdGhpcy54PU1hdGgubWF4KHQsTWF0aC5taW4oZSx0aGlzLngpKSx0aGlzLnk9TWF0aC5tYXgodCxNYXRoLm1pbihlLHRoaXMueSkpLHRoaXMuej1NYXRoLm1heCh0LE1hdGgubWluKGUsdGhpcy56KSksdGhpc31jbGFtcExlbmd0aCh0LGUpe2xldCBpPXRoaXMubGVuZ3RoKCk7cmV0dXJuIHRoaXMuZGl2aWRlU2NhbGFyKGl8fDEpLm11bHRpcGx5U2NhbGFyKE1hdGgubWF4KHQsTWF0aC5taW4oZSxpKSkpfWZsb29yKCl7cmV0dXJuIHRoaXMueD1NYXRoLmZsb29yKHRoaXMueCksdGhpcy55PU1hdGguZmxvb3IodGhpcy55KSx0aGlzLno9TWF0aC5mbG9vcih0aGlzLnopLHRoaXN9Y2VpbCgpe3JldHVybiB0aGlzLng9TWF0aC5jZWlsKHRoaXMueCksdGhpcy55PU1hdGguY2VpbCh0aGlzLnkpLHRoaXMuej1NYXRoLmNlaWwodGhpcy56KSx0aGlzfXJvdW5kKCl7cmV0dXJuIHRoaXMueD1NYXRoLnJvdW5kKHRoaXMueCksdGhpcy55PU1hdGgucm91bmQodGhpcy55KSx0aGlzLno9TWF0aC5yb3VuZCh0aGlzLnopLHRoaXN9cm91bmRUb1plcm8oKXtyZXR1cm4gdGhpcy54PXRoaXMueDwwP01hdGguY2VpbCh0aGlzLngpOk1hdGguZmxvb3IodGhpcy54KSx0aGlzLnk9dGhpcy55PDA/TWF0aC5jZWlsKHRoaXMueSk6TWF0aC5mbG9vcih0aGlzLnkpLHRoaXMuej10aGlzLno8MD9NYXRoLmNlaWwodGhpcy56KTpNYXRoLmZsb29yKHRoaXMueiksdGhpc31uZWdhdGUoKXtyZXR1cm4gdGhpcy54PS10aGlzLngsdGhpcy55PS10aGlzLnksdGhpcy56PS10aGlzLnosdGhpc31kb3QodCl7cmV0dXJuIHRoaXMueCp0LngrdGhpcy55KnQueSt0aGlzLnoqdC56fWxlbmd0aFNxKCl7cmV0dXJuIHRoaXMueCp0aGlzLngrdGhpcy55KnRoaXMueSt0aGlzLnoqdGhpcy56fWxlbmd0aCgpe3JldHVybiBNYXRoLnNxcnQodGhpcy54KnRoaXMueCt0aGlzLnkqdGhpcy55K3RoaXMueip0aGlzLnopfW1hbmhhdHRhbkxlbmd0aCgpe3JldHVybiBNYXRoLmFicyh0aGlzLngpK01hdGguYWJzKHRoaXMueSkrTWF0aC5hYnModGhpcy56KX1ub3JtYWxpemUoKXtyZXR1cm4gdGhpcy5kaXZpZGVTY2FsYXIodGhpcy5sZW5ndGgoKXx8MSl9c2V0TGVuZ3RoKHQpe3JldHVybiB0aGlzLm5vcm1hbGl6ZSgpLm11bHRpcGx5U2NhbGFyKHQpfWxlcnAodCxlKXtyZXR1cm4gdGhpcy54Kz0odC54LXRoaXMueCkqZSx0aGlzLnkrPSh0LnktdGhpcy55KSplLHRoaXMueis9KHQuei10aGlzLnopKmUsdGhpc31sZXJwVmVjdG9ycyh0LGUsaSl7cmV0dXJuIHRoaXMueD10LngrKGUueC10LngpKmksdGhpcy55PXQueSsoZS55LXQueSkqaSx0aGlzLno9dC56KyhlLnotdC56KSppLHRoaXN9Y3Jvc3ModCxlKXtyZXR1cm4gZSE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuY3Jvc3MoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5jcm9zc1ZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMuY3Jvc3NWZWN0b3JzKHQsZSkpOnRoaXMuY3Jvc3NWZWN0b3JzKHRoaXMsdCl9Y3Jvc3NWZWN0b3JzKHQsZSl7bGV0IGk9dC54LHI9dC55LHM9dC56LG89ZS54LGE9ZS55LGw9ZS56O3JldHVybiB0aGlzLng9cipsLXMqYSx0aGlzLnk9cypvLWkqbCx0aGlzLno9aSphLXIqbyx0aGlzfXByb2plY3RPblZlY3Rvcih0KXtsZXQgZT10Lmxlbmd0aFNxKCk7aWYoZT09PTApcmV0dXJuIHRoaXMuc2V0KDAsMCwwKTtsZXQgaT10LmRvdCh0aGlzKS9lO3JldHVybiB0aGlzLmNvcHkodCkubXVsdGlwbHlTY2FsYXIoaSl9cHJvamVjdE9uUGxhbmUodCl7cmV0dXJuIGx1LmNvcHkodGhpcykucHJvamVjdE9uVmVjdG9yKHQpLHRoaXMuc3ViKGx1KX1yZWZsZWN0KHQpe3JldHVybiB0aGlzLnN1YihsdS5jb3B5KHQpLm11bHRpcGx5U2NhbGFyKDIqdGhpcy5kb3QodCkpKX1hbmdsZVRvKHQpe2xldCBlPU1hdGguc3FydCh0aGlzLmxlbmd0aFNxKCkqdC5sZW5ndGhTcSgpKTtpZihlPT09MClyZXR1cm4gTWF0aC5QSS8yO2xldCBpPXRoaXMuZG90KHQpL2U7cmV0dXJuIE1hdGguYWNvcyhJZShpLC0xLDEpKX1kaXN0YW5jZVRvKHQpe3JldHVybiBNYXRoLnNxcnQodGhpcy5kaXN0YW5jZVRvU3F1YXJlZCh0KSl9ZGlzdGFuY2VUb1NxdWFyZWQodCl7bGV0IGU9dGhpcy54LXQueCxpPXRoaXMueS10Lnkscj10aGlzLnotdC56O3JldHVybiBlKmUraSppK3Iqcn1tYW5oYXR0YW5EaXN0YW5jZVRvKHQpe3JldHVybiBNYXRoLmFicyh0aGlzLngtdC54KStNYXRoLmFicyh0aGlzLnktdC55KStNYXRoLmFicyh0aGlzLnotdC56KX1zZXRGcm9tU3BoZXJpY2FsKHQpe3JldHVybiB0aGlzLnNldEZyb21TcGhlcmljYWxDb29yZHModC5yYWRpdXMsdC5waGksdC50aGV0YSl9c2V0RnJvbVNwaGVyaWNhbENvb3Jkcyh0LGUsaSl7bGV0IHI9TWF0aC5zaW4oZSkqdDtyZXR1cm4gdGhpcy54PXIqTWF0aC5zaW4oaSksdGhpcy55PU1hdGguY29zKGUpKnQsdGhpcy56PXIqTWF0aC5jb3MoaSksdGhpc31zZXRGcm9tQ3lsaW5kcmljYWwodCl7cmV0dXJuIHRoaXMuc2V0RnJvbUN5bGluZHJpY2FsQ29vcmRzKHQucmFkaXVzLHQudGhldGEsdC55KX1zZXRGcm9tQ3lsaW5kcmljYWxDb29yZHModCxlLGkpe3JldHVybiB0aGlzLng9dCpNYXRoLnNpbihlKSx0aGlzLnk9aSx0aGlzLno9dCpNYXRoLmNvcyhlKSx0aGlzfXNldEZyb21NYXRyaXhQb3NpdGlvbih0KXtsZXQgZT10LmVsZW1lbnRzO3JldHVybiB0aGlzLng9ZVsxMl0sdGhpcy55PWVbMTNdLHRoaXMuej1lWzE0XSx0aGlzfXNldEZyb21NYXRyaXhTY2FsZSh0KXtsZXQgZT10aGlzLnNldEZyb21NYXRyaXhDb2x1bW4odCwwKS5sZW5ndGgoKSxpPXRoaXMuc2V0RnJvbU1hdHJpeENvbHVtbih0LDEpLmxlbmd0aCgpLHI9dGhpcy5zZXRGcm9tTWF0cml4Q29sdW1uKHQsMikubGVuZ3RoKCk7cmV0dXJuIHRoaXMueD1lLHRoaXMueT1pLHRoaXMuej1yLHRoaXN9c2V0RnJvbU1hdHJpeENvbHVtbih0LGUpe3JldHVybiB0aGlzLmZyb21BcnJheSh0LmVsZW1lbnRzLGUqNCl9c2V0RnJvbU1hdHJpeDNDb2x1bW4odCxlKXtyZXR1cm4gdGhpcy5mcm9tQXJyYXkodC5lbGVtZW50cyxlKjMpfWVxdWFscyh0KXtyZXR1cm4gdC54PT09dGhpcy54JiZ0Lnk9PT10aGlzLnkmJnQuej09PXRoaXMuen1mcm9tQXJyYXkodCxlPTApe3JldHVybiB0aGlzLng9dFtlXSx0aGlzLnk9dFtlKzFdLHRoaXMuej10W2UrMl0sdGhpc310b0FycmF5KHQ9W10sZT0wKXtyZXR1cm4gdFtlXT10aGlzLngsdFtlKzFdPXRoaXMueSx0W2UrMl09dGhpcy56LHR9ZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LGUsaSl7cmV0dXJuIGkhPT12b2lkIDAmJmNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogb2Zmc2V0IGhhcyBiZWVuIHJlbW92ZWQgZnJvbSAuZnJvbUJ1ZmZlckF0dHJpYnV0ZSgpLiIpLHRoaXMueD10LmdldFgoZSksdGhpcy55PXQuZ2V0WShlKSx0aGlzLno9dC5nZXRaKGUpLHRoaXN9cmFuZG9tKCl7cmV0dXJuIHRoaXMueD1NYXRoLnJhbmRvbSgpLHRoaXMueT1NYXRoLnJhbmRvbSgpLHRoaXMuej1NYXRoLnJhbmRvbSgpLHRoaXN9cmFuZG9tRGlyZWN0aW9uKCl7bGV0IHQ9KE1hdGgucmFuZG9tKCktLjUpKjIsZT1NYXRoLnJhbmRvbSgpKk1hdGguUEkqMixpPU1hdGguc3FydCgxLWNmKHQsMikpO3JldHVybiB0aGlzLng9aSpNYXRoLmNvcyhlKSx0aGlzLnk9aSpNYXRoLnNpbihlKSx0aGlzLno9dCx0aGlzfSpbU3ltYm9sLml0ZXJhdG9yXSgpe3lpZWxkIHRoaXMueCx5aWVsZCB0aGlzLnkseWllbGQgdGhpcy56fX07VC5wcm90b3R5cGUuaXNWZWN0b3IzPSEwO3ZhciBsdT1uZXcgVCxyZz1uZXcgRWUsR2U9Y2xhc3N7Y29uc3RydWN0b3IodD1uZXcgVCgxLzAsMS8wLDEvMCksZT1uZXcgVCgtMS8wLC0xLzAsLTEvMCkpe3RoaXMubWluPXQsdGhpcy5tYXg9ZX1zZXQodCxlKXtyZXR1cm4gdGhpcy5taW4uY29weSh0KSx0aGlzLm1heC5jb3B5KGUpLHRoaXN9c2V0RnJvbUFycmF5KHQpe2xldCBlPTEvMCxpPTEvMCxyPTEvMCxzPS0xLzAsbz0tMS8wLGE9LTEvMDtmb3IobGV0IGw9MCxjPXQubGVuZ3RoO2w8YztsKz0zKXtsZXQgdT10W2xdLGg9dFtsKzFdLGY9dFtsKzJdO3U8ZSYmKGU9dSksaDxpJiYoaT1oKSxmPHImJihyPWYpLHU+cyYmKHM9dSksaD5vJiYobz1oKSxmPmEmJihhPWYpfXJldHVybiB0aGlzLm1pbi5zZXQoZSxpLHIpLHRoaXMubWF4LnNldChzLG8sYSksdGhpc31zZXRGcm9tQnVmZmVyQXR0cmlidXRlKHQpe2xldCBlPTEvMCxpPTEvMCxyPTEvMCxzPS0xLzAsbz0tMS8wLGE9LTEvMDtmb3IobGV0IGw9MCxjPXQuY291bnQ7bDxjO2wrKyl7bGV0IHU9dC5nZXRYKGwpLGg9dC5nZXRZKGwpLGY9dC5nZXRaKGwpO3U8ZSYmKGU9dSksaDxpJiYoaT1oKSxmPHImJihyPWYpLHU+cyYmKHM9dSksaD5vJiYobz1oKSxmPmEmJihhPWYpfXJldHVybiB0aGlzLm1pbi5zZXQoZSxpLHIpLHRoaXMubWF4LnNldChzLG8sYSksdGhpc31zZXRGcm9tUG9pbnRzKHQpe3RoaXMubWFrZUVtcHR5KCk7Zm9yKGxldCBlPTAsaT10Lmxlbmd0aDtlPGk7ZSsrKXRoaXMuZXhwYW5kQnlQb2ludCh0W2VdKTtyZXR1cm4gdGhpc31zZXRGcm9tQ2VudGVyQW5kU2l6ZSh0LGUpe2xldCBpPUlpLmNvcHkoZSkubXVsdGlwbHlTY2FsYXIoLjUpO3JldHVybiB0aGlzLm1pbi5jb3B5KHQpLnN1YihpKSx0aGlzLm1heC5jb3B5KHQpLmFkZChpKSx0aGlzfXNldEZyb21PYmplY3QodCxlPSExKXtyZXR1cm4gdGhpcy5tYWtlRW1wdHkoKSx0aGlzLmV4cGFuZEJ5T2JqZWN0KHQsZSl9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfWNvcHkodCl7cmV0dXJuIHRoaXMubWluLmNvcHkodC5taW4pLHRoaXMubWF4LmNvcHkodC5tYXgpLHRoaXN9bWFrZUVtcHR5KCl7cmV0dXJuIHRoaXMubWluLng9dGhpcy5taW4ueT10aGlzLm1pbi56PTEvMCx0aGlzLm1heC54PXRoaXMubWF4Lnk9dGhpcy5tYXguej0tMS8wLHRoaXN9aXNFbXB0eSgpe3JldHVybiB0aGlzLm1heC54PHRoaXMubWluLnh8fHRoaXMubWF4Lnk8dGhpcy5taW4ueXx8dGhpcy5tYXguejx0aGlzLm1pbi56fWdldENlbnRlcih0KXtyZXR1cm4gdGhpcy5pc0VtcHR5KCk/dC5zZXQoMCwwLDApOnQuYWRkVmVjdG9ycyh0aGlzLm1pbix0aGlzLm1heCkubXVsdGlwbHlTY2FsYXIoLjUpfWdldFNpemUodCl7cmV0dXJuIHRoaXMuaXNFbXB0eSgpP3Quc2V0KDAsMCwwKTp0LnN1YlZlY3RvcnModGhpcy5tYXgsdGhpcy5taW4pfWV4cGFuZEJ5UG9pbnQodCl7cmV0dXJuIHRoaXMubWluLm1pbih0KSx0aGlzLm1heC5tYXgodCksdGhpc31leHBhbmRCeVZlY3Rvcih0KXtyZXR1cm4gdGhpcy5taW4uc3ViKHQpLHRoaXMubWF4LmFkZCh0KSx0aGlzfWV4cGFuZEJ5U2NhbGFyKHQpe3JldHVybiB0aGlzLm1pbi5hZGRTY2FsYXIoLXQpLHRoaXMubWF4LmFkZFNjYWxhcih0KSx0aGlzfWV4cGFuZEJ5T2JqZWN0KHQsZT0hMSl7dC51cGRhdGVXb3JsZE1hdHJpeCghMSwhMSk7bGV0IGk9dC5nZW9tZXRyeTtpZihpIT09dm9pZCAwKWlmKGUmJmkuYXR0cmlidXRlcyE9bnVsbCYmaS5hdHRyaWJ1dGVzLnBvc2l0aW9uIT09dm9pZCAwKXtsZXQgcz1pLmF0dHJpYnV0ZXMucG9zaXRpb247Zm9yKGxldCBvPTAsYT1zLmNvdW50O288YTtvKyspSWkuZnJvbUJ1ZmZlckF0dHJpYnV0ZShzLG8pLmFwcGx5TWF0cml4NCh0Lm1hdHJpeFdvcmxkKSx0aGlzLmV4cGFuZEJ5UG9pbnQoSWkpfWVsc2UgaS5ib3VuZGluZ0JveD09PW51bGwmJmkuY29tcHV0ZUJvdW5kaW5nQm94KCksY3UuY29weShpLmJvdW5kaW5nQm94KSxjdS5hcHBseU1hdHJpeDQodC5tYXRyaXhXb3JsZCksdGhpcy51bmlvbihjdSk7bGV0IHI9dC5jaGlsZHJlbjtmb3IobGV0IHM9MCxvPXIubGVuZ3RoO3M8bztzKyspdGhpcy5leHBhbmRCeU9iamVjdChyW3NdLGUpO3JldHVybiB0aGlzfWNvbnRhaW5zUG9pbnQodCl7cmV0dXJuISh0Lng8dGhpcy5taW4ueHx8dC54PnRoaXMubWF4Lnh8fHQueTx0aGlzLm1pbi55fHx0Lnk+dGhpcy5tYXgueXx8dC56PHRoaXMubWluLnp8fHQuej50aGlzLm1heC56KX1jb250YWluc0JveCh0KXtyZXR1cm4gdGhpcy5taW4ueDw9dC5taW4ueCYmdC5tYXgueDw9dGhpcy5tYXgueCYmdGhpcy5taW4ueTw9dC5taW4ueSYmdC5tYXgueTw9dGhpcy5tYXgueSYmdGhpcy5taW4uejw9dC5taW4ueiYmdC5tYXguejw9dGhpcy5tYXguen1nZXRQYXJhbWV0ZXIodCxlKXtyZXR1cm4gZS5zZXQoKHQueC10aGlzLm1pbi54KS8odGhpcy5tYXgueC10aGlzLm1pbi54KSwodC55LXRoaXMubWluLnkpLyh0aGlzLm1heC55LXRoaXMubWluLnkpLCh0LnotdGhpcy5taW4ueikvKHRoaXMubWF4LnotdGhpcy5taW4ueikpfWludGVyc2VjdHNCb3godCl7cmV0dXJuISh0Lm1heC54PHRoaXMubWluLnh8fHQubWluLng+dGhpcy5tYXgueHx8dC5tYXgueTx0aGlzLm1pbi55fHx0Lm1pbi55PnRoaXMubWF4Lnl8fHQubWF4Lno8dGhpcy5taW4uenx8dC5taW4uej50aGlzLm1heC56KX1pbnRlcnNlY3RzU3BoZXJlKHQpe3JldHVybiB0aGlzLmNsYW1wUG9pbnQodC5jZW50ZXIsSWkpLElpLmRpc3RhbmNlVG9TcXVhcmVkKHQuY2VudGVyKTw9dC5yYWRpdXMqdC5yYWRpdXN9aW50ZXJzZWN0c1BsYW5lKHQpe2xldCBlLGk7cmV0dXJuIHQubm9ybWFsLng+MD8oZT10Lm5vcm1hbC54KnRoaXMubWluLngsaT10Lm5vcm1hbC54KnRoaXMubWF4LngpOihlPXQubm9ybWFsLngqdGhpcy5tYXgueCxpPXQubm9ybWFsLngqdGhpcy5taW4ueCksdC5ub3JtYWwueT4wPyhlKz10Lm5vcm1hbC55KnRoaXMubWluLnksaSs9dC5ub3JtYWwueSp0aGlzLm1heC55KTooZSs9dC5ub3JtYWwueSp0aGlzLm1heC55LGkrPXQubm9ybWFsLnkqdGhpcy5taW4ueSksdC5ub3JtYWwuej4wPyhlKz10Lm5vcm1hbC56KnRoaXMubWluLnosaSs9dC5ub3JtYWwueip0aGlzLm1heC56KTooZSs9dC5ub3JtYWwueip0aGlzLm1heC56LGkrPXQubm9ybWFsLnoqdGhpcy5taW4ueiksZTw9LXQuY29uc3RhbnQmJmk+PS10LmNvbnN0YW50fWludGVyc2VjdHNUcmlhbmdsZSh0KXtpZih0aGlzLmlzRW1wdHkoKSlyZXR1cm4hMTt0aGlzLmdldENlbnRlcihrcyksX2Euc3ViVmVjdG9ycyh0aGlzLm1heCxrcyksbXIuc3ViVmVjdG9ycyh0LmEsa3MpLGdyLnN1YlZlY3RvcnModC5iLGtzKSx4ci5zdWJWZWN0b3JzKHQuYyxrcykscW4uc3ViVmVjdG9ycyhncixtciksWG4uc3ViVmVjdG9ycyh4cixnciksTmkuc3ViVmVjdG9ycyhtcix4cik7bGV0IGU9WzAsLXFuLnoscW4ueSwwLC1Ybi56LFhuLnksMCwtTmkueixOaS55LHFuLnosMCwtcW4ueCxYbi56LDAsLVhuLngsTmkueiwwLC1OaS54LC1xbi55LHFuLngsMCwtWG4ueSxYbi54LDAsLU5pLnksTmkueCwwXTtyZXR1cm4hdXUoZSxtcixncix4cixfYSl8fChlPVsxLDAsMCwwLDEsMCwwLDAsMV0sIXV1KGUsbXIsZ3IseHIsX2EpKT8hMTood2EuY3Jvc3NWZWN0b3JzKHFuLFhuKSxlPVt3YS54LHdhLnksd2Euel0sdXUoZSxtcixncix4cixfYSkpfWNsYW1wUG9pbnQodCxlKXtyZXR1cm4gZS5jb3B5KHQpLmNsYW1wKHRoaXMubWluLHRoaXMubWF4KX1kaXN0YW5jZVRvUG9pbnQodCl7cmV0dXJuIElpLmNvcHkodCkuY2xhbXAodGhpcy5taW4sdGhpcy5tYXgpLnN1Yih0KS5sZW5ndGgoKX1nZXRCb3VuZGluZ1NwaGVyZSh0KXtyZXR1cm4gdGhpcy5nZXRDZW50ZXIodC5jZW50ZXIpLHQucmFkaXVzPXRoaXMuZ2V0U2l6ZShJaSkubGVuZ3RoKCkqLjUsdH1pbnRlcnNlY3QodCl7cmV0dXJuIHRoaXMubWluLm1heCh0Lm1pbiksdGhpcy5tYXgubWluKHQubWF4KSx0aGlzLmlzRW1wdHkoKSYmdGhpcy5tYWtlRW1wdHkoKSx0aGlzfXVuaW9uKHQpe3JldHVybiB0aGlzLm1pbi5taW4odC5taW4pLHRoaXMubWF4Lm1heCh0Lm1heCksdGhpc31hcHBseU1hdHJpeDQodCl7cmV0dXJuIHRoaXMuaXNFbXB0eSgpP3RoaXM6KEFuWzBdLnNldCh0aGlzLm1pbi54LHRoaXMubWluLnksdGhpcy5taW4ueikuYXBwbHlNYXRyaXg0KHQpLEFuWzFdLnNldCh0aGlzLm1pbi54LHRoaXMubWluLnksdGhpcy5tYXgueikuYXBwbHlNYXRyaXg0KHQpLEFuWzJdLnNldCh0aGlzLm1pbi54LHRoaXMubWF4LnksdGhpcy5taW4ueikuYXBwbHlNYXRyaXg0KHQpLEFuWzNdLnNldCh0aGlzLm1pbi54LHRoaXMubWF4LnksdGhpcy5tYXgueikuYXBwbHlNYXRyaXg0KHQpLEFuWzRdLnNldCh0aGlzLm1heC54LHRoaXMubWluLnksdGhpcy5taW4ueikuYXBwbHlNYXRyaXg0KHQpLEFuWzVdLnNldCh0aGlzLm1heC54LHRoaXMubWluLnksdGhpcy5tYXgueikuYXBwbHlNYXRyaXg0KHQpLEFuWzZdLnNldCh0aGlzLm1heC54LHRoaXMubWF4LnksdGhpcy5taW4ueikuYXBwbHlNYXRyaXg0KHQpLEFuWzddLnNldCh0aGlzLm1heC54LHRoaXMubWF4LnksdGhpcy5tYXgueikuYXBwbHlNYXRyaXg0KHQpLHRoaXMuc2V0RnJvbVBvaW50cyhBbiksdGhpcyl9dHJhbnNsYXRlKHQpe3JldHVybiB0aGlzLm1pbi5hZGQodCksdGhpcy5tYXguYWRkKHQpLHRoaXN9ZXF1YWxzKHQpe3JldHVybiB0Lm1pbi5lcXVhbHModGhpcy5taW4pJiZ0Lm1heC5lcXVhbHModGhpcy5tYXgpfX07R2UucHJvdG90eXBlLmlzQm94Mz0hMDt2YXIgQW49W25ldyBULG5ldyBULG5ldyBULG5ldyBULG5ldyBULG5ldyBULG5ldyBULG5ldyBUXSxJaT1uZXcgVCxjdT1uZXcgR2UsbXI9bmV3IFQsZ3I9bmV3IFQseHI9bmV3IFQscW49bmV3IFQsWG49bmV3IFQsTmk9bmV3IFQsa3M9bmV3IFQsX2E9bmV3IFQsd2E9bmV3IFQsRmk9bmV3IFQ7ZnVuY3Rpb24gdXUobix0LGUsaSxyKXtmb3IobGV0IHM9MCxvPW4ubGVuZ3RoLTM7czw9bztzKz0zKXtGaS5mcm9tQXJyYXkobixzKTtsZXQgYT1yLngqTWF0aC5hYnMoRmkueCkrci55Kk1hdGguYWJzKEZpLnkpK3IueipNYXRoLmFicyhGaS56KSxsPXQuZG90KEZpKSxjPWUuZG90KEZpKSx1PWkuZG90KEZpKTtpZihNYXRoLm1heCgtTWF0aC5tYXgobCxjLHUpLE1hdGgubWluKGwsYyx1KSk+YSlyZXR1cm4hMX1yZXR1cm4hMH12YXIgck09bmV3IEdlLHNnPW5ldyBULE1hPW5ldyBULGh1PW5ldyBULHNpPWNsYXNze2NvbnN0cnVjdG9yKHQ9bmV3IFQsZT0tMSl7dGhpcy5jZW50ZXI9dCx0aGlzLnJhZGl1cz1lfXNldCh0LGUpe3JldHVybiB0aGlzLmNlbnRlci5jb3B5KHQpLHRoaXMucmFkaXVzPWUsdGhpc31zZXRGcm9tUG9pbnRzKHQsZSl7bGV0IGk9dGhpcy5jZW50ZXI7ZSE9PXZvaWQgMD9pLmNvcHkoZSk6ck0uc2V0RnJvbVBvaW50cyh0KS5nZXRDZW50ZXIoaSk7bGV0IHI9MDtmb3IobGV0IHM9MCxvPXQubGVuZ3RoO3M8bztzKyspcj1NYXRoLm1heChyLGkuZGlzdGFuY2VUb1NxdWFyZWQodFtzXSkpO3JldHVybiB0aGlzLnJhZGl1cz1NYXRoLnNxcnQociksdGhpc31jb3B5KHQpe3JldHVybiB0aGlzLmNlbnRlci5jb3B5KHQuY2VudGVyKSx0aGlzLnJhZGl1cz10LnJhZGl1cyx0aGlzfWlzRW1wdHkoKXtyZXR1cm4gdGhpcy5yYWRpdXM8MH1tYWtlRW1wdHkoKXtyZXR1cm4gdGhpcy5jZW50ZXIuc2V0KDAsMCwwKSx0aGlzLnJhZGl1cz0tMSx0aGlzfWNvbnRhaW5zUG9pbnQodCl7cmV0dXJuIHQuZGlzdGFuY2VUb1NxdWFyZWQodGhpcy5jZW50ZXIpPD10aGlzLnJhZGl1cyp0aGlzLnJhZGl1c31kaXN0YW5jZVRvUG9pbnQodCl7cmV0dXJuIHQuZGlzdGFuY2VUbyh0aGlzLmNlbnRlciktdGhpcy5yYWRpdXN9aW50ZXJzZWN0c1NwaGVyZSh0KXtsZXQgZT10aGlzLnJhZGl1cyt0LnJhZGl1cztyZXR1cm4gdC5jZW50ZXIuZGlzdGFuY2VUb1NxdWFyZWQodGhpcy5jZW50ZXIpPD1lKmV9aW50ZXJzZWN0c0JveCh0KXtyZXR1cm4gdC5pbnRlcnNlY3RzU3BoZXJlKHRoaXMpfWludGVyc2VjdHNQbGFuZSh0KXtyZXR1cm4gTWF0aC5hYnModC5kaXN0YW5jZVRvUG9pbnQodGhpcy5jZW50ZXIpKTw9dGhpcy5yYWRpdXN9Y2xhbXBQb2ludCh0LGUpe2xldCBpPXRoaXMuY2VudGVyLmRpc3RhbmNlVG9TcXVhcmVkKHQpO3JldHVybiBlLmNvcHkodCksaT50aGlzLnJhZGl1cyp0aGlzLnJhZGl1cyYmKGUuc3ViKHRoaXMuY2VudGVyKS5ub3JtYWxpemUoKSxlLm11bHRpcGx5U2NhbGFyKHRoaXMucmFkaXVzKS5hZGQodGhpcy5jZW50ZXIpKSxlfWdldEJvdW5kaW5nQm94KHQpe3JldHVybiB0aGlzLmlzRW1wdHkoKT8odC5tYWtlRW1wdHkoKSx0KToodC5zZXQodGhpcy5jZW50ZXIsdGhpcy5jZW50ZXIpLHQuZXhwYW5kQnlTY2FsYXIodGhpcy5yYWRpdXMpLHQpfWFwcGx5TWF0cml4NCh0KXtyZXR1cm4gdGhpcy5jZW50ZXIuYXBwbHlNYXRyaXg0KHQpLHRoaXMucmFkaXVzPXRoaXMucmFkaXVzKnQuZ2V0TWF4U2NhbGVPbkF4aXMoKSx0aGlzfXRyYW5zbGF0ZSh0KXtyZXR1cm4gdGhpcy5jZW50ZXIuYWRkKHQpLHRoaXN9ZXhwYW5kQnlQb2ludCh0KXtodS5zdWJWZWN0b3JzKHQsdGhpcy5jZW50ZXIpO2xldCBlPWh1Lmxlbmd0aFNxKCk7aWYoZT50aGlzLnJhZGl1cyp0aGlzLnJhZGl1cyl7bGV0IGk9TWF0aC5zcXJ0KGUpLHI9KGktdGhpcy5yYWRpdXMpKi41O3RoaXMuY2VudGVyLmFkZChodS5tdWx0aXBseVNjYWxhcihyL2kpKSx0aGlzLnJhZGl1cys9cn1yZXR1cm4gdGhpc311bmlvbih0KXtyZXR1cm4gdGhpcy5jZW50ZXIuZXF1YWxzKHQuY2VudGVyKT09PSEwP01hLnNldCgwLDAsMSkubXVsdGlwbHlTY2FsYXIodC5yYWRpdXMpOk1hLnN1YlZlY3RvcnModC5jZW50ZXIsdGhpcy5jZW50ZXIpLm5vcm1hbGl6ZSgpLm11bHRpcGx5U2NhbGFyKHQucmFkaXVzKSx0aGlzLmV4cGFuZEJ5UG9pbnQoc2cuY29weSh0LmNlbnRlcikuYWRkKE1hKSksdGhpcy5leHBhbmRCeVBvaW50KHNnLmNvcHkodC5jZW50ZXIpLnN1YihNYSkpLHRoaXN9ZXF1YWxzKHQpe3JldHVybiB0LmNlbnRlci5lcXVhbHModGhpcy5jZW50ZXIpJiZ0LnJhZGl1cz09PXRoaXMucmFkaXVzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX19LENuPW5ldyBULGZ1PW5ldyBULGJhPW5ldyBULFluPW5ldyBULGR1PW5ldyBULFNhPW5ldyBULHB1PW5ldyBULG9pPWNsYXNze2NvbnN0cnVjdG9yKHQ9bmV3IFQsZT1uZXcgVCgwLDAsLTEpKXt0aGlzLm9yaWdpbj10LHRoaXMuZGlyZWN0aW9uPWV9c2V0KHQsZSl7cmV0dXJuIHRoaXMub3JpZ2luLmNvcHkodCksdGhpcy5kaXJlY3Rpb24uY29weShlKSx0aGlzfWNvcHkodCl7cmV0dXJuIHRoaXMub3JpZ2luLmNvcHkodC5vcmlnaW4pLHRoaXMuZGlyZWN0aW9uLmNvcHkodC5kaXJlY3Rpb24pLHRoaXN9YXQodCxlKXtyZXR1cm4gZS5jb3B5KHRoaXMuZGlyZWN0aW9uKS5tdWx0aXBseVNjYWxhcih0KS5hZGQodGhpcy5vcmlnaW4pfWxvb2tBdCh0KXtyZXR1cm4gdGhpcy5kaXJlY3Rpb24uY29weSh0KS5zdWIodGhpcy5vcmlnaW4pLm5vcm1hbGl6ZSgpLHRoaXN9cmVjYXN0KHQpe3JldHVybiB0aGlzLm9yaWdpbi5jb3B5KHRoaXMuYXQodCxDbikpLHRoaXN9Y2xvc2VzdFBvaW50VG9Qb2ludCh0LGUpe2Uuc3ViVmVjdG9ycyh0LHRoaXMub3JpZ2luKTtsZXQgaT1lLmRvdCh0aGlzLmRpcmVjdGlvbik7cmV0dXJuIGk8MD9lLmNvcHkodGhpcy5vcmlnaW4pOmUuY29weSh0aGlzLmRpcmVjdGlvbikubXVsdGlwbHlTY2FsYXIoaSkuYWRkKHRoaXMub3JpZ2luKX1kaXN0YW5jZVRvUG9pbnQodCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLmRpc3RhbmNlU3FUb1BvaW50KHQpKX1kaXN0YW5jZVNxVG9Qb2ludCh0KXtsZXQgZT1Dbi5zdWJWZWN0b3JzKHQsdGhpcy5vcmlnaW4pLmRvdCh0aGlzLmRpcmVjdGlvbik7cmV0dXJuIGU8MD90aGlzLm9yaWdpbi5kaXN0YW5jZVRvU3F1YXJlZCh0KTooQ24uY29weSh0aGlzLmRpcmVjdGlvbikubXVsdGlwbHlTY2FsYXIoZSkuYWRkKHRoaXMub3JpZ2luKSxDbi5kaXN0YW5jZVRvU3F1YXJlZCh0KSl9ZGlzdGFuY2VTcVRvU2VnbWVudCh0LGUsaSxyKXtmdS5jb3B5KHQpLmFkZChlKS5tdWx0aXBseVNjYWxhciguNSksYmEuY29weShlKS5zdWIodCkubm9ybWFsaXplKCksWW4uY29weSh0aGlzLm9yaWdpbikuc3ViKGZ1KTtsZXQgcz10LmRpc3RhbmNlVG8oZSkqLjUsbz0tdGhpcy5kaXJlY3Rpb24uZG90KGJhKSxhPVluLmRvdCh0aGlzLmRpcmVjdGlvbiksbD0tWW4uZG90KGJhKSxjPVluLmxlbmd0aFNxKCksdT1NYXRoLmFicygxLW8qbyksaCxmLGQsZztpZih1PjApaWYoaD1vKmwtYSxmPW8qYS1sLGc9cyp1LGg+PTApaWYoZj49LWcpaWYoZjw9Zyl7bGV0IHg9MS91O2gqPXgsZio9eCxkPWgqKGgrbypmKzIqYSkrZioobypoK2YrMipsKStjfWVsc2UgZj1zLGg9TWF0aC5tYXgoMCwtKG8qZithKSksZD0taCpoK2YqKGYrMipsKStjO2Vsc2UgZj0tcyxoPU1hdGgubWF4KDAsLShvKmYrYSkpLGQ9LWgqaCtmKihmKzIqbCkrYztlbHNlIGY8PS1nPyhoPU1hdGgubWF4KDAsLSgtbypzK2EpKSxmPWg+MD8tczpNYXRoLm1pbihNYXRoLm1heCgtcywtbCkscyksZD0taCpoK2YqKGYrMipsKStjKTpmPD1nPyhoPTAsZj1NYXRoLm1pbihNYXRoLm1heCgtcywtbCkscyksZD1mKihmKzIqbCkrYyk6KGg9TWF0aC5tYXgoMCwtKG8qcythKSksZj1oPjA/czpNYXRoLm1pbihNYXRoLm1heCgtcywtbCkscyksZD0taCpoK2YqKGYrMipsKStjKTtlbHNlIGY9bz4wPy1zOnMsaD1NYXRoLm1heCgwLC0obypmK2EpKSxkPS1oKmgrZiooZisyKmwpK2M7cmV0dXJuIGkmJmkuY29weSh0aGlzLmRpcmVjdGlvbikubXVsdGlwbHlTY2FsYXIoaCkuYWRkKHRoaXMub3JpZ2luKSxyJiZyLmNvcHkoYmEpLm11bHRpcGx5U2NhbGFyKGYpLmFkZChmdSksZH1pbnRlcnNlY3RTcGhlcmUodCxlKXtDbi5zdWJWZWN0b3JzKHQuY2VudGVyLHRoaXMub3JpZ2luKTtsZXQgaT1Dbi5kb3QodGhpcy5kaXJlY3Rpb24pLHI9Q24uZG90KENuKS1pKmkscz10LnJhZGl1cyp0LnJhZGl1cztpZihyPnMpcmV0dXJuIG51bGw7bGV0IG89TWF0aC5zcXJ0KHMtciksYT1pLW8sbD1pK287cmV0dXJuIGE8MCYmbDwwP251bGw6YTwwP3RoaXMuYXQobCxlKTp0aGlzLmF0KGEsZSl9aW50ZXJzZWN0c1NwaGVyZSh0KXtyZXR1cm4gdGhpcy5kaXN0YW5jZVNxVG9Qb2ludCh0LmNlbnRlcik8PXQucmFkaXVzKnQucmFkaXVzfWRpc3RhbmNlVG9QbGFuZSh0KXtsZXQgZT10Lm5vcm1hbC5kb3QodGhpcy5kaXJlY3Rpb24pO2lmKGU9PT0wKXJldHVybiB0LmRpc3RhbmNlVG9Qb2ludCh0aGlzLm9yaWdpbik9PT0wPzA6bnVsbDtsZXQgaT0tKHRoaXMub3JpZ2luLmRvdCh0Lm5vcm1hbCkrdC5jb25zdGFudCkvZTtyZXR1cm4gaT49MD9pOm51bGx9aW50ZXJzZWN0UGxhbmUodCxlKXtsZXQgaT10aGlzLmRpc3RhbmNlVG9QbGFuZSh0KTtyZXR1cm4gaT09PW51bGw/bnVsbDp0aGlzLmF0KGksZSl9aW50ZXJzZWN0c1BsYW5lKHQpe2xldCBlPXQuZGlzdGFuY2VUb1BvaW50KHRoaXMub3JpZ2luKTtyZXR1cm4gZT09PTB8fHQubm9ybWFsLmRvdCh0aGlzLmRpcmVjdGlvbikqZTwwfWludGVyc2VjdEJveCh0LGUpe2xldCBpLHIscyxvLGEsbCxjPTEvdGhpcy5kaXJlY3Rpb24ueCx1PTEvdGhpcy5kaXJlY3Rpb24ueSxoPTEvdGhpcy5kaXJlY3Rpb24ueixmPXRoaXMub3JpZ2luO3JldHVybiBjPj0wPyhpPSh0Lm1pbi54LWYueCkqYyxyPSh0Lm1heC54LWYueCkqYyk6KGk9KHQubWF4LngtZi54KSpjLHI9KHQubWluLngtZi54KSpjKSx1Pj0wPyhzPSh0Lm1pbi55LWYueSkqdSxvPSh0Lm1heC55LWYueSkqdSk6KHM9KHQubWF4LnktZi55KSp1LG89KHQubWluLnktZi55KSp1KSxpPm98fHM+cnx8KChzPml8fGkhPT1pKSYmKGk9cyksKG88cnx8ciE9PXIpJiYocj1vKSxoPj0wPyhhPSh0Lm1pbi56LWYueikqaCxsPSh0Lm1heC56LWYueikqaCk6KGE9KHQubWF4LnotZi56KSpoLGw9KHQubWluLnotZi56KSpoKSxpPmx8fGE+cil8fCgoYT5pfHxpIT09aSkmJihpPWEpLChsPHJ8fHIhPT1yKSYmKHI9bCkscjwwKT9udWxsOnRoaXMuYXQoaT49MD9pOnIsZSl9aW50ZXJzZWN0c0JveCh0KXtyZXR1cm4gdGhpcy5pbnRlcnNlY3RCb3godCxDbikhPT1udWxsfWludGVyc2VjdFRyaWFuZ2xlKHQsZSxpLHIscyl7ZHUuc3ViVmVjdG9ycyhlLHQpLFNhLnN1YlZlY3RvcnMoaSx0KSxwdS5jcm9zc1ZlY3RvcnMoZHUsU2EpO2xldCBvPXRoaXMuZGlyZWN0aW9uLmRvdChwdSksYTtpZihvPjApe2lmKHIpcmV0dXJuIG51bGw7YT0xfWVsc2UgaWYobzwwKWE9LTEsbz0tbztlbHNlIHJldHVybiBudWxsO1luLnN1YlZlY3RvcnModGhpcy5vcmlnaW4sdCk7bGV0IGw9YSp0aGlzLmRpcmVjdGlvbi5kb3QoU2EuY3Jvc3NWZWN0b3JzKFluLFNhKSk7aWYobDwwKXJldHVybiBudWxsO2xldCBjPWEqdGhpcy5kaXJlY3Rpb24uZG90KGR1LmNyb3NzKFluKSk7aWYoYzwwfHxsK2M+bylyZXR1cm4gbnVsbDtsZXQgdT0tYSpZbi5kb3QocHUpO3JldHVybiB1PDA/bnVsbDp0aGlzLmF0KHUvbyxzKX1hcHBseU1hdHJpeDQodCl7cmV0dXJuIHRoaXMub3JpZ2luLmFwcGx5TWF0cml4NCh0KSx0aGlzLmRpcmVjdGlvbi50cmFuc2Zvcm1EaXJlY3Rpb24odCksdGhpc31lcXVhbHModCl7cmV0dXJuIHQub3JpZ2luLmVxdWFscyh0aGlzLm9yaWdpbikmJnQuZGlyZWN0aW9uLmVxdWFscyh0aGlzLmRpcmVjdGlvbil9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfX0sd3Q9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmVsZW1lbnRzPVsxLDAsMCwwLDAsMSwwLDAsMCwwLDEsMCwwLDAsMCwxXSxhcmd1bWVudHMubGVuZ3RoPjAmJmNvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IHRoZSBjb25zdHJ1Y3RvciBubyBsb25nZXIgcmVhZHMgYXJndW1lbnRzLiB1c2UgLnNldCgpIGluc3RlYWQuIil9c2V0KHQsZSxpLHIscyxvLGEsbCxjLHUsaCxmLGQsZyx4LHYpe2xldCBtPXRoaXMuZWxlbWVudHM7cmV0dXJuIG1bMF09dCxtWzRdPWUsbVs4XT1pLG1bMTJdPXIsbVsxXT1zLG1bNV09byxtWzldPWEsbVsxM109bCxtWzJdPWMsbVs2XT11LG1bMTBdPWgsbVsxNF09ZixtWzNdPWQsbVs3XT1nLG1bMTFdPXgsbVsxNV09dix0aGlzfWlkZW50aXR5KCl7cmV0dXJuIHRoaXMuc2V0KDEsMCwwLDAsMCwxLDAsMCwwLDAsMSwwLDAsMCwwLDEpLHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHd0KCkuZnJvbUFycmF5KHRoaXMuZWxlbWVudHMpfWNvcHkodCl7bGV0IGU9dGhpcy5lbGVtZW50cyxpPXQuZWxlbWVudHM7cmV0dXJuIGVbMF09aVswXSxlWzFdPWlbMV0sZVsyXT1pWzJdLGVbM109aVszXSxlWzRdPWlbNF0sZVs1XT1pWzVdLGVbNl09aVs2XSxlWzddPWlbN10sZVs4XT1pWzhdLGVbOV09aVs5XSxlWzEwXT1pWzEwXSxlWzExXT1pWzExXSxlWzEyXT1pWzEyXSxlWzEzXT1pWzEzXSxlWzE0XT1pWzE0XSxlWzE1XT1pWzE1XSx0aGlzfWNvcHlQb3NpdGlvbih0KXtsZXQgZT10aGlzLmVsZW1lbnRzLGk9dC5lbGVtZW50cztyZXR1cm4gZVsxMl09aVsxMl0sZVsxM109aVsxM10sZVsxNF09aVsxNF0sdGhpc31zZXRGcm9tTWF0cml4Myh0KXtsZXQgZT10LmVsZW1lbnRzO3JldHVybiB0aGlzLnNldChlWzBdLGVbM10sZVs2XSwwLGVbMV0sZVs0XSxlWzddLDAsZVsyXSxlWzVdLGVbOF0sMCwwLDAsMCwxKSx0aGlzfWV4dHJhY3RCYXNpcyh0LGUsaSl7cmV0dXJuIHQuc2V0RnJvbU1hdHJpeENvbHVtbih0aGlzLDApLGUuc2V0RnJvbU1hdHJpeENvbHVtbih0aGlzLDEpLGkuc2V0RnJvbU1hdHJpeENvbHVtbih0aGlzLDIpLHRoaXN9bWFrZUJhc2lzKHQsZSxpKXtyZXR1cm4gdGhpcy5zZXQodC54LGUueCxpLngsMCx0LnksZS55LGkueSwwLHQueixlLnosaS56LDAsMCwwLDAsMSksdGhpc31leHRyYWN0Um90YXRpb24odCl7bGV0IGU9dGhpcy5lbGVtZW50cyxpPXQuZWxlbWVudHMscj0xL3lyLnNldEZyb21NYXRyaXhDb2x1bW4odCwwKS5sZW5ndGgoKSxzPTEveXIuc2V0RnJvbU1hdHJpeENvbHVtbih0LDEpLmxlbmd0aCgpLG89MS95ci5zZXRGcm9tTWF0cml4Q29sdW1uKHQsMikubGVuZ3RoKCk7cmV0dXJuIGVbMF09aVswXSpyLGVbMV09aVsxXSpyLGVbMl09aVsyXSpyLGVbM109MCxlWzRdPWlbNF0qcyxlWzVdPWlbNV0qcyxlWzZdPWlbNl0qcyxlWzddPTAsZVs4XT1pWzhdKm8sZVs5XT1pWzldKm8sZVsxMF09aVsxMF0qbyxlWzExXT0wLGVbMTJdPTAsZVsxM109MCxlWzE0XT0wLGVbMTVdPTEsdGhpc31tYWtlUm90YXRpb25Gcm9tRXVsZXIodCl7dCYmdC5pc0V1bGVyfHxjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXg0OiAubWFrZVJvdGF0aW9uRnJvbUV1bGVyKCkgbm93IGV4cGVjdHMgYSBFdWxlciByb3RhdGlvbiByYXRoZXIgdGhhbiBhIFZlY3RvcjMgYW5kIG9yZGVyLiIpO2xldCBlPXRoaXMuZWxlbWVudHMsaT10Lngscj10Lnkscz10Lnosbz1NYXRoLmNvcyhpKSxhPU1hdGguc2luKGkpLGw9TWF0aC5jb3MociksYz1NYXRoLnNpbihyKSx1PU1hdGguY29zKHMpLGg9TWF0aC5zaW4ocyk7aWYodC5vcmRlcj09PSJYWVoiKXtsZXQgZj1vKnUsZD1vKmgsZz1hKnUseD1hKmg7ZVswXT1sKnUsZVs0XT0tbCpoLGVbOF09YyxlWzFdPWQrZypjLGVbNV09Zi14KmMsZVs5XT0tYSpsLGVbMl09eC1mKmMsZVs2XT1nK2QqYyxlWzEwXT1vKmx9ZWxzZSBpZih0Lm9yZGVyPT09IllYWiIpe2xldCBmPWwqdSxkPWwqaCxnPWMqdSx4PWMqaDtlWzBdPWYreCphLGVbNF09ZyphLWQsZVs4XT1vKmMsZVsxXT1vKmgsZVs1XT1vKnUsZVs5XT0tYSxlWzJdPWQqYS1nLGVbNl09eCtmKmEsZVsxMF09bypsfWVsc2UgaWYodC5vcmRlcj09PSJaWFkiKXtsZXQgZj1sKnUsZD1sKmgsZz1jKnUseD1jKmg7ZVswXT1mLXgqYSxlWzRdPS1vKmgsZVs4XT1nK2QqYSxlWzFdPWQrZyphLGVbNV09byp1LGVbOV09eC1mKmEsZVsyXT0tbypjLGVbNl09YSxlWzEwXT1vKmx9ZWxzZSBpZih0Lm9yZGVyPT09IlpZWCIpe2xldCBmPW8qdSxkPW8qaCxnPWEqdSx4PWEqaDtlWzBdPWwqdSxlWzRdPWcqYy1kLGVbOF09ZipjK3gsZVsxXT1sKmgsZVs1XT14KmMrZixlWzldPWQqYy1nLGVbMl09LWMsZVs2XT1hKmwsZVsxMF09bypsfWVsc2UgaWYodC5vcmRlcj09PSJZWlgiKXtsZXQgZj1vKmwsZD1vKmMsZz1hKmwseD1hKmM7ZVswXT1sKnUsZVs0XT14LWYqaCxlWzhdPWcqaCtkLGVbMV09aCxlWzVdPW8qdSxlWzldPS1hKnUsZVsyXT0tYyp1LGVbNl09ZCpoK2csZVsxMF09Zi14Kmh9ZWxzZSBpZih0Lm9yZGVyPT09IlhaWSIpe2xldCBmPW8qbCxkPW8qYyxnPWEqbCx4PWEqYztlWzBdPWwqdSxlWzRdPS1oLGVbOF09Yyp1LGVbMV09ZipoK3gsZVs1XT1vKnUsZVs5XT1kKmgtZyxlWzJdPWcqaC1kLGVbNl09YSp1LGVbMTBdPXgqaCtmfXJldHVybiBlWzNdPTAsZVs3XT0wLGVbMTFdPTAsZVsxMl09MCxlWzEzXT0wLGVbMTRdPTAsZVsxNV09MSx0aGlzfW1ha2VSb3RhdGlvbkZyb21RdWF0ZXJuaW9uKHQpe3JldHVybiB0aGlzLmNvbXBvc2Uoc00sdCxvTSl9bG9va0F0KHQsZSxpKXtsZXQgcj10aGlzLmVsZW1lbnRzO3JldHVybiBrZS5zdWJWZWN0b3JzKHQsZSksa2UubGVuZ3RoU3EoKT09PTAmJihrZS56PTEpLGtlLm5vcm1hbGl6ZSgpLFpuLmNyb3NzVmVjdG9ycyhpLGtlKSxabi5sZW5ndGhTcSgpPT09MCYmKE1hdGguYWJzKGkueik9PT0xP2tlLngrPTFlLTQ6a2Uueis9MWUtNCxrZS5ub3JtYWxpemUoKSxabi5jcm9zc1ZlY3RvcnMoaSxrZSkpLFpuLm5vcm1hbGl6ZSgpLEVhLmNyb3NzVmVjdG9ycyhrZSxabiksclswXT1abi54LHJbNF09RWEueCxyWzhdPWtlLngsclsxXT1abi55LHJbNV09RWEueSxyWzldPWtlLnksclsyXT1abi56LHJbNl09RWEueixyWzEwXT1rZS56LHRoaXN9bXVsdGlwbHkodCxlKXtyZXR1cm4gZSE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAubXVsdGlwbHkoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5tdWx0aXBseU1hdHJpY2VzKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLm11bHRpcGx5TWF0cmljZXModCxlKSk6dGhpcy5tdWx0aXBseU1hdHJpY2VzKHRoaXMsdCl9cHJlbXVsdGlwbHkodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlNYXRyaWNlcyh0LHRoaXMpfW11bHRpcGx5TWF0cmljZXModCxlKXtsZXQgaT10LmVsZW1lbnRzLHI9ZS5lbGVtZW50cyxzPXRoaXMuZWxlbWVudHMsbz1pWzBdLGE9aVs0XSxsPWlbOF0sYz1pWzEyXSx1PWlbMV0saD1pWzVdLGY9aVs5XSxkPWlbMTNdLGc9aVsyXSx4PWlbNl0sdj1pWzEwXSxtPWlbMTRdLHA9aVszXSxiPWlbN10sXz1pWzExXSxTPWlbMTVdLEw9clswXSxBPXJbNF0sSD1yWzhdLHR0PXJbMTJdLFg9clsxXSx5PXJbNV0sUj1yWzldLEQ9clsxM10sRj1yWzJdLHo9cls2XSxOPXJbMTBdLFY9clsxNF0sUT1yWzNdLGF0PXJbN10sRz1yWzExXSwkPXJbMTVdO3JldHVybiBzWzBdPW8qTCthKlgrbCpGK2MqUSxzWzRdPW8qQSthKnkrbCp6K2MqYXQsc1s4XT1vKkgrYSpSK2wqTitjKkcsc1sxMl09byp0dCthKkQrbCpWK2MqJCxzWzFdPXUqTCtoKlgrZipGK2QqUSxzWzVdPXUqQStoKnkrZip6K2QqYXQsc1s5XT11KkgraCpSK2YqTitkKkcsc1sxM109dSp0dCtoKkQrZipWK2QqJCxzWzJdPWcqTCt4KlgrdipGK20qUSxzWzZdPWcqQSt4Knkrdip6K20qYXQsc1sxMF09ZypIK3gqUit2Kk4rbSpHLHNbMTRdPWcqdHQreCpEK3YqVittKiQsc1szXT1wKkwrYipYK18qRitTKlEsc1s3XT1wKkErYip5K18qeitTKmF0LHNbMTFdPXAqSCtiKlIrXypOK1MqRyxzWzE1XT1wKnR0K2IqRCtfKlYrUyokLHRoaXN9bXVsdGlwbHlTY2FsYXIodCl7bGV0IGU9dGhpcy5lbGVtZW50cztyZXR1cm4gZVswXSo9dCxlWzRdKj10LGVbOF0qPXQsZVsxMl0qPXQsZVsxXSo9dCxlWzVdKj10LGVbOV0qPXQsZVsxM10qPXQsZVsyXSo9dCxlWzZdKj10LGVbMTBdKj10LGVbMTRdKj10LGVbM10qPXQsZVs3XSo9dCxlWzExXSo9dCxlWzE1XSo9dCx0aGlzfWRldGVybWluYW50KCl7bGV0IHQ9dGhpcy5lbGVtZW50cyxlPXRbMF0saT10WzRdLHI9dFs4XSxzPXRbMTJdLG89dFsxXSxhPXRbNV0sbD10WzldLGM9dFsxM10sdT10WzJdLGg9dFs2XSxmPXRbMTBdLGQ9dFsxNF0sZz10WzNdLHg9dFs3XSx2PXRbMTFdLG09dFsxNV07cmV0dXJuIGcqKCtzKmwqaC1yKmMqaC1zKmEqZitpKmMqZityKmEqZC1pKmwqZCkreCooK2UqbCpkLWUqYypmK3MqbypmLXIqbypkK3IqYyp1LXMqbCp1KSt2KigrZSpjKmgtZSphKmQtcypvKmgraSpvKmQrcyphKnUtaSpjKnUpK20qKC1yKmEqdS1lKmwqaCtlKmEqZityKm8qaC1pKm8qZitpKmwqdSl9dHJhbnNwb3NlKCl7bGV0IHQ9dGhpcy5lbGVtZW50cyxlO3JldHVybiBlPXRbMV0sdFsxXT10WzRdLHRbNF09ZSxlPXRbMl0sdFsyXT10WzhdLHRbOF09ZSxlPXRbNl0sdFs2XT10WzldLHRbOV09ZSxlPXRbM10sdFszXT10WzEyXSx0WzEyXT1lLGU9dFs3XSx0WzddPXRbMTNdLHRbMTNdPWUsZT10WzExXSx0WzExXT10WzE0XSx0WzE0XT1lLHRoaXN9c2V0UG9zaXRpb24odCxlLGkpe2xldCByPXRoaXMuZWxlbWVudHM7cmV0dXJuIHQuaXNWZWN0b3IzPyhyWzEyXT10LngsclsxM109dC55LHJbMTRdPXQueik6KHJbMTJdPXQsclsxM109ZSxyWzE0XT1pKSx0aGlzfWludmVydCgpe2xldCB0PXRoaXMuZWxlbWVudHMsZT10WzBdLGk9dFsxXSxyPXRbMl0scz10WzNdLG89dFs0XSxhPXRbNV0sbD10WzZdLGM9dFs3XSx1PXRbOF0saD10WzldLGY9dFsxMF0sZD10WzExXSxnPXRbMTJdLHg9dFsxM10sdj10WzE0XSxtPXRbMTVdLHA9aCp2KmMteCpmKmMreCpsKmQtYSp2KmQtaCpsKm0rYSpmKm0sYj1nKmYqYy11KnYqYy1nKmwqZCtvKnYqZCt1KmwqbS1vKmYqbSxfPXUqeCpjLWcqaCpjK2cqYSpkLW8qeCpkLXUqYSptK28qaCptLFM9ZypoKmwtdSp4KmwtZyphKmYrbyp4KmYrdSphKnYtbypoKnYsTD1lKnAraSpiK3IqXytzKlM7aWYoTD09PTApcmV0dXJuIHRoaXMuc2V0KDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDApO2xldCBBPTEvTDtyZXR1cm4gdFswXT1wKkEsdFsxXT0oeCpmKnMtaCp2KnMteCpyKmQraSp2KmQraCpyKm0taSpmKm0pKkEsdFsyXT0oYSp2KnMteCpsKnMreCpyKmMtaSp2KmMtYSpyKm0raSpsKm0pKkEsdFszXT0oaCpsKnMtYSpmKnMtaCpyKmMraSpmKmMrYSpyKmQtaSpsKmQpKkEsdFs0XT1iKkEsdFs1XT0odSp2KnMtZypmKnMrZypyKmQtZSp2KmQtdSpyKm0rZSpmKm0pKkEsdFs2XT0oZypsKnMtbyp2KnMtZypyKmMrZSp2KmMrbypyKm0tZSpsKm0pKkEsdFs3XT0obypmKnMtdSpsKnMrdSpyKmMtZSpmKmMtbypyKmQrZSpsKmQpKkEsdFs4XT1fKkEsdFs5XT0oZypoKnMtdSp4KnMtZyppKmQrZSp4KmQrdSppKm0tZSpoKm0pKkEsdFsxMF09KG8qeCpzLWcqYSpzK2cqaSpjLWUqeCpjLW8qaSptK2UqYSptKSpBLHRbMTFdPSh1KmEqcy1vKmgqcy11KmkqYytlKmgqYytvKmkqZC1lKmEqZCkqQSx0WzEyXT1TKkEsdFsxM109KHUqeCpyLWcqaCpyK2cqaSpmLWUqeCpmLXUqaSp2K2UqaCp2KSpBLHRbMTRdPShnKmEqci1vKngqci1nKmkqbCtlKngqbCtvKmkqdi1lKmEqdikqQSx0WzE1XT0obypoKnItdSphKnIrdSppKmwtZSpoKmwtbyppKmYrZSphKmYpKkEsdGhpc31zY2FsZSh0KXtsZXQgZT10aGlzLmVsZW1lbnRzLGk9dC54LHI9dC55LHM9dC56O3JldHVybiBlWzBdKj1pLGVbNF0qPXIsZVs4XSo9cyxlWzFdKj1pLGVbNV0qPXIsZVs5XSo9cyxlWzJdKj1pLGVbNl0qPXIsZVsxMF0qPXMsZVszXSo9aSxlWzddKj1yLGVbMTFdKj1zLHRoaXN9Z2V0TWF4U2NhbGVPbkF4aXMoKXtsZXQgdD10aGlzLmVsZW1lbnRzLGU9dFswXSp0WzBdK3RbMV0qdFsxXSt0WzJdKnRbMl0saT10WzRdKnRbNF0rdFs1XSp0WzVdK3RbNl0qdFs2XSxyPXRbOF0qdFs4XSt0WzldKnRbOV0rdFsxMF0qdFsxMF07cmV0dXJuIE1hdGguc3FydChNYXRoLm1heChlLGkscikpfW1ha2VUcmFuc2xhdGlvbih0LGUsaSl7cmV0dXJuIHRoaXMuc2V0KDEsMCwwLHQsMCwxLDAsZSwwLDAsMSxpLDAsMCwwLDEpLHRoaXN9bWFrZVJvdGF0aW9uWCh0KXtsZXQgZT1NYXRoLmNvcyh0KSxpPU1hdGguc2luKHQpO3JldHVybiB0aGlzLnNldCgxLDAsMCwwLDAsZSwtaSwwLDAsaSxlLDAsMCwwLDAsMSksdGhpc31tYWtlUm90YXRpb25ZKHQpe2xldCBlPU1hdGguY29zKHQpLGk9TWF0aC5zaW4odCk7cmV0dXJuIHRoaXMuc2V0KGUsMCxpLDAsMCwxLDAsMCwtaSwwLGUsMCwwLDAsMCwxKSx0aGlzfW1ha2VSb3RhdGlvbloodCl7bGV0IGU9TWF0aC5jb3ModCksaT1NYXRoLnNpbih0KTtyZXR1cm4gdGhpcy5zZXQoZSwtaSwwLDAsaSxlLDAsMCwwLDAsMSwwLDAsMCwwLDEpLHRoaXN9bWFrZVJvdGF0aW9uQXhpcyh0LGUpe2xldCBpPU1hdGguY29zKGUpLHI9TWF0aC5zaW4oZSkscz0xLWksbz10LngsYT10LnksbD10LnosYz1zKm8sdT1zKmE7cmV0dXJuIHRoaXMuc2V0KGMqbytpLGMqYS1yKmwsYypsK3IqYSwwLGMqYStyKmwsdSphK2ksdSpsLXIqbywwLGMqbC1yKmEsdSpsK3IqbyxzKmwqbCtpLDAsMCwwLDAsMSksdGhpc31tYWtlU2NhbGUodCxlLGkpe3JldHVybiB0aGlzLnNldCh0LDAsMCwwLDAsZSwwLDAsMCwwLGksMCwwLDAsMCwxKSx0aGlzfW1ha2VTaGVhcih0LGUsaSxyLHMsbyl7cmV0dXJuIHRoaXMuc2V0KDEsaSxzLDAsdCwxLG8sMCxlLHIsMSwwLDAsMCwwLDEpLHRoaXN9Y29tcG9zZSh0LGUsaSl7bGV0IHI9dGhpcy5lbGVtZW50cyxzPWUuX3gsbz1lLl95LGE9ZS5feixsPWUuX3csYz1zK3MsdT1vK28saD1hK2EsZj1zKmMsZD1zKnUsZz1zKmgseD1vKnUsdj1vKmgsbT1hKmgscD1sKmMsYj1sKnUsXz1sKmgsUz1pLngsTD1pLnksQT1pLno7cmV0dXJuIHJbMF09KDEtKHgrbSkpKlMsclsxXT0oZCtfKSpTLHJbMl09KGctYikqUyxyWzNdPTAscls0XT0oZC1fKSpMLHJbNV09KDEtKGYrbSkpKkwscls2XT0oditwKSpMLHJbN109MCxyWzhdPShnK2IpKkEscls5XT0odi1wKSpBLHJbMTBdPSgxLShmK3gpKSpBLHJbMTFdPTAsclsxMl09dC54LHJbMTNdPXQueSxyWzE0XT10LnosclsxNV09MSx0aGlzfWRlY29tcG9zZSh0LGUsaSl7bGV0IHI9dGhpcy5lbGVtZW50cyxzPXlyLnNldChyWzBdLHJbMV0sclsyXSkubGVuZ3RoKCksbz15ci5zZXQocls0XSxyWzVdLHJbNl0pLmxlbmd0aCgpLGE9eXIuc2V0KHJbOF0scls5XSxyWzEwXSkubGVuZ3RoKCk7dGhpcy5kZXRlcm1pbmFudCgpPDAmJihzPS1zKSx0Lng9clsxMl0sdC55PXJbMTNdLHQuej1yWzE0XSxLZS5jb3B5KHRoaXMpO2xldCBjPTEvcyx1PTEvbyxoPTEvYTtyZXR1cm4gS2UuZWxlbWVudHNbMF0qPWMsS2UuZWxlbWVudHNbMV0qPWMsS2UuZWxlbWVudHNbMl0qPWMsS2UuZWxlbWVudHNbNF0qPXUsS2UuZWxlbWVudHNbNV0qPXUsS2UuZWxlbWVudHNbNl0qPXUsS2UuZWxlbWVudHNbOF0qPWgsS2UuZWxlbWVudHNbOV0qPWgsS2UuZWxlbWVudHNbMTBdKj1oLGUuc2V0RnJvbVJvdGF0aW9uTWF0cml4KEtlKSxpLng9cyxpLnk9byxpLno9YSx0aGlzfW1ha2VQZXJzcGVjdGl2ZSh0LGUsaSxyLHMsbyl7bz09PXZvaWQgMCYmY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAubWFrZVBlcnNwZWN0aXZlKCkgaGFzIGJlZW4gcmVkZWZpbmVkIGFuZCBoYXMgYSBuZXcgc2lnbmF0dXJlLiBQbGVhc2UgY2hlY2sgdGhlIGRvY3MuIik7bGV0IGE9dGhpcy5lbGVtZW50cyxsPTIqcy8oZS10KSxjPTIqcy8oaS1yKSx1PShlK3QpLyhlLXQpLGg9KGkrcikvKGktciksZj0tKG8rcykvKG8tcyksZD0tMipvKnMvKG8tcyk7cmV0dXJuIGFbMF09bCxhWzRdPTAsYVs4XT11LGFbMTJdPTAsYVsxXT0wLGFbNV09YyxhWzldPWgsYVsxM109MCxhWzJdPTAsYVs2XT0wLGFbMTBdPWYsYVsxNF09ZCxhWzNdPTAsYVs3XT0wLGFbMTFdPS0xLGFbMTVdPTAsdGhpc31tYWtlT3J0aG9ncmFwaGljKHQsZSxpLHIscyxvKXtsZXQgYT10aGlzLmVsZW1lbnRzLGw9MS8oZS10KSxjPTEvKGktciksdT0xLyhvLXMpLGg9KGUrdCkqbCxmPShpK3IpKmMsZD0obytzKSp1O3JldHVybiBhWzBdPTIqbCxhWzRdPTAsYVs4XT0wLGFbMTJdPS1oLGFbMV09MCxhWzVdPTIqYyxhWzldPTAsYVsxM109LWYsYVsyXT0wLGFbNl09MCxhWzEwXT0tMip1LGFbMTRdPS1kLGFbM109MCxhWzddPTAsYVsxMV09MCxhWzE1XT0xLHRoaXN9ZXF1YWxzKHQpe2xldCBlPXRoaXMuZWxlbWVudHMsaT10LmVsZW1lbnRzO2ZvcihsZXQgcj0wO3I8MTY7cisrKWlmKGVbcl0hPT1pW3JdKXJldHVybiExO3JldHVybiEwfWZyb21BcnJheSh0LGU9MCl7Zm9yKGxldCBpPTA7aTwxNjtpKyspdGhpcy5lbGVtZW50c1tpXT10W2krZV07cmV0dXJuIHRoaXN9dG9BcnJheSh0PVtdLGU9MCl7bGV0IGk9dGhpcy5lbGVtZW50cztyZXR1cm4gdFtlXT1pWzBdLHRbZSsxXT1pWzFdLHRbZSsyXT1pWzJdLHRbZSszXT1pWzNdLHRbZSs0XT1pWzRdLHRbZSs1XT1pWzVdLHRbZSs2XT1pWzZdLHRbZSs3XT1pWzddLHRbZSs4XT1pWzhdLHRbZSs5XT1pWzldLHRbZSsxMF09aVsxMF0sdFtlKzExXT1pWzExXSx0W2UrMTJdPWlbMTJdLHRbZSsxM109aVsxM10sdFtlKzE0XT1pWzE0XSx0W2UrMTVdPWlbMTVdLHR9fTt3dC5wcm90b3R5cGUuaXNNYXRyaXg0PSEwO3ZhciB5cj1uZXcgVCxLZT1uZXcgd3Qsc009bmV3IFQoMCwwLDApLG9NPW5ldyBUKDEsMSwxKSxabj1uZXcgVCxFYT1uZXcgVCxrZT1uZXcgVCxvZz1uZXcgd3QsYWc9bmV3IEVlLGFpPWNsYXNze2NvbnN0cnVjdG9yKHQ9MCxlPTAsaT0wLHI9YWkuRGVmYXVsdE9yZGVyKXt0aGlzLl94PXQsdGhpcy5feT1lLHRoaXMuX3o9aSx0aGlzLl9vcmRlcj1yfWdldCB4KCl7cmV0dXJuIHRoaXMuX3h9c2V0IHgodCl7dGhpcy5feD10LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKX1nZXQgeSgpe3JldHVybiB0aGlzLl95fXNldCB5KHQpe3RoaXMuX3k9dCx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCl9Z2V0IHooKXtyZXR1cm4gdGhpcy5fen1zZXQgeih0KXt0aGlzLl96PXQsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpfWdldCBvcmRlcigpe3JldHVybiB0aGlzLl9vcmRlcn1zZXQgb3JkZXIodCl7dGhpcy5fb3JkZXI9dCx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCl9c2V0KHQsZSxpLHI9dGhpcy5fb3JkZXIpe3JldHVybiB0aGlzLl94PXQsdGhpcy5feT1lLHRoaXMuX3o9aSx0aGlzLl9vcmRlcj1yLHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKHRoaXMuX3gsdGhpcy5feSx0aGlzLl96LHRoaXMuX29yZGVyKX1jb3B5KHQpe3JldHVybiB0aGlzLl94PXQuX3gsdGhpcy5feT10Ll95LHRoaXMuX3o9dC5feix0aGlzLl9vcmRlcj10Ll9vcmRlcix0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31zZXRGcm9tUm90YXRpb25NYXRyaXgodCxlPXRoaXMuX29yZGVyLGk9ITApe2xldCByPXQuZWxlbWVudHMscz1yWzBdLG89cls0XSxhPXJbOF0sbD1yWzFdLGM9cls1XSx1PXJbOV0saD1yWzJdLGY9cls2XSxkPXJbMTBdO3N3aXRjaChlKXtjYXNlIlhZWiI6dGhpcy5feT1NYXRoLmFzaW4oSWUoYSwtMSwxKSksTWF0aC5hYnMoYSk8Ljk5OTk5OTk/KHRoaXMuX3g9TWF0aC5hdGFuMigtdSxkKSx0aGlzLl96PU1hdGguYXRhbjIoLW8scykpOih0aGlzLl94PU1hdGguYXRhbjIoZixjKSx0aGlzLl96PTApO2JyZWFrO2Nhc2UiWVhaIjp0aGlzLl94PU1hdGguYXNpbigtSWUodSwtMSwxKSksTWF0aC5hYnModSk8Ljk5OTk5OTk/KHRoaXMuX3k9TWF0aC5hdGFuMihhLGQpLHRoaXMuX3o9TWF0aC5hdGFuMihsLGMpKToodGhpcy5feT1NYXRoLmF0YW4yKC1oLHMpLHRoaXMuX3o9MCk7YnJlYWs7Y2FzZSJaWFkiOnRoaXMuX3g9TWF0aC5hc2luKEllKGYsLTEsMSkpLE1hdGguYWJzKGYpPC45OTk5OTk5Pyh0aGlzLl95PU1hdGguYXRhbjIoLWgsZCksdGhpcy5fej1NYXRoLmF0YW4yKC1vLGMpKToodGhpcy5feT0wLHRoaXMuX3o9TWF0aC5hdGFuMihsLHMpKTticmVhaztjYXNlIlpZWCI6dGhpcy5feT1NYXRoLmFzaW4oLUllKGgsLTEsMSkpLE1hdGguYWJzKGgpPC45OTk5OTk5Pyh0aGlzLl94PU1hdGguYXRhbjIoZixkKSx0aGlzLl96PU1hdGguYXRhbjIobCxzKSk6KHRoaXMuX3g9MCx0aGlzLl96PU1hdGguYXRhbjIoLW8sYykpO2JyZWFrO2Nhc2UiWVpYIjp0aGlzLl96PU1hdGguYXNpbihJZShsLC0xLDEpKSxNYXRoLmFicyhsKTwuOTk5OTk5OT8odGhpcy5feD1NYXRoLmF0YW4yKC11LGMpLHRoaXMuX3k9TWF0aC5hdGFuMigtaCxzKSk6KHRoaXMuX3g9MCx0aGlzLl95PU1hdGguYXRhbjIoYSxkKSk7YnJlYWs7Y2FzZSJYWlkiOnRoaXMuX3o9TWF0aC5hc2luKC1JZShvLC0xLDEpKSxNYXRoLmFicyhvKTwuOTk5OTk5OT8odGhpcy5feD1NYXRoLmF0YW4yKGYsYyksdGhpcy5feT1NYXRoLmF0YW4yKGEscykpOih0aGlzLl94PU1hdGguYXRhbjIoLXUsZCksdGhpcy5feT0wKTticmVhaztkZWZhdWx0OmNvbnNvbGUud2FybigiVEhSRUUuRXVsZXI6IC5zZXRGcm9tUm90YXRpb25NYXRyaXgoKSBlbmNvdW50ZXJlZCBhbiB1bmtub3duIG9yZGVyOiAiK2UpfXJldHVybiB0aGlzLl9vcmRlcj1lLGk9PT0hMCYmdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9c2V0RnJvbVF1YXRlcm5pb24odCxlLGkpe3JldHVybiBvZy5tYWtlUm90YXRpb25Gcm9tUXVhdGVybmlvbih0KSx0aGlzLnNldEZyb21Sb3RhdGlvbk1hdHJpeChvZyxlLGkpfXNldEZyb21WZWN0b3IzKHQsZT10aGlzLl9vcmRlcil7cmV0dXJuIHRoaXMuc2V0KHQueCx0LnksdC56LGUpfXJlb3JkZXIodCl7cmV0dXJuIGFnLnNldEZyb21FdWxlcih0aGlzKSx0aGlzLnNldEZyb21RdWF0ZXJuaW9uKGFnLHQpfWVxdWFscyh0KXtyZXR1cm4gdC5feD09PXRoaXMuX3gmJnQuX3k9PT10aGlzLl95JiZ0Ll96PT09dGhpcy5feiYmdC5fb3JkZXI9PT10aGlzLl9vcmRlcn1mcm9tQXJyYXkodCl7cmV0dXJuIHRoaXMuX3g9dFswXSx0aGlzLl95PXRbMV0sdGhpcy5fej10WzJdLHRbM10hPT12b2lkIDAmJih0aGlzLl9vcmRlcj10WzNdKSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc310b0FycmF5KHQ9W10sZT0wKXtyZXR1cm4gdFtlXT10aGlzLl94LHRbZSsxXT10aGlzLl95LHRbZSsyXT10aGlzLl96LHRbZSszXT10aGlzLl9vcmRlcix0fXRvVmVjdG9yMyh0KXtyZXR1cm4gdD90LnNldCh0aGlzLl94LHRoaXMuX3ksdGhpcy5feik6bmV3IFQodGhpcy5feCx0aGlzLl95LHRoaXMuX3opfV9vbkNoYW5nZSh0KXtyZXR1cm4gdGhpcy5fb25DaGFuZ2VDYWxsYmFjaz10LHRoaXN9X29uQ2hhbmdlQ2FsbGJhY2soKXt9fTthaS5wcm90b3R5cGUuaXNFdWxlcj0hMDthaS5EZWZhdWx0T3JkZXI9IlhZWiI7YWkuUm90YXRpb25PcmRlcnM9WyJYWVoiLCJZWlgiLCJaWFkiLCJYWlkiLCJZWFoiLCJaWVgiXTt2YXIgaWw9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLm1hc2s9MX1zZXQodCl7dGhpcy5tYXNrPSgxPDx0fDApPj4+MH1lbmFibGUodCl7dGhpcy5tYXNrfD0xPDx0fDB9ZW5hYmxlQWxsKCl7dGhpcy5tYXNrPS0xfXRvZ2dsZSh0KXt0aGlzLm1hc2tePTE8PHR8MH1kaXNhYmxlKHQpe3RoaXMubWFzayY9figxPDx0fDApfWRpc2FibGVBbGwoKXt0aGlzLm1hc2s9MH10ZXN0KHQpe3JldHVybih0aGlzLm1hc2smdC5tYXNrKSE9PTB9aXNFbmFibGVkKHQpe3JldHVybih0aGlzLm1hc2smKDE8PHR8MCkpIT09MH19LGFNPTAsbGc9bmV3IFQsdnI9bmV3IEVlLFJuPW5ldyB3dCxUYT1uZXcgVCxIcz1uZXcgVCxsTT1uZXcgVCxjTT1uZXcgRWUsY2c9bmV3IFQoMSwwLDApLHVnPW5ldyBUKDAsMSwwKSxoZz1uZXcgVCgwLDAsMSksdU09e3R5cGU6ImFkZGVkIn0sZmc9e3R5cGU6InJlbW92ZWQifSxrdD1jbGFzcyBleHRlbmRzIElue2NvbnN0cnVjdG9yKCl7c3VwZXIoKSxPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywiaWQiLHt2YWx1ZTphTSsrfSksdGhpcy51dWlkPXRuKCksdGhpcy5uYW1lPSIiLHRoaXMudHlwZT0iT2JqZWN0M0QiLHRoaXMucGFyZW50PW51bGwsdGhpcy5jaGlsZHJlbj1bXSx0aGlzLnVwPWt0LkRlZmF1bHRVcC5jbG9uZSgpO2xldCB0PW5ldyBULGU9bmV3IGFpLGk9bmV3IEVlLHI9bmV3IFQoMSwxLDEpO2Z1bmN0aW9uIHMoKXtpLnNldEZyb21FdWxlcihlLCExKX1mdW5jdGlvbiBvKCl7ZS5zZXRGcm9tUXVhdGVybmlvbihpLHZvaWQgMCwhMSl9ZS5fb25DaGFuZ2UocyksaS5fb25DaGFuZ2UobyksT2JqZWN0LmRlZmluZVByb3BlcnRpZXModGhpcyx7cG9zaXRpb246e2NvbmZpZ3VyYWJsZTohMCxlbnVtZXJhYmxlOiEwLHZhbHVlOnR9LHJvdGF0aW9uOntjb25maWd1cmFibGU6ITAsZW51bWVyYWJsZTohMCx2YWx1ZTplfSxxdWF0ZXJuaW9uOntjb25maWd1cmFibGU6ITAsZW51bWVyYWJsZTohMCx2YWx1ZTppfSxzY2FsZTp7Y29uZmlndXJhYmxlOiEwLGVudW1lcmFibGU6ITAsdmFsdWU6cn0sbW9kZWxWaWV3TWF0cml4Ont2YWx1ZTpuZXcgd3R9LG5vcm1hbE1hdHJpeDp7dmFsdWU6bmV3IGRlfX0pLHRoaXMubWF0cml4PW5ldyB3dCx0aGlzLm1hdHJpeFdvcmxkPW5ldyB3dCx0aGlzLm1hdHJpeEF1dG9VcGRhdGU9a3QuRGVmYXVsdE1hdHJpeEF1dG9VcGRhdGUsdGhpcy5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlPSExLHRoaXMubGF5ZXJzPW5ldyBpbCx0aGlzLnZpc2libGU9ITAsdGhpcy5jYXN0U2hhZG93PSExLHRoaXMucmVjZWl2ZVNoYWRvdz0hMSx0aGlzLmZydXN0dW1DdWxsZWQ9ITAsdGhpcy5yZW5kZXJPcmRlcj0wLHRoaXMuYW5pbWF0aW9ucz1bXSx0aGlzLnVzZXJEYXRhPXt9fW9uQmVmb3JlUmVuZGVyKCl7fW9uQWZ0ZXJSZW5kZXIoKXt9YXBwbHlNYXRyaXg0KHQpe3RoaXMubWF0cml4QXV0b1VwZGF0ZSYmdGhpcy51cGRhdGVNYXRyaXgoKSx0aGlzLm1hdHJpeC5wcmVtdWx0aXBseSh0KSx0aGlzLm1hdHJpeC5kZWNvbXBvc2UodGhpcy5wb3NpdGlvbix0aGlzLnF1YXRlcm5pb24sdGhpcy5zY2FsZSl9YXBwbHlRdWF0ZXJuaW9uKHQpe3JldHVybiB0aGlzLnF1YXRlcm5pb24ucHJlbXVsdGlwbHkodCksdGhpc31zZXRSb3RhdGlvbkZyb21BeGlzQW5nbGUodCxlKXt0aGlzLnF1YXRlcm5pb24uc2V0RnJvbUF4aXNBbmdsZSh0LGUpfXNldFJvdGF0aW9uRnJvbUV1bGVyKHQpe3RoaXMucXVhdGVybmlvbi5zZXRGcm9tRXVsZXIodCwhMCl9c2V0Um90YXRpb25Gcm9tTWF0cml4KHQpe3RoaXMucXVhdGVybmlvbi5zZXRGcm9tUm90YXRpb25NYXRyaXgodCl9c2V0Um90YXRpb25Gcm9tUXVhdGVybmlvbih0KXt0aGlzLnF1YXRlcm5pb24uY29weSh0KX1yb3RhdGVPbkF4aXModCxlKXtyZXR1cm4gdnIuc2V0RnJvbUF4aXNBbmdsZSh0LGUpLHRoaXMucXVhdGVybmlvbi5tdWx0aXBseSh2ciksdGhpc31yb3RhdGVPbldvcmxkQXhpcyh0LGUpe3JldHVybiB2ci5zZXRGcm9tQXhpc0FuZ2xlKHQsZSksdGhpcy5xdWF0ZXJuaW9uLnByZW11bHRpcGx5KHZyKSx0aGlzfXJvdGF0ZVgodCl7cmV0dXJuIHRoaXMucm90YXRlT25BeGlzKGNnLHQpfXJvdGF0ZVkodCl7cmV0dXJuIHRoaXMucm90YXRlT25BeGlzKHVnLHQpfXJvdGF0ZVoodCl7cmV0dXJuIHRoaXMucm90YXRlT25BeGlzKGhnLHQpfXRyYW5zbGF0ZU9uQXhpcyh0LGUpe3JldHVybiBsZy5jb3B5KHQpLmFwcGx5UXVhdGVybmlvbih0aGlzLnF1YXRlcm5pb24pLHRoaXMucG9zaXRpb24uYWRkKGxnLm11bHRpcGx5U2NhbGFyKGUpKSx0aGlzfXRyYW5zbGF0ZVgodCl7cmV0dXJuIHRoaXMudHJhbnNsYXRlT25BeGlzKGNnLHQpfXRyYW5zbGF0ZVkodCl7cmV0dXJuIHRoaXMudHJhbnNsYXRlT25BeGlzKHVnLHQpfXRyYW5zbGF0ZVoodCl7cmV0dXJuIHRoaXMudHJhbnNsYXRlT25BeGlzKGhnLHQpfWxvY2FsVG9Xb3JsZCh0KXtyZXR1cm4gdC5hcHBseU1hdHJpeDQodGhpcy5tYXRyaXhXb3JsZCl9d29ybGRUb0xvY2FsKHQpe3JldHVybiB0LmFwcGx5TWF0cml4NChSbi5jb3B5KHRoaXMubWF0cml4V29ybGQpLmludmVydCgpKX1sb29rQXQodCxlLGkpe3QuaXNWZWN0b3IzP1RhLmNvcHkodCk6VGEuc2V0KHQsZSxpKTtsZXQgcj10aGlzLnBhcmVudDt0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKSxIcy5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5tYXRyaXhXb3JsZCksdGhpcy5pc0NhbWVyYXx8dGhpcy5pc0xpZ2h0P1JuLmxvb2tBdChIcyxUYSx0aGlzLnVwKTpSbi5sb29rQXQoVGEsSHMsdGhpcy51cCksdGhpcy5xdWF0ZXJuaW9uLnNldEZyb21Sb3RhdGlvbk1hdHJpeChSbiksciYmKFJuLmV4dHJhY3RSb3RhdGlvbihyLm1hdHJpeFdvcmxkKSx2ci5zZXRGcm9tUm90YXRpb25NYXRyaXgoUm4pLHRoaXMucXVhdGVybmlvbi5wcmVtdWx0aXBseSh2ci5pbnZlcnQoKSkpfWFkZCh0KXtpZihhcmd1bWVudHMubGVuZ3RoPjEpe2ZvcihsZXQgZT0wO2U8YXJndW1lbnRzLmxlbmd0aDtlKyspdGhpcy5hZGQoYXJndW1lbnRzW2VdKTtyZXR1cm4gdGhpc31yZXR1cm4gdD09PXRoaXM/KGNvbnNvbGUuZXJyb3IoIlRIUkVFLk9iamVjdDNELmFkZDogb2JqZWN0IGNhbid0IGJlIGFkZGVkIGFzIGEgY2hpbGQgb2YgaXRzZWxmLiIsdCksdGhpcyk6KHQmJnQuaXNPYmplY3QzRD8odC5wYXJlbnQhPT1udWxsJiZ0LnBhcmVudC5yZW1vdmUodCksdC5wYXJlbnQ9dGhpcyx0aGlzLmNoaWxkcmVuLnB1c2godCksdC5kaXNwYXRjaEV2ZW50KHVNKSk6Y29uc29sZS5lcnJvcigiVEhSRUUuT2JqZWN0M0QuYWRkOiBvYmplY3Qgbm90IGFuIGluc3RhbmNlIG9mIFRIUkVFLk9iamVjdDNELiIsdCksdGhpcyl9cmVtb3ZlKHQpe2lmKGFyZ3VtZW50cy5sZW5ndGg+MSl7Zm9yKGxldCBpPTA7aTxhcmd1bWVudHMubGVuZ3RoO2krKyl0aGlzLnJlbW92ZShhcmd1bWVudHNbaV0pO3JldHVybiB0aGlzfWxldCBlPXRoaXMuY2hpbGRyZW4uaW5kZXhPZih0KTtyZXR1cm4gZSE9PS0xJiYodC5wYXJlbnQ9bnVsbCx0aGlzLmNoaWxkcmVuLnNwbGljZShlLDEpLHQuZGlzcGF0Y2hFdmVudChmZykpLHRoaXN9cmVtb3ZlRnJvbVBhcmVudCgpe2xldCB0PXRoaXMucGFyZW50O3JldHVybiB0IT09bnVsbCYmdC5yZW1vdmUodGhpcyksdGhpc31jbGVhcigpe2ZvcihsZXQgdD0wO3Q8dGhpcy5jaGlsZHJlbi5sZW5ndGg7dCsrKXtsZXQgZT10aGlzLmNoaWxkcmVuW3RdO2UucGFyZW50PW51bGwsZS5kaXNwYXRjaEV2ZW50KGZnKX1yZXR1cm4gdGhpcy5jaGlsZHJlbi5sZW5ndGg9MCx0aGlzfWF0dGFjaCh0KXtyZXR1cm4gdGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSksUm4uY29weSh0aGlzLm1hdHJpeFdvcmxkKS5pbnZlcnQoKSx0LnBhcmVudCE9PW51bGwmJih0LnBhcmVudC51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSksUm4ubXVsdGlwbHkodC5wYXJlbnQubWF0cml4V29ybGQpKSx0LmFwcGx5TWF0cml4NChSbiksdGhpcy5hZGQodCksdC51cGRhdGVXb3JsZE1hdHJpeCghMSwhMCksdGhpc31nZXRPYmplY3RCeUlkKHQpe3JldHVybiB0aGlzLmdldE9iamVjdEJ5UHJvcGVydHkoImlkIix0KX1nZXRPYmplY3RCeU5hbWUodCl7cmV0dXJuIHRoaXMuZ2V0T2JqZWN0QnlQcm9wZXJ0eSgibmFtZSIsdCl9Z2V0T2JqZWN0QnlQcm9wZXJ0eSh0LGUpe2lmKHRoaXNbdF09PT1lKXJldHVybiB0aGlzO2ZvcihsZXQgaT0wLHI9dGhpcy5jaGlsZHJlbi5sZW5ndGg7aTxyO2krKyl7bGV0IG89dGhpcy5jaGlsZHJlbltpXS5nZXRPYmplY3RCeVByb3BlcnR5KHQsZSk7aWYobyE9PXZvaWQgMClyZXR1cm4gb319Z2V0V29ybGRQb3NpdGlvbih0KXtyZXR1cm4gdGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSksdC5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5tYXRyaXhXb3JsZCl9Z2V0V29ybGRRdWF0ZXJuaW9uKHQpe3JldHVybiB0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKSx0aGlzLm1hdHJpeFdvcmxkLmRlY29tcG9zZShIcyx0LGxNKSx0fWdldFdvcmxkU2NhbGUodCl7cmV0dXJuIHRoaXMudXBkYXRlV29ybGRNYXRyaXgoITAsITEpLHRoaXMubWF0cml4V29ybGQuZGVjb21wb3NlKEhzLGNNLHQpLHR9Z2V0V29ybGREaXJlY3Rpb24odCl7dGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSk7bGV0IGU9dGhpcy5tYXRyaXhXb3JsZC5lbGVtZW50cztyZXR1cm4gdC5zZXQoZVs4XSxlWzldLGVbMTBdKS5ub3JtYWxpemUoKX1yYXljYXN0KCl7fXRyYXZlcnNlKHQpe3QodGhpcyk7bGV0IGU9dGhpcy5jaGlsZHJlbjtmb3IobGV0IGk9MCxyPWUubGVuZ3RoO2k8cjtpKyspZVtpXS50cmF2ZXJzZSh0KX10cmF2ZXJzZVZpc2libGUodCl7aWYodGhpcy52aXNpYmxlPT09ITEpcmV0dXJuO3QodGhpcyk7bGV0IGU9dGhpcy5jaGlsZHJlbjtmb3IobGV0IGk9MCxyPWUubGVuZ3RoO2k8cjtpKyspZVtpXS50cmF2ZXJzZVZpc2libGUodCl9dHJhdmVyc2VBbmNlc3RvcnModCl7bGV0IGU9dGhpcy5wYXJlbnQ7ZSE9PW51bGwmJih0KGUpLGUudHJhdmVyc2VBbmNlc3RvcnModCkpfXVwZGF0ZU1hdHJpeCgpe3RoaXMubWF0cml4LmNvbXBvc2UodGhpcy5wb3NpdGlvbix0aGlzLnF1YXRlcm5pb24sdGhpcy5zY2FsZSksdGhpcy5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlPSEwfXVwZGF0ZU1hdHJpeFdvcmxkKHQpe3RoaXMubWF0cml4QXV0b1VwZGF0ZSYmdGhpcy51cGRhdGVNYXRyaXgoKSwodGhpcy5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlfHx0KSYmKHRoaXMucGFyZW50PT09bnVsbD90aGlzLm1hdHJpeFdvcmxkLmNvcHkodGhpcy5tYXRyaXgpOnRoaXMubWF0cml4V29ybGQubXVsdGlwbHlNYXRyaWNlcyh0aGlzLnBhcmVudC5tYXRyaXhXb3JsZCx0aGlzLm1hdHJpeCksdGhpcy5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlPSExLHQ9ITApO2xldCBlPXRoaXMuY2hpbGRyZW47Zm9yKGxldCBpPTAscj1lLmxlbmd0aDtpPHI7aSsrKWVbaV0udXBkYXRlTWF0cml4V29ybGQodCl9dXBkYXRlV29ybGRNYXRyaXgodCxlKXtsZXQgaT10aGlzLnBhcmVudDtpZih0PT09ITAmJmkhPT1udWxsJiZpLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKSx0aGlzLm1hdHJpeEF1dG9VcGRhdGUmJnRoaXMudXBkYXRlTWF0cml4KCksdGhpcy5wYXJlbnQ9PT1udWxsP3RoaXMubWF0cml4V29ybGQuY29weSh0aGlzLm1hdHJpeCk6dGhpcy5tYXRyaXhXb3JsZC5tdWx0aXBseU1hdHJpY2VzKHRoaXMucGFyZW50Lm1hdHJpeFdvcmxkLHRoaXMubWF0cml4KSxlPT09ITApe2xldCByPXRoaXMuY2hpbGRyZW47Zm9yKGxldCBzPTAsbz1yLmxlbmd0aDtzPG87cysrKXJbc10udXBkYXRlV29ybGRNYXRyaXgoITEsITApfX10b0pTT04odCl7bGV0IGU9dD09PXZvaWQgMHx8dHlwZW9mIHQ9PSJzdHJpbmciLGk9e307ZSYmKHQ9e2dlb21ldHJpZXM6e30sbWF0ZXJpYWxzOnt9LHRleHR1cmVzOnt9LGltYWdlczp7fSxzaGFwZXM6e30sc2tlbGV0b25zOnt9LGFuaW1hdGlvbnM6e319LGkubWV0YWRhdGE9e3ZlcnNpb246NC41LHR5cGU6Ik9iamVjdCIsZ2VuZXJhdG9yOiJPYmplY3QzRC50b0pTT04ifSk7bGV0IHI9e307ci51dWlkPXRoaXMudXVpZCxyLnR5cGU9dGhpcy50eXBlLHRoaXMubmFtZSE9PSIiJiYoci5uYW1lPXRoaXMubmFtZSksdGhpcy5jYXN0U2hhZG93PT09ITAmJihyLmNhc3RTaGFkb3c9ITApLHRoaXMucmVjZWl2ZVNoYWRvdz09PSEwJiYoci5yZWNlaXZlU2hhZG93PSEwKSx0aGlzLnZpc2libGU9PT0hMSYmKHIudmlzaWJsZT0hMSksdGhpcy5mcnVzdHVtQ3VsbGVkPT09ITEmJihyLmZydXN0dW1DdWxsZWQ9ITEpLHRoaXMucmVuZGVyT3JkZXIhPT0wJiYoci5yZW5kZXJPcmRlcj10aGlzLnJlbmRlck9yZGVyKSxKU09OLnN0cmluZ2lmeSh0aGlzLnVzZXJEYXRhKSE9PSJ7fSImJihyLnVzZXJEYXRhPXRoaXMudXNlckRhdGEpLHIubGF5ZXJzPXRoaXMubGF5ZXJzLm1hc2ssci5tYXRyaXg9dGhpcy5tYXRyaXgudG9BcnJheSgpLHRoaXMubWF0cml4QXV0b1VwZGF0ZT09PSExJiYoci5tYXRyaXhBdXRvVXBkYXRlPSExKSx0aGlzLmlzSW5zdGFuY2VkTWVzaCYmKHIudHlwZT0iSW5zdGFuY2VkTWVzaCIsci5jb3VudD10aGlzLmNvdW50LHIuaW5zdGFuY2VNYXRyaXg9dGhpcy5pbnN0YW5jZU1hdHJpeC50b0pTT04oKSx0aGlzLmluc3RhbmNlQ29sb3IhPT1udWxsJiYoci5pbnN0YW5jZUNvbG9yPXRoaXMuaW5zdGFuY2VDb2xvci50b0pTT04oKSkpO2Z1bmN0aW9uIHMoYSxsKXtyZXR1cm4gYVtsLnV1aWRdPT09dm9pZCAwJiYoYVtsLnV1aWRdPWwudG9KU09OKHQpKSxsLnV1aWR9aWYodGhpcy5pc1NjZW5lKXRoaXMuYmFja2dyb3VuZCYmKHRoaXMuYmFja2dyb3VuZC5pc0NvbG9yP3IuYmFja2dyb3VuZD10aGlzLmJhY2tncm91bmQudG9KU09OKCk6dGhpcy5iYWNrZ3JvdW5kLmlzVGV4dHVyZSYmKHIuYmFja2dyb3VuZD10aGlzLmJhY2tncm91bmQudG9KU09OKHQpLnV1aWQpKSx0aGlzLmVudmlyb25tZW50JiZ0aGlzLmVudmlyb25tZW50LmlzVGV4dHVyZSYmKHIuZW52aXJvbm1lbnQ9dGhpcy5lbnZpcm9ubWVudC50b0pTT04odCkudXVpZCk7ZWxzZSBpZih0aGlzLmlzTWVzaHx8dGhpcy5pc0xpbmV8fHRoaXMuaXNQb2ludHMpe3IuZ2VvbWV0cnk9cyh0Lmdlb21ldHJpZXMsdGhpcy5nZW9tZXRyeSk7bGV0IGE9dGhpcy5nZW9tZXRyeS5wYXJhbWV0ZXJzO2lmKGEhPT12b2lkIDAmJmEuc2hhcGVzIT09dm9pZCAwKXtsZXQgbD1hLnNoYXBlcztpZihBcnJheS5pc0FycmF5KGwpKWZvcihsZXQgYz0wLHU9bC5sZW5ndGg7Yzx1O2MrKyl7bGV0IGg9bFtjXTtzKHQuc2hhcGVzLGgpfWVsc2Ugcyh0LnNoYXBlcyxsKX19aWYodGhpcy5pc1NraW5uZWRNZXNoJiYoci5iaW5kTW9kZT10aGlzLmJpbmRNb2RlLHIuYmluZE1hdHJpeD10aGlzLmJpbmRNYXRyaXgudG9BcnJheSgpLHRoaXMuc2tlbGV0b24hPT12b2lkIDAmJihzKHQuc2tlbGV0b25zLHRoaXMuc2tlbGV0b24pLHIuc2tlbGV0b249dGhpcy5za2VsZXRvbi51dWlkKSksdGhpcy5tYXRlcmlhbCE9PXZvaWQgMClpZihBcnJheS5pc0FycmF5KHRoaXMubWF0ZXJpYWwpKXtsZXQgYT1bXTtmb3IobGV0IGw9MCxjPXRoaXMubWF0ZXJpYWwubGVuZ3RoO2w8YztsKyspYS5wdXNoKHModC5tYXRlcmlhbHMsdGhpcy5tYXRlcmlhbFtsXSkpO3IubWF0ZXJpYWw9YX1lbHNlIHIubWF0ZXJpYWw9cyh0Lm1hdGVyaWFscyx0aGlzLm1hdGVyaWFsKTtpZih0aGlzLmNoaWxkcmVuLmxlbmd0aD4wKXtyLmNoaWxkcmVuPVtdO2ZvcihsZXQgYT0wO2E8dGhpcy5jaGlsZHJlbi5sZW5ndGg7YSsrKXIuY2hpbGRyZW4ucHVzaCh0aGlzLmNoaWxkcmVuW2FdLnRvSlNPTih0KS5vYmplY3QpfWlmKHRoaXMuYW5pbWF0aW9ucy5sZW5ndGg+MCl7ci5hbmltYXRpb25zPVtdO2ZvcihsZXQgYT0wO2E8dGhpcy5hbmltYXRpb25zLmxlbmd0aDthKyspe2xldCBsPXRoaXMuYW5pbWF0aW9uc1thXTtyLmFuaW1hdGlvbnMucHVzaChzKHQuYW5pbWF0aW9ucyxsKSl9fWlmKGUpe2xldCBhPW8odC5nZW9tZXRyaWVzKSxsPW8odC5tYXRlcmlhbHMpLGM9byh0LnRleHR1cmVzKSx1PW8odC5pbWFnZXMpLGg9byh0LnNoYXBlcyksZj1vKHQuc2tlbGV0b25zKSxkPW8odC5hbmltYXRpb25zKTthLmxlbmd0aD4wJiYoaS5nZW9tZXRyaWVzPWEpLGwubGVuZ3RoPjAmJihpLm1hdGVyaWFscz1sKSxjLmxlbmd0aD4wJiYoaS50ZXh0dXJlcz1jKSx1Lmxlbmd0aD4wJiYoaS5pbWFnZXM9dSksaC5sZW5ndGg+MCYmKGkuc2hhcGVzPWgpLGYubGVuZ3RoPjAmJihpLnNrZWxldG9ucz1mKSxkLmxlbmd0aD4wJiYoaS5hbmltYXRpb25zPWQpfXJldHVybiBpLm9iamVjdD1yLGk7ZnVuY3Rpb24gbyhhKXtsZXQgbD1bXTtmb3IobGV0IGMgaW4gYSl7bGV0IHU9YVtjXTtkZWxldGUgdS5tZXRhZGF0YSxsLnB1c2godSl9cmV0dXJuIGx9fWNsb25lKHQpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyx0KX1jb3B5KHQsZT0hMCl7aWYodGhpcy5uYW1lPXQubmFtZSx0aGlzLnVwLmNvcHkodC51cCksdGhpcy5wb3NpdGlvbi5jb3B5KHQucG9zaXRpb24pLHRoaXMucm90YXRpb24ub3JkZXI9dC5yb3RhdGlvbi5vcmRlcix0aGlzLnF1YXRlcm5pb24uY29weSh0LnF1YXRlcm5pb24pLHRoaXMuc2NhbGUuY29weSh0LnNjYWxlKSx0aGlzLm1hdHJpeC5jb3B5KHQubWF0cml4KSx0aGlzLm1hdHJpeFdvcmxkLmNvcHkodC5tYXRyaXhXb3JsZCksdGhpcy5tYXRyaXhBdXRvVXBkYXRlPXQubWF0cml4QXV0b1VwZGF0ZSx0aGlzLm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9dC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlLHRoaXMubGF5ZXJzLm1hc2s9dC5sYXllcnMubWFzayx0aGlzLnZpc2libGU9dC52aXNpYmxlLHRoaXMuY2FzdFNoYWRvdz10LmNhc3RTaGFkb3csdGhpcy5yZWNlaXZlU2hhZG93PXQucmVjZWl2ZVNoYWRvdyx0aGlzLmZydXN0dW1DdWxsZWQ9dC5mcnVzdHVtQ3VsbGVkLHRoaXMucmVuZGVyT3JkZXI9dC5yZW5kZXJPcmRlcix0aGlzLnVzZXJEYXRhPUpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkodC51c2VyRGF0YSkpLGU9PT0hMClmb3IobGV0IGk9MDtpPHQuY2hpbGRyZW4ubGVuZ3RoO2krKyl7bGV0IHI9dC5jaGlsZHJlbltpXTt0aGlzLmFkZChyLmNsb25lKCkpfXJldHVybiB0aGlzfX07a3QuRGVmYXVsdFVwPW5ldyBUKDAsMSwwKTtrdC5EZWZhdWx0TWF0cml4QXV0b1VwZGF0ZT0hMDtrdC5wcm90b3R5cGUuaXNPYmplY3QzRD0hMDt2YXIgUWU9bmV3IFQsTG49bmV3IFQsbXU9bmV3IFQsUG49bmV3IFQsX3I9bmV3IFQsd3I9bmV3IFQsZGc9bmV3IFQsZ3U9bmV3IFQseHU9bmV3IFQseXU9bmV3IFQscmU9Y2xhc3N7Y29uc3RydWN0b3IodD1uZXcgVCxlPW5ldyBULGk9bmV3IFQpe3RoaXMuYT10LHRoaXMuYj1lLHRoaXMuYz1pfXN0YXRpYyBnZXROb3JtYWwodCxlLGkscil7ci5zdWJWZWN0b3JzKGksZSksUWUuc3ViVmVjdG9ycyh0LGUpLHIuY3Jvc3MoUWUpO2xldCBzPXIubGVuZ3RoU3EoKTtyZXR1cm4gcz4wP3IubXVsdGlwbHlTY2FsYXIoMS9NYXRoLnNxcnQocykpOnIuc2V0KDAsMCwwKX1zdGF0aWMgZ2V0QmFyeWNvb3JkKHQsZSxpLHIscyl7UWUuc3ViVmVjdG9ycyhyLGUpLExuLnN1YlZlY3RvcnMoaSxlKSxtdS5zdWJWZWN0b3JzKHQsZSk7bGV0IG89UWUuZG90KFFlKSxhPVFlLmRvdChMbiksbD1RZS5kb3QobXUpLGM9TG4uZG90KExuKSx1PUxuLmRvdChtdSksaD1vKmMtYSphO2lmKGg9PT0wKXJldHVybiBzLnNldCgtMiwtMSwtMSk7bGV0IGY9MS9oLGQ9KGMqbC1hKnUpKmYsZz0obyp1LWEqbCkqZjtyZXR1cm4gcy5zZXQoMS1kLWcsZyxkKX1zdGF0aWMgY29udGFpbnNQb2ludCh0LGUsaSxyKXtyZXR1cm4gdGhpcy5nZXRCYXJ5Y29vcmQodCxlLGkscixQbiksUG4ueD49MCYmUG4ueT49MCYmUG4ueCtQbi55PD0xfXN0YXRpYyBnZXRVVih0LGUsaSxyLHMsbyxhLGwpe3JldHVybiB0aGlzLmdldEJhcnljb29yZCh0LGUsaSxyLFBuKSxsLnNldCgwLDApLGwuYWRkU2NhbGVkVmVjdG9yKHMsUG4ueCksbC5hZGRTY2FsZWRWZWN0b3IobyxQbi55KSxsLmFkZFNjYWxlZFZlY3RvcihhLFBuLnopLGx9c3RhdGljIGlzRnJvbnRGYWNpbmcodCxlLGkscil7cmV0dXJuIFFlLnN1YlZlY3RvcnMoaSxlKSxMbi5zdWJWZWN0b3JzKHQsZSksUWUuY3Jvc3MoTG4pLmRvdChyKTwwfXNldCh0LGUsaSl7cmV0dXJuIHRoaXMuYS5jb3B5KHQpLHRoaXMuYi5jb3B5KGUpLHRoaXMuYy5jb3B5KGkpLHRoaXN9c2V0RnJvbVBvaW50c0FuZEluZGljZXModCxlLGkscil7cmV0dXJuIHRoaXMuYS5jb3B5KHRbZV0pLHRoaXMuYi5jb3B5KHRbaV0pLHRoaXMuYy5jb3B5KHRbcl0pLHRoaXN9c2V0RnJvbUF0dHJpYnV0ZUFuZEluZGljZXModCxlLGkscil7cmV0dXJuIHRoaXMuYS5mcm9tQnVmZmVyQXR0cmlidXRlKHQsZSksdGhpcy5iLmZyb21CdWZmZXJBdHRyaWJ1dGUodCxpKSx0aGlzLmMuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LHIpLHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfWNvcHkodCl7cmV0dXJuIHRoaXMuYS5jb3B5KHQuYSksdGhpcy5iLmNvcHkodC5iKSx0aGlzLmMuY29weSh0LmMpLHRoaXN9Z2V0QXJlYSgpe3JldHVybiBRZS5zdWJWZWN0b3JzKHRoaXMuYyx0aGlzLmIpLExuLnN1YlZlY3RvcnModGhpcy5hLHRoaXMuYiksUWUuY3Jvc3MoTG4pLmxlbmd0aCgpKi41fWdldE1pZHBvaW50KHQpe3JldHVybiB0LmFkZFZlY3RvcnModGhpcy5hLHRoaXMuYikuYWRkKHRoaXMuYykubXVsdGlwbHlTY2FsYXIoMS8zKX1nZXROb3JtYWwodCl7cmV0dXJuIHJlLmdldE5vcm1hbCh0aGlzLmEsdGhpcy5iLHRoaXMuYyx0KX1nZXRQbGFuZSh0KXtyZXR1cm4gdC5zZXRGcm9tQ29wbGFuYXJQb2ludHModGhpcy5hLHRoaXMuYix0aGlzLmMpfWdldEJhcnljb29yZCh0LGUpe3JldHVybiByZS5nZXRCYXJ5Y29vcmQodCx0aGlzLmEsdGhpcy5iLHRoaXMuYyxlKX1nZXRVVih0LGUsaSxyLHMpe3JldHVybiByZS5nZXRVVih0LHRoaXMuYSx0aGlzLmIsdGhpcy5jLGUsaSxyLHMpfWNvbnRhaW5zUG9pbnQodCl7cmV0dXJuIHJlLmNvbnRhaW5zUG9pbnQodCx0aGlzLmEsdGhpcy5iLHRoaXMuYyl9aXNGcm9udEZhY2luZyh0KXtyZXR1cm4gcmUuaXNGcm9udEZhY2luZyh0aGlzLmEsdGhpcy5iLHRoaXMuYyx0KX1pbnRlcnNlY3RzQm94KHQpe3JldHVybiB0LmludGVyc2VjdHNUcmlhbmdsZSh0aGlzKX1jbG9zZXN0UG9pbnRUb1BvaW50KHQsZSl7bGV0IGk9dGhpcy5hLHI9dGhpcy5iLHM9dGhpcy5jLG8sYTtfci5zdWJWZWN0b3JzKHIsaSksd3Iuc3ViVmVjdG9ycyhzLGkpLGd1LnN1YlZlY3RvcnModCxpKTtsZXQgbD1fci5kb3QoZ3UpLGM9d3IuZG90KGd1KTtpZihsPD0wJiZjPD0wKXJldHVybiBlLmNvcHkoaSk7eHUuc3ViVmVjdG9ycyh0LHIpO2xldCB1PV9yLmRvdCh4dSksaD13ci5kb3QoeHUpO2lmKHU+PTAmJmg8PXUpcmV0dXJuIGUuY29weShyKTtsZXQgZj1sKmgtdSpjO2lmKGY8PTAmJmw+PTAmJnU8PTApcmV0dXJuIG89bC8obC11KSxlLmNvcHkoaSkuYWRkU2NhbGVkVmVjdG9yKF9yLG8pO3l1LnN1YlZlY3RvcnModCxzKTtsZXQgZD1fci5kb3QoeXUpLGc9d3IuZG90KHl1KTtpZihnPj0wJiZkPD1nKXJldHVybiBlLmNvcHkocyk7bGV0IHg9ZCpjLWwqZztpZih4PD0wJiZjPj0wJiZnPD0wKXJldHVybiBhPWMvKGMtZyksZS5jb3B5KGkpLmFkZFNjYWxlZFZlY3Rvcih3cixhKTtsZXQgdj11KmctZCpoO2lmKHY8PTAmJmgtdT49MCYmZC1nPj0wKXJldHVybiBkZy5zdWJWZWN0b3JzKHMsciksYT0oaC11KS8oaC11KyhkLWcpKSxlLmNvcHkocikuYWRkU2NhbGVkVmVjdG9yKGRnLGEpO2xldCBtPTEvKHYreCtmKTtyZXR1cm4gbz14Km0sYT1mKm0sZS5jb3B5KGkpLmFkZFNjYWxlZFZlY3RvcihfcixvKS5hZGRTY2FsZWRWZWN0b3Iod3IsYSl9ZXF1YWxzKHQpe3JldHVybiB0LmEuZXF1YWxzKHRoaXMuYSkmJnQuYi5lcXVhbHModGhpcy5iKSYmdC5jLmVxdWFscyh0aGlzLmMpfX0saE09MCx4ZT1jbGFzcyBleHRlbmRzIElue2NvbnN0cnVjdG9yKCl7c3VwZXIoKSxPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywiaWQiLHt2YWx1ZTpoTSsrfSksdGhpcy51dWlkPXRuKCksdGhpcy5uYW1lPSIiLHRoaXMudHlwZT0iTWF0ZXJpYWwiLHRoaXMuZm9nPSEwLHRoaXMuYmxlbmRpbmc9S3MsdGhpcy5zaWRlPWVvLHRoaXMudmVydGV4Q29sb3JzPSExLHRoaXMub3BhY2l0eT0xLHRoaXMudHJhbnNwYXJlbnQ9ITEsdGhpcy5ibGVuZFNyYz11MCx0aGlzLmJsZW5kRHN0PWgwLHRoaXMuYmxlbmRFcXVhdGlvbj1Jcix0aGlzLmJsZW5kU3JjQWxwaGE9bnVsbCx0aGlzLmJsZW5kRHN0QWxwaGE9bnVsbCx0aGlzLmJsZW5kRXF1YXRpb25BbHBoYT1udWxsLHRoaXMuZGVwdGhGdW5jPXp1LHRoaXMuZGVwdGhUZXN0PSEwLHRoaXMuZGVwdGhXcml0ZT0hMCx0aGlzLnN0ZW5jaWxXcml0ZU1hc2s9MjU1LHRoaXMuc3RlbmNpbEZ1bmM9dE0sdGhpcy5zdGVuY2lsUmVmPTAsdGhpcy5zdGVuY2lsRnVuY01hc2s9MjU1LHRoaXMuc3RlbmNpbEZhaWw9bnUsdGhpcy5zdGVuY2lsWkZhaWw9bnUsdGhpcy5zdGVuY2lsWlBhc3M9bnUsdGhpcy5zdGVuY2lsV3JpdGU9ITEsdGhpcy5jbGlwcGluZ1BsYW5lcz1udWxsLHRoaXMuY2xpcEludGVyc2VjdGlvbj0hMSx0aGlzLmNsaXBTaGFkb3dzPSExLHRoaXMuc2hhZG93U2lkZT1udWxsLHRoaXMuY29sb3JXcml0ZT0hMCx0aGlzLmFscGhhV3JpdGU9ITAsdGhpcy5wcmVjaXNpb249bnVsbCx0aGlzLnBvbHlnb25PZmZzZXQ9ITEsdGhpcy5wb2x5Z29uT2Zmc2V0RmFjdG9yPTAsdGhpcy5wb2x5Z29uT2Zmc2V0VW5pdHM9MCx0aGlzLmRpdGhlcmluZz0hMSx0aGlzLmFscGhhVG9Db3ZlcmFnZT0hMSx0aGlzLnByZW11bHRpcGxpZWRBbHBoYT0hMSx0aGlzLnZpc2libGU9ITAsdGhpcy50b25lTWFwcGVkPSEwLHRoaXMudXNlckRhdGE9e30sdGhpcy52ZXJzaW9uPTAsdGhpcy5fYWxwaGFUZXN0PTB9Z2V0IGFscGhhVGVzdCgpe3JldHVybiB0aGlzLl9hbHBoYVRlc3R9c2V0IGFscGhhVGVzdCh0KXt0aGlzLl9hbHBoYVRlc3Q+MCE9dD4wJiZ0aGlzLnZlcnNpb24rKyx0aGlzLl9hbHBoYVRlc3Q9dH1vbkJ1aWxkKCl7fW9uQmVmb3JlUmVuZGVyKCl7fW9uQmVmb3JlQ29tcGlsZSgpe31jdXN0b21Qcm9ncmFtQ2FjaGVLZXkoKXtyZXR1cm4gdGhpcy5vbkJlZm9yZUNvbXBpbGUudG9TdHJpbmcoKX1zZXRWYWx1ZXModCl7aWYodCE9PXZvaWQgMClmb3IobGV0IGUgaW4gdCl7bGV0IGk9dFtlXTtpZihpPT09dm9pZCAwKXtjb25zb2xlLndhcm4oIlRIUkVFLk1hdGVyaWFsOiAnIitlKyInIHBhcmFtZXRlciBpcyB1bmRlZmluZWQuIik7Y29udGludWV9aWYoZT09PSJzaGFkaW5nIil7Y29uc29sZS53YXJuKCJUSFJFRS4iK3RoaXMudHlwZSsiOiAuc2hhZGluZyBoYXMgYmVlbiByZW1vdmVkLiBVc2UgdGhlIGJvb2xlYW4gLmZsYXRTaGFkaW5nIGluc3RlYWQuIiksdGhpcy5mbGF0U2hhZGluZz1pPT09YzA7Y29udGludWV9bGV0IHI9dGhpc1tlXTtpZihyPT09dm9pZCAwKXtjb25zb2xlLndhcm4oIlRIUkVFLiIrdGhpcy50eXBlKyI6ICciK2UrIicgaXMgbm90IGEgcHJvcGVydHkgb2YgdGhpcyBtYXRlcmlhbC4iKTtjb250aW51ZX1yJiZyLmlzQ29sb3I/ci5zZXQoaSk6ciYmci5pc1ZlY3RvcjMmJmkmJmkuaXNWZWN0b3IzP3IuY29weShpKTp0aGlzW2VdPWl9fXRvSlNPTih0KXtsZXQgZT10PT09dm9pZCAwfHx0eXBlb2YgdD09InN0cmluZyI7ZSYmKHQ9e3RleHR1cmVzOnt9LGltYWdlczp7fX0pO2xldCBpPXttZXRhZGF0YTp7dmVyc2lvbjo0LjUsdHlwZToiTWF0ZXJpYWwiLGdlbmVyYXRvcjoiTWF0ZXJpYWwudG9KU09OIn19O2kudXVpZD10aGlzLnV1aWQsaS50eXBlPXRoaXMudHlwZSx0aGlzLm5hbWUhPT0iIiYmKGkubmFtZT10aGlzLm5hbWUpLHRoaXMuY29sb3ImJnRoaXMuY29sb3IuaXNDb2xvciYmKGkuY29sb3I9dGhpcy5jb2xvci5nZXRIZXgoKSksdGhpcy5yb3VnaG5lc3MhPT12b2lkIDAmJihpLnJvdWdobmVzcz10aGlzLnJvdWdobmVzcyksdGhpcy5tZXRhbG5lc3MhPT12b2lkIDAmJihpLm1ldGFsbmVzcz10aGlzLm1ldGFsbmVzcyksdGhpcy5zaGVlbiE9PXZvaWQgMCYmKGkuc2hlZW49dGhpcy5zaGVlbiksdGhpcy5zaGVlbkNvbG9yJiZ0aGlzLnNoZWVuQ29sb3IuaXNDb2xvciYmKGkuc2hlZW5Db2xvcj10aGlzLnNoZWVuQ29sb3IuZ2V0SGV4KCkpLHRoaXMuc2hlZW5Sb3VnaG5lc3MhPT12b2lkIDAmJihpLnNoZWVuUm91Z2huZXNzPXRoaXMuc2hlZW5Sb3VnaG5lc3MpLHRoaXMuZW1pc3NpdmUmJnRoaXMuZW1pc3NpdmUuaXNDb2xvciYmKGkuZW1pc3NpdmU9dGhpcy5lbWlzc2l2ZS5nZXRIZXgoKSksdGhpcy5lbWlzc2l2ZUludGVuc2l0eSYmdGhpcy5lbWlzc2l2ZUludGVuc2l0eSE9PTEmJihpLmVtaXNzaXZlSW50ZW5zaXR5PXRoaXMuZW1pc3NpdmVJbnRlbnNpdHkpLHRoaXMuc3BlY3VsYXImJnRoaXMuc3BlY3VsYXIuaXNDb2xvciYmKGkuc3BlY3VsYXI9dGhpcy5zcGVjdWxhci5nZXRIZXgoKSksdGhpcy5zcGVjdWxhckludGVuc2l0eSE9PXZvaWQgMCYmKGkuc3BlY3VsYXJJbnRlbnNpdHk9dGhpcy5zcGVjdWxhckludGVuc2l0eSksdGhpcy5zcGVjdWxhckNvbG9yJiZ0aGlzLnNwZWN1bGFyQ29sb3IuaXNDb2xvciYmKGkuc3BlY3VsYXJDb2xvcj10aGlzLnNwZWN1bGFyQ29sb3IuZ2V0SGV4KCkpLHRoaXMuc2hpbmluZXNzIT09dm9pZCAwJiYoaS5zaGluaW5lc3M9dGhpcy5zaGluaW5lc3MpLHRoaXMuY2xlYXJjb2F0IT09dm9pZCAwJiYoaS5jbGVhcmNvYXQ9dGhpcy5jbGVhcmNvYXQpLHRoaXMuY2xlYXJjb2F0Um91Z2huZXNzIT09dm9pZCAwJiYoaS5jbGVhcmNvYXRSb3VnaG5lc3M9dGhpcy5jbGVhcmNvYXRSb3VnaG5lc3MpLHRoaXMuY2xlYXJjb2F0TWFwJiZ0aGlzLmNsZWFyY29hdE1hcC5pc1RleHR1cmUmJihpLmNsZWFyY29hdE1hcD10aGlzLmNsZWFyY29hdE1hcC50b0pTT04odCkudXVpZCksdGhpcy5jbGVhcmNvYXRSb3VnaG5lc3NNYXAmJnRoaXMuY2xlYXJjb2F0Um91Z2huZXNzTWFwLmlzVGV4dHVyZSYmKGkuY2xlYXJjb2F0Um91Z2huZXNzTWFwPXRoaXMuY2xlYXJjb2F0Um91Z2huZXNzTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLmNsZWFyY29hdE5vcm1hbE1hcCYmdGhpcy5jbGVhcmNvYXROb3JtYWxNYXAuaXNUZXh0dXJlJiYoaS5jbGVhcmNvYXROb3JtYWxNYXA9dGhpcy5jbGVhcmNvYXROb3JtYWxNYXAudG9KU09OKHQpLnV1aWQsaS5jbGVhcmNvYXROb3JtYWxTY2FsZT10aGlzLmNsZWFyY29hdE5vcm1hbFNjYWxlLnRvQXJyYXkoKSksdGhpcy5tYXAmJnRoaXMubWFwLmlzVGV4dHVyZSYmKGkubWFwPXRoaXMubWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLm1hdGNhcCYmdGhpcy5tYXRjYXAuaXNUZXh0dXJlJiYoaS5tYXRjYXA9dGhpcy5tYXRjYXAudG9KU09OKHQpLnV1aWQpLHRoaXMuYWxwaGFNYXAmJnRoaXMuYWxwaGFNYXAuaXNUZXh0dXJlJiYoaS5hbHBoYU1hcD10aGlzLmFscGhhTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLmxpZ2h0TWFwJiZ0aGlzLmxpZ2h0TWFwLmlzVGV4dHVyZSYmKGkubGlnaHRNYXA9dGhpcy5saWdodE1hcC50b0pTT04odCkudXVpZCxpLmxpZ2h0TWFwSW50ZW5zaXR5PXRoaXMubGlnaHRNYXBJbnRlbnNpdHkpLHRoaXMuYW9NYXAmJnRoaXMuYW9NYXAuaXNUZXh0dXJlJiYoaS5hb01hcD10aGlzLmFvTWFwLnRvSlNPTih0KS51dWlkLGkuYW9NYXBJbnRlbnNpdHk9dGhpcy5hb01hcEludGVuc2l0eSksdGhpcy5idW1wTWFwJiZ0aGlzLmJ1bXBNYXAuaXNUZXh0dXJlJiYoaS5idW1wTWFwPXRoaXMuYnVtcE1hcC50b0pTT04odCkudXVpZCxpLmJ1bXBTY2FsZT10aGlzLmJ1bXBTY2FsZSksdGhpcy5ub3JtYWxNYXAmJnRoaXMubm9ybWFsTWFwLmlzVGV4dHVyZSYmKGkubm9ybWFsTWFwPXRoaXMubm9ybWFsTWFwLnRvSlNPTih0KS51dWlkLGkubm9ybWFsTWFwVHlwZT10aGlzLm5vcm1hbE1hcFR5cGUsaS5ub3JtYWxTY2FsZT10aGlzLm5vcm1hbFNjYWxlLnRvQXJyYXkoKSksdGhpcy5kaXNwbGFjZW1lbnRNYXAmJnRoaXMuZGlzcGxhY2VtZW50TWFwLmlzVGV4dHVyZSYmKGkuZGlzcGxhY2VtZW50TWFwPXRoaXMuZGlzcGxhY2VtZW50TWFwLnRvSlNPTih0KS51dWlkLGkuZGlzcGxhY2VtZW50U2NhbGU9dGhpcy5kaXNwbGFjZW1lbnRTY2FsZSxpLmRpc3BsYWNlbWVudEJpYXM9dGhpcy5kaXNwbGFjZW1lbnRCaWFzKSx0aGlzLnJvdWdobmVzc01hcCYmdGhpcy5yb3VnaG5lc3NNYXAuaXNUZXh0dXJlJiYoaS5yb3VnaG5lc3NNYXA9dGhpcy5yb3VnaG5lc3NNYXAudG9KU09OKHQpLnV1aWQpLHRoaXMubWV0YWxuZXNzTWFwJiZ0aGlzLm1ldGFsbmVzc01hcC5pc1RleHR1cmUmJihpLm1ldGFsbmVzc01hcD10aGlzLm1ldGFsbmVzc01hcC50b0pTT04odCkudXVpZCksdGhpcy5lbWlzc2l2ZU1hcCYmdGhpcy5lbWlzc2l2ZU1hcC5pc1RleHR1cmUmJihpLmVtaXNzaXZlTWFwPXRoaXMuZW1pc3NpdmVNYXAudG9KU09OKHQpLnV1aWQpLHRoaXMuc3BlY3VsYXJNYXAmJnRoaXMuc3BlY3VsYXJNYXAuaXNUZXh0dXJlJiYoaS5zcGVjdWxhck1hcD10aGlzLnNwZWN1bGFyTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLnNwZWN1bGFySW50ZW5zaXR5TWFwJiZ0aGlzLnNwZWN1bGFySW50ZW5zaXR5TWFwLmlzVGV4dHVyZSYmKGkuc3BlY3VsYXJJbnRlbnNpdHlNYXA9dGhpcy5zcGVjdWxhckludGVuc2l0eU1hcC50b0pTT04odCkudXVpZCksdGhpcy5zcGVjdWxhckNvbG9yTWFwJiZ0aGlzLnNwZWN1bGFyQ29sb3JNYXAuaXNUZXh0dXJlJiYoaS5zcGVjdWxhckNvbG9yTWFwPXRoaXMuc3BlY3VsYXJDb2xvck1hcC50b0pTT04odCkudXVpZCksdGhpcy5lbnZNYXAmJnRoaXMuZW52TWFwLmlzVGV4dHVyZSYmKGkuZW52TWFwPXRoaXMuZW52TWFwLnRvSlNPTih0KS51dWlkLHRoaXMuY29tYmluZSE9PXZvaWQgMCYmKGkuY29tYmluZT10aGlzLmNvbWJpbmUpKSx0aGlzLmVudk1hcEludGVuc2l0eSE9PXZvaWQgMCYmKGkuZW52TWFwSW50ZW5zaXR5PXRoaXMuZW52TWFwSW50ZW5zaXR5KSx0aGlzLnJlZmxlY3Rpdml0eSE9PXZvaWQgMCYmKGkucmVmbGVjdGl2aXR5PXRoaXMucmVmbGVjdGl2aXR5KSx0aGlzLnJlZnJhY3Rpb25SYXRpbyE9PXZvaWQgMCYmKGkucmVmcmFjdGlvblJhdGlvPXRoaXMucmVmcmFjdGlvblJhdGlvKSx0aGlzLmdyYWRpZW50TWFwJiZ0aGlzLmdyYWRpZW50TWFwLmlzVGV4dHVyZSYmKGkuZ3JhZGllbnRNYXA9dGhpcy5ncmFkaWVudE1hcC50b0pTT04odCkudXVpZCksdGhpcy50cmFuc21pc3Npb24hPT12b2lkIDAmJihpLnRyYW5zbWlzc2lvbj10aGlzLnRyYW5zbWlzc2lvbiksdGhpcy50cmFuc21pc3Npb25NYXAmJnRoaXMudHJhbnNtaXNzaW9uTWFwLmlzVGV4dHVyZSYmKGkudHJhbnNtaXNzaW9uTWFwPXRoaXMudHJhbnNtaXNzaW9uTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLnRoaWNrbmVzcyE9PXZvaWQgMCYmKGkudGhpY2tuZXNzPXRoaXMudGhpY2tuZXNzKSx0aGlzLnRoaWNrbmVzc01hcCYmdGhpcy50aGlja25lc3NNYXAuaXNUZXh0dXJlJiYoaS50aGlja25lc3NNYXA9dGhpcy50aGlja25lc3NNYXAudG9KU09OKHQpLnV1aWQpLHRoaXMuYXR0ZW51YXRpb25EaXN0YW5jZSE9PXZvaWQgMCYmKGkuYXR0ZW51YXRpb25EaXN0YW5jZT10aGlzLmF0dGVudWF0aW9uRGlzdGFuY2UpLHRoaXMuYXR0ZW51YXRpb25Db2xvciE9PXZvaWQgMCYmKGkuYXR0ZW51YXRpb25Db2xvcj10aGlzLmF0dGVudWF0aW9uQ29sb3IuZ2V0SGV4KCkpLHRoaXMuc2l6ZSE9PXZvaWQgMCYmKGkuc2l6ZT10aGlzLnNpemUpLHRoaXMuc2hhZG93U2lkZSE9PW51bGwmJihpLnNoYWRvd1NpZGU9dGhpcy5zaGFkb3dTaWRlKSx0aGlzLnNpemVBdHRlbnVhdGlvbiE9PXZvaWQgMCYmKGkuc2l6ZUF0dGVudWF0aW9uPXRoaXMuc2l6ZUF0dGVudWF0aW9uKSx0aGlzLmJsZW5kaW5nIT09S3MmJihpLmJsZW5kaW5nPXRoaXMuYmxlbmRpbmcpLHRoaXMuc2lkZSE9PWVvJiYoaS5zaWRlPXRoaXMuc2lkZSksdGhpcy52ZXJ0ZXhDb2xvcnMmJihpLnZlcnRleENvbG9ycz0hMCksdGhpcy5vcGFjaXR5PDEmJihpLm9wYWNpdHk9dGhpcy5vcGFjaXR5KSx0aGlzLnRyYW5zcGFyZW50PT09ITAmJihpLnRyYW5zcGFyZW50PXRoaXMudHJhbnNwYXJlbnQpLGkuZGVwdGhGdW5jPXRoaXMuZGVwdGhGdW5jLGkuZGVwdGhUZXN0PXRoaXMuZGVwdGhUZXN0LGkuZGVwdGhXcml0ZT10aGlzLmRlcHRoV3JpdGUsaS5jb2xvcldyaXRlPXRoaXMuY29sb3JXcml0ZSxpLmFscGhhV3JpdGU9dGhpcy5hbHBoYVdyaXRlLGkuc3RlbmNpbFdyaXRlPXRoaXMuc3RlbmNpbFdyaXRlLGkuc3RlbmNpbFdyaXRlTWFzaz10aGlzLnN0ZW5jaWxXcml0ZU1hc2ssaS5zdGVuY2lsRnVuYz10aGlzLnN0ZW5jaWxGdW5jLGkuc3RlbmNpbFJlZj10aGlzLnN0ZW5jaWxSZWYsaS5zdGVuY2lsRnVuY01hc2s9dGhpcy5zdGVuY2lsRnVuY01hc2ssaS5zdGVuY2lsRmFpbD10aGlzLnN0ZW5jaWxGYWlsLGkuc3RlbmNpbFpGYWlsPXRoaXMuc3RlbmNpbFpGYWlsLGkuc3RlbmNpbFpQYXNzPXRoaXMuc3RlbmNpbFpQYXNzLHRoaXMucm90YXRpb24mJnRoaXMucm90YXRpb24hPT0wJiYoaS5yb3RhdGlvbj10aGlzLnJvdGF0aW9uKSx0aGlzLnBvbHlnb25PZmZzZXQ9PT0hMCYmKGkucG9seWdvbk9mZnNldD0hMCksdGhpcy5wb2x5Z29uT2Zmc2V0RmFjdG9yIT09MCYmKGkucG9seWdvbk9mZnNldEZhY3Rvcj10aGlzLnBvbHlnb25PZmZzZXRGYWN0b3IpLHRoaXMucG9seWdvbk9mZnNldFVuaXRzIT09MCYmKGkucG9seWdvbk9mZnNldFVuaXRzPXRoaXMucG9seWdvbk9mZnNldFVuaXRzKSx0aGlzLmxpbmV3aWR0aCYmdGhpcy5saW5ld2lkdGghPT0xJiYoaS5saW5ld2lkdGg9dGhpcy5saW5ld2lkdGgpLHRoaXMuZGFzaFNpemUhPT12b2lkIDAmJihpLmRhc2hTaXplPXRoaXMuZGFzaFNpemUpLHRoaXMuZ2FwU2l6ZSE9PXZvaWQgMCYmKGkuZ2FwU2l6ZT10aGlzLmdhcFNpemUpLHRoaXMuc2NhbGUhPT12b2lkIDAmJihpLnNjYWxlPXRoaXMuc2NhbGUpLHRoaXMuZGl0aGVyaW5nPT09ITAmJihpLmRpdGhlcmluZz0hMCksdGhpcy5hbHBoYVRlc3Q+MCYmKGkuYWxwaGFUZXN0PXRoaXMuYWxwaGFUZXN0KSx0aGlzLmFscGhhVG9Db3ZlcmFnZT09PSEwJiYoaS5hbHBoYVRvQ292ZXJhZ2U9dGhpcy5hbHBoYVRvQ292ZXJhZ2UpLHRoaXMucHJlbXVsdGlwbGllZEFscGhhPT09ITAmJihpLnByZW11bHRpcGxpZWRBbHBoYT10aGlzLnByZW11bHRpcGxpZWRBbHBoYSksdGhpcy53aXJlZnJhbWU9PT0hMCYmKGkud2lyZWZyYW1lPXRoaXMud2lyZWZyYW1lKSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD4xJiYoaS53aXJlZnJhbWVMaW5ld2lkdGg9dGhpcy53aXJlZnJhbWVMaW5ld2lkdGgpLHRoaXMud2lyZWZyYW1lTGluZWNhcCE9PSJyb3VuZCImJihpLndpcmVmcmFtZUxpbmVjYXA9dGhpcy53aXJlZnJhbWVMaW5lY2FwKSx0aGlzLndpcmVmcmFtZUxpbmVqb2luIT09InJvdW5kIiYmKGkud2lyZWZyYW1lTGluZWpvaW49dGhpcy53aXJlZnJhbWVMaW5lam9pbiksdGhpcy5mbGF0U2hhZGluZz09PSEwJiYoaS5mbGF0U2hhZGluZz10aGlzLmZsYXRTaGFkaW5nKSx0aGlzLnZpc2libGU9PT0hMSYmKGkudmlzaWJsZT0hMSksdGhpcy50b25lTWFwcGVkPT09ITEmJihpLnRvbmVNYXBwZWQ9ITEpLEpTT04uc3RyaW5naWZ5KHRoaXMudXNlckRhdGEpIT09Int9IiYmKGkudXNlckRhdGE9dGhpcy51c2VyRGF0YSk7ZnVuY3Rpb24gcihzKXtsZXQgbz1bXTtmb3IobGV0IGEgaW4gcyl7bGV0IGw9c1thXTtkZWxldGUgbC5tZXRhZGF0YSxvLnB1c2gobCl9cmV0dXJuIG99aWYoZSl7bGV0IHM9cih0LnRleHR1cmVzKSxvPXIodC5pbWFnZXMpO3MubGVuZ3RoPjAmJihpLnRleHR1cmVzPXMpLG8ubGVuZ3RoPjAmJihpLmltYWdlcz1vKX1yZXR1cm4gaX1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyl9Y29weSh0KXt0aGlzLm5hbWU9dC5uYW1lLHRoaXMuZm9nPXQuZm9nLHRoaXMuYmxlbmRpbmc9dC5ibGVuZGluZyx0aGlzLnNpZGU9dC5zaWRlLHRoaXMudmVydGV4Q29sb3JzPXQudmVydGV4Q29sb3JzLHRoaXMub3BhY2l0eT10Lm9wYWNpdHksdGhpcy50cmFuc3BhcmVudD10LnRyYW5zcGFyZW50LHRoaXMuYmxlbmRTcmM9dC5ibGVuZFNyYyx0aGlzLmJsZW5kRHN0PXQuYmxlbmREc3QsdGhpcy5ibGVuZEVxdWF0aW9uPXQuYmxlbmRFcXVhdGlvbix0aGlzLmJsZW5kU3JjQWxwaGE9dC5ibGVuZFNyY0FscGhhLHRoaXMuYmxlbmREc3RBbHBoYT10LmJsZW5kRHN0QWxwaGEsdGhpcy5ibGVuZEVxdWF0aW9uQWxwaGE9dC5ibGVuZEVxdWF0aW9uQWxwaGEsdGhpcy5kZXB0aEZ1bmM9dC5kZXB0aEZ1bmMsdGhpcy5kZXB0aFRlc3Q9dC5kZXB0aFRlc3QsdGhpcy5kZXB0aFdyaXRlPXQuZGVwdGhXcml0ZSx0aGlzLnN0ZW5jaWxXcml0ZU1hc2s9dC5zdGVuY2lsV3JpdGVNYXNrLHRoaXMuc3RlbmNpbEZ1bmM9dC5zdGVuY2lsRnVuYyx0aGlzLnN0ZW5jaWxSZWY9dC5zdGVuY2lsUmVmLHRoaXMuc3RlbmNpbEZ1bmNNYXNrPXQuc3RlbmNpbEZ1bmNNYXNrLHRoaXMuc3RlbmNpbEZhaWw9dC5zdGVuY2lsRmFpbCx0aGlzLnN0ZW5jaWxaRmFpbD10LnN0ZW5jaWxaRmFpbCx0aGlzLnN0ZW5jaWxaUGFzcz10LnN0ZW5jaWxaUGFzcyx0aGlzLnN0ZW5jaWxXcml0ZT10LnN0ZW5jaWxXcml0ZTtsZXQgZT10LmNsaXBwaW5nUGxhbmVzLGk9bnVsbDtpZihlIT09bnVsbCl7bGV0IHI9ZS5sZW5ndGg7aT1uZXcgQXJyYXkocik7Zm9yKGxldCBzPTA7cyE9PXI7KytzKWlbc109ZVtzXS5jbG9uZSgpfXJldHVybiB0aGlzLmNsaXBwaW5nUGxhbmVzPWksdGhpcy5jbGlwSW50ZXJzZWN0aW9uPXQuY2xpcEludGVyc2VjdGlvbix0aGlzLmNsaXBTaGFkb3dzPXQuY2xpcFNoYWRvd3MsdGhpcy5zaGFkb3dTaWRlPXQuc2hhZG93U2lkZSx0aGlzLmNvbG9yV3JpdGU9dC5jb2xvcldyaXRlLHRoaXMuYWxwaGFXcml0ZT10LmFscGhhV3JpdGUsdGhpcy5wcmVjaXNpb249dC5wcmVjaXNpb24sdGhpcy5wb2x5Z29uT2Zmc2V0PXQucG9seWdvbk9mZnNldCx0aGlzLnBvbHlnb25PZmZzZXRGYWN0b3I9dC5wb2x5Z29uT2Zmc2V0RmFjdG9yLHRoaXMucG9seWdvbk9mZnNldFVuaXRzPXQucG9seWdvbk9mZnNldFVuaXRzLHRoaXMuZGl0aGVyaW5nPXQuZGl0aGVyaW5nLHRoaXMuYWxwaGFUZXN0PXQuYWxwaGFUZXN0LHRoaXMuYWxwaGFUb0NvdmVyYWdlPXQuYWxwaGFUb0NvdmVyYWdlLHRoaXMucHJlbXVsdGlwbGllZEFscGhhPXQucHJlbXVsdGlwbGllZEFscGhhLHRoaXMudmlzaWJsZT10LnZpc2libGUsdGhpcy50b25lTWFwcGVkPXQudG9uZU1hcHBlZCx0aGlzLnVzZXJEYXRhPUpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkodC51c2VyRGF0YSkpLHRoaXN9ZGlzcG9zZSgpe3RoaXMuZGlzcGF0Y2hFdmVudCh7dHlwZToiZGlzcG9zZSJ9KX1zZXQgbmVlZHNVcGRhdGUodCl7dD09PSEwJiZ0aGlzLnZlcnNpb24rK319O3hlLnByb3RvdHlwZS5pc01hdGVyaWFsPSEwO3ZhciBraT1jbGFzcyBleHRlbmRzIHhle2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJNZXNoQmFzaWNNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgZnQoMTY3NzcyMTUpLHRoaXMubWFwPW51bGwsdGhpcy5saWdodE1hcD1udWxsLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9MSx0aGlzLmFvTWFwPW51bGwsdGhpcy5hb01hcEludGVuc2l0eT0xLHRoaXMuc3BlY3VsYXJNYXA9bnVsbCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy5lbnZNYXA9bnVsbCx0aGlzLmNvbWJpbmU9Q2wsdGhpcy5yZWZsZWN0aXZpdHk9MSx0aGlzLnJlZnJhY3Rpb25SYXRpbz0uOTgsdGhpcy53aXJlZnJhbWU9ITEsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9MSx0aGlzLndpcmVmcmFtZUxpbmVjYXA9InJvdW5kIix0aGlzLndpcmVmcmFtZUxpbmVqb2luPSJyb3VuZCIsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5tYXA9dC5tYXAsdGhpcy5saWdodE1hcD10LmxpZ2h0TWFwLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9dC5saWdodE1hcEludGVuc2l0eSx0aGlzLmFvTWFwPXQuYW9NYXAsdGhpcy5hb01hcEludGVuc2l0eT10LmFvTWFwSW50ZW5zaXR5LHRoaXMuc3BlY3VsYXJNYXA9dC5zcGVjdWxhck1hcCx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5lbnZNYXA9dC5lbnZNYXAsdGhpcy5jb21iaW5lPXQuY29tYmluZSx0aGlzLnJlZmxlY3Rpdml0eT10LnJlZmxlY3Rpdml0eSx0aGlzLnJlZnJhY3Rpb25SYXRpbz10LnJlZnJhY3Rpb25SYXRpbyx0aGlzLndpcmVmcmFtZT10LndpcmVmcmFtZSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD10LndpcmVmcmFtZUxpbmV3aWR0aCx0aGlzLndpcmVmcmFtZUxpbmVjYXA9dC53aXJlZnJhbWVMaW5lY2FwLHRoaXMud2lyZWZyYW1lTGluZWpvaW49dC53aXJlZnJhbWVMaW5lam9pbix0aGlzfX07a2kucHJvdG90eXBlLmlzTWVzaEJhc2ljTWF0ZXJpYWw9ITA7dmFyIEp0PW5ldyBULEFhPW5ldyBLLFF0PWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXtpZihBcnJheS5pc0FycmF5KHQpKXRocm93IG5ldyBUeXBlRXJyb3IoIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZTogYXJyYXkgc2hvdWxkIGJlIGEgVHlwZWQgQXJyYXkuIik7dGhpcy5uYW1lPSIiLHRoaXMuYXJyYXk9dCx0aGlzLml0ZW1TaXplPWUsdGhpcy5jb3VudD10IT09dm9pZCAwP3QubGVuZ3RoL2U6MCx0aGlzLm5vcm1hbGl6ZWQ9aT09PSEwLHRoaXMudXNhZ2U9aW8sdGhpcy51cGRhdGVSYW5nZT17b2Zmc2V0OjAsY291bnQ6LTF9LHRoaXMudmVyc2lvbj0wfW9uVXBsb2FkQ2FsbGJhY2soKXt9c2V0IG5lZWRzVXBkYXRlKHQpe3Q9PT0hMCYmdGhpcy52ZXJzaW9uKyt9c2V0VXNhZ2UodCl7cmV0dXJuIHRoaXMudXNhZ2U9dCx0aGlzfWNvcHkodCl7cmV0dXJuIHRoaXMubmFtZT10Lm5hbWUsdGhpcy5hcnJheT1uZXcgdC5hcnJheS5jb25zdHJ1Y3Rvcih0LmFycmF5KSx0aGlzLml0ZW1TaXplPXQuaXRlbVNpemUsdGhpcy5jb3VudD10LmNvdW50LHRoaXMubm9ybWFsaXplZD10Lm5vcm1hbGl6ZWQsdGhpcy51c2FnZT10LnVzYWdlLHRoaXN9Y29weUF0KHQsZSxpKXt0Kj10aGlzLml0ZW1TaXplLGkqPWUuaXRlbVNpemU7Zm9yKGxldCByPTAscz10aGlzLml0ZW1TaXplO3I8cztyKyspdGhpcy5hcnJheVt0K3JdPWUuYXJyYXlbaStyXTtyZXR1cm4gdGhpc31jb3B5QXJyYXkodCl7cmV0dXJuIHRoaXMuYXJyYXkuc2V0KHQpLHRoaXN9Y29weUNvbG9yc0FycmF5KHQpe2xldCBlPXRoaXMuYXJyYXksaT0wO2ZvcihsZXQgcj0wLHM9dC5sZW5ndGg7cjxzO3IrKyl7bGV0IG89dFtyXTtvPT09dm9pZCAwJiYoY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGUuY29weUNvbG9yc0FycmF5KCk6IGNvbG9yIGlzIHVuZGVmaW5lZCIsciksbz1uZXcgZnQpLGVbaSsrXT1vLnIsZVtpKytdPW8uZyxlW2krK109by5ifXJldHVybiB0aGlzfWNvcHlWZWN0b3Iyc0FycmF5KHQpe2xldCBlPXRoaXMuYXJyYXksaT0wO2ZvcihsZXQgcj0wLHM9dC5sZW5ndGg7cjxzO3IrKyl7bGV0IG89dFtyXTtvPT09dm9pZCAwJiYoY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGUuY29weVZlY3RvcjJzQXJyYXkoKTogdmVjdG9yIGlzIHVuZGVmaW5lZCIsciksbz1uZXcgSyksZVtpKytdPW8ueCxlW2krK109by55fXJldHVybiB0aGlzfWNvcHlWZWN0b3Izc0FycmF5KHQpe2xldCBlPXRoaXMuYXJyYXksaT0wO2ZvcihsZXQgcj0wLHM9dC5sZW5ndGg7cjxzO3IrKyl7bGV0IG89dFtyXTtvPT09dm9pZCAwJiYoY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGUuY29weVZlY3RvcjNzQXJyYXkoKTogdmVjdG9yIGlzIHVuZGVmaW5lZCIsciksbz1uZXcgVCksZVtpKytdPW8ueCxlW2krK109by55LGVbaSsrXT1vLnp9cmV0dXJuIHRoaXN9Y29weVZlY3RvcjRzQXJyYXkodCl7bGV0IGU9dGhpcy5hcnJheSxpPTA7Zm9yKGxldCByPTAscz10Lmxlbmd0aDtyPHM7cisrKXtsZXQgbz10W3JdO289PT12b2lkIDAmJihjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZS5jb3B5VmVjdG9yNHNBcnJheSgpOiB2ZWN0b3IgaXMgdW5kZWZpbmVkIixyKSxvPW5ldyBXdCksZVtpKytdPW8ueCxlW2krK109by55LGVbaSsrXT1vLnosZVtpKytdPW8ud31yZXR1cm4gdGhpc31hcHBseU1hdHJpeDModCl7aWYodGhpcy5pdGVtU2l6ZT09PTIpZm9yKGxldCBlPTAsaT10aGlzLmNvdW50O2U8aTtlKyspQWEuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0aGlzLGUpLEFhLmFwcGx5TWF0cml4Myh0KSx0aGlzLnNldFhZKGUsQWEueCxBYS55KTtlbHNlIGlmKHRoaXMuaXRlbVNpemU9PT0zKWZvcihsZXQgZT0wLGk9dGhpcy5jb3VudDtlPGk7ZSsrKUp0LmZyb21CdWZmZXJBdHRyaWJ1dGUodGhpcyxlKSxKdC5hcHBseU1hdHJpeDModCksdGhpcy5zZXRYWVooZSxKdC54LEp0LnksSnQueik7cmV0dXJuIHRoaXN9YXBwbHlNYXRyaXg0KHQpe2ZvcihsZXQgZT0wLGk9dGhpcy5jb3VudDtlPGk7ZSsrKUp0Lng9dGhpcy5nZXRYKGUpLEp0Lnk9dGhpcy5nZXRZKGUpLEp0Lno9dGhpcy5nZXRaKGUpLEp0LmFwcGx5TWF0cml4NCh0KSx0aGlzLnNldFhZWihlLEp0LngsSnQueSxKdC56KTtyZXR1cm4gdGhpc31hcHBseU5vcm1hbE1hdHJpeCh0KXtmb3IobGV0IGU9MCxpPXRoaXMuY291bnQ7ZTxpO2UrKylKdC54PXRoaXMuZ2V0WChlKSxKdC55PXRoaXMuZ2V0WShlKSxKdC56PXRoaXMuZ2V0WihlKSxKdC5hcHBseU5vcm1hbE1hdHJpeCh0KSx0aGlzLnNldFhZWihlLEp0LngsSnQueSxKdC56KTtyZXR1cm4gdGhpc310cmFuc2Zvcm1EaXJlY3Rpb24odCl7Zm9yKGxldCBlPTAsaT10aGlzLmNvdW50O2U8aTtlKyspSnQueD10aGlzLmdldFgoZSksSnQueT10aGlzLmdldFkoZSksSnQuej10aGlzLmdldFooZSksSnQudHJhbnNmb3JtRGlyZWN0aW9uKHQpLHRoaXMuc2V0WFlaKGUsSnQueCxKdC55LEp0LnopO3JldHVybiB0aGlzfXNldCh0LGU9MCl7cmV0dXJuIHRoaXMuYXJyYXkuc2V0KHQsZSksdGhpc31nZXRYKHQpe3JldHVybiB0aGlzLmFycmF5W3QqdGhpcy5pdGVtU2l6ZV19c2V0WCh0LGUpe3JldHVybiB0aGlzLmFycmF5W3QqdGhpcy5pdGVtU2l6ZV09ZSx0aGlzfWdldFkodCl7cmV0dXJuIHRoaXMuYXJyYXlbdCp0aGlzLml0ZW1TaXplKzFdfXNldFkodCxlKXtyZXR1cm4gdGhpcy5hcnJheVt0KnRoaXMuaXRlbVNpemUrMV09ZSx0aGlzfWdldFoodCl7cmV0dXJuIHRoaXMuYXJyYXlbdCp0aGlzLml0ZW1TaXplKzJdfXNldFoodCxlKXtyZXR1cm4gdGhpcy5hcnJheVt0KnRoaXMuaXRlbVNpemUrMl09ZSx0aGlzfWdldFcodCl7cmV0dXJuIHRoaXMuYXJyYXlbdCp0aGlzLml0ZW1TaXplKzNdfXNldFcodCxlKXtyZXR1cm4gdGhpcy5hcnJheVt0KnRoaXMuaXRlbVNpemUrM109ZSx0aGlzfXNldFhZKHQsZSxpKXtyZXR1cm4gdCo9dGhpcy5pdGVtU2l6ZSx0aGlzLmFycmF5W3QrMF09ZSx0aGlzLmFycmF5W3QrMV09aSx0aGlzfXNldFhZWih0LGUsaSxyKXtyZXR1cm4gdCo9dGhpcy5pdGVtU2l6ZSx0aGlzLmFycmF5W3QrMF09ZSx0aGlzLmFycmF5W3QrMV09aSx0aGlzLmFycmF5W3QrMl09cix0aGlzfXNldFhZWlcodCxlLGkscixzKXtyZXR1cm4gdCo9dGhpcy5pdGVtU2l6ZSx0aGlzLmFycmF5W3QrMF09ZSx0aGlzLmFycmF5W3QrMV09aSx0aGlzLmFycmF5W3QrMl09cix0aGlzLmFycmF5W3QrM109cyx0aGlzfW9uVXBsb2FkKHQpe3JldHVybiB0aGlzLm9uVXBsb2FkQ2FsbGJhY2s9dCx0aGlzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKHRoaXMuYXJyYXksdGhpcy5pdGVtU2l6ZSkuY29weSh0aGlzKX10b0pTT04oKXtsZXQgdD17aXRlbVNpemU6dGhpcy5pdGVtU2l6ZSx0eXBlOnRoaXMuYXJyYXkuY29uc3RydWN0b3IubmFtZSxhcnJheTpBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbCh0aGlzLmFycmF5KSxub3JtYWxpemVkOnRoaXMubm9ybWFsaXplZH07cmV0dXJuIHRoaXMubmFtZSE9PSIiJiYodC5uYW1lPXRoaXMubmFtZSksdGhpcy51c2FnZSE9PWlvJiYodC51c2FnZT10aGlzLnVzYWdlKSwodGhpcy51cGRhdGVSYW5nZS5vZmZzZXQhPT0wfHx0aGlzLnVwZGF0ZVJhbmdlLmNvdW50IT09LTEpJiYodC51cGRhdGVSYW5nZT10aGlzLnVwZGF0ZVJhbmdlKSx0fX07UXQucHJvdG90eXBlLmlzQnVmZmVyQXR0cmlidXRlPSEwO3ZhciBybD1jbGFzcyBleHRlbmRzIFF0e2NvbnN0cnVjdG9yKHQsZSxpKXtzdXBlcihuZXcgVWludDE2QXJyYXkodCksZSxpKX19O3ZhciBzbD1jbGFzcyBleHRlbmRzIFF0e2NvbnN0cnVjdG9yKHQsZSxpKXtzdXBlcihuZXcgVWludDMyQXJyYXkodCksZSxpKX19LFd1PWNsYXNzIGV4dGVuZHMgUXR7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKG5ldyBVaW50MTZBcnJheSh0KSxlLGkpfX07V3UucHJvdG90eXBlLmlzRmxvYXQxNkJ1ZmZlckF0dHJpYnV0ZT0hMDt2YXIgZWU9Y2xhc3MgZXh0ZW5kcyBRdHtjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIobmV3IEZsb2F0MzJBcnJheSh0KSxlLGkpfX07dmFyIGZNPTAscWU9bmV3IHd0LHZ1PW5ldyBrdCxNcj1uZXcgVCxIZT1uZXcgR2UsVnM9bmV3IEdlLGdlPW5ldyBULEh0PWNsYXNzIGV4dGVuZHMgSW57Y29uc3RydWN0b3IoKXtzdXBlcigpLE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCJpZCIse3ZhbHVlOmZNKyt9KSx0aGlzLnV1aWQ9dG4oKSx0aGlzLm5hbWU9IiIsdGhpcy50eXBlPSJCdWZmZXJHZW9tZXRyeSIsdGhpcy5pbmRleD1udWxsLHRoaXMuYXR0cmlidXRlcz17fSx0aGlzLm1vcnBoQXR0cmlidXRlcz17fSx0aGlzLm1vcnBoVGFyZ2V0c1JlbGF0aXZlPSExLHRoaXMuZ3JvdXBzPVtdLHRoaXMuYm91bmRpbmdCb3g9bnVsbCx0aGlzLmJvdW5kaW5nU3BoZXJlPW51bGwsdGhpcy5kcmF3UmFuZ2U9e3N0YXJ0OjAsY291bnQ6MS8wfSx0aGlzLnVzZXJEYXRhPXt9fWdldEluZGV4KCl7cmV0dXJuIHRoaXMuaW5kZXh9c2V0SW5kZXgodCl7cmV0dXJuIEFycmF5LmlzQXJyYXkodCk/dGhpcy5pbmRleD1uZXcocDAodCk/c2w6cmwpKHQsMSk6dGhpcy5pbmRleD10LHRoaXN9Z2V0QXR0cmlidXRlKHQpe3JldHVybiB0aGlzLmF0dHJpYnV0ZXNbdF19c2V0QXR0cmlidXRlKHQsZSl7cmV0dXJuIHRoaXMuYXR0cmlidXRlc1t0XT1lLHRoaXN9ZGVsZXRlQXR0cmlidXRlKHQpe3JldHVybiBkZWxldGUgdGhpcy5hdHRyaWJ1dGVzW3RdLHRoaXN9aGFzQXR0cmlidXRlKHQpe3JldHVybiB0aGlzLmF0dHJpYnV0ZXNbdF0hPT12b2lkIDB9YWRkR3JvdXAodCxlLGk9MCl7dGhpcy5ncm91cHMucHVzaCh7c3RhcnQ6dCxjb3VudDplLG1hdGVyaWFsSW5kZXg6aX0pfWNsZWFyR3JvdXBzKCl7dGhpcy5ncm91cHM9W119c2V0RHJhd1JhbmdlKHQsZSl7dGhpcy5kcmF3UmFuZ2Uuc3RhcnQ9dCx0aGlzLmRyYXdSYW5nZS5jb3VudD1lfWFwcGx5TWF0cml4NCh0KXtsZXQgZT10aGlzLmF0dHJpYnV0ZXMucG9zaXRpb247ZSE9PXZvaWQgMCYmKGUuYXBwbHlNYXRyaXg0KHQpLGUubmVlZHNVcGRhdGU9ITApO2xldCBpPXRoaXMuYXR0cmlidXRlcy5ub3JtYWw7aWYoaSE9PXZvaWQgMCl7bGV0IHM9bmV3IGRlKCkuZ2V0Tm9ybWFsTWF0cml4KHQpO2kuYXBwbHlOb3JtYWxNYXRyaXgocyksaS5uZWVkc1VwZGF0ZT0hMH1sZXQgcj10aGlzLmF0dHJpYnV0ZXMudGFuZ2VudDtyZXR1cm4gciE9PXZvaWQgMCYmKHIudHJhbnNmb3JtRGlyZWN0aW9uKHQpLHIubmVlZHNVcGRhdGU9ITApLHRoaXMuYm91bmRpbmdCb3ghPT1udWxsJiZ0aGlzLmNvbXB1dGVCb3VuZGluZ0JveCgpLHRoaXMuYm91bmRpbmdTcGhlcmUhPT1udWxsJiZ0aGlzLmNvbXB1dGVCb3VuZGluZ1NwaGVyZSgpLHRoaXN9YXBwbHlRdWF0ZXJuaW9uKHQpe3JldHVybiBxZS5tYWtlUm90YXRpb25Gcm9tUXVhdGVybmlvbih0KSx0aGlzLmFwcGx5TWF0cml4NChxZSksdGhpc31yb3RhdGVYKHQpe3JldHVybiBxZS5tYWtlUm90YXRpb25YKHQpLHRoaXMuYXBwbHlNYXRyaXg0KHFlKSx0aGlzfXJvdGF0ZVkodCl7cmV0dXJuIHFlLm1ha2VSb3RhdGlvblkodCksdGhpcy5hcHBseU1hdHJpeDQocWUpLHRoaXN9cm90YXRlWih0KXtyZXR1cm4gcWUubWFrZVJvdGF0aW9uWih0KSx0aGlzLmFwcGx5TWF0cml4NChxZSksdGhpc310cmFuc2xhdGUodCxlLGkpe3JldHVybiBxZS5tYWtlVHJhbnNsYXRpb24odCxlLGkpLHRoaXMuYXBwbHlNYXRyaXg0KHFlKSx0aGlzfXNjYWxlKHQsZSxpKXtyZXR1cm4gcWUubWFrZVNjYWxlKHQsZSxpKSx0aGlzLmFwcGx5TWF0cml4NChxZSksdGhpc31sb29rQXQodCl7cmV0dXJuIHZ1Lmxvb2tBdCh0KSx2dS51cGRhdGVNYXRyaXgoKSx0aGlzLmFwcGx5TWF0cml4NCh2dS5tYXRyaXgpLHRoaXN9Y2VudGVyKCl7cmV0dXJuIHRoaXMuY29tcHV0ZUJvdW5kaW5nQm94KCksdGhpcy5ib3VuZGluZ0JveC5nZXRDZW50ZXIoTXIpLm5lZ2F0ZSgpLHRoaXMudHJhbnNsYXRlKE1yLngsTXIueSxNci56KSx0aGlzfXNldEZyb21Qb2ludHModCl7bGV0IGU9W107Zm9yKGxldCBpPTAscj10Lmxlbmd0aDtpPHI7aSsrKXtsZXQgcz10W2ldO2UucHVzaChzLngscy55LHMuenx8MCl9cmV0dXJuIHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IGVlKGUsMykpLHRoaXN9Y29tcHV0ZUJvdW5kaW5nQm94KCl7dGhpcy5ib3VuZGluZ0JveD09PW51bGwmJih0aGlzLmJvdW5kaW5nQm94PW5ldyBHZSk7bGV0IHQ9dGhpcy5hdHRyaWJ1dGVzLnBvc2l0aW9uLGU9dGhpcy5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb247aWYodCYmdC5pc0dMQnVmZmVyQXR0cmlidXRlKXtjb25zb2xlLmVycm9yKCdUSFJFRS5CdWZmZXJHZW9tZXRyeS5jb21wdXRlQm91bmRpbmdCb3goKTogR0xCdWZmZXJBdHRyaWJ1dGUgcmVxdWlyZXMgYSBtYW51YWwgYm91bmRpbmcgYm94LiBBbHRlcm5hdGl2ZWx5IHNldCAibWVzaC5mcnVzdHVtQ3VsbGVkIiB0byAiZmFsc2UiLicsdGhpcyksdGhpcy5ib3VuZGluZ0JveC5zZXQobmV3IFQoLTEvMCwtMS8wLC0xLzApLG5ldyBUKDEvMCwxLzAsMS8wKSk7cmV0dXJufWlmKHQhPT12b2lkIDApe2lmKHRoaXMuYm91bmRpbmdCb3guc2V0RnJvbUJ1ZmZlckF0dHJpYnV0ZSh0KSxlKWZvcihsZXQgaT0wLHI9ZS5sZW5ndGg7aTxyO2krKyl7bGV0IHM9ZVtpXTtIZS5zZXRGcm9tQnVmZmVyQXR0cmlidXRlKHMpLHRoaXMubW9ycGhUYXJnZXRzUmVsYXRpdmU/KGdlLmFkZFZlY3RvcnModGhpcy5ib3VuZGluZ0JveC5taW4sSGUubWluKSx0aGlzLmJvdW5kaW5nQm94LmV4cGFuZEJ5UG9pbnQoZ2UpLGdlLmFkZFZlY3RvcnModGhpcy5ib3VuZGluZ0JveC5tYXgsSGUubWF4KSx0aGlzLmJvdW5kaW5nQm94LmV4cGFuZEJ5UG9pbnQoZ2UpKToodGhpcy5ib3VuZGluZ0JveC5leHBhbmRCeVBvaW50KEhlLm1pbiksdGhpcy5ib3VuZGluZ0JveC5leHBhbmRCeVBvaW50KEhlLm1heCkpfX1lbHNlIHRoaXMuYm91bmRpbmdCb3gubWFrZUVtcHR5KCk7KGlzTmFOKHRoaXMuYm91bmRpbmdCb3gubWluLngpfHxpc05hTih0aGlzLmJvdW5kaW5nQm94Lm1pbi55KXx8aXNOYU4odGhpcy5ib3VuZGluZ0JveC5taW4ueikpJiZjb25zb2xlLmVycm9yKCdUSFJFRS5CdWZmZXJHZW9tZXRyeS5jb21wdXRlQm91bmRpbmdCb3goKTogQ29tcHV0ZWQgbWluL21heCBoYXZlIE5hTiB2YWx1ZXMuIFRoZSAicG9zaXRpb24iIGF0dHJpYnV0ZSBpcyBsaWtlbHkgdG8gaGF2ZSBOYU4gdmFsdWVzLicsdGhpcyl9Y29tcHV0ZUJvdW5kaW5nU3BoZXJlKCl7dGhpcy5ib3VuZGluZ1NwaGVyZT09PW51bGwmJih0aGlzLmJvdW5kaW5nU3BoZXJlPW5ldyBzaSk7bGV0IHQ9dGhpcy5hdHRyaWJ1dGVzLnBvc2l0aW9uLGU9dGhpcy5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb247aWYodCYmdC5pc0dMQnVmZmVyQXR0cmlidXRlKXtjb25zb2xlLmVycm9yKCdUSFJFRS5CdWZmZXJHZW9tZXRyeS5jb21wdXRlQm91bmRpbmdTcGhlcmUoKTogR0xCdWZmZXJBdHRyaWJ1dGUgcmVxdWlyZXMgYSBtYW51YWwgYm91bmRpbmcgc3BoZXJlLiBBbHRlcm5hdGl2ZWx5IHNldCAibWVzaC5mcnVzdHVtQ3VsbGVkIiB0byAiZmFsc2UiLicsdGhpcyksdGhpcy5ib3VuZGluZ1NwaGVyZS5zZXQobmV3IFQsMS8wKTtyZXR1cm59aWYodCl7bGV0IGk9dGhpcy5ib3VuZGluZ1NwaGVyZS5jZW50ZXI7aWYoSGUuc2V0RnJvbUJ1ZmZlckF0dHJpYnV0ZSh0KSxlKWZvcihsZXQgcz0wLG89ZS5sZW5ndGg7czxvO3MrKyl7bGV0IGE9ZVtzXTtWcy5zZXRGcm9tQnVmZmVyQXR0cmlidXRlKGEpLHRoaXMubW9ycGhUYXJnZXRzUmVsYXRpdmU/KGdlLmFkZFZlY3RvcnMoSGUubWluLFZzLm1pbiksSGUuZXhwYW5kQnlQb2ludChnZSksZ2UuYWRkVmVjdG9ycyhIZS5tYXgsVnMubWF4KSxIZS5leHBhbmRCeVBvaW50KGdlKSk6KEhlLmV4cGFuZEJ5UG9pbnQoVnMubWluKSxIZS5leHBhbmRCeVBvaW50KFZzLm1heCkpfUhlLmdldENlbnRlcihpKTtsZXQgcj0wO2ZvcihsZXQgcz0wLG89dC5jb3VudDtzPG87cysrKWdlLmZyb21CdWZmZXJBdHRyaWJ1dGUodCxzKSxyPU1hdGgubWF4KHIsaS5kaXN0YW5jZVRvU3F1YXJlZChnZSkpO2lmKGUpZm9yKGxldCBzPTAsbz1lLmxlbmd0aDtzPG87cysrKXtsZXQgYT1lW3NdLGw9dGhpcy5tb3JwaFRhcmdldHNSZWxhdGl2ZTtmb3IobGV0IGM9MCx1PWEuY291bnQ7Yzx1O2MrKylnZS5mcm9tQnVmZmVyQXR0cmlidXRlKGEsYyksbCYmKE1yLmZyb21CdWZmZXJBdHRyaWJ1dGUodCxjKSxnZS5hZGQoTXIpKSxyPU1hdGgubWF4KHIsaS5kaXN0YW5jZVRvU3F1YXJlZChnZSkpfXRoaXMuYm91bmRpbmdTcGhlcmUucmFkaXVzPU1hdGguc3FydChyKSxpc05hTih0aGlzLmJvdW5kaW5nU3BoZXJlLnJhZGl1cykmJmNvbnNvbGUuZXJyb3IoJ1RIUkVFLkJ1ZmZlckdlb21ldHJ5LmNvbXB1dGVCb3VuZGluZ1NwaGVyZSgpOiBDb21wdXRlZCByYWRpdXMgaXMgTmFOLiBUaGUgInBvc2l0aW9uIiBhdHRyaWJ1dGUgaXMgbGlrZWx5IHRvIGhhdmUgTmFOIHZhbHVlcy4nLHRoaXMpfX1jb21wdXRlVGFuZ2VudHMoKXtsZXQgdD10aGlzLmluZGV4LGU9dGhpcy5hdHRyaWJ1dGVzO2lmKHQ9PT1udWxsfHxlLnBvc2l0aW9uPT09dm9pZCAwfHxlLm5vcm1hbD09PXZvaWQgMHx8ZS51dj09PXZvaWQgMCl7Y29uc29sZS5lcnJvcigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5jb21wdXRlVGFuZ2VudHMoKSBmYWlsZWQuIE1pc3NpbmcgcmVxdWlyZWQgYXR0cmlidXRlcyAoaW5kZXgsIHBvc2l0aW9uLCBub3JtYWwgb3IgdXYpIik7cmV0dXJufWxldCBpPXQuYXJyYXkscj1lLnBvc2l0aW9uLmFycmF5LHM9ZS5ub3JtYWwuYXJyYXksbz1lLnV2LmFycmF5LGE9ci5sZW5ndGgvMztlLnRhbmdlbnQ9PT12b2lkIDAmJnRoaXMuc2V0QXR0cmlidXRlKCJ0YW5nZW50IixuZXcgUXQobmV3IEZsb2F0MzJBcnJheSg0KmEpLDQpKTtsZXQgbD1lLnRhbmdlbnQuYXJyYXksYz1bXSx1PVtdO2ZvcihsZXQgWD0wO1g8YTtYKyspY1tYXT1uZXcgVCx1W1hdPW5ldyBUO2xldCBoPW5ldyBULGY9bmV3IFQsZD1uZXcgVCxnPW5ldyBLLHg9bmV3IEssdj1uZXcgSyxtPW5ldyBULHA9bmV3IFQ7ZnVuY3Rpb24gYihYLHksUil7aC5mcm9tQXJyYXkocixYKjMpLGYuZnJvbUFycmF5KHIseSozKSxkLmZyb21BcnJheShyLFIqMyksZy5mcm9tQXJyYXkobyxYKjIpLHguZnJvbUFycmF5KG8seSoyKSx2LmZyb21BcnJheShvLFIqMiksZi5zdWIoaCksZC5zdWIoaCkseC5zdWIoZyksdi5zdWIoZyk7bGV0IEQ9MS8oeC54KnYueS12LngqeC55KTshaXNGaW5pdGUoRCl8fChtLmNvcHkoZikubXVsdGlwbHlTY2FsYXIodi55KS5hZGRTY2FsZWRWZWN0b3IoZCwteC55KS5tdWx0aXBseVNjYWxhcihEKSxwLmNvcHkoZCkubXVsdGlwbHlTY2FsYXIoeC54KS5hZGRTY2FsZWRWZWN0b3IoZiwtdi54KS5tdWx0aXBseVNjYWxhcihEKSxjW1hdLmFkZChtKSxjW3ldLmFkZChtKSxjW1JdLmFkZChtKSx1W1hdLmFkZChwKSx1W3ldLmFkZChwKSx1W1JdLmFkZChwKSl9bGV0IF89dGhpcy5ncm91cHM7Xy5sZW5ndGg9PT0wJiYoXz1be3N0YXJ0OjAsY291bnQ6aS5sZW5ndGh9XSk7Zm9yKGxldCBYPTAseT1fLmxlbmd0aDtYPHk7KytYKXtsZXQgUj1fW1hdLEQ9Ui5zdGFydCxGPVIuY291bnQ7Zm9yKGxldCB6PUQsTj1EK0Y7ejxOO3orPTMpYihpW3orMF0saVt6KzFdLGlbeisyXSl9bGV0IFM9bmV3IFQsTD1uZXcgVCxBPW5ldyBULEg9bmV3IFQ7ZnVuY3Rpb24gdHQoWCl7QS5mcm9tQXJyYXkocyxYKjMpLEguY29weShBKTtsZXQgeT1jW1hdO1MuY29weSh5KSxTLnN1YihBLm11bHRpcGx5U2NhbGFyKEEuZG90KHkpKSkubm9ybWFsaXplKCksTC5jcm9zc1ZlY3RvcnMoSCx5KTtsZXQgRD1MLmRvdCh1W1hdKTwwPy0xOjE7bFtYKjRdPVMueCxsW1gqNCsxXT1TLnksbFtYKjQrMl09Uy56LGxbWCo0KzNdPUR9Zm9yKGxldCBYPTAseT1fLmxlbmd0aDtYPHk7KytYKXtsZXQgUj1fW1hdLEQ9Ui5zdGFydCxGPVIuY291bnQ7Zm9yKGxldCB6PUQsTj1EK0Y7ejxOO3orPTMpdHQoaVt6KzBdKSx0dChpW3orMV0pLHR0KGlbeisyXSl9fWNvbXB1dGVWZXJ0ZXhOb3JtYWxzKCl7bGV0IHQ9dGhpcy5pbmRleCxlPXRoaXMuZ2V0QXR0cmlidXRlKCJwb3NpdGlvbiIpO2lmKGUhPT12b2lkIDApe2xldCBpPXRoaXMuZ2V0QXR0cmlidXRlKCJub3JtYWwiKTtpZihpPT09dm9pZCAwKWk9bmV3IFF0KG5ldyBGbG9hdDMyQXJyYXkoZS5jb3VudCozKSwzKSx0aGlzLnNldEF0dHJpYnV0ZSgibm9ybWFsIixpKTtlbHNlIGZvcihsZXQgZj0wLGQ9aS5jb3VudDtmPGQ7ZisrKWkuc2V0WFlaKGYsMCwwLDApO2xldCByPW5ldyBULHM9bmV3IFQsbz1uZXcgVCxhPW5ldyBULGw9bmV3IFQsYz1uZXcgVCx1PW5ldyBULGg9bmV3IFQ7aWYodClmb3IobGV0IGY9MCxkPXQuY291bnQ7ZjxkO2YrPTMpe2xldCBnPXQuZ2V0WChmKzApLHg9dC5nZXRYKGYrMSksdj10LmdldFgoZisyKTtyLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSxnKSxzLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSx4KSxvLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSx2KSx1LnN1YlZlY3RvcnMobyxzKSxoLnN1YlZlY3RvcnMocixzKSx1LmNyb3NzKGgpLGEuZnJvbUJ1ZmZlckF0dHJpYnV0ZShpLGcpLGwuZnJvbUJ1ZmZlckF0dHJpYnV0ZShpLHgpLGMuZnJvbUJ1ZmZlckF0dHJpYnV0ZShpLHYpLGEuYWRkKHUpLGwuYWRkKHUpLGMuYWRkKHUpLGkuc2V0WFlaKGcsYS54LGEueSxhLnopLGkuc2V0WFlaKHgsbC54LGwueSxsLnopLGkuc2V0WFlaKHYsYy54LGMueSxjLnopfWVsc2UgZm9yKGxldCBmPTAsZD1lLmNvdW50O2Y8ZDtmKz0zKXIuZnJvbUJ1ZmZlckF0dHJpYnV0ZShlLGYrMCkscy5mcm9tQnVmZmVyQXR0cmlidXRlKGUsZisxKSxvLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSxmKzIpLHUuc3ViVmVjdG9ycyhvLHMpLGguc3ViVmVjdG9ycyhyLHMpLHUuY3Jvc3MoaCksaS5zZXRYWVooZiswLHUueCx1LnksdS56KSxpLnNldFhZWihmKzEsdS54LHUueSx1LnopLGkuc2V0WFlaKGYrMix1LngsdS55LHUueik7dGhpcy5ub3JtYWxpemVOb3JtYWxzKCksaS5uZWVkc1VwZGF0ZT0hMH19bWVyZ2UodCxlKXtpZighKHQmJnQuaXNCdWZmZXJHZW9tZXRyeSkpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkJ1ZmZlckdlb21ldHJ5Lm1lcmdlKCk6IGdlb21ldHJ5IG5vdCBhbiBpbnN0YW5jZSBvZiBUSFJFRS5CdWZmZXJHZW9tZXRyeS4iLHQpO3JldHVybn1lPT09dm9pZCAwJiYoZT0wLGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnkubWVyZ2UoKTogT3ZlcndyaXRpbmcgb3JpZ2luYWwgZ2VvbWV0cnksIHN0YXJ0aW5nIGF0IG9mZnNldD0wLiBVc2UgQnVmZmVyR2VvbWV0cnlVdGlscy5tZXJnZUJ1ZmZlckdlb21ldHJpZXMoKSBmb3IgbG9zc2xlc3MgbWVyZ2UuIikpO2xldCBpPXRoaXMuYXR0cmlidXRlcztmb3IobGV0IHIgaW4gaSl7aWYodC5hdHRyaWJ1dGVzW3JdPT09dm9pZCAwKWNvbnRpbnVlO2xldCBvPWlbcl0uYXJyYXksYT10LmF0dHJpYnV0ZXNbcl0sbD1hLmFycmF5LGM9YS5pdGVtU2l6ZSplLHU9TWF0aC5taW4obC5sZW5ndGgsby5sZW5ndGgtYyk7Zm9yKGxldCBoPTAsZj1jO2g8dTtoKyssZisrKW9bZl09bFtoXX1yZXR1cm4gdGhpc31ub3JtYWxpemVOb3JtYWxzKCl7bGV0IHQ9dGhpcy5hdHRyaWJ1dGVzLm5vcm1hbDtmb3IobGV0IGU9MCxpPXQuY291bnQ7ZTxpO2UrKylnZS5mcm9tQnVmZmVyQXR0cmlidXRlKHQsZSksZ2Uubm9ybWFsaXplKCksdC5zZXRYWVooZSxnZS54LGdlLnksZ2Uueil9dG9Ob25JbmRleGVkKCl7ZnVuY3Rpb24gdChhLGwpe2xldCBjPWEuYXJyYXksdT1hLml0ZW1TaXplLGg9YS5ub3JtYWxpemVkLGY9bmV3IGMuY29uc3RydWN0b3IobC5sZW5ndGgqdSksZD0wLGc9MDtmb3IobGV0IHg9MCx2PWwubGVuZ3RoO3g8djt4Kyspe2EuaXNJbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZT9kPWxbeF0qYS5kYXRhLnN0cmlkZSthLm9mZnNldDpkPWxbeF0qdTtmb3IobGV0IG09MDttPHU7bSsrKWZbZysrXT1jW2QrK119cmV0dXJuIG5ldyBRdChmLHUsaCl9aWYodGhpcy5pbmRleD09PW51bGwpcmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnkudG9Ob25JbmRleGVkKCk6IEJ1ZmZlckdlb21ldHJ5IGlzIGFscmVhZHkgbm9uLWluZGV4ZWQuIiksdGhpcztsZXQgZT1uZXcgSHQsaT10aGlzLmluZGV4LmFycmF5LHI9dGhpcy5hdHRyaWJ1dGVzO2ZvcihsZXQgYSBpbiByKXtsZXQgbD1yW2FdLGM9dChsLGkpO2Uuc2V0QXR0cmlidXRlKGEsYyl9bGV0IHM9dGhpcy5tb3JwaEF0dHJpYnV0ZXM7Zm9yKGxldCBhIGluIHMpe2xldCBsPVtdLGM9c1thXTtmb3IobGV0IHU9MCxoPWMubGVuZ3RoO3U8aDt1Kyspe2xldCBmPWNbdV0sZD10KGYsaSk7bC5wdXNoKGQpfWUubW9ycGhBdHRyaWJ1dGVzW2FdPWx9ZS5tb3JwaFRhcmdldHNSZWxhdGl2ZT10aGlzLm1vcnBoVGFyZ2V0c1JlbGF0aXZlO2xldCBvPXRoaXMuZ3JvdXBzO2ZvcihsZXQgYT0wLGw9by5sZW5ndGg7YTxsO2ErKyl7bGV0IGM9b1thXTtlLmFkZEdyb3VwKGMuc3RhcnQsYy5jb3VudCxjLm1hdGVyaWFsSW5kZXgpfXJldHVybiBlfXRvSlNPTigpe2xldCB0PXttZXRhZGF0YTp7dmVyc2lvbjo0LjUsdHlwZToiQnVmZmVyR2VvbWV0cnkiLGdlbmVyYXRvcjoiQnVmZmVyR2VvbWV0cnkudG9KU09OIn19O2lmKHQudXVpZD10aGlzLnV1aWQsdC50eXBlPXRoaXMudHlwZSx0aGlzLm5hbWUhPT0iIiYmKHQubmFtZT10aGlzLm5hbWUpLE9iamVjdC5rZXlzKHRoaXMudXNlckRhdGEpLmxlbmd0aD4wJiYodC51c2VyRGF0YT10aGlzLnVzZXJEYXRhKSx0aGlzLnBhcmFtZXRlcnMhPT12b2lkIDApe2xldCBsPXRoaXMucGFyYW1ldGVycztmb3IobGV0IGMgaW4gbClsW2NdIT09dm9pZCAwJiYodFtjXT1sW2NdKTtyZXR1cm4gdH10LmRhdGE9e2F0dHJpYnV0ZXM6e319O2xldCBlPXRoaXMuaW5kZXg7ZSE9PW51bGwmJih0LmRhdGEuaW5kZXg9e3R5cGU6ZS5hcnJheS5jb25zdHJ1Y3Rvci5uYW1lLGFycmF5OkFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGUuYXJyYXkpfSk7bGV0IGk9dGhpcy5hdHRyaWJ1dGVzO2ZvcihsZXQgbCBpbiBpKXtsZXQgYz1pW2xdO3QuZGF0YS5hdHRyaWJ1dGVzW2xdPWMudG9KU09OKHQuZGF0YSl9bGV0IHI9e30scz0hMTtmb3IobGV0IGwgaW4gdGhpcy5tb3JwaEF0dHJpYnV0ZXMpe2xldCBjPXRoaXMubW9ycGhBdHRyaWJ1dGVzW2xdLHU9W107Zm9yKGxldCBoPTAsZj1jLmxlbmd0aDtoPGY7aCsrKXtsZXQgZD1jW2hdO3UucHVzaChkLnRvSlNPTih0LmRhdGEpKX11Lmxlbmd0aD4wJiYocltsXT11LHM9ITApfXMmJih0LmRhdGEubW9ycGhBdHRyaWJ1dGVzPXIsdC5kYXRhLm1vcnBoVGFyZ2V0c1JlbGF0aXZlPXRoaXMubW9ycGhUYXJnZXRzUmVsYXRpdmUpO2xldCBvPXRoaXMuZ3JvdXBzO28ubGVuZ3RoPjAmJih0LmRhdGEuZ3JvdXBzPUpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkobykpKTtsZXQgYT10aGlzLmJvdW5kaW5nU3BoZXJlO3JldHVybiBhIT09bnVsbCYmKHQuZGF0YS5ib3VuZGluZ1NwaGVyZT17Y2VudGVyOmEuY2VudGVyLnRvQXJyYXkoKSxyYWRpdXM6YS5yYWRpdXN9KSx0fWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX1jb3B5KHQpe3RoaXMuaW5kZXg9bnVsbCx0aGlzLmF0dHJpYnV0ZXM9e30sdGhpcy5tb3JwaEF0dHJpYnV0ZXM9e30sdGhpcy5ncm91cHM9W10sdGhpcy5ib3VuZGluZ0JveD1udWxsLHRoaXMuYm91bmRpbmdTcGhlcmU9bnVsbDtsZXQgZT17fTt0aGlzLm5hbWU9dC5uYW1lO2xldCBpPXQuaW5kZXg7aSE9PW51bGwmJnRoaXMuc2V0SW5kZXgoaS5jbG9uZShlKSk7bGV0IHI9dC5hdHRyaWJ1dGVzO2ZvcihsZXQgYyBpbiByKXtsZXQgdT1yW2NdO3RoaXMuc2V0QXR0cmlidXRlKGMsdS5jbG9uZShlKSl9bGV0IHM9dC5tb3JwaEF0dHJpYnV0ZXM7Zm9yKGxldCBjIGluIHMpe2xldCB1PVtdLGg9c1tjXTtmb3IobGV0IGY9MCxkPWgubGVuZ3RoO2Y8ZDtmKyspdS5wdXNoKGhbZl0uY2xvbmUoZSkpO3RoaXMubW9ycGhBdHRyaWJ1dGVzW2NdPXV9dGhpcy5tb3JwaFRhcmdldHNSZWxhdGl2ZT10Lm1vcnBoVGFyZ2V0c1JlbGF0aXZlO2xldCBvPXQuZ3JvdXBzO2ZvcihsZXQgYz0wLHU9by5sZW5ndGg7Yzx1O2MrKyl7bGV0IGg9b1tjXTt0aGlzLmFkZEdyb3VwKGguc3RhcnQsaC5jb3VudCxoLm1hdGVyaWFsSW5kZXgpfWxldCBhPXQuYm91bmRpbmdCb3g7YSE9PW51bGwmJih0aGlzLmJvdW5kaW5nQm94PWEuY2xvbmUoKSk7bGV0IGw9dC5ib3VuZGluZ1NwaGVyZTtyZXR1cm4gbCE9PW51bGwmJih0aGlzLmJvdW5kaW5nU3BoZXJlPWwuY2xvbmUoKSksdGhpcy5kcmF3UmFuZ2Uuc3RhcnQ9dC5kcmF3UmFuZ2Uuc3RhcnQsdGhpcy5kcmF3UmFuZ2UuY291bnQ9dC5kcmF3UmFuZ2UuY291bnQsdGhpcy51c2VyRGF0YT10LnVzZXJEYXRhLHQucGFyYW1ldGVycyE9PXZvaWQgMCYmKHRoaXMucGFyYW1ldGVycz1PYmplY3QuYXNzaWduKHt9LHQucGFyYW1ldGVycykpLHRoaXN9ZGlzcG9zZSgpe3RoaXMuZGlzcGF0Y2hFdmVudCh7dHlwZToiZGlzcG9zZSJ9KX19O0h0LnByb3RvdHlwZS5pc0J1ZmZlckdlb21ldHJ5PSEwO3ZhciBwZz1uZXcgd3QsYnI9bmV3IG9pLF91PW5ldyBzaSxKbj1uZXcgVCwkbj1uZXcgVCxLbj1uZXcgVCx3dT1uZXcgVCxNdT1uZXcgVCxidT1uZXcgVCxDYT1uZXcgVCxSYT1uZXcgVCxMYT1uZXcgVCxQYT1uZXcgSyxEYT1uZXcgSyxJYT1uZXcgSyxTdT1uZXcgVCxOYT1uZXcgVCxvZT1jbGFzcyBleHRlbmRzIGt0e2NvbnN0cnVjdG9yKHQ9bmV3IEh0LGU9bmV3IGtpKXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaCIsdGhpcy5nZW9tZXRyeT10LHRoaXMubWF0ZXJpYWw9ZSx0aGlzLnVwZGF0ZU1vcnBoVGFyZ2V0cygpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdC5tb3JwaFRhcmdldEluZmx1ZW5jZXMhPT12b2lkIDAmJih0aGlzLm1vcnBoVGFyZ2V0SW5mbHVlbmNlcz10Lm1vcnBoVGFyZ2V0SW5mbHVlbmNlcy5zbGljZSgpKSx0Lm1vcnBoVGFyZ2V0RGljdGlvbmFyeSE9PXZvaWQgMCYmKHRoaXMubW9ycGhUYXJnZXREaWN0aW9uYXJ5PU9iamVjdC5hc3NpZ24oe30sdC5tb3JwaFRhcmdldERpY3Rpb25hcnkpKSx0aGlzLm1hdGVyaWFsPXQubWF0ZXJpYWwsdGhpcy5nZW9tZXRyeT10Lmdlb21ldHJ5LHRoaXN9dXBkYXRlTW9ycGhUYXJnZXRzKCl7bGV0IHQ9dGhpcy5nZW9tZXRyeTtpZih0LmlzQnVmZmVyR2VvbWV0cnkpe2xldCBlPXQubW9ycGhBdHRyaWJ1dGVzLGk9T2JqZWN0LmtleXMoZSk7aWYoaS5sZW5ndGg+MCl7bGV0IHI9ZVtpWzBdXTtpZihyIT09dm9pZCAwKXt0aGlzLm1vcnBoVGFyZ2V0SW5mbHVlbmNlcz1bXSx0aGlzLm1vcnBoVGFyZ2V0RGljdGlvbmFyeT17fTtmb3IobGV0IHM9MCxvPXIubGVuZ3RoO3M8bztzKyspe2xldCBhPXJbc10ubmFtZXx8U3RyaW5nKHMpO3RoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzLnB1c2goMCksdGhpcy5tb3JwaFRhcmdldERpY3Rpb25hcnlbYV09c319fX1lbHNle2xldCBlPXQubW9ycGhUYXJnZXRzO2UhPT12b2lkIDAmJmUubGVuZ3RoPjAmJmNvbnNvbGUuZXJyb3IoIlRIUkVFLk1lc2gudXBkYXRlTW9ycGhUYXJnZXRzKCkgbm8gbG9uZ2VyIHN1cHBvcnRzIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX19cmF5Y2FzdCh0LGUpe2xldCBpPXRoaXMuZ2VvbWV0cnkscj10aGlzLm1hdGVyaWFsLHM9dGhpcy5tYXRyaXhXb3JsZDtpZihyPT09dm9pZCAwfHwoaS5ib3VuZGluZ1NwaGVyZT09PW51bGwmJmkuY29tcHV0ZUJvdW5kaW5nU3BoZXJlKCksX3UuY29weShpLmJvdW5kaW5nU3BoZXJlKSxfdS5hcHBseU1hdHJpeDQocyksdC5yYXkuaW50ZXJzZWN0c1NwaGVyZShfdSk9PT0hMSl8fChwZy5jb3B5KHMpLmludmVydCgpLGJyLmNvcHkodC5yYXkpLmFwcGx5TWF0cml4NChwZyksaS5ib3VuZGluZ0JveCE9PW51bGwmJmJyLmludGVyc2VjdHNCb3goaS5ib3VuZGluZ0JveCk9PT0hMSkpcmV0dXJuO2xldCBvO2lmKGkuaXNCdWZmZXJHZW9tZXRyeSl7bGV0IGE9aS5pbmRleCxsPWkuYXR0cmlidXRlcy5wb3NpdGlvbixjPWkubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uLHU9aS5tb3JwaFRhcmdldHNSZWxhdGl2ZSxoPWkuYXR0cmlidXRlcy51dixmPWkuYXR0cmlidXRlcy51djIsZD1pLmdyb3VwcyxnPWkuZHJhd1JhbmdlO2lmKGEhPT1udWxsKWlmKEFycmF5LmlzQXJyYXkocikpZm9yKGxldCB4PTAsdj1kLmxlbmd0aDt4PHY7eCsrKXtsZXQgbT1kW3hdLHA9clttLm1hdGVyaWFsSW5kZXhdLGI9TWF0aC5tYXgobS5zdGFydCxnLnN0YXJ0KSxfPU1hdGgubWluKGEuY291bnQsTWF0aC5taW4obS5zdGFydCttLmNvdW50LGcuc3RhcnQrZy5jb3VudCkpO2ZvcihsZXQgUz1iLEw9XztTPEw7Uys9Myl7bGV0IEE9YS5nZXRYKFMpLEg9YS5nZXRYKFMrMSksdHQ9YS5nZXRYKFMrMik7bz1GYSh0aGlzLHAsdCxicixsLGMsdSxoLGYsQSxILHR0KSxvJiYoby5mYWNlSW5kZXg9TWF0aC5mbG9vcihTLzMpLG8uZmFjZS5tYXRlcmlhbEluZGV4PW0ubWF0ZXJpYWxJbmRleCxlLnB1c2gobykpfX1lbHNle2xldCB4PU1hdGgubWF4KDAsZy5zdGFydCksdj1NYXRoLm1pbihhLmNvdW50LGcuc3RhcnQrZy5jb3VudCk7Zm9yKGxldCBtPXgscD12O208cDttKz0zKXtsZXQgYj1hLmdldFgobSksXz1hLmdldFgobSsxKSxTPWEuZ2V0WChtKzIpO289RmEodGhpcyxyLHQsYnIsbCxjLHUsaCxmLGIsXyxTKSxvJiYoby5mYWNlSW5kZXg9TWF0aC5mbG9vcihtLzMpLGUucHVzaChvKSl9fWVsc2UgaWYobCE9PXZvaWQgMClpZihBcnJheS5pc0FycmF5KHIpKWZvcihsZXQgeD0wLHY9ZC5sZW5ndGg7eDx2O3grKyl7bGV0IG09ZFt4XSxwPXJbbS5tYXRlcmlhbEluZGV4XSxiPU1hdGgubWF4KG0uc3RhcnQsZy5zdGFydCksXz1NYXRoLm1pbihsLmNvdW50LE1hdGgubWluKG0uc3RhcnQrbS5jb3VudCxnLnN0YXJ0K2cuY291bnQpKTtmb3IobGV0IFM9YixMPV87UzxMO1MrPTMpe2xldCBBPVMsSD1TKzEsdHQ9UysyO289RmEodGhpcyxwLHQsYnIsbCxjLHUsaCxmLEEsSCx0dCksbyYmKG8uZmFjZUluZGV4PU1hdGguZmxvb3IoUy8zKSxvLmZhY2UubWF0ZXJpYWxJbmRleD1tLm1hdGVyaWFsSW5kZXgsZS5wdXNoKG8pKX19ZWxzZXtsZXQgeD1NYXRoLm1heCgwLGcuc3RhcnQpLHY9TWF0aC5taW4obC5jb3VudCxnLnN0YXJ0K2cuY291bnQpO2ZvcihsZXQgbT14LHA9djttPHA7bSs9Myl7bGV0IGI9bSxfPW0rMSxTPW0rMjtvPUZhKHRoaXMscix0LGJyLGwsYyx1LGgsZixiLF8sUyksbyYmKG8uZmFjZUluZGV4PU1hdGguZmxvb3IobS8zKSxlLnB1c2gobykpfX19ZWxzZSBpLmlzR2VvbWV0cnkmJmNvbnNvbGUuZXJyb3IoIlRIUkVFLk1lc2gucmF5Y2FzdCgpIG5vIGxvbmdlciBzdXBwb3J0cyBUSFJFRS5HZW9tZXRyeS4gVXNlIFRIUkVFLkJ1ZmZlckdlb21ldHJ5IGluc3RlYWQuIil9fTtvZS5wcm90b3R5cGUuaXNNZXNoPSEwO2Z1bmN0aW9uIGRNKG4sdCxlLGkscixzLG8sYSl7bGV0IGw7aWYodC5zaWRlPT09aGU/bD1pLmludGVyc2VjdFRyaWFuZ2xlKG8scyxyLCEwLGEpOmw9aS5pbnRlcnNlY3RUcmlhbmdsZShyLHMsbyx0LnNpZGUhPT1IcixhKSxsPT09bnVsbClyZXR1cm4gbnVsbDtOYS5jb3B5KGEpLE5hLmFwcGx5TWF0cml4NChuLm1hdHJpeFdvcmxkKTtsZXQgYz1lLnJheS5vcmlnaW4uZGlzdGFuY2VUbyhOYSk7cmV0dXJuIGM8ZS5uZWFyfHxjPmUuZmFyP251bGw6e2Rpc3RhbmNlOmMscG9pbnQ6TmEuY2xvbmUoKSxvYmplY3Q6bn19ZnVuY3Rpb24gRmEobix0LGUsaSxyLHMsbyxhLGwsYyx1LGgpe0puLmZyb21CdWZmZXJBdHRyaWJ1dGUocixjKSwkbi5mcm9tQnVmZmVyQXR0cmlidXRlKHIsdSksS24uZnJvbUJ1ZmZlckF0dHJpYnV0ZShyLGgpO2xldCBmPW4ubW9ycGhUYXJnZXRJbmZsdWVuY2VzO2lmKHMmJmYpe0NhLnNldCgwLDAsMCksUmEuc2V0KDAsMCwwKSxMYS5zZXQoMCwwLDApO2ZvcihsZXQgZz0wLHg9cy5sZW5ndGg7Zzx4O2crKyl7bGV0IHY9ZltnXSxtPXNbZ107diE9PTAmJih3dS5mcm9tQnVmZmVyQXR0cmlidXRlKG0sYyksTXUuZnJvbUJ1ZmZlckF0dHJpYnV0ZShtLHUpLGJ1LmZyb21CdWZmZXJBdHRyaWJ1dGUobSxoKSxvPyhDYS5hZGRTY2FsZWRWZWN0b3Iod3UsdiksUmEuYWRkU2NhbGVkVmVjdG9yKE11LHYpLExhLmFkZFNjYWxlZFZlY3RvcihidSx2KSk6KENhLmFkZFNjYWxlZFZlY3Rvcih3dS5zdWIoSm4pLHYpLFJhLmFkZFNjYWxlZFZlY3RvcihNdS5zdWIoJG4pLHYpLExhLmFkZFNjYWxlZFZlY3RvcihidS5zdWIoS24pLHYpKSl9Sm4uYWRkKENhKSwkbi5hZGQoUmEpLEtuLmFkZChMYSl9bi5pc1NraW5uZWRNZXNoJiYobi5ib25lVHJhbnNmb3JtKGMsSm4pLG4uYm9uZVRyYW5zZm9ybSh1LCRuKSxuLmJvbmVUcmFuc2Zvcm0oaCxLbikpO2xldCBkPWRNKG4sdCxlLGksSm4sJG4sS24sU3UpO2lmKGQpe2EmJihQYS5mcm9tQnVmZmVyQXR0cmlidXRlKGEsYyksRGEuZnJvbUJ1ZmZlckF0dHJpYnV0ZShhLHUpLElhLmZyb21CdWZmZXJBdHRyaWJ1dGUoYSxoKSxkLnV2PXJlLmdldFVWKFN1LEpuLCRuLEtuLFBhLERhLElhLG5ldyBLKSksbCYmKFBhLmZyb21CdWZmZXJBdHRyaWJ1dGUobCxjKSxEYS5mcm9tQnVmZmVyQXR0cmlidXRlKGwsdSksSWEuZnJvbUJ1ZmZlckF0dHJpYnV0ZShsLGgpLGQudXYyPXJlLmdldFVWKFN1LEpuLCRuLEtuLFBhLERhLElhLG5ldyBLKSk7bGV0IGc9e2E6YyxiOnUsYzpoLG5vcm1hbDpuZXcgVCxtYXRlcmlhbEluZGV4OjB9O3JlLmdldE5vcm1hbChKbiwkbixLbixnLm5vcm1hbCksZC5mYWNlPWd9cmV0dXJuIGR9dmFyIEhpPWNsYXNzIGV4dGVuZHMgSHR7Y29uc3RydWN0b3IodD0xLGU9MSxpPTEscj0xLHM9MSxvPTEpe3N1cGVyKCksdGhpcy50eXBlPSJCb3hHZW9tZXRyeSIsdGhpcy5wYXJhbWV0ZXJzPXt3aWR0aDp0LGhlaWdodDplLGRlcHRoOmksd2lkdGhTZWdtZW50czpyLGhlaWdodFNlZ21lbnRzOnMsZGVwdGhTZWdtZW50czpvfTtsZXQgYT10aGlzO3I9TWF0aC5mbG9vcihyKSxzPU1hdGguZmxvb3Iocyksbz1NYXRoLmZsb29yKG8pO2xldCBsPVtdLGM9W10sdT1bXSxoPVtdLGY9MCxkPTA7ZygieiIsInkiLCJ4IiwtMSwtMSxpLGUsdCxvLHMsMCksZygieiIsInkiLCJ4IiwxLC0xLGksZSwtdCxvLHMsMSksZygieCIsInoiLCJ5IiwxLDEsdCxpLGUscixvLDIpLGcoIngiLCJ6IiwieSIsMSwtMSx0LGksLWUscixvLDMpLGcoIngiLCJ5IiwieiIsMSwtMSx0LGUsaSxyLHMsNCksZygieCIsInkiLCJ6IiwtMSwtMSx0LGUsLWkscixzLDUpLHRoaXMuc2V0SW5kZXgobCksdGhpcy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgZWUoYywzKSksdGhpcy5zZXRBdHRyaWJ1dGUoIm5vcm1hbCIsbmV3IGVlKHUsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJ1diIsbmV3IGVlKGgsMikpO2Z1bmN0aW9uIGcoeCx2LG0scCxiLF8sUyxMLEEsSCx0dCl7bGV0IFg9Xy9BLHk9Uy9ILFI9Xy8yLEQ9Uy8yLEY9TC8yLHo9QSsxLE49SCsxLFY9MCxRPTAsYXQ9bmV3IFQ7Zm9yKGxldCBHPTA7RzxOO0crKyl7bGV0ICQ9Ryp5LUQ7Zm9yKGxldCBsdD0wO2x0PHo7bHQrKyl7bGV0IGR0PWx0KlgtUjthdFt4XT1kdCpwLGF0W3ZdPSQqYixhdFttXT1GLGMucHVzaChhdC54LGF0LnksYXQueiksYXRbeF09MCxhdFt2XT0wLGF0W21dPUw+MD8xOi0xLHUucHVzaChhdC54LGF0LnksYXQueiksaC5wdXNoKGx0L0EpLGgucHVzaCgxLUcvSCksVis9MX19Zm9yKGxldCBHPTA7RzxIO0crKylmb3IobGV0ICQ9MDskPEE7JCsrKXtsZXQgbHQ9ZiskK3oqRyxkdD1mKyQreiooRysxKSx4dD1mKygkKzEpK3oqKEcrMSksaz1mKygkKzEpK3oqRztsLnB1c2gobHQsZHQsayksbC5wdXNoKGR0LHh0LGspLFErPTZ9YS5hZGRHcm91cChkLFEsdHQpLGQrPVEsZis9Vn19c3RhdGljIGZyb21KU09OKHQpe3JldHVybiBuZXcgSGkodC53aWR0aCx0LmhlaWdodCx0LmRlcHRoLHQud2lkdGhTZWdtZW50cyx0LmhlaWdodFNlZ21lbnRzLHQuZGVwdGhTZWdtZW50cyl9fTtmdW5jdGlvbiBHcihuKXtsZXQgdD17fTtmb3IobGV0IGUgaW4gbil7dFtlXT17fTtmb3IobGV0IGkgaW4gbltlXSl7bGV0IHI9bltlXVtpXTtyJiYoci5pc0NvbG9yfHxyLmlzTWF0cml4M3x8ci5pc01hdHJpeDR8fHIuaXNWZWN0b3IyfHxyLmlzVmVjdG9yM3x8ci5pc1ZlY3RvcjR8fHIuaXNUZXh0dXJlfHxyLmlzUXVhdGVybmlvbik/dFtlXVtpXT1yLmNsb25lKCk6QXJyYXkuaXNBcnJheShyKT90W2VdW2ldPXIuc2xpY2UoKTp0W2VdW2ldPXJ9fXJldHVybiB0fWZ1bmN0aW9uIE1lKG4pe2xldCB0PXt9O2ZvcihsZXQgZT0wO2U8bi5sZW5ndGg7ZSsrKXtsZXQgaT1HcihuW2VdKTtmb3IobGV0IHIgaW4gaSl0W3JdPWlbcl19cmV0dXJuIHR9dmFyIHBNPXtjbG9uZTpHcixtZXJnZTpNZX0sbU09YHZvaWQgbWFpbigpIHsKCWdsX1Bvc2l0aW9uID0gcHJvamVjdGlvbk1hdHJpeCAqIG1vZGVsVmlld01hdHJpeCAqIHZlYzQoIHBvc2l0aW9uLCAxLjAgKTsKfWAsZ009YHZvaWQgbWFpbigpIHsKCWdsX0ZyYWdDb2xvciA9IHZlYzQoIDEuMCwgMC4wLCAwLjAsIDEuMCApOwp9YCxGbj1jbGFzcyBleHRlbmRzIHhle2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJTaGFkZXJNYXRlcmlhbCIsdGhpcy5kZWZpbmVzPXt9LHRoaXMudW5pZm9ybXM9e30sdGhpcy52ZXJ0ZXhTaGFkZXI9bU0sdGhpcy5mcmFnbWVudFNoYWRlcj1nTSx0aGlzLmxpbmV3aWR0aD0xLHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy5mb2c9ITEsdGhpcy5saWdodHM9ITEsdGhpcy5jbGlwcGluZz0hMSx0aGlzLmV4dGVuc2lvbnM9e2Rlcml2YXRpdmVzOiExLGZyYWdEZXB0aDohMSxkcmF3QnVmZmVyczohMSxzaGFkZXJUZXh0dXJlTE9EOiExfSx0aGlzLmRlZmF1bHRBdHRyaWJ1dGVWYWx1ZXM9e2NvbG9yOlsxLDEsMV0sdXY6WzAsMF0sdXYyOlswLDBdfSx0aGlzLmluZGV4MEF0dHJpYnV0ZU5hbWU9dm9pZCAwLHRoaXMudW5pZm9ybXNOZWVkVXBkYXRlPSExLHRoaXMuZ2xzbFZlcnNpb249bnVsbCx0IT09dm9pZCAwJiYodC5hdHRyaWJ1dGVzIT09dm9pZCAwJiZjb25zb2xlLmVycm9yKCJUSFJFRS5TaGFkZXJNYXRlcmlhbDogYXR0cmlidXRlcyBzaG91bGQgbm93IGJlIGRlZmluZWQgaW4gVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKSx0aGlzLnNldFZhbHVlcyh0KSl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmZyYWdtZW50U2hhZGVyPXQuZnJhZ21lbnRTaGFkZXIsdGhpcy52ZXJ0ZXhTaGFkZXI9dC52ZXJ0ZXhTaGFkZXIsdGhpcy51bmlmb3Jtcz1Hcih0LnVuaWZvcm1zKSx0aGlzLmRlZmluZXM9T2JqZWN0LmFzc2lnbih7fSx0LmRlZmluZXMpLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXMubGlnaHRzPXQubGlnaHRzLHRoaXMuY2xpcHBpbmc9dC5jbGlwcGluZyx0aGlzLmV4dGVuc2lvbnM9T2JqZWN0LmFzc2lnbih7fSx0LmV4dGVuc2lvbnMpLHRoaXMuZ2xzbFZlcnNpb249dC5nbHNsVmVyc2lvbix0aGlzfXRvSlNPTih0KXtsZXQgZT1zdXBlci50b0pTT04odCk7ZS5nbHNsVmVyc2lvbj10aGlzLmdsc2xWZXJzaW9uLGUudW5pZm9ybXM9e307Zm9yKGxldCByIGluIHRoaXMudW5pZm9ybXMpe2xldCBvPXRoaXMudW5pZm9ybXNbcl0udmFsdWU7byYmby5pc1RleHR1cmU/ZS51bmlmb3Jtc1tyXT17dHlwZToidCIsdmFsdWU6by50b0pTT04odCkudXVpZH06byYmby5pc0NvbG9yP2UudW5pZm9ybXNbcl09e3R5cGU6ImMiLHZhbHVlOm8uZ2V0SGV4KCl9Om8mJm8uaXNWZWN0b3IyP2UudW5pZm9ybXNbcl09e3R5cGU6InYyIix2YWx1ZTpvLnRvQXJyYXkoKX06byYmby5pc1ZlY3RvcjM/ZS51bmlmb3Jtc1tyXT17dHlwZToidjMiLHZhbHVlOm8udG9BcnJheSgpfTpvJiZvLmlzVmVjdG9yND9lLnVuaWZvcm1zW3JdPXt0eXBlOiJ2NCIsdmFsdWU6by50b0FycmF5KCl9Om8mJm8uaXNNYXRyaXgzP2UudW5pZm9ybXNbcl09e3R5cGU6Im0zIix2YWx1ZTpvLnRvQXJyYXkoKX06byYmby5pc01hdHJpeDQ/ZS51bmlmb3Jtc1tyXT17dHlwZToibTQiLHZhbHVlOm8udG9BcnJheSgpfTplLnVuaWZvcm1zW3JdPXt2YWx1ZTpvfX1PYmplY3Qua2V5cyh0aGlzLmRlZmluZXMpLmxlbmd0aD4wJiYoZS5kZWZpbmVzPXRoaXMuZGVmaW5lcyksZS52ZXJ0ZXhTaGFkZXI9dGhpcy52ZXJ0ZXhTaGFkZXIsZS5mcmFnbWVudFNoYWRlcj10aGlzLmZyYWdtZW50U2hhZGVyO2xldCBpPXt9O2ZvcihsZXQgciBpbiB0aGlzLmV4dGVuc2lvbnMpdGhpcy5leHRlbnNpb25zW3JdPT09ITAmJihpW3JdPSEwKTtyZXR1cm4gT2JqZWN0LmtleXMoaSkubGVuZ3RoPjAmJihlLmV4dGVuc2lvbnM9aSksZX19O0ZuLnByb3RvdHlwZS5pc1NoYWRlck1hdGVyaWFsPSEwO3ZhciBvbz1jbGFzcyBleHRlbmRzIGt0e2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLnR5cGU9IkNhbWVyYSIsdGhpcy5tYXRyaXhXb3JsZEludmVyc2U9bmV3IHd0LHRoaXMucHJvamVjdGlvbk1hdHJpeD1uZXcgd3QsdGhpcy5wcm9qZWN0aW9uTWF0cml4SW52ZXJzZT1uZXcgd3R9Y29weSh0LGUpe3JldHVybiBzdXBlci5jb3B5KHQsZSksdGhpcy5tYXRyaXhXb3JsZEludmVyc2UuY29weSh0Lm1hdHJpeFdvcmxkSW52ZXJzZSksdGhpcy5wcm9qZWN0aW9uTWF0cml4LmNvcHkodC5wcm9qZWN0aW9uTWF0cml4KSx0aGlzLnByb2plY3Rpb25NYXRyaXhJbnZlcnNlLmNvcHkodC5wcm9qZWN0aW9uTWF0cml4SW52ZXJzZSksdGhpc31nZXRXb3JsZERpcmVjdGlvbih0KXt0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKTtsZXQgZT10aGlzLm1hdHJpeFdvcmxkLmVsZW1lbnRzO3JldHVybiB0LnNldCgtZVs4XSwtZVs5XSwtZVsxMF0pLm5vcm1hbGl6ZSgpfXVwZGF0ZU1hdHJpeFdvcmxkKHQpe3N1cGVyLnVwZGF0ZU1hdHJpeFdvcmxkKHQpLHRoaXMubWF0cml4V29ybGRJbnZlcnNlLmNvcHkodGhpcy5tYXRyaXhXb3JsZCkuaW52ZXJ0KCl9dXBkYXRlV29ybGRNYXRyaXgodCxlKXtzdXBlci51cGRhdGVXb3JsZE1hdHJpeCh0LGUpLHRoaXMubWF0cml4V29ybGRJbnZlcnNlLmNvcHkodGhpcy5tYXRyaXhXb3JsZCkuaW52ZXJ0KCl9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfX07b28ucHJvdG90eXBlLmlzQ2FtZXJhPSEwO3ZhciBTZT1jbGFzcyBleHRlbmRzIG9ve2NvbnN0cnVjdG9yKHQ9NTAsZT0xLGk9LjEscj0yZTMpe3N1cGVyKCksdGhpcy50eXBlPSJQZXJzcGVjdGl2ZUNhbWVyYSIsdGhpcy5mb3Y9dCx0aGlzLnpvb209MSx0aGlzLm5lYXI9aSx0aGlzLmZhcj1yLHRoaXMuZm9jdXM9MTAsdGhpcy5hc3BlY3Q9ZSx0aGlzLnZpZXc9bnVsbCx0aGlzLmZpbG1HYXVnZT0zNSx0aGlzLmZpbG1PZmZzZXQ9MCx0aGlzLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX1jb3B5KHQsZSl7cmV0dXJuIHN1cGVyLmNvcHkodCxlKSx0aGlzLmZvdj10LmZvdix0aGlzLnpvb209dC56b29tLHRoaXMubmVhcj10Lm5lYXIsdGhpcy5mYXI9dC5mYXIsdGhpcy5mb2N1cz10LmZvY3VzLHRoaXMuYXNwZWN0PXQuYXNwZWN0LHRoaXMudmlldz10LnZpZXc9PT1udWxsP251bGw6T2JqZWN0LmFzc2lnbih7fSx0LnZpZXcpLHRoaXMuZmlsbUdhdWdlPXQuZmlsbUdhdWdlLHRoaXMuZmlsbU9mZnNldD10LmZpbG1PZmZzZXQsdGhpc31zZXRGb2NhbExlbmd0aCh0KXtsZXQgZT0uNSp0aGlzLmdldEZpbG1IZWlnaHQoKS90O3RoaXMuZm92PVZ1KjIqTWF0aC5hdGFuKGUpLHRoaXMudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpfWdldEZvY2FsTGVuZ3RoKCl7bGV0IHQ9TWF0aC50YW4oaXUqLjUqdGhpcy5mb3YpO3JldHVybiAuNSp0aGlzLmdldEZpbG1IZWlnaHQoKS90fWdldEVmZmVjdGl2ZUZPVigpe3JldHVybiBWdSoyKk1hdGguYXRhbihNYXRoLnRhbihpdSouNSp0aGlzLmZvdikvdGhpcy56b29tKX1nZXRGaWxtV2lkdGgoKXtyZXR1cm4gdGhpcy5maWxtR2F1Z2UqTWF0aC5taW4odGhpcy5hc3BlY3QsMSl9Z2V0RmlsbUhlaWdodCgpe3JldHVybiB0aGlzLmZpbG1HYXVnZS9NYXRoLm1heCh0aGlzLmFzcGVjdCwxKX1zZXRWaWV3T2Zmc2V0KHQsZSxpLHIscyxvKXt0aGlzLmFzcGVjdD10L2UsdGhpcy52aWV3PT09bnVsbCYmKHRoaXMudmlldz17ZW5hYmxlZDohMCxmdWxsV2lkdGg6MSxmdWxsSGVpZ2h0OjEsb2Zmc2V0WDowLG9mZnNldFk6MCx3aWR0aDoxLGhlaWdodDoxfSksdGhpcy52aWV3LmVuYWJsZWQ9ITAsdGhpcy52aWV3LmZ1bGxXaWR0aD10LHRoaXMudmlldy5mdWxsSGVpZ2h0PWUsdGhpcy52aWV3Lm9mZnNldFg9aSx0aGlzLnZpZXcub2Zmc2V0WT1yLHRoaXMudmlldy53aWR0aD1zLHRoaXMudmlldy5oZWlnaHQ9byx0aGlzLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX1jbGVhclZpZXdPZmZzZXQoKXt0aGlzLnZpZXchPT1udWxsJiYodGhpcy52aWV3LmVuYWJsZWQ9ITEpLHRoaXMudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpfXVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKXtsZXQgdD10aGlzLm5lYXIsZT10Kk1hdGgudGFuKGl1Ki41KnRoaXMuZm92KS90aGlzLnpvb20saT0yKmUscj10aGlzLmFzcGVjdCppLHM9LS41KnIsbz10aGlzLnZpZXc7aWYodGhpcy52aWV3IT09bnVsbCYmdGhpcy52aWV3LmVuYWJsZWQpe2xldCBsPW8uZnVsbFdpZHRoLGM9by5mdWxsSGVpZ2h0O3MrPW8ub2Zmc2V0WCpyL2wsZS09by5vZmZzZXRZKmkvYyxyKj1vLndpZHRoL2wsaSo9by5oZWlnaHQvY31sZXQgYT10aGlzLmZpbG1PZmZzZXQ7YSE9PTAmJihzKz10KmEvdGhpcy5nZXRGaWxtV2lkdGgoKSksdGhpcy5wcm9qZWN0aW9uTWF0cml4Lm1ha2VQZXJzcGVjdGl2ZShzLHMrcixlLGUtaSx0LHRoaXMuZmFyKSx0aGlzLnByb2plY3Rpb25NYXRyaXhJbnZlcnNlLmNvcHkodGhpcy5wcm9qZWN0aW9uTWF0cml4KS5pbnZlcnQoKX10b0pTT04odCl7bGV0IGU9c3VwZXIudG9KU09OKHQpO3JldHVybiBlLm9iamVjdC5mb3Y9dGhpcy5mb3YsZS5vYmplY3Quem9vbT10aGlzLnpvb20sZS5vYmplY3QubmVhcj10aGlzLm5lYXIsZS5vYmplY3QuZmFyPXRoaXMuZmFyLGUub2JqZWN0LmZvY3VzPXRoaXMuZm9jdXMsZS5vYmplY3QuYXNwZWN0PXRoaXMuYXNwZWN0LHRoaXMudmlldyE9PW51bGwmJihlLm9iamVjdC52aWV3PU9iamVjdC5hc3NpZ24oe30sdGhpcy52aWV3KSksZS5vYmplY3QuZmlsbUdhdWdlPXRoaXMuZmlsbUdhdWdlLGUub2JqZWN0LmZpbG1PZmZzZXQ9dGhpcy5maWxtT2Zmc2V0LGV9fTtTZS5wcm90b3R5cGUuaXNQZXJzcGVjdGl2ZUNhbWVyYT0hMDt2YXIgU3I9OTAsRXI9MSxhbz1jbGFzcyBleHRlbmRzIGt0e2NvbnN0cnVjdG9yKHQsZSxpKXtpZihzdXBlcigpLHRoaXMudHlwZT0iQ3ViZUNhbWVyYSIsaS5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldCE9PSEwKXtjb25zb2xlLmVycm9yKCJUSFJFRS5DdWJlQ2FtZXJhOiBUaGUgY29uc3RydWN0b3Igbm93IGV4cGVjdHMgYW4gaW5zdGFuY2Ugb2YgV2ViR0xDdWJlUmVuZGVyVGFyZ2V0IGFzIHRoaXJkIHBhcmFtZXRlci4iKTtyZXR1cm59dGhpcy5yZW5kZXJUYXJnZXQ9aTtsZXQgcj1uZXcgU2UoU3IsRXIsdCxlKTtyLmxheWVycz10aGlzLmxheWVycyxyLnVwLnNldCgwLC0xLDApLHIubG9va0F0KG5ldyBUKDEsMCwwKSksdGhpcy5hZGQocik7bGV0IHM9bmV3IFNlKFNyLEVyLHQsZSk7cy5sYXllcnM9dGhpcy5sYXllcnMscy51cC5zZXQoMCwtMSwwKSxzLmxvb2tBdChuZXcgVCgtMSwwLDApKSx0aGlzLmFkZChzKTtsZXQgbz1uZXcgU2UoU3IsRXIsdCxlKTtvLmxheWVycz10aGlzLmxheWVycyxvLnVwLnNldCgwLDAsMSksby5sb29rQXQobmV3IFQoMCwxLDApKSx0aGlzLmFkZChvKTtsZXQgYT1uZXcgU2UoU3IsRXIsdCxlKTthLmxheWVycz10aGlzLmxheWVycyxhLnVwLnNldCgwLDAsLTEpLGEubG9va0F0KG5ldyBUKDAsLTEsMCkpLHRoaXMuYWRkKGEpO2xldCBsPW5ldyBTZShTcixFcix0LGUpO2wubGF5ZXJzPXRoaXMubGF5ZXJzLGwudXAuc2V0KDAsLTEsMCksbC5sb29rQXQobmV3IFQoMCwwLDEpKSx0aGlzLmFkZChsKTtsZXQgYz1uZXcgU2UoU3IsRXIsdCxlKTtjLmxheWVycz10aGlzLmxheWVycyxjLnVwLnNldCgwLC0xLDApLGMubG9va0F0KG5ldyBUKDAsMCwtMSkpLHRoaXMuYWRkKGMpfXVwZGF0ZSh0LGUpe3RoaXMucGFyZW50PT09bnVsbCYmdGhpcy51cGRhdGVNYXRyaXhXb3JsZCgpO2xldCBpPXRoaXMucmVuZGVyVGFyZ2V0LFtyLHMsbyxhLGwsY109dGhpcy5jaGlsZHJlbix1PXQueHIuZW5hYmxlZCxoPXQuZ2V0UmVuZGVyVGFyZ2V0KCk7dC54ci5lbmFibGVkPSExO2xldCBmPWkudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM7aS50ZXh0dXJlLmdlbmVyYXRlTWlwbWFwcz0hMSx0LnNldFJlbmRlclRhcmdldChpLDApLHQucmVuZGVyKGUsciksdC5zZXRSZW5kZXJUYXJnZXQoaSwxKSx0LnJlbmRlcihlLHMpLHQuc2V0UmVuZGVyVGFyZ2V0KGksMiksdC5yZW5kZXIoZSxvKSx0LnNldFJlbmRlclRhcmdldChpLDMpLHQucmVuZGVyKGUsYSksdC5zZXRSZW5kZXJUYXJnZXQoaSw0KSx0LnJlbmRlcihlLGwpLGkudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM9Zix0LnNldFJlbmRlclRhcmdldChpLDUpLHQucmVuZGVyKGUsYyksdC5zZXRSZW5kZXJUYXJnZXQoaCksdC54ci5lbmFibGVkPXUsaS50ZXh0dXJlLm5lZWRzUE1SRU1VcGRhdGU9ITB9fSxXcj1jbGFzcyBleHRlbmRzIGFle2NvbnN0cnVjdG9yKHQsZSxpLHIscyxvLGEsbCxjLHUpe3Q9dCE9PXZvaWQgMD90OltdLGU9ZSE9PXZvaWQgMD9lOkFvLHN1cGVyKHQsZSxpLHIscyxvLGEsbCxjLHUpLHRoaXMuZmxpcFk9ITF9Z2V0IGltYWdlcygpe3JldHVybiB0aGlzLmltYWdlfXNldCBpbWFnZXModCl7dGhpcy5pbWFnZT10fX07V3IucHJvdG90eXBlLmlzQ3ViZVRleHR1cmU9ITA7dmFyIG9sPWNsYXNzIGV4dGVuZHMgTmV7Y29uc3RydWN0b3IodCxlLGkpe051bWJlci5pc0ludGVnZXIoZSkmJihjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMQ3ViZVJlbmRlclRhcmdldDogY29uc3RydWN0b3Igc2lnbmF0dXJlIGlzIG5vdyBXZWJHTEN1YmVSZW5kZXJUYXJnZXQoIHNpemUsIG9wdGlvbnMgKSIpLGU9aSksc3VwZXIodCx0LGUpLGU9ZXx8e30sdGhpcy50ZXh0dXJlPW5ldyBXcih2b2lkIDAsZS5tYXBwaW5nLGUud3JhcFMsZS53cmFwVCxlLm1hZ0ZpbHRlcixlLm1pbkZpbHRlcixlLmZvcm1hdCxlLnR5cGUsZS5hbmlzb3Ryb3B5LGUuZW5jb2RpbmcpLHRoaXMudGV4dHVyZS5pc1JlbmRlclRhcmdldFRleHR1cmU9ITAsdGhpcy50ZXh0dXJlLmdlbmVyYXRlTWlwbWFwcz1lLmdlbmVyYXRlTWlwbWFwcyE9PXZvaWQgMD9lLmdlbmVyYXRlTWlwbWFwczohMSx0aGlzLnRleHR1cmUubWluRmlsdGVyPWUubWluRmlsdGVyIT09dm9pZCAwP2UubWluRmlsdGVyOmJlfWZyb21FcXVpcmVjdGFuZ3VsYXJUZXh0dXJlKHQsZSl7dGhpcy50ZXh0dXJlLnR5cGU9ZS50eXBlLHRoaXMudGV4dHVyZS5mb3JtYXQ9UmUsdGhpcy50ZXh0dXJlLmVuY29kaW5nPWUuZW5jb2RpbmcsdGhpcy50ZXh0dXJlLmdlbmVyYXRlTWlwbWFwcz1lLmdlbmVyYXRlTWlwbWFwcyx0aGlzLnRleHR1cmUubWluRmlsdGVyPWUubWluRmlsdGVyLHRoaXMudGV4dHVyZS5tYWdGaWx0ZXI9ZS5tYWdGaWx0ZXI7bGV0IGk9e3VuaWZvcm1zOnt0RXF1aXJlY3Q6e3ZhbHVlOm51bGx9fSx2ZXJ0ZXhTaGFkZXI6YAoKCQkJCXZhcnlpbmcgdmVjMyB2V29ybGREaXJlY3Rpb247CgoJCQkJdmVjMyB0cmFuc2Zvcm1EaXJlY3Rpb24oIGluIHZlYzMgZGlyLCBpbiBtYXQ0IG1hdHJpeCApIHsKCgkJCQkJcmV0dXJuIG5vcm1hbGl6ZSggKCBtYXRyaXggKiB2ZWM0KCBkaXIsIDAuMCApICkueHl6ICk7CgoJCQkJfQoKCQkJCXZvaWQgbWFpbigpIHsKCgkJCQkJdldvcmxkRGlyZWN0aW9uID0gdHJhbnNmb3JtRGlyZWN0aW9uKCBwb3NpdGlvbiwgbW9kZWxNYXRyaXggKTsKCgkJCQkJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCQkJCQkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CgoJCQkJfQoJCQlgLGZyYWdtZW50U2hhZGVyOmAKCgkJCQl1bmlmb3JtIHNhbXBsZXIyRCB0RXF1aXJlY3Q7CgoJCQkJdmFyeWluZyB2ZWMzIHZXb3JsZERpcmVjdGlvbjsKCgkJCQkjaW5jbHVkZSA8Y29tbW9uPgoKCQkJCXZvaWQgbWFpbigpIHsKCgkJCQkJdmVjMyBkaXJlY3Rpb24gPSBub3JtYWxpemUoIHZXb3JsZERpcmVjdGlvbiApOwoKCQkJCQl2ZWMyIHNhbXBsZVVWID0gZXF1aXJlY3RVdiggZGlyZWN0aW9uICk7CgoJCQkJCWdsX0ZyYWdDb2xvciA9IHRleHR1cmUyRCggdEVxdWlyZWN0LCBzYW1wbGVVViApOwoKCQkJCX0KCQkJYH0scj1uZXcgSGkoNSw1LDUpLHM9bmV3IEZuKHtuYW1lOiJDdWJlbWFwRnJvbUVxdWlyZWN0Iix1bmlmb3JtczpHcihpLnVuaWZvcm1zKSx2ZXJ0ZXhTaGFkZXI6aS52ZXJ0ZXhTaGFkZXIsZnJhZ21lbnRTaGFkZXI6aS5mcmFnbWVudFNoYWRlcixzaWRlOmhlLGJsZW5kaW5nOmpufSk7cy51bmlmb3Jtcy50RXF1aXJlY3QudmFsdWU9ZTtsZXQgbz1uZXcgb2UocixzKSxhPWUubWluRmlsdGVyO3JldHVybiBlLm1pbkZpbHRlcj09PUxsJiYoZS5taW5GaWx0ZXI9YmUpLG5ldyBhbygxLDEwLHRoaXMpLnVwZGF0ZSh0LG8pLGUubWluRmlsdGVyPWEsby5nZW9tZXRyeS5kaXNwb3NlKCksby5tYXRlcmlhbC5kaXNwb3NlKCksdGhpc31jbGVhcih0LGUsaSxyKXtsZXQgcz10LmdldFJlbmRlclRhcmdldCgpO2ZvcihsZXQgbz0wO288NjtvKyspdC5zZXRSZW5kZXJUYXJnZXQodGhpcyxvKSx0LmNsZWFyKGUsaSxyKTt0LnNldFJlbmRlclRhcmdldChzKX19O29sLnByb3RvdHlwZS5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldD0hMDt2YXIgRXU9bmV3IFQseE09bmV3IFQseU09bmV3IGRlLGplPWNsYXNze2NvbnN0cnVjdG9yKHQ9bmV3IFQoMSwwLDApLGU9MCl7dGhpcy5ub3JtYWw9dCx0aGlzLmNvbnN0YW50PWV9c2V0KHQsZSl7cmV0dXJuIHRoaXMubm9ybWFsLmNvcHkodCksdGhpcy5jb25zdGFudD1lLHRoaXN9c2V0Q29tcG9uZW50cyh0LGUsaSxyKXtyZXR1cm4gdGhpcy5ub3JtYWwuc2V0KHQsZSxpKSx0aGlzLmNvbnN0YW50PXIsdGhpc31zZXRGcm9tTm9ybWFsQW5kQ29wbGFuYXJQb2ludCh0LGUpe3JldHVybiB0aGlzLm5vcm1hbC5jb3B5KHQpLHRoaXMuY29uc3RhbnQ9LWUuZG90KHRoaXMubm9ybWFsKSx0aGlzfXNldEZyb21Db3BsYW5hclBvaW50cyh0LGUsaSl7bGV0IHI9RXUuc3ViVmVjdG9ycyhpLGUpLmNyb3NzKHhNLnN1YlZlY3RvcnModCxlKSkubm9ybWFsaXplKCk7cmV0dXJuIHRoaXMuc2V0RnJvbU5vcm1hbEFuZENvcGxhbmFyUG9pbnQocix0KSx0aGlzfWNvcHkodCl7cmV0dXJuIHRoaXMubm9ybWFsLmNvcHkodC5ub3JtYWwpLHRoaXMuY29uc3RhbnQ9dC5jb25zdGFudCx0aGlzfW5vcm1hbGl6ZSgpe2xldCB0PTEvdGhpcy5ub3JtYWwubGVuZ3RoKCk7cmV0dXJuIHRoaXMubm9ybWFsLm11bHRpcGx5U2NhbGFyKHQpLHRoaXMuY29uc3RhbnQqPXQsdGhpc31uZWdhdGUoKXtyZXR1cm4gdGhpcy5jb25zdGFudCo9LTEsdGhpcy5ub3JtYWwubmVnYXRlKCksdGhpc31kaXN0YW5jZVRvUG9pbnQodCl7cmV0dXJuIHRoaXMubm9ybWFsLmRvdCh0KSt0aGlzLmNvbnN0YW50fWRpc3RhbmNlVG9TcGhlcmUodCl7cmV0dXJuIHRoaXMuZGlzdGFuY2VUb1BvaW50KHQuY2VudGVyKS10LnJhZGl1c31wcm9qZWN0UG9pbnQodCxlKXtyZXR1cm4gZS5jb3B5KHRoaXMubm9ybWFsKS5tdWx0aXBseVNjYWxhcigtdGhpcy5kaXN0YW5jZVRvUG9pbnQodCkpLmFkZCh0KX1pbnRlcnNlY3RMaW5lKHQsZSl7bGV0IGk9dC5kZWx0YShFdSkscj10aGlzLm5vcm1hbC5kb3QoaSk7aWYocj09PTApcmV0dXJuIHRoaXMuZGlzdGFuY2VUb1BvaW50KHQuc3RhcnQpPT09MD9lLmNvcHkodC5zdGFydCk6bnVsbDtsZXQgcz0tKHQuc3RhcnQuZG90KHRoaXMubm9ybWFsKSt0aGlzLmNvbnN0YW50KS9yO3JldHVybiBzPDB8fHM+MT9udWxsOmUuY29weShpKS5tdWx0aXBseVNjYWxhcihzKS5hZGQodC5zdGFydCl9aW50ZXJzZWN0c0xpbmUodCl7bGV0IGU9dGhpcy5kaXN0YW5jZVRvUG9pbnQodC5zdGFydCksaT10aGlzLmRpc3RhbmNlVG9Qb2ludCh0LmVuZCk7cmV0dXJuIGU8MCYmaT4wfHxpPDAmJmU+MH1pbnRlcnNlY3RzQm94KHQpe3JldHVybiB0LmludGVyc2VjdHNQbGFuZSh0aGlzKX1pbnRlcnNlY3RzU3BoZXJlKHQpe3JldHVybiB0LmludGVyc2VjdHNQbGFuZSh0aGlzKX1jb3BsYW5hclBvaW50KHQpe3JldHVybiB0LmNvcHkodGhpcy5ub3JtYWwpLm11bHRpcGx5U2NhbGFyKC10aGlzLmNvbnN0YW50KX1hcHBseU1hdHJpeDQodCxlKXtsZXQgaT1lfHx5TS5nZXROb3JtYWxNYXRyaXgodCkscj10aGlzLmNvcGxhbmFyUG9pbnQoRXUpLmFwcGx5TWF0cml4NCh0KSxzPXRoaXMubm9ybWFsLmFwcGx5TWF0cml4MyhpKS5ub3JtYWxpemUoKTtyZXR1cm4gdGhpcy5jb25zdGFudD0tci5kb3QocyksdGhpc310cmFuc2xhdGUodCl7cmV0dXJuIHRoaXMuY29uc3RhbnQtPXQuZG90KHRoaXMubm9ybWFsKSx0aGlzfWVxdWFscyh0KXtyZXR1cm4gdC5ub3JtYWwuZXF1YWxzKHRoaXMubm9ybWFsKSYmdC5jb25zdGFudD09PXRoaXMuY29uc3RhbnR9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfX07amUucHJvdG90eXBlLmlzUGxhbmU9ITA7dmFyIFRyPW5ldyBzaSx6YT1uZXcgVCxxcj1jbGFzc3tjb25zdHJ1Y3Rvcih0PW5ldyBqZSxlPW5ldyBqZSxpPW5ldyBqZSxyPW5ldyBqZSxzPW5ldyBqZSxvPW5ldyBqZSl7dGhpcy5wbGFuZXM9W3QsZSxpLHIscyxvXX1zZXQodCxlLGkscixzLG8pe2xldCBhPXRoaXMucGxhbmVzO3JldHVybiBhWzBdLmNvcHkodCksYVsxXS5jb3B5KGUpLGFbMl0uY29weShpKSxhWzNdLmNvcHkociksYVs0XS5jb3B5KHMpLGFbNV0uY29weShvKSx0aGlzfWNvcHkodCl7bGV0IGU9dGhpcy5wbGFuZXM7Zm9yKGxldCBpPTA7aTw2O2krKyllW2ldLmNvcHkodC5wbGFuZXNbaV0pO3JldHVybiB0aGlzfXNldEZyb21Qcm9qZWN0aW9uTWF0cml4KHQpe2xldCBlPXRoaXMucGxhbmVzLGk9dC5lbGVtZW50cyxyPWlbMF0scz1pWzFdLG89aVsyXSxhPWlbM10sbD1pWzRdLGM9aVs1XSx1PWlbNl0saD1pWzddLGY9aVs4XSxkPWlbOV0sZz1pWzEwXSx4PWlbMTFdLHY9aVsxMl0sbT1pWzEzXSxwPWlbMTRdLGI9aVsxNV07cmV0dXJuIGVbMF0uc2V0Q29tcG9uZW50cyhhLXIsaC1sLHgtZixiLXYpLm5vcm1hbGl6ZSgpLGVbMV0uc2V0Q29tcG9uZW50cyhhK3IsaCtsLHgrZixiK3YpLm5vcm1hbGl6ZSgpLGVbMl0uc2V0Q29tcG9uZW50cyhhK3MsaCtjLHgrZCxiK20pLm5vcm1hbGl6ZSgpLGVbM10uc2V0Q29tcG9uZW50cyhhLXMsaC1jLHgtZCxiLW0pLm5vcm1hbGl6ZSgpLGVbNF0uc2V0Q29tcG9uZW50cyhhLW8saC11LHgtZyxiLXApLm5vcm1hbGl6ZSgpLGVbNV0uc2V0Q29tcG9uZW50cyhhK28saCt1LHgrZyxiK3ApLm5vcm1hbGl6ZSgpLHRoaXN9aW50ZXJzZWN0c09iamVjdCh0KXtsZXQgZT10Lmdlb21ldHJ5O3JldHVybiBlLmJvdW5kaW5nU3BoZXJlPT09bnVsbCYmZS5jb21wdXRlQm91bmRpbmdTcGhlcmUoKSxUci5jb3B5KGUuYm91bmRpbmdTcGhlcmUpLmFwcGx5TWF0cml4NCh0Lm1hdHJpeFdvcmxkKSx0aGlzLmludGVyc2VjdHNTcGhlcmUoVHIpfWludGVyc2VjdHNTcHJpdGUodCl7cmV0dXJuIFRyLmNlbnRlci5zZXQoMCwwLDApLFRyLnJhZGl1cz0uNzA3MTA2NzgxMTg2NTQ3NixUci5hcHBseU1hdHJpeDQodC5tYXRyaXhXb3JsZCksdGhpcy5pbnRlcnNlY3RzU3BoZXJlKFRyKX1pbnRlcnNlY3RzU3BoZXJlKHQpe2xldCBlPXRoaXMucGxhbmVzLGk9dC5jZW50ZXIscj0tdC5yYWRpdXM7Zm9yKGxldCBzPTA7czw2O3MrKylpZihlW3NdLmRpc3RhbmNlVG9Qb2ludChpKTxyKXJldHVybiExO3JldHVybiEwfWludGVyc2VjdHNCb3godCl7bGV0IGU9dGhpcy5wbGFuZXM7Zm9yKGxldCBpPTA7aTw2O2krKyl7bGV0IHI9ZVtpXTtpZih6YS54PXIubm9ybWFsLng+MD90Lm1heC54OnQubWluLngsemEueT1yLm5vcm1hbC55PjA/dC5tYXgueTp0Lm1pbi55LHphLno9ci5ub3JtYWwuej4wP3QubWF4Lno6dC5taW4ueixyLmRpc3RhbmNlVG9Qb2ludCh6YSk8MClyZXR1cm4hMX1yZXR1cm4hMH1jb250YWluc1BvaW50KHQpe2xldCBlPXRoaXMucGxhbmVzO2ZvcihsZXQgaT0wO2k8NjtpKyspaWYoZVtpXS5kaXN0YW5jZVRvUG9pbnQodCk8MClyZXR1cm4hMTtyZXR1cm4hMH1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyl9fTtmdW5jdGlvbiBnMCgpe2xldCBuPW51bGwsdD0hMSxlPW51bGwsaT1udWxsO2Z1bmN0aW9uIHIocyxvKXtlKHMsbyksaT1uLnJlcXVlc3RBbmltYXRpb25GcmFtZShyKX1yZXR1cm57c3RhcnQ6ZnVuY3Rpb24oKXt0IT09ITAmJmUhPT1udWxsJiYoaT1uLnJlcXVlc3RBbmltYXRpb25GcmFtZShyKSx0PSEwKX0sc3RvcDpmdW5jdGlvbigpe24uY2FuY2VsQW5pbWF0aW9uRnJhbWUoaSksdD0hMX0sc2V0QW5pbWF0aW9uTG9vcDpmdW5jdGlvbihzKXtlPXN9LHNldENvbnRleHQ6ZnVuY3Rpb24ocyl7bj1zfX19ZnVuY3Rpb24gdk0obix0KXtsZXQgZT10LmlzV2ViR0wyLGk9bmV3IFdlYWtNYXA7ZnVuY3Rpb24gcihjLHUpe2xldCBoPWMuYXJyYXksZj1jLnVzYWdlLGQ9bi5jcmVhdGVCdWZmZXIoKTtuLmJpbmRCdWZmZXIodSxkKSxuLmJ1ZmZlckRhdGEodSxoLGYpLGMub25VcGxvYWRDYWxsYmFjaygpO2xldCBnPTUxMjY7cmV0dXJuIGggaW5zdGFuY2VvZiBGbG9hdDMyQXJyYXk/Zz01MTI2OmggaW5zdGFuY2VvZiBGbG9hdDY0QXJyYXk/Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTEF0dHJpYnV0ZXM6IFVuc3VwcG9ydGVkIGRhdGEgYnVmZmVyIGZvcm1hdDogRmxvYXQ2NEFycmF5LiIpOmggaW5zdGFuY2VvZiBVaW50MTZBcnJheT9jLmlzRmxvYXQxNkJ1ZmZlckF0dHJpYnV0ZT9lP2c9NTEzMTpjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMQXR0cmlidXRlczogVXNhZ2Ugb2YgRmxvYXQxNkJ1ZmZlckF0dHJpYnV0ZSByZXF1aXJlcyBXZWJHTDIuIik6Zz01MTIzOmggaW5zdGFuY2VvZiBJbnQxNkFycmF5P2c9NTEyMjpoIGluc3RhbmNlb2YgVWludDMyQXJyYXk/Zz01MTI1OmggaW5zdGFuY2VvZiBJbnQzMkFycmF5P2c9NTEyNDpoIGluc3RhbmNlb2YgSW50OEFycmF5P2c9NTEyMDooaCBpbnN0YW5jZW9mIFVpbnQ4QXJyYXl8fGggaW5zdGFuY2VvZiBVaW50OENsYW1wZWRBcnJheSkmJihnPTUxMjEpLHtidWZmZXI6ZCx0eXBlOmcsYnl0ZXNQZXJFbGVtZW50OmguQllURVNfUEVSX0VMRU1FTlQsdmVyc2lvbjpjLnZlcnNpb259fWZ1bmN0aW9uIHMoYyx1LGgpe2xldCBmPXUuYXJyYXksZD11LnVwZGF0ZVJhbmdlO24uYmluZEJ1ZmZlcihoLGMpLGQuY291bnQ9PT0tMT9uLmJ1ZmZlclN1YkRhdGEoaCwwLGYpOihlP24uYnVmZmVyU3ViRGF0YShoLGQub2Zmc2V0KmYuQllURVNfUEVSX0VMRU1FTlQsZixkLm9mZnNldCxkLmNvdW50KTpuLmJ1ZmZlclN1YkRhdGEoaCxkLm9mZnNldCpmLkJZVEVTX1BFUl9FTEVNRU5ULGYuc3ViYXJyYXkoZC5vZmZzZXQsZC5vZmZzZXQrZC5jb3VudCkpLGQuY291bnQ9LTEpfWZ1bmN0aW9uIG8oYyl7cmV0dXJuIGMuaXNJbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZSYmKGM9Yy5kYXRhKSxpLmdldChjKX1mdW5jdGlvbiBhKGMpe2MuaXNJbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZSYmKGM9Yy5kYXRhKTtsZXQgdT1pLmdldChjKTt1JiYobi5kZWxldGVCdWZmZXIodS5idWZmZXIpLGkuZGVsZXRlKGMpKX1mdW5jdGlvbiBsKGMsdSl7aWYoYy5pc0dMQnVmZmVyQXR0cmlidXRlKXtsZXQgZj1pLmdldChjKTsoIWZ8fGYudmVyc2lvbjxjLnZlcnNpb24pJiZpLnNldChjLHtidWZmZXI6Yy5idWZmZXIsdHlwZTpjLnR5cGUsYnl0ZXNQZXJFbGVtZW50OmMuZWxlbWVudFNpemUsdmVyc2lvbjpjLnZlcnNpb259KTtyZXR1cm59Yy5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlJiYoYz1jLmRhdGEpO2xldCBoPWkuZ2V0KGMpO2g9PT12b2lkIDA/aS5zZXQoYyxyKGMsdSkpOmgudmVyc2lvbjxjLnZlcnNpb24mJihzKGguYnVmZmVyLGMsdSksaC52ZXJzaW9uPWMudmVyc2lvbil9cmV0dXJue2dldDpvLHJlbW92ZTphLHVwZGF0ZTpsfX12YXIgbG89Y2xhc3MgZXh0ZW5kcyBIdHtjb25zdHJ1Y3Rvcih0PTEsZT0xLGk9MSxyPTEpe3N1cGVyKCksdGhpcy50eXBlPSJQbGFuZUdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3dpZHRoOnQsaGVpZ2h0OmUsd2lkdGhTZWdtZW50czppLGhlaWdodFNlZ21lbnRzOnJ9O2xldCBzPXQvMixvPWUvMixhPU1hdGguZmxvb3IoaSksbD1NYXRoLmZsb29yKHIpLGM9YSsxLHU9bCsxLGg9dC9hLGY9ZS9sLGQ9W10sZz1bXSx4PVtdLHY9W107Zm9yKGxldCBtPTA7bTx1O20rKyl7bGV0IHA9bSpmLW87Zm9yKGxldCBiPTA7YjxjO2IrKyl7bGV0IF89YipoLXM7Zy5wdXNoKF8sLXAsMCkseC5wdXNoKDAsMCwxKSx2LnB1c2goYi9hKSx2LnB1c2goMS1tL2wpfX1mb3IobGV0IG09MDttPGw7bSsrKWZvcihsZXQgcD0wO3A8YTtwKyspe2xldCBiPXArYyptLF89cCtjKihtKzEpLFM9cCsxK2MqKG0rMSksTD1wKzErYyptO2QucHVzaChiLF8sTCksZC5wdXNoKF8sUyxMKX10aGlzLnNldEluZGV4KGQpLHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IGVlKGcsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG5ldyBlZSh4LDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyBlZSh2LDIpKX1zdGF0aWMgZnJvbUpTT04odCl7cmV0dXJuIG5ldyBsbyh0LndpZHRoLHQuaGVpZ2h0LHQud2lkdGhTZWdtZW50cyx0LmhlaWdodFNlZ21lbnRzKX19LF9NPWAjaWZkZWYgVVNFX0FMUEhBTUFQCglkaWZmdXNlQ29sb3IuYSAqPSB0ZXh0dXJlMkQoIGFscGhhTWFwLCB2VXYgKS5nOwojZW5kaWZgLHdNPWAjaWZkZWYgVVNFX0FMUEhBTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBhbHBoYU1hcDsKI2VuZGlmYCxNTT1gI2lmZGVmIFVTRV9BTFBIQVRFU1QKCWlmICggZGlmZnVzZUNvbG9yLmEgPCBhbHBoYVRlc3QgKSBkaXNjYXJkOwojZW5kaWZgLGJNPWAjaWZkZWYgVVNFX0FMUEhBVEVTVAoJdW5pZm9ybSBmbG9hdCBhbHBoYVRlc3Q7CiNlbmRpZmAsU009YCNpZmRlZiBVU0VfQU9NQVAKCWZsb2F0IGFtYmllbnRPY2NsdXNpb24gPSAoIHRleHR1cmUyRCggYW9NYXAsIHZVdjIgKS5yIC0gMS4wICkgKiBhb01hcEludGVuc2l0eSArIDEuMDsKCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSAqPSBhbWJpZW50T2NjbHVzaW9uOwoJI2lmIGRlZmluZWQoIFVTRV9FTlZNQVAgKSAmJiBkZWZpbmVkKCBTVEFOREFSRCApCgkJZmxvYXQgZG90TlYgPSBzYXR1cmF0ZSggZG90KCBnZW9tZXRyeS5ub3JtYWwsIGdlb21ldHJ5LnZpZXdEaXIgKSApOwoJCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0U3BlY3VsYXIgKj0gY29tcHV0ZVNwZWN1bGFyT2NjbHVzaW9uKCBkb3ROViwgYW1iaWVudE9jY2x1c2lvbiwgbWF0ZXJpYWwucm91Z2huZXNzICk7CgkjZW5kaWYKI2VuZGlmYCxFTT1gI2lmZGVmIFVTRV9BT01BUAoJdW5pZm9ybSBzYW1wbGVyMkQgYW9NYXA7Cgl1bmlmb3JtIGZsb2F0IGFvTWFwSW50ZW5zaXR5OwojZW5kaWZgLFRNPSJ2ZWMzIHRyYW5zZm9ybWVkID0gdmVjMyggcG9zaXRpb24gKTsiLEFNPWB2ZWMzIG9iamVjdE5vcm1hbCA9IHZlYzMoIG5vcm1hbCApOwojaWZkZWYgVVNFX1RBTkdFTlQKCXZlYzMgb2JqZWN0VGFuZ2VudCA9IHZlYzMoIHRhbmdlbnQueHl6ICk7CiNlbmRpZmAsQ009YHZlYzMgQlJERl9MYW1iZXJ0KCBjb25zdCBpbiB2ZWMzIGRpZmZ1c2VDb2xvciApIHsKCXJldHVybiBSRUNJUFJPQ0FMX1BJICogZGlmZnVzZUNvbG9yOwp9CnZlYzMgRl9TY2hsaWNrKCBjb25zdCBpbiB2ZWMzIGYwLCBjb25zdCBpbiBmbG9hdCBmOTAsIGNvbnN0IGluIGZsb2F0IGRvdFZIICkgewoJZmxvYXQgZnJlc25lbCA9IGV4cDIoICggLSA1LjU1NDczICogZG90VkggLSA2Ljk4MzE2ICkgKiBkb3RWSCApOwoJcmV0dXJuIGYwICogKCAxLjAgLSBmcmVzbmVsICkgKyAoIGY5MCAqIGZyZXNuZWwgKTsKfQpmbG9hdCBWX0dHWF9TbWl0aENvcnJlbGF0ZWQoIGNvbnN0IGluIGZsb2F0IGFscGhhLCBjb25zdCBpbiBmbG9hdCBkb3ROTCwgY29uc3QgaW4gZmxvYXQgZG90TlYgKSB7CglmbG9hdCBhMiA9IHBvdzIoIGFscGhhICk7CglmbG9hdCBndiA9IGRvdE5MICogc3FydCggYTIgKyAoIDEuMCAtIGEyICkgKiBwb3cyKCBkb3ROViApICk7CglmbG9hdCBnbCA9IGRvdE5WICogc3FydCggYTIgKyAoIDEuMCAtIGEyICkgKiBwb3cyKCBkb3ROTCApICk7CglyZXR1cm4gMC41IC8gbWF4KCBndiArIGdsLCBFUFNJTE9OICk7Cn0KZmxvYXQgRF9HR1goIGNvbnN0IGluIGZsb2F0IGFscGhhLCBjb25zdCBpbiBmbG9hdCBkb3ROSCApIHsKCWZsb2F0IGEyID0gcG93MiggYWxwaGEgKTsKCWZsb2F0IGRlbm9tID0gcG93MiggZG90TkggKSAqICggYTIgLSAxLjAgKSArIDEuMDsKCXJldHVybiBSRUNJUFJPQ0FMX1BJICogYTIgLyBwb3cyKCBkZW5vbSApOwp9CnZlYzMgQlJERl9HR1goIGNvbnN0IGluIHZlYzMgbGlnaHREaXIsIGNvbnN0IGluIHZlYzMgdmlld0RpciwgY29uc3QgaW4gdmVjMyBub3JtYWwsIGNvbnN0IGluIHZlYzMgZjAsIGNvbnN0IGluIGZsb2F0IGY5MCwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzICkgewoJZmxvYXQgYWxwaGEgPSBwb3cyKCByb3VnaG5lc3MgKTsKCXZlYzMgaGFsZkRpciA9IG5vcm1hbGl6ZSggbGlnaHREaXIgKyB2aWV3RGlyICk7CglmbG9hdCBkb3ROTCA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgbGlnaHREaXIgKSApOwoJZmxvYXQgZG90TlYgPSBzYXR1cmF0ZSggZG90KCBub3JtYWwsIHZpZXdEaXIgKSApOwoJZmxvYXQgZG90TkggPSBzYXR1cmF0ZSggZG90KCBub3JtYWwsIGhhbGZEaXIgKSApOwoJZmxvYXQgZG90VkggPSBzYXR1cmF0ZSggZG90KCB2aWV3RGlyLCBoYWxmRGlyICkgKTsKCXZlYzMgRiA9IEZfU2NobGljayggZjAsIGY5MCwgZG90VkggKTsKCWZsb2F0IFYgPSBWX0dHWF9TbWl0aENvcnJlbGF0ZWQoIGFscGhhLCBkb3ROTCwgZG90TlYgKTsKCWZsb2F0IEQgPSBEX0dHWCggYWxwaGEsIGRvdE5IICk7CglyZXR1cm4gRiAqICggViAqIEQgKTsKfQp2ZWMyIExUQ19VdiggY29uc3QgaW4gdmVjMyBOLCBjb25zdCBpbiB2ZWMzIFYsIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcyApIHsKCWNvbnN0IGZsb2F0IExVVF9TSVpFID0gNjQuMDsKCWNvbnN0IGZsb2F0IExVVF9TQ0FMRSA9ICggTFVUX1NJWkUgLSAxLjAgKSAvIExVVF9TSVpFOwoJY29uc3QgZmxvYXQgTFVUX0JJQVMgPSAwLjUgLyBMVVRfU0laRTsKCWZsb2F0IGRvdE5WID0gc2F0dXJhdGUoIGRvdCggTiwgViApICk7Cgl2ZWMyIHV2ID0gdmVjMiggcm91Z2huZXNzLCBzcXJ0KCAxLjAgLSBkb3ROViApICk7Cgl1diA9IHV2ICogTFVUX1NDQUxFICsgTFVUX0JJQVM7CglyZXR1cm4gdXY7Cn0KZmxvYXQgTFRDX0NsaXBwZWRTcGhlcmVGb3JtRmFjdG9yKCBjb25zdCBpbiB2ZWMzIGYgKSB7CglmbG9hdCBsID0gbGVuZ3RoKCBmICk7CglyZXR1cm4gbWF4KCAoIGwgKiBsICsgZi56ICkgLyAoIGwgKyAxLjAgKSwgMC4wICk7Cn0KdmVjMyBMVENfRWRnZVZlY3RvckZvcm1GYWN0b3IoIGNvbnN0IGluIHZlYzMgdjEsIGNvbnN0IGluIHZlYzMgdjIgKSB7CglmbG9hdCB4ID0gZG90KCB2MSwgdjIgKTsKCWZsb2F0IHkgPSBhYnMoIHggKTsKCWZsb2F0IGEgPSAwLjg1NDM5ODUgKyAoIDAuNDk2NTE1NSArIDAuMDE0NTIwNiAqIHkgKSAqIHk7CglmbG9hdCBiID0gMy40MTc1OTQwICsgKCA0LjE2MTY3MjQgKyB5ICkgKiB5OwoJZmxvYXQgdiA9IGEgLyBiOwoJZmxvYXQgdGhldGFfc2ludGhldGEgPSAoIHggPiAwLjAgKSA/IHYgOiAwLjUgKiBpbnZlcnNlc3FydCggbWF4KCAxLjAgLSB4ICogeCwgMWUtNyApICkgLSB2OwoJcmV0dXJuIGNyb3NzKCB2MSwgdjIgKSAqIHRoZXRhX3NpbnRoZXRhOwp9CnZlYzMgTFRDX0V2YWx1YXRlKCBjb25zdCBpbiB2ZWMzIE4sIGNvbnN0IGluIHZlYzMgViwgY29uc3QgaW4gdmVjMyBQLCBjb25zdCBpbiBtYXQzIG1JbnYsIGNvbnN0IGluIHZlYzMgcmVjdENvb3Jkc1sgNCBdICkgewoJdmVjMyB2MSA9IHJlY3RDb29yZHNbIDEgXSAtIHJlY3RDb29yZHNbIDAgXTsKCXZlYzMgdjIgPSByZWN0Q29vcmRzWyAzIF0gLSByZWN0Q29vcmRzWyAwIF07Cgl2ZWMzIGxpZ2h0Tm9ybWFsID0gY3Jvc3MoIHYxLCB2MiApOwoJaWYoIGRvdCggbGlnaHROb3JtYWwsIFAgLSByZWN0Q29vcmRzWyAwIF0gKSA8IDAuMCApIHJldHVybiB2ZWMzKCAwLjAgKTsKCXZlYzMgVDEsIFQyOwoJVDEgPSBub3JtYWxpemUoIFYgLSBOICogZG90KCBWLCBOICkgKTsKCVQyID0gLSBjcm9zcyggTiwgVDEgKTsKCW1hdDMgbWF0ID0gbUludiAqIHRyYW5zcG9zZU1hdDMoIG1hdDMoIFQxLCBUMiwgTiApICk7Cgl2ZWMzIGNvb3Jkc1sgNCBdOwoJY29vcmRzWyAwIF0gPSBtYXQgKiAoIHJlY3RDb29yZHNbIDAgXSAtIFAgKTsKCWNvb3Jkc1sgMSBdID0gbWF0ICogKCByZWN0Q29vcmRzWyAxIF0gLSBQICk7Cgljb29yZHNbIDIgXSA9IG1hdCAqICggcmVjdENvb3Jkc1sgMiBdIC0gUCApOwoJY29vcmRzWyAzIF0gPSBtYXQgKiAoIHJlY3RDb29yZHNbIDMgXSAtIFAgKTsKCWNvb3Jkc1sgMCBdID0gbm9ybWFsaXplKCBjb29yZHNbIDAgXSApOwoJY29vcmRzWyAxIF0gPSBub3JtYWxpemUoIGNvb3Jkc1sgMSBdICk7Cgljb29yZHNbIDIgXSA9IG5vcm1hbGl6ZSggY29vcmRzWyAyIF0gKTsKCWNvb3Jkc1sgMyBdID0gbm9ybWFsaXplKCBjb29yZHNbIDMgXSApOwoJdmVjMyB2ZWN0b3JGb3JtRmFjdG9yID0gdmVjMyggMC4wICk7Cgl2ZWN0b3JGb3JtRmFjdG9yICs9IExUQ19FZGdlVmVjdG9yRm9ybUZhY3RvciggY29vcmRzWyAwIF0sIGNvb3Jkc1sgMSBdICk7Cgl2ZWN0b3JGb3JtRmFjdG9yICs9IExUQ19FZGdlVmVjdG9yRm9ybUZhY3RvciggY29vcmRzWyAxIF0sIGNvb3Jkc1sgMiBdICk7Cgl2ZWN0b3JGb3JtRmFjdG9yICs9IExUQ19FZGdlVmVjdG9yRm9ybUZhY3RvciggY29vcmRzWyAyIF0sIGNvb3Jkc1sgMyBdICk7Cgl2ZWN0b3JGb3JtRmFjdG9yICs9IExUQ19FZGdlVmVjdG9yRm9ybUZhY3RvciggY29vcmRzWyAzIF0sIGNvb3Jkc1sgMCBdICk7CglmbG9hdCByZXN1bHQgPSBMVENfQ2xpcHBlZFNwaGVyZUZvcm1GYWN0b3IoIHZlY3RvckZvcm1GYWN0b3IgKTsKCXJldHVybiB2ZWMzKCByZXN1bHQgKTsKfQpmbG9hdCBHX0JsaW5uUGhvbmdfSW1wbGljaXQoICkgewoJcmV0dXJuIDAuMjU7Cn0KZmxvYXQgRF9CbGlublBob25nKCBjb25zdCBpbiBmbG9hdCBzaGluaW5lc3MsIGNvbnN0IGluIGZsb2F0IGRvdE5IICkgewoJcmV0dXJuIFJFQ0lQUk9DQUxfUEkgKiAoIHNoaW5pbmVzcyAqIDAuNSArIDEuMCApICogcG93KCBkb3ROSCwgc2hpbmluZXNzICk7Cn0KdmVjMyBCUkRGX0JsaW5uUGhvbmcoIGNvbnN0IGluIHZlYzMgbGlnaHREaXIsIGNvbnN0IGluIHZlYzMgdmlld0RpciwgY29uc3QgaW4gdmVjMyBub3JtYWwsIGNvbnN0IGluIHZlYzMgc3BlY3VsYXJDb2xvciwgY29uc3QgaW4gZmxvYXQgc2hpbmluZXNzICkgewoJdmVjMyBoYWxmRGlyID0gbm9ybWFsaXplKCBsaWdodERpciArIHZpZXdEaXIgKTsKCWZsb2F0IGRvdE5IID0gc2F0dXJhdGUoIGRvdCggbm9ybWFsLCBoYWxmRGlyICkgKTsKCWZsb2F0IGRvdFZIID0gc2F0dXJhdGUoIGRvdCggdmlld0RpciwgaGFsZkRpciApICk7Cgl2ZWMzIEYgPSBGX1NjaGxpY2soIHNwZWN1bGFyQ29sb3IsIDEuMCwgZG90VkggKTsKCWZsb2F0IEcgPSBHX0JsaW5uUGhvbmdfSW1wbGljaXQoICk7CglmbG9hdCBEID0gRF9CbGlublBob25nKCBzaGluaW5lc3MsIGRvdE5IICk7CglyZXR1cm4gRiAqICggRyAqIEQgKTsKfQojaWYgZGVmaW5lZCggVVNFX1NIRUVOICkKZmxvYXQgRF9DaGFybGllKCBmbG9hdCByb3VnaG5lc3MsIGZsb2F0IGRvdE5IICkgewoJZmxvYXQgYWxwaGEgPSBwb3cyKCByb3VnaG5lc3MgKTsKCWZsb2F0IGludkFscGhhID0gMS4wIC8gYWxwaGE7CglmbG9hdCBjb3MyaCA9IGRvdE5IICogZG90Tkg7CglmbG9hdCBzaW4yaCA9IG1heCggMS4wIC0gY29zMmgsIDAuMDA3ODEyNSApOwoJcmV0dXJuICggMi4wICsgaW52QWxwaGEgKSAqIHBvdyggc2luMmgsIGludkFscGhhICogMC41ICkgLyAoIDIuMCAqIFBJICk7Cn0KZmxvYXQgVl9OZXViZWx0KCBmbG9hdCBkb3ROViwgZmxvYXQgZG90TkwgKSB7CglyZXR1cm4gc2F0dXJhdGUoIDEuMCAvICggNC4wICogKCBkb3ROTCArIGRvdE5WIC0gZG90TkwgKiBkb3ROViApICkgKTsKfQp2ZWMzIEJSREZfU2hlZW4oIGNvbnN0IGluIHZlYzMgbGlnaHREaXIsIGNvbnN0IGluIHZlYzMgdmlld0RpciwgY29uc3QgaW4gdmVjMyBub3JtYWwsIHZlYzMgc2hlZW5Db2xvciwgY29uc3QgaW4gZmxvYXQgc2hlZW5Sb3VnaG5lc3MgKSB7Cgl2ZWMzIGhhbGZEaXIgPSBub3JtYWxpemUoIGxpZ2h0RGlyICsgdmlld0RpciApOwoJZmxvYXQgZG90TkwgPSBzYXR1cmF0ZSggZG90KCBub3JtYWwsIGxpZ2h0RGlyICkgKTsKCWZsb2F0IGRvdE5WID0gc2F0dXJhdGUoIGRvdCggbm9ybWFsLCB2aWV3RGlyICkgKTsKCWZsb2F0IGRvdE5IID0gc2F0dXJhdGUoIGRvdCggbm9ybWFsLCBoYWxmRGlyICkgKTsKCWZsb2F0IEQgPSBEX0NoYXJsaWUoIHNoZWVuUm91Z2huZXNzLCBkb3ROSCApOwoJZmxvYXQgViA9IFZfTmV1YmVsdCggZG90TlYsIGRvdE5MICk7CglyZXR1cm4gc2hlZW5Db2xvciAqICggRCAqIFYgKTsKfQojZW5kaWZgLFJNPWAjaWZkZWYgVVNFX0JVTVBNQVAKCXVuaWZvcm0gc2FtcGxlcjJEIGJ1bXBNYXA7Cgl1bmlmb3JtIGZsb2F0IGJ1bXBTY2FsZTsKCXZlYzIgZEhkeHlfZndkKCkgewoJCXZlYzIgZFNUZHggPSBkRmR4KCB2VXYgKTsKCQl2ZWMyIGRTVGR5ID0gZEZkeSggdlV2ICk7CgkJZmxvYXQgSGxsID0gYnVtcFNjYWxlICogdGV4dHVyZTJEKCBidW1wTWFwLCB2VXYgKS54OwoJCWZsb2F0IGRCeCA9IGJ1bXBTY2FsZSAqIHRleHR1cmUyRCggYnVtcE1hcCwgdlV2ICsgZFNUZHggKS54IC0gSGxsOwoJCWZsb2F0IGRCeSA9IGJ1bXBTY2FsZSAqIHRleHR1cmUyRCggYnVtcE1hcCwgdlV2ICsgZFNUZHkgKS54IC0gSGxsOwoJCXJldHVybiB2ZWMyKCBkQngsIGRCeSApOwoJfQoJdmVjMyBwZXJ0dXJiTm9ybWFsQXJiKCB2ZWMzIHN1cmZfcG9zLCB2ZWMzIHN1cmZfbm9ybSwgdmVjMiBkSGR4eSwgZmxvYXQgZmFjZURpcmVjdGlvbiApIHsKCQl2ZWMzIHZTaWdtYVggPSB2ZWMzKCBkRmR4KCBzdXJmX3Bvcy54ICksIGRGZHgoIHN1cmZfcG9zLnkgKSwgZEZkeCggc3VyZl9wb3MueiApICk7CgkJdmVjMyB2U2lnbWFZID0gdmVjMyggZEZkeSggc3VyZl9wb3MueCApLCBkRmR5KCBzdXJmX3Bvcy55ICksIGRGZHkoIHN1cmZfcG9zLnogKSApOwoJCXZlYzMgdk4gPSBzdXJmX25vcm07CgkJdmVjMyBSMSA9IGNyb3NzKCB2U2lnbWFZLCB2TiApOwoJCXZlYzMgUjIgPSBjcm9zcyggdk4sIHZTaWdtYVggKTsKCQlmbG9hdCBmRGV0ID0gZG90KCB2U2lnbWFYLCBSMSApICogZmFjZURpcmVjdGlvbjsKCQl2ZWMzIHZHcmFkID0gc2lnbiggZkRldCApICogKCBkSGR4eS54ICogUjEgKyBkSGR4eS55ICogUjIgKTsKCQlyZXR1cm4gbm9ybWFsaXplKCBhYnMoIGZEZXQgKSAqIHN1cmZfbm9ybSAtIHZHcmFkICk7Cgl9CiNlbmRpZmAsTE09YCNpZiBOVU1fQ0xJUFBJTkdfUExBTkVTID4gMAoJdmVjNCBwbGFuZTsKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IFVOSU9OX0NMSVBQSU5HX1BMQU5FUzsgaSArKyApIHsKCQlwbGFuZSA9IGNsaXBwaW5nUGxhbmVzWyBpIF07CgkJaWYgKCBkb3QoIHZDbGlwUG9zaXRpb24sIHBsYW5lLnh5eiApID4gcGxhbmUudyApIGRpc2NhcmQ7Cgl9CgkjcHJhZ21hIHVucm9sbF9sb29wX2VuZAoJI2lmIFVOSU9OX0NMSVBQSU5HX1BMQU5FUyA8IE5VTV9DTElQUElOR19QTEFORVMKCQlib29sIGNsaXBwZWQgPSB0cnVlOwoJCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCQlmb3IgKCBpbnQgaSA9IFVOSU9OX0NMSVBQSU5HX1BMQU5FUzsgaSA8IE5VTV9DTElQUElOR19QTEFORVM7IGkgKysgKSB7CgkJCXBsYW5lID0gY2xpcHBpbmdQbGFuZXNbIGkgXTsKCQkJY2xpcHBlZCA9ICggZG90KCB2Q2xpcFBvc2l0aW9uLCBwbGFuZS54eXogKSA+IHBsYW5lLncgKSAmJiBjbGlwcGVkOwoJCX0KCQkjcHJhZ21hIHVucm9sbF9sb29wX2VuZAoJCWlmICggY2xpcHBlZCApIGRpc2NhcmQ7CgkjZW5kaWYKI2VuZGlmYCxQTT1gI2lmIE5VTV9DTElQUElOR19QTEFORVMgPiAwCgl2YXJ5aW5nIHZlYzMgdkNsaXBQb3NpdGlvbjsKCXVuaWZvcm0gdmVjNCBjbGlwcGluZ1BsYW5lc1sgTlVNX0NMSVBQSU5HX1BMQU5FUyBdOwojZW5kaWZgLERNPWAjaWYgTlVNX0NMSVBQSU5HX1BMQU5FUyA+IDAKCXZhcnlpbmcgdmVjMyB2Q2xpcFBvc2l0aW9uOwojZW5kaWZgLElNPWAjaWYgTlVNX0NMSVBQSU5HX1BMQU5FUyA+IDAKCXZDbGlwUG9zaXRpb24gPSAtIG12UG9zaXRpb24ueHl6OwojZW5kaWZgLE5NPWAjaWYgZGVmaW5lZCggVVNFX0NPTE9SX0FMUEhBICkKCWRpZmZ1c2VDb2xvciAqPSB2Q29sb3I7CiNlbGlmIGRlZmluZWQoIFVTRV9DT0xPUiApCglkaWZmdXNlQ29sb3IucmdiICo9IHZDb2xvcjsKI2VuZGlmYCxGTT1gI2lmIGRlZmluZWQoIFVTRV9DT0xPUl9BTFBIQSApCgl2YXJ5aW5nIHZlYzQgdkNvbG9yOwojZWxpZiBkZWZpbmVkKCBVU0VfQ09MT1IgKQoJdmFyeWluZyB2ZWMzIHZDb2xvcjsKI2VuZGlmYCx6TT1gI2lmIGRlZmluZWQoIFVTRV9DT0xPUl9BTFBIQSApCgl2YXJ5aW5nIHZlYzQgdkNvbG9yOwojZWxpZiBkZWZpbmVkKCBVU0VfQ09MT1IgKSB8fCBkZWZpbmVkKCBVU0VfSU5TVEFOQ0lOR19DT0xPUiApCgl2YXJ5aW5nIHZlYzMgdkNvbG9yOwojZW5kaWZgLFVNPWAjaWYgZGVmaW5lZCggVVNFX0NPTE9SX0FMUEhBICkKCXZDb2xvciA9IHZlYzQoIDEuMCApOwojZWxpZiBkZWZpbmVkKCBVU0VfQ09MT1IgKSB8fCBkZWZpbmVkKCBVU0VfSU5TVEFOQ0lOR19DT0xPUiApCgl2Q29sb3IgPSB2ZWMzKCAxLjAgKTsKI2VuZGlmCiNpZmRlZiBVU0VfQ09MT1IKCXZDb2xvciAqPSBjb2xvcjsKI2VuZGlmCiNpZmRlZiBVU0VfSU5TVEFOQ0lOR19DT0xPUgoJdkNvbG9yLnh5eiAqPSBpbnN0YW5jZUNvbG9yLnh5ejsKI2VuZGlmYCxCTT1gI2RlZmluZSBQSSAzLjE0MTU5MjY1MzU4OTc5MwojZGVmaW5lIFBJMiA2LjI4MzE4NTMwNzE3OTU4NgojZGVmaW5lIFBJX0hBTEYgMS41NzA3OTYzMjY3OTQ4OTY2CiNkZWZpbmUgUkVDSVBST0NBTF9QSSAwLjMxODMwOTg4NjE4Mzc5MDcKI2RlZmluZSBSRUNJUFJPQ0FMX1BJMiAwLjE1OTE1NDk0MzA5MTg5NTM1CiNkZWZpbmUgRVBTSUxPTiAxZS02CiNpZm5kZWYgc2F0dXJhdGUKI2RlZmluZSBzYXR1cmF0ZSggYSApIGNsYW1wKCBhLCAwLjAsIDEuMCApCiNlbmRpZgojZGVmaW5lIHdoaXRlQ29tcGxlbWVudCggYSApICggMS4wIC0gc2F0dXJhdGUoIGEgKSApCmZsb2F0IHBvdzIoIGNvbnN0IGluIGZsb2F0IHggKSB7IHJldHVybiB4Kng7IH0KZmxvYXQgcG93MyggY29uc3QgaW4gZmxvYXQgeCApIHsgcmV0dXJuIHgqeCp4OyB9CmZsb2F0IHBvdzQoIGNvbnN0IGluIGZsb2F0IHggKSB7IGZsb2F0IHgyID0geCp4OyByZXR1cm4geDIqeDI7IH0KZmxvYXQgbWF4MyggY29uc3QgaW4gdmVjMyB2ICkgeyByZXR1cm4gbWF4KCBtYXgoIHYueCwgdi55ICksIHYueiApOyB9CmZsb2F0IGF2ZXJhZ2UoIGNvbnN0IGluIHZlYzMgY29sb3IgKSB7IHJldHVybiBkb3QoIGNvbG9yLCB2ZWMzKCAwLjMzMzMgKSApOyB9CmhpZ2hwIGZsb2F0IHJhbmQoIGNvbnN0IGluIHZlYzIgdXYgKSB7Cgljb25zdCBoaWdocCBmbG9hdCBhID0gMTIuOTg5OCwgYiA9IDc4LjIzMywgYyA9IDQzNzU4LjU0NTM7CgloaWdocCBmbG9hdCBkdCA9IGRvdCggdXYueHksIHZlYzIoIGEsYiApICksIHNuID0gbW9kKCBkdCwgUEkgKTsKCXJldHVybiBmcmFjdCggc2luKCBzbiApICogYyApOwp9CiNpZmRlZiBISUdIX1BSRUNJU0lPTgoJZmxvYXQgcHJlY2lzaW9uU2FmZUxlbmd0aCggdmVjMyB2ICkgeyByZXR1cm4gbGVuZ3RoKCB2ICk7IH0KI2Vsc2UKCWZsb2F0IHByZWNpc2lvblNhZmVMZW5ndGgoIHZlYzMgdiApIHsKCQlmbG9hdCBtYXhDb21wb25lbnQgPSBtYXgzKCBhYnMoIHYgKSApOwoJCXJldHVybiBsZW5ndGgoIHYgLyBtYXhDb21wb25lbnQgKSAqIG1heENvbXBvbmVudDsKCX0KI2VuZGlmCnN0cnVjdCBJbmNpZGVudExpZ2h0IHsKCXZlYzMgY29sb3I7Cgl2ZWMzIGRpcmVjdGlvbjsKCWJvb2wgdmlzaWJsZTsKfTsKc3RydWN0IFJlZmxlY3RlZExpZ2h0IHsKCXZlYzMgZGlyZWN0RGlmZnVzZTsKCXZlYzMgZGlyZWN0U3BlY3VsYXI7Cgl2ZWMzIGluZGlyZWN0RGlmZnVzZTsKCXZlYzMgaW5kaXJlY3RTcGVjdWxhcjsKfTsKc3RydWN0IEdlb21ldHJpY0NvbnRleHQgewoJdmVjMyBwb3NpdGlvbjsKCXZlYzMgbm9ybWFsOwoJdmVjMyB2aWV3RGlyOwojaWZkZWYgVVNFX0NMRUFSQ09BVAoJdmVjMyBjbGVhcmNvYXROb3JtYWw7CiNlbmRpZgp9Owp2ZWMzIHRyYW5zZm9ybURpcmVjdGlvbiggaW4gdmVjMyBkaXIsIGluIG1hdDQgbWF0cml4ICkgewoJcmV0dXJuIG5vcm1hbGl6ZSggKCBtYXRyaXggKiB2ZWM0KCBkaXIsIDAuMCApICkueHl6ICk7Cn0KdmVjMyBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCBpbiB2ZWMzIGRpciwgaW4gbWF0NCBtYXRyaXggKSB7CglyZXR1cm4gbm9ybWFsaXplKCAoIHZlYzQoIGRpciwgMC4wICkgKiBtYXRyaXggKS54eXogKTsKfQptYXQzIHRyYW5zcG9zZU1hdDMoIGNvbnN0IGluIG1hdDMgbSApIHsKCW1hdDMgdG1wOwoJdG1wWyAwIF0gPSB2ZWMzKCBtWyAwIF0ueCwgbVsgMSBdLngsIG1bIDIgXS54ICk7Cgl0bXBbIDEgXSA9IHZlYzMoIG1bIDAgXS55LCBtWyAxIF0ueSwgbVsgMiBdLnkgKTsKCXRtcFsgMiBdID0gdmVjMyggbVsgMCBdLnosIG1bIDEgXS56LCBtWyAyIF0ueiApOwoJcmV0dXJuIHRtcDsKfQpmbG9hdCBsaW5lYXJUb1JlbGF0aXZlTHVtaW5hbmNlKCBjb25zdCBpbiB2ZWMzIGNvbG9yICkgewoJdmVjMyB3ZWlnaHRzID0gdmVjMyggMC4yMTI2LCAwLjcxNTIsIDAuMDcyMiApOwoJcmV0dXJuIGRvdCggd2VpZ2h0cywgY29sb3IucmdiICk7Cn0KYm9vbCBpc1BlcnNwZWN0aXZlTWF0cml4KCBtYXQ0IG0gKSB7CglyZXR1cm4gbVsgMiBdWyAzIF0gPT0gLSAxLjA7Cn0KdmVjMiBlcXVpcmVjdFV2KCBpbiB2ZWMzIGRpciApIHsKCWZsb2F0IHUgPSBhdGFuKCBkaXIueiwgZGlyLnggKSAqIFJFQ0lQUk9DQUxfUEkyICsgMC41OwoJZmxvYXQgdiA9IGFzaW4oIGNsYW1wKCBkaXIueSwgLSAxLjAsIDEuMCApICkgKiBSRUNJUFJPQ0FMX1BJICsgMC41OwoJcmV0dXJuIHZlYzIoIHUsIHYgKTsKfWAsT009YCNpZmRlZiBFTlZNQVBfVFlQRV9DVUJFX1VWCgkjZGVmaW5lIGN1YmVVVl9tYXhNaXBMZXZlbCA4LjAKCSNkZWZpbmUgY3ViZVVWX21pbk1pcExldmVsIDQuMAoJI2RlZmluZSBjdWJlVVZfbWF4VGlsZVNpemUgMjU2LjAKCSNkZWZpbmUgY3ViZVVWX21pblRpbGVTaXplIDE2LjAKCWZsb2F0IGdldEZhY2UoIHZlYzMgZGlyZWN0aW9uICkgewoJCXZlYzMgYWJzRGlyZWN0aW9uID0gYWJzKCBkaXJlY3Rpb24gKTsKCQlmbG9hdCBmYWNlID0gLSAxLjA7CgkJaWYgKCBhYnNEaXJlY3Rpb24ueCA+IGFic0RpcmVjdGlvbi56ICkgewoJCQlpZiAoIGFic0RpcmVjdGlvbi54ID4gYWJzRGlyZWN0aW9uLnkgKQoJCQkJZmFjZSA9IGRpcmVjdGlvbi54ID4gMC4wID8gMC4wIDogMy4wOwoJCQllbHNlCgkJCQlmYWNlID0gZGlyZWN0aW9uLnkgPiAwLjAgPyAxLjAgOiA0LjA7CgkJfSBlbHNlIHsKCQkJaWYgKCBhYnNEaXJlY3Rpb24ueiA+IGFic0RpcmVjdGlvbi55ICkKCQkJCWZhY2UgPSBkaXJlY3Rpb24ueiA+IDAuMCA/IDIuMCA6IDUuMDsKCQkJZWxzZQoJCQkJZmFjZSA9IGRpcmVjdGlvbi55ID4gMC4wID8gMS4wIDogNC4wOwoJCX0KCQlyZXR1cm4gZmFjZTsKCX0KCXZlYzIgZ2V0VVYoIHZlYzMgZGlyZWN0aW9uLCBmbG9hdCBmYWNlICkgewoJCXZlYzIgdXY7CgkJaWYgKCBmYWNlID09IDAuMCApIHsKCQkJdXYgPSB2ZWMyKCBkaXJlY3Rpb24ueiwgZGlyZWN0aW9uLnkgKSAvIGFicyggZGlyZWN0aW9uLnggKTsKCQl9IGVsc2UgaWYgKCBmYWNlID09IDEuMCApIHsKCQkJdXYgPSB2ZWMyKCAtIGRpcmVjdGlvbi54LCAtIGRpcmVjdGlvbi56ICkgLyBhYnMoIGRpcmVjdGlvbi55ICk7CgkJfSBlbHNlIGlmICggZmFjZSA9PSAyLjAgKSB7CgkJCXV2ID0gdmVjMiggLSBkaXJlY3Rpb24ueCwgZGlyZWN0aW9uLnkgKSAvIGFicyggZGlyZWN0aW9uLnogKTsKCQl9IGVsc2UgaWYgKCBmYWNlID09IDMuMCApIHsKCQkJdXYgPSB2ZWMyKCAtIGRpcmVjdGlvbi56LCBkaXJlY3Rpb24ueSApIC8gYWJzKCBkaXJlY3Rpb24ueCApOwoJCX0gZWxzZSBpZiAoIGZhY2UgPT0gNC4wICkgewoJCQl1diA9IHZlYzIoIC0gZGlyZWN0aW9uLngsIGRpcmVjdGlvbi56ICkgLyBhYnMoIGRpcmVjdGlvbi55ICk7CgkJfSBlbHNlIHsKCQkJdXYgPSB2ZWMyKCBkaXJlY3Rpb24ueCwgZGlyZWN0aW9uLnkgKSAvIGFicyggZGlyZWN0aW9uLnogKTsKCQl9CgkJcmV0dXJuIDAuNSAqICggdXYgKyAxLjAgKTsKCX0KCXZlYzMgYmlsaW5lYXJDdWJlVVYoIHNhbXBsZXIyRCBlbnZNYXAsIHZlYzMgZGlyZWN0aW9uLCBmbG9hdCBtaXBJbnQgKSB7CgkJZmxvYXQgZmFjZSA9IGdldEZhY2UoIGRpcmVjdGlvbiApOwoJCWZsb2F0IGZpbHRlckludCA9IG1heCggY3ViZVVWX21pbk1pcExldmVsIC0gbWlwSW50LCAwLjAgKTsKCQltaXBJbnQgPSBtYXgoIG1pcEludCwgY3ViZVVWX21pbk1pcExldmVsICk7CgkJZmxvYXQgZmFjZVNpemUgPSBleHAyKCBtaXBJbnQgKTsKCQlmbG9hdCB0ZXhlbFNpemUgPSAxLjAgLyAoIDMuMCAqIGN1YmVVVl9tYXhUaWxlU2l6ZSApOwoJCXZlYzIgdXYgPSBnZXRVViggZGlyZWN0aW9uLCBmYWNlICkgKiAoIGZhY2VTaXplIC0gMS4wICkgKyAwLjU7CgkJaWYgKCBmYWNlID4gMi4wICkgewoJCQl1di55ICs9IGZhY2VTaXplOwoJCQlmYWNlIC09IDMuMDsKCQl9CgkJdXYueCArPSBmYWNlICogZmFjZVNpemU7CgkJaWYgKCBtaXBJbnQgPCBjdWJlVVZfbWF4TWlwTGV2ZWwgKSB7CgkJCXV2LnkgKz0gMi4wICogY3ViZVVWX21heFRpbGVTaXplOwoJCX0KCQl1di55ICs9IGZpbHRlckludCAqIDIuMCAqIGN1YmVVVl9taW5UaWxlU2l6ZTsKCQl1di54ICs9IDMuMCAqIG1heCggMC4wLCBjdWJlVVZfbWF4VGlsZVNpemUgLSAyLjAgKiBmYWNlU2l6ZSApOwoJCXV2ICo9IHRleGVsU2l6ZTsKCQlyZXR1cm4gdGV4dHVyZTJEKCBlbnZNYXAsIHV2ICkucmdiOwoJfQoJI2RlZmluZSByMCAxLjAKCSNkZWZpbmUgdjAgMC4zMzkKCSNkZWZpbmUgbTAgLSAyLjAKCSNkZWZpbmUgcjEgMC44CgkjZGVmaW5lIHYxIDAuMjc2CgkjZGVmaW5lIG0xIC0gMS4wCgkjZGVmaW5lIHI0IDAuNAoJI2RlZmluZSB2NCAwLjA0NgoJI2RlZmluZSBtNCAyLjAKCSNkZWZpbmUgcjUgMC4zMDUKCSNkZWZpbmUgdjUgMC4wMTYKCSNkZWZpbmUgbTUgMy4wCgkjZGVmaW5lIHI2IDAuMjEKCSNkZWZpbmUgdjYgMC4wMDM4CgkjZGVmaW5lIG02IDQuMAoJZmxvYXQgcm91Z2huZXNzVG9NaXAoIGZsb2F0IHJvdWdobmVzcyApIHsKCQlmbG9hdCBtaXAgPSAwLjA7CgkJaWYgKCByb3VnaG5lc3MgPj0gcjEgKSB7CgkJCW1pcCA9ICggcjAgLSByb3VnaG5lc3MgKSAqICggbTEgLSBtMCApIC8gKCByMCAtIHIxICkgKyBtMDsKCQl9IGVsc2UgaWYgKCByb3VnaG5lc3MgPj0gcjQgKSB7CgkJCW1pcCA9ICggcjEgLSByb3VnaG5lc3MgKSAqICggbTQgLSBtMSApIC8gKCByMSAtIHI0ICkgKyBtMTsKCQl9IGVsc2UgaWYgKCByb3VnaG5lc3MgPj0gcjUgKSB7CgkJCW1pcCA9ICggcjQgLSByb3VnaG5lc3MgKSAqICggbTUgLSBtNCApIC8gKCByNCAtIHI1ICkgKyBtNDsKCQl9IGVsc2UgaWYgKCByb3VnaG5lc3MgPj0gcjYgKSB7CgkJCW1pcCA9ICggcjUgLSByb3VnaG5lc3MgKSAqICggbTYgLSBtNSApIC8gKCByNSAtIHI2ICkgKyBtNTsKCQl9IGVsc2UgewoJCQltaXAgPSAtIDIuMCAqIGxvZzIoIDEuMTYgKiByb3VnaG5lc3MgKTsJCX0KCQlyZXR1cm4gbWlwOwoJfQoJdmVjNCB0ZXh0dXJlQ3ViZVVWKCBzYW1wbGVyMkQgZW52TWFwLCB2ZWMzIHNhbXBsZURpciwgZmxvYXQgcm91Z2huZXNzICkgewoJCWZsb2F0IG1pcCA9IGNsYW1wKCByb3VnaG5lc3NUb01pcCggcm91Z2huZXNzICksIG0wLCBjdWJlVVZfbWF4TWlwTGV2ZWwgKTsKCQlmbG9hdCBtaXBGID0gZnJhY3QoIG1pcCApOwoJCWZsb2F0IG1pcEludCA9IGZsb29yKCBtaXAgKTsKCQl2ZWMzIGNvbG9yMCA9IGJpbGluZWFyQ3ViZVVWKCBlbnZNYXAsIHNhbXBsZURpciwgbWlwSW50ICk7CgkJaWYgKCBtaXBGID09IDAuMCApIHsKCQkJcmV0dXJuIHZlYzQoIGNvbG9yMCwgMS4wICk7CgkJfSBlbHNlIHsKCQkJdmVjMyBjb2xvcjEgPSBiaWxpbmVhckN1YmVVViggZW52TWFwLCBzYW1wbGVEaXIsIG1pcEludCArIDEuMCApOwoJCQlyZXR1cm4gdmVjNCggbWl4KCBjb2xvcjAsIGNvbG9yMSwgbWlwRiApLCAxLjAgKTsKCQl9Cgl9CiNlbmRpZmAsa009YHZlYzMgdHJhbnNmb3JtZWROb3JtYWwgPSBvYmplY3ROb3JtYWw7CiNpZmRlZiBVU0VfSU5TVEFOQ0lORwoJbWF0MyBtID0gbWF0MyggaW5zdGFuY2VNYXRyaXggKTsKCXRyYW5zZm9ybWVkTm9ybWFsIC89IHZlYzMoIGRvdCggbVsgMCBdLCBtWyAwIF0gKSwgZG90KCBtWyAxIF0sIG1bIDEgXSApLCBkb3QoIG1bIDIgXSwgbVsgMiBdICkgKTsKCXRyYW5zZm9ybWVkTm9ybWFsID0gbSAqIHRyYW5zZm9ybWVkTm9ybWFsOwojZW5kaWYKdHJhbnNmb3JtZWROb3JtYWwgPSBub3JtYWxNYXRyaXggKiB0cmFuc2Zvcm1lZE5vcm1hbDsKI2lmZGVmIEZMSVBfU0lERUQKCXRyYW5zZm9ybWVkTm9ybWFsID0gLSB0cmFuc2Zvcm1lZE5vcm1hbDsKI2VuZGlmCiNpZmRlZiBVU0VfVEFOR0VOVAoJdmVjMyB0cmFuc2Zvcm1lZFRhbmdlbnQgPSAoIG1vZGVsVmlld01hdHJpeCAqIHZlYzQoIG9iamVjdFRhbmdlbnQsIDAuMCApICkueHl6OwoJI2lmZGVmIEZMSVBfU0lERUQKCQl0cmFuc2Zvcm1lZFRhbmdlbnQgPSAtIHRyYW5zZm9ybWVkVGFuZ2VudDsKCSNlbmRpZgojZW5kaWZgLEhNPWAjaWZkZWYgVVNFX0RJU1BMQUNFTUVOVE1BUAoJdW5pZm9ybSBzYW1wbGVyMkQgZGlzcGxhY2VtZW50TWFwOwoJdW5pZm9ybSBmbG9hdCBkaXNwbGFjZW1lbnRTY2FsZTsKCXVuaWZvcm0gZmxvYXQgZGlzcGxhY2VtZW50QmlhczsKI2VuZGlmYCxWTT1gI2lmZGVmIFVTRV9ESVNQTEFDRU1FTlRNQVAKCXRyYW5zZm9ybWVkICs9IG5vcm1hbGl6ZSggb2JqZWN0Tm9ybWFsICkgKiAoIHRleHR1cmUyRCggZGlzcGxhY2VtZW50TWFwLCB2VXYgKS54ICogZGlzcGxhY2VtZW50U2NhbGUgKyBkaXNwbGFjZW1lbnRCaWFzICk7CiNlbmRpZmAsR009YCNpZmRlZiBVU0VfRU1JU1NJVkVNQVAKCXZlYzQgZW1pc3NpdmVDb2xvciA9IHRleHR1cmUyRCggZW1pc3NpdmVNYXAsIHZVdiApOwoJdG90YWxFbWlzc2l2ZVJhZGlhbmNlICo9IGVtaXNzaXZlQ29sb3IucmdiOwojZW5kaWZgLFdNPWAjaWZkZWYgVVNFX0VNSVNTSVZFTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBlbWlzc2l2ZU1hcDsKI2VuZGlmYCxxTT0iZ2xfRnJhZ0NvbG9yID0gbGluZWFyVG9PdXRwdXRUZXhlbCggZ2xfRnJhZ0NvbG9yICk7IixYTT1gdmVjNCBMaW5lYXJUb0xpbmVhciggaW4gdmVjNCB2YWx1ZSApIHsKCXJldHVybiB2YWx1ZTsKfQp2ZWM0IExpbmVhclRvc1JHQiggaW4gdmVjNCB2YWx1ZSApIHsKCXJldHVybiB2ZWM0KCBtaXgoIHBvdyggdmFsdWUucmdiLCB2ZWMzKCAwLjQxNjY2ICkgKSAqIDEuMDU1IC0gdmVjMyggMC4wNTUgKSwgdmFsdWUucmdiICogMTIuOTIsIHZlYzMoIGxlc3NUaGFuRXF1YWwoIHZhbHVlLnJnYiwgdmVjMyggMC4wMDMxMzA4ICkgKSApICksIHZhbHVlLmEgKTsKfWAsWU09YCNpZmRlZiBVU0VfRU5WTUFQCgkjaWZkZWYgRU5WX1dPUkxEUE9TCgkJdmVjMyBjYW1lcmFUb0ZyYWc7CgkJaWYgKCBpc09ydGhvZ3JhcGhpYyApIHsKCQkJY2FtZXJhVG9GcmFnID0gbm9ybWFsaXplKCB2ZWMzKCAtIHZpZXdNYXRyaXhbIDAgXVsgMiBdLCAtIHZpZXdNYXRyaXhbIDEgXVsgMiBdLCAtIHZpZXdNYXRyaXhbIDIgXVsgMiBdICkgKTsKCQl9IGVsc2UgewoJCQljYW1lcmFUb0ZyYWcgPSBub3JtYWxpemUoIHZXb3JsZFBvc2l0aW9uIC0gY2FtZXJhUG9zaXRpb24gKTsKCQl9CgkJdmVjMyB3b3JsZE5vcm1hbCA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIG5vcm1hbCwgdmlld01hdHJpeCApOwoJCSNpZmRlZiBFTlZNQVBfTU9ERV9SRUZMRUNUSU9OCgkJCXZlYzMgcmVmbGVjdFZlYyA9IHJlZmxlY3QoIGNhbWVyYVRvRnJhZywgd29ybGROb3JtYWwgKTsKCQkjZWxzZQoJCQl2ZWMzIHJlZmxlY3RWZWMgPSByZWZyYWN0KCBjYW1lcmFUb0ZyYWcsIHdvcmxkTm9ybWFsLCByZWZyYWN0aW9uUmF0aW8gKTsKCQkjZW5kaWYKCSNlbHNlCgkJdmVjMyByZWZsZWN0VmVjID0gdlJlZmxlY3Q7CgkjZW5kaWYKCSNpZmRlZiBFTlZNQVBfVFlQRV9DVUJFCgkJdmVjNCBlbnZDb2xvciA9IHRleHR1cmVDdWJlKCBlbnZNYXAsIHZlYzMoIGZsaXBFbnZNYXAgKiByZWZsZWN0VmVjLngsIHJlZmxlY3RWZWMueXogKSApOwoJI2VsaWYgZGVmaW5lZCggRU5WTUFQX1RZUEVfQ1VCRV9VViApCgkJdmVjNCBlbnZDb2xvciA9IHRleHR1cmVDdWJlVVYoIGVudk1hcCwgcmVmbGVjdFZlYywgMC4wICk7CgkjZWxzZQoJCXZlYzQgZW52Q29sb3IgPSB2ZWM0KCAwLjAgKTsKCSNlbmRpZgoJI2lmZGVmIEVOVk1BUF9CTEVORElOR19NVUxUSVBMWQoJCW91dGdvaW5nTGlnaHQgPSBtaXgoIG91dGdvaW5nTGlnaHQsIG91dGdvaW5nTGlnaHQgKiBlbnZDb2xvci54eXosIHNwZWN1bGFyU3RyZW5ndGggKiByZWZsZWN0aXZpdHkgKTsKCSNlbGlmIGRlZmluZWQoIEVOVk1BUF9CTEVORElOR19NSVggKQoJCW91dGdvaW5nTGlnaHQgPSBtaXgoIG91dGdvaW5nTGlnaHQsIGVudkNvbG9yLnh5eiwgc3BlY3VsYXJTdHJlbmd0aCAqIHJlZmxlY3Rpdml0eSApOwoJI2VsaWYgZGVmaW5lZCggRU5WTUFQX0JMRU5ESU5HX0FERCApCgkJb3V0Z29pbmdMaWdodCArPSBlbnZDb2xvci54eXogKiBzcGVjdWxhclN0cmVuZ3RoICogcmVmbGVjdGl2aXR5OwoJI2VuZGlmCiNlbmRpZmAsWk09YCNpZmRlZiBVU0VfRU5WTUFQCgl1bmlmb3JtIGZsb2F0IGVudk1hcEludGVuc2l0eTsKCXVuaWZvcm0gZmxvYXQgZmxpcEVudk1hcDsKCSNpZmRlZiBFTlZNQVBfVFlQRV9DVUJFCgkJdW5pZm9ybSBzYW1wbGVyQ3ViZSBlbnZNYXA7CgkjZWxzZQoJCXVuaWZvcm0gc2FtcGxlcjJEIGVudk1hcDsKCSNlbmRpZgoJCiNlbmRpZmAsSk09YCNpZmRlZiBVU0VfRU5WTUFQCgl1bmlmb3JtIGZsb2F0IHJlZmxlY3Rpdml0eTsKCSNpZiBkZWZpbmVkKCBVU0VfQlVNUE1BUCApIHx8IGRlZmluZWQoIFVTRV9OT1JNQUxNQVAgKSB8fCBkZWZpbmVkKCBQSE9ORyApCgkJI2RlZmluZSBFTlZfV09STERQT1MKCSNlbmRpZgoJI2lmZGVmIEVOVl9XT1JMRFBPUwoJCXZhcnlpbmcgdmVjMyB2V29ybGRQb3NpdGlvbjsKCQl1bmlmb3JtIGZsb2F0IHJlZnJhY3Rpb25SYXRpbzsKCSNlbHNlCgkJdmFyeWluZyB2ZWMzIHZSZWZsZWN0OwoJI2VuZGlmCiNlbmRpZmAsJE09YCNpZmRlZiBVU0VfRU5WTUFQCgkjaWYgZGVmaW5lZCggVVNFX0JVTVBNQVAgKSB8fCBkZWZpbmVkKCBVU0VfTk9STUFMTUFQICkgfHxkZWZpbmVkKCBQSE9ORyApCgkJI2RlZmluZSBFTlZfV09STERQT1MKCSNlbmRpZgoJI2lmZGVmIEVOVl9XT1JMRFBPUwoJCQoJCXZhcnlpbmcgdmVjMyB2V29ybGRQb3NpdGlvbjsKCSNlbHNlCgkJdmFyeWluZyB2ZWMzIHZSZWZsZWN0OwoJCXVuaWZvcm0gZmxvYXQgcmVmcmFjdGlvblJhdGlvOwoJI2VuZGlmCiNlbmRpZmAsS009YCNpZmRlZiBVU0VfRU5WTUFQCgkjaWZkZWYgRU5WX1dPUkxEUE9TCgkJdldvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uLnh5ejsKCSNlbHNlCgkJdmVjMyBjYW1lcmFUb1ZlcnRleDsKCQlpZiAoIGlzT3J0aG9ncmFwaGljICkgewoJCQljYW1lcmFUb1ZlcnRleCA9IG5vcm1hbGl6ZSggdmVjMyggLSB2aWV3TWF0cml4WyAwIF1bIDIgXSwgLSB2aWV3TWF0cml4WyAxIF1bIDIgXSwgLSB2aWV3TWF0cml4WyAyIF1bIDIgXSApICk7CgkJfSBlbHNlIHsKCQkJY2FtZXJhVG9WZXJ0ZXggPSBub3JtYWxpemUoIHdvcmxkUG9zaXRpb24ueHl6IC0gY2FtZXJhUG9zaXRpb24gKTsKCQl9CgkJdmVjMyB3b3JsZE5vcm1hbCA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIHRyYW5zZm9ybWVkTm9ybWFsLCB2aWV3TWF0cml4ICk7CgkJI2lmZGVmIEVOVk1BUF9NT0RFX1JFRkxFQ1RJT04KCQkJdlJlZmxlY3QgPSByZWZsZWN0KCBjYW1lcmFUb1ZlcnRleCwgd29ybGROb3JtYWwgKTsKCQkjZWxzZQoJCQl2UmVmbGVjdCA9IHJlZnJhY3QoIGNhbWVyYVRvVmVydGV4LCB3b3JsZE5vcm1hbCwgcmVmcmFjdGlvblJhdGlvICk7CgkJI2VuZGlmCgkjZW5kaWYKI2VuZGlmYCxRTT1gI2lmZGVmIFVTRV9GT0cKCXZGb2dEZXB0aCA9IC0gbXZQb3NpdGlvbi56OwojZW5kaWZgLGpNPWAjaWZkZWYgVVNFX0ZPRwoJdmFyeWluZyBmbG9hdCB2Rm9nRGVwdGg7CiNlbmRpZmAsdGI9YCNpZmRlZiBVU0VfRk9HCgkjaWZkZWYgRk9HX0VYUDIKCQlmbG9hdCBmb2dGYWN0b3IgPSAxLjAgLSBleHAoIC0gZm9nRGVuc2l0eSAqIGZvZ0RlbnNpdHkgKiB2Rm9nRGVwdGggKiB2Rm9nRGVwdGggKTsKCSNlbHNlCgkJZmxvYXQgZm9nRmFjdG9yID0gc21vb3Roc3RlcCggZm9nTmVhciwgZm9nRmFyLCB2Rm9nRGVwdGggKTsKCSNlbmRpZgoJZ2xfRnJhZ0NvbG9yLnJnYiA9IG1peCggZ2xfRnJhZ0NvbG9yLnJnYiwgZm9nQ29sb3IsIGZvZ0ZhY3RvciApOwojZW5kaWZgLGViPWAjaWZkZWYgVVNFX0ZPRwoJdW5pZm9ybSB2ZWMzIGZvZ0NvbG9yOwoJdmFyeWluZyBmbG9hdCB2Rm9nRGVwdGg7CgkjaWZkZWYgRk9HX0VYUDIKCQl1bmlmb3JtIGZsb2F0IGZvZ0RlbnNpdHk7CgkjZWxzZQoJCXVuaWZvcm0gZmxvYXQgZm9nTmVhcjsKCQl1bmlmb3JtIGZsb2F0IGZvZ0ZhcjsKCSNlbmRpZgojZW5kaWZgLG5iPWAjaWZkZWYgVVNFX0dSQURJRU5UTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBncmFkaWVudE1hcDsKI2VuZGlmCnZlYzMgZ2V0R3JhZGllbnRJcnJhZGlhbmNlKCB2ZWMzIG5vcm1hbCwgdmVjMyBsaWdodERpcmVjdGlvbiApIHsKCWZsb2F0IGRvdE5MID0gZG90KCBub3JtYWwsIGxpZ2h0RGlyZWN0aW9uICk7Cgl2ZWMyIGNvb3JkID0gdmVjMiggZG90TkwgKiAwLjUgKyAwLjUsIDAuMCApOwoJI2lmZGVmIFVTRV9HUkFESUVOVE1BUAoJCXJldHVybiB2ZWMzKCB0ZXh0dXJlMkQoIGdyYWRpZW50TWFwLCBjb29yZCApLnIgKTsKCSNlbHNlCgkJcmV0dXJuICggY29vcmQueCA8IDAuNyApID8gdmVjMyggMC43ICkgOiB2ZWMzKCAxLjAgKTsKCSNlbmRpZgp9YCxpYj1gI2lmZGVmIFVTRV9MSUdIVE1BUAoJdmVjNCBsaWdodE1hcFRleGVsID0gdGV4dHVyZTJEKCBsaWdodE1hcCwgdlV2MiApOwoJdmVjMyBsaWdodE1hcElycmFkaWFuY2UgPSBsaWdodE1hcFRleGVsLnJnYiAqIGxpZ2h0TWFwSW50ZW5zaXR5OwoJI2lmbmRlZiBQSFlTSUNBTExZX0NPUlJFQ1RfTElHSFRTCgkJbGlnaHRNYXBJcnJhZGlhbmNlICo9IFBJOwoJI2VuZGlmCglyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKz0gbGlnaHRNYXBJcnJhZGlhbmNlOwojZW5kaWZgLHJiPWAjaWZkZWYgVVNFX0xJR0hUTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBsaWdodE1hcDsKCXVuaWZvcm0gZmxvYXQgbGlnaHRNYXBJbnRlbnNpdHk7CiNlbmRpZmAsc2I9YHZlYzMgZGlmZnVzZSA9IHZlYzMoIDEuMCApOwpHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5OwpnZW9tZXRyeS5wb3NpdGlvbiA9IG12UG9zaXRpb24ueHl6OwpnZW9tZXRyeS5ub3JtYWwgPSBub3JtYWxpemUoIHRyYW5zZm9ybWVkTm9ybWFsICk7Cmdlb21ldHJ5LnZpZXdEaXIgPSAoIGlzT3J0aG9ncmFwaGljICkgPyB2ZWMzKCAwLCAwLCAxICkgOiBub3JtYWxpemUoIC1tdlBvc2l0aW9uLnh5eiApOwpHZW9tZXRyaWNDb250ZXh0IGJhY2tHZW9tZXRyeTsKYmFja0dlb21ldHJ5LnBvc2l0aW9uID0gZ2VvbWV0cnkucG9zaXRpb247CmJhY2tHZW9tZXRyeS5ub3JtYWwgPSAtZ2VvbWV0cnkubm9ybWFsOwpiYWNrR2VvbWV0cnkudmlld0RpciA9IGdlb21ldHJ5LnZpZXdEaXI7CnZMaWdodEZyb250ID0gdmVjMyggMC4wICk7CnZJbmRpcmVjdEZyb250ID0gdmVjMyggMC4wICk7CiNpZmRlZiBET1VCTEVfU0lERUQKCXZMaWdodEJhY2sgPSB2ZWMzKCAwLjAgKTsKCXZJbmRpcmVjdEJhY2sgPSB2ZWMzKCAwLjAgKTsKI2VuZGlmCkluY2lkZW50TGlnaHQgZGlyZWN0TGlnaHQ7CmZsb2F0IGRvdE5MOwp2ZWMzIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTsKdkluZGlyZWN0RnJvbnQgKz0gZ2V0QW1iaWVudExpZ2h0SXJyYWRpYW5jZSggYW1iaWVudExpZ2h0Q29sb3IgKTsKdkluZGlyZWN0RnJvbnQgKz0gZ2V0TGlnaHRQcm9iZUlycmFkaWFuY2UoIGxpZ2h0UHJvYmUsIGdlb21ldHJ5Lm5vcm1hbCApOwojaWZkZWYgRE9VQkxFX1NJREVECgl2SW5kaXJlY3RCYWNrICs9IGdldEFtYmllbnRMaWdodElycmFkaWFuY2UoIGFtYmllbnRMaWdodENvbG9yICk7Cgl2SW5kaXJlY3RCYWNrICs9IGdldExpZ2h0UHJvYmVJcnJhZGlhbmNlKCBsaWdodFByb2JlLCBiYWNrR2VvbWV0cnkubm9ybWFsICk7CiNlbmRpZgojaWYgTlVNX1BPSU5UX0xJR0hUUyA+IDAKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9QT0lOVF9MSUdIVFM7IGkgKysgKSB7CgkJZ2V0UG9pbnRMaWdodEluZm8oIHBvaW50TGlnaHRzWyBpIF0sIGdlb21ldHJ5LCBkaXJlY3RMaWdodCApOwoJCWRvdE5MID0gZG90KCBnZW9tZXRyeS5ub3JtYWwsIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiApOwoJCWRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZSA9IGRpcmVjdExpZ2h0LmNvbG9yOwoJCXZMaWdodEZyb250ICs9IHNhdHVyYXRlKCBkb3ROTCApICogZGlyZWN0TGlnaHRDb2xvcl9EaWZmdXNlOwoJCSNpZmRlZiBET1VCTEVfU0lERUQKCQkJdkxpZ2h0QmFjayArPSBzYXR1cmF0ZSggLSBkb3ROTCApICogZGlyZWN0TGlnaHRDb2xvcl9EaWZmdXNlOwoJCSNlbmRpZgoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKI2VuZGlmCiNpZiBOVU1fU1BPVF9MSUdIVFMgPiAwCgkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0Cglmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fU1BPVF9MSUdIVFM7IGkgKysgKSB7CgkJZ2V0U3BvdExpZ2h0SW5mbyggc3BvdExpZ2h0c1sgaSBdLCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTsKCQlkb3ROTCA9IGRvdCggZ2VvbWV0cnkubm9ybWFsLCBkaXJlY3RMaWdodC5kaXJlY3Rpb24gKTsKCQlkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2UgPSBkaXJlY3RMaWdodC5jb2xvcjsKCQl2TGlnaHRGcm9udCArPSBzYXR1cmF0ZSggZG90TkwgKSAqIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTsKCQkjaWZkZWYgRE9VQkxFX1NJREVECgkJCXZMaWdodEJhY2sgKz0gc2F0dXJhdGUoIC0gZG90TkwgKSAqIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTsKCQkjZW5kaWYKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCiNlbmRpZgojaWYgTlVNX0RJUl9MSUdIVFMgPiAwCgkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0Cglmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fRElSX0xJR0hUUzsgaSArKyApIHsKCQlnZXREaXJlY3Rpb25hbExpZ2h0SW5mbyggZGlyZWN0aW9uYWxMaWdodHNbIGkgXSwgZ2VvbWV0cnksIGRpcmVjdExpZ2h0ICk7CgkJZG90TkwgPSBkb3QoIGdlb21ldHJ5Lm5vcm1hbCwgZGlyZWN0TGlnaHQuZGlyZWN0aW9uICk7CgkJZGlyZWN0TGlnaHRDb2xvcl9EaWZmdXNlID0gZGlyZWN0TGlnaHQuY29sb3I7CgkJdkxpZ2h0RnJvbnQgKz0gc2F0dXJhdGUoIGRvdE5MICkgKiBkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2U7CgkJI2lmZGVmIERPVUJMRV9TSURFRAoJCQl2TGlnaHRCYWNrICs9IHNhdHVyYXRlKCAtIGRvdE5MICkgKiBkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2U7CgkJI2VuZGlmCgl9CgkjcHJhZ21hIHVucm9sbF9sb29wX2VuZAojZW5kaWYKI2lmIE5VTV9IRU1JX0xJR0hUUyA+IDAKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9IRU1JX0xJR0hUUzsgaSArKyApIHsKCQl2SW5kaXJlY3RGcm9udCArPSBnZXRIZW1pc3BoZXJlTGlnaHRJcnJhZGlhbmNlKCBoZW1pc3BoZXJlTGlnaHRzWyBpIF0sIGdlb21ldHJ5Lm5vcm1hbCApOwoJCSNpZmRlZiBET1VCTEVfU0lERUQKCQkJdkluZGlyZWN0QmFjayArPSBnZXRIZW1pc3BoZXJlTGlnaHRJcnJhZGlhbmNlKCBoZW1pc3BoZXJlTGlnaHRzWyBpIF0sIGJhY2tHZW9tZXRyeS5ub3JtYWwgKTsKCQkjZW5kaWYKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCiNlbmRpZmAsb2I9YHVuaWZvcm0gYm9vbCByZWNlaXZlU2hhZG93Owp1bmlmb3JtIHZlYzMgYW1iaWVudExpZ2h0Q29sb3I7CnVuaWZvcm0gdmVjMyBsaWdodFByb2JlWyA5IF07CnZlYzMgc2hHZXRJcnJhZGlhbmNlQXQoIGluIHZlYzMgbm9ybWFsLCBpbiB2ZWMzIHNoQ29lZmZpY2llbnRzWyA5IF0gKSB7CglmbG9hdCB4ID0gbm9ybWFsLngsIHkgPSBub3JtYWwueSwgeiA9IG5vcm1hbC56OwoJdmVjMyByZXN1bHQgPSBzaENvZWZmaWNpZW50c1sgMCBdICogMC44ODYyMjc7CglyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDEgXSAqIDIuMCAqIDAuNTExNjY0ICogeTsKCXJlc3VsdCArPSBzaENvZWZmaWNpZW50c1sgMiBdICogMi4wICogMC41MTE2NjQgKiB6OwoJcmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyAzIF0gKiAyLjAgKiAwLjUxMTY2NCAqIHg7CglyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDQgXSAqIDIuMCAqIDAuNDI5MDQzICogeCAqIHk7CglyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDUgXSAqIDIuMCAqIDAuNDI5MDQzICogeSAqIHo7CglyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDYgXSAqICggMC43NDMxMjUgKiB6ICogeiAtIDAuMjQ3NzA4ICk7CglyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDcgXSAqIDIuMCAqIDAuNDI5MDQzICogeCAqIHo7CglyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDggXSAqIDAuNDI5MDQzICogKCB4ICogeCAtIHkgKiB5ICk7CglyZXR1cm4gcmVzdWx0Owp9CnZlYzMgZ2V0TGlnaHRQcm9iZUlycmFkaWFuY2UoIGNvbnN0IGluIHZlYzMgbGlnaHRQcm9iZVsgOSBdLCBjb25zdCBpbiB2ZWMzIG5vcm1hbCApIHsKCXZlYzMgd29ybGROb3JtYWwgPSBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCBub3JtYWwsIHZpZXdNYXRyaXggKTsKCXZlYzMgaXJyYWRpYW5jZSA9IHNoR2V0SXJyYWRpYW5jZUF0KCB3b3JsZE5vcm1hbCwgbGlnaHRQcm9iZSApOwoJcmV0dXJuIGlycmFkaWFuY2U7Cn0KdmVjMyBnZXRBbWJpZW50TGlnaHRJcnJhZGlhbmNlKCBjb25zdCBpbiB2ZWMzIGFtYmllbnRMaWdodENvbG9yICkgewoJdmVjMyBpcnJhZGlhbmNlID0gYW1iaWVudExpZ2h0Q29sb3I7CglyZXR1cm4gaXJyYWRpYW5jZTsKfQpmbG9hdCBnZXREaXN0YW5jZUF0dGVudWF0aW9uKCBjb25zdCBpbiBmbG9hdCBsaWdodERpc3RhbmNlLCBjb25zdCBpbiBmbG9hdCBjdXRvZmZEaXN0YW5jZSwgY29uc3QgaW4gZmxvYXQgZGVjYXlFeHBvbmVudCApIHsKCSNpZiBkZWZpbmVkICggUEhZU0lDQUxMWV9DT1JSRUNUX0xJR0hUUyApCgkJZmxvYXQgZGlzdGFuY2VGYWxsb2ZmID0gMS4wIC8gbWF4KCBwb3coIGxpZ2h0RGlzdGFuY2UsIGRlY2F5RXhwb25lbnQgKSwgMC4wMSApOwoJCWlmICggY3V0b2ZmRGlzdGFuY2UgPiAwLjAgKSB7CgkJCWRpc3RhbmNlRmFsbG9mZiAqPSBwb3cyKCBzYXR1cmF0ZSggMS4wIC0gcG93NCggbGlnaHREaXN0YW5jZSAvIGN1dG9mZkRpc3RhbmNlICkgKSApOwoJCX0KCQlyZXR1cm4gZGlzdGFuY2VGYWxsb2ZmOwoJI2Vsc2UKCQlpZiAoIGN1dG9mZkRpc3RhbmNlID4gMC4wICYmIGRlY2F5RXhwb25lbnQgPiAwLjAgKSB7CgkJCXJldHVybiBwb3coIHNhdHVyYXRlKCAtIGxpZ2h0RGlzdGFuY2UgLyBjdXRvZmZEaXN0YW5jZSArIDEuMCApLCBkZWNheUV4cG9uZW50ICk7CgkJfQoJCXJldHVybiAxLjA7CgkjZW5kaWYKfQpmbG9hdCBnZXRTcG90QXR0ZW51YXRpb24oIGNvbnN0IGluIGZsb2F0IGNvbmVDb3NpbmUsIGNvbnN0IGluIGZsb2F0IHBlbnVtYnJhQ29zaW5lLCBjb25zdCBpbiBmbG9hdCBhbmdsZUNvc2luZSApIHsKCXJldHVybiBzbW9vdGhzdGVwKCBjb25lQ29zaW5lLCBwZW51bWJyYUNvc2luZSwgYW5nbGVDb3NpbmUgKTsKfQojaWYgTlVNX0RJUl9MSUdIVFMgPiAwCglzdHJ1Y3QgRGlyZWN0aW9uYWxMaWdodCB7CgkJdmVjMyBkaXJlY3Rpb247CgkJdmVjMyBjb2xvcjsKCX07Cgl1bmlmb3JtIERpcmVjdGlvbmFsTGlnaHQgZGlyZWN0aW9uYWxMaWdodHNbIE5VTV9ESVJfTElHSFRTIF07Cgl2b2lkIGdldERpcmVjdGlvbmFsTGlnaHRJbmZvKCBjb25zdCBpbiBEaXJlY3Rpb25hbExpZ2h0IGRpcmVjdGlvbmFsTGlnaHQsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIG91dCBJbmNpZGVudExpZ2h0IGxpZ2h0ICkgewoJCWxpZ2h0LmNvbG9yID0gZGlyZWN0aW9uYWxMaWdodC5jb2xvcjsKCQlsaWdodC5kaXJlY3Rpb24gPSBkaXJlY3Rpb25hbExpZ2h0LmRpcmVjdGlvbjsKCQlsaWdodC52aXNpYmxlID0gdHJ1ZTsKCX0KI2VuZGlmCiNpZiBOVU1fUE9JTlRfTElHSFRTID4gMAoJc3RydWN0IFBvaW50TGlnaHQgewoJCXZlYzMgcG9zaXRpb247CgkJdmVjMyBjb2xvcjsKCQlmbG9hdCBkaXN0YW5jZTsKCQlmbG9hdCBkZWNheTsKCX07Cgl1bmlmb3JtIFBvaW50TGlnaHQgcG9pbnRMaWdodHNbIE5VTV9QT0lOVF9MSUdIVFMgXTsKCXZvaWQgZ2V0UG9pbnRMaWdodEluZm8oIGNvbnN0IGluIFBvaW50TGlnaHQgcG9pbnRMaWdodCwgY29uc3QgaW4gR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeSwgb3V0IEluY2lkZW50TGlnaHQgbGlnaHQgKSB7CgkJdmVjMyBsVmVjdG9yID0gcG9pbnRMaWdodC5wb3NpdGlvbiAtIGdlb21ldHJ5LnBvc2l0aW9uOwoJCWxpZ2h0LmRpcmVjdGlvbiA9IG5vcm1hbGl6ZSggbFZlY3RvciApOwoJCWZsb2F0IGxpZ2h0RGlzdGFuY2UgPSBsZW5ndGgoIGxWZWN0b3IgKTsKCQlsaWdodC5jb2xvciA9IHBvaW50TGlnaHQuY29sb3I7CgkJbGlnaHQuY29sb3IgKj0gZ2V0RGlzdGFuY2VBdHRlbnVhdGlvbiggbGlnaHREaXN0YW5jZSwgcG9pbnRMaWdodC5kaXN0YW5jZSwgcG9pbnRMaWdodC5kZWNheSApOwoJCWxpZ2h0LnZpc2libGUgPSAoIGxpZ2h0LmNvbG9yICE9IHZlYzMoIDAuMCApICk7Cgl9CiNlbmRpZgojaWYgTlVNX1NQT1RfTElHSFRTID4gMAoJc3RydWN0IFNwb3RMaWdodCB7CgkJdmVjMyBwb3NpdGlvbjsKCQl2ZWMzIGRpcmVjdGlvbjsKCQl2ZWMzIGNvbG9yOwoJCWZsb2F0IGRpc3RhbmNlOwoJCWZsb2F0IGRlY2F5OwoJCWZsb2F0IGNvbmVDb3M7CgkJZmxvYXQgcGVudW1icmFDb3M7Cgl9OwoJdW5pZm9ybSBTcG90TGlnaHQgc3BvdExpZ2h0c1sgTlVNX1NQT1RfTElHSFRTIF07Cgl2b2lkIGdldFNwb3RMaWdodEluZm8oIGNvbnN0IGluIFNwb3RMaWdodCBzcG90TGlnaHQsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIG91dCBJbmNpZGVudExpZ2h0IGxpZ2h0ICkgewoJCXZlYzMgbFZlY3RvciA9IHNwb3RMaWdodC5wb3NpdGlvbiAtIGdlb21ldHJ5LnBvc2l0aW9uOwoJCWxpZ2h0LmRpcmVjdGlvbiA9IG5vcm1hbGl6ZSggbFZlY3RvciApOwoJCWZsb2F0IGFuZ2xlQ29zID0gZG90KCBsaWdodC5kaXJlY3Rpb24sIHNwb3RMaWdodC5kaXJlY3Rpb24gKTsKCQlmbG9hdCBzcG90QXR0ZW51YXRpb24gPSBnZXRTcG90QXR0ZW51YXRpb24oIHNwb3RMaWdodC5jb25lQ29zLCBzcG90TGlnaHQucGVudW1icmFDb3MsIGFuZ2xlQ29zICk7CgkJaWYgKCBzcG90QXR0ZW51YXRpb24gPiAwLjAgKSB7CgkJCWZsb2F0IGxpZ2h0RGlzdGFuY2UgPSBsZW5ndGgoIGxWZWN0b3IgKTsKCQkJbGlnaHQuY29sb3IgPSBzcG90TGlnaHQuY29sb3IgKiBzcG90QXR0ZW51YXRpb247CgkJCWxpZ2h0LmNvbG9yICo9IGdldERpc3RhbmNlQXR0ZW51YXRpb24oIGxpZ2h0RGlzdGFuY2UsIHNwb3RMaWdodC5kaXN0YW5jZSwgc3BvdExpZ2h0LmRlY2F5ICk7CgkJCWxpZ2h0LnZpc2libGUgPSAoIGxpZ2h0LmNvbG9yICE9IHZlYzMoIDAuMCApICk7CgkJfSBlbHNlIHsKCQkJbGlnaHQuY29sb3IgPSB2ZWMzKCAwLjAgKTsKCQkJbGlnaHQudmlzaWJsZSA9IGZhbHNlOwoJCX0KCX0KI2VuZGlmCiNpZiBOVU1fUkVDVF9BUkVBX0xJR0hUUyA+IDAKCXN0cnVjdCBSZWN0QXJlYUxpZ2h0IHsKCQl2ZWMzIGNvbG9yOwoJCXZlYzMgcG9zaXRpb247CgkJdmVjMyBoYWxmV2lkdGg7CgkJdmVjMyBoYWxmSGVpZ2h0OwoJfTsKCXVuaWZvcm0gc2FtcGxlcjJEIGx0Y18xOwl1bmlmb3JtIHNhbXBsZXIyRCBsdGNfMjsKCXVuaWZvcm0gUmVjdEFyZWFMaWdodCByZWN0QXJlYUxpZ2h0c1sgTlVNX1JFQ1RfQVJFQV9MSUdIVFMgXTsKI2VuZGlmCiNpZiBOVU1fSEVNSV9MSUdIVFMgPiAwCglzdHJ1Y3QgSGVtaXNwaGVyZUxpZ2h0IHsKCQl2ZWMzIGRpcmVjdGlvbjsKCQl2ZWMzIHNreUNvbG9yOwoJCXZlYzMgZ3JvdW5kQ29sb3I7Cgl9OwoJdW5pZm9ybSBIZW1pc3BoZXJlTGlnaHQgaGVtaXNwaGVyZUxpZ2h0c1sgTlVNX0hFTUlfTElHSFRTIF07Cgl2ZWMzIGdldEhlbWlzcGhlcmVMaWdodElycmFkaWFuY2UoIGNvbnN0IGluIEhlbWlzcGhlcmVMaWdodCBoZW1pTGlnaHQsIGNvbnN0IGluIHZlYzMgbm9ybWFsICkgewoJCWZsb2F0IGRvdE5MID0gZG90KCBub3JtYWwsIGhlbWlMaWdodC5kaXJlY3Rpb24gKTsKCQlmbG9hdCBoZW1pRGlmZnVzZVdlaWdodCA9IDAuNSAqIGRvdE5MICsgMC41OwoJCXZlYzMgaXJyYWRpYW5jZSA9IG1peCggaGVtaUxpZ2h0Lmdyb3VuZENvbG9yLCBoZW1pTGlnaHQuc2t5Q29sb3IsIGhlbWlEaWZmdXNlV2VpZ2h0ICk7CgkJcmV0dXJuIGlycmFkaWFuY2U7Cgl9CiNlbmRpZmAsYWI9YCNpZiBkZWZpbmVkKCBVU0VfRU5WTUFQICkKCSNpZmRlZiBFTlZNQVBfTU9ERV9SRUZSQUNUSU9OCgkJdW5pZm9ybSBmbG9hdCByZWZyYWN0aW9uUmF0aW87CgkjZW5kaWYKCXZlYzMgZ2V0SUJMSXJyYWRpYW5jZSggY29uc3QgaW4gdmVjMyBub3JtYWwgKSB7CgkJI2lmIGRlZmluZWQoIEVOVk1BUF9UWVBFX0NVQkVfVVYgKQoJCQl2ZWMzIHdvcmxkTm9ybWFsID0gaW52ZXJzZVRyYW5zZm9ybURpcmVjdGlvbiggbm9ybWFsLCB2aWV3TWF0cml4ICk7CgkJCXZlYzQgZW52TWFwQ29sb3IgPSB0ZXh0dXJlQ3ViZVVWKCBlbnZNYXAsIHdvcmxkTm9ybWFsLCAxLjAgKTsKCQkJcmV0dXJuIFBJICogZW52TWFwQ29sb3IucmdiICogZW52TWFwSW50ZW5zaXR5OwoJCSNlbHNlCgkJCXJldHVybiB2ZWMzKCAwLjAgKTsKCQkjZW5kaWYKCX0KCXZlYzMgZ2V0SUJMUmFkaWFuY2UoIGNvbnN0IGluIHZlYzMgdmlld0RpciwgY29uc3QgaW4gdmVjMyBub3JtYWwsIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcyApIHsKCQkjaWYgZGVmaW5lZCggRU5WTUFQX1RZUEVfQ1VCRV9VViApCgkJCXZlYzMgcmVmbGVjdFZlYzsKCQkJI2lmZGVmIEVOVk1BUF9NT0RFX1JFRkxFQ1RJT04KCQkJCXJlZmxlY3RWZWMgPSByZWZsZWN0KCAtIHZpZXdEaXIsIG5vcm1hbCApOwoJCQkJcmVmbGVjdFZlYyA9IG5vcm1hbGl6ZSggbWl4KCByZWZsZWN0VmVjLCBub3JtYWwsIHJvdWdobmVzcyAqIHJvdWdobmVzcykgKTsKCQkJI2Vsc2UKCQkJCXJlZmxlY3RWZWMgPSByZWZyYWN0KCAtIHZpZXdEaXIsIG5vcm1hbCwgcmVmcmFjdGlvblJhdGlvICk7CgkJCSNlbmRpZgoJCQlyZWZsZWN0VmVjID0gaW52ZXJzZVRyYW5zZm9ybURpcmVjdGlvbiggcmVmbGVjdFZlYywgdmlld01hdHJpeCApOwoJCQl2ZWM0IGVudk1hcENvbG9yID0gdGV4dHVyZUN1YmVVViggZW52TWFwLCByZWZsZWN0VmVjLCByb3VnaG5lc3MgKTsKCQkJcmV0dXJuIGVudk1hcENvbG9yLnJnYiAqIGVudk1hcEludGVuc2l0eTsKCQkjZWxzZQoJCQlyZXR1cm4gdmVjMyggMC4wICk7CgkJI2VuZGlmCgl9CiNlbmRpZmAsbGI9YFRvb25NYXRlcmlhbCBtYXRlcmlhbDsKbWF0ZXJpYWwuZGlmZnVzZUNvbG9yID0gZGlmZnVzZUNvbG9yLnJnYjtgLGNiPWB2YXJ5aW5nIHZlYzMgdlZpZXdQb3NpdGlvbjsKc3RydWN0IFRvb25NYXRlcmlhbCB7Cgl2ZWMzIGRpZmZ1c2VDb2xvcjsKfTsKdm9pZCBSRV9EaXJlY3RfVG9vbiggY29uc3QgaW4gSW5jaWRlbnRMaWdodCBkaXJlY3RMaWdodCwgY29uc3QgaW4gR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeSwgY29uc3QgaW4gVG9vbk1hdGVyaWFsIG1hdGVyaWFsLCBpbm91dCBSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCApIHsKCXZlYzMgaXJyYWRpYW5jZSA9IGdldEdyYWRpZW50SXJyYWRpYW5jZSggZ2VvbWV0cnkubm9ybWFsLCBkaXJlY3RMaWdodC5kaXJlY3Rpb24gKSAqIGRpcmVjdExpZ2h0LmNvbG9yOwoJcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArPSBpcnJhZGlhbmNlICogQlJERl9MYW1iZXJ0KCBtYXRlcmlhbC5kaWZmdXNlQ29sb3IgKTsKfQp2b2lkIFJFX0luZGlyZWN0RGlmZnVzZV9Ub29uKCBjb25zdCBpbiB2ZWMzIGlycmFkaWFuY2UsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIGNvbnN0IGluIFRvb25NYXRlcmlhbCBtYXRlcmlhbCwgaW5vdXQgUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgKSB7CglyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKz0gaXJyYWRpYW5jZSAqIEJSREZfTGFtYmVydCggbWF0ZXJpYWwuZGlmZnVzZUNvbG9yICk7Cn0KI2RlZmluZSBSRV9EaXJlY3QJCQkJUkVfRGlyZWN0X1Rvb24KI2RlZmluZSBSRV9JbmRpcmVjdERpZmZ1c2UJCVJFX0luZGlyZWN0RGlmZnVzZV9Ub29uCiNkZWZpbmUgTWF0ZXJpYWxfTGlnaHRQcm9iZUxPRCggbWF0ZXJpYWwgKQkoMClgLHViPWBCbGlublBob25nTWF0ZXJpYWwgbWF0ZXJpYWw7Cm1hdGVyaWFsLmRpZmZ1c2VDb2xvciA9IGRpZmZ1c2VDb2xvci5yZ2I7Cm1hdGVyaWFsLnNwZWN1bGFyQ29sb3IgPSBzcGVjdWxhcjsKbWF0ZXJpYWwuc3BlY3VsYXJTaGluaW5lc3MgPSBzaGluaW5lc3M7Cm1hdGVyaWFsLnNwZWN1bGFyU3RyZW5ndGggPSBzcGVjdWxhclN0cmVuZ3RoO2AsaGI9YHZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uOwpzdHJ1Y3QgQmxpbm5QaG9uZ01hdGVyaWFsIHsKCXZlYzMgZGlmZnVzZUNvbG9yOwoJdmVjMyBzcGVjdWxhckNvbG9yOwoJZmxvYXQgc3BlY3VsYXJTaGluaW5lc3M7CglmbG9hdCBzcGVjdWxhclN0cmVuZ3RoOwp9Owp2b2lkIFJFX0RpcmVjdF9CbGlublBob25nKCBjb25zdCBpbiBJbmNpZGVudExpZ2h0IGRpcmVjdExpZ2h0LCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBjb25zdCBpbiBCbGlublBob25nTWF0ZXJpYWwgbWF0ZXJpYWwsIGlub3V0IFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ICkgewoJZmxvYXQgZG90TkwgPSBzYXR1cmF0ZSggZG90KCBnZW9tZXRyeS5ub3JtYWwsIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiApICk7Cgl2ZWMzIGlycmFkaWFuY2UgPSBkb3ROTCAqIGRpcmVjdExpZ2h0LmNvbG9yOwoJcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArPSBpcnJhZGlhbmNlICogQlJERl9MYW1iZXJ0KCBtYXRlcmlhbC5kaWZmdXNlQ29sb3IgKTsKCXJlZmxlY3RlZExpZ2h0LmRpcmVjdFNwZWN1bGFyICs9IGlycmFkaWFuY2UgKiBCUkRGX0JsaW5uUGhvbmcoIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiwgZ2VvbWV0cnkudmlld0RpciwgZ2VvbWV0cnkubm9ybWFsLCBtYXRlcmlhbC5zcGVjdWxhckNvbG9yLCBtYXRlcmlhbC5zcGVjdWxhclNoaW5pbmVzcyApICogbWF0ZXJpYWwuc3BlY3VsYXJTdHJlbmd0aDsKfQp2b2lkIFJFX0luZGlyZWN0RGlmZnVzZV9CbGlublBob25nKCBjb25zdCBpbiB2ZWMzIGlycmFkaWFuY2UsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIGNvbnN0IGluIEJsaW5uUGhvbmdNYXRlcmlhbCBtYXRlcmlhbCwgaW5vdXQgUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgKSB7CglyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKz0gaXJyYWRpYW5jZSAqIEJSREZfTGFtYmVydCggbWF0ZXJpYWwuZGlmZnVzZUNvbG9yICk7Cn0KI2RlZmluZSBSRV9EaXJlY3QJCQkJUkVfRGlyZWN0X0JsaW5uUGhvbmcKI2RlZmluZSBSRV9JbmRpcmVjdERpZmZ1c2UJCVJFX0luZGlyZWN0RGlmZnVzZV9CbGlublBob25nCiNkZWZpbmUgTWF0ZXJpYWxfTGlnaHRQcm9iZUxPRCggbWF0ZXJpYWwgKQkoMClgLGZiPWBQaHlzaWNhbE1hdGVyaWFsIG1hdGVyaWFsOwptYXRlcmlhbC5kaWZmdXNlQ29sb3IgPSBkaWZmdXNlQ29sb3IucmdiICogKCAxLjAgLSBtZXRhbG5lc3NGYWN0b3IgKTsKdmVjMyBkeHkgPSBtYXgoIGFicyggZEZkeCggZ2VvbWV0cnlOb3JtYWwgKSApLCBhYnMoIGRGZHkoIGdlb21ldHJ5Tm9ybWFsICkgKSApOwpmbG9hdCBnZW9tZXRyeVJvdWdobmVzcyA9IG1heCggbWF4KCBkeHkueCwgZHh5LnkgKSwgZHh5LnogKTsKbWF0ZXJpYWwucm91Z2huZXNzID0gbWF4KCByb3VnaG5lc3NGYWN0b3IsIDAuMDUyNSApO21hdGVyaWFsLnJvdWdobmVzcyArPSBnZW9tZXRyeVJvdWdobmVzczsKbWF0ZXJpYWwucm91Z2huZXNzID0gbWluKCBtYXRlcmlhbC5yb3VnaG5lc3MsIDEuMCApOwojaWZkZWYgSU9SCgkjaWZkZWYgU1BFQ1VMQVIKCQlmbG9hdCBzcGVjdWxhckludGVuc2l0eUZhY3RvciA9IHNwZWN1bGFySW50ZW5zaXR5OwoJCXZlYzMgc3BlY3VsYXJDb2xvckZhY3RvciA9IHNwZWN1bGFyQ29sb3I7CgkJI2lmZGVmIFVTRV9TUEVDVUxBUklOVEVOU0lUWU1BUAoJCQlzcGVjdWxhckludGVuc2l0eUZhY3RvciAqPSB0ZXh0dXJlMkQoIHNwZWN1bGFySW50ZW5zaXR5TWFwLCB2VXYgKS5hOwoJCSNlbmRpZgoJCSNpZmRlZiBVU0VfU1BFQ1VMQVJDT0xPUk1BUAoJCQlzcGVjdWxhckNvbG9yRmFjdG9yICo9IHRleHR1cmUyRCggc3BlY3VsYXJDb2xvck1hcCwgdlV2ICkucmdiOwoJCSNlbmRpZgoJCW1hdGVyaWFsLnNwZWN1bGFyRjkwID0gbWl4KCBzcGVjdWxhckludGVuc2l0eUZhY3RvciwgMS4wLCBtZXRhbG5lc3NGYWN0b3IgKTsKCSNlbHNlCgkJZmxvYXQgc3BlY3VsYXJJbnRlbnNpdHlGYWN0b3IgPSAxLjA7CgkJdmVjMyBzcGVjdWxhckNvbG9yRmFjdG9yID0gdmVjMyggMS4wICk7CgkJbWF0ZXJpYWwuc3BlY3VsYXJGOTAgPSAxLjA7CgkjZW5kaWYKCW1hdGVyaWFsLnNwZWN1bGFyQ29sb3IgPSBtaXgoIG1pbiggcG93MiggKCBpb3IgLSAxLjAgKSAvICggaW9yICsgMS4wICkgKSAqIHNwZWN1bGFyQ29sb3JGYWN0b3IsIHZlYzMoIDEuMCApICkgKiBzcGVjdWxhckludGVuc2l0eUZhY3RvciwgZGlmZnVzZUNvbG9yLnJnYiwgbWV0YWxuZXNzRmFjdG9yICk7CiNlbHNlCgltYXRlcmlhbC5zcGVjdWxhckNvbG9yID0gbWl4KCB2ZWMzKCAwLjA0ICksIGRpZmZ1c2VDb2xvci5yZ2IsIG1ldGFsbmVzc0ZhY3RvciApOwoJbWF0ZXJpYWwuc3BlY3VsYXJGOTAgPSAxLjA7CiNlbmRpZgojaWZkZWYgVVNFX0NMRUFSQ09BVAoJbWF0ZXJpYWwuY2xlYXJjb2F0ID0gY2xlYXJjb2F0OwoJbWF0ZXJpYWwuY2xlYXJjb2F0Um91Z2huZXNzID0gY2xlYXJjb2F0Um91Z2huZXNzOwoJbWF0ZXJpYWwuY2xlYXJjb2F0RjAgPSB2ZWMzKCAwLjA0ICk7CgltYXRlcmlhbC5jbGVhcmNvYXRGOTAgPSAxLjA7CgkjaWZkZWYgVVNFX0NMRUFSQ09BVE1BUAoJCW1hdGVyaWFsLmNsZWFyY29hdCAqPSB0ZXh0dXJlMkQoIGNsZWFyY29hdE1hcCwgdlV2ICkueDsKCSNlbmRpZgoJI2lmZGVmIFVTRV9DTEVBUkNPQVRfUk9VR0hORVNTTUFQCgkJbWF0ZXJpYWwuY2xlYXJjb2F0Um91Z2huZXNzICo9IHRleHR1cmUyRCggY2xlYXJjb2F0Um91Z2huZXNzTWFwLCB2VXYgKS55OwoJI2VuZGlmCgltYXRlcmlhbC5jbGVhcmNvYXQgPSBzYXR1cmF0ZSggbWF0ZXJpYWwuY2xlYXJjb2F0ICk7CW1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcyA9IG1heCggbWF0ZXJpYWwuY2xlYXJjb2F0Um91Z2huZXNzLCAwLjA1MjUgKTsKCW1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcyArPSBnZW9tZXRyeVJvdWdobmVzczsKCW1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcyA9IG1pbiggbWF0ZXJpYWwuY2xlYXJjb2F0Um91Z2huZXNzLCAxLjAgKTsKI2VuZGlmCiNpZmRlZiBVU0VfU0hFRU4KCW1hdGVyaWFsLnNoZWVuQ29sb3IgPSBzaGVlbkNvbG9yOwoJI2lmZGVmIFVTRV9TSEVFTkNPTE9STUFQCgkJbWF0ZXJpYWwuc2hlZW5Db2xvciAqPSB0ZXh0dXJlMkQoIHNoZWVuQ29sb3JNYXAsIHZVdiApLnJnYjsKCSNlbmRpZgoJbWF0ZXJpYWwuc2hlZW5Sb3VnaG5lc3MgPSBjbGFtcCggc2hlZW5Sb3VnaG5lc3MsIDAuMDcsIDEuMCApOwoJI2lmZGVmIFVTRV9TSEVFTlJPVUdITkVTU01BUAoJCW1hdGVyaWFsLnNoZWVuUm91Z2huZXNzICo9IHRleHR1cmUyRCggc2hlZW5Sb3VnaG5lc3NNYXAsIHZVdiApLmE7CgkjZW5kaWYKI2VuZGlmYCxkYj1gc3RydWN0IFBoeXNpY2FsTWF0ZXJpYWwgewoJdmVjMyBkaWZmdXNlQ29sb3I7CglmbG9hdCByb3VnaG5lc3M7Cgl2ZWMzIHNwZWN1bGFyQ29sb3I7CglmbG9hdCBzcGVjdWxhckY5MDsKCSNpZmRlZiBVU0VfQ0xFQVJDT0FUCgkJZmxvYXQgY2xlYXJjb2F0OwoJCWZsb2F0IGNsZWFyY29hdFJvdWdobmVzczsKCQl2ZWMzIGNsZWFyY29hdEYwOwoJCWZsb2F0IGNsZWFyY29hdEY5MDsKCSNlbmRpZgoJI2lmZGVmIFVTRV9TSEVFTgoJCXZlYzMgc2hlZW5Db2xvcjsKCQlmbG9hdCBzaGVlblJvdWdobmVzczsKCSNlbmRpZgp9Owp2ZWMzIGNsZWFyY29hdFNwZWN1bGFyID0gdmVjMyggMC4wICk7CnZlYzMgc2hlZW5TcGVjdWxhciA9IHZlYzMoIDAuMCApOwpmbG9hdCBJQkxTaGVlbkJSREYoIGNvbnN0IGluIHZlYzMgbm9ybWFsLCBjb25zdCBpbiB2ZWMzIHZpZXdEaXIsIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcykgewoJZmxvYXQgZG90TlYgPSBzYXR1cmF0ZSggZG90KCBub3JtYWwsIHZpZXdEaXIgKSApOwoJZmxvYXQgcjIgPSByb3VnaG5lc3MgKiByb3VnaG5lc3M7CglmbG9hdCBhID0gcm91Z2huZXNzIDwgMC4yNSA/IC0zMzkuMiAqIHIyICsgMTYxLjQgKiByb3VnaG5lc3MgLSAyNS45IDogLTguNDggKiByMiArIDE0LjMgKiByb3VnaG5lc3MgLSA5Ljk1OwoJZmxvYXQgYiA9IHJvdWdobmVzcyA8IDAuMjUgPyA0NC4wICogcjIgLSAyMy43ICogcm91Z2huZXNzICsgMy4yNiA6IDEuOTcgKiByMiAtIDMuMjcgKiByb3VnaG5lc3MgKyAwLjcyOwoJZmxvYXQgREcgPSBleHAoIGEgKiBkb3ROViArIGIgKSArICggcm91Z2huZXNzIDwgMC4yNSA/IDAuMCA6IDAuMSAqICggcm91Z2huZXNzIC0gMC4yNSApICk7CglyZXR1cm4gc2F0dXJhdGUoIERHICogUkVDSVBST0NBTF9QSSApOwp9CnZlYzIgREZHQXBwcm94KCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MgKSB7CglmbG9hdCBkb3ROViA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgdmlld0RpciApICk7Cgljb25zdCB2ZWM0IGMwID0gdmVjNCggLSAxLCAtIDAuMDI3NSwgLSAwLjU3MiwgMC4wMjIgKTsKCWNvbnN0IHZlYzQgYzEgPSB2ZWM0KCAxLCAwLjA0MjUsIDEuMDQsIC0gMC4wNCApOwoJdmVjNCByID0gcm91Z2huZXNzICogYzAgKyBjMTsKCWZsb2F0IGEwMDQgPSBtaW4oIHIueCAqIHIueCwgZXhwMiggLSA5LjI4ICogZG90TlYgKSApICogci54ICsgci55OwoJdmVjMiBmYWIgPSB2ZWMyKCAtIDEuMDQsIDEuMDQgKSAqIGEwMDQgKyByLnp3OwoJcmV0dXJuIGZhYjsKfQp2ZWMzIEVudmlyb25tZW50QlJERiggY29uc3QgaW4gdmVjMyBub3JtYWwsIGNvbnN0IGluIHZlYzMgdmlld0RpciwgY29uc3QgaW4gdmVjMyBzcGVjdWxhckNvbG9yLCBjb25zdCBpbiBmbG9hdCBzcGVjdWxhckY5MCwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzICkgewoJdmVjMiBmYWIgPSBERkdBcHByb3goIG5vcm1hbCwgdmlld0Rpciwgcm91Z2huZXNzICk7CglyZXR1cm4gc3BlY3VsYXJDb2xvciAqIGZhYi54ICsgc3BlY3VsYXJGOTAgKiBmYWIueTsKfQp2b2lkIGNvbXB1dGVNdWx0aXNjYXR0ZXJpbmcoIGNvbnN0IGluIHZlYzMgbm9ybWFsLCBjb25zdCBpbiB2ZWMzIHZpZXdEaXIsIGNvbnN0IGluIHZlYzMgc3BlY3VsYXJDb2xvciwgY29uc3QgaW4gZmxvYXQgc3BlY3VsYXJGOTAsIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcywgaW5vdXQgdmVjMyBzaW5nbGVTY2F0dGVyLCBpbm91dCB2ZWMzIG11bHRpU2NhdHRlciApIHsKCXZlYzIgZmFiID0gREZHQXBwcm94KCBub3JtYWwsIHZpZXdEaXIsIHJvdWdobmVzcyApOwoJdmVjMyBGc3NFc3MgPSBzcGVjdWxhckNvbG9yICogZmFiLnggKyBzcGVjdWxhckY5MCAqIGZhYi55OwoJZmxvYXQgRXNzID0gZmFiLnggKyBmYWIueTsKCWZsb2F0IEVtcyA9IDEuMCAtIEVzczsKCXZlYzMgRmF2ZyA9IHNwZWN1bGFyQ29sb3IgKyAoIDEuMCAtIHNwZWN1bGFyQ29sb3IgKSAqIDAuMDQ3NjE5Owl2ZWMzIEZtcyA9IEZzc0VzcyAqIEZhdmcgLyAoIDEuMCAtIEVtcyAqIEZhdmcgKTsKCXNpbmdsZVNjYXR0ZXIgKz0gRnNzRXNzOwoJbXVsdGlTY2F0dGVyICs9IEZtcyAqIEVtczsKfQojaWYgTlVNX1JFQ1RfQVJFQV9MSUdIVFMgPiAwCgl2b2lkIFJFX0RpcmVjdF9SZWN0QXJlYV9QaHlzaWNhbCggY29uc3QgaW4gUmVjdEFyZWFMaWdodCByZWN0QXJlYUxpZ2h0LCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBjb25zdCBpbiBQaHlzaWNhbE1hdGVyaWFsIG1hdGVyaWFsLCBpbm91dCBSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCApIHsKCQl2ZWMzIG5vcm1hbCA9IGdlb21ldHJ5Lm5vcm1hbDsKCQl2ZWMzIHZpZXdEaXIgPSBnZW9tZXRyeS52aWV3RGlyOwoJCXZlYzMgcG9zaXRpb24gPSBnZW9tZXRyeS5wb3NpdGlvbjsKCQl2ZWMzIGxpZ2h0UG9zID0gcmVjdEFyZWFMaWdodC5wb3NpdGlvbjsKCQl2ZWMzIGhhbGZXaWR0aCA9IHJlY3RBcmVhTGlnaHQuaGFsZldpZHRoOwoJCXZlYzMgaGFsZkhlaWdodCA9IHJlY3RBcmVhTGlnaHQuaGFsZkhlaWdodDsKCQl2ZWMzIGxpZ2h0Q29sb3IgPSByZWN0QXJlYUxpZ2h0LmNvbG9yOwoJCWZsb2F0IHJvdWdobmVzcyA9IG1hdGVyaWFsLnJvdWdobmVzczsKCQl2ZWMzIHJlY3RDb29yZHNbIDQgXTsKCQlyZWN0Q29vcmRzWyAwIF0gPSBsaWdodFBvcyArIGhhbGZXaWR0aCAtIGhhbGZIZWlnaHQ7CQlyZWN0Q29vcmRzWyAxIF0gPSBsaWdodFBvcyAtIGhhbGZXaWR0aCAtIGhhbGZIZWlnaHQ7CgkJcmVjdENvb3Jkc1sgMiBdID0gbGlnaHRQb3MgLSBoYWxmV2lkdGggKyBoYWxmSGVpZ2h0OwoJCXJlY3RDb29yZHNbIDMgXSA9IGxpZ2h0UG9zICsgaGFsZldpZHRoICsgaGFsZkhlaWdodDsKCQl2ZWMyIHV2ID0gTFRDX1V2KCBub3JtYWwsIHZpZXdEaXIsIHJvdWdobmVzcyApOwoJCXZlYzQgdDEgPSB0ZXh0dXJlMkQoIGx0Y18xLCB1diApOwoJCXZlYzQgdDIgPSB0ZXh0dXJlMkQoIGx0Y18yLCB1diApOwoJCW1hdDMgbUludiA9IG1hdDMoCgkJCXZlYzMoIHQxLngsIDAsIHQxLnkgKSwKCQkJdmVjMyggICAgMCwgMSwgICAgMCApLAoJCQl2ZWMzKCB0MS56LCAwLCB0MS53ICkKCQkpOwoJCXZlYzMgZnJlc25lbCA9ICggbWF0ZXJpYWwuc3BlY3VsYXJDb2xvciAqIHQyLnggKyAoIHZlYzMoIDEuMCApIC0gbWF0ZXJpYWwuc3BlY3VsYXJDb2xvciApICogdDIueSApOwoJCXJlZmxlY3RlZExpZ2h0LmRpcmVjdFNwZWN1bGFyICs9IGxpZ2h0Q29sb3IgKiBmcmVzbmVsICogTFRDX0V2YWx1YXRlKCBub3JtYWwsIHZpZXdEaXIsIHBvc2l0aW9uLCBtSW52LCByZWN0Q29vcmRzICk7CgkJcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArPSBsaWdodENvbG9yICogbWF0ZXJpYWwuZGlmZnVzZUNvbG9yICogTFRDX0V2YWx1YXRlKCBub3JtYWwsIHZpZXdEaXIsIHBvc2l0aW9uLCBtYXQzKCAxLjAgKSwgcmVjdENvb3JkcyApOwoJfQojZW5kaWYKdm9pZCBSRV9EaXJlY3RfUGh5c2ljYWwoIGNvbnN0IGluIEluY2lkZW50TGlnaHQgZGlyZWN0TGlnaHQsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIGNvbnN0IGluIFBoeXNpY2FsTWF0ZXJpYWwgbWF0ZXJpYWwsIGlub3V0IFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ICkgewoJZmxvYXQgZG90TkwgPSBzYXR1cmF0ZSggZG90KCBnZW9tZXRyeS5ub3JtYWwsIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiApICk7Cgl2ZWMzIGlycmFkaWFuY2UgPSBkb3ROTCAqIGRpcmVjdExpZ2h0LmNvbG9yOwoJI2lmZGVmIFVTRV9DTEVBUkNPQVQKCQlmbG9hdCBkb3ROTGNjID0gc2F0dXJhdGUoIGRvdCggZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsLCBkaXJlY3RMaWdodC5kaXJlY3Rpb24gKSApOwoJCXZlYzMgY2NJcnJhZGlhbmNlID0gZG90TkxjYyAqIGRpcmVjdExpZ2h0LmNvbG9yOwoJCWNsZWFyY29hdFNwZWN1bGFyICs9IGNjSXJyYWRpYW5jZSAqIEJSREZfR0dYKCBkaXJlY3RMaWdodC5kaXJlY3Rpb24sIGdlb21ldHJ5LnZpZXdEaXIsIGdlb21ldHJ5LmNsZWFyY29hdE5vcm1hbCwgbWF0ZXJpYWwuY2xlYXJjb2F0RjAsIG1hdGVyaWFsLmNsZWFyY29hdEY5MCwgbWF0ZXJpYWwuY2xlYXJjb2F0Um91Z2huZXNzICk7CgkjZW5kaWYKCSNpZmRlZiBVU0VfU0hFRU4KCQlzaGVlblNwZWN1bGFyICs9IGlycmFkaWFuY2UgKiBCUkRGX1NoZWVuKCBkaXJlY3RMaWdodC5kaXJlY3Rpb24sIGdlb21ldHJ5LnZpZXdEaXIsIGdlb21ldHJ5Lm5vcm1hbCwgbWF0ZXJpYWwuc2hlZW5Db2xvciwgbWF0ZXJpYWwuc2hlZW5Sb3VnaG5lc3MgKTsKCSNlbmRpZgoJcmVmbGVjdGVkTGlnaHQuZGlyZWN0U3BlY3VsYXIgKz0gaXJyYWRpYW5jZSAqIEJSREZfR0dYKCBkaXJlY3RMaWdodC5kaXJlY3Rpb24sIGdlb21ldHJ5LnZpZXdEaXIsIGdlb21ldHJ5Lm5vcm1hbCwgbWF0ZXJpYWwuc3BlY3VsYXJDb2xvciwgbWF0ZXJpYWwuc3BlY3VsYXJGOTAsIG1hdGVyaWFsLnJvdWdobmVzcyApOwoJcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArPSBpcnJhZGlhbmNlICogQlJERl9MYW1iZXJ0KCBtYXRlcmlhbC5kaWZmdXNlQ29sb3IgKTsKfQp2b2lkIFJFX0luZGlyZWN0RGlmZnVzZV9QaHlzaWNhbCggY29uc3QgaW4gdmVjMyBpcnJhZGlhbmNlLCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBjb25zdCBpbiBQaHlzaWNhbE1hdGVyaWFsIG1hdGVyaWFsLCBpbm91dCBSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCApIHsKCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSBpcnJhZGlhbmNlICogQlJERl9MYW1iZXJ0KCBtYXRlcmlhbC5kaWZmdXNlQ29sb3IgKTsKfQp2b2lkIFJFX0luZGlyZWN0U3BlY3VsYXJfUGh5c2ljYWwoIGNvbnN0IGluIHZlYzMgcmFkaWFuY2UsIGNvbnN0IGluIHZlYzMgaXJyYWRpYW5jZSwgY29uc3QgaW4gdmVjMyBjbGVhcmNvYXRSYWRpYW5jZSwgY29uc3QgaW4gR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeSwgY29uc3QgaW4gUGh5c2ljYWxNYXRlcmlhbCBtYXRlcmlhbCwgaW5vdXQgUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQpIHsKCSNpZmRlZiBVU0VfQ0xFQVJDT0FUCgkJY2xlYXJjb2F0U3BlY3VsYXIgKz0gY2xlYXJjb2F0UmFkaWFuY2UgKiBFbnZpcm9ubWVudEJSREYoIGdlb21ldHJ5LmNsZWFyY29hdE5vcm1hbCwgZ2VvbWV0cnkudmlld0RpciwgbWF0ZXJpYWwuY2xlYXJjb2F0RjAsIG1hdGVyaWFsLmNsZWFyY29hdEY5MCwgbWF0ZXJpYWwuY2xlYXJjb2F0Um91Z2huZXNzICk7CgkjZW5kaWYKCSNpZmRlZiBVU0VfU0hFRU4KCQlzaGVlblNwZWN1bGFyICs9IGlycmFkaWFuY2UgKiBtYXRlcmlhbC5zaGVlbkNvbG9yICogSUJMU2hlZW5CUkRGKCBnZW9tZXRyeS5ub3JtYWwsIGdlb21ldHJ5LnZpZXdEaXIsIG1hdGVyaWFsLnNoZWVuUm91Z2huZXNzICk7CgkjZW5kaWYKCXZlYzMgc2luZ2xlU2NhdHRlcmluZyA9IHZlYzMoIDAuMCApOwoJdmVjMyBtdWx0aVNjYXR0ZXJpbmcgPSB2ZWMzKCAwLjAgKTsKCXZlYzMgY29zaW5lV2VpZ2h0ZWRJcnJhZGlhbmNlID0gaXJyYWRpYW5jZSAqIFJFQ0lQUk9DQUxfUEk7Cgljb21wdXRlTXVsdGlzY2F0dGVyaW5nKCBnZW9tZXRyeS5ub3JtYWwsIGdlb21ldHJ5LnZpZXdEaXIsIG1hdGVyaWFsLnNwZWN1bGFyQ29sb3IsIG1hdGVyaWFsLnNwZWN1bGFyRjkwLCBtYXRlcmlhbC5yb3VnaG5lc3MsIHNpbmdsZVNjYXR0ZXJpbmcsIG11bHRpU2NhdHRlcmluZyApOwoJdmVjMyBkaWZmdXNlID0gbWF0ZXJpYWwuZGlmZnVzZUNvbG9yICogKCAxLjAgLSAoIHNpbmdsZVNjYXR0ZXJpbmcgKyBtdWx0aVNjYXR0ZXJpbmcgKSApOwoJcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3RTcGVjdWxhciArPSByYWRpYW5jZSAqIHNpbmdsZVNjYXR0ZXJpbmc7CglyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdFNwZWN1bGFyICs9IG11bHRpU2NhdHRlcmluZyAqIGNvc2luZVdlaWdodGVkSXJyYWRpYW5jZTsKCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSBkaWZmdXNlICogY29zaW5lV2VpZ2h0ZWRJcnJhZGlhbmNlOwp9CiNkZWZpbmUgUkVfRGlyZWN0CQkJCVJFX0RpcmVjdF9QaHlzaWNhbAojZGVmaW5lIFJFX0RpcmVjdF9SZWN0QXJlYQkJUkVfRGlyZWN0X1JlY3RBcmVhX1BoeXNpY2FsCiNkZWZpbmUgUkVfSW5kaXJlY3REaWZmdXNlCQlSRV9JbmRpcmVjdERpZmZ1c2VfUGh5c2ljYWwKI2RlZmluZSBSRV9JbmRpcmVjdFNwZWN1bGFyCQlSRV9JbmRpcmVjdFNwZWN1bGFyX1BoeXNpY2FsCmZsb2F0IGNvbXB1dGVTcGVjdWxhck9jY2x1c2lvbiggY29uc3QgaW4gZmxvYXQgZG90TlYsIGNvbnN0IGluIGZsb2F0IGFtYmllbnRPY2NsdXNpb24sIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcyApIHsKCXJldHVybiBzYXR1cmF0ZSggcG93KCBkb3ROViArIGFtYmllbnRPY2NsdXNpb24sIGV4cDIoIC0gMTYuMCAqIHJvdWdobmVzcyAtIDEuMCApICkgLSAxLjAgKyBhbWJpZW50T2NjbHVzaW9uICk7Cn1gLHBiPWAKR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeTsKZ2VvbWV0cnkucG9zaXRpb24gPSAtIHZWaWV3UG9zaXRpb247Cmdlb21ldHJ5Lm5vcm1hbCA9IG5vcm1hbDsKZ2VvbWV0cnkudmlld0RpciA9ICggaXNPcnRob2dyYXBoaWMgKSA/IHZlYzMoIDAsIDAsIDEgKSA6IG5vcm1hbGl6ZSggdlZpZXdQb3NpdGlvbiApOwojaWZkZWYgVVNFX0NMRUFSQ09BVAoJZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsID0gY2xlYXJjb2F0Tm9ybWFsOwojZW5kaWYKSW5jaWRlbnRMaWdodCBkaXJlY3RMaWdodDsKI2lmICggTlVNX1BPSU5UX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3QgKQoJUG9pbnRMaWdodCBwb2ludExpZ2h0OwoJI2lmIGRlZmluZWQoIFVTRV9TSEFET1dNQVAgKSAmJiBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyA+IDAKCVBvaW50TGlnaHRTaGFkb3cgcG9pbnRMaWdodFNoYWRvdzsKCSNlbmRpZgoJI3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydAoJZm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1BPSU5UX0xJR0hUUzsgaSArKyApIHsKCQlwb2ludExpZ2h0ID0gcG9pbnRMaWdodHNbIGkgXTsKCQlnZXRQb2ludExpZ2h0SW5mbyggcG9pbnRMaWdodCwgZ2VvbWV0cnksIGRpcmVjdExpZ2h0ICk7CgkJI2lmIGRlZmluZWQoIFVTRV9TSEFET1dNQVAgKSAmJiAoIFVOUk9MTEVEX0xPT1BfSU5ERVggPCBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyApCgkJcG9pbnRMaWdodFNoYWRvdyA9IHBvaW50TGlnaHRTaGFkb3dzWyBpIF07CgkJZGlyZWN0TGlnaHQuY29sb3IgKj0gYWxsKCBidmVjMiggZGlyZWN0TGlnaHQudmlzaWJsZSwgcmVjZWl2ZVNoYWRvdyApICkgPyBnZXRQb2ludFNoYWRvdyggcG9pbnRTaGFkb3dNYXBbIGkgXSwgcG9pbnRMaWdodFNoYWRvdy5zaGFkb3dNYXBTaXplLCBwb2ludExpZ2h0U2hhZG93LnNoYWRvd0JpYXMsIHBvaW50TGlnaHRTaGFkb3cuc2hhZG93UmFkaXVzLCB2UG9pbnRTaGFkb3dDb29yZFsgaSBdLCBwb2ludExpZ2h0U2hhZG93LnNoYWRvd0NhbWVyYU5lYXIsIHBvaW50TGlnaHRTaGFkb3cuc2hhZG93Q2FtZXJhRmFyICkgOiAxLjA7CgkJI2VuZGlmCgkJUkVfRGlyZWN0KCBkaXJlY3RMaWdodCwgZ2VvbWV0cnksIG1hdGVyaWFsLCByZWZsZWN0ZWRMaWdodCApOwoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKI2VuZGlmCiNpZiAoIE5VTV9TUE9UX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3QgKQoJU3BvdExpZ2h0IHNwb3RMaWdodDsKCSNpZiBkZWZpbmVkKCBVU0VfU0hBRE9XTUFQICkgJiYgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyA+IDAKCVNwb3RMaWdodFNoYWRvdyBzcG90TGlnaHRTaGFkb3c7CgkjZW5kaWYKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9TUE9UX0xJR0hUUzsgaSArKyApIHsKCQlzcG90TGlnaHQgPSBzcG90TGlnaHRzWyBpIF07CgkJZ2V0U3BvdExpZ2h0SW5mbyggc3BvdExpZ2h0LCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTsKCQkjaWYgZGVmaW5lZCggVVNFX1NIQURPV01BUCApICYmICggVU5ST0xMRURfTE9PUF9JTkRFWCA8IE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgKQoJCXNwb3RMaWdodFNoYWRvdyA9IHNwb3RMaWdodFNoYWRvd3NbIGkgXTsKCQlkaXJlY3RMaWdodC5jb2xvciAqPSBhbGwoIGJ2ZWMyKCBkaXJlY3RMaWdodC52aXNpYmxlLCByZWNlaXZlU2hhZG93ICkgKSA/IGdldFNoYWRvdyggc3BvdFNoYWRvd01hcFsgaSBdLCBzcG90TGlnaHRTaGFkb3cuc2hhZG93TWFwU2l6ZSwgc3BvdExpZ2h0U2hhZG93LnNoYWRvd0JpYXMsIHNwb3RMaWdodFNoYWRvdy5zaGFkb3dSYWRpdXMsIHZTcG90U2hhZG93Q29vcmRbIGkgXSApIDogMS4wOwoJCSNlbmRpZgoJCVJFX0RpcmVjdCggZGlyZWN0TGlnaHQsIGdlb21ldHJ5LCBtYXRlcmlhbCwgcmVmbGVjdGVkTGlnaHQgKTsKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCiNlbmRpZgojaWYgKCBOVU1fRElSX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3QgKQoJRGlyZWN0aW9uYWxMaWdodCBkaXJlY3Rpb25hbExpZ2h0OwoJI2lmIGRlZmluZWQoIFVTRV9TSEFET1dNQVAgKSAmJiBOVU1fRElSX0xJR0hUX1NIQURPV1MgPiAwCglEaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3c7CgkjZW5kaWYKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9ESVJfTElHSFRTOyBpICsrICkgewoJCWRpcmVjdGlvbmFsTGlnaHQgPSBkaXJlY3Rpb25hbExpZ2h0c1sgaSBdOwoJCWdldERpcmVjdGlvbmFsTGlnaHRJbmZvKCBkaXJlY3Rpb25hbExpZ2h0LCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTsKCQkjaWYgZGVmaW5lZCggVVNFX1NIQURPV01BUCApICYmICggVU5ST0xMRURfTE9PUF9JTkRFWCA8IE5VTV9ESVJfTElHSFRfU0hBRE9XUyApCgkJZGlyZWN0aW9uYWxMaWdodFNoYWRvdyA9IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzWyBpIF07CgkJZGlyZWN0TGlnaHQuY29sb3IgKj0gYWxsKCBidmVjMiggZGlyZWN0TGlnaHQudmlzaWJsZSwgcmVjZWl2ZVNoYWRvdyApICkgPyBnZXRTaGFkb3coIGRpcmVjdGlvbmFsU2hhZG93TWFwWyBpIF0sIGRpcmVjdGlvbmFsTGlnaHRTaGFkb3cuc2hhZG93TWFwU2l6ZSwgZGlyZWN0aW9uYWxMaWdodFNoYWRvdy5zaGFkb3dCaWFzLCBkaXJlY3Rpb25hbExpZ2h0U2hhZG93LnNoYWRvd1JhZGl1cywgdkRpcmVjdGlvbmFsU2hhZG93Q29vcmRbIGkgXSApIDogMS4wOwoJCSNlbmRpZgoJCVJFX0RpcmVjdCggZGlyZWN0TGlnaHQsIGdlb21ldHJ5LCBtYXRlcmlhbCwgcmVmbGVjdGVkTGlnaHQgKTsKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCiNlbmRpZgojaWYgKCBOVU1fUkVDVF9BUkVBX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3RfUmVjdEFyZWEgKQoJUmVjdEFyZWFMaWdodCByZWN0QXJlYUxpZ2h0OwoJI3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydAoJZm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1JFQ1RfQVJFQV9MSUdIVFM7IGkgKysgKSB7CgkJcmVjdEFyZWFMaWdodCA9IHJlY3RBcmVhTGlnaHRzWyBpIF07CgkJUkVfRGlyZWN0X1JlY3RBcmVhKCByZWN0QXJlYUxpZ2h0LCBnZW9tZXRyeSwgbWF0ZXJpYWwsIHJlZmxlY3RlZExpZ2h0ICk7Cgl9CgkjcHJhZ21hIHVucm9sbF9sb29wX2VuZAojZW5kaWYKI2lmIGRlZmluZWQoIFJFX0luZGlyZWN0RGlmZnVzZSApCgl2ZWMzIGlibElycmFkaWFuY2UgPSB2ZWMzKCAwLjAgKTsKCXZlYzMgaXJyYWRpYW5jZSA9IGdldEFtYmllbnRMaWdodElycmFkaWFuY2UoIGFtYmllbnRMaWdodENvbG9yICk7CglpcnJhZGlhbmNlICs9IGdldExpZ2h0UHJvYmVJcnJhZGlhbmNlKCBsaWdodFByb2JlLCBnZW9tZXRyeS5ub3JtYWwgKTsKCSNpZiAoIE5VTV9IRU1JX0xJR0hUUyA+IDAgKQoJCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCQlmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fSEVNSV9MSUdIVFM7IGkgKysgKSB7CgkJCWlycmFkaWFuY2UgKz0gZ2V0SGVtaXNwaGVyZUxpZ2h0SXJyYWRpYW5jZSggaGVtaXNwaGVyZUxpZ2h0c1sgaSBdLCBnZW9tZXRyeS5ub3JtYWwgKTsKCQl9CgkJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCSNlbmRpZgojZW5kaWYKI2lmIGRlZmluZWQoIFJFX0luZGlyZWN0U3BlY3VsYXIgKQoJdmVjMyByYWRpYW5jZSA9IHZlYzMoIDAuMCApOwoJdmVjMyBjbGVhcmNvYXRSYWRpYW5jZSA9IHZlYzMoIDAuMCApOwojZW5kaWZgLG1iPWAjaWYgZGVmaW5lZCggUkVfSW5kaXJlY3REaWZmdXNlICkKCSNpZmRlZiBVU0VfTElHSFRNQVAKCQl2ZWM0IGxpZ2h0TWFwVGV4ZWwgPSB0ZXh0dXJlMkQoIGxpZ2h0TWFwLCB2VXYyICk7CgkJdmVjMyBsaWdodE1hcElycmFkaWFuY2UgPSBsaWdodE1hcFRleGVsLnJnYiAqIGxpZ2h0TWFwSW50ZW5zaXR5OwoJCSNpZm5kZWYgUEhZU0lDQUxMWV9DT1JSRUNUX0xJR0hUUwoJCQlsaWdodE1hcElycmFkaWFuY2UgKj0gUEk7CgkJI2VuZGlmCgkJaXJyYWRpYW5jZSArPSBsaWdodE1hcElycmFkaWFuY2U7CgkjZW5kaWYKCSNpZiBkZWZpbmVkKCBVU0VfRU5WTUFQICkgJiYgZGVmaW5lZCggU1RBTkRBUkQgKSAmJiBkZWZpbmVkKCBFTlZNQVBfVFlQRV9DVUJFX1VWICkKCQlpYmxJcnJhZGlhbmNlICs9IGdldElCTElycmFkaWFuY2UoIGdlb21ldHJ5Lm5vcm1hbCApOwoJI2VuZGlmCiNlbmRpZgojaWYgZGVmaW5lZCggVVNFX0VOVk1BUCApICYmIGRlZmluZWQoIFJFX0luZGlyZWN0U3BlY3VsYXIgKQoJcmFkaWFuY2UgKz0gZ2V0SUJMUmFkaWFuY2UoIGdlb21ldHJ5LnZpZXdEaXIsIGdlb21ldHJ5Lm5vcm1hbCwgbWF0ZXJpYWwucm91Z2huZXNzICk7CgkjaWZkZWYgVVNFX0NMRUFSQ09BVAoJCWNsZWFyY29hdFJhZGlhbmNlICs9IGdldElCTFJhZGlhbmNlKCBnZW9tZXRyeS52aWV3RGlyLCBnZW9tZXRyeS5jbGVhcmNvYXROb3JtYWwsIG1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcyApOwoJI2VuZGlmCiNlbmRpZmAsZ2I9YCNpZiBkZWZpbmVkKCBSRV9JbmRpcmVjdERpZmZ1c2UgKQoJUkVfSW5kaXJlY3REaWZmdXNlKCBpcnJhZGlhbmNlLCBnZW9tZXRyeSwgbWF0ZXJpYWwsIHJlZmxlY3RlZExpZ2h0ICk7CiNlbmRpZgojaWYgZGVmaW5lZCggUkVfSW5kaXJlY3RTcGVjdWxhciApCglSRV9JbmRpcmVjdFNwZWN1bGFyKCByYWRpYW5jZSwgaWJsSXJyYWRpYW5jZSwgY2xlYXJjb2F0UmFkaWFuY2UsIGdlb21ldHJ5LCBtYXRlcmlhbCwgcmVmbGVjdGVkTGlnaHQgKTsKI2VuZGlmYCx4Yj1gI2lmIGRlZmluZWQoIFVTRV9MT0dERVBUSEJVRiApICYmIGRlZmluZWQoIFVTRV9MT0dERVBUSEJVRl9FWFQgKQoJZ2xfRnJhZ0RlcHRoRVhUID0gdklzUGVyc3BlY3RpdmUgPT0gMC4wID8gZ2xfRnJhZ0Nvb3JkLnogOiBsb2cyKCB2RnJhZ0RlcHRoICkgKiBsb2dEZXB0aEJ1ZkZDICogMC41OwojZW5kaWZgLHliPWAjaWYgZGVmaW5lZCggVVNFX0xPR0RFUFRIQlVGICkgJiYgZGVmaW5lZCggVVNFX0xPR0RFUFRIQlVGX0VYVCApCgl1bmlmb3JtIGZsb2F0IGxvZ0RlcHRoQnVmRkM7Cgl2YXJ5aW5nIGZsb2F0IHZGcmFnRGVwdGg7Cgl2YXJ5aW5nIGZsb2F0IHZJc1BlcnNwZWN0aXZlOwojZW5kaWZgLHZiPWAjaWZkZWYgVVNFX0xPR0RFUFRIQlVGCgkjaWZkZWYgVVNFX0xPR0RFUFRIQlVGX0VYVAoJCXZhcnlpbmcgZmxvYXQgdkZyYWdEZXB0aDsKCQl2YXJ5aW5nIGZsb2F0IHZJc1BlcnNwZWN0aXZlOwoJI2Vsc2UKCQl1bmlmb3JtIGZsb2F0IGxvZ0RlcHRoQnVmRkM7CgkjZW5kaWYKI2VuZGlmYCxfYj1gI2lmZGVmIFVTRV9MT0dERVBUSEJVRgoJI2lmZGVmIFVTRV9MT0dERVBUSEJVRl9FWFQKCQl2RnJhZ0RlcHRoID0gMS4wICsgZ2xfUG9zaXRpb24udzsKCQl2SXNQZXJzcGVjdGl2ZSA9IGZsb2F0KCBpc1BlcnNwZWN0aXZlTWF0cml4KCBwcm9qZWN0aW9uTWF0cml4ICkgKTsKCSNlbHNlCgkJaWYgKCBpc1BlcnNwZWN0aXZlTWF0cml4KCBwcm9qZWN0aW9uTWF0cml4ICkgKSB7CgkJCWdsX1Bvc2l0aW9uLnogPSBsb2cyKCBtYXgoIEVQU0lMT04sIGdsX1Bvc2l0aW9uLncgKyAxLjAgKSApICogbG9nRGVwdGhCdWZGQyAtIDEuMDsKCQkJZ2xfUG9zaXRpb24ueiAqPSBnbF9Qb3NpdGlvbi53OwoJCX0KCSNlbmRpZgojZW5kaWZgLHdiPWAjaWZkZWYgVVNFX01BUAoJdmVjNCBzYW1wbGVkRGlmZnVzZUNvbG9yID0gdGV4dHVyZTJEKCBtYXAsIHZVdiApOwoJI2lmZGVmIERFQ09ERV9WSURFT19URVhUVVJFCgkJc2FtcGxlZERpZmZ1c2VDb2xvciA9IHZlYzQoIG1peCggcG93KCBzYW1wbGVkRGlmZnVzZUNvbG9yLnJnYiAqIDAuOTQ3ODY3Mjk4NiArIHZlYzMoIDAuMDUyMTMyNzAxNCApLCB2ZWMzKCAyLjQgKSApLCBzYW1wbGVkRGlmZnVzZUNvbG9yLnJnYiAqIDAuMDc3Mzk5MzgwOCwgdmVjMyggbGVzc1RoYW5FcXVhbCggc2FtcGxlZERpZmZ1c2VDb2xvci5yZ2IsIHZlYzMoIDAuMDQwNDUgKSApICkgKSwgc2FtcGxlZERpZmZ1c2VDb2xvci53ICk7CgkjZW5kaWYKCWRpZmZ1c2VDb2xvciAqPSBzYW1wbGVkRGlmZnVzZUNvbG9yOwojZW5kaWZgLE1iPWAjaWZkZWYgVVNFX01BUAoJdW5pZm9ybSBzYW1wbGVyMkQgbWFwOwojZW5kaWZgLGJiPWAjaWYgZGVmaW5lZCggVVNFX01BUCApIHx8IGRlZmluZWQoIFVTRV9BTFBIQU1BUCApCgl2ZWMyIHV2ID0gKCB1dlRyYW5zZm9ybSAqIHZlYzMoIGdsX1BvaW50Q29vcmQueCwgMS4wIC0gZ2xfUG9pbnRDb29yZC55LCAxICkgKS54eTsKI2VuZGlmCiNpZmRlZiBVU0VfTUFQCglkaWZmdXNlQ29sb3IgKj0gdGV4dHVyZTJEKCBtYXAsIHV2ICk7CiNlbmRpZgojaWZkZWYgVVNFX0FMUEhBTUFQCglkaWZmdXNlQ29sb3IuYSAqPSB0ZXh0dXJlMkQoIGFscGhhTWFwLCB1diApLmc7CiNlbmRpZmAsU2I9YCNpZiBkZWZpbmVkKCBVU0VfTUFQICkgfHwgZGVmaW5lZCggVVNFX0FMUEhBTUFQICkKCXVuaWZvcm0gbWF0MyB1dlRyYW5zZm9ybTsKI2VuZGlmCiNpZmRlZiBVU0VfTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBtYXA7CiNlbmRpZgojaWZkZWYgVVNFX0FMUEhBTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBhbHBoYU1hcDsKI2VuZGlmYCxFYj1gZmxvYXQgbWV0YWxuZXNzRmFjdG9yID0gbWV0YWxuZXNzOwojaWZkZWYgVVNFX01FVEFMTkVTU01BUAoJdmVjNCB0ZXhlbE1ldGFsbmVzcyA9IHRleHR1cmUyRCggbWV0YWxuZXNzTWFwLCB2VXYgKTsKCW1ldGFsbmVzc0ZhY3RvciAqPSB0ZXhlbE1ldGFsbmVzcy5iOwojZW5kaWZgLFRiPWAjaWZkZWYgVVNFX01FVEFMTkVTU01BUAoJdW5pZm9ybSBzYW1wbGVyMkQgbWV0YWxuZXNzTWFwOwojZW5kaWZgLEFiPWAjaWZkZWYgVVNFX01PUlBITk9STUFMUwoJb2JqZWN0Tm9ybWFsICo9IG1vcnBoVGFyZ2V0QmFzZUluZmx1ZW5jZTsKCSNpZmRlZiBNT1JQSFRBUkdFVFNfVEVYVFVSRQoJCWZvciAoIGludCBpID0gMDsgaSA8IE1PUlBIVEFSR0VUU19DT1VOVDsgaSArKyApIHsKCQkJaWYgKCBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIGkgXSAhPSAwLjAgKSBvYmplY3ROb3JtYWwgKz0gZ2V0TW9ycGgoIGdsX1ZlcnRleElELCBpLCAxLCAyICkgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIGkgXTsKCQl9CgkjZWxzZQoJCW9iamVjdE5vcm1hbCArPSBtb3JwaE5vcm1hbDAgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDAgXTsKCQlvYmplY3ROb3JtYWwgKz0gbW9ycGhOb3JtYWwxICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAxIF07CgkJb2JqZWN0Tm9ybWFsICs9IG1vcnBoTm9ybWFsMiAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgMiBdOwoJCW9iamVjdE5vcm1hbCArPSBtb3JwaE5vcm1hbDMgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDMgXTsKCSNlbmRpZgojZW5kaWZgLENiPWAjaWZkZWYgVVNFX01PUlBIVEFSR0VUUwoJdW5pZm9ybSBmbG9hdCBtb3JwaFRhcmdldEJhc2VJbmZsdWVuY2U7CgkjaWZkZWYgTU9SUEhUQVJHRVRTX1RFWFRVUkUKCQl1bmlmb3JtIGZsb2F0IG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgTU9SUEhUQVJHRVRTX0NPVU5UIF07CgkJdW5pZm9ybSBzYW1wbGVyMkRBcnJheSBtb3JwaFRhcmdldHNUZXh0dXJlOwoJCXVuaWZvcm0gdmVjMiBtb3JwaFRhcmdldHNUZXh0dXJlU2l6ZTsKCQl2ZWMzIGdldE1vcnBoKCBjb25zdCBpbiBpbnQgdmVydGV4SW5kZXgsIGNvbnN0IGluIGludCBtb3JwaFRhcmdldEluZGV4LCBjb25zdCBpbiBpbnQgb2Zmc2V0LCBjb25zdCBpbiBpbnQgc3RyaWRlICkgewoJCQlmbG9hdCB0ZXhlbEluZGV4ID0gZmxvYXQoIHZlcnRleEluZGV4ICogc3RyaWRlICsgb2Zmc2V0ICk7CgkJCWZsb2F0IHkgPSBmbG9vciggdGV4ZWxJbmRleCAvIG1vcnBoVGFyZ2V0c1RleHR1cmVTaXplLnggKTsKCQkJZmxvYXQgeCA9IHRleGVsSW5kZXggLSB5ICogbW9ycGhUYXJnZXRzVGV4dHVyZVNpemUueDsKCQkJdmVjMyBtb3JwaFVWID0gdmVjMyggKCB4ICsgMC41ICkgLyBtb3JwaFRhcmdldHNUZXh0dXJlU2l6ZS54LCB5IC8gbW9ycGhUYXJnZXRzVGV4dHVyZVNpemUueSwgbW9ycGhUYXJnZXRJbmRleCApOwoJCQlyZXR1cm4gdGV4dHVyZSggbW9ycGhUYXJnZXRzVGV4dHVyZSwgbW9ycGhVViApLnh5ejsKCQl9CgkjZWxzZQoJCSNpZm5kZWYgVVNFX01PUlBITk9STUFMUwoJCQl1bmlmb3JtIGZsb2F0IG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgOCBdOwoJCSNlbHNlCgkJCXVuaWZvcm0gZmxvYXQgbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyA0IF07CgkJI2VuZGlmCgkjZW5kaWYKI2VuZGlmYCxSYj1gI2lmZGVmIFVTRV9NT1JQSFRBUkdFVFMKCXRyYW5zZm9ybWVkICo9IG1vcnBoVGFyZ2V0QmFzZUluZmx1ZW5jZTsKCSNpZmRlZiBNT1JQSFRBUkdFVFNfVEVYVFVSRQoJCWZvciAoIGludCBpID0gMDsgaSA8IE1PUlBIVEFSR0VUU19DT1VOVDsgaSArKyApIHsKCQkJI2lmbmRlZiBVU0VfTU9SUEhOT1JNQUxTCgkJCQlpZiAoIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgaSBdICE9IDAuMCApIHRyYW5zZm9ybWVkICs9IGdldE1vcnBoKCBnbF9WZXJ0ZXhJRCwgaSwgMCwgMSApICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyBpIF07CgkJCSNlbHNlCgkJCQlpZiAoIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgaSBdICE9IDAuMCApIHRyYW5zZm9ybWVkICs9IGdldE1vcnBoKCBnbF9WZXJ0ZXhJRCwgaSwgMCwgMiApICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyBpIF07CgkJCSNlbmRpZgoJCX0KCSNlbHNlCgkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQwICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAwIF07CgkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQxICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAxIF07CgkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQyICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAyIF07CgkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQzICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAzIF07CgkJI2lmbmRlZiBVU0VfTU9SUEhOT1JNQUxTCgkJCXRyYW5zZm9ybWVkICs9IG1vcnBoVGFyZ2V0NCAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgNCBdOwoJCQl0cmFuc2Zvcm1lZCArPSBtb3JwaFRhcmdldDUgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDUgXTsKCQkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQ2ICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyA2IF07CgkJCXRyYW5zZm9ybWVkICs9IG1vcnBoVGFyZ2V0NyAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgNyBdOwoJCSNlbmRpZgoJI2VuZGlmCiNlbmRpZmAsTGI9YGZsb2F0IGZhY2VEaXJlY3Rpb24gPSBnbF9Gcm9udEZhY2luZyA/IDEuMCA6IC0gMS4wOwojaWZkZWYgRkxBVF9TSEFERUQKCXZlYzMgZmR4ID0gdmVjMyggZEZkeCggdlZpZXdQb3NpdGlvbi54ICksIGRGZHgoIHZWaWV3UG9zaXRpb24ueSApLCBkRmR4KCB2Vmlld1Bvc2l0aW9uLnogKSApOwoJdmVjMyBmZHkgPSB2ZWMzKCBkRmR5KCB2Vmlld1Bvc2l0aW9uLnggKSwgZEZkeSggdlZpZXdQb3NpdGlvbi55ICksIGRGZHkoIHZWaWV3UG9zaXRpb24ueiApICk7Cgl2ZWMzIG5vcm1hbCA9IG5vcm1hbGl6ZSggY3Jvc3MoIGZkeCwgZmR5ICkgKTsKI2Vsc2UKCXZlYzMgbm9ybWFsID0gbm9ybWFsaXplKCB2Tm9ybWFsICk7CgkjaWZkZWYgRE9VQkxFX1NJREVECgkJbm9ybWFsID0gbm9ybWFsICogZmFjZURpcmVjdGlvbjsKCSNlbmRpZgoJI2lmZGVmIFVTRV9UQU5HRU5UCgkJdmVjMyB0YW5nZW50ID0gbm9ybWFsaXplKCB2VGFuZ2VudCApOwoJCXZlYzMgYml0YW5nZW50ID0gbm9ybWFsaXplKCB2Qml0YW5nZW50ICk7CgkJI2lmZGVmIERPVUJMRV9TSURFRAoJCQl0YW5nZW50ID0gdGFuZ2VudCAqIGZhY2VEaXJlY3Rpb247CgkJCWJpdGFuZ2VudCA9IGJpdGFuZ2VudCAqIGZhY2VEaXJlY3Rpb247CgkJI2VuZGlmCgkJI2lmIGRlZmluZWQoIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAgKSB8fCBkZWZpbmVkKCBVU0VfQ0xFQVJDT0FUX05PUk1BTE1BUCApCgkJCW1hdDMgdlRCTiA9IG1hdDMoIHRhbmdlbnQsIGJpdGFuZ2VudCwgbm9ybWFsICk7CgkJI2VuZGlmCgkjZW5kaWYKI2VuZGlmCnZlYzMgZ2VvbWV0cnlOb3JtYWwgPSBub3JtYWw7YCxQYj1gI2lmZGVmIE9CSkVDVFNQQUNFX05PUk1BTE1BUAoJbm9ybWFsID0gdGV4dHVyZTJEKCBub3JtYWxNYXAsIHZVdiApLnh5eiAqIDIuMCAtIDEuMDsKCSNpZmRlZiBGTElQX1NJREVECgkJbm9ybWFsID0gLSBub3JtYWw7CgkjZW5kaWYKCSNpZmRlZiBET1VCTEVfU0lERUQKCQlub3JtYWwgPSBub3JtYWwgKiBmYWNlRGlyZWN0aW9uOwoJI2VuZGlmCglub3JtYWwgPSBub3JtYWxpemUoIG5vcm1hbE1hdHJpeCAqIG5vcm1hbCApOwojZWxpZiBkZWZpbmVkKCBUQU5HRU5UU1BBQ0VfTk9STUFMTUFQICkKCXZlYzMgbWFwTiA9IHRleHR1cmUyRCggbm9ybWFsTWFwLCB2VXYgKS54eXogKiAyLjAgLSAxLjA7CgltYXBOLnh5ICo9IG5vcm1hbFNjYWxlOwoJI2lmZGVmIFVTRV9UQU5HRU5UCgkJbm9ybWFsID0gbm9ybWFsaXplKCB2VEJOICogbWFwTiApOwoJI2Vsc2UKCQlub3JtYWwgPSBwZXJ0dXJiTm9ybWFsMkFyYiggLSB2Vmlld1Bvc2l0aW9uLCBub3JtYWwsIG1hcE4sIGZhY2VEaXJlY3Rpb24gKTsKCSNlbmRpZgojZWxpZiBkZWZpbmVkKCBVU0VfQlVNUE1BUCApCglub3JtYWwgPSBwZXJ0dXJiTm9ybWFsQXJiKCAtIHZWaWV3UG9zaXRpb24sIG5vcm1hbCwgZEhkeHlfZndkKCksIGZhY2VEaXJlY3Rpb24gKTsKI2VuZGlmYCxEYj1gI2lmbmRlZiBGTEFUX1NIQURFRAoJdmFyeWluZyB2ZWMzIHZOb3JtYWw7CgkjaWZkZWYgVVNFX1RBTkdFTlQKCQl2YXJ5aW5nIHZlYzMgdlRhbmdlbnQ7CgkJdmFyeWluZyB2ZWMzIHZCaXRhbmdlbnQ7CgkjZW5kaWYKI2VuZGlmYCxJYj1gI2lmbmRlZiBGTEFUX1NIQURFRAoJdmFyeWluZyB2ZWMzIHZOb3JtYWw7CgkjaWZkZWYgVVNFX1RBTkdFTlQKCQl2YXJ5aW5nIHZlYzMgdlRhbmdlbnQ7CgkJdmFyeWluZyB2ZWMzIHZCaXRhbmdlbnQ7CgkjZW5kaWYKI2VuZGlmYCxOYj1gI2lmbmRlZiBGTEFUX1NIQURFRAoJdk5vcm1hbCA9IG5vcm1hbGl6ZSggdHJhbnNmb3JtZWROb3JtYWwgKTsKCSNpZmRlZiBVU0VfVEFOR0VOVAoJCXZUYW5nZW50ID0gbm9ybWFsaXplKCB0cmFuc2Zvcm1lZFRhbmdlbnQgKTsKCQl2Qml0YW5nZW50ID0gbm9ybWFsaXplKCBjcm9zcyggdk5vcm1hbCwgdlRhbmdlbnQgKSAqIHRhbmdlbnQudyApOwoJI2VuZGlmCiNlbmRpZmAsRmI9YCNpZmRlZiBVU0VfTk9STUFMTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBub3JtYWxNYXA7Cgl1bmlmb3JtIHZlYzIgbm9ybWFsU2NhbGU7CiNlbmRpZgojaWZkZWYgT0JKRUNUU1BBQ0VfTk9STUFMTUFQCgl1bmlmb3JtIG1hdDMgbm9ybWFsTWF0cml4OwojZW5kaWYKI2lmICEgZGVmaW5lZCAoIFVTRV9UQU5HRU5UICkgJiYgKCBkZWZpbmVkICggVEFOR0VOVFNQQUNFX05PUk1BTE1BUCApIHx8IGRlZmluZWQgKCBVU0VfQ0xFQVJDT0FUX05PUk1BTE1BUCApICkKCXZlYzMgcGVydHVyYk5vcm1hbDJBcmIoIHZlYzMgZXllX3BvcywgdmVjMyBzdXJmX25vcm0sIHZlYzMgbWFwTiwgZmxvYXQgZmFjZURpcmVjdGlvbiApIHsKCQl2ZWMzIHEwID0gdmVjMyggZEZkeCggZXllX3Bvcy54ICksIGRGZHgoIGV5ZV9wb3MueSApLCBkRmR4KCBleWVfcG9zLnogKSApOwoJCXZlYzMgcTEgPSB2ZWMzKCBkRmR5KCBleWVfcG9zLnggKSwgZEZkeSggZXllX3Bvcy55ICksIGRGZHkoIGV5ZV9wb3MueiApICk7CgkJdmVjMiBzdDAgPSBkRmR4KCB2VXYuc3QgKTsKCQl2ZWMyIHN0MSA9IGRGZHkoIHZVdi5zdCApOwoJCXZlYzMgTiA9IHN1cmZfbm9ybTsKCQl2ZWMzIHExcGVycCA9IGNyb3NzKCBxMSwgTiApOwoJCXZlYzMgcTBwZXJwID0gY3Jvc3MoIE4sIHEwICk7CgkJdmVjMyBUID0gcTFwZXJwICogc3QwLnggKyBxMHBlcnAgKiBzdDEueDsKCQl2ZWMzIEIgPSBxMXBlcnAgKiBzdDAueSArIHEwcGVycCAqIHN0MS55OwoJCWZsb2F0IGRldCA9IG1heCggZG90KCBULCBUICksIGRvdCggQiwgQiApICk7CgkJZmxvYXQgc2NhbGUgPSAoIGRldCA9PSAwLjAgKSA/IDAuMCA6IGZhY2VEaXJlY3Rpb24gKiBpbnZlcnNlc3FydCggZGV0ICk7CgkJcmV0dXJuIG5vcm1hbGl6ZSggVCAqICggbWFwTi54ICogc2NhbGUgKSArIEIgKiAoIG1hcE4ueSAqIHNjYWxlICkgKyBOICogbWFwTi56ICk7Cgl9CiNlbmRpZmAsemI9YCNpZmRlZiBVU0VfQ0xFQVJDT0FUCgl2ZWMzIGNsZWFyY29hdE5vcm1hbCA9IGdlb21ldHJ5Tm9ybWFsOwojZW5kaWZgLFViPWAjaWZkZWYgVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVAKCXZlYzMgY2xlYXJjb2F0TWFwTiA9IHRleHR1cmUyRCggY2xlYXJjb2F0Tm9ybWFsTWFwLCB2VXYgKS54eXogKiAyLjAgLSAxLjA7CgljbGVhcmNvYXRNYXBOLnh5ICo9IGNsZWFyY29hdE5vcm1hbFNjYWxlOwoJI2lmZGVmIFVTRV9UQU5HRU5UCgkJY2xlYXJjb2F0Tm9ybWFsID0gbm9ybWFsaXplKCB2VEJOICogY2xlYXJjb2F0TWFwTiApOwoJI2Vsc2UKCQljbGVhcmNvYXROb3JtYWwgPSBwZXJ0dXJiTm9ybWFsMkFyYiggLSB2Vmlld1Bvc2l0aW9uLCBjbGVhcmNvYXROb3JtYWwsIGNsZWFyY29hdE1hcE4sIGZhY2VEaXJlY3Rpb24gKTsKCSNlbmRpZgojZW5kaWZgLEJiPWAjaWZkZWYgVVNFX0NMRUFSQ09BVE1BUAoJdW5pZm9ybSBzYW1wbGVyMkQgY2xlYXJjb2F0TWFwOwojZW5kaWYKI2lmZGVmIFVTRV9DTEVBUkNPQVRfUk9VR0hORVNTTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBjbGVhcmNvYXRSb3VnaG5lc3NNYXA7CiNlbmRpZgojaWZkZWYgVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVAKCXVuaWZvcm0gc2FtcGxlcjJEIGNsZWFyY29hdE5vcm1hbE1hcDsKCXVuaWZvcm0gdmVjMiBjbGVhcmNvYXROb3JtYWxTY2FsZTsKI2VuZGlmYCxPYj1gI2lmZGVmIE9QQVFVRQpkaWZmdXNlQ29sb3IuYSA9IDEuMDsKI2VuZGlmCiNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OCmRpZmZ1c2VDb2xvci5hICo9IHRyYW5zbWlzc2lvbkFscGhhICsgMC4xOwojZW5kaWYKZ2xfRnJhZ0NvbG9yID0gdmVjNCggb3V0Z29pbmdMaWdodCwgZGlmZnVzZUNvbG9yLmEgKTtgLGtiPWB2ZWMzIHBhY2tOb3JtYWxUb1JHQiggY29uc3QgaW4gdmVjMyBub3JtYWwgKSB7CglyZXR1cm4gbm9ybWFsaXplKCBub3JtYWwgKSAqIDAuNSArIDAuNTsKfQp2ZWMzIHVucGFja1JHQlRvTm9ybWFsKCBjb25zdCBpbiB2ZWMzIHJnYiApIHsKCXJldHVybiAyLjAgKiByZ2IueHl6IC0gMS4wOwp9CmNvbnN0IGZsb2F0IFBhY2tVcHNjYWxlID0gMjU2LiAvIDI1NS47Y29uc3QgZmxvYXQgVW5wYWNrRG93bnNjYWxlID0gMjU1LiAvIDI1Ni47CmNvbnN0IHZlYzMgUGFja0ZhY3RvcnMgPSB2ZWMzKCAyNTYuICogMjU2LiAqIDI1Ni4sIDI1Ni4gKiAyNTYuLCAyNTYuICk7CmNvbnN0IHZlYzQgVW5wYWNrRmFjdG9ycyA9IFVucGFja0Rvd25zY2FsZSAvIHZlYzQoIFBhY2tGYWN0b3JzLCAxLiApOwpjb25zdCBmbG9hdCBTaGlmdFJpZ2h0OCA9IDEuIC8gMjU2LjsKdmVjNCBwYWNrRGVwdGhUb1JHQkEoIGNvbnN0IGluIGZsb2F0IHYgKSB7Cgl2ZWM0IHIgPSB2ZWM0KCBmcmFjdCggdiAqIFBhY2tGYWN0b3JzICksIHYgKTsKCXIueXp3IC09IHIueHl6ICogU2hpZnRSaWdodDg7CXJldHVybiByICogUGFja1Vwc2NhbGU7Cn0KZmxvYXQgdW5wYWNrUkdCQVRvRGVwdGgoIGNvbnN0IGluIHZlYzQgdiApIHsKCXJldHVybiBkb3QoIHYsIFVucGFja0ZhY3RvcnMgKTsKfQp2ZWM0IHBhY2sySGFsZlRvUkdCQSggdmVjMiB2ICkgewoJdmVjNCByID0gdmVjNCggdi54LCBmcmFjdCggdi54ICogMjU1LjAgKSwgdi55LCBmcmFjdCggdi55ICogMjU1LjAgKSApOwoJcmV0dXJuIHZlYzQoIHIueCAtIHIueSAvIDI1NS4wLCByLnksIHIueiAtIHIudyAvIDI1NS4wLCByLncgKTsKfQp2ZWMyIHVucGFja1JHQkFUbzJIYWxmKCB2ZWM0IHYgKSB7CglyZXR1cm4gdmVjMiggdi54ICsgKCB2LnkgLyAyNTUuMCApLCB2LnogKyAoIHYudyAvIDI1NS4wICkgKTsKfQpmbG9hdCB2aWV3WlRvT3J0aG9ncmFwaGljRGVwdGgoIGNvbnN0IGluIGZsb2F0IHZpZXdaLCBjb25zdCBpbiBmbG9hdCBuZWFyLCBjb25zdCBpbiBmbG9hdCBmYXIgKSB7CglyZXR1cm4gKCB2aWV3WiArIG5lYXIgKSAvICggbmVhciAtIGZhciApOwp9CmZsb2F0IG9ydGhvZ3JhcGhpY0RlcHRoVG9WaWV3WiggY29uc3QgaW4gZmxvYXQgbGluZWFyQ2xpcFosIGNvbnN0IGluIGZsb2F0IG5lYXIsIGNvbnN0IGluIGZsb2F0IGZhciApIHsKCXJldHVybiBsaW5lYXJDbGlwWiAqICggbmVhciAtIGZhciApIC0gbmVhcjsKfQpmbG9hdCB2aWV3WlRvUGVyc3BlY3RpdmVEZXB0aCggY29uc3QgaW4gZmxvYXQgdmlld1osIGNvbnN0IGluIGZsb2F0IG5lYXIsIGNvbnN0IGluIGZsb2F0IGZhciApIHsKCXJldHVybiAoICggbmVhciArIHZpZXdaICkgKiBmYXIgKSAvICggKCBmYXIgLSBuZWFyICkgKiB2aWV3WiApOwp9CmZsb2F0IHBlcnNwZWN0aXZlRGVwdGhUb1ZpZXdaKCBjb25zdCBpbiBmbG9hdCBpbnZDbGlwWiwgY29uc3QgaW4gZmxvYXQgbmVhciwgY29uc3QgaW4gZmxvYXQgZmFyICkgewoJcmV0dXJuICggbmVhciAqIGZhciApIC8gKCAoIGZhciAtIG5lYXIgKSAqIGludkNsaXBaIC0gZmFyICk7Cn1gLEhiPWAjaWZkZWYgUFJFTVVMVElQTElFRF9BTFBIQQoJZ2xfRnJhZ0NvbG9yLnJnYiAqPSBnbF9GcmFnQ29sb3IuYTsKI2VuZGlmYCxWYj1gdmVjNCBtdlBvc2l0aW9uID0gdmVjNCggdHJhbnNmb3JtZWQsIDEuMCApOwojaWZkZWYgVVNFX0lOU1RBTkNJTkcKCW12UG9zaXRpb24gPSBpbnN0YW5jZU1hdHJpeCAqIG12UG9zaXRpb247CiNlbmRpZgptdlBvc2l0aW9uID0gbW9kZWxWaWV3TWF0cml4ICogbXZQb3NpdGlvbjsKZ2xfUG9zaXRpb24gPSBwcm9qZWN0aW9uTWF0cml4ICogbXZQb3NpdGlvbjtgLEdiPWAjaWZkZWYgRElUSEVSSU5HCglnbF9GcmFnQ29sb3IucmdiID0gZGl0aGVyaW5nKCBnbF9GcmFnQ29sb3IucmdiICk7CiNlbmRpZmAsV2I9YCNpZmRlZiBESVRIRVJJTkcKCXZlYzMgZGl0aGVyaW5nKCB2ZWMzIGNvbG9yICkgewoJCWZsb2F0IGdyaWRfcG9zaXRpb24gPSByYW5kKCBnbF9GcmFnQ29vcmQueHkgKTsKCQl2ZWMzIGRpdGhlcl9zaGlmdF9SR0IgPSB2ZWMzKCAwLjI1IC8gMjU1LjAsIC0wLjI1IC8gMjU1LjAsIDAuMjUgLyAyNTUuMCApOwoJCWRpdGhlcl9zaGlmdF9SR0IgPSBtaXgoIDIuMCAqIGRpdGhlcl9zaGlmdF9SR0IsIC0yLjAgKiBkaXRoZXJfc2hpZnRfUkdCLCBncmlkX3Bvc2l0aW9uICk7CgkJcmV0dXJuIGNvbG9yICsgZGl0aGVyX3NoaWZ0X1JHQjsKCX0KI2VuZGlmYCxxYj1gZmxvYXQgcm91Z2huZXNzRmFjdG9yID0gcm91Z2huZXNzOwojaWZkZWYgVVNFX1JPVUdITkVTU01BUAoJdmVjNCB0ZXhlbFJvdWdobmVzcyA9IHRleHR1cmUyRCggcm91Z2huZXNzTWFwLCB2VXYgKTsKCXJvdWdobmVzc0ZhY3RvciAqPSB0ZXhlbFJvdWdobmVzcy5nOwojZW5kaWZgLFhiPWAjaWZkZWYgVVNFX1JPVUdITkVTU01BUAoJdW5pZm9ybSBzYW1wbGVyMkQgcm91Z2huZXNzTWFwOwojZW5kaWZgLFliPWAjaWZkZWYgVVNFX1NIQURPV01BUAoJI2lmIE5VTV9ESVJfTElHSFRfU0hBRE9XUyA+IDAKCQl1bmlmb3JtIHNhbXBsZXIyRCBkaXJlY3Rpb25hbFNoYWRvd01hcFsgTlVNX0RJUl9MSUdIVF9TSEFET1dTIF07CgkJdmFyeWluZyB2ZWM0IHZEaXJlY3Rpb25hbFNoYWRvd0Nvb3JkWyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTsKCQlzdHJ1Y3QgRGlyZWN0aW9uYWxMaWdodFNoYWRvdyB7CgkJCWZsb2F0IHNoYWRvd0JpYXM7CgkJCWZsb2F0IHNoYWRvd05vcm1hbEJpYXM7CgkJCWZsb2F0IHNoYWRvd1JhZGl1czsKCQkJdmVjMiBzaGFkb3dNYXBTaXplOwoJCX07CgkJdW5pZm9ybSBEaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzWyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTsKCSNlbmRpZgoJI2lmIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwCgkJdW5pZm9ybSBzYW1wbGVyMkQgc3BvdFNoYWRvd01hcFsgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyBdOwoJCXZhcnlpbmcgdmVjNCB2U3BvdFNoYWRvd0Nvb3JkWyBOVU1fU1BPVF9MSUdIVF9TSEFET1dTIF07CgkJc3RydWN0IFNwb3RMaWdodFNoYWRvdyB7CgkJCWZsb2F0IHNoYWRvd0JpYXM7CgkJCWZsb2F0IHNoYWRvd05vcm1hbEJpYXM7CgkJCWZsb2F0IHNoYWRvd1JhZGl1czsKCQkJdmVjMiBzaGFkb3dNYXBTaXplOwoJCX07CgkJdW5pZm9ybSBTcG90TGlnaHRTaGFkb3cgc3BvdExpZ2h0U2hhZG93c1sgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyBdOwoJI2VuZGlmCgkjaWYgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgPiAwCgkJdW5pZm9ybSBzYW1wbGVyMkQgcG9pbnRTaGFkb3dNYXBbIE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTIF07CgkJdmFyeWluZyB2ZWM0IHZQb2ludFNoYWRvd0Nvb3JkWyBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyBdOwoJCXN0cnVjdCBQb2ludExpZ2h0U2hhZG93IHsKCQkJZmxvYXQgc2hhZG93QmlhczsKCQkJZmxvYXQgc2hhZG93Tm9ybWFsQmlhczsKCQkJZmxvYXQgc2hhZG93UmFkaXVzOwoJCQl2ZWMyIHNoYWRvd01hcFNpemU7CgkJCWZsb2F0IHNoYWRvd0NhbWVyYU5lYXI7CgkJCWZsb2F0IHNoYWRvd0NhbWVyYUZhcjsKCQl9OwoJCXVuaWZvcm0gUG9pbnRMaWdodFNoYWRvdyBwb2ludExpZ2h0U2hhZG93c1sgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgXTsKCSNlbmRpZgoJZmxvYXQgdGV4dHVyZTJEQ29tcGFyZSggc2FtcGxlcjJEIGRlcHRocywgdmVjMiB1diwgZmxvYXQgY29tcGFyZSApIHsKCQlyZXR1cm4gc3RlcCggY29tcGFyZSwgdW5wYWNrUkdCQVRvRGVwdGgoIHRleHR1cmUyRCggZGVwdGhzLCB1diApICkgKTsKCX0KCXZlYzIgdGV4dHVyZTJERGlzdHJpYnV0aW9uKCBzYW1wbGVyMkQgc2hhZG93LCB2ZWMyIHV2ICkgewoJCXJldHVybiB1bnBhY2tSR0JBVG8ySGFsZiggdGV4dHVyZTJEKCBzaGFkb3csIHV2ICkgKTsKCX0KCWZsb2F0IFZTTVNoYWRvdyAoc2FtcGxlcjJEIHNoYWRvdywgdmVjMiB1diwgZmxvYXQgY29tcGFyZSApewoJCWZsb2F0IG9jY2x1c2lvbiA9IDEuMDsKCQl2ZWMyIGRpc3RyaWJ1dGlvbiA9IHRleHR1cmUyRERpc3RyaWJ1dGlvbiggc2hhZG93LCB1diApOwoJCWZsb2F0IGhhcmRfc2hhZG93ID0gc3RlcCggY29tcGFyZSAsIGRpc3RyaWJ1dGlvbi54ICk7CgkJaWYgKGhhcmRfc2hhZG93ICE9IDEuMCApIHsKCQkJZmxvYXQgZGlzdGFuY2UgPSBjb21wYXJlIC0gZGlzdHJpYnV0aW9uLnggOwoJCQlmbG9hdCB2YXJpYW5jZSA9IG1heCggMC4wMDAwMCwgZGlzdHJpYnV0aW9uLnkgKiBkaXN0cmlidXRpb24ueSApOwoJCQlmbG9hdCBzb2Z0bmVzc19wcm9iYWJpbGl0eSA9IHZhcmlhbmNlIC8gKHZhcmlhbmNlICsgZGlzdGFuY2UgKiBkaXN0YW5jZSApOwkJCXNvZnRuZXNzX3Byb2JhYmlsaXR5ID0gY2xhbXAoICggc29mdG5lc3NfcHJvYmFiaWxpdHkgLSAwLjMgKSAvICggMC45NSAtIDAuMyApLCAwLjAsIDEuMCApOwkJCW9jY2x1c2lvbiA9IGNsYW1wKCBtYXgoIGhhcmRfc2hhZG93LCBzb2Z0bmVzc19wcm9iYWJpbGl0eSApLCAwLjAsIDEuMCApOwoJCX0KCQlyZXR1cm4gb2NjbHVzaW9uOwoJfQoJZmxvYXQgZ2V0U2hhZG93KCBzYW1wbGVyMkQgc2hhZG93TWFwLCB2ZWMyIHNoYWRvd01hcFNpemUsIGZsb2F0IHNoYWRvd0JpYXMsIGZsb2F0IHNoYWRvd1JhZGl1cywgdmVjNCBzaGFkb3dDb29yZCApIHsKCQlmbG9hdCBzaGFkb3cgPSAxLjA7CgkJc2hhZG93Q29vcmQueHl6IC89IHNoYWRvd0Nvb3JkLnc7CgkJc2hhZG93Q29vcmQueiArPSBzaGFkb3dCaWFzOwoJCWJ2ZWM0IGluRnJ1c3R1bVZlYyA9IGJ2ZWM0ICggc2hhZG93Q29vcmQueCA+PSAwLjAsIHNoYWRvd0Nvb3JkLnggPD0gMS4wLCBzaGFkb3dDb29yZC55ID49IDAuMCwgc2hhZG93Q29vcmQueSA8PSAxLjAgKTsKCQlib29sIGluRnJ1c3R1bSA9IGFsbCggaW5GcnVzdHVtVmVjICk7CgkJYnZlYzIgZnJ1c3R1bVRlc3RWZWMgPSBidmVjMiggaW5GcnVzdHVtLCBzaGFkb3dDb29yZC56IDw9IDEuMCApOwoJCWJvb2wgZnJ1c3R1bVRlc3QgPSBhbGwoIGZydXN0dW1UZXN0VmVjICk7CgkJaWYgKCBmcnVzdHVtVGVzdCApIHsKCQkjaWYgZGVmaW5lZCggU0hBRE9XTUFQX1RZUEVfUENGICkKCQkJdmVjMiB0ZXhlbFNpemUgPSB2ZWMyKCAxLjAgKSAvIHNoYWRvd01hcFNpemU7CgkJCWZsb2F0IGR4MCA9IC0gdGV4ZWxTaXplLnggKiBzaGFkb3dSYWRpdXM7CgkJCWZsb2F0IGR5MCA9IC0gdGV4ZWxTaXplLnkgKiBzaGFkb3dSYWRpdXM7CgkJCWZsb2F0IGR4MSA9ICsgdGV4ZWxTaXplLnggKiBzaGFkb3dSYWRpdXM7CgkJCWZsb2F0IGR5MSA9ICsgdGV4ZWxTaXplLnkgKiBzaGFkb3dSYWRpdXM7CgkJCWZsb2F0IGR4MiA9IGR4MCAvIDIuMDsKCQkJZmxvYXQgZHkyID0gZHkwIC8gMi4wOwoJCQlmbG9hdCBkeDMgPSBkeDEgLyAyLjA7CgkJCWZsb2F0IGR5MyA9IGR5MSAvIDIuMDsKCQkJc2hhZG93ID0gKAoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MCwgZHkwICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTAgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDEsIGR5MCApLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MiwgZHkyICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTIgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDMsIGR5MiApLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MCwgMC4wICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggZHgyLCAwLjAgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggZHgzLCAwLjAgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDEsIDAuMCApLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MiwgZHkzICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTMgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDMsIGR5MyApLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MCwgZHkxICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTEgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDEsIGR5MSApLCBzaGFkb3dDb29yZC56ICkKCQkJKSAqICggMS4wIC8gMTcuMCApOwoJCSNlbGlmIGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1BDRl9TT0ZUICkKCQkJdmVjMiB0ZXhlbFNpemUgPSB2ZWMyKCAxLjAgKSAvIHNoYWRvd01hcFNpemU7CgkJCWZsb2F0IGR4ID0gdGV4ZWxTaXplLng7CgkJCWZsb2F0IGR5ID0gdGV4ZWxTaXplLnk7CgkJCXZlYzIgdXYgPSBzaGFkb3dDb29yZC54eTsKCQkJdmVjMiBmID0gZnJhY3QoIHV2ICogc2hhZG93TWFwU2l6ZSArIDAuNSApOwoJCQl1diAtPSBmICogdGV4ZWxTaXplOwoJCQlzaGFkb3cgPSAoCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2LCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIGR4LCAwLjAgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAwLjAsIGR5ICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdGV4ZWxTaXplLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJbWl4KCB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggLWR4LCAwLjAgKSwgc2hhZG93Q29vcmQueiApLCAKCQkJCQkgdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIDIuMCAqIGR4LCAwLjAgKSwgc2hhZG93Q29vcmQueiApLAoJCQkJCSBmLnggKSArCgkJCQltaXgoIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAtZHgsIGR5ICksIHNoYWRvd0Nvb3JkLnogKSwgCgkJCQkJIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAyLjAgKiBkeCwgZHkgKSwgc2hhZG93Q29vcmQueiApLAoJCQkJCSBmLnggKSArCgkJCQltaXgoIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAwLjAsIC1keSApLCBzaGFkb3dDb29yZC56ICksIAoJCQkJCSB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggMC4wLCAyLjAgKiBkeSApLCBzaGFkb3dDb29yZC56ICksCgkJCQkJIGYueSApICsKCQkJCW1peCggdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIGR4LCAtZHkgKSwgc2hhZG93Q29vcmQueiApLCAKCQkJCQkgdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIGR4LCAyLjAgKiBkeSApLCBzaGFkb3dDb29yZC56ICksCgkJCQkJIGYueSApICsKCQkJCW1peCggbWl4KCB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggLWR4LCAtZHkgKSwgc2hhZG93Q29vcmQueiApLCAKCQkJCQkJICB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggMi4wICogZHgsIC1keSApLCBzaGFkb3dDb29yZC56ICksCgkJCQkJCSAgZi54ICksCgkJCQkJIG1peCggdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIC1keCwgMi4wICogZHkgKSwgc2hhZG93Q29vcmQueiApLCAKCQkJCQkJICB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggMi4wICogZHgsIDIuMCAqIGR5ICksIHNoYWRvd0Nvb3JkLnogKSwKCQkJCQkJICBmLnggKSwKCQkJCQkgZi55ICkKCQkJKSAqICggMS4wIC8gOS4wICk7CgkJI2VsaWYgZGVmaW5lZCggU0hBRE9XTUFQX1RZUEVfVlNNICkKCQkJc2hhZG93ID0gVlNNU2hhZG93KCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5LCBzaGFkb3dDb29yZC56ICk7CgkJI2Vsc2UKCQkJc2hhZG93ID0gdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSwgc2hhZG93Q29vcmQueiApOwoJCSNlbmRpZgoJCX0KCQlyZXR1cm4gc2hhZG93OwoJfQoJdmVjMiBjdWJlVG9VViggdmVjMyB2LCBmbG9hdCB0ZXhlbFNpemVZICkgewoJCXZlYzMgYWJzViA9IGFicyggdiApOwoJCWZsb2F0IHNjYWxlVG9DdWJlID0gMS4wIC8gbWF4KCBhYnNWLngsIG1heCggYWJzVi55LCBhYnNWLnogKSApOwoJCWFic1YgKj0gc2NhbGVUb0N1YmU7CgkJdiAqPSBzY2FsZVRvQ3ViZSAqICggMS4wIC0gMi4wICogdGV4ZWxTaXplWSApOwoJCXZlYzIgcGxhbmFyID0gdi54eTsKCQlmbG9hdCBhbG1vc3RBVGV4ZWwgPSAxLjUgKiB0ZXhlbFNpemVZOwoJCWZsb2F0IGFsbW9zdE9uZSA9IDEuMCAtIGFsbW9zdEFUZXhlbDsKCQlpZiAoIGFic1YueiA+PSBhbG1vc3RPbmUgKSB7CgkJCWlmICggdi56ID4gMC4wICkKCQkJCXBsYW5hci54ID0gNC4wIC0gdi54OwoJCX0gZWxzZSBpZiAoIGFic1YueCA+PSBhbG1vc3RPbmUgKSB7CgkJCWZsb2F0IHNpZ25YID0gc2lnbiggdi54ICk7CgkJCXBsYW5hci54ID0gdi56ICogc2lnblggKyAyLjAgKiBzaWduWDsKCQl9IGVsc2UgaWYgKCBhYnNWLnkgPj0gYWxtb3N0T25lICkgewoJCQlmbG9hdCBzaWduWSA9IHNpZ24oIHYueSApOwoJCQlwbGFuYXIueCA9IHYueCArIDIuMCAqIHNpZ25ZICsgMi4wOwoJCQlwbGFuYXIueSA9IHYueiAqIHNpZ25ZIC0gMi4wOwoJCX0KCQlyZXR1cm4gdmVjMiggMC4xMjUsIDAuMjUgKSAqIHBsYW5hciArIHZlYzIoIDAuMzc1LCAwLjc1ICk7Cgl9CglmbG9hdCBnZXRQb2ludFNoYWRvdyggc2FtcGxlcjJEIHNoYWRvd01hcCwgdmVjMiBzaGFkb3dNYXBTaXplLCBmbG9hdCBzaGFkb3dCaWFzLCBmbG9hdCBzaGFkb3dSYWRpdXMsIHZlYzQgc2hhZG93Q29vcmQsIGZsb2F0IHNoYWRvd0NhbWVyYU5lYXIsIGZsb2F0IHNoYWRvd0NhbWVyYUZhciApIHsKCQl2ZWMyIHRleGVsU2l6ZSA9IHZlYzIoIDEuMCApIC8gKCBzaGFkb3dNYXBTaXplICogdmVjMiggNC4wLCAyLjAgKSApOwoJCXZlYzMgbGlnaHRUb1Bvc2l0aW9uID0gc2hhZG93Q29vcmQueHl6OwoJCWZsb2F0IGRwID0gKCBsZW5ndGgoIGxpZ2h0VG9Qb3NpdGlvbiApIC0gc2hhZG93Q2FtZXJhTmVhciApIC8gKCBzaGFkb3dDYW1lcmFGYXIgLSBzaGFkb3dDYW1lcmFOZWFyICk7CQlkcCArPSBzaGFkb3dCaWFzOwoJCXZlYzMgYmQzRCA9IG5vcm1hbGl6ZSggbGlnaHRUb1Bvc2l0aW9uICk7CgkJI2lmIGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1BDRiApIHx8IGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1BDRl9TT0ZUICkgfHwgZGVmaW5lZCggU0hBRE9XTUFQX1RZUEVfVlNNICkKCQkJdmVjMiBvZmZzZXQgPSB2ZWMyKCAtIDEsIDEgKSAqIHNoYWRvd1JhZGl1cyAqIHRleGVsU2l6ZS55OwoJCQlyZXR1cm4gKAoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC54eXksIHRleGVsU2l6ZS55ICksIGRwICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC55eXksIHRleGVsU2l6ZS55ICksIGRwICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC54eXgsIHRleGVsU2l6ZS55ICksIGRwICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC55eXgsIHRleGVsU2l6ZS55ICksIGRwICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCwgdGV4ZWxTaXplLnkgKSwgZHAgKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnh4eSwgdGV4ZWxTaXplLnkgKSwgZHAgKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnl4eSwgdGV4ZWxTaXplLnkgKSwgZHAgKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnh4eCwgdGV4ZWxTaXplLnkgKSwgZHAgKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnl4eCwgdGV4ZWxTaXplLnkgKSwgZHAgKQoJCQkpICogKCAxLjAgLyA5LjAgKTsKCQkjZWxzZQoJCQlyZXR1cm4gdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCwgdGV4ZWxTaXplLnkgKSwgZHAgKTsKCQkjZW5kaWYKCX0KI2VuZGlmYCxaYj1gI2lmZGVmIFVTRV9TSEFET1dNQVAKCSNpZiBOVU1fRElSX0xJR0hUX1NIQURPV1MgPiAwCgkJdW5pZm9ybSBtYXQ0IGRpcmVjdGlvbmFsU2hhZG93TWF0cml4WyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTsKCQl2YXJ5aW5nIHZlYzQgdkRpcmVjdGlvbmFsU2hhZG93Q29vcmRbIE5VTV9ESVJfTElHSFRfU0hBRE9XUyBdOwoJCXN0cnVjdCBEaXJlY3Rpb25hbExpZ2h0U2hhZG93IHsKCQkJZmxvYXQgc2hhZG93QmlhczsKCQkJZmxvYXQgc2hhZG93Tm9ybWFsQmlhczsKCQkJZmxvYXQgc2hhZG93UmFkaXVzOwoJCQl2ZWMyIHNoYWRvd01hcFNpemU7CgkJfTsKCQl1bmlmb3JtIERpcmVjdGlvbmFsTGlnaHRTaGFkb3cgZGlyZWN0aW9uYWxMaWdodFNoYWRvd3NbIE5VTV9ESVJfTElHSFRfU0hBRE9XUyBdOwoJI2VuZGlmCgkjaWYgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyA+IDAKCQl1bmlmb3JtIG1hdDQgc3BvdFNoYWRvd01hdHJpeFsgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyBdOwoJCXZhcnlpbmcgdmVjNCB2U3BvdFNoYWRvd0Nvb3JkWyBOVU1fU1BPVF9MSUdIVF9TSEFET1dTIF07CgkJc3RydWN0IFNwb3RMaWdodFNoYWRvdyB7CgkJCWZsb2F0IHNoYWRvd0JpYXM7CgkJCWZsb2F0IHNoYWRvd05vcm1hbEJpYXM7CgkJCWZsb2F0IHNoYWRvd1JhZGl1czsKCQkJdmVjMiBzaGFkb3dNYXBTaXplOwoJCX07CgkJdW5pZm9ybSBTcG90TGlnaHRTaGFkb3cgc3BvdExpZ2h0U2hhZG93c1sgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyBdOwoJI2VuZGlmCgkjaWYgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgPiAwCgkJdW5pZm9ybSBtYXQ0IHBvaW50U2hhZG93TWF0cml4WyBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyBdOwoJCXZhcnlpbmcgdmVjNCB2UG9pbnRTaGFkb3dDb29yZFsgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgXTsKCQlzdHJ1Y3QgUG9pbnRMaWdodFNoYWRvdyB7CgkJCWZsb2F0IHNoYWRvd0JpYXM7CgkJCWZsb2F0IHNoYWRvd05vcm1hbEJpYXM7CgkJCWZsb2F0IHNoYWRvd1JhZGl1czsKCQkJdmVjMiBzaGFkb3dNYXBTaXplOwoJCQlmbG9hdCBzaGFkb3dDYW1lcmFOZWFyOwoJCQlmbG9hdCBzaGFkb3dDYW1lcmFGYXI7CgkJfTsKCQl1bmlmb3JtIFBvaW50TGlnaHRTaGFkb3cgcG9pbnRMaWdodFNoYWRvd3NbIE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTIF07CgkjZW5kaWYKI2VuZGlmYCxKYj1gI2lmZGVmIFVTRV9TSEFET1dNQVAKCSNpZiBOVU1fRElSX0xJR0hUX1NIQURPV1MgPiAwIHx8IE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwIHx8IE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTID4gMAoJCXZlYzMgc2hhZG93V29ybGROb3JtYWwgPSBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCB0cmFuc2Zvcm1lZE5vcm1hbCwgdmlld01hdHJpeCApOwoJCXZlYzQgc2hhZG93V29ybGRQb3NpdGlvbjsKCSNlbmRpZgoJI2lmIE5VTV9ESVJfTElHSFRfU0hBRE9XUyA+IDAKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9ESVJfTElHSFRfU0hBRE9XUzsgaSArKyApIHsKCQlzaGFkb3dXb3JsZFBvc2l0aW9uID0gd29ybGRQb3NpdGlvbiArIHZlYzQoIHNoYWRvd1dvcmxkTm9ybWFsICogZGlyZWN0aW9uYWxMaWdodFNoYWRvd3NbIGkgXS5zaGFkb3dOb3JtYWxCaWFzLCAwICk7CgkJdkRpcmVjdGlvbmFsU2hhZG93Q29vcmRbIGkgXSA9IGRpcmVjdGlvbmFsU2hhZG93TWF0cml4WyBpIF0gKiBzaGFkb3dXb3JsZFBvc2l0aW9uOwoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCSNlbmRpZgoJI2lmIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwCgkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0Cglmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fU1BPVF9MSUdIVF9TSEFET1dTOyBpICsrICkgewoJCXNoYWRvd1dvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uICsgdmVjNCggc2hhZG93V29ybGROb3JtYWwgKiBzcG90TGlnaHRTaGFkb3dzWyBpIF0uc2hhZG93Tm9ybWFsQmlhcywgMCApOwoJCXZTcG90U2hhZG93Q29vcmRbIGkgXSA9IHNwb3RTaGFkb3dNYXRyaXhbIGkgXSAqIHNoYWRvd1dvcmxkUG9zaXRpb247Cgl9CgkjcHJhZ21hIHVucm9sbF9sb29wX2VuZAoJI2VuZGlmCgkjaWYgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgPiAwCgkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0Cglmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUzsgaSArKyApIHsKCQlzaGFkb3dXb3JsZFBvc2l0aW9uID0gd29ybGRQb3NpdGlvbiArIHZlYzQoIHNoYWRvd1dvcmxkTm9ybWFsICogcG9pbnRMaWdodFNoYWRvd3NbIGkgXS5zaGFkb3dOb3JtYWxCaWFzLCAwICk7CgkJdlBvaW50U2hhZG93Q29vcmRbIGkgXSA9IHBvaW50U2hhZG93TWF0cml4WyBpIF0gKiBzaGFkb3dXb3JsZFBvc2l0aW9uOwoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCSNlbmRpZgojZW5kaWZgLCRiPWBmbG9hdCBnZXRTaGFkb3dNYXNrKCkgewoJZmxvYXQgc2hhZG93ID0gMS4wOwoJI2lmZGVmIFVTRV9TSEFET1dNQVAKCSNpZiBOVU1fRElSX0xJR0hUX1NIQURPV1MgPiAwCglEaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHQ7CgkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0Cglmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fRElSX0xJR0hUX1NIQURPV1M7IGkgKysgKSB7CgkJZGlyZWN0aW9uYWxMaWdodCA9IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzWyBpIF07CgkJc2hhZG93ICo9IHJlY2VpdmVTaGFkb3cgPyBnZXRTaGFkb3coIGRpcmVjdGlvbmFsU2hhZG93TWFwWyBpIF0sIGRpcmVjdGlvbmFsTGlnaHQuc2hhZG93TWFwU2l6ZSwgZGlyZWN0aW9uYWxMaWdodC5zaGFkb3dCaWFzLCBkaXJlY3Rpb25hbExpZ2h0LnNoYWRvd1JhZGl1cywgdkRpcmVjdGlvbmFsU2hhZG93Q29vcmRbIGkgXSApIDogMS4wOwoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCSNlbmRpZgoJI2lmIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwCglTcG90TGlnaHRTaGFkb3cgc3BvdExpZ2h0OwoJI3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydAoJZm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1NQT1RfTElHSFRfU0hBRE9XUzsgaSArKyApIHsKCQlzcG90TGlnaHQgPSBzcG90TGlnaHRTaGFkb3dzWyBpIF07CgkJc2hhZG93ICo9IHJlY2VpdmVTaGFkb3cgPyBnZXRTaGFkb3coIHNwb3RTaGFkb3dNYXBbIGkgXSwgc3BvdExpZ2h0LnNoYWRvd01hcFNpemUsIHNwb3RMaWdodC5zaGFkb3dCaWFzLCBzcG90TGlnaHQuc2hhZG93UmFkaXVzLCB2U3BvdFNoYWRvd0Nvb3JkWyBpIF0gKSA6IDEuMDsKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCgkjZW5kaWYKCSNpZiBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyA+IDAKCVBvaW50TGlnaHRTaGFkb3cgcG9pbnRMaWdodDsKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTOyBpICsrICkgewoJCXBvaW50TGlnaHQgPSBwb2ludExpZ2h0U2hhZG93c1sgaSBdOwoJCXNoYWRvdyAqPSByZWNlaXZlU2hhZG93ID8gZ2V0UG9pbnRTaGFkb3coIHBvaW50U2hhZG93TWFwWyBpIF0sIHBvaW50TGlnaHQuc2hhZG93TWFwU2l6ZSwgcG9pbnRMaWdodC5zaGFkb3dCaWFzLCBwb2ludExpZ2h0LnNoYWRvd1JhZGl1cywgdlBvaW50U2hhZG93Q29vcmRbIGkgXSwgcG9pbnRMaWdodC5zaGFkb3dDYW1lcmFOZWFyLCBwb2ludExpZ2h0LnNoYWRvd0NhbWVyYUZhciApIDogMS4wOwoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCSNlbmRpZgoJI2VuZGlmCglyZXR1cm4gc2hhZG93Owp9YCxLYj1gI2lmZGVmIFVTRV9TS0lOTklORwoJbWF0NCBib25lTWF0WCA9IGdldEJvbmVNYXRyaXgoIHNraW5JbmRleC54ICk7CgltYXQ0IGJvbmVNYXRZID0gZ2V0Qm9uZU1hdHJpeCggc2tpbkluZGV4LnkgKTsKCW1hdDQgYm9uZU1hdFogPSBnZXRCb25lTWF0cml4KCBza2luSW5kZXgueiApOwoJbWF0NCBib25lTWF0VyA9IGdldEJvbmVNYXRyaXgoIHNraW5JbmRleC53ICk7CiNlbmRpZmAsUWI9YCNpZmRlZiBVU0VfU0tJTk5JTkcKCXVuaWZvcm0gbWF0NCBiaW5kTWF0cml4OwoJdW5pZm9ybSBtYXQ0IGJpbmRNYXRyaXhJbnZlcnNlOwoJI2lmZGVmIEJPTkVfVEVYVFVSRQoJCXVuaWZvcm0gaGlnaHAgc2FtcGxlcjJEIGJvbmVUZXh0dXJlOwoJCXVuaWZvcm0gaW50IGJvbmVUZXh0dXJlU2l6ZTsKCQltYXQ0IGdldEJvbmVNYXRyaXgoIGNvbnN0IGluIGZsb2F0IGkgKSB7CgkJCWZsb2F0IGogPSBpICogNC4wOwoJCQlmbG9hdCB4ID0gbW9kKCBqLCBmbG9hdCggYm9uZVRleHR1cmVTaXplICkgKTsKCQkJZmxvYXQgeSA9IGZsb29yKCBqIC8gZmxvYXQoIGJvbmVUZXh0dXJlU2l6ZSApICk7CgkJCWZsb2F0IGR4ID0gMS4wIC8gZmxvYXQoIGJvbmVUZXh0dXJlU2l6ZSApOwoJCQlmbG9hdCBkeSA9IDEuMCAvIGZsb2F0KCBib25lVGV4dHVyZVNpemUgKTsKCQkJeSA9IGR5ICogKCB5ICsgMC41ICk7CgkJCXZlYzQgdjEgPSB0ZXh0dXJlMkQoIGJvbmVUZXh0dXJlLCB2ZWMyKCBkeCAqICggeCArIDAuNSApLCB5ICkgKTsKCQkJdmVjNCB2MiA9IHRleHR1cmUyRCggYm9uZVRleHR1cmUsIHZlYzIoIGR4ICogKCB4ICsgMS41ICksIHkgKSApOwoJCQl2ZWM0IHYzID0gdGV4dHVyZTJEKCBib25lVGV4dHVyZSwgdmVjMiggZHggKiAoIHggKyAyLjUgKSwgeSApICk7CgkJCXZlYzQgdjQgPSB0ZXh0dXJlMkQoIGJvbmVUZXh0dXJlLCB2ZWMyKCBkeCAqICggeCArIDMuNSApLCB5ICkgKTsKCQkJbWF0NCBib25lID0gbWF0NCggdjEsIHYyLCB2MywgdjQgKTsKCQkJcmV0dXJuIGJvbmU7CgkJfQoJI2Vsc2UKCQl1bmlmb3JtIG1hdDQgYm9uZU1hdHJpY2VzWyBNQVhfQk9ORVMgXTsKCQltYXQ0IGdldEJvbmVNYXRyaXgoIGNvbnN0IGluIGZsb2F0IGkgKSB7CgkJCW1hdDQgYm9uZSA9IGJvbmVNYXRyaWNlc1sgaW50KGkpIF07CgkJCXJldHVybiBib25lOwoJCX0KCSNlbmRpZgojZW5kaWZgLGpiPWAjaWZkZWYgVVNFX1NLSU5OSU5HCgl2ZWM0IHNraW5WZXJ0ZXggPSBiaW5kTWF0cml4ICogdmVjNCggdHJhbnNmb3JtZWQsIDEuMCApOwoJdmVjNCBza2lubmVkID0gdmVjNCggMC4wICk7Cglza2lubmVkICs9IGJvbmVNYXRYICogc2tpblZlcnRleCAqIHNraW5XZWlnaHQueDsKCXNraW5uZWQgKz0gYm9uZU1hdFkgKiBza2luVmVydGV4ICogc2tpbldlaWdodC55OwoJc2tpbm5lZCArPSBib25lTWF0WiAqIHNraW5WZXJ0ZXggKiBza2luV2VpZ2h0Lno7Cglza2lubmVkICs9IGJvbmVNYXRXICogc2tpblZlcnRleCAqIHNraW5XZWlnaHQudzsKCXRyYW5zZm9ybWVkID0gKCBiaW5kTWF0cml4SW52ZXJzZSAqIHNraW5uZWQgKS54eXo7CiNlbmRpZmAsdDE9YCNpZmRlZiBVU0VfU0tJTk5JTkcKCW1hdDQgc2tpbk1hdHJpeCA9IG1hdDQoIDAuMCApOwoJc2tpbk1hdHJpeCArPSBza2luV2VpZ2h0LnggKiBib25lTWF0WDsKCXNraW5NYXRyaXggKz0gc2tpbldlaWdodC55ICogYm9uZU1hdFk7Cglza2luTWF0cml4ICs9IHNraW5XZWlnaHQueiAqIGJvbmVNYXRaOwoJc2tpbk1hdHJpeCArPSBza2luV2VpZ2h0LncgKiBib25lTWF0VzsKCXNraW5NYXRyaXggPSBiaW5kTWF0cml4SW52ZXJzZSAqIHNraW5NYXRyaXggKiBiaW5kTWF0cml4OwoJb2JqZWN0Tm9ybWFsID0gdmVjNCggc2tpbk1hdHJpeCAqIHZlYzQoIG9iamVjdE5vcm1hbCwgMC4wICkgKS54eXo7CgkjaWZkZWYgVVNFX1RBTkdFTlQKCQlvYmplY3RUYW5nZW50ID0gdmVjNCggc2tpbk1hdHJpeCAqIHZlYzQoIG9iamVjdFRhbmdlbnQsIDAuMCApICkueHl6OwoJI2VuZGlmCiNlbmRpZmAsZTE9YGZsb2F0IHNwZWN1bGFyU3RyZW5ndGg7CiNpZmRlZiBVU0VfU1BFQ1VMQVJNQVAKCXZlYzQgdGV4ZWxTcGVjdWxhciA9IHRleHR1cmUyRCggc3BlY3VsYXJNYXAsIHZVdiApOwoJc3BlY3VsYXJTdHJlbmd0aCA9IHRleGVsU3BlY3VsYXIucjsKI2Vsc2UKCXNwZWN1bGFyU3RyZW5ndGggPSAxLjA7CiNlbmRpZmAsbjE9YCNpZmRlZiBVU0VfU1BFQ1VMQVJNQVAKCXVuaWZvcm0gc2FtcGxlcjJEIHNwZWN1bGFyTWFwOwojZW5kaWZgLGkxPWAjaWYgZGVmaW5lZCggVE9ORV9NQVBQSU5HICkKCWdsX0ZyYWdDb2xvci5yZ2IgPSB0b25lTWFwcGluZyggZ2xfRnJhZ0NvbG9yLnJnYiApOwojZW5kaWZgLHIxPWAjaWZuZGVmIHNhdHVyYXRlCiNkZWZpbmUgc2F0dXJhdGUoIGEgKSBjbGFtcCggYSwgMC4wLCAxLjAgKQojZW5kaWYKdW5pZm9ybSBmbG9hdCB0b25lTWFwcGluZ0V4cG9zdXJlOwp2ZWMzIExpbmVhclRvbmVNYXBwaW5nKCB2ZWMzIGNvbG9yICkgewoJcmV0dXJuIHRvbmVNYXBwaW5nRXhwb3N1cmUgKiBjb2xvcjsKfQp2ZWMzIFJlaW5oYXJkVG9uZU1hcHBpbmcoIHZlYzMgY29sb3IgKSB7Cgljb2xvciAqPSB0b25lTWFwcGluZ0V4cG9zdXJlOwoJcmV0dXJuIHNhdHVyYXRlKCBjb2xvciAvICggdmVjMyggMS4wICkgKyBjb2xvciApICk7Cn0KdmVjMyBPcHRpbWl6ZWRDaW5lb25Ub25lTWFwcGluZyggdmVjMyBjb2xvciApIHsKCWNvbG9yICo9IHRvbmVNYXBwaW5nRXhwb3N1cmU7Cgljb2xvciA9IG1heCggdmVjMyggMC4wICksIGNvbG9yIC0gMC4wMDQgKTsKCXJldHVybiBwb3coICggY29sb3IgKiAoIDYuMiAqIGNvbG9yICsgMC41ICkgKSAvICggY29sb3IgKiAoIDYuMiAqIGNvbG9yICsgMS43ICkgKyAwLjA2ICksIHZlYzMoIDIuMiApICk7Cn0KdmVjMyBSUlRBbmRPRFRGaXQoIHZlYzMgdiApIHsKCXZlYzMgYSA9IHYgKiAoIHYgKyAwLjAyNDU3ODYgKSAtIDAuMDAwMDkwNTM3OwoJdmVjMyBiID0gdiAqICggMC45ODM3MjkgKiB2ICsgMC40MzI5NTEwICkgKyAwLjIzODA4MTsKCXJldHVybiBhIC8gYjsKfQp2ZWMzIEFDRVNGaWxtaWNUb25lTWFwcGluZyggdmVjMyBjb2xvciApIHsKCWNvbnN0IG1hdDMgQUNFU0lucHV0TWF0ID0gbWF0MygKCQl2ZWMzKCAwLjU5NzE5LCAwLjA3NjAwLCAwLjAyODQwICksCQl2ZWMzKCAwLjM1NDU4LCAwLjkwODM0LCAwLjEzMzgzICksCgkJdmVjMyggMC4wNDgyMywgMC4wMTU2NiwgMC44Mzc3NyApCgkpOwoJY29uc3QgbWF0MyBBQ0VTT3V0cHV0TWF0ID0gbWF0MygKCQl2ZWMzKCAgMS42MDQ3NSwgLTAuMTAyMDgsIC0wLjAwMzI3ICksCQl2ZWMzKCAtMC41MzEwOCwgIDEuMTA4MTMsIC0wLjA3Mjc2ICksCgkJdmVjMyggLTAuMDczNjcsIC0wLjAwNjA1LCAgMS4wNzYwMiApCgkpOwoJY29sb3IgKj0gdG9uZU1hcHBpbmdFeHBvc3VyZSAvIDAuNjsKCWNvbG9yID0gQUNFU0lucHV0TWF0ICogY29sb3I7Cgljb2xvciA9IFJSVEFuZE9EVEZpdCggY29sb3IgKTsKCWNvbG9yID0gQUNFU091dHB1dE1hdCAqIGNvbG9yOwoJcmV0dXJuIHNhdHVyYXRlKCBjb2xvciApOwp9CnZlYzMgQ3VzdG9tVG9uZU1hcHBpbmcoIHZlYzMgY29sb3IgKSB7IHJldHVybiBjb2xvcjsgfWAsczE9YCNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OCglmbG9hdCB0cmFuc21pc3Npb25BbHBoYSA9IDEuMDsKCWZsb2F0IHRyYW5zbWlzc2lvbkZhY3RvciA9IHRyYW5zbWlzc2lvbjsKCWZsb2F0IHRoaWNrbmVzc0ZhY3RvciA9IHRoaWNrbmVzczsKCSNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OTUFQCgkJdHJhbnNtaXNzaW9uRmFjdG9yICo9IHRleHR1cmUyRCggdHJhbnNtaXNzaW9uTWFwLCB2VXYgKS5yOwoJI2VuZGlmCgkjaWZkZWYgVVNFX1RISUNLTkVTU01BUAoJCXRoaWNrbmVzc0ZhY3RvciAqPSB0ZXh0dXJlMkQoIHRoaWNrbmVzc01hcCwgdlV2ICkuZzsKCSNlbmRpZgoJdmVjMyBwb3MgPSB2V29ybGRQb3NpdGlvbjsKCXZlYzMgdiA9IG5vcm1hbGl6ZSggY2FtZXJhUG9zaXRpb24gLSBwb3MgKTsKCXZlYzMgbiA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIG5vcm1hbCwgdmlld01hdHJpeCApOwoJdmVjNCB0cmFuc21pc3Npb24gPSBnZXRJQkxWb2x1bWVSZWZyYWN0aW9uKAoJCW4sIHYsIHJvdWdobmVzc0ZhY3RvciwgbWF0ZXJpYWwuZGlmZnVzZUNvbG9yLCBtYXRlcmlhbC5zcGVjdWxhckNvbG9yLCBtYXRlcmlhbC5zcGVjdWxhckY5MCwKCQlwb3MsIG1vZGVsTWF0cml4LCB2aWV3TWF0cml4LCBwcm9qZWN0aW9uTWF0cml4LCBpb3IsIHRoaWNrbmVzc0ZhY3RvciwKCQlhdHRlbnVhdGlvbkNvbG9yLCBhdHRlbnVhdGlvbkRpc3RhbmNlICk7Cgl0b3RhbERpZmZ1c2UgPSBtaXgoIHRvdGFsRGlmZnVzZSwgdHJhbnNtaXNzaW9uLnJnYiwgdHJhbnNtaXNzaW9uRmFjdG9yICk7Cgl0cmFuc21pc3Npb25BbHBoYSA9IG1peCggdHJhbnNtaXNzaW9uQWxwaGEsIHRyYW5zbWlzc2lvbi5hLCB0cmFuc21pc3Npb25GYWN0b3IgKTsKI2VuZGlmYCxvMT1gI2lmZGVmIFVTRV9UUkFOU01JU1NJT04KCXVuaWZvcm0gZmxvYXQgdHJhbnNtaXNzaW9uOwoJdW5pZm9ybSBmbG9hdCB0aGlja25lc3M7Cgl1bmlmb3JtIGZsb2F0IGF0dGVudWF0aW9uRGlzdGFuY2U7Cgl1bmlmb3JtIHZlYzMgYXR0ZW51YXRpb25Db2xvcjsKCSNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OTUFQCgkJdW5pZm9ybSBzYW1wbGVyMkQgdHJhbnNtaXNzaW9uTWFwOwoJI2VuZGlmCgkjaWZkZWYgVVNFX1RISUNLTkVTU01BUAoJCXVuaWZvcm0gc2FtcGxlcjJEIHRoaWNrbmVzc01hcDsKCSNlbmRpZgoJdW5pZm9ybSB2ZWMyIHRyYW5zbWlzc2lvblNhbXBsZXJTaXplOwoJdW5pZm9ybSBzYW1wbGVyMkQgdHJhbnNtaXNzaW9uU2FtcGxlck1hcDsKCXVuaWZvcm0gbWF0NCBtb2RlbE1hdHJpeDsKCXVuaWZvcm0gbWF0NCBwcm9qZWN0aW9uTWF0cml4OwoJdmFyeWluZyB2ZWMzIHZXb3JsZFBvc2l0aW9uOwoJdmVjMyBnZXRWb2x1bWVUcmFuc21pc3Npb25SYXkoIGNvbnN0IGluIHZlYzMgbiwgY29uc3QgaW4gdmVjMyB2LCBjb25zdCBpbiBmbG9hdCB0aGlja25lc3MsIGNvbnN0IGluIGZsb2F0IGlvciwgY29uc3QgaW4gbWF0NCBtb2RlbE1hdHJpeCApIHsKCQl2ZWMzIHJlZnJhY3Rpb25WZWN0b3IgPSByZWZyYWN0KCAtIHYsIG5vcm1hbGl6ZSggbiApLCAxLjAgLyBpb3IgKTsKCQl2ZWMzIG1vZGVsU2NhbGU7CgkJbW9kZWxTY2FsZS54ID0gbGVuZ3RoKCB2ZWMzKCBtb2RlbE1hdHJpeFsgMCBdLnh5eiApICk7CgkJbW9kZWxTY2FsZS55ID0gbGVuZ3RoKCB2ZWMzKCBtb2RlbE1hdHJpeFsgMSBdLnh5eiApICk7CgkJbW9kZWxTY2FsZS56ID0gbGVuZ3RoKCB2ZWMzKCBtb2RlbE1hdHJpeFsgMiBdLnh5eiApICk7CgkJcmV0dXJuIG5vcm1hbGl6ZSggcmVmcmFjdGlvblZlY3RvciApICogdGhpY2tuZXNzICogbW9kZWxTY2FsZTsKCX0KCWZsb2F0IGFwcGx5SW9yVG9Sb3VnaG5lc3MoIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcywgY29uc3QgaW4gZmxvYXQgaW9yICkgewoJCXJldHVybiByb3VnaG5lc3MgKiBjbGFtcCggaW9yICogMi4wIC0gMi4wLCAwLjAsIDEuMCApOwoJfQoJdmVjNCBnZXRUcmFuc21pc3Npb25TYW1wbGUoIGNvbnN0IGluIHZlYzIgZnJhZ0Nvb3JkLCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MsIGNvbnN0IGluIGZsb2F0IGlvciApIHsKCQlmbG9hdCBmcmFtZWJ1ZmZlckxvZCA9IGxvZzIoIHRyYW5zbWlzc2lvblNhbXBsZXJTaXplLnggKSAqIGFwcGx5SW9yVG9Sb3VnaG5lc3MoIHJvdWdobmVzcywgaW9yICk7CgkJI2lmZGVmIFRFWFRVUkVfTE9EX0VYVAoJCQlyZXR1cm4gdGV4dHVyZTJETG9kRVhUKCB0cmFuc21pc3Npb25TYW1wbGVyTWFwLCBmcmFnQ29vcmQueHksIGZyYW1lYnVmZmVyTG9kICk7CgkJI2Vsc2UKCQkJcmV0dXJuIHRleHR1cmUyRCggdHJhbnNtaXNzaW9uU2FtcGxlck1hcCwgZnJhZ0Nvb3JkLnh5LCBmcmFtZWJ1ZmZlckxvZCApOwoJCSNlbmRpZgoJfQoJdmVjMyBhcHBseVZvbHVtZUF0dGVudWF0aW9uKCBjb25zdCBpbiB2ZWMzIHJhZGlhbmNlLCBjb25zdCBpbiBmbG9hdCB0cmFuc21pc3Npb25EaXN0YW5jZSwgY29uc3QgaW4gdmVjMyBhdHRlbnVhdGlvbkNvbG9yLCBjb25zdCBpbiBmbG9hdCBhdHRlbnVhdGlvbkRpc3RhbmNlICkgewoJCWlmICggYXR0ZW51YXRpb25EaXN0YW5jZSA9PSAwLjAgKSB7CgkJCXJldHVybiByYWRpYW5jZTsKCQl9IGVsc2UgewoJCQl2ZWMzIGF0dGVudWF0aW9uQ29lZmZpY2llbnQgPSAtbG9nKCBhdHRlbnVhdGlvbkNvbG9yICkgLyBhdHRlbnVhdGlvbkRpc3RhbmNlOwoJCQl2ZWMzIHRyYW5zbWl0dGFuY2UgPSBleHAoIC0gYXR0ZW51YXRpb25Db2VmZmljaWVudCAqIHRyYW5zbWlzc2lvbkRpc3RhbmNlICk7CQkJcmV0dXJuIHRyYW5zbWl0dGFuY2UgKiByYWRpYW5jZTsKCQl9Cgl9Cgl2ZWM0IGdldElCTFZvbHVtZVJlZnJhY3Rpb24oIGNvbnN0IGluIHZlYzMgbiwgY29uc3QgaW4gdmVjMyB2LCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MsIGNvbnN0IGluIHZlYzMgZGlmZnVzZUNvbG9yLAoJCWNvbnN0IGluIHZlYzMgc3BlY3VsYXJDb2xvciwgY29uc3QgaW4gZmxvYXQgc3BlY3VsYXJGOTAsIGNvbnN0IGluIHZlYzMgcG9zaXRpb24sIGNvbnN0IGluIG1hdDQgbW9kZWxNYXRyaXgsCgkJY29uc3QgaW4gbWF0NCB2aWV3TWF0cml4LCBjb25zdCBpbiBtYXQ0IHByb2pNYXRyaXgsIGNvbnN0IGluIGZsb2F0IGlvciwgY29uc3QgaW4gZmxvYXQgdGhpY2tuZXNzLAoJCWNvbnN0IGluIHZlYzMgYXR0ZW51YXRpb25Db2xvciwgY29uc3QgaW4gZmxvYXQgYXR0ZW51YXRpb25EaXN0YW5jZSApIHsKCQl2ZWMzIHRyYW5zbWlzc2lvblJheSA9IGdldFZvbHVtZVRyYW5zbWlzc2lvblJheSggbiwgdiwgdGhpY2tuZXNzLCBpb3IsIG1vZGVsTWF0cml4ICk7CgkJdmVjMyByZWZyYWN0ZWRSYXlFeGl0ID0gcG9zaXRpb24gKyB0cmFuc21pc3Npb25SYXk7CgkJdmVjNCBuZGNQb3MgPSBwcm9qTWF0cml4ICogdmlld01hdHJpeCAqIHZlYzQoIHJlZnJhY3RlZFJheUV4aXQsIDEuMCApOwoJCXZlYzIgcmVmcmFjdGlvbkNvb3JkcyA9IG5kY1Bvcy54eSAvIG5kY1Bvcy53OwoJCXJlZnJhY3Rpb25Db29yZHMgKz0gMS4wOwoJCXJlZnJhY3Rpb25Db29yZHMgLz0gMi4wOwoJCXZlYzQgdHJhbnNtaXR0ZWRMaWdodCA9IGdldFRyYW5zbWlzc2lvblNhbXBsZSggcmVmcmFjdGlvbkNvb3Jkcywgcm91Z2huZXNzLCBpb3IgKTsKCQl2ZWMzIGF0dGVudWF0ZWRDb2xvciA9IGFwcGx5Vm9sdW1lQXR0ZW51YXRpb24oIHRyYW5zbWl0dGVkTGlnaHQucmdiLCBsZW5ndGgoIHRyYW5zbWlzc2lvblJheSApLCBhdHRlbnVhdGlvbkNvbG9yLCBhdHRlbnVhdGlvbkRpc3RhbmNlICk7CgkJdmVjMyBGID0gRW52aXJvbm1lbnRCUkRGKCBuLCB2LCBzcGVjdWxhckNvbG9yLCBzcGVjdWxhckY5MCwgcm91Z2huZXNzICk7CgkJcmV0dXJuIHZlYzQoICggMS4wIC0gRiApICogYXR0ZW51YXRlZENvbG9yICogZGlmZnVzZUNvbG9yLCB0cmFuc21pdHRlZExpZ2h0LmEgKTsKCX0KI2VuZGlmYCxhMT1gI2lmICggZGVmaW5lZCggVVNFX1VWICkgJiYgISBkZWZpbmVkKCBVVlNfVkVSVEVYX09OTFkgKSApCgl2YXJ5aW5nIHZlYzIgdlV2OwojZW5kaWZgLGwxPWAjaWZkZWYgVVNFX1VWCgkjaWZkZWYgVVZTX1ZFUlRFWF9PTkxZCgkJdmVjMiB2VXY7CgkjZWxzZQoJCXZhcnlpbmcgdmVjMiB2VXY7CgkjZW5kaWYKCXVuaWZvcm0gbWF0MyB1dlRyYW5zZm9ybTsKI2VuZGlmYCxjMT1gI2lmZGVmIFVTRV9VVgoJdlV2ID0gKCB1dlRyYW5zZm9ybSAqIHZlYzMoIHV2LCAxICkgKS54eTsKI2VuZGlmYCx1MT1gI2lmIGRlZmluZWQoIFVTRV9MSUdIVE1BUCApIHx8IGRlZmluZWQoIFVTRV9BT01BUCApCgl2YXJ5aW5nIHZlYzIgdlV2MjsKI2VuZGlmYCxoMT1gI2lmIGRlZmluZWQoIFVTRV9MSUdIVE1BUCApIHx8IGRlZmluZWQoIFVTRV9BT01BUCApCglhdHRyaWJ1dGUgdmVjMiB1djI7Cgl2YXJ5aW5nIHZlYzIgdlV2MjsKCXVuaWZvcm0gbWF0MyB1djJUcmFuc2Zvcm07CiNlbmRpZmAsZjE9YCNpZiBkZWZpbmVkKCBVU0VfTElHSFRNQVAgKSB8fCBkZWZpbmVkKCBVU0VfQU9NQVAgKQoJdlV2MiA9ICggdXYyVHJhbnNmb3JtICogdmVjMyggdXYyLCAxICkgKS54eTsKI2VuZGlmYCxkMT1gI2lmIGRlZmluZWQoIFVTRV9FTlZNQVAgKSB8fCBkZWZpbmVkKCBESVNUQU5DRSApIHx8IGRlZmluZWQgKCBVU0VfU0hBRE9XTUFQICkgfHwgZGVmaW5lZCAoIFVTRV9UUkFOU01JU1NJT04gKQoJdmVjNCB3b3JsZFBvc2l0aW9uID0gdmVjNCggdHJhbnNmb3JtZWQsIDEuMCApOwoJI2lmZGVmIFVTRV9JTlNUQU5DSU5HCgkJd29ybGRQb3NpdGlvbiA9IGluc3RhbmNlTWF0cml4ICogd29ybGRQb3NpdGlvbjsKCSNlbmRpZgoJd29ybGRQb3NpdGlvbiA9IG1vZGVsTWF0cml4ICogd29ybGRQb3NpdGlvbjsKI2VuZGlmYCxwMT1gdmFyeWluZyB2ZWMyIHZVdjsKdW5pZm9ybSBtYXQzIHV2VHJhbnNmb3JtOwp2b2lkIG1haW4oKSB7Cgl2VXYgPSAoIHV2VHJhbnNmb3JtICogdmVjMyggdXYsIDEgKSApLnh5OwoJZ2xfUG9zaXRpb24gPSB2ZWM0KCBwb3NpdGlvbi54eSwgMS4wLCAxLjAgKTsKfWAsbTE9YHVuaWZvcm0gc2FtcGxlcjJEIHQyRDsKdmFyeWluZyB2ZWMyIHZVdjsKdm9pZCBtYWluKCkgewoJZ2xfRnJhZ0NvbG9yID0gdGV4dHVyZTJEKCB0MkQsIHZVdiApOwoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KfWAsZzE9YHZhcnlpbmcgdmVjMyB2V29ybGREaXJlY3Rpb247CiNpbmNsdWRlIDxjb21tb24+CnZvaWQgbWFpbigpIHsKCXZXb3JsZERpcmVjdGlvbiA9IHRyYW5zZm9ybURpcmVjdGlvbiggcG9zaXRpb24sIG1vZGVsTWF0cml4ICk7CgkjaW5jbHVkZSA8YmVnaW5fdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJZ2xfUG9zaXRpb24ueiA9IGdsX1Bvc2l0aW9uLnc7Cn1gLHgxPWAjaW5jbHVkZSA8ZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50Pgp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CnZhcnlpbmcgdmVjMyB2V29ybGREaXJlY3Rpb247CiNpbmNsdWRlIDxjdWJlX3V2X3JlZmxlY3Rpb25fZnJhZ21lbnQ+CnZvaWQgbWFpbigpIHsKCXZlYzMgdlJlZmxlY3QgPSB2V29ybGREaXJlY3Rpb247CgkjaW5jbHVkZSA8ZW52bWFwX2ZyYWdtZW50PgoJZ2xfRnJhZ0NvbG9yID0gZW52Q29sb3I7CglnbF9GcmFnQ29sb3IuYSAqPSBvcGFjaXR5OwoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KfWAseTE9YCNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDx1dl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2tpbm5pbmdfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4KdmFyeWluZyB2ZWMyIHZIaWdoUHJlY2lzaW9uWlc7CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDx1dl92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PgoJI2lmZGVmIFVTRV9ESVNQTEFDRU1FTlRNQVAKCQkjaW5jbHVkZSA8YmVnaW5ub3JtYWxfdmVydGV4PgoJCSNpbmNsdWRlIDxtb3JwaG5vcm1hbF92ZXJ0ZXg+CgkJI2luY2x1ZGUgPHNraW5ub3JtYWxfdmVydGV4PgoJI2VuZGlmCgkjaW5jbHVkZSA8YmVnaW5fdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD4KCSNpbmNsdWRlIDxza2lubmluZ192ZXJ0ZXg+CgkjaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3ZlcnRleD4KCSNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD4KCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl92ZXJ0ZXg+CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD4KCXZIaWdoUHJlY2lzaW9uWlcgPSBnbF9Qb3NpdGlvbi56dzsKfWAsdjE9YCNpZiBERVBUSF9QQUNLSU5HID09IDMyMDAKCXVuaWZvcm0gZmxvYXQgb3BhY2l0eTsKI2VuZGlmCiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDxwYWNraW5nPgojaW5jbHVkZSA8dXZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2YXJ5aW5nIHZlYzIgdkhpZ2hQcmVjaXNpb25aVzsKdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD4KCXZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggMS4wICk7CgkjaWYgREVQVEhfUEFDS0lORyA9PSAzMjAwCgkJZGlmZnVzZUNvbG9yLmEgPSBvcGFjaXR5OwoJI2VuZGlmCgkjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD4KCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD4KCWZsb2F0IGZyYWdDb29yZFogPSAwLjUgKiB2SGlnaFByZWNpc2lvblpXWzBdIC8gdkhpZ2hQcmVjaXNpb25aV1sxXSArIDAuNTsKCSNpZiBERVBUSF9QQUNLSU5HID09IDMyMDAKCQlnbF9GcmFnQ29sb3IgPSB2ZWM0KCB2ZWMzKCAxLjAgLSBmcmFnQ29vcmRaICksIG9wYWNpdHkgKTsKCSNlbGlmIERFUFRIX1BBQ0tJTkcgPT0gMzIwMQoJCWdsX0ZyYWdDb2xvciA9IHBhY2tEZXB0aFRvUkdCQSggZnJhZ0Nvb3JkWiApOwoJI2VuZGlmCn1gLF8xPWAjZGVmaW5lIERJU1RBTkNFCnZhcnlpbmcgdmVjMyB2V29ybGRQb3NpdGlvbjsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPHV2X3ZlcnRleD4KCSNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+CgkjaWZkZWYgVVNFX0RJU1BMQUNFTUVOVE1BUAoJCSNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+CgkJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCQkjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+CgkjZW5kaWYKCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD4KCSNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJI2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD4KCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PgoJdldvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uLnh5ejsKfWAsdzE9YCNkZWZpbmUgRElTVEFOQ0UKdW5pZm9ybSB2ZWMzIHJlZmVyZW5jZVBvc2l0aW9uOwp1bmlmb3JtIGZsb2F0IG5lYXJEaXN0YW5jZTsKdW5pZm9ybSBmbG9hdCBmYXJEaXN0YW5jZTsKdmFyeWluZyB2ZWMzIHZXb3JsZFBvc2l0aW9uOwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8cGFja2luZz4KI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4gKCkgewoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD4KCXZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggMS4wICk7CgkjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD4KCWZsb2F0IGRpc3QgPSBsZW5ndGgoIHZXb3JsZFBvc2l0aW9uIC0gcmVmZXJlbmNlUG9zaXRpb24gKTsKCWRpc3QgPSAoIGRpc3QgLSBuZWFyRGlzdGFuY2UgKSAvICggZmFyRGlzdGFuY2UgLSBuZWFyRGlzdGFuY2UgKTsKCWRpc3QgPSBzYXR1cmF0ZSggZGlzdCApOwoJZ2xfRnJhZ0NvbG9yID0gcGFja0RlcHRoVG9SR0JBKCBkaXN0ICk7Cn1gLE0xPWB2YXJ5aW5nIHZlYzMgdldvcmxkRGlyZWN0aW9uOwojaW5jbHVkZSA8Y29tbW9uPgp2b2lkIG1haW4oKSB7Cgl2V29ybGREaXJlY3Rpb24gPSB0cmFuc2Zvcm1EaXJlY3Rpb24oIHBvc2l0aW9uLCBtb2RlbE1hdHJpeCApOwoJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCSNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD4KfWAsYjE9YHVuaWZvcm0gc2FtcGxlcjJEIHRFcXVpcmVjdDsKdmFyeWluZyB2ZWMzIHZXb3JsZERpcmVjdGlvbjsKI2luY2x1ZGUgPGNvbW1vbj4Kdm9pZCBtYWluKCkgewoJdmVjMyBkaXJlY3Rpb24gPSBub3JtYWxpemUoIHZXb3JsZERpcmVjdGlvbiApOwoJdmVjMiBzYW1wbGVVViA9IGVxdWlyZWN0VXYoIGRpcmVjdGlvbiApOwoJZ2xfRnJhZ0NvbG9yID0gdGV4dHVyZTJEKCB0RXF1aXJlY3QsIHNhbXBsZVVWICk7CgkjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZW5jb2RpbmdzX2ZyYWdtZW50Pgp9YCxTMT1gdW5pZm9ybSBmbG9hdCBzY2FsZTsKYXR0cmlidXRlIGZsb2F0IGxpbmVEaXN0YW5jZTsKdmFyeWluZyBmbG9hdCB2TGluZURpc3RhbmNlOwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8Y29sb3JfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxmb2dfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfdmVydGV4Pgp2b2lkIG1haW4oKSB7Cgl2TGluZURpc3RhbmNlID0gc2NhbGUgKiBsaW5lRGlzdGFuY2U7CgkjaW5jbHVkZSA8Y29sb3JfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+CgkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PgoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+CgkjaW5jbHVkZSA8Zm9nX3ZlcnRleD4KfWAsRTE9YHVuaWZvcm0gdmVjMyBkaWZmdXNlOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CnVuaWZvcm0gZmxvYXQgZGFzaFNpemU7CnVuaWZvcm0gZmxvYXQgdG90YWxTaXplOwp2YXJ5aW5nIGZsb2F0IHZMaW5lRGlzdGFuY2U7CiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Zm9nX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+CglpZiAoIG1vZCggdkxpbmVEaXN0YW5jZSwgdG90YWxTaXplICkgPiBkYXNoU2l6ZSApIHsKCQlkaXNjYXJkOwoJfQoJdmVjMyBvdXRnb2luZ0xpZ2h0ID0gdmVjMyggMC4wICk7Cgl2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTsKCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD4KCSNpbmNsdWRlIDxjb2xvcl9mcmFnbWVudD4KCW91dGdvaW5nTGlnaHQgPSBkaWZmdXNlQ29sb3IucmdiOwoJI2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD4KCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+Cn1gLFQxPWAjaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDx1djJfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxlbnZtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2tpbm5pbmdfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPHV2X3ZlcnRleD4KCSNpbmNsdWRlIDx1djJfdmVydGV4PgoJI2luY2x1ZGUgPGNvbG9yX3ZlcnRleD4KCSNpZiBkZWZpbmVkICggVVNFX0VOVk1BUCApIHx8IGRlZmluZWQgKCBVU0VfU0tJTk5JTkcgKQoJCSNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+CgkJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCQkjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PgoJCSNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD4KCQkjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+CgkjZW5kaWYKCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD4KCSNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD4KCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl92ZXJ0ZXg+CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD4KCSNpbmNsdWRlIDx3b3JsZHBvc192ZXJ0ZXg+CgkjaW5jbHVkZSA8ZW52bWFwX3ZlcnRleD4KCSNpbmNsdWRlIDxmb2dfdmVydGV4Pgp9YCxBMT1gdW5pZm9ybSB2ZWMzIGRpZmZ1c2U7CnVuaWZvcm0gZmxvYXQgb3BhY2l0eTsKI2lmbmRlZiBGTEFUX1NIQURFRAoJdmFyeWluZyB2ZWMzIHZOb3JtYWw7CiNlbmRpZgojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8ZGl0aGVyaW5nX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8dXZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHV2Ml9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYW1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGF0ZXN0X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhb21hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bGlnaHRtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGVudm1hcF9jb21tb25fcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGVudm1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y3ViZV91dl9yZWZsZWN0aW9uX2ZyYWdtZW50PgojaW5jbHVkZSA8Zm9nX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxzcGVjdWxhcm1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJdmVjNCBkaWZmdXNlQ29sb3IgPSB2ZWM0KCBkaWZmdXNlLCBvcGFjaXR5ICk7CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGNvbG9yX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD4KCSNpbmNsdWRlIDxzcGVjdWxhcm1hcF9mcmFnbWVudD4KCVJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ID0gUmVmbGVjdGVkTGlnaHQoIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApICk7CgkjaWZkZWYgVVNFX0xJR0hUTUFQCgkJdmVjNCBsaWdodE1hcFRleGVsPSB0ZXh0dXJlMkQoIGxpZ2h0TWFwLCB2VXYyICk7CgkJcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICs9IGxpZ2h0TWFwVGV4ZWwucmdiICogbGlnaHRNYXBJbnRlbnNpdHk7CgkjZWxzZQoJCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSB2ZWMzKCAxLjAgKTsKCSNlbmRpZgoJI2luY2x1ZGUgPGFvbWFwX2ZyYWdtZW50PgoJcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICo9IGRpZmZ1c2VDb2xvci5yZ2I7Cgl2ZWMzIG91dGdvaW5nTGlnaHQgPSByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2U7CgkjaW5jbHVkZSA8ZW52bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD4KCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50Pgp9YCxDMT1gI2RlZmluZSBMQU1CRVJUCnZhcnlpbmcgdmVjMyB2TGlnaHRGcm9udDsKdmFyeWluZyB2ZWMzIHZJbmRpcmVjdEZyb250OwojaWZkZWYgRE9VQkxFX1NJREVECgl2YXJ5aW5nIHZlYzMgdkxpZ2h0QmFjazsKCXZhcnlpbmcgdmVjMyB2SW5kaXJlY3RCYWNrOwojZW5kaWYKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8dXYyX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8ZW52bWFwX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8YnNkZnM+CiNpbmNsdWRlIDxsaWdodHNfcGFyc19iZWdpbj4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfdmVydGV4Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8dXZfdmVydGV4PgoJI2luY2x1ZGUgPHV2Ml92ZXJ0ZXg+CgkjaW5jbHVkZSA8Y29sb3JfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2lubm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGRlZmF1bHRub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD4KCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PgoJI2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD4KCSNpbmNsdWRlIDxlbnZtYXBfdmVydGV4PgoJI2luY2x1ZGUgPGxpZ2h0c19sYW1iZXJ0X3ZlcnRleD4KCSNpbmNsdWRlIDxzaGFkb3dtYXBfdmVydGV4PgoJI2luY2x1ZGUgPGZvZ192ZXJ0ZXg+Cn1gLFIxPWB1bmlmb3JtIHZlYzMgZGlmZnVzZTsKdW5pZm9ybSB2ZWMzIGVtaXNzaXZlOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CnZhcnlpbmcgdmVjMyB2TGlnaHRGcm9udDsKdmFyeWluZyB2ZWMzIHZJbmRpcmVjdEZyb250OwojaWZkZWYgRE9VQkxFX1NJREVECgl2YXJ5aW5nIHZlYzMgdkxpZ2h0QmFjazsKCXZhcnlpbmcgdmVjMyB2SW5kaXJlY3RCYWNrOwojZW5kaWYKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHBhY2tpbmc+CiNpbmNsdWRlIDxkaXRoZXJpbmdfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8dXYyX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFvbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsaWdodG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW1pc3NpdmVtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGVudm1hcF9jb21tb25fcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGVudm1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y3ViZV91dl9yZWZsZWN0aW9uX2ZyYWdtZW50PgojaW5jbHVkZSA8YnNkZnM+CiNpbmNsdWRlIDxsaWdodHNfcGFyc19iZWdpbj4KI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxzaGFkb3dtYXNrX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxzcGVjdWxhcm1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJdmVjNCBkaWZmdXNlQ29sb3IgPSB2ZWM0KCBkaWZmdXNlLCBvcGFjaXR5ICk7CglSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCA9IFJlZmxlY3RlZExpZ2h0KCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSApOwoJdmVjMyB0b3RhbEVtaXNzaXZlUmFkaWFuY2UgPSBlbWlzc2l2ZTsKCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD4KCSNpbmNsdWRlIDxtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGFtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPHNwZWN1bGFybWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVtaXNzaXZlbWFwX2ZyYWdtZW50PgoJI2lmZGVmIERPVUJMRV9TSURFRAoJCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSAoIGdsX0Zyb250RmFjaW5nICkgPyB2SW5kaXJlY3RGcm9udCA6IHZJbmRpcmVjdEJhY2s7CgkjZWxzZQoJCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSB2SW5kaXJlY3RGcm9udDsKCSNlbmRpZgoJI2luY2x1ZGUgPGxpZ2h0bWFwX2ZyYWdtZW50PgoJcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICo9IEJSREZfTGFtYmVydCggZGlmZnVzZUNvbG9yLnJnYiApOwoJI2lmZGVmIERPVUJMRV9TSURFRAoJCXJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgPSAoIGdsX0Zyb250RmFjaW5nICkgPyB2TGlnaHRGcm9udCA6IHZMaWdodEJhY2s7CgkjZWxzZQoJCXJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgPSB2TGlnaHRGcm9udDsKCSNlbmRpZgoJcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSAqPSBCUkRGX0xhbWJlcnQoIGRpZmZ1c2VDb2xvci5yZ2IgKSAqIGdldFNoYWRvd01hc2soKTsKCSNpbmNsdWRlIDxhb21hcF9mcmFnbWVudD4KCXZlYzMgb3V0Z29pbmdMaWdodCA9IHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKyByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKyB0b3RhbEVtaXNzaXZlUmFkaWFuY2U7CgkjaW5jbHVkZSA8ZW52bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD4KCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50Pgp9YCxMMT1gI2RlZmluZSBNQVRDQVAKdmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247CiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDx1dl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bm9ybWFsX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfdmVydGV4Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8dXZfdmVydGV4PgoJI2luY2x1ZGUgPGNvbG9yX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGhub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5iYXNlX3ZlcnRleD4KCSNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxkZWZhdWx0bm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PgoJI2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PgoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+CgkjaW5jbHVkZSA8Zm9nX3ZlcnRleD4KCXZWaWV3UG9zaXRpb24gPSAtIG12UG9zaXRpb24ueHl6Owp9YCxQMT1gI2RlZmluZSBNQVRDQVAKdW5pZm9ybSB2ZWMzIGRpZmZ1c2U7CnVuaWZvcm0gZmxvYXQgb3BhY2l0eTsKdW5pZm9ybSBzYW1wbGVyMkQgbWF0Y2FwOwp2YXJ5aW5nIHZlYzMgdlZpZXdQb3NpdGlvbjsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPGRpdGhlcmluZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y29sb3JfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bm9ybWFsX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxidW1wbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxub3JtYWxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc19mcmFnbWVudD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD4KCXZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggZGlmZnVzZSwgb3BhY2l0eSApOwoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxjb2xvcl9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYXRlc3RfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bm9ybWFsX2ZyYWdtZW50X2JlZ2luPgoJI2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9tYXBzPgoJdmVjMyB2aWV3RGlyID0gbm9ybWFsaXplKCB2Vmlld1Bvc2l0aW9uICk7Cgl2ZWMzIHggPSBub3JtYWxpemUoIHZlYzMoIHZpZXdEaXIueiwgMC4wLCAtIHZpZXdEaXIueCApICk7Cgl2ZWMzIHkgPSBjcm9zcyggdmlld0RpciwgeCApOwoJdmVjMiB1diA9IHZlYzIoIGRvdCggeCwgbm9ybWFsICksIGRvdCggeSwgbm9ybWFsICkgKSAqIDAuNDk1ICsgMC41OwoJI2lmZGVmIFVTRV9NQVRDQVAKCQl2ZWM0IG1hdGNhcENvbG9yID0gdGV4dHVyZTJEKCBtYXRjYXAsIHV2ICk7CgkjZWxzZQoJCXZlYzQgbWF0Y2FwQ29sb3IgPSB2ZWM0KCB2ZWMzKCBtaXgoIDAuMiwgMC44LCB1di55ICkgKSwgMS4wICk7CgkjZW5kaWYKCXZlYzMgb3V0Z29pbmdMaWdodCA9IGRpZmZ1c2VDb2xvci5yZ2IgKiBtYXRjYXBDb2xvci5yZ2I7CgkjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KCSNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8cHJlbXVsdGlwbGllZF9hbHBoYV9mcmFnbWVudD4KCSNpbmNsdWRlIDxkaXRoZXJpbmdfZnJhZ21lbnQ+Cn1gLEQxPWAjZGVmaW5lIE5PUk1BTAojaWYgZGVmaW5lZCggRkxBVF9TSEFERUQgKSB8fCBkZWZpbmVkKCBVU0VfQlVNUE1BUCApIHx8IGRlZmluZWQoIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAgKQoJdmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247CiNlbmRpZgojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxub3JtYWxfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNraW5uaW5nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDx1dl92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD4KCSNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD4KCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PgojaWYgZGVmaW5lZCggRkxBVF9TSEFERUQgKSB8fCBkZWZpbmVkKCBVU0VfQlVNUE1BUCApIHx8IGRlZmluZWQoIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAgKQoJdlZpZXdQb3NpdGlvbiA9IC0gbXZQb3NpdGlvbi54eXo7CiNlbmRpZgp9YCxJMT1gI2RlZmluZSBOT1JNQUwKdW5pZm9ybSBmbG9hdCBvcGFjaXR5OwojaWYgZGVmaW5lZCggRkxBVF9TSEFERUQgKSB8fCBkZWZpbmVkKCBVU0VfQlVNUE1BUCApIHx8IGRlZmluZWQoIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAgKQoJdmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247CiNlbmRpZgojaW5jbHVkZSA8cGFja2luZz4KI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxub3JtYWxfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGJ1bXBtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG5vcm1hbG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9iZWdpbj4KCSNpbmNsdWRlIDxub3JtYWxfZnJhZ21lbnRfbWFwcz4KCWdsX0ZyYWdDb2xvciA9IHZlYzQoIHBhY2tOb3JtYWxUb1JHQiggbm9ybWFsICksIG9wYWNpdHkgKTsKfWAsTjE9YCNkZWZpbmUgUEhPTkcKdmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247CiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDx1dl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHV2Ml9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGVudm1hcF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bm9ybWFsX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfdmVydGV4Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8dXZfdmVydGV4PgoJI2luY2x1ZGUgPHV2Ml92ZXJ0ZXg+CgkjaW5jbHVkZSA8Y29sb3JfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2lubm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGRlZmF1bHRub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5fdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD4KCSNpbmNsdWRlIDxza2lubmluZ192ZXJ0ZXg+CgkjaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3ZlcnRleD4KCSNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD4KCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl92ZXJ0ZXg+CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD4KCXZWaWV3UG9zaXRpb24gPSAtIG12UG9zaXRpb24ueHl6OwoJI2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD4KCSNpbmNsdWRlIDxlbnZtYXBfdmVydGV4PgoJI2luY2x1ZGUgPHNoYWRvd21hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8Zm9nX3ZlcnRleD4KfWAsRjE9YCNkZWZpbmUgUEhPTkcKdW5pZm9ybSB2ZWMzIGRpZmZ1c2U7CnVuaWZvcm0gdmVjMyBlbWlzc2l2ZTsKdW5pZm9ybSB2ZWMzIHNwZWN1bGFyOwp1bmlmb3JtIGZsb2F0IHNoaW5pbmVzczsKdW5pZm9ybSBmbG9hdCBvcGFjaXR5OwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8cGFja2luZz4KI2luY2x1ZGUgPGRpdGhlcmluZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y29sb3JfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDx1djJfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YW9tYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxpZ2h0bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxlbWlzc2l2ZW1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW52bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjdWJlX3V2X3JlZmxlY3Rpb25fZnJhZ21lbnQ+CiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGJzZGZzPgojaW5jbHVkZSA8bGlnaHRzX3BhcnNfYmVnaW4+CiNpbmNsdWRlIDxub3JtYWxfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxpZ2h0c19waG9uZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxidW1wbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxub3JtYWxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHNwZWN1bGFybWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+Cgl2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTsKCVJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ID0gUmVmbGVjdGVkTGlnaHQoIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApICk7Cgl2ZWMzIHRvdGFsRW1pc3NpdmVSYWRpYW5jZSA9IGVtaXNzaXZlOwoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxjb2xvcl9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYXRlc3RfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8c3BlY3VsYXJtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bm9ybWFsX2ZyYWdtZW50X2JlZ2luPgoJI2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9tYXBzPgoJI2luY2x1ZGUgPGVtaXNzaXZlbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGxpZ2h0c19waG9uZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxsaWdodHNfZnJhZ21lbnRfYmVnaW4+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X21hcHM+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X2VuZD4KCSNpbmNsdWRlIDxhb21hcF9mcmFnbWVudD4KCXZlYzMgb3V0Z29pbmdMaWdodCA9IHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKyByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKyByZWZsZWN0ZWRMaWdodC5kaXJlY3RTcGVjdWxhciArIHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0U3BlY3VsYXIgKyB0b3RhbEVtaXNzaXZlUmFkaWFuY2U7CgkjaW5jbHVkZSA8ZW52bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD4KCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50Pgp9YCx6MT1gI2RlZmluZSBTVEFOREFSRAp2YXJ5aW5nIHZlYzMgdlZpZXdQb3NpdGlvbjsKI2lmZGVmIFVTRV9UUkFOU01JU1NJT04KCXZhcnlpbmcgdmVjMyB2V29ybGRQb3NpdGlvbjsKI2VuZGlmCiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDx1dl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHV2Ml9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bm9ybWFsX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfdmVydGV4Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8dXZfdmVydGV4PgoJI2luY2x1ZGUgPHV2Ml92ZXJ0ZXg+CgkjaW5jbHVkZSA8Y29sb3JfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2lubm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGRlZmF1bHRub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5fdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD4KCSNpbmNsdWRlIDxza2lubmluZ192ZXJ0ZXg+CgkjaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3ZlcnRleD4KCSNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD4KCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl92ZXJ0ZXg+CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD4KCXZWaWV3UG9zaXRpb24gPSAtIG12UG9zaXRpb24ueHl6OwoJI2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD4KCSNpbmNsdWRlIDxzaGFkb3dtYXBfdmVydGV4PgoJI2luY2x1ZGUgPGZvZ192ZXJ0ZXg+CiNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OCgl2V29ybGRQb3NpdGlvbiA9IHdvcmxkUG9zaXRpb24ueHl6OwojZW5kaWYKfWAsVTE9YCNkZWZpbmUgU1RBTkRBUkQKI2lmZGVmIFBIWVNJQ0FMCgkjZGVmaW5lIElPUgoJI2RlZmluZSBTUEVDVUxBUgojZW5kaWYKdW5pZm9ybSB2ZWMzIGRpZmZ1c2U7CnVuaWZvcm0gdmVjMyBlbWlzc2l2ZTsKdW5pZm9ybSBmbG9hdCByb3VnaG5lc3M7CnVuaWZvcm0gZmxvYXQgbWV0YWxuZXNzOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CiNpZmRlZiBJT1IKCXVuaWZvcm0gZmxvYXQgaW9yOwojZW5kaWYKI2lmZGVmIFNQRUNVTEFSCgl1bmlmb3JtIGZsb2F0IHNwZWN1bGFySW50ZW5zaXR5OwoJdW5pZm9ybSB2ZWMzIHNwZWN1bGFyQ29sb3I7CgkjaWZkZWYgVVNFX1NQRUNVTEFSSU5URU5TSVRZTUFQCgkJdW5pZm9ybSBzYW1wbGVyMkQgc3BlY3VsYXJJbnRlbnNpdHlNYXA7CgkjZW5kaWYKCSNpZmRlZiBVU0VfU1BFQ1VMQVJDT0xPUk1BUAoJCXVuaWZvcm0gc2FtcGxlcjJEIHNwZWN1bGFyQ29sb3JNYXA7CgkjZW5kaWYKI2VuZGlmCiNpZmRlZiBVU0VfQ0xFQVJDT0FUCgl1bmlmb3JtIGZsb2F0IGNsZWFyY29hdDsKCXVuaWZvcm0gZmxvYXQgY2xlYXJjb2F0Um91Z2huZXNzOwojZW5kaWYKI2lmZGVmIFVTRV9TSEVFTgoJdW5pZm9ybSB2ZWMzIHNoZWVuQ29sb3I7Cgl1bmlmb3JtIGZsb2F0IHNoZWVuUm91Z2huZXNzOwoJI2lmZGVmIFVTRV9TSEVFTkNPTE9STUFQCgkJdW5pZm9ybSBzYW1wbGVyMkQgc2hlZW5Db2xvck1hcDsKCSNlbmRpZgoJI2lmZGVmIFVTRV9TSEVFTlJPVUdITkVTU01BUAoJCXVuaWZvcm0gc2FtcGxlcjJEIHNoZWVuUm91Z2huZXNzTWFwOwoJI2VuZGlmCiNlbmRpZgp2YXJ5aW5nIHZlYzMgdlZpZXdQb3NpdGlvbjsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHBhY2tpbmc+CiNpbmNsdWRlIDxkaXRoZXJpbmdfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8dXYyX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFvbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsaWdodG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW1pc3NpdmVtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGJzZGZzPgojaW5jbHVkZSA8Y3ViZV91dl9yZWZsZWN0aW9uX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW52bWFwX3BoeXNpY2FsX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxpZ2h0c19wYXJzX2JlZ2luPgojaW5jbHVkZSA8bm9ybWFsX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsaWdodHNfcGh5c2ljYWxfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHRyYW5zbWlzc2lvbl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxidW1wbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxub3JtYWxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsZWFyY29hdF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8cm91Z2huZXNzbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxtZXRhbG5lc3NtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc19mcmFnbWVudD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD4KCXZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggZGlmZnVzZSwgb3BhY2l0eSApOwoJUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgPSBSZWZsZWN0ZWRMaWdodCggdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICkgKTsKCXZlYzMgdG90YWxFbWlzc2l2ZVJhZGlhbmNlID0gZW1pc3NpdmU7CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGNvbG9yX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD4KCSNpbmNsdWRlIDxyb3VnaG5lc3NtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bWV0YWxuZXNzbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9iZWdpbj4KCSNpbmNsdWRlIDxub3JtYWxfZnJhZ21lbnRfbWFwcz4KCSNpbmNsdWRlIDxjbGVhcmNvYXRfbm9ybWFsX2ZyYWdtZW50X2JlZ2luPgoJI2luY2x1ZGUgPGNsZWFyY29hdF9ub3JtYWxfZnJhZ21lbnRfbWFwcz4KCSNpbmNsdWRlIDxlbWlzc2l2ZW1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxsaWdodHNfcGh5c2ljYWxfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X2JlZ2luPgoJI2luY2x1ZGUgPGxpZ2h0c19mcmFnbWVudF9tYXBzPgoJI2luY2x1ZGUgPGxpZ2h0c19mcmFnbWVudF9lbmQ+CgkjaW5jbHVkZSA8YW9tYXBfZnJhZ21lbnQ+Cgl2ZWMzIHRvdGFsRGlmZnVzZSA9IHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKyByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2U7Cgl2ZWMzIHRvdGFsU3BlY3VsYXIgPSByZWZsZWN0ZWRMaWdodC5kaXJlY3RTcGVjdWxhciArIHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0U3BlY3VsYXI7CgkjaW5jbHVkZSA8dHJhbnNtaXNzaW9uX2ZyYWdtZW50PgoJdmVjMyBvdXRnb2luZ0xpZ2h0ID0gdG90YWxEaWZmdXNlICsgdG90YWxTcGVjdWxhciArIHRvdGFsRW1pc3NpdmVSYWRpYW5jZTsKCSNpZmRlZiBVU0VfU0hFRU4KCQlmbG9hdCBzaGVlbkVuZXJneUNvbXAgPSAxLjAgLSAwLjE1NyAqIG1heDMoIG1hdGVyaWFsLnNoZWVuQ29sb3IgKTsKCQlvdXRnb2luZ0xpZ2h0ID0gb3V0Z29pbmdMaWdodCAqIHNoZWVuRW5lcmd5Q29tcCArIHNoZWVuU3BlY3VsYXI7CgkjZW5kaWYKCSNpZmRlZiBVU0VfQ0xFQVJDT0FUCgkJZmxvYXQgZG90TlZjYyA9IHNhdHVyYXRlKCBkb3QoIGdlb21ldHJ5LmNsZWFyY29hdE5vcm1hbCwgZ2VvbWV0cnkudmlld0RpciApICk7CgkJdmVjMyBGY2MgPSBGX1NjaGxpY2soIG1hdGVyaWFsLmNsZWFyY29hdEYwLCBtYXRlcmlhbC5jbGVhcmNvYXRGOTAsIGRvdE5WY2MgKTsKCQlvdXRnb2luZ0xpZ2h0ID0gb3V0Z29pbmdMaWdodCAqICggMS4wIC0gbWF0ZXJpYWwuY2xlYXJjb2F0ICogRmNjICkgKyBjbGVhcmNvYXRTcGVjdWxhciAqIG1hdGVyaWFsLmNsZWFyY29hdDsKCSNlbmRpZgoJI2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD4KCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50Pgp9YCxCMT1gI2RlZmluZSBUT09OCnZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uOwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDx1djJfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG5vcm1hbF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2tpbm5pbmdfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxzaGFkb3dtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPHV2X3ZlcnRleD4KCSNpbmNsdWRlIDx1djJfdmVydGV4PgoJI2luY2x1ZGUgPGNvbG9yX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGhub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5iYXNlX3ZlcnRleD4KCSNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxkZWZhdWx0bm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PgoJI2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PgoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+Cgl2Vmlld1Bvc2l0aW9uID0gLSBtdlBvc2l0aW9uLnh5ejsKCSNpbmNsdWRlIDx3b3JsZHBvc192ZXJ0ZXg+CgkjaW5jbHVkZSA8c2hhZG93bWFwX3ZlcnRleD4KCSNpbmNsdWRlIDxmb2dfdmVydGV4Pgp9YCxPMT1gI2RlZmluZSBUT09OCnVuaWZvcm0gdmVjMyBkaWZmdXNlOwp1bmlmb3JtIHZlYzMgZW1pc3NpdmU7CnVuaWZvcm0gZmxvYXQgb3BhY2l0eTsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHBhY2tpbmc+CiNpbmNsdWRlIDxkaXRoZXJpbmdfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8dXYyX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFvbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsaWdodG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW1pc3NpdmVtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGdyYWRpZW50bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGJzZGZzPgojaW5jbHVkZSA8bGlnaHRzX3BhcnNfYmVnaW4+CiNpbmNsdWRlIDxub3JtYWxfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxpZ2h0c190b29uX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxzaGFkb3dtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGJ1bXBtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG5vcm1hbG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJdmVjNCBkaWZmdXNlQ29sb3IgPSB2ZWM0KCBkaWZmdXNlLCBvcGFjaXR5ICk7CglSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCA9IFJlZmxlY3RlZExpZ2h0KCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSApOwoJdmVjMyB0b3RhbEVtaXNzaXZlUmFkaWFuY2UgPSBlbWlzc2l2ZTsKCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD4KCSNpbmNsdWRlIDxtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGFtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9iZWdpbj4KCSNpbmNsdWRlIDxub3JtYWxfZnJhZ21lbnRfbWFwcz4KCSNpbmNsdWRlIDxlbWlzc2l2ZW1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxsaWdodHNfdG9vbl9mcmFnbWVudD4KCSNpbmNsdWRlIDxsaWdodHNfZnJhZ21lbnRfYmVnaW4+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X21hcHM+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X2VuZD4KCSNpbmNsdWRlIDxhb21hcF9mcmFnbWVudD4KCXZlYzMgb3V0Z29pbmdMaWdodCA9IHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKyByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKyB0b3RhbEVtaXNzaXZlUmFkaWFuY2U7CgkjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KCSNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8cHJlbXVsdGlwbGllZF9hbHBoYV9mcmFnbWVudD4KCSNpbmNsdWRlIDxkaXRoZXJpbmdfZnJhZ21lbnQ+Cn1gLGsxPWB1bmlmb3JtIGZsb2F0IHNpemU7CnVuaWZvcm0gZmxvYXQgc2NhbGU7CiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDxjb2xvcl92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5fdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD4KCSNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD4KCWdsX1BvaW50U2l6ZSA9IHNpemU7CgkjaWZkZWYgVVNFX1NJWkVBVFRFTlVBVElPTgoJCWJvb2wgaXNQZXJzcGVjdGl2ZSA9IGlzUGVyc3BlY3RpdmVNYXRyaXgoIHByb2plY3Rpb25NYXRyaXggKTsKCQlpZiAoIGlzUGVyc3BlY3RpdmUgKSBnbF9Qb2ludFNpemUgKj0gKCBzY2FsZSAvIC0gbXZQb3NpdGlvbi56ICk7CgkjZW5kaWYKCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl92ZXJ0ZXg+CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD4KCSNpbmNsdWRlIDx3b3JsZHBvc192ZXJ0ZXg+CgkjaW5jbHVkZSA8Zm9nX3ZlcnRleD4KfWAsSDE9YHVuaWZvcm0gdmVjMyBkaWZmdXNlOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bWFwX3BhcnRpY2xlX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJdmVjMyBvdXRnb2luZ0xpZ2h0ID0gdmVjMyggMC4wICk7Cgl2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTsKCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD4KCSNpbmNsdWRlIDxtYXBfcGFydGljbGVfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PgoJb3V0Z29pbmdMaWdodCA9IGRpZmZ1c2VDb2xvci5yZ2I7CgkjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KCSNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8cHJlbXVsdGlwbGllZF9hbHBoYV9mcmFnbWVudD4KfWAsVjE9YCNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDxmb2dfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNraW5uaW5nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfdmVydGV4Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8YmVnaW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5fdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD4KCSNpbmNsdWRlIDxza2lubmluZ192ZXJ0ZXg+CgkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CgkjaW5jbHVkZSA8d29ybGRwb3NfdmVydGV4PgoJI2luY2x1ZGUgPHNoYWRvd21hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8Zm9nX3ZlcnRleD4KfWAsRzE9YHVuaWZvcm0gdmVjMyBjb2xvcjsKdW5pZm9ybSBmbG9hdCBvcGFjaXR5OwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8cGFja2luZz4KI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YnNkZnM+CiNpbmNsdWRlIDxsaWdodHNfcGFyc19iZWdpbj4KI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8c2hhZG93bWFza19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CglnbF9GcmFnQ29sb3IgPSB2ZWM0KCBjb2xvciwgb3BhY2l0eSAqICggMS4wIC0gZ2V0U2hhZG93TWFzaygpICkgKTsKCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50Pgp9YCxXMT1gdW5pZm9ybSBmbG9hdCByb3RhdGlvbjsKdW5pZm9ybSB2ZWMyIGNlbnRlcjsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDx1dl92ZXJ0ZXg+Cgl2ZWM0IG12UG9zaXRpb24gPSBtb2RlbFZpZXdNYXRyaXggKiB2ZWM0KCAwLjAsIDAuMCwgMC4wLCAxLjAgKTsKCXZlYzIgc2NhbGU7CglzY2FsZS54ID0gbGVuZ3RoKCB2ZWMzKCBtb2RlbE1hdHJpeFsgMCBdLngsIG1vZGVsTWF0cml4WyAwIF0ueSwgbW9kZWxNYXRyaXhbIDAgXS56ICkgKTsKCXNjYWxlLnkgPSBsZW5ndGgoIHZlYzMoIG1vZGVsTWF0cml4WyAxIF0ueCwgbW9kZWxNYXRyaXhbIDEgXS55LCBtb2RlbE1hdHJpeFsgMSBdLnogKSApOwoJI2lmbmRlZiBVU0VfU0laRUFUVEVOVUFUSU9OCgkJYm9vbCBpc1BlcnNwZWN0aXZlID0gaXNQZXJzcGVjdGl2ZU1hdHJpeCggcHJvamVjdGlvbk1hdHJpeCApOwoJCWlmICggaXNQZXJzcGVjdGl2ZSApIHNjYWxlICo9IC0gbXZQb3NpdGlvbi56OwoJI2VuZGlmCgl2ZWMyIGFsaWduZWRQb3NpdGlvbiA9ICggcG9zaXRpb24ueHkgLSAoIGNlbnRlciAtIHZlYzIoIDAuNSApICkgKSAqIHNjYWxlOwoJdmVjMiByb3RhdGVkUG9zaXRpb247Cglyb3RhdGVkUG9zaXRpb24ueCA9IGNvcyggcm90YXRpb24gKSAqIGFsaWduZWRQb3NpdGlvbi54IC0gc2luKCByb3RhdGlvbiApICogYWxpZ25lZFBvc2l0aW9uLnk7Cglyb3RhdGVkUG9zaXRpb24ueSA9IHNpbiggcm90YXRpb24gKSAqIGFsaWduZWRQb3NpdGlvbi54ICsgY29zKCByb3RhdGlvbiApICogYWxpZ25lZFBvc2l0aW9uLnk7CgltdlBvc2l0aW9uLnh5ICs9IHJvdGF0ZWRQb3NpdGlvbjsKCWdsX1Bvc2l0aW9uID0gcHJvamVjdGlvbk1hdHJpeCAqIG12UG9zaXRpb247CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PgoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+CgkjaW5jbHVkZSA8Zm9nX3ZlcnRleD4KfWAscTE9YHVuaWZvcm0gdmVjMyBkaWZmdXNlOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYW1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGF0ZXN0X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc19mcmFnbWVudD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD4KCXZlYzMgb3V0Z29pbmdMaWdodCA9IHZlYzMoIDAuMCApOwoJdmVjNCBkaWZmdXNlQ29sb3IgPSB2ZWM0KCBkaWZmdXNlLCBvcGFjaXR5ICk7CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD4KCW91dGdvaW5nTGlnaHQgPSBkaWZmdXNlQ29sb3IucmdiOwoJI2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD4KCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50Pgp9YCxQdD17YWxwaGFtYXBfZnJhZ21lbnQ6X00sYWxwaGFtYXBfcGFyc19mcmFnbWVudDp3TSxhbHBoYXRlc3RfZnJhZ21lbnQ6TU0sYWxwaGF0ZXN0X3BhcnNfZnJhZ21lbnQ6Yk0sYW9tYXBfZnJhZ21lbnQ6U00sYW9tYXBfcGFyc19mcmFnbWVudDpFTSxiZWdpbl92ZXJ0ZXg6VE0sYmVnaW5ub3JtYWxfdmVydGV4OkFNLGJzZGZzOkNNLGJ1bXBtYXBfcGFyc19mcmFnbWVudDpSTSxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ6TE0sY2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ6UE0sY2xpcHBpbmdfcGxhbmVzX3BhcnNfdmVydGV4OkRNLGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg6SU0sY29sb3JfZnJhZ21lbnQ6Tk0sY29sb3JfcGFyc19mcmFnbWVudDpGTSxjb2xvcl9wYXJzX3ZlcnRleDp6TSxjb2xvcl92ZXJ0ZXg6VU0sY29tbW9uOkJNLGN1YmVfdXZfcmVmbGVjdGlvbl9mcmFnbWVudDpPTSxkZWZhdWx0bm9ybWFsX3ZlcnRleDprTSxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg6SE0sZGlzcGxhY2VtZW50bWFwX3ZlcnRleDpWTSxlbWlzc2l2ZW1hcF9mcmFnbWVudDpHTSxlbWlzc2l2ZW1hcF9wYXJzX2ZyYWdtZW50OldNLGVuY29kaW5nc19mcmFnbWVudDpxTSxlbmNvZGluZ3NfcGFyc19mcmFnbWVudDpYTSxlbnZtYXBfZnJhZ21lbnQ6WU0sZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50OlpNLGVudm1hcF9wYXJzX2ZyYWdtZW50OkpNLGVudm1hcF9wYXJzX3ZlcnRleDokTSxlbnZtYXBfcGh5c2ljYWxfcGFyc19mcmFnbWVudDphYixlbnZtYXBfdmVydGV4OktNLGZvZ192ZXJ0ZXg6UU0sZm9nX3BhcnNfdmVydGV4OmpNLGZvZ19mcmFnbWVudDp0Yixmb2dfcGFyc19mcmFnbWVudDplYixncmFkaWVudG1hcF9wYXJzX2ZyYWdtZW50Om5iLGxpZ2h0bWFwX2ZyYWdtZW50OmliLGxpZ2h0bWFwX3BhcnNfZnJhZ21lbnQ6cmIsbGlnaHRzX2xhbWJlcnRfdmVydGV4OnNiLGxpZ2h0c19wYXJzX2JlZ2luOm9iLGxpZ2h0c190b29uX2ZyYWdtZW50OmxiLGxpZ2h0c190b29uX3BhcnNfZnJhZ21lbnQ6Y2IsbGlnaHRzX3Bob25nX2ZyYWdtZW50OnViLGxpZ2h0c19waG9uZ19wYXJzX2ZyYWdtZW50OmhiLGxpZ2h0c19waHlzaWNhbF9mcmFnbWVudDpmYixsaWdodHNfcGh5c2ljYWxfcGFyc19mcmFnbWVudDpkYixsaWdodHNfZnJhZ21lbnRfYmVnaW46cGIsbGlnaHRzX2ZyYWdtZW50X21hcHM6bWIsbGlnaHRzX2ZyYWdtZW50X2VuZDpnYixsb2dkZXB0aGJ1Zl9mcmFnbWVudDp4Yixsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50OnliLGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4OnZiLGxvZ2RlcHRoYnVmX3ZlcnRleDpfYixtYXBfZnJhZ21lbnQ6d2IsbWFwX3BhcnNfZnJhZ21lbnQ6TWIsbWFwX3BhcnRpY2xlX2ZyYWdtZW50OmJiLG1hcF9wYXJ0aWNsZV9wYXJzX2ZyYWdtZW50OlNiLG1ldGFsbmVzc21hcF9mcmFnbWVudDpFYixtZXRhbG5lc3NtYXBfcGFyc19mcmFnbWVudDpUYixtb3JwaG5vcm1hbF92ZXJ0ZXg6QWIsbW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg6Q2IsbW9ycGh0YXJnZXRfdmVydGV4OlJiLG5vcm1hbF9mcmFnbWVudF9iZWdpbjpMYixub3JtYWxfZnJhZ21lbnRfbWFwczpQYixub3JtYWxfcGFyc19mcmFnbWVudDpEYixub3JtYWxfcGFyc192ZXJ0ZXg6SWIsbm9ybWFsX3ZlcnRleDpOYixub3JtYWxtYXBfcGFyc19mcmFnbWVudDpGYixjbGVhcmNvYXRfbm9ybWFsX2ZyYWdtZW50X2JlZ2luOnpiLGNsZWFyY29hdF9ub3JtYWxfZnJhZ21lbnRfbWFwczpVYixjbGVhcmNvYXRfcGFyc19mcmFnbWVudDpCYixvdXRwdXRfZnJhZ21lbnQ6T2IscGFja2luZzprYixwcmVtdWx0aXBsaWVkX2FscGhhX2ZyYWdtZW50OkhiLHByb2plY3RfdmVydGV4OlZiLGRpdGhlcmluZ19mcmFnbWVudDpHYixkaXRoZXJpbmdfcGFyc19mcmFnbWVudDpXYixyb3VnaG5lc3NtYXBfZnJhZ21lbnQ6cWIscm91Z2huZXNzbWFwX3BhcnNfZnJhZ21lbnQ6WGIsc2hhZG93bWFwX3BhcnNfZnJhZ21lbnQ6WWIsc2hhZG93bWFwX3BhcnNfdmVydGV4OlpiLHNoYWRvd21hcF92ZXJ0ZXg6SmIsc2hhZG93bWFza19wYXJzX2ZyYWdtZW50OiRiLHNraW5iYXNlX3ZlcnRleDpLYixza2lubmluZ19wYXJzX3ZlcnRleDpRYixza2lubmluZ192ZXJ0ZXg6amIsc2tpbm5vcm1hbF92ZXJ0ZXg6dDEsc3BlY3VsYXJtYXBfZnJhZ21lbnQ6ZTEsc3BlY3VsYXJtYXBfcGFyc19mcmFnbWVudDpuMSx0b25lbWFwcGluZ19mcmFnbWVudDppMSx0b25lbWFwcGluZ19wYXJzX2ZyYWdtZW50OnIxLHRyYW5zbWlzc2lvbl9mcmFnbWVudDpzMSx0cmFuc21pc3Npb25fcGFyc19mcmFnbWVudDpvMSx1dl9wYXJzX2ZyYWdtZW50OmExLHV2X3BhcnNfdmVydGV4OmwxLHV2X3ZlcnRleDpjMSx1djJfcGFyc19mcmFnbWVudDp1MSx1djJfcGFyc192ZXJ0ZXg6aDEsdXYyX3ZlcnRleDpmMSx3b3JsZHBvc192ZXJ0ZXg6ZDEsYmFja2dyb3VuZF92ZXJ0OnAxLGJhY2tncm91bmRfZnJhZzptMSxjdWJlX3ZlcnQ6ZzEsY3ViZV9mcmFnOngxLGRlcHRoX3ZlcnQ6eTEsZGVwdGhfZnJhZzp2MSxkaXN0YW5jZVJHQkFfdmVydDpfMSxkaXN0YW5jZVJHQkFfZnJhZzp3MSxlcXVpcmVjdF92ZXJ0Ok0xLGVxdWlyZWN0X2ZyYWc6YjEsbGluZWRhc2hlZF92ZXJ0OlMxLGxpbmVkYXNoZWRfZnJhZzpFMSxtZXNoYmFzaWNfdmVydDpUMSxtZXNoYmFzaWNfZnJhZzpBMSxtZXNobGFtYmVydF92ZXJ0OkMxLG1lc2hsYW1iZXJ0X2ZyYWc6UjEsbWVzaG1hdGNhcF92ZXJ0OkwxLG1lc2htYXRjYXBfZnJhZzpQMSxtZXNobm9ybWFsX3ZlcnQ6RDEsbWVzaG5vcm1hbF9mcmFnOkkxLG1lc2hwaG9uZ192ZXJ0Ok4xLG1lc2hwaG9uZ19mcmFnOkYxLG1lc2hwaHlzaWNhbF92ZXJ0OnoxLG1lc2hwaHlzaWNhbF9mcmFnOlUxLG1lc2h0b29uX3ZlcnQ6QjEsbWVzaHRvb25fZnJhZzpPMSxwb2ludHNfdmVydDprMSxwb2ludHNfZnJhZzpIMSxzaGFkb3dfdmVydDpWMSxzaGFkb3dfZnJhZzpHMSxzcHJpdGVfdmVydDpXMSxzcHJpdGVfZnJhZzpxMX0sb3Q9e2NvbW1vbjp7ZGlmZnVzZTp7dmFsdWU6bmV3IGZ0KDE2Nzc3MjE1KX0sb3BhY2l0eTp7dmFsdWU6MX0sbWFwOnt2YWx1ZTpudWxsfSx1dlRyYW5zZm9ybTp7dmFsdWU6bmV3IGRlfSx1djJUcmFuc2Zvcm06e3ZhbHVlOm5ldyBkZX0sYWxwaGFNYXA6e3ZhbHVlOm51bGx9LGFscGhhVGVzdDp7dmFsdWU6MH19LHNwZWN1bGFybWFwOntzcGVjdWxhck1hcDp7dmFsdWU6bnVsbH19LGVudm1hcDp7ZW52TWFwOnt2YWx1ZTpudWxsfSxmbGlwRW52TWFwOnt2YWx1ZTotMX0scmVmbGVjdGl2aXR5Ont2YWx1ZToxfSxpb3I6e3ZhbHVlOjEuNX0scmVmcmFjdGlvblJhdGlvOnt2YWx1ZTouOTh9fSxhb21hcDp7YW9NYXA6e3ZhbHVlOm51bGx9LGFvTWFwSW50ZW5zaXR5Ont2YWx1ZToxfX0sbGlnaHRtYXA6e2xpZ2h0TWFwOnt2YWx1ZTpudWxsfSxsaWdodE1hcEludGVuc2l0eTp7dmFsdWU6MX19LGVtaXNzaXZlbWFwOntlbWlzc2l2ZU1hcDp7dmFsdWU6bnVsbH19LGJ1bXBtYXA6e2J1bXBNYXA6e3ZhbHVlOm51bGx9LGJ1bXBTY2FsZTp7dmFsdWU6MX19LG5vcm1hbG1hcDp7bm9ybWFsTWFwOnt2YWx1ZTpudWxsfSxub3JtYWxTY2FsZTp7dmFsdWU6bmV3IEsoMSwxKX19LGRpc3BsYWNlbWVudG1hcDp7ZGlzcGxhY2VtZW50TWFwOnt2YWx1ZTpudWxsfSxkaXNwbGFjZW1lbnRTY2FsZTp7dmFsdWU6MX0sZGlzcGxhY2VtZW50Qmlhczp7dmFsdWU6MH19LHJvdWdobmVzc21hcDp7cm91Z2huZXNzTWFwOnt2YWx1ZTpudWxsfX0sbWV0YWxuZXNzbWFwOnttZXRhbG5lc3NNYXA6e3ZhbHVlOm51bGx9fSxncmFkaWVudG1hcDp7Z3JhZGllbnRNYXA6e3ZhbHVlOm51bGx9fSxmb2c6e2ZvZ0RlbnNpdHk6e3ZhbHVlOjI1ZS01fSxmb2dOZWFyOnt2YWx1ZToxfSxmb2dGYXI6e3ZhbHVlOjJlM30sZm9nQ29sb3I6e3ZhbHVlOm5ldyBmdCgxNjc3NzIxNSl9fSxsaWdodHM6e2FtYmllbnRMaWdodENvbG9yOnt2YWx1ZTpbXX0sbGlnaHRQcm9iZTp7dmFsdWU6W119LGRpcmVjdGlvbmFsTGlnaHRzOnt2YWx1ZTpbXSxwcm9wZXJ0aWVzOntkaXJlY3Rpb246e30sY29sb3I6e319fSxkaXJlY3Rpb25hbExpZ2h0U2hhZG93czp7dmFsdWU6W10scHJvcGVydGllczp7c2hhZG93Qmlhczp7fSxzaGFkb3dOb3JtYWxCaWFzOnt9LHNoYWRvd1JhZGl1czp7fSxzaGFkb3dNYXBTaXplOnt9fX0sZGlyZWN0aW9uYWxTaGFkb3dNYXA6e3ZhbHVlOltdfSxkaXJlY3Rpb25hbFNoYWRvd01hdHJpeDp7dmFsdWU6W119LHNwb3RMaWdodHM6e3ZhbHVlOltdLHByb3BlcnRpZXM6e2NvbG9yOnt9LHBvc2l0aW9uOnt9LGRpcmVjdGlvbjp7fSxkaXN0YW5jZTp7fSxjb25lQ29zOnt9LHBlbnVtYnJhQ29zOnt9LGRlY2F5Ont9fX0sc3BvdExpZ2h0U2hhZG93czp7dmFsdWU6W10scHJvcGVydGllczp7c2hhZG93Qmlhczp7fSxzaGFkb3dOb3JtYWxCaWFzOnt9LHNoYWRvd1JhZGl1czp7fSxzaGFkb3dNYXBTaXplOnt9fX0sc3BvdFNoYWRvd01hcDp7dmFsdWU6W119LHNwb3RTaGFkb3dNYXRyaXg6e3ZhbHVlOltdfSxwb2ludExpZ2h0czp7dmFsdWU6W10scHJvcGVydGllczp7Y29sb3I6e30scG9zaXRpb246e30sZGVjYXk6e30sZGlzdGFuY2U6e319fSxwb2ludExpZ2h0U2hhZG93czp7dmFsdWU6W10scHJvcGVydGllczp7c2hhZG93Qmlhczp7fSxzaGFkb3dOb3JtYWxCaWFzOnt9LHNoYWRvd1JhZGl1czp7fSxzaGFkb3dNYXBTaXplOnt9LHNoYWRvd0NhbWVyYU5lYXI6e30sc2hhZG93Q2FtZXJhRmFyOnt9fX0scG9pbnRTaGFkb3dNYXA6e3ZhbHVlOltdfSxwb2ludFNoYWRvd01hdHJpeDp7dmFsdWU6W119LGhlbWlzcGhlcmVMaWdodHM6e3ZhbHVlOltdLHByb3BlcnRpZXM6e2RpcmVjdGlvbjp7fSxza3lDb2xvcjp7fSxncm91bmRDb2xvcjp7fX19LHJlY3RBcmVhTGlnaHRzOnt2YWx1ZTpbXSxwcm9wZXJ0aWVzOntjb2xvcjp7fSxwb3NpdGlvbjp7fSx3aWR0aDp7fSxoZWlnaHQ6e319fSxsdGNfMTp7dmFsdWU6bnVsbH0sbHRjXzI6e3ZhbHVlOm51bGx9fSxwb2ludHM6e2RpZmZ1c2U6e3ZhbHVlOm5ldyBmdCgxNjc3NzIxNSl9LG9wYWNpdHk6e3ZhbHVlOjF9LHNpemU6e3ZhbHVlOjF9LHNjYWxlOnt2YWx1ZToxfSxtYXA6e3ZhbHVlOm51bGx9LGFscGhhTWFwOnt2YWx1ZTpudWxsfSxhbHBoYVRlc3Q6e3ZhbHVlOjB9LHV2VHJhbnNmb3JtOnt2YWx1ZTpuZXcgZGV9fSxzcHJpdGU6e2RpZmZ1c2U6e3ZhbHVlOm5ldyBmdCgxNjc3NzIxNSl9LG9wYWNpdHk6e3ZhbHVlOjF9LGNlbnRlcjp7dmFsdWU6bmV3IEsoLjUsLjUpfSxyb3RhdGlvbjp7dmFsdWU6MH0sbWFwOnt2YWx1ZTpudWxsfSxhbHBoYU1hcDp7dmFsdWU6bnVsbH0sYWxwaGFUZXN0Ont2YWx1ZTowfSx1dlRyYW5zZm9ybTp7dmFsdWU6bmV3IGRlfX19LGZuPXtiYXNpYzp7dW5pZm9ybXM6TWUoW290LmNvbW1vbixvdC5zcGVjdWxhcm1hcCxvdC5lbnZtYXAsb3QuYW9tYXAsb3QubGlnaHRtYXAsb3QuZm9nXSksdmVydGV4U2hhZGVyOlB0Lm1lc2hiYXNpY192ZXJ0LGZyYWdtZW50U2hhZGVyOlB0Lm1lc2hiYXNpY19mcmFnfSxsYW1iZXJ0Ont1bmlmb3JtczpNZShbb3QuY29tbW9uLG90LnNwZWN1bGFybWFwLG90LmVudm1hcCxvdC5hb21hcCxvdC5saWdodG1hcCxvdC5lbWlzc2l2ZW1hcCxvdC5mb2csb3QubGlnaHRzLHtlbWlzc2l2ZTp7dmFsdWU6bmV3IGZ0KDApfX1dKSx2ZXJ0ZXhTaGFkZXI6UHQubWVzaGxhbWJlcnRfdmVydCxmcmFnbWVudFNoYWRlcjpQdC5tZXNobGFtYmVydF9mcmFnfSxwaG9uZzp7dW5pZm9ybXM6TWUoW290LmNvbW1vbixvdC5zcGVjdWxhcm1hcCxvdC5lbnZtYXAsb3QuYW9tYXAsb3QubGlnaHRtYXAsb3QuZW1pc3NpdmVtYXAsb3QuYnVtcG1hcCxvdC5ub3JtYWxtYXAsb3QuZGlzcGxhY2VtZW50bWFwLG90LmZvZyxvdC5saWdodHMse2VtaXNzaXZlOnt2YWx1ZTpuZXcgZnQoMCl9LHNwZWN1bGFyOnt2YWx1ZTpuZXcgZnQoMTExODQ4MSl9LHNoaW5pbmVzczp7dmFsdWU6MzB9fV0pLHZlcnRleFNoYWRlcjpQdC5tZXNocGhvbmdfdmVydCxmcmFnbWVudFNoYWRlcjpQdC5tZXNocGhvbmdfZnJhZ30sc3RhbmRhcmQ6e3VuaWZvcm1zOk1lKFtvdC5jb21tb24sb3QuZW52bWFwLG90LmFvbWFwLG90LmxpZ2h0bWFwLG90LmVtaXNzaXZlbWFwLG90LmJ1bXBtYXAsb3Qubm9ybWFsbWFwLG90LmRpc3BsYWNlbWVudG1hcCxvdC5yb3VnaG5lc3NtYXAsb3QubWV0YWxuZXNzbWFwLG90LmZvZyxvdC5saWdodHMse2VtaXNzaXZlOnt2YWx1ZTpuZXcgZnQoMCl9LHJvdWdobmVzczp7dmFsdWU6MX0sbWV0YWxuZXNzOnt2YWx1ZTowfSxlbnZNYXBJbnRlbnNpdHk6e3ZhbHVlOjF9fV0pLHZlcnRleFNoYWRlcjpQdC5tZXNocGh5c2ljYWxfdmVydCxmcmFnbWVudFNoYWRlcjpQdC5tZXNocGh5c2ljYWxfZnJhZ30sdG9vbjp7dW5pZm9ybXM6TWUoW290LmNvbW1vbixvdC5hb21hcCxvdC5saWdodG1hcCxvdC5lbWlzc2l2ZW1hcCxvdC5idW1wbWFwLG90Lm5vcm1hbG1hcCxvdC5kaXNwbGFjZW1lbnRtYXAsb3QuZ3JhZGllbnRtYXAsb3QuZm9nLG90LmxpZ2h0cyx7ZW1pc3NpdmU6e3ZhbHVlOm5ldyBmdCgwKX19XSksdmVydGV4U2hhZGVyOlB0Lm1lc2h0b29uX3ZlcnQsZnJhZ21lbnRTaGFkZXI6UHQubWVzaHRvb25fZnJhZ30sbWF0Y2FwOnt1bmlmb3JtczpNZShbb3QuY29tbW9uLG90LmJ1bXBtYXAsb3Qubm9ybWFsbWFwLG90LmRpc3BsYWNlbWVudG1hcCxvdC5mb2cse21hdGNhcDp7dmFsdWU6bnVsbH19XSksdmVydGV4U2hhZGVyOlB0Lm1lc2htYXRjYXBfdmVydCxmcmFnbWVudFNoYWRlcjpQdC5tZXNobWF0Y2FwX2ZyYWd9LHBvaW50czp7dW5pZm9ybXM6TWUoW290LnBvaW50cyxvdC5mb2ddKSx2ZXJ0ZXhTaGFkZXI6UHQucG9pbnRzX3ZlcnQsZnJhZ21lbnRTaGFkZXI6UHQucG9pbnRzX2ZyYWd9LGRhc2hlZDp7dW5pZm9ybXM6TWUoW290LmNvbW1vbixvdC5mb2cse3NjYWxlOnt2YWx1ZToxfSxkYXNoU2l6ZTp7dmFsdWU6MX0sdG90YWxTaXplOnt2YWx1ZToyfX1dKSx2ZXJ0ZXhTaGFkZXI6UHQubGluZWRhc2hlZF92ZXJ0LGZyYWdtZW50U2hhZGVyOlB0LmxpbmVkYXNoZWRfZnJhZ30sZGVwdGg6e3VuaWZvcm1zOk1lKFtvdC5jb21tb24sb3QuZGlzcGxhY2VtZW50bWFwXSksdmVydGV4U2hhZGVyOlB0LmRlcHRoX3ZlcnQsZnJhZ21lbnRTaGFkZXI6UHQuZGVwdGhfZnJhZ30sbm9ybWFsOnt1bmlmb3JtczpNZShbb3QuY29tbW9uLG90LmJ1bXBtYXAsb3Qubm9ybWFsbWFwLG90LmRpc3BsYWNlbWVudG1hcCx7b3BhY2l0eTp7dmFsdWU6MX19XSksdmVydGV4U2hhZGVyOlB0Lm1lc2hub3JtYWxfdmVydCxmcmFnbWVudFNoYWRlcjpQdC5tZXNobm9ybWFsX2ZyYWd9LHNwcml0ZTp7dW5pZm9ybXM6TWUoW290LnNwcml0ZSxvdC5mb2ddKSx2ZXJ0ZXhTaGFkZXI6UHQuc3ByaXRlX3ZlcnQsZnJhZ21lbnRTaGFkZXI6UHQuc3ByaXRlX2ZyYWd9LGJhY2tncm91bmQ6e3VuaWZvcm1zOnt1dlRyYW5zZm9ybTp7dmFsdWU6bmV3IGRlfSx0MkQ6e3ZhbHVlOm51bGx9fSx2ZXJ0ZXhTaGFkZXI6UHQuYmFja2dyb3VuZF92ZXJ0LGZyYWdtZW50U2hhZGVyOlB0LmJhY2tncm91bmRfZnJhZ30sY3ViZTp7dW5pZm9ybXM6TWUoW290LmVudm1hcCx7b3BhY2l0eTp7dmFsdWU6MX19XSksdmVydGV4U2hhZGVyOlB0LmN1YmVfdmVydCxmcmFnbWVudFNoYWRlcjpQdC5jdWJlX2ZyYWd9LGVxdWlyZWN0Ont1bmlmb3Jtczp7dEVxdWlyZWN0Ont2YWx1ZTpudWxsfX0sdmVydGV4U2hhZGVyOlB0LmVxdWlyZWN0X3ZlcnQsZnJhZ21lbnRTaGFkZXI6UHQuZXF1aXJlY3RfZnJhZ30sZGlzdGFuY2VSR0JBOnt1bmlmb3JtczpNZShbb3QuY29tbW9uLG90LmRpc3BsYWNlbWVudG1hcCx7cmVmZXJlbmNlUG9zaXRpb246e3ZhbHVlOm5ldyBUfSxuZWFyRGlzdGFuY2U6e3ZhbHVlOjF9LGZhckRpc3RhbmNlOnt2YWx1ZToxZTN9fV0pLHZlcnRleFNoYWRlcjpQdC5kaXN0YW5jZVJHQkFfdmVydCxmcmFnbWVudFNoYWRlcjpQdC5kaXN0YW5jZVJHQkFfZnJhZ30sc2hhZG93Ont1bmlmb3JtczpNZShbb3QubGlnaHRzLG90LmZvZyx7Y29sb3I6e3ZhbHVlOm5ldyBmdCgwKX0sb3BhY2l0eTp7dmFsdWU6MX19XSksdmVydGV4U2hhZGVyOlB0LnNoYWRvd192ZXJ0LGZyYWdtZW50U2hhZGVyOlB0LnNoYWRvd19mcmFnfX07Zm4ucGh5c2ljYWw9e3VuaWZvcm1zOk1lKFtmbi5zdGFuZGFyZC51bmlmb3Jtcyx7Y2xlYXJjb2F0Ont2YWx1ZTowfSxjbGVhcmNvYXRNYXA6e3ZhbHVlOm51bGx9LGNsZWFyY29hdFJvdWdobmVzczp7dmFsdWU6MH0sY2xlYXJjb2F0Um91Z2huZXNzTWFwOnt2YWx1ZTpudWxsfSxjbGVhcmNvYXROb3JtYWxTY2FsZTp7dmFsdWU6bmV3IEsoMSwxKX0sY2xlYXJjb2F0Tm9ybWFsTWFwOnt2YWx1ZTpudWxsfSxzaGVlbjp7dmFsdWU6MH0sc2hlZW5Db2xvcjp7dmFsdWU6bmV3IGZ0KDApfSxzaGVlbkNvbG9yTWFwOnt2YWx1ZTpudWxsfSxzaGVlblJvdWdobmVzczp7dmFsdWU6MX0sc2hlZW5Sb3VnaG5lc3NNYXA6e3ZhbHVlOm51bGx9LHRyYW5zbWlzc2lvbjp7dmFsdWU6MH0sdHJhbnNtaXNzaW9uTWFwOnt2YWx1ZTpudWxsfSx0cmFuc21pc3Npb25TYW1wbGVyU2l6ZTp7dmFsdWU6bmV3IEt9LHRyYW5zbWlzc2lvblNhbXBsZXJNYXA6e3ZhbHVlOm51bGx9LHRoaWNrbmVzczp7dmFsdWU6MH0sdGhpY2tuZXNzTWFwOnt2YWx1ZTpudWxsfSxhdHRlbnVhdGlvbkRpc3RhbmNlOnt2YWx1ZTowfSxhdHRlbnVhdGlvbkNvbG9yOnt2YWx1ZTpuZXcgZnQoMCl9LHNwZWN1bGFySW50ZW5zaXR5Ont2YWx1ZToxfSxzcGVjdWxhckludGVuc2l0eU1hcDp7dmFsdWU6bnVsbH0sc3BlY3VsYXJDb2xvcjp7dmFsdWU6bmV3IGZ0KDEsMSwxKX0sc3BlY3VsYXJDb2xvck1hcDp7dmFsdWU6bnVsbH19XSksdmVydGV4U2hhZGVyOlB0Lm1lc2hwaHlzaWNhbF92ZXJ0LGZyYWdtZW50U2hhZGVyOlB0Lm1lc2hwaHlzaWNhbF9mcmFnfTtmdW5jdGlvbiBYMShuLHQsZSxpLHIscyl7bGV0IG89bmV3IGZ0KDApLGE9cj09PSEwPzA6MSxsLGMsdT1udWxsLGg9MCxmPW51bGw7ZnVuY3Rpb24gZCh4LHYpe2xldCBtPSExLHA9di5pc1NjZW5lPT09ITA/di5iYWNrZ3JvdW5kOm51bGw7cCYmcC5pc1RleHR1cmUmJihwPXQuZ2V0KHApKTtsZXQgYj1uLnhyLF89Yi5nZXRTZXNzaW9uJiZiLmdldFNlc3Npb24oKTtfJiZfLmVudmlyb25tZW50QmxlbmRNb2RlPT09ImFkZGl0aXZlIiYmKHA9bnVsbCkscD09PW51bGw/ZyhvLGEpOnAmJnAuaXNDb2xvciYmKGcocCwxKSxtPSEwKSwobi5hdXRvQ2xlYXJ8fG0pJiZuLmNsZWFyKG4uYXV0b0NsZWFyQ29sb3Isbi5hdXRvQ2xlYXJEZXB0aCxuLmF1dG9DbGVhclN0ZW5jaWwpLHAmJihwLmlzQ3ViZVRleHR1cmV8fHAubWFwcGluZz09PVJsKT8oYz09PXZvaWQgMCYmKGM9bmV3IG9lKG5ldyBIaSgxLDEsMSksbmV3IEZuKHtuYW1lOiJCYWNrZ3JvdW5kQ3ViZU1hdGVyaWFsIix1bmlmb3JtczpHcihmbi5jdWJlLnVuaWZvcm1zKSx2ZXJ0ZXhTaGFkZXI6Zm4uY3ViZS52ZXJ0ZXhTaGFkZXIsZnJhZ21lbnRTaGFkZXI6Zm4uY3ViZS5mcmFnbWVudFNoYWRlcixzaWRlOmhlLGRlcHRoVGVzdDohMSxkZXB0aFdyaXRlOiExLGZvZzohMX0pKSxjLmdlb21ldHJ5LmRlbGV0ZUF0dHJpYnV0ZSgibm9ybWFsIiksYy5nZW9tZXRyeS5kZWxldGVBdHRyaWJ1dGUoInV2IiksYy5vbkJlZm9yZVJlbmRlcj1mdW5jdGlvbihTLEwsQSl7dGhpcy5tYXRyaXhXb3JsZC5jb3B5UG9zaXRpb24oQS5tYXRyaXhXb3JsZCl9LE9iamVjdC5kZWZpbmVQcm9wZXJ0eShjLm1hdGVyaWFsLCJlbnZNYXAiLHtnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy51bmlmb3Jtcy5lbnZNYXAudmFsdWV9fSksaS51cGRhdGUoYykpLGMubWF0ZXJpYWwudW5pZm9ybXMuZW52TWFwLnZhbHVlPXAsYy5tYXRlcmlhbC51bmlmb3Jtcy5mbGlwRW52TWFwLnZhbHVlPXAuaXNDdWJlVGV4dHVyZSYmcC5pc1JlbmRlclRhcmdldFRleHR1cmU9PT0hMT8tMToxLCh1IT09cHx8aCE9PXAudmVyc2lvbnx8ZiE9PW4udG9uZU1hcHBpbmcpJiYoYy5tYXRlcmlhbC5uZWVkc1VwZGF0ZT0hMCx1PXAsaD1wLnZlcnNpb24sZj1uLnRvbmVNYXBwaW5nKSx4LnVuc2hpZnQoYyxjLmdlb21ldHJ5LGMubWF0ZXJpYWwsMCwwLG51bGwpKTpwJiZwLmlzVGV4dHVyZSYmKGw9PT12b2lkIDAmJihsPW5ldyBvZShuZXcgbG8oMiwyKSxuZXcgRm4oe25hbWU6IkJhY2tncm91bmRNYXRlcmlhbCIsdW5pZm9ybXM6R3IoZm4uYmFja2dyb3VuZC51bmlmb3JtcyksdmVydGV4U2hhZGVyOmZuLmJhY2tncm91bmQudmVydGV4U2hhZGVyLGZyYWdtZW50U2hhZGVyOmZuLmJhY2tncm91bmQuZnJhZ21lbnRTaGFkZXIsc2lkZTplbyxkZXB0aFRlc3Q6ITEsZGVwdGhXcml0ZTohMSxmb2c6ITF9KSksbC5nZW9tZXRyeS5kZWxldGVBdHRyaWJ1dGUoIm5vcm1hbCIpLE9iamVjdC5kZWZpbmVQcm9wZXJ0eShsLm1hdGVyaWFsLCJtYXAiLHtnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy51bmlmb3Jtcy50MkQudmFsdWV9fSksaS51cGRhdGUobCkpLGwubWF0ZXJpYWwudW5pZm9ybXMudDJELnZhbHVlPXAscC5tYXRyaXhBdXRvVXBkYXRlPT09ITAmJnAudXBkYXRlTWF0cml4KCksbC5tYXRlcmlhbC51bmlmb3Jtcy51dlRyYW5zZm9ybS52YWx1ZS5jb3B5KHAubWF0cml4KSwodSE9PXB8fGghPT1wLnZlcnNpb258fGYhPT1uLnRvbmVNYXBwaW5nKSYmKGwubWF0ZXJpYWwubmVlZHNVcGRhdGU9ITAsdT1wLGg9cC52ZXJzaW9uLGY9bi50b25lTWFwcGluZykseC51bnNoaWZ0KGwsbC5nZW9tZXRyeSxsLm1hdGVyaWFsLDAsMCxudWxsKSl9ZnVuY3Rpb24gZyh4LHYpe2UuYnVmZmVycy5jb2xvci5zZXRDbGVhcih4LnIseC5nLHguYix2LHMpfXJldHVybntnZXRDbGVhckNvbG9yOmZ1bmN0aW9uKCl7cmV0dXJuIG99LHNldENsZWFyQ29sb3I6ZnVuY3Rpb24oeCx2PTEpe28uc2V0KHgpLGE9dixnKG8sYSl9LGdldENsZWFyQWxwaGE6ZnVuY3Rpb24oKXtyZXR1cm4gYX0sc2V0Q2xlYXJBbHBoYTpmdW5jdGlvbih4KXthPXgsZyhvLGEpfSxyZW5kZXI6ZH19ZnVuY3Rpb24gWTEobix0LGUsaSl7bGV0IHI9bi5nZXRQYXJhbWV0ZXIoMzQ5MjEpLHM9aS5pc1dlYkdMMj9udWxsOnQuZ2V0KCJPRVNfdmVydGV4X2FycmF5X29iamVjdCIpLG89aS5pc1dlYkdMMnx8cyE9PW51bGwsYT17fSxsPXgobnVsbCksYz1sO2Z1bmN0aW9uIHUoRCxGLHosTixWKXtsZXQgUT0hMTtpZihvKXtsZXQgYXQ9ZyhOLHosRik7YyE9PWF0JiYoYz1hdCxmKGMub2JqZWN0KSksUT12KE4sViksUSYmbShOLFYpfWVsc2V7bGV0IGF0PUYud2lyZWZyYW1lPT09ITA7KGMuZ2VvbWV0cnkhPT1OLmlkfHxjLnByb2dyYW0hPT16LmlkfHxjLndpcmVmcmFtZSE9PWF0KSYmKGMuZ2VvbWV0cnk9Ti5pZCxjLnByb2dyYW09ei5pZCxjLndpcmVmcmFtZT1hdCxRPSEwKX1ELmlzSW5zdGFuY2VkTWVzaD09PSEwJiYoUT0hMCksViE9PW51bGwmJmUudXBkYXRlKFYsMzQ5NjMpLFEmJihBKEQsRix6LE4pLFYhPT1udWxsJiZuLmJpbmRCdWZmZXIoMzQ5NjMsZS5nZXQoVikuYnVmZmVyKSl9ZnVuY3Rpb24gaCgpe3JldHVybiBpLmlzV2ViR0wyP24uY3JlYXRlVmVydGV4QXJyYXkoKTpzLmNyZWF0ZVZlcnRleEFycmF5T0VTKCl9ZnVuY3Rpb24gZihEKXtyZXR1cm4gaS5pc1dlYkdMMj9uLmJpbmRWZXJ0ZXhBcnJheShEKTpzLmJpbmRWZXJ0ZXhBcnJheU9FUyhEKX1mdW5jdGlvbiBkKEQpe3JldHVybiBpLmlzV2ViR0wyP24uZGVsZXRlVmVydGV4QXJyYXkoRCk6cy5kZWxldGVWZXJ0ZXhBcnJheU9FUyhEKX1mdW5jdGlvbiBnKEQsRix6KXtsZXQgTj16LndpcmVmcmFtZT09PSEwLFY9YVtELmlkXTtWPT09dm9pZCAwJiYoVj17fSxhW0QuaWRdPVYpO2xldCBRPVZbRi5pZF07UT09PXZvaWQgMCYmKFE9e30sVltGLmlkXT1RKTtsZXQgYXQ9UVtOXTtyZXR1cm4gYXQ9PT12b2lkIDAmJihhdD14KGgoKSksUVtOXT1hdCksYXR9ZnVuY3Rpb24geChEKXtsZXQgRj1bXSx6PVtdLE49W107Zm9yKGxldCBWPTA7VjxyO1YrKylGW1ZdPTAseltWXT0wLE5bVl09MDtyZXR1cm57Z2VvbWV0cnk6bnVsbCxwcm9ncmFtOm51bGwsd2lyZWZyYW1lOiExLG5ld0F0dHJpYnV0ZXM6RixlbmFibGVkQXR0cmlidXRlczp6LGF0dHJpYnV0ZURpdmlzb3JzOk4sb2JqZWN0OkQsYXR0cmlidXRlczp7fSxpbmRleDpudWxsfX1mdW5jdGlvbiB2KEQsRil7bGV0IHo9Yy5hdHRyaWJ1dGVzLE49RC5hdHRyaWJ1dGVzLFY9MDtmb3IobGV0IFEgaW4gTil7bGV0IGF0PXpbUV0sRz1OW1FdO2lmKGF0PT09dm9pZCAwfHxhdC5hdHRyaWJ1dGUhPT1HfHxhdC5kYXRhIT09Ry5kYXRhKXJldHVybiEwO1YrK31yZXR1cm4gYy5hdHRyaWJ1dGVzTnVtIT09Vnx8Yy5pbmRleCE9PUZ9ZnVuY3Rpb24gbShELEYpe2xldCB6PXt9LE49RC5hdHRyaWJ1dGVzLFY9MDtmb3IobGV0IFEgaW4gTil7bGV0IGF0PU5bUV0sRz17fTtHLmF0dHJpYnV0ZT1hdCxhdC5kYXRhJiYoRy5kYXRhPWF0LmRhdGEpLHpbUV09RyxWKyt9Yy5hdHRyaWJ1dGVzPXosYy5hdHRyaWJ1dGVzTnVtPVYsYy5pbmRleD1GfWZ1bmN0aW9uIHAoKXtsZXQgRD1jLm5ld0F0dHJpYnV0ZXM7Zm9yKGxldCBGPTAsej1ELmxlbmd0aDtGPHo7RisrKURbRl09MH1mdW5jdGlvbiBiKEQpe18oRCwwKX1mdW5jdGlvbiBfKEQsRil7bGV0IHo9Yy5uZXdBdHRyaWJ1dGVzLE49Yy5lbmFibGVkQXR0cmlidXRlcyxWPWMuYXR0cmlidXRlRGl2aXNvcnM7eltEXT0xLE5bRF09PT0wJiYobi5lbmFibGVWZXJ0ZXhBdHRyaWJBcnJheShEKSxOW0RdPTEpLFZbRF0hPT1GJiYoKGkuaXNXZWJHTDI/bjp0LmdldCgiQU5HTEVfaW5zdGFuY2VkX2FycmF5cyIpKVtpLmlzV2ViR0wyPyJ2ZXJ0ZXhBdHRyaWJEaXZpc29yIjoidmVydGV4QXR0cmliRGl2aXNvckFOR0xFIl0oRCxGKSxWW0RdPUYpfWZ1bmN0aW9uIFMoKXtsZXQgRD1jLm5ld0F0dHJpYnV0ZXMsRj1jLmVuYWJsZWRBdHRyaWJ1dGVzO2ZvcihsZXQgej0wLE49Ri5sZW5ndGg7ejxOO3orKylGW3pdIT09RFt6XSYmKG4uZGlzYWJsZVZlcnRleEF0dHJpYkFycmF5KHopLEZbel09MCl9ZnVuY3Rpb24gTChELEYseixOLFYsUSl7aS5pc1dlYkdMMj09PSEwJiYoej09PTUxMjR8fHo9PT01MTI1KT9uLnZlcnRleEF0dHJpYklQb2ludGVyKEQsRix6LFYsUSk6bi52ZXJ0ZXhBdHRyaWJQb2ludGVyKEQsRix6LE4sVixRKX1mdW5jdGlvbiBBKEQsRix6LE4pe2lmKGkuaXNXZWJHTDI9PT0hMSYmKEQuaXNJbnN0YW5jZWRNZXNofHxOLmlzSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnkpJiZ0LmdldCgiQU5HTEVfaW5zdGFuY2VkX2FycmF5cyIpPT09bnVsbClyZXR1cm47cCgpO2xldCBWPU4uYXR0cmlidXRlcyxRPXouZ2V0QXR0cmlidXRlcygpLGF0PUYuZGVmYXVsdEF0dHJpYnV0ZVZhbHVlcztmb3IobGV0IEcgaW4gUSl7bGV0ICQ9UVtHXTtpZigkLmxvY2F0aW9uPj0wKXtsZXQgbHQ9VltHXTtpZihsdD09PXZvaWQgMCYmKEc9PT0iaW5zdGFuY2VNYXRyaXgiJiZELmluc3RhbmNlTWF0cml4JiYobHQ9RC5pbnN0YW5jZU1hdHJpeCksRz09PSJpbnN0YW5jZUNvbG9yIiYmRC5pbnN0YW5jZUNvbG9yJiYobHQ9RC5pbnN0YW5jZUNvbG9yKSksbHQhPT12b2lkIDApe2xldCBkdD1sdC5ub3JtYWxpemVkLHh0PWx0Lml0ZW1TaXplLGs9ZS5nZXQobHQpO2lmKGs9PT12b2lkIDApY29udGludWU7bGV0IEZ0PWsuYnVmZmVyLG10PWsudHlwZSxTdD1rLmJ5dGVzUGVyRWxlbWVudDtpZihsdC5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlKXtsZXQgQj1sdC5kYXRhLHN0PUIuc3RyaWRlLG50PWx0Lm9mZnNldDtpZihCJiZCLmlzSW5zdGFuY2VkSW50ZXJsZWF2ZWRCdWZmZXIpe2ZvcihsZXQgQz0wO0M8JC5sb2NhdGlvblNpemU7QysrKV8oJC5sb2NhdGlvbitDLEIubWVzaFBlckF0dHJpYnV0ZSk7RC5pc0luc3RhbmNlZE1lc2ghPT0hMCYmTi5fbWF4SW5zdGFuY2VDb3VudD09PXZvaWQgMCYmKE4uX21heEluc3RhbmNlQ291bnQ9Qi5tZXNoUGVyQXR0cmlidXRlKkIuY291bnQpfWVsc2UgZm9yKGxldCBDPTA7QzwkLmxvY2F0aW9uU2l6ZTtDKyspYigkLmxvY2F0aW9uK0MpO24uYmluZEJ1ZmZlcigzNDk2MixGdCk7Zm9yKGxldCBDPTA7QzwkLmxvY2F0aW9uU2l6ZTtDKyspTCgkLmxvY2F0aW9uK0MseHQvJC5sb2NhdGlvblNpemUsbXQsZHQsc3QqU3QsKG50K3h0LyQubG9jYXRpb25TaXplKkMpKlN0KX1lbHNle2lmKGx0LmlzSW5zdGFuY2VkQnVmZmVyQXR0cmlidXRlKXtmb3IobGV0IEI9MDtCPCQubG9jYXRpb25TaXplO0IrKylfKCQubG9jYXRpb24rQixsdC5tZXNoUGVyQXR0cmlidXRlKTtELmlzSW5zdGFuY2VkTWVzaCE9PSEwJiZOLl9tYXhJbnN0YW5jZUNvdW50PT09dm9pZCAwJiYoTi5fbWF4SW5zdGFuY2VDb3VudD1sdC5tZXNoUGVyQXR0cmlidXRlKmx0LmNvdW50KX1lbHNlIGZvcihsZXQgQj0wO0I8JC5sb2NhdGlvblNpemU7QisrKWIoJC5sb2NhdGlvbitCKTtuLmJpbmRCdWZmZXIoMzQ5NjIsRnQpO2ZvcihsZXQgQj0wO0I8JC5sb2NhdGlvblNpemU7QisrKUwoJC5sb2NhdGlvbitCLHh0LyQubG9jYXRpb25TaXplLG10LGR0LHh0KlN0LHh0LyQubG9jYXRpb25TaXplKkIqU3QpfX1lbHNlIGlmKGF0IT09dm9pZCAwKXtsZXQgZHQ9YXRbR107aWYoZHQhPT12b2lkIDApc3dpdGNoKGR0Lmxlbmd0aCl7Y2FzZSAyOm4udmVydGV4QXR0cmliMmZ2KCQubG9jYXRpb24sZHQpO2JyZWFrO2Nhc2UgMzpuLnZlcnRleEF0dHJpYjNmdigkLmxvY2F0aW9uLGR0KTticmVhaztjYXNlIDQ6bi52ZXJ0ZXhBdHRyaWI0ZnYoJC5sb2NhdGlvbixkdCk7YnJlYWs7ZGVmYXVsdDpuLnZlcnRleEF0dHJpYjFmdigkLmxvY2F0aW9uLGR0KX19fX1TKCl9ZnVuY3Rpb24gSCgpe3koKTtmb3IobGV0IEQgaW4gYSl7bGV0IEY9YVtEXTtmb3IobGV0IHogaW4gRil7bGV0IE49Rlt6XTtmb3IobGV0IFYgaW4gTilkKE5bVl0ub2JqZWN0KSxkZWxldGUgTltWXTtkZWxldGUgRlt6XX1kZWxldGUgYVtEXX19ZnVuY3Rpb24gdHQoRCl7aWYoYVtELmlkXT09PXZvaWQgMClyZXR1cm47bGV0IEY9YVtELmlkXTtmb3IobGV0IHogaW4gRil7bGV0IE49Rlt6XTtmb3IobGV0IFYgaW4gTilkKE5bVl0ub2JqZWN0KSxkZWxldGUgTltWXTtkZWxldGUgRlt6XX1kZWxldGUgYVtELmlkXX1mdW5jdGlvbiBYKEQpe2ZvcihsZXQgRiBpbiBhKXtsZXQgej1hW0ZdO2lmKHpbRC5pZF09PT12b2lkIDApY29udGludWU7bGV0IE49eltELmlkXTtmb3IobGV0IFYgaW4gTilkKE5bVl0ub2JqZWN0KSxkZWxldGUgTltWXTtkZWxldGUgeltELmlkXX19ZnVuY3Rpb24geSgpe1IoKSxjIT09bCYmKGM9bCxmKGMub2JqZWN0KSl9ZnVuY3Rpb24gUigpe2wuZ2VvbWV0cnk9bnVsbCxsLnByb2dyYW09bnVsbCxsLndpcmVmcmFtZT0hMX1yZXR1cm57c2V0dXA6dSxyZXNldDp5LHJlc2V0RGVmYXVsdFN0YXRlOlIsZGlzcG9zZTpILHJlbGVhc2VTdGF0ZXNPZkdlb21ldHJ5OnR0LHJlbGVhc2VTdGF0ZXNPZlByb2dyYW06WCxpbml0QXR0cmlidXRlczpwLGVuYWJsZUF0dHJpYnV0ZTpiLGRpc2FibGVVbnVzZWRBdHRyaWJ1dGVzOlN9fWZ1bmN0aW9uIFoxKG4sdCxlLGkpe2xldCByPWkuaXNXZWJHTDIscztmdW5jdGlvbiBvKGMpe3M9Y31mdW5jdGlvbiBhKGMsdSl7bi5kcmF3QXJyYXlzKHMsYyx1KSxlLnVwZGF0ZSh1LHMsMSl9ZnVuY3Rpb24gbChjLHUsaCl7aWYoaD09PTApcmV0dXJuO2xldCBmLGQ7aWYocilmPW4sZD0iZHJhd0FycmF5c0luc3RhbmNlZCI7ZWxzZSBpZihmPXQuZ2V0KCJBTkdMRV9pbnN0YW5jZWRfYXJyYXlzIiksZD0iZHJhd0FycmF5c0luc3RhbmNlZEFOR0xFIixmPT09bnVsbCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xCdWZmZXJSZW5kZXJlcjogdXNpbmcgVEhSRUUuSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnkgYnV0IGhhcmR3YXJlIGRvZXMgbm90IHN1cHBvcnQgZXh0ZW5zaW9uIEFOR0xFX2luc3RhbmNlZF9hcnJheXMuIik7cmV0dXJufWZbZF0ocyxjLHUsaCksZS51cGRhdGUodSxzLGgpfXRoaXMuc2V0TW9kZT1vLHRoaXMucmVuZGVyPWEsdGhpcy5yZW5kZXJJbnN0YW5jZXM9bH1mdW5jdGlvbiBKMShuLHQsZSl7bGV0IGk7ZnVuY3Rpb24gcigpe2lmKGkhPT12b2lkIDApcmV0dXJuIGk7aWYodC5oYXMoIkVYVF90ZXh0dXJlX2ZpbHRlcl9hbmlzb3Ryb3BpYyIpPT09ITApe2xldCBBPXQuZ2V0KCJFWFRfdGV4dHVyZV9maWx0ZXJfYW5pc290cm9waWMiKTtpPW4uZ2V0UGFyYW1ldGVyKEEuTUFYX1RFWFRVUkVfTUFYX0FOSVNPVFJPUFlfRVhUKX1lbHNlIGk9MDtyZXR1cm4gaX1mdW5jdGlvbiBzKEEpe2lmKEE9PT0iaGlnaHAiKXtpZihuLmdldFNoYWRlclByZWNpc2lvbkZvcm1hdCgzNTYzMywzNjMzOCkucHJlY2lzaW9uPjAmJm4uZ2V0U2hhZGVyUHJlY2lzaW9uRm9ybWF0KDM1NjMyLDM2MzM4KS5wcmVjaXNpb24+MClyZXR1cm4iaGlnaHAiO0E9Im1lZGl1bXAifXJldHVybiBBPT09Im1lZGl1bXAiJiZuLmdldFNoYWRlclByZWNpc2lvbkZvcm1hdCgzNTYzMywzNjMzNykucHJlY2lzaW9uPjAmJm4uZ2V0U2hhZGVyUHJlY2lzaW9uRm9ybWF0KDM1NjMyLDM2MzM3KS5wcmVjaXNpb24+MD8ibWVkaXVtcCI6Imxvd3AifWxldCBvPXR5cGVvZiBXZWJHTDJSZW5kZXJpbmdDb250ZXh0IT0idW5kZWZpbmVkIiYmbiBpbnN0YW5jZW9mIFdlYkdMMlJlbmRlcmluZ0NvbnRleHR8fHR5cGVvZiBXZWJHTDJDb21wdXRlUmVuZGVyaW5nQ29udGV4dCE9InVuZGVmaW5lZCImJm4gaW5zdGFuY2VvZiBXZWJHTDJDb21wdXRlUmVuZGVyaW5nQ29udGV4dCxhPWUucHJlY2lzaW9uIT09dm9pZCAwP2UucHJlY2lzaW9uOiJoaWdocCIsbD1zKGEpO2whPT1hJiYoY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiIsYSwibm90IHN1cHBvcnRlZCwgdXNpbmciLGwsImluc3RlYWQuIiksYT1sKTtsZXQgYz1vfHx0LmhhcygiV0VCR0xfZHJhd19idWZmZXJzIiksdT1lLmxvZ2FyaXRobWljRGVwdGhCdWZmZXI9PT0hMCxoPW4uZ2V0UGFyYW1ldGVyKDM0OTMwKSxmPW4uZ2V0UGFyYW1ldGVyKDM1NjYwKSxkPW4uZ2V0UGFyYW1ldGVyKDMzNzkpLGc9bi5nZXRQYXJhbWV0ZXIoMzQwNzYpLHg9bi5nZXRQYXJhbWV0ZXIoMzQ5MjEpLHY9bi5nZXRQYXJhbWV0ZXIoMzYzNDcpLG09bi5nZXRQYXJhbWV0ZXIoMzYzNDgpLHA9bi5nZXRQYXJhbWV0ZXIoMzYzNDkpLGI9Zj4wLF89b3x8dC5oYXMoIk9FU190ZXh0dXJlX2Zsb2F0IiksUz1iJiZfLEw9bz9uLmdldFBhcmFtZXRlcigzNjE4Myk6MDtyZXR1cm57aXNXZWJHTDI6byxkcmF3QnVmZmVyczpjLGdldE1heEFuaXNvdHJvcHk6cixnZXRNYXhQcmVjaXNpb246cyxwcmVjaXNpb246YSxsb2dhcml0aG1pY0RlcHRoQnVmZmVyOnUsbWF4VGV4dHVyZXM6aCxtYXhWZXJ0ZXhUZXh0dXJlczpmLG1heFRleHR1cmVTaXplOmQsbWF4Q3ViZW1hcFNpemU6ZyxtYXhBdHRyaWJ1dGVzOngsbWF4VmVydGV4VW5pZm9ybXM6dixtYXhWYXJ5aW5nczptLG1heEZyYWdtZW50VW5pZm9ybXM6cCx2ZXJ0ZXhUZXh0dXJlczpiLGZsb2F0RnJhZ21lbnRUZXh0dXJlczpfLGZsb2F0VmVydGV4VGV4dHVyZXM6UyxtYXhTYW1wbGVzOkx9fWZ1bmN0aW9uICQxKG4pe2xldCB0PXRoaXMsZT1udWxsLGk9MCxyPSExLHM9ITEsbz1uZXcgamUsYT1uZXcgZGUsbD17dmFsdWU6bnVsbCxuZWVkc1VwZGF0ZTohMX07dGhpcy51bmlmb3JtPWwsdGhpcy5udW1QbGFuZXM9MCx0aGlzLm51bUludGVyc2VjdGlvbj0wLHRoaXMuaW5pdD1mdW5jdGlvbihoLGYsZCl7bGV0IGc9aC5sZW5ndGghPT0wfHxmfHxpIT09MHx8cjtyZXR1cm4gcj1mLGU9dShoLGQsMCksaT1oLmxlbmd0aCxnfSx0aGlzLmJlZ2luU2hhZG93cz1mdW5jdGlvbigpe3M9ITAsdShudWxsKX0sdGhpcy5lbmRTaGFkb3dzPWZ1bmN0aW9uKCl7cz0hMSxjKCl9LHRoaXMuc2V0U3RhdGU9ZnVuY3Rpb24oaCxmLGQpe2xldCBnPWguY2xpcHBpbmdQbGFuZXMseD1oLmNsaXBJbnRlcnNlY3Rpb24sdj1oLmNsaXBTaGFkb3dzLG09bi5nZXQoaCk7aWYoIXJ8fGc9PT1udWxsfHxnLmxlbmd0aD09PTB8fHMmJiF2KXM/dShudWxsKTpjKCk7ZWxzZXtsZXQgcD1zPzA6aSxiPXAqNCxfPW0uY2xpcHBpbmdTdGF0ZXx8bnVsbDtsLnZhbHVlPV8sXz11KGcsZixiLGQpO2ZvcihsZXQgUz0wO1MhPT1iOysrUylfW1NdPWVbU107bS5jbGlwcGluZ1N0YXRlPV8sdGhpcy5udW1JbnRlcnNlY3Rpb249eD90aGlzLm51bVBsYW5lczowLHRoaXMubnVtUGxhbmVzKz1wfX07ZnVuY3Rpb24gYygpe2wudmFsdWUhPT1lJiYobC52YWx1ZT1lLGwubmVlZHNVcGRhdGU9aT4wKSx0Lm51bVBsYW5lcz1pLHQubnVtSW50ZXJzZWN0aW9uPTB9ZnVuY3Rpb24gdShoLGYsZCxnKXtsZXQgeD1oIT09bnVsbD9oLmxlbmd0aDowLHY9bnVsbDtpZih4IT09MCl7aWYodj1sLnZhbHVlLGchPT0hMHx8dj09PW51bGwpe2xldCBtPWQreCo0LHA9Zi5tYXRyaXhXb3JsZEludmVyc2U7YS5nZXROb3JtYWxNYXRyaXgocCksKHY9PT1udWxsfHx2Lmxlbmd0aDxtKSYmKHY9bmV3IEZsb2F0MzJBcnJheShtKSk7Zm9yKGxldCBiPTAsXz1kO2IhPT14OysrYixfKz00KW8uY29weShoW2JdKS5hcHBseU1hdHJpeDQocCxhKSxvLm5vcm1hbC50b0FycmF5KHYsXyksdltfKzNdPW8uY29uc3RhbnR9bC52YWx1ZT12LGwubmVlZHNVcGRhdGU9ITB9cmV0dXJuIHQubnVtUGxhbmVzPXgsdC5udW1JbnRlcnNlY3Rpb249MCx2fX1mdW5jdGlvbiBLMShuKXtsZXQgdD1uZXcgV2Vha01hcDtmdW5jdGlvbiBlKG8sYSl7cmV0dXJuIGE9PT1VdT9vLm1hcHBpbmc9QW86YT09PUJ1JiYoby5tYXBwaW5nPUNvKSxvfWZ1bmN0aW9uIGkobyl7aWYobyYmby5pc1RleHR1cmUmJm8uaXNSZW5kZXJUYXJnZXRUZXh0dXJlPT09ITEpe2xldCBhPW8ubWFwcGluZztpZihhPT09VXV8fGE9PT1CdSlpZih0LmhhcyhvKSl7bGV0IGw9dC5nZXQobykudGV4dHVyZTtyZXR1cm4gZShsLG8ubWFwcGluZyl9ZWxzZXtsZXQgbD1vLmltYWdlO2lmKGwmJmwuaGVpZ2h0PjApe2xldCBjPW5ldyBvbChsLmhlaWdodC8yKTtyZXR1cm4gYy5mcm9tRXF1aXJlY3Rhbmd1bGFyVGV4dHVyZShuLG8pLHQuc2V0KG8sYyksby5hZGRFdmVudExpc3RlbmVyKCJkaXNwb3NlIixyKSxlKGMudGV4dHVyZSxvLm1hcHBpbmcpfWVsc2UgcmV0dXJuIG51bGx9fXJldHVybiBvfWZ1bmN0aW9uIHIobyl7bGV0IGE9by50YXJnZXQ7YS5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixyKTtsZXQgbD10LmdldChhKTtsIT09dm9pZCAwJiYodC5kZWxldGUoYSksbC5kaXNwb3NlKCkpfWZ1bmN0aW9uIHMoKXt0PW5ldyBXZWFrTWFwfXJldHVybntnZXQ6aSxkaXNwb3NlOnN9fXZhciBWaT1jbGFzcyBleHRlbmRzIG9ve2NvbnN0cnVjdG9yKHQ9LTEsZT0xLGk9MSxyPS0xLHM9LjEsbz0yZTMpe3N1cGVyKCksdGhpcy50eXBlPSJPcnRob2dyYXBoaWNDYW1lcmEiLHRoaXMuem9vbT0xLHRoaXMudmlldz1udWxsLHRoaXMubGVmdD10LHRoaXMucmlnaHQ9ZSx0aGlzLnRvcD1pLHRoaXMuYm90dG9tPXIsdGhpcy5uZWFyPXMsdGhpcy5mYXI9byx0aGlzLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX1jb3B5KHQsZSl7cmV0dXJuIHN1cGVyLmNvcHkodCxlKSx0aGlzLmxlZnQ9dC5sZWZ0LHRoaXMucmlnaHQ9dC5yaWdodCx0aGlzLnRvcD10LnRvcCx0aGlzLmJvdHRvbT10LmJvdHRvbSx0aGlzLm5lYXI9dC5uZWFyLHRoaXMuZmFyPXQuZmFyLHRoaXMuem9vbT10Lnpvb20sdGhpcy52aWV3PXQudmlldz09PW51bGw/bnVsbDpPYmplY3QuYXNzaWduKHt9LHQudmlldyksdGhpc31zZXRWaWV3T2Zmc2V0KHQsZSxpLHIscyxvKXt0aGlzLnZpZXc9PT1udWxsJiYodGhpcy52aWV3PXtlbmFibGVkOiEwLGZ1bGxXaWR0aDoxLGZ1bGxIZWlnaHQ6MSxvZmZzZXRYOjAsb2Zmc2V0WTowLHdpZHRoOjEsaGVpZ2h0OjF9KSx0aGlzLnZpZXcuZW5hYmxlZD0hMCx0aGlzLnZpZXcuZnVsbFdpZHRoPXQsdGhpcy52aWV3LmZ1bGxIZWlnaHQ9ZSx0aGlzLnZpZXcub2Zmc2V0WD1pLHRoaXMudmlldy5vZmZzZXRZPXIsdGhpcy52aWV3LndpZHRoPXMsdGhpcy52aWV3LmhlaWdodD1vLHRoaXMudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpfWNsZWFyVmlld09mZnNldCgpe3RoaXMudmlldyE9PW51bGwmJih0aGlzLnZpZXcuZW5hYmxlZD0hMSksdGhpcy51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl9dXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpe2xldCB0PSh0aGlzLnJpZ2h0LXRoaXMubGVmdCkvKDIqdGhpcy56b29tKSxlPSh0aGlzLnRvcC10aGlzLmJvdHRvbSkvKDIqdGhpcy56b29tKSxpPSh0aGlzLnJpZ2h0K3RoaXMubGVmdCkvMixyPSh0aGlzLnRvcCt0aGlzLmJvdHRvbSkvMixzPWktdCxvPWkrdCxhPXIrZSxsPXItZTtpZih0aGlzLnZpZXchPT1udWxsJiZ0aGlzLnZpZXcuZW5hYmxlZCl7bGV0IGM9KHRoaXMucmlnaHQtdGhpcy5sZWZ0KS90aGlzLnZpZXcuZnVsbFdpZHRoL3RoaXMuem9vbSx1PSh0aGlzLnRvcC10aGlzLmJvdHRvbSkvdGhpcy52aWV3LmZ1bGxIZWlnaHQvdGhpcy56b29tO3MrPWMqdGhpcy52aWV3Lm9mZnNldFgsbz1zK2MqdGhpcy52aWV3LndpZHRoLGEtPXUqdGhpcy52aWV3Lm9mZnNldFksbD1hLXUqdGhpcy52aWV3LmhlaWdodH10aGlzLnByb2plY3Rpb25NYXRyaXgubWFrZU9ydGhvZ3JhcGhpYyhzLG8sYSxsLHRoaXMubmVhcix0aGlzLmZhciksdGhpcy5wcm9qZWN0aW9uTWF0cml4SW52ZXJzZS5jb3B5KHRoaXMucHJvamVjdGlvbk1hdHJpeCkuaW52ZXJ0KCl9dG9KU09OKHQpe2xldCBlPXN1cGVyLnRvSlNPTih0KTtyZXR1cm4gZS5vYmplY3Quem9vbT10aGlzLnpvb20sZS5vYmplY3QubGVmdD10aGlzLmxlZnQsZS5vYmplY3QucmlnaHQ9dGhpcy5yaWdodCxlLm9iamVjdC50b3A9dGhpcy50b3AsZS5vYmplY3QuYm90dG9tPXRoaXMuYm90dG9tLGUub2JqZWN0Lm5lYXI9dGhpcy5uZWFyLGUub2JqZWN0LmZhcj10aGlzLmZhcix0aGlzLnZpZXchPT1udWxsJiYoZS5vYmplY3Qudmlldz1PYmplY3QuYXNzaWduKHt9LHRoaXMudmlldykpLGV9fTtWaS5wcm90b3R5cGUuaXNPcnRob2dyYXBoaWNDYW1lcmE9ITA7dmFyIFhyPWNsYXNzIGV4dGVuZHMgRm57Y29uc3RydWN0b3IodCl7c3VwZXIodCksdGhpcy50eXBlPSJSYXdTaGFkZXJNYXRlcmlhbCJ9fTtYci5wcm90b3R5cGUuaXNSYXdTaGFkZXJNYXRlcmlhbD0hMDt2YXIga3I9NCxuaT04LGhuPU1hdGgucG93KDIsbmkpLHgwPVsuMTI1LC4yMTUsLjM1LC40NDYsLjUyNiwuNTgyXSx5MD1uaS1rcisxK3gwLmxlbmd0aCxBcj0yMCxUdT1uZXcgVmkse19sb2RQbGFuZXM6R3MsX3NpemVMb2RzOm1nLF9zaWdtYXM6VWF9PVExKCksZ2c9bmV3IGZ0LEF1PW51bGwsemk9KDErTWF0aC5zcXJ0KDUpKS8yLENyPTEvemkseGc9W25ldyBUKDEsMSwxKSxuZXcgVCgtMSwxLDEpLG5ldyBUKDEsMSwtMSksbmV3IFQoLTEsMSwtMSksbmV3IFQoMCx6aSxDciksbmV3IFQoMCx6aSwtQ3IpLG5ldyBUKENyLDAsemkpLG5ldyBUKC1DciwwLHppKSxuZXcgVCh6aSxDciwwKSxuZXcgVCgtemksQ3IsMCldLGFsPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuX3JlbmRlcmVyPXQsdGhpcy5fcGluZ1BvbmdSZW5kZXJUYXJnZXQ9bnVsbCx0aGlzLl9ibHVyTWF0ZXJpYWw9ajEoQXIpLHRoaXMuX2VxdWlyZWN0U2hhZGVyPW51bGwsdGhpcy5fY3ViZW1hcFNoYWRlcj1udWxsLHRoaXMuX2NvbXBpbGVNYXRlcmlhbCh0aGlzLl9ibHVyTWF0ZXJpYWwpfWZyb21TY2VuZSh0LGU9MCxpPS4xLHI9MTAwKXtBdT10aGlzLl9yZW5kZXJlci5nZXRSZW5kZXJUYXJnZXQoKTtsZXQgcz10aGlzLl9hbGxvY2F0ZVRhcmdldHMoKTtyZXR1cm4gdGhpcy5fc2NlbmVUb0N1YmVVVih0LGkscixzKSxlPjAmJnRoaXMuX2JsdXIocywwLDAsZSksdGhpcy5fYXBwbHlQTVJFTShzKSx0aGlzLl9jbGVhbnVwKHMpLHN9ZnJvbUVxdWlyZWN0YW5ndWxhcih0LGU9bnVsbCl7cmV0dXJuIHRoaXMuX2Zyb21UZXh0dXJlKHQsZSl9ZnJvbUN1YmVtYXAodCxlPW51bGwpe3JldHVybiB0aGlzLl9mcm9tVGV4dHVyZSh0LGUpfWNvbXBpbGVDdWJlbWFwU2hhZGVyKCl7dGhpcy5fY3ViZW1hcFNoYWRlcj09PW51bGwmJih0aGlzLl9jdWJlbWFwU2hhZGVyPV9nKCksdGhpcy5fY29tcGlsZU1hdGVyaWFsKHRoaXMuX2N1YmVtYXBTaGFkZXIpKX1jb21waWxlRXF1aXJlY3Rhbmd1bGFyU2hhZGVyKCl7dGhpcy5fZXF1aXJlY3RTaGFkZXI9PT1udWxsJiYodGhpcy5fZXF1aXJlY3RTaGFkZXI9dmcoKSx0aGlzLl9jb21waWxlTWF0ZXJpYWwodGhpcy5fZXF1aXJlY3RTaGFkZXIpKX1kaXNwb3NlKCl7dGhpcy5fYmx1ck1hdGVyaWFsLmRpc3Bvc2UoKSx0aGlzLl9waW5nUG9uZ1JlbmRlclRhcmdldCE9PW51bGwmJnRoaXMuX3BpbmdQb25nUmVuZGVyVGFyZ2V0LmRpc3Bvc2UoKSx0aGlzLl9jdWJlbWFwU2hhZGVyIT09bnVsbCYmdGhpcy5fY3ViZW1hcFNoYWRlci5kaXNwb3NlKCksdGhpcy5fZXF1aXJlY3RTaGFkZXIhPT1udWxsJiZ0aGlzLl9lcXVpcmVjdFNoYWRlci5kaXNwb3NlKCk7Zm9yKGxldCB0PTA7dDxHcy5sZW5ndGg7dCsrKUdzW3RdLmRpc3Bvc2UoKX1fY2xlYW51cCh0KXt0aGlzLl9yZW5kZXJlci5zZXRSZW5kZXJUYXJnZXQoQXUpLHQuc2Npc3NvclRlc3Q9ITEsQmEodCwwLDAsdC53aWR0aCx0LmhlaWdodCl9X2Zyb21UZXh0dXJlKHQsZSl7QXU9dGhpcy5fcmVuZGVyZXIuZ2V0UmVuZGVyVGFyZ2V0KCk7bGV0IGk9ZXx8dGhpcy5fYWxsb2NhdGVUYXJnZXRzKHQpO3JldHVybiB0aGlzLl90ZXh0dXJlVG9DdWJlVVYodCxpKSx0aGlzLl9hcHBseVBNUkVNKGkpLHRoaXMuX2NsZWFudXAoaSksaX1fYWxsb2NhdGVUYXJnZXRzKHQpe2xldCBlPXttYWdGaWx0ZXI6YmUsbWluRmlsdGVyOmJlLGdlbmVyYXRlTWlwbWFwczohMSx0eXBlOlVyLGZvcm1hdDpSZSxlbmNvZGluZzpyaSxkZXB0aEJ1ZmZlcjohMX0saT15ZyhlKTtyZXR1cm4gaS5kZXB0aEJ1ZmZlcj0hdCx0aGlzLl9waW5nUG9uZ1JlbmRlclRhcmdldD09PW51bGwmJih0aGlzLl9waW5nUG9uZ1JlbmRlclRhcmdldD15ZyhlKSksaX1fY29tcGlsZU1hdGVyaWFsKHQpe2xldCBlPW5ldyBvZShHc1swXSx0KTt0aGlzLl9yZW5kZXJlci5jb21waWxlKGUsVHUpfV9zY2VuZVRvQ3ViZVVWKHQsZSxpLHIpe2xldCBhPW5ldyBTZSg5MCwxLGUsaSksbD1bMSwtMSwxLDEsMSwxXSxjPVsxLDEsMSwtMSwtMSwtMV0sdT10aGlzLl9yZW5kZXJlcixoPXUuYXV0b0NsZWFyLGY9dS50b25lTWFwcGluZzt1LmdldENsZWFyQ29sb3IoZ2cpLHUudG9uZU1hcHBpbmc9dGksdS5hdXRvQ2xlYXI9ITE7bGV0IGQ9bmV3IGtpKHtuYW1lOiJQTVJFTS5CYWNrZ3JvdW5kIixzaWRlOmhlLGRlcHRoV3JpdGU6ITEsZGVwdGhUZXN0OiExfSksZz1uZXcgb2UobmV3IEhpLGQpLHg9ITEsdj10LmJhY2tncm91bmQ7dj92LmlzQ29sb3ImJihkLmNvbG9yLmNvcHkodiksdC5iYWNrZ3JvdW5kPW51bGwseD0hMCk6KGQuY29sb3IuY29weShnZykseD0hMCk7Zm9yKGxldCBtPTA7bTw2O20rKyl7bGV0IHA9bSUzO3A9PT0wPyhhLnVwLnNldCgwLGxbbV0sMCksYS5sb29rQXQoY1ttXSwwLDApKTpwPT09MT8oYS51cC5zZXQoMCwwLGxbbV0pLGEubG9va0F0KDAsY1ttXSwwKSk6KGEudXAuc2V0KDAsbFttXSwwKSxhLmxvb2tBdCgwLDAsY1ttXSkpLEJhKHIscCpobixtPjI/aG46MCxobixobiksdS5zZXRSZW5kZXJUYXJnZXQocikseCYmdS5yZW5kZXIoZyxhKSx1LnJlbmRlcih0LGEpfWcuZ2VvbWV0cnkuZGlzcG9zZSgpLGcubWF0ZXJpYWwuZGlzcG9zZSgpLHUudG9uZU1hcHBpbmc9Zix1LmF1dG9DbGVhcj1oLHQuYmFja2dyb3VuZD12fV90ZXh0dXJlVG9DdWJlVVYodCxlKXtsZXQgaT10aGlzLl9yZW5kZXJlcixyPXQubWFwcGluZz09PUFvfHx0Lm1hcHBpbmc9PT1DbztyPyh0aGlzLl9jdWJlbWFwU2hhZGVyPT09bnVsbCYmKHRoaXMuX2N1YmVtYXBTaGFkZXI9X2coKSksdGhpcy5fY3ViZW1hcFNoYWRlci51bmlmb3Jtcy5mbGlwRW52TWFwLnZhbHVlPXQuaXNSZW5kZXJUYXJnZXRUZXh0dXJlPT09ITE/LTE6MSk6dGhpcy5fZXF1aXJlY3RTaGFkZXI9PT1udWxsJiYodGhpcy5fZXF1aXJlY3RTaGFkZXI9dmcoKSk7bGV0IHM9cj90aGlzLl9jdWJlbWFwU2hhZGVyOnRoaXMuX2VxdWlyZWN0U2hhZGVyLG89bmV3IG9lKEdzWzBdLHMpLGE9cy51bmlmb3JtczthLmVudk1hcC52YWx1ZT10LHJ8fGEudGV4ZWxTaXplLnZhbHVlLnNldCgxL3QuaW1hZ2Uud2lkdGgsMS90LmltYWdlLmhlaWdodCksQmEoZSwwLDAsMypobiwyKmhuKSxpLnNldFJlbmRlclRhcmdldChlKSxpLnJlbmRlcihvLFR1KX1fYXBwbHlQTVJFTSh0KXtsZXQgZT10aGlzLl9yZW5kZXJlcixpPWUuYXV0b0NsZWFyO2UuYXV0b0NsZWFyPSExO2ZvcihsZXQgcj0xO3I8eTA7cisrKXtsZXQgcz1NYXRoLnNxcnQoVWFbcl0qVWFbcl0tVWFbci0xXSpVYVtyLTFdKSxvPXhnWyhyLTEpJXhnLmxlbmd0aF07dGhpcy5fYmx1cih0LHItMSxyLHMsbyl9ZS5hdXRvQ2xlYXI9aX1fYmx1cih0LGUsaSxyLHMpe2xldCBvPXRoaXMuX3BpbmdQb25nUmVuZGVyVGFyZ2V0O3RoaXMuX2hhbGZCbHVyKHQsbyxlLGksciwibGF0aXR1ZGluYWwiLHMpLHRoaXMuX2hhbGZCbHVyKG8sdCxpLGksciwibG9uZ2l0dWRpbmFsIixzKX1faGFsZkJsdXIodCxlLGkscixzLG8sYSl7bGV0IGw9dGhpcy5fcmVuZGVyZXIsYz10aGlzLl9ibHVyTWF0ZXJpYWw7byE9PSJsYXRpdHVkaW5hbCImJm8hPT0ibG9uZ2l0dWRpbmFsIiYmY29uc29sZS5lcnJvcigiYmx1ciBkaXJlY3Rpb24gbXVzdCBiZSBlaXRoZXIgbGF0aXR1ZGluYWwgb3IgbG9uZ2l0dWRpbmFsISIpO2xldCB1PTMsaD1uZXcgb2UoR3Nbcl0sYyksZj1jLnVuaWZvcm1zLGQ9bWdbaV0tMSxnPWlzRmluaXRlKHMpP01hdGguUEkvKDIqZCk6MipNYXRoLlBJLygyKkFyLTEpLHg9cy9nLHY9aXNGaW5pdGUocyk/MStNYXRoLmZsb29yKHUqeCk6QXI7dj5BciYmY29uc29sZS53YXJuKGBzaWdtYVJhZGlhbnMsICR7c30sIGlzIHRvbyBsYXJnZSBhbmQgd2lsbCBjbGlwLCBhcyBpdCByZXF1ZXN0ZWQgJHt2fSBzYW1wbGVzIHdoZW4gdGhlIG1heGltdW0gaXMgc2V0IHRvICR7QXJ9YCk7bGV0IG09W10scD0wO2ZvcihsZXQgTD0wO0w8QXI7KytMKXtsZXQgQT1ML3gsSD1NYXRoLmV4cCgtQSpBLzIpO20ucHVzaChIKSxMPT09MD9wKz1IOkw8diYmKHArPTIqSCl9Zm9yKGxldCBMPTA7TDxtLmxlbmd0aDtMKyspbVtMXT1tW0xdL3A7Zi5lbnZNYXAudmFsdWU9dC50ZXh0dXJlLGYuc2FtcGxlcy52YWx1ZT12LGYud2VpZ2h0cy52YWx1ZT1tLGYubGF0aXR1ZGluYWwudmFsdWU9bz09PSJsYXRpdHVkaW5hbCIsYSYmKGYucG9sZUF4aXMudmFsdWU9YSksZi5kVGhldGEudmFsdWU9ZyxmLm1pcEludC52YWx1ZT1uaS1pO2xldCBiPW1nW3JdLF89MypNYXRoLm1heCgwLGhuLTIqYiksUz0ocj09PTA/MDoyKmhuKSsyKmIqKHI+bmkta3I/ci1uaStrcjowKTtCYShlLF8sUywzKmIsMipiKSxsLnNldFJlbmRlclRhcmdldChlKSxsLnJlbmRlcihoLFR1KX19O2Z1bmN0aW9uIFExKCl7bGV0IG49W10sdD1bXSxlPVtdLGk9bmk7Zm9yKGxldCByPTA7cjx5MDtyKyspe2xldCBzPU1hdGgucG93KDIsaSk7dC5wdXNoKHMpO2xldCBvPTEvcztyPm5pLWtyP289eDBbci1uaStrci0xXTpyPT09MCYmKG89MCksZS5wdXNoKG8pO2xldCBhPTEvKHMtMSksbD0tYS8yLGM9MSthLzIsdT1bbCxsLGMsbCxjLGMsbCxsLGMsYyxsLGNdLGg9NixmPTYsZD0zLGc9Mix4PTEsdj1uZXcgRmxvYXQzMkFycmF5KGQqZipoKSxtPW5ldyBGbG9hdDMyQXJyYXkoZypmKmgpLHA9bmV3IEZsb2F0MzJBcnJheSh4KmYqaCk7Zm9yKGxldCBfPTA7XzxoO18rKyl7bGV0IFM9XyUzKjIvMy0xLEw9Xz4yPzA6LTEsQT1bUyxMLDAsUysyLzMsTCwwLFMrMi8zLEwrMSwwLFMsTCwwLFMrMi8zLEwrMSwwLFMsTCsxLDBdO3Yuc2V0KEEsZCpmKl8pLG0uc2V0KHUsZypmKl8pO2xldCBIPVtfLF8sXyxfLF8sX107cC5zZXQoSCx4KmYqXyl9bGV0IGI9bmV3IEh0O2Iuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IFF0KHYsZCkpLGIuc2V0QXR0cmlidXRlKCJ1diIsbmV3IFF0KG0sZykpLGIuc2V0QXR0cmlidXRlKCJmYWNlSW5kZXgiLG5ldyBRdChwLHgpKSxuLnB1c2goYiksaT5rciYmaS0tfXJldHVybntfbG9kUGxhbmVzOm4sX3NpemVMb2RzOnQsX3NpZ21hczplfX1mdW5jdGlvbiB5ZyhuKXtsZXQgdD1uZXcgTmUoMypobiwzKmhuLG4pO3JldHVybiB0LnRleHR1cmUubWFwcGluZz1SbCx0LnRleHR1cmUubmFtZT0iUE1SRU0uY3ViZVV2Iix0LnNjaXNzb3JUZXN0PSEwLHR9ZnVuY3Rpb24gQmEobix0LGUsaSxyKXtuLnZpZXdwb3J0LnNldCh0LGUsaSxyKSxuLnNjaXNzb3Iuc2V0KHQsZSxpLHIpfWZ1bmN0aW9uIGoxKG4pe2xldCB0PW5ldyBGbG9hdDMyQXJyYXkobiksZT1uZXcgVCgwLDEsMCk7cmV0dXJuIG5ldyBYcih7bmFtZToiU3BoZXJpY2FsR2F1c3NpYW5CbHVyIixkZWZpbmVzOntufSx1bmlmb3Jtczp7ZW52TWFwOnt2YWx1ZTpudWxsfSxzYW1wbGVzOnt2YWx1ZToxfSx3ZWlnaHRzOnt2YWx1ZTp0fSxsYXRpdHVkaW5hbDp7dmFsdWU6ITF9LGRUaGV0YTp7dmFsdWU6MH0sbWlwSW50Ont2YWx1ZTowfSxwb2xlQXhpczp7dmFsdWU6ZX19LHZlcnRleFNoYWRlcjpqaCgpLGZyYWdtZW50U2hhZGVyOmAKCgkJCXByZWNpc2lvbiBtZWRpdW1wIGZsb2F0OwoJCQlwcmVjaXNpb24gbWVkaXVtcCBpbnQ7CgoJCQl2YXJ5aW5nIHZlYzMgdk91dHB1dERpcmVjdGlvbjsKCgkJCXVuaWZvcm0gc2FtcGxlcjJEIGVudk1hcDsKCQkJdW5pZm9ybSBpbnQgc2FtcGxlczsKCQkJdW5pZm9ybSBmbG9hdCB3ZWlnaHRzWyBuIF07CgkJCXVuaWZvcm0gYm9vbCBsYXRpdHVkaW5hbDsKCQkJdW5pZm9ybSBmbG9hdCBkVGhldGE7CgkJCXVuaWZvcm0gZmxvYXQgbWlwSW50OwoJCQl1bmlmb3JtIHZlYzMgcG9sZUF4aXM7CgoJCQkjZGVmaW5lIEVOVk1BUF9UWVBFX0NVQkVfVVYKCQkJI2luY2x1ZGUgPGN1YmVfdXZfcmVmbGVjdGlvbl9mcmFnbWVudD4KCgkJCXZlYzMgZ2V0U2FtcGxlKCBmbG9hdCB0aGV0YSwgdmVjMyBheGlzICkgewoKCQkJCWZsb2F0IGNvc1RoZXRhID0gY29zKCB0aGV0YSApOwoJCQkJLy8gUm9kcmlndWVzJyBheGlzLWFuZ2xlIHJvdGF0aW9uCgkJCQl2ZWMzIHNhbXBsZURpcmVjdGlvbiA9IHZPdXRwdXREaXJlY3Rpb24gKiBjb3NUaGV0YQoJCQkJCSsgY3Jvc3MoIGF4aXMsIHZPdXRwdXREaXJlY3Rpb24gKSAqIHNpbiggdGhldGEgKQoJCQkJCSsgYXhpcyAqIGRvdCggYXhpcywgdk91dHB1dERpcmVjdGlvbiApICogKCAxLjAgLSBjb3NUaGV0YSApOwoKCQkJCXJldHVybiBiaWxpbmVhckN1YmVVViggZW52TWFwLCBzYW1wbGVEaXJlY3Rpb24sIG1pcEludCApOwoKCQkJfQoKCQkJdm9pZCBtYWluKCkgewoKCQkJCXZlYzMgYXhpcyA9IGxhdGl0dWRpbmFsID8gcG9sZUF4aXMgOiBjcm9zcyggcG9sZUF4aXMsIHZPdXRwdXREaXJlY3Rpb24gKTsKCgkJCQlpZiAoIGFsbCggZXF1YWwoIGF4aXMsIHZlYzMoIDAuMCApICkgKSApIHsKCgkJCQkJYXhpcyA9IHZlYzMoIHZPdXRwdXREaXJlY3Rpb24ueiwgMC4wLCAtIHZPdXRwdXREaXJlY3Rpb24ueCApOwoKCQkJCX0KCgkJCQlheGlzID0gbm9ybWFsaXplKCBheGlzICk7CgoJCQkJZ2xfRnJhZ0NvbG9yID0gdmVjNCggMC4wLCAwLjAsIDAuMCwgMS4wICk7CgkJCQlnbF9GcmFnQ29sb3IucmdiICs9IHdlaWdodHNbIDAgXSAqIGdldFNhbXBsZSggMC4wLCBheGlzICk7CgoJCQkJZm9yICggaW50IGkgPSAxOyBpIDwgbjsgaSsrICkgewoKCQkJCQlpZiAoIGkgPj0gc2FtcGxlcyApIHsKCgkJCQkJCWJyZWFrOwoKCQkJCQl9CgoJCQkJCWZsb2F0IHRoZXRhID0gZFRoZXRhICogZmxvYXQoIGkgKTsKCQkJCQlnbF9GcmFnQ29sb3IucmdiICs9IHdlaWdodHNbIGkgXSAqIGdldFNhbXBsZSggLTEuMCAqIHRoZXRhLCBheGlzICk7CgkJCQkJZ2xfRnJhZ0NvbG9yLnJnYiArPSB3ZWlnaHRzWyBpIF0gKiBnZXRTYW1wbGUoIHRoZXRhLCBheGlzICk7CgoJCQkJfQoKCQkJfQoJCWAsYmxlbmRpbmc6am4sZGVwdGhUZXN0OiExLGRlcHRoV3JpdGU6ITF9KX1mdW5jdGlvbiB2Zygpe2xldCBuPW5ldyBLKDEsMSk7cmV0dXJuIG5ldyBYcih7bmFtZToiRXF1aXJlY3Rhbmd1bGFyVG9DdWJlVVYiLHVuaWZvcm1zOntlbnZNYXA6e3ZhbHVlOm51bGx9LHRleGVsU2l6ZTp7dmFsdWU6bn19LHZlcnRleFNoYWRlcjpqaCgpLGZyYWdtZW50U2hhZGVyOmAKCgkJCXByZWNpc2lvbiBtZWRpdW1wIGZsb2F0OwoJCQlwcmVjaXNpb24gbWVkaXVtcCBpbnQ7CgoJCQl2YXJ5aW5nIHZlYzMgdk91dHB1dERpcmVjdGlvbjsKCgkJCXVuaWZvcm0gc2FtcGxlcjJEIGVudk1hcDsKCQkJdW5pZm9ybSB2ZWMyIHRleGVsU2l6ZTsKCgkJCSNpbmNsdWRlIDxjb21tb24+CgoJCQl2b2lkIG1haW4oKSB7CgoJCQkJZ2xfRnJhZ0NvbG9yID0gdmVjNCggMC4wLCAwLjAsIDAuMCwgMS4wICk7CgoJCQkJdmVjMyBvdXRwdXREaXJlY3Rpb24gPSBub3JtYWxpemUoIHZPdXRwdXREaXJlY3Rpb24gKTsKCQkJCXZlYzIgdXYgPSBlcXVpcmVjdFV2KCBvdXRwdXREaXJlY3Rpb24gKTsKCgkJCQl2ZWMyIGYgPSBmcmFjdCggdXYgLyB0ZXhlbFNpemUgLSAwLjUgKTsKCQkJCXV2IC09IGYgKiB0ZXhlbFNpemU7CgkJCQl2ZWMzIHRsID0gdGV4dHVyZTJEICggZW52TWFwLCB1diApLnJnYjsKCQkJCXV2LnggKz0gdGV4ZWxTaXplLng7CgkJCQl2ZWMzIHRyID0gdGV4dHVyZTJEICggZW52TWFwLCB1diApLnJnYjsKCQkJCXV2LnkgKz0gdGV4ZWxTaXplLnk7CgkJCQl2ZWMzIGJyID0gdGV4dHVyZTJEICggZW52TWFwLCB1diApLnJnYjsKCQkJCXV2LnggLT0gdGV4ZWxTaXplLng7CgkJCQl2ZWMzIGJsID0gdGV4dHVyZTJEICggZW52TWFwLCB1diApLnJnYjsKCgkJCQl2ZWMzIHRtID0gbWl4KCB0bCwgdHIsIGYueCApOwoJCQkJdmVjMyBibSA9IG1peCggYmwsIGJyLCBmLnggKTsKCQkJCWdsX0ZyYWdDb2xvci5yZ2IgPSBtaXgoIHRtLCBibSwgZi55ICk7CgoJCQl9CgkJYCxibGVuZGluZzpqbixkZXB0aFRlc3Q6ITEsZGVwdGhXcml0ZTohMX0pfWZ1bmN0aW9uIF9nKCl7cmV0dXJuIG5ldyBYcih7bmFtZToiQ3ViZW1hcFRvQ3ViZVVWIix1bmlmb3Jtczp7ZW52TWFwOnt2YWx1ZTpudWxsfSxmbGlwRW52TWFwOnt2YWx1ZTotMX19LHZlcnRleFNoYWRlcjpqaCgpLGZyYWdtZW50U2hhZGVyOmAKCgkJCXByZWNpc2lvbiBtZWRpdW1wIGZsb2F0OwoJCQlwcmVjaXNpb24gbWVkaXVtcCBpbnQ7CgoJCQl1bmlmb3JtIGZsb2F0IGZsaXBFbnZNYXA7CgoJCQl2YXJ5aW5nIHZlYzMgdk91dHB1dERpcmVjdGlvbjsKCgkJCXVuaWZvcm0gc2FtcGxlckN1YmUgZW52TWFwOwoKCQkJdm9pZCBtYWluKCkgewoKCQkJCWdsX0ZyYWdDb2xvciA9IHRleHR1cmVDdWJlKCBlbnZNYXAsIHZlYzMoIGZsaXBFbnZNYXAgKiB2T3V0cHV0RGlyZWN0aW9uLngsIHZPdXRwdXREaXJlY3Rpb24ueXogKSApOwoKCQkJfQoJCWAsYmxlbmRpbmc6am4sZGVwdGhUZXN0OiExLGRlcHRoV3JpdGU6ITF9KX1mdW5jdGlvbiBqaCgpe3JldHVybmAKCgkJcHJlY2lzaW9uIG1lZGl1bXAgZmxvYXQ7CgkJcHJlY2lzaW9uIG1lZGl1bXAgaW50OwoKCQlhdHRyaWJ1dGUgdmVjMyBwb3NpdGlvbjsKCQlhdHRyaWJ1dGUgdmVjMiB1djsKCQlhdHRyaWJ1dGUgZmxvYXQgZmFjZUluZGV4OwoKCQl2YXJ5aW5nIHZlYzMgdk91dHB1dERpcmVjdGlvbjsKCgkJLy8gUkggY29vcmRpbmF0ZSBzeXN0ZW07IFBNUkVNIGZhY2UtaW5kZXhpbmcgY29udmVudGlvbgoJCXZlYzMgZ2V0RGlyZWN0aW9uKCB2ZWMyIHV2LCBmbG9hdCBmYWNlICkgewoKCQkJdXYgPSAyLjAgKiB1diAtIDEuMDsKCgkJCXZlYzMgZGlyZWN0aW9uID0gdmVjMyggdXYsIDEuMCApOwoKCQkJaWYgKCBmYWNlID09IDAuMCApIHsKCgkJCQlkaXJlY3Rpb24gPSBkaXJlY3Rpb24uenl4OyAvLyAoIDEsIHYsIHUgKSBwb3MgeAoKCQkJfSBlbHNlIGlmICggZmFjZSA9PSAxLjAgKSB7CgoJCQkJZGlyZWN0aW9uID0gZGlyZWN0aW9uLnh6eTsKCQkJCWRpcmVjdGlvbi54eiAqPSAtMS4wOyAvLyAoIC11LCAxLCAtdiApIHBvcyB5CgoJCQl9IGVsc2UgaWYgKCBmYWNlID09IDIuMCApIHsKCgkJCQlkaXJlY3Rpb24ueCAqPSAtMS4wOyAvLyAoIC11LCB2LCAxICkgcG9zIHoKCgkJCX0gZWxzZSBpZiAoIGZhY2UgPT0gMy4wICkgewoKCQkJCWRpcmVjdGlvbiA9IGRpcmVjdGlvbi56eXg7CgkJCQlkaXJlY3Rpb24ueHogKj0gLTEuMDsgLy8gKCAtMSwgdiwgLXUgKSBuZWcgeAoKCQkJfSBlbHNlIGlmICggZmFjZSA9PSA0LjAgKSB7CgoJCQkJZGlyZWN0aW9uID0gZGlyZWN0aW9uLnh6eTsKCQkJCWRpcmVjdGlvbi54eSAqPSAtMS4wOyAvLyAoIC11LCAtMSwgdiApIG5lZyB5CgoJCQl9IGVsc2UgaWYgKCBmYWNlID09IDUuMCApIHsKCgkJCQlkaXJlY3Rpb24ueiAqPSAtMS4wOyAvLyAoIHUsIHYsIC0xICkgbmVnIHoKCgkJCX0KCgkJCXJldHVybiBkaXJlY3Rpb247CgoJCX0KCgkJdm9pZCBtYWluKCkgewoKCQkJdk91dHB1dERpcmVjdGlvbiA9IGdldERpcmVjdGlvbiggdXYsIGZhY2VJbmRleCApOwoJCQlnbF9Qb3NpdGlvbiA9IHZlYzQoIHBvc2l0aW9uLCAxLjAgKTsKCgkJfQoJYH1mdW5jdGlvbiB0UyhuKXtsZXQgdD1uZXcgV2Vha01hcCxlPW51bGw7ZnVuY3Rpb24gaShhKXtpZihhJiZhLmlzVGV4dHVyZSl7bGV0IGw9YS5tYXBwaW5nLGM9bD09PVV1fHxsPT09QnUsdT1sPT09QW98fGw9PT1DbztpZihjfHx1KWlmKGEuaXNSZW5kZXJUYXJnZXRUZXh0dXJlJiZhLm5lZWRzUE1SRU1VcGRhdGU9PT0hMCl7YS5uZWVkc1BNUkVNVXBkYXRlPSExO2xldCBoPXQuZ2V0KGEpO3JldHVybiBlPT09bnVsbCYmKGU9bmV3IGFsKG4pKSxoPWM/ZS5mcm9tRXF1aXJlY3Rhbmd1bGFyKGEsaCk6ZS5mcm9tQ3ViZW1hcChhLGgpLHQuc2V0KGEsaCksaC50ZXh0dXJlfWVsc2V7aWYodC5oYXMoYSkpcmV0dXJuIHQuZ2V0KGEpLnRleHR1cmU7e2xldCBoPWEuaW1hZ2U7aWYoYyYmaCYmaC5oZWlnaHQ+MHx8dSYmaCYmcihoKSl7ZT09PW51bGwmJihlPW5ldyBhbChuKSk7bGV0IGY9Yz9lLmZyb21FcXVpcmVjdGFuZ3VsYXIoYSk6ZS5mcm9tQ3ViZW1hcChhKTtyZXR1cm4gdC5zZXQoYSxmKSxhLmFkZEV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLHMpLGYudGV4dHVyZX1lbHNlIHJldHVybiBudWxsfX19cmV0dXJuIGF9ZnVuY3Rpb24gcihhKXtsZXQgbD0wLGM9Njtmb3IobGV0IHU9MDt1PGM7dSsrKWFbdV0hPT12b2lkIDAmJmwrKztyZXR1cm4gbD09PWN9ZnVuY3Rpb24gcyhhKXtsZXQgbD1hLnRhcmdldDtsLnJlbW92ZUV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLHMpO2xldCBjPXQuZ2V0KGwpO2MhPT12b2lkIDAmJih0LmRlbGV0ZShsKSxjLmRpc3Bvc2UoKSl9ZnVuY3Rpb24gbygpe3Q9bmV3IFdlYWtNYXAsZSE9PW51bGwmJihlLmRpc3Bvc2UoKSxlPW51bGwpfXJldHVybntnZXQ6aSxkaXNwb3NlOm99fWZ1bmN0aW9uIGVTKG4pe2xldCB0PXt9O2Z1bmN0aW9uIGUoaSl7aWYodFtpXSE9PXZvaWQgMClyZXR1cm4gdFtpXTtsZXQgcjtzd2l0Y2goaSl7Y2FzZSJXRUJHTF9kZXB0aF90ZXh0dXJlIjpyPW4uZ2V0RXh0ZW5zaW9uKCJXRUJHTF9kZXB0aF90ZXh0dXJlIil8fG4uZ2V0RXh0ZW5zaW9uKCJNT1pfV0VCR0xfZGVwdGhfdGV4dHVyZSIpfHxuLmdldEV4dGVuc2lvbigiV0VCS0lUX1dFQkdMX2RlcHRoX3RleHR1cmUiKTticmVhaztjYXNlIkVYVF90ZXh0dXJlX2ZpbHRlcl9hbmlzb3Ryb3BpYyI6cj1uLmdldEV4dGVuc2lvbigiRVhUX3RleHR1cmVfZmlsdGVyX2FuaXNvdHJvcGljIil8fG4uZ2V0RXh0ZW5zaW9uKCJNT1pfRVhUX3RleHR1cmVfZmlsdGVyX2FuaXNvdHJvcGljIil8fG4uZ2V0RXh0ZW5zaW9uKCJXRUJLSVRfRVhUX3RleHR1cmVfZmlsdGVyX2FuaXNvdHJvcGljIik7YnJlYWs7Y2FzZSJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfczN0YyI6cj1uLmdldEV4dGVuc2lvbigiV0VCR0xfY29tcHJlc3NlZF90ZXh0dXJlX3MzdGMiKXx8bi5nZXRFeHRlbnNpb24oIk1PWl9XRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfczN0YyIpfHxuLmdldEV4dGVuc2lvbigiV0VCS0lUX1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjIik7YnJlYWs7Y2FzZSJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfcHZydGMiOnI9bi5nZXRFeHRlbnNpb24oIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9wdnJ0YyIpfHxuLmdldEV4dGVuc2lvbigiV0VCS0lUX1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9wdnJ0YyIpO2JyZWFrO2RlZmF1bHQ6cj1uLmdldEV4dGVuc2lvbihpKX1yZXR1cm4gdFtpXT1yLHJ9cmV0dXJue2hhczpmdW5jdGlvbihpKXtyZXR1cm4gZShpKSE9PW51bGx9LGluaXQ6ZnVuY3Rpb24oaSl7aS5pc1dlYkdMMj9lKCJFWFRfY29sb3JfYnVmZmVyX2Zsb2F0Iik6KGUoIldFQkdMX2RlcHRoX3RleHR1cmUiKSxlKCJPRVNfdGV4dHVyZV9mbG9hdCIpLGUoIk9FU190ZXh0dXJlX2hhbGZfZmxvYXQiKSxlKCJPRVNfdGV4dHVyZV9oYWxmX2Zsb2F0X2xpbmVhciIpLGUoIk9FU19zdGFuZGFyZF9kZXJpdmF0aXZlcyIpLGUoIk9FU19lbGVtZW50X2luZGV4X3VpbnQiKSxlKCJPRVNfdmVydGV4X2FycmF5X29iamVjdCIpLGUoIkFOR0xFX2luc3RhbmNlZF9hcnJheXMiKSksZSgiT0VTX3RleHR1cmVfZmxvYXRfbGluZWFyIiksZSgiRVhUX2NvbG9yX2J1ZmZlcl9oYWxmX2Zsb2F0IiksZSgiV0VCR0xfbXVsdGlzYW1wbGVkX3JlbmRlcl90b190ZXh0dXJlIil9LGdldDpmdW5jdGlvbihpKXtsZXQgcj1lKGkpO3JldHVybiByPT09bnVsbCYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAiK2krIiBleHRlbnNpb24gbm90IHN1cHBvcnRlZC4iKSxyfX19ZnVuY3Rpb24gblMobix0LGUsaSl7bGV0IHI9e30scz1uZXcgV2Vha01hcDtmdW5jdGlvbiBvKGgpe2xldCBmPWgudGFyZ2V0O2YuaW5kZXghPT1udWxsJiZ0LnJlbW92ZShmLmluZGV4KTtmb3IobGV0IGcgaW4gZi5hdHRyaWJ1dGVzKXQucmVtb3ZlKGYuYXR0cmlidXRlc1tnXSk7Zi5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixvKSxkZWxldGUgcltmLmlkXTtsZXQgZD1zLmdldChmKTtkJiYodC5yZW1vdmUoZCkscy5kZWxldGUoZikpLGkucmVsZWFzZVN0YXRlc09mR2VvbWV0cnkoZiksZi5pc0luc3RhbmNlZEJ1ZmZlckdlb21ldHJ5PT09ITAmJmRlbGV0ZSBmLl9tYXhJbnN0YW5jZUNvdW50LGUubWVtb3J5Lmdlb21ldHJpZXMtLX1mdW5jdGlvbiBhKGgsZil7cmV0dXJuIHJbZi5pZF09PT0hMHx8KGYuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsbykscltmLmlkXT0hMCxlLm1lbW9yeS5nZW9tZXRyaWVzKyspLGZ9ZnVuY3Rpb24gbChoKXtsZXQgZj1oLmF0dHJpYnV0ZXM7Zm9yKGxldCBnIGluIGYpdC51cGRhdGUoZltnXSwzNDk2Mik7bGV0IGQ9aC5tb3JwaEF0dHJpYnV0ZXM7Zm9yKGxldCBnIGluIGQpe2xldCB4PWRbZ107Zm9yKGxldCB2PTAsbT14Lmxlbmd0aDt2PG07disrKXQudXBkYXRlKHhbdl0sMzQ5NjIpfX1mdW5jdGlvbiBjKGgpe2xldCBmPVtdLGQ9aC5pbmRleCxnPWguYXR0cmlidXRlcy5wb3NpdGlvbix4PTA7aWYoZCE9PW51bGwpe2xldCBwPWQuYXJyYXk7eD1kLnZlcnNpb247Zm9yKGxldCBiPTAsXz1wLmxlbmd0aDtiPF87Yis9Myl7bGV0IFM9cFtiKzBdLEw9cFtiKzFdLEE9cFtiKzJdO2YucHVzaChTLEwsTCxBLEEsUyl9fWVsc2V7bGV0IHA9Zy5hcnJheTt4PWcudmVyc2lvbjtmb3IobGV0IGI9MCxfPXAubGVuZ3RoLzMtMTtiPF87Yis9Myl7bGV0IFM9YiswLEw9YisxLEE9YisyO2YucHVzaChTLEwsTCxBLEEsUyl9fWxldCB2PW5ldyhwMChmKT9zbDpybCkoZiwxKTt2LnZlcnNpb249eDtsZXQgbT1zLmdldChoKTttJiZ0LnJlbW92ZShtKSxzLnNldChoLHYpfWZ1bmN0aW9uIHUoaCl7bGV0IGY9cy5nZXQoaCk7aWYoZil7bGV0IGQ9aC5pbmRleDtkIT09bnVsbCYmZi52ZXJzaW9uPGQudmVyc2lvbiYmYyhoKX1lbHNlIGMoaCk7cmV0dXJuIHMuZ2V0KGgpfXJldHVybntnZXQ6YSx1cGRhdGU6bCxnZXRXaXJlZnJhbWVBdHRyaWJ1dGU6dX19ZnVuY3Rpb24gaVMobix0LGUsaSl7bGV0IHI9aS5pc1dlYkdMMixzO2Z1bmN0aW9uIG8oZil7cz1mfWxldCBhLGw7ZnVuY3Rpb24gYyhmKXthPWYudHlwZSxsPWYuYnl0ZXNQZXJFbGVtZW50fWZ1bmN0aW9uIHUoZixkKXtuLmRyYXdFbGVtZW50cyhzLGQsYSxmKmwpLGUudXBkYXRlKGQscywxKX1mdW5jdGlvbiBoKGYsZCxnKXtpZihnPT09MClyZXR1cm47bGV0IHgsdjtpZihyKXg9bix2PSJkcmF3RWxlbWVudHNJbnN0YW5jZWQiO2Vsc2UgaWYoeD10LmdldCgiQU5HTEVfaW5zdGFuY2VkX2FycmF5cyIpLHY9ImRyYXdFbGVtZW50c0luc3RhbmNlZEFOR0xFIix4PT09bnVsbCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xJbmRleGVkQnVmZmVyUmVuZGVyZXI6IHVzaW5nIFRIUkVFLkluc3RhbmNlZEJ1ZmZlckdlb21ldHJ5IGJ1dCBoYXJkd2FyZSBkb2VzIG5vdCBzdXBwb3J0IGV4dGVuc2lvbiBBTkdMRV9pbnN0YW5jZWRfYXJyYXlzLiIpO3JldHVybn14W3ZdKHMsZCxhLGYqbCxnKSxlLnVwZGF0ZShkLHMsZyl9dGhpcy5zZXRNb2RlPW8sdGhpcy5zZXRJbmRleD1jLHRoaXMucmVuZGVyPXUsdGhpcy5yZW5kZXJJbnN0YW5jZXM9aH1mdW5jdGlvbiByUyhuKXtsZXQgdD17Z2VvbWV0cmllczowLHRleHR1cmVzOjB9LGU9e2ZyYW1lOjAsY2FsbHM6MCx0cmlhbmdsZXM6MCxwb2ludHM6MCxsaW5lczowfTtmdW5jdGlvbiBpKHMsbyxhKXtzd2l0Y2goZS5jYWxscysrLG8pe2Nhc2UgNDplLnRyaWFuZ2xlcys9YSoocy8zKTticmVhaztjYXNlIDE6ZS5saW5lcys9YSoocy8yKTticmVhaztjYXNlIDM6ZS5saW5lcys9YSoocy0xKTticmVhaztjYXNlIDI6ZS5saW5lcys9YSpzO2JyZWFrO2Nhc2UgMDplLnBvaW50cys9YSpzO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xJbmZvOiBVbmtub3duIGRyYXcgbW9kZToiLG8pO2JyZWFrfX1mdW5jdGlvbiByKCl7ZS5mcmFtZSsrLGUuY2FsbHM9MCxlLnRyaWFuZ2xlcz0wLGUucG9pbnRzPTAsZS5saW5lcz0wfXJldHVybnttZW1vcnk6dCxyZW5kZXI6ZSxwcm9ncmFtczpudWxsLGF1dG9SZXNldDohMCxyZXNldDpyLHVwZGF0ZTppfX12YXIgY289Y2xhc3MgZXh0ZW5kcyBhZXtjb25zdHJ1Y3Rvcih0PW51bGwsZT0xLGk9MSxyPTEpe3N1cGVyKG51bGwpLHRoaXMuaW1hZ2U9e2RhdGE6dCx3aWR0aDplLGhlaWdodDppLGRlcHRoOnJ9LHRoaXMubWFnRmlsdGVyPWZlLHRoaXMubWluRmlsdGVyPWZlLHRoaXMud3JhcFI9VmUsdGhpcy5nZW5lcmF0ZU1pcG1hcHM9ITEsdGhpcy5mbGlwWT0hMSx0aGlzLnVucGFja0FsaWdubWVudD0xfX07Y28ucHJvdG90eXBlLmlzRGF0YVRleHR1cmUyREFycmF5PSEwO2Z1bmN0aW9uIHNTKG4sdCl7cmV0dXJuIG5bMF0tdFswXX1mdW5jdGlvbiBvUyhuLHQpe3JldHVybiBNYXRoLmFicyh0WzFdKS1NYXRoLmFicyhuWzFdKX1mdW5jdGlvbiB3ZyhuLHQpe2xldCBlPTEsaT10LmlzSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGU/dC5kYXRhLmFycmF5OnQuYXJyYXk7aSBpbnN0YW5jZW9mIEludDhBcnJheT9lPTEyNzppIGluc3RhbmNlb2YgSW50MTZBcnJheT9lPTMyNzY3OmkgaW5zdGFuY2VvZiBJbnQzMkFycmF5P2U9MjE0NzQ4MzY0Nzpjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTE1vcnBodGFyZ2V0czogVW5zdXBwb3J0ZWQgbW9ycGggYXR0cmlidXRlIGRhdGEgdHlwZTogIixpKSxuLmRpdmlkZVNjYWxhcihlKX1mdW5jdGlvbiBhUyhuLHQsZSl7bGV0IGk9e30scj1uZXcgRmxvYXQzMkFycmF5KDgpLHM9bmV3IFdlYWtNYXAsbz1uZXcgVCxhPVtdO2ZvcihsZXQgYz0wO2M8ODtjKyspYVtjXT1bYywwXTtmdW5jdGlvbiBsKGMsdSxoLGYpe2xldCBkPWMubW9ycGhUYXJnZXRJbmZsdWVuY2VzO2lmKHQuaXNXZWJHTDI9PT0hMCl7bGV0IGc9dS5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24ubGVuZ3RoLHg9cy5nZXQodSk7aWYoeD09PXZvaWQgMHx8eC5jb3VudCE9PWcpe2xldCBSPWZ1bmN0aW9uKCl7WC5kaXNwb3NlKCkscy5kZWxldGUodSksdS5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixSKX07eCE9PXZvaWQgMCYmeC50ZXh0dXJlLmRpc3Bvc2UoKTtsZXQgcD11Lm1vcnBoQXR0cmlidXRlcy5ub3JtYWwhPT12b2lkIDAsYj11Lm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbixfPXUubW9ycGhBdHRyaWJ1dGVzLm5vcm1hbHx8W10sUz11LmF0dHJpYnV0ZXMucG9zaXRpb24uY291bnQsTD1wPT09ITA/MjoxLEE9UypMLEg9MTtBPnQubWF4VGV4dHVyZVNpemUmJihIPU1hdGguY2VpbChBL3QubWF4VGV4dHVyZVNpemUpLEE9dC5tYXhUZXh0dXJlU2l6ZSk7bGV0IHR0PW5ldyBGbG9hdDMyQXJyYXkoQSpIKjQqZyksWD1uZXcgY28odHQsQSxILGcpO1guZm9ybWF0PVJlLFgudHlwZT1VaSxYLm5lZWRzVXBkYXRlPSEwO2xldCB5PUwqNDtmb3IobGV0IEQ9MDtEPGc7RCsrKXtsZXQgRj1iW0RdLHo9X1tEXSxOPUEqSCo0KkQ7Zm9yKGxldCBWPTA7VjxGLmNvdW50O1YrKyl7by5mcm9tQnVmZmVyQXR0cmlidXRlKEYsViksRi5ub3JtYWxpemVkPT09ITAmJndnKG8sRik7bGV0IFE9Vip5O3R0W04rUSswXT1vLngsdHRbTitRKzFdPW8ueSx0dFtOK1ErMl09by56LHR0W04rUSszXT0wLHA9PT0hMCYmKG8uZnJvbUJ1ZmZlckF0dHJpYnV0ZSh6LFYpLHoubm9ybWFsaXplZD09PSEwJiZ3ZyhvLHopLHR0W04rUSs0XT1vLngsdHRbTitRKzVdPW8ueSx0dFtOK1ErNl09by56LHR0W04rUSs3XT0wKX19eD17Y291bnQ6Zyx0ZXh0dXJlOlgsc2l6ZTpuZXcgSyhBLEgpfSxzLnNldCh1LHgpLHUuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsUil9bGV0IHY9MDtmb3IobGV0IHA9MDtwPGQubGVuZ3RoO3ArKyl2Kz1kW3BdO2xldCBtPXUubW9ycGhUYXJnZXRzUmVsYXRpdmU/MToxLXY7Zi5nZXRVbmlmb3JtcygpLnNldFZhbHVlKG4sIm1vcnBoVGFyZ2V0QmFzZUluZmx1ZW5jZSIsbSksZi5nZXRVbmlmb3JtcygpLnNldFZhbHVlKG4sIm1vcnBoVGFyZ2V0SW5mbHVlbmNlcyIsZCksZi5nZXRVbmlmb3JtcygpLnNldFZhbHVlKG4sIm1vcnBoVGFyZ2V0c1RleHR1cmUiLHgudGV4dHVyZSxlKSxmLmdldFVuaWZvcm1zKCkuc2V0VmFsdWUobiwibW9ycGhUYXJnZXRzVGV4dHVyZVNpemUiLHguc2l6ZSl9ZWxzZXtsZXQgZz1kPT09dm9pZCAwPzA6ZC5sZW5ndGgseD1pW3UuaWRdO2lmKHg9PT12b2lkIDB8fHgubGVuZ3RoIT09Zyl7eD1bXTtmb3IobGV0IF89MDtfPGc7XysrKXhbX109W18sMF07aVt1LmlkXT14fWZvcihsZXQgXz0wO188ZztfKyspe2xldCBTPXhbX107U1swXT1fLFNbMV09ZFtfXX14LnNvcnQob1MpO2ZvcihsZXQgXz0wO188ODtfKyspXzxnJiZ4W19dWzFdPyhhW19dWzBdPXhbX11bMF0sYVtfXVsxXT14W19dWzFdKTooYVtfXVswXT1OdW1iZXIuTUFYX1NBRkVfSU5URUdFUixhW19dWzFdPTApO2Euc29ydChzUyk7bGV0IHY9dS5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24sbT11Lm1vcnBoQXR0cmlidXRlcy5ub3JtYWwscD0wO2ZvcihsZXQgXz0wO188ODtfKyspe2xldCBTPWFbX10sTD1TWzBdLEE9U1sxXTtMIT09TnVtYmVyLk1BWF9TQUZFX0lOVEVHRVImJkE/KHYmJnUuZ2V0QXR0cmlidXRlKCJtb3JwaFRhcmdldCIrXykhPT12W0xdJiZ1LnNldEF0dHJpYnV0ZSgibW9ycGhUYXJnZXQiK18sdltMXSksbSYmdS5nZXRBdHRyaWJ1dGUoIm1vcnBoTm9ybWFsIitfKSE9PW1bTF0mJnUuc2V0QXR0cmlidXRlKCJtb3JwaE5vcm1hbCIrXyxtW0xdKSxyW19dPUEscCs9QSk6KHYmJnUuaGFzQXR0cmlidXRlKCJtb3JwaFRhcmdldCIrXyk9PT0hMCYmdS5kZWxldGVBdHRyaWJ1dGUoIm1vcnBoVGFyZ2V0IitfKSxtJiZ1Lmhhc0F0dHJpYnV0ZSgibW9ycGhOb3JtYWwiK18pPT09ITAmJnUuZGVsZXRlQXR0cmlidXRlKCJtb3JwaE5vcm1hbCIrXykscltfXT0wKX1sZXQgYj11Lm1vcnBoVGFyZ2V0c1JlbGF0aXZlPzE6MS1wO2YuZ2V0VW5pZm9ybXMoKS5zZXRWYWx1ZShuLCJtb3JwaFRhcmdldEJhc2VJbmZsdWVuY2UiLGIpLGYuZ2V0VW5pZm9ybXMoKS5zZXRWYWx1ZShuLCJtb3JwaFRhcmdldEluZmx1ZW5jZXMiLHIpfX1yZXR1cm57dXBkYXRlOmx9fWZ1bmN0aW9uIGxTKG4sdCxlLGkpe2xldCByPW5ldyBXZWFrTWFwO2Z1bmN0aW9uIHMobCl7bGV0IGM9aS5yZW5kZXIuZnJhbWUsdT1sLmdlb21ldHJ5LGg9dC5nZXQobCx1KTtyZXR1cm4gci5nZXQoaCkhPT1jJiYodC51cGRhdGUoaCksci5zZXQoaCxjKSksbC5pc0luc3RhbmNlZE1lc2gmJihsLmhhc0V2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLGEpPT09ITEmJmwuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsYSksZS51cGRhdGUobC5pbnN0YW5jZU1hdHJpeCwzNDk2MiksbC5pbnN0YW5jZUNvbG9yIT09bnVsbCYmZS51cGRhdGUobC5pbnN0YW5jZUNvbG9yLDM0OTYyKSksaH1mdW5jdGlvbiBvKCl7cj1uZXcgV2Vha01hcH1mdW5jdGlvbiBhKGwpe2xldCBjPWwudGFyZ2V0O2MucmVtb3ZlRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsYSksZS5yZW1vdmUoYy5pbnN0YW5jZU1hdHJpeCksYy5pbnN0YW5jZUNvbG9yIT09bnVsbCYmZS5yZW1vdmUoYy5pbnN0YW5jZUNvbG9yKX1yZXR1cm57dXBkYXRlOnMsZGlzcG9zZTpvfX12YXIgbGw9Y2xhc3MgZXh0ZW5kcyBhZXtjb25zdHJ1Y3Rvcih0PW51bGwsZT0xLGk9MSxyPTEpe3N1cGVyKG51bGwpLHRoaXMuaW1hZ2U9e2RhdGE6dCx3aWR0aDplLGhlaWdodDppLGRlcHRoOnJ9LHRoaXMubWFnRmlsdGVyPWZlLHRoaXMubWluRmlsdGVyPWZlLHRoaXMud3JhcFI9VmUsdGhpcy5nZW5lcmF0ZU1pcG1hcHM9ITEsdGhpcy5mbGlwWT0hMSx0aGlzLnVucGFja0FsaWdubWVudD0xfX07bGwucHJvdG90eXBlLmlzRGF0YVRleHR1cmUzRD0hMDt2YXIgdjA9bmV3IGFlLF8wPW5ldyBjbyx3MD1uZXcgbGwsTTA9bmV3IFdyLE1nPVtdLGJnPVtdLFNnPW5ldyBGbG9hdDMyQXJyYXkoMTYpLEVnPW5ldyBGbG9hdDMyQXJyYXkoOSksVGc9bmV3IEZsb2F0MzJBcnJheSg0KTtmdW5jdGlvbiBlcyhuLHQsZSl7bGV0IGk9blswXTtpZihpPD0wfHxpPjApcmV0dXJuIG47bGV0IHI9dCplLHM9TWdbcl07aWYocz09PXZvaWQgMCYmKHM9bmV3IEZsb2F0MzJBcnJheShyKSxNZ1tyXT1zKSx0IT09MCl7aS50b0FycmF5KHMsMCk7Zm9yKGxldCBvPTEsYT0wO28hPT10OysrbylhKz1lLG5bb10udG9BcnJheShzLGEpfXJldHVybiBzfWZ1bmN0aW9uIExlKG4sdCl7aWYobi5sZW5ndGghPT10Lmxlbmd0aClyZXR1cm4hMTtmb3IobGV0IGU9MCxpPW4ubGVuZ3RoO2U8aTtlKyspaWYobltlXSE9PXRbZV0pcmV0dXJuITE7cmV0dXJuITB9ZnVuY3Rpb24gVGUobix0KXtmb3IobGV0IGU9MCxpPXQubGVuZ3RoO2U8aTtlKyspbltlXT10W2VdfWZ1bmN0aW9uIFBsKG4sdCl7bGV0IGU9YmdbdF07ZT09PXZvaWQgMCYmKGU9bmV3IEludDMyQXJyYXkodCksYmdbdF09ZSk7Zm9yKGxldCBpPTA7aSE9PXQ7KytpKWVbaV09bi5hbGxvY2F0ZVRleHR1cmVVbml0KCk7cmV0dXJuIGV9ZnVuY3Rpb24gY1Mobix0KXtsZXQgZT10aGlzLmNhY2hlO2VbMF0hPT10JiYobi51bmlmb3JtMWYodGhpcy5hZGRyLHQpLGVbMF09dCl9ZnVuY3Rpb24gdVMobix0KXtsZXQgZT10aGlzLmNhY2hlO2lmKHQueCE9PXZvaWQgMCkoZVswXSE9PXQueHx8ZVsxXSE9PXQueSkmJihuLnVuaWZvcm0yZih0aGlzLmFkZHIsdC54LHQueSksZVswXT10LngsZVsxXT10LnkpO2Vsc2V7aWYoTGUoZSx0KSlyZXR1cm47bi51bmlmb3JtMmZ2KHRoaXMuYWRkcix0KSxUZShlLHQpfX1mdW5jdGlvbiBoUyhuLHQpe2xldCBlPXRoaXMuY2FjaGU7aWYodC54IT09dm9pZCAwKShlWzBdIT09dC54fHxlWzFdIT09dC55fHxlWzJdIT09dC56KSYmKG4udW5pZm9ybTNmKHRoaXMuYWRkcix0LngsdC55LHQueiksZVswXT10LngsZVsxXT10LnksZVsyXT10LnopO2Vsc2UgaWYodC5yIT09dm9pZCAwKShlWzBdIT09dC5yfHxlWzFdIT09dC5nfHxlWzJdIT09dC5iKSYmKG4udW5pZm9ybTNmKHRoaXMuYWRkcix0LnIsdC5nLHQuYiksZVswXT10LnIsZVsxXT10LmcsZVsyXT10LmIpO2Vsc2V7aWYoTGUoZSx0KSlyZXR1cm47bi51bmlmb3JtM2Z2KHRoaXMuYWRkcix0KSxUZShlLHQpfX1mdW5jdGlvbiBmUyhuLHQpe2xldCBlPXRoaXMuY2FjaGU7aWYodC54IT09dm9pZCAwKShlWzBdIT09dC54fHxlWzFdIT09dC55fHxlWzJdIT09dC56fHxlWzNdIT09dC53KSYmKG4udW5pZm9ybTRmKHRoaXMuYWRkcix0LngsdC55LHQueix0LncpLGVbMF09dC54LGVbMV09dC55LGVbMl09dC56LGVbM109dC53KTtlbHNle2lmKExlKGUsdCkpcmV0dXJuO24udW5pZm9ybTRmdih0aGlzLmFkZHIsdCksVGUoZSx0KX19ZnVuY3Rpb24gZFMobix0KXtsZXQgZT10aGlzLmNhY2hlLGk9dC5lbGVtZW50cztpZihpPT09dm9pZCAwKXtpZihMZShlLHQpKXJldHVybjtuLnVuaWZvcm1NYXRyaXgyZnYodGhpcy5hZGRyLCExLHQpLFRlKGUsdCl9ZWxzZXtpZihMZShlLGkpKXJldHVybjtUZy5zZXQoaSksbi51bmlmb3JtTWF0cml4MmZ2KHRoaXMuYWRkciwhMSxUZyksVGUoZSxpKX19ZnVuY3Rpb24gcFMobix0KXtsZXQgZT10aGlzLmNhY2hlLGk9dC5lbGVtZW50cztpZihpPT09dm9pZCAwKXtpZihMZShlLHQpKXJldHVybjtuLnVuaWZvcm1NYXRyaXgzZnYodGhpcy5hZGRyLCExLHQpLFRlKGUsdCl9ZWxzZXtpZihMZShlLGkpKXJldHVybjtFZy5zZXQoaSksbi51bmlmb3JtTWF0cml4M2Z2KHRoaXMuYWRkciwhMSxFZyksVGUoZSxpKX19ZnVuY3Rpb24gbVMobix0KXtsZXQgZT10aGlzLmNhY2hlLGk9dC5lbGVtZW50cztpZihpPT09dm9pZCAwKXtpZihMZShlLHQpKXJldHVybjtuLnVuaWZvcm1NYXRyaXg0ZnYodGhpcy5hZGRyLCExLHQpLFRlKGUsdCl9ZWxzZXtpZihMZShlLGkpKXJldHVybjtTZy5zZXQoaSksbi51bmlmb3JtTWF0cml4NGZ2KHRoaXMuYWRkciwhMSxTZyksVGUoZSxpKX19ZnVuY3Rpb24gZ1Mobix0KXtsZXQgZT10aGlzLmNhY2hlO2VbMF0hPT10JiYobi51bmlmb3JtMWkodGhpcy5hZGRyLHQpLGVbMF09dCl9ZnVuY3Rpb24geFMobix0KXtsZXQgZT10aGlzLmNhY2hlO0xlKGUsdCl8fChuLnVuaWZvcm0yaXYodGhpcy5hZGRyLHQpLFRlKGUsdCkpfWZ1bmN0aW9uIHlTKG4sdCl7bGV0IGU9dGhpcy5jYWNoZTtMZShlLHQpfHwobi51bmlmb3JtM2l2KHRoaXMuYWRkcix0KSxUZShlLHQpKX1mdW5jdGlvbiB2UyhuLHQpe2xldCBlPXRoaXMuY2FjaGU7TGUoZSx0KXx8KG4udW5pZm9ybTRpdih0aGlzLmFkZHIsdCksVGUoZSx0KSl9ZnVuY3Rpb24gX1Mobix0KXtsZXQgZT10aGlzLmNhY2hlO2VbMF0hPT10JiYobi51bmlmb3JtMXVpKHRoaXMuYWRkcix0KSxlWzBdPXQpfWZ1bmN0aW9uIHdTKG4sdCl7bGV0IGU9dGhpcy5jYWNoZTtMZShlLHQpfHwobi51bmlmb3JtMnVpdih0aGlzLmFkZHIsdCksVGUoZSx0KSl9ZnVuY3Rpb24gTVMobix0KXtsZXQgZT10aGlzLmNhY2hlO0xlKGUsdCl8fChuLnVuaWZvcm0zdWl2KHRoaXMuYWRkcix0KSxUZShlLHQpKX1mdW5jdGlvbiBiUyhuLHQpe2xldCBlPXRoaXMuY2FjaGU7TGUoZSx0KXx8KG4udW5pZm9ybTR1aXYodGhpcy5hZGRyLHQpLFRlKGUsdCkpfWZ1bmN0aW9uIFNTKG4sdCxlKXtsZXQgaT10aGlzLmNhY2hlLHI9ZS5hbGxvY2F0ZVRleHR1cmVVbml0KCk7aVswXSE9PXImJihuLnVuaWZvcm0xaSh0aGlzLmFkZHIsciksaVswXT1yKSxlLnNhZmVTZXRUZXh0dXJlMkQodHx8djAscil9ZnVuY3Rpb24gRVMobix0LGUpe2xldCBpPXRoaXMuY2FjaGUscj1lLmFsbG9jYXRlVGV4dHVyZVVuaXQoKTtpWzBdIT09ciYmKG4udW5pZm9ybTFpKHRoaXMuYWRkcixyKSxpWzBdPXIpLGUuc2V0VGV4dHVyZTNEKHR8fHcwLHIpfWZ1bmN0aW9uIFRTKG4sdCxlKXtsZXQgaT10aGlzLmNhY2hlLHI9ZS5hbGxvY2F0ZVRleHR1cmVVbml0KCk7aVswXSE9PXImJihuLnVuaWZvcm0xaSh0aGlzLmFkZHIsciksaVswXT1yKSxlLnNhZmVTZXRUZXh0dXJlQ3ViZSh0fHxNMCxyKX1mdW5jdGlvbiBBUyhuLHQsZSl7bGV0IGk9dGhpcy5jYWNoZSxyPWUuYWxsb2NhdGVUZXh0dXJlVW5pdCgpO2lbMF0hPT1yJiYobi51bmlmb3JtMWkodGhpcy5hZGRyLHIpLGlbMF09ciksZS5zZXRUZXh0dXJlMkRBcnJheSh0fHxfMCxyKX1mdW5jdGlvbiBDUyhuKXtzd2l0Y2gobil7Y2FzZSA1MTI2OnJldHVybiBjUztjYXNlIDM1NjY0OnJldHVybiB1UztjYXNlIDM1NjY1OnJldHVybiBoUztjYXNlIDM1NjY2OnJldHVybiBmUztjYXNlIDM1Njc0OnJldHVybiBkUztjYXNlIDM1Njc1OnJldHVybiBwUztjYXNlIDM1Njc2OnJldHVybiBtUztjYXNlIDUxMjQ6Y2FzZSAzNTY3MDpyZXR1cm4gZ1M7Y2FzZSAzNTY2NzpjYXNlIDM1NjcxOnJldHVybiB4UztjYXNlIDM1NjY4OmNhc2UgMzU2NzI6cmV0dXJuIHlTO2Nhc2UgMzU2Njk6Y2FzZSAzNTY3MzpyZXR1cm4gdlM7Y2FzZSA1MTI1OnJldHVybiBfUztjYXNlIDM2Mjk0OnJldHVybiB3UztjYXNlIDM2Mjk1OnJldHVybiBNUztjYXNlIDM2Mjk2OnJldHVybiBiUztjYXNlIDM1Njc4OmNhc2UgMzYxOTg6Y2FzZSAzNjI5ODpjYXNlIDM2MzA2OmNhc2UgMzU2ODI6cmV0dXJuIFNTO2Nhc2UgMzU2Nzk6Y2FzZSAzNjI5OTpjYXNlIDM2MzA3OnJldHVybiBFUztjYXNlIDM1NjgwOmNhc2UgMzYzMDA6Y2FzZSAzNjMwODpjYXNlIDM2MjkzOnJldHVybiBUUztjYXNlIDM2Mjg5OmNhc2UgMzYzMDM6Y2FzZSAzNjMxMTpjYXNlIDM2MjkyOnJldHVybiBBU319ZnVuY3Rpb24gUlMobix0KXtuLnVuaWZvcm0xZnYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIExTKG4sdCl7bGV0IGU9ZXModCx0aGlzLnNpemUsMik7bi51bmlmb3JtMmZ2KHRoaXMuYWRkcixlKX1mdW5jdGlvbiBQUyhuLHQpe2xldCBlPWVzKHQsdGhpcy5zaXplLDMpO24udW5pZm9ybTNmdih0aGlzLmFkZHIsZSl9ZnVuY3Rpb24gRFMobix0KXtsZXQgZT1lcyh0LHRoaXMuc2l6ZSw0KTtuLnVuaWZvcm00ZnYodGhpcy5hZGRyLGUpfWZ1bmN0aW9uIElTKG4sdCl7bGV0IGU9ZXModCx0aGlzLnNpemUsNCk7bi51bmlmb3JtTWF0cml4MmZ2KHRoaXMuYWRkciwhMSxlKX1mdW5jdGlvbiBOUyhuLHQpe2xldCBlPWVzKHQsdGhpcy5zaXplLDkpO24udW5pZm9ybU1hdHJpeDNmdih0aGlzLmFkZHIsITEsZSl9ZnVuY3Rpb24gRlMobix0KXtsZXQgZT1lcyh0LHRoaXMuc2l6ZSwxNik7bi51bmlmb3JtTWF0cml4NGZ2KHRoaXMuYWRkciwhMSxlKX1mdW5jdGlvbiB6UyhuLHQpe24udW5pZm9ybTFpdih0aGlzLmFkZHIsdCl9ZnVuY3Rpb24gVVMobix0KXtuLnVuaWZvcm0yaXYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIEJTKG4sdCl7bi51bmlmb3JtM2l2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBPUyhuLHQpe24udW5pZm9ybTRpdih0aGlzLmFkZHIsdCl9ZnVuY3Rpb24ga1Mobix0KXtuLnVuaWZvcm0xdWl2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBIUyhuLHQpe24udW5pZm9ybTJ1aXYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIFZTKG4sdCl7bi51bmlmb3JtM3Vpdih0aGlzLmFkZHIsdCl9ZnVuY3Rpb24gR1Mobix0KXtuLnVuaWZvcm00dWl2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBXUyhuLHQsZSl7bGV0IGk9dC5sZW5ndGgscj1QbChlLGkpO24udW5pZm9ybTFpdih0aGlzLmFkZHIscik7Zm9yKGxldCBzPTA7cyE9PWk7KytzKWUuc2FmZVNldFRleHR1cmUyRCh0W3NdfHx2MCxyW3NdKX1mdW5jdGlvbiBxUyhuLHQsZSl7bGV0IGk9dC5sZW5ndGgscj1QbChlLGkpO24udW5pZm9ybTFpdih0aGlzLmFkZHIscik7Zm9yKGxldCBzPTA7cyE9PWk7KytzKWUuc2V0VGV4dHVyZTNEKHRbc118fHcwLHJbc10pfWZ1bmN0aW9uIFhTKG4sdCxlKXtsZXQgaT10Lmxlbmd0aCxyPVBsKGUsaSk7bi51bmlmb3JtMWl2KHRoaXMuYWRkcixyKTtmb3IobGV0IHM9MDtzIT09aTsrK3MpZS5zYWZlU2V0VGV4dHVyZUN1YmUodFtzXXx8TTAscltzXSl9ZnVuY3Rpb24gWVMobix0LGUpe2xldCBpPXQubGVuZ3RoLHI9UGwoZSxpKTtuLnVuaWZvcm0xaXYodGhpcy5hZGRyLHIpO2ZvcihsZXQgcz0wO3MhPT1pOysrcyllLnNldFRleHR1cmUyREFycmF5KHRbc118fF8wLHJbc10pfWZ1bmN0aW9uIFpTKG4pe3N3aXRjaChuKXtjYXNlIDUxMjY6cmV0dXJuIFJTO2Nhc2UgMzU2NjQ6cmV0dXJuIExTO2Nhc2UgMzU2NjU6cmV0dXJuIFBTO2Nhc2UgMzU2NjY6cmV0dXJuIERTO2Nhc2UgMzU2NzQ6cmV0dXJuIElTO2Nhc2UgMzU2NzU6cmV0dXJuIE5TO2Nhc2UgMzU2NzY6cmV0dXJuIEZTO2Nhc2UgNTEyNDpjYXNlIDM1NjcwOnJldHVybiB6UztjYXNlIDM1NjY3OmNhc2UgMzU2NzE6cmV0dXJuIFVTO2Nhc2UgMzU2Njg6Y2FzZSAzNTY3MjpyZXR1cm4gQlM7Y2FzZSAzNTY2OTpjYXNlIDM1NjczOnJldHVybiBPUztjYXNlIDUxMjU6cmV0dXJuIGtTO2Nhc2UgMzYyOTQ6cmV0dXJuIEhTO2Nhc2UgMzYyOTU6cmV0dXJuIFZTO2Nhc2UgMzYyOTY6cmV0dXJuIEdTO2Nhc2UgMzU2Nzg6Y2FzZSAzNjE5ODpjYXNlIDM2Mjk4OmNhc2UgMzYzMDY6Y2FzZSAzNTY4MjpyZXR1cm4gV1M7Y2FzZSAzNTY3OTpjYXNlIDM2Mjk5OmNhc2UgMzYzMDc6cmV0dXJuIHFTO2Nhc2UgMzU2ODA6Y2FzZSAzNjMwMDpjYXNlIDM2MzA4OmNhc2UgMzYyOTM6cmV0dXJuIFhTO2Nhc2UgMzYyODk6Y2FzZSAzNjMwMzpjYXNlIDM2MzExOmNhc2UgMzYyOTI6cmV0dXJuIFlTfX1mdW5jdGlvbiBKUyhuLHQsZSl7dGhpcy5pZD1uLHRoaXMuYWRkcj1lLHRoaXMuY2FjaGU9W10sdGhpcy5zZXRWYWx1ZT1DUyh0LnR5cGUpfWZ1bmN0aW9uIGIwKG4sdCxlKXt0aGlzLmlkPW4sdGhpcy5hZGRyPWUsdGhpcy5jYWNoZT1bXSx0aGlzLnNpemU9dC5zaXplLHRoaXMuc2V0VmFsdWU9WlModC50eXBlKX1iMC5wcm90b3R5cGUudXBkYXRlQ2FjaGU9ZnVuY3Rpb24obil7bGV0IHQ9dGhpcy5jYWNoZTtuIGluc3RhbmNlb2YgRmxvYXQzMkFycmF5JiZ0Lmxlbmd0aCE9PW4ubGVuZ3RoJiYodGhpcy5jYWNoZT1uZXcgRmxvYXQzMkFycmF5KG4ubGVuZ3RoKSksVGUodCxuKX07ZnVuY3Rpb24gUzAobil7dGhpcy5pZD1uLHRoaXMuc2VxPVtdLHRoaXMubWFwPXt9fVMwLnByb3RvdHlwZS5zZXRWYWx1ZT1mdW5jdGlvbihuLHQsZSl7bGV0IGk9dGhpcy5zZXE7Zm9yKGxldCByPTAscz1pLmxlbmd0aDtyIT09czsrK3Ipe2xldCBvPWlbcl07by5zZXRWYWx1ZShuLHRbby5pZF0sZSl9fTt2YXIgQ3U9LyhcdyspKFxdKT8oXFt8XC4pPy9nO2Z1bmN0aW9uIEFnKG4sdCl7bi5zZXEucHVzaCh0KSxuLm1hcFt0LmlkXT10fWZ1bmN0aW9uICRTKG4sdCxlKXtsZXQgaT1uLm5hbWUscj1pLmxlbmd0aDtmb3IoQ3UubGFzdEluZGV4PTA7Oyl7bGV0IHM9Q3UuZXhlYyhpKSxvPUN1Lmxhc3RJbmRleCxhPXNbMV0sbD1zWzJdPT09Il0iLGM9c1szXTtpZihsJiYoYT1hfDApLGM9PT12b2lkIDB8fGM9PT0iWyImJm8rMj09PXIpe0FnKGUsYz09PXZvaWQgMD9uZXcgSlMoYSxuLHQpOm5ldyBiMChhLG4sdCkpO2JyZWFrfWVsc2V7bGV0IGg9ZS5tYXBbYV07aD09PXZvaWQgMCYmKGg9bmV3IFMwKGEpLEFnKGUsaCkpLGU9aH19fWZ1bmN0aW9uIGlpKG4sdCl7dGhpcy5zZXE9W10sdGhpcy5tYXA9e307bGV0IGU9bi5nZXRQcm9ncmFtUGFyYW1ldGVyKHQsMzU3MTgpO2ZvcihsZXQgaT0wO2k8ZTsrK2kpe2xldCByPW4uZ2V0QWN0aXZlVW5pZm9ybSh0LGkpLHM9bi5nZXRVbmlmb3JtTG9jYXRpb24odCxyLm5hbWUpOyRTKHIscyx0aGlzKX19aWkucHJvdG90eXBlLnNldFZhbHVlPWZ1bmN0aW9uKG4sdCxlLGkpe2xldCByPXRoaXMubWFwW3RdO3IhPT12b2lkIDAmJnIuc2V0VmFsdWUobixlLGkpfTtpaS5wcm90b3R5cGUuc2V0T3B0aW9uYWw9ZnVuY3Rpb24obix0LGUpe2xldCBpPXRbZV07aSE9PXZvaWQgMCYmdGhpcy5zZXRWYWx1ZShuLGUsaSl9O2lpLnVwbG9hZD1mdW5jdGlvbihuLHQsZSxpKXtmb3IobGV0IHI9MCxzPXQubGVuZ3RoO3IhPT1zOysrcil7bGV0IG89dFtyXSxhPWVbby5pZF07YS5uZWVkc1VwZGF0ZSE9PSExJiZvLnNldFZhbHVlKG4sYS52YWx1ZSxpKX19O2lpLnNlcVdpdGhWYWx1ZT1mdW5jdGlvbihuLHQpe2xldCBlPVtdO2ZvcihsZXQgaT0wLHI9bi5sZW5ndGg7aSE9PXI7KytpKXtsZXQgcz1uW2ldO3MuaWQgaW4gdCYmZS5wdXNoKHMpfXJldHVybiBlfTtmdW5jdGlvbiBDZyhuLHQsZSl7bGV0IGk9bi5jcmVhdGVTaGFkZXIodCk7cmV0dXJuIG4uc2hhZGVyU291cmNlKGksZSksbi5jb21waWxlU2hhZGVyKGkpLGl9dmFyIEtTPTA7ZnVuY3Rpb24gUVMobil7bGV0IHQ9bi5zcGxpdChgCmApO2ZvcihsZXQgZT0wO2U8dC5sZW5ndGg7ZSsrKXRbZV09ZSsxKyI6ICIrdFtlXTtyZXR1cm4gdC5qb2luKGAKYCl9ZnVuY3Rpb24galMobil7c3dpdGNoKG4pe2Nhc2Ugcmk6cmV0dXJuWyJMaW5lYXIiLCIoIHZhbHVlICkiXTtjYXNlICR0OnJldHVyblsic1JHQiIsIiggdmFsdWUgKSJdO2RlZmF1bHQ6cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xQcm9ncmFtOiBVbnN1cHBvcnRlZCBlbmNvZGluZzoiLG4pLFsiTGluZWFyIiwiKCB2YWx1ZSApIl19fWZ1bmN0aW9uIFJnKG4sdCxlKXtsZXQgaT1uLmdldFNoYWRlclBhcmFtZXRlcih0LDM1NzEzKSxyPW4uZ2V0U2hhZGVySW5mb0xvZyh0KS50cmltKCk7cmV0dXJuIGkmJnI9PT0iIj8iIjplLnRvVXBwZXJDYXNlKCkrYAoKYCtyK2AKCmArUVMobi5nZXRTaGFkZXJTb3VyY2UodCkpfWZ1bmN0aW9uIHRFKG4sdCl7bGV0IGU9alModCk7cmV0dXJuInZlYzQgIituKyIoIHZlYzQgdmFsdWUgKSB7IHJldHVybiBMaW5lYXJUbyIrZVswXStlWzFdKyI7IH0ifWZ1bmN0aW9uIGVFKG4sdCl7bGV0IGU7c3dpdGNoKHQpe2Nhc2UgQXc6ZT0iTGluZWFyIjticmVhaztjYXNlIEN3OmU9IlJlaW5oYXJkIjticmVhaztjYXNlIFJ3OmU9Ik9wdGltaXplZENpbmVvbiI7YnJlYWs7Y2FzZSBMdzplPSJBQ0VTRmlsbWljIjticmVhaztjYXNlIFB3OmU9IkN1c3RvbSI7YnJlYWs7ZGVmYXVsdDpjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUHJvZ3JhbTogVW5zdXBwb3J0ZWQgdG9uZU1hcHBpbmc6Iix0KSxlPSJMaW5lYXIifXJldHVybiJ2ZWMzICIrbisiKCB2ZWMzIGNvbG9yICkgeyByZXR1cm4gIitlKyJUb25lTWFwcGluZyggY29sb3IgKTsgfSJ9ZnVuY3Rpb24gbkUobil7cmV0dXJuW24uZXh0ZW5zaW9uRGVyaXZhdGl2ZXN8fG4uZW52TWFwQ3ViZVVWfHxuLmJ1bXBNYXB8fG4udGFuZ2VudFNwYWNlTm9ybWFsTWFwfHxuLmNsZWFyY29hdE5vcm1hbE1hcHx8bi5mbGF0U2hhZGluZ3x8bi5zaGFkZXJJRD09PSJwaHlzaWNhbCI/IiNleHRlbnNpb24gR0xfT0VTX3N0YW5kYXJkX2Rlcml2YXRpdmVzIDogZW5hYmxlIjoiIiwobi5leHRlbnNpb25GcmFnRGVwdGh8fG4ubG9nYXJpdGhtaWNEZXB0aEJ1ZmZlcikmJm4ucmVuZGVyZXJFeHRlbnNpb25GcmFnRGVwdGg/IiNleHRlbnNpb24gR0xfRVhUX2ZyYWdfZGVwdGggOiBlbmFibGUiOiIiLG4uZXh0ZW5zaW9uRHJhd0J1ZmZlcnMmJm4ucmVuZGVyZXJFeHRlbnNpb25EcmF3QnVmZmVycz8iI2V4dGVuc2lvbiBHTF9FWFRfZHJhd19idWZmZXJzIDogcmVxdWlyZSI6IiIsKG4uZXh0ZW5zaW9uU2hhZGVyVGV4dHVyZUxPRHx8bi5lbnZNYXB8fG4udHJhbnNtaXNzaW9uKSYmbi5yZW5kZXJlckV4dGVuc2lvblNoYWRlclRleHR1cmVMb2Q/IiNleHRlbnNpb24gR0xfRVhUX3NoYWRlcl90ZXh0dXJlX2xvZCA6IGVuYWJsZSI6IiJdLmZpbHRlcigkcykuam9pbihgCmApfWZ1bmN0aW9uIGlFKG4pe2xldCB0PVtdO2ZvcihsZXQgZSBpbiBuKXtsZXQgaT1uW2VdO2khPT0hMSYmdC5wdXNoKCIjZGVmaW5lICIrZSsiICIraSl9cmV0dXJuIHQuam9pbihgCmApfWZ1bmN0aW9uIHJFKG4sdCl7bGV0IGU9e30saT1uLmdldFByb2dyYW1QYXJhbWV0ZXIodCwzNTcyMSk7Zm9yKGxldCByPTA7cjxpO3IrKyl7bGV0IHM9bi5nZXRBY3RpdmVBdHRyaWIodCxyKSxvPXMubmFtZSxhPTE7cy50eXBlPT09MzU2NzQmJihhPTIpLHMudHlwZT09PTM1Njc1JiYoYT0zKSxzLnR5cGU9PT0zNTY3NiYmKGE9NCksZVtvXT17dHlwZTpzLnR5cGUsbG9jYXRpb246bi5nZXRBdHRyaWJMb2NhdGlvbih0LG8pLGxvY2F0aW9uU2l6ZTphfX1yZXR1cm4gZX1mdW5jdGlvbiAkcyhuKXtyZXR1cm4gbiE9PSIifWZ1bmN0aW9uIExnKG4sdCl7cmV0dXJuIG4ucmVwbGFjZSgvTlVNX0RJUl9MSUdIVFMvZyx0Lm51bURpckxpZ2h0cykucmVwbGFjZSgvTlVNX1NQT1RfTElHSFRTL2csdC5udW1TcG90TGlnaHRzKS5yZXBsYWNlKC9OVU1fUkVDVF9BUkVBX0xJR0hUUy9nLHQubnVtUmVjdEFyZWFMaWdodHMpLnJlcGxhY2UoL05VTV9QT0lOVF9MSUdIVFMvZyx0Lm51bVBvaW50TGlnaHRzKS5yZXBsYWNlKC9OVU1fSEVNSV9MSUdIVFMvZyx0Lm51bUhlbWlMaWdodHMpLnJlcGxhY2UoL05VTV9ESVJfTElHSFRfU0hBRE9XUy9nLHQubnVtRGlyTGlnaHRTaGFkb3dzKS5yZXBsYWNlKC9OVU1fU1BPVF9MSUdIVF9TSEFET1dTL2csdC5udW1TcG90TGlnaHRTaGFkb3dzKS5yZXBsYWNlKC9OVU1fUE9JTlRfTElHSFRfU0hBRE9XUy9nLHQubnVtUG9pbnRMaWdodFNoYWRvd3MpfWZ1bmN0aW9uIFBnKG4sdCl7cmV0dXJuIG4ucmVwbGFjZSgvTlVNX0NMSVBQSU5HX1BMQU5FUy9nLHQubnVtQ2xpcHBpbmdQbGFuZXMpLnJlcGxhY2UoL1VOSU9OX0NMSVBQSU5HX1BMQU5FUy9nLHQubnVtQ2xpcHBpbmdQbGFuZXMtdC5udW1DbGlwSW50ZXJzZWN0aW9uKX12YXIgc0U9L15bIFx0XSojaW5jbHVkZSArPChbXHdcZC4vXSspPi9nbTtmdW5jdGlvbiBxdShuKXtyZXR1cm4gbi5yZXBsYWNlKHNFLG9FKX1mdW5jdGlvbiBvRShuLHQpe2xldCBlPVB0W3RdO2lmKGU9PT12b2lkIDApdGhyb3cgbmV3IEVycm9yKCJDYW4gbm90IHJlc29sdmUgI2luY2x1ZGUgPCIrdCsiPiIpO3JldHVybiBxdShlKX12YXIgYUU9LyNwcmFnbWEgdW5yb2xsX2xvb3BbXHNdKz9mb3IgXCggaW50IGkgXD0gKFxkKylcOyBpIDwgKFxkKylcOyBpIFwrXCsgXCkgXHsoW1xzXFNdKz8pKD89XH0pXH0vZyxsRT0vI3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydFxzK2ZvclxzKlwoXHMqaW50XHMraVxzKj1ccyooXGQrKVxzKjtccyppXHMqPFxzKihcZCspXHMqO1xzKmlccypcK1wrXHMqXClccyp7KFtcc1xTXSs/KX1ccysjcHJhZ21hIHVucm9sbF9sb29wX2VuZC9nO2Z1bmN0aW9uIERnKG4pe3JldHVybiBuLnJlcGxhY2UobEUsRTApLnJlcGxhY2UoYUUsY0UpfWZ1bmN0aW9uIGNFKG4sdCxlLGkpe3JldHVybiBjb25zb2xlLndhcm4oIldlYkdMUHJvZ3JhbTogI3ByYWdtYSB1bnJvbGxfbG9vcCBzaGFkZXIgc3ludGF4IGlzIGRlcHJlY2F0ZWQuIFBsZWFzZSB1c2UgI3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydCBzeW50YXggaW5zdGVhZC4iKSxFMChuLHQsZSxpKX1mdW5jdGlvbiBFMChuLHQsZSxpKXtsZXQgcj0iIjtmb3IobGV0IHM9cGFyc2VJbnQodCk7czxwYXJzZUludChlKTtzKyspcis9aS5yZXBsYWNlKC9cW1xzKmlccypcXS9nLCJbICIrcysiIF0iKS5yZXBsYWNlKC9VTlJPTExFRF9MT09QX0lOREVYL2cscyk7cmV0dXJuIHJ9ZnVuY3Rpb24gSWcobil7bGV0IHQ9InByZWNpc2lvbiAiK24ucHJlY2lzaW9uK2AgZmxvYXQ7CnByZWNpc2lvbiBgK24ucHJlY2lzaW9uKyIgaW50OyI7cmV0dXJuIG4ucHJlY2lzaW9uPT09ImhpZ2hwIj90Kz1gCiNkZWZpbmUgSElHSF9QUkVDSVNJT05gOm4ucHJlY2lzaW9uPT09Im1lZGl1bXAiP3QrPWAKI2RlZmluZSBNRURJVU1fUFJFQ0lTSU9OYDpuLnByZWNpc2lvbj09PSJsb3dwIiYmKHQrPWAKI2RlZmluZSBMT1dfUFJFQ0lTSU9OYCksdH1mdW5jdGlvbiB1RShuKXtsZXQgdD0iU0hBRE9XTUFQX1RZUEVfQkFTSUMiO3JldHVybiBuLnNoYWRvd01hcFR5cGU9PT1sMD90PSJTSEFET1dNQVBfVFlQRV9QQ0YiOm4uc2hhZG93TWFwVHlwZT09PXN3P3Q9IlNIQURPV01BUF9UWVBFX1BDRl9TT0ZUIjpuLnNoYWRvd01hcFR5cGU9PT1KcyYmKHQ9IlNIQURPV01BUF9UWVBFX1ZTTSIpLHR9ZnVuY3Rpb24gaEUobil7bGV0IHQ9IkVOVk1BUF9UWVBFX0NVQkUiO2lmKG4uZW52TWFwKXN3aXRjaChuLmVudk1hcE1vZGUpe2Nhc2UgQW86Y2FzZSBDbzp0PSJFTlZNQVBfVFlQRV9DVUJFIjticmVhaztjYXNlIFJsOmNhc2UgS2g6dD0iRU5WTUFQX1RZUEVfQ1VCRV9VViI7YnJlYWt9cmV0dXJuIHR9ZnVuY3Rpb24gZkUobil7bGV0IHQ9IkVOVk1BUF9NT0RFX1JFRkxFQ1RJT04iO2lmKG4uZW52TWFwKXN3aXRjaChuLmVudk1hcE1vZGUpe2Nhc2UgQ286Y2FzZSBLaDp0PSJFTlZNQVBfTU9ERV9SRUZSQUNUSU9OIjticmVha31yZXR1cm4gdH1mdW5jdGlvbiBkRShuKXtsZXQgdD0iRU5WTUFQX0JMRU5ESU5HX05PTkUiO2lmKG4uZW52TWFwKXN3aXRjaChuLmNvbWJpbmUpe2Nhc2UgQ2w6dD0iRU5WTUFQX0JMRU5ESU5HX01VTFRJUExZIjticmVhaztjYXNlIEV3OnQ9IkVOVk1BUF9CTEVORElOR19NSVgiO2JyZWFrO2Nhc2UgVHc6dD0iRU5WTUFQX0JMRU5ESU5HX0FERCI7YnJlYWt9cmV0dXJuIHR9ZnVuY3Rpb24gcEUobix0LGUsaSl7bGV0IHI9bi5nZXRDb250ZXh0KCkscz1lLmRlZmluZXMsbz1lLnZlcnRleFNoYWRlcixhPWUuZnJhZ21lbnRTaGFkZXIsbD11RShlKSxjPWhFKGUpLHU9ZkUoZSksaD1kRShlKSxmPWUuaXNXZWJHTDI/IiI6bkUoZSksZD1pRShzKSxnPXIuY3JlYXRlUHJvZ3JhbSgpLHgsdixtPWUuZ2xzbFZlcnNpb24/IiN2ZXJzaW9uICIrZS5nbHNsVmVyc2lvbitgCmA6IiI7ZS5pc1Jhd1NoYWRlck1hdGVyaWFsPyh4PVtkXS5maWx0ZXIoJHMpLmpvaW4oYApgKSx4Lmxlbmd0aD4wJiYoeCs9YApgKSx2PVtmLGRdLmZpbHRlcigkcykuam9pbihgCmApLHYubGVuZ3RoPjAmJih2Kz1gCmApKTooeD1bSWcoZSksIiNkZWZpbmUgU0hBREVSX05BTUUgIitlLnNoYWRlck5hbWUsZCxlLmluc3RhbmNpbmc/IiNkZWZpbmUgVVNFX0lOU1RBTkNJTkciOiIiLGUuaW5zdGFuY2luZ0NvbG9yPyIjZGVmaW5lIFVTRV9JTlNUQU5DSU5HX0NPTE9SIjoiIixlLnN1cHBvcnRzVmVydGV4VGV4dHVyZXM/IiNkZWZpbmUgVkVSVEVYX1RFWFRVUkVTIjoiIiwiI2RlZmluZSBNQVhfQk9ORVMgIitlLm1heEJvbmVzLGUudXNlRm9nJiZlLmZvZz8iI2RlZmluZSBVU0VfRk9HIjoiIixlLnVzZUZvZyYmZS5mb2dFeHAyPyIjZGVmaW5lIEZPR19FWFAyIjoiIixlLm1hcD8iI2RlZmluZSBVU0VfTUFQIjoiIixlLmVudk1hcD8iI2RlZmluZSBVU0VfRU5WTUFQIjoiIixlLmVudk1hcD8iI2RlZmluZSAiK3U6IiIsZS5saWdodE1hcD8iI2RlZmluZSBVU0VfTElHSFRNQVAiOiIiLGUuYW9NYXA/IiNkZWZpbmUgVVNFX0FPTUFQIjoiIixlLmVtaXNzaXZlTWFwPyIjZGVmaW5lIFVTRV9FTUlTU0lWRU1BUCI6IiIsZS5idW1wTWFwPyIjZGVmaW5lIFVTRV9CVU1QTUFQIjoiIixlLm5vcm1hbE1hcD8iI2RlZmluZSBVU0VfTk9STUFMTUFQIjoiIixlLm5vcm1hbE1hcCYmZS5vYmplY3RTcGFjZU5vcm1hbE1hcD8iI2RlZmluZSBPQkpFQ1RTUEFDRV9OT1JNQUxNQVAiOiIiLGUubm9ybWFsTWFwJiZlLnRhbmdlbnRTcGFjZU5vcm1hbE1hcD8iI2RlZmluZSBUQU5HRU5UU1BBQ0VfTk9STUFMTUFQIjoiIixlLmNsZWFyY29hdE1hcD8iI2RlZmluZSBVU0VfQ0xFQVJDT0FUTUFQIjoiIixlLmNsZWFyY29hdFJvdWdobmVzc01hcD8iI2RlZmluZSBVU0VfQ0xFQVJDT0FUX1JPVUdITkVTU01BUCI6IiIsZS5jbGVhcmNvYXROb3JtYWxNYXA/IiNkZWZpbmUgVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVAiOiIiLGUuZGlzcGxhY2VtZW50TWFwJiZlLnN1cHBvcnRzVmVydGV4VGV4dHVyZXM/IiNkZWZpbmUgVVNFX0RJU1BMQUNFTUVOVE1BUCI6IiIsZS5zcGVjdWxhck1hcD8iI2RlZmluZSBVU0VfU1BFQ1VMQVJNQVAiOiIiLGUuc3BlY3VsYXJJbnRlbnNpdHlNYXA/IiNkZWZpbmUgVVNFX1NQRUNVTEFSSU5URU5TSVRZTUFQIjoiIixlLnNwZWN1bGFyQ29sb3JNYXA/IiNkZWZpbmUgVVNFX1NQRUNVTEFSQ09MT1JNQVAiOiIiLGUucm91Z2huZXNzTWFwPyIjZGVmaW5lIFVTRV9ST1VHSE5FU1NNQVAiOiIiLGUubWV0YWxuZXNzTWFwPyIjZGVmaW5lIFVTRV9NRVRBTE5FU1NNQVAiOiIiLGUuYWxwaGFNYXA/IiNkZWZpbmUgVVNFX0FMUEhBTUFQIjoiIixlLnRyYW5zbWlzc2lvbj8iI2RlZmluZSBVU0VfVFJBTlNNSVNTSU9OIjoiIixlLnRyYW5zbWlzc2lvbk1hcD8iI2RlZmluZSBVU0VfVFJBTlNNSVNTSU9OTUFQIjoiIixlLnRoaWNrbmVzc01hcD8iI2RlZmluZSBVU0VfVEhJQ0tORVNTTUFQIjoiIixlLnNoZWVuQ29sb3JNYXA/IiNkZWZpbmUgVVNFX1NIRUVOQ09MT1JNQVAiOiIiLGUuc2hlZW5Sb3VnaG5lc3NNYXA/IiNkZWZpbmUgVVNFX1NIRUVOUk9VR0hORVNTTUFQIjoiIixlLnZlcnRleFRhbmdlbnRzPyIjZGVmaW5lIFVTRV9UQU5HRU5UIjoiIixlLnZlcnRleENvbG9ycz8iI2RlZmluZSBVU0VfQ09MT1IiOiIiLGUudmVydGV4QWxwaGFzPyIjZGVmaW5lIFVTRV9DT0xPUl9BTFBIQSI6IiIsZS52ZXJ0ZXhVdnM/IiNkZWZpbmUgVVNFX1VWIjoiIixlLnV2c1ZlcnRleE9ubHk/IiNkZWZpbmUgVVZTX1ZFUlRFWF9PTkxZIjoiIixlLmZsYXRTaGFkaW5nPyIjZGVmaW5lIEZMQVRfU0hBREVEIjoiIixlLnNraW5uaW5nPyIjZGVmaW5lIFVTRV9TS0lOTklORyI6IiIsZS51c2VWZXJ0ZXhUZXh0dXJlPyIjZGVmaW5lIEJPTkVfVEVYVFVSRSI6IiIsZS5tb3JwaFRhcmdldHM/IiNkZWZpbmUgVVNFX01PUlBIVEFSR0VUUyI6IiIsZS5tb3JwaE5vcm1hbHMmJmUuZmxhdFNoYWRpbmc9PT0hMT8iI2RlZmluZSBVU0VfTU9SUEhOT1JNQUxTIjoiIixlLm1vcnBoVGFyZ2V0cyYmZS5pc1dlYkdMMj8iI2RlZmluZSBNT1JQSFRBUkdFVFNfVEVYVFVSRSI6IiIsZS5tb3JwaFRhcmdldHMmJmUuaXNXZWJHTDI/IiNkZWZpbmUgTU9SUEhUQVJHRVRTX0NPVU5UICIrZS5tb3JwaFRhcmdldHNDb3VudDoiIixlLmRvdWJsZVNpZGVkPyIjZGVmaW5lIERPVUJMRV9TSURFRCI6IiIsZS5mbGlwU2lkZWQ/IiNkZWZpbmUgRkxJUF9TSURFRCI6IiIsZS5zaGFkb3dNYXBFbmFibGVkPyIjZGVmaW5lIFVTRV9TSEFET1dNQVAiOiIiLGUuc2hhZG93TWFwRW5hYmxlZD8iI2RlZmluZSAiK2w6IiIsZS5zaXplQXR0ZW51YXRpb24/IiNkZWZpbmUgVVNFX1NJWkVBVFRFTlVBVElPTiI6IiIsZS5sb2dhcml0aG1pY0RlcHRoQnVmZmVyPyIjZGVmaW5lIFVTRV9MT0dERVBUSEJVRiI6IiIsZS5sb2dhcml0aG1pY0RlcHRoQnVmZmVyJiZlLnJlbmRlcmVyRXh0ZW5zaW9uRnJhZ0RlcHRoPyIjZGVmaW5lIFVTRV9MT0dERVBUSEJVRl9FWFQiOiIiLCJ1bmlmb3JtIG1hdDQgbW9kZWxNYXRyaXg7IiwidW5pZm9ybSBtYXQ0IG1vZGVsVmlld01hdHJpeDsiLCJ1bmlmb3JtIG1hdDQgcHJvamVjdGlvbk1hdHJpeDsiLCJ1bmlmb3JtIG1hdDQgdmlld01hdHJpeDsiLCJ1bmlmb3JtIG1hdDMgbm9ybWFsTWF0cml4OyIsInVuaWZvcm0gdmVjMyBjYW1lcmFQb3NpdGlvbjsiLCJ1bmlmb3JtIGJvb2wgaXNPcnRob2dyYXBoaWM7IiwiI2lmZGVmIFVTRV9JTlNUQU5DSU5HIiwiCWF0dHJpYnV0ZSBtYXQ0IGluc3RhbmNlTWF0cml4OyIsIiNlbmRpZiIsIiNpZmRlZiBVU0VfSU5TVEFOQ0lOR19DT0xPUiIsIglhdHRyaWJ1dGUgdmVjMyBpbnN0YW5jZUNvbG9yOyIsIiNlbmRpZiIsImF0dHJpYnV0ZSB2ZWMzIHBvc2l0aW9uOyIsImF0dHJpYnV0ZSB2ZWMzIG5vcm1hbDsiLCJhdHRyaWJ1dGUgdmVjMiB1djsiLCIjaWZkZWYgVVNFX1RBTkdFTlQiLCIJYXR0cmlidXRlIHZlYzQgdGFuZ2VudDsiLCIjZW5kaWYiLCIjaWYgZGVmaW5lZCggVVNFX0NPTE9SX0FMUEhBICkiLCIJYXR0cmlidXRlIHZlYzQgY29sb3I7IiwiI2VsaWYgZGVmaW5lZCggVVNFX0NPTE9SICkiLCIJYXR0cmlidXRlIHZlYzMgY29sb3I7IiwiI2VuZGlmIiwiI2lmICggZGVmaW5lZCggVVNFX01PUlBIVEFSR0VUUyApICYmICEgZGVmaW5lZCggTU9SUEhUQVJHRVRTX1RFWFRVUkUgKSApIiwiCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0MDsiLCIJYXR0cmlidXRlIHZlYzMgbW9ycGhUYXJnZXQxOyIsIglhdHRyaWJ1dGUgdmVjMyBtb3JwaFRhcmdldDI7IiwiCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0MzsiLCIJI2lmZGVmIFVTRV9NT1JQSE5PUk1BTFMiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoTm9ybWFsMDsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoTm9ybWFsMTsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoTm9ybWFsMjsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoTm9ybWFsMzsiLCIJI2Vsc2UiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0NDsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0NTsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0NjsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0NzsiLCIJI2VuZGlmIiwiI2VuZGlmIiwiI2lmZGVmIFVTRV9TS0lOTklORyIsIglhdHRyaWJ1dGUgdmVjNCBza2luSW5kZXg7IiwiCWF0dHJpYnV0ZSB2ZWM0IHNraW5XZWlnaHQ7IiwiI2VuZGlmIixgCmBdLmZpbHRlcigkcykuam9pbihgCmApLHY9W2YsSWcoZSksIiNkZWZpbmUgU0hBREVSX05BTUUgIitlLnNoYWRlck5hbWUsZCxlLnVzZUZvZyYmZS5mb2c/IiNkZWZpbmUgVVNFX0ZPRyI6IiIsZS51c2VGb2cmJmUuZm9nRXhwMj8iI2RlZmluZSBGT0dfRVhQMiI6IiIsZS5tYXA/IiNkZWZpbmUgVVNFX01BUCI6IiIsZS5tYXRjYXA/IiNkZWZpbmUgVVNFX01BVENBUCI6IiIsZS5lbnZNYXA/IiNkZWZpbmUgVVNFX0VOVk1BUCI6IiIsZS5lbnZNYXA/IiNkZWZpbmUgIitjOiIiLGUuZW52TWFwPyIjZGVmaW5lICIrdToiIixlLmVudk1hcD8iI2RlZmluZSAiK2g6IiIsZS5saWdodE1hcD8iI2RlZmluZSBVU0VfTElHSFRNQVAiOiIiLGUuYW9NYXA/IiNkZWZpbmUgVVNFX0FPTUFQIjoiIixlLmVtaXNzaXZlTWFwPyIjZGVmaW5lIFVTRV9FTUlTU0lWRU1BUCI6IiIsZS5idW1wTWFwPyIjZGVmaW5lIFVTRV9CVU1QTUFQIjoiIixlLm5vcm1hbE1hcD8iI2RlZmluZSBVU0VfTk9STUFMTUFQIjoiIixlLm5vcm1hbE1hcCYmZS5vYmplY3RTcGFjZU5vcm1hbE1hcD8iI2RlZmluZSBPQkpFQ1RTUEFDRV9OT1JNQUxNQVAiOiIiLGUubm9ybWFsTWFwJiZlLnRhbmdlbnRTcGFjZU5vcm1hbE1hcD8iI2RlZmluZSBUQU5HRU5UU1BBQ0VfTk9STUFMTUFQIjoiIixlLmNsZWFyY29hdD8iI2RlZmluZSBVU0VfQ0xFQVJDT0FUIjoiIixlLmNsZWFyY29hdE1hcD8iI2RlZmluZSBVU0VfQ0xFQVJDT0FUTUFQIjoiIixlLmNsZWFyY29hdFJvdWdobmVzc01hcD8iI2RlZmluZSBVU0VfQ0xFQVJDT0FUX1JPVUdITkVTU01BUCI6IiIsZS5jbGVhcmNvYXROb3JtYWxNYXA/IiNkZWZpbmUgVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVAiOiIiLGUuc3BlY3VsYXJNYXA/IiNkZWZpbmUgVVNFX1NQRUNVTEFSTUFQIjoiIixlLnNwZWN1bGFySW50ZW5zaXR5TWFwPyIjZGVmaW5lIFVTRV9TUEVDVUxBUklOVEVOU0lUWU1BUCI6IiIsZS5zcGVjdWxhckNvbG9yTWFwPyIjZGVmaW5lIFVTRV9TUEVDVUxBUkNPTE9STUFQIjoiIixlLnJvdWdobmVzc01hcD8iI2RlZmluZSBVU0VfUk9VR0hORVNTTUFQIjoiIixlLm1ldGFsbmVzc01hcD8iI2RlZmluZSBVU0VfTUVUQUxORVNTTUFQIjoiIixlLmFscGhhTWFwPyIjZGVmaW5lIFVTRV9BTFBIQU1BUCI6IiIsZS5hbHBoYVRlc3Q/IiNkZWZpbmUgVVNFX0FMUEhBVEVTVCI6IiIsZS5zaGVlbj8iI2RlZmluZSBVU0VfU0hFRU4iOiIiLGUuc2hlZW5Db2xvck1hcD8iI2RlZmluZSBVU0VfU0hFRU5DT0xPUk1BUCI6IiIsZS5zaGVlblJvdWdobmVzc01hcD8iI2RlZmluZSBVU0VfU0hFRU5ST1VHSE5FU1NNQVAiOiIiLGUudHJhbnNtaXNzaW9uPyIjZGVmaW5lIFVTRV9UUkFOU01JU1NJT04iOiIiLGUudHJhbnNtaXNzaW9uTWFwPyIjZGVmaW5lIFVTRV9UUkFOU01JU1NJT05NQVAiOiIiLGUudGhpY2tuZXNzTWFwPyIjZGVmaW5lIFVTRV9USElDS05FU1NNQVAiOiIiLGUuZGVjb2RlVmlkZW9UZXh0dXJlPyIjZGVmaW5lIERFQ09ERV9WSURFT19URVhUVVJFIjoiIixlLnZlcnRleFRhbmdlbnRzPyIjZGVmaW5lIFVTRV9UQU5HRU5UIjoiIixlLnZlcnRleENvbG9yc3x8ZS5pbnN0YW5jaW5nQ29sb3I/IiNkZWZpbmUgVVNFX0NPTE9SIjoiIixlLnZlcnRleEFscGhhcz8iI2RlZmluZSBVU0VfQ09MT1JfQUxQSEEiOiIiLGUudmVydGV4VXZzPyIjZGVmaW5lIFVTRV9VViI6IiIsZS51dnNWZXJ0ZXhPbmx5PyIjZGVmaW5lIFVWU19WRVJURVhfT05MWSI6IiIsZS5ncmFkaWVudE1hcD8iI2RlZmluZSBVU0VfR1JBRElFTlRNQVAiOiIiLGUuZmxhdFNoYWRpbmc/IiNkZWZpbmUgRkxBVF9TSEFERUQiOiIiLGUuZG91YmxlU2lkZWQ/IiNkZWZpbmUgRE9VQkxFX1NJREVEIjoiIixlLmZsaXBTaWRlZD8iI2RlZmluZSBGTElQX1NJREVEIjoiIixlLnNoYWRvd01hcEVuYWJsZWQ/IiNkZWZpbmUgVVNFX1NIQURPV01BUCI6IiIsZS5zaGFkb3dNYXBFbmFibGVkPyIjZGVmaW5lICIrbDoiIixlLnByZW11bHRpcGxpZWRBbHBoYT8iI2RlZmluZSBQUkVNVUxUSVBMSUVEX0FMUEhBIjoiIixlLnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzPyIjZGVmaW5lIFBIWVNJQ0FMTFlfQ09SUkVDVF9MSUdIVFMiOiIiLGUubG9nYXJpdGhtaWNEZXB0aEJ1ZmZlcj8iI2RlZmluZSBVU0VfTE9HREVQVEhCVUYiOiIiLGUubG9nYXJpdGhtaWNEZXB0aEJ1ZmZlciYmZS5yZW5kZXJlckV4dGVuc2lvbkZyYWdEZXB0aD8iI2RlZmluZSBVU0VfTE9HREVQVEhCVUZfRVhUIjoiIiwoZS5leHRlbnNpb25TaGFkZXJUZXh0dXJlTE9EfHxlLmVudk1hcCkmJmUucmVuZGVyZXJFeHRlbnNpb25TaGFkZXJUZXh0dXJlTG9kPyIjZGVmaW5lIFRFWFRVUkVfTE9EX0VYVCI6IiIsInVuaWZvcm0gbWF0NCB2aWV3TWF0cml4OyIsInVuaWZvcm0gdmVjMyBjYW1lcmFQb3NpdGlvbjsiLCJ1bmlmb3JtIGJvb2wgaXNPcnRob2dyYXBoaWM7IixlLnRvbmVNYXBwaW5nIT09dGk/IiNkZWZpbmUgVE9ORV9NQVBQSU5HIjoiIixlLnRvbmVNYXBwaW5nIT09dGk/UHQudG9uZW1hcHBpbmdfcGFyc19mcmFnbWVudDoiIixlLnRvbmVNYXBwaW5nIT09dGk/ZUUoInRvbmVNYXBwaW5nIixlLnRvbmVNYXBwaW5nKToiIixlLmRpdGhlcmluZz8iI2RlZmluZSBESVRIRVJJTkciOiIiLGUuYWxwaGFXcml0ZT8iIjoiI2RlZmluZSBPUEFRVUUiLFB0LmVuY29kaW5nc19wYXJzX2ZyYWdtZW50LHRFKCJsaW5lYXJUb091dHB1dFRleGVsIixlLm91dHB1dEVuY29kaW5nKSxlLmRlcHRoUGFja2luZz8iI2RlZmluZSBERVBUSF9QQUNLSU5HICIrZS5kZXB0aFBhY2tpbmc6IiIsYApgXS5maWx0ZXIoJHMpLmpvaW4oYApgKSksbz1xdShvKSxvPUxnKG8sZSksbz1QZyhvLGUpLGE9cXUoYSksYT1MZyhhLGUpLGE9UGcoYSxlKSxvPURnKG8pLGE9RGcoYSksZS5pc1dlYkdMMiYmZS5pc1Jhd1NoYWRlck1hdGVyaWFsIT09ITAmJihtPWAjdmVyc2lvbiAzMDAgZXMKYCx4PVsicHJlY2lzaW9uIG1lZGl1bXAgc2FtcGxlcjJEQXJyYXk7IiwiI2RlZmluZSBhdHRyaWJ1dGUgaW4iLCIjZGVmaW5lIHZhcnlpbmcgb3V0IiwiI2RlZmluZSB0ZXh0dXJlMkQgdGV4dHVyZSJdLmpvaW4oYApgKStgCmAreCx2PVsiI2RlZmluZSB2YXJ5aW5nIGluIixlLmdsc2xWZXJzaW9uPT09bmc/IiI6ImxheW91dChsb2NhdGlvbiA9IDApIG91dCBoaWdocCB2ZWM0IHBjX2ZyYWdDb2xvcjsiLGUuZ2xzbFZlcnNpb249PT1uZz8iIjoiI2RlZmluZSBnbF9GcmFnQ29sb3IgcGNfZnJhZ0NvbG9yIiwiI2RlZmluZSBnbF9GcmFnRGVwdGhFWFQgZ2xfRnJhZ0RlcHRoIiwiI2RlZmluZSB0ZXh0dXJlMkQgdGV4dHVyZSIsIiNkZWZpbmUgdGV4dHVyZUN1YmUgdGV4dHVyZSIsIiNkZWZpbmUgdGV4dHVyZTJEUHJvaiB0ZXh0dXJlUHJvaiIsIiNkZWZpbmUgdGV4dHVyZTJETG9kRVhUIHRleHR1cmVMb2QiLCIjZGVmaW5lIHRleHR1cmUyRFByb2pMb2RFWFQgdGV4dHVyZVByb2pMb2QiLCIjZGVmaW5lIHRleHR1cmVDdWJlTG9kRVhUIHRleHR1cmVMb2QiLCIjZGVmaW5lIHRleHR1cmUyREdyYWRFWFQgdGV4dHVyZUdyYWQiLCIjZGVmaW5lIHRleHR1cmUyRFByb2pHcmFkRVhUIHRleHR1cmVQcm9qR3JhZCIsIiNkZWZpbmUgdGV4dHVyZUN1YmVHcmFkRVhUIHRleHR1cmVHcmFkIl0uam9pbihgCmApK2AKYCt2KTtsZXQgcD1tK3grbyxiPW0rdithLF89Q2cociwzNTYzMyxwKSxTPUNnKHIsMzU2MzIsYik7aWYoci5hdHRhY2hTaGFkZXIoZyxfKSxyLmF0dGFjaFNoYWRlcihnLFMpLGUuaW5kZXgwQXR0cmlidXRlTmFtZSE9PXZvaWQgMD9yLmJpbmRBdHRyaWJMb2NhdGlvbihnLDAsZS5pbmRleDBBdHRyaWJ1dGVOYW1lKTplLm1vcnBoVGFyZ2V0cz09PSEwJiZyLmJpbmRBdHRyaWJMb2NhdGlvbihnLDAsInBvc2l0aW9uIiksci5saW5rUHJvZ3JhbShnKSxuLmRlYnVnLmNoZWNrU2hhZGVyRXJyb3JzKXtsZXQgSD1yLmdldFByb2dyYW1JbmZvTG9nKGcpLnRyaW0oKSx0dD1yLmdldFNoYWRlckluZm9Mb2coXykudHJpbSgpLFg9ci5nZXRTaGFkZXJJbmZvTG9nKFMpLnRyaW0oKSx5PSEwLFI9ITA7aWYoci5nZXRQcm9ncmFtUGFyYW1ldGVyKGcsMzU3MTQpPT09ITEpe3k9ITE7bGV0IEQ9UmcocixfLCJ2ZXJ0ZXgiKSxGPVJnKHIsUywiZnJhZ21lbnQiKTtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFByb2dyYW06IFNoYWRlciBFcnJvciAiK3IuZ2V0RXJyb3IoKSsiIC0gVkFMSURBVEVfU1RBVFVTICIrci5nZXRQcm9ncmFtUGFyYW1ldGVyKGcsMzU3MTUpK2AKClByb2dyYW0gSW5mbyBMb2c6IGArSCtgCmArRCtgCmArRil9ZWxzZSBIIT09IiI/Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFByb2dyYW06IFByb2dyYW0gSW5mbyBMb2c6IixIKToodHQ9PT0iInx8WD09PSIiKSYmKFI9ITEpO1ImJih0aGlzLmRpYWdub3N0aWNzPXtydW5uYWJsZTp5LHByb2dyYW1Mb2c6SCx2ZXJ0ZXhTaGFkZXI6e2xvZzp0dCxwcmVmaXg6eH0sZnJhZ21lbnRTaGFkZXI6e2xvZzpYLHByZWZpeDp2fX0pfXIuZGVsZXRlU2hhZGVyKF8pLHIuZGVsZXRlU2hhZGVyKFMpO2xldCBMO3RoaXMuZ2V0VW5pZm9ybXM9ZnVuY3Rpb24oKXtyZXR1cm4gTD09PXZvaWQgMCYmKEw9bmV3IGlpKHIsZykpLEx9O2xldCBBO3JldHVybiB0aGlzLmdldEF0dHJpYnV0ZXM9ZnVuY3Rpb24oKXtyZXR1cm4gQT09PXZvaWQgMCYmKEE9ckUocixnKSksQX0sdGhpcy5kZXN0cm95PWZ1bmN0aW9uKCl7aS5yZWxlYXNlU3RhdGVzT2ZQcm9ncmFtKHRoaXMpLHIuZGVsZXRlUHJvZ3JhbShnKSx0aGlzLnByb2dyYW09dm9pZCAwfSx0aGlzLm5hbWU9ZS5zaGFkZXJOYW1lLHRoaXMuaWQ9S1MrKyx0aGlzLmNhY2hlS2V5PXQsdGhpcy51c2VkVGltZXM9MSx0aGlzLnByb2dyYW09Zyx0aGlzLnZlcnRleFNoYWRlcj1fLHRoaXMuZnJhZ21lbnRTaGFkZXI9Uyx0aGlzfXZhciBtRT0wLFh1PWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5zaGFkZXJDYWNoZT1uZXcgTWFwLHRoaXMubWF0ZXJpYWxDYWNoZT1uZXcgTWFwfXVwZGF0ZSh0KXtsZXQgZT10LnZlcnRleFNoYWRlcixpPXQuZnJhZ21lbnRTaGFkZXIscj10aGlzLl9nZXRTaGFkZXJTdGFnZShlKSxzPXRoaXMuX2dldFNoYWRlclN0YWdlKGkpLG89dGhpcy5fZ2V0U2hhZGVyQ2FjaGVGb3JNYXRlcmlhbCh0KTtyZXR1cm4gby5oYXMocik9PT0hMSYmKG8uYWRkKHIpLHIudXNlZFRpbWVzKyspLG8uaGFzKHMpPT09ITEmJihvLmFkZChzKSxzLnVzZWRUaW1lcysrKSx0aGlzfXJlbW92ZSh0KXtsZXQgZT10aGlzLm1hdGVyaWFsQ2FjaGUuZ2V0KHQpO2ZvcihsZXQgaSBvZiBlKWkudXNlZFRpbWVzLS0saS51c2VkVGltZXM9PT0wJiZ0aGlzLnNoYWRlckNhY2hlLmRlbGV0ZShpKTtyZXR1cm4gdGhpcy5tYXRlcmlhbENhY2hlLmRlbGV0ZSh0KSx0aGlzfWdldFZlcnRleFNoYWRlcklEKHQpe3JldHVybiB0aGlzLl9nZXRTaGFkZXJTdGFnZSh0LnZlcnRleFNoYWRlcikuaWR9Z2V0RnJhZ21lbnRTaGFkZXJJRCh0KXtyZXR1cm4gdGhpcy5fZ2V0U2hhZGVyU3RhZ2UodC5mcmFnbWVudFNoYWRlcikuaWR9ZGlzcG9zZSgpe3RoaXMuc2hhZGVyQ2FjaGUuY2xlYXIoKSx0aGlzLm1hdGVyaWFsQ2FjaGUuY2xlYXIoKX1fZ2V0U2hhZGVyQ2FjaGVGb3JNYXRlcmlhbCh0KXtsZXQgZT10aGlzLm1hdGVyaWFsQ2FjaGU7cmV0dXJuIGUuaGFzKHQpPT09ITEmJmUuc2V0KHQsbmV3IFNldCksZS5nZXQodCl9X2dldFNoYWRlclN0YWdlKHQpe2xldCBlPXRoaXMuc2hhZGVyQ2FjaGU7aWYoZS5oYXModCk9PT0hMSl7bGV0IGk9bmV3IFl1O2Uuc2V0KHQsaSl9cmV0dXJuIGUuZ2V0KHQpfX0sWXU9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmlkPW1FKyssdGhpcy51c2VkVGltZXM9MH19O2Z1bmN0aW9uIGdFKG4sdCxlLGkscixzLG8pe2xldCBhPW5ldyBpbCxsPW5ldyBYdSxjPVtdLHU9ci5pc1dlYkdMMixoPXIubG9nYXJpdGhtaWNEZXB0aEJ1ZmZlcixmPXIuZmxvYXRWZXJ0ZXhUZXh0dXJlcyxkPXIubWF4VmVydGV4VW5pZm9ybXMsZz1yLnZlcnRleFRleHR1cmVzLHg9ci5wcmVjaXNpb24sdj17TWVzaERlcHRoTWF0ZXJpYWw6ImRlcHRoIixNZXNoRGlzdGFuY2VNYXRlcmlhbDoiZGlzdGFuY2VSR0JBIixNZXNoTm9ybWFsTWF0ZXJpYWw6Im5vcm1hbCIsTWVzaEJhc2ljTWF0ZXJpYWw6ImJhc2ljIixNZXNoTGFtYmVydE1hdGVyaWFsOiJsYW1iZXJ0IixNZXNoUGhvbmdNYXRlcmlhbDoicGhvbmciLE1lc2hUb29uTWF0ZXJpYWw6InRvb24iLE1lc2hTdGFuZGFyZE1hdGVyaWFsOiJwaHlzaWNhbCIsTWVzaFBoeXNpY2FsTWF0ZXJpYWw6InBoeXNpY2FsIixNZXNoTWF0Y2FwTWF0ZXJpYWw6Im1hdGNhcCIsTGluZUJhc2ljTWF0ZXJpYWw6ImJhc2ljIixMaW5lRGFzaGVkTWF0ZXJpYWw6ImRhc2hlZCIsUG9pbnRzTWF0ZXJpYWw6InBvaW50cyIsU2hhZG93TWF0ZXJpYWw6InNoYWRvdyIsU3ByaXRlTWF0ZXJpYWw6InNwcml0ZSJ9O2Z1bmN0aW9uIG0oeSl7bGV0IEQ9eS5za2VsZXRvbi5ib25lcztpZihmKXJldHVybiAxMDI0O3tsZXQgej1NYXRoLmZsb29yKChkLTIwKS80KSxOPU1hdGgubWluKHosRC5sZW5ndGgpO3JldHVybiBOPEQubGVuZ3RoPyhjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IFNrZWxldG9uIGhhcyAiK0QubGVuZ3RoKyIgYm9uZXMuIFRoaXMgR1BVIHN1cHBvcnRzICIrTisiLiIpLDApOk59fWZ1bmN0aW9uIHAoeSxSLEQsRix6KXtsZXQgTj1GLmZvZyxWPXkuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbD9GLmVudmlyb25tZW50Om51bGwsUT0oeS5pc01lc2hTdGFuZGFyZE1hdGVyaWFsP2U6dCkuZ2V0KHkuZW52TWFwfHxWKSxhdD12W3kudHlwZV0sRz16LmlzU2tpbm5lZE1lc2g/bSh6KTowO3kucHJlY2lzaW9uIT09bnVsbCYmKHg9ci5nZXRNYXhQcmVjaXNpb24oeS5wcmVjaXNpb24pLHghPT15LnByZWNpc2lvbiYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFByb2dyYW0uZ2V0UGFyYW1ldGVyczoiLHkucHJlY2lzaW9uLCJub3Qgc3VwcG9ydGVkLCB1c2luZyIseCwiaW5zdGVhZC4iKSk7bGV0ICQsbHQsZHQseHQ7aWYoYXQpe2xldCBCPWZuW2F0XTskPUIudmVydGV4U2hhZGVyLGx0PUIuZnJhZ21lbnRTaGFkZXJ9ZWxzZSAkPXkudmVydGV4U2hhZGVyLGx0PXkuZnJhZ21lbnRTaGFkZXIsbC51cGRhdGUoeSksZHQ9bC5nZXRWZXJ0ZXhTaGFkZXJJRCh5KSx4dD1sLmdldEZyYWdtZW50U2hhZGVySUQoeSk7bGV0IGs9bi5nZXRSZW5kZXJUYXJnZXQoKSxGdD15LmFscGhhVGVzdD4wLG10PXkuY2xlYXJjb2F0PjA7cmV0dXJue2lzV2ViR0wyOnUsc2hhZGVySUQ6YXQsc2hhZGVyTmFtZTp5LnR5cGUsdmVydGV4U2hhZGVyOiQsZnJhZ21lbnRTaGFkZXI6bHQsZGVmaW5lczp5LmRlZmluZXMsY3VzdG9tVmVydGV4U2hhZGVySUQ6ZHQsY3VzdG9tRnJhZ21lbnRTaGFkZXJJRDp4dCxpc1Jhd1NoYWRlck1hdGVyaWFsOnkuaXNSYXdTaGFkZXJNYXRlcmlhbD09PSEwLGdsc2xWZXJzaW9uOnkuZ2xzbFZlcnNpb24scHJlY2lzaW9uOngsaW5zdGFuY2luZzp6LmlzSW5zdGFuY2VkTWVzaD09PSEwLGluc3RhbmNpbmdDb2xvcjp6LmlzSW5zdGFuY2VkTWVzaD09PSEwJiZ6Lmluc3RhbmNlQ29sb3IhPT1udWxsLHN1cHBvcnRzVmVydGV4VGV4dHVyZXM6ZyxvdXRwdXRFbmNvZGluZzprPT09bnVsbD9uLm91dHB1dEVuY29kaW5nOmsuaXNYUlJlbmRlclRhcmdldD09PSEwP2sudGV4dHVyZS5lbmNvZGluZzpyaSxtYXA6ISF5Lm1hcCxtYXRjYXA6ISF5Lm1hdGNhcCxlbnZNYXA6ISFRLGVudk1hcE1vZGU6USYmUS5tYXBwaW5nLGVudk1hcEN1YmVVVjohIVEmJihRLm1hcHBpbmc9PT1SbHx8US5tYXBwaW5nPT09S2gpLGxpZ2h0TWFwOiEheS5saWdodE1hcCxhb01hcDohIXkuYW9NYXAsZW1pc3NpdmVNYXA6ISF5LmVtaXNzaXZlTWFwLGJ1bXBNYXA6ISF5LmJ1bXBNYXAsbm9ybWFsTWFwOiEheS5ub3JtYWxNYXAsb2JqZWN0U3BhY2VOb3JtYWxNYXA6eS5ub3JtYWxNYXBUeXBlPT09ancsdGFuZ2VudFNwYWNlTm9ybWFsTWFwOnkubm9ybWFsTWFwVHlwZT09PXRzLGRlY29kZVZpZGVvVGV4dHVyZTohIXkubWFwJiZ5Lm1hcC5pc1ZpZGVvVGV4dHVyZT09PSEwJiZ5Lm1hcC5lbmNvZGluZz09PSR0LGNsZWFyY29hdDptdCxjbGVhcmNvYXRNYXA6bXQmJiEheS5jbGVhcmNvYXRNYXAsY2xlYXJjb2F0Um91Z2huZXNzTWFwOm10JiYhIXkuY2xlYXJjb2F0Um91Z2huZXNzTWFwLGNsZWFyY29hdE5vcm1hbE1hcDptdCYmISF5LmNsZWFyY29hdE5vcm1hbE1hcCxkaXNwbGFjZW1lbnRNYXA6ISF5LmRpc3BsYWNlbWVudE1hcCxyb3VnaG5lc3NNYXA6ISF5LnJvdWdobmVzc01hcCxtZXRhbG5lc3NNYXA6ISF5Lm1ldGFsbmVzc01hcCxzcGVjdWxhck1hcDohIXkuc3BlY3VsYXJNYXAsc3BlY3VsYXJJbnRlbnNpdHlNYXA6ISF5LnNwZWN1bGFySW50ZW5zaXR5TWFwLHNwZWN1bGFyQ29sb3JNYXA6ISF5LnNwZWN1bGFyQ29sb3JNYXAsYWxwaGFNYXA6ISF5LmFscGhhTWFwLGFscGhhVGVzdDpGdCxhbHBoYVdyaXRlOnkuYWxwaGFXcml0ZXx8eS50cmFuc3BhcmVudCxncmFkaWVudE1hcDohIXkuZ3JhZGllbnRNYXAsc2hlZW46eS5zaGVlbj4wLHNoZWVuQ29sb3JNYXA6ISF5LnNoZWVuQ29sb3JNYXAsc2hlZW5Sb3VnaG5lc3NNYXA6ISF5LnNoZWVuUm91Z2huZXNzTWFwLHRyYW5zbWlzc2lvbjp5LnRyYW5zbWlzc2lvbj4wLHRyYW5zbWlzc2lvbk1hcDohIXkudHJhbnNtaXNzaW9uTWFwLHRoaWNrbmVzc01hcDohIXkudGhpY2tuZXNzTWFwLGNvbWJpbmU6eS5jb21iaW5lLHZlcnRleFRhbmdlbnRzOiEheS5ub3JtYWxNYXAmJiEhei5nZW9tZXRyeSYmISF6Lmdlb21ldHJ5LmF0dHJpYnV0ZXMudGFuZ2VudCx2ZXJ0ZXhDb2xvcnM6eS52ZXJ0ZXhDb2xvcnMsdmVydGV4QWxwaGFzOnkudmVydGV4Q29sb3JzPT09ITAmJiEhei5nZW9tZXRyeSYmISF6Lmdlb21ldHJ5LmF0dHJpYnV0ZXMuY29sb3ImJnouZ2VvbWV0cnkuYXR0cmlidXRlcy5jb2xvci5pdGVtU2l6ZT09PTQsdmVydGV4VXZzOiEheS5tYXB8fCEheS5idW1wTWFwfHwhIXkubm9ybWFsTWFwfHwhIXkuc3BlY3VsYXJNYXB8fCEheS5hbHBoYU1hcHx8ISF5LmVtaXNzaXZlTWFwfHwhIXkucm91Z2huZXNzTWFwfHwhIXkubWV0YWxuZXNzTWFwfHwhIXkuY2xlYXJjb2F0TWFwfHwhIXkuY2xlYXJjb2F0Um91Z2huZXNzTWFwfHwhIXkuY2xlYXJjb2F0Tm9ybWFsTWFwfHwhIXkuZGlzcGxhY2VtZW50TWFwfHwhIXkudHJhbnNtaXNzaW9uTWFwfHwhIXkudGhpY2tuZXNzTWFwfHwhIXkuc3BlY3VsYXJJbnRlbnNpdHlNYXB8fCEheS5zcGVjdWxhckNvbG9yTWFwfHwhIXkuc2hlZW5Db2xvck1hcHx8ISF5LnNoZWVuUm91Z2huZXNzTWFwLHV2c1ZlcnRleE9ubHk6ISghIXkubWFwfHwhIXkuYnVtcE1hcHx8ISF5Lm5vcm1hbE1hcHx8ISF5LnNwZWN1bGFyTWFwfHwhIXkuYWxwaGFNYXB8fCEheS5lbWlzc2l2ZU1hcHx8ISF5LnJvdWdobmVzc01hcHx8ISF5Lm1ldGFsbmVzc01hcHx8ISF5LmNsZWFyY29hdE5vcm1hbE1hcHx8eS50cmFuc21pc3Npb24+MHx8ISF5LnRyYW5zbWlzc2lvbk1hcHx8ISF5LnRoaWNrbmVzc01hcHx8ISF5LnNwZWN1bGFySW50ZW5zaXR5TWFwfHwhIXkuc3BlY3VsYXJDb2xvck1hcHx8eS5zaGVlbj4wfHwhIXkuc2hlZW5Db2xvck1hcHx8ISF5LnNoZWVuUm91Z2huZXNzTWFwKSYmISF5LmRpc3BsYWNlbWVudE1hcCxmb2c6ISFOLHVzZUZvZzp5LmZvZyxmb2dFeHAyOk4mJk4uaXNGb2dFeHAyLGZsYXRTaGFkaW5nOiEheS5mbGF0U2hhZGluZyxzaXplQXR0ZW51YXRpb246eS5zaXplQXR0ZW51YXRpb24sbG9nYXJpdGhtaWNEZXB0aEJ1ZmZlcjpoLHNraW5uaW5nOnouaXNTa2lubmVkTWVzaD09PSEwJiZHPjAsbWF4Qm9uZXM6Ryx1c2VWZXJ0ZXhUZXh0dXJlOmYsbW9ycGhUYXJnZXRzOiEhei5nZW9tZXRyeSYmISF6Lmdlb21ldHJ5Lm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbixtb3JwaE5vcm1hbHM6ISF6Lmdlb21ldHJ5JiYhIXouZ2VvbWV0cnkubW9ycGhBdHRyaWJ1dGVzLm5vcm1hbCxtb3JwaFRhcmdldHNDb3VudDohIXouZ2VvbWV0cnkmJiEhei5nZW9tZXRyeS5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24/ei5nZW9tZXRyeS5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24ubGVuZ3RoOjAsbnVtRGlyTGlnaHRzOlIuZGlyZWN0aW9uYWwubGVuZ3RoLG51bVBvaW50TGlnaHRzOlIucG9pbnQubGVuZ3RoLG51bVNwb3RMaWdodHM6Ui5zcG90Lmxlbmd0aCxudW1SZWN0QXJlYUxpZ2h0czpSLnJlY3RBcmVhLmxlbmd0aCxudW1IZW1pTGlnaHRzOlIuaGVtaS5sZW5ndGgsbnVtRGlyTGlnaHRTaGFkb3dzOlIuZGlyZWN0aW9uYWxTaGFkb3dNYXAubGVuZ3RoLG51bVBvaW50TGlnaHRTaGFkb3dzOlIucG9pbnRTaGFkb3dNYXAubGVuZ3RoLG51bVNwb3RMaWdodFNoYWRvd3M6Ui5zcG90U2hhZG93TWFwLmxlbmd0aCxudW1DbGlwcGluZ1BsYW5lczpvLm51bVBsYW5lcyxudW1DbGlwSW50ZXJzZWN0aW9uOm8ubnVtSW50ZXJzZWN0aW9uLGRpdGhlcmluZzp5LmRpdGhlcmluZyxzaGFkb3dNYXBFbmFibGVkOm4uc2hhZG93TWFwLmVuYWJsZWQmJkQubGVuZ3RoPjAsc2hhZG93TWFwVHlwZTpuLnNoYWRvd01hcC50eXBlLHRvbmVNYXBwaW5nOnkudG9uZU1hcHBlZD9uLnRvbmVNYXBwaW5nOnRpLHBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzOm4ucGh5c2ljYWxseUNvcnJlY3RMaWdodHMscHJlbXVsdGlwbGllZEFscGhhOnkucHJlbXVsdGlwbGllZEFscGhhLGRvdWJsZVNpZGVkOnkuc2lkZT09PUhyLGZsaXBTaWRlZDp5LnNpZGU9PT1oZSxkZXB0aFBhY2tpbmc6eS5kZXB0aFBhY2tpbmchPT12b2lkIDA/eS5kZXB0aFBhY2tpbmc6ITEsaW5kZXgwQXR0cmlidXRlTmFtZTp5LmluZGV4MEF0dHJpYnV0ZU5hbWUsZXh0ZW5zaW9uRGVyaXZhdGl2ZXM6eS5leHRlbnNpb25zJiZ5LmV4dGVuc2lvbnMuZGVyaXZhdGl2ZXMsZXh0ZW5zaW9uRnJhZ0RlcHRoOnkuZXh0ZW5zaW9ucyYmeS5leHRlbnNpb25zLmZyYWdEZXB0aCxleHRlbnNpb25EcmF3QnVmZmVyczp5LmV4dGVuc2lvbnMmJnkuZXh0ZW5zaW9ucy5kcmF3QnVmZmVycyxleHRlbnNpb25TaGFkZXJUZXh0dXJlTE9EOnkuZXh0ZW5zaW9ucyYmeS5leHRlbnNpb25zLnNoYWRlclRleHR1cmVMT0QscmVuZGVyZXJFeHRlbnNpb25GcmFnRGVwdGg6dXx8aS5oYXMoIkVYVF9mcmFnX2RlcHRoIikscmVuZGVyZXJFeHRlbnNpb25EcmF3QnVmZmVyczp1fHxpLmhhcygiV0VCR0xfZHJhd19idWZmZXJzIikscmVuZGVyZXJFeHRlbnNpb25TaGFkZXJUZXh0dXJlTG9kOnV8fGkuaGFzKCJFWFRfc2hhZGVyX3RleHR1cmVfbG9kIiksY3VzdG9tUHJvZ3JhbUNhY2hlS2V5OnkuY3VzdG9tUHJvZ3JhbUNhY2hlS2V5KCl9fWZ1bmN0aW9uIGIoeSl7bGV0IFI9W107aWYoeS5zaGFkZXJJRD9SLnB1c2goeS5zaGFkZXJJRCk6KFIucHVzaCh5LmN1c3RvbVZlcnRleFNoYWRlcklEKSxSLnB1c2goeS5jdXN0b21GcmFnbWVudFNoYWRlcklEKSkseS5kZWZpbmVzIT09dm9pZCAwKWZvcihsZXQgRCBpbiB5LmRlZmluZXMpUi5wdXNoKEQpLFIucHVzaCh5LmRlZmluZXNbRF0pO3JldHVybiB5LmlzUmF3U2hhZGVyTWF0ZXJpYWw9PT0hMSYmKF8oUix5KSxTKFIseSksUi5wdXNoKG4ub3V0cHV0RW5jb2RpbmcpKSxSLnB1c2goeS5jdXN0b21Qcm9ncmFtQ2FjaGVLZXkpLFIuam9pbigpfWZ1bmN0aW9uIF8oeSxSKXt5LnB1c2goUi5wcmVjaXNpb24pLHkucHVzaChSLm91dHB1dEVuY29kaW5nKSx5LnB1c2goUi5lbnZNYXBNb2RlKSx5LnB1c2goUi5jb21iaW5lKSx5LnB1c2goUi52ZXJ0ZXhVdnMpLHkucHVzaChSLmZvZ0V4cDIpLHkucHVzaChSLnNpemVBdHRlbnVhdGlvbikseS5wdXNoKFIubWF4Qm9uZXMpLHkucHVzaChSLm1vcnBoVGFyZ2V0c0NvdW50KSx5LnB1c2goUi5udW1EaXJMaWdodHMpLHkucHVzaChSLm51bVBvaW50TGlnaHRzKSx5LnB1c2goUi5udW1TcG90TGlnaHRzKSx5LnB1c2goUi5udW1IZW1pTGlnaHRzKSx5LnB1c2goUi5udW1SZWN0QXJlYUxpZ2h0cykseS5wdXNoKFIubnVtRGlyTGlnaHRTaGFkb3dzKSx5LnB1c2goUi5udW1Qb2ludExpZ2h0U2hhZG93cykseS5wdXNoKFIubnVtU3BvdExpZ2h0U2hhZG93cykseS5wdXNoKFIuc2hhZG93TWFwVHlwZSkseS5wdXNoKFIudG9uZU1hcHBpbmcpLHkucHVzaChSLm51bUNsaXBwaW5nUGxhbmVzKSx5LnB1c2goUi5udW1DbGlwSW50ZXJzZWN0aW9uKSx5LnB1c2goUi5hbHBoYVdyaXRlKX1mdW5jdGlvbiBTKHksUil7YS5kaXNhYmxlQWxsKCksUi5pc1dlYkdMMiYmYS5lbmFibGUoMCksUi5zdXBwb3J0c1ZlcnRleFRleHR1cmVzJiZhLmVuYWJsZSgxKSxSLmluc3RhbmNpbmcmJmEuZW5hYmxlKDIpLFIuaW5zdGFuY2luZ0NvbG9yJiZhLmVuYWJsZSgzKSxSLm1hcCYmYS5lbmFibGUoNCksUi5tYXRjYXAmJmEuZW5hYmxlKDUpLFIuZW52TWFwJiZhLmVuYWJsZSg2KSxSLmVudk1hcEN1YmVVViYmYS5lbmFibGUoNyksUi5saWdodE1hcCYmYS5lbmFibGUoOCksUi5hb01hcCYmYS5lbmFibGUoOSksUi5lbWlzc2l2ZU1hcCYmYS5lbmFibGUoMTApLFIuYnVtcE1hcCYmYS5lbmFibGUoMTEpLFIubm9ybWFsTWFwJiZhLmVuYWJsZSgxMiksUi5vYmplY3RTcGFjZU5vcm1hbE1hcCYmYS5lbmFibGUoMTMpLFIudGFuZ2VudFNwYWNlTm9ybWFsTWFwJiZhLmVuYWJsZSgxNCksUi5jbGVhcmNvYXQmJmEuZW5hYmxlKDE1KSxSLmNsZWFyY29hdE1hcCYmYS5lbmFibGUoMTYpLFIuY2xlYXJjb2F0Um91Z2huZXNzTWFwJiZhLmVuYWJsZSgxNyksUi5jbGVhcmNvYXROb3JtYWxNYXAmJmEuZW5hYmxlKDE4KSxSLmRpc3BsYWNlbWVudE1hcCYmYS5lbmFibGUoMTkpLFIuc3BlY3VsYXJNYXAmJmEuZW5hYmxlKDIwKSxSLnJvdWdobmVzc01hcCYmYS5lbmFibGUoMjEpLFIubWV0YWxuZXNzTWFwJiZhLmVuYWJsZSgyMiksUi5ncmFkaWVudE1hcCYmYS5lbmFibGUoMjMpLFIuYWxwaGFNYXAmJmEuZW5hYmxlKDI0KSxSLmFscGhhVGVzdCYmYS5lbmFibGUoMjUpLFIudmVydGV4Q29sb3JzJiZhLmVuYWJsZSgyNiksUi52ZXJ0ZXhBbHBoYXMmJmEuZW5hYmxlKDI3KSxSLnZlcnRleFV2cyYmYS5lbmFibGUoMjgpLFIudmVydGV4VGFuZ2VudHMmJmEuZW5hYmxlKDI5KSxSLnV2c1ZlcnRleE9ubHkmJmEuZW5hYmxlKDMwKSxSLmZvZyYmYS5lbmFibGUoMzEpLHkucHVzaChhLm1hc2spLGEuZGlzYWJsZUFsbCgpLFIudXNlRm9nJiZhLmVuYWJsZSgwKSxSLmZsYXRTaGFkaW5nJiZhLmVuYWJsZSgxKSxSLmxvZ2FyaXRobWljRGVwdGhCdWZmZXImJmEuZW5hYmxlKDIpLFIuc2tpbm5pbmcmJmEuZW5hYmxlKDMpLFIudXNlVmVydGV4VGV4dHVyZSYmYS5lbmFibGUoNCksUi5tb3JwaFRhcmdldHMmJmEuZW5hYmxlKDUpLFIubW9ycGhOb3JtYWxzJiZhLmVuYWJsZSg2KSxSLnByZW11bHRpcGxpZWRBbHBoYSYmYS5lbmFibGUoNyksUi5zaGFkb3dNYXBFbmFibGVkJiZhLmVuYWJsZSg4KSxSLnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzJiZhLmVuYWJsZSg5KSxSLmRvdWJsZVNpZGVkJiZhLmVuYWJsZSgxMCksUi5mbGlwU2lkZWQmJmEuZW5hYmxlKDExKSxSLmRlcHRoUGFja2luZyYmYS5lbmFibGUoMTIpLFIuZGl0aGVyaW5nJiZhLmVuYWJsZSgxMyksUi5zcGVjdWxhckludGVuc2l0eU1hcCYmYS5lbmFibGUoMTQpLFIuc3BlY3VsYXJDb2xvck1hcCYmYS5lbmFibGUoMTUpLFIudHJhbnNtaXNzaW9uJiZhLmVuYWJsZSgxNiksUi50cmFuc21pc3Npb25NYXAmJmEuZW5hYmxlKDE3KSxSLnRoaWNrbmVzc01hcCYmYS5lbmFibGUoMTgpLFIuc2hlZW4mJmEuZW5hYmxlKDE5KSxSLnNoZWVuQ29sb3JNYXAmJmEuZW5hYmxlKDIwKSxSLnNoZWVuUm91Z2huZXNzTWFwJiZhLmVuYWJsZSgyMSksUi5kZWNvZGVWaWRlb1RleHR1cmUmJmEuZW5hYmxlKDIyKSx5LnB1c2goYS5tYXNrKX1mdW5jdGlvbiBMKHkpe2xldCBSPXZbeS50eXBlXSxEO2lmKFIpe2xldCBGPWZuW1JdO0Q9cE0uY2xvbmUoRi51bmlmb3Jtcyl9ZWxzZSBEPXkudW5pZm9ybXM7cmV0dXJuIER9ZnVuY3Rpb24gQSh5LFIpe2xldCBEO2ZvcihsZXQgRj0wLHo9Yy5sZW5ndGg7Rjx6O0YrKyl7bGV0IE49Y1tGXTtpZihOLmNhY2hlS2V5PT09Uil7RD1OLCsrRC51c2VkVGltZXM7YnJlYWt9fXJldHVybiBEPT09dm9pZCAwJiYoRD1uZXcgcEUobixSLHkscyksYy5wdXNoKEQpKSxEfWZ1bmN0aW9uIEgoeSl7aWYoLS15LnVzZWRUaW1lcz09PTApe2xldCBSPWMuaW5kZXhPZih5KTtjW1JdPWNbYy5sZW5ndGgtMV0sYy5wb3AoKSx5LmRlc3Ryb3koKX19ZnVuY3Rpb24gdHQoeSl7bC5yZW1vdmUoeSl9ZnVuY3Rpb24gWCgpe2wuZGlzcG9zZSgpfXJldHVybntnZXRQYXJhbWV0ZXJzOnAsZ2V0UHJvZ3JhbUNhY2hlS2V5OmIsZ2V0VW5pZm9ybXM6TCxhY3F1aXJlUHJvZ3JhbTpBLHJlbGVhc2VQcm9ncmFtOkgscmVsZWFzZVNoYWRlckNhY2hlOnR0LHByb2dyYW1zOmMsZGlzcG9zZTpYfX1mdW5jdGlvbiB4RSgpe2xldCBuPW5ldyBXZWFrTWFwO2Z1bmN0aW9uIHQocyl7bGV0IG89bi5nZXQocyk7cmV0dXJuIG89PT12b2lkIDAmJihvPXt9LG4uc2V0KHMsbykpLG99ZnVuY3Rpb24gZShzKXtuLmRlbGV0ZShzKX1mdW5jdGlvbiBpKHMsbyxhKXtuLmdldChzKVtvXT1hfWZ1bmN0aW9uIHIoKXtuPW5ldyBXZWFrTWFwfXJldHVybntnZXQ6dCxyZW1vdmU6ZSx1cGRhdGU6aSxkaXNwb3NlOnJ9fWZ1bmN0aW9uIHlFKG4sdCl7cmV0dXJuIG4uZ3JvdXBPcmRlciE9PXQuZ3JvdXBPcmRlcj9uLmdyb3VwT3JkZXItdC5ncm91cE9yZGVyOm4ucmVuZGVyT3JkZXIhPT10LnJlbmRlck9yZGVyP24ucmVuZGVyT3JkZXItdC5yZW5kZXJPcmRlcjpuLm1hdGVyaWFsLmlkIT09dC5tYXRlcmlhbC5pZD9uLm1hdGVyaWFsLmlkLXQubWF0ZXJpYWwuaWQ6bi56IT09dC56P24uei10Lno6bi5pZC10LmlkfWZ1bmN0aW9uIE5nKG4sdCl7cmV0dXJuIG4uZ3JvdXBPcmRlciE9PXQuZ3JvdXBPcmRlcj9uLmdyb3VwT3JkZXItdC5ncm91cE9yZGVyOm4ucmVuZGVyT3JkZXIhPT10LnJlbmRlck9yZGVyP24ucmVuZGVyT3JkZXItdC5yZW5kZXJPcmRlcjpuLnohPT10Lno/dC56LW4uejpuLmlkLXQuaWR9ZnVuY3Rpb24gRmcoKXtsZXQgbj1bXSx0PTAsZT1bXSxpPVtdLHI9W107ZnVuY3Rpb24gcygpe3Q9MCxlLmxlbmd0aD0wLGkubGVuZ3RoPTAsci5sZW5ndGg9MH1mdW5jdGlvbiBvKGgsZixkLGcseCx2KXtsZXQgbT1uW3RdO3JldHVybiBtPT09dm9pZCAwPyhtPXtpZDpoLmlkLG9iamVjdDpoLGdlb21ldHJ5OmYsbWF0ZXJpYWw6ZCxncm91cE9yZGVyOmcscmVuZGVyT3JkZXI6aC5yZW5kZXJPcmRlcix6OngsZ3JvdXA6dn0sblt0XT1tKToobS5pZD1oLmlkLG0ub2JqZWN0PWgsbS5nZW9tZXRyeT1mLG0ubWF0ZXJpYWw9ZCxtLmdyb3VwT3JkZXI9ZyxtLnJlbmRlck9yZGVyPWgucmVuZGVyT3JkZXIsbS56PXgsbS5ncm91cD12KSx0KyssbX1mdW5jdGlvbiBhKGgsZixkLGcseCx2KXtsZXQgbT1vKGgsZixkLGcseCx2KTtkLnRyYW5zbWlzc2lvbj4wP2kucHVzaChtKTpkLnRyYW5zcGFyZW50PT09ITA/ci5wdXNoKG0pOmUucHVzaChtKX1mdW5jdGlvbiBsKGgsZixkLGcseCx2KXtsZXQgbT1vKGgsZixkLGcseCx2KTtkLnRyYW5zbWlzc2lvbj4wP2kudW5zaGlmdChtKTpkLnRyYW5zcGFyZW50PT09ITA/ci51bnNoaWZ0KG0pOmUudW5zaGlmdChtKX1mdW5jdGlvbiBjKGgsZil7ZS5sZW5ndGg+MSYmZS5zb3J0KGh8fHlFKSxpLmxlbmd0aD4xJiZpLnNvcnQoZnx8TmcpLHIubGVuZ3RoPjEmJnIuc29ydChmfHxOZyl9ZnVuY3Rpb24gdSgpe2ZvcihsZXQgaD10LGY9bi5sZW5ndGg7aDxmO2grKyl7bGV0IGQ9bltoXTtpZihkLmlkPT09bnVsbClicmVhaztkLmlkPW51bGwsZC5vYmplY3Q9bnVsbCxkLmdlb21ldHJ5PW51bGwsZC5tYXRlcmlhbD1udWxsLGQuZ3JvdXA9bnVsbH19cmV0dXJue29wYXF1ZTplLHRyYW5zbWlzc2l2ZTppLHRyYW5zcGFyZW50OnIsaW5pdDpzLHB1c2g6YSx1bnNoaWZ0OmwsZmluaXNoOnUsc29ydDpjfX1mdW5jdGlvbiB2RSgpe2xldCBuPW5ldyBXZWFrTWFwO2Z1bmN0aW9uIHQoaSxyKXtsZXQgcztyZXR1cm4gbi5oYXMoaSk9PT0hMT8ocz1uZXcgRmcsbi5zZXQoaSxbc10pKTpyPj1uLmdldChpKS5sZW5ndGg/KHM9bmV3IEZnLG4uZ2V0KGkpLnB1c2gocykpOnM9bi5nZXQoaSlbcl0sc31mdW5jdGlvbiBlKCl7bj1uZXcgV2Vha01hcH1yZXR1cm57Z2V0OnQsZGlzcG9zZTplfX1mdW5jdGlvbiBfRSgpe2xldCBuPXt9O3JldHVybntnZXQ6ZnVuY3Rpb24odCl7aWYoblt0LmlkXSE9PXZvaWQgMClyZXR1cm4gblt0LmlkXTtsZXQgZTtzd2l0Y2godC50eXBlKXtjYXNlIkRpcmVjdGlvbmFsTGlnaHQiOmU9e2RpcmVjdGlvbjpuZXcgVCxjb2xvcjpuZXcgZnR9O2JyZWFrO2Nhc2UiU3BvdExpZ2h0IjplPXtwb3NpdGlvbjpuZXcgVCxkaXJlY3Rpb246bmV3IFQsY29sb3I6bmV3IGZ0LGRpc3RhbmNlOjAsY29uZUNvczowLHBlbnVtYnJhQ29zOjAsZGVjYXk6MH07YnJlYWs7Y2FzZSJQb2ludExpZ2h0IjplPXtwb3NpdGlvbjpuZXcgVCxjb2xvcjpuZXcgZnQsZGlzdGFuY2U6MCxkZWNheTowfTticmVhaztjYXNlIkhlbWlzcGhlcmVMaWdodCI6ZT17ZGlyZWN0aW9uOm5ldyBULHNreUNvbG9yOm5ldyBmdCxncm91bmRDb2xvcjpuZXcgZnR9O2JyZWFrO2Nhc2UiUmVjdEFyZWFMaWdodCI6ZT17Y29sb3I6bmV3IGZ0LHBvc2l0aW9uOm5ldyBULGhhbGZXaWR0aDpuZXcgVCxoYWxmSGVpZ2h0Om5ldyBUfTticmVha31yZXR1cm4gblt0LmlkXT1lLGV9fX1mdW5jdGlvbiB3RSgpe2xldCBuPXt9O3JldHVybntnZXQ6ZnVuY3Rpb24odCl7aWYoblt0LmlkXSE9PXZvaWQgMClyZXR1cm4gblt0LmlkXTtsZXQgZTtzd2l0Y2godC50eXBlKXtjYXNlIkRpcmVjdGlvbmFsTGlnaHQiOmU9e3NoYWRvd0JpYXM6MCxzaGFkb3dOb3JtYWxCaWFzOjAsc2hhZG93UmFkaXVzOjEsc2hhZG93TWFwU2l6ZTpuZXcgS307YnJlYWs7Y2FzZSJTcG90TGlnaHQiOmU9e3NoYWRvd0JpYXM6MCxzaGFkb3dOb3JtYWxCaWFzOjAsc2hhZG93UmFkaXVzOjEsc2hhZG93TWFwU2l6ZTpuZXcgS307YnJlYWs7Y2FzZSJQb2ludExpZ2h0IjplPXtzaGFkb3dCaWFzOjAsc2hhZG93Tm9ybWFsQmlhczowLHNoYWRvd1JhZGl1czoxLHNoYWRvd01hcFNpemU6bmV3IEssc2hhZG93Q2FtZXJhTmVhcjoxLHNoYWRvd0NhbWVyYUZhcjoxZTN9O2JyZWFrfXJldHVybiBuW3QuaWRdPWUsZX19fXZhciBNRT0wO2Z1bmN0aW9uIGJFKG4sdCl7cmV0dXJuKHQuY2FzdFNoYWRvdz8xOjApLShuLmNhc3RTaGFkb3c/MTowKX1mdW5jdGlvbiBTRShuLHQpe2xldCBlPW5ldyBfRSxpPXdFKCkscj17dmVyc2lvbjowLGhhc2g6e2RpcmVjdGlvbmFsTGVuZ3RoOi0xLHBvaW50TGVuZ3RoOi0xLHNwb3RMZW5ndGg6LTEscmVjdEFyZWFMZW5ndGg6LTEsaGVtaUxlbmd0aDotMSxudW1EaXJlY3Rpb25hbFNoYWRvd3M6LTEsbnVtUG9pbnRTaGFkb3dzOi0xLG51bVNwb3RTaGFkb3dzOi0xfSxhbWJpZW50OlswLDAsMF0scHJvYmU6W10sZGlyZWN0aW9uYWw6W10sZGlyZWN0aW9uYWxTaGFkb3c6W10sZGlyZWN0aW9uYWxTaGFkb3dNYXA6W10sZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXg6W10sc3BvdDpbXSxzcG90U2hhZG93OltdLHNwb3RTaGFkb3dNYXA6W10sc3BvdFNoYWRvd01hdHJpeDpbXSxyZWN0QXJlYTpbXSxyZWN0QXJlYUxUQzE6bnVsbCxyZWN0QXJlYUxUQzI6bnVsbCxwb2ludDpbXSxwb2ludFNoYWRvdzpbXSxwb2ludFNoYWRvd01hcDpbXSxwb2ludFNoYWRvd01hdHJpeDpbXSxoZW1pOltdfTtmb3IobGV0IHU9MDt1PDk7dSsrKXIucHJvYmUucHVzaChuZXcgVCk7bGV0IHM9bmV3IFQsbz1uZXcgd3QsYT1uZXcgd3Q7ZnVuY3Rpb24gbCh1LGgpe2xldCBmPTAsZD0wLGc9MDtmb3IobGV0IHR0PTA7dHQ8OTt0dCsrKXIucHJvYmVbdHRdLnNldCgwLDAsMCk7bGV0IHg9MCx2PTAsbT0wLHA9MCxiPTAsXz0wLFM9MCxMPTA7dS5zb3J0KGJFKTtsZXQgQT1oIT09ITA/TWF0aC5QSToxO2ZvcihsZXQgdHQ9MCxYPXUubGVuZ3RoO3R0PFg7dHQrKyl7bGV0IHk9dVt0dF0sUj15LmNvbG9yLEQ9eS5pbnRlbnNpdHksRj15LmRpc3RhbmNlLHo9eS5zaGFkb3cmJnkuc2hhZG93Lm1hcD95LnNoYWRvdy5tYXAudGV4dHVyZTpudWxsO2lmKHkuaXNBbWJpZW50TGlnaHQpZis9Ui5yKkQqQSxkKz1SLmcqRCpBLGcrPVIuYipEKkE7ZWxzZSBpZih5LmlzTGlnaHRQcm9iZSlmb3IobGV0IE49MDtOPDk7TisrKXIucHJvYmVbTl0uYWRkU2NhbGVkVmVjdG9yKHkuc2guY29lZmZpY2llbnRzW05dLEQpO2Vsc2UgaWYoeS5pc0RpcmVjdGlvbmFsTGlnaHQpe2xldCBOPWUuZ2V0KHkpO2lmKE4uY29sb3IuY29weSh5LmNvbG9yKS5tdWx0aXBseVNjYWxhcih5LmludGVuc2l0eSpBKSx5LmNhc3RTaGFkb3cpe2xldCBWPXkuc2hhZG93LFE9aS5nZXQoeSk7US5zaGFkb3dCaWFzPVYuYmlhcyxRLnNoYWRvd05vcm1hbEJpYXM9Vi5ub3JtYWxCaWFzLFEuc2hhZG93UmFkaXVzPVYucmFkaXVzLFEuc2hhZG93TWFwU2l6ZT1WLm1hcFNpemUsci5kaXJlY3Rpb25hbFNoYWRvd1t4XT1RLHIuZGlyZWN0aW9uYWxTaGFkb3dNYXBbeF09eixyLmRpcmVjdGlvbmFsU2hhZG93TWF0cml4W3hdPXkuc2hhZG93Lm1hdHJpeCxfKyt9ci5kaXJlY3Rpb25hbFt4XT1OLHgrK31lbHNlIGlmKHkuaXNTcG90TGlnaHQpe2xldCBOPWUuZ2V0KHkpO2lmKE4ucG9zaXRpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKHkubWF0cml4V29ybGQpLE4uY29sb3IuY29weShSKS5tdWx0aXBseVNjYWxhcihEKkEpLE4uZGlzdGFuY2U9RixOLmNvbmVDb3M9TWF0aC5jb3MoeS5hbmdsZSksTi5wZW51bWJyYUNvcz1NYXRoLmNvcyh5LmFuZ2xlKigxLXkucGVudW1icmEpKSxOLmRlY2F5PXkuZGVjYXkseS5jYXN0U2hhZG93KXtsZXQgVj15LnNoYWRvdyxRPWkuZ2V0KHkpO1Euc2hhZG93Qmlhcz1WLmJpYXMsUS5zaGFkb3dOb3JtYWxCaWFzPVYubm9ybWFsQmlhcyxRLnNoYWRvd1JhZGl1cz1WLnJhZGl1cyxRLnNoYWRvd01hcFNpemU9Vi5tYXBTaXplLHIuc3BvdFNoYWRvd1ttXT1RLHIuc3BvdFNoYWRvd01hcFttXT16LHIuc3BvdFNoYWRvd01hdHJpeFttXT15LnNoYWRvdy5tYXRyaXgsTCsrfXIuc3BvdFttXT1OLG0rK31lbHNlIGlmKHkuaXNSZWN0QXJlYUxpZ2h0KXtsZXQgTj1lLmdldCh5KTtOLmNvbG9yLmNvcHkoUikubXVsdGlwbHlTY2FsYXIoRCksTi5oYWxmV2lkdGguc2V0KHkud2lkdGgqLjUsMCwwKSxOLmhhbGZIZWlnaHQuc2V0KDAseS5oZWlnaHQqLjUsMCksci5yZWN0QXJlYVtwXT1OLHArK31lbHNlIGlmKHkuaXNQb2ludExpZ2h0KXtsZXQgTj1lLmdldCh5KTtpZihOLmNvbG9yLmNvcHkoeS5jb2xvcikubXVsdGlwbHlTY2FsYXIoeS5pbnRlbnNpdHkqQSksTi5kaXN0YW5jZT15LmRpc3RhbmNlLE4uZGVjYXk9eS5kZWNheSx5LmNhc3RTaGFkb3cpe2xldCBWPXkuc2hhZG93LFE9aS5nZXQoeSk7US5zaGFkb3dCaWFzPVYuYmlhcyxRLnNoYWRvd05vcm1hbEJpYXM9Vi5ub3JtYWxCaWFzLFEuc2hhZG93UmFkaXVzPVYucmFkaXVzLFEuc2hhZG93TWFwU2l6ZT1WLm1hcFNpemUsUS5zaGFkb3dDYW1lcmFOZWFyPVYuY2FtZXJhLm5lYXIsUS5zaGFkb3dDYW1lcmFGYXI9Vi5jYW1lcmEuZmFyLHIucG9pbnRTaGFkb3dbdl09USxyLnBvaW50U2hhZG93TWFwW3ZdPXosci5wb2ludFNoYWRvd01hdHJpeFt2XT15LnNoYWRvdy5tYXRyaXgsUysrfXIucG9pbnRbdl09Tix2Kyt9ZWxzZSBpZih5LmlzSGVtaXNwaGVyZUxpZ2h0KXtsZXQgTj1lLmdldCh5KTtOLnNreUNvbG9yLmNvcHkoeS5jb2xvcikubXVsdGlwbHlTY2FsYXIoRCpBKSxOLmdyb3VuZENvbG9yLmNvcHkoeS5ncm91bmRDb2xvcikubXVsdGlwbHlTY2FsYXIoRCpBKSxyLmhlbWlbYl09TixiKyt9fXA+MCYmKHQuaXNXZWJHTDJ8fG4uaGFzKCJPRVNfdGV4dHVyZV9mbG9hdF9saW5lYXIiKT09PSEwPyhyLnJlY3RBcmVhTFRDMT1vdC5MVENfRkxPQVRfMSxyLnJlY3RBcmVhTFRDMj1vdC5MVENfRkxPQVRfMik6bi5oYXMoIk9FU190ZXh0dXJlX2hhbGZfZmxvYXRfbGluZWFyIik9PT0hMD8oci5yZWN0QXJlYUxUQzE9b3QuTFRDX0hBTEZfMSxyLnJlY3RBcmVhTFRDMj1vdC5MVENfSEFMRl8yKTpjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBVbmFibGUgdG8gdXNlIFJlY3RBcmVhTGlnaHQuIE1pc3NpbmcgV2ViR0wgZXh0ZW5zaW9ucy4iKSksci5hbWJpZW50WzBdPWYsci5hbWJpZW50WzFdPWQsci5hbWJpZW50WzJdPWc7bGV0IEg9ci5oYXNoOyhILmRpcmVjdGlvbmFsTGVuZ3RoIT09eHx8SC5wb2ludExlbmd0aCE9PXZ8fEguc3BvdExlbmd0aCE9PW18fEgucmVjdEFyZWFMZW5ndGghPT1wfHxILmhlbWlMZW5ndGghPT1ifHxILm51bURpcmVjdGlvbmFsU2hhZG93cyE9PV98fEgubnVtUG9pbnRTaGFkb3dzIT09U3x8SC5udW1TcG90U2hhZG93cyE9PUwpJiYoci5kaXJlY3Rpb25hbC5sZW5ndGg9eCxyLnNwb3QubGVuZ3RoPW0sci5yZWN0QXJlYS5sZW5ndGg9cCxyLnBvaW50Lmxlbmd0aD12LHIuaGVtaS5sZW5ndGg9YixyLmRpcmVjdGlvbmFsU2hhZG93Lmxlbmd0aD1fLHIuZGlyZWN0aW9uYWxTaGFkb3dNYXAubGVuZ3RoPV8sci5wb2ludFNoYWRvdy5sZW5ndGg9UyxyLnBvaW50U2hhZG93TWFwLmxlbmd0aD1TLHIuc3BvdFNoYWRvdy5sZW5ndGg9TCxyLnNwb3RTaGFkb3dNYXAubGVuZ3RoPUwsci5kaXJlY3Rpb25hbFNoYWRvd01hdHJpeC5sZW5ndGg9XyxyLnBvaW50U2hhZG93TWF0cml4Lmxlbmd0aD1TLHIuc3BvdFNoYWRvd01hdHJpeC5sZW5ndGg9TCxILmRpcmVjdGlvbmFsTGVuZ3RoPXgsSC5wb2ludExlbmd0aD12LEguc3BvdExlbmd0aD1tLEgucmVjdEFyZWFMZW5ndGg9cCxILmhlbWlMZW5ndGg9YixILm51bURpcmVjdGlvbmFsU2hhZG93cz1fLEgubnVtUG9pbnRTaGFkb3dzPVMsSC5udW1TcG90U2hhZG93cz1MLHIudmVyc2lvbj1NRSsrKX1mdW5jdGlvbiBjKHUsaCl7bGV0IGY9MCxkPTAsZz0wLHg9MCx2PTAsbT1oLm1hdHJpeFdvcmxkSW52ZXJzZTtmb3IobGV0IHA9MCxiPXUubGVuZ3RoO3A8YjtwKyspe2xldCBfPXVbcF07aWYoXy5pc0RpcmVjdGlvbmFsTGlnaHQpe2xldCBTPXIuZGlyZWN0aW9uYWxbZl07Uy5kaXJlY3Rpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKF8ubWF0cml4V29ybGQpLHMuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKF8udGFyZ2V0Lm1hdHJpeFdvcmxkKSxTLmRpcmVjdGlvbi5zdWIocyksUy5kaXJlY3Rpb24udHJhbnNmb3JtRGlyZWN0aW9uKG0pLGYrK31lbHNlIGlmKF8uaXNTcG90TGlnaHQpe2xldCBTPXIuc3BvdFtnXTtTLnBvc2l0aW9uLnNldEZyb21NYXRyaXhQb3NpdGlvbihfLm1hdHJpeFdvcmxkKSxTLnBvc2l0aW9uLmFwcGx5TWF0cml4NChtKSxTLmRpcmVjdGlvbi5zZXRGcm9tTWF0cml4UG9zaXRpb24oXy5tYXRyaXhXb3JsZCkscy5zZXRGcm9tTWF0cml4UG9zaXRpb24oXy50YXJnZXQubWF0cml4V29ybGQpLFMuZGlyZWN0aW9uLnN1YihzKSxTLmRpcmVjdGlvbi50cmFuc2Zvcm1EaXJlY3Rpb24obSksZysrfWVsc2UgaWYoXy5pc1JlY3RBcmVhTGlnaHQpe2xldCBTPXIucmVjdEFyZWFbeF07Uy5wb3NpdGlvbi5zZXRGcm9tTWF0cml4UG9zaXRpb24oXy5tYXRyaXhXb3JsZCksUy5wb3NpdGlvbi5hcHBseU1hdHJpeDQobSksYS5pZGVudGl0eSgpLG8uY29weShfLm1hdHJpeFdvcmxkKSxvLnByZW11bHRpcGx5KG0pLGEuZXh0cmFjdFJvdGF0aW9uKG8pLFMuaGFsZldpZHRoLnNldChfLndpZHRoKi41LDAsMCksUy5oYWxmSGVpZ2h0LnNldCgwLF8uaGVpZ2h0Ki41LDApLFMuaGFsZldpZHRoLmFwcGx5TWF0cml4NChhKSxTLmhhbGZIZWlnaHQuYXBwbHlNYXRyaXg0KGEpLHgrK31lbHNlIGlmKF8uaXNQb2ludExpZ2h0KXtsZXQgUz1yLnBvaW50W2RdO1MucG9zaXRpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKF8ubWF0cml4V29ybGQpLFMucG9zaXRpb24uYXBwbHlNYXRyaXg0KG0pLGQrK31lbHNlIGlmKF8uaXNIZW1pc3BoZXJlTGlnaHQpe2xldCBTPXIuaGVtaVt2XTtTLmRpcmVjdGlvbi5zZXRGcm9tTWF0cml4UG9zaXRpb24oXy5tYXRyaXhXb3JsZCksUy5kaXJlY3Rpb24udHJhbnNmb3JtRGlyZWN0aW9uKG0pLFMuZGlyZWN0aW9uLm5vcm1hbGl6ZSgpLHYrK319fXJldHVybntzZXR1cDpsLHNldHVwVmlldzpjLHN0YXRlOnJ9fWZ1bmN0aW9uIHpnKG4sdCl7bGV0IGU9bmV3IFNFKG4sdCksaT1bXSxyPVtdO2Z1bmN0aW9uIHMoKXtpLmxlbmd0aD0wLHIubGVuZ3RoPTB9ZnVuY3Rpb24gbyhoKXtpLnB1c2goaCl9ZnVuY3Rpb24gYShoKXtyLnB1c2goaCl9ZnVuY3Rpb24gbChoKXtlLnNldHVwKGksaCl9ZnVuY3Rpb24gYyhoKXtlLnNldHVwVmlldyhpLGgpfXJldHVybntpbml0OnMsc3RhdGU6e2xpZ2h0c0FycmF5Omksc2hhZG93c0FycmF5OnIsbGlnaHRzOmV9LHNldHVwTGlnaHRzOmwsc2V0dXBMaWdodHNWaWV3OmMscHVzaExpZ2h0Om8scHVzaFNoYWRvdzphfX1mdW5jdGlvbiBFRShuLHQpe2xldCBlPW5ldyBXZWFrTWFwO2Z1bmN0aW9uIGkocyxvPTApe2xldCBhO3JldHVybiBlLmhhcyhzKT09PSExPyhhPW5ldyB6ZyhuLHQpLGUuc2V0KHMsW2FdKSk6bz49ZS5nZXQocykubGVuZ3RoPyhhPW5ldyB6ZyhuLHQpLGUuZ2V0KHMpLnB1c2goYSkpOmE9ZS5nZXQocylbb10sYX1mdW5jdGlvbiByKCl7ZT1uZXcgV2Vha01hcH1yZXR1cm57Z2V0OmksZGlzcG9zZTpyfX12YXIgY2w9Y2xhc3MgZXh0ZW5kcyB4ZXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaERlcHRoTWF0ZXJpYWwiLHRoaXMuZGVwdGhQYWNraW5nPUt3LHRoaXMubWFwPW51bGwsdGhpcy5hbHBoYU1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50TWFwPW51bGwsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT0xLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz0wLHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy5mb2c9ITEsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmRlcHRoUGFja2luZz10LmRlcHRoUGFja2luZyx0aGlzLm1hcD10Lm1hcCx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5kaXNwbGFjZW1lbnRNYXA9dC5kaXNwbGFjZW1lbnRNYXAsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT10LmRpc3BsYWNlbWVudFNjYWxlLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz10LmRpc3BsYWNlbWVudEJpYXMsdGhpcy53aXJlZnJhbWU9dC53aXJlZnJhbWUsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9dC53aXJlZnJhbWVMaW5ld2lkdGgsdGhpc319O2NsLnByb3RvdHlwZS5pc01lc2hEZXB0aE1hdGVyaWFsPSEwO3ZhciB1bD1jbGFzcyBleHRlbmRzIHhle2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJNZXNoRGlzdGFuY2VNYXRlcmlhbCIsdGhpcy5yZWZlcmVuY2VQb3NpdGlvbj1uZXcgVCx0aGlzLm5lYXJEaXN0YW5jZT0xLHRoaXMuZmFyRGlzdGFuY2U9MWUzLHRoaXMubWFwPW51bGwsdGhpcy5hbHBoYU1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50TWFwPW51bGwsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT0xLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz0wLHRoaXMuZm9nPSExLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5yZWZlcmVuY2VQb3NpdGlvbi5jb3B5KHQucmVmZXJlbmNlUG9zaXRpb24pLHRoaXMubmVhckRpc3RhbmNlPXQubmVhckRpc3RhbmNlLHRoaXMuZmFyRGlzdGFuY2U9dC5mYXJEaXN0YW5jZSx0aGlzLm1hcD10Lm1hcCx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5kaXNwbGFjZW1lbnRNYXA9dC5kaXNwbGFjZW1lbnRNYXAsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT10LmRpc3BsYWNlbWVudFNjYWxlLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz10LmRpc3BsYWNlbWVudEJpYXMsdGhpc319O3VsLnByb3RvdHlwZS5pc01lc2hEaXN0YW5jZU1hdGVyaWFsPSEwO3ZhciBURT1gdm9pZCBtYWluKCkgewoJZ2xfUG9zaXRpb24gPSB2ZWM0KCBwb3NpdGlvbiwgMS4wICk7Cn1gLEFFPWB1bmlmb3JtIHNhbXBsZXIyRCBzaGFkb3dfcGFzczsKdW5pZm9ybSB2ZWMyIHJlc29sdXRpb247CnVuaWZvcm0gZmxvYXQgcmFkaXVzOwojaW5jbHVkZSA8cGFja2luZz4Kdm9pZCBtYWluKCkgewoJY29uc3QgZmxvYXQgc2FtcGxlcyA9IGZsb2F0KCBWU01fU0FNUExFUyApOwoJZmxvYXQgbWVhbiA9IDAuMDsKCWZsb2F0IHNxdWFyZWRfbWVhbiA9IDAuMDsKCWZsb2F0IHV2U3RyaWRlID0gc2FtcGxlcyA8PSAxLjAgPyAwLjAgOiAyLjAgLyAoIHNhbXBsZXMgLSAxLjAgKTsKCWZsb2F0IHV2U3RhcnQgPSBzYW1wbGVzIDw9IDEuMCA/IDAuMCA6IC0gMS4wOwoJZm9yICggZmxvYXQgaSA9IDAuMDsgaSA8IHNhbXBsZXM7IGkgKysgKSB7CgkJZmxvYXQgdXZPZmZzZXQgPSB1dlN0YXJ0ICsgaSAqIHV2U3RyaWRlOwoJCSNpZmRlZiBIT1JJWk9OVEFMX1BBU1MKCQkJdmVjMiBkaXN0cmlidXRpb24gPSB1bnBhY2tSR0JBVG8ySGFsZiggdGV4dHVyZTJEKCBzaGFkb3dfcGFzcywgKCBnbF9GcmFnQ29vcmQueHkgKyB2ZWMyKCB1dk9mZnNldCwgMC4wICkgKiByYWRpdXMgKSAvIHJlc29sdXRpb24gKSApOwoJCQltZWFuICs9IGRpc3RyaWJ1dGlvbi54OwoJCQlzcXVhcmVkX21lYW4gKz0gZGlzdHJpYnV0aW9uLnkgKiBkaXN0cmlidXRpb24ueSArIGRpc3RyaWJ1dGlvbi54ICogZGlzdHJpYnV0aW9uLng7CgkJI2Vsc2UKCQkJZmxvYXQgZGVwdGggPSB1bnBhY2tSR0JBVG9EZXB0aCggdGV4dHVyZTJEKCBzaGFkb3dfcGFzcywgKCBnbF9GcmFnQ29vcmQueHkgKyB2ZWMyKCAwLjAsIHV2T2Zmc2V0ICkgKiByYWRpdXMgKSAvIHJlc29sdXRpb24gKSApOwoJCQltZWFuICs9IGRlcHRoOwoJCQlzcXVhcmVkX21lYW4gKz0gZGVwdGggKiBkZXB0aDsKCQkjZW5kaWYKCX0KCW1lYW4gPSBtZWFuIC8gc2FtcGxlczsKCXNxdWFyZWRfbWVhbiA9IHNxdWFyZWRfbWVhbiAvIHNhbXBsZXM7CglmbG9hdCBzdGRfZGV2ID0gc3FydCggc3F1YXJlZF9tZWFuIC0gbWVhbiAqIG1lYW4gKTsKCWdsX0ZyYWdDb2xvciA9IHBhY2sySGFsZlRvUkdCQSggdmVjMiggbWVhbiwgc3RkX2RldiApICk7Cn1gO2Z1bmN0aW9uIFQwKG4sdCxlKXtsZXQgaT1uZXcgcXIscj1uZXcgSyxzPW5ldyBLLG89bmV3IFd0LGE9bmV3IGNsKHtkZXB0aFBhY2tpbmc6UXd9KSxsPW5ldyB1bCxjPXt9LHU9ZS5tYXhUZXh0dXJlU2l6ZSxoPXswOmhlLDE6ZW8sMjpIcn0sZj1uZXcgRm4oe2RlZmluZXM6e1ZTTV9TQU1QTEVTOjh9LHVuaWZvcm1zOntzaGFkb3dfcGFzczp7dmFsdWU6bnVsbH0scmVzb2x1dGlvbjp7dmFsdWU6bmV3IEt9LHJhZGl1czp7dmFsdWU6NH19LHZlcnRleFNoYWRlcjpURSxmcmFnbWVudFNoYWRlcjpBRX0pLGQ9Zi5jbG9uZSgpO2QuZGVmaW5lcy5IT1JJWk9OVEFMX1BBU1M9MTtsZXQgZz1uZXcgSHQ7Zy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgUXQobmV3IEZsb2F0MzJBcnJheShbLTEsLTEsLjUsMywtMSwuNSwtMSwzLC41XSksMykpO2xldCB4PW5ldyBvZShnLGYpLHY9dGhpczt0aGlzLmVuYWJsZWQ9ITEsdGhpcy5hdXRvVXBkYXRlPSEwLHRoaXMubmVlZHNVcGRhdGU9ITEsdGhpcy50eXBlPWwwLHRoaXMucmVuZGVyPWZ1bmN0aW9uKF8sUyxMKXtpZih2LmVuYWJsZWQ9PT0hMXx8di5hdXRvVXBkYXRlPT09ITEmJnYubmVlZHNVcGRhdGU9PT0hMXx8Xy5sZW5ndGg9PT0wKXJldHVybjtsZXQgQT1uLmdldFJlbmRlclRhcmdldCgpLEg9bi5nZXRBY3RpdmVDdWJlRmFjZSgpLHR0PW4uZ2V0QWN0aXZlTWlwbWFwTGV2ZWwoKSxYPW4uc3RhdGU7WC5zZXRCbGVuZGluZyhqbiksWC5idWZmZXJzLmNvbG9yLnNldENsZWFyKDEsMSwxLDEpLFguYnVmZmVycy5kZXB0aC5zZXRUZXN0KCEwKSxYLnNldFNjaXNzb3JUZXN0KCExKTtmb3IobGV0IHk9MCxSPV8ubGVuZ3RoO3k8Ujt5Kyspe2xldCBEPV9beV0sRj1ELnNoYWRvdztpZihGPT09dm9pZCAwKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMU2hhZG93TWFwOiIsRCwiaGFzIG5vIHNoYWRvdy4iKTtjb250aW51ZX1pZihGLmF1dG9VcGRhdGU9PT0hMSYmRi5uZWVkc1VwZGF0ZT09PSExKWNvbnRpbnVlO3IuY29weShGLm1hcFNpemUpO2xldCB6PUYuZ2V0RnJhbWVFeHRlbnRzKCk7aWYoci5tdWx0aXBseSh6KSxzLmNvcHkoRi5tYXBTaXplKSwoci54PnV8fHIueT51KSYmKHIueD51JiYocy54PU1hdGguZmxvb3IodS96LngpLHIueD1zLngqei54LEYubWFwU2l6ZS54PXMueCksci55PnUmJihzLnk9TWF0aC5mbG9vcih1L3oueSksci55PXMueSp6LnksRi5tYXBTaXplLnk9cy55KSksRi5tYXA9PT1udWxsJiYhRi5pc1BvaW50TGlnaHRTaGFkb3cmJnRoaXMudHlwZT09PUpzKXtsZXQgVj17bWluRmlsdGVyOmJlLG1hZ0ZpbHRlcjpiZSxmb3JtYXQ6UmV9O0YubWFwPW5ldyBOZShyLngsci55LFYpLEYubWFwLnRleHR1cmUubmFtZT1ELm5hbWUrIi5zaGFkb3dNYXAiLEYubWFwUGFzcz1uZXcgTmUoci54LHIueSxWKSxGLmNhbWVyYS51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl9aWYoRi5tYXA9PT1udWxsKXtsZXQgVj17bWluRmlsdGVyOmZlLG1hZ0ZpbHRlcjpmZSxmb3JtYXQ6UmV9O0YubWFwPW5ldyBOZShyLngsci55LFYpLEYubWFwLnRleHR1cmUubmFtZT1ELm5hbWUrIi5zaGFkb3dNYXAiLEYuY2FtZXJhLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX1uLnNldFJlbmRlclRhcmdldChGLm1hcCksbi5jbGVhcigpO2xldCBOPUYuZ2V0Vmlld3BvcnRDb3VudCgpO2ZvcihsZXQgVj0wO1Y8TjtWKyspe2xldCBRPUYuZ2V0Vmlld3BvcnQoVik7by5zZXQocy54KlEueCxzLnkqUS55LHMueCpRLnoscy55KlEudyksWC52aWV3cG9ydChvKSxGLnVwZGF0ZU1hdHJpY2VzKEQsViksaT1GLmdldEZydXN0dW0oKSxiKFMsTCxGLmNhbWVyYSxELHRoaXMudHlwZSl9IUYuaXNQb2ludExpZ2h0U2hhZG93JiZ0aGlzLnR5cGU9PT1KcyYmbShGLEwpLEYubmVlZHNVcGRhdGU9ITF9di5uZWVkc1VwZGF0ZT0hMSxuLnNldFJlbmRlclRhcmdldChBLEgsdHQpfTtmdW5jdGlvbiBtKF8sUyl7bGV0IEw9dC51cGRhdGUoeCk7Zi5kZWZpbmVzLlZTTV9TQU1QTEVTIT09Xy5ibHVyU2FtcGxlcyYmKGYuZGVmaW5lcy5WU01fU0FNUExFUz1fLmJsdXJTYW1wbGVzLGQuZGVmaW5lcy5WU01fU0FNUExFUz1fLmJsdXJTYW1wbGVzLGYubmVlZHNVcGRhdGU9ITAsZC5uZWVkc1VwZGF0ZT0hMCksZi51bmlmb3Jtcy5zaGFkb3dfcGFzcy52YWx1ZT1fLm1hcC50ZXh0dXJlLGYudW5pZm9ybXMucmVzb2x1dGlvbi52YWx1ZT1fLm1hcFNpemUsZi51bmlmb3Jtcy5yYWRpdXMudmFsdWU9Xy5yYWRpdXMsbi5zZXRSZW5kZXJUYXJnZXQoXy5tYXBQYXNzKSxuLmNsZWFyKCksbi5yZW5kZXJCdWZmZXJEaXJlY3QoUyxudWxsLEwsZix4LG51bGwpLGQudW5pZm9ybXMuc2hhZG93X3Bhc3MudmFsdWU9Xy5tYXBQYXNzLnRleHR1cmUsZC51bmlmb3Jtcy5yZXNvbHV0aW9uLnZhbHVlPV8ubWFwU2l6ZSxkLnVuaWZvcm1zLnJhZGl1cy52YWx1ZT1fLnJhZGl1cyxuLnNldFJlbmRlclRhcmdldChfLm1hcCksbi5jbGVhcigpLG4ucmVuZGVyQnVmZmVyRGlyZWN0KFMsbnVsbCxMLGQseCxudWxsKX1mdW5jdGlvbiBwKF8sUyxMLEEsSCx0dCxYKXtsZXQgeT1udWxsLFI9QS5pc1BvaW50TGlnaHQ9PT0hMD9fLmN1c3RvbURpc3RhbmNlTWF0ZXJpYWw6Xy5jdXN0b21EZXB0aE1hdGVyaWFsO2lmKFIhPT12b2lkIDA/eT1SOnk9QS5pc1BvaW50TGlnaHQ9PT0hMD9sOmEsbi5sb2NhbENsaXBwaW5nRW5hYmxlZCYmTC5jbGlwU2hhZG93cz09PSEwJiZMLmNsaXBwaW5nUGxhbmVzLmxlbmd0aCE9PTB8fEwuZGlzcGxhY2VtZW50TWFwJiZMLmRpc3BsYWNlbWVudFNjYWxlIT09MHx8TC5hbHBoYU1hcCYmTC5hbHBoYVRlc3Q+MCl7bGV0IEQ9eS51dWlkLEY9TC51dWlkLHo9Y1tEXTt6PT09dm9pZCAwJiYoej17fSxjW0RdPXopO2xldCBOPXpbRl07Tj09PXZvaWQgMCYmKE49eS5jbG9uZSgpLHpbRl09TikseT1OfXJldHVybiB5LnZpc2libGU9TC52aXNpYmxlLHkud2lyZWZyYW1lPUwud2lyZWZyYW1lLFg9PT1Kcz95LnNpZGU9TC5zaGFkb3dTaWRlIT09bnVsbD9MLnNoYWRvd1NpZGU6TC5zaWRlOnkuc2lkZT1MLnNoYWRvd1NpZGUhPT1udWxsP0wuc2hhZG93U2lkZTpoW0wuc2lkZV0seS5hbHBoYU1hcD1MLmFscGhhTWFwLHkuYWxwaGFUZXN0PUwuYWxwaGFUZXN0LHkuY2xpcFNoYWRvd3M9TC5jbGlwU2hhZG93cyx5LmNsaXBwaW5nUGxhbmVzPUwuY2xpcHBpbmdQbGFuZXMseS5jbGlwSW50ZXJzZWN0aW9uPUwuY2xpcEludGVyc2VjdGlvbix5LmRpc3BsYWNlbWVudE1hcD1MLmRpc3BsYWNlbWVudE1hcCx5LmRpc3BsYWNlbWVudFNjYWxlPUwuZGlzcGxhY2VtZW50U2NhbGUseS5kaXNwbGFjZW1lbnRCaWFzPUwuZGlzcGxhY2VtZW50Qmlhcyx5LndpcmVmcmFtZUxpbmV3aWR0aD1MLndpcmVmcmFtZUxpbmV3aWR0aCx5LmxpbmV3aWR0aD1MLmxpbmV3aWR0aCxBLmlzUG9pbnRMaWdodD09PSEwJiZ5LmlzTWVzaERpc3RhbmNlTWF0ZXJpYWw9PT0hMCYmKHkucmVmZXJlbmNlUG9zaXRpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKEEubWF0cml4V29ybGQpLHkubmVhckRpc3RhbmNlPUgseS5mYXJEaXN0YW5jZT10dCkseX1mdW5jdGlvbiBiKF8sUyxMLEEsSCl7aWYoXy52aXNpYmxlPT09ITEpcmV0dXJuO2lmKF8ubGF5ZXJzLnRlc3QoUy5sYXllcnMpJiYoXy5pc01lc2h8fF8uaXNMaW5lfHxfLmlzUG9pbnRzKSYmKF8uY2FzdFNoYWRvd3x8Xy5yZWNlaXZlU2hhZG93JiZIPT09SnMpJiYoIV8uZnJ1c3R1bUN1bGxlZHx8aS5pbnRlcnNlY3RzT2JqZWN0KF8pKSl7Xy5tb2RlbFZpZXdNYXRyaXgubXVsdGlwbHlNYXRyaWNlcyhMLm1hdHJpeFdvcmxkSW52ZXJzZSxfLm1hdHJpeFdvcmxkKTtsZXQgeT10LnVwZGF0ZShfKSxSPV8ubWF0ZXJpYWw7aWYoQXJyYXkuaXNBcnJheShSKSl7bGV0IEQ9eS5ncm91cHM7Zm9yKGxldCBGPTAsej1ELmxlbmd0aDtGPHo7RisrKXtsZXQgTj1EW0ZdLFY9UltOLm1hdGVyaWFsSW5kZXhdO2lmKFYmJlYudmlzaWJsZSl7bGV0IFE9cChfLHksVixBLEwubmVhcixMLmZhcixIKTtuLnJlbmRlckJ1ZmZlckRpcmVjdChMLG51bGwseSxRLF8sTil9fX1lbHNlIGlmKFIudmlzaWJsZSl7bGV0IEQ9cChfLHksUixBLEwubmVhcixMLmZhcixIKTtuLnJlbmRlckJ1ZmZlckRpcmVjdChMLG51bGwseSxELF8sbnVsbCl9fWxldCBYPV8uY2hpbGRyZW47Zm9yKGxldCB5PTAsUj1YLmxlbmd0aDt5PFI7eSsrKWIoWFt5XSxTLEwsQSxIKX19ZnVuY3Rpb24gQ0Uobix0LGUpe2xldCBpPWUuaXNXZWJHTDI7ZnVuY3Rpb24gcigpe2xldCBQPSExLHB0PW5ldyBXdCxodD1udWxsLEV0PW5ldyBXdCgwLDAsMCwwKTtyZXR1cm57c2V0TWFzazpmdW5jdGlvbihZKXtodCE9PVkmJiFQJiYobi5jb2xvck1hc2soWSxZLFksWSksaHQ9WSl9LHNldExvY2tlZDpmdW5jdGlvbihZKXtQPVl9LHNldENsZWFyOmZ1bmN0aW9uKFksTXQsRHQsanQsemUpe3plPT09ITAmJihZKj1qdCxNdCo9anQsRHQqPWp0KSxwdC5zZXQoWSxNdCxEdCxqdCksRXQuZXF1YWxzKHB0KT09PSExJiYobi5jbGVhckNvbG9yKFksTXQsRHQsanQpLEV0LmNvcHkocHQpKX0scmVzZXQ6ZnVuY3Rpb24oKXtQPSExLGh0PW51bGwsRXQuc2V0KC0xLDAsMCwwKX19fWZ1bmN0aW9uIHMoKXtsZXQgUD0hMSxwdD1udWxsLGh0PW51bGwsRXQ9bnVsbDtyZXR1cm57c2V0VGVzdDpmdW5jdGlvbihZKXtZP2soMjkyOSk6RnQoMjkyOSl9LHNldE1hc2s6ZnVuY3Rpb24oWSl7cHQhPT1ZJiYhUCYmKG4uZGVwdGhNYXNrKFkpLHB0PVkpfSxzZXRGdW5jOmZ1bmN0aW9uKFkpe2lmKGh0IT09WSl7aWYoWSlzd2l0Y2goWSl7Y2FzZSB5dzpuLmRlcHRoRnVuYyg1MTIpO2JyZWFrO2Nhc2Ugdnc6bi5kZXB0aEZ1bmMoNTE5KTticmVhaztjYXNlIF93Om4uZGVwdGhGdW5jKDUxMyk7YnJlYWs7Y2FzZSB6dTpuLmRlcHRoRnVuYyg1MTUpO2JyZWFrO2Nhc2Ugd3c6bi5kZXB0aEZ1bmMoNTE0KTticmVhaztjYXNlIE13Om4uZGVwdGhGdW5jKDUxOCk7YnJlYWs7Y2FzZSBidzpuLmRlcHRoRnVuYyg1MTYpO2JyZWFrO2Nhc2UgU3c6bi5kZXB0aEZ1bmMoNTE3KTticmVhaztkZWZhdWx0Om4uZGVwdGhGdW5jKDUxNSl9ZWxzZSBuLmRlcHRoRnVuYyg1MTUpO2h0PVl9fSxzZXRMb2NrZWQ6ZnVuY3Rpb24oWSl7UD1ZfSxzZXRDbGVhcjpmdW5jdGlvbihZKXtFdCE9PVkmJihuLmNsZWFyRGVwdGgoWSksRXQ9WSl9LHJlc2V0OmZ1bmN0aW9uKCl7UD0hMSxwdD1udWxsLGh0PW51bGwsRXQ9bnVsbH19fWZ1bmN0aW9uIG8oKXtsZXQgUD0hMSxwdD1udWxsLGh0PW51bGwsRXQ9bnVsbCxZPW51bGwsTXQ9bnVsbCxEdD1udWxsLGp0PW51bGwsemU9bnVsbDtyZXR1cm57c2V0VGVzdDpmdW5jdGlvbihuZSl7UHx8KG5lP2soMjk2MCk6RnQoMjk2MCkpfSxzZXRNYXNrOmZ1bmN0aW9uKG5lKXtwdCE9PW5lJiYhUCYmKG4uc3RlbmNpbE1hc2sobmUpLHB0PW5lKX0sc2V0RnVuYzpmdW5jdGlvbihuZSxlbix5bil7KGh0IT09bmV8fEV0IT09ZW58fFkhPT15bikmJihuLnN0ZW5jaWxGdW5jKG5lLGVuLHluKSxodD1uZSxFdD1lbixZPXluKX0sc2V0T3A6ZnVuY3Rpb24obmUsZW4seW4peyhNdCE9PW5lfHxEdCE9PWVufHxqdCE9PXluKSYmKG4uc3RlbmNpbE9wKG5lLGVuLHluKSxNdD1uZSxEdD1lbixqdD15bil9LHNldExvY2tlZDpmdW5jdGlvbihuZSl7UD1uZX0sc2V0Q2xlYXI6ZnVuY3Rpb24obmUpe3plIT09bmUmJihuLmNsZWFyU3RlbmNpbChuZSksemU9bmUpfSxyZXNldDpmdW5jdGlvbigpe1A9ITEscHQ9bnVsbCxodD1udWxsLEV0PW51bGwsWT1udWxsLE10PW51bGwsRHQ9bnVsbCxqdD1udWxsLHplPW51bGx9fX1sZXQgYT1uZXcgcixsPW5ldyBzLGM9bmV3IG8sdT17fSxoPXt9LGY9bmV3IFdlYWtNYXAsZD1bXSxnPW51bGwseD0hMSx2PW51bGwsbT1udWxsLHA9bnVsbCxiPW51bGwsXz1udWxsLFM9bnVsbCxMPW51bGwsQT0hMSxIPW51bGwsdHQ9bnVsbCxYPW51bGwseT1udWxsLFI9bnVsbCxEPW4uZ2V0UGFyYW1ldGVyKDM1NjYxKSxGPSExLHo9MCxOPW4uZ2V0UGFyYW1ldGVyKDc5MzgpO04uaW5kZXhPZigiV2ViR0wiKSE9PS0xPyh6PXBhcnNlRmxvYXQoL15XZWJHTCAoXGQpLy5leGVjKE4pWzFdKSxGPXo+PTEpOk4uaW5kZXhPZigiT3BlbkdMIEVTIikhPT0tMSYmKHo9cGFyc2VGbG9hdCgvXk9wZW5HTCBFUyAoXGQpLy5leGVjKE4pWzFdKSxGPXo+PTIpO2xldCBWPW51bGwsUT17fSxhdD1uLmdldFBhcmFtZXRlcigzMDg4KSxHPW4uZ2V0UGFyYW1ldGVyKDI5NzgpLCQ9bmV3IFd0KCkuZnJvbUFycmF5KGF0KSxsdD1uZXcgV3QoKS5mcm9tQXJyYXkoRyk7ZnVuY3Rpb24gZHQoUCxwdCxodCl7bGV0IEV0PW5ldyBVaW50OEFycmF5KDQpLFk9bi5jcmVhdGVUZXh0dXJlKCk7bi5iaW5kVGV4dHVyZShQLFkpLG4udGV4UGFyYW1ldGVyaShQLDEwMjQxLDk3MjgpLG4udGV4UGFyYW1ldGVyaShQLDEwMjQwLDk3MjgpO2ZvcihsZXQgTXQ9MDtNdDxodDtNdCsrKW4udGV4SW1hZ2UyRChwdCtNdCwwLDY0MDgsMSwxLDAsNjQwOCw1MTIxLEV0KTtyZXR1cm4gWX1sZXQgeHQ9e307eHRbMzU1M109ZHQoMzU1MywzNTUzLDEpLHh0WzM0MDY3XT1kdCgzNDA2NywzNDA2OSw2KSxhLnNldENsZWFyKDAsMCwwLDEpLGwuc2V0Q2xlYXIoMSksYy5zZXRDbGVhcigwKSxrKDI5MjkpLGwuc2V0RnVuYyh6dSksSighMSksaXQoQW0pLGsoMjg4NCksQyhqbik7ZnVuY3Rpb24gayhQKXt1W1BdIT09ITAmJihuLmVuYWJsZShQKSx1W1BdPSEwKX1mdW5jdGlvbiBGdChQKXt1W1BdIT09ITEmJihuLmRpc2FibGUoUCksdVtQXT0hMSl9ZnVuY3Rpb24gbXQoUCxwdCl7cmV0dXJuIGhbUF0hPT1wdD8obi5iaW5kRnJhbWVidWZmZXIoUCxwdCksaFtQXT1wdCxpJiYoUD09PTM2MDA5JiYoaFszNjE2MF09cHQpLFA9PT0zNjE2MCYmKGhbMzYwMDldPXB0KSksITApOiExfWZ1bmN0aW9uIFN0KFAscHQpe2xldCBodD1kLEV0PSExO2lmKFApaWYoaHQ9Zi5nZXQocHQpLGh0PT09dm9pZCAwJiYoaHQ9W10sZi5zZXQocHQsaHQpKSxQLmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHMpe2xldCBZPVAudGV4dHVyZTtpZihodC5sZW5ndGghPT1ZLmxlbmd0aHx8aHRbMF0hPT0zNjA2NCl7Zm9yKGxldCBNdD0wLER0PVkubGVuZ3RoO010PER0O010KyspaHRbTXRdPTM2MDY0K010O2h0Lmxlbmd0aD1ZLmxlbmd0aCxFdD0hMH19ZWxzZSBodFswXSE9PTM2MDY0JiYoaHRbMF09MzYwNjQsRXQ9ITApO2Vsc2UgaHRbMF0hPT0xMDI5JiYoaHRbMF09MTAyOSxFdD0hMCk7RXQmJihlLmlzV2ViR0wyP24uZHJhd0J1ZmZlcnMoaHQpOnQuZ2V0KCJXRUJHTF9kcmF3X2J1ZmZlcnMiKS5kcmF3QnVmZmVyc1dFQkdMKGh0KSl9ZnVuY3Rpb24gQihQKXtyZXR1cm4gZyE9PVA/KG4udXNlUHJvZ3JhbShQKSxnPVAsITApOiExfWxldCBzdD17W0lyXTozMjc3NCxbYXddOjMyNzc4LFtsd106MzI3Nzl9O2lmKGkpc3RbUG1dPTMyNzc1LHN0W0RtXT0zMjc3NjtlbHNle2xldCBQPXQuZ2V0KCJFWFRfYmxlbmRfbWlubWF4Iik7UCE9PW51bGwmJihzdFtQbV09UC5NSU5fRVhULHN0W0RtXT1QLk1BWF9FWFQpfWxldCBudD17W2N3XTowLFt1d106MSxbaHddOjc2OCxbdTBdOjc3MCxbeHddOjc3NixbbXddOjc3NCxbZHddOjc3MixbZnddOjc2OSxbaDBdOjc3MSxbZ3ddOjc3NSxbcHddOjc3M307ZnVuY3Rpb24gQyhQLHB0LGh0LEV0LFksTXQsRHQsanQpe2lmKFA9PT1qbil7eD09PSEwJiYoRnQoMzA0MikseD0hMSk7cmV0dXJufWlmKHg9PT0hMSYmKGsoMzA0MikseD0hMCksUCE9PW93KXtpZihQIT09dnx8anQhPT1BKXtpZigobSE9PUlyfHxfIT09SXIpJiYobi5ibGVuZEVxdWF0aW9uKDMyNzc0KSxtPUlyLF89SXIpLGp0KXN3aXRjaChQKXtjYXNlIEtzOm4uYmxlbmRGdW5jU2VwYXJhdGUoMSw3NzEsMSw3NzEpO2JyZWFrO2Nhc2UgQ206bi5ibGVuZEZ1bmMoMSwxKTticmVhaztjYXNlIFJtOm4uYmxlbmRGdW5jU2VwYXJhdGUoMCw3NjksMCwxKTticmVhaztjYXNlIExtOm4uYmxlbmRGdW5jU2VwYXJhdGUoMCw3NjgsMCw3NzApO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZTogSW52YWxpZCBibGVuZGluZzogIixQKTticmVha31lbHNlIHN3aXRjaChQKXtjYXNlIEtzOm4uYmxlbmRGdW5jU2VwYXJhdGUoNzcwLDc3MSwxLDc3MSk7YnJlYWs7Y2FzZSBDbTpuLmJsZW5kRnVuYyg3NzAsMSk7YnJlYWs7Y2FzZSBSbTpuLmJsZW5kRnVuY1NlcGFyYXRlKDAsNzY5LDAsMSk7YnJlYWs7Y2FzZSBMbTpuLmJsZW5kRnVuYygwLDc2OCk7YnJlYWs7ZGVmYXVsdDpjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFN0YXRlOiBJbnZhbGlkIGJsZW5kaW5nOiAiLFApO2JyZWFrfXA9bnVsbCxiPW51bGwsUz1udWxsLEw9bnVsbCx2PVAsQT1qdH1yZXR1cm59WT1ZfHxwdCxNdD1NdHx8aHQsRHQ9RHR8fEV0LChwdCE9PW18fFkhPT1fKSYmKG4uYmxlbmRFcXVhdGlvblNlcGFyYXRlKHN0W3B0XSxzdFtZXSksbT1wdCxfPVkpLChodCE9PXB8fEV0IT09Ynx8TXQhPT1TfHxEdCE9PUwpJiYobi5ibGVuZEZ1bmNTZXBhcmF0ZShudFtodF0sbnRbRXRdLG50W010XSxudFtEdF0pLHA9aHQsYj1FdCxTPU10LEw9RHQpLHY9UCxBPW51bGx9ZnVuY3Rpb24gaihQLHB0KXtQLnNpZGU9PT1Icj9GdCgyODg0KTprKDI4ODQpO2xldCBodD1QLnNpZGU9PT1oZTtwdCYmKGh0PSFodCksSihodCksUC5ibGVuZGluZz09PUtzJiZQLnRyYW5zcGFyZW50PT09ITE/Qyhqbik6QyhQLmJsZW5kaW5nLFAuYmxlbmRFcXVhdGlvbixQLmJsZW5kU3JjLFAuYmxlbmREc3QsUC5ibGVuZEVxdWF0aW9uQWxwaGEsUC5ibGVuZFNyY0FscGhhLFAuYmxlbmREc3RBbHBoYSxQLnByZW11bHRpcGxpZWRBbHBoYSksbC5zZXRGdW5jKFAuZGVwdGhGdW5jKSxsLnNldFRlc3QoUC5kZXB0aFRlc3QpLGwuc2V0TWFzayhQLmRlcHRoV3JpdGUpLGEuc2V0TWFzayhQLmNvbG9yV3JpdGUpO2xldCBFdD1QLnN0ZW5jaWxXcml0ZTtjLnNldFRlc3QoRXQpLEV0JiYoYy5zZXRNYXNrKFAuc3RlbmNpbFdyaXRlTWFzayksYy5zZXRGdW5jKFAuc3RlbmNpbEZ1bmMsUC5zdGVuY2lsUmVmLFAuc3RlbmNpbEZ1bmNNYXNrKSxjLnNldE9wKFAuc3RlbmNpbEZhaWwsUC5zdGVuY2lsWkZhaWwsUC5zdGVuY2lsWlBhc3MpKSx2dChQLnBvbHlnb25PZmZzZXQsUC5wb2x5Z29uT2Zmc2V0RmFjdG9yLFAucG9seWdvbk9mZnNldFVuaXRzKSxQLmFscGhhVG9Db3ZlcmFnZT09PSEwP2soMzI5MjYpOkZ0KDMyOTI2KX1mdW5jdGlvbiBKKFApe0ghPT1QJiYoUD9uLmZyb250RmFjZSgyMzA0KTpuLmZyb250RmFjZSgyMzA1KSxIPVApfWZ1bmN0aW9uIGl0KFApe1AhPT1pdz8oaygyODg0KSxQIT09dHQmJihQPT09QW0/bi5jdWxsRmFjZSgxMDI5KTpQPT09cnc/bi5jdWxsRmFjZSgxMDI4KTpuLmN1bGxGYWNlKDEwMzIpKSk6RnQoMjg4NCksdHQ9UH1mdW5jdGlvbiBldChQKXtQIT09WCYmKEYmJm4ubGluZVdpZHRoKFApLFg9UCl9ZnVuY3Rpb24gdnQoUCxwdCxodCl7UD8oaygzMjgyMyksKHkhPT1wdHx8UiE9PWh0KSYmKG4ucG9seWdvbk9mZnNldChwdCxodCkseT1wdCxSPWh0KSk6RnQoMzI4MjMpfWZ1bmN0aW9uIGJ0KFApe1A/aygzMDg5KTpGdCgzMDg5KX1mdW5jdGlvbiBJdChQKXtQPT09dm9pZCAwJiYoUD0zMzk4NCtELTEpLFYhPT1QJiYobi5hY3RpdmVUZXh0dXJlKFApLFY9UCl9ZnVuY3Rpb24gWnQoUCxwdCl7Vj09PW51bGwmJkl0KCk7bGV0IGh0PVFbVl07aHQ9PT12b2lkIDAmJihodD17dHlwZTp2b2lkIDAsdGV4dHVyZTp2b2lkIDB9LFFbVl09aHQpLChodC50eXBlIT09UHx8aHQudGV4dHVyZSE9PXB0KSYmKG4uYmluZFRleHR1cmUoUCxwdHx8eHRbUF0pLGh0LnR5cGU9UCxodC50ZXh0dXJlPXB0KX1mdW5jdGlvbiBxdCgpe2xldCBQPVFbVl07UCE9PXZvaWQgMCYmUC50eXBlIT09dm9pZCAwJiYobi5iaW5kVGV4dHVyZShQLnR5cGUsbnVsbCksUC50eXBlPXZvaWQgMCxQLnRleHR1cmU9dm9pZCAwKX1mdW5jdGlvbiBFKCl7dHJ5e24uY29tcHJlc3NlZFRleEltYWdlMkQuYXBwbHkobixhcmd1bWVudHMpfWNhdGNoKFApe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IixQKX19ZnVuY3Rpb24gdygpe3RyeXtuLnRleFN1YkltYWdlMkQuYXBwbHkobixhcmd1bWVudHMpfWNhdGNoKFApe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IixQKX19ZnVuY3Rpb24gcSgpe3RyeXtuLnRleFN1YkltYWdlM0QuYXBwbHkobixhcmd1bWVudHMpfWNhdGNoKFApe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IixQKX19ZnVuY3Rpb24gcnQoKXt0cnl7bi5jb21wcmVzc2VkVGV4U3ViSW1hZ2UyRC5hcHBseShuLGFyZ3VtZW50cyl9Y2F0Y2goUCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZToiLFApfX1mdW5jdGlvbiBndCgpe3RyeXtuLnRleFN0b3JhZ2UyRC5hcHBseShuLGFyZ3VtZW50cyl9Y2F0Y2goUCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZToiLFApfX1mdW5jdGlvbiBXKCl7dHJ5e24udGV4U3RvcmFnZTNELmFwcGx5KG4sYXJndW1lbnRzKX1jYXRjaChQKXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFN0YXRlOiIsUCl9fWZ1bmN0aW9uIF90KCl7dHJ5e24udGV4SW1hZ2UyRC5hcHBseShuLGFyZ3VtZW50cyl9Y2F0Y2goUCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZToiLFApfX1mdW5jdGlvbiB5dCgpe3RyeXtuLnRleEltYWdlM0QuYXBwbHkobixhcmd1bWVudHMpfWNhdGNoKFApe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IixQKX19ZnVuY3Rpb24gdXQoUCl7JC5lcXVhbHMoUCk9PT0hMSYmKG4uc2Npc3NvcihQLngsUC55LFAueixQLncpLCQuY29weShQKSl9ZnVuY3Rpb24gY3QoUCl7bHQuZXF1YWxzKFApPT09ITEmJihuLnZpZXdwb3J0KFAueCxQLnksUC56LFAudyksbHQuY29weShQKSl9ZnVuY3Rpb24gQXQoKXtuLmRpc2FibGUoMzA0Miksbi5kaXNhYmxlKDI4ODQpLG4uZGlzYWJsZSgyOTI5KSxuLmRpc2FibGUoMzI4MjMpLG4uZGlzYWJsZSgzMDg5KSxuLmRpc2FibGUoMjk2MCksbi5kaXNhYmxlKDMyOTI2KSxuLmJsZW5kRXF1YXRpb24oMzI3NzQpLG4uYmxlbmRGdW5jKDEsMCksbi5ibGVuZEZ1bmNTZXBhcmF0ZSgxLDAsMSwwKSxuLmNvbG9yTWFzayghMCwhMCwhMCwhMCksbi5jbGVhckNvbG9yKDAsMCwwLDApLG4uZGVwdGhNYXNrKCEwKSxuLmRlcHRoRnVuYyg1MTMpLG4uY2xlYXJEZXB0aCgxKSxuLnN0ZW5jaWxNYXNrKDQyOTQ5NjcyOTUpLG4uc3RlbmNpbEZ1bmMoNTE5LDAsNDI5NDk2NzI5NSksbi5zdGVuY2lsT3AoNzY4MCw3NjgwLDc2ODApLG4uY2xlYXJTdGVuY2lsKDApLG4uY3VsbEZhY2UoMTAyOSksbi5mcm9udEZhY2UoMjMwNSksbi5wb2x5Z29uT2Zmc2V0KDAsMCksbi5hY3RpdmVUZXh0dXJlKDMzOTg0KSxuLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxudWxsKSxpPT09ITAmJihuLmJpbmRGcmFtZWJ1ZmZlcigzNjAwOSxudWxsKSxuLmJpbmRGcmFtZWJ1ZmZlcigzNjAwOCxudWxsKSksbi51c2VQcm9ncmFtKG51bGwpLG4ubGluZVdpZHRoKDEpLG4uc2Npc3NvcigwLDAsbi5jYW52YXMud2lkdGgsbi5jYW52YXMuaGVpZ2h0KSxuLnZpZXdwb3J0KDAsMCxuLmNhbnZhcy53aWR0aCxuLmNhbnZhcy5oZWlnaHQpLHU9e30sVj1udWxsLFE9e30saD17fSxmPW5ldyBXZWFrTWFwLGQ9W10sZz1udWxsLHg9ITEsdj1udWxsLG09bnVsbCxwPW51bGwsYj1udWxsLF89bnVsbCxTPW51bGwsTD1udWxsLEE9ITEsSD1udWxsLHR0PW51bGwsWD1udWxsLHk9bnVsbCxSPW51bGwsJC5zZXQoMCwwLG4uY2FudmFzLndpZHRoLG4uY2FudmFzLmhlaWdodCksbHQuc2V0KDAsMCxuLmNhbnZhcy53aWR0aCxuLmNhbnZhcy5oZWlnaHQpLGEucmVzZXQoKSxsLnJlc2V0KCksYy5yZXNldCgpfXJldHVybntidWZmZXJzOntjb2xvcjphLGRlcHRoOmwsc3RlbmNpbDpjfSxlbmFibGU6ayxkaXNhYmxlOkZ0LGJpbmRGcmFtZWJ1ZmZlcjptdCxkcmF3QnVmZmVyczpTdCx1c2VQcm9ncmFtOkIsc2V0QmxlbmRpbmc6QyxzZXRNYXRlcmlhbDpqLHNldEZsaXBTaWRlZDpKLHNldEN1bGxGYWNlOml0LHNldExpbmVXaWR0aDpldCxzZXRQb2x5Z29uT2Zmc2V0OnZ0LHNldFNjaXNzb3JUZXN0OmJ0LGFjdGl2ZVRleHR1cmU6SXQsYmluZFRleHR1cmU6WnQsdW5iaW5kVGV4dHVyZTpxdCxjb21wcmVzc2VkVGV4SW1hZ2UyRDpFLHRleEltYWdlMkQ6X3QsdGV4SW1hZ2UzRDp5dCx0ZXhTdG9yYWdlMkQ6Z3QsdGV4U3RvcmFnZTNEOlcsdGV4U3ViSW1hZ2UyRDp3LHRleFN1YkltYWdlM0Q6cSxjb21wcmVzc2VkVGV4U3ViSW1hZ2UyRDpydCxzY2lzc29yOnV0LHZpZXdwb3J0OmN0LHJlc2V0OkF0fX1mdW5jdGlvbiBSRShuLHQsZSxpLHIscyxvKXtsZXQgYT1yLmlzV2ViR0wyLGw9ci5tYXhUZXh0dXJlcyxjPXIubWF4Q3ViZW1hcFNpemUsdT1yLm1heFRleHR1cmVTaXplLGg9ci5tYXhTYW1wbGVzLGQ9dC5oYXMoIldFQkdMX211bHRpc2FtcGxlZF9yZW5kZXJfdG9fdGV4dHVyZSIpP3QuZ2V0KCJXRUJHTF9tdWx0aXNhbXBsZWRfcmVuZGVyX3RvX3RleHR1cmUiKTp2b2lkIDAsZz1uZXcgV2Vha01hcCx4LHY9ITE7dHJ5e3Y9dHlwZW9mIE9mZnNjcmVlbkNhbnZhcyE9InVuZGVmaW5lZCImJm5ldyBPZmZzY3JlZW5DYW52YXMoMSwxKS5nZXRDb250ZXh0KCIyZCIpIT09bnVsbH1jYXRjaChFKXt9ZnVuY3Rpb24gbShFLHcpe3JldHVybiB2P25ldyBPZmZzY3JlZW5DYW52YXMoRSx3KTpybygiY2FudmFzIil9ZnVuY3Rpb24gcChFLHcscSxydCl7bGV0IGd0PTE7aWYoKEUud2lkdGg+cnR8fEUuaGVpZ2h0PnJ0KSYmKGd0PXJ0L01hdGgubWF4KEUud2lkdGgsRS5oZWlnaHQpKSxndDwxfHx3PT09ITApaWYodHlwZW9mIEhUTUxJbWFnZUVsZW1lbnQhPSJ1bmRlZmluZWQiJiZFIGluc3RhbmNlb2YgSFRNTEltYWdlRWxlbWVudHx8dHlwZW9mIEhUTUxDYW52YXNFbGVtZW50IT0idW5kZWZpbmVkIiYmRSBpbnN0YW5jZW9mIEhUTUxDYW52YXNFbGVtZW50fHx0eXBlb2YgSW1hZ2VCaXRtYXAhPSJ1bmRlZmluZWQiJiZFIGluc3RhbmNlb2YgSW1hZ2VCaXRtYXApe2xldCBXPXc/bk06TWF0aC5mbG9vcixfdD1XKGd0KkUud2lkdGgpLHl0PVcoZ3QqRS5oZWlnaHQpO3g9PT12b2lkIDAmJih4PW0oX3QseXQpKTtsZXQgdXQ9cT9tKF90LHl0KTp4O3JldHVybiB1dC53aWR0aD1fdCx1dC5oZWlnaHQ9eXQsdXQuZ2V0Q29udGV4dCgiMmQiKS5kcmF3SW1hZ2UoRSwwLDAsX3QseXQpLGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogVGV4dHVyZSBoYXMgYmVlbiByZXNpemVkIGZyb20gKCIrRS53aWR0aCsieCIrRS5oZWlnaHQrIikgdG8gKCIrX3QrIngiK3l0KyIpLiIpLHV0fWVsc2UgcmV0dXJuImRhdGEiaW4gRSYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBJbWFnZSBpbiBEYXRhVGV4dHVyZSBpcyB0b28gYmlnICgiK0Uud2lkdGgrIngiK0UuaGVpZ2h0KyIpLiIpLEU7cmV0dXJuIEV9ZnVuY3Rpb24gYihFKXtyZXR1cm4gaWcoRS53aWR0aCkmJmlnKEUuaGVpZ2h0KX1mdW5jdGlvbiBfKEUpe3JldHVybiBhPyExOkUud3JhcFMhPT1WZXx8RS53cmFwVCE9PVZlfHxFLm1pbkZpbHRlciE9PWZlJiZFLm1pbkZpbHRlciE9PWJlfWZ1bmN0aW9uIFMoRSx3KXtyZXR1cm4gRS5nZW5lcmF0ZU1pcG1hcHMmJncmJkUubWluRmlsdGVyIT09ZmUmJkUubWluRmlsdGVyIT09YmV9ZnVuY3Rpb24gTChFKXtuLmdlbmVyYXRlTWlwbWFwKEUpfWZ1bmN0aW9uIEEoRSx3LHEscnQsZ3Q9ITEpe2lmKGE9PT0hMSlyZXR1cm4gdztpZihFIT09bnVsbCl7aWYobltFXSE9PXZvaWQgMClyZXR1cm4gbltFXTtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IEF0dGVtcHQgdG8gdXNlIG5vbi1leGlzdGluZyBXZWJHTCBpbnRlcm5hbCBmb3JtYXQgJyIrRSsiJyIpfWxldCBXPXc7cmV0dXJuIHc9PT02NDAzJiYocT09PTUxMjYmJihXPTMzMzI2KSxxPT09NTEzMSYmKFc9MzMzMjUpLHE9PT01MTIxJiYoVz0zMzMyMSkpLHc9PT0zMzMxOSYmKHE9PT01MTI2JiYoVz0zMzMyOCkscT09PTUxMzEmJihXPTMzMzI3KSxxPT09NTEyMSYmKFc9MzMzMjMpKSx3PT09NjQwOCYmKHE9PT01MTI2JiYoVz0zNDgzNikscT09PTUxMzEmJihXPTM0ODQyKSxxPT09NTEyMSYmKFc9cnQ9PT0kdCYmZ3Q9PT0hMT8zNTkwNzozMjg1NikscT09PTMyODE5JiYoVz0zMjg1NCkscT09PTMyODIwJiYoVz0zMjg1NSkpLChXPT09MzMzMjV8fFc9PT0zMzMyNnx8Vz09PTMzMzI3fHxXPT09MzMzMjh8fFc9PT0zNDg0Mnx8Vz09PTM0ODM2KSYmdC5nZXQoIkVYVF9jb2xvcl9idWZmZXJfZmxvYXQiKSxXfWZ1bmN0aW9uIEgoRSx3LHEpe3JldHVybiBTKEUscSk9PT0hMHx8RS5pc0ZyYW1lYnVmZmVyVGV4dHVyZSYmRS5taW5GaWx0ZXIhPT1mZSYmRS5taW5GaWx0ZXIhPT1iZT9NYXRoLmxvZzIoTWF0aC5tYXgody53aWR0aCx3LmhlaWdodCkpKzE6RS5taXBtYXBzIT09dm9pZCAwJiZFLm1pcG1hcHMubGVuZ3RoPjA/RS5taXBtYXBzLmxlbmd0aDpFLmlzQ29tcHJlc3NlZFRleHR1cmUmJkFycmF5LmlzQXJyYXkoRS5pbWFnZSk/dy5taXBtYXBzLmxlbmd0aDoxfWZ1bmN0aW9uIHR0KEUpe3JldHVybiBFPT09ZmV8fEU9PT1JbXx8RT09PU5tPzk3Mjg6OTcyOX1mdW5jdGlvbiBYKEUpe2xldCB3PUUudGFyZ2V0O3cucmVtb3ZlRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsWCksUih3KSx3LmlzVmlkZW9UZXh0dXJlJiZnLmRlbGV0ZSh3KSxvLm1lbW9yeS50ZXh0dXJlcy0tfWZ1bmN0aW9uIHkoRSl7bGV0IHc9RS50YXJnZXQ7dy5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIix5KSxEKHcpfWZ1bmN0aW9uIFIoRSl7bGV0IHc9aS5nZXQoRSk7dy5fX3dlYmdsSW5pdCE9PXZvaWQgMCYmKG4uZGVsZXRlVGV4dHVyZSh3Ll9fd2ViZ2xUZXh0dXJlKSxpLnJlbW92ZShFKSl9ZnVuY3Rpb24gRChFKXtsZXQgdz1FLnRleHR1cmUscT1pLmdldChFKSxydD1pLmdldCh3KTtpZighIUUpe2lmKHJ0Ll9fd2ViZ2xUZXh0dXJlIT09dm9pZCAwJiYobi5kZWxldGVUZXh0dXJlKHJ0Ll9fd2ViZ2xUZXh0dXJlKSxvLm1lbW9yeS50ZXh0dXJlcy0tKSxFLmRlcHRoVGV4dHVyZSYmRS5kZXB0aFRleHR1cmUuZGlzcG9zZSgpLEUuaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQpZm9yKGxldCBndD0wO2d0PDY7Z3QrKyluLmRlbGV0ZUZyYW1lYnVmZmVyKHEuX193ZWJnbEZyYW1lYnVmZmVyW2d0XSkscS5fX3dlYmdsRGVwdGhidWZmZXImJm4uZGVsZXRlUmVuZGVyYnVmZmVyKHEuX193ZWJnbERlcHRoYnVmZmVyW2d0XSk7ZWxzZSBuLmRlbGV0ZUZyYW1lYnVmZmVyKHEuX193ZWJnbEZyYW1lYnVmZmVyKSxxLl9fd2ViZ2xEZXB0aGJ1ZmZlciYmbi5kZWxldGVSZW5kZXJidWZmZXIocS5fX3dlYmdsRGVwdGhidWZmZXIpLHEuX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyJiZuLmRlbGV0ZUZyYW1lYnVmZmVyKHEuX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyKSxxLl9fd2ViZ2xDb2xvclJlbmRlcmJ1ZmZlciYmbi5kZWxldGVSZW5kZXJidWZmZXIocS5fX3dlYmdsQ29sb3JSZW5kZXJidWZmZXIpLHEuX193ZWJnbERlcHRoUmVuZGVyYnVmZmVyJiZuLmRlbGV0ZVJlbmRlcmJ1ZmZlcihxLl9fd2ViZ2xEZXB0aFJlbmRlcmJ1ZmZlcik7aWYoRS5pc1dlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzKWZvcihsZXQgZ3Q9MCxXPXcubGVuZ3RoO2d0PFc7Z3QrKyl7bGV0IF90PWkuZ2V0KHdbZ3RdKTtfdC5fX3dlYmdsVGV4dHVyZSYmKG4uZGVsZXRlVGV4dHVyZShfdC5fX3dlYmdsVGV4dHVyZSksby5tZW1vcnkudGV4dHVyZXMtLSksaS5yZW1vdmUod1tndF0pfWkucmVtb3ZlKHcpLGkucmVtb3ZlKEUpfX1sZXQgRj0wO2Z1bmN0aW9uIHooKXtGPTB9ZnVuY3Rpb24gTigpe2xldCBFPUY7cmV0dXJuIEU+PWwmJmNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xUZXh0dXJlczogVHJ5aW5nIHRvIHVzZSAiK0UrIiB0ZXh0dXJlIHVuaXRzIHdoaWxlIHRoaXMgR1BVIHN1cHBvcnRzIG9ubHkgIitsKSxGKz0xLEV9ZnVuY3Rpb24gVihFLHcpe2xldCBxPWkuZ2V0KEUpO2lmKEUuaXNWaWRlb1RleHR1cmUmJmV0KEUpLEUudmVyc2lvbj4wJiZxLl9fdmVyc2lvbiE9PUUudmVyc2lvbil7bGV0IHJ0PUUuaW1hZ2U7aWYocnQ9PT12b2lkIDApY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIG1hcmtlZCBmb3IgdXBkYXRlIGJ1dCBpbWFnZSBpcyB1bmRlZmluZWQiKTtlbHNlIGlmKHJ0LmNvbXBsZXRlPT09ITEpY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIG1hcmtlZCBmb3IgdXBkYXRlIGJ1dCBpbWFnZSBpcyBpbmNvbXBsZXRlIik7ZWxzZXtrKHEsRSx3KTtyZXR1cm59fWUuYWN0aXZlVGV4dHVyZSgzMzk4NCt3KSxlLmJpbmRUZXh0dXJlKDM1NTMscS5fX3dlYmdsVGV4dHVyZSl9ZnVuY3Rpb24gUShFLHcpe2xldCBxPWkuZ2V0KEUpO2lmKEUudmVyc2lvbj4wJiZxLl9fdmVyc2lvbiE9PUUudmVyc2lvbil7ayhxLEUsdyk7cmV0dXJufWUuYWN0aXZlVGV4dHVyZSgzMzk4NCt3KSxlLmJpbmRUZXh0dXJlKDM1ODY2LHEuX193ZWJnbFRleHR1cmUpfWZ1bmN0aW9uIGF0KEUsdyl7bGV0IHE9aS5nZXQoRSk7aWYoRS52ZXJzaW9uPjAmJnEuX192ZXJzaW9uIT09RS52ZXJzaW9uKXtrKHEsRSx3KTtyZXR1cm59ZS5hY3RpdmVUZXh0dXJlKDMzOTg0K3cpLGUuYmluZFRleHR1cmUoMzI4NzkscS5fX3dlYmdsVGV4dHVyZSl9ZnVuY3Rpb24gRyhFLHcpe2xldCBxPWkuZ2V0KEUpO2lmKEUudmVyc2lvbj4wJiZxLl9fdmVyc2lvbiE9PUUudmVyc2lvbil7RnQocSxFLHcpO3JldHVybn1lLmFjdGl2ZVRleHR1cmUoMzM5ODQrdyksZS5iaW5kVGV4dHVyZSgzNDA2NyxxLl9fd2ViZ2xUZXh0dXJlKX1sZXQgJD17W091XToxMDQ5NyxbVmVdOjMzMDcxLFtrdV06MzM2NDh9LGx0PXtbZmVdOjk3MjgsW0ltXTo5OTg0LFtObV06OTk4NixbYmVdOjk3MjksW0R3XTo5OTg1LFtMbF06OTk4N307ZnVuY3Rpb24gZHQoRSx3LHEpe2lmKHE/KG4udGV4UGFyYW1ldGVyaShFLDEwMjQyLCRbdy53cmFwU10pLG4udGV4UGFyYW1ldGVyaShFLDEwMjQzLCRbdy53cmFwVF0pLChFPT09MzI4Nzl8fEU9PT0zNTg2NikmJm4udGV4UGFyYW1ldGVyaShFLDMyODgyLCRbdy53cmFwUl0pLG4udGV4UGFyYW1ldGVyaShFLDEwMjQwLGx0W3cubWFnRmlsdGVyXSksbi50ZXhQYXJhbWV0ZXJpKEUsMTAyNDEsbHRbdy5taW5GaWx0ZXJdKSk6KG4udGV4UGFyYW1ldGVyaShFLDEwMjQyLDMzMDcxKSxuLnRleFBhcmFtZXRlcmkoRSwxMDI0MywzMzA3MSksKEU9PT0zMjg3OXx8RT09PTM1ODY2KSYmbi50ZXhQYXJhbWV0ZXJpKEUsMzI4ODIsMzMwNzEpLCh3LndyYXBTIT09VmV8fHcud3JhcFQhPT1WZSkmJmNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogVGV4dHVyZSBpcyBub3QgcG93ZXIgb2YgdHdvLiBUZXh0dXJlLndyYXBTIGFuZCBUZXh0dXJlLndyYXBUIHNob3VsZCBiZSBzZXQgdG8gVEhSRUUuQ2xhbXBUb0VkZ2VXcmFwcGluZy4iKSxuLnRleFBhcmFtZXRlcmkoRSwxMDI0MCx0dCh3Lm1hZ0ZpbHRlcikpLG4udGV4UGFyYW1ldGVyaShFLDEwMjQxLHR0KHcubWluRmlsdGVyKSksdy5taW5GaWx0ZXIhPT1mZSYmdy5taW5GaWx0ZXIhPT1iZSYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIGlzIG5vdCBwb3dlciBvZiB0d28uIFRleHR1cmUubWluRmlsdGVyIHNob3VsZCBiZSBzZXQgdG8gVEhSRUUuTmVhcmVzdEZpbHRlciBvciBUSFJFRS5MaW5lYXJGaWx0ZXIuIikpLHQuaGFzKCJFWFRfdGV4dHVyZV9maWx0ZXJfYW5pc290cm9waWMiKT09PSEwKXtsZXQgcnQ9dC5nZXQoIkVYVF90ZXh0dXJlX2ZpbHRlcl9hbmlzb3Ryb3BpYyIpO2lmKHcudHlwZT09PVVpJiZ0LmhhcygiT0VTX3RleHR1cmVfZmxvYXRfbGluZWFyIik9PT0hMXx8YT09PSExJiZ3LnR5cGU9PT1VciYmdC5oYXMoIk9FU190ZXh0dXJlX2hhbGZfZmxvYXRfbGluZWFyIik9PT0hMSlyZXR1cm47KHcuYW5pc290cm9weT4xfHxpLmdldCh3KS5fX2N1cnJlbnRBbmlzb3Ryb3B5KSYmKG4udGV4UGFyYW1ldGVyZihFLHJ0LlRFWFRVUkVfTUFYX0FOSVNPVFJPUFlfRVhULE1hdGgubWluKHcuYW5pc290cm9weSxyLmdldE1heEFuaXNvdHJvcHkoKSkpLGkuZ2V0KHcpLl9fY3VycmVudEFuaXNvdHJvcHk9dy5hbmlzb3Ryb3B5KX19ZnVuY3Rpb24geHQoRSx3KXtFLl9fd2ViZ2xJbml0PT09dm9pZCAwJiYoRS5fX3dlYmdsSW5pdD0hMCx3LmFkZEV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLFgpLEUuX193ZWJnbFRleHR1cmU9bi5jcmVhdGVUZXh0dXJlKCksby5tZW1vcnkudGV4dHVyZXMrKyl9ZnVuY3Rpb24gayhFLHcscSl7bGV0IHJ0PTM1NTM7dy5pc0RhdGFUZXh0dXJlMkRBcnJheSYmKHJ0PTM1ODY2KSx3LmlzRGF0YVRleHR1cmUzRCYmKHJ0PTMyODc5KSx4dChFLHcpLGUuYWN0aXZlVGV4dHVyZSgzMzk4NCtxKSxlLmJpbmRUZXh0dXJlKHJ0LEUuX193ZWJnbFRleHR1cmUpLG4ucGl4ZWxTdG9yZWkoMzc0NDAsdy5mbGlwWSksbi5waXhlbFN0b3JlaSgzNzQ0MSx3LnByZW11bHRpcGx5QWxwaGEpLG4ucGl4ZWxTdG9yZWkoMzMxNyx3LnVucGFja0FsaWdubWVudCksbi5waXhlbFN0b3JlaSgzNzQ0MywwKTtsZXQgZ3Q9Xyh3KSYmYih3LmltYWdlKT09PSExLFc9cCh3LmltYWdlLGd0LCExLHUpO1c9dnQodyxXKTtsZXQgX3Q9YihXKXx8YSx5dD1zLmNvbnZlcnQody5mb3JtYXQsdy5lbmNvZGluZyksdXQ9cy5jb252ZXJ0KHcudHlwZSksY3Q9QSh3LmludGVybmFsRm9ybWF0LHl0LHV0LHcuZW5jb2Rpbmcsdy5pc1ZpZGVvVGV4dHVyZSk7ZHQocnQsdyxfdCk7bGV0IEF0LFA9dy5taXBtYXBzLHB0PWEmJncuaXNWaWRlb1RleHR1cmUhPT0hMCxodD1FLl9fdmVyc2lvbj09PXZvaWQgMCxFdD1IKHcsVyxfdCk7aWYody5pc0RlcHRoVGV4dHVyZSljdD02NDAyLGE/dy50eXBlPT09VWk/Y3Q9MzYwMTI6dy50eXBlPT09UWE/Y3Q9MzMxOTA6dy50eXBlPT09QnI/Y3Q9MzUwNTY6Y3Q9MzMxODk6dy50eXBlPT09VWkmJmNvbnNvbGUuZXJyb3IoIldlYkdMUmVuZGVyZXI6IEZsb2F0aW5nIHBvaW50IGRlcHRoIHRleHR1cmUgcmVxdWlyZXMgV2ViR0wyLiIpLHcuZm9ybWF0PT09T2kmJmN0PT09NjQwMiYmdy50eXBlIT09bm8mJncudHlwZSE9PVFhJiYoY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBVc2UgVW5zaWduZWRTaG9ydFR5cGUgb3IgVW5zaWduZWRJbnRUeXBlIGZvciBEZXB0aEZvcm1hdCBEZXB0aFRleHR1cmUuIiksdy50eXBlPW5vLHV0PXMuY29udmVydCh3LnR5cGUpKSx3LmZvcm1hdD09PVZyJiZjdD09PTY0MDImJihjdD0zNDA0MSx3LnR5cGUhPT1CciYmKGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogVXNlIFVuc2lnbmVkSW50MjQ4VHlwZSBmb3IgRGVwdGhTdGVuY2lsRm9ybWF0IERlcHRoVGV4dHVyZS4iKSx3LnR5cGU9QnIsdXQ9cy5jb252ZXJ0KHcudHlwZSkpKSxwdCYmaHQ/ZS50ZXhTdG9yYWdlMkQoMzU1MywxLGN0LFcud2lkdGgsVy5oZWlnaHQpOmUudGV4SW1hZ2UyRCgzNTUzLDAsY3QsVy53aWR0aCxXLmhlaWdodCwwLHl0LHV0LG51bGwpO2Vsc2UgaWYody5pc0RhdGFUZXh0dXJlKWlmKFAubGVuZ3RoPjAmJl90KXtwdCYmaHQmJmUudGV4U3RvcmFnZTJEKDM1NTMsRXQsY3QsUFswXS53aWR0aCxQWzBdLmhlaWdodCk7Zm9yKGxldCBZPTAsTXQ9UC5sZW5ndGg7WTxNdDtZKyspQXQ9UFtZXSxwdD9lLnRleFN1YkltYWdlMkQoMzU1MywwLDAsMCxBdC53aWR0aCxBdC5oZWlnaHQseXQsdXQsQXQuZGF0YSk6ZS50ZXhJbWFnZTJEKDM1NTMsWSxjdCxBdC53aWR0aCxBdC5oZWlnaHQsMCx5dCx1dCxBdC5kYXRhKTt3LmdlbmVyYXRlTWlwbWFwcz0hMX1lbHNlIHB0PyhodCYmZS50ZXhTdG9yYWdlMkQoMzU1MyxFdCxjdCxXLndpZHRoLFcuaGVpZ2h0KSxlLnRleFN1YkltYWdlMkQoMzU1MywwLDAsMCxXLndpZHRoLFcuaGVpZ2h0LHl0LHV0LFcuZGF0YSkpOmUudGV4SW1hZ2UyRCgzNTUzLDAsY3QsVy53aWR0aCxXLmhlaWdodCwwLHl0LHV0LFcuZGF0YSk7ZWxzZSBpZih3LmlzQ29tcHJlc3NlZFRleHR1cmUpe3B0JiZodCYmZS50ZXhTdG9yYWdlMkQoMzU1MyxFdCxjdCxQWzBdLndpZHRoLFBbMF0uaGVpZ2h0KTtmb3IobGV0IFk9MCxNdD1QLmxlbmd0aDtZPE10O1krKylBdD1QW1ldLHcuZm9ybWF0IT09UmU/eXQhPT1udWxsP3B0P2UuY29tcHJlc3NlZFRleFN1YkltYWdlMkQoMzU1MyxZLDAsMCxBdC53aWR0aCxBdC5oZWlnaHQseXQsQXQuZGF0YSk6ZS5jb21wcmVzc2VkVGV4SW1hZ2UyRCgzNTUzLFksY3QsQXQud2lkdGgsQXQuaGVpZ2h0LDAsQXQuZGF0YSk6Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBBdHRlbXB0IHRvIGxvYWQgdW5zdXBwb3J0ZWQgY29tcHJlc3NlZCB0ZXh0dXJlIGZvcm1hdCBpbiAudXBsb2FkVGV4dHVyZSgpIik6cHQ/ZS50ZXhTdWJJbWFnZTJEKDM1NTMsWSwwLDAsQXQud2lkdGgsQXQuaGVpZ2h0LHl0LHV0LEF0LmRhdGEpOmUudGV4SW1hZ2UyRCgzNTUzLFksY3QsQXQud2lkdGgsQXQuaGVpZ2h0LDAseXQsdXQsQXQuZGF0YSl9ZWxzZSBpZih3LmlzRGF0YVRleHR1cmUyREFycmF5KXB0PyhodCYmZS50ZXhTdG9yYWdlM0QoMzU4NjYsRXQsY3QsVy53aWR0aCxXLmhlaWdodCxXLmRlcHRoKSxlLnRleFN1YkltYWdlM0QoMzU4NjYsMCwwLDAsMCxXLndpZHRoLFcuaGVpZ2h0LFcuZGVwdGgseXQsdXQsVy5kYXRhKSk6ZS50ZXhJbWFnZTNEKDM1ODY2LDAsY3QsVy53aWR0aCxXLmhlaWdodCxXLmRlcHRoLDAseXQsdXQsVy5kYXRhKTtlbHNlIGlmKHcuaXNEYXRhVGV4dHVyZTNEKXB0PyhodCYmZS50ZXhTdG9yYWdlM0QoMzI4NzksRXQsY3QsVy53aWR0aCxXLmhlaWdodCxXLmRlcHRoKSxlLnRleFN1YkltYWdlM0QoMzI4NzksMCwwLDAsMCxXLndpZHRoLFcuaGVpZ2h0LFcuZGVwdGgseXQsdXQsVy5kYXRhKSk6ZS50ZXhJbWFnZTNEKDMyODc5LDAsY3QsVy53aWR0aCxXLmhlaWdodCxXLmRlcHRoLDAseXQsdXQsVy5kYXRhKTtlbHNlIGlmKHcuaXNGcmFtZWJ1ZmZlclRleHR1cmUpcHQmJmh0P2UudGV4U3RvcmFnZTJEKDM1NTMsRXQsY3QsVy53aWR0aCxXLmhlaWdodCk6ZS50ZXhJbWFnZTJEKDM1NTMsMCxjdCxXLndpZHRoLFcuaGVpZ2h0LDAseXQsdXQsbnVsbCk7ZWxzZSBpZihQLmxlbmd0aD4wJiZfdCl7cHQmJmh0JiZlLnRleFN0b3JhZ2UyRCgzNTUzLEV0LGN0LFBbMF0ud2lkdGgsUFswXS5oZWlnaHQpO2ZvcihsZXQgWT0wLE10PVAubGVuZ3RoO1k8TXQ7WSsrKUF0PVBbWV0scHQ/ZS50ZXhTdWJJbWFnZTJEKDM1NTMsWSwwLDAseXQsdXQsQXQpOmUudGV4SW1hZ2UyRCgzNTUzLFksY3QseXQsdXQsQXQpO3cuZ2VuZXJhdGVNaXBtYXBzPSExfWVsc2UgcHQ/KGh0JiZlLnRleFN0b3JhZ2UyRCgzNTUzLEV0LGN0LFcud2lkdGgsVy5oZWlnaHQpLGUudGV4U3ViSW1hZ2UyRCgzNTUzLDAsMCwwLHl0LHV0LFcpKTplLnRleEltYWdlMkQoMzU1MywwLGN0LHl0LHV0LFcpO1ModyxfdCkmJkwocnQpLEUuX192ZXJzaW9uPXcudmVyc2lvbix3Lm9uVXBkYXRlJiZ3Lm9uVXBkYXRlKHcpfWZ1bmN0aW9uIEZ0KEUsdyxxKXtpZih3LmltYWdlLmxlbmd0aCE9PTYpcmV0dXJuO3h0KEUsdyksZS5hY3RpdmVUZXh0dXJlKDMzOTg0K3EpLGUuYmluZFRleHR1cmUoMzQwNjcsRS5fX3dlYmdsVGV4dHVyZSksbi5waXhlbFN0b3JlaSgzNzQ0MCx3LmZsaXBZKSxuLnBpeGVsU3RvcmVpKDM3NDQxLHcucHJlbXVsdGlwbHlBbHBoYSksbi5waXhlbFN0b3JlaSgzMzE3LHcudW5wYWNrQWxpZ25tZW50KSxuLnBpeGVsU3RvcmVpKDM3NDQzLDApO2xldCBydD13JiYody5pc0NvbXByZXNzZWRUZXh0dXJlfHx3LmltYWdlWzBdLmlzQ29tcHJlc3NlZFRleHR1cmUpLGd0PXcuaW1hZ2VbMF0mJncuaW1hZ2VbMF0uaXNEYXRhVGV4dHVyZSxXPVtdO2ZvcihsZXQgWT0wO1k8NjtZKyspIXJ0JiYhZ3Q/V1tZXT1wKHcuaW1hZ2VbWV0sITEsITAsYyk6V1tZXT1ndD93LmltYWdlW1ldLmltYWdlOncuaW1hZ2VbWV0sV1tZXT12dCh3LFdbWV0pO2xldCBfdD1XWzBdLHl0PWIoX3QpfHxhLHV0PXMuY29udmVydCh3LmZvcm1hdCx3LmVuY29kaW5nKSxjdD1zLmNvbnZlcnQody50eXBlKSxBdD1BKHcuaW50ZXJuYWxGb3JtYXQsdXQsY3Qsdy5lbmNvZGluZyksUD1hJiZ3LmlzVmlkZW9UZXh0dXJlIT09ITAscHQ9RS5fX3ZlcnNpb249PT12b2lkIDAsaHQ9SCh3LF90LHl0KTtkdCgzNDA2Nyx3LHl0KTtsZXQgRXQ7aWYocnQpe1AmJnB0JiZlLnRleFN0b3JhZ2UyRCgzNDA2NyxodCxBdCxfdC53aWR0aCxfdC5oZWlnaHQpO2ZvcihsZXQgWT0wO1k8NjtZKyspe0V0PVdbWV0ubWlwbWFwcztmb3IobGV0IE10PTA7TXQ8RXQubGVuZ3RoO010Kyspe2xldCBEdD1FdFtNdF07dy5mb3JtYXQhPT1SZT91dCE9PW51bGw/UD9lLmNvbXByZXNzZWRUZXhTdWJJbWFnZTJEKDM0MDY5K1ksTXQsMCwwLER0LndpZHRoLER0LmhlaWdodCx1dCxEdC5kYXRhKTplLmNvbXByZXNzZWRUZXhJbWFnZTJEKDM0MDY5K1ksTXQsQXQsRHQud2lkdGgsRHQuaGVpZ2h0LDAsRHQuZGF0YSk6Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBBdHRlbXB0IHRvIGxvYWQgdW5zdXBwb3J0ZWQgY29tcHJlc3NlZCB0ZXh0dXJlIGZvcm1hdCBpbiAuc2V0VGV4dHVyZUN1YmUoKSIpOlA/ZS50ZXhTdWJJbWFnZTJEKDM0MDY5K1ksTXQsMCwwLER0LndpZHRoLER0LmhlaWdodCx1dCxjdCxEdC5kYXRhKTplLnRleEltYWdlMkQoMzQwNjkrWSxNdCxBdCxEdC53aWR0aCxEdC5oZWlnaHQsMCx1dCxjdCxEdC5kYXRhKX19fWVsc2V7RXQ9dy5taXBtYXBzLFAmJnB0JiYoRXQubGVuZ3RoPjAmJmh0KyssZS50ZXhTdG9yYWdlMkQoMzQwNjcsaHQsQXQsV1swXS53aWR0aCxXWzBdLmhlaWdodCkpO2ZvcihsZXQgWT0wO1k8NjtZKyspaWYoZ3Qpe1A/ZS50ZXhTdWJJbWFnZTJEKDM0MDY5K1ksMCwwLDAsV1tZXS53aWR0aCxXW1ldLmhlaWdodCx1dCxjdCxXW1ldLmRhdGEpOmUudGV4SW1hZ2UyRCgzNDA2OStZLDAsQXQsV1tZXS53aWR0aCxXW1ldLmhlaWdodCwwLHV0LGN0LFdbWV0uZGF0YSk7Zm9yKGxldCBNdD0wO010PEV0Lmxlbmd0aDtNdCsrKXtsZXQganQ9RXRbTXRdLmltYWdlW1ldLmltYWdlO1A/ZS50ZXhTdWJJbWFnZTJEKDM0MDY5K1ksTXQrMSwwLDAsanQud2lkdGgsanQuaGVpZ2h0LHV0LGN0LGp0LmRhdGEpOmUudGV4SW1hZ2UyRCgzNDA2OStZLE10KzEsQXQsanQud2lkdGgsanQuaGVpZ2h0LDAsdXQsY3QsanQuZGF0YSl9fWVsc2V7UD9lLnRleFN1YkltYWdlMkQoMzQwNjkrWSwwLDAsMCx1dCxjdCxXW1ldKTplLnRleEltYWdlMkQoMzQwNjkrWSwwLEF0LHV0LGN0LFdbWV0pO2ZvcihsZXQgTXQ9MDtNdDxFdC5sZW5ndGg7TXQrKyl7bGV0IER0PUV0W010XTtQP2UudGV4U3ViSW1hZ2UyRCgzNDA2OStZLE10KzEsMCwwLHV0LGN0LER0LmltYWdlW1ldKTplLnRleEltYWdlMkQoMzQwNjkrWSxNdCsxLEF0LHV0LGN0LER0LmltYWdlW1ldKX19fVModyx5dCkmJkwoMzQwNjcpLEUuX192ZXJzaW9uPXcudmVyc2lvbix3Lm9uVXBkYXRlJiZ3Lm9uVXBkYXRlKHcpfWZ1bmN0aW9uIG10KEUsdyxxLHJ0LGd0KXtsZXQgVz1zLmNvbnZlcnQocS5mb3JtYXQscS5lbmNvZGluZyksX3Q9cy5jb252ZXJ0KHEudHlwZSkseXQ9QShxLmludGVybmFsRm9ybWF0LFcsX3QscS5lbmNvZGluZyk7aS5nZXQodykuX19oYXNFeHRlcm5hbFRleHR1cmVzfHwoZ3Q9PT0zMjg3OXx8Z3Q9PT0zNTg2Nj9lLnRleEltYWdlM0QoZ3QsMCx5dCx3LndpZHRoLHcuaGVpZ2h0LHcuZGVwdGgsMCxXLF90LG51bGwpOmUudGV4SW1hZ2UyRChndCwwLHl0LHcud2lkdGgsdy5oZWlnaHQsMCxXLF90LG51bGwpKSxlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxFKSx3LnVzZVJlbmRlclRvVGV4dHVyZT9kLmZyYW1lYnVmZmVyVGV4dHVyZTJETXVsdGlzYW1wbGVFWFQoMzYxNjAscnQsZ3QsaS5nZXQocSkuX193ZWJnbFRleHR1cmUsMCxpdCh3KSk6bi5mcmFtZWJ1ZmZlclRleHR1cmUyRCgzNjE2MCxydCxndCxpLmdldChxKS5fX3dlYmdsVGV4dHVyZSwwKSxlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxudWxsKX1mdW5jdGlvbiBTdChFLHcscSl7aWYobi5iaW5kUmVuZGVyYnVmZmVyKDM2MTYxLEUpLHcuZGVwdGhCdWZmZXImJiF3LnN0ZW5jaWxCdWZmZXIpe2xldCBydD0zMzE4OTtpZihxfHx3LnVzZVJlbmRlclRvVGV4dHVyZSl7bGV0IGd0PXcuZGVwdGhUZXh0dXJlO2d0JiZndC5pc0RlcHRoVGV4dHVyZSYmKGd0LnR5cGU9PT1VaT9ydD0zNjAxMjpndC50eXBlPT09UWEmJihydD0zMzE5MCkpO2xldCBXPWl0KHcpO3cudXNlUmVuZGVyVG9UZXh0dXJlP2QucmVuZGVyYnVmZmVyU3RvcmFnZU11bHRpc2FtcGxlRVhUKDM2MTYxLFcscnQsdy53aWR0aCx3LmhlaWdodCk6bi5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGUoMzYxNjEsVyxydCx3LndpZHRoLHcuaGVpZ2h0KX1lbHNlIG4ucmVuZGVyYnVmZmVyU3RvcmFnZSgzNjE2MSxydCx3LndpZHRoLHcuaGVpZ2h0KTtuLmZyYW1lYnVmZmVyUmVuZGVyYnVmZmVyKDM2MTYwLDM2MDk2LDM2MTYxLEUpfWVsc2UgaWYody5kZXB0aEJ1ZmZlciYmdy5zdGVuY2lsQnVmZmVyKXtsZXQgcnQ9aXQodyk7cSYmdy51c2VSZW5kZXJidWZmZXI/bi5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGUoMzYxNjEscnQsMzUwNTYsdy53aWR0aCx3LmhlaWdodCk6dy51c2VSZW5kZXJUb1RleHR1cmU/ZC5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGVFWFQoMzYxNjEscnQsMzUwNTYsdy53aWR0aCx3LmhlaWdodCk6bi5yZW5kZXJidWZmZXJTdG9yYWdlKDM2MTYxLDM0MDQxLHcud2lkdGgsdy5oZWlnaHQpLG4uZnJhbWVidWZmZXJSZW5kZXJidWZmZXIoMzYxNjAsMzMzMDYsMzYxNjEsRSl9ZWxzZXtsZXQgcnQ9dy5pc1dlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzPT09ITA/dy50ZXh0dXJlWzBdOncudGV4dHVyZSxndD1zLmNvbnZlcnQocnQuZm9ybWF0LHJ0LmVuY29kaW5nKSxXPXMuY29udmVydChydC50eXBlKSxfdD1BKHJ0LmludGVybmFsRm9ybWF0LGd0LFcscnQuZW5jb2RpbmcpLHl0PWl0KHcpO3EmJncudXNlUmVuZGVyYnVmZmVyP24ucmVuZGVyYnVmZmVyU3RvcmFnZU11bHRpc2FtcGxlKDM2MTYxLHl0LF90LHcud2lkdGgsdy5oZWlnaHQpOncudXNlUmVuZGVyVG9UZXh0dXJlP2QucmVuZGVyYnVmZmVyU3RvcmFnZU11bHRpc2FtcGxlRVhUKDM2MTYxLHl0LF90LHcud2lkdGgsdy5oZWlnaHQpOm4ucmVuZGVyYnVmZmVyU3RvcmFnZSgzNjE2MSxfdCx3LndpZHRoLHcuaGVpZ2h0KX1uLmJpbmRSZW5kZXJidWZmZXIoMzYxNjEsbnVsbCl9ZnVuY3Rpb24gQihFLHcpe2lmKHcmJncuaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQpdGhyb3cgbmV3IEVycm9yKCJEZXB0aCBUZXh0dXJlIHdpdGggY3ViZSByZW5kZXIgdGFyZ2V0cyBpcyBub3Qgc3VwcG9ydGVkIik7aWYoZS5iaW5kRnJhbWVidWZmZXIoMzYxNjAsRSksISh3LmRlcHRoVGV4dHVyZSYmdy5kZXB0aFRleHR1cmUuaXNEZXB0aFRleHR1cmUpKXRocm93IG5ldyBFcnJvcigicmVuZGVyVGFyZ2V0LmRlcHRoVGV4dHVyZSBtdXN0IGJlIGFuIGluc3RhbmNlIG9mIFRIUkVFLkRlcHRoVGV4dHVyZSIpOyghaS5nZXQody5kZXB0aFRleHR1cmUpLl9fd2ViZ2xUZXh0dXJlfHx3LmRlcHRoVGV4dHVyZS5pbWFnZS53aWR0aCE9PXcud2lkdGh8fHcuZGVwdGhUZXh0dXJlLmltYWdlLmhlaWdodCE9PXcuaGVpZ2h0KSYmKHcuZGVwdGhUZXh0dXJlLmltYWdlLndpZHRoPXcud2lkdGgsdy5kZXB0aFRleHR1cmUuaW1hZ2UuaGVpZ2h0PXcuaGVpZ2h0LHcuZGVwdGhUZXh0dXJlLm5lZWRzVXBkYXRlPSEwKSxWKHcuZGVwdGhUZXh0dXJlLDApO2xldCBydD1pLmdldCh3LmRlcHRoVGV4dHVyZSkuX193ZWJnbFRleHR1cmUsZ3Q9aXQodyk7aWYody5kZXB0aFRleHR1cmUuZm9ybWF0PT09T2kpdy51c2VSZW5kZXJUb1RleHR1cmU/ZC5mcmFtZWJ1ZmZlclRleHR1cmUyRE11bHRpc2FtcGxlRVhUKDM2MTYwLDM2MDk2LDM1NTMscnQsMCxndCk6bi5mcmFtZWJ1ZmZlclRleHR1cmUyRCgzNjE2MCwzNjA5NiwzNTUzLHJ0LDApO2Vsc2UgaWYody5kZXB0aFRleHR1cmUuZm9ybWF0PT09VnIpdy51c2VSZW5kZXJUb1RleHR1cmU/ZC5mcmFtZWJ1ZmZlclRleHR1cmUyRE11bHRpc2FtcGxlRVhUKDM2MTYwLDMzMzA2LDM1NTMscnQsMCxndCk6bi5mcmFtZWJ1ZmZlclRleHR1cmUyRCgzNjE2MCwzMzMwNiwzNTUzLHJ0LDApO2Vsc2UgdGhyb3cgbmV3IEVycm9yKCJVbmtub3duIGRlcHRoVGV4dHVyZSBmb3JtYXQiKX1mdW5jdGlvbiBzdChFKXtsZXQgdz1pLmdldChFKSxxPUUuaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQ9PT0hMDtpZihFLmRlcHRoVGV4dHVyZSYmIXcuX19hdXRvQWxsb2NhdGVEZXB0aEJ1ZmZlcil7aWYocSl0aHJvdyBuZXcgRXJyb3IoInRhcmdldC5kZXB0aFRleHR1cmUgbm90IHN1cHBvcnRlZCBpbiBDdWJlIHJlbmRlciB0YXJnZXRzIik7Qih3Ll9fd2ViZ2xGcmFtZWJ1ZmZlcixFKX1lbHNlIGlmKHEpe3cuX193ZWJnbERlcHRoYnVmZmVyPVtdO2ZvcihsZXQgcnQ9MDtydDw2O3J0KyspZS5iaW5kRnJhbWVidWZmZXIoMzYxNjAsdy5fX3dlYmdsRnJhbWVidWZmZXJbcnRdKSx3Ll9fd2ViZ2xEZXB0aGJ1ZmZlcltydF09bi5jcmVhdGVSZW5kZXJidWZmZXIoKSxTdCh3Ll9fd2ViZ2xEZXB0aGJ1ZmZlcltydF0sRSwhMSl9ZWxzZSBlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCx3Ll9fd2ViZ2xGcmFtZWJ1ZmZlciksdy5fX3dlYmdsRGVwdGhidWZmZXI9bi5jcmVhdGVSZW5kZXJidWZmZXIoKSxTdCh3Ll9fd2ViZ2xEZXB0aGJ1ZmZlcixFLCExKTtlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxudWxsKX1mdW5jdGlvbiBudChFLHcscSl7bGV0IHJ0PWkuZ2V0KEUpO3chPT12b2lkIDAmJm10KHJ0Ll9fd2ViZ2xGcmFtZWJ1ZmZlcixFLEUudGV4dHVyZSwzNjA2NCwzNTUzKSxxIT09dm9pZCAwJiZzdChFKX1mdW5jdGlvbiBDKEUpe2xldCB3PUUudGV4dHVyZSxxPWkuZ2V0KEUpLHJ0PWkuZ2V0KHcpO0UuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIseSksRS5pc1dlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzIT09ITAmJihydC5fX3dlYmdsVGV4dHVyZT09PXZvaWQgMCYmKHJ0Ll9fd2ViZ2xUZXh0dXJlPW4uY3JlYXRlVGV4dHVyZSgpKSxydC5fX3ZlcnNpb249dy52ZXJzaW9uLG8ubWVtb3J5LnRleHR1cmVzKyspO2xldCBndD1FLmlzV2ViR0xDdWJlUmVuZGVyVGFyZ2V0PT09ITAsVz1FLmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHM9PT0hMCxfdD13LmlzRGF0YVRleHR1cmUzRHx8dy5pc0RhdGFUZXh0dXJlMkRBcnJheSx5dD1iKEUpfHxhO2lmKGd0KXtxLl9fd2ViZ2xGcmFtZWJ1ZmZlcj1bXTtmb3IobGV0IHV0PTA7dXQ8Njt1dCsrKXEuX193ZWJnbEZyYW1lYnVmZmVyW3V0XT1uLmNyZWF0ZUZyYW1lYnVmZmVyKCl9ZWxzZSBpZihxLl9fd2ViZ2xGcmFtZWJ1ZmZlcj1uLmNyZWF0ZUZyYW1lYnVmZmVyKCksVylpZihyLmRyYXdCdWZmZXJzKXtsZXQgdXQ9RS50ZXh0dXJlO2ZvcihsZXQgY3Q9MCxBdD11dC5sZW5ndGg7Y3Q8QXQ7Y3QrKyl7bGV0IFA9aS5nZXQodXRbY3RdKTtQLl9fd2ViZ2xUZXh0dXJlPT09dm9pZCAwJiYoUC5fX3dlYmdsVGV4dHVyZT1uLmNyZWF0ZVRleHR1cmUoKSxvLm1lbW9yeS50ZXh0dXJlcysrKX19ZWxzZSBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IFdlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzIGNhbiBvbmx5IGJlIHVzZWQgd2l0aCBXZWJHTDIgb3IgV0VCR0xfZHJhd19idWZmZXJzIGV4dGVuc2lvbi4iKTtlbHNlIGlmKEUudXNlUmVuZGVyYnVmZmVyKWlmKGEpe3EuX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyPW4uY3JlYXRlRnJhbWVidWZmZXIoKSxxLl9fd2ViZ2xDb2xvclJlbmRlcmJ1ZmZlcj1uLmNyZWF0ZVJlbmRlcmJ1ZmZlcigpLG4uYmluZFJlbmRlcmJ1ZmZlcigzNjE2MSxxLl9fd2ViZ2xDb2xvclJlbmRlcmJ1ZmZlcik7bGV0IHV0PXMuY29udmVydCh3LmZvcm1hdCx3LmVuY29kaW5nKSxjdD1zLmNvbnZlcnQody50eXBlKSxBdD1BKHcuaW50ZXJuYWxGb3JtYXQsdXQsY3Qsdy5lbmNvZGluZyksUD1pdChFKTtuLnJlbmRlcmJ1ZmZlclN0b3JhZ2VNdWx0aXNhbXBsZSgzNjE2MSxQLEF0LEUud2lkdGgsRS5oZWlnaHQpLGUuYmluZEZyYW1lYnVmZmVyKDM2MTYwLHEuX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyKSxuLmZyYW1lYnVmZmVyUmVuZGVyYnVmZmVyKDM2MTYwLDM2MDY0LDM2MTYxLHEuX193ZWJnbENvbG9yUmVuZGVyYnVmZmVyKSxuLmJpbmRSZW5kZXJidWZmZXIoMzYxNjEsbnVsbCksRS5kZXB0aEJ1ZmZlciYmKHEuX193ZWJnbERlcHRoUmVuZGVyYnVmZmVyPW4uY3JlYXRlUmVuZGVyYnVmZmVyKCksU3QocS5fX3dlYmdsRGVwdGhSZW5kZXJidWZmZXIsRSwhMCkpLGUuYmluZEZyYW1lYnVmZmVyKDM2MTYwLG51bGwpfWVsc2UgY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBXZWJHTE11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0IGNhbiBvbmx5IGJlIHVzZWQgd2l0aCBXZWJHTDIuIik7aWYoZ3Qpe2UuYmluZFRleHR1cmUoMzQwNjcscnQuX193ZWJnbFRleHR1cmUpLGR0KDM0MDY3LHcseXQpO2ZvcihsZXQgdXQ9MDt1dDw2O3V0KyspbXQocS5fX3dlYmdsRnJhbWVidWZmZXJbdXRdLEUsdywzNjA2NCwzNDA2OSt1dCk7Uyh3LHl0KSYmTCgzNDA2NyksZS51bmJpbmRUZXh0dXJlKCl9ZWxzZSBpZihXKXtsZXQgdXQ9RS50ZXh0dXJlO2ZvcihsZXQgY3Q9MCxBdD11dC5sZW5ndGg7Y3Q8QXQ7Y3QrKyl7bGV0IFA9dXRbY3RdLHB0PWkuZ2V0KFApO2UuYmluZFRleHR1cmUoMzU1MyxwdC5fX3dlYmdsVGV4dHVyZSksZHQoMzU1MyxQLHl0KSxtdChxLl9fd2ViZ2xGcmFtZWJ1ZmZlcixFLFAsMzYwNjQrY3QsMzU1MyksUyhQLHl0KSYmTCgzNTUzKX1lLnVuYmluZFRleHR1cmUoKX1lbHNle2xldCB1dD0zNTUzO190JiYoYT91dD13LmlzRGF0YVRleHR1cmUzRD8zMjg3OTozNTg2Njpjb25zb2xlLndhcm4oIlRIUkVFLkRhdGFUZXh0dXJlM0QgYW5kIFRIUkVFLkRhdGFUZXh0dXJlMkRBcnJheSBvbmx5IHN1cHBvcnRlZCB3aXRoIFdlYkdMMi4iKSksZS5iaW5kVGV4dHVyZSh1dCxydC5fX3dlYmdsVGV4dHVyZSksZHQodXQsdyx5dCksbXQocS5fX3dlYmdsRnJhbWVidWZmZXIsRSx3LDM2MDY0LHV0KSxTKHcseXQpJiZMKHV0KSxlLnVuYmluZFRleHR1cmUoKX1FLmRlcHRoQnVmZmVyJiZzdChFKX1mdW5jdGlvbiBqKEUpe2xldCB3PWIoRSl8fGEscT1FLmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHM9PT0hMD9FLnRleHR1cmU6W0UudGV4dHVyZV07Zm9yKGxldCBydD0wLGd0PXEubGVuZ3RoO3J0PGd0O3J0Kyspe2xldCBXPXFbcnRdO2lmKFMoVyx3KSl7bGV0IF90PUUuaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQ/MzQwNjc6MzU1Myx5dD1pLmdldChXKS5fX3dlYmdsVGV4dHVyZTtlLmJpbmRUZXh0dXJlKF90LHl0KSxMKF90KSxlLnVuYmluZFRleHR1cmUoKX19fWZ1bmN0aW9uIEooRSl7aWYoRS51c2VSZW5kZXJidWZmZXIpaWYoYSl7bGV0IHc9RS53aWR0aCxxPUUuaGVpZ2h0LHJ0PTE2Mzg0LGd0PVszNjA2NF0sVz1FLnN0ZW5jaWxCdWZmZXI/MzMzMDY6MzYwOTY7RS5kZXB0aEJ1ZmZlciYmZ3QucHVzaChXKSxFLmlnbm9yZURlcHRoRm9yTXVsdGlzYW1wbGVDb3B5fHwoRS5kZXB0aEJ1ZmZlciYmKHJ0fD0yNTYpLEUuc3RlbmNpbEJ1ZmZlciYmKHJ0fD0xMDI0KSk7bGV0IF90PWkuZ2V0KEUpO2UuYmluZEZyYW1lYnVmZmVyKDM2MDA4LF90Ll9fd2ViZ2xNdWx0aXNhbXBsZWRGcmFtZWJ1ZmZlciksZS5iaW5kRnJhbWVidWZmZXIoMzYwMDksX3QuX193ZWJnbEZyYW1lYnVmZmVyKSxFLmlnbm9yZURlcHRoRm9yTXVsdGlzYW1wbGVDb3B5JiYobi5pbnZhbGlkYXRlRnJhbWVidWZmZXIoMzYwMDgsW1ddKSxuLmludmFsaWRhdGVGcmFtZWJ1ZmZlcigzNjAwOSxbV10pKSxuLmJsaXRGcmFtZWJ1ZmZlcigwLDAsdyxxLDAsMCx3LHEscnQsOTcyOCksbi5pbnZhbGlkYXRlRnJhbWVidWZmZXIoMzYwMDgsZ3QpLGUuYmluZEZyYW1lYnVmZmVyKDM2MDA4LG51bGwpLGUuYmluZEZyYW1lYnVmZmVyKDM2MDA5LF90Ll9fd2ViZ2xNdWx0aXNhbXBsZWRGcmFtZWJ1ZmZlcil9ZWxzZSBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IFdlYkdMTXVsdGlzYW1wbGVSZW5kZXJUYXJnZXQgY2FuIG9ubHkgYmUgdXNlZCB3aXRoIFdlYkdMMi4iKX1mdW5jdGlvbiBpdChFKXtyZXR1cm4gYSYmKEUudXNlUmVuZGVyYnVmZmVyfHxFLnVzZVJlbmRlclRvVGV4dHVyZSk/TWF0aC5taW4oaCxFLnNhbXBsZXMpOjB9ZnVuY3Rpb24gZXQoRSl7bGV0IHc9by5yZW5kZXIuZnJhbWU7Zy5nZXQoRSkhPT13JiYoZy5zZXQoRSx3KSxFLnVwZGF0ZSgpKX1mdW5jdGlvbiB2dChFLHcpe2xldCBxPUUuZW5jb2RpbmcscnQ9RS5mb3JtYXQsZ3Q9RS50eXBlO3JldHVybiBFLmlzQ29tcHJlc3NlZFRleHR1cmU9PT0hMHx8RS5pc1ZpZGVvVGV4dHVyZT09PSEwfHxFLmZvcm1hdD09PUh1fHxxIT09cmkmJihxPT09JHQ/YT09PSExP3QuaGFzKCJFWFRfc1JHQiIpPT09ITAmJnJ0PT09UmU/KEUuZm9ybWF0PUh1LEUubWluRmlsdGVyPWJlLEUuZ2VuZXJhdGVNaXBtYXBzPSExKTp3PU5uLnNSR0JUb0xpbmVhcih3KToocnQhPT1SZXx8Z3QhPT1laSkmJmNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xUZXh0dXJlczogc1JHQiBlbmNvZGVkIHRleHR1cmVzIGhhdmUgdG8gdXNlIFJHQkFGb3JtYXQgYW5kIFVuc2lnbmVkQnl0ZVR5cGUuIik6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xUZXh0dXJlczogVW5zdXBwb3J0ZWQgdGV4dHVyZSBlbmNvZGluZzoiLHEpKSx3fWxldCBidD0hMSxJdD0hMTtmdW5jdGlvbiBadChFLHcpe0UmJkUuaXNXZWJHTFJlbmRlclRhcmdldCYmKGJ0PT09ITEmJihjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMVGV4dHVyZXMuc2FmZVNldFRleHR1cmUyRDogZG9uJ3QgdXNlIHJlbmRlciB0YXJnZXRzIGFzIHRleHR1cmVzLiBVc2UgdGhlaXIgLnRleHR1cmUgcHJvcGVydHkgaW5zdGVhZC4iKSxidD0hMCksRT1FLnRleHR1cmUpLFYoRSx3KX1mdW5jdGlvbiBxdChFLHcpe0UmJkUuaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQmJihJdD09PSExJiYoY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFRleHR1cmVzLnNhZmVTZXRUZXh0dXJlQ3ViZTogZG9uJ3QgdXNlIGN1YmUgcmVuZGVyIHRhcmdldHMgYXMgdGV4dHVyZXMuIFVzZSB0aGVpciAudGV4dHVyZSBwcm9wZXJ0eSBpbnN0ZWFkLiIpLEl0PSEwKSxFPUUudGV4dHVyZSksRyhFLHcpfXRoaXMuYWxsb2NhdGVUZXh0dXJlVW5pdD1OLHRoaXMucmVzZXRUZXh0dXJlVW5pdHM9eix0aGlzLnNldFRleHR1cmUyRD1WLHRoaXMuc2V0VGV4dHVyZTJEQXJyYXk9USx0aGlzLnNldFRleHR1cmUzRD1hdCx0aGlzLnNldFRleHR1cmVDdWJlPUcsdGhpcy5yZWJpbmRUZXh0dXJlcz1udCx0aGlzLnNldHVwUmVuZGVyVGFyZ2V0PUMsdGhpcy51cGRhdGVSZW5kZXJUYXJnZXRNaXBtYXA9aix0aGlzLnVwZGF0ZU11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0PUosdGhpcy5zZXR1cERlcHRoUmVuZGVyYnVmZmVyPXN0LHRoaXMuc2V0dXBGcmFtZUJ1ZmZlclRleHR1cmU9bXQsdGhpcy5zYWZlU2V0VGV4dHVyZTJEPVp0LHRoaXMuc2FmZVNldFRleHR1cmVDdWJlPXF0fWZ1bmN0aW9uIExFKG4sdCxlKXtsZXQgaT1lLmlzV2ViR0wyO2Z1bmN0aW9uIHIocyxvPW51bGwpe2xldCBhO2lmKHM9PT1laSlyZXR1cm4gNTEyMTtpZihzPT09encpcmV0dXJuIDMyODE5O2lmKHM9PT1VdylyZXR1cm4gMzI4MjA7aWYocz09PUl3KXJldHVybiA1MTIwO2lmKHM9PT1OdylyZXR1cm4gNTEyMjtpZihzPT09bm8pcmV0dXJuIDUxMjM7aWYocz09PUZ3KXJldHVybiA1MTI0O2lmKHM9PT1RYSlyZXR1cm4gNTEyNTtpZihzPT09VWkpcmV0dXJuIDUxMjY7aWYocz09PVVyKXJldHVybiBpPzUxMzE6KGE9dC5nZXQoIk9FU190ZXh0dXJlX2hhbGZfZmxvYXQiKSxhIT09bnVsbD9hLkhBTEZfRkxPQVRfT0VTOm51bGwpO2lmKHM9PT1CdylyZXR1cm4gNjQwNjtpZihzPT09UmUpcmV0dXJuIDY0MDg7aWYocz09PU93KXJldHVybiA2NDA5O2lmKHM9PT1rdylyZXR1cm4gNjQxMDtpZihzPT09T2kpcmV0dXJuIDY0MDI7aWYocz09PVZyKXJldHVybiAzNDA0MTtpZihzPT09SHcpcmV0dXJuIDY0MDM7aWYocz09PUh1KXJldHVybiBhPXQuZ2V0KCJFWFRfc1JHQiIpLGEhPT1udWxsP2EuU1JHQl9BTFBIQV9FWFQ6bnVsbDtpZihzPT09VncpcmV0dXJuIDM2MjQ0O2lmKHM9PT1HdylyZXR1cm4gMzMzMTk7aWYocz09PVd3KXJldHVybiAzMzMyMDtpZihzPT09cXcpcmV0dXJuIDM2MjQ5O2lmKHM9PT1LY3x8cz09PVFjfHxzPT09amN8fHM9PT10dSlpZihvPT09JHQpaWYoYT10LmdldCgiV0VCR0xfY29tcHJlc3NlZF90ZXh0dXJlX3MzdGNfc3JnYiIpLGEhPT1udWxsKXtpZihzPT09S2MpcmV0dXJuIGEuQ09NUFJFU1NFRF9TUkdCX1MzVENfRFhUMV9FWFQ7aWYocz09PVFjKXJldHVybiBhLkNPTVBSRVNTRURfU1JHQl9BTFBIQV9TM1RDX0RYVDFfRVhUO2lmKHM9PT1qYylyZXR1cm4gYS5DT01QUkVTU0VEX1NSR0JfQUxQSEFfUzNUQ19EWFQzX0VYVDtpZihzPT09dHUpcmV0dXJuIGEuQ09NUFJFU1NFRF9TUkdCX0FMUEhBX1MzVENfRFhUNV9FWFR9ZWxzZSByZXR1cm4gbnVsbDtlbHNlIGlmKGE9dC5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjIiksYSE9PW51bGwpe2lmKHM9PT1LYylyZXR1cm4gYS5DT01QUkVTU0VEX1JHQl9TM1RDX0RYVDFfRVhUO2lmKHM9PT1RYylyZXR1cm4gYS5DT01QUkVTU0VEX1JHQkFfUzNUQ19EWFQxX0VYVDtpZihzPT09amMpcmV0dXJuIGEuQ09NUFJFU1NFRF9SR0JBX1MzVENfRFhUM19FWFQ7aWYocz09PXR1KXJldHVybiBhLkNPTVBSRVNTRURfUkdCQV9TM1RDX0RYVDVfRVhUfWVsc2UgcmV0dXJuIG51bGw7aWYocz09PUZtfHxzPT09em18fHM9PT1VbXx8cz09PUJtKWlmKGE9dC5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9wdnJ0YyIpLGEhPT1udWxsKXtpZihzPT09Rm0pcmV0dXJuIGEuQ09NUFJFU1NFRF9SR0JfUFZSVENfNEJQUFYxX0lNRztpZihzPT09em0pcmV0dXJuIGEuQ09NUFJFU1NFRF9SR0JfUFZSVENfMkJQUFYxX0lNRztpZihzPT09VW0pcmV0dXJuIGEuQ09NUFJFU1NFRF9SR0JBX1BWUlRDXzRCUFBWMV9JTUc7aWYocz09PUJtKXJldHVybiBhLkNPTVBSRVNTRURfUkdCQV9QVlJUQ18yQlBQVjFfSU1HfWVsc2UgcmV0dXJuIG51bGw7aWYocz09PVh3KXJldHVybiBhPXQuZ2V0KCJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfZXRjMSIpLGEhPT1udWxsP2EuQ09NUFJFU1NFRF9SR0JfRVRDMV9XRUJHTDpudWxsO2lmKHM9PT1PbXx8cz09PWttKWlmKGE9dC5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9ldGMiKSxhIT09bnVsbCl7aWYocz09PU9tKXJldHVybiBvPT09JHQ/YS5DT01QUkVTU0VEX1NSR0I4X0VUQzI6YS5DT01QUkVTU0VEX1JHQjhfRVRDMjtpZihzPT09a20pcmV0dXJuIG89PT0kdD9hLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0VUQzJfRUFDOmEuQ09NUFJFU1NFRF9SR0JBOF9FVEMyX0VBQ31lbHNlIHJldHVybiBudWxsO2lmKHM9PT1IbXx8cz09PVZtfHxzPT09R218fHM9PT1XbXx8cz09PXFtfHxzPT09WG18fHM9PT1ZbXx8cz09PVptfHxzPT09Sm18fHM9PT0kbXx8cz09PUttfHxzPT09UW18fHM9PT1qbXx8cz09PXRnKWlmKGE9dC5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9hc3RjIiksYSE9PW51bGwpe2lmKHM9PT1IbSlyZXR1cm4gbz09PSR0P2EuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ180eDRfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfNHg0X0tIUjtpZihzPT09Vm0pcmV0dXJuIG89PT0kdD9hLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfNXg0X0tIUjphLkNPTVBSRVNTRURfUkdCQV9BU1RDXzV4NF9LSFI7aWYocz09PUdtKXJldHVybiBvPT09JHQ/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzV4NV9LSFI6YS5DT01QUkVTU0VEX1JHQkFfQVNUQ181eDVfS0hSO2lmKHM9PT1XbSlyZXR1cm4gbz09PSR0P2EuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ182eDVfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfNng1X0tIUjtpZihzPT09cW0pcmV0dXJuIG89PT0kdD9hLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfNng2X0tIUjphLkNPTVBSRVNTRURfUkdCQV9BU1RDXzZ4Nl9LSFI7aWYocz09PVhtKXJldHVybiBvPT09JHQ/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzh4NV9LSFI6YS5DT01QUkVTU0VEX1JHQkFfQVNUQ184eDVfS0hSO2lmKHM9PT1ZbSlyZXR1cm4gbz09PSR0P2EuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ184eDZfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfOHg2X0tIUjtpZihzPT09Wm0pcmV0dXJuIG89PT0kdD9hLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfOHg4X0tIUjphLkNPTVBSRVNTRURfUkdCQV9BU1RDXzh4OF9LSFI7aWYocz09PUptKXJldHVybiBvPT09JHQ/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDVfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfMTB4NV9LSFI7aWYocz09PSRtKXJldHVybiBvPT09JHQ/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDZfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfMTB4Nl9LSFI7aWYocz09PUttKXJldHVybiBvPT09JHQ/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDhfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfMTB4OF9LSFI7aWYocz09PVFtKXJldHVybiBvPT09JHQ/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDEwX0tIUjphLkNPTVBSRVNTRURfUkdCQV9BU1RDXzEweDEwX0tIUjtpZihzPT09am0pcmV0dXJuIG89PT0kdD9hLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfMTJ4MTBfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfMTJ4MTBfS0hSO2lmKHM9PT10ZylyZXR1cm4gbz09PSR0P2EuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ18xMngxMl9LSFI6YS5DT01QUkVTU0VEX1JHQkFfQVNUQ18xMngxMl9LSFJ9ZWxzZSByZXR1cm4gbnVsbDtpZihzPT09ZWcpaWYoYT10LmdldCgiRVhUX3RleHR1cmVfY29tcHJlc3Npb25fYnB0YyIpLGEhPT1udWxsKXtpZihzPT09ZWcpcmV0dXJuIG89PT0kdD9hLkNPTVBSRVNTRURfU1JHQl9BTFBIQV9CUFRDX1VOT1JNX0VYVDphLkNPTVBSRVNTRURfUkdCQV9CUFRDX1VOT1JNX0VYVH1lbHNlIHJldHVybiBudWxsO2lmKHM9PT1CcilyZXR1cm4gaT8zNDA0MjooYT10LmdldCgiV0VCR0xfZGVwdGhfdGV4dHVyZSIpLGEhPT1udWxsP2EuVU5TSUdORURfSU5UXzI0XzhfV0VCR0w6bnVsbCl9cmV0dXJue2NvbnZlcnQ6cn19dmFyIGhsPWNsYXNzIGV4dGVuZHMgU2V7Y29uc3RydWN0b3IodD1bXSl7c3VwZXIoKSx0aGlzLmNhbWVyYXM9dH19O2hsLnByb3RvdHlwZS5pc0FycmF5Q2FtZXJhPSEwO3ZhciBCaT1jbGFzcyBleHRlbmRzIGt0e2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLnR5cGU9Ikdyb3VwIn19O0JpLnByb3RvdHlwZS5pc0dyb3VwPSEwO3ZhciBQRT17dHlwZToibW92ZSJ9LFFzPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5fdGFyZ2V0UmF5PW51bGwsdGhpcy5fZ3JpcD1udWxsLHRoaXMuX2hhbmQ9bnVsbH1nZXRIYW5kU3BhY2UoKXtyZXR1cm4gdGhpcy5faGFuZD09PW51bGwmJih0aGlzLl9oYW5kPW5ldyBCaSx0aGlzLl9oYW5kLm1hdHJpeEF1dG9VcGRhdGU9ITEsdGhpcy5faGFuZC52aXNpYmxlPSExLHRoaXMuX2hhbmQuam9pbnRzPXt9LHRoaXMuX2hhbmQuaW5wdXRTdGF0ZT17cGluY2hpbmc6ITF9KSx0aGlzLl9oYW5kfWdldFRhcmdldFJheVNwYWNlKCl7cmV0dXJuIHRoaXMuX3RhcmdldFJheT09PW51bGwmJih0aGlzLl90YXJnZXRSYXk9bmV3IEJpLHRoaXMuX3RhcmdldFJheS5tYXRyaXhBdXRvVXBkYXRlPSExLHRoaXMuX3RhcmdldFJheS52aXNpYmxlPSExLHRoaXMuX3RhcmdldFJheS5oYXNMaW5lYXJWZWxvY2l0eT0hMSx0aGlzLl90YXJnZXRSYXkubGluZWFyVmVsb2NpdHk9bmV3IFQsdGhpcy5fdGFyZ2V0UmF5Lmhhc0FuZ3VsYXJWZWxvY2l0eT0hMSx0aGlzLl90YXJnZXRSYXkuYW5ndWxhclZlbG9jaXR5PW5ldyBUKSx0aGlzLl90YXJnZXRSYXl9Z2V0R3JpcFNwYWNlKCl7cmV0dXJuIHRoaXMuX2dyaXA9PT1udWxsJiYodGhpcy5fZ3JpcD1uZXcgQmksdGhpcy5fZ3JpcC5tYXRyaXhBdXRvVXBkYXRlPSExLHRoaXMuX2dyaXAudmlzaWJsZT0hMSx0aGlzLl9ncmlwLmhhc0xpbmVhclZlbG9jaXR5PSExLHRoaXMuX2dyaXAubGluZWFyVmVsb2NpdHk9bmV3IFQsdGhpcy5fZ3JpcC5oYXNBbmd1bGFyVmVsb2NpdHk9ITEsdGhpcy5fZ3JpcC5hbmd1bGFyVmVsb2NpdHk9bmV3IFQpLHRoaXMuX2dyaXB9ZGlzcGF0Y2hFdmVudCh0KXtyZXR1cm4gdGhpcy5fdGFyZ2V0UmF5IT09bnVsbCYmdGhpcy5fdGFyZ2V0UmF5LmRpc3BhdGNoRXZlbnQodCksdGhpcy5fZ3JpcCE9PW51bGwmJnRoaXMuX2dyaXAuZGlzcGF0Y2hFdmVudCh0KSx0aGlzLl9oYW5kIT09bnVsbCYmdGhpcy5faGFuZC5kaXNwYXRjaEV2ZW50KHQpLHRoaXN9ZGlzY29ubmVjdCh0KXtyZXR1cm4gdGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJkaXNjb25uZWN0ZWQiLGRhdGE6dH0pLHRoaXMuX3RhcmdldFJheSE9PW51bGwmJih0aGlzLl90YXJnZXRSYXkudmlzaWJsZT0hMSksdGhpcy5fZ3JpcCE9PW51bGwmJih0aGlzLl9ncmlwLnZpc2libGU9ITEpLHRoaXMuX2hhbmQhPT1udWxsJiYodGhpcy5faGFuZC52aXNpYmxlPSExKSx0aGlzfXVwZGF0ZSh0LGUsaSl7bGV0IHI9bnVsbCxzPW51bGwsbz1udWxsLGE9dGhpcy5fdGFyZ2V0UmF5LGw9dGhpcy5fZ3JpcCxjPXRoaXMuX2hhbmQ7aWYodCYmZS5zZXNzaW9uLnZpc2liaWxpdHlTdGF0ZSE9PSJ2aXNpYmxlLWJsdXJyZWQiKWlmKGEhPT1udWxsJiYocj1lLmdldFBvc2UodC50YXJnZXRSYXlTcGFjZSxpKSxyIT09bnVsbCYmKGEubWF0cml4LmZyb21BcnJheShyLnRyYW5zZm9ybS5tYXRyaXgpLGEubWF0cml4LmRlY29tcG9zZShhLnBvc2l0aW9uLGEucm90YXRpb24sYS5zY2FsZSksci5saW5lYXJWZWxvY2l0eT8oYS5oYXNMaW5lYXJWZWxvY2l0eT0hMCxhLmxpbmVhclZlbG9jaXR5LmNvcHkoci5saW5lYXJWZWxvY2l0eSkpOmEuaGFzTGluZWFyVmVsb2NpdHk9ITEsci5hbmd1bGFyVmVsb2NpdHk/KGEuaGFzQW5ndWxhclZlbG9jaXR5PSEwLGEuYW5ndWxhclZlbG9jaXR5LmNvcHkoci5hbmd1bGFyVmVsb2NpdHkpKTphLmhhc0FuZ3VsYXJWZWxvY2l0eT0hMSx0aGlzLmRpc3BhdGNoRXZlbnQoUEUpKSksYyYmdC5oYW5kKXtvPSEwO2ZvcihsZXQgeCBvZiB0LmhhbmQudmFsdWVzKCkpe2xldCB2PWUuZ2V0Sm9pbnRQb3NlKHgsaSk7aWYoYy5qb2ludHNbeC5qb2ludE5hbWVdPT09dm9pZCAwKXtsZXQgcD1uZXcgQmk7cC5tYXRyaXhBdXRvVXBkYXRlPSExLHAudmlzaWJsZT0hMSxjLmpvaW50c1t4LmpvaW50TmFtZV09cCxjLmFkZChwKX1sZXQgbT1jLmpvaW50c1t4LmpvaW50TmFtZV07diE9PW51bGwmJihtLm1hdHJpeC5mcm9tQXJyYXkodi50cmFuc2Zvcm0ubWF0cml4KSxtLm1hdHJpeC5kZWNvbXBvc2UobS5wb3NpdGlvbixtLnJvdGF0aW9uLG0uc2NhbGUpLG0uam9pbnRSYWRpdXM9di5yYWRpdXMpLG0udmlzaWJsZT12IT09bnVsbH1sZXQgdT1jLmpvaW50c1siaW5kZXgtZmluZ2VyLXRpcCJdLGg9Yy5qb2ludHNbInRodW1iLXRpcCJdLGY9dS5wb3NpdGlvbi5kaXN0YW5jZVRvKGgucG9zaXRpb24pLGQ9LjAyLGc9LjAwNTtjLmlucHV0U3RhdGUucGluY2hpbmcmJmY+ZCtnPyhjLmlucHV0U3RhdGUucGluY2hpbmc9ITEsdGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJwaW5jaGVuZCIsaGFuZGVkbmVzczp0LmhhbmRlZG5lc3MsdGFyZ2V0OnRoaXN9KSk6IWMuaW5wdXRTdGF0ZS5waW5jaGluZyYmZjw9ZC1nJiYoYy5pbnB1dFN0YXRlLnBpbmNoaW5nPSEwLHRoaXMuZGlzcGF0Y2hFdmVudCh7dHlwZToicGluY2hzdGFydCIsaGFuZGVkbmVzczp0LmhhbmRlZG5lc3MsdGFyZ2V0OnRoaXN9KSl9ZWxzZSBsIT09bnVsbCYmdC5ncmlwU3BhY2UmJihzPWUuZ2V0UG9zZSh0LmdyaXBTcGFjZSxpKSxzIT09bnVsbCYmKGwubWF0cml4LmZyb21BcnJheShzLnRyYW5zZm9ybS5tYXRyaXgpLGwubWF0cml4LmRlY29tcG9zZShsLnBvc2l0aW9uLGwucm90YXRpb24sbC5zY2FsZSkscy5saW5lYXJWZWxvY2l0eT8obC5oYXNMaW5lYXJWZWxvY2l0eT0hMCxsLmxpbmVhclZlbG9jaXR5LmNvcHkocy5saW5lYXJWZWxvY2l0eSkpOmwuaGFzTGluZWFyVmVsb2NpdHk9ITEscy5hbmd1bGFyVmVsb2NpdHk/KGwuaGFzQW5ndWxhclZlbG9jaXR5PSEwLGwuYW5ndWxhclZlbG9jaXR5LmNvcHkocy5hbmd1bGFyVmVsb2NpdHkpKTpsLmhhc0FuZ3VsYXJWZWxvY2l0eT0hMSkpO3JldHVybiBhIT09bnVsbCYmKGEudmlzaWJsZT1yIT09bnVsbCksbCE9PW51bGwmJihsLnZpc2libGU9cyE9PW51bGwpLGMhPT1udWxsJiYoYy52aXNpYmxlPW8hPT1udWxsKSx0aGlzfX0sdW89Y2xhc3MgZXh0ZW5kcyBhZXtjb25zdHJ1Y3Rvcih0LGUsaSxyLHMsbyxhLGwsYyx1KXtpZih1PXUhPT12b2lkIDA/dTpPaSx1IT09T2kmJnUhPT1Wcil0aHJvdyBuZXcgRXJyb3IoIkRlcHRoVGV4dHVyZSBmb3JtYXQgbXVzdCBiZSBlaXRoZXIgVEhSRUUuRGVwdGhGb3JtYXQgb3IgVEhSRUUuRGVwdGhTdGVuY2lsRm9ybWF0Iik7aT09PXZvaWQgMCYmdT09PU9pJiYoaT1ubyksaT09PXZvaWQgMCYmdT09PVZyJiYoaT1Cciksc3VwZXIobnVsbCxyLHMsbyxhLGwsdSxpLGMpLHRoaXMuaW1hZ2U9e3dpZHRoOnQsaGVpZ2h0OmV9LHRoaXMubWFnRmlsdGVyPWEhPT12b2lkIDA/YTpmZSx0aGlzLm1pbkZpbHRlcj1sIT09dm9pZCAwP2w6ZmUsdGhpcy5mbGlwWT0hMSx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMX19O3VvLnByb3RvdHlwZS5pc0RlcHRoVGV4dHVyZT0hMDt2YXIgWnU9Y2xhc3MgZXh0ZW5kcyBJbntjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKCk7bGV0IGk9dGhpcyxyPW51bGwscz0xLG89bnVsbCxhPSJsb2NhbC1mbG9vciIsbD10LmV4dGVuc2lvbnMuaGFzKCJXRUJHTF9tdWx0aXNhbXBsZWRfcmVuZGVyX3RvX3RleHR1cmUiKSxjPW51bGwsdT1udWxsLGg9bnVsbCxmPW51bGwsZD0hMSxnPW51bGwseD1lLmdldENvbnRleHRBdHRyaWJ1dGVzKCksdj1udWxsLG09bnVsbCxwPVtdLGI9bmV3IE1hcCxfPW5ldyBTZTtfLmxheWVycy5lbmFibGUoMSksXy52aWV3cG9ydD1uZXcgV3Q7bGV0IFM9bmV3IFNlO1MubGF5ZXJzLmVuYWJsZSgyKSxTLnZpZXdwb3J0PW5ldyBXdDtsZXQgTD1bXyxTXSxBPW5ldyBobDtBLmxheWVycy5lbmFibGUoMSksQS5sYXllcnMuZW5hYmxlKDIpO2xldCBIPW51bGwsdHQ9bnVsbDt0aGlzLmNhbWVyYUF1dG9VcGRhdGU9ITAsdGhpcy5lbmFibGVkPSExLHRoaXMuaXNQcmVzZW50aW5nPSExLHRoaXMuZ2V0Q29udHJvbGxlcj1mdW5jdGlvbihHKXtsZXQgJD1wW0ddO3JldHVybiAkPT09dm9pZCAwJiYoJD1uZXcgUXMscFtHXT0kKSwkLmdldFRhcmdldFJheVNwYWNlKCl9LHRoaXMuZ2V0Q29udHJvbGxlckdyaXA9ZnVuY3Rpb24oRyl7bGV0ICQ9cFtHXTtyZXR1cm4gJD09PXZvaWQgMCYmKCQ9bmV3IFFzLHBbR109JCksJC5nZXRHcmlwU3BhY2UoKX0sdGhpcy5nZXRIYW5kPWZ1bmN0aW9uKEcpe2xldCAkPXBbR107cmV0dXJuICQ9PT12b2lkIDAmJigkPW5ldyBRcyxwW0ddPSQpLCQuZ2V0SGFuZFNwYWNlKCl9O2Z1bmN0aW9uIFgoRyl7bGV0ICQ9Yi5nZXQoRy5pbnB1dFNvdXJjZSk7JCYmJC5kaXNwYXRjaEV2ZW50KHt0eXBlOkcudHlwZSxkYXRhOkcuaW5wdXRTb3VyY2V9KX1mdW5jdGlvbiB5KCl7Yi5mb3JFYWNoKGZ1bmN0aW9uKEcsJCl7Ry5kaXNjb25uZWN0KCQpfSksYi5jbGVhcigpLEg9bnVsbCx0dD1udWxsLHQuc2V0UmVuZGVyVGFyZ2V0KHYpLGY9bnVsbCxoPW51bGwsdT1udWxsLHI9bnVsbCxtPW51bGwsYXQuc3RvcCgpLGkuaXNQcmVzZW50aW5nPSExLGkuZGlzcGF0Y2hFdmVudCh7dHlwZToic2Vzc2lvbmVuZCJ9KX10aGlzLnNldEZyYW1lYnVmZmVyU2NhbGVGYWN0b3I9ZnVuY3Rpb24oRyl7cz1HLGkuaXNQcmVzZW50aW5nPT09ITAmJmNvbnNvbGUud2FybigiVEhSRUUuV2ViWFJNYW5hZ2VyOiBDYW5ub3QgY2hhbmdlIGZyYW1lYnVmZmVyIHNjYWxlIHdoaWxlIHByZXNlbnRpbmcuIil9LHRoaXMuc2V0UmVmZXJlbmNlU3BhY2VUeXBlPWZ1bmN0aW9uKEcpe2E9RyxpLmlzUHJlc2VudGluZz09PSEwJiZjb25zb2xlLndhcm4oIlRIUkVFLldlYlhSTWFuYWdlcjogQ2Fubm90IGNoYW5nZSByZWZlcmVuY2Ugc3BhY2UgdHlwZSB3aGlsZSBwcmVzZW50aW5nLiIpfSx0aGlzLmdldFJlZmVyZW5jZVNwYWNlPWZ1bmN0aW9uKCl7cmV0dXJuIG99LHRoaXMuZ2V0QmFzZUxheWVyPWZ1bmN0aW9uKCl7cmV0dXJuIGghPT1udWxsP2g6Zn0sdGhpcy5nZXRCaW5kaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHV9LHRoaXMuZ2V0RnJhbWU9ZnVuY3Rpb24oKXtyZXR1cm4gZ30sdGhpcy5nZXRTZXNzaW9uPWZ1bmN0aW9uKCl7cmV0dXJuIHJ9LHRoaXMuc2V0U2Vzc2lvbj1mdW5jdGlvbihHKXtyZXR1cm4gaGYodGhpcyxudWxsLGZ1bmN0aW9uKigpe2lmKHI9RyxyIT09bnVsbCl7aWYodj10LmdldFJlbmRlclRhcmdldCgpLHIuYWRkRXZlbnRMaXN0ZW5lcigic2VsZWN0IixYKSxyLmFkZEV2ZW50TGlzdGVuZXIoInNlbGVjdHN0YXJ0IixYKSxyLmFkZEV2ZW50TGlzdGVuZXIoInNlbGVjdGVuZCIsWCksci5hZGRFdmVudExpc3RlbmVyKCJzcXVlZXplIixYKSxyLmFkZEV2ZW50TGlzdGVuZXIoInNxdWVlemVzdGFydCIsWCksci5hZGRFdmVudExpc3RlbmVyKCJzcXVlZXplZW5kIixYKSxyLmFkZEV2ZW50TGlzdGVuZXIoImVuZCIseSksci5hZGRFdmVudExpc3RlbmVyKCJpbnB1dHNvdXJjZXNjaGFuZ2UiLFIpLHgueHJDb21wYXRpYmxlIT09ITAmJih5aWVsZCBlLm1ha2VYUkNvbXBhdGlibGUoKSksci5yZW5kZXJTdGF0ZS5sYXllcnM9PT12b2lkIDB8fHQuY2FwYWJpbGl0aWVzLmlzV2ViR0wyPT09ITEpe2xldCAkPXthbnRpYWxpYXM6ci5yZW5kZXJTdGF0ZS5sYXllcnM9PT12b2lkIDA/eC5hbnRpYWxpYXM6ITAsYWxwaGE6eC5hbHBoYSxkZXB0aDp4LmRlcHRoLHN0ZW5jaWw6eC5zdGVuY2lsLGZyYW1lYnVmZmVyU2NhbGVGYWN0b3I6c307Zj1uZXcgWFJXZWJHTExheWVyKHIsZSwkKSxyLnVwZGF0ZVJlbmRlclN0YXRlKHtiYXNlTGF5ZXI6Zn0pLG09bmV3IE5lKGYuZnJhbWVidWZmZXJXaWR0aCxmLmZyYW1lYnVmZmVySGVpZ2h0LHtmb3JtYXQ6UmUsdHlwZTplaSxlbmNvZGluZzp0Lm91dHB1dEVuY29kaW5nfSl9ZWxzZXtkPXguYW50aWFsaWFzO2xldCAkPW51bGwsbHQ9bnVsbCxkdD1udWxsO3guZGVwdGgmJihkdD14LnN0ZW5jaWw/MzUwNTY6MzMxOTAsJD14LnN0ZW5jaWw/VnI6T2ksbHQ9eC5zdGVuY2lsP0JyOm5vKTtsZXQgeHQ9e2NvbG9yRm9ybWF0OnQub3V0cHV0RW5jb2Rpbmc9PT0kdD8zNTkwNzozMjg1NixkZXB0aEZvcm1hdDpkdCxzY2FsZUZhY3RvcjpzfTt1PW5ldyBYUldlYkdMQmluZGluZyhyLGUpLGg9dS5jcmVhdGVQcm9qZWN0aW9uTGF5ZXIoeHQpLHIudXBkYXRlUmVuZGVyU3RhdGUoe2xheWVyczpbaF19KSxkP209bmV3IHNvKGgudGV4dHVyZVdpZHRoLGgudGV4dHVyZUhlaWdodCx7Zm9ybWF0OlJlLHR5cGU6ZWksZGVwdGhUZXh0dXJlOm5ldyB1byhoLnRleHR1cmVXaWR0aCxoLnRleHR1cmVIZWlnaHQsbHQsdm9pZCAwLHZvaWQgMCx2b2lkIDAsdm9pZCAwLHZvaWQgMCx2b2lkIDAsJCksc3RlbmNpbEJ1ZmZlcjp4LnN0ZW5jaWwsaWdub3JlRGVwdGg6aC5pZ25vcmVEZXB0aFZhbHVlcyx1c2VSZW5kZXJUb1RleHR1cmU6bCxlbmNvZGluZzp0Lm91dHB1dEVuY29kaW5nfSk6bT1uZXcgTmUoaC50ZXh0dXJlV2lkdGgsaC50ZXh0dXJlSGVpZ2h0LHtmb3JtYXQ6UmUsdHlwZTplaSxkZXB0aFRleHR1cmU6bmV3IHVvKGgudGV4dHVyZVdpZHRoLGgudGV4dHVyZUhlaWdodCxsdCx2b2lkIDAsdm9pZCAwLHZvaWQgMCx2b2lkIDAsdm9pZCAwLHZvaWQgMCwkKSxzdGVuY2lsQnVmZmVyOnguc3RlbmNpbCxpZ25vcmVEZXB0aDpoLmlnbm9yZURlcHRoVmFsdWVzLGVuY29kaW5nOnQub3V0cHV0RW5jb2Rpbmd9KX1tLmlzWFJSZW5kZXJUYXJnZXQ9ITAsdGhpcy5zZXRGb3ZlYXRpb24oMSksbz15aWVsZCByLnJlcXVlc3RSZWZlcmVuY2VTcGFjZShhKSxhdC5zZXRDb250ZXh0KHIpLGF0LnN0YXJ0KCksaS5pc1ByZXNlbnRpbmc9ITAsaS5kaXNwYXRjaEV2ZW50KHt0eXBlOiJzZXNzaW9uc3RhcnQifSl9fSl9O2Z1bmN0aW9uIFIoRyl7bGV0ICQ9ci5pbnB1dFNvdXJjZXM7Zm9yKGxldCBsdD0wO2x0PHAubGVuZ3RoO2x0KyspYi5zZXQoJFtsdF0scFtsdF0pO2ZvcihsZXQgbHQ9MDtsdDxHLnJlbW92ZWQubGVuZ3RoO2x0Kyspe2xldCBkdD1HLnJlbW92ZWRbbHRdLHh0PWIuZ2V0KGR0KTt4dCYmKHh0LmRpc3BhdGNoRXZlbnQoe3R5cGU6ImRpc2Nvbm5lY3RlZCIsZGF0YTpkdH0pLGIuZGVsZXRlKGR0KSl9Zm9yKGxldCBsdD0wO2x0PEcuYWRkZWQubGVuZ3RoO2x0Kyspe2xldCBkdD1HLmFkZGVkW2x0XSx4dD1iLmdldChkdCk7eHQmJnh0LmRpc3BhdGNoRXZlbnQoe3R5cGU6ImNvbm5lY3RlZCIsZGF0YTpkdH0pfX1sZXQgRD1uZXcgVCxGPW5ldyBUO2Z1bmN0aW9uIHooRywkLGx0KXtELnNldEZyb21NYXRyaXhQb3NpdGlvbigkLm1hdHJpeFdvcmxkKSxGLnNldEZyb21NYXRyaXhQb3NpdGlvbihsdC5tYXRyaXhXb3JsZCk7bGV0IGR0PUQuZGlzdGFuY2VUbyhGKSx4dD0kLnByb2plY3Rpb25NYXRyaXguZWxlbWVudHMsaz1sdC5wcm9qZWN0aW9uTWF0cml4LmVsZW1lbnRzLEZ0PXh0WzE0XS8oeHRbMTBdLTEpLG10PXh0WzE0XS8oeHRbMTBdKzEpLFN0PSh4dFs5XSsxKS94dFs1XSxCPSh4dFs5XS0xKS94dFs1XSxzdD0oeHRbOF0tMSkveHRbMF0sbnQ9KGtbOF0rMSkva1swXSxDPUZ0KnN0LGo9RnQqbnQsSj1kdC8oLXN0K250KSxpdD1KKi1zdDskLm1hdHJpeFdvcmxkLmRlY29tcG9zZShHLnBvc2l0aW9uLEcucXVhdGVybmlvbixHLnNjYWxlKSxHLnRyYW5zbGF0ZVgoaXQpLEcudHJhbnNsYXRlWihKKSxHLm1hdHJpeFdvcmxkLmNvbXBvc2UoRy5wb3NpdGlvbixHLnF1YXRlcm5pb24sRy5zY2FsZSksRy5tYXRyaXhXb3JsZEludmVyc2UuY29weShHLm1hdHJpeFdvcmxkKS5pbnZlcnQoKTtsZXQgZXQ9RnQrSix2dD1tdCtKLGJ0PUMtaXQsSXQ9aisoZHQtaXQpLFp0PVN0Km10L3Z0KmV0LHF0PUIqbXQvdnQqZXQ7Ry5wcm9qZWN0aW9uTWF0cml4Lm1ha2VQZXJzcGVjdGl2ZShidCxJdCxadCxxdCxldCx2dCl9ZnVuY3Rpb24gTihHLCQpeyQ9PT1udWxsP0cubWF0cml4V29ybGQuY29weShHLm1hdHJpeCk6Ry5tYXRyaXhXb3JsZC5tdWx0aXBseU1hdHJpY2VzKCQubWF0cml4V29ybGQsRy5tYXRyaXgpLEcubWF0cml4V29ybGRJbnZlcnNlLmNvcHkoRy5tYXRyaXhXb3JsZCkuaW52ZXJ0KCl9dGhpcy51cGRhdGVDYW1lcmE9ZnVuY3Rpb24oRyl7aWYocj09PW51bGwpcmV0dXJuO0EubmVhcj1TLm5lYXI9Xy5uZWFyPUcubmVhcixBLmZhcj1TLmZhcj1fLmZhcj1HLmZhciwoSCE9PUEubmVhcnx8dHQhPT1BLmZhcikmJihyLnVwZGF0ZVJlbmRlclN0YXRlKHtkZXB0aE5lYXI6QS5uZWFyLGRlcHRoRmFyOkEuZmFyfSksSD1BLm5lYXIsdHQ9QS5mYXIpO2xldCAkPUcucGFyZW50LGx0PUEuY2FtZXJhcztOKEEsJCk7Zm9yKGxldCB4dD0wO3h0PGx0Lmxlbmd0aDt4dCsrKU4obHRbeHRdLCQpO0EubWF0cml4V29ybGQuZGVjb21wb3NlKEEucG9zaXRpb24sQS5xdWF0ZXJuaW9uLEEuc2NhbGUpLEcucG9zaXRpb24uY29weShBLnBvc2l0aW9uKSxHLnF1YXRlcm5pb24uY29weShBLnF1YXRlcm5pb24pLEcuc2NhbGUuY29weShBLnNjYWxlKSxHLm1hdHJpeC5jb3B5KEEubWF0cml4KSxHLm1hdHJpeFdvcmxkLmNvcHkoQS5tYXRyaXhXb3JsZCk7bGV0IGR0PUcuY2hpbGRyZW47Zm9yKGxldCB4dD0wLGs9ZHQubGVuZ3RoO3h0PGs7eHQrKylkdFt4dF0udXBkYXRlTWF0cml4V29ybGQoITApO2x0Lmxlbmd0aD09PTI/eihBLF8sUyk6QS5wcm9qZWN0aW9uTWF0cml4LmNvcHkoXy5wcm9qZWN0aW9uTWF0cml4KX0sdGhpcy5nZXRDYW1lcmE9ZnVuY3Rpb24oKXtyZXR1cm4gQX0sdGhpcy5nZXRGb3ZlYXRpb249ZnVuY3Rpb24oKXtpZihoIT09bnVsbClyZXR1cm4gaC5maXhlZEZvdmVhdGlvbjtpZihmIT09bnVsbClyZXR1cm4gZi5maXhlZEZvdmVhdGlvbn0sdGhpcy5zZXRGb3ZlYXRpb249ZnVuY3Rpb24oRyl7aCE9PW51bGwmJihoLmZpeGVkRm92ZWF0aW9uPUcpLGYhPT1udWxsJiZmLmZpeGVkRm92ZWF0aW9uIT09dm9pZCAwJiYoZi5maXhlZEZvdmVhdGlvbj1HKX07bGV0IFY9bnVsbDtmdW5jdGlvbiBRKEcsJCl7aWYoYz0kLmdldFZpZXdlclBvc2UobyksZz0kLGMhPT1udWxsKXtsZXQgZHQ9Yy52aWV3cztmIT09bnVsbCYmKHQuc2V0UmVuZGVyVGFyZ2V0RnJhbWVidWZmZXIobSxmLmZyYW1lYnVmZmVyKSx0LnNldFJlbmRlclRhcmdldChtKSk7bGV0IHh0PSExO2R0Lmxlbmd0aCE9PUEuY2FtZXJhcy5sZW5ndGgmJihBLmNhbWVyYXMubGVuZ3RoPTAseHQ9ITApO2ZvcihsZXQgaz0wO2s8ZHQubGVuZ3RoO2srKyl7bGV0IEZ0PWR0W2tdLG10PW51bGw7aWYoZiE9PW51bGwpbXQ9Zi5nZXRWaWV3cG9ydChGdCk7ZWxzZXtsZXQgQj11LmdldFZpZXdTdWJJbWFnZShoLEZ0KTttdD1CLnZpZXdwb3J0LGs9PT0wJiYodC5zZXRSZW5kZXJUYXJnZXRUZXh0dXJlcyhtLEIuY29sb3JUZXh0dXJlLGguaWdub3JlRGVwdGhWYWx1ZXM/dm9pZCAwOkIuZGVwdGhTdGVuY2lsVGV4dHVyZSksdC5zZXRSZW5kZXJUYXJnZXQobSkpfWxldCBTdD1MW2tdO1N0Lm1hdHJpeC5mcm9tQXJyYXkoRnQudHJhbnNmb3JtLm1hdHJpeCksU3QucHJvamVjdGlvbk1hdHJpeC5mcm9tQXJyYXkoRnQucHJvamVjdGlvbk1hdHJpeCksU3Qudmlld3BvcnQuc2V0KG10LngsbXQueSxtdC53aWR0aCxtdC5oZWlnaHQpLGs9PT0wJiZBLm1hdHJpeC5jb3B5KFN0Lm1hdHJpeCkseHQ9PT0hMCYmQS5jYW1lcmFzLnB1c2goU3QpfX1sZXQgbHQ9ci5pbnB1dFNvdXJjZXM7Zm9yKGxldCBkdD0wO2R0PHAubGVuZ3RoO2R0Kyspe2xldCB4dD1wW2R0XSxrPWx0W2R0XTt4dC51cGRhdGUoaywkLG8pfVYmJlYoRywkKSxnPW51bGx9bGV0IGF0PW5ldyBnMDthdC5zZXRBbmltYXRpb25Mb29wKFEpLHRoaXMuc2V0QW5pbWF0aW9uTG9vcD1mdW5jdGlvbihHKXtWPUd9LHRoaXMuZGlzcG9zZT1mdW5jdGlvbigpe319fTtmdW5jdGlvbiBERShuKXtmdW5jdGlvbiB0KG0scCl7bS5mb2dDb2xvci52YWx1ZS5jb3B5KHAuY29sb3IpLHAuaXNGb2c/KG0uZm9nTmVhci52YWx1ZT1wLm5lYXIsbS5mb2dGYXIudmFsdWU9cC5mYXIpOnAuaXNGb2dFeHAyJiYobS5mb2dEZW5zaXR5LnZhbHVlPXAuZGVuc2l0eSl9ZnVuY3Rpb24gZShtLHAsYixfLFMpe3AuaXNNZXNoQmFzaWNNYXRlcmlhbD9pKG0scCk6cC5pc01lc2hMYW1iZXJ0TWF0ZXJpYWw/KGkobSxwKSxsKG0scCkpOnAuaXNNZXNoVG9vbk1hdGVyaWFsPyhpKG0scCksdShtLHApKTpwLmlzTWVzaFBob25nTWF0ZXJpYWw/KGkobSxwKSxjKG0scCkpOnAuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbD8oaShtLHApLHAuaXNNZXNoUGh5c2ljYWxNYXRlcmlhbD9mKG0scCxTKTpoKG0scCkpOnAuaXNNZXNoTWF0Y2FwTWF0ZXJpYWw/KGkobSxwKSxkKG0scCkpOnAuaXNNZXNoRGVwdGhNYXRlcmlhbD8oaShtLHApLGcobSxwKSk6cC5pc01lc2hEaXN0YW5jZU1hdGVyaWFsPyhpKG0scCkseChtLHApKTpwLmlzTWVzaE5vcm1hbE1hdGVyaWFsPyhpKG0scCksdihtLHApKTpwLmlzTGluZUJhc2ljTWF0ZXJpYWw/KHIobSxwKSxwLmlzTGluZURhc2hlZE1hdGVyaWFsJiZzKG0scCkpOnAuaXNQb2ludHNNYXRlcmlhbD9vKG0scCxiLF8pOnAuaXNTcHJpdGVNYXRlcmlhbD9hKG0scCk6cC5pc1NoYWRvd01hdGVyaWFsPyhtLmNvbG9yLnZhbHVlLmNvcHkocC5jb2xvciksbS5vcGFjaXR5LnZhbHVlPXAub3BhY2l0eSk6cC5pc1NoYWRlck1hdGVyaWFsJiYocC51bmlmb3Jtc05lZWRVcGRhdGU9ITEpfWZ1bmN0aW9uIGkobSxwKXttLm9wYWNpdHkudmFsdWU9cC5vcGFjaXR5LHAuY29sb3ImJm0uZGlmZnVzZS52YWx1ZS5jb3B5KHAuY29sb3IpLHAuZW1pc3NpdmUmJm0uZW1pc3NpdmUudmFsdWUuY29weShwLmVtaXNzaXZlKS5tdWx0aXBseVNjYWxhcihwLmVtaXNzaXZlSW50ZW5zaXR5KSxwLm1hcCYmKG0ubWFwLnZhbHVlPXAubWFwKSxwLmFscGhhTWFwJiYobS5hbHBoYU1hcC52YWx1ZT1wLmFscGhhTWFwKSxwLnNwZWN1bGFyTWFwJiYobS5zcGVjdWxhck1hcC52YWx1ZT1wLnNwZWN1bGFyTWFwKSxwLmFscGhhVGVzdD4wJiYobS5hbHBoYVRlc3QudmFsdWU9cC5hbHBoYVRlc3QpO2xldCBiPW4uZ2V0KHApLmVudk1hcDtiJiYobS5lbnZNYXAudmFsdWU9YixtLmZsaXBFbnZNYXAudmFsdWU9Yi5pc0N1YmVUZXh0dXJlJiZiLmlzUmVuZGVyVGFyZ2V0VGV4dHVyZT09PSExPy0xOjEsbS5yZWZsZWN0aXZpdHkudmFsdWU9cC5yZWZsZWN0aXZpdHksbS5pb3IudmFsdWU9cC5pb3IsbS5yZWZyYWN0aW9uUmF0aW8udmFsdWU9cC5yZWZyYWN0aW9uUmF0aW8pLHAubGlnaHRNYXAmJihtLmxpZ2h0TWFwLnZhbHVlPXAubGlnaHRNYXAsbS5saWdodE1hcEludGVuc2l0eS52YWx1ZT1wLmxpZ2h0TWFwSW50ZW5zaXR5KSxwLmFvTWFwJiYobS5hb01hcC52YWx1ZT1wLmFvTWFwLG0uYW9NYXBJbnRlbnNpdHkudmFsdWU9cC5hb01hcEludGVuc2l0eSk7bGV0IF87cC5tYXA/Xz1wLm1hcDpwLnNwZWN1bGFyTWFwP189cC5zcGVjdWxhck1hcDpwLmRpc3BsYWNlbWVudE1hcD9fPXAuZGlzcGxhY2VtZW50TWFwOnAubm9ybWFsTWFwP189cC5ub3JtYWxNYXA6cC5idW1wTWFwP189cC5idW1wTWFwOnAucm91Z2huZXNzTWFwP189cC5yb3VnaG5lc3NNYXA6cC5tZXRhbG5lc3NNYXA/Xz1wLm1ldGFsbmVzc01hcDpwLmFscGhhTWFwP189cC5hbHBoYU1hcDpwLmVtaXNzaXZlTWFwP189cC5lbWlzc2l2ZU1hcDpwLmNsZWFyY29hdE1hcD9fPXAuY2xlYXJjb2F0TWFwOnAuY2xlYXJjb2F0Tm9ybWFsTWFwP189cC5jbGVhcmNvYXROb3JtYWxNYXA6cC5jbGVhcmNvYXRSb3VnaG5lc3NNYXA/Xz1wLmNsZWFyY29hdFJvdWdobmVzc01hcDpwLnNwZWN1bGFySW50ZW5zaXR5TWFwP189cC5zcGVjdWxhckludGVuc2l0eU1hcDpwLnNwZWN1bGFyQ29sb3JNYXA/Xz1wLnNwZWN1bGFyQ29sb3JNYXA6cC50cmFuc21pc3Npb25NYXA/Xz1wLnRyYW5zbWlzc2lvbk1hcDpwLnRoaWNrbmVzc01hcD9fPXAudGhpY2tuZXNzTWFwOnAuc2hlZW5Db2xvck1hcD9fPXAuc2hlZW5Db2xvck1hcDpwLnNoZWVuUm91Z2huZXNzTWFwJiYoXz1wLnNoZWVuUm91Z2huZXNzTWFwKSxfIT09dm9pZCAwJiYoXy5pc1dlYkdMUmVuZGVyVGFyZ2V0JiYoXz1fLnRleHR1cmUpLF8ubWF0cml4QXV0b1VwZGF0ZT09PSEwJiZfLnVwZGF0ZU1hdHJpeCgpLG0udXZUcmFuc2Zvcm0udmFsdWUuY29weShfLm1hdHJpeCkpO2xldCBTO3AuYW9NYXA/Uz1wLmFvTWFwOnAubGlnaHRNYXAmJihTPXAubGlnaHRNYXApLFMhPT12b2lkIDAmJihTLmlzV2ViR0xSZW5kZXJUYXJnZXQmJihTPVMudGV4dHVyZSksUy5tYXRyaXhBdXRvVXBkYXRlPT09ITAmJlMudXBkYXRlTWF0cml4KCksbS51djJUcmFuc2Zvcm0udmFsdWUuY29weShTLm1hdHJpeCkpfWZ1bmN0aW9uIHIobSxwKXttLmRpZmZ1c2UudmFsdWUuY29weShwLmNvbG9yKSxtLm9wYWNpdHkudmFsdWU9cC5vcGFjaXR5fWZ1bmN0aW9uIHMobSxwKXttLmRhc2hTaXplLnZhbHVlPXAuZGFzaFNpemUsbS50b3RhbFNpemUudmFsdWU9cC5kYXNoU2l6ZStwLmdhcFNpemUsbS5zY2FsZS52YWx1ZT1wLnNjYWxlfWZ1bmN0aW9uIG8obSxwLGIsXyl7bS5kaWZmdXNlLnZhbHVlLmNvcHkocC5jb2xvciksbS5vcGFjaXR5LnZhbHVlPXAub3BhY2l0eSxtLnNpemUudmFsdWU9cC5zaXplKmIsbS5zY2FsZS52YWx1ZT1fKi41LHAubWFwJiYobS5tYXAudmFsdWU9cC5tYXApLHAuYWxwaGFNYXAmJihtLmFscGhhTWFwLnZhbHVlPXAuYWxwaGFNYXApLHAuYWxwaGFUZXN0PjAmJihtLmFscGhhVGVzdC52YWx1ZT1wLmFscGhhVGVzdCk7bGV0IFM7cC5tYXA/Uz1wLm1hcDpwLmFscGhhTWFwJiYoUz1wLmFscGhhTWFwKSxTIT09dm9pZCAwJiYoUy5tYXRyaXhBdXRvVXBkYXRlPT09ITAmJlMudXBkYXRlTWF0cml4KCksbS51dlRyYW5zZm9ybS52YWx1ZS5jb3B5KFMubWF0cml4KSl9ZnVuY3Rpb24gYShtLHApe20uZGlmZnVzZS52YWx1ZS5jb3B5KHAuY29sb3IpLG0ub3BhY2l0eS52YWx1ZT1wLm9wYWNpdHksbS5yb3RhdGlvbi52YWx1ZT1wLnJvdGF0aW9uLHAubWFwJiYobS5tYXAudmFsdWU9cC5tYXApLHAuYWxwaGFNYXAmJihtLmFscGhhTWFwLnZhbHVlPXAuYWxwaGFNYXApLHAuYWxwaGFUZXN0PjAmJihtLmFscGhhVGVzdC52YWx1ZT1wLmFscGhhVGVzdCk7bGV0IGI7cC5tYXA/Yj1wLm1hcDpwLmFscGhhTWFwJiYoYj1wLmFscGhhTWFwKSxiIT09dm9pZCAwJiYoYi5tYXRyaXhBdXRvVXBkYXRlPT09ITAmJmIudXBkYXRlTWF0cml4KCksbS51dlRyYW5zZm9ybS52YWx1ZS5jb3B5KGIubWF0cml4KSl9ZnVuY3Rpb24gbChtLHApe3AuZW1pc3NpdmVNYXAmJihtLmVtaXNzaXZlTWFwLnZhbHVlPXAuZW1pc3NpdmVNYXApfWZ1bmN0aW9uIGMobSxwKXttLnNwZWN1bGFyLnZhbHVlLmNvcHkocC5zcGVjdWxhciksbS5zaGluaW5lc3MudmFsdWU9TWF0aC5tYXgocC5zaGluaW5lc3MsMWUtNCkscC5lbWlzc2l2ZU1hcCYmKG0uZW1pc3NpdmVNYXAudmFsdWU9cC5lbWlzc2l2ZU1hcCkscC5idW1wTWFwJiYobS5idW1wTWFwLnZhbHVlPXAuYnVtcE1hcCxtLmJ1bXBTY2FsZS52YWx1ZT1wLmJ1bXBTY2FsZSxwLnNpZGU9PT1oZSYmKG0uYnVtcFNjYWxlLnZhbHVlKj0tMSkpLHAubm9ybWFsTWFwJiYobS5ub3JtYWxNYXAudmFsdWU9cC5ub3JtYWxNYXAsbS5ub3JtYWxTY2FsZS52YWx1ZS5jb3B5KHAubm9ybWFsU2NhbGUpLHAuc2lkZT09PWhlJiZtLm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSxwLmRpc3BsYWNlbWVudE1hcCYmKG0uZGlzcGxhY2VtZW50TWFwLnZhbHVlPXAuZGlzcGxhY2VtZW50TWFwLG0uZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9cC5kaXNwbGFjZW1lbnRTY2FsZSxtLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9cC5kaXNwbGFjZW1lbnRCaWFzKX1mdW5jdGlvbiB1KG0scCl7cC5ncmFkaWVudE1hcCYmKG0uZ3JhZGllbnRNYXAudmFsdWU9cC5ncmFkaWVudE1hcCkscC5lbWlzc2l2ZU1hcCYmKG0uZW1pc3NpdmVNYXAudmFsdWU9cC5lbWlzc2l2ZU1hcCkscC5idW1wTWFwJiYobS5idW1wTWFwLnZhbHVlPXAuYnVtcE1hcCxtLmJ1bXBTY2FsZS52YWx1ZT1wLmJ1bXBTY2FsZSxwLnNpZGU9PT1oZSYmKG0uYnVtcFNjYWxlLnZhbHVlKj0tMSkpLHAubm9ybWFsTWFwJiYobS5ub3JtYWxNYXAudmFsdWU9cC5ub3JtYWxNYXAsbS5ub3JtYWxTY2FsZS52YWx1ZS5jb3B5KHAubm9ybWFsU2NhbGUpLHAuc2lkZT09PWhlJiZtLm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSxwLmRpc3BsYWNlbWVudE1hcCYmKG0uZGlzcGxhY2VtZW50TWFwLnZhbHVlPXAuZGlzcGxhY2VtZW50TWFwLG0uZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9cC5kaXNwbGFjZW1lbnRTY2FsZSxtLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9cC5kaXNwbGFjZW1lbnRCaWFzKX1mdW5jdGlvbiBoKG0scCl7bS5yb3VnaG5lc3MudmFsdWU9cC5yb3VnaG5lc3MsbS5tZXRhbG5lc3MudmFsdWU9cC5tZXRhbG5lc3MscC5yb3VnaG5lc3NNYXAmJihtLnJvdWdobmVzc01hcC52YWx1ZT1wLnJvdWdobmVzc01hcCkscC5tZXRhbG5lc3NNYXAmJihtLm1ldGFsbmVzc01hcC52YWx1ZT1wLm1ldGFsbmVzc01hcCkscC5lbWlzc2l2ZU1hcCYmKG0uZW1pc3NpdmVNYXAudmFsdWU9cC5lbWlzc2l2ZU1hcCkscC5idW1wTWFwJiYobS5idW1wTWFwLnZhbHVlPXAuYnVtcE1hcCxtLmJ1bXBTY2FsZS52YWx1ZT1wLmJ1bXBTY2FsZSxwLnNpZGU9PT1oZSYmKG0uYnVtcFNjYWxlLnZhbHVlKj0tMSkpLHAubm9ybWFsTWFwJiYobS5ub3JtYWxNYXAudmFsdWU9cC5ub3JtYWxNYXAsbS5ub3JtYWxTY2FsZS52YWx1ZS5jb3B5KHAubm9ybWFsU2NhbGUpLHAuc2lkZT09PWhlJiZtLm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSxwLmRpc3BsYWNlbWVudE1hcCYmKG0uZGlzcGxhY2VtZW50TWFwLnZhbHVlPXAuZGlzcGxhY2VtZW50TWFwLG0uZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9cC5kaXNwbGFjZW1lbnRTY2FsZSxtLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9cC5kaXNwbGFjZW1lbnRCaWFzKSxuLmdldChwKS5lbnZNYXAmJihtLmVudk1hcEludGVuc2l0eS52YWx1ZT1wLmVudk1hcEludGVuc2l0eSl9ZnVuY3Rpb24gZihtLHAsYil7aChtLHApLG0uaW9yLnZhbHVlPXAuaW9yLHAuc2hlZW4+MCYmKG0uc2hlZW5Db2xvci52YWx1ZS5jb3B5KHAuc2hlZW5Db2xvcikubXVsdGlwbHlTY2FsYXIocC5zaGVlbiksbS5zaGVlblJvdWdobmVzcy52YWx1ZT1wLnNoZWVuUm91Z2huZXNzLHAuc2hlZW5Db2xvck1hcCYmKG0uc2hlZW5Db2xvck1hcC52YWx1ZT1wLnNoZWVuQ29sb3JNYXApLHAuc2hlZW5Sb3VnaG5lc3NNYXAmJihtLnNoZWVuUm91Z2huZXNzTWFwLnZhbHVlPXAuc2hlZW5Sb3VnaG5lc3NNYXApKSxwLmNsZWFyY29hdD4wJiYobS5jbGVhcmNvYXQudmFsdWU9cC5jbGVhcmNvYXQsbS5jbGVhcmNvYXRSb3VnaG5lc3MudmFsdWU9cC5jbGVhcmNvYXRSb3VnaG5lc3MscC5jbGVhcmNvYXRNYXAmJihtLmNsZWFyY29hdE1hcC52YWx1ZT1wLmNsZWFyY29hdE1hcCkscC5jbGVhcmNvYXRSb3VnaG5lc3NNYXAmJihtLmNsZWFyY29hdFJvdWdobmVzc01hcC52YWx1ZT1wLmNsZWFyY29hdFJvdWdobmVzc01hcCkscC5jbGVhcmNvYXROb3JtYWxNYXAmJihtLmNsZWFyY29hdE5vcm1hbFNjYWxlLnZhbHVlLmNvcHkocC5jbGVhcmNvYXROb3JtYWxTY2FsZSksbS5jbGVhcmNvYXROb3JtYWxNYXAudmFsdWU9cC5jbGVhcmNvYXROb3JtYWxNYXAscC5zaWRlPT09aGUmJm0uY2xlYXJjb2F0Tm9ybWFsU2NhbGUudmFsdWUubmVnYXRlKCkpKSxwLnRyYW5zbWlzc2lvbj4wJiYobS50cmFuc21pc3Npb24udmFsdWU9cC50cmFuc21pc3Npb24sbS50cmFuc21pc3Npb25TYW1wbGVyTWFwLnZhbHVlPWIudGV4dHVyZSxtLnRyYW5zbWlzc2lvblNhbXBsZXJTaXplLnZhbHVlLnNldChiLndpZHRoLGIuaGVpZ2h0KSxwLnRyYW5zbWlzc2lvbk1hcCYmKG0udHJhbnNtaXNzaW9uTWFwLnZhbHVlPXAudHJhbnNtaXNzaW9uTWFwKSxtLnRoaWNrbmVzcy52YWx1ZT1wLnRoaWNrbmVzcyxwLnRoaWNrbmVzc01hcCYmKG0udGhpY2tuZXNzTWFwLnZhbHVlPXAudGhpY2tuZXNzTWFwKSxtLmF0dGVudWF0aW9uRGlzdGFuY2UudmFsdWU9cC5hdHRlbnVhdGlvbkRpc3RhbmNlLG0uYXR0ZW51YXRpb25Db2xvci52YWx1ZS5jb3B5KHAuYXR0ZW51YXRpb25Db2xvcikpLG0uc3BlY3VsYXJJbnRlbnNpdHkudmFsdWU9cC5zcGVjdWxhckludGVuc2l0eSxtLnNwZWN1bGFyQ29sb3IudmFsdWUuY29weShwLnNwZWN1bGFyQ29sb3IpLHAuc3BlY3VsYXJJbnRlbnNpdHlNYXAmJihtLnNwZWN1bGFySW50ZW5zaXR5TWFwLnZhbHVlPXAuc3BlY3VsYXJJbnRlbnNpdHlNYXApLHAuc3BlY3VsYXJDb2xvck1hcCYmKG0uc3BlY3VsYXJDb2xvck1hcC52YWx1ZT1wLnNwZWN1bGFyQ29sb3JNYXApfWZ1bmN0aW9uIGQobSxwKXtwLm1hdGNhcCYmKG0ubWF0Y2FwLnZhbHVlPXAubWF0Y2FwKSxwLmJ1bXBNYXAmJihtLmJ1bXBNYXAudmFsdWU9cC5idW1wTWFwLG0uYnVtcFNjYWxlLnZhbHVlPXAuYnVtcFNjYWxlLHAuc2lkZT09PWhlJiYobS5idW1wU2NhbGUudmFsdWUqPS0xKSkscC5ub3JtYWxNYXAmJihtLm5vcm1hbE1hcC52YWx1ZT1wLm5vcm1hbE1hcCxtLm5vcm1hbFNjYWxlLnZhbHVlLmNvcHkocC5ub3JtYWxTY2FsZSkscC5zaWRlPT09aGUmJm0ubm9ybWFsU2NhbGUudmFsdWUubmVnYXRlKCkpLHAuZGlzcGxhY2VtZW50TWFwJiYobS5kaXNwbGFjZW1lbnRNYXAudmFsdWU9cC5kaXNwbGFjZW1lbnRNYXAsbS5kaXNwbGFjZW1lbnRTY2FsZS52YWx1ZT1wLmRpc3BsYWNlbWVudFNjYWxlLG0uZGlzcGxhY2VtZW50Qmlhcy52YWx1ZT1wLmRpc3BsYWNlbWVudEJpYXMpfWZ1bmN0aW9uIGcobSxwKXtwLmRpc3BsYWNlbWVudE1hcCYmKG0uZGlzcGxhY2VtZW50TWFwLnZhbHVlPXAuZGlzcGxhY2VtZW50TWFwLG0uZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9cC5kaXNwbGFjZW1lbnRTY2FsZSxtLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9cC5kaXNwbGFjZW1lbnRCaWFzKX1mdW5jdGlvbiB4KG0scCl7cC5kaXNwbGFjZW1lbnRNYXAmJihtLmRpc3BsYWNlbWVudE1hcC52YWx1ZT1wLmRpc3BsYWNlbWVudE1hcCxtLmRpc3BsYWNlbWVudFNjYWxlLnZhbHVlPXAuZGlzcGxhY2VtZW50U2NhbGUsbS5kaXNwbGFjZW1lbnRCaWFzLnZhbHVlPXAuZGlzcGxhY2VtZW50QmlhcyksbS5yZWZlcmVuY2VQb3NpdGlvbi52YWx1ZS5jb3B5KHAucmVmZXJlbmNlUG9zaXRpb24pLG0ubmVhckRpc3RhbmNlLnZhbHVlPXAubmVhckRpc3RhbmNlLG0uZmFyRGlzdGFuY2UudmFsdWU9cC5mYXJEaXN0YW5jZX1mdW5jdGlvbiB2KG0scCl7cC5idW1wTWFwJiYobS5idW1wTWFwLnZhbHVlPXAuYnVtcE1hcCxtLmJ1bXBTY2FsZS52YWx1ZT1wLmJ1bXBTY2FsZSxwLnNpZGU9PT1oZSYmKG0uYnVtcFNjYWxlLnZhbHVlKj0tMSkpLHAubm9ybWFsTWFwJiYobS5ub3JtYWxNYXAudmFsdWU9cC5ub3JtYWxNYXAsbS5ub3JtYWxTY2FsZS52YWx1ZS5jb3B5KHAubm9ybWFsU2NhbGUpLHAuc2lkZT09PWhlJiZtLm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSxwLmRpc3BsYWNlbWVudE1hcCYmKG0uZGlzcGxhY2VtZW50TWFwLnZhbHVlPXAuZGlzcGxhY2VtZW50TWFwLG0uZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9cC5kaXNwbGFjZW1lbnRTY2FsZSxtLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9cC5kaXNwbGFjZW1lbnRCaWFzKX1yZXR1cm57cmVmcmVzaEZvZ1VuaWZvcm1zOnQscmVmcmVzaE1hdGVyaWFsVW5pZm9ybXM6ZX19ZnVuY3Rpb24gSUUoKXtsZXQgbj1ybygiY2FudmFzIik7cmV0dXJuIG4uc3R5bGUuZGlzcGxheT0iYmxvY2siLG59ZnVuY3Rpb24gVnQobj17fSl7bGV0IHQ9bi5jYW52YXMhPT12b2lkIDA/bi5jYW52YXM6SUUoKSxlPW4uY29udGV4dCE9PXZvaWQgMD9uLmNvbnRleHQ6bnVsbCxpPW4uYWxwaGEhPT12b2lkIDA/bi5hbHBoYTohMSxyPW4uZGVwdGghPT12b2lkIDA/bi5kZXB0aDohMCxzPW4uc3RlbmNpbCE9PXZvaWQgMD9uLnN0ZW5jaWw6ITAsbz1uLmFudGlhbGlhcyE9PXZvaWQgMD9uLmFudGlhbGlhczohMSxhPW4ucHJlbXVsdGlwbGllZEFscGhhIT09dm9pZCAwP24ucHJlbXVsdGlwbGllZEFscGhhOiEwLGw9bi5wcmVzZXJ2ZURyYXdpbmdCdWZmZXIhPT12b2lkIDA/bi5wcmVzZXJ2ZURyYXdpbmdCdWZmZXI6ITEsYz1uLnBvd2VyUHJlZmVyZW5jZSE9PXZvaWQgMD9uLnBvd2VyUHJlZmVyZW5jZToiZGVmYXVsdCIsdT1uLmZhaWxJZk1ham9yUGVyZm9ybWFuY2VDYXZlYXQhPT12b2lkIDA/bi5mYWlsSWZNYWpvclBlcmZvcm1hbmNlQ2F2ZWF0OiExLGg9bnVsbCxmPW51bGwsZD1bXSxnPVtdO3RoaXMuZG9tRWxlbWVudD10LHRoaXMuZGVidWc9e2NoZWNrU2hhZGVyRXJyb3JzOiEwfSx0aGlzLmF1dG9DbGVhcj0hMCx0aGlzLmF1dG9DbGVhckNvbG9yPSEwLHRoaXMuYXV0b0NsZWFyRGVwdGg9ITAsdGhpcy5hdXRvQ2xlYXJTdGVuY2lsPSEwLHRoaXMuc29ydE9iamVjdHM9ITAsdGhpcy5jbGlwcGluZ1BsYW5lcz1bXSx0aGlzLmxvY2FsQ2xpcHBpbmdFbmFibGVkPSExLHRoaXMub3V0cHV0RW5jb2Rpbmc9cmksdGhpcy5waHlzaWNhbGx5Q29ycmVjdExpZ2h0cz0hMSx0aGlzLnRvbmVNYXBwaW5nPXRpLHRoaXMudG9uZU1hcHBpbmdFeHBvc3VyZT0xO2xldCB4PXRoaXMsdj0hMSxtPTAscD0wLGI9bnVsbCxfPS0xLFM9bnVsbCxMPW5ldyBXdCxBPW5ldyBXdCxIPW51bGwsdHQ9dC53aWR0aCxYPXQuaGVpZ2h0LHk9MSxSPW51bGwsRD1udWxsLEY9bmV3IFd0KDAsMCx0dCxYKSx6PW5ldyBXdCgwLDAsdHQsWCksTj0hMSxWPW5ldyBxcixRPSExLGF0PSExLEc9bnVsbCwkPW5ldyB3dCxsdD1uZXcgVCxkdD17YmFja2dyb3VuZDpudWxsLGZvZzpudWxsLGVudmlyb25tZW50Om51bGwsb3ZlcnJpZGVNYXRlcmlhbDpudWxsLGlzU2NlbmU6ITB9O2Z1bmN0aW9uIHh0KCl7cmV0dXJuIGI9PT1udWxsP3k6MX1sZXQgaz1lO2Z1bmN0aW9uIEZ0KE0sSSl7Zm9yKGxldCBPPTA7TzxNLmxlbmd0aDtPKyspe2xldCBVPU1bT10sWj10LmdldENvbnRleHQoVSxJKTtpZihaIT09bnVsbClyZXR1cm4gWn1yZXR1cm4gbnVsbH10cnl7bGV0IE09e2FscGhhOiEwLGRlcHRoOnIsc3RlbmNpbDpzLGFudGlhbGlhczpvLHByZW11bHRpcGxpZWRBbHBoYTphLHByZXNlcnZlRHJhd2luZ0J1ZmZlcjpsLHBvd2VyUHJlZmVyZW5jZTpjLGZhaWxJZk1ham9yUGVyZm9ybWFuY2VDYXZlYXQ6dX07aWYoInNldEF0dHJpYnV0ZSJpbiB0JiZ0LnNldEF0dHJpYnV0ZSgiZGF0YS1lbmdpbmUiLGB0aHJlZS5qcyByJHskaH1gKSx0LmFkZEV2ZW50TGlzdGVuZXIoIndlYmdsY29udGV4dGxvc3QiLEF0LCExKSx0LmFkZEV2ZW50TGlzdGVuZXIoIndlYmdsY29udGV4dHJlc3RvcmVkIixQLCExKSxrPT09bnVsbCl7bGV0IEk9WyJ3ZWJnbDIiLCJ3ZWJnbCIsImV4cGVyaW1lbnRhbC13ZWJnbCJdO2lmKHguaXNXZWJHTDFSZW5kZXJlcj09PSEwJiZJLnNoaWZ0KCksaz1GdChJLE0pLGs9PT1udWxsKXRocm93IEZ0KEkpP25ldyBFcnJvcigiRXJyb3IgY3JlYXRpbmcgV2ViR0wgY29udGV4dCB3aXRoIHlvdXIgc2VsZWN0ZWQgYXR0cmlidXRlcy4iKTpuZXcgRXJyb3IoIkVycm9yIGNyZWF0aW5nIFdlYkdMIGNvbnRleHQuIil9ay5nZXRTaGFkZXJQcmVjaXNpb25Gb3JtYXQ9PT12b2lkIDAmJihrLmdldFNoYWRlclByZWNpc2lvbkZvcm1hdD1mdW5jdGlvbigpe3JldHVybntyYW5nZU1pbjoxLHJhbmdlTWF4OjEscHJlY2lzaW9uOjF9fSl9Y2F0Y2goTSl7dGhyb3cgY29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlcjogIitNLm1lc3NhZ2UpLE19bGV0IG10LFN0LEIsc3QsbnQsQyxqLEosaXQsZXQsdnQsYnQsSXQsWnQscXQsRSx3LHEscnQsZ3QsVyxfdCx5dDtmdW5jdGlvbiB1dCgpe210PW5ldyBlUyhrKSxTdD1uZXcgSjEoayxtdCxuKSxtdC5pbml0KFN0KSxfdD1uZXcgTEUoayxtdCxTdCksQj1uZXcgQ0UoayxtdCxTdCksc3Q9bmV3IHJTKGspLG50PW5ldyB4RSxDPW5ldyBSRShrLG10LEIsbnQsU3QsX3Qsc3QpLGo9bmV3IEsxKHgpLEo9bmV3IHRTKHgpLGl0PW5ldyB2TShrLFN0KSx5dD1uZXcgWTEoayxtdCxpdCxTdCksZXQ9bmV3IG5TKGssaXQsc3QseXQpLHZ0PW5ldyBsUyhrLGV0LGl0LHN0KSxydD1uZXcgYVMoayxTdCxDKSxFPW5ldyAkMShudCksYnQ9bmV3IGdFKHgsaixKLG10LFN0LHl0LEUpLEl0PW5ldyBERShudCksWnQ9bmV3IHZFLHF0PW5ldyBFRShtdCxTdCkscT1uZXcgWDEoeCxqLEIsdnQsaSxhKSx3PW5ldyBUMCh4LHZ0LFN0KSxndD1uZXcgWjEoayxtdCxzdCxTdCksVz1uZXcgaVMoayxtdCxzdCxTdCksc3QucHJvZ3JhbXM9YnQucHJvZ3JhbXMseC5jYXBhYmlsaXRpZXM9U3QseC5leHRlbnNpb25zPW10LHgucHJvcGVydGllcz1udCx4LnJlbmRlckxpc3RzPVp0LHguc2hhZG93TWFwPXcseC5zdGF0ZT1CLHguaW5mbz1zdH11dCgpO2xldCBjdD1uZXcgWnUoeCxrKTt0aGlzLnhyPWN0LHRoaXMuZ2V0Q29udGV4dD1mdW5jdGlvbigpe3JldHVybiBrfSx0aGlzLmdldENvbnRleHRBdHRyaWJ1dGVzPWZ1bmN0aW9uKCl7cmV0dXJuIGsuZ2V0Q29udGV4dEF0dHJpYnV0ZXMoKX0sdGhpcy5mb3JjZUNvbnRleHRMb3NzPWZ1bmN0aW9uKCl7bGV0IE09bXQuZ2V0KCJXRUJHTF9sb3NlX2NvbnRleHQiKTtNJiZNLmxvc2VDb250ZXh0KCl9LHRoaXMuZm9yY2VDb250ZXh0UmVzdG9yZT1mdW5jdGlvbigpe2xldCBNPW10LmdldCgiV0VCR0xfbG9zZV9jb250ZXh0Iik7TSYmTS5yZXN0b3JlQ29udGV4dCgpfSx0aGlzLmdldFBpeGVsUmF0aW89ZnVuY3Rpb24oKXtyZXR1cm4geX0sdGhpcy5zZXRQaXhlbFJhdGlvPWZ1bmN0aW9uKE0pe00hPT12b2lkIDAmJih5PU0sdGhpcy5zZXRTaXplKHR0LFgsITEpKX0sdGhpcy5nZXRTaXplPWZ1bmN0aW9uKE0pe3JldHVybiBNLnNldCh0dCxYKX0sdGhpcy5zZXRTaXplPWZ1bmN0aW9uKE0sSSxPKXtpZihjdC5pc1ByZXNlbnRpbmcpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogQ2FuJ3QgY2hhbmdlIHNpemUgd2hpbGUgVlIgZGV2aWNlIGlzIHByZXNlbnRpbmcuIik7cmV0dXJufXR0PU0sWD1JLHQud2lkdGg9TWF0aC5mbG9vcihNKnkpLHQuaGVpZ2h0PU1hdGguZmxvb3IoSSp5KSxPIT09ITEmJih0LnN0eWxlLndpZHRoPU0rInB4Iix0LnN0eWxlLmhlaWdodD1JKyJweCIpLHRoaXMuc2V0Vmlld3BvcnQoMCwwLE0sSSl9LHRoaXMuZ2V0RHJhd2luZ0J1ZmZlclNpemU9ZnVuY3Rpb24oTSl7cmV0dXJuIE0uc2V0KHR0KnksWCp5KS5mbG9vcigpfSx0aGlzLnNldERyYXdpbmdCdWZmZXJTaXplPWZ1bmN0aW9uKE0sSSxPKXt0dD1NLFg9SSx5PU8sdC53aWR0aD1NYXRoLmZsb29yKE0qTyksdC5oZWlnaHQ9TWF0aC5mbG9vcihJKk8pLHRoaXMuc2V0Vmlld3BvcnQoMCwwLE0sSSl9LHRoaXMuZ2V0Q3VycmVudFZpZXdwb3J0PWZ1bmN0aW9uKE0pe3JldHVybiBNLmNvcHkoTCl9LHRoaXMuZ2V0Vmlld3BvcnQ9ZnVuY3Rpb24oTSl7cmV0dXJuIE0uY29weShGKX0sdGhpcy5zZXRWaWV3cG9ydD1mdW5jdGlvbihNLEksTyxVKXtNLmlzVmVjdG9yND9GLnNldChNLngsTS55LE0ueixNLncpOkYuc2V0KE0sSSxPLFUpLEIudmlld3BvcnQoTC5jb3B5KEYpLm11bHRpcGx5U2NhbGFyKHkpLmZsb29yKCkpfSx0aGlzLmdldFNjaXNzb3I9ZnVuY3Rpb24oTSl7cmV0dXJuIE0uY29weSh6KX0sdGhpcy5zZXRTY2lzc29yPWZ1bmN0aW9uKE0sSSxPLFUpe00uaXNWZWN0b3I0P3ouc2V0KE0ueCxNLnksTS56LE0udyk6ei5zZXQoTSxJLE8sVSksQi5zY2lzc29yKEEuY29weSh6KS5tdWx0aXBseVNjYWxhcih5KS5mbG9vcigpKX0sdGhpcy5nZXRTY2lzc29yVGVzdD1mdW5jdGlvbigpe3JldHVybiBOfSx0aGlzLnNldFNjaXNzb3JUZXN0PWZ1bmN0aW9uKE0pe0Iuc2V0U2Npc3NvclRlc3QoTj1NKX0sdGhpcy5zZXRPcGFxdWVTb3J0PWZ1bmN0aW9uKE0pe1I9TX0sdGhpcy5zZXRUcmFuc3BhcmVudFNvcnQ9ZnVuY3Rpb24oTSl7RD1NfSx0aGlzLmdldENsZWFyQ29sb3I9ZnVuY3Rpb24oTSl7cmV0dXJuIE0uY29weShxLmdldENsZWFyQ29sb3IoKSl9LHRoaXMuc2V0Q2xlYXJDb2xvcj1mdW5jdGlvbigpe3Euc2V0Q2xlYXJDb2xvci5hcHBseShxLGFyZ3VtZW50cyl9LHRoaXMuZ2V0Q2xlYXJBbHBoYT1mdW5jdGlvbigpe3JldHVybiBxLmdldENsZWFyQWxwaGEoKX0sdGhpcy5zZXRDbGVhckFscGhhPWZ1bmN0aW9uKCl7cS5zZXRDbGVhckFscGhhLmFwcGx5KHEsYXJndW1lbnRzKX0sdGhpcy5jbGVhcj1mdW5jdGlvbihNLEksTyl7bGV0IFU9MDsoTT09PXZvaWQgMHx8TSkmJihVfD0xNjM4NCksKEk9PT12b2lkIDB8fEkpJiYoVXw9MjU2KSwoTz09PXZvaWQgMHx8TykmJihVfD0xMDI0KSxrLmNsZWFyKFUpfSx0aGlzLmNsZWFyQ29sb3I9ZnVuY3Rpb24oKXt0aGlzLmNsZWFyKCEwLCExLCExKX0sdGhpcy5jbGVhckRlcHRoPWZ1bmN0aW9uKCl7dGhpcy5jbGVhcighMSwhMCwhMSl9LHRoaXMuY2xlYXJTdGVuY2lsPWZ1bmN0aW9uKCl7dGhpcy5jbGVhcighMSwhMSwhMCl9LHRoaXMuZGlzcG9zZT1mdW5jdGlvbigpe3QucmVtb3ZlRXZlbnRMaXN0ZW5lcigid2ViZ2xjb250ZXh0bG9zdCIsQXQsITEpLHQucmVtb3ZlRXZlbnRMaXN0ZW5lcigid2ViZ2xjb250ZXh0cmVzdG9yZWQiLFAsITEpLFp0LmRpc3Bvc2UoKSxxdC5kaXNwb3NlKCksbnQuZGlzcG9zZSgpLGouZGlzcG9zZSgpLEouZGlzcG9zZSgpLHZ0LmRpc3Bvc2UoKSx5dC5kaXNwb3NlKCksYnQuZGlzcG9zZSgpLGN0LmRpc3Bvc2UoKSxjdC5yZW1vdmVFdmVudExpc3RlbmVyKCJzZXNzaW9uc3RhcnQiLER0KSxjdC5yZW1vdmVFdmVudExpc3RlbmVyKCJzZXNzaW9uZW5kIixqdCksRyYmKEcuZGlzcG9zZSgpLEc9bnVsbCksemUuc3RvcCgpfTtmdW5jdGlvbiBBdChNKXtNLnByZXZlbnREZWZhdWx0KCksY29uc29sZS5sb2coIlRIUkVFLldlYkdMUmVuZGVyZXI6IENvbnRleHQgTG9zdC4iKSx2PSEwfWZ1bmN0aW9uIFAoKXtjb25zb2xlLmxvZygiVEhSRUUuV2ViR0xSZW5kZXJlcjogQ29udGV4dCBSZXN0b3JlZC4iKSx2PSExO2xldCBNPXN0LmF1dG9SZXNldCxJPXcuZW5hYmxlZCxPPXcuYXV0b1VwZGF0ZSxVPXcubmVlZHNVcGRhdGUsWj13LnR5cGU7dXQoKSxzdC5hdXRvUmVzZXQ9TSx3LmVuYWJsZWQ9SSx3LmF1dG9VcGRhdGU9Tyx3Lm5lZWRzVXBkYXRlPVUsdy50eXBlPVp9ZnVuY3Rpb24gcHQoTSl7bGV0IEk9TS50YXJnZXQ7SS5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixwdCksaHQoSSl9ZnVuY3Rpb24gaHQoTSl7RXQoTSksbnQucmVtb3ZlKE0pfWZ1bmN0aW9uIEV0KE0pe2xldCBJPW50LmdldChNKS5wcm9ncmFtcztJIT09dm9pZCAwJiYoSS5mb3JFYWNoKGZ1bmN0aW9uKE8pe2J0LnJlbGVhc2VQcm9ncmFtKE8pfSksTS5pc1NoYWRlck1hdGVyaWFsJiZidC5yZWxlYXNlU2hhZGVyQ2FjaGUoTSkpfXRoaXMucmVuZGVyQnVmZmVyRGlyZWN0PWZ1bmN0aW9uKE0sSSxPLFUsWixUdCl7ST09PW51bGwmJihJPWR0KTtsZXQgQ3Q9Wi5pc01lc2gmJloubWF0cml4V29ybGQuZGV0ZXJtaW5hbnQoKTwwLEx0PUIwKE0sSSxPLFUsWik7Qi5zZXRNYXRlcmlhbChVLEN0KTtsZXQgUnQ9Ty5pbmRleCxHdD1PLmF0dHJpYnV0ZXMucG9zaXRpb247aWYoUnQ9PT1udWxsKXtpZihHdD09PXZvaWQgMHx8R3QuY291bnQ9PT0wKXJldHVybn1lbHNlIGlmKFJ0LmNvdW50PT09MClyZXR1cm47bGV0IHp0PTE7VS53aXJlZnJhbWU9PT0hMCYmKFJ0PWV0LmdldFdpcmVmcmFtZUF0dHJpYnV0ZShPKSx6dD0yKSx5dC5zZXR1cChaLFUsTHQsTyxSdCk7bGV0IFV0LGllPWd0O1J0IT09bnVsbCYmKFV0PWl0LmdldChSdCksaWU9VyxpZS5zZXRJbmRleChVdCkpO2xldCBmaT1SdCE9PW51bGw/UnQuY291bnQ6R3QuY291bnQsWmk9Ty5kcmF3UmFuZ2Uuc3RhcnQqenQsT3Q9Ty5kcmF3UmFuZ2UuY291bnQqenQsbm49VHQhPT1udWxsP1R0LnN0YXJ0Knp0OjAsbGU9VHQhPT1udWxsP1R0LmNvdW50Knp0OjEvMCxybj1NYXRoLm1heChaaSxubiksSW89TWF0aC5taW4oZmksWmkrT3Qsbm4rbGUpLTEsc249TWF0aC5tYXgoMCxJby1ybisxKTtpZihzbiE9PTApe2lmKFouaXNNZXNoKVUud2lyZWZyYW1lPT09ITA/KEIuc2V0TGluZVdpZHRoKFUud2lyZWZyYW1lTGluZXdpZHRoKnh0KCkpLGllLnNldE1vZGUoMSkpOmllLnNldE1vZGUoNCk7ZWxzZSBpZihaLmlzTGluZSl7bGV0IHZuPVUubGluZXdpZHRoO3ZuPT09dm9pZCAwJiYodm49MSksQi5zZXRMaW5lV2lkdGgodm4qeHQoKSksWi5pc0xpbmVTZWdtZW50cz9pZS5zZXRNb2RlKDEpOlouaXNMaW5lTG9vcD9pZS5zZXRNb2RlKDIpOmllLnNldE1vZGUoMyl9ZWxzZSBaLmlzUG9pbnRzP2llLnNldE1vZGUoMCk6Wi5pc1Nwcml0ZSYmaWUuc2V0TW9kZSg0KTtpZihaLmlzSW5zdGFuY2VkTWVzaClpZS5yZW5kZXJJbnN0YW5jZXMocm4sc24sWi5jb3VudCk7ZWxzZSBpZihPLmlzSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnkpe2xldCB2bj1NYXRoLm1pbihPLmluc3RhbmNlQ291bnQsTy5fbWF4SW5zdGFuY2VDb3VudCk7aWUucmVuZGVySW5zdGFuY2VzKHJuLHNuLHZuKX1lbHNlIGllLnJlbmRlcihybixzbil9fSx0aGlzLmNvbXBpbGU9ZnVuY3Rpb24oTSxJKXtmPXF0LmdldChNKSxmLmluaXQoKSxnLnB1c2goZiksTS50cmF2ZXJzZVZpc2libGUoZnVuY3Rpb24oTyl7Ty5pc0xpZ2h0JiZPLmxheWVycy50ZXN0KEkubGF5ZXJzKSYmKGYucHVzaExpZ2h0KE8pLE8uY2FzdFNoYWRvdyYmZi5wdXNoU2hhZG93KE8pKX0pLGYuc2V0dXBMaWdodHMoeC5waHlzaWNhbGx5Q29ycmVjdExpZ2h0cyksTS50cmF2ZXJzZShmdW5jdGlvbihPKXtsZXQgVT1PLm1hdGVyaWFsO2lmKFUpaWYoQXJyYXkuaXNBcnJheShVKSlmb3IobGV0IFo9MDtaPFUubGVuZ3RoO1orKyl7bGV0IFR0PVVbWl07T2woVHQsTSxPKX1lbHNlIE9sKFUsTSxPKX0pLGcucG9wKCksZj1udWxsfTtsZXQgWT1udWxsO2Z1bmN0aW9uIE10KE0pe1kmJlkoTSl9ZnVuY3Rpb24gRHQoKXt6ZS5zdG9wKCl9ZnVuY3Rpb24ganQoKXt6ZS5zdGFydCgpfWxldCB6ZT1uZXcgZzA7emUuc2V0QW5pbWF0aW9uTG9vcChNdCksdHlwZW9mIHdpbmRvdyE9InVuZGVmaW5lZCImJnplLnNldENvbnRleHQod2luZG93KSx0aGlzLnNldEFuaW1hdGlvbkxvb3A9ZnVuY3Rpb24oTSl7WT1NLGN0LnNldEFuaW1hdGlvbkxvb3AoTSksTT09PW51bGw/emUuc3RvcCgpOnplLnN0YXJ0KCl9LGN0LmFkZEV2ZW50TGlzdGVuZXIoInNlc3Npb25zdGFydCIsRHQpLGN0LmFkZEV2ZW50TGlzdGVuZXIoInNlc3Npb25lbmQiLGp0KSx0aGlzLnJlbmRlcj1mdW5jdGlvbihNLEkpe2lmKEkhPT12b2lkIDAmJkkuaXNDYW1lcmEhPT0hMCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlci5yZW5kZXI6IGNhbWVyYSBpcyBub3QgYW4gaW5zdGFuY2Ugb2YgVEhSRUUuQ2FtZXJhLiIpO3JldHVybn1pZih2PT09ITApcmV0dXJuO00uYXV0b1VwZGF0ZT09PSEwJiZNLnVwZGF0ZU1hdHJpeFdvcmxkKCksSS5wYXJlbnQ9PT1udWxsJiZJLnVwZGF0ZU1hdHJpeFdvcmxkKCksY3QuZW5hYmxlZD09PSEwJiZjdC5pc1ByZXNlbnRpbmc9PT0hMCYmKGN0LmNhbWVyYUF1dG9VcGRhdGU9PT0hMCYmY3QudXBkYXRlQ2FtZXJhKEkpLEk9Y3QuZ2V0Q2FtZXJhKCkpLE0uaXNTY2VuZT09PSEwJiZNLm9uQmVmb3JlUmVuZGVyKHgsTSxJLGIpLGY9cXQuZ2V0KE0sZy5sZW5ndGgpLGYuaW5pdCgpLGcucHVzaChmKSwkLm11bHRpcGx5TWF0cmljZXMoSS5wcm9qZWN0aW9uTWF0cml4LEkubWF0cml4V29ybGRJbnZlcnNlKSxWLnNldEZyb21Qcm9qZWN0aW9uTWF0cml4KCQpLGF0PXRoaXMubG9jYWxDbGlwcGluZ0VuYWJsZWQsUT1FLmluaXQodGhpcy5jbGlwcGluZ1BsYW5lcyxhdCxJKSxoPVp0LmdldChNLGQubGVuZ3RoKSxoLmluaXQoKSxkLnB1c2goaCksbmUoTSxJLDAseC5zb3J0T2JqZWN0cyksaC5maW5pc2goKSx4LnNvcnRPYmplY3RzPT09ITAmJmguc29ydChSLEQpLFE9PT0hMCYmRS5iZWdpblNoYWRvd3MoKTtsZXQgTz1mLnN0YXRlLnNoYWRvd3NBcnJheTtpZih3LnJlbmRlcihPLE0sSSksUT09PSEwJiZFLmVuZFNoYWRvd3MoKSx0aGlzLmluZm8uYXV0b1Jlc2V0PT09ITAmJnRoaXMuaW5mby5yZXNldCgpLHEucmVuZGVyKGgsTSksZi5zZXR1cExpZ2h0cyh4LnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzKSxJLmlzQXJyYXlDYW1lcmEpe2xldCBVPUkuY2FtZXJhcztmb3IobGV0IFo9MCxUdD1VLmxlbmd0aDtaPFR0O1orKyl7bGV0IEN0PVVbWl07ZW4oaCxNLEN0LEN0LnZpZXdwb3J0KX19ZWxzZSBlbihoLE0sSSk7YiE9PW51bGwmJihDLnVwZGF0ZU11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0KGIpLEMudXBkYXRlUmVuZGVyVGFyZ2V0TWlwbWFwKGIpKSxNLmlzU2NlbmU9PT0hMCYmTS5vbkFmdGVyUmVuZGVyKHgsTSxJKSxCLmJ1ZmZlcnMuZGVwdGguc2V0VGVzdCghMCksQi5idWZmZXJzLmRlcHRoLnNldE1hc2soITApLEIuYnVmZmVycy5jb2xvci5zZXRNYXNrKCEwKSxCLnNldFBvbHlnb25PZmZzZXQoITEpLHl0LnJlc2V0RGVmYXVsdFN0YXRlKCksXz0tMSxTPW51bGwsZy5wb3AoKSxnLmxlbmd0aD4wP2Y9Z1tnLmxlbmd0aC0xXTpmPW51bGwsZC5wb3AoKSxkLmxlbmd0aD4wP2g9ZFtkLmxlbmd0aC0xXTpoPW51bGx9O2Z1bmN0aW9uIG5lKE0sSSxPLFUpe2lmKE0udmlzaWJsZT09PSExKXJldHVybjtpZihNLmxheWVycy50ZXN0KEkubGF5ZXJzKSl7aWYoTS5pc0dyb3VwKU89TS5yZW5kZXJPcmRlcjtlbHNlIGlmKE0uaXNMT0QpTS5hdXRvVXBkYXRlPT09ITAmJk0udXBkYXRlKEkpO2Vsc2UgaWYoTS5pc0xpZ2h0KWYucHVzaExpZ2h0KE0pLE0uY2FzdFNoYWRvdyYmZi5wdXNoU2hhZG93KE0pO2Vsc2UgaWYoTS5pc1Nwcml0ZSl7aWYoIU0uZnJ1c3R1bUN1bGxlZHx8Vi5pbnRlcnNlY3RzU3ByaXRlKE0pKXtVJiZsdC5zZXRGcm9tTWF0cml4UG9zaXRpb24oTS5tYXRyaXhXb3JsZCkuYXBwbHlNYXRyaXg0KCQpO2xldCBDdD12dC51cGRhdGUoTSksTHQ9TS5tYXRlcmlhbDtMdC52aXNpYmxlJiZoLnB1c2goTSxDdCxMdCxPLGx0LnosbnVsbCl9fWVsc2UgaWYoKE0uaXNNZXNofHxNLmlzTGluZXx8TS5pc1BvaW50cykmJihNLmlzU2tpbm5lZE1lc2gmJk0uc2tlbGV0b24uZnJhbWUhPT1zdC5yZW5kZXIuZnJhbWUmJihNLnNrZWxldG9uLnVwZGF0ZSgpLE0uc2tlbGV0b24uZnJhbWU9c3QucmVuZGVyLmZyYW1lKSwhTS5mcnVzdHVtQ3VsbGVkfHxWLmludGVyc2VjdHNPYmplY3QoTSkpKXtVJiZsdC5zZXRGcm9tTWF0cml4UG9zaXRpb24oTS5tYXRyaXhXb3JsZCkuYXBwbHlNYXRyaXg0KCQpO2xldCBDdD12dC51cGRhdGUoTSksTHQ9TS5tYXRlcmlhbDtpZihBcnJheS5pc0FycmF5KEx0KSl7bGV0IFJ0PUN0Lmdyb3Vwcztmb3IobGV0IEd0PTAsenQ9UnQubGVuZ3RoO0d0PHp0O0d0Kyspe2xldCBVdD1SdFtHdF0saWU9THRbVXQubWF0ZXJpYWxJbmRleF07aWUmJmllLnZpc2libGUmJmgucHVzaChNLEN0LGllLE8sbHQueixVdCl9fWVsc2UgTHQudmlzaWJsZSYmaC5wdXNoKE0sQ3QsTHQsTyxsdC56LG51bGwpfX1sZXQgVHQ9TS5jaGlsZHJlbjtmb3IobGV0IEN0PTAsTHQ9VHQubGVuZ3RoO0N0PEx0O0N0KyspbmUoVHRbQ3RdLEksTyxVKX1mdW5jdGlvbiBlbihNLEksTyxVKXtsZXQgWj1NLm9wYXF1ZSxUdD1NLnRyYW5zbWlzc2l2ZSxDdD1NLnRyYW5zcGFyZW50O2Yuc2V0dXBMaWdodHNWaWV3KE8pLFR0Lmxlbmd0aD4wJiZ5bihaLEksTyksVSYmQi52aWV3cG9ydChMLmNvcHkoVSkpLFoubGVuZ3RoPjAmJkRvKFosSSxPKSxUdC5sZW5ndGg+MCYmRG8oVHQsSSxPKSxDdC5sZW5ndGg+MCYmRG8oQ3QsSSxPKX1mdW5jdGlvbiB5bihNLEksTyl7aWYoRz09PW51bGwpe2xldCBDdD1vPT09ITAmJlN0LmlzV2ViR0wyPT09ITA/c286TmU7Rz1uZXcgQ3QoMTAyNCwxMDI0LHtnZW5lcmF0ZU1pcG1hcHM6ITAsdHlwZTpfdC5jb252ZXJ0KFVyKSE9PW51bGw/VXI6ZWksbWluRmlsdGVyOkxsLG1hZ0ZpbHRlcjpmZSx3cmFwUzpWZSx3cmFwVDpWZSx1c2VSZW5kZXJUb1RleHR1cmU6bXQuaGFzKCJXRUJHTF9tdWx0aXNhbXBsZWRfcmVuZGVyX3RvX3RleHR1cmUiKX0pfWxldCBVPXguZ2V0UmVuZGVyVGFyZ2V0KCk7eC5zZXRSZW5kZXJUYXJnZXQoRykseC5jbGVhcigpO2xldCBaPXgudG9uZU1hcHBpbmc7eC50b25lTWFwcGluZz10aSxEbyhNLEksTykseC50b25lTWFwcGluZz1aLEMudXBkYXRlTXVsdGlzYW1wbGVSZW5kZXJUYXJnZXQoRyksQy51cGRhdGVSZW5kZXJUYXJnZXRNaXBtYXAoRykseC5zZXRSZW5kZXJUYXJnZXQoVSl9ZnVuY3Rpb24gRG8oTSxJLE8pe2xldCBVPUkuaXNTY2VuZT09PSEwP0kub3ZlcnJpZGVNYXRlcmlhbDpudWxsO2ZvcihsZXQgWj0wLFR0PU0ubGVuZ3RoO1o8VHQ7WisrKXtsZXQgQ3Q9TVtaXSxMdD1DdC5vYmplY3QsUnQ9Q3QuZ2VvbWV0cnksR3Q9VT09PW51bGw/Q3QubWF0ZXJpYWw6VSx6dD1DdC5ncm91cDtMdC5sYXllcnMudGVzdChPLmxheWVycykmJlUwKEx0LEksTyxSdCxHdCx6dCl9fWZ1bmN0aW9uIFUwKE0sSSxPLFUsWixUdCl7TS5vbkJlZm9yZVJlbmRlcih4LEksTyxVLFosVHQpLE0ubW9kZWxWaWV3TWF0cml4Lm11bHRpcGx5TWF0cmljZXMoTy5tYXRyaXhXb3JsZEludmVyc2UsTS5tYXRyaXhXb3JsZCksTS5ub3JtYWxNYXRyaXguZ2V0Tm9ybWFsTWF0cml4KE0ubW9kZWxWaWV3TWF0cml4KSxaLm9uQmVmb3JlUmVuZGVyKHgsSSxPLFUsTSxUdCksWi50cmFuc3BhcmVudD09PSEwJiZaLnNpZGU9PT1Icj8oWi5zaWRlPWhlLFoubmVlZHNVcGRhdGU9ITAseC5yZW5kZXJCdWZmZXJEaXJlY3QoTyxJLFUsWixNLFR0KSxaLnNpZGU9ZW8sWi5uZWVkc1VwZGF0ZT0hMCx4LnJlbmRlckJ1ZmZlckRpcmVjdChPLEksVSxaLE0sVHQpLFouc2lkZT1Icik6eC5yZW5kZXJCdWZmZXJEaXJlY3QoTyxJLFUsWixNLFR0KSxNLm9uQWZ0ZXJSZW5kZXIoeCxJLE8sVSxaLFR0KX1mdW5jdGlvbiBPbChNLEksTyl7SS5pc1NjZW5lIT09ITAmJihJPWR0KTtsZXQgVT1udC5nZXQoTSksWj1mLnN0YXRlLmxpZ2h0cyxUdD1mLnN0YXRlLnNoYWRvd3NBcnJheSxDdD1aLnN0YXRlLnZlcnNpb24sTHQ9YnQuZ2V0UGFyYW1ldGVycyhNLFouc3RhdGUsVHQsSSxPKSxSdD1idC5nZXRQcm9ncmFtQ2FjaGVLZXkoTHQpLEd0PVUucHJvZ3JhbXM7VS5lbnZpcm9ubWVudD1NLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWw/SS5lbnZpcm9ubWVudDpudWxsLFUuZm9nPUkuZm9nLFUuZW52TWFwPShNLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWw/SjpqKS5nZXQoTS5lbnZNYXB8fFUuZW52aXJvbm1lbnQpLEd0PT09dm9pZCAwJiYoTS5hZGRFdmVudExpc3RlbmVyKCJkaXNwb3NlIixwdCksR3Q9bmV3IE1hcCxVLnByb2dyYW1zPUd0KTtsZXQgenQ9R3QuZ2V0KFJ0KTtpZih6dCE9PXZvaWQgMCl7aWYoVS5jdXJyZW50UHJvZ3JhbT09PXp0JiZVLmxpZ2h0c1N0YXRlVmVyc2lvbj09PUN0KXJldHVybiBvZihNLEx0KSx6dH1lbHNlIEx0LnVuaWZvcm1zPWJ0LmdldFVuaWZvcm1zKE0pLE0ub25CdWlsZChPLEx0LHgpLE0ub25CZWZvcmVDb21waWxlKEx0LHgpLHp0PWJ0LmFjcXVpcmVQcm9ncmFtKEx0LFJ0KSxHdC5zZXQoUnQsenQpLFUudW5pZm9ybXM9THQudW5pZm9ybXM7bGV0IFV0PVUudW5pZm9ybXM7KCFNLmlzU2hhZGVyTWF0ZXJpYWwmJiFNLmlzUmF3U2hhZGVyTWF0ZXJpYWx8fE0uY2xpcHBpbmc9PT0hMCkmJihVdC5jbGlwcGluZ1BsYW5lcz1FLnVuaWZvcm0pLG9mKE0sTHQpLFUubmVlZHNMaWdodHM9azAoTSksVS5saWdodHNTdGF0ZVZlcnNpb249Q3QsVS5uZWVkc0xpZ2h0cyYmKFV0LmFtYmllbnRMaWdodENvbG9yLnZhbHVlPVouc3RhdGUuYW1iaWVudCxVdC5saWdodFByb2JlLnZhbHVlPVouc3RhdGUucHJvYmUsVXQuZGlyZWN0aW9uYWxMaWdodHMudmFsdWU9Wi5zdGF0ZS5kaXJlY3Rpb25hbCxVdC5kaXJlY3Rpb25hbExpZ2h0U2hhZG93cy52YWx1ZT1aLnN0YXRlLmRpcmVjdGlvbmFsU2hhZG93LFV0LnNwb3RMaWdodHMudmFsdWU9Wi5zdGF0ZS5zcG90LFV0LnNwb3RMaWdodFNoYWRvd3MudmFsdWU9Wi5zdGF0ZS5zcG90U2hhZG93LFV0LnJlY3RBcmVhTGlnaHRzLnZhbHVlPVouc3RhdGUucmVjdEFyZWEsVXQubHRjXzEudmFsdWU9Wi5zdGF0ZS5yZWN0QXJlYUxUQzEsVXQubHRjXzIudmFsdWU9Wi5zdGF0ZS5yZWN0QXJlYUxUQzIsVXQucG9pbnRMaWdodHMudmFsdWU9Wi5zdGF0ZS5wb2ludCxVdC5wb2ludExpZ2h0U2hhZG93cy52YWx1ZT1aLnN0YXRlLnBvaW50U2hhZG93LFV0LmhlbWlzcGhlcmVMaWdodHMudmFsdWU9Wi5zdGF0ZS5oZW1pLFV0LmRpcmVjdGlvbmFsU2hhZG93TWFwLnZhbHVlPVouc3RhdGUuZGlyZWN0aW9uYWxTaGFkb3dNYXAsVXQuZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXgudmFsdWU9Wi5zdGF0ZS5kaXJlY3Rpb25hbFNoYWRvd01hdHJpeCxVdC5zcG90U2hhZG93TWFwLnZhbHVlPVouc3RhdGUuc3BvdFNoYWRvd01hcCxVdC5zcG90U2hhZG93TWF0cml4LnZhbHVlPVouc3RhdGUuc3BvdFNoYWRvd01hdHJpeCxVdC5wb2ludFNoYWRvd01hcC52YWx1ZT1aLnN0YXRlLnBvaW50U2hhZG93TWFwLFV0LnBvaW50U2hhZG93TWF0cml4LnZhbHVlPVouc3RhdGUucG9pbnRTaGFkb3dNYXRyaXgpO2xldCBpZT16dC5nZXRVbmlmb3JtcygpLGZpPWlpLnNlcVdpdGhWYWx1ZShpZS5zZXEsVXQpO3JldHVybiBVLmN1cnJlbnRQcm9ncmFtPXp0LFUudW5pZm9ybXNMaXN0PWZpLHp0fWZ1bmN0aW9uIG9mKE0sSSl7bGV0IE89bnQuZ2V0KE0pO08ub3V0cHV0RW5jb2Rpbmc9SS5vdXRwdXRFbmNvZGluZyxPLmluc3RhbmNpbmc9SS5pbnN0YW5jaW5nLE8uc2tpbm5pbmc9SS5za2lubmluZyxPLm1vcnBoVGFyZ2V0cz1JLm1vcnBoVGFyZ2V0cyxPLm1vcnBoTm9ybWFscz1JLm1vcnBoTm9ybWFscyxPLm1vcnBoVGFyZ2V0c0NvdW50PUkubW9ycGhUYXJnZXRzQ291bnQsTy5udW1DbGlwcGluZ1BsYW5lcz1JLm51bUNsaXBwaW5nUGxhbmVzLE8ubnVtSW50ZXJzZWN0aW9uPUkubnVtQ2xpcEludGVyc2VjdGlvbixPLnZlcnRleEFscGhhcz1JLnZlcnRleEFscGhhcyxPLnZlcnRleFRhbmdlbnRzPUkudmVydGV4VGFuZ2VudHMsTy50b25lTWFwcGluZz1JLnRvbmVNYXBwaW5nfWZ1bmN0aW9uIEIwKE0sSSxPLFUsWil7SS5pc1NjZW5lIT09ITAmJihJPWR0KSxDLnJlc2V0VGV4dHVyZVVuaXRzKCk7bGV0IFR0PUkuZm9nLEN0PVUuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbD9JLmVudmlyb25tZW50Om51bGwsTHQ9Yj09PW51bGw/eC5vdXRwdXRFbmNvZGluZzpiLmlzWFJSZW5kZXJUYXJnZXQ9PT0hMD9iLnRleHR1cmUuZW5jb2Rpbmc6cmksUnQ9KFUuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbD9KOmopLmdldChVLmVudk1hcHx8Q3QpLEd0PVUudmVydGV4Q29sb3JzPT09ITAmJiEhTy5hdHRyaWJ1dGVzLmNvbG9yJiZPLmF0dHJpYnV0ZXMuY29sb3IuaXRlbVNpemU9PT00LHp0PSEhVS5ub3JtYWxNYXAmJiEhTy5hdHRyaWJ1dGVzLnRhbmdlbnQsVXQ9ISFPLm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbixpZT0hIU8ubW9ycGhBdHRyaWJ1dGVzLm5vcm1hbCxmaT1PLm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbj9PLm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbi5sZW5ndGg6MCxaaT1VLnRvbmVNYXBwZWQ/eC50b25lTWFwcGluZzp0aSxPdD1udC5nZXQoVSksbm49Zi5zdGF0ZS5saWdodHM7aWYoUT09PSEwJiYoYXQ9PT0hMHx8TSE9PVMpKXtsZXQgWmU9TT09PVMmJlUuaWQ9PT1fO0Uuc2V0U3RhdGUoVSxNLFplKX1sZXQgbGU9ITE7VS52ZXJzaW9uPT09T3QuX192ZXJzaW9uPyhPdC5uZWVkc0xpZ2h0cyYmT3QubGlnaHRzU3RhdGVWZXJzaW9uIT09bm4uc3RhdGUudmVyc2lvbnx8T3Qub3V0cHV0RW5jb2RpbmchPT1MdHx8Wi5pc0luc3RhbmNlZE1lc2gmJk90Lmluc3RhbmNpbmc9PT0hMXx8IVouaXNJbnN0YW5jZWRNZXNoJiZPdC5pbnN0YW5jaW5nPT09ITB8fFouaXNTa2lubmVkTWVzaCYmT3Quc2tpbm5pbmc9PT0hMXx8IVouaXNTa2lubmVkTWVzaCYmT3Quc2tpbm5pbmc9PT0hMHx8T3QuZW52TWFwIT09UnR8fFUuZm9nJiZPdC5mb2chPT1UdHx8T3QubnVtQ2xpcHBpbmdQbGFuZXMhPT12b2lkIDAmJihPdC5udW1DbGlwcGluZ1BsYW5lcyE9PUUubnVtUGxhbmVzfHxPdC5udW1JbnRlcnNlY3Rpb24hPT1FLm51bUludGVyc2VjdGlvbil8fE90LnZlcnRleEFscGhhcyE9PUd0fHxPdC52ZXJ0ZXhUYW5nZW50cyE9PXp0fHxPdC5tb3JwaFRhcmdldHMhPT1VdHx8T3QubW9ycGhOb3JtYWxzIT09aWV8fE90LnRvbmVNYXBwaW5nIT09Wml8fFN0LmlzV2ViR0wyPT09ITAmJk90Lm1vcnBoVGFyZ2V0c0NvdW50IT09ZmkpJiYobGU9ITApOihsZT0hMCxPdC5fX3ZlcnNpb249VS52ZXJzaW9uKTtsZXQgcm49T3QuY3VycmVudFByb2dyYW07bGU9PT0hMCYmKHJuPU9sKFUsSSxaKSk7bGV0IElvPSExLHNuPSExLHZuPSExLF9lPXJuLmdldFVuaWZvcm1zKCksbnM9T3QudW5pZm9ybXM7aWYoQi51c2VQcm9ncmFtKHJuLnByb2dyYW0pJiYoSW89ITAsc249ITAsdm49ITApLFUuaWQhPT1fJiYoXz1VLmlkLHNuPSEwKSxJb3x8UyE9PU0pe2lmKF9lLnNldFZhbHVlKGssInByb2plY3Rpb25NYXRyaXgiLE0ucHJvamVjdGlvbk1hdHJpeCksU3QubG9nYXJpdGhtaWNEZXB0aEJ1ZmZlciYmX2Uuc2V0VmFsdWUoaywibG9nRGVwdGhCdWZGQyIsMi8oTWF0aC5sb2coTS5mYXIrMSkvTWF0aC5MTjIpKSxTIT09TSYmKFM9TSxzbj0hMCx2bj0hMCksVS5pc1NoYWRlck1hdGVyaWFsfHxVLmlzTWVzaFBob25nTWF0ZXJpYWx8fFUuaXNNZXNoVG9vbk1hdGVyaWFsfHxVLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWx8fFUuZW52TWFwKXtsZXQgWmU9X2UubWFwLmNhbWVyYVBvc2l0aW9uO1plIT09dm9pZCAwJiZaZS5zZXRWYWx1ZShrLGx0LnNldEZyb21NYXRyaXhQb3NpdGlvbihNLm1hdHJpeFdvcmxkKSl9KFUuaXNNZXNoUGhvbmdNYXRlcmlhbHx8VS5pc01lc2hUb29uTWF0ZXJpYWx8fFUuaXNNZXNoTGFtYmVydE1hdGVyaWFsfHxVLmlzTWVzaEJhc2ljTWF0ZXJpYWx8fFUuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbHx8VS5pc1NoYWRlck1hdGVyaWFsKSYmX2Uuc2V0VmFsdWUoaywiaXNPcnRob2dyYXBoaWMiLE0uaXNPcnRob2dyYXBoaWNDYW1lcmE9PT0hMCksKFUuaXNNZXNoUGhvbmdNYXRlcmlhbHx8VS5pc01lc2hUb29uTWF0ZXJpYWx8fFUuaXNNZXNoTGFtYmVydE1hdGVyaWFsfHxVLmlzTWVzaEJhc2ljTWF0ZXJpYWx8fFUuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbHx8VS5pc1NoYWRlck1hdGVyaWFsfHxVLmlzU2hhZG93TWF0ZXJpYWx8fFouaXNTa2lubmVkTWVzaCkmJl9lLnNldFZhbHVlKGssInZpZXdNYXRyaXgiLE0ubWF0cml4V29ybGRJbnZlcnNlKX1pZihaLmlzU2tpbm5lZE1lc2gpe19lLnNldE9wdGlvbmFsKGssWiwiYmluZE1hdHJpeCIpLF9lLnNldE9wdGlvbmFsKGssWiwiYmluZE1hdHJpeEludmVyc2UiKTtsZXQgWmU9Wi5za2VsZXRvbjtaZSYmKFN0LmZsb2F0VmVydGV4VGV4dHVyZXM/KFplLmJvbmVUZXh0dXJlPT09bnVsbCYmWmUuY29tcHV0ZUJvbmVUZXh0dXJlKCksX2Uuc2V0VmFsdWUoaywiYm9uZVRleHR1cmUiLFplLmJvbmVUZXh0dXJlLEMpLF9lLnNldFZhbHVlKGssImJvbmVUZXh0dXJlU2l6ZSIsWmUuYm9uZVRleHR1cmVTaXplKSk6X2Uuc2V0T3B0aW9uYWwoayxaZSwiYm9uZU1hdHJpY2VzIikpfXJldHVybiEhTyYmKE8ubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uIT09dm9pZCAwfHxPLm1vcnBoQXR0cmlidXRlcy5ub3JtYWwhPT12b2lkIDApJiZydC51cGRhdGUoWixPLFUscm4pLChzbnx8T3QucmVjZWl2ZVNoYWRvdyE9PVoucmVjZWl2ZVNoYWRvdykmJihPdC5yZWNlaXZlU2hhZG93PVoucmVjZWl2ZVNoYWRvdyxfZS5zZXRWYWx1ZShrLCJyZWNlaXZlU2hhZG93IixaLnJlY2VpdmVTaGFkb3cpKSxzbiYmKF9lLnNldFZhbHVlKGssInRvbmVNYXBwaW5nRXhwb3N1cmUiLHgudG9uZU1hcHBpbmdFeHBvc3VyZSksT3QubmVlZHNMaWdodHMmJk8wKG5zLHZuKSxUdCYmVS5mb2cmJkl0LnJlZnJlc2hGb2dVbmlmb3JtcyhucyxUdCksSXQucmVmcmVzaE1hdGVyaWFsVW5pZm9ybXMobnMsVSx5LFgsRyksaWkudXBsb2FkKGssT3QudW5pZm9ybXNMaXN0LG5zLEMpKSxVLmlzU2hhZGVyTWF0ZXJpYWwmJlUudW5pZm9ybXNOZWVkVXBkYXRlPT09ITAmJihpaS51cGxvYWQoayxPdC51bmlmb3Jtc0xpc3QsbnMsQyksVS51bmlmb3Jtc05lZWRVcGRhdGU9ITEpLFUuaXNTcHJpdGVNYXRlcmlhbCYmX2Uuc2V0VmFsdWUoaywiY2VudGVyIixaLmNlbnRlciksX2Uuc2V0VmFsdWUoaywibW9kZWxWaWV3TWF0cml4IixaLm1vZGVsVmlld01hdHJpeCksX2Uuc2V0VmFsdWUoaywibm9ybWFsTWF0cml4IixaLm5vcm1hbE1hdHJpeCksX2Uuc2V0VmFsdWUoaywibW9kZWxNYXRyaXgiLFoubWF0cml4V29ybGQpLHJufWZ1bmN0aW9uIE8wKE0sSSl7TS5hbWJpZW50TGlnaHRDb2xvci5uZWVkc1VwZGF0ZT1JLE0ubGlnaHRQcm9iZS5uZWVkc1VwZGF0ZT1JLE0uZGlyZWN0aW9uYWxMaWdodHMubmVlZHNVcGRhdGU9SSxNLmRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzLm5lZWRzVXBkYXRlPUksTS5wb2ludExpZ2h0cy5uZWVkc1VwZGF0ZT1JLE0ucG9pbnRMaWdodFNoYWRvd3MubmVlZHNVcGRhdGU9SSxNLnNwb3RMaWdodHMubmVlZHNVcGRhdGU9SSxNLnNwb3RMaWdodFNoYWRvd3MubmVlZHNVcGRhdGU9SSxNLnJlY3RBcmVhTGlnaHRzLm5lZWRzVXBkYXRlPUksTS5oZW1pc3BoZXJlTGlnaHRzLm5lZWRzVXBkYXRlPUl9ZnVuY3Rpb24gazAoTSl7cmV0dXJuIE0uaXNNZXNoTGFtYmVydE1hdGVyaWFsfHxNLmlzTWVzaFRvb25NYXRlcmlhbHx8TS5pc01lc2hQaG9uZ01hdGVyaWFsfHxNLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWx8fE0uaXNTaGFkb3dNYXRlcmlhbHx8TS5pc1NoYWRlck1hdGVyaWFsJiZNLmxpZ2h0cz09PSEwfXRoaXMuZ2V0QWN0aXZlQ3ViZUZhY2U9ZnVuY3Rpb24oKXtyZXR1cm4gbX0sdGhpcy5nZXRBY3RpdmVNaXBtYXBMZXZlbD1mdW5jdGlvbigpe3JldHVybiBwfSx0aGlzLmdldFJlbmRlclRhcmdldD1mdW5jdGlvbigpe3JldHVybiBifSx0aGlzLnNldFJlbmRlclRhcmdldFRleHR1cmVzPWZ1bmN0aW9uKE0sSSxPKXtudC5nZXQoTS50ZXh0dXJlKS5fX3dlYmdsVGV4dHVyZT1JLG50LmdldChNLmRlcHRoVGV4dHVyZSkuX193ZWJnbFRleHR1cmU9TztsZXQgVT1udC5nZXQoTSk7VS5fX2hhc0V4dGVybmFsVGV4dHVyZXM9ITAsVS5fX2hhc0V4dGVybmFsVGV4dHVyZXMmJihVLl9fYXV0b0FsbG9jYXRlRGVwdGhCdWZmZXI9Tz09PXZvaWQgMCxVLl9fYXV0b0FsbG9jYXRlRGVwdGhCdWZmZXJ8fE0udXNlUmVuZGVyVG9UZXh0dXJlJiYoY29uc29sZS53YXJuKCJyZW5kZXItdG8tdGV4dHVyZSBleHRlbnNpb24gd2FzIGRpc2FibGVkIGJlY2F1c2UgYW4gZXh0ZXJuYWwgdGV4dHVyZSB3YXMgcHJvdmlkZWQiKSxNLnVzZVJlbmRlclRvVGV4dHVyZT0hMSxNLnVzZVJlbmRlcmJ1ZmZlcj0hMCkpfSx0aGlzLnNldFJlbmRlclRhcmdldEZyYW1lYnVmZmVyPWZ1bmN0aW9uKE0sSSl7bGV0IE89bnQuZ2V0KE0pO08uX193ZWJnbEZyYW1lYnVmZmVyPUksTy5fX3VzZURlZmF1bHRGcmFtZWJ1ZmZlcj1JPT09dm9pZCAwfSx0aGlzLnNldFJlbmRlclRhcmdldD1mdW5jdGlvbihNLEk9MCxPPTApe2I9TSxtPUkscD1PO2xldCBVPSEwO2lmKE0pe2xldCBSdD1udC5nZXQoTSk7UnQuX191c2VEZWZhdWx0RnJhbWVidWZmZXIhPT12b2lkIDA/KEIuYmluZEZyYW1lYnVmZmVyKDM2MTYwLG51bGwpLFU9ITEpOlJ0Ll9fd2ViZ2xGcmFtZWJ1ZmZlcj09PXZvaWQgMD9DLnNldHVwUmVuZGVyVGFyZ2V0KE0pOlJ0Ll9faGFzRXh0ZXJuYWxUZXh0dXJlcyYmQy5yZWJpbmRUZXh0dXJlcyhNLG50LmdldChNLnRleHR1cmUpLl9fd2ViZ2xUZXh0dXJlLG50LmdldChNLmRlcHRoVGV4dHVyZSkuX193ZWJnbFRleHR1cmUpfWxldCBaPW51bGwsVHQ9ITEsQ3Q9ITE7aWYoTSl7bGV0IFJ0PU0udGV4dHVyZTsoUnQuaXNEYXRhVGV4dHVyZTNEfHxSdC5pc0RhdGFUZXh0dXJlMkRBcnJheSkmJihDdD0hMCk7bGV0IEd0PW50LmdldChNKS5fX3dlYmdsRnJhbWVidWZmZXI7TS5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldD8oWj1HdFtJXSxUdD0hMCk6TS51c2VSZW5kZXJidWZmZXI/Wj1udC5nZXQoTSkuX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyOlo9R3QsTC5jb3B5KE0udmlld3BvcnQpLEEuY29weShNLnNjaXNzb3IpLEg9TS5zY2lzc29yVGVzdH1lbHNlIEwuY29weShGKS5tdWx0aXBseVNjYWxhcih5KS5mbG9vcigpLEEuY29weSh6KS5tdWx0aXBseVNjYWxhcih5KS5mbG9vcigpLEg9TjtpZihCLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxaKSYmU3QuZHJhd0J1ZmZlcnMmJlUmJkIuZHJhd0J1ZmZlcnMoTSxaKSxCLnZpZXdwb3J0KEwpLEIuc2Npc3NvcihBKSxCLnNldFNjaXNzb3JUZXN0KEgpLFR0KXtsZXQgUnQ9bnQuZ2V0KE0udGV4dHVyZSk7ay5mcmFtZWJ1ZmZlclRleHR1cmUyRCgzNjE2MCwzNjA2NCwzNDA2OStJLFJ0Ll9fd2ViZ2xUZXh0dXJlLE8pfWVsc2UgaWYoQ3Qpe2xldCBSdD1udC5nZXQoTS50ZXh0dXJlKSxHdD1JfHwwO2suZnJhbWVidWZmZXJUZXh0dXJlTGF5ZXIoMzYxNjAsMzYwNjQsUnQuX193ZWJnbFRleHR1cmUsT3x8MCxHdCl9Xz0tMX0sdGhpcy5yZWFkUmVuZGVyVGFyZ2V0UGl4ZWxzPWZ1bmN0aW9uKE0sSSxPLFUsWixUdCxDdCl7aWYoIShNJiZNLmlzV2ViR0xSZW5kZXJUYXJnZXQpKXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyLnJlYWRSZW5kZXJUYXJnZXRQaXhlbHM6IHJlbmRlclRhcmdldCBpcyBub3QgVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQuIik7cmV0dXJufWxldCBMdD1udC5nZXQoTSkuX193ZWJnbEZyYW1lYnVmZmVyO2lmKE0uaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQmJkN0IT09dm9pZCAwJiYoTHQ9THRbQ3RdKSxMdCl7Qi5iaW5kRnJhbWVidWZmZXIoMzYxNjAsTHQpO3RyeXtsZXQgUnQ9TS50ZXh0dXJlLEd0PVJ0LmZvcm1hdCx6dD1SdC50eXBlO2lmKEd0IT09UmUmJl90LmNvbnZlcnQoR3QpIT09ay5nZXRQYXJhbWV0ZXIoMzU3MzkpKXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyLnJlYWRSZW5kZXJUYXJnZXRQaXhlbHM6IHJlbmRlclRhcmdldCBpcyBub3QgaW4gUkdCQSBvciBpbXBsZW1lbnRhdGlvbiBkZWZpbmVkIGZvcm1hdC4iKTtyZXR1cm59bGV0IFV0PXp0PT09VXImJihtdC5oYXMoIkVYVF9jb2xvcl9idWZmZXJfaGFsZl9mbG9hdCIpfHxTdC5pc1dlYkdMMiYmbXQuaGFzKCJFWFRfY29sb3JfYnVmZmVyX2Zsb2F0IikpO2lmKHp0IT09ZWkmJl90LmNvbnZlcnQoenQpIT09ay5nZXRQYXJhbWV0ZXIoMzU3MzgpJiYhKHp0PT09VWkmJihTdC5pc1dlYkdMMnx8bXQuaGFzKCJPRVNfdGV4dHVyZV9mbG9hdCIpfHxtdC5oYXMoIldFQkdMX2NvbG9yX2J1ZmZlcl9mbG9hdCIpKSkmJiFVdCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlci5yZWFkUmVuZGVyVGFyZ2V0UGl4ZWxzOiByZW5kZXJUYXJnZXQgaXMgbm90IGluIFVuc2lnbmVkQnl0ZVR5cGUgb3IgaW1wbGVtZW50YXRpb24gZGVmaW5lZCB0eXBlLiIpO3JldHVybn1rLmNoZWNrRnJhbWVidWZmZXJTdGF0dXMoMzYxNjApPT09MzYwNTM/ST49MCYmSTw9TS53aWR0aC1VJiZPPj0wJiZPPD1NLmhlaWdodC1aJiZrLnJlYWRQaXhlbHMoSSxPLFUsWixfdC5jb252ZXJ0KEd0KSxfdC5jb252ZXJ0KHp0KSxUdCk6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlci5yZWFkUmVuZGVyVGFyZ2V0UGl4ZWxzOiByZWFkUGl4ZWxzIGZyb20gcmVuZGVyVGFyZ2V0IGZhaWxlZC4gRnJhbWVidWZmZXIgbm90IGNvbXBsZXRlLiIpfWZpbmFsbHl7bGV0IFJ0PWIhPT1udWxsP250LmdldChiKS5fX3dlYmdsRnJhbWVidWZmZXI6bnVsbDtCLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxSdCl9fX0sdGhpcy5jb3B5RnJhbWVidWZmZXJUb1RleHR1cmU9ZnVuY3Rpb24oTSxJLE89MCl7aWYoSS5pc0ZyYW1lYnVmZmVyVGV4dHVyZSE9PSEwKXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBjb3B5RnJhbWVidWZmZXJUb1RleHR1cmUoKSBjYW4gb25seSBiZSB1c2VkIHdpdGggRnJhbWVidWZmZXJUZXh0dXJlLiIpO3JldHVybn1sZXQgVT1NYXRoLnBvdygyLC1PKSxaPU1hdGguZmxvb3IoSS5pbWFnZS53aWR0aCpVKSxUdD1NYXRoLmZsb29yKEkuaW1hZ2UuaGVpZ2h0KlUpO0Muc2V0VGV4dHVyZTJEKEksMCksay5jb3B5VGV4U3ViSW1hZ2UyRCgzNTUzLE8sMCwwLE0ueCxNLnksWixUdCksQi51bmJpbmRUZXh0dXJlKCl9LHRoaXMuY29weVRleHR1cmVUb1RleHR1cmU9ZnVuY3Rpb24oTSxJLE8sVT0wKXtsZXQgWj1JLmltYWdlLndpZHRoLFR0PUkuaW1hZ2UuaGVpZ2h0LEN0PV90LmNvbnZlcnQoTy5mb3JtYXQpLEx0PV90LmNvbnZlcnQoTy50eXBlKTtDLnNldFRleHR1cmUyRChPLDApLGsucGl4ZWxTdG9yZWkoMzc0NDAsTy5mbGlwWSksay5waXhlbFN0b3JlaSgzNzQ0MSxPLnByZW11bHRpcGx5QWxwaGEpLGsucGl4ZWxTdG9yZWkoMzMxNyxPLnVucGFja0FsaWdubWVudCksSS5pc0RhdGFUZXh0dXJlP2sudGV4U3ViSW1hZ2UyRCgzNTUzLFUsTS54LE0ueSxaLFR0LEN0LEx0LEkuaW1hZ2UuZGF0YSk6SS5pc0NvbXByZXNzZWRUZXh0dXJlP2suY29tcHJlc3NlZFRleFN1YkltYWdlMkQoMzU1MyxVLE0ueCxNLnksSS5taXBtYXBzWzBdLndpZHRoLEkubWlwbWFwc1swXS5oZWlnaHQsQ3QsSS5taXBtYXBzWzBdLmRhdGEpOmsudGV4U3ViSW1hZ2UyRCgzNTUzLFUsTS54LE0ueSxDdCxMdCxJLmltYWdlKSxVPT09MCYmTy5nZW5lcmF0ZU1pcG1hcHMmJmsuZ2VuZXJhdGVNaXBtYXAoMzU1MyksQi51bmJpbmRUZXh0dXJlKCl9LHRoaXMuY29weVRleHR1cmVUb1RleHR1cmUzRD1mdW5jdGlvbihNLEksTyxVLFo9MCl7aWYoeC5pc1dlYkdMMVJlbmRlcmVyKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXIuY29weVRleHR1cmVUb1RleHR1cmUzRDogY2FuIG9ubHkgYmUgdXNlZCB3aXRoIFdlYkdMMi4iKTtyZXR1cm59bGV0IFR0PU0ubWF4LngtTS5taW4ueCsxLEN0PU0ubWF4LnktTS5taW4ueSsxLEx0PU0ubWF4LnotTS5taW4ueisxLFJ0PV90LmNvbnZlcnQoVS5mb3JtYXQpLEd0PV90LmNvbnZlcnQoVS50eXBlKSx6dDtpZihVLmlzRGF0YVRleHR1cmUzRClDLnNldFRleHR1cmUzRChVLDApLHp0PTMyODc5O2Vsc2UgaWYoVS5pc0RhdGFUZXh0dXJlMkRBcnJheSlDLnNldFRleHR1cmUyREFycmF5KFUsMCksenQ9MzU4NjY7ZWxzZXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXIuY29weVRleHR1cmVUb1RleHR1cmUzRDogb25seSBzdXBwb3J0cyBUSFJFRS5EYXRhVGV4dHVyZTNEIGFuZCBUSFJFRS5EYXRhVGV4dHVyZTJEQXJyYXkuIik7cmV0dXJufWsucGl4ZWxTdG9yZWkoMzc0NDAsVS5mbGlwWSksay5waXhlbFN0b3JlaSgzNzQ0MSxVLnByZW11bHRpcGx5QWxwaGEpLGsucGl4ZWxTdG9yZWkoMzMxNyxVLnVucGFja0FsaWdubWVudCk7bGV0IFV0PWsuZ2V0UGFyYW1ldGVyKDMzMTQpLGllPWsuZ2V0UGFyYW1ldGVyKDMyODc4KSxmaT1rLmdldFBhcmFtZXRlcigzMzE2KSxaaT1rLmdldFBhcmFtZXRlcigzMzE1KSxPdD1rLmdldFBhcmFtZXRlcigzMjg3Nyksbm49Ty5pc0NvbXByZXNzZWRUZXh0dXJlP08ubWlwbWFwc1swXTpPLmltYWdlO2sucGl4ZWxTdG9yZWkoMzMxNCxubi53aWR0aCksay5waXhlbFN0b3JlaSgzMjg3OCxubi5oZWlnaHQpLGsucGl4ZWxTdG9yZWkoMzMxNixNLm1pbi54KSxrLnBpeGVsU3RvcmVpKDMzMTUsTS5taW4ueSksay5waXhlbFN0b3JlaSgzMjg3NyxNLm1pbi56KSxPLmlzRGF0YVRleHR1cmV8fE8uaXNEYXRhVGV4dHVyZTNEP2sudGV4U3ViSW1hZ2UzRCh6dCxaLEkueCxJLnksSS56LFR0LEN0LEx0LFJ0LEd0LG5uLmRhdGEpOk8uaXNDb21wcmVzc2VkVGV4dHVyZT8oY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyLmNvcHlUZXh0dXJlVG9UZXh0dXJlM0Q6IHVudGVzdGVkIHN1cHBvcnQgZm9yIGNvbXByZXNzZWQgc3JjVGV4dHVyZS4iKSxrLmNvbXByZXNzZWRUZXhTdWJJbWFnZTNEKHp0LFosSS54LEkueSxJLnosVHQsQ3QsTHQsUnQsbm4uZGF0YSkpOmsudGV4U3ViSW1hZ2UzRCh6dCxaLEkueCxJLnksSS56LFR0LEN0LEx0LFJ0LEd0LG5uKSxrLnBpeGVsU3RvcmVpKDMzMTQsVXQpLGsucGl4ZWxTdG9yZWkoMzI4NzgsaWUpLGsucGl4ZWxTdG9yZWkoMzMxNixmaSksay5waXhlbFN0b3JlaSgzMzE1LFppKSxrLnBpeGVsU3RvcmVpKDMyODc3LE90KSxaPT09MCYmVS5nZW5lcmF0ZU1pcG1hcHMmJmsuZ2VuZXJhdGVNaXBtYXAoenQpLEIudW5iaW5kVGV4dHVyZSgpfSx0aGlzLmluaXRUZXh0dXJlPWZ1bmN0aW9uKE0pe0Muc2V0VGV4dHVyZTJEKE0sMCksQi51bmJpbmRUZXh0dXJlKCl9LHRoaXMucmVzZXRTdGF0ZT1mdW5jdGlvbigpe209MCxwPTAsYj1udWxsLEIucmVzZXQoKSx5dC5yZXNldCgpfSx0eXBlb2YgX19USFJFRV9ERVZUT09MU19fIT0idW5kZWZpbmVkIiYmX19USFJFRV9ERVZUT09MU19fLmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCJvYnNlcnZlIix7ZGV0YWlsOnRoaXN9KSl9VnQucHJvdG90eXBlLmlzV2ViR0xSZW5kZXJlcj0hMDt2YXIgSnU9Y2xhc3MgZXh0ZW5kcyBWdHt9O0p1LnByb3RvdHlwZS5pc1dlYkdMMVJlbmRlcmVyPSEwO3ZhciBobz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGU9MjVlLTUpe3RoaXMubmFtZT0iIix0aGlzLmNvbG9yPW5ldyBmdCh0KSx0aGlzLmRlbnNpdHk9ZX1jbG9uZSgpe3JldHVybiBuZXcgaG8odGhpcy5jb2xvcix0aGlzLmRlbnNpdHkpfXRvSlNPTigpe3JldHVybnt0eXBlOiJGb2dFeHAyIixjb2xvcjp0aGlzLmNvbG9yLmdldEhleCgpLGRlbnNpdHk6dGhpcy5kZW5zaXR5fX19O2hvLnByb3RvdHlwZS5pc0ZvZ0V4cDI9ITA7dmFyIGZvPWNsYXNze2NvbnN0cnVjdG9yKHQsZT0xLGk9MWUzKXt0aGlzLm5hbWU9IiIsdGhpcy5jb2xvcj1uZXcgZnQodCksdGhpcy5uZWFyPWUsdGhpcy5mYXI9aX1jbG9uZSgpe3JldHVybiBuZXcgZm8odGhpcy5jb2xvcix0aGlzLm5lYXIsdGhpcy5mYXIpfXRvSlNPTigpe3JldHVybnt0eXBlOiJGb2ciLGNvbG9yOnRoaXMuY29sb3IuZ2V0SGV4KCksbmVhcjp0aGlzLm5lYXIsZmFyOnRoaXMuZmFyfX19O2ZvLnByb3RvdHlwZS5pc0ZvZz0hMDt2YXIgWXI9Y2xhc3MgZXh0ZW5kcyBrdHtjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy50eXBlPSJTY2VuZSIsdGhpcy5iYWNrZ3JvdW5kPW51bGwsdGhpcy5lbnZpcm9ubWVudD1udWxsLHRoaXMuZm9nPW51bGwsdGhpcy5vdmVycmlkZU1hdGVyaWFsPW51bGwsdGhpcy5hdXRvVXBkYXRlPSEwLHR5cGVvZiBfX1RIUkVFX0RFVlRPT0xTX18hPSJ1bmRlZmluZWQiJiZfX1RIUkVFX0RFVlRPT0xTX18uZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoIm9ic2VydmUiLHtkZXRhaWw6dGhpc30pKX1jb3B5KHQsZSl7cmV0dXJuIHN1cGVyLmNvcHkodCxlKSx0LmJhY2tncm91bmQhPT1udWxsJiYodGhpcy5iYWNrZ3JvdW5kPXQuYmFja2dyb3VuZC5jbG9uZSgpKSx0LmVudmlyb25tZW50IT09bnVsbCYmKHRoaXMuZW52aXJvbm1lbnQ9dC5lbnZpcm9ubWVudC5jbG9uZSgpKSx0LmZvZyE9PW51bGwmJih0aGlzLmZvZz10LmZvZy5jbG9uZSgpKSx0Lm92ZXJyaWRlTWF0ZXJpYWwhPT1udWxsJiYodGhpcy5vdmVycmlkZU1hdGVyaWFsPXQub3ZlcnJpZGVNYXRlcmlhbC5jbG9uZSgpKSx0aGlzLmF1dG9VcGRhdGU9dC5hdXRvVXBkYXRlLHRoaXMubWF0cml4QXV0b1VwZGF0ZT10Lm1hdHJpeEF1dG9VcGRhdGUsdGhpc310b0pTT04odCl7bGV0IGU9c3VwZXIudG9KU09OKHQpO3JldHVybiB0aGlzLmZvZyE9PW51bGwmJihlLm9iamVjdC5mb2c9dGhpcy5mb2cudG9KU09OKCkpLGV9fTtZci5wcm90b3R5cGUuaXNTY2VuZT0hMDt2YXIgR2k9Y2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLmFycmF5PXQsdGhpcy5zdHJpZGU9ZSx0aGlzLmNvdW50PXQhPT12b2lkIDA/dC5sZW5ndGgvZTowLHRoaXMudXNhZ2U9aW8sdGhpcy51cGRhdGVSYW5nZT17b2Zmc2V0OjAsY291bnQ6LTF9LHRoaXMudmVyc2lvbj0wLHRoaXMudXVpZD10bigpfW9uVXBsb2FkQ2FsbGJhY2soKXt9c2V0IG5lZWRzVXBkYXRlKHQpe3Q9PT0hMCYmdGhpcy52ZXJzaW9uKyt9c2V0VXNhZ2UodCl7cmV0dXJuIHRoaXMudXNhZ2U9dCx0aGlzfWNvcHkodCl7cmV0dXJuIHRoaXMuYXJyYXk9bmV3IHQuYXJyYXkuY29uc3RydWN0b3IodC5hcnJheSksdGhpcy5jb3VudD10LmNvdW50LHRoaXMuc3RyaWRlPXQuc3RyaWRlLHRoaXMudXNhZ2U9dC51c2FnZSx0aGlzfWNvcHlBdCh0LGUsaSl7dCo9dGhpcy5zdHJpZGUsaSo9ZS5zdHJpZGU7Zm9yKGxldCByPTAscz10aGlzLnN0cmlkZTtyPHM7cisrKXRoaXMuYXJyYXlbdCtyXT1lLmFycmF5W2krcl07cmV0dXJuIHRoaXN9c2V0KHQsZT0wKXtyZXR1cm4gdGhpcy5hcnJheS5zZXQodCxlKSx0aGlzfWNsb25lKHQpe3QuYXJyYXlCdWZmZXJzPT09dm9pZCAwJiYodC5hcnJheUJ1ZmZlcnM9e30pLHRoaXMuYXJyYXkuYnVmZmVyLl91dWlkPT09dm9pZCAwJiYodGhpcy5hcnJheS5idWZmZXIuX3V1aWQ9dG4oKSksdC5hcnJheUJ1ZmZlcnNbdGhpcy5hcnJheS5idWZmZXIuX3V1aWRdPT09dm9pZCAwJiYodC5hcnJheUJ1ZmZlcnNbdGhpcy5hcnJheS5idWZmZXIuX3V1aWRdPXRoaXMuYXJyYXkuc2xpY2UoMCkuYnVmZmVyKTtsZXQgZT1uZXcgdGhpcy5hcnJheS5jb25zdHJ1Y3Rvcih0LmFycmF5QnVmZmVyc1t0aGlzLmFycmF5LmJ1ZmZlci5fdXVpZF0pLGk9bmV3IHRoaXMuY29uc3RydWN0b3IoZSx0aGlzLnN0cmlkZSk7cmV0dXJuIGkuc2V0VXNhZ2UodGhpcy51c2FnZSksaX1vblVwbG9hZCh0KXtyZXR1cm4gdGhpcy5vblVwbG9hZENhbGxiYWNrPXQsdGhpc310b0pTT04odCl7cmV0dXJuIHQuYXJyYXlCdWZmZXJzPT09dm9pZCAwJiYodC5hcnJheUJ1ZmZlcnM9e30pLHRoaXMuYXJyYXkuYnVmZmVyLl91dWlkPT09dm9pZCAwJiYodGhpcy5hcnJheS5idWZmZXIuX3V1aWQ9dG4oKSksdC5hcnJheUJ1ZmZlcnNbdGhpcy5hcnJheS5idWZmZXIuX3V1aWRdPT09dm9pZCAwJiYodC5hcnJheUJ1ZmZlcnNbdGhpcy5hcnJheS5idWZmZXIuX3V1aWRdPUFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKG5ldyBVaW50MzJBcnJheSh0aGlzLmFycmF5LmJ1ZmZlcikpKSx7dXVpZDp0aGlzLnV1aWQsYnVmZmVyOnRoaXMuYXJyYXkuYnVmZmVyLl91dWlkLHR5cGU6dGhpcy5hcnJheS5jb25zdHJ1Y3Rvci5uYW1lLHN0cmlkZTp0aGlzLnN0cmlkZX19fTtHaS5wcm90b3R5cGUuaXNJbnRlcmxlYXZlZEJ1ZmZlcj0hMDt2YXIgc2U9bmV3IFQsV2k9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscj0hMSl7dGhpcy5uYW1lPSIiLHRoaXMuZGF0YT10LHRoaXMuaXRlbVNpemU9ZSx0aGlzLm9mZnNldD1pLHRoaXMubm9ybWFsaXplZD1yPT09ITB9Z2V0IGNvdW50KCl7cmV0dXJuIHRoaXMuZGF0YS5jb3VudH1nZXQgYXJyYXkoKXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5fXNldCBuZWVkc1VwZGF0ZSh0KXt0aGlzLmRhdGEubmVlZHNVcGRhdGU9dH1hcHBseU1hdHJpeDQodCl7Zm9yKGxldCBlPTAsaT10aGlzLmRhdGEuY291bnQ7ZTxpO2UrKylzZS54PXRoaXMuZ2V0WChlKSxzZS55PXRoaXMuZ2V0WShlKSxzZS56PXRoaXMuZ2V0WihlKSxzZS5hcHBseU1hdHJpeDQodCksdGhpcy5zZXRYWVooZSxzZS54LHNlLnksc2Uueik7cmV0dXJuIHRoaXN9YXBwbHlOb3JtYWxNYXRyaXgodCl7Zm9yKGxldCBlPTAsaT10aGlzLmNvdW50O2U8aTtlKyspc2UueD10aGlzLmdldFgoZSksc2UueT10aGlzLmdldFkoZSksc2Uuej10aGlzLmdldFooZSksc2UuYXBwbHlOb3JtYWxNYXRyaXgodCksdGhpcy5zZXRYWVooZSxzZS54LHNlLnksc2Uueik7cmV0dXJuIHRoaXN9dHJhbnNmb3JtRGlyZWN0aW9uKHQpe2ZvcihsZXQgZT0wLGk9dGhpcy5jb3VudDtlPGk7ZSsrKXNlLng9dGhpcy5nZXRYKGUpLHNlLnk9dGhpcy5nZXRZKGUpLHNlLno9dGhpcy5nZXRaKGUpLHNlLnRyYW5zZm9ybURpcmVjdGlvbih0KSx0aGlzLnNldFhZWihlLHNlLngsc2UueSxzZS56KTtyZXR1cm4gdGhpc31zZXRYKHQsZSl7cmV0dXJuIHRoaXMuZGF0YS5hcnJheVt0KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXRdPWUsdGhpc31zZXRZKHQsZSl7cmV0dXJuIHRoaXMuZGF0YS5hcnJheVt0KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQrMV09ZSx0aGlzfXNldFoodCxlKXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5W3QqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldCsyXT1lLHRoaXN9c2V0Vyh0LGUpe3JldHVybiB0aGlzLmRhdGEuYXJyYXlbdCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0KzNdPWUsdGhpc31nZXRYKHQpe3JldHVybiB0aGlzLmRhdGEuYXJyYXlbdCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0XX1nZXRZKHQpe3JldHVybiB0aGlzLmRhdGEuYXJyYXlbdCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0KzFdfWdldFoodCl7cmV0dXJuIHRoaXMuZGF0YS5hcnJheVt0KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQrMl19Z2V0Vyh0KXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5W3QqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldCszXX1zZXRYWSh0LGUsaSl7cmV0dXJuIHQ9dCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0LHRoaXMuZGF0YS5hcnJheVt0KzBdPWUsdGhpcy5kYXRhLmFycmF5W3QrMV09aSx0aGlzfXNldFhZWih0LGUsaSxyKXtyZXR1cm4gdD10KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQsdGhpcy5kYXRhLmFycmF5W3QrMF09ZSx0aGlzLmRhdGEuYXJyYXlbdCsxXT1pLHRoaXMuZGF0YS5hcnJheVt0KzJdPXIsdGhpc31zZXRYWVpXKHQsZSxpLHIscyl7cmV0dXJuIHQ9dCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0LHRoaXMuZGF0YS5hcnJheVt0KzBdPWUsdGhpcy5kYXRhLmFycmF5W3QrMV09aSx0aGlzLmRhdGEuYXJyYXlbdCsyXT1yLHRoaXMuZGF0YS5hcnJheVt0KzNdPXMsdGhpc31jbG9uZSh0KXtpZih0PT09dm9pZCAwKXtjb25zb2xlLmxvZygiVEhSRUUuSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGUuY2xvbmUoKTogQ2xvbmluZyBhbiBpbnRlcmxhdmVkIGJ1ZmZlciBhdHRyaWJ1dGUgd2lsbCBkZWludGVybGVhdmUgYnVmZmVyIGRhdGEuIik7bGV0IGU9W107Zm9yKGxldCBpPTA7aTx0aGlzLmNvdW50O2krKyl7bGV0IHI9aSp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0O2ZvcihsZXQgcz0wO3M8dGhpcy5pdGVtU2l6ZTtzKyspZS5wdXNoKHRoaXMuZGF0YS5hcnJheVtyK3NdKX1yZXR1cm4gbmV3IFF0KG5ldyB0aGlzLmFycmF5LmNvbnN0cnVjdG9yKGUpLHRoaXMuaXRlbVNpemUsdGhpcy5ub3JtYWxpemVkKX1lbHNlIHJldHVybiB0LmludGVybGVhdmVkQnVmZmVycz09PXZvaWQgMCYmKHQuaW50ZXJsZWF2ZWRCdWZmZXJzPXt9KSx0LmludGVybGVhdmVkQnVmZmVyc1t0aGlzLmRhdGEudXVpZF09PT12b2lkIDAmJih0LmludGVybGVhdmVkQnVmZmVyc1t0aGlzLmRhdGEudXVpZF09dGhpcy5kYXRhLmNsb25lKHQpKSxuZXcgV2kodC5pbnRlcmxlYXZlZEJ1ZmZlcnNbdGhpcy5kYXRhLnV1aWRdLHRoaXMuaXRlbVNpemUsdGhpcy5vZmZzZXQsdGhpcy5ub3JtYWxpemVkKX10b0pTT04odCl7aWYodD09PXZvaWQgMCl7Y29uc29sZS5sb2coIlRIUkVFLkludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlLnRvSlNPTigpOiBTZXJpYWxpemluZyBhbiBpbnRlcmxhdmVkIGJ1ZmZlciBhdHRyaWJ1dGUgd2lsbCBkZWludGVybGVhdmUgYnVmZmVyIGRhdGEuIik7bGV0IGU9W107Zm9yKGxldCBpPTA7aTx0aGlzLmNvdW50O2krKyl7bGV0IHI9aSp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0O2ZvcihsZXQgcz0wO3M8dGhpcy5pdGVtU2l6ZTtzKyspZS5wdXNoKHRoaXMuZGF0YS5hcnJheVtyK3NdKX1yZXR1cm57aXRlbVNpemU6dGhpcy5pdGVtU2l6ZSx0eXBlOnRoaXMuYXJyYXkuY29uc3RydWN0b3IubmFtZSxhcnJheTplLG5vcm1hbGl6ZWQ6dGhpcy5ub3JtYWxpemVkfX1lbHNlIHJldHVybiB0LmludGVybGVhdmVkQnVmZmVycz09PXZvaWQgMCYmKHQuaW50ZXJsZWF2ZWRCdWZmZXJzPXt9KSx0LmludGVybGVhdmVkQnVmZmVyc1t0aGlzLmRhdGEudXVpZF09PT12b2lkIDAmJih0LmludGVybGVhdmVkQnVmZmVyc1t0aGlzLmRhdGEudXVpZF09dGhpcy5kYXRhLnRvSlNPTih0KSkse2lzSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGU6ITAsaXRlbVNpemU6dGhpcy5pdGVtU2l6ZSxkYXRhOnRoaXMuZGF0YS51dWlkLG9mZnNldDp0aGlzLm9mZnNldCxub3JtYWxpemVkOnRoaXMubm9ybWFsaXplZH19fTtXaS5wcm90b3R5cGUuaXNJbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZT0hMDt2YXIgZmw9Y2xhc3MgZXh0ZW5kcyB4ZXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iU3ByaXRlTWF0ZXJpYWwiLHRoaXMuY29sb3I9bmV3IGZ0KDE2Nzc3MjE1KSx0aGlzLm1hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLnJvdGF0aW9uPTAsdGhpcy5zaXplQXR0ZW51YXRpb249ITAsdGhpcy50cmFuc3BhcmVudD0hMCx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuY29sb3IuY29weSh0LmNvbG9yKSx0aGlzLm1hcD10Lm1hcCx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5yb3RhdGlvbj10LnJvdGF0aW9uLHRoaXMuc2l6ZUF0dGVudWF0aW9uPXQuc2l6ZUF0dGVudWF0aW9uLHRoaXN9fTtmbC5wcm90b3R5cGUuaXNTcHJpdGVNYXRlcmlhbD0hMDt2YXIgUnIsV3M9bmV3IFQsTHI9bmV3IFQsUHI9bmV3IFQsRHI9bmV3IEsscXM9bmV3IEssQTA9bmV3IHd0LE9hPW5ldyBULFhzPW5ldyBULGthPW5ldyBULFVnPW5ldyBLLFJ1PW5ldyBLLEJnPW5ldyBLLCR1PWNsYXNzIGV4dGVuZHMga3R7Y29uc3RydWN0b3IodCl7aWYoc3VwZXIoKSx0aGlzLnR5cGU9IlNwcml0ZSIsUnI9PT12b2lkIDApe1JyPW5ldyBIdDtsZXQgZT1uZXcgRmxvYXQzMkFycmF5KFstLjUsLS41LDAsMCwwLC41LC0uNSwwLDEsMCwuNSwuNSwwLDEsMSwtLjUsLjUsMCwwLDFdKSxpPW5ldyBHaShlLDUpO1JyLnNldEluZGV4KFswLDEsMiwwLDIsM10pLFJyLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyBXaShpLDMsMCwhMSkpLFJyLnNldEF0dHJpYnV0ZSgidXYiLG5ldyBXaShpLDIsMywhMSkpfXRoaXMuZ2VvbWV0cnk9UnIsdGhpcy5tYXRlcmlhbD10IT09dm9pZCAwP3Q6bmV3IGZsLHRoaXMuY2VudGVyPW5ldyBLKC41LC41KX1yYXljYXN0KHQsZSl7dC5jYW1lcmE9PT1udWxsJiZjb25zb2xlLmVycm9yKCdUSFJFRS5TcHJpdGU6ICJSYXljYXN0ZXIuY2FtZXJhIiBuZWVkcyB0byBiZSBzZXQgaW4gb3JkZXIgdG8gcmF5Y2FzdCBhZ2FpbnN0IHNwcml0ZXMuJyksTHIuc2V0RnJvbU1hdHJpeFNjYWxlKHRoaXMubWF0cml4V29ybGQpLEEwLmNvcHkodC5jYW1lcmEubWF0cml4V29ybGQpLHRoaXMubW9kZWxWaWV3TWF0cml4Lm11bHRpcGx5TWF0cmljZXModC5jYW1lcmEubWF0cml4V29ybGRJbnZlcnNlLHRoaXMubWF0cml4V29ybGQpLFByLnNldEZyb21NYXRyaXhQb3NpdGlvbih0aGlzLm1vZGVsVmlld01hdHJpeCksdC5jYW1lcmEuaXNQZXJzcGVjdGl2ZUNhbWVyYSYmdGhpcy5tYXRlcmlhbC5zaXplQXR0ZW51YXRpb249PT0hMSYmTHIubXVsdGlwbHlTY2FsYXIoLVByLnopO2xldCBpPXRoaXMubWF0ZXJpYWwucm90YXRpb24scixzO2khPT0wJiYocz1NYXRoLmNvcyhpKSxyPU1hdGguc2luKGkpKTtsZXQgbz10aGlzLmNlbnRlcjtIYShPYS5zZXQoLS41LC0uNSwwKSxQcixvLExyLHIscyksSGEoWHMuc2V0KC41LC0uNSwwKSxQcixvLExyLHIscyksSGEoa2Euc2V0KC41LC41LDApLFByLG8sTHIscixzKSxVZy5zZXQoMCwwKSxSdS5zZXQoMSwwKSxCZy5zZXQoMSwxKTtsZXQgYT10LnJheS5pbnRlcnNlY3RUcmlhbmdsZShPYSxYcyxrYSwhMSxXcyk7aWYoYT09PW51bGwmJihIYShYcy5zZXQoLS41LC41LDApLFByLG8sTHIscixzKSxSdS5zZXQoMCwxKSxhPXQucmF5LmludGVyc2VjdFRyaWFuZ2xlKE9hLGthLFhzLCExLFdzKSxhPT09bnVsbCkpcmV0dXJuO2xldCBsPXQucmF5Lm9yaWdpbi5kaXN0YW5jZVRvKFdzKTtsPHQubmVhcnx8bD50LmZhcnx8ZS5wdXNoKHtkaXN0YW5jZTpsLHBvaW50OldzLmNsb25lKCksdXY6cmUuZ2V0VVYoV3MsT2EsWHMsa2EsVWcsUnUsQmcsbmV3IEspLGZhY2U6bnVsbCxvYmplY3Q6dGhpc30pfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdC5jZW50ZXIhPT12b2lkIDAmJnRoaXMuY2VudGVyLmNvcHkodC5jZW50ZXIpLHRoaXMubWF0ZXJpYWw9dC5tYXRlcmlhbCx0aGlzfX07JHUucHJvdG90eXBlLmlzU3ByaXRlPSEwO2Z1bmN0aW9uIEhhKG4sdCxlLGkscixzKXtEci5zdWJWZWN0b3JzKG4sZSkuYWRkU2NhbGFyKC41KS5tdWx0aXBseShpKSxyIT09dm9pZCAwPyhxcy54PXMqRHIueC1yKkRyLnkscXMueT1yKkRyLngrcypEci55KTpxcy5jb3B5KERyKSxuLmNvcHkodCksbi54Kz1xcy54LG4ueSs9cXMueSxuLmFwcGx5TWF0cml4NChBMCl9dmFyIE9nPW5ldyBULGtnPW5ldyBXdCxIZz1uZXcgV3QsTkU9bmV3IFQsVmc9bmV3IHd0LGRsPWNsYXNzIGV4dGVuZHMgb2V7Y29uc3RydWN0b3IodCxlKXtzdXBlcih0LGUpLHRoaXMudHlwZT0iU2tpbm5lZE1lc2giLHRoaXMuYmluZE1vZGU9ImF0dGFjaGVkIix0aGlzLmJpbmRNYXRyaXg9bmV3IHd0LHRoaXMuYmluZE1hdHJpeEludmVyc2U9bmV3IHd0fWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5iaW5kTW9kZT10LmJpbmRNb2RlLHRoaXMuYmluZE1hdHJpeC5jb3B5KHQuYmluZE1hdHJpeCksdGhpcy5iaW5kTWF0cml4SW52ZXJzZS5jb3B5KHQuYmluZE1hdHJpeEludmVyc2UpLHRoaXMuc2tlbGV0b249dC5za2VsZXRvbix0aGlzfWJpbmQodCxlKXt0aGlzLnNrZWxldG9uPXQsZT09PXZvaWQgMCYmKHRoaXMudXBkYXRlTWF0cml4V29ybGQoITApLHRoaXMuc2tlbGV0b24uY2FsY3VsYXRlSW52ZXJzZXMoKSxlPXRoaXMubWF0cml4V29ybGQpLHRoaXMuYmluZE1hdHJpeC5jb3B5KGUpLHRoaXMuYmluZE1hdHJpeEludmVyc2UuY29weShlKS5pbnZlcnQoKX1wb3NlKCl7dGhpcy5za2VsZXRvbi5wb3NlKCl9bm9ybWFsaXplU2tpbldlaWdodHMoKXtsZXQgdD1uZXcgV3QsZT10aGlzLmdlb21ldHJ5LmF0dHJpYnV0ZXMuc2tpbldlaWdodDtmb3IobGV0IGk9MCxyPWUuY291bnQ7aTxyO2krKyl7dC54PWUuZ2V0WChpKSx0Lnk9ZS5nZXRZKGkpLHQuej1lLmdldFooaSksdC53PWUuZ2V0VyhpKTtsZXQgcz0xL3QubWFuaGF0dGFuTGVuZ3RoKCk7cyE9PTEvMD90Lm11bHRpcGx5U2NhbGFyKHMpOnQuc2V0KDEsMCwwLDApLGUuc2V0WFlaVyhpLHQueCx0LnksdC56LHQudyl9fXVwZGF0ZU1hdHJpeFdvcmxkKHQpe3N1cGVyLnVwZGF0ZU1hdHJpeFdvcmxkKHQpLHRoaXMuYmluZE1vZGU9PT0iYXR0YWNoZWQiP3RoaXMuYmluZE1hdHJpeEludmVyc2UuY29weSh0aGlzLm1hdHJpeFdvcmxkKS5pbnZlcnQoKTp0aGlzLmJpbmRNb2RlPT09ImRldGFjaGVkIj90aGlzLmJpbmRNYXRyaXhJbnZlcnNlLmNvcHkodGhpcy5iaW5kTWF0cml4KS5pbnZlcnQoKTpjb25zb2xlLndhcm4oIlRIUkVFLlNraW5uZWRNZXNoOiBVbnJlY29nbml6ZWQgYmluZE1vZGU6ICIrdGhpcy5iaW5kTW9kZSl9Ym9uZVRyYW5zZm9ybSh0LGUpe2xldCBpPXRoaXMuc2tlbGV0b24scj10aGlzLmdlb21ldHJ5O2tnLmZyb21CdWZmZXJBdHRyaWJ1dGUoci5hdHRyaWJ1dGVzLnNraW5JbmRleCx0KSxIZy5mcm9tQnVmZmVyQXR0cmlidXRlKHIuYXR0cmlidXRlcy5za2luV2VpZ2h0LHQpLE9nLmNvcHkoZSkuYXBwbHlNYXRyaXg0KHRoaXMuYmluZE1hdHJpeCksZS5zZXQoMCwwLDApO2ZvcihsZXQgcz0wO3M8NDtzKyspe2xldCBvPUhnLmdldENvbXBvbmVudChzKTtpZihvIT09MCl7bGV0IGE9a2cuZ2V0Q29tcG9uZW50KHMpO1ZnLm11bHRpcGx5TWF0cmljZXMoaS5ib25lc1thXS5tYXRyaXhXb3JsZCxpLmJvbmVJbnZlcnNlc1thXSksZS5hZGRTY2FsZWRWZWN0b3IoTkUuY29weShPZykuYXBwbHlNYXRyaXg0KFZnKSxvKX19cmV0dXJuIGUuYXBwbHlNYXRyaXg0KHRoaXMuYmluZE1hdHJpeEludmVyc2UpfX07ZGwucHJvdG90eXBlLmlzU2tpbm5lZE1lc2g9ITA7dmFyIEt1PWNsYXNzIGV4dGVuZHMga3R7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMudHlwZT0iQm9uZSJ9fTtLdS5wcm90b3R5cGUuaXNCb25lPSEwO3ZhciBRdT1jbGFzcyBleHRlbmRzIGFle2NvbnN0cnVjdG9yKHQ9bnVsbCxlPTEsaT0xLHIscyxvLGEsbCxjPWZlLHU9ZmUsaCxmKXtzdXBlcihudWxsLG8sYSxsLGMsdSxyLHMsaCxmKSx0aGlzLmltYWdlPXtkYXRhOnQsd2lkdGg6ZSxoZWlnaHQ6aX0sdGhpcy5tYWdGaWx0ZXI9Yyx0aGlzLm1pbkZpbHRlcj11LHRoaXMuZ2VuZXJhdGVNaXBtYXBzPSExLHRoaXMuZmxpcFk9ITEsdGhpcy51bnBhY2tBbGlnbm1lbnQ9MX19O1F1LnByb3RvdHlwZS5pc0RhdGFUZXh0dXJlPSEwO3ZhciBwbz1jbGFzcyBleHRlbmRzIFF0e2NvbnN0cnVjdG9yKHQsZSxpLHI9MSl7dHlwZW9mIGk9PSJudW1iZXIiJiYocj1pLGk9ITEsY29uc29sZS5lcnJvcigiVEhSRUUuSW5zdGFuY2VkQnVmZmVyQXR0cmlidXRlOiBUaGUgY29uc3RydWN0b3Igbm93IGV4cGVjdHMgbm9ybWFsaXplZCBhcyB0aGUgdGhpcmQgYXJndW1lbnQuIikpLHN1cGVyKHQsZSxpKSx0aGlzLm1lc2hQZXJBdHRyaWJ1dGU9cn1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMubWVzaFBlckF0dHJpYnV0ZT10Lm1lc2hQZXJBdHRyaWJ1dGUsdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC5tZXNoUGVyQXR0cmlidXRlPXRoaXMubWVzaFBlckF0dHJpYnV0ZSx0LmlzSW5zdGFuY2VkQnVmZmVyQXR0cmlidXRlPSEwLHR9fTtwby5wcm90b3R5cGUuaXNJbnN0YW5jZWRCdWZmZXJBdHRyaWJ1dGU9ITA7dmFyIEdnPW5ldyB3dCxXZz1uZXcgd3QsVmE9W10sWXM9bmV3IG9lLGp1PWNsYXNzIGV4dGVuZHMgb2V7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKHQsZSksdGhpcy5pbnN0YW5jZU1hdHJpeD1uZXcgcG8obmV3IEZsb2F0MzJBcnJheShpKjE2KSwxNiksdGhpcy5pbnN0YW5jZUNvbG9yPW51bGwsdGhpcy5jb3VudD1pLHRoaXMuZnJ1c3R1bUN1bGxlZD0hMX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuaW5zdGFuY2VNYXRyaXguY29weSh0Lmluc3RhbmNlTWF0cml4KSx0Lmluc3RhbmNlQ29sb3IhPT1udWxsJiYodGhpcy5pbnN0YW5jZUNvbG9yPXQuaW5zdGFuY2VDb2xvci5jbG9uZSgpKSx0aGlzLmNvdW50PXQuY291bnQsdGhpc31nZXRDb2xvckF0KHQsZSl7ZS5mcm9tQXJyYXkodGhpcy5pbnN0YW5jZUNvbG9yLmFycmF5LHQqMyl9Z2V0TWF0cml4QXQodCxlKXtlLmZyb21BcnJheSh0aGlzLmluc3RhbmNlTWF0cml4LmFycmF5LHQqMTYpfXJheWNhc3QodCxlKXtsZXQgaT10aGlzLm1hdHJpeFdvcmxkLHI9dGhpcy5jb3VudDtpZihZcy5nZW9tZXRyeT10aGlzLmdlb21ldHJ5LFlzLm1hdGVyaWFsPXRoaXMubWF0ZXJpYWwsWXMubWF0ZXJpYWwhPT12b2lkIDApZm9yKGxldCBzPTA7czxyO3MrKyl7dGhpcy5nZXRNYXRyaXhBdChzLEdnKSxXZy5tdWx0aXBseU1hdHJpY2VzKGksR2cpLFlzLm1hdHJpeFdvcmxkPVdnLFlzLnJheWNhc3QodCxWYSk7Zm9yKGxldCBvPTAsYT1WYS5sZW5ndGg7bzxhO28rKyl7bGV0IGw9VmFbb107bC5pbnN0YW5jZUlkPXMsbC5vYmplY3Q9dGhpcyxlLnB1c2gobCl9VmEubGVuZ3RoPTB9fXNldENvbG9yQXQodCxlKXt0aGlzLmluc3RhbmNlQ29sb3I9PT1udWxsJiYodGhpcy5pbnN0YW5jZUNvbG9yPW5ldyBwbyhuZXcgRmxvYXQzMkFycmF5KHRoaXMuaW5zdGFuY2VNYXRyaXguY291bnQqMyksMykpLGUudG9BcnJheSh0aGlzLmluc3RhbmNlQ29sb3IuYXJyYXksdCozKX1zZXRNYXRyaXhBdCh0LGUpe2UudG9BcnJheSh0aGlzLmluc3RhbmNlTWF0cml4LmFycmF5LHQqMTYpfXVwZGF0ZU1vcnBoVGFyZ2V0cygpe31kaXNwb3NlKCl7dGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJkaXNwb3NlIn0pfX07anUucHJvdG90eXBlLmlzSW5zdGFuY2VkTWVzaD0hMDt2YXIgem49Y2xhc3MgZXh0ZW5kcyB4ZXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTGluZUJhc2ljTWF0ZXJpYWwiLHRoaXMuY29sb3I9bmV3IGZ0KDE2Nzc3MjE1KSx0aGlzLmxpbmV3aWR0aD0xLHRoaXMubGluZWNhcD0icm91bmQiLHRoaXMubGluZWpvaW49InJvdW5kIix0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuY29sb3IuY29weSh0LmNvbG9yKSx0aGlzLmxpbmV3aWR0aD10LmxpbmV3aWR0aCx0aGlzLmxpbmVjYXA9dC5saW5lY2FwLHRoaXMubGluZWpvaW49dC5saW5lam9pbix0aGlzfX07em4ucHJvdG90eXBlLmlzTGluZUJhc2ljTWF0ZXJpYWw9ITA7dmFyIHFnPW5ldyBULFhnPW5ldyBULFlnPW5ldyB3dCxMdT1uZXcgb2ksR2E9bmV3IHNpLG1vPWNsYXNzIGV4dGVuZHMga3R7Y29uc3RydWN0b3IodD1uZXcgSHQsZT1uZXcgem4pe3N1cGVyKCksdGhpcy50eXBlPSJMaW5lIix0aGlzLmdlb21ldHJ5PXQsdGhpcy5tYXRlcmlhbD1lLHRoaXMudXBkYXRlTW9ycGhUYXJnZXRzKCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLm1hdGVyaWFsPXQubWF0ZXJpYWwsdGhpcy5nZW9tZXRyeT10Lmdlb21ldHJ5LHRoaXN9Y29tcHV0ZUxpbmVEaXN0YW5jZXMoKXtsZXQgdD10aGlzLmdlb21ldHJ5O2lmKHQuaXNCdWZmZXJHZW9tZXRyeSlpZih0LmluZGV4PT09bnVsbCl7bGV0IGU9dC5hdHRyaWJ1dGVzLnBvc2l0aW9uLGk9WzBdO2ZvcihsZXQgcj0xLHM9ZS5jb3VudDtyPHM7cisrKXFnLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSxyLTEpLFhnLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSxyKSxpW3JdPWlbci0xXSxpW3JdKz1xZy5kaXN0YW5jZVRvKFhnKTt0LnNldEF0dHJpYnV0ZSgibGluZURpc3RhbmNlIixuZXcgZWUoaSwxKSl9ZWxzZSBjb25zb2xlLndhcm4oIlRIUkVFLkxpbmUuY29tcHV0ZUxpbmVEaXN0YW5jZXMoKTogQ29tcHV0YXRpb24gb25seSBwb3NzaWJsZSB3aXRoIG5vbi1pbmRleGVkIEJ1ZmZlckdlb21ldHJ5LiIpO2Vsc2UgdC5pc0dlb21ldHJ5JiZjb25zb2xlLmVycm9yKCJUSFJFRS5MaW5lLmNvbXB1dGVMaW5lRGlzdGFuY2VzKCkgbm8gbG9uZ2VyIHN1cHBvcnRzIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKTtyZXR1cm4gdGhpc31yYXljYXN0KHQsZSl7bGV0IGk9dGhpcy5nZW9tZXRyeSxyPXRoaXMubWF0cml4V29ybGQscz10LnBhcmFtcy5MaW5lLnRocmVzaG9sZCxvPWkuZHJhd1JhbmdlO2lmKGkuYm91bmRpbmdTcGhlcmU9PT1udWxsJiZpLmNvbXB1dGVCb3VuZGluZ1NwaGVyZSgpLEdhLmNvcHkoaS5ib3VuZGluZ1NwaGVyZSksR2EuYXBwbHlNYXRyaXg0KHIpLEdhLnJhZGl1cys9cyx0LnJheS5pbnRlcnNlY3RzU3BoZXJlKEdhKT09PSExKXJldHVybjtZZy5jb3B5KHIpLmludmVydCgpLEx1LmNvcHkodC5yYXkpLmFwcGx5TWF0cml4NChZZyk7bGV0IGE9cy8oKHRoaXMuc2NhbGUueCt0aGlzLnNjYWxlLnkrdGhpcy5zY2FsZS56KS8zKSxsPWEqYSxjPW5ldyBULHU9bmV3IFQsaD1uZXcgVCxmPW5ldyBULGQ9dGhpcy5pc0xpbmVTZWdtZW50cz8yOjE7aWYoaS5pc0J1ZmZlckdlb21ldHJ5KXtsZXQgZz1pLmluZGV4LHY9aS5hdHRyaWJ1dGVzLnBvc2l0aW9uO2lmKGchPT1udWxsKXtsZXQgbT1NYXRoLm1heCgwLG8uc3RhcnQpLHA9TWF0aC5taW4oZy5jb3VudCxvLnN0YXJ0K28uY291bnQpO2ZvcihsZXQgYj1tLF89cC0xO2I8XztiKz1kKXtsZXQgUz1nLmdldFgoYiksTD1nLmdldFgoYisxKTtpZihjLmZyb21CdWZmZXJBdHRyaWJ1dGUodixTKSx1LmZyb21CdWZmZXJBdHRyaWJ1dGUodixMKSxMdS5kaXN0YW5jZVNxVG9TZWdtZW50KGMsdSxmLGgpPmwpY29udGludWU7Zi5hcHBseU1hdHJpeDQodGhpcy5tYXRyaXhXb3JsZCk7bGV0IEg9dC5yYXkub3JpZ2luLmRpc3RhbmNlVG8oZik7SDx0Lm5lYXJ8fEg+dC5mYXJ8fGUucHVzaCh7ZGlzdGFuY2U6SCxwb2ludDpoLmNsb25lKCkuYXBwbHlNYXRyaXg0KHRoaXMubWF0cml4V29ybGQpLGluZGV4OmIsZmFjZTpudWxsLGZhY2VJbmRleDpudWxsLG9iamVjdDp0aGlzfSl9fWVsc2V7bGV0IG09TWF0aC5tYXgoMCxvLnN0YXJ0KSxwPU1hdGgubWluKHYuY291bnQsby5zdGFydCtvLmNvdW50KTtmb3IobGV0IGI9bSxfPXAtMTtiPF87Yis9ZCl7aWYoYy5mcm9tQnVmZmVyQXR0cmlidXRlKHYsYiksdS5mcm9tQnVmZmVyQXR0cmlidXRlKHYsYisxKSxMdS5kaXN0YW5jZVNxVG9TZWdtZW50KGMsdSxmLGgpPmwpY29udGludWU7Zi5hcHBseU1hdHJpeDQodGhpcy5tYXRyaXhXb3JsZCk7bGV0IEw9dC5yYXkub3JpZ2luLmRpc3RhbmNlVG8oZik7TDx0Lm5lYXJ8fEw+dC5mYXJ8fGUucHVzaCh7ZGlzdGFuY2U6TCxwb2ludDpoLmNsb25lKCkuYXBwbHlNYXRyaXg0KHRoaXMubWF0cml4V29ybGQpLGluZGV4OmIsZmFjZTpudWxsLGZhY2VJbmRleDpudWxsLG9iamVjdDp0aGlzfSl9fX1lbHNlIGkuaXNHZW9tZXRyeSYmY29uc29sZS5lcnJvcigiVEhSRUUuTGluZS5yYXljYXN0KCkgbm8gbG9uZ2VyIHN1cHBvcnRzIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX11cGRhdGVNb3JwaFRhcmdldHMoKXtsZXQgdD10aGlzLmdlb21ldHJ5O2lmKHQuaXNCdWZmZXJHZW9tZXRyeSl7bGV0IGU9dC5tb3JwaEF0dHJpYnV0ZXMsaT1PYmplY3Qua2V5cyhlKTtpZihpLmxlbmd0aD4wKXtsZXQgcj1lW2lbMF1dO2lmKHIhPT12b2lkIDApe3RoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzPVtdLHRoaXMubW9ycGhUYXJnZXREaWN0aW9uYXJ5PXt9O2ZvcihsZXQgcz0wLG89ci5sZW5ndGg7czxvO3MrKyl7bGV0IGE9cltzXS5uYW1lfHxTdHJpbmcocyk7dGhpcy5tb3JwaFRhcmdldEluZmx1ZW5jZXMucHVzaCgwKSx0aGlzLm1vcnBoVGFyZ2V0RGljdGlvbmFyeVthXT1zfX19fWVsc2V7bGV0IGU9dC5tb3JwaFRhcmdldHM7ZSE9PXZvaWQgMCYmZS5sZW5ndGg+MCYmY29uc29sZS5lcnJvcigiVEhSRUUuTGluZS51cGRhdGVNb3JwaFRhcmdldHMoKSBkb2VzIG5vdCBzdXBwb3J0IFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX19fTttby5wcm90b3R5cGUuaXNMaW5lPSEwO3ZhciBaZz1uZXcgVCxKZz1uZXcgVCxnbz1jbGFzcyBleHRlbmRzIG1ve2NvbnN0cnVjdG9yKHQsZSl7c3VwZXIodCxlKSx0aGlzLnR5cGU9IkxpbmVTZWdtZW50cyJ9Y29tcHV0ZUxpbmVEaXN0YW5jZXMoKXtsZXQgdD10aGlzLmdlb21ldHJ5O2lmKHQuaXNCdWZmZXJHZW9tZXRyeSlpZih0LmluZGV4PT09bnVsbCl7bGV0IGU9dC5hdHRyaWJ1dGVzLnBvc2l0aW9uLGk9W107Zm9yKGxldCByPTAscz1lLmNvdW50O3I8cztyKz0yKVpnLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSxyKSxKZy5mcm9tQnVmZmVyQXR0cmlidXRlKGUscisxKSxpW3JdPXI9PT0wPzA6aVtyLTFdLGlbcisxXT1pW3JdK1pnLmRpc3RhbmNlVG8oSmcpO3Quc2V0QXR0cmlidXRlKCJsaW5lRGlzdGFuY2UiLG5ldyBlZShpLDEpKX1lbHNlIGNvbnNvbGUud2FybigiVEhSRUUuTGluZVNlZ21lbnRzLmNvbXB1dGVMaW5lRGlzdGFuY2VzKCk6IENvbXB1dGF0aW9uIG9ubHkgcG9zc2libGUgd2l0aCBub24taW5kZXhlZCBCdWZmZXJHZW9tZXRyeS4iKTtlbHNlIHQuaXNHZW9tZXRyeSYmY29uc29sZS5lcnJvcigiVEhSRUUuTGluZVNlZ21lbnRzLmNvbXB1dGVMaW5lRGlzdGFuY2VzKCkgbm8gbG9uZ2VyIHN1cHBvcnRzIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKTtyZXR1cm4gdGhpc319O2dvLnByb3RvdHlwZS5pc0xpbmVTZWdtZW50cz0hMDt2YXIgdGg9Y2xhc3MgZXh0ZW5kcyBtb3tjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKHQsZSksdGhpcy50eXBlPSJMaW5lTG9vcCJ9fTt0aC5wcm90b3R5cGUuaXNMaW5lTG9vcD0hMDt2YXIgcGw9Y2xhc3MgZXh0ZW5kcyB4ZXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iUG9pbnRzTWF0ZXJpYWwiLHRoaXMuY29sb3I9bmV3IGZ0KDE2Nzc3MjE1KSx0aGlzLm1hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLnNpemU9MSx0aGlzLnNpemVBdHRlbnVhdGlvbj0hMCx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuY29sb3IuY29weSh0LmNvbG9yKSx0aGlzLm1hcD10Lm1hcCx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5zaXplPXQuc2l6ZSx0aGlzLnNpemVBdHRlbnVhdGlvbj10LnNpemVBdHRlbnVhdGlvbix0aGlzfX07cGwucHJvdG90eXBlLmlzUG9pbnRzTWF0ZXJpYWw9ITA7dmFyICRnPW5ldyB3dCxlaD1uZXcgb2ksV2E9bmV3IHNpLHFhPW5ldyBULG5oPWNsYXNzIGV4dGVuZHMga3R7Y29uc3RydWN0b3IodD1uZXcgSHQsZT1uZXcgcGwpe3N1cGVyKCksdGhpcy50eXBlPSJQb2ludHMiLHRoaXMuZ2VvbWV0cnk9dCx0aGlzLm1hdGVyaWFsPWUsdGhpcy51cGRhdGVNb3JwaFRhcmdldHMoKX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMubWF0ZXJpYWw9dC5tYXRlcmlhbCx0aGlzLmdlb21ldHJ5PXQuZ2VvbWV0cnksdGhpc31yYXljYXN0KHQsZSl7bGV0IGk9dGhpcy5nZW9tZXRyeSxyPXRoaXMubWF0cml4V29ybGQscz10LnBhcmFtcy5Qb2ludHMudGhyZXNob2xkLG89aS5kcmF3UmFuZ2U7aWYoaS5ib3VuZGluZ1NwaGVyZT09PW51bGwmJmkuY29tcHV0ZUJvdW5kaW5nU3BoZXJlKCksV2EuY29weShpLmJvdW5kaW5nU3BoZXJlKSxXYS5hcHBseU1hdHJpeDQociksV2EucmFkaXVzKz1zLHQucmF5LmludGVyc2VjdHNTcGhlcmUoV2EpPT09ITEpcmV0dXJuOyRnLmNvcHkocikuaW52ZXJ0KCksZWguY29weSh0LnJheSkuYXBwbHlNYXRyaXg0KCRnKTtsZXQgYT1zLygodGhpcy5zY2FsZS54K3RoaXMuc2NhbGUueSt0aGlzLnNjYWxlLnopLzMpLGw9YSphO2lmKGkuaXNCdWZmZXJHZW9tZXRyeSl7bGV0IGM9aS5pbmRleCxoPWkuYXR0cmlidXRlcy5wb3NpdGlvbjtpZihjIT09bnVsbCl7bGV0IGY9TWF0aC5tYXgoMCxvLnN0YXJ0KSxkPU1hdGgubWluKGMuY291bnQsby5zdGFydCtvLmNvdW50KTtmb3IobGV0IGc9Zix4PWQ7Zzx4O2crKyl7bGV0IHY9Yy5nZXRYKGcpO3FhLmZyb21CdWZmZXJBdHRyaWJ1dGUoaCx2KSxLZyhxYSx2LGwscix0LGUsdGhpcyl9fWVsc2V7bGV0IGY9TWF0aC5tYXgoMCxvLnN0YXJ0KSxkPU1hdGgubWluKGguY291bnQsby5zdGFydCtvLmNvdW50KTtmb3IobGV0IGc9Zix4PWQ7Zzx4O2crKylxYS5mcm9tQnVmZmVyQXR0cmlidXRlKGgsZyksS2cocWEsZyxsLHIsdCxlLHRoaXMpfX1lbHNlIGNvbnNvbGUuZXJyb3IoIlRIUkVFLlBvaW50cy5yYXljYXN0KCkgbm8gbG9uZ2VyIHN1cHBvcnRzIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX11cGRhdGVNb3JwaFRhcmdldHMoKXtsZXQgdD10aGlzLmdlb21ldHJ5O2lmKHQuaXNCdWZmZXJHZW9tZXRyeSl7bGV0IGU9dC5tb3JwaEF0dHJpYnV0ZXMsaT1PYmplY3Qua2V5cyhlKTtpZihpLmxlbmd0aD4wKXtsZXQgcj1lW2lbMF1dO2lmKHIhPT12b2lkIDApe3RoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzPVtdLHRoaXMubW9ycGhUYXJnZXREaWN0aW9uYXJ5PXt9O2ZvcihsZXQgcz0wLG89ci5sZW5ndGg7czxvO3MrKyl7bGV0IGE9cltzXS5uYW1lfHxTdHJpbmcocyk7dGhpcy5tb3JwaFRhcmdldEluZmx1ZW5jZXMucHVzaCgwKSx0aGlzLm1vcnBoVGFyZ2V0RGljdGlvbmFyeVthXT1zfX19fWVsc2V7bGV0IGU9dC5tb3JwaFRhcmdldHM7ZSE9PXZvaWQgMCYmZS5sZW5ndGg+MCYmY29uc29sZS5lcnJvcigiVEhSRUUuUG9pbnRzLnVwZGF0ZU1vcnBoVGFyZ2V0cygpIGRvZXMgbm90IHN1cHBvcnQgVEhSRUUuR2VvbWV0cnkuIFVzZSBUSFJFRS5CdWZmZXJHZW9tZXRyeSBpbnN0ZWFkLiIpfX19O25oLnByb3RvdHlwZS5pc1BvaW50cz0hMDtmdW5jdGlvbiBLZyhuLHQsZSxpLHIscyxvKXtsZXQgYT1laC5kaXN0YW5jZVNxVG9Qb2ludChuKTtpZihhPGUpe2xldCBsPW5ldyBUO2VoLmNsb3Nlc3RQb2ludFRvUG9pbnQobixsKSxsLmFwcGx5TWF0cml4NChpKTtsZXQgYz1yLnJheS5vcmlnaW4uZGlzdGFuY2VUbyhsKTtpZihjPHIubmVhcnx8Yz5yLmZhcilyZXR1cm47cy5wdXNoKHtkaXN0YW5jZTpjLGRpc3RhbmNlVG9SYXk6TWF0aC5zcXJ0KGEpLHBvaW50OmwsaW5kZXg6dCxmYWNlOm51bGwsb2JqZWN0Om99KX19dmFyIGloPWNsYXNzIGV4dGVuZHMgYWV7Y29uc3RydWN0b3IodCxlLGkscixzLG8sYSxsLGMpe3N1cGVyKHQsZSxpLHIscyxvLGEsbCxjKSx0aGlzLm1pbkZpbHRlcj1vIT09dm9pZCAwP286YmUsdGhpcy5tYWdGaWx0ZXI9cyE9PXZvaWQgMD9zOmJlLHRoaXMuZ2VuZXJhdGVNaXBtYXBzPSExO2xldCB1PXRoaXM7ZnVuY3Rpb24gaCgpe3UubmVlZHNVcGRhdGU9ITAsdC5yZXF1ZXN0VmlkZW9GcmFtZUNhbGxiYWNrKGgpfSJyZXF1ZXN0VmlkZW9GcmFtZUNhbGxiYWNrImluIHQmJnQucmVxdWVzdFZpZGVvRnJhbWVDYWxsYmFjayhoKX1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3Rvcih0aGlzLmltYWdlKS5jb3B5KHRoaXMpfXVwZGF0ZSgpe2xldCB0PXRoaXMuaW1hZ2U7InJlcXVlc3RWaWRlb0ZyYW1lQ2FsbGJhY2siaW4gdD09PSExJiZ0LnJlYWR5U3RhdGU+PXQuSEFWRV9DVVJSRU5UX0RBVEEmJih0aGlzLm5lZWRzVXBkYXRlPSEwKX19O2loLnByb3RvdHlwZS5pc1ZpZGVvVGV4dHVyZT0hMDt2YXIgcmg9Y2xhc3MgZXh0ZW5kcyBhZXtjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIoe3dpZHRoOnQsaGVpZ2h0OmV9KSx0aGlzLmZvcm1hdD1pLHRoaXMubWFnRmlsdGVyPWZlLHRoaXMubWluRmlsdGVyPWZlLHRoaXMuZ2VuZXJhdGVNaXBtYXBzPSExLHRoaXMubmVlZHNVcGRhdGU9ITB9fTtyaC5wcm90b3R5cGUuaXNGcmFtZWJ1ZmZlclRleHR1cmU9ITA7dmFyIHNoPWNsYXNzIGV4dGVuZHMgYWV7Y29uc3RydWN0b3IodCxlLGkscixzLG8sYSxsLGMsdSxoLGYpe3N1cGVyKG51bGwsbyxhLGwsYyx1LHIscyxoLGYpLHRoaXMuaW1hZ2U9e3dpZHRoOmUsaGVpZ2h0Oml9LHRoaXMubWlwbWFwcz10LHRoaXMuZmxpcFk9ITEsdGhpcy5nZW5lcmF0ZU1pcG1hcHM9ITF9fTtzaC5wcm90b3R5cGUuaXNDb21wcmVzc2VkVGV4dHVyZT0hMDt2YXIgb2g9Y2xhc3MgZXh0ZW5kcyBhZXtjb25zdHJ1Y3Rvcih0LGUsaSxyLHMsbyxhLGwsYyl7c3VwZXIodCxlLGkscixzLG8sYSxsLGMpLHRoaXMubmVlZHNVcGRhdGU9ITB9fTtvaC5wcm90b3R5cGUuaXNDYW52YXNUZXh0dXJlPSEwO3ZhciBacj1jbGFzcyBleHRlbmRzIEh0e2NvbnN0cnVjdG9yKHQ9MSxlPTgsaT0wLHI9TWF0aC5QSSoyKXtzdXBlcigpLHRoaXMudHlwZT0iQ2lyY2xlR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17cmFkaXVzOnQsc2VnbWVudHM6ZSx0aGV0YVN0YXJ0OmksdGhldGFMZW5ndGg6cn0sZT1NYXRoLm1heCgzLGUpO2xldCBzPVtdLG89W10sYT1bXSxsPVtdLGM9bmV3IFQsdT1uZXcgSztvLnB1c2goMCwwLDApLGEucHVzaCgwLDAsMSksbC5wdXNoKC41LC41KTtmb3IobGV0IGg9MCxmPTM7aDw9ZTtoKyssZis9Myl7bGV0IGQ9aStoL2UqcjtjLng9dCpNYXRoLmNvcyhkKSxjLnk9dCpNYXRoLnNpbihkKSxvLnB1c2goYy54LGMueSxjLnopLGEucHVzaCgwLDAsMSksdS54PShvW2ZdL3QrMSkvMix1Lnk9KG9bZisxXS90KzEpLzIsbC5wdXNoKHUueCx1LnkpfWZvcihsZXQgaD0xO2g8PWU7aCsrKXMucHVzaChoLGgrMSwwKTt0aGlzLnNldEluZGV4KHMpLHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IGVlKG8sMykpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG5ldyBlZShhLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyBlZShsLDIpKX1zdGF0aWMgZnJvbUpTT04odCl7cmV0dXJuIG5ldyBacih0LnJhZGl1cyx0LnNlZ21lbnRzLHQudGhldGFTdGFydCx0LnRoZXRhTGVuZ3RoKX19O3ZhciBTaz1uZXcgVCxFaz1uZXcgVCxUaz1uZXcgVCxBaz1uZXcgcmU7dmFyIEZlPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy50eXBlPSJDdXJ2ZSIsdGhpcy5hcmNMZW5ndGhEaXZpc2lvbnM9MjAwfWdldFBvaW50KCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQ3VydmU6IC5nZXRQb2ludCgpIG5vdCBpbXBsZW1lbnRlZC4iKSxudWxsfWdldFBvaW50QXQodCxlKXtsZXQgaT10aGlzLmdldFV0b1RtYXBwaW5nKHQpO3JldHVybiB0aGlzLmdldFBvaW50KGksZSl9Z2V0UG9pbnRzKHQ9NSl7bGV0IGU9W107Zm9yKGxldCBpPTA7aTw9dDtpKyspZS5wdXNoKHRoaXMuZ2V0UG9pbnQoaS90KSk7cmV0dXJuIGV9Z2V0U3BhY2VkUG9pbnRzKHQ9NSl7bGV0IGU9W107Zm9yKGxldCBpPTA7aTw9dDtpKyspZS5wdXNoKHRoaXMuZ2V0UG9pbnRBdChpL3QpKTtyZXR1cm4gZX1nZXRMZW5ndGgoKXtsZXQgdD10aGlzLmdldExlbmd0aHMoKTtyZXR1cm4gdFt0Lmxlbmd0aC0xXX1nZXRMZW5ndGhzKHQ9dGhpcy5hcmNMZW5ndGhEaXZpc2lvbnMpe2lmKHRoaXMuY2FjaGVBcmNMZW5ndGhzJiZ0aGlzLmNhY2hlQXJjTGVuZ3Rocy5sZW5ndGg9PT10KzEmJiF0aGlzLm5lZWRzVXBkYXRlKXJldHVybiB0aGlzLmNhY2hlQXJjTGVuZ3Roczt0aGlzLm5lZWRzVXBkYXRlPSExO2xldCBlPVtdLGkscj10aGlzLmdldFBvaW50KDApLHM9MDtlLnB1c2goMCk7Zm9yKGxldCBvPTE7bzw9dDtvKyspaT10aGlzLmdldFBvaW50KG8vdCkscys9aS5kaXN0YW5jZVRvKHIpLGUucHVzaChzKSxyPWk7cmV0dXJuIHRoaXMuY2FjaGVBcmNMZW5ndGhzPWUsZX11cGRhdGVBcmNMZW5ndGhzKCl7dGhpcy5uZWVkc1VwZGF0ZT0hMCx0aGlzLmdldExlbmd0aHMoKX1nZXRVdG9UbWFwcGluZyh0LGUpe2xldCBpPXRoaXMuZ2V0TGVuZ3RocygpLHI9MCxzPWkubGVuZ3RoLG87ZT9vPWU6bz10Kmlbcy0xXTtsZXQgYT0wLGw9cy0xLGM7Zm9yKDthPD1sOylpZihyPU1hdGguZmxvb3IoYSsobC1hKS8yKSxjPWlbcl0tbyxjPDApYT1yKzE7ZWxzZSBpZihjPjApbD1yLTE7ZWxzZXtsPXI7YnJlYWt9aWYocj1sLGlbcl09PT1vKXJldHVybiByLyhzLTEpO2xldCB1PWlbcl0sZj1pW3IrMV0tdSxkPShvLXUpL2Y7cmV0dXJuKHIrZCkvKHMtMSl9Z2V0VGFuZ2VudCh0LGUpe2xldCByPXQtMWUtNCxzPXQrMWUtNDtyPDAmJihyPTApLHM+MSYmKHM9MSk7bGV0IG89dGhpcy5nZXRQb2ludChyKSxhPXRoaXMuZ2V0UG9pbnQocyksbD1lfHwoby5pc1ZlY3RvcjI/bmV3IEs6bmV3IFQpO3JldHVybiBsLmNvcHkoYSkuc3ViKG8pLm5vcm1hbGl6ZSgpLGx9Z2V0VGFuZ2VudEF0KHQsZSl7bGV0IGk9dGhpcy5nZXRVdG9UbWFwcGluZyh0KTtyZXR1cm4gdGhpcy5nZXRUYW5nZW50KGksZSl9Y29tcHV0ZUZyZW5ldEZyYW1lcyh0LGUpe2xldCBpPW5ldyBULHI9W10scz1bXSxvPVtdLGE9bmV3IFQsbD1uZXcgd3Q7Zm9yKGxldCBkPTA7ZDw9dDtkKyspe2xldCBnPWQvdDtyW2RdPXRoaXMuZ2V0VGFuZ2VudEF0KGcsbmV3IFQpfXNbMF09bmV3IFQsb1swXT1uZXcgVDtsZXQgYz1OdW1iZXIuTUFYX1ZBTFVFLHU9TWF0aC5hYnMoclswXS54KSxoPU1hdGguYWJzKHJbMF0ueSksZj1NYXRoLmFicyhyWzBdLnopO3U8PWMmJihjPXUsaS5zZXQoMSwwLDApKSxoPD1jJiYoYz1oLGkuc2V0KDAsMSwwKSksZjw9YyYmaS5zZXQoMCwwLDEpLGEuY3Jvc3NWZWN0b3JzKHJbMF0saSkubm9ybWFsaXplKCksc1swXS5jcm9zc1ZlY3RvcnMoclswXSxhKSxvWzBdLmNyb3NzVmVjdG9ycyhyWzBdLHNbMF0pO2ZvcihsZXQgZD0xO2Q8PXQ7ZCsrKXtpZihzW2RdPXNbZC0xXS5jbG9uZSgpLG9bZF09b1tkLTFdLmNsb25lKCksYS5jcm9zc1ZlY3RvcnMocltkLTFdLHJbZF0pLGEubGVuZ3RoKCk+TnVtYmVyLkVQU0lMT04pe2Eubm9ybWFsaXplKCk7bGV0IGc9TWF0aC5hY29zKEllKHJbZC0xXS5kb3QocltkXSksLTEsMSkpO3NbZF0uYXBwbHlNYXRyaXg0KGwubWFrZVJvdGF0aW9uQXhpcyhhLGcpKX1vW2RdLmNyb3NzVmVjdG9ycyhyW2RdLHNbZF0pfWlmKGU9PT0hMCl7bGV0IGQ9TWF0aC5hY29zKEllKHNbMF0uZG90KHNbdF0pLC0xLDEpKTtkLz10LHJbMF0uZG90KGEuY3Jvc3NWZWN0b3JzKHNbMF0sc1t0XSkpPjAmJihkPS1kKTtmb3IobGV0IGc9MTtnPD10O2crKylzW2ddLmFwcGx5TWF0cml4NChsLm1ha2VSb3RhdGlvbkF4aXMocltnXSxkKmcpKSxvW2ddLmNyb3NzVmVjdG9ycyhyW2ddLHNbZ10pfXJldHVybnt0YW5nZW50czpyLG5vcm1hbHM6cyxiaW5vcm1hbHM6b319Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfWNvcHkodCl7cmV0dXJuIHRoaXMuYXJjTGVuZ3RoRGl2aXNpb25zPXQuYXJjTGVuZ3RoRGl2aXNpb25zLHRoaXN9dG9KU09OKCl7bGV0IHQ9e21ldGFkYXRhOnt2ZXJzaW9uOjQuNSx0eXBlOiJDdXJ2ZSIsZ2VuZXJhdG9yOiJDdXJ2ZS50b0pTT04ifX07cmV0dXJuIHQuYXJjTGVuZ3RoRGl2aXNpb25zPXRoaXMuYXJjTGVuZ3RoRGl2aXNpb25zLHQudHlwZT10aGlzLnR5cGUsdH1mcm9tSlNPTih0KXtyZXR1cm4gdGhpcy5hcmNMZW5ndGhEaXZpc2lvbnM9dC5hcmNMZW5ndGhEaXZpc2lvbnMsdGhpc319LEpyPWNsYXNzIGV4dGVuZHMgRmV7Y29uc3RydWN0b3IodD0wLGU9MCxpPTEscj0xLHM9MCxvPU1hdGguUEkqMixhPSExLGw9MCl7c3VwZXIoKSx0aGlzLnR5cGU9IkVsbGlwc2VDdXJ2ZSIsdGhpcy5hWD10LHRoaXMuYVk9ZSx0aGlzLnhSYWRpdXM9aSx0aGlzLnlSYWRpdXM9cix0aGlzLmFTdGFydEFuZ2xlPXMsdGhpcy5hRW5kQW5nbGU9byx0aGlzLmFDbG9ja3dpc2U9YSx0aGlzLmFSb3RhdGlvbj1sfWdldFBvaW50KHQsZSl7bGV0IGk9ZXx8bmV3IEsscj1NYXRoLlBJKjIscz10aGlzLmFFbmRBbmdsZS10aGlzLmFTdGFydEFuZ2xlLG89TWF0aC5hYnMocyk8TnVtYmVyLkVQU0lMT047Zm9yKDtzPDA7KXMrPXI7Zm9yKDtzPnI7KXMtPXI7czxOdW1iZXIuRVBTSUxPTiYmKG8/cz0wOnM9ciksdGhpcy5hQ2xvY2t3aXNlPT09ITAmJiFvJiYocz09PXI/cz0tcjpzPXMtcik7bGV0IGE9dGhpcy5hU3RhcnRBbmdsZSt0KnMsbD10aGlzLmFYK3RoaXMueFJhZGl1cypNYXRoLmNvcyhhKSxjPXRoaXMuYVkrdGhpcy55UmFkaXVzKk1hdGguc2luKGEpO2lmKHRoaXMuYVJvdGF0aW9uIT09MCl7bGV0IHU9TWF0aC5jb3ModGhpcy5hUm90YXRpb24pLGg9TWF0aC5zaW4odGhpcy5hUm90YXRpb24pLGY9bC10aGlzLmFYLGQ9Yy10aGlzLmFZO2w9Zip1LWQqaCt0aGlzLmFYLGM9ZipoK2QqdSt0aGlzLmFZfXJldHVybiBpLnNldChsLGMpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5hWD10LmFYLHRoaXMuYVk9dC5hWSx0aGlzLnhSYWRpdXM9dC54UmFkaXVzLHRoaXMueVJhZGl1cz10LnlSYWRpdXMsdGhpcy5hU3RhcnRBbmdsZT10LmFTdGFydEFuZ2xlLHRoaXMuYUVuZEFuZ2xlPXQuYUVuZEFuZ2xlLHRoaXMuYUNsb2Nrd2lzZT10LmFDbG9ja3dpc2UsdGhpcy5hUm90YXRpb249dC5hUm90YXRpb24sdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC5hWD10aGlzLmFYLHQuYVk9dGhpcy5hWSx0LnhSYWRpdXM9dGhpcy54UmFkaXVzLHQueVJhZGl1cz10aGlzLnlSYWRpdXMsdC5hU3RhcnRBbmdsZT10aGlzLmFTdGFydEFuZ2xlLHQuYUVuZEFuZ2xlPXRoaXMuYUVuZEFuZ2xlLHQuYUNsb2Nrd2lzZT10aGlzLmFDbG9ja3dpc2UsdC5hUm90YXRpb249dGhpcy5hUm90YXRpb24sdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy5hWD10LmFYLHRoaXMuYVk9dC5hWSx0aGlzLnhSYWRpdXM9dC54UmFkaXVzLHRoaXMueVJhZGl1cz10LnlSYWRpdXMsdGhpcy5hU3RhcnRBbmdsZT10LmFTdGFydEFuZ2xlLHRoaXMuYUVuZEFuZ2xlPXQuYUVuZEFuZ2xlLHRoaXMuYUNsb2Nrd2lzZT10LmFDbG9ja3dpc2UsdGhpcy5hUm90YXRpb249dC5hUm90YXRpb24sdGhpc319O0pyLnByb3RvdHlwZS5pc0VsbGlwc2VDdXJ2ZT0hMDt2YXIgbWw9Y2xhc3MgZXh0ZW5kcyBKcntjb25zdHJ1Y3Rvcih0LGUsaSxyLHMsbyl7c3VwZXIodCxlLGksaSxyLHMsbyksdGhpcy50eXBlPSJBcmNDdXJ2ZSJ9fTttbC5wcm90b3R5cGUuaXNBcmNDdXJ2ZT0hMDtmdW5jdGlvbiB0Zigpe2xldCBuPTAsdD0wLGU9MCxpPTA7ZnVuY3Rpb24gcihzLG8sYSxsKXtuPXMsdD1hLGU9LTMqcyszKm8tMiphLWwsaT0yKnMtMipvK2ErbH1yZXR1cm57aW5pdENhdG11bGxSb206ZnVuY3Rpb24ocyxvLGEsbCxjKXtyKG8sYSxjKihhLXMpLGMqKGwtbykpfSxpbml0Tm9udW5pZm9ybUNhdG11bGxSb206ZnVuY3Rpb24ocyxvLGEsbCxjLHUsaCl7bGV0IGY9KG8tcykvYy0oYS1zKS8oYyt1KSsoYS1vKS91LGQ9KGEtbykvdS0obC1vKS8odStoKSsobC1hKS9oO2YqPXUsZCo9dSxyKG8sYSxmLGQpfSxjYWxjOmZ1bmN0aW9uKHMpe2xldCBvPXMqcyxhPW8qcztyZXR1cm4gbit0KnMrZSpvK2kqYX19fXZhciBYYT1uZXcgVCxQdT1uZXcgdGYsRHU9bmV3IHRmLEl1PW5ldyB0ZixnbD1jbGFzcyBleHRlbmRzIEZle2NvbnN0cnVjdG9yKHQ9W10sZT0hMSxpPSJjZW50cmlwZXRhbCIscj0uNSl7c3VwZXIoKSx0aGlzLnR5cGU9IkNhdG11bGxSb21DdXJ2ZTMiLHRoaXMucG9pbnRzPXQsdGhpcy5jbG9zZWQ9ZSx0aGlzLmN1cnZlVHlwZT1pLHRoaXMudGVuc2lvbj1yfWdldFBvaW50KHQsZT1uZXcgVCl7bGV0IGk9ZSxyPXRoaXMucG9pbnRzLHM9ci5sZW5ndGgsbz0ocy0odGhpcy5jbG9zZWQ/MDoxKSkqdCxhPU1hdGguZmxvb3IobyksbD1vLWE7dGhpcy5jbG9zZWQ/YSs9YT4wPzA6KE1hdGguZmxvb3IoTWF0aC5hYnMoYSkvcykrMSkqczpsPT09MCYmYT09PXMtMSYmKGE9cy0yLGw9MSk7bGV0IGMsdTt0aGlzLmNsb3NlZHx8YT4wP2M9clsoYS0xKSVzXTooWGEuc3ViVmVjdG9ycyhyWzBdLHJbMV0pLmFkZChyWzBdKSxjPVhhKTtsZXQgaD1yW2Elc10sZj1yWyhhKzEpJXNdO2lmKHRoaXMuY2xvc2VkfHxhKzI8cz91PXJbKGErMiklc106KFhhLnN1YlZlY3RvcnMocltzLTFdLHJbcy0yXSkuYWRkKHJbcy0xXSksdT1YYSksdGhpcy5jdXJ2ZVR5cGU9PT0iY2VudHJpcGV0YWwifHx0aGlzLmN1cnZlVHlwZT09PSJjaG9yZGFsIil7bGV0IGQ9dGhpcy5jdXJ2ZVR5cGU9PT0iY2hvcmRhbCI/LjU6LjI1LGc9TWF0aC5wb3coYy5kaXN0YW5jZVRvU3F1YXJlZChoKSxkKSx4PU1hdGgucG93KGguZGlzdGFuY2VUb1NxdWFyZWQoZiksZCksdj1NYXRoLnBvdyhmLmRpc3RhbmNlVG9TcXVhcmVkKHUpLGQpO3g8MWUtNCYmKHg9MSksZzwxZS00JiYoZz14KSx2PDFlLTQmJih2PXgpLFB1LmluaXROb251bmlmb3JtQ2F0bXVsbFJvbShjLngsaC54LGYueCx1LngsZyx4LHYpLER1LmluaXROb251bmlmb3JtQ2F0bXVsbFJvbShjLnksaC55LGYueSx1LnksZyx4LHYpLEl1LmluaXROb251bmlmb3JtQ2F0bXVsbFJvbShjLnosaC56LGYueix1LnosZyx4LHYpfWVsc2UgdGhpcy5jdXJ2ZVR5cGU9PT0iY2F0bXVsbHJvbSImJihQdS5pbml0Q2F0bXVsbFJvbShjLngsaC54LGYueCx1LngsdGhpcy50ZW5zaW9uKSxEdS5pbml0Q2F0bXVsbFJvbShjLnksaC55LGYueSx1LnksdGhpcy50ZW5zaW9uKSxJdS5pbml0Q2F0bXVsbFJvbShjLnosaC56LGYueix1LnosdGhpcy50ZW5zaW9uKSk7cmV0dXJuIGkuc2V0KFB1LmNhbGMobCksRHUuY2FsYyhsKSxJdS5jYWxjKGwpKSxpfWNvcHkodCl7c3VwZXIuY29weSh0KSx0aGlzLnBvaW50cz1bXTtmb3IobGV0IGU9MCxpPXQucG9pbnRzLmxlbmd0aDtlPGk7ZSsrKXtsZXQgcj10LnBvaW50c1tlXTt0aGlzLnBvaW50cy5wdXNoKHIuY2xvbmUoKSl9cmV0dXJuIHRoaXMuY2xvc2VkPXQuY2xvc2VkLHRoaXMuY3VydmVUeXBlPXQuY3VydmVUeXBlLHRoaXMudGVuc2lvbj10LnRlbnNpb24sdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTt0LnBvaW50cz1bXTtmb3IobGV0IGU9MCxpPXRoaXMucG9pbnRzLmxlbmd0aDtlPGk7ZSsrKXtsZXQgcj10aGlzLnBvaW50c1tlXTt0LnBvaW50cy5wdXNoKHIudG9BcnJheSgpKX1yZXR1cm4gdC5jbG9zZWQ9dGhpcy5jbG9zZWQsdC5jdXJ2ZVR5cGU9dGhpcy5jdXJ2ZVR5cGUsdC50ZW5zaW9uPXRoaXMudGVuc2lvbix0fWZyb21KU09OKHQpe3N1cGVyLmZyb21KU09OKHQpLHRoaXMucG9pbnRzPVtdO2ZvcihsZXQgZT0wLGk9dC5wb2ludHMubGVuZ3RoO2U8aTtlKyspe2xldCByPXQucG9pbnRzW2VdO3RoaXMucG9pbnRzLnB1c2gobmV3IFQoKS5mcm9tQXJyYXkocikpfXJldHVybiB0aGlzLmNsb3NlZD10LmNsb3NlZCx0aGlzLmN1cnZlVHlwZT10LmN1cnZlVHlwZSx0aGlzLnRlbnNpb249dC50ZW5zaW9uLHRoaXN9fTtnbC5wcm90b3R5cGUuaXNDYXRtdWxsUm9tQ3VydmUzPSEwO2Z1bmN0aW9uIFFnKG4sdCxlLGkscil7bGV0IHM9KGktdCkqLjUsbz0oci1lKSouNSxhPW4qbixsPW4qYTtyZXR1cm4oMiplLTIqaStzK28pKmwrKC0zKmUrMyppLTIqcy1vKSphK3MqbitlfWZ1bmN0aW9uIEZFKG4sdCl7bGV0IGU9MS1uO3JldHVybiBlKmUqdH1mdW5jdGlvbiB6RShuLHQpe3JldHVybiAyKigxLW4pKm4qdH1mdW5jdGlvbiBVRShuLHQpe3JldHVybiBuKm4qdH1mdW5jdGlvbiBqcyhuLHQsZSxpKXtyZXR1cm4gRkUobix0KSt6RShuLGUpK1VFKG4saSl9ZnVuY3Rpb24gQkUobix0KXtsZXQgZT0xLW47cmV0dXJuIGUqZSplKnR9ZnVuY3Rpb24gT0Uobix0KXtsZXQgZT0xLW47cmV0dXJuIDMqZSplKm4qdH1mdW5jdGlvbiBrRShuLHQpe3JldHVybiAzKigxLW4pKm4qbip0fWZ1bmN0aW9uIEhFKG4sdCl7cmV0dXJuIG4qbipuKnR9ZnVuY3Rpb24gdG8obix0LGUsaSxyKXtyZXR1cm4gQkUobix0KStPRShuLGUpK2tFKG4saSkrSEUobixyKX12YXIgeG89Y2xhc3MgZXh0ZW5kcyBGZXtjb25zdHJ1Y3Rvcih0PW5ldyBLLGU9bmV3IEssaT1uZXcgSyxyPW5ldyBLKXtzdXBlcigpLHRoaXMudHlwZT0iQ3ViaWNCZXppZXJDdXJ2ZSIsdGhpcy52MD10LHRoaXMudjE9ZSx0aGlzLnYyPWksdGhpcy52Mz1yfWdldFBvaW50KHQsZT1uZXcgSyl7bGV0IGk9ZSxyPXRoaXMudjAscz10aGlzLnYxLG89dGhpcy52MixhPXRoaXMudjM7cmV0dXJuIGkuc2V0KHRvKHQsci54LHMueCxvLngsYS54KSx0byh0LHIueSxzLnksby55LGEueSkpLGl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLnYwLmNvcHkodC52MCksdGhpcy52MS5jb3B5KHQudjEpLHRoaXMudjIuY29weSh0LnYyKSx0aGlzLnYzLmNvcHkodC52MyksdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC52MD10aGlzLnYwLnRvQXJyYXkoKSx0LnYxPXRoaXMudjEudG9BcnJheSgpLHQudjI9dGhpcy52Mi50b0FycmF5KCksdC52Mz10aGlzLnYzLnRvQXJyYXkoKSx0fWZyb21KU09OKHQpe3JldHVybiBzdXBlci5mcm9tSlNPTih0KSx0aGlzLnYwLmZyb21BcnJheSh0LnYwKSx0aGlzLnYxLmZyb21BcnJheSh0LnYxKSx0aGlzLnYyLmZyb21BcnJheSh0LnYyKSx0aGlzLnYzLmZyb21BcnJheSh0LnYzKSx0aGlzfX07eG8ucHJvdG90eXBlLmlzQ3ViaWNCZXppZXJDdXJ2ZT0hMDt2YXIgeGw9Y2xhc3MgZXh0ZW5kcyBGZXtjb25zdHJ1Y3Rvcih0PW5ldyBULGU9bmV3IFQsaT1uZXcgVCxyPW5ldyBUKXtzdXBlcigpLHRoaXMudHlwZT0iQ3ViaWNCZXppZXJDdXJ2ZTMiLHRoaXMudjA9dCx0aGlzLnYxPWUsdGhpcy52Mj1pLHRoaXMudjM9cn1nZXRQb2ludCh0LGU9bmV3IFQpe2xldCBpPWUscj10aGlzLnYwLHM9dGhpcy52MSxvPXRoaXMudjIsYT10aGlzLnYzO3JldHVybiBpLnNldCh0byh0LHIueCxzLngsby54LGEueCksdG8odCxyLnkscy55LG8ueSxhLnkpLHRvKHQsci56LHMueixvLnosYS56KSksaX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMudjAuY29weSh0LnYwKSx0aGlzLnYxLmNvcHkodC52MSksdGhpcy52Mi5jb3B5KHQudjIpLHRoaXMudjMuY29weSh0LnYzKSx0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3JldHVybiB0LnYwPXRoaXMudjAudG9BcnJheSgpLHQudjE9dGhpcy52MS50b0FycmF5KCksdC52Mj10aGlzLnYyLnRvQXJyYXkoKSx0LnYzPXRoaXMudjMudG9BcnJheSgpLHR9ZnJvbUpTT04odCl7cmV0dXJuIHN1cGVyLmZyb21KU09OKHQpLHRoaXMudjAuZnJvbUFycmF5KHQudjApLHRoaXMudjEuZnJvbUFycmF5KHQudjEpLHRoaXMudjIuZnJvbUFycmF5KHQudjIpLHRoaXMudjMuZnJvbUFycmF5KHQudjMpLHRoaXN9fTt4bC5wcm90b3R5cGUuaXNDdWJpY0JlemllckN1cnZlMz0hMDt2YXIgJHI9Y2xhc3MgZXh0ZW5kcyBGZXtjb25zdHJ1Y3Rvcih0PW5ldyBLLGU9bmV3IEspe3N1cGVyKCksdGhpcy50eXBlPSJMaW5lQ3VydmUiLHRoaXMudjE9dCx0aGlzLnYyPWV9Z2V0UG9pbnQodCxlPW5ldyBLKXtsZXQgaT1lO3JldHVybiB0PT09MT9pLmNvcHkodGhpcy52Mik6KGkuY29weSh0aGlzLnYyKS5zdWIodGhpcy52MSksaS5tdWx0aXBseVNjYWxhcih0KS5hZGQodGhpcy52MSkpLGl9Z2V0UG9pbnRBdCh0LGUpe3JldHVybiB0aGlzLmdldFBvaW50KHQsZSl9Z2V0VGFuZ2VudCh0LGUpe2xldCBpPWV8fG5ldyBLO3JldHVybiBpLmNvcHkodGhpcy52Mikuc3ViKHRoaXMudjEpLm5vcm1hbGl6ZSgpLGl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLnYxLmNvcHkodC52MSksdGhpcy52Mi5jb3B5KHQudjIpLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIHQudjE9dGhpcy52MS50b0FycmF5KCksdC52Mj10aGlzLnYyLnRvQXJyYXkoKSx0fWZyb21KU09OKHQpe3JldHVybiBzdXBlci5mcm9tSlNPTih0KSx0aGlzLnYxLmZyb21BcnJheSh0LnYxKSx0aGlzLnYyLmZyb21BcnJheSh0LnYyKSx0aGlzfX07JHIucHJvdG90eXBlLmlzTGluZUN1cnZlPSEwO3ZhciBhaD1jbGFzcyBleHRlbmRzIEZle2NvbnN0cnVjdG9yKHQ9bmV3IFQsZT1uZXcgVCl7c3VwZXIoKSx0aGlzLnR5cGU9IkxpbmVDdXJ2ZTMiLHRoaXMuaXNMaW5lQ3VydmUzPSEwLHRoaXMudjE9dCx0aGlzLnYyPWV9Z2V0UG9pbnQodCxlPW5ldyBUKXtsZXQgaT1lO3JldHVybiB0PT09MT9pLmNvcHkodGhpcy52Mik6KGkuY29weSh0aGlzLnYyKS5zdWIodGhpcy52MSksaS5tdWx0aXBseVNjYWxhcih0KS5hZGQodGhpcy52MSkpLGl9Z2V0UG9pbnRBdCh0LGUpe3JldHVybiB0aGlzLmdldFBvaW50KHQsZSl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLnYxLmNvcHkodC52MSksdGhpcy52Mi5jb3B5KHQudjIpLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIHQudjE9dGhpcy52MS50b0FycmF5KCksdC52Mj10aGlzLnYyLnRvQXJyYXkoKSx0fWZyb21KU09OKHQpe3JldHVybiBzdXBlci5mcm9tSlNPTih0KSx0aGlzLnYxLmZyb21BcnJheSh0LnYxKSx0aGlzLnYyLmZyb21BcnJheSh0LnYyKSx0aGlzfX0seW89Y2xhc3MgZXh0ZW5kcyBGZXtjb25zdHJ1Y3Rvcih0PW5ldyBLLGU9bmV3IEssaT1uZXcgSyl7c3VwZXIoKSx0aGlzLnR5cGU9IlF1YWRyYXRpY0JlemllckN1cnZlIix0aGlzLnYwPXQsdGhpcy52MT1lLHRoaXMudjI9aX1nZXRQb2ludCh0LGU9bmV3IEspe2xldCBpPWUscj10aGlzLnYwLHM9dGhpcy52MSxvPXRoaXMudjI7cmV0dXJuIGkuc2V0KGpzKHQsci54LHMueCxvLngpLGpzKHQsci55LHMueSxvLnkpKSxpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy52MC5jb3B5KHQudjApLHRoaXMudjEuY29weSh0LnYxKSx0aGlzLnYyLmNvcHkodC52MiksdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC52MD10aGlzLnYwLnRvQXJyYXkoKSx0LnYxPXRoaXMudjEudG9BcnJheSgpLHQudjI9dGhpcy52Mi50b0FycmF5KCksdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy52MC5mcm9tQXJyYXkodC52MCksdGhpcy52MS5mcm9tQXJyYXkodC52MSksdGhpcy52Mi5mcm9tQXJyYXkodC52MiksdGhpc319O3lvLnByb3RvdHlwZS5pc1F1YWRyYXRpY0JlemllckN1cnZlPSEwO3ZhciB5bD1jbGFzcyBleHRlbmRzIEZle2NvbnN0cnVjdG9yKHQ9bmV3IFQsZT1uZXcgVCxpPW5ldyBUKXtzdXBlcigpLHRoaXMudHlwZT0iUXVhZHJhdGljQmV6aWVyQ3VydmUzIix0aGlzLnYwPXQsdGhpcy52MT1lLHRoaXMudjI9aX1nZXRQb2ludCh0LGU9bmV3IFQpe2xldCBpPWUscj10aGlzLnYwLHM9dGhpcy52MSxvPXRoaXMudjI7cmV0dXJuIGkuc2V0KGpzKHQsci54LHMueCxvLngpLGpzKHQsci55LHMueSxvLnkpLGpzKHQsci56LHMueixvLnopKSxpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy52MC5jb3B5KHQudjApLHRoaXMudjEuY29weSh0LnYxKSx0aGlzLnYyLmNvcHkodC52MiksdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC52MD10aGlzLnYwLnRvQXJyYXkoKSx0LnYxPXRoaXMudjEudG9BcnJheSgpLHQudjI9dGhpcy52Mi50b0FycmF5KCksdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy52MC5mcm9tQXJyYXkodC52MCksdGhpcy52MS5mcm9tQXJyYXkodC52MSksdGhpcy52Mi5mcm9tQXJyYXkodC52MiksdGhpc319O3lsLnByb3RvdHlwZS5pc1F1YWRyYXRpY0JlemllckN1cnZlMz0hMDt2YXIgdm89Y2xhc3MgZXh0ZW5kcyBGZXtjb25zdHJ1Y3Rvcih0PVtdKXtzdXBlcigpLHRoaXMudHlwZT0iU3BsaW5lQ3VydmUiLHRoaXMucG9pbnRzPXR9Z2V0UG9pbnQodCxlPW5ldyBLKXtsZXQgaT1lLHI9dGhpcy5wb2ludHMscz0oci5sZW5ndGgtMSkqdCxvPU1hdGguZmxvb3IocyksYT1zLW8sbD1yW289PT0wP286by0xXSxjPXJbb10sdT1yW28+ci5sZW5ndGgtMj9yLmxlbmd0aC0xOm8rMV0saD1yW28+ci5sZW5ndGgtMz9yLmxlbmd0aC0xOm8rMl07cmV0dXJuIGkuc2V0KFFnKGEsbC54LGMueCx1LngsaC54KSxRZyhhLGwueSxjLnksdS55LGgueSkpLGl9Y29weSh0KXtzdXBlci5jb3B5KHQpLHRoaXMucG9pbnRzPVtdO2ZvcihsZXQgZT0wLGk9dC5wb2ludHMubGVuZ3RoO2U8aTtlKyspe2xldCByPXQucG9pbnRzW2VdO3RoaXMucG9pbnRzLnB1c2goci5jbG9uZSgpKX1yZXR1cm4gdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTt0LnBvaW50cz1bXTtmb3IobGV0IGU9MCxpPXRoaXMucG9pbnRzLmxlbmd0aDtlPGk7ZSsrKXtsZXQgcj10aGlzLnBvaW50c1tlXTt0LnBvaW50cy5wdXNoKHIudG9BcnJheSgpKX1yZXR1cm4gdH1mcm9tSlNPTih0KXtzdXBlci5mcm9tSlNPTih0KSx0aGlzLnBvaW50cz1bXTtmb3IobGV0IGU9MCxpPXQucG9pbnRzLmxlbmd0aDtlPGk7ZSsrKXtsZXQgcj10LnBvaW50c1tlXTt0aGlzLnBvaW50cy5wdXNoKG5ldyBLKCkuZnJvbUFycmF5KHIpKX1yZXR1cm4gdGhpc319O3ZvLnByb3RvdHlwZS5pc1NwbGluZUN1cnZlPSEwO3ZhciBDMD1PYmplY3QuZnJlZXplKHtfX3Byb3RvX186bnVsbCxBcmNDdXJ2ZTptbCxDYXRtdWxsUm9tQ3VydmUzOmdsLEN1YmljQmV6aWVyQ3VydmU6eG8sQ3ViaWNCZXppZXJDdXJ2ZTM6eGwsRWxsaXBzZUN1cnZlOkpyLExpbmVDdXJ2ZTokcixMaW5lQ3VydmUzOmFoLFF1YWRyYXRpY0JlemllckN1cnZlOnlvLFF1YWRyYXRpY0JlemllckN1cnZlMzp5bCxTcGxpbmVDdXJ2ZTp2b30pLGxoPWNsYXNzIGV4dGVuZHMgRmV7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMudHlwZT0iQ3VydmVQYXRoIix0aGlzLmN1cnZlcz1bXSx0aGlzLmF1dG9DbG9zZT0hMX1hZGQodCl7dGhpcy5jdXJ2ZXMucHVzaCh0KX1jbG9zZVBhdGgoKXtsZXQgdD10aGlzLmN1cnZlc1swXS5nZXRQb2ludCgwKSxlPXRoaXMuY3VydmVzW3RoaXMuY3VydmVzLmxlbmd0aC0xXS5nZXRQb2ludCgxKTt0LmVxdWFscyhlKXx8dGhpcy5jdXJ2ZXMucHVzaChuZXcgJHIoZSx0KSl9Z2V0UG9pbnQodCxlKXtsZXQgaT10KnRoaXMuZ2V0TGVuZ3RoKCkscj10aGlzLmdldEN1cnZlTGVuZ3RocygpLHM9MDtmb3IoO3M8ci5sZW5ndGg7KXtpZihyW3NdPj1pKXtsZXQgbz1yW3NdLWksYT10aGlzLmN1cnZlc1tzXSxsPWEuZ2V0TGVuZ3RoKCksYz1sPT09MD8wOjEtby9sO3JldHVybiBhLmdldFBvaW50QXQoYyxlKX1zKyt9cmV0dXJuIG51bGx9Z2V0TGVuZ3RoKCl7bGV0IHQ9dGhpcy5nZXRDdXJ2ZUxlbmd0aHMoKTtyZXR1cm4gdFt0Lmxlbmd0aC0xXX11cGRhdGVBcmNMZW5ndGhzKCl7dGhpcy5uZWVkc1VwZGF0ZT0hMCx0aGlzLmNhY2hlTGVuZ3Rocz1udWxsLHRoaXMuZ2V0Q3VydmVMZW5ndGhzKCl9Z2V0Q3VydmVMZW5ndGhzKCl7aWYodGhpcy5jYWNoZUxlbmd0aHMmJnRoaXMuY2FjaGVMZW5ndGhzLmxlbmd0aD09PXRoaXMuY3VydmVzLmxlbmd0aClyZXR1cm4gdGhpcy5jYWNoZUxlbmd0aHM7bGV0IHQ9W10sZT0wO2ZvcihsZXQgaT0wLHI9dGhpcy5jdXJ2ZXMubGVuZ3RoO2k8cjtpKyspZSs9dGhpcy5jdXJ2ZXNbaV0uZ2V0TGVuZ3RoKCksdC5wdXNoKGUpO3JldHVybiB0aGlzLmNhY2hlTGVuZ3Rocz10LHR9Z2V0U3BhY2VkUG9pbnRzKHQ9NDApe2xldCBlPVtdO2ZvcihsZXQgaT0wO2k8PXQ7aSsrKWUucHVzaCh0aGlzLmdldFBvaW50KGkvdCkpO3JldHVybiB0aGlzLmF1dG9DbG9zZSYmZS5wdXNoKGVbMF0pLGV9Z2V0UG9pbnRzKHQ9MTIpe2xldCBlPVtdLGk7Zm9yKGxldCByPTAscz10aGlzLmN1cnZlcztyPHMubGVuZ3RoO3IrKyl7bGV0IG89c1tyXSxhPW8mJm8uaXNFbGxpcHNlQ3VydmU/dCoyOm8mJihvLmlzTGluZUN1cnZlfHxvLmlzTGluZUN1cnZlMyk/MTpvJiZvLmlzU3BsaW5lQ3VydmU/dCpvLnBvaW50cy5sZW5ndGg6dCxsPW8uZ2V0UG9pbnRzKGEpO2ZvcihsZXQgYz0wO2M8bC5sZW5ndGg7YysrKXtsZXQgdT1sW2NdO2kmJmkuZXF1YWxzKHUpfHwoZS5wdXNoKHUpLGk9dSl9fXJldHVybiB0aGlzLmF1dG9DbG9zZSYmZS5sZW5ndGg+MSYmIWVbZS5sZW5ndGgtMV0uZXF1YWxzKGVbMF0pJiZlLnB1c2goZVswXSksZX1jb3B5KHQpe3N1cGVyLmNvcHkodCksdGhpcy5jdXJ2ZXM9W107Zm9yKGxldCBlPTAsaT10LmN1cnZlcy5sZW5ndGg7ZTxpO2UrKyl7bGV0IHI9dC5jdXJ2ZXNbZV07dGhpcy5jdXJ2ZXMucHVzaChyLmNsb25lKCkpfXJldHVybiB0aGlzLmF1dG9DbG9zZT10LmF1dG9DbG9zZSx0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3QuYXV0b0Nsb3NlPXRoaXMuYXV0b0Nsb3NlLHQuY3VydmVzPVtdO2ZvcihsZXQgZT0wLGk9dGhpcy5jdXJ2ZXMubGVuZ3RoO2U8aTtlKyspe2xldCByPXRoaXMuY3VydmVzW2VdO3QuY3VydmVzLnB1c2goci50b0pTT04oKSl9cmV0dXJuIHR9ZnJvbUpTT04odCl7c3VwZXIuZnJvbUpTT04odCksdGhpcy5hdXRvQ2xvc2U9dC5hdXRvQ2xvc2UsdGhpcy5jdXJ2ZXM9W107Zm9yKGxldCBlPTAsaT10LmN1cnZlcy5sZW5ndGg7ZTxpO2UrKyl7bGV0IHI9dC5jdXJ2ZXNbZV07dGhpcy5jdXJ2ZXMucHVzaChuZXcgQzBbci50eXBlXSgpLmZyb21KU09OKHIpKX1yZXR1cm4gdGhpc319LF9vPWNsYXNzIGV4dGVuZHMgbGh7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9IlBhdGgiLHRoaXMuY3VycmVudFBvaW50PW5ldyBLLHQmJnRoaXMuc2V0RnJvbVBvaW50cyh0KX1zZXRGcm9tUG9pbnRzKHQpe3RoaXMubW92ZVRvKHRbMF0ueCx0WzBdLnkpO2ZvcihsZXQgZT0xLGk9dC5sZW5ndGg7ZTxpO2UrKyl0aGlzLmxpbmVUbyh0W2VdLngsdFtlXS55KTtyZXR1cm4gdGhpc31tb3ZlVG8odCxlKXtyZXR1cm4gdGhpcy5jdXJyZW50UG9pbnQuc2V0KHQsZSksdGhpc31saW5lVG8odCxlKXtsZXQgaT1uZXcgJHIodGhpcy5jdXJyZW50UG9pbnQuY2xvbmUoKSxuZXcgSyh0LGUpKTtyZXR1cm4gdGhpcy5jdXJ2ZXMucHVzaChpKSx0aGlzLmN1cnJlbnRQb2ludC5zZXQodCxlKSx0aGlzfXF1YWRyYXRpY0N1cnZlVG8odCxlLGkscil7bGV0IHM9bmV3IHlvKHRoaXMuY3VycmVudFBvaW50LmNsb25lKCksbmV3IEsodCxlKSxuZXcgSyhpLHIpKTtyZXR1cm4gdGhpcy5jdXJ2ZXMucHVzaChzKSx0aGlzLmN1cnJlbnRQb2ludC5zZXQoaSxyKSx0aGlzfWJlemllckN1cnZlVG8odCxlLGkscixzLG8pe2xldCBhPW5ldyB4byh0aGlzLmN1cnJlbnRQb2ludC5jbG9uZSgpLG5ldyBLKHQsZSksbmV3IEsoaSxyKSxuZXcgSyhzLG8pKTtyZXR1cm4gdGhpcy5jdXJ2ZXMucHVzaChhKSx0aGlzLmN1cnJlbnRQb2ludC5zZXQocyxvKSx0aGlzfXNwbGluZVRocnUodCl7bGV0IGU9W3RoaXMuY3VycmVudFBvaW50LmNsb25lKCldLmNvbmNhdCh0KSxpPW5ldyB2byhlKTtyZXR1cm4gdGhpcy5jdXJ2ZXMucHVzaChpKSx0aGlzLmN1cnJlbnRQb2ludC5jb3B5KHRbdC5sZW5ndGgtMV0pLHRoaXN9YXJjKHQsZSxpLHIscyxvKXtsZXQgYT10aGlzLmN1cnJlbnRQb2ludC54LGw9dGhpcy5jdXJyZW50UG9pbnQueTtyZXR1cm4gdGhpcy5hYnNhcmModCthLGUrbCxpLHIscyxvKSx0aGlzfWFic2FyYyh0LGUsaSxyLHMsbyl7cmV0dXJuIHRoaXMuYWJzZWxsaXBzZSh0LGUsaSxpLHIscyxvKSx0aGlzfWVsbGlwc2UodCxlLGkscixzLG8sYSxsKXtsZXQgYz10aGlzLmN1cnJlbnRQb2ludC54LHU9dGhpcy5jdXJyZW50UG9pbnQueTtyZXR1cm4gdGhpcy5hYnNlbGxpcHNlKHQrYyxlK3UsaSxyLHMsbyxhLGwpLHRoaXN9YWJzZWxsaXBzZSh0LGUsaSxyLHMsbyxhLGwpe2xldCBjPW5ldyBKcih0LGUsaSxyLHMsbyxhLGwpO2lmKHRoaXMuY3VydmVzLmxlbmd0aD4wKXtsZXQgaD1jLmdldFBvaW50KDApO2guZXF1YWxzKHRoaXMuY3VycmVudFBvaW50KXx8dGhpcy5saW5lVG8oaC54LGgueSl9dGhpcy5jdXJ2ZXMucHVzaChjKTtsZXQgdT1jLmdldFBvaW50KDEpO3JldHVybiB0aGlzLmN1cnJlbnRQb2ludC5jb3B5KHUpLHRoaXN9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmN1cnJlbnRQb2ludC5jb3B5KHQuY3VycmVudFBvaW50KSx0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3JldHVybiB0LmN1cnJlbnRQb2ludD10aGlzLmN1cnJlbnRQb2ludC50b0FycmF5KCksdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy5jdXJyZW50UG9pbnQuZnJvbUFycmF5KHQuY3VycmVudFBvaW50KSx0aGlzfX0sVW49Y2xhc3MgZXh0ZW5kcyBfb3tjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KSx0aGlzLnV1aWQ9dG4oKSx0aGlzLnR5cGU9IlNoYXBlIix0aGlzLmhvbGVzPVtdfWdldFBvaW50c0hvbGVzKHQpe2xldCBlPVtdO2ZvcihsZXQgaT0wLHI9dGhpcy5ob2xlcy5sZW5ndGg7aTxyO2krKyllW2ldPXRoaXMuaG9sZXNbaV0uZ2V0UG9pbnRzKHQpO3JldHVybiBlfWV4dHJhY3RQb2ludHModCl7cmV0dXJue3NoYXBlOnRoaXMuZ2V0UG9pbnRzKHQpLGhvbGVzOnRoaXMuZ2V0UG9pbnRzSG9sZXModCl9fWNvcHkodCl7c3VwZXIuY29weSh0KSx0aGlzLmhvbGVzPVtdO2ZvcihsZXQgZT0wLGk9dC5ob2xlcy5sZW5ndGg7ZTxpO2UrKyl7bGV0IHI9dC5ob2xlc1tlXTt0aGlzLmhvbGVzLnB1c2goci5jbG9uZSgpKX1yZXR1cm4gdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTt0LnV1aWQ9dGhpcy51dWlkLHQuaG9sZXM9W107Zm9yKGxldCBlPTAsaT10aGlzLmhvbGVzLmxlbmd0aDtlPGk7ZSsrKXtsZXQgcj10aGlzLmhvbGVzW2VdO3QuaG9sZXMucHVzaChyLnRvSlNPTigpKX1yZXR1cm4gdH1mcm9tSlNPTih0KXtzdXBlci5mcm9tSlNPTih0KSx0aGlzLnV1aWQ9dC51dWlkLHRoaXMuaG9sZXM9W107Zm9yKGxldCBlPTAsaT10LmhvbGVzLmxlbmd0aDtlPGk7ZSsrKXtsZXQgcj10LmhvbGVzW2VdO3RoaXMuaG9sZXMucHVzaChuZXcgX28oKS5mcm9tSlNPTihyKSl9cmV0dXJuIHRoaXN9fSxWRT17dHJpYW5ndWxhdGU6ZnVuY3Rpb24obix0LGU9Mil7bGV0IGk9dCYmdC5sZW5ndGgscj1pP3RbMF0qZTpuLmxlbmd0aCxzPVIwKG4sMCxyLGUsITApLG89W107aWYoIXN8fHMubmV4dD09PXMucHJldilyZXR1cm4gbztsZXQgYSxsLGMsdSxoLGYsZDtpZihpJiYocz1ZRShuLHQscyxlKSksbi5sZW5ndGg+ODAqZSl7YT1jPW5bMF0sbD11PW5bMV07Zm9yKGxldCBnPWU7ZzxyO2crPWUpaD1uW2ddLGY9bltnKzFdLGg8YSYmKGE9aCksZjxsJiYobD1mKSxoPmMmJihjPWgpLGY+dSYmKHU9Zik7ZD1NYXRoLm1heChjLWEsdS1sKSxkPWQhPT0wPzEvZDowfXJldHVybiB3byhzLG8sZSxhLGwsZCksb319O2Z1bmN0aW9uIFIwKG4sdCxlLGkscil7bGV0IHMsbztpZihyPT09clQobix0LGUsaSk+MClmb3Iocz10O3M8ZTtzKz1pKW89amcocyxuW3NdLG5bcysxXSxvKTtlbHNlIGZvcihzPWUtaTtzPj10O3MtPWkpbz1qZyhzLG5bc10sbltzKzFdLG8pO3JldHVybiBvJiZEbChvLG8ubmV4dCkmJihibyhvKSxvPW8ubmV4dCksb31mdW5jdGlvbiBsaShuLHQpe2lmKCFuKXJldHVybiBuO3R8fCh0PW4pO2xldCBlPW4saTtkbyBpZihpPSExLCFlLnN0ZWluZXImJihEbChlLGUubmV4dCl8fHRlKGUucHJldixlLGUubmV4dCk9PT0wKSl7aWYoYm8oZSksZT10PWUucHJldixlPT09ZS5uZXh0KWJyZWFrO2k9ITB9ZWxzZSBlPWUubmV4dDt3aGlsZShpfHxlIT09dCk7cmV0dXJuIHR9ZnVuY3Rpb24gd28obix0LGUsaSxyLHMsbyl7aWYoIW4pcmV0dXJuOyFvJiZzJiZRRShuLGkscixzKTtsZXQgYT1uLGwsYztmb3IoO24ucHJldiE9PW4ubmV4dDspe2lmKGw9bi5wcmV2LGM9bi5uZXh0LHM/V0UobixpLHIscyk6R0Uobikpe3QucHVzaChsLmkvZSksdC5wdXNoKG4uaS9lKSx0LnB1c2goYy5pL2UpLGJvKG4pLG49Yy5uZXh0LGE9Yy5uZXh0O2NvbnRpbnVlfWlmKG49YyxuPT09YSl7bz9vPT09MT8obj1xRShsaShuKSx0LGUpLHdvKG4sdCxlLGkscixzLDIpKTpvPT09MiYmWEUobix0LGUsaSxyLHMpOndvKGxpKG4pLHQsZSxpLHIscywxKTticmVha319fWZ1bmN0aW9uIEdFKG4pe2xldCB0PW4ucHJldixlPW4saT1uLm5leHQ7aWYodGUodCxlLGkpPj0wKXJldHVybiExO2xldCByPW4ubmV4dC5uZXh0O2Zvcig7ciE9PW4ucHJldjspe2lmKHpyKHQueCx0LnksZS54LGUueSxpLngsaS55LHIueCxyLnkpJiZ0ZShyLnByZXYscixyLm5leHQpPj0wKXJldHVybiExO3I9ci5uZXh0fXJldHVybiEwfWZ1bmN0aW9uIFdFKG4sdCxlLGkpe2xldCByPW4ucHJldixzPW4sbz1uLm5leHQ7aWYodGUocixzLG8pPj0wKXJldHVybiExO2xldCBhPXIueDxzLng/ci54PG8ueD9yLng6by54OnMueDxvLng/cy54Om8ueCxsPXIueTxzLnk/ci55PG8ueT9yLnk6by55OnMueTxvLnk/cy55Om8ueSxjPXIueD5zLng/ci54Pm8ueD9yLng6by54OnMueD5vLng/cy54Om8ueCx1PXIueT5zLnk/ci55Pm8ueT9yLnk6by55OnMueT5vLnk/cy55Om8ueSxoPWNoKGEsbCx0LGUsaSksZj1jaChjLHUsdCxlLGkpLGQ9bi5wcmV2WixnPW4ubmV4dFo7Zm9yKDtkJiZkLno+PWgmJmcmJmcuejw9Zjspe2lmKGQhPT1uLnByZXYmJmQhPT1uLm5leHQmJnpyKHIueCxyLnkscy54LHMueSxvLngsby55LGQueCxkLnkpJiZ0ZShkLnByZXYsZCxkLm5leHQpPj0wfHwoZD1kLnByZXZaLGchPT1uLnByZXYmJmchPT1uLm5leHQmJnpyKHIueCxyLnkscy54LHMueSxvLngsby55LGcueCxnLnkpJiZ0ZShnLnByZXYsZyxnLm5leHQpPj0wKSlyZXR1cm4hMTtnPWcubmV4dFp9Zm9yKDtkJiZkLno+PWg7KXtpZihkIT09bi5wcmV2JiZkIT09bi5uZXh0JiZ6cihyLngsci55LHMueCxzLnksby54LG8ueSxkLngsZC55KSYmdGUoZC5wcmV2LGQsZC5uZXh0KT49MClyZXR1cm4hMTtkPWQucHJldlp9Zm9yKDtnJiZnLno8PWY7KXtpZihnIT09bi5wcmV2JiZnIT09bi5uZXh0JiZ6cihyLngsci55LHMueCxzLnksby54LG8ueSxnLngsZy55KSYmdGUoZy5wcmV2LGcsZy5uZXh0KT49MClyZXR1cm4hMTtnPWcubmV4dFp9cmV0dXJuITB9ZnVuY3Rpb24gcUUobix0LGUpe2xldCBpPW47ZG97bGV0IHI9aS5wcmV2LHM9aS5uZXh0Lm5leHQ7IURsKHIscykmJkwwKHIsaSxpLm5leHQscykmJk1vKHIscykmJk1vKHMscikmJih0LnB1c2goci5pL2UpLHQucHVzaChpLmkvZSksdC5wdXNoKHMuaS9lKSxibyhpKSxibyhpLm5leHQpLGk9bj1zKSxpPWkubmV4dH13aGlsZShpIT09bik7cmV0dXJuIGxpKGkpfWZ1bmN0aW9uIFhFKG4sdCxlLGkscixzKXtsZXQgbz1uO2Rve2xldCBhPW8ubmV4dC5uZXh0O2Zvcig7YSE9PW8ucHJldjspe2lmKG8uaSE9PWEuaSYmZVQobyxhKSl7bGV0IGw9UDAobyxhKTtvPWxpKG8sby5uZXh0KSxsPWxpKGwsbC5uZXh0KSx3byhvLHQsZSxpLHIscyksd28obCx0LGUsaSxyLHMpO3JldHVybn1hPWEubmV4dH1vPW8ubmV4dH13aGlsZShvIT09bil9ZnVuY3Rpb24gWUUobix0LGUsaSl7bGV0IHI9W10scyxvLGEsbCxjO2ZvcihzPTAsbz10Lmxlbmd0aDtzPG87cysrKWE9dFtzXSppLGw9czxvLTE/dFtzKzFdKmk6bi5sZW5ndGgsYz1SMChuLGEsbCxpLCExKSxjPT09Yy5uZXh0JiYoYy5zdGVpbmVyPSEwKSxyLnB1c2godFQoYykpO2ZvcihyLnNvcnQoWkUpLHM9MDtzPHIubGVuZ3RoO3MrKylKRShyW3NdLGUpLGU9bGkoZSxlLm5leHQpO3JldHVybiBlfWZ1bmN0aW9uIFpFKG4sdCl7cmV0dXJuIG4ueC10Lnh9ZnVuY3Rpb24gSkUobix0KXtpZih0PSRFKG4sdCksdCl7bGV0IGU9UDAodCxuKTtsaSh0LHQubmV4dCksbGkoZSxlLm5leHQpfX1mdW5jdGlvbiAkRShuLHQpe2xldCBlPXQsaT1uLngscj1uLnkscz0tMS8wLG87ZG97aWYocjw9ZS55JiZyPj1lLm5leHQueSYmZS5uZXh0LnkhPT1lLnkpe2xldCBmPWUueCsoci1lLnkpKihlLm5leHQueC1lLngpLyhlLm5leHQueS1lLnkpO2lmKGY8PWkmJmY+cyl7aWYocz1mLGY9PT1pKXtpZihyPT09ZS55KXJldHVybiBlO2lmKHI9PT1lLm5leHQueSlyZXR1cm4gZS5uZXh0fW89ZS54PGUubmV4dC54P2U6ZS5uZXh0fX1lPWUubmV4dH13aGlsZShlIT09dCk7aWYoIW8pcmV0dXJuIG51bGw7aWYoaT09PXMpcmV0dXJuIG87bGV0IGE9byxsPW8ueCxjPW8ueSx1PTEvMCxoO2U9bztkbyBpPj1lLngmJmUueD49bCYmaSE9PWUueCYmenIocjxjP2k6cyxyLGwsYyxyPGM/czppLHIsZS54LGUueSkmJihoPU1hdGguYWJzKHItZS55KS8oaS1lLngpLE1vKGUsbikmJihoPHV8fGg9PT11JiYoZS54Pm8ueHx8ZS54PT09by54JiZLRShvLGUpKSkmJihvPWUsdT1oKSksZT1lLm5leHQ7d2hpbGUoZSE9PWEpO3JldHVybiBvfWZ1bmN0aW9uIEtFKG4sdCl7cmV0dXJuIHRlKG4ucHJldixuLHQucHJldik8MCYmdGUodC5uZXh0LG4sbi5uZXh0KTwwfWZ1bmN0aW9uIFFFKG4sdCxlLGkpe2xldCByPW47ZG8gci56PT09bnVsbCYmKHIuej1jaChyLngsci55LHQsZSxpKSksci5wcmV2Wj1yLnByZXYsci5uZXh0Wj1yLm5leHQscj1yLm5leHQ7d2hpbGUociE9PW4pO3IucHJldloubmV4dFo9bnVsbCxyLnByZXZaPW51bGwsakUocil9ZnVuY3Rpb24gakUobil7bGV0IHQsZSxpLHIscyxvLGEsbCxjPTE7ZG97Zm9yKGU9bixuPW51bGwscz1udWxsLG89MDtlOyl7Zm9yKG8rKyxpPWUsYT0wLHQ9MDt0PGMmJihhKyssaT1pLm5leHRaLCEhaSk7dCsrKTtmb3IobD1jO2E+MHx8bD4wJiZpOylhIT09MCYmKGw9PT0wfHwhaXx8ZS56PD1pLnopPyhyPWUsZT1lLm5leHRaLGEtLSk6KHI9aSxpPWkubmV4dFosbC0tKSxzP3MubmV4dFo9cjpuPXIsci5wcmV2Wj1zLHM9cjtlPWl9cy5uZXh0Wj1udWxsLGMqPTJ9d2hpbGUobz4xKTtyZXR1cm4gbn1mdW5jdGlvbiBjaChuLHQsZSxpLHIpe3JldHVybiBuPTMyNzY3KihuLWUpKnIsdD0zMjc2NyoodC1pKSpyLG49KG58bjw8OCkmMTY3MTE5MzUsbj0obnxuPDw0KSYyNTI2NDUxMzUsbj0obnxuPDwyKSY4NTg5OTM0NTksbj0obnxuPDwxKSYxNDMxNjU1NzY1LHQ9KHR8dDw8OCkmMTY3MTE5MzUsdD0odHx0PDw0KSYyNTI2NDUxMzUsdD0odHx0PDwyKSY4NTg5OTM0NTksdD0odHx0PDwxKSYxNDMxNjU1NzY1LG58dDw8MX1mdW5jdGlvbiB0VChuKXtsZXQgdD1uLGU9bjtkbyh0Lng8ZS54fHx0Lng9PT1lLngmJnQueTxlLnkpJiYoZT10KSx0PXQubmV4dDt3aGlsZSh0IT09bik7cmV0dXJuIGV9ZnVuY3Rpb24genIobix0LGUsaSxyLHMsbyxhKXtyZXR1cm4oci1vKSoodC1hKS0obi1vKSoocy1hKT49MCYmKG4tbykqKGktYSktKGUtbykqKHQtYSk+PTAmJihlLW8pKihzLWEpLShyLW8pKihpLWEpPj0wfWZ1bmN0aW9uIGVUKG4sdCl7cmV0dXJuIG4ubmV4dC5pIT09dC5pJiZuLnByZXYuaSE9PXQuaSYmIW5UKG4sdCkmJihNbyhuLHQpJiZNbyh0LG4pJiZpVChuLHQpJiYodGUobi5wcmV2LG4sdC5wcmV2KXx8dGUobix0LnByZXYsdCkpfHxEbChuLHQpJiZ0ZShuLnByZXYsbixuLm5leHQpPjAmJnRlKHQucHJldix0LHQubmV4dCk+MCl9ZnVuY3Rpb24gdGUobix0LGUpe3JldHVybih0Lnktbi55KSooZS54LXQueCktKHQueC1uLngpKihlLnktdC55KX1mdW5jdGlvbiBEbChuLHQpe3JldHVybiBuLng9PT10LngmJm4ueT09PXQueX1mdW5jdGlvbiBMMChuLHQsZSxpKXtsZXQgcj1aYSh0ZShuLHQsZSkpLHM9WmEodGUobix0LGkpKSxvPVphKHRlKGUsaSxuKSksYT1aYSh0ZShlLGksdCkpO3JldHVybiEhKHIhPT1zJiZvIT09YXx8cj09PTAmJllhKG4sZSx0KXx8cz09PTAmJllhKG4saSx0KXx8bz09PTAmJllhKGUsbixpKXx8YT09PTAmJllhKGUsdCxpKSl9ZnVuY3Rpb24gWWEobix0LGUpe3JldHVybiB0Lng8PU1hdGgubWF4KG4ueCxlLngpJiZ0Lng+PU1hdGgubWluKG4ueCxlLngpJiZ0Lnk8PU1hdGgubWF4KG4ueSxlLnkpJiZ0Lnk+PU1hdGgubWluKG4ueSxlLnkpfWZ1bmN0aW9uIFphKG4pe3JldHVybiBuPjA/MTpuPDA/LTE6MH1mdW5jdGlvbiBuVChuLHQpe2xldCBlPW47ZG97aWYoZS5pIT09bi5pJiZlLm5leHQuaSE9PW4uaSYmZS5pIT09dC5pJiZlLm5leHQuaSE9PXQuaSYmTDAoZSxlLm5leHQsbix0KSlyZXR1cm4hMDtlPWUubmV4dH13aGlsZShlIT09bik7cmV0dXJuITF9ZnVuY3Rpb24gTW8obix0KXtyZXR1cm4gdGUobi5wcmV2LG4sbi5uZXh0KTwwP3RlKG4sdCxuLm5leHQpPj0wJiZ0ZShuLG4ucHJldix0KT49MDp0ZShuLHQsbi5wcmV2KTwwfHx0ZShuLG4ubmV4dCx0KTwwfWZ1bmN0aW9uIGlUKG4sdCl7bGV0IGU9bixpPSExLHI9KG4ueCt0LngpLzIscz0obi55K3QueSkvMjtkbyBlLnk+cyE9ZS5uZXh0Lnk+cyYmZS5uZXh0LnkhPT1lLnkmJnI8KGUubmV4dC54LWUueCkqKHMtZS55KS8oZS5uZXh0LnktZS55KStlLngmJihpPSFpKSxlPWUubmV4dDt3aGlsZShlIT09bik7cmV0dXJuIGl9ZnVuY3Rpb24gUDAobix0KXtsZXQgZT1uZXcgdWgobi5pLG4ueCxuLnkpLGk9bmV3IHVoKHQuaSx0LngsdC55KSxyPW4ubmV4dCxzPXQucHJldjtyZXR1cm4gbi5uZXh0PXQsdC5wcmV2PW4sZS5uZXh0PXIsci5wcmV2PWUsaS5uZXh0PWUsZS5wcmV2PWkscy5uZXh0PWksaS5wcmV2PXMsaX1mdW5jdGlvbiBqZyhuLHQsZSxpKXtsZXQgcj1uZXcgdWgobix0LGUpO3JldHVybiBpPyhyLm5leHQ9aS5uZXh0LHIucHJldj1pLGkubmV4dC5wcmV2PXIsaS5uZXh0PXIpOihyLnByZXY9cixyLm5leHQ9cikscn1mdW5jdGlvbiBibyhuKXtuLm5leHQucHJldj1uLnByZXYsbi5wcmV2Lm5leHQ9bi5uZXh0LG4ucHJldlomJihuLnByZXZaLm5leHRaPW4ubmV4dFopLG4ubmV4dFomJihuLm5leHRaLnByZXZaPW4ucHJldlopfWZ1bmN0aW9uIHVoKG4sdCxlKXt0aGlzLmk9bix0aGlzLng9dCx0aGlzLnk9ZSx0aGlzLnByZXY9bnVsbCx0aGlzLm5leHQ9bnVsbCx0aGlzLno9bnVsbCx0aGlzLnByZXZaPW51bGwsdGhpcy5uZXh0Wj1udWxsLHRoaXMuc3RlaW5lcj0hMX1mdW5jdGlvbiByVChuLHQsZSxpKXtsZXQgcj0wO2ZvcihsZXQgcz10LG89ZS1pO3M8ZTtzKz1pKXIrPShuW29dLW5bc10pKihuW3MrMV0rbltvKzFdKSxvPXM7cmV0dXJuIHJ9dmFyIGRuPWNsYXNze3N0YXRpYyBhcmVhKHQpe2xldCBlPXQubGVuZ3RoLGk9MDtmb3IobGV0IHI9ZS0xLHM9MDtzPGU7cj1zKyspaSs9dFtyXS54KnRbc10ueS10W3NdLngqdFtyXS55O3JldHVybiBpKi41fXN0YXRpYyBpc0Nsb2NrV2lzZSh0KXtyZXR1cm4gZG4uYXJlYSh0KTwwfXN0YXRpYyB0cmlhbmd1bGF0ZVNoYXBlKHQsZSl7bGV0IGk9W10scj1bXSxzPVtdO3QwKHQpLGUwKGksdCk7bGV0IG89dC5sZW5ndGg7ZS5mb3JFYWNoKHQwKTtmb3IobGV0IGw9MDtsPGUubGVuZ3RoO2wrKylyLnB1c2gobyksbys9ZVtsXS5sZW5ndGgsZTAoaSxlW2xdKTtsZXQgYT1WRS50cmlhbmd1bGF0ZShpLHIpO2ZvcihsZXQgbD0wO2w8YS5sZW5ndGg7bCs9MylzLnB1c2goYS5zbGljZShsLGwrMykpO3JldHVybiBzfX07ZnVuY3Rpb24gdDAobil7bGV0IHQ9bi5sZW5ndGg7dD4yJiZuW3QtMV0uZXF1YWxzKG5bMF0pJiZuLnBvcCgpfWZ1bmN0aW9uIGUwKG4sdCl7Zm9yKGxldCBlPTA7ZTx0Lmxlbmd0aDtlKyspbi5wdXNoKHRbZV0ueCksbi5wdXNoKHRbZV0ueSl9dmFyIGNpPWNsYXNzIGV4dGVuZHMgSHR7Y29uc3RydWN0b3IodD1uZXcgVW4oW25ldyBLKC41LC41KSxuZXcgSygtLjUsLjUpLG5ldyBLKC0uNSwtLjUpLG5ldyBLKC41LC0uNSldKSxlPXt9KXtzdXBlcigpLHRoaXMudHlwZT0iRXh0cnVkZUdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3NoYXBlczp0LG9wdGlvbnM6ZX0sdD1BcnJheS5pc0FycmF5KHQpP3Q6W3RdO2xldCBpPXRoaXMscj1bXSxzPVtdO2ZvcihsZXQgYT0wLGw9dC5sZW5ndGg7YTxsO2ErKyl7bGV0IGM9dFthXTtvKGMpfXRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IGVlKHIsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJ1diIsbmV3IGVlKHMsMikpLHRoaXMuY29tcHV0ZVZlcnRleE5vcm1hbHMoKTtmdW5jdGlvbiBvKGEpe2xldCBsPVtdLGM9ZS5jdXJ2ZVNlZ21lbnRzIT09dm9pZCAwP2UuY3VydmVTZWdtZW50czoxMix1PWUuc3RlcHMhPT12b2lkIDA/ZS5zdGVwczoxLGg9ZS5kZXB0aCE9PXZvaWQgMD9lLmRlcHRoOjEsZj1lLmJldmVsRW5hYmxlZCE9PXZvaWQgMD9lLmJldmVsRW5hYmxlZDohMCxkPWUuYmV2ZWxUaGlja25lc3MhPT12b2lkIDA/ZS5iZXZlbFRoaWNrbmVzczouMixnPWUuYmV2ZWxTaXplIT09dm9pZCAwP2UuYmV2ZWxTaXplOmQtLjEseD1lLmJldmVsT2Zmc2V0IT09dm9pZCAwP2UuYmV2ZWxPZmZzZXQ6MCx2PWUuYmV2ZWxTZWdtZW50cyE9PXZvaWQgMD9lLmJldmVsU2VnbWVudHM6MyxtPWUuZXh0cnVkZVBhdGgscD1lLlVWR2VuZXJhdG9yIT09dm9pZCAwP2UuVVZHZW5lcmF0b3I6c1Q7ZS5hbW91bnQhPT12b2lkIDAmJihjb25zb2xlLndhcm4oIlRIUkVFLkV4dHJ1ZGVCdWZmZXJHZW9tZXRyeTogYW1vdW50IGhhcyBiZWVuIHJlbmFtZWQgdG8gZGVwdGguIiksaD1lLmFtb3VudCk7bGV0IGIsXz0hMSxTLEwsQSxIO20mJihiPW0uZ2V0U3BhY2VkUG9pbnRzKHUpLF89ITAsZj0hMSxTPW0uY29tcHV0ZUZyZW5ldEZyYW1lcyh1LCExKSxMPW5ldyBULEE9bmV3IFQsSD1uZXcgVCksZnx8KHY9MCxkPTAsZz0wLHg9MCk7bGV0IHR0PWEuZXh0cmFjdFBvaW50cyhjKSxYPXR0LnNoYXBlLHk9dHQuaG9sZXM7aWYoIWRuLmlzQ2xvY2tXaXNlKFgpKXtYPVgucmV2ZXJzZSgpO2ZvcihsZXQgQz0wLGo9eS5sZW5ndGg7QzxqO0MrKyl7bGV0IEo9eVtDXTtkbi5pc0Nsb2NrV2lzZShKKSYmKHlbQ109Si5yZXZlcnNlKCkpfX1sZXQgRD1kbi50cmlhbmd1bGF0ZVNoYXBlKFgseSksRj1YO2ZvcihsZXQgQz0wLGo9eS5sZW5ndGg7QzxqO0MrKyl7bGV0IEo9eVtDXTtYPVguY29uY2F0KEopfWZ1bmN0aW9uIHooQyxqLEope3JldHVybiBqfHxjb25zb2xlLmVycm9yKCJUSFJFRS5FeHRydWRlR2VvbWV0cnk6IHZlYyBkb2VzIG5vdCBleGlzdCIpLGouY2xvbmUoKS5tdWx0aXBseVNjYWxhcihKKS5hZGQoQyl9bGV0IE49WC5sZW5ndGgsVj1ELmxlbmd0aDtmdW5jdGlvbiBRKEMsaixKKXtsZXQgaXQsZXQsdnQsYnQ9Qy54LWoueCxJdD1DLnktai55LFp0PUoueC1DLngscXQ9Si55LUMueSxFPWJ0KmJ0K0l0Kkl0LHc9YnQqcXQtSXQqWnQ7aWYoTWF0aC5hYnModyk+TnVtYmVyLkVQU0lMT04pe2xldCBxPU1hdGguc3FydChFKSxydD1NYXRoLnNxcnQoWnQqWnQrcXQqcXQpLGd0PWoueC1JdC9xLFc9ai55K2J0L3EsX3Q9Si54LXF0L3J0LHl0PUoueStadC9ydCx1dD0oKF90LWd0KSpxdC0oeXQtVykqWnQpLyhidCpxdC1JdCpadCk7aXQ9Z3QrYnQqdXQtQy54LGV0PVcrSXQqdXQtQy55O2xldCBjdD1pdCppdCtldCpldDtpZihjdDw9MilyZXR1cm4gbmV3IEsoaXQsZXQpO3Z0PU1hdGguc3FydChjdC8yKX1lbHNle2xldCBxPSExO2J0Pk51bWJlci5FUFNJTE9OP1p0Pk51bWJlci5FUFNJTE9OJiYocT0hMCk6YnQ8LU51bWJlci5FUFNJTE9OP1p0PC1OdW1iZXIuRVBTSUxPTiYmKHE9ITApOk1hdGguc2lnbihJdCk9PT1NYXRoLnNpZ24ocXQpJiYocT0hMCkscT8oaXQ9LUl0LGV0PWJ0LHZ0PU1hdGguc3FydChFKSk6KGl0PWJ0LGV0PUl0LHZ0PU1hdGguc3FydChFLzIpKX1yZXR1cm4gbmV3IEsoaXQvdnQsZXQvdnQpfWxldCBhdD1bXTtmb3IobGV0IEM9MCxqPUYubGVuZ3RoLEo9ai0xLGl0PUMrMTtDPGo7QysrLEorKyxpdCsrKUo9PT1qJiYoSj0wKSxpdD09PWomJihpdD0wKSxhdFtDXT1RKEZbQ10sRltKXSxGW2l0XSk7bGV0IEc9W10sJCxsdD1hdC5jb25jYXQoKTtmb3IobGV0IEM9MCxqPXkubGVuZ3RoO0M8ajtDKyspe2xldCBKPXlbQ107JD1bXTtmb3IobGV0IGl0PTAsZXQ9Si5sZW5ndGgsdnQ9ZXQtMSxidD1pdCsxO2l0PGV0O2l0KyssdnQrKyxidCsrKXZ0PT09ZXQmJih2dD0wKSxidD09PWV0JiYoYnQ9MCksJFtpdF09UShKW2l0XSxKW3Z0XSxKW2J0XSk7Ry5wdXNoKCQpLGx0PWx0LmNvbmNhdCgkKX1mb3IobGV0IEM9MDtDPHY7QysrKXtsZXQgaj1DL3YsSj1kKk1hdGguY29zKGoqTWF0aC5QSS8yKSxpdD1nKk1hdGguc2luKGoqTWF0aC5QSS8yKSt4O2ZvcihsZXQgZXQ9MCx2dD1GLmxlbmd0aDtldDx2dDtldCsrKXtsZXQgYnQ9eihGW2V0XSxhdFtldF0saXQpO210KGJ0LngsYnQueSwtSil9Zm9yKGxldCBldD0wLHZ0PXkubGVuZ3RoO2V0PHZ0O2V0Kyspe2xldCBidD15W2V0XTskPUdbZXRdO2ZvcihsZXQgSXQ9MCxadD1idC5sZW5ndGg7SXQ8WnQ7SXQrKyl7bGV0IHF0PXooYnRbSXRdLCRbSXRdLGl0KTttdChxdC54LHF0LnksLUopfX19bGV0IGR0PWcreDtmb3IobGV0IEM9MDtDPE47QysrKXtsZXQgaj1mP3ooWFtDXSxsdFtDXSxkdCk6WFtDXTtfPyhBLmNvcHkoUy5ub3JtYWxzWzBdKS5tdWx0aXBseVNjYWxhcihqLngpLEwuY29weShTLmJpbm9ybWFsc1swXSkubXVsdGlwbHlTY2FsYXIoai55KSxILmNvcHkoYlswXSkuYWRkKEEpLmFkZChMKSxtdChILngsSC55LEgueikpOm10KGoueCxqLnksMCl9Zm9yKGxldCBDPTE7Qzw9dTtDKyspZm9yKGxldCBqPTA7ajxOO2orKyl7bGV0IEo9Zj96KFhbal0sbHRbal0sZHQpOlhbal07Xz8oQS5jb3B5KFMubm9ybWFsc1tDXSkubXVsdGlwbHlTY2FsYXIoSi54KSxMLmNvcHkoUy5iaW5vcm1hbHNbQ10pLm11bHRpcGx5U2NhbGFyKEoueSksSC5jb3B5KGJbQ10pLmFkZChBKS5hZGQoTCksbXQoSC54LEgueSxILnopKTptdChKLngsSi55LGgvdSpDKX1mb3IobGV0IEM9di0xO0M+PTA7Qy0tKXtsZXQgaj1DL3YsSj1kKk1hdGguY29zKGoqTWF0aC5QSS8yKSxpdD1nKk1hdGguc2luKGoqTWF0aC5QSS8yKSt4O2ZvcihsZXQgZXQ9MCx2dD1GLmxlbmd0aDtldDx2dDtldCsrKXtsZXQgYnQ9eihGW2V0XSxhdFtldF0saXQpO210KGJ0LngsYnQueSxoK0opfWZvcihsZXQgZXQ9MCx2dD15Lmxlbmd0aDtldDx2dDtldCsrKXtsZXQgYnQ9eVtldF07JD1HW2V0XTtmb3IobGV0IEl0PTAsWnQ9YnQubGVuZ3RoO0l0PFp0O0l0Kyspe2xldCBxdD16KGJ0W0l0XSwkW0l0XSxpdCk7Xz9tdChxdC54LHF0LnkrYlt1LTFdLnksYlt1LTFdLngrSik6bXQocXQueCxxdC55LGgrSil9fX14dCgpLGsoKTtmdW5jdGlvbiB4dCgpe2xldCBDPXIubGVuZ3RoLzM7aWYoZil7bGV0IGo9MCxKPU4qajtmb3IobGV0IGl0PTA7aXQ8VjtpdCsrKXtsZXQgZXQ9RFtpdF07U3QoZXRbMl0rSixldFsxXStKLGV0WzBdK0opfWo9dSt2KjIsSj1OKmo7Zm9yKGxldCBpdD0wO2l0PFY7aXQrKyl7bGV0IGV0PURbaXRdO1N0KGV0WzBdK0osZXRbMV0rSixldFsyXStKKX19ZWxzZXtmb3IobGV0IGo9MDtqPFY7aisrKXtsZXQgSj1EW2pdO1N0KEpbMl0sSlsxXSxKWzBdKX1mb3IobGV0IGo9MDtqPFY7aisrKXtsZXQgSj1EW2pdO1N0KEpbMF0rTip1LEpbMV0rTip1LEpbMl0rTip1KX19aS5hZGRHcm91cChDLHIubGVuZ3RoLzMtQywwKX1mdW5jdGlvbiBrKCl7bGV0IEM9ci5sZW5ndGgvMyxqPTA7RnQoRixqKSxqKz1GLmxlbmd0aDtmb3IobGV0IEo9MCxpdD15Lmxlbmd0aDtKPGl0O0orKyl7bGV0IGV0PXlbSl07RnQoZXQsaiksais9ZXQubGVuZ3RofWkuYWRkR3JvdXAoQyxyLmxlbmd0aC8zLUMsMSl9ZnVuY3Rpb24gRnQoQyxqKXtsZXQgSj1DLmxlbmd0aDtmb3IoOy0tSj49MDspe2xldCBpdD1KLGV0PUotMTtldDwwJiYoZXQ9Qy5sZW5ndGgtMSk7Zm9yKGxldCB2dD0wLGJ0PXUrdioyO3Z0PGJ0O3Z0Kyspe2xldCBJdD1OKnZ0LFp0PU4qKHZ0KzEpLHF0PWoraXQrSXQsRT1qK2V0K0l0LHc9aitldCtadCxxPWoraXQrWnQ7QihxdCxFLHcscSl9fX1mdW5jdGlvbiBtdChDLGosSil7bC5wdXNoKEMpLGwucHVzaChqKSxsLnB1c2goSil9ZnVuY3Rpb24gU3QoQyxqLEope3N0KEMpLHN0KGopLHN0KEopO2xldCBpdD1yLmxlbmd0aC8zLGV0PXAuZ2VuZXJhdGVUb3BVVihpLHIsaXQtMyxpdC0yLGl0LTEpO250KGV0WzBdKSxudChldFsxXSksbnQoZXRbMl0pfWZ1bmN0aW9uIEIoQyxqLEosaXQpe3N0KEMpLHN0KGopLHN0KGl0KSxzdChqKSxzdChKKSxzdChpdCk7bGV0IGV0PXIubGVuZ3RoLzMsdnQ9cC5nZW5lcmF0ZVNpZGVXYWxsVVYoaSxyLGV0LTYsZXQtMyxldC0yLGV0LTEpO250KHZ0WzBdKSxudCh2dFsxXSksbnQodnRbM10pLG50KHZ0WzFdKSxudCh2dFsyXSksbnQodnRbM10pfWZ1bmN0aW9uIHN0KEMpe3IucHVzaChsW0MqMyswXSksci5wdXNoKGxbQyozKzFdKSxyLnB1c2gobFtDKjMrMl0pfWZ1bmN0aW9uIG50KEMpe3MucHVzaChDLngpLHMucHVzaChDLnkpfX19dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCksZT10aGlzLnBhcmFtZXRlcnMuc2hhcGVzLGk9dGhpcy5wYXJhbWV0ZXJzLm9wdGlvbnM7cmV0dXJuIG9UKGUsaSx0KX1zdGF0aWMgZnJvbUpTT04odCxlKXtsZXQgaT1bXTtmb3IobGV0IHM9MCxvPXQuc2hhcGVzLmxlbmd0aDtzPG87cysrKXtsZXQgYT1lW3Quc2hhcGVzW3NdXTtpLnB1c2goYSl9bGV0IHI9dC5vcHRpb25zLmV4dHJ1ZGVQYXRoO3JldHVybiByIT09dm9pZCAwJiYodC5vcHRpb25zLmV4dHJ1ZGVQYXRoPW5ldyBDMFtyLnR5cGVdKCkuZnJvbUpTT04ocikpLG5ldyBjaShpLHQub3B0aW9ucyl9fSxzVD17Z2VuZXJhdGVUb3BVVjpmdW5jdGlvbihuLHQsZSxpLHIpe2xldCBzPXRbZSozXSxvPXRbZSozKzFdLGE9dFtpKjNdLGw9dFtpKjMrMV0sYz10W3IqM10sdT10W3IqMysxXTtyZXR1cm5bbmV3IEsocyxvKSxuZXcgSyhhLGwpLG5ldyBLKGMsdSldfSxnZW5lcmF0ZVNpZGVXYWxsVVY6ZnVuY3Rpb24obix0LGUsaSxyLHMpe2xldCBvPXRbZSozXSxhPXRbZSozKzFdLGw9dFtlKjMrMl0sYz10W2kqM10sdT10W2kqMysxXSxoPXRbaSozKzJdLGY9dFtyKjNdLGQ9dFtyKjMrMV0sZz10W3IqMysyXSx4PXRbcyozXSx2PXRbcyozKzFdLG09dFtzKjMrMl07cmV0dXJuIE1hdGguYWJzKGEtdSk8TWF0aC5hYnMoby1jKT9bbmV3IEsobywxLWwpLG5ldyBLKGMsMS1oKSxuZXcgSyhmLDEtZyksbmV3IEsoeCwxLW0pXTpbbmV3IEsoYSwxLWwpLG5ldyBLKHUsMS1oKSxuZXcgSyhkLDEtZyksbmV3IEsodiwxLW0pXX19O2Z1bmN0aW9uIG9UKG4sdCxlKXtpZihlLnNoYXBlcz1bXSxBcnJheS5pc0FycmF5KG4pKWZvcihsZXQgaT0wLHI9bi5sZW5ndGg7aTxyO2krKyl7bGV0IHM9bltpXTtlLnNoYXBlcy5wdXNoKHMudXVpZCl9ZWxzZSBlLnNoYXBlcy5wdXNoKG4udXVpZCk7cmV0dXJuIHQuZXh0cnVkZVBhdGghPT12b2lkIDAmJihlLm9wdGlvbnMuZXh0cnVkZVBhdGg9dC5leHRydWRlUGF0aC50b0pTT04oKSksZX12YXIgcWk9Y2xhc3MgZXh0ZW5kcyBIdHtjb25zdHJ1Y3Rvcih0PW5ldyBVbihbbmV3IEsoMCwuNSksbmV3IEsoLS41LC0uNSksbmV3IEsoLjUsLS41KV0pLGU9MTIpe3N1cGVyKCksdGhpcy50eXBlPSJTaGFwZUdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3NoYXBlczp0LGN1cnZlU2VnbWVudHM6ZX07bGV0IGk9W10scj1bXSxzPVtdLG89W10sYT0wLGw9MDtpZihBcnJheS5pc0FycmF5KHQpPT09ITEpYyh0KTtlbHNlIGZvcihsZXQgdT0wO3U8dC5sZW5ndGg7dSsrKWModFt1XSksdGhpcy5hZGRHcm91cChhLGwsdSksYSs9bCxsPTA7dGhpcy5zZXRJbmRleChpKSx0aGlzLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyBlZShyLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgibm9ybWFsIixuZXcgZWUocywzKSksdGhpcy5zZXRBdHRyaWJ1dGUoInV2IixuZXcgZWUobywyKSk7ZnVuY3Rpb24gYyh1KXtsZXQgaD1yLmxlbmd0aC8zLGY9dS5leHRyYWN0UG9pbnRzKGUpLGQ9Zi5zaGFwZSxnPWYuaG9sZXM7ZG4uaXNDbG9ja1dpc2UoZCk9PT0hMSYmKGQ9ZC5yZXZlcnNlKCkpO2ZvcihsZXQgdj0wLG09Zy5sZW5ndGg7djxtO3YrKyl7bGV0IHA9Z1t2XTtkbi5pc0Nsb2NrV2lzZShwKT09PSEwJiYoZ1t2XT1wLnJldmVyc2UoKSl9bGV0IHg9ZG4udHJpYW5ndWxhdGVTaGFwZShkLGcpO2ZvcihsZXQgdj0wLG09Zy5sZW5ndGg7djxtO3YrKyl7bGV0IHA9Z1t2XTtkPWQuY29uY2F0KHApfWZvcihsZXQgdj0wLG09ZC5sZW5ndGg7djxtO3YrKyl7bGV0IHA9ZFt2XTtyLnB1c2gocC54LHAueSwwKSxzLnB1c2goMCwwLDEpLG8ucHVzaChwLngscC55KX1mb3IobGV0IHY9MCxtPXgubGVuZ3RoO3Y8bTt2Kyspe2xldCBwPXhbdl0sYj1wWzBdK2gsXz1wWzFdK2gsUz1wWzJdK2g7aS5wdXNoKGIsXyxTKSxsKz0zfX19dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCksZT10aGlzLnBhcmFtZXRlcnMuc2hhcGVzO3JldHVybiBhVChlLHQpfXN0YXRpYyBmcm9tSlNPTih0LGUpe2xldCBpPVtdO2ZvcihsZXQgcj0wLHM9dC5zaGFwZXMubGVuZ3RoO3I8cztyKyspe2xldCBvPWVbdC5zaGFwZXNbcl1dO2kucHVzaChvKX1yZXR1cm4gbmV3IHFpKGksdC5jdXJ2ZVNlZ21lbnRzKX19O2Z1bmN0aW9uIGFUKG4sdCl7aWYodC5zaGFwZXM9W10sQXJyYXkuaXNBcnJheShuKSlmb3IobGV0IGU9MCxpPW4ubGVuZ3RoO2U8aTtlKyspe2xldCByPW5bZV07dC5zaGFwZXMucHVzaChyLnV1aWQpfWVsc2UgdC5zaGFwZXMucHVzaChuLnV1aWQpO3JldHVybiB0fXZhciBoaD1jbGFzcyBleHRlbmRzIHhle2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJTaGFkb3dNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgZnQoMCksdGhpcy50cmFuc3BhcmVudD0hMCx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuY29sb3IuY29weSh0LmNvbG9yKSx0aGlzfX07aGgucHJvdG90eXBlLmlzU2hhZG93TWF0ZXJpYWw9ITA7dmFyIHZsPWNsYXNzIGV4dGVuZHMgeGV7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLmRlZmluZXM9e1NUQU5EQVJEOiIifSx0aGlzLnR5cGU9Ik1lc2hTdGFuZGFyZE1hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyBmdCgxNjc3NzIxNSksdGhpcy5yb3VnaG5lc3M9MSx0aGlzLm1ldGFsbmVzcz0wLHRoaXMubWFwPW51bGwsdGhpcy5saWdodE1hcD1udWxsLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9MSx0aGlzLmFvTWFwPW51bGwsdGhpcy5hb01hcEludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmU9bmV3IGZ0KDApLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9MSx0aGlzLmVtaXNzaXZlTWFwPW51bGwsdGhpcy5idW1wTWFwPW51bGwsdGhpcy5idW1wU2NhbGU9MSx0aGlzLm5vcm1hbE1hcD1udWxsLHRoaXMubm9ybWFsTWFwVHlwZT10cyx0aGlzLm5vcm1hbFNjYWxlPW5ldyBLKDEsMSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9bnVsbCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPTEsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPTAsdGhpcy5yb3VnaG5lc3NNYXA9bnVsbCx0aGlzLm1ldGFsbmVzc01hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLmVudk1hcD1udWxsLHRoaXMuZW52TWFwSW50ZW5zaXR5PTEsdGhpcy5yZWZyYWN0aW9uUmF0aW89Ljk4LHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy53aXJlZnJhbWVMaW5lY2FwPSJyb3VuZCIsdGhpcy53aXJlZnJhbWVMaW5lam9pbj0icm91bmQiLHRoaXMuZmxhdFNoYWRpbmc9ITEsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmRlZmluZXM9e1NUQU5EQVJEOiIifSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5yb3VnaG5lc3M9dC5yb3VnaG5lc3MsdGhpcy5tZXRhbG5lc3M9dC5tZXRhbG5lc3MsdGhpcy5tYXA9dC5tYXAsdGhpcy5saWdodE1hcD10LmxpZ2h0TWFwLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9dC5saWdodE1hcEludGVuc2l0eSx0aGlzLmFvTWFwPXQuYW9NYXAsdGhpcy5hb01hcEludGVuc2l0eT10LmFvTWFwSW50ZW5zaXR5LHRoaXMuZW1pc3NpdmUuY29weSh0LmVtaXNzaXZlKSx0aGlzLmVtaXNzaXZlTWFwPXQuZW1pc3NpdmVNYXAsdGhpcy5lbWlzc2l2ZUludGVuc2l0eT10LmVtaXNzaXZlSW50ZW5zaXR5LHRoaXMuYnVtcE1hcD10LmJ1bXBNYXAsdGhpcy5idW1wU2NhbGU9dC5idW1wU2NhbGUsdGhpcy5ub3JtYWxNYXA9dC5ub3JtYWxNYXAsdGhpcy5ub3JtYWxNYXBUeXBlPXQubm9ybWFsTWFwVHlwZSx0aGlzLm5vcm1hbFNjYWxlLmNvcHkodC5ub3JtYWxTY2FsZSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9dC5kaXNwbGFjZW1lbnRNYXAsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT10LmRpc3BsYWNlbWVudFNjYWxlLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz10LmRpc3BsYWNlbWVudEJpYXMsdGhpcy5yb3VnaG5lc3NNYXA9dC5yb3VnaG5lc3NNYXAsdGhpcy5tZXRhbG5lc3NNYXA9dC5tZXRhbG5lc3NNYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZW52TWFwPXQuZW52TWFwLHRoaXMuZW52TWFwSW50ZW5zaXR5PXQuZW52TWFwSW50ZW5zaXR5LHRoaXMucmVmcmFjdGlvblJhdGlvPXQucmVmcmFjdGlvblJhdGlvLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXMud2lyZWZyYW1lTGluZWNhcD10LndpcmVmcmFtZUxpbmVjYXAsdGhpcy53aXJlZnJhbWVMaW5lam9pbj10LndpcmVmcmFtZUxpbmVqb2luLHRoaXMuZmxhdFNoYWRpbmc9dC5mbGF0U2hhZGluZyx0aGlzfX07dmwucHJvdG90eXBlLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWw9ITA7dmFyIGZoPWNsYXNzIGV4dGVuZHMgdmx7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLmRlZmluZXM9e1NUQU5EQVJEOiIiLFBIWVNJQ0FMOiIifSx0aGlzLnR5cGU9Ik1lc2hQaHlzaWNhbE1hdGVyaWFsIix0aGlzLmNsZWFyY29hdE1hcD1udWxsLHRoaXMuY2xlYXJjb2F0Um91Z2huZXNzPTAsdGhpcy5jbGVhcmNvYXRSb3VnaG5lc3NNYXA9bnVsbCx0aGlzLmNsZWFyY29hdE5vcm1hbFNjYWxlPW5ldyBLKDEsMSksdGhpcy5jbGVhcmNvYXROb3JtYWxNYXA9bnVsbCx0aGlzLmlvcj0xLjUsT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsInJlZmxlY3Rpdml0eSIse2dldDpmdW5jdGlvbigpe3JldHVybiBJZSgyLjUqKHRoaXMuaW9yLTEpLyh0aGlzLmlvcisxKSwwLDEpfSxzZXQ6ZnVuY3Rpb24oZSl7dGhpcy5pb3I9KDErLjQqZSkvKDEtLjQqZSl9fSksdGhpcy5zaGVlbkNvbG9yPW5ldyBmdCgwKSx0aGlzLnNoZWVuQ29sb3JNYXA9bnVsbCx0aGlzLnNoZWVuUm91Z2huZXNzPTEsdGhpcy5zaGVlblJvdWdobmVzc01hcD1udWxsLHRoaXMudHJhbnNtaXNzaW9uTWFwPW51bGwsdGhpcy50aGlja25lc3M9MCx0aGlzLnRoaWNrbmVzc01hcD1udWxsLHRoaXMuYXR0ZW51YXRpb25EaXN0YW5jZT0wLHRoaXMuYXR0ZW51YXRpb25Db2xvcj1uZXcgZnQoMSwxLDEpLHRoaXMuc3BlY3VsYXJJbnRlbnNpdHk9MSx0aGlzLnNwZWN1bGFySW50ZW5zaXR5TWFwPW51bGwsdGhpcy5zcGVjdWxhckNvbG9yPW5ldyBmdCgxLDEsMSksdGhpcy5zcGVjdWxhckNvbG9yTWFwPW51bGwsdGhpcy5fc2hlZW49MCx0aGlzLl9jbGVhcmNvYXQ9MCx0aGlzLl90cmFuc21pc3Npb249MCx0aGlzLnNldFZhbHVlcyh0KX1nZXQgc2hlZW4oKXtyZXR1cm4gdGhpcy5fc2hlZW59c2V0IHNoZWVuKHQpe3RoaXMuX3NoZWVuPjAhPXQ+MCYmdGhpcy52ZXJzaW9uKyssdGhpcy5fc2hlZW49dH1nZXQgY2xlYXJjb2F0KCl7cmV0dXJuIHRoaXMuX2NsZWFyY29hdH1zZXQgY2xlYXJjb2F0KHQpe3RoaXMuX2NsZWFyY29hdD4wIT10PjAmJnRoaXMudmVyc2lvbisrLHRoaXMuX2NsZWFyY29hdD10fWdldCB0cmFuc21pc3Npb24oKXtyZXR1cm4gdGhpcy5fdHJhbnNtaXNzaW9ufXNldCB0cmFuc21pc3Npb24odCl7dGhpcy5fdHJhbnNtaXNzaW9uPjAhPXQ+MCYmdGhpcy52ZXJzaW9uKyssdGhpcy5fdHJhbnNtaXNzaW9uPXR9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmRlZmluZXM9e1NUQU5EQVJEOiIiLFBIWVNJQ0FMOiIifSx0aGlzLmNsZWFyY29hdD10LmNsZWFyY29hdCx0aGlzLmNsZWFyY29hdE1hcD10LmNsZWFyY29hdE1hcCx0aGlzLmNsZWFyY29hdFJvdWdobmVzcz10LmNsZWFyY29hdFJvdWdobmVzcyx0aGlzLmNsZWFyY29hdFJvdWdobmVzc01hcD10LmNsZWFyY29hdFJvdWdobmVzc01hcCx0aGlzLmNsZWFyY29hdE5vcm1hbE1hcD10LmNsZWFyY29hdE5vcm1hbE1hcCx0aGlzLmNsZWFyY29hdE5vcm1hbFNjYWxlLmNvcHkodC5jbGVhcmNvYXROb3JtYWxTY2FsZSksdGhpcy5pb3I9dC5pb3IsdGhpcy5zaGVlbj10LnNoZWVuLHRoaXMuc2hlZW5Db2xvci5jb3B5KHQuc2hlZW5Db2xvciksdGhpcy5zaGVlbkNvbG9yTWFwPXQuc2hlZW5Db2xvck1hcCx0aGlzLnNoZWVuUm91Z2huZXNzPXQuc2hlZW5Sb3VnaG5lc3MsdGhpcy5zaGVlblJvdWdobmVzc01hcD10LnNoZWVuUm91Z2huZXNzTWFwLHRoaXMudHJhbnNtaXNzaW9uPXQudHJhbnNtaXNzaW9uLHRoaXMudHJhbnNtaXNzaW9uTWFwPXQudHJhbnNtaXNzaW9uTWFwLHRoaXMudGhpY2tuZXNzPXQudGhpY2tuZXNzLHRoaXMudGhpY2tuZXNzTWFwPXQudGhpY2tuZXNzTWFwLHRoaXMuYXR0ZW51YXRpb25EaXN0YW5jZT10LmF0dGVudWF0aW9uRGlzdGFuY2UsdGhpcy5hdHRlbnVhdGlvbkNvbG9yLmNvcHkodC5hdHRlbnVhdGlvbkNvbG9yKSx0aGlzLnNwZWN1bGFySW50ZW5zaXR5PXQuc3BlY3VsYXJJbnRlbnNpdHksdGhpcy5zcGVjdWxhckludGVuc2l0eU1hcD10LnNwZWN1bGFySW50ZW5zaXR5TWFwLHRoaXMuc3BlY3VsYXJDb2xvci5jb3B5KHQuc3BlY3VsYXJDb2xvciksdGhpcy5zcGVjdWxhckNvbG9yTWFwPXQuc3BlY3VsYXJDb2xvck1hcCx0aGlzfX07ZmgucHJvdG90eXBlLmlzTWVzaFBoeXNpY2FsTWF0ZXJpYWw9ITA7dmFyIGRoPWNsYXNzIGV4dGVuZHMgeGV7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9Ik1lc2hQaG9uZ01hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyBmdCgxNjc3NzIxNSksdGhpcy5zcGVjdWxhcj1uZXcgZnQoMTExODQ4MSksdGhpcy5zaGluaW5lc3M9MzAsdGhpcy5tYXA9bnVsbCx0aGlzLmxpZ2h0TWFwPW51bGwsdGhpcy5saWdodE1hcEludGVuc2l0eT0xLHRoaXMuYW9NYXA9bnVsbCx0aGlzLmFvTWFwSW50ZW5zaXR5PTEsdGhpcy5lbWlzc2l2ZT1uZXcgZnQoMCksdGhpcy5lbWlzc2l2ZUludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmVNYXA9bnVsbCx0aGlzLmJ1bXBNYXA9bnVsbCx0aGlzLmJ1bXBTY2FsZT0xLHRoaXMubm9ybWFsTWFwPW51bGwsdGhpcy5ub3JtYWxNYXBUeXBlPXRzLHRoaXMubm9ybWFsU2NhbGU9bmV3IEsoMSwxKSx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLnNwZWN1bGFyTWFwPW51bGwsdGhpcy5hbHBoYU1hcD1udWxsLHRoaXMuZW52TWFwPW51bGwsdGhpcy5jb21iaW5lPUNsLHRoaXMucmVmbGVjdGl2aXR5PTEsdGhpcy5yZWZyYWN0aW9uUmF0aW89Ljk4LHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy53aXJlZnJhbWVMaW5lY2FwPSJyb3VuZCIsdGhpcy53aXJlZnJhbWVMaW5lam9pbj0icm91bmQiLHRoaXMuZmxhdFNoYWRpbmc9ITEsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5zcGVjdWxhci5jb3B5KHQuc3BlY3VsYXIpLHRoaXMuc2hpbmluZXNzPXQuc2hpbmluZXNzLHRoaXMubWFwPXQubWFwLHRoaXMubGlnaHRNYXA9dC5saWdodE1hcCx0aGlzLmxpZ2h0TWFwSW50ZW5zaXR5PXQubGlnaHRNYXBJbnRlbnNpdHksdGhpcy5hb01hcD10LmFvTWFwLHRoaXMuYW9NYXBJbnRlbnNpdHk9dC5hb01hcEludGVuc2l0eSx0aGlzLmVtaXNzaXZlLmNvcHkodC5lbWlzc2l2ZSksdGhpcy5lbWlzc2l2ZU1hcD10LmVtaXNzaXZlTWFwLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9dC5lbWlzc2l2ZUludGVuc2l0eSx0aGlzLmJ1bXBNYXA9dC5idW1wTWFwLHRoaXMuYnVtcFNjYWxlPXQuYnVtcFNjYWxlLHRoaXMubm9ybWFsTWFwPXQubm9ybWFsTWFwLHRoaXMubm9ybWFsTWFwVHlwZT10Lm5vcm1hbE1hcFR5cGUsdGhpcy5ub3JtYWxTY2FsZS5jb3B5KHQubm9ybWFsU2NhbGUpLHRoaXMuZGlzcGxhY2VtZW50TWFwPXQuZGlzcGxhY2VtZW50TWFwLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9dC5kaXNwbGFjZW1lbnRTY2FsZSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9dC5kaXNwbGFjZW1lbnRCaWFzLHRoaXMuc3BlY3VsYXJNYXA9dC5zcGVjdWxhck1hcCx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5lbnZNYXA9dC5lbnZNYXAsdGhpcy5jb21iaW5lPXQuY29tYmluZSx0aGlzLnJlZmxlY3Rpdml0eT10LnJlZmxlY3Rpdml0eSx0aGlzLnJlZnJhY3Rpb25SYXRpbz10LnJlZnJhY3Rpb25SYXRpbyx0aGlzLndpcmVmcmFtZT10LndpcmVmcmFtZSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD10LndpcmVmcmFtZUxpbmV3aWR0aCx0aGlzLndpcmVmcmFtZUxpbmVjYXA9dC53aXJlZnJhbWVMaW5lY2FwLHRoaXMud2lyZWZyYW1lTGluZWpvaW49dC53aXJlZnJhbWVMaW5lam9pbix0aGlzLmZsYXRTaGFkaW5nPXQuZmxhdFNoYWRpbmcsdGhpc319O2RoLnByb3RvdHlwZS5pc01lc2hQaG9uZ01hdGVyaWFsPSEwO3ZhciBwaD1jbGFzcyBleHRlbmRzIHhle2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5kZWZpbmVzPXtUT09OOiIifSx0aGlzLnR5cGU9Ik1lc2hUb29uTWF0ZXJpYWwiLHRoaXMuY29sb3I9bmV3IGZ0KDE2Nzc3MjE1KSx0aGlzLm1hcD1udWxsLHRoaXMuZ3JhZGllbnRNYXA9bnVsbCx0aGlzLmxpZ2h0TWFwPW51bGwsdGhpcy5saWdodE1hcEludGVuc2l0eT0xLHRoaXMuYW9NYXA9bnVsbCx0aGlzLmFvTWFwSW50ZW5zaXR5PTEsdGhpcy5lbWlzc2l2ZT1uZXcgZnQoMCksdGhpcy5lbWlzc2l2ZUludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmVNYXA9bnVsbCx0aGlzLmJ1bXBNYXA9bnVsbCx0aGlzLmJ1bXBTY2FsZT0xLHRoaXMubm9ybWFsTWFwPW51bGwsdGhpcy5ub3JtYWxNYXBUeXBlPXRzLHRoaXMubm9ybWFsU2NhbGU9bmV3IEsoMSwxKSx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy53aXJlZnJhbWU9ITEsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9MSx0aGlzLndpcmVmcmFtZUxpbmVjYXA9InJvdW5kIix0aGlzLndpcmVmcmFtZUxpbmVqb2luPSJyb3VuZCIsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5tYXA9dC5tYXAsdGhpcy5ncmFkaWVudE1hcD10LmdyYWRpZW50TWFwLHRoaXMubGlnaHRNYXA9dC5saWdodE1hcCx0aGlzLmxpZ2h0TWFwSW50ZW5zaXR5PXQubGlnaHRNYXBJbnRlbnNpdHksdGhpcy5hb01hcD10LmFvTWFwLHRoaXMuYW9NYXBJbnRlbnNpdHk9dC5hb01hcEludGVuc2l0eSx0aGlzLmVtaXNzaXZlLmNvcHkodC5lbWlzc2l2ZSksdGhpcy5lbWlzc2l2ZU1hcD10LmVtaXNzaXZlTWFwLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9dC5lbWlzc2l2ZUludGVuc2l0eSx0aGlzLmJ1bXBNYXA9dC5idW1wTWFwLHRoaXMuYnVtcFNjYWxlPXQuYnVtcFNjYWxlLHRoaXMubm9ybWFsTWFwPXQubm9ybWFsTWFwLHRoaXMubm9ybWFsTWFwVHlwZT10Lm5vcm1hbE1hcFR5cGUsdGhpcy5ub3JtYWxTY2FsZS5jb3B5KHQubm9ybWFsU2NhbGUpLHRoaXMuZGlzcGxhY2VtZW50TWFwPXQuZGlzcGxhY2VtZW50TWFwLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9dC5kaXNwbGFjZW1lbnRTY2FsZSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9dC5kaXNwbGFjZW1lbnRCaWFzLHRoaXMuYWxwaGFNYXA9dC5hbHBoYU1hcCx0aGlzLndpcmVmcmFtZT10LndpcmVmcmFtZSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD10LndpcmVmcmFtZUxpbmV3aWR0aCx0aGlzLndpcmVmcmFtZUxpbmVjYXA9dC53aXJlZnJhbWVMaW5lY2FwLHRoaXMud2lyZWZyYW1lTGluZWpvaW49dC53aXJlZnJhbWVMaW5lam9pbix0aGlzfX07cGgucHJvdG90eXBlLmlzTWVzaFRvb25NYXRlcmlhbD0hMDt2YXIgbWg9Y2xhc3MgZXh0ZW5kcyB4ZXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaE5vcm1hbE1hdGVyaWFsIix0aGlzLmJ1bXBNYXA9bnVsbCx0aGlzLmJ1bXBTY2FsZT0xLHRoaXMubm9ybWFsTWFwPW51bGwsdGhpcy5ub3JtYWxNYXBUeXBlPXRzLHRoaXMubm9ybWFsU2NhbGU9bmV3IEsoMSwxKSx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLndpcmVmcmFtZT0hMSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD0xLHRoaXMuZm9nPSExLHRoaXMuZmxhdFNoYWRpbmc9ITEsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmJ1bXBNYXA9dC5idW1wTWFwLHRoaXMuYnVtcFNjYWxlPXQuYnVtcFNjYWxlLHRoaXMubm9ybWFsTWFwPXQubm9ybWFsTWFwLHRoaXMubm9ybWFsTWFwVHlwZT10Lm5vcm1hbE1hcFR5cGUsdGhpcy5ub3JtYWxTY2FsZS5jb3B5KHQubm9ybWFsU2NhbGUpLHRoaXMuZGlzcGxhY2VtZW50TWFwPXQuZGlzcGxhY2VtZW50TWFwLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9dC5kaXNwbGFjZW1lbnRTY2FsZSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9dC5kaXNwbGFjZW1lbnRCaWFzLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXMuZmxhdFNoYWRpbmc9dC5mbGF0U2hhZGluZyx0aGlzfX07bWgucHJvdG90eXBlLmlzTWVzaE5vcm1hbE1hdGVyaWFsPSEwO3ZhciBnaD1jbGFzcyBleHRlbmRzIHhle2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJNZXNoTGFtYmVydE1hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyBmdCgxNjc3NzIxNSksdGhpcy5tYXA9bnVsbCx0aGlzLmxpZ2h0TWFwPW51bGwsdGhpcy5saWdodE1hcEludGVuc2l0eT0xLHRoaXMuYW9NYXA9bnVsbCx0aGlzLmFvTWFwSW50ZW5zaXR5PTEsdGhpcy5lbWlzc2l2ZT1uZXcgZnQoMCksdGhpcy5lbWlzc2l2ZUludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmVNYXA9bnVsbCx0aGlzLnNwZWN1bGFyTWFwPW51bGwsdGhpcy5hbHBoYU1hcD1udWxsLHRoaXMuZW52TWFwPW51bGwsdGhpcy5jb21iaW5lPUNsLHRoaXMucmVmbGVjdGl2aXR5PTEsdGhpcy5yZWZyYWN0aW9uUmF0aW89Ljk4LHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy53aXJlZnJhbWVMaW5lY2FwPSJyb3VuZCIsdGhpcy53aXJlZnJhbWVMaW5lam9pbj0icm91bmQiLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMubWFwPXQubWFwLHRoaXMubGlnaHRNYXA9dC5saWdodE1hcCx0aGlzLmxpZ2h0TWFwSW50ZW5zaXR5PXQubGlnaHRNYXBJbnRlbnNpdHksdGhpcy5hb01hcD10LmFvTWFwLHRoaXMuYW9NYXBJbnRlbnNpdHk9dC5hb01hcEludGVuc2l0eSx0aGlzLmVtaXNzaXZlLmNvcHkodC5lbWlzc2l2ZSksdGhpcy5lbWlzc2l2ZU1hcD10LmVtaXNzaXZlTWFwLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9dC5lbWlzc2l2ZUludGVuc2l0eSx0aGlzLnNwZWN1bGFyTWFwPXQuc3BlY3VsYXJNYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZW52TWFwPXQuZW52TWFwLHRoaXMuY29tYmluZT10LmNvbWJpbmUsdGhpcy5yZWZsZWN0aXZpdHk9dC5yZWZsZWN0aXZpdHksdGhpcy5yZWZyYWN0aW9uUmF0aW89dC5yZWZyYWN0aW9uUmF0aW8sdGhpcy53aXJlZnJhbWU9dC53aXJlZnJhbWUsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9dC53aXJlZnJhbWVMaW5ld2lkdGgsdGhpcy53aXJlZnJhbWVMaW5lY2FwPXQud2lyZWZyYW1lTGluZWNhcCx0aGlzLndpcmVmcmFtZUxpbmVqb2luPXQud2lyZWZyYW1lTGluZWpvaW4sdGhpc319O2doLnByb3RvdHlwZS5pc01lc2hMYW1iZXJ0TWF0ZXJpYWw9ITA7dmFyIHhoPWNsYXNzIGV4dGVuZHMgeGV7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLmRlZmluZXM9e01BVENBUDoiIn0sdGhpcy50eXBlPSJNZXNoTWF0Y2FwTWF0ZXJpYWwiLHRoaXMuY29sb3I9bmV3IGZ0KDE2Nzc3MjE1KSx0aGlzLm1hdGNhcD1udWxsLHRoaXMubWFwPW51bGwsdGhpcy5idW1wTWFwPW51bGwsdGhpcy5idW1wU2NhbGU9MSx0aGlzLm5vcm1hbE1hcD1udWxsLHRoaXMubm9ybWFsTWFwVHlwZT10cyx0aGlzLm5vcm1hbFNjYWxlPW5ldyBLKDEsMSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9bnVsbCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPTEsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPTAsdGhpcy5hbHBoYU1hcD1udWxsLHRoaXMuZmxhdFNoYWRpbmc9ITEsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmRlZmluZXM9e01BVENBUDoiIn0sdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMubWF0Y2FwPXQubWF0Y2FwLHRoaXMubWFwPXQubWFwLHRoaXMuYnVtcE1hcD10LmJ1bXBNYXAsdGhpcy5idW1wU2NhbGU9dC5idW1wU2NhbGUsdGhpcy5ub3JtYWxNYXA9dC5ub3JtYWxNYXAsdGhpcy5ub3JtYWxNYXBUeXBlPXQubm9ybWFsTWFwVHlwZSx0aGlzLm5vcm1hbFNjYWxlLmNvcHkodC5ub3JtYWxTY2FsZSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9dC5kaXNwbGFjZW1lbnRNYXAsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT10LmRpc3BsYWNlbWVudFNjYWxlLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz10LmRpc3BsYWNlbWVudEJpYXMsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZmxhdFNoYWRpbmc9dC5mbGF0U2hhZGluZyx0aGlzfX07eGgucHJvdG90eXBlLmlzTWVzaE1hdGNhcE1hdGVyaWFsPSEwO3ZhciB5aD1jbGFzcyBleHRlbmRzIHpue2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJMaW5lRGFzaGVkTWF0ZXJpYWwiLHRoaXMuc2NhbGU9MSx0aGlzLmRhc2hTaXplPTMsdGhpcy5nYXBTaXplPTEsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLnNjYWxlPXQuc2NhbGUsdGhpcy5kYXNoU2l6ZT10LmRhc2hTaXplLHRoaXMuZ2FwU2l6ZT10LmdhcFNpemUsdGhpc319O3loLnByb3RvdHlwZS5pc0xpbmVEYXNoZWRNYXRlcmlhbD0hMDt2YXIgS3Q9e2FycmF5U2xpY2U6ZnVuY3Rpb24obix0LGUpe3JldHVybiBLdC5pc1R5cGVkQXJyYXkobik/bmV3IG4uY29uc3RydWN0b3Iobi5zdWJhcnJheSh0LGUhPT12b2lkIDA/ZTpuLmxlbmd0aCkpOm4uc2xpY2UodCxlKX0sY29udmVydEFycmF5OmZ1bmN0aW9uKG4sdCxlKXtyZXR1cm4hbnx8IWUmJm4uY29uc3RydWN0b3I9PT10P246dHlwZW9mIHQuQllURVNfUEVSX0VMRU1FTlQ9PSJudW1iZXIiP25ldyB0KG4pOkFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKG4pfSxpc1R5cGVkQXJyYXk6ZnVuY3Rpb24obil7cmV0dXJuIEFycmF5QnVmZmVyLmlzVmlldyhuKSYmIShuIGluc3RhbmNlb2YgRGF0YVZpZXcpfSxnZXRLZXlmcmFtZU9yZGVyOmZ1bmN0aW9uKG4pe2Z1bmN0aW9uIHQocixzKXtyZXR1cm4gbltyXS1uW3NdfWxldCBlPW4ubGVuZ3RoLGk9bmV3IEFycmF5KGUpO2ZvcihsZXQgcj0wO3IhPT1lOysrcilpW3JdPXI7cmV0dXJuIGkuc29ydCh0KSxpfSxzb3J0ZWRBcnJheTpmdW5jdGlvbihuLHQsZSl7bGV0IGk9bi5sZW5ndGgscj1uZXcgbi5jb25zdHJ1Y3RvcihpKTtmb3IobGV0IHM9MCxvPTA7byE9PWk7KytzKXtsZXQgYT1lW3NdKnQ7Zm9yKGxldCBsPTA7bCE9PXQ7KytsKXJbbysrXT1uW2ErbF19cmV0dXJuIHJ9LGZsYXR0ZW5KU09OOmZ1bmN0aW9uKG4sdCxlLGkpe2xldCByPTEscz1uWzBdO2Zvcig7cyE9PXZvaWQgMCYmc1tpXT09PXZvaWQgMDspcz1uW3IrK107aWYocz09PXZvaWQgMClyZXR1cm47bGV0IG89c1tpXTtpZihvIT09dm9pZCAwKWlmKEFycmF5LmlzQXJyYXkobykpZG8gbz1zW2ldLG8hPT12b2lkIDAmJih0LnB1c2gocy50aW1lKSxlLnB1c2guYXBwbHkoZSxvKSkscz1uW3IrK107d2hpbGUocyE9PXZvaWQgMCk7ZWxzZSBpZihvLnRvQXJyYXkhPT12b2lkIDApZG8gbz1zW2ldLG8hPT12b2lkIDAmJih0LnB1c2gocy50aW1lKSxvLnRvQXJyYXkoZSxlLmxlbmd0aCkpLHM9bltyKytdO3doaWxlKHMhPT12b2lkIDApO2Vsc2UgZG8gbz1zW2ldLG8hPT12b2lkIDAmJih0LnB1c2gocy50aW1lKSxlLnB1c2gobykpLHM9bltyKytdO3doaWxlKHMhPT12b2lkIDApfSxzdWJjbGlwOmZ1bmN0aW9uKG4sdCxlLGkscj0zMCl7bGV0IHM9bi5jbG9uZSgpO3MubmFtZT10O2xldCBvPVtdO2ZvcihsZXQgbD0wO2w8cy50cmFja3MubGVuZ3RoOysrbCl7bGV0IGM9cy50cmFja3NbbF0sdT1jLmdldFZhbHVlU2l6ZSgpLGg9W10sZj1bXTtmb3IobGV0IGQ9MDtkPGMudGltZXMubGVuZ3RoOysrZCl7bGV0IGc9Yy50aW1lc1tkXSpyO2lmKCEoZzxlfHxnPj1pKSl7aC5wdXNoKGMudGltZXNbZF0pO2ZvcihsZXQgeD0wO3g8dTsrK3gpZi5wdXNoKGMudmFsdWVzW2QqdSt4XSl9fWgubGVuZ3RoIT09MCYmKGMudGltZXM9S3QuY29udmVydEFycmF5KGgsYy50aW1lcy5jb25zdHJ1Y3RvciksYy52YWx1ZXM9S3QuY29udmVydEFycmF5KGYsYy52YWx1ZXMuY29uc3RydWN0b3IpLG8ucHVzaChjKSl9cy50cmFja3M9bztsZXQgYT0xLzA7Zm9yKGxldCBsPTA7bDxzLnRyYWNrcy5sZW5ndGg7KytsKWE+cy50cmFja3NbbF0udGltZXNbMF0mJihhPXMudHJhY2tzW2xdLnRpbWVzWzBdKTtmb3IobGV0IGw9MDtsPHMudHJhY2tzLmxlbmd0aDsrK2wpcy50cmFja3NbbF0uc2hpZnQoLTEqYSk7cmV0dXJuIHMucmVzZXREdXJhdGlvbigpLHN9LG1ha2VDbGlwQWRkaXRpdmU6ZnVuY3Rpb24obix0PTAsZT1uLGk9MzApe2k8PTAmJihpPTMwKTtsZXQgcj1lLnRyYWNrcy5sZW5ndGgscz10L2k7Zm9yKGxldCBvPTA7bzxyOysrbyl7bGV0IGE9ZS50cmFja3Nbb10sbD1hLlZhbHVlVHlwZU5hbWU7aWYobD09PSJib29sInx8bD09PSJzdHJpbmciKWNvbnRpbnVlO2xldCBjPW4udHJhY2tzLmZpbmQoZnVuY3Rpb24obSl7cmV0dXJuIG0ubmFtZT09PWEubmFtZSYmbS5WYWx1ZVR5cGVOYW1lPT09bH0pO2lmKGM9PT12b2lkIDApY29udGludWU7bGV0IHU9MCxoPWEuZ2V0VmFsdWVTaXplKCk7YS5jcmVhdGVJbnRlcnBvbGFudC5pc0ludGVycG9sYW50RmFjdG9yeU1ldGhvZEdMVEZDdWJpY1NwbGluZSYmKHU9aC8zKTtsZXQgZj0wLGQ9Yy5nZXRWYWx1ZVNpemUoKTtjLmNyZWF0ZUludGVycG9sYW50LmlzSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kR0xURkN1YmljU3BsaW5lJiYoZj1kLzMpO2xldCBnPWEudGltZXMubGVuZ3RoLTEseDtpZihzPD1hLnRpbWVzWzBdKXtsZXQgbT11LHA9aC11O3g9S3QuYXJyYXlTbGljZShhLnZhbHVlcyxtLHApfWVsc2UgaWYocz49YS50aW1lc1tnXSl7bGV0IG09ZypoK3UscD1tK2gtdTt4PUt0LmFycmF5U2xpY2UoYS52YWx1ZXMsbSxwKX1lbHNle2xldCBtPWEuY3JlYXRlSW50ZXJwb2xhbnQoKSxwPXUsYj1oLXU7bS5ldmFsdWF0ZShzKSx4PUt0LmFycmF5U2xpY2UobS5yZXN1bHRCdWZmZXIscCxiKX1sPT09InF1YXRlcm5pb24iJiZuZXcgRWUoKS5mcm9tQXJyYXkoeCkubm9ybWFsaXplKCkuY29uanVnYXRlKCkudG9BcnJheSh4KTtsZXQgdj1jLnRpbWVzLmxlbmd0aDtmb3IobGV0IG09MDttPHY7KyttKXtsZXQgcD1tKmQrZjtpZihsPT09InF1YXRlcm5pb24iKUVlLm11bHRpcGx5UXVhdGVybmlvbnNGbGF0KGMudmFsdWVzLHAseCwwLGMudmFsdWVzLHApO2Vsc2V7bGV0IGI9ZC1mKjI7Zm9yKGxldCBfPTA7XzxiOysrXyljLnZhbHVlc1twK19dLT14W19dfX19cmV0dXJuIG4uYmxlbmRNb2RlPWQwLG59fSxwbj1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXt0aGlzLnBhcmFtZXRlclBvc2l0aW9ucz10LHRoaXMuX2NhY2hlZEluZGV4PTAsdGhpcy5yZXN1bHRCdWZmZXI9ciE9PXZvaWQgMD9yOm5ldyBlLmNvbnN0cnVjdG9yKGkpLHRoaXMuc2FtcGxlVmFsdWVzPWUsdGhpcy52YWx1ZVNpemU9aSx0aGlzLnNldHRpbmdzPW51bGwsdGhpcy5EZWZhdWx0U2V0dGluZ3NfPXt9fWV2YWx1YXRlKHQpe2xldCBlPXRoaXMucGFyYW1ldGVyUG9zaXRpb25zLGk9dGhpcy5fY2FjaGVkSW5kZXgscj1lW2ldLHM9ZVtpLTFdO3Q6e2U6e2xldCBvO246e2k6aWYoISh0PHIpKXtmb3IobGV0IGE9aSsyOzspe2lmKHI9PT12b2lkIDApe2lmKHQ8cylicmVhayBpO3JldHVybiBpPWUubGVuZ3RoLHRoaXMuX2NhY2hlZEluZGV4PWksdGhpcy5hZnRlckVuZF8oaS0xLHQscyl9aWYoaT09PWEpYnJlYWs7aWYocz1yLHI9ZVsrK2ldLHQ8cilicmVhayBlfW89ZS5sZW5ndGg7YnJlYWsgbn1pZighKHQ+PXMpKXtsZXQgYT1lWzFdO3Q8YSYmKGk9MixzPWEpO2ZvcihsZXQgbD1pLTI7Oyl7aWYocz09PXZvaWQgMClyZXR1cm4gdGhpcy5fY2FjaGVkSW5kZXg9MCx0aGlzLmJlZm9yZVN0YXJ0XygwLHQscik7aWYoaT09PWwpYnJlYWs7aWYocj1zLHM9ZVstLWktMV0sdD49cylicmVhayBlfW89aSxpPTA7YnJlYWsgbn1icmVhayB0fWZvcig7aTxvOyl7bGV0IGE9aStvPj4+MTt0PGVbYV0/bz1hOmk9YSsxfWlmKHI9ZVtpXSxzPWVbaS0xXSxzPT09dm9pZCAwKXJldHVybiB0aGlzLl9jYWNoZWRJbmRleD0wLHRoaXMuYmVmb3JlU3RhcnRfKDAsdCxyKTtpZihyPT09dm9pZCAwKXJldHVybiBpPWUubGVuZ3RoLHRoaXMuX2NhY2hlZEluZGV4PWksdGhpcy5hZnRlckVuZF8oaS0xLHMsdCl9dGhpcy5fY2FjaGVkSW5kZXg9aSx0aGlzLmludGVydmFsQ2hhbmdlZF8oaSxzLHIpfXJldHVybiB0aGlzLmludGVycG9sYXRlXyhpLHMsdCxyKX1nZXRTZXR0aW5nc18oKXtyZXR1cm4gdGhpcy5zZXR0aW5nc3x8dGhpcy5EZWZhdWx0U2V0dGluZ3NffWNvcHlTYW1wbGVWYWx1ZV8odCl7bGV0IGU9dGhpcy5yZXN1bHRCdWZmZXIsaT10aGlzLnNhbXBsZVZhbHVlcyxyPXRoaXMudmFsdWVTaXplLHM9dCpyO2ZvcihsZXQgbz0wO28hPT1yOysrbyllW29dPWlbcytvXTtyZXR1cm4gZX1pbnRlcnBvbGF0ZV8oKXt0aHJvdyBuZXcgRXJyb3IoImNhbGwgdG8gYWJzdHJhY3QgbWV0aG9kIil9aW50ZXJ2YWxDaGFuZ2VkXygpe319O3BuLnByb3RvdHlwZS5iZWZvcmVTdGFydF89cG4ucHJvdG90eXBlLmNvcHlTYW1wbGVWYWx1ZV87cG4ucHJvdG90eXBlLmFmdGVyRW5kXz1wbi5wcm90b3R5cGUuY29weVNhbXBsZVZhbHVlXzt2YXIgdmg9Y2xhc3MgZXh0ZW5kcyBwbntjb25zdHJ1Y3Rvcih0LGUsaSxyKXtzdXBlcih0LGUsaSxyKSx0aGlzLl93ZWlnaHRQcmV2PS0wLHRoaXMuX29mZnNldFByZXY9LTAsdGhpcy5fd2VpZ2h0TmV4dD0tMCx0aGlzLl9vZmZzZXROZXh0PS0wLHRoaXMuRGVmYXVsdFNldHRpbmdzXz17ZW5kaW5nU3RhcnQ6TnIsZW5kaW5nRW5kOk5yfX1pbnRlcnZhbENoYW5nZWRfKHQsZSxpKXtsZXQgcj10aGlzLnBhcmFtZXRlclBvc2l0aW9ucyxzPXQtMixvPXQrMSxhPXJbc10sbD1yW29dO2lmKGE9PT12b2lkIDApc3dpdGNoKHRoaXMuZ2V0U2V0dGluZ3NfKCkuZW5kaW5nU3RhcnQpe2Nhc2UgRnI6cz10LGE9MiplLWk7YnJlYWs7Y2FzZSBlbDpzPXIubGVuZ3RoLTIsYT1lK3Jbc10tcltzKzFdO2JyZWFrO2RlZmF1bHQ6cz10LGE9aX1pZihsPT09dm9pZCAwKXN3aXRjaCh0aGlzLmdldFNldHRpbmdzXygpLmVuZGluZ0VuZCl7Y2FzZSBGcjpvPXQsbD0yKmktZTticmVhaztjYXNlIGVsOm89MSxsPWkrclsxXS1yWzBdO2JyZWFrO2RlZmF1bHQ6bz10LTEsbD1lfWxldCBjPShpLWUpKi41LHU9dGhpcy52YWx1ZVNpemU7dGhpcy5fd2VpZ2h0UHJldj1jLyhlLWEpLHRoaXMuX3dlaWdodE5leHQ9Yy8obC1pKSx0aGlzLl9vZmZzZXRQcmV2PXMqdSx0aGlzLl9vZmZzZXROZXh0PW8qdX1pbnRlcnBvbGF0ZV8odCxlLGkscil7bGV0IHM9dGhpcy5yZXN1bHRCdWZmZXIsbz10aGlzLnNhbXBsZVZhbHVlcyxhPXRoaXMudmFsdWVTaXplLGw9dCphLGM9bC1hLHU9dGhpcy5fb2Zmc2V0UHJldixoPXRoaXMuX29mZnNldE5leHQsZj10aGlzLl93ZWlnaHRQcmV2LGQ9dGhpcy5fd2VpZ2h0TmV4dCxnPShpLWUpLyhyLWUpLHg9ZypnLHY9eCpnLG09LWYqdisyKmYqeC1mKmcscD0oMStmKSp2KygtMS41LTIqZikqeCsoLS41K2YpKmcrMSxiPSgtMS1kKSp2KygxLjUrZCkqeCsuNSpnLF89ZCp2LWQqeDtmb3IobGV0IFM9MDtTIT09YTsrK1Mpc1tTXT1tKm9bdStTXStwKm9bYytTXStiKm9bbCtTXStfKm9baCtTXTtyZXR1cm4gc319LF9sPWNsYXNzIGV4dGVuZHMgcG57Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIodCxlLGkscil9aW50ZXJwb2xhdGVfKHQsZSxpLHIpe2xldCBzPXRoaXMucmVzdWx0QnVmZmVyLG89dGhpcy5zYW1wbGVWYWx1ZXMsYT10aGlzLnZhbHVlU2l6ZSxsPXQqYSxjPWwtYSx1PShpLWUpLyhyLWUpLGg9MS11O2ZvcihsZXQgZj0wO2YhPT1hOysrZilzW2ZdPW9bYytmXSpoK29bbCtmXSp1O3JldHVybiBzfX0sX2g9Y2xhc3MgZXh0ZW5kcyBwbntjb25zdHJ1Y3Rvcih0LGUsaSxyKXtzdXBlcih0LGUsaSxyKX1pbnRlcnBvbGF0ZV8odCl7cmV0dXJuIHRoaXMuY29weVNhbXBsZVZhbHVlXyh0LTEpfX0sWGU9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscil7aWYodD09PXZvaWQgMCl0aHJvdyBuZXcgRXJyb3IoIlRIUkVFLktleWZyYW1lVHJhY2s6IHRyYWNrIG5hbWUgaXMgdW5kZWZpbmVkIik7aWYoZT09PXZvaWQgMHx8ZS5sZW5ndGg9PT0wKXRocm93IG5ldyBFcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogbm8ga2V5ZnJhbWVzIGluIHRyYWNrIG5hbWVkICIrdCk7dGhpcy5uYW1lPXQsdGhpcy50aW1lcz1LdC5jb252ZXJ0QXJyYXkoZSx0aGlzLlRpbWVCdWZmZXJUeXBlKSx0aGlzLnZhbHVlcz1LdC5jb252ZXJ0QXJyYXkoaSx0aGlzLlZhbHVlQnVmZmVyVHlwZSksdGhpcy5zZXRJbnRlcnBvbGF0aW9uKHJ8fHRoaXMuRGVmYXVsdEludGVycG9sYXRpb24pfXN0YXRpYyB0b0pTT04odCl7bGV0IGU9dC5jb25zdHJ1Y3RvcixpO2lmKGUudG9KU09OIT09dGhpcy50b0pTT04paT1lLnRvSlNPTih0KTtlbHNle2k9e25hbWU6dC5uYW1lLHRpbWVzOkt0LmNvbnZlcnRBcnJheSh0LnRpbWVzLEFycmF5KSx2YWx1ZXM6S3QuY29udmVydEFycmF5KHQudmFsdWVzLEFycmF5KX07bGV0IHI9dC5nZXRJbnRlcnBvbGF0aW9uKCk7ciE9PXQuRGVmYXVsdEludGVycG9sYXRpb24mJihpLmludGVycG9sYXRpb249cil9cmV0dXJuIGkudHlwZT10LlZhbHVlVHlwZU5hbWUsaX1JbnRlcnBvbGFudEZhY3RvcnlNZXRob2REaXNjcmV0ZSh0KXtyZXR1cm4gbmV3IF9oKHRoaXMudGltZXMsdGhpcy52YWx1ZXMsdGhpcy5nZXRWYWx1ZVNpemUoKSx0KX1JbnRlcnBvbGFudEZhY3RvcnlNZXRob2RMaW5lYXIodCl7cmV0dXJuIG5ldyBfbCh0aGlzLnRpbWVzLHRoaXMudmFsdWVzLHRoaXMuZ2V0VmFsdWVTaXplKCksdCl9SW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kU21vb3RoKHQpe3JldHVybiBuZXcgdmgodGhpcy50aW1lcyx0aGlzLnZhbHVlcyx0aGlzLmdldFZhbHVlU2l6ZSgpLHQpfXNldEludGVycG9sYXRpb24odCl7bGV0IGU7c3dpdGNoKHQpe2Nhc2UgamE6ZT10aGlzLkludGVycG9sYW50RmFjdG9yeU1ldGhvZERpc2NyZXRlO2JyZWFrO2Nhc2UgdGw6ZT10aGlzLkludGVycG9sYW50RmFjdG9yeU1ldGhvZExpbmVhcjticmVhaztjYXNlIGV1OmU9dGhpcy5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2RTbW9vdGg7YnJlYWt9aWYoZT09PXZvaWQgMCl7bGV0IGk9InVuc3VwcG9ydGVkIGludGVycG9sYXRpb24gZm9yICIrdGhpcy5WYWx1ZVR5cGVOYW1lKyIga2V5ZnJhbWUgdHJhY2sgbmFtZWQgIit0aGlzLm5hbWU7aWYodGhpcy5jcmVhdGVJbnRlcnBvbGFudD09PXZvaWQgMClpZih0IT09dGhpcy5EZWZhdWx0SW50ZXJwb2xhdGlvbil0aGlzLnNldEludGVycG9sYXRpb24odGhpcy5EZWZhdWx0SW50ZXJwb2xhdGlvbik7ZWxzZSB0aHJvdyBuZXcgRXJyb3IoaSk7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuS2V5ZnJhbWVUcmFjazoiLGkpLHRoaXN9cmV0dXJuIHRoaXMuY3JlYXRlSW50ZXJwb2xhbnQ9ZSx0aGlzfWdldEludGVycG9sYXRpb24oKXtzd2l0Y2godGhpcy5jcmVhdGVJbnRlcnBvbGFudCl7Y2FzZSB0aGlzLkludGVycG9sYW50RmFjdG9yeU1ldGhvZERpc2NyZXRlOnJldHVybiBqYTtjYXNlIHRoaXMuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kTGluZWFyOnJldHVybiB0bDtjYXNlIHRoaXMuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kU21vb3RoOnJldHVybiBldX19Z2V0VmFsdWVTaXplKCl7cmV0dXJuIHRoaXMudmFsdWVzLmxlbmd0aC90aGlzLnRpbWVzLmxlbmd0aH1zaGlmdCh0KXtpZih0IT09MCl7bGV0IGU9dGhpcy50aW1lcztmb3IobGV0IGk9MCxyPWUubGVuZ3RoO2khPT1yOysraSllW2ldKz10fXJldHVybiB0aGlzfXNjYWxlKHQpe2lmKHQhPT0xKXtsZXQgZT10aGlzLnRpbWVzO2ZvcihsZXQgaT0wLHI9ZS5sZW5ndGg7aSE9PXI7KytpKWVbaV0qPXR9cmV0dXJuIHRoaXN9dHJpbSh0LGUpe2xldCBpPXRoaXMudGltZXMscj1pLmxlbmd0aCxzPTAsbz1yLTE7Zm9yKDtzIT09ciYmaVtzXTx0OykrK3M7Zm9yKDtvIT09LTEmJmlbb10+ZTspLS1vO2lmKCsrbyxzIT09MHx8byE9PXIpe3M+PW8mJihvPU1hdGgubWF4KG8sMSkscz1vLTEpO2xldCBhPXRoaXMuZ2V0VmFsdWVTaXplKCk7dGhpcy50aW1lcz1LdC5hcnJheVNsaWNlKGkscyxvKSx0aGlzLnZhbHVlcz1LdC5hcnJheVNsaWNlKHRoaXMudmFsdWVzLHMqYSxvKmEpfXJldHVybiB0aGlzfXZhbGlkYXRlKCl7bGV0IHQ9ITAsZT10aGlzLmdldFZhbHVlU2l6ZSgpO2UtTWF0aC5mbG9vcihlKSE9PTAmJihjb25zb2xlLmVycm9yKCJUSFJFRS5LZXlmcmFtZVRyYWNrOiBJbnZhbGlkIHZhbHVlIHNpemUgaW4gdHJhY2suIix0aGlzKSx0PSExKTtsZXQgaT10aGlzLnRpbWVzLHI9dGhpcy52YWx1ZXMscz1pLmxlbmd0aDtzPT09MCYmKGNvbnNvbGUuZXJyb3IoIlRIUkVFLktleWZyYW1lVHJhY2s6IFRyYWNrIGlzIGVtcHR5LiIsdGhpcyksdD0hMSk7bGV0IG89bnVsbDtmb3IobGV0IGE9MDthIT09czthKyspe2xldCBsPWlbYV07aWYodHlwZW9mIGw9PSJudW1iZXIiJiZpc05hTihsKSl7Y29uc29sZS5lcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogVGltZSBpcyBub3QgYSB2YWxpZCBudW1iZXIuIix0aGlzLGEsbCksdD0hMTticmVha31pZihvIT09bnVsbCYmbz5sKXtjb25zb2xlLmVycm9yKCJUSFJFRS5LZXlmcmFtZVRyYWNrOiBPdXQgb2Ygb3JkZXIga2V5cy4iLHRoaXMsYSxsLG8pLHQ9ITE7YnJlYWt9bz1sfWlmKHIhPT12b2lkIDAmJkt0LmlzVHlwZWRBcnJheShyKSlmb3IobGV0IGE9MCxsPXIubGVuZ3RoO2EhPT1sOysrYSl7bGV0IGM9clthXTtpZihpc05hTihjKSl7Y29uc29sZS5lcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogVmFsdWUgaXMgbm90IGEgdmFsaWQgbnVtYmVyLiIsdGhpcyxhLGMpLHQ9ITE7YnJlYWt9fXJldHVybiB0fW9wdGltaXplKCl7bGV0IHQ9S3QuYXJyYXlTbGljZSh0aGlzLnRpbWVzKSxlPUt0LmFycmF5U2xpY2UodGhpcy52YWx1ZXMpLGk9dGhpcy5nZXRWYWx1ZVNpemUoKSxyPXRoaXMuZ2V0SW50ZXJwb2xhdGlvbigpPT09ZXUscz10Lmxlbmd0aC0xLG89MTtmb3IobGV0IGE9MTthPHM7KythKXtsZXQgbD0hMSxjPXRbYV0sdT10W2ErMV07aWYoYyE9PXUmJihhIT09MXx8YyE9PXRbMF0pKWlmKHIpbD0hMDtlbHNle2xldCBoPWEqaSxmPWgtaSxkPWgraTtmb3IobGV0IGc9MDtnIT09aTsrK2cpe2xldCB4PWVbaCtnXTtpZih4IT09ZVtmK2ddfHx4IT09ZVtkK2ddKXtsPSEwO2JyZWFrfX19aWYobCl7aWYoYSE9PW8pe3Rbb109dFthXTtsZXQgaD1hKmksZj1vKmk7Zm9yKGxldCBkPTA7ZCE9PWk7KytkKWVbZitkXT1lW2grZF19KytvfX1pZihzPjApe3Rbb109dFtzXTtmb3IobGV0IGE9cyppLGw9byppLGM9MDtjIT09aTsrK2MpZVtsK2NdPWVbYStjXTsrK299cmV0dXJuIG8hPT10Lmxlbmd0aD8odGhpcy50aW1lcz1LdC5hcnJheVNsaWNlKHQsMCxvKSx0aGlzLnZhbHVlcz1LdC5hcnJheVNsaWNlKGUsMCxvKmkpKToodGhpcy50aW1lcz10LHRoaXMudmFsdWVzPWUpLHRoaXN9Y2xvbmUoKXtsZXQgdD1LdC5hcnJheVNsaWNlKHRoaXMudGltZXMsMCksZT1LdC5hcnJheVNsaWNlKHRoaXMudmFsdWVzLDApLGk9dGhpcy5jb25zdHJ1Y3RvcixyPW5ldyBpKHRoaXMubmFtZSx0LGUpO3JldHVybiByLmNyZWF0ZUludGVycG9sYW50PXRoaXMuY3JlYXRlSW50ZXJwb2xhbnQscn19O1hlLnByb3RvdHlwZS5UaW1lQnVmZmVyVHlwZT1GbG9hdDMyQXJyYXk7WGUucHJvdG90eXBlLlZhbHVlQnVmZmVyVHlwZT1GbG9hdDMyQXJyYXk7WGUucHJvdG90eXBlLkRlZmF1bHRJbnRlcnBvbGF0aW9uPXRsO3ZhciB1aT1jbGFzcyBleHRlbmRzIFhle307dWkucHJvdG90eXBlLlZhbHVlVHlwZU5hbWU9ImJvb2wiO3VpLnByb3RvdHlwZS5WYWx1ZUJ1ZmZlclR5cGU9QXJyYXk7dWkucHJvdG90eXBlLkRlZmF1bHRJbnRlcnBvbGF0aW9uPWphO3VpLnByb3RvdHlwZS5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2RMaW5lYXI9dm9pZCAwO3VpLnByb3RvdHlwZS5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2RTbW9vdGg9dm9pZCAwO3ZhciB3bD1jbGFzcyBleHRlbmRzIFhle307d2wucHJvdG90eXBlLlZhbHVlVHlwZU5hbWU9ImNvbG9yIjt2YXIgS3I9Y2xhc3MgZXh0ZW5kcyBYZXt9O0tyLnByb3RvdHlwZS5WYWx1ZVR5cGVOYW1lPSJudW1iZXIiO3ZhciB3aD1jbGFzcyBleHRlbmRzIHBue2NvbnN0cnVjdG9yKHQsZSxpLHIpe3N1cGVyKHQsZSxpLHIpfWludGVycG9sYXRlXyh0LGUsaSxyKXtsZXQgcz10aGlzLnJlc3VsdEJ1ZmZlcixvPXRoaXMuc2FtcGxlVmFsdWVzLGE9dGhpcy52YWx1ZVNpemUsbD0oaS1lKS8oci1lKSxjPXQqYTtmb3IobGV0IHU9YythO2MhPT11O2MrPTQpRWUuc2xlcnBGbGF0KHMsMCxvLGMtYSxvLGMsbCk7cmV0dXJuIHN9fSxYaT1jbGFzcyBleHRlbmRzIFhle0ludGVycG9sYW50RmFjdG9yeU1ldGhvZExpbmVhcih0KXtyZXR1cm4gbmV3IHdoKHRoaXMudGltZXMsdGhpcy52YWx1ZXMsdGhpcy5nZXRWYWx1ZVNpemUoKSx0KX19O1hpLnByb3RvdHlwZS5WYWx1ZVR5cGVOYW1lPSJxdWF0ZXJuaW9uIjtYaS5wcm90b3R5cGUuRGVmYXVsdEludGVycG9sYXRpb249dGw7WGkucHJvdG90eXBlLkludGVycG9sYW50RmFjdG9yeU1ldGhvZFNtb290aD12b2lkIDA7dmFyIGhpPWNsYXNzIGV4dGVuZHMgWGV7fTtoaS5wcm90b3R5cGUuVmFsdWVUeXBlTmFtZT0ic3RyaW5nIjtoaS5wcm90b3R5cGUuVmFsdWVCdWZmZXJUeXBlPUFycmF5O2hpLnByb3RvdHlwZS5EZWZhdWx0SW50ZXJwb2xhdGlvbj1qYTtoaS5wcm90b3R5cGUuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kTGluZWFyPXZvaWQgMDtoaS5wcm90b3R5cGUuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kU21vb3RoPXZvaWQgMDt2YXIgUXI9Y2xhc3MgZXh0ZW5kcyBYZXt9O1FyLnByb3RvdHlwZS5WYWx1ZVR5cGVOYW1lPSJ2ZWN0b3IiO3ZhciBNbD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGU9LTEsaSxyPVFoKXt0aGlzLm5hbWU9dCx0aGlzLnRyYWNrcz1pLHRoaXMuZHVyYXRpb249ZSx0aGlzLmJsZW5kTW9kZT1yLHRoaXMudXVpZD10bigpLHRoaXMuZHVyYXRpb248MCYmdGhpcy5yZXNldER1cmF0aW9uKCl9c3RhdGljIHBhcnNlKHQpe2xldCBlPVtdLGk9dC50cmFja3Mscj0xLyh0LmZwc3x8MSk7Zm9yKGxldCBvPTAsYT1pLmxlbmd0aDtvIT09YTsrK28pZS5wdXNoKGNUKGlbb10pLnNjYWxlKHIpKTtsZXQgcz1uZXcgdGhpcyh0Lm5hbWUsdC5kdXJhdGlvbixlLHQuYmxlbmRNb2RlKTtyZXR1cm4gcy51dWlkPXQudXVpZCxzfXN0YXRpYyB0b0pTT04odCl7bGV0IGU9W10saT10LnRyYWNrcyxyPXtuYW1lOnQubmFtZSxkdXJhdGlvbjp0LmR1cmF0aW9uLHRyYWNrczplLHV1aWQ6dC51dWlkLGJsZW5kTW9kZTp0LmJsZW5kTW9kZX07Zm9yKGxldCBzPTAsbz1pLmxlbmd0aDtzIT09bzsrK3MpZS5wdXNoKFhlLnRvSlNPTihpW3NdKSk7cmV0dXJuIHJ9c3RhdGljIENyZWF0ZUZyb21Nb3JwaFRhcmdldFNlcXVlbmNlKHQsZSxpLHIpe2xldCBzPWUubGVuZ3RoLG89W107Zm9yKGxldCBhPTA7YTxzO2ErKyl7bGV0IGw9W10sYz1bXTtsLnB1c2goKGErcy0xKSVzLGEsKGErMSklcyksYy5wdXNoKDAsMSwwKTtsZXQgdT1LdC5nZXRLZXlmcmFtZU9yZGVyKGwpO2w9S3Quc29ydGVkQXJyYXkobCwxLHUpLGM9S3Quc29ydGVkQXJyYXkoYywxLHUpLCFyJiZsWzBdPT09MCYmKGwucHVzaChzKSxjLnB1c2goY1swXSkpLG8ucHVzaChuZXcgS3IoIi5tb3JwaFRhcmdldEluZmx1ZW5jZXNbIitlW2FdLm5hbWUrIl0iLGwsYykuc2NhbGUoMS9pKSl9cmV0dXJuIG5ldyB0aGlzKHQsLTEsbyl9c3RhdGljIGZpbmRCeU5hbWUodCxlKXtsZXQgaT10O2lmKCFBcnJheS5pc0FycmF5KHQpKXtsZXQgcj10O2k9ci5nZW9tZXRyeSYmci5nZW9tZXRyeS5hbmltYXRpb25zfHxyLmFuaW1hdGlvbnN9Zm9yKGxldCByPTA7cjxpLmxlbmd0aDtyKyspaWYoaVtyXS5uYW1lPT09ZSlyZXR1cm4gaVtyXTtyZXR1cm4gbnVsbH1zdGF0aWMgQ3JlYXRlQ2xpcHNGcm9tTW9ycGhUYXJnZXRTZXF1ZW5jZXModCxlLGkpe2xldCByPXt9LHM9L14oW1x3LV0qPykoW1xkXSspJC87Zm9yKGxldCBhPTAsbD10Lmxlbmd0aDthPGw7YSsrKXtsZXQgYz10W2FdLHU9Yy5uYW1lLm1hdGNoKHMpO2lmKHUmJnUubGVuZ3RoPjEpe2xldCBoPXVbMV0sZj1yW2hdO2Z8fChyW2hdPWY9W10pLGYucHVzaChjKX19bGV0IG89W107Zm9yKGxldCBhIGluIHIpby5wdXNoKHRoaXMuQ3JlYXRlRnJvbU1vcnBoVGFyZ2V0U2VxdWVuY2UoYSxyW2FdLGUsaSkpO3JldHVybiBvfXN0YXRpYyBwYXJzZUFuaW1hdGlvbih0LGUpe2lmKCF0KXJldHVybiBjb25zb2xlLmVycm9yKCJUSFJFRS5BbmltYXRpb25DbGlwOiBObyBhbmltYXRpb24gaW4gSlNPTkxvYWRlciBkYXRhLiIpLG51bGw7bGV0IGk9ZnVuY3Rpb24oaCxmLGQsZyx4KXtpZihkLmxlbmd0aCE9PTApe2xldCB2PVtdLG09W107S3QuZmxhdHRlbkpTT04oZCx2LG0sZyksdi5sZW5ndGghPT0wJiZ4LnB1c2gobmV3IGgoZix2LG0pKX19LHI9W10scz10Lm5hbWV8fCJkZWZhdWx0IixvPXQuZnBzfHwzMCxhPXQuYmxlbmRNb2RlLGw9dC5sZW5ndGh8fC0xLGM9dC5oaWVyYXJjaHl8fFtdO2ZvcihsZXQgaD0wO2g8Yy5sZW5ndGg7aCsrKXtsZXQgZj1jW2hdLmtleXM7aWYoISghZnx8Zi5sZW5ndGg9PT0wKSlpZihmWzBdLm1vcnBoVGFyZ2V0cyl7bGV0IGQ9e30sZztmb3IoZz0wO2c8Zi5sZW5ndGg7ZysrKWlmKGZbZ10ubW9ycGhUYXJnZXRzKWZvcihsZXQgeD0wO3g8ZltnXS5tb3JwaFRhcmdldHMubGVuZ3RoO3grKylkW2ZbZ10ubW9ycGhUYXJnZXRzW3hdXT0tMTtmb3IobGV0IHggaW4gZCl7bGV0IHY9W10sbT1bXTtmb3IobGV0IHA9MDtwIT09ZltnXS5tb3JwaFRhcmdldHMubGVuZ3RoOysrcCl7bGV0IGI9ZltnXTt2LnB1c2goYi50aW1lKSxtLnB1c2goYi5tb3JwaFRhcmdldD09PXg/MTowKX1yLnB1c2gobmV3IEtyKCIubW9ycGhUYXJnZXRJbmZsdWVuY2VbIit4KyJdIix2LG0pKX1sPWQubGVuZ3RoKihvfHwxKX1lbHNle2xldCBkPSIuYm9uZXNbIitlW2hdLm5hbWUrIl0iO2koUXIsZCsiLnBvc2l0aW9uIixmLCJwb3MiLHIpLGkoWGksZCsiLnF1YXRlcm5pb24iLGYsInJvdCIsciksaShRcixkKyIuc2NhbGUiLGYsInNjbCIscil9fXJldHVybiByLmxlbmd0aD09PTA/bnVsbDpuZXcgdGhpcyhzLGwscixhKX1yZXNldER1cmF0aW9uKCl7bGV0IHQ9dGhpcy50cmFja3MsZT0wO2ZvcihsZXQgaT0wLHI9dC5sZW5ndGg7aSE9PXI7KytpKXtsZXQgcz10aGlzLnRyYWNrc1tpXTtlPU1hdGgubWF4KGUscy50aW1lc1tzLnRpbWVzLmxlbmd0aC0xXSl9cmV0dXJuIHRoaXMuZHVyYXRpb249ZSx0aGlzfXRyaW0oKXtmb3IobGV0IHQ9MDt0PHRoaXMudHJhY2tzLmxlbmd0aDt0KyspdGhpcy50cmFja3NbdF0udHJpbSgwLHRoaXMuZHVyYXRpb24pO3JldHVybiB0aGlzfXZhbGlkYXRlKCl7bGV0IHQ9ITA7Zm9yKGxldCBlPTA7ZTx0aGlzLnRyYWNrcy5sZW5ndGg7ZSsrKXQ9dCYmdGhpcy50cmFja3NbZV0udmFsaWRhdGUoKTtyZXR1cm4gdH1vcHRpbWl6ZSgpe2ZvcihsZXQgdD0wO3Q8dGhpcy50cmFja3MubGVuZ3RoO3QrKyl0aGlzLnRyYWNrc1t0XS5vcHRpbWl6ZSgpO3JldHVybiB0aGlzfWNsb25lKCl7bGV0IHQ9W107Zm9yKGxldCBlPTA7ZTx0aGlzLnRyYWNrcy5sZW5ndGg7ZSsrKXQucHVzaCh0aGlzLnRyYWNrc1tlXS5jbG9uZSgpKTtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5uYW1lLHRoaXMuZHVyYXRpb24sdCx0aGlzLmJsZW5kTW9kZSl9dG9KU09OKCl7cmV0dXJuIHRoaXMuY29uc3RydWN0b3IudG9KU09OKHRoaXMpfX07ZnVuY3Rpb24gbFQobil7c3dpdGNoKG4udG9Mb3dlckNhc2UoKSl7Y2FzZSJzY2FsYXIiOmNhc2UiZG91YmxlIjpjYXNlImZsb2F0IjpjYXNlIm51bWJlciI6Y2FzZSJpbnRlZ2VyIjpyZXR1cm4gS3I7Y2FzZSJ2ZWN0b3IiOmNhc2UidmVjdG9yMiI6Y2FzZSJ2ZWN0b3IzIjpjYXNlInZlY3RvcjQiOnJldHVybiBRcjtjYXNlImNvbG9yIjpyZXR1cm4gd2w7Y2FzZSJxdWF0ZXJuaW9uIjpyZXR1cm4gWGk7Y2FzZSJib29sIjpjYXNlImJvb2xlYW4iOnJldHVybiB1aTtjYXNlInN0cmluZyI6cmV0dXJuIGhpfXRocm93IG5ldyBFcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogVW5zdXBwb3J0ZWQgdHlwZU5hbWU6ICIrbil9ZnVuY3Rpb24gY1Qobil7aWYobi50eXBlPT09dm9pZCAwKXRocm93IG5ldyBFcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogdHJhY2sgdHlwZSB1bmRlZmluZWQsIGNhbiBub3QgcGFyc2UiKTtsZXQgdD1sVChuLnR5cGUpO2lmKG4udGltZXM9PT12b2lkIDApe2xldCBlPVtdLGk9W107S3QuZmxhdHRlbkpTT04obi5rZXlzLGUsaSwidmFsdWUiKSxuLnRpbWVzPWUsbi52YWx1ZXM9aX1yZXR1cm4gdC5wYXJzZSE9PXZvaWQgMD90LnBhcnNlKG4pOm5ldyB0KG4ubmFtZSxuLnRpbWVzLG4udmFsdWVzLG4uaW50ZXJwb2xhdGlvbil9dmFyIGpyPXtlbmFibGVkOiExLGZpbGVzOnt9LGFkZDpmdW5jdGlvbihuLHQpe3RoaXMuZW5hYmxlZCE9PSExJiYodGhpcy5maWxlc1tuXT10KX0sZ2V0OmZ1bmN0aW9uKG4pe2lmKHRoaXMuZW5hYmxlZCE9PSExKXJldHVybiB0aGlzLmZpbGVzW25dfSxyZW1vdmU6ZnVuY3Rpb24obil7ZGVsZXRlIHRoaXMuZmlsZXNbbl19LGNsZWFyOmZ1bmN0aW9uKCl7dGhpcy5maWxlcz17fX19LE1oPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXtsZXQgcj10aGlzLHM9ITEsbz0wLGE9MCxsLGM9W107dGhpcy5vblN0YXJ0PXZvaWQgMCx0aGlzLm9uTG9hZD10LHRoaXMub25Qcm9ncmVzcz1lLHRoaXMub25FcnJvcj1pLHRoaXMuaXRlbVN0YXJ0PWZ1bmN0aW9uKHUpe2ErKyxzPT09ITEmJnIub25TdGFydCE9PXZvaWQgMCYmci5vblN0YXJ0KHUsbyxhKSxzPSEwfSx0aGlzLml0ZW1FbmQ9ZnVuY3Rpb24odSl7bysrLHIub25Qcm9ncmVzcyE9PXZvaWQgMCYmci5vblByb2dyZXNzKHUsbyxhKSxvPT09YSYmKHM9ITEsci5vbkxvYWQhPT12b2lkIDAmJnIub25Mb2FkKCkpfSx0aGlzLml0ZW1FcnJvcj1mdW5jdGlvbih1KXtyLm9uRXJyb3IhPT12b2lkIDAmJnIub25FcnJvcih1KX0sdGhpcy5yZXNvbHZlVVJMPWZ1bmN0aW9uKHUpe3JldHVybiBsP2wodSk6dX0sdGhpcy5zZXRVUkxNb2RpZmllcj1mdW5jdGlvbih1KXtyZXR1cm4gbD11LHRoaXN9LHRoaXMuYWRkSGFuZGxlcj1mdW5jdGlvbih1LGgpe3JldHVybiBjLnB1c2godSxoKSx0aGlzfSx0aGlzLnJlbW92ZUhhbmRsZXI9ZnVuY3Rpb24odSl7bGV0IGg9Yy5pbmRleE9mKHUpO3JldHVybiBoIT09LTEmJmMuc3BsaWNlKGgsMiksdGhpc30sdGhpcy5nZXRIYW5kbGVyPWZ1bmN0aW9uKHUpe2ZvcihsZXQgaD0wLGY9Yy5sZW5ndGg7aDxmO2grPTIpe2xldCBkPWNbaF0sZz1jW2grMV07aWYoZC5nbG9iYWwmJihkLmxhc3RJbmRleD0wKSxkLnRlc3QodSkpcmV0dXJuIGd9cmV0dXJuIG51bGx9fX0sdVQ9bmV3IE1oLG1uPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMubWFuYWdlcj10IT09dm9pZCAwP3Q6dVQsdGhpcy5jcm9zc09yaWdpbj0iYW5vbnltb3VzIix0aGlzLndpdGhDcmVkZW50aWFscz0hMSx0aGlzLnBhdGg9IiIsdGhpcy5yZXNvdXJjZVBhdGg9IiIsdGhpcy5yZXF1ZXN0SGVhZGVyPXt9fWxvYWQoKXt9bG9hZEFzeW5jKHQsZSl7bGV0IGk9dGhpcztyZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24ocixzKXtpLmxvYWQodCxyLGUscyl9KX1wYXJzZSgpe31zZXRDcm9zc09yaWdpbih0KXtyZXR1cm4gdGhpcy5jcm9zc09yaWdpbj10LHRoaXN9c2V0V2l0aENyZWRlbnRpYWxzKHQpe3JldHVybiB0aGlzLndpdGhDcmVkZW50aWFscz10LHRoaXN9c2V0UGF0aCh0KXtyZXR1cm4gdGhpcy5wYXRoPXQsdGhpc31zZXRSZXNvdXJjZVBhdGgodCl7cmV0dXJuIHRoaXMucmVzb3VyY2VQYXRoPXQsdGhpc31zZXRSZXF1ZXN0SGVhZGVyKHQpe3JldHVybiB0aGlzLnJlcXVlc3RIZWFkZXI9dCx0aGlzfX0sRG49e30sYmg9Y2xhc3MgZXh0ZW5kcyBtbntjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KX1sb2FkKHQsZSxpLHIpe3Q9PT12b2lkIDAmJih0PSIiKSx0aGlzLnBhdGghPT12b2lkIDAmJih0PXRoaXMucGF0aCt0KSx0PXRoaXMubWFuYWdlci5yZXNvbHZlVVJMKHQpO2xldCBzPWpyLmdldCh0KTtpZihzIT09dm9pZCAwKXJldHVybiB0aGlzLm1hbmFnZXIuaXRlbVN0YXJ0KHQpLHNldFRpbWVvdXQoKCk9PntlJiZlKHMpLHRoaXMubWFuYWdlci5pdGVtRW5kKHQpfSwwKSxzO2lmKERuW3RdIT09dm9pZCAwKXtEblt0XS5wdXNoKHtvbkxvYWQ6ZSxvblByb2dyZXNzOmksb25FcnJvcjpyfSk7cmV0dXJufURuW3RdPVtdLERuW3RdLnB1c2goe29uTG9hZDplLG9uUHJvZ3Jlc3M6aSxvbkVycm9yOnJ9KTtsZXQgbz1uZXcgUmVxdWVzdCh0LHtoZWFkZXJzOm5ldyBIZWFkZXJzKHRoaXMucmVxdWVzdEhlYWRlciksY3JlZGVudGlhbHM6dGhpcy53aXRoQ3JlZGVudGlhbHM/ImluY2x1ZGUiOiJzYW1lLW9yaWdpbiJ9KSxhPXRoaXMubWltZVR5cGUsbD10aGlzLnJlc3BvbnNlVHlwZTtmZXRjaChvKS50aGVuKGM9PntpZihjLnN0YXR1cz09PTIwMHx8Yy5zdGF0dXM9PT0wKXtpZihjLnN0YXR1cz09PTAmJmNvbnNvbGUud2FybigiVEhSRUUuRmlsZUxvYWRlcjogSFRUUCBTdGF0dXMgMCByZWNlaXZlZC4iKSx0eXBlb2YgUmVhZGFibGVTdHJlYW09PSJ1bmRlZmluZWQifHxjLmJvZHkuZ2V0UmVhZGVyPT09dm9pZCAwKXJldHVybiBjO2xldCB1PURuW3RdLGg9Yy5ib2R5LmdldFJlYWRlcigpLGY9Yy5oZWFkZXJzLmdldCgiQ29udGVudC1MZW5ndGgiKSxkPWY/cGFyc2VJbnQoZik6MCxnPWQhPT0wLHg9MCx2PW5ldyBSZWFkYWJsZVN0cmVhbSh7c3RhcnQobSl7cCgpO2Z1bmN0aW9uIHAoKXtoLnJlYWQoKS50aGVuKCh7ZG9uZTpiLHZhbHVlOl99KT0+e2lmKGIpbS5jbG9zZSgpO2Vsc2V7eCs9Xy5ieXRlTGVuZ3RoO2xldCBTPW5ldyBQcm9ncmVzc0V2ZW50KCJwcm9ncmVzcyIse2xlbmd0aENvbXB1dGFibGU6Zyxsb2FkZWQ6eCx0b3RhbDpkfSk7Zm9yKGxldCBMPTAsQT11Lmxlbmd0aDtMPEE7TCsrKXtsZXQgSD11W0xdO0gub25Qcm9ncmVzcyYmSC5vblByb2dyZXNzKFMpfW0uZW5xdWV1ZShfKSxwKCl9fSl9fX0pO3JldHVybiBuZXcgUmVzcG9uc2Uodil9ZWxzZSB0aHJvdyBFcnJvcihgZmV0Y2ggZm9yICIke2MudXJsfSIgcmVzcG9uZGVkIHdpdGggJHtjLnN0YXR1c306ICR7Yy5zdGF0dXNUZXh0fWApfSkudGhlbihjPT57c3dpdGNoKGwpe2Nhc2UiYXJyYXlidWZmZXIiOnJldHVybiBjLmFycmF5QnVmZmVyKCk7Y2FzZSJibG9iIjpyZXR1cm4gYy5ibG9iKCk7Y2FzZSJkb2N1bWVudCI6cmV0dXJuIGMudGV4dCgpLnRoZW4odT0+bmV3IERPTVBhcnNlcigpLnBhcnNlRnJvbVN0cmluZyh1LGEpKTtjYXNlImpzb24iOnJldHVybiBjLmpzb24oKTtkZWZhdWx0OmlmKGE9PT12b2lkIDApcmV0dXJuIGMudGV4dCgpO3tsZXQgaD0vY2hhcnNldD0iPyhbXjsiXHNdKikiPy9pLmV4ZWMoYSksZj1oJiZoWzFdP2hbMV0udG9Mb3dlckNhc2UoKTp2b2lkIDAsZD1uZXcgVGV4dERlY29kZXIoZik7cmV0dXJuIGMuYXJyYXlCdWZmZXIoKS50aGVuKGc9PmQuZGVjb2RlKGcpKX19fSkudGhlbihjPT57anIuYWRkKHQsYyk7bGV0IHU9RG5bdF07ZGVsZXRlIERuW3RdO2ZvcihsZXQgaD0wLGY9dS5sZW5ndGg7aDxmO2grKyl7bGV0IGQ9dVtoXTtkLm9uTG9hZCYmZC5vbkxvYWQoYyl9fSkuY2F0Y2goYz0+e2xldCB1PURuW3RdO2lmKHU9PT12b2lkIDApdGhyb3cgdGhpcy5tYW5hZ2VyLml0ZW1FcnJvcih0KSxjO2RlbGV0ZSBEblt0XTtmb3IobGV0IGg9MCxmPXUubGVuZ3RoO2g8ZjtoKyspe2xldCBkPXVbaF07ZC5vbkVycm9yJiZkLm9uRXJyb3IoYyl9dGhpcy5tYW5hZ2VyLml0ZW1FcnJvcih0KX0pLmZpbmFsbHkoKCk9Pnt0aGlzLm1hbmFnZXIuaXRlbUVuZCh0KX0pLHRoaXMubWFuYWdlci5pdGVtU3RhcnQodCl9c2V0UmVzcG9uc2VUeXBlKHQpe3JldHVybiB0aGlzLnJlc3BvbnNlVHlwZT10LHRoaXN9c2V0TWltZVR5cGUodCl7cmV0dXJuIHRoaXMubWltZVR5cGU9dCx0aGlzfX07dmFyIGJsPWNsYXNzIGV4dGVuZHMgbW57Y29uc3RydWN0b3IodCl7c3VwZXIodCl9bG9hZCh0LGUsaSxyKXt0aGlzLnBhdGghPT12b2lkIDAmJih0PXRoaXMucGF0aCt0KSx0PXRoaXMubWFuYWdlci5yZXNvbHZlVVJMKHQpO2xldCBzPXRoaXMsbz1qci5nZXQodCk7aWYobyE9PXZvaWQgMClyZXR1cm4gcy5tYW5hZ2VyLml0ZW1TdGFydCh0KSxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7ZSYmZShvKSxzLm1hbmFnZXIuaXRlbUVuZCh0KX0sMCksbztsZXQgYT1ybygiaW1nIik7ZnVuY3Rpb24gbCgpe3UoKSxqci5hZGQodCx0aGlzKSxlJiZlKHRoaXMpLHMubWFuYWdlci5pdGVtRW5kKHQpfWZ1bmN0aW9uIGMoaCl7dSgpLHImJnIoaCkscy5tYW5hZ2VyLml0ZW1FcnJvcih0KSxzLm1hbmFnZXIuaXRlbUVuZCh0KX1mdW5jdGlvbiB1KCl7YS5yZW1vdmVFdmVudExpc3RlbmVyKCJsb2FkIixsLCExKSxhLnJlbW92ZUV2ZW50TGlzdGVuZXIoImVycm9yIixjLCExKX1yZXR1cm4gYS5hZGRFdmVudExpc3RlbmVyKCJsb2FkIixsLCExKSxhLmFkZEV2ZW50TGlzdGVuZXIoImVycm9yIixjLCExKSx0LnN1YnN0cigwLDUpIT09ImRhdGE6IiYmdGhpcy5jcm9zc09yaWdpbiE9PXZvaWQgMCYmKGEuY3Jvc3NPcmlnaW49dGhpcy5jcm9zc09yaWdpbikscy5tYW5hZ2VyLml0ZW1TdGFydCh0KSxhLnNyYz10LGF9fSxTaD1jbGFzcyBleHRlbmRzIG1ue2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpfWxvYWQodCxlLGkscil7bGV0IHM9bmV3IFdyLG89bmV3IGJsKHRoaXMubWFuYWdlcik7by5zZXRDcm9zc09yaWdpbih0aGlzLmNyb3NzT3JpZ2luKSxvLnNldFBhdGgodGhpcy5wYXRoKTtsZXQgYT0wO2Z1bmN0aW9uIGwoYyl7by5sb2FkKHRbY10sZnVuY3Rpb24odSl7cy5pbWFnZXNbY109dSxhKyssYT09PTYmJihzLm5lZWRzVXBkYXRlPSEwLGUmJmUocykpfSx2b2lkIDAscil9Zm9yKGxldCBjPTA7Yzx0Lmxlbmd0aDsrK2MpbChjKTtyZXR1cm4gc319O3ZhciBFaD1jbGFzcyBleHRlbmRzIG1ue2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpfWxvYWQodCxlLGkscil7bGV0IHM9bmV3IGFlLG89bmV3IGJsKHRoaXMubWFuYWdlcik7cmV0dXJuIG8uc2V0Q3Jvc3NPcmlnaW4odGhpcy5jcm9zc09yaWdpbiksby5zZXRQYXRoKHRoaXMucGF0aCksby5sb2FkKHQsZnVuY3Rpb24oYSl7cy5pbWFnZT1hLHMubmVlZHNVcGRhdGU9ITAsZSE9PXZvaWQgMCYmZShzKX0saSxyKSxzfX0sWWU9Y2xhc3MgZXh0ZW5kcyBrdHtjb25zdHJ1Y3Rvcih0LGU9MSl7c3VwZXIoKSx0aGlzLnR5cGU9IkxpZ2h0Iix0aGlzLmNvbG9yPW5ldyBmdCh0KSx0aGlzLmludGVuc2l0eT1lfWRpc3Bvc2UoKXt9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5pbnRlbnNpdHk9dC5pbnRlbnNpdHksdGhpc310b0pTT04odCl7bGV0IGU9c3VwZXIudG9KU09OKHQpO3JldHVybiBlLm9iamVjdC5jb2xvcj10aGlzLmNvbG9yLmdldEhleCgpLGUub2JqZWN0LmludGVuc2l0eT10aGlzLmludGVuc2l0eSx0aGlzLmdyb3VuZENvbG9yIT09dm9pZCAwJiYoZS5vYmplY3QuZ3JvdW5kQ29sb3I9dGhpcy5ncm91bmRDb2xvci5nZXRIZXgoKSksdGhpcy5kaXN0YW5jZSE9PXZvaWQgMCYmKGUub2JqZWN0LmRpc3RhbmNlPXRoaXMuZGlzdGFuY2UpLHRoaXMuYW5nbGUhPT12b2lkIDAmJihlLm9iamVjdC5hbmdsZT10aGlzLmFuZ2xlKSx0aGlzLmRlY2F5IT09dm9pZCAwJiYoZS5vYmplY3QuZGVjYXk9dGhpcy5kZWNheSksdGhpcy5wZW51bWJyYSE9PXZvaWQgMCYmKGUub2JqZWN0LnBlbnVtYnJhPXRoaXMucGVudW1icmEpLHRoaXMuc2hhZG93IT09dm9pZCAwJiYoZS5vYmplY3Quc2hhZG93PXRoaXMuc2hhZG93LnRvSlNPTigpKSxlfX07WWUucHJvdG90eXBlLmlzTGlnaHQ9ITA7dmFyIFRoPWNsYXNzIGV4dGVuZHMgWWV7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKHQsaSksdGhpcy50eXBlPSJIZW1pc3BoZXJlTGlnaHQiLHRoaXMucG9zaXRpb24uY29weShrdC5EZWZhdWx0VXApLHRoaXMudXBkYXRlTWF0cml4KCksdGhpcy5ncm91bmRDb2xvcj1uZXcgZnQoZSl9Y29weSh0KXtyZXR1cm4gWWUucHJvdG90eXBlLmNvcHkuY2FsbCh0aGlzLHQpLHRoaXMuZ3JvdW5kQ29sb3IuY29weSh0Lmdyb3VuZENvbG9yKSx0aGlzfX07VGgucHJvdG90eXBlLmlzSGVtaXNwaGVyZUxpZ2h0PSEwO3ZhciBuMD1uZXcgd3QsaTA9bmV3IFQscjA9bmV3IFQsU289Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5jYW1lcmE9dCx0aGlzLmJpYXM9MCx0aGlzLm5vcm1hbEJpYXM9MCx0aGlzLnJhZGl1cz0xLHRoaXMuYmx1clNhbXBsZXM9OCx0aGlzLm1hcFNpemU9bmV3IEsoNTEyLDUxMiksdGhpcy5tYXA9bnVsbCx0aGlzLm1hcFBhc3M9bnVsbCx0aGlzLm1hdHJpeD1uZXcgd3QsdGhpcy5hdXRvVXBkYXRlPSEwLHRoaXMubmVlZHNVcGRhdGU9ITEsdGhpcy5fZnJ1c3R1bT1uZXcgcXIsdGhpcy5fZnJhbWVFeHRlbnRzPW5ldyBLKDEsMSksdGhpcy5fdmlld3BvcnRDb3VudD0xLHRoaXMuX3ZpZXdwb3J0cz1bbmV3IFd0KDAsMCwxLDEpXX1nZXRWaWV3cG9ydENvdW50KCl7cmV0dXJuIHRoaXMuX3ZpZXdwb3J0Q291bnR9Z2V0RnJ1c3R1bSgpe3JldHVybiB0aGlzLl9mcnVzdHVtfXVwZGF0ZU1hdHJpY2VzKHQpe2xldCBlPXRoaXMuY2FtZXJhLGk9dGhpcy5tYXRyaXg7aTAuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKHQubWF0cml4V29ybGQpLGUucG9zaXRpb24uY29weShpMCkscjAuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKHQudGFyZ2V0Lm1hdHJpeFdvcmxkKSxlLmxvb2tBdChyMCksZS51cGRhdGVNYXRyaXhXb3JsZCgpLG4wLm11bHRpcGx5TWF0cmljZXMoZS5wcm9qZWN0aW9uTWF0cml4LGUubWF0cml4V29ybGRJbnZlcnNlKSx0aGlzLl9mcnVzdHVtLnNldEZyb21Qcm9qZWN0aW9uTWF0cml4KG4wKSxpLnNldCguNSwwLDAsLjUsMCwuNSwwLC41LDAsMCwuNSwuNSwwLDAsMCwxKSxpLm11bHRpcGx5KGUucHJvamVjdGlvbk1hdHJpeCksaS5tdWx0aXBseShlLm1hdHJpeFdvcmxkSW52ZXJzZSl9Z2V0Vmlld3BvcnQodCl7cmV0dXJuIHRoaXMuX3ZpZXdwb3J0c1t0XX1nZXRGcmFtZUV4dGVudHMoKXtyZXR1cm4gdGhpcy5fZnJhbWVFeHRlbnRzfWRpc3Bvc2UoKXt0aGlzLm1hcCYmdGhpcy5tYXAuZGlzcG9zZSgpLHRoaXMubWFwUGFzcyYmdGhpcy5tYXBQYXNzLmRpc3Bvc2UoKX1jb3B5KHQpe3JldHVybiB0aGlzLmNhbWVyYT10LmNhbWVyYS5jbG9uZSgpLHRoaXMuYmlhcz10LmJpYXMsdGhpcy5yYWRpdXM9dC5yYWRpdXMsdGhpcy5tYXBTaXplLmNvcHkodC5tYXBTaXplKSx0aGlzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX10b0pTT04oKXtsZXQgdD17fTtyZXR1cm4gdGhpcy5iaWFzIT09MCYmKHQuYmlhcz10aGlzLmJpYXMpLHRoaXMubm9ybWFsQmlhcyE9PTAmJih0Lm5vcm1hbEJpYXM9dGhpcy5ub3JtYWxCaWFzKSx0aGlzLnJhZGl1cyE9PTEmJih0LnJhZGl1cz10aGlzLnJhZGl1cyksKHRoaXMubWFwU2l6ZS54IT09NTEyfHx0aGlzLm1hcFNpemUueSE9PTUxMikmJih0Lm1hcFNpemU9dGhpcy5tYXBTaXplLnRvQXJyYXkoKSksdC5jYW1lcmE9dGhpcy5jYW1lcmEudG9KU09OKCExKS5vYmplY3QsZGVsZXRlIHQuY2FtZXJhLm1hdHJpeCx0fX0sU2w9Y2xhc3MgZXh0ZW5kcyBTb3tjb25zdHJ1Y3Rvcigpe3N1cGVyKG5ldyBTZSg1MCwxLC41LDUwMCkpLHRoaXMuZm9jdXM9MX11cGRhdGVNYXRyaWNlcyh0KXtsZXQgZT10aGlzLmNhbWVyYSxpPVZ1KjIqdC5hbmdsZSp0aGlzLmZvY3VzLHI9dGhpcy5tYXBTaXplLndpZHRoL3RoaXMubWFwU2l6ZS5oZWlnaHQscz10LmRpc3RhbmNlfHxlLmZhcjsoaSE9PWUuZm92fHxyIT09ZS5hc3BlY3R8fHMhPT1lLmZhcikmJihlLmZvdj1pLGUuYXNwZWN0PXIsZS5mYXI9cyxlLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKSksc3VwZXIudXBkYXRlTWF0cmljZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmZvY3VzPXQuZm9jdXMsdGhpc319O1NsLnByb3RvdHlwZS5pc1Nwb3RMaWdodFNoYWRvdz0hMDt2YXIgQWg9Y2xhc3MgZXh0ZW5kcyBZZXtjb25zdHJ1Y3Rvcih0LGUsaT0wLHI9TWF0aC5QSS8zLHM9MCxvPTEpe3N1cGVyKHQsZSksdGhpcy50eXBlPSJTcG90TGlnaHQiLHRoaXMucG9zaXRpb24uY29weShrdC5EZWZhdWx0VXApLHRoaXMudXBkYXRlTWF0cml4KCksdGhpcy50YXJnZXQ9bmV3IGt0LHRoaXMuZGlzdGFuY2U9aSx0aGlzLmFuZ2xlPXIsdGhpcy5wZW51bWJyYT1zLHRoaXMuZGVjYXk9byx0aGlzLnNoYWRvdz1uZXcgU2x9Z2V0IHBvd2VyKCl7cmV0dXJuIHRoaXMuaW50ZW5zaXR5Kk1hdGguUEl9c2V0IHBvd2VyKHQpe3RoaXMuaW50ZW5zaXR5PXQvTWF0aC5QSX1kaXNwb3NlKCl7dGhpcy5zaGFkb3cuZGlzcG9zZSgpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5kaXN0YW5jZT10LmRpc3RhbmNlLHRoaXMuYW5nbGU9dC5hbmdsZSx0aGlzLnBlbnVtYnJhPXQucGVudW1icmEsdGhpcy5kZWNheT10LmRlY2F5LHRoaXMudGFyZ2V0PXQudGFyZ2V0LmNsb25lKCksdGhpcy5zaGFkb3c9dC5zaGFkb3cuY2xvbmUoKSx0aGlzfX07QWgucHJvdG90eXBlLmlzU3BvdExpZ2h0PSEwO3ZhciBzMD1uZXcgd3QsWnM9bmV3IFQsTnU9bmV3IFQsRWw9Y2xhc3MgZXh0ZW5kcyBTb3tjb25zdHJ1Y3Rvcigpe3N1cGVyKG5ldyBTZSg5MCwxLC41LDUwMCkpLHRoaXMuX2ZyYW1lRXh0ZW50cz1uZXcgSyg0LDIpLHRoaXMuX3ZpZXdwb3J0Q291bnQ9Nix0aGlzLl92aWV3cG9ydHM9W25ldyBXdCgyLDEsMSwxKSxuZXcgV3QoMCwxLDEsMSksbmV3IFd0KDMsMSwxLDEpLG5ldyBXdCgxLDEsMSwxKSxuZXcgV3QoMywwLDEsMSksbmV3IFd0KDEsMCwxLDEpXSx0aGlzLl9jdWJlRGlyZWN0aW9ucz1bbmV3IFQoMSwwLDApLG5ldyBUKC0xLDAsMCksbmV3IFQoMCwwLDEpLG5ldyBUKDAsMCwtMSksbmV3IFQoMCwxLDApLG5ldyBUKDAsLTEsMCldLHRoaXMuX2N1YmVVcHM9W25ldyBUKDAsMSwwKSxuZXcgVCgwLDEsMCksbmV3IFQoMCwxLDApLG5ldyBUKDAsMSwwKSxuZXcgVCgwLDAsMSksbmV3IFQoMCwwLC0xKV19dXBkYXRlTWF0cmljZXModCxlPTApe2xldCBpPXRoaXMuY2FtZXJhLHI9dGhpcy5tYXRyaXgscz10LmRpc3RhbmNlfHxpLmZhcjtzIT09aS5mYXImJihpLmZhcj1zLGkudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpKSxacy5zZXRGcm9tTWF0cml4UG9zaXRpb24odC5tYXRyaXhXb3JsZCksaS5wb3NpdGlvbi5jb3B5KFpzKSxOdS5jb3B5KGkucG9zaXRpb24pLE51LmFkZCh0aGlzLl9jdWJlRGlyZWN0aW9uc1tlXSksaS51cC5jb3B5KHRoaXMuX2N1YmVVcHNbZV0pLGkubG9va0F0KE51KSxpLnVwZGF0ZU1hdHJpeFdvcmxkKCksci5tYWtlVHJhbnNsYXRpb24oLVpzLngsLVpzLnksLVpzLnopLHMwLm11bHRpcGx5TWF0cmljZXMoaS5wcm9qZWN0aW9uTWF0cml4LGkubWF0cml4V29ybGRJbnZlcnNlKSx0aGlzLl9mcnVzdHVtLnNldEZyb21Qcm9qZWN0aW9uTWF0cml4KHMwKX19O0VsLnByb3RvdHlwZS5pc1BvaW50TGlnaHRTaGFkb3c9ITA7dmFyIENoPWNsYXNzIGV4dGVuZHMgWWV7Y29uc3RydWN0b3IodCxlLGk9MCxyPTEpe3N1cGVyKHQsZSksdGhpcy50eXBlPSJQb2ludExpZ2h0Iix0aGlzLmRpc3RhbmNlPWksdGhpcy5kZWNheT1yLHRoaXMuc2hhZG93PW5ldyBFbH1nZXQgcG93ZXIoKXtyZXR1cm4gdGhpcy5pbnRlbnNpdHkqNCpNYXRoLlBJfXNldCBwb3dlcih0KXt0aGlzLmludGVuc2l0eT10Lyg0Kk1hdGguUEkpfWRpc3Bvc2UoKXt0aGlzLnNoYWRvdy5kaXNwb3NlKCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmRpc3RhbmNlPXQuZGlzdGFuY2UsdGhpcy5kZWNheT10LmRlY2F5LHRoaXMuc2hhZG93PXQuc2hhZG93LmNsb25lKCksdGhpc319O0NoLnByb3RvdHlwZS5pc1BvaW50TGlnaHQ9ITA7dmFyIFRsPWNsYXNzIGV4dGVuZHMgU297Y29uc3RydWN0b3IoKXtzdXBlcihuZXcgVmkoLTUsNSw1LC01LC41LDUwMCkpfX07VGwucHJvdG90eXBlLmlzRGlyZWN0aW9uYWxMaWdodFNoYWRvdz0hMDt2YXIgUmg9Y2xhc3MgZXh0ZW5kcyBZZXtjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKHQsZSksdGhpcy50eXBlPSJEaXJlY3Rpb25hbExpZ2h0Iix0aGlzLnBvc2l0aW9uLmNvcHkoa3QuRGVmYXVsdFVwKSx0aGlzLnVwZGF0ZU1hdHJpeCgpLHRoaXMudGFyZ2V0PW5ldyBrdCx0aGlzLnNoYWRvdz1uZXcgVGx9ZGlzcG9zZSgpe3RoaXMuc2hhZG93LmRpc3Bvc2UoKX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMudGFyZ2V0PXQudGFyZ2V0LmNsb25lKCksdGhpcy5zaGFkb3c9dC5zaGFkb3cuY2xvbmUoKSx0aGlzfX07UmgucHJvdG90eXBlLmlzRGlyZWN0aW9uYWxMaWdodD0hMDt2YXIgTGg9Y2xhc3MgZXh0ZW5kcyBZZXtjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKHQsZSksdGhpcy50eXBlPSJBbWJpZW50TGlnaHQifX07TGgucHJvdG90eXBlLmlzQW1iaWVudExpZ2h0PSEwO3ZhciBQaD1jbGFzcyBleHRlbmRzIFlle2NvbnN0cnVjdG9yKHQsZSxpPTEwLHI9MTApe3N1cGVyKHQsZSksdGhpcy50eXBlPSJSZWN0QXJlYUxpZ2h0Iix0aGlzLndpZHRoPWksdGhpcy5oZWlnaHQ9cn1nZXQgcG93ZXIoKXtyZXR1cm4gdGhpcy5pbnRlbnNpdHkqdGhpcy53aWR0aCp0aGlzLmhlaWdodCpNYXRoLlBJfXNldCBwb3dlcih0KXt0aGlzLmludGVuc2l0eT10Lyh0aGlzLndpZHRoKnRoaXMuaGVpZ2h0Kk1hdGguUEkpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy53aWR0aD10LndpZHRoLHRoaXMuaGVpZ2h0PXQuaGVpZ2h0LHRoaXN9dG9KU09OKHQpe2xldCBlPXN1cGVyLnRvSlNPTih0KTtyZXR1cm4gZS5vYmplY3Qud2lkdGg9dGhpcy53aWR0aCxlLm9iamVjdC5oZWlnaHQ9dGhpcy5oZWlnaHQsZX19O1BoLnByb3RvdHlwZS5pc1JlY3RBcmVhTGlnaHQ9ITA7dmFyIEFsPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5jb2VmZmljaWVudHM9W107Zm9yKGxldCB0PTA7dDw5O3QrKyl0aGlzLmNvZWZmaWNpZW50cy5wdXNoKG5ldyBUKX1zZXQodCl7Zm9yKGxldCBlPTA7ZTw5O2UrKyl0aGlzLmNvZWZmaWNpZW50c1tlXS5jb3B5KHRbZV0pO3JldHVybiB0aGlzfXplcm8oKXtmb3IobGV0IHQ9MDt0PDk7dCsrKXRoaXMuY29lZmZpY2llbnRzW3RdLnNldCgwLDAsMCk7cmV0dXJuIHRoaXN9Z2V0QXQodCxlKXtsZXQgaT10Lngscj10Lnkscz10Lnosbz10aGlzLmNvZWZmaWNpZW50cztyZXR1cm4gZS5jb3B5KG9bMF0pLm11bHRpcGx5U2NhbGFyKC4yODIwOTUpLGUuYWRkU2NhbGVkVmVjdG9yKG9bMV0sLjQ4ODYwMypyKSxlLmFkZFNjYWxlZFZlY3RvcihvWzJdLC40ODg2MDMqcyksZS5hZGRTY2FsZWRWZWN0b3Iob1szXSwuNDg4NjAzKmkpLGUuYWRkU2NhbGVkVmVjdG9yKG9bNF0sMS4wOTI1NDgqKGkqcikpLGUuYWRkU2NhbGVkVmVjdG9yKG9bNV0sMS4wOTI1NDgqKHIqcykpLGUuYWRkU2NhbGVkVmVjdG9yKG9bNl0sLjMxNTM5MiooMypzKnMtMSkpLGUuYWRkU2NhbGVkVmVjdG9yKG9bN10sMS4wOTI1NDgqKGkqcykpLGUuYWRkU2NhbGVkVmVjdG9yKG9bOF0sLjU0NjI3NCooaSppLXIqcikpLGV9Z2V0SXJyYWRpYW5jZUF0KHQsZSl7bGV0IGk9dC54LHI9dC55LHM9dC56LG89dGhpcy5jb2VmZmljaWVudHM7cmV0dXJuIGUuY29weShvWzBdKS5tdWx0aXBseVNjYWxhciguODg2MjI3KSxlLmFkZFNjYWxlZFZlY3RvcihvWzFdLDIqLjUxMTY2NCpyKSxlLmFkZFNjYWxlZFZlY3RvcihvWzJdLDIqLjUxMTY2NCpzKSxlLmFkZFNjYWxlZFZlY3RvcihvWzNdLDIqLjUxMTY2NCppKSxlLmFkZFNjYWxlZFZlY3RvcihvWzRdLDIqLjQyOTA0MyppKnIpLGUuYWRkU2NhbGVkVmVjdG9yKG9bNV0sMiouNDI5MDQzKnIqcyksZS5hZGRTY2FsZWRWZWN0b3Iob1s2XSwuNzQzMTI1KnMqcy0uMjQ3NzA4KSxlLmFkZFNjYWxlZFZlY3RvcihvWzddLDIqLjQyOTA0MyppKnMpLGUuYWRkU2NhbGVkVmVjdG9yKG9bOF0sLjQyOTA0MyooaSppLXIqcikpLGV9YWRkKHQpe2ZvcihsZXQgZT0wO2U8OTtlKyspdGhpcy5jb2VmZmljaWVudHNbZV0uYWRkKHQuY29lZmZpY2llbnRzW2VdKTtyZXR1cm4gdGhpc31hZGRTY2FsZWRTSCh0LGUpe2ZvcihsZXQgaT0wO2k8OTtpKyspdGhpcy5jb2VmZmljaWVudHNbaV0uYWRkU2NhbGVkVmVjdG9yKHQuY29lZmZpY2llbnRzW2ldLGUpO3JldHVybiB0aGlzfXNjYWxlKHQpe2ZvcihsZXQgZT0wO2U8OTtlKyspdGhpcy5jb2VmZmljaWVudHNbZV0ubXVsdGlwbHlTY2FsYXIodCk7cmV0dXJuIHRoaXN9bGVycCh0LGUpe2ZvcihsZXQgaT0wO2k8OTtpKyspdGhpcy5jb2VmZmljaWVudHNbaV0ubGVycCh0LmNvZWZmaWNpZW50c1tpXSxlKTtyZXR1cm4gdGhpc31lcXVhbHModCl7Zm9yKGxldCBlPTA7ZTw5O2UrKylpZighdGhpcy5jb2VmZmljaWVudHNbZV0uZXF1YWxzKHQuY29lZmZpY2llbnRzW2VdKSlyZXR1cm4hMTtyZXR1cm4hMH1jb3B5KHQpe3JldHVybiB0aGlzLnNldCh0LmNvZWZmaWNpZW50cyl9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfWZyb21BcnJheSh0LGU9MCl7bGV0IGk9dGhpcy5jb2VmZmljaWVudHM7Zm9yKGxldCByPTA7cjw5O3IrKylpW3JdLmZyb21BcnJheSh0LGUrciozKTtyZXR1cm4gdGhpc310b0FycmF5KHQ9W10sZT0wKXtsZXQgaT10aGlzLmNvZWZmaWNpZW50cztmb3IobGV0IHI9MDtyPDk7cisrKWlbcl0udG9BcnJheSh0LGUrciozKTtyZXR1cm4gdH1zdGF0aWMgZ2V0QmFzaXNBdCh0LGUpe2xldCBpPXQueCxyPXQueSxzPXQuejtlWzBdPS4yODIwOTUsZVsxXT0uNDg4NjAzKnIsZVsyXT0uNDg4NjAzKnMsZVszXT0uNDg4NjAzKmksZVs0XT0xLjA5MjU0OCppKnIsZVs1XT0xLjA5MjU0OCpyKnMsZVs2XT0uMzE1MzkyKigzKnMqcy0xKSxlWzddPTEuMDkyNTQ4KmkqcyxlWzhdPS41NDYyNzQqKGkqaS1yKnIpfX07QWwucHJvdG90eXBlLmlzU3BoZXJpY2FsSGFybW9uaWNzMz0hMDt2YXIgRW89Y2xhc3MgZXh0ZW5kcyBZZXtjb25zdHJ1Y3Rvcih0PW5ldyBBbCxlPTEpe3N1cGVyKHZvaWQgMCxlKSx0aGlzLnNoPXR9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLnNoLmNvcHkodC5zaCksdGhpc31mcm9tSlNPTih0KXtyZXR1cm4gdGhpcy5pbnRlbnNpdHk9dC5pbnRlbnNpdHksdGhpcy5zaC5mcm9tQXJyYXkodC5zaCksdGhpc310b0pTT04odCl7bGV0IGU9c3VwZXIudG9KU09OKHQpO3JldHVybiBlLm9iamVjdC5zaD10aGlzLnNoLnRvQXJyYXkoKSxlfX07RW8ucHJvdG90eXBlLmlzTGlnaHRQcm9iZT0hMDt2YXIgRGg9Y2xhc3N7c3RhdGljIGRlY29kZVRleHQodCl7aWYodHlwZW9mIFRleHREZWNvZGVyIT0idW5kZWZpbmVkIilyZXR1cm4gbmV3IFRleHREZWNvZGVyKCkuZGVjb2RlKHQpO2xldCBlPSIiO2ZvcihsZXQgaT0wLHI9dC5sZW5ndGg7aTxyO2krKyllKz1TdHJpbmcuZnJvbUNoYXJDb2RlKHRbaV0pO3RyeXtyZXR1cm4gZGVjb2RlVVJJQ29tcG9uZW50KGVzY2FwZShlKSl9Y2F0Y2goaSl7cmV0dXJuIGV9fXN0YXRpYyBleHRyYWN0VXJsQmFzZSh0KXtsZXQgZT10Lmxhc3RJbmRleE9mKCIvIik7cmV0dXJuIGU9PT0tMT8iLi8iOnQuc3Vic3RyKDAsZSsxKX1zdGF0aWMgcmVzb2x2ZVVSTCh0LGUpe3JldHVybiB0eXBlb2YgdCE9InN0cmluZyJ8fHQ9PT0iIj8iIjooL15odHRwcz86XC9cLy9pLnRlc3QoZSkmJi9eXC8vLnRlc3QodCkmJihlPWUucmVwbGFjZSgvKF5odHRwcz86XC9cL1teXC9dKykuKi9pLCIkMSIpKSwvXihodHRwcz86KT9cL1wvL2kudGVzdCh0KXx8L15kYXRhOi4qLC4qJC9pLnRlc3QodCl8fC9eYmxvYjouKiQvaS50ZXN0KHQpP3Q6ZSt0KX19LEloPWNsYXNzIGV4dGVuZHMgSHR7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMudHlwZT0iSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnkiLHRoaXMuaW5zdGFuY2VDb3VudD0xLzB9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmluc3RhbmNlQ291bnQ9dC5pbnN0YW5jZUNvdW50LHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTih0aGlzKTtyZXR1cm4gdC5pbnN0YW5jZUNvdW50PXRoaXMuaW5zdGFuY2VDb3VudCx0LmlzSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnk9ITAsdH19O0loLnByb3RvdHlwZS5pc0luc3RhbmNlZEJ1ZmZlckdlb21ldHJ5PSEwO3ZhciBOaD1jbGFzcyBleHRlbmRzIG1ue2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpLHR5cGVvZiBjcmVhdGVJbWFnZUJpdG1hcD09InVuZGVmaW5lZCImJmNvbnNvbGUud2FybigiVEhSRUUuSW1hZ2VCaXRtYXBMb2FkZXI6IGNyZWF0ZUltYWdlQml0bWFwKCkgbm90IHN1cHBvcnRlZC4iKSx0eXBlb2YgZmV0Y2g9PSJ1bmRlZmluZWQiJiZjb25zb2xlLndhcm4oIlRIUkVFLkltYWdlQml0bWFwTG9hZGVyOiBmZXRjaCgpIG5vdCBzdXBwb3J0ZWQuIiksdGhpcy5vcHRpb25zPXtwcmVtdWx0aXBseUFscGhhOiJub25lIn19c2V0T3B0aW9ucyh0KXtyZXR1cm4gdGhpcy5vcHRpb25zPXQsdGhpc31sb2FkKHQsZSxpLHIpe3Q9PT12b2lkIDAmJih0PSIiKSx0aGlzLnBhdGghPT12b2lkIDAmJih0PXRoaXMucGF0aCt0KSx0PXRoaXMubWFuYWdlci5yZXNvbHZlVVJMKHQpO2xldCBzPXRoaXMsbz1qci5nZXQodCk7aWYobyE9PXZvaWQgMClyZXR1cm4gcy5tYW5hZ2VyLml0ZW1TdGFydCh0KSxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7ZSYmZShvKSxzLm1hbmFnZXIuaXRlbUVuZCh0KX0sMCksbztsZXQgYT17fTthLmNyZWRlbnRpYWxzPXRoaXMuY3Jvc3NPcmlnaW49PT0iYW5vbnltb3VzIj8ic2FtZS1vcmlnaW4iOiJpbmNsdWRlIixhLmhlYWRlcnM9dGhpcy5yZXF1ZXN0SGVhZGVyLGZldGNoKHQsYSkudGhlbihmdW5jdGlvbihsKXtyZXR1cm4gbC5ibG9iKCl9KS50aGVuKGZ1bmN0aW9uKGwpe3JldHVybiBjcmVhdGVJbWFnZUJpdG1hcChsLE9iamVjdC5hc3NpZ24ocy5vcHRpb25zLHtjb2xvclNwYWNlQ29udmVyc2lvbjoibm9uZSJ9KSl9KS50aGVuKGZ1bmN0aW9uKGwpe2pyLmFkZCh0LGwpLGUmJmUobCkscy5tYW5hZ2VyLml0ZW1FbmQodCl9KS5jYXRjaChmdW5jdGlvbihsKXtyJiZyKGwpLHMubWFuYWdlci5pdGVtRXJyb3IodCkscy5tYW5hZ2VyLml0ZW1FbmQodCl9KSxzLm1hbmFnZXIuaXRlbVN0YXJ0KHQpfX07TmgucHJvdG90eXBlLmlzSW1hZ2VCaXRtYXBMb2FkZXI9ITA7dmFyIEphLGhUPXtnZXRDb250ZXh0OmZ1bmN0aW9uKCl7cmV0dXJuIEphPT09dm9pZCAwJiYoSmE9bmV3KHdpbmRvdy5BdWRpb0NvbnRleHR8fHdpbmRvdy53ZWJraXRBdWRpb0NvbnRleHQpKSxKYX0sc2V0Q29udGV4dDpmdW5jdGlvbihuKXtKYT1ufX0sRmg9Y2xhc3MgZXh0ZW5kcyBtbntjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KX1sb2FkKHQsZSxpLHIpe2xldCBzPXRoaXMsbz1uZXcgYmgodGhpcy5tYW5hZ2VyKTtvLnNldFJlc3BvbnNlVHlwZSgiYXJyYXlidWZmZXIiKSxvLnNldFBhdGgodGhpcy5wYXRoKSxvLnNldFJlcXVlc3RIZWFkZXIodGhpcy5yZXF1ZXN0SGVhZGVyKSxvLnNldFdpdGhDcmVkZW50aWFscyh0aGlzLndpdGhDcmVkZW50aWFscyksby5sb2FkKHQsZnVuY3Rpb24oYSl7dHJ5e2xldCBsPWEuc2xpY2UoMCk7aFQuZ2V0Q29udGV4dCgpLmRlY29kZUF1ZGlvRGF0YShsLGZ1bmN0aW9uKHUpe2UodSl9KX1jYXRjaChsKXtyP3IobCk6Y29uc29sZS5lcnJvcihsKSxzLm1hbmFnZXIuaXRlbUVycm9yKHQpfX0saSxyKX19LHpoPWNsYXNzIGV4dGVuZHMgRW97Y29uc3RydWN0b3IodCxlLGk9MSl7c3VwZXIodm9pZCAwLGkpO2xldCByPW5ldyBmdCgpLnNldCh0KSxzPW5ldyBmdCgpLnNldChlKSxvPW5ldyBUKHIucixyLmcsci5iKSxhPW5ldyBUKHMucixzLmcscy5iKSxsPU1hdGguc3FydChNYXRoLlBJKSxjPWwqTWF0aC5zcXJ0KC43NSk7dGhpcy5zaC5jb2VmZmljaWVudHNbMF0uY29weShvKS5hZGQoYSkubXVsdGlwbHlTY2FsYXIobCksdGhpcy5zaC5jb2VmZmljaWVudHNbMV0uY29weShvKS5zdWIoYSkubXVsdGlwbHlTY2FsYXIoYyl9fTt6aC5wcm90b3R5cGUuaXNIZW1pc3BoZXJlTGlnaHRQcm9iZT0hMDt2YXIgVWg9Y2xhc3MgZXh0ZW5kcyBFb3tjb25zdHJ1Y3Rvcih0LGU9MSl7c3VwZXIodm9pZCAwLGUpO2xldCBpPW5ldyBmdCgpLnNldCh0KTt0aGlzLnNoLmNvZWZmaWNpZW50c1swXS5zZXQoaS5yLGkuZyxpLmIpLm11bHRpcGx5U2NhbGFyKDIqTWF0aC5zcXJ0KE1hdGguUEkpKX19O1VoLnByb3RvdHlwZS5pc0FtYmllbnRMaWdodFByb2JlPSEwO3ZhciBCaD1jbGFzcyBleHRlbmRzIGt0e2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJBdWRpbyIsdGhpcy5saXN0ZW5lcj10LHRoaXMuY29udGV4dD10LmNvbnRleHQsdGhpcy5nYWluPXRoaXMuY29udGV4dC5jcmVhdGVHYWluKCksdGhpcy5nYWluLmNvbm5lY3QodC5nZXRJbnB1dCgpKSx0aGlzLmF1dG9wbGF5PSExLHRoaXMuYnVmZmVyPW51bGwsdGhpcy5kZXR1bmU9MCx0aGlzLmxvb3A9ITEsdGhpcy5sb29wU3RhcnQ9MCx0aGlzLmxvb3BFbmQ9MCx0aGlzLm9mZnNldD0wLHRoaXMuZHVyYXRpb249dm9pZCAwLHRoaXMucGxheWJhY2tSYXRlPTEsdGhpcy5pc1BsYXlpbmc9ITEsdGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9ITAsdGhpcy5zb3VyY2U9bnVsbCx0aGlzLnNvdXJjZVR5cGU9ImVtcHR5Iix0aGlzLl9zdGFydGVkQXQ9MCx0aGlzLl9wcm9ncmVzcz0wLHRoaXMuX2Nvbm5lY3RlZD0hMSx0aGlzLmZpbHRlcnM9W119Z2V0T3V0cHV0KCl7cmV0dXJuIHRoaXMuZ2Fpbn1zZXROb2RlU291cmNlKHQpe3JldHVybiB0aGlzLmhhc1BsYXliYWNrQ29udHJvbD0hMSx0aGlzLnNvdXJjZVR5cGU9ImF1ZGlvTm9kZSIsdGhpcy5zb3VyY2U9dCx0aGlzLmNvbm5lY3QoKSx0aGlzfXNldE1lZGlhRWxlbWVudFNvdXJjZSh0KXtyZXR1cm4gdGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9ITEsdGhpcy5zb3VyY2VUeXBlPSJtZWRpYU5vZGUiLHRoaXMuc291cmNlPXRoaXMuY29udGV4dC5jcmVhdGVNZWRpYUVsZW1lbnRTb3VyY2UodCksdGhpcy5jb25uZWN0KCksdGhpc31zZXRNZWRpYVN0cmVhbVNvdXJjZSh0KXtyZXR1cm4gdGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9ITEsdGhpcy5zb3VyY2VUeXBlPSJtZWRpYVN0cmVhbU5vZGUiLHRoaXMuc291cmNlPXRoaXMuY29udGV4dC5jcmVhdGVNZWRpYVN0cmVhbVNvdXJjZSh0KSx0aGlzLmNvbm5lY3QoKSx0aGlzfXNldEJ1ZmZlcih0KXtyZXR1cm4gdGhpcy5idWZmZXI9dCx0aGlzLnNvdXJjZVR5cGU9ImJ1ZmZlciIsdGhpcy5hdXRvcGxheSYmdGhpcy5wbGF5KCksdGhpc31wbGF5KHQ9MCl7aWYodGhpcy5pc1BsYXlpbmc9PT0hMCl7Y29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogQXVkaW8gaXMgYWxyZWFkeSBwbGF5aW5nLiIpO3JldHVybn1pZih0aGlzLmhhc1BsYXliYWNrQ29udHJvbD09PSExKXtjb25zb2xlLndhcm4oIlRIUkVFLkF1ZGlvOiB0aGlzIEF1ZGlvIGhhcyBubyBwbGF5YmFjayBjb250cm9sLiIpO3JldHVybn10aGlzLl9zdGFydGVkQXQ9dGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lK3Q7bGV0IGU9dGhpcy5jb250ZXh0LmNyZWF0ZUJ1ZmZlclNvdXJjZSgpO3JldHVybiBlLmJ1ZmZlcj10aGlzLmJ1ZmZlcixlLmxvb3A9dGhpcy5sb29wLGUubG9vcFN0YXJ0PXRoaXMubG9vcFN0YXJ0LGUubG9vcEVuZD10aGlzLmxvb3BFbmQsZS5vbmVuZGVkPXRoaXMub25FbmRlZC5iaW5kKHRoaXMpLGUuc3RhcnQodGhpcy5fc3RhcnRlZEF0LHRoaXMuX3Byb2dyZXNzK3RoaXMub2Zmc2V0LHRoaXMuZHVyYXRpb24pLHRoaXMuaXNQbGF5aW5nPSEwLHRoaXMuc291cmNlPWUsdGhpcy5zZXREZXR1bmUodGhpcy5kZXR1bmUpLHRoaXMuc2V0UGxheWJhY2tSYXRlKHRoaXMucGxheWJhY2tSYXRlKSx0aGlzLmNvbm5lY3QoKX1wYXVzZSgpe2lmKHRoaXMuaGFzUGxheWJhY2tDb250cm9sPT09ITEpe2NvbnNvbGUud2FybigiVEhSRUUuQXVkaW86IHRoaXMgQXVkaW8gaGFzIG5vIHBsYXliYWNrIGNvbnRyb2wuIik7cmV0dXJufXJldHVybiB0aGlzLmlzUGxheWluZz09PSEwJiYodGhpcy5fcHJvZ3Jlc3MrPU1hdGgubWF4KHRoaXMuY29udGV4dC5jdXJyZW50VGltZS10aGlzLl9zdGFydGVkQXQsMCkqdGhpcy5wbGF5YmFja1JhdGUsdGhpcy5sb29wPT09ITAmJih0aGlzLl9wcm9ncmVzcz10aGlzLl9wcm9ncmVzcyUodGhpcy5kdXJhdGlvbnx8dGhpcy5idWZmZXIuZHVyYXRpb24pKSx0aGlzLnNvdXJjZS5zdG9wKCksdGhpcy5zb3VyY2Uub25lbmRlZD1udWxsLHRoaXMuaXNQbGF5aW5nPSExKSx0aGlzfXN0b3AoKXtpZih0aGlzLmhhc1BsYXliYWNrQ29udHJvbD09PSExKXtjb25zb2xlLndhcm4oIlRIUkVFLkF1ZGlvOiB0aGlzIEF1ZGlvIGhhcyBubyBwbGF5YmFjayBjb250cm9sLiIpO3JldHVybn1yZXR1cm4gdGhpcy5fcHJvZ3Jlc3M9MCx0aGlzLnNvdXJjZS5zdG9wKCksdGhpcy5zb3VyY2Uub25lbmRlZD1udWxsLHRoaXMuaXNQbGF5aW5nPSExLHRoaXN9Y29ubmVjdCgpe2lmKHRoaXMuZmlsdGVycy5sZW5ndGg+MCl7dGhpcy5zb3VyY2UuY29ubmVjdCh0aGlzLmZpbHRlcnNbMF0pO2ZvcihsZXQgdD0xLGU9dGhpcy5maWx0ZXJzLmxlbmd0aDt0PGU7dCsrKXRoaXMuZmlsdGVyc1t0LTFdLmNvbm5lY3QodGhpcy5maWx0ZXJzW3RdKTt0aGlzLmZpbHRlcnNbdGhpcy5maWx0ZXJzLmxlbmd0aC0xXS5jb25uZWN0KHRoaXMuZ2V0T3V0cHV0KCkpfWVsc2UgdGhpcy5zb3VyY2UuY29ubmVjdCh0aGlzLmdldE91dHB1dCgpKTtyZXR1cm4gdGhpcy5fY29ubmVjdGVkPSEwLHRoaXN9ZGlzY29ubmVjdCgpe2lmKHRoaXMuZmlsdGVycy5sZW5ndGg+MCl7dGhpcy5zb3VyY2UuZGlzY29ubmVjdCh0aGlzLmZpbHRlcnNbMF0pO2ZvcihsZXQgdD0xLGU9dGhpcy5maWx0ZXJzLmxlbmd0aDt0PGU7dCsrKXRoaXMuZmlsdGVyc1t0LTFdLmRpc2Nvbm5lY3QodGhpcy5maWx0ZXJzW3RdKTt0aGlzLmZpbHRlcnNbdGhpcy5maWx0ZXJzLmxlbmd0aC0xXS5kaXNjb25uZWN0KHRoaXMuZ2V0T3V0cHV0KCkpfWVsc2UgdGhpcy5zb3VyY2UuZGlzY29ubmVjdCh0aGlzLmdldE91dHB1dCgpKTtyZXR1cm4gdGhpcy5fY29ubmVjdGVkPSExLHRoaXN9Z2V0RmlsdGVycygpe3JldHVybiB0aGlzLmZpbHRlcnN9c2V0RmlsdGVycyh0KXtyZXR1cm4gdHx8KHQ9W10pLHRoaXMuX2Nvbm5lY3RlZD09PSEwPyh0aGlzLmRpc2Nvbm5lY3QoKSx0aGlzLmZpbHRlcnM9dC5zbGljZSgpLHRoaXMuY29ubmVjdCgpKTp0aGlzLmZpbHRlcnM9dC5zbGljZSgpLHRoaXN9c2V0RGV0dW5lKHQpe2lmKHRoaXMuZGV0dW5lPXQsdGhpcy5zb3VyY2UuZGV0dW5lIT09dm9pZCAwKXJldHVybiB0aGlzLmlzUGxheWluZz09PSEwJiZ0aGlzLnNvdXJjZS5kZXR1bmUuc2V0VGFyZ2V0QXRUaW1lKHRoaXMuZGV0dW5lLHRoaXMuY29udGV4dC5jdXJyZW50VGltZSwuMDEpLHRoaXN9Z2V0RGV0dW5lKCl7cmV0dXJuIHRoaXMuZGV0dW5lfWdldEZpbHRlcigpe3JldHVybiB0aGlzLmdldEZpbHRlcnMoKVswXX1zZXRGaWx0ZXIodCl7cmV0dXJuIHRoaXMuc2V0RmlsdGVycyh0P1t0XTpbXSl9c2V0UGxheWJhY2tSYXRlKHQpe2lmKHRoaXMuaGFzUGxheWJhY2tDb250cm9sPT09ITEpe2NvbnNvbGUud2FybigiVEhSRUUuQXVkaW86IHRoaXMgQXVkaW8gaGFzIG5vIHBsYXliYWNrIGNvbnRyb2wuIik7cmV0dXJufXJldHVybiB0aGlzLnBsYXliYWNrUmF0ZT10LHRoaXMuaXNQbGF5aW5nPT09ITAmJnRoaXMuc291cmNlLnBsYXliYWNrUmF0ZS5zZXRUYXJnZXRBdFRpbWUodGhpcy5wbGF5YmFja1JhdGUsdGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lLC4wMSksdGhpc31nZXRQbGF5YmFja1JhdGUoKXtyZXR1cm4gdGhpcy5wbGF5YmFja1JhdGV9b25FbmRlZCgpe3RoaXMuaXNQbGF5aW5nPSExfWdldExvb3AoKXtyZXR1cm4gdGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9PT0hMT8oY29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogdGhpcyBBdWRpbyBoYXMgbm8gcGxheWJhY2sgY29udHJvbC4iKSwhMSk6dGhpcy5sb29wfXNldExvb3AodCl7aWYodGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9PT0hMSl7Y29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogdGhpcyBBdWRpbyBoYXMgbm8gcGxheWJhY2sgY29udHJvbC4iKTtyZXR1cm59cmV0dXJuIHRoaXMubG9vcD10LHRoaXMuaXNQbGF5aW5nPT09ITAmJih0aGlzLnNvdXJjZS5sb29wPXRoaXMubG9vcCksdGhpc31zZXRMb29wU3RhcnQodCl7cmV0dXJuIHRoaXMubG9vcFN0YXJ0PXQsdGhpc31zZXRMb29wRW5kKHQpe3JldHVybiB0aGlzLmxvb3BFbmQ9dCx0aGlzfWdldFZvbHVtZSgpe3JldHVybiB0aGlzLmdhaW4uZ2Fpbi52YWx1ZX1zZXRWb2x1bWUodCl7cmV0dXJuIHRoaXMuZ2Fpbi5nYWluLnNldFRhcmdldEF0VGltZSh0LHRoaXMuY29udGV4dC5jdXJyZW50VGltZSwuMDEpLHRoaXN9fTt2YXIgT2g9Y2xhc3N7Y29uc3RydWN0b3IodCxlPTIwNDgpe3RoaXMuYW5hbHlzZXI9dC5jb250ZXh0LmNyZWF0ZUFuYWx5c2VyKCksdGhpcy5hbmFseXNlci5mZnRTaXplPWUsdGhpcy5kYXRhPW5ldyBVaW50OEFycmF5KHRoaXMuYW5hbHlzZXIuZnJlcXVlbmN5QmluQ291bnQpLHQuZ2V0T3V0cHV0KCkuY29ubmVjdCh0aGlzLmFuYWx5c2VyKX1nZXRGcmVxdWVuY3lEYXRhKCl7cmV0dXJuIHRoaXMuYW5hbHlzZXIuZ2V0Qnl0ZUZyZXF1ZW5jeURhdGEodGhpcy5kYXRhKSx0aGlzLmRhdGF9Z2V0QXZlcmFnZUZyZXF1ZW5jeSgpe2xldCB0PTAsZT10aGlzLmdldEZyZXF1ZW5jeURhdGEoKTtmb3IobGV0IGk9MDtpPGUubGVuZ3RoO2krKyl0Kz1lW2ldO3JldHVybiB0L2UubGVuZ3RofX0sa2g9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3RoaXMuYmluZGluZz10LHRoaXMudmFsdWVTaXplPWk7bGV0IHIscyxvO3N3aXRjaChlKXtjYXNlInF1YXRlcm5pb24iOnI9dGhpcy5fc2xlcnAscz10aGlzLl9zbGVycEFkZGl0aXZlLG89dGhpcy5fc2V0QWRkaXRpdmVJZGVudGl0eVF1YXRlcm5pb24sdGhpcy5idWZmZXI9bmV3IEZsb2F0NjRBcnJheShpKjYpLHRoaXMuX3dvcmtJbmRleD01O2JyZWFrO2Nhc2Uic3RyaW5nIjpjYXNlImJvb2wiOnI9dGhpcy5fc2VsZWN0LHM9dGhpcy5fc2VsZWN0LG89dGhpcy5fc2V0QWRkaXRpdmVJZGVudGl0eU90aGVyLHRoaXMuYnVmZmVyPW5ldyBBcnJheShpKjUpO2JyZWFrO2RlZmF1bHQ6cj10aGlzLl9sZXJwLHM9dGhpcy5fbGVycEFkZGl0aXZlLG89dGhpcy5fc2V0QWRkaXRpdmVJZGVudGl0eU51bWVyaWMsdGhpcy5idWZmZXI9bmV3IEZsb2F0NjRBcnJheShpKjUpfXRoaXMuX21peEJ1ZmZlclJlZ2lvbj1yLHRoaXMuX21peEJ1ZmZlclJlZ2lvbkFkZGl0aXZlPXMsdGhpcy5fc2V0SWRlbnRpdHk9byx0aGlzLl9vcmlnSW5kZXg9Myx0aGlzLl9hZGRJbmRleD00LHRoaXMuY3VtdWxhdGl2ZVdlaWdodD0wLHRoaXMuY3VtdWxhdGl2ZVdlaWdodEFkZGl0aXZlPTAsdGhpcy51c2VDb3VudD0wLHRoaXMucmVmZXJlbmNlQ291bnQ9MH1hY2N1bXVsYXRlKHQsZSl7bGV0IGk9dGhpcy5idWZmZXIscj10aGlzLnZhbHVlU2l6ZSxzPXQqcityLG89dGhpcy5jdW11bGF0aXZlV2VpZ2h0O2lmKG89PT0wKXtmb3IobGV0IGE9MDthIT09cjsrK2EpaVtzK2FdPWlbYV07bz1lfWVsc2V7bys9ZTtsZXQgYT1lL287dGhpcy5fbWl4QnVmZmVyUmVnaW9uKGkscywwLGEscil9dGhpcy5jdW11bGF0aXZlV2VpZ2h0PW99YWNjdW11bGF0ZUFkZGl0aXZlKHQpe2xldCBlPXRoaXMuYnVmZmVyLGk9dGhpcy52YWx1ZVNpemUscj1pKnRoaXMuX2FkZEluZGV4O3RoaXMuY3VtdWxhdGl2ZVdlaWdodEFkZGl0aXZlPT09MCYmdGhpcy5fc2V0SWRlbnRpdHkoKSx0aGlzLl9taXhCdWZmZXJSZWdpb25BZGRpdGl2ZShlLHIsMCx0LGkpLHRoaXMuY3VtdWxhdGl2ZVdlaWdodEFkZGl0aXZlKz10fWFwcGx5KHQpe2xldCBlPXRoaXMudmFsdWVTaXplLGk9dGhpcy5idWZmZXIscj10KmUrZSxzPXRoaXMuY3VtdWxhdGl2ZVdlaWdodCxvPXRoaXMuY3VtdWxhdGl2ZVdlaWdodEFkZGl0aXZlLGE9dGhpcy5iaW5kaW5nO2lmKHRoaXMuY3VtdWxhdGl2ZVdlaWdodD0wLHRoaXMuY3VtdWxhdGl2ZVdlaWdodEFkZGl0aXZlPTAsczwxKXtsZXQgbD1lKnRoaXMuX29yaWdJbmRleDt0aGlzLl9taXhCdWZmZXJSZWdpb24oaSxyLGwsMS1zLGUpfW8+MCYmdGhpcy5fbWl4QnVmZmVyUmVnaW9uQWRkaXRpdmUoaSxyLHRoaXMuX2FkZEluZGV4KmUsMSxlKTtmb3IobGV0IGw9ZSxjPWUrZTtsIT09YzsrK2wpaWYoaVtsXSE9PWlbbCtlXSl7YS5zZXRWYWx1ZShpLHIpO2JyZWFrfX1zYXZlT3JpZ2luYWxTdGF0ZSgpe2xldCB0PXRoaXMuYmluZGluZyxlPXRoaXMuYnVmZmVyLGk9dGhpcy52YWx1ZVNpemUscj1pKnRoaXMuX29yaWdJbmRleDt0LmdldFZhbHVlKGUscik7Zm9yKGxldCBzPWksbz1yO3MhPT1vOysrcyllW3NdPWVbcitzJWldO3RoaXMuX3NldElkZW50aXR5KCksdGhpcy5jdW11bGF0aXZlV2VpZ2h0PTAsdGhpcy5jdW11bGF0aXZlV2VpZ2h0QWRkaXRpdmU9MH1yZXN0b3JlT3JpZ2luYWxTdGF0ZSgpe2xldCB0PXRoaXMudmFsdWVTaXplKjM7dGhpcy5iaW5kaW5nLnNldFZhbHVlKHRoaXMuYnVmZmVyLHQpfV9zZXRBZGRpdGl2ZUlkZW50aXR5TnVtZXJpYygpe2xldCB0PXRoaXMuX2FkZEluZGV4KnRoaXMudmFsdWVTaXplLGU9dCt0aGlzLnZhbHVlU2l6ZTtmb3IobGV0IGk9dDtpPGU7aSsrKXRoaXMuYnVmZmVyW2ldPTB9X3NldEFkZGl0aXZlSWRlbnRpdHlRdWF0ZXJuaW9uKCl7dGhpcy5fc2V0QWRkaXRpdmVJZGVudGl0eU51bWVyaWMoKSx0aGlzLmJ1ZmZlclt0aGlzLl9hZGRJbmRleCp0aGlzLnZhbHVlU2l6ZSszXT0xfV9zZXRBZGRpdGl2ZUlkZW50aXR5T3RoZXIoKXtsZXQgdD10aGlzLl9vcmlnSW5kZXgqdGhpcy52YWx1ZVNpemUsZT10aGlzLl9hZGRJbmRleCp0aGlzLnZhbHVlU2l6ZTtmb3IobGV0IGk9MDtpPHRoaXMudmFsdWVTaXplO2krKyl0aGlzLmJ1ZmZlcltlK2ldPXRoaXMuYnVmZmVyW3QraV19X3NlbGVjdCh0LGUsaSxyLHMpe2lmKHI+PS41KWZvcihsZXQgbz0wO28hPT1zOysrbyl0W2Urb109dFtpK29dfV9zbGVycCh0LGUsaSxyKXtFZS5zbGVycEZsYXQodCxlLHQsZSx0LGkscil9X3NsZXJwQWRkaXRpdmUodCxlLGkscixzKXtsZXQgbz10aGlzLl93b3JrSW5kZXgqcztFZS5tdWx0aXBseVF1YXRlcm5pb25zRmxhdCh0LG8sdCxlLHQsaSksRWUuc2xlcnBGbGF0KHQsZSx0LGUsdCxvLHIpfV9sZXJwKHQsZSxpLHIscyl7bGV0IG89MS1yO2ZvcihsZXQgYT0wO2EhPT1zOysrYSl7bGV0IGw9ZSthO3RbbF09dFtsXSpvK3RbaSthXSpyfX1fbGVycEFkZGl0aXZlKHQsZSxpLHIscyl7Zm9yKGxldCBvPTA7byE9PXM7KytvKXtsZXQgYT1lK287dFthXT10W2FdK3RbaStvXSpyfX19LGVmPSJcXFtcXF1cXC46XFwvIixmVD1uZXcgUmVnRXhwKCJbIitlZisiXSIsImciKSxuZj0iW14iK2VmKyJdIixkVD0iW14iK2VmLnJlcGxhY2UoIlxcLiIsIiIpKyJdIixwVD0vKCg/OldDK1tcLzpdKSopLy5zb3VyY2UucmVwbGFjZSgiV0MiLG5mKSxtVD0vKFdDT0QrKT8vLnNvdXJjZS5yZXBsYWNlKCJXQ09EIixkVCksZ1Q9Lyg/OlwuKFdDKykoPzpcWyguKylcXSk/KT8vLnNvdXJjZS5yZXBsYWNlKCJXQyIsbmYpLHhUPS9cLihXQyspKD86XFsoLispXF0pPy8uc291cmNlLnJlcGxhY2UoIldDIixuZikseVQ9bmV3IFJlZ0V4cCgiXiIrcFQrbVQrZ1QreFQrIiQiKSx2VD1bIm1hdGVyaWFsIiwibWF0ZXJpYWxzIiwiYm9uZXMiXSxIaD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7bGV0IHI9aXx8TnQucGFyc2VUcmFja05hbWUoZSk7dGhpcy5fdGFyZ2V0R3JvdXA9dCx0aGlzLl9iaW5kaW5ncz10LnN1YnNjcmliZV8oZSxyKX1nZXRWYWx1ZSh0LGUpe3RoaXMuYmluZCgpO2xldCBpPXRoaXMuX3RhcmdldEdyb3VwLm5DYWNoZWRPYmplY3RzXyxyPXRoaXMuX2JpbmRpbmdzW2ldO3IhPT12b2lkIDAmJnIuZ2V0VmFsdWUodCxlKX1zZXRWYWx1ZSh0LGUpe2xldCBpPXRoaXMuX2JpbmRpbmdzO2ZvcihsZXQgcj10aGlzLl90YXJnZXRHcm91cC5uQ2FjaGVkT2JqZWN0c18scz1pLmxlbmd0aDtyIT09czsrK3IpaVtyXS5zZXRWYWx1ZSh0LGUpfWJpbmQoKXtsZXQgdD10aGlzLl9iaW5kaW5ncztmb3IobGV0IGU9dGhpcy5fdGFyZ2V0R3JvdXAubkNhY2hlZE9iamVjdHNfLGk9dC5sZW5ndGg7ZSE9PWk7KytlKXRbZV0uYmluZCgpfXVuYmluZCgpe2xldCB0PXRoaXMuX2JpbmRpbmdzO2ZvcihsZXQgZT10aGlzLl90YXJnZXRHcm91cC5uQ2FjaGVkT2JqZWN0c18saT10Lmxlbmd0aDtlIT09aTsrK2UpdFtlXS51bmJpbmQoKX19LE50PWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLnBhdGg9ZSx0aGlzLnBhcnNlZFBhdGg9aXx8TnQucGFyc2VUcmFja05hbWUoZSksdGhpcy5ub2RlPU50LmZpbmROb2RlKHQsdGhpcy5wYXJzZWRQYXRoLm5vZGVOYW1lKXx8dCx0aGlzLnJvb3ROb2RlPXQsdGhpcy5nZXRWYWx1ZT10aGlzLl9nZXRWYWx1ZV91bmJvdW5kLHRoaXMuc2V0VmFsdWU9dGhpcy5fc2V0VmFsdWVfdW5ib3VuZH1zdGF0aWMgY3JlYXRlKHQsZSxpKXtyZXR1cm4gdCYmdC5pc0FuaW1hdGlvbk9iamVjdEdyb3VwP25ldyBOdC5Db21wb3NpdGUodCxlLGkpOm5ldyBOdCh0LGUsaSl9c3RhdGljIHNhbml0aXplTm9kZU5hbWUodCl7cmV0dXJuIHQucmVwbGFjZSgvXHMvZywiXyIpLnJlcGxhY2UoZlQsIiIpfXN0YXRpYyBwYXJzZVRyYWNrTmFtZSh0KXtsZXQgZT15VC5leGVjKHQpO2lmKCFlKXRocm93IG5ldyBFcnJvcigiUHJvcGVydHlCaW5kaW5nOiBDYW5ub3QgcGFyc2UgdHJhY2tOYW1lOiAiK3QpO2xldCBpPXtub2RlTmFtZTplWzJdLG9iamVjdE5hbWU6ZVszXSxvYmplY3RJbmRleDplWzRdLHByb3BlcnR5TmFtZTplWzVdLHByb3BlcnR5SW5kZXg6ZVs2XX0scj1pLm5vZGVOYW1lJiZpLm5vZGVOYW1lLmxhc3RJbmRleE9mKCIuIik7aWYociE9PXZvaWQgMCYmciE9PS0xKXtsZXQgcz1pLm5vZGVOYW1lLnN1YnN0cmluZyhyKzEpO3ZULmluZGV4T2YocykhPT0tMSYmKGkubm9kZU5hbWU9aS5ub2RlTmFtZS5zdWJzdHJpbmcoMCxyKSxpLm9iamVjdE5hbWU9cyl9aWYoaS5wcm9wZXJ0eU5hbWU9PT1udWxsfHxpLnByb3BlcnR5TmFtZS5sZW5ndGg9PT0wKXRocm93IG5ldyBFcnJvcigiUHJvcGVydHlCaW5kaW5nOiBjYW4gbm90IHBhcnNlIHByb3BlcnR5TmFtZSBmcm9tIHRyYWNrTmFtZTogIit0KTtyZXR1cm4gaX1zdGF0aWMgZmluZE5vZGUodCxlKXtpZighZXx8ZT09PSIifHxlPT09Ii4ifHxlPT09LTF8fGU9PT10Lm5hbWV8fGU9PT10LnV1aWQpcmV0dXJuIHQ7aWYodC5za2VsZXRvbil7bGV0IGk9dC5za2VsZXRvbi5nZXRCb25lQnlOYW1lKGUpO2lmKGkhPT12b2lkIDApcmV0dXJuIGl9aWYodC5jaGlsZHJlbil7bGV0IGk9ZnVuY3Rpb24ocyl7Zm9yKGxldCBvPTA7bzxzLmxlbmd0aDtvKyspe2xldCBhPXNbb107aWYoYS5uYW1lPT09ZXx8YS51dWlkPT09ZSlyZXR1cm4gYTtsZXQgbD1pKGEuY2hpbGRyZW4pO2lmKGwpcmV0dXJuIGx9cmV0dXJuIG51bGx9LHI9aSh0LmNoaWxkcmVuKTtpZihyKXJldHVybiByfXJldHVybiBudWxsfV9nZXRWYWx1ZV91bmF2YWlsYWJsZSgpe31fc2V0VmFsdWVfdW5hdmFpbGFibGUoKXt9X2dldFZhbHVlX2RpcmVjdCh0LGUpe3RbZV09dGhpcy50YXJnZXRPYmplY3RbdGhpcy5wcm9wZXJ0eU5hbWVdfV9nZXRWYWx1ZV9hcnJheSh0LGUpe2xldCBpPXRoaXMucmVzb2x2ZWRQcm9wZXJ0eTtmb3IobGV0IHI9MCxzPWkubGVuZ3RoO3IhPT1zOysrcil0W2UrK109aVtyXX1fZ2V0VmFsdWVfYXJyYXlFbGVtZW50KHQsZSl7dFtlXT10aGlzLnJlc29sdmVkUHJvcGVydHlbdGhpcy5wcm9wZXJ0eUluZGV4XX1fZ2V0VmFsdWVfdG9BcnJheSh0LGUpe3RoaXMucmVzb2x2ZWRQcm9wZXJ0eS50b0FycmF5KHQsZSl9X3NldFZhbHVlX2RpcmVjdCh0LGUpe3RoaXMudGFyZ2V0T2JqZWN0W3RoaXMucHJvcGVydHlOYW1lXT10W2VdfV9zZXRWYWx1ZV9kaXJlY3Rfc2V0TmVlZHNVcGRhdGUodCxlKXt0aGlzLnRhcmdldE9iamVjdFt0aGlzLnByb3BlcnR5TmFtZV09dFtlXSx0aGlzLnRhcmdldE9iamVjdC5uZWVkc1VwZGF0ZT0hMH1fc2V0VmFsdWVfZGlyZWN0X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGUodCxlKXt0aGlzLnRhcmdldE9iamVjdFt0aGlzLnByb3BlcnR5TmFtZV09dFtlXSx0aGlzLnRhcmdldE9iamVjdC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlPSEwfV9zZXRWYWx1ZV9hcnJheSh0LGUpe2xldCBpPXRoaXMucmVzb2x2ZWRQcm9wZXJ0eTtmb3IobGV0IHI9MCxzPWkubGVuZ3RoO3IhPT1zOysrcilpW3JdPXRbZSsrXX1fc2V0VmFsdWVfYXJyYXlfc2V0TmVlZHNVcGRhdGUodCxlKXtsZXQgaT10aGlzLnJlc29sdmVkUHJvcGVydHk7Zm9yKGxldCByPTAscz1pLmxlbmd0aDtyIT09czsrK3IpaVtyXT10W2UrK107dGhpcy50YXJnZXRPYmplY3QubmVlZHNVcGRhdGU9ITB9X3NldFZhbHVlX2FycmF5X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGUodCxlKXtsZXQgaT10aGlzLnJlc29sdmVkUHJvcGVydHk7Zm9yKGxldCByPTAscz1pLmxlbmd0aDtyIT09czsrK3IpaVtyXT10W2UrK107dGhpcy50YXJnZXRPYmplY3QubWF0cml4V29ybGROZWVkc1VwZGF0ZT0hMH1fc2V0VmFsdWVfYXJyYXlFbGVtZW50KHQsZSl7dGhpcy5yZXNvbHZlZFByb3BlcnR5W3RoaXMucHJvcGVydHlJbmRleF09dFtlXX1fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE5lZWRzVXBkYXRlKHQsZSl7dGhpcy5yZXNvbHZlZFByb3BlcnR5W3RoaXMucHJvcGVydHlJbmRleF09dFtlXSx0aGlzLnRhcmdldE9iamVjdC5uZWVkc1VwZGF0ZT0hMH1fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGUodCxlKXt0aGlzLnJlc29sdmVkUHJvcGVydHlbdGhpcy5wcm9wZXJ0eUluZGV4XT10W2VdLHRoaXMudGFyZ2V0T2JqZWN0Lm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9ITB9X3NldFZhbHVlX2Zyb21BcnJheSh0LGUpe3RoaXMucmVzb2x2ZWRQcm9wZXJ0eS5mcm9tQXJyYXkodCxlKX1fc2V0VmFsdWVfZnJvbUFycmF5X3NldE5lZWRzVXBkYXRlKHQsZSl7dGhpcy5yZXNvbHZlZFByb3BlcnR5LmZyb21BcnJheSh0LGUpLHRoaXMudGFyZ2V0T2JqZWN0Lm5lZWRzVXBkYXRlPSEwfV9zZXRWYWx1ZV9mcm9tQXJyYXlfc2V0TWF0cml4V29ybGROZWVkc1VwZGF0ZSh0LGUpe3RoaXMucmVzb2x2ZWRQcm9wZXJ0eS5mcm9tQXJyYXkodCxlKSx0aGlzLnRhcmdldE9iamVjdC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlPSEwfV9nZXRWYWx1ZV91bmJvdW5kKHQsZSl7dGhpcy5iaW5kKCksdGhpcy5nZXRWYWx1ZSh0LGUpfV9zZXRWYWx1ZV91bmJvdW5kKHQsZSl7dGhpcy5iaW5kKCksdGhpcy5zZXRWYWx1ZSh0LGUpfWJpbmQoKXtsZXQgdD10aGlzLm5vZGUsZT10aGlzLnBhcnNlZFBhdGgsaT1lLm9iamVjdE5hbWUscj1lLnByb3BlcnR5TmFtZSxzPWUucHJvcGVydHlJbmRleDtpZih0fHwodD1OdC5maW5kTm9kZSh0aGlzLnJvb3ROb2RlLGUubm9kZU5hbWUpfHx0aGlzLnJvb3ROb2RlLHRoaXMubm9kZT10KSx0aGlzLmdldFZhbHVlPXRoaXMuX2dldFZhbHVlX3VuYXZhaWxhYmxlLHRoaXMuc2V0VmFsdWU9dGhpcy5fc2V0VmFsdWVfdW5hdmFpbGFibGUsIXQpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlByb3BlcnR5QmluZGluZzogVHJ5aW5nIHRvIHVwZGF0ZSBub2RlIGZvciB0cmFjazogIit0aGlzLnBhdGgrIiBidXQgaXQgd2Fzbid0IGZvdW5kLiIpO3JldHVybn1pZihpKXtsZXQgYz1lLm9iamVjdEluZGV4O3N3aXRjaChpKXtjYXNlIm1hdGVyaWFscyI6aWYoIXQubWF0ZXJpYWwpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlByb3BlcnR5QmluZGluZzogQ2FuIG5vdCBiaW5kIHRvIG1hdGVyaWFsIGFzIG5vZGUgZG9lcyBub3QgaGF2ZSBhIG1hdGVyaWFsLiIsdGhpcyk7cmV0dXJufWlmKCF0Lm1hdGVyaWFsLm1hdGVyaWFscyl7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbWF0ZXJpYWwubWF0ZXJpYWxzIGFzIG5vZGUubWF0ZXJpYWwgZG9lcyBub3QgaGF2ZSBhIG1hdGVyaWFscyBhcnJheS4iLHRoaXMpO3JldHVybn10PXQubWF0ZXJpYWwubWF0ZXJpYWxzO2JyZWFrO2Nhc2UiYm9uZXMiOmlmKCF0LnNrZWxldG9uKXtjb25zb2xlLmVycm9yKCJUSFJFRS5Qcm9wZXJ0eUJpbmRpbmc6IENhbiBub3QgYmluZCB0byBib25lcyBhcyBub2RlIGRvZXMgbm90IGhhdmUgYSBza2VsZXRvbi4iLHRoaXMpO3JldHVybn10PXQuc2tlbGV0b24uYm9uZXM7Zm9yKGxldCB1PTA7dTx0Lmxlbmd0aDt1KyspaWYodFt1XS5uYW1lPT09Yyl7Yz11O2JyZWFrfWJyZWFrO2RlZmF1bHQ6aWYodFtpXT09PXZvaWQgMCl7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gb2JqZWN0TmFtZSBvZiBub2RlIHVuZGVmaW5lZC4iLHRoaXMpO3JldHVybn10PXRbaV19aWYoYyE9PXZvaWQgMCl7aWYodFtjXT09PXZvaWQgMCl7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBUcnlpbmcgdG8gYmluZCB0byBvYmplY3RJbmRleCBvZiBvYmplY3ROYW1lLCBidXQgaXMgdW5kZWZpbmVkLiIsdGhpcyx0KTtyZXR1cm59dD10W2NdfX1sZXQgbz10W3JdO2lmKG89PT12b2lkIDApe2xldCBjPWUubm9kZU5hbWU7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBUcnlpbmcgdG8gdXBkYXRlIHByb3BlcnR5IGZvciB0cmFjazogIitjKyIuIityKyIgYnV0IGl0IHdhc24ndCBmb3VuZC4iLHQpO3JldHVybn1sZXQgYT10aGlzLlZlcnNpb25pbmcuTm9uZTt0aGlzLnRhcmdldE9iamVjdD10LHQubmVlZHNVcGRhdGUhPT12b2lkIDA/YT10aGlzLlZlcnNpb25pbmcuTmVlZHNVcGRhdGU6dC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlIT09dm9pZCAwJiYoYT10aGlzLlZlcnNpb25pbmcuTWF0cml4V29ybGROZWVkc1VwZGF0ZSk7bGV0IGw9dGhpcy5CaW5kaW5nVHlwZS5EaXJlY3Q7aWYocyE9PXZvaWQgMCl7aWYocj09PSJtb3JwaFRhcmdldEluZmx1ZW5jZXMiKXtpZighdC5nZW9tZXRyeSl7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbW9ycGhUYXJnZXRJbmZsdWVuY2VzIGJlY2F1c2Ugbm9kZSBkb2VzIG5vdCBoYXZlIGEgZ2VvbWV0cnkuIix0aGlzKTtyZXR1cm59aWYodC5nZW9tZXRyeS5pc0J1ZmZlckdlb21ldHJ5KXtpZighdC5nZW9tZXRyeS5tb3JwaEF0dHJpYnV0ZXMpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlByb3BlcnR5QmluZGluZzogQ2FuIG5vdCBiaW5kIHRvIG1vcnBoVGFyZ2V0SW5mbHVlbmNlcyBiZWNhdXNlIG5vZGUgZG9lcyBub3QgaGF2ZSBhIGdlb21ldHJ5Lm1vcnBoQXR0cmlidXRlcy4iLHRoaXMpO3JldHVybn10Lm1vcnBoVGFyZ2V0RGljdGlvbmFyeVtzXSE9PXZvaWQgMCYmKHM9dC5tb3JwaFRhcmdldERpY3Rpb25hcnlbc10pfWVsc2V7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbW9ycGhUYXJnZXRJbmZsdWVuY2VzIG9uIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iLHRoaXMpO3JldHVybn19bD10aGlzLkJpbmRpbmdUeXBlLkFycmF5RWxlbWVudCx0aGlzLnJlc29sdmVkUHJvcGVydHk9byx0aGlzLnByb3BlcnR5SW5kZXg9c31lbHNlIG8uZnJvbUFycmF5IT09dm9pZCAwJiZvLnRvQXJyYXkhPT12b2lkIDA/KGw9dGhpcy5CaW5kaW5nVHlwZS5IYXNGcm9tVG9BcnJheSx0aGlzLnJlc29sdmVkUHJvcGVydHk9byk6QXJyYXkuaXNBcnJheShvKT8obD10aGlzLkJpbmRpbmdUeXBlLkVudGlyZUFycmF5LHRoaXMucmVzb2x2ZWRQcm9wZXJ0eT1vKTp0aGlzLnByb3BlcnR5TmFtZT1yO3RoaXMuZ2V0VmFsdWU9dGhpcy5HZXR0ZXJCeUJpbmRpbmdUeXBlW2xdLHRoaXMuc2V0VmFsdWU9dGhpcy5TZXR0ZXJCeUJpbmRpbmdUeXBlQW5kVmVyc2lvbmluZ1tsXVthXX11bmJpbmQoKXt0aGlzLm5vZGU9bnVsbCx0aGlzLmdldFZhbHVlPXRoaXMuX2dldFZhbHVlX3VuYm91bmQsdGhpcy5zZXRWYWx1ZT10aGlzLl9zZXRWYWx1ZV91bmJvdW5kfX07TnQuQ29tcG9zaXRlPUhoO050LnByb3RvdHlwZS5CaW5kaW5nVHlwZT17RGlyZWN0OjAsRW50aXJlQXJyYXk6MSxBcnJheUVsZW1lbnQ6MixIYXNGcm9tVG9BcnJheTozfTtOdC5wcm90b3R5cGUuVmVyc2lvbmluZz17Tm9uZTowLE5lZWRzVXBkYXRlOjEsTWF0cml4V29ybGROZWVkc1VwZGF0ZToyfTtOdC5wcm90b3R5cGUuR2V0dGVyQnlCaW5kaW5nVHlwZT1bTnQucHJvdG90eXBlLl9nZXRWYWx1ZV9kaXJlY3QsTnQucHJvdG90eXBlLl9nZXRWYWx1ZV9hcnJheSxOdC5wcm90b3R5cGUuX2dldFZhbHVlX2FycmF5RWxlbWVudCxOdC5wcm90b3R5cGUuX2dldFZhbHVlX3RvQXJyYXldO050LnByb3RvdHlwZS5TZXR0ZXJCeUJpbmRpbmdUeXBlQW5kVmVyc2lvbmluZz1bW050LnByb3RvdHlwZS5fc2V0VmFsdWVfZGlyZWN0LE50LnByb3RvdHlwZS5fc2V0VmFsdWVfZGlyZWN0X3NldE5lZWRzVXBkYXRlLE50LnByb3RvdHlwZS5fc2V0VmFsdWVfZGlyZWN0X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGVdLFtOdC5wcm90b3R5cGUuX3NldFZhbHVlX2FycmF5LE50LnByb3RvdHlwZS5fc2V0VmFsdWVfYXJyYXlfc2V0TmVlZHNVcGRhdGUsTnQucHJvdG90eXBlLl9zZXRWYWx1ZV9hcnJheV9zZXRNYXRyaXhXb3JsZE5lZWRzVXBkYXRlXSxbTnQucHJvdG90eXBlLl9zZXRWYWx1ZV9hcnJheUVsZW1lbnQsTnQucHJvdG90eXBlLl9zZXRWYWx1ZV9hcnJheUVsZW1lbnRfc2V0TmVlZHNVcGRhdGUsTnQucHJvdG90eXBlLl9zZXRWYWx1ZV9hcnJheUVsZW1lbnRfc2V0TWF0cml4V29ybGROZWVkc1VwZGF0ZV0sW050LnByb3RvdHlwZS5fc2V0VmFsdWVfZnJvbUFycmF5LE50LnByb3RvdHlwZS5fc2V0VmFsdWVfZnJvbUFycmF5X3NldE5lZWRzVXBkYXRlLE50LnByb3RvdHlwZS5fc2V0VmFsdWVfZnJvbUFycmF5X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGVdXTt2YXIgVmg9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnV1aWQ9dG4oKSx0aGlzLl9vYmplY3RzPUFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGFyZ3VtZW50cyksdGhpcy5uQ2FjaGVkT2JqZWN0c189MDtsZXQgdD17fTt0aGlzLl9pbmRpY2VzQnlVVUlEPXQ7Zm9yKGxldCBpPTAscj1hcmd1bWVudHMubGVuZ3RoO2khPT1yOysraSl0W2FyZ3VtZW50c1tpXS51dWlkXT1pO3RoaXMuX3BhdGhzPVtdLHRoaXMuX3BhcnNlZFBhdGhzPVtdLHRoaXMuX2JpbmRpbmdzPVtdLHRoaXMuX2JpbmRpbmdzSW5kaWNlc0J5UGF0aD17fTtsZXQgZT10aGlzO3RoaXMuc3RhdHM9e29iamVjdHM6e2dldCB0b3RhbCgpe3JldHVybiBlLl9vYmplY3RzLmxlbmd0aH0sZ2V0IGluVXNlKCl7cmV0dXJuIHRoaXMudG90YWwtZS5uQ2FjaGVkT2JqZWN0c199fSxnZXQgYmluZGluZ3NQZXJPYmplY3QoKXtyZXR1cm4gZS5fYmluZGluZ3MubGVuZ3RofX19YWRkKCl7bGV0IHQ9dGhpcy5fb2JqZWN0cyxlPXRoaXMuX2luZGljZXNCeVVVSUQsaT10aGlzLl9wYXRocyxyPXRoaXMuX3BhcnNlZFBhdGhzLHM9dGhpcy5fYmluZGluZ3Msbz1zLmxlbmd0aCxhLGw9dC5sZW5ndGgsYz10aGlzLm5DYWNoZWRPYmplY3RzXztmb3IobGV0IHU9MCxoPWFyZ3VtZW50cy5sZW5ndGg7dSE9PWg7Kyt1KXtsZXQgZj1hcmd1bWVudHNbdV0sZD1mLnV1aWQsZz1lW2RdO2lmKGc9PT12b2lkIDApe2c9bCsrLGVbZF09Zyx0LnB1c2goZik7Zm9yKGxldCB4PTAsdj1vO3ghPT12OysreClzW3hdLnB1c2gobmV3IE50KGYsaVt4XSxyW3hdKSl9ZWxzZSBpZihnPGMpe2E9dFtnXTtsZXQgeD0tLWMsdj10W3hdO2Vbdi51dWlkXT1nLHRbZ109dixlW2RdPXgsdFt4XT1mO2ZvcihsZXQgbT0wLHA9bzttIT09cDsrK20pe2xldCBiPXNbbV0sXz1iW3hdLFM9YltnXTtiW2ddPV8sUz09PXZvaWQgMCYmKFM9bmV3IE50KGYsaVttXSxyW21dKSksYlt4XT1TfX1lbHNlIHRbZ10hPT1hJiZjb25zb2xlLmVycm9yKCJUSFJFRS5BbmltYXRpb25PYmplY3RHcm91cDogRGlmZmVyZW50IG9iamVjdHMgd2l0aCB0aGUgc2FtZSBVVUlEIGRldGVjdGVkLiBDbGVhbiB0aGUgY2FjaGVzIG9yIHJlY3JlYXRlIHlvdXIgaW5mcmFzdHJ1Y3R1cmUgd2hlbiByZWxvYWRpbmcgc2NlbmVzLiIpfXRoaXMubkNhY2hlZE9iamVjdHNfPWN9cmVtb3ZlKCl7bGV0IHQ9dGhpcy5fb2JqZWN0cyxlPXRoaXMuX2luZGljZXNCeVVVSUQsaT10aGlzLl9iaW5kaW5ncyxyPWkubGVuZ3RoLHM9dGhpcy5uQ2FjaGVkT2JqZWN0c187Zm9yKGxldCBvPTAsYT1hcmd1bWVudHMubGVuZ3RoO28hPT1hOysrbyl7bGV0IGw9YXJndW1lbnRzW29dLGM9bC51dWlkLHU9ZVtjXTtpZih1IT09dm9pZCAwJiZ1Pj1zKXtsZXQgaD1zKyssZj10W2hdO2VbZi51dWlkXT11LHRbdV09ZixlW2NdPWgsdFtoXT1sO2ZvcihsZXQgZD0wLGc9cjtkIT09ZzsrK2Qpe2xldCB4PWlbZF0sdj14W2hdLG09eFt1XTt4W3VdPXYseFtoXT1tfX19dGhpcy5uQ2FjaGVkT2JqZWN0c189c311bmNhY2hlKCl7bGV0IHQ9dGhpcy5fb2JqZWN0cyxlPXRoaXMuX2luZGljZXNCeVVVSUQsaT10aGlzLl9iaW5kaW5ncyxyPWkubGVuZ3RoLHM9dGhpcy5uQ2FjaGVkT2JqZWN0c18sbz10Lmxlbmd0aDtmb3IobGV0IGE9MCxsPWFyZ3VtZW50cy5sZW5ndGg7YSE9PWw7KythKXtsZXQgYz1hcmd1bWVudHNbYV0sdT1jLnV1aWQsaD1lW3VdO2lmKGghPT12b2lkIDApaWYoZGVsZXRlIGVbdV0saDxzKXtsZXQgZj0tLXMsZD10W2ZdLGc9LS1vLHg9dFtnXTtlW2QudXVpZF09aCx0W2hdPWQsZVt4LnV1aWRdPWYsdFtmXT14LHQucG9wKCk7Zm9yKGxldCB2PTAsbT1yO3YhPT1tOysrdil7bGV0IHA9aVt2XSxiPXBbZl0sXz1wW2ddO3BbaF09YixwW2ZdPV8scC5wb3AoKX19ZWxzZXtsZXQgZj0tLW8sZD10W2ZdO2Y+MCYmKGVbZC51dWlkXT1oKSx0W2hdPWQsdC5wb3AoKTtmb3IobGV0IGc9MCx4PXI7ZyE9PXg7KytnKXtsZXQgdj1pW2ddO3ZbaF09dltmXSx2LnBvcCgpfX19dGhpcy5uQ2FjaGVkT2JqZWN0c189c31zdWJzY3JpYmVfKHQsZSl7bGV0IGk9dGhpcy5fYmluZGluZ3NJbmRpY2VzQnlQYXRoLHI9aVt0XSxzPXRoaXMuX2JpbmRpbmdzO2lmKHIhPT12b2lkIDApcmV0dXJuIHNbcl07bGV0IG89dGhpcy5fcGF0aHMsYT10aGlzLl9wYXJzZWRQYXRocyxsPXRoaXMuX29iamVjdHMsYz1sLmxlbmd0aCx1PXRoaXMubkNhY2hlZE9iamVjdHNfLGg9bmV3IEFycmF5KGMpO3I9cy5sZW5ndGgsaVt0XT1yLG8ucHVzaCh0KSxhLnB1c2goZSkscy5wdXNoKGgpO2ZvcihsZXQgZj11LGQ9bC5sZW5ndGg7ZiE9PWQ7KytmKXtsZXQgZz1sW2ZdO2hbZl09bmV3IE50KGcsdCxlKX1yZXR1cm4gaH11bnN1YnNjcmliZV8odCl7bGV0IGU9dGhpcy5fYmluZGluZ3NJbmRpY2VzQnlQYXRoLGk9ZVt0XTtpZihpIT09dm9pZCAwKXtsZXQgcj10aGlzLl9wYXRocyxzPXRoaXMuX3BhcnNlZFBhdGhzLG89dGhpcy5fYmluZGluZ3MsYT1vLmxlbmd0aC0xLGw9b1thXSxjPXRbYV07ZVtjXT1pLG9baV09bCxvLnBvcCgpLHNbaV09c1thXSxzLnBvcCgpLHJbaV09clthXSxyLnBvcCgpfX19O1ZoLnByb3RvdHlwZS5pc0FuaW1hdGlvbk9iamVjdEdyb3VwPSEwO3ZhciBHaD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaT1udWxsLHI9ZS5ibGVuZE1vZGUpe3RoaXMuX21peGVyPXQsdGhpcy5fY2xpcD1lLHRoaXMuX2xvY2FsUm9vdD1pLHRoaXMuYmxlbmRNb2RlPXI7bGV0IHM9ZS50cmFja3Msbz1zLmxlbmd0aCxhPW5ldyBBcnJheShvKSxsPXtlbmRpbmdTdGFydDpOcixlbmRpbmdFbmQ6TnJ9O2ZvcihsZXQgYz0wO2MhPT1vOysrYyl7bGV0IHU9c1tjXS5jcmVhdGVJbnRlcnBvbGFudChudWxsKTthW2NdPXUsdS5zZXR0aW5ncz1sfXRoaXMuX2ludGVycG9sYW50U2V0dGluZ3M9bCx0aGlzLl9pbnRlcnBvbGFudHM9YSx0aGlzLl9wcm9wZXJ0eUJpbmRpbmdzPW5ldyBBcnJheShvKSx0aGlzLl9jYWNoZUluZGV4PW51bGwsdGhpcy5fYnlDbGlwQ2FjaGVJbmRleD1udWxsLHRoaXMuX3RpbWVTY2FsZUludGVycG9sYW50PW51bGwsdGhpcy5fd2VpZ2h0SW50ZXJwb2xhbnQ9bnVsbCx0aGlzLmxvb3A9WncsdGhpcy5fbG9vcENvdW50PS0xLHRoaXMuX3N0YXJ0VGltZT1udWxsLHRoaXMudGltZT0wLHRoaXMudGltZVNjYWxlPTEsdGhpcy5fZWZmZWN0aXZlVGltZVNjYWxlPTEsdGhpcy53ZWlnaHQ9MSx0aGlzLl9lZmZlY3RpdmVXZWlnaHQ9MSx0aGlzLnJlcGV0aXRpb25zPTEvMCx0aGlzLnBhdXNlZD0hMSx0aGlzLmVuYWJsZWQ9ITAsdGhpcy5jbGFtcFdoZW5GaW5pc2hlZD0hMSx0aGlzLnplcm9TbG9wZUF0U3RhcnQ9ITAsdGhpcy56ZXJvU2xvcGVBdEVuZD0hMH1wbGF5KCl7cmV0dXJuIHRoaXMuX21peGVyLl9hY3RpdmF0ZUFjdGlvbih0aGlzKSx0aGlzfXN0b3AoKXtyZXR1cm4gdGhpcy5fbWl4ZXIuX2RlYWN0aXZhdGVBY3Rpb24odGhpcyksdGhpcy5yZXNldCgpfXJlc2V0KCl7cmV0dXJuIHRoaXMucGF1c2VkPSExLHRoaXMuZW5hYmxlZD0hMCx0aGlzLnRpbWU9MCx0aGlzLl9sb29wQ291bnQ9LTEsdGhpcy5fc3RhcnRUaW1lPW51bGwsdGhpcy5zdG9wRmFkaW5nKCkuc3RvcFdhcnBpbmcoKX1pc1J1bm5pbmcoKXtyZXR1cm4gdGhpcy5lbmFibGVkJiYhdGhpcy5wYXVzZWQmJnRoaXMudGltZVNjYWxlIT09MCYmdGhpcy5fc3RhcnRUaW1lPT09bnVsbCYmdGhpcy5fbWl4ZXIuX2lzQWN0aXZlQWN0aW9uKHRoaXMpfWlzU2NoZWR1bGVkKCl7cmV0dXJuIHRoaXMuX21peGVyLl9pc0FjdGl2ZUFjdGlvbih0aGlzKX1zdGFydEF0KHQpe3JldHVybiB0aGlzLl9zdGFydFRpbWU9dCx0aGlzfXNldExvb3AodCxlKXtyZXR1cm4gdGhpcy5sb29wPXQsdGhpcy5yZXBldGl0aW9ucz1lLHRoaXN9c2V0RWZmZWN0aXZlV2VpZ2h0KHQpe3JldHVybiB0aGlzLndlaWdodD10LHRoaXMuX2VmZmVjdGl2ZVdlaWdodD10aGlzLmVuYWJsZWQ/dDowLHRoaXMuc3RvcEZhZGluZygpfWdldEVmZmVjdGl2ZVdlaWdodCgpe3JldHVybiB0aGlzLl9lZmZlY3RpdmVXZWlnaHR9ZmFkZUluKHQpe3JldHVybiB0aGlzLl9zY2hlZHVsZUZhZGluZyh0LDAsMSl9ZmFkZU91dCh0KXtyZXR1cm4gdGhpcy5fc2NoZWR1bGVGYWRpbmcodCwxLDApfWNyb3NzRmFkZUZyb20odCxlLGkpe2lmKHQuZmFkZU91dChlKSx0aGlzLmZhZGVJbihlKSxpKXtsZXQgcj10aGlzLl9jbGlwLmR1cmF0aW9uLHM9dC5fY2xpcC5kdXJhdGlvbixvPXMvcixhPXIvczt0LndhcnAoMSxvLGUpLHRoaXMud2FycChhLDEsZSl9cmV0dXJuIHRoaXN9Y3Jvc3NGYWRlVG8odCxlLGkpe3JldHVybiB0LmNyb3NzRmFkZUZyb20odGhpcyxlLGkpfXN0b3BGYWRpbmcoKXtsZXQgdD10aGlzLl93ZWlnaHRJbnRlcnBvbGFudDtyZXR1cm4gdCE9PW51bGwmJih0aGlzLl93ZWlnaHRJbnRlcnBvbGFudD1udWxsLHRoaXMuX21peGVyLl90YWtlQmFja0NvbnRyb2xJbnRlcnBvbGFudCh0KSksdGhpc31zZXRFZmZlY3RpdmVUaW1lU2NhbGUodCl7cmV0dXJuIHRoaXMudGltZVNjYWxlPXQsdGhpcy5fZWZmZWN0aXZlVGltZVNjYWxlPXRoaXMucGF1c2VkPzA6dCx0aGlzLnN0b3BXYXJwaW5nKCl9Z2V0RWZmZWN0aXZlVGltZVNjYWxlKCl7cmV0dXJuIHRoaXMuX2VmZmVjdGl2ZVRpbWVTY2FsZX1zZXREdXJhdGlvbih0KXtyZXR1cm4gdGhpcy50aW1lU2NhbGU9dGhpcy5fY2xpcC5kdXJhdGlvbi90LHRoaXMuc3RvcFdhcnBpbmcoKX1zeW5jV2l0aCh0KXtyZXR1cm4gdGhpcy50aW1lPXQudGltZSx0aGlzLnRpbWVTY2FsZT10LnRpbWVTY2FsZSx0aGlzLnN0b3BXYXJwaW5nKCl9aGFsdCh0KXtyZXR1cm4gdGhpcy53YXJwKHRoaXMuX2VmZmVjdGl2ZVRpbWVTY2FsZSwwLHQpfXdhcnAodCxlLGkpe2xldCByPXRoaXMuX21peGVyLHM9ci50aW1lLG89dGhpcy50aW1lU2NhbGUsYT10aGlzLl90aW1lU2NhbGVJbnRlcnBvbGFudDthPT09bnVsbCYmKGE9ci5fbGVuZENvbnRyb2xJbnRlcnBvbGFudCgpLHRoaXMuX3RpbWVTY2FsZUludGVycG9sYW50PWEpO2xldCBsPWEucGFyYW1ldGVyUG9zaXRpb25zLGM9YS5zYW1wbGVWYWx1ZXM7cmV0dXJuIGxbMF09cyxsWzFdPXMraSxjWzBdPXQvbyxjWzFdPWUvbyx0aGlzfXN0b3BXYXJwaW5nKCl7bGV0IHQ9dGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ7cmV0dXJuIHQhPT1udWxsJiYodGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ9bnVsbCx0aGlzLl9taXhlci5fdGFrZUJhY2tDb250cm9sSW50ZXJwb2xhbnQodCkpLHRoaXN9Z2V0TWl4ZXIoKXtyZXR1cm4gdGhpcy5fbWl4ZXJ9Z2V0Q2xpcCgpe3JldHVybiB0aGlzLl9jbGlwfWdldFJvb3QoKXtyZXR1cm4gdGhpcy5fbG9jYWxSb290fHx0aGlzLl9taXhlci5fcm9vdH1fdXBkYXRlKHQsZSxpLHIpe2lmKCF0aGlzLmVuYWJsZWQpe3RoaXMuX3VwZGF0ZVdlaWdodCh0KTtyZXR1cm59bGV0IHM9dGhpcy5fc3RhcnRUaW1lO2lmKHMhPT1udWxsKXtsZXQgbD0odC1zKSppO2lmKGw8MHx8aT09PTApcmV0dXJuO3RoaXMuX3N0YXJ0VGltZT1udWxsLGU9aSpsfWUqPXRoaXMuX3VwZGF0ZVRpbWVTY2FsZSh0KTtsZXQgbz10aGlzLl91cGRhdGVUaW1lKGUpLGE9dGhpcy5fdXBkYXRlV2VpZ2h0KHQpO2lmKGE+MCl7bGV0IGw9dGhpcy5faW50ZXJwb2xhbnRzLGM9dGhpcy5fcHJvcGVydHlCaW5kaW5ncztzd2l0Y2godGhpcy5ibGVuZE1vZGUpe2Nhc2UgZDA6Zm9yKGxldCB1PTAsaD1sLmxlbmd0aDt1IT09aDsrK3UpbFt1XS5ldmFsdWF0ZShvKSxjW3VdLmFjY3VtdWxhdGVBZGRpdGl2ZShhKTticmVhaztjYXNlIFFoOmRlZmF1bHQ6Zm9yKGxldCB1PTAsaD1sLmxlbmd0aDt1IT09aDsrK3UpbFt1XS5ldmFsdWF0ZShvKSxjW3VdLmFjY3VtdWxhdGUocixhKX19fV91cGRhdGVXZWlnaHQodCl7bGV0IGU9MDtpZih0aGlzLmVuYWJsZWQpe2U9dGhpcy53ZWlnaHQ7bGV0IGk9dGhpcy5fd2VpZ2h0SW50ZXJwb2xhbnQ7aWYoaSE9PW51bGwpe2xldCByPWkuZXZhbHVhdGUodClbMF07ZSo9cix0PmkucGFyYW1ldGVyUG9zaXRpb25zWzFdJiYodGhpcy5zdG9wRmFkaW5nKCkscj09PTAmJih0aGlzLmVuYWJsZWQ9ITEpKX19cmV0dXJuIHRoaXMuX2VmZmVjdGl2ZVdlaWdodD1lLGV9X3VwZGF0ZVRpbWVTY2FsZSh0KXtsZXQgZT0wO2lmKCF0aGlzLnBhdXNlZCl7ZT10aGlzLnRpbWVTY2FsZTtsZXQgaT10aGlzLl90aW1lU2NhbGVJbnRlcnBvbGFudDtpIT09bnVsbCYmKGUqPWkuZXZhbHVhdGUodClbMF0sdD5pLnBhcmFtZXRlclBvc2l0aW9uc1sxXSYmKHRoaXMuc3RvcFdhcnBpbmcoKSxlPT09MD90aGlzLnBhdXNlZD0hMDp0aGlzLnRpbWVTY2FsZT1lKSl9cmV0dXJuIHRoaXMuX2VmZmVjdGl2ZVRpbWVTY2FsZT1lLGV9X3VwZGF0ZVRpbWUodCl7bGV0IGU9dGhpcy5fY2xpcC5kdXJhdGlvbixpPXRoaXMubG9vcCxyPXRoaXMudGltZSt0LHM9dGhpcy5fbG9vcENvdW50LG89aT09PUp3O2lmKHQ9PT0wKXJldHVybiBzPT09LTE/cjpvJiYocyYxKT09PTE/ZS1yOnI7aWYoaT09PVl3KXtzPT09LTEmJih0aGlzLl9sb29wQ291bnQ9MCx0aGlzLl9zZXRFbmRpbmdzKCEwLCEwLCExKSk7dDp7aWYocj49ZSlyPWU7ZWxzZSBpZihyPDApcj0wO2Vsc2V7dGhpcy50aW1lPXI7YnJlYWsgdH10aGlzLmNsYW1wV2hlbkZpbmlzaGVkP3RoaXMucGF1c2VkPSEwOnRoaXMuZW5hYmxlZD0hMSx0aGlzLnRpbWU9cix0aGlzLl9taXhlci5kaXNwYXRjaEV2ZW50KHt0eXBlOiJmaW5pc2hlZCIsYWN0aW9uOnRoaXMsZGlyZWN0aW9uOnQ8MD8tMToxfSl9fWVsc2V7aWYocz09PS0xJiYodD49MD8ocz0wLHRoaXMuX3NldEVuZGluZ3MoITAsdGhpcy5yZXBldGl0aW9ucz09PTAsbykpOnRoaXMuX3NldEVuZGluZ3ModGhpcy5yZXBldGl0aW9ucz09PTAsITAsbykpLHI+PWV8fHI8MCl7bGV0IGE9TWF0aC5mbG9vcihyL2UpO3ItPWUqYSxzKz1NYXRoLmFicyhhKTtsZXQgbD10aGlzLnJlcGV0aXRpb25zLXM7aWYobDw9MCl0aGlzLmNsYW1wV2hlbkZpbmlzaGVkP3RoaXMucGF1c2VkPSEwOnRoaXMuZW5hYmxlZD0hMSxyPXQ+MD9lOjAsdGhpcy50aW1lPXIsdGhpcy5fbWl4ZXIuZGlzcGF0Y2hFdmVudCh7dHlwZToiZmluaXNoZWQiLGFjdGlvbjp0aGlzLGRpcmVjdGlvbjp0PjA/MTotMX0pO2Vsc2V7aWYobD09PTEpe2xldCBjPXQ8MDt0aGlzLl9zZXRFbmRpbmdzKGMsIWMsbyl9ZWxzZSB0aGlzLl9zZXRFbmRpbmdzKCExLCExLG8pO3RoaXMuX2xvb3BDb3VudD1zLHRoaXMudGltZT1yLHRoaXMuX21peGVyLmRpc3BhdGNoRXZlbnQoe3R5cGU6Imxvb3AiLGFjdGlvbjp0aGlzLGxvb3BEZWx0YTphfSl9fWVsc2UgdGhpcy50aW1lPXI7aWYobyYmKHMmMSk9PT0xKXJldHVybiBlLXJ9cmV0dXJuIHJ9X3NldEVuZGluZ3ModCxlLGkpe2xldCByPXRoaXMuX2ludGVycG9sYW50U2V0dGluZ3M7aT8oci5lbmRpbmdTdGFydD1GcixyLmVuZGluZ0VuZD1Gcik6KHQ/ci5lbmRpbmdTdGFydD10aGlzLnplcm9TbG9wZUF0U3RhcnQ/RnI6TnI6ci5lbmRpbmdTdGFydD1lbCxlP3IuZW5kaW5nRW5kPXRoaXMuemVyb1Nsb3BlQXRFbmQ/RnI6TnI6ci5lbmRpbmdFbmQ9ZWwpfV9zY2hlZHVsZUZhZGluZyh0LGUsaSl7bGV0IHI9dGhpcy5fbWl4ZXIscz1yLnRpbWUsbz10aGlzLl93ZWlnaHRJbnRlcnBvbGFudDtvPT09bnVsbCYmKG89ci5fbGVuZENvbnRyb2xJbnRlcnBvbGFudCgpLHRoaXMuX3dlaWdodEludGVycG9sYW50PW8pO2xldCBhPW8ucGFyYW1ldGVyUG9zaXRpb25zLGw9by5zYW1wbGVWYWx1ZXM7cmV0dXJuIGFbMF09cyxsWzBdPWUsYVsxXT1zK3QsbFsxXT1pLHRoaXN9fSxXaD1jbGFzcyBleHRlbmRzIElue2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5fcm9vdD10LHRoaXMuX2luaXRNZW1vcnlNYW5hZ2VyKCksdGhpcy5fYWNjdUluZGV4PTAsdGhpcy50aW1lPTAsdGhpcy50aW1lU2NhbGU9MX1fYmluZEFjdGlvbih0LGUpe2xldCBpPXQuX2xvY2FsUm9vdHx8dGhpcy5fcm9vdCxyPXQuX2NsaXAudHJhY2tzLHM9ci5sZW5ndGgsbz10Ll9wcm9wZXJ0eUJpbmRpbmdzLGE9dC5faW50ZXJwb2xhbnRzLGw9aS51dWlkLGM9dGhpcy5fYmluZGluZ3NCeVJvb3RBbmROYW1lLHU9Y1tsXTt1PT09dm9pZCAwJiYodT17fSxjW2xdPXUpO2ZvcihsZXQgaD0wO2ghPT1zOysraCl7bGV0IGY9cltoXSxkPWYubmFtZSxnPXVbZF07aWYoZyE9PXZvaWQgMCkrK2cucmVmZXJlbmNlQ291bnQsb1toXT1nO2Vsc2V7aWYoZz1vW2hdLGchPT12b2lkIDApe2cuX2NhY2hlSW5kZXg9PT1udWxsJiYoKytnLnJlZmVyZW5jZUNvdW50LHRoaXMuX2FkZEluYWN0aXZlQmluZGluZyhnLGwsZCkpO2NvbnRpbnVlfWxldCB4PWUmJmUuX3Byb3BlcnR5QmluZGluZ3NbaF0uYmluZGluZy5wYXJzZWRQYXRoO2c9bmV3IGtoKE50LmNyZWF0ZShpLGQseCksZi5WYWx1ZVR5cGVOYW1lLGYuZ2V0VmFsdWVTaXplKCkpLCsrZy5yZWZlcmVuY2VDb3VudCx0aGlzLl9hZGRJbmFjdGl2ZUJpbmRpbmcoZyxsLGQpLG9baF09Z31hW2hdLnJlc3VsdEJ1ZmZlcj1nLmJ1ZmZlcn19X2FjdGl2YXRlQWN0aW9uKHQpe2lmKCF0aGlzLl9pc0FjdGl2ZUFjdGlvbih0KSl7aWYodC5fY2FjaGVJbmRleD09PW51bGwpe2xldCBpPSh0Ll9sb2NhbFJvb3R8fHRoaXMuX3Jvb3QpLnV1aWQscj10Ll9jbGlwLnV1aWQscz10aGlzLl9hY3Rpb25zQnlDbGlwW3JdO3RoaXMuX2JpbmRBY3Rpb24odCxzJiZzLmtub3duQWN0aW9uc1swXSksdGhpcy5fYWRkSW5hY3RpdmVBY3Rpb24odCxyLGkpfWxldCBlPXQuX3Byb3BlcnR5QmluZGluZ3M7Zm9yKGxldCBpPTAscj1lLmxlbmd0aDtpIT09cjsrK2kpe2xldCBzPWVbaV07cy51c2VDb3VudCsrPT09MCYmKHRoaXMuX2xlbmRCaW5kaW5nKHMpLHMuc2F2ZU9yaWdpbmFsU3RhdGUoKSl9dGhpcy5fbGVuZEFjdGlvbih0KX19X2RlYWN0aXZhdGVBY3Rpb24odCl7aWYodGhpcy5faXNBY3RpdmVBY3Rpb24odCkpe2xldCBlPXQuX3Byb3BlcnR5QmluZGluZ3M7Zm9yKGxldCBpPTAscj1lLmxlbmd0aDtpIT09cjsrK2kpe2xldCBzPWVbaV07LS1zLnVzZUNvdW50PT09MCYmKHMucmVzdG9yZU9yaWdpbmFsU3RhdGUoKSx0aGlzLl90YWtlQmFja0JpbmRpbmcocykpfXRoaXMuX3Rha2VCYWNrQWN0aW9uKHQpfX1faW5pdE1lbW9yeU1hbmFnZXIoKXt0aGlzLl9hY3Rpb25zPVtdLHRoaXMuX25BY3RpdmVBY3Rpb25zPTAsdGhpcy5fYWN0aW9uc0J5Q2xpcD17fSx0aGlzLl9iaW5kaW5ncz1bXSx0aGlzLl9uQWN0aXZlQmluZGluZ3M9MCx0aGlzLl9iaW5kaW5nc0J5Um9vdEFuZE5hbWU9e30sdGhpcy5fY29udHJvbEludGVycG9sYW50cz1bXSx0aGlzLl9uQWN0aXZlQ29udHJvbEludGVycG9sYW50cz0wO2xldCB0PXRoaXM7dGhpcy5zdGF0cz17YWN0aW9uczp7Z2V0IHRvdGFsKCl7cmV0dXJuIHQuX2FjdGlvbnMubGVuZ3RofSxnZXQgaW5Vc2UoKXtyZXR1cm4gdC5fbkFjdGl2ZUFjdGlvbnN9fSxiaW5kaW5nczp7Z2V0IHRvdGFsKCl7cmV0dXJuIHQuX2JpbmRpbmdzLmxlbmd0aH0sZ2V0IGluVXNlKCl7cmV0dXJuIHQuX25BY3RpdmVCaW5kaW5nc319LGNvbnRyb2xJbnRlcnBvbGFudHM6e2dldCB0b3RhbCgpe3JldHVybiB0Ll9jb250cm9sSW50ZXJwb2xhbnRzLmxlbmd0aH0sZ2V0IGluVXNlKCl7cmV0dXJuIHQuX25BY3RpdmVDb250cm9sSW50ZXJwb2xhbnRzfX19fV9pc0FjdGl2ZUFjdGlvbih0KXtsZXQgZT10Ll9jYWNoZUluZGV4O3JldHVybiBlIT09bnVsbCYmZTx0aGlzLl9uQWN0aXZlQWN0aW9uc31fYWRkSW5hY3RpdmVBY3Rpb24odCxlLGkpe2xldCByPXRoaXMuX2FjdGlvbnMscz10aGlzLl9hY3Rpb25zQnlDbGlwLG89c1tlXTtpZihvPT09dm9pZCAwKW89e2tub3duQWN0aW9uczpbdF0sYWN0aW9uQnlSb290Ont9fSx0Ll9ieUNsaXBDYWNoZUluZGV4PTAsc1tlXT1vO2Vsc2V7bGV0IGE9by5rbm93bkFjdGlvbnM7dC5fYnlDbGlwQ2FjaGVJbmRleD1hLmxlbmd0aCxhLnB1c2godCl9dC5fY2FjaGVJbmRleD1yLmxlbmd0aCxyLnB1c2godCksby5hY3Rpb25CeVJvb3RbaV09dH1fcmVtb3ZlSW5hY3RpdmVBY3Rpb24odCl7bGV0IGU9dGhpcy5fYWN0aW9ucyxpPWVbZS5sZW5ndGgtMV0scj10Ll9jYWNoZUluZGV4O2kuX2NhY2hlSW5kZXg9cixlW3JdPWksZS5wb3AoKSx0Ll9jYWNoZUluZGV4PW51bGw7bGV0IHM9dC5fY2xpcC51dWlkLG89dGhpcy5fYWN0aW9uc0J5Q2xpcCxhPW9bc10sbD1hLmtub3duQWN0aW9ucyxjPWxbbC5sZW5ndGgtMV0sdT10Ll9ieUNsaXBDYWNoZUluZGV4O2MuX2J5Q2xpcENhY2hlSW5kZXg9dSxsW3VdPWMsbC5wb3AoKSx0Ll9ieUNsaXBDYWNoZUluZGV4PW51bGw7bGV0IGg9YS5hY3Rpb25CeVJvb3QsZj0odC5fbG9jYWxSb290fHx0aGlzLl9yb290KS51dWlkO2RlbGV0ZSBoW2ZdLGwubGVuZ3RoPT09MCYmZGVsZXRlIG9bc10sdGhpcy5fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nc0ZvckFjdGlvbih0KX1fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nc0ZvckFjdGlvbih0KXtsZXQgZT10Ll9wcm9wZXJ0eUJpbmRpbmdzO2ZvcihsZXQgaT0wLHI9ZS5sZW5ndGg7aSE9PXI7KytpKXtsZXQgcz1lW2ldOy0tcy5yZWZlcmVuY2VDb3VudD09PTAmJnRoaXMuX3JlbW92ZUluYWN0aXZlQmluZGluZyhzKX19X2xlbmRBY3Rpb24odCl7bGV0IGU9dGhpcy5fYWN0aW9ucyxpPXQuX2NhY2hlSW5kZXgscj10aGlzLl9uQWN0aXZlQWN0aW9ucysrLHM9ZVtyXTt0Ll9jYWNoZUluZGV4PXIsZVtyXT10LHMuX2NhY2hlSW5kZXg9aSxlW2ldPXN9X3Rha2VCYWNrQWN0aW9uKHQpe2xldCBlPXRoaXMuX2FjdGlvbnMsaT10Ll9jYWNoZUluZGV4LHI9LS10aGlzLl9uQWN0aXZlQWN0aW9ucyxzPWVbcl07dC5fY2FjaGVJbmRleD1yLGVbcl09dCxzLl9jYWNoZUluZGV4PWksZVtpXT1zfV9hZGRJbmFjdGl2ZUJpbmRpbmcodCxlLGkpe2xldCByPXRoaXMuX2JpbmRpbmdzQnlSb290QW5kTmFtZSxzPXRoaXMuX2JpbmRpbmdzLG89cltlXTtvPT09dm9pZCAwJiYobz17fSxyW2VdPW8pLG9baV09dCx0Ll9jYWNoZUluZGV4PXMubGVuZ3RoLHMucHVzaCh0KX1fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nKHQpe2xldCBlPXRoaXMuX2JpbmRpbmdzLGk9dC5iaW5kaW5nLHI9aS5yb290Tm9kZS51dWlkLHM9aS5wYXRoLG89dGhpcy5fYmluZGluZ3NCeVJvb3RBbmROYW1lLGE9b1tyXSxsPWVbZS5sZW5ndGgtMV0sYz10Ll9jYWNoZUluZGV4O2wuX2NhY2hlSW5kZXg9YyxlW2NdPWwsZS5wb3AoKSxkZWxldGUgYVtzXSxPYmplY3Qua2V5cyhhKS5sZW5ndGg9PT0wJiZkZWxldGUgb1tyXX1fbGVuZEJpbmRpbmcodCl7bGV0IGU9dGhpcy5fYmluZGluZ3MsaT10Ll9jYWNoZUluZGV4LHI9dGhpcy5fbkFjdGl2ZUJpbmRpbmdzKysscz1lW3JdO3QuX2NhY2hlSW5kZXg9cixlW3JdPXQscy5fY2FjaGVJbmRleD1pLGVbaV09c31fdGFrZUJhY2tCaW5kaW5nKHQpe2xldCBlPXRoaXMuX2JpbmRpbmdzLGk9dC5fY2FjaGVJbmRleCxyPS0tdGhpcy5fbkFjdGl2ZUJpbmRpbmdzLHM9ZVtyXTt0Ll9jYWNoZUluZGV4PXIsZVtyXT10LHMuX2NhY2hlSW5kZXg9aSxlW2ldPXN9X2xlbmRDb250cm9sSW50ZXJwb2xhbnQoKXtsZXQgdD10aGlzLl9jb250cm9sSW50ZXJwb2xhbnRzLGU9dGhpcy5fbkFjdGl2ZUNvbnRyb2xJbnRlcnBvbGFudHMrKyxpPXRbZV07cmV0dXJuIGk9PT12b2lkIDAmJihpPW5ldyBfbChuZXcgRmxvYXQzMkFycmF5KDIpLG5ldyBGbG9hdDMyQXJyYXkoMiksMSx0aGlzLl9jb250cm9sSW50ZXJwb2xhbnRzUmVzdWx0QnVmZmVyKSxpLl9fY2FjaGVJbmRleD1lLHRbZV09aSksaX1fdGFrZUJhY2tDb250cm9sSW50ZXJwb2xhbnQodCl7bGV0IGU9dGhpcy5fY29udHJvbEludGVycG9sYW50cyxpPXQuX19jYWNoZUluZGV4LHI9LS10aGlzLl9uQWN0aXZlQ29udHJvbEludGVycG9sYW50cyxzPWVbcl07dC5fX2NhY2hlSW5kZXg9cixlW3JdPXQscy5fX2NhY2hlSW5kZXg9aSxlW2ldPXN9Y2xpcEFjdGlvbih0LGUsaSl7bGV0IHI9ZXx8dGhpcy5fcm9vdCxzPXIudXVpZCxvPXR5cGVvZiB0PT0ic3RyaW5nIj9NbC5maW5kQnlOYW1lKHIsdCk6dCxhPW8hPT1udWxsP28udXVpZDp0LGw9dGhpcy5fYWN0aW9uc0J5Q2xpcFthXSxjPW51bGw7aWYoaT09PXZvaWQgMCYmKG8hPT1udWxsP2k9by5ibGVuZE1vZGU6aT1RaCksbCE9PXZvaWQgMCl7bGV0IGg9bC5hY3Rpb25CeVJvb3Rbc107aWYoaCE9PXZvaWQgMCYmaC5ibGVuZE1vZGU9PT1pKXJldHVybiBoO2M9bC5rbm93bkFjdGlvbnNbMF0sbz09PW51bGwmJihvPWMuX2NsaXApfWlmKG89PT1udWxsKXJldHVybiBudWxsO2xldCB1PW5ldyBHaCh0aGlzLG8sZSxpKTtyZXR1cm4gdGhpcy5fYmluZEFjdGlvbih1LGMpLHRoaXMuX2FkZEluYWN0aXZlQWN0aW9uKHUsYSxzKSx1fWV4aXN0aW5nQWN0aW9uKHQsZSl7bGV0IGk9ZXx8dGhpcy5fcm9vdCxyPWkudXVpZCxzPXR5cGVvZiB0PT0ic3RyaW5nIj9NbC5maW5kQnlOYW1lKGksdCk6dCxvPXM/cy51dWlkOnQsYT10aGlzLl9hY3Rpb25zQnlDbGlwW29dO3JldHVybiBhIT09dm9pZCAwJiZhLmFjdGlvbkJ5Um9vdFtyXXx8bnVsbH1zdG9wQWxsQWN0aW9uKCl7bGV0IHQ9dGhpcy5fYWN0aW9ucyxlPXRoaXMuX25BY3RpdmVBY3Rpb25zO2ZvcihsZXQgaT1lLTE7aT49MDstLWkpdFtpXS5zdG9wKCk7cmV0dXJuIHRoaXN9dXBkYXRlKHQpe3QqPXRoaXMudGltZVNjYWxlO2xldCBlPXRoaXMuX2FjdGlvbnMsaT10aGlzLl9uQWN0aXZlQWN0aW9ucyxyPXRoaXMudGltZSs9dCxzPU1hdGguc2lnbih0KSxvPXRoaXMuX2FjY3VJbmRleF49MTtmb3IobGV0IGM9MDtjIT09aTsrK2MpZVtjXS5fdXBkYXRlKHIsdCxzLG8pO2xldCBhPXRoaXMuX2JpbmRpbmdzLGw9dGhpcy5fbkFjdGl2ZUJpbmRpbmdzO2ZvcihsZXQgYz0wO2MhPT1sOysrYylhW2NdLmFwcGx5KG8pO3JldHVybiB0aGlzfXNldFRpbWUodCl7dGhpcy50aW1lPTA7Zm9yKGxldCBlPTA7ZTx0aGlzLl9hY3Rpb25zLmxlbmd0aDtlKyspdGhpcy5fYWN0aW9uc1tlXS50aW1lPTA7cmV0dXJuIHRoaXMudXBkYXRlKHQpfWdldFJvb3QoKXtyZXR1cm4gdGhpcy5fcm9vdH11bmNhY2hlQ2xpcCh0KXtsZXQgZT10aGlzLl9hY3Rpb25zLGk9dC51dWlkLHI9dGhpcy5fYWN0aW9uc0J5Q2xpcCxzPXJbaV07aWYocyE9PXZvaWQgMCl7bGV0IG89cy5rbm93bkFjdGlvbnM7Zm9yKGxldCBhPTAsbD1vLmxlbmd0aDthIT09bDsrK2Epe2xldCBjPW9bYV07dGhpcy5fZGVhY3RpdmF0ZUFjdGlvbihjKTtsZXQgdT1jLl9jYWNoZUluZGV4LGg9ZVtlLmxlbmd0aC0xXTtjLl9jYWNoZUluZGV4PW51bGwsYy5fYnlDbGlwQ2FjaGVJbmRleD1udWxsLGguX2NhY2hlSW5kZXg9dSxlW3VdPWgsZS5wb3AoKSx0aGlzLl9yZW1vdmVJbmFjdGl2ZUJpbmRpbmdzRm9yQWN0aW9uKGMpfWRlbGV0ZSByW2ldfX11bmNhY2hlUm9vdCh0KXtsZXQgZT10LnV1aWQsaT10aGlzLl9hY3Rpb25zQnlDbGlwO2ZvcihsZXQgbyBpbiBpKXtsZXQgYT1pW29dLmFjdGlvbkJ5Um9vdCxsPWFbZV07bCE9PXZvaWQgMCYmKHRoaXMuX2RlYWN0aXZhdGVBY3Rpb24obCksdGhpcy5fcmVtb3ZlSW5hY3RpdmVBY3Rpb24obCkpfWxldCByPXRoaXMuX2JpbmRpbmdzQnlSb290QW5kTmFtZSxzPXJbZV07aWYocyE9PXZvaWQgMClmb3IobGV0IG8gaW4gcyl7bGV0IGE9c1tvXTthLnJlc3RvcmVPcmlnaW5hbFN0YXRlKCksdGhpcy5fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nKGEpfX11bmNhY2hlQWN0aW9uKHQsZSl7bGV0IGk9dGhpcy5leGlzdGluZ0FjdGlvbih0LGUpO2khPT1udWxsJiYodGhpcy5fZGVhY3RpdmF0ZUFjdGlvbihpKSx0aGlzLl9yZW1vdmVJbmFjdGl2ZUFjdGlvbihpKSl9fTtXaC5wcm90b3R5cGUuX2NvbnRyb2xJbnRlcnBvbGFudHNSZXN1bHRCdWZmZXI9bmV3IEZsb2F0MzJBcnJheSgxKTt2YXIgVG89Y2xhc3N7Y29uc3RydWN0b3IodCl7dHlwZW9mIHQ9PSJzdHJpbmciJiYoY29uc29sZS53YXJuKCJUSFJFRS5Vbmlmb3JtOiBUeXBlIHBhcmFtZXRlciBpcyBubyBsb25nZXIgbmVlZGVkLiIpLHQ9YXJndW1lbnRzWzFdKSx0aGlzLnZhbHVlPXR9Y2xvbmUoKXtyZXR1cm4gbmV3IFRvKHRoaXMudmFsdWUuY2xvbmU9PT12b2lkIDA/dGhpcy52YWx1ZTp0aGlzLnZhbHVlLmNsb25lKCkpfX0scWg9Y2xhc3MgZXh0ZW5kcyBHaXtjb25zdHJ1Y3Rvcih0LGUsaT0xKXtzdXBlcih0LGUpLHRoaXMubWVzaFBlckF0dHJpYnV0ZT1pfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5tZXNoUGVyQXR0cmlidXRlPXQubWVzaFBlckF0dHJpYnV0ZSx0aGlzfWNsb25lKHQpe2xldCBlPXN1cGVyLmNsb25lKHQpO3JldHVybiBlLm1lc2hQZXJBdHRyaWJ1dGU9dGhpcy5tZXNoUGVyQXR0cmlidXRlLGV9dG9KU09OKHQpe2xldCBlPXN1cGVyLnRvSlNPTih0KTtyZXR1cm4gZS5pc0luc3RhbmNlZEludGVybGVhdmVkQnVmZmVyPSEwLGUubWVzaFBlckF0dHJpYnV0ZT10aGlzLm1lc2hQZXJBdHRyaWJ1dGUsZX19O3FoLnByb3RvdHlwZS5pc0luc3RhbmNlZEludGVybGVhdmVkQnVmZmVyPSEwO3ZhciBYaD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLHMpe3RoaXMuYnVmZmVyPXQsdGhpcy50eXBlPWUsdGhpcy5pdGVtU2l6ZT1pLHRoaXMuZWxlbWVudFNpemU9cix0aGlzLmNvdW50PXMsdGhpcy52ZXJzaW9uPTB9c2V0IG5lZWRzVXBkYXRlKHQpe3Q9PT0hMCYmdGhpcy52ZXJzaW9uKyt9c2V0QnVmZmVyKHQpe3JldHVybiB0aGlzLmJ1ZmZlcj10LHRoaXN9c2V0VHlwZSh0LGUpe3JldHVybiB0aGlzLnR5cGU9dCx0aGlzLmVsZW1lbnRTaXplPWUsdGhpc31zZXRJdGVtU2l6ZSh0KXtyZXR1cm4gdGhpcy5pdGVtU2l6ZT10LHRoaXN9c2V0Q291bnQodCl7cmV0dXJuIHRoaXMuY291bnQ9dCx0aGlzfX07WGgucHJvdG90eXBlLmlzR0xCdWZmZXJBdHRyaWJ1dGU9ITA7dmFyIG8wPW5ldyBLLFlpPWNsYXNze2NvbnN0cnVjdG9yKHQ9bmV3IEsoMS8wLDEvMCksZT1uZXcgSygtMS8wLC0xLzApKXt0aGlzLm1pbj10LHRoaXMubWF4PWV9c2V0KHQsZSl7cmV0dXJuIHRoaXMubWluLmNvcHkodCksdGhpcy5tYXguY29weShlKSx0aGlzfXNldEZyb21Qb2ludHModCl7dGhpcy5tYWtlRW1wdHkoKTtmb3IobGV0IGU9MCxpPXQubGVuZ3RoO2U8aTtlKyspdGhpcy5leHBhbmRCeVBvaW50KHRbZV0pO3JldHVybiB0aGlzfXNldEZyb21DZW50ZXJBbmRTaXplKHQsZSl7bGV0IGk9bzAuY29weShlKS5tdWx0aXBseVNjYWxhciguNSk7cmV0dXJuIHRoaXMubWluLmNvcHkodCkuc3ViKGkpLHRoaXMubWF4LmNvcHkodCkuYWRkKGkpLHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfWNvcHkodCl7cmV0dXJuIHRoaXMubWluLmNvcHkodC5taW4pLHRoaXMubWF4LmNvcHkodC5tYXgpLHRoaXN9bWFrZUVtcHR5KCl7cmV0dXJuIHRoaXMubWluLng9dGhpcy5taW4ueT0xLzAsdGhpcy5tYXgueD10aGlzLm1heC55PS0xLzAsdGhpc31pc0VtcHR5KCl7cmV0dXJuIHRoaXMubWF4Lng8dGhpcy5taW4ueHx8dGhpcy5tYXgueTx0aGlzLm1pbi55fWdldENlbnRlcih0KXtyZXR1cm4gdGhpcy5pc0VtcHR5KCk/dC5zZXQoMCwwKTp0LmFkZFZlY3RvcnModGhpcy5taW4sdGhpcy5tYXgpLm11bHRpcGx5U2NhbGFyKC41KX1nZXRTaXplKHQpe3JldHVybiB0aGlzLmlzRW1wdHkoKT90LnNldCgwLDApOnQuc3ViVmVjdG9ycyh0aGlzLm1heCx0aGlzLm1pbil9ZXhwYW5kQnlQb2ludCh0KXtyZXR1cm4gdGhpcy5taW4ubWluKHQpLHRoaXMubWF4Lm1heCh0KSx0aGlzfWV4cGFuZEJ5VmVjdG9yKHQpe3JldHVybiB0aGlzLm1pbi5zdWIodCksdGhpcy5tYXguYWRkKHQpLHRoaXN9ZXhwYW5kQnlTY2FsYXIodCl7cmV0dXJuIHRoaXMubWluLmFkZFNjYWxhcigtdCksdGhpcy5tYXguYWRkU2NhbGFyKHQpLHRoaXN9Y29udGFpbnNQb2ludCh0KXtyZXR1cm4hKHQueDx0aGlzLm1pbi54fHx0Lng+dGhpcy5tYXgueHx8dC55PHRoaXMubWluLnl8fHQueT50aGlzLm1heC55KX1jb250YWluc0JveCh0KXtyZXR1cm4gdGhpcy5taW4ueDw9dC5taW4ueCYmdC5tYXgueDw9dGhpcy5tYXgueCYmdGhpcy5taW4ueTw9dC5taW4ueSYmdC5tYXgueTw9dGhpcy5tYXgueX1nZXRQYXJhbWV0ZXIodCxlKXtyZXR1cm4gZS5zZXQoKHQueC10aGlzLm1pbi54KS8odGhpcy5tYXgueC10aGlzLm1pbi54KSwodC55LXRoaXMubWluLnkpLyh0aGlzLm1heC55LXRoaXMubWluLnkpKX1pbnRlcnNlY3RzQm94KHQpe3JldHVybiEodC5tYXgueDx0aGlzLm1pbi54fHx0Lm1pbi54PnRoaXMubWF4Lnh8fHQubWF4Lnk8dGhpcy5taW4ueXx8dC5taW4ueT50aGlzLm1heC55KX1jbGFtcFBvaW50KHQsZSl7cmV0dXJuIGUuY29weSh0KS5jbGFtcCh0aGlzLm1pbix0aGlzLm1heCl9ZGlzdGFuY2VUb1BvaW50KHQpe3JldHVybiBvMC5jb3B5KHQpLmNsYW1wKHRoaXMubWluLHRoaXMubWF4KS5zdWIodCkubGVuZ3RoKCl9aW50ZXJzZWN0KHQpe3JldHVybiB0aGlzLm1pbi5tYXgodC5taW4pLHRoaXMubWF4Lm1pbih0Lm1heCksdGhpc311bmlvbih0KXtyZXR1cm4gdGhpcy5taW4ubWluKHQubWluKSx0aGlzLm1heC5tYXgodC5tYXgpLHRoaXN9dHJhbnNsYXRlKHQpe3JldHVybiB0aGlzLm1pbi5hZGQodCksdGhpcy5tYXguYWRkKHQpLHRoaXN9ZXF1YWxzKHQpe3JldHVybiB0Lm1pbi5lcXVhbHModGhpcy5taW4pJiZ0Lm1heC5lcXVhbHModGhpcy5tYXgpfX07WWkucHJvdG90eXBlLmlzQm94Mj0hMDt2YXIgYTA9bmV3IFQsJGE9bmV3IFQsWWg9Y2xhc3N7Y29uc3RydWN0b3IodD1uZXcgVCxlPW5ldyBUKXt0aGlzLnN0YXJ0PXQsdGhpcy5lbmQ9ZX1zZXQodCxlKXtyZXR1cm4gdGhpcy5zdGFydC5jb3B5KHQpLHRoaXMuZW5kLmNvcHkoZSksdGhpc31jb3B5KHQpe3JldHVybiB0aGlzLnN0YXJ0LmNvcHkodC5zdGFydCksdGhpcy5lbmQuY29weSh0LmVuZCksdGhpc31nZXRDZW50ZXIodCl7cmV0dXJuIHQuYWRkVmVjdG9ycyh0aGlzLnN0YXJ0LHRoaXMuZW5kKS5tdWx0aXBseVNjYWxhciguNSl9ZGVsdGEodCl7cmV0dXJuIHQuc3ViVmVjdG9ycyh0aGlzLmVuZCx0aGlzLnN0YXJ0KX1kaXN0YW5jZVNxKCl7cmV0dXJuIHRoaXMuc3RhcnQuZGlzdGFuY2VUb1NxdWFyZWQodGhpcy5lbmQpfWRpc3RhbmNlKCl7cmV0dXJuIHRoaXMuc3RhcnQuZGlzdGFuY2VUbyh0aGlzLmVuZCl9YXQodCxlKXtyZXR1cm4gdGhpcy5kZWx0YShlKS5tdWx0aXBseVNjYWxhcih0KS5hZGQodGhpcy5zdGFydCl9Y2xvc2VzdFBvaW50VG9Qb2ludFBhcmFtZXRlcih0LGUpe2EwLnN1YlZlY3RvcnModCx0aGlzLnN0YXJ0KSwkYS5zdWJWZWN0b3JzKHRoaXMuZW5kLHRoaXMuc3RhcnQpO2xldCBpPSRhLmRvdCgkYSkscz0kYS5kb3QoYTApL2k7cmV0dXJuIGUmJihzPUllKHMsMCwxKSksc31jbG9zZXN0UG9pbnRUb1BvaW50KHQsZSxpKXtsZXQgcj10aGlzLmNsb3Nlc3RQb2ludFRvUG9pbnRQYXJhbWV0ZXIodCxlKTtyZXR1cm4gdGhpcy5kZWx0YShpKS5tdWx0aXBseVNjYWxhcihyKS5hZGQodGhpcy5zdGFydCl9YXBwbHlNYXRyaXg0KHQpe3JldHVybiB0aGlzLnN0YXJ0LmFwcGx5TWF0cml4NCh0KSx0aGlzLmVuZC5hcHBseU1hdHJpeDQodCksdGhpc31lcXVhbHModCl7cmV0dXJuIHQuc3RhcnQuZXF1YWxzKHRoaXMuc3RhcnQpJiZ0LmVuZC5lcXVhbHModGhpcy5lbmQpfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX19O3ZhciBRbj1uZXcgVCxLYT1uZXcgd3QsRnU9bmV3IHd0LFpoPWNsYXNzIGV4dGVuZHMgZ297Y29uc3RydWN0b3IodCl7bGV0IGU9RDAodCksaT1uZXcgSHQscj1bXSxzPVtdLG89bmV3IGZ0KDAsMCwxKSxhPW5ldyBmdCgwLDEsMCk7Zm9yKGxldCBjPTA7YzxlLmxlbmd0aDtjKyspe2xldCB1PWVbY107dS5wYXJlbnQmJnUucGFyZW50LmlzQm9uZSYmKHIucHVzaCgwLDAsMCksci5wdXNoKDAsMCwwKSxzLnB1c2goby5yLG8uZyxvLmIpLHMucHVzaChhLnIsYS5nLGEuYikpfWkuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IGVlKHIsMykpLGkuc2V0QXR0cmlidXRlKCJjb2xvciIsbmV3IGVlKHMsMykpO2xldCBsPW5ldyB6bih7dmVydGV4Q29sb3JzOiEwLGRlcHRoVGVzdDohMSxkZXB0aFdyaXRlOiExLHRvbmVNYXBwZWQ6ITEsdHJhbnNwYXJlbnQ6ITB9KTtzdXBlcihpLGwpLHRoaXMudHlwZT0iU2tlbGV0b25IZWxwZXIiLHRoaXMuaXNTa2VsZXRvbkhlbHBlcj0hMCx0aGlzLnJvb3Q9dCx0aGlzLmJvbmVzPWUsdGhpcy5tYXRyaXg9dC5tYXRyaXhXb3JsZCx0aGlzLm1hdHJpeEF1dG9VcGRhdGU9ITF9dXBkYXRlTWF0cml4V29ybGQodCl7bGV0IGU9dGhpcy5ib25lcyxpPXRoaXMuZ2VvbWV0cnkscj1pLmdldEF0dHJpYnV0ZSgicG9zaXRpb24iKTtGdS5jb3B5KHRoaXMucm9vdC5tYXRyaXhXb3JsZCkuaW52ZXJ0KCk7Zm9yKGxldCBzPTAsbz0wO3M8ZS5sZW5ndGg7cysrKXtsZXQgYT1lW3NdO2EucGFyZW50JiZhLnBhcmVudC5pc0JvbmUmJihLYS5tdWx0aXBseU1hdHJpY2VzKEZ1LGEubWF0cml4V29ybGQpLFFuLnNldEZyb21NYXRyaXhQb3NpdGlvbihLYSksci5zZXRYWVoobyxRbi54LFFuLnksUW4ueiksS2EubXVsdGlwbHlNYXRyaWNlcyhGdSxhLnBhcmVudC5tYXRyaXhXb3JsZCksUW4uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKEthKSxyLnNldFhZWihvKzEsUW4ueCxRbi55LFFuLnopLG8rPTIpfWkuZ2V0QXR0cmlidXRlKCJwb3NpdGlvbiIpLm5lZWRzVXBkYXRlPSEwLHN1cGVyLnVwZGF0ZU1hdHJpeFdvcmxkKHQpfX07ZnVuY3Rpb24gRDAobil7bGV0IHQ9W107biYmbi5pc0JvbmUmJnQucHVzaChuKTtmb3IobGV0IGU9MDtlPG4uY2hpbGRyZW4ubGVuZ3RoO2UrKyl0LnB1c2guYXBwbHkodCxEMChuLmNoaWxkcmVuW2VdKSk7cmV0dXJuIHR9dmFyIEpoPWNsYXNzIGV4dGVuZHMgZ297Y29uc3RydWN0b3IodD0xMCxlPTEwLGk9NDQ3MzkyNCxyPTg5NDc4NDgpe2k9bmV3IGZ0KGkpLHI9bmV3IGZ0KHIpO2xldCBzPWUvMixvPXQvZSxhPXQvMixsPVtdLGM9W107Zm9yKGxldCBmPTAsZD0wLGc9LWE7Zjw9ZTtmKyssZys9byl7bC5wdXNoKC1hLDAsZyxhLDAsZyksbC5wdXNoKGcsMCwtYSxnLDAsYSk7bGV0IHg9Zj09PXM/aTpyO3gudG9BcnJheShjLGQpLGQrPTMseC50b0FycmF5KGMsZCksZCs9Myx4LnRvQXJyYXkoYyxkKSxkKz0zLHgudG9BcnJheShjLGQpLGQrPTN9bGV0IHU9bmV3IEh0O3Uuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IGVlKGwsMykpLHUuc2V0QXR0cmlidXRlKCJjb2xvciIsbmV3IGVlKGMsMykpO2xldCBoPW5ldyB6bih7dmVydGV4Q29sb3JzOiEwLHRvbmVNYXBwZWQ6ITF9KTtzdXBlcih1LGgpLHRoaXMudHlwZT0iR3JpZEhlbHBlciJ9fTt2YXIgX1Q9bmV3IEZsb2F0MzJBcnJheSgxKSxDaz1uZXcgSW50MzJBcnJheShfVC5idWZmZXIpO0ZlLmNyZWF0ZT1mdW5jdGlvbihuLHQpe3JldHVybiBjb25zb2xlLmxvZygiVEhSRUUuQ3VydmUuY3JlYXRlKCkgaGFzIGJlZW4gZGVwcmVjYXRlZCIpLG4ucHJvdG90eXBlPU9iamVjdC5jcmVhdGUoRmUucHJvdG90eXBlKSxuLnByb3RvdHlwZS5jb25zdHJ1Y3Rvcj1uLG4ucHJvdG90eXBlLmdldFBvaW50PXQsbn07X28ucHJvdG90eXBlLmZyb21Qb2ludHM9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUGF0aDogLmZyb21Qb2ludHMoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5zZXRGcm9tUG9pbnRzKCkuIiksdGhpcy5zZXRGcm9tUG9pbnRzKG4pfTtKaC5wcm90b3R5cGUuc2V0Q29sb3JzPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuR3JpZEhlbHBlcjogc2V0Q29sb3JzKCkgaGFzIGJlZW4gZGVwcmVjYXRlZCwgcGFzcyB0aGVtIGluIHRoZSBjb25zdHJ1Y3RvciBpbnN0ZWFkLiIpfTtaaC5wcm90b3R5cGUudXBkYXRlPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuU2tlbGV0b25IZWxwZXI6IHVwZGF0ZSgpIG5vIGxvbmdlciBuZWVkcyB0byBiZSBjYWxsZWQuIil9O21uLnByb3RvdHlwZS5leHRyYWN0VXJsQmFzZT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Mb2FkZXI6IC5leHRyYWN0VXJsQmFzZSgpIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSBUSFJFRS5Mb2FkZXJVdGlscy5leHRyYWN0VXJsQmFzZSgpIGluc3RlYWQuIiksRGguZXh0cmFjdFVybEJhc2Uobil9O21uLkhhbmRsZXJzPXthZGQ6ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5Mb2FkZXI6IEhhbmRsZXJzLmFkZCgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBMb2FkaW5nTWFuYWdlci5hZGRIYW5kbGVyKCkgaW5zdGVhZC4iKX0sZ2V0OmZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTG9hZGVyOiBIYW5kbGVycy5nZXQoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgTG9hZGluZ01hbmFnZXIuZ2V0SGFuZGxlcigpIGluc3RlYWQuIil9fTtZaS5wcm90b3R5cGUuY2VudGVyPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDI6IC5jZW50ZXIoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRDZW50ZXIoKS4iKSx0aGlzLmdldENlbnRlcihuKX07WWkucHJvdG90eXBlLmVtcHR5PWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MjogLmVtcHR5KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaXNFbXB0eSgpLiIpLHRoaXMuaXNFbXB0eSgpfTtZaS5wcm90b3R5cGUuaXNJbnRlcnNlY3Rpb25Cb3g9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MjogLmlzSW50ZXJzZWN0aW9uQm94KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaW50ZXJzZWN0c0JveCgpLiIpLHRoaXMuaW50ZXJzZWN0c0JveChuKX07WWkucHJvdG90eXBlLnNpemU9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MjogLnNpemUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRTaXplKCkuIiksdGhpcy5nZXRTaXplKG4pfTtHZS5wcm90b3R5cGUuY2VudGVyPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDM6IC5jZW50ZXIoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRDZW50ZXIoKS4iKSx0aGlzLmdldENlbnRlcihuKX07R2UucHJvdG90eXBlLmVtcHR5PWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MzogLmVtcHR5KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaXNFbXB0eSgpLiIpLHRoaXMuaXNFbXB0eSgpfTtHZS5wcm90b3R5cGUuaXNJbnRlcnNlY3Rpb25Cb3g9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MzogLmlzSW50ZXJzZWN0aW9uQm94KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaW50ZXJzZWN0c0JveCgpLiIpLHRoaXMuaW50ZXJzZWN0c0JveChuKX07R2UucHJvdG90eXBlLmlzSW50ZXJzZWN0aW9uU3BoZXJlPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDM6IC5pc0ludGVyc2VjdGlvblNwaGVyZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmludGVyc2VjdHNTcGhlcmUoKS4iKSx0aGlzLmludGVyc2VjdHNTcGhlcmUobil9O0dlLnByb3RvdHlwZS5zaXplPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDM6IC5zaXplKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0U2l6ZSgpLiIpLHRoaXMuZ2V0U2l6ZShuKX07c2kucHJvdG90eXBlLmVtcHR5PWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuU3BoZXJlOiAuZW1wdHkoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5pc0VtcHR5KCkuIiksdGhpcy5pc0VtcHR5KCl9O3FyLnByb3RvdHlwZS5zZXRGcm9tTWF0cml4PWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkZydXN0dW06IC5zZXRGcm9tTWF0cml4KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuc2V0RnJvbVByb2plY3Rpb25NYXRyaXgoKS4iKSx0aGlzLnNldEZyb21Qcm9qZWN0aW9uTWF0cml4KG4pfTtZaC5wcm90b3R5cGUuY2VudGVyPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkxpbmUzOiAuY2VudGVyKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0Q2VudGVyKCkuIiksdGhpcy5nZXRDZW50ZXIobil9O2RlLnByb3RvdHlwZS5mbGF0dGVuVG9BcnJheU9mZnNldD1mdW5jdGlvbihuLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDM6IC5mbGF0dGVuVG9BcnJheU9mZnNldCgpIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSAudG9BcnJheSgpIGluc3RlYWQuIiksdGhpcy50b0FycmF5KG4sdCl9O2RlLnByb3RvdHlwZS5tdWx0aXBseVZlY3RvcjM9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4MzogLm11bHRpcGx5VmVjdG9yMygpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSB2ZWN0b3IuYXBwbHlNYXRyaXgzKCBtYXRyaXggKSBpbnN0ZWFkLiIpLG4uYXBwbHlNYXRyaXgzKHRoaXMpfTtkZS5wcm90b3R5cGUubXVsdGlwbHlWZWN0b3IzQXJyYXk9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXgzOiAubXVsdGlwbHlWZWN0b3IzQXJyYXkoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtkZS5wcm90b3R5cGUuYXBwbHlUb0J1ZmZlckF0dHJpYnV0ZT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXgzOiAuYXBwbHlUb0J1ZmZlckF0dHJpYnV0ZSgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBhdHRyaWJ1dGUuYXBwbHlNYXRyaXgzKCBtYXRyaXggKSBpbnN0ZWFkLiIpLG4uYXBwbHlNYXRyaXgzKHRoaXMpfTtkZS5wcm90b3R5cGUuYXBwbHlUb1ZlY3RvcjNBcnJheT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDM6IC5hcHBseVRvVmVjdG9yM0FycmF5KCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07ZGUucHJvdG90eXBlLmdldEludmVyc2U9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4MzogLmdldEludmVyc2UoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgbWF0cml4SW52LmNvcHkoIG1hdHJpeCApLmludmVydCgpOyBpbnN0ZWFkLiIpLHRoaXMuY29weShuKS5pbnZlcnQoKX07d3QucHJvdG90eXBlLmV4dHJhY3RQb3NpdGlvbj1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAuZXh0cmFjdFBvc2l0aW9uKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuY29weVBvc2l0aW9uKCkuIiksdGhpcy5jb3B5UG9zaXRpb24obil9O3d0LnByb3RvdHlwZS5mbGF0dGVuVG9BcnJheU9mZnNldD1mdW5jdGlvbihuLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5mbGF0dGVuVG9BcnJheU9mZnNldCgpIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSAudG9BcnJheSgpIGluc3RlYWQuIiksdGhpcy50b0FycmF5KG4sdCl9O3d0LnByb3RvdHlwZS5nZXRQb3NpdGlvbj1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5nZXRQb3NpdGlvbigpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBWZWN0b3IzLnNldEZyb21NYXRyaXhQb3NpdGlvbiggbWF0cml4ICkgaW5zdGVhZC4iKSxuZXcgVCgpLnNldEZyb21NYXRyaXhDb2x1bW4odGhpcywzKX07d3QucHJvdG90eXBlLnNldFJvdGF0aW9uRnJvbVF1YXRlcm5pb249ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLnNldFJvdGF0aW9uRnJvbVF1YXRlcm5pb24oKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5tYWtlUm90YXRpb25Gcm9tUXVhdGVybmlvbigpLiIpLHRoaXMubWFrZVJvdGF0aW9uRnJvbVF1YXRlcm5pb24obil9O3d0LnByb3RvdHlwZS5tdWx0aXBseVRvQXJyYXk9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5tdWx0aXBseVRvQXJyYXkoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTt3dC5wcm90b3R5cGUubXVsdGlwbHlWZWN0b3IzPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5tdWx0aXBseVZlY3RvcjMoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgdmVjdG9yLmFwcGx5TWF0cml4NCggbWF0cml4ICkgaW5zdGVhZC4iKSxuLmFwcGx5TWF0cml4NCh0aGlzKX07d3QucHJvdG90eXBlLm11bHRpcGx5VmVjdG9yND1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAubXVsdGlwbHlWZWN0b3I0KCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIHZlY3Rvci5hcHBseU1hdHJpeDQoIG1hdHJpeCApIGluc3RlYWQuIiksbi5hcHBseU1hdHJpeDQodGhpcyl9O3d0LnByb3RvdHlwZS5tdWx0aXBseVZlY3RvcjNBcnJheT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IC5tdWx0aXBseVZlY3RvcjNBcnJheSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O3d0LnByb3RvdHlwZS5yb3RhdGVBeGlzPWZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLnJvdGF0ZUF4aXMoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgVmVjdG9yMy50cmFuc2Zvcm1EaXJlY3Rpb24oIG1hdHJpeCApIGluc3RlYWQuIiksbi50cmFuc2Zvcm1EaXJlY3Rpb24odGhpcyl9O3d0LnByb3RvdHlwZS5jcm9zc1ZlY3Rvcj1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAuY3Jvc3NWZWN0b3IoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgdmVjdG9yLmFwcGx5TWF0cml4NCggbWF0cml4ICkgaW5zdGVhZC4iKSxuLmFwcGx5TWF0cml4NCh0aGlzKX07d3QucHJvdG90eXBlLnRyYW5zbGF0ZT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IC50cmFuc2xhdGUoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTt3dC5wcm90b3R5cGUucm90YXRlWD1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IC5yb3RhdGVYKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07d3QucHJvdG90eXBlLnJvdGF0ZVk9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXg0OiAucm90YXRlWSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O3d0LnByb3RvdHlwZS5yb3RhdGVaPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWF0cml4NDogLnJvdGF0ZVooKSBoYXMgYmVlbiByZW1vdmVkLiIpfTt3dC5wcm90b3R5cGUucm90YXRlQnlBeGlzPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWF0cml4NDogLnJvdGF0ZUJ5QXhpcygpIGhhcyBiZWVuIHJlbW92ZWQuIil9O3d0LnByb3RvdHlwZS5hcHBseVRvQnVmZmVyQXR0cmlidXRlPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5hcHBseVRvQnVmZmVyQXR0cmlidXRlKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIGF0dHJpYnV0ZS5hcHBseU1hdHJpeDQoIG1hdHJpeCApIGluc3RlYWQuIiksbi5hcHBseU1hdHJpeDQodGhpcyl9O3d0LnByb3RvdHlwZS5hcHBseVRvVmVjdG9yM0FycmF5PWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWF0cml4NDogLmFwcGx5VG9WZWN0b3IzQXJyYXkoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTt3dC5wcm90b3R5cGUubWFrZUZydXN0dW09ZnVuY3Rpb24obix0LGUsaSxyLHMpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5tYWtlRnJ1c3R1bSgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSAubWFrZVBlcnNwZWN0aXZlKCBsZWZ0LCByaWdodCwgdG9wLCBib3R0b20sIG5lYXIsIGZhciApIGluc3RlYWQuIiksdGhpcy5tYWtlUGVyc3BlY3RpdmUobix0LGksZSxyLHMpfTt3dC5wcm90b3R5cGUuZ2V0SW52ZXJzZT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAuZ2V0SW52ZXJzZSgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBtYXRyaXhJbnYuY29weSggbWF0cml4ICkuaW52ZXJ0KCk7IGluc3RlYWQuIiksdGhpcy5jb3B5KG4pLmludmVydCgpfTtqZS5wcm90b3R5cGUuaXNJbnRlcnNlY3Rpb25MaW5lPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlBsYW5lOiAuaXNJbnRlcnNlY3Rpb25MaW5lKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaW50ZXJzZWN0c0xpbmUoKS4iKSx0aGlzLmludGVyc2VjdHNMaW5lKG4pfTtFZS5wcm90b3R5cGUubXVsdGlwbHlWZWN0b3IzPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlF1YXRlcm5pb246IC5tdWx0aXBseVZlY3RvcjMoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgaXMgbm93IHZlY3Rvci5hcHBseVF1YXRlcm5pb24oIHF1YXRlcm5pb24gKSBpbnN0ZWFkLiIpLG4uYXBwbHlRdWF0ZXJuaW9uKHRoaXMpfTtFZS5wcm90b3R5cGUuaW52ZXJzZT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlF1YXRlcm5pb246IC5pbnZlcnNlKCkgaGFzIGJlZW4gcmVuYW1lZCB0byBpbnZlcnQoKS4iKSx0aGlzLmludmVydCgpfTtvaS5wcm90b3R5cGUuaXNJbnRlcnNlY3Rpb25Cb3g9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUmF5OiAuaXNJbnRlcnNlY3Rpb25Cb3goKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5pbnRlcnNlY3RzQm94KCkuIiksdGhpcy5pbnRlcnNlY3RzQm94KG4pfTtvaS5wcm90b3R5cGUuaXNJbnRlcnNlY3Rpb25QbGFuZT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5SYXk6IC5pc0ludGVyc2VjdGlvblBsYW5lKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaW50ZXJzZWN0c1BsYW5lKCkuIiksdGhpcy5pbnRlcnNlY3RzUGxhbmUobil9O29pLnByb3RvdHlwZS5pc0ludGVyc2VjdGlvblNwaGVyZT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5SYXk6IC5pc0ludGVyc2VjdGlvblNwaGVyZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmludGVyc2VjdHNTcGhlcmUoKS4iKSx0aGlzLmludGVyc2VjdHNTcGhlcmUobil9O3JlLnByb3RvdHlwZS5hcmVhPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVHJpYW5nbGU6IC5hcmVhKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0QXJlYSgpLiIpLHRoaXMuZ2V0QXJlYSgpfTtyZS5wcm90b3R5cGUuYmFyeWNvb3JkRnJvbVBvaW50PWZ1bmN0aW9uKG4sdCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVHJpYW5nbGU6IC5iYXJ5Y29vcmRGcm9tUG9pbnQoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRCYXJ5Y29vcmQoKS4iKSx0aGlzLmdldEJhcnljb29yZChuLHQpfTtyZS5wcm90b3R5cGUubWlkcG9pbnQ9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVHJpYW5nbGU6IC5taWRwb2ludCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldE1pZHBvaW50KCkuIiksdGhpcy5nZXRNaWRwb2ludChuKX07cmUucHJvdG90eXBlbm9ybWFsPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlRyaWFuZ2xlOiAubm9ybWFsKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0Tm9ybWFsKCkuIiksdGhpcy5nZXROb3JtYWwobil9O3JlLnByb3RvdHlwZS5wbGFuZT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5UcmlhbmdsZTogLnBsYW5lKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0UGxhbmUoKS4iKSx0aGlzLmdldFBsYW5lKG4pfTtyZS5iYXJ5Y29vcmRGcm9tUG9pbnQ9ZnVuY3Rpb24obix0LGUsaSxyKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5UcmlhbmdsZTogLmJhcnljb29yZEZyb21Qb2ludCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldEJhcnljb29yZCgpLiIpLHJlLmdldEJhcnljb29yZChuLHQsZSxpLHIpfTtyZS5ub3JtYWw9ZnVuY3Rpb24obix0LGUsaSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVHJpYW5nbGU6IC5ub3JtYWwoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXROb3JtYWwoKS4iKSxyZS5nZXROb3JtYWwobix0LGUsaSl9O1VuLnByb3RvdHlwZS5leHRyYWN0QWxsUG9pbnRzPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlNoYXBlOiAuZXh0cmFjdEFsbFBvaW50cygpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSAuZXh0cmFjdFBvaW50cygpIGluc3RlYWQuIiksdGhpcy5leHRyYWN0UG9pbnRzKG4pfTtVbi5wcm90b3R5cGUuZXh0cnVkZT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5TaGFwZTogLmV4dHJ1ZGUoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgRXh0cnVkZUdlb21ldHJ5KCkgaW5zdGVhZC4iKSxuZXcgY2kodGhpcyxuKX07VW4ucHJvdG90eXBlLm1ha2VHZW9tZXRyeT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5TaGFwZTogLm1ha2VHZW9tZXRyeSgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBTaGFwZUdlb21ldHJ5KCkgaW5zdGVhZC4iKSxuZXcgcWkodGhpcyxuKX07Sy5wcm90b3R5cGUuZnJvbUF0dHJpYnV0ZT1mdW5jdGlvbihuLHQsZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMjogLmZyb21BdHRyaWJ1dGUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5mcm9tQnVmZmVyQXR0cmlidXRlKCkuIiksdGhpcy5mcm9tQnVmZmVyQXR0cmlidXRlKG4sdCxlKX07Sy5wcm90b3R5cGUuZGlzdGFuY2VUb01hbmhhdHRhbj1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IyOiAuZGlzdGFuY2VUb01hbmhhdHRhbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLm1hbmhhdHRhbkRpc3RhbmNlVG8oKS4iKSx0aGlzLm1hbmhhdHRhbkRpc3RhbmNlVG8obil9O0sucHJvdG90eXBlLmxlbmd0aE1hbmhhdHRhbj1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjI6IC5sZW5ndGhNYW5oYXR0YW4oKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5tYW5oYXR0YW5MZW5ndGgoKS4iKSx0aGlzLm1hbmhhdHRhbkxlbmd0aCgpfTtULnByb3RvdHlwZS5zZXRFdWxlckZyb21Sb3RhdGlvbk1hdHJpeD1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlZlY3RvcjM6IC5zZXRFdWxlckZyb21Sb3RhdGlvbk1hdHJpeCgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBFdWxlci5zZXRGcm9tUm90YXRpb25NYXRyaXgoKSBpbnN0ZWFkLiIpfTtULnByb3RvdHlwZS5zZXRFdWxlckZyb21RdWF0ZXJuaW9uPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuVmVjdG9yMzogLnNldEV1bGVyRnJvbVF1YXRlcm5pb24oKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgRXVsZXIuc2V0RnJvbVF1YXRlcm5pb24oKSBpbnN0ZWFkLiIpfTtULnByb3RvdHlwZS5nZXRQb3NpdGlvbkZyb21NYXRyaXg9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogLmdldFBvc2l0aW9uRnJvbU1hdHJpeCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLnNldEZyb21NYXRyaXhQb3NpdGlvbigpLiIpLHRoaXMuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKG4pfTtULnByb3RvdHlwZS5nZXRTY2FsZUZyb21NYXRyaXg9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogLmdldFNjYWxlRnJvbU1hdHJpeCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLnNldEZyb21NYXRyaXhTY2FsZSgpLiIpLHRoaXMuc2V0RnJvbU1hdHJpeFNjYWxlKG4pfTtULnByb3RvdHlwZS5nZXRDb2x1bW5Gcm9tTWF0cml4PWZ1bmN0aW9uKG4sdCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogLmdldENvbHVtbkZyb21NYXRyaXgoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5zZXRGcm9tTWF0cml4Q29sdW1uKCkuIiksdGhpcy5zZXRGcm9tTWF0cml4Q29sdW1uKHQsbil9O1QucHJvdG90eXBlLmFwcGx5UHJvamVjdGlvbj1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuYXBwbHlQcm9qZWN0aW9uKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIC5hcHBseU1hdHJpeDQoIG0gKSBpbnN0ZWFkLiIpLHRoaXMuYXBwbHlNYXRyaXg0KG4pfTtULnByb3RvdHlwZS5mcm9tQXR0cmlidXRlPWZ1bmN0aW9uKG4sdCxlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuZnJvbUF0dHJpYnV0ZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmZyb21CdWZmZXJBdHRyaWJ1dGUoKS4iKSx0aGlzLmZyb21CdWZmZXJBdHRyaWJ1dGUobix0LGUpfTtULnByb3RvdHlwZS5kaXN0YW5jZVRvTWFuaGF0dGFuPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5kaXN0YW5jZVRvTWFuaGF0dGFuKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAubWFuaGF0dGFuRGlzdGFuY2VUbygpLiIpLHRoaXMubWFuaGF0dGFuRGlzdGFuY2VUbyhuKX07VC5wcm90b3R5cGUubGVuZ3RoTWFuaGF0dGFuPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogLmxlbmd0aE1hbmhhdHRhbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLm1hbmhhdHRhbkxlbmd0aCgpLiIpLHRoaXMubWFuaGF0dGFuTGVuZ3RoKCl9O1d0LnByb3RvdHlwZS5mcm9tQXR0cmlidXRlPWZ1bmN0aW9uKG4sdCxlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3I0OiAuZnJvbUF0dHJpYnV0ZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmZyb21CdWZmZXJBdHRyaWJ1dGUoKS4iKSx0aGlzLmZyb21CdWZmZXJBdHRyaWJ1dGUobix0LGUpfTtXdC5wcm90b3R5cGUubGVuZ3RoTWFuaGF0dGFuPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yNDogLmxlbmd0aE1hbmhhdHRhbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLm1hbmhhdHRhbkxlbmd0aCgpLiIpLHRoaXMubWFuaGF0dGFuTGVuZ3RoKCl9O2t0LnByb3RvdHlwZS5nZXRDaGlsZEJ5TmFtZT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5PYmplY3QzRDogLmdldENoaWxkQnlOYW1lKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0T2JqZWN0QnlOYW1lKCkuIiksdGhpcy5nZXRPYmplY3RCeU5hbWUobil9O2t0LnByb3RvdHlwZS5yZW5kZXJEZXB0aD1mdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuT2JqZWN0M0Q6IC5yZW5kZXJEZXB0aCBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLnJlbmRlck9yZGVyLCBpbnN0ZWFkLiIpfTtrdC5wcm90b3R5cGUudHJhbnNsYXRlPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuT2JqZWN0M0Q6IC50cmFuc2xhdGUoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLnRyYW5zbGF0ZU9uQXhpcyggYXhpcywgZGlzdGFuY2UgKSBpbnN0ZWFkLiIpLHRoaXMudHJhbnNsYXRlT25BeGlzKHQsbil9O2t0LnByb3RvdHlwZS5nZXRXb3JsZFJvdGF0aW9uPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuT2JqZWN0M0Q6IC5nZXRXb3JsZFJvdGF0aW9uKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIFRIUkVFLk9iamVjdDNELmdldFdvcmxkUXVhdGVybmlvbiggdGFyZ2V0ICkgaW5zdGVhZC4iKX07a3QucHJvdG90eXBlLmFwcGx5TWF0cml4PWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdDNEOiAuYXBwbHlNYXRyaXgoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5hcHBseU1hdHJpeDQoKS4iKSx0aGlzLmFwcGx5TWF0cml4NChuKX07T2JqZWN0LmRlZmluZVByb3BlcnRpZXMoa3QucHJvdG90eXBlLHtldWxlck9yZGVyOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5PYmplY3QzRDogLmV1bGVyT3JkZXIgaXMgbm93IC5yb3RhdGlvbi5vcmRlci4iKSx0aGlzLnJvdGF0aW9uLm9yZGVyfSxzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5PYmplY3QzRDogLmV1bGVyT3JkZXIgaXMgbm93IC5yb3RhdGlvbi5vcmRlci4iKSx0aGlzLnJvdGF0aW9uLm9yZGVyPW59fSx1c2VRdWF0ZXJuaW9uOntnZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdDNEOiAudXNlUXVhdGVybmlvbiBoYXMgYmVlbiByZW1vdmVkLiBUaGUgbGlicmFyeSBub3cgdXNlcyBxdWF0ZXJuaW9ucyBieSBkZWZhdWx0LiIpfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdDNEOiAudXNlUXVhdGVybmlvbiBoYXMgYmVlbiByZW1vdmVkLiBUaGUgbGlicmFyeSBub3cgdXNlcyBxdWF0ZXJuaW9ucyBieSBkZWZhdWx0LiIpfX19KTtvZS5wcm90b3R5cGUuc2V0RHJhd01vZGU9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NZXNoOiAuc2V0RHJhd01vZGUoKSBoYXMgYmVlbiByZW1vdmVkLiBUaGUgcmVuZGVyZXIgbm93IGFsd2F5cyBhc3N1bWVzIFRIUkVFLlRyaWFuZ2xlc0RyYXdNb2RlLiBUcmFuc2Zvcm0geW91ciBnZW9tZXRyeSB2aWEgQnVmZmVyR2VvbWV0cnlVdGlscy50b1RyaWFuZ2xlc0RyYXdNb2RlKCkgaWYgbmVjZXNzYXJ5LiIpfTtPYmplY3QuZGVmaW5lUHJvcGVydGllcyhvZS5wcm90b3R5cGUse2RyYXdNb2RlOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS5lcnJvcigiVEhSRUUuTWVzaDogLmRyYXdNb2RlIGhhcyBiZWVuIHJlbW92ZWQuIFRoZSByZW5kZXJlciBub3cgYWx3YXlzIGFzc3VtZXMgVEhSRUUuVHJpYW5nbGVzRHJhd01vZGUuIiksJHd9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1lc2g6IC5kcmF3TW9kZSBoYXMgYmVlbiByZW1vdmVkLiBUaGUgcmVuZGVyZXIgbm93IGFsd2F5cyBhc3N1bWVzIFRIUkVFLlRyaWFuZ2xlc0RyYXdNb2RlLiBUcmFuc2Zvcm0geW91ciBnZW9tZXRyeSB2aWEgQnVmZmVyR2VvbWV0cnlVdGlscy50b1RyaWFuZ2xlc0RyYXdNb2RlKCkgaWYgbmVjZXNzYXJ5LiIpfX19KTtkbC5wcm90b3R5cGUuaW5pdEJvbmVzPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuU2tpbm5lZE1lc2g6IGluaXRCb25lcygpIGhhcyBiZWVuIHJlbW92ZWQuIil9O1NlLnByb3RvdHlwZS5zZXRMZW5zPWZ1bmN0aW9uKG4sdCl7Y29uc29sZS53YXJuKCJUSFJFRS5QZXJzcGVjdGl2ZUNhbWVyYS5zZXRMZW5zIGlzIGRlcHJlY2F0ZWQuIFVzZSAuc2V0Rm9jYWxMZW5ndGggYW5kIC5maWxtR2F1Z2UgZm9yIGEgcGhvdG9ncmFwaGljIHNldHVwLiIpLHQhPT12b2lkIDAmJih0aGlzLmZpbG1HYXVnZT10KSx0aGlzLnNldEZvY2FsTGVuZ3RoKG4pfTtPYmplY3QuZGVmaW5lUHJvcGVydGllcyhZZS5wcm90b3R5cGUse29ubHlTaGFkb3c6e3NldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5vbmx5U2hhZG93IGhhcyBiZWVuIHJlbW92ZWQuIil9fSxzaGFkb3dDYW1lcmFGb3Y6e3NldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93Q2FtZXJhRm92IGlzIG5vdyAuc2hhZG93LmNhbWVyYS5mb3YuIiksdGhpcy5zaGFkb3cuY2FtZXJhLmZvdj1ufX0sc2hhZG93Q2FtZXJhTGVmdDp7c2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dDYW1lcmFMZWZ0IGlzIG5vdyAuc2hhZG93LmNhbWVyYS5sZWZ0LiIpLHRoaXMuc2hhZG93LmNhbWVyYS5sZWZ0PW59fSxzaGFkb3dDYW1lcmFSaWdodDp7c2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dDYW1lcmFSaWdodCBpcyBub3cgLnNoYWRvdy5jYW1lcmEucmlnaHQuIiksdGhpcy5zaGFkb3cuY2FtZXJhLnJpZ2h0PW59fSxzaGFkb3dDYW1lcmFUb3A6e3NldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93Q2FtZXJhVG9wIGlzIG5vdyAuc2hhZG93LmNhbWVyYS50b3AuIiksdGhpcy5zaGFkb3cuY2FtZXJhLnRvcD1ufX0sc2hhZG93Q2FtZXJhQm90dG9tOntzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0NhbWVyYUJvdHRvbSBpcyBub3cgLnNoYWRvdy5jYW1lcmEuYm90dG9tLiIpLHRoaXMuc2hhZG93LmNhbWVyYS5ib3R0b209bn19LHNoYWRvd0NhbWVyYU5lYXI6e3NldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93Q2FtZXJhTmVhciBpcyBub3cgLnNoYWRvdy5jYW1lcmEubmVhci4iKSx0aGlzLnNoYWRvdy5jYW1lcmEubmVhcj1ufX0sc2hhZG93Q2FtZXJhRmFyOntzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0NhbWVyYUZhciBpcyBub3cgLnNoYWRvdy5jYW1lcmEuZmFyLiIpLHRoaXMuc2hhZG93LmNhbWVyYS5mYXI9bn19LHNoYWRvd0NhbWVyYVZpc2libGU6e3NldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dDYW1lcmFWaXNpYmxlIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBuZXcgVEhSRUUuQ2FtZXJhSGVscGVyKCBsaWdodC5zaGFkb3cuY2FtZXJhICkgaW5zdGVhZC4iKX19LHNoYWRvd0JpYXM6e3NldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93QmlhcyBpcyBub3cgLnNoYWRvdy5iaWFzLiIpLHRoaXMuc2hhZG93LmJpYXM9bn19LHNoYWRvd0RhcmtuZXNzOntzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93RGFya25lc3MgaGFzIGJlZW4gcmVtb3ZlZC4iKX19LHNoYWRvd01hcFdpZHRoOntzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd01hcFdpZHRoIGlzIG5vdyAuc2hhZG93Lm1hcFNpemUud2lkdGguIiksdGhpcy5zaGFkb3cubWFwU2l6ZS53aWR0aD1ufX0sc2hhZG93TWFwSGVpZ2h0OntzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd01hcEhlaWdodCBpcyBub3cgLnNoYWRvdy5tYXBTaXplLmhlaWdodC4iKSx0aGlzLnNoYWRvdy5tYXBTaXplLmhlaWdodD1ufX19KTtPYmplY3QuZGVmaW5lUHJvcGVydGllcyhRdC5wcm90b3R5cGUse2xlbmd0aDp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyQXR0cmlidXRlOiAubGVuZ3RoIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSAuY291bnQgaW5zdGVhZC4iKSx0aGlzLmFycmF5Lmxlbmd0aH19LGR5bmFtaWM6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZTogLmR5bmFtaWMgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIC51c2FnZSBpbnN0ZWFkLiIpLHRoaXMudXNhZ2U9PT1ubH0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGU6IC5keW5hbWljIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSAudXNhZ2UgaW5zdGVhZC4iKSx0aGlzLnNldFVzYWdlKG5sKX19fSk7UXQucHJvdG90eXBlLnNldER5bmFtaWM9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyQXR0cmlidXRlOiAuc2V0RHluYW1pYygpIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSAuc2V0VXNhZ2UoKSBpbnN0ZWFkLiIpLHRoaXMuc2V0VXNhZ2Uobj09PSEwP25sOmlvKSx0aGlzfTtRdC5wcm90b3R5cGUuY29weUluZGljZXNBcnJheT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZTogLmNvcHlJbmRpY2VzQXJyYXkoKSBoYXMgYmVlbiByZW1vdmVkLiIpfSxRdC5wcm90b3R5cGUuc2V0QXJyYXk9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGU6IC5zZXRBcnJheSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgQnVmZmVyR2VvbWV0cnkgLnNldEF0dHJpYnV0ZSB0byByZXBsYWNlL3Jlc2l6ZSBhdHRyaWJ1dGUgYnVmZmVycyIpfTtIdC5wcm90b3R5cGUuYWRkSW5kZXg9ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLmFkZEluZGV4KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuc2V0SW5kZXgoKS4iKSx0aGlzLnNldEluZGV4KG4pfTtIdC5wcm90b3R5cGUuYWRkQXR0cmlidXRlPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5hZGRBdHRyaWJ1dGUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5zZXRBdHRyaWJ1dGUoKS4iKSwhKHQmJnQuaXNCdWZmZXJBdHRyaWJ1dGUpJiYhKHQmJnQuaXNJbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZSk/KGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5hZGRBdHRyaWJ1dGUoKSBub3cgZXhwZWN0cyAoIG5hbWUsIGF0dHJpYnV0ZSApLiIpLHRoaXMuc2V0QXR0cmlidXRlKG4sbmV3IFF0KGFyZ3VtZW50c1sxXSxhcmd1bWVudHNbMl0pKSk6bj09PSJpbmRleCI/KGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnkuYWRkQXR0cmlidXRlOiBVc2UgLnNldEluZGV4KCkgZm9yIGluZGV4IGF0dHJpYnV0ZS4iKSx0aGlzLnNldEluZGV4KHQpLHRoaXMpOnRoaXMuc2V0QXR0cmlidXRlKG4sdCl9O0h0LnByb3RvdHlwZS5hZGREcmF3Q2FsbD1mdW5jdGlvbihuLHQsZSl7ZSE9PXZvaWQgMCYmY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLmFkZERyYXdDYWxsKCkgbm8gbG9uZ2VyIHN1cHBvcnRzIGluZGV4T2Zmc2V0LiIpLGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5hZGREcmF3Q2FsbCgpIGlzIG5vdyAuYWRkR3JvdXAoKS4iKSx0aGlzLmFkZEdyb3VwKG4sdCl9O0h0LnByb3RvdHlwZS5jbGVhckRyYXdDYWxscz1mdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5jbGVhckRyYXdDYWxscygpIGlzIG5vdyAuY2xlYXJHcm91cHMoKS4iKSx0aGlzLmNsZWFyR3JvdXBzKCl9O0h0LnByb3RvdHlwZS5jb21wdXRlT2Zmc2V0cz1mdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5jb21wdXRlT2Zmc2V0cygpIGhhcyBiZWVuIHJlbW92ZWQuIil9O0h0LnByb3RvdHlwZS5yZW1vdmVBdHRyaWJ1dGU9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5yZW1vdmVBdHRyaWJ1dGUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5kZWxldGVBdHRyaWJ1dGUoKS4iKSx0aGlzLmRlbGV0ZUF0dHJpYnV0ZShuKX07SHQucHJvdG90eXBlLmFwcGx5TWF0cml4PWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuYXBwbHlNYXRyaXgoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5hcHBseU1hdHJpeDQoKS4iKSx0aGlzLmFwcGx5TWF0cml4NChuKX07T2JqZWN0LmRlZmluZVByb3BlcnRpZXMoSHQucHJvdG90eXBlLHtkcmF3Y2FsbHM6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLmVycm9yKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLmRyYXdjYWxscyBoYXMgYmVlbiByZW5hbWVkIHRvIC5ncm91cHMuIiksdGhpcy5ncm91cHN9fSxvZmZzZXRzOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLm9mZnNldHMgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ3JvdXBzLiIpLHRoaXMuZ3JvdXBzfX19KTtHaS5wcm90b3R5cGUuc2V0RHluYW1pYz1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5JbnRlcmxlYXZlZEJ1ZmZlcjogLnNldER5bmFtaWMoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnNldFVzYWdlKCkgaW5zdGVhZC4iKSx0aGlzLnNldFVzYWdlKG49PT0hMD9ubDppbyksdGhpc307R2kucHJvdG90eXBlLnNldEFycmF5PWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuSW50ZXJsZWF2ZWRCdWZmZXI6IC5zZXRBcnJheSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgQnVmZmVyR2VvbWV0cnkgLnNldEF0dHJpYnV0ZSB0byByZXBsYWNlL3Jlc2l6ZSBhdHRyaWJ1dGUgYnVmZmVycyIpfTtjaS5wcm90b3R5cGUuZ2V0QXJyYXlzPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuRXh0cnVkZUdlb21ldHJ5OiAuZ2V0QXJyYXlzKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07Y2kucHJvdG90eXBlLmFkZFNoYXBlTGlzdD1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkV4dHJ1ZGVHZW9tZXRyeTogLmFkZFNoYXBlTGlzdCgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O2NpLnByb3RvdHlwZS5hZGRTaGFwZT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkV4dHJ1ZGVHZW9tZXRyeTogLmFkZFNoYXBlKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07WXIucHJvdG90eXBlLmRpc3Bvc2U9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5TY2VuZTogLmRpc3Bvc2UoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtUby5wcm90b3R5cGUub25VcGRhdGU9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Vbmlmb3JtOiAub25VcGRhdGUoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2Ugb2JqZWN0Lm9uQmVmb3JlUmVuZGVyKCkgaW5zdGVhZC4iKSx0aGlzfTtPYmplY3QuZGVmaW5lUHJvcGVydGllcyh4ZS5wcm90b3R5cGUse3dyYXBBcm91bmQ6e2dldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuTWF0ZXJpYWw6IC53cmFwQXJvdW5kIGhhcyBiZWVuIHJlbW92ZWQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuTWF0ZXJpYWw6IC53cmFwQXJvdW5kIGhhcyBiZWVuIHJlbW92ZWQuIil9fSxvdmVyZHJhdzp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5NYXRlcmlhbDogLm92ZXJkcmF3IGhhcyBiZWVuIHJlbW92ZWQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuTWF0ZXJpYWw6IC5vdmVyZHJhdyBoYXMgYmVlbiByZW1vdmVkLiIpfX0sd3JhcFJHQjp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0ZXJpYWw6IC53cmFwUkdCIGhhcyBiZWVuIHJlbW92ZWQuIiksbmV3IGZ0fX0sc2hhZGluZzp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuIit0aGlzLnR5cGUrIjogLnNoYWRpbmcgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIHRoZSBib29sZWFuIC5mbGF0U2hhZGluZyBpbnN0ZWFkLiIpfSxzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS4iK3RoaXMudHlwZSsiOiAuc2hhZGluZyBoYXMgYmVlbiByZW1vdmVkLiBVc2UgdGhlIGJvb2xlYW4gLmZsYXRTaGFkaW5nIGluc3RlYWQuIiksdGhpcy5mbGF0U2hhZGluZz1uPT09YzB9fSxzdGVuY2lsTWFzazp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuIit0aGlzLnR5cGUrIjogLnN0ZW5jaWxNYXNrIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSAuc3RlbmNpbEZ1bmNNYXNrIGluc3RlYWQuIiksdGhpcy5zdGVuY2lsRnVuY01hc2t9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLiIrdGhpcy50eXBlKyI6IC5zdGVuY2lsTWFzayBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLnN0ZW5jaWxGdW5jTWFzayBpbnN0ZWFkLiIpLHRoaXMuc3RlbmNpbEZ1bmNNYXNrPW59fSx2ZXJ0ZXhUYW5nZW50czp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS4iK3RoaXMudHlwZSsiOiAudmVydGV4VGFuZ2VudHMgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS4iK3RoaXMudHlwZSsiOiAudmVydGV4VGFuZ2VudHMgaGFzIGJlZW4gcmVtb3ZlZC4iKX19fSk7T2JqZWN0LmRlZmluZVByb3BlcnRpZXMoRm4ucHJvdG90eXBlLHtkZXJpdmF0aXZlczp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuU2hhZGVyTWF0ZXJpYWw6IC5kZXJpdmF0aXZlcyBoYXMgYmVlbiBtb3ZlZCB0byAuZXh0ZW5zaW9ucy5kZXJpdmF0aXZlcy4iKSx0aGlzLmV4dGVuc2lvbnMuZGVyaXZhdGl2ZXN9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLiBTaGFkZXJNYXRlcmlhbDogLmRlcml2YXRpdmVzIGhhcyBiZWVuIG1vdmVkIHRvIC5leHRlbnNpb25zLmRlcml2YXRpdmVzLiIpLHRoaXMuZXh0ZW5zaW9ucy5kZXJpdmF0aXZlcz1ufX19KTtWdC5wcm90b3R5cGUuY2xlYXJUYXJnZXQ9ZnVuY3Rpb24obix0LGUsaSl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuY2xlYXJUYXJnZXQoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnNldFJlbmRlclRhcmdldCgpIGFuZCAuY2xlYXIoKSBpbnN0ZWFkLiIpLHRoaXMuc2V0UmVuZGVyVGFyZ2V0KG4pLHRoaXMuY2xlYXIodCxlLGkpfTtWdC5wcm90b3R5cGUuYW5pbWF0ZT1mdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5hbmltYXRlKCkgaXMgbm93IC5zZXRBbmltYXRpb25Mb29wKCkuIiksdGhpcy5zZXRBbmltYXRpb25Mb29wKG4pfTtWdC5wcm90b3R5cGUuZ2V0Q3VycmVudFJlbmRlclRhcmdldD1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRDdXJyZW50UmVuZGVyVGFyZ2V0KCkgaXMgbm93IC5nZXRSZW5kZXJUYXJnZXQoKS4iKSx0aGlzLmdldFJlbmRlclRhcmdldCgpfTtWdC5wcm90b3R5cGUuZ2V0TWF4QW5pc290cm9weT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRNYXhBbmlzb3Ryb3B5KCkgaXMgbm93IC5jYXBhYmlsaXRpZXMuZ2V0TWF4QW5pc290cm9weSgpLiIpLHRoaXMuY2FwYWJpbGl0aWVzLmdldE1heEFuaXNvdHJvcHkoKX07VnQucHJvdG90eXBlLmdldFByZWNpc2lvbj1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRQcmVjaXNpb24oKSBpcyBub3cgLmNhcGFiaWxpdGllcy5wcmVjaXNpb24uIiksdGhpcy5jYXBhYmlsaXRpZXMucHJlY2lzaW9ufTtWdC5wcm90b3R5cGUucmVzZXRHTFN0YXRlPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnJlc2V0R0xTdGF0ZSgpIGlzIG5vdyAuc3RhdGUucmVzZXQoKS4iKSx0aGlzLnN0YXRlLnJlc2V0KCl9O1Z0LnByb3RvdHlwZS5zdXBwb3J0c0Zsb2F0VGV4dHVyZXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNGbG9hdFRleHR1cmVzKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ09FU190ZXh0dXJlX2Zsb2F0JyApLiIpLHRoaXMuZXh0ZW5zaW9ucy5nZXQoIk9FU190ZXh0dXJlX2Zsb2F0Iil9O1Z0LnByb3RvdHlwZS5zdXBwb3J0c0hhbGZGbG9hdFRleHR1cmVzPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnN1cHBvcnRzSGFsZkZsb2F0VGV4dHVyZXMoKSBpcyBub3cgLmV4dGVuc2lvbnMuZ2V0KCAnT0VTX3RleHR1cmVfaGFsZl9mbG9hdCcgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJPRVNfdGV4dHVyZV9oYWxmX2Zsb2F0Iil9O1Z0LnByb3RvdHlwZS5zdXBwb3J0c1N0YW5kYXJkRGVyaXZhdGl2ZXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNTdGFuZGFyZERlcml2YXRpdmVzKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ09FU19zdGFuZGFyZF9kZXJpdmF0aXZlcycgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJPRVNfc3RhbmRhcmRfZGVyaXZhdGl2ZXMiKX07VnQucHJvdG90eXBlLnN1cHBvcnRzQ29tcHJlc3NlZFRleHR1cmVTM1RDPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnN1cHBvcnRzQ29tcHJlc3NlZFRleHR1cmVTM1RDKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjJyApLiIpLHRoaXMuZXh0ZW5zaW9ucy5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjIil9O1Z0LnByb3RvdHlwZS5zdXBwb3J0c0NvbXByZXNzZWRUZXh0dXJlUFZSVEM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNDb21wcmVzc2VkVGV4dHVyZVBWUlRDKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9wdnJ0YycgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfcHZydGMiKX07VnQucHJvdG90eXBlLnN1cHBvcnRzQmxlbmRNaW5NYXg9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNCbGVuZE1pbk1heCgpIGlzIG5vdyAuZXh0ZW5zaW9ucy5nZXQoICdFWFRfYmxlbmRfbWlubWF4JyApLiIpLHRoaXMuZXh0ZW5zaW9ucy5nZXQoIkVYVF9ibGVuZF9taW5tYXgiKX07VnQucHJvdG90eXBlLnN1cHBvcnRzVmVydGV4VGV4dHVyZXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNWZXJ0ZXhUZXh0dXJlcygpIGlzIG5vdyAuY2FwYWJpbGl0aWVzLnZlcnRleFRleHR1cmVzLiIpLHRoaXMuY2FwYWJpbGl0aWVzLnZlcnRleFRleHR1cmVzfTtWdC5wcm90b3R5cGUuc3VwcG9ydHNJbnN0YW5jZWRBcnJheXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNJbnN0YW5jZWRBcnJheXMoKSBpcyBub3cgLmV4dGVuc2lvbnMuZ2V0KCAnQU5HTEVfaW5zdGFuY2VkX2FycmF5cycgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJBTkdMRV9pbnN0YW5jZWRfYXJyYXlzIil9O1Z0LnByb3RvdHlwZS5lbmFibGVTY2lzc29yVGVzdD1mdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5lbmFibGVTY2lzc29yVGVzdCgpIGlzIG5vdyAuc2V0U2Npc3NvclRlc3QoKS4iKSx0aGlzLnNldFNjaXNzb3JUZXN0KG4pfTtWdC5wcm90b3R5cGUuaW5pdE1hdGVyaWFsPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuaW5pdE1hdGVyaWFsKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07VnQucHJvdG90eXBlLmFkZFByZVBsdWdpbj1mdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLmFkZFByZVBsdWdpbigpIGhhcyBiZWVuIHJlbW92ZWQuIil9O1Z0LnByb3RvdHlwZS5hZGRQb3N0UGx1Z2luPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuYWRkUG9zdFBsdWdpbigpIGhhcyBiZWVuIHJlbW92ZWQuIil9O1Z0LnByb3RvdHlwZS51cGRhdGVTaGFkb3dNYXA9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC51cGRhdGVTaGFkb3dNYXAoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtWdC5wcm90b3R5cGUuc2V0RmFjZUN1bGxpbmc9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zZXRGYWNlQ3VsbGluZygpIGhhcyBiZWVuIHJlbW92ZWQuIil9O1Z0LnByb3RvdHlwZS5hbGxvY1RleHR1cmVVbml0PWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuYWxsb2NUZXh0dXJlVW5pdCgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O1Z0LnByb3RvdHlwZS5zZXRUZXh0dXJlPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc2V0VGV4dHVyZSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O1Z0LnByb3RvdHlwZS5zZXRUZXh0dXJlMkQ9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zZXRUZXh0dXJlMkQoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtWdC5wcm90b3R5cGUuc2V0VGV4dHVyZUN1YmU9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zZXRUZXh0dXJlQ3ViZSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O1Z0LnByb3RvdHlwZS5nZXRBY3RpdmVNaXBNYXBMZXZlbD1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRBY3RpdmVNaXBNYXBMZXZlbCgpIGlzIG5vdyAuZ2V0QWN0aXZlTWlwbWFwTGV2ZWwoKS4iKSx0aGlzLmdldEFjdGl2ZU1pcG1hcExldmVsKCl9O09iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKFZ0LnByb3RvdHlwZSx7c2hhZG93TWFwRW5hYmxlZDp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc2hhZG93TWFwLmVuYWJsZWR9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXBFbmFibGVkIGlzIG5vdyAuc2hhZG93TWFwLmVuYWJsZWQuIiksdGhpcy5zaGFkb3dNYXAuZW5hYmxlZD1ufX0sc2hhZG93TWFwVHlwZTp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc2hhZG93TWFwLnR5cGV9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXBUeXBlIGlzIG5vdyAuc2hhZG93TWFwLnR5cGUuIiksdGhpcy5zaGFkb3dNYXAudHlwZT1ufX0sc2hhZG93TWFwQ3VsbEZhY2U6e2dldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcEN1bGxGYWNlIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcEN1bGxGYWNlIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9fSxjb250ZXh0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuY29udGV4dCBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLmdldENvbnRleHQoKSBpbnN0ZWFkLiIpLHRoaXMuZ2V0Q29udGV4dCgpfX0sdnI6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC52ciBoYXMgYmVlbiByZW5hbWVkIHRvIC54ciIpLHRoaXMueHJ9fSxnYW1tYUlucHV0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuZ2FtbWFJbnB1dCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgdGhlIGVuY29kaW5nIGZvciB0ZXh0dXJlcyB2aWEgVGV4dHVyZS5lbmNvZGluZyBpbnN0ZWFkLiIpLCExfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nYW1tYUlucHV0IGhhcyBiZWVuIHJlbW92ZWQuIFNldCB0aGUgZW5jb2RpbmcgZm9yIHRleHR1cmVzIHZpYSBUZXh0dXJlLmVuY29kaW5nIGluc3RlYWQuIil9fSxnYW1tYU91dHB1dDp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLmdhbW1hT3V0cHV0IGhhcyBiZWVuIHJlbW92ZWQuIFNldCBXZWJHTFJlbmRlcmVyLm91dHB1dEVuY29kaW5nIGluc3RlYWQuIiksITF9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nYW1tYU91dHB1dCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgV2ViR0xSZW5kZXJlci5vdXRwdXRFbmNvZGluZyBpbnN0ZWFkLiIpLHRoaXMub3V0cHV0RW5jb2Rpbmc9bj09PSEwPyR0OnJpfX0sdG9uZU1hcHBpbmdXaGl0ZVBvaW50OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAudG9uZU1hcHBpbmdXaGl0ZVBvaW50IGhhcyBiZWVuIHJlbW92ZWQuIiksMX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAudG9uZU1hcHBpbmdXaGl0ZVBvaW50IGhhcyBiZWVuIHJlbW92ZWQuIil9fSxnYW1tYUZhY3Rvcjp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLmdhbW1hRmFjdG9yIGhhcyBiZWVuIHJlbW92ZWQuIiksMn0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuZ2FtbWFGYWN0b3IgaGFzIGJlZW4gcmVtb3ZlZC4iKX19fSk7T2JqZWN0LmRlZmluZVByb3BlcnRpZXMoVDAucHJvdG90eXBlLHtjdWxsRmFjZTp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc2hhZG93TWFwLmN1bGxGYWNlIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcC5jdWxsRmFjZSBoYXMgYmVlbiByZW1vdmVkLiBTZXQgTWF0ZXJpYWwuc2hhZG93U2lkZSBpbnN0ZWFkLiIpfX0scmVuZGVyUmV2ZXJzZVNpZGVkOntnZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXAucmVuZGVyUmV2ZXJzZVNpZGVkIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcC5yZW5kZXJSZXZlcnNlU2lkZWQgaGFzIGJlZW4gcmVtb3ZlZC4gU2V0IE1hdGVyaWFsLnNoYWRvd1NpZGUgaW5zdGVhZC4iKX19LHJlbmRlclNpbmdsZVNpZGVkOntnZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXAucmVuZGVyU2luZ2xlU2lkZWQgaGFzIGJlZW4gcmVtb3ZlZC4gU2V0IE1hdGVyaWFsLnNoYWRvd1NpZGUgaW5zdGVhZC4iKX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc2hhZG93TWFwLnJlbmRlclNpbmdsZVNpZGVkIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9fX0pO09iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKE5lLnByb3RvdHlwZSx7d3JhcFM6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAud3JhcFMgaXMgbm93IC50ZXh0dXJlLndyYXBTLiIpLHRoaXMudGV4dHVyZS53cmFwU30sc2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC53cmFwUyBpcyBub3cgLnRleHR1cmUud3JhcFMuIiksdGhpcy50ZXh0dXJlLndyYXBTPW59fSx3cmFwVDp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC53cmFwVCBpcyBub3cgLnRleHR1cmUud3JhcFQuIiksdGhpcy50ZXh0dXJlLndyYXBUfSxzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLndyYXBUIGlzIG5vdyAudGV4dHVyZS53cmFwVC4iKSx0aGlzLnRleHR1cmUud3JhcFQ9bn19LG1hZ0ZpbHRlcjp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5tYWdGaWx0ZXIgaXMgbm93IC50ZXh0dXJlLm1hZ0ZpbHRlci4iKSx0aGlzLnRleHR1cmUubWFnRmlsdGVyfSxzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLm1hZ0ZpbHRlciBpcyBub3cgLnRleHR1cmUubWFnRmlsdGVyLiIpLHRoaXMudGV4dHVyZS5tYWdGaWx0ZXI9bn19LG1pbkZpbHRlcjp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5taW5GaWx0ZXIgaXMgbm93IC50ZXh0dXJlLm1pbkZpbHRlci4iKSx0aGlzLnRleHR1cmUubWluRmlsdGVyfSxzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLm1pbkZpbHRlciBpcyBub3cgLnRleHR1cmUubWluRmlsdGVyLiIpLHRoaXMudGV4dHVyZS5taW5GaWx0ZXI9bn19LGFuaXNvdHJvcHk6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAuYW5pc290cm9weSBpcyBub3cgLnRleHR1cmUuYW5pc290cm9weS4iKSx0aGlzLnRleHR1cmUuYW5pc290cm9weX0sc2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5hbmlzb3Ryb3B5IGlzIG5vdyAudGV4dHVyZS5hbmlzb3Ryb3B5LiIpLHRoaXMudGV4dHVyZS5hbmlzb3Ryb3B5PW59fSxvZmZzZXQ6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAub2Zmc2V0IGlzIG5vdyAudGV4dHVyZS5vZmZzZXQuIiksdGhpcy50ZXh0dXJlLm9mZnNldH0sc2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5vZmZzZXQgaXMgbm93IC50ZXh0dXJlLm9mZnNldC4iKSx0aGlzLnRleHR1cmUub2Zmc2V0PW59fSxyZXBlYXQ6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAucmVwZWF0IGlzIG5vdyAudGV4dHVyZS5yZXBlYXQuIiksdGhpcy50ZXh0dXJlLnJlcGVhdH0sc2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5yZXBlYXQgaXMgbm93IC50ZXh0dXJlLnJlcGVhdC4iKSx0aGlzLnRleHR1cmUucmVwZWF0PW59fSxmb3JtYXQ6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAuZm9ybWF0IGlzIG5vdyAudGV4dHVyZS5mb3JtYXQuIiksdGhpcy50ZXh0dXJlLmZvcm1hdH0sc2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5mb3JtYXQgaXMgbm93IC50ZXh0dXJlLmZvcm1hdC4iKSx0aGlzLnRleHR1cmUuZm9ybWF0PW59fSx0eXBlOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLnR5cGUgaXMgbm93IC50ZXh0dXJlLnR5cGUuIiksdGhpcy50ZXh0dXJlLnR5cGV9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAudHlwZSBpcyBub3cgLnRleHR1cmUudHlwZS4iKSx0aGlzLnRleHR1cmUudHlwZT1ufX0sZ2VuZXJhdGVNaXBtYXBzOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLmdlbmVyYXRlTWlwbWFwcyBpcyBub3cgLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzLiIpLHRoaXMudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHN9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAuZ2VuZXJhdGVNaXBtYXBzIGlzIG5vdyAudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHMuIiksdGhpcy50ZXh0dXJlLmdlbmVyYXRlTWlwbWFwcz1ufX19KTtCaC5wcm90b3R5cGUubG9hZD1mdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLkF1ZGlvOiAubG9hZCBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgVEhSRUUuQXVkaW9Mb2FkZXIgaW5zdGVhZC4iKTtsZXQgdD10aGlzO3JldHVybiBuZXcgRmgoKS5sb2FkKG4sZnVuY3Rpb24oaSl7dC5zZXRCdWZmZXIoaSl9KSx0aGlzfTtPaC5wcm90b3R5cGUuZ2V0RGF0YT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkF1ZGlvQW5hbHlzZXI6IC5nZXREYXRhKCkgaXMgbm93IC5nZXRGcmVxdWVuY3lEYXRhKCkuIiksdGhpcy5nZXRGcmVxdWVuY3lEYXRhKCl9O2FvLnByb3RvdHlwZS51cGRhdGVDdWJlTWFwPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQ3ViZUNhbWVyYTogLnVwZGF0ZUN1YmVNYXAoKSBpcyBub3cgLnVwZGF0ZSgpLiIpLHRoaXMudXBkYXRlKG4sdCl9O2FvLnByb3RvdHlwZS5jbGVhcj1mdW5jdGlvbihuLHQsZSxpKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5DdWJlQ2FtZXJhOiAuY2xlYXIoKSBpcyBub3cgLnJlbmRlclRhcmdldC5jbGVhcigpLiIpLHRoaXMucmVuZGVyVGFyZ2V0LmNsZWFyKG4sdCxlLGkpfTtObi5jcm9zc09yaWdpbj12b2lkIDA7Tm4ubG9hZFRleHR1cmU9ZnVuY3Rpb24obix0LGUsaSl7Y29uc29sZS53YXJuKCJUSFJFRS5JbWFnZVV0aWxzLmxvYWRUZXh0dXJlIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSBUSFJFRS5UZXh0dXJlTG9hZGVyKCkgaW5zdGVhZC4iKTtsZXQgcj1uZXcgRWg7ci5zZXRDcm9zc09yaWdpbih0aGlzLmNyb3NzT3JpZ2luKTtsZXQgcz1yLmxvYWQobixlLHZvaWQgMCxpKTtyZXR1cm4gdCYmKHMubWFwcGluZz10KSxzfTtObi5sb2FkVGV4dHVyZUN1YmU9ZnVuY3Rpb24obix0LGUsaSl7Y29uc29sZS53YXJuKCJUSFJFRS5JbWFnZVV0aWxzLmxvYWRUZXh0dXJlQ3ViZSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgVEhSRUUuQ3ViZVRleHR1cmVMb2FkZXIoKSBpbnN0ZWFkLiIpO2xldCByPW5ldyBTaDtyLnNldENyb3NzT3JpZ2luKHRoaXMuY3Jvc3NPcmlnaW4pO2xldCBzPXIubG9hZChuLGUsdm9pZCAwLGkpO3JldHVybiB0JiYocy5tYXBwaW5nPXQpLHN9O05uLmxvYWRDb21wcmVzc2VkVGV4dHVyZT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkltYWdlVXRpbHMubG9hZENvbXByZXNzZWRUZXh0dXJlIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBUSFJFRS5ERFNMb2FkZXIgaW5zdGVhZC4iKX07Tm4ubG9hZENvbXByZXNzZWRUZXh0dXJlQ3ViZT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkltYWdlVXRpbHMubG9hZENvbXByZXNzZWRUZXh0dXJlQ3ViZSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgVEhSRUUuRERTTG9hZGVyIGluc3RlYWQuIil9O3R5cGVvZiBfX1RIUkVFX0RFVlRPT0xTX18hPSJ1bmRlZmluZWQiJiZfX1RIUkVFX0RFVlRPT0xTX18uZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoInJlZ2lzdGVyIix7ZGV0YWlsOntyZXZpc2lvbjokaH19KSk7dHlwZW9mIHdpbmRvdyE9InVuZGVmaW5lZCImJih3aW5kb3cuX19USFJFRV9fP2NvbnNvbGUud2FybigiV0FSTklORzogTXVsdGlwbGUgaW5zdGFuY2VzIG9mIFRocmVlLmpzIGJlaW5nIGltcG9ydGVkLiIpOndpbmRvdy5fX1RIUkVFX189JGgpO2Z1bmN0aW9uIHJmKG4sdCxlKXtpZihlPT09MSlyZXR1cm4gbmV3IGZ0KHQpO2xldCBpPXBpKHQpO2lmKCFpKXRocm93IG5ldyBFcnJvcihgZDMgZmFpbGVkIHRvIHJlY29nbml6ZSB0aGUgY29sb3I6ICR7dH1gKTtyZXR1cm4gbmV3IGZ0KHNjKGksbikoMS1lKSl9dmFyIGduOyhmdW5jdGlvbihuKXtuW24uQ0lSQ0xFPTBdPSJDSVJDTEUiLG5bbi5MSU5FPTFdPSJMSU5FIixuW24uVFJJQU5HTEU9Ml09IlRSSUFOR0xFIixuW24uVFJBUEVaT0lEPTNdPSJUUkFQRVpPSUQifSkoZ258fChnbj17fSkpO2Z1bmN0aW9uIE4wKG4sdCl7bGV0IGU9dC5sZW5ndGgvMixpPW4uYXR0cmlidXRlcy5wb3NpdGlvbjsoIWl8fGkuY291bnQhPT1lKjMpJiYoaT1uZXcgUXQobmV3IEZsb2F0MzJBcnJheShlKjMpLDMpLG4uc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsaSkpO2xldCByPWkuYXJyYXk7Zm9yKGxldCBzPTA7czxlO3MrKylyW3MqM109dFtzKjJdLHJbcyozKzFdPXRbcyoyKzFdO2kubmVlZHNVcGRhdGU9ITAsbi5zZXREcmF3UmFuZ2UoMCxlKjMpLG4uY29tcHV0ZUJvdW5kaW5nU3BoZXJlKCl9ZnVuY3Rpb24gRjAobix0LGUpe2xldCBpPU1hdGgubWF4KHQubGVuZ3RoLzItMSwwKSxyPWkqMiozLHM9ciozLG89bi5hdHRyaWJ1dGVzLnBvc2l0aW9uOyghb3x8by5jb3VudCE9PXIpJiYobz1uZXcgUXQobmV3IEZsb2F0MzJBcnJheShzKSwzKSxuLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG8pKTtsZXQgYT1vLmFycmF5O2ZvcihsZXQgbD0wO2w8aTtsKyspe2xldFtjLHUsaCxmXT1bdFsyKmxdLHRbMipsKzFdLHRbMipsKzJdLHRbMipsKzNdXSxkPW5ldyBLKGMsdSksZz1uZXcgSyhoLGYpLHg9bmV3IEsoaC1jLGYtdSksdj1uZXcgSygteC55LHgueCkuc2V0TGVuZ3RoKGUvMiksbT1kLmNsb25lKCkuYWRkKHYpLHA9ZC5jbG9uZSgpLnN1Yih2KSxiPWcuY2xvbmUoKS5hZGQodiksXz1nLmNsb25lKCkuc3ViKHYpLFM9W20ueCxtLnksMCxwLngscC55LDAsYi54LGIueSwwLGIueCxiLnksMCxwLngscC55LDAsXy54LF8ueSwwXTthLnNldChTLGwqUy5sZW5ndGgpfW8ubmVlZHNVcGRhdGU9ITAsbi5zZXREcmF3UmFuZ2UoMCxzKSxuLmNvbXB1dGVCb3VuZGluZ1NwaGVyZSgpfWZ1bmN0aW9uIElsKG4sdCxlLGkpe2xldHt2aXNpYmxlOnIsY29sb3I6cyxvcGFjaXR5Om99PWk7aWYoQXJyYXkuaXNBcnJheSh0Lm1hdGVyaWFsKSl0aHJvdyBuZXcgRXJyb3IoIkludmFyaWFudCBlcnJvcjogb25seSBleHBlY3Qgb25lIG1hdGVyaWFsIG9uIGFuIG9iamVjdCIpO2xldCBhPXQubWF0ZXJpYWw7aWYoYS52aXNpYmxlIT09ciYmKGEudmlzaWJsZT1yLGEubmVlZHNVcGRhdGU9ITApLCFyKXJldHVybiExO2xldCBsPXJmKG4scyxvIT1udWxsP286MSksYz1lKHQuZ2VvbWV0cnkpO3JldHVybiB0Lmdlb21ldHJ5IT09YyYmKHQuZ2VvbWV0cnk9YyksYS5jb2xvci5lcXVhbHMobCl8fChhLmNvbG9yLnNldChsKSxhLm5lZWRzVXBkYXRlPSEwKSwhMH12YXIgd1Q9e2NyZWF0ZVNjZW5lOigpPT5uZXcgWXJ9LFJvPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe3RoaXMuY29vcmRpbmF0b3I9ZSx0aGlzLnNjZW5lPXdULmNyZWF0ZVNjZW5lKCksdGhpcy5iYWNrZ3JvdW5kQ29sb3I9IiNmZmYiLERpLmlzT2Zmc2NyZWVuQ2FudmFzU3VwcG9ydGVkKCkmJnQgaW5zdGFuY2VvZiBPZmZzY3JlZW5DYW52YXMmJih0LnN0eWxlPXQuc3R5bGV8fHt9KSxyJiZ0LmFkZEV2ZW50TGlzdGVuZXIoIndlYmdsY29udGV4dGxvc3QiLHIpLHRoaXMucmVuZGVyZXI9bmV3IFZ0KHtjYW52YXM6dCxhbnRpYWxpYXM6ITAsYWxwaGE6ITB9KSx0aGlzLnJlbmRlcmVyLnNldFBpeGVsUmF0aW8oaSl9b25SZXNpemUodCl7dGhpcy5yZW5kZXJlci5zZXRTaXplKHQud2lkdGgsdC5oZWlnaHQpfWRlc3Ryb3lPYmplY3QodCl7bGV0IGU9dC5vYmozZDtpZih0aGlzLnNjZW5lLnJlbW92ZShlKSxlIGluc3RhbmNlb2Ygb2Upe2UuZ2VvbWV0cnkuZGlzcG9zZSgpO2xldCBpPUFycmF5LmlzQXJyYXkoZS5tYXRlcmlhbCk/ZS5tYXRlcmlhbDpbZS5tYXRlcmlhbF07Zm9yKGxldCByIG9mIGkpci5kaXNwb3NlKCl9fXNldFVzZURhcmtNb2RlKHQpe3RoaXMuYmFja2dyb3VuZENvbG9yPXQ/IiMzMDMwMzAiOiIjZmZmIn1jcmVhdGVPclVwZGF0ZUxpbmVPYmplY3QodCxlLGkpe3ZhciB1O2lmKCF0JiYhaS52aXNpYmxlKXJldHVybiBudWxsO2xldHt2aXNpYmxlOnIsd2lkdGg6c309aTtpZighdCl7bGV0IGg9cmYodGhpcy5iYWNrZ3JvdW5kQ29sb3IsaS5jb2xvciwodT1pLm9wYWNpdHkpIT1udWxsP3U6MSksZj1uZXcgSHQsZD1uZXcgem4oe2NvbG9yOmh9KSxnPW5ldyBvZShmLGQpO3JldHVybiBkLnZpc2libGU9cixGMChmLGUscyksdGhpcy5zY2VuZS5hZGQoZykse3R5cGU6Z24uTElORSxkYXRhOmUsb2JqM2Q6Zyx3aWR0aDpzfX1sZXR7ZGF0YTpvLG9iajNkOmEsd2lkdGg6bH09dDtyZXR1cm4gSWwodGhpcy5iYWNrZ3JvdW5kQ29sb3IsYSxoPT4oKHMhPT1sfHwhb3x8IURpLmFyZVBvbHlsaW5lc0VxdWFsKG8sZSkpJiZGMChoLGUscyksaCksaSk/e3R5cGU6Z24uTElORSxkYXRhOmUsb2JqM2Q6YSx3aWR0aDpzfTp0fWNyZWF0ZU1lc2godCxlKXtpZighZS52aXNpYmxlKXJldHVybiBudWxsO2xldHt2aXNpYmxlOmksY29sb3I6cixvcGFjaXR5OnN9PWUsbz1yZih0aGlzLmJhY2tncm91bmRDb2xvcixyLHMhPW51bGw/czoxKSxhPW5ldyBraSh7Y29sb3I6byx2aXNpYmxlOml9KTtyZXR1cm4gbmV3IG9lKHQsYSl9Y3JlYXRlT3JVcGRhdGVUcmlhbmdsZU9iamVjdCh0LGUsaSl7bGV0e3NpemU6cn09aSxzPXIqTWF0aC5zcXJ0KDMpLzIsbz1uZXcgRmxvYXQzMkFycmF5KFtlLngtci8yLGUueS1zLzMsZS54K3IvMixlLnktcy8zLGUueCxlLnkrcyoyLzNdKTtpZighdCl7bGV0IGw9bmV3IEh0O04wKGwsbyk7bGV0IGM9dGhpcy5jcmVhdGVNZXNoKGwsaSk7cmV0dXJuIGM9PT1udWxsP251bGw6KHRoaXMuc2NlbmUuYWRkKGMpLHt0eXBlOmduLlRSSUFOR0xFLGRhdGE6ZSxvYmozZDpjfSl9cmV0dXJuIElsKHRoaXMuYmFja2dyb3VuZENvbG9yLHQub2JqM2QsbD0+KE4wKGwsbyksbCksaSk/e3R5cGU6Z24uVFJJQU5HTEUsZGF0YTplLG9iajNkOnQub2JqM2R9OnR9Y3JlYXRlT3JVcGRhdGVDaXJjbGVPYmplY3QodCxlLGkpe2xldHtyYWRpdXM6cn09aSxzPW5ldyBacihpLnJhZGl1cyk7aWYoIXQpe2xldCBhPXRoaXMuY3JlYXRlTWVzaChzLGkpO3JldHVybiBhPT09bnVsbD9udWxsOihhLnBvc2l0aW9uLnNldChlLngsZS55LDApLHRoaXMuc2NlbmUuYWRkKGEpLHt0eXBlOmduLkNJUkNMRSxkYXRhOntsb2M6ZSxyYWRpdXM6cn0sb2JqM2Q6YX0pfXJldHVybiBJbCh0aGlzLmJhY2tncm91bmRDb2xvcix0Lm9iajNkLCgpPT5zLGkpPyh0Lm9iajNkLnBvc2l0aW9uLnNldChlLngsZS55LDApLHt0eXBlOmduLkNJUkNMRSxkYXRhOntsb2M6ZSxyYWRpdXM6cn0sb2JqM2Q6dC5vYmozZH0pOnR9Y3JlYXRlT3JVcGRhdGVUcmFwZXpvaWRPYmplY3QodCxlLGkscil7aWYoZS55IT09aS55KXRocm93IG5ldyBSYW5nZUVycm9yKCJJbnB1dCBlcnJvcjogc3RhcnQueSAhPSBlbmQueS4iKTtsZXR7YWx0aXR1ZGU6c309cixvPTIvTWF0aC5zcXJ0KDMpKnMsYT1uZXcgVW4oW25ldyBLKGUueC1vLzIsZS55LXMvMiksbmV3IEsoZS54LGUueStzLzIpLG5ldyBLKGkueCxpLnkrcy8yKSxuZXcgSyhpLngrby8yLGkueS1zLzIpXSk7YS5hdXRvQ2xvc2U9ITA7bGV0IGw9bmV3IHFpKGEpO2lmKCF0KXtsZXQgdT10aGlzLmNyZWF0ZU1lc2gobCxyKTtyZXR1cm4gdT09PW51bGw/bnVsbDoodGhpcy5zY2VuZS5hZGQodSkse3R5cGU6Z24uVFJBUEVaT0lELGRhdGE6W2UsaV0sb2JqM2Q6dX0pfXJldHVybiBJbCh0aGlzLmJhY2tncm91bmRDb2xvcix0Lm9iajNkLCgpPT5sLHIpP3t0eXBlOmduLlRSQVBFWk9JRCxkYXRhOltlLGldLG9iajNkOnQub2JqM2R9OnR9Zmx1c2goKXt0aGlzLnJlbmRlcmVyLnJlbmRlcih0aGlzLnNjZW5lLHRoaXMuY29vcmRpbmF0b3IuZ2V0Q2FtZXJhKCkpfWRpc3Bvc2UoKXt0aGlzLnJlbmRlcmVyLmRpc3Bvc2UoKX19O3ZhciBObD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMucmVuZGVyQ2FjaGU9dCx0aGlzLnJlbmRlcmVyPWV9c2V0TGluZSh0LGUsaSl7bGV0IHI9dGhpcy5yZW5kZXJlci5jcmVhdGVPclVwZGF0ZUxpbmVPYmplY3QodGhpcy5yZW5kZXJDYWNoZS5nZXRGcm9tUHJldmlvdXNGcmFtZSh0KSxlLGkpO3ImJnRoaXMucmVuZGVyQ2FjaGUuc2V0VG9DdXJyZW50RnJhbWUodCxyKX1zZXRUcmlhbmdsZSh0LGUsaSl7bGV0IHI9dGhpcy5yZW5kZXJlci5jcmVhdGVPclVwZGF0ZVRyaWFuZ2xlT2JqZWN0KHRoaXMucmVuZGVyQ2FjaGUuZ2V0RnJvbVByZXZpb3VzRnJhbWUodCksZSxpKTtyJiZ0aGlzLnJlbmRlckNhY2hlLnNldFRvQ3VycmVudEZyYW1lKHQscil9c2V0Q2lyY2xlKHQsZSxpKXtsZXQgcj10aGlzLnJlbmRlcmVyLmNyZWF0ZU9yVXBkYXRlQ2lyY2xlT2JqZWN0KHRoaXMucmVuZGVyQ2FjaGUuZ2V0RnJvbVByZXZpb3VzRnJhbWUodCksZSxpKTtyJiZ0aGlzLnJlbmRlckNhY2hlLnNldFRvQ3VycmVudEZyYW1lKHQscil9c2V0VHJhcGV6b2lkKHQsZSxpLHIpe2xldCBzPXRoaXMucmVuZGVyZXIuY3JlYXRlT3JVcGRhdGVUcmFwZXpvaWRPYmplY3QodGhpcy5yZW5kZXJDYWNoZS5nZXRGcm9tUHJldmlvdXNGcmFtZSh0KSxlLGkscik7cyYmdGhpcy5yZW5kZXJDYWNoZS5zZXRUb0N1cnJlbnRGcmFtZSh0LHMpfX07dmFyIHNmPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5wcmV2RnJhbWVDYWNoZT1uZXcgTWFwLHRoaXMuY3VyckZyYW1lQ2FjaGU9bmV3IE1hcH1nZXRGcm9tUHJldmlvdXNGcmFtZSh0KXtsZXQgZT10aGlzLnByZXZGcmFtZUNhY2hlLmdldCh0KTtyZXR1cm4gZSE9bnVsbD9lOm51bGx9c2V0VG9DdXJyZW50RnJhbWUodCxlKXt0aGlzLmN1cnJGcmFtZUNhY2hlLnNldCh0LGUpfWZpbmFsaXplRnJhbWVBbmRHZXRSZW1vdmVkKCl7bGV0IHQ9W107Zm9yKGxldFtlLGldb2YgdGhpcy5wcmV2RnJhbWVDYWNoZS5lbnRyaWVzKCkpdGhpcy5jdXJyRnJhbWVDYWNoZS5oYXMoZSl8fHQucHVzaChpKTtyZXR1cm4gdGhpcy5wcmV2RnJhbWVDYWNoZT10aGlzLmN1cnJGcmFtZUNhY2hlLHRoaXMuY3VyckZyYW1lQ2FjaGU9bmV3IE1hcCx0fX0sRmw9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5yYXdTZXJpZXNEYXRhPVtdLHRoaXMuc2VyaWVzPVtdLHRoaXMucGFpbnREaXJ0eT0hMCx0aGlzLnJlbmRlckNhY2hlPW5ldyBzZix0aGlzLmNvb3JkaW5hdGVJZGVudGlmaWVyPW51bGwsdGhpcy5sYXlvdXQ9e3g6MCx3aWR0aDoxLHk6MCxoZWlnaHQ6MX0sdGhpcy5nZXRNZXRhZGF0YU1hcEltcGw9dC5nZXRNZXRhZGF0YU1hcCx0aGlzLmNvb3JkaW5hdG9yPXQuY29vcmRpbmF0b3IsdGhpcy5yZW5kZXJlcj10LnJlbmRlcmVyLHRoaXMucGFpbnRCcnVzaD1uZXcgTmwodGhpcy5yZW5kZXJDYWNoZSx0aGlzLnJlbmRlcmVyKX1zZXRMYXlvdXRSZWN0KHQpeyh0aGlzLmxheW91dC54IT09dC54fHx0aGlzLmxheW91dC53aWR0aCE9PXQud2lkdGh8fHRoaXMubGF5b3V0LnkhPT10Lnl8fHRoaXMubGF5b3V0LmhlaWdodCE9PXQuaGVpZ2h0KSYmKHRoaXMucGFpbnREaXJ0eT0hMCksdGhpcy5sYXlvdXQ9dH1nZXRMYXlvdXRSZWN0KCl7cmV0dXJuIHRoaXMubGF5b3V0fWdldE1ldGFkYXRhTWFwKCl7cmV0dXJuIHRoaXMuZ2V0TWV0YWRhdGFNYXBJbXBsKCl9bWFya0FzUGFpbnREaXJ0eSgpe3RoaXMucGFpbnREaXJ0eT0hMH1yZW5kZXIoKXtpZih0aGlzLnRyYW5zZm9ybUNvb3JkaW5hdGVzSWZTdGFsZSgpLCEhdGhpcy5wYWludERpcnR5KXt0aGlzLnJlZHJhdygpO2ZvcihsZXQgdCBvZiB0aGlzLnJlbmRlckNhY2hlLmZpbmFsaXplRnJhbWVBbmRHZXRSZW1vdmVkKCkpdGhpcy5yZW5kZXJlci5kZXN0cm95T2JqZWN0KHQpO3RoaXMucGFpbnREaXJ0eT0hMX19aXNDb29yZGluYXRlVXBkYXRlZCgpe3JldHVybiB0aGlzLmNvb3JkaW5hdG9yLmdldFVwZGF0ZUlkZW50aWZpZXIoKSE9PXRoaXMuY29vcmRpbmF0ZUlkZW50aWZpZXJ9Y2xlYXJDb29yZGluYXRlSWRlbnRpZmllcigpe3RoaXMuY29vcmRpbmF0ZUlkZW50aWZpZXI9bnVsbH1zZXREYXRhKHQpe3RoaXMuY2xlYXJDb29yZGluYXRlSWRlbnRpZmllcigpLHRoaXMucmF3U2VyaWVzRGF0YT10fXRyYW5zZm9ybUNvb3JkaW5hdGVzSWZTdGFsZSgpe2lmKCF0aGlzLmlzQ29vcmRpbmF0ZVVwZGF0ZWQoKSlyZXR1cm47bGV0IHQ9dGhpcy5nZXRMYXlvdXRSZWN0KCk7dGhpcy5zZXJpZXM9bmV3IEFycmF5KHRoaXMucmF3U2VyaWVzRGF0YS5sZW5ndGgpO2ZvcihsZXQgZT0wO2U8dGhpcy5yYXdTZXJpZXNEYXRhLmxlbmd0aDtlKyspe2xldCBpPXRoaXMucmF3U2VyaWVzRGF0YVtlXTt0aGlzLnNlcmllc1tlXT17aWQ6aS5pZCxwb2x5bGluZTpuZXcgRmxvYXQzMkFycmF5KGkucG9pbnRzLmxlbmd0aCoyKX07Zm9yKGxldCByPTA7cjxpLnBvaW50cy5sZW5ndGg7cisrKXtsZXRbcyxvXT10aGlzLmNvb3JkaW5hdG9yLnRyYW5zZm9ybURhdGFUb1VpQ29vcmQodCxbaS5wb2ludHNbcl0ueCxpLnBvaW50c1tyXS55XSk7dGhpcy5zZXJpZXNbZV0ucG9seWxpbmVbcioyXT1zLHRoaXMuc2VyaWVzW2VdLnBvbHlsaW5lW3IqMisxXT1vfX10aGlzLmNvb3JkaW5hdGVJZGVudGlmaWVyPXRoaXMuY29vcmRpbmF0b3IuZ2V0VXBkYXRlSWRlbnRpZmllcigpLHRoaXMubWFya0FzUGFpbnREaXJ0eSgpfX07dmFyIExvOyhmdW5jdGlvbihuKXtuW24uTlVNQkVSPTBdPSJOVU1CRVIiLG5bbi5OQU49MV09Ik5BTiJ9KShMb3x8KExvPXt9KSk7dmFyIHpsPWNsYXNzIGV4dGVuZHMgRmx7cmVjb3JkUGFydGl0aW9uKHQsZSxpKXtyZXR1cm4gdD97dHlwZTpMby5OVU1CRVIscG9seWxpbmU6ZX06e3R5cGU6TG8uTkFOLHBvbHlsaW5lOmUubWFwKChyLHMpPT5pc05hTihyKT9zJTI9PT0wP2kueDppLnk6cil9fXBhcnRpdGlvblBvbHlsaW5lKHQpe2xldCBlPVtdLGk9MCxyPSExLHM9dGhpcy5jb29yZGluYXRvci50cmFuc2Zvcm1EYXRhVG9VaUNvb3JkKHRoaXMuZ2V0TGF5b3V0UmVjdCgpLFswLDBdKSxvPXt4OnNbMF0seTpzWzFdfSxhPW51bGw7Zm9yKGxldCBsPTA7bDx0Lmxlbmd0aDtsKz0yKXtsZXQgYz10W2xdLHU9dFtsKzFdLGg9aXNOYU4oYyl8fGlzTmFOKHUpO2ghPT1yJiZpIT09bCYmKGUucHVzaCh0aGlzLnJlY29yZFBhcnRpdGlvbighcix0LnNsaWNlKGksbCksYT09PW51bGw/e3g6Yyx5OnV9OmEpKSxpPWwpLGh8fChhPXt4OmMseTp1fSkscj1ofXJldHVybiBpIT09dC5sZW5ndGgtMSYmZS5wdXNoKHRoaXMucmVjb3JkUGFydGl0aW9uKCFyLHQuc2xpY2UoaSx0Lmxlbmd0aCksYSE9bnVsbD9hOm8pKSxlfXJlZHJhdygpe3ZhciB0LGUsaTtmb3IobGV0IHIgb2YgdGhpcy5zZXJpZXMpe2xldCBvPXRoaXMuZ2V0TWV0YWRhdGFNYXAoKVtyLmlkXTtpZighbyljb250aW51ZTtpZihyLnBvbHlsaW5lLmxlbmd0aCUyIT09MCl0aHJvdyBuZXcgRXJyb3IoYENhbm5vdCBoYXZlIG9kZCBsZW5ndGgtZWQgcG9seWxpbmU6ICR7ci5wb2x5bGluZS5sZW5ndGh9YCk7bGV0IGE9dGhpcy5wYXJ0aXRpb25Qb2x5bGluZShyLnBvbHlsaW5lKTtmb3IobGV0W2wse3R5cGU6Yyxwb2x5bGluZTp1fV1vZiBhLmVudHJpZXMoKSlpZihjPT09TG8uTlVNQkVSKXUubGVuZ3RoPT09Mj90aGlzLnBhaW50QnJ1c2guc2V0Q2lyY2xlKEpTT04uc3RyaW5naWZ5KFsiY2lyY2xlIixyLmlkLGxdKSx7eDp1WzBdLHk6dVsxXX0se2NvbG9yOm8uY29sb3IsdmlzaWJsZTpvLnZpc2libGUsb3BhY2l0eToodD1vLm9wYWNpdHkpIT1udWxsP3Q6MSxyYWRpdXM6NH0pOnRoaXMucGFpbnRCcnVzaC5zZXRMaW5lKEpTT04uc3RyaW5naWZ5KFsibGluZSIsci5pZCxsXSksdSx7Y29sb3I6by5jb2xvcix2aXNpYmxlOm8udmlzaWJsZSxvcGFjaXR5OihlPW8ub3BhY2l0eSkhPW51bGw/ZToxLHdpZHRoOjJ9KTtlbHNlIGlmKCFvLmF1eClmb3IobGV0IGg9MDtoPHUubGVuZ3RoO2grPTIpdGhpcy5wYWludEJydXNoLnNldFRyaWFuZ2xlKEpTT04uc3RyaW5naWZ5KFsiTmFOIixyLmlkLHVbaF0sdVtoKzFdXSkse3g6dVtoXSx5OnVbaCsxXX0se2NvbG9yOm8uY29sb3IsdmlzaWJsZTpvLnZpc2libGUsb3BhY2l0eTooaT1vLm9wYWNpdHkpIT1udWxsP2k6MSxzaXplOjEyfSl9fX07dmFyIFVsPWNsYXNzIGV4dGVuZHMgZHJ7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuY2FtZXJhPW5ldyBWaSgwLDFlMywxZTMsMCwwLDEwMCl9aXNZQXhpc1BvaW50ZWREb3duKCl7cmV0dXJuITF9c2V0RG9tQ29udGFpbmVyUmVjdCh0KXtzdXBlci5zZXREb21Db250YWluZXJSZWN0KHQpLHRoaXMuY2FtZXJhLmxlZnQ9dC54LHRoaXMuY2FtZXJhLnJpZ2h0PXQueCt0LndpZHRoLHRoaXMuY2FtZXJhLnRvcD10LnkrdC5oZWlnaHQsdGhpcy5jYW1lcmEuYm90dG9tPXQueSx0aGlzLmNhbWVyYS51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl9Z2V0Q2FtZXJhKCl7cmV0dXJuIHRoaXMuY2FtZXJhfX07dmFyIE1UPXtyZXF1ZXN0QW5pbWF0aW9uRnJhbWU6bj0+c2VsZi5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUobil9LEJsPWNsYXNze2NvbnN0cnVjdG9yKHQpe3N3aXRjaCh0aGlzLm1ldGFkYXRhTWFwPXt9LHRoaXMuc2hvdWxkUmVwYWludD0hMSx0aGlzLmNhbGxiYWNrcz10LmNhbGxiYWNrcyx0LnR5cGUpe2Nhc2UgVG4uU1ZHOnt0aGlzLmNvb3JkaW5hdG9yPW5ldyBkcix0aGlzLnJlbmRlcmVyPW5ldyBPcyh0LmNvbnRhaW5lcik7YnJlYWt9Y2FzZSBUbi5XRUJHTDp7bGV0IGU9bmV3IFVsO3RoaXMuY29vcmRpbmF0b3I9ZSx0aGlzLnJlbmRlcmVyPW5ldyBSbyh0LmNvbnRhaW5lcixlLHQuZGV2aWNlUGl4ZWxSYXRpbyx0LmNhbGxiYWNrcy5vbkNvbnRleHRMb3N0KTticmVha319dGhpcy5yZW5kZXJlci5zZXRVc2VEYXJrTW9kZSh0LnVzZURhcmtNb2RlKSx0aGlzLnNlcmllc0xpbmVWaWV3PW5ldyB6bCh7cmVuZGVyZXI6dGhpcy5yZW5kZXJlcixjb29yZGluYXRvcjp0aGlzLmNvb3JkaW5hdG9yLGdldE1ldGFkYXRhTWFwOigpPT50aGlzLm1ldGFkYXRhTWFwfSksdGhpcy5yZXNpemUodC5kb21EaW1lbnNpb24pfWRpc3Bvc2UoKXt9c2V0WFNjYWxlVHlwZSh0KXt0aGlzLmNvb3JkaW5hdG9yLnNldFhTY2FsZShmcih0KSksdGhpcy5zY2hlZHVsZVJlcGFpbnQoKX1zZXRZU2NhbGVUeXBlKHQpe3RoaXMuY29vcmRpbmF0b3Iuc2V0WVNjYWxlKGZyKHQpKSx0aGlzLnNjaGVkdWxlUmVwYWludCgpfXJlc2l6ZSh0KXt0aGlzLmNvb3JkaW5hdG9yLnNldERvbUNvbnRhaW5lclJlY3QoTm8oe3g6MCx5OjB9LHQpKSx0aGlzLnJlbmRlcmVyLm9uUmVzaXplKE5vKHt4OjAseTowfSx0KSksdGhpcy5zZXJpZXNMaW5lVmlldy5zZXRMYXlvdXRSZWN0KHVmKE5vKHt9LHQpLHt4OjAseTowfSkpLHRoaXMuc2NoZWR1bGVSZXBhaW50KCl9c2V0TWV0YWRhdGEodCl7bGV0IGU9ITE7T2JqZWN0LmVudHJpZXModCkuZm9yRWFjaCgoW2kscl0pPT57bGV0IHM9dGhpcy5tZXRhZGF0YU1hcFtpXTsoIXN8fHIuY29sb3IhPT1zLmNvbG9yfHxyLnZpc2libGUhPT1zLnZpc2libGV8fHIub3BhY2l0eSE9PXMub3BhY2l0eSkmJihlPSEwKSx0aGlzLm1ldGFkYXRhTWFwW2ldPXJ9KSxlJiZ0aGlzLnNlcmllc0xpbmVWaWV3Lm1hcmtBc1BhaW50RGlydHkoKSx0aGlzLnNjaGVkdWxlUmVwYWludCgpfXNldFZpZXdCb3godCl7dGhpcy5jb29yZGluYXRvci5zZXRWaWV3Qm94UmVjdCh7eDp0LnhbMF0sd2lkdGg6dC54WzFdLXQueFswXSx5OnQueVswXSxoZWlnaHQ6dC55WzFdLXQueVswXX0pLHRoaXMuc2NoZWR1bGVSZXBhaW50KCl9c2V0RGF0YSh0KXt0aGlzLnNlcmllc0xpbmVWaWV3LnNldERhdGEodCksdGhpcy5zY2hlZHVsZVJlcGFpbnQoKX1zZXRVc2VEYXJrTW9kZSh0KXt0aGlzLnJlbmRlcmVyLnNldFVzZURhcmtNb2RlKHQpLHRoaXMuc2VyaWVzTGluZVZpZXcubWFya0FzUGFpbnREaXJ0eSgpLHRoaXMuc2NoZWR1bGVSZXBhaW50KCl9c2NoZWR1bGVSZXBhaW50KCl7dGhpcy5zaG91bGRSZXBhaW50fHwodGhpcy5zaG91bGRSZXBhaW50PSEwLE1ULnJlcXVlc3RBbmltYXRpb25GcmFtZSgoKT0+e3RoaXMucmVwYWludCgpLHRoaXMuc2hvdWxkUmVwYWludD0hMX0pKX1yZXBhaW50KCl7dGhpcy5zZXJpZXNMaW5lVmlldy5yZW5kZXIoKSx0aGlzLnJlbmRlcmVyLmZsdXNoKCksdGhpcy5jYWxsYmFja3Mub25EcmF3RW5kKCl9fTtmdW5jdGlvbiB6MChuKXtsZXR7ZmxhdHRlbmVkU2VyaWVzOnQsaWRzQW5kTGVuZ3RoczplfT1uLGk9bmV3IEZsb2F0NjRBcnJheSh0KSxyPVtdO2lmKGkubGVuZ3RoJTIhPT0wKXRocm93IG5ldyBFcnJvcigiYGZsYXR0ZW5lZFNlcmllc2AgbXVzdCBoYXZlIGV2ZW4gbnVtYmVyIG9mIGVsZW1lbnRzIik7bGV0IHM9MDtmb3IobGV0e2lkOm8sbGVuZ3RoOmF9b2YgZSl7bGV0IGw9W107Zm9yKGxldCBjPTA7YzxhO2MrKylsLnB1c2goe3g6aVtzKytdLHk6aVtzKytdfSk7ci5wdXNoKHtpZDpvLHBvaW50czpsfSl9cmV0dXJuIHJ9dmFyIHhuOyhmdW5jdGlvbihuKXtuW24uU0VSSUVTX0RBVEFfVVBEQVRFRD0wXT0iU0VSSUVTX0RBVEFfVVBEQVRFRCIsbltuLlNFUklFU19NRVRBREFUQV9DSEFOR0VEPTFdPSJTRVJJRVNfTUVUQURBVEFfQ0hBTkdFRCIsbltuLlNDQUxFX1VQREFURUQ9Ml09IlNDQUxFX1VQREFURUQiLG5bbi5WSUVXX0JPWF9VUERBVEVEPTNdPSJWSUVXX0JPWF9VUERBVEVEIixuW24uSU5JVD00XT0iSU5JVCIsbltuLkRPTV9SRVNJWkVEPTVdPSJET01fUkVTSVpFRCIsbltuLkRBUktfTU9ERV9VUERBVEVEPTZdPSJEQVJLX01PREVfVVBEQVRFRCIsbltuLkRJU1BPU0VEPTddPSJESVNQT1NFRCJ9KSh4bnx8KHhuPXt9KSk7dmFyIFBvOyhmdW5jdGlvbihuKXtuW24uT05fUkVEUkFXX0VORD0wXT0iT05fUkVEUkFXX0VORCIsbltuLk9OX0NPTlRFWFRfTE9TVD0xXT0iT05fQ09OVEVYVF9MT1NUIn0pKFBvfHwoUG89e30pKTtzZWxmLmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLG49PntiVChuLnBvcnRzWzBdLG4uZGF0YSl9KTtmdW5jdGlvbiBiVChuLHQpe2xldHtjYW52YXM6ZSxkZXZpY2VQaXhlbFJhdGlvOmksZGltOnIscmVuZGVyZXJUeXBlOnMsdXNlRGFya01vZGU6b309dCxhPXtvbkRyYXdFbmQ6KCk9PntuLnBvc3RNZXNzYWdlKHt0eXBlOlBvLk9OX1JFRFJBV19FTkR9KX0sb25Db250ZXh0TG9zdDooKT0+e24ucG9zdE1lc3NhZ2Uoe3R5cGU6UG8uT05fQ09OVEVYVF9MT1NUfSl9fSxsO3N3aXRjaChzKXtjYXNlIFRuLldFQkdMOmw9e3R5cGU6VG4uV0VCR0wsZG9tRGltZW5zaW9uOnIsY2FsbGJhY2tzOmEsY29udGFpbmVyOmUsZGV2aWNlUGl4ZWxSYXRpbzppLHVzZURhcmtNb2RlOm99O2JyZWFrO2RlZmF1bHQ6dGhyb3cgbmV3IFJhbmdlRXJyb3IoYEludmFyaWFudCBlcnJvcjogY2Fubm90IGhhdmUgT2Zmc2NyZWVuIGNoYXJ0IGZvciByZW5kZXJlciB0eXBlOiAke3N9YCl9bGV0IGM9bmV3IEJsKGwpO24ub25tZXNzYWdlPWZ1bmN0aW9uKHUpe2xldCBoPXUuZGF0YTtzd2l0Y2goaC50eXBlKXtjYXNlIHhuLlNFUklFU19EQVRBX1VQREFURUQ6e2xldCBmPXowKGguY29tcGFjdERhdGFTZXJpZXMpO2Muc2V0RGF0YShmKTticmVha31jYXNlIHhuLlNFUklFU19NRVRBREFUQV9DSEFOR0VEOntjLnNldE1ldGFkYXRhKGgubWV0YWRhdGEpO2JyZWFrfWNhc2UgeG4uVklFV19CT1hfVVBEQVRFRDp7Yy5zZXRWaWV3Qm94KGguZXh0ZW50KTticmVha31jYXNlIHhuLkRPTV9SRVNJWkVEOntjLnJlc2l6ZShoLmRpbSk7YnJlYWt9Y2FzZSB4bi5EQVJLX01PREVfVVBEQVRFRDp7Yy5zZXRVc2VEYXJrTW9kZShoLnVzZURhcmtNb2RlKTticmVha31jYXNlIHhuLlNDQUxFX1VQREFURUQ6e3N3aXRjaChoLmF4aXMpe2Nhc2UieCI6Yy5zZXRYU2NhbGVUeXBlKGguc2NhbGVUeXBlKTticmVhaztjYXNlInkiOmMuc2V0WVNjYWxlVHlwZShoLnNjYWxlVHlwZSk7YnJlYWs7ZGVmYXVsdDpsZXQgZj1oLmF4aXM7dGhyb3cgbmV3IFJhbmdlRXJyb3IoYFVua25vd24gYXhpczogJHtmfWApfWJyZWFrfWNhc2UgeG4uRElTUE9TRUQ6e2MuZGlzcG9zZSgpO2JyZWFrfX19fX0pKCk7Ci8qKgogKiBAbGljZW5zZQogKiBDb3B5cmlnaHQgMjAxMC0yMDIyIFRocmVlLmpzIEF1dGhvcnMKICogU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVAogKi8KLy8jIHNvdXJjZU1hcHBpbmdVUkw9Y2hhcnRfd29ya2VyLmpzLm1hcAo=", + "headers": [ + [ + "content-type", + "text/javascript; charset=utf-8" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/data/environment": { + "data": "eyJ2ZXJzaW9uIjogIjIuMTIuMSIsICJkYXRhX2xvY2F0aW9uIjogIi4vd29ya19kaXJzIiwgIndpbmRvd190aXRsZSI6ICIiLCAiZXhwZXJpbWVudF9uYW1lIjogIiIsICJleHBlcmltZW50X2Rlc2NyaXB0aW9uIjogIiIsICJjcmVhdGlvbl90aW1lIjogMCwgImRlYnVnIjogeyJkYXRhX3Byb3ZpZGVyIjogIk11bHRpcGxleGVyRGF0YVByb3ZpZGVyKGxvZ2Rpcj0nLi93b3JrX2RpcnMnKSIsICJmbGFncyI6IHsibG9nZGlyIjogIi4vd29ya19kaXJzIiwgImxvZ2Rpcl9zcGVjIjogIiIsICJob3N0IjogbnVsbCwgImJpbmRfYWxsIjogZmFsc2UsICJwb3J0IjogbnVsbCwgInJldXNlX3BvcnQiOiBmYWxzZSwgImxvYWRfZmFzdCI6ICJhdXRvIiwgImV4dHJhX2RhdGFfc2VydmVyX2ZsYWdzIjogIiIsICJncnBjX2NyZWRzX3R5cGUiOiAibG9jYWwiLCAiZ3JwY19kYXRhX3Byb3ZpZGVyIjogIiIsICJwdXJnZV9vcnBoYW5lZF9kYXRhIjogdHJ1ZSwgImRiIjogIiIsICJkYl9pbXBvcnQiOiBmYWxzZSwgImluc3BlY3QiOiBmYWxzZSwgInZlcnNpb25fdGIiOiBmYWxzZSwgInRhZyI6ICIiLCAiZXZlbnRfZmlsZSI6ICIiLCAicGF0aF9wcmVmaXgiOiAiIiwgIndpbmRvd190aXRsZSI6ICIiLCAibWF4X3JlbG9hZF90aHJlYWRzIjogMSwgInJlbG9hZF9pbnRlcnZhbCI6IDUuMCwgInJlbG9hZF90YXNrIjogImF1dG8iLCAicmVsb2FkX211bHRpZmlsZSI6IG51bGwsICJyZWxvYWRfbXVsdGlmaWxlX2luYWN0aXZlX3NlY3MiOiA4NjQwMCwgImdlbmVyaWNfZGF0YSI6ICJhdXRvIiwgInNhbXBsZXNfcGVyX3BsdWdpbiI6IHt9LCAiZGV0ZWN0X2ZpbGVfcmVwbGFjZW1lbnQiOiBudWxsLCAiY3VzdG9tX3ByZWRpY3RfZm4iOiAiIiwgIndpdF9kYXRhX2RpciI6ICIiLCAiX190ZW5zb3Jib2FyZF9zdWJjb21tYW5kIjogInNlcnZlIn19fQ==", + "headers": [ + [ + "content-type", + "application/json" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/data/plugins_listing": { + "data": "eyJ0aW1lc2VyaWVzIjogeyJkaXNhYmxlX3JlbG9hZCI6IGZhbHNlLCAiZW5hYmxlZCI6IHRydWUsICJyZW1vdmVfZG9tIjogZmFsc2UsICJ0YWJfbmFtZSI6ICJUaW1lIFNlcmllcyIsICJsb2FkaW5nX21lY2hhbmlzbSI6IHsidHlwZSI6ICJOR19DT01QT05FTlQifX0sICJzY2FsYXJzIjogeyJkaXNhYmxlX3JlbG9hZCI6IGZhbHNlLCAiZW5hYmxlZCI6IHRydWUsICJyZW1vdmVfZG9tIjogZmFsc2UsICJ0YWJfbmFtZSI6ICJzY2FsYXJzIiwgImxvYWRpbmdfbWVjaGFuaXNtIjogeyJ0eXBlIjogIkNVU1RPTV9FTEVNRU5UIiwgImVsZW1lbnRfbmFtZSI6ICJ0Zi1zY2FsYXItZGFzaGJvYXJkIn19LCAiY3VzdG9tX3NjYWxhcnMiOiB7ImRpc2FibGVfcmVsb2FkIjogZmFsc2UsICJlbmFibGVkIjogZmFsc2UsICJyZW1vdmVfZG9tIjogZmFsc2UsICJ0YWJfbmFtZSI6ICJDdXN0b20gU2NhbGFycyIsICJsb2FkaW5nX21lY2hhbmlzbSI6IHsidHlwZSI6ICJDVVNUT01fRUxFTUVOVCIsICJlbGVtZW50X25hbWUiOiAidGYtY3VzdG9tLXNjYWxhci1kYXNoYm9hcmQifX0sICJpbWFnZXMiOiB7ImRpc2FibGVfcmVsb2FkIjogZmFsc2UsICJlbmFibGVkIjogZmFsc2UsICJyZW1vdmVfZG9tIjogZmFsc2UsICJ0YWJfbmFtZSI6ICJpbWFnZXMiLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiQ1VTVE9NX0VMRU1FTlQiLCAiZWxlbWVudF9uYW1lIjogInRmLWltYWdlLWRhc2hib2FyZCJ9fSwgImF1ZGlvIjogeyJkaXNhYmxlX3JlbG9hZCI6IGZhbHNlLCAiZW5hYmxlZCI6IGZhbHNlLCAicmVtb3ZlX2RvbSI6IGZhbHNlLCAidGFiX25hbWUiOiAiYXVkaW8iLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiQ1VTVE9NX0VMRU1FTlQiLCAiZWxlbWVudF9uYW1lIjogInRmLWF1ZGlvLWRhc2hib2FyZCJ9fSwgImRlYnVnZ2VyLXYyIjogeyJkaXNhYmxlX3JlbG9hZCI6IGZhbHNlLCAiZW5hYmxlZCI6IGZhbHNlLCAicmVtb3ZlX2RvbSI6IGZhbHNlLCAidGFiX25hbWUiOiAiRGVidWdnZXIgVjIiLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiTkdfQ09NUE9ORU5UIn19LCAiZ3JhcGhzIjogeyJkaXNhYmxlX3JlbG9hZCI6IHRydWUsICJlbmFibGVkIjogZmFsc2UsICJyZW1vdmVfZG9tIjogZmFsc2UsICJ0YWJfbmFtZSI6ICJncmFwaHMiLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiQ1VTVE9NX0VMRU1FTlQiLCAiZWxlbWVudF9uYW1lIjogInRmLWdyYXBoLWRhc2hib2FyZCJ9fSwgImRpc3RyaWJ1dGlvbnMiOiB7ImRpc2FibGVfcmVsb2FkIjogZmFsc2UsICJlbmFibGVkIjogZmFsc2UsICJyZW1vdmVfZG9tIjogZmFsc2UsICJ0YWJfbmFtZSI6ICJkaXN0cmlidXRpb25zIiwgImxvYWRpbmdfbWVjaGFuaXNtIjogeyJ0eXBlIjogIkNVU1RPTV9FTEVNRU5UIiwgImVsZW1lbnRfbmFtZSI6ICJ0Zi1kaXN0cmlidXRpb24tZGFzaGJvYXJkIn19LCAiaGlzdG9ncmFtcyI6IHsiZGlzYWJsZV9yZWxvYWQiOiBmYWxzZSwgImVuYWJsZWQiOiBmYWxzZSwgInJlbW92ZV9kb20iOiBmYWxzZSwgInRhYl9uYW1lIjogImhpc3RvZ3JhbXMiLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiQ1VTVE9NX0VMRU1FTlQiLCAiZWxlbWVudF9uYW1lIjogInRmLWhpc3RvZ3JhbS1kYXNoYm9hcmQifX0sICJ0ZXh0IjogeyJkaXNhYmxlX3JlbG9hZCI6IGZhbHNlLCAiZW5hYmxlZCI6IHRydWUsICJyZW1vdmVfZG9tIjogZmFsc2UsICJ0YWJfbmFtZSI6ICJ0ZXh0IiwgImxvYWRpbmdfbWVjaGFuaXNtIjogeyJ0eXBlIjogIkNVU1RPTV9FTEVNRU5UIiwgImVsZW1lbnRfbmFtZSI6ICJ0Zi10ZXh0LWRhc2hib2FyZCJ9fSwgInByX2N1cnZlcyI6IHsiZGlzYWJsZV9yZWxvYWQiOiBmYWxzZSwgImVuYWJsZWQiOiBmYWxzZSwgInJlbW92ZV9kb20iOiBmYWxzZSwgInRhYl9uYW1lIjogIlBSIEN1cnZlcyIsICJsb2FkaW5nX21lY2hhbmlzbSI6IHsidHlwZSI6ICJDVVNUT01fRUxFTUVOVCIsICJlbGVtZW50X25hbWUiOiAidGYtcHItY3VydmUtZGFzaGJvYXJkIn19LCAicHJvZmlsZV9yZWRpcmVjdCI6IHsiZGlzYWJsZV9yZWxvYWQiOiBmYWxzZSwgImVuYWJsZWQiOiBmYWxzZSwgInJlbW92ZV9kb20iOiBmYWxzZSwgInRhYl9uYW1lIjogIlByb2ZpbGUiLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiQ1VTVE9NX0VMRU1FTlQiLCAiZWxlbWVudF9uYW1lIjogInRmLXByb2ZpbGUtcmVkaXJlY3QtZGFzaGJvYXJkIn19LCAiaHBhcmFtcyI6IHsiZGlzYWJsZV9yZWxvYWQiOiBmYWxzZSwgImVuYWJsZWQiOiBmYWxzZSwgInJlbW92ZV9kb20iOiBmYWxzZSwgInRhYl9uYW1lIjogImhwYXJhbXMiLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiQ1VTVE9NX0VMRU1FTlQiLCAiZWxlbWVudF9uYW1lIjogInRmLWhwYXJhbXMtZGFzaGJvYXJkIn19LCAibWVzaCI6IHsiZGlzYWJsZV9yZWxvYWQiOiBmYWxzZSwgImVuYWJsZWQiOiBmYWxzZSwgInJlbW92ZV9kb20iOiBmYWxzZSwgInRhYl9uYW1lIjogIm1lc2giLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiQ1VTVE9NX0VMRU1FTlQiLCAiZWxlbWVudF9uYW1lIjogIm1lc2gtZGFzaGJvYXJkIn19LCAicHJvamVjdG9yIjogeyJkaXNhYmxlX3JlbG9hZCI6IHRydWUsICJlbmFibGVkIjogZmFsc2UsICJyZW1vdmVfZG9tIjogZmFsc2UsICJ0YWJfbmFtZSI6ICJwcm9qZWN0b3IiLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiSUZSQU1FIiwgIm1vZHVsZV9wYXRoIjogIi9kYXRhL3BsdWdpbi9wcm9qZWN0b3IvaW5kZXguanMifX0sICJ3aGF0aWYiOiB7ImRpc2FibGVfcmVsb2FkIjogZmFsc2UsICJlbmFibGVkIjogZmFsc2UsICJyZW1vdmVfZG9tIjogZmFsc2UsICJ0YWJfbmFtZSI6ICJXaGF0LUlmIFRvb2wiLCAibG9hZGluZ19tZWNoYW5pc20iOiB7InR5cGUiOiAiSUZSQU1FIiwgIm1vZHVsZV9wYXRoIjogIi9kYXRhL3BsdWdpbi93aGF0aWYvaW5kZXguanMifX19", + "headers": [ + [ + "content-type", + "application/json" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/data/runs": { + "data": "WyJydG1kZXRfdGlueV8xeGI0LTIwZV9iYWxsb29uLzIwMjMwNDE3XzEwMjgzNS92aXNfZGF0YSJd", + "headers": [ + [ + "content-type", + "application/json" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/experiment/defaultExperimentId/data/plugin/timeseries/tags": { + "data": "eyJzY2FsYXJzIjogeyJydW5UYWdJbmZvIjogeyJydG1kZXRfdGlueV8xeGI0LTIwZV9iYWxsb29uLzIwMjMwNDE3XzEwMjgzNS92aXNfZGF0YSI6IFsiY29jby9iYm94X21BUCIsICJjb2NvL2Jib3hfbUFQXzUwIiwgImNvY28vYmJveF9tQVBfNzUiLCAiY29jby9iYm94X21BUF9sIiwgImNvY28vYmJveF9tQVBfbSIsICJjb2NvL2Jib3hfbUFQX3MiLCAiZGF0YV90aW1lIiwgImVwb2NoIiwgImxvc3MiLCAibG9zc19iYm94IiwgImxvc3NfY2xzIiwgImxyIiwgIm1lbW9yeSIsICJ0aW1lIl19LCAidGFnRGVzY3JpcHRpb25zIjoge319LCAiaGlzdG9ncmFtcyI6IHsicnVuVGFnSW5mbyI6IHt9LCAidGFnRGVzY3JpcHRpb25zIjoge319LCAiaW1hZ2VzIjogeyJ0YWdEZXNjcmlwdGlvbnMiOiB7fSwgInRhZ1J1blNhbXBsZWRJbmZvIjoge319fQ==", + "headers": [ + [ + "content-type", + "application/json" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/experiment/defaultExperimentId/data/plugin/timeseries/timeSeries?requests=%5B%7B%22plugin%22:%22scalars%22,%22tag%22:%22coco/bbox_mAP%22%7D%5D": { + "data": "W3sicGx1Z2luIjogInNjYWxhcnMiLCAidGFnIjogImNvY28vYmJveF9tQVAiLCAicnVuVG9TZXJpZXMiOiB7InJ0bWRldF90aW55XzF4YjQtMjBlX2JhbGxvb24vMjAyMzA0MTdfMTAyODM1L3Zpc19kYXRhIjogW3sid2FsbFRpbWUiOiAxNjgxNzI3MzM1LjQ0MjIzNzksICJzdGVwIjogMSwgInZhbHVlIjogMC4wNDM5OTk5OTk3NjE1ODE0Mn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzQ1LjYzOTAwNTcsICJzdGVwIjogMiwgInZhbHVlIjogMC4xMjgwMDAwMDYwNzk2NzM3N30sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzUyLjk2ODcxOTIsICJzdGVwIjogMywgInZhbHVlIjogMC4zMDc5OTk5OTgzMzEwNjk5NX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzYyLjQ5MTM0OSwgInN0ZXAiOiA0LCAidmFsdWUiOiAwLjQwNTk5OTk4ODMxNzQ4OTZ9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM3MS42NTA0NDAyLCAic3RlcCI6IDUsICJ2YWx1ZSI6IDAuNDI1OTk5OTk5MDQ2MzI1N30sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzgxLjQyNjk5MSwgInN0ZXAiOiA2LCAidmFsdWUiOiAwLjQ1NTAwMDAxMzExMzAyMTg1fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczODkuNTM3NDE4OCwgInN0ZXAiOiA3LCAidmFsdWUiOiAwLjQ5Mzk5OTk4Nzg0MDY1MjQ3fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczOTcuNjg0OTgxLCAic3RlcCI6IDgsICJ2YWx1ZSI6IDAuNTQxOTk5OTk1NzA4NDY1Nn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDA3LjAzMzE4MSwgInN0ZXAiOiA5LCAidmFsdWUiOiAwLjYxMTAwMDAwMTQzMDUxMTV9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQxNi4wMzkzMTk1LCAic3RlcCI6IDEwLCAidmFsdWUiOiAwLjYyMzAwMDAyNTc0OTIwNjV9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQyNS42NTUyMzgyLCAic3RlcCI6IDExLCAidmFsdWUiOiAwLjY0NDk5OTk4MDkyNjUxMzd9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQzMy4wMDQyMzY1LCAic3RlcCI6IDEyLCAidmFsdWUiOiAwLjYxNjk5OTk4Mzc4NzUzNjZ9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ0Mi4wNDk3MzIyLCAic3RlcCI6IDEzLCAidmFsdWUiOiAwLjYzNDk5OTk5MDQ2MzI1Njh9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ0Ny45MzQ3MzksICJzdGVwIjogMTQsICJ2YWx1ZSI6IDAuNjM1OTk5OTc3NTg4NjUzNn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDU3Ljk3NTI4NTMsICJzdGVwIjogMTUsICJ2YWx1ZSI6IDAuNjQzOTk5OTkzODAxMTE2OX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDYzLjgyNTkzMTgsICJzdGVwIjogMTYsICJ2YWx1ZSI6IDAuNjY2OTk5OTk1NzA4NDY1Nn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDczLjQ2ODcxMDIsICJzdGVwIjogMTcsICJ2YWx1ZSI6IDAuNjcwMDAwMDE2Njg5MzAwNX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDgwLjc4OTkzNzUsICJzdGVwIjogMTgsICJ2YWx1ZSI6IDAuNjU3MDAwMDA1MjQ1MjA4N30sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDg4LjkyMzkyOCwgInN0ZXAiOiAxOSwgInZhbHVlIjogMC42NDgwMDAwMDE5MDczNDg2fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0OTQuNTY5MzkzLCAic3RlcCI6IDIwLCAidmFsdWUiOiAwLjY1MjAwMDAxMDAxMzU4MDN9XX19XQ==", + "headers": [ + [ + "content-type", + "application/json" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/experiment/defaultExperimentId/data/plugin/timeseries/timeSeries?requests=%5B%7B%22plugin%22:%22scalars%22,%22tag%22:%22coco/bbox_mAP_50%22%7D%5D": { + "data": "W3sicGx1Z2luIjogInNjYWxhcnMiLCAidGFnIjogImNvY28vYmJveF9tQVBfNTAiLCAicnVuVG9TZXJpZXMiOiB7InJ0bWRldF90aW55XzF4YjQtMjBlX2JhbGxvb24vMjAyMzA0MTdfMTAyODM1L3Zpc19kYXRhIjogW3sid2FsbFRpbWUiOiAxNjgxNzI3MzM1LjQ0MjQ1MDMsICJzdGVwIjogMSwgInZhbHVlIjogMC4wNTkwMDAwMDAzNTc2Mjc4N30sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzQ1LjYzOTEwOTYsICJzdGVwIjogMiwgInZhbHVlIjogMC4xNjUwMDAwMDY1NTY1MTA5M30sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzUyLjk2ODgxNDgsICJzdGVwIjogMywgInZhbHVlIjogMC4zODEwMDAwMTIxNTkzNDc1M30sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzYyLjQ5MTQ1MDgsICJzdGVwIjogNCwgInZhbHVlIjogMC41MzEwMDAwMTgxMTk4MTJ9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM3MS42NTA2MTc2LCAic3RlcCI6IDUsICJ2YWx1ZSI6IDAuNTQ2OTk5OTkwOTQwMDk0fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczODEuNDI3MDg5NywgInN0ZXAiOiA2LCAidmFsdWUiOiAwLjU3ODk5OTk5NjE4NTMwMjd9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM4OS41Mzc1NDI4LCAic3RlcCI6IDcsICJ2YWx1ZSI6IDAuNjU3OTk5OTkyMzcwNjA1NX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3Mzk3LjY4NTA4NTMsICJzdGVwIjogOCwgInZhbHVlIjogMC42OTMwMDAwMTg1OTY2NDkyfSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0MDcuMDMzMzAyNSwgInN0ZXAiOiA5LCAidmFsdWUiOiAwLjcyNTAwMDAyMzg0MTg1Nzl9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQxNi4wMzk0NTQyLCAic3RlcCI6IDEwLCAidmFsdWUiOiAwLjc0MDk5OTk5NjY2MjEzOTl9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQyNS42NTUzNTAyLCAic3RlcCI6IDExLCAidmFsdWUiOiAwLjc3MjAwMDAxNDc4MTk1MTl9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQzMy4wMDQzMzczLCAic3RlcCI6IDEyLCAidmFsdWUiOiAwLjc1MDk5OTk4NzEyNTM5Njd9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ0Mi4wNDk4MjUsICJzdGVwIjogMTMsICJ2YWx1ZSI6IDAuNzU0OTk5OTk1MjMxNjI4NH0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDQ3LjkzNDgzMTEsICJzdGVwIjogMTQsICJ2YWx1ZSI6IDAuNzM5MDAwMDIyNDExMzQ2NH0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDU3Ljk3NTQwNTIsICJzdGVwIjogMTUsICJ2YWx1ZSI6IDAuNzV9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ2My44MjYwMjU3LCAic3RlcCI6IDE2LCAidmFsdWUiOiAwLjc2ODk5OTk5MzgwMTExNjl9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ3My40Njg4MDgsICJzdGVwIjogMTcsICJ2YWx1ZSI6IDAuNzY4OTk5OTkzODAxMTE2OX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDgwLjc5MDAzMzgsICJzdGVwIjogMTgsICJ2YWx1ZSI6IDAuNzU0OTk5OTk1MjMxNjI4NH0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDg4LjkyNDAyNTUsICJzdGVwIjogMTksICJ2YWx1ZSI6IDAuNzU4MDAwMDE2MjEyNDYzNH0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDk0LjU2OTUyMywgInN0ZXAiOiAyMCwgInZhbHVlIjogMC43OTQwMDAwMjk1NjM5MDM4fV19fV0=", + "headers": [ + [ + "content-type", + "application/json" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/font-roboto/RxZJdnzeo3R5zSexge8UUZBw1xU1rKptJj_0jans920.woff2": { + "data": "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", + "headers": [ + [ + "content-type", + "font/woff2" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/font-roboto/d-6IYplOFocCacKzxwXSOJBw1xU1rKptJj_0jans920.woff2": { + "data": "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", + "headers": [ + [ + "content-type", + "font/woff2" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/font-roboto/vPcynSL0qHq_6dX7lKVByXYhjbSpvc47ee6xR_80Hnw.woff2": { + "data": "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", + "headers": [ + [ + "content-type", + "font/woff2" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/icon_bundle.svg": { + "data": "PD94bWwgdmVyc2lvbj0iMS4wIiA/Pjxzdmc+PGRlZnM+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBpZD0iYXJyb3dfZG93bndhcmRfMjRweCI+PHBhdGggZmlsbD0iIzAxMDEwMSIgZD0iTTIwIDEybC0xLjQxLTEuNDFMMTMgMTYuMTdWNGgtMnYxMi4xN2wtNS41OC01LjU5TDQgMTJsOCA4IDgtOHoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGlkPSJhcnJvd191cHdhcmRfMjRweCI+PHBhdGggZD0iTTQgMTJsMS40MSAxLjQxTDExIDcuODNWMjBoMlY3LjgzbDUuNTggNS41OUwyMCAxMmwtOC04LTggOHoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjQiIGlkPSJicmlnaHRuZXNzXzZfMjRweCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0yMCAxNS4zMUwyMy4zMSAxMiAyMCA4LjY5VjRoLTQuNjlMMTIgLjY5IDguNjkgNEg0djQuNjlMLjY5IDEyIDQgMTUuMzFWMjBoNC42OUwxMiAyMy4zMSAxNS4zMSAyMEgyMHYtNC42OXpNMTIgMThWNmMzLjMxIDAgNiAyLjY5IDYgNnMtMi42OSA2LTYgNnoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjQiIGlkPSJidWdfcmVwb3J0XzI0cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMjAgOGgtMi44MWMtLjQ1LS43OC0xLjA3LTEuNDUtMS44Mi0xLjk2TDE3IDQuNDEgMTUuNTkgM2wtMi4xNyAyLjE3QzEyLjk2IDUuMDYgMTIuNDkgNSAxMiA1Yy0uNDkgMC0uOTYuMDYtMS40MS4xN0w4LjQxIDMgNyA0LjQxbDEuNjIgMS42M0M3Ljg4IDYuNTUgNy4yNiA3LjIyIDYuODEgOEg0djJoMi4wOWMtLjA1LjMzLS4wOS42Ni0uMDkgMXYxSDR2MmgydjFjMCAuMzQuMDQuNjcuMDkgMUg0djJoMi44MWMxLjA0IDEuNzkgMi45NyAzIDUuMTkgM3M0LjE1LTEuMjEgNS4xOS0zSDIwdi0yaC0yLjA5Yy4wNS0uMzMuMDktLjY2LjA5LTF2LTFoMnYtMmgtMnYtMWMwLS4zNC0uMDQtLjY3LS4wOS0xSDIwVjh6bS02IDhoLTR2LTJoNHYyem0wLTRoLTR2LTJoNHYyeiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9ImNhbmNlbF8yNHB4Ij48cGF0aCBkPSJNMTIgMkM2LjQ3IDIgMiA2LjQ3IDIgMTJzNC40NyAxMCAxMCAxMCAxMC00LjQ3IDEwLTEwUzE3LjUzIDIgMTIgMnptNSAxMy41OUwxNS41OSAxNyAxMiAxMy40MSA4LjQxIDE3IDcgMTUuNTkgMTAuNTkgMTIgNyA4LjQxIDguNDEgNyAxMiAxMC41OSAxNS41OSA3IDE3IDguNDEgMTMuNDEgMTIgMTcgMTUuNTl6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBpZD0iY2hhbmdlX2hpc3RvcnlfMjRweCI+PHBhdGggZD0iTTEyIDcuNzdMMTguMzkgMThINS42MUwxMiA3Ljc3TTEyIDRMMiAyMGgyMEwxMiA0eiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9ImNoZXZyb25fbGVmdF8yNHB4Ij48cGF0aCBkPSJNMTUuNDEgNy40MUwxNCA2bC02IDYgNiA2IDEuNDEtMS40MUwxMC44MyAxMnoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGlkPSJjaGV2cm9uX3JpZ2h0XzI0cHgiPjxwYXRoIGQ9Ik0xMCA2TDguNTkgNy40MSAxMy4xNyAxMmwtNC41OCA0LjU5TDEwIDE4bDYtNnoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGlkPSJjbGVhcl8yNHB4Ij48cGF0aCBkPSJNMTkgNi40MUwxNy41OSA1IDEyIDEwLjU5IDYuNDEgNSA1IDYuNDEgMTAuNTkgMTIgNSAxNy41OSA2LjQxIDE5IDEyIDEzLjQxIDE3LjU5IDE5IDE5IDE3LjU5IDEzLjQxIDEyeiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaWQ9ImNsb3NlXzI0cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTkgNi40MUwxNy41OSA1IDEyIDEwLjU5IDYuNDEgNSA1IDYuNDEgMTAuNTkgMTIgNSAxNy41OSA2LjQxIDE5IDEyIDEzLjQxIDE3LjU5IDE5IDE5IDE3LjU5IDEzLjQxIDEyeiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9ImNvbnRlbnRfY29weV8yNHB4Ij48cGF0aCBkPSJNMTYgMUg0Yy0xLjEgMC0yIC45LTIgMnYxNGgyVjNoMTJWMXptMyA0SDhjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTFjMS4xIDAgMi0uOSAyLTJWN2MwLTEuMS0uOS0yLTItMnptMCAxNkg4VjdoMTF2MTR6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaWQ9ImRhcmtfbW9kZV8yNHB4Ij48cmVjdCBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiB3aWR0aD0iMjQiLz48cGF0aCBkPSJNMTIsM2MtNC45NywwLTksNC4wMy05LDlzNC4wMyw5LDksOXM5LTQuMDMsOS05YzAtMC40Ni0wLjA0LTAuOTItMC4xLTEuMzZjLTAuOTgsMS4zNy0yLjU4LDIuMjYtNC40LDIuMjYgYy0yLjk4LDAtNS40LTIuNDItNS40LTUuNGMwLTEuODEsMC44OS0zLjQyLDIuMjYtNC40QzEyLjkyLDMuMDQsMTIuNDYsMywxMiwzTDEyLDN6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBpZD0iZG9uZV8yNHB4Ij48cGF0aCBkPSJNOSAxNi4yTDQuOCAxMmwtMS40IDEuNEw5IDE5IDIxIDdsLTEuNC0xLjRMOSAxNi4yeiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaWQ9ImRyYWdfaW5kaWNhdG9yXzI0cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMFYweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0xMSAxOGMwIDEuMS0uOSAyLTIgMnMtMi0uOS0yLTIgLjktMiAyLTIgMiAuOSAyIDJ6bS0yLThjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yem0wLTZjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yem02IDRjMS4xIDAgMi0uOSAyLTJzLS45LTItMi0yLTIgLjktMiAyIC45IDIgMiAyem0wIDJjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yem0wIDZjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yeiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaWQ9ImVkaXRfMjRweCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0zIDE3LjI1VjIxaDMuNzVMMTcuODEgOS45NGwtMy43NS0zLjc1TDMgMTcuMjV6TTIwLjcxIDcuMDRjLjM5LS4zOS4zOS0xLjAyIDAtMS40MWwtMi4zNC0yLjM0Yy0uMzktLjM5LTEuMDItLjM5LTEuNDEgMGwtMS44MyAxLjgzIDMuNzUgMy43NSAxLjgzLTEuODN6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBpZD0iZXJyb3JfMjRweCI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTEgMTVoLTJ2LTJoMnYyem0wLTRoLTJWN2gydjZ6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBpZD0iZXhwYW5kX2xlc3NfMjRweCI+PHBhdGggZD0iTTEyIDhsLTYgNiAxLjQxIDEuNDFMMTIgMTAuODNsNC41OSA0LjU4TDE4IDE0eiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9ImV4cGFuZF9tb3JlXzI0cHgiPjxwYXRoIGQ9Ik0xNi41OSA4LjU5TDEyIDEzLjE3IDcuNDEgOC41OSA2IDEwbDYgNiA2LTZ6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaWQ9ImZpbHRlcl9hbHRfMjRweCI+PGc+PHBhdGggZD0iTTAsMGgyNCBNMjQsMjRIMCIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik00LjI1LDUuNjFDNi4yNyw4LjIsMTAsMTMsMTAsMTN2NmMwLDAuNTUsMC40NSwxLDEsMWgyYzAuNTUsMCwxLTAuNDUsMS0xdi02YzAsMCwzLjcyLTQuOCw1Ljc0LTcuMzkgQzIwLjI1LDQuOTUsMTkuNzgsNCwxOC45NSw0SDUuMDRDNC4yMSw0LDMuNzQsNC45NSw0LjI1LDUuNjF6Ii8+PHBhdGggZD0iTTAsMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PC9nPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9ImZsYWdfMjRweCI+PHBhdGggZD0iTTE0LjQgNkwxNCA0SDV2MTdoMnYtN2g1LjZsLjQgMmg3VjZ6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBpZD0iZnVsbHNjcmVlbl8yNHB4Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTcgMTRINXY1aDV2LTJIN3YtM3ptLTItNGgyVjdoM1Y1SDV2NXptMTIgN2gtM3YyaDV2LTVoLTJ2M3pNMTQgNXYyaDN2M2gyVjVoLTV6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBpZD0iZnVsbHNjcmVlbl9leGl0XzI0cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNNSAxNmgzdjNoMnYtNUg1djJ6bTMtOEg1djJoNVY1SDh2M3ptNiAxMWgydi0zaDN2LTJoLTV2NXptMi0xMVY1aC0ydjVoNVY4aC0zeiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9ImdldF9hcHBfMjRweCI+PHBhdGggZD0iTTE5IDloLTRWM0g5djZINWw3IDcgNy03ek01IDE4djJoMTR2LTJINXoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGlkPSJncm91cF93b3JrXzI0cHgiPjxwYXRoIGQ9Ik0xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyek04IDE3LjVjLTEuMzggMC0yLjUtMS4xMi0yLjUtMi41czEuMTItMi41IDIuNS0yLjUgMi41IDEuMTIgMi41IDIuNS0xLjEyIDIuNS0yLjUgMi41ek05LjUgOGMwLTEuMzggMS4xMi0yLjUgMi41LTIuNXMyLjUgMS4xMiAyLjUgMi41LTEuMTIgMi41LTIuNSAyLjVTOS41IDkuMzggOS41IDh6bTYuNSA5LjVjLTEuMzggMC0yLjUtMS4xMi0yLjUtMi41czEuMTItMi41IDIuNS0yLjUgMi41IDEuMTIgMi41IDIuNS0xLjEyIDIuNS0yLjUgMi41eiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9ImhlbHBfb3V0bGluZV8yNHB4Ij48cGF0aCBkPSJNMTEgMThoMnYtMmgtMnYyem0xLTE2QzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDgtMy41OSA4LTggOHptMC0xNGMtMi4yMSAwLTQgMS43OS00IDRoMmMwLTEuMS45LTIgMi0yczIgLjkgMiAyYzAgMi0zIDEuNzUtMyA1aDJjMC0yLjI1IDMtMi41IDMtNSAwLTIuMjEtMS43OS00LTQtNHoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjQiIGlkPSJpbWFnZV9zZWFyY2hfMjRweCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMFYweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0xOCAxM3Y3SDRWNmg1LjAyYy4wNS0uNzEuMjItMS4zOC40OC0ySDRjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJ2LTVsLTItMnptLTEuNSA1aC0xMWwyLjc1LTMuNTMgMS45NiAyLjM2IDIuNzUtMy41NHptMi44LTkuMTFjLjQ0LS43LjctMS41MS43LTIuMzlDMjAgNC4wMSAxNy45OSAyIDE1LjUgMlMxMSA0LjAxIDExIDYuNXMyLjAxIDQuNSA0LjQ5IDQuNWMuODggMCAxLjctLjI2IDIuMzktLjdMMjEgMTMuNDIgMjIuNDIgMTIgMTkuMyA4Ljg5ek0xNS41IDlDMTQuMTIgOSAxMyA3Ljg4IDEzIDYuNVMxNC4xMiA0IDE1LjUgNCAxOCA1LjEyIDE4IDYuNSAxNi44OCA5IDE1LjUgOXoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGlkPSJpbmZvX291dGxpbmVfMjRweCI+PHBhdGggZD0iTTExIDE3aDJ2LTZoLTJ2NnptMS0xNUM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6TTExIDloMlY3aC0ydjJ6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaWQ9ImtlZXBfMjRweCI+PGc+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIyNCIgd2lkdGg9IjI0Ii8+PC9nPjxnPjxwYXRoIGQ9Ik0xNiw5VjRsMSwwYzAuNTUsMCwxLTAuNDUsMS0xdjBjMC0wLjU1LTAuNDUtMS0xLTFIN0M2LjQ1LDIsNiwyLjQ1LDYsM3YwIGMwLDAuNTUsMC40NSwxLDEsMWwxLDB2NWMwLDEuNjYtMS4zNCwzLTMsM2gwdjJoNS45N3Y3bDEsMWwxLTF2LTdIMTl2LTJoMEMxNy4zNCwxMiwxNiwxMC42NiwxNiw5eiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9nPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyNCAyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjQiIGlkPSJrZWVwX291dGxpbmVfMjRweCI+PGc+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIyNCIgd2lkdGg9IjI0Ii8+PC9nPjxnPjxwYXRoIGQ9Ik0xNCw0djVjMCwxLjEyLDAuMzcsMi4xNiwxLDNIOWMwLjY1LTAuODYsMS0xLjksMS0zVjRIMTQgTTE3LDJIN0M2LjQ1LDIsNiwyLjQ1LDYsM2MwLDAuNTUsMC40NSwxLDEsMWMwLDAsMCwwLDAsMGwxLDB2NSBjMCwxLjY2LTEuMzQsMy0zLDN2Mmg1Ljk3djdsMSwxbDEtMXYtN0gxOXYtMmMwLDAsMCwwLDAsMGMtMS42NiwwLTMtMS4zNC0zLTNWNGwxLDBjMCwwLDAsMCwwLDBjMC41NSwwLDEtMC40NSwxLTEgQzE4LDIuNDUsMTcuNTUsMiwxNywyTDE3LDJ6Ii8+PC9nPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyNCAyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjQiIGlkPSJsaWdodF9tb2RlXzI0cHgiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0xMiw3Yy0yLjc2LDAtNSwyLjI0LTUsNXMyLjI0LDUsNSw1czUtMi4yNCw1LTVTMTQuNzYsNywxMiw3TDEyLDd6IE0yLDEzbDIsMGMwLjU1LDAsMS0wLjQ1LDEtMXMtMC40NS0xLTEtMWwtMiwwIGMtMC41NSwwLTEsMC40NS0xLDFTMS40NSwxMywyLDEzeiBNMjAsMTNsMiwwYzAuNTUsMCwxLTAuNDUsMS0xcy0wLjQ1LTEtMS0xbC0yLDBjLTAuNTUsMC0xLDAuNDUtMSwxUzE5LjQ1LDEzLDIwLDEzeiBNMTEsMnYyIGMwLDAuNTUsMC40NSwxLDEsMXMxLTAuNDUsMS0xVjJjMC0wLjU1LTAuNDUtMS0xLTFTMTEsMS40NSwxMSwyeiBNMTEsMjB2MmMwLDAuNTUsMC40NSwxLDEsMXMxLTAuNDUsMS0xdi0yYzAtMC41NS0wLjQ1LTEtMS0xIEMxMS40NSwxOSwxMSwxOS40NSwxMSwyMHogTTUuOTksNC41OGMtMC4zOS0wLjM5LTEuMDMtMC4zOS0xLjQxLDBjLTAuMzksMC4zOS0wLjM5LDEuMDMsMCwxLjQxbDEuMDYsMS4wNiBjMC4zOSwwLjM5LDEuMDMsMC4zOSwxLjQxLDBzMC4zOS0xLjAzLDAtMS40MUw1Ljk5LDQuNTh6IE0xOC4zNiwxNi45NWMtMC4zOS0wLjM5LTEuMDMtMC4zOS0xLjQxLDBjLTAuMzksMC4zOS0wLjM5LDEuMDMsMCwxLjQxIGwxLjA2LDEuMDZjMC4zOSwwLjM5LDEuMDMsMC4zOSwxLjQxLDBjMC4zOS0wLjM5LDAuMzktMS4wMywwLTEuNDFMMTguMzYsMTYuOTV6IE0xOS40Miw1Ljk5YzAuMzktMC4zOSwwLjM5LTEuMDMsMC0xLjQxIGMtMC4zOS0wLjM5LTEuMDMtMC4zOS0xLjQxLDBsLTEuMDYsMS4wNmMtMC4zOSwwLjM5LTAuMzksMS4wMywwLDEuNDFzMS4wMywwLjM5LDEuNDEsMEwxOS40Miw1Ljk5eiBNNy4wNSwxOC4zNiBjMC4zOS0wLjM5LDAuMzktMS4wMywwLTEuNDFjLTAuMzktMC4zOS0xLjAzLTAuMzktMS40MSwwbC0xLjA2LDEuMDZjLTAuMzksMC4zOS0wLjM5LDEuMDMsMCwxLjQxczEuMDMsMC4zOSwxLjQxLDBMNy4wNSwxOC4zNnoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBpZD0ibGluZV93ZWlnaHRfMjRweCI+PGc+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIyNCIgd2lkdGg9IjI0IiB4PSIwIi8+PC9nPjxnPjxnPjxnPjxwYXRoIGQ9Ik0zLDE3aDE4di0ySDNWMTd6IE0zLDIwaDE4di0xSDNWMjB6IE0zLDEzaDE4di0zSDNWMTN6IE0zLDR2NGgxOFY0SDN6Ii8+PC9nPjwvZz48L2c+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBpZD0ibW9yZV92ZXJ0XzI0cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTIgOGMxLjEgMCAyLS45IDItMnMtLjktMi0yLTItMiAuOS0yIDIgLjkgMiAyIDJ6bTAgMmMtMS4xIDAtMiAuOS0yIDJzLjkgMiAyIDIgMi0uOSAyLTItLjktMi0yLTJ6bTAgNmMtMS4xIDAtMiAuOS0yIDJzLjkgMiAyIDIgMi0uOSAyLTItLjktMi0yLTJ6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBpZD0ibm90aWZpY2F0aW9uc19ub25lXzI0cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTIgMjJjMS4xIDAgMi0uOSAyLTJoLTRjMCAxLjEuOSAyIDIgMnptNi02di01YzAtMy4wNy0xLjYzLTUuNjQtNC41LTYuMzJWNGMwLS44My0uNjctMS41LTEuNS0xLjVzLTEuNS42Ny0xLjUgMS41di42OEM3LjY0IDUuMzYgNiA3LjkyIDYgMTF2NWwtMiAydjFoMTZ2LTFsLTItMnptLTIgMUg4di02YzAtMi40OCAxLjUxLTQuNSA0LTQuNXM0IDIuMDIgNCA0LjV2NnoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGlkPSJwYWxldHRlXzI0cHgiPjxwYXRoIGQ9Ik0xMiAzYy00Ljk3IDAtOSA0LjAzLTkgOXM0LjAzIDkgOSA5Yy44MyAwIDEuNS0uNjcgMS41LTEuNSAwLS4zOS0uMTUtLjc0LS4zOS0xLjAxLS4yMy0uMjYtLjM4LS42MS0uMzgtLjk5IDAtLjgzLjY3LTEuNSAxLjUtMS41SDE2YzIuNzYgMCA1LTIuMjQgNS01IDAtNC40Mi00LjAzLTgtOS04em0tNS41IDljLS44MyAwLTEuNS0uNjctMS41LTEuNVM1LjY3IDkgNi41IDkgOCA5LjY3IDggMTAuNSA3LjMzIDEyIDYuNSAxMnptMy00QzguNjcgOCA4IDcuMzMgOCA2LjVTOC42NyA1IDkuNSA1czEuNS42NyAxLjUgMS41UzEwLjMzIDggOS41IDh6bTUgMGMtLjgzIDAtMS41LS42Ny0xLjUtMS41UzEzLjY3IDUgMTQuNSA1czEuNS42NyAxLjUgMS41UzE1LjMzIDggMTQuNSA4em0zIDRjLS44MyAwLTEuNS0uNjctMS41LTEuNVMxNi42NyA5IDE3LjUgOXMxLjUuNjcgMS41IDEuNS0uNjcgMS41LTEuNSAxLjV6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBpZD0icmVmcmVzaF8yNHB4Ij48cGF0aCBkPSJNMTcuNjUgNi4zNUMxNi4yIDQuOSAxNC4yMSA0IDEyIDRjLTQuNDIgMC03Ljk5IDMuNTgtNy45OSA4czMuNTcgOCA3Ljk5IDhjMy43MyAwIDYuODQtMi41NSA3LjczLTZoLTIuMDhjLS44MiAyLjMzLTMuMDQgNC01LjY1IDQtMy4zMSAwLTYtMi42OS02LTZzMi42OS02IDYtNmMxLjY2IDAgMy4xNC42OSA0LjIyIDEuNzhMMTMgMTFoN1Y0bC0yLjM1IDIuMzV6Ii8+PC9zdmc+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBpZD0ic2VhcmNoXzI0cHgiPjxwYXRoIGQ9Ik0xNS41IDE0aC0uNzlsLS4yOC0uMjdDMTUuNDEgMTIuNTkgMTYgMTEuMTEgMTYgOS41IDE2IDUuOTEgMTMuMDkgMyA5LjUgM1MzIDUuOTEgMyA5LjUgNS45MSAxNiA5LjUgMTZjMS42MSAwIDMuMDktLjU5IDQuMjMtMS41N2wuMjcuMjh2Ljc5bDUgNC45OUwyMC40OSAxOWwtNC45OS01em0tNiAwQzcuMDEgMTQgNSAxMS45OSA1IDkuNVM3LjAxIDUgOS41IDUgMTQgNy4wMSAxNCA5LjUgMTEuOTkgMTQgOS41IDE0eiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9InNldHRpbmdzXzI0cHgiPjxwYXRoIGQ9Ik0xOS40MyAxMi45OGMuMDQtLjMyLjA3LS42NC4wNy0uOThzLS4wMy0uNjYtLjA3LS45OGwyLjExLTEuNjVjLjE5LS4xNS4yNC0uNDIuMTItLjY0bC0yLTMuNDZjLS4xMi0uMjItLjM5LS4zLS42MS0uMjJsLTIuNDkgMWMtLjUyLS40LTEuMDgtLjczLTEuNjktLjk4bC0uMzgtMi42NUMxNC40NiAyLjE4IDE0LjI1IDIgMTQgMmgtNGMtLjI1IDAtLjQ2LjE4LS40OS40MmwtLjM4IDIuNjVjLS42MS4yNS0xLjE3LjU5LTEuNjkuOThsLTIuNDktMWMtLjIzLS4wOS0uNDkgMC0uNjEuMjJsLTIgMy40NmMtLjEzLjIyLS4wNy40OS4xMi42NGwyLjExIDEuNjVjLS4wNC4zMi0uMDcuNjUtLjA3Ljk4cy4wMy42Ni4wNy45OGwtMi4xMSAxLjY1Yy0uMTkuMTUtLjI0LjQyLS4xMi42NGwyIDMuNDZjLjEyLjIyLjM5LjMuNjEuMjJsMi40OS0xYy41Mi40IDEuMDguNzMgMS42OS45OGwuMzggMi42NWMuMDMuMjQuMjQuNDIuNDkuNDJoNGMuMjUgMCAuNDYtLjE4LjQ5LS40MmwuMzgtMi42NWMuNjEtLjI1IDEuMTctLjU5IDEuNjktLjk4bDIuNDkgMWMuMjMuMDkuNDkgMCAuNjEtLjIybDItMy40NmMuMTItLjIyLjA3LS40OS0uMTItLjY0bC0yLjExLTEuNjV6TTEyIDE1LjVjLTEuOTMgMC0zLjUtMS41Ny0zLjUtMy41czEuNTctMy41IDMuNS0zLjUgMy41IDEuNTcgMy41IDMuNS0xLjU3IDMuNS0zLjUgMy41eiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaWQ9InNldHRpbmdzX2JhY2t1cF9yZXN0b3JlXzI0cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTQgMTJjMC0xLjEtLjktMi0yLTJzLTIgLjktMiAyIC45IDIgMiAyIDItLjkgMi0yem0tMi05Yy00Ljk3IDAtOSA0LjAzLTkgOUgwbDQgNCA0LTRINWMwLTMuODcgMy4xMy03IDctN3M3IDMuMTMgNyA3LTMuMTMgNy03IDdjLTEuNTEgMC0yLjkxLS40OS00LjA2LTEuM2wtMS40MiAxLjQ0QzguMDQgMjAuMyA5Ljk0IDIxIDEyIDIxYzQuOTcgMCA5LTQuMDMgOS05cy00LjAzLTktOS05eiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaWQ9InNldHRpbmdzX292ZXJzY2FuXzI0cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTIuMDEgNS41TDEwIDhoNGwtMS45OS0yLjV6TTE4IDEwdjRsMi41LTEuOTlMMTggMTB6TTYgMTBsLTIuNSAyLjAxTDYgMTR2LTR6bTggNmgtNGwyLjAxIDIuNUwxNCAxNnptNy0xM0gzYy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE4YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTYuMDFIM1Y0Ljk5aDE4djE0LjAyeiIvPjwvc3ZnPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgaWQ9InZpc2liaWxpdHlfb2ZmXzI0cHgiPjxwYXRoIGQ9Ik0xMiA3YzIuNzYgMCA1IDIuMjQgNSA1IDAgLjY1LS4xMyAxLjI2LS4zNiAxLjgzbDIuOTIgMi45MmMxLjUxLTEuMjYgMi43LTIuODkgMy40My00Ljc1LTEuNzMtNC4zOS02LTcuNS0xMS03LjUtMS40IDAtMi43NC4yNS0zLjk4LjdsMi4xNiAyLjE2QzEwLjc0IDcuMTMgMTEuMzUgNyAxMiA3ek0yIDQuMjdsMi4yOCAyLjI4LjQ2LjQ2QzMuMDggOC4zIDEuNzggMTAuMDIgMSAxMmMxLjczIDQuMzkgNiA3LjUgMTEgNy41IDEuNTUgMCAzLjAzLS4zIDQuMzgtLjg0bC40Mi40MkwxOS43MyAyMiAyMSAyMC43MyAzLjI3IDMgMiA0LjI3ek03LjUzIDkuOGwxLjU1IDEuNTVjLS4wNS4yMS0uMDguNDMtLjA4LjY1IDAgMS42NiAxLjM0IDMgMyAzIC4yMiAwIC40NC0uMDMuNjUtLjA4bDEuNTUgMS41NWMtLjY3LjMzLTEuNDEuNTMtMi4yLjUzLTIuNzYgMC01LTIuMjQtNS01IDAtLjc5LjItMS41My41My0yLjJ6bTQuMzEtLjc4bDMuMTUgMy4xNS4wMi0uMTZjMC0xLjY2LTEuMzQtMy0zLTNsLS4xNy4wMXoiLz48L3N2Zz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjQiIGlkPSJ3YXJuaW5nXzI0cHgiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMSAyMWgyMkwxMiAyIDEgMjF6bTEyLTNoLTJ2LTJoMnYyem0wLTRoLTJ2LTRoMnY0eiIvPjwvc3ZnPjwvZGVmcz48L3N2Zz4K", + "headers": [ + [ + "content-type", + "image/svg+xml; charset=utf-8" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/index.js?_file_hash=486f34d2": { + "data": "dmFyIENMT1NVUkVfTk9fREVQUyA9IHRydWU7CndpbmRvdy5wb2x5bWVyU2tpcExvYWRpbmdGb250Um9ib3RvID0gdHJ1ZTsKLy8gQ29weXJpZ2h0IDIwMTQgR29vZ2xlIEluYy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KLy8KLy8gTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7Ci8vIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KLy8gICAgIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAovLwovLyBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKLy8KLy8gVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQovLyBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAovLyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KLy8gICAgIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmQKLy8gbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgohZnVuY3Rpb24oKXt2YXIgYT17fSxiPXt9LGM9e307IWZ1bmN0aW9uKGEsYil7ZnVuY3Rpb24gYyhhKXtpZigibnVtYmVyIj09dHlwZW9mIGEpcmV0dXJuIGE7dmFyIGI9e307Zm9yKHZhciBjIGluIGEpYltjXT1hW2NdO3JldHVybiBifWZ1bmN0aW9uIGQoKXt0aGlzLl9kZWxheT0wLHRoaXMuX2VuZERlbGF5PTAsdGhpcy5fZmlsbD0ibm9uZSIsdGhpcy5faXRlcmF0aW9uU3RhcnQ9MCx0aGlzLl9pdGVyYXRpb25zPTEsdGhpcy5fZHVyYXRpb249MCx0aGlzLl9wbGF5YmFja1JhdGU9MSx0aGlzLl9kaXJlY3Rpb249Im5vcm1hbCIsdGhpcy5fZWFzaW5nPSJsaW5lYXIiLHRoaXMuX2Vhc2luZ0Z1bmN0aW9uPXh9ZnVuY3Rpb24gZSgpe3JldHVybiBhLmlzRGVwcmVjYXRlZCgiSW52YWxpZCB0aW1pbmcgaW5wdXRzIiwiMjAxNi0wMy0wMiIsIlR5cGVFcnJvciBleGNlcHRpb25zIHdpbGwgYmUgdGhyb3duIGluc3RlYWQuIiwhMCl9ZnVuY3Rpb24gZihiLGMsZSl7dmFyIGY9bmV3IGQ7cmV0dXJuIGMmJihmLmZpbGw9ImJvdGgiLGYuZHVyYXRpb249ImF1dG8iKSwibnVtYmVyIiE9dHlwZW9mIGJ8fGlzTmFOKGIpP3ZvaWQgMCE9PWImJk9iamVjdC5nZXRPd25Qcm9wZXJ0eU5hbWVzKGIpLmZvckVhY2goZnVuY3Rpb24oYyl7aWYoImF1dG8iIT1iW2NdKXtpZigoIm51bWJlciI9PXR5cGVvZiBmW2NdfHwiZHVyYXRpb24iPT1jKSYmKCJudW1iZXIiIT10eXBlb2YgYltjXXx8aXNOYU4oYltjXSkpKXJldHVybjtpZigiZmlsbCI9PWMmJi0xPT12LmluZGV4T2YoYltjXSkpcmV0dXJuO2lmKCJkaXJlY3Rpb24iPT1jJiYtMT09dy5pbmRleE9mKGJbY10pKXJldHVybjtpZigicGxheWJhY2tSYXRlIj09YyYmMSE9PWJbY10mJmEuaXNEZXByZWNhdGVkKCJBbmltYXRpb25FZmZlY3RUaW1pbmcucGxheWJhY2tSYXRlIiwiMjAxNC0xMS0yOCIsIlVzZSBBbmltYXRpb24ucGxheWJhY2tSYXRlIGluc3RlYWQuIikpcmV0dXJuO2ZbY109YltjXX19KTpmLmR1cmF0aW9uPWIsZn1mdW5jdGlvbiBnKGEpe3JldHVybiJudW1iZXIiPT10eXBlb2YgYSYmKGE9aXNOYU4oYSk/e2R1cmF0aW9uOjB9OntkdXJhdGlvbjphfSksYX1mdW5jdGlvbiBoKGIsYyl7cmV0dXJuIGI9YS5udW1lcmljVGltaW5nVG9PYmplY3QoYiksZihiLGMpfWZ1bmN0aW9uIGkoYSxiLGMsZCl7cmV0dXJuIGE8MHx8YT4xfHxjPDB8fGM+MT94OmZ1bmN0aW9uKGUpe2Z1bmN0aW9uIGYoYSxiLGMpe3JldHVybiAzKmEqKDEtYykqKDEtYykqYyszKmIqKDEtYykqYypjK2MqYypjfWlmKGU8PTApe3ZhciBnPTA7cmV0dXJuIGE+MD9nPWIvYTohYiYmYz4wJiYoZz1kL2MpLGcqZX1pZihlPj0xKXt2YXIgaD0wO3JldHVybiBjPDE/aD0oZC0xKS8oYy0xKToxPT1jJiZhPDEmJihoPShiLTEpLyhhLTEpKSwxK2gqKGUtMSl9Zm9yKHZhciBpPTAsaj0xO2k8ajspe3ZhciBrPShpK2opLzIsbD1mKGEsYyxrKTtpZihNYXRoLmFicyhlLWwpPDFlLTUpcmV0dXJuIGYoYixkLGspO2w8ZT9pPWs6aj1rfXJldHVybiBmKGIsZCxrKX19ZnVuY3Rpb24gaihhLGIpe3JldHVybiBmdW5jdGlvbihjKXtpZihjPj0xKXJldHVybiAxO3ZhciBkPTEvYTtyZXR1cm4oYys9YipkKS1jJWR9fWZ1bmN0aW9uIGsoYSl7Q3x8KEM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iikuc3R5bGUpLEMuYW5pbWF0aW9uVGltaW5nRnVuY3Rpb249IiIsQy5hbmltYXRpb25UaW1pbmdGdW5jdGlvbj1hO3ZhciBiPUMuYW5pbWF0aW9uVGltaW5nRnVuY3Rpb247aWYoIiI9PWImJmUoKSl0aHJvdyBuZXcgVHlwZUVycm9yKGErIiBpcyBub3QgYSB2YWxpZCB2YWx1ZSBmb3IgZWFzaW5nIik7cmV0dXJuIGJ9ZnVuY3Rpb24gbChhKXtpZigibGluZWFyIj09YSlyZXR1cm4geDt2YXIgYj1FLmV4ZWMoYSk7aWYoYilyZXR1cm4gaS5hcHBseSh0aGlzLGIuc2xpY2UoMSkubWFwKE51bWJlcikpO3ZhciBjPUYuZXhlYyhhKTtpZihjKXJldHVybiBqKE51bWJlcihjWzFdKSxBKTt2YXIgZD1HLmV4ZWMoYSk7cmV0dXJuIGQ/aihOdW1iZXIoZFsxXSkse3N0YXJ0OnksbWlkZGxlOnosZW5kOkF9W2RbMl1dKTpCW2FdfHx4fWZ1bmN0aW9uIG0oYSl7cmV0dXJuIE1hdGguYWJzKG4oYSkvYS5wbGF5YmFja1JhdGUpfWZ1bmN0aW9uIG4oYSl7cmV0dXJuIDA9PT1hLmR1cmF0aW9ufHwwPT09YS5pdGVyYXRpb25zPzA6YS5kdXJhdGlvbiphLml0ZXJhdGlvbnN9ZnVuY3Rpb24gbyhhLGIsYyl7aWYobnVsbD09YilyZXR1cm4gSDt2YXIgZD1jLmRlbGF5K2ErYy5lbmREZWxheTtyZXR1cm4gYjxNYXRoLm1pbihjLmRlbGF5LGQpP0k6Yj49TWF0aC5taW4oYy5kZWxheSthLGQpP0o6S31mdW5jdGlvbiBwKGEsYixjLGQsZSl7c3dpdGNoKGQpe2Nhc2UgSTpyZXR1cm4iYmFja3dhcmRzIj09Ynx8ImJvdGgiPT1iPzA6bnVsbDtjYXNlIEs6cmV0dXJuIGMtZTtjYXNlIEo6cmV0dXJuImZvcndhcmRzIj09Ynx8ImJvdGgiPT1iP2E6bnVsbDtjYXNlIEg6cmV0dXJuIG51bGx9fWZ1bmN0aW9uIHEoYSxiLGMsZCxlKXt2YXIgZj1lO3JldHVybiAwPT09YT9iIT09SSYmKGYrPWMpOmYrPWQvYSxmfWZ1bmN0aW9uIHIoYSxiLGMsZCxlLGYpe3ZhciBnPWE9PT0xLzA/YiUxOmElMTtyZXR1cm4gMCE9PWd8fGMhPT1KfHwwPT09ZHx8MD09PWUmJjAhPT1mfHwoZz0xKSxnfWZ1bmN0aW9uIHMoYSxiLGMsZCl7cmV0dXJuIGE9PT1KJiZiPT09MS8wPzEvMDoxPT09Yz9NYXRoLmZsb29yKGQpLTE6TWF0aC5mbG9vcihkKX1mdW5jdGlvbiB0KGEsYixjKXt2YXIgZD1hO2lmKCJub3JtYWwiIT09YSYmInJldmVyc2UiIT09YSl7dmFyIGU9YjsiYWx0ZXJuYXRlLXJldmVyc2UiPT09YSYmKGUrPTEpLGQ9Im5vcm1hbCIsZSE9PTEvMCYmZSUyIT0wJiYoZD0icmV2ZXJzZSIpfXJldHVybiJub3JtYWwiPT09ZD9jOjEtY31mdW5jdGlvbiB1KGEsYixjKXt2YXIgZD1vKGEsYixjKSxlPXAoYSxjLmZpbGwsYixkLGMuZGVsYXkpO2lmKG51bGw9PT1lKXJldHVybiBudWxsO3ZhciBmPXEoYy5kdXJhdGlvbixkLGMuaXRlcmF0aW9ucyxlLGMuaXRlcmF0aW9uU3RhcnQpLGc9cihmLGMuaXRlcmF0aW9uU3RhcnQsZCxjLml0ZXJhdGlvbnMsZSxjLmR1cmF0aW9uKSxoPXMoZCxjLml0ZXJhdGlvbnMsZyxmKSxpPXQoYy5kaXJlY3Rpb24saCxnKTtyZXR1cm4gYy5fZWFzaW5nRnVuY3Rpb24oaSl9dmFyIHY9ImJhY2t3YXJkc3xmb3J3YXJkc3xib3RofG5vbmUiLnNwbGl0KCJ8Iiksdz0icmV2ZXJzZXxhbHRlcm5hdGV8YWx0ZXJuYXRlLXJldmVyc2UiLnNwbGl0KCJ8IikseD1mdW5jdGlvbihhKXtyZXR1cm4gYX07ZC5wcm90b3R5cGU9e19zZXRNZW1iZXI6ZnVuY3Rpb24oYixjKXt0aGlzWyJfIitiXT1jLHRoaXMuX2VmZmVjdCYmKHRoaXMuX2VmZmVjdC5fdGltaW5nSW5wdXRbYl09Yyx0aGlzLl9lZmZlY3QuX3RpbWluZz1hLm5vcm1hbGl6ZVRpbWluZ0lucHV0KHRoaXMuX2VmZmVjdC5fdGltaW5nSW5wdXQpLHRoaXMuX2VmZmVjdC5hY3RpdmVEdXJhdGlvbj1hLmNhbGN1bGF0ZUFjdGl2ZUR1cmF0aW9uKHRoaXMuX2VmZmVjdC5fdGltaW5nKSx0aGlzLl9lZmZlY3QuX2FuaW1hdGlvbiYmdGhpcy5fZWZmZWN0Ll9hbmltYXRpb24uX3JlYnVpbGRVbmRlcmx5aW5nQW5pbWF0aW9uKCkpfSxnZXQgcGxheWJhY2tSYXRlKCl7cmV0dXJuIHRoaXMuX3BsYXliYWNrUmF0ZX0sc2V0IGRlbGF5KGEpe3RoaXMuX3NldE1lbWJlcigiZGVsYXkiLGEpfSxnZXQgZGVsYXkoKXtyZXR1cm4gdGhpcy5fZGVsYXl9LHNldCBlbmREZWxheShhKXt0aGlzLl9zZXRNZW1iZXIoImVuZERlbGF5IixhKX0sZ2V0IGVuZERlbGF5KCl7cmV0dXJuIHRoaXMuX2VuZERlbGF5fSxzZXQgZmlsbChhKXt0aGlzLl9zZXRNZW1iZXIoImZpbGwiLGEpfSxnZXQgZmlsbCgpe3JldHVybiB0aGlzLl9maWxsfSxzZXQgaXRlcmF0aW9uU3RhcnQoYSl7aWYoKGlzTmFOKGEpfHxhPDApJiZlKCkpdGhyb3cgbmV3IFR5cGVFcnJvcigiaXRlcmF0aW9uU3RhcnQgbXVzdCBiZSBhIG5vbi1uZWdhdGl2ZSBudW1iZXIsIHJlY2VpdmVkOiAiK2EpO3RoaXMuX3NldE1lbWJlcigiaXRlcmF0aW9uU3RhcnQiLGEpfSxnZXQgaXRlcmF0aW9uU3RhcnQoKXtyZXR1cm4gdGhpcy5faXRlcmF0aW9uU3RhcnR9LHNldCBkdXJhdGlvbihhKXtpZigiYXV0byIhPWEmJihpc05hTihhKXx8YTwwKSYmZSgpKXRocm93IG5ldyBUeXBlRXJyb3IoImR1cmF0aW9uIG11c3QgYmUgbm9uLW5lZ2F0aXZlIG9yIGF1dG8sIHJlY2VpdmVkOiAiK2EpO3RoaXMuX3NldE1lbWJlcigiZHVyYXRpb24iLGEpfSxnZXQgZHVyYXRpb24oKXtyZXR1cm4gdGhpcy5fZHVyYXRpb259LHNldCBkaXJlY3Rpb24oYSl7dGhpcy5fc2V0TWVtYmVyKCJkaXJlY3Rpb24iLGEpfSxnZXQgZGlyZWN0aW9uKCl7cmV0dXJuIHRoaXMuX2RpcmVjdGlvbn0sc2V0IGVhc2luZyhhKXt0aGlzLl9lYXNpbmdGdW5jdGlvbj1sKGsoYSkpLHRoaXMuX3NldE1lbWJlcigiZWFzaW5nIixhKX0sZ2V0IGVhc2luZygpe3JldHVybiB0aGlzLl9lYXNpbmd9LHNldCBpdGVyYXRpb25zKGEpe2lmKChpc05hTihhKXx8YTwwKSYmZSgpKXRocm93IG5ldyBUeXBlRXJyb3IoIml0ZXJhdGlvbnMgbXVzdCBiZSBub24tbmVnYXRpdmUsIHJlY2VpdmVkOiAiK2EpO3RoaXMuX3NldE1lbWJlcigiaXRlcmF0aW9ucyIsYSl9LGdldCBpdGVyYXRpb25zKCl7cmV0dXJuIHRoaXMuX2l0ZXJhdGlvbnN9fTt2YXIgeT0xLHo9LjUsQT0wLEI9e2Vhc2U6aSguMjUsLjEsLjI1LDEpLCJlYXNlLWluIjppKC40MiwwLDEsMSksImVhc2Utb3V0IjppKDAsMCwuNTgsMSksImVhc2UtaW4tb3V0IjppKC40MiwwLC41OCwxKSwic3RlcC1zdGFydCI6aigxLHkpLCJzdGVwLW1pZGRsZSI6aigxLHopLCJzdGVwLWVuZCI6aigxLEEpfSxDPW51bGwsRD0iXFxzKigtP1xcZCtcXC4/XFxkKnwtP1xcLlxcZCspXFxzKiIsRT1uZXcgUmVnRXhwKCJjdWJpYy1iZXppZXJcXCgiK0QrIiwiK0QrIiwiK0QrIiwiK0QrIlxcKSIpLEY9L3N0ZXBzXChccyooXGQrKVxzKlwpLyxHPS9zdGVwc1woXHMqKFxkKylccyosXHMqKHN0YXJ0fG1pZGRsZXxlbmQpXHMqXCkvLEg9MCxJPTEsSj0yLEs9MzthLmNsb25lVGltaW5nSW5wdXQ9YyxhLm1ha2VUaW1pbmc9ZixhLm51bWVyaWNUaW1pbmdUb09iamVjdD1nLGEubm9ybWFsaXplVGltaW5nSW5wdXQ9aCxhLmNhbGN1bGF0ZUFjdGl2ZUR1cmF0aW9uPW0sYS5jYWxjdWxhdGVJdGVyYXRpb25Qcm9ncmVzcz11LGEuY2FsY3VsYXRlUGhhc2U9byxhLm5vcm1hbGl6ZUVhc2luZz1rLGEucGFyc2VFYXNpbmdGdW5jdGlvbj1sfShhKSxmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSxiKXtyZXR1cm4gYSBpbiBrP2tbYV1bYl18fGI6Yn1mdW5jdGlvbiBkKGEpe3JldHVybiJkaXNwbGF5Ij09PWF8fDA9PT1hLmxhc3RJbmRleE9mKCJhbmltYXRpb24iLDApfHwwPT09YS5sYXN0SW5kZXhPZigidHJhbnNpdGlvbiIsMCl9ZnVuY3Rpb24gZShhLGIsZSl7aWYoIWQoYSkpe3ZhciBmPWhbYV07aWYoZil7aS5zdHlsZVthXT1iO2Zvcih2YXIgZyBpbiBmKXt2YXIgaj1mW2ddLGs9aS5zdHlsZVtqXTtlW2pdPWMoaixrKX19ZWxzZSBlW2FdPWMoYSxiKX19ZnVuY3Rpb24gZihhKXt2YXIgYj1bXTtmb3IodmFyIGMgaW4gYSlpZighKGMgaW5bImVhc2luZyIsIm9mZnNldCIsImNvbXBvc2l0ZSJdKSl7dmFyIGQ9YVtjXTtBcnJheS5pc0FycmF5KGQpfHwoZD1bZF0pO2Zvcih2YXIgZSxmPWQubGVuZ3RoLGc9MDtnPGY7ZysrKWU9e30sZS5vZmZzZXQ9Im9mZnNldCJpbiBhP2Eub2Zmc2V0OjE9PWY/MTpnLyhmLTEpLCJlYXNpbmciaW4gYSYmKGUuZWFzaW5nPWEuZWFzaW5nKSwiY29tcG9zaXRlImluIGEmJihlLmNvbXBvc2l0ZT1hLmNvbXBvc2l0ZSksZVtjXT1kW2ddLGIucHVzaChlKX1yZXR1cm4gYi5zb3J0KGZ1bmN0aW9uKGEsYil7cmV0dXJuIGEub2Zmc2V0LWIub2Zmc2V0fSksYn1mdW5jdGlvbiBnKGIpe2Z1bmN0aW9uIGMoKXt2YXIgYT1kLmxlbmd0aDtudWxsPT1kW2EtMV0ub2Zmc2V0JiYoZFthLTFdLm9mZnNldD0xKSxhPjEmJm51bGw9PWRbMF0ub2Zmc2V0JiYoZFswXS5vZmZzZXQ9MCk7Zm9yKHZhciBiPTAsYz1kWzBdLm9mZnNldCxlPTE7ZTxhO2UrKyl7dmFyIGY9ZFtlXS5vZmZzZXQ7aWYobnVsbCE9Zil7Zm9yKHZhciBnPTE7ZzxlLWI7ZysrKWRbYitnXS5vZmZzZXQ9YysoZi1jKSpnLyhlLWIpO2I9ZSxjPWZ9fX1pZihudWxsPT1iKXJldHVybltdO3dpbmRvdy5TeW1ib2wmJlN5bWJvbC5pdGVyYXRvciYmQXJyYXkucHJvdG90eXBlLmZyb20mJmJbU3ltYm9sLml0ZXJhdG9yXSYmKGI9QXJyYXkuZnJvbShiKSksQXJyYXkuaXNBcnJheShiKXx8KGI9ZihiKSk7Zm9yKHZhciBkPWIubWFwKGZ1bmN0aW9uKGIpe3ZhciBjPXt9O2Zvcih2YXIgZCBpbiBiKXt2YXIgZj1iW2RdO2lmKCJvZmZzZXQiPT1kKXtpZihudWxsIT1mKXtpZihmPU51bWJlcihmKSwhaXNGaW5pdGUoZikpdGhyb3cgbmV3IFR5cGVFcnJvcigiS2V5ZnJhbWUgb2Zmc2V0cyBtdXN0IGJlIG51bWJlcnMuIik7aWYoZjwwfHxmPjEpdGhyb3cgbmV3IFR5cGVFcnJvcigiS2V5ZnJhbWUgb2Zmc2V0cyBtdXN0IGJlIGJldHdlZW4gMCBhbmQgMS4iKX19ZWxzZSBpZigiY29tcG9zaXRlIj09ZCl7aWYoImFkZCI9PWZ8fCJhY2N1bXVsYXRlIj09Zil0aHJvd3t0eXBlOkRPTUV4Y2VwdGlvbi5OT1RfU1VQUE9SVEVEX0VSUixuYW1lOiJOb3RTdXBwb3J0ZWRFcnJvciIsbWVzc2FnZToiYWRkIGNvbXBvc2l0aW5nIGlzIG5vdCBzdXBwb3J0ZWQifTtpZigicmVwbGFjZSIhPWYpdGhyb3cgbmV3IFR5cGVFcnJvcigiSW52YWxpZCBjb21wb3NpdGUgbW9kZSAiK2YrIi4iKX1lbHNlIGY9ImVhc2luZyI9PWQ/YS5ub3JtYWxpemVFYXNpbmcoZik6IiIrZjtlKGQsZixjKX1yZXR1cm4gdm9pZCAwPT1jLm9mZnNldCYmKGMub2Zmc2V0PW51bGwpLHZvaWQgMD09Yy5lYXNpbmcmJihjLmVhc2luZz0ibGluZWFyIiksY30pLGc9ITAsaD0tMS8wLGk9MDtpPGQubGVuZ3RoO2krKyl7dmFyIGo9ZFtpXS5vZmZzZXQ7aWYobnVsbCE9ail7aWYoajxoKXRocm93IG5ldyBUeXBlRXJyb3IoIktleWZyYW1lcyBhcmUgbm90IGxvb3NlbHkgc29ydGVkIGJ5IG9mZnNldC4gU29ydCBvciBzcGVjaWZ5IG9mZnNldHMuIik7aD1qfWVsc2UgZz0hMX1yZXR1cm4gZD1kLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYS5vZmZzZXQ+PTAmJmEub2Zmc2V0PD0xfSksZ3x8YygpLGR9dmFyIGg9e2JhY2tncm91bmQ6WyJiYWNrZ3JvdW5kSW1hZ2UiLCJiYWNrZ3JvdW5kUG9zaXRpb24iLCJiYWNrZ3JvdW5kU2l6ZSIsImJhY2tncm91bmRSZXBlYXQiLCJiYWNrZ3JvdW5kQXR0YWNobWVudCIsImJhY2tncm91bmRPcmlnaW4iLCJiYWNrZ3JvdW5kQ2xpcCIsImJhY2tncm91bmRDb2xvciJdLGJvcmRlcjpbImJvcmRlclRvcENvbG9yIiwiYm9yZGVyVG9wU3R5bGUiLCJib3JkZXJUb3BXaWR0aCIsImJvcmRlclJpZ2h0Q29sb3IiLCJib3JkZXJSaWdodFN0eWxlIiwiYm9yZGVyUmlnaHRXaWR0aCIsImJvcmRlckJvdHRvbUNvbG9yIiwiYm9yZGVyQm90dG9tU3R5bGUiLCJib3JkZXJCb3R0b21XaWR0aCIsImJvcmRlckxlZnRDb2xvciIsImJvcmRlckxlZnRTdHlsZSIsImJvcmRlckxlZnRXaWR0aCJdLGJvcmRlckJvdHRvbTpbImJvcmRlckJvdHRvbVdpZHRoIiwiYm9yZGVyQm90dG9tU3R5bGUiLCJib3JkZXJCb3R0b21Db2xvciJdLGJvcmRlckNvbG9yOlsiYm9yZGVyVG9wQ29sb3IiLCJib3JkZXJSaWdodENvbG9yIiwiYm9yZGVyQm90dG9tQ29sb3IiLCJib3JkZXJMZWZ0Q29sb3IiXSxib3JkZXJMZWZ0OlsiYm9yZGVyTGVmdFdpZHRoIiwiYm9yZGVyTGVmdFN0eWxlIiwiYm9yZGVyTGVmdENvbG9yIl0sYm9yZGVyUmFkaXVzOlsiYm9yZGVyVG9wTGVmdFJhZGl1cyIsImJvcmRlclRvcFJpZ2h0UmFkaXVzIiwiYm9yZGVyQm90dG9tUmlnaHRSYWRpdXMiLCJib3JkZXJCb3R0b21MZWZ0UmFkaXVzIl0sYm9yZGVyUmlnaHQ6WyJib3JkZXJSaWdodFdpZHRoIiwiYm9yZGVyUmlnaHRTdHlsZSIsImJvcmRlclJpZ2h0Q29sb3IiXSxib3JkZXJUb3A6WyJib3JkZXJUb3BXaWR0aCIsImJvcmRlclRvcFN0eWxlIiwiYm9yZGVyVG9wQ29sb3IiXSxib3JkZXJXaWR0aDpbImJvcmRlclRvcFdpZHRoIiwiYm9yZGVyUmlnaHRXaWR0aCIsImJvcmRlckJvdHRvbVdpZHRoIiwiYm9yZGVyTGVmdFdpZHRoIl0sZmxleDpbImZsZXhHcm93IiwiZmxleFNocmluayIsImZsZXhCYXNpcyJdLGZvbnQ6WyJmb250RmFtaWx5IiwiZm9udFNpemUiLCJmb250U3R5bGUiLCJmb250VmFyaWFudCIsImZvbnRXZWlnaHQiLCJsaW5lSGVpZ2h0Il0sbWFyZ2luOlsibWFyZ2luVG9wIiwibWFyZ2luUmlnaHQiLCJtYXJnaW5Cb3R0b20iLCJtYXJnaW5MZWZ0Il0sb3V0bGluZTpbIm91dGxpbmVDb2xvciIsIm91dGxpbmVTdHlsZSIsIm91dGxpbmVXaWR0aCJdLHBhZGRpbmc6WyJwYWRkaW5nVG9wIiwicGFkZGluZ1JpZ2h0IiwicGFkZGluZ0JvdHRvbSIsInBhZGRpbmdMZWZ0Il19LGk9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiwiZGl2Iiksaj17dGhpbjoiMXB4IixtZWRpdW06IjNweCIsdGhpY2s6IjVweCJ9LGs9e2JvcmRlckJvdHRvbVdpZHRoOmosYm9yZGVyTGVmdFdpZHRoOmosYm9yZGVyUmlnaHRXaWR0aDpqLGJvcmRlclRvcFdpZHRoOmosZm9udFNpemU6eyJ4eC1zbWFsbCI6IjYwJSIsIngtc21hbGwiOiI3NSUiLHNtYWxsOiI4OSUiLG1lZGl1bToiMTAwJSIsbGFyZ2U6IjEyMCUiLCJ4LWxhcmdlIjoiMTUwJSIsInh4LWxhcmdlIjoiMjAwJSJ9LGZvbnRXZWlnaHQ6e25vcm1hbDoiNDAwIixib2xkOiI3MDAifSxvdXRsaW5lV2lkdGg6aix0ZXh0U2hhZG93Ontub25lOiIwcHggMHB4IDBweCB0cmFuc3BhcmVudCJ9LGJveFNoYWRvdzp7bm9uZToiMHB4IDBweCAwcHggMHB4IHRyYW5zcGFyZW50In19O2EuY29udmVydFRvQXJyYXlGb3JtPWYsYS5ub3JtYWxpemVLZXlmcmFtZXM9Z30oYSksZnVuY3Rpb24oYSl7dmFyIGI9e307YS5pc0RlcHJlY2F0ZWQ9ZnVuY3Rpb24oYSxjLGQsZSl7dmFyIGY9ZT8iYXJlIjoiaXMiLGc9bmV3IERhdGUsaD1uZXcgRGF0ZShjKTtyZXR1cm4gaC5zZXRNb250aChoLmdldE1vbnRoKCkrMyksIShnPGgmJihhIGluIGJ8fGNvbnNvbGUud2FybigiV2ViIEFuaW1hdGlvbnM6ICIrYSsiICIrZisiIGRlcHJlY2F0ZWQgYW5kIHdpbGwgc3RvcCB3b3JraW5nIG9uICIraC50b0RhdGVTdHJpbmcoKSsiLiAiK2QpLGJbYV09ITAsMSkpfSxhLmRlcHJlY2F0ZWQ9ZnVuY3Rpb24oYixjLGQsZSl7dmFyIGY9ZT8iYXJlIjoiaXMiO2lmKGEuaXNEZXByZWNhdGVkKGIsYyxkLGUpKXRocm93IG5ldyBFcnJvcihiKyIgIitmKyIgbm8gbG9uZ2VyIHN1cHBvcnRlZC4gIitkKX19KGEpLGZ1bmN0aW9uKCl7aWYoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LmFuaW1hdGUpe3ZhciBjPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5hbmltYXRlKFtdLDApLGQ9ITA7aWYoYyYmKGQ9ITEsInBsYXl8Y3VycmVudFRpbWV8cGF1c2V8cmV2ZXJzZXxwbGF5YmFja1JhdGV8Y2FuY2VsfGZpbmlzaHxzdGFydFRpbWV8cGxheVN0YXRlIi5zcGxpdCgifCIpLmZvckVhY2goZnVuY3Rpb24oYSl7dm9pZCAwPT09Y1thXSYmKGQ9ITApfSkpLCFkKXJldHVybn0hZnVuY3Rpb24oYSxiLGMpe2Z1bmN0aW9uIGQoYSl7Zm9yKHZhciBiPXt9LGM9MDtjPGEubGVuZ3RoO2MrKylmb3IodmFyIGQgaW4gYVtjXSlpZigib2Zmc2V0IiE9ZCYmImVhc2luZyIhPWQmJiJjb21wb3NpdGUiIT1kKXt2YXIgZT17b2Zmc2V0OmFbY10ub2Zmc2V0LGVhc2luZzphW2NdLmVhc2luZyx2YWx1ZTphW2NdW2RdfTtiW2RdPWJbZF18fFtdLGJbZF0ucHVzaChlKX1mb3IodmFyIGYgaW4gYil7dmFyIGc9YltmXTtpZigwIT1nWzBdLm9mZnNldHx8MSE9Z1tnLmxlbmd0aC0xXS5vZmZzZXQpdGhyb3d7dHlwZTpET01FeGNlcHRpb24uTk9UX1NVUFBPUlRFRF9FUlIsbmFtZToiTm90U3VwcG9ydGVkRXJyb3IiLG1lc3NhZ2U6IlBhcnRpYWwga2V5ZnJhbWVzIGFyZSBub3Qgc3VwcG9ydGVkIn19cmV0dXJuIGJ9ZnVuY3Rpb24gZShjKXt2YXIgZD1bXTtmb3IodmFyIGUgaW4gYylmb3IodmFyIGY9Y1tlXSxnPTA7ZzxmLmxlbmd0aC0xO2crKyl7dmFyIGg9ZyxpPWcrMSxqPWZbaF0ub2Zmc2V0LGs9ZltpXS5vZmZzZXQsbD1qLG09azswPT1nJiYobD0tMS8wLDA9PWsmJihpPWgpKSxnPT1mLmxlbmd0aC0yJiYobT0xLzAsMT09aiYmKGg9aSkpLGQucHVzaCh7YXBwbHlGcm9tOmwsYXBwbHlUbzptLHN0YXJ0T2Zmc2V0OmZbaF0ub2Zmc2V0LGVuZE9mZnNldDpmW2ldLm9mZnNldCxlYXNpbmdGdW5jdGlvbjphLnBhcnNlRWFzaW5nRnVuY3Rpb24oZltoXS5lYXNpbmcpLHByb3BlcnR5OmUsaW50ZXJwb2xhdGlvbjpiLnByb3BlcnR5SW50ZXJwb2xhdGlvbihlLGZbaF0udmFsdWUsZltpXS52YWx1ZSl9KX1yZXR1cm4gZC5zb3J0KGZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuc3RhcnRPZmZzZXQtYi5zdGFydE9mZnNldH0pLGR9Yi5jb252ZXJ0RWZmZWN0SW5wdXQ9ZnVuY3Rpb24oYyl7dmFyIGY9YS5ub3JtYWxpemVLZXlmcmFtZXMoYyksZz1kKGYpLGg9ZShnKTtyZXR1cm4gZnVuY3Rpb24oYSxjKXtpZihudWxsIT1jKWguZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBjPj1hLmFwcGx5RnJvbSYmYzxhLmFwcGx5VG99KS5mb3JFYWNoKGZ1bmN0aW9uKGQpe3ZhciBlPWMtZC5zdGFydE9mZnNldCxmPWQuZW5kT2Zmc2V0LWQuc3RhcnRPZmZzZXQsZz0wPT1mPzA6ZC5lYXNpbmdGdW5jdGlvbihlL2YpO2IuYXBwbHkoYSxkLnByb3BlcnR5LGQuaW50ZXJwb2xhdGlvbihnKSl9KTtlbHNlIGZvcih2YXIgZCBpbiBnKSJvZmZzZXQiIT1kJiYiZWFzaW5nIiE9ZCYmImNvbXBvc2l0ZSIhPWQmJmIuY2xlYXIoYSxkKX19fShhLGIpLGZ1bmN0aW9uKGEsYixjKXtmdW5jdGlvbiBkKGEpe3JldHVybiBhLnJlcGxhY2UoLy0oLikvZyxmdW5jdGlvbihhLGIpe3JldHVybiBiLnRvVXBwZXJDYXNlKCl9KX1mdW5jdGlvbiBlKGEsYixjKXtoW2NdPWhbY118fFtdLGhbY10ucHVzaChbYSxiXSl9ZnVuY3Rpb24gZihhLGIsYyl7Zm9yKHZhciBmPTA7ZjxjLmxlbmd0aDtmKyspe2UoYSxiLGQoY1tmXSkpfX1mdW5jdGlvbiBnKGMsZSxmKXt2YXIgZz1jOy8tLy50ZXN0KGMpJiYhYS5pc0RlcHJlY2F0ZWQoIkh5cGhlbmF0ZWQgcHJvcGVydHkgbmFtZXMiLCIyMDE2LTAzLTIyIiwiVXNlIGNhbWVsQ2FzZSBpbnN0ZWFkLiIsITApJiYoZz1kKGMpKSwiaW5pdGlhbCIhPWUmJiJpbml0aWFsIiE9Znx8KCJpbml0aWFsIj09ZSYmKGU9aVtnXSksImluaXRpYWwiPT1mJiYoZj1pW2ddKSk7Zm9yKHZhciBqPWU9PWY/W106aFtnXSxrPTA7aiYmazxqLmxlbmd0aDtrKyspe3ZhciBsPWpba11bMF0oZSksbT1qW2tdWzBdKGYpO2lmKHZvaWQgMCE9PWwmJnZvaWQgMCE9PW0pe3ZhciBuPWpba11bMV0obCxtKTtpZihuKXt2YXIgbz1iLkludGVycG9sYXRpb24uYXBwbHkobnVsbCxuKTtyZXR1cm4gZnVuY3Rpb24oYSl7cmV0dXJuIDA9PWE/ZToxPT1hP2Y6byhhKX19fX1yZXR1cm4gYi5JbnRlcnBvbGF0aW9uKCExLCEwLGZ1bmN0aW9uKGEpe3JldHVybiBhP2Y6ZX0pfXZhciBoPXt9O2IuYWRkUHJvcGVydGllc0hhbmRsZXI9Zjt2YXIgaT17YmFja2dyb3VuZENvbG9yOiJ0cmFuc3BhcmVudCIsYmFja2dyb3VuZFBvc2l0aW9uOiIwJSAwJSIsYm9yZGVyQm90dG9tQ29sb3I6ImN1cnJlbnRDb2xvciIsYm9yZGVyQm90dG9tTGVmdFJhZGl1czoiMHB4Iixib3JkZXJCb3R0b21SaWdodFJhZGl1czoiMHB4Iixib3JkZXJCb3R0b21XaWR0aDoiM3B4Iixib3JkZXJMZWZ0Q29sb3I6ImN1cnJlbnRDb2xvciIsYm9yZGVyTGVmdFdpZHRoOiIzcHgiLGJvcmRlclJpZ2h0Q29sb3I6ImN1cnJlbnRDb2xvciIsYm9yZGVyUmlnaHRXaWR0aDoiM3B4Iixib3JkZXJTcGFjaW5nOiIycHgiLGJvcmRlclRvcENvbG9yOiJjdXJyZW50Q29sb3IiLGJvcmRlclRvcExlZnRSYWRpdXM6IjBweCIsYm9yZGVyVG9wUmlnaHRSYWRpdXM6IjBweCIsYm9yZGVyVG9wV2lkdGg6IjNweCIsYm90dG9tOiJhdXRvIixjbGlwOiJyZWN0KDBweCwgMHB4LCAwcHgsIDBweCkiLGNvbG9yOiJibGFjayIsZm9udFNpemU6IjEwMCUiLGZvbnRXZWlnaHQ6IjQwMCIsaGVpZ2h0OiJhdXRvIixsZWZ0OiJhdXRvIixsZXR0ZXJTcGFjaW5nOiJub3JtYWwiLGxpbmVIZWlnaHQ6IjEyMCUiLG1hcmdpbkJvdHRvbToiMHB4IixtYXJnaW5MZWZ0OiIwcHgiLG1hcmdpblJpZ2h0OiIwcHgiLG1hcmdpblRvcDoiMHB4IixtYXhIZWlnaHQ6Im5vbmUiLG1heFdpZHRoOiJub25lIixtaW5IZWlnaHQ6IjBweCIsbWluV2lkdGg6IjBweCIsb3BhY2l0eToiMS4wIixvdXRsaW5lQ29sb3I6ImludmVydCIsb3V0bGluZU9mZnNldDoiMHB4IixvdXRsaW5lV2lkdGg6IjNweCIscGFkZGluZ0JvdHRvbToiMHB4IixwYWRkaW5nTGVmdDoiMHB4IixwYWRkaW5nUmlnaHQ6IjBweCIscGFkZGluZ1RvcDoiMHB4IixyaWdodDoiYXV0byIsc3Ryb2tlRGFzaGFycmF5OiJub25lIixzdHJva2VEYXNob2Zmc2V0OiIwcHgiLHRleHRJbmRlbnQ6IjBweCIsdGV4dFNoYWRvdzoiMHB4IDBweCAwcHggdHJhbnNwYXJlbnQiLHRvcDoiYXV0byIsdHJhbnNmb3JtOiIiLHZlcnRpY2FsQWxpZ246IjBweCIsdmlzaWJpbGl0eToidmlzaWJsZSIsd2lkdGg6ImF1dG8iLHdvcmRTcGFjaW5nOiJub3JtYWwiLHpJbmRleDoiYXV0byJ9O2IucHJvcGVydHlJbnRlcnBvbGF0aW9uPWd9KGEsYiksZnVuY3Rpb24oYSxiLGMpe2Z1bmN0aW9uIGQoYil7dmFyIGM9YS5jYWxjdWxhdGVBY3RpdmVEdXJhdGlvbihiKSxkPWZ1bmN0aW9uKGQpe3JldHVybiBhLmNhbGN1bGF0ZUl0ZXJhdGlvblByb2dyZXNzKGMsZCxiKX07cmV0dXJuIGQuX3RvdGFsRHVyYXRpb249Yi5kZWxheStjK2IuZW5kRGVsYXksZH1iLktleWZyYW1lRWZmZWN0PWZ1bmN0aW9uKGMsZSxmLGcpe3ZhciBoLGk9ZChhLm5vcm1hbGl6ZVRpbWluZ0lucHV0KGYpKSxqPWIuY29udmVydEVmZmVjdElucHV0KGUpLGs9ZnVuY3Rpb24oKXtqKGMsaCl9O3JldHVybiBrLl91cGRhdGU9ZnVuY3Rpb24oYSl7cmV0dXJuIG51bGwhPT0oaD1pKGEpKX0say5fY2xlYXI9ZnVuY3Rpb24oKXtqKGMsbnVsbCl9LGsuX2hhc1NhbWVUYXJnZXQ9ZnVuY3Rpb24oYSl7cmV0dXJuIGM9PT1hfSxrLl90YXJnZXQ9YyxrLl90b3RhbER1cmF0aW9uPWkuX3RvdGFsRHVyYXRpb24say5faWQ9ZyxrfX0oYSxiKSxmdW5jdGlvbihhLGIpe2EuYXBwbHk9ZnVuY3Rpb24oYixjLGQpe2Iuc3R5bGVbYS5wcm9wZXJ0eU5hbWUoYyldPWR9LGEuY2xlYXI9ZnVuY3Rpb24oYixjKXtiLnN0eWxlW2EucHJvcGVydHlOYW1lKGMpXT0iIn19KGIpLGZ1bmN0aW9uKGEpe3dpbmRvdy5FbGVtZW50LnByb3RvdHlwZS5hbmltYXRlPWZ1bmN0aW9uKGIsYyl7dmFyIGQ9IiI7cmV0dXJuIGMmJmMuaWQmJihkPWMuaWQpLGEudGltZWxpbmUuX3BsYXkoYS5LZXlmcmFtZUVmZmVjdCh0aGlzLGIsYyxkKSl9fShiKSxmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSxiLGQpe2lmKCJudW1iZXIiPT10eXBlb2YgYSYmIm51bWJlciI9PXR5cGVvZiBiKXJldHVybiBhKigxLWQpK2IqZDtpZigiYm9vbGVhbiI9PXR5cGVvZiBhJiYiYm9vbGVhbiI9PXR5cGVvZiBiKXJldHVybiBkPC41P2E6YjtpZihhLmxlbmd0aD09Yi5sZW5ndGgpe2Zvcih2YXIgZT1bXSxmPTA7ZjxhLmxlbmd0aDtmKyspZS5wdXNoKGMoYVtmXSxiW2ZdLGQpKTtyZXR1cm4gZX10aHJvdyJNaXNtYXRjaGVkIGludGVycG9sYXRpb24gYXJndW1lbnRzICIrYSsiOiIrYn1hLkludGVycG9sYXRpb249ZnVuY3Rpb24oYSxiLGQpe3JldHVybiBmdW5jdGlvbihlKXtyZXR1cm4gZChjKGEsYixlKSl9fX0oYiksZnVuY3Rpb24oYSxiLGMpe2Euc2VxdWVuY2VOdW1iZXI9MDt2YXIgZD1mdW5jdGlvbihhLGIsYyl7dGhpcy50YXJnZXQ9YSx0aGlzLmN1cnJlbnRUaW1lPWIsdGhpcy50aW1lbGluZVRpbWU9Yyx0aGlzLnR5cGU9ImZpbmlzaCIsdGhpcy5idWJibGVzPSExLHRoaXMuY2FuY2VsYWJsZT0hMSx0aGlzLmN1cnJlbnRUYXJnZXQ9YSx0aGlzLmRlZmF1bHRQcmV2ZW50ZWQ9ITEsdGhpcy5ldmVudFBoYXNlPUV2ZW50LkFUX1RBUkdFVCx0aGlzLnRpbWVTdGFtcD1EYXRlLm5vdygpfTtiLkFuaW1hdGlvbj1mdW5jdGlvbihiKXt0aGlzLmlkPSIiLGImJmIuX2lkJiYodGhpcy5pZD1iLl9pZCksdGhpcy5fc2VxdWVuY2VOdW1iZXI9YS5zZXF1ZW5jZU51bWJlcisrLHRoaXMuX2N1cnJlbnRUaW1lPTAsdGhpcy5fc3RhcnRUaW1lPW51bGwsdGhpcy5fcGF1c2VkPSExLHRoaXMuX3BsYXliYWNrUmF0ZT0xLHRoaXMuX2luVGltZWxpbmU9ITAsdGhpcy5fZmluaXNoZWRGbGFnPSEwLHRoaXMub25maW5pc2g9bnVsbCx0aGlzLl9maW5pc2hIYW5kbGVycz1bXSx0aGlzLl9lZmZlY3Q9Yix0aGlzLl9pbkVmZmVjdD10aGlzLl9lZmZlY3QuX3VwZGF0ZSgwKSx0aGlzLl9pZGxlPSEwLHRoaXMuX2N1cnJlbnRUaW1lUGVuZGluZz0hMX0sYi5BbmltYXRpb24ucHJvdG90eXBlPXtfZW5zdXJlQWxpdmU6ZnVuY3Rpb24oKXt0aGlzLnBsYXliYWNrUmF0ZTwwJiYwPT09dGhpcy5jdXJyZW50VGltZT90aGlzLl9pbkVmZmVjdD10aGlzLl9lZmZlY3QuX3VwZGF0ZSgtMSk6dGhpcy5faW5FZmZlY3Q9dGhpcy5fZWZmZWN0Ll91cGRhdGUodGhpcy5jdXJyZW50VGltZSksdGhpcy5faW5UaW1lbGluZXx8IXRoaXMuX2luRWZmZWN0JiZ0aGlzLl9maW5pc2hlZEZsYWd8fCh0aGlzLl9pblRpbWVsaW5lPSEwLGIudGltZWxpbmUuX2FuaW1hdGlvbnMucHVzaCh0aGlzKSl9LF90aWNrQ3VycmVudFRpbWU6ZnVuY3Rpb24oYSxiKXthIT10aGlzLl9jdXJyZW50VGltZSYmKHRoaXMuX2N1cnJlbnRUaW1lPWEsdGhpcy5faXNGaW5pc2hlZCYmIWImJih0aGlzLl9jdXJyZW50VGltZT10aGlzLl9wbGF5YmFja1JhdGU+MD90aGlzLl90b3RhbER1cmF0aW9uOjApLHRoaXMuX2Vuc3VyZUFsaXZlKCkpfSxnZXQgY3VycmVudFRpbWUoKXtyZXR1cm4gdGhpcy5faWRsZXx8dGhpcy5fY3VycmVudFRpbWVQZW5kaW5nP251bGw6dGhpcy5fY3VycmVudFRpbWV9LHNldCBjdXJyZW50VGltZShhKXthPSthLGlzTmFOKGEpfHwoYi5yZXN0YXJ0KCksdGhpcy5fcGF1c2VkfHxudWxsPT10aGlzLl9zdGFydFRpbWV8fCh0aGlzLl9zdGFydFRpbWU9dGhpcy5fdGltZWxpbmUuY3VycmVudFRpbWUtYS90aGlzLl9wbGF5YmFja1JhdGUpLHRoaXMuX2N1cnJlbnRUaW1lUGVuZGluZz0hMSx0aGlzLl9jdXJyZW50VGltZSE9YSYmKHRoaXMuX2lkbGUmJih0aGlzLl9pZGxlPSExLHRoaXMuX3BhdXNlZD0hMCksdGhpcy5fdGlja0N1cnJlbnRUaW1lKGEsITApLGIuYXBwbHlEaXJ0aWVkQW5pbWF0aW9uKHRoaXMpKSl9LGdldCBzdGFydFRpbWUoKXtyZXR1cm4gdGhpcy5fc3RhcnRUaW1lfSxzZXQgc3RhcnRUaW1lKGEpe2E9K2EsaXNOYU4oYSl8fHRoaXMuX3BhdXNlZHx8dGhpcy5faWRsZXx8KHRoaXMuX3N0YXJ0VGltZT1hLHRoaXMuX3RpY2tDdXJyZW50VGltZSgodGhpcy5fdGltZWxpbmUuY3VycmVudFRpbWUtdGhpcy5fc3RhcnRUaW1lKSp0aGlzLnBsYXliYWNrUmF0ZSksYi5hcHBseURpcnRpZWRBbmltYXRpb24odGhpcykpfSxnZXQgcGxheWJhY2tSYXRlKCl7cmV0dXJuIHRoaXMuX3BsYXliYWNrUmF0ZX0sc2V0IHBsYXliYWNrUmF0ZShhKXtpZihhIT10aGlzLl9wbGF5YmFja1JhdGUpe3ZhciBjPXRoaXMuY3VycmVudFRpbWU7dGhpcy5fcGxheWJhY2tSYXRlPWEsdGhpcy5fc3RhcnRUaW1lPW51bGwsInBhdXNlZCIhPXRoaXMucGxheVN0YXRlJiYiaWRsZSIhPXRoaXMucGxheVN0YXRlJiYodGhpcy5fZmluaXNoZWRGbGFnPSExLHRoaXMuX2lkbGU9ITEsdGhpcy5fZW5zdXJlQWxpdmUoKSxiLmFwcGx5RGlydGllZEFuaW1hdGlvbih0aGlzKSksbnVsbCE9YyYmKHRoaXMuY3VycmVudFRpbWU9Yyl9fSxnZXQgX2lzRmluaXNoZWQoKXtyZXR1cm4hdGhpcy5faWRsZSYmKHRoaXMuX3BsYXliYWNrUmF0ZT4wJiZ0aGlzLl9jdXJyZW50VGltZT49dGhpcy5fdG90YWxEdXJhdGlvbnx8dGhpcy5fcGxheWJhY2tSYXRlPDAmJnRoaXMuX2N1cnJlbnRUaW1lPD0wKX0sZ2V0IF90b3RhbER1cmF0aW9uKCl7cmV0dXJuIHRoaXMuX2VmZmVjdC5fdG90YWxEdXJhdGlvbn0sZ2V0IHBsYXlTdGF0ZSgpe3JldHVybiB0aGlzLl9pZGxlPyJpZGxlIjpudWxsPT10aGlzLl9zdGFydFRpbWUmJiF0aGlzLl9wYXVzZWQmJjAhPXRoaXMucGxheWJhY2tSYXRlfHx0aGlzLl9jdXJyZW50VGltZVBlbmRpbmc/InBlbmRpbmciOnRoaXMuX3BhdXNlZD8icGF1c2VkIjp0aGlzLl9pc0ZpbmlzaGVkPyJmaW5pc2hlZCI6InJ1bm5pbmcifSxfcmV3aW5kOmZ1bmN0aW9uKCl7aWYodGhpcy5fcGxheWJhY2tSYXRlPj0wKXRoaXMuX2N1cnJlbnRUaW1lPTA7ZWxzZXtpZighKHRoaXMuX3RvdGFsRHVyYXRpb248MS8wKSl0aHJvdyBuZXcgRE9NRXhjZXB0aW9uKCJVbmFibGUgdG8gcmV3aW5kIG5lZ2F0aXZlIHBsYXliYWNrIHJhdGUgYW5pbWF0aW9uIHdpdGggaW5maW5pdGUgZHVyYXRpb24iLCJJbnZhbGlkU3RhdGVFcnJvciIpO3RoaXMuX2N1cnJlbnRUaW1lPXRoaXMuX3RvdGFsRHVyYXRpb259fSxwbGF5OmZ1bmN0aW9uKCl7dGhpcy5fcGF1c2VkPSExLCh0aGlzLl9pc0ZpbmlzaGVkfHx0aGlzLl9pZGxlKSYmKHRoaXMuX3Jld2luZCgpLHRoaXMuX3N0YXJ0VGltZT1udWxsKSx0aGlzLl9maW5pc2hlZEZsYWc9ITEsdGhpcy5faWRsZT0hMSx0aGlzLl9lbnN1cmVBbGl2ZSgpLGIuYXBwbHlEaXJ0aWVkQW5pbWF0aW9uKHRoaXMpfSxwYXVzZTpmdW5jdGlvbigpe3RoaXMuX2lzRmluaXNoZWR8fHRoaXMuX3BhdXNlZHx8dGhpcy5faWRsZT90aGlzLl9pZGxlJiYodGhpcy5fcmV3aW5kKCksdGhpcy5faWRsZT0hMSk6dGhpcy5fY3VycmVudFRpbWVQZW5kaW5nPSEwLHRoaXMuX3N0YXJ0VGltZT1udWxsLHRoaXMuX3BhdXNlZD0hMH0sZmluaXNoOmZ1bmN0aW9uKCl7dGhpcy5faWRsZXx8KHRoaXMuY3VycmVudFRpbWU9dGhpcy5fcGxheWJhY2tSYXRlPjA/dGhpcy5fdG90YWxEdXJhdGlvbjowLHRoaXMuX3N0YXJ0VGltZT10aGlzLl90b3RhbER1cmF0aW9uLXRoaXMuY3VycmVudFRpbWUsdGhpcy5fY3VycmVudFRpbWVQZW5kaW5nPSExLGIuYXBwbHlEaXJ0aWVkQW5pbWF0aW9uKHRoaXMpKX0sY2FuY2VsOmZ1bmN0aW9uKCl7dGhpcy5faW5FZmZlY3QmJih0aGlzLl9pbkVmZmVjdD0hMSx0aGlzLl9pZGxlPSEwLHRoaXMuX3BhdXNlZD0hMSx0aGlzLl9maW5pc2hlZEZsYWc9ITAsdGhpcy5fY3VycmVudFRpbWU9MCx0aGlzLl9zdGFydFRpbWU9bnVsbCx0aGlzLl9lZmZlY3QuX3VwZGF0ZShudWxsKSxiLmFwcGx5RGlydGllZEFuaW1hdGlvbih0aGlzKSl9LHJldmVyc2U6ZnVuY3Rpb24oKXt0aGlzLnBsYXliYWNrUmF0ZSo9LTEsdGhpcy5wbGF5KCl9LGFkZEV2ZW50TGlzdGVuZXI6ZnVuY3Rpb24oYSxiKXsiZnVuY3Rpb24iPT10eXBlb2YgYiYmImZpbmlzaCI9PWEmJnRoaXMuX2ZpbmlzaEhhbmRsZXJzLnB1c2goYil9LHJlbW92ZUV2ZW50TGlzdGVuZXI6ZnVuY3Rpb24oYSxiKXtpZigiZmluaXNoIj09YSl7dmFyIGM9dGhpcy5fZmluaXNoSGFuZGxlcnMuaW5kZXhPZihiKTtjPj0wJiZ0aGlzLl9maW5pc2hIYW5kbGVycy5zcGxpY2UoYywxKX19LF9maXJlRXZlbnRzOmZ1bmN0aW9uKGEpe2lmKHRoaXMuX2lzRmluaXNoZWQpe2lmKCF0aGlzLl9maW5pc2hlZEZsYWcpe3ZhciBiPW5ldyBkKHRoaXMsdGhpcy5fY3VycmVudFRpbWUsYSksYz10aGlzLl9maW5pc2hIYW5kbGVycy5jb25jYXQodGhpcy5vbmZpbmlzaD9bdGhpcy5vbmZpbmlzaF06W10pO3NldFRpbWVvdXQoZnVuY3Rpb24oKXtjLmZvckVhY2goZnVuY3Rpb24oYSl7YS5jYWxsKGIudGFyZ2V0LGIpfSl9LDApLHRoaXMuX2ZpbmlzaGVkRmxhZz0hMH19ZWxzZSB0aGlzLl9maW5pc2hlZEZsYWc9ITF9LF90aWNrOmZ1bmN0aW9uKGEsYil7dGhpcy5faWRsZXx8dGhpcy5fcGF1c2VkfHwobnVsbD09dGhpcy5fc3RhcnRUaW1lP2ImJih0aGlzLnN0YXJ0VGltZT1hLXRoaXMuX2N1cnJlbnRUaW1lL3RoaXMucGxheWJhY2tSYXRlKTp0aGlzLl9pc0ZpbmlzaGVkfHx0aGlzLl90aWNrQ3VycmVudFRpbWUoKGEtdGhpcy5fc3RhcnRUaW1lKSp0aGlzLnBsYXliYWNrUmF0ZSkpLGImJih0aGlzLl9jdXJyZW50VGltZVBlbmRpbmc9ITEsdGhpcy5fZmlyZUV2ZW50cyhhKSl9LGdldCBfbmVlZHNUaWNrKCl7cmV0dXJuIHRoaXMucGxheVN0YXRlIGlue3BlbmRpbmc6MSxydW5uaW5nOjF9fHwhdGhpcy5fZmluaXNoZWRGbGFnfSxfdGFyZ2V0QW5pbWF0aW9uczpmdW5jdGlvbigpe3ZhciBhPXRoaXMuX2VmZmVjdC5fdGFyZ2V0O3JldHVybiBhLl9hY3RpdmVBbmltYXRpb25zfHwoYS5fYWN0aXZlQW5pbWF0aW9ucz1bXSksYS5fYWN0aXZlQW5pbWF0aW9uc30sX21hcmtUYXJnZXQ6ZnVuY3Rpb24oKXt2YXIgYT10aGlzLl90YXJnZXRBbmltYXRpb25zKCk7LTE9PT1hLmluZGV4T2YodGhpcykmJmEucHVzaCh0aGlzKX0sX3VubWFya1RhcmdldDpmdW5jdGlvbigpe3ZhciBhPXRoaXMuX3RhcmdldEFuaW1hdGlvbnMoKSxiPWEuaW5kZXhPZih0aGlzKTstMSE9PWImJmEuc3BsaWNlKGIsMSl9fX0oYSxiKSxmdW5jdGlvbihhLGIsYyl7ZnVuY3Rpb24gZChhKXt2YXIgYj1qO2o9W10sYTxxLmN1cnJlbnRUaW1lJiYoYT1xLmN1cnJlbnRUaW1lKSxxLl9hbmltYXRpb25zLnNvcnQoZSkscS5fYW5pbWF0aW9ucz1oKGEsITAscS5fYW5pbWF0aW9ucylbMF0sYi5mb3JFYWNoKGZ1bmN0aW9uKGIpe2JbMV0oYSl9KSxnKCksbD12b2lkIDB9ZnVuY3Rpb24gZShhLGIpe3JldHVybiBhLl9zZXF1ZW5jZU51bWJlci1iLl9zZXF1ZW5jZU51bWJlcn1mdW5jdGlvbiBmKCl7dGhpcy5fYW5pbWF0aW9ucz1bXSx0aGlzLmN1cnJlbnRUaW1lPXdpbmRvdy5wZXJmb3JtYW5jZSYmcGVyZm9ybWFuY2Uubm93P3BlcmZvcm1hbmNlLm5vdygpOjB9ZnVuY3Rpb24gZygpe28uZm9yRWFjaChmdW5jdGlvbihhKXthKCl9KSxvLmxlbmd0aD0wfWZ1bmN0aW9uIGgoYSxjLGQpe3A9ITAsbj0hMSxiLnRpbWVsaW5lLmN1cnJlbnRUaW1lPWEsbT0hMTt2YXIgZT1bXSxmPVtdLGc9W10saD1bXTtyZXR1cm4gZC5mb3JFYWNoKGZ1bmN0aW9uKGIpe2IuX3RpY2soYSxjKSxiLl9pbkVmZmVjdD8oZi5wdXNoKGIuX2VmZmVjdCksYi5fbWFya1RhcmdldCgpKTooZS5wdXNoKGIuX2VmZmVjdCksYi5fdW5tYXJrVGFyZ2V0KCkpLGIuX25lZWRzVGljayYmKG09ITApO3ZhciBkPWIuX2luRWZmZWN0fHxiLl9uZWVkc1RpY2s7Yi5faW5UaW1lbGluZT1kLGQ/Zy5wdXNoKGIpOmgucHVzaChiKX0pLG8ucHVzaC5hcHBseShvLGUpLG8ucHVzaC5hcHBseShvLGYpLG0mJnJlcXVlc3RBbmltYXRpb25GcmFtZShmdW5jdGlvbigpe30pLHA9ITEsW2csaF19dmFyIGk9d2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZSxqPVtdLGs9MDt3aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lPWZ1bmN0aW9uKGEpe3ZhciBiPWsrKztyZXR1cm4gMD09ai5sZW5ndGgmJmkoZCksai5wdXNoKFtiLGFdKSxifSx3aW5kb3cuY2FuY2VsQW5pbWF0aW9uRnJhbWU9ZnVuY3Rpb24oYSl7ai5mb3JFYWNoKGZ1bmN0aW9uKGIpe2JbMF09PWEmJihiWzFdPWZ1bmN0aW9uKCl7fSl9KX0sZi5wcm90b3R5cGU9e19wbGF5OmZ1bmN0aW9uKGMpe2MuX3RpbWluZz1hLm5vcm1hbGl6ZVRpbWluZ0lucHV0KGMudGltaW5nKTt2YXIgZD1uZXcgYi5BbmltYXRpb24oYyk7cmV0dXJuIGQuX2lkbGU9ITEsZC5fdGltZWxpbmU9dGhpcyx0aGlzLl9hbmltYXRpb25zLnB1c2goZCksYi5yZXN0YXJ0KCksYi5hcHBseURpcnRpZWRBbmltYXRpb24oZCksZH19O3ZhciBsPXZvaWQgMCxtPSExLG49ITE7Yi5yZXN0YXJ0PWZ1bmN0aW9uKCl7cmV0dXJuIG18fChtPSEwLHJlcXVlc3RBbmltYXRpb25GcmFtZShmdW5jdGlvbigpe30pLG49ITApLG59LGIuYXBwbHlEaXJ0aWVkQW5pbWF0aW9uPWZ1bmN0aW9uKGEpe2lmKCFwKXthLl9tYXJrVGFyZ2V0KCk7dmFyIGM9YS5fdGFyZ2V0QW5pbWF0aW9ucygpO2Muc29ydChlKSxoKGIudGltZWxpbmUuY3VycmVudFRpbWUsITEsYy5zbGljZSgpKVsxXS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBiPXEuX2FuaW1hdGlvbnMuaW5kZXhPZihhKTstMSE9PWImJnEuX2FuaW1hdGlvbnMuc3BsaWNlKGIsMSl9KSxnKCl9fTt2YXIgbz1bXSxwPSExLHE9bmV3IGY7Yi50aW1lbGluZT1xfShhLGIpLGZ1bmN0aW9uKGEpe2Z1bmN0aW9uIGIoYSxiKXt2YXIgYz1hLmV4ZWMoYik7aWYoYylyZXR1cm4gYz1hLmlnbm9yZUNhc2U/Y1swXS50b0xvd2VyQ2FzZSgpOmNbMF0sW2MsYi5zdWJzdHIoYy5sZW5ndGgpXX1mdW5jdGlvbiBjKGEsYil7Yj1iLnJlcGxhY2UoL15ccyovLCIiKTt2YXIgYz1hKGIpO2lmKGMpcmV0dXJuW2NbMF0sY1sxXS5yZXBsYWNlKC9eXHMqLywiIildfWZ1bmN0aW9uIGQoYSxkLGUpe2E9Yy5iaW5kKG51bGwsYSk7Zm9yKHZhciBmPVtdOzspe3ZhciBnPWEoZSk7aWYoIWcpcmV0dXJuW2YsZV07aWYoZi5wdXNoKGdbMF0pLGU9Z1sxXSwhKGc9YihkLGUpKXx8IiI9PWdbMV0pcmV0dXJuW2YsZV07ZT1nWzFdfX1mdW5jdGlvbiBlKGEsYil7Zm9yKHZhciBjPTAsZD0wO2Q8Yi5sZW5ndGgmJighL1xzfCwvLnRlc3QoYltkXSl8fDAhPWMpO2QrKylpZigiKCI9PWJbZF0pYysrO2Vsc2UgaWYoIikiPT1iW2RdJiYoYy0tLDA9PWMmJmQrKyxjPD0wKSlicmVhazt2YXIgZT1hKGIuc3Vic3RyKDAsZCkpO3JldHVybiB2b2lkIDA9PWU/dm9pZCAwOltlLGIuc3Vic3RyKGQpXX1mdW5jdGlvbiBmKGEsYil7Zm9yKHZhciBjPWEsZD1iO2MmJmQ7KWM+ZD9jJT1kOmQlPWM7cmV0dXJuIGM9YSpiLyhjK2QpfWZ1bmN0aW9uIGcoYSl7cmV0dXJuIGZ1bmN0aW9uKGIpe3ZhciBjPWEoYik7cmV0dXJuIGMmJihjWzBdPXZvaWQgMCksY319ZnVuY3Rpb24gaChhLGIpe3JldHVybiBmdW5jdGlvbihjKXtyZXR1cm4gYShjKXx8W2IsY119fWZ1bmN0aW9uIGkoYixjKXtmb3IodmFyIGQ9W10sZT0wO2U8Yi5sZW5ndGg7ZSsrKXt2YXIgZj1hLmNvbnN1bWVUcmltbWVkKGJbZV0sYyk7aWYoIWZ8fCIiPT1mWzBdKXJldHVybjt2b2lkIDAhPT1mWzBdJiZkLnB1c2goZlswXSksYz1mWzFdfWlmKCIiPT1jKXJldHVybiBkfWZ1bmN0aW9uIGooYSxiLGMsZCxlKXtmb3IodmFyIGc9W10saD1bXSxpPVtdLGo9ZihkLmxlbmd0aCxlLmxlbmd0aCksaz0wO2s8ajtrKyspe3ZhciBsPWIoZFtrJWQubGVuZ3RoXSxlW2slZS5sZW5ndGhdKTtpZighbClyZXR1cm47Zy5wdXNoKGxbMF0pLGgucHVzaChsWzFdKSxpLnB1c2gobFsyXSl9cmV0dXJuW2csaCxmdW5jdGlvbihiKXt2YXIgZD1iLm1hcChmdW5jdGlvbihhLGIpe3JldHVybiBpW2JdKGEpfSkuam9pbihjKTtyZXR1cm4gYT9hKGQpOmR9XX1mdW5jdGlvbiBrKGEsYixjKXtmb3IodmFyIGQ9W10sZT1bXSxmPVtdLGc9MCxoPTA7aDxjLmxlbmd0aDtoKyspaWYoImZ1bmN0aW9uIj09dHlwZW9mIGNbaF0pe3ZhciBpPWNbaF0oYVtnXSxiW2crK10pO2QucHVzaChpWzBdKSxlLnB1c2goaVsxXSksZi5wdXNoKGlbMl0pfWVsc2UhZnVuY3Rpb24oYSl7ZC5wdXNoKCExKSxlLnB1c2goITEpLGYucHVzaChmdW5jdGlvbigpe3JldHVybiBjW2FdfSl9KGgpO3JldHVybltkLGUsZnVuY3Rpb24oYSl7Zm9yKHZhciBiPSIiLGM9MDtjPGEubGVuZ3RoO2MrKyliKz1mW2NdKGFbY10pO3JldHVybiBifV19YS5jb25zdW1lVG9rZW49YixhLmNvbnN1bWVUcmltbWVkPWMsYS5jb25zdW1lUmVwZWF0ZWQ9ZCxhLmNvbnN1bWVQYXJlbnRoZXNpc2VkPWUsYS5pZ25vcmU9ZyxhLm9wdGlvbmFsPWgsYS5jb25zdW1lTGlzdD1pLGEubWVyZ2VOZXN0ZWRSZXBlYXRlZD1qLmJpbmQobnVsbCxudWxsKSxhLm1lcmdlV3JhcHBlZE5lc3RlZFJlcGVhdGVkPWosYS5tZXJnZUxpc3Q9a30oYiksZnVuY3Rpb24oYSl7ZnVuY3Rpb24gYihiKXtmdW5jdGlvbiBjKGIpe3ZhciBjPWEuY29uc3VtZVRva2VuKC9eaW5zZXQvaSxiKTtyZXR1cm4gYz8oZC5pbnNldD0hMCxjKTooYz1hLmNvbnN1bWVMZW5ndGhPclBlcmNlbnQoYikpPyhkLmxlbmd0aHMucHVzaChjWzBdKSxjKTooYz1hLmNvbnN1bWVDb2xvcihiKSxjPyhkLmNvbG9yPWNbMF0sYyk6dm9pZCAwKX12YXIgZD17aW5zZXQ6ITEsbGVuZ3RoczpbXSxjb2xvcjpudWxsfSxlPWEuY29uc3VtZVJlcGVhdGVkKGMsL14vLGIpO2lmKGUmJmVbMF0ubGVuZ3RoKXJldHVybltkLGVbMV1dfWZ1bmN0aW9uIGMoYyl7dmFyIGQ9YS5jb25zdW1lUmVwZWF0ZWQoYiwvXiwvLGMpO2lmKGQmJiIiPT1kWzFdKXJldHVybiBkWzBdfWZ1bmN0aW9uIGQoYixjKXtmb3IoO2IubGVuZ3Rocy5sZW5ndGg8TWF0aC5tYXgoYi5sZW5ndGhzLmxlbmd0aCxjLmxlbmd0aHMubGVuZ3RoKTspYi5sZW5ndGhzLnB1c2goe3B4OjB9KTtmb3IoO2MubGVuZ3Rocy5sZW5ndGg8TWF0aC5tYXgoYi5sZW5ndGhzLmxlbmd0aCxjLmxlbmd0aHMubGVuZ3RoKTspYy5sZW5ndGhzLnB1c2goe3B4OjB9KTtpZihiLmluc2V0PT1jLmluc2V0JiYhIWIuY29sb3I9PSEhYy5jb2xvcil7Zm9yKHZhciBkLGU9W10sZj1bW10sMF0sZz1bW10sMF0saD0wO2g8Yi5sZW5ndGhzLmxlbmd0aDtoKyspe3ZhciBpPWEubWVyZ2VEaW1lbnNpb25zKGIubGVuZ3Roc1toXSxjLmxlbmd0aHNbaF0sMj09aCk7ZlswXS5wdXNoKGlbMF0pLGdbMF0ucHVzaChpWzFdKSxlLnB1c2goaVsyXSl9aWYoYi5jb2xvciYmYy5jb2xvcil7dmFyIGo9YS5tZXJnZUNvbG9ycyhiLmNvbG9yLGMuY29sb3IpO2ZbMV09alswXSxnWzFdPWpbMV0sZD1qWzJdfXJldHVybltmLGcsZnVuY3Rpb24oYSl7Zm9yKHZhciBjPWIuaW5zZXQ/Imluc2V0ICI6IiAiLGY9MDtmPGUubGVuZ3RoO2YrKyljKz1lW2ZdKGFbMF1bZl0pKyIgIjtyZXR1cm4gZCYmKGMrPWQoYVsxXSkpLGN9XX19ZnVuY3Rpb24gZShiLGMsZCxlKXtmdW5jdGlvbiBmKGEpe3JldHVybntpbnNldDphLGNvbG9yOlswLDAsMCwwXSxsZW5ndGhzOlt7cHg6MH0se3B4OjB9LHtweDowfSx7cHg6MH1dfX1mb3IodmFyIGc9W10saD1bXSxpPTA7aTxkLmxlbmd0aHx8aTxlLmxlbmd0aDtpKyspe3ZhciBqPWRbaV18fGYoZVtpXS5pbnNldCksaz1lW2ldfHxmKGRbaV0uaW5zZXQpO2cucHVzaChqKSxoLnB1c2goayl9cmV0dXJuIGEubWVyZ2VOZXN0ZWRSZXBlYXRlZChiLGMsZyxoKX12YXIgZj1lLmJpbmQobnVsbCxkLCIsICIpO2EuYWRkUHJvcGVydGllc0hhbmRsZXIoYyxmLFsiYm94LXNoYWRvdyIsInRleHQtc2hhZG93Il0pfShiKSxmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7cmV0dXJuIGEudG9GaXhlZCgzKS5yZXBsYWNlKC8wKyQvLCIiKS5yZXBsYWNlKC9cLiQvLCIiKX1mdW5jdGlvbiBkKGEsYixjKXtyZXR1cm4gTWF0aC5taW4oYixNYXRoLm1heChhLGMpKX1mdW5jdGlvbiBlKGEpe2lmKC9eXHMqWy0rXT8oXGQqXC4pP1xkK1xzKiQvLnRlc3QoYSkpcmV0dXJuIE51bWJlcihhKX1mdW5jdGlvbiBmKGEsYil7cmV0dXJuW2EsYixjXX1mdW5jdGlvbiBnKGEsYil7aWYoMCE9YSlyZXR1cm4gaSgwLDEvMCkoYSxiKX1mdW5jdGlvbiBoKGEsYil7cmV0dXJuW2EsYixmdW5jdGlvbihhKXtyZXR1cm4gTWF0aC5yb3VuZChkKDEsMS8wLGEpKX1dfWZ1bmN0aW9uIGkoYSxiKXtyZXR1cm4gZnVuY3Rpb24oZSxmKXtyZXR1cm5bZSxmLGZ1bmN0aW9uKGUpe3JldHVybiBjKGQoYSxiLGUpKX1dfX1mdW5jdGlvbiBqKGEpe3ZhciBiPWEudHJpbSgpLnNwbGl0KC9ccypbXHMsXVxzKi8pO2lmKDAhPT1iLmxlbmd0aCl7Zm9yKHZhciBjPVtdLGQ9MDtkPGIubGVuZ3RoO2QrKyl7dmFyIGY9ZShiW2RdKTtpZih2b2lkIDA9PT1mKXJldHVybjtjLnB1c2goZil9cmV0dXJuIGN9fWZ1bmN0aW9uIGsoYSxiKXtpZihhLmxlbmd0aD09Yi5sZW5ndGgpcmV0dXJuW2EsYixmdW5jdGlvbihhKXtyZXR1cm4gYS5tYXAoYykuam9pbigiICIpfV19ZnVuY3Rpb24gbChhLGIpe3JldHVyblthLGIsTWF0aC5yb3VuZF19YS5jbGFtcD1kLGEuYWRkUHJvcGVydGllc0hhbmRsZXIoaixrLFsic3Ryb2tlLWRhc2hhcnJheSJdKSxhLmFkZFByb3BlcnRpZXNIYW5kbGVyKGUsaSgwLDEvMCksWyJib3JkZXItaW1hZ2Utd2lkdGgiLCJsaW5lLWhlaWdodCJdKSxhLmFkZFByb3BlcnRpZXNIYW5kbGVyKGUsaSgwLDEpLFsib3BhY2l0eSIsInNoYXBlLWltYWdlLXRocmVzaG9sZCJdKSxhLmFkZFByb3BlcnRpZXNIYW5kbGVyKGUsZyxbImZsZXgtZ3JvdyIsImZsZXgtc2hyaW5rIl0pLGEuYWRkUHJvcGVydGllc0hhbmRsZXIoZSxoLFsib3JwaGFucyIsIndpZG93cyJdKSxhLmFkZFByb3BlcnRpZXNIYW5kbGVyKGUsbCxbInotaW5kZXgiXSksYS5wYXJzZU51bWJlcj1lLGEucGFyc2VOdW1iZXJMaXN0PWosYS5tZXJnZU51bWJlcnM9ZixhLm51bWJlclRvU3RyaW5nPWN9KGIpLGZ1bmN0aW9uKGEsYil7ZnVuY3Rpb24gYyhhLGIpe2lmKCJ2aXNpYmxlIj09YXx8InZpc2libGUiPT1iKXJldHVyblswLDEsZnVuY3Rpb24oYyl7cmV0dXJuIGM8PTA/YTpjPj0xP2I6InZpc2libGUifV19YS5hZGRQcm9wZXJ0aWVzSGFuZGxlcihTdHJpbmcsYyxbInZpc2liaWxpdHkiXSl9KGIpLGZ1bmN0aW9uKGEsYil7ZnVuY3Rpb24gYyhhKXthPWEudHJpbSgpLGYuZmlsbFN0eWxlPSIjMDAwIixmLmZpbGxTdHlsZT1hO3ZhciBiPWYuZmlsbFN0eWxlO2lmKGYuZmlsbFN0eWxlPSIjZmZmIixmLmZpbGxTdHlsZT1hLGI9PWYuZmlsbFN0eWxlKXtmLmZpbGxSZWN0KDAsMCwxLDEpO3ZhciBjPWYuZ2V0SW1hZ2VEYXRhKDAsMCwxLDEpLmRhdGE7Zi5jbGVhclJlY3QoMCwwLDEsMSk7dmFyIGQ9Y1szXS8yNTU7cmV0dXJuW2NbMF0qZCxjWzFdKmQsY1syXSpkLGRdfX1mdW5jdGlvbiBkKGIsYyl7cmV0dXJuW2IsYyxmdW5jdGlvbihiKXtmdW5jdGlvbiBjKGEpe3JldHVybiBNYXRoLm1heCgwLE1hdGgubWluKDI1NSxhKSl9aWYoYlszXSlmb3IodmFyIGQ9MDtkPDM7ZCsrKWJbZF09TWF0aC5yb3VuZChjKGJbZF0vYlszXSkpO3JldHVybiBiWzNdPWEubnVtYmVyVG9TdHJpbmcoYS5jbGFtcCgwLDEsYlszXSkpLCJyZ2JhKCIrYi5qb2luKCIsIikrIikifV19dmFyIGU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiwiY2FudmFzIik7ZS53aWR0aD1lLmhlaWdodD0xO3ZhciBmPWUuZ2V0Q29udGV4dCgiMmQiKTthLmFkZFByb3BlcnRpZXNIYW5kbGVyKGMsZCxbImJhY2tncm91bmQtY29sb3IiLCJib3JkZXItYm90dG9tLWNvbG9yIiwiYm9yZGVyLWxlZnQtY29sb3IiLCJib3JkZXItcmlnaHQtY29sb3IiLCJib3JkZXItdG9wLWNvbG9yIiwiY29sb3IiLCJmaWxsIiwiZmxvb2QtY29sb3IiLCJsaWdodGluZy1jb2xvciIsIm91dGxpbmUtY29sb3IiLCJzdG9wLWNvbG9yIiwic3Ryb2tlIiwidGV4dC1kZWNvcmF0aW9uLWNvbG9yIl0pLGEuY29uc3VtZUNvbG9yPWEuY29uc3VtZVBhcmVudGhlc2lzZWQuYmluZChudWxsLGMpLGEubWVyZ2VDb2xvcnM9ZH0oYiksZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEpe2Z1bmN0aW9uIGIoKXt2YXIgYj1oLmV4ZWMoYSk7Zz1iP2JbMF06dm9pZCAwfWZ1bmN0aW9uIGMoKXt2YXIgYT1OdW1iZXIoZyk7cmV0dXJuIGIoKSxhfWZ1bmN0aW9uIGQoKXtpZigiKCIhPT1nKXJldHVybiBjKCk7YigpO3ZhciBhPWYoKTtyZXR1cm4iKSIhPT1nP05hTjooYigpLGEpfWZ1bmN0aW9uIGUoKXtmb3IodmFyIGE9ZCgpOyIqIj09PWd8fCIvIj09PWc7KXt2YXIgYz1nO2IoKTt2YXIgZT1kKCk7IioiPT09Yz9hKj1lOmEvPWV9cmV0dXJuIGF9ZnVuY3Rpb24gZigpe2Zvcih2YXIgYT1lKCk7IisiPT09Z3x8Ii0iPT09Zzspe3ZhciBjPWc7YigpO3ZhciBkPWUoKTsiKyI9PT1jP2ErPWQ6YS09ZH1yZXR1cm4gYX12YXIgZyxoPS8oW1wrXC1cd1wuXSt8W1woXClcKlwvXSkvZztyZXR1cm4gYigpLGYoKX1mdW5jdGlvbiBkKGEsYil7aWYoIjAiPT0oYj1iLnRyaW0oKS50b0xvd2VyQ2FzZSgpKSYmInB4Ii5zZWFyY2goYSk+PTApcmV0dXJue3B4OjB9O2lmKC9eW14oXSokfF5jYWxjLy50ZXN0KGIpKXtiPWIucmVwbGFjZSgvY2FsY1woL2csIigiKTt2YXIgZD17fTtiPWIucmVwbGFjZShhLGZ1bmN0aW9uKGEpe3JldHVybiBkW2FdPW51bGwsIlUiK2F9KTtmb3IodmFyIGU9IlUoIithLnNvdXJjZSsiKSIsZj1iLnJlcGxhY2UoL1stK10/KFxkKlwuKT9cZCsoW0VlXVstK10/XGQrKT8vZywiTiIpLnJlcGxhY2UobmV3IFJlZ0V4cCgiTiIrZSwiZyIpLCJEIikucmVwbGFjZSgvXHNbKy1dXHMvZywiTyIpLnJlcGxhY2UoL1xzL2csIiIpLGc9Wy9OXCooRCkvZywvKE58RClbKlwvXU4vZywvKE58RClPXDEvZywvXCgoTnxEKVwpL2ddLGg9MDtoPGcubGVuZ3RoOylnW2hdLnRlc3QoZik/KGY9Zi5yZXBsYWNlKGdbaF0sIiQxIiksaD0wKTpoKys7aWYoIkQiPT1mKXtmb3IodmFyIGkgaW4gZCl7dmFyIGo9YyhiLnJlcGxhY2UobmV3IFJlZ0V4cCgiVSIraSwiZyIpLCIiKS5yZXBsYWNlKG5ldyBSZWdFeHAoZSwiZyIpLCIqMCIpKTtpZighaXNGaW5pdGUoaikpcmV0dXJuO2RbaV09an1yZXR1cm4gZH19fWZ1bmN0aW9uIGUoYSxiKXtyZXR1cm4gZihhLGIsITApfWZ1bmN0aW9uIGYoYixjLGQpe3ZhciBlLGY9W107Zm9yKGUgaW4gYilmLnB1c2goZSk7Zm9yKGUgaW4gYylmLmluZGV4T2YoZSk8MCYmZi5wdXNoKGUpO3JldHVybiBiPWYubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBiW2FdfHwwfSksYz1mLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gY1thXXx8MH0pLFtiLGMsZnVuY3Rpb24oYil7dmFyIGM9Yi5tYXAoZnVuY3Rpb24oYyxlKXtyZXR1cm4gMT09Yi5sZW5ndGgmJmQmJihjPU1hdGgubWF4KGMsMCkpLGEubnVtYmVyVG9TdHJpbmcoYykrZltlXX0pLmpvaW4oIiArICIpO3JldHVybiBiLmxlbmd0aD4xPyJjYWxjKCIrYysiKSI6Y31dfXZhciBnPSJweHxlbXxleHxjaHxyZW18dnd8dmh8dm1pbnx2bWF4fGNtfG1tfGlufHB0fHBjIixoPWQuYmluZChudWxsLG5ldyBSZWdFeHAoZywiZyIpKSxpPWQuYmluZChudWxsLG5ldyBSZWdFeHAoZysifCUiLCJnIikpLGo9ZC5iaW5kKG51bGwsL2RlZ3xyYWR8Z3JhZHx0dXJuL2cpO2EucGFyc2VMZW5ndGg9aCxhLnBhcnNlTGVuZ3RoT3JQZXJjZW50PWksYS5jb25zdW1lTGVuZ3RoT3JQZXJjZW50PWEuY29uc3VtZVBhcmVudGhlc2lzZWQuYmluZChudWxsLGkpLGEucGFyc2VBbmdsZT1qLGEubWVyZ2VEaW1lbnNpb25zPWY7dmFyIGs9YS5jb25zdW1lUGFyZW50aGVzaXNlZC5iaW5kKG51bGwsaCksbD1hLmNvbnN1bWVSZXBlYXRlZC5iaW5kKHZvaWQgMCxrLC9eLyksbT1hLmNvbnN1bWVSZXBlYXRlZC5iaW5kKHZvaWQgMCxsLC9eLC8pO2EuY29uc3VtZVNpemVQYWlyTGlzdD1tO3ZhciBuPWZ1bmN0aW9uKGEpe3ZhciBiPW0oYSk7aWYoYiYmIiI9PWJbMV0pcmV0dXJuIGJbMF19LG89YS5tZXJnZU5lc3RlZFJlcGVhdGVkLmJpbmQodm9pZCAwLGUsIiAiKSxwPWEubWVyZ2VOZXN0ZWRSZXBlYXRlZC5iaW5kKHZvaWQgMCxvLCIsIik7YS5tZXJnZU5vbk5lZ2F0aXZlU2l6ZVBhaXI9byxhLmFkZFByb3BlcnRpZXNIYW5kbGVyKG4scCxbImJhY2tncm91bmQtc2l6ZSJdKSxhLmFkZFByb3BlcnRpZXNIYW5kbGVyKGksZSxbImJvcmRlci1ib3R0b20td2lkdGgiLCJib3JkZXItaW1hZ2Utd2lkdGgiLCJib3JkZXItbGVmdC13aWR0aCIsImJvcmRlci1yaWdodC13aWR0aCIsImJvcmRlci10b3Atd2lkdGgiLCJmbGV4LWJhc2lzIiwiZm9udC1zaXplIiwiaGVpZ2h0IiwibGluZS1oZWlnaHQiLCJtYXgtaGVpZ2h0IiwibWF4LXdpZHRoIiwib3V0bGluZS13aWR0aCIsIndpZHRoIl0pLGEuYWRkUHJvcGVydGllc0hhbmRsZXIoaSxmLFsiYm9yZGVyLWJvdHRvbS1sZWZ0LXJhZGl1cyIsImJvcmRlci1ib3R0b20tcmlnaHQtcmFkaXVzIiwiYm9yZGVyLXRvcC1sZWZ0LXJhZGl1cyIsImJvcmRlci10b3AtcmlnaHQtcmFkaXVzIiwiYm90dG9tIiwibGVmdCIsImxldHRlci1zcGFjaW5nIiwibWFyZ2luLWJvdHRvbSIsIm1hcmdpbi1sZWZ0IiwibWFyZ2luLXJpZ2h0IiwibWFyZ2luLXRvcCIsIm1pbi1oZWlnaHQiLCJtaW4td2lkdGgiLCJvdXRsaW5lLW9mZnNldCIsInBhZGRpbmctYm90dG9tIiwicGFkZGluZy1sZWZ0IiwicGFkZGluZy1yaWdodCIsInBhZGRpbmctdG9wIiwicGVyc3BlY3RpdmUiLCJyaWdodCIsInNoYXBlLW1hcmdpbiIsInN0cm9rZS1kYXNob2Zmc2V0IiwidGV4dC1pbmRlbnQiLCJ0b3AiLCJ2ZXJ0aWNhbC1hbGlnbiIsIndvcmQtc3BhY2luZyJdKX0oYiksZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGIpe3JldHVybiBhLmNvbnN1bWVMZW5ndGhPclBlcmNlbnQoYil8fGEuY29uc3VtZVRva2VuKC9eYXV0by8sYil9ZnVuY3Rpb24gZChiKXt2YXIgZD1hLmNvbnN1bWVMaXN0KFthLmlnbm9yZShhLmNvbnN1bWVUb2tlbi5iaW5kKG51bGwsL15yZWN0LykpLGEuaWdub3JlKGEuY29uc3VtZVRva2VuLmJpbmQobnVsbCwvXlwoLykpLGEuY29uc3VtZVJlcGVhdGVkLmJpbmQobnVsbCxjLC9eLC8pLGEuaWdub3JlKGEuY29uc3VtZVRva2VuLmJpbmQobnVsbCwvXlwpLykpXSxiKTtpZihkJiY0PT1kWzBdLmxlbmd0aClyZXR1cm4gZFswXX1mdW5jdGlvbiBlKGIsYyl7cmV0dXJuImF1dG8iPT1ifHwiYXV0byI9PWM/WyEwLCExLGZ1bmN0aW9uKGQpe3ZhciBlPWQ/YjpjO2lmKCJhdXRvIj09ZSlyZXR1cm4iYXV0byI7dmFyIGY9YS5tZXJnZURpbWVuc2lvbnMoZSxlKTtyZXR1cm4gZlsyXShmWzBdKX1dOmEubWVyZ2VEaW1lbnNpb25zKGIsYyl9ZnVuY3Rpb24gZihhKXtyZXR1cm4icmVjdCgiK2ErIikifXZhciBnPWEubWVyZ2VXcmFwcGVkTmVzdGVkUmVwZWF0ZWQuYmluZChudWxsLGYsZSwiLCAiKTthLnBhcnNlQm94PWQsYS5tZXJnZUJveGVzPWcsYS5hZGRQcm9wZXJ0aWVzSGFuZGxlcihkLGcsWyJjbGlwIl0pfShiKSxmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7cmV0dXJuIGZ1bmN0aW9uKGIpe3ZhciBjPTA7cmV0dXJuIGEubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhPT09az9iW2MrK106YX0pfX1mdW5jdGlvbiBkKGEpe3JldHVybiBhfWZ1bmN0aW9uIGUoYil7aWYoIm5vbmUiPT0oYj1iLnRvTG93ZXJDYXNlKCkudHJpbSgpKSlyZXR1cm5bXTtmb3IodmFyIGMsZD0vXHMqKFx3KylcKChbXildKilcKS9nLGU9W10sZj0wO2M9ZC5leGVjKGIpOyl7aWYoYy5pbmRleCE9ZilyZXR1cm47Zj1jLmluZGV4K2NbMF0ubGVuZ3RoO3ZhciBnPWNbMV0saD1uW2ddO2lmKCFoKXJldHVybjt2YXIgaT1jWzJdLnNwbGl0KCIsIiksaj1oWzBdO2lmKGoubGVuZ3RoPGkubGVuZ3RoKXJldHVybjtmb3IodmFyIGs9W10sbz0wO288ai5sZW5ndGg7bysrKXt2YXIgcCxxPWlbb10scj1qW29dO2lmKHZvaWQgMD09PShwPXE/e0E6ZnVuY3Rpb24oYil7cmV0dXJuIjAiPT1iLnRyaW0oKT9tOmEucGFyc2VBbmdsZShiKX0sTjphLnBhcnNlTnVtYmVyLFQ6YS5wYXJzZUxlbmd0aE9yUGVyY2VudCxMOmEucGFyc2VMZW5ndGh9W3IudG9VcHBlckNhc2UoKV0ocSk6e2E6bSxuOmtbMF0sdDpsfVtyXSkpcmV0dXJuO2sucHVzaChwKX1pZihlLnB1c2goe3Q6ZyxkOmt9KSxkLmxhc3RJbmRleD09Yi5sZW5ndGgpcmV0dXJuIGV9fWZ1bmN0aW9uIGYoYSl7cmV0dXJuIGEudG9GaXhlZCg2KS5yZXBsYWNlKCIuMDAwMDAwIiwiIil9ZnVuY3Rpb24gZyhiLGMpe2lmKGIuZGVjb21wb3NpdGlvblBhaXIhPT1jKXtiLmRlY29tcG9zaXRpb25QYWlyPWM7dmFyIGQ9YS5tYWtlTWF0cml4RGVjb21wb3NpdGlvbihiKX1pZihjLmRlY29tcG9zaXRpb25QYWlyIT09Yil7Yy5kZWNvbXBvc2l0aW9uUGFpcj1iO3ZhciBlPWEubWFrZU1hdHJpeERlY29tcG9zaXRpb24oYyl9cmV0dXJuIG51bGw9PWRbMF18fG51bGw9PWVbMF0/W1shMV0sWyEwXSxmdW5jdGlvbihhKXtyZXR1cm4gYT9jWzBdLmQ6YlswXS5kfV06KGRbMF0ucHVzaCgwKSxlWzBdLnB1c2goMSksW2QsZSxmdW5jdGlvbihiKXt2YXIgYz1hLnF1YXQoZFswXVszXSxlWzBdWzNdLGJbNV0pO3JldHVybiBhLmNvbXBvc2VNYXRyaXgoYlswXSxiWzFdLGJbMl0sYyxiWzRdKS5tYXAoZikuam9pbigiLCIpfV0pfWZ1bmN0aW9uIGgoYSl7cmV0dXJuIGEucmVwbGFjZSgvW3h5XS8sIiIpfWZ1bmN0aW9uIGkoYSl7cmV0dXJuIGEucmVwbGFjZSgvKHh8eXx6fDNkKT8kLywiM2QiKX1mdW5jdGlvbiBqKGIsYyl7dmFyIGQ9YS5tYWtlTWF0cml4RGVjb21wb3NpdGlvbiYmITAsZT0hMTtpZighYi5sZW5ndGh8fCFjLmxlbmd0aCl7Yi5sZW5ndGh8fChlPSEwLGI9YyxjPVtdKTtmb3IodmFyIGY9MDtmPGIubGVuZ3RoO2YrKyl7dmFyIGo9YltmXS50LGs9YltmXS5kLGw9InNjYWxlIj09ai5zdWJzdHIoMCw1KT8xOjA7Yy5wdXNoKHt0OmosZDprLm1hcChmdW5jdGlvbihhKXtpZigibnVtYmVyIj09dHlwZW9mIGEpcmV0dXJuIGw7dmFyIGI9e307Zm9yKHZhciBjIGluIGEpYltjXT1sO3JldHVybiBifSl9KX19dmFyIG09ZnVuY3Rpb24oYSxiKXtyZXR1cm4icGVyc3BlY3RpdmUiPT1hJiYicGVyc3BlY3RpdmUiPT1ifHwoIm1hdHJpeCI9PWF8fCJtYXRyaXgzZCI9PWEpJiYoIm1hdHJpeCI9PWJ8fCJtYXRyaXgzZCI9PWIpfSxvPVtdLHA9W10scT1bXTtpZihiLmxlbmd0aCE9Yy5sZW5ndGgpe2lmKCFkKXJldHVybjt2YXIgcj1nKGIsYyk7bz1bclswXV0scD1bclsxXV0scT1bWyJtYXRyaXgiLFtyWzJdXV1dfWVsc2UgZm9yKHZhciBmPTA7ZjxiLmxlbmd0aDtmKyspe3ZhciBqLHM9YltmXS50LHQ9Y1tmXS50LHU9YltmXS5kLHY9Y1tmXS5kLHc9bltzXSx4PW5bdF07aWYobShzLHQpKXtpZighZClyZXR1cm47dmFyIHI9ZyhbYltmXV0sW2NbZl1dKTtvLnB1c2goclswXSkscC5wdXNoKHJbMV0pLHEucHVzaChbIm1hdHJpeCIsW3JbMl1dXSl9ZWxzZXtpZihzPT10KWo9cztlbHNlIGlmKHdbMl0mJnhbMl0mJmgocyk9PWgodCkpaj1oKHMpLHU9d1syXSh1KSx2PXhbMl0odik7ZWxzZXtpZighd1sxXXx8IXhbMV18fGkocykhPWkodCkpe2lmKCFkKXJldHVybjt2YXIgcj1nKGIsYyk7bz1bclswXV0scD1bclsxXV0scT1bWyJtYXRyaXgiLFtyWzJdXV1dO2JyZWFrfWo9aShzKSx1PXdbMV0odSksdj14WzFdKHYpfWZvcih2YXIgeT1bXSx6PVtdLEE9W10sQj0wO0I8dS5sZW5ndGg7QisrKXt2YXIgQz0ibnVtYmVyIj09dHlwZW9mIHVbQl0/YS5tZXJnZU51bWJlcnM6YS5tZXJnZURpbWVuc2lvbnMscj1DKHVbQl0sdltCXSk7eVtCXT1yWzBdLHpbQl09clsxXSxBLnB1c2goclsyXSl9by5wdXNoKHkpLHAucHVzaCh6KSxxLnB1c2goW2osQV0pfX1pZihlKXt2YXIgRD1vO289cCxwPUR9cmV0dXJuW28scCxmdW5jdGlvbihhKXtyZXR1cm4gYS5tYXAoZnVuY3Rpb24oYSxiKXt2YXIgYz1hLm1hcChmdW5jdGlvbihhLGMpe3JldHVybiBxW2JdWzFdW2NdKGEpfSkuam9pbigiLCIpO3JldHVybiJtYXRyaXgiPT1xW2JdWzBdJiYxNj09Yy5zcGxpdCgiLCIpLmxlbmd0aCYmKHFbYl1bMF09Im1hdHJpeDNkIikscVtiXVswXSsiKCIrYysiKSJ9KS5qb2luKCIgIil9XX12YXIgaz1udWxsLGw9e3B4OjB9LG09e2RlZzowfSxuPXttYXRyaXg6WyJOTk5OTk4iLFtrLGssMCwwLGssaywwLDAsMCwwLDEsMCxrLGssMCwxXSxkXSxtYXRyaXgzZDpbIk5OTk5OTk5OTk5OTk5OTk4iLGRdLHJvdGF0ZTpbIkEiXSxyb3RhdGV4OlsiQSJdLHJvdGF0ZXk6WyJBIl0scm90YXRlejpbIkEiXSxyb3RhdGUzZDpbIk5OTkEiXSxwZXJzcGVjdGl2ZTpbIkwiXSxzY2FsZTpbIk5uIixjKFtrLGssMV0pLGRdLHNjYWxleDpbIk4iLGMoW2ssMSwxXSksYyhbaywxXSldLHNjYWxleTpbIk4iLGMoWzEsaywxXSksYyhbMSxrXSldLHNjYWxlejpbIk4iLGMoWzEsMSxrXSldLHNjYWxlM2Q6WyJOTk4iLGRdLHNrZXc6WyJBYSIsbnVsbCxkXSxza2V3eDpbIkEiLG51bGwsYyhbayxtXSldLHNrZXd5OlsiQSIsbnVsbCxjKFttLGtdKV0sdHJhbnNsYXRlOlsiVHQiLGMoW2ssayxsXSksZF0sdHJhbnNsYXRleDpbIlQiLGMoW2ssbCxsXSksYyhbayxsXSldLHRyYW5zbGF0ZXk6WyJUIixjKFtsLGssbF0pLGMoW2wsa10pXSx0cmFuc2xhdGV6OlsiTCIsYyhbbCxsLGtdKV0sdHJhbnNsYXRlM2Q6WyJUVEwiLGRdfTthLmFkZFByb3BlcnRpZXNIYW5kbGVyKGUsaixbInRyYW5zZm9ybSJdKSxhLnRyYW5zZm9ybVRvU3ZnTWF0cml4PWZ1bmN0aW9uKGIpe3ZhciBjPWEudHJhbnNmb3JtTGlzdFRvTWF0cml4KGUoYikpO3JldHVybiJtYXRyaXgoIitmKGNbMF0pKyIgIitmKGNbMV0pKyIgIitmKGNbNF0pKyIgIitmKGNbNV0pKyIgIitmKGNbMTJdKSsiICIrZihjWzEzXSkrIikifX0oYiksZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYil7Yi5jb25jYXQoW2FdKS5mb3JFYWNoKGZ1bmN0aW9uKGIpe2IgaW4gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnN0eWxlJiYoZFthXT1iKSxlW2JdPWF9KX12YXIgZD17fSxlPXt9O2MoInRyYW5zZm9ybSIsWyJ3ZWJraXRUcmFuc2Zvcm0iLCJtc1RyYW5zZm9ybSJdKSxjKCJ0cmFuc2Zvcm1PcmlnaW4iLFsid2Via2l0VHJhbnNmb3JtT3JpZ2luIl0pLGMoInBlcnNwZWN0aXZlIixbIndlYmtpdFBlcnNwZWN0aXZlIl0pLGMoInBlcnNwZWN0aXZlT3JpZ2luIixbIndlYmtpdFBlcnNwZWN0aXZlT3JpZ2luIl0pLGEucHJvcGVydHlOYW1lPWZ1bmN0aW9uKGEpe3JldHVybiBkW2FdfHxhfSxhLnVucHJlZml4ZWRQcm9wZXJ0eU5hbWU9ZnVuY3Rpb24oYSl7cmV0dXJuIGVbYV18fGF9fShiKX0oKSxmdW5jdGlvbigpe2lmKHZvaWQgMD09PWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpLmFuaW1hdGUoW10pLm9uY2FuY2VsKXt2YXIgYTtpZih3aW5kb3cucGVyZm9ybWFuY2UmJnBlcmZvcm1hbmNlLm5vdyl2YXIgYT1mdW5jdGlvbigpe3JldHVybiBwZXJmb3JtYW5jZS5ub3coKX07ZWxzZSB2YXIgYT1mdW5jdGlvbigpe3JldHVybiBEYXRlLm5vdygpfTt2YXIgYj1mdW5jdGlvbihhLGIsYyl7dGhpcy50YXJnZXQ9YSx0aGlzLmN1cnJlbnRUaW1lPWIsdGhpcy50aW1lbGluZVRpbWU9Yyx0aGlzLnR5cGU9ImNhbmNlbCIsdGhpcy5idWJibGVzPSExLHRoaXMuY2FuY2VsYWJsZT0hMSx0aGlzLmN1cnJlbnRUYXJnZXQ9YSx0aGlzLmRlZmF1bHRQcmV2ZW50ZWQ9ITEsdGhpcy5ldmVudFBoYXNlPUV2ZW50LkFUX1RBUkdFVCx0aGlzLnRpbWVTdGFtcD1EYXRlLm5vdygpfSxjPXdpbmRvdy5FbGVtZW50LnByb3RvdHlwZS5hbmltYXRlO3dpbmRvdy5FbGVtZW50LnByb3RvdHlwZS5hbmltYXRlPWZ1bmN0aW9uKGQsZSl7dmFyIGY9Yy5jYWxsKHRoaXMsZCxlKTtmLl9jYW5jZWxIYW5kbGVycz1bXSxmLm9uY2FuY2VsPW51bGw7dmFyIGc9Zi5jYW5jZWw7Zi5jYW5jZWw9ZnVuY3Rpb24oKXtnLmNhbGwodGhpcyk7dmFyIGM9bmV3IGIodGhpcyxudWxsLGEoKSksZD10aGlzLl9jYW5jZWxIYW5kbGVycy5jb25jYXQodGhpcy5vbmNhbmNlbD9bdGhpcy5vbmNhbmNlbF06W10pO3NldFRpbWVvdXQoZnVuY3Rpb24oKXtkLmZvckVhY2goZnVuY3Rpb24oYSl7YS5jYWxsKGMudGFyZ2V0LGMpfSl9LDApfTt2YXIgaD1mLmFkZEV2ZW50TGlzdGVuZXI7Zi5hZGRFdmVudExpc3RlbmVyPWZ1bmN0aW9uKGEsYil7ImZ1bmN0aW9uIj09dHlwZW9mIGImJiJjYW5jZWwiPT1hP3RoaXMuX2NhbmNlbEhhbmRsZXJzLnB1c2goYik6aC5jYWxsKHRoaXMsYSxiKX07dmFyIGk9Zi5yZW1vdmVFdmVudExpc3RlbmVyO3JldHVybiBmLnJlbW92ZUV2ZW50TGlzdGVuZXI9ZnVuY3Rpb24oYSxiKXtpZigiY2FuY2VsIj09YSl7dmFyIGM9dGhpcy5fY2FuY2VsSGFuZGxlcnMuaW5kZXhPZihiKTtjPj0wJiZ0aGlzLl9jYW5jZWxIYW5kbGVycy5zcGxpY2UoYywxKX1lbHNlIGkuY2FsbCh0aGlzLGEsYil9LGZ9fX0oKSxmdW5jdGlvbihhKXt2YXIgYj1kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsYz1udWxsLGQ9ITE7dHJ5e3ZhciBlPWdldENvbXB1dGVkU3R5bGUoYikuZ2V0UHJvcGVydHlWYWx1ZSgib3BhY2l0eSIpLGY9IjAiPT1lPyIxIjoiMCI7Yz1iLmFuaW1hdGUoe29wYWNpdHk6W2YsZl19LHtkdXJhdGlvbjoxfSksYy5jdXJyZW50VGltZT0wLGQ9Z2V0Q29tcHV0ZWRTdHlsZShiKS5nZXRQcm9wZXJ0eVZhbHVlKCJvcGFjaXR5Iik9PWZ9Y2F0Y2goYSl7fWZpbmFsbHl7YyYmYy5jYW5jZWwoKX1pZighZCl7dmFyIGc9d2luZG93LkVsZW1lbnQucHJvdG90eXBlLmFuaW1hdGU7d2luZG93LkVsZW1lbnQucHJvdG90eXBlLmFuaW1hdGU9ZnVuY3Rpb24oYixjKXtyZXR1cm4gd2luZG93LlN5bWJvbCYmU3ltYm9sLml0ZXJhdG9yJiZBcnJheS5wcm90b3R5cGUuZnJvbSYmYltTeW1ib2wuaXRlcmF0b3JdJiYoYj1BcnJheS5mcm9tKGIpKSxBcnJheS5pc0FycmF5KGIpfHxudWxsPT09Ynx8KGI9YS5jb252ZXJ0VG9BcnJheUZvcm0oYikpLGcuY2FsbCh0aGlzLGIsYyl9fX0oYSksZnVuY3Rpb24oYSxiLGMpe2Z1bmN0aW9uIGQoYSl7dmFyIGM9Yi50aW1lbGluZTtjLmN1cnJlbnRUaW1lPWEsYy5fZGlzY2FyZEFuaW1hdGlvbnMoKSwwPT1jLl9hbmltYXRpb25zLmxlbmd0aD9mPSExOnJlcXVlc3RBbmltYXRpb25GcmFtZShkKX12YXIgZT13aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lO3dpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWU9ZnVuY3Rpb24oYSl7cmV0dXJuIGUoZnVuY3Rpb24oYyl7Yi50aW1lbGluZS5fdXBkYXRlQW5pbWF0aW9uc1Byb21pc2VzKCksYShjKSxiLnRpbWVsaW5lLl91cGRhdGVBbmltYXRpb25zUHJvbWlzZXMoKX0pfSxiLkFuaW1hdGlvblRpbWVsaW5lPWZ1bmN0aW9uKCl7dGhpcy5fYW5pbWF0aW9ucz1bXSx0aGlzLmN1cnJlbnRUaW1lPXZvaWQgMH0sYi5BbmltYXRpb25UaW1lbGluZS5wcm90b3R5cGU9e2dldEFuaW1hdGlvbnM6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZGlzY2FyZEFuaW1hdGlvbnMoKSx0aGlzLl9hbmltYXRpb25zLnNsaWNlKCl9LF91cGRhdGVBbmltYXRpb25zUHJvbWlzZXM6ZnVuY3Rpb24oKXtiLmFuaW1hdGlvbnNXaXRoUHJvbWlzZXM9Yi5hbmltYXRpb25zV2l0aFByb21pc2VzLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYS5fdXBkYXRlUHJvbWlzZXMoKX0pfSxfZGlzY2FyZEFuaW1hdGlvbnM6ZnVuY3Rpb24oKXt0aGlzLl91cGRhdGVBbmltYXRpb25zUHJvbWlzZXMoKSx0aGlzLl9hbmltYXRpb25zPXRoaXMuX2FuaW1hdGlvbnMuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiJmaW5pc2hlZCIhPWEucGxheVN0YXRlJiYiaWRsZSIhPWEucGxheVN0YXRlfSl9LF9wbGF5OmZ1bmN0aW9uKGEpe3ZhciBjPW5ldyBiLkFuaW1hdGlvbihhLHRoaXMpO3JldHVybiB0aGlzLl9hbmltYXRpb25zLnB1c2goYyksYi5yZXN0YXJ0V2ViQW5pbWF0aW9uc05leHRUaWNrKCksYy5fdXBkYXRlUHJvbWlzZXMoKSxjLl9hbmltYXRpb24ucGxheSgpLGMuX3VwZGF0ZVByb21pc2VzKCksY30scGxheTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmYS5yZW1vdmUoKSx0aGlzLl9wbGF5KGEpfX07dmFyIGY9ITE7Yi5yZXN0YXJ0V2ViQW5pbWF0aW9uc05leHRUaWNrPWZ1bmN0aW9uKCl7Znx8KGY9ITAscmVxdWVzdEFuaW1hdGlvbkZyYW1lKGQpKX07dmFyIGc9bmV3IGIuQW5pbWF0aW9uVGltZWxpbmU7Yi50aW1lbGluZT1nO3RyeXtPYmplY3QuZGVmaW5lUHJvcGVydHkod2luZG93LmRvY3VtZW50LCJ0aW1lbGluZSIse2NvbmZpZ3VyYWJsZTohMCxnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gZ319KX1jYXRjaChhKXt9dHJ5e3dpbmRvdy5kb2N1bWVudC50aW1lbGluZT1nfWNhdGNoKGEpe319KDAsYyksZnVuY3Rpb24oYSxiLGMpe2IuYW5pbWF0aW9uc1dpdGhQcm9taXNlcz1bXSxiLkFuaW1hdGlvbj1mdW5jdGlvbihiLGMpe2lmKHRoaXMuaWQ9IiIsYiYmYi5faWQmJih0aGlzLmlkPWIuX2lkKSx0aGlzLmVmZmVjdD1iLGImJihiLl9hbmltYXRpb249dGhpcyksIWMpdGhyb3cgbmV3IEVycm9yKCJBbmltYXRpb24gd2l0aCBudWxsIHRpbWVsaW5lIGlzIG5vdCBzdXBwb3J0ZWQiKTt0aGlzLl90aW1lbGluZT1jLHRoaXMuX3NlcXVlbmNlTnVtYmVyPWEuc2VxdWVuY2VOdW1iZXIrKyx0aGlzLl9ob2xkVGltZT0wLHRoaXMuX3BhdXNlZD0hMSx0aGlzLl9pc0dyb3VwPSExLHRoaXMuX2FuaW1hdGlvbj1udWxsLHRoaXMuX2NoaWxkQW5pbWF0aW9ucz1bXSx0aGlzLl9jYWxsYmFjaz1udWxsLHRoaXMuX29sZFBsYXlTdGF0ZT0iaWRsZSIsdGhpcy5fcmVidWlsZFVuZGVybHlpbmdBbmltYXRpb24oKSx0aGlzLl9hbmltYXRpb24uY2FuY2VsKCksdGhpcy5fdXBkYXRlUHJvbWlzZXMoKX0sYi5BbmltYXRpb24ucHJvdG90eXBlPXtfdXBkYXRlUHJvbWlzZXM6ZnVuY3Rpb24oKXt2YXIgYT10aGlzLl9vbGRQbGF5U3RhdGUsYj10aGlzLnBsYXlTdGF0ZTtyZXR1cm4gdGhpcy5fcmVhZHlQcm9taXNlJiZiIT09YSYmKCJpZGxlIj09Yj8odGhpcy5fcmVqZWN0UmVhZHlQcm9taXNlKCksdGhpcy5fcmVhZHlQcm9taXNlPXZvaWQgMCk6InBlbmRpbmciPT1hP3RoaXMuX3Jlc29sdmVSZWFkeVByb21pc2UoKToicGVuZGluZyI9PWImJih0aGlzLl9yZWFkeVByb21pc2U9dm9pZCAwKSksdGhpcy5fZmluaXNoZWRQcm9taXNlJiZiIT09YSYmKCJpZGxlIj09Yj8odGhpcy5fcmVqZWN0RmluaXNoZWRQcm9taXNlKCksdGhpcy5fZmluaXNoZWRQcm9taXNlPXZvaWQgMCk6ImZpbmlzaGVkIj09Yj90aGlzLl9yZXNvbHZlRmluaXNoZWRQcm9taXNlKCk6ImZpbmlzaGVkIj09YSYmKHRoaXMuX2ZpbmlzaGVkUHJvbWlzZT12b2lkIDApKSx0aGlzLl9vbGRQbGF5U3RhdGU9dGhpcy5wbGF5U3RhdGUsdGhpcy5fcmVhZHlQcm9taXNlfHx0aGlzLl9maW5pc2hlZFByb21pc2V9LF9yZWJ1aWxkVW5kZXJseWluZ0FuaW1hdGlvbjpmdW5jdGlvbigpe3RoaXMuX3VwZGF0ZVByb21pc2VzKCk7dmFyIGEsYyxkLGUsZj0hIXRoaXMuX2FuaW1hdGlvbjtmJiYoYT10aGlzLnBsYXliYWNrUmF0ZSxjPXRoaXMuX3BhdXNlZCxkPXRoaXMuc3RhcnRUaW1lLGU9dGhpcy5jdXJyZW50VGltZSx0aGlzLl9hbmltYXRpb24uY2FuY2VsKCksdGhpcy5fYW5pbWF0aW9uLl93cmFwcGVyPW51bGwsdGhpcy5fYW5pbWF0aW9uPW51bGwpLCghdGhpcy5lZmZlY3R8fHRoaXMuZWZmZWN0IGluc3RhbmNlb2Ygd2luZG93LktleWZyYW1lRWZmZWN0KSYmKHRoaXMuX2FuaW1hdGlvbj1iLm5ld1VuZGVybHlpbmdBbmltYXRpb25Gb3JLZXlmcmFtZUVmZmVjdCh0aGlzLmVmZmVjdCksYi5iaW5kQW5pbWF0aW9uRm9yS2V5ZnJhbWVFZmZlY3QodGhpcykpLCh0aGlzLmVmZmVjdCBpbnN0YW5jZW9mIHdpbmRvdy5TZXF1ZW5jZUVmZmVjdHx8dGhpcy5lZmZlY3QgaW5zdGFuY2VvZiB3aW5kb3cuR3JvdXBFZmZlY3QpJiYodGhpcy5fYW5pbWF0aW9uPWIubmV3VW5kZXJseWluZ0FuaW1hdGlvbkZvckdyb3VwKHRoaXMuZWZmZWN0KSxiLmJpbmRBbmltYXRpb25Gb3JHcm91cCh0aGlzKSksdGhpcy5lZmZlY3QmJnRoaXMuZWZmZWN0Ll9vbnNhbXBsZSYmYi5iaW5kQW5pbWF0aW9uRm9yQ3VzdG9tRWZmZWN0KHRoaXMpLGYmJigxIT1hJiYodGhpcy5wbGF5YmFja1JhdGU9YSksbnVsbCE9PWQ/dGhpcy5zdGFydFRpbWU9ZDpudWxsIT09ZT90aGlzLmN1cnJlbnRUaW1lPWU6bnVsbCE9PXRoaXMuX2hvbGRUaW1lJiYodGhpcy5jdXJyZW50VGltZT10aGlzLl9ob2xkVGltZSksYyYmdGhpcy5wYXVzZSgpKSx0aGlzLl91cGRhdGVQcm9taXNlcygpfSxfdXBkYXRlQ2hpbGRyZW46ZnVuY3Rpb24oKXtpZih0aGlzLmVmZmVjdCYmImlkbGUiIT10aGlzLnBsYXlTdGF0ZSl7dmFyIGE9dGhpcy5lZmZlY3QuX3RpbWluZy5kZWxheTt0aGlzLl9jaGlsZEFuaW1hdGlvbnMuZm9yRWFjaChmdW5jdGlvbihjKXt0aGlzLl9hcnJhbmdlQ2hpbGRyZW4oYyxhKSx0aGlzLmVmZmVjdCBpbnN0YW5jZW9mIHdpbmRvdy5TZXF1ZW5jZUVmZmVjdCYmKGErPWIuZ3JvdXBDaGlsZER1cmF0aW9uKGMuZWZmZWN0KSl9LmJpbmQodGhpcykpfX0sX3NldEV4dGVybmFsQW5pbWF0aW9uOmZ1bmN0aW9uKGEpe2lmKHRoaXMuZWZmZWN0JiZ0aGlzLl9pc0dyb3VwKWZvcih2YXIgYj0wO2I8dGhpcy5lZmZlY3QuY2hpbGRyZW4ubGVuZ3RoO2IrKyl0aGlzLmVmZmVjdC5jaGlsZHJlbltiXS5fYW5pbWF0aW9uPWEsdGhpcy5fY2hpbGRBbmltYXRpb25zW2JdLl9zZXRFeHRlcm5hbEFuaW1hdGlvbihhKX0sX2NvbnN0cnVjdENoaWxkQW5pbWF0aW9uczpmdW5jdGlvbigpe2lmKHRoaXMuZWZmZWN0JiZ0aGlzLl9pc0dyb3VwKXt2YXIgYT10aGlzLmVmZmVjdC5fdGltaW5nLmRlbGF5O3RoaXMuX3JlbW92ZUNoaWxkQW5pbWF0aW9ucygpLHRoaXMuZWZmZWN0LmNoaWxkcmVuLmZvckVhY2goZnVuY3Rpb24oYyl7dmFyIGQ9Yi50aW1lbGluZS5fcGxheShjKTt0aGlzLl9jaGlsZEFuaW1hdGlvbnMucHVzaChkKSxkLnBsYXliYWNrUmF0ZT10aGlzLnBsYXliYWNrUmF0ZSx0aGlzLl9wYXVzZWQmJmQucGF1c2UoKSxjLl9hbmltYXRpb249dGhpcy5lZmZlY3QuX2FuaW1hdGlvbix0aGlzLl9hcnJhbmdlQ2hpbGRyZW4oZCxhKSx0aGlzLmVmZmVjdCBpbnN0YW5jZW9mIHdpbmRvdy5TZXF1ZW5jZUVmZmVjdCYmKGErPWIuZ3JvdXBDaGlsZER1cmF0aW9uKGMpKX0uYmluZCh0aGlzKSl9fSxfYXJyYW5nZUNoaWxkcmVuOmZ1bmN0aW9uKGEsYil7bnVsbD09PXRoaXMuc3RhcnRUaW1lP2EuY3VycmVudFRpbWU9dGhpcy5jdXJyZW50VGltZS1iL3RoaXMucGxheWJhY2tSYXRlOmEuc3RhcnRUaW1lIT09dGhpcy5zdGFydFRpbWUrYi90aGlzLnBsYXliYWNrUmF0ZSYmKGEuc3RhcnRUaW1lPXRoaXMuc3RhcnRUaW1lK2IvdGhpcy5wbGF5YmFja1JhdGUpfSxnZXQgdGltZWxpbmUoKXtyZXR1cm4gdGhpcy5fdGltZWxpbmV9LGdldCBwbGF5U3RhdGUoKXtyZXR1cm4gdGhpcy5fYW5pbWF0aW9uP3RoaXMuX2FuaW1hdGlvbi5wbGF5U3RhdGU6ImlkbGUifSxnZXQgZmluaXNoZWQoKXtyZXR1cm4gd2luZG93LlByb21pc2U/KHRoaXMuX2ZpbmlzaGVkUHJvbWlzZXx8KC0xPT1iLmFuaW1hdGlvbnNXaXRoUHJvbWlzZXMuaW5kZXhPZih0aGlzKSYmYi5hbmltYXRpb25zV2l0aFByb21pc2VzLnB1c2godGhpcyksdGhpcy5fZmluaXNoZWRQcm9taXNlPW5ldyBQcm9taXNlKGZ1bmN0aW9uKGEsYil7dGhpcy5fcmVzb2x2ZUZpbmlzaGVkUHJvbWlzZT1mdW5jdGlvbigpe2EodGhpcyl9LHRoaXMuX3JlamVjdEZpbmlzaGVkUHJvbWlzZT1mdW5jdGlvbigpe2Ioe3R5cGU6RE9NRXhjZXB0aW9uLkFCT1JUX0VSUixuYW1lOiJBYm9ydEVycm9yIn0pfX0uYmluZCh0aGlzKSksImZpbmlzaGVkIj09dGhpcy5wbGF5U3RhdGUmJnRoaXMuX3Jlc29sdmVGaW5pc2hlZFByb21pc2UoKSksdGhpcy5fZmluaXNoZWRQcm9taXNlKTooY29uc29sZS53YXJuKCJBbmltYXRpb24gUHJvbWlzZXMgcmVxdWlyZSBKYXZhU2NyaXB0IFByb21pc2UgY29uc3RydWN0b3IiKSxudWxsKX0sZ2V0IHJlYWR5KCl7cmV0dXJuIHdpbmRvdy5Qcm9taXNlPyh0aGlzLl9yZWFkeVByb21pc2V8fCgtMT09Yi5hbmltYXRpb25zV2l0aFByb21pc2VzLmluZGV4T2YodGhpcykmJmIuYW5pbWF0aW9uc1dpdGhQcm9taXNlcy5wdXNoKHRoaXMpLHRoaXMuX3JlYWR5UHJvbWlzZT1uZXcgUHJvbWlzZShmdW5jdGlvbihhLGIpe3RoaXMuX3Jlc29sdmVSZWFkeVByb21pc2U9ZnVuY3Rpb24oKXthKHRoaXMpfSx0aGlzLl9yZWplY3RSZWFkeVByb21pc2U9ZnVuY3Rpb24oKXtiKHt0eXBlOkRPTUV4Y2VwdGlvbi5BQk9SVF9FUlIsbmFtZToiQWJvcnRFcnJvciJ9KX19LmJpbmQodGhpcykpLCJwZW5kaW5nIiE9PXRoaXMucGxheVN0YXRlJiZ0aGlzLl9yZXNvbHZlUmVhZHlQcm9taXNlKCkpLHRoaXMuX3JlYWR5UHJvbWlzZSk6KGNvbnNvbGUud2FybigiQW5pbWF0aW9uIFByb21pc2VzIHJlcXVpcmUgSmF2YVNjcmlwdCBQcm9taXNlIGNvbnN0cnVjdG9yIiksbnVsbCl9LGdldCBvbmZpbmlzaCgpe3JldHVybiB0aGlzLl9hbmltYXRpb24ub25maW5pc2h9LHNldCBvbmZpbmlzaChhKXt0aGlzLl9hbmltYXRpb24ub25maW5pc2g9ImZ1bmN0aW9uIj09dHlwZW9mIGE/ZnVuY3Rpb24oYil7Yi50YXJnZXQ9dGhpcyxhLmNhbGwodGhpcyxiKX0uYmluZCh0aGlzKTphfSxnZXQgb25jYW5jZWwoKXtyZXR1cm4gdGhpcy5fYW5pbWF0aW9uLm9uY2FuY2VsfSxzZXQgb25jYW5jZWwoYSl7dGhpcy5fYW5pbWF0aW9uLm9uY2FuY2VsPSJmdW5jdGlvbiI9PXR5cGVvZiBhP2Z1bmN0aW9uKGIpe2IudGFyZ2V0PXRoaXMsYS5jYWxsKHRoaXMsYil9LmJpbmQodGhpcyk6YX0sZ2V0IGN1cnJlbnRUaW1lKCl7dGhpcy5fdXBkYXRlUHJvbWlzZXMoKTt2YXIgYT10aGlzLl9hbmltYXRpb24uY3VycmVudFRpbWU7cmV0dXJuIHRoaXMuX3VwZGF0ZVByb21pc2VzKCksYX0sc2V0IGN1cnJlbnRUaW1lKGEpe3RoaXMuX3VwZGF0ZVByb21pc2VzKCksdGhpcy5fYW5pbWF0aW9uLmN1cnJlbnRUaW1lPWlzRmluaXRlKGEpP2E6TWF0aC5zaWduKGEpKk51bWJlci5NQVhfVkFMVUUsdGhpcy5fcmVnaXN0ZXIoKSx0aGlzLl9mb3JFYWNoQ2hpbGQoZnVuY3Rpb24oYixjKXtiLmN1cnJlbnRUaW1lPWEtY30pLHRoaXMuX3VwZGF0ZVByb21pc2VzKCl9LGdldCBzdGFydFRpbWUoKXtyZXR1cm4gdGhpcy5fYW5pbWF0aW9uLnN0YXJ0VGltZX0sc2V0IHN0YXJ0VGltZShhKXt0aGlzLl91cGRhdGVQcm9taXNlcygpLHRoaXMuX2FuaW1hdGlvbi5zdGFydFRpbWU9aXNGaW5pdGUoYSk/YTpNYXRoLnNpZ24oYSkqTnVtYmVyLk1BWF9WQUxVRSx0aGlzLl9yZWdpc3RlcigpLHRoaXMuX2ZvckVhY2hDaGlsZChmdW5jdGlvbihiLGMpe2Iuc3RhcnRUaW1lPWErY30pLHRoaXMuX3VwZGF0ZVByb21pc2VzKCl9LGdldCBwbGF5YmFja1JhdGUoKXtyZXR1cm4gdGhpcy5fYW5pbWF0aW9uLnBsYXliYWNrUmF0ZX0sc2V0IHBsYXliYWNrUmF0ZShhKXt0aGlzLl91cGRhdGVQcm9taXNlcygpO3ZhciBiPXRoaXMuY3VycmVudFRpbWU7dGhpcy5fYW5pbWF0aW9uLnBsYXliYWNrUmF0ZT1hLHRoaXMuX2ZvckVhY2hDaGlsZChmdW5jdGlvbihiKXtiLnBsYXliYWNrUmF0ZT1hfSksbnVsbCE9PWImJih0aGlzLmN1cnJlbnRUaW1lPWIpLHRoaXMuX3VwZGF0ZVByb21pc2VzKCl9LHBsYXk6ZnVuY3Rpb24oKXt0aGlzLl91cGRhdGVQcm9taXNlcygpLHRoaXMuX3BhdXNlZD0hMSx0aGlzLl9hbmltYXRpb24ucGxheSgpLC0xPT10aGlzLl90aW1lbGluZS5fYW5pbWF0aW9ucy5pbmRleE9mKHRoaXMpJiZ0aGlzLl90aW1lbGluZS5fYW5pbWF0aW9ucy5wdXNoKHRoaXMpLHRoaXMuX3JlZ2lzdGVyKCksYi5hd2FpdFN0YXJ0VGltZSh0aGlzKSx0aGlzLl9mb3JFYWNoQ2hpbGQoZnVuY3Rpb24oYSl7dmFyIGI9YS5jdXJyZW50VGltZTthLnBsYXkoKSxhLmN1cnJlbnRUaW1lPWJ9KSx0aGlzLl91cGRhdGVQcm9taXNlcygpfSxwYXVzZTpmdW5jdGlvbigpe3RoaXMuX3VwZGF0ZVByb21pc2VzKCksdGhpcy5jdXJyZW50VGltZSYmKHRoaXMuX2hvbGRUaW1lPXRoaXMuY3VycmVudFRpbWUpLHRoaXMuX2FuaW1hdGlvbi5wYXVzZSgpLHRoaXMuX3JlZ2lzdGVyKCksdGhpcy5fZm9yRWFjaENoaWxkKGZ1bmN0aW9uKGEpe2EucGF1c2UoKX0pLHRoaXMuX3BhdXNlZD0hMCx0aGlzLl91cGRhdGVQcm9taXNlcygpfSxmaW5pc2g6ZnVuY3Rpb24oKXt0aGlzLl91cGRhdGVQcm9taXNlcygpLHRoaXMuX2FuaW1hdGlvbi5maW5pc2goKSx0aGlzLl9yZWdpc3RlcigpLHRoaXMuX3VwZGF0ZVByb21pc2VzKCl9LGNhbmNlbDpmdW5jdGlvbigpe3RoaXMuX3VwZGF0ZVByb21pc2VzKCksdGhpcy5fYW5pbWF0aW9uLmNhbmNlbCgpLHRoaXMuX3JlZ2lzdGVyKCksdGhpcy5fcmVtb3ZlQ2hpbGRBbmltYXRpb25zKCksdGhpcy5fdXBkYXRlUHJvbWlzZXMoKX0scmV2ZXJzZTpmdW5jdGlvbigpe3RoaXMuX3VwZGF0ZVByb21pc2VzKCk7dmFyIGE9dGhpcy5jdXJyZW50VGltZTt0aGlzLl9hbmltYXRpb24ucmV2ZXJzZSgpLHRoaXMuX2ZvckVhY2hDaGlsZChmdW5jdGlvbihhKXthLnJldmVyc2UoKX0pLG51bGwhPT1hJiYodGhpcy5jdXJyZW50VGltZT1hKSx0aGlzLl91cGRhdGVQcm9taXNlcygpfSxhZGRFdmVudExpc3RlbmVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9YjsiZnVuY3Rpb24iPT10eXBlb2YgYiYmKGM9ZnVuY3Rpb24oYSl7YS50YXJnZXQ9dGhpcyxiLmNhbGwodGhpcyxhKX0uYmluZCh0aGlzKSxiLl93cmFwcGVyPWMpLHRoaXMuX2FuaW1hdGlvbi5hZGRFdmVudExpc3RlbmVyKGEsYyl9LHJlbW92ZUV2ZW50TGlzdGVuZXI6ZnVuY3Rpb24oYSxiKXt0aGlzLl9hbmltYXRpb24ucmVtb3ZlRXZlbnRMaXN0ZW5lcihhLGImJmIuX3dyYXBwZXJ8fGIpfSxfcmVtb3ZlQ2hpbGRBbmltYXRpb25zOmZ1bmN0aW9uKCl7Zm9yKDt0aGlzLl9jaGlsZEFuaW1hdGlvbnMubGVuZ3RoOyl0aGlzLl9jaGlsZEFuaW1hdGlvbnMucG9wKCkuY2FuY2VsKCl9LF9mb3JFYWNoQ2hpbGQ6ZnVuY3Rpb24oYil7dmFyIGM9MDtpZih0aGlzLmVmZmVjdC5jaGlsZHJlbiYmdGhpcy5fY2hpbGRBbmltYXRpb25zLmxlbmd0aDx0aGlzLmVmZmVjdC5jaGlsZHJlbi5sZW5ndGgmJnRoaXMuX2NvbnN0cnVjdENoaWxkQW5pbWF0aW9ucygpLHRoaXMuX2NoaWxkQW5pbWF0aW9ucy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2IuY2FsbCh0aGlzLGEsYyksdGhpcy5lZmZlY3QgaW5zdGFuY2VvZiB3aW5kb3cuU2VxdWVuY2VFZmZlY3QmJihjKz1hLmVmZmVjdC5hY3RpdmVEdXJhdGlvbil9LmJpbmQodGhpcykpLCJwZW5kaW5nIiE9dGhpcy5wbGF5U3RhdGUpe3ZhciBkPXRoaXMuZWZmZWN0Ll90aW1pbmcsZT10aGlzLmN1cnJlbnRUaW1lO251bGwhPT1lJiYoZT1hLmNhbGN1bGF0ZUl0ZXJhdGlvblByb2dyZXNzKGEuY2FsY3VsYXRlQWN0aXZlRHVyYXRpb24oZCksZSxkKSksKG51bGw9PWV8fGlzTmFOKGUpKSYmdGhpcy5fcmVtb3ZlQ2hpbGRBbmltYXRpb25zKCl9fX0sd2luZG93LkFuaW1hdGlvbj1iLkFuaW1hdGlvbn0oYSxjKSxmdW5jdGlvbihhLGIsYyl7ZnVuY3Rpb24gZChiKXt0aGlzLl9mcmFtZXM9YS5ub3JtYWxpemVLZXlmcmFtZXMoYil9ZnVuY3Rpb24gZSgpe2Zvcih2YXIgYT0hMTtpLmxlbmd0aDspaS5zaGlmdCgpLl91cGRhdGVDaGlsZHJlbigpLGE9ITA7cmV0dXJuIGF9dmFyIGY9ZnVuY3Rpb24oYSl7aWYoYS5fYW5pbWF0aW9uPXZvaWQgMCxhIGluc3RhbmNlb2Ygd2luZG93LlNlcXVlbmNlRWZmZWN0fHxhIGluc3RhbmNlb2Ygd2luZG93Lkdyb3VwRWZmZWN0KWZvcih2YXIgYj0wO2I8YS5jaGlsZHJlbi5sZW5ndGg7YisrKWYoYS5jaGlsZHJlbltiXSl9O2IucmVtb3ZlTXVsdGk9ZnVuY3Rpb24oYSl7Zm9yKHZhciBiPVtdLGM9MDtjPGEubGVuZ3RoO2MrKyl7dmFyIGQ9YVtjXTtkLl9wYXJlbnQ/KC0xPT1iLmluZGV4T2YoZC5fcGFyZW50KSYmYi5wdXNoKGQuX3BhcmVudCksZC5fcGFyZW50LmNoaWxkcmVuLnNwbGljZShkLl9wYXJlbnQuY2hpbGRyZW4uaW5kZXhPZihkKSwxKSxkLl9wYXJlbnQ9bnVsbCxmKGQpKTpkLl9hbmltYXRpb24mJmQuX2FuaW1hdGlvbi5lZmZlY3Q9PWQmJihkLl9hbmltYXRpb24uY2FuY2VsKCksZC5fYW5pbWF0aW9uLmVmZmVjdD1uZXcgS2V5ZnJhbWVFZmZlY3QobnVsbCxbXSksZC5fYW5pbWF0aW9uLl9jYWxsYmFjayYmKGQuX2FuaW1hdGlvbi5fY2FsbGJhY2suX2FuaW1hdGlvbj1udWxsKSxkLl9hbmltYXRpb24uX3JlYnVpbGRVbmRlcmx5aW5nQW5pbWF0aW9uKCksZihkKSl9Zm9yKGM9MDtjPGIubGVuZ3RoO2MrKyliW2NdLl9yZWJ1aWxkKCl9LGIuS2V5ZnJhbWVFZmZlY3Q9ZnVuY3Rpb24oYixjLGUsZil7cmV0dXJuIHRoaXMudGFyZ2V0PWIsdGhpcy5fcGFyZW50PW51bGwsZT1hLm51bWVyaWNUaW1pbmdUb09iamVjdChlKSx0aGlzLl90aW1pbmdJbnB1dD1hLmNsb25lVGltaW5nSW5wdXQoZSksdGhpcy5fdGltaW5nPWEubm9ybWFsaXplVGltaW5nSW5wdXQoZSksdGhpcy50aW1pbmc9YS5tYWtlVGltaW5nKGUsITEsdGhpcyksdGhpcy50aW1pbmcuX2VmZmVjdD10aGlzLCJmdW5jdGlvbiI9PXR5cGVvZiBjPyhhLmRlcHJlY2F0ZWQoIkN1c3RvbSBLZXlmcmFtZUVmZmVjdCIsIjIwMTUtMDYtMjIiLCJVc2UgS2V5ZnJhbWVFZmZlY3Qub25zYW1wbGUgaW5zdGVhZC4iKSx0aGlzLl9ub3JtYWxpemVkS2V5ZnJhbWVzPWMpOnRoaXMuX25vcm1hbGl6ZWRLZXlmcmFtZXM9bmV3IGQoYyksdGhpcy5fa2V5ZnJhbWVzPWMsdGhpcy5hY3RpdmVEdXJhdGlvbj1hLmNhbGN1bGF0ZUFjdGl2ZUR1cmF0aW9uKHRoaXMuX3RpbWluZyksdGhpcy5faWQ9Zix0aGlzfSxiLktleWZyYW1lRWZmZWN0LnByb3RvdHlwZT17Z2V0RnJhbWVzOmZ1bmN0aW9uKCl7cmV0dXJuImZ1bmN0aW9uIj09dHlwZW9mIHRoaXMuX25vcm1hbGl6ZWRLZXlmcmFtZXM/dGhpcy5fbm9ybWFsaXplZEtleWZyYW1lczp0aGlzLl9ub3JtYWxpemVkS2V5ZnJhbWVzLl9mcmFtZXN9LHNldCBvbnNhbXBsZShhKXtpZigiZnVuY3Rpb24iPT10eXBlb2YgdGhpcy5nZXRGcmFtZXMoKSl0aHJvdyBuZXcgRXJyb3IoIlNldHRpbmcgb25zYW1wbGUgb24gY3VzdG9tIGVmZmVjdCBLZXlmcmFtZUVmZmVjdCBpcyBub3Qgc3VwcG9ydGVkLiIpO3RoaXMuX29uc2FtcGxlPWEsdGhpcy5fYW5pbWF0aW9uJiZ0aGlzLl9hbmltYXRpb24uX3JlYnVpbGRVbmRlcmx5aW5nQW5pbWF0aW9uKCl9LGdldCBwYXJlbnQoKXtyZXR1cm4gdGhpcy5fcGFyZW50fSxjbG9uZTpmdW5jdGlvbigpe2lmKCJmdW5jdGlvbiI9PXR5cGVvZiB0aGlzLmdldEZyYW1lcygpKXRocm93IG5ldyBFcnJvcigiQ2xvbmluZyBjdXN0b20gZWZmZWN0cyBpcyBub3Qgc3VwcG9ydGVkLiIpO3ZhciBiPW5ldyBLZXlmcmFtZUVmZmVjdCh0aGlzLnRhcmdldCxbXSxhLmNsb25lVGltaW5nSW5wdXQodGhpcy5fdGltaW5nSW5wdXQpLHRoaXMuX2lkKTtyZXR1cm4gYi5fbm9ybWFsaXplZEtleWZyYW1lcz10aGlzLl9ub3JtYWxpemVkS2V5ZnJhbWVzLGIuX2tleWZyYW1lcz10aGlzLl9rZXlmcmFtZXMsYn0scmVtb3ZlOmZ1bmN0aW9uKCl7Yi5yZW1vdmVNdWx0aShbdGhpc10pfX07dmFyIGc9RWxlbWVudC5wcm90b3R5cGUuYW5pbWF0ZTtFbGVtZW50LnByb3RvdHlwZS5hbmltYXRlPWZ1bmN0aW9uKGEsYyl7dmFyIGQ9IiI7cmV0dXJuIGMmJmMuaWQmJihkPWMuaWQpLGIudGltZWxpbmUuX3BsYXkobmV3IGIuS2V5ZnJhbWVFZmZlY3QodGhpcyxhLGMsZCkpfTt2YXIgaD1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiLCJkaXYiKTtiLm5ld1VuZGVybHlpbmdBbmltYXRpb25Gb3JLZXlmcmFtZUVmZmVjdD1mdW5jdGlvbihhKXtpZihhKXt2YXIgYj1hLnRhcmdldHx8aCxjPWEuX2tleWZyYW1lczsiZnVuY3Rpb24iPT10eXBlb2YgYyYmKGM9W10pO3ZhciBkPWEuX3RpbWluZ0lucHV0O2QuaWQ9YS5faWR9ZWxzZSB2YXIgYj1oLGM9W10sZD0wO3JldHVybiBnLmFwcGx5KGIsW2MsZF0pfSxiLmJpbmRBbmltYXRpb25Gb3JLZXlmcmFtZUVmZmVjdD1mdW5jdGlvbihhKXthLmVmZmVjdCYmImZ1bmN0aW9uIj09dHlwZW9mIGEuZWZmZWN0Ll9ub3JtYWxpemVkS2V5ZnJhbWVzJiZiLmJpbmRBbmltYXRpb25Gb3JDdXN0b21FZmZlY3QoYSl9O3ZhciBpPVtdO2IuYXdhaXRTdGFydFRpbWU9ZnVuY3Rpb24oYSl7bnVsbD09PWEuc3RhcnRUaW1lJiZhLl9pc0dyb3VwJiYoMD09aS5sZW5ndGgmJnJlcXVlc3RBbmltYXRpb25GcmFtZShlKSxpLnB1c2goYSkpfTt2YXIgaj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZTtPYmplY3QuZGVmaW5lUHJvcGVydHkod2luZG93LCJnZXRDb21wdXRlZFN0eWxlIix7Y29uZmlndXJhYmxlOiEwLGVudW1lcmFibGU6ITAsdmFsdWU6ZnVuY3Rpb24oKXtiLnRpbWVsaW5lLl91cGRhdGVBbmltYXRpb25zUHJvbWlzZXMoKTt2YXIgYT1qLmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyZXR1cm4gZSgpJiYoYT1qLmFwcGx5KHRoaXMsYXJndW1lbnRzKSksYi50aW1lbGluZS5fdXBkYXRlQW5pbWF0aW9uc1Byb21pc2VzKCksYX19KSx3aW5kb3cuS2V5ZnJhbWVFZmZlY3Q9Yi5LZXlmcmFtZUVmZmVjdCx3aW5kb3cuRWxlbWVudC5wcm90b3R5cGUuZ2V0QW5pbWF0aW9ucz1mdW5jdGlvbigpe3JldHVybiBkb2N1bWVudC50aW1lbGluZS5nZXRBbmltYXRpb25zKCkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBudWxsIT09YS5lZmZlY3QmJmEuZWZmZWN0LnRhcmdldD09dGhpc30uYmluZCh0aGlzKSl9fShhLGMpLGZ1bmN0aW9uKGEsYixjKXtmdW5jdGlvbiBkKGEpe2EuX3JlZ2lzdGVyZWR8fChhLl9yZWdpc3RlcmVkPSEwLGcucHVzaChhKSxofHwoaD0hMCxyZXF1ZXN0QW5pbWF0aW9uRnJhbWUoZSkpKX1mdW5jdGlvbiBlKGEpe3ZhciBiPWc7Zz1bXSxiLnNvcnQoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYS5fc2VxdWVuY2VOdW1iZXItYi5fc2VxdWVuY2VOdW1iZXJ9KSxiPWIuZmlsdGVyKGZ1bmN0aW9uKGEpe2EoKTt2YXIgYj1hLl9hbmltYXRpb24/YS5fYW5pbWF0aW9uLnBsYXlTdGF0ZToiaWRsZSI7cmV0dXJuInJ1bm5pbmciIT1iJiYicGVuZGluZyIhPWImJihhLl9yZWdpc3RlcmVkPSExKSxhLl9yZWdpc3RlcmVkfSksZy5wdXNoLmFwcGx5KGcsYiksZy5sZW5ndGg/KGg9ITAscmVxdWVzdEFuaW1hdGlvbkZyYW1lKGUpKTpoPSExfXZhciBmPShkb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiLCJkaXYiKSwwKTtiLmJpbmRBbmltYXRpb25Gb3JDdXN0b21FZmZlY3Q9ZnVuY3Rpb24oYil7dmFyIGMsZT1iLmVmZmVjdC50YXJnZXQsZz0iZnVuY3Rpb24iPT10eXBlb2YgYi5lZmZlY3QuZ2V0RnJhbWVzKCk7Yz1nP2IuZWZmZWN0LmdldEZyYW1lcygpOmIuZWZmZWN0Ll9vbnNhbXBsZTt2YXIgaD1iLmVmZmVjdC50aW1pbmcsaT1udWxsO2g9YS5ub3JtYWxpemVUaW1pbmdJbnB1dChoKTt2YXIgaj1mdW5jdGlvbigpe3ZhciBkPWouX2FuaW1hdGlvbj9qLl9hbmltYXRpb24uY3VycmVudFRpbWU6bnVsbDtudWxsIT09ZCYmKGQ9YS5jYWxjdWxhdGVJdGVyYXRpb25Qcm9ncmVzcyhhLmNhbGN1bGF0ZUFjdGl2ZUR1cmF0aW9uKGgpLGQsaCksaXNOYU4oZCkmJihkPW51bGwpKSxkIT09aSYmKGc/YyhkLGUsYi5lZmZlY3QpOmMoZCxiLmVmZmVjdCxiLmVmZmVjdC5fYW5pbWF0aW9uKSksaT1kfTtqLl9hbmltYXRpb249YixqLl9yZWdpc3RlcmVkPSExLGouX3NlcXVlbmNlTnVtYmVyPWYrKyxiLl9jYWxsYmFjaz1qLGQoail9O3ZhciBnPVtdLGg9ITE7Yi5BbmltYXRpb24ucHJvdG90eXBlLl9yZWdpc3Rlcj1mdW5jdGlvbigpe3RoaXMuX2NhbGxiYWNrJiZkKHRoaXMuX2NhbGxiYWNrKX19KGEsYyksZnVuY3Rpb24oYSxiLGMpe2Z1bmN0aW9uIGQoYSl7cmV0dXJuIGEuX3RpbWluZy5kZWxheSthLmFjdGl2ZUR1cmF0aW9uK2EuX3RpbWluZy5lbmREZWxheX1mdW5jdGlvbiBlKGIsYyxkKXt0aGlzLl9pZD1kLHRoaXMuX3BhcmVudD1udWxsLHRoaXMuY2hpbGRyZW49Ynx8W10sdGhpcy5fcmVwYXJlbnQodGhpcy5jaGlsZHJlbiksYz1hLm51bWVyaWNUaW1pbmdUb09iamVjdChjKSx0aGlzLl90aW1pbmdJbnB1dD1hLmNsb25lVGltaW5nSW5wdXQoYyksdGhpcy5fdGltaW5nPWEubm9ybWFsaXplVGltaW5nSW5wdXQoYywhMCksdGhpcy50aW1pbmc9YS5tYWtlVGltaW5nKGMsITAsdGhpcyksdGhpcy50aW1pbmcuX2VmZmVjdD10aGlzLCJhdXRvIj09PXRoaXMuX3RpbWluZy5kdXJhdGlvbiYmKHRoaXMuX3RpbWluZy5kdXJhdGlvbj10aGlzLmFjdGl2ZUR1cmF0aW9uKX13aW5kb3cuU2VxdWVuY2VFZmZlY3Q9ZnVuY3Rpb24oKXtlLmFwcGx5KHRoaXMsYXJndW1lbnRzKX0sd2luZG93Lkdyb3VwRWZmZWN0PWZ1bmN0aW9uKCl7ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyl9LGUucHJvdG90eXBlPXtfaXNBbmNlc3RvcjpmdW5jdGlvbihhKXtmb3IodmFyIGI9dGhpcztudWxsIT09Yjspe2lmKGI9PWEpcmV0dXJuITA7Yj1iLl9wYXJlbnR9cmV0dXJuITF9LF9yZWJ1aWxkOmZ1bmN0aW9uKCl7Zm9yKHZhciBhPXRoaXM7YTspImF1dG8iPT09YS50aW1pbmcuZHVyYXRpb24mJihhLl90aW1pbmcuZHVyYXRpb249YS5hY3RpdmVEdXJhdGlvbiksYT1hLl9wYXJlbnQ7dGhpcy5fYW5pbWF0aW9uJiZ0aGlzLl9hbmltYXRpb24uX3JlYnVpbGRVbmRlcmx5aW5nQW5pbWF0aW9uKCl9LF9yZXBhcmVudDpmdW5jdGlvbihhKXtiLnJlbW92ZU11bHRpKGEpO2Zvcih2YXIgYz0wO2M8YS5sZW5ndGg7YysrKWFbY10uX3BhcmVudD10aGlzfSxfcHV0Q2hpbGQ6ZnVuY3Rpb24oYSxiKXtmb3IodmFyIGM9Yj8iQ2Fubm90IGFwcGVuZCBhbiBhbmNlc3RvciBvciBzZWxmIjoiQ2Fubm90IHByZXBlbmQgYW4gYW5jZXN0b3Igb3Igc2VsZiIsZD0wO2Q8YS5sZW5ndGg7ZCsrKWlmKHRoaXMuX2lzQW5jZXN0b3IoYVtkXSkpdGhyb3d7dHlwZTpET01FeGNlcHRpb24uSElFUkFSQ0hZX1JFUVVFU1RfRVJSLG5hbWU6IkhpZXJhcmNoeVJlcXVlc3RFcnJvciIsbWVzc2FnZTpjfTtmb3IodmFyIGQ9MDtkPGEubGVuZ3RoO2QrKyliP3RoaXMuY2hpbGRyZW4ucHVzaChhW2RdKTp0aGlzLmNoaWxkcmVuLnVuc2hpZnQoYVtkXSk7dGhpcy5fcmVwYXJlbnQoYSksdGhpcy5fcmVidWlsZCgpfSxhcHBlbmQ6ZnVuY3Rpb24oKXt0aGlzLl9wdXRDaGlsZChhcmd1bWVudHMsITApfSxwcmVwZW5kOmZ1bmN0aW9uKCl7dGhpcy5fcHV0Q2hpbGQoYXJndW1lbnRzLCExKX0sZ2V0IHBhcmVudCgpe3JldHVybiB0aGlzLl9wYXJlbnR9LGdldCBmaXJzdENoaWxkKCl7cmV0dXJuIHRoaXMuY2hpbGRyZW4ubGVuZ3RoP3RoaXMuY2hpbGRyZW5bMF06bnVsbH0sZ2V0IGxhc3RDaGlsZCgpe3JldHVybiB0aGlzLmNoaWxkcmVuLmxlbmd0aD90aGlzLmNoaWxkcmVuW3RoaXMuY2hpbGRyZW4ubGVuZ3RoLTFdOm51bGx9LGNsb25lOmZ1bmN0aW9uKCl7Zm9yKHZhciBiPWEuY2xvbmVUaW1pbmdJbnB1dCh0aGlzLl90aW1pbmdJbnB1dCksYz1bXSxkPTA7ZDx0aGlzLmNoaWxkcmVuLmxlbmd0aDtkKyspYy5wdXNoKHRoaXMuY2hpbGRyZW5bZF0uY2xvbmUoKSk7cmV0dXJuIHRoaXMgaW5zdGFuY2VvZiBHcm91cEVmZmVjdD9uZXcgR3JvdXBFZmZlY3QoYyxiKTpuZXcgU2VxdWVuY2VFZmZlY3QoYyxiKX0scmVtb3ZlOmZ1bmN0aW9uKCl7Yi5yZW1vdmVNdWx0aShbdGhpc10pfX0sd2luZG93LlNlcXVlbmNlRWZmZWN0LnByb3RvdHlwZT1PYmplY3QuY3JlYXRlKGUucHJvdG90eXBlKSxPYmplY3QuZGVmaW5lUHJvcGVydHkod2luZG93LlNlcXVlbmNlRWZmZWN0LnByb3RvdHlwZSwiYWN0aXZlRHVyYXRpb24iLHtnZXQ6ZnVuY3Rpb24oKXt2YXIgYT0wO3JldHVybiB0aGlzLmNoaWxkcmVuLmZvckVhY2goZnVuY3Rpb24oYil7YSs9ZChiKX0pLE1hdGgubWF4KGEsMCl9fSksd2luZG93Lkdyb3VwRWZmZWN0LnByb3RvdHlwZT1PYmplY3QuY3JlYXRlKGUucHJvdG90eXBlKSxPYmplY3QuZGVmaW5lUHJvcGVydHkod2luZG93Lkdyb3VwRWZmZWN0LnByb3RvdHlwZSwiYWN0aXZlRHVyYXRpb24iLHtnZXQ6ZnVuY3Rpb24oKXt2YXIgYT0wO3JldHVybiB0aGlzLmNoaWxkcmVuLmZvckVhY2goZnVuY3Rpb24oYil7YT1NYXRoLm1heChhLGQoYikpfSksYX19KSxiLm5ld1VuZGVybHlpbmdBbmltYXRpb25Gb3JHcm91cD1mdW5jdGlvbihjKXt2YXIgZCxlPW51bGwsZj1mdW5jdGlvbihiKXt2YXIgYz1kLl93cmFwcGVyO2lmKGMmJiJwZW5kaW5nIiE9Yy5wbGF5U3RhdGUmJmMuZWZmZWN0KXJldHVybiBudWxsPT1iP3ZvaWQgYy5fcmVtb3ZlQ2hpbGRBbmltYXRpb25zKCk6MD09YiYmYy5wbGF5YmFja1JhdGU8MCYmKGV8fChlPWEubm9ybWFsaXplVGltaW5nSW5wdXQoYy5lZmZlY3QudGltaW5nKSksYj1hLmNhbGN1bGF0ZUl0ZXJhdGlvblByb2dyZXNzKGEuY2FsY3VsYXRlQWN0aXZlRHVyYXRpb24oZSksLTEsZSksaXNOYU4oYil8fG51bGw9PWIpPyhjLl9mb3JFYWNoQ2hpbGQoZnVuY3Rpb24oYSl7YS5jdXJyZW50VGltZT0tMX0pLHZvaWQgYy5fcmVtb3ZlQ2hpbGRBbmltYXRpb25zKCkpOnZvaWQgMH0sZz1uZXcgS2V5ZnJhbWVFZmZlY3QobnVsbCxbXSxjLl90aW1pbmcsYy5faWQpO3JldHVybiBnLm9uc2FtcGxlPWYsZD1iLnRpbWVsaW5lLl9wbGF5KGcpfSxiLmJpbmRBbmltYXRpb25Gb3JHcm91cD1mdW5jdGlvbihhKXthLl9hbmltYXRpb24uX3dyYXBwZXI9YSxhLl9pc0dyb3VwPSEwLGIuYXdhaXRTdGFydFRpbWUoYSksYS5fY29uc3RydWN0Q2hpbGRBbmltYXRpb25zKCksYS5fc2V0RXh0ZXJuYWxBbmltYXRpb24oYSl9LGIuZ3JvdXBDaGlsZER1cmF0aW9uPWR9KGEsYyl9KCk7KCgpPT57dmFyIGQxZT1PYmplY3QuY3JlYXRlO3ZhciBCTT1PYmplY3QuZGVmaW5lUHJvcGVydHksbTFlPU9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzLGcxZT1PYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yLF8xZT1PYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyx5MWU9T2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMsaGR0PU9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHMsdjFlPU9iamVjdC5nZXRQcm90b3R5cGVPZixwZHQ9T2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eSx4MWU9T2JqZWN0LnByb3RvdHlwZS5wcm9wZXJ0eUlzRW51bWVyYWJsZTt2YXIgRUk9TWF0aC5wb3csZmR0PShlLHQscik9PnQgaW4gZT9CTShlLHQse2VudW1lcmFibGU6ITAsY29uZmlndXJhYmxlOiEwLHdyaXRhYmxlOiEwLHZhbHVlOnJ9KTplW3RdPXIsS2w9KGUsdCk9Pntmb3IodmFyIHIgaW4gdHx8KHQ9e30pKXBkdC5jYWxsKHQscikmJmZkdChlLHIsdFtyXSk7aWYoaGR0KWZvcih2YXIgciBvZiBoZHQodCkpeDFlLmNhbGwodCxyKSYmZmR0KGUscix0W3JdKTtyZXR1cm4gZX0sTXg9KGUsdCk9Pm0xZShlLF8xZSh0KSk7dmFyIEV4PShlPT50eXBlb2YgcmVxdWlyZSE9InVuZGVmaW5lZCI/cmVxdWlyZTp0eXBlb2YgUHJveHkhPSJ1bmRlZmluZWQiP25ldyBQcm94eShlLHtnZXQ6KHQscik9Pih0eXBlb2YgcmVxdWlyZSE9InVuZGVmaW5lZCI/cmVxdWlyZTp0KVtyXX0pOmUpKGZ1bmN0aW9uKGUpe2lmKHR5cGVvZiByZXF1aXJlIT0idW5kZWZpbmVkIilyZXR1cm4gcmVxdWlyZS5hcHBseSh0aGlzLGFyZ3VtZW50cyk7dGhyb3cgbmV3IEVycm9yKCdEeW5hbWljIHJlcXVpcmUgb2YgIicrZSsnIiBpcyBub3Qgc3VwcG9ydGVkJyl9KTt2YXIgTT0oZSx0KT0+KCk9PihlJiYodD1lKGU9MCkpLHQpO3ZhciBIPShlLHQpPT4oKT0+KHR8fGUoKHQ9e2V4cG9ydHM6e319KS5leHBvcnRzLHQpLHQuZXhwb3J0cyksS3M9KGUsdCk9Pntmb3IodmFyIHIgaW4gdClCTShlLHIse2dldDp0W3JdLGVudW1lcmFibGU6ITB9KX0sZGR0PShlLHQscixuKT0+e2lmKHQmJnR5cGVvZiB0PT0ib2JqZWN0Inx8dHlwZW9mIHQ9PSJmdW5jdGlvbiIpZm9yKGxldCBpIG9mIHkxZSh0KSkhcGR0LmNhbGwoZSxpKSYmaSE9PXImJkJNKGUsaSx7Z2V0OigpPT50W2ldLGVudW1lcmFibGU6IShuPWcxZSh0LGkpKXx8bi5lbnVtZXJhYmxlfSk7cmV0dXJuIGV9O3ZhciBFZT0oZSx0LHIpPT4ocj1lIT1udWxsP2QxZSh2MWUoZSkpOnt9LGRkdCh0fHwhZXx8IWUuX19lc01vZHVsZT9CTShyLCJkZWZhdWx0Iix7dmFsdWU6ZSxlbnVtZXJhYmxlOiEwfSk6cixlKSksVXQ9ZT0+ZGR0KEJNKHt9LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KSxlKTt2YXIgUmk9KGUsdCxyKT0+bmV3IFByb21pc2UoKG4saSk9Pnt2YXIgbz1sPT57dHJ5e3Moci5uZXh0KGwpKX1jYXRjaChjKXtpKGMpfX0sYT1sPT57dHJ5e3Moci50aHJvdyhsKSl9Y2F0Y2goYyl7aShjKX19LHM9bD0+bC5kb25lP24obC52YWx1ZSk6UHJvbWlzZS5yZXNvbHZlKGwudmFsdWUpLnRoZW4obyxhKTtzKChyPXIuYXBwbHkoZSx0KSkubmV4dCgpKX0pO3ZhciBPZHQ9SCgobF9yLEFJKT0+e3ZhciBtZHQsZ2R0LF9kdCx5ZHQsdmR0LHhkdCxiZHQsd2R0LFNkdCxUSSx1RyxNZHQsRWR0LFRkdCxUeCxDZHQsQWR0LFBkdCxJZHQsTGR0LGtkdCxSZHQsTmR0LERkdCxDSTsoZnVuY3Rpb24oZSl7dmFyIHQ9dHlwZW9mIGdsb2JhbD09Im9iamVjdCI/Z2xvYmFsOnR5cGVvZiBzZWxmPT0ib2JqZWN0Ij9zZWxmOnR5cGVvZiB0aGlzPT0ib2JqZWN0Ij90aGlzOnt9O3R5cGVvZiBkZWZpbmU9PSJmdW5jdGlvbiImJmRlZmluZS5hbWQ/ZGVmaW5lKCJ0c2xpYiIsWyJleHBvcnRzIl0sZnVuY3Rpb24obil7ZShyKHQscihuKSkpfSk6dHlwZW9mIEFJPT0ib2JqZWN0IiYmdHlwZW9mIEFJLmV4cG9ydHM9PSJvYmplY3QiP2Uocih0LHIoQUkuZXhwb3J0cykpKTplKHIodCkpO2Z1bmN0aW9uIHIobixpKXtyZXR1cm4gbiE9PXQmJih0eXBlb2YgT2JqZWN0LmNyZWF0ZT09ImZ1bmN0aW9uIj9PYmplY3QuZGVmaW5lUHJvcGVydHkobiwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk6bi5fX2VzTW9kdWxlPSEwKSxmdW5jdGlvbihvLGEpe3JldHVybiBuW29dPWk/aShvLGEpOmF9fX0pKGZ1bmN0aW9uKGUpe3ZhciB0PU9iamVjdC5zZXRQcm90b3R5cGVPZnx8e19fcHJvdG9fXzpbXX1pbnN0YW5jZW9mIEFycmF5JiZmdW5jdGlvbihuLGkpe24uX19wcm90b19fPWl9fHxmdW5jdGlvbihuLGkpe2Zvcih2YXIgbyBpbiBpKU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChpLG8pJiYobltvXT1pW29dKX07bWR0PWZ1bmN0aW9uKG4saSl7aWYodHlwZW9mIGkhPSJmdW5jdGlvbiImJmkhPT1udWxsKXRocm93IG5ldyBUeXBlRXJyb3IoIkNsYXNzIGV4dGVuZHMgdmFsdWUgIitTdHJpbmcoaSkrIiBpcyBub3QgYSBjb25zdHJ1Y3RvciBvciBudWxsIik7dChuLGkpO2Z1bmN0aW9uIG8oKXt0aGlzLmNvbnN0cnVjdG9yPW59bi5wcm90b3R5cGU9aT09PW51bGw/T2JqZWN0LmNyZWF0ZShpKTooby5wcm90b3R5cGU9aS5wcm90b3R5cGUsbmV3IG8pfSxnZHQ9T2JqZWN0LmFzc2lnbnx8ZnVuY3Rpb24obil7Zm9yKHZhciBpLG89MSxhPWFyZ3VtZW50cy5sZW5ndGg7bzxhO28rKyl7aT1hcmd1bWVudHNbb107Zm9yKHZhciBzIGluIGkpT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGkscykmJihuW3NdPWlbc10pfXJldHVybiBufSxfZHQ9ZnVuY3Rpb24obixpKXt2YXIgbz17fTtmb3IodmFyIGEgaW4gbilPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwobixhKSYmaS5pbmRleE9mKGEpPDAmJihvW2FdPW5bYV0pO2lmKG4hPW51bGwmJnR5cGVvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzPT0iZnVuY3Rpb24iKWZvcih2YXIgcz0wLGE9T2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scyhuKTtzPGEubGVuZ3RoO3MrKylpLmluZGV4T2YoYVtzXSk8MCYmT2JqZWN0LnByb3RvdHlwZS5wcm9wZXJ0eUlzRW51bWVyYWJsZS5jYWxsKG4sYVtzXSkmJihvW2Fbc11dPW5bYVtzXV0pO3JldHVybiBvfSx5ZHQ9ZnVuY3Rpb24obixpLG8sYSl7dmFyIHM9YXJndW1lbnRzLmxlbmd0aCxsPXM8Mz9pOmE9PT1udWxsP2E9T2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcihpLG8pOmEsYztpZih0eXBlb2YgUmVmbGVjdD09Im9iamVjdCImJnR5cGVvZiBSZWZsZWN0LmRlY29yYXRlPT0iZnVuY3Rpb24iKWw9UmVmbGVjdC5kZWNvcmF0ZShuLGksbyxhKTtlbHNlIGZvcih2YXIgdT1uLmxlbmd0aC0xO3U+PTA7dS0tKShjPW5bdV0pJiYobD0oczwzP2MobCk6cz4zP2MoaSxvLGwpOmMoaSxvKSl8fGwpO3JldHVybiBzPjMmJmwmJk9iamVjdC5kZWZpbmVQcm9wZXJ0eShpLG8sbCksbH0sdmR0PWZ1bmN0aW9uKG4saSl7cmV0dXJuIGZ1bmN0aW9uKG8sYSl7aShvLGEsbil9fSx4ZHQ9ZnVuY3Rpb24obixpKXtpZih0eXBlb2YgUmVmbGVjdD09Im9iamVjdCImJnR5cGVvZiBSZWZsZWN0Lm1ldGFkYXRhPT0iZnVuY3Rpb24iKXJldHVybiBSZWZsZWN0Lm1ldGFkYXRhKG4saSl9LGJkdD1mdW5jdGlvbihuLGksbyxhKXtmdW5jdGlvbiBzKGwpe3JldHVybiBsIGluc3RhbmNlb2Ygbz9sOm5ldyBvKGZ1bmN0aW9uKGMpe2MobCl9KX1yZXR1cm4gbmV3KG98fChvPVByb21pc2UpKShmdW5jdGlvbihsLGMpe2Z1bmN0aW9uIHUocCl7dHJ5e2YoYS5uZXh0KHApKX1jYXRjaChkKXtjKGQpfX1mdW5jdGlvbiBoKHApe3RyeXtmKGEudGhyb3cocCkpfWNhdGNoKGQpe2MoZCl9fWZ1bmN0aW9uIGYocCl7cC5kb25lP2wocC52YWx1ZSk6cyhwLnZhbHVlKS50aGVuKHUsaCl9ZigoYT1hLmFwcGx5KG4saXx8W10pKS5uZXh0KCkpfSl9LHdkdD1mdW5jdGlvbihuLGkpe3ZhciBvPXtsYWJlbDowLHNlbnQ6ZnVuY3Rpb24oKXtpZihsWzBdJjEpdGhyb3cgbFsxXTtyZXR1cm4gbFsxXX0sdHJ5czpbXSxvcHM6W119LGEscyxsLGM7cmV0dXJuIGM9e25leHQ6dSgwKSx0aHJvdzp1KDEpLHJldHVybjp1KDIpfSx0eXBlb2YgU3ltYm9sPT0iZnVuY3Rpb24iJiYoY1tTeW1ib2wuaXRlcmF0b3JdPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXN9KSxjO2Z1bmN0aW9uIHUoZil7cmV0dXJuIGZ1bmN0aW9uKHApe3JldHVybiBoKFtmLHBdKX19ZnVuY3Rpb24gaChmKXtpZihhKXRocm93IG5ldyBUeXBlRXJyb3IoIkdlbmVyYXRvciBpcyBhbHJlYWR5IGV4ZWN1dGluZy4iKTtmb3IoO2MmJihjPTAsZlswXSYmKG89MCkpLG87KXRyeXtpZihhPTEscyYmKGw9ZlswXSYyP3MucmV0dXJuOmZbMF0/cy50aHJvd3x8KChsPXMucmV0dXJuKSYmbC5jYWxsKHMpLDApOnMubmV4dCkmJiEobD1sLmNhbGwocyxmWzFdKSkuZG9uZSlyZXR1cm4gbDtzd2l0Y2gocz0wLGwmJihmPVtmWzBdJjIsbC52YWx1ZV0pLGZbMF0pe2Nhc2UgMDpjYXNlIDE6bD1mO2JyZWFrO2Nhc2UgNDpyZXR1cm4gby5sYWJlbCsrLHt2YWx1ZTpmWzFdLGRvbmU6ITF9O2Nhc2UgNTpvLmxhYmVsKysscz1mWzFdLGY9WzBdO2NvbnRpbnVlO2Nhc2UgNzpmPW8ub3BzLnBvcCgpLG8udHJ5cy5wb3AoKTtjb250aW51ZTtkZWZhdWx0OmlmKGw9by50cnlzLCEobD1sLmxlbmd0aD4wJiZsW2wubGVuZ3RoLTFdKSYmKGZbMF09PT02fHxmWzBdPT09Mikpe289MDtjb250aW51ZX1pZihmWzBdPT09MyYmKCFsfHxmWzFdPmxbMF0mJmZbMV08bFszXSkpe28ubGFiZWw9ZlsxXTticmVha31pZihmWzBdPT09NiYmby5sYWJlbDxsWzFdKXtvLmxhYmVsPWxbMV0sbD1mO2JyZWFrfWlmKGwmJm8ubGFiZWw8bFsyXSl7by5sYWJlbD1sWzJdLG8ub3BzLnB1c2goZik7YnJlYWt9bFsyXSYmby5vcHMucG9wKCksby50cnlzLnBvcCgpO2NvbnRpbnVlfWY9aS5jYWxsKG4sbyl9Y2F0Y2gocCl7Zj1bNixwXSxzPTB9ZmluYWxseXthPWw9MH1pZihmWzBdJjUpdGhyb3cgZlsxXTtyZXR1cm57dmFsdWU6ZlswXT9mWzFdOnZvaWQgMCxkb25lOiEwfX19LFNkdD1mdW5jdGlvbihuLGkpe2Zvcih2YXIgbyBpbiBuKW8hPT0iZGVmYXVsdCImJiFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoaSxvKSYmQ0koaSxuLG8pfSxDST1PYmplY3QuY3JlYXRlP2Z1bmN0aW9uKG4saSxvLGEpe2E9PT12b2lkIDAmJihhPW8pO3ZhciBzPU9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IoaSxvKTsoIXN8fCgiZ2V0ImluIHM/IWkuX19lc01vZHVsZTpzLndyaXRhYmxlfHxzLmNvbmZpZ3VyYWJsZSkpJiYocz17ZW51bWVyYWJsZTohMCxnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gaVtvXX19KSxPYmplY3QuZGVmaW5lUHJvcGVydHkobixhLHMpfTpmdW5jdGlvbihuLGksbyxhKXthPT09dm9pZCAwJiYoYT1vKSxuW2FdPWlbb119LFRJPWZ1bmN0aW9uKG4pe3ZhciBpPXR5cGVvZiBTeW1ib2w9PSJmdW5jdGlvbiImJlN5bWJvbC5pdGVyYXRvcixvPWkmJm5baV0sYT0wO2lmKG8pcmV0dXJuIG8uY2FsbChuKTtpZihuJiZ0eXBlb2Ygbi5sZW5ndGg9PSJudW1iZXIiKXJldHVybntuZXh0OmZ1bmN0aW9uKCl7cmV0dXJuIG4mJmE+PW4ubGVuZ3RoJiYobj12b2lkIDApLHt2YWx1ZTpuJiZuW2ErK10sZG9uZTohbn19fTt0aHJvdyBuZXcgVHlwZUVycm9yKGk/Ik9iamVjdCBpcyBub3QgaXRlcmFibGUuIjoiU3ltYm9sLml0ZXJhdG9yIGlzIG5vdCBkZWZpbmVkLiIpfSx1Rz1mdW5jdGlvbihuLGkpe3ZhciBvPXR5cGVvZiBTeW1ib2w9PSJmdW5jdGlvbiImJm5bU3ltYm9sLml0ZXJhdG9yXTtpZighbylyZXR1cm4gbjt2YXIgYT1vLmNhbGwobikscyxsPVtdLGM7dHJ5e2Zvcig7KGk9PT12b2lkIDB8fGktLSA+MCkmJiEocz1hLm5leHQoKSkuZG9uZTspbC5wdXNoKHMudmFsdWUpfWNhdGNoKHUpe2M9e2Vycm9yOnV9fWZpbmFsbHl7dHJ5e3MmJiFzLmRvbmUmJihvPWEucmV0dXJuKSYmby5jYWxsKGEpfWZpbmFsbHl7aWYoYyl0aHJvdyBjLmVycm9yfX1yZXR1cm4gbH0sTWR0PWZ1bmN0aW9uKCl7Zm9yKHZhciBuPVtdLGk9MDtpPGFyZ3VtZW50cy5sZW5ndGg7aSsrKW49bi5jb25jYXQodUcoYXJndW1lbnRzW2ldKSk7cmV0dXJuIG59LEVkdD1mdW5jdGlvbigpe2Zvcih2YXIgbj0wLGk9MCxvPWFyZ3VtZW50cy5sZW5ndGg7aTxvO2krKyluKz1hcmd1bWVudHNbaV0ubGVuZ3RoO2Zvcih2YXIgYT1BcnJheShuKSxzPTAsaT0wO2k8bztpKyspZm9yKHZhciBsPWFyZ3VtZW50c1tpXSxjPTAsdT1sLmxlbmd0aDtjPHU7YysrLHMrKylhW3NdPWxbY107cmV0dXJuIGF9LFRkdD1mdW5jdGlvbihuLGksbyl7aWYob3x8YXJndW1lbnRzLmxlbmd0aD09PTIpZm9yKHZhciBhPTAscz1pLmxlbmd0aCxsO2E8czthKyspKGx8fCEoYSBpbiBpKSkmJihsfHwobD1BcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChpLDAsYSkpLGxbYV09aVthXSk7cmV0dXJuIG4uY29uY2F0KGx8fEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGkpKX0sVHg9ZnVuY3Rpb24obil7cmV0dXJuIHRoaXMgaW5zdGFuY2VvZiBUeD8odGhpcy52PW4sdGhpcyk6bmV3IFR4KG4pfSxDZHQ9ZnVuY3Rpb24obixpLG8pe2lmKCFTeW1ib2wuYXN5bmNJdGVyYXRvcil0aHJvdyBuZXcgVHlwZUVycm9yKCJTeW1ib2wuYXN5bmNJdGVyYXRvciBpcyBub3QgZGVmaW5lZC4iKTt2YXIgYT1vLmFwcGx5KG4saXx8W10pLHMsbD1bXTtyZXR1cm4gcz17fSxjKCJuZXh0IiksYygidGhyb3ciKSxjKCJyZXR1cm4iKSxzW1N5bWJvbC5hc3luY0l0ZXJhdG9yXT1mdW5jdGlvbigpe3JldHVybiB0aGlzfSxzO2Z1bmN0aW9uIGMoZyl7YVtnXSYmKHNbZ109ZnVuY3Rpb24oXyl7cmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKHkseCl7bC5wdXNoKFtnLF8seSx4XSk+MXx8dShnLF8pfSl9KX1mdW5jdGlvbiB1KGcsXyl7dHJ5e2goYVtnXShfKSl9Y2F0Y2goeSl7ZChsWzBdWzNdLHkpfX1mdW5jdGlvbiBoKGcpe2cudmFsdWUgaW5zdGFuY2VvZiBUeD9Qcm9taXNlLnJlc29sdmUoZy52YWx1ZS52KS50aGVuKGYscCk6ZChsWzBdWzJdLGcpfWZ1bmN0aW9uIGYoZyl7dSgibmV4dCIsZyl9ZnVuY3Rpb24gcChnKXt1KCJ0aHJvdyIsZyl9ZnVuY3Rpb24gZChnLF8pe2coXyksbC5zaGlmdCgpLGwubGVuZ3RoJiZ1KGxbMF1bMF0sbFswXVsxXSl9fSxBZHQ9ZnVuY3Rpb24obil7dmFyIGksbztyZXR1cm4gaT17fSxhKCJuZXh0IiksYSgidGhyb3ciLGZ1bmN0aW9uKHMpe3Rocm93IHN9KSxhKCJyZXR1cm4iKSxpW1N5bWJvbC5pdGVyYXRvcl09ZnVuY3Rpb24oKXtyZXR1cm4gdGhpc30saTtmdW5jdGlvbiBhKHMsbCl7aVtzXT1uW3NdP2Z1bmN0aW9uKGMpe3JldHVybihvPSFvKT97dmFsdWU6VHgobltzXShjKSksZG9uZTpzPT09InJldHVybiJ9Omw/bChjKTpjfTpsfX0sUGR0PWZ1bmN0aW9uKG4pe2lmKCFTeW1ib2wuYXN5bmNJdGVyYXRvcil0aHJvdyBuZXcgVHlwZUVycm9yKCJTeW1ib2wuYXN5bmNJdGVyYXRvciBpcyBub3QgZGVmaW5lZC4iKTt2YXIgaT1uW1N5bWJvbC5hc3luY0l0ZXJhdG9yXSxvO3JldHVybiBpP2kuY2FsbChuKToobj10eXBlb2YgVEk9PSJmdW5jdGlvbiI/VEkobik6bltTeW1ib2wuaXRlcmF0b3JdKCksbz17fSxhKCJuZXh0IiksYSgidGhyb3ciKSxhKCJyZXR1cm4iKSxvW1N5bWJvbC5hc3luY0l0ZXJhdG9yXT1mdW5jdGlvbigpe3JldHVybiB0aGlzfSxvKTtmdW5jdGlvbiBhKGwpe29bbF09bltsXSYmZnVuY3Rpb24oYyl7cmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKHUsaCl7Yz1uW2xdKGMpLHModSxoLGMuZG9uZSxjLnZhbHVlKX0pfX1mdW5jdGlvbiBzKGwsYyx1LGgpe1Byb21pc2UucmVzb2x2ZShoKS50aGVuKGZ1bmN0aW9uKGYpe2woe3ZhbHVlOmYsZG9uZTp1fSl9LGMpfX0sSWR0PWZ1bmN0aW9uKG4saSl7cmV0dXJuIE9iamVjdC5kZWZpbmVQcm9wZXJ0eT9PYmplY3QuZGVmaW5lUHJvcGVydHkobiwicmF3Iix7dmFsdWU6aX0pOm4ucmF3PWksbn07dmFyIHI9T2JqZWN0LmNyZWF0ZT9mdW5jdGlvbihuLGkpe09iamVjdC5kZWZpbmVQcm9wZXJ0eShuLCJkZWZhdWx0Iix7ZW51bWVyYWJsZTohMCx2YWx1ZTppfSl9OmZ1bmN0aW9uKG4saSl7bi5kZWZhdWx0PWl9O0xkdD1mdW5jdGlvbihuKXtpZihuJiZuLl9fZXNNb2R1bGUpcmV0dXJuIG47dmFyIGk9e307aWYobiE9bnVsbClmb3IodmFyIG8gaW4gbilvIT09ImRlZmF1bHQiJiZPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwobixvKSYmQ0koaSxuLG8pO3JldHVybiByKGksbiksaX0sa2R0PWZ1bmN0aW9uKG4pe3JldHVybiBuJiZuLl9fZXNNb2R1bGU/bjp7ZGVmYXVsdDpufX0sUmR0PWZ1bmN0aW9uKG4saSxvLGEpe2lmKG89PT0iYSImJiFhKXRocm93IG5ldyBUeXBlRXJyb3IoIlByaXZhdGUgYWNjZXNzb3Igd2FzIGRlZmluZWQgd2l0aG91dCBhIGdldHRlciIpO2lmKHR5cGVvZiBpPT0iZnVuY3Rpb24iP24hPT1pfHwhYTohaS5oYXMobikpdGhyb3cgbmV3IFR5cGVFcnJvcigiQ2Fubm90IHJlYWQgcHJpdmF0ZSBtZW1iZXIgZnJvbSBhbiBvYmplY3Qgd2hvc2UgY2xhc3MgZGlkIG5vdCBkZWNsYXJlIGl0Iik7cmV0dXJuIG89PT0ibSI/YTpvPT09ImEiP2EuY2FsbChuKTphP2EudmFsdWU6aS5nZXQobil9LE5kdD1mdW5jdGlvbihuLGksbyxhLHMpe2lmKGE9PT0ibSIpdGhyb3cgbmV3IFR5cGVFcnJvcigiUHJpdmF0ZSBtZXRob2QgaXMgbm90IHdyaXRhYmxlIik7aWYoYT09PSJhIiYmIXMpdGhyb3cgbmV3IFR5cGVFcnJvcigiUHJpdmF0ZSBhY2Nlc3NvciB3YXMgZGVmaW5lZCB3aXRob3V0IGEgc2V0dGVyIik7aWYodHlwZW9mIGk9PSJmdW5jdGlvbiI/biE9PWl8fCFzOiFpLmhhcyhuKSl0aHJvdyBuZXcgVHlwZUVycm9yKCJDYW5ub3Qgd3JpdGUgcHJpdmF0ZSBtZW1iZXIgdG8gYW4gb2JqZWN0IHdob3NlIGNsYXNzIGRpZCBub3QgZGVjbGFyZSBpdCIpO3JldHVybiBhPT09ImEiP3MuY2FsbChuLG8pOnM/cy52YWx1ZT1vOmkuc2V0KG4sbyksb30sRGR0PWZ1bmN0aW9uKG4saSl7aWYoaT09PW51bGx8fHR5cGVvZiBpIT0ib2JqZWN0IiYmdHlwZW9mIGkhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IFR5cGVFcnJvcigiQ2Fubm90IHVzZSAnaW4nIG9wZXJhdG9yIG9uIG5vbi1vYmplY3QiKTtyZXR1cm4gdHlwZW9mIG49PSJmdW5jdGlvbiI/aT09PW46bi5oYXMoaSl9LGUoIl9fZXh0ZW5kcyIsbWR0KSxlKCJfX2Fzc2lnbiIsZ2R0KSxlKCJfX3Jlc3QiLF9kdCksZSgiX19kZWNvcmF0ZSIseWR0KSxlKCJfX3BhcmFtIix2ZHQpLGUoIl9fbWV0YWRhdGEiLHhkdCksZSgiX19hd2FpdGVyIixiZHQpLGUoIl9fZ2VuZXJhdG9yIix3ZHQpLGUoIl9fZXhwb3J0U3RhciIsU2R0KSxlKCJfX2NyZWF0ZUJpbmRpbmciLENJKSxlKCJfX3ZhbHVlcyIsVEkpLGUoIl9fcmVhZCIsdUcpLGUoIl9fc3ByZWFkIixNZHQpLGUoIl9fc3ByZWFkQXJyYXlzIixFZHQpLGUoIl9fc3ByZWFkQXJyYXkiLFRkdCksZSgiX19hd2FpdCIsVHgpLGUoIl9fYXN5bmNHZW5lcmF0b3IiLENkdCksZSgiX19hc3luY0RlbGVnYXRvciIsQWR0KSxlKCJfX2FzeW5jVmFsdWVzIixQZHQpLGUoIl9fbWFrZVRlbXBsYXRlT2JqZWN0IixJZHQpLGUoIl9faW1wb3J0U3RhciIsTGR0KSxlKCJfX2ltcG9ydERlZmF1bHQiLGtkdCksZSgiX19jbGFzc1ByaXZhdGVGaWVsZEdldCIsUmR0KSxlKCJfX2NsYXNzUHJpdmF0ZUZpZWxkU2V0IixOZHQpLGUoIl9fY2xhc3NQcml2YXRlRmllbGRJbiIsRGR0KX0pfSk7dmFyIE9lPUgoKFJ4LCRNKT0+eyhmdW5jdGlvbigpe3ZhciBlLHQ9IjQuMTcuMjEiLHI9MjAwLG49IlVuc3VwcG9ydGVkIGNvcmUtanMgdXNlLiBUcnkgaHR0cHM6Ly9ucG1zLmlvL3NlYXJjaD9xPXBvbnlmaWxsLiIsaT0iRXhwZWN0ZWQgYSBmdW5jdGlvbiIsbz0iSW52YWxpZCBgdmFyaWFibGVgIG9wdGlvbiBwYXNzZWQgaW50byBgXy50ZW1wbGF0ZWAiLGE9Il9fbG9kYXNoX2hhc2hfdW5kZWZpbmVkX18iLHM9NTAwLGw9Il9fbG9kYXNoX3BsYWNlaG9sZGVyX18iLGM9MSx1PTIsaD00LGY9MSxwPTIsZD0xLGc9MixfPTQseT04LHg9MTYsYj0zMixTPTY0LEM9MTI4LFA9MjU2LGs9NTEyLE89MzAsRD0iLi4uIixCPTgwMCxJPTE2LEw9MSxSPTIsRj0zLHo9MS8wLFU9OTAwNzE5OTI1NDc0MDk5MSxXPTE3OTc2OTMxMzQ4NjIzMTU3ZTI5MixaPTAvMCxydD00Mjk0OTY3Mjk1LG90PXJ0LTEsc3Q9cnQ+Pj4xLFN0PVtbImFyeSIsQ10sWyJiaW5kIixkXSxbImJpbmRLZXkiLGddLFsiY3VycnkiLHldLFsiY3VycnlSaWdodCIseF0sWyJmbGlwIixrXSxbInBhcnRpYWwiLGJdLFsicGFydGlhbFJpZ2h0IixTXSxbInJlYXJnIixQXV0sYnQ9IltvYmplY3QgQXJndW1lbnRzXSIsTXQ9IltvYmplY3QgQXJyYXldIixsdD0iW29iamVjdCBBc3luY0Z1bmN0aW9uXSIsS3Q9IltvYmplY3QgQm9vbGVhbl0iLF90PSJbb2JqZWN0IERhdGVdIixjdD0iW29iamVjdCBET01FeGNlcHRpb25dIixYPSJbb2JqZWN0IEVycm9yXSIsZXQ9IltvYmplY3QgRnVuY3Rpb25dIixkdD0iW29iamVjdCBHZW5lcmF0b3JGdW5jdGlvbl0iLHE9IltvYmplY3QgTWFwXSIscHQ9IltvYmplY3QgTnVtYmVyXSIsaHQ9IltvYmplY3QgTnVsbF0iLHd0PSJbb2JqZWN0IE9iamVjdF0iLGt0PSJbb2JqZWN0IFByb21pc2VdIixpZT0iW29iamVjdCBQcm94eV0iLGVlPSJbb2JqZWN0IFJlZ0V4cF0iLExlPSJbb2JqZWN0IFNldF0iLGFyPSJbb2JqZWN0IFN0cmluZ10iLGZyPSJbb2JqZWN0IFN5bWJvbF0iLHR0PSJbb2JqZWN0IFVuZGVmaW5lZF0iLCQ9IltvYmplY3QgV2Vha01hcF0iLEl0PSJbb2JqZWN0IFdlYWtTZXRdIiwkdD0iW29iamVjdCBBcnJheUJ1ZmZlcl0iLGhlPSJbb2JqZWN0IERhdGFWaWV3XSIsVHQ9IltvYmplY3QgRmxvYXQzMkFycmF5XSIsYmU9IltvYmplY3QgRmxvYXQ2NEFycmF5XSIsbnQ9IltvYmplY3QgSW50OEFycmF5XSIsQ3Q9IltvYmplY3QgSW50MTZBcnJheV0iLFd0PSJbb2JqZWN0IEludDMyQXJyYXldIixmZT0iW29iamVjdCBVaW50OEFycmF5XSIsYXQ9IltvYmplY3QgVWludDhDbGFtcGVkQXJyYXldIixzZT0iW29iamVjdCBVaW50MTZBcnJheV0iLFF0PSJbb2JqZWN0IFVpbnQzMkFycmF5XSIsQ2U9L1xiX19wIFwrPSAnJzsvZyxQdD0vXGIoX19wIFwrPSkgJycgXCsvZyxOdD0vKF9fZVwoLio/XCl8XGJfX3RcKSkgXCtcbicnOy9nLHplPS8mKD86YW1wfGx0fGd0fHF1b3R8IzM5KTsvZyx5bj0vWyY8PiInXS9nLFdpPVJlZ0V4cCh6ZS5zb3VyY2UpLEFyPVJlZ0V4cCh5bi5zb3VyY2UpLFBhPS88JS0oW1xzXFNdKz8pJT4vZyxobz0vPCUoW1xzXFNdKz8pJT4vZyxJYT0vPCU9KFtcc1xTXSs/KSU+L2csbHg9L1wufFxbKD86W15bXF1dKnwoWyInXSkoPzooPyFcMSlbXlxcXXxcXC4pKj9cMSlcXS8sY209L15cdyokLyxKMD0vW14uW1xdXSt8XFsoPzooLT9cZCsoPzpcLlxkKyk/KXwoWyInXSkoKD86KD8hXDIpW15cXF18XFwuKSo/KVwyKVxdfCg/PSg/OlwufFxbXF0pKD86XC58XFtcXXwkKSkvZyxjbj0vW1xcXiQuKis/KClbXF17fXxdL2csY3g9UmVnRXhwKGNuLnNvdXJjZSkscnA9L15ccysvLEs9L1xzLyxndD0vXHsoPzpcblwvXCogXFt3cmFwcGVkIHdpdGggLitcXSBcKlwvKT9cbj8vLEV0PS9ce1xuXC9cKiBcW3dyYXBwZWQgd2l0aCAoLispXF0gXCovLHh0PS8sPyAmIC8sRnQ9L1teXHgwMC1ceDJmXHgzYS1ceDQwXHg1Yi1ceDYwXHg3Yi1ceDdmXSsvZyxWZT0vWygpPSx7fVxbXF1cL1xzXS8sVWU9L1xcKFxcKT8vZyx0cj0vXCRceyhbXlxcfV0qKD86XFwuW15cXH1dKikqKVx9L2csS2U9L1x3KiQvLFhyPS9eWy0rXTB4WzAtOWEtZl0rJC9pLF9yPS9eMGJbMDFdKyQvaSxQcj0vXlxbb2JqZWN0IC4rP0NvbnN0cnVjdG9yXF0kLyxYbj0vXjBvWzAtN10rJC9pLG5wPS9eKD86MHxbMS05XVxkKikkLyx1bT0vW1x4YzAtXHhkNlx4ZDgtXHhmNlx4ZjgtXHhmZlx1MDEwMC1cdTAxN2ZdL2csbXI9LygkXikvLEZsPS9bJ1xuXHJcdTIwMjhcdTIwMjlcXF0vZywkbj0iXFx1ZDgwMC1cXHVkZmZmIixCbD0iXFx1MDMwMC1cXHUwMzZmIix1eD0iXFx1ZmUyMC1cXHVmZTJmIixIbD0iXFx1MjBkMC1cXHUyMGZmIixWbD1CbCt1eCtIbCxZaT0iXFx1MjcwMC1cXHUyN2JmIixobT0iYS16XFx4ZGYtXFx4ZjZcXHhmOC1cXHhmZiIscXM9IlxceGFjXFx4YjFcXHhkN1xceGY3IixocGU9IlxceDAwLVxceDJmXFx4M2EtXFx4NDBcXHg1Yi1cXHg2MFxceDdiLVxceGJmIixmcGU9IlxcdTIwMDAtXFx1MjA2ZiIscHBlPSIgXFx0XFx4MGJcXGZcXHhhMFxcdWZlZmZcXG5cXHJcXHUyMDI4XFx1MjAyOVxcdTE2ODBcXHUxODBlXFx1MjAwMFxcdTIwMDFcXHUyMDAyXFx1MjAwM1xcdTIwMDRcXHUyMDA1XFx1MjAwNlxcdTIwMDdcXHUyMDA4XFx1MjAwOVxcdTIwMGFcXHUyMDJmXFx1MjA1ZlxcdTMwMDAiLHFodD0iQS1aXFx4YzAtXFx4ZDZcXHhkOC1cXHhkZSIsR2h0PSJcXHVmZTBlXFx1ZmUwZiIsV2h0PXFzK2hwZStmcGUrcHBlLHRxPSJbJ1x1MjAxOV0iLGRwZT0iWyIrJG4rIl0iLFlodD0iWyIrV2h0KyJdIixCNj0iWyIrVmwrIl0iLGpodD0iXFxkKyIsbXBlPSJbIitZaSsiXSIsWGh0PSJbIitobSsiXSIsJGh0PSJbXiIrJG4rV2h0K2podCtZaStobStxaHQrIl0iLGVxPSJcXHVkODNjW1xcdWRmZmItXFx1ZGZmZl0iLGdwZT0iKD86IitCNisifCIrZXErIikiLEtodD0iW14iKyRuKyJdIixycT0iKD86XFx1ZDgzY1tcXHVkZGU2LVxcdWRkZmZdKXsyfSIsbnE9IltcXHVkODAwLVxcdWRiZmZdW1xcdWRjMDAtXFx1ZGZmZl0iLGh4PSJbIitxaHQrIl0iLFpodD0iXFx1MjAwZCIsSmh0PSIoPzoiK1hodCsifCIrJGh0KyIpIixfcGU9Iig/OiIraHgrInwiKyRodCsiKSIsUWh0PSIoPzoiK3RxKyIoPzpkfGxsfG18cmV8c3x0fHZlKSk/Iix0ZnQ9Iig/OiIrdHErIig/OkR8TEx8TXxSRXxTfFR8VkUpKT8iLGVmdD1ncGUrIj8iLHJmdD0iWyIrR2h0KyJdPyIseXBlPSIoPzoiK1podCsiKD86IitbS2h0LHJxLG5xXS5qb2luKCJ8IikrIikiK3JmdCtlZnQrIikqIix2cGU9IlxcZCooPzoxc3R8Mm5kfDNyZHwoPyFbMTIzXSlcXGR0aCkoPz1cXGJ8W0EtWl9dKSIseHBlPSJcXGQqKD86MVNUfDJORHwzUkR8KD8hWzEyM10pXFxkVEgpKD89XFxifFthLXpfXSkiLG5mdD1yZnQrZWZ0K3lwZSxicGU9Iig/OiIrW21wZSxycSxucV0uam9pbigifCIpKyIpIituZnQsd3BlPSIoPzoiK1tLaHQrQjYrIj8iLEI2LHJxLG5xLGRwZV0uam9pbigifCIpKyIpIixTcGU9UmVnRXhwKHRxLCJnIiksTXBlPVJlZ0V4cChCNiwiZyIpLGlxPVJlZ0V4cChlcSsiKD89IitlcSsiKXwiK3dwZStuZnQsImciKSxFcGU9UmVnRXhwKFtoeCsiPyIrWGh0KyIrIitRaHQrIig/PSIrW1lodCxoeCwiJCJdLmpvaW4oInwiKSsiKSIsX3BlKyIrIit0ZnQrIig/PSIrW1lodCxoeCtKaHQsIiQiXS5qb2luKCJ8IikrIikiLGh4KyI/IitKaHQrIisiK1FodCxoeCsiKyIrdGZ0LHhwZSx2cGUsamh0LGJwZV0uam9pbigifCIpLCJnIiksVHBlPVJlZ0V4cCgiWyIrWmh0KyRuK1ZsK0dodCsiXSIpLENwZT0vW2Etel1bQS1aXXxbQS1aXXsyfVthLXpdfFswLTldW2EtekEtWl18W2EtekEtWl1bMC05XXxbXmEtekEtWjAtOSBdLyxBcGU9WyJBcnJheSIsIkJ1ZmZlciIsIkRhdGFWaWV3IiwiRGF0ZSIsIkVycm9yIiwiRmxvYXQzMkFycmF5IiwiRmxvYXQ2NEFycmF5IiwiRnVuY3Rpb24iLCJJbnQ4QXJyYXkiLCJJbnQxNkFycmF5IiwiSW50MzJBcnJheSIsIk1hcCIsIk1hdGgiLCJPYmplY3QiLCJQcm9taXNlIiwiUmVnRXhwIiwiU2V0IiwiU3RyaW5nIiwiU3ltYm9sIiwiVHlwZUVycm9yIiwiVWludDhBcnJheSIsIlVpbnQ4Q2xhbXBlZEFycmF5IiwiVWludDE2QXJyYXkiLCJVaW50MzJBcnJheSIsIldlYWtNYXAiLCJfIiwiY2xlYXJUaW1lb3V0IiwiaXNGaW5pdGUiLCJwYXJzZUludCIsInNldFRpbWVvdXQiXSxQcGU9LTEsUm49e307Um5bVHRdPVJuW2JlXT1SbltudF09Um5bQ3RdPVJuW1d0XT1SbltmZV09Um5bYXRdPVJuW3NlXT1SbltRdF09ITAsUm5bYnRdPVJuW010XT1SblskdF09Um5bS3RdPVJuW2hlXT1SbltfdF09Um5bWF09Um5bZXRdPVJuW3FdPVJuW3B0XT1Sblt3dF09Um5bZWVdPVJuW0xlXT1Sblthcl09Um5bJF09ITE7dmFyIFRuPXt9O1RuW2J0XT1UbltNdF09VG5bJHRdPVRuW2hlXT1UbltLdF09VG5bX3RdPVRuW1R0XT1UbltiZV09VG5bbnRdPVRuW0N0XT1UbltXdF09VG5bcV09VG5bcHRdPVRuW3d0XT1UbltlZV09VG5bTGVdPVRuW2FyXT1Ubltmcl09VG5bZmVdPVRuW2F0XT1UbltzZV09VG5bUXRdPSEwLFRuW1hdPVRuW2V0XT1UblskXT0hMTt2YXIgSXBlPXtcdTAwQzA6IkEiLFx1MDBDMToiQSIsXHUwMEMyOiJBIixcdTAwQzM6IkEiLFx1MDBDNDoiQSIsXHUwMEM1OiJBIixcdTAwRTA6ImEiLFx1MDBFMToiYSIsXHUwMEUyOiJhIixcdTAwRTM6ImEiLFx1MDBFNDoiYSIsXHUwMEU1OiJhIixcdTAwQzc6IkMiLFx1MDBFNzoiYyIsXHUwMEQwOiJEIixcdTAwRjA6ImQiLFx1MDBDODoiRSIsXHUwMEM5OiJFIixcdTAwQ0E6IkUiLFx1MDBDQjoiRSIsXHUwMEU4OiJlIixcdTAwRTk6ImUiLFx1MDBFQToiZSIsXHUwMEVCOiJlIixcdTAwQ0M6IkkiLFx1MDBDRDoiSSIsXHUwMENFOiJJIixcdTAwQ0Y6IkkiLFx1MDBFQzoiaSIsXHUwMEVEOiJpIixcdTAwRUU6ImkiLFx1MDBFRjoiaSIsXHUwMEQxOiJOIixcdTAwRjE6Im4iLFx1MDBEMjoiTyIsXHUwMEQzOiJPIixcdTAwRDQ6Ik8iLFx1MDBENToiTyIsXHUwMEQ2OiJPIixcdTAwRDg6Ik8iLFx1MDBGMjoibyIsXHUwMEYzOiJvIixcdTAwRjQ6Im8iLFx1MDBGNToibyIsXHUwMEY2OiJvIixcdTAwRjg6Im8iLFx1MDBEOToiVSIsXHUwMERBOiJVIixcdTAwREI6IlUiLFx1MDBEQzoiVSIsXHUwMEY5OiJ1IixcdTAwRkE6InUiLFx1MDBGQjoidSIsXHUwMEZDOiJ1IixcdTAwREQ6IlkiLFx1MDBGRDoieSIsXHUwMEZGOiJ5IixcdTAwQzY6IkFlIixcdTAwRTY6ImFlIixcdTAwREU6IlRoIixcdTAwRkU6InRoIixcdTAwREY6InNzIixcdTAxMDA6IkEiLFx1MDEwMjoiQSIsXHUwMTA0OiJBIixcdTAxMDE6ImEiLFx1MDEwMzoiYSIsXHUwMTA1OiJhIixcdTAxMDY6IkMiLFx1MDEwODoiQyIsXHUwMTBBOiJDIixcdTAxMEM6IkMiLFx1MDEwNzoiYyIsXHUwMTA5OiJjIixcdTAxMEI6ImMiLFx1MDEwRDoiYyIsXHUwMTBFOiJEIixcdTAxMTA6IkQiLFx1MDEwRjoiZCIsXHUwMTExOiJkIixcdTAxMTI6IkUiLFx1MDExNDoiRSIsXHUwMTE2OiJFIixcdTAxMTg6IkUiLFx1MDExQToiRSIsXHUwMTEzOiJlIixcdTAxMTU6ImUiLFx1MDExNzoiZSIsXHUwMTE5OiJlIixcdTAxMUI6ImUiLFx1MDExQzoiRyIsXHUwMTFFOiJHIixcdTAxMjA6IkciLFx1MDEyMjoiRyIsXHUwMTFEOiJnIixcdTAxMUY6ImciLFx1MDEyMToiZyIsXHUwMTIzOiJnIixcdTAxMjQ6IkgiLFx1MDEyNjoiSCIsXHUwMTI1OiJoIixcdTAxMjc6ImgiLFx1MDEyODoiSSIsXHUwMTJBOiJJIixcdTAxMkM6IkkiLFx1MDEyRToiSSIsXHUwMTMwOiJJIixcdTAxMjk6ImkiLFx1MDEyQjoiaSIsXHUwMTJEOiJpIixcdTAxMkY6ImkiLFx1MDEzMToiaSIsXHUwMTM0OiJKIixcdTAxMzU6ImoiLFx1MDEzNjoiSyIsXHUwMTM3OiJrIixcdTAxMzg6ImsiLFx1MDEzOToiTCIsXHUwMTNCOiJMIixcdTAxM0Q6IkwiLFx1MDEzRjoiTCIsXHUwMTQxOiJMIixcdTAxM0E6ImwiLFx1MDEzQzoibCIsXHUwMTNFOiJsIixcdTAxNDA6ImwiLFx1MDE0MjoibCIsXHUwMTQzOiJOIixcdTAxNDU6Ik4iLFx1MDE0NzoiTiIsXHUwMTRBOiJOIixcdTAxNDQ6Im4iLFx1MDE0NjoibiIsXHUwMTQ4OiJuIixcdTAxNEI6Im4iLFx1MDE0QzoiTyIsXHUwMTRFOiJPIixcdTAxNTA6Ik8iLFx1MDE0RDoibyIsXHUwMTRGOiJvIixcdTAxNTE6Im8iLFx1MDE1NDoiUiIsXHUwMTU2OiJSIixcdTAxNTg6IlIiLFx1MDE1NToiciIsXHUwMTU3OiJyIixcdTAxNTk6InIiLFx1MDE1QToiUyIsXHUwMTVDOiJTIixcdTAxNUU6IlMiLFx1MDE2MDoiUyIsXHUwMTVCOiJzIixcdTAxNUQ6InMiLFx1MDE1RjoicyIsXHUwMTYxOiJzIixcdTAxNjI6IlQiLFx1MDE2NDoiVCIsXHUwMTY2OiJUIixcdTAxNjM6InQiLFx1MDE2NToidCIsXHUwMTY3OiJ0IixcdTAxNjg6IlUiLFx1MDE2QToiVSIsXHUwMTZDOiJVIixcdTAxNkU6IlUiLFx1MDE3MDoiVSIsXHUwMTcyOiJVIixcdTAxNjk6InUiLFx1MDE2QjoidSIsXHUwMTZEOiJ1IixcdTAxNkY6InUiLFx1MDE3MToidSIsXHUwMTczOiJ1IixcdTAxNzQ6IlciLFx1MDE3NToidyIsXHUwMTc2OiJZIixcdTAxNzc6InkiLFx1MDE3ODoiWSIsXHUwMTc5OiJaIixcdTAxN0I6IloiLFx1MDE3RDoiWiIsXHUwMTdBOiJ6IixcdTAxN0M6InoiLFx1MDE3RToieiIsXHUwMTMyOiJJSiIsXHUwMTMzOiJpaiIsXHUwMTUyOiJPZSIsXHUwMTUzOiJvZSIsXHUwMTQ5OiInbiIsXHUwMTdGOiJzIn0sTHBlPXsiJiI6IiZhbXA7IiwiPCI6IiZsdDsiLCI+IjoiJmd0OyIsJyInOiImcXVvdDsiLCInIjoiJiMzOTsifSxrcGU9eyImYW1wOyI6IiYiLCImbHQ7IjoiPCIsIiZndDsiOiI+IiwiJnF1b3Q7IjonIicsIiYjMzk7IjoiJyJ9LFJwZT17IlxcIjoiXFwiLCInIjoiJyIsIlxuIjoibiIsIlxyIjoiciIsIlx1MjAyOCI6InUyMDI4IiwiXHUyMDI5IjoidTIwMjkifSxOcGU9cGFyc2VGbG9hdCxEcGU9cGFyc2VJbnQsaWZ0PXR5cGVvZiBnbG9iYWw9PSJvYmplY3QiJiZnbG9iYWwmJmdsb2JhbC5PYmplY3Q9PT1PYmplY3QmJmdsb2JhbCxPcGU9dHlwZW9mIHNlbGY9PSJvYmplY3QiJiZzZWxmJiZzZWxmLk9iamVjdD09PU9iamVjdCYmc2VsZixmbz1pZnR8fE9wZXx8RnVuY3Rpb24oInJldHVybiB0aGlzIikoKSxvcT10eXBlb2YgUng9PSJvYmplY3QiJiZSeCYmIVJ4Lm5vZGVUeXBlJiZSeCxRMD1vcSYmdHlwZW9mICRNPT0ib2JqZWN0IiYmJE0mJiEkTS5ub2RlVHlwZSYmJE0sb2Z0PVEwJiZRMC5leHBvcnRzPT09b3EsYXE9b2Z0JiZpZnQucHJvY2VzcyxVbD1mdW5jdGlvbigpe3RyeXt2YXIgdXQ9UTAmJlEwLnJlcXVpcmUmJlEwLnJlcXVpcmUoInV0aWwiKS50eXBlcztyZXR1cm4gdXR8fGFxJiZhcS5iaW5kaW5nJiZhcS5iaW5kaW5nKCJ1dGlsIil9Y2F0Y2goQXQpe319KCksYWZ0PVVsJiZVbC5pc0FycmF5QnVmZmVyLHNmdD1VbCYmVWwuaXNEYXRlLGxmdD1VbCYmVWwuaXNNYXAsY2Z0PVVsJiZVbC5pc1JlZ0V4cCx1ZnQ9VWwmJlVsLmlzU2V0LGhmdD1VbCYmVWwuaXNUeXBlZEFycmF5O2Z1bmN0aW9uIEdzKHV0LEF0LHZ0KXtzd2l0Y2godnQubGVuZ3RoKXtjYXNlIDA6cmV0dXJuIHV0LmNhbGwoQXQpO2Nhc2UgMTpyZXR1cm4gdXQuY2FsbChBdCx2dFswXSk7Y2FzZSAyOnJldHVybiB1dC5jYWxsKEF0LHZ0WzBdLHZ0WzFdKTtjYXNlIDM6cmV0dXJuIHV0LmNhbGwoQXQsdnRbMF0sdnRbMV0sdnRbMl0pfXJldHVybiB1dC5hcHBseShBdCx2dCl9ZnVuY3Rpb24genBlKHV0LEF0LHZ0LGdlKXtmb3IodmFyIFdlPS0xLCRyPXV0PT1udWxsPzA6dXQubGVuZ3RoOysrV2U8JHI7KXt2YXIgamk9dXRbV2VdO0F0KGdlLGppLHZ0KGppKSx1dCl9cmV0dXJuIGdlfWZ1bmN0aW9uIHFsKHV0LEF0KXtmb3IodmFyIHZ0PS0xLGdlPXV0PT1udWxsPzA6dXQubGVuZ3RoOysrdnQ8Z2UmJkF0KHV0W3Z0XSx2dCx1dCkhPT0hMTspO3JldHVybiB1dH1mdW5jdGlvbiBGcGUodXQsQXQpe2Zvcih2YXIgdnQ9dXQ9PW51bGw/MDp1dC5sZW5ndGg7dnQtLSYmQXQodXRbdnRdLHZ0LHV0KSE9PSExOyk7cmV0dXJuIHV0fWZ1bmN0aW9uIGZmdCh1dCxBdCl7Zm9yKHZhciB2dD0tMSxnZT11dD09bnVsbD8wOnV0Lmxlbmd0aDsrK3Z0PGdlOylpZighQXQodXRbdnRdLHZ0LHV0KSlyZXR1cm4hMTtyZXR1cm4hMH1mdW5jdGlvbiBmbSh1dCxBdCl7Zm9yKHZhciB2dD0tMSxnZT11dD09bnVsbD8wOnV0Lmxlbmd0aCxXZT0wLCRyPVtdOysrdnQ8Z2U7KXt2YXIgamk9dXRbdnRdO0F0KGppLHZ0LHV0KSYmKCRyW1dlKytdPWppKX1yZXR1cm4gJHJ9ZnVuY3Rpb24gSDYodXQsQXQpe3ZhciB2dD11dD09bnVsbD8wOnV0Lmxlbmd0aDtyZXR1cm4hIXZ0JiZmeCh1dCxBdCwwKT4tMX1mdW5jdGlvbiBzcSh1dCxBdCx2dCl7Zm9yKHZhciBnZT0tMSxXZT11dD09bnVsbD8wOnV0Lmxlbmd0aDsrK2dlPFdlOylpZih2dChBdCx1dFtnZV0pKXJldHVybiEwO3JldHVybiExfWZ1bmN0aW9uIEtuKHV0LEF0KXtmb3IodmFyIHZ0PS0xLGdlPXV0PT1udWxsPzA6dXQubGVuZ3RoLFdlPUFycmF5KGdlKTsrK3Z0PGdlOylXZVt2dF09QXQodXRbdnRdLHZ0LHV0KTtyZXR1cm4gV2V9ZnVuY3Rpb24gcG0odXQsQXQpe2Zvcih2YXIgdnQ9LTEsZ2U9QXQubGVuZ3RoLFdlPXV0Lmxlbmd0aDsrK3Z0PGdlOyl1dFtXZSt2dF09QXRbdnRdO3JldHVybiB1dH1mdW5jdGlvbiBscSh1dCxBdCx2dCxnZSl7dmFyIFdlPS0xLCRyPXV0PT1udWxsPzA6dXQubGVuZ3RoO2ZvcihnZSYmJHImJih2dD11dFsrK1dlXSk7KytXZTwkcjspdnQ9QXQodnQsdXRbV2VdLFdlLHV0KTtyZXR1cm4gdnR9ZnVuY3Rpb24gQnBlKHV0LEF0LHZ0LGdlKXt2YXIgV2U9dXQ9PW51bGw/MDp1dC5sZW5ndGg7Zm9yKGdlJiZXZSYmKHZ0PXV0Wy0tV2VdKTtXZS0tOyl2dD1BdCh2dCx1dFtXZV0sV2UsdXQpO3JldHVybiB2dH1mdW5jdGlvbiBjcSh1dCxBdCl7Zm9yKHZhciB2dD0tMSxnZT11dD09bnVsbD8wOnV0Lmxlbmd0aDsrK3Z0PGdlOylpZihBdCh1dFt2dF0sdnQsdXQpKXJldHVybiEwO3JldHVybiExfXZhciBIcGU9dXEoImxlbmd0aCIpO2Z1bmN0aW9uIFZwZSh1dCl7cmV0dXJuIHV0LnNwbGl0KCIiKX1mdW5jdGlvbiBVcGUodXQpe3JldHVybiB1dC5tYXRjaChGdCl8fFtdfWZ1bmN0aW9uIHBmdCh1dCxBdCx2dCl7dmFyIGdlO3JldHVybiB2dCh1dCxmdW5jdGlvbihXZSwkcixqaSl7aWYoQXQoV2UsJHIsamkpKXJldHVybiBnZT0kciwhMX0pLGdlfWZ1bmN0aW9uIFY2KHV0LEF0LHZ0LGdlKXtmb3IodmFyIFdlPXV0Lmxlbmd0aCwkcj12dCsoZ2U/MTotMSk7Z2U/JHItLTorKyRyPFdlOylpZihBdCh1dFskcl0sJHIsdXQpKXJldHVybiAkcjtyZXR1cm4tMX1mdW5jdGlvbiBmeCh1dCxBdCx2dCl7cmV0dXJuIEF0PT09QXQ/dGRlKHV0LEF0LHZ0KTpWNih1dCxkZnQsdnQpfWZ1bmN0aW9uIHFwZSh1dCxBdCx2dCxnZSl7Zm9yKHZhciBXZT12dC0xLCRyPXV0Lmxlbmd0aDsrK1dlPCRyOylpZihnZSh1dFtXZV0sQXQpKXJldHVybiBXZTtyZXR1cm4tMX1mdW5jdGlvbiBkZnQodXQpe3JldHVybiB1dCE9PXV0fWZ1bmN0aW9uIG1mdCh1dCxBdCl7dmFyIHZ0PXV0PT1udWxsPzA6dXQubGVuZ3RoO3JldHVybiB2dD9mcSh1dCxBdCkvdnQ6Wn1mdW5jdGlvbiB1cSh1dCl7cmV0dXJuIGZ1bmN0aW9uKEF0KXtyZXR1cm4gQXQ9PW51bGw/ZTpBdFt1dF19fWZ1bmN0aW9uIGhxKHV0KXtyZXR1cm4gZnVuY3Rpb24oQXQpe3JldHVybiB1dD09bnVsbD9lOnV0W0F0XX19ZnVuY3Rpb24gZ2Z0KHV0LEF0LHZ0LGdlLFdlKXtyZXR1cm4gV2UodXQsZnVuY3Rpb24oJHIsamksdm4pe3Z0PWdlPyhnZT0hMSwkcik6QXQodnQsJHIsamksdm4pfSksdnR9ZnVuY3Rpb24gR3BlKHV0LEF0KXt2YXIgdnQ9dXQubGVuZ3RoO2Zvcih1dC5zb3J0KEF0KTt2dC0tOyl1dFt2dF09dXRbdnRdLnZhbHVlO3JldHVybiB1dH1mdW5jdGlvbiBmcSh1dCxBdCl7Zm9yKHZhciB2dCxnZT0tMSxXZT11dC5sZW5ndGg7KytnZTxXZTspe3ZhciAkcj1BdCh1dFtnZV0pOyRyIT09ZSYmKHZ0PXZ0PT09ZT8kcjp2dCskcil9cmV0dXJuIHZ0fWZ1bmN0aW9uIHBxKHV0LEF0KXtmb3IodmFyIHZ0PS0xLGdlPUFycmF5KHV0KTsrK3Z0PHV0OylnZVt2dF09QXQodnQpO3JldHVybiBnZX1mdW5jdGlvbiBXcGUodXQsQXQpe3JldHVybiBLbihBdCxmdW5jdGlvbih2dCl7cmV0dXJuW3Z0LHV0W3Z0XV19KX1mdW5jdGlvbiBfZnQodXQpe3JldHVybiB1dCYmdXQuc2xpY2UoMCxiZnQodXQpKzEpLnJlcGxhY2UocnAsIiIpfWZ1bmN0aW9uIFdzKHV0KXtyZXR1cm4gZnVuY3Rpb24oQXQpe3JldHVybiB1dChBdCl9fWZ1bmN0aW9uIGRxKHV0LEF0KXtyZXR1cm4gS24oQXQsZnVuY3Rpb24odnQpe3JldHVybiB1dFt2dF19KX1mdW5jdGlvbiBNTSh1dCxBdCl7cmV0dXJuIHV0LmhhcyhBdCl9ZnVuY3Rpb24geWZ0KHV0LEF0KXtmb3IodmFyIHZ0PS0xLGdlPXV0Lmxlbmd0aDsrK3Z0PGdlJiZmeChBdCx1dFt2dF0sMCk+LTE7KTtyZXR1cm4gdnR9ZnVuY3Rpb24gdmZ0KHV0LEF0KXtmb3IodmFyIHZ0PXV0Lmxlbmd0aDt2dC0tJiZmeChBdCx1dFt2dF0sMCk+LTE7KTtyZXR1cm4gdnR9ZnVuY3Rpb24gWXBlKHV0LEF0KXtmb3IodmFyIHZ0PXV0Lmxlbmd0aCxnZT0wO3Z0LS07KXV0W3Z0XT09PUF0JiYrK2dlO3JldHVybiBnZX12YXIganBlPWhxKElwZSksWHBlPWhxKExwZSk7ZnVuY3Rpb24gJHBlKHV0KXtyZXR1cm4iXFwiK1JwZVt1dF19ZnVuY3Rpb24gS3BlKHV0LEF0KXtyZXR1cm4gdXQ9PW51bGw/ZTp1dFtBdF19ZnVuY3Rpb24gcHgodXQpe3JldHVybiBUcGUudGVzdCh1dCl9ZnVuY3Rpb24gWnBlKHV0KXtyZXR1cm4gQ3BlLnRlc3QodXQpfWZ1bmN0aW9uIEpwZSh1dCl7Zm9yKHZhciBBdCx2dD1bXTshKEF0PXV0Lm5leHQoKSkuZG9uZTspdnQucHVzaChBdC52YWx1ZSk7cmV0dXJuIHZ0fWZ1bmN0aW9uIG1xKHV0KXt2YXIgQXQ9LTEsdnQ9QXJyYXkodXQuc2l6ZSk7cmV0dXJuIHV0LmZvckVhY2goZnVuY3Rpb24oZ2UsV2Upe3Z0WysrQXRdPVtXZSxnZV19KSx2dH1mdW5jdGlvbiB4ZnQodXQsQXQpe3JldHVybiBmdW5jdGlvbih2dCl7cmV0dXJuIHV0KEF0KHZ0KSl9fWZ1bmN0aW9uIGRtKHV0LEF0KXtmb3IodmFyIHZ0PS0xLGdlPXV0Lmxlbmd0aCxXZT0wLCRyPVtdOysrdnQ8Z2U7KXt2YXIgamk9dXRbdnRdOyhqaT09PUF0fHxqaT09PWwpJiYodXRbdnRdPWwsJHJbV2UrK109dnQpfXJldHVybiAkcn1mdW5jdGlvbiBVNih1dCl7dmFyIEF0PS0xLHZ0PUFycmF5KHV0LnNpemUpO3JldHVybiB1dC5mb3JFYWNoKGZ1bmN0aW9uKGdlKXt2dFsrK0F0XT1nZX0pLHZ0fWZ1bmN0aW9uIFFwZSh1dCl7dmFyIEF0PS0xLHZ0PUFycmF5KHV0LnNpemUpO3JldHVybiB1dC5mb3JFYWNoKGZ1bmN0aW9uKGdlKXt2dFsrK0F0XT1bZ2UsZ2VdfSksdnR9ZnVuY3Rpb24gdGRlKHV0LEF0LHZ0KXtmb3IodmFyIGdlPXZ0LTEsV2U9dXQubGVuZ3RoOysrZ2U8V2U7KWlmKHV0W2dlXT09PUF0KXJldHVybiBnZTtyZXR1cm4tMX1mdW5jdGlvbiBlZGUodXQsQXQsdnQpe2Zvcih2YXIgZ2U9dnQrMTtnZS0tOylpZih1dFtnZV09PT1BdClyZXR1cm4gZ2U7cmV0dXJuIGdlfWZ1bmN0aW9uIGR4KHV0KXtyZXR1cm4gcHgodXQpP25kZSh1dCk6SHBlKHV0KX1mdW5jdGlvbiB0dSh1dCl7cmV0dXJuIHB4KHV0KT9pZGUodXQpOlZwZSh1dCl9ZnVuY3Rpb24gYmZ0KHV0KXtmb3IodmFyIEF0PXV0Lmxlbmd0aDtBdC0tJiZLLnRlc3QodXQuY2hhckF0KEF0KSk7KTtyZXR1cm4gQXR9dmFyIHJkZT1ocShrcGUpO2Z1bmN0aW9uIG5kZSh1dCl7Zm9yKHZhciBBdD1pcS5sYXN0SW5kZXg9MDtpcS50ZXN0KHV0KTspKytBdDtyZXR1cm4gQXR9ZnVuY3Rpb24gaWRlKHV0KXtyZXR1cm4gdXQubWF0Y2goaXEpfHxbXX1mdW5jdGlvbiBvZGUodXQpe3JldHVybiB1dC5tYXRjaChFcGUpfHxbXX12YXIgYWRlPWZ1bmN0aW9uIHV0KEF0KXtBdD1BdD09bnVsbD9mbzptbS5kZWZhdWx0cyhmby5PYmplY3QoKSxBdCxtbS5waWNrKGZvLEFwZSkpO3ZhciB2dD1BdC5BcnJheSxnZT1BdC5EYXRlLFdlPUF0LkVycm9yLCRyPUF0LkZ1bmN0aW9uLGppPUF0Lk1hdGgsdm49QXQuT2JqZWN0LGdxPUF0LlJlZ0V4cCxzZGU9QXQuU3RyaW5nLEdsPUF0LlR5cGVFcnJvcixxNj12dC5wcm90b3R5cGUsbGRlPSRyLnByb3RvdHlwZSxteD12bi5wcm90b3R5cGUsRzY9QXRbIl9fY29yZS1qc19zaGFyZWRfXyJdLFc2PWxkZS50b1N0cmluZyx1bj1teC5oYXNPd25Qcm9wZXJ0eSxjZGU9MCx3ZnQ9ZnVuY3Rpb24oKXt2YXIgbT0vW14uXSskLy5leGVjKEc2JiZHNi5rZXlzJiZHNi5rZXlzLklFX1BST1RPfHwiIik7cmV0dXJuIG0/IlN5bWJvbChzcmMpXzEuIittOiIifSgpLFk2PW14LnRvU3RyaW5nLHVkZT1XNi5jYWxsKHZuKSxoZGU9Zm8uXyxmZGU9Z3EoIl4iK1c2LmNhbGwodW4pLnJlcGxhY2UoY24sIlxcJCYiKS5yZXBsYWNlKC9oYXNPd25Qcm9wZXJ0eXwoZnVuY3Rpb24pLio/KD89XFxcKCl8IGZvciAuKz8oPz1cXFxdKS9nLCIkMS4qPyIpKyIkIiksajY9b2Z0P0F0LkJ1ZmZlcjplLGdtPUF0LlN5bWJvbCxYNj1BdC5VaW50OEFycmF5LFNmdD1qNj9qNi5hbGxvY1Vuc2FmZTplLCQ2PXhmdCh2bi5nZXRQcm90b3R5cGVPZix2biksTWZ0PXZuLmNyZWF0ZSxFZnQ9bXgucHJvcGVydHlJc0VudW1lcmFibGUsSzY9cTYuc3BsaWNlLFRmdD1nbT9nbS5pc0NvbmNhdFNwcmVhZGFibGU6ZSxFTT1nbT9nbS5pdGVyYXRvcjplLHRfPWdtP2dtLnRvU3RyaW5nVGFnOmUsWjY9ZnVuY3Rpb24oKXt0cnl7dmFyIG09b18odm4sImRlZmluZVByb3BlcnR5Iik7cmV0dXJuIG0oe30sIiIse30pLG19Y2F0Y2godil7fX0oKSxwZGU9QXQuY2xlYXJUaW1lb3V0IT09Zm8uY2xlYXJUaW1lb3V0JiZBdC5jbGVhclRpbWVvdXQsZGRlPWdlJiZnZS5ub3chPT1mby5EYXRlLm5vdyYmZ2Uubm93LG1kZT1BdC5zZXRUaW1lb3V0IT09Zm8uc2V0VGltZW91dCYmQXQuc2V0VGltZW91dCxKNj1qaS5jZWlsLFE2PWppLmZsb29yLF9xPXZuLmdldE93blByb3BlcnR5U3ltYm9scyxnZGU9ajY/ajYuaXNCdWZmZXI6ZSxDZnQ9QXQuaXNGaW5pdGUsX2RlPXE2LmpvaW4seWRlPXhmdCh2bi5rZXlzLHZuKSxYaT1qaS5tYXgscmE9amkubWluLHZkZT1nZS5ub3cseGRlPUF0LnBhcnNlSW50LEFmdD1qaS5yYW5kb20sYmRlPXE2LnJldmVyc2UseXE9b18oQXQsIkRhdGFWaWV3IiksVE09b18oQXQsIk1hcCIpLHZxPW9fKEF0LCJQcm9taXNlIiksZ3g9b18oQXQsIlNldCIpLENNPW9fKEF0LCJXZWFrTWFwIiksQU09b18odm4sImNyZWF0ZSIpLHRJPUNNJiZuZXcgQ00sX3g9e30sd2RlPWFfKHlxKSxTZGU9YV8oVE0pLE1kZT1hXyh2cSksRWRlPWFfKGd4KSxUZGU9YV8oQ00pLGVJPWdtP2dtLnByb3RvdHlwZTplLFBNPWVJP2VJLnZhbHVlT2Y6ZSxQZnQ9ZUk/ZUkudG9TdHJpbmc6ZTtmdW5jdGlvbiBHKG0pe2lmKGxpKG0pJiYhJGUobSkmJiEobSBpbnN0YW5jZW9mIGdyKSl7aWYobSBpbnN0YW5jZW9mIFdsKXJldHVybiBtO2lmKHVuLmNhbGwobSwiX193cmFwcGVkX18iKSlyZXR1cm4gSXB0KG0pfXJldHVybiBuZXcgV2wobSl9dmFyIHl4PWZ1bmN0aW9uKCl7ZnVuY3Rpb24gbSgpe31yZXR1cm4gZnVuY3Rpb24odil7aWYoIXJpKHYpKXJldHVybnt9O2lmKE1mdClyZXR1cm4gTWZ0KHYpO20ucHJvdG90eXBlPXY7dmFyIFQ9bmV3IG07cmV0dXJuIG0ucHJvdG90eXBlPWUsVH19KCk7ZnVuY3Rpb24gckkoKXt9ZnVuY3Rpb24gV2wobSx2KXt0aGlzLl9fd3JhcHBlZF9fPW0sdGhpcy5fX2FjdGlvbnNfXz1bXSx0aGlzLl9fY2hhaW5fXz0hIXYsdGhpcy5fX2luZGV4X189MCx0aGlzLl9fdmFsdWVzX189ZX1HLnRlbXBsYXRlU2V0dGluZ3M9e2VzY2FwZTpQYSxldmFsdWF0ZTpobyxpbnRlcnBvbGF0ZTpJYSx2YXJpYWJsZToiIixpbXBvcnRzOntfOkd9fSxHLnByb3RvdHlwZT1ySS5wcm90b3R5cGUsRy5wcm90b3R5cGUuY29uc3RydWN0b3I9RyxXbC5wcm90b3R5cGU9eXgockkucHJvdG90eXBlKSxXbC5wcm90b3R5cGUuY29uc3RydWN0b3I9V2w7ZnVuY3Rpb24gZ3IobSl7dGhpcy5fX3dyYXBwZWRfXz1tLHRoaXMuX19hY3Rpb25zX189W10sdGhpcy5fX2Rpcl9fPTEsdGhpcy5fX2ZpbHRlcmVkX189ITEsdGhpcy5fX2l0ZXJhdGVlc19fPVtdLHRoaXMuX190YWtlQ291bnRfXz1ydCx0aGlzLl9fdmlld3NfXz1bXX1mdW5jdGlvbiBDZGUoKXt2YXIgbT1uZXcgZ3IodGhpcy5fX3dyYXBwZWRfXyk7cmV0dXJuIG0uX19hY3Rpb25zX189cHModGhpcy5fX2FjdGlvbnNfXyksbS5fX2Rpcl9fPXRoaXMuX19kaXJfXyxtLl9fZmlsdGVyZWRfXz10aGlzLl9fZmlsdGVyZWRfXyxtLl9faXRlcmF0ZWVzX189cHModGhpcy5fX2l0ZXJhdGVlc19fKSxtLl9fdGFrZUNvdW50X189dGhpcy5fX3Rha2VDb3VudF9fLG0uX192aWV3c19fPXBzKHRoaXMuX192aWV3c19fKSxtfWZ1bmN0aW9uIEFkZSgpe2lmKHRoaXMuX19maWx0ZXJlZF9fKXt2YXIgbT1uZXcgZ3IodGhpcyk7bS5fX2Rpcl9fPS0xLG0uX19maWx0ZXJlZF9fPSEwfWVsc2UgbT10aGlzLmNsb25lKCksbS5fX2Rpcl9fKj0tMTtyZXR1cm4gbX1mdW5jdGlvbiBQZGUoKXt2YXIgbT10aGlzLl9fd3JhcHBlZF9fLnZhbHVlKCksdj10aGlzLl9fZGlyX18sVD0kZShtKSxOPXY8MCxWPVQ/bS5sZW5ndGg6MCxZPVZtZSgwLFYsdGhpcy5fX3ZpZXdzX18pLEo9WS5zdGFydCxpdD1ZLmVuZCxmdD1pdC1KLER0PU4/aXQ6Si0xLE90PXRoaXMuX19pdGVyYXRlZXNfXyxWdD1PdC5sZW5ndGgsb2U9MCxUZT1yYShmdCx0aGlzLl9fdGFrZUNvdW50X18pO2lmKCFUfHwhTiYmVj09ZnQmJlRlPT1mdClyZXR1cm4gUWZ0KG0sdGhpcy5fX2FjdGlvbnNfXyk7dmFyIE5lPVtdO3Q6Zm9yKDtmdC0tJiZvZTxUZTspe0R0Kz12O2Zvcih2YXIgZXI9LTEsRGU9bVtEdF07KytlcjxWdDspe3ZhciBwcj1PdFtlcl0seXI9cHIuaXRlcmF0ZWUsWHM9cHIudHlwZSxSYT15cihEZSk7aWYoWHM9PVIpRGU9UmE7ZWxzZSBpZighUmEpe2lmKFhzPT1MKWNvbnRpbnVlIHQ7YnJlYWsgdH19TmVbb2UrK109RGV9cmV0dXJuIE5lfWdyLnByb3RvdHlwZT15eChySS5wcm90b3R5cGUpLGdyLnByb3RvdHlwZS5jb25zdHJ1Y3Rvcj1ncjtmdW5jdGlvbiBlXyhtKXt2YXIgdj0tMSxUPW09PW51bGw/MDptLmxlbmd0aDtmb3IodGhpcy5jbGVhcigpOysrdjxUOyl7dmFyIE49bVt2XTt0aGlzLnNldChOWzBdLE5bMV0pfX1mdW5jdGlvbiBJZGUoKXt0aGlzLl9fZGF0YV9fPUFNP0FNKG51bGwpOnt9LHRoaXMuc2l6ZT0wfWZ1bmN0aW9uIExkZShtKXt2YXIgdj10aGlzLmhhcyhtKSYmZGVsZXRlIHRoaXMuX19kYXRhX19bbV07cmV0dXJuIHRoaXMuc2l6ZS09dj8xOjAsdn1mdW5jdGlvbiBrZGUobSl7dmFyIHY9dGhpcy5fX2RhdGFfXztpZihBTSl7dmFyIFQ9dlttXTtyZXR1cm4gVD09PWE/ZTpUfXJldHVybiB1bi5jYWxsKHYsbSk/dlttXTplfWZ1bmN0aW9uIFJkZShtKXt2YXIgdj10aGlzLl9fZGF0YV9fO3JldHVybiBBTT92W21dIT09ZTp1bi5jYWxsKHYsbSl9ZnVuY3Rpb24gTmRlKG0sdil7dmFyIFQ9dGhpcy5fX2RhdGFfXztyZXR1cm4gdGhpcy5zaXplKz10aGlzLmhhcyhtKT8wOjEsVFttXT1BTSYmdj09PWU/YTp2LHRoaXN9ZV8ucHJvdG90eXBlLmNsZWFyPUlkZSxlXy5wcm90b3R5cGUuZGVsZXRlPUxkZSxlXy5wcm90b3R5cGUuZ2V0PWtkZSxlXy5wcm90b3R5cGUuaGFzPVJkZSxlXy5wcm90b3R5cGUuc2V0PU5kZTtmdW5jdGlvbiBpcChtKXt2YXIgdj0tMSxUPW09PW51bGw/MDptLmxlbmd0aDtmb3IodGhpcy5jbGVhcigpOysrdjxUOyl7dmFyIE49bVt2XTt0aGlzLnNldChOWzBdLE5bMV0pfX1mdW5jdGlvbiBEZGUoKXt0aGlzLl9fZGF0YV9fPVtdLHRoaXMuc2l6ZT0wfWZ1bmN0aW9uIE9kZShtKXt2YXIgdj10aGlzLl9fZGF0YV9fLFQ9bkkodixtKTtpZihUPDApcmV0dXJuITE7dmFyIE49di5sZW5ndGgtMTtyZXR1cm4gVD09Tj92LnBvcCgpOks2LmNhbGwodixULDEpLC0tdGhpcy5zaXplLCEwfWZ1bmN0aW9uIHpkZShtKXt2YXIgdj10aGlzLl9fZGF0YV9fLFQ9bkkodixtKTtyZXR1cm4gVDwwP2U6dltUXVsxXX1mdW5jdGlvbiBGZGUobSl7cmV0dXJuIG5JKHRoaXMuX19kYXRhX18sbSk+LTF9ZnVuY3Rpb24gQmRlKG0sdil7dmFyIFQ9dGhpcy5fX2RhdGFfXyxOPW5JKFQsbSk7cmV0dXJuIE48MD8oKyt0aGlzLnNpemUsVC5wdXNoKFttLHZdKSk6VFtOXVsxXT12LHRoaXN9aXAucHJvdG90eXBlLmNsZWFyPURkZSxpcC5wcm90b3R5cGUuZGVsZXRlPU9kZSxpcC5wcm90b3R5cGUuZ2V0PXpkZSxpcC5wcm90b3R5cGUuaGFzPUZkZSxpcC5wcm90b3R5cGUuc2V0PUJkZTtmdW5jdGlvbiBvcChtKXt2YXIgdj0tMSxUPW09PW51bGw/MDptLmxlbmd0aDtmb3IodGhpcy5jbGVhcigpOysrdjxUOyl7dmFyIE49bVt2XTt0aGlzLnNldChOWzBdLE5bMV0pfX1mdW5jdGlvbiBIZGUoKXt0aGlzLnNpemU9MCx0aGlzLl9fZGF0YV9fPXtoYXNoOm5ldyBlXyxtYXA6bmV3KFRNfHxpcCksc3RyaW5nOm5ldyBlX319ZnVuY3Rpb24gVmRlKG0pe3ZhciB2PW1JKHRoaXMsbSkuZGVsZXRlKG0pO3JldHVybiB0aGlzLnNpemUtPXY/MTowLHZ9ZnVuY3Rpb24gVWRlKG0pe3JldHVybiBtSSh0aGlzLG0pLmdldChtKX1mdW5jdGlvbiBxZGUobSl7cmV0dXJuIG1JKHRoaXMsbSkuaGFzKG0pfWZ1bmN0aW9uIEdkZShtLHYpe3ZhciBUPW1JKHRoaXMsbSksTj1ULnNpemU7cmV0dXJuIFQuc2V0KG0sdiksdGhpcy5zaXplKz1ULnNpemU9PU4/MDoxLHRoaXN9b3AucHJvdG90eXBlLmNsZWFyPUhkZSxvcC5wcm90b3R5cGUuZGVsZXRlPVZkZSxvcC5wcm90b3R5cGUuZ2V0PVVkZSxvcC5wcm90b3R5cGUuaGFzPXFkZSxvcC5wcm90b3R5cGUuc2V0PUdkZTtmdW5jdGlvbiByXyhtKXt2YXIgdj0tMSxUPW09PW51bGw/MDptLmxlbmd0aDtmb3IodGhpcy5fX2RhdGFfXz1uZXcgb3A7Kyt2PFQ7KXRoaXMuYWRkKG1bdl0pfWZ1bmN0aW9uIFdkZShtKXtyZXR1cm4gdGhpcy5fX2RhdGFfXy5zZXQobSxhKSx0aGlzfWZ1bmN0aW9uIFlkZShtKXtyZXR1cm4gdGhpcy5fX2RhdGFfXy5oYXMobSl9cl8ucHJvdG90eXBlLmFkZD1yXy5wcm90b3R5cGUucHVzaD1XZGUscl8ucHJvdG90eXBlLmhhcz1ZZGU7ZnVuY3Rpb24gZXUobSl7dmFyIHY9dGhpcy5fX2RhdGFfXz1uZXcgaXAobSk7dGhpcy5zaXplPXYuc2l6ZX1mdW5jdGlvbiBqZGUoKXt0aGlzLl9fZGF0YV9fPW5ldyBpcCx0aGlzLnNpemU9MH1mdW5jdGlvbiBYZGUobSl7dmFyIHY9dGhpcy5fX2RhdGFfXyxUPXYuZGVsZXRlKG0pO3JldHVybiB0aGlzLnNpemU9di5zaXplLFR9ZnVuY3Rpb24gJGRlKG0pe3JldHVybiB0aGlzLl9fZGF0YV9fLmdldChtKX1mdW5jdGlvbiBLZGUobSl7cmV0dXJuIHRoaXMuX19kYXRhX18uaGFzKG0pfWZ1bmN0aW9uIFpkZShtLHYpe3ZhciBUPXRoaXMuX19kYXRhX187aWYoVCBpbnN0YW5jZW9mIGlwKXt2YXIgTj1ULl9fZGF0YV9fO2lmKCFUTXx8Ti5sZW5ndGg8ci0xKXJldHVybiBOLnB1c2goW20sdl0pLHRoaXMuc2l6ZT0rK1Quc2l6ZSx0aGlzO1Q9dGhpcy5fX2RhdGFfXz1uZXcgb3AoTil9cmV0dXJuIFQuc2V0KG0sdiksdGhpcy5zaXplPVQuc2l6ZSx0aGlzfWV1LnByb3RvdHlwZS5jbGVhcj1qZGUsZXUucHJvdG90eXBlLmRlbGV0ZT1YZGUsZXUucHJvdG90eXBlLmdldD0kZGUsZXUucHJvdG90eXBlLmhhcz1LZGUsZXUucHJvdG90eXBlLnNldD1aZGU7ZnVuY3Rpb24gSWZ0KG0sdil7dmFyIFQ9JGUobSksTj0hVCYmc18obSksVj0hVCYmIU4mJmJtKG0pLFk9IVQmJiFOJiYhViYmd3gobSksSj1UfHxOfHxWfHxZLGl0PUo/cHEobS5sZW5ndGgsc2RlKTpbXSxmdD1pdC5sZW5ndGg7Zm9yKHZhciBEdCBpbiBtKSh2fHx1bi5jYWxsKG0sRHQpKSYmIShKJiYoRHQ9PSJsZW5ndGgifHxWJiYoRHQ9PSJvZmZzZXQifHxEdD09InBhcmVudCIpfHxZJiYoRHQ9PSJidWZmZXIifHxEdD09ImJ5dGVMZW5ndGgifHxEdD09ImJ5dGVPZmZzZXQiKXx8Y3AoRHQsZnQpKSkmJml0LnB1c2goRHQpO3JldHVybiBpdH1mdW5jdGlvbiBMZnQobSl7dmFyIHY9bS5sZW5ndGg7cmV0dXJuIHY/bVtJcSgwLHYtMSldOmV9ZnVuY3Rpb24gSmRlKG0sdil7cmV0dXJuIGdJKHBzKG0pLG5fKHYsMCxtLmxlbmd0aCkpfWZ1bmN0aW9uIFFkZShtKXtyZXR1cm4gZ0kocHMobSkpfWZ1bmN0aW9uIHhxKG0sdixUKXsoVCE9PWUmJiFydShtW3ZdLFQpfHxUPT09ZSYmISh2IGluIG0pKSYmYXAobSx2LFQpfWZ1bmN0aW9uIElNKG0sdixUKXt2YXIgTj1tW3ZdOyghKHVuLmNhbGwobSx2KSYmcnUoTixUKSl8fFQ9PT1lJiYhKHYgaW4gbSkpJiZhcChtLHYsVCl9ZnVuY3Rpb24gbkkobSx2KXtmb3IodmFyIFQ9bS5sZW5ndGg7VC0tOylpZihydShtW1RdWzBdLHYpKXJldHVybiBUO3JldHVybi0xfWZ1bmN0aW9uIHRtZShtLHYsVCxOKXtyZXR1cm4gX20obSxmdW5jdGlvbihWLFksSil7dihOLFYsVChWKSxKKX0pLE59ZnVuY3Rpb24ga2Z0KG0sdil7cmV0dXJuIG0mJmdoKHYscG8odiksbSl9ZnVuY3Rpb24gZW1lKG0sdil7cmV0dXJuIG0mJmdoKHYsbXModiksbSl9ZnVuY3Rpb24gYXAobSx2LFQpe3Y9PSJfX3Byb3RvX18iJiZaNj9aNihtLHYse2NvbmZpZ3VyYWJsZTohMCxlbnVtZXJhYmxlOiEwLHZhbHVlOlQsd3JpdGFibGU6ITB9KTptW3ZdPVR9ZnVuY3Rpb24gYnEobSx2KXtmb3IodmFyIFQ9LTEsTj12Lmxlbmd0aCxWPXZ0KE4pLFk9bT09bnVsbDsrK1Q8TjspVltUXT1ZP2U6ZUcobSx2W1RdKTtyZXR1cm4gVn1mdW5jdGlvbiBuXyhtLHYsVCl7cmV0dXJuIG09PT1tJiYoVCE9PWUmJihtPW08PVQ/bTpUKSx2IT09ZSYmKG09bT49dj9tOnYpKSxtfWZ1bmN0aW9uIFlsKG0sdixULE4sVixZKXt2YXIgSixpdD12JmMsZnQ9diZ1LER0PXYmaDtpZihUJiYoSj1WP1QobSxOLFYsWSk6VChtKSksSiE9PWUpcmV0dXJuIEo7aWYoIXJpKG0pKXJldHVybiBtO3ZhciBPdD0kZShtKTtpZihPdCl7aWYoSj1xbWUobSksIWl0KXJldHVybiBwcyhtLEopfWVsc2V7dmFyIFZ0PW5hKG0pLG9lPVZ0PT1ldHx8VnQ9PWR0O2lmKGJtKG0pKXJldHVybiBycHQobSxpdCk7aWYoVnQ9PXd0fHxWdD09YnR8fG9lJiYhVil7aWYoSj1mdHx8b2U/e306YnB0KG0pLCFpdClyZXR1cm4gZnQ/a21lKG0sZW1lKEosbSkpOkxtZShtLGtmdChKLG0pKX1lbHNle2lmKCFUbltWdF0pcmV0dXJuIFY/bTp7fTtKPUdtZShtLFZ0LGl0KX19WXx8KFk9bmV3IGV1KTt2YXIgVGU9WS5nZXQobSk7aWYoVGUpcmV0dXJuIFRlO1kuc2V0KG0sSiksS3B0KG0pP20uZm9yRWFjaChmdW5jdGlvbihEZSl7Si5hZGQoWWwoRGUsdixULERlLG0sWSkpfSk6WHB0KG0pJiZtLmZvckVhY2goZnVuY3Rpb24oRGUscHIpe0ouc2V0KHByLFlsKERlLHYsVCxwcixtLFkpKX0pO3ZhciBOZT1EdD9mdD9WcTpIcTpmdD9tczpwbyxlcj1PdD9lOk5lKG0pO3JldHVybiBxbChlcnx8bSxmdW5jdGlvbihEZSxwcil7ZXImJihwcj1EZSxEZT1tW3ByXSksSU0oSixwcixZbChEZSx2LFQscHIsbSxZKSl9KSxKfWZ1bmN0aW9uIHJtZShtKXt2YXIgdj1wbyhtKTtyZXR1cm4gZnVuY3Rpb24oVCl7cmV0dXJuIFJmdChULG0sdil9fWZ1bmN0aW9uIFJmdChtLHYsVCl7dmFyIE49VC5sZW5ndGg7aWYobT09bnVsbClyZXR1cm4hTjtmb3IobT12bihtKTtOLS07KXt2YXIgVj1UW05dLFk9dltWXSxKPW1bVl07aWYoSj09PWUmJiEoViBpbiBtKXx8IVkoSikpcmV0dXJuITF9cmV0dXJuITB9ZnVuY3Rpb24gTmZ0KG0sdixUKXtpZih0eXBlb2YgbSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgR2woaSk7cmV0dXJuIHpNKGZ1bmN0aW9uKCl7bS5hcHBseShlLFQpfSx2KX1mdW5jdGlvbiBMTShtLHYsVCxOKXt2YXIgVj0tMSxZPUg2LEo9ITAsaXQ9bS5sZW5ndGgsZnQ9W10sRHQ9di5sZW5ndGg7aWYoIWl0KXJldHVybiBmdDtUJiYodj1Lbih2LFdzKFQpKSksTj8oWT1zcSxKPSExKTp2Lmxlbmd0aD49ciYmKFk9TU0sSj0hMSx2PW5ldyByXyh2KSk7dDpmb3IoOysrVjxpdDspe3ZhciBPdD1tW1ZdLFZ0PVQ9PW51bGw/T3Q6VChPdCk7aWYoT3Q9Tnx8T3QhPT0wP090OjAsSiYmVnQ9PT1WdCl7Zm9yKHZhciBvZT1EdDtvZS0tOylpZih2W29lXT09PVZ0KWNvbnRpbnVlIHQ7ZnQucHVzaChPdCl9ZWxzZSBZKHYsVnQsTil8fGZ0LnB1c2goT3QpfXJldHVybiBmdH12YXIgX209c3B0KG1oKSxEZnQ9c3B0KFNxLCEwKTtmdW5jdGlvbiBubWUobSx2KXt2YXIgVD0hMDtyZXR1cm4gX20obSxmdW5jdGlvbihOLFYsWSl7cmV0dXJuIFQ9ISF2KE4sVixZKSxUfSksVH1mdW5jdGlvbiBpSShtLHYsVCl7Zm9yKHZhciBOPS0xLFY9bS5sZW5ndGg7KytOPFY7KXt2YXIgWT1tW05dLEo9dihZKTtpZihKIT1udWxsJiYoaXQ9PT1lP0o9PT1KJiYhanMoSik6VChKLGl0KSkpdmFyIGl0PUosZnQ9WX1yZXR1cm4gZnR9ZnVuY3Rpb24gaW1lKG0sdixULE4pe3ZhciBWPW0ubGVuZ3RoO2ZvcihUPVFlKFQpLFQ8MCYmKFQ9LVQ+Vj8wOlYrVCksTj1OPT09ZXx8Tj5WP1Y6UWUoTiksTjwwJiYoTis9ViksTj1UPk4/MDpKcHQoTik7VDxOOyltW1QrK109djtyZXR1cm4gbX1mdW5jdGlvbiBPZnQobSx2KXt2YXIgVD1bXTtyZXR1cm4gX20obSxmdW5jdGlvbihOLFYsWSl7dihOLFYsWSkmJlQucHVzaChOKX0pLFR9ZnVuY3Rpb24gUm8obSx2LFQsTixWKXt2YXIgWT0tMSxKPW0ubGVuZ3RoO2ZvcihUfHwoVD1ZbWUpLFZ8fChWPVtdKTsrK1k8Sjspe3ZhciBpdD1tW1ldO3Y+MCYmVChpdCk/dj4xP1JvKGl0LHYtMSxULE4sVik6cG0oVixpdCk6Tnx8KFZbVi5sZW5ndGhdPWl0KX1yZXR1cm4gVn12YXIgd3E9bHB0KCksemZ0PWxwdCghMCk7ZnVuY3Rpb24gbWgobSx2KXtyZXR1cm4gbSYmd3EobSx2LHBvKX1mdW5jdGlvbiBTcShtLHYpe3JldHVybiBtJiZ6ZnQobSx2LHBvKX1mdW5jdGlvbiBvSShtLHYpe3JldHVybiBmbSh2LGZ1bmN0aW9uKFQpe3JldHVybiB1cChtW1RdKX0pfWZ1bmN0aW9uIGlfKG0sdil7dj12bSh2LG0pO2Zvcih2YXIgVD0wLE49di5sZW5ndGg7bSE9bnVsbCYmVDxOOyltPW1bX2godltUKytdKV07cmV0dXJuIFQmJlQ9PU4/bTplfWZ1bmN0aW9uIEZmdChtLHYsVCl7dmFyIE49dihtKTtyZXR1cm4gJGUobSk/TjpwbShOLFQobSkpfWZ1bmN0aW9uIExhKG0pe3JldHVybiBtPT1udWxsP209PT1lP3R0Omh0OnRfJiZ0XyBpbiB2bihtKT9IbWUobSk6UW1lKG0pfWZ1bmN0aW9uIE1xKG0sdil7cmV0dXJuIG0+dn1mdW5jdGlvbiBvbWUobSx2KXtyZXR1cm4gbSE9bnVsbCYmdW4uY2FsbChtLHYpfWZ1bmN0aW9uIGFtZShtLHYpe3JldHVybiBtIT1udWxsJiZ2IGluIHZuKG0pfWZ1bmN0aW9uIHNtZShtLHYsVCl7cmV0dXJuIG0+PXJhKHYsVCkmJm08WGkodixUKX1mdW5jdGlvbiBFcShtLHYsVCl7Zm9yKHZhciBOPVQ/c3E6SDYsVj1tWzBdLmxlbmd0aCxZPW0ubGVuZ3RoLEo9WSxpdD12dChZKSxmdD0xLzAsRHQ9W107Si0tOyl7dmFyIE90PW1bSl07SiYmdiYmKE90PUtuKE90LFdzKHYpKSksZnQ9cmEoT3QubGVuZ3RoLGZ0KSxpdFtKXT0hVCYmKHZ8fFY+PTEyMCYmT3QubGVuZ3RoPj0xMjApP25ldyByXyhKJiZPdCk6ZX1PdD1tWzBdO3ZhciBWdD0tMSxvZT1pdFswXTt0OmZvcig7KytWdDxWJiZEdC5sZW5ndGg8ZnQ7KXt2YXIgVGU9T3RbVnRdLE5lPXY/dihUZSk6VGU7aWYoVGU9VHx8VGUhPT0wP1RlOjAsIShvZT9NTShvZSxOZSk6TihEdCxOZSxUKSkpe2ZvcihKPVk7LS1KOyl7dmFyIGVyPWl0W0pdO2lmKCEoZXI/TU0oZXIsTmUpOk4obVtKXSxOZSxUKSkpY29udGludWUgdH1vZSYmb2UucHVzaChOZSksRHQucHVzaChUZSl9fXJldHVybiBEdH1mdW5jdGlvbiBsbWUobSx2LFQsTil7cmV0dXJuIG1oKG0sZnVuY3Rpb24oVixZLEope3YoTixUKFYpLFksSil9KSxOfWZ1bmN0aW9uIGtNKG0sdixUKXt2PXZtKHYsbSksbT1FcHQobSx2KTt2YXIgTj1tPT1udWxsP206bVtfaChYbCh2KSldO3JldHVybiBOPT1udWxsP2U6R3MoTixtLFQpfWZ1bmN0aW9uIEJmdChtKXtyZXR1cm4gbGkobSkmJkxhKG0pPT1idH1mdW5jdGlvbiBjbWUobSl7cmV0dXJuIGxpKG0pJiZMYShtKT09JHR9ZnVuY3Rpb24gdW1lKG0pe3JldHVybiBsaShtKSYmTGEobSk9PV90fWZ1bmN0aW9uIFJNKG0sdixULE4sVil7cmV0dXJuIG09PT12PyEwOm09PW51bGx8fHY9PW51bGx8fCFsaShtKSYmIWxpKHYpP20hPT1tJiZ2IT09djpobWUobSx2LFQsTixSTSxWKX1mdW5jdGlvbiBobWUobSx2LFQsTixWLFkpe3ZhciBKPSRlKG0pLGl0PSRlKHYpLGZ0PUo/TXQ6bmEobSksRHQ9aXQ/TXQ6bmEodik7ZnQ9ZnQ9PWJ0P3d0OmZ0LER0PUR0PT1idD93dDpEdDt2YXIgT3Q9ZnQ9PXd0LFZ0PUR0PT13dCxvZT1mdD09RHQ7aWYob2UmJmJtKG0pKXtpZighYm0odikpcmV0dXJuITE7Sj0hMCxPdD0hMX1pZihvZSYmIU90KXJldHVybiBZfHwoWT1uZXcgZXUpLEp8fHd4KG0pP3lwdChtLHYsVCxOLFYsWSk6Rm1lKG0sdixmdCxULE4sVixZKTtpZighKFQmZikpe3ZhciBUZT1PdCYmdW4uY2FsbChtLCJfX3dyYXBwZWRfXyIpLE5lPVZ0JiZ1bi5jYWxsKHYsIl9fd3JhcHBlZF9fIik7aWYoVGV8fE5lKXt2YXIgZXI9VGU/bS52YWx1ZSgpOm0sRGU9TmU/di52YWx1ZSgpOnY7cmV0dXJuIFl8fChZPW5ldyBldSksVihlcixEZSxULE4sWSl9fXJldHVybiBvZT8oWXx8KFk9bmV3IGV1KSxCbWUobSx2LFQsTixWLFkpKTohMX1mdW5jdGlvbiBmbWUobSl7cmV0dXJuIGxpKG0pJiZuYShtKT09cX1mdW5jdGlvbiBUcShtLHYsVCxOKXt2YXIgVj1ULmxlbmd0aCxZPVYsSj0hTjtpZihtPT1udWxsKXJldHVybiFZO2ZvcihtPXZuKG0pO1YtLTspe3ZhciBpdD1UW1ZdO2lmKEomJml0WzJdP2l0WzFdIT09bVtpdFswXV06IShpdFswXWluIG0pKXJldHVybiExfWZvcig7KytWPFk7KXtpdD1UW1ZdO3ZhciBmdD1pdFswXSxEdD1tW2Z0XSxPdD1pdFsxXTtpZihKJiZpdFsyXSl7aWYoRHQ9PT1lJiYhKGZ0IGluIG0pKXJldHVybiExfWVsc2V7dmFyIFZ0PW5ldyBldTtpZihOKXZhciBvZT1OKER0LE90LGZ0LG0sdixWdCk7aWYoIShvZT09PWU/Uk0oT3QsRHQsZnxwLE4sVnQpOm9lKSlyZXR1cm4hMX19cmV0dXJuITB9ZnVuY3Rpb24gSGZ0KG0pe2lmKCFyaShtKXx8WG1lKG0pKXJldHVybiExO3ZhciB2PXVwKG0pP2ZkZTpQcjtyZXR1cm4gdi50ZXN0KGFfKG0pKX1mdW5jdGlvbiBwbWUobSl7cmV0dXJuIGxpKG0pJiZMYShtKT09ZWV9ZnVuY3Rpb24gZG1lKG0pe3JldHVybiBsaShtKSYmbmEobSk9PUxlfWZ1bmN0aW9uIG1tZShtKXtyZXR1cm4gbGkobSkmJndJKG0ubGVuZ3RoKSYmISFSbltMYShtKV19ZnVuY3Rpb24gVmZ0KG0pe3JldHVybiB0eXBlb2YgbT09ImZ1bmN0aW9uIj9tOm09PW51bGw/Z3M6dHlwZW9mIG09PSJvYmplY3QiPyRlKG0pP0dmdChtWzBdLG1bMV0pOnFmdChtKTpjZHQobSl9ZnVuY3Rpb24gQ3EobSl7aWYoIU9NKG0pKXJldHVybiB5ZGUobSk7dmFyIHY9W107Zm9yKHZhciBUIGluIHZuKG0pKXVuLmNhbGwobSxUKSYmVCE9ImNvbnN0cnVjdG9yIiYmdi5wdXNoKFQpO3JldHVybiB2fWZ1bmN0aW9uIGdtZShtKXtpZighcmkobSkpcmV0dXJuIEptZShtKTt2YXIgdj1PTShtKSxUPVtdO2Zvcih2YXIgTiBpbiBtKU49PSJjb25zdHJ1Y3RvciImJih2fHwhdW4uY2FsbChtLE4pKXx8VC5wdXNoKE4pO3JldHVybiBUfWZ1bmN0aW9uIEFxKG0sdil7cmV0dXJuIG08dn1mdW5jdGlvbiBVZnQobSx2KXt2YXIgVD0tMSxOPWRzKG0pP3Z0KG0ubGVuZ3RoKTpbXTtyZXR1cm4gX20obSxmdW5jdGlvbihWLFksSil7TlsrK1RdPXYoVixZLEopfSksTn1mdW5jdGlvbiBxZnQobSl7dmFyIHY9cXEobSk7cmV0dXJuIHYubGVuZ3RoPT0xJiZ2WzBdWzJdP1NwdCh2WzBdWzBdLHZbMF1bMV0pOmZ1bmN0aW9uKFQpe3JldHVybiBUPT09bXx8VHEoVCxtLHYpfX1mdW5jdGlvbiBHZnQobSx2KXtyZXR1cm4gV3EobSkmJndwdCh2KT9TcHQoX2gobSksdik6ZnVuY3Rpb24oVCl7dmFyIE49ZUcoVCxtKTtyZXR1cm4gTj09PWUmJk49PT12P3JHKFQsbSk6Uk0odixOLGZ8cCl9fWZ1bmN0aW9uIGFJKG0sdixULE4sVil7bSE9PXYmJndxKHYsZnVuY3Rpb24oWSxKKXtpZihWfHwoVj1uZXcgZXUpLHJpKFkpKV9tZShtLHYsSixULGFJLE4sVik7ZWxzZXt2YXIgaXQ9Tj9OKGpxKG0sSiksWSxKKyIiLG0sdixWKTplO2l0PT09ZSYmKGl0PVkpLHhxKG0sSixpdCl9fSxtcyl9ZnVuY3Rpb24gX21lKG0sdixULE4sVixZLEope3ZhciBpdD1qcShtLFQpLGZ0PWpxKHYsVCksRHQ9Si5nZXQoZnQpO2lmKER0KXt4cShtLFQsRHQpO3JldHVybn12YXIgT3Q9WT9ZKGl0LGZ0LFQrIiIsbSx2LEopOmUsVnQ9T3Q9PT1lO2lmKFZ0KXt2YXIgb2U9JGUoZnQpLFRlPSFvZSYmYm0oZnQpLE5lPSFvZSYmIVRlJiZ3eChmdCk7T3Q9ZnQsb2V8fFRlfHxOZT8kZShpdCk/T3Q9aXQ6YmkoaXQpP090PXBzKGl0KTpUZT8oVnQ9ITEsT3Q9cnB0KGZ0LCEwKSk6TmU/KFZ0PSExLE90PW5wdChmdCwhMCkpOk90PVtdOkZNKGZ0KXx8c18oZnQpPyhPdD1pdCxzXyhpdCk/T3Q9UXB0KGl0KTooIXJpKGl0KXx8dXAoaXQpKSYmKE90PWJwdChmdCkpKTpWdD0hMX1WdCYmKEouc2V0KGZ0LE90KSxWKE90LGZ0LE4sWSxKKSxKLmRlbGV0ZShmdCkpLHhxKG0sVCxPdCl9ZnVuY3Rpb24gV2Z0KG0sdil7dmFyIFQ9bS5sZW5ndGg7aWYoISFUKXJldHVybiB2Kz12PDA/VDowLGNwKHYsVCk/bVt2XTplfWZ1bmN0aW9uIFlmdChtLHYsVCl7di5sZW5ndGg/dj1Lbih2LGZ1bmN0aW9uKFkpe3JldHVybiAkZShZKT9mdW5jdGlvbihKKXtyZXR1cm4gaV8oSixZLmxlbmd0aD09PTE/WVswXTpZKX06WX0pOnY9W2dzXTt2YXIgTj0tMTt2PUtuKHYsV3Moa2UoKSkpO3ZhciBWPVVmdChtLGZ1bmN0aW9uKFksSixpdCl7dmFyIGZ0PUtuKHYsZnVuY3Rpb24oRHQpe3JldHVybiBEdChZKX0pO3JldHVybntjcml0ZXJpYTpmdCxpbmRleDorK04sdmFsdWU6WX19KTtyZXR1cm4gR3BlKFYsZnVuY3Rpb24oWSxKKXtyZXR1cm4gSW1lKFksSixUKX0pfWZ1bmN0aW9uIHltZShtLHYpe3JldHVybiBqZnQobSx2LGZ1bmN0aW9uKFQsTil7cmV0dXJuIHJHKG0sTil9KX1mdW5jdGlvbiBqZnQobSx2LFQpe2Zvcih2YXIgTj0tMSxWPXYubGVuZ3RoLFk9e307KytOPFY7KXt2YXIgSj12W05dLGl0PWlfKG0sSik7VChpdCxKKSYmTk0oWSx2bShKLG0pLGl0KX1yZXR1cm4gWX1mdW5jdGlvbiB2bWUobSl7cmV0dXJuIGZ1bmN0aW9uKHYpe3JldHVybiBpXyh2LG0pfX1mdW5jdGlvbiBQcShtLHYsVCxOKXt2YXIgVj1OP3FwZTpmeCxZPS0xLEo9di5sZW5ndGgsaXQ9bTtmb3IobT09PXYmJih2PXBzKHYpKSxUJiYoaXQ9S24obSxXcyhUKSkpOysrWTxKOylmb3IodmFyIGZ0PTAsRHQ9dltZXSxPdD1UP1QoRHQpOkR0OyhmdD1WKGl0LE90LGZ0LE4pKT4tMTspaXQhPT1tJiZLNi5jYWxsKGl0LGZ0LDEpLEs2LmNhbGwobSxmdCwxKTtyZXR1cm4gbX1mdW5jdGlvbiBYZnQobSx2KXtmb3IodmFyIFQ9bT92Lmxlbmd0aDowLE49VC0xO1QtLTspe3ZhciBWPXZbVF07aWYoVD09Tnx8ViE9PVkpe3ZhciBZPVY7Y3AoVik/SzYuY2FsbChtLFYsMSk6UnEobSxWKX19cmV0dXJuIG19ZnVuY3Rpb24gSXEobSx2KXtyZXR1cm4gbStRNihBZnQoKSoodi1tKzEpKX1mdW5jdGlvbiB4bWUobSx2LFQsTil7Zm9yKHZhciBWPS0xLFk9WGkoSjYoKHYtbSkvKFR8fDEpKSwwKSxKPXZ0KFkpO1ktLTspSltOP1k6KytWXT1tLG0rPVQ7cmV0dXJuIEp9ZnVuY3Rpb24gTHEobSx2KXt2YXIgVD0iIjtpZighbXx8djwxfHx2PlUpcmV0dXJuIFQ7ZG8gdiUyJiYoVCs9bSksdj1RNih2LzIpLHYmJihtKz1tKTt3aGlsZSh2KTtyZXR1cm4gVH1mdW5jdGlvbiBucihtLHYpe3JldHVybiBYcShNcHQobSx2LGdzKSxtKyIiKX1mdW5jdGlvbiBibWUobSl7cmV0dXJuIExmdChTeChtKSl9ZnVuY3Rpb24gd21lKG0sdil7dmFyIFQ9U3gobSk7cmV0dXJuIGdJKFQsbl8odiwwLFQubGVuZ3RoKSl9ZnVuY3Rpb24gTk0obSx2LFQsTil7aWYoIXJpKG0pKXJldHVybiBtO3Y9dm0odixtKTtmb3IodmFyIFY9LTEsWT12Lmxlbmd0aCxKPVktMSxpdD1tO2l0IT1udWxsJiYrK1Y8WTspe3ZhciBmdD1faCh2W1ZdKSxEdD1UO2lmKGZ0PT09Il9fcHJvdG9fXyJ8fGZ0PT09ImNvbnN0cnVjdG9yInx8ZnQ9PT0icHJvdG90eXBlIilyZXR1cm4gbTtpZihWIT1KKXt2YXIgT3Q9aXRbZnRdO0R0PU4/TihPdCxmdCxpdCk6ZSxEdD09PWUmJihEdD1yaShPdCk/T3Q6Y3AodltWKzFdKT9bXTp7fSl9SU0oaXQsZnQsRHQpLGl0PWl0W2Z0XX1yZXR1cm4gbX12YXIgJGZ0PXRJP2Z1bmN0aW9uKG0sdil7cmV0dXJuIHRJLnNldChtLHYpLG19OmdzLFNtZT1aNj9mdW5jdGlvbihtLHYpe3JldHVybiBaNihtLCJ0b1N0cmluZyIse2NvbmZpZ3VyYWJsZTohMCxlbnVtZXJhYmxlOiExLHZhbHVlOmlHKHYpLHdyaXRhYmxlOiEwfSl9OmdzO2Z1bmN0aW9uIE1tZShtKXtyZXR1cm4gZ0koU3gobSkpfWZ1bmN0aW9uIGpsKG0sdixUKXt2YXIgTj0tMSxWPW0ubGVuZ3RoO3Y8MCYmKHY9LXY+Vj8wOlYrdiksVD1UPlY/VjpULFQ8MCYmKFQrPVYpLFY9dj5UPzA6VC12Pj4+MCx2Pj4+PTA7Zm9yKHZhciBZPXZ0KFYpOysrTjxWOylZW05dPW1bTit2XTtyZXR1cm4gWX1mdW5jdGlvbiBFbWUobSx2KXt2YXIgVDtyZXR1cm4gX20obSxmdW5jdGlvbihOLFYsWSl7cmV0dXJuIFQ9dihOLFYsWSksIVR9KSwhIVR9ZnVuY3Rpb24gc0kobSx2LFQpe3ZhciBOPTAsVj1tPT1udWxsP046bS5sZW5ndGg7aWYodHlwZW9mIHY9PSJudW1iZXIiJiZ2PT09diYmVjw9c3Qpe2Zvcig7TjxWOyl7dmFyIFk9TitWPj4+MSxKPW1bWV07SiE9PW51bGwmJiFqcyhKKSYmKFQ/Sjw9djpKPHYpP049WSsxOlY9WX1yZXR1cm4gVn1yZXR1cm4ga3EobSx2LGdzLFQpfWZ1bmN0aW9uIGtxKG0sdixULE4pe3ZhciBWPTAsWT1tPT1udWxsPzA6bS5sZW5ndGg7aWYoWT09PTApcmV0dXJuIDA7dj1UKHYpO2Zvcih2YXIgSj12IT09dixpdD12PT09bnVsbCxmdD1qcyh2KSxEdD12PT09ZTtWPFk7KXt2YXIgT3Q9UTYoKFYrWSkvMiksVnQ9VChtW090XSksb2U9VnQhPT1lLFRlPVZ0PT09bnVsbCxOZT1WdD09PVZ0LGVyPWpzKFZ0KTtpZihKKXZhciBEZT1OfHxOZTtlbHNlIER0P0RlPU5lJiYoTnx8b2UpOml0P0RlPU5lJiZvZSYmKE58fCFUZSk6ZnQ/RGU9TmUmJm9lJiYhVGUmJihOfHwhZXIpOlRlfHxlcj9EZT0hMTpEZT1OP1Z0PD12OlZ0PHY7RGU/Vj1PdCsxOlk9T3R9cmV0dXJuIHJhKFksb3QpfWZ1bmN0aW9uIEtmdChtLHYpe2Zvcih2YXIgVD0tMSxOPW0ubGVuZ3RoLFY9MCxZPVtdOysrVDxOOyl7dmFyIEo9bVtUXSxpdD12P3YoSik6SjtpZighVHx8IXJ1KGl0LGZ0KSl7dmFyIGZ0PWl0O1lbVisrXT1KPT09MD8wOkp9fXJldHVybiBZfWZ1bmN0aW9uIFpmdChtKXtyZXR1cm4gdHlwZW9mIG09PSJudW1iZXIiP206anMobSk/WjorbX1mdW5jdGlvbiBZcyhtKXtpZih0eXBlb2YgbT09InN0cmluZyIpcmV0dXJuIG07aWYoJGUobSkpcmV0dXJuIEtuKG0sWXMpKyIiO2lmKGpzKG0pKXJldHVybiBQZnQ/UGZ0LmNhbGwobSk6IiI7dmFyIHY9bSsiIjtyZXR1cm4gdj09IjAiJiYxL209PS16PyItMCI6dn1mdW5jdGlvbiB5bShtLHYsVCl7dmFyIE49LTEsVj1INixZPW0ubGVuZ3RoLEo9ITAsaXQ9W10sZnQ9aXQ7aWYoVClKPSExLFY9c3E7ZWxzZSBpZihZPj1yKXt2YXIgRHQ9dj9udWxsOk9tZShtKTtpZihEdClyZXR1cm4gVTYoRHQpO0o9ITEsVj1NTSxmdD1uZXcgcl99ZWxzZSBmdD12P1tdOml0O3Q6Zm9yKDsrK048WTspe3ZhciBPdD1tW05dLFZ0PXY/dihPdCk6T3Q7aWYoT3Q9VHx8T3QhPT0wP090OjAsSiYmVnQ9PT1WdCl7Zm9yKHZhciBvZT1mdC5sZW5ndGg7b2UtLTspaWYoZnRbb2VdPT09VnQpY29udGludWUgdDt2JiZmdC5wdXNoKFZ0KSxpdC5wdXNoKE90KX1lbHNlIFYoZnQsVnQsVCl8fChmdCE9PWl0JiZmdC5wdXNoKFZ0KSxpdC5wdXNoKE90KSl9cmV0dXJuIGl0fWZ1bmN0aW9uIFJxKG0sdil7cmV0dXJuIHY9dm0odixtKSxtPUVwdChtLHYpLG09PW51bGx8fGRlbGV0ZSBtW19oKFhsKHYpKV19ZnVuY3Rpb24gSmZ0KG0sdixULE4pe3JldHVybiBOTShtLHYsVChpXyhtLHYpKSxOKX1mdW5jdGlvbiBsSShtLHYsVCxOKXtmb3IodmFyIFY9bS5sZW5ndGgsWT1OP1Y6LTE7KE4/WS0tOisrWTxWKSYmdihtW1ldLFksbSk7KTtyZXR1cm4gVD9qbChtLE4/MDpZLE4/WSsxOlYpOmpsKG0sTj9ZKzE6MCxOP1Y6WSl9ZnVuY3Rpb24gUWZ0KG0sdil7dmFyIFQ9bTtyZXR1cm4gVCBpbnN0YW5jZW9mIGdyJiYoVD1ULnZhbHVlKCkpLGxxKHYsZnVuY3Rpb24oTixWKXtyZXR1cm4gVi5mdW5jLmFwcGx5KFYudGhpc0FyZyxwbShbTl0sVi5hcmdzKSl9LFQpfWZ1bmN0aW9uIE5xKG0sdixUKXt2YXIgTj1tLmxlbmd0aDtpZihOPDIpcmV0dXJuIE4/eW0obVswXSk6W107Zm9yKHZhciBWPS0xLFk9dnQoTik7KytWPE47KWZvcih2YXIgSj1tW1ZdLGl0PS0xOysraXQ8TjspaXQhPVYmJihZW1ZdPUxNKFlbVl18fEosbVtpdF0sdixUKSk7cmV0dXJuIHltKFJvKFksMSksdixUKX1mdW5jdGlvbiB0cHQobSx2LFQpe2Zvcih2YXIgTj0tMSxWPW0ubGVuZ3RoLFk9di5sZW5ndGgsSj17fTsrK048Vjspe3ZhciBpdD1OPFk/dltOXTplO1QoSixtW05dLGl0KX1yZXR1cm4gSn1mdW5jdGlvbiBEcShtKXtyZXR1cm4gYmkobSk/bTpbXX1mdW5jdGlvbiBPcShtKXtyZXR1cm4gdHlwZW9mIG09PSJmdW5jdGlvbiI/bTpnc31mdW5jdGlvbiB2bShtLHYpe3JldHVybiAkZShtKT9tOldxKG0sdik/W21dOlBwdChvbihtKSl9dmFyIFRtZT1ucjtmdW5jdGlvbiB4bShtLHYsVCl7dmFyIE49bS5sZW5ndGg7cmV0dXJuIFQ9VD09PWU/TjpULCF2JiZUPj1OP206amwobSx2LFQpfXZhciBlcHQ9cGRlfHxmdW5jdGlvbihtKXtyZXR1cm4gZm8uY2xlYXJUaW1lb3V0KG0pfTtmdW5jdGlvbiBycHQobSx2KXtpZih2KXJldHVybiBtLnNsaWNlKCk7dmFyIFQ9bS5sZW5ndGgsTj1TZnQ/U2Z0KFQpOm5ldyBtLmNvbnN0cnVjdG9yKFQpO3JldHVybiBtLmNvcHkoTiksTn1mdW5jdGlvbiB6cShtKXt2YXIgdj1uZXcgbS5jb25zdHJ1Y3RvcihtLmJ5dGVMZW5ndGgpO3JldHVybiBuZXcgWDYodikuc2V0KG5ldyBYNihtKSksdn1mdW5jdGlvbiBDbWUobSx2KXt2YXIgVD12P3pxKG0uYnVmZmVyKTptLmJ1ZmZlcjtyZXR1cm4gbmV3IG0uY29uc3RydWN0b3IoVCxtLmJ5dGVPZmZzZXQsbS5ieXRlTGVuZ3RoKX1mdW5jdGlvbiBBbWUobSl7dmFyIHY9bmV3IG0uY29uc3RydWN0b3IobS5zb3VyY2UsS2UuZXhlYyhtKSk7cmV0dXJuIHYubGFzdEluZGV4PW0ubGFzdEluZGV4LHZ9ZnVuY3Rpb24gUG1lKG0pe3JldHVybiBQTT92bihQTS5jYWxsKG0pKTp7fX1mdW5jdGlvbiBucHQobSx2KXt2YXIgVD12P3pxKG0uYnVmZmVyKTptLmJ1ZmZlcjtyZXR1cm4gbmV3IG0uY29uc3RydWN0b3IoVCxtLmJ5dGVPZmZzZXQsbS5sZW5ndGgpfWZ1bmN0aW9uIGlwdChtLHYpe2lmKG0hPT12KXt2YXIgVD1tIT09ZSxOPW09PT1udWxsLFY9bT09PW0sWT1qcyhtKSxKPXYhPT1lLGl0PXY9PT1udWxsLGZ0PXY9PT12LER0PWpzKHYpO2lmKCFpdCYmIUR0JiYhWSYmbT52fHxZJiZKJiZmdCYmIWl0JiYhRHR8fE4mJkomJmZ0fHwhVCYmZnR8fCFWKXJldHVybiAxO2lmKCFOJiYhWSYmIUR0JiZtPHZ8fER0JiZUJiZWJiYhTiYmIVl8fGl0JiZUJiZWfHwhSiYmVnx8IWZ0KXJldHVybi0xfXJldHVybiAwfWZ1bmN0aW9uIEltZShtLHYsVCl7Zm9yKHZhciBOPS0xLFY9bS5jcml0ZXJpYSxZPXYuY3JpdGVyaWEsSj1WLmxlbmd0aCxpdD1ULmxlbmd0aDsrK048Sjspe3ZhciBmdD1pcHQoVltOXSxZW05dKTtpZihmdCl7aWYoTj49aXQpcmV0dXJuIGZ0O3ZhciBEdD1UW05dO3JldHVybiBmdCooRHQ9PSJkZXNjIj8tMToxKX19cmV0dXJuIG0uaW5kZXgtdi5pbmRleH1mdW5jdGlvbiBvcHQobSx2LFQsTil7Zm9yKHZhciBWPS0xLFk9bS5sZW5ndGgsSj1ULmxlbmd0aCxpdD0tMSxmdD12Lmxlbmd0aCxEdD1YaShZLUosMCksT3Q9dnQoZnQrRHQpLFZ0PSFOOysraXQ8ZnQ7KU90W2l0XT12W2l0XTtmb3IoOysrVjxKOykoVnR8fFY8WSkmJihPdFtUW1ZdXT1tW1ZdKTtmb3IoO0R0LS07KU90W2l0KytdPW1bVisrXTtyZXR1cm4gT3R9ZnVuY3Rpb24gYXB0KG0sdixULE4pe2Zvcih2YXIgVj0tMSxZPW0ubGVuZ3RoLEo9LTEsaXQ9VC5sZW5ndGgsZnQ9LTEsRHQ9di5sZW5ndGgsT3Q9WGkoWS1pdCwwKSxWdD12dChPdCtEdCksb2U9IU47KytWPE90OylWdFtWXT1tW1ZdO2Zvcih2YXIgVGU9VjsrK2Z0PER0OylWdFtUZStmdF09dltmdF07Zm9yKDsrK0o8aXQ7KShvZXx8VjxZKSYmKFZ0W1RlK1RbSl1dPW1bVisrXSk7cmV0dXJuIFZ0fWZ1bmN0aW9uIHBzKG0sdil7dmFyIFQ9LTEsTj1tLmxlbmd0aDtmb3Iodnx8KHY9dnQoTikpOysrVDxOOyl2W1RdPW1bVF07cmV0dXJuIHZ9ZnVuY3Rpb24gZ2gobSx2LFQsTil7dmFyIFY9IVQ7VHx8KFQ9e30pO2Zvcih2YXIgWT0tMSxKPXYubGVuZ3RoOysrWTxKOyl7dmFyIGl0PXZbWV0sZnQ9Tj9OKFRbaXRdLG1baXRdLGl0LFQsbSk6ZTtmdD09PWUmJihmdD1tW2l0XSksVj9hcChULGl0LGZ0KTpJTShULGl0LGZ0KX1yZXR1cm4gVH1mdW5jdGlvbiBMbWUobSx2KXtyZXR1cm4gZ2gobSxHcShtKSx2KX1mdW5jdGlvbiBrbWUobSx2KXtyZXR1cm4gZ2gobSx2cHQobSksdil9ZnVuY3Rpb24gY0kobSx2KXtyZXR1cm4gZnVuY3Rpb24oVCxOKXt2YXIgVj0kZShUKT96cGU6dG1lLFk9dj92KCk6e307cmV0dXJuIFYoVCxtLGtlKE4sMiksWSl9fWZ1bmN0aW9uIHZ4KG0pe3JldHVybiBucihmdW5jdGlvbih2LFQpe3ZhciBOPS0xLFY9VC5sZW5ndGgsWT1WPjE/VFtWLTFdOmUsSj1WPjI/VFsyXTplO2ZvcihZPW0ubGVuZ3RoPjMmJnR5cGVvZiBZPT0iZnVuY3Rpb24iPyhWLS0sWSk6ZSxKJiZrYShUWzBdLFRbMV0sSikmJihZPVY8Mz9lOlksVj0xKSx2PXZuKHYpOysrTjxWOyl7dmFyIGl0PVRbTl07aXQmJm0odixpdCxOLFkpfXJldHVybiB2fSl9ZnVuY3Rpb24gc3B0KG0sdil7cmV0dXJuIGZ1bmN0aW9uKFQsTil7aWYoVD09bnVsbClyZXR1cm4gVDtpZighZHMoVCkpcmV0dXJuIG0oVCxOKTtmb3IodmFyIFY9VC5sZW5ndGgsWT12P1Y6LTEsSj12bihUKTsodj9ZLS06KytZPFYpJiZOKEpbWV0sWSxKKSE9PSExOyk7cmV0dXJuIFR9fWZ1bmN0aW9uIGxwdChtKXtyZXR1cm4gZnVuY3Rpb24odixULE4pe2Zvcih2YXIgVj0tMSxZPXZuKHYpLEo9Tih2KSxpdD1KLmxlbmd0aDtpdC0tOyl7dmFyIGZ0PUpbbT9pdDorK1ZdO2lmKFQoWVtmdF0sZnQsWSk9PT0hMSlicmVha31yZXR1cm4gdn19ZnVuY3Rpb24gUm1lKG0sdixUKXt2YXIgTj12JmQsVj1ETShtKTtmdW5jdGlvbiBZKCl7dmFyIEo9dGhpcyYmdGhpcyE9PWZvJiZ0aGlzIGluc3RhbmNlb2YgWT9WOm07cmV0dXJuIEouYXBwbHkoTj9UOnRoaXMsYXJndW1lbnRzKX1yZXR1cm4gWX1mdW5jdGlvbiBjcHQobSl7cmV0dXJuIGZ1bmN0aW9uKHYpe3Y9b24odik7dmFyIFQ9cHgodik/dHUodik6ZSxOPVQ/VFswXTp2LmNoYXJBdCgwKSxWPVQ/eG0oVCwxKS5qb2luKCIiKTp2LnNsaWNlKDEpO3JldHVybiBOW21dKCkrVn19ZnVuY3Rpb24geHgobSl7cmV0dXJuIGZ1bmN0aW9uKHYpe3JldHVybiBscShzZHQoYWR0KHYpLnJlcGxhY2UoU3BlLCIiKSksbSwiIil9fWZ1bmN0aW9uIERNKG0pe3JldHVybiBmdW5jdGlvbigpe3ZhciB2PWFyZ3VtZW50cztzd2l0Y2godi5sZW5ndGgpe2Nhc2UgMDpyZXR1cm4gbmV3IG07Y2FzZSAxOnJldHVybiBuZXcgbSh2WzBdKTtjYXNlIDI6cmV0dXJuIG5ldyBtKHZbMF0sdlsxXSk7Y2FzZSAzOnJldHVybiBuZXcgbSh2WzBdLHZbMV0sdlsyXSk7Y2FzZSA0OnJldHVybiBuZXcgbSh2WzBdLHZbMV0sdlsyXSx2WzNdKTtjYXNlIDU6cmV0dXJuIG5ldyBtKHZbMF0sdlsxXSx2WzJdLHZbM10sdls0XSk7Y2FzZSA2OnJldHVybiBuZXcgbSh2WzBdLHZbMV0sdlsyXSx2WzNdLHZbNF0sdls1XSk7Y2FzZSA3OnJldHVybiBuZXcgbSh2WzBdLHZbMV0sdlsyXSx2WzNdLHZbNF0sdls1XSx2WzZdKX12YXIgVD15eChtLnByb3RvdHlwZSksTj1tLmFwcGx5KFQsdik7cmV0dXJuIHJpKE4pP046VH19ZnVuY3Rpb24gTm1lKG0sdixUKXt2YXIgTj1ETShtKTtmdW5jdGlvbiBWKCl7Zm9yKHZhciBZPWFyZ3VtZW50cy5sZW5ndGgsSj12dChZKSxpdD1ZLGZ0PWJ4KFYpO2l0LS07KUpbaXRdPWFyZ3VtZW50c1tpdF07dmFyIER0PVk8MyYmSlswXSE9PWZ0JiZKW1ktMV0hPT1mdD9bXTpkbShKLGZ0KTtpZihZLT1EdC5sZW5ndGgsWTxUKXJldHVybiBkcHQobSx2LHVJLFYucGxhY2Vob2xkZXIsZSxKLER0LGUsZSxULVkpO3ZhciBPdD10aGlzJiZ0aGlzIT09Zm8mJnRoaXMgaW5zdGFuY2VvZiBWP046bTtyZXR1cm4gR3MoT3QsdGhpcyxKKX1yZXR1cm4gVn1mdW5jdGlvbiB1cHQobSl7cmV0dXJuIGZ1bmN0aW9uKHYsVCxOKXt2YXIgVj12bih2KTtpZighZHModikpe3ZhciBZPWtlKFQsMyk7dj1wbyh2KSxUPWZ1bmN0aW9uKGl0KXtyZXR1cm4gWShWW2l0XSxpdCxWKX19dmFyIEo9bSh2LFQsTik7cmV0dXJuIEo+LTE/VltZP3ZbSl06Sl06ZX19ZnVuY3Rpb24gaHB0KG0pe3JldHVybiBscChmdW5jdGlvbih2KXt2YXIgVD12Lmxlbmd0aCxOPVQsVj1XbC5wcm90b3R5cGUudGhydTtmb3IobSYmdi5yZXZlcnNlKCk7Ti0tOyl7dmFyIFk9dltOXTtpZih0eXBlb2YgWSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgR2woaSk7aWYoViYmIUomJmRJKFkpPT0id3JhcHBlciIpdmFyIEo9bmV3IFdsKFtdLCEwKX1mb3IoTj1KP046VDsrK048VDspe1k9dltOXTt2YXIgaXQ9ZEkoWSksZnQ9aXQ9PSJ3cmFwcGVyIj9VcShZKTplO2Z0JiZZcShmdFswXSkmJmZ0WzFdPT0oQ3x5fGJ8UCkmJiFmdFs0XS5sZW5ndGgmJmZ0WzldPT0xP0o9SltkSShmdFswXSldLmFwcGx5KEosZnRbM10pOko9WS5sZW5ndGg9PTEmJllxKFkpP0pbaXRdKCk6Si50aHJ1KFkpfXJldHVybiBmdW5jdGlvbigpe3ZhciBEdD1hcmd1bWVudHMsT3Q9RHRbMF07aWYoSiYmRHQubGVuZ3RoPT0xJiYkZShPdCkpcmV0dXJuIEoucGxhbnQoT3QpLnZhbHVlKCk7Zm9yKHZhciBWdD0wLG9lPVQ/dltWdF0uYXBwbHkodGhpcyxEdCk6T3Q7KytWdDxUOylvZT12W1Z0XS5jYWxsKHRoaXMsb2UpO3JldHVybiBvZX19KX1mdW5jdGlvbiB1SShtLHYsVCxOLFYsWSxKLGl0LGZ0LER0KXt2YXIgT3Q9diZDLFZ0PXYmZCxvZT12JmcsVGU9diYoeXx4KSxOZT12JmssZXI9b2U/ZTpETShtKTtmdW5jdGlvbiBEZSgpe2Zvcih2YXIgcHI9YXJndW1lbnRzLmxlbmd0aCx5cj12dChwciksWHM9cHI7WHMtLTspeXJbWHNdPWFyZ3VtZW50c1tYc107aWYoVGUpdmFyIFJhPWJ4KERlKSwkcz1ZcGUoeXIsUmEpO2lmKE4mJih5cj1vcHQoeXIsTixWLFRlKSksWSYmKHlyPWFwdCh5cixZLEosVGUpKSxwci09JHMsVGUmJnByPER0KXt2YXIgd2k9ZG0oeXIsUmEpO3JldHVybiBkcHQobSx2LHVJLERlLnBsYWNlaG9sZGVyLFQseXIsd2ksaXQsZnQsRHQtcHIpfXZhciBudT1WdD9UOnRoaXMsZnA9b2U/bnVbbV06bTtyZXR1cm4gcHI9eXIubGVuZ3RoLGl0P3lyPXRnZSh5cixpdCk6TmUmJnByPjEmJnlyLnJldmVyc2UoKSxPdCYmZnQ8cHImJih5ci5sZW5ndGg9ZnQpLHRoaXMmJnRoaXMhPT1mbyYmdGhpcyBpbnN0YW5jZW9mIERlJiYoZnA9ZXJ8fERNKGZwKSksZnAuYXBwbHkobnUseXIpfXJldHVybiBEZX1mdW5jdGlvbiBmcHQobSx2KXtyZXR1cm4gZnVuY3Rpb24oVCxOKXtyZXR1cm4gbG1lKFQsbSx2KE4pLHt9KX19ZnVuY3Rpb24gaEkobSx2KXtyZXR1cm4gZnVuY3Rpb24oVCxOKXt2YXIgVjtpZihUPT09ZSYmTj09PWUpcmV0dXJuIHY7aWYoVCE9PWUmJihWPVQpLE4hPT1lKXtpZihWPT09ZSlyZXR1cm4gTjt0eXBlb2YgVD09InN0cmluZyJ8fHR5cGVvZiBOPT0ic3RyaW5nIj8oVD1ZcyhUKSxOPVlzKE4pKTooVD1aZnQoVCksTj1aZnQoTikpLFY9bShULE4pfXJldHVybiBWfX1mdW5jdGlvbiBGcShtKXtyZXR1cm4gbHAoZnVuY3Rpb24odil7cmV0dXJuIHY9S24odixXcyhrZSgpKSksbnIoZnVuY3Rpb24oVCl7dmFyIE49dGhpcztyZXR1cm4gbSh2LGZ1bmN0aW9uKFYpe3JldHVybiBHcyhWLE4sVCl9KX0pfSl9ZnVuY3Rpb24gZkkobSx2KXt2PXY9PT1lPyIgIjpZcyh2KTt2YXIgVD12Lmxlbmd0aDtpZihUPDIpcmV0dXJuIFQ/THEodixtKTp2O3ZhciBOPUxxKHYsSjYobS9keCh2KSkpO3JldHVybiBweCh2KT94bSh0dShOKSwwLG0pLmpvaW4oIiIpOk4uc2xpY2UoMCxtKX1mdW5jdGlvbiBEbWUobSx2LFQsTil7dmFyIFY9diZkLFk9RE0obSk7ZnVuY3Rpb24gSigpe2Zvcih2YXIgaXQ9LTEsZnQ9YXJndW1lbnRzLmxlbmd0aCxEdD0tMSxPdD1OLmxlbmd0aCxWdD12dChPdCtmdCksb2U9dGhpcyYmdGhpcyE9PWZvJiZ0aGlzIGluc3RhbmNlb2YgSj9ZOm07KytEdDxPdDspVnRbRHRdPU5bRHRdO2Zvcig7ZnQtLTspVnRbRHQrK109YXJndW1lbnRzWysraXRdO3JldHVybiBHcyhvZSxWP1Q6dGhpcyxWdCl9cmV0dXJuIEp9ZnVuY3Rpb24gcHB0KG0pe3JldHVybiBmdW5jdGlvbih2LFQsTil7cmV0dXJuIE4mJnR5cGVvZiBOIT0ibnVtYmVyIiYma2EodixULE4pJiYoVD1OPWUpLHY9aHAodiksVD09PWU/KFQ9dix2PTApOlQ9aHAoVCksTj1OPT09ZT92PFQ/MTotMTpocChOKSx4bWUodixULE4sbSl9fWZ1bmN0aW9uIHBJKG0pe3JldHVybiBmdW5jdGlvbih2LFQpe3JldHVybiB0eXBlb2Ygdj09InN0cmluZyImJnR5cGVvZiBUPT0ic3RyaW5nInx8KHY9JGwodiksVD0kbChUKSksbSh2LFQpfX1mdW5jdGlvbiBkcHQobSx2LFQsTixWLFksSixpdCxmdCxEdCl7dmFyIE90PXYmeSxWdD1PdD9KOmUsb2U9T3Q/ZTpKLFRlPU90P1k6ZSxOZT1PdD9lOlk7dnw9T3Q/YjpTLHYmPX4oT3Q/UzpiKSx2Jl98fCh2Jj1+KGR8ZykpO3ZhciBlcj1bbSx2LFYsVGUsVnQsTmUsb2UsaXQsZnQsRHRdLERlPVQuYXBwbHkoZSxlcik7cmV0dXJuIFlxKG0pJiZUcHQoRGUsZXIpLERlLnBsYWNlaG9sZGVyPU4sQ3B0KERlLG0sdil9ZnVuY3Rpb24gQnEobSl7dmFyIHY9amlbbV07cmV0dXJuIGZ1bmN0aW9uKFQsTil7aWYoVD0kbChUKSxOPU49PW51bGw/MDpyYShRZShOKSwyOTIpLE4mJkNmdChUKSl7dmFyIFY9KG9uKFQpKyJlIikuc3BsaXQoImUiKSxZPXYoVlswXSsiZSIrKCtWWzFdK04pKTtyZXR1cm4gVj0ob24oWSkrImUiKS5zcGxpdCgiZSIpLCsoVlswXSsiZSIrKCtWWzFdLU4pKX1yZXR1cm4gdihUKX19dmFyIE9tZT1neCYmMS9VNihuZXcgZ3goWywtMF0pKVsxXT09ej9mdW5jdGlvbihtKXtyZXR1cm4gbmV3IGd4KG0pfTpzRztmdW5jdGlvbiBtcHQobSl7cmV0dXJuIGZ1bmN0aW9uKHYpe3ZhciBUPW5hKHYpO3JldHVybiBUPT1xP21xKHYpOlQ9PUxlP1FwZSh2KTpXcGUodixtKHYpKX19ZnVuY3Rpb24gc3AobSx2LFQsTixWLFksSixpdCl7dmFyIGZ0PXYmZztpZighZnQmJnR5cGVvZiBtIT0iZnVuY3Rpb24iKXRocm93IG5ldyBHbChpKTt2YXIgRHQ9Tj9OLmxlbmd0aDowO2lmKER0fHwodiY9fihifFMpLE49Vj1lKSxKPUo9PT1lP0o6WGkoUWUoSiksMCksaXQ9aXQ9PT1lP2l0OlFlKGl0KSxEdC09Vj9WLmxlbmd0aDowLHYmUyl7dmFyIE90PU4sVnQ9VjtOPVY9ZX12YXIgb2U9ZnQ/ZTpVcShtKSxUZT1bbSx2LFQsTixWLE90LFZ0LFksSixpdF07aWYob2UmJlptZShUZSxvZSksbT1UZVswXSx2PVRlWzFdLFQ9VGVbMl0sTj1UZVszXSxWPVRlWzRdLGl0PVRlWzldPVRlWzldPT09ZT9mdD8wOm0ubGVuZ3RoOlhpKFRlWzldLUR0LDApLCFpdCYmdiYoeXx4KSYmKHYmPX4oeXx4KSksIXZ8fHY9PWQpdmFyIE5lPVJtZShtLHYsVCk7ZWxzZSB2PT15fHx2PT14P05lPU5tZShtLHYsaXQpOih2PT1ifHx2PT0oZHxiKSkmJiFWLmxlbmd0aD9OZT1EbWUobSx2LFQsTik6TmU9dUkuYXBwbHkoZSxUZSk7dmFyIGVyPW9lPyRmdDpUcHQ7cmV0dXJuIENwdChlcihOZSxUZSksbSx2KX1mdW5jdGlvbiBncHQobSx2LFQsTil7cmV0dXJuIG09PT1lfHxydShtLG14W1RdKSYmIXVuLmNhbGwoTixUKT92Om19ZnVuY3Rpb24gX3B0KG0sdixULE4sVixZKXtyZXR1cm4gcmkobSkmJnJpKHYpJiYoWS5zZXQodixtKSxhSShtLHYsZSxfcHQsWSksWS5kZWxldGUodikpLG19ZnVuY3Rpb24gem1lKG0pe3JldHVybiBGTShtKT9lOm19ZnVuY3Rpb24geXB0KG0sdixULE4sVixZKXt2YXIgSj1UJmYsaXQ9bS5sZW5ndGgsZnQ9di5sZW5ndGg7aWYoaXQhPWZ0JiYhKEomJmZ0Pml0KSlyZXR1cm4hMTt2YXIgRHQ9WS5nZXQobSksT3Q9WS5nZXQodik7aWYoRHQmJk90KXJldHVybiBEdD09diYmT3Q9PW07dmFyIFZ0PS0xLG9lPSEwLFRlPVQmcD9uZXcgcl86ZTtmb3IoWS5zZXQobSx2KSxZLnNldCh2LG0pOysrVnQ8aXQ7KXt2YXIgTmU9bVtWdF0sZXI9dltWdF07aWYoTil2YXIgRGU9Sj9OKGVyLE5lLFZ0LHYsbSxZKTpOKE5lLGVyLFZ0LG0sdixZKTtpZihEZSE9PWUpe2lmKERlKWNvbnRpbnVlO29lPSExO2JyZWFrfWlmKFRlKXtpZighY3EodixmdW5jdGlvbihwcix5cil7aWYoIU1NKFRlLHlyKSYmKE5lPT09cHJ8fFYoTmUscHIsVCxOLFkpKSlyZXR1cm4gVGUucHVzaCh5cil9KSl7b2U9ITE7YnJlYWt9fWVsc2UgaWYoIShOZT09PWVyfHxWKE5lLGVyLFQsTixZKSkpe29lPSExO2JyZWFrfX1yZXR1cm4gWS5kZWxldGUobSksWS5kZWxldGUodiksb2V9ZnVuY3Rpb24gRm1lKG0sdixULE4sVixZLEope3N3aXRjaChUKXtjYXNlIGhlOmlmKG0uYnl0ZUxlbmd0aCE9di5ieXRlTGVuZ3RofHxtLmJ5dGVPZmZzZXQhPXYuYnl0ZU9mZnNldClyZXR1cm4hMTttPW0uYnVmZmVyLHY9di5idWZmZXI7Y2FzZSAkdDpyZXR1cm4hKG0uYnl0ZUxlbmd0aCE9di5ieXRlTGVuZ3RofHwhWShuZXcgWDYobSksbmV3IFg2KHYpKSk7Y2FzZSBLdDpjYXNlIF90OmNhc2UgcHQ6cmV0dXJuIHJ1KCttLCt2KTtjYXNlIFg6cmV0dXJuIG0ubmFtZT09di5uYW1lJiZtLm1lc3NhZ2U9PXYubWVzc2FnZTtjYXNlIGVlOmNhc2UgYXI6cmV0dXJuIG09PXYrIiI7Y2FzZSBxOnZhciBpdD1tcTtjYXNlIExlOnZhciBmdD1OJmY7aWYoaXR8fChpdD1VNiksbS5zaXplIT12LnNpemUmJiFmdClyZXR1cm4hMTt2YXIgRHQ9Si5nZXQobSk7aWYoRHQpcmV0dXJuIER0PT12O058PXAsSi5zZXQobSx2KTt2YXIgT3Q9eXB0KGl0KG0pLGl0KHYpLE4sVixZLEopO3JldHVybiBKLmRlbGV0ZShtKSxPdDtjYXNlIGZyOmlmKFBNKXJldHVybiBQTS5jYWxsKG0pPT1QTS5jYWxsKHYpfXJldHVybiExfWZ1bmN0aW9uIEJtZShtLHYsVCxOLFYsWSl7dmFyIEo9VCZmLGl0PUhxKG0pLGZ0PWl0Lmxlbmd0aCxEdD1IcSh2KSxPdD1EdC5sZW5ndGg7aWYoZnQhPU90JiYhSilyZXR1cm4hMTtmb3IodmFyIFZ0PWZ0O1Z0LS07KXt2YXIgb2U9aXRbVnRdO2lmKCEoSj9vZSBpbiB2OnVuLmNhbGwodixvZSkpKXJldHVybiExfXZhciBUZT1ZLmdldChtKSxOZT1ZLmdldCh2KTtpZihUZSYmTmUpcmV0dXJuIFRlPT12JiZOZT09bTt2YXIgZXI9ITA7WS5zZXQobSx2KSxZLnNldCh2LG0pO2Zvcih2YXIgRGU9SjsrK1Z0PGZ0Oyl7b2U9aXRbVnRdO3ZhciBwcj1tW29lXSx5cj12W29lXTtpZihOKXZhciBYcz1KP04oeXIscHIsb2UsdixtLFkpOk4ocHIseXIsb2UsbSx2LFkpO2lmKCEoWHM9PT1lP3ByPT09eXJ8fFYocHIseXIsVCxOLFkpOlhzKSl7ZXI9ITE7YnJlYWt9RGV8fChEZT1vZT09ImNvbnN0cnVjdG9yIil9aWYoZXImJiFEZSl7dmFyIFJhPW0uY29uc3RydWN0b3IsJHM9di5jb25zdHJ1Y3RvcjtSYSE9JHMmJiJjb25zdHJ1Y3RvciJpbiBtJiYiY29uc3RydWN0b3IiaW4gdiYmISh0eXBlb2YgUmE9PSJmdW5jdGlvbiImJlJhIGluc3RhbmNlb2YgUmEmJnR5cGVvZiAkcz09ImZ1bmN0aW9uIiYmJHMgaW5zdGFuY2VvZiAkcykmJihlcj0hMSl9cmV0dXJuIFkuZGVsZXRlKG0pLFkuZGVsZXRlKHYpLGVyfWZ1bmN0aW9uIGxwKG0pe3JldHVybiBYcShNcHQobSxlLFJwdCksbSsiIil9ZnVuY3Rpb24gSHEobSl7cmV0dXJuIEZmdChtLHBvLEdxKX1mdW5jdGlvbiBWcShtKXtyZXR1cm4gRmZ0KG0sbXMsdnB0KX12YXIgVXE9dEk/ZnVuY3Rpb24obSl7cmV0dXJuIHRJLmdldChtKX06c0c7ZnVuY3Rpb24gZEkobSl7Zm9yKHZhciB2PW0ubmFtZSsiIixUPV94W3ZdLE49dW4uY2FsbChfeCx2KT9ULmxlbmd0aDowO04tLTspe3ZhciBWPVRbTl0sWT1WLmZ1bmM7aWYoWT09bnVsbHx8WT09bSlyZXR1cm4gVi5uYW1lfXJldHVybiB2fWZ1bmN0aW9uIGJ4KG0pe3ZhciB2PXVuLmNhbGwoRywicGxhY2Vob2xkZXIiKT9HOm07cmV0dXJuIHYucGxhY2Vob2xkZXJ9ZnVuY3Rpb24ga2UoKXt2YXIgbT1HLml0ZXJhdGVlfHxvRztyZXR1cm4gbT1tPT09b0c/VmZ0Om0sYXJndW1lbnRzLmxlbmd0aD9tKGFyZ3VtZW50c1swXSxhcmd1bWVudHNbMV0pOm19ZnVuY3Rpb24gbUkobSx2KXt2YXIgVD1tLl9fZGF0YV9fO3JldHVybiBqbWUodik/VFt0eXBlb2Ygdj09InN0cmluZyI/InN0cmluZyI6Imhhc2giXTpULm1hcH1mdW5jdGlvbiBxcShtKXtmb3IodmFyIHY9cG8obSksVD12Lmxlbmd0aDtULS07KXt2YXIgTj12W1RdLFY9bVtOXTt2W1RdPVtOLFYsd3B0KFYpXX1yZXR1cm4gdn1mdW5jdGlvbiBvXyhtLHYpe3ZhciBUPUtwZShtLHYpO3JldHVybiBIZnQoVCk/VDplfWZ1bmN0aW9uIEhtZShtKXt2YXIgdj11bi5jYWxsKG0sdF8pLFQ9bVt0X107dHJ5e21bdF9dPWU7dmFyIE49ITB9Y2F0Y2goWSl7fXZhciBWPVk2LmNhbGwobSk7cmV0dXJuIE4mJih2P21bdF9dPVQ6ZGVsZXRlIG1bdF9dKSxWfXZhciBHcT1fcT9mdW5jdGlvbihtKXtyZXR1cm4gbT09bnVsbD9bXToobT12bihtKSxmbShfcShtKSxmdW5jdGlvbih2KXtyZXR1cm4gRWZ0LmNhbGwobSx2KX0pKX06bEcsdnB0PV9xP2Z1bmN0aW9uKG0pe2Zvcih2YXIgdj1bXTttOylwbSh2LEdxKG0pKSxtPSQ2KG0pO3JldHVybiB2fTpsRyxuYT1MYTsoeXEmJm5hKG5ldyB5cShuZXcgQXJyYXlCdWZmZXIoMSkpKSE9aGV8fFRNJiZuYShuZXcgVE0pIT1xfHx2cSYmbmEodnEucmVzb2x2ZSgpKSE9a3R8fGd4JiZuYShuZXcgZ3gpIT1MZXx8Q00mJm5hKG5ldyBDTSkhPSQpJiYobmE9ZnVuY3Rpb24obSl7dmFyIHY9TGEobSksVD12PT13dD9tLmNvbnN0cnVjdG9yOmUsTj1UP2FfKFQpOiIiO2lmKE4pc3dpdGNoKE4pe2Nhc2Ugd2RlOnJldHVybiBoZTtjYXNlIFNkZTpyZXR1cm4gcTtjYXNlIE1kZTpyZXR1cm4ga3Q7Y2FzZSBFZGU6cmV0dXJuIExlO2Nhc2UgVGRlOnJldHVybiAkfXJldHVybiB2fSk7ZnVuY3Rpb24gVm1lKG0sdixUKXtmb3IodmFyIE49LTEsVj1ULmxlbmd0aDsrK048Vjspe3ZhciBZPVRbTl0sSj1ZLnNpemU7c3dpdGNoKFkudHlwZSl7Y2FzZSJkcm9wIjptKz1KO2JyZWFrO2Nhc2UiZHJvcFJpZ2h0Ijp2LT1KO2JyZWFrO2Nhc2UidGFrZSI6dj1yYSh2LG0rSik7YnJlYWs7Y2FzZSJ0YWtlUmlnaHQiOm09WGkobSx2LUopO2JyZWFrfX1yZXR1cm57c3RhcnQ6bSxlbmQ6dn19ZnVuY3Rpb24gVW1lKG0pe3ZhciB2PW0ubWF0Y2goRXQpO3JldHVybiB2P3ZbMV0uc3BsaXQoeHQpOltdfWZ1bmN0aW9uIHhwdChtLHYsVCl7dj12bSh2LG0pO2Zvcih2YXIgTj0tMSxWPXYubGVuZ3RoLFk9ITE7KytOPFY7KXt2YXIgSj1faCh2W05dKTtpZighKFk9bSE9bnVsbCYmVChtLEopKSlicmVhazttPW1bSl19cmV0dXJuIFl8fCsrTiE9Vj9ZOihWPW09PW51bGw/MDptLmxlbmd0aCwhIVYmJndJKFYpJiZjcChKLFYpJiYoJGUobSl8fHNfKG0pKSl9ZnVuY3Rpb24gcW1lKG0pe3ZhciB2PW0ubGVuZ3RoLFQ9bmV3IG0uY29uc3RydWN0b3Iodik7cmV0dXJuIHYmJnR5cGVvZiBtWzBdPT0ic3RyaW5nIiYmdW4uY2FsbChtLCJpbmRleCIpJiYoVC5pbmRleD1tLmluZGV4LFQuaW5wdXQ9bS5pbnB1dCksVH1mdW5jdGlvbiBicHQobSl7cmV0dXJuIHR5cGVvZiBtLmNvbnN0cnVjdG9yPT0iZnVuY3Rpb24iJiYhT00obSk/eXgoJDYobSkpOnt9fWZ1bmN0aW9uIEdtZShtLHYsVCl7dmFyIE49bS5jb25zdHJ1Y3Rvcjtzd2l0Y2godil7Y2FzZSAkdDpyZXR1cm4genEobSk7Y2FzZSBLdDpjYXNlIF90OnJldHVybiBuZXcgTigrbSk7Y2FzZSBoZTpyZXR1cm4gQ21lKG0sVCk7Y2FzZSBUdDpjYXNlIGJlOmNhc2UgbnQ6Y2FzZSBDdDpjYXNlIFd0OmNhc2UgZmU6Y2FzZSBhdDpjYXNlIHNlOmNhc2UgUXQ6cmV0dXJuIG5wdChtLFQpO2Nhc2UgcTpyZXR1cm4gbmV3IE47Y2FzZSBwdDpjYXNlIGFyOnJldHVybiBuZXcgTihtKTtjYXNlIGVlOnJldHVybiBBbWUobSk7Y2FzZSBMZTpyZXR1cm4gbmV3IE47Y2FzZSBmcjpyZXR1cm4gUG1lKG0pfX1mdW5jdGlvbiBXbWUobSx2KXt2YXIgVD12Lmxlbmd0aDtpZighVClyZXR1cm4gbTt2YXIgTj1ULTE7cmV0dXJuIHZbTl09KFQ+MT8iJiAiOiIiKSt2W05dLHY9di5qb2luKFQ+Mj8iLCAiOiIgIiksbS5yZXBsYWNlKGd0LGB7Ci8qIFt3cmFwcGVkIHdpdGggYCt2K2BdICovCmApfWZ1bmN0aW9uIFltZShtKXtyZXR1cm4gJGUobSl8fHNfKG0pfHwhIShUZnQmJm0mJm1bVGZ0XSl9ZnVuY3Rpb24gY3AobSx2KXt2YXIgVD10eXBlb2YgbTtyZXR1cm4gdj12PT1udWxsP1U6diwhIXYmJihUPT0ibnVtYmVyInx8VCE9InN5bWJvbCImJm5wLnRlc3QobSkpJiZtPi0xJiZtJTE9PTAmJm08dn1mdW5jdGlvbiBrYShtLHYsVCl7aWYoIXJpKFQpKXJldHVybiExO3ZhciBOPXR5cGVvZiB2O3JldHVybihOPT0ibnVtYmVyIj9kcyhUKSYmY3AodixULmxlbmd0aCk6Tj09InN0cmluZyImJnYgaW4gVCk/cnUoVFt2XSxtKTohMX1mdW5jdGlvbiBXcShtLHYpe2lmKCRlKG0pKXJldHVybiExO3ZhciBUPXR5cGVvZiBtO3JldHVybiBUPT0ibnVtYmVyInx8VD09InN5bWJvbCJ8fFQ9PSJib29sZWFuInx8bT09bnVsbHx8anMobSk/ITA6Y20udGVzdChtKXx8IWx4LnRlc3QobSl8fHYhPW51bGwmJm0gaW4gdm4odil9ZnVuY3Rpb24gam1lKG0pe3ZhciB2PXR5cGVvZiBtO3JldHVybiB2PT0ic3RyaW5nInx8dj09Im51bWJlciJ8fHY9PSJzeW1ib2wifHx2PT0iYm9vbGVhbiI/bSE9PSJfX3Byb3RvX18iOm09PT1udWxsfWZ1bmN0aW9uIFlxKG0pe3ZhciB2PWRJKG0pLFQ9R1t2XTtpZih0eXBlb2YgVCE9ImZ1bmN0aW9uInx8ISh2IGluIGdyLnByb3RvdHlwZSkpcmV0dXJuITE7aWYobT09PVQpcmV0dXJuITA7dmFyIE49VXEoVCk7cmV0dXJuISFOJiZtPT09TlswXX1mdW5jdGlvbiBYbWUobSl7cmV0dXJuISF3ZnQmJndmdCBpbiBtfXZhciAkbWU9RzY/dXA6Y0c7ZnVuY3Rpb24gT00obSl7dmFyIHY9bSYmbS5jb25zdHJ1Y3RvcixUPXR5cGVvZiB2PT0iZnVuY3Rpb24iJiZ2LnByb3RvdHlwZXx8bXg7cmV0dXJuIG09PT1UfWZ1bmN0aW9uIHdwdChtKXtyZXR1cm4gbT09PW0mJiFyaShtKX1mdW5jdGlvbiBTcHQobSx2KXtyZXR1cm4gZnVuY3Rpb24oVCl7cmV0dXJuIFQ9PW51bGw/ITE6VFttXT09PXYmJih2IT09ZXx8bSBpbiB2bihUKSl9fWZ1bmN0aW9uIEttZShtKXt2YXIgdj14SShtLGZ1bmN0aW9uKE4pe3JldHVybiBULnNpemU9PT1zJiZULmNsZWFyKCksTn0pLFQ9di5jYWNoZTtyZXR1cm4gdn1mdW5jdGlvbiBabWUobSx2KXt2YXIgVD1tWzFdLE49dlsxXSxWPVR8TixZPVY8KGR8Z3xDKSxKPU49PUMmJlQ9PXl8fE49PUMmJlQ9PVAmJm1bN10ubGVuZ3RoPD12WzhdfHxOPT0oQ3xQKSYmdls3XS5sZW5ndGg8PXZbOF0mJlQ9PXk7aWYoIShZfHxKKSlyZXR1cm4gbTtOJmQmJihtWzJdPXZbMl0sVnw9VCZkPzA6Xyk7dmFyIGl0PXZbM107aWYoaXQpe3ZhciBmdD1tWzNdO21bM109ZnQ/b3B0KGZ0LGl0LHZbNF0pOml0LG1bNF09ZnQ/ZG0obVszXSxsKTp2WzRdfXJldHVybiBpdD12WzVdLGl0JiYoZnQ9bVs1XSxtWzVdPWZ0P2FwdChmdCxpdCx2WzZdKTppdCxtWzZdPWZ0P2RtKG1bNV0sbCk6dls2XSksaXQ9dls3XSxpdCYmKG1bN109aXQpLE4mQyYmKG1bOF09bVs4XT09bnVsbD92WzhdOnJhKG1bOF0sdls4XSkpLG1bOV09PW51bGwmJihtWzldPXZbOV0pLG1bMF09dlswXSxtWzFdPVYsbX1mdW5jdGlvbiBKbWUobSl7dmFyIHY9W107aWYobSE9bnVsbClmb3IodmFyIFQgaW4gdm4obSkpdi5wdXNoKFQpO3JldHVybiB2fWZ1bmN0aW9uIFFtZShtKXtyZXR1cm4gWTYuY2FsbChtKX1mdW5jdGlvbiBNcHQobSx2LFQpe3JldHVybiB2PVhpKHY9PT1lP20ubGVuZ3RoLTE6diwwKSxmdW5jdGlvbigpe2Zvcih2YXIgTj1hcmd1bWVudHMsVj0tMSxZPVhpKE4ubGVuZ3RoLXYsMCksSj12dChZKTsrK1Y8WTspSltWXT1OW3YrVl07Vj0tMTtmb3IodmFyIGl0PXZ0KHYrMSk7KytWPHY7KWl0W1ZdPU5bVl07cmV0dXJuIGl0W3ZdPVQoSiksR3MobSx0aGlzLGl0KX19ZnVuY3Rpb24gRXB0KG0sdil7cmV0dXJuIHYubGVuZ3RoPDI/bTppXyhtLGpsKHYsMCwtMSkpfWZ1bmN0aW9uIHRnZShtLHYpe2Zvcih2YXIgVD1tLmxlbmd0aCxOPXJhKHYubGVuZ3RoLFQpLFY9cHMobSk7Ti0tOyl7dmFyIFk9dltOXTttW05dPWNwKFksVCk/VltZXTplfXJldHVybiBtfWZ1bmN0aW9uIGpxKG0sdil7aWYoISh2PT09ImNvbnN0cnVjdG9yIiYmdHlwZW9mIG1bdl09PSJmdW5jdGlvbiIpJiZ2IT0iX19wcm90b19fIilyZXR1cm4gbVt2XX12YXIgVHB0PUFwdCgkZnQpLHpNPW1kZXx8ZnVuY3Rpb24obSx2KXtyZXR1cm4gZm8uc2V0VGltZW91dChtLHYpfSxYcT1BcHQoU21lKTtmdW5jdGlvbiBDcHQobSx2LFQpe3ZhciBOPXYrIiI7cmV0dXJuIFhxKG0sV21lKE4sZWdlKFVtZShOKSxUKSkpfWZ1bmN0aW9uIEFwdChtKXt2YXIgdj0wLFQ9MDtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgTj12ZGUoKSxWPUktKE4tVCk7aWYoVD1OLFY+MCl7aWYoKyt2Pj1CKXJldHVybiBhcmd1bWVudHNbMF19ZWxzZSB2PTA7cmV0dXJuIG0uYXBwbHkoZSxhcmd1bWVudHMpfX1mdW5jdGlvbiBnSShtLHYpe3ZhciBUPS0xLE49bS5sZW5ndGgsVj1OLTE7Zm9yKHY9dj09PWU/Tjp2OysrVDx2Oyl7dmFyIFk9SXEoVCxWKSxKPW1bWV07bVtZXT1tW1RdLG1bVF09Sn1yZXR1cm4gbS5sZW5ndGg9dixtfXZhciBQcHQ9S21lKGZ1bmN0aW9uKG0pe3ZhciB2PVtdO3JldHVybiBtLmNoYXJDb2RlQXQoMCk9PT00NiYmdi5wdXNoKCIiKSxtLnJlcGxhY2UoSjAsZnVuY3Rpb24oVCxOLFYsWSl7di5wdXNoKFY/WS5yZXBsYWNlKFVlLCIkMSIpOk58fFQpfSksdn0pO2Z1bmN0aW9uIF9oKG0pe2lmKHR5cGVvZiBtPT0ic3RyaW5nInx8anMobSkpcmV0dXJuIG07dmFyIHY9bSsiIjtyZXR1cm4gdj09IjAiJiYxL209PS16PyItMCI6dn1mdW5jdGlvbiBhXyhtKXtpZihtIT1udWxsKXt0cnl7cmV0dXJuIFc2LmNhbGwobSl9Y2F0Y2godil7fXRyeXtyZXR1cm4gbSsiIn1jYXRjaCh2KXt9fXJldHVybiIifWZ1bmN0aW9uIGVnZShtLHYpe3JldHVybiBxbChTdCxmdW5jdGlvbihUKXt2YXIgTj0iXy4iK1RbMF07diZUWzFdJiYhSDYobSxOKSYmbS5wdXNoKE4pfSksbS5zb3J0KCl9ZnVuY3Rpb24gSXB0KG0pe2lmKG0gaW5zdGFuY2VvZiBncilyZXR1cm4gbS5jbG9uZSgpO3ZhciB2PW5ldyBXbChtLl9fd3JhcHBlZF9fLG0uX19jaGFpbl9fKTtyZXR1cm4gdi5fX2FjdGlvbnNfXz1wcyhtLl9fYWN0aW9uc19fKSx2Ll9faW5kZXhfXz1tLl9faW5kZXhfXyx2Ll9fdmFsdWVzX189bS5fX3ZhbHVlc19fLHZ9ZnVuY3Rpb24gcmdlKG0sdixUKXsoVD9rYShtLHYsVCk6dj09PWUpP3Y9MTp2PVhpKFFlKHYpLDApO3ZhciBOPW09PW51bGw/MDptLmxlbmd0aDtpZighTnx8djwxKXJldHVybltdO2Zvcih2YXIgVj0wLFk9MCxKPXZ0KEo2KE4vdikpO1Y8TjspSltZKytdPWpsKG0sVixWKz12KTtyZXR1cm4gSn1mdW5jdGlvbiBuZ2UobSl7Zm9yKHZhciB2PS0xLFQ9bT09bnVsbD8wOm0ubGVuZ3RoLE49MCxWPVtdOysrdjxUOyl7dmFyIFk9bVt2XTtZJiYoVltOKytdPVkpfXJldHVybiBWfWZ1bmN0aW9uIGlnZSgpe3ZhciBtPWFyZ3VtZW50cy5sZW5ndGg7aWYoIW0pcmV0dXJuW107Zm9yKHZhciB2PXZ0KG0tMSksVD1hcmd1bWVudHNbMF0sTj1tO04tLTspdltOLTFdPWFyZ3VtZW50c1tOXTtyZXR1cm4gcG0oJGUoVCk/cHMoVCk6W1RdLFJvKHYsMSkpfXZhciBvZ2U9bnIoZnVuY3Rpb24obSx2KXtyZXR1cm4gYmkobSk/TE0obSxSbyh2LDEsYmksITApKTpbXX0pLGFnZT1ucihmdW5jdGlvbihtLHYpe3ZhciBUPVhsKHYpO3JldHVybiBiaShUKSYmKFQ9ZSksYmkobSk/TE0obSxSbyh2LDEsYmksITApLGtlKFQsMikpOltdfSksc2dlPW5yKGZ1bmN0aW9uKG0sdil7dmFyIFQ9WGwodik7cmV0dXJuIGJpKFQpJiYoVD1lKSxiaShtKT9MTShtLFJvKHYsMSxiaSwhMCksZSxUKTpbXX0pO2Z1bmN0aW9uIGxnZShtLHYsVCl7dmFyIE49bT09bnVsbD8wOm0ubGVuZ3RoO3JldHVybiBOPyh2PVR8fHY9PT1lPzE6UWUodiksamwobSx2PDA/MDp2LE4pKTpbXX1mdW5jdGlvbiBjZ2UobSx2LFQpe3ZhciBOPW09PW51bGw/MDptLmxlbmd0aDtyZXR1cm4gTj8odj1UfHx2PT09ZT8xOlFlKHYpLHY9Ti12LGpsKG0sMCx2PDA/MDp2KSk6W119ZnVuY3Rpb24gdWdlKG0sdil7cmV0dXJuIG0mJm0ubGVuZ3RoP2xJKG0sa2UodiwzKSwhMCwhMCk6W119ZnVuY3Rpb24gaGdlKG0sdil7cmV0dXJuIG0mJm0ubGVuZ3RoP2xJKG0sa2UodiwzKSwhMCk6W119ZnVuY3Rpb24gZmdlKG0sdixULE4pe3ZhciBWPW09PW51bGw/MDptLmxlbmd0aDtyZXR1cm4gVj8oVCYmdHlwZW9mIFQhPSJudW1iZXIiJiZrYShtLHYsVCkmJihUPTAsTj1WKSxpbWUobSx2LFQsTikpOltdfWZ1bmN0aW9uIExwdChtLHYsVCl7dmFyIE49bT09bnVsbD8wOm0ubGVuZ3RoO2lmKCFOKXJldHVybi0xO3ZhciBWPVQ9PW51bGw/MDpRZShUKTtyZXR1cm4gVjwwJiYoVj1YaShOK1YsMCkpLFY2KG0sa2UodiwzKSxWKX1mdW5jdGlvbiBrcHQobSx2LFQpe3ZhciBOPW09PW51bGw/MDptLmxlbmd0aDtpZighTilyZXR1cm4tMTt2YXIgVj1OLTE7cmV0dXJuIFQhPT1lJiYoVj1RZShUKSxWPVQ8MD9YaShOK1YsMCk6cmEoVixOLTEpKSxWNihtLGtlKHYsMyksViwhMCl9ZnVuY3Rpb24gUnB0KG0pe3ZhciB2PW09PW51bGw/MDptLmxlbmd0aDtyZXR1cm4gdj9SbyhtLDEpOltdfWZ1bmN0aW9uIHBnZShtKXt2YXIgdj1tPT1udWxsPzA6bS5sZW5ndGg7cmV0dXJuIHY/Um8obSx6KTpbXX1mdW5jdGlvbiBkZ2UobSx2KXt2YXIgVD1tPT1udWxsPzA6bS5sZW5ndGg7cmV0dXJuIFQ/KHY9dj09PWU/MTpRZSh2KSxSbyhtLHYpKTpbXX1mdW5jdGlvbiBtZ2UobSl7Zm9yKHZhciB2PS0xLFQ9bT09bnVsbD8wOm0ubGVuZ3RoLE49e307Kyt2PFQ7KXt2YXIgVj1tW3ZdO05bVlswXV09VlsxXX1yZXR1cm4gTn1mdW5jdGlvbiBOcHQobSl7cmV0dXJuIG0mJm0ubGVuZ3RoP21bMF06ZX1mdW5jdGlvbiBnZ2UobSx2LFQpe3ZhciBOPW09PW51bGw/MDptLmxlbmd0aDtpZighTilyZXR1cm4tMTt2YXIgVj1UPT1udWxsPzA6UWUoVCk7cmV0dXJuIFY8MCYmKFY9WGkoTitWLDApKSxmeChtLHYsVil9ZnVuY3Rpb24gX2dlKG0pe3ZhciB2PW09PW51bGw/MDptLmxlbmd0aDtyZXR1cm4gdj9qbChtLDAsLTEpOltdfXZhciB5Z2U9bnIoZnVuY3Rpb24obSl7dmFyIHY9S24obSxEcSk7cmV0dXJuIHYubGVuZ3RoJiZ2WzBdPT09bVswXT9FcSh2KTpbXX0pLHZnZT1ucihmdW5jdGlvbihtKXt2YXIgdj1YbChtKSxUPUtuKG0sRHEpO3JldHVybiB2PT09WGwoVCk/dj1lOlQucG9wKCksVC5sZW5ndGgmJlRbMF09PT1tWzBdP0VxKFQsa2UodiwyKSk6W119KSx4Z2U9bnIoZnVuY3Rpb24obSl7dmFyIHY9WGwobSksVD1LbihtLERxKTtyZXR1cm4gdj10eXBlb2Ygdj09ImZ1bmN0aW9uIj92OmUsdiYmVC5wb3AoKSxULmxlbmd0aCYmVFswXT09PW1bMF0/RXEoVCxlLHYpOltdfSk7ZnVuY3Rpb24gYmdlKG0sdil7cmV0dXJuIG09PW51bGw/IiI6X2RlLmNhbGwobSx2KX1mdW5jdGlvbiBYbChtKXt2YXIgdj1tPT1udWxsPzA6bS5sZW5ndGg7cmV0dXJuIHY/bVt2LTFdOmV9ZnVuY3Rpb24gd2dlKG0sdixUKXt2YXIgTj1tPT1udWxsPzA6bS5sZW5ndGg7aWYoIU4pcmV0dXJuLTE7dmFyIFY9TjtyZXR1cm4gVCE9PWUmJihWPVFlKFQpLFY9VjwwP1hpKE4rViwwKTpyYShWLE4tMSkpLHY9PT12P2VkZShtLHYsVik6VjYobSxkZnQsViwhMCl9ZnVuY3Rpb24gU2dlKG0sdil7cmV0dXJuIG0mJm0ubGVuZ3RoP1dmdChtLFFlKHYpKTplfXZhciBNZ2U9bnIoRHB0KTtmdW5jdGlvbiBEcHQobSx2KXtyZXR1cm4gbSYmbS5sZW5ndGgmJnYmJnYubGVuZ3RoP1BxKG0sdik6bX1mdW5jdGlvbiBFZ2UobSx2LFQpe3JldHVybiBtJiZtLmxlbmd0aCYmdiYmdi5sZW5ndGg/UHEobSx2LGtlKFQsMikpOm19ZnVuY3Rpb24gVGdlKG0sdixUKXtyZXR1cm4gbSYmbS5sZW5ndGgmJnYmJnYubGVuZ3RoP1BxKG0sdixlLFQpOm19dmFyIENnZT1scChmdW5jdGlvbihtLHYpe3ZhciBUPW09PW51bGw/MDptLmxlbmd0aCxOPWJxKG0sdik7cmV0dXJuIFhmdChtLEtuKHYsZnVuY3Rpb24oVil7cmV0dXJuIGNwKFYsVCk/K1Y6Vn0pLnNvcnQoaXB0KSksTn0pO2Z1bmN0aW9uIEFnZShtLHYpe3ZhciBUPVtdO2lmKCEobSYmbS5sZW5ndGgpKXJldHVybiBUO3ZhciBOPS0xLFY9W10sWT1tLmxlbmd0aDtmb3Iodj1rZSh2LDMpOysrTjxZOyl7dmFyIEo9bVtOXTt2KEosTixtKSYmKFQucHVzaChKKSxWLnB1c2goTikpfXJldHVybiBYZnQobSxWKSxUfWZ1bmN0aW9uICRxKG0pe3JldHVybiBtPT1udWxsP206YmRlLmNhbGwobSl9ZnVuY3Rpb24gUGdlKG0sdixUKXt2YXIgTj1tPT1udWxsPzA6bS5sZW5ndGg7cmV0dXJuIE4/KFQmJnR5cGVvZiBUIT0ibnVtYmVyIiYma2EobSx2LFQpPyh2PTAsVD1OKToodj12PT1udWxsPzA6UWUodiksVD1UPT09ZT9OOlFlKFQpKSxqbChtLHYsVCkpOltdfWZ1bmN0aW9uIElnZShtLHYpe3JldHVybiBzSShtLHYpfWZ1bmN0aW9uIExnZShtLHYsVCl7cmV0dXJuIGtxKG0sdixrZShULDIpKX1mdW5jdGlvbiBrZ2UobSx2KXt2YXIgVD1tPT1udWxsPzA6bS5sZW5ndGg7aWYoVCl7dmFyIE49c0kobSx2KTtpZihOPFQmJnJ1KG1bTl0sdikpcmV0dXJuIE59cmV0dXJuLTF9ZnVuY3Rpb24gUmdlKG0sdil7cmV0dXJuIHNJKG0sdiwhMCl9ZnVuY3Rpb24gTmdlKG0sdixUKXtyZXR1cm4ga3EobSx2LGtlKFQsMiksITApfWZ1bmN0aW9uIERnZShtLHYpe3ZhciBUPW09PW51bGw/MDptLmxlbmd0aDtpZihUKXt2YXIgTj1zSShtLHYsITApLTE7aWYocnUobVtOXSx2KSlyZXR1cm4gTn1yZXR1cm4tMX1mdW5jdGlvbiBPZ2UobSl7cmV0dXJuIG0mJm0ubGVuZ3RoP0tmdChtKTpbXX1mdW5jdGlvbiB6Z2UobSx2KXtyZXR1cm4gbSYmbS5sZW5ndGg/S2Z0KG0sa2UodiwyKSk6W119ZnVuY3Rpb24gRmdlKG0pe3ZhciB2PW09PW51bGw/MDptLmxlbmd0aDtyZXR1cm4gdj9qbChtLDEsdik6W119ZnVuY3Rpb24gQmdlKG0sdixUKXtyZXR1cm4gbSYmbS5sZW5ndGg/KHY9VHx8dj09PWU/MTpRZSh2KSxqbChtLDAsdjwwPzA6dikpOltdfWZ1bmN0aW9uIEhnZShtLHYsVCl7dmFyIE49bT09bnVsbD8wOm0ubGVuZ3RoO3JldHVybiBOPyh2PVR8fHY9PT1lPzE6UWUodiksdj1OLXYsamwobSx2PDA/MDp2LE4pKTpbXX1mdW5jdGlvbiBWZ2UobSx2KXtyZXR1cm4gbSYmbS5sZW5ndGg/bEkobSxrZSh2LDMpLCExLCEwKTpbXX1mdW5jdGlvbiBVZ2UobSx2KXtyZXR1cm4gbSYmbS5sZW5ndGg/bEkobSxrZSh2LDMpKTpbXX12YXIgcWdlPW5yKGZ1bmN0aW9uKG0pe3JldHVybiB5bShSbyhtLDEsYmksITApKX0pLEdnZT1ucihmdW5jdGlvbihtKXt2YXIgdj1YbChtKTtyZXR1cm4gYmkodikmJih2PWUpLHltKFJvKG0sMSxiaSwhMCksa2UodiwyKSl9KSxXZ2U9bnIoZnVuY3Rpb24obSl7dmFyIHY9WGwobSk7cmV0dXJuIHY9dHlwZW9mIHY9PSJmdW5jdGlvbiI/djplLHltKFJvKG0sMSxiaSwhMCksZSx2KX0pO2Z1bmN0aW9uIFlnZShtKXtyZXR1cm4gbSYmbS5sZW5ndGg/eW0obSk6W119ZnVuY3Rpb24gamdlKG0sdil7cmV0dXJuIG0mJm0ubGVuZ3RoP3ltKG0sa2UodiwyKSk6W119ZnVuY3Rpb24gWGdlKG0sdil7cmV0dXJuIHY9dHlwZW9mIHY9PSJmdW5jdGlvbiI/djplLG0mJm0ubGVuZ3RoP3ltKG0sZSx2KTpbXX1mdW5jdGlvbiBLcShtKXtpZighKG0mJm0ubGVuZ3RoKSlyZXR1cm5bXTt2YXIgdj0wO3JldHVybiBtPWZtKG0sZnVuY3Rpb24oVCl7aWYoYmkoVCkpcmV0dXJuIHY9WGkoVC5sZW5ndGgsdiksITB9KSxwcSh2LGZ1bmN0aW9uKFQpe3JldHVybiBLbihtLHVxKFQpKX0pfWZ1bmN0aW9uIE9wdChtLHYpe2lmKCEobSYmbS5sZW5ndGgpKXJldHVybltdO3ZhciBUPUtxKG0pO3JldHVybiB2PT1udWxsP1Q6S24oVCxmdW5jdGlvbihOKXtyZXR1cm4gR3ModixlLE4pfSl9dmFyICRnZT1ucihmdW5jdGlvbihtLHYpe3JldHVybiBiaShtKT9MTShtLHYpOltdfSksS2dlPW5yKGZ1bmN0aW9uKG0pe3JldHVybiBOcShmbShtLGJpKSl9KSxaZ2U9bnIoZnVuY3Rpb24obSl7dmFyIHY9WGwobSk7cmV0dXJuIGJpKHYpJiYodj1lKSxOcShmbShtLGJpKSxrZSh2LDIpKX0pLEpnZT1ucihmdW5jdGlvbihtKXt2YXIgdj1YbChtKTtyZXR1cm4gdj10eXBlb2Ygdj09ImZ1bmN0aW9uIj92OmUsTnEoZm0obSxiaSksZSx2KX0pLFFnZT1ucihLcSk7ZnVuY3Rpb24gdDBlKG0sdil7cmV0dXJuIHRwdChtfHxbXSx2fHxbXSxJTSl9ZnVuY3Rpb24gZTBlKG0sdil7cmV0dXJuIHRwdChtfHxbXSx2fHxbXSxOTSl9dmFyIHIwZT1ucihmdW5jdGlvbihtKXt2YXIgdj1tLmxlbmd0aCxUPXY+MT9tW3YtMV06ZTtyZXR1cm4gVD10eXBlb2YgVD09ImZ1bmN0aW9uIj8obS5wb3AoKSxUKTplLE9wdChtLFQpfSk7ZnVuY3Rpb24genB0KG0pe3ZhciB2PUcobSk7cmV0dXJuIHYuX19jaGFpbl9fPSEwLHZ9ZnVuY3Rpb24gbjBlKG0sdil7cmV0dXJuIHYobSksbX1mdW5jdGlvbiBfSShtLHYpe3JldHVybiB2KG0pfXZhciBpMGU9bHAoZnVuY3Rpb24obSl7dmFyIHY9bS5sZW5ndGgsVD12P21bMF06MCxOPXRoaXMuX193cmFwcGVkX18sVj1mdW5jdGlvbihZKXtyZXR1cm4gYnEoWSxtKX07cmV0dXJuIHY+MXx8dGhpcy5fX2FjdGlvbnNfXy5sZW5ndGh8fCEoTiBpbnN0YW5jZW9mIGdyKXx8IWNwKFQpP3RoaXMudGhydShWKTooTj1OLnNsaWNlKFQsK1QrKHY/MTowKSksTi5fX2FjdGlvbnNfXy5wdXNoKHtmdW5jOl9JLGFyZ3M6W1ZdLHRoaXNBcmc6ZX0pLG5ldyBXbChOLHRoaXMuX19jaGFpbl9fKS50aHJ1KGZ1bmN0aW9uKFkpe3JldHVybiB2JiYhWS5sZW5ndGgmJlkucHVzaChlKSxZfSkpfSk7ZnVuY3Rpb24gbzBlKCl7cmV0dXJuIHpwdCh0aGlzKX1mdW5jdGlvbiBhMGUoKXtyZXR1cm4gbmV3IFdsKHRoaXMudmFsdWUoKSx0aGlzLl9fY2hhaW5fXyl9ZnVuY3Rpb24gczBlKCl7dGhpcy5fX3ZhbHVlc19fPT09ZSYmKHRoaXMuX192YWx1ZXNfXz1acHQodGhpcy52YWx1ZSgpKSk7dmFyIG09dGhpcy5fX2luZGV4X18+PXRoaXMuX192YWx1ZXNfXy5sZW5ndGgsdj1tP2U6dGhpcy5fX3ZhbHVlc19fW3RoaXMuX19pbmRleF9fKytdO3JldHVybntkb25lOm0sdmFsdWU6dn19ZnVuY3Rpb24gbDBlKCl7cmV0dXJuIHRoaXN9ZnVuY3Rpb24gYzBlKG0pe2Zvcih2YXIgdixUPXRoaXM7VCBpbnN0YW5jZW9mIHJJOyl7dmFyIE49SXB0KFQpO04uX19pbmRleF9fPTAsTi5fX3ZhbHVlc19fPWUsdj9WLl9fd3JhcHBlZF9fPU46dj1OO3ZhciBWPU47VD1ULl9fd3JhcHBlZF9ffXJldHVybiBWLl9fd3JhcHBlZF9fPW0sdn1mdW5jdGlvbiB1MGUoKXt2YXIgbT10aGlzLl9fd3JhcHBlZF9fO2lmKG0gaW5zdGFuY2VvZiBncil7dmFyIHY9bTtyZXR1cm4gdGhpcy5fX2FjdGlvbnNfXy5sZW5ndGgmJih2PW5ldyBncih0aGlzKSksdj12LnJldmVyc2UoKSx2Ll9fYWN0aW9uc19fLnB1c2goe2Z1bmM6X0ksYXJnczpbJHFdLHRoaXNBcmc6ZX0pLG5ldyBXbCh2LHRoaXMuX19jaGFpbl9fKX1yZXR1cm4gdGhpcy50aHJ1KCRxKX1mdW5jdGlvbiBoMGUoKXtyZXR1cm4gUWZ0KHRoaXMuX193cmFwcGVkX18sdGhpcy5fX2FjdGlvbnNfXyl9dmFyIGYwZT1jSShmdW5jdGlvbihtLHYsVCl7dW4uY2FsbChtLFQpPysrbVtUXTphcChtLFQsMSl9KTtmdW5jdGlvbiBwMGUobSx2LFQpe3ZhciBOPSRlKG0pP2ZmdDpubWU7cmV0dXJuIFQmJmthKG0sdixUKSYmKHY9ZSksTihtLGtlKHYsMykpfWZ1bmN0aW9uIGQwZShtLHYpe3ZhciBUPSRlKG0pP2ZtOk9mdDtyZXR1cm4gVChtLGtlKHYsMykpfXZhciBtMGU9dXB0KExwdCksZzBlPXVwdChrcHQpO2Z1bmN0aW9uIF8wZShtLHYpe3JldHVybiBSbyh5SShtLHYpLDEpfWZ1bmN0aW9uIHkwZShtLHYpe3JldHVybiBSbyh5SShtLHYpLHopfWZ1bmN0aW9uIHYwZShtLHYsVCl7cmV0dXJuIFQ9VD09PWU/MTpRZShUKSxSbyh5SShtLHYpLFQpfWZ1bmN0aW9uIEZwdChtLHYpe3ZhciBUPSRlKG0pP3FsOl9tO3JldHVybiBUKG0sa2UodiwzKSl9ZnVuY3Rpb24gQnB0KG0sdil7dmFyIFQ9JGUobSk/RnBlOkRmdDtyZXR1cm4gVChtLGtlKHYsMykpfXZhciB4MGU9Y0koZnVuY3Rpb24obSx2LFQpe3VuLmNhbGwobSxUKT9tW1RdLnB1c2godik6YXAobSxULFt2XSl9KTtmdW5jdGlvbiBiMGUobSx2LFQsTil7bT1kcyhtKT9tOlN4KG0pLFQ9VCYmIU4/UWUoVCk6MDt2YXIgVj1tLmxlbmd0aDtyZXR1cm4gVDwwJiYoVD1YaShWK1QsMCkpLFNJKG0pP1Q8PVYmJm0uaW5kZXhPZih2LFQpPi0xOiEhViYmZngobSx2LFQpPi0xfXZhciB3MGU9bnIoZnVuY3Rpb24obSx2LFQpe3ZhciBOPS0xLFY9dHlwZW9mIHY9PSJmdW5jdGlvbiIsWT1kcyhtKT92dChtLmxlbmd0aCk6W107cmV0dXJuIF9tKG0sZnVuY3Rpb24oSil7WVsrK05dPVY/R3ModixKLFQpOmtNKEosdixUKX0pLFl9KSxTMGU9Y0koZnVuY3Rpb24obSx2LFQpe2FwKG0sVCx2KX0pO2Z1bmN0aW9uIHlJKG0sdil7dmFyIFQ9JGUobSk/S246VWZ0O3JldHVybiBUKG0sa2UodiwzKSl9ZnVuY3Rpb24gTTBlKG0sdixULE4pe3JldHVybiBtPT1udWxsP1tdOigkZSh2KXx8KHY9dj09bnVsbD9bXTpbdl0pLFQ9Tj9lOlQsJGUoVCl8fChUPVQ9PW51bGw/W106W1RdKSxZZnQobSx2LFQpKX12YXIgRTBlPWNJKGZ1bmN0aW9uKG0sdixUKXttW1Q/MDoxXS5wdXNoKHYpfSxmdW5jdGlvbigpe3JldHVybltbXSxbXV19KTtmdW5jdGlvbiBUMGUobSx2LFQpe3ZhciBOPSRlKG0pP2xxOmdmdCxWPWFyZ3VtZW50cy5sZW5ndGg8MztyZXR1cm4gTihtLGtlKHYsNCksVCxWLF9tKX1mdW5jdGlvbiBDMGUobSx2LFQpe3ZhciBOPSRlKG0pP0JwZTpnZnQsVj1hcmd1bWVudHMubGVuZ3RoPDM7cmV0dXJuIE4obSxrZSh2LDQpLFQsVixEZnQpfWZ1bmN0aW9uIEEwZShtLHYpe3ZhciBUPSRlKG0pP2ZtOk9mdDtyZXR1cm4gVChtLGJJKGtlKHYsMykpKX1mdW5jdGlvbiBQMGUobSl7dmFyIHY9JGUobSk/TGZ0OmJtZTtyZXR1cm4gdihtKX1mdW5jdGlvbiBJMGUobSx2LFQpeyhUP2thKG0sdixUKTp2PT09ZSk/dj0xOnY9UWUodik7dmFyIE49JGUobSk/SmRlOndtZTtyZXR1cm4gTihtLHYpfWZ1bmN0aW9uIEwwZShtKXt2YXIgdj0kZShtKT9RZGU6TW1lO3JldHVybiB2KG0pfWZ1bmN0aW9uIGswZShtKXtpZihtPT1udWxsKXJldHVybiAwO2lmKGRzKG0pKXJldHVybiBTSShtKT9keChtKTptLmxlbmd0aDt2YXIgdj1uYShtKTtyZXR1cm4gdj09cXx8dj09TGU/bS5zaXplOkNxKG0pLmxlbmd0aH1mdW5jdGlvbiBSMGUobSx2LFQpe3ZhciBOPSRlKG0pP2NxOkVtZTtyZXR1cm4gVCYma2EobSx2LFQpJiYodj1lKSxOKG0sa2UodiwzKSl9dmFyIE4wZT1ucihmdW5jdGlvbihtLHYpe2lmKG09PW51bGwpcmV0dXJuW107dmFyIFQ9di5sZW5ndGg7cmV0dXJuIFQ+MSYma2EobSx2WzBdLHZbMV0pP3Y9W106VD4yJiZrYSh2WzBdLHZbMV0sdlsyXSkmJih2PVt2WzBdXSksWWZ0KG0sUm8odiwxKSxbXSl9KSx2ST1kZGV8fGZ1bmN0aW9uKCl7cmV0dXJuIGZvLkRhdGUubm93KCl9O2Z1bmN0aW9uIEQwZShtLHYpe2lmKHR5cGVvZiB2IT0iZnVuY3Rpb24iKXRocm93IG5ldyBHbChpKTtyZXR1cm4gbT1RZShtKSxmdW5jdGlvbigpe2lmKC0tbTwxKXJldHVybiB2LmFwcGx5KHRoaXMsYXJndW1lbnRzKX19ZnVuY3Rpb24gSHB0KG0sdixUKXtyZXR1cm4gdj1UP2U6dix2PW0mJnY9PW51bGw/bS5sZW5ndGg6dixzcChtLEMsZSxlLGUsZSx2KX1mdW5jdGlvbiBWcHQobSx2KXt2YXIgVDtpZih0eXBlb2YgdiE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgR2woaSk7cmV0dXJuIG09UWUobSksZnVuY3Rpb24oKXtyZXR1cm4tLW0+MCYmKFQ9di5hcHBseSh0aGlzLGFyZ3VtZW50cykpLG08PTEmJih2PWUpLFR9fXZhciBacT1ucihmdW5jdGlvbihtLHYsVCl7dmFyIE49ZDtpZihULmxlbmd0aCl7dmFyIFY9ZG0oVCxieChacSkpO058PWJ9cmV0dXJuIHNwKG0sTix2LFQsVil9KSxVcHQ9bnIoZnVuY3Rpb24obSx2LFQpe3ZhciBOPWR8ZztpZihULmxlbmd0aCl7dmFyIFY9ZG0oVCxieChVcHQpKTtOfD1ifXJldHVybiBzcCh2LE4sbSxULFYpfSk7ZnVuY3Rpb24gcXB0KG0sdixUKXt2PVQ/ZTp2O3ZhciBOPXNwKG0seSxlLGUsZSxlLGUsdik7cmV0dXJuIE4ucGxhY2Vob2xkZXI9cXB0LnBsYWNlaG9sZGVyLE59ZnVuY3Rpb24gR3B0KG0sdixUKXt2PVQ/ZTp2O3ZhciBOPXNwKG0seCxlLGUsZSxlLGUsdik7cmV0dXJuIE4ucGxhY2Vob2xkZXI9R3B0LnBsYWNlaG9sZGVyLE59ZnVuY3Rpb24gV3B0KG0sdixUKXt2YXIgTixWLFksSixpdCxmdCxEdD0wLE90PSExLFZ0PSExLG9lPSEwO2lmKHR5cGVvZiBtIT0iZnVuY3Rpb24iKXRocm93IG5ldyBHbChpKTt2PSRsKHYpfHwwLHJpKFQpJiYoT3Q9ISFULmxlYWRpbmcsVnQ9Im1heFdhaXQiaW4gVCxZPVZ0P1hpKCRsKFQubWF4V2FpdCl8fDAsdik6WSxvZT0idHJhaWxpbmciaW4gVD8hIVQudHJhaWxpbmc6b2UpO2Z1bmN0aW9uIFRlKHdpKXt2YXIgbnU9TixmcD1WO3JldHVybiBOPVY9ZSxEdD13aSxKPW0uYXBwbHkoZnAsbnUpLEp9ZnVuY3Rpb24gTmUod2kpe3JldHVybiBEdD13aSxpdD16TShwcix2KSxPdD9UZSh3aSk6Sn1mdW5jdGlvbiBlcih3aSl7dmFyIG51PXdpLWZ0LGZwPXdpLUR0LHVkdD12LW51O3JldHVybiBWdD9yYSh1ZHQsWS1mcCk6dWR0fWZ1bmN0aW9uIERlKHdpKXt2YXIgbnU9d2ktZnQsZnA9d2ktRHQ7cmV0dXJuIGZ0PT09ZXx8bnU+PXZ8fG51PDB8fFZ0JiZmcD49WX1mdW5jdGlvbiBwcigpe3ZhciB3aT12SSgpO2lmKERlKHdpKSlyZXR1cm4geXIod2kpO2l0PXpNKHByLGVyKHdpKSl9ZnVuY3Rpb24geXIod2kpe3JldHVybiBpdD1lLG9lJiZOP1RlKHdpKTooTj1WPWUsSil9ZnVuY3Rpb24gWHMoKXtpdCE9PWUmJmVwdChpdCksRHQ9MCxOPWZ0PVY9aXQ9ZX1mdW5jdGlvbiBSYSgpe3JldHVybiBpdD09PWU/Sjp5cih2SSgpKX1mdW5jdGlvbiAkcygpe3ZhciB3aT12SSgpLG51PURlKHdpKTtpZihOPWFyZ3VtZW50cyxWPXRoaXMsZnQ9d2ksbnUpe2lmKGl0PT09ZSlyZXR1cm4gTmUoZnQpO2lmKFZ0KXJldHVybiBlcHQoaXQpLGl0PXpNKHByLHYpLFRlKGZ0KX1yZXR1cm4gaXQ9PT1lJiYoaXQ9ek0ocHIsdikpLEp9cmV0dXJuICRzLmNhbmNlbD1Ycywkcy5mbHVzaD1SYSwkc312YXIgTzBlPW5yKGZ1bmN0aW9uKG0sdil7cmV0dXJuIE5mdChtLDEsdil9KSx6MGU9bnIoZnVuY3Rpb24obSx2LFQpe3JldHVybiBOZnQobSwkbCh2KXx8MCxUKX0pO2Z1bmN0aW9uIEYwZShtKXtyZXR1cm4gc3AobSxrKX1mdW5jdGlvbiB4SShtLHYpe2lmKHR5cGVvZiBtIT0iZnVuY3Rpb24ifHx2IT1udWxsJiZ0eXBlb2YgdiE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgR2woaSk7dmFyIFQ9ZnVuY3Rpb24oKXt2YXIgTj1hcmd1bWVudHMsVj12P3YuYXBwbHkodGhpcyxOKTpOWzBdLFk9VC5jYWNoZTtpZihZLmhhcyhWKSlyZXR1cm4gWS5nZXQoVik7dmFyIEo9bS5hcHBseSh0aGlzLE4pO3JldHVybiBULmNhY2hlPVkuc2V0KFYsSil8fFksSn07cmV0dXJuIFQuY2FjaGU9bmV3KHhJLkNhY2hlfHxvcCksVH14SS5DYWNoZT1vcDtmdW5jdGlvbiBiSShtKXtpZih0eXBlb2YgbSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgR2woaSk7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHY9YXJndW1lbnRzO3N3aXRjaCh2Lmxlbmd0aCl7Y2FzZSAwOnJldHVybiFtLmNhbGwodGhpcyk7Y2FzZSAxOnJldHVybiFtLmNhbGwodGhpcyx2WzBdKTtjYXNlIDI6cmV0dXJuIW0uY2FsbCh0aGlzLHZbMF0sdlsxXSk7Y2FzZSAzOnJldHVybiFtLmNhbGwodGhpcyx2WzBdLHZbMV0sdlsyXSl9cmV0dXJuIW0uYXBwbHkodGhpcyx2KX19ZnVuY3Rpb24gQjBlKG0pe3JldHVybiBWcHQoMixtKX12YXIgSDBlPVRtZShmdW5jdGlvbihtLHYpe3Y9di5sZW5ndGg9PTEmJiRlKHZbMF0pP0tuKHZbMF0sV3Moa2UoKSkpOktuKFJvKHYsMSksV3Moa2UoKSkpO3ZhciBUPXYubGVuZ3RoO3JldHVybiBucihmdW5jdGlvbihOKXtmb3IodmFyIFY9LTEsWT1yYShOLmxlbmd0aCxUKTsrK1Y8WTspTltWXT12W1ZdLmNhbGwodGhpcyxOW1ZdKTtyZXR1cm4gR3MobSx0aGlzLE4pfSl9KSxKcT1ucihmdW5jdGlvbihtLHYpe3ZhciBUPWRtKHYsYngoSnEpKTtyZXR1cm4gc3AobSxiLGUsdixUKX0pLFlwdD1ucihmdW5jdGlvbihtLHYpe3ZhciBUPWRtKHYsYngoWXB0KSk7cmV0dXJuIHNwKG0sUyxlLHYsVCl9KSxWMGU9bHAoZnVuY3Rpb24obSx2KXtyZXR1cm4gc3AobSxQLGUsZSxlLHYpfSk7ZnVuY3Rpb24gVTBlKG0sdil7aWYodHlwZW9mIG0hPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEdsKGkpO3JldHVybiB2PXY9PT1lP3Y6UWUodiksbnIobSx2KX1mdW5jdGlvbiBxMGUobSx2KXtpZih0eXBlb2YgbSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgR2woaSk7cmV0dXJuIHY9dj09bnVsbD8wOlhpKFFlKHYpLDApLG5yKGZ1bmN0aW9uKFQpe3ZhciBOPVRbdl0sVj14bShULDAsdik7cmV0dXJuIE4mJnBtKFYsTiksR3MobSx0aGlzLFYpfSl9ZnVuY3Rpb24gRzBlKG0sdixUKXt2YXIgTj0hMCxWPSEwO2lmKHR5cGVvZiBtIT0iZnVuY3Rpb24iKXRocm93IG5ldyBHbChpKTtyZXR1cm4gcmkoVCkmJihOPSJsZWFkaW5nImluIFQ/ISFULmxlYWRpbmc6TixWPSJ0cmFpbGluZyJpbiBUPyEhVC50cmFpbGluZzpWKSxXcHQobSx2LHtsZWFkaW5nOk4sbWF4V2FpdDp2LHRyYWlsaW5nOlZ9KX1mdW5jdGlvbiBXMGUobSl7cmV0dXJuIEhwdChtLDEpfWZ1bmN0aW9uIFkwZShtLHYpe3JldHVybiBKcShPcSh2KSxtKX1mdW5jdGlvbiBqMGUoKXtpZighYXJndW1lbnRzLmxlbmd0aClyZXR1cm5bXTt2YXIgbT1hcmd1bWVudHNbMF07cmV0dXJuICRlKG0pP206W21dfWZ1bmN0aW9uIFgwZShtKXtyZXR1cm4gWWwobSxoKX1mdW5jdGlvbiAkMGUobSx2KXtyZXR1cm4gdj10eXBlb2Ygdj09ImZ1bmN0aW9uIj92OmUsWWwobSxoLHYpfWZ1bmN0aW9uIEswZShtKXtyZXR1cm4gWWwobSxjfGgpfWZ1bmN0aW9uIFowZShtLHYpe3JldHVybiB2PXR5cGVvZiB2PT0iZnVuY3Rpb24iP3Y6ZSxZbChtLGN8aCx2KX1mdW5jdGlvbiBKMGUobSx2KXtyZXR1cm4gdj09bnVsbHx8UmZ0KG0sdixwbyh2KSl9ZnVuY3Rpb24gcnUobSx2KXtyZXR1cm4gbT09PXZ8fG0hPT1tJiZ2IT09dn12YXIgUTBlPXBJKE1xKSx0X2U9cEkoZnVuY3Rpb24obSx2KXtyZXR1cm4gbT49dn0pLHNfPUJmdChmdW5jdGlvbigpe3JldHVybiBhcmd1bWVudHN9KCkpP0JmdDpmdW5jdGlvbihtKXtyZXR1cm4gbGkobSkmJnVuLmNhbGwobSwiY2FsbGVlIikmJiFFZnQuY2FsbChtLCJjYWxsZWUiKX0sJGU9dnQuaXNBcnJheSxlX2U9YWZ0P1dzKGFmdCk6Y21lO2Z1bmN0aW9uIGRzKG0pe3JldHVybiBtIT1udWxsJiZ3SShtLmxlbmd0aCkmJiF1cChtKX1mdW5jdGlvbiBiaShtKXtyZXR1cm4gbGkobSkmJmRzKG0pfWZ1bmN0aW9uIHJfZShtKXtyZXR1cm4gbT09PSEwfHxtPT09ITF8fGxpKG0pJiZMYShtKT09S3R9dmFyIGJtPWdkZXx8Y0csbl9lPXNmdD9XcyhzZnQpOnVtZTtmdW5jdGlvbiBpX2UobSl7cmV0dXJuIGxpKG0pJiZtLm5vZGVUeXBlPT09MSYmIUZNKG0pfWZ1bmN0aW9uIG9fZShtKXtpZihtPT1udWxsKXJldHVybiEwO2lmKGRzKG0pJiYoJGUobSl8fHR5cGVvZiBtPT0ic3RyaW5nInx8dHlwZW9mIG0uc3BsaWNlPT0iZnVuY3Rpb24ifHxibShtKXx8d3gobSl8fHNfKG0pKSlyZXR1cm4hbS5sZW5ndGg7dmFyIHY9bmEobSk7aWYodj09cXx8dj09TGUpcmV0dXJuIW0uc2l6ZTtpZihPTShtKSlyZXR1cm4hQ3EobSkubGVuZ3RoO2Zvcih2YXIgVCBpbiBtKWlmKHVuLmNhbGwobSxUKSlyZXR1cm4hMTtyZXR1cm4hMH1mdW5jdGlvbiBhX2UobSx2KXtyZXR1cm4gUk0obSx2KX1mdW5jdGlvbiBzX2UobSx2LFQpe1Q9dHlwZW9mIFQ9PSJmdW5jdGlvbiI/VDplO3ZhciBOPVQ/VChtLHYpOmU7cmV0dXJuIE49PT1lP1JNKG0sdixlLFQpOiEhTn1mdW5jdGlvbiBRcShtKXtpZighbGkobSkpcmV0dXJuITE7dmFyIHY9TGEobSk7cmV0dXJuIHY9PVh8fHY9PWN0fHx0eXBlb2YgbS5tZXNzYWdlPT0ic3RyaW5nIiYmdHlwZW9mIG0ubmFtZT09InN0cmluZyImJiFGTShtKX1mdW5jdGlvbiBsX2UobSl7cmV0dXJuIHR5cGVvZiBtPT0ibnVtYmVyIiYmQ2Z0KG0pfWZ1bmN0aW9uIHVwKG0pe2lmKCFyaShtKSlyZXR1cm4hMTt2YXIgdj1MYShtKTtyZXR1cm4gdj09ZXR8fHY9PWR0fHx2PT1sdHx8dj09aWV9ZnVuY3Rpb24ganB0KG0pe3JldHVybiB0eXBlb2YgbT09Im51bWJlciImJm09PVFlKG0pfWZ1bmN0aW9uIHdJKG0pe3JldHVybiB0eXBlb2YgbT09Im51bWJlciImJm0+LTEmJm0lMT09MCYmbTw9VX1mdW5jdGlvbiByaShtKXt2YXIgdj10eXBlb2YgbTtyZXR1cm4gbSE9bnVsbCYmKHY9PSJvYmplY3QifHx2PT0iZnVuY3Rpb24iKX1mdW5jdGlvbiBsaShtKXtyZXR1cm4gbSE9bnVsbCYmdHlwZW9mIG09PSJvYmplY3QifXZhciBYcHQ9bGZ0P1dzKGxmdCk6Zm1lO2Z1bmN0aW9uIGNfZShtLHYpe3JldHVybiBtPT09dnx8VHEobSx2LHFxKHYpKX1mdW5jdGlvbiB1X2UobSx2LFQpe3JldHVybiBUPXR5cGVvZiBUPT0iZnVuY3Rpb24iP1Q6ZSxUcShtLHYscXEodiksVCl9ZnVuY3Rpb24gaF9lKG0pe3JldHVybiAkcHQobSkmJm0hPSttfWZ1bmN0aW9uIGZfZShtKXtpZigkbWUobSkpdGhyb3cgbmV3IFdlKG4pO3JldHVybiBIZnQobSl9ZnVuY3Rpb24gcF9lKG0pe3JldHVybiBtPT09bnVsbH1mdW5jdGlvbiBkX2UobSl7cmV0dXJuIG09PW51bGx9ZnVuY3Rpb24gJHB0KG0pe3JldHVybiB0eXBlb2YgbT09Im51bWJlciJ8fGxpKG0pJiZMYShtKT09cHR9ZnVuY3Rpb24gRk0obSl7aWYoIWxpKG0pfHxMYShtKSE9d3QpcmV0dXJuITE7dmFyIHY9JDYobSk7aWYodj09PW51bGwpcmV0dXJuITA7dmFyIFQ9dW4uY2FsbCh2LCJjb25zdHJ1Y3RvciIpJiZ2LmNvbnN0cnVjdG9yO3JldHVybiB0eXBlb2YgVD09ImZ1bmN0aW9uIiYmVCBpbnN0YW5jZW9mIFQmJlc2LmNhbGwoVCk9PXVkZX12YXIgdEc9Y2Z0P1dzKGNmdCk6cG1lO2Z1bmN0aW9uIG1fZShtKXtyZXR1cm4ganB0KG0pJiZtPj0tVSYmbTw9VX12YXIgS3B0PXVmdD9Xcyh1ZnQpOmRtZTtmdW5jdGlvbiBTSShtKXtyZXR1cm4gdHlwZW9mIG09PSJzdHJpbmcifHwhJGUobSkmJmxpKG0pJiZMYShtKT09YXJ9ZnVuY3Rpb24ganMobSl7cmV0dXJuIHR5cGVvZiBtPT0ic3ltYm9sInx8bGkobSkmJkxhKG0pPT1mcn12YXIgd3g9aGZ0P1dzKGhmdCk6bW1lO2Z1bmN0aW9uIGdfZShtKXtyZXR1cm4gbT09PWV9ZnVuY3Rpb24gX19lKG0pe3JldHVybiBsaShtKSYmbmEobSk9PSR9ZnVuY3Rpb24geV9lKG0pe3JldHVybiBsaShtKSYmTGEobSk9PUl0fXZhciB2X2U9cEkoQXEpLHhfZT1wSShmdW5jdGlvbihtLHYpe3JldHVybiBtPD12fSk7ZnVuY3Rpb24gWnB0KG0pe2lmKCFtKXJldHVybltdO2lmKGRzKG0pKXJldHVybiBTSShtKT90dShtKTpwcyhtKTtpZihFTSYmbVtFTV0pcmV0dXJuIEpwZShtW0VNXSgpKTt2YXIgdj1uYShtKSxUPXY9PXE/bXE6dj09TGU/VTY6U3g7cmV0dXJuIFQobSl9ZnVuY3Rpb24gaHAobSl7aWYoIW0pcmV0dXJuIG09PT0wP206MDtpZihtPSRsKG0pLG09PT16fHxtPT09LXope3ZhciB2PW08MD8tMToxO3JldHVybiB2Kld9cmV0dXJuIG09PT1tP206MH1mdW5jdGlvbiBRZShtKXt2YXIgdj1ocChtKSxUPXYlMTtyZXR1cm4gdj09PXY/VD92LVQ6djowfWZ1bmN0aW9uIEpwdChtKXtyZXR1cm4gbT9uXyhRZShtKSwwLHJ0KTowfWZ1bmN0aW9uICRsKG0pe2lmKHR5cGVvZiBtPT0ibnVtYmVyIilyZXR1cm4gbTtpZihqcyhtKSlyZXR1cm4gWjtpZihyaShtKSl7dmFyIHY9dHlwZW9mIG0udmFsdWVPZj09ImZ1bmN0aW9uIj9tLnZhbHVlT2YoKTptO209cmkodik/disiIjp2fWlmKHR5cGVvZiBtIT0ic3RyaW5nIilyZXR1cm4gbT09PTA/bTorbTttPV9mdChtKTt2YXIgVD1fci50ZXN0KG0pO3JldHVybiBUfHxYbi50ZXN0KG0pP0RwZShtLnNsaWNlKDIpLFQ/Mjo4KTpYci50ZXN0KG0pP1o6K219ZnVuY3Rpb24gUXB0KG0pe3JldHVybiBnaChtLG1zKG0pKX1mdW5jdGlvbiBiX2UobSl7cmV0dXJuIG0/bl8oUWUobSksLVUsVSk6bT09PTA/bTowfWZ1bmN0aW9uIG9uKG0pe3JldHVybiBtPT1udWxsPyIiOllzKG0pfXZhciB3X2U9dngoZnVuY3Rpb24obSx2KXtpZihPTSh2KXx8ZHModikpe2doKHYscG8odiksbSk7cmV0dXJufWZvcih2YXIgVCBpbiB2KXVuLmNhbGwodixUKSYmSU0obSxULHZbVF0pfSksdGR0PXZ4KGZ1bmN0aW9uKG0sdil7Z2godixtcyh2KSxtKX0pLE1JPXZ4KGZ1bmN0aW9uKG0sdixULE4pe2doKHYsbXModiksbSxOKX0pLFNfZT12eChmdW5jdGlvbihtLHYsVCxOKXtnaCh2LHBvKHYpLG0sTil9KSxNX2U9bHAoYnEpO2Z1bmN0aW9uIEVfZShtLHYpe3ZhciBUPXl4KG0pO3JldHVybiB2PT1udWxsP1Q6a2Z0KFQsdil9dmFyIFRfZT1ucihmdW5jdGlvbihtLHYpe209dm4obSk7dmFyIFQ9LTEsTj12Lmxlbmd0aCxWPU4+Mj92WzJdOmU7Zm9yKFYmJmthKHZbMF0sdlsxXSxWKSYmKE49MSk7KytUPE47KWZvcih2YXIgWT12W1RdLEo9bXMoWSksaXQ9LTEsZnQ9Si5sZW5ndGg7KytpdDxmdDspe3ZhciBEdD1KW2l0XSxPdD1tW0R0XTsoT3Q9PT1lfHxydShPdCxteFtEdF0pJiYhdW4uY2FsbChtLER0KSkmJihtW0R0XT1ZW0R0XSl9cmV0dXJuIG19KSxDX2U9bnIoZnVuY3Rpb24obSl7cmV0dXJuIG0ucHVzaChlLF9wdCksR3MoZWR0LGUsbSl9KTtmdW5jdGlvbiBBX2UobSx2KXtyZXR1cm4gcGZ0KG0sa2UodiwzKSxtaCl9ZnVuY3Rpb24gUF9lKG0sdil7cmV0dXJuIHBmdChtLGtlKHYsMyksU3EpfWZ1bmN0aW9uIElfZShtLHYpe3JldHVybiBtPT1udWxsP206d3EobSxrZSh2LDMpLG1zKX1mdW5jdGlvbiBMX2UobSx2KXtyZXR1cm4gbT09bnVsbD9tOnpmdChtLGtlKHYsMyksbXMpfWZ1bmN0aW9uIGtfZShtLHYpe3JldHVybiBtJiZtaChtLGtlKHYsMykpfWZ1bmN0aW9uIFJfZShtLHYpe3JldHVybiBtJiZTcShtLGtlKHYsMykpfWZ1bmN0aW9uIE5fZShtKXtyZXR1cm4gbT09bnVsbD9bXTpvSShtLHBvKG0pKX1mdW5jdGlvbiBEX2UobSl7cmV0dXJuIG09PW51bGw/W106b0kobSxtcyhtKSl9ZnVuY3Rpb24gZUcobSx2LFQpe3ZhciBOPW09PW51bGw/ZTppXyhtLHYpO3JldHVybiBOPT09ZT9UOk59ZnVuY3Rpb24gT19lKG0sdil7cmV0dXJuIG0hPW51bGwmJnhwdChtLHYsb21lKX1mdW5jdGlvbiByRyhtLHYpe3JldHVybiBtIT1udWxsJiZ4cHQobSx2LGFtZSl9dmFyIHpfZT1mcHQoZnVuY3Rpb24obSx2LFQpe3YhPW51bGwmJnR5cGVvZiB2LnRvU3RyaW5nIT0iZnVuY3Rpb24iJiYodj1ZNi5jYWxsKHYpKSxtW3ZdPVR9LGlHKGdzKSksRl9lPWZwdChmdW5jdGlvbihtLHYsVCl7diE9bnVsbCYmdHlwZW9mIHYudG9TdHJpbmchPSJmdW5jdGlvbiImJih2PVk2LmNhbGwodikpLHVuLmNhbGwobSx2KT9tW3ZdLnB1c2goVCk6bVt2XT1bVF19LGtlKSxCX2U9bnIoa00pO2Z1bmN0aW9uIHBvKG0pe3JldHVybiBkcyhtKT9JZnQobSk6Q3EobSl9ZnVuY3Rpb24gbXMobSl7cmV0dXJuIGRzKG0pP0lmdChtLCEwKTpnbWUobSl9ZnVuY3Rpb24gSF9lKG0sdil7dmFyIFQ9e307cmV0dXJuIHY9a2UodiwzKSxtaChtLGZ1bmN0aW9uKE4sVixZKXthcChULHYoTixWLFkpLE4pfSksVH1mdW5jdGlvbiBWX2UobSx2KXt2YXIgVD17fTtyZXR1cm4gdj1rZSh2LDMpLG1oKG0sZnVuY3Rpb24oTixWLFkpe2FwKFQsVix2KE4sVixZKSl9KSxUfXZhciBVX2U9dngoZnVuY3Rpb24obSx2LFQpe2FJKG0sdixUKX0pLGVkdD12eChmdW5jdGlvbihtLHYsVCxOKXthSShtLHYsVCxOKX0pLHFfZT1scChmdW5jdGlvbihtLHYpe3ZhciBUPXt9O2lmKG09PW51bGwpcmV0dXJuIFQ7dmFyIE49ITE7dj1Lbih2LGZ1bmN0aW9uKFkpe3JldHVybiBZPXZtKFksbSksTnx8KE49WS5sZW5ndGg+MSksWX0pLGdoKG0sVnEobSksVCksTiYmKFQ9WWwoVCxjfHV8aCx6bWUpKTtmb3IodmFyIFY9di5sZW5ndGg7Vi0tOylScShULHZbVl0pO3JldHVybiBUfSk7ZnVuY3Rpb24gR19lKG0sdil7cmV0dXJuIHJkdChtLGJJKGtlKHYpKSl9dmFyIFdfZT1scChmdW5jdGlvbihtLHYpe3JldHVybiBtPT1udWxsP3t9OnltZShtLHYpfSk7ZnVuY3Rpb24gcmR0KG0sdil7aWYobT09bnVsbClyZXR1cm57fTt2YXIgVD1LbihWcShtKSxmdW5jdGlvbihOKXtyZXR1cm5bTl19KTtyZXR1cm4gdj1rZSh2KSxqZnQobSxULGZ1bmN0aW9uKE4sVil7cmV0dXJuIHYoTixWWzBdKX0pfWZ1bmN0aW9uIFlfZShtLHYsVCl7dj12bSh2LG0pO3ZhciBOPS0xLFY9di5sZW5ndGg7Zm9yKFZ8fChWPTEsbT1lKTsrK048Vjspe3ZhciBZPW09PW51bGw/ZTptW19oKHZbTl0pXTtZPT09ZSYmKE49VixZPVQpLG09dXAoWSk/WS5jYWxsKG0pOll9cmV0dXJuIG19ZnVuY3Rpb24gal9lKG0sdixUKXtyZXR1cm4gbT09bnVsbD9tOk5NKG0sdixUKX1mdW5jdGlvbiBYX2UobSx2LFQsTil7cmV0dXJuIE49dHlwZW9mIE49PSJmdW5jdGlvbiI/TjplLG09PW51bGw/bTpOTShtLHYsVCxOKX12YXIgbmR0PW1wdChwbyksaWR0PW1wdChtcyk7ZnVuY3Rpb24gJF9lKG0sdixUKXt2YXIgTj0kZShtKSxWPU58fGJtKG0pfHx3eChtKTtpZih2PWtlKHYsNCksVD09bnVsbCl7dmFyIFk9bSYmbS5jb25zdHJ1Y3RvcjtWP1Q9Tj9uZXcgWTpbXTpyaShtKT9UPXVwKFkpP3l4KCQ2KG0pKTp7fTpUPXt9fXJldHVybihWP3FsOm1oKShtLGZ1bmN0aW9uKEosaXQsZnQpe3JldHVybiB2KFQsSixpdCxmdCl9KSxUfWZ1bmN0aW9uIEtfZShtLHYpe3JldHVybiBtPT1udWxsPyEwOlJxKG0sdil9ZnVuY3Rpb24gWl9lKG0sdixUKXtyZXR1cm4gbT09bnVsbD9tOkpmdChtLHYsT3EoVCkpfWZ1bmN0aW9uIEpfZShtLHYsVCxOKXtyZXR1cm4gTj10eXBlb2YgTj09ImZ1bmN0aW9uIj9OOmUsbT09bnVsbD9tOkpmdChtLHYsT3EoVCksTil9ZnVuY3Rpb24gU3gobSl7cmV0dXJuIG09PW51bGw/W106ZHEobSxwbyhtKSl9ZnVuY3Rpb24gUV9lKG0pe3JldHVybiBtPT1udWxsP1tdOmRxKG0sbXMobSkpfWZ1bmN0aW9uIHR5ZShtLHYsVCl7cmV0dXJuIFQ9PT1lJiYoVD12LHY9ZSksVCE9PWUmJihUPSRsKFQpLFQ9VD09PVQ/VDowKSx2IT09ZSYmKHY9JGwodiksdj12PT09dj92OjApLG5fKCRsKG0pLHYsVCl9ZnVuY3Rpb24gZXllKG0sdixUKXtyZXR1cm4gdj1ocCh2KSxUPT09ZT8oVD12LHY9MCk6VD1ocChUKSxtPSRsKG0pLHNtZShtLHYsVCl9ZnVuY3Rpb24gcnllKG0sdixUKXtpZihUJiZ0eXBlb2YgVCE9ImJvb2xlYW4iJiZrYShtLHYsVCkmJih2PVQ9ZSksVD09PWUmJih0eXBlb2Ygdj09ImJvb2xlYW4iPyhUPXYsdj1lKTp0eXBlb2YgbT09ImJvb2xlYW4iJiYoVD1tLG09ZSkpLG09PT1lJiZ2PT09ZT8obT0wLHY9MSk6KG09aHAobSksdj09PWU/KHY9bSxtPTApOnY9aHAodikpLG0+dil7dmFyIE49bTttPXYsdj1OfWlmKFR8fG0lMXx8diUxKXt2YXIgVj1BZnQoKTtyZXR1cm4gcmEobStWKih2LW0rTnBlKCIxZS0iKygoVisiIikubGVuZ3RoLTEpKSksdil9cmV0dXJuIElxKG0sdil9dmFyIG55ZT14eChmdW5jdGlvbihtLHYsVCl7cmV0dXJuIHY9di50b0xvd2VyQ2FzZSgpLG0rKFQ/b2R0KHYpOnYpfSk7ZnVuY3Rpb24gb2R0KG0pe3JldHVybiBuRyhvbihtKS50b0xvd2VyQ2FzZSgpKX1mdW5jdGlvbiBhZHQobSl7cmV0dXJuIG09b24obSksbSYmbS5yZXBsYWNlKHVtLGpwZSkucmVwbGFjZShNcGUsIiIpfWZ1bmN0aW9uIGl5ZShtLHYsVCl7bT1vbihtKSx2PVlzKHYpO3ZhciBOPW0ubGVuZ3RoO1Q9VD09PWU/TjpuXyhRZShUKSwwLE4pO3ZhciBWPVQ7cmV0dXJuIFQtPXYubGVuZ3RoLFQ+PTAmJm0uc2xpY2UoVCxWKT09dn1mdW5jdGlvbiBveWUobSl7cmV0dXJuIG09b24obSksbSYmQXIudGVzdChtKT9tLnJlcGxhY2UoeW4sWHBlKTptfWZ1bmN0aW9uIGF5ZShtKXtyZXR1cm4gbT1vbihtKSxtJiZjeC50ZXN0KG0pP20ucmVwbGFjZShjbiwiXFwkJiIpOm19dmFyIHN5ZT14eChmdW5jdGlvbihtLHYsVCl7cmV0dXJuIG0rKFQ/Ii0iOiIiKSt2LnRvTG93ZXJDYXNlKCl9KSxseWU9eHgoZnVuY3Rpb24obSx2LFQpe3JldHVybiBtKyhUPyIgIjoiIikrdi50b0xvd2VyQ2FzZSgpfSksY3llPWNwdCgidG9Mb3dlckNhc2UiKTtmdW5jdGlvbiB1eWUobSx2LFQpe209b24obSksdj1RZSh2KTt2YXIgTj12P2R4KG0pOjA7aWYoIXZ8fE4+PXYpcmV0dXJuIG07dmFyIFY9KHYtTikvMjtyZXR1cm4gZkkoUTYoViksVCkrbStmSShKNihWKSxUKX1mdW5jdGlvbiBoeWUobSx2LFQpe209b24obSksdj1RZSh2KTt2YXIgTj12P2R4KG0pOjA7cmV0dXJuIHYmJk48dj9tK2ZJKHYtTixUKTptfWZ1bmN0aW9uIGZ5ZShtLHYsVCl7bT1vbihtKSx2PVFlKHYpO3ZhciBOPXY/ZHgobSk6MDtyZXR1cm4gdiYmTjx2P2ZJKHYtTixUKSttOm19ZnVuY3Rpb24gcHllKG0sdixUKXtyZXR1cm4gVHx8dj09bnVsbD92PTA6diYmKHY9K3YpLHhkZShvbihtKS5yZXBsYWNlKHJwLCIiKSx2fHwwKX1mdW5jdGlvbiBkeWUobSx2LFQpe3JldHVybihUP2thKG0sdixUKTp2PT09ZSk/dj0xOnY9UWUodiksTHEob24obSksdil9ZnVuY3Rpb24gbXllKCl7dmFyIG09YXJndW1lbnRzLHY9b24obVswXSk7cmV0dXJuIG0ubGVuZ3RoPDM/djp2LnJlcGxhY2UobVsxXSxtWzJdKX12YXIgZ3llPXh4KGZ1bmN0aW9uKG0sdixUKXtyZXR1cm4gbSsoVD8iXyI6IiIpK3YudG9Mb3dlckNhc2UoKX0pO2Z1bmN0aW9uIF95ZShtLHYsVCl7cmV0dXJuIFQmJnR5cGVvZiBUIT0ibnVtYmVyIiYma2EobSx2LFQpJiYodj1UPWUpLFQ9VD09PWU/cnQ6VD4+PjAsVD8obT1vbihtKSxtJiYodHlwZW9mIHY9PSJzdHJpbmcifHx2IT1udWxsJiYhdEcodikpJiYodj1Zcyh2KSwhdiYmcHgobSkpP3htKHR1KG0pLDAsVCk6bS5zcGxpdCh2LFQpKTpbXX12YXIgeXllPXh4KGZ1bmN0aW9uKG0sdixUKXtyZXR1cm4gbSsoVD8iICI6IiIpK25HKHYpfSk7ZnVuY3Rpb24gdnllKG0sdixUKXtyZXR1cm4gbT1vbihtKSxUPVQ9PW51bGw/MDpuXyhRZShUKSwwLG0ubGVuZ3RoKSx2PVlzKHYpLG0uc2xpY2UoVCxUK3YubGVuZ3RoKT09dn1mdW5jdGlvbiB4eWUobSx2LFQpe3ZhciBOPUcudGVtcGxhdGVTZXR0aW5ncztUJiZrYShtLHYsVCkmJih2PWUpLG09b24obSksdj1NSSh7fSx2LE4sZ3B0KTt2YXIgVj1NSSh7fSx2LmltcG9ydHMsTi5pbXBvcnRzLGdwdCksWT1wbyhWKSxKPWRxKFYsWSksaXQsZnQsRHQ9MCxPdD12LmludGVycG9sYXRlfHxtcixWdD0iX19wICs9ICciLG9lPWdxKCh2LmVzY2FwZXx8bXIpLnNvdXJjZSsifCIrT3Quc291cmNlKyJ8IisoT3Q9PT1JYT90cjptcikuc291cmNlKyJ8Iisodi5ldmFsdWF0ZXx8bXIpLnNvdXJjZSsifCQiLCJnIiksVGU9Ii8vIyBzb3VyY2VVUkw9IisodW4uY2FsbCh2LCJzb3VyY2VVUkwiKT8odi5zb3VyY2VVUkwrIiIpLnJlcGxhY2UoL1xzL2csIiAiKToibG9kYXNoLnRlbXBsYXRlU291cmNlc1siKyArK1BwZSsiXSIpK2AKYDttLnJlcGxhY2Uob2UsZnVuY3Rpb24oRGUscHIseXIsWHMsUmEsJHMpe3JldHVybiB5cnx8KHlyPVhzKSxWdCs9bS5zbGljZShEdCwkcykucmVwbGFjZShGbCwkcGUpLHByJiYoaXQ9ITAsVnQrPWAnICsKX19lKGArcHIrYCkgKwonYCksUmEmJihmdD0hMCxWdCs9YCc7CmArUmErYDsKX19wICs9ICdgKSx5ciYmKFZ0Kz1gJyArCigoX190ID0gKGAreXIrYCkpID09IG51bGwgPyAnJyA6IF9fdCkgKwonYCksRHQ9JHMrRGUubGVuZ3RoLERlfSksVnQrPWAnOwpgO3ZhciBOZT11bi5jYWxsKHYsInZhcmlhYmxlIikmJnYudmFyaWFibGU7aWYoIU5lKVZ0PWB3aXRoIChvYmopIHsKYCtWdCtgCn0KYDtlbHNlIGlmKFZlLnRlc3QoTmUpKXRocm93IG5ldyBXZShvKTtWdD0oZnQ/VnQucmVwbGFjZShDZSwiIik6VnQpLnJlcGxhY2UoUHQsIiQxIikucmVwbGFjZShOdCwiJDE7IiksVnQ9ImZ1bmN0aW9uKCIrKE5lfHwib2JqIikrYCkgewpgKyhOZT8iIjpgb2JqIHx8IChvYmogPSB7fSk7CmApKyJ2YXIgX190LCBfX3AgPSAnJyIrKGl0PyIsIF9fZSA9IF8uZXNjYXBlIjoiIikrKGZ0P2AsIF9faiA9IEFycmF5LnByb3RvdHlwZS5qb2luOwpmdW5jdGlvbiBwcmludCgpIHsgX19wICs9IF9fai5jYWxsKGFyZ3VtZW50cywgJycpIH0KYDpgOwpgKStWdCtgcmV0dXJuIF9fcAp9YDt2YXIgZXI9bGR0KGZ1bmN0aW9uKCl7cmV0dXJuICRyKFksVGUrInJldHVybiAiK1Z0KS5hcHBseShlLEopfSk7aWYoZXIuc291cmNlPVZ0LFFxKGVyKSl0aHJvdyBlcjtyZXR1cm4gZXJ9ZnVuY3Rpb24gYnllKG0pe3JldHVybiBvbihtKS50b0xvd2VyQ2FzZSgpfWZ1bmN0aW9uIHd5ZShtKXtyZXR1cm4gb24obSkudG9VcHBlckNhc2UoKX1mdW5jdGlvbiBTeWUobSx2LFQpe2lmKG09b24obSksbSYmKFR8fHY9PT1lKSlyZXR1cm4gX2Z0KG0pO2lmKCFtfHwhKHY9WXModikpKXJldHVybiBtO3ZhciBOPXR1KG0pLFY9dHUodiksWT15ZnQoTixWKSxKPXZmdChOLFYpKzE7cmV0dXJuIHhtKE4sWSxKKS5qb2luKCIiKX1mdW5jdGlvbiBNeWUobSx2LFQpe2lmKG09b24obSksbSYmKFR8fHY9PT1lKSlyZXR1cm4gbS5zbGljZSgwLGJmdChtKSsxKTtpZighbXx8ISh2PVlzKHYpKSlyZXR1cm4gbTt2YXIgTj10dShtKSxWPXZmdChOLHR1KHYpKSsxO3JldHVybiB4bShOLDAsVikuam9pbigiIil9ZnVuY3Rpb24gRXllKG0sdixUKXtpZihtPW9uKG0pLG0mJihUfHx2PT09ZSkpcmV0dXJuIG0ucmVwbGFjZShycCwiIik7aWYoIW18fCEodj1Zcyh2KSkpcmV0dXJuIG07dmFyIE49dHUobSksVj15ZnQoTix0dSh2KSk7cmV0dXJuIHhtKE4sVikuam9pbigiIil9ZnVuY3Rpb24gVHllKG0sdil7dmFyIFQ9TyxOPUQ7aWYocmkodikpe3ZhciBWPSJzZXBhcmF0b3IiaW4gdj92LnNlcGFyYXRvcjpWO1Q9Imxlbmd0aCJpbiB2P1FlKHYubGVuZ3RoKTpULE49Im9taXNzaW9uImluIHY/WXModi5vbWlzc2lvbik6Tn1tPW9uKG0pO3ZhciBZPW0ubGVuZ3RoO2lmKHB4KG0pKXt2YXIgSj10dShtKTtZPUoubGVuZ3RofWlmKFQ+PVkpcmV0dXJuIG07dmFyIGl0PVQtZHgoTik7aWYoaXQ8MSlyZXR1cm4gTjt2YXIgZnQ9Sj94bShKLDAsaXQpLmpvaW4oIiIpOm0uc2xpY2UoMCxpdCk7aWYoVj09PWUpcmV0dXJuIGZ0K047aWYoSiYmKGl0Kz1mdC5sZW5ndGgtaXQpLHRHKFYpKXtpZihtLnNsaWNlKGl0KS5zZWFyY2goVikpe3ZhciBEdCxPdD1mdDtmb3IoVi5nbG9iYWx8fChWPWdxKFYuc291cmNlLG9uKEtlLmV4ZWMoVikpKyJnIikpLFYubGFzdEluZGV4PTA7RHQ9Vi5leGVjKE90KTspdmFyIFZ0PUR0LmluZGV4O2Z0PWZ0LnNsaWNlKDAsVnQ9PT1lP2l0OlZ0KX19ZWxzZSBpZihtLmluZGV4T2YoWXMoViksaXQpIT1pdCl7dmFyIG9lPWZ0Lmxhc3RJbmRleE9mKFYpO29lPi0xJiYoZnQ9ZnQuc2xpY2UoMCxvZSkpfXJldHVybiBmdCtOfWZ1bmN0aW9uIEN5ZShtKXtyZXR1cm4gbT1vbihtKSxtJiZXaS50ZXN0KG0pP20ucmVwbGFjZSh6ZSxyZGUpOm19dmFyIEF5ZT14eChmdW5jdGlvbihtLHYsVCl7cmV0dXJuIG0rKFQ/IiAiOiIiKSt2LnRvVXBwZXJDYXNlKCl9KSxuRz1jcHQoInRvVXBwZXJDYXNlIik7ZnVuY3Rpb24gc2R0KG0sdixUKXtyZXR1cm4gbT1vbihtKSx2PVQ/ZTp2LHY9PT1lP1pwZShtKT9vZGUobSk6VXBlKG0pOm0ubWF0Y2godil8fFtdfXZhciBsZHQ9bnIoZnVuY3Rpb24obSx2KXt0cnl7cmV0dXJuIEdzKG0sZSx2KX1jYXRjaChUKXtyZXR1cm4gUXEoVCk/VDpuZXcgV2UoVCl9fSksUHllPWxwKGZ1bmN0aW9uKG0sdil7cmV0dXJuIHFsKHYsZnVuY3Rpb24oVCl7VD1faChUKSxhcChtLFQsWnEobVtUXSxtKSl9KSxtfSk7ZnVuY3Rpb24gSXllKG0pe3ZhciB2PW09PW51bGw/MDptLmxlbmd0aCxUPWtlKCk7cmV0dXJuIG09dj9LbihtLGZ1bmN0aW9uKE4pe2lmKHR5cGVvZiBOWzFdIT0iZnVuY3Rpb24iKXRocm93IG5ldyBHbChpKTtyZXR1cm5bVChOWzBdKSxOWzFdXX0pOltdLG5yKGZ1bmN0aW9uKE4pe2Zvcih2YXIgVj0tMTsrK1Y8djspe3ZhciBZPW1bVl07aWYoR3MoWVswXSx0aGlzLE4pKXJldHVybiBHcyhZWzFdLHRoaXMsTil9fSl9ZnVuY3Rpb24gTHllKG0pe3JldHVybiBybWUoWWwobSxjKSl9ZnVuY3Rpb24gaUcobSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIG19fWZ1bmN0aW9uIGt5ZShtLHYpe3JldHVybiBtPT1udWxsfHxtIT09bT92Om19dmFyIFJ5ZT1ocHQoKSxOeWU9aHB0KCEwKTtmdW5jdGlvbiBncyhtKXtyZXR1cm4gbX1mdW5jdGlvbiBvRyhtKXtyZXR1cm4gVmZ0KHR5cGVvZiBtPT0iZnVuY3Rpb24iP206WWwobSxjKSl9ZnVuY3Rpb24gRHllKG0pe3JldHVybiBxZnQoWWwobSxjKSl9ZnVuY3Rpb24gT3llKG0sdil7cmV0dXJuIEdmdChtLFlsKHYsYykpfXZhciB6eWU9bnIoZnVuY3Rpb24obSx2KXtyZXR1cm4gZnVuY3Rpb24oVCl7cmV0dXJuIGtNKFQsbSx2KX19KSxGeWU9bnIoZnVuY3Rpb24obSx2KXtyZXR1cm4gZnVuY3Rpb24oVCl7cmV0dXJuIGtNKG0sVCx2KX19KTtmdW5jdGlvbiBhRyhtLHYsVCl7dmFyIE49cG8odiksVj1vSSh2LE4pO1Q9PW51bGwmJiEocmkodikmJihWLmxlbmd0aHx8IU4ubGVuZ3RoKSkmJihUPXYsdj1tLG09dGhpcyxWPW9JKHYscG8odikpKTt2YXIgWT0hKHJpKFQpJiYiY2hhaW4iaW4gVCl8fCEhVC5jaGFpbixKPXVwKG0pO3JldHVybiBxbChWLGZ1bmN0aW9uKGl0KXt2YXIgZnQ9dltpdF07bVtpdF09ZnQsSiYmKG0ucHJvdG90eXBlW2l0XT1mdW5jdGlvbigpe3ZhciBEdD10aGlzLl9fY2hhaW5fXztpZihZfHxEdCl7dmFyIE90PW0odGhpcy5fX3dyYXBwZWRfXyksVnQ9T3QuX19hY3Rpb25zX189cHModGhpcy5fX2FjdGlvbnNfXyk7cmV0dXJuIFZ0LnB1c2goe2Z1bmM6ZnQsYXJnczphcmd1bWVudHMsdGhpc0FyZzptfSksT3QuX19jaGFpbl9fPUR0LE90fXJldHVybiBmdC5hcHBseShtLHBtKFt0aGlzLnZhbHVlKCldLGFyZ3VtZW50cykpfSl9KSxtfWZ1bmN0aW9uIEJ5ZSgpe3JldHVybiBmby5fPT09dGhpcyYmKGZvLl89aGRlKSx0aGlzfWZ1bmN0aW9uIHNHKCl7fWZ1bmN0aW9uIEh5ZShtKXtyZXR1cm4gbT1RZShtKSxucihmdW5jdGlvbih2KXtyZXR1cm4gV2Z0KHYsbSl9KX12YXIgVnllPUZxKEtuKSxVeWU9RnEoZmZ0KSxxeWU9RnEoY3EpO2Z1bmN0aW9uIGNkdChtKXtyZXR1cm4gV3EobSk/dXEoX2gobSkpOnZtZShtKX1mdW5jdGlvbiBHeWUobSl7cmV0dXJuIGZ1bmN0aW9uKHYpe3JldHVybiBtPT1udWxsP2U6aV8obSx2KX19dmFyIFd5ZT1wcHQoKSxZeWU9cHB0KCEwKTtmdW5jdGlvbiBsRygpe3JldHVybltdfWZ1bmN0aW9uIGNHKCl7cmV0dXJuITF9ZnVuY3Rpb24ganllKCl7cmV0dXJue319ZnVuY3Rpb24gWHllKCl7cmV0dXJuIiJ9ZnVuY3Rpb24gJHllKCl7cmV0dXJuITB9ZnVuY3Rpb24gS3llKG0sdil7aWYobT1RZShtKSxtPDF8fG0+VSlyZXR1cm5bXTt2YXIgVD1ydCxOPXJhKG0scnQpO3Y9a2UodiksbS09cnQ7Zm9yKHZhciBWPXBxKE4sdik7KytUPG07KXYoVCk7cmV0dXJuIFZ9ZnVuY3Rpb24gWnllKG0pe3JldHVybiAkZShtKT9LbihtLF9oKTpqcyhtKT9bbV06cHMoUHB0KG9uKG0pKSl9ZnVuY3Rpb24gSnllKG0pe3ZhciB2PSsrY2RlO3JldHVybiBvbihtKSt2fXZhciBReWU9aEkoZnVuY3Rpb24obSx2KXtyZXR1cm4gbSt2fSwwKSx0MWU9QnEoImNlaWwiKSxlMWU9aEkoZnVuY3Rpb24obSx2KXtyZXR1cm4gbS92fSwxKSxyMWU9QnEoImZsb29yIik7ZnVuY3Rpb24gbjFlKG0pe3JldHVybiBtJiZtLmxlbmd0aD9pSShtLGdzLE1xKTplfWZ1bmN0aW9uIGkxZShtLHYpe3JldHVybiBtJiZtLmxlbmd0aD9pSShtLGtlKHYsMiksTXEpOmV9ZnVuY3Rpb24gbzFlKG0pe3JldHVybiBtZnQobSxncyl9ZnVuY3Rpb24gYTFlKG0sdil7cmV0dXJuIG1mdChtLGtlKHYsMikpfWZ1bmN0aW9uIHMxZShtKXtyZXR1cm4gbSYmbS5sZW5ndGg/aUkobSxncyxBcSk6ZX1mdW5jdGlvbiBsMWUobSx2KXtyZXR1cm4gbSYmbS5sZW5ndGg/aUkobSxrZSh2LDIpLEFxKTplfXZhciBjMWU9aEkoZnVuY3Rpb24obSx2KXtyZXR1cm4gbSp2fSwxKSx1MWU9QnEoInJvdW5kIiksaDFlPWhJKGZ1bmN0aW9uKG0sdil7cmV0dXJuIG0tdn0sMCk7ZnVuY3Rpb24gZjFlKG0pe3JldHVybiBtJiZtLmxlbmd0aD9mcShtLGdzKTowfWZ1bmN0aW9uIHAxZShtLHYpe3JldHVybiBtJiZtLmxlbmd0aD9mcShtLGtlKHYsMikpOjB9cmV0dXJuIEcuYWZ0ZXI9RDBlLEcuYXJ5PUhwdCxHLmFzc2lnbj13X2UsRy5hc3NpZ25Jbj10ZHQsRy5hc3NpZ25JbldpdGg9TUksRy5hc3NpZ25XaXRoPVNfZSxHLmF0PU1fZSxHLmJlZm9yZT1WcHQsRy5iaW5kPVpxLEcuYmluZEFsbD1QeWUsRy5iaW5kS2V5PVVwdCxHLmNhc3RBcnJheT1qMGUsRy5jaGFpbj16cHQsRy5jaHVuaz1yZ2UsRy5jb21wYWN0PW5nZSxHLmNvbmNhdD1pZ2UsRy5jb25kPUl5ZSxHLmNvbmZvcm1zPUx5ZSxHLmNvbnN0YW50PWlHLEcuY291bnRCeT1mMGUsRy5jcmVhdGU9RV9lLEcuY3Vycnk9cXB0LEcuY3VycnlSaWdodD1HcHQsRy5kZWJvdW5jZT1XcHQsRy5kZWZhdWx0cz1UX2UsRy5kZWZhdWx0c0RlZXA9Q19lLEcuZGVmZXI9TzBlLEcuZGVsYXk9ejBlLEcuZGlmZmVyZW5jZT1vZ2UsRy5kaWZmZXJlbmNlQnk9YWdlLEcuZGlmZmVyZW5jZVdpdGg9c2dlLEcuZHJvcD1sZ2UsRy5kcm9wUmlnaHQ9Y2dlLEcuZHJvcFJpZ2h0V2hpbGU9dWdlLEcuZHJvcFdoaWxlPWhnZSxHLmZpbGw9ZmdlLEcuZmlsdGVyPWQwZSxHLmZsYXRNYXA9XzBlLEcuZmxhdE1hcERlZXA9eTBlLEcuZmxhdE1hcERlcHRoPXYwZSxHLmZsYXR0ZW49UnB0LEcuZmxhdHRlbkRlZXA9cGdlLEcuZmxhdHRlbkRlcHRoPWRnZSxHLmZsaXA9RjBlLEcuZmxvdz1SeWUsRy5mbG93UmlnaHQ9TnllLEcuZnJvbVBhaXJzPW1nZSxHLmZ1bmN0aW9ucz1OX2UsRy5mdW5jdGlvbnNJbj1EX2UsRy5ncm91cEJ5PXgwZSxHLmluaXRpYWw9X2dlLEcuaW50ZXJzZWN0aW9uPXlnZSxHLmludGVyc2VjdGlvbkJ5PXZnZSxHLmludGVyc2VjdGlvbldpdGg9eGdlLEcuaW52ZXJ0PXpfZSxHLmludmVydEJ5PUZfZSxHLmludm9rZU1hcD13MGUsRy5pdGVyYXRlZT1vRyxHLmtleUJ5PVMwZSxHLmtleXM9cG8sRy5rZXlzSW49bXMsRy5tYXA9eUksRy5tYXBLZXlzPUhfZSxHLm1hcFZhbHVlcz1WX2UsRy5tYXRjaGVzPUR5ZSxHLm1hdGNoZXNQcm9wZXJ0eT1PeWUsRy5tZW1vaXplPXhJLEcubWVyZ2U9VV9lLEcubWVyZ2VXaXRoPWVkdCxHLm1ldGhvZD16eWUsRy5tZXRob2RPZj1GeWUsRy5taXhpbj1hRyxHLm5lZ2F0ZT1iSSxHLm50aEFyZz1IeWUsRy5vbWl0PXFfZSxHLm9taXRCeT1HX2UsRy5vbmNlPUIwZSxHLm9yZGVyQnk9TTBlLEcub3Zlcj1WeWUsRy5vdmVyQXJncz1IMGUsRy5vdmVyRXZlcnk9VXllLEcub3ZlclNvbWU9cXllLEcucGFydGlhbD1KcSxHLnBhcnRpYWxSaWdodD1ZcHQsRy5wYXJ0aXRpb249RTBlLEcucGljaz1XX2UsRy5waWNrQnk9cmR0LEcucHJvcGVydHk9Y2R0LEcucHJvcGVydHlPZj1HeWUsRy5wdWxsPU1nZSxHLnB1bGxBbGw9RHB0LEcucHVsbEFsbEJ5PUVnZSxHLnB1bGxBbGxXaXRoPVRnZSxHLnB1bGxBdD1DZ2UsRy5yYW5nZT1XeWUsRy5yYW5nZVJpZ2h0PVl5ZSxHLnJlYXJnPVYwZSxHLnJlamVjdD1BMGUsRy5yZW1vdmU9QWdlLEcucmVzdD1VMGUsRy5yZXZlcnNlPSRxLEcuc2FtcGxlU2l6ZT1JMGUsRy5zZXQ9al9lLEcuc2V0V2l0aD1YX2UsRy5zaHVmZmxlPUwwZSxHLnNsaWNlPVBnZSxHLnNvcnRCeT1OMGUsRy5zb3J0ZWRVbmlxPU9nZSxHLnNvcnRlZFVuaXFCeT16Z2UsRy5zcGxpdD1feWUsRy5zcHJlYWQ9cTBlLEcudGFpbD1GZ2UsRy50YWtlPUJnZSxHLnRha2VSaWdodD1IZ2UsRy50YWtlUmlnaHRXaGlsZT1WZ2UsRy50YWtlV2hpbGU9VWdlLEcudGFwPW4wZSxHLnRocm90dGxlPUcwZSxHLnRocnU9X0ksRy50b0FycmF5PVpwdCxHLnRvUGFpcnM9bmR0LEcudG9QYWlyc0luPWlkdCxHLnRvUGF0aD1aeWUsRy50b1BsYWluT2JqZWN0PVFwdCxHLnRyYW5zZm9ybT0kX2UsRy51bmFyeT1XMGUsRy51bmlvbj1xZ2UsRy51bmlvbkJ5PUdnZSxHLnVuaW9uV2l0aD1XZ2UsRy51bmlxPVlnZSxHLnVuaXFCeT1qZ2UsRy51bmlxV2l0aD1YZ2UsRy51bnNldD1LX2UsRy51bnppcD1LcSxHLnVuemlwV2l0aD1PcHQsRy51cGRhdGU9Wl9lLEcudXBkYXRlV2l0aD1KX2UsRy52YWx1ZXM9U3gsRy52YWx1ZXNJbj1RX2UsRy53aXRob3V0PSRnZSxHLndvcmRzPXNkdCxHLndyYXA9WTBlLEcueG9yPUtnZSxHLnhvckJ5PVpnZSxHLnhvcldpdGg9SmdlLEcuemlwPVFnZSxHLnppcE9iamVjdD10MGUsRy56aXBPYmplY3REZWVwPWUwZSxHLnppcFdpdGg9cjBlLEcuZW50cmllcz1uZHQsRy5lbnRyaWVzSW49aWR0LEcuZXh0ZW5kPXRkdCxHLmV4dGVuZFdpdGg9TUksYUcoRyxHKSxHLmFkZD1ReWUsRy5hdHRlbXB0PWxkdCxHLmNhbWVsQ2FzZT1ueWUsRy5jYXBpdGFsaXplPW9kdCxHLmNlaWw9dDFlLEcuY2xhbXA9dHllLEcuY2xvbmU9WDBlLEcuY2xvbmVEZWVwPUswZSxHLmNsb25lRGVlcFdpdGg9WjBlLEcuY2xvbmVXaXRoPSQwZSxHLmNvbmZvcm1zVG89SjBlLEcuZGVidXJyPWFkdCxHLmRlZmF1bHRUbz1reWUsRy5kaXZpZGU9ZTFlLEcuZW5kc1dpdGg9aXllLEcuZXE9cnUsRy5lc2NhcGU9b3llLEcuZXNjYXBlUmVnRXhwPWF5ZSxHLmV2ZXJ5PXAwZSxHLmZpbmQ9bTBlLEcuZmluZEluZGV4PUxwdCxHLmZpbmRLZXk9QV9lLEcuZmluZExhc3Q9ZzBlLEcuZmluZExhc3RJbmRleD1rcHQsRy5maW5kTGFzdEtleT1QX2UsRy5mbG9vcj1yMWUsRy5mb3JFYWNoPUZwdCxHLmZvckVhY2hSaWdodD1CcHQsRy5mb3JJbj1JX2UsRy5mb3JJblJpZ2h0PUxfZSxHLmZvck93bj1rX2UsRy5mb3JPd25SaWdodD1SX2UsRy5nZXQ9ZUcsRy5ndD1RMGUsRy5ndGU9dF9lLEcuaGFzPU9fZSxHLmhhc0luPXJHLEcuaGVhZD1OcHQsRy5pZGVudGl0eT1ncyxHLmluY2x1ZGVzPWIwZSxHLmluZGV4T2Y9Z2dlLEcuaW5SYW5nZT1leWUsRy5pbnZva2U9Ql9lLEcuaXNBcmd1bWVudHM9c18sRy5pc0FycmF5PSRlLEcuaXNBcnJheUJ1ZmZlcj1lX2UsRy5pc0FycmF5TGlrZT1kcyxHLmlzQXJyYXlMaWtlT2JqZWN0PWJpLEcuaXNCb29sZWFuPXJfZSxHLmlzQnVmZmVyPWJtLEcuaXNEYXRlPW5fZSxHLmlzRWxlbWVudD1pX2UsRy5pc0VtcHR5PW9fZSxHLmlzRXF1YWw9YV9lLEcuaXNFcXVhbFdpdGg9c19lLEcuaXNFcnJvcj1RcSxHLmlzRmluaXRlPWxfZSxHLmlzRnVuY3Rpb249dXAsRy5pc0ludGVnZXI9anB0LEcuaXNMZW5ndGg9d0ksRy5pc01hcD1YcHQsRy5pc01hdGNoPWNfZSxHLmlzTWF0Y2hXaXRoPXVfZSxHLmlzTmFOPWhfZSxHLmlzTmF0aXZlPWZfZSxHLmlzTmlsPWRfZSxHLmlzTnVsbD1wX2UsRy5pc051bWJlcj0kcHQsRy5pc09iamVjdD1yaSxHLmlzT2JqZWN0TGlrZT1saSxHLmlzUGxhaW5PYmplY3Q9Rk0sRy5pc1JlZ0V4cD10RyxHLmlzU2FmZUludGVnZXI9bV9lLEcuaXNTZXQ9S3B0LEcuaXNTdHJpbmc9U0ksRy5pc1N5bWJvbD1qcyxHLmlzVHlwZWRBcnJheT13eCxHLmlzVW5kZWZpbmVkPWdfZSxHLmlzV2Vha01hcD1fX2UsRy5pc1dlYWtTZXQ9eV9lLEcuam9pbj1iZ2UsRy5rZWJhYkNhc2U9c3llLEcubGFzdD1YbCxHLmxhc3RJbmRleE9mPXdnZSxHLmxvd2VyQ2FzZT1seWUsRy5sb3dlckZpcnN0PWN5ZSxHLmx0PXZfZSxHLmx0ZT14X2UsRy5tYXg9bjFlLEcubWF4Qnk9aTFlLEcubWVhbj1vMWUsRy5tZWFuQnk9YTFlLEcubWluPXMxZSxHLm1pbkJ5PWwxZSxHLnN0dWJBcnJheT1sRyxHLnN0dWJGYWxzZT1jRyxHLnN0dWJPYmplY3Q9anllLEcuc3R1YlN0cmluZz1YeWUsRy5zdHViVHJ1ZT0keWUsRy5tdWx0aXBseT1jMWUsRy5udGg9U2dlLEcubm9Db25mbGljdD1CeWUsRy5ub29wPXNHLEcubm93PXZJLEcucGFkPXV5ZSxHLnBhZEVuZD1oeWUsRy5wYWRTdGFydD1meWUsRy5wYXJzZUludD1weWUsRy5yYW5kb209cnllLEcucmVkdWNlPVQwZSxHLnJlZHVjZVJpZ2h0PUMwZSxHLnJlcGVhdD1keWUsRy5yZXBsYWNlPW15ZSxHLnJlc3VsdD1ZX2UsRy5yb3VuZD11MWUsRy5ydW5JbkNvbnRleHQ9dXQsRy5zYW1wbGU9UDBlLEcuc2l6ZT1rMGUsRy5zbmFrZUNhc2U9Z3llLEcuc29tZT1SMGUsRy5zb3J0ZWRJbmRleD1JZ2UsRy5zb3J0ZWRJbmRleEJ5PUxnZSxHLnNvcnRlZEluZGV4T2Y9a2dlLEcuc29ydGVkTGFzdEluZGV4PVJnZSxHLnNvcnRlZExhc3RJbmRleEJ5PU5nZSxHLnNvcnRlZExhc3RJbmRleE9mPURnZSxHLnN0YXJ0Q2FzZT15eWUsRy5zdGFydHNXaXRoPXZ5ZSxHLnN1YnRyYWN0PWgxZSxHLnN1bT1mMWUsRy5zdW1CeT1wMWUsRy50ZW1wbGF0ZT14eWUsRy50aW1lcz1LeWUsRy50b0Zpbml0ZT1ocCxHLnRvSW50ZWdlcj1RZSxHLnRvTGVuZ3RoPUpwdCxHLnRvTG93ZXI9YnllLEcudG9OdW1iZXI9JGwsRy50b1NhZmVJbnRlZ2VyPWJfZSxHLnRvU3RyaW5nPW9uLEcudG9VcHBlcj13eWUsRy50cmltPVN5ZSxHLnRyaW1FbmQ9TXllLEcudHJpbVN0YXJ0PUV5ZSxHLnRydW5jYXRlPVR5ZSxHLnVuZXNjYXBlPUN5ZSxHLnVuaXF1ZUlkPUp5ZSxHLnVwcGVyQ2FzZT1BeWUsRy51cHBlckZpcnN0PW5HLEcuZWFjaD1GcHQsRy5lYWNoUmlnaHQ9QnB0LEcuZmlyc3Q9TnB0LGFHKEcsZnVuY3Rpb24oKXt2YXIgbT17fTtyZXR1cm4gbWgoRyxmdW5jdGlvbih2LFQpe3VuLmNhbGwoRy5wcm90b3R5cGUsVCl8fChtW1RdPXYpfSksbX0oKSx7Y2hhaW46ITF9KSxHLlZFUlNJT049dCxxbChbImJpbmQiLCJiaW5kS2V5IiwiY3VycnkiLCJjdXJyeVJpZ2h0IiwicGFydGlhbCIsInBhcnRpYWxSaWdodCJdLGZ1bmN0aW9uKG0pe0dbbV0ucGxhY2Vob2xkZXI9R30pLHFsKFsiZHJvcCIsInRha2UiXSxmdW5jdGlvbihtLHYpe2dyLnByb3RvdHlwZVttXT1mdW5jdGlvbihUKXtUPVQ9PT1lPzE6WGkoUWUoVCksMCk7dmFyIE49dGhpcy5fX2ZpbHRlcmVkX18mJiF2P25ldyBncih0aGlzKTp0aGlzLmNsb25lKCk7cmV0dXJuIE4uX19maWx0ZXJlZF9fP04uX190YWtlQ291bnRfXz1yYShULE4uX190YWtlQ291bnRfXyk6Ti5fX3ZpZXdzX18ucHVzaCh7c2l6ZTpyYShULHJ0KSx0eXBlOm0rKE4uX19kaXJfXzwwPyJSaWdodCI6IiIpfSksTn0sZ3IucHJvdG90eXBlW20rIlJpZ2h0Il09ZnVuY3Rpb24oVCl7cmV0dXJuIHRoaXMucmV2ZXJzZSgpW21dKFQpLnJldmVyc2UoKX19KSxxbChbImZpbHRlciIsIm1hcCIsInRha2VXaGlsZSJdLGZ1bmN0aW9uKG0sdil7dmFyIFQ9disxLE49VD09THx8VD09Rjtnci5wcm90b3R5cGVbbV09ZnVuY3Rpb24oVil7dmFyIFk9dGhpcy5jbG9uZSgpO3JldHVybiBZLl9faXRlcmF0ZWVzX18ucHVzaCh7aXRlcmF0ZWU6a2UoViwzKSx0eXBlOlR9KSxZLl9fZmlsdGVyZWRfXz1ZLl9fZmlsdGVyZWRfX3x8TixZfX0pLHFsKFsiaGVhZCIsImxhc3QiXSxmdW5jdGlvbihtLHYpe3ZhciBUPSJ0YWtlIisodj8iUmlnaHQiOiIiKTtnci5wcm90b3R5cGVbbV09ZnVuY3Rpb24oKXtyZXR1cm4gdGhpc1tUXSgxKS52YWx1ZSgpWzBdfX0pLHFsKFsiaW5pdGlhbCIsInRhaWwiXSxmdW5jdGlvbihtLHYpe3ZhciBUPSJkcm9wIisodj8iIjoiUmlnaHQiKTtnci5wcm90b3R5cGVbbV09ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fX2ZpbHRlcmVkX18/bmV3IGdyKHRoaXMpOnRoaXNbVF0oMSl9fSksZ3IucHJvdG90eXBlLmNvbXBhY3Q9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5maWx0ZXIoZ3MpfSxnci5wcm90b3R5cGUuZmluZD1mdW5jdGlvbihtKXtyZXR1cm4gdGhpcy5maWx0ZXIobSkuaGVhZCgpfSxnci5wcm90b3R5cGUuZmluZExhc3Q9ZnVuY3Rpb24obSl7cmV0dXJuIHRoaXMucmV2ZXJzZSgpLmZpbmQobSl9LGdyLnByb3RvdHlwZS5pbnZva2VNYXA9bnIoZnVuY3Rpb24obSx2KXtyZXR1cm4gdHlwZW9mIG09PSJmdW5jdGlvbiI/bmV3IGdyKHRoaXMpOnRoaXMubWFwKGZ1bmN0aW9uKFQpe3JldHVybiBrTShULG0sdil9KX0pLGdyLnByb3RvdHlwZS5yZWplY3Q9ZnVuY3Rpb24obSl7cmV0dXJuIHRoaXMuZmlsdGVyKGJJKGtlKG0pKSl9LGdyLnByb3RvdHlwZS5zbGljZT1mdW5jdGlvbihtLHYpe209UWUobSk7dmFyIFQ9dGhpcztyZXR1cm4gVC5fX2ZpbHRlcmVkX18mJihtPjB8fHY8MCk/bmV3IGdyKFQpOihtPDA/VD1ULnRha2VSaWdodCgtbSk6bSYmKFQ9VC5kcm9wKG0pKSx2IT09ZSYmKHY9UWUodiksVD12PDA/VC5kcm9wUmlnaHQoLXYpOlQudGFrZSh2LW0pKSxUKX0sZ3IucHJvdG90eXBlLnRha2VSaWdodFdoaWxlPWZ1bmN0aW9uKG0pe3JldHVybiB0aGlzLnJldmVyc2UoKS50YWtlV2hpbGUobSkucmV2ZXJzZSgpfSxnci5wcm90b3R5cGUudG9BcnJheT1mdW5jdGlvbigpe3JldHVybiB0aGlzLnRha2UocnQpfSxtaChnci5wcm90b3R5cGUsZnVuY3Rpb24obSx2KXt2YXIgVD0vXig/OmZpbHRlcnxmaW5kfG1hcHxyZWplY3QpfFdoaWxlJC8udGVzdCh2KSxOPS9eKD86aGVhZHxsYXN0KSQvLnRlc3QodiksVj1HW04/InRha2UiKyh2PT0ibGFzdCI/IlJpZ2h0IjoiIik6dl0sWT1OfHwvXmZpbmQvLnRlc3Qodik7IVZ8fChHLnByb3RvdHlwZVt2XT1mdW5jdGlvbigpe3ZhciBKPXRoaXMuX193cmFwcGVkX18saXQ9Tj9bMV06YXJndW1lbnRzLGZ0PUogaW5zdGFuY2VvZiBncixEdD1pdFswXSxPdD1mdHx8JGUoSiksVnQ9ZnVuY3Rpb24ocHIpe3ZhciB5cj1WLmFwcGx5KEcscG0oW3ByXSxpdCkpO3JldHVybiBOJiZvZT95clswXTp5cn07T3QmJlQmJnR5cGVvZiBEdD09ImZ1bmN0aW9uIiYmRHQubGVuZ3RoIT0xJiYoZnQ9T3Q9ITEpO3ZhciBvZT10aGlzLl9fY2hhaW5fXyxUZT0hIXRoaXMuX19hY3Rpb25zX18ubGVuZ3RoLE5lPVkmJiFvZSxlcj1mdCYmIVRlO2lmKCFZJiZPdCl7Sj1lcj9KOm5ldyBncih0aGlzKTt2YXIgRGU9bS5hcHBseShKLGl0KTtyZXR1cm4gRGUuX19hY3Rpb25zX18ucHVzaCh7ZnVuYzpfSSxhcmdzOltWdF0sdGhpc0FyZzplfSksbmV3IFdsKERlLG9lKX1yZXR1cm4gTmUmJmVyP20uYXBwbHkodGhpcyxpdCk6KERlPXRoaXMudGhydShWdCksTmU/Tj9EZS52YWx1ZSgpWzBdOkRlLnZhbHVlKCk6RGUpfSl9KSxxbChbInBvcCIsInB1c2giLCJzaGlmdCIsInNvcnQiLCJzcGxpY2UiLCJ1bnNoaWZ0Il0sZnVuY3Rpb24obSl7dmFyIHY9cTZbbV0sVD0vXig/OnB1c2h8c29ydHx1bnNoaWZ0KSQvLnRlc3QobSk/InRhcCI6InRocnUiLE49L14oPzpwb3B8c2hpZnQpJC8udGVzdChtKTtHLnByb3RvdHlwZVttXT1mdW5jdGlvbigpe3ZhciBWPWFyZ3VtZW50cztpZihOJiYhdGhpcy5fX2NoYWluX18pe3ZhciBZPXRoaXMudmFsdWUoKTtyZXR1cm4gdi5hcHBseSgkZShZKT9ZOltdLFYpfXJldHVybiB0aGlzW1RdKGZ1bmN0aW9uKEope3JldHVybiB2LmFwcGx5KCRlKEopP0o6W10sVil9KX19KSxtaChnci5wcm90b3R5cGUsZnVuY3Rpb24obSx2KXt2YXIgVD1HW3ZdO2lmKFQpe3ZhciBOPVQubmFtZSsiIjt1bi5jYWxsKF94LE4pfHwoX3hbTl09W10pLF94W05dLnB1c2goe25hbWU6dixmdW5jOlR9KX19KSxfeFt1SShlLGcpLm5hbWVdPVt7bmFtZToid3JhcHBlciIsZnVuYzplfV0sZ3IucHJvdG90eXBlLmNsb25lPUNkZSxnci5wcm90b3R5cGUucmV2ZXJzZT1BZGUsZ3IucHJvdG90eXBlLnZhbHVlPVBkZSxHLnByb3RvdHlwZS5hdD1pMGUsRy5wcm90b3R5cGUuY2hhaW49bzBlLEcucHJvdG90eXBlLmNvbW1pdD1hMGUsRy5wcm90b3R5cGUubmV4dD1zMGUsRy5wcm90b3R5cGUucGxhbnQ9YzBlLEcucHJvdG90eXBlLnJldmVyc2U9dTBlLEcucHJvdG90eXBlLnRvSlNPTj1HLnByb3RvdHlwZS52YWx1ZU9mPUcucHJvdG90eXBlLnZhbHVlPWgwZSxHLnByb3RvdHlwZS5maXJzdD1HLnByb3RvdHlwZS5oZWFkLEVNJiYoRy5wcm90b3R5cGVbRU1dPWwwZSksR30sbW09YWRlKCk7dHlwZW9mIGRlZmluZT09ImZ1bmN0aW9uIiYmdHlwZW9mIGRlZmluZS5hbWQ9PSJvYmplY3QiJiZkZWZpbmUuYW1kPyhmby5fPW1tLGRlZmluZShmdW5jdGlvbigpe3JldHVybiBtbX0pKTpRMD8oKFEwLmV4cG9ydHM9bW0pLl89bW0sb3EuXz1tbSk6Zm8uXz1tbX0pLmNhbGwoUngpfSk7ZnVuY3Rpb24gZl90KCl7Zm9yKHZhciBlPTAsdD1hcmd1bWVudHMubGVuZ3RoLHI9e30sbjtlPHQ7KytlKXtpZighKG49YXJndW1lbnRzW2VdKyIiKXx8biBpbiByfHwvW1xzLl0vLnRlc3QobikpdGhyb3cgbmV3IEVycm9yKCJpbGxlZ2FsIHR5cGU6ICIrbik7cltuXT1bXX1yZXR1cm4gbmV3IFo5KHIpfWZ1bmN0aW9uIFo5KGUpe3RoaXMuXz1lfWZ1bmN0aW9uIGgyZShlLHQpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKS5tYXAoZnVuY3Rpb24ocil7dmFyIG49IiIsaT1yLmluZGV4T2YoIi4iKTtpZihpPj0wJiYobj1yLnNsaWNlKGkrMSkscj1yLnNsaWNlKDAsaSkpLHImJiF0Lmhhc093blByb3BlcnR5KHIpKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK3IpO3JldHVybnt0eXBlOnIsbmFtZTpufX0pfWZ1bmN0aW9uIGYyZShlLHQpe2Zvcih2YXIgcj0wLG49ZS5sZW5ndGgsaTtyPG47KytyKWlmKChpPWVbcl0pLm5hbWU9PT10KXJldHVybiBpLnZhbHVlfWZ1bmN0aW9uIGhfdChlLHQscil7Zm9yKHZhciBuPTAsaT1lLmxlbmd0aDtuPGk7KytuKWlmKGVbbl0ubmFtZT09PXQpe2Vbbl09dTJlLGU9ZS5zbGljZSgwLG4pLmNvbmNhdChlLnNsaWNlKG4rMSkpO2JyZWFrfXJldHVybiByIT1udWxsJiZlLnB1c2goe25hbWU6dCx2YWx1ZTpyfSksZX12YXIgdTJlLHZzLHBfdD1NKCgpPT57dTJlPXt2YWx1ZTpmdW5jdGlvbigpe319O1o5LnByb3RvdHlwZT1mX3QucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpaOSxvbjpmdW5jdGlvbihlLHQpe3ZhciByPXRoaXMuXyxuPWgyZShlKyIiLHIpLGksbz0tMSxhPW4ubGVuZ3RoO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7Zm9yKDsrK288YTspaWYoKGk9KGU9bltvXSkudHlwZSkmJihpPWYyZShyW2ldLGUubmFtZSkpKXJldHVybiBpO3JldHVybn1pZih0IT1udWxsJiZ0eXBlb2YgdCE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgY2FsbGJhY2s6ICIrdCk7Zm9yKDsrK288YTspaWYoaT0oZT1uW29dKS50eXBlKXJbaV09aF90KHJbaV0sZS5uYW1lLHQpO2Vsc2UgaWYodD09bnVsbClmb3IoaSBpbiByKXJbaV09aF90KHJbaV0sZS5uYW1lLG51bGwpO3JldHVybiB0aGlzfSxjb3B5OmZ1bmN0aW9uKCl7dmFyIGU9e30sdD10aGlzLl87Zm9yKHZhciByIGluIHQpZVtyXT10W3JdLnNsaWNlKCk7cmV0dXJuIG5ldyBaOShlKX0sY2FsbDpmdW5jdGlvbihlLHQpe2lmKChpPWFyZ3VtZW50cy5sZW5ndGgtMik+MClmb3IodmFyIHI9bmV3IEFycmF5KGkpLG49MCxpLG87bjxpOysrbilyW25dPWFyZ3VtZW50c1tuKzJdO2lmKCF0aGlzLl8uaGFzT3duUHJvcGVydHkoZSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrZSk7Zm9yKG89dGhpcy5fW2VdLG49MCxpPW8ubGVuZ3RoO248aTsrK24pb1tuXS52YWx1ZS5hcHBseSh0LHIpfSxhcHBseTpmdW5jdGlvbihlLHQscil7aWYoIXRoaXMuXy5oYXNPd25Qcm9wZXJ0eShlKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gdHlwZTogIitlKTtmb3IodmFyIG49dGhpcy5fW2VdLGk9MCxvPW4ubGVuZ3RoO2k8bzsrK2kpbltpXS52YWx1ZS5hcHBseSh0LHIpfX07dnM9Zl90fSk7dmFyIGttPU0oKCk9PntwX3QoKX0pO2Z1bmN0aW9uIHlZKGUpe3JldHVybitlfXZhciB3MXQ9TSgoKT0+e30pO2Z1bmN0aW9uIHZZKGUpe3JldHVybiBlKmV9ZnVuY3Rpb24geFkoZSl7cmV0dXJuIGUqKDItZSl9ZnVuY3Rpb24gUEwoZSl7cmV0dXJuKChlKj0yKTw9MT9lKmU6LS1lKigyLWUpKzEpLzJ9dmFyIFMxdD1NKCgpPT57fSk7ZnVuY3Rpb24gYlkoZSl7cmV0dXJuIGUqZSplfWZ1bmN0aW9uIHdZKGUpe3JldHVybi0tZSplKmUrMX1mdW5jdGlvbiB4cyhlKXtyZXR1cm4oKGUqPTIpPD0xP2UqZSplOihlLT0yKSplKmUrMikvMn12YXIgTTF0PU0oKCk9Pnt9KTt2YXIgU1ksTVksRVksSUwsRTF0PU0oKCk9PntTWT0zLE1ZPWZ1bmN0aW9uIGUodCl7dD0rdDtmdW5jdGlvbiByKG4pe3JldHVybiBNYXRoLnBvdyhuLHQpfXJldHVybiByLmV4cG9uZW50PWUscn0oU1kpLEVZPWZ1bmN0aW9uIGUodCl7dD0rdDtmdW5jdGlvbiByKG4pe3JldHVybiAxLU1hdGgucG93KDEtbix0KX1yZXR1cm4gci5leHBvbmVudD1lLHJ9KFNZKSxJTD1mdW5jdGlvbiBlKHQpe3Q9K3Q7ZnVuY3Rpb24gcihuKXtyZXR1cm4oKG4qPTIpPD0xP01hdGgucG93KG4sdCk6Mi1NYXRoLnBvdygyLW4sdCkpLzJ9cmV0dXJuIHIuZXhwb25lbnQ9ZSxyfShTWSl9KTtmdW5jdGlvbiBUWShlKXtyZXR1cm4rZT09MT8xOjEtTWF0aC5jb3MoZSpDMXQpfWZ1bmN0aW9uIENZKGUpe3JldHVybiBNYXRoLnNpbihlKkMxdCl9ZnVuY3Rpb24gTEwoZSl7cmV0dXJuKDEtTWF0aC5jb3MoVDF0KmUpKS8yfXZhciBUMXQsQzF0LEExdD1NKCgpPT57VDF0PU1hdGguUEksQzF0PVQxdC8yfSk7ZnVuY3Rpb24gRGgoZSl7cmV0dXJuKE1hdGgucG93KDIsLTEwKmUpLS4wMDA5NzY1NjI1KSoxLjAwMDk3NzUxNzEwNjU0OTR9dmFyIEFZPU0oKCk9Pnt9KTtmdW5jdGlvbiBQWShlKXtyZXR1cm4gRGgoMS0rZSl9ZnVuY3Rpb24gSVkoZSl7cmV0dXJuIDEtRGgoZSl9ZnVuY3Rpb24ga0woZSl7cmV0dXJuKChlKj0yKTw9MT9EaCgxLWUpOjItRGgoZS0xKSkvMn12YXIgUDF0PU0oKCk9PntBWSgpfSk7ZnVuY3Rpb24gTFkoZSl7cmV0dXJuIDEtTWF0aC5zcXJ0KDEtZSplKX1mdW5jdGlvbiBrWShlKXtyZXR1cm4gTWF0aC5zcXJ0KDEtIC0tZSplKX1mdW5jdGlvbiBSTChlKXtyZXR1cm4oKGUqPTIpPD0xPzEtTWF0aC5zcXJ0KDEtZSplKTpNYXRoLnNxcnQoMS0oZS09MikqZSkrMSkvMn12YXIgSTF0PU0oKCk9Pnt9KTtmdW5jdGlvbiBOWShlKXtyZXR1cm4gMS1QXygxLWUpfWZ1bmN0aW9uIFBfKGUpe3JldHVybihlPStlKTxSWT9OTCplKmU6ZTxvU2U/TkwqKGUtPWlTZSkqZSthU2U6ZTxsU2U/TkwqKGUtPXNTZSkqZStjU2U6TkwqKGUtPXVTZSkqZStoU2V9ZnVuY3Rpb24gRFkoZSl7cmV0dXJuKChlKj0yKTw9MT8xLVBfKDEtZSk6UF8oZS0xKSsxKS8yfXZhciBSWSxpU2Usb1NlLGFTZSxzU2UsbFNlLGNTZSx1U2UsaFNlLE5MLEwxdD1NKCgpPT57Ulk9LjM2MzYzNjM2MzYzNjM2MzY1LGlTZT02LzExLG9TZT04LzExLGFTZT0zLzQsc1NlPTkvMTEsbFNlPTEwLzExLGNTZT0xNS8xNix1U2U9MjEvMjIsaFNlPTYzLzY0LE5MPTEvUlkvUll9KTt2YXIgT1kselksRlksREwsazF0PU0oKCk9PntPWT0xLjcwMTU4LHpZPWZ1bmN0aW9uIGUodCl7dD0rdDtmdW5jdGlvbiByKG4pe3JldHVybihuPStuKSpuKih0KihuLTEpK24pfXJldHVybiByLm92ZXJzaG9vdD1lLHJ9KE9ZKSxGWT1mdW5jdGlvbiBlKHQpe3Q9K3Q7ZnVuY3Rpb24gcihuKXtyZXR1cm4tLW4qbiooKG4rMSkqdCtuKSsxfXJldHVybiByLm92ZXJzaG9vdD1lLHJ9KE9ZKSxETD1mdW5jdGlvbiBlKHQpe3Q9K3Q7ZnVuY3Rpb24gcihuKXtyZXR1cm4oKG4qPTIpPDE/bipuKigodCsxKSpuLXQpOihuLT0yKSpuKigodCsxKSpuK3QpKzIpLzJ9cmV0dXJuIHIub3ZlcnNob290PWUscn0oT1kpfSk7dmFyIFNiLEJZLEhZLFZZLE9MLFVZLFIxdD1NKCgpPT57QVkoKTtTYj0yKk1hdGguUEksQlk9MSxIWT0uMyxWWT1mdW5jdGlvbiBlKHQscil7dmFyIG49TWF0aC5hc2luKDEvKHQ9TWF0aC5tYXgoMSx0KSkpKihyLz1TYik7ZnVuY3Rpb24gaShvKXtyZXR1cm4gdCpEaCgtIC0tbykqTWF0aC5zaW4oKG4tbykvcil9cmV0dXJuIGkuYW1wbGl0dWRlPWZ1bmN0aW9uKG8pe3JldHVybiBlKG8scipTYil9LGkucGVyaW9kPWZ1bmN0aW9uKG8pe3JldHVybiBlKHQsbyl9LGl9KEJZLEhZKSxPTD1mdW5jdGlvbiBlKHQscil7dmFyIG49TWF0aC5hc2luKDEvKHQ9TWF0aC5tYXgoMSx0KSkpKihyLz1TYik7ZnVuY3Rpb24gaShvKXtyZXR1cm4gMS10KkRoKG89K28pKk1hdGguc2luKChvK24pL3IpfXJldHVybiBpLmFtcGxpdHVkZT1mdW5jdGlvbihvKXtyZXR1cm4gZShvLHIqU2IpfSxpLnBlcmlvZD1mdW5jdGlvbihvKXtyZXR1cm4gZSh0LG8pfSxpfShCWSxIWSksVVk9ZnVuY3Rpb24gZSh0LHIpe3ZhciBuPU1hdGguYXNpbigxLyh0PU1hdGgubWF4KDEsdCkpKSooci89U2IpO2Z1bmN0aW9uIGkobyl7cmV0dXJuKChvPW8qMi0xKTwwP3QqRGgoLW8pKk1hdGguc2luKChuLW8pL3IpOjItdCpEaChvKSpNYXRoLnNpbigobitvKS9yKSkvMn1yZXR1cm4gaS5hbXBsaXR1ZGU9ZnVuY3Rpb24obyl7cmV0dXJuIGUobyxyKlNiKX0saS5wZXJpb2Q9ZnVuY3Rpb24obyl7cmV0dXJuIGUodCxvKX0saX0oQlksSFkpfSk7dmFyIE4xdD17fTtLcyhOMXQse2Vhc2VCYWNrOigpPT5ETCxlYXNlQmFja0luOigpPT56WSxlYXNlQmFja0luT3V0OigpPT5ETCxlYXNlQmFja091dDooKT0+RlksZWFzZUJvdW5jZTooKT0+UF8sZWFzZUJvdW5jZUluOigpPT5OWSxlYXNlQm91bmNlSW5PdXQ6KCk9PkRZLGVhc2VCb3VuY2VPdXQ6KCk9PlBfLGVhc2VDaXJjbGU6KCk9PlJMLGVhc2VDaXJjbGVJbjooKT0+TFksZWFzZUNpcmNsZUluT3V0OigpPT5STCxlYXNlQ2lyY2xlT3V0OigpPT5rWSxlYXNlQ3ViaWM6KCk9PnhzLGVhc2VDdWJpY0luOigpPT5iWSxlYXNlQ3ViaWNJbk91dDooKT0+eHMsZWFzZUN1YmljT3V0OigpPT53WSxlYXNlRWxhc3RpYzooKT0+T0wsZWFzZUVsYXN0aWNJbjooKT0+VlksZWFzZUVsYXN0aWNJbk91dDooKT0+VVksZWFzZUVsYXN0aWNPdXQ6KCk9Pk9MLGVhc2VFeHA6KCk9PmtMLGVhc2VFeHBJbjooKT0+UFksZWFzZUV4cEluT3V0OigpPT5rTCxlYXNlRXhwT3V0OigpPT5JWSxlYXNlTGluZWFyOigpPT55WSxlYXNlUG9seTooKT0+SUwsZWFzZVBvbHlJbjooKT0+TVksZWFzZVBvbHlJbk91dDooKT0+SUwsZWFzZVBvbHlPdXQ6KCk9PkVZLGVhc2VRdWFkOigpPT5QTCxlYXNlUXVhZEluOigpPT52WSxlYXNlUXVhZEluT3V0OigpPT5QTCxlYXNlUXVhZE91dDooKT0+eFksZWFzZVNpbjooKT0+TEwsZWFzZVNpbkluOigpPT5UWSxlYXNlU2luSW5PdXQ6KCk9PkxMLGVhc2VTaW5PdXQ6KCk9PkNZfSk7dmFyIElfPU0oKCk9Pnt3MXQoKTtTMXQoKTtNMXQoKTtFMXQoKTtBMXQoKTtQMXQoKTtJMXQoKTtMMXQoKTtrMXQoKTtSMXQoKX0pO2Z1bmN0aW9uIFdMKCl7fWZ1bmN0aW9uIFgxdChlLHQpe3ZhciByPW5ldyBXTDtpZihlIGluc3RhbmNlb2YgV0wpZS5lYWNoKGZ1bmN0aW9uKHMsbCl7ci5zZXQobCxzKX0pO2Vsc2UgaWYoQXJyYXkuaXNBcnJheShlKSl7dmFyIG49LTEsaT1lLmxlbmd0aCxvO2lmKHQ9PW51bGwpZm9yKDsrK248aTspci5zZXQobixlW25dKTtlbHNlIGZvcig7KytuPGk7KXIuc2V0KHQobz1lW25dLG4sZSksbyl9ZWxzZSBpZihlKWZvcih2YXIgYSBpbiBlKXIuc2V0KGEsZVthXSk7cmV0dXJuIHJ9dmFyIGVsLEppLFlMPU0oKCk9PntlbD0iJCI7V0wucHJvdG90eXBlPVgxdC5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOldMLGhhczpmdW5jdGlvbihlKXtyZXR1cm4gZWwrZSBpbiB0aGlzfSxnZXQ6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXNbZWwrZV19LHNldDpmdW5jdGlvbihlLHQpe3JldHVybiB0aGlzW2VsK2VdPXQsdGhpc30scmVtb3ZlOmZ1bmN0aW9uKGUpe3ZhciB0PWVsK2U7cmV0dXJuIHQgaW4gdGhpcyYmZGVsZXRlIHRoaXNbdF19LGNsZWFyOmZ1bmN0aW9uKCl7Zm9yKHZhciBlIGluIHRoaXMpZVswXT09PWVsJiZkZWxldGUgdGhpc1tlXX0sa2V5czpmdW5jdGlvbigpe3ZhciBlPVtdO2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1lbCYmZS5wdXNoKHQuc2xpY2UoMSkpO3JldHVybiBlfSx2YWx1ZXM6ZnVuY3Rpb24oKXt2YXIgZT1bXTtmb3IodmFyIHQgaW4gdGhpcyl0WzBdPT09ZWwmJmUucHVzaCh0aGlzW3RdKTtyZXR1cm4gZX0sZW50cmllczpmdW5jdGlvbigpe3ZhciBlPVtdO2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1lbCYmZS5wdXNoKHtrZXk6dC5zbGljZSgxKSx2YWx1ZTp0aGlzW3RdfSk7cmV0dXJuIGV9LHNpemU6ZnVuY3Rpb24oKXt2YXIgZT0wO2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1lbCYmKytlO3JldHVybiBlfSxlbXB0eTpmdW5jdGlvbigpe2Zvcih2YXIgZSBpbiB0aGlzKWlmKGVbMF09PT1lbClyZXR1cm4hMTtyZXR1cm4hMH0sZWFjaDpmdW5jdGlvbihlKXtmb3IodmFyIHQgaW4gdGhpcyl0WzBdPT09ZWwmJmUodGhpc1t0XSx0LnNsaWNlKDEpLHRoaXMpfX07Smk9WDF0fSk7ZnVuY3Rpb24gWjF0KCl7dmFyIGU9W10sdD1bXSxyLG4saTtmdW5jdGlvbiBvKHMsbCxjLHUpe2lmKGw+PWUubGVuZ3RoKXJldHVybiByIT1udWxsJiZzLnNvcnQociksbiE9bnVsbD9uKHMpOnM7Zm9yKHZhciBoPS0xLGY9cy5sZW5ndGgscD1lW2wrK10sZCxnLF89SmkoKSx5LHg9YygpOysraDxmOykoeT1fLmdldChkPXAoZz1zW2hdKSsiIikpP3kucHVzaChnKTpfLnNldChkLFtnXSk7cmV0dXJuIF8uZWFjaChmdW5jdGlvbihiLFMpe3UoeCxTLG8oYixsLGMsdSkpfSkseH1mdW5jdGlvbiBhKHMsbCl7aWYoKytsPmUubGVuZ3RoKXJldHVybiBzO3ZhciBjLHU9dFtsLTFdO3JldHVybiBuIT1udWxsJiZsPj1lLmxlbmd0aD9jPXMuZW50cmllcygpOihjPVtdLHMuZWFjaChmdW5jdGlvbihoLGYpe2MucHVzaCh7a2V5OmYsdmFsdWVzOmEoaCxsKX0pfSkpLHUhPW51bGw/Yy5zb3J0KGZ1bmN0aW9uKGgsZil7cmV0dXJuIHUoaC5rZXksZi5rZXkpfSk6Y31yZXR1cm4gaT17b2JqZWN0OmZ1bmN0aW9uKHMpe3JldHVybiBvKHMsMCxQU2UsSVNlKX0sbWFwOmZ1bmN0aW9uKHMpe3JldHVybiBvKHMsMCwkMXQsSzF0KX0sZW50cmllczpmdW5jdGlvbihzKXtyZXR1cm4gYShvKHMsMCwkMXQsSzF0KSwwKX0sa2V5OmZ1bmN0aW9uKHMpe3JldHVybiBlLnB1c2gocyksaX0sc29ydEtleXM6ZnVuY3Rpb24ocyl7cmV0dXJuIHRbZS5sZW5ndGgtMV09cyxpfSxzb3J0VmFsdWVzOmZ1bmN0aW9uKHMpe3JldHVybiByPXMsaX0scm9sbHVwOmZ1bmN0aW9uKHMpe3JldHVybiBuPXMsaX19fWZ1bmN0aW9uIFBTZSgpe3JldHVybnt9fWZ1bmN0aW9uIElTZShlLHQscil7ZVt0XT1yfWZ1bmN0aW9uICQxdCgpe3JldHVybiBKaSgpfWZ1bmN0aW9uIEsxdChlLHQscil7ZS5zZXQodCxyKX12YXIgSjF0PU0oKCk9PntZTCgpfSk7ZnVuY3Rpb24gakwoKXt9ZnVuY3Rpb24gUTF0KGUsdCl7dmFyIHI9bmV3IGpMO2lmKGUgaW5zdGFuY2VvZiBqTCllLmVhY2goZnVuY3Rpb24obyl7ci5hZGQobyl9KTtlbHNlIGlmKGUpe3ZhciBuPS0xLGk9ZS5sZW5ndGg7aWYodD09bnVsbClmb3IoOysrbjxpOylyLmFkZChlW25dKTtlbHNlIGZvcig7KytuPGk7KXIuYWRkKHQoZVtuXSxuLGUpKX1yZXR1cm4gcn12YXIga18sdHZ0LGV2dD1NKCgpPT57WUwoKTtrXz1KaS5wcm90b3R5cGU7akwucHJvdG90eXBlPVExdC5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOmpMLGhhczprXy5oYXMsYWRkOmZ1bmN0aW9uKGUpe3JldHVybiBlKz0iIix0aGlzW2VsK2VdPWUsdGhpc30scmVtb3ZlOmtfLnJlbW92ZSxjbGVhcjprXy5jbGVhcix2YWx1ZXM6a18ua2V5cyxzaXplOmtfLnNpemUsZW1wdHk6a18uZW1wdHksZWFjaDprXy5lYWNofTt0dnQ9UTF0fSk7ZnVuY3Rpb24gWEwoZSl7dmFyIHQ9W107Zm9yKHZhciByIGluIGUpdC5wdXNoKHIpO3JldHVybiB0fXZhciBydnQ9TSgoKT0+e30pO2Z1bmN0aW9uIG52dChlKXt2YXIgdD1bXTtmb3IodmFyIHIgaW4gZSl0LnB1c2goZVtyXSk7cmV0dXJuIHR9dmFyIGl2dD1NKCgpPT57fSk7ZnVuY3Rpb24gb3Z0KGUpe3ZhciB0PVtdO2Zvcih2YXIgciBpbiBlKXQucHVzaCh7a2V5OnIsdmFsdWU6ZVtyXX0pO3JldHVybiB0fXZhciBhdnQ9TSgoKT0+e30pO3ZhciBUYj1NKCgpPT57SjF0KCk7ZXZ0KCk7WUwoKTtydnQoKTtpdnQoKTthdnQoKX0pO2Z1bmN0aW9uIGR2dChlKXtyZXR1cm4gbmV3IEZ1bmN0aW9uKCJkIiwicmV0dXJuIHsiK2UubWFwKGZ1bmN0aW9uKHQscil7cmV0dXJuIEpTT04uc3RyaW5naWZ5KHQpKyI6IGRbIityKyddIHx8ICIiJ30pLmpvaW4oIiwiKSsifSIpfWZ1bmN0aW9uIEJTZShlLHQpe3ZhciByPWR2dChlKTtyZXR1cm4gZnVuY3Rpb24obixpKXtyZXR1cm4gdChyKG4pLGksZSl9fWZ1bmN0aW9uIHB2dChlKXt2YXIgdD1PYmplY3QuY3JlYXRlKG51bGwpLHI9W107cmV0dXJuIGUuZm9yRWFjaChmdW5jdGlvbihuKXtmb3IodmFyIGkgaW4gbilpIGluIHR8fHIucHVzaCh0W2ldPWkpfSkscn1mdW5jdGlvbiB3cyhlLHQpe3ZhciByPWUrIiIsbj1yLmxlbmd0aDtyZXR1cm4gbjx0P25ldyBBcnJheSh0LW4rMSkuam9pbigwKStyOnJ9ZnVuY3Rpb24gSFNlKGUpe3JldHVybiBlPDA/Ii0iK3dzKC1lLDYpOmU+OTk5OT8iKyIrd3MoZSw2KTp3cyhlLDQpfWZ1bmN0aW9uIFZTZShlKXt2YXIgdD1lLmdldFVUQ0hvdXJzKCkscj1lLmdldFVUQ01pbnV0ZXMoKSxuPWUuZ2V0VVRDU2Vjb25kcygpLGk9ZS5nZXRVVENNaWxsaXNlY29uZHMoKTtyZXR1cm4gaXNOYU4oZSk/IkludmFsaWQgRGF0ZSI6SFNlKGUuZ2V0VVRDRnVsbFllYXIoKSw0KSsiLSIrd3MoZS5nZXRVVENNb250aCgpKzEsMikrIi0iK3dzKGUuZ2V0VVRDRGF0ZSgpLDIpKyhpPyJUIit3cyh0LDIpKyI6Iit3cyhyLDIpKyI6Iit3cyhuLDIpKyIuIit3cyhpLDMpKyJaIjpuPyJUIit3cyh0LDIpKyI6Iit3cyhyLDIpKyI6Iit3cyhuLDIpKyJaIjpyfHx0PyJUIit3cyh0LDIpKyI6Iit3cyhyLDIpKyJaIjoiIil9ZnVuY3Rpb24gV20oZSl7dmFyIHQ9bmV3IFJlZ0V4cCgnWyInK2UrYApccl1gKSxyPWUuY2hhckNvZGVBdCgwKTtmdW5jdGlvbiBuKGgsZil7dmFyIHAsZCxnPWkoaCxmdW5jdGlvbihfLHkpe2lmKHApcmV0dXJuIHAoXyx5LTEpO2Q9XyxwPWY/QlNlKF8sZik6ZHZ0KF8pfSk7cmV0dXJuIGcuY29sdW1ucz1kfHxbXSxnfWZ1bmN0aW9uIGkoaCxmKXt2YXIgcD1bXSxkPWgubGVuZ3RoLGc9MCxfPTAseSx4PWQ8PTAsYj0hMTtoLmNoYXJDb2RlQXQoZC0xKT09PVZFJiYtLWQsaC5jaGFyQ29kZUF0KGQtMSk9PT1paiYmLS1kO2Z1bmN0aW9uIFMoKXtpZih4KXJldHVybiByajtpZihiKXJldHVybiBiPSExLGZ2dDt2YXIgUCxrPWcsTztpZihoLmNoYXJDb2RlQXQoayk9PT1uail7Zm9yKDtnKys8ZCYmaC5jaGFyQ29kZUF0KGcpIT09bmp8fGguY2hhckNvZGVBdCgrK2cpPT09bmo7KTtyZXR1cm4oUD1nKT49ZD94PSEwOihPPWguY2hhckNvZGVBdChnKyspKT09PVZFP2I9ITA6Tz09PWlqJiYoYj0hMCxoLmNoYXJDb2RlQXQoZyk9PT1WRSYmKytnKSxoLnNsaWNlKGsrMSxQLTEpLnJlcGxhY2UoLyIiL2csJyInKX1mb3IoO2c8ZDspe2lmKChPPWguY2hhckNvZGVBdChQPWcrKykpPT09VkUpYj0hMDtlbHNlIGlmKE89PT1pailiPSEwLGguY2hhckNvZGVBdChnKT09PVZFJiYrK2c7ZWxzZSBpZihPIT09ciljb250aW51ZTtyZXR1cm4gaC5zbGljZShrLFApfXJldHVybiB4PSEwLGguc2xpY2UoayxkKX1mb3IoOyh5PVMoKSkhPT1yajspe2Zvcih2YXIgQz1bXTt5IT09ZnZ0JiZ5IT09cmo7KUMucHVzaCh5KSx5PVMoKTtmJiYoQz1mKEMsXysrKSk9PW51bGx8fHAucHVzaChDKX1yZXR1cm4gcH1mdW5jdGlvbiBvKGgsZil7cmV0dXJuIGgubWFwKGZ1bmN0aW9uKHApe3JldHVybiBmLm1hcChmdW5jdGlvbihkKXtyZXR1cm4gdShwW2RdKX0pLmpvaW4oZSl9KX1mdW5jdGlvbiBhKGgsZil7cmV0dXJuIGY9PW51bGwmJihmPXB2dChoKSksW2YubWFwKHUpLmpvaW4oZSldLmNvbmNhdChvKGgsZikpLmpvaW4oYApgKX1mdW5jdGlvbiBzKGgsZil7cmV0dXJuIGY9PW51bGwmJihmPXB2dChoKSksbyhoLGYpLmpvaW4oYApgKX1mdW5jdGlvbiBsKGgpe3JldHVybiBoLm1hcChjKS5qb2luKGAKYCl9ZnVuY3Rpb24gYyhoKXtyZXR1cm4gaC5tYXAodSkuam9pbihlKX1mdW5jdGlvbiB1KGgpe3JldHVybiBoPT1udWxsPyIiOmggaW5zdGFuY2VvZiBEYXRlP1ZTZShoKTp0LnRlc3QoaCs9IiIpPyciJytoLnJlcGxhY2UoLyIvZywnIiInKSsnIic6aH1yZXR1cm57cGFyc2U6bixwYXJzZVJvd3M6aSxmb3JtYXQ6YSxmb3JtYXRCb2R5OnMsZm9ybWF0Um93czpsLGZvcm1hdFJvdzpjLGZvcm1hdFZhbHVlOnV9fXZhciBmdnQscmosbmosVkUsaWosUUw9TSgoKT0+e2Z2dD17fSxyaj17fSxuaj0zNCxWRT0xMCxpaj0xM30pO3ZhciBSXyxDYixtdnQsZ3Z0LF92dCx5dnQsdnZ0LHh2dCxidnQ9TSgoKT0+e1FMKCk7Ul89V20oIiwiKSxDYj1SXy5wYXJzZSxtdnQ9Ul8ucGFyc2VSb3dzLGd2dD1SXy5mb3JtYXQsX3Z0PVJfLmZvcm1hdEJvZHkseXZ0PVJfLmZvcm1hdFJvd3MsdnZ0PVJfLmZvcm1hdFJvdyx4dnQ9Ul8uZm9ybWF0VmFsdWV9KTt2YXIgTl8sQWIsd3Z0LFN2dCxNdnQsRXZ0LFR2dCxDdnQsQXZ0PU0oKCk9PntRTCgpO05fPVdtKCIJIiksQWI9Tl8ucGFyc2Usd3Z0PU5fLnBhcnNlUm93cyxTdnQ9Tl8uZm9ybWF0LE12dD1OXy5mb3JtYXRCb2R5LEV2dD1OXy5mb3JtYXRSb3dzLFR2dD1OXy5mb3JtYXRSb3csQ3Z0PU5fLmZvcm1hdFZhbHVlfSk7ZnVuY3Rpb24gb2ooZSl7Zm9yKHZhciB0IGluIGUpe3ZhciByPWVbdF0udHJpbSgpLG4saTtpZighcilyPW51bGw7ZWxzZSBpZihyPT09InRydWUiKXI9ITA7ZWxzZSBpZihyPT09ImZhbHNlIilyPSExO2Vsc2UgaWYocj09PSJOYU4iKXI9TmFOO2Vsc2UgaWYoIWlzTmFOKG49K3IpKXI9bjtlbHNlIGlmKGk9ci5tYXRjaCgvXihbLStdXGR7Mn0pP1xkezR9KC1cZHsyfSgtXGR7Mn0pPyk/KFRcZHsyfTpcZHsyfSg6XGR7Mn0oXC5cZHszfSk/KT8oWnxbLStdXGR7Mn06XGR7Mn0pPyk/JC8pKVVTZSYmISFpWzRdJiYhaVs3XSYmKHI9ci5yZXBsYWNlKC8tL2csIi8iKS5yZXBsYWNlKC9ULywiICIpKSxyPW5ldyBEYXRlKHIpO2Vsc2UgY29udGludWU7ZVt0XT1yfXJldHVybiBlfXZhciBVU2UsUHZ0PU0oKCk9PntVU2U9bmV3IERhdGUoIjIwMTktMDEtMDFUMDA6MDAiKS5nZXRIb3VycygpfHxuZXcgRGF0ZSgiMjAxOS0wNy0wMVQwMDowMCIpLmdldEhvdXJzKCl9KTt2YXIgVUU9TSgoKT0+e1FMKCk7YnZ0KCk7QXZ0KCk7UHZ0KCl9KTt2YXIgcGU9e307S3MocGUse19fYXNzaWduOigpPT5wNWUsX19hc3luY0RlbGVnYXRvcjooKT0+UzVlLF9fYXN5bmNHZW5lcmF0b3I6KCk9Pnc1ZSxfX2FzeW5jVmFsdWVzOigpPT5NNWUsX19hd2FpdDooKT0+VjUsX19hd2FpdGVyOigpPT55NWUsX19kZWNvcmF0ZTooKT0+bTVlLF9fZXhwb3J0U3RhcjooKT0+eDVlLF9fZXh0ZW5kczooKT0+ZjVlLF9fZ2VuZXJhdG9yOigpPT52NWUsX19tYWtlVGVtcGxhdGVPYmplY3Q6KCk9PkU1ZSxfX21ldGFkYXRhOigpPT5fNWUsX19wYXJhbTooKT0+ZzVlLF9fcmVhZDooKT0+SjN0LF9fcmVzdDooKT0+ZDVlLF9fc3ByZWFkOigpPT5iNWUsX192YWx1ZXM6KCk9PnYkfSk7ZnVuY3Rpb24gZjVlKGUsdCl7aDVlKGUsdCk7ZnVuY3Rpb24gcigpe3RoaXMuY29uc3RydWN0b3I9ZX1lLnByb3RvdHlwZT10PT09bnVsbD9PYmplY3QuY3JlYXRlKHQpOihyLnByb3RvdHlwZT10LnByb3RvdHlwZSxuZXcgcil9ZnVuY3Rpb24gZDVlKGUsdCl7dmFyIHI9e307Zm9yKHZhciBuIGluIGUpT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGUsbikmJnQuaW5kZXhPZihuKTwwJiYocltuXT1lW25dKTtpZihlIT1udWxsJiZ0eXBlb2YgT2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scz09ImZ1bmN0aW9uIilmb3IodmFyIGk9MCxuPU9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHMoZSk7aTxuLmxlbmd0aDtpKyspdC5pbmRleE9mKG5baV0pPDAmJihyW25baV1dPWVbbltpXV0pO3JldHVybiByfWZ1bmN0aW9uIG01ZShlLHQscixuKXt2YXIgaT1hcmd1bWVudHMubGVuZ3RoLG89aTwzP3Q6bj09PW51bGw/bj1PYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKHQscik6bixhO2lmKHR5cGVvZiBSZWZsZWN0PT0ib2JqZWN0IiYmdHlwZW9mIFJlZmxlY3QuZGVjb3JhdGU9PSJmdW5jdGlvbiIpbz1SZWZsZWN0LmRlY29yYXRlKGUsdCxyLG4pO2Vsc2UgZm9yKHZhciBzPWUubGVuZ3RoLTE7cz49MDtzLS0pKGE9ZVtzXSkmJihvPShpPDM/YShvKTppPjM/YSh0LHIsbyk6YSh0LHIpKXx8byk7cmV0dXJuIGk+MyYmbyYmT2JqZWN0LmRlZmluZVByb3BlcnR5KHQscixvKSxvfWZ1bmN0aW9uIGc1ZShlLHQpe3JldHVybiBmdW5jdGlvbihyLG4pe3QocixuLGUpfX1mdW5jdGlvbiBfNWUoZSx0KXtpZih0eXBlb2YgUmVmbGVjdD09Im9iamVjdCImJnR5cGVvZiBSZWZsZWN0Lm1ldGFkYXRhPT0iZnVuY3Rpb24iKXJldHVybiBSZWZsZWN0Lm1ldGFkYXRhKGUsdCl9ZnVuY3Rpb24geTVlKGUsdCxyLG4pe3JldHVybiBuZXcocnx8KHI9UHJvbWlzZSkpKGZ1bmN0aW9uKGksbyl7ZnVuY3Rpb24gYShjKXt0cnl7bChuLm5leHQoYykpfWNhdGNoKHUpe28odSl9fWZ1bmN0aW9uIHMoYyl7dHJ5e2wobi50aHJvdyhjKSl9Y2F0Y2godSl7byh1KX19ZnVuY3Rpb24gbChjKXtjLmRvbmU/aShjLnZhbHVlKTpuZXcgcihmdW5jdGlvbih1KXt1KGMudmFsdWUpfSkudGhlbihhLHMpfWwoKG49bi5hcHBseShlLHR8fFtdKSkubmV4dCgpKX0pfWZ1bmN0aW9uIHY1ZShlLHQpe3ZhciByPXtsYWJlbDowLHNlbnQ6ZnVuY3Rpb24oKXtpZihvWzBdJjEpdGhyb3cgb1sxXTtyZXR1cm4gb1sxXX0sdHJ5czpbXSxvcHM6W119LG4saSxvLGE7cmV0dXJuIGE9e25leHQ6cygwKSx0aHJvdzpzKDEpLHJldHVybjpzKDIpfSx0eXBlb2YgU3ltYm9sPT0iZnVuY3Rpb24iJiYoYVtTeW1ib2wuaXRlcmF0b3JdPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXN9KSxhO2Z1bmN0aW9uIHMoYyl7cmV0dXJuIGZ1bmN0aW9uKHUpe3JldHVybiBsKFtjLHVdKX19ZnVuY3Rpb24gbChjKXtpZihuKXRocm93IG5ldyBUeXBlRXJyb3IoIkdlbmVyYXRvciBpcyBhbHJlYWR5IGV4ZWN1dGluZy4iKTtmb3IoO3I7KXRyeXtpZihuPTEsaSYmKG89aVtjWzBdJjI/InJldHVybiI6Y1swXT8idGhyb3ciOiJuZXh0Il0pJiYhKG89by5jYWxsKGksY1sxXSkpLmRvbmUpcmV0dXJuIG87c3dpdGNoKGk9MCxvJiYoYz1bMCxvLnZhbHVlXSksY1swXSl7Y2FzZSAwOmNhc2UgMTpvPWM7YnJlYWs7Y2FzZSA0OnJldHVybiByLmxhYmVsKysse3ZhbHVlOmNbMV0sZG9uZTohMX07Y2FzZSA1OnIubGFiZWwrKyxpPWNbMV0sYz1bMF07Y29udGludWU7Y2FzZSA3OmM9ci5vcHMucG9wKCksci50cnlzLnBvcCgpO2NvbnRpbnVlO2RlZmF1bHQ6aWYobz1yLnRyeXMsIShvPW8ubGVuZ3RoPjAmJm9bby5sZW5ndGgtMV0pJiYoY1swXT09PTZ8fGNbMF09PT0yKSl7cj0wO2NvbnRpbnVlfWlmKGNbMF09PT0zJiYoIW98fGNbMV0+b1swXSYmY1sxXTxvWzNdKSl7ci5sYWJlbD1jWzFdO2JyZWFrfWlmKGNbMF09PT02JiZyLmxhYmVsPG9bMV0pe3IubGFiZWw9b1sxXSxvPWM7YnJlYWt9aWYobyYmci5sYWJlbDxvWzJdKXtyLmxhYmVsPW9bMl0sci5vcHMucHVzaChjKTticmVha31vWzJdJiZyLm9wcy5wb3AoKSxyLnRyeXMucG9wKCk7Y29udGludWV9Yz10LmNhbGwoZSxyKX1jYXRjaCh1KXtjPVs2LHVdLGk9MH1maW5hbGx5e249bz0wfWlmKGNbMF0mNSl0aHJvdyBjWzFdO3JldHVybnt2YWx1ZTpjWzBdP2NbMV06dm9pZCAwLGRvbmU6ITB9fX1mdW5jdGlvbiB4NWUoZSx0KXtmb3IodmFyIHIgaW4gZSl0Lmhhc093blByb3BlcnR5KHIpfHwodFtyXT1lW3JdKX1mdW5jdGlvbiB2JChlKXt2YXIgdD10eXBlb2YgU3ltYm9sPT0iZnVuY3Rpb24iJiZlW1N5bWJvbC5pdGVyYXRvcl0scj0wO3JldHVybiB0P3QuY2FsbChlKTp7bmV4dDpmdW5jdGlvbigpe3JldHVybiBlJiZyPj1lLmxlbmd0aCYmKGU9dm9pZCAwKSx7dmFsdWU6ZSYmZVtyKytdLGRvbmU6IWV9fX19ZnVuY3Rpb24gSjN0KGUsdCl7dmFyIHI9dHlwZW9mIFN5bWJvbD09ImZ1bmN0aW9uIiYmZVtTeW1ib2wuaXRlcmF0b3JdO2lmKCFyKXJldHVybiBlO3ZhciBuPXIuY2FsbChlKSxpLG89W10sYTt0cnl7Zm9yKDsodD09PXZvaWQgMHx8dC0tID4wKSYmIShpPW4ubmV4dCgpKS5kb25lOylvLnB1c2goaS52YWx1ZSl9Y2F0Y2gocyl7YT17ZXJyb3I6c319ZmluYWxseXt0cnl7aSYmIWkuZG9uZSYmKHI9bi5yZXR1cm4pJiZyLmNhbGwobil9ZmluYWxseXtpZihhKXRocm93IGEuZXJyb3J9fXJldHVybiBvfWZ1bmN0aW9uIGI1ZSgpe2Zvcih2YXIgZT1bXSx0PTA7dDxhcmd1bWVudHMubGVuZ3RoO3QrKyllPWUuY29uY2F0KEozdChhcmd1bWVudHNbdF0pKTtyZXR1cm4gZX1mdW5jdGlvbiBWNShlKXtyZXR1cm4gdGhpcyBpbnN0YW5jZW9mIFY1Pyh0aGlzLnY9ZSx0aGlzKTpuZXcgVjUoZSl9ZnVuY3Rpb24gdzVlKGUsdCxyKXtpZighU3ltYm9sLmFzeW5jSXRlcmF0b3IpdGhyb3cgbmV3IFR5cGVFcnJvcigiU3ltYm9sLmFzeW5jSXRlcmF0b3IgaXMgbm90IGRlZmluZWQuIik7dmFyIG49ci5hcHBseShlLHR8fFtdKSxpLG89W107cmV0dXJuIGk9e30sYSgibmV4dCIpLGEoInRocm93IiksYSgicmV0dXJuIiksaVtTeW1ib2wuYXN5bmNJdGVyYXRvcl09ZnVuY3Rpb24oKXtyZXR1cm4gdGhpc30saTtmdW5jdGlvbiBhKGYpe25bZl0mJihpW2ZdPWZ1bmN0aW9uKHApe3JldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbihkLGcpe28ucHVzaChbZixwLGQsZ10pPjF8fHMoZixwKX0pfSl9ZnVuY3Rpb24gcyhmLHApe3RyeXtsKG5bZl0ocCkpfWNhdGNoKGQpe2gob1swXVszXSxkKX19ZnVuY3Rpb24gbChmKXtmLnZhbHVlIGluc3RhbmNlb2YgVjU/UHJvbWlzZS5yZXNvbHZlKGYudmFsdWUudikudGhlbihjLHUpOmgob1swXVsyXSxmKX1mdW5jdGlvbiBjKGYpe3MoIm5leHQiLGYpfWZ1bmN0aW9uIHUoZil7cygidGhyb3ciLGYpfWZ1bmN0aW9uIGgoZixwKXtmKHApLG8uc2hpZnQoKSxvLmxlbmd0aCYmcyhvWzBdWzBdLG9bMF1bMV0pfX1mdW5jdGlvbiBTNWUoZSl7dmFyIHQscjtyZXR1cm4gdD17fSxuKCJuZXh0IiksbigidGhyb3ciLGZ1bmN0aW9uKGkpe3Rocm93IGl9KSxuKCJyZXR1cm4iKSx0W1N5bWJvbC5pdGVyYXRvcl09ZnVuY3Rpb24oKXtyZXR1cm4gdGhpc30sdDtmdW5jdGlvbiBuKGksbyl7ZVtpXSYmKHRbaV09ZnVuY3Rpb24oYSl7cmV0dXJuKHI9IXIpP3t2YWx1ZTpWNShlW2ldKGEpKSxkb25lOmk9PT0icmV0dXJuIn06bz9vKGEpOmF9KX19ZnVuY3Rpb24gTTVlKGUpe2lmKCFTeW1ib2wuYXN5bmNJdGVyYXRvcil0aHJvdyBuZXcgVHlwZUVycm9yKCJTeW1ib2wuYXN5bmNJdGVyYXRvciBpcyBub3QgZGVmaW5lZC4iKTt2YXIgdD1lW1N5bWJvbC5hc3luY0l0ZXJhdG9yXTtyZXR1cm4gdD90LmNhbGwoZSk6dHlwZW9mIHYkPT0iZnVuY3Rpb24iP3YkKGUpOmVbU3ltYm9sLml0ZXJhdG9yXSgpfWZ1bmN0aW9uIEU1ZShlLHQpe3JldHVybiBPYmplY3QuZGVmaW5lUHJvcGVydHk/T2JqZWN0LmRlZmluZVByb3BlcnR5KGUsInJhdyIse3ZhbHVlOnR9KTplLnJhdz10LGV9dmFyIGg1ZSxwNWUsZGU9TSgoKT0+e2g1ZT1PYmplY3Quc2V0UHJvdG90eXBlT2Z8fHtfX3Byb3RvX186W119aW5zdGFuY2VvZiBBcnJheSYmZnVuY3Rpb24oZSx0KXtlLl9fcHJvdG9fXz10fXx8ZnVuY3Rpb24oZSx0KXtmb3IodmFyIHIgaW4gdCl0Lmhhc093blByb3BlcnR5KHIpJiYoZVtyXT10W3JdKX07cDVlPU9iamVjdC5hc3NpZ258fGZ1bmN0aW9uKHQpe2Zvcih2YXIgcixuPTEsaT1hcmd1bWVudHMubGVuZ3RoO248aTtuKyspe3I9YXJndW1lbnRzW25dO2Zvcih2YXIgbyBpbiByKU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChyLG8pJiYodFtvXT1yW29dKX1yZXR1cm4gdH19KTt2YXIgUTN0LHRNdD1NKCgpPT57UTN0PSI0LjEzLjAifSk7ZnVuY3Rpb24gX2MoZSx0KXtyZXR1cm4gZTx0Py0xOmU+dD8xOmU+PXQ/MDpOYU59dmFyIHB5PU0oKCk9Pnt9KTtmdW5jdGlvbiBvUihlKXtyZXR1cm4gZS5sZW5ndGg9PT0xJiYoZT1UNWUoZSkpLHtsZWZ0OmZ1bmN0aW9uKHQscixuLGkpe2ZvcihuPT1udWxsJiYobj0wKSxpPT1udWxsJiYoaT10Lmxlbmd0aCk7bjxpOyl7dmFyIG89bitpPj4+MTtlKHRbb10scik8MD9uPW8rMTppPW99cmV0dXJuIG59LHJpZ2h0OmZ1bmN0aW9uKHQscixuLGkpe2ZvcihuPT1udWxsJiYobj0wKSxpPT1udWxsJiYoaT10Lmxlbmd0aCk7bjxpOyl7dmFyIG89bitpPj4+MTtlKHRbb10scik+MD9pPW86bj1vKzF9cmV0dXJuIG59fX1mdW5jdGlvbiBUNWUoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7cmV0dXJuIF9jKGUodCkscil9fXZhciB4JD1NKCgpPT57cHkoKX0pO3ZhciBlTXQsYiQsck10LGFSLHckPU0oKCk9PntweSgpO3gkKCk7ZU10PW9SKF9jKSxiJD1lTXQucmlnaHQsck10PWVNdC5sZWZ0LGFSPWIkfSk7ZnVuY3Rpb24gbk10KGUsdCl7dD09bnVsbCYmKHQ9UyQpO2Zvcih2YXIgcj0wLG49ZS5sZW5ndGgtMSxpPWVbMF0sbz1uZXcgQXJyYXkobjwwPzA6bik7cjxuOylvW3JdPXQoaSxpPWVbKytyXSk7cmV0dXJuIG99ZnVuY3Rpb24gUyQoZSx0KXtyZXR1cm5bZSx0XX12YXIgTSQ9TSgoKT0+e30pO2Z1bmN0aW9uIGlNdChlLHQscil7dmFyIG49ZS5sZW5ndGgsaT10Lmxlbmd0aCxvPW5ldyBBcnJheShuKmkpLGEscyxsLGM7Zm9yKHI9PW51bGwmJihyPVMkKSxhPWw9MDthPG47KythKWZvcihjPWVbYV0scz0wO3M8aTsrK3MsKytsKW9bbF09cihjLHRbc10pO3JldHVybiBvfXZhciBvTXQ9TSgoKT0+e00kKCl9KTtmdW5jdGlvbiBhTXQoZSx0KXtyZXR1cm4gdDxlPy0xOnQ+ZT8xOnQ+PWU/MDpOYU59dmFyIHNNdD1NKCgpPT57fSk7ZnVuY3Rpb24gYWwoZSl7cmV0dXJuIGU9PT1udWxsP05hTjorZX12YXIgbDI9TSgoKT0+e30pO2Z1bmN0aW9uIHNSKGUsdCl7dmFyIHI9ZS5sZW5ndGgsbj0wLGk9LTEsbz0wLGEscyxsPTA7aWYodD09bnVsbClmb3IoOysraTxyOylpc05hTihhPWFsKGVbaV0pKXx8KHM9YS1vLG8rPXMvKytuLGwrPXMqKGEtbykpO2Vsc2UgZm9yKDsrK2k8cjspaXNOYU4oYT1hbCh0KGVbaV0saSxlKSkpfHwocz1hLW8sbys9cy8rK24sbCs9cyooYS1vKSk7aWYobj4xKXJldHVybiBsLyhuLTEpfXZhciBFJD1NKCgpPT57bDIoKX0pO2Z1bmN0aW9uIGxSKGUsdCl7dmFyIHI9c1IoZSx0KTtyZXR1cm4gciYmTWF0aC5zcXJ0KHIpfXZhciBUJD1NKCgpPT57RSQoKX0pO2Z1bmN0aW9uIGNSKGUsdCl7dmFyIHI9ZS5sZW5ndGgsbj0tMSxpLG8sYTtpZih0PT1udWxsKXtmb3IoOysrbjxyOylpZigoaT1lW25dKSE9bnVsbCYmaT49aSlmb3Iobz1hPWk7KytuPHI7KShpPWVbbl0pIT1udWxsJiYobz5pJiYobz1pKSxhPGkmJihhPWkpKX1lbHNlIGZvcig7KytuPHI7KWlmKChpPXQoZVtuXSxuLGUpKSE9bnVsbCYmaT49aSlmb3Iobz1hPWk7KytuPHI7KShpPXQoZVtuXSxuLGUpKSE9bnVsbCYmKG8+aSYmKG89aSksYTxpJiYoYT1pKSk7cmV0dXJuW28sYV19dmFyIEMkPU0oKCk9Pnt9KTt2YXIgbE10LGNNdCx1TXQsQSQ9TSgoKT0+e2xNdD1BcnJheS5wcm90b3R5cGUsY010PWxNdC5zbGljZSx1TXQ9bE10Lm1hcH0pO2Z1bmN0aW9uIFU1KGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX12YXIgaE10PU0oKCk9Pnt9KTtmdW5jdGlvbiBmTXQoZSl7cmV0dXJuIGV9dmFyIHBNdD1NKCgpPT57fSk7ZnVuY3Rpb24gdVIoZSx0LHIpe2U9K2UsdD0rdCxyPShpPWFyZ3VtZW50cy5sZW5ndGgpPDI/KHQ9ZSxlPTAsMSk6aTwzPzE6K3I7Zm9yKHZhciBuPS0xLGk9TWF0aC5tYXgoMCxNYXRoLmNlaWwoKHQtZSkvcikpfDAsbz1uZXcgQXJyYXkoaSk7KytuPGk7KW9bbl09ZStuKnI7cmV0dXJuIG99dmFyIFAkPU0oKCk9Pnt9KTtmdW5jdGlvbiBkTXQoZSx0LHIpe3ZhciBuLGk9LTEsbyxhLHM7aWYodD0rdCxlPStlLHI9K3IsZT09PXQmJnI+MClyZXR1cm5bZV07aWYoKG49dDxlKSYmKG89ZSxlPXQsdD1vKSwocz1SJChlLHQscikpPT09MHx8IWlzRmluaXRlKHMpKXJldHVybltdO2lmKHM+MClmb3IoZT1NYXRoLmNlaWwoZS9zKSx0PU1hdGguZmxvb3IodC9zKSxhPW5ldyBBcnJheShvPU1hdGguY2VpbCh0LWUrMSkpOysraTxvOylhW2ldPShlK2kpKnM7ZWxzZSBmb3IoZT1NYXRoLmZsb29yKGUqcyksdD1NYXRoLmNlaWwodCpzKSxhPW5ldyBBcnJheShvPU1hdGguY2VpbChlLXQrMSkpOysraTxvOylhW2ldPShlLWkpL3M7cmV0dXJuIG4mJmEucmV2ZXJzZSgpLGF9ZnVuY3Rpb24gUiQoZSx0LHIpe3ZhciBuPSh0LWUpL01hdGgubWF4KDAsciksaT1NYXRoLmZsb29yKE1hdGgubG9nKG4pL01hdGguTE4xMCksbz1uL01hdGgucG93KDEwLGkpO3JldHVybiBpPj0wPyhvPj1JJD8xMDpvPj1MJD81Om8+PWskPzI6MSkqTWF0aC5wb3coMTAsaSk6LU1hdGgucG93KDEwLC1pKS8obz49SSQ/MTA6bz49TCQ/NTpvPj1rJD8yOjEpfWZ1bmN0aW9uIGhSKGUsdCxyKXt2YXIgbj1NYXRoLmFicyh0LWUpL01hdGgubWF4KDAsciksaT1NYXRoLnBvdygxMCxNYXRoLmZsb29yKE1hdGgubG9nKG4pL01hdGguTE4xMCkpLG89bi9pO3JldHVybiBvPj1JJD9pKj0xMDpvPj1MJD9pKj01Om8+PWskJiYoaSo9MiksdDxlPy1pOml9dmFyIEkkLEwkLGskLE4kPU0oKCk9PntJJD1NYXRoLnNxcnQoNTApLEwkPU1hdGguc3FydCgxMCksayQ9TWF0aC5zcXJ0KDIpfSk7ZnVuY3Rpb24gZlIoZSl7cmV0dXJuIE1hdGguY2VpbChNYXRoLmxvZyhlLmxlbmd0aCkvTWF0aC5MTjIpKzF9dmFyIEQkPU0oKCk9Pnt9KTtmdW5jdGlvbiBtTXQoKXt2YXIgZT1mTXQsdD1jUixyPWZSO2Z1bmN0aW9uIG4oaSl7dmFyIG8sYT1pLmxlbmd0aCxzLGw9bmV3IEFycmF5KGEpO2ZvcihvPTA7bzxhOysrbylsW29dPWUoaVtvXSxvLGkpO3ZhciBjPXQobCksdT1jWzBdLGg9Y1sxXSxmPXIobCx1LGgpO0FycmF5LmlzQXJyYXkoZil8fChmPWhSKHUsaCxmKSxmPXVSKE1hdGguY2VpbCh1L2YpKmYsTWF0aC5mbG9vcihoL2YpKmYsZikpO2Zvcih2YXIgcD1mLmxlbmd0aDtmWzBdPD11OylmLnNoaWZ0KCksLS1wO2Zvcig7ZltwLTFdPmg7KWYucG9wKCksLS1wO3ZhciBkPW5ldyBBcnJheShwKzEpLGc7Zm9yKG89MDtvPD1wOysrbylnPWRbb109W10sZy54MD1vPjA/ZltvLTFdOnUsZy54MT1vPHA/ZltvXTpoO2ZvcihvPTA7bzxhOysrbylzPWxbb10sdTw9cyYmczw9aCYmZFthUihmLHMsMCxwKV0ucHVzaChpW29dKTtyZXR1cm4gZH1yZXR1cm4gbi52YWx1ZT1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2YgaT09ImZ1bmN0aW9uIj9pOlU1KGkpLG4pOmV9LG4uZG9tYWluPWZ1bmN0aW9uKGkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBpPT0iZnVuY3Rpb24iP2k6VTUoW2lbMF0saVsxXV0pLG4pOnR9LG4udGhyZXNob2xkcz1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2YgaT09ImZ1bmN0aW9uIj9pOkFycmF5LmlzQXJyYXkoaSk/VTUoY010LmNhbGwoaSkpOlU1KGkpLG4pOnJ9LG59dmFyIGdNdD1NKCgpPT57QSQoKTt3JCgpO2hNdCgpO0MkKCk7cE10KCk7UCQoKTtOJCgpO0QkKCl9KTtmdW5jdGlvbiBkeShlLHQscil7aWYocj09bnVsbCYmKHI9YWwpLCEhKG49ZS5sZW5ndGgpKXtpZigodD0rdCk8PTB8fG48MilyZXR1cm4rcihlWzBdLDAsZSk7aWYodD49MSlyZXR1cm4rcihlW24tMV0sbi0xLGUpO3ZhciBuLGk9KG4tMSkqdCxvPU1hdGguZmxvb3IoaSksYT0rcihlW29dLG8sZSkscz0rcihlW28rMV0sbysxLGUpO3JldHVybiBhKyhzLWEpKihpLW8pfX12YXIgcFI9TSgoKT0+e2wyKCl9KTtmdW5jdGlvbiBfTXQoZSx0LHIpe3JldHVybiBlPXVNdC5jYWxsKGUsYWwpLnNvcnQoX2MpLE1hdGguY2VpbCgoci10KS8oMiooZHkoZSwuNzUpLWR5KGUsLjI1KSkqTWF0aC5wb3coZS5sZW5ndGgsLTEvMykpKX12YXIgeU10PU0oKCk9PntBJCgpO3B5KCk7bDIoKTtwUigpfSk7ZnVuY3Rpb24gdk10KGUsdCxyKXtyZXR1cm4gTWF0aC5jZWlsKChyLXQpLygzLjUqbFIoZSkqTWF0aC5wb3coZS5sZW5ndGgsLTEvMykpKX12YXIgeE10PU0oKCk9PntUJCgpfSk7ZnVuY3Rpb24gYk10KGUsdCl7dmFyIHI9ZS5sZW5ndGgsbj0tMSxpLG87aWYodD09bnVsbCl7Zm9yKDsrK248cjspaWYoKGk9ZVtuXSkhPW51bGwmJmk+PWkpZm9yKG89aTsrK248cjspKGk9ZVtuXSkhPW51bGwmJmk+byYmKG89aSl9ZWxzZSBmb3IoOysrbjxyOylpZigoaT10KGVbbl0sbixlKSkhPW51bGwmJmk+PWkpZm9yKG89aTsrK248cjspKGk9dChlW25dLG4sZSkpIT1udWxsJiZpPm8mJihvPWkpO3JldHVybiBvfXZhciB3TXQ9TSgoKT0+e30pO2Z1bmN0aW9uIFNNdChlLHQpe3ZhciByPWUubGVuZ3RoLG49cixpPS0xLG8sYT0wO2lmKHQ9PW51bGwpZm9yKDsrK2k8cjspaXNOYU4obz1hbChlW2ldKSk/LS1uOmErPW87ZWxzZSBmb3IoOysraTxyOylpc05hTihvPWFsKHQoZVtpXSxpLGUpKSk/LS1uOmErPW87aWYobilyZXR1cm4gYS9ufXZhciBNTXQ9TSgoKT0+e2wyKCl9KTtmdW5jdGlvbiBFTXQoZSx0KXt2YXIgcj1lLmxlbmd0aCxuPS0xLGksbz1bXTtpZih0PT1udWxsKWZvcig7KytuPHI7KWlzTmFOKGk9YWwoZVtuXSkpfHxvLnB1c2goaSk7ZWxzZSBmb3IoOysrbjxyOylpc05hTihpPWFsKHQoZVtuXSxuLGUpKSl8fG8ucHVzaChpKTtyZXR1cm4gZHkoby5zb3J0KF9jKSwuNSl9dmFyIFRNdD1NKCgpPT57cHkoKTtsMigpO3BSKCl9KTtmdW5jdGlvbiBDTXQoZSl7Zm9yKHZhciB0PWUubGVuZ3RoLHIsbj0tMSxpPTAsbyxhOysrbjx0OylpKz1lW25dLmxlbmd0aDtmb3Iobz1uZXcgQXJyYXkoaSk7LS10Pj0wOylmb3IoYT1lW3RdLHI9YS5sZW5ndGg7LS1yPj0wOylvWy0taV09YVtyXTtyZXR1cm4gb312YXIgQU10PU0oKCk9Pnt9KTtmdW5jdGlvbiBkUihlLHQpe3ZhciByPWUubGVuZ3RoLG49LTEsaSxvO2lmKHQ9PW51bGwpe2Zvcig7KytuPHI7KWlmKChpPWVbbl0pIT1udWxsJiZpPj1pKWZvcihvPWk7KytuPHI7KShpPWVbbl0pIT1udWxsJiZvPmkmJihvPWkpfWVsc2UgZm9yKDsrK248cjspaWYoKGk9dChlW25dLG4sZSkpIT1udWxsJiZpPj1pKWZvcihvPWk7KytuPHI7KShpPXQoZVtuXSxuLGUpKSE9bnVsbCYmbz5pJiYobz1pKTtyZXR1cm4gb312YXIgTyQ9TSgoKT0+e30pO2Z1bmN0aW9uIFBNdChlLHQpe2Zvcih2YXIgcj10Lmxlbmd0aCxuPW5ldyBBcnJheShyKTtyLS07KW5bcl09ZVt0W3JdXTtyZXR1cm4gbn12YXIgSU10PU0oKCk9Pnt9KTtmdW5jdGlvbiBMTXQoZSx0KXtpZighIShyPWUubGVuZ3RoKSl7dmFyIHIsbj0wLGk9MCxvLGE9ZVtpXTtmb3IodD09bnVsbCYmKHQ9X2MpOysrbjxyOykodChvPWVbbl0sYSk8MHx8dChhLGEpIT09MCkmJihhPW8saT1uKTtpZih0KGEsYSk9PT0wKXJldHVybiBpfX12YXIga010PU0oKCk9PntweSgpfSk7ZnVuY3Rpb24gUk10KGUsdCxyKXtmb3IodmFyIG49KHI9PW51bGw/ZS5sZW5ndGg6ciktKHQ9dD09bnVsbD8wOit0KSxpLG87bjspbz1NYXRoLnJhbmRvbSgpKm4tLXwwLGk9ZVtuK3RdLGVbbit0XT1lW28rdF0sZVtvK3RdPWk7cmV0dXJuIGV9dmFyIE5NdD1NKCgpPT57fSk7ZnVuY3Rpb24gRE10KGUsdCl7dmFyIHI9ZS5sZW5ndGgsbj0tMSxpLG89MDtpZih0PT1udWxsKWZvcig7KytuPHI7KShpPStlW25dKSYmKG8rPWkpO2Vsc2UgZm9yKDsrK248cjspKGk9K3QoZVtuXSxuLGUpKSYmKG8rPWkpO3JldHVybiBvfXZhciBPTXQ9TSgoKT0+e30pO2Z1bmN0aW9uIG1SKGUpe2lmKCEobz1lLmxlbmd0aCkpcmV0dXJuW107Zm9yKHZhciB0PS0xLHI9ZFIoZSxDNWUpLG49bmV3IEFycmF5KHIpOysrdDxyOylmb3IodmFyIGk9LTEsbyxhPW5bdF09bmV3IEFycmF5KG8pOysraTxvOylhW2ldPWVbaV1bdF07cmV0dXJuIG59ZnVuY3Rpb24gQzVlKGUpe3JldHVybiBlLmxlbmd0aH12YXIgeiQ9TSgoKT0+e08kKCl9KTtmdW5jdGlvbiB6TXQoKXtyZXR1cm4gbVIoYXJndW1lbnRzKX12YXIgRk10PU0oKCk9Pnt6JCgpfSk7dmFyIEJNdD1NKCgpPT57dyQoKTtweSgpO3gkKCk7b010KCk7c010KCk7VCQoKTtDJCgpO2dNdCgpO3lNdCgpO3hNdCgpO0QkKCk7d010KCk7TU10KCk7VE10KCk7QU10KCk7TyQoKTtNJCgpO0lNdCgpO3BSKCk7UCQoKTtrTXQoKTtOTXQoKTtPTXQoKTtOJCgpO3okKCk7RSQoKTtGTXQoKX0pO3ZhciBnUixITXQ9TSgoKT0+e2dSPUFycmF5LnByb3RvdHlwZS5zbGljZX0pO2Z1bmN0aW9uIFZNdChlKXtyZXR1cm4gZX12YXIgVU10PU0oKCk9Pnt9KTtmdW5jdGlvbiBBNWUoZSl7cmV0dXJuInRyYW5zbGF0ZSgiKyhlKy41KSsiLDApIn1mdW5jdGlvbiBQNWUoZSl7cmV0dXJuInRyYW5zbGF0ZSgwLCIrKGUrLjUpKyIpIn1mdW5jdGlvbiBJNWUoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3JldHVybitlKHQpfX1mdW5jdGlvbiBMNWUoZSl7dmFyIHQ9TWF0aC5tYXgoMCxlLmJhbmR3aWR0aCgpLTEpLzI7cmV0dXJuIGUucm91bmQoKSYmKHQ9TWF0aC5yb3VuZCh0KSksZnVuY3Rpb24ocil7cmV0dXJuK2UocikrdH19ZnVuY3Rpb24gazVlKCl7cmV0dXJuIXRoaXMuX19heGlzfWZ1bmN0aW9uIHZSKGUsdCl7dmFyIHI9W10sbj1udWxsLGk9bnVsbCxvPTYsYT02LHM9MyxsPWU9PT1fUnx8ZT09PXE1Py0xOjEsYz1lPT09cTV8fGU9PT15Uj8ieCI6InkiLHU9ZT09PV9SfHxlPT09RiQ/QTVlOlA1ZTtmdW5jdGlvbiBoKGYpe3ZhciBwPW49PW51bGw/dC50aWNrcz90LnRpY2tzLmFwcGx5KHQscik6dC5kb21haW4oKTpuLGQ9aT09bnVsbD90LnRpY2tGb3JtYXQ/dC50aWNrRm9ybWF0LmFwcGx5KHQscik6Vk10OmksZz1NYXRoLm1heChvLDApK3MsXz10LnJhbmdlKCkseT0rX1swXSsuNSx4PStfW18ubGVuZ3RoLTFdKy41LGI9KHQuYmFuZHdpZHRoP0w1ZTpJNWUpKHQuY29weSgpKSxTPWYuc2VsZWN0aW9uP2Yuc2VsZWN0aW9uKCk6ZixDPVMuc2VsZWN0QWxsKCIuZG9tYWluIikuZGF0YShbbnVsbF0pLFA9Uy5zZWxlY3RBbGwoIi50aWNrIikuZGF0YShwLHQpLm9yZGVyKCksaz1QLmV4aXQoKSxPPVAuZW50ZXIoKS5hcHBlbmQoImciKS5hdHRyKCJjbGFzcyIsInRpY2siKSxEPVAuc2VsZWN0KCJsaW5lIiksQj1QLnNlbGVjdCgidGV4dCIpO0M9Qy5tZXJnZShDLmVudGVyKCkuaW5zZXJ0KCJwYXRoIiwiLnRpY2siKS5hdHRyKCJjbGFzcyIsImRvbWFpbiIpLmF0dHIoInN0cm9rZSIsIiMwMDAiKSksUD1QLm1lcmdlKE8pLEQ9RC5tZXJnZShPLmFwcGVuZCgibGluZSIpLmF0dHIoInN0cm9rZSIsIiMwMDAiKS5hdHRyKGMrIjIiLGwqbykpLEI9Qi5tZXJnZShPLmFwcGVuZCgidGV4dCIpLmF0dHIoImZpbGwiLCIjMDAwIikuYXR0cihjLGwqZykuYXR0cigiZHkiLGU9PT1fUj8iMGVtIjplPT09RiQ/IjAuNzFlbSI6IjAuMzJlbSIpKSxmIT09UyYmKEM9Qy50cmFuc2l0aW9uKGYpLFA9UC50cmFuc2l0aW9uKGYpLEQ9RC50cmFuc2l0aW9uKGYpLEI9Qi50cmFuc2l0aW9uKGYpLGs9ay50cmFuc2l0aW9uKGYpLmF0dHIoIm9wYWNpdHkiLHFNdCkuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihJKXtyZXR1cm4gaXNGaW5pdGUoST1iKEkpKT91KEkpOnRoaXMuZ2V0QXR0cmlidXRlKCJ0cmFuc2Zvcm0iKX0pLE8uYXR0cigib3BhY2l0eSIscU10KS5hdHRyKCJ0cmFuc2Zvcm0iLGZ1bmN0aW9uKEkpe3ZhciBMPXRoaXMucGFyZW50Tm9kZS5fX2F4aXM7cmV0dXJuIHUoTCYmaXNGaW5pdGUoTD1MKEkpKT9MOmIoSSkpfSkpLGsucmVtb3ZlKCksQy5hdHRyKCJkIixlPT09cTV8fGU9PXlSPyJNIitsKmErIiwiK3krIkgwLjVWIit4KyJIIitsKmE6Ik0iK3krIiwiK2wqYSsiVjAuNUgiK3grIlYiK2wqYSksUC5hdHRyKCJvcGFjaXR5IiwxKS5hdHRyKCJ0cmFuc2Zvcm0iLGZ1bmN0aW9uKEkpe3JldHVybiB1KGIoSSkpfSksRC5hdHRyKGMrIjIiLGwqbyksQi5hdHRyKGMsbCpnKS50ZXh0KGQpLFMuZmlsdGVyKGs1ZSkuYXR0cigiZmlsbCIsIm5vbmUiKS5hdHRyKCJmb250LXNpemUiLDEwKS5hdHRyKCJmb250LWZhbWlseSIsInNhbnMtc2VyaWYiKS5hdHRyKCJ0ZXh0LWFuY2hvciIsZT09PXlSPyJzdGFydCI6ZT09PXE1PyJlbmQiOiJtaWRkbGUiKSxTLmVhY2goZnVuY3Rpb24oKXt0aGlzLl9fYXhpcz1ifSl9cmV0dXJuIGguc2NhbGU9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9ZixoKTp0fSxoLnRpY2tzPWZ1bmN0aW9uKCl7cmV0dXJuIHI9Z1IuY2FsbChhcmd1bWVudHMpLGh9LGgudGlja0FyZ3VtZW50cz1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj1mPT1udWxsP1tdOmdSLmNhbGwoZiksaCk6ci5zbGljZSgpfSxoLnRpY2tWYWx1ZXM9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49Zj09bnVsbD9udWxsOmdSLmNhbGwoZiksaCk6biYmbi5zbGljZSgpfSxoLnRpY2tGb3JtYXQ9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9ZixoKTppfSxoLnRpY2tTaXplPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPWE9K2YsaCk6b30saC50aWNrU2l6ZUlubmVyPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPStmLGgpOm99LGgudGlja1NpemVPdXRlcj1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYT0rZixoKTphfSxoLnRpY2tQYWRkaW5nPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhzPStmLGgpOnN9LGh9ZnVuY3Rpb24gR010KGUpe3JldHVybiB2UihfUixlKX1mdW5jdGlvbiBXTXQoZSl7cmV0dXJuIHZSKHlSLGUpfWZ1bmN0aW9uIFlNdChlKXtyZXR1cm4gdlIoRiQsZSl9ZnVuY3Rpb24gak10KGUpe3JldHVybiB2UihxNSxlKX12YXIgX1IseVIsRiQscTUscU10LFhNdD1NKCgpPT57SE10KCk7VU10KCk7X1I9MSx5Uj0yLEYkPTMscTU9NCxxTXQ9MWUtNn0pO3ZhciAkTXQ9TSgoKT0+e1hNdCgpfSk7ZnVuY3Rpb24gWk10KCl7Zm9yKHZhciBlPTAsdD1hcmd1bWVudHMubGVuZ3RoLHI9e30sbjtlPHQ7KytlKXtpZighKG49YXJndW1lbnRzW2VdKyIiKXx8biBpbiByfHwvW1xzLl0vLnRlc3QobikpdGhyb3cgbmV3IEVycm9yKCJpbGxlZ2FsIHR5cGU6ICIrbik7cltuXT1bXX1yZXR1cm4gbmV3IHhSKHIpfWZ1bmN0aW9uIHhSKGUpe3RoaXMuXz1lfWZ1bmN0aW9uIE41ZShlLHQpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKS5tYXAoZnVuY3Rpb24ocil7dmFyIG49IiIsaT1yLmluZGV4T2YoIi4iKTtpZihpPj0wJiYobj1yLnNsaWNlKGkrMSkscj1yLnNsaWNlKDAsaSkpLHImJiF0Lmhhc093blByb3BlcnR5KHIpKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK3IpO3JldHVybnt0eXBlOnIsbmFtZTpufX0pfWZ1bmN0aW9uIEQ1ZShlLHQpe2Zvcih2YXIgcj0wLG49ZS5sZW5ndGgsaTtyPG47KytyKWlmKChpPWVbcl0pLm5hbWU9PT10KXJldHVybiBpLnZhbHVlfWZ1bmN0aW9uIEtNdChlLHQscil7Zm9yKHZhciBuPTAsaT1lLmxlbmd0aDtuPGk7KytuKWlmKGVbbl0ubmFtZT09PXQpe2Vbbl09UjVlLGU9ZS5zbGljZSgwLG4pLmNvbmNhdChlLnNsaWNlKG4rMSkpO2JyZWFrfXJldHVybiByIT1udWxsJiZlLnB1c2goe25hbWU6dCx2YWx1ZTpyfSksZX12YXIgUjVlLEc1LEpNdD1NKCgpPT57UjVlPXt2YWx1ZTpmdW5jdGlvbigpe319O3hSLnByb3RvdHlwZT1aTXQucHJvdG90eXBlPXtjb25zdHJ1Y3Rvcjp4UixvbjpmdW5jdGlvbihlLHQpe3ZhciByPXRoaXMuXyxuPU41ZShlKyIiLHIpLGksbz0tMSxhPW4ubGVuZ3RoO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7Zm9yKDsrK288YTspaWYoKGk9KGU9bltvXSkudHlwZSkmJihpPUQ1ZShyW2ldLGUubmFtZSkpKXJldHVybiBpO3JldHVybn1pZih0IT1udWxsJiZ0eXBlb2YgdCE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgY2FsbGJhY2s6ICIrdCk7Zm9yKDsrK288YTspaWYoaT0oZT1uW29dKS50eXBlKXJbaV09S010KHJbaV0sZS5uYW1lLHQpO2Vsc2UgaWYodD09bnVsbClmb3IoaSBpbiByKXJbaV09S010KHJbaV0sZS5uYW1lLG51bGwpO3JldHVybiB0aGlzfSxjb3B5OmZ1bmN0aW9uKCl7dmFyIGU9e30sdD10aGlzLl87Zm9yKHZhciByIGluIHQpZVtyXT10W3JdLnNsaWNlKCk7cmV0dXJuIG5ldyB4UihlKX0sY2FsbDpmdW5jdGlvbihlLHQpe2lmKChpPWFyZ3VtZW50cy5sZW5ndGgtMik+MClmb3IodmFyIHI9bmV3IEFycmF5KGkpLG49MCxpLG87bjxpOysrbilyW25dPWFyZ3VtZW50c1tuKzJdO2lmKCF0aGlzLl8uaGFzT3duUHJvcGVydHkoZSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrZSk7Zm9yKG89dGhpcy5fW2VdLG49MCxpPW8ubGVuZ3RoO248aTsrK24pb1tuXS52YWx1ZS5hcHBseSh0LHIpfSxhcHBseTpmdW5jdGlvbihlLHQscil7aWYoIXRoaXMuXy5oYXNPd25Qcm9wZXJ0eShlKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gdHlwZTogIitlKTtmb3IodmFyIG49dGhpcy5fW2VdLGk9MCxvPW4ubGVuZ3RoO2k8bzsrK2kpbltpXS52YWx1ZS5hcHBseSh0LHIpfX07RzU9Wk10fSk7dmFyIEIkPU0oKCk9PntKTXQoKX0pO3ZhciBiUixIJCxWJD1NKCgpPT57YlI9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiLEgkPXtzdmc6Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIix4aHRtbDpiUix4bGluazoiaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIseG1sOiJodHRwOi8vd3d3LnczLm9yZy9YTUwvMTk5OC9uYW1lc3BhY2UiLHhtbG5zOiJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3htbG5zLyJ9fSk7ZnVuY3Rpb24gVnAoZSl7dmFyIHQ9ZSs9IiIscj10LmluZGV4T2YoIjoiKTtyZXR1cm4gcj49MCYmKHQ9ZS5zbGljZSgwLHIpKSE9PSJ4bWxucyImJihlPWUuc2xpY2UocisxKSksSCQuaGFzT3duUHJvcGVydHkodCk/e3NwYWNlOkgkW3RdLGxvY2FsOmV9OmV9dmFyIHdSPU0oKCk9PntWJCgpfSk7ZnVuY3Rpb24gTzVlKGUpe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PXRoaXMub3duZXJEb2N1bWVudCxyPXRoaXMubmFtZXNwYWNlVVJJO3JldHVybiByPT09YlImJnQuZG9jdW1lbnRFbGVtZW50Lm5hbWVzcGFjZVVSST09PWJSP3QuY3JlYXRlRWxlbWVudChlKTp0LmNyZWF0ZUVsZW1lbnROUyhyLGUpfX1mdW5jdGlvbiB6NWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMub3duZXJEb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoZS5zcGFjZSxlLmxvY2FsKX19ZnVuY3Rpb24gU1IoZSl7dmFyIHQ9VnAoZSk7cmV0dXJuKHQubG9jYWw/ejVlOk81ZSkodCl9dmFyIFUkPU0oKCk9Pnt3UigpO1YkKCl9KTtmdW5jdGlvbiBGNWUoKXt9ZnVuY3Rpb24gbXkoZSl7cmV0dXJuIGU9PW51bGw/RjVlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMucXVlcnlTZWxlY3RvcihlKX19dmFyIE1SPU0oKCk9Pnt9KTtmdW5jdGlvbiBRTXQoZSl7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPW15KGUpKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9dC5sZW5ndGgsbj1uZXcgQXJyYXkociksaT0wO2k8cjsrK2kpZm9yKHZhciBvPXRbaV0sYT1vLmxlbmd0aCxzPW5baV09bmV3IEFycmF5KGEpLGwsYyx1PTA7dTxhOysrdSkobD1vW3VdKSYmKGM9ZS5jYWxsKGwsbC5fX2RhdGFfXyx1LG8pKSYmKCJfX2RhdGFfXyJpbiBsJiYoYy5fX2RhdGFfXz1sLl9fZGF0YV9fKSxzW3VdPWMpO3JldHVybiBuZXcgcGkobix0aGlzLl9wYXJlbnRzKX12YXIgdEV0PU0oKCk9Pnt3dSgpO01SKCl9KTtmdW5jdGlvbiBCNWUoKXtyZXR1cm5bXX1mdW5jdGlvbiBXNShlKXtyZXR1cm4gZT09bnVsbD9CNWU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5xdWVyeVNlbGVjdG9yQWxsKGUpfX12YXIgcSQ9TSgoKT0+e30pO2Z1bmN0aW9uIGVFdChlKXt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9VzUoZSkpO2Zvcih2YXIgdD10aGlzLl9ncm91cHMscj10Lmxlbmd0aCxuPVtdLGk9W10sbz0wO288cjsrK28pZm9yKHZhciBhPXRbb10scz1hLmxlbmd0aCxsLGM9MDtjPHM7KytjKShsPWFbY10pJiYobi5wdXNoKGUuY2FsbChsLGwuX19kYXRhX18sYyxhKSksaS5wdXNoKGwpKTtyZXR1cm4gbmV3IHBpKG4saSl9dmFyIHJFdD1NKCgpPT57d3UoKTtxJCgpfSk7ZnVuY3Rpb24gWTUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMubWF0Y2hlcyhlKX19dmFyIEckPU0oKCk9Pnt9KTtmdW5jdGlvbiBuRXQoZSl7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPVk1KGUpKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9dC5sZW5ndGgsbj1uZXcgQXJyYXkociksaT0wO2k8cjsrK2kpZm9yKHZhciBvPXRbaV0sYT1vLmxlbmd0aCxzPW5baV09W10sbCxjPTA7YzxhOysrYykobD1vW2NdKSYmZS5jYWxsKGwsbC5fX2RhdGFfXyxjLG8pJiZzLnB1c2gobCk7cmV0dXJuIG5ldyBwaShuLHRoaXMuX3BhcmVudHMpfXZhciBpRXQ9TSgoKT0+e3d1KCk7RyQoKX0pO2Z1bmN0aW9uIEVSKGUpe3JldHVybiBuZXcgQXJyYXkoZS5sZW5ndGgpfXZhciBXJD1NKCgpPT57fSk7ZnVuY3Rpb24gb0V0KCl7cmV0dXJuIG5ldyBwaSh0aGlzLl9lbnRlcnx8dGhpcy5fZ3JvdXBzLm1hcChFUiksdGhpcy5fcGFyZW50cyl9ZnVuY3Rpb24gajUoZSx0KXt0aGlzLm93bmVyRG9jdW1lbnQ9ZS5vd25lckRvY3VtZW50LHRoaXMubmFtZXNwYWNlVVJJPWUubmFtZXNwYWNlVVJJLHRoaXMuX25leHQ9bnVsbCx0aGlzLl9wYXJlbnQ9ZSx0aGlzLl9fZGF0YV9fPXR9dmFyIFkkPU0oKCk9PntXJCgpO3d1KCk7ajUucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpqNSxhcHBlbmRDaGlsZDpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fcGFyZW50Lmluc2VydEJlZm9yZShlLHRoaXMuX25leHQpfSxpbnNlcnRCZWZvcmU6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gdGhpcy5fcGFyZW50Lmluc2VydEJlZm9yZShlLHQpfSxxdWVyeVNlbGVjdG9yOmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLl9wYXJlbnQucXVlcnlTZWxlY3RvcihlKX0scXVlcnlTZWxlY3RvckFsbDpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fcGFyZW50LnF1ZXJ5U2VsZWN0b3JBbGwoZSl9fX0pO2Z1bmN0aW9uIGFFdChlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIHNFdD1NKCgpPT57fSk7ZnVuY3Rpb24gSDVlKGUsdCxyLG4saSxvKXtmb3IodmFyIGE9MCxzLGw9dC5sZW5ndGgsYz1vLmxlbmd0aDthPGM7KythKShzPXRbYV0pPyhzLl9fZGF0YV9fPW9bYV0sblthXT1zKTpyW2FdPW5ldyBqNShlLG9bYV0pO2Zvcig7YTxsOysrYSkocz10W2FdKSYmKGlbYV09cyl9ZnVuY3Rpb24gVjVlKGUsdCxyLG4saSxvLGEpe3ZhciBzLGwsYz17fSx1PXQubGVuZ3RoLGg9by5sZW5ndGgsZj1uZXcgQXJyYXkodSkscDtmb3Iocz0wO3M8dTsrK3MpKGw9dFtzXSkmJihmW3NdPXA9bEV0K2EuY2FsbChsLGwuX19kYXRhX18scyx0KSxwIGluIGM/aVtzXT1sOmNbcF09bCk7Zm9yKHM9MDtzPGg7KytzKXA9bEV0K2EuY2FsbChlLG9bc10scyxvKSwobD1jW3BdKT8obltzXT1sLGwuX19kYXRhX189b1tzXSxjW3BdPW51bGwpOnJbc109bmV3IGo1KGUsb1tzXSk7Zm9yKHM9MDtzPHU7KytzKShsPXRbc10pJiZjW2Zbc11dPT09bCYmKGlbc109bCl9ZnVuY3Rpb24gY0V0KGUsdCl7aWYoIWUpcmV0dXJuIHA9bmV3IEFycmF5KHRoaXMuc2l6ZSgpKSxjPS0xLHRoaXMuZWFjaChmdW5jdGlvbihQKXtwWysrY109UH0pLHA7dmFyIHI9dD9WNWU6SDVlLG49dGhpcy5fcGFyZW50cyxpPXRoaXMuX2dyb3Vwczt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9YUV0KGUpKTtmb3IodmFyIG89aS5sZW5ndGgsYT1uZXcgQXJyYXkobykscz1uZXcgQXJyYXkobyksbD1uZXcgQXJyYXkobyksYz0wO2M8bzsrK2Mpe3ZhciB1PW5bY10saD1pW2NdLGY9aC5sZW5ndGgscD1lLmNhbGwodSx1JiZ1Ll9fZGF0YV9fLGMsbiksZD1wLmxlbmd0aCxnPXNbY109bmV3IEFycmF5KGQpLF89YVtjXT1uZXcgQXJyYXkoZCkseT1sW2NdPW5ldyBBcnJheShmKTtyKHUsaCxnLF8seSxwLHQpO2Zvcih2YXIgeD0wLGI9MCxTLEM7eDxkOysreClpZihTPWdbeF0pe2Zvcih4Pj1iJiYoYj14KzEpOyEoQz1fW2JdKSYmKytiPGQ7KTtTLl9uZXh0PUN8fG51bGx9fXJldHVybiBhPW5ldyBwaShhLG4pLGEuX2VudGVyPXMsYS5fZXhpdD1sLGF9dmFyIGxFdCx1RXQ9TSgoKT0+e3d1KCk7WSQoKTtzRXQoKTtsRXQ9IiQifSk7ZnVuY3Rpb24gaEV0KCl7cmV0dXJuIG5ldyBwaSh0aGlzLl9leGl0fHx0aGlzLl9ncm91cHMubWFwKEVSKSx0aGlzLl9wYXJlbnRzKX12YXIgZkV0PU0oKCk9PntXJCgpO3d1KCl9KTtmdW5jdGlvbiBwRXQoZSx0LHIpe3ZhciBuPXRoaXMuZW50ZXIoKSxpPXRoaXMsbz10aGlzLmV4aXQoKTtyZXR1cm4gbj10eXBlb2YgZT09ImZ1bmN0aW9uIj9lKG4pOm4uYXBwZW5kKGUrIiIpLHQhPW51bGwmJihpPXQoaSkpLHI9PW51bGw/by5yZW1vdmUoKTpyKG8pLG4mJmk/bi5tZXJnZShpKS5vcmRlcigpOml9dmFyIGRFdD1NKCgpPT57fSk7ZnVuY3Rpb24gbUV0KGUpe2Zvcih2YXIgdD10aGlzLl9ncm91cHMscj1lLl9ncm91cHMsbj10Lmxlbmd0aCxpPXIubGVuZ3RoLG89TWF0aC5taW4obixpKSxhPW5ldyBBcnJheShuKSxzPTA7czxvOysrcylmb3IodmFyIGw9dFtzXSxjPXJbc10sdT1sLmxlbmd0aCxoPWFbc109bmV3IEFycmF5KHUpLGYscD0wO3A8dTsrK3ApKGY9bFtwXXx8Y1twXSkmJihoW3BdPWYpO2Zvcig7czxuOysrcylhW3NdPXRbc107cmV0dXJuIG5ldyBwaShhLHRoaXMuX3BhcmVudHMpfXZhciBnRXQ9TSgoKT0+e3d1KCl9KTtmdW5jdGlvbiBfRXQoKXtmb3IodmFyIGU9dGhpcy5fZ3JvdXBzLHQ9LTEscj1lLmxlbmd0aDsrK3Q8cjspZm9yKHZhciBuPWVbdF0saT1uLmxlbmd0aC0xLG89bltpXSxhOy0taT49MDspKGE9bltpXSkmJihvJiZhLmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKG8pXjQmJm8ucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUoYSxvKSxvPWEpO3JldHVybiB0aGlzfXZhciB5RXQ9TSgoKT0+e30pO2Z1bmN0aW9uIHZFdChlKXtlfHwoZT1VNWUpO2Z1bmN0aW9uIHQoaCxmKXtyZXR1cm4gaCYmZj9lKGguX19kYXRhX18sZi5fX2RhdGFfXyk6IWgtIWZ9Zm9yKHZhciByPXRoaXMuX2dyb3VwcyxuPXIubGVuZ3RoLGk9bmV3IEFycmF5KG4pLG89MDtvPG47KytvKXtmb3IodmFyIGE9cltvXSxzPWEubGVuZ3RoLGw9aVtvXT1uZXcgQXJyYXkocyksYyx1PTA7dTxzOysrdSkoYz1hW3VdKSYmKGxbdV09Yyk7bC5zb3J0KHQpfXJldHVybiBuZXcgcGkoaSx0aGlzLl9wYXJlbnRzKS5vcmRlcigpfWZ1bmN0aW9uIFU1ZShlLHQpe3JldHVybiBlPHQ/LTE6ZT50PzE6ZT49dD8wOk5hTn12YXIgeEV0PU0oKCk9Pnt3dSgpfSk7ZnVuY3Rpb24gYkV0KCl7dmFyIGU9YXJndW1lbnRzWzBdO3JldHVybiBhcmd1bWVudHNbMF09dGhpcyxlLmFwcGx5KG51bGwsYXJndW1lbnRzKSx0aGlzfXZhciB3RXQ9TSgoKT0+e30pO2Z1bmN0aW9uIFNFdCgpe3ZhciBlPW5ldyBBcnJheSh0aGlzLnNpemUoKSksdD0tMTtyZXR1cm4gdGhpcy5lYWNoKGZ1bmN0aW9uKCl7ZVsrK3RdPXRoaXN9KSxlfXZhciBNRXQ9TSgoKT0+e30pO2Z1bmN0aW9uIEVFdCgpe2Zvcih2YXIgZT10aGlzLl9ncm91cHMsdD0wLHI9ZS5sZW5ndGg7dDxyOysrdClmb3IodmFyIG49ZVt0XSxpPTAsbz1uLmxlbmd0aDtpPG87KytpKXt2YXIgYT1uW2ldO2lmKGEpcmV0dXJuIGF9cmV0dXJuIG51bGx9dmFyIFRFdD1NKCgpPT57fSk7ZnVuY3Rpb24gQ0V0KCl7dmFyIGU9MDtyZXR1cm4gdGhpcy5lYWNoKGZ1bmN0aW9uKCl7KytlfSksZX12YXIgQUV0PU0oKCk9Pnt9KTtmdW5jdGlvbiBQRXQoKXtyZXR1cm4hdGhpcy5ub2RlKCl9dmFyIElFdD1NKCgpPT57fSk7ZnVuY3Rpb24gTEV0KGUpe2Zvcih2YXIgdD10aGlzLl9ncm91cHMscj0wLG49dC5sZW5ndGg7cjxuOysrcilmb3IodmFyIGk9dFtyXSxvPTAsYT1pLmxlbmd0aCxzO288YTsrK28pKHM9aVtvXSkmJmUuY2FsbChzLHMuX19kYXRhX18sbyxpKTtyZXR1cm4gdGhpc312YXIga0V0PU0oKCk9Pnt9KTtmdW5jdGlvbiBxNWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGUoZSl9fWZ1bmN0aW9uIEc1ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnJlbW92ZUF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCl9fWZ1bmN0aW9uIFc1ZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc2V0QXR0cmlidXRlKGUsdCl9fWZ1bmN0aW9uIFk1ZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc2V0QXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsLHQpfX1mdW5jdGlvbiBqNWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgcj10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyPT1udWxsP3RoaXMucmVtb3ZlQXR0cmlidXRlKGUpOnRoaXMuc2V0QXR0cmlidXRlKGUscil9fWZ1bmN0aW9uIFg1ZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3ZhciByPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3I9PW51bGw/dGhpcy5yZW1vdmVBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpOnRoaXMuc2V0QXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsLHIpfX1mdW5jdGlvbiBSRXQoZSx0KXt2YXIgcj1WcChlKTtpZihhcmd1bWVudHMubGVuZ3RoPDIpe3ZhciBuPXRoaXMubm9kZSgpO3JldHVybiByLmxvY2FsP24uZ2V0QXR0cmlidXRlTlMoci5zcGFjZSxyLmxvY2FsKTpuLmdldEF0dHJpYnV0ZShyKX1yZXR1cm4gdGhpcy5lYWNoKCh0PT1udWxsP3IubG9jYWw/RzVlOnE1ZTp0eXBlb2YgdD09ImZ1bmN0aW9uIj9yLmxvY2FsP1g1ZTpqNWU6ci5sb2NhbD9ZNWU6VzVlKShyLHQpKX12YXIgTkV0PU0oKCk9Pnt3UigpfSk7ZnVuY3Rpb24gVFIoZSl7cmV0dXJuIGUub3duZXJEb2N1bWVudCYmZS5vd25lckRvY3VtZW50LmRlZmF1bHRWaWV3fHxlLmRvY3VtZW50JiZlfHxlLmRlZmF1bHRWaWV3fXZhciBqJD1NKCgpPT57fSk7ZnVuY3Rpb24gJDVlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkoZSl9fWZ1bmN0aW9uIEs1ZShlLHQscil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zdHlsZS5zZXRQcm9wZXJ0eShlLHQscil9fWZ1bmN0aW9uIFo1ZShlLHQscil7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIG49dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7bj09bnVsbD90aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KGUpOnRoaXMuc3R5bGUuc2V0UHJvcGVydHkoZSxuLHIpfX1mdW5jdGlvbiBERXQoZSx0LHIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPjE/dGhpcy5lYWNoKCh0PT1udWxsPyQ1ZTp0eXBlb2YgdD09ImZ1bmN0aW9uIj9aNWU6SzVlKShlLHQscj09bnVsbD8iIjpyKSk6YWcodGhpcy5ub2RlKCksZSl9ZnVuY3Rpb24gYWcoZSx0KXtyZXR1cm4gZS5zdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKHQpfHxUUihlKS5nZXRDb21wdXRlZFN0eWxlKGUsbnVsbCkuZ2V0UHJvcGVydHlWYWx1ZSh0KX12YXIgWCQ9TSgoKT0+e2okKCl9KTtmdW5jdGlvbiBKNWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7ZGVsZXRlIHRoaXNbZV19fWZ1bmN0aW9uIFE1ZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3RoaXNbZV09dH19ZnVuY3Rpb24gdFRlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHI9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cj09bnVsbD9kZWxldGUgdGhpc1tlXTp0aGlzW2VdPXJ9fWZ1bmN0aW9uIE9FdChlLHQpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPjE/dGhpcy5lYWNoKCh0PT1udWxsP0o1ZTp0eXBlb2YgdD09ImZ1bmN0aW9uIj90VGU6UTVlKShlLHQpKTp0aGlzLm5vZGUoKVtlXX12YXIgekV0PU0oKCk9Pnt9KTtmdW5jdGlvbiBGRXQoZSl7cmV0dXJuIGUudHJpbSgpLnNwbGl0KC9efFxzKy8pfWZ1bmN0aW9uICQkKGUpe3JldHVybiBlLmNsYXNzTGlzdHx8bmV3IEJFdChlKX1mdW5jdGlvbiBCRXQoZSl7dGhpcy5fbm9kZT1lLHRoaXMuX25hbWVzPUZFdChlLmdldEF0dHJpYnV0ZSgiY2xhc3MiKXx8IiIpfWZ1bmN0aW9uIEhFdChlLHQpe2Zvcih2YXIgcj0kJChlKSxuPS0xLGk9dC5sZW5ndGg7KytuPGk7KXIuYWRkKHRbbl0pfWZ1bmN0aW9uIFZFdChlLHQpe2Zvcih2YXIgcj0kJChlKSxuPS0xLGk9dC5sZW5ndGg7KytuPGk7KXIucmVtb3ZlKHRbbl0pfWZ1bmN0aW9uIGVUZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtIRXQodGhpcyxlKX19ZnVuY3Rpb24gclRlKGUpe3JldHVybiBmdW5jdGlvbigpe1ZFdCh0aGlzLGUpfX1mdW5jdGlvbiBuVGUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXsodC5hcHBseSh0aGlzLGFyZ3VtZW50cyk/SEV0OlZFdCkodGhpcyxlKX19ZnVuY3Rpb24gVUV0KGUsdCl7dmFyIHI9RkV0KGUrIiIpO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7Zm9yKHZhciBuPSQkKHRoaXMubm9kZSgpKSxpPS0xLG89ci5sZW5ndGg7KytpPG87KWlmKCFuLmNvbnRhaW5zKHJbaV0pKXJldHVybiExO3JldHVybiEwfXJldHVybiB0aGlzLmVhY2goKHR5cGVvZiB0PT0iZnVuY3Rpb24iP25UZTp0P2VUZTpyVGUpKHIsdCkpfXZhciBxRXQ9TSgoKT0+e0JFdC5wcm90b3R5cGU9e2FkZDpmdW5jdGlvbihlKXt2YXIgdD10aGlzLl9uYW1lcy5pbmRleE9mKGUpO3Q8MCYmKHRoaXMuX25hbWVzLnB1c2goZSksdGhpcy5fbm9kZS5zZXRBdHRyaWJ1dGUoImNsYXNzIix0aGlzLl9uYW1lcy5qb2luKCIgIikpKX0scmVtb3ZlOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX25hbWVzLmluZGV4T2YoZSk7dD49MCYmKHRoaXMuX25hbWVzLnNwbGljZSh0LDEpLHRoaXMuX25vZGUuc2V0QXR0cmlidXRlKCJjbGFzcyIsdGhpcy5fbmFtZXMuam9pbigiICIpKSl9LGNvbnRhaW5zOmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLl9uYW1lcy5pbmRleE9mKGUpPj0wfX19KTtmdW5jdGlvbiBpVGUoKXt0aGlzLnRleHRDb250ZW50PSIifWZ1bmN0aW9uIG9UZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnRleHRDb250ZW50PWV9fWZ1bmN0aW9uIGFUZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD1lLmFwcGx5KHRoaXMsYXJndW1lbnRzKTt0aGlzLnRleHRDb250ZW50PXQ9PW51bGw/IiI6dH19ZnVuY3Rpb24gR0V0KGUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaChlPT1udWxsP2lUZToodHlwZW9mIGU9PSJmdW5jdGlvbiI/YVRlOm9UZSkoZSkpOnRoaXMubm9kZSgpLnRleHRDb250ZW50fXZhciBXRXQ9TSgoKT0+e30pO2Z1bmN0aW9uIHNUZSgpe3RoaXMuaW5uZXJIVE1MPSIifWZ1bmN0aW9uIGxUZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLmlubmVySFRNTD1lfX1mdW5jdGlvbiBjVGUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyk7dGhpcy5pbm5lckhUTUw9dD09bnVsbD8iIjp0fX1mdW5jdGlvbiBZRXQoZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5lYWNoKGU9PW51bGw/c1RlOih0eXBlb2YgZT09ImZ1bmN0aW9uIj9jVGU6bFRlKShlKSk6dGhpcy5ub2RlKCkuaW5uZXJIVE1MfXZhciBqRXQ9TSgoKT0+e30pO2Z1bmN0aW9uIHVUZSgpe3RoaXMubmV4dFNpYmxpbmcmJnRoaXMucGFyZW50Tm9kZS5hcHBlbmRDaGlsZCh0aGlzKX1mdW5jdGlvbiBYRXQoKXtyZXR1cm4gdGhpcy5lYWNoKHVUZSl9dmFyICRFdD1NKCgpPT57fSk7ZnVuY3Rpb24gaFRlKCl7dGhpcy5wcmV2aW91c1NpYmxpbmcmJnRoaXMucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUodGhpcyx0aGlzLnBhcmVudE5vZGUuZmlyc3RDaGlsZCl9ZnVuY3Rpb24gS0V0KCl7cmV0dXJuIHRoaXMuZWFjaChoVGUpfXZhciBaRXQ9TSgoKT0+e30pO2Z1bmN0aW9uIEpFdChlKXt2YXIgdD10eXBlb2YgZT09ImZ1bmN0aW9uIj9lOlNSKGUpO3JldHVybiB0aGlzLnNlbGVjdChmdW5jdGlvbigpe3JldHVybiB0aGlzLmFwcGVuZENoaWxkKHQuYXBwbHkodGhpcyxhcmd1bWVudHMpKX0pfXZhciBRRXQ9TSgoKT0+e1UkKCl9KTtmdW5jdGlvbiBmVGUoKXtyZXR1cm4gbnVsbH1mdW5jdGlvbiB0NXQoZSx0KXt2YXIgcj10eXBlb2YgZT09ImZ1bmN0aW9uIj9lOlNSKGUpLG49dD09bnVsbD9mVGU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/dDpteSh0KTtyZXR1cm4gdGhpcy5zZWxlY3QoZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5pbnNlcnRCZWZvcmUoci5hcHBseSh0aGlzLGFyZ3VtZW50cyksbi5hcHBseSh0aGlzLGFyZ3VtZW50cyl8fG51bGwpfSl9dmFyIGU1dD1NKCgpPT57VSQoKTtNUigpfSk7ZnVuY3Rpb24gcFRlKCl7dmFyIGU9dGhpcy5wYXJlbnROb2RlO2UmJmUucmVtb3ZlQ2hpbGQodGhpcyl9ZnVuY3Rpb24gcjV0KCl7cmV0dXJuIHRoaXMuZWFjaChwVGUpfXZhciBuNXQ9TSgoKT0+e30pO2Z1bmN0aW9uIGRUZSgpe3ZhciBlPXRoaXMuY2xvbmVOb2RlKCExKSx0PXRoaXMucGFyZW50Tm9kZTtyZXR1cm4gdD90Lmluc2VydEJlZm9yZShlLHRoaXMubmV4dFNpYmxpbmcpOmV9ZnVuY3Rpb24gbVRlKCl7dmFyIGU9dGhpcy5jbG9uZU5vZGUoITApLHQ9dGhpcy5wYXJlbnROb2RlO3JldHVybiB0P3QuaW5zZXJ0QmVmb3JlKGUsdGhpcy5uZXh0U2libGluZyk6ZX1mdW5jdGlvbiBpNXQoZSl7cmV0dXJuIHRoaXMuc2VsZWN0KGU/bVRlOmRUZSl9dmFyIG81dD1NKCgpPT57fSk7ZnVuY3Rpb24gYTV0KGUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMucHJvcGVydHkoIl9fZGF0YV9fIixlKTp0aGlzLm5vZGUoKS5fX2RhdGFfX312YXIgczV0PU0oKCk9Pnt9KTtmdW5jdGlvbiBnVGUoZSx0LHIpe3JldHVybiBlPXU1dChlLHQsciksZnVuY3Rpb24obil7dmFyIGk9bi5yZWxhdGVkVGFyZ2V0OyghaXx8aSE9PXRoaXMmJiEoaS5jb21wYXJlRG9jdW1lbnRQb3NpdGlvbih0aGlzKSY4KSkmJmUuY2FsbCh0aGlzLG4pfX1mdW5jdGlvbiB1NXQoZSx0LHIpe3JldHVybiBmdW5jdGlvbihuKXt2YXIgaT13cjt3cj1uO3RyeXtlLmNhbGwodGhpcyx0aGlzLl9fZGF0YV9fLHQscil9ZmluYWxseXt3cj1pfX19ZnVuY3Rpb24gX1RlKGUpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKS5tYXAoZnVuY3Rpb24odCl7dmFyIHI9IiIsbj10LmluZGV4T2YoIi4iKTtyZXR1cm4gbj49MCYmKHI9dC5zbGljZShuKzEpLHQ9dC5zbGljZSgwLG4pKSx7dHlwZTp0LG5hbWU6cn19KX1mdW5jdGlvbiB5VGUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5fX29uO2lmKCEhdCl7Zm9yKHZhciByPTAsbj0tMSxpPXQubGVuZ3RoLG87cjxpOysrcilvPXRbcl0sKCFlLnR5cGV8fG8udHlwZT09PWUudHlwZSkmJm8ubmFtZT09PWUubmFtZT90aGlzLnJlbW92ZUV2ZW50TGlzdGVuZXIoby50eXBlLG8ubGlzdGVuZXIsby5jYXB0dXJlKTp0Wysrbl09bzsrK24/dC5sZW5ndGg9bjpkZWxldGUgdGhpcy5fX29ufX19ZnVuY3Rpb24gdlRlKGUsdCxyKXt2YXIgbj1jNXQuaGFzT3duUHJvcGVydHkoZS50eXBlKT9nVGU6dTV0O3JldHVybiBmdW5jdGlvbihpLG8sYSl7dmFyIHM9dGhpcy5fX29uLGwsYz1uKHQsbyxhKTtpZihzKXtmb3IodmFyIHU9MCxoPXMubGVuZ3RoO3U8aDsrK3UpaWYoKGw9c1t1XSkudHlwZT09PWUudHlwZSYmbC5uYW1lPT09ZS5uYW1lKXt0aGlzLnJlbW92ZUV2ZW50TGlzdGVuZXIobC50eXBlLGwubGlzdGVuZXIsbC5jYXB0dXJlKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIobC50eXBlLGwubGlzdGVuZXI9YyxsLmNhcHR1cmU9ciksbC52YWx1ZT10O3JldHVybn19dGhpcy5hZGRFdmVudExpc3RlbmVyKGUudHlwZSxjLHIpLGw9e3R5cGU6ZS50eXBlLG5hbWU6ZS5uYW1lLHZhbHVlOnQsbGlzdGVuZXI6YyxjYXB0dXJlOnJ9LHM/cy5wdXNoKGwpOnRoaXMuX19vbj1bbF19fWZ1bmN0aW9uIGg1dChlLHQscil7dmFyIG49X1RlKGUrIiIpLGksbz1uLmxlbmd0aCxhO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7dmFyIHM9dGhpcy5ub2RlKCkuX19vbjtpZihzKXtmb3IodmFyIGw9MCxjPXMubGVuZ3RoLHU7bDxjOysrbClmb3IoaT0wLHU9c1tsXTtpPG87KytpKWlmKChhPW5baV0pLnR5cGU9PT11LnR5cGUmJmEubmFtZT09PXUubmFtZSlyZXR1cm4gdS52YWx1ZX1yZXR1cm59Zm9yKHM9dD92VGU6eVRlLHI9PW51bGwmJihyPSExKSxpPTA7aTxvOysraSl0aGlzLmVhY2gocyhuW2ldLHQscikpO3JldHVybiB0aGlzfWZ1bmN0aW9uIEskKGUsdCxyLG4pe3ZhciBpPXdyO2Uuc291cmNlRXZlbnQ9d3Isd3I9ZTt0cnl7cmV0dXJuIHQuYXBwbHkocixuKX1maW5hbGx5e3dyPWl9fXZhciBjNXQsd3IsbDV0LENSPU0oKCk9PntjNXQ9e30sd3I9bnVsbDt0eXBlb2YgZG9jdW1lbnQhPSJ1bmRlZmluZWQiJiYobDV0PWRvY3VtZW50LmRvY3VtZW50RWxlbWVudCwib25tb3VzZWVudGVyImluIGw1dHx8KGM1dD17bW91c2VlbnRlcjoibW91c2VvdmVyIixtb3VzZWxlYXZlOiJtb3VzZW91dCJ9KSl9KTtmdW5jdGlvbiBmNXQoZSx0LHIpe3ZhciBuPVRSKGUpLGk9bi5DdXN0b21FdmVudDt0eXBlb2YgaT09ImZ1bmN0aW9uIj9pPW5ldyBpKHQscik6KGk9bi5kb2N1bWVudC5jcmVhdGVFdmVudCgiRXZlbnQiKSxyPyhpLmluaXRFdmVudCh0LHIuYnViYmxlcyxyLmNhbmNlbGFibGUpLGkuZGV0YWlsPXIuZGV0YWlsKTppLmluaXRFdmVudCh0LCExLCExKSksZS5kaXNwYXRjaEV2ZW50KGkpfWZ1bmN0aW9uIHhUZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBmNXQodGhpcyxlLHQpfX1mdW5jdGlvbiBiVGUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZjV0KHRoaXMsZSx0LmFwcGx5KHRoaXMsYXJndW1lbnRzKSl9fWZ1bmN0aW9uIHA1dChlLHQpe3JldHVybiB0aGlzLmVhY2goKHR5cGVvZiB0PT0iZnVuY3Rpb24iP2JUZTp4VGUpKGUsdCkpfXZhciBkNXQ9TSgoKT0+e2okKCl9KTtmdW5jdGlvbiBwaShlLHQpe3RoaXMuX2dyb3Vwcz1lLHRoaXMuX3BhcmVudHM9dH1mdW5jdGlvbiBtNXQoKXtyZXR1cm4gbmV3IHBpKFtbZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50XV0sWiQpfXZhciBaJCxVcCx3dT1NKCgpPT57dEV0KCk7ckV0KCk7aUV0KCk7dUV0KCk7WSQoKTtmRXQoKTtkRXQoKTtnRXQoKTt5RXQoKTt4RXQoKTt3RXQoKTtNRXQoKTtURXQoKTtBRXQoKTtJRXQoKTtrRXQoKTtORXQoKTtYJCgpO3pFdCgpO3FFdCgpO1dFdCgpO2pFdCgpOyRFdCgpO1pFdCgpO1FFdCgpO2U1dCgpO241dCgpO281dCgpO3M1dCgpO0NSKCk7ZDV0KCk7WiQ9W251bGxdO3BpLnByb3RvdHlwZT1tNXQucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpwaSxzZWxlY3Q6UU10LHNlbGVjdEFsbDplRXQsZmlsdGVyOm5FdCxkYXRhOmNFdCxlbnRlcjpvRXQsZXhpdDpoRXQsam9pbjpwRXQsbWVyZ2U6bUV0LG9yZGVyOl9FdCxzb3J0OnZFdCxjYWxsOmJFdCxub2RlczpTRXQsbm9kZTpFRXQsc2l6ZTpDRXQsZW1wdHk6UEV0LGVhY2g6TEV0LGF0dHI6UkV0LHN0eWxlOkRFdCxwcm9wZXJ0eTpPRXQsY2xhc3NlZDpVRXQsdGV4dDpHRXQsaHRtbDpZRXQscmFpc2U6WEV0LGxvd2VyOktFdCxhcHBlbmQ6SkV0LGluc2VydDp0NXQscmVtb3ZlOnI1dCxjbG9uZTppNXQsZGF0dW06YTV0LG9uOmg1dCxkaXNwYXRjaDpwNXR9O1VwPW01dH0pO2Z1bmN0aW9uIHFwKGUpe3JldHVybiB0eXBlb2YgZT09InN0cmluZyI/bmV3IHBpKFtbZG9jdW1lbnQucXVlcnlTZWxlY3RvcihlKV1dLFtkb2N1bWVudC5kb2N1bWVudEVsZW1lbnRdKTpuZXcgcGkoW1tlXV0sWiQpfXZhciBnNXQ9TSgoKT0+e3d1KCl9KTtmdW5jdGlvbiBfNXQoKXtmb3IodmFyIGU9d3IsdDt0PWUuc291cmNlRXZlbnQ7KWU9dDtyZXR1cm4gZX12YXIgeTV0PU0oKCk9PntDUigpfSk7ZnVuY3Rpb24gdjV0KGUsdCl7dmFyIHI9ZS5vd25lclNWR0VsZW1lbnR8fGU7aWYoci5jcmVhdGVTVkdQb2ludCl7dmFyIG49ci5jcmVhdGVTVkdQb2ludCgpO3JldHVybiBuLng9dC5jbGllbnRYLG4ueT10LmNsaWVudFksbj1uLm1hdHJpeFRyYW5zZm9ybShlLmdldFNjcmVlbkNUTSgpLmludmVyc2UoKSksW24ueCxuLnldfXZhciBpPWUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7cmV0dXJuW3QuY2xpZW50WC1pLmxlZnQtZS5jbGllbnRMZWZ0LHQuY2xpZW50WS1pLnRvcC1lLmNsaWVudFRvcF19dmFyIHg1dD1NKCgpPT57fSk7ZnVuY3Rpb24gQVIoZSl7dmFyIHQ9XzV0KCk7cmV0dXJuIHQuY2hhbmdlZFRvdWNoZXMmJih0PXQuY2hhbmdlZFRvdWNoZXNbMF0pLHY1dChlLHQpfXZhciBiNXQ9TSgoKT0+e3k1dCgpO3g1dCgpfSk7dmFyIEVzPU0oKCk9PntHJCgpO2I1dCgpO3dSKCk7ZzV0KCk7d3UoKTtNUigpO3EkKCk7WCQoKTtDUigpfSk7ZnVuY3Rpb24gUFIoKXt3ci5wcmV2ZW50RGVmYXVsdCgpLHdyLnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpfXZhciB3NXQ9TSgoKT0+e0VzKCl9KTtmdW5jdGlvbiBKJChlKXt2YXIgdD1lLmRvY3VtZW50LmRvY3VtZW50RWxlbWVudCxyPXFwKGUpLm9uKCJkcmFnc3RhcnQuZHJhZyIsUFIsITApOyJvbnNlbGVjdHN0YXJ0ImluIHQ/ci5vbigic2VsZWN0c3RhcnQuZHJhZyIsUFIsITApOih0Ll9fbm9zZWxlY3Q9dC5zdHlsZS5Nb3pVc2VyU2VsZWN0LHQuc3R5bGUuTW96VXNlclNlbGVjdD0ibm9uZSIpfWZ1bmN0aW9uIFEkKGUsdCl7dmFyIHI9ZS5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsbj1xcChlKS5vbigiZHJhZ3N0YXJ0LmRyYWciLG51bGwpO3QmJihuLm9uKCJjbGljay5kcmFnIixQUiwhMCksc2V0VGltZW91dChmdW5jdGlvbigpe24ub24oImNsaWNrLmRyYWciLG51bGwpfSwwKSksIm9uc2VsZWN0c3RhcnQiaW4gcj9uLm9uKCJzZWxlY3RzdGFydC5kcmFnIixudWxsKTooci5zdHlsZS5Nb3pVc2VyU2VsZWN0PXIuX19ub3NlbGVjdCxkZWxldGUgci5fX25vc2VsZWN0KX12YXIgUzV0PU0oKCk9PntFcygpO3c1dCgpfSk7dmFyIE01dD1NKCgpPT57UzV0KCl9KTtmdW5jdGlvbiBJUihlLHQscil7ZS5wcm90b3R5cGU9dC5wcm90b3R5cGU9cixyLmNvbnN0cnVjdG9yPWV9ZnVuY3Rpb24gdEsoZSx0KXt2YXIgcj1PYmplY3QuY3JlYXRlKGUucHJvdG90eXBlKTtmb3IodmFyIG4gaW4gdClyW25dPXRbbl07cmV0dXJuIHJ9dmFyIEU1dD1NKCgpPT57fSk7ZnVuY3Rpb24gSzUoKXt9ZnVuY3Rpb24gQzV0KCl7cmV0dXJuIHRoaXMucmdiKCkuZm9ybWF0SGV4KCl9ZnVuY3Rpb24gUFRlKCl7cmV0dXJuIFI1dCh0aGlzKS5mb3JtYXRIc2woKX1mdW5jdGlvbiBBNXQoKXtyZXR1cm4gdGhpcy5yZ2IoKS5mb3JtYXRSZ2IoKX1mdW5jdGlvbiBTdShlKXt2YXIgdCxyO3JldHVybiBlPShlKyIiKS50cmltKCkudG9Mb3dlckNhc2UoKSwodD13VGUuZXhlYyhlKSk/KHI9dFsxXS5sZW5ndGgsdD1wYXJzZUludCh0WzFdLDE2KSxyPT09Nj9QNXQodCk6cj09PTM/bmV3IHNsKHQ+PjgmMTV8dD4+NCYyNDAsdD4+NCYxNXx0JjI0MCwodCYxNSk8PDR8dCYxNSwxKTpyPT09OD9MUih0Pj4yNCYyNTUsdD4+MTYmMjU1LHQ+PjgmMjU1LCh0JjI1NSkvMjU1KTpyPT09ND9MUih0Pj4xMiYxNXx0Pj44JjI0MCx0Pj44JjE1fHQ+PjQmMjQwLHQ+PjQmMTV8dCYyNDAsKCh0JjE1KTw8NHx0JjE1KS8yNTUpOm51bGwpOih0PVNUZS5leGVjKGUpKT9uZXcgc2wodFsxXSx0WzJdLHRbM10sMSk6KHQ9TVRlLmV4ZWMoZSkpP25ldyBzbCh0WzFdKjI1NS8xMDAsdFsyXSoyNTUvMTAwLHRbM10qMjU1LzEwMCwxKToodD1FVGUuZXhlYyhlKSk/TFIodFsxXSx0WzJdLHRbM10sdFs0XSk6KHQ9VFRlLmV4ZWMoZSkpP0xSKHRbMV0qMjU1LzEwMCx0WzJdKjI1NS8xMDAsdFszXSoyNTUvMTAwLHRbNF0pOih0PUNUZS5leGVjKGUpKT9rNXQodFsxXSx0WzJdLzEwMCx0WzNdLzEwMCwxKToodD1BVGUuZXhlYyhlKSk/azV0KHRbMV0sdFsyXS8xMDAsdFszXS8xMDAsdFs0XSk6VDV0Lmhhc093blByb3BlcnR5KGUpP1A1dChUNXRbZV0pOmU9PT0idHJhbnNwYXJlbnQiP25ldyBzbChOYU4sTmFOLE5hTiwwKTpudWxsfWZ1bmN0aW9uIFA1dChlKXtyZXR1cm4gbmV3IHNsKGU+PjE2JjI1NSxlPj44JjI1NSxlJjI1NSwxKX1mdW5jdGlvbiBMUihlLHQscixuKXtyZXR1cm4gbjw9MCYmKGU9dD1yPU5hTiksbmV3IHNsKGUsdCxyLG4pfWZ1bmN0aW9uIElUZShlKXtyZXR1cm4gZSBpbnN0YW5jZW9mIEs1fHwoZT1TdShlKSksZT8oZT1lLnJnYigpLG5ldyBzbChlLnIsZS5nLGUuYixlLm9wYWNpdHkpKTpuZXcgc2x9ZnVuY3Rpb24gdTIoZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP0lUZShlKTpuZXcgc2woZSx0LHIsbj09bnVsbD8xOm4pfWZ1bmN0aW9uIHNsKGUsdCxyLG4pe3RoaXMucj0rZSx0aGlzLmc9K3QsdGhpcy5iPStyLHRoaXMub3BhY2l0eT0rbn1mdW5jdGlvbiBJNXQoKXtyZXR1cm4iIyIrZUsodGhpcy5yKStlSyh0aGlzLmcpK2VLKHRoaXMuYil9ZnVuY3Rpb24gTDV0KCl7dmFyIGU9dGhpcy5vcGFjaXR5O3JldHVybiBlPWlzTmFOKGUpPzE6TWF0aC5tYXgoMCxNYXRoLm1pbigxLGUpKSwoZT09PTE/InJnYigiOiJyZ2JhKCIpK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQodGhpcy5yKXx8MCkpKyIsICIrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCh0aGlzLmcpfHwwKSkrIiwgIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKHRoaXMuYil8fDApKSsoZT09PTE/IikiOiIsICIrZSsiKSIpfWZ1bmN0aW9uIGVLKGUpe3JldHVybiBlPU1hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQoZSl8fDApKSwoZTwxNj8iMCI6IiIpK2UudG9TdHJpbmcoMTYpfWZ1bmN0aW9uIGs1dChlLHQscixuKXtyZXR1cm4gbjw9MD9lPXQ9cj1OYU46cjw9MHx8cj49MT9lPXQ9TmFOOnQ8PTAmJihlPU5hTiksbmV3IEtoKGUsdCxyLG4pfWZ1bmN0aW9uIFI1dChlKXtpZihlIGluc3RhbmNlb2YgS2gpcmV0dXJuIG5ldyBLaChlLmgsZS5zLGUubCxlLm9wYWNpdHkpO2lmKGUgaW5zdGFuY2VvZiBLNXx8KGU9U3UoZSkpLCFlKXJldHVybiBuZXcgS2g7aWYoZSBpbnN0YW5jZW9mIEtoKXJldHVybiBlO2U9ZS5yZ2IoKTt2YXIgdD1lLnIvMjU1LHI9ZS5nLzI1NSxuPWUuYi8yNTUsaT1NYXRoLm1pbih0LHIsbiksbz1NYXRoLm1heCh0LHIsbiksYT1OYU4scz1vLWksbD0obytpKS8yO3JldHVybiBzPyh0PT09bz9hPShyLW4pL3MrKHI8bikqNjpyPT09bz9hPShuLXQpL3MrMjphPSh0LXIpL3MrNCxzLz1sPC41P28raToyLW8taSxhKj02MCk6cz1sPjAmJmw8MT8wOmEsbmV3IEtoKGEscyxsLGUub3BhY2l0eSl9ZnVuY3Rpb24gTjV0KGUsdCxyLG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MT9SNXQoZSk6bmV3IEtoKGUsdCxyLG49PW51bGw/MTpuKX1mdW5jdGlvbiBLaChlLHQscixuKXt0aGlzLmg9K2UsdGhpcy5zPSt0LHRoaXMubD0rcix0aGlzLm9wYWNpdHk9K259ZnVuY3Rpb24gcksoZSx0LHIpe3JldHVybihlPDYwP3QrKHItdCkqZS82MDplPDE4MD9yOmU8MjQwP3QrKHItdCkqKDI0MC1lKS82MDp0KSoyNTV9dmFyIFg1LGtSLGMyLCQ1LFpoLHdUZSxTVGUsTVRlLEVUZSxUVGUsQ1RlLEFUZSxUNXQsRDV0PU0oKCk9PntFNXQoKTtYNT0uNyxrUj0xL1g1LGMyPSJcXHMqKFsrLV0/XFxkKylcXHMqIiwkNT0iXFxzKihbKy1dP1xcZCpcXC4/XFxkKyg/OltlRV1bKy1dP1xcZCspPylcXHMqIixaaD0iXFxzKihbKy1dP1xcZCpcXC4/XFxkKyg/OltlRV1bKy1dP1xcZCspPyklXFxzKiIsd1RlPS9eIyhbMC05YS1mXXszLDh9KSQvLFNUZT1uZXcgUmVnRXhwKCJecmdiXFwoIitbYzIsYzIsYzJdKyJcXCkkIiksTVRlPW5ldyBSZWdFeHAoIl5yZ2JcXCgiK1taaCxaaCxaaF0rIlxcKSQiKSxFVGU9bmV3IFJlZ0V4cCgiXnJnYmFcXCgiK1tjMixjMixjMiwkNV0rIlxcKSQiKSxUVGU9bmV3IFJlZ0V4cCgiXnJnYmFcXCgiK1taaCxaaCxaaCwkNV0rIlxcKSQiKSxDVGU9bmV3IFJlZ0V4cCgiXmhzbFxcKCIrWyQ1LFpoLFpoXSsiXFwpJCIpLEFUZT1uZXcgUmVnRXhwKCJeaHNsYVxcKCIrWyQ1LFpoLFpoLCQ1XSsiXFwpJCIpLFQ1dD17YWxpY2VibHVlOjE1NzkyMzgzLGFudGlxdWV3aGl0ZToxNjQ0NDM3NSxhcXVhOjY1NTM1LGFxdWFtYXJpbmU6ODM4ODU2NCxhenVyZToxNTc5NDE3NSxiZWlnZToxNjExOTI2MCxiaXNxdWU6MTY3NzAyNDQsYmxhY2s6MCxibGFuY2hlZGFsbW9uZDoxNjc3MjA0NSxibHVlOjI1NSxibHVldmlvbGV0OjkwNTUyMDIsYnJvd246MTA4MjQyMzQsYnVybHl3b29kOjE0NTk2MjMxLGNhZGV0Ymx1ZTo2MjY2NTI4LGNoYXJ0cmV1c2U6ODM4ODM1MixjaG9jb2xhdGU6MTM3ODk0NzAsY29yYWw6MTY3NDQyNzIsY29ybmZsb3dlcmJsdWU6NjU5MTk4MSxjb3Juc2lsazoxNjc3NTM4OCxjcmltc29uOjE0NDIzMTAwLGN5YW46NjU1MzUsZGFya2JsdWU6MTM5LGRhcmtjeWFuOjM1NzIzLGRhcmtnb2xkZW5yb2Q6MTIwOTI5MzksZGFya2dyYXk6MTExMTkwMTcsZGFya2dyZWVuOjI1NjAwLGRhcmtncmV5OjExMTE5MDE3LGRhcmtraGFraToxMjQzMzI1OSxkYXJrbWFnZW50YTo5MTA5NjQzLGRhcmtvbGl2ZWdyZWVuOjU1OTc5OTksZGFya29yYW5nZToxNjc0NzUyMCxkYXJrb3JjaGlkOjEwMDQwMDEyLGRhcmtyZWQ6OTEwOTUwNCxkYXJrc2FsbW9uOjE1MzA4NDEwLGRhcmtzZWFncmVlbjo5NDE5OTE5LGRhcmtzbGF0ZWJsdWU6NDczNDM0NyxkYXJrc2xhdGVncmF5OjMxMDA0OTUsZGFya3NsYXRlZ3JleTozMTAwNDk1LGRhcmt0dXJxdW9pc2U6NTI5NDUsZGFya3Zpb2xldDo5Njk5NTM5LGRlZXBwaW5rOjE2NzE2OTQ3LGRlZXBza3libHVlOjQ5MTUxLGRpbWdyYXk6NjkwODI2NSxkaW1ncmV5OjY5MDgyNjUsZG9kZ2VyYmx1ZToyMDAzMTk5LGZpcmVicmljazoxMTY3NDE0NixmbG9yYWx3aGl0ZToxNjc3NTkyMCxmb3Jlc3RncmVlbjoyMjYzODQyLGZ1Y2hzaWE6MTY3MTE5MzUsZ2FpbnNib3JvOjE0NDc0NDYwLGdob3N0d2hpdGU6MTYzMTY2NzEsZ29sZDoxNjc2NjcyMCxnb2xkZW5yb2Q6MTQzMjkxMjAsZ3JheTo4NDIxNTA0LGdyZWVuOjMyNzY4LGdyZWVueWVsbG93OjExNDAzMDU1LGdyZXk6ODQyMTUwNCxob25leWRldzoxNTc5NDE2MCxob3RwaW5rOjE2NzM4NzQwLGluZGlhbnJlZDoxMzQ1ODUyNCxpbmRpZ286NDkxNTMzMCxpdm9yeToxNjc3NzIwMCxraGFraToxNTc4NzY2MCxsYXZlbmRlcjoxNTEzMjQxMCxsYXZlbmRlcmJsdXNoOjE2NzczMzY1LGxhd25ncmVlbjo4MTkwOTc2LGxlbW9uY2hpZmZvbjoxNjc3NTg4NSxsaWdodGJsdWU6MTEzOTMyNTQsbGlnaHRjb3JhbDoxNTc2MTUzNixsaWdodGN5YW46MTQ3NDU1OTksbGlnaHRnb2xkZW5yb2R5ZWxsb3c6MTY0NDgyMTAsbGlnaHRncmF5OjEzODgyMzIzLGxpZ2h0Z3JlZW46OTQ5ODI1NixsaWdodGdyZXk6MTM4ODIzMjMsbGlnaHRwaW5rOjE2NzU4NDY1LGxpZ2h0c2FsbW9uOjE2NzUyNzYyLGxpZ2h0c2VhZ3JlZW46MjE0Mjg5MCxsaWdodHNreWJsdWU6ODkwMDM0NixsaWdodHNsYXRlZ3JheTo3ODMzNzUzLGxpZ2h0c2xhdGVncmV5Ojc4MzM3NTMsbGlnaHRzdGVlbGJsdWU6MTE1ODQ3MzQsbGlnaHR5ZWxsb3c6MTY3NzcxODQsbGltZTo2NTI4MCxsaW1lZ3JlZW46MzMyOTMzMCxsaW5lbjoxNjQ0NTY3MCxtYWdlbnRhOjE2NzExOTM1LG1hcm9vbjo4Mzg4NjA4LG1lZGl1bWFxdWFtYXJpbmU6NjczNzMyMixtZWRpdW1ibHVlOjIwNSxtZWRpdW1vcmNoaWQ6MTIyMTE2NjcsbWVkaXVtcHVycGxlOjk2NjI2ODMsbWVkaXVtc2VhZ3JlZW46Mzk3ODA5NyxtZWRpdW1zbGF0ZWJsdWU6ODA4Nzc5MCxtZWRpdW1zcHJpbmdncmVlbjo2NDE1NCxtZWRpdW10dXJxdW9pc2U6NDc3MjMwMCxtZWRpdW12aW9sZXRyZWQ6MTMwNDcxNzMsbWlkbmlnaHRibHVlOjE2NDQ5MTIsbWludGNyZWFtOjE2MTIxODUwLG1pc3R5cm9zZToxNjc3MDI3Myxtb2NjYXNpbjoxNjc3MDIyOSxuYXZham93aGl0ZToxNjc2ODY4NSxuYXZ5OjEyOCxvbGRsYWNlOjE2NjQzNTU4LG9saXZlOjg0MjEzNzYsb2xpdmVkcmFiOjcwNDg3Mzksb3JhbmdlOjE2NzUzOTIwLG9yYW5nZXJlZDoxNjcyOTM0NCxvcmNoaWQ6MTQzMTU3MzQscGFsZWdvbGRlbnJvZDoxNTY1NzEzMCxwYWxlZ3JlZW46MTAwMjU4ODAscGFsZXR1cnF1b2lzZToxMTUyOTk2NixwYWxldmlvbGV0cmVkOjE0MzgxMjAzLHBhcGF5YXdoaXA6MTY3NzMwNzcscGVhY2hwdWZmOjE2NzY3NjczLHBlcnU6MTM0Njg5OTEscGluazoxNjc2MTAzNSxwbHVtOjE0NTI0NjM3LHBvd2RlcmJsdWU6MTE1OTE5MTAscHVycGxlOjgzODg3MzYscmViZWNjYXB1cnBsZTo2Njk3ODgxLHJlZDoxNjcxMTY4MCxyb3N5YnJvd246MTIzNTc1MTkscm95YWxibHVlOjQyODY5NDUsc2FkZGxlYnJvd246OTEyNzE4NyxzYWxtb246MTY0MTY4ODIsc2FuZHlicm93bjoxNjAzMjg2NCxzZWFncmVlbjozMDUwMzI3LHNlYXNoZWxsOjE2Nzc0NjM4LHNpZW5uYToxMDUwNjc5NyxzaWx2ZXI6MTI2MzIyNTYsc2t5Ymx1ZTo4OTAwMzMxLHNsYXRlYmx1ZTo2OTcwMDYxLHNsYXRlZ3JheTo3MzcyOTQ0LHNsYXRlZ3JleTo3MzcyOTQ0LHNub3c6MTY3NzU5MzAsc3ByaW5nZ3JlZW46NjU0MDcsc3RlZWxibHVlOjQ2MjA5ODAsdGFuOjEzODA4NzgwLHRlYWw6MzI4OTYsdGhpc3RsZToxNDIwNDg4OCx0b21hdG86MTY3MzcwOTUsdHVycXVvaXNlOjQyNTE4NTYsdmlvbGV0OjE1NjMxMDg2LHdoZWF0OjE2MTEzMzMxLHdoaXRlOjE2Nzc3MjE1LHdoaXRlc21va2U6MTYxMTkyODUseWVsbG93OjE2Nzc2OTYwLHllbGxvd2dyZWVuOjEwMTQ1MDc0fTtJUihLNSxTdSx7Y29weTpmdW5jdGlvbihlKXtyZXR1cm4gT2JqZWN0LmFzc2lnbihuZXcgdGhpcy5jb25zdHJ1Y3Rvcix0aGlzLGUpfSxkaXNwbGF5YWJsZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLnJnYigpLmRpc3BsYXlhYmxlKCl9LGhleDpDNXQsZm9ybWF0SGV4OkM1dCxmb3JtYXRIc2w6UFRlLGZvcm1hdFJnYjpBNXQsdG9TdHJpbmc6QTV0fSk7SVIoc2wsdTIsdEsoSzUse2JyaWdodGVyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/a1I6TWF0aC5wb3coa1IsZSksbmV3IHNsKHRoaXMuciplLHRoaXMuZyplLHRoaXMuYiplLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihlKXtyZXR1cm4gZT1lPT1udWxsP1g1Ok1hdGgucG93KFg1LGUpLG5ldyBzbCh0aGlzLnIqZSx0aGlzLmcqZSx0aGlzLmIqZSx0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpc30sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4tLjU8PXRoaXMuciYmdGhpcy5yPDI1NS41JiYtLjU8PXRoaXMuZyYmdGhpcy5nPDI1NS41JiYtLjU8PXRoaXMuYiYmdGhpcy5iPDI1NS41JiYwPD10aGlzLm9wYWNpdHkmJnRoaXMub3BhY2l0eTw9MX0saGV4Okk1dCxmb3JtYXRIZXg6STV0LGZvcm1hdFJnYjpMNXQsdG9TdHJpbmc6TDV0fSkpO0lSKEtoLE41dCx0SyhLNSx7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9rUjpNYXRoLnBvdyhrUixlKSxuZXcgS2godGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9YNTpNYXRoLnBvdyhYNSxlKSxuZXcgS2godGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLmglMzYwKyh0aGlzLmg8MCkqMzYwLHQ9aXNOYU4oZSl8fGlzTmFOKHRoaXMucyk/MDp0aGlzLnMscj10aGlzLmwsbj1yKyhyPC41P3I6MS1yKSp0LGk9MipyLW47cmV0dXJuIG5ldyBzbChySyhlPj0yNDA/ZS0yNDA6ZSsxMjAsaSxuKSxySyhlLGksbikscksoZTwxMjA/ZSsyNDA6ZS0xMjAsaSxuKSx0aGlzLm9wYWNpdHkpfSxkaXNwbGF5YWJsZTpmdW5jdGlvbigpe3JldHVybigwPD10aGlzLnMmJnRoaXMuczw9MXx8aXNOYU4odGhpcy5zKSkmJjA8PXRoaXMubCYmdGhpcy5sPD0xJiYwPD10aGlzLm9wYWNpdHkmJnRoaXMub3BhY2l0eTw9MX0sZm9ybWF0SHNsOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5vcGFjaXR5O3JldHVybiBlPWlzTmFOKGUpPzE6TWF0aC5tYXgoMCxNYXRoLm1pbigxLGUpKSwoZT09PTE/ImhzbCgiOiJoc2xhKCIpKyh0aGlzLmh8fDApKyIsICIrKHRoaXMuc3x8MCkqMTAwKyIlLCAiKyh0aGlzLmx8fDApKjEwMCsiJSIrKGU9PT0xPyIpIjoiLCAiK2UrIikiKX19KSl9KTt2YXIgUlI9TSgoKT0+e0Q1dCgpfSk7ZnVuY3Rpb24gbksoZSx0LHIsbixpKXt2YXIgbz1lKmUsYT1vKmU7cmV0dXJuKCgxLTMqZSszKm8tYSkqdCsoNC02Km8rMyphKSpyKygxKzMqZSszKm8tMyphKSpuK2EqaSkvNn1mdW5jdGlvbiBPNXQoZSl7dmFyIHQ9ZS5sZW5ndGgtMTtyZXR1cm4gZnVuY3Rpb24ocil7dmFyIG49cjw9MD9yPTA6cj49MT8ocj0xLHQtMSk6TWF0aC5mbG9vcihyKnQpLGk9ZVtuXSxvPWVbbisxXSxhPW4+MD9lW24tMV06MippLW8scz1uPHQtMT9lW24rMl06MipvLWk7cmV0dXJuIG5LKChyLW4vdCkqdCxhLGksbyxzKX19dmFyIGlLPU0oKCk9Pnt9KTtmdW5jdGlvbiB6NXQoZSl7dmFyIHQ9ZS5sZW5ndGg7cmV0dXJuIGZ1bmN0aW9uKHIpe3ZhciBuPU1hdGguZmxvb3IoKChyJT0xKTwwPysrcjpyKSp0KSxpPWVbKG4rdC0xKSV0XSxvPWVbbiV0XSxhPWVbKG4rMSkldF0scz1lWyhuKzIpJXRdO3JldHVybiBuSygoci1uL3QpKnQsaSxvLGEscyl9fXZhciBGNXQ9TSgoKT0+e2lLKCl9KTtmdW5jdGlvbiBaNShlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIG9LPU0oKCk9Pnt9KTtmdW5jdGlvbiBMVGUoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocil7cmV0dXJuIGUrcip0fX1mdW5jdGlvbiBrVGUoZSx0LHIpe3JldHVybiBlPU1hdGgucG93KGUsciksdD1NYXRoLnBvdyh0LHIpLWUscj0xL3IsZnVuY3Rpb24obil7cmV0dXJuIE1hdGgucG93KGUrbip0LHIpfX1mdW5jdGlvbiBCNXQoZSl7cmV0dXJuKGU9K2UpPT0xP05SOmZ1bmN0aW9uKHQscil7cmV0dXJuIHItdD9rVGUodCxyLGUpOlo1KGlzTmFOKHQpP3I6dCl9fWZ1bmN0aW9uIE5SKGUsdCl7dmFyIHI9dC1lO3JldHVybiByP0xUZShlLHIpOlo1KGlzTmFOKGUpP3Q6ZSl9dmFyIEg1dD1NKCgpPT57b0soKX0pO2Z1bmN0aW9uIFY1dChlKXtyZXR1cm4gZnVuY3Rpb24odCl7dmFyIHI9dC5sZW5ndGgsbj1uZXcgQXJyYXkociksaT1uZXcgQXJyYXkociksbz1uZXcgQXJyYXkociksYSxzO2ZvcihhPTA7YTxyOysrYSlzPXUyKHRbYV0pLG5bYV09cy5yfHwwLGlbYV09cy5nfHwwLG9bYV09cy5ifHwwO3JldHVybiBuPWUobiksaT1lKGkpLG89ZShvKSxzLm9wYWNpdHk9MSxmdW5jdGlvbihsKXtyZXR1cm4gcy5yPW4obCkscy5nPWkobCkscy5iPW8obCkscysiIn19fXZhciBneSxSVGUsTlRlLGFLPU0oKCk9PntSUigpO2lLKCk7RjV0KCk7SDV0KCk7Z3k9ZnVuY3Rpb24gZSh0KXt2YXIgcj1CNXQodCk7ZnVuY3Rpb24gbihpLG8pe3ZhciBhPXIoKGk9dTIoaSkpLnIsKG89dTIobykpLnIpLHM9cihpLmcsby5nKSxsPXIoaS5iLG8uYiksYz1OUihpLm9wYWNpdHksby5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24odSl7cmV0dXJuIGkucj1hKHUpLGkuZz1zKHUpLGkuYj1sKHUpLGkub3BhY2l0eT1jKHUpLGkrIiJ9fXJldHVybiBuLmdhbW1hPWUsbn0oMSk7UlRlPVY1dChPNXQpLE5UZT1WNXQoejV0KX0pO2Z1bmN0aW9uIFU1dChlLHQpe3R8fCh0PVtdKTt2YXIgcj1lP01hdGgubWluKHQubGVuZ3RoLGUubGVuZ3RoKTowLG49dC5zbGljZSgpLGk7cmV0dXJuIGZ1bmN0aW9uKG8pe2ZvcihpPTA7aTxyOysraSluW2ldPWVbaV0qKDEtbykrdFtpXSpvO3JldHVybiBufX1mdW5jdGlvbiBxNXQoZSl7cmV0dXJuIEFycmF5QnVmZmVyLmlzVmlldyhlKSYmIShlIGluc3RhbmNlb2YgRGF0YVZpZXcpfXZhciBHNXQ9TSgoKT0+e30pO2Z1bmN0aW9uIFc1dChlLHQpe3ZhciByPXQ/dC5sZW5ndGg6MCxuPWU/TWF0aC5taW4ocixlLmxlbmd0aCk6MCxpPW5ldyBBcnJheShuKSxvPW5ldyBBcnJheShyKSxhO2ZvcihhPTA7YTxuOysrYSlpW2FdPV95KGVbYV0sdFthXSk7Zm9yKDthPHI7KythKW9bYV09dFthXTtyZXR1cm4gZnVuY3Rpb24ocyl7Zm9yKGE9MDthPG47KythKW9bYV09aVthXShzKTtyZXR1cm4gb319dmFyIFk1dD1NKCgpPT57RFIoKX0pO2Z1bmN0aW9uIGo1dChlLHQpe3ZhciByPW5ldyBEYXRlO3JldHVybiBlPStlLHQ9K3QsZnVuY3Rpb24obil7cmV0dXJuIHIuc2V0VGltZShlKigxLW4pK3Qqbikscn19dmFyIFg1dD1NKCgpPT57fSk7ZnVuY3Rpb24gVWEoZSx0KXtyZXR1cm4gZT0rZSx0PSt0LGZ1bmN0aW9uKHIpe3JldHVybiBlKigxLXIpK3Qqcn19dmFyIEo1PU0oKCk9Pnt9KTtmdW5jdGlvbiAkNXQoZSx0KXt2YXIgcj17fSxuPXt9LGk7KGU9PT1udWxsfHx0eXBlb2YgZSE9Im9iamVjdCIpJiYoZT17fSksKHQ9PT1udWxsfHx0eXBlb2YgdCE9Im9iamVjdCIpJiYodD17fSk7Zm9yKGkgaW4gdClpIGluIGU/cltpXT1feShlW2ldLHRbaV0pOm5baV09dFtpXTtyZXR1cm4gZnVuY3Rpb24obyl7Zm9yKGkgaW4gciluW2ldPXJbaV0obyk7cmV0dXJuIG59fXZhciBLNXQ9TSgoKT0+e0RSKCl9KTtmdW5jdGlvbiBEVGUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fWZ1bmN0aW9uIE9UZShlKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuIGUodCkrIiJ9fWZ1bmN0aW9uIFE1KGUsdCl7dmFyIHI9bEsubGFzdEluZGV4PXNLLmxhc3RJbmRleD0wLG4saSxvLGE9LTEscz1bXSxsPVtdO2ZvcihlPWUrIiIsdD10KyIiOyhuPWxLLmV4ZWMoZSkpJiYoaT1zSy5leGVjKHQpKTspKG89aS5pbmRleCk+ciYmKG89dC5zbGljZShyLG8pLHNbYV0/c1thXSs9bzpzWysrYV09byksKG49blswXSk9PT0oaT1pWzBdKT9zW2FdP3NbYV0rPWk6c1srK2FdPWk6KHNbKythXT1udWxsLGwucHVzaCh7aTphLHg6VWEobixpKX0pKSxyPXNLLmxhc3RJbmRleDtyZXR1cm4gcjx0Lmxlbmd0aCYmKG89dC5zbGljZShyKSxzW2FdP3NbYV0rPW86c1srK2FdPW8pLHMubGVuZ3RoPDI/bFswXT9PVGUobFswXS54KTpEVGUodCk6KHQ9bC5sZW5ndGgsZnVuY3Rpb24oYyl7Zm9yKHZhciB1PTAsaDt1PHQ7Kyt1KXNbKGg9bFt1XSkuaV09aC54KGMpO3JldHVybiBzLmpvaW4oIiIpfSl9dmFyIGxLLHNLLGNLPU0oKCk9PntKNSgpO2xLPS9bLStdPyg/OlxkK1wuP1xkKnxcLj9cZCspKD86W2VFXVstK10/XGQrKT8vZyxzSz1uZXcgUmVnRXhwKGxLLnNvdXJjZSwiZyIpfSk7ZnVuY3Rpb24gX3koZSx0KXt2YXIgcj10eXBlb2YgdCxuO3JldHVybiB0PT1udWxsfHxyPT09ImJvb2xlYW4iP1o1KHQpOihyPT09Im51bWJlciI/VWE6cj09PSJzdHJpbmciPyhuPVN1KHQpKT8odD1uLGd5KTpRNTp0IGluc3RhbmNlb2YgU3U/Z3k6dCBpbnN0YW5jZW9mIERhdGU/ajV0OnE1dCh0KT9VNXQ6QXJyYXkuaXNBcnJheSh0KT9XNXQ6dHlwZW9mIHQudmFsdWVPZiE9ImZ1bmN0aW9uIiYmdHlwZW9mIHQudG9TdHJpbmchPSJmdW5jdGlvbiJ8fGlzTmFOKHQpPyQ1dDpVYSkoZSx0KX12YXIgRFI9TSgoKT0+e1JSKCk7YUsoKTtZNXQoKTtYNXQoKTtKNSgpO0s1dCgpO2NLKCk7b0soKTtHNXQoKX0pO2Z1bmN0aW9uIHVLKGUsdCxyLG4saSxvKXt2YXIgYSxzLGw7cmV0dXJuKGE9TWF0aC5zcXJ0KGUqZSt0KnQpKSYmKGUvPWEsdC89YSksKGw9ZSpyK3QqbikmJihyLT1lKmwsbi09dCpsKSwocz1NYXRoLnNxcnQocipyK24qbikpJiYoci89cyxuLz1zLGwvPXMpLGUqbjx0KnImJihlPS1lLHQ9LXQsbD0tbCxhPS1hKSx7dHJhbnNsYXRlWDppLHRyYW5zbGF0ZVk6byxyb3RhdGU6TWF0aC5hdGFuMih0LGUpKlo1dCxza2V3WDpNYXRoLmF0YW4obCkqWjV0LHNjYWxlWDphLHNjYWxlWTpzfX12YXIgWjV0LE9SLEo1dD1NKCgpPT57WjV0PTE4MC9NYXRoLlBJLE9SPXt0cmFuc2xhdGVYOjAsdHJhbnNsYXRlWTowLHJvdGF0ZTowLHNrZXdYOjAsc2NhbGVYOjEsc2NhbGVZOjF9fSk7ZnVuY3Rpb24gdFR0KGUpe3JldHVybiBlPT09Im5vbmUiP09SOih0VHx8KHRUPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkRJViIpLGhLPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudCxRNXQ9ZG9jdW1lbnQuZGVmYXVsdFZpZXcpLHRULnN0eWxlLnRyYW5zZm9ybT1lLGU9UTV0LmdldENvbXB1dGVkU3R5bGUoaEsuYXBwZW5kQ2hpbGQodFQpLG51bGwpLmdldFByb3BlcnR5VmFsdWUoInRyYW5zZm9ybSIpLGhLLnJlbW92ZUNoaWxkKHRUKSxlPWUuc2xpY2UoNywtMSkuc3BsaXQoIiwiKSx1SygrZVswXSwrZVsxXSwrZVsyXSwrZVszXSwrZVs0XSwrZVs1XSkpfWZ1bmN0aW9uIGVUdChlKXtyZXR1cm4gZT09bnVsbD9PUjooelJ8fCh6Uj1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwiZyIpKSx6Ui5zZXRBdHRyaWJ1dGUoInRyYW5zZm9ybSIsZSksKGU9elIudHJhbnNmb3JtLmJhc2VWYWwuY29uc29saWRhdGUoKSk/KGU9ZS5tYXRyaXgsdUsoZS5hLGUuYixlLmMsZS5kLGUuZSxlLmYpKTpPUil9dmFyIHRULGhLLFE1dCx6UixyVHQ9TSgoKT0+e0o1dCgpfSk7ZnVuY3Rpb24gblR0KGUsdCxyLG4pe2Z1bmN0aW9uIGkoYyl7cmV0dXJuIGMubGVuZ3RoP2MucG9wKCkrIiAiOiIifWZ1bmN0aW9uIG8oYyx1LGgsZixwLGQpe2lmKGMhPT1ofHx1IT09Zil7dmFyIGc9cC5wdXNoKCJ0cmFuc2xhdGUoIixudWxsLHQsbnVsbCxyKTtkLnB1c2goe2k6Zy00LHg6VWEoYyxoKX0se2k6Zy0yLHg6VWEodSxmKX0pfWVsc2UoaHx8ZikmJnAucHVzaCgidHJhbnNsYXRlKCIraCt0K2Yrcil9ZnVuY3Rpb24gYShjLHUsaCxmKXtjIT09dT8oYy11PjE4MD91Kz0zNjA6dS1jPjE4MCYmKGMrPTM2MCksZi5wdXNoKHtpOmgucHVzaChpKGgpKyJyb3RhdGUoIixudWxsLG4pLTIseDpVYShjLHUpfSkpOnUmJmgucHVzaChpKGgpKyJyb3RhdGUoIit1K24pfWZ1bmN0aW9uIHMoYyx1LGgsZil7YyE9PXU/Zi5wdXNoKHtpOmgucHVzaChpKGgpKyJza2V3WCgiLG51bGwsbiktMix4OlVhKGMsdSl9KTp1JiZoLnB1c2goaShoKSsic2tld1goIit1K24pfWZ1bmN0aW9uIGwoYyx1LGgsZixwLGQpe2lmKGMhPT1ofHx1IT09Zil7dmFyIGc9cC5wdXNoKGkocCkrInNjYWxlKCIsbnVsbCwiLCIsbnVsbCwiKSIpO2QucHVzaCh7aTpnLTQseDpVYShjLGgpfSx7aTpnLTIseDpVYSh1LGYpfSl9ZWxzZShoIT09MXx8ZiE9PTEpJiZwLnB1c2goaShwKSsic2NhbGUoIitoKyIsIitmKyIpIil9cmV0dXJuIGZ1bmN0aW9uKGMsdSl7dmFyIGg9W10sZj1bXTtyZXR1cm4gYz1lKGMpLHU9ZSh1KSxvKGMudHJhbnNsYXRlWCxjLnRyYW5zbGF0ZVksdS50cmFuc2xhdGVYLHUudHJhbnNsYXRlWSxoLGYpLGEoYy5yb3RhdGUsdS5yb3RhdGUsaCxmKSxzKGMuc2tld1gsdS5za2V3WCxoLGYpLGwoYy5zY2FsZVgsYy5zY2FsZVksdS5zY2FsZVgsdS5zY2FsZVksaCxmKSxjPXU9bnVsbCxmdW5jdGlvbihwKXtmb3IodmFyIGQ9LTEsZz1mLmxlbmd0aCxfOysrZDxnOyloWyhfPWZbZF0pLmldPV8ueChwKTtyZXR1cm4gaC5qb2luKCIiKX19fXZhciBmSyxwSyxpVHQ9TSgoKT0+e0o1KCk7clR0KCk7Zks9blR0KHRUdCwicHgsICIsInB4KSIsImRlZykiKSxwSz1uVHQoZVR0LCIsICIsIikiLCIpIil9KTt2YXIgZVQ9TSgoKT0+e0RSKCk7SjUoKTtjSygpO2lUdCgpO2FLKCl9KTtmdW5jdGlvbiBmMigpe3JldHVybiB5eXx8KHNUdCh6VGUpLHl5PW9ULm5vdygpK0hSKX1mdW5jdGlvbiB6VGUoKXt5eT0wfWZ1bmN0aW9uIGFUKCl7dGhpcy5fY2FsbD10aGlzLl90aW1lPXRoaXMuX25leHQ9bnVsbH1mdW5jdGlvbiBWUihlLHQscil7dmFyIG49bmV3IGFUO3JldHVybiBuLnJlc3RhcnQoZSx0LHIpLG59ZnVuY3Rpb24gbFR0KCl7ZjIoKSwrK2gyO2Zvcih2YXIgZT1GUix0O2U7KSh0PXl5LWUuX3RpbWUpPj0wJiZlLl9jYWxsLmNhbGwobnVsbCx0KSxlPWUuX25leHQ7LS1oMn1mdW5jdGlvbiBvVHQoKXt5eT0oQlI9b1Qubm93KCkpK0hSLGgyPW5UPTA7dHJ5e2xUdCgpfWZpbmFsbHl7aDI9MCxCVGUoKSx5eT0wfX1mdW5jdGlvbiBGVGUoKXt2YXIgZT1vVC5ub3coKSx0PWUtQlI7dD5hVHQmJihIUi09dCxCUj1lKX1mdW5jdGlvbiBCVGUoKXtmb3IodmFyIGUsdD1GUixyLG49MS8wO3Q7KXQuX2NhbGw/KG4+dC5fdGltZSYmKG49dC5fdGltZSksZT10LHQ9dC5fbmV4dCk6KHI9dC5fbmV4dCx0Ll9uZXh0PW51bGwsdD1lP2UuX25leHQ9cjpGUj1yKTtpVD1lLGRLKG4pfWZ1bmN0aW9uIGRLKGUpe2lmKCFoMil7blQmJihuVD1jbGVhclRpbWVvdXQoblQpKTt2YXIgdD1lLXl5O3Q+MjQ/KGU8MS8wJiYoblQ9c2V0VGltZW91dChvVHQsZS1vVC5ub3coKS1IUikpLHJUJiYoclQ9Y2xlYXJJbnRlcnZhbChyVCkpKTooclR8fChCUj1vVC5ub3coKSxyVD1zZXRJbnRlcnZhbChGVGUsYVR0KSksaDI9MSxzVHQob1R0KSl9fXZhciBoMixuVCxyVCxhVHQsRlIsaVQsQlIseXksSFIsb1Qsc1R0LG1LPU0oKCk9PntoMj0wLG5UPTAsclQ9MCxhVHQ9MWUzLEJSPTAseXk9MCxIUj0wLG9UPXR5cGVvZiBwZXJmb3JtYW5jZT09Im9iamVjdCImJnBlcmZvcm1hbmNlLm5vdz9wZXJmb3JtYW5jZTpEYXRlLHNUdD10eXBlb2Ygd2luZG93PT0ib2JqZWN0IiYmd2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZT93aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lLmJpbmQod2luZG93KTpmdW5jdGlvbihlKXtzZXRUaW1lb3V0KGUsMTcpfTthVC5wcm90b3R5cGU9VlIucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjphVCxyZXN0YXJ0OmZ1bmN0aW9uKGUsdCxyKXtpZih0eXBlb2YgZSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgVHlwZUVycm9yKCJjYWxsYmFjayBpcyBub3QgYSBmdW5jdGlvbiIpO3I9KHI9PW51bGw/ZjIoKTorcikrKHQ9PW51bGw/MDordCksIXRoaXMuX25leHQmJmlUIT09dGhpcyYmKGlUP2lULl9uZXh0PXRoaXM6RlI9dGhpcyxpVD10aGlzKSx0aGlzLl9jYWxsPWUsdGhpcy5fdGltZT1yLGRLKCl9LHN0b3A6ZnVuY3Rpb24oKXt0aGlzLl9jYWxsJiYodGhpcy5fY2FsbD1udWxsLHRoaXMuX3RpbWU9MS8wLGRLKCkpfX19KTtmdW5jdGlvbiBVUihlLHQscil7dmFyIG49bmV3IGFUO3JldHVybiB0PXQ9PW51bGw/MDordCxuLnJlc3RhcnQoZnVuY3Rpb24oaSl7bi5zdG9wKCksZShpK3QpfSx0LHIpLG59dmFyIGNUdD1NKCgpPT57bUsoKX0pO3ZhciBnSz1NKCgpPT57bUsoKTtjVHQoKX0pO2Z1bmN0aW9uIHNnKGUsdCxyLG4saSxvKXt2YXIgYT1lLl9fdHJhbnNpdGlvbjtpZighYSllLl9fdHJhbnNpdGlvbj17fTtlbHNlIGlmKHIgaW4gYSlyZXR1cm47VVRlKGUscix7bmFtZTp0LGluZGV4Om4sZ3JvdXA6aSxvbjpIVGUsdHdlZW46VlRlLHRpbWU6by50aW1lLGRlbGF5Om8uZGVsYXksZHVyYXRpb246by5kdXJhdGlvbixlYXNlOm8uZWFzZSx0aW1lcjpudWxsLHN0YXRlOmhUdH0pfWZ1bmN0aW9uIGxUKGUsdCl7dmFyIHI9UWkoZSx0KTtpZihyLnN0YXRlPmhUdCl0aHJvdyBuZXcgRXJyb3IoInRvbyBsYXRlOyBhbHJlYWR5IHNjaGVkdWxlZCIpO3JldHVybiByfWZ1bmN0aW9uIHFhKGUsdCl7dmFyIHI9UWkoZSx0KTtpZihyLnN0YXRlPnFSKXRocm93IG5ldyBFcnJvcigidG9vIGxhdGU7IGFscmVhZHkgcnVubmluZyIpO3JldHVybiByfWZ1bmN0aW9uIFFpKGUsdCl7dmFyIHI9ZS5fX3RyYW5zaXRpb247aWYoIXJ8fCEocj1yW3RdKSl0aHJvdyBuZXcgRXJyb3IoInRyYW5zaXRpb24gbm90IGZvdW5kIik7cmV0dXJuIHJ9ZnVuY3Rpb24gVVRlKGUsdCxyKXt2YXIgbj1lLl9fdHJhbnNpdGlvbixpO25bdF09cixyLnRpbWVyPVZSKG8sMCxyLnRpbWUpO2Z1bmN0aW9uIG8oYyl7ci5zdGF0ZT1fSyxyLnRpbWVyLnJlc3RhcnQoYSxyLmRlbGF5LHIudGltZSksci5kZWxheTw9YyYmYShjLXIuZGVsYXkpfWZ1bmN0aW9uIGEoYyl7dmFyIHUsaCxmLHA7aWYoci5zdGF0ZSE9PV9LKXJldHVybiBsKCk7Zm9yKHUgaW4gbilpZihwPW5bdV0scC5uYW1lPT09ci5uYW1lKXtpZihwLnN0YXRlPT09cVIpcmV0dXJuIFVSKGEpO3Auc3RhdGU9PT11VHQ/KHAuc3RhdGU9c1QscC50aW1lci5zdG9wKCkscC5vbi5jYWxsKCJpbnRlcnJ1cHQiLGUsZS5fX2RhdGFfXyxwLmluZGV4LHAuZ3JvdXApLGRlbGV0ZSBuW3VdKTordTx0JiYocC5zdGF0ZT1zVCxwLnRpbWVyLnN0b3AoKSxwLm9uLmNhbGwoImNhbmNlbCIsZSxlLl9fZGF0YV9fLHAuaW5kZXgscC5ncm91cCksZGVsZXRlIG5bdV0pfWlmKFVSKGZ1bmN0aW9uKCl7ci5zdGF0ZT09PXFSJiYoci5zdGF0ZT11VHQsci50aW1lci5yZXN0YXJ0KHMsci5kZWxheSxyLnRpbWUpLHMoYykpfSksci5zdGF0ZT1HUixyLm9uLmNhbGwoInN0YXJ0IixlLGUuX19kYXRhX18sci5pbmRleCxyLmdyb3VwKSxyLnN0YXRlPT09R1Ipe2ZvcihyLnN0YXRlPXFSLGk9bmV3IEFycmF5KGY9ci50d2Vlbi5sZW5ndGgpLHU9MCxoPS0xO3U8ZjsrK3UpKHA9ci50d2Vlblt1XS52YWx1ZS5jYWxsKGUsZS5fX2RhdGFfXyxyLmluZGV4LHIuZ3JvdXApKSYmKGlbKytoXT1wKTtpLmxlbmd0aD1oKzF9fWZ1bmN0aW9uIHMoYyl7Zm9yKHZhciB1PWM8ci5kdXJhdGlvbj9yLmVhc2UuY2FsbChudWxsLGMvci5kdXJhdGlvbik6KHIudGltZXIucmVzdGFydChsKSxyLnN0YXRlPVdSLDEpLGg9LTEsZj1pLmxlbmd0aDsrK2g8ZjspaVtoXS5jYWxsKGUsdSk7ci5zdGF0ZT09PVdSJiYoci5vbi5jYWxsKCJlbmQiLGUsZS5fX2RhdGFfXyxyLmluZGV4LHIuZ3JvdXApLGwoKSl9ZnVuY3Rpb24gbCgpe3Iuc3RhdGU9c1Qsci50aW1lci5zdG9wKCksZGVsZXRlIG5bdF07Zm9yKHZhciBjIGluIG4pcmV0dXJuO2RlbGV0ZSBlLl9fdHJhbnNpdGlvbn19dmFyIEhUZSxWVGUsaFR0LF9LLEdSLHFSLHVUdCxXUixzVCxUcz1NKCgpPT57QiQoKTtnSygpO0hUZT1HNSgic3RhcnQiLCJlbmQiLCJjYW5jZWwiLCJpbnRlcnJ1cHQiKSxWVGU9W10saFR0PTAsX0s9MSxHUj0yLHFSPTMsdVR0PTQsV1I9NSxzVD02fSk7ZnVuY3Rpb24gcDIoZSx0KXt2YXIgcj1lLl9fdHJhbnNpdGlvbixuLGksbz0hMCxhO2lmKCEhcil7dD10PT1udWxsP251bGw6dCsiIjtmb3IoYSBpbiByKXtpZigobj1yW2FdKS5uYW1lIT09dCl7bz0hMTtjb250aW51ZX1pPW4uc3RhdGU+R1ImJm4uc3RhdGU8V1Isbi5zdGF0ZT1zVCxuLnRpbWVyLnN0b3AoKSxuLm9uLmNhbGwoaT8iaW50ZXJydXB0IjoiY2FuY2VsIixlLGUuX19kYXRhX18sbi5pbmRleCxuLmdyb3VwKSxkZWxldGUgclthXX1vJiZkZWxldGUgZS5fX3RyYW5zaXRpb259fXZhciB5Sz1NKCgpPT57VHMoKX0pO2Z1bmN0aW9uIGZUdChlKXtyZXR1cm4gdGhpcy5lYWNoKGZ1bmN0aW9uKCl7cDIodGhpcyxlKX0pfXZhciBwVHQ9TSgoKT0+e3lLKCl9KTtmdW5jdGlvbiBxVGUoZSx0KXt2YXIgcixuO3JldHVybiBmdW5jdGlvbigpe3ZhciBpPXFhKHRoaXMsZSksbz1pLnR3ZWVuO2lmKG8hPT1yKXtuPXI9bztmb3IodmFyIGE9MCxzPW4ubGVuZ3RoO2E8czsrK2EpaWYoblthXS5uYW1lPT09dCl7bj1uLnNsaWNlKCksbi5zcGxpY2UoYSwxKTticmVha319aS50d2Vlbj1ufX1mdW5jdGlvbiBHVGUoZSx0LHIpe3ZhciBuLGk7aWYodHlwZW9mIHIhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3JldHVybiBmdW5jdGlvbigpe3ZhciBvPXFhKHRoaXMsZSksYT1vLnR3ZWVuO2lmKGEhPT1uKXtpPShuPWEpLnNsaWNlKCk7Zm9yKHZhciBzPXtuYW1lOnQsdmFsdWU6cn0sbD0wLGM9aS5sZW5ndGg7bDxjOysrbClpZihpW2xdLm5hbWU9PT10KXtpW2xdPXM7YnJlYWt9bD09PWMmJmkucHVzaChzKX1vLnR3ZWVuPWl9fWZ1bmN0aW9uIGRUdChlLHQpe3ZhciByPXRoaXMuX2lkO2lmKGUrPSIiLGFyZ3VtZW50cy5sZW5ndGg8Mil7Zm9yKHZhciBuPVFpKHRoaXMubm9kZSgpLHIpLnR3ZWVuLGk9MCxvPW4ubGVuZ3RoLGE7aTxvOysraSlpZigoYT1uW2ldKS5uYW1lPT09ZSlyZXR1cm4gYS52YWx1ZTtyZXR1cm4gbnVsbH1yZXR1cm4gdGhpcy5lYWNoKCh0PT1udWxsP3FUZTpHVGUpKHIsZSx0KSl9ZnVuY3Rpb24gZDIoZSx0LHIpe3ZhciBuPWUuX2lkO3JldHVybiBlLmVhY2goZnVuY3Rpb24oKXt2YXIgaT1xYSh0aGlzLG4pOyhpLnZhbHVlfHwoaS52YWx1ZT17fSkpW3RdPXIuYXBwbHkodGhpcyxhcmd1bWVudHMpfSksZnVuY3Rpb24oaSl7cmV0dXJuIFFpKGksbikudmFsdWVbdF19fXZhciBjVD1NKCgpPT57VHMoKX0pO2Z1bmN0aW9uIFlSKGUsdCl7dmFyIHI7cmV0dXJuKHR5cGVvZiB0PT0ibnVtYmVyIj9VYTp0IGluc3RhbmNlb2YgU3U/Z3k6KHI9U3UodCkpPyh0PXIsZ3kpOlE1KShlLHQpfXZhciB2Sz1NKCgpPT57UlIoKTtlVCgpfSk7ZnVuY3Rpb24gV1RlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlKGUpfX1mdW5jdGlvbiBZVGUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpfX1mdW5jdGlvbiBqVGUoZSx0LHIpe3ZhciBuLGk9cisiIixvO3JldHVybiBmdW5jdGlvbigpe3ZhciBhPXRoaXMuZ2V0QXR0cmlidXRlKGUpO3JldHVybiBhPT09aT9udWxsOmE9PT1uP286bz10KG49YSxyKX19ZnVuY3Rpb24gWFRlKGUsdCxyKXt2YXIgbixpPXIrIiIsbztyZXR1cm4gZnVuY3Rpb24oKXt2YXIgYT10aGlzLmdldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCk7cmV0dXJuIGE9PT1pP251bGw6YT09PW4/bzpvPXQobj1hLHIpfX1mdW5jdGlvbiAkVGUoZSx0LHIpe3ZhciBuLGksbztyZXR1cm4gZnVuY3Rpb24oKXt2YXIgYSxzPXIodGhpcyksbDtyZXR1cm4gcz09bnVsbD92b2lkIHRoaXMucmVtb3ZlQXR0cmlidXRlKGUpOihhPXRoaXMuZ2V0QXR0cmlidXRlKGUpLGw9cysiIixhPT09bD9udWxsOmE9PT1uJiZsPT09aT9vOihpPWwsbz10KG49YSxzKSkpfX1mdW5jdGlvbiBLVGUoZSx0LHIpe3ZhciBuLGksbztyZXR1cm4gZnVuY3Rpb24oKXt2YXIgYSxzPXIodGhpcyksbDtyZXR1cm4gcz09bnVsbD92b2lkIHRoaXMucmVtb3ZlQXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKTooYT10aGlzLmdldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCksbD1zKyIiLGE9PT1sP251bGw6YT09PW4mJmw9PT1pP286KGk9bCxvPXQobj1hLHMpKSl9fWZ1bmN0aW9uIG1UdChlLHQpe3ZhciByPVZwKGUpLG49cj09PSJ0cmFuc2Zvcm0iP3BLOllSO3JldHVybiB0aGlzLmF0dHJUd2VlbihlLHR5cGVvZiB0PT0iZnVuY3Rpb24iPyhyLmxvY2FsP0tUZTokVGUpKHIsbixkMih0aGlzLCJhdHRyLiIrZSx0KSk6dD09bnVsbD8oci5sb2NhbD9ZVGU6V1RlKShyKTooci5sb2NhbD9YVGU6alRlKShyLG4sdCkpfXZhciBnVHQ9TSgoKT0+e2VUKCk7RXMoKTtjVCgpO3ZLKCl9KTtmdW5jdGlvbiBaVGUoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocil7dGhpcy5zZXRBdHRyaWJ1dGUoZSx0LmNhbGwodGhpcyxyKSl9fWZ1bmN0aW9uIEpUZShlLHQpe3JldHVybiBmdW5jdGlvbihyKXt0aGlzLnNldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCx0LmNhbGwodGhpcyxyKSl9fWZ1bmN0aW9uIFFUZShlLHQpe3ZhciByLG47ZnVuY3Rpb24gaSgpe3ZhciBvPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiBvIT09biYmKHI9KG49bykmJkpUZShlLG8pKSxyfXJldHVybiBpLl92YWx1ZT10LGl9ZnVuY3Rpb24gdENlKGUsdCl7dmFyIHIsbjtmdW5jdGlvbiBpKCl7dmFyIG89dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cmV0dXJuIG8hPT1uJiYocj0obj1vKSYmWlRlKGUsbykpLHJ9cmV0dXJuIGkuX3ZhbHVlPXQsaX1mdW5jdGlvbiBfVHQoZSx0KXt2YXIgcj0iYXR0ci4iK2U7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXJldHVybihyPXRoaXMudHdlZW4ocikpJiZyLl92YWx1ZTtpZih0PT1udWxsKXJldHVybiB0aGlzLnR3ZWVuKHIsbnVsbCk7aWYodHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3ZhciBuPVZwKGUpO3JldHVybiB0aGlzLnR3ZWVuKHIsKG4ubG9jYWw/UVRlOnRDZSkobix0KSl9dmFyIHlUdD1NKCgpPT57RXMoKX0pO2Z1bmN0aW9uIGVDZShlLHQpe3JldHVybiBmdW5jdGlvbigpe2xUKHRoaXMsZSkuZGVsYXk9K3QuYXBwbHkodGhpcyxhcmd1bWVudHMpfX1mdW5jdGlvbiByQ2UoZSx0KXtyZXR1cm4gdD0rdCxmdW5jdGlvbigpe2xUKHRoaXMsZSkuZGVsYXk9dH19ZnVuY3Rpb24gdlR0KGUpe3ZhciB0PXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaCgodHlwZW9mIGU9PSJmdW5jdGlvbiI/ZUNlOnJDZSkodCxlKSk6UWkodGhpcy5ub2RlKCksdCkuZGVsYXl9dmFyIHhUdD1NKCgpPT57VHMoKX0pO2Z1bmN0aW9uIG5DZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3FhKHRoaXMsZSkuZHVyYXRpb249K3QuYXBwbHkodGhpcyxhcmd1bWVudHMpfX1mdW5jdGlvbiBpQ2UoZSx0KXtyZXR1cm4gdD0rdCxmdW5jdGlvbigpe3FhKHRoaXMsZSkuZHVyYXRpb249dH19ZnVuY3Rpb24gYlR0KGUpe3ZhciB0PXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaCgodHlwZW9mIGU9PSJmdW5jdGlvbiI/bkNlOmlDZSkodCxlKSk6UWkodGhpcy5ub2RlKCksdCkuZHVyYXRpb259dmFyIHdUdD1NKCgpPT57VHMoKX0pO2Z1bmN0aW9uIG9DZShlLHQpe2lmKHR5cGVvZiB0IT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gZnVuY3Rpb24oKXtxYSh0aGlzLGUpLmVhc2U9dH19ZnVuY3Rpb24gU1R0KGUpe3ZhciB0PXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaChvQ2UodCxlKSk6UWkodGhpcy5ub2RlKCksdCkuZWFzZX12YXIgTVR0PU0oKCk9PntUcygpfSk7ZnVuY3Rpb24gRVR0KGUpe3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1ZNShlKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPXQubGVuZ3RoLG49bmV3IEFycmF5KHIpLGk9MDtpPHI7KytpKWZvcih2YXIgbz10W2ldLGE9by5sZW5ndGgscz1uW2ldPVtdLGwsYz0wO2M8YTsrK2MpKGw9b1tjXSkmJmUuY2FsbChsLGwuX19kYXRhX18sYyxvKSYmcy5wdXNoKGwpO3JldHVybiBuZXcgdWEobix0aGlzLl9wYXJlbnRzLHRoaXMuX25hbWUsdGhpcy5faWQpfXZhciBUVHQ9TSgoKT0+e0VzKCk7R3AoKX0pO2Z1bmN0aW9uIENUdChlKXtpZihlLl9pZCE9PXRoaXMuX2lkKXRocm93IG5ldyBFcnJvcjtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9ZS5fZ3JvdXBzLG49dC5sZW5ndGgsaT1yLmxlbmd0aCxvPU1hdGgubWluKG4saSksYT1uZXcgQXJyYXkobikscz0wO3M8bzsrK3MpZm9yKHZhciBsPXRbc10sYz1yW3NdLHU9bC5sZW5ndGgsaD1hW3NdPW5ldyBBcnJheSh1KSxmLHA9MDtwPHU7KytwKShmPWxbcF18fGNbcF0pJiYoaFtwXT1mKTtmb3IoO3M8bjsrK3MpYVtzXT10W3NdO3JldHVybiBuZXcgdWEoYSx0aGlzLl9wYXJlbnRzLHRoaXMuX25hbWUsdGhpcy5faWQpfXZhciBBVHQ9TSgoKT0+e0dwKCl9KTtmdW5jdGlvbiBhQ2UoZSl7cmV0dXJuKGUrIiIpLnRyaW0oKS5zcGxpdCgvXnxccysvKS5ldmVyeShmdW5jdGlvbih0KXt2YXIgcj10LmluZGV4T2YoIi4iKTtyZXR1cm4gcj49MCYmKHQ9dC5zbGljZSgwLHIpKSwhdHx8dD09PSJzdGFydCJ9KX1mdW5jdGlvbiBzQ2UoZSx0LHIpe3ZhciBuLGksbz1hQ2UodCk/bFQ6cWE7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGE9byh0aGlzLGUpLHM9YS5vbjtzIT09biYmKGk9KG49cykuY29weSgpKS5vbih0LHIpLGEub249aX19ZnVuY3Rpb24gUFR0KGUsdCl7dmFyIHI9dGhpcy5faWQ7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg8Mj9RaSh0aGlzLm5vZGUoKSxyKS5vbi5vbihlKTp0aGlzLmVhY2goc0NlKHIsZSx0KSl9dmFyIElUdD1NKCgpPT57VHMoKX0pO2Z1bmN0aW9uIGxDZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD10aGlzLnBhcmVudE5vZGU7Zm9yKHZhciByIGluIHRoaXMuX190cmFuc2l0aW9uKWlmKCtyIT09ZSlyZXR1cm47dCYmdC5yZW1vdmVDaGlsZCh0aGlzKX19ZnVuY3Rpb24gTFR0KCl7cmV0dXJuIHRoaXMub24oImVuZC5yZW1vdmUiLGxDZSh0aGlzLl9pZCkpfXZhciBrVHQ9TSgoKT0+e30pO2Z1bmN0aW9uIFJUdChlKXt2YXIgdD10aGlzLl9uYW1lLHI9dGhpcy5faWQ7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPW15KGUpKTtmb3IodmFyIG49dGhpcy5fZ3JvdXBzLGk9bi5sZW5ndGgsbz1uZXcgQXJyYXkoaSksYT0wO2E8aTsrK2EpZm9yKHZhciBzPW5bYV0sbD1zLmxlbmd0aCxjPW9bYV09bmV3IEFycmF5KGwpLHUsaCxmPTA7ZjxsOysrZikodT1zW2ZdKSYmKGg9ZS5jYWxsKHUsdS5fX2RhdGFfXyxmLHMpKSYmKCJfX2RhdGFfXyJpbiB1JiYoaC5fX2RhdGFfXz11Ll9fZGF0YV9fKSxjW2ZdPWgsc2coY1tmXSx0LHIsZixjLFFpKHUscikpKTtyZXR1cm4gbmV3IHVhKG8sdGhpcy5fcGFyZW50cyx0LHIpfXZhciBOVHQ9TSgoKT0+e0VzKCk7R3AoKTtUcygpfSk7ZnVuY3Rpb24gRFR0KGUpe3ZhciB0PXRoaXMuX25hbWUscj10aGlzLl9pZDt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9VzUoZSkpO2Zvcih2YXIgbj10aGlzLl9ncm91cHMsaT1uLmxlbmd0aCxvPVtdLGE9W10scz0wO3M8aTsrK3MpZm9yKHZhciBsPW5bc10sYz1sLmxlbmd0aCx1LGg9MDtoPGM7KytoKWlmKHU9bFtoXSl7Zm9yKHZhciBmPWUuY2FsbCh1LHUuX19kYXRhX18saCxsKSxwLGQ9UWkodSxyKSxnPTAsXz1mLmxlbmd0aDtnPF87KytnKShwPWZbZ10pJiZzZyhwLHQscixnLGYsZCk7by5wdXNoKGYpLGEucHVzaCh1KX1yZXR1cm4gbmV3IHVhKG8sYSx0LHIpfXZhciBPVHQ9TSgoKT0+e0VzKCk7R3AoKTtUcygpfSk7ZnVuY3Rpb24gelR0KCl7cmV0dXJuIG5ldyBjQ2UodGhpcy5fZ3JvdXBzLHRoaXMuX3BhcmVudHMpfXZhciBjQ2UsRlR0PU0oKCk9PntFcygpO2NDZT1VcC5wcm90b3R5cGUuY29uc3RydWN0b3J9KTtmdW5jdGlvbiB1Q2UoZSx0KXt2YXIgcixuLGk7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIG89YWcodGhpcyxlKSxhPSh0aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KGUpLGFnKHRoaXMsZSkpO3JldHVybiBvPT09YT9udWxsOm89PT1yJiZhPT09bj9pOmk9dChyPW8sbj1hKX19ZnVuY3Rpb24gQlR0KGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkoZSl9fWZ1bmN0aW9uIGhDZShlLHQscil7dmFyIG4saT1yKyIiLG87cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGE9YWcodGhpcyxlKTtyZXR1cm4gYT09PWk/bnVsbDphPT09bj9vOm89dChuPWEscil9fWZ1bmN0aW9uIGZDZShlLHQscil7dmFyIG4saSxvO3JldHVybiBmdW5jdGlvbigpe3ZhciBhPWFnKHRoaXMsZSkscz1yKHRoaXMpLGw9cysiIjtyZXR1cm4gcz09bnVsbCYmKGw9cz0odGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShlKSxhZyh0aGlzLGUpKSksYT09PWw/bnVsbDphPT09biYmbD09PWk/bzooaT1sLG89dChuPWEscykpfX1mdW5jdGlvbiBwQ2UoZSx0KXt2YXIgcixuLGksbz0ic3R5bGUuIit0LGE9ImVuZC4iK28scztyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbD1xYSh0aGlzLGUpLGM9bC5vbix1PWwudmFsdWVbb109PW51bGw/c3x8KHM9QlR0KHQpKTp2b2lkIDA7KGMhPT1yfHxpIT09dSkmJihuPShyPWMpLmNvcHkoKSkub24oYSxpPXUpLGwub249bn19ZnVuY3Rpb24gSFR0KGUsdCxyKXt2YXIgbj0oZSs9IiIpPT0idHJhbnNmb3JtIj9mSzpZUjtyZXR1cm4gdD09bnVsbD90aGlzLnN0eWxlVHdlZW4oZSx1Q2UoZSxuKSkub24oImVuZC5zdHlsZS4iK2UsQlR0KGUpKTp0eXBlb2YgdD09ImZ1bmN0aW9uIj90aGlzLnN0eWxlVHdlZW4oZSxmQ2UoZSxuLGQyKHRoaXMsInN0eWxlLiIrZSx0KSkpLmVhY2gocENlKHRoaXMuX2lkLGUpKTp0aGlzLnN0eWxlVHdlZW4oZSxoQ2UoZSxuLHQpLHIpLm9uKCJlbmQuc3R5bGUuIitlLG51bGwpfXZhciBWVHQ9TSgoKT0+e2VUKCk7RXMoKTtUcygpO2NUKCk7dksoKX0pO2Z1bmN0aW9uIGRDZShlLHQscil7cmV0dXJuIGZ1bmN0aW9uKG4pe3RoaXMuc3R5bGUuc2V0UHJvcGVydHkoZSx0LmNhbGwodGhpcyxuKSxyKX19ZnVuY3Rpb24gbUNlKGUsdCxyKXt2YXIgbixpO2Z1bmN0aW9uIG8oKXt2YXIgYT10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyZXR1cm4gYSE9PWkmJihuPShpPWEpJiZkQ2UoZSxhLHIpKSxufXJldHVybiBvLl92YWx1ZT10LG99ZnVuY3Rpb24gVVR0KGUsdCxyKXt2YXIgbj0ic3R5bGUuIisoZSs9IiIpO2lmKGFyZ3VtZW50cy5sZW5ndGg8MilyZXR1cm4obj10aGlzLnR3ZWVuKG4pKSYmbi5fdmFsdWU7aWYodD09bnVsbClyZXR1cm4gdGhpcy50d2VlbihuLG51bGwpO2lmKHR5cGVvZiB0IT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gdGhpcy50d2VlbihuLG1DZShlLHQscj09bnVsbD8iIjpyKSl9dmFyIHFUdD1NKCgpPT57fSk7ZnVuY3Rpb24gZ0NlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMudGV4dENvbnRlbnQ9ZX19ZnVuY3Rpb24gX0NlKGUpe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PWUodGhpcyk7dGhpcy50ZXh0Q29udGVudD10PT1udWxsPyIiOnR9fWZ1bmN0aW9uIEdUdChlKXtyZXR1cm4gdGhpcy50d2VlbigidGV4dCIsdHlwZW9mIGU9PSJmdW5jdGlvbiI/X0NlKGQyKHRoaXMsInRleHQiLGUpKTpnQ2UoZT09bnVsbD8iIjplKyIiKSl9dmFyIFdUdD1NKCgpPT57Y1QoKX0pO2Z1bmN0aW9uIHlDZShlKXtyZXR1cm4gZnVuY3Rpb24odCl7dGhpcy50ZXh0Q29udGVudD1lLmNhbGwodGhpcyx0KX19ZnVuY3Rpb24gdkNlKGUpe3ZhciB0LHI7ZnVuY3Rpb24gbigpe3ZhciBpPWUuYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiBpIT09ciYmKHQ9KHI9aSkmJnlDZShpKSksdH1yZXR1cm4gbi5fdmFsdWU9ZSxufWZ1bmN0aW9uIFlUdChlKXt2YXIgdD0idGV4dCI7aWYoYXJndW1lbnRzLmxlbmd0aDwxKXJldHVybih0PXRoaXMudHdlZW4odCkpJiZ0Ll92YWx1ZTtpZihlPT1udWxsKXJldHVybiB0aGlzLnR3ZWVuKHQsbnVsbCk7aWYodHlwZW9mIGUhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3JldHVybiB0aGlzLnR3ZWVuKHQsdkNlKGUpKX12YXIgalR0PU0oKCk9Pnt9KTtmdW5jdGlvbiBYVHQoKXtmb3IodmFyIGU9dGhpcy5fbmFtZSx0PXRoaXMuX2lkLHI9alIoKSxuPXRoaXMuX2dyb3VwcyxpPW4ubGVuZ3RoLG89MDtvPGk7KytvKWZvcih2YXIgYT1uW29dLHM9YS5sZW5ndGgsbCxjPTA7YzxzOysrYylpZihsPWFbY10pe3ZhciB1PVFpKGwsdCk7c2cobCxlLHIsYyxhLHt0aW1lOnUudGltZSt1LmRlbGF5K3UuZHVyYXRpb24sZGVsYXk6MCxkdXJhdGlvbjp1LmR1cmF0aW9uLGVhc2U6dS5lYXNlfSl9cmV0dXJuIG5ldyB1YShuLHRoaXMuX3BhcmVudHMsZSxyKX12YXIgJFR0PU0oKCk9PntHcCgpO1RzKCl9KTtmdW5jdGlvbiBLVHQoKXt2YXIgZSx0LHI9dGhpcyxuPXIuX2lkLGk9ci5zaXplKCk7cmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKG8sYSl7dmFyIHM9e3ZhbHVlOmF9LGw9e3ZhbHVlOmZ1bmN0aW9uKCl7LS1pPT09MCYmbygpfX07ci5lYWNoKGZ1bmN0aW9uKCl7dmFyIGM9cWEodGhpcyxuKSx1PWMub247dSE9PWUmJih0PShlPXUpLmNvcHkoKSx0Ll8uY2FuY2VsLnB1c2gocyksdC5fLmludGVycnVwdC5wdXNoKHMpLHQuXy5lbmQucHVzaChsKSksYy5vbj10fSl9KX12YXIgWlR0PU0oKCk9PntUcygpfSk7ZnVuY3Rpb24gdWEoZSx0LHIsbil7dGhpcy5fZ3JvdXBzPWUsdGhpcy5fcGFyZW50cz10LHRoaXMuX25hbWU9cix0aGlzLl9pZD1ufWZ1bmN0aW9uIHhLKGUpe3JldHVybiBVcCgpLnRyYW5zaXRpb24oZSl9ZnVuY3Rpb24galIoKXtyZXR1cm4rK3hDZX12YXIgeENlLG0yLEdwPU0oKCk9PntFcygpO2dUdCgpO3lUdCgpO3hUdCgpO3dUdCgpO01UdCgpO1RUdCgpO0FUdCgpO0lUdCgpO2tUdCgpO05UdCgpO09UdCgpO0ZUdCgpO1ZUdCgpO3FUdCgpO1dUdCgpO2pUdCgpOyRUdCgpO2NUKCk7WlR0KCk7eENlPTA7bTI9VXAucHJvdG90eXBlO3VhLnByb3RvdHlwZT14Sy5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOnVhLHNlbGVjdDpSVHQsc2VsZWN0QWxsOkRUdCxmaWx0ZXI6RVR0LG1lcmdlOkNUdCxzZWxlY3Rpb246elR0LHRyYW5zaXRpb246WFR0LGNhbGw6bTIuY2FsbCxub2RlczptMi5ub2Rlcyxub2RlOm0yLm5vZGUsc2l6ZTptMi5zaXplLGVtcHR5Om0yLmVtcHR5LGVhY2g6bTIuZWFjaCxvbjpQVHQsYXR0cjptVHQsYXR0clR3ZWVuOl9UdCxzdHlsZTpIVHQsc3R5bGVUd2VlbjpVVHQsdGV4dDpHVHQsdGV4dFR3ZWVuOllUdCxyZW1vdmU6TFR0LHR3ZWVuOmRUdCxkZWxheTp2VHQsZHVyYXRpb246YlR0LGVhc2U6U1R0LGVuZDpLVHR9fSk7ZnVuY3Rpb24gYkNlKGUsdCl7Zm9yKHZhciByOyEocj1lLl9fdHJhbnNpdGlvbil8fCEocj1yW3RdKTspaWYoIShlPWUucGFyZW50Tm9kZSkpcmV0dXJuIGJLLnRpbWU9ZjIoKSxiSztyZXR1cm4gcn1mdW5jdGlvbiBKVHQoZSl7dmFyIHQscjtlIGluc3RhbmNlb2YgdWE/KHQ9ZS5faWQsZT1lLl9uYW1lKToodD1qUigpLChyPWJLKS50aW1lPWYyKCksZT1lPT1udWxsP251bGw6ZSsiIik7Zm9yKHZhciBuPXRoaXMuX2dyb3VwcyxpPW4ubGVuZ3RoLG89MDtvPGk7KytvKWZvcih2YXIgYT1uW29dLHM9YS5sZW5ndGgsbCxjPTA7YzxzOysrYykobD1hW2NdKSYmc2cobCxlLHQsYyxhLHJ8fGJDZShsLHQpKTtyZXR1cm4gbmV3IHVhKG4sdGhpcy5fcGFyZW50cyxlLHQpfXZhciBiSyxRVHQ9TSgoKT0+e0dwKCk7VHMoKTtJXygpO2dLKCk7Yks9e3RpbWU6bnVsbCxkZWxheTowLGR1cmF0aW9uOjI1MCxlYXNlOnhzfX0pO3ZhciB0Q3Q9TSgoKT0+e0VzKCk7cFR0KCk7UVR0KCk7VXAucHJvdG90eXBlLmludGVycnVwdD1mVHQ7VXAucHJvdG90eXBlLnRyYW5zaXRpb249SlR0fSk7dmFyIGVDdD1NKCgpPT57R3AoKTtUcygpfSk7dmFyIHJDdD1NKCgpPT57dEN0KCk7R3AoKTtlQ3QoKTt5SygpfSk7ZnVuY3Rpb24gd0soZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fXZhciBuQ3Q9TSgoKT0+e30pO2Z1bmN0aW9uIGlDdChlLHQscil7dGhpcy50YXJnZXQ9ZSx0aGlzLnR5cGU9dCx0aGlzLnNlbGVjdGlvbj1yfXZhciBvQ3Q9TSgoKT0+e30pO2Z1bmN0aW9uIFNLKCl7d3Iuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCl9ZnVuY3Rpb24gdVQoKXt3ci5wcmV2ZW50RGVmYXVsdCgpLHdyLnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpfXZhciBhQ3Q9TSgoKT0+e0VzKCl9KTtmdW5jdGlvbiBoVChlKXtyZXR1cm57dHlwZTplfX1mdW5jdGlvbiBFQ2UoKXtyZXR1cm4hd3IuYnV0dG9ufWZ1bmN0aW9uIFRDZSgpe3ZhciBlPXRoaXMub3duZXJTVkdFbGVtZW50fHx0aGlzO3JldHVybltbMCwwXSxbZS53aWR0aC5iYXNlVmFsLnZhbHVlLGUuaGVpZ2h0LmJhc2VWYWwudmFsdWVdXX1mdW5jdGlvbiBFSyhlKXtmb3IoOyFlLl9fYnJ1c2g7KWlmKCEoZT1lLnBhcmVudE5vZGUpKXJldHVybjtyZXR1cm4gZS5fX2JydXNofWZ1bmN0aW9uIFRLKGUpe3JldHVybiBlWzBdWzBdPT09ZVsxXVswXXx8ZVswXVsxXT09PWVbMV1bMV19ZnVuY3Rpb24gdUN0KGUpe3ZhciB0PWUuX19icnVzaDtyZXR1cm4gdD90LmRpbS5vdXRwdXQodC5zZWxlY3Rpb24pOm51bGx9ZnVuY3Rpb24gaEN0KCl7cmV0dXJuIENLKFhSKX1mdW5jdGlvbiBmQ3QoKXtyZXR1cm4gQ0soJFIpfWZ1bmN0aW9uIHBDdCgpe3JldHVybiBDSyh3Q2UpfWZ1bmN0aW9uIENLKGUpe3ZhciB0PVRDZSxyPUVDZSxuPUc1KGEsInN0YXJ0IiwiYnJ1c2giLCJlbmQiKSxpPTYsbztmdW5jdGlvbiBhKGYpe3ZhciBwPWYucHJvcGVydHkoIl9fYnJ1c2giLGgpLnNlbGVjdEFsbCgiLm92ZXJsYXkiKS5kYXRhKFtoVCgib3ZlcmxheSIpXSk7cC5lbnRlcigpLmFwcGVuZCgicmVjdCIpLmF0dHIoImNsYXNzIiwib3ZlcmxheSIpLmF0dHIoInBvaW50ZXItZXZlbnRzIiwiYWxsIikuYXR0cigiY3Vyc29yIixXcC5vdmVybGF5KS5tZXJnZShwKS5lYWNoKGZ1bmN0aW9uKCl7dmFyIGc9RUsodGhpcykuZXh0ZW50O3FwKHRoaXMpLmF0dHIoIngiLGdbMF1bMF0pLmF0dHIoInkiLGdbMF1bMV0pLmF0dHIoIndpZHRoIixnWzFdWzBdLWdbMF1bMF0pLmF0dHIoImhlaWdodCIsZ1sxXVsxXS1nWzBdWzFdKX0pLGYuc2VsZWN0QWxsKCIuc2VsZWN0aW9uIikuZGF0YShbaFQoInNlbGVjdGlvbiIpXSkuZW50ZXIoKS5hcHBlbmQoInJlY3QiKS5hdHRyKCJjbGFzcyIsInNlbGVjdGlvbiIpLmF0dHIoImN1cnNvciIsV3Auc2VsZWN0aW9uKS5hdHRyKCJmaWxsIiwiIzc3NyIpLmF0dHIoImZpbGwtb3BhY2l0eSIsLjMpLmF0dHIoInN0cm9rZSIsIiNmZmYiKS5hdHRyKCJzaGFwZS1yZW5kZXJpbmciLCJjcmlzcEVkZ2VzIik7dmFyIGQ9Zi5zZWxlY3RBbGwoIi5oYW5kbGUiKS5kYXRhKGUuaGFuZGxlcyxmdW5jdGlvbihnKXtyZXR1cm4gZy50eXBlfSk7ZC5leGl0KCkucmVtb3ZlKCksZC5lbnRlcigpLmFwcGVuZCgicmVjdCIpLmF0dHIoImNsYXNzIixmdW5jdGlvbihnKXtyZXR1cm4iaGFuZGxlIGhhbmRsZS0tIitnLnR5cGV9KS5hdHRyKCJjdXJzb3IiLGZ1bmN0aW9uKGcpe3JldHVybiBXcFtnLnR5cGVdfSksZi5lYWNoKHMpLmF0dHIoImZpbGwiLCJub25lIikuYXR0cigicG9pbnRlci1ldmVudHMiLCJhbGwiKS5zdHlsZSgiLXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yIiwicmdiYSgwLDAsMCwwKSIpLm9uKCJtb3VzZWRvd24uYnJ1c2ggdG91Y2hzdGFydC5icnVzaCIsdSl9YS5tb3ZlPWZ1bmN0aW9uKGYscCl7Zi5zZWxlY3Rpb24/Zi5vbigic3RhcnQuYnJ1c2giLGZ1bmN0aW9uKCl7bCh0aGlzLGFyZ3VtZW50cykuYmVmb3Jlc3RhcnQoKS5zdGFydCgpfSkub24oImludGVycnVwdC5icnVzaCBlbmQuYnJ1c2giLGZ1bmN0aW9uKCl7bCh0aGlzLGFyZ3VtZW50cykuZW5kKCl9KS50d2VlbigiYnJ1c2giLGZ1bmN0aW9uKCl7dmFyIGQ9dGhpcyxnPWQuX19icnVzaCxfPWwoZCxhcmd1bWVudHMpLHk9Zy5zZWxlY3Rpb24seD1lLmlucHV0KHR5cGVvZiBwPT0iZnVuY3Rpb24iP3AuYXBwbHkodGhpcyxhcmd1bWVudHMpOnAsZy5leHRlbnQpLGI9X3koeSx4KTtmdW5jdGlvbiBTKEMpe2cuc2VsZWN0aW9uPUM9PT0xJiZUSyh4KT9udWxsOmIoQykscy5jYWxsKGQpLF8uYnJ1c2goKX1yZXR1cm4geSYmeD9TOlMoMSl9KTpmLmVhY2goZnVuY3Rpb24oKXt2YXIgZD10aGlzLGc9YXJndW1lbnRzLF89ZC5fX2JydXNoLHk9ZS5pbnB1dCh0eXBlb2YgcD09ImZ1bmN0aW9uIj9wLmFwcGx5KGQsZyk6cCxfLmV4dGVudCkseD1sKGQsZykuYmVmb3Jlc3RhcnQoKTtwMihkKSxfLnNlbGVjdGlvbj15PT1udWxsfHxUSyh5KT9udWxsOnkscy5jYWxsKGQpLHguc3RhcnQoKS5icnVzaCgpLmVuZCgpfSl9O2Z1bmN0aW9uIHMoKXt2YXIgZj1xcCh0aGlzKSxwPUVLKHRoaXMpLnNlbGVjdGlvbjtwPyhmLnNlbGVjdEFsbCgiLnNlbGVjdGlvbiIpLnN0eWxlKCJkaXNwbGF5IixudWxsKS5hdHRyKCJ4IixwWzBdWzBdKS5hdHRyKCJ5IixwWzBdWzFdKS5hdHRyKCJ3aWR0aCIscFsxXVswXS1wWzBdWzBdKS5hdHRyKCJoZWlnaHQiLHBbMV1bMV0tcFswXVsxXSksZi5zZWxlY3RBbGwoIi5oYW5kbGUiKS5zdHlsZSgiZGlzcGxheSIsbnVsbCkuYXR0cigieCIsZnVuY3Rpb24oZCl7cmV0dXJuIGQudHlwZVtkLnR5cGUubGVuZ3RoLTFdPT09ImUiP3BbMV1bMF0taS8yOnBbMF1bMF0taS8yfSkuYXR0cigieSIsZnVuY3Rpb24oZCl7cmV0dXJuIGQudHlwZVswXT09PSJzIj9wWzFdWzFdLWkvMjpwWzBdWzFdLWkvMn0pLmF0dHIoIndpZHRoIixmdW5jdGlvbihkKXtyZXR1cm4gZC50eXBlPT09Im4ifHxkLnR5cGU9PT0icyI/cFsxXVswXS1wWzBdWzBdK2k6aX0pLmF0dHIoImhlaWdodCIsZnVuY3Rpb24oZCl7cmV0dXJuIGQudHlwZT09PSJlInx8ZC50eXBlPT09InciP3BbMV1bMV0tcFswXVsxXStpOml9KSk6Zi5zZWxlY3RBbGwoIi5zZWxlY3Rpb24sLmhhbmRsZSIpLnN0eWxlKCJkaXNwbGF5Iiwibm9uZSIpLmF0dHIoIngiLG51bGwpLmF0dHIoInkiLG51bGwpLmF0dHIoIndpZHRoIixudWxsKS5hdHRyKCJoZWlnaHQiLG51bGwpfWZ1bmN0aW9uIGwoZixwKXtyZXR1cm4gZi5fX2JydXNoLmVtaXR0ZXJ8fG5ldyBjKGYscCl9ZnVuY3Rpb24gYyhmLHApe3RoaXMudGhhdD1mLHRoaXMuYXJncz1wLHRoaXMuc3RhdGU9Zi5fX2JydXNoLHRoaXMuYWN0aXZlPTB9Yy5wcm90b3R5cGU9e2JlZm9yZXN0YXJ0OmZ1bmN0aW9uKCl7cmV0dXJuKyt0aGlzLmFjdGl2ZT09PTEmJih0aGlzLnN0YXRlLmVtaXR0ZXI9dGhpcyx0aGlzLnN0YXJ0aW5nPSEwKSx0aGlzfSxzdGFydDpmdW5jdGlvbigpe3JldHVybiB0aGlzLnN0YXJ0aW5nJiYodGhpcy5zdGFydGluZz0hMSx0aGlzLmVtaXQoInN0YXJ0IikpLHRoaXN9LGJydXNoOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZW1pdCgiYnJ1c2giKSx0aGlzfSxlbmQ6ZnVuY3Rpb24oKXtyZXR1cm4tLXRoaXMuYWN0aXZlPT09MCYmKGRlbGV0ZSB0aGlzLnN0YXRlLmVtaXR0ZXIsdGhpcy5lbWl0KCJlbmQiKSksdGhpc30sZW1pdDpmdW5jdGlvbihmKXtLJChuZXcgaUN0KGEsZixlLm91dHB1dCh0aGlzLnN0YXRlLnNlbGVjdGlvbikpLG4uYXBwbHksbixbZix0aGlzLnRoYXQsdGhpcy5hcmdzXSl9fTtmdW5jdGlvbiB1KCl7aWYod3IudG91Y2hlcyl7aWYod3IuY2hhbmdlZFRvdWNoZXMubGVuZ3RoPHdyLnRvdWNoZXMubGVuZ3RoKXJldHVybiB1VCgpfWVsc2UgaWYobylyZXR1cm47aWYoIXIuYXBwbHkodGhpcyxhcmd1bWVudHMpKXJldHVybjt2YXIgZj10aGlzLHA9d3IudGFyZ2V0Ll9fZGF0YV9fLnR5cGUsZD0od3IubWV0YUtleT9wPSJvdmVybGF5IjpwKT09PSJzZWxlY3Rpb24iP3NDdDp3ci5hbHRLZXk/XzI6ZzIsZz1lPT09JFI/bnVsbDpTQ2VbcF0sXz1lPT09WFI/bnVsbDpNQ2VbcF0seT1FSyhmKSx4PXkuZXh0ZW50LGI9eS5zZWxlY3Rpb24sUz14WzBdWzBdLEMsUCxrPXhbMF1bMV0sTyxELEI9eFsxXVswXSxJLEwsUj14WzFdWzFdLEYseixVLFcsWixydD1nJiZfJiZ3ci5zaGlmdEtleSxvdCxzdCxTdD1BUihmKSxidD1TdCxNdD1sKGYsYXJndW1lbnRzKS5iZWZvcmVzdGFydCgpO3A9PT0ib3ZlcmxheSI/eS5zZWxlY3Rpb249Yj1bW0M9ZT09PSRSP1M6U3RbMF0sTz1lPT09WFI/azpTdFsxXV0sW0k9ZT09PSRSP0I6QyxGPWU9PT1YUj9SOk9dXTooQz1iWzBdWzBdLE89YlswXVsxXSxJPWJbMV1bMF0sRj1iWzFdWzFdKSxQPUMsRD1PLEw9SSx6PUY7dmFyIGx0PXFwKGYpLmF0dHIoInBvaW50ZXItZXZlbnRzIiwibm9uZSIpLEt0PWx0LnNlbGVjdEFsbCgiLm92ZXJsYXkiKS5hdHRyKCJjdXJzb3IiLFdwW3BdKTtpZih3ci50b3VjaGVzKWx0Lm9uKCJ0b3VjaG1vdmUuYnJ1c2giLGN0LCEwKS5vbigidG91Y2hlbmQuYnJ1c2ggdG91Y2hjYW5jZWwuYnJ1c2giLGV0LCEwKTtlbHNle3ZhciBfdD1xcCh3ci52aWV3KS5vbigia2V5ZG93bi5icnVzaCIsZHQsITApLm9uKCJrZXl1cC5icnVzaCIscSwhMCkub24oIm1vdXNlbW92ZS5icnVzaCIsY3QsITApLm9uKCJtb3VzZXVwLmJydXNoIixldCwhMCk7SiQod3Iudmlldyl9U0soKSxwMihmKSxzLmNhbGwoZiksTXQuc3RhcnQoKTtmdW5jdGlvbiBjdCgpe3ZhciBwdD1BUihmKTtydCYmIW90JiYhc3QmJihNYXRoLmFicyhwdFswXS1idFswXSk+TWF0aC5hYnMocHRbMV0tYnRbMV0pP3N0PSEwOm90PSEwKSxidD1wdCxaPSEwLHVUKCksWCgpfWZ1bmN0aW9uIFgoKXt2YXIgcHQ7c3dpdGNoKFU9YnRbMF0tU3RbMF0sVz1idFsxXS1TdFsxXSxkKXtjYXNlIE1LOmNhc2Ugc0N0OntnJiYoVT1NYXRoLm1heChTLUMsTWF0aC5taW4oQi1JLFUpKSxQPUMrVSxMPUkrVSksXyYmKFc9TWF0aC5tYXgoay1PLE1hdGgubWluKFItRixXKSksRD1PK1csej1GK1cpO2JyZWFrfWNhc2UgZzI6e2c8MD8oVT1NYXRoLm1heChTLUMsTWF0aC5taW4oQi1DLFUpKSxQPUMrVSxMPUkpOmc+MCYmKFU9TWF0aC5tYXgoUy1JLE1hdGgubWluKEItSSxVKSksUD1DLEw9SStVKSxfPDA/KFc9TWF0aC5tYXgoay1PLE1hdGgubWluKFItTyxXKSksRD1PK1csej1GKTpfPjAmJihXPU1hdGgubWF4KGstRixNYXRoLm1pbihSLUYsVykpLEQ9Tyx6PUYrVyk7YnJlYWt9Y2FzZSBfMjp7ZyYmKFA9TWF0aC5tYXgoUyxNYXRoLm1pbihCLEMtVSpnKSksTD1NYXRoLm1heChTLE1hdGgubWluKEIsSStVKmcpKSksXyYmKEQ9TWF0aC5tYXgoayxNYXRoLm1pbihSLE8tVypfKSksej1NYXRoLm1heChrLE1hdGgubWluKFIsRitXKl8pKSk7YnJlYWt9fUw8UCYmKGcqPS0xLHB0PUMsQz1JLEk9cHQscHQ9UCxQPUwsTD1wdCxwIGluIGxDdCYmS3QuYXR0cigiY3Vyc29yIixXcFtwPWxDdFtwXV0pKSx6PEQmJihfKj0tMSxwdD1PLE89RixGPXB0LHB0PUQsRD16LHo9cHQscCBpbiBjQ3QmJkt0LmF0dHIoImN1cnNvciIsV3BbcD1jQ3RbcF1dKSkseS5zZWxlY3Rpb24mJihiPXkuc2VsZWN0aW9uKSxvdCYmKFA9YlswXVswXSxMPWJbMV1bMF0pLHN0JiYoRD1iWzBdWzFdLHo9YlsxXVsxXSksKGJbMF1bMF0hPT1QfHxiWzBdWzFdIT09RHx8YlsxXVswXSE9PUx8fGJbMV1bMV0hPT16KSYmKHkuc2VsZWN0aW9uPVtbUCxEXSxbTCx6XV0scy5jYWxsKGYpLE10LmJydXNoKCkpfWZ1bmN0aW9uIGV0KCl7aWYoU0soKSx3ci50b3VjaGVzKXtpZih3ci50b3VjaGVzLmxlbmd0aClyZXR1cm47byYmY2xlYXJUaW1lb3V0KG8pLG89c2V0VGltZW91dChmdW5jdGlvbigpe289bnVsbH0sNTAwKSxsdC5vbigidG91Y2htb3ZlLmJydXNoIHRvdWNoZW5kLmJydXNoIHRvdWNoY2FuY2VsLmJydXNoIixudWxsKX1lbHNlIFEkKHdyLnZpZXcsWiksX3Qub24oImtleWRvd24uYnJ1c2gga2V5dXAuYnJ1c2ggbW91c2Vtb3ZlLmJydXNoIG1vdXNldXAuYnJ1c2giLG51bGwpO2x0LmF0dHIoInBvaW50ZXItZXZlbnRzIiwiYWxsIiksS3QuYXR0cigiY3Vyc29yIixXcC5vdmVybGF5KSx5LnNlbGVjdGlvbiYmKGI9eS5zZWxlY3Rpb24pLFRLKGIpJiYoeS5zZWxlY3Rpb249bnVsbCxzLmNhbGwoZikpLE10LmVuZCgpfWZ1bmN0aW9uIGR0KCl7c3dpdGNoKHdyLmtleUNvZGUpe2Nhc2UgMTY6e3J0PWcmJl87YnJlYWt9Y2FzZSAxODp7ZD09PWcyJiYoZyYmKEk9TC1VKmcsQz1QK1UqZyksXyYmKEY9ei1XKl8sTz1EK1cqXyksZD1fMixYKCkpO2JyZWFrfWNhc2UgMzI6eyhkPT09ZzJ8fGQ9PT1fMikmJihnPDA/ST1MLVU6Zz4wJiYoQz1QLVUpLF88MD9GPXotVzpfPjAmJihPPUQtVyksZD1NSyxLdC5hdHRyKCJjdXJzb3IiLFdwLnNlbGVjdGlvbiksWCgpKTticmVha31kZWZhdWx0OnJldHVybn11VCgpfWZ1bmN0aW9uIHEoKXtzd2l0Y2god3Iua2V5Q29kZSl7Y2FzZSAxNjp7cnQmJihvdD1zdD1ydD0hMSxYKCkpO2JyZWFrfWNhc2UgMTg6e2Q9PT1fMiYmKGc8MD9JPUw6Zz4wJiYoQz1QKSxfPDA/Rj16Ol8+MCYmKE89RCksZD1nMixYKCkpO2JyZWFrfWNhc2UgMzI6e2Q9PT1NSyYmKHdyLmFsdEtleT8oZyYmKEk9TC1VKmcsQz1QK1UqZyksXyYmKEY9ei1XKl8sTz1EK1cqXyksZD1fMik6KGc8MD9JPUw6Zz4wJiYoQz1QKSxfPDA/Rj16Ol8+MCYmKE89RCksZD1nMiksS3QuYXR0cigiY3Vyc29yIixXcFtwXSksWCgpKTticmVha31kZWZhdWx0OnJldHVybn11VCgpfX1mdW5jdGlvbiBoKCl7dmFyIGY9dGhpcy5fX2JydXNofHx7c2VsZWN0aW9uOm51bGx9O3JldHVybiBmLmV4dGVudD10LmFwcGx5KHRoaXMsYXJndW1lbnRzKSxmLmRpbT1lLGZ9cmV0dXJuIGEuZXh0ZW50PWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBmPT0iZnVuY3Rpb24iP2Y6d0soW1srZlswXVswXSwrZlswXVsxXV0sWytmWzFdWzBdLCtmWzFdWzFdXV0pLGEpOnR9LGEuZmlsdGVyPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPXR5cGVvZiBmPT0iZnVuY3Rpb24iP2Y6d0soISFmKSxhKTpyfSxhLmhhbmRsZVNpemU9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9K2YsYSk6aX0sYS5vbj1mdW5jdGlvbigpe3ZhciBmPW4ub24uYXBwbHkobixhcmd1bWVudHMpO3JldHVybiBmPT09bj9hOmZ9LGF9dmFyIHNDdCxNSyxnMixfMixYUiwkUix3Q2UsV3AsbEN0LGNDdCxTQ2UsTUNlLGRDdD1NKCgpPT57QiQoKTtNNXQoKTtlVCgpO0VzKCk7ckN0KCk7bkN0KCk7b0N0KCk7YUN0KCk7c0N0PXtuYW1lOiJkcmFnIn0sTUs9e25hbWU6InNwYWNlIn0sZzI9e25hbWU6ImhhbmRsZSJ9LF8yPXtuYW1lOiJjZW50ZXIifSxYUj17bmFtZToieCIsaGFuZGxlczpbImUiLCJ3Il0ubWFwKGhUKSxpbnB1dDpmdW5jdGlvbihlLHQpe3JldHVybiBlJiZbW2VbMF0sdFswXVsxXV0sW2VbMV0sdFsxXVsxXV1dfSxvdXRwdXQ6ZnVuY3Rpb24oZSl7cmV0dXJuIGUmJltlWzBdWzBdLGVbMV1bMF1dfX0sJFI9e25hbWU6InkiLGhhbmRsZXM6WyJuIiwicyJdLm1hcChoVCksaW5wdXQ6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gZSYmW1t0WzBdWzBdLGVbMF1dLFt0WzFdWzBdLGVbMV1dXX0sb3V0cHV0OmZ1bmN0aW9uKGUpe3JldHVybiBlJiZbZVswXVsxXSxlWzFdWzFdXX19LHdDZT17bmFtZToieHkiLGhhbmRsZXM6WyJuIiwiZSIsInMiLCJ3IiwibnciLCJuZSIsInNlIiwic3ciXS5tYXAoaFQpLGlucHV0OmZ1bmN0aW9uKGUpe3JldHVybiBlfSxvdXRwdXQ6ZnVuY3Rpb24oZSl7cmV0dXJuIGV9fSxXcD17b3ZlcmxheToiY3Jvc3NoYWlyIixzZWxlY3Rpb246Im1vdmUiLG46Im5zLXJlc2l6ZSIsZToiZXctcmVzaXplIixzOiJucy1yZXNpemUiLHc6ImV3LXJlc2l6ZSIsbnc6Im53c2UtcmVzaXplIixuZToibmVzdy1yZXNpemUiLHNlOiJud3NlLXJlc2l6ZSIsc3c6Im5lc3ctcmVzaXplIn0sbEN0PXtlOiJ3Iix3OiJlIixudzoibmUiLG5lOiJudyIsc2U6InN3Iixzdzoic2UifSxjQ3Q9e246InMiLHM6Im4iLG53OiJzdyIsbmU6InNlIixzZToibmUiLHN3OiJudyJ9LFNDZT17b3ZlcmxheToxLHNlbGVjdGlvbjoxLG46bnVsbCxlOjEsczpudWxsLHc6LTEsbnc6LTEsbmU6MSxzZToxLHN3Oi0xfSxNQ2U9e292ZXJsYXk6MSxzZWxlY3Rpb246MSxuOi0xLGU6bnVsbCxzOjEsdzpudWxsLG53Oi0xLG5lOi0xLHNlOjEsc3c6MX19KTt2YXIgbUN0PU0oKCk9PntkQ3QoKX0pO2Z1bmN0aW9uIGxnKGUsdCl7cmV0dXJuIGU8dD8tMTplPnQ/MTplPj10PzA6TmFOfXZhciB2eT1NKCgpPT57fSk7ZnVuY3Rpb24gQUsoZSl7cmV0dXJuIGUubGVuZ3RoPT09MSYmKGU9Q0NlKGUpKSx7bGVmdDpmdW5jdGlvbih0LHIsbixpKXtmb3Iobj09bnVsbCYmKG49MCksaT09bnVsbCYmKGk9dC5sZW5ndGgpO248aTspe3ZhciBvPW4raT4+PjE7ZSh0W29dLHIpPDA/bj1vKzE6aT1vfXJldHVybiBufSxyaWdodDpmdW5jdGlvbih0LHIsbixpKXtmb3Iobj09bnVsbCYmKG49MCksaT09bnVsbCYmKGk9dC5sZW5ndGgpO248aTspe3ZhciBvPW4raT4+PjE7ZSh0W29dLHIpPjA/aT1vOm49bysxfXJldHVybiBufX19ZnVuY3Rpb24gQ0NlKGUpe3JldHVybiBmdW5jdGlvbih0LHIpe3JldHVybiBsZyhlKHQpLHIpfX12YXIgUEs9TSgoKT0+e3Z5KCl9KTt2YXIgZ0N0LEFDZSxQQ2UsSUs9TSgoKT0+e3Z5KCk7UEsoKTtnQ3Q9QUsobGcpLEFDZT1nQ3QucmlnaHQsUENlPWdDdC5sZWZ0fSk7dmFyIExLPU0oKCk9Pnt9KTt2YXIgX0N0PU0oKCk9PntMSygpfSk7dmFyIHlDdD1NKCgpPT57fSk7dmFyIHkyPU0oKCk9Pnt9KTt2YXIga0s9TSgoKT0+e3kyKCl9KTt2YXIgUks9TSgoKT0+e2tLKCl9KTt2YXIgTks9TSgoKT0+e30pO3ZhciB2Q3QsTENlLGtDZSxESz1NKCgpPT57dkN0PUFycmF5LnByb3RvdHlwZSxMQ2U9dkN0LnNsaWNlLGtDZT12Q3QubWFwfSk7dmFyIHhDdD1NKCgpPT57fSk7dmFyIGJDdD1NKCgpPT57fSk7ZnVuY3Rpb24gZlQoZSx0LHIpe2U9K2UsdD0rdCxyPShpPWFyZ3VtZW50cy5sZW5ndGgpPDI/KHQ9ZSxlPTAsMSk6aTwzPzE6K3I7Zm9yKHZhciBuPS0xLGk9TWF0aC5tYXgoMCxNYXRoLmNlaWwoKHQtZSkvcikpfDAsbz1uZXcgQXJyYXkoaSk7KytuPGk7KW9bbl09ZStuKnI7cmV0dXJuIG99dmFyIE9LPU0oKCk9Pnt9KTt2YXIgZmxuLHBsbixkbG4seks9TSgoKT0+e2Zsbj1NYXRoLnNxcnQoNTApLHBsbj1NYXRoLnNxcnQoMTApLGRsbj1NYXRoLnNxcnQoMil9KTt2YXIgRks9TSgoKT0+e30pO3ZhciB3Q3Q9TSgoKT0+e0RLKCk7SUsoKTt4Q3QoKTtOSygpO2JDdCgpO09LKCk7eksoKTtGSygpfSk7dmFyIFpSPU0oKCk9Pnt5MigpfSk7dmFyIE1DdD1NKCgpPT57REsoKTt2eSgpO3kyKCk7WlIoKX0pO3ZhciBFQ3Q9TSgoKT0+e1JLKCl9KTt2YXIgVEN0PU0oKCk9Pnt9KTt2YXIgQ0N0PU0oKCk9Pnt5MigpfSk7dmFyIEFDdD1NKCgpPT57dnkoKTt5MigpO1pSKCl9KTt2YXIgUEN0PU0oKCk9Pnt9KTt2YXIgQks9TSgoKT0+e30pO3ZhciBJQ3Q9TSgoKT0+e30pO3ZhciBMQ3Q9TSgoKT0+e3Z5KCl9KTt2YXIga0N0PU0oKCk9Pnt9KTt2YXIgUkN0PU0oKCk9Pnt9KTt2YXIgSEs9TSgoKT0+e0JLKCl9KTt2YXIgTkN0PU0oKCk9PntISygpfSk7dmFyIERDdD1NKCgpPT57SUsoKTt2eSgpO1BLKCk7X0N0KCk7eUN0KCk7UksoKTtOSygpO3dDdCgpO01DdCgpO0VDdCgpO0ZLKCk7VEN0KCk7Q0N0KCk7QUN0KCk7UEN0KCk7QksoKTtMSygpO0lDdCgpO1pSKCk7T0soKTtMQ3QoKTtrQ3QoKTtSQ3QoKTt6SygpO0hLKCk7a0soKTtOQ3QoKX0pO3ZhciBWSyxVSyxPQ3QscFQscUssR0ssV0s9TSgoKT0+e1ZLPU1hdGguY29zLFVLPU1hdGguc2luLE9DdD1NYXRoLlBJLHBUPU9DdC8yLHFLPU9DdCoyLEdLPU1hdGgubWF4fSk7ZnVuY3Rpb24gSENlKGUpe3JldHVybiBmdW5jdGlvbih0LHIpe3JldHVybiBlKHQuc291cmNlLnZhbHVlK3QudGFyZ2V0LnZhbHVlLHIuc291cmNlLnZhbHVlK3IudGFyZ2V0LnZhbHVlKX19ZnVuY3Rpb24gekN0KCl7dmFyIGU9MCx0PW51bGwscj1udWxsLG49bnVsbDtmdW5jdGlvbiBpKG8pe3ZhciBhPW8ubGVuZ3RoLHM9W10sbD1mVChhKSxjPVtdLHU9W10saD11Lmdyb3Vwcz1uZXcgQXJyYXkoYSksZj1uZXcgQXJyYXkoYSphKSxwLGQsZyxfLHkseDtmb3IocD0wLHk9LTE7Kyt5PGE7KXtmb3IoZD0wLHg9LTE7Kyt4PGE7KWQrPW9beV1beF07cy5wdXNoKGQpLGMucHVzaChmVChhKSkscCs9ZH1mb3IodCYmbC5zb3J0KGZ1bmN0aW9uKEIsSSl7cmV0dXJuIHQoc1tCXSxzW0ldKX0pLHImJmMuZm9yRWFjaChmdW5jdGlvbihCLEkpe0Iuc29ydChmdW5jdGlvbihMLFIpe3JldHVybiByKG9bSV1bTF0sb1tJXVtSXSl9KX0pLHA9R0soMCxxSy1lKmEpL3AsXz1wP2U6cUsvYSxkPTAseT0tMTsrK3k8YTspe2ZvcihnPWQseD0tMTsrK3g8YTspe3ZhciBiPWxbeV0sUz1jW2JdW3hdLEM9b1tiXVtTXSxQPWQsaz1kKz1DKnA7ZltTKmErYl09e2luZGV4OmIsc3ViaW5kZXg6UyxzdGFydEFuZ2xlOlAsZW5kQW5nbGU6ayx2YWx1ZTpDfX1oW2JdPXtpbmRleDpiLHN0YXJ0QW5nbGU6ZyxlbmRBbmdsZTpkLHZhbHVlOnNbYl19LGQrPV99Zm9yKHk9LTE7Kyt5PGE7KWZvcih4PXktMTsrK3g8YTspe3ZhciBPPWZbeCphK3ldLEQ9Zlt5KmEreF07KE8udmFsdWV8fEQudmFsdWUpJiZ1LnB1c2goTy52YWx1ZTxELnZhbHVlP3tzb3VyY2U6RCx0YXJnZXQ6T306e3NvdXJjZTpPLHRhcmdldDpEfSl9cmV0dXJuIG4/dS5zb3J0KG4pOnV9cmV0dXJuIGkucGFkQW5nbGU9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9R0soMCxvKSxpKTplfSxpLnNvcnRHcm91cHM9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9byxpKTp0fSxpLnNvcnRTdWJncm91cHM9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9byxpKTpyfSxpLnNvcnRDaG9yZHM9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG89PW51bGw/bj1udWxsOihuPUhDZShvKSkuXz1vLGkpOm4mJm4uX30saX12YXIgRkN0PU0oKCk9PntEQ3QoKTtXSygpfSk7dmFyIEJDdCxIQ3Q9TSgoKT0+e0JDdD1BcnJheS5wcm90b3R5cGUuc2xpY2V9KTtmdW5jdGlvbiBKUihlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIFZDdD1NKCgpPT57fSk7ZnVuY3Rpb24gWEsoKXt0aGlzLl94MD10aGlzLl95MD10aGlzLl94MT10aGlzLl95MT1udWxsLHRoaXMuXz0iIn1mdW5jdGlvbiBVQ3QoKXtyZXR1cm4gbmV3IFhLfXZhciBZSyxqSyx4eSxWQ2UsJEsscUN0PU0oKCk9PntZSz1NYXRoLlBJLGpLPTIqWUsseHk9MWUtNixWQ2U9aksteHk7WEsucHJvdG90eXBlPVVDdC5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOlhLLG1vdmVUbzpmdW5jdGlvbihlLHQpe3RoaXMuXys9Ik0iKyh0aGlzLl94MD10aGlzLl94MT0rZSkrIiwiKyh0aGlzLl95MD10aGlzLl95MT0rdCl9LGNsb3NlUGF0aDpmdW5jdGlvbigpe3RoaXMuX3gxIT09bnVsbCYmKHRoaXMuX3gxPXRoaXMuX3gwLHRoaXMuX3kxPXRoaXMuX3kwLHRoaXMuXys9IloiKX0sbGluZVRvOmZ1bmN0aW9uKGUsdCl7dGhpcy5fKz0iTCIrKHRoaXMuX3gxPStlKSsiLCIrKHRoaXMuX3kxPSt0KX0scXVhZHJhdGljQ3VydmVUbzpmdW5jdGlvbihlLHQscixuKXt0aGlzLl8rPSJRIisgK2UrIiwiKyArdCsiLCIrKHRoaXMuX3gxPStyKSsiLCIrKHRoaXMuX3kxPStuKX0sYmV6aWVyQ3VydmVUbzpmdW5jdGlvbihlLHQscixuLGksbyl7dGhpcy5fKz0iQyIrICtlKyIsIisgK3QrIiwiKyArcisiLCIrICtuKyIsIisodGhpcy5feDE9K2kpKyIsIisodGhpcy5feTE9K28pfSxhcmNUbzpmdW5jdGlvbihlLHQscixuLGkpe2U9K2UsdD0rdCxyPStyLG49K24saT0raTt2YXIgbz10aGlzLl94MSxhPXRoaXMuX3kxLHM9ci1lLGw9bi10LGM9by1lLHU9YS10LGg9YypjK3UqdTtpZihpPDApdGhyb3cgbmV3IEVycm9yKCJuZWdhdGl2ZSByYWRpdXM6ICIraSk7aWYodGhpcy5feDE9PT1udWxsKXRoaXMuXys9Ik0iKyh0aGlzLl94MT1lKSsiLCIrKHRoaXMuX3kxPXQpO2Vsc2UgaWYoaD54eSlpZighKE1hdGguYWJzKHUqcy1sKmMpPnh5KXx8IWkpdGhpcy5fKz0iTCIrKHRoaXMuX3gxPWUpKyIsIisodGhpcy5feTE9dCk7ZWxzZXt2YXIgZj1yLW8scD1uLWEsZD1zKnMrbCpsLGc9ZipmK3AqcCxfPU1hdGguc3FydChkKSx5PU1hdGguc3FydChoKSx4PWkqTWF0aC50YW4oKFlLLU1hdGguYWNvcygoZCtoLWcpLygyKl8qeSkpKS8yKSxiPXgveSxTPXgvXztNYXRoLmFicyhiLTEpPnh5JiYodGhpcy5fKz0iTCIrKGUrYipjKSsiLCIrKHQrYip1KSksdGhpcy5fKz0iQSIraSsiLCIraSsiLDAsMCwiKyArKHUqZj5jKnApKyIsIisodGhpcy5feDE9ZStTKnMpKyIsIisodGhpcy5feTE9dCtTKmwpfX0sYXJjOmZ1bmN0aW9uKGUsdCxyLG4saSxvKXtlPStlLHQ9K3Qscj0rcixvPSEhbzt2YXIgYT1yKk1hdGguY29zKG4pLHM9cipNYXRoLnNpbihuKSxsPWUrYSxjPXQrcyx1PTFebyxoPW8/bi1pOmktbjtpZihyPDApdGhyb3cgbmV3IEVycm9yKCJuZWdhdGl2ZSByYWRpdXM6ICIrcik7dGhpcy5feDE9PT1udWxsP3RoaXMuXys9Ik0iK2wrIiwiK2M6KE1hdGguYWJzKHRoaXMuX3gxLWwpPnh5fHxNYXRoLmFicyh0aGlzLl95MS1jKT54eSkmJih0aGlzLl8rPSJMIitsKyIsIitjKSxyJiYoaDwwJiYoaD1oJWpLK2pLKSxoPlZDZT90aGlzLl8rPSJBIityKyIsIityKyIsMCwxLCIrdSsiLCIrKGUtYSkrIiwiKyh0LXMpKyJBIityKyIsIityKyIsMCwxLCIrdSsiLCIrKHRoaXMuX3gxPWwpKyIsIisodGhpcy5feTE9Yyk6aD54eSYmKHRoaXMuXys9IkEiK3IrIiwiK3IrIiwwLCIrICsoaD49WUspKyIsIit1KyIsIisodGhpcy5feDE9ZStyKk1hdGguY29zKGkpKSsiLCIrKHRoaXMuX3kxPXQrcipNYXRoLnNpbihpKSkpKX0scmVjdDpmdW5jdGlvbihlLHQscixuKXt0aGlzLl8rPSJNIisodGhpcy5feDA9dGhpcy5feDE9K2UpKyIsIisodGhpcy5feTA9dGhpcy5feTE9K3QpKyJoIisgK3IrInYiKyArbisiaCIrLXIrIloifSx0b1N0cmluZzpmdW5jdGlvbigpe3JldHVybiB0aGlzLl99fTskSz1VQ3R9KTt2YXIgR0N0PU0oKCk9PntxQ3QoKX0pO2Z1bmN0aW9uIFVDZShlKXtyZXR1cm4gZS5zb3VyY2V9ZnVuY3Rpb24gcUNlKGUpe3JldHVybiBlLnRhcmdldH1mdW5jdGlvbiBHQ2UoZSl7cmV0dXJuIGUucmFkaXVzfWZ1bmN0aW9uIFdDZShlKXtyZXR1cm4gZS5zdGFydEFuZ2xlfWZ1bmN0aW9uIFlDZShlKXtyZXR1cm4gZS5lbmRBbmdsZX1mdW5jdGlvbiBXQ3QoKXt2YXIgZT1VQ2UsdD1xQ2Uscj1HQ2Usbj1XQ2UsaT1ZQ2Usbz1udWxsO2Z1bmN0aW9uIGEoKXt2YXIgcyxsPUJDdC5jYWxsKGFyZ3VtZW50cyksYz1lLmFwcGx5KHRoaXMsbCksdT10LmFwcGx5KHRoaXMsbCksaD0rci5hcHBseSh0aGlzLChsWzBdPWMsbCkpLGY9bi5hcHBseSh0aGlzLGwpLXBULHA9aS5hcHBseSh0aGlzLGwpLXBULGQ9aCpWSyhmKSxnPWgqVUsoZiksXz0rci5hcHBseSh0aGlzLChsWzBdPXUsbCkpLHk9bi5hcHBseSh0aGlzLGwpLXBULHg9aS5hcHBseSh0aGlzLGwpLXBUO2lmKG98fChvPXM9JEsoKSksby5tb3ZlVG8oZCxnKSxvLmFyYygwLDAsaCxmLHApLChmIT09eXx8cCE9PXgpJiYoby5xdWFkcmF0aWNDdXJ2ZVRvKDAsMCxfKlZLKHkpLF8qVUsoeSkpLG8uYXJjKDAsMCxfLHkseCkpLG8ucXVhZHJhdGljQ3VydmVUbygwLDAsZCxnKSxvLmNsb3NlUGF0aCgpLHMpcmV0dXJuIG89bnVsbCxzKyIifHxudWxsfXJldHVybiBhLnJhZGl1cz1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOkpSKCtzKSxhKTpyfSxhLnN0YXJ0QW5nbGU9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpKUigrcyksYSk6bn0sYS5lbmRBbmdsZT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOkpSKCtzKSxhKTppfSxhLnNvdXJjZT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT1zLGEpOmV9LGEudGFyZ2V0PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXMsYSk6dH0sYS5jb250ZXh0PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPXM9PW51bGw/bnVsbDpzLGEpOm99LGF9dmFyIFlDdD1NKCgpPT57SEN0KCk7VkN0KCk7V0soKTtHQ3QoKX0pO3ZhciBqQ3Q9TSgoKT0+e0ZDdCgpO1lDdCgpfSk7ZnVuY3Rpb24gUVIoKXt9ZnVuY3Rpb24gWEN0KGUsdCl7dmFyIHI9bmV3IFFSO2lmKGUgaW5zdGFuY2VvZiBRUillLmVhY2goZnVuY3Rpb24ocyxsKXtyLnNldChsLHMpfSk7ZWxzZSBpZihBcnJheS5pc0FycmF5KGUpKXt2YXIgbj0tMSxpPWUubGVuZ3RoLG87aWYodD09bnVsbClmb3IoOysrbjxpOylyLnNldChuLGVbbl0pO2Vsc2UgZm9yKDsrK248aTspci5zZXQodChvPWVbbl0sbixlKSxvKX1lbHNlIGlmKGUpZm9yKHZhciBhIGluIGUpci5zZXQoYSxlW2FdKTtyZXR1cm4gcn12YXIgbGwsYnksdE49TSgoKT0+e2xsPSIkIjtRUi5wcm90b3R5cGU9WEN0LnByb3RvdHlwZT17Y29uc3RydWN0b3I6UVIsaGFzOmZ1bmN0aW9uKGUpe3JldHVybiBsbCtlIGluIHRoaXN9LGdldDpmdW5jdGlvbihlKXtyZXR1cm4gdGhpc1tsbCtlXX0sc2V0OmZ1bmN0aW9uKGUsdCl7cmV0dXJuIHRoaXNbbGwrZV09dCx0aGlzfSxyZW1vdmU6ZnVuY3Rpb24oZSl7dmFyIHQ9bGwrZTtyZXR1cm4gdCBpbiB0aGlzJiZkZWxldGUgdGhpc1t0XX0sY2xlYXI6ZnVuY3Rpb24oKXtmb3IodmFyIGUgaW4gdGhpcyllWzBdPT09bGwmJmRlbGV0ZSB0aGlzW2VdfSxrZXlzOmZ1bmN0aW9uKCl7dmFyIGU9W107Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PWxsJiZlLnB1c2godC5zbGljZSgxKSk7cmV0dXJuIGV9LHZhbHVlczpmdW5jdGlvbigpe3ZhciBlPVtdO2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1sbCYmZS5wdXNoKHRoaXNbdF0pO3JldHVybiBlfSxlbnRyaWVzOmZ1bmN0aW9uKCl7dmFyIGU9W107Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PWxsJiZlLnB1c2goe2tleTp0LnNsaWNlKDEpLHZhbHVlOnRoaXNbdF19KTtyZXR1cm4gZX0sc2l6ZTpmdW5jdGlvbigpe3ZhciBlPTA7Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PWxsJiYrK2U7cmV0dXJuIGV9LGVtcHR5OmZ1bmN0aW9uKCl7Zm9yKHZhciBlIGluIHRoaXMpaWYoZVswXT09PWxsKXJldHVybiExO3JldHVybiEwfSxlYWNoOmZ1bmN0aW9uKGUpe2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1sbCYmZSh0aGlzW3RdLHQuc2xpY2UoMSksdGhpcyl9fTtieT1YQ3R9KTtmdW5jdGlvbiBaQ3QoKXt2YXIgZT1bXSx0PVtdLHIsbixpO2Z1bmN0aW9uIG8ocyxsLGMsdSl7aWYobD49ZS5sZW5ndGgpcmV0dXJuIHIhPW51bGwmJnMuc29ydChyKSxuIT1udWxsP24ocyk6cztmb3IodmFyIGg9LTEsZj1zLmxlbmd0aCxwPWVbbCsrXSxkLGcsXz1ieSgpLHkseD1jKCk7KytoPGY7KSh5PV8uZ2V0KGQ9cChnPXNbaF0pKyIiKSk/eS5wdXNoKGcpOl8uc2V0KGQsW2ddKTtyZXR1cm4gXy5lYWNoKGZ1bmN0aW9uKGIsUyl7dSh4LFMsbyhiLGwsYyx1KSl9KSx4fWZ1bmN0aW9uIGEocyxsKXtpZigrK2w+ZS5sZW5ndGgpcmV0dXJuIHM7dmFyIGMsdT10W2wtMV07cmV0dXJuIG4hPW51bGwmJmw+PWUubGVuZ3RoP2M9cy5lbnRyaWVzKCk6KGM9W10scy5lYWNoKGZ1bmN0aW9uKGgsZil7Yy5wdXNoKHtrZXk6Zix2YWx1ZXM6YShoLGwpfSl9KSksdSE9bnVsbD9jLnNvcnQoZnVuY3Rpb24oaCxmKXtyZXR1cm4gdShoLmtleSxmLmtleSl9KTpjfXJldHVybiBpPXtvYmplY3Q6ZnVuY3Rpb24ocyl7cmV0dXJuIG8ocywwLGpDZSxYQ2UpfSxtYXA6ZnVuY3Rpb24ocyl7cmV0dXJuIG8ocywwLCRDdCxLQ3QpfSxlbnRyaWVzOmZ1bmN0aW9uKHMpe3JldHVybiBhKG8ocywwLCRDdCxLQ3QpLDApfSxrZXk6ZnVuY3Rpb24ocyl7cmV0dXJuIGUucHVzaChzKSxpfSxzb3J0S2V5czpmdW5jdGlvbihzKXtyZXR1cm4gdFtlLmxlbmd0aC0xXT1zLGl9LHNvcnRWYWx1ZXM6ZnVuY3Rpb24ocyl7cmV0dXJuIHI9cyxpfSxyb2xsdXA6ZnVuY3Rpb24ocyl7cmV0dXJuIG49cyxpfX19ZnVuY3Rpb24gakNlKCl7cmV0dXJue319ZnVuY3Rpb24gWENlKGUsdCxyKXtlW3RdPXJ9ZnVuY3Rpb24gJEN0KCl7cmV0dXJuIGJ5KCl9ZnVuY3Rpb24gS0N0KGUsdCxyKXtlLnNldCh0LHIpfXZhciBKQ3Q9TSgoKT0+e3ROKCl9KTtmdW5jdGlvbiBlTigpe31mdW5jdGlvbiBRQ3QoZSx0KXt2YXIgcj1uZXcgZU47aWYoZSBpbnN0YW5jZW9mIGVOKWUuZWFjaChmdW5jdGlvbihvKXtyLmFkZChvKX0pO2Vsc2UgaWYoZSl7dmFyIG49LTEsaT1lLmxlbmd0aDtpZih0PT1udWxsKWZvcig7KytuPGk7KXIuYWRkKGVbbl0pO2Vsc2UgZm9yKDsrK248aTspci5hZGQodChlW25dLG4sZSkpfXJldHVybiByfXZhciB3eSx0QXQsZUF0PU0oKCk9Pnt0TigpO3d5PWJ5LnByb3RvdHlwZTtlTi5wcm90b3R5cGU9UUN0LnByb3RvdHlwZT17Y29uc3RydWN0b3I6ZU4saGFzOnd5LmhhcyxhZGQ6ZnVuY3Rpb24oZSl7cmV0dXJuIGUrPSIiLHRoaXNbbGwrZV09ZSx0aGlzfSxyZW1vdmU6d3kucmVtb3ZlLGNsZWFyOnd5LmNsZWFyLHZhbHVlczp3eS5rZXlzLHNpemU6d3kuc2l6ZSxlbXB0eTp3eS5lbXB0eSxlYWNoOnd5LmVhY2h9O3RBdD1RQ3R9KTtmdW5jdGlvbiByQXQoZSl7dmFyIHQ9W107Zm9yKHZhciByIGluIGUpdC5wdXNoKHIpO3JldHVybiB0fXZhciBuQXQ9TSgoKT0+e30pO2Z1bmN0aW9uIGlBdChlKXt2YXIgdD1bXTtmb3IodmFyIHIgaW4gZSl0LnB1c2goZVtyXSk7cmV0dXJuIHR9dmFyIG9BdD1NKCgpPT57fSk7ZnVuY3Rpb24gYUF0KGUpe3ZhciB0PVtdO2Zvcih2YXIgciBpbiBlKXQucHVzaCh7a2V5OnIsdmFsdWU6ZVtyXX0pO3JldHVybiB0fXZhciBzQXQ9TSgoKT0+e30pO3ZhciBsQXQ9TSgoKT0+e0pDdCgpO2VBdCgpO3ROKCk7bkF0KCk7b0F0KCk7c0F0KCl9KTtmdW5jdGlvbiBZcChlLHQscil7ZS5wcm90b3R5cGU9dC5wcm90b3R5cGU9cixyLmNvbnN0cnVjdG9yPWV9ZnVuY3Rpb24gY2coZSx0KXt2YXIgcj1PYmplY3QuY3JlYXRlKGUucHJvdG90eXBlKTtmb3IodmFyIG4gaW4gdClyW25dPXRbbl07cmV0dXJuIHJ9dmFyIHJOPU0oKCk9Pnt9KTtmdW5jdGlvbiB0Zigpe31mdW5jdGlvbiB4MihlKXt2YXIgdDtyZXR1cm4gZT0oZSsiIikudHJpbSgpLnRvTG93ZXJDYXNlKCksKHQ9JENlLmV4ZWMoZSkpPyh0PXBhcnNlSW50KHRbMV0sMTYpLG5ldyB0byh0Pj44JjE1fHQ+PjQmMjQwLHQ+PjQmMTV8dCYyNDAsKHQmMTUpPDw0fHQmMTUsMSkpOih0PUtDZS5leGVjKGUpKT91QXQocGFyc2VJbnQodFsxXSwxNikpOih0PVpDZS5leGVjKGUpKT9uZXcgdG8odFsxXSx0WzJdLHRbM10sMSk6KHQ9SkNlLmV4ZWMoZSkpP25ldyB0byh0WzFdKjI1NS8xMDAsdFsyXSoyNTUvMTAwLHRbM10qMjU1LzEwMCwxKToodD1RQ2UuZXhlYyhlKSk/aEF0KHRbMV0sdFsyXSx0WzNdLHRbNF0pOih0PXRBZS5leGVjKGUpKT9oQXQodFsxXSoyNTUvMTAwLHRbMl0qMjU1LzEwMCx0WzNdKjI1NS8xMDAsdFs0XSk6KHQ9ZUFlLmV4ZWMoZSkpP2ZBdCh0WzFdLHRbMl0vMTAwLHRbM10vMTAwLDEpOih0PXJBZS5leGVjKGUpKT9mQXQodFsxXSx0WzJdLzEwMCx0WzNdLzEwMCx0WzRdKTpjQXQuaGFzT3duUHJvcGVydHkoZSk/dUF0KGNBdFtlXSk6ZT09PSJ0cmFuc3BhcmVudCI/bmV3IHRvKE5hTixOYU4sTmFOLDApOm51bGx9ZnVuY3Rpb24gdUF0KGUpe3JldHVybiBuZXcgdG8oZT4+MTYmMjU1LGU+PjgmMjU1LGUmMjU1LDEpfWZ1bmN0aW9uIGhBdChlLHQscixuKXtyZXR1cm4gbjw9MCYmKGU9dD1yPU5hTiksbmV3IHRvKGUsdCxyLG4pfWZ1bmN0aW9uIG1UKGUpe3JldHVybiBlIGluc3RhbmNlb2YgdGZ8fChlPXgyKGUpKSxlPyhlPWUucmdiKCksbmV3IHRvKGUucixlLmcsZS5iLGUub3BhY2l0eSkpOm5ldyB0b31mdW5jdGlvbiBaSyhlLHQscixuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTE/bVQoZSk6bmV3IHRvKGUsdCxyLG49PW51bGw/MTpuKX1mdW5jdGlvbiB0byhlLHQscixuKXt0aGlzLnI9K2UsdGhpcy5nPSt0LHRoaXMuYj0rcix0aGlzLm9wYWNpdHk9K259ZnVuY3Rpb24gZkF0KGUsdCxyLG4pe3JldHVybiBuPD0wP2U9dD1yPU5hTjpyPD0wfHxyPj0xP2U9dD1OYU46dDw9MCYmKGU9TmFOKSxuZXcgSmgoZSx0LHIsbil9ZnVuY3Rpb24gbkFlKGUpe2lmKGUgaW5zdGFuY2VvZiBKaClyZXR1cm4gbmV3IEpoKGUuaCxlLnMsZS5sLGUub3BhY2l0eSk7aWYoZSBpbnN0YW5jZW9mIHRmfHwoZT14MihlKSksIWUpcmV0dXJuIG5ldyBKaDtpZihlIGluc3RhbmNlb2YgSmgpcmV0dXJuIGU7ZT1lLnJnYigpO3ZhciB0PWUuci8yNTUscj1lLmcvMjU1LG49ZS5iLzI1NSxpPU1hdGgubWluKHQscixuKSxvPU1hdGgubWF4KHQscixuKSxhPU5hTixzPW8taSxsPShvK2kpLzI7cmV0dXJuIHM/KHQ9PT1vP2E9KHItbikvcysocjxuKSo2OnI9PT1vP2E9KG4tdCkvcysyOmE9KHQtcikvcys0LHMvPWw8LjU/bytpOjItby1pLGEqPTYwKTpzPWw+MCYmbDwxPzA6YSxuZXcgSmgoYSxzLGwsZS5vcGFjaXR5KX1mdW5jdGlvbiBKSyhlLHQscixuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTE/bkFlKGUpOm5ldyBKaChlLHQscixuPT1udWxsPzE6bil9ZnVuY3Rpb24gSmgoZSx0LHIsbil7dGhpcy5oPStlLHRoaXMucz0rdCx0aGlzLmw9K3IsdGhpcy5vcGFjaXR5PStufWZ1bmN0aW9uIEtLKGUsdCxyKXtyZXR1cm4oZTw2MD90KyhyLXQpKmUvNjA6ZTwxODA/cjplPDI0MD90KyhyLXQpKigyNDAtZSkvNjA6dCkqMjU1fXZhciB1ZyxTeSx2MixkVCxRaCwkQ2UsS0NlLFpDZSxKQ2UsUUNlLHRBZSxlQWUsckFlLGNBdCxuTj1NKCgpPT57ck4oKTt1Zz0uNyxTeT0xL3VnLHYyPSJcXHMqKFsrLV0/XFxkKylcXHMqIixkVD0iXFxzKihbKy1dP1xcZCpcXC4/XFxkKyg/OltlRV1bKy1dP1xcZCspPylcXHMqIixRaD0iXFxzKihbKy1dP1xcZCpcXC4/XFxkKyg/OltlRV1bKy1dP1xcZCspPyklXFxzKiIsJENlPS9eIyhbMC05YS1mXXszfSkkLyxLQ2U9L14jKFswLTlhLWZdezZ9KSQvLFpDZT1uZXcgUmVnRXhwKCJecmdiXFwoIitbdjIsdjIsdjJdKyJcXCkkIiksSkNlPW5ldyBSZWdFeHAoIl5yZ2JcXCgiK1tRaCxRaCxRaF0rIlxcKSQiKSxRQ2U9bmV3IFJlZ0V4cCgiXnJnYmFcXCgiK1t2Mix2Mix2MixkVF0rIlxcKSQiKSx0QWU9bmV3IFJlZ0V4cCgiXnJnYmFcXCgiK1tRaCxRaCxRaCxkVF0rIlxcKSQiKSxlQWU9bmV3IFJlZ0V4cCgiXmhzbFxcKCIrW2RULFFoLFFoXSsiXFwpJCIpLHJBZT1uZXcgUmVnRXhwKCJeaHNsYVxcKCIrW2RULFFoLFFoLGRUXSsiXFwpJCIpLGNBdD17YWxpY2VibHVlOjE1NzkyMzgzLGFudGlxdWV3aGl0ZToxNjQ0NDM3NSxhcXVhOjY1NTM1LGFxdWFtYXJpbmU6ODM4ODU2NCxhenVyZToxNTc5NDE3NSxiZWlnZToxNjExOTI2MCxiaXNxdWU6MTY3NzAyNDQsYmxhY2s6MCxibGFuY2hlZGFsbW9uZDoxNjc3MjA0NSxibHVlOjI1NSxibHVldmlvbGV0OjkwNTUyMDIsYnJvd246MTA4MjQyMzQsYnVybHl3b29kOjE0NTk2MjMxLGNhZGV0Ymx1ZTo2MjY2NTI4LGNoYXJ0cmV1c2U6ODM4ODM1MixjaG9jb2xhdGU6MTM3ODk0NzAsY29yYWw6MTY3NDQyNzIsY29ybmZsb3dlcmJsdWU6NjU5MTk4MSxjb3Juc2lsazoxNjc3NTM4OCxjcmltc29uOjE0NDIzMTAwLGN5YW46NjU1MzUsZGFya2JsdWU6MTM5LGRhcmtjeWFuOjM1NzIzLGRhcmtnb2xkZW5yb2Q6MTIwOTI5MzksZGFya2dyYXk6MTExMTkwMTcsZGFya2dyZWVuOjI1NjAwLGRhcmtncmV5OjExMTE5MDE3LGRhcmtraGFraToxMjQzMzI1OSxkYXJrbWFnZW50YTo5MTA5NjQzLGRhcmtvbGl2ZWdyZWVuOjU1OTc5OTksZGFya29yYW5nZToxNjc0NzUyMCxkYXJrb3JjaGlkOjEwMDQwMDEyLGRhcmtyZWQ6OTEwOTUwNCxkYXJrc2FsbW9uOjE1MzA4NDEwLGRhcmtzZWFncmVlbjo5NDE5OTE5LGRhcmtzbGF0ZWJsdWU6NDczNDM0NyxkYXJrc2xhdGVncmF5OjMxMDA0OTUsZGFya3NsYXRlZ3JleTozMTAwNDk1LGRhcmt0dXJxdW9pc2U6NTI5NDUsZGFya3Zpb2xldDo5Njk5NTM5LGRlZXBwaW5rOjE2NzE2OTQ3LGRlZXBza3libHVlOjQ5MTUxLGRpbWdyYXk6NjkwODI2NSxkaW1ncmV5OjY5MDgyNjUsZG9kZ2VyYmx1ZToyMDAzMTk5LGZpcmVicmljazoxMTY3NDE0NixmbG9yYWx3aGl0ZToxNjc3NTkyMCxmb3Jlc3RncmVlbjoyMjYzODQyLGZ1Y2hzaWE6MTY3MTE5MzUsZ2FpbnNib3JvOjE0NDc0NDYwLGdob3N0d2hpdGU6MTYzMTY2NzEsZ29sZDoxNjc2NjcyMCxnb2xkZW5yb2Q6MTQzMjkxMjAsZ3JheTo4NDIxNTA0LGdyZWVuOjMyNzY4LGdyZWVueWVsbG93OjExNDAzMDU1LGdyZXk6ODQyMTUwNCxob25leWRldzoxNTc5NDE2MCxob3RwaW5rOjE2NzM4NzQwLGluZGlhbnJlZDoxMzQ1ODUyNCxpbmRpZ286NDkxNTMzMCxpdm9yeToxNjc3NzIwMCxraGFraToxNTc4NzY2MCxsYXZlbmRlcjoxNTEzMjQxMCxsYXZlbmRlcmJsdXNoOjE2NzczMzY1LGxhd25ncmVlbjo4MTkwOTc2LGxlbW9uY2hpZmZvbjoxNjc3NTg4NSxsaWdodGJsdWU6MTEzOTMyNTQsbGlnaHRjb3JhbDoxNTc2MTUzNixsaWdodGN5YW46MTQ3NDU1OTksbGlnaHRnb2xkZW5yb2R5ZWxsb3c6MTY0NDgyMTAsbGlnaHRncmF5OjEzODgyMzIzLGxpZ2h0Z3JlZW46OTQ5ODI1NixsaWdodGdyZXk6MTM4ODIzMjMsbGlnaHRwaW5rOjE2NzU4NDY1LGxpZ2h0c2FsbW9uOjE2NzUyNzYyLGxpZ2h0c2VhZ3JlZW46MjE0Mjg5MCxsaWdodHNreWJsdWU6ODkwMDM0NixsaWdodHNsYXRlZ3JheTo3ODMzNzUzLGxpZ2h0c2xhdGVncmV5Ojc4MzM3NTMsbGlnaHRzdGVlbGJsdWU6MTE1ODQ3MzQsbGlnaHR5ZWxsb3c6MTY3NzcxODQsbGltZTo2NTI4MCxsaW1lZ3JlZW46MzMyOTMzMCxsaW5lbjoxNjQ0NTY3MCxtYWdlbnRhOjE2NzExOTM1LG1hcm9vbjo4Mzg4NjA4LG1lZGl1bWFxdWFtYXJpbmU6NjczNzMyMixtZWRpdW1ibHVlOjIwNSxtZWRpdW1vcmNoaWQ6MTIyMTE2NjcsbWVkaXVtcHVycGxlOjk2NjI2ODMsbWVkaXVtc2VhZ3JlZW46Mzk3ODA5NyxtZWRpdW1zbGF0ZWJsdWU6ODA4Nzc5MCxtZWRpdW1zcHJpbmdncmVlbjo2NDE1NCxtZWRpdW10dXJxdW9pc2U6NDc3MjMwMCxtZWRpdW12aW9sZXRyZWQ6MTMwNDcxNzMsbWlkbmlnaHRibHVlOjE2NDQ5MTIsbWludGNyZWFtOjE2MTIxODUwLG1pc3R5cm9zZToxNjc3MDI3Myxtb2NjYXNpbjoxNjc3MDIyOSxuYXZham93aGl0ZToxNjc2ODY4NSxuYXZ5OjEyOCxvbGRsYWNlOjE2NjQzNTU4LG9saXZlOjg0MjEzNzYsb2xpdmVkcmFiOjcwNDg3Mzksb3JhbmdlOjE2NzUzOTIwLG9yYW5nZXJlZDoxNjcyOTM0NCxvcmNoaWQ6MTQzMTU3MzQscGFsZWdvbGRlbnJvZDoxNTY1NzEzMCxwYWxlZ3JlZW46MTAwMjU4ODAscGFsZXR1cnF1b2lzZToxMTUyOTk2NixwYWxldmlvbGV0cmVkOjE0MzgxMjAzLHBhcGF5YXdoaXA6MTY3NzMwNzcscGVhY2hwdWZmOjE2NzY3NjczLHBlcnU6MTM0Njg5OTEscGluazoxNjc2MTAzNSxwbHVtOjE0NTI0NjM3LHBvd2RlcmJsdWU6MTE1OTE5MTAscHVycGxlOjgzODg3MzYscmViZWNjYXB1cnBsZTo2Njk3ODgxLHJlZDoxNjcxMTY4MCxyb3N5YnJvd246MTIzNTc1MTkscm95YWxibHVlOjQyODY5NDUsc2FkZGxlYnJvd246OTEyNzE4NyxzYWxtb246MTY0MTY4ODIsc2FuZHlicm93bjoxNjAzMjg2NCxzZWFncmVlbjozMDUwMzI3LHNlYXNoZWxsOjE2Nzc0NjM4LHNpZW5uYToxMDUwNjc5NyxzaWx2ZXI6MTI2MzIyNTYsc2t5Ymx1ZTo4OTAwMzMxLHNsYXRlYmx1ZTo2OTcwMDYxLHNsYXRlZ3JheTo3MzcyOTQ0LHNsYXRlZ3JleTo3MzcyOTQ0LHNub3c6MTY3NzU5MzAsc3ByaW5nZ3JlZW46NjU0MDcsc3RlZWxibHVlOjQ2MjA5ODAsdGFuOjEzODA4NzgwLHRlYWw6MzI4OTYsdGhpc3RsZToxNDIwNDg4OCx0b21hdG86MTY3MzcwOTUsdHVycXVvaXNlOjQyNTE4NTYsdmlvbGV0OjE1NjMxMDg2LHdoZWF0OjE2MTEzMzMxLHdoaXRlOjE2Nzc3MjE1LHdoaXRlc21va2U6MTYxMTkyODUseWVsbG93OjE2Nzc2OTYwLHllbGxvd2dyZWVuOjEwMTQ1MDc0fTtZcCh0Zix4Mix7ZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5yZ2IoKS5kaXNwbGF5YWJsZSgpfSx0b1N0cmluZzpmdW5jdGlvbigpe3JldHVybiB0aGlzLnJnYigpKyIifX0pO1lwKHRvLFpLLGNnKHRmLHticmlnaHRlcjpmdW5jdGlvbihlKXtyZXR1cm4gZT1lPT1udWxsP1N5Ok1hdGgucG93KFN5LGUpLG5ldyB0byh0aGlzLnIqZSx0aGlzLmcqZSx0aGlzLmIqZSx0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD91ZzpNYXRoLnBvdyh1ZyxlKSxuZXcgdG8odGhpcy5yKmUsdGhpcy5nKmUsdGhpcy5iKmUsdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXN9LGRpc3BsYXlhYmxlOmZ1bmN0aW9uKCl7cmV0dXJuIDA8PXRoaXMuciYmdGhpcy5yPD0yNTUmJjA8PXRoaXMuZyYmdGhpcy5nPD0yNTUmJjA8PXRoaXMuYiYmdGhpcy5iPD0yNTUmJjA8PXRoaXMub3BhY2l0eSYmdGhpcy5vcGFjaXR5PD0xfSx0b1N0cmluZzpmdW5jdGlvbigpe3ZhciBlPXRoaXMub3BhY2l0eTtyZXR1cm4gZT1pc05hTihlKT8xOk1hdGgubWF4KDAsTWF0aC5taW4oMSxlKSksKGU9PT0xPyJyZ2IoIjoicmdiYSgiKStNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKHRoaXMucil8fDApKSsiLCAiK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQodGhpcy5nKXx8MCkpKyIsICIrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCh0aGlzLmIpfHwwKSkrKGU9PT0xPyIpIjoiLCAiK2UrIikiKX19KSk7WXAoSmgsSkssY2codGYse2JyaWdodGVyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/U3k6TWF0aC5wb3coU3ksZSksbmV3IEpoKHRoaXMuaCx0aGlzLnMsdGhpcy5sKmUsdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/dWc6TWF0aC5wb3codWcsZSksbmV3IEpoKHRoaXMuaCx0aGlzLnMsdGhpcy5sKmUsdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5oJTM2MCsodGhpcy5oPDApKjM2MCx0PWlzTmFOKGUpfHxpc05hTih0aGlzLnMpPzA6dGhpcy5zLHI9dGhpcy5sLG49cisocjwuNT9yOjEtcikqdCxpPTIqci1uO3JldHVybiBuZXcgdG8oS0soZT49MjQwP2UtMjQwOmUrMTIwLGksbiksS0soZSxpLG4pLEtLKGU8MTIwP2UrMjQwOmUtMTIwLGksbiksdGhpcy5vcGFjaXR5KX0sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4oMDw9dGhpcy5zJiZ0aGlzLnM8PTF8fGlzTmFOKHRoaXMucykpJiYwPD10aGlzLmwmJnRoaXMubDw9MSYmMDw9dGhpcy5vcGFjaXR5JiZ0aGlzLm9wYWNpdHk8PTF9fSkpfSk7dmFyIGlOLG9OLFFLPU0oKCk9PntpTj1NYXRoLlBJLzE4MCxvTj0xODAvTWF0aC5QSX0pO2Z1bmN0aW9uIGlaKGUpe2lmKGUgaW5zdGFuY2VvZiBqcClyZXR1cm4gbmV3IGpwKGUubCxlLmEsZS5iLGUub3BhY2l0eSk7aWYoZSBpbnN0YW5jZW9mIGhnKXt2YXIgdD1lLmgqaU47cmV0dXJuIG5ldyBqcChlLmwsTWF0aC5jb3ModCkqZS5jLE1hdGguc2luKHQpKmUuYyxlLm9wYWNpdHkpfWUgaW5zdGFuY2VvZiB0b3x8KGU9bVQoZSkpO3ZhciByPW5aKGUuciksbj1uWihlLmcpLGk9blooZS5iKSxvPXRaKCguNDEyNDU2NCpyKy4zNTc1NzYxKm4rLjE4MDQzNzUqaSkvcEF0KSxhPXRaKCguMjEyNjcyOSpyKy43MTUxNTIyKm4rLjA3MjE3NSppKS9kQXQpLHM9dFooKC4wMTkzMzM5KnIrLjExOTE5MipuKy45NTAzMDQxKmkpL21BdCk7cmV0dXJuIG5ldyBqcCgxMTYqYS0xNiw1MDAqKG8tYSksMjAwKihhLXMpLGUub3BhY2l0eSl9ZnVuY3Rpb24gc04oZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP2laKGUpOm5ldyBqcChlLHQscixuPT1udWxsPzE6bil9ZnVuY3Rpb24ganAoZSx0LHIsbil7dGhpcy5sPStlLHRoaXMuYT0rdCx0aGlzLmI9K3IsdGhpcy5vcGFjaXR5PStufWZ1bmN0aW9uIHRaKGUpe3JldHVybiBlPmlBZT9NYXRoLnBvdyhlLDEvMyk6ZS9fQXQrZ0F0fWZ1bmN0aW9uIGVaKGUpe3JldHVybiBlPmIyP2UqZSplOl9BdCooZS1nQXQpfWZ1bmN0aW9uIHJaKGUpe3JldHVybiAyNTUqKGU8PS4wMDMxMzA4PzEyLjkyKmU6MS4wNTUqTWF0aC5wb3coZSwxLzIuNCktLjA1NSl9ZnVuY3Rpb24gblooZSl7cmV0dXJuKGUvPTI1NSk8PS4wNDA0NT9lLzEyLjkyOk1hdGgucG93KChlKy4wNTUpLzEuMDU1LDIuNCl9ZnVuY3Rpb24gb0FlKGUpe2lmKGUgaW5zdGFuY2VvZiBoZylyZXR1cm4gbmV3IGhnKGUuaCxlLmMsZS5sLGUub3BhY2l0eSk7ZSBpbnN0YW5jZW9mIGpwfHwoZT1pWihlKSk7dmFyIHQ9TWF0aC5hdGFuMihlLmIsZS5hKSpvTjtyZXR1cm4gbmV3IGhnKHQ8MD90KzM2MDp0LE1hdGguc3FydChlLmEqZS5hK2UuYiplLmIpLGUubCxlLm9wYWNpdHkpfWZ1bmN0aW9uIG9aKGUsdCxyLG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MT9vQWUoZSk6bmV3IGhnKGUsdCxyLG49PW51bGw/MTpuKX1mdW5jdGlvbiBoZyhlLHQscixuKXt0aGlzLmg9K2UsdGhpcy5jPSt0LHRoaXMubD0rcix0aGlzLm9wYWNpdHk9K259dmFyIGFOLHBBdCxkQXQsbUF0LGdBdCxiMixfQXQsaUFlLHlBdD1NKCgpPT57ck4oKTtuTigpO1FLKCk7YU49MTgscEF0PS45NTA0NyxkQXQ9MSxtQXQ9MS4wODg4MyxnQXQ9NC8yOSxiMj02LzI5LF9BdD0zKmIyKmIyLGlBZT1iMipiMipiMjtZcChqcCxzTixjZyh0Zix7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyBqcCh0aGlzLmwrYU4qKGU9PW51bGw/MTplKSx0aGlzLmEsdGhpcy5iLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihlKXtyZXR1cm4gbmV3IGpwKHRoaXMubC1hTiooZT09bnVsbD8xOmUpLHRoaXMuYSx0aGlzLmIsdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7dmFyIGU9KHRoaXMubCsxNikvMTE2LHQ9aXNOYU4odGhpcy5hKT9lOmUrdGhpcy5hLzUwMCxyPWlzTmFOKHRoaXMuYik/ZTplLXRoaXMuYi8yMDA7cmV0dXJuIGU9ZEF0KmVaKGUpLHQ9cEF0KmVaKHQpLHI9bUF0KmVaKHIpLG5ldyB0byhyWigzLjI0MDQ1NDIqdC0xLjUzNzEzODUqZS0uNDk4NTMxNCpyKSxyWigtLjk2OTI2Nip0KzEuODc2MDEwOCplKy4wNDE1NTYqciksclooLjA1NTY0MzQqdC0uMjA0MDI1OSplKzEuMDU3MjI1MipyKSx0aGlzLm9wYWNpdHkpfX0pKTtZcChoZyxvWixjZyh0Zix7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyBoZyh0aGlzLmgsdGhpcy5jLHRoaXMubCthTiooZT09bnVsbD8xOmUpLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihlKXtyZXR1cm4gbmV3IGhnKHRoaXMuaCx0aGlzLmMsdGhpcy5sLWFOKihlPT1udWxsPzE6ZSksdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7cmV0dXJuIGlaKHRoaXMpLnJnYigpfX0pKX0pO2Z1bmN0aW9uIGFBZShlKXtpZihlIGluc3RhbmNlb2YgTXkpcmV0dXJuIG5ldyBNeShlLmgsZS5zLGUubCxlLm9wYWNpdHkpO2UgaW5zdGFuY2VvZiB0b3x8KGU9bVQoZSkpO3ZhciB0PWUuci8yNTUscj1lLmcvMjU1LG49ZS5iLzI1NSxpPShiQXQqbit2QXQqdC14QXQqcikvKGJBdCt2QXQteEF0KSxvPW4taSxhPShnVCooci1pKS1zWipvKS9sTixzPU1hdGguc3FydChhKmErbypvKS8oZ1QqaSooMS1pKSksbD1zP01hdGguYXRhbjIoYSxvKSpvTi0xMjA6TmFOO3JldHVybiBuZXcgTXkobDwwP2wrMzYwOmwscyxpLGUub3BhY2l0eSl9ZnVuY3Rpb24gY04oZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP2FBZShlKTpuZXcgTXkoZSx0LHIsbj09bnVsbD8xOm4pfWZ1bmN0aW9uIE15KGUsdCxyLG4pe3RoaXMuaD0rZSx0aGlzLnM9K3QsdGhpcy5sPStyLHRoaXMub3BhY2l0eT0rbn12YXIgd0F0LGFaLHNaLGxOLGdULHZBdCx4QXQsYkF0LFNBdD1NKCgpPT57ck4oKTtuTigpO1FLKCk7d0F0PS0uMTQ4NjEsYVo9MS43ODI3NyxzWj0tLjI5MjI3LGxOPS0uOTA2NDksZ1Q9MS45NzI5NCx2QXQ9Z1QqbE4seEF0PWdUKmFaLGJBdD1hWipzWi1sTip3QXQ7WXAoTXksY04sY2codGYse2JyaWdodGVyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/U3k6TWF0aC5wb3coU3ksZSksbmV3IE15KHRoaXMuaCx0aGlzLnMsdGhpcy5sKmUsdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/dWc6TWF0aC5wb3codWcsZSksbmV3IE15KHRoaXMuaCx0aGlzLnMsdGhpcy5sKmUsdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7dmFyIGU9aXNOYU4odGhpcy5oKT8wOih0aGlzLmgrMTIwKSppTix0PSt0aGlzLmwscj1pc05hTih0aGlzLnMpPzA6dGhpcy5zKnQqKDEtdCksbj1NYXRoLmNvcyhlKSxpPU1hdGguc2luKGUpO3JldHVybiBuZXcgdG8oMjU1Kih0K3IqKHdBdCpuK2FaKmkpKSwyNTUqKHQrciooc1oqbitsTippKSksMjU1Kih0K3IqKGdUKm4pKSx0aGlzLm9wYWNpdHkpfX0pKX0pO3ZhciBNQXQ9TSgoKT0+e25OKCk7eUF0KCk7U0F0KCl9KTtmdW5jdGlvbiBUQXQoKXtmb3IodmFyIGU9MCx0PWFyZ3VtZW50cy5sZW5ndGgscj17fSxuO2U8dDsrK2Upe2lmKCEobj1hcmd1bWVudHNbZV0rIiIpfHxuIGluIHIpdGhyb3cgbmV3IEVycm9yKCJpbGxlZ2FsIHR5cGU6ICIrbik7cltuXT1bXX1yZXR1cm4gbmV3IHVOKHIpfWZ1bmN0aW9uIHVOKGUpe3RoaXMuXz1lfWZ1bmN0aW9uIGxBZShlLHQpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKS5tYXAoZnVuY3Rpb24ocil7dmFyIG49IiIsaT1yLmluZGV4T2YoIi4iKTtpZihpPj0wJiYobj1yLnNsaWNlKGkrMSkscj1yLnNsaWNlKDAsaSkpLHImJiF0Lmhhc093blByb3BlcnR5KHIpKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK3IpO3JldHVybnt0eXBlOnIsbmFtZTpufX0pfWZ1bmN0aW9uIGNBZShlLHQpe2Zvcih2YXIgcj0wLG49ZS5sZW5ndGgsaTtyPG47KytyKWlmKChpPWVbcl0pLm5hbWU9PT10KXJldHVybiBpLnZhbHVlfWZ1bmN0aW9uIEVBdChlLHQscil7Zm9yKHZhciBuPTAsaT1lLmxlbmd0aDtuPGk7KytuKWlmKGVbbl0ubmFtZT09PXQpe2Vbbl09c0FlLGU9ZS5zbGljZSgwLG4pLmNvbmNhdChlLnNsaWNlKG4rMSkpO2JyZWFrfXJldHVybiByIT1udWxsJiZlLnB1c2goe25hbWU6dCx2YWx1ZTpyfSksZX12YXIgc0FlLENBdCxBQXQ9TSgoKT0+e3NBZT17dmFsdWU6ZnVuY3Rpb24oKXt9fTt1Ti5wcm90b3R5cGU9VEF0LnByb3RvdHlwZT17Y29uc3RydWN0b3I6dU4sb246ZnVuY3Rpb24oZSx0KXt2YXIgcj10aGlzLl8sbj1sQWUoZSsiIixyKSxpLG89LTEsYT1uLmxlbmd0aDtpZihhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcig7KytvPGE7KWlmKChpPShlPW5bb10pLnR5cGUpJiYoaT1jQWUocltpXSxlLm5hbWUpKSlyZXR1cm4gaTtyZXR1cm59aWYodCE9bnVsbCYmdHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIGNhbGxiYWNrOiAiK3QpO2Zvcig7KytvPGE7KWlmKGk9KGU9bltvXSkudHlwZSlyW2ldPUVBdChyW2ldLGUubmFtZSx0KTtlbHNlIGlmKHQ9PW51bGwpZm9yKGkgaW4gcilyW2ldPUVBdChyW2ldLGUubmFtZSxudWxsKTtyZXR1cm4gdGhpc30sY29weTpmdW5jdGlvbigpe3ZhciBlPXt9LHQ9dGhpcy5fO2Zvcih2YXIgciBpbiB0KWVbcl09dFtyXS5zbGljZSgpO3JldHVybiBuZXcgdU4oZSl9LGNhbGw6ZnVuY3Rpb24oZSx0KXtpZigoaT1hcmd1bWVudHMubGVuZ3RoLTIpPjApZm9yKHZhciByPW5ldyBBcnJheShpKSxuPTAsaSxvO248aTsrK24pcltuXT1hcmd1bWVudHNbbisyXTtpZighdGhpcy5fLmhhc093blByb3BlcnR5KGUpKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK2UpO2ZvcihvPXRoaXMuX1tlXSxuPTAsaT1vLmxlbmd0aDtuPGk7KytuKW9bbl0udmFsdWUuYXBwbHkodCxyKX0sYXBwbHk6ZnVuY3Rpb24oZSx0LHIpe2lmKCF0aGlzLl8uaGFzT3duUHJvcGVydHkoZSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrZSk7Zm9yKHZhciBuPXRoaXMuX1tlXSxpPTAsbz1uLmxlbmd0aDtpPG87KytpKW5baV0udmFsdWUuYXBwbHkodCxyKX19O0NBdD1UQXR9KTt2YXIgUEF0PU0oKCk9PntBQXQoKX0pO2Z1bmN0aW9uIExBdCgpe2Zvcih2YXIgZT0wLHQ9YXJndW1lbnRzLmxlbmd0aCxyPXt9LG47ZTx0OysrZSl7aWYoIShuPWFyZ3VtZW50c1tlXSsiIil8fG4gaW4gcnx8L1tccy5dLy50ZXN0KG4pKXRocm93IG5ldyBFcnJvcigiaWxsZWdhbCB0eXBlOiAiK24pO3Jbbl09W119cmV0dXJuIG5ldyBoTihyKX1mdW5jdGlvbiBoTihlKXt0aGlzLl89ZX1mdW5jdGlvbiBoQWUoZSx0KXtyZXR1cm4gZS50cmltKCkuc3BsaXQoL158XHMrLykubWFwKGZ1bmN0aW9uKHIpe3ZhciBuPSIiLGk9ci5pbmRleE9mKCIuIik7aWYoaT49MCYmKG49ci5zbGljZShpKzEpLHI9ci5zbGljZSgwLGkpKSxyJiYhdC5oYXNPd25Qcm9wZXJ0eShyKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gdHlwZTogIityKTtyZXR1cm57dHlwZTpyLG5hbWU6bn19KX1mdW5jdGlvbiBmQWUoZSx0KXtmb3IodmFyIHI9MCxuPWUubGVuZ3RoLGk7cjxuOysrcilpZigoaT1lW3JdKS5uYW1lPT09dClyZXR1cm4gaS52YWx1ZX1mdW5jdGlvbiBJQXQoZSx0LHIpe2Zvcih2YXIgbj0wLGk9ZS5sZW5ndGg7bjxpOysrbilpZihlW25dLm5hbWU9PT10KXtlW25dPXVBZSxlPWUuc2xpY2UoMCxuKS5jb25jYXQoZS5zbGljZShuKzEpKTticmVha31yZXR1cm4gciE9bnVsbCYmZS5wdXNoKHtuYW1lOnQsdmFsdWU6cn0pLGV9dmFyIHVBZSxsWixrQXQ9TSgoKT0+e3VBZT17dmFsdWU6ZnVuY3Rpb24oKXt9fTtoTi5wcm90b3R5cGU9TEF0LnByb3RvdHlwZT17Y29uc3RydWN0b3I6aE4sb246ZnVuY3Rpb24oZSx0KXt2YXIgcj10aGlzLl8sbj1oQWUoZSsiIixyKSxpLG89LTEsYT1uLmxlbmd0aDtpZihhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcig7KytvPGE7KWlmKChpPShlPW5bb10pLnR5cGUpJiYoaT1mQWUocltpXSxlLm5hbWUpKSlyZXR1cm4gaTtyZXR1cm59aWYodCE9bnVsbCYmdHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIGNhbGxiYWNrOiAiK3QpO2Zvcig7KytvPGE7KWlmKGk9KGU9bltvXSkudHlwZSlyW2ldPUlBdChyW2ldLGUubmFtZSx0KTtlbHNlIGlmKHQ9PW51bGwpZm9yKGkgaW4gcilyW2ldPUlBdChyW2ldLGUubmFtZSxudWxsKTtyZXR1cm4gdGhpc30sY29weTpmdW5jdGlvbigpe3ZhciBlPXt9LHQ9dGhpcy5fO2Zvcih2YXIgciBpbiB0KWVbcl09dFtyXS5zbGljZSgpO3JldHVybiBuZXcgaE4oZSl9LGNhbGw6ZnVuY3Rpb24oZSx0KXtpZigoaT1hcmd1bWVudHMubGVuZ3RoLTIpPjApZm9yKHZhciByPW5ldyBBcnJheShpKSxuPTAsaSxvO248aTsrK24pcltuXT1hcmd1bWVudHNbbisyXTtpZighdGhpcy5fLmhhc093blByb3BlcnR5KGUpKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK2UpO2ZvcihvPXRoaXMuX1tlXSxuPTAsaT1vLmxlbmd0aDtuPGk7KytuKW9bbl0udmFsdWUuYXBwbHkodCxyKX0sYXBwbHk6ZnVuY3Rpb24oZSx0LHIpe2lmKCF0aGlzLl8uaGFzT3duUHJvcGVydHkoZSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrZSk7Zm9yKHZhciBuPXRoaXMuX1tlXSxpPTAsbz1uLmxlbmd0aDtpPG87KytpKW5baV0udmFsdWUuYXBwbHkodCxyKX19O2xaPUxBdH0pO3ZhciBSQXQ9TSgoKT0+e2tBdCgpfSk7dmFyIGZOLGNaLHVaPU0oKCk9PntmTj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIsY1o9e3N2ZzoiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLHhodG1sOmZOLHhsaW5rOiJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIix4bWw6Imh0dHA6Ly93d3cudzMub3JnL1hNTC8xOTk4L25hbWVzcGFjZSIseG1sbnM6Imh0dHA6Ly93d3cudzMub3JnLzIwMDAveG1sbnMvIn19KTtmdW5jdGlvbiBwTihlKXt2YXIgdD1lKz0iIixyPXQuaW5kZXhPZigiOiIpO3JldHVybiByPj0wJiYodD1lLnNsaWNlKDAscikpIT09InhtbG5zIiYmKGU9ZS5zbGljZShyKzEpKSxjWi5oYXNPd25Qcm9wZXJ0eSh0KT97c3BhY2U6Y1pbdF0sbG9jYWw6ZX06ZX12YXIgaFo9TSgoKT0+e3VaKCl9KTtmdW5jdGlvbiBwQWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5vd25lckRvY3VtZW50LHI9dGhpcy5uYW1lc3BhY2VVUkk7cmV0dXJuIHI9PT1mTiYmdC5kb2N1bWVudEVsZW1lbnQubmFtZXNwYWNlVVJJPT09Zk4/dC5jcmVhdGVFbGVtZW50KGUpOnQuY3JlYXRlRWxlbWVudE5TKHIsZSl9fWZ1bmN0aW9uIGRBZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5vd25lckRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyhlLnNwYWNlLGUubG9jYWwpfX1mdW5jdGlvbiBkTihlKXt2YXIgdD1wTihlKTtyZXR1cm4odC5sb2NhbD9kQWU6cEFlKSh0KX12YXIgZlo9TSgoKT0+e2haKCk7dVooKX0pO2Z1bmN0aW9uIG1BZSgpe31mdW5jdGlvbiBtTihlKXtyZXR1cm4gZT09bnVsbD9tQWU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5xdWVyeVNlbGVjdG9yKGUpfX12YXIgcFo9TSgoKT0+e30pO2Z1bmN0aW9uIE5BdChlKXt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9bU4oZSkpO2Zvcih2YXIgdD10aGlzLl9ncm91cHMscj10Lmxlbmd0aCxuPW5ldyBBcnJheShyKSxpPTA7aTxyOysraSlmb3IodmFyIG89dFtpXSxhPW8ubGVuZ3RoLHM9bltpXT1uZXcgQXJyYXkoYSksbCxjLHU9MDt1PGE7Kyt1KShsPW9bdV0pJiYoYz1lLmNhbGwobCxsLl9fZGF0YV9fLHUsbykpJiYoIl9fZGF0YV9fImluIGwmJihjLl9fZGF0YV9fPWwuX19kYXRhX18pLHNbdV09Yyk7cmV0dXJuIG5ldyBkaShuLHRoaXMuX3BhcmVudHMpfXZhciBEQXQ9TSgoKT0+e2VmKCk7cFooKX0pO2Z1bmN0aW9uIGdBZSgpe3JldHVybltdfWZ1bmN0aW9uIE9BdChlKXtyZXR1cm4gZT09bnVsbD9nQWU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5xdWVyeVNlbGVjdG9yQWxsKGUpfX12YXIgekF0PU0oKCk9Pnt9KTtmdW5jdGlvbiBGQXQoZSl7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPU9BdChlKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPXQubGVuZ3RoLG49W10saT1bXSxvPTA7bzxyOysrbylmb3IodmFyIGE9dFtvXSxzPWEubGVuZ3RoLGwsYz0wO2M8czsrK2MpKGw9YVtjXSkmJihuLnB1c2goZS5jYWxsKGwsbC5fX2RhdGFfXyxjLGEpKSxpLnB1c2gobCkpO3JldHVybiBuZXcgZGkobixpKX12YXIgQkF0PU0oKCk9PntlZigpO3pBdCgpfSk7ZnVuY3Rpb24gSEF0KGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiB0aGlzLm1hdGNoZXMoZSl9fXZhciBWQXQ9TSgoKT0+e30pO2Z1bmN0aW9uIFVBdChlKXt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9SEF0KGUpKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9dC5sZW5ndGgsbj1uZXcgQXJyYXkociksaT0wO2k8cjsrK2kpZm9yKHZhciBvPXRbaV0sYT1vLmxlbmd0aCxzPW5baV09W10sbCxjPTA7YzxhOysrYykobD1vW2NdKSYmZS5jYWxsKGwsbC5fX2RhdGFfXyxjLG8pJiZzLnB1c2gobCk7cmV0dXJuIG5ldyBkaShuLHRoaXMuX3BhcmVudHMpfXZhciBxQXQ9TSgoKT0+e2VmKCk7VkF0KCl9KTtmdW5jdGlvbiBnTihlKXtyZXR1cm4gbmV3IEFycmF5KGUubGVuZ3RoKX12YXIgZFo9TSgoKT0+e30pO2Z1bmN0aW9uIEdBdCgpe3JldHVybiBuZXcgZGkodGhpcy5fZW50ZXJ8fHRoaXMuX2dyb3Vwcy5tYXAoZ04pLHRoaXMuX3BhcmVudHMpfWZ1bmN0aW9uIF9UKGUsdCl7dGhpcy5vd25lckRvY3VtZW50PWUub3duZXJEb2N1bWVudCx0aGlzLm5hbWVzcGFjZVVSST1lLm5hbWVzcGFjZVVSSSx0aGlzLl9uZXh0PW51bGwsdGhpcy5fcGFyZW50PWUsdGhpcy5fX2RhdGFfXz10fXZhciBtWj1NKCgpPT57ZFooKTtlZigpO19ULnByb3RvdHlwZT17Y29uc3RydWN0b3I6X1QsYXBwZW5kQ2hpbGQ6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX3BhcmVudC5pbnNlcnRCZWZvcmUoZSx0aGlzLl9uZXh0KX0saW5zZXJ0QmVmb3JlOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIHRoaXMuX3BhcmVudC5pbnNlcnRCZWZvcmUoZSx0KX0scXVlcnlTZWxlY3RvcjpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fcGFyZW50LnF1ZXJ5U2VsZWN0b3IoZSl9LHF1ZXJ5U2VsZWN0b3JBbGw6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX3BhcmVudC5xdWVyeVNlbGVjdG9yQWxsKGUpfX19KTtmdW5jdGlvbiBXQXQoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fXZhciBZQXQ9TSgoKT0+e30pO2Z1bmN0aW9uIF9BZShlLHQscixuLGksbyl7Zm9yKHZhciBhPTAscyxsPXQubGVuZ3RoLGM9by5sZW5ndGg7YTxjOysrYSkocz10W2FdKT8ocy5fX2RhdGFfXz1vW2FdLG5bYV09cyk6clthXT1uZXcgX1QoZSxvW2FdKTtmb3IoO2E8bDsrK2EpKHM9dFthXSkmJihpW2FdPXMpfWZ1bmN0aW9uIHlBZShlLHQscixuLGksbyxhKXt2YXIgcyxsLGM9e30sdT10Lmxlbmd0aCxoPW8ubGVuZ3RoLGY9bmV3IEFycmF5KHUpLHA7Zm9yKHM9MDtzPHU7KytzKShsPXRbc10pJiYoZltzXT1wPWpBdCthLmNhbGwobCxsLl9fZGF0YV9fLHMsdCkscCBpbiBjP2lbc109bDpjW3BdPWwpO2ZvcihzPTA7czxoOysrcylwPWpBdCthLmNhbGwoZSxvW3NdLHMsbyksKGw9Y1twXSk/KG5bc109bCxsLl9fZGF0YV9fPW9bc10sY1twXT1udWxsKTpyW3NdPW5ldyBfVChlLG9bc10pO2ZvcihzPTA7czx1OysrcykobD10W3NdKSYmY1tmW3NdXT09PWwmJihpW3NdPWwpfWZ1bmN0aW9uIFhBdChlLHQpe2lmKCFlKXJldHVybiBwPW5ldyBBcnJheSh0aGlzLnNpemUoKSksYz0tMSx0aGlzLmVhY2goZnVuY3Rpb24oUCl7cFsrK2NdPVB9KSxwO3ZhciByPXQ/eUFlOl9BZSxuPXRoaXMuX3BhcmVudHMsaT10aGlzLl9ncm91cHM7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPVdBdChlKSk7Zm9yKHZhciBvPWkubGVuZ3RoLGE9bmV3IEFycmF5KG8pLHM9bmV3IEFycmF5KG8pLGw9bmV3IEFycmF5KG8pLGM9MDtjPG87KytjKXt2YXIgdT1uW2NdLGg9aVtjXSxmPWgubGVuZ3RoLHA9ZS5jYWxsKHUsdSYmdS5fX2RhdGFfXyxjLG4pLGQ9cC5sZW5ndGgsZz1zW2NdPW5ldyBBcnJheShkKSxfPWFbY109bmV3IEFycmF5KGQpLHk9bFtjXT1uZXcgQXJyYXkoZik7cih1LGgsZyxfLHkscCx0KTtmb3IodmFyIHg9MCxiPTAsUyxDO3g8ZDsrK3gpaWYoUz1nW3hdKXtmb3IoeD49YiYmKGI9eCsxKTshKEM9X1tiXSkmJisrYjxkOyk7Uy5fbmV4dD1DfHxudWxsfX1yZXR1cm4gYT1uZXcgZGkoYSxuKSxhLl9lbnRlcj1zLGEuX2V4aXQ9bCxhfXZhciBqQXQsJEF0PU0oKCk9PntlZigpO21aKCk7WUF0KCk7akF0PSIkIn0pO2Z1bmN0aW9uIEtBdCgpe3JldHVybiBuZXcgZGkodGhpcy5fZXhpdHx8dGhpcy5fZ3JvdXBzLm1hcChnTiksdGhpcy5fcGFyZW50cyl9dmFyIFpBdD1NKCgpPT57ZFooKTtlZigpfSk7ZnVuY3Rpb24gSkF0KGUsdCxyKXt2YXIgbj10aGlzLmVudGVyKCksaT10aGlzLG89dGhpcy5leGl0KCk7cmV0dXJuIG49dHlwZW9mIGU9PSJmdW5jdGlvbiI/ZShuKTpuLmFwcGVuZChlKyIiKSx0IT1udWxsJiYoaT10KGkpKSxyPT1udWxsP28ucmVtb3ZlKCk6cihvKSxuJiZpP24ubWVyZ2UoaSkub3JkZXIoKTppfXZhciBRQXQ9TSgoKT0+e30pO2Z1bmN0aW9uIHQ0dChlKXtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9ZS5fZ3JvdXBzLG49dC5sZW5ndGgsaT1yLmxlbmd0aCxvPU1hdGgubWluKG4saSksYT1uZXcgQXJyYXkobikscz0wO3M8bzsrK3MpZm9yKHZhciBsPXRbc10sYz1yW3NdLHU9bC5sZW5ndGgsaD1hW3NdPW5ldyBBcnJheSh1KSxmLHA9MDtwPHU7KytwKShmPWxbcF18fGNbcF0pJiYoaFtwXT1mKTtmb3IoO3M8bjsrK3MpYVtzXT10W3NdO3JldHVybiBuZXcgZGkoYSx0aGlzLl9wYXJlbnRzKX12YXIgZTR0PU0oKCk9PntlZigpfSk7ZnVuY3Rpb24gcjR0KCl7Zm9yKHZhciBlPXRoaXMuX2dyb3Vwcyx0PS0xLHI9ZS5sZW5ndGg7Kyt0PHI7KWZvcih2YXIgbj1lW3RdLGk9bi5sZW5ndGgtMSxvPW5baV0sYTstLWk+PTA7KShhPW5baV0pJiYobyYmYS5jb21wYXJlRG9jdW1lbnRQb3NpdGlvbihvKV40JiZvLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGEsbyksbz1hKTtyZXR1cm4gdGhpc312YXIgbjR0PU0oKCk9Pnt9KTtmdW5jdGlvbiBpNHQoZSl7ZXx8KGU9dkFlKTtmdW5jdGlvbiB0KGgsZil7cmV0dXJuIGgmJmY/ZShoLl9fZGF0YV9fLGYuX19kYXRhX18pOiFoLSFmfWZvcih2YXIgcj10aGlzLl9ncm91cHMsbj1yLmxlbmd0aCxpPW5ldyBBcnJheShuKSxvPTA7bzxuOysrbyl7Zm9yKHZhciBhPXJbb10scz1hLmxlbmd0aCxsPWlbb109bmV3IEFycmF5KHMpLGMsdT0wO3U8czsrK3UpKGM9YVt1XSkmJihsW3VdPWMpO2wuc29ydCh0KX1yZXR1cm4gbmV3IGRpKGksdGhpcy5fcGFyZW50cykub3JkZXIoKX1mdW5jdGlvbiB2QWUoZSx0KXtyZXR1cm4gZTx0Py0xOmU+dD8xOmU+PXQ/MDpOYU59dmFyIG80dD1NKCgpPT57ZWYoKX0pO2Z1bmN0aW9uIGE0dCgpe3ZhciBlPWFyZ3VtZW50c1swXTtyZXR1cm4gYXJndW1lbnRzWzBdPXRoaXMsZS5hcHBseShudWxsLGFyZ3VtZW50cyksdGhpc312YXIgczR0PU0oKCk9Pnt9KTtmdW5jdGlvbiBsNHQoKXt2YXIgZT1uZXcgQXJyYXkodGhpcy5zaXplKCkpLHQ9LTE7cmV0dXJuIHRoaXMuZWFjaChmdW5jdGlvbigpe2VbKyt0XT10aGlzfSksZX12YXIgYzR0PU0oKCk9Pnt9KTtmdW5jdGlvbiB1NHQoKXtmb3IodmFyIGU9dGhpcy5fZ3JvdXBzLHQ9MCxyPWUubGVuZ3RoO3Q8cjsrK3QpZm9yKHZhciBuPWVbdF0saT0wLG89bi5sZW5ndGg7aTxvOysraSl7dmFyIGE9bltpXTtpZihhKXJldHVybiBhfXJldHVybiBudWxsfXZhciBoNHQ9TSgoKT0+e30pO2Z1bmN0aW9uIGY0dCgpe3ZhciBlPTA7cmV0dXJuIHRoaXMuZWFjaChmdW5jdGlvbigpeysrZX0pLGV9dmFyIHA0dD1NKCgpPT57fSk7ZnVuY3Rpb24gZDR0KCl7cmV0dXJuIXRoaXMubm9kZSgpfXZhciBtNHQ9TSgoKT0+e30pO2Z1bmN0aW9uIGc0dChlKXtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9MCxuPXQubGVuZ3RoO3I8bjsrK3IpZm9yKHZhciBpPXRbcl0sbz0wLGE9aS5sZW5ndGgscztvPGE7KytvKShzPWlbb10pJiZlLmNhbGwocyxzLl9fZGF0YV9fLG8saSk7cmV0dXJuIHRoaXN9dmFyIF80dD1NKCgpPT57fSk7ZnVuY3Rpb24geEFlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlKGUpfX1mdW5jdGlvbiBiQWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpfX1mdW5jdGlvbiB3QWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZShlLHQpfX1mdW5jdGlvbiBTQWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCx0KX19ZnVuY3Rpb24gTUFlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHI9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cj09bnVsbD90aGlzLnJlbW92ZUF0dHJpYnV0ZShlKTp0aGlzLnNldEF0dHJpYnV0ZShlLHIpfX1mdW5jdGlvbiBFQWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgcj10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyPT1udWxsP3RoaXMucmVtb3ZlQXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKTp0aGlzLnNldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCxyKX19ZnVuY3Rpb24geTR0KGUsdCl7dmFyIHI9cE4oZSk7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXt2YXIgbj10aGlzLm5vZGUoKTtyZXR1cm4gci5sb2NhbD9uLmdldEF0dHJpYnV0ZU5TKHIuc3BhY2Usci5sb2NhbCk6bi5nZXRBdHRyaWJ1dGUocil9cmV0dXJuIHRoaXMuZWFjaCgodD09bnVsbD9yLmxvY2FsP2JBZTp4QWU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/ci5sb2NhbD9FQWU6TUFlOnIubG9jYWw/U0FlOndBZSkocix0KSl9dmFyIHY0dD1NKCgpPT57aFooKX0pO2Z1bmN0aW9uIF9OKGUpe3JldHVybiBlLm93bmVyRG9jdW1lbnQmJmUub3duZXJEb2N1bWVudC5kZWZhdWx0Vmlld3x8ZS5kb2N1bWVudCYmZXx8ZS5kZWZhdWx0Vmlld312YXIgZ1o9TSgoKT0+e30pO2Z1bmN0aW9uIFRBZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KGUpfX1mdW5jdGlvbiBDQWUoZSx0LHIpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc3R5bGUuc2V0UHJvcGVydHkoZSx0LHIpfX1mdW5jdGlvbiBBQWUoZSx0LHIpe3JldHVybiBmdW5jdGlvbigpe3ZhciBuPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO249PW51bGw/dGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShlKTp0aGlzLnN0eWxlLnNldFByb3BlcnR5KGUsbixyKX19ZnVuY3Rpb24geDR0KGUsdCxyKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD4xP3RoaXMuZWFjaCgodD09bnVsbD9UQWU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/QUFlOkNBZSkoZSx0LHI9PW51bGw/IiI6cikpOlBBZSh0aGlzLm5vZGUoKSxlKX1mdW5jdGlvbiBQQWUoZSx0KXtyZXR1cm4gZS5zdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKHQpfHxfTihlKS5nZXRDb21wdXRlZFN0eWxlKGUsbnVsbCkuZ2V0UHJvcGVydHlWYWx1ZSh0KX12YXIgYjR0PU0oKCk9PntnWigpfSk7ZnVuY3Rpb24gSUFlKGUpe3JldHVybiBmdW5jdGlvbigpe2RlbGV0ZSB0aGlzW2VdfX1mdW5jdGlvbiBMQWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzW2VdPXR9fWZ1bmN0aW9uIGtBZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3ZhciByPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3I9PW51bGw/ZGVsZXRlIHRoaXNbZV06dGhpc1tlXT1yfX1mdW5jdGlvbiB3NHQoZSx0KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD4xP3RoaXMuZWFjaCgodD09bnVsbD9JQWU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/a0FlOkxBZSkoZSx0KSk6dGhpcy5ub2RlKClbZV19dmFyIFM0dD1NKCgpPT57fSk7ZnVuY3Rpb24gTTR0KGUpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKX1mdW5jdGlvbiBfWihlKXtyZXR1cm4gZS5jbGFzc0xpc3R8fG5ldyBFNHQoZSl9ZnVuY3Rpb24gRTR0KGUpe3RoaXMuX25vZGU9ZSx0aGlzLl9uYW1lcz1NNHQoZS5nZXRBdHRyaWJ1dGUoImNsYXNzIil8fCIiKX1mdW5jdGlvbiBUNHQoZSx0KXtmb3IodmFyIHI9X1ooZSksbj0tMSxpPXQubGVuZ3RoOysrbjxpOylyLmFkZCh0W25dKX1mdW5jdGlvbiBDNHQoZSx0KXtmb3IodmFyIHI9X1ooZSksbj0tMSxpPXQubGVuZ3RoOysrbjxpOylyLnJlbW92ZSh0W25dKX1mdW5jdGlvbiBSQWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7VDR0KHRoaXMsZSl9fWZ1bmN0aW9uIE5BZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtDNHQodGhpcyxlKX19ZnVuY3Rpb24gREFlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7KHQuYXBwbHkodGhpcyxhcmd1bWVudHMpP1Q0dDpDNHQpKHRoaXMsZSl9fWZ1bmN0aW9uIEE0dChlLHQpe3ZhciByPU00dChlKyIiKTtpZihhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcih2YXIgbj1fWih0aGlzLm5vZGUoKSksaT0tMSxvPXIubGVuZ3RoOysraTxvOylpZighbi5jb250YWlucyhyW2ldKSlyZXR1cm4hMTtyZXR1cm4hMH1yZXR1cm4gdGhpcy5lYWNoKCh0eXBlb2YgdD09ImZ1bmN0aW9uIj9EQWU6dD9SQWU6TkFlKShyLHQpKX12YXIgUDR0PU0oKCk9PntFNHQucHJvdG90eXBlPXthZGQ6ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy5fbmFtZXMuaW5kZXhPZihlKTt0PDAmJih0aGlzLl9uYW1lcy5wdXNoKGUpLHRoaXMuX25vZGUuc2V0QXR0cmlidXRlKCJjbGFzcyIsdGhpcy5fbmFtZXMuam9pbigiICIpKSl9LHJlbW92ZTpmdW5jdGlvbihlKXt2YXIgdD10aGlzLl9uYW1lcy5pbmRleE9mKGUpO3Q+PTAmJih0aGlzLl9uYW1lcy5zcGxpY2UodCwxKSx0aGlzLl9ub2RlLnNldEF0dHJpYnV0ZSgiY2xhc3MiLHRoaXMuX25hbWVzLmpvaW4oIiAiKSkpfSxjb250YWluczpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fbmFtZXMuaW5kZXhPZihlKT49MH19fSk7ZnVuY3Rpb24gT0FlKCl7dGhpcy50ZXh0Q29udGVudD0iIn1mdW5jdGlvbiB6QWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy50ZXh0Q29udGVudD1lfX1mdW5jdGlvbiBGQWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyk7dGhpcy50ZXh0Q29udGVudD10PT1udWxsPyIiOnR9fWZ1bmN0aW9uIEk0dChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2goZT09bnVsbD9PQWU6KHR5cGVvZiBlPT0iZnVuY3Rpb24iP0ZBZTp6QWUpKGUpKTp0aGlzLm5vZGUoKS50ZXh0Q29udGVudH12YXIgTDR0PU0oKCk9Pnt9KTtmdW5jdGlvbiBCQWUoKXt0aGlzLmlubmVySFRNTD0iIn1mdW5jdGlvbiBIQWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5pbm5lckhUTUw9ZX19ZnVuY3Rpb24gVkFlKGUpe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PWUuYXBwbHkodGhpcyxhcmd1bWVudHMpO3RoaXMuaW5uZXJIVE1MPXQ9PW51bGw/IiI6dH19ZnVuY3Rpb24gazR0KGUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaChlPT1udWxsP0JBZToodHlwZW9mIGU9PSJmdW5jdGlvbiI/VkFlOkhBZSkoZSkpOnRoaXMubm9kZSgpLmlubmVySFRNTH12YXIgUjR0PU0oKCk9Pnt9KTtmdW5jdGlvbiBVQWUoKXt0aGlzLm5leHRTaWJsaW5nJiZ0aGlzLnBhcmVudE5vZGUuYXBwZW5kQ2hpbGQodGhpcyl9ZnVuY3Rpb24gTjR0KCl7cmV0dXJuIHRoaXMuZWFjaChVQWUpfXZhciBENHQ9TSgoKT0+e30pO2Z1bmN0aW9uIHFBZSgpe3RoaXMucHJldmlvdXNTaWJsaW5nJiZ0aGlzLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKHRoaXMsdGhpcy5wYXJlbnROb2RlLmZpcnN0Q2hpbGQpfWZ1bmN0aW9uIE80dCgpe3JldHVybiB0aGlzLmVhY2gocUFlKX12YXIgejR0PU0oKCk9Pnt9KTtmdW5jdGlvbiBGNHQoZSl7dmFyIHQ9dHlwZW9mIGU9PSJmdW5jdGlvbiI/ZTpkTihlKTtyZXR1cm4gdGhpcy5zZWxlY3QoZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5hcHBlbmRDaGlsZCh0LmFwcGx5KHRoaXMsYXJndW1lbnRzKSl9KX12YXIgQjR0PU0oKCk9PntmWigpfSk7ZnVuY3Rpb24gR0FlKCl7cmV0dXJuIG51bGx9ZnVuY3Rpb24gSDR0KGUsdCl7dmFyIHI9dHlwZW9mIGU9PSJmdW5jdGlvbiI/ZTpkTihlKSxuPXQ9PW51bGw/R0FlOnR5cGVvZiB0PT0iZnVuY3Rpb24iP3Q6bU4odCk7cmV0dXJuIHRoaXMuc2VsZWN0KGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuaW5zZXJ0QmVmb3JlKHIuYXBwbHkodGhpcyxhcmd1bWVudHMpLG4uYXBwbHkodGhpcyxhcmd1bWVudHMpfHxudWxsKX0pfXZhciBWNHQ9TSgoKT0+e2ZaKCk7cFooKX0pO2Z1bmN0aW9uIFdBZSgpe3ZhciBlPXRoaXMucGFyZW50Tm9kZTtlJiZlLnJlbW92ZUNoaWxkKHRoaXMpfWZ1bmN0aW9uIFU0dCgpe3JldHVybiB0aGlzLmVhY2goV0FlKX12YXIgcTR0PU0oKCk9Pnt9KTtmdW5jdGlvbiBZQWUoKXt2YXIgZT10aGlzLmNsb25lTm9kZSghMSksdD10aGlzLnBhcmVudE5vZGU7cmV0dXJuIHQ/dC5pbnNlcnRCZWZvcmUoZSx0aGlzLm5leHRTaWJsaW5nKTplfWZ1bmN0aW9uIGpBZSgpe3ZhciBlPXRoaXMuY2xvbmVOb2RlKCEwKSx0PXRoaXMucGFyZW50Tm9kZTtyZXR1cm4gdD90Lmluc2VydEJlZm9yZShlLHRoaXMubmV4dFNpYmxpbmcpOmV9ZnVuY3Rpb24gRzR0KGUpe3JldHVybiB0aGlzLnNlbGVjdChlP2pBZTpZQWUpfXZhciBXNHQ9TSgoKT0+e30pO2Z1bmN0aW9uIFk0dChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLnByb3BlcnR5KCJfX2RhdGFfXyIsZSk6dGhpcy5ub2RlKCkuX19kYXRhX199dmFyIGo0dD1NKCgpPT57fSk7ZnVuY3Rpb24gWEFlKGUsdCxyKXtyZXR1cm4gZT1LNHQoZSx0LHIpLGZ1bmN0aW9uKG4pe3ZhciBpPW4ucmVsYXRlZFRhcmdldDsoIWl8fGkhPT10aGlzJiYhKGkuY29tcGFyZURvY3VtZW50UG9zaXRpb24odGhpcykmOCkpJiZlLmNhbGwodGhpcyxuKX19ZnVuY3Rpb24gSzR0KGUsdCxyKXtyZXR1cm4gZnVuY3Rpb24obil7dmFyIGk9c247c249bjt0cnl7ZS5jYWxsKHRoaXMsdGhpcy5fX2RhdGFfXyx0LHIpfWZpbmFsbHl7c249aX19fWZ1bmN0aW9uICRBZShlKXtyZXR1cm4gZS50cmltKCkuc3BsaXQoL158XHMrLykubWFwKGZ1bmN0aW9uKHQpe3ZhciByPSIiLG49dC5pbmRleE9mKCIuIik7cmV0dXJuIG4+PTAmJihyPXQuc2xpY2UobisxKSx0PXQuc2xpY2UoMCxuKSkse3R5cGU6dCxuYW1lOnJ9fSl9ZnVuY3Rpb24gS0FlKGUpe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PXRoaXMuX19vbjtpZighIXQpe2Zvcih2YXIgcj0wLG49LTEsaT10Lmxlbmd0aCxvO3I8aTsrK3Ipbz10W3JdLCghZS50eXBlfHxvLnR5cGU9PT1lLnR5cGUpJiZvLm5hbWU9PT1lLm5hbWU/dGhpcy5yZW1vdmVFdmVudExpc3RlbmVyKG8udHlwZSxvLmxpc3RlbmVyLG8uY2FwdHVyZSk6dFsrK25dPW87KytuP3QubGVuZ3RoPW46ZGVsZXRlIHRoaXMuX19vbn19fWZ1bmN0aW9uIFpBZShlLHQscil7dmFyIG49JDR0Lmhhc093blByb3BlcnR5KGUudHlwZSk/WEFlOks0dDtyZXR1cm4gZnVuY3Rpb24oaSxvLGEpe3ZhciBzPXRoaXMuX19vbixsLGM9bih0LG8sYSk7aWYocyl7Zm9yKHZhciB1PTAsaD1zLmxlbmd0aDt1PGg7Kyt1KWlmKChsPXNbdV0pLnR5cGU9PT1lLnR5cGUmJmwubmFtZT09PWUubmFtZSl7dGhpcy5yZW1vdmVFdmVudExpc3RlbmVyKGwudHlwZSxsLmxpc3RlbmVyLGwuY2FwdHVyZSksdGhpcy5hZGRFdmVudExpc3RlbmVyKGwudHlwZSxsLmxpc3RlbmVyPWMsbC5jYXB0dXJlPXIpLGwudmFsdWU9dDtyZXR1cm59fXRoaXMuYWRkRXZlbnRMaXN0ZW5lcihlLnR5cGUsYyxyKSxsPXt0eXBlOmUudHlwZSxuYW1lOmUubmFtZSx2YWx1ZTp0LGxpc3RlbmVyOmMsY2FwdHVyZTpyfSxzP3MucHVzaChsKTp0aGlzLl9fb249W2xdfX1mdW5jdGlvbiBaNHQoZSx0LHIpe3ZhciBuPSRBZShlKyIiKSxpLG89bi5sZW5ndGgsYTtpZihhcmd1bWVudHMubGVuZ3RoPDIpe3ZhciBzPXRoaXMubm9kZSgpLl9fb247aWYocyl7Zm9yKHZhciBsPTAsYz1zLmxlbmd0aCx1O2w8YzsrK2wpZm9yKGk9MCx1PXNbbF07aTxvOysraSlpZigoYT1uW2ldKS50eXBlPT09dS50eXBlJiZhLm5hbWU9PT11Lm5hbWUpcmV0dXJuIHUudmFsdWV9cmV0dXJufWZvcihzPXQ/WkFlOktBZSxyPT1udWxsJiYocj0hMSksaT0wO2k8bzsrK2kpdGhpcy5lYWNoKHMobltpXSx0LHIpKTtyZXR1cm4gdGhpc31mdW5jdGlvbiB5TihlLHQscixuKXt2YXIgaT1zbjtlLnNvdXJjZUV2ZW50PXNuLHNuPWU7dHJ5e3JldHVybiB0LmFwcGx5KHIsbil9ZmluYWxseXtzbj1pfX12YXIgJDR0LHNuLFg0dCx2Tj1NKCgpPT57JDR0PXt9LHNuPW51bGw7dHlwZW9mIGRvY3VtZW50IT0idW5kZWZpbmVkIiYmKFg0dD1kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsIm9ubW91c2VlbnRlciJpbiBYNHR8fCgkNHQ9e21vdXNlZW50ZXI6Im1vdXNlb3ZlciIsbW91c2VsZWF2ZToibW91c2VvdXQifSkpfSk7ZnVuY3Rpb24gSjR0KGUsdCxyKXt2YXIgbj1fTihlKSxpPW4uQ3VzdG9tRXZlbnQ7dHlwZW9mIGk9PSJmdW5jdGlvbiI/aT1uZXcgaSh0LHIpOihpPW4uZG9jdW1lbnQuY3JlYXRlRXZlbnQoIkV2ZW50Iikscj8oaS5pbml0RXZlbnQodCxyLmJ1YmJsZXMsci5jYW5jZWxhYmxlKSxpLmRldGFpbD1yLmRldGFpbCk6aS5pbml0RXZlbnQodCwhMSwhMSkpLGUuZGlzcGF0Y2hFdmVudChpKX1mdW5jdGlvbiBKQWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gSjR0KHRoaXMsZSx0KX19ZnVuY3Rpb24gUUFlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIEo0dCh0aGlzLGUsdC5hcHBseSh0aGlzLGFyZ3VtZW50cykpfX1mdW5jdGlvbiBRNHQoZSx0KXtyZXR1cm4gdGhpcy5lYWNoKCh0eXBlb2YgdD09ImZ1bmN0aW9uIj9RQWU6SkFlKShlLHQpKX12YXIgdFB0PU0oKCk9PntnWigpfSk7ZnVuY3Rpb24gZGkoZSx0KXt0aGlzLl9ncm91cHM9ZSx0aGlzLl9wYXJlbnRzPXR9ZnVuY3Rpb24gdDRlKCl7cmV0dXJuIG5ldyBkaShbW2RvY3VtZW50LmRvY3VtZW50RWxlbWVudF1dLHlaKX12YXIgeVosZWY9TSgoKT0+e0RBdCgpO0JBdCgpO3FBdCgpOyRBdCgpO21aKCk7WkF0KCk7UUF0KCk7ZTR0KCk7bjR0KCk7bzR0KCk7czR0KCk7YzR0KCk7aDR0KCk7cDR0KCk7bTR0KCk7XzR0KCk7djR0KCk7YjR0KCk7UzR0KCk7UDR0KCk7TDR0KCk7UjR0KCk7RDR0KCk7ejR0KCk7QjR0KCk7VjR0KCk7cTR0KCk7VzR0KCk7ajR0KCk7dk4oKTt0UHQoKTt5Wj1bbnVsbF07ZGkucHJvdG90eXBlPXQ0ZS5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOmRpLHNlbGVjdDpOQXQsc2VsZWN0QWxsOkZBdCxmaWx0ZXI6VUF0LGRhdGE6WEF0LGVudGVyOkdBdCxleGl0OktBdCxqb2luOkpBdCxtZXJnZTp0NHQsb3JkZXI6cjR0LHNvcnQ6aTR0LGNhbGw6YTR0LG5vZGVzOmw0dCxub2RlOnU0dCxzaXplOmY0dCxlbXB0eTpkNHQsZWFjaDpnNHQsYXR0cjp5NHQsc3R5bGU6eDR0LHByb3BlcnR5Onc0dCxjbGFzc2VkOkE0dCx0ZXh0Okk0dCxodG1sOms0dCxyYWlzZTpONHQsbG93ZXI6TzR0LGFwcGVuZDpGNHQsaW5zZXJ0Okg0dCxyZW1vdmU6VTR0LGNsb25lOkc0dCxkYXR1bTpZNHQsb246WjR0LGRpc3BhdGNoOlE0dH19KTtmdW5jdGlvbiBFeShlKXtyZXR1cm4gdHlwZW9mIGU9PSJzdHJpbmciP25ldyBkaShbW2RvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoZSldXSxbZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50XSk6bmV3IGRpKFtbZV1dLHlaKX12YXIgZVB0PU0oKCk9PntlZigpfSk7ZnVuY3Rpb24geE4oKXtmb3IodmFyIGU9c24sdDt0PWUuc291cmNlRXZlbnQ7KWU9dDtyZXR1cm4gZX12YXIgdlo9TSgoKT0+e3ZOKCl9KTtmdW5jdGlvbiBiTihlLHQpe3ZhciByPWUub3duZXJTVkdFbGVtZW50fHxlO2lmKHIuY3JlYXRlU1ZHUG9pbnQpe3ZhciBuPXIuY3JlYXRlU1ZHUG9pbnQoKTtyZXR1cm4gbi54PXQuY2xpZW50WCxuLnk9dC5jbGllbnRZLG49bi5tYXRyaXhUcmFuc2Zvcm0oZS5nZXRTY3JlZW5DVE0oKS5pbnZlcnNlKCkpLFtuLngsbi55XX12YXIgaT1lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO3JldHVyblt0LmNsaWVudFgtaS5sZWZ0LWUuY2xpZW50TGVmdCx0LmNsaWVudFktaS50b3AtZS5jbGllbnRUb3BdfXZhciB4Wj1NKCgpPT57fSk7ZnVuY3Rpb24gYlooZSl7dmFyIHQ9eE4oKTtyZXR1cm4gdC5jaGFuZ2VkVG91Y2hlcyYmKHQ9dC5jaGFuZ2VkVG91Y2hlc1swXSksYk4oZSx0KX12YXIgclB0PU0oKCk9Pnt2WigpO3haKCl9KTtmdW5jdGlvbiB3WihlLHQscil7YXJndW1lbnRzLmxlbmd0aDwzJiYocj10LHQ9eE4oKS5jaGFuZ2VkVG91Y2hlcyk7Zm9yKHZhciBuPTAsaT10P3QubGVuZ3RoOjAsbztuPGk7KytuKWlmKChvPXRbbl0pLmlkZW50aWZpZXI9PT1yKXJldHVybiBiTihlLG8pO3JldHVybiBudWxsfXZhciBuUHQ9TSgoKT0+e3ZaKCk7eFooKX0pO3ZhciB3Tj1NKCgpPT57clB0KCk7ZVB0KCk7blB0KCk7dk4oKX0pO2Z1bmN0aW9uIFNOKCl7c24uc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCl9ZnVuY3Rpb24gZmcoKXtzbi5wcmV2ZW50RGVmYXVsdCgpLHNuLnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpfXZhciBTWj1NKCgpPT57d04oKX0pO2Z1bmN0aW9uIE1OKGUpe3ZhciB0PWUuZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LHI9RXkoZSkub24oImRyYWdzdGFydC5kcmFnIixmZywhMCk7Im9uc2VsZWN0c3RhcnQiaW4gdD9yLm9uKCJzZWxlY3RzdGFydC5kcmFnIixmZywhMCk6KHQuX19ub3NlbGVjdD10LnN0eWxlLk1velVzZXJTZWxlY3QsdC5zdHlsZS5Nb3pVc2VyU2VsZWN0PSJub25lIil9ZnVuY3Rpb24gRU4oZSx0KXt2YXIgcj1lLmRvY3VtZW50LmRvY3VtZW50RWxlbWVudCxuPUV5KGUpLm9uKCJkcmFnc3RhcnQuZHJhZyIsbnVsbCk7dCYmKG4ub24oImNsaWNrLmRyYWciLGZnLCEwKSxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7bi5vbigiY2xpY2suZHJhZyIsbnVsbCl9LDApKSwib25zZWxlY3RzdGFydCJpbiByP24ub24oInNlbGVjdHN0YXJ0LmRyYWciLG51bGwpOihyLnN0eWxlLk1velVzZXJTZWxlY3Q9ci5fX25vc2VsZWN0LGRlbGV0ZSByLl9fbm9zZWxlY3QpfXZhciBNWj1NKCgpPT57d04oKTtTWigpfSk7ZnVuY3Rpb24geVQoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fXZhciBpUHQ9TSgoKT0+e30pO2Z1bmN0aW9uIHZUKGUsdCxyLG4saSxvLGEscyxsLGMpe3RoaXMudGFyZ2V0PWUsdGhpcy50eXBlPXQsdGhpcy5zdWJqZWN0PXIsdGhpcy5pZGVudGlmaWVyPW4sdGhpcy5hY3RpdmU9aSx0aGlzLng9byx0aGlzLnk9YSx0aGlzLmR4PXMsdGhpcy5keT1sLHRoaXMuXz1jfXZhciBvUHQ9TSgoKT0+e3ZULnByb3RvdHlwZS5vbj1mdW5jdGlvbigpe3ZhciBlPXRoaXMuXy5vbi5hcHBseSh0aGlzLl8sYXJndW1lbnRzKTtyZXR1cm4gZT09PXRoaXMuXz90aGlzOmV9fSk7ZnVuY3Rpb24gZTRlKCl7cmV0dXJuIXNuLmJ1dHRvbn1mdW5jdGlvbiByNGUoKXtyZXR1cm4gdGhpcy5wYXJlbnROb2RlfWZ1bmN0aW9uIG40ZShlKXtyZXR1cm4gZT09bnVsbD97eDpzbi54LHk6c24ueX06ZX1mdW5jdGlvbiBpNGUoKXtyZXR1cm4ib250b3VjaHN0YXJ0ImluIHRoaXN9ZnVuY3Rpb24gYVB0KCl7dmFyIGU9ZTRlLHQ9cjRlLHI9bjRlLG49aTRlLGk9e30sbz1sWigic3RhcnQiLCJkcmFnIiwiZW5kIiksYT0wLHMsbCxjLHUsaD0wO2Z1bmN0aW9uIGYoUyl7Uy5vbigibW91c2Vkb3duLmRyYWciLHApLmZpbHRlcihuKS5vbigidG91Y2hzdGFydC5kcmFnIixfKS5vbigidG91Y2htb3ZlLmRyYWciLHkpLm9uKCJ0b3VjaGVuZC5kcmFnIHRvdWNoY2FuY2VsLmRyYWciLHgpLnN0eWxlKCJ0b3VjaC1hY3Rpb24iLCJub25lIikuc3R5bGUoIi13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvciIsInJnYmEoMCwwLDAsMCkiKX1mdW5jdGlvbiBwKCl7aWYoISh1fHwhZS5hcHBseSh0aGlzLGFyZ3VtZW50cykpKXt2YXIgUz1iKCJtb3VzZSIsdC5hcHBseSh0aGlzLGFyZ3VtZW50cyksYlosdGhpcyxhcmd1bWVudHMpOyFTfHwoRXkoc24udmlldykub24oIm1vdXNlbW92ZS5kcmFnIixkLCEwKS5vbigibW91c2V1cC5kcmFnIixnLCEwKSxNTihzbi52aWV3KSxTTigpLGM9ITEscz1zbi5jbGllbnRYLGw9c24uY2xpZW50WSxTKCJzdGFydCIpKX19ZnVuY3Rpb24gZCgpe2lmKGZnKCksIWMpe3ZhciBTPXNuLmNsaWVudFgtcyxDPXNuLmNsaWVudFktbDtjPVMqUytDKkM+aH1pLm1vdXNlKCJkcmFnIil9ZnVuY3Rpb24gZygpe0V5KHNuLnZpZXcpLm9uKCJtb3VzZW1vdmUuZHJhZyBtb3VzZXVwLmRyYWciLG51bGwpLEVOKHNuLnZpZXcsYyksZmcoKSxpLm1vdXNlKCJlbmQiKX1mdW5jdGlvbiBfKCl7aWYoISFlLmFwcGx5KHRoaXMsYXJndW1lbnRzKSl7dmFyIFM9c24uY2hhbmdlZFRvdWNoZXMsQz10LmFwcGx5KHRoaXMsYXJndW1lbnRzKSxQPVMubGVuZ3RoLGssTztmb3Ioaz0wO2s8UDsrK2spKE89YihTW2tdLmlkZW50aWZpZXIsQyx3Wix0aGlzLGFyZ3VtZW50cykpJiYoU04oKSxPKCJzdGFydCIpKX19ZnVuY3Rpb24geSgpe3ZhciBTPXNuLmNoYW5nZWRUb3VjaGVzLEM9Uy5sZW5ndGgsUCxrO2ZvcihQPTA7UDxDOysrUCkoaz1pW1NbUF0uaWRlbnRpZmllcl0pJiYoZmcoKSxrKCJkcmFnIikpfWZ1bmN0aW9uIHgoKXt2YXIgUz1zbi5jaGFuZ2VkVG91Y2hlcyxDPVMubGVuZ3RoLFAsaztmb3IodSYmY2xlYXJUaW1lb3V0KHUpLHU9c2V0VGltZW91dChmdW5jdGlvbigpe3U9bnVsbH0sNTAwKSxQPTA7UDxDOysrUCkoaz1pW1NbUF0uaWRlbnRpZmllcl0pJiYoU04oKSxrKCJlbmQiKSl9ZnVuY3Rpb24gYihTLEMsUCxrLE8pe3ZhciBEPVAoQyxTKSxCLEksTCxSPW8uY29weSgpO2lmKCEheU4obmV3IHZUKGYsImJlZm9yZXN0YXJ0IixCLFMsYSxEWzBdLERbMV0sMCwwLFIpLGZ1bmN0aW9uKCl7cmV0dXJuKHNuLnN1YmplY3Q9Qj1yLmFwcGx5KGssTykpPT1udWxsPyExOihJPUIueC1EWzBdfHwwLEw9Qi55LURbMV18fDAsITApfSkpcmV0dXJuIGZ1bmN0aW9uIEYoeil7dmFyIFU9RCxXO3N3aXRjaCh6KXtjYXNlInN0YXJ0IjppW1NdPUYsVz1hKys7YnJlYWs7Y2FzZSJlbmQiOmRlbGV0ZSBpW1NdLC0tYTtjYXNlImRyYWciOkQ9UChDLFMpLFc9YTticmVha315TihuZXcgdlQoZix6LEIsUyxXLERbMF0rSSxEWzFdK0wsRFswXS1VWzBdLERbMV0tVVsxXSxSKSxSLmFwcGx5LFIsW3osayxPXSl9fXJldHVybiBmLmZpbHRlcj1mdW5jdGlvbihTKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2YgUz09ImZ1bmN0aW9uIj9TOnlUKCEhUyksZik6ZX0sZi5jb250YWluZXI9ZnVuY3Rpb24oUyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9dHlwZW9mIFM9PSJmdW5jdGlvbiI/Uzp5VChTKSxmKTp0fSxmLnN1YmplY3Q9ZnVuY3Rpb24oUyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9dHlwZW9mIFM9PSJmdW5jdGlvbiI/Uzp5VChTKSxmKTpyfSxmLnRvdWNoYWJsZT1mdW5jdGlvbihTKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj10eXBlb2YgUz09ImZ1bmN0aW9uIj9TOnlUKCEhUyksZik6bn0sZi5vbj1mdW5jdGlvbigpe3ZhciBTPW8ub24uYXBwbHkobyxhcmd1bWVudHMpO3JldHVybiBTPT09bz9mOlN9LGYuY2xpY2tEaXN0YW5jZT1mdW5jdGlvbihTKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaD0oUz0rUykqUyxmKTpNYXRoLnNxcnQoaCl9LGZ9dmFyIHNQdD1NKCgpPT57UkF0KCk7d04oKTtNWigpO1NaKCk7aVB0KCk7b1B0KCl9KTt2YXIgbFB0PU0oKCk9PntzUHQoKTtNWigpfSk7ZnVuY3Rpb24gdVB0KGUpe3JldHVybiBuZXcgRnVuY3Rpb24oImQiLCJyZXR1cm4geyIrZS5tYXAoZnVuY3Rpb24odCxyKXtyZXR1cm4gSlNPTi5zdHJpbmdpZnkodCkrIjogZFsiK3IrIl0ifSkuam9pbigiLCIpKyJ9Iil9ZnVuY3Rpb24gbzRlKGUsdCl7dmFyIHI9dVB0KGUpO3JldHVybiBmdW5jdGlvbihuLGkpe3JldHVybiB0KHIobiksaSxlKX19ZnVuY3Rpb24gYTRlKGUpe3ZhciB0PU9iamVjdC5jcmVhdGUobnVsbCkscj1bXTtyZXR1cm4gZS5mb3JFYWNoKGZ1bmN0aW9uKG4pe2Zvcih2YXIgaSBpbiBuKWkgaW4gdHx8ci5wdXNoKHRbaV09aSl9KSxyfWZ1bmN0aW9uIHcyKGUpe3ZhciB0PW5ldyBSZWdFeHAoJ1siJytlK2AKXHJdYCkscj1lLmNoYXJDb2RlQXQoMCk7ZnVuY3Rpb24gbihjLHUpe3ZhciBoLGYscD1pKGMsZnVuY3Rpb24oZCxnKXtpZihoKXJldHVybiBoKGQsZy0xKTtmPWQsaD11P280ZShkLHUpOnVQdChkKX0pO3JldHVybiBwLmNvbHVtbnM9Znx8W10scH1mdW5jdGlvbiBpKGMsdSl7dmFyIGg9W10sZj1jLmxlbmd0aCxwPTAsZD0wLGcsXz1mPD0wLHk9ITE7Yy5jaGFyQ29kZUF0KGYtMSk9PT14VCYmLS1mLGMuY2hhckNvZGVBdChmLTEpPT09Q1omJi0tZjtmdW5jdGlvbiB4KCl7aWYoXylyZXR1cm4gRVo7aWYoeSlyZXR1cm4geT0hMSxjUHQ7dmFyIFMsQz1wLFA7aWYoYy5jaGFyQ29kZUF0KEMpPT09VFope2Zvcig7cCsrPGYmJmMuY2hhckNvZGVBdChwKSE9PVRafHxjLmNoYXJDb2RlQXQoKytwKT09PVRaOyk7cmV0dXJuKFM9cCk+PWY/Xz0hMDooUD1jLmNoYXJDb2RlQXQocCsrKSk9PT14VD95PSEwOlA9PT1DWiYmKHk9ITAsYy5jaGFyQ29kZUF0KHApPT09eFQmJisrcCksYy5zbGljZShDKzEsUy0xKS5yZXBsYWNlKC8iIi9nLCciJyl9Zm9yKDtwPGY7KXtpZigoUD1jLmNoYXJDb2RlQXQoUz1wKyspKT09PXhUKXk9ITA7ZWxzZSBpZihQPT09Q1opeT0hMCxjLmNoYXJDb2RlQXQocCk9PT14VCYmKytwO2Vsc2UgaWYoUCE9PXIpY29udGludWU7cmV0dXJuIGMuc2xpY2UoQyxTKX1yZXR1cm4gXz0hMCxjLnNsaWNlKEMsZil9Zm9yKDsoZz14KCkpIT09RVo7KXtmb3IodmFyIGI9W107ZyE9PWNQdCYmZyE9PUVaOyliLnB1c2goZyksZz14KCk7dSYmKGI9dShiLGQrKykpPT1udWxsfHxoLnB1c2goYil9cmV0dXJuIGh9ZnVuY3Rpb24gbyhjLHUpe3JldHVybiB1PT1udWxsJiYodT1hNGUoYykpLFt1Lm1hcChsKS5qb2luKGUpXS5jb25jYXQoYy5tYXAoZnVuY3Rpb24oaCl7cmV0dXJuIHUubWFwKGZ1bmN0aW9uKGYpe3JldHVybiBsKGhbZl0pfSkuam9pbihlKX0pKS5qb2luKGAKYCl9ZnVuY3Rpb24gYShjKXtyZXR1cm4gYy5tYXAocykuam9pbihgCmApfWZ1bmN0aW9uIHMoYyl7cmV0dXJuIGMubWFwKGwpLmpvaW4oZSl9ZnVuY3Rpb24gbChjKXtyZXR1cm4gYz09bnVsbD8iIjp0LnRlc3QoYys9IiIpPyciJytjLnJlcGxhY2UoLyIvZywnIiInKSsnIic6Y31yZXR1cm57cGFyc2U6bixwYXJzZVJvd3M6aSxmb3JtYXQ6byxmb3JtYXRSb3dzOmF9fXZhciBjUHQsRVosVFoseFQsQ1osVE49TSgoKT0+e2NQdD17fSxFWj17fSxUWj0zNCx4VD0xMCxDWj0xM30pO3ZhciBDTixoUHQsZlB0LHBQdCxkUHQsbVB0PU0oKCk9PntUTigpO0NOPXcyKCIsIiksaFB0PUNOLnBhcnNlLGZQdD1DTi5wYXJzZVJvd3MscFB0PUNOLmZvcm1hdCxkUHQ9Q04uZm9ybWF0Um93c30pO3ZhciBBTixnUHQsX1B0LHlQdCx2UHQseFB0PU0oKCk9PntUTigpO0FOPXcyKCIJIiksZ1B0PUFOLnBhcnNlLF9QdD1BTi5wYXJzZVJvd3MseVB0PUFOLmZvcm1hdCx2UHQ9QU4uZm9ybWF0Um93c30pO3ZhciBiUHQ9TSgoKT0+e1ROKCk7bVB0KCk7eFB0KCl9KTtmdW5jdGlvbiB3UHQoZSl7cmV0dXJuK2V9dmFyIFNQdD1NKCgpPT57fSk7ZnVuY3Rpb24gTVB0KGUpe3JldHVybiBlKmV9ZnVuY3Rpb24gRVB0KGUpe3JldHVybiBlKigyLWUpfWZ1bmN0aW9uIEFaKGUpe3JldHVybigoZSo9Mik8PTE/ZSplOi0tZSooMi1lKSsxKS8yfXZhciBUUHQ9TSgoKT0+e30pO2Z1bmN0aW9uIENQdChlKXtyZXR1cm4gZSplKmV9ZnVuY3Rpb24gQVB0KGUpe3JldHVybi0tZSplKmUrMX1mdW5jdGlvbiBQWihlKXtyZXR1cm4oKGUqPTIpPD0xP2UqZSplOihlLT0yKSplKmUrMikvMn12YXIgUFB0PU0oKCk9Pnt9KTt2YXIgSVosSVB0LExQdCxMWixrUHQ9TSgoKT0+e0laPTMsSVB0PWZ1bmN0aW9uIGUodCl7dD0rdDtmdW5jdGlvbiByKG4pe3JldHVybiBNYXRoLnBvdyhuLHQpfXJldHVybiByLmV4cG9uZW50PWUscn0oSVopLExQdD1mdW5jdGlvbiBlKHQpe3Q9K3Q7ZnVuY3Rpb24gcihuKXtyZXR1cm4gMS1NYXRoLnBvdygxLW4sdCl9cmV0dXJuIHIuZXhwb25lbnQ9ZSxyfShJWiksTFo9ZnVuY3Rpb24gZSh0KXt0PSt0O2Z1bmN0aW9uIHIobil7cmV0dXJuKChuKj0yKTw9MT9NYXRoLnBvdyhuLHQpOjItTWF0aC5wb3coMi1uLHQpKS8yfXJldHVybiByLmV4cG9uZW50PWUscn0oSVopfSk7ZnVuY3Rpb24gRFB0KGUpe3JldHVybiAxLU1hdGguY29zKGUqTlB0KX1mdW5jdGlvbiBPUHQoZSl7cmV0dXJuIE1hdGguc2luKGUqTlB0KX1mdW5jdGlvbiBrWihlKXtyZXR1cm4oMS1NYXRoLmNvcyhSUHQqZSkpLzJ9dmFyIFJQdCxOUHQselB0PU0oKCk9PntSUHQ9TWF0aC5QSSxOUHQ9UlB0LzJ9KTtmdW5jdGlvbiBGUHQoZSl7cmV0dXJuIE1hdGgucG93KDIsMTAqZS0xMCl9ZnVuY3Rpb24gQlB0KGUpe3JldHVybiAxLU1hdGgucG93KDIsLTEwKmUpfWZ1bmN0aW9uIFJaKGUpe3JldHVybigoZSo9Mik8PTE/TWF0aC5wb3coMiwxMCplLTEwKToyLU1hdGgucG93KDIsMTAtMTAqZSkpLzJ9dmFyIEhQdD1NKCgpPT57fSk7ZnVuY3Rpb24gVlB0KGUpe3JldHVybiAxLU1hdGguc3FydCgxLWUqZSl9ZnVuY3Rpb24gVVB0KGUpe3JldHVybiBNYXRoLnNxcnQoMS0gLS1lKmUpfWZ1bmN0aW9uIE5aKGUpe3JldHVybigoZSo9Mik8PTE/MS1NYXRoLnNxcnQoMS1lKmUpOk1hdGguc3FydCgxLShlLT0yKSplKSsxKS8yfXZhciBxUHQ9TSgoKT0+e30pO2Z1bmN0aW9uIEdQdChlKXtyZXR1cm4gMS1TMigxLWUpfWZ1bmN0aW9uIFMyKGUpe3JldHVybihlPStlKTxEWj9QTiplKmU6ZTxsNGU/UE4qKGUtPXM0ZSkqZStjNGU6ZTxoNGU/UE4qKGUtPXU0ZSkqZStmNGU6UE4qKGUtPXA0ZSkqZStkNGV9ZnVuY3Rpb24gV1B0KGUpe3JldHVybigoZSo9Mik8PTE/MS1TMigxLWUpOlMyKGUtMSkrMSkvMn12YXIgRFosczRlLGw0ZSxjNGUsdTRlLGg0ZSxmNGUscDRlLGQ0ZSxQTixZUHQ9TSgoKT0+e0RaPS4zNjM2MzYzNjM2MzYzNjM2NSxzNGU9Ni8xMSxsNGU9OC8xMSxjNGU9My80LHU0ZT05LzExLGg0ZT0xMC8xMSxmNGU9MTUvMTYscDRlPTIxLzIyLGQ0ZT02My82NCxQTj0xL0RaL0RafSk7dmFyIE9aLGpQdCxYUHQselosJFB0PU0oKCk9PntPWj0xLjcwMTU4LGpQdD1mdW5jdGlvbiBlKHQpe3Q9K3Q7ZnVuY3Rpb24gcihuKXtyZXR1cm4gbipuKigodCsxKSpuLXQpfXJldHVybiByLm92ZXJzaG9vdD1lLHJ9KE9aKSxYUHQ9ZnVuY3Rpb24gZSh0KXt0PSt0O2Z1bmN0aW9uIHIobil7cmV0dXJuLS1uKm4qKCh0KzEpKm4rdCkrMX1yZXR1cm4gci5vdmVyc2hvb3Q9ZSxyfShPWikselo9ZnVuY3Rpb24gZSh0KXt0PSt0O2Z1bmN0aW9uIHIobil7cmV0dXJuKChuKj0yKTwxP24qbiooKHQrMSkqbi10KToobi09MikqbiooKHQrMSkqbit0KSsyKS8yfXJldHVybiByLm92ZXJzaG9vdD1lLHJ9KE9aKX0pO3ZhciBNMixGWixCWixLUHQsSFosWlB0LEpQdD1NKCgpPT57TTI9MipNYXRoLlBJLEZaPTEsQlo9LjMsS1B0PWZ1bmN0aW9uIGUodCxyKXt2YXIgbj1NYXRoLmFzaW4oMS8odD1NYXRoLm1heCgxLHQpKSkqKHIvPU0yKTtmdW5jdGlvbiBpKG8pe3JldHVybiB0Kk1hdGgucG93KDIsMTAqLS1vKSpNYXRoLnNpbigobi1vKS9yKX1yZXR1cm4gaS5hbXBsaXR1ZGU9ZnVuY3Rpb24obyl7cmV0dXJuIGUobyxyKk0yKX0saS5wZXJpb2Q9ZnVuY3Rpb24obyl7cmV0dXJuIGUodCxvKX0saX0oRlosQlopLEhaPWZ1bmN0aW9uIGUodCxyKXt2YXIgbj1NYXRoLmFzaW4oMS8odD1NYXRoLm1heCgxLHQpKSkqKHIvPU0yKTtmdW5jdGlvbiBpKG8pe3JldHVybiAxLXQqTWF0aC5wb3coMiwtMTAqKG89K28pKSpNYXRoLnNpbigobytuKS9yKX1yZXR1cm4gaS5hbXBsaXR1ZGU9ZnVuY3Rpb24obyl7cmV0dXJuIGUobyxyKk0yKX0saS5wZXJpb2Q9ZnVuY3Rpb24obyl7cmV0dXJuIGUodCxvKX0saX0oRlosQlopLFpQdD1mdW5jdGlvbiBlKHQscil7dmFyIG49TWF0aC5hc2luKDEvKHQ9TWF0aC5tYXgoMSx0KSkpKihyLz1NMik7ZnVuY3Rpb24gaShvKXtyZXR1cm4oKG89byoyLTEpPDA/dCpNYXRoLnBvdygyLDEwKm8pKk1hdGguc2luKChuLW8pL3IpOjItdCpNYXRoLnBvdygyLC0xMCpvKSpNYXRoLnNpbigobitvKS9yKSkvMn1yZXR1cm4gaS5hbXBsaXR1ZGU9ZnVuY3Rpb24obyl7cmV0dXJuIGUobyxyKk0yKX0saS5wZXJpb2Q9ZnVuY3Rpb24obyl7cmV0dXJuIGUodCxvKX0saX0oRlosQlopfSk7dmFyIFFQdD1NKCgpPT57U1B0KCk7VFB0KCk7UFB0KCk7a1B0KCk7elB0KCk7SFB0KCk7cVB0KCk7WVB0KCk7JFB0KCk7SlB0KCl9KTtmdW5jdGlvbiB0NnQoZSx0KXt2YXIgcjtlPT1udWxsJiYoZT0wKSx0PT1udWxsJiYodD0wKTtmdW5jdGlvbiBuKCl7dmFyIGksbz1yLmxlbmd0aCxhLHM9MCxsPTA7Zm9yKGk9MDtpPG87KytpKWE9cltpXSxzKz1hLngsbCs9YS55O2ZvcihzPXMvby1lLGw9bC9vLXQsaT0wO2k8bzsrK2kpYT1yW2ldLGEueC09cyxhLnktPWx9cmV0dXJuIG4uaW5pdGlhbGl6ZT1mdW5jdGlvbihpKXtyPWl9LG4ueD1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT0raSxuKTplfSxuLnk9ZnVuY3Rpb24oaSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9K2ksbik6dH0sbn12YXIgZTZ0PU0oKCk9Pnt9KTtmdW5jdGlvbiBGbihlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIFR5PU0oKCk9Pnt9KTtmdW5jdGlvbiBNdSgpe3JldHVybihNYXRoLnJhbmRvbSgpLS41KSoxZS02fXZhciBJTj1NKCgpPT57fSk7ZnVuY3Rpb24gcjZ0KGUpe3ZhciB0PSt0aGlzLl94LmNhbGwobnVsbCxlKSxyPSt0aGlzLl95LmNhbGwobnVsbCxlKTtyZXR1cm4gbjZ0KHRoaXMuY292ZXIodCxyKSx0LHIsZSl9ZnVuY3Rpb24gbjZ0KGUsdCxyLG4pe2lmKGlzTmFOKHQpfHxpc05hTihyKSlyZXR1cm4gZTt2YXIgaSxvPWUuX3Jvb3QsYT17ZGF0YTpufSxzPWUuX3gwLGw9ZS5feTAsYz1lLl94MSx1PWUuX3kxLGgsZixwLGQsZyxfLHkseDtpZighbylyZXR1cm4gZS5fcm9vdD1hLGU7Zm9yKDtvLmxlbmd0aDspaWYoKGc9dD49KGg9KHMrYykvMikpP3M9aDpjPWgsKF89cj49KGY9KGwrdSkvMikpP2w9Zjp1PWYsaT1vLCEobz1vW3k9Xzw8MXxnXSkpcmV0dXJuIGlbeV09YSxlO2lmKHA9K2UuX3guY2FsbChudWxsLG8uZGF0YSksZD0rZS5feS5jYWxsKG51bGwsby5kYXRhKSx0PT09cCYmcj09PWQpcmV0dXJuIGEubmV4dD1vLGk/aVt5XT1hOmUuX3Jvb3Q9YSxlO2RvIGk9aT9pW3ldPW5ldyBBcnJheSg0KTplLl9yb290PW5ldyBBcnJheSg0KSwoZz10Pj0oaD0ocytjKS8yKSk/cz1oOmM9aCwoXz1yPj0oZj0obCt1KS8yKSk/bD1mOnU9Zjt3aGlsZSgoeT1fPDwxfGcpPT09KHg9KGQ+PWYpPDwxfHA+PWgpKTtyZXR1cm4gaVt4XT1vLGlbeV09YSxlfWZ1bmN0aW9uIGk2dChlKXt2YXIgdCxyLG49ZS5sZW5ndGgsaSxvLGE9bmV3IEFycmF5KG4pLHM9bmV3IEFycmF5KG4pLGw9MS8wLGM9MS8wLHU9LTEvMCxoPS0xLzA7Zm9yKHI9MDtyPG47KytyKWlzTmFOKGk9K3RoaXMuX3guY2FsbChudWxsLHQ9ZVtyXSkpfHxpc05hTihvPSt0aGlzLl95LmNhbGwobnVsbCx0KSl8fChhW3JdPWksc1tyXT1vLGk8bCYmKGw9aSksaT51JiYodT1pKSxvPGMmJihjPW8pLG8+aCYmKGg9bykpO2lmKGw+dXx8Yz5oKXJldHVybiB0aGlzO2Zvcih0aGlzLmNvdmVyKGwsYykuY292ZXIodSxoKSxyPTA7cjxuOysrciluNnQodGhpcyxhW3JdLHNbcl0sZVtyXSk7cmV0dXJuIHRoaXN9dmFyIG82dD1NKCgpPT57fSk7ZnVuY3Rpb24gYTZ0KGUsdCl7aWYoaXNOYU4oZT0rZSl8fGlzTmFOKHQ9K3QpKXJldHVybiB0aGlzO3ZhciByPXRoaXMuX3gwLG49dGhpcy5feTAsaT10aGlzLl94MSxvPXRoaXMuX3kxO2lmKGlzTmFOKHIpKWk9KHI9TWF0aC5mbG9vcihlKSkrMSxvPShuPU1hdGguZmxvb3IodCkpKzE7ZWxzZXtmb3IodmFyIGE9aS1yLHM9dGhpcy5fcm9vdCxsLGM7cj5lfHxlPj1pfHxuPnR8fHQ+PW87KXN3aXRjaChjPSh0PG4pPDwxfGU8cixsPW5ldyBBcnJheSg0KSxsW2NdPXMscz1sLGEqPTIsYyl7Y2FzZSAwOmk9cithLG89bithO2JyZWFrO2Nhc2UgMTpyPWktYSxvPW4rYTticmVhaztjYXNlIDI6aT1yK2Esbj1vLWE7YnJlYWs7Y2FzZSAzOnI9aS1hLG49by1hO2JyZWFrfXRoaXMuX3Jvb3QmJnRoaXMuX3Jvb3QubGVuZ3RoJiYodGhpcy5fcm9vdD1zKX1yZXR1cm4gdGhpcy5feDA9cix0aGlzLl95MD1uLHRoaXMuX3gxPWksdGhpcy5feTE9byx0aGlzfXZhciBzNnQ9TSgoKT0+e30pO2Z1bmN0aW9uIGw2dCgpe3ZhciBlPVtdO3JldHVybiB0aGlzLnZpc2l0KGZ1bmN0aW9uKHQpe2lmKCF0Lmxlbmd0aClkbyBlLnB1c2godC5kYXRhKTt3aGlsZSh0PXQubmV4dCl9KSxlfXZhciBjNnQ9TSgoKT0+e30pO2Z1bmN0aW9uIHU2dChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmNvdmVyKCtlWzBdWzBdLCtlWzBdWzFdKS5jb3ZlcigrZVsxXVswXSwrZVsxXVsxXSk6aXNOYU4odGhpcy5feDApP3ZvaWQgMDpbW3RoaXMuX3gwLHRoaXMuX3kwXSxbdGhpcy5feDEsdGhpcy5feTFdXX12YXIgaDZ0PU0oKCk9Pnt9KTtmdW5jdGlvbiBTbyhlLHQscixuLGkpe3RoaXMubm9kZT1lLHRoaXMueDA9dCx0aGlzLnkwPXIsdGhpcy54MT1uLHRoaXMueTE9aX12YXIgTE49TSgoKT0+e30pO2Z1bmN0aW9uIGY2dChlLHQscil7dmFyIG4saT10aGlzLl94MCxvPXRoaXMuX3kwLGEscyxsLGMsdT10aGlzLl94MSxoPXRoaXMuX3kxLGY9W10scD10aGlzLl9yb290LGQsZztmb3IocCYmZi5wdXNoKG5ldyBTbyhwLGksbyx1LGgpKSxyPT1udWxsP3I9MS8wOihpPWUtcixvPXQtcix1PWUrcixoPXQrcixyKj1yKTtkPWYucG9wKCk7KWlmKCEoIShwPWQubm9kZSl8fChhPWQueDApPnV8fChzPWQueTApPmh8fChsPWQueDEpPGl8fChjPWQueTEpPG8pKWlmKHAubGVuZ3RoKXt2YXIgXz0oYStsKS8yLHk9KHMrYykvMjtmLnB1c2gobmV3IFNvKHBbM10sXyx5LGwsYyksbmV3IFNvKHBbMl0sYSx5LF8sYyksbmV3IFNvKHBbMV0sXyxzLGwseSksbmV3IFNvKHBbMF0sYSxzLF8seSkpLChnPSh0Pj15KTw8MXxlPj1fKSYmKGQ9ZltmLmxlbmd0aC0xXSxmW2YubGVuZ3RoLTFdPWZbZi5sZW5ndGgtMS1nXSxmW2YubGVuZ3RoLTEtZ109ZCl9ZWxzZXt2YXIgeD1lLSt0aGlzLl94LmNhbGwobnVsbCxwLmRhdGEpLGI9dC0rdGhpcy5feS5jYWxsKG51bGwscC5kYXRhKSxTPXgqeCtiKmI7aWYoUzxyKXt2YXIgQz1NYXRoLnNxcnQocj1TKTtpPWUtQyxvPXQtQyx1PWUrQyxoPXQrQyxuPXAuZGF0YX19cmV0dXJuIG59dmFyIHA2dD1NKCgpPT57TE4oKX0pO2Z1bmN0aW9uIGQ2dChlKXtpZihpc05hTih1PSt0aGlzLl94LmNhbGwobnVsbCxlKSl8fGlzTmFOKGg9K3RoaXMuX3kuY2FsbChudWxsLGUpKSlyZXR1cm4gdGhpczt2YXIgdCxyPXRoaXMuX3Jvb3QsbixpLG8sYT10aGlzLl94MCxzPXRoaXMuX3kwLGw9dGhpcy5feDEsYz10aGlzLl95MSx1LGgsZixwLGQsZyxfLHk7aWYoIXIpcmV0dXJuIHRoaXM7aWYoci5sZW5ndGgpZm9yKDs7KXtpZigoZD11Pj0oZj0oYStsKS8yKSk/YT1mOmw9ZiwoZz1oPj0ocD0ocytjKS8yKSk/cz1wOmM9cCx0PXIsIShyPXJbXz1nPDwxfGRdKSlyZXR1cm4gdGhpcztpZighci5sZW5ndGgpYnJlYWs7KHRbXysxJjNdfHx0W18rMiYzXXx8dFtfKzMmM10pJiYobj10LHk9Xyl9Zm9yKDtyLmRhdGEhPT1lOylpZihpPXIsIShyPXIubmV4dCkpcmV0dXJuIHRoaXM7cmV0dXJuKG89ci5uZXh0KSYmZGVsZXRlIHIubmV4dCxpPyhvP2kubmV4dD1vOmRlbGV0ZSBpLm5leHQsdGhpcyk6dD8obz90W19dPW86ZGVsZXRlIHRbX10sKHI9dFswXXx8dFsxXXx8dFsyXXx8dFszXSkmJnI9PT0odFszXXx8dFsyXXx8dFsxXXx8dFswXSkmJiFyLmxlbmd0aCYmKG4/blt5XT1yOnRoaXMuX3Jvb3Q9ciksdGhpcyk6KHRoaXMuX3Jvb3Q9byx0aGlzKX1mdW5jdGlvbiBtNnQoZSl7Zm9yKHZhciB0PTAscj1lLmxlbmd0aDt0PHI7Kyt0KXRoaXMucmVtb3ZlKGVbdF0pO3JldHVybiB0aGlzfXZhciBnNnQ9TSgoKT0+e30pO2Z1bmN0aW9uIF82dCgpe3JldHVybiB0aGlzLl9yb290fXZhciB5NnQ9TSgoKT0+e30pO2Z1bmN0aW9uIHY2dCgpe3ZhciBlPTA7cmV0dXJuIHRoaXMudmlzaXQoZnVuY3Rpb24odCl7aWYoIXQubGVuZ3RoKWRvKytlO3doaWxlKHQ9dC5uZXh0KX0pLGV9dmFyIHg2dD1NKCgpPT57fSk7ZnVuY3Rpb24gYjZ0KGUpe3ZhciB0PVtdLHIsbj10aGlzLl9yb290LGksbyxhLHMsbDtmb3IobiYmdC5wdXNoKG5ldyBTbyhuLHRoaXMuX3gwLHRoaXMuX3kwLHRoaXMuX3gxLHRoaXMuX3kxKSk7cj10LnBvcCgpOylpZighZShuPXIubm9kZSxvPXIueDAsYT1yLnkwLHM9ci54MSxsPXIueTEpJiZuLmxlbmd0aCl7dmFyIGM9KG8rcykvMix1PShhK2wpLzI7KGk9blszXSkmJnQucHVzaChuZXcgU28oaSxjLHUscyxsKSksKGk9blsyXSkmJnQucHVzaChuZXcgU28oaSxvLHUsYyxsKSksKGk9blsxXSkmJnQucHVzaChuZXcgU28oaSxjLGEscyx1KSksKGk9blswXSkmJnQucHVzaChuZXcgU28oaSxvLGEsYyx1KSl9cmV0dXJuIHRoaXN9dmFyIHc2dD1NKCgpPT57TE4oKX0pO2Z1bmN0aW9uIFM2dChlKXt2YXIgdD1bXSxyPVtdLG47Zm9yKHRoaXMuX3Jvb3QmJnQucHVzaChuZXcgU28odGhpcy5fcm9vdCx0aGlzLl94MCx0aGlzLl95MCx0aGlzLl94MSx0aGlzLl95MSkpO249dC5wb3AoKTspe3ZhciBpPW4ubm9kZTtpZihpLmxlbmd0aCl7dmFyIG8sYT1uLngwLHM9bi55MCxsPW4ueDEsYz1uLnkxLHU9KGErbCkvMixoPShzK2MpLzI7KG89aVswXSkmJnQucHVzaChuZXcgU28obyxhLHMsdSxoKSksKG89aVsxXSkmJnQucHVzaChuZXcgU28obyx1LHMsbCxoKSksKG89aVsyXSkmJnQucHVzaChuZXcgU28obyxhLGgsdSxjKSksKG89aVszXSkmJnQucHVzaChuZXcgU28obyx1LGgsbCxjKSl9ci5wdXNoKG4pfWZvcig7bj1yLnBvcCgpOyllKG4ubm9kZSxuLngwLG4ueTAsbi54MSxuLnkxKTtyZXR1cm4gdGhpc312YXIgTTZ0PU0oKCk9PntMTigpfSk7ZnVuY3Rpb24gRTZ0KGUpe3JldHVybiBlWzBdfWZ1bmN0aW9uIFQ2dChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odGhpcy5feD1lLHRoaXMpOnRoaXMuX3h9dmFyIEM2dD1NKCgpPT57fSk7ZnVuY3Rpb24gQTZ0KGUpe3JldHVybiBlWzFdfWZ1bmN0aW9uIFA2dChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odGhpcy5feT1lLHRoaXMpOnRoaXMuX3l9dmFyIEk2dD1NKCgpPT57fSk7ZnVuY3Rpb24gQ3koZSx0LHIpe3ZhciBuPW5ldyBWWih0PT1udWxsP0U2dDp0LHI9PW51bGw/QTZ0OnIsTmFOLE5hTixOYU4sTmFOKTtyZXR1cm4gZT09bnVsbD9uOm4uYWRkQWxsKGUpfWZ1bmN0aW9uIFZaKGUsdCxyLG4saSxvKXt0aGlzLl94PWUsdGhpcy5feT10LHRoaXMuX3gwPXIsdGhpcy5feTA9bix0aGlzLl94MT1pLHRoaXMuX3kxPW8sdGhpcy5fcm9vdD12b2lkIDB9ZnVuY3Rpb24gTDZ0KGUpe2Zvcih2YXIgdD17ZGF0YTplLmRhdGF9LHI9dDtlPWUubmV4dDspcj1yLm5leHQ9e2RhdGE6ZS5kYXRhfTtyZXR1cm4gdH12YXIgR2EsazZ0PU0oKCk9PntvNnQoKTtzNnQoKTtjNnQoKTtoNnQoKTtwNnQoKTtnNnQoKTt5NnQoKTt4NnQoKTt3NnQoKTtNNnQoKTtDNnQoKTtJNnQoKTtHYT1DeS5wcm90b3R5cGU9VloucHJvdG90eXBlO0dhLmNvcHk9ZnVuY3Rpb24oKXt2YXIgZT1uZXcgVloodGhpcy5feCx0aGlzLl95LHRoaXMuX3gwLHRoaXMuX3kwLHRoaXMuX3gxLHRoaXMuX3kxKSx0PXRoaXMuX3Jvb3QscixuO2lmKCF0KXJldHVybiBlO2lmKCF0Lmxlbmd0aClyZXR1cm4gZS5fcm9vdD1MNnQodCksZTtmb3Iocj1be3NvdXJjZTp0LHRhcmdldDplLl9yb290PW5ldyBBcnJheSg0KX1dO3Q9ci5wb3AoKTspZm9yKHZhciBpPTA7aTw0OysraSkobj10LnNvdXJjZVtpXSkmJihuLmxlbmd0aD9yLnB1c2goe3NvdXJjZTpuLHRhcmdldDp0LnRhcmdldFtpXT1uZXcgQXJyYXkoNCl9KTp0LnRhcmdldFtpXT1MNnQobikpO3JldHVybiBlfTtHYS5hZGQ9cjZ0O0dhLmFkZEFsbD1pNnQ7R2EuY292ZXI9YTZ0O0dhLmRhdGE9bDZ0O0dhLmV4dGVudD11NnQ7R2EuZmluZD1mNnQ7R2EucmVtb3ZlPWQ2dDtHYS5yZW1vdmVBbGw9bTZ0O0dhLnJvb3Q9XzZ0O0dhLnNpemU9djZ0O0dhLnZpc2l0PWI2dDtHYS52aXNpdEFmdGVyPVM2dDtHYS54PVQ2dDtHYS55PVA2dH0pO3ZhciBVWj1NKCgpPT57azZ0KCl9KTtmdW5jdGlvbiBtNGUoZSl7cmV0dXJuIGUueCtlLnZ4fWZ1bmN0aW9uIGc0ZShlKXtyZXR1cm4gZS55K2Uudnl9ZnVuY3Rpb24gUjZ0KGUpe3ZhciB0LHIsbj0xLGk9MTt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9Rm4oZT09bnVsbD8xOitlKSk7ZnVuY3Rpb24gbygpe2Zvcih2YXIgbCxjPXQubGVuZ3RoLHUsaCxmLHAsZCxnLF89MDtfPGk7KytfKWZvcih1PUN5KHQsbTRlLGc0ZSkudmlzaXRBZnRlcihhKSxsPTA7bDxjOysrbCloPXRbbF0sZD1yW2guaW5kZXhdLGc9ZCpkLGY9aC54K2gudngscD1oLnkraC52eSx1LnZpc2l0KHkpO2Z1bmN0aW9uIHkoeCxiLFMsQyxQKXt2YXIgaz14LmRhdGEsTz14LnIsRD1kK087aWYoayl7aWYoay5pbmRleD5oLmluZGV4KXt2YXIgQj1mLWsueC1rLnZ4LEk9cC1rLnktay52eSxMPUIqQitJKkk7TDxEKkQmJihCPT09MCYmKEI9TXUoKSxMKz1CKkIpLEk9PT0wJiYoST1NdSgpLEwrPUkqSSksTD0oRC0oTD1NYXRoLnNxcnQoTCkpKS9MKm4saC52eCs9KEIqPUwpKihEPShPKj1PKS8oZytPKSksaC52eSs9KEkqPUwpKkQsay52eC09QiooRD0xLUQpLGsudnktPUkqRCl9cmV0dXJufXJldHVybiBiPmYrRHx8QzxmLUR8fFM+cCtEfHxQPHAtRH19ZnVuY3Rpb24gYShsKXtpZihsLmRhdGEpcmV0dXJuIGwucj1yW2wuZGF0YS5pbmRleF07Zm9yKHZhciBjPWwucj0wO2M8NDsrK2MpbFtjXSYmbFtjXS5yPmwuciYmKGwucj1sW2NdLnIpfWZ1bmN0aW9uIHMoKXtpZighIXQpe3ZhciBsLGM9dC5sZW5ndGgsdTtmb3Iocj1uZXcgQXJyYXkoYyksbD0wO2w8YzsrK2wpdT10W2xdLHJbdS5pbmRleF09K2UodSxsLHQpfX1yZXR1cm4gby5pbml0aWFsaXplPWZ1bmN0aW9uKGwpe3Q9bCxzKCl9LG8uaXRlcmF0aW9ucz1mdW5jdGlvbihsKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT0rbCxvKTppfSxvLnN0cmVuZ3RoPWZ1bmN0aW9uKGwpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPStsLG8pOm59LG8ucmFkaXVzPWZ1bmN0aW9uKGwpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiBsPT0iZnVuY3Rpb24iP2w6Rm4oK2wpLHMoKSxvKTplfSxvfXZhciBONnQ9TSgoKT0+e1R5KCk7SU4oKTtVWigpfSk7ZnVuY3Rpb24ga04oKXt9ZnVuY3Rpb24gRDZ0KGUsdCl7dmFyIHI9bmV3IGtOO2lmKGUgaW5zdGFuY2VvZiBrTillLmVhY2goZnVuY3Rpb24ocyxsKXtyLnNldChsLHMpfSk7ZWxzZSBpZihBcnJheS5pc0FycmF5KGUpKXt2YXIgbj0tMSxpPWUubGVuZ3RoLG87aWYodD09bnVsbClmb3IoOysrbjxpOylyLnNldChuLGVbbl0pO2Vsc2UgZm9yKDsrK248aTspci5zZXQodChvPWVbbl0sbixlKSxvKX1lbHNlIGlmKGUpZm9yKHZhciBhIGluIGUpci5zZXQoYSxlW2FdKTtyZXR1cm4gcn12YXIgY2wscGcsUk49TSgoKT0+e2NsPSIkIjtrTi5wcm90b3R5cGU9RDZ0LnByb3RvdHlwZT17Y29uc3RydWN0b3I6a04saGFzOmZ1bmN0aW9uKGUpe3JldHVybiBjbCtlIGluIHRoaXN9LGdldDpmdW5jdGlvbihlKXtyZXR1cm4gdGhpc1tjbCtlXX0sc2V0OmZ1bmN0aW9uKGUsdCl7cmV0dXJuIHRoaXNbY2wrZV09dCx0aGlzfSxyZW1vdmU6ZnVuY3Rpb24oZSl7dmFyIHQ9Y2wrZTtyZXR1cm4gdCBpbiB0aGlzJiZkZWxldGUgdGhpc1t0XX0sY2xlYXI6ZnVuY3Rpb24oKXtmb3IodmFyIGUgaW4gdGhpcyllWzBdPT09Y2wmJmRlbGV0ZSB0aGlzW2VdfSxrZXlzOmZ1bmN0aW9uKCl7dmFyIGU9W107Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PWNsJiZlLnB1c2godC5zbGljZSgxKSk7cmV0dXJuIGV9LHZhbHVlczpmdW5jdGlvbigpe3ZhciBlPVtdO2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1jbCYmZS5wdXNoKHRoaXNbdF0pO3JldHVybiBlfSxlbnRyaWVzOmZ1bmN0aW9uKCl7dmFyIGU9W107Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PWNsJiZlLnB1c2goe2tleTp0LnNsaWNlKDEpLHZhbHVlOnRoaXNbdF19KTtyZXR1cm4gZX0sc2l6ZTpmdW5jdGlvbigpe3ZhciBlPTA7Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PWNsJiYrK2U7cmV0dXJuIGV9LGVtcHR5OmZ1bmN0aW9uKCl7Zm9yKHZhciBlIGluIHRoaXMpaWYoZVswXT09PWNsKXJldHVybiExO3JldHVybiEwfSxlYWNoOmZ1bmN0aW9uKGUpe2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1jbCYmZSh0aGlzW3RdLHQuc2xpY2UoMSksdGhpcyl9fTtwZz1ENnR9KTt2YXIgTzZ0PU0oKCk9PntSTigpfSk7ZnVuY3Rpb24gTk4oKXt9ZnVuY3Rpb24gXzRlKGUsdCl7dmFyIHI9bmV3IE5OO2lmKGUgaW5zdGFuY2VvZiBOTillLmVhY2goZnVuY3Rpb24obyl7ci5hZGQobyl9KTtlbHNlIGlmKGUpe3ZhciBuPS0xLGk9ZS5sZW5ndGg7aWYodD09bnVsbClmb3IoOysrbjxpOylyLmFkZChlW25dKTtlbHNlIGZvcig7KytuPGk7KXIuYWRkKHQoZVtuXSxuLGUpKX1yZXR1cm4gcn12YXIgQXksejZ0PU0oKCk9PntSTigpO0F5PXBnLnByb3RvdHlwZTtOTi5wcm90b3R5cGU9XzRlLnByb3RvdHlwZT17Y29uc3RydWN0b3I6Tk4saGFzOkF5LmhhcyxhZGQ6ZnVuY3Rpb24oZSl7cmV0dXJuIGUrPSIiLHRoaXNbY2wrZV09ZSx0aGlzfSxyZW1vdmU6QXkucmVtb3ZlLGNsZWFyOkF5LmNsZWFyLHZhbHVlczpBeS5rZXlzLHNpemU6QXkuc2l6ZSxlbXB0eTpBeS5lbXB0eSxlYWNoOkF5LmVhY2h9fSk7dmFyIEY2dD1NKCgpPT57fSk7dmFyIEI2dD1NKCgpPT57fSk7dmFyIEg2dD1NKCgpPT57fSk7dmFyIHFaPU0oKCk9PntPNnQoKTt6NnQoKTtSTigpO0Y2dCgpO0I2dCgpO0g2dCgpfSk7ZnVuY3Rpb24geTRlKGUpe3JldHVybiBlLmluZGV4fWZ1bmN0aW9uIFY2dChlLHQpe3ZhciByPWUuZ2V0KHQpO2lmKCFyKXRocm93IG5ldyBFcnJvcigibWlzc2luZzogIit0KTtyZXR1cm4gcn1mdW5jdGlvbiBVNnQoZSl7dmFyIHQ9eTRlLHI9dSxuLGk9Rm4oMzApLG8sYSxzLGwsYz0xO2U9PW51bGwmJihlPVtdKTtmdW5jdGlvbiB1KGcpe3JldHVybiAxL01hdGgubWluKHNbZy5zb3VyY2UuaW5kZXhdLHNbZy50YXJnZXQuaW5kZXhdKX1mdW5jdGlvbiBoKGcpe2Zvcih2YXIgXz0wLHk9ZS5sZW5ndGg7XzxjOysrXylmb3IodmFyIHg9MCxiLFMsQyxQLGssTyxEO3g8eTsrK3gpYj1lW3hdLFM9Yi5zb3VyY2UsQz1iLnRhcmdldCxQPUMueCtDLnZ4LVMueC1TLnZ4fHxNdSgpLGs9Qy55K0MudnktUy55LVMudnl8fE11KCksTz1NYXRoLnNxcnQoUCpQK2sqayksTz0oTy1vW3hdKS9PKmcqblt4XSxQKj1PLGsqPU8sQy52eC09UCooRD1sW3hdKSxDLnZ5LT1rKkQsUy52eCs9UCooRD0xLUQpLFMudnkrPWsqRH1mdW5jdGlvbiBmKCl7aWYoISFhKXt2YXIgZyxfPWEubGVuZ3RoLHk9ZS5sZW5ndGgseD1wZyhhLHQpLGI7Zm9yKGc9MCxzPW5ldyBBcnJheShfKTtnPHk7KytnKWI9ZVtnXSxiLmluZGV4PWcsdHlwZW9mIGIuc291cmNlIT0ib2JqZWN0IiYmKGIuc291cmNlPVY2dCh4LGIuc291cmNlKSksdHlwZW9mIGIudGFyZ2V0IT0ib2JqZWN0IiYmKGIudGFyZ2V0PVY2dCh4LGIudGFyZ2V0KSksc1tiLnNvdXJjZS5pbmRleF09KHNbYi5zb3VyY2UuaW5kZXhdfHwwKSsxLHNbYi50YXJnZXQuaW5kZXhdPShzW2IudGFyZ2V0LmluZGV4XXx8MCkrMTtmb3IoZz0wLGw9bmV3IEFycmF5KHkpO2c8eTsrK2cpYj1lW2ddLGxbZ109c1tiLnNvdXJjZS5pbmRleF0vKHNbYi5zb3VyY2UuaW5kZXhdK3NbYi50YXJnZXQuaW5kZXhdKTtuPW5ldyBBcnJheSh5KSxwKCksbz1uZXcgQXJyYXkoeSksZCgpfX1mdW5jdGlvbiBwKCl7aWYoISFhKWZvcih2YXIgZz0wLF89ZS5sZW5ndGg7ZzxfOysrZyluW2ddPStyKGVbZ10sZyxlKX1mdW5jdGlvbiBkKCl7aWYoISFhKWZvcih2YXIgZz0wLF89ZS5sZW5ndGg7ZzxfOysrZylvW2ddPStpKGVbZ10sZyxlKX1yZXR1cm4gaC5pbml0aWFsaXplPWZ1bmN0aW9uKGcpe2E9ZyxmKCl9LGgubGlua3M9ZnVuY3Rpb24oZyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9ZyxmKCksaCk6ZX0saC5pZD1mdW5jdGlvbihnKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD1nLGgpOnR9LGguaXRlcmF0aW9ucz1mdW5jdGlvbihnKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYz0rZyxoKTpjfSxoLnN0cmVuZ3RoPWZ1bmN0aW9uKGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPXR5cGVvZiBnPT0iZnVuY3Rpb24iP2c6Rm4oK2cpLHAoKSxoKTpyfSxoLmRpc3RhbmNlPWZ1bmN0aW9uKGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPXR5cGVvZiBnPT0iZnVuY3Rpb24iP2c6Rm4oK2cpLGQoKSxoKTppfSxofXZhciBxNnQ9TSgoKT0+e1R5KCk7SU4oKTtxWigpfSk7ZnVuY3Rpb24gVzZ0KCl7Zm9yKHZhciBlPTAsdD1hcmd1bWVudHMubGVuZ3RoLHI9e30sbjtlPHQ7KytlKXtpZighKG49YXJndW1lbnRzW2VdKyIiKXx8biBpbiByfHwvW1xzLl0vLnRlc3QobikpdGhyb3cgbmV3IEVycm9yKCJpbGxlZ2FsIHR5cGU6ICIrbik7cltuXT1bXX1yZXR1cm4gbmV3IEROKHIpfWZ1bmN0aW9uIEROKGUpe3RoaXMuXz1lfWZ1bmN0aW9uIHg0ZShlLHQpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKS5tYXAoZnVuY3Rpb24ocil7dmFyIG49IiIsaT1yLmluZGV4T2YoIi4iKTtpZihpPj0wJiYobj1yLnNsaWNlKGkrMSkscj1yLnNsaWNlKDAsaSkpLHImJiF0Lmhhc093blByb3BlcnR5KHIpKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK3IpO3JldHVybnt0eXBlOnIsbmFtZTpufX0pfWZ1bmN0aW9uIGI0ZShlLHQpe2Zvcih2YXIgcj0wLG49ZS5sZW5ndGgsaTtyPG47KytyKWlmKChpPWVbcl0pLm5hbWU9PT10KXJldHVybiBpLnZhbHVlfWZ1bmN0aW9uIEc2dChlLHQscil7Zm9yKHZhciBuPTAsaT1lLmxlbmd0aDtuPGk7KytuKWlmKGVbbl0ubmFtZT09PXQpe2Vbbl09djRlLGU9ZS5zbGljZSgwLG4pLmNvbmNhdChlLnNsaWNlKG4rMSkpO2JyZWFrfXJldHVybiByIT1udWxsJiZlLnB1c2goe25hbWU6dCx2YWx1ZTpyfSksZX12YXIgdjRlLEdaLFk2dD1NKCgpPT57djRlPXt2YWx1ZTpmdW5jdGlvbigpe319O0ROLnByb3RvdHlwZT1XNnQucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpETixvbjpmdW5jdGlvbihlLHQpe3ZhciByPXRoaXMuXyxuPXg0ZShlKyIiLHIpLGksbz0tMSxhPW4ubGVuZ3RoO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7Zm9yKDsrK288YTspaWYoKGk9KGU9bltvXSkudHlwZSkmJihpPWI0ZShyW2ldLGUubmFtZSkpKXJldHVybiBpO3JldHVybn1pZih0IT1udWxsJiZ0eXBlb2YgdCE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgY2FsbGJhY2s6ICIrdCk7Zm9yKDsrK288YTspaWYoaT0oZT1uW29dKS50eXBlKXJbaV09RzZ0KHJbaV0sZS5uYW1lLHQpO2Vsc2UgaWYodD09bnVsbClmb3IoaSBpbiByKXJbaV09RzZ0KHJbaV0sZS5uYW1lLG51bGwpO3JldHVybiB0aGlzfSxjb3B5OmZ1bmN0aW9uKCl7dmFyIGU9e30sdD10aGlzLl87Zm9yKHZhciByIGluIHQpZVtyXT10W3JdLnNsaWNlKCk7cmV0dXJuIG5ldyBETihlKX0sY2FsbDpmdW5jdGlvbihlLHQpe2lmKChpPWFyZ3VtZW50cy5sZW5ndGgtMik+MClmb3IodmFyIHI9bmV3IEFycmF5KGkpLG49MCxpLG87bjxpOysrbilyW25dPWFyZ3VtZW50c1tuKzJdO2lmKCF0aGlzLl8uaGFzT3duUHJvcGVydHkoZSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrZSk7Zm9yKG89dGhpcy5fW2VdLG49MCxpPW8ubGVuZ3RoO248aTsrK24pb1tuXS52YWx1ZS5hcHBseSh0LHIpfSxhcHBseTpmdW5jdGlvbihlLHQscil7aWYoIXRoaXMuXy5oYXNPd25Qcm9wZXJ0eShlKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gdHlwZTogIitlKTtmb3IodmFyIG49dGhpcy5fW2VdLGk9MCxvPW4ubGVuZ3RoO2k8bzsrK2kpbltpXS52YWx1ZS5hcHBseSh0LHIpfX07R1o9VzZ0fSk7dmFyIGo2dD1NKCgpPT57WTZ0KCl9KTtmdW5jdGlvbiBqWigpe3JldHVybiBQeXx8KEs2dCh3NGUpLFB5PU1ULm5vdygpK0ZOKX1mdW5jdGlvbiB3NGUoKXtQeT0wfWZ1bmN0aW9uIFdaKCl7dGhpcy5fY2FsbD10aGlzLl90aW1lPXRoaXMuX25leHQ9bnVsbH1mdW5jdGlvbiBCTihlLHQscil7dmFyIG49bmV3IFdaO3JldHVybiBuLnJlc3RhcnQoZSx0LHIpLG59ZnVuY3Rpb24gWjZ0KCl7alooKSwrK0UyO2Zvcih2YXIgZT1PTix0O2U7KSh0PVB5LWUuX3RpbWUpPj0wJiZlLl9jYWxsLmNhbGwobnVsbCx0KSxlPWUuX25leHQ7LS1FMn1mdW5jdGlvbiBYNnQoKXtQeT0oek49TVQubm93KCkpK0ZOLEUyPXdUPTA7dHJ5e1o2dCgpfWZpbmFsbHl7RTI9MCxNNGUoKSxQeT0wfX1mdW5jdGlvbiBTNGUoKXt2YXIgZT1NVC5ub3coKSx0PWUtek47dD4kNnQmJihGTi09dCx6Tj1lKX1mdW5jdGlvbiBNNGUoKXtmb3IodmFyIGUsdD1PTixyLG49MS8wO3Q7KXQuX2NhbGw/KG4+dC5fdGltZSYmKG49dC5fdGltZSksZT10LHQ9dC5fbmV4dCk6KHI9dC5fbmV4dCx0Ll9uZXh0PW51bGwsdD1lP2UuX25leHQ9cjpPTj1yKTtTVD1lLFlaKG4pfWZ1bmN0aW9uIFlaKGUpe2lmKCFFMil7d1QmJih3VD1jbGVhclRpbWVvdXQod1QpKTt2YXIgdD1lLVB5O3Q+MjQ/KGU8MS8wJiYod1Q9c2V0VGltZW91dChYNnQsZS1NVC5ub3coKS1GTikpLGJUJiYoYlQ9Y2xlYXJJbnRlcnZhbChiVCkpKTooYlR8fCh6Tj1NVC5ub3coKSxiVD1zZXRJbnRlcnZhbChTNGUsJDZ0KSksRTI9MSxLNnQoWDZ0KSl9fXZhciBFMix3VCxiVCwkNnQsT04sU1Qsek4sUHksRk4sTVQsSzZ0LEo2dD1NKCgpPT57RTI9MCx3VD0wLGJUPTAsJDZ0PTFlMyx6Tj0wLFB5PTAsRk49MCxNVD10eXBlb2YgcGVyZm9ybWFuY2U9PSJvYmplY3QiJiZwZXJmb3JtYW5jZS5ub3c/cGVyZm9ybWFuY2U6RGF0ZSxLNnQ9dHlwZW9mIHdpbmRvdz09Im9iamVjdCImJndpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWU/d2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZS5iaW5kKHdpbmRvdyk6ZnVuY3Rpb24oZSl7c2V0VGltZW91dChlLDE3KX07V1oucHJvdG90eXBlPUJOLnByb3RvdHlwZT17Y29uc3RydWN0b3I6V1oscmVzdGFydDpmdW5jdGlvbihlLHQscil7aWYodHlwZW9mIGUhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IFR5cGVFcnJvcigiY2FsbGJhY2sgaXMgbm90IGEgZnVuY3Rpb24iKTtyPShyPT1udWxsP2paKCk6K3IpKyh0PT1udWxsPzA6K3QpLCF0aGlzLl9uZXh0JiZTVCE9PXRoaXMmJihTVD9TVC5fbmV4dD10aGlzOk9OPXRoaXMsU1Q9dGhpcyksdGhpcy5fY2FsbD1lLHRoaXMuX3RpbWU9cixZWigpfSxzdG9wOmZ1bmN0aW9uKCl7dGhpcy5fY2FsbCYmKHRoaXMuX2NhbGw9bnVsbCx0aGlzLl90aW1lPTEvMCxZWigpKX19fSk7dmFyIFE2dD1NKCgpPT57SjZ0KCl9KTtmdW5jdGlvbiB0SXQoZSl7cmV0dXJuIGUueH1mdW5jdGlvbiBlSXQoZSl7cmV0dXJuIGUueX1mdW5jdGlvbiBySXQoZSl7dmFyIHQscj0xLG49LjAwMSxpPTEtTWF0aC5wb3cobiwxLzMwMCksbz0wLGE9LjYscz1wZygpLGw9Qk4odSksYz1HWigidGljayIsImVuZCIpO2U9PW51bGwmJihlPVtdKTtmdW5jdGlvbiB1KCl7aCgpLGMuY2FsbCgidGljayIsdCkscjxuJiYobC5zdG9wKCksYy5jYWxsKCJlbmQiLHQpKX1mdW5jdGlvbiBoKCl7dmFyIGQsZz1lLmxlbmd0aCxfO2ZvcihyKz0oby1yKSppLHMuZWFjaChmdW5jdGlvbih5KXt5KHIpfSksZD0wO2Q8ZzsrK2QpXz1lW2RdLF8uZng9PW51bGw/Xy54Kz1fLnZ4Kj1hOihfLng9Xy5meCxfLnZ4PTApLF8uZnk9PW51bGw/Xy55Kz1fLnZ5Kj1hOihfLnk9Xy5meSxfLnZ5PTApfWZ1bmN0aW9uIGYoKXtmb3IodmFyIGQ9MCxnPWUubGVuZ3RoLF87ZDxnOysrZCl7aWYoXz1lW2RdLF8uaW5kZXg9ZCxpc05hTihfLngpfHxpc05hTihfLnkpKXt2YXIgeT1FNGUqTWF0aC5zcXJ0KGQpLHg9ZCpUNGU7Xy54PXkqTWF0aC5jb3MoeCksXy55PXkqTWF0aC5zaW4oeCl9KGlzTmFOKF8udngpfHxpc05hTihfLnZ5KSkmJihfLnZ4PV8udnk9MCl9fWZ1bmN0aW9uIHAoZCl7cmV0dXJuIGQuaW5pdGlhbGl6ZSYmZC5pbml0aWFsaXplKGUpLGR9cmV0dXJuIGYoKSx0PXt0aWNrOmgscmVzdGFydDpmdW5jdGlvbigpe3JldHVybiBsLnJlc3RhcnQodSksdH0sc3RvcDpmdW5jdGlvbigpe3JldHVybiBsLnN0b3AoKSx0fSxub2RlczpmdW5jdGlvbihkKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT1kLGYoKSxzLmVhY2gocCksdCk6ZX0sYWxwaGE6ZnVuY3Rpb24oZCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9K2QsdCk6cn0sYWxwaGFNaW46ZnVuY3Rpb24oZCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49K2QsdCk6bn0sYWxwaGFEZWNheTpmdW5jdGlvbihkKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT0rZCx0KToraX0sYWxwaGFUYXJnZXQ6ZnVuY3Rpb24oZCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG89K2QsdCk6b30sdmVsb2NpdHlEZWNheTpmdW5jdGlvbihkKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYT0xLWQsdCk6MS1hfSxmb3JjZTpmdW5jdGlvbihkLGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPjE/KGc9PW51bGw/cy5yZW1vdmUoZCk6cy5zZXQoZCxwKGcpKSx0KTpzLmdldChkKX0sZmluZDpmdW5jdGlvbihkLGcsXyl7dmFyIHk9MCx4PWUubGVuZ3RoLGIsUyxDLFAsaztmb3IoXz09bnVsbD9fPTEvMDpfKj1fLHk9MDt5PHg7Kyt5KVA9ZVt5XSxiPWQtUC54LFM9Zy1QLnksQz1iKmIrUypTLEM8XyYmKGs9UCxfPUMpO3JldHVybiBrfSxvbjpmdW5jdGlvbihkLGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPjE/KGMub24oZCxnKSx0KTpjLm9uKGQpfX19dmFyIEU0ZSxUNGUsWFo9TSgoKT0+e2o2dCgpO3FaKCk7UTZ0KCk7RTRlPTEwLFQ0ZT1NYXRoLlBJKigzLU1hdGguc3FydCg1KSl9KTtmdW5jdGlvbiBuSXQoKXt2YXIgZSx0LHIsbj1GbigtMzApLGksbz0xLGE9MS8wLHM9LjgxO2Z1bmN0aW9uIGwoZil7dmFyIHAsZD1lLmxlbmd0aCxnPUN5KGUsdEl0LGVJdCkudmlzaXRBZnRlcih1KTtmb3Iocj1mLHA9MDtwPGQ7KytwKXQ9ZVtwXSxnLnZpc2l0KGgpfWZ1bmN0aW9uIGMoKXtpZighIWUpe3ZhciBmLHA9ZS5sZW5ndGgsZDtmb3IoaT1uZXcgQXJyYXkocCksZj0wO2Y8cDsrK2YpZD1lW2ZdLGlbZC5pbmRleF09K24oZCxmLGUpfX1mdW5jdGlvbiB1KGYpe3ZhciBwPTAsZCxnLF89MCx5LHgsYjtpZihmLmxlbmd0aCl7Zm9yKHk9eD1iPTA7Yjw0OysrYikoZD1mW2JdKSYmKGc9TWF0aC5hYnMoZC52YWx1ZSkpJiYocCs9ZC52YWx1ZSxfKz1nLHkrPWcqZC54LHgrPWcqZC55KTtmLng9eS9fLGYueT14L199ZWxzZXtkPWYsZC54PWQuZGF0YS54LGQueT1kLmRhdGEueTtkbyBwKz1pW2QuZGF0YS5pbmRleF07d2hpbGUoZD1kLm5leHQpfWYudmFsdWU9cH1mdW5jdGlvbiBoKGYscCxkLGcpe2lmKCFmLnZhbHVlKXJldHVybiEwO3ZhciBfPWYueC10LngseT1mLnktdC55LHg9Zy1wLGI9XypfK3kqeTtpZih4KngvczxiKXJldHVybiBiPGEmJihfPT09MCYmKF89TXUoKSxiKz1fKl8pLHk9PT0wJiYoeT1NdSgpLGIrPXkqeSksYjxvJiYoYj1NYXRoLnNxcnQobypiKSksdC52eCs9XypmLnZhbHVlKnIvYix0LnZ5Kz15KmYudmFsdWUqci9iKSwhMDtpZihmLmxlbmd0aHx8Yj49YSlyZXR1cm47KGYuZGF0YSE9PXR8fGYubmV4dCkmJihfPT09MCYmKF89TXUoKSxiKz1fKl8pLHk9PT0wJiYoeT1NdSgpLGIrPXkqeSksYjxvJiYoYj1NYXRoLnNxcnQobypiKSkpO2RvIGYuZGF0YSE9PXQmJih4PWlbZi5kYXRhLmluZGV4XSpyL2IsdC52eCs9Xyp4LHQudnkrPXkqeCk7d2hpbGUoZj1mLm5leHQpfXJldHVybiBsLmluaXRpYWxpemU9ZnVuY3Rpb24oZil7ZT1mLGMoKX0sbC5zdHJlbmd0aD1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj10eXBlb2YgZj09ImZ1bmN0aW9uIj9mOkZuKCtmKSxjKCksbCk6bn0sbC5kaXN0YW5jZU1pbj1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz1mKmYsbCk6TWF0aC5zcXJ0KG8pfSxsLmRpc3RhbmNlTWF4PWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhhPWYqZixsKTpNYXRoLnNxcnQoYSl9LGwudGhldGE9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHM9ZipmLGwpOk1hdGguc3FydChzKX0sbH12YXIgaUl0PU0oKCk9PntUeSgpO0lOKCk7VVooKTtYWigpfSk7ZnVuY3Rpb24gb0l0KGUsdCxyKXt2YXIgbixpPUZuKC4xKSxvLGE7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPUZuKCtlKSksdD09bnVsbCYmKHQ9MCkscj09bnVsbCYmKHI9MCk7ZnVuY3Rpb24gcyhjKXtmb3IodmFyIHU9MCxoPW4ubGVuZ3RoO3U8aDsrK3Upe3ZhciBmPW5bdV0scD1mLngtdHx8MWUtNixkPWYueS1yfHwxZS02LGc9TWF0aC5zcXJ0KHAqcCtkKmQpLF89KGFbdV0tZykqb1t1XSpjL2c7Zi52eCs9cCpfLGYudnkrPWQqX319ZnVuY3Rpb24gbCgpe2lmKCEhbil7dmFyIGMsdT1uLmxlbmd0aDtmb3Iobz1uZXcgQXJyYXkodSksYT1uZXcgQXJyYXkodSksYz0wO2M8dTsrK2MpYVtjXT0rZShuW2NdLGMsbiksb1tjXT1pc05hTihhW2NdKT8wOitpKG5bY10sYyxuKX19cmV0dXJuIHMuaW5pdGlhbGl6ZT1mdW5jdGlvbihjKXtuPWMsbCgpfSxzLnN0cmVuZ3RoPWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPXR5cGVvZiBjPT0iZnVuY3Rpb24iP2M6Rm4oK2MpLGwoKSxzKTppfSxzLnJhZGl1cz1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2YgYz09ImZ1bmN0aW9uIj9jOkZuKCtjKSxsKCkscyk6ZX0scy54PWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PStjLHMpOnR9LHMueT1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj0rYyxzKTpyfSxzfXZhciBhSXQ9TSgoKT0+e1R5KCl9KTtmdW5jdGlvbiBzSXQoZSl7dmFyIHQ9Rm4oLjEpLHIsbixpO3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1GbihlPT1udWxsPzA6K2UpKTtmdW5jdGlvbiBvKHMpe2Zvcih2YXIgbD0wLGM9ci5sZW5ndGgsdTtsPGM7KytsKXU9cltsXSx1LnZ4Kz0oaVtsXS11LngpKm5bbF0qc31mdW5jdGlvbiBhKCl7aWYoISFyKXt2YXIgcyxsPXIubGVuZ3RoO2ZvcihuPW5ldyBBcnJheShsKSxpPW5ldyBBcnJheShsKSxzPTA7czxsOysrcyluW3NdPWlzTmFOKGlbc109K2UocltzXSxzLHIpKT8wOit0KHJbc10scyxyKX19cmV0dXJuIG8uaW5pdGlhbGl6ZT1mdW5jdGlvbihzKXtyPXMsYSgpfSxvLnN0cmVuZ3RoPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBzPT0iZnVuY3Rpb24iP3M6Rm4oK3MpLGEoKSxvKTp0fSxvLng9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpGbigrcyksYSgpLG8pOmV9LG99dmFyIGxJdD1NKCgpPT57VHkoKX0pO2Z1bmN0aW9uIGNJdChlKXt2YXIgdD1GbiguMSkscixuLGk7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPUZuKGU9PW51bGw/MDorZSkpO2Z1bmN0aW9uIG8ocyl7Zm9yKHZhciBsPTAsYz1yLmxlbmd0aCx1O2w8YzsrK2wpdT1yW2xdLHUudnkrPShpW2xdLXUueSkqbltsXSpzfWZ1bmN0aW9uIGEoKXtpZighIXIpe3ZhciBzLGw9ci5sZW5ndGg7Zm9yKG49bmV3IEFycmF5KGwpLGk9bmV3IEFycmF5KGwpLHM9MDtzPGw7KytzKW5bc109aXNOYU4oaVtzXT0rZShyW3NdLHMscikpPzA6K3QocltzXSxzLHIpfX1yZXR1cm4gby5pbml0aWFsaXplPWZ1bmN0aW9uKHMpe3I9cyxhKCl9LG8uc3RyZW5ndGg9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpGbigrcyksYSgpLG8pOnR9LG8ueT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOkZuKCtzKSxhKCksbyk6ZX0sb312YXIgdUl0PU0oKCk9PntUeSgpfSk7dmFyIGhJdD1NKCgpPT57ZTZ0KCk7TjZ0KCk7cTZ0KCk7aUl0KCk7YUl0KCk7WFooKTtsSXQoKTt1SXQoKX0pO2Z1bmN0aW9uIEl5KGUsdCl7aWYoKHI9KGU9dD9lLnRvRXhwb25lbnRpYWwodC0xKTplLnRvRXhwb25lbnRpYWwoKSkuaW5kZXhPZigiZSIpKTwwKXJldHVybiBudWxsO3ZhciByLG49ZS5zbGljZSgwLHIpO3JldHVybltuLmxlbmd0aD4xP25bMF0rbi5zbGljZSgyKTpuLCtlLnNsaWNlKHIrMSldfXZhciBITj1NKCgpPT57fSk7ZnVuY3Rpb24gcmYoZSl7cmV0dXJuIGU9SXkoTWF0aC5hYnMoZSkpLGU/ZVsxXTpOYU59dmFyIEVUPU0oKCk9PntITigpfSk7ZnVuY3Rpb24gZkl0KGUsdCl7cmV0dXJuIGZ1bmN0aW9uKHIsbil7Zm9yKHZhciBpPXIubGVuZ3RoLG89W10sYT0wLHM9ZVswXSxsPTA7aT4wJiZzPjAmJihsK3MrMT5uJiYocz1NYXRoLm1heCgxLG4tbCkpLG8ucHVzaChyLnN1YnN0cmluZyhpLT1zLGkrcykpLCEoKGwrPXMrMSk+bikpOylzPWVbYT0oYSsxKSVlLmxlbmd0aF07cmV0dXJuIG8ucmV2ZXJzZSgpLmpvaW4odCl9fXZhciBwSXQ9TSgoKT0+e30pO2Z1bmN0aW9uIGRJdChlKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuIHQucmVwbGFjZSgvWzAtOV0vZyxmdW5jdGlvbihyKXtyZXR1cm4gZVsrcl19KX19dmFyIG1JdD1NKCgpPT57fSk7ZnVuY3Rpb24gZ0l0KGUsdCl7ZT1lLnRvUHJlY2lzaW9uKHQpO3Q6Zm9yKHZhciByPWUubGVuZ3RoLG49MSxpPS0xLG87bjxyOysrbilzd2l0Y2goZVtuXSl7Y2FzZSIuIjppPW89bjticmVhaztjYXNlIjAiOmk9PT0wJiYoaT1uKSxvPW47YnJlYWs7Y2FzZSJlIjpicmVhayB0O2RlZmF1bHQ6aT4wJiYoaT0wKTticmVha31yZXR1cm4gaT4wP2Uuc2xpY2UoMCxpKStlLnNsaWNlKG8rMSk6ZX12YXIgX0l0PU0oKCk9Pnt9KTtmdW5jdGlvbiB5SXQoZSx0KXt2YXIgcj1JeShlLHQpO2lmKCFyKXJldHVybiBlKyIiO3ZhciBuPXJbMF0saT1yWzFdLG89aS0oJFo9TWF0aC5tYXgoLTgsTWF0aC5taW4oOCxNYXRoLmZsb29yKGkvMykpKSozKSsxLGE9bi5sZW5ndGg7cmV0dXJuIG89PT1hP246bz5hP24rbmV3IEFycmF5KG8tYSsxKS5qb2luKCIwIik6bz4wP24uc2xpY2UoMCxvKSsiLiIrbi5zbGljZShvKToiMC4iK25ldyBBcnJheSgxLW8pLmpvaW4oIjAiKStJeShlLE1hdGgubWF4KDAsdCtvLTEpKVswXX12YXIgJFosS1o9TSgoKT0+e0hOKCl9KTtmdW5jdGlvbiBaWihlLHQpe3ZhciByPUl5KGUsdCk7aWYoIXIpcmV0dXJuIGUrIiI7dmFyIG49clswXSxpPXJbMV07cmV0dXJuIGk8MD8iMC4iK25ldyBBcnJheSgtaSkuam9pbigiMCIpK246bi5sZW5ndGg+aSsxP24uc2xpY2UoMCxpKzEpKyIuIituLnNsaWNlKGkrMSk6bituZXcgQXJyYXkoaS1uLmxlbmd0aCsyKS5qb2luKCIwIil9dmFyIHZJdD1NKCgpPT57SE4oKX0pO3ZhciBWTixKWj1NKCgpPT57X0l0KCk7S1ooKTt2SXQoKTtWTj17IiI6Z0l0LCIlIjpmdW5jdGlvbihlLHQpe3JldHVybihlKjEwMCkudG9GaXhlZCh0KX0sYjpmdW5jdGlvbihlKXtyZXR1cm4gTWF0aC5yb3VuZChlKS50b1N0cmluZygyKX0sYzpmdW5jdGlvbihlKXtyZXR1cm4gZSsiIn0sZDpmdW5jdGlvbihlKXtyZXR1cm4gTWF0aC5yb3VuZChlKS50b1N0cmluZygxMCl9LGU6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gZS50b0V4cG9uZW50aWFsKHQpfSxmOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIGUudG9GaXhlZCh0KX0sZzpmdW5jdGlvbihlLHQpe3JldHVybiBlLnRvUHJlY2lzaW9uKHQpfSxvOmZ1bmN0aW9uKGUpe3JldHVybiBNYXRoLnJvdW5kKGUpLnRvU3RyaW5nKDgpfSxwOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIFpaKGUqMTAwLHQpfSxyOlpaLHM6eUl0LFg6ZnVuY3Rpb24oZSl7cmV0dXJuIE1hdGgucm91bmQoZSkudG9TdHJpbmcoMTYpLnRvVXBwZXJDYXNlKCl9LHg6ZnVuY3Rpb24oZSl7cmV0dXJuIE1hdGgucm91bmQoZSkudG9TdHJpbmcoMTYpfX19KTtmdW5jdGlvbiBMeShlKXtyZXR1cm4gbmV3IFFaKGUpfWZ1bmN0aW9uIFFaKGUpe2lmKCEodD1DNGUuZXhlYyhlKSkpdGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIGZvcm1hdDogIitlKTt2YXIgdCxyPXRbMV18fCIgIixuPXRbMl18fCI+IixpPXRbM118fCItIixvPXRbNF18fCIiLGE9ISF0WzVdLHM9dFs2XSYmK3RbNl0sbD0hIXRbN10sYz10WzhdJiYrdFs4XS5zbGljZSgxKSx1PXRbOV18fCIiO3U9PT0ibiI/KGw9ITAsdT0iZyIpOlZOW3VdfHwodT0iIiksKGF8fHI9PT0iMCImJm49PT0iPSIpJiYoYT0hMCxyPSIwIixuPSI9IiksdGhpcy5maWxsPXIsdGhpcy5hbGlnbj1uLHRoaXMuc2lnbj1pLHRoaXMuc3ltYm9sPW8sdGhpcy56ZXJvPWEsdGhpcy53aWR0aD1zLHRoaXMuY29tbWE9bCx0aGlzLnByZWNpc2lvbj1jLHRoaXMudHlwZT11fXZhciBDNGUsdEo9TSgoKT0+e0paKCk7QzRlPS9eKD86KC4pPyhbPD49Xl0pKT8oWytcLVwoIF0pPyhbJCNdKT8oMCk/KFxkKyk/KCwpPyhcLlxkKyk/KFthLXolXSk/JC9pO0x5LnByb3RvdHlwZT1RWi5wcm90b3R5cGU7UVoucHJvdG90eXBlLnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZmlsbCt0aGlzLmFsaWduK3RoaXMuc2lnbit0aGlzLnN5bWJvbCsodGhpcy56ZXJvPyIwIjoiIikrKHRoaXMud2lkdGg9PW51bGw/IiI6TWF0aC5tYXgoMSx0aGlzLndpZHRofDApKSsodGhpcy5jb21tYT8iLCI6IiIpKyh0aGlzLnByZWNpc2lvbj09bnVsbD8iIjoiLiIrTWF0aC5tYXgoMCx0aGlzLnByZWNpc2lvbnwwKSkrdGhpcy50eXBlfX0pO2Z1bmN0aW9uIGVKKGUpe3JldHVybiBlfXZhciB4SXQ9TSgoKT0+e30pO2Z1bmN0aW9uIFVOKGUpe3ZhciB0PWUuZ3JvdXBpbmcmJmUudGhvdXNhbmRzP2ZJdChlLmdyb3VwaW5nLGUudGhvdXNhbmRzKTplSixyPWUuY3VycmVuY3ksbj1lLmRlY2ltYWwsaT1lLm51bWVyYWxzP2RJdChlLm51bWVyYWxzKTplSixvPWUucGVyY2VudHx8IiUiO2Z1bmN0aW9uIGEobCl7bD1MeShsKTt2YXIgYz1sLmZpbGwsdT1sLmFsaWduLGg9bC5zaWduLGY9bC5zeW1ib2wscD1sLnplcm8sZD1sLndpZHRoLGc9bC5jb21tYSxfPWwucHJlY2lzaW9uLHk9bC50eXBlLHg9Zj09PSIkIj9yWzBdOmY9PT0iIyImJi9bYm94WF0vLnRlc3QoeSk/IjAiK3kudG9Mb3dlckNhc2UoKToiIixiPWY9PT0iJCI/clsxXTovWyVwXS8udGVzdCh5KT9vOiIiLFM9Vk5beV0sQz0heXx8L1tkZWZncHJzJV0vLnRlc3QoeSk7Xz1fPT1udWxsP3k/NjoxMjovW2dwcnNdLy50ZXN0KHkpP01hdGgubWF4KDEsTWF0aC5taW4oMjEsXykpOk1hdGgubWF4KDAsTWF0aC5taW4oMjAsXykpO2Z1bmN0aW9uIFAoayl7dmFyIE89eCxEPWIsQixJLEw7aWYoeT09PSJjIilEPVMoaykrRCxrPSIiO2Vsc2V7az0razt2YXIgUj1rPDA7aWYoaz1TKE1hdGguYWJzKGspLF8pLFImJitrPT0wJiYoUj0hMSksTz0oUj9oPT09IigiP2g6Ii0iOmg9PT0iLSJ8fGg9PT0iKCI/IiI6aCkrTyxEPSh5PT09InMiP2JJdFs4KyRaLzNdOiIiKStEKyhSJiZoPT09IigiPyIpIjoiIiksQyl7Zm9yKEI9LTEsST1rLmxlbmd0aDsrK0I8STspaWYoTD1rLmNoYXJDb2RlQXQoQiksNDg+THx8TD41Nyl7RD0oTD09PTQ2P24ray5zbGljZShCKzEpOmsuc2xpY2UoQikpK0Qsaz1rLnNsaWNlKDAsQik7YnJlYWt9fX1nJiYhcCYmKGs9dChrLDEvMCkpO3ZhciBGPU8ubGVuZ3RoK2subGVuZ3RoK0QubGVuZ3RoLHo9RjxkP25ldyBBcnJheShkLUYrMSkuam9pbihjKToiIjtzd2l0Y2goZyYmcCYmKGs9dCh6K2ssei5sZW5ndGg/ZC1ELmxlbmd0aDoxLzApLHo9IiIpLHUpe2Nhc2UiPCI6az1PK2srRCt6O2JyZWFrO2Nhc2UiPSI6az1PK3oraytEO2JyZWFrO2Nhc2UiXiI6az16LnNsaWNlKDAsRj16Lmxlbmd0aD4+MSkrTytrK0Qrei5zbGljZShGKTticmVhaztkZWZhdWx0Oms9eitPK2srRDticmVha31yZXR1cm4gaShrKX1yZXR1cm4gUC50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiBsKyIifSxQfWZ1bmN0aW9uIHMobCxjKXt2YXIgdT1hKChsPUx5KGwpLGwudHlwZT0iZiIsbCkpLGg9TWF0aC5tYXgoLTgsTWF0aC5taW4oOCxNYXRoLmZsb29yKHJmKGMpLzMpKSkqMyxmPU1hdGgucG93KDEwLC1oKSxwPWJJdFs4K2gvM107cmV0dXJuIGZ1bmN0aW9uKGQpe3JldHVybiB1KGYqZCkrcH19cmV0dXJue2Zvcm1hdDphLGZvcm1hdFByZWZpeDpzfX12YXIgYkl0LHJKPU0oKCk9PntFVCgpO3BJdCgpO21JdCgpO3RKKCk7SlooKTtLWigpO3hJdCgpO2JJdD1bInkiLCJ6IiwiYSIsImYiLCJwIiwibiIsIlx4QjUiLCJtIiwiIiwiayIsIk0iLCJHIiwiVCIsIlAiLCJFIiwiWiIsIlkiXX0pO2Z1bmN0aW9uIEdOKGUpe3JldHVybiBxTj1VTihlKSxuSj1xTi5mb3JtYXQsaUo9cU4uZm9ybWF0UHJlZml4LHFOfXZhciBxTixuSixpSix3SXQ9TSgoKT0+e3JKKCk7R04oe2RlY2ltYWw6Ii4iLHRob3VzYW5kczoiLCIsZ3JvdXBpbmc6WzNdLGN1cnJlbmN5OlsiJCIsIiJdfSl9KTtmdW5jdGlvbiBTSXQoZSl7cmV0dXJuIE1hdGgubWF4KDAsLXJmKE1hdGguYWJzKGUpKSl9dmFyIE1JdD1NKCgpPT57RVQoKX0pO2Z1bmN0aW9uIEVJdChlLHQpe3JldHVybiBNYXRoLm1heCgwLE1hdGgubWF4KC04LE1hdGgubWluKDgsTWF0aC5mbG9vcihyZih0KS8zKSkpKjMtcmYoTWF0aC5hYnMoZSkpKX12YXIgVEl0PU0oKCk9PntFVCgpfSk7ZnVuY3Rpb24gQ0l0KGUsdCl7cmV0dXJuIGU9TWF0aC5hYnMoZSksdD1NYXRoLmFicyh0KS1lLE1hdGgubWF4KDAscmYodCktcmYoZSkpKzF9dmFyIEFJdD1NKCgpPT57RVQoKX0pO3ZhciBQSXQ9TSgoKT0+e3dJdCgpO3JKKCk7dEooKTtNSXQoKTtUSXQoKTtBSXQoKX0pO2Z1bmN0aW9uIENzKCl7cmV0dXJuIG5ldyBZTn1mdW5jdGlvbiBZTigpe3RoaXMucmVzZXQoKX1mdW5jdGlvbiBJSXQoZSx0LHIpe3ZhciBuPWUucz10K3IsaT1uLXQsbz1uLWk7ZS50PXQtbysoci1pKX12YXIgV04sa3k9TSgoKT0+e1lOLnByb3RvdHlwZT17Y29uc3RydWN0b3I6WU4scmVzZXQ6ZnVuY3Rpb24oKXt0aGlzLnM9dGhpcy50PTB9LGFkZDpmdW5jdGlvbihlKXtJSXQoV04sZSx0aGlzLnQpLElJdCh0aGlzLFdOLnMsdGhpcy5zKSx0aGlzLnM/dGhpcy50Kz1XTi50OnRoaXMucz1XTi50fSx2YWx1ZU9mOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc319O1dOPW5ldyBZTn0pO2Z1bmN0aW9uICROKGUpe3JldHVybiBlPjE/MDplPC0xP3JyOk1hdGguYWNvcyhlKX1mdW5jdGlvbiBKbihlKXtyZXR1cm4gZT4xP0JuOmU8LTE/LUJuOk1hdGguYXNpbihlKX1mdW5jdGlvbiBhSihlKXtyZXR1cm4oZT1KdChlLzIpKSplfXZhciBjZSxvSixycixCbixUMixCaSxVcix3ZSxZZSx5YyxTbixhZSxUVCxqTixSeSxYTixKdCxkZyxScixDMixscj1NKCgpPT57Y2U9MWUtNixvSj0xZS0xMixycj1NYXRoLlBJLEJuPXJyLzIsVDI9cnIvNCxCaT1ycioyLFVyPTE4MC9ycix3ZT1yci8xODAsWWU9TWF0aC5hYnMseWM9TWF0aC5hdGFuLFNuPU1hdGguYXRhbjIsYWU9TWF0aC5jb3MsVFQ9TWF0aC5jZWlsLGpOPU1hdGguZXhwLFJ5PU1hdGgubG9nLFhOPU1hdGgucG93LEp0PU1hdGguc2luLGRnPU1hdGguc2lnbnx8ZnVuY3Rpb24oZSl7cmV0dXJuIGU+MD8xOmU8MD8tMTowfSxScj1NYXRoLnNxcnQsQzI9TWF0aC50YW59KTtmdW5jdGlvbiBxcigpe312YXIgWHA9TSgoKT0+e30pO2Z1bmN0aW9uIEtOKGUsdCl7ZSYma0l0Lmhhc093blByb3BlcnR5KGUudHlwZSkmJmtJdFtlLnR5cGVdKGUsdCl9ZnVuY3Rpb24gc0ooZSx0LHIpe3ZhciBuPS0xLGk9ZS5sZW5ndGgtcixvO2Zvcih0LmxpbmVTdGFydCgpOysrbjxpOylvPWVbbl0sdC5wb2ludChvWzBdLG9bMV0sb1syXSk7dC5saW5lRW5kKCl9ZnVuY3Rpb24gUkl0KGUsdCl7dmFyIHI9LTEsbj1lLmxlbmd0aDtmb3IodC5wb2x5Z29uU3RhcnQoKTsrK3I8bjspc0ooZVtyXSx0LDEpO3QucG9seWdvbkVuZCgpfWZ1bmN0aW9uIE1vKGUsdCl7ZSYmTEl0Lmhhc093blByb3BlcnR5KGUudHlwZSk/TEl0W2UudHlwZV0oZSx0KTpLTihlLHQpfXZhciBMSXQsa0l0LG1nPU0oKCk9PntMSXQ9e0ZlYXR1cmU6ZnVuY3Rpb24oZSx0KXtLTihlLmdlb21ldHJ5LHQpfSxGZWF0dXJlQ29sbGVjdGlvbjpmdW5jdGlvbihlLHQpe2Zvcih2YXIgcj1lLmZlYXR1cmVzLG49LTEsaT1yLmxlbmd0aDsrK248aTspS04ocltuXS5nZW9tZXRyeSx0KX19LGtJdD17U3BoZXJlOmZ1bmN0aW9uKGUsdCl7dC5zcGhlcmUoKX0sUG9pbnQ6ZnVuY3Rpb24oZSx0KXtlPWUuY29vcmRpbmF0ZXMsdC5wb2ludChlWzBdLGVbMV0sZVsyXSl9LE11bHRpUG9pbnQ6ZnVuY3Rpb24oZSx0KXtmb3IodmFyIHI9ZS5jb29yZGluYXRlcyxuPS0xLGk9ci5sZW5ndGg7KytuPGk7KWU9cltuXSx0LnBvaW50KGVbMF0sZVsxXSxlWzJdKX0sTGluZVN0cmluZzpmdW5jdGlvbihlLHQpe3NKKGUuY29vcmRpbmF0ZXMsdCwwKX0sTXVsdGlMaW5lU3RyaW5nOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuY29vcmRpbmF0ZXMsbj0tMSxpPXIubGVuZ3RoOysrbjxpOylzSihyW25dLHQsMCl9LFBvbHlnb246ZnVuY3Rpb24oZSx0KXtSSXQoZS5jb29yZGluYXRlcyx0KX0sTXVsdGlQb2x5Z29uOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuY29vcmRpbmF0ZXMsbj0tMSxpPXIubGVuZ3RoOysrbjxpOylSSXQocltuXSx0KX0sR2VvbWV0cnlDb2xsZWN0aW9uOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuZ2VvbWV0cmllcyxuPS0xLGk9ci5sZW5ndGg7KytuPGk7KUtOKHJbbl0sdCl9fX0pO2Z1bmN0aW9uIEE0ZSgpe0V1LnBvaW50PUk0ZX1mdW5jdGlvbiBQNGUoKXtPSXQoTkl0LERJdCl9ZnVuY3Rpb24gSTRlKGUsdCl7RXUucG9pbnQ9T0l0LE5JdD1lLERJdD10LGUqPXdlLHQqPXdlLGxKPWUsY0o9YWUodD10LzIrVDIpLHVKPUp0KHQpfWZ1bmN0aW9uIE9JdChlLHQpe2UqPXdlLHQqPXdlLHQ9dC8yK1QyO3ZhciByPWUtbEosbj1yPj0wPzE6LTEsaT1uKnIsbz1hZSh0KSxhPUp0KHQpLHM9dUoqYSxsPWNKKm8rcyphZShpKSxjPXMqbipKdChpKTtDVC5hZGQoU24oYyxsKSksbEo9ZSxjSj1vLHVKPWF9ZnVuY3Rpb24gekl0KGUpe3JldHVybiBaTi5yZXNldCgpLE1vKGUsRXUpLFpOKjJ9dmFyIENULFpOLE5JdCxESXQsbEosY0osdUosRXUsaEo9TSgoKT0+e2t5KCk7bHIoKTtYcCgpO21nKCk7Q1Q9Q3MoKSxaTj1DcygpLEV1PXtwb2ludDpxcixsaW5lU3RhcnQ6cXIsbGluZUVuZDpxcixwb2x5Z29uU3RhcnQ6ZnVuY3Rpb24oKXtDVC5yZXNldCgpLEV1LmxpbmVTdGFydD1BNGUsRXUubGluZUVuZD1QNGV9LHBvbHlnb25FbmQ6ZnVuY3Rpb24oKXt2YXIgZT0rQ1Q7Wk4uYWRkKGU8MD9CaStlOmUpLHRoaXMubGluZVN0YXJ0PXRoaXMubGluZUVuZD10aGlzLnBvaW50PXFyfSxzcGhlcmU6ZnVuY3Rpb24oKXtaTi5hZGQoQmkpfX19KTtmdW5jdGlvbiBOeShlKXtyZXR1cm5bU24oZVsxXSxlWzBdKSxKbihlWzJdKV19ZnVuY3Rpb24gdmMoZSl7dmFyIHQ9ZVswXSxyPWVbMV0sbj1hZShyKTtyZXR1cm5bbiphZSh0KSxuKkp0KHQpLEp0KHIpXX1mdW5jdGlvbiBBVChlLHQpe3JldHVybiBlWzBdKnRbMF0rZVsxXSp0WzFdK2VbMl0qdFsyXX1mdW5jdGlvbiAkcChlLHQpe3JldHVybltlWzFdKnRbMl0tZVsyXSp0WzFdLGVbMl0qdFswXS1lWzBdKnRbMl0sZVswXSp0WzFdLWVbMV0qdFswXV19ZnVuY3Rpb24gSk4oZSx0KXtlWzBdKz10WzBdLGVbMV0rPXRbMV0sZVsyXSs9dFsyXX1mdW5jdGlvbiBQVChlLHQpe3JldHVybltlWzBdKnQsZVsxXSp0LGVbMl0qdF19ZnVuY3Rpb24gRHkoZSl7dmFyIHQ9UnIoZVswXSplWzBdK2VbMV0qZVsxXStlWzJdKmVbMl0pO2VbMF0vPXQsZVsxXS89dCxlWzJdLz10fXZhciBBMj1NKCgpPT57bHIoKX0pO2Z1bmN0aW9uIGZKKGUsdCl7Z2cucHVzaChacD1bbWk9ZSxFaT1lXSksdDxobCYmKGhsPXQpLHQ+eGMmJih4Yz10KX1mdW5jdGlvbiBxSXQoZSx0KXt2YXIgcj12YyhbZSp3ZSx0KndlXSk7aWYoUDIpe3ZhciBuPSRwKFAyLHIpLGk9W25bMV0sLW5bMF0sMF0sbz0kcChpLG4pO0R5KG8pLG89Tnkobyk7dmFyIGE9ZS1PeSxzPWE+MD8xOi0xLGw9b1swXSpVcipzLGMsdT1ZZShhKT4xODA7dV4ocypPeTxsJiZsPHMqZSk/KGM9b1sxXSpVcixjPnhjJiYoeGM9YykpOihsPShsKzM2MCklMzYwLTE4MCx1XihzKk95PGwmJmw8cyplKT8oYz0tb1sxXSpVcixjPGhsJiYoaGw9YykpOih0PGhsJiYoaGw9dCksdD54YyYmKHhjPXQpKSksdT9lPE95P3VsKG1pLGUpPnVsKG1pLEVpKSYmKEVpPWUpOnVsKGUsRWkpPnVsKG1pLEVpKSYmKG1pPWUpOkVpPj1taT8oZTxtaSYmKG1pPWUpLGU+RWkmJihFaT1lKSk6ZT5PeT91bChtaSxlKT51bChtaSxFaSkmJihFaT1lKTp1bChlLEVpKT51bChtaSxFaSkmJihtaT1lKX1lbHNlIGdnLnB1c2goWnA9W21pPWUsRWk9ZV0pO3Q8aGwmJihobD10KSx0PnhjJiYoeGM9dCksUDI9cixPeT1lfWZ1bmN0aW9uIEZJdCgpe0twLnBvaW50PXFJdH1mdW5jdGlvbiBCSXQoKXtacFswXT1taSxacFsxXT1FaSxLcC5wb2ludD1mSixQMj1udWxsfWZ1bmN0aW9uIEdJdChlLHQpe2lmKFAyKXt2YXIgcj1lLU95O0lULmFkZChZZShyKT4xODA/cisocj4wPzM2MDotMzYwKTpyKX1lbHNlIFZJdD1lLFVJdD10O0V1LnBvaW50KGUsdCkscUl0KGUsdCl9ZnVuY3Rpb24gTDRlKCl7RXUubGluZVN0YXJ0KCl9ZnVuY3Rpb24gazRlKCl7R0l0KFZJdCxVSXQpLEV1LmxpbmVFbmQoKSxZZShJVCk+Y2UmJihtaT0tKEVpPTE4MCkpLFpwWzBdPW1pLFpwWzFdPUVpLFAyPW51bGx9ZnVuY3Rpb24gdWwoZSx0KXtyZXR1cm4odC09ZSk8MD90KzM2MDp0fWZ1bmN0aW9uIFI0ZShlLHQpe3JldHVybiBlWzBdLXRbMF19ZnVuY3Rpb24gSEl0KGUsdCl7cmV0dXJuIGVbMF08PWVbMV0/ZVswXTw9dCYmdDw9ZVsxXTp0PGVbMF18fGVbMV08dH1mdW5jdGlvbiBXSXQoZSl7dmFyIHQscixuLGksbyxhLHM7aWYoeGM9RWk9LShtaT1obD0xLzApLGdnPVtdLE1vKGUsS3ApLHI9Z2cubGVuZ3RoKXtmb3IoZ2cuc29ydChSNGUpLHQ9MSxuPWdnWzBdLG89W25dO3Q8cjsrK3QpaT1nZ1t0XSxISXQobixpWzBdKXx8SEl0KG4saVsxXSk/KHVsKG5bMF0saVsxXSk+dWwoblswXSxuWzFdKSYmKG5bMV09aVsxXSksdWwoaVswXSxuWzFdKT51bChuWzBdLG5bMV0pJiYoblswXT1pWzBdKSk6by5wdXNoKG49aSk7Zm9yKGE9LTEvMCxyPW8ubGVuZ3RoLTEsdD0wLG49b1tyXTt0PD1yO249aSwrK3QpaT1vW3RdLChzPXVsKG5bMV0saVswXSkpPmEmJihhPXMsbWk9aVswXSxFaT1uWzFdKX1yZXR1cm4gZ2c9WnA9bnVsbCxtaT09PTEvMHx8aGw9PT0xLzA/W1tOYU4sTmFOXSxbTmFOLE5hTl1dOltbbWksaGxdLFtFaSx4Y11dfXZhciBtaSxobCxFaSx4YyxPeSxWSXQsVUl0LFAyLElULGdnLFpwLEtwLFlJdD1NKCgpPT57a3koKTtoSigpO0EyKCk7bHIoKTttZygpO0lUPUNzKCksS3A9e3BvaW50OmZKLGxpbmVTdGFydDpGSXQsbGluZUVuZDpCSXQscG9seWdvblN0YXJ0OmZ1bmN0aW9uKCl7S3AucG9pbnQ9R0l0LEtwLmxpbmVTdGFydD1MNGUsS3AubGluZUVuZD1rNGUsSVQucmVzZXQoKSxFdS5wb2x5Z29uU3RhcnQoKX0scG9seWdvbkVuZDpmdW5jdGlvbigpe0V1LnBvbHlnb25FbmQoKSxLcC5wb2ludD1mSixLcC5saW5lU3RhcnQ9Rkl0LEtwLmxpbmVFbmQ9Qkl0LENUPDA/KG1pPS0oRWk9MTgwKSxobD0tKHhjPTkwKSk6SVQ+Y2U/eGM9OTA6SVQ8LWNlJiYoaGw9LTkwKSxacFswXT1taSxacFsxXT1FaX19fSk7ZnVuY3Rpb24gZ0ooZSx0KXtlKj13ZSx0Kj13ZTt2YXIgcj1hZSh0KTtrVChyKmFlKGUpLHIqSnQoZSksSnQodCkpfWZ1bmN0aW9uIGtUKGUsdCxyKXsrK0xULHREKz0oZS10RCkvTFQsZUQrPSh0LWVEKS9MVCxyRCs9KHItckQpL0xUfWZ1bmN0aW9uIGpJdCgpe1R1LnBvaW50PU40ZX1mdW5jdGlvbiBONGUoZSx0KXtlKj13ZSx0Kj13ZTt2YXIgcj1hZSh0KTtXYT1yKmFlKGUpLFlhPXIqSnQoZSksamE9SnQodCksVHUucG9pbnQ9RDRlLGtUKFdhLFlhLGphKX1mdW5jdGlvbiBENGUoZSx0KXtlKj13ZSx0Kj13ZTt2YXIgcj1hZSh0KSxuPXIqYWUoZSksaT1yKkp0KGUpLG89SnQodCksYT1TbihScigoYT1ZYSpvLWphKmkpKmErKGE9amEqbi1XYSpvKSphKyhhPVdhKmktWWEqbikqYSksV2EqbitZYSppK2phKm8pO1FOKz1hLG5EKz1hKihXYSsoV2E9bikpLGlEKz1hKihZYSsoWWE9aSkpLG9EKz1hKihqYSsoamE9bykpLGtUKFdhLFlhLGphKX1mdW5jdGlvbiBYSXQoKXtUdS5wb2ludD1nSn1mdW5jdGlvbiBPNGUoKXtUdS5wb2ludD1GNGV9ZnVuY3Rpb24gejRlKCl7Wkl0KCRJdCxLSXQpLFR1LnBvaW50PWdKfWZ1bmN0aW9uIEY0ZShlLHQpeyRJdD1lLEtJdD10LGUqPXdlLHQqPXdlLFR1LnBvaW50PVpJdDt2YXIgcj1hZSh0KTtXYT1yKmFlKGUpLFlhPXIqSnQoZSksamE9SnQodCksa1QoV2EsWWEsamEpfWZ1bmN0aW9uIFpJdChlLHQpe2UqPXdlLHQqPXdlO3ZhciByPWFlKHQpLG49ciphZShlKSxpPXIqSnQoZSksbz1KdCh0KSxhPVlhKm8tamEqaSxzPWphKm4tV2EqbyxsPVdhKmktWWEqbixjPVJyKGEqYStzKnMrbCpsKSx1PUpuKGMpLGg9YyYmLXUvYztwSis9aCphLGRKKz1oKnMsbUorPWgqbCxRTis9dSxuRCs9dSooV2ErKFdhPW4pKSxpRCs9dSooWWErKFlhPWkpKSxvRCs9dSooamErKGphPW8pKSxrVChXYSxZYSxqYSl9ZnVuY3Rpb24gSkl0KGUpe0xUPVFOPXREPWVEPXJEPW5EPWlEPW9EPXBKPWRKPW1KPTAsTW8oZSxUdSk7dmFyIHQ9cEoscj1kSixuPW1KLGk9dCp0K3IqcituKm47cmV0dXJuIGk8b0omJih0PW5ELHI9aUQsbj1vRCxRTjxjZSYmKHQ9dEQscj1lRCxuPXJEKSxpPXQqdCtyKnIrbipuLGk8b0opP1tOYU4sTmFOXTpbU24ocix0KSpVcixKbihuL1JyKGkpKSpVcl19dmFyIExULFFOLHRELGVELHJELG5ELGlELG9ELHBKLGRKLG1KLCRJdCxLSXQsV2EsWWEsamEsVHUsUUl0PU0oKCk9PntscigpO1hwKCk7bWcoKTtUdT17c3BoZXJlOnFyLHBvaW50OmdKLGxpbmVTdGFydDpqSXQsbGluZUVuZDpYSXQscG9seWdvblN0YXJ0OmZ1bmN0aW9uKCl7VHUubGluZVN0YXJ0PU80ZSxUdS5saW5lRW5kPXo0ZX0scG9seWdvbkVuZDpmdW5jdGlvbigpe1R1LmxpbmVTdGFydD1qSXQsVHUubGluZUVuZD1YSXR9fX0pO2Z1bmN0aW9uIHp5KGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX12YXIgdDl0PU0oKCk9Pnt9KTtmdW5jdGlvbiBhRChlLHQpe2Z1bmN0aW9uIHIobixpKXtyZXR1cm4gbj1lKG4saSksdChuWzBdLG5bMV0pfXJldHVybiBlLmludmVydCYmdC5pbnZlcnQmJihyLmludmVydD1mdW5jdGlvbihuLGkpe3JldHVybiBuPXQuaW52ZXJ0KG4saSksbiYmZS5pbnZlcnQoblswXSxuWzFdKX0pLHJ9dmFyIF9KPU0oKCk9Pnt9KTtmdW5jdGlvbiB5SihlLHQpe3JldHVybltlPnJyP2UtQmk6ZTwtcnI/ZStCaTplLHRdfWZ1bmN0aW9uIFJUKGUsdCxyKXtyZXR1cm4oZSU9QmkpP3R8fHI/YUQocjl0KGUpLG45dCh0LHIpKTpyOXQoZSk6dHx8cj9uOXQodCxyKTp5Sn1mdW5jdGlvbiBlOXQoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7cmV0dXJuIHQrPWUsW3Q+cnI/dC1CaTp0PC1ycj90K0JpOnQscl19fWZ1bmN0aW9uIHI5dChlKXt2YXIgdD1lOXQoZSk7cmV0dXJuIHQuaW52ZXJ0PWU5dCgtZSksdH1mdW5jdGlvbiBuOXQoZSx0KXt2YXIgcj1hZShlKSxuPUp0KGUpLGk9YWUodCksbz1KdCh0KTtmdW5jdGlvbiBhKHMsbCl7dmFyIGM9YWUobCksdT1hZShzKSpjLGg9SnQocykqYyxmPUp0KGwpLHA9ZipyK3UqbjtyZXR1cm5bU24oaCppLXAqbyx1KnItZipuKSxKbihwKmkraCpvKV19cmV0dXJuIGEuaW52ZXJ0PWZ1bmN0aW9uKHMsbCl7dmFyIGM9YWUobCksdT1hZShzKSpjLGg9SnQocykqYyxmPUp0KGwpLHA9ZippLWgqbztyZXR1cm5bU24oaCppK2Yqbyx1KnIrcCpuKSxKbihwKnItdSpuKV19LGF9ZnVuY3Rpb24gc0QoZSl7ZT1SVChlWzBdKndlLGVbMV0qd2UsZS5sZW5ndGg+Mj9lWzJdKndlOjApO2Z1bmN0aW9uIHQocil7cmV0dXJuIHI9ZShyWzBdKndlLHJbMV0qd2UpLHJbMF0qPVVyLHJbMV0qPVVyLHJ9cmV0dXJuIHQuaW52ZXJ0PWZ1bmN0aW9uKHIpe3JldHVybiByPWUuaW52ZXJ0KHJbMF0qd2UsclsxXSp3ZSksclswXSo9VXIsclsxXSo9VXIscn0sdH12YXIgTlQ9TSgoKT0+e19KKCk7bHIoKTt5Si5pbnZlcnQ9eUp9KTtmdW5jdGlvbiB2SihlLHQscixuLGksbyl7aWYoISFyKXt2YXIgYT1hZSh0KSxzPUp0KHQpLGw9bipyO2k9PW51bGw/KGk9dCtuKkJpLG89dC1sLzIpOihpPWk5dChhLGkpLG89aTl0KGEsbyksKG4+MD9pPG86aT5vKSYmKGkrPW4qQmkpKTtmb3IodmFyIGMsdT1pO24+MD91Pm86dTxvO3UtPWwpYz1OeShbYSwtcyphZSh1KSwtcypKdCh1KV0pLGUucG9pbnQoY1swXSxjWzFdKX19ZnVuY3Rpb24gaTl0KGUsdCl7dD12Yyh0KSx0WzBdLT1lLER5KHQpO3ZhciByPSROKC10WzFdKTtyZXR1cm4oKC10WzJdPDA/LXI6cikrQmktY2UpJUJpfWZ1bmN0aW9uIG85dCgpe3ZhciBlPXp5KFswLDBdKSx0PXp5KDkwKSxyPXp5KDYpLG4saSxvPXtwb2ludDphfTtmdW5jdGlvbiBhKGwsYyl7bi5wdXNoKGw9aShsLGMpKSxsWzBdKj1VcixsWzFdKj1Vcn1mdW5jdGlvbiBzKCl7dmFyIGw9ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyksYz10LmFwcGx5KHRoaXMsYXJndW1lbnRzKSp3ZSx1PXIuYXBwbHkodGhpcyxhcmd1bWVudHMpKndlO3JldHVybiBuPVtdLGk9UlQoLWxbMF0qd2UsLWxbMV0qd2UsMCkuaW52ZXJ0LHZKKG8sYyx1LDEpLGw9e3R5cGU6IlBvbHlnb24iLGNvb3JkaW5hdGVzOltuXX0sbj1pPW51bGwsbH1yZXR1cm4gcy5jZW50ZXI9ZnVuY3Rpb24obCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIGw9PSJmdW5jdGlvbiI/bDp6eShbK2xbMF0sK2xbMV1dKSxzKTplfSxzLnJhZGl1cz1mdW5jdGlvbihsKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD10eXBlb2YgbD09ImZ1bmN0aW9uIj9sOnp5KCtsKSxzKTp0fSxzLnByZWNpc2lvbj1mdW5jdGlvbihsKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2YgbD09ImZ1bmN0aW9uIj9sOnp5KCtsKSxzKTpyfSxzfXZhciB4Sj1NKCgpPT57QTIoKTt0OXQoKTtscigpO05UKCl9KTtmdW5jdGlvbiBsRCgpe3ZhciBlPVtdLHQ7cmV0dXJue3BvaW50OmZ1bmN0aW9uKHIsbil7dC5wdXNoKFtyLG5dKX0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7ZS5wdXNoKHQ9W10pfSxsaW5lRW5kOnFyLHJlam9pbjpmdW5jdGlvbigpe2UubGVuZ3RoPjEmJmUucHVzaChlLnBvcCgpLmNvbmNhdChlLnNoaWZ0KCkpKX0scmVzdWx0OmZ1bmN0aW9uKCl7dmFyIHI9ZTtyZXR1cm4gZT1bXSx0PW51bGwscn19fXZhciBiSj1NKCgpPT57WHAoKX0pO2Z1bmN0aW9uIEkyKGUsdCl7cmV0dXJuIFllKGVbMF0tdFswXSk8Y2UmJlllKGVbMV0tdFsxXSk8Y2V9dmFyIHdKPU0oKCk9PntscigpfSk7ZnVuY3Rpb24gY0QoZSx0LHIsbil7dGhpcy54PWUsdGhpcy56PXQsdGhpcy5vPXIsdGhpcy5lPW4sdGhpcy52PSExLHRoaXMubj10aGlzLnA9bnVsbH1mdW5jdGlvbiB1RChlLHQscixuLGkpe3ZhciBvPVtdLGE9W10scyxsO2lmKGUuZm9yRWFjaChmdW5jdGlvbihkKXtpZighKChnPWQubGVuZ3RoLTEpPD0wKSl7dmFyIGcsXz1kWzBdLHk9ZFtnXSx4O2lmKEkyKF8seSkpe2ZvcihpLmxpbmVTdGFydCgpLHM9MDtzPGc7KytzKWkucG9pbnQoKF89ZFtzXSlbMF0sX1sxXSk7aS5saW5lRW5kKCk7cmV0dXJufW8ucHVzaCh4PW5ldyBjRChfLGQsbnVsbCwhMCkpLGEucHVzaCh4Lm89bmV3IGNEKF8sbnVsbCx4LCExKSksby5wdXNoKHg9bmV3IGNEKHksZCxudWxsLCExKSksYS5wdXNoKHgubz1uZXcgY0QoeSxudWxsLHgsITApKX19KSwhIW8ubGVuZ3RoKXtmb3IoYS5zb3J0KHQpLGE5dChvKSxhOXQoYSkscz0wLGw9YS5sZW5ndGg7czxsOysrcylhW3NdLmU9cj0hcjtmb3IodmFyIGM9b1swXSx1LGg7Oyl7Zm9yKHZhciBmPWMscD0hMDtmLnY7KWlmKChmPWYubik9PT1jKXJldHVybjt1PWYueixpLmxpbmVTdGFydCgpO2Rve2lmKGYudj1mLm8udj0hMCxmLmUpe2lmKHApZm9yKHM9MCxsPXUubGVuZ3RoO3M8bDsrK3MpaS5wb2ludCgoaD11W3NdKVswXSxoWzFdKTtlbHNlIG4oZi54LGYubi54LDEsaSk7Zj1mLm59ZWxzZXtpZihwKWZvcih1PWYucC56LHM9dS5sZW5ndGgtMTtzPj0wOy0tcylpLnBvaW50KChoPXVbc10pWzBdLGhbMV0pO2Vsc2UgbihmLngsZi5wLngsLTEsaSk7Zj1mLnB9Zj1mLm8sdT1mLnoscD0hcH13aGlsZSghZi52KTtpLmxpbmVFbmQoKX19fWZ1bmN0aW9uIGE5dChlKXtpZighISh0PWUubGVuZ3RoKSl7Zm9yKHZhciB0LHI9MCxuPWVbMF0saTsrK3I8dDspbi5uPWk9ZVtyXSxpLnA9bixuPWk7bi5uPWk9ZVswXSxpLnA9bn19dmFyIFNKPU0oKCk9Pnt3SigpfSk7ZnVuY3Rpb24gaEQoZSx0KXt2YXIgcj10WzBdLG49dFsxXSxpPVtKdChyKSwtYWUociksMF0sbz0wLGE9MDtNSi5yZXNldCgpO2Zvcih2YXIgcz0wLGw9ZS5sZW5ndGg7czxsOysrcylpZighISh1PShjPWVbc10pLmxlbmd0aCkpZm9yKHZhciBjLHUsaD1jW3UtMV0sZj1oWzBdLHA9aFsxXS8yK1QyLGQ9SnQocCksZz1hZShwKSxfPTA7Xzx1OysrXyxmPXgsZD1TLGc9QyxoPXkpe3ZhciB5PWNbX10seD15WzBdLGI9eVsxXS8yK1QyLFM9SnQoYiksQz1hZShiKSxQPXgtZixrPVA+PTA/MTotMSxPPWsqUCxEPU8+cnIsQj1kKlM7aWYoTUouYWRkKFNuKEIqaypKdChPKSxnKkMrQiphZShPKSkpLG8rPUQ/UCtrKkJpOlAsRF5mPj1yXng+PXIpe3ZhciBJPSRwKHZjKGgpLHZjKHkpKTtEeShJKTt2YXIgTD0kcChpLEkpO0R5KEwpO3ZhciBSPShEXlA+PTA/LTE6MSkqSm4oTFsyXSk7KG4+Unx8bj09PVImJihJWzBdfHxJWzFdKSkmJihhKz1EXlA+PTA/MTotMSl9fXJldHVybihvPC1jZXx8bzxjZSYmTUo8LWNlKV5hJjF9dmFyIE1KLEVKPU0oKCk9PntreSgpO0EyKCk7bHIoKTtNSj1DcygpfSk7ZnVuY3Rpb24gX2coZSx0KXtyZXR1cm4gZTx0Py0xOmU+dD8xOmU+PXQ/MDpOYU59dmFyIEZ5PU0oKCk9Pnt9KTtmdW5jdGlvbiBUSihlKXtyZXR1cm4gZS5sZW5ndGg9PT0xJiYoZT1CNGUoZSkpLHtsZWZ0OmZ1bmN0aW9uKHQscixuLGkpe2ZvcihuPT1udWxsJiYobj0wKSxpPT1udWxsJiYoaT10Lmxlbmd0aCk7bjxpOyl7dmFyIG89bitpPj4+MTtlKHRbb10scik8MD9uPW8rMTppPW99cmV0dXJuIG59LHJpZ2h0OmZ1bmN0aW9uKHQscixuLGkpe2ZvcihuPT1udWxsJiYobj0wKSxpPT1udWxsJiYoaT10Lmxlbmd0aCk7bjxpOyl7dmFyIG89bitpPj4+MTtlKHRbb10scik+MD9pPW86bj1vKzF9cmV0dXJuIG59fX1mdW5jdGlvbiBCNGUoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7cmV0dXJuIF9nKGUodCkscil9fXZhciBDSj1NKCgpPT57RnkoKX0pO3ZhciBzOXQsSDRlLFY0ZSxBSj1NKCgpPT57RnkoKTtDSigpO3M5dD1USihfZyksSDRlPXM5dC5yaWdodCxWNGU9czl0LmxlZnR9KTt2YXIgUEo9TSgoKT0+e30pO3ZhciBsOXQ9TSgoKT0+e1BKKCl9KTt2YXIgYzl0PU0oKCk9Pnt9KTt2YXIgTDI9TSgoKT0+e30pO3ZhciBJSj1NKCgpPT57TDIoKX0pO3ZhciBMSj1NKCgpPT57SUooKX0pO3ZhciBrSj1NKCgpPT57fSk7dmFyIHU5dCxxNGUsRzRlLFJKPU0oKCk9Pnt1OXQ9QXJyYXkucHJvdG90eXBlLHE0ZT11OXQuc2xpY2UsRzRlPXU5dC5tYXB9KTt2YXIgaDl0PU0oKCk9Pnt9KTt2YXIgZjl0PU0oKCk9Pnt9KTtmdW5jdGlvbiBKcChlLHQscil7ZT0rZSx0PSt0LHI9KGk9YXJndW1lbnRzLmxlbmd0aCk8Mj8odD1lLGU9MCwxKTppPDM/MTorcjtmb3IodmFyIG49LTEsaT1NYXRoLm1heCgwLE1hdGguY2VpbCgodC1lKS9yKSl8MCxvPW5ldyBBcnJheShpKTsrK248aTspb1tuXT1lK24qcjtyZXR1cm4gb312YXIgTko9TSgoKT0+e30pO3ZhciBiMG4sdzBuLFMwbixESj1NKCgpPT57YjBuPU1hdGguc3FydCg1MCksdzBuPU1hdGguc3FydCgxMCksUzBuPU1hdGguc3FydCgyKX0pO3ZhciBPSj1NKCgpPT57fSk7dmFyIHA5dD1NKCgpPT57UkooKTtBSigpO2g5dCgpO2tKKCk7Zjl0KCk7TkooKTtESigpO09KKCl9KTt2YXIgcEQ9TSgoKT0+e0wyKCl9KTt2YXIgbTl0PU0oKCk9PntSSigpO0Z5KCk7TDIoKTtwRCgpfSk7dmFyIGc5dD1NKCgpPT57TEooKX0pO3ZhciBfOXQ9TSgoKT0+e30pO3ZhciB5OXQ9TSgoKT0+e0wyKCl9KTt2YXIgdjl0PU0oKCk9PntGeSgpO0wyKCk7cEQoKX0pO2Z1bmN0aW9uIERUKGUpe2Zvcih2YXIgdD1lLmxlbmd0aCxyLG49LTEsaT0wLG8sYTsrK248dDspaSs9ZVtuXS5sZW5ndGg7Zm9yKG89bmV3IEFycmF5KGkpOy0tdD49MDspZm9yKGE9ZVt0XSxyPWEubGVuZ3RoOy0tcj49MDspb1stLWldPWFbcl07cmV0dXJuIG99dmFyIHg5dD1NKCgpPT57fSk7dmFyIHpKPU0oKCk9Pnt9KTt2YXIgYjl0PU0oKCk9Pnt9KTt2YXIgdzl0PU0oKCk9PntGeSgpfSk7dmFyIFM5dD1NKCgpPT57fSk7dmFyIE05dD1NKCgpPT57fSk7dmFyIEZKPU0oKCk9Pnt6SigpfSk7dmFyIEU5dD1NKCgpPT57RkooKX0pO3ZhciBkRD1NKCgpPT57QUooKTtGeSgpO0NKKCk7bDl0KCk7Yzl0KCk7TEooKTtrSigpO3A5dCgpO205dCgpO2c5dCgpO09KKCk7Xzl0KCk7eTl0KCk7djl0KCk7eDl0KCk7ekooKTtQSigpO2I5dCgpO3BEKCk7TkooKTt3OXQoKTtTOXQoKTtNOXQoKTtESigpO0ZKKCk7SUooKTtFOXQoKX0pO2Z1bmN0aW9uIG1EKGUsdCxyLG4pe3JldHVybiBmdW5jdGlvbihpKXt2YXIgbz10KGkpLGE9bEQoKSxzPXQoYSksbD0hMSxjLHUsaCxmPXtwb2ludDpwLGxpbmVTdGFydDpnLGxpbmVFbmQ6Xyxwb2x5Z29uU3RhcnQ6ZnVuY3Rpb24oKXtmLnBvaW50PXksZi5saW5lU3RhcnQ9eCxmLmxpbmVFbmQ9Yix1PVtdLGM9W119LHBvbHlnb25FbmQ6ZnVuY3Rpb24oKXtmLnBvaW50PXAsZi5saW5lU3RhcnQ9ZyxmLmxpbmVFbmQ9Xyx1PURUKHUpO3ZhciBTPWhEKGMsbik7dS5sZW5ndGg/KGx8fChpLnBvbHlnb25TdGFydCgpLGw9ITApLHVEKHUsUTRlLFMscixpKSk6UyYmKGx8fChpLnBvbHlnb25TdGFydCgpLGw9ITApLGkubGluZVN0YXJ0KCkscihudWxsLG51bGwsMSxpKSxpLmxpbmVFbmQoKSksbCYmKGkucG9seWdvbkVuZCgpLGw9ITEpLHU9Yz1udWxsfSxzcGhlcmU6ZnVuY3Rpb24oKXtpLnBvbHlnb25TdGFydCgpLGkubGluZVN0YXJ0KCkscihudWxsLG51bGwsMSxpKSxpLmxpbmVFbmQoKSxpLnBvbHlnb25FbmQoKX19O2Z1bmN0aW9uIHAoUyxDKXtlKFMsQykmJmkucG9pbnQoUyxDKX1mdW5jdGlvbiBkKFMsQyl7by5wb2ludChTLEMpfWZ1bmN0aW9uIGcoKXtmLnBvaW50PWQsby5saW5lU3RhcnQoKX1mdW5jdGlvbiBfKCl7Zi5wb2ludD1wLG8ubGluZUVuZCgpfWZ1bmN0aW9uIHkoUyxDKXtoLnB1c2goW1MsQ10pLHMucG9pbnQoUyxDKX1mdW5jdGlvbiB4KCl7cy5saW5lU3RhcnQoKSxoPVtdfWZ1bmN0aW9uIGIoKXt5KGhbMF1bMF0saFswXVsxXSkscy5saW5lRW5kKCk7dmFyIFM9cy5jbGVhbigpLEM9YS5yZXN1bHQoKSxQLGs9Qy5sZW5ndGgsTyxELEI7aWYoaC5wb3AoKSxjLnB1c2goaCksaD1udWxsLCEhayl7aWYoUyYxKXtpZihEPUNbMF0sKE89RC5sZW5ndGgtMSk+MCl7Zm9yKGx8fChpLnBvbHlnb25TdGFydCgpLGw9ITApLGkubGluZVN0YXJ0KCksUD0wO1A8TzsrK1ApaS5wb2ludCgoQj1EW1BdKVswXSxCWzFdKTtpLmxpbmVFbmQoKX1yZXR1cm59az4xJiZTJjImJkMucHVzaChDLnBvcCgpLmNvbmNhdChDLnNoaWZ0KCkpKSx1LnB1c2goQy5maWx0ZXIoSjRlKSl9fXJldHVybiBmfX1mdW5jdGlvbiBKNGUoZSl7cmV0dXJuIGUubGVuZ3RoPjF9ZnVuY3Rpb24gUTRlKGUsdCl7cmV0dXJuKChlPWUueClbMF08MD9lWzFdLUJuLWNlOkJuLWVbMV0pLSgodD10LngpWzBdPDA/dFsxXS1Cbi1jZTpCbi10WzFdKX12YXIgQko9TSgoKT0+e2JKKCk7U0ooKTtscigpO0VKKCk7ZEQoKX0pO2Z1bmN0aW9uIHRQZShlKXt2YXIgdD1OYU4scj1OYU4sbj1OYU4saTtyZXR1cm57bGluZVN0YXJ0OmZ1bmN0aW9uKCl7ZS5saW5lU3RhcnQoKSxpPTF9LHBvaW50OmZ1bmN0aW9uKG8sYSl7dmFyIHM9bz4wP3JyOi1ycixsPVllKG8tdCk7WWUobC1ycik8Y2U/KGUucG9pbnQodCxyPShyK2EpLzI+MD9CbjotQm4pLGUucG9pbnQobixyKSxlLmxpbmVFbmQoKSxlLmxpbmVTdGFydCgpLGUucG9pbnQocyxyKSxlLnBvaW50KG8sciksaT0wKTpuIT09cyYmbD49cnImJihZZSh0LW4pPGNlJiYodC09bipjZSksWWUoby1zKTxjZSYmKG8tPXMqY2UpLHI9ZVBlKHQscixvLGEpLGUucG9pbnQobixyKSxlLmxpbmVFbmQoKSxlLmxpbmVTdGFydCgpLGUucG9pbnQocyxyKSxpPTApLGUucG9pbnQodD1vLHI9YSksbj1zfSxsaW5lRW5kOmZ1bmN0aW9uKCl7ZS5saW5lRW5kKCksdD1yPU5hTn0sY2xlYW46ZnVuY3Rpb24oKXtyZXR1cm4gMi1pfX19ZnVuY3Rpb24gZVBlKGUsdCxyLG4pe3ZhciBpLG8sYT1KdChlLXIpO3JldHVybiBZZShhKT5jZT95YygoSnQodCkqKG89YWUobikpKkp0KHIpLUp0KG4pKihpPWFlKHQpKSpKdChlKSkvKGkqbyphKSk6KHQrbikvMn1mdW5jdGlvbiByUGUoZSx0LHIsbil7dmFyIGk7aWYoZT09bnVsbClpPXIqQm4sbi5wb2ludCgtcnIsaSksbi5wb2ludCgwLGkpLG4ucG9pbnQocnIsaSksbi5wb2ludChyciwwKSxuLnBvaW50KHJyLC1pKSxuLnBvaW50KDAsLWkpLG4ucG9pbnQoLXJyLC1pKSxuLnBvaW50KC1yciwwKSxuLnBvaW50KC1ycixpKTtlbHNlIGlmKFllKGVbMF0tdFswXSk+Y2Upe3ZhciBvPWVbMF08dFswXT9ycjotcnI7aT1yKm8vMixuLnBvaW50KC1vLGkpLG4ucG9pbnQoMCxpKSxuLnBvaW50KG8saSl9ZWxzZSBuLnBvaW50KHRbMF0sdFsxXSl9dmFyIE9ULEhKPU0oKCk9PntCSigpO2xyKCk7T1Q9bUQoZnVuY3Rpb24oKXtyZXR1cm4hMH0sdFBlLHJQZSxbLXJyLC1Cbl0pfSk7ZnVuY3Rpb24gZ0QoZSl7dmFyIHQ9YWUoZSkscj02KndlLG49dD4wLGk9WWUodCk+Y2U7ZnVuY3Rpb24gbyh1LGgsZixwKXt2SihwLGUscixmLHUsaCl9ZnVuY3Rpb24gYSh1LGgpe3JldHVybiBhZSh1KSphZShoKT50fWZ1bmN0aW9uIHModSl7dmFyIGgsZixwLGQsZztyZXR1cm57bGluZVN0YXJ0OmZ1bmN0aW9uKCl7ZD1wPSExLGc9MX0scG9pbnQ6ZnVuY3Rpb24oXyx5KXt2YXIgeD1bXyx5XSxiLFM9YShfLHkpLEM9bj9TPzA6YyhfLHkpOlM/YyhfKyhfPDA/cnI6LXJyKSx5KTowO2lmKCFoJiYoZD1wPVMpJiZ1LmxpbmVTdGFydCgpLFMhPT1wJiYoYj1sKGgseCksKCFifHxJMihoLGIpfHxJMih4LGIpKSYmKHhbMF0rPWNlLHhbMV0rPWNlLFM9YSh4WzBdLHhbMV0pKSksUyE9PXApZz0wLFM/KHUubGluZVN0YXJ0KCksYj1sKHgsaCksdS5wb2ludChiWzBdLGJbMV0pKTooYj1sKGgseCksdS5wb2ludChiWzBdLGJbMV0pLHUubGluZUVuZCgpKSxoPWI7ZWxzZSBpZihpJiZoJiZuXlMpe3ZhciBQOyEoQyZmKSYmKFA9bCh4LGgsITApKSYmKGc9MCxuPyh1LmxpbmVTdGFydCgpLHUucG9pbnQoUFswXVswXSxQWzBdWzFdKSx1LnBvaW50KFBbMV1bMF0sUFsxXVsxXSksdS5saW5lRW5kKCkpOih1LnBvaW50KFBbMV1bMF0sUFsxXVsxXSksdS5saW5lRW5kKCksdS5saW5lU3RhcnQoKSx1LnBvaW50KFBbMF1bMF0sUFswXVsxXSkpKX1TJiYoIWh8fCFJMihoLHgpKSYmdS5wb2ludCh4WzBdLHhbMV0pLGg9eCxwPVMsZj1DfSxsaW5lRW5kOmZ1bmN0aW9uKCl7cCYmdS5saW5lRW5kKCksaD1udWxsfSxjbGVhbjpmdW5jdGlvbigpe3JldHVybiBnfChkJiZwKTw8MX19fWZ1bmN0aW9uIGwodSxoLGYpe3ZhciBwPXZjKHUpLGQ9dmMoaCksZz1bMSwwLDBdLF89JHAocCxkKSx5PUFUKF8sXykseD1fWzBdLGI9eS14Kng7aWYoIWIpcmV0dXJuIWYmJnU7dmFyIFM9dCp5L2IsQz0tdCp4L2IsUD0kcChnLF8pLGs9UFQoZyxTKSxPPVBUKF8sQyk7Sk4oayxPKTt2YXIgRD1QLEI9QVQoayxEKSxJPUFUKEQsRCksTD1CKkItSSooQVQoayxrKS0xKTtpZighKEw8MCkpe3ZhciBSPVJyKEwpLEY9UFQoRCwoLUItUikvSSk7aWYoSk4oRixrKSxGPU55KEYpLCFmKXJldHVybiBGO3ZhciB6PXVbMF0sVT1oWzBdLFc9dVsxXSxaPWhbMV0scnQ7VTx6JiYocnQ9eix6PVUsVT1ydCk7dmFyIG90PVUteixzdD1ZZShvdC1ycik8Y2UsU3Q9c3R8fG90PGNlO2lmKCFzdCYmWjxXJiYocnQ9VyxXPVosWj1ydCksU3Q/c3Q/VytaPjBeRlsxXTwoWWUoRlswXS16KTxjZT9XOlopOlc8PUZbMV0mJkZbMV08PVo6b3Q+cnJeKHo8PUZbMF0mJkZbMF08PVUpKXt2YXIgYnQ9UFQoRCwoLUIrUikvSSk7cmV0dXJuIEpOKGJ0LGspLFtGLE55KGJ0KV19fX1mdW5jdGlvbiBjKHUsaCl7dmFyIGY9bj9lOnJyLWUscD0wO3JldHVybiB1PC1mP3B8PTE6dT5mJiYocHw9MiksaDwtZj9wfD00Omg+ZiYmKHB8PTgpLHB9cmV0dXJuIG1EKGEscyxvLG4/WzAsLWVdOlstcnIsZS1ycl0pfXZhciBWSj1NKCgpPT57QTIoKTt4SigpO2xyKCk7d0ooKTtCSigpfSk7ZnVuY3Rpb24gVDl0KGUsdCxyLG4saSxvKXt2YXIgYT1lWzBdLHM9ZVsxXSxsPXRbMF0sYz10WzFdLHU9MCxoPTEsZj1sLWEscD1jLXMsZDtpZihkPXItYSwhKCFmJiZkPjApKXtpZihkLz1mLGY8MCl7aWYoZDx1KXJldHVybjtkPGgmJihoPWQpfWVsc2UgaWYoZj4wKXtpZihkPmgpcmV0dXJuO2Q+dSYmKHU9ZCl9aWYoZD1pLWEsISghZiYmZDwwKSl7aWYoZC89ZixmPDApe2lmKGQ+aClyZXR1cm47ZD51JiYodT1kKX1lbHNlIGlmKGY+MCl7aWYoZDx1KXJldHVybjtkPGgmJihoPWQpfWlmKGQ9bi1zLCEoIXAmJmQ+MCkpe2lmKGQvPXAscDwwKXtpZihkPHUpcmV0dXJuO2Q8aCYmKGg9ZCl9ZWxzZSBpZihwPjApe2lmKGQ+aClyZXR1cm47ZD51JiYodT1kKX1pZihkPW8tcywhKCFwJiZkPDApKXtpZihkLz1wLHA8MCl7aWYoZD5oKXJldHVybjtkPnUmJih1PWQpfWVsc2UgaWYocD4wKXtpZihkPHUpcmV0dXJuO2Q8aCYmKGg9ZCl9cmV0dXJuIHU+MCYmKGVbMF09YSt1KmYsZVsxXT1zK3UqcCksaDwxJiYodFswXT1hK2gqZix0WzFdPXMraCpwKSwhMH19fX19dmFyIEM5dD1NKCgpPT57fSk7ZnVuY3Rpb24gUXAoZSx0LHIsbil7ZnVuY3Rpb24gaShjLHUpe3JldHVybiBlPD1jJiZjPD1yJiZ0PD11JiZ1PD1ufWZ1bmN0aW9uIG8oYyx1LGgsZil7dmFyIHA9MCxkPTA7aWYoYz09bnVsbHx8KHA9YShjLGgpKSE9PShkPWEodSxoKSl8fGwoYyx1KTwwXmg+MClkbyBmLnBvaW50KHA9PT0wfHxwPT09Mz9lOnIscD4xP246dCk7d2hpbGUoKHA9KHAraCs0KSU0KSE9PWQpO2Vsc2UgZi5wb2ludCh1WzBdLHVbMV0pfWZ1bmN0aW9uIGEoYyx1KXtyZXR1cm4gWWUoY1swXS1lKTxjZT91PjA/MDozOlllKGNbMF0tcik8Y2U/dT4wPzI6MTpZZShjWzFdLXQpPGNlP3U+MD8xOjA6dT4wPzM6Mn1mdW5jdGlvbiBzKGMsdSl7cmV0dXJuIGwoYy54LHUueCl9ZnVuY3Rpb24gbChjLHUpe3ZhciBoPWEoYywxKSxmPWEodSwxKTtyZXR1cm4gaCE9PWY/aC1mOmg9PT0wP3VbMV0tY1sxXTpoPT09MT9jWzBdLXVbMF06aD09PTI/Y1sxXS11WzFdOnVbMF0tY1swXX1yZXR1cm4gZnVuY3Rpb24oYyl7dmFyIHU9YyxoPWxEKCksZixwLGQsZyxfLHkseCxiLFMsQyxQLGs9e3BvaW50Ok8sbGluZVN0YXJ0OkwsbGluZUVuZDpSLHBvbHlnb25TdGFydDpCLHBvbHlnb25FbmQ6SX07ZnVuY3Rpb24gTyh6LFUpe2koeixVKSYmdS5wb2ludCh6LFUpfWZ1bmN0aW9uIEQoKXtmb3IodmFyIHo9MCxVPTAsVz1wLmxlbmd0aDtVPFc7KytVKWZvcih2YXIgWj1wW1VdLHJ0PTEsb3Q9Wi5sZW5ndGgsc3Q9WlswXSxTdCxidCxNdD1zdFswXSxsdD1zdFsxXTtydDxvdDsrK3J0KVN0PU10LGJ0PWx0LHN0PVpbcnRdLE10PXN0WzBdLGx0PXN0WzFdLGJ0PD1uP2x0Pm4mJihNdC1TdCkqKG4tYnQpPihsdC1idCkqKGUtU3QpJiYrK3o6bHQ8PW4mJihNdC1TdCkqKG4tYnQpPChsdC1idCkqKGUtU3QpJiYtLXo7cmV0dXJuIHp9ZnVuY3Rpb24gQigpe3U9aCxmPVtdLHA9W10sUD0hMH1mdW5jdGlvbiBJKCl7dmFyIHo9RCgpLFU9UCYmeixXPShmPURUKGYpKS5sZW5ndGg7KFV8fFcpJiYoYy5wb2x5Z29uU3RhcnQoKSxVJiYoYy5saW5lU3RhcnQoKSxvKG51bGwsbnVsbCwxLGMpLGMubGluZUVuZCgpKSxXJiZ1RChmLHMseixvLGMpLGMucG9seWdvbkVuZCgpKSx1PWMsZj1wPWQ9bnVsbH1mdW5jdGlvbiBMKCl7ay5wb2ludD1GLHAmJnAucHVzaChkPVtdKSxDPSEwLFM9ITEseD1iPU5hTn1mdW5jdGlvbiBSKCl7ZiYmKEYoZyxfKSx5JiZTJiZoLnJlam9pbigpLGYucHVzaChoLnJlc3VsdCgpKSksay5wb2ludD1PLFMmJnUubGluZUVuZCgpfWZ1bmN0aW9uIEYoeixVKXt2YXIgVz1pKHosVSk7aWYocCYmZC5wdXNoKFt6LFVdKSxDKWc9eixfPVUseT1XLEM9ITEsVyYmKHUubGluZVN0YXJ0KCksdS5wb2ludCh6LFUpKTtlbHNlIGlmKFcmJlMpdS5wb2ludCh6LFUpO2Vsc2V7dmFyIFo9W3g9TWF0aC5tYXgoX0QsTWF0aC5taW4oelQseCkpLGI9TWF0aC5tYXgoX0QsTWF0aC5taW4oelQsYikpXSxydD1bej1NYXRoLm1heChfRCxNYXRoLm1pbih6VCx6KSksVT1NYXRoLm1heChfRCxNYXRoLm1pbih6VCxVKSldO1Q5dChaLHJ0LGUsdCxyLG4pPyhTfHwodS5saW5lU3RhcnQoKSx1LnBvaW50KFpbMF0sWlsxXSkpLHUucG9pbnQocnRbMF0scnRbMV0pLFd8fHUubGluZUVuZCgpLFA9ITEpOlcmJih1LmxpbmVTdGFydCgpLHUucG9pbnQoeixVKSxQPSExKX14PXosYj1VLFM9V31yZXR1cm4ga319dmFyIHpULF9ELEZUPU0oKCk9PntscigpO2JKKCk7Qzl0KCk7U0ooKTtkRCgpO3pUPTFlOSxfRD0telR9KTtmdW5jdGlvbiBBOXQoKXt2YXIgZT0wLHQ9MCxyPTk2MCxuPTUwMCxpLG8sYTtyZXR1cm4gYT17c3RyZWFtOmZ1bmN0aW9uKHMpe3JldHVybiBpJiZvPT09cz9pOmk9UXAoZSx0LHIsbikobz1zKX0sZXh0ZW50OmZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPStzWzBdWzBdLHQ9K3NbMF1bMV0scj0rc1sxXVswXSxuPStzWzFdWzFdLGk9bz1udWxsLGEpOltbZSx0XSxbcixuXV19fX12YXIgUDl0PU0oKCk9PntGVCgpfSk7ZnVuY3Rpb24gblBlKCl7azIucG9pbnQ9b1BlLGsyLmxpbmVFbmQ9aVBlfWZ1bmN0aW9uIGlQZSgpe2syLnBvaW50PWsyLmxpbmVFbmQ9cXJ9ZnVuY3Rpb24gb1BlKGUsdCl7ZSo9d2UsdCo9d2UscUo9ZSx5RD1KdCh0KSx2RD1hZSh0KSxrMi5wb2ludD1hUGV9ZnVuY3Rpb24gYVBlKGUsdCl7ZSo9d2UsdCo9d2U7dmFyIHI9SnQodCksbj1hZSh0KSxpPVllKGUtcUopLG89YWUoaSksYT1KdChpKSxzPW4qYSxsPXZEKnIteUQqbipvLGM9eUQqcit2RCpuKm87VUouYWRkKFNuKFJyKHMqcytsKmwpLGMpKSxxSj1lLHlEPXIsdkQ9bn1mdW5jdGlvbiB4RChlKXtyZXR1cm4gVUoucmVzZXQoKSxNbyhlLGsyKSwrVUp9dmFyIFVKLHFKLHlELHZELGsyLEdKPU0oKCk9PntreSgpO2xyKCk7WHAoKTttZygpO1VKPUNzKCksazI9e3NwaGVyZTpxcixwb2ludDpxcixsaW5lU3RhcnQ6blBlLGxpbmVFbmQ6cXIscG9seWdvblN0YXJ0OnFyLHBvbHlnb25FbmQ6cXJ9fSk7ZnVuY3Rpb24gQnkoZSx0KXtyZXR1cm4gV0pbMF09ZSxXSlsxXT10LHhEKHNQZSl9dmFyIFdKLHNQZSxZSj1NKCgpPT57R0ooKTtXSj1bbnVsbCxudWxsXSxzUGU9e3R5cGU6IkxpbmVTdHJpbmciLGNvb3JkaW5hdGVzOldKfX0pO2Z1bmN0aW9uIGJEKGUsdCl7cmV0dXJuIGUmJkw5dC5oYXNPd25Qcm9wZXJ0eShlLnR5cGUpP0w5dFtlLnR5cGVdKGUsdCk6ITF9ZnVuY3Rpb24gazl0KGUsdCl7cmV0dXJuIEJ5KGUsdCk9PT0wfWZ1bmN0aW9uIFI5dChlLHQpe3ZhciByPUJ5KGVbMF0sZVsxXSksbj1CeShlWzBdLHQpLGk9QnkodCxlWzFdKTtyZXR1cm4gbitpPD1yK2NlfWZ1bmN0aW9uIE45dChlLHQpe3JldHVybiEhaEQoZS5tYXAobFBlKSxEOXQodCkpfWZ1bmN0aW9uIGxQZShlKXtyZXR1cm4gZT1lLm1hcChEOXQpLGUucG9wKCksZX1mdW5jdGlvbiBEOXQoZSl7cmV0dXJuW2VbMF0qd2UsZVsxXSp3ZV19ZnVuY3Rpb24gTzl0KGUsdCl7cmV0dXJuKGUmJkk5dC5oYXNPd25Qcm9wZXJ0eShlLnR5cGUpP0k5dFtlLnR5cGVdOmJEKShlLHQpfXZhciBJOXQsTDl0LHo5dD1NKCgpPT57RUooKTtZSigpO2xyKCk7STl0PXtGZWF0dXJlOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIGJEKGUuZ2VvbWV0cnksdCl9LEZlYXR1cmVDb2xsZWN0aW9uOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuZmVhdHVyZXMsbj0tMSxpPXIubGVuZ3RoOysrbjxpOylpZihiRChyW25dLmdlb21ldHJ5LHQpKXJldHVybiEwO3JldHVybiExfX0sTDl0PXtTcGhlcmU6ZnVuY3Rpb24oKXtyZXR1cm4hMH0sUG9pbnQ6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gazl0KGUuY29vcmRpbmF0ZXMsdCl9LE11bHRpUG9pbnQ6ZnVuY3Rpb24oZSx0KXtmb3IodmFyIHI9ZS5jb29yZGluYXRlcyxuPS0xLGk9ci5sZW5ndGg7KytuPGk7KWlmKGs5dChyW25dLHQpKXJldHVybiEwO3JldHVybiExfSxMaW5lU3RyaW5nOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIFI5dChlLmNvb3JkaW5hdGVzLHQpfSxNdWx0aUxpbmVTdHJpbmc6ZnVuY3Rpb24oZSx0KXtmb3IodmFyIHI9ZS5jb29yZGluYXRlcyxuPS0xLGk9ci5sZW5ndGg7KytuPGk7KWlmKFI5dChyW25dLHQpKXJldHVybiEwO3JldHVybiExfSxQb2x5Z29uOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIE45dChlLmNvb3JkaW5hdGVzLHQpfSxNdWx0aVBvbHlnb246ZnVuY3Rpb24oZSx0KXtmb3IodmFyIHI9ZS5jb29yZGluYXRlcyxuPS0xLGk9ci5sZW5ndGg7KytuPGk7KWlmKE45dChyW25dLHQpKXJldHVybiEwO3JldHVybiExfSxHZW9tZXRyeUNvbGxlY3Rpb246ZnVuY3Rpb24oZSx0KXtmb3IodmFyIHI9ZS5nZW9tZXRyaWVzLG49LTEsaT1yLmxlbmd0aDsrK248aTspaWYoYkQocltuXSx0KSlyZXR1cm4hMDtyZXR1cm4hMX19fSk7ZnVuY3Rpb24gRjl0KGUsdCxyKXt2YXIgbj1KcChlLHQtY2UscikuY29uY2F0KHQpO3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4gbi5tYXAoZnVuY3Rpb24obyl7cmV0dXJuW2ksb119KX19ZnVuY3Rpb24gQjl0KGUsdCxyKXt2YXIgbj1KcChlLHQtY2UscikuY29uY2F0KHQpO3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4gbi5tYXAoZnVuY3Rpb24obyl7cmV0dXJuW28saV19KX19ZnVuY3Rpb24gd0QoKXt2YXIgZSx0LHIsbixpLG8sYSxzLGw9MTAsYz1sLHU9OTAsaD0zNjAsZixwLGQsZyxfPTIuNTtmdW5jdGlvbiB5KCl7cmV0dXJue3R5cGU6Ik11bHRpTGluZVN0cmluZyIsY29vcmRpbmF0ZXM6eCgpfX1mdW5jdGlvbiB4KCl7cmV0dXJuIEpwKFRUKG4vdSkqdSxyLHUpLm1hcChkKS5jb25jYXQoSnAoVFQocy9oKSpoLGEsaCkubWFwKGcpKS5jb25jYXQoSnAoVFQodC9sKSpsLGUsbCkuZmlsdGVyKGZ1bmN0aW9uKGIpe3JldHVybiBZZShiJXUpPmNlfSkubWFwKGYpKS5jb25jYXQoSnAoVFQoby9jKSpjLGksYykuZmlsdGVyKGZ1bmN0aW9uKGIpe3JldHVybiBZZShiJWgpPmNlfSkubWFwKHApKX1yZXR1cm4geS5saW5lcz1mdW5jdGlvbigpe3JldHVybiB4KCkubWFwKGZ1bmN0aW9uKGIpe3JldHVybnt0eXBlOiJMaW5lU3RyaW5nIixjb29yZGluYXRlczpifX0pfSx5Lm91dGxpbmU9ZnVuY3Rpb24oKXtyZXR1cm57dHlwZToiUG9seWdvbiIsY29vcmRpbmF0ZXM6W2QobikuY29uY2F0KGcoYSkuc2xpY2UoMSksZChyKS5yZXZlcnNlKCkuc2xpY2UoMSksZyhzKS5yZXZlcnNlKCkuc2xpY2UoMSkpXX19LHkuZXh0ZW50PWZ1bmN0aW9uKGIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3kuZXh0ZW50TWFqb3IoYikuZXh0ZW50TWlub3IoYik6eS5leHRlbnRNaW5vcigpfSx5LmV4dGVudE1ham9yPWZ1bmN0aW9uKGIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPStiWzBdWzBdLHI9K2JbMV1bMF0scz0rYlswXVsxXSxhPStiWzFdWzFdLG4+ciYmKGI9bixuPXIscj1iKSxzPmEmJihiPXMscz1hLGE9YikseS5wcmVjaXNpb24oXykpOltbbixzXSxbcixhXV19LHkuZXh0ZW50TWlub3I9ZnVuY3Rpb24oYil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9K2JbMF1bMF0sZT0rYlsxXVswXSxvPStiWzBdWzFdLGk9K2JbMV1bMV0sdD5lJiYoYj10LHQ9ZSxlPWIpLG8+aSYmKGI9byxvPWksaT1iKSx5LnByZWNpc2lvbihfKSk6W1t0LG9dLFtlLGldXX0seS5zdGVwPWZ1bmN0aW9uKGIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3kuc3RlcE1ham9yKGIpLnN0ZXBNaW5vcihiKTp5LnN0ZXBNaW5vcigpfSx5LnN0ZXBNYWpvcj1mdW5jdGlvbihiKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odT0rYlswXSxoPStiWzFdLHkpOlt1LGhdfSx5LnN0ZXBNaW5vcj1mdW5jdGlvbihiKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obD0rYlswXSxjPStiWzFdLHkpOltsLGNdfSx5LnByZWNpc2lvbj1mdW5jdGlvbihiKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oXz0rYixmPUY5dChvLGksOTApLHA9Qjl0KHQsZSxfKSxkPUY5dChzLGEsOTApLGc9Qjl0KG4scixfKSx5KTpffSx5LmV4dGVudE1ham9yKFtbLTE4MCwtOTArY2VdLFsxODAsOTAtY2VdXSkuZXh0ZW50TWlub3IoW1stMTgwLC04MC1jZV0sWzE4MCw4MCtjZV1dKX1mdW5jdGlvbiBIOXQoKXtyZXR1cm4gd0QoKSgpfXZhciBWOXQ9TSgoKT0+e2REKCk7bHIoKX0pO2Z1bmN0aW9uIFU5dChlLHQpe3ZhciByPWVbMF0qd2Usbj1lWzFdKndlLGk9dFswXSp3ZSxvPXRbMV0qd2UsYT1hZShuKSxzPUp0KG4pLGw9YWUobyksYz1KdChvKSx1PWEqYWUociksaD1hKkp0KHIpLGY9bCphZShpKSxwPWwqSnQoaSksZD0yKkpuKFJyKGFKKG8tbikrYSpsKmFKKGktcikpKSxnPUp0KGQpLF89ZD9mdW5jdGlvbih5KXt2YXIgeD1KdCh5Kj1kKS9nLGI9SnQoZC15KS9nLFM9Yip1K3gqZixDPWIqaCt4KnAsUD1iKnMreCpjO3JldHVybltTbihDLFMpKlVyLFNuKFAsUnIoUypTK0MqQykpKlVyXX06ZnVuY3Rpb24oKXtyZXR1cm5bcipVcixuKlVyXX07cmV0dXJuIF8uZGlzdGFuY2U9ZCxffXZhciBxOXQ9TSgoKT0+e2xyKCl9KTtmdW5jdGlvbiBuZihlKXtyZXR1cm4gZX12YXIgU0Q9TSgoKT0+e30pO2Z1bmN0aW9uIGNQZSgpe3lnLnBvaW50PXVQZX1mdW5jdGlvbiB1UGUoZSx0KXt5Zy5wb2ludD1ZOXQsRzl0PSRKPWUsVzl0PUtKPXR9ZnVuY3Rpb24gWTl0KGUsdCl7WEouYWRkKEtKKmUtJEoqdCksJEo9ZSxLSj10fWZ1bmN0aW9uIGhQZSgpe1k5dChHOXQsVzl0KX12YXIgakosWEosRzl0LFc5dCwkSixLSix5ZyxaSixqOXQ9TSgoKT0+e2t5KCk7bHIoKTtYcCgpO2pKPUNzKCksWEo9Q3MoKSx5Zz17cG9pbnQ6cXIsbGluZVN0YXJ0OnFyLGxpbmVFbmQ6cXIscG9seWdvblN0YXJ0OmZ1bmN0aW9uKCl7eWcubGluZVN0YXJ0PWNQZSx5Zy5saW5lRW5kPWhQZX0scG9seWdvbkVuZDpmdW5jdGlvbigpe3lnLmxpbmVTdGFydD15Zy5saW5lRW5kPXlnLnBvaW50PXFyLGpKLmFkZChZZShYSikpLFhKLnJlc2V0KCl9LHJlc3VsdDpmdW5jdGlvbigpe3ZhciBlPWpKLzI7cmV0dXJuIGpKLnJlc2V0KCksZX19O1pKPXlnfSk7ZnVuY3Rpb24gcFBlKGUsdCl7ZTxSMiYmKFIyPWUpLGU+QlQmJihCVD1lKSx0PE1EJiYoTUQ9dCksdD5FRCYmKEVEPXQpfXZhciBSMixNRCxCVCxFRCxmUGUsTjIsSko9TSgoKT0+e1hwKCk7UjI9MS8wLE1EPVIyLEJUPS1SMixFRD1CVCxmUGU9e3BvaW50OnBQZSxsaW5lU3RhcnQ6cXIsbGluZUVuZDpxcixwb2x5Z29uU3RhcnQ6cXIscG9seWdvbkVuZDpxcixyZXN1bHQ6ZnVuY3Rpb24oKXt2YXIgZT1bW1IyLE1EXSxbQlQsRURdXTtyZXR1cm4gQlQ9RUQ9LShNRD1SMj0xLzApLGV9fTtOMj1mUGV9KTtmdW5jdGlvbiBIeShlLHQpe1FKKz1lLHRRKz10LCsrSFR9ZnVuY3Rpb24gWDl0KCl7Q3UucG9pbnQ9ZFBlfWZ1bmN0aW9uIGRQZShlLHQpe0N1LnBvaW50PW1QZSxIeShvZj1lLGFmPXQpfWZ1bmN0aW9uIG1QZShlLHQpe3ZhciByPWUtb2Ysbj10LWFmLGk9UnIocipyK24qbik7VEQrPWkqKG9mK2UpLzIsQ0QrPWkqKGFmK3QpLzIsRDIrPWksSHkob2Y9ZSxhZj10KX1mdW5jdGlvbiAkOXQoKXtDdS5wb2ludD1IeX1mdW5jdGlvbiBnUGUoKXtDdS5wb2ludD15UGV9ZnVuY3Rpb24gX1BlKCl7Sjl0KEs5dCxaOXQpfWZ1bmN0aW9uIHlQZShlLHQpe0N1LnBvaW50PUo5dCxIeShLOXQ9b2Y9ZSxaOXQ9YWY9dCl9ZnVuY3Rpb24gSjl0KGUsdCl7dmFyIHI9ZS1vZixuPXQtYWYsaT1ScihyKnIrbipuKTtURCs9aSoob2YrZSkvMixDRCs9aSooYWYrdCkvMixEMis9aSxpPWFmKmUtb2YqdCxlUSs9aSoob2YrZSksclErPWkqKGFmK3QpLFZUKz1pKjMsSHkob2Y9ZSxhZj10KX12YXIgUUosdFEsSFQsVEQsQ0QsRDIsZVEsclEsVlQsSzl0LFo5dCxvZixhZixDdSxuUSxROXQ9TSgoKT0+e2xyKCk7UUo9MCx0UT0wLEhUPTAsVEQ9MCxDRD0wLEQyPTAsZVE9MCxyUT0wLFZUPTAsQ3U9e3BvaW50Okh5LGxpbmVTdGFydDpYOXQsbGluZUVuZDokOXQscG9seWdvblN0YXJ0OmZ1bmN0aW9uKCl7Q3UubGluZVN0YXJ0PWdQZSxDdS5saW5lRW5kPV9QZX0scG9seWdvbkVuZDpmdW5jdGlvbigpe0N1LnBvaW50PUh5LEN1LmxpbmVTdGFydD1YOXQsQ3UubGluZUVuZD0kOXR9LHJlc3VsdDpmdW5jdGlvbigpe3ZhciBlPVZUP1tlUS9WVCxyUS9WVF06RDI/W1REL0QyLENEL0QyXTpIVD9bUUovSFQsdFEvSFRdOltOYU4sTmFOXTtyZXR1cm4gUUo9dFE9SFQ9VEQ9Q0Q9RDI9ZVE9clE9VlQ9MCxlfX07blE9Q3V9KTtmdW5jdGlvbiBBRChlKXt0aGlzLl9jb250ZXh0PWV9dmFyIHRMdD1NKCgpPT57bHIoKTtYcCgpO0FELnByb3RvdHlwZT17X3JhZGl1czo0LjUscG9pbnRSYWRpdXM6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX3JhZGl1cz1lLHRoaXN9LHBvbHlnb25TdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0scG9seWdvbkVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT09PTAmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCksdGhpcy5fcG9pbnQ9TmFOfSxwb2ludDpmdW5jdGlvbihlLHQpe3N3aXRjaCh0aGlzLl9wb2ludCl7Y2FzZSAwOnt0aGlzLl9jb250ZXh0Lm1vdmVUbyhlLHQpLHRoaXMuX3BvaW50PTE7YnJlYWt9Y2FzZSAxOnt0aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpO2JyZWFrfWRlZmF1bHQ6e3RoaXMuX2NvbnRleHQubW92ZVRvKGUrdGhpcy5fcmFkaXVzLHQpLHRoaXMuX2NvbnRleHQuYXJjKGUsdCx0aGlzLl9yYWRpdXMsMCxCaSk7YnJlYWt9fX0scmVzdWx0OnFyfX0pO2Z1bmN0aW9uIHZQZShlLHQpe1BELnBvaW50PW5MdCxlTHQ9VVQ9ZSxyTHQ9cVQ9dH1mdW5jdGlvbiBuTHQoZSx0KXtVVC09ZSxxVC09dCxvUS5hZGQoUnIoVVQqVVQrcVQqcVQpKSxVVD1lLHFUPXR9dmFyIG9RLGlRLGVMdCxyTHQsVVQscVQsUEQsYVEsaUx0PU0oKCk9PntreSgpO2xyKCk7WHAoKTtvUT1DcygpLFBEPXtwb2ludDpxcixsaW5lU3RhcnQ6ZnVuY3Rpb24oKXtQRC5wb2ludD12UGV9LGxpbmVFbmQ6ZnVuY3Rpb24oKXtpUSYmbkx0KGVMdCxyTHQpLFBELnBvaW50PXFyfSxwb2x5Z29uU3RhcnQ6ZnVuY3Rpb24oKXtpUT0hMH0scG9seWdvbkVuZDpmdW5jdGlvbigpe2lRPW51bGx9LHJlc3VsdDpmdW5jdGlvbigpe3ZhciBlPStvUTtyZXR1cm4gb1EucmVzZXQoKSxlfX07YVE9UER9KTtmdW5jdGlvbiBJRCgpe3RoaXMuX3N0cmluZz1bXX1mdW5jdGlvbiBvTHQoZSl7cmV0dXJuIm0wLCIrZSsiYSIrZSsiLCIrZSsiIDAgMSwxIDAsIistMiplKyJhIitlKyIsIitlKyIgMCAxLDEgMCwiKzIqZSsieiJ9dmFyIGFMdD1NKCgpPT57SUQucHJvdG90eXBlPXtfcmFkaXVzOjQuNSxfY2lyY2xlOm9MdCg0LjUpLHBvaW50UmFkaXVzOmZ1bmN0aW9uKGUpe3JldHVybihlPStlKSE9PXRoaXMuX3JhZGl1cyYmKHRoaXMuX3JhZGl1cz1lLHRoaXMuX2NpcmNsZT1udWxsKSx0aGlzfSxwb2x5Z29uU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPTB9LHBvbHlnb25FbmQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPU5hTn0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9PT0wJiZ0aGlzLl9zdHJpbmcucHVzaCgiWiIpLHRoaXMuX3BvaW50PU5hTn0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMDp7dGhpcy5fc3RyaW5nLnB1c2goIk0iLGUsIiwiLHQpLHRoaXMuX3BvaW50PTE7YnJlYWt9Y2FzZSAxOnt0aGlzLl9zdHJpbmcucHVzaCgiTCIsZSwiLCIsdCk7YnJlYWt9ZGVmYXVsdDp7dGhpcy5fY2lyY2xlPT1udWxsJiYodGhpcy5fY2lyY2xlPW9MdCh0aGlzLl9yYWRpdXMpKSx0aGlzLl9zdHJpbmcucHVzaCgiTSIsZSwiLCIsdCx0aGlzLl9jaXJjbGUpO2JyZWFrfX19LHJlc3VsdDpmdW5jdGlvbigpe2lmKHRoaXMuX3N0cmluZy5sZW5ndGgpe3ZhciBlPXRoaXMuX3N0cmluZy5qb2luKCIiKTtyZXR1cm4gdGhpcy5fc3RyaW5nPVtdLGV9ZWxzZSByZXR1cm4gbnVsbH19fSk7ZnVuY3Rpb24gc0x0KGUsdCl7dmFyIHI9NC41LG4saTtmdW5jdGlvbiBvKGEpe3JldHVybiBhJiYodHlwZW9mIHI9PSJmdW5jdGlvbiImJmkucG9pbnRSYWRpdXMoK3IuYXBwbHkodGhpcyxhcmd1bWVudHMpKSxNbyhhLG4oaSkpKSxpLnJlc3VsdCgpfXJldHVybiBvLmFyZWE9ZnVuY3Rpb24oYSl7cmV0dXJuIE1vKGEsbihaSikpLFpKLnJlc3VsdCgpfSxvLm1lYXN1cmU9ZnVuY3Rpb24oYSl7cmV0dXJuIE1vKGEsbihhUSkpLGFRLnJlc3VsdCgpfSxvLmJvdW5kcz1mdW5jdGlvbihhKXtyZXR1cm4gTW8oYSxuKE4yKSksTjIucmVzdWx0KCl9LG8uY2VudHJvaWQ9ZnVuY3Rpb24oYSl7cmV0dXJuIE1vKGEsbihuUSkpLG5RLnJlc3VsdCgpfSxvLnByb2plY3Rpb249ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49YT09bnVsbD8oZT1udWxsLG5mKTooZT1hKS5zdHJlYW0sbyk6ZX0sby5jb250ZXh0PWZ1bmN0aW9uKGEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPWE9PW51bGw/KHQ9bnVsbCxuZXcgSUQpOm5ldyBBRCh0PWEpLHR5cGVvZiByIT0iZnVuY3Rpb24iJiZpLnBvaW50UmFkaXVzKHIpLG8pOnR9LG8ucG9pbnRSYWRpdXM9ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9dHlwZW9mIGE9PSJmdW5jdGlvbiI/YTooaS5wb2ludFJhZGl1cygrYSksK2EpLG8pOnJ9LG8ucHJvamVjdGlvbihlKS5jb250ZXh0KHQpfXZhciBsTHQ9TSgoKT0+e1NEKCk7bWcoKTtqOXQoKTtKSigpO1E5dCgpO3RMdCgpO2lMdCgpO2FMdCgpfSk7ZnVuY3Rpb24gY0x0KGUpe3JldHVybntzdHJlYW06dmcoZSl9fWZ1bmN0aW9uIHZnKGUpe3JldHVybiBmdW5jdGlvbih0KXt2YXIgcj1uZXcgc1E7Zm9yKHZhciBuIGluIGUpcltuXT1lW25dO3JldHVybiByLnN0cmVhbT10LHJ9fWZ1bmN0aW9uIHNRKCl7fXZhciBHVD1NKCgpPT57c1EucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpzUSxwb2ludDpmdW5jdGlvbihlLHQpe3RoaXMuc3RyZWFtLnBvaW50KGUsdCl9LHNwaGVyZTpmdW5jdGlvbigpe3RoaXMuc3RyZWFtLnNwaGVyZSgpfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLnN0cmVhbS5saW5lU3RhcnQoKX0sbGluZUVuZDpmdW5jdGlvbigpe3RoaXMuc3RyZWFtLmxpbmVFbmQoKX0scG9seWdvblN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5zdHJlYW0ucG9seWdvblN0YXJ0KCl9LHBvbHlnb25FbmQ6ZnVuY3Rpb24oKXt0aGlzLnN0cmVhbS5wb2x5Z29uRW5kKCl9fX0pO2Z1bmN0aW9uIGxRKGUsdCxyKXt2YXIgbj1lLmNsaXBFeHRlbnQmJmUuY2xpcEV4dGVudCgpO3JldHVybiBlLnNjYWxlKDE1MCkudHJhbnNsYXRlKFswLDBdKSxuIT1udWxsJiZlLmNsaXBFeHRlbnQobnVsbCksTW8ocixlLnN0cmVhbShOMikpLHQoTjIucmVzdWx0KCkpLG4hPW51bGwmJmUuY2xpcEV4dGVudChuKSxlfWZ1bmN0aW9uIFZ5KGUsdCxyKXtyZXR1cm4gbFEoZSxmdW5jdGlvbihuKXt2YXIgaT10WzFdWzBdLXRbMF1bMF0sbz10WzFdWzFdLXRbMF1bMV0sYT1NYXRoLm1pbihpLyhuWzFdWzBdLW5bMF1bMF0pLG8vKG5bMV1bMV0tblswXVsxXSkpLHM9K3RbMF1bMF0rKGktYSooblsxXVswXStuWzBdWzBdKSkvMixsPSt0WzBdWzFdKyhvLWEqKG5bMV1bMV0rblswXVsxXSkpLzI7ZS5zY2FsZSgxNTAqYSkudHJhbnNsYXRlKFtzLGxdKX0scil9ZnVuY3Rpb24gTzIoZSx0LHIpe3JldHVybiBWeShlLFtbMCwwXSx0XSxyKX1mdW5jdGlvbiB6MihlLHQscil7cmV0dXJuIGxRKGUsZnVuY3Rpb24obil7dmFyIGk9K3Qsbz1pLyhuWzFdWzBdLW5bMF1bMF0pLGE9KGktbyooblsxXVswXStuWzBdWzBdKSkvMixzPS1vKm5bMF1bMV07ZS5zY2FsZSgxNTAqbykudHJhbnNsYXRlKFthLHNdKX0scil9ZnVuY3Rpb24gRjIoZSx0LHIpe3JldHVybiBsUShlLGZ1bmN0aW9uKG4pe3ZhciBpPSt0LG89aS8oblsxXVsxXS1uWzBdWzFdKSxhPS1vKm5bMF1bMF0scz0oaS1vKihuWzFdWzFdK25bMF1bMV0pKS8yO2Uuc2NhbGUoMTUwKm8pLnRyYW5zbGF0ZShbYSxzXSl9LHIpfXZhciBMRD1NKCgpPT57bWcoKTtKSigpfSk7ZnVuY3Rpb24gY1EoZSx0KXtyZXR1cm4rdD93UGUoZSx0KTpiUGUoZSl9ZnVuY3Rpb24gYlBlKGUpe3JldHVybiB2Zyh7cG9pbnQ6ZnVuY3Rpb24odCxyKXt0PWUodCxyKSx0aGlzLnN0cmVhbS5wb2ludCh0WzBdLHRbMV0pfX0pfWZ1bmN0aW9uIHdQZShlLHQpe2Z1bmN0aW9uIHIobixpLG8sYSxzLGwsYyx1LGgsZixwLGQsZyxfKXt2YXIgeT1jLW4seD11LWksYj15KnkreCp4O2lmKGI+NCp0JiZnLS0pe3ZhciBTPWErZixDPXMrcCxQPWwrZCxrPVJyKFMqUytDKkMrUCpQKSxPPUpuKFAvPWspLEQ9WWUoWWUoUCktMSk8Y2V8fFllKG8taCk8Y2U/KG8raCkvMjpTbihDLFMpLEI9ZShELE8pLEk9QlswXSxMPUJbMV0sUj1JLW4sRj1MLWksej14KlIteSpGOyh6KnovYj50fHxZZSgoeSpSK3gqRikvYi0uNSk+LjN8fGEqZitzKnArbCpkPHhQZSkmJihyKG4saSxvLGEscyxsLEksTCxELFMvPWssQy89ayxQLGcsXyksXy5wb2ludChJLEwpLHIoSSxMLEQsUyxDLFAsYyx1LGgsZixwLGQsZyxfKSl9fXJldHVybiBmdW5jdGlvbihuKXt2YXIgaSxvLGEscyxsLGMsdSxoLGYscCxkLGcsXz17cG9pbnQ6eSxsaW5lU3RhcnQ6eCxsaW5lRW5kOlMscG9seWdvblN0YXJ0OmZ1bmN0aW9uKCl7bi5wb2x5Z29uU3RhcnQoKSxfLmxpbmVTdGFydD1DfSxwb2x5Z29uRW5kOmZ1bmN0aW9uKCl7bi5wb2x5Z29uRW5kKCksXy5saW5lU3RhcnQ9eH19O2Z1bmN0aW9uIHkoTyxEKXtPPWUoTyxEKSxuLnBvaW50KE9bMF0sT1sxXSl9ZnVuY3Rpb24geCgpe2g9TmFOLF8ucG9pbnQ9YixuLmxpbmVTdGFydCgpfWZ1bmN0aW9uIGIoTyxEKXt2YXIgQj12YyhbTyxEXSksST1lKE8sRCk7cihoLGYsdSxwLGQsZyxoPUlbMF0sZj1JWzFdLHU9TyxwPUJbMF0sZD1CWzFdLGc9QlsyXSx1THQsbiksbi5wb2ludChoLGYpfWZ1bmN0aW9uIFMoKXtfLnBvaW50PXksbi5saW5lRW5kKCl9ZnVuY3Rpb24gQygpe3goKSxfLnBvaW50PVAsXy5saW5lRW5kPWt9ZnVuY3Rpb24gUChPLEQpe2IoaT1PLEQpLG89aCxhPWYscz1wLGw9ZCxjPWcsXy5wb2ludD1ifWZ1bmN0aW9uIGsoKXtyKGgsZix1LHAsZCxnLG8sYSxpLHMsbCxjLHVMdCxuKSxfLmxpbmVFbmQ9UyxTKCl9cmV0dXJuIF99fXZhciB1THQseFBlLGhMdD1NKCgpPT57QTIoKTtscigpO0dUKCk7dUx0PTE2LHhQZT1hZSgzMCp3ZSl9KTtmdW5jdGlvbiBNUGUoZSl7cmV0dXJuIHZnKHtwb2ludDpmdW5jdGlvbih0LHIpe3ZhciBuPWUodCxyKTtyZXR1cm4gdGhpcy5zdHJlYW0ucG9pbnQoblswXSxuWzFdKX19KX1mdW5jdGlvbiBlbyhlKXtyZXR1cm4gV1QoZnVuY3Rpb24oKXtyZXR1cm4gZX0pKCl9ZnVuY3Rpb24gV1QoZSl7dmFyIHQscj0xNTAsbj00ODAsaT0yNTAsbyxhLHM9MCxsPTAsYz0wLHU9MCxoPTAsZixwLGQ9bnVsbCxnPU9ULF89bnVsbCx5LHgsYixTPW5mLEM9LjUsUD1jUShJLEMpLGssTztmdW5jdGlvbiBEKEYpe3JldHVybiBGPXAoRlswXSp3ZSxGWzFdKndlKSxbRlswXSpyK28sYS1GWzFdKnJdfWZ1bmN0aW9uIEIoRil7cmV0dXJuIEY9cC5pbnZlcnQoKEZbMF0tbykvciwoYS1GWzFdKS9yKSxGJiZbRlswXSpVcixGWzFdKlVyXX1mdW5jdGlvbiBJKEYseil7cmV0dXJuIEY9dChGLHopLFtGWzBdKnIrbyxhLUZbMV0qcl19RC5zdHJlYW09ZnVuY3Rpb24oRil7cmV0dXJuIGsmJk89PT1GP2s6az1TUGUoTVBlKGYpKGcoUChTKE89RikpKSkpfSxELnByZWNsaXA9ZnVuY3Rpb24oRil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGc9RixkPXZvaWQgMCxSKCkpOmd9LEQucG9zdGNsaXA9ZnVuY3Rpb24oRil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KFM9RixfPXk9eD1iPW51bGwsUigpKTpTfSxELmNsaXBBbmdsZT1mdW5jdGlvbihGKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZz0rRj9nRChkPUYqd2UpOihkPW51bGwsT1QpLFIoKSk6ZCpVcn0sRC5jbGlwRXh0ZW50PWZ1bmN0aW9uKEYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhTPUY9PW51bGw/KF89eT14PWI9bnVsbCxuZik6UXAoXz0rRlswXVswXSx5PStGWzBdWzFdLHg9K0ZbMV1bMF0sYj0rRlsxXVsxXSksUigpKTpfPT1udWxsP251bGw6W1tfLHldLFt4LGJdXX0sRC5zY2FsZT1mdW5jdGlvbihGKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj0rRixMKCkpOnJ9LEQudHJhbnNsYXRlPWZ1bmN0aW9uKEYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPStGWzBdLGk9K0ZbMV0sTCgpKTpbbixpXX0sRC5jZW50ZXI9ZnVuY3Rpb24oRil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHM9RlswXSUzNjAqd2UsbD1GWzFdJTM2MCp3ZSxMKCkpOltzKlVyLGwqVXJdfSxELnJvdGF0ZT1mdW5jdGlvbihGKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYz1GWzBdJTM2MCp3ZSx1PUZbMV0lMzYwKndlLGg9Ri5sZW5ndGg+Mj9GWzJdJTM2MCp3ZTowLEwoKSk6W2MqVXIsdSpVcixoKlVyXX0sRC5wcmVjaXNpb249ZnVuY3Rpb24oRil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KFA9Y1EoSSxDPUYqRiksUigpKTpScihDKX0sRC5maXRFeHRlbnQ9ZnVuY3Rpb24oRix6KXtyZXR1cm4gVnkoRCxGLHopfSxELmZpdFNpemU9ZnVuY3Rpb24oRix6KXtyZXR1cm4gTzIoRCxGLHopfSxELmZpdFdpZHRoPWZ1bmN0aW9uKEYseil7cmV0dXJuIHoyKEQsRix6KX0sRC5maXRIZWlnaHQ9ZnVuY3Rpb24oRix6KXtyZXR1cm4gRjIoRCxGLHopfTtmdW5jdGlvbiBMKCl7cD1hRChmPVJUKGMsdSxoKSx0KTt2YXIgRj10KHMsbCk7cmV0dXJuIG89bi1GWzBdKnIsYT1pK0ZbMV0qcixSKCl9ZnVuY3Rpb24gUigpe3JldHVybiBrPU89bnVsbCxEfXJldHVybiBmdW5jdGlvbigpe3JldHVybiB0PWUuYXBwbHkodGhpcyxhcmd1bWVudHMpLEQuaW52ZXJ0PXQuaW52ZXJ0JiZCLEwoKX19dmFyIFNQZSxBdT1NKCgpPT57SEooKTtWSigpO0ZUKCk7X0ooKTtTRCgpO2xyKCk7TlQoKTtHVCgpO0xEKCk7aEx0KCk7U1BlPXZnKHtwb2ludDpmdW5jdGlvbihlLHQpe3RoaXMuc3RyZWFtLnBvaW50KGUqd2UsdCp3ZSl9fSl9KTtmdW5jdGlvbiBCMihlKXt2YXIgdD0wLHI9cnIvMyxuPVdUKGUpLGk9bih0LHIpO3JldHVybiBpLnBhcmFsbGVscz1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD9uKHQ9b1swXSp3ZSxyPW9bMV0qd2UpOlt0KlVyLHIqVXJdfSxpfXZhciBrRD1NKCgpPT57bHIoKTtBdSgpfSk7ZnVuY3Rpb24gZkx0KGUpe3ZhciB0PWFlKGUpO2Z1bmN0aW9uIHIobixpKXtyZXR1cm5bbip0LEp0KGkpL3RdfXJldHVybiByLmludmVydD1mdW5jdGlvbihuLGkpe3JldHVybltuL3QsSm4oaSp0KV19LHJ9dmFyIHBMdD1NKCgpPT57bHIoKX0pO2Z1bmN0aW9uIHVRKGUsdCl7dmFyIHI9SnQoZSksbj0ocitKdCh0KSkvMjtpZihZZShuKTxjZSlyZXR1cm4gZkx0KGUpO3ZhciBpPTErciooMipuLXIpLG89UnIoaSkvbjtmdW5jdGlvbiBhKHMsbCl7dmFyIGM9UnIoaS0yKm4qSnQobCkpL247cmV0dXJuW2MqSnQocyo9biksby1jKmFlKHMpXX1yZXR1cm4gYS5pbnZlcnQ9ZnVuY3Rpb24ocyxsKXt2YXIgYz1vLWw7cmV0dXJuW1NuKHMsWWUoYykpL24qZGcoYyksSm4oKGktKHMqcytjKmMpKm4qbikvKDIqbikpXX0sYX1mdW5jdGlvbiBVeSgpe3JldHVybiBCMih1USkuc2NhbGUoMTU1LjQyNCkuY2VudGVyKFswLDMzLjY0NDJdKX12YXIgUkQ9TSgoKT0+e2xyKCk7a0QoKTtwTHQoKX0pO2Z1bmN0aW9uIE5EKCl7cmV0dXJuIFV5KCkucGFyYWxsZWxzKFsyOS41LDQ1LjVdKS5zY2FsZSgxMDcwKS50cmFuc2xhdGUoWzQ4MCwyNTBdKS5yb3RhdGUoWzk2LDBdKS5jZW50ZXIoWy0uNiwzOC43XSl9dmFyIGhRPU0oKCk9PntSRCgpfSk7ZnVuY3Rpb24gRVBlKGUpe3ZhciB0PWUubGVuZ3RoO3JldHVybntwb2ludDpmdW5jdGlvbihyLG4pe2Zvcih2YXIgaT0tMTsrK2k8dDspZVtpXS5wb2ludChyLG4pfSxzcGhlcmU6ZnVuY3Rpb24oKXtmb3IodmFyIHI9LTE7KytyPHQ7KWVbcl0uc3BoZXJlKCl9LGxpbmVTdGFydDpmdW5jdGlvbigpe2Zvcih2YXIgcj0tMTsrK3I8dDspZVtyXS5saW5lU3RhcnQoKX0sbGluZUVuZDpmdW5jdGlvbigpe2Zvcih2YXIgcj0tMTsrK3I8dDspZVtyXS5saW5lRW5kKCl9LHBvbHlnb25TdGFydDpmdW5jdGlvbigpe2Zvcih2YXIgcj0tMTsrK3I8dDspZVtyXS5wb2x5Z29uU3RhcnQoKX0scG9seWdvbkVuZDpmdW5jdGlvbigpe2Zvcih2YXIgcj0tMTsrK3I8dDspZVtyXS5wb2x5Z29uRW5kKCl9fX1mdW5jdGlvbiBkTHQoKXt2YXIgZSx0LHI9TkQoKSxuLGk9VXkoKS5yb3RhdGUoWzE1NCwwXSkuY2VudGVyKFstMiw1OC41XSkucGFyYWxsZWxzKFs1NSw2NV0pLG8sYT1VeSgpLnJvdGF0ZShbMTU3LDBdKS5jZW50ZXIoWy0zLDE5LjldKS5wYXJhbGxlbHMoWzgsMThdKSxzLGwsYz17cG9pbnQ6ZnVuY3Rpb24oZixwKXtsPVtmLHBdfX07ZnVuY3Rpb24gdShmKXt2YXIgcD1mWzBdLGQ9ZlsxXTtyZXR1cm4gbD1udWxsLG4ucG9pbnQocCxkKSxsfHwoby5wb2ludChwLGQpLGwpfHwocy5wb2ludChwLGQpLGwpfXUuaW52ZXJ0PWZ1bmN0aW9uKGYpe3ZhciBwPXIuc2NhbGUoKSxkPXIudHJhbnNsYXRlKCksZz0oZlswXS1kWzBdKS9wLF89KGZbMV0tZFsxXSkvcDtyZXR1cm4oXz49LjEyJiZfPC4yMzQmJmc+PS0uNDI1JiZnPC0uMjE0P2k6Xz49LjE2NiYmXzwuMjM0JiZnPj0tLjIxNCYmZzwtLjExNT9hOnIpLmludmVydChmKX0sdS5zdHJlYW09ZnVuY3Rpb24oZil7cmV0dXJuIGUmJnQ9PT1mP2U6ZT1FUGUoW3Iuc3RyZWFtKHQ9ZiksaS5zdHJlYW0oZiksYS5zdHJlYW0oZildKX0sdS5wcmVjaXNpb249ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHIucHJlY2lzaW9uKGYpLGkucHJlY2lzaW9uKGYpLGEucHJlY2lzaW9uKGYpLGgoKSk6ci5wcmVjaXNpb24oKX0sdS5zY2FsZT1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oci5zY2FsZShmKSxpLnNjYWxlKGYqLjM1KSxhLnNjYWxlKGYpLHUudHJhbnNsYXRlKHIudHJhbnNsYXRlKCkpKTpyLnNjYWxlKCl9LHUudHJhbnNsYXRlPWZ1bmN0aW9uKGYpe2lmKCFhcmd1bWVudHMubGVuZ3RoKXJldHVybiByLnRyYW5zbGF0ZSgpO3ZhciBwPXIuc2NhbGUoKSxkPStmWzBdLGc9K2ZbMV07cmV0dXJuIG49ci50cmFuc2xhdGUoZikuY2xpcEV4dGVudChbW2QtLjQ1NSpwLGctLjIzOCpwXSxbZCsuNDU1KnAsZysuMjM4KnBdXSkuc3RyZWFtKGMpLG89aS50cmFuc2xhdGUoW2QtLjMwNypwLGcrLjIwMSpwXSkuY2xpcEV4dGVudChbW2QtLjQyNSpwK2NlLGcrLjEyKnArY2VdLFtkLS4yMTQqcC1jZSxnKy4yMzQqcC1jZV1dKS5zdHJlYW0oYykscz1hLnRyYW5zbGF0ZShbZC0uMjA1KnAsZysuMjEyKnBdKS5jbGlwRXh0ZW50KFtbZC0uMjE0KnArY2UsZysuMTY2KnArY2VdLFtkLS4xMTUqcC1jZSxnKy4yMzQqcC1jZV1dKS5zdHJlYW0oYyksaCgpfSx1LmZpdEV4dGVudD1mdW5jdGlvbihmLHApe3JldHVybiBWeSh1LGYscCl9LHUuZml0U2l6ZT1mdW5jdGlvbihmLHApe3JldHVybiBPMih1LGYscCl9LHUuZml0V2lkdGg9ZnVuY3Rpb24oZixwKXtyZXR1cm4gejIodSxmLHApfSx1LmZpdEhlaWdodD1mdW5jdGlvbihmLHApe3JldHVybiBGMih1LGYscCl9O2Z1bmN0aW9uIGgoKXtyZXR1cm4gZT10PW51bGwsdX1yZXR1cm4gdS5zY2FsZSgxMDcwKX12YXIgbUx0PU0oKCk9PntscigpO2hRKCk7UkQoKTtMRCgpfSk7ZnVuY3Rpb24gREQoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7dmFyIG49YWUodCksaT1hZShyKSxvPWUobippKTtyZXR1cm5bbyppKkp0KHQpLG8qSnQocildfX1mdW5jdGlvbiBzZihlKXtyZXR1cm4gZnVuY3Rpb24odCxyKXt2YXIgbj1Scih0KnQrcipyKSxpPWUobiksbz1KdChpKSxhPWFlKGkpO3JldHVybltTbih0Km8sbiphKSxKbihuJiZyKm8vbildfX12YXIgSDI9TSgoKT0+e2xyKCl9KTtmdW5jdGlvbiBnTHQoKXtyZXR1cm4gZW8oT0QpLnNjYWxlKDEyNC43NSkuY2xpcEFuZ2xlKDE4MC0uMDAxKX12YXIgT0QsX0x0PU0oKCk9PntscigpO0gyKCk7QXUoKTtPRD1ERChmdW5jdGlvbihlKXtyZXR1cm4gUnIoMi8oMStlKSl9KTtPRC5pbnZlcnQ9c2YoZnVuY3Rpb24oZSl7cmV0dXJuIDIqSm4oZS8yKX0pfSk7ZnVuY3Rpb24geUx0KCl7cmV0dXJuIGVvKHpEKS5zY2FsZSg3OS40MTg4KS5jbGlwQW5nbGUoMTgwLS4wMDEpfXZhciB6RCx2THQ9TSgoKT0+e2xyKCk7SDIoKTtBdSgpO3pEPUREKGZ1bmN0aW9uKGUpe3JldHVybihlPSROKGUpKSYmZS9KdChlKX0pO3pELmludmVydD1zZihmdW5jdGlvbihlKXtyZXR1cm4gZX0pfSk7ZnVuY3Rpb24gcXkoZSx0KXtyZXR1cm5bZSxSeShDMigoQm4rdCkvMikpXX1mdW5jdGlvbiB4THQoKXtyZXR1cm4gZlEocXkpLnNjYWxlKDk2MS9CaSl9ZnVuY3Rpb24gZlEoZSl7dmFyIHQ9ZW8oZSkscj10LmNlbnRlcixuPXQuc2NhbGUsaT10LnRyYW5zbGF0ZSxvPXQuY2xpcEV4dGVudCxhPW51bGwscyxsLGM7dC5zY2FsZT1mdW5jdGlvbihoKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obihoKSx1KCkpOm4oKX0sdC50cmFuc2xhdGU9ZnVuY3Rpb24oaCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGkoaCksdSgpKTppKCl9LHQuY2VudGVyPWZ1bmN0aW9uKGgpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyKGgpLHUoKSk6cigpfSx0LmNsaXBFeHRlbnQ9ZnVuY3Rpb24oaCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGg9PW51bGw/YT1zPWw9Yz1udWxsOihhPStoWzBdWzBdLHM9K2hbMF1bMV0sbD0raFsxXVswXSxjPStoWzFdWzFdKSx1KCkpOmE9PW51bGw/bnVsbDpbW2Esc10sW2wsY11dfTtmdW5jdGlvbiB1KCl7dmFyIGg9cnIqbigpLGY9dChzRCh0LnJvdGF0ZSgpKS5pbnZlcnQoWzAsMF0pKTtyZXR1cm4gbyhhPT1udWxsP1tbZlswXS1oLGZbMV0taF0sW2ZbMF0raCxmWzFdK2hdXTplPT09cXk/W1tNYXRoLm1heChmWzBdLWgsYSksc10sW01hdGgubWluKGZbMF0raCxsKSxjXV06W1thLE1hdGgubWF4KGZbMV0taCxzKV0sW2wsTWF0aC5taW4oZlsxXStoLGMpXV0pfXJldHVybiB1KCl9dmFyIEZEPU0oKCk9PntscigpO05UKCk7QXUoKTtxeS5pbnZlcnQ9ZnVuY3Rpb24oZSx0KXtyZXR1cm5bZSwyKnljKGpOKHQpKS1Cbl19fSk7ZnVuY3Rpb24gQkQoZSl7cmV0dXJuIEMyKChCbitlKS8yKX1mdW5jdGlvbiBwUShlLHQpe3ZhciByPWFlKGUpLG49ZT09PXQ/SnQoZSk6Unkoci9hZSh0KSkvUnkoQkQodCkvQkQoZSkpLGk9cipYTihCRChlKSxuKS9uO2lmKCFuKXJldHVybiBxeTtmdW5jdGlvbiBvKGEscyl7aT4wP3M8LUJuK2NlJiYocz0tQm4rY2UpOnM+Qm4tY2UmJihzPUJuLWNlKTt2YXIgbD1pL1hOKEJEKHMpLG4pO3JldHVybltsKkp0KG4qYSksaS1sKmFlKG4qYSldfXJldHVybiBvLmludmVydD1mdW5jdGlvbihhLHMpe3ZhciBsPWktcyxjPWRnKG4pKlJyKGEqYStsKmwpO3JldHVybltTbihhLFllKGwpKS9uKmRnKGwpLDIqeWMoWE4oaS9jLDEvbikpLUJuXX0sb31mdW5jdGlvbiBiTHQoKXtyZXR1cm4gQjIocFEpLnNjYWxlKDEwOS41KS5wYXJhbGxlbHMoWzMwLDMwXSl9dmFyIHdMdD1NKCgpPT57bHIoKTtrRCgpO0ZEKCl9KTtmdW5jdGlvbiBHeShlLHQpe3JldHVybltlLHRdfWZ1bmN0aW9uIFNMdCgpe3JldHVybiBlbyhHeSkuc2NhbGUoMTUyLjYzKX12YXIgZFE9TSgoKT0+e0F1KCk7R3kuaW52ZXJ0PUd5fSk7ZnVuY3Rpb24gbVEoZSx0KXt2YXIgcj1hZShlKSxuPWU9PT10P0p0KGUpOihyLWFlKHQpKS8odC1lKSxpPXIvbitlO2lmKFllKG4pPGNlKXJldHVybiBHeTtmdW5jdGlvbiBvKGEscyl7dmFyIGw9aS1zLGM9biphO3JldHVybltsKkp0KGMpLGktbCphZShjKV19cmV0dXJuIG8uaW52ZXJ0PWZ1bmN0aW9uKGEscyl7dmFyIGw9aS1zO3JldHVybltTbihhLFllKGwpKS9uKmRnKGwpLGktZGcobikqUnIoYSphK2wqbCldfSxvfWZ1bmN0aW9uIE1MdCgpe3JldHVybiBCMihtUSkuc2NhbGUoMTMxLjE1NCkuY2VudGVyKFswLDEzLjkzODldKX12YXIgRUx0PU0oKCk9PntscigpO2tEKCk7ZFEoKX0pO2Z1bmN0aW9uIEhEKGUsdCl7dmFyIHI9YWUodCksbj1hZShlKSpyO3JldHVybltyKkp0KGUpL24sSnQodCkvbl19ZnVuY3Rpb24gVEx0KCl7cmV0dXJuIGVvKEhEKS5zY2FsZSgxNDQuMDQ5KS5jbGlwQW5nbGUoNjApfXZhciBDTHQ9TSgoKT0+e2xyKCk7SDIoKTtBdSgpO0hELmludmVydD1zZih5Yyl9KTtmdW5jdGlvbiBWRChlLHQscixuKXtyZXR1cm4gZT09PTEmJnQ9PT0xJiZyPT09MCYmbj09PTA/bmY6dmcoe3BvaW50OmZ1bmN0aW9uKGksbyl7dGhpcy5zdHJlYW0ucG9pbnQoaSplK3Isbyp0K24pfX0pfWZ1bmN0aW9uIEFMdCgpe3ZhciBlPTEsdD0wLHI9MCxuPTEsaT0xLG89bmYsYT1udWxsLHMsbCxjLHU9bmYsaCxmLHA7ZnVuY3Rpb24gZCgpe3JldHVybiBoPWY9bnVsbCxwfXJldHVybiBwPXtzdHJlYW06ZnVuY3Rpb24oZyl7cmV0dXJuIGgmJmY9PT1nP2g6aD1vKHUoZj1nKSl9LHBvc3RjbGlwOmZ1bmN0aW9uKGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh1PWcsYT1zPWw9Yz1udWxsLGQoKSk6dX0sY2xpcEV4dGVudDpmdW5jdGlvbihnKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odT1nPT1udWxsPyhhPXM9bD1jPW51bGwsbmYpOlFwKGE9K2dbMF1bMF0scz0rZ1swXVsxXSxsPStnWzFdWzBdLGM9K2dbMV1bMV0pLGQoKSk6YT09bnVsbD9udWxsOltbYSxzXSxbbCxjXV19LHNjYWxlOmZ1bmN0aW9uKGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPVZEKChlPStnKSpuLGUqaSx0LHIpLGQoKSk6ZX0sdHJhbnNsYXRlOmZ1bmN0aW9uKGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPVZEKGUqbixlKmksdD0rZ1swXSxyPStnWzFdKSxkKCkpOlt0LHJdfSxyZWZsZWN0WDpmdW5jdGlvbihnKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz1WRChlKihuPWc/LTE6MSksZSppLHQsciksZCgpKTpuPDB9LHJlZmxlY3RZOmZ1bmN0aW9uKGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPVZEKGUqbixlKihpPWc/LTE6MSksdCxyKSxkKCkpOmk8MH0sZml0RXh0ZW50OmZ1bmN0aW9uKGcsXyl7cmV0dXJuIFZ5KHAsZyxfKX0sZml0U2l6ZTpmdW5jdGlvbihnLF8pe3JldHVybiBPMihwLGcsXyl9LGZpdFdpZHRoOmZ1bmN0aW9uKGcsXyl7cmV0dXJuIHoyKHAsZyxfKX0sZml0SGVpZ2h0OmZ1bmN0aW9uKGcsXyl7cmV0dXJuIEYyKHAsZyxfKX19fXZhciBQTHQ9TSgoKT0+e0ZUKCk7U0QoKTtHVCgpO0xEKCl9KTtmdW5jdGlvbiBVRChlLHQpe3ZhciByPXQqdCxuPXIqcjtyZXR1cm5bZSooLjg3MDctLjEzMTk3OSpyK24qKC0uMDEzNzkxK24qKC4wMDM5NzEqci0uMDAxNTI5Km4pKSksdCooMS4wMDcyMjYrciooLjAxNTA4NStuKigtLjA0NDQ3NSsuMDI4ODc0KnItLjAwNTkxNipuKSkpXX1mdW5jdGlvbiBJTHQoKXtyZXR1cm4gZW8oVUQpLnNjYWxlKDE3NS4yOTUpfXZhciBMTHQ9TSgoKT0+e0F1KCk7bHIoKTtVRC5pbnZlcnQ9ZnVuY3Rpb24oZSx0KXt2YXIgcj10LG49MjUsaTtkb3t2YXIgbz1yKnIsYT1vKm87ci09aT0ociooMS4wMDcyMjYrbyooLjAxNTA4NSthKigtLjA0NDQ3NSsuMDI4ODc0Km8tLjAwNTkxNiphKSkpLXQpLygxLjAwNzIyNitvKiguMDE1MDg1KjMrYSooLS4wNDQ0NzUqNysuMDI4ODc0Kjkqby0uMDA1OTE2KjExKmEpKSl9d2hpbGUoWWUoaSk+Y2UmJi0tbj4wKTtyZXR1cm5bZS8oLjg3MDcrKG89cipyKSooLS4xMzE5NzkrbyooLS4wMTM3OTErbypvKm8qKC4wMDM5NzEtLjAwMTUyOSpvKSkpKSxyXX19KTtmdW5jdGlvbiBxRChlLHQpe3JldHVyblthZSh0KSpKdChlKSxKdCh0KV19ZnVuY3Rpb24ga0x0KCl7cmV0dXJuIGVvKHFEKS5zY2FsZSgyNDkuNSkuY2xpcEFuZ2xlKDkwK2NlKX12YXIgUkx0PU0oKCk9PntscigpO0gyKCk7QXUoKTtxRC5pbnZlcnQ9c2YoSm4pfSk7ZnVuY3Rpb24gR0QoZSx0KXt2YXIgcj1hZSh0KSxuPTErYWUoZSkqcjtyZXR1cm5bcipKdChlKS9uLEp0KHQpL25dfWZ1bmN0aW9uIE5MdCgpe3JldHVybiBlbyhHRCkuc2NhbGUoMjUwKS5jbGlwQW5nbGUoMTQyKX12YXIgREx0PU0oKCk9PntscigpO0gyKCk7QXUoKTtHRC5pbnZlcnQ9c2YoZnVuY3Rpb24oZSl7cmV0dXJuIDIqeWMoZSl9KX0pO2Z1bmN0aW9uIFdEKGUsdCl7cmV0dXJuW1J5KEMyKChCbit0KS8yKSksLWVdfWZ1bmN0aW9uIE9MdCgpe3ZhciBlPWZRKFdEKSx0PWUuY2VudGVyLHI9ZS5yb3RhdGU7cmV0dXJuIGUuY2VudGVyPWZ1bmN0aW9uKG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3QoWy1uWzFdLG5bMF1dKToobj10KCksW25bMV0sLW5bMF1dKX0sZS5yb3RhdGU9ZnVuY3Rpb24obil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/cihbblswXSxuWzFdLG4ubGVuZ3RoPjI/blsyXSs5MDo5MF0pOihuPXIoKSxbblswXSxuWzFdLG5bMl0tOTBdKX0scihbMCwwLDkwXSkuc2NhbGUoMTU5LjE1NSl9dmFyIHpMdD1NKCgpPT57bHIoKTtGRCgpO1dELmludmVydD1mdW5jdGlvbihlLHQpe3JldHVyblstdCwyKnljKGpOKGUpKS1Cbl19fSk7dmFyIEZMdD1NKCgpPT57aEooKTtZSXQoKTtRSXQoKTt4SigpO0hKKCk7VkooKTtQOXQoKTtGVCgpO3o5dCgpO1lKKCk7Vjl0KCk7cTl0KCk7R0ooKTtsTHQoKTtoUSgpO21MdCgpO19MdCgpO3ZMdCgpO3dMdCgpO1JEKCk7RUx0KCk7ZFEoKTtDTHQoKTtQTHQoKTtBdSgpO0ZEKCk7TEx0KCk7Ukx0KCk7REx0KCk7ekx0KCk7TlQoKTttZygpO0dUKCl9KTtmdW5jdGlvbiBUUGUoZSx0KXtyZXR1cm4gZS5wYXJlbnQ9PT10LnBhcmVudD8xOjJ9ZnVuY3Rpb24gQ1BlKGUpe3JldHVybiBlLnJlZHVjZShBUGUsMCkvZS5sZW5ndGh9ZnVuY3Rpb24gQVBlKGUsdCl7cmV0dXJuIGUrdC54fWZ1bmN0aW9uIFBQZShlKXtyZXR1cm4gMStlLnJlZHVjZShJUGUsMCl9ZnVuY3Rpb24gSVBlKGUsdCl7cmV0dXJuIE1hdGgubWF4KGUsdC55KX1mdW5jdGlvbiBMUGUoZSl7Zm9yKHZhciB0O3Q9ZS5jaGlsZHJlbjspZT10WzBdO3JldHVybiBlfWZ1bmN0aW9uIGtQZShlKXtmb3IodmFyIHQ7dD1lLmNoaWxkcmVuOyllPXRbdC5sZW5ndGgtMV07cmV0dXJuIGV9ZnVuY3Rpb24gQkx0KCl7dmFyIGU9VFBlLHQ9MSxyPTEsbj0hMTtmdW5jdGlvbiBpKG8pe3ZhciBhLHM9MDtvLmVhY2hBZnRlcihmdW5jdGlvbihmKXt2YXIgcD1mLmNoaWxkcmVuO3A/KGYueD1DUGUocCksZi55PVBQZShwKSk6KGYueD1hP3MrPWUoZixhKTowLGYueT0wLGE9Zil9KTt2YXIgbD1MUGUobyksYz1rUGUobyksdT1sLngtZShsLGMpLzIsaD1jLngrZShjLGwpLzI7cmV0dXJuIG8uZWFjaEFmdGVyKG4/ZnVuY3Rpb24oZil7Zi54PShmLngtby54KSp0LGYueT0oby55LWYueSkqcn06ZnVuY3Rpb24oZil7Zi54PShmLngtdSkvKGgtdSkqdCxmLnk9KDEtKG8ueT9mLnkvby55OjEpKSpyfSl9cmV0dXJuIGkuc2VwYXJhdGlvbj1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT1vLGkpOmV9LGkuc2l6ZT1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj0hMSx0PStvWzBdLHI9K29bMV0saSk6bj9udWxsOlt0LHJdfSxpLm5vZGVTaXplPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPSEwLHQ9K29bMF0scj0rb1sxXSxpKTpuP1t0LHJdOm51bGx9LGl9dmFyIEhMdD1NKCgpPT57fSk7ZnVuY3Rpb24gUlBlKGUpe3ZhciB0PTAscj1lLmNoaWxkcmVuLG49ciYmci5sZW5ndGg7aWYoIW4pdD0xO2Vsc2UgZm9yKDstLW4+PTA7KXQrPXJbbl0udmFsdWU7ZS52YWx1ZT10fWZ1bmN0aW9uIFZMdCgpe3JldHVybiB0aGlzLmVhY2hBZnRlcihSUGUpfXZhciBVTHQ9TSgoKT0+e30pO2Z1bmN0aW9uIHFMdChlKXt2YXIgdD10aGlzLHIsbj1bdF0saSxvLGE7ZG8gZm9yKHI9bi5yZXZlcnNlKCksbj1bXTt0PXIucG9wKCk7KWlmKGUodCksaT10LmNoaWxkcmVuLGkpZm9yKG89MCxhPWkubGVuZ3RoO288YTsrK28pbi5wdXNoKGlbb10pO3doaWxlKG4ubGVuZ3RoKTtyZXR1cm4gdGhpc312YXIgR0x0PU0oKCk9Pnt9KTtmdW5jdGlvbiBXTHQoZSl7Zm9yKHZhciB0PXRoaXMscj1bdF0sbixpO3Q9ci5wb3AoKTspaWYoZSh0KSxuPXQuY2hpbGRyZW4sbilmb3IoaT1uLmxlbmd0aC0xO2k+PTA7LS1pKXIucHVzaChuW2ldKTtyZXR1cm4gdGhpc312YXIgWUx0PU0oKCk9Pnt9KTtmdW5jdGlvbiBqTHQoZSl7Zm9yKHZhciB0PXRoaXMscj1bdF0sbj1bXSxpLG8sYTt0PXIucG9wKCk7KWlmKG4ucHVzaCh0KSxpPXQuY2hpbGRyZW4saSlmb3Iobz0wLGE9aS5sZW5ndGg7bzxhOysrbylyLnB1c2goaVtvXSk7Zm9yKDt0PW4ucG9wKCk7KWUodCk7cmV0dXJuIHRoaXN9dmFyIFhMdD1NKCgpPT57fSk7ZnVuY3Rpb24gJEx0KGUpe3JldHVybiB0aGlzLmVhY2hBZnRlcihmdW5jdGlvbih0KXtmb3IodmFyIHI9K2UodC5kYXRhKXx8MCxuPXQuY2hpbGRyZW4saT1uJiZuLmxlbmd0aDstLWk+PTA7KXIrPW5baV0udmFsdWU7dC52YWx1ZT1yfSl9dmFyIEtMdD1NKCgpPT57fSk7ZnVuY3Rpb24gWkx0KGUpe3JldHVybiB0aGlzLmVhY2hCZWZvcmUoZnVuY3Rpb24odCl7dC5jaGlsZHJlbiYmdC5jaGlsZHJlbi5zb3J0KGUpfSl9dmFyIEpMdD1NKCgpPT57fSk7ZnVuY3Rpb24gUUx0KGUpe2Zvcih2YXIgdD10aGlzLHI9TlBlKHQsZSksbj1bdF07dCE9PXI7KXQ9dC5wYXJlbnQsbi5wdXNoKHQpO2Zvcih2YXIgaT1uLmxlbmd0aDtlIT09cjspbi5zcGxpY2UoaSwwLGUpLGU9ZS5wYXJlbnQ7cmV0dXJuIG59ZnVuY3Rpb24gTlBlKGUsdCl7aWYoZT09PXQpcmV0dXJuIGU7dmFyIHI9ZS5hbmNlc3RvcnMoKSxuPXQuYW5jZXN0b3JzKCksaT1udWxsO2ZvcihlPXIucG9wKCksdD1uLnBvcCgpO2U9PT10OylpPWUsZT1yLnBvcCgpLHQ9bi5wb3AoKTtyZXR1cm4gaX12YXIgdGt0PU0oKCk9Pnt9KTtmdW5jdGlvbiBla3QoKXtmb3IodmFyIGU9dGhpcyx0PVtlXTtlPWUucGFyZW50Oyl0LnB1c2goZSk7cmV0dXJuIHR9dmFyIHJrdD1NKCgpPT57fSk7ZnVuY3Rpb24gbmt0KCl7dmFyIGU9W107cmV0dXJuIHRoaXMuZWFjaChmdW5jdGlvbih0KXtlLnB1c2godCl9KSxlfXZhciBpa3Q9TSgoKT0+e30pO2Z1bmN0aW9uIG9rdCgpe3ZhciBlPVtdO3JldHVybiB0aGlzLmVhY2hCZWZvcmUoZnVuY3Rpb24odCl7dC5jaGlsZHJlbnx8ZS5wdXNoKHQpfSksZX12YXIgYWt0PU0oKCk9Pnt9KTtmdW5jdGlvbiBza3QoKXt2YXIgZT10aGlzLHQ9W107cmV0dXJuIGUuZWFjaChmdW5jdGlvbihyKXtyIT09ZSYmdC5wdXNoKHtzb3VyY2U6ci5wYXJlbnQsdGFyZ2V0OnJ9KX0pLHR9dmFyIGxrdD1NKCgpPT57fSk7ZnVuY3Rpb24gWVQoZSx0KXt2YXIgcj1uZXcgeGcoZSksbj0rZS52YWx1ZSYmKHIudmFsdWU9ZS52YWx1ZSksaSxvPVtyXSxhLHMsbCxjO2Zvcih0PT1udWxsJiYodD1PUGUpO2k9by5wb3AoKTspaWYobiYmKGkudmFsdWU9K2kuZGF0YS52YWx1ZSksKHM9dChpLmRhdGEpKSYmKGM9cy5sZW5ndGgpKWZvcihpLmNoaWxkcmVuPW5ldyBBcnJheShjKSxsPWMtMTtsPj0wOy0tbClvLnB1c2goYT1pLmNoaWxkcmVuW2xdPW5ldyB4ZyhzW2xdKSksYS5wYXJlbnQ9aSxhLmRlcHRoPWkuZGVwdGgrMTtyZXR1cm4gci5lYWNoQmVmb3JlKGdRKX1mdW5jdGlvbiBEUGUoKXtyZXR1cm4gWVQodGhpcykuZWFjaEJlZm9yZSh6UGUpfWZ1bmN0aW9uIE9QZShlKXtyZXR1cm4gZS5jaGlsZHJlbn1mdW5jdGlvbiB6UGUoZSl7ZS5kYXRhPWUuZGF0YS5kYXRhfWZ1bmN0aW9uIGdRKGUpe3ZhciB0PTA7ZG8gZS5oZWlnaHQ9dDt3aGlsZSgoZT1lLnBhcmVudCkmJmUuaGVpZ2h0PCsrdCl9ZnVuY3Rpb24geGcoZSl7dGhpcy5kYXRhPWUsdGhpcy5kZXB0aD10aGlzLmhlaWdodD0wLHRoaXMucGFyZW50PW51bGx9dmFyIFlEPU0oKCk9PntVTHQoKTtHTHQoKTtZTHQoKTtYTHQoKTtLTHQoKTtKTHQoKTt0a3QoKTtya3QoKTtpa3QoKTtha3QoKTtsa3QoKTt4Zy5wcm90b3R5cGU9WVQucHJvdG90eXBlPXtjb25zdHJ1Y3Rvcjp4Zyxjb3VudDpWTHQsZWFjaDpxTHQsZWFjaEFmdGVyOmpMdCxlYWNoQmVmb3JlOldMdCxzdW06JEx0LHNvcnQ6Wkx0LHBhdGg6UUx0LGFuY2VzdG9yczpla3QsZGVzY2VuZGFudHM6bmt0LGxlYXZlczpva3QsbGlua3M6c2t0LGNvcHk6RFBlfX0pO2Z1bmN0aW9uIHVrdChlKXtmb3IodmFyIHQ9ZS5sZW5ndGgscixuO3Q7KW49TWF0aC5yYW5kb20oKSp0LS18MCxyPWVbdF0sZVt0XT1lW25dLGVbbl09cjtyZXR1cm4gZX12YXIgY2t0LGhrdD1NKCgpPT57Y2t0PUFycmF5LnByb3RvdHlwZS5zbGljZX0pO2Z1bmN0aW9uIFhEKGUpe2Zvcih2YXIgdD0wLHI9KGU9dWt0KGNrdC5jYWxsKGUpKSkubGVuZ3RoLG49W10saSxvO3Q8cjspaT1lW3RdLG8mJmZrdChvLGkpPysrdDoobz1CUGUobj1GUGUobixpKSksdD0wKTtyZXR1cm4gb31mdW5jdGlvbiBGUGUoZSx0KXt2YXIgcixuO2lmKF9RKHQsZSkpcmV0dXJuW3RdO2ZvcihyPTA7cjxlLmxlbmd0aDsrK3IpaWYoakQodCxlW3JdKSYmX1EoalQoZVtyXSx0KSxlKSlyZXR1cm5bZVtyXSx0XTtmb3Iocj0wO3I8ZS5sZW5ndGgtMTsrK3IpZm9yKG49cisxO248ZS5sZW5ndGg7KytuKWlmKGpEKGpUKGVbcl0sZVtuXSksdCkmJmpEKGpUKGVbcl0sdCksZVtuXSkmJmpEKGpUKGVbbl0sdCksZVtyXSkmJl9RKHBrdChlW3JdLGVbbl0sdCksZSkpcmV0dXJuW2Vbcl0sZVtuXSx0XTt0aHJvdyBuZXcgRXJyb3J9ZnVuY3Rpb24gakQoZSx0KXt2YXIgcj1lLnItdC5yLG49dC54LWUueCxpPXQueS1lLnk7cmV0dXJuIHI8MHx8cipyPG4qbitpKml9ZnVuY3Rpb24gZmt0KGUsdCl7dmFyIHI9ZS5yLXQucisxZS02LG49dC54LWUueCxpPXQueS1lLnk7cmV0dXJuIHI+MCYmcipyPm4qbitpKml9ZnVuY3Rpb24gX1EoZSx0KXtmb3IodmFyIHI9MDtyPHQubGVuZ3RoOysrcilpZighZmt0KGUsdFtyXSkpcmV0dXJuITE7cmV0dXJuITB9ZnVuY3Rpb24gQlBlKGUpe3N3aXRjaChlLmxlbmd0aCl7Y2FzZSAxOnJldHVybiBIUGUoZVswXSk7Y2FzZSAyOnJldHVybiBqVChlWzBdLGVbMV0pO2Nhc2UgMzpyZXR1cm4gcGt0KGVbMF0sZVsxXSxlWzJdKX19ZnVuY3Rpb24gSFBlKGUpe3JldHVybnt4OmUueCx5OmUueSxyOmUucn19ZnVuY3Rpb24galQoZSx0KXt2YXIgcj1lLngsbj1lLnksaT1lLnIsbz10LngsYT10Lnkscz10LnIsbD1vLXIsYz1hLW4sdT1zLWksaD1NYXRoLnNxcnQobCpsK2MqYyk7cmV0dXJue3g6KHIrbytsL2gqdSkvMix5OihuK2ErYy9oKnUpLzIscjooaCtpK3MpLzJ9fWZ1bmN0aW9uIHBrdChlLHQscil7dmFyIG49ZS54LGk9ZS55LG89ZS5yLGE9dC54LHM9dC55LGw9dC5yLGM9ci54LHU9ci55LGg9ci5yLGY9bi1hLHA9bi1jLGQ9aS1zLGc9aS11LF89bC1vLHk9aC1vLHg9bipuK2kqaS1vKm8sYj14LWEqYS1zKnMrbCpsLFM9eC1jKmMtdSp1K2gqaCxDPXAqZC1mKmcsUD0oZCpTLWcqYikvKEMqMiktbixrPShnKl8tZCp5KS9DLE89KHAqYi1mKlMpLyhDKjIpLWksRD0oZip5LXAqXykvQyxCPWsqaytEKkQtMSxJPTIqKG8rUCprK08qRCksTD1QKlArTypPLW8qbyxSPS0oQj8oSStNYXRoLnNxcnQoSSpJLTQqQipMKSkvKDIqQik6TC9JKTtyZXR1cm57eDpuK1AraypSLHk6aStPK0QqUixyOlJ9fXZhciB5UT1NKCgpPT57aGt0KCl9KTtmdW5jdGlvbiBka3QoZSx0LHIpe3ZhciBuPWUueCxpPWUueSxvPXQucityLnIsYT1lLnIrci5yLHM9dC54LW4sbD10LnktaSxjPXMqcytsKmw7aWYoYyl7dmFyIHU9LjUrKChhKj1hKS0obyo9bykpLygyKmMpLGg9TWF0aC5zcXJ0KE1hdGgubWF4KDAsMipvKihhK2MpLShhLT1jKSphLW8qbykpLygyKmMpO3IueD1uK3UqcytoKmwsci55PWkrdSpsLWgqc31lbHNlIHIueD1uK2Esci55PWl9ZnVuY3Rpb24gbWt0KGUsdCl7dmFyIHI9dC54LWUueCxuPXQueS1lLnksaT1lLnIrdC5yO3JldHVybiBpKmktMWUtNj5yKnIrbipufWZ1bmN0aW9uIGdrdChlKXt2YXIgdD1lLl8scj1lLm5leHQuXyxuPXQucityLnIsaT0odC54KnIucityLngqdC5yKS9uLG89KHQueSpyLnIrci55KnQucikvbjtyZXR1cm4gaSppK28qb31mdW5jdGlvbiAkRChlKXt0aGlzLl89ZSx0aGlzLm5leHQ9bnVsbCx0aGlzLnByZXZpb3VzPW51bGx9ZnVuY3Rpb24gdlEoZSl7aWYoIShpPWUubGVuZ3RoKSlyZXR1cm4gMDt2YXIgdCxyLG4saSxvLGEscyxsLGMsdSxoO2lmKHQ9ZVswXSx0Lng9MCx0Lnk9MCwhKGk+MSkpcmV0dXJuIHQucjtpZihyPWVbMV0sdC54PS1yLnIsci54PXQucixyLnk9MCwhKGk+MikpcmV0dXJuIHQucityLnI7ZGt0KHIsdCxuPWVbMl0pLHQ9bmV3ICREKHQpLHI9bmV3ICREKHIpLG49bmV3ICREKG4pLHQubmV4dD1uLnByZXZpb3VzPXIsci5uZXh0PXQucHJldmlvdXM9bixuLm5leHQ9ci5wcmV2aW91cz10O3Q6Zm9yKHM9MztzPGk7KytzKXtka3QodC5fLHIuXyxuPWVbc10pLG49bmV3ICREKG4pLGw9ci5uZXh0LGM9dC5wcmV2aW91cyx1PXIuXy5yLGg9dC5fLnI7ZG8gaWYodTw9aCl7aWYobWt0KGwuXyxuLl8pKXtyPWwsdC5uZXh0PXIsci5wcmV2aW91cz10LC0tcztjb250aW51ZSB0fXUrPWwuXy5yLGw9bC5uZXh0fWVsc2V7aWYobWt0KGMuXyxuLl8pKXt0PWMsdC5uZXh0PXIsci5wcmV2aW91cz10LC0tcztjb250aW51ZSB0fWgrPWMuXy5yLGM9Yy5wcmV2aW91c313aGlsZShsIT09Yy5uZXh0KTtmb3Iobi5wcmV2aW91cz10LG4ubmV4dD1yLHQubmV4dD1yLnByZXZpb3VzPXI9bixvPWdrdCh0KTsobj1uLm5leHQpIT09cjspKGE9Z2t0KG4pKTxvJiYodD1uLG89YSk7cj10Lm5leHR9Zm9yKHQ9W3IuX10sbj1yOyhuPW4ubmV4dCkhPT1yOyl0LnB1c2gobi5fKTtmb3Iobj1YRCh0KSxzPTA7czxpOysrcyl0PWVbc10sdC54LT1uLngsdC55LT1uLnk7cmV0dXJuIG4ucn1mdW5jdGlvbiBfa3QoZSl7cmV0dXJuIHZRKGUpLGV9dmFyIHhRPU0oKCk9Pnt5USgpfSk7ZnVuY3Rpb24geWt0KGUpe3JldHVybiBlPT1udWxsP251bGw6VjIoZSl9ZnVuY3Rpb24gVjIoZSl7aWYodHlwZW9mIGUhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3JldHVybiBlfXZhciBLRD1NKCgpPT57fSk7ZnVuY3Rpb24gdGQoKXtyZXR1cm4gMH1mdW5jdGlvbiBiZyhlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIGJRPU0oKCk9Pnt9KTtmdW5jdGlvbiBWUGUoZSl7cmV0dXJuIE1hdGguc3FydChlLnZhbHVlKX1mdW5jdGlvbiBia3QoKXt2YXIgZT1udWxsLHQ9MSxyPTEsbj10ZDtmdW5jdGlvbiBpKG8pe3JldHVybiBvLng9dC8yLG8ueT1yLzIsZT9vLmVhY2hCZWZvcmUodmt0KGUpKS5lYWNoQWZ0ZXIod1EobiwuNSkpLmVhY2hCZWZvcmUoeGt0KDEpKTpvLmVhY2hCZWZvcmUodmt0KFZQZSkpLmVhY2hBZnRlcih3USh0ZCwxKSkuZWFjaEFmdGVyKHdRKG4sby5yL01hdGgubWluKHQscikpKS5lYWNoQmVmb3JlKHhrdChNYXRoLm1pbih0LHIpLygyKm8ucikpKSxvfXJldHVybiBpLnJhZGl1cz1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT15a3QobyksaSk6ZX0saS5zaXplPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PStvWzBdLHI9K29bMV0saSk6W3Qscl19LGkucGFkZGluZz1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj10eXBlb2Ygbz09ImZ1bmN0aW9uIj9vOmJnKCtvKSxpKTpufSxpfWZ1bmN0aW9uIHZrdChlKXtyZXR1cm4gZnVuY3Rpb24odCl7dC5jaGlsZHJlbnx8KHQucj1NYXRoLm1heCgwLCtlKHQpfHwwKSl9fWZ1bmN0aW9uIHdRKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKHIpe2lmKG49ci5jaGlsZHJlbil7dmFyIG4saSxvPW4ubGVuZ3RoLGE9ZShyKSp0fHwwLHM7aWYoYSlmb3IoaT0wO2k8bzsrK2kpbltpXS5yKz1hO2lmKHM9dlEobiksYSlmb3IoaT0wO2k8bzsrK2kpbltpXS5yLT1hO3Iucj1zK2F9fX1mdW5jdGlvbiB4a3QoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3ZhciByPXQucGFyZW50O3Qucio9ZSxyJiYodC54PXIueCtlKnQueCx0Lnk9ci55K2UqdC55KX19dmFyIHdrdD1NKCgpPT57eFEoKTtLRCgpO2JRKCl9KTtmdW5jdGlvbiBaRChlKXtlLngwPU1hdGgucm91bmQoZS54MCksZS55MD1NYXRoLnJvdW5kKGUueTApLGUueDE9TWF0aC5yb3VuZChlLngxKSxlLnkxPU1hdGgucm91bmQoZS55MSl9dmFyIFNRPU0oKCk9Pnt9KTtmdW5jdGlvbiBsZihlLHQscixuLGkpe2Zvcih2YXIgbz1lLmNoaWxkcmVuLGEscz0tMSxsPW8ubGVuZ3RoLGM9ZS52YWx1ZSYmKG4tdCkvZS52YWx1ZTsrK3M8bDspYT1vW3NdLGEueTA9cixhLnkxPWksYS54MD10LGEueDE9dCs9YS52YWx1ZSpjfXZhciBVMj1NKCgpPT57fSk7ZnVuY3Rpb24gU2t0KCl7dmFyIGU9MSx0PTEscj0wLG49ITE7ZnVuY3Rpb24gaShhKXt2YXIgcz1hLmhlaWdodCsxO3JldHVybiBhLngwPWEueTA9cixhLngxPWUsYS55MT10L3MsYS5lYWNoQmVmb3JlKG8odCxzKSksbiYmYS5lYWNoQmVmb3JlKFpEKSxhfWZ1bmN0aW9uIG8oYSxzKXtyZXR1cm4gZnVuY3Rpb24obCl7bC5jaGlsZHJlbiYmbGYobCxsLngwLGEqKGwuZGVwdGgrMSkvcyxsLngxLGEqKGwuZGVwdGgrMikvcyk7dmFyIGM9bC54MCx1PWwueTAsaD1sLngxLXIsZj1sLnkxLXI7aDxjJiYoYz1oPShjK2gpLzIpLGY8dSYmKHU9Zj0odStmKS8yKSxsLngwPWMsbC55MD11LGwueDE9aCxsLnkxPWZ9fXJldHVybiBpLnJvdW5kPWZ1bmN0aW9uKGEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPSEhYSxpKTpufSxpLnNpemU9ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9K2FbMF0sdD0rYVsxXSxpKTpbZSx0XX0saS5wYWRkaW5nPWZ1bmN0aW9uKGEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPSthLGkpOnJ9LGl9dmFyIE1rdD1NKCgpPT57U1EoKTtVMigpfSk7ZnVuY3Rpb24gcVBlKGUpe3JldHVybiBlLmlkfWZ1bmN0aW9uIEdQZShlKXtyZXR1cm4gZS5wYXJlbnRJZH1mdW5jdGlvbiBDa3QoKXt2YXIgZT1xUGUsdD1HUGU7ZnVuY3Rpb24gcihuKXt2YXIgaSxvLGE9bi5sZW5ndGgscyxsLGMsdT1uZXcgQXJyYXkoYSksaCxmLHA9e307Zm9yKG89MDtvPGE7KytvKWk9bltvXSxjPXVbb109bmV3IHhnKGkpLChoPWUoaSxvLG4pKSE9bnVsbCYmKGgrPSIiKSYmKGY9RWt0KyhjLmlkPWgpLHBbZl09ZiBpbiBwP1RrdDpjKTtmb3Iobz0wO288YTsrK28paWYoYz11W29dLGg9dChuW29dLG8sbiksaD09bnVsbHx8IShoKz0iIikpe2lmKHMpdGhyb3cgbmV3IEVycm9yKCJtdWx0aXBsZSByb290cyIpO3M9Y31lbHNle2lmKGw9cFtFa3QraF0sIWwpdGhyb3cgbmV3IEVycm9yKCJtaXNzaW5nOiAiK2gpO2lmKGw9PT1Ua3QpdGhyb3cgbmV3IEVycm9yKCJhbWJpZ3VvdXM6ICIraCk7bC5jaGlsZHJlbj9sLmNoaWxkcmVuLnB1c2goYyk6bC5jaGlsZHJlbj1bY10sYy5wYXJlbnQ9bH1pZighcyl0aHJvdyBuZXcgRXJyb3IoIm5vIHJvb3QiKTtpZihzLnBhcmVudD1VUGUscy5lYWNoQmVmb3JlKGZ1bmN0aW9uKGQpe2QuZGVwdGg9ZC5wYXJlbnQuZGVwdGgrMSwtLWF9KS5lYWNoQmVmb3JlKGdRKSxzLnBhcmVudD1udWxsLGE+MCl0aHJvdyBuZXcgRXJyb3IoImN5Y2xlIik7cmV0dXJuIHN9cmV0dXJuIHIuaWQ9ZnVuY3Rpb24obil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9VjIobikscik6ZX0sci5wYXJlbnRJZD1mdW5jdGlvbihuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD1WMihuKSxyKTp0fSxyfXZhciBFa3QsVVBlLFRrdCxBa3Q9TSgoKT0+e0tEKCk7WUQoKTtFa3Q9IiQiLFVQZT17ZGVwdGg6LTF9LFRrdD17fX0pO2Z1bmN0aW9uIFdQZShlLHQpe3JldHVybiBlLnBhcmVudD09PXQucGFyZW50PzE6Mn1mdW5jdGlvbiBNUShlKXt2YXIgdD1lLmNoaWxkcmVuO3JldHVybiB0P3RbMF06ZS50fWZ1bmN0aW9uIEVRKGUpe3ZhciB0PWUuY2hpbGRyZW47cmV0dXJuIHQ/dFt0Lmxlbmd0aC0xXTplLnR9ZnVuY3Rpb24gWVBlKGUsdCxyKXt2YXIgbj1yLyh0LmktZS5pKTt0LmMtPW4sdC5zKz1yLGUuYys9bix0LnorPXIsdC5tKz1yfWZ1bmN0aW9uIGpQZShlKXtmb3IodmFyIHQ9MCxyPTAsbj1lLmNoaWxkcmVuLGk9bi5sZW5ndGgsbzstLWk+PTA7KW89bltpXSxvLnorPXQsby5tKz10LHQrPW8ucysocis9by5jKX1mdW5jdGlvbiBYUGUoZSx0LHIpe3JldHVybiBlLmEucGFyZW50PT09dC5wYXJlbnQ/ZS5hOnJ9ZnVuY3Rpb24gSkQoZSx0KXt0aGlzLl89ZSx0aGlzLnBhcmVudD1udWxsLHRoaXMuY2hpbGRyZW49bnVsbCx0aGlzLkE9bnVsbCx0aGlzLmE9dGhpcyx0aGlzLno9MCx0aGlzLm09MCx0aGlzLmM9MCx0aGlzLnM9MCx0aGlzLnQ9bnVsbCx0aGlzLmk9dH1mdW5jdGlvbiAkUGUoZSl7Zm9yKHZhciB0PW5ldyBKRChlLDApLHIsbj1bdF0saSxvLGEscztyPW4ucG9wKCk7KWlmKG89ci5fLmNoaWxkcmVuKWZvcihyLmNoaWxkcmVuPW5ldyBBcnJheShzPW8ubGVuZ3RoKSxhPXMtMTthPj0wOy0tYSluLnB1c2goaT1yLmNoaWxkcmVuW2FdPW5ldyBKRChvW2FdLGEpKSxpLnBhcmVudD1yO3JldHVybih0LnBhcmVudD1uZXcgSkQobnVsbCwwKSkuY2hpbGRyZW49W3RdLHR9ZnVuY3Rpb24gUGt0KCl7dmFyIGU9V1BlLHQ9MSxyPTEsbj1udWxsO2Z1bmN0aW9uIGkoYyl7dmFyIHU9JFBlKGMpO2lmKHUuZWFjaEFmdGVyKG8pLHUucGFyZW50Lm09LXUueix1LmVhY2hCZWZvcmUoYSksbiljLmVhY2hCZWZvcmUobCk7ZWxzZXt2YXIgaD1jLGY9YyxwPWM7Yy5lYWNoQmVmb3JlKGZ1bmN0aW9uKHgpe3gueDxoLngmJihoPXgpLHgueD5mLngmJihmPXgpLHguZGVwdGg+cC5kZXB0aCYmKHA9eCl9KTt2YXIgZD1oPT09Zj8xOmUoaCxmKS8yLGc9ZC1oLngsXz10LyhmLngrZCtnKSx5PXIvKHAuZGVwdGh8fDEpO2MuZWFjaEJlZm9yZShmdW5jdGlvbih4KXt4Lng9KHgueCtnKSpfLHgueT14LmRlcHRoKnl9KX1yZXR1cm4gY31mdW5jdGlvbiBvKGMpe3ZhciB1PWMuY2hpbGRyZW4saD1jLnBhcmVudC5jaGlsZHJlbixmPWMuaT9oW2MuaS0xXTpudWxsO2lmKHUpe2pQZShjKTt2YXIgcD0odVswXS56K3VbdS5sZW5ndGgtMV0ueikvMjtmPyhjLno9Zi56K2UoYy5fLGYuXyksYy5tPWMuei1wKTpjLno9cH1lbHNlIGYmJihjLno9Zi56K2UoYy5fLGYuXykpO2MucGFyZW50LkE9cyhjLGYsYy5wYXJlbnQuQXx8aFswXSl9ZnVuY3Rpb24gYShjKXtjLl8ueD1jLnorYy5wYXJlbnQubSxjLm0rPWMucGFyZW50Lm19ZnVuY3Rpb24gcyhjLHUsaCl7aWYodSl7Zm9yKHZhciBmPWMscD1jLGQ9dSxnPWYucGFyZW50LmNoaWxkcmVuWzBdLF89Zi5tLHk9cC5tLHg9ZC5tLGI9Zy5tLFM7ZD1FUShkKSxmPU1RKGYpLGQmJmY7KWc9TVEoZykscD1FUShwKSxwLmE9YyxTPWQueit4LWYuei1fK2UoZC5fLGYuXyksUz4wJiYoWVBlKFhQZShkLGMsaCksYyxTKSxfKz1TLHkrPVMpLHgrPWQubSxfKz1mLm0sYis9Zy5tLHkrPXAubTtkJiYhRVEocCkmJihwLnQ9ZCxwLm0rPXgteSksZiYmIU1RKGcpJiYoZy50PWYsZy5tKz1fLWIsaD1jKX1yZXR1cm4gaH1mdW5jdGlvbiBsKGMpe2MueCo9dCxjLnk9Yy5kZXB0aCpyfXJldHVybiBpLnNlcGFyYXRpb249ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9YyxpKTplfSxpLnNpemU9ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49ITEsdD0rY1swXSxyPStjWzFdLGkpOm4/bnVsbDpbdCxyXX0saS5ub2RlU2l6ZT1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj0hMCx0PStjWzBdLHI9K2NbMV0saSk6bj9bdCxyXTpudWxsfSxpfXZhciBJa3Q9TSgoKT0+e1lEKCk7SkQucHJvdG90eXBlPU9iamVjdC5jcmVhdGUoeGcucHJvdG90eXBlKX0pO2Z1bmN0aW9uIHdnKGUsdCxyLG4saSl7Zm9yKHZhciBvPWUuY2hpbGRyZW4sYSxzPS0xLGw9by5sZW5ndGgsYz1lLnZhbHVlJiYoaS1yKS9lLnZhbHVlOysrczxsOylhPW9bc10sYS54MD10LGEueDE9bixhLnkwPXIsYS55MT1yKz1hLnZhbHVlKmN9dmFyIFhUPU0oKCk9Pnt9KTtmdW5jdGlvbiBDUShlLHQscixuLGksbyl7Zm9yKHZhciBhPVtdLHM9dC5jaGlsZHJlbixsLGMsdT0wLGg9MCxmPXMubGVuZ3RoLHAsZCxnPXQudmFsdWUsXyx5LHgsYixTLEMsUDt1PGY7KXtwPWktcixkPW8tbjtkbyBfPXNbaCsrXS52YWx1ZTt3aGlsZSghXyYmaDxmKTtmb3IoeT14PV8sQz1NYXRoLm1heChkL3AscC9kKS8oZyplKSxQPV8qXypDLFM9TWF0aC5tYXgoeC9QLFAveSk7aDxmOysraCl7aWYoXys9Yz1zW2hdLnZhbHVlLGM8eSYmKHk9YyksYz54JiYoeD1jKSxQPV8qXypDLGI9TWF0aC5tYXgoeC9QLFAveSksYj5TKXtfLT1jO2JyZWFrfVM9Yn1hLnB1c2gobD17dmFsdWU6XyxkaWNlOnA8ZCxjaGlsZHJlbjpzLnNsaWNlKHUsaCl9KSxsLmRpY2U/bGYobCxyLG4saSxnP24rPWQqXy9nOm8pOndnKGwscixuLGc/cis9cCpfL2c6aSxvKSxnLT1fLHU9aH1yZXR1cm4gYX12YXIgVFEsUUQsdE89TSgoKT0+e1UyKCk7WFQoKTtUUT0oMStNYXRoLnNxcnQoNSkpLzI7UUQ9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4saSxvLGEscyl7Q1EodCxuLGksbyxhLHMpfXJldHVybiByLnJhdGlvPWZ1bmN0aW9uKG4pe3JldHVybiBlKChuPStuKT4xP246MSl9LHJ9KFRRKX0pO2Z1bmN0aW9uIExrdCgpe3ZhciBlPVFELHQ9ITEscj0xLG49MSxpPVswXSxvPXRkLGE9dGQscz10ZCxsPXRkLGM9dGQ7ZnVuY3Rpb24gdShmKXtyZXR1cm4gZi54MD1mLnkwPTAsZi54MT1yLGYueTE9bixmLmVhY2hCZWZvcmUoaCksaT1bMF0sdCYmZi5lYWNoQmVmb3JlKFpEKSxmfWZ1bmN0aW9uIGgoZil7dmFyIHA9aVtmLmRlcHRoXSxkPWYueDArcCxnPWYueTArcCxfPWYueDEtcCx5PWYueTEtcDtfPGQmJihkPV89KGQrXykvMikseTxnJiYoZz15PShnK3kpLzIpLGYueDA9ZCxmLnkwPWcsZi54MT1fLGYueTE9eSxmLmNoaWxkcmVuJiYocD1pW2YuZGVwdGgrMV09byhmKS8yLGQrPWMoZiktcCxnKz1hKGYpLXAsXy09cyhmKS1wLHktPWwoZiktcCxfPGQmJihkPV89KGQrXykvMikseTxnJiYoZz15PShnK3kpLzIpLGUoZixkLGcsXyx5KSl9cmV0dXJuIHUucm91bmQ9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9ISFmLHUpOnR9LHUuc2l6ZT1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj0rZlswXSxuPStmWzFdLHUpOltyLG5dfSx1LnRpbGU9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9VjIoZiksdSk6ZX0sdS5wYWRkaW5nPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3UucGFkZGluZ0lubmVyKGYpLnBhZGRpbmdPdXRlcihmKTp1LnBhZGRpbmdJbm5lcigpfSx1LnBhZGRpbmdJbm5lcj1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz10eXBlb2YgZj09ImZ1bmN0aW9uIj9mOmJnKCtmKSx1KTpvfSx1LnBhZGRpbmdPdXRlcj1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD91LnBhZGRpbmdUb3AoZikucGFkZGluZ1JpZ2h0KGYpLnBhZGRpbmdCb3R0b20oZikucGFkZGluZ0xlZnQoZik6dS5wYWRkaW5nVG9wKCl9LHUucGFkZGluZ1RvcD1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYT10eXBlb2YgZj09ImZ1bmN0aW9uIj9mOmJnKCtmKSx1KTphfSx1LnBhZGRpbmdSaWdodD1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocz10eXBlb2YgZj09ImZ1bmN0aW9uIj9mOmJnKCtmKSx1KTpzfSx1LnBhZGRpbmdCb3R0b209ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGw9dHlwZW9mIGY9PSJmdW5jdGlvbiI/ZjpiZygrZiksdSk6bH0sdS5wYWRkaW5nTGVmdD1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYz10eXBlb2YgZj09ImZ1bmN0aW9uIj9mOmJnKCtmKSx1KTpjfSx1fXZhciBra3Q9TSgoKT0+e1NRKCk7dE8oKTtLRCgpO2JRKCl9KTtmdW5jdGlvbiBSa3QoZSx0LHIsbixpKXt2YXIgbz1lLmNoaWxkcmVuLGEscz1vLmxlbmd0aCxsLGM9bmV3IEFycmF5KHMrMSk7Zm9yKGNbMF09bD1hPTA7YTxzOysrYSljW2ErMV09bCs9b1thXS52YWx1ZTt1KDAscyxlLnZhbHVlLHQscixuLGkpO2Z1bmN0aW9uIHUoaCxmLHAsZCxnLF8seSl7aWYoaD49Zi0xKXt2YXIgeD1vW2hdO3gueDA9ZCx4LnkwPWcseC54MT1fLHgueTE9eTtyZXR1cm59Zm9yKHZhciBiPWNbaF0sUz1wLzIrYixDPWgrMSxQPWYtMTtDPFA7KXt2YXIgaz1DK1A+Pj4xO2Nba108Uz9DPWsrMTpQPWt9Uy1jW0MtMV08Y1tDXS1TJiZoKzE8QyYmLS1DO3ZhciBPPWNbQ10tYixEPXAtTztpZihfLWQ+eS1nKXt2YXIgQj0oZCpEK18qTykvcDt1KGgsQyxPLGQsZyxCLHkpLHUoQyxmLEQsQixnLF8seSl9ZWxzZXt2YXIgST0oZypEK3kqTykvcDt1KGgsQyxPLGQsZyxfLEkpLHUoQyxmLEQsZCxJLF8seSl9fX12YXIgTmt0PU0oKCk9Pnt9KTtmdW5jdGlvbiBEa3QoZSx0LHIsbixpKXsoZS5kZXB0aCYxP3dnOmxmKShlLHQscixuLGkpfXZhciBPa3Q9TSgoKT0+e1UyKCk7WFQoKX0pO3ZhciB6a3QsRmt0PU0oKCk9PntVMigpO1hUKCk7dE8oKTt6a3Q9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4saSxvLGEscyl7aWYoKGw9bi5fc3F1YXJpZnkpJiZsLnJhdGlvPT09dClmb3IodmFyIGwsYyx1LGgsZj0tMSxwLGQ9bC5sZW5ndGgsZz1uLnZhbHVlOysrZjxkOyl7Zm9yKGM9bFtmXSx1PWMuY2hpbGRyZW4saD1jLnZhbHVlPTAscD11Lmxlbmd0aDtoPHA7KytoKWMudmFsdWUrPXVbaF0udmFsdWU7Yy5kaWNlP2xmKGMsaSxvLGEsbys9KHMtbykqYy52YWx1ZS9nKTp3ZyhjLGksbyxpKz0oYS1pKSpjLnZhbHVlL2cscyksZy09Yy52YWx1ZX1lbHNlIG4uX3NxdWFyaWZ5PWw9Q1EodCxuLGksbyxhLHMpLGwucmF0aW89dH1yZXR1cm4gci5yYXRpbz1mdW5jdGlvbihuKXtyZXR1cm4gZSgobj0rbik+MT9uOjEpfSxyfShUUSl9KTt2YXIgQmt0PU0oKCk9PntITHQoKTtZRCgpO3drdCgpO3hRKCk7eVEoKTtNa3QoKTtBa3QoKTtJa3QoKTtra3QoKTtOa3QoKTtVMigpO1hUKCk7T2t0KCk7dE8oKTtGa3QoKX0pO2Z1bmN0aW9uIGVkKGUsdCxyKXtlLnByb3RvdHlwZT10LnByb3RvdHlwZT1yLHIuY29uc3RydWN0b3I9ZX1mdW5jdGlvbiBTZyhlLHQpe3ZhciByPU9iamVjdC5jcmVhdGUoZS5wcm90b3R5cGUpO2Zvcih2YXIgbiBpbiB0KXJbbl09dFtuXTtyZXR1cm4gcn12YXIgZU89TSgoKT0+e30pO2Z1bmN0aW9uIGhmKCl7fWZ1bmN0aW9uIFZrdCgpe3JldHVybiB0aGlzLnJnYigpLmZvcm1hdEhleCgpfWZ1bmN0aW9uIG42ZSgpe3JldHVybiBqa3QodGhpcykuZm9ybWF0SHNsKCl9ZnVuY3Rpb24gVWt0KCl7cmV0dXJuIHRoaXMucmdiKCkuZm9ybWF0UmdiKCl9ZnVuY3Rpb24gRWcoZSl7dmFyIHQscjtyZXR1cm4gZT0oZSsiIikudHJpbSgpLnRvTG93ZXJDYXNlKCksKHQ9S1BlLmV4ZWMoZSkpPyhyPXRbMV0ubGVuZ3RoLHQ9cGFyc2VJbnQodFsxXSwxNikscj09PTY/cWt0KHQpOnI9PT0zP25ldyBybyh0Pj44JjE1fHQ+PjQmMjQwLHQ+PjQmMTV8dCYyNDAsKHQmMTUpPDw0fHQmMTUsMSk6cj09PTg/ck8odD4+MjQmMjU1LHQ+PjE2JjI1NSx0Pj44JjI1NSwodCYyNTUpLzI1NSk6cj09PTQ/ck8odD4+MTImMTV8dD4+OCYyNDAsdD4+OCYxNXx0Pj40JjI0MCx0Pj40JjE1fHQmMjQwLCgodCYxNSk8PDR8dCYxNSkvMjU1KTpudWxsKToodD1aUGUuZXhlYyhlKSk/bmV3IHJvKHRbMV0sdFsyXSx0WzNdLDEpOih0PUpQZS5leGVjKGUpKT9uZXcgcm8odFsxXSoyNTUvMTAwLHRbMl0qMjU1LzEwMCx0WzNdKjI1NS8xMDAsMSk6KHQ9UVBlLmV4ZWMoZSkpP3JPKHRbMV0sdFsyXSx0WzNdLHRbNF0pOih0PXQ2ZS5leGVjKGUpKT9yTyh0WzFdKjI1NS8xMDAsdFsyXSoyNTUvMTAwLHRbM10qMjU1LzEwMCx0WzRdKToodD1lNmUuZXhlYyhlKSk/WWt0KHRbMV0sdFsyXS8xMDAsdFszXS8xMDAsMSk6KHQ9cjZlLmV4ZWMoZSkpP1lrdCh0WzFdLHRbMl0vMTAwLHRbM10vMTAwLHRbNF0pOkhrdC5oYXNPd25Qcm9wZXJ0eShlKT9xa3QoSGt0W2VdKTplPT09InRyYW5zcGFyZW50Ij9uZXcgcm8oTmFOLE5hTixOYU4sMCk6bnVsbH1mdW5jdGlvbiBxa3QoZSl7cmV0dXJuIG5ldyBybyhlPj4xNiYyNTUsZT4+OCYyNTUsZSYyNTUsMSl9ZnVuY3Rpb24gck8oZSx0LHIsbil7cmV0dXJuIG48PTAmJihlPXQ9cj1OYU4pLG5ldyBybyhlLHQscixuKX1mdW5jdGlvbiBLVChlKXtyZXR1cm4gZSBpbnN0YW5jZW9mIGhmfHwoZT1FZyhlKSksZT8oZT1lLnJnYigpLG5ldyBybyhlLnIsZS5nLGUuYixlLm9wYWNpdHkpKTpuZXcgcm99ZnVuY3Rpb24gRzIoZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP0tUKGUpOm5ldyBybyhlLHQscixuPT1udWxsPzE6bil9ZnVuY3Rpb24gcm8oZSx0LHIsbil7dGhpcy5yPStlLHRoaXMuZz0rdCx0aGlzLmI9K3IsdGhpcy5vcGFjaXR5PStufWZ1bmN0aW9uIEdrdCgpe3JldHVybiIjIitBUSh0aGlzLnIpK0FRKHRoaXMuZykrQVEodGhpcy5iKX1mdW5jdGlvbiBXa3QoKXt2YXIgZT10aGlzLm9wYWNpdHk7cmV0dXJuIGU9aXNOYU4oZSk/MTpNYXRoLm1heCgwLE1hdGgubWluKDEsZSkpLChlPT09MT8icmdiKCI6InJnYmEoIikrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCh0aGlzLnIpfHwwKSkrIiwgIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKHRoaXMuZyl8fDApKSsiLCAiK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQodGhpcy5iKXx8MCkpKyhlPT09MT8iKSI6IiwgIitlKyIpIil9ZnVuY3Rpb24gQVEoZSl7cmV0dXJuIGU9TWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZChlKXx8MCkpLChlPDE2PyIwIjoiIikrZS50b1N0cmluZygxNil9ZnVuY3Rpb24gWWt0KGUsdCxyLG4pe3JldHVybiBuPD0wP2U9dD1yPU5hTjpyPD0wfHxyPj0xP2U9dD1OYU46dDw9MCYmKGU9TmFOKSxuZXcgY2YoZSx0LHIsbil9ZnVuY3Rpb24gamt0KGUpe2lmKGUgaW5zdGFuY2VvZiBjZilyZXR1cm4gbmV3IGNmKGUuaCxlLnMsZS5sLGUub3BhY2l0eSk7aWYoZSBpbnN0YW5jZW9mIGhmfHwoZT1FZyhlKSksIWUpcmV0dXJuIG5ldyBjZjtpZihlIGluc3RhbmNlb2YgY2YpcmV0dXJuIGU7ZT1lLnJnYigpO3ZhciB0PWUuci8yNTUscj1lLmcvMjU1LG49ZS5iLzI1NSxpPU1hdGgubWluKHQscixuKSxvPU1hdGgubWF4KHQscixuKSxhPU5hTixzPW8taSxsPShvK2kpLzI7cmV0dXJuIHM/KHQ9PT1vP2E9KHItbikvcysocjxuKSo2OnI9PT1vP2E9KG4tdCkvcysyOmE9KHQtcikvcys0LHMvPWw8LjU/bytpOjItby1pLGEqPTYwKTpzPWw+MCYmbDwxPzA6YSxuZXcgY2YoYSxzLGwsZS5vcGFjaXR5KX1mdW5jdGlvbiBaVChlLHQscixuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTE/amt0KGUpOm5ldyBjZihlLHQscixuPT1udWxsPzE6bil9ZnVuY3Rpb24gY2YoZSx0LHIsbil7dGhpcy5oPStlLHRoaXMucz0rdCx0aGlzLmw9K3IsdGhpcy5vcGFjaXR5PStufWZ1bmN0aW9uIFBRKGUsdCxyKXtyZXR1cm4oZTw2MD90KyhyLXQpKmUvNjA6ZTwxODA/cjplPDI0MD90KyhyLXQpKigyNDAtZSkvNjA6dCkqMjU1fXZhciBNZyxXeSxxMiwkVCx1ZixLUGUsWlBlLEpQZSxRUGUsdDZlLGU2ZSxyNmUsSGt0LG5PPU0oKCk9PntlTygpO01nPS43LFd5PTEvTWcscTI9IlxccyooWystXT9cXGQrKVxccyoiLCRUPSJcXHMqKFsrLV0/XFxkKlxcLj9cXGQrKD86W2VFXVsrLV0/XFxkKyk/KVxccyoiLHVmPSJcXHMqKFsrLV0/XFxkKlxcLj9cXGQrKD86W2VFXVsrLV0/XFxkKyk/KSVcXHMqIixLUGU9L14jKFswLTlhLWZdezMsOH0pJC8sWlBlPW5ldyBSZWdFeHAoIl5yZ2JcXCgiK1txMixxMixxMl0rIlxcKSQiKSxKUGU9bmV3IFJlZ0V4cCgiXnJnYlxcKCIrW3VmLHVmLHVmXSsiXFwpJCIpLFFQZT1uZXcgUmVnRXhwKCJecmdiYVxcKCIrW3EyLHEyLHEyLCRUXSsiXFwpJCIpLHQ2ZT1uZXcgUmVnRXhwKCJecmdiYVxcKCIrW3VmLHVmLHVmLCRUXSsiXFwpJCIpLGU2ZT1uZXcgUmVnRXhwKCJeaHNsXFwoIitbJFQsdWYsdWZdKyJcXCkkIikscjZlPW5ldyBSZWdFeHAoIl5oc2xhXFwoIitbJFQsdWYsdWYsJFRdKyJcXCkkIiksSGt0PXthbGljZWJsdWU6MTU3OTIzODMsYW50aXF1ZXdoaXRlOjE2NDQ0Mzc1LGFxdWE6NjU1MzUsYXF1YW1hcmluZTo4Mzg4NTY0LGF6dXJlOjE1Nzk0MTc1LGJlaWdlOjE2MTE5MjYwLGJpc3F1ZToxNjc3MDI0NCxibGFjazowLGJsYW5jaGVkYWxtb25kOjE2NzcyMDQ1LGJsdWU6MjU1LGJsdWV2aW9sZXQ6OTA1NTIwMixicm93bjoxMDgyNDIzNCxidXJseXdvb2Q6MTQ1OTYyMzEsY2FkZXRibHVlOjYyNjY1MjgsY2hhcnRyZXVzZTo4Mzg4MzUyLGNob2NvbGF0ZToxMzc4OTQ3MCxjb3JhbDoxNjc0NDI3Mixjb3JuZmxvd2VyYmx1ZTo2NTkxOTgxLGNvcm5zaWxrOjE2Nzc1Mzg4LGNyaW1zb246MTQ0MjMxMDAsY3lhbjo2NTUzNSxkYXJrYmx1ZToxMzksZGFya2N5YW46MzU3MjMsZGFya2dvbGRlbnJvZDoxMjA5MjkzOSxkYXJrZ3JheToxMTExOTAxNyxkYXJrZ3JlZW46MjU2MDAsZGFya2dyZXk6MTExMTkwMTcsZGFya2toYWtpOjEyNDMzMjU5LGRhcmttYWdlbnRhOjkxMDk2NDMsZGFya29saXZlZ3JlZW46NTU5Nzk5OSxkYXJrb3JhbmdlOjE2NzQ3NTIwLGRhcmtvcmNoaWQ6MTAwNDAwMTIsZGFya3JlZDo5MTA5NTA0LGRhcmtzYWxtb246MTUzMDg0MTAsZGFya3NlYWdyZWVuOjk0MTk5MTksZGFya3NsYXRlYmx1ZTo0NzM0MzQ3LGRhcmtzbGF0ZWdyYXk6MzEwMDQ5NSxkYXJrc2xhdGVncmV5OjMxMDA0OTUsZGFya3R1cnF1b2lzZTo1Mjk0NSxkYXJrdmlvbGV0Ojk2OTk1MzksZGVlcHBpbms6MTY3MTY5NDcsZGVlcHNreWJsdWU6NDkxNTEsZGltZ3JheTo2OTA4MjY1LGRpbWdyZXk6NjkwODI2NSxkb2RnZXJibHVlOjIwMDMxOTksZmlyZWJyaWNrOjExNjc0MTQ2LGZsb3JhbHdoaXRlOjE2Nzc1OTIwLGZvcmVzdGdyZWVuOjIyNjM4NDIsZnVjaHNpYToxNjcxMTkzNSxnYWluc2Jvcm86MTQ0NzQ0NjAsZ2hvc3R3aGl0ZToxNjMxNjY3MSxnb2xkOjE2NzY2NzIwLGdvbGRlbnJvZDoxNDMyOTEyMCxncmF5Ojg0MjE1MDQsZ3JlZW46MzI3NjgsZ3JlZW55ZWxsb3c6MTE0MDMwNTUsZ3JleTo4NDIxNTA0LGhvbmV5ZGV3OjE1Nzk0MTYwLGhvdHBpbms6MTY3Mzg3NDAsaW5kaWFucmVkOjEzNDU4NTI0LGluZGlnbzo0OTE1MzMwLGl2b3J5OjE2Nzc3MjAwLGtoYWtpOjE1Nzg3NjYwLGxhdmVuZGVyOjE1MTMyNDEwLGxhdmVuZGVyYmx1c2g6MTY3NzMzNjUsbGF3bmdyZWVuOjgxOTA5NzYsbGVtb25jaGlmZm9uOjE2Nzc1ODg1LGxpZ2h0Ymx1ZToxMTM5MzI1NCxsaWdodGNvcmFsOjE1NzYxNTM2LGxpZ2h0Y3lhbjoxNDc0NTU5OSxsaWdodGdvbGRlbnJvZHllbGxvdzoxNjQ0ODIxMCxsaWdodGdyYXk6MTM4ODIzMjMsbGlnaHRncmVlbjo5NDk4MjU2LGxpZ2h0Z3JleToxMzg4MjMyMyxsaWdodHBpbms6MTY3NTg0NjUsbGlnaHRzYWxtb246MTY3NTI3NjIsbGlnaHRzZWFncmVlbjoyMTQyODkwLGxpZ2h0c2t5Ymx1ZTo4OTAwMzQ2LGxpZ2h0c2xhdGVncmF5Ojc4MzM3NTMsbGlnaHRzbGF0ZWdyZXk6NzgzMzc1MyxsaWdodHN0ZWVsYmx1ZToxMTU4NDczNCxsaWdodHllbGxvdzoxNjc3NzE4NCxsaW1lOjY1MjgwLGxpbWVncmVlbjozMzI5MzMwLGxpbmVuOjE2NDQ1NjcwLG1hZ2VudGE6MTY3MTE5MzUsbWFyb29uOjgzODg2MDgsbWVkaXVtYXF1YW1hcmluZTo2NzM3MzIyLG1lZGl1bWJsdWU6MjA1LG1lZGl1bW9yY2hpZDoxMjIxMTY2NyxtZWRpdW1wdXJwbGU6OTY2MjY4MyxtZWRpdW1zZWFncmVlbjozOTc4MDk3LG1lZGl1bXNsYXRlYmx1ZTo4MDg3NzkwLG1lZGl1bXNwcmluZ2dyZWVuOjY0MTU0LG1lZGl1bXR1cnF1b2lzZTo0NzcyMzAwLG1lZGl1bXZpb2xldHJlZDoxMzA0NzE3MyxtaWRuaWdodGJsdWU6MTY0NDkxMixtaW50Y3JlYW06MTYxMjE4NTAsbWlzdHlyb3NlOjE2NzcwMjczLG1vY2Nhc2luOjE2NzcwMjI5LG5hdmFqb3doaXRlOjE2NzY4Njg1LG5hdnk6MTI4LG9sZGxhY2U6MTY2NDM1NTgsb2xpdmU6ODQyMTM3NixvbGl2ZWRyYWI6NzA0ODczOSxvcmFuZ2U6MTY3NTM5MjAsb3JhbmdlcmVkOjE2NzI5MzQ0LG9yY2hpZDoxNDMxNTczNCxwYWxlZ29sZGVucm9kOjE1NjU3MTMwLHBhbGVncmVlbjoxMDAyNTg4MCxwYWxldHVycXVvaXNlOjExNTI5OTY2LHBhbGV2aW9sZXRyZWQ6MTQzODEyMDMscGFwYXlhd2hpcDoxNjc3MzA3NyxwZWFjaHB1ZmY6MTY3Njc2NzMscGVydToxMzQ2ODk5MSxwaW5rOjE2NzYxMDM1LHBsdW06MTQ1MjQ2MzcscG93ZGVyYmx1ZToxMTU5MTkxMCxwdXJwbGU6ODM4ODczNixyZWJlY2NhcHVycGxlOjY2OTc4ODEscmVkOjE2NzExNjgwLHJvc3licm93bjoxMjM1NzUxOSxyb3lhbGJsdWU6NDI4Njk0NSxzYWRkbGVicm93bjo5MTI3MTg3LHNhbG1vbjoxNjQxNjg4MixzYW5keWJyb3duOjE2MDMyODY0LHNlYWdyZWVuOjMwNTAzMjcsc2Vhc2hlbGw6MTY3NzQ2Mzgsc2llbm5hOjEwNTA2Nzk3LHNpbHZlcjoxMjYzMjI1Nixza3libHVlOjg5MDAzMzEsc2xhdGVibHVlOjY5NzAwNjEsc2xhdGVncmF5OjczNzI5NDQsc2xhdGVncmV5OjczNzI5NDQsc25vdzoxNjc3NTkzMCxzcHJpbmdncmVlbjo2NTQwNyxzdGVlbGJsdWU6NDYyMDk4MCx0YW46MTM4MDg3ODAsdGVhbDozMjg5Nix0aGlzdGxlOjE0MjA0ODg4LHRvbWF0bzoxNjczNzA5NSx0dXJxdW9pc2U6NDI1MTg1Nix2aW9sZXQ6MTU2MzEwODYsd2hlYXQ6MTYxMTMzMzEsd2hpdGU6MTY3NzcyMTUsd2hpdGVzbW9rZToxNjExOTI4NSx5ZWxsb3c6MTY3NzY5NjAseWVsbG93Z3JlZW46MTAxNDUwNzR9O2VkKGhmLEVnLHtjb3B5OmZ1bmN0aW9uKGUpe3JldHVybiBPYmplY3QuYXNzaWduKG5ldyB0aGlzLmNvbnN0cnVjdG9yLHRoaXMsZSl9LGRpc3BsYXlhYmxlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMucmdiKCkuZGlzcGxheWFibGUoKX0saGV4OlZrdCxmb3JtYXRIZXg6Vmt0LGZvcm1hdEhzbDpuNmUsZm9ybWF0UmdiOlVrdCx0b1N0cmluZzpVa3R9KTtlZChybyxHMixTZyhoZix7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9XeTpNYXRoLnBvdyhXeSxlKSxuZXcgcm8odGhpcy5yKmUsdGhpcy5nKmUsdGhpcy5iKmUsdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/TWc6TWF0aC5wb3coTWcsZSksbmV3IHJvKHRoaXMuciplLHRoaXMuZyplLHRoaXMuYiplLHRoaXMub3BhY2l0eSl9LHJnYjpmdW5jdGlvbigpe3JldHVybiB0aGlzfSxkaXNwbGF5YWJsZTpmdW5jdGlvbigpe3JldHVybi0uNTw9dGhpcy5yJiZ0aGlzLnI8MjU1LjUmJi0uNTw9dGhpcy5nJiZ0aGlzLmc8MjU1LjUmJi0uNTw9dGhpcy5iJiZ0aGlzLmI8MjU1LjUmJjA8PXRoaXMub3BhY2l0eSYmdGhpcy5vcGFjaXR5PD0xfSxoZXg6R2t0LGZvcm1hdEhleDpHa3QsZm9ybWF0UmdiOldrdCx0b1N0cmluZzpXa3R9KSk7ZWQoY2YsWlQsU2coaGYse2JyaWdodGVyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/V3k6TWF0aC5wb3coV3ksZSksbmV3IGNmKHRoaXMuaCx0aGlzLnMsdGhpcy5sKmUsdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/TWc6TWF0aC5wb3coTWcsZSksbmV3IGNmKHRoaXMuaCx0aGlzLnMsdGhpcy5sKmUsdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5oJTM2MCsodGhpcy5oPDApKjM2MCx0PWlzTmFOKGUpfHxpc05hTih0aGlzLnMpPzA6dGhpcy5zLHI9dGhpcy5sLG49cisocjwuNT9yOjEtcikqdCxpPTIqci1uO3JldHVybiBuZXcgcm8oUFEoZT49MjQwP2UtMjQwOmUrMTIwLGksbiksUFEoZSxpLG4pLFBRKGU8MTIwP2UrMjQwOmUtMTIwLGksbiksdGhpcy5vcGFjaXR5KX0sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4oMDw9dGhpcy5zJiZ0aGlzLnM8PTF8fGlzTmFOKHRoaXMucykpJiYwPD10aGlzLmwmJnRoaXMubDw9MSYmMDw9dGhpcy5vcGFjaXR5JiZ0aGlzLm9wYWNpdHk8PTF9LGZvcm1hdEhzbDpmdW5jdGlvbigpe3ZhciBlPXRoaXMub3BhY2l0eTtyZXR1cm4gZT1pc05hTihlKT8xOk1hdGgubWF4KDAsTWF0aC5taW4oMSxlKSksKGU9PT0xPyJoc2woIjoiaHNsYSgiKSsodGhpcy5ofHwwKSsiLCAiKyh0aGlzLnN8fDApKjEwMCsiJSwgIisodGhpcy5sfHwwKSoxMDArIiUiKyhlPT09MT8iKSI6IiwgIitlKyIpIil9fSkpfSk7dmFyIGlPLG9PLElRPU0oKCk9PntpTz1NYXRoLlBJLzE4MCxvTz0xODAvTWF0aC5QSX0pO2Z1bmN0aW9uIFFrdChlKXtpZihlIGluc3RhbmNlb2YgZmYpcmV0dXJuIG5ldyBmZihlLmwsZS5hLGUuYixlLm9wYWNpdHkpO2lmKGUgaW5zdGFuY2VvZiByZClyZXR1cm4gdDh0KGUpO2UgaW5zdGFuY2VvZiByb3x8KGU9S1QoZSkpO3ZhciB0PU5RKGUucikscj1OUShlLmcpLG49TlEoZS5iKSxpPUxRKCguMjIyNTA0NSp0Ky43MTY4Nzg2KnIrLjA2MDYxNjkqbikvJGt0KSxvLGE7cmV0dXJuIHQ9PT1yJiZyPT09bj9vPWE9aToobz1MUSgoLjQzNjA3NDcqdCsuMzg1MDY0OSpyKy4xNDMwODA0Km4pL1hrdCksYT1MUSgoLjAxMzkzMjIqdCsuMDk3MTA0NSpyKy43MTQxNzMzKm4pL0trdCkpLG5ldyBmZigxMTYqaS0xNiw1MDAqKG8taSksMjAwKihpLWEpLGUub3BhY2l0eSl9ZnVuY3Rpb24gWTIoZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP1FrdChlKTpuZXcgZmYoZSx0LHIsbj09bnVsbD8xOm4pfWZ1bmN0aW9uIGZmKGUsdCxyLG4pe3RoaXMubD0rZSx0aGlzLmE9K3QsdGhpcy5iPStyLHRoaXMub3BhY2l0eT0rbn1mdW5jdGlvbiBMUShlKXtyZXR1cm4gZT5pNmU/TWF0aC5wb3coZSwxLzMpOmUvSmt0K1prdH1mdW5jdGlvbiBrUShlKXtyZXR1cm4gZT5XMj9lKmUqZTpKa3QqKGUtWmt0KX1mdW5jdGlvbiBSUShlKXtyZXR1cm4gMjU1KihlPD0uMDAzMTMwOD8xMi45MiplOjEuMDU1Kk1hdGgucG93KGUsMS8yLjQpLS4wNTUpfWZ1bmN0aW9uIE5RKGUpe3JldHVybihlLz0yNTUpPD0uMDQwNDU/ZS8xMi45MjpNYXRoLnBvdygoZSsuMDU1KS8xLjA1NSwyLjQpfWZ1bmN0aW9uIG82ZShlKXtpZihlIGluc3RhbmNlb2YgcmQpcmV0dXJuIG5ldyByZChlLmgsZS5jLGUubCxlLm9wYWNpdHkpO2lmKGUgaW5zdGFuY2VvZiBmZnx8KGU9UWt0KGUpKSxlLmE9PT0wJiZlLmI9PT0wKXJldHVybiBuZXcgcmQoTmFOLDA8ZS5sJiZlLmw8MTAwPzA6TmFOLGUubCxlLm9wYWNpdHkpO3ZhciB0PU1hdGguYXRhbjIoZS5iLGUuYSkqb087cmV0dXJuIG5ldyByZCh0PDA/dCszNjA6dCxNYXRoLnNxcnQoZS5hKmUuYStlLmIqZS5iKSxlLmwsZS5vcGFjaXR5KX1mdW5jdGlvbiBKVChlLHQscixuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTE/bzZlKGUpOm5ldyByZChlLHQscixuPT1udWxsPzE6bil9ZnVuY3Rpb24gcmQoZSx0LHIsbil7dGhpcy5oPStlLHRoaXMuYz0rdCx0aGlzLmw9K3IsdGhpcy5vcGFjaXR5PStufWZ1bmN0aW9uIHQ4dChlKXtpZihpc05hTihlLmgpKXJldHVybiBuZXcgZmYoZS5sLDAsMCxlLm9wYWNpdHkpO3ZhciB0PWUuaCppTztyZXR1cm4gbmV3IGZmKGUubCxNYXRoLmNvcyh0KSplLmMsTWF0aC5zaW4odCkqZS5jLGUub3BhY2l0eSl9dmFyIGFPLFhrdCwka3QsS2t0LFprdCxXMixKa3QsaTZlLGU4dD1NKCgpPT57ZU8oKTtuTygpO0lRKCk7YU89MTgsWGt0PS45NjQyMiwka3Q9MSxLa3Q9LjgyNTIxLFprdD00LzI5LFcyPTYvMjksSmt0PTMqVzIqVzIsaTZlPVcyKlcyKlcyO2VkKGZmLFkyLFNnKGhmLHticmlnaHRlcjpmdW5jdGlvbihlKXtyZXR1cm4gbmV3IGZmKHRoaXMubCthTyooZT09bnVsbD8xOmUpLHRoaXMuYSx0aGlzLmIsdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKGUpe3JldHVybiBuZXcgZmYodGhpcy5sLWFPKihlPT1udWxsPzE6ZSksdGhpcy5hLHRoaXMuYix0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXt2YXIgZT0odGhpcy5sKzE2KS8xMTYsdD1pc05hTih0aGlzLmEpP2U6ZSt0aGlzLmEvNTAwLHI9aXNOYU4odGhpcy5iKT9lOmUtdGhpcy5iLzIwMDtyZXR1cm4gdD1Ya3Qqa1EodCksZT0ka3Qqa1EoZSkscj1La3Qqa1EociksbmV3IHJvKFJRKDMuMTMzODU2MSp0LTEuNjE2ODY2NyplLS40OTA2MTQ2KnIpLFJRKC0uOTc4NzY4NCp0KzEuOTE2MTQxNSplKy4wMzM0NTQqciksUlEoLjA3MTk0NTMqdC0uMjI4OTkxNCplKzEuNDA1MjQyNypyKSx0aGlzLm9wYWNpdHkpfX0pKTtlZChyZCxKVCxTZyhoZix7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyByZCh0aGlzLmgsdGhpcy5jLHRoaXMubCthTyooZT09bnVsbD8xOmUpLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihlKXtyZXR1cm4gbmV3IHJkKHRoaXMuaCx0aGlzLmMsdGhpcy5sLWFPKihlPT1udWxsPzE6ZSksdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7cmV0dXJuIHQ4dCh0aGlzKS5yZ2IoKX19KSl9KTtmdW5jdGlvbiBhNmUoZSl7aWYoZSBpbnN0YW5jZW9mIFl5KXJldHVybiBuZXcgWXkoZS5oLGUucyxlLmwsZS5vcGFjaXR5KTtlIGluc3RhbmNlb2Ygcm98fChlPUtUKGUpKTt2YXIgdD1lLnIvMjU1LHI9ZS5nLzI1NSxuPWUuYi8yNTUsaT0oaTh0Km4rcjh0KnQtbjh0KnIpLyhpOHQrcjh0LW44dCksbz1uLWksYT0oUVQqKHItaSktT1Eqbykvc08scz1NYXRoLnNxcnQoYSphK28qbykvKFFUKmkqKDEtaSkpLGw9cz9NYXRoLmF0YW4yKGEsbykqb08tMTIwOk5hTjtyZXR1cm4gbmV3IFl5KGw8MD9sKzM2MDpsLHMsaSxlLm9wYWNpdHkpfWZ1bmN0aW9uIGoyKGUsdCxyLG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MT9hNmUoZSk6bmV3IFl5KGUsdCxyLG49PW51bGw/MTpuKX1mdW5jdGlvbiBZeShlLHQscixuKXt0aGlzLmg9K2UsdGhpcy5zPSt0LHRoaXMubD0rcix0aGlzLm9wYWNpdHk9K259dmFyIG84dCxEUSxPUSxzTyxRVCxyOHQsbjh0LGk4dCxhOHQ9TSgoKT0+e2VPKCk7bk8oKTtJUSgpO284dD0tLjE0ODYxLERRPTEuNzgyNzcsT1E9LS4yOTIyNyxzTz0tLjkwNjQ5LFFUPTEuOTcyOTQscjh0PVFUKnNPLG44dD1RVCpEUSxpOHQ9RFEqT1Etc08qbzh0O2VkKFl5LGoyLFNnKGhmLHticmlnaHRlcjpmdW5jdGlvbihlKXtyZXR1cm4gZT1lPT1udWxsP1d5Ok1hdGgucG93KFd5LGUpLG5ldyBZeSh0aGlzLmgsdGhpcy5zLHRoaXMubCplLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihlKXtyZXR1cm4gZT1lPT1udWxsP01nOk1hdGgucG93KE1nLGUpLG5ldyBZeSh0aGlzLmgsdGhpcy5zLHRoaXMubCplLHRoaXMub3BhY2l0eSl9LHJnYjpmdW5jdGlvbigpe3ZhciBlPWlzTmFOKHRoaXMuaCk/MDoodGhpcy5oKzEyMCkqaU8sdD0rdGhpcy5sLHI9aXNOYU4odGhpcy5zKT8wOnRoaXMucyp0KigxLXQpLG49TWF0aC5jb3MoZSksaT1NYXRoLnNpbihlKTtyZXR1cm4gbmV3IHJvKDI1NSoodCtyKihvOHQqbitEUSppKSksMjU1Kih0K3IqKE9RKm4rc08qaSkpLDI1NSoodCtyKihRVCpuKSksdGhpcy5vcGFjaXR5KX19KSl9KTt2YXIgank9TSgoKT0+e25PKCk7ZTh0KCk7YTh0KCl9KTtmdW5jdGlvbiB6UShlLHQscixuLGkpe3ZhciBvPWUqZSxhPW8qZTtyZXR1cm4oKDEtMyplKzMqby1hKSp0Kyg0LTYqbyszKmEpKnIrKDErMyplKzMqby0zKmEpKm4rYSppKS82fWZ1bmN0aW9uIGxPKGUpe3ZhciB0PWUubGVuZ3RoLTE7cmV0dXJuIGZ1bmN0aW9uKHIpe3ZhciBuPXI8PTA/cj0wOnI+PTE/KHI9MSx0LTEpOk1hdGguZmxvb3Iocip0KSxpPWVbbl0sbz1lW24rMV0sYT1uPjA/ZVtuLTFdOjIqaS1vLHM9bjx0LTE/ZVtuKzJdOjIqby1pO3JldHVybiB6USgoci1uL3QpKnQsYSxpLG8scyl9fXZhciBjTz1NKCgpPT57fSk7ZnVuY3Rpb24gdU8oZSl7dmFyIHQ9ZS5sZW5ndGg7cmV0dXJuIGZ1bmN0aW9uKHIpe3ZhciBuPU1hdGguZmxvb3IoKChyJT0xKTwwPysrcjpyKSp0KSxpPWVbKG4rdC0xKSV0XSxvPWVbbiV0XSxhPWVbKG4rMSkldF0scz1lWyhuKzIpJXRdO3JldHVybiB6USgoci1uL3QpKnQsaSxvLGEscyl9fXZhciBGUT1NKCgpPT57Y08oKX0pO2Z1bmN0aW9uIFgyKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX12YXIgQlE9TSgoKT0+e30pO2Z1bmN0aW9uIHM4dChlLHQpe3JldHVybiBmdW5jdGlvbihyKXtyZXR1cm4gZStyKnR9fWZ1bmN0aW9uIHM2ZShlLHQscil7cmV0dXJuIGU9TWF0aC5wb3coZSxyKSx0PU1hdGgucG93KHQsciktZSxyPTEvcixmdW5jdGlvbihuKXtyZXR1cm4gTWF0aC5wb3coZStuKnQscil9fWZ1bmN0aW9uICQyKGUsdCl7dmFyIHI9dC1lO3JldHVybiByP3M4dChlLHI+MTgwfHxyPC0xODA/ci0zNjAqTWF0aC5yb3VuZChyLzM2MCk6cik6WDIoaXNOYU4oZSk/dDplKX1mdW5jdGlvbiBsOHQoZSl7cmV0dXJuKGU9K2UpPT0xP1FuOmZ1bmN0aW9uKHQscil7cmV0dXJuIHItdD9zNmUodCxyLGUpOlgyKGlzTmFOKHQpP3I6dCl9fWZ1bmN0aW9uIFFuKGUsdCl7dmFyIHI9dC1lO3JldHVybiByP3M4dChlLHIpOlgyKGlzTmFOKGUpP3Q6ZSl9dmFyIEsyPU0oKCk9PntCUSgpfSk7ZnVuY3Rpb24gYzh0KGUpe3JldHVybiBmdW5jdGlvbih0KXt2YXIgcj10Lmxlbmd0aCxuPW5ldyBBcnJheShyKSxpPW5ldyBBcnJheShyKSxvPW5ldyBBcnJheShyKSxhLHM7Zm9yKGE9MDthPHI7KythKXM9RzIodFthXSksblthXT1zLnJ8fDAsaVthXT1zLmd8fDAsb1thXT1zLmJ8fDA7cmV0dXJuIG49ZShuKSxpPWUoaSksbz1lKG8pLHMub3BhY2l0eT0xLGZ1bmN0aW9uKGwpe3JldHVybiBzLnI9bihsKSxzLmc9aShsKSxzLmI9byhsKSxzKyIifX19dmFyIHRDLHU4dCxoOHQsSFE9TSgoKT0+e2p5KCk7Y08oKTtGUSgpO0syKCk7dEM9ZnVuY3Rpb24gZSh0KXt2YXIgcj1sOHQodCk7ZnVuY3Rpb24gbihpLG8pe3ZhciBhPXIoKGk9RzIoaSkpLnIsKG89RzIobykpLnIpLHM9cihpLmcsby5nKSxsPXIoaS5iLG8uYiksYz1RbihpLm9wYWNpdHksby5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24odSl7cmV0dXJuIGkucj1hKHUpLGkuZz1zKHUpLGkuYj1sKHUpLGkub3BhY2l0eT1jKHUpLGkrIiJ9fXJldHVybiBuLmdhbW1hPWUsbn0oMSk7dTh0PWM4dChsTyksaDh0PWM4dCh1Tyl9KTtmdW5jdGlvbiBoTyhlLHQpe3ZhciByPXQ/dC5sZW5ndGg6MCxuPWU/TWF0aC5taW4ocixlLmxlbmd0aCk6MCxpPW5ldyBBcnJheShuKSxvPW5ldyBBcnJheShyKSxhO2ZvcihhPTA7YTxuOysrYSlpW2FdPVoyKGVbYV0sdFthXSk7Zm9yKDthPHI7KythKW9bYV09dFthXTtyZXR1cm4gZnVuY3Rpb24ocyl7Zm9yKGE9MDthPG47KythKW9bYV09aVthXShzKTtyZXR1cm4gb319dmFyIFZRPU0oKCk9PntmTygpfSk7ZnVuY3Rpb24gcE8oZSx0KXt2YXIgcj1uZXcgRGF0ZTtyZXR1cm4gZT0rZSx0LT1lLGZ1bmN0aW9uKG4pe3JldHVybiByLnNldFRpbWUoZSt0Km4pLHJ9fXZhciBVUT1NKCgpPT57fSk7ZnVuY3Rpb24gQXMoZSx0KXtyZXR1cm4gZT0rZSx0LT1lLGZ1bmN0aW9uKHIpe3JldHVybiBlK3Qqcn19dmFyIGVDPU0oKCk9Pnt9KTtmdW5jdGlvbiBkTyhlLHQpe3ZhciByPXt9LG49e30saTsoZT09PW51bGx8fHR5cGVvZiBlIT0ib2JqZWN0IikmJihlPXt9KSwodD09PW51bGx8fHR5cGVvZiB0IT0ib2JqZWN0IikmJih0PXt9KTtmb3IoaSBpbiB0KWkgaW4gZT9yW2ldPVoyKGVbaV0sdFtpXSk6bltpXT10W2ldO3JldHVybiBmdW5jdGlvbihvKXtmb3IoaSBpbiByKW5baV09cltpXShvKTtyZXR1cm4gbn19dmFyIHFRPU0oKCk9PntmTygpfSk7ZnVuY3Rpb24gbDZlKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX1mdW5jdGlvbiBjNmUoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3JldHVybiBlKHQpKyIifX1mdW5jdGlvbiBtTyhlLHQpe3ZhciByPVdRLmxhc3RJbmRleD1HUS5sYXN0SW5kZXg9MCxuLGksbyxhPS0xLHM9W10sbD1bXTtmb3IoZT1lKyIiLHQ9dCsiIjsobj1XUS5leGVjKGUpKSYmKGk9R1EuZXhlYyh0KSk7KShvPWkuaW5kZXgpPnImJihvPXQuc2xpY2UocixvKSxzW2FdP3NbYV0rPW86c1srK2FdPW8pLChuPW5bMF0pPT09KGk9aVswXSk/c1thXT9zW2FdKz1pOnNbKythXT1pOihzWysrYV09bnVsbCxsLnB1c2goe2k6YSx4OkFzKG4saSl9KSkscj1HUS5sYXN0SW5kZXg7cmV0dXJuIHI8dC5sZW5ndGgmJihvPXQuc2xpY2Uociksc1thXT9zW2FdKz1vOnNbKythXT1vKSxzLmxlbmd0aDwyP2xbMF0/YzZlKGxbMF0ueCk6bDZlKHQpOih0PWwubGVuZ3RoLGZ1bmN0aW9uKGMpe2Zvcih2YXIgdT0wLGg7dTx0OysrdSlzWyhoPWxbdV0pLmldPWgueChjKTtyZXR1cm4gcy5qb2luKCIiKX0pfXZhciBXUSxHUSxZUT1NKCgpPT57ZUMoKTtXUT0vWy0rXT8oPzpcZCtcLj9cZCp8XC4/XGQrKSg/OltlRV1bLStdP1xkKyk/L2csR1E9bmV3IFJlZ0V4cChXUS5zb3VyY2UsImciKX0pO2Z1bmN0aW9uIFoyKGUsdCl7dmFyIHI9dHlwZW9mIHQsbjtyZXR1cm4gdD09bnVsbHx8cj09PSJib29sZWFuIj9YMih0KToocj09PSJudW1iZXIiP0FzOnI9PT0ic3RyaW5nIj8obj1FZyh0KSk/KHQ9bix0Qyk6bU86dCBpbnN0YW5jZW9mIEVnP3RDOnQgaW5zdGFuY2VvZiBEYXRlP3BPOkFycmF5LmlzQXJyYXkodCk/aE86dHlwZW9mIHQudmFsdWVPZiE9ImZ1bmN0aW9uIiYmdHlwZW9mIHQudG9TdHJpbmchPSJmdW5jdGlvbiJ8fGlzTmFOKHQpP2RPOkFzKShlLHQpfXZhciBmTz1NKCgpPT57ankoKTtIUSgpO1ZRKCk7VVEoKTtlQygpO3FRKCk7WVEoKTtCUSgpfSk7ZnVuY3Rpb24gZjh0KGUsdCl7cmV0dXJuIGU9K2UsdC09ZSxmdW5jdGlvbihyKXtyZXR1cm4gTWF0aC5yb3VuZChlK3Qqcil9fXZhciBwOHQ9TSgoKT0+e30pO2Z1bmN0aW9uIGpRKGUsdCxyLG4saSxvKXt2YXIgYSxzLGw7cmV0dXJuKGE9TWF0aC5zcXJ0KGUqZSt0KnQpKSYmKGUvPWEsdC89YSksKGw9ZSpyK3QqbikmJihyLT1lKmwsbi09dCpsKSwocz1NYXRoLnNxcnQocipyK24qbikpJiYoci89cyxuLz1zLGwvPXMpLGUqbjx0KnImJihlPS1lLHQ9LXQsbD0tbCxhPS1hKSx7dHJhbnNsYXRlWDppLHRyYW5zbGF0ZVk6byxyb3RhdGU6TWF0aC5hdGFuMih0LGUpKmQ4dCxza2V3WDpNYXRoLmF0YW4obCkqZDh0LHNjYWxlWDphLHNjYWxlWTpzfX12YXIgZDh0LGdPLG04dD1NKCgpPT57ZDh0PTE4MC9NYXRoLlBJLGdPPXt0cmFuc2xhdGVYOjAsdHJhbnNsYXRlWTowLHJvdGF0ZTowLHNrZXdYOjAsc2NhbGVYOjEsc2NhbGVZOjF9fSk7ZnVuY3Rpb24gXzh0KGUpe3JldHVybiBlPT09Im5vbmUiP2dPOihyQ3x8KHJDPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkRJViIpLFhRPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudCxnOHQ9ZG9jdW1lbnQuZGVmYXVsdFZpZXcpLHJDLnN0eWxlLnRyYW5zZm9ybT1lLGU9Zzh0LmdldENvbXB1dGVkU3R5bGUoWFEuYXBwZW5kQ2hpbGQockMpLG51bGwpLmdldFByb3BlcnR5VmFsdWUoInRyYW5zZm9ybSIpLFhRLnJlbW92ZUNoaWxkKHJDKSxlPWUuc2xpY2UoNywtMSkuc3BsaXQoIiwiKSxqUSgrZVswXSwrZVsxXSwrZVsyXSwrZVszXSwrZVs0XSwrZVs1XSkpfWZ1bmN0aW9uIHk4dChlKXtyZXR1cm4gZT09bnVsbD9nTzooX098fChfTz1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwiZyIpKSxfTy5zZXRBdHRyaWJ1dGUoInRyYW5zZm9ybSIsZSksKGU9X08udHJhbnNmb3JtLmJhc2VWYWwuY29uc29saWRhdGUoKSk/KGU9ZS5tYXRyaXgsalEoZS5hLGUuYixlLmMsZS5kLGUuZSxlLmYpKTpnTyl9dmFyIHJDLFhRLGc4dCxfTyx2OHQ9TSgoKT0+e204dCgpfSk7ZnVuY3Rpb24geDh0KGUsdCxyLG4pe2Z1bmN0aW9uIGkoYyl7cmV0dXJuIGMubGVuZ3RoP2MucG9wKCkrIiAiOiIifWZ1bmN0aW9uIG8oYyx1LGgsZixwLGQpe2lmKGMhPT1ofHx1IT09Zil7dmFyIGc9cC5wdXNoKCJ0cmFuc2xhdGUoIixudWxsLHQsbnVsbCxyKTtkLnB1c2goe2k6Zy00LHg6QXMoYyxoKX0se2k6Zy0yLHg6QXModSxmKX0pfWVsc2UoaHx8ZikmJnAucHVzaCgidHJhbnNsYXRlKCIraCt0K2Yrcil9ZnVuY3Rpb24gYShjLHUsaCxmKXtjIT09dT8oYy11PjE4MD91Kz0zNjA6dS1jPjE4MCYmKGMrPTM2MCksZi5wdXNoKHtpOmgucHVzaChpKGgpKyJyb3RhdGUoIixudWxsLG4pLTIseDpBcyhjLHUpfSkpOnUmJmgucHVzaChpKGgpKyJyb3RhdGUoIit1K24pfWZ1bmN0aW9uIHMoYyx1LGgsZil7YyE9PXU/Zi5wdXNoKHtpOmgucHVzaChpKGgpKyJza2V3WCgiLG51bGwsbiktMix4OkFzKGMsdSl9KTp1JiZoLnB1c2goaShoKSsic2tld1goIit1K24pfWZ1bmN0aW9uIGwoYyx1LGgsZixwLGQpe2lmKGMhPT1ofHx1IT09Zil7dmFyIGc9cC5wdXNoKGkocCkrInNjYWxlKCIsbnVsbCwiLCIsbnVsbCwiKSIpO2QucHVzaCh7aTpnLTQseDpBcyhjLGgpfSx7aTpnLTIseDpBcyh1LGYpfSl9ZWxzZShoIT09MXx8ZiE9PTEpJiZwLnB1c2goaShwKSsic2NhbGUoIitoKyIsIitmKyIpIil9cmV0dXJuIGZ1bmN0aW9uKGMsdSl7dmFyIGg9W10sZj1bXTtyZXR1cm4gYz1lKGMpLHU9ZSh1KSxvKGMudHJhbnNsYXRlWCxjLnRyYW5zbGF0ZVksdS50cmFuc2xhdGVYLHUudHJhbnNsYXRlWSxoLGYpLGEoYy5yb3RhdGUsdS5yb3RhdGUsaCxmKSxzKGMuc2tld1gsdS5za2V3WCxoLGYpLGwoYy5zY2FsZVgsYy5zY2FsZVksdS5zY2FsZVgsdS5zY2FsZVksaCxmKSxjPXU9bnVsbCxmdW5jdGlvbihwKXtmb3IodmFyIGQ9LTEsZz1mLmxlbmd0aCxfOysrZDxnOyloWyhfPWZbZF0pLmldPV8ueChwKTtyZXR1cm4gaC5qb2luKCIiKX19fXZhciBiOHQsdzh0LFM4dD1NKCgpPT57ZUMoKTt2OHQoKTtiOHQ9eDh0KF84dCwicHgsICIsInB4KSIsImRlZykiKSx3OHQ9eDh0KHk4dCwiLCAiLCIpIiwiKSIpfSk7ZnVuY3Rpb24gRTh0KGUpe3JldHVybigoZT1NYXRoLmV4cChlKSkrMS9lKS8yfWZ1bmN0aW9uIGg2ZShlKXtyZXR1cm4oKGU9TWF0aC5leHAoZSkpLTEvZSkvMn1mdW5jdGlvbiBmNmUoZSl7cmV0dXJuKChlPU1hdGguZXhwKDIqZSkpLTEpLyhlKzEpfWZ1bmN0aW9uIFQ4dChlLHQpe3ZhciByPWVbMF0sbj1lWzFdLGk9ZVsyXSxvPXRbMF0sYT10WzFdLHM9dFsyXSxsPW8tcixjPWEtbix1PWwqbCtjKmMsaCxmO2lmKHU8dTZlKWY9TWF0aC5sb2cocy9pKS9uQyxoPWZ1bmN0aW9uKHgpe3JldHVybltyK3gqbCxuK3gqYyxpKk1hdGguZXhwKG5DKngqZildfTtlbHNle3ZhciBwPU1hdGguc3FydCh1KSxkPShzKnMtaSppK004dCp1KS8oMippKiRRKnApLGc9KHMqcy1pKmktTTh0KnUpLygyKnMqJFEqcCksXz1NYXRoLmxvZyhNYXRoLnNxcnQoZCpkKzEpLWQpLHk9TWF0aC5sb2coTWF0aC5zcXJ0KGcqZysxKS1nKTtmPSh5LV8pL25DLGg9ZnVuY3Rpb24oeCl7dmFyIGI9eCpmLFM9RTh0KF8pLEM9aS8oJFEqcCkqKFMqZjZlKG5DKmIrXyktaDZlKF8pKTtyZXR1cm5bcitDKmwsbitDKmMsaSpTL0U4dChuQypiK18pXX19cmV0dXJuIGguZHVyYXRpb249ZioxZTMsaH12YXIgbkMsJFEsTTh0LHU2ZSxDOHQ9TSgoKT0+e25DPU1hdGguU1FSVDIsJFE9MixNOHQ9NCx1NmU9MWUtMTJ9KTtmdW5jdGlvbiBBOHQoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7dmFyIG49ZSgodD1aVCh0KSkuaCwocj1aVChyKSkuaCksaT1Rbih0LnMsci5zKSxvPVFuKHQubCxyLmwpLGE9UW4odC5vcGFjaXR5LHIub3BhY2l0eSk7cmV0dXJuIGZ1bmN0aW9uKHMpe3JldHVybiB0Lmg9bihzKSx0LnM9aShzKSx0Lmw9byhzKSx0Lm9wYWNpdHk9YShzKSx0KyIifX19dmFyIFA4dCxJOHQsTDh0PU0oKCk9PntqeSgpO0syKCk7UDh0PUE4dCgkMiksSTh0PUE4dChRbil9KTtmdW5jdGlvbiBLUShlLHQpe3ZhciByPVFuKChlPVkyKGUpKS5sLCh0PVkyKHQpKS5sKSxuPVFuKGUuYSx0LmEpLGk9UW4oZS5iLHQuYiksbz1RbihlLm9wYWNpdHksdC5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24oYSl7cmV0dXJuIGUubD1yKGEpLGUuYT1uKGEpLGUuYj1pKGEpLGUub3BhY2l0eT1vKGEpLGUrIiJ9fXZhciBrOHQ9TSgoKT0+e2p5KCk7SzIoKX0pO2Z1bmN0aW9uIFI4dChlKXtyZXR1cm4gZnVuY3Rpb24odCxyKXt2YXIgbj1lKCh0PUpUKHQpKS5oLChyPUpUKHIpKS5oKSxpPVFuKHQuYyxyLmMpLG89UW4odC5sLHIubCksYT1Rbih0Lm9wYWNpdHksci5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24ocyl7cmV0dXJuIHQuaD1uKHMpLHQuYz1pKHMpLHQubD1vKHMpLHQub3BhY2l0eT1hKHMpLHQrIiJ9fX12YXIgTjh0LEQ4dCxPOHQ9TSgoKT0+e2p5KCk7SzIoKTtOOHQ9Ujh0KCQyKSxEOHQ9Ujh0KFFuKX0pO2Z1bmN0aW9uIHo4dChlKXtyZXR1cm4gZnVuY3Rpb24gdChyKXtyPStyO2Z1bmN0aW9uIG4oaSxvKXt2YXIgYT1lKChpPWoyKGkpKS5oLChvPWoyKG8pKS5oKSxzPVFuKGkucyxvLnMpLGw9UW4oaS5sLG8ubCksYz1RbihpLm9wYWNpdHksby5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24odSl7cmV0dXJuIGkuaD1hKHUpLGkucz1zKHUpLGkubD1sKE1hdGgucG93KHUscikpLGkub3BhY2l0eT1jKHUpLGkrIiJ9fXJldHVybiBuLmdhbW1hPXQsbn0oMSl9dmFyIEY4dCxCOHQsSDh0PU0oKCk9PntqeSgpO0syKCk7Rjh0PXo4dCgkMiksQjh0PXo4dChRbil9KTtmdW5jdGlvbiBWOHQoZSx0KXtmb3IodmFyIHI9bmV3IEFycmF5KHQpLG49MDtuPHQ7KytuKXJbbl09ZShuLyh0LTEpKTtyZXR1cm4gcn12YXIgVTh0PU0oKCk9Pnt9KTt2YXIgcTh0PU0oKCk9PntmTygpO1ZRKCk7Y08oKTtGUSgpO1VRKCk7ZUMoKTtxUSgpO3A4dCgpO1lRKCk7Uzh0KCk7Qzh0KCk7SFEoKTtMOHQoKTtrOHQoKTtPOHQoKTtIOHQoKTtVOHQoKX0pO2Z1bmN0aW9uIFFRKCl7dGhpcy5feDA9dGhpcy5feTA9dGhpcy5feDE9dGhpcy5feTE9bnVsbCx0aGlzLl89IiJ9ZnVuY3Rpb24gRzh0KCl7cmV0dXJuIG5ldyBRUX12YXIgWlEsSlEsWHkscDZlLFc4dCxZOHQ9TSgoKT0+e1pRPU1hdGguUEksSlE9MipaUSxYeT0xZS02LHA2ZT1KUS1YeTtRUS5wcm90b3R5cGU9Rzh0LnByb3RvdHlwZT17Y29uc3RydWN0b3I6UVEsbW92ZVRvOmZ1bmN0aW9uKGUsdCl7dGhpcy5fKz0iTSIrKHRoaXMuX3gwPXRoaXMuX3gxPStlKSsiLCIrKHRoaXMuX3kwPXRoaXMuX3kxPSt0KX0sY2xvc2VQYXRoOmZ1bmN0aW9uKCl7dGhpcy5feDEhPT1udWxsJiYodGhpcy5feDE9dGhpcy5feDAsdGhpcy5feTE9dGhpcy5feTAsdGhpcy5fKz0iWiIpfSxsaW5lVG86ZnVuY3Rpb24oZSx0KXt0aGlzLl8rPSJMIisodGhpcy5feDE9K2UpKyIsIisodGhpcy5feTE9K3QpfSxxdWFkcmF0aWNDdXJ2ZVRvOmZ1bmN0aW9uKGUsdCxyLG4pe3RoaXMuXys9IlEiKyArZSsiLCIrICt0KyIsIisodGhpcy5feDE9K3IpKyIsIisodGhpcy5feTE9K24pfSxiZXppZXJDdXJ2ZVRvOmZ1bmN0aW9uKGUsdCxyLG4saSxvKXt0aGlzLl8rPSJDIisgK2UrIiwiKyArdCsiLCIrICtyKyIsIisgK24rIiwiKyh0aGlzLl94MT0raSkrIiwiKyh0aGlzLl95MT0rbyl9LGFyY1RvOmZ1bmN0aW9uKGUsdCxyLG4saSl7ZT0rZSx0PSt0LHI9K3Isbj0rbixpPStpO3ZhciBvPXRoaXMuX3gxLGE9dGhpcy5feTEscz1yLWUsbD1uLXQsYz1vLWUsdT1hLXQsaD1jKmMrdSp1O2lmKGk8MCl0aHJvdyBuZXcgRXJyb3IoIm5lZ2F0aXZlIHJhZGl1czogIitpKTtpZih0aGlzLl94MT09PW51bGwpdGhpcy5fKz0iTSIrKHRoaXMuX3gxPWUpKyIsIisodGhpcy5feTE9dCk7ZWxzZSBpZihoPlh5KWlmKCEoTWF0aC5hYnModSpzLWwqYyk+WHkpfHwhaSl0aGlzLl8rPSJMIisodGhpcy5feDE9ZSkrIiwiKyh0aGlzLl95MT10KTtlbHNle3ZhciBmPXItbyxwPW4tYSxkPXMqcytsKmwsZz1mKmYrcCpwLF89TWF0aC5zcXJ0KGQpLHk9TWF0aC5zcXJ0KGgpLHg9aSpNYXRoLnRhbigoWlEtTWF0aC5hY29zKChkK2gtZykvKDIqXyp5KSkpLzIpLGI9eC95LFM9eC9fO01hdGguYWJzKGItMSk+WHkmJih0aGlzLl8rPSJMIisoZStiKmMpKyIsIisodCtiKnUpKSx0aGlzLl8rPSJBIitpKyIsIitpKyIsMCwwLCIrICsodSpmPmMqcCkrIiwiKyh0aGlzLl94MT1lK1MqcykrIiwiKyh0aGlzLl95MT10K1MqbCl9fSxhcmM6ZnVuY3Rpb24oZSx0LHIsbixpLG8pe2U9K2UsdD0rdCxyPStyO3ZhciBhPXIqTWF0aC5jb3Mobikscz1yKk1hdGguc2luKG4pLGw9ZSthLGM9dCtzLHU9MV5vLGg9bz9uLWk6aS1uO2lmKHI8MCl0aHJvdyBuZXcgRXJyb3IoIm5lZ2F0aXZlIHJhZGl1czogIityKTt0aGlzLl94MT09PW51bGw/dGhpcy5fKz0iTSIrbCsiLCIrYzooTWF0aC5hYnModGhpcy5feDEtbCk+WHl8fE1hdGguYWJzKHRoaXMuX3kxLWMpPlh5KSYmKHRoaXMuXys9IkwiK2wrIiwiK2MpLHImJihoPDAmJihoPWglSlErSlEpLGg+cDZlP3RoaXMuXys9IkEiK3IrIiwiK3IrIiwwLDEsIit1KyIsIisoZS1hKSsiLCIrKHQtcykrIkEiK3IrIiwiK3IrIiwwLDEsIit1KyIsIisodGhpcy5feDE9bCkrIiwiKyh0aGlzLl95MT1jKTpoPlh5JiYodGhpcy5fKz0iQSIrcisiLCIrcisiLDAsIisgKyhoPj1aUSkrIiwiK3UrIiwiKyh0aGlzLl94MT1lK3IqTWF0aC5jb3MoaSkpKyIsIisodGhpcy5feTE9dCtyKk1hdGguc2luKGkpKSkpfSxyZWN0OmZ1bmN0aW9uKGUsdCxyLG4pe3RoaXMuXys9Ik0iKyh0aGlzLl94MD10aGlzLl94MT0rZSkrIiwiKyh0aGlzLl95MD10aGlzLl95MT0rdCkrImgiKyArcisidiIrICtuKyJoIistcisiWiJ9LHRvU3RyaW5nOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX319O1c4dD1HOHR9KTt2YXIgajh0PU0oKCk9PntZOHQoKX0pO2Z1bmN0aW9uIFg4dChlKXtmb3IodmFyIHQ9LTEscj1lLmxlbmd0aCxuLGk9ZVtyLTFdLG89MDsrK3Q8cjspbj1pLGk9ZVt0XSxvKz1uWzFdKmlbMF0tblswXSppWzFdO3JldHVybiBvLzJ9dmFyICQ4dD1NKCgpPT57fSk7ZnVuY3Rpb24gSzh0KGUpe2Zvcih2YXIgdD0tMSxyPWUubGVuZ3RoLG49MCxpPTAsbyxhPWVbci0xXSxzLGw9MDsrK3Q8cjspbz1hLGE9ZVt0XSxsKz1zPW9bMF0qYVsxXS1hWzBdKm9bMV0sbis9KG9bMF0rYVswXSkqcyxpKz0ob1sxXSthWzFdKSpzO3JldHVybiBsKj0zLFtuL2wsaS9sXX12YXIgWjh0PU0oKCk9Pnt9KTtmdW5jdGlvbiBKOHQoZSx0LHIpe3JldHVybih0WzBdLWVbMF0pKihyWzFdLWVbMV0pLSh0WzFdLWVbMV0pKihyWzBdLWVbMF0pfXZhciBROHQ9TSgoKT0+e30pO2Z1bmN0aW9uIGQ2ZShlLHQpe3JldHVybiBlWzBdLXRbMF18fGVbMV0tdFsxXX1mdW5jdGlvbiB0UnQoZSl7Zm9yKHZhciB0PWUubGVuZ3RoLHI9WzAsMV0sbj0yLGk9MjtpPHQ7KytpKXtmb3IoO24+MSYmSjh0KGVbcltuLTJdXSxlW3Jbbi0xXV0sZVtpXSk8PTA7KS0tbjtyW24rK109aX1yZXR1cm4gci5zbGljZSgwLG4pfWZ1bmN0aW9uIGVSdChlKXtpZigocj1lLmxlbmd0aCk8MylyZXR1cm4gbnVsbDt2YXIgdCxyLG49bmV3IEFycmF5KHIpLGk9bmV3IEFycmF5KHIpO2Zvcih0PTA7dDxyOysrdCluW3RdPVsrZVt0XVswXSwrZVt0XVsxXSx0XTtmb3Iobi5zb3J0KGQ2ZSksdD0wO3Q8cjsrK3QpaVt0XT1bblt0XVswXSwtblt0XVsxXV07dmFyIG89dFJ0KG4pLGE9dFJ0KGkpLHM9YVswXT09PW9bMF0sbD1hW2EubGVuZ3RoLTFdPT09b1tvLmxlbmd0aC0xXSxjPVtdO2Zvcih0PW8ubGVuZ3RoLTE7dD49MDstLXQpYy5wdXNoKGVbbltvW3RdXVsyXV0pO2Zvcih0PStzO3Q8YS5sZW5ndGgtbDsrK3QpYy5wdXNoKGVbblthW3RdXVsyXV0pO3JldHVybiBjfXZhciByUnQ9TSgoKT0+e1E4dCgpfSk7ZnVuY3Rpb24gblJ0KGUsdCl7Zm9yKHZhciByPWUubGVuZ3RoLG49ZVtyLTFdLGk9dFswXSxvPXRbMV0sYT1uWzBdLHM9blsxXSxsLGMsdT0hMSxoPTA7aDxyOysraCluPWVbaF0sbD1uWzBdLGM9blsxXSxjPm8hPXM+byYmaTwoYS1sKSooby1jKS8ocy1jKStsJiYodT0hdSksYT1sLHM9YztyZXR1cm4gdX12YXIgaVJ0PU0oKCk9Pnt9KTtmdW5jdGlvbiBvUnQoZSl7Zm9yKHZhciB0PS0xLHI9ZS5sZW5ndGgsbj1lW3ItMV0saSxvLGE9blswXSxzPW5bMV0sbD0wOysrdDxyOylpPWEsbz1zLG49ZVt0XSxhPW5bMF0scz1uWzFdLGktPWEsby09cyxsKz1NYXRoLnNxcnQoaSppK28qbyk7cmV0dXJuIGx9dmFyIGFSdD1NKCgpPT57fSk7dmFyIHNSdD1NKCgpPT57JDh0KCk7Wjh0KCk7clJ0KCk7aVJ0KCk7YVJ0KCl9KTtmdW5jdGlvbiBsUnQoZSl7dmFyIHQ9K3RoaXMuX3guY2FsbChudWxsLGUpLHI9K3RoaXMuX3kuY2FsbChudWxsLGUpO3JldHVybiBjUnQodGhpcy5jb3Zlcih0LHIpLHQscixlKX1mdW5jdGlvbiBjUnQoZSx0LHIsbil7aWYoaXNOYU4odCl8fGlzTmFOKHIpKXJldHVybiBlO3ZhciBpLG89ZS5fcm9vdCxhPXtkYXRhOm59LHM9ZS5feDAsbD1lLl95MCxjPWUuX3gxLHU9ZS5feTEsaCxmLHAsZCxnLF8seSx4O2lmKCFvKXJldHVybiBlLl9yb290PWEsZTtmb3IoO28ubGVuZ3RoOylpZigoZz10Pj0oaD0ocytjKS8yKSk/cz1oOmM9aCwoXz1yPj0oZj0obCt1KS8yKSk/bD1mOnU9ZixpPW8sIShvPW9beT1fPDwxfGddKSlyZXR1cm4gaVt5XT1hLGU7aWYocD0rZS5feC5jYWxsKG51bGwsby5kYXRhKSxkPStlLl95LmNhbGwobnVsbCxvLmRhdGEpLHQ9PT1wJiZyPT09ZClyZXR1cm4gYS5uZXh0PW8saT9pW3ldPWE6ZS5fcm9vdD1hLGU7ZG8gaT1pP2lbeV09bmV3IEFycmF5KDQpOmUuX3Jvb3Q9bmV3IEFycmF5KDQpLChnPXQ+PShoPShzK2MpLzIpKT9zPWg6Yz1oLChfPXI+PShmPShsK3UpLzIpKT9sPWY6dT1mO3doaWxlKCh5PV88PDF8Zyk9PT0oeD0oZD49Zik8PDF8cD49aCkpO3JldHVybiBpW3hdPW8saVt5XT1hLGV9ZnVuY3Rpb24gdVJ0KGUpe3ZhciB0LHIsbj1lLmxlbmd0aCxpLG8sYT1uZXcgQXJyYXkobikscz1uZXcgQXJyYXkobiksbD0xLzAsYz0xLzAsdT0tMS8wLGg9LTEvMDtmb3Iocj0wO3I8bjsrK3IpaXNOYU4oaT0rdGhpcy5feC5jYWxsKG51bGwsdD1lW3JdKSl8fGlzTmFOKG89K3RoaXMuX3kuY2FsbChudWxsLHQpKXx8KGFbcl09aSxzW3JdPW8saTxsJiYobD1pKSxpPnUmJih1PWkpLG88YyYmKGM9byksbz5oJiYoaD1vKSk7Zm9yKHU8bCYmKGw9dGhpcy5feDAsdT10aGlzLl94MSksaDxjJiYoYz10aGlzLl95MCxoPXRoaXMuX3kxKSx0aGlzLmNvdmVyKGwsYykuY292ZXIodSxoKSxyPTA7cjxuOysrciljUnQodGhpcyxhW3JdLHNbcl0sZVtyXSk7cmV0dXJuIHRoaXN9dmFyIGhSdD1NKCgpPT57fSk7ZnVuY3Rpb24gZlJ0KGUsdCl7aWYoaXNOYU4oZT0rZSl8fGlzTmFOKHQ9K3QpKXJldHVybiB0aGlzO3ZhciByPXRoaXMuX3gwLG49dGhpcy5feTAsaT10aGlzLl94MSxvPXRoaXMuX3kxO2lmKGlzTmFOKHIpKWk9KHI9TWF0aC5mbG9vcihlKSkrMSxvPShuPU1hdGguZmxvb3IodCkpKzE7ZWxzZSBpZihyPmV8fGU+aXx8bj50fHx0Pm8pe3ZhciBhPWktcixzPXRoaXMuX3Jvb3QsbCxjO3N3aXRjaChjPSh0PChuK28pLzIpPDwxfGU8KHIraSkvMil7Y2FzZSAwOntkbyBsPW5ldyBBcnJheSg0KSxsW2NdPXMscz1sO3doaWxlKGEqPTIsaT1yK2Esbz1uK2EsZT5pfHx0Pm8pO2JyZWFrfWNhc2UgMTp7ZG8gbD1uZXcgQXJyYXkoNCksbFtjXT1zLHM9bDt3aGlsZShhKj0yLHI9aS1hLG89bithLHI+ZXx8dD5vKTticmVha31jYXNlIDI6e2RvIGw9bmV3IEFycmF5KDQpLGxbY109cyxzPWw7d2hpbGUoYSo9MixpPXIrYSxuPW8tYSxlPml8fG4+dCk7YnJlYWt9Y2FzZSAzOntkbyBsPW5ldyBBcnJheSg0KSxsW2NdPXMscz1sO3doaWxlKGEqPTIscj1pLWEsbj1vLWEscj5lfHxuPnQpO2JyZWFrfX10aGlzLl9yb290JiZ0aGlzLl9yb290Lmxlbmd0aCYmKHRoaXMuX3Jvb3Q9cyl9ZWxzZSByZXR1cm4gdGhpcztyZXR1cm4gdGhpcy5feDA9cix0aGlzLl95MD1uLHRoaXMuX3gxPWksdGhpcy5feTE9byx0aGlzfXZhciBwUnQ9TSgoKT0+e30pO2Z1bmN0aW9uIGRSdCgpe3ZhciBlPVtdO3JldHVybiB0aGlzLnZpc2l0KGZ1bmN0aW9uKHQpe2lmKCF0Lmxlbmd0aClkbyBlLnB1c2godC5kYXRhKTt3aGlsZSh0PXQubmV4dCl9KSxlfXZhciBtUnQ9TSgoKT0+e30pO2Z1bmN0aW9uIGdSdChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmNvdmVyKCtlWzBdWzBdLCtlWzBdWzFdKS5jb3ZlcigrZVsxXVswXSwrZVsxXVsxXSk6aXNOYU4odGhpcy5feDApP3ZvaWQgMDpbW3RoaXMuX3gwLHRoaXMuX3kwXSxbdGhpcy5feDEsdGhpcy5feTFdXX12YXIgX1J0PU0oKCk9Pnt9KTtmdW5jdGlvbiBFbyhlLHQscixuLGkpe3RoaXMubm9kZT1lLHRoaXMueDA9dCx0aGlzLnkwPXIsdGhpcy54MT1uLHRoaXMueTE9aX12YXIgeU89TSgoKT0+e30pO2Z1bmN0aW9uIHlSdChlLHQscil7dmFyIG4saT10aGlzLl94MCxvPXRoaXMuX3kwLGEscyxsLGMsdT10aGlzLl94MSxoPXRoaXMuX3kxLGY9W10scD10aGlzLl9yb290LGQsZztmb3IocCYmZi5wdXNoKG5ldyBFbyhwLGksbyx1LGgpKSxyPT1udWxsP3I9MS8wOihpPWUtcixvPXQtcix1PWUrcixoPXQrcixyKj1yKTtkPWYucG9wKCk7KWlmKCEoIShwPWQubm9kZSl8fChhPWQueDApPnV8fChzPWQueTApPmh8fChsPWQueDEpPGl8fChjPWQueTEpPG8pKWlmKHAubGVuZ3RoKXt2YXIgXz0oYStsKS8yLHk9KHMrYykvMjtmLnB1c2gobmV3IEVvKHBbM10sXyx5LGwsYyksbmV3IEVvKHBbMl0sYSx5LF8sYyksbmV3IEVvKHBbMV0sXyxzLGwseSksbmV3IEVvKHBbMF0sYSxzLF8seSkpLChnPSh0Pj15KTw8MXxlPj1fKSYmKGQ9ZltmLmxlbmd0aC0xXSxmW2YubGVuZ3RoLTFdPWZbZi5sZW5ndGgtMS1nXSxmW2YubGVuZ3RoLTEtZ109ZCl9ZWxzZXt2YXIgeD1lLSt0aGlzLl94LmNhbGwobnVsbCxwLmRhdGEpLGI9dC0rdGhpcy5feS5jYWxsKG51bGwscC5kYXRhKSxTPXgqeCtiKmI7aWYoUzxyKXt2YXIgQz1NYXRoLnNxcnQocj1TKTtpPWUtQyxvPXQtQyx1PWUrQyxoPXQrQyxuPXAuZGF0YX19cmV0dXJuIG59dmFyIHZSdD1NKCgpPT57eU8oKX0pO2Z1bmN0aW9uIHhSdChlKXtpZihpc05hTih1PSt0aGlzLl94LmNhbGwobnVsbCxlKSl8fGlzTmFOKGg9K3RoaXMuX3kuY2FsbChudWxsLGUpKSlyZXR1cm4gdGhpczt2YXIgdCxyPXRoaXMuX3Jvb3QsbixpLG8sYT10aGlzLl94MCxzPXRoaXMuX3kwLGw9dGhpcy5feDEsYz10aGlzLl95MSx1LGgsZixwLGQsZyxfLHk7aWYoIXIpcmV0dXJuIHRoaXM7aWYoci5sZW5ndGgpZm9yKDs7KXtpZigoZD11Pj0oZj0oYStsKS8yKSk/YT1mOmw9ZiwoZz1oPj0ocD0ocytjKS8yKSk/cz1wOmM9cCx0PXIsIShyPXJbXz1nPDwxfGRdKSlyZXR1cm4gdGhpcztpZighci5sZW5ndGgpYnJlYWs7KHRbXysxJjNdfHx0W18rMiYzXXx8dFtfKzMmM10pJiYobj10LHk9Xyl9Zm9yKDtyLmRhdGEhPT1lOylpZihpPXIsIShyPXIubmV4dCkpcmV0dXJuIHRoaXM7cmV0dXJuKG89ci5uZXh0KSYmZGVsZXRlIHIubmV4dCxpPyhvP2kubmV4dD1vOmRlbGV0ZSBpLm5leHQsdGhpcyk6dD8obz90W19dPW86ZGVsZXRlIHRbX10sKHI9dFswXXx8dFsxXXx8dFsyXXx8dFszXSkmJnI9PT0odFszXXx8dFsyXXx8dFsxXXx8dFswXSkmJiFyLmxlbmd0aCYmKG4/blt5XT1yOnRoaXMuX3Jvb3Q9ciksdGhpcyk6KHRoaXMuX3Jvb3Q9byx0aGlzKX1mdW5jdGlvbiBiUnQoZSl7Zm9yKHZhciB0PTAscj1lLmxlbmd0aDt0PHI7Kyt0KXRoaXMucmVtb3ZlKGVbdF0pO3JldHVybiB0aGlzfXZhciB3UnQ9TSgoKT0+e30pO2Z1bmN0aW9uIFNSdCgpe3JldHVybiB0aGlzLl9yb290fXZhciBNUnQ9TSgoKT0+e30pO2Z1bmN0aW9uIEVSdCgpe3ZhciBlPTA7cmV0dXJuIHRoaXMudmlzaXQoZnVuY3Rpb24odCl7aWYoIXQubGVuZ3RoKWRvKytlO3doaWxlKHQ9dC5uZXh0KX0pLGV9dmFyIFRSdD1NKCgpPT57fSk7ZnVuY3Rpb24gQ1J0KGUpe3ZhciB0PVtdLHIsbj10aGlzLl9yb290LGksbyxhLHMsbDtmb3IobiYmdC5wdXNoKG5ldyBFbyhuLHRoaXMuX3gwLHRoaXMuX3kwLHRoaXMuX3gxLHRoaXMuX3kxKSk7cj10LnBvcCgpOylpZighZShuPXIubm9kZSxvPXIueDAsYT1yLnkwLHM9ci54MSxsPXIueTEpJiZuLmxlbmd0aCl7dmFyIGM9KG8rcykvMix1PShhK2wpLzI7KGk9blszXSkmJnQucHVzaChuZXcgRW8oaSxjLHUscyxsKSksKGk9blsyXSkmJnQucHVzaChuZXcgRW8oaSxvLHUsYyxsKSksKGk9blsxXSkmJnQucHVzaChuZXcgRW8oaSxjLGEscyx1KSksKGk9blswXSkmJnQucHVzaChuZXcgRW8oaSxvLGEsYyx1KSl9cmV0dXJuIHRoaXN9dmFyIEFSdD1NKCgpPT57eU8oKX0pO2Z1bmN0aW9uIFBSdChlKXt2YXIgdD1bXSxyPVtdLG47Zm9yKHRoaXMuX3Jvb3QmJnQucHVzaChuZXcgRW8odGhpcy5fcm9vdCx0aGlzLl94MCx0aGlzLl95MCx0aGlzLl94MSx0aGlzLl95MSkpO249dC5wb3AoKTspe3ZhciBpPW4ubm9kZTtpZihpLmxlbmd0aCl7dmFyIG8sYT1uLngwLHM9bi55MCxsPW4ueDEsYz1uLnkxLHU9KGErbCkvMixoPShzK2MpLzI7KG89aVswXSkmJnQucHVzaChuZXcgRW8obyxhLHMsdSxoKSksKG89aVsxXSkmJnQucHVzaChuZXcgRW8obyx1LHMsbCxoKSksKG89aVsyXSkmJnQucHVzaChuZXcgRW8obyxhLGgsdSxjKSksKG89aVszXSkmJnQucHVzaChuZXcgRW8obyx1LGgsbCxjKSl9ci5wdXNoKG4pfWZvcig7bj1yLnBvcCgpOyllKG4ubm9kZSxuLngwLG4ueTAsbi54MSxuLnkxKTtyZXR1cm4gdGhpc312YXIgSVJ0PU0oKCk9Pnt5TygpfSk7ZnVuY3Rpb24gTFJ0KGUpe3JldHVybiBlWzBdfWZ1bmN0aW9uIGtSdChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odGhpcy5feD1lLHRoaXMpOnRoaXMuX3h9dmFyIFJSdD1NKCgpPT57fSk7ZnVuY3Rpb24gTlJ0KGUpe3JldHVybiBlWzFdfWZ1bmN0aW9uIERSdChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odGhpcy5feT1lLHRoaXMpOnRoaXMuX3l9dmFyIE9SdD1NKCgpPT57fSk7ZnVuY3Rpb24gdk8oZSx0LHIpe3ZhciBuPW5ldyB0dHQodD09bnVsbD9MUnQ6dCxyPT1udWxsP05SdDpyLE5hTixOYU4sTmFOLE5hTik7cmV0dXJuIGU9PW51bGw/bjpuLmFkZEFsbChlKX1mdW5jdGlvbiB0dHQoZSx0LHIsbixpLG8pe3RoaXMuX3g9ZSx0aGlzLl95PXQsdGhpcy5feDA9cix0aGlzLl95MD1uLHRoaXMuX3gxPWksdGhpcy5feTE9byx0aGlzLl9yb290PXZvaWQgMH1mdW5jdGlvbiB6UnQoZSl7Zm9yKHZhciB0PXtkYXRhOmUuZGF0YX0scj10O2U9ZS5uZXh0OylyPXIubmV4dD17ZGF0YTplLmRhdGF9O3JldHVybiB0fXZhciBYYSxGUnQ9TSgoKT0+e2hSdCgpO3BSdCgpO21SdCgpO19SdCgpO3ZSdCgpO3dSdCgpO01SdCgpO1RSdCgpO0FSdCgpO0lSdCgpO1JSdCgpO09SdCgpO1hhPXZPLnByb3RvdHlwZT10dHQucHJvdG90eXBlO1hhLmNvcHk9ZnVuY3Rpb24oKXt2YXIgZT1uZXcgdHR0KHRoaXMuX3gsdGhpcy5feSx0aGlzLl94MCx0aGlzLl95MCx0aGlzLl94MSx0aGlzLl95MSksdD10aGlzLl9yb290LHIsbjtpZighdClyZXR1cm4gZTtpZighdC5sZW5ndGgpcmV0dXJuIGUuX3Jvb3Q9elJ0KHQpLGU7Zm9yKHI9W3tzb3VyY2U6dCx0YXJnZXQ6ZS5fcm9vdD1uZXcgQXJyYXkoNCl9XTt0PXIucG9wKCk7KWZvcih2YXIgaT0wO2k8NDsrK2kpKG49dC5zb3VyY2VbaV0pJiYobi5sZW5ndGg/ci5wdXNoKHtzb3VyY2U6bix0YXJnZXQ6dC50YXJnZXRbaV09bmV3IEFycmF5KDQpfSk6dC50YXJnZXRbaV09elJ0KG4pKTtyZXR1cm4gZX07WGEuYWRkPWxSdDtYYS5hZGRBbGw9dVJ0O1hhLmNvdmVyPWZSdDtYYS5kYXRhPWRSdDtYYS5leHRlbnQ9Z1J0O1hhLmZpbmQ9eVJ0O1hhLnJlbW92ZT14UnQ7WGEucmVtb3ZlQWxsPWJSdDtYYS5yb290PVNSdDtYYS5zaXplPUVSdDtYYS52aXNpdD1DUnQ7WGEudmlzaXRBZnRlcj1QUnQ7WGEueD1rUnQ7WGEueT1EUnR9KTt2YXIgQlJ0PU0oKCk9PntGUnQoKX0pO3ZhciBIUnQsVlJ0PU0oKCk9PntIUnQ9W10uc2xpY2V9KTtmdW5jdGlvbiBldHQoZSl7dGhpcy5fc2l6ZT1lLHRoaXMuX2NhbGw9dGhpcy5fZXJyb3I9bnVsbCx0aGlzLl90YXNrcz1bXSx0aGlzLl9kYXRhPVtdLHRoaXMuX3dhaXRpbmc9dGhpcy5fYWN0aXZlPXRoaXMuX2VuZGVkPXRoaXMuX3N0YXJ0PTB9ZnVuY3Rpb24gVVJ0KGUpe2lmKCFlLl9zdGFydCl0cnl7ZzZlKGUpfWNhdGNoKHQpe2lmKGUuX3Rhc2tzW2UuX2VuZGVkK2UuX2FjdGl2ZS0xXSlydHQoZSx0KTtlbHNlIGlmKCFlLl9kYXRhKXRocm93IHR9fWZ1bmN0aW9uIGc2ZShlKXtmb3IoO2UuX3N0YXJ0PWUuX3dhaXRpbmcmJmUuX2FjdGl2ZTxlLl9zaXplOyl7dmFyIHQ9ZS5fZW5kZWQrZS5fYWN0aXZlLHI9ZS5fdGFza3NbdF0sbj1yLmxlbmd0aC0xLGk9cltuXTtyW25dPV82ZShlLHQpLC0tZS5fd2FpdGluZywrK2UuX2FjdGl2ZSxyPWkuYXBwbHkobnVsbCxyKSxlLl90YXNrc1t0XSYmKGUuX3Rhc2tzW3RdPXJ8fG02ZSl9fWZ1bmN0aW9uIF82ZShlLHQpe3JldHVybiBmdW5jdGlvbihyLG4peyFlLl90YXNrc1t0XXx8KC0tZS5fYWN0aXZlLCsrZS5fZW5kZWQsZS5fdGFza3NbdF09bnVsbCxlLl9lcnJvcj09bnVsbCYmKHIhPW51bGw/cnR0KGUscik6KGUuX2RhdGFbdF09bixlLl93YWl0aW5nP1VSdChlKTp4TyhlKSkpKX19ZnVuY3Rpb24gcnR0KGUsdCl7dmFyIHI9ZS5fdGFza3MubGVuZ3RoLG47Zm9yKGUuX2Vycm9yPXQsZS5fZGF0YT12b2lkIDAsZS5fd2FpdGluZz1OYU47LS1yPj0wOylpZigobj1lLl90YXNrc1tyXSkmJihlLl90YXNrc1tyXT1udWxsLG4uYWJvcnQpKXRyeXtuLmFib3J0KCl9Y2F0Y2goaSl7fWUuX2FjdGl2ZT1OYU4seE8oZSl9ZnVuY3Rpb24geE8oZSl7aWYoIWUuX2FjdGl2ZSYmZS5fY2FsbCl7dmFyIHQ9ZS5fZGF0YTtlLl9kYXRhPXZvaWQgMCxlLl9jYWxsKGUuX2Vycm9yLHQpfX1mdW5jdGlvbiBiTyhlKXtpZihlPT1udWxsKWU9MS8wO2Vsc2UgaWYoISgoZT0rZSk+PTEpKXRocm93IG5ldyBFcnJvcigiaW52YWxpZCBjb25jdXJyZW5jeSIpO3JldHVybiBuZXcgZXR0KGUpfXZhciBtNmUscVJ0PU0oKCk9PntWUnQoKTttNmU9e307ZXR0LnByb3RvdHlwZT1iTy5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOmV0dCxkZWZlcjpmdW5jdGlvbihlKXtpZih0eXBlb2YgZSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgY2FsbGJhY2siKTtpZih0aGlzLl9jYWxsKXRocm93IG5ldyBFcnJvcigiZGVmZXIgYWZ0ZXIgYXdhaXQiKTtpZih0aGlzLl9lcnJvciE9bnVsbClyZXR1cm4gdGhpczt2YXIgdD1IUnQuY2FsbChhcmd1bWVudHMsMSk7cmV0dXJuIHQucHVzaChlKSwrK3RoaXMuX3dhaXRpbmcsdGhpcy5fdGFza3MucHVzaCh0KSxVUnQodGhpcyksdGhpc30sYWJvcnQ6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZXJyb3I9PW51bGwmJnJ0dCh0aGlzLG5ldyBFcnJvcigiYWJvcnQiKSksdGhpc30sYXdhaXQ6ZnVuY3Rpb24oZSl7aWYodHlwZW9mIGUhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIGNhbGxiYWNrIik7aWYodGhpcy5fY2FsbCl0aHJvdyBuZXcgRXJyb3IoIm11bHRpcGxlIGF3YWl0Iik7cmV0dXJuIHRoaXMuX2NhbGw9ZnVuY3Rpb24odCxyKXtlLmFwcGx5KG51bGwsW3RdLmNvbmNhdChyKSl9LHhPKHRoaXMpLHRoaXN9LGF3YWl0QWxsOmZ1bmN0aW9uKGUpe2lmKHR5cGVvZiBlIT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcigiaW52YWxpZCBjYWxsYmFjayIpO2lmKHRoaXMuX2NhbGwpdGhyb3cgbmV3IEVycm9yKCJtdWx0aXBsZSBhd2FpdCIpO3JldHVybiB0aGlzLl9jYWxsPWUseE8odGhpcyksdGhpc319fSk7dmFyIEdSdD1NKCgpPT57cVJ0KCl9KTtmdW5jdGlvbiBiYygpe3JldHVybiBNYXRoLnJhbmRvbSgpfXZhciAkeT1NKCgpPT57fSk7dmFyIFdSdCxZUnQ9TSgoKT0+eyR5KCk7V1J0PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuLGkpe3JldHVybiBuPW49PW51bGw/MDorbixpPWk9PW51bGw/MToraSxhcmd1bWVudHMubGVuZ3RoPT09MT8oaT1uLG49MCk6aS09bixmdW5jdGlvbigpe3JldHVybiB0KCkqaStufX1yZXR1cm4gci5zb3VyY2U9ZSxyfShiYyl9KTt2YXIgd08sbnR0PU0oKCk9PnskeSgpO3dPPWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuLGkpe3ZhciBvLGE7cmV0dXJuIG49bj09bnVsbD8wOituLGk9aT09bnVsbD8xOitpLGZ1bmN0aW9uKCl7dmFyIHM7aWYobyE9bnVsbClzPW8sbz1udWxsO2Vsc2UgZG8gbz10KCkqMi0xLHM9dCgpKjItMSxhPW8qbytzKnM7d2hpbGUoIWF8fGE+MSk7cmV0dXJuIG4raSpzKk1hdGguc3FydCgtMipNYXRoLmxvZyhhKS9hKX19cmV0dXJuIHIuc291cmNlPWUscn0oYmMpfSk7dmFyIGpSdCxYUnQ9TSgoKT0+eyR5KCk7bnR0KCk7alJ0PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcigpe3ZhciBuPXdPLnNvdXJjZSh0KS5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIE1hdGguZXhwKG4oKSl9fXJldHVybiByLnNvdXJjZT1lLHJ9KGJjKX0pO3ZhciBTTyxpdHQ9TSgoKT0+eyR5KCk7U089ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4pe3JldHVybiBmdW5jdGlvbigpe2Zvcih2YXIgaT0wLG89MDtvPG47KytvKWkrPXQoKTtyZXR1cm4gaX19cmV0dXJuIHIuc291cmNlPWUscn0oYmMpfSk7dmFyICRSdCxLUnQ9TSgoKT0+eyR5KCk7aXR0KCk7JFJ0PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuKXt2YXIgaT1TTy5zb3VyY2UodCkobik7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGkoKS9ufX1yZXR1cm4gci5zb3VyY2U9ZSxyfShiYyl9KTt2YXIgWlJ0LEpSdD1NKCgpPT57JHkoKTtaUnQ9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4pe3JldHVybiBmdW5jdGlvbigpe3JldHVybi1NYXRoLmxvZygxLXQoKSkvbn19cmV0dXJuIHIuc291cmNlPWUscn0oYmMpfSk7dmFyIFFSdD1NKCgpPT57WVJ0KCk7bnR0KCk7WFJ0KCk7S1J0KCk7aXR0KCk7SlJ0KCl9KTtmdW5jdGlvbiBKMihlLHQpe3ZhciByLG49dnMoImJlZm9yZXNlbmQiLCJwcm9ncmVzcyIsImxvYWQiLCJlcnJvciIpLGksbz1KaSgpLGE9bmV3IFhNTEh0dHBSZXF1ZXN0LHM9bnVsbCxsPW51bGwsYyx1LGg9MDt0eXBlb2YgWERvbWFpblJlcXVlc3QhPSJ1bmRlZmluZWQiJiYhKCJ3aXRoQ3JlZGVudGlhbHMiaW4gYSkmJi9eKGh0dHAocyk/Oik/XC9cLy8udGVzdChlKSYmKGE9bmV3IFhEb21haW5SZXF1ZXN0KSwib25sb2FkImluIGE/YS5vbmxvYWQ9YS5vbmVycm9yPWEub250aW1lb3V0PWY6YS5vbnJlYWR5c3RhdGVjaGFuZ2U9ZnVuY3Rpb24ocCl7YS5yZWFkeVN0YXRlPjMmJmYocCl9O2Z1bmN0aW9uIGYocCl7dmFyIGQ9YS5zdGF0dXMsZztpZighZCYmdjZlKGEpfHxkPj0yMDAmJmQ8MzAwfHxkPT09MzA0KXtpZihjKXRyeXtnPWMuY2FsbChyLGEpfWNhdGNoKF8pe24uY2FsbCgiZXJyb3IiLHIsXyk7cmV0dXJufWVsc2UgZz1hO24uY2FsbCgibG9hZCIscixnKX1lbHNlIG4uY2FsbCgiZXJyb3IiLHIscCl9aWYoYS5vbnByb2dyZXNzPWZ1bmN0aW9uKHApe24uY2FsbCgicHJvZ3Jlc3MiLHIscCl9LHI9e2hlYWRlcjpmdW5jdGlvbihwLGQpe3JldHVybiBwPShwKyIiKS50b0xvd2VyQ2FzZSgpLGFyZ3VtZW50cy5sZW5ndGg8Mj9vLmdldChwKTooZD09bnVsbD9vLnJlbW92ZShwKTpvLnNldChwLGQrIiIpLHIpfSxtaW1lVHlwZTpmdW5jdGlvbihwKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT1wPT1udWxsP251bGw6cCsiIixyKTppfSxyZXNwb25zZVR5cGU6ZnVuY3Rpb24ocCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHU9cCxyKTp1fSx0aW1lb3V0OmZ1bmN0aW9uKHApe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhoPStwLHIpOmh9LHVzZXI6ZnVuY3Rpb24ocCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg8MT9zOihzPXA9PW51bGw/bnVsbDpwKyIiLHIpfSxwYXNzd29yZDpmdW5jdGlvbihwKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aDwxP2w6KGw9cD09bnVsbD9udWxsOnArIiIscil9LHJlc3BvbnNlOmZ1bmN0aW9uKHApe3JldHVybiBjPXAscn0sZ2V0OmZ1bmN0aW9uKHAsZCl7cmV0dXJuIHIuc2VuZCgiR0VUIixwLGQpfSxwb3N0OmZ1bmN0aW9uKHAsZCl7cmV0dXJuIHIuc2VuZCgiUE9TVCIscCxkKX0sc2VuZDpmdW5jdGlvbihwLGQsZyl7cmV0dXJuIGEub3BlbihwLGUsITAscyxsKSxpIT1udWxsJiYhby5oYXMoImFjY2VwdCIpJiZvLnNldCgiYWNjZXB0IixpKyIsKi8qIiksYS5zZXRSZXF1ZXN0SGVhZGVyJiZvLmVhY2goZnVuY3Rpb24oXyx5KXthLnNldFJlcXVlc3RIZWFkZXIoeSxfKX0pLGkhPW51bGwmJmEub3ZlcnJpZGVNaW1lVHlwZSYmYS5vdmVycmlkZU1pbWVUeXBlKGkpLHUhPW51bGwmJihhLnJlc3BvbnNlVHlwZT11KSxoPjAmJihhLnRpbWVvdXQ9aCksZz09bnVsbCYmdHlwZW9mIGQ9PSJmdW5jdGlvbiImJihnPWQsZD1udWxsKSxnIT1udWxsJiZnLmxlbmd0aD09PTEmJihnPXk2ZShnKSksZyE9bnVsbCYmci5vbigiZXJyb3IiLGcpLm9uKCJsb2FkIixmdW5jdGlvbihfKXtnKG51bGwsXyl9KSxuLmNhbGwoImJlZm9yZXNlbmQiLHIsYSksYS5zZW5kKGQ9PW51bGw/bnVsbDpkKSxyfSxhYm9ydDpmdW5jdGlvbigpe3JldHVybiBhLmFib3J0KCkscn0sb246ZnVuY3Rpb24oKXt2YXIgcD1uLm9uLmFwcGx5KG4sYXJndW1lbnRzKTtyZXR1cm4gcD09PW4/cjpwfX0sdCE9bnVsbCl7aWYodHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIGNhbGxiYWNrOiAiK3QpO3JldHVybiByLmdldCh0KX1yZXR1cm4gcn1mdW5jdGlvbiB5NmUoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7ZSh0PT1udWxsP3I6bnVsbCl9fWZ1bmN0aW9uIHY2ZShlKXt2YXIgdD1lLnJlc3BvbnNlVHlwZTtyZXR1cm4gdCYmdCE9PSJ0ZXh0Ij9lLnJlc3BvbnNlOmUucmVzcG9uc2VUZXh0fXZhciBNTz1NKCgpPT57VGIoKTtrbSgpfSk7ZnVuY3Rpb24gVGcoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocixuKXt2YXIgaT1KMihyKS5taW1lVHlwZShlKS5yZXNwb25zZSh0KTtpZihuIT1udWxsKXtpZih0eXBlb2YgbiE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgY2FsbGJhY2s6ICIrbik7cmV0dXJuIGkuZ2V0KG4pfXJldHVybiBpfX12YXIgaUM9TSgoKT0+e01PKCl9KTt2YXIgdE50LGVOdD1NKCgpPT57aUMoKTt0TnQ9VGcoInRleHQvaHRtbCIsZnVuY3Rpb24oZSl7cmV0dXJuIGRvY3VtZW50LmNyZWF0ZVJhbmdlKCkuY3JlYXRlQ29udGV4dHVhbEZyYWdtZW50KGUucmVzcG9uc2VUZXh0KX0pfSk7dmFyIHJOdCxuTnQ9TSgoKT0+e2lDKCk7ck50PVRnKCJhcHBsaWNhdGlvbi9qc29uIixmdW5jdGlvbihlKXtyZXR1cm4gSlNPTi5wYXJzZShlLnJlc3BvbnNlVGV4dCl9KX0pO3ZhciBpTnQsb050PU0oKCk9PntpQygpO2lOdD1UZygidGV4dC9wbGFpbiIsZnVuY3Rpb24oZSl7cmV0dXJuIGUucmVzcG9uc2VUZXh0fSl9KTt2YXIgYU50LHNOdD1NKCgpPT57aUMoKTthTnQ9VGcoImFwcGxpY2F0aW9uL3htbCIsZnVuY3Rpb24oZSl7dmFyIHQ9ZS5yZXNwb25zZVhNTDtpZighdCl0aHJvdyBuZXcgRXJyb3IoInBhcnNlIGVycm9yIik7cmV0dXJuIHR9KX0pO2Z1bmN0aW9uIEVPKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKHIsbixpKXthcmd1bWVudHMubGVuZ3RoPDMmJihpPW4sbj1udWxsKTt2YXIgbz1KMihyKS5taW1lVHlwZShlKTtyZXR1cm4gby5yb3c9ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/by5yZXNwb25zZSh4NmUodCxuPWEpKTpufSxvLnJvdyhuKSxpP28uZ2V0KGkpOm99fWZ1bmN0aW9uIHg2ZShlLHQpe3JldHVybiBmdW5jdGlvbihyKXtyZXR1cm4gZShyLnJlc3BvbnNlVGV4dCx0KX19dmFyIG90dD1NKCgpPT57TU8oKX0pO3ZhciBsTnQsY050PU0oKCk9PntVRSgpO290dCgpO2xOdD1FTygidGV4dC9jc3YiLENiKX0pO3ZhciB1TnQsaE50PU0oKCk9PntVRSgpO290dCgpO3VOdD1FTygidGV4dC90YWItc2VwYXJhdGVkLXZhbHVlcyIsQWIpfSk7dmFyIGZOdD1NKCgpPT57TU8oKTtlTnQoKTtuTnQoKTtvTnQoKTtzTnQoKTtjTnQoKTtoTnQoKX0pO2Z1bmN0aW9uIHBmKGUsdCl7cmV0dXJuIGU8dD8tMTplPnQ/MTplPj10PzA6TmFOfXZhciBLeT1NKCgpPT57fSk7ZnVuY3Rpb24gb0MoZSl7cmV0dXJuIGUubGVuZ3RoPT09MSYmKGU9YjZlKGUpKSx7bGVmdDpmdW5jdGlvbih0LHIsbixpKXtmb3Iobj09bnVsbCYmKG49MCksaT09bnVsbCYmKGk9dC5sZW5ndGgpO248aTspe3ZhciBvPW4raT4+PjE7ZSh0W29dLHIpPDA/bj1vKzE6aT1vfXJldHVybiBufSxyaWdodDpmdW5jdGlvbih0LHIsbixpKXtmb3Iobj09bnVsbCYmKG49MCksaT09bnVsbCYmKGk9dC5sZW5ndGgpO248aTspe3ZhciBvPW4raT4+PjE7ZSh0W29dLHIpPjA/aT1vOm49bysxfXJldHVybiBufX19ZnVuY3Rpb24gYjZlKGUpe3JldHVybiBmdW5jdGlvbih0LHIpe3JldHVybiBwZihlKHQpLHIpfX12YXIgYXR0PU0oKCk9PntLeSgpfSk7dmFyIHBOdCxkTnQsdzZlLGRmLHN0dD1NKCgpPT57S3koKTthdHQoKTtwTnQ9b0MocGYpLGROdD1wTnQucmlnaHQsdzZlPXBOdC5sZWZ0LGRmPWROdH0pO3ZhciBsdHQ9TSgoKT0+e30pO3ZhciBtTnQ9TSgoKT0+e2x0dCgpfSk7dmFyIGdOdD1NKCgpPT57fSk7ZnVuY3Rpb24gUTIoZSl7cmV0dXJuIGU9PT1udWxsP05hTjorZX12YXIgdHc9TSgoKT0+e30pO3ZhciBjdHQ9TSgoKT0+e3R3KCl9KTt2YXIgdXR0PU0oKCk9PntjdHQoKX0pO3ZhciBodHQ9TSgoKT0+e30pO3ZhciBfTnQsTTZlLEU2ZSxmdHQ9TSgoKT0+e19OdD1BcnJheS5wcm90b3R5cGUsTTZlPV9OdC5zbGljZSxFNmU9X050Lm1hcH0pO3ZhciB5TnQ9TSgoKT0+e30pO3ZhciB2TnQ9TSgoKT0+e30pO2Z1bmN0aW9uIFRPKGUsdCxyKXtlPStlLHQ9K3Qscj0oaT1hcmd1bWVudHMubGVuZ3RoKTwyPyh0PWUsZT0wLDEpOmk8Mz8xOityO2Zvcih2YXIgbj0tMSxpPU1hdGgubWF4KDAsTWF0aC5jZWlsKCh0LWUpL3IpKXwwLG89bmV3IEFycmF5KGkpOysrbjxpOylvW25dPWUrbipyO3JldHVybiBvfXZhciBwdHQ9TSgoKT0+e30pO2Z1bmN0aW9uIGFDKGUsdCxyKXt2YXIgbixpPS0xLG8sYSxzO2lmKHQ9K3QsZT0rZSxyPStyLGU9PT10JiZyPjApcmV0dXJuW2VdO2lmKChuPXQ8ZSkmJihvPWUsZT10LHQ9byksKHM9ZXcoZSx0LHIpKT09PTB8fCFpc0Zpbml0ZShzKSlyZXR1cm5bXTtpZihzPjApZm9yKGU9TWF0aC5jZWlsKGUvcyksdD1NYXRoLmZsb29yKHQvcyksYT1uZXcgQXJyYXkobz1NYXRoLmNlaWwodC1lKzEpKTsrK2k8bzspYVtpXT0oZStpKSpzO2Vsc2UgZm9yKGU9TWF0aC5mbG9vcihlKnMpLHQ9TWF0aC5jZWlsKHQqcyksYT1uZXcgQXJyYXkobz1NYXRoLmNlaWwoZS10KzEpKTsrK2k8bzspYVtpXT0oZS1pKS9zO3JldHVybiBuJiZhLnJldmVyc2UoKSxhfWZ1bmN0aW9uIGV3KGUsdCxyKXt2YXIgbj0odC1lKS9NYXRoLm1heCgwLHIpLGk9TWF0aC5mbG9vcihNYXRoLmxvZyhuKS9NYXRoLkxOMTApLG89bi9NYXRoLnBvdygxMCxpKTtyZXR1cm4gaT49MD8obz49ZHR0PzEwOm8+PW10dD81Om8+PWd0dD8yOjEpKk1hdGgucG93KDEwLGkpOi1NYXRoLnBvdygxMCwtaSkvKG8+PWR0dD8xMDpvPj1tdHQ/NTpvPj1ndHQ/MjoxKX1mdW5jdGlvbiBaeShlLHQscil7dmFyIG49TWF0aC5hYnModC1lKS9NYXRoLm1heCgwLHIpLGk9TWF0aC5wb3coMTAsTWF0aC5mbG9vcihNYXRoLmxvZyhuKS9NYXRoLkxOMTApKSxvPW4vaTtyZXR1cm4gbz49ZHR0P2kqPTEwOm8+PW10dD9pKj01Om8+PWd0dCYmKGkqPTIpLHQ8ZT8taTppfXZhciBkdHQsbXR0LGd0dCxfdHQ9TSgoKT0+e2R0dD1NYXRoLnNxcnQoNTApLG10dD1NYXRoLnNxcnQoMTApLGd0dD1NYXRoLnNxcnQoMil9KTt2YXIgeXR0PU0oKCk9Pnt9KTt2YXIgeE50PU0oKCk9PntmdHQoKTtzdHQoKTt5TnQoKTtodHQoKTt2TnQoKTtwdHQoKTtfdHQoKTt5dHQoKX0pO2Z1bmN0aW9uIHNDKGUsdCxyKXtpZihyPT1udWxsJiYocj1RMiksISEobj1lLmxlbmd0aCkpe2lmKCh0PSt0KTw9MHx8bjwyKXJldHVybityKGVbMF0sMCxlKTtpZih0Pj0xKXJldHVybityKGVbbi0xXSxuLTEsZSk7dmFyIG4saT0obi0xKSp0LG89TWF0aC5mbG9vcihpKSxhPStyKGVbb10sbyxlKSxzPStyKGVbbysxXSxvKzEsZSk7cmV0dXJuIGErKHMtYSkqKGktbyl9fXZhciBDTz1NKCgpPT57dHcoKX0pO3ZhciBiTnQ9TSgoKT0+e2Z0dCgpO0t5KCk7dHcoKTtDTygpfSk7dmFyIHdOdD1NKCgpPT57dXR0KCl9KTt2YXIgU050PU0oKCk9Pnt9KTt2YXIgTU50PU0oKCk9Pnt0dygpfSk7dmFyIEVOdD1NKCgpPT57S3koKTt0dygpO0NPKCl9KTt2YXIgVE50PU0oKCk9Pnt9KTt2YXIgdnR0PU0oKCk9Pnt9KTt2YXIgQ050PU0oKCk9Pnt9KTt2YXIgQU50PU0oKCk9PntLeSgpfSk7dmFyIFBOdD1NKCgpPT57fSk7dmFyIElOdD1NKCgpPT57fSk7dmFyIHh0dD1NKCgpPT57dnR0KCl9KTt2YXIgTE50PU0oKCk9Pnt4dHQoKX0pO3ZhciBtZj1NKCgpPT57c3R0KCk7S3koKTthdHQoKTttTnQoKTtnTnQoKTt1dHQoKTtodHQoKTt4TnQoKTtiTnQoKTt3TnQoKTt5dHQoKTtTTnQoKTtNTnQoKTtFTnQoKTtUTnQoKTt2dHQoKTtsdHQoKTtDTnQoKTtDTygpO3B0dCgpO0FOdCgpO1BOdCgpO0lOdCgpO190dCgpO3h0dCgpO2N0dCgpO0xOdCgpfSk7ZnVuY3Rpb24gQU8oKXt9ZnVuY3Rpb24ga050KGUsdCl7dmFyIHI9bmV3IEFPO2lmKGUgaW5zdGFuY2VvZiBBTyllLmVhY2goZnVuY3Rpb24ocyxsKXtyLnNldChsLHMpfSk7ZWxzZSBpZihBcnJheS5pc0FycmF5KGUpKXt2YXIgbj0tMSxpPWUubGVuZ3RoLG87aWYodD09bnVsbClmb3IoOysrbjxpOylyLnNldChuLGVbbl0pO2Vsc2UgZm9yKDsrK248aTspci5zZXQodChvPWVbbl0sbixlKSxvKX1lbHNlIGlmKGUpZm9yKHZhciBhIGluIGUpci5zZXQoYSxlW2FdKTtyZXR1cm4gcn12YXIgZmwsSnksUE89TSgoKT0+e2ZsPSIkIjtBTy5wcm90b3R5cGU9a050LnByb3RvdHlwZT17Y29uc3RydWN0b3I6QU8saGFzOmZ1bmN0aW9uKGUpe3JldHVybiBmbCtlIGluIHRoaXN9LGdldDpmdW5jdGlvbihlKXtyZXR1cm4gdGhpc1tmbCtlXX0sc2V0OmZ1bmN0aW9uKGUsdCl7cmV0dXJuIHRoaXNbZmwrZV09dCx0aGlzfSxyZW1vdmU6ZnVuY3Rpb24oZSl7dmFyIHQ9ZmwrZTtyZXR1cm4gdCBpbiB0aGlzJiZkZWxldGUgdGhpc1t0XX0sY2xlYXI6ZnVuY3Rpb24oKXtmb3IodmFyIGUgaW4gdGhpcyllWzBdPT09ZmwmJmRlbGV0ZSB0aGlzW2VdfSxrZXlzOmZ1bmN0aW9uKCl7dmFyIGU9W107Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PWZsJiZlLnB1c2godC5zbGljZSgxKSk7cmV0dXJuIGV9LHZhbHVlczpmdW5jdGlvbigpe3ZhciBlPVtdO2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1mbCYmZS5wdXNoKHRoaXNbdF0pO3JldHVybiBlfSxlbnRyaWVzOmZ1bmN0aW9uKCl7dmFyIGU9W107Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PWZsJiZlLnB1c2goe2tleTp0LnNsaWNlKDEpLHZhbHVlOnRoaXNbdF19KTtyZXR1cm4gZX0sc2l6ZTpmdW5jdGlvbigpe3ZhciBlPTA7Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PWZsJiYrK2U7cmV0dXJuIGV9LGVtcHR5OmZ1bmN0aW9uKCl7Zm9yKHZhciBlIGluIHRoaXMpaWYoZVswXT09PWZsKXJldHVybiExO3JldHVybiEwfSxlYWNoOmZ1bmN0aW9uKGUpe2Zvcih2YXIgdCBpbiB0aGlzKXRbMF09PT1mbCYmZSh0aGlzW3RdLHQuc2xpY2UoMSksdGhpcyl9fTtKeT1rTnR9KTt2YXIgUk50PU0oKCk9PntQTygpfSk7ZnVuY3Rpb24gSU8oKXt9ZnVuY3Rpb24gTDZlKGUsdCl7dmFyIHI9bmV3IElPO2lmKGUgaW5zdGFuY2VvZiBJTyllLmVhY2goZnVuY3Rpb24obyl7ci5hZGQobyl9KTtlbHNlIGlmKGUpe3ZhciBuPS0xLGk9ZS5sZW5ndGg7aWYodD09bnVsbClmb3IoOysrbjxpOylyLmFkZChlW25dKTtlbHNlIGZvcig7KytuPGk7KXIuYWRkKHQoZVtuXSxuLGUpKX1yZXR1cm4gcn12YXIgUXksTk50PU0oKCk9PntQTygpO1F5PUp5LnByb3RvdHlwZTtJTy5wcm90b3R5cGU9TDZlLnByb3RvdHlwZT17Y29uc3RydWN0b3I6SU8saGFzOlF5LmhhcyxhZGQ6ZnVuY3Rpb24oZSl7cmV0dXJuIGUrPSIiLHRoaXNbZmwrZV09ZSx0aGlzfSxyZW1vdmU6UXkucmVtb3ZlLGNsZWFyOlF5LmNsZWFyLHZhbHVlczpReS5rZXlzLHNpemU6UXkuc2l6ZSxlbXB0eTpReS5lbXB0eSxlYWNoOlF5LmVhY2h9fSk7dmFyIEROdD1NKCgpPT57fSk7dmFyIE9OdD1NKCgpPT57fSk7dmFyIHpOdD1NKCgpPT57fSk7dmFyIEZOdD1NKCgpPT57Uk50KCk7Tk50KCk7UE8oKTtETnQoKTtPTnQoKTt6TnQoKX0pO3ZhciBCTnQscncscGwsQ2c9TSgoKT0+e0JOdD1BcnJheS5wcm90b3R5cGUscnc9Qk50Lm1hcCxwbD1CTnQuc2xpY2V9KTtmdW5jdGlvbiBudyhlKXt2YXIgdD1KeSgpLHI9W10sbj1MTztlPWU9PW51bGw/W106cGwuY2FsbChlKTtmdW5jdGlvbiBpKG8pe3ZhciBhPW8rIiIscz10LmdldChhKTtpZighcyl7aWYobiE9PUxPKXJldHVybiBuO3Quc2V0KGEscz1yLnB1c2gobykpfXJldHVybiBlWyhzLTEpJWUubGVuZ3RoXX1yZXR1cm4gaS5kb21haW49ZnVuY3Rpb24obyl7aWYoIWFyZ3VtZW50cy5sZW5ndGgpcmV0dXJuIHIuc2xpY2UoKTtyPVtdLHQ9SnkoKTtmb3IodmFyIGE9LTEscz1vLmxlbmd0aCxsLGM7KythPHM7KXQuaGFzKGM9KGw9b1thXSkrIiIpfHx0LnNldChjLHIucHVzaChsKSk7cmV0dXJuIGl9LGkucmFuZ2U9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9cGwuY2FsbChvKSxpKTplLnNsaWNlKCl9LGkudW5rbm93bj1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj1vLGkpOm59LGkuY29weT1mdW5jdGlvbigpe3JldHVybiBudygpLmRvbWFpbihyKS5yYW5nZShlKS51bmtub3duKG4pfSxpfXZhciBMTyxidHQ9TSgoKT0+e0ZOdCgpO0NnKCk7TE89e25hbWU6ImltcGxpY2l0In19KTtmdW5jdGlvbiBsQygpe3ZhciBlPW53KCkudW5rbm93bih2b2lkIDApLHQ9ZS5kb21haW4scj1lLnJhbmdlLG49WzAsMV0saSxvLGE9ITEscz0wLGw9MCxjPS41O2RlbGV0ZSBlLnVua25vd247ZnVuY3Rpb24gdSgpe3ZhciBoPXQoKS5sZW5ndGgsZj1uWzFdPG5bMF0scD1uW2YtMF0sZD1uWzEtZl07aT0oZC1wKS9NYXRoLm1heCgxLGgtcytsKjIpLGEmJihpPU1hdGguZmxvb3IoaSkpLHArPShkLXAtaSooaC1zKSkqYyxvPWkqKDEtcyksYSYmKHA9TWF0aC5yb3VuZChwKSxvPU1hdGgucm91bmQobykpO3ZhciBnPVRPKGgpLm1hcChmdW5jdGlvbihfKXtyZXR1cm4gcCtpKl99KTtyZXR1cm4gcihmP2cucmV2ZXJzZSgpOmcpfXJldHVybiBlLmRvbWFpbj1mdW5jdGlvbihoKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odChoKSx1KCkpOnQoKX0sZS5yYW5nZT1mdW5jdGlvbihoKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj1bK2hbMF0sK2hbMV1dLHUoKSk6bi5zbGljZSgpfSxlLnJhbmdlUm91bmQ9ZnVuY3Rpb24oaCl7cmV0dXJuIG49WytoWzBdLCtoWzFdXSxhPSEwLHUoKX0sZS5iYW5kd2lkdGg9ZnVuY3Rpb24oKXtyZXR1cm4gb30sZS5zdGVwPWZ1bmN0aW9uKCl7cmV0dXJuIGl9LGUucm91bmQ9ZnVuY3Rpb24oaCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGE9ISFoLHUoKSk6YX0sZS5wYWRkaW5nPWZ1bmN0aW9uKGgpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhzPWw9TWF0aC5tYXgoMCxNYXRoLm1pbigxLGgpKSx1KCkpOnN9LGUucGFkZGluZ0lubmVyPWZ1bmN0aW9uKGgpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhzPU1hdGgubWF4KDAsTWF0aC5taW4oMSxoKSksdSgpKTpzfSxlLnBhZGRpbmdPdXRlcj1mdW5jdGlvbihoKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obD1NYXRoLm1heCgwLE1hdGgubWluKDEsaCkpLHUoKSk6bH0sZS5hbGlnbj1mdW5jdGlvbihoKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYz1NYXRoLm1heCgwLE1hdGgubWluKDEsaCkpLHUoKSk6Y30sZS5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIGxDKCkuZG9tYWluKHQoKSkucmFuZ2Uobikucm91bmQoYSkucGFkZGluZ0lubmVyKHMpLnBhZGRpbmdPdXRlcihsKS5hbGlnbihjKX0sdSgpfWZ1bmN0aW9uIEhOdChlKXt2YXIgdD1lLmNvcHk7cmV0dXJuIGUucGFkZGluZz1lLnBhZGRpbmdPdXRlcixkZWxldGUgZS5wYWRkaW5nSW5uZXIsZGVsZXRlIGUucGFkZGluZ091dGVyLGUuY29weT1mdW5jdGlvbigpe3JldHVybiBITnQodCgpKX0sZX1mdW5jdGlvbiBWTnQoKXtyZXR1cm4gSE50KGxDKCkucGFkZGluZ0lubmVyKDEpKX12YXIgVU50PU0oKCk9PnttZigpO2J0dCgpfSk7ZnVuY3Rpb24gaXcoZSx0LHIpe2UucHJvdG90eXBlPXQucHJvdG90eXBlPXIsci5jb25zdHJ1Y3Rvcj1lfWZ1bmN0aW9uIGNDKGUsdCl7dmFyIHI9T2JqZWN0LmNyZWF0ZShlLnByb3RvdHlwZSk7Zm9yKHZhciBuIGluIHQpcltuXT10W25dO3JldHVybiByfXZhciB3dHQ9TSgoKT0+e30pO2Z1bmN0aW9uIGUxKCl7fWZ1bmN0aW9uIEdOdCgpe3JldHVybiB0aGlzLnJnYigpLmZvcm1hdEhleCgpfWZ1bmN0aW9uIEI2ZSgpe3JldHVybiBLTnQodGhpcykuZm9ybWF0SHNsKCl9ZnVuY3Rpb24gV050KCl7cmV0dXJuIHRoaXMucmdiKCkuZm9ybWF0UmdiKCl9ZnVuY3Rpb24gUGcoZSl7dmFyIHQscjtyZXR1cm4gZT0oZSsiIikudHJpbSgpLnRvTG93ZXJDYXNlKCksKHQ9azZlLmV4ZWMoZSkpPyhyPXRbMV0ubGVuZ3RoLHQ9cGFyc2VJbnQodFsxXSwxNikscj09PTY/WU50KHQpOnI9PT0zP25ldyBoYSh0Pj44JjE1fHQ+PjQmMjQwLHQ+PjQmMTV8dCYyNDAsKHQmMTUpPDw0fHQmMTUsMSk6cj09PTg/a08odD4+MjQmMjU1LHQ+PjE2JjI1NSx0Pj44JjI1NSwodCYyNTUpLzI1NSk6cj09PTQ/a08odD4+MTImMTV8dD4+OCYyNDAsdD4+OCYxNXx0Pj40JjI0MCx0Pj40JjE1fHQmMjQwLCgodCYxNSk8PDR8dCYxNSkvMjU1KTpudWxsKToodD1SNmUuZXhlYyhlKSk/bmV3IGhhKHRbMV0sdFsyXSx0WzNdLDEpOih0PU42ZS5leGVjKGUpKT9uZXcgaGEodFsxXSoyNTUvMTAwLHRbMl0qMjU1LzEwMCx0WzNdKjI1NS8xMDAsMSk6KHQ9RDZlLmV4ZWMoZSkpP2tPKHRbMV0sdFsyXSx0WzNdLHRbNF0pOih0PU82ZS5leGVjKGUpKT9rTyh0WzFdKjI1NS8xMDAsdFsyXSoyNTUvMTAwLHRbM10qMjU1LzEwMCx0WzRdKToodD16NmUuZXhlYyhlKSk/JE50KHRbMV0sdFsyXS8xMDAsdFszXS8xMDAsMSk6KHQ9RjZlLmV4ZWMoZSkpPyROdCh0WzFdLHRbMl0vMTAwLHRbM10vMTAwLHRbNF0pOnFOdC5oYXNPd25Qcm9wZXJ0eShlKT9ZTnQocU50W2VdKTplPT09InRyYW5zcGFyZW50Ij9uZXcgaGEoTmFOLE5hTixOYU4sMCk6bnVsbH1mdW5jdGlvbiBZTnQoZSl7cmV0dXJuIG5ldyBoYShlPj4xNiYyNTUsZT4+OCYyNTUsZSYyNTUsMSl9ZnVuY3Rpb24ga08oZSx0LHIsbil7cmV0dXJuIG48PTAmJihlPXQ9cj1OYU4pLG5ldyBoYShlLHQscixuKX1mdW5jdGlvbiBFdHQoZSl7cmV0dXJuIGUgaW5zdGFuY2VvZiBlMXx8KGU9UGcoZSkpLGU/KGU9ZS5yZ2IoKSxuZXcgaGEoZS5yLGUuZyxlLmIsZS5vcGFjaXR5KSk6bmV3IGhhfWZ1bmN0aW9uIGF3KGUsdCxyLG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MT9FdHQoZSk6bmV3IGhhKGUsdCxyLG49PW51bGw/MTpuKX1mdW5jdGlvbiBoYShlLHQscixuKXt0aGlzLnI9K2UsdGhpcy5nPSt0LHRoaXMuYj0rcix0aGlzLm9wYWNpdHk9K259ZnVuY3Rpb24gak50KCl7cmV0dXJuIiMiK1N0dCh0aGlzLnIpK1N0dCh0aGlzLmcpK1N0dCh0aGlzLmIpfWZ1bmN0aW9uIFhOdCgpe3ZhciBlPXRoaXMub3BhY2l0eTtyZXR1cm4gZT1pc05hTihlKT8xOk1hdGgubWF4KDAsTWF0aC5taW4oMSxlKSksKGU9PT0xPyJyZ2IoIjoicmdiYSgiKStNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKHRoaXMucil8fDApKSsiLCAiK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQodGhpcy5nKXx8MCkpKyIsICIrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCh0aGlzLmIpfHwwKSkrKGU9PT0xPyIpIjoiLCAiK2UrIikiKX1mdW5jdGlvbiBTdHQoZSl7cmV0dXJuIGU9TWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZChlKXx8MCkpLChlPDE2PyIwIjoiIikrZS50b1N0cmluZygxNil9ZnVuY3Rpb24gJE50KGUsdCxyLG4pe3JldHVybiBuPD0wP2U9dD1yPU5hTjpyPD0wfHxyPj0xP2U9dD1OYU46dDw9MCYmKGU9TmFOKSxuZXcgZ2YoZSx0LHIsbil9ZnVuY3Rpb24gS050KGUpe2lmKGUgaW5zdGFuY2VvZiBnZilyZXR1cm4gbmV3IGdmKGUuaCxlLnMsZS5sLGUub3BhY2l0eSk7aWYoZSBpbnN0YW5jZW9mIGUxfHwoZT1QZyhlKSksIWUpcmV0dXJuIG5ldyBnZjtpZihlIGluc3RhbmNlb2YgZ2YpcmV0dXJuIGU7ZT1lLnJnYigpO3ZhciB0PWUuci8yNTUscj1lLmcvMjU1LG49ZS5iLzI1NSxpPU1hdGgubWluKHQscixuKSxvPU1hdGgubWF4KHQscixuKSxhPU5hTixzPW8taSxsPShvK2kpLzI7cmV0dXJuIHM/KHQ9PT1vP2E9KHItbikvcysocjxuKSo2OnI9PT1vP2E9KG4tdCkvcysyOmE9KHQtcikvcys0LHMvPWw8LjU/bytpOjItby1pLGEqPTYwKTpzPWw+MCYmbDwxPzA6YSxuZXcgZ2YoYSxzLGwsZS5vcGFjaXR5KX1mdW5jdGlvbiBaTnQoZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP0tOdChlKTpuZXcgZ2YoZSx0LHIsbj09bnVsbD8xOm4pfWZ1bmN0aW9uIGdmKGUsdCxyLG4pe3RoaXMuaD0rZSx0aGlzLnM9K3QsdGhpcy5sPStyLHRoaXMub3BhY2l0eT0rbn1mdW5jdGlvbiBNdHQoZSx0LHIpe3JldHVybihlPDYwP3QrKHItdCkqZS82MDplPDE4MD9yOmU8MjQwP3QrKHItdCkqKDI0MC1lKS82MDp0KSoyNTV9dmFyIEFnLHQxLG93LHVDLF9mLGs2ZSxSNmUsTjZlLEQ2ZSxPNmUsejZlLEY2ZSxxTnQsVHR0PU0oKCk9Pnt3dHQoKTtBZz0uNyx0MT0xL0FnLG93PSJcXHMqKFsrLV0/XFxkKylcXHMqIix1Qz0iXFxzKihbKy1dP1xcZCpcXC4/XFxkKyg/OltlRV1bKy1dP1xcZCspPylcXHMqIixfZj0iXFxzKihbKy1dP1xcZCpcXC4/XFxkKyg/OltlRV1bKy1dP1xcZCspPyklXFxzKiIsazZlPS9eIyhbMC05YS1mXXszLDh9KSQvLFI2ZT1uZXcgUmVnRXhwKCJecmdiXFwoIitbb3csb3csb3ddKyJcXCkkIiksTjZlPW5ldyBSZWdFeHAoIl5yZ2JcXCgiK1tfZixfZixfZl0rIlxcKSQiKSxENmU9bmV3IFJlZ0V4cCgiXnJnYmFcXCgiK1tvdyxvdyxvdyx1Q10rIlxcKSQiKSxPNmU9bmV3IFJlZ0V4cCgiXnJnYmFcXCgiK1tfZixfZixfZix1Q10rIlxcKSQiKSx6NmU9bmV3IFJlZ0V4cCgiXmhzbFxcKCIrW3VDLF9mLF9mXSsiXFwpJCIpLEY2ZT1uZXcgUmVnRXhwKCJeaHNsYVxcKCIrW3VDLF9mLF9mLHVDXSsiXFwpJCIpLHFOdD17YWxpY2VibHVlOjE1NzkyMzgzLGFudGlxdWV3aGl0ZToxNjQ0NDM3NSxhcXVhOjY1NTM1LGFxdWFtYXJpbmU6ODM4ODU2NCxhenVyZToxNTc5NDE3NSxiZWlnZToxNjExOTI2MCxiaXNxdWU6MTY3NzAyNDQsYmxhY2s6MCxibGFuY2hlZGFsbW9uZDoxNjc3MjA0NSxibHVlOjI1NSxibHVldmlvbGV0OjkwNTUyMDIsYnJvd246MTA4MjQyMzQsYnVybHl3b29kOjE0NTk2MjMxLGNhZGV0Ymx1ZTo2MjY2NTI4LGNoYXJ0cmV1c2U6ODM4ODM1MixjaG9jb2xhdGU6MTM3ODk0NzAsY29yYWw6MTY3NDQyNzIsY29ybmZsb3dlcmJsdWU6NjU5MTk4MSxjb3Juc2lsazoxNjc3NTM4OCxjcmltc29uOjE0NDIzMTAwLGN5YW46NjU1MzUsZGFya2JsdWU6MTM5LGRhcmtjeWFuOjM1NzIzLGRhcmtnb2xkZW5yb2Q6MTIwOTI5MzksZGFya2dyYXk6MTExMTkwMTcsZGFya2dyZWVuOjI1NjAwLGRhcmtncmV5OjExMTE5MDE3LGRhcmtraGFraToxMjQzMzI1OSxkYXJrbWFnZW50YTo5MTA5NjQzLGRhcmtvbGl2ZWdyZWVuOjU1OTc5OTksZGFya29yYW5nZToxNjc0NzUyMCxkYXJrb3JjaGlkOjEwMDQwMDEyLGRhcmtyZWQ6OTEwOTUwNCxkYXJrc2FsbW9uOjE1MzA4NDEwLGRhcmtzZWFncmVlbjo5NDE5OTE5LGRhcmtzbGF0ZWJsdWU6NDczNDM0NyxkYXJrc2xhdGVncmF5OjMxMDA0OTUsZGFya3NsYXRlZ3JleTozMTAwNDk1LGRhcmt0dXJxdW9pc2U6NTI5NDUsZGFya3Zpb2xldDo5Njk5NTM5LGRlZXBwaW5rOjE2NzE2OTQ3LGRlZXBza3libHVlOjQ5MTUxLGRpbWdyYXk6NjkwODI2NSxkaW1ncmV5OjY5MDgyNjUsZG9kZ2VyYmx1ZToyMDAzMTk5LGZpcmVicmljazoxMTY3NDE0NixmbG9yYWx3aGl0ZToxNjc3NTkyMCxmb3Jlc3RncmVlbjoyMjYzODQyLGZ1Y2hzaWE6MTY3MTE5MzUsZ2FpbnNib3JvOjE0NDc0NDYwLGdob3N0d2hpdGU6MTYzMTY2NzEsZ29sZDoxNjc2NjcyMCxnb2xkZW5yb2Q6MTQzMjkxMjAsZ3JheTo4NDIxNTA0LGdyZWVuOjMyNzY4LGdyZWVueWVsbG93OjExNDAzMDU1LGdyZXk6ODQyMTUwNCxob25leWRldzoxNTc5NDE2MCxob3RwaW5rOjE2NzM4NzQwLGluZGlhbnJlZDoxMzQ1ODUyNCxpbmRpZ286NDkxNTMzMCxpdm9yeToxNjc3NzIwMCxraGFraToxNTc4NzY2MCxsYXZlbmRlcjoxNTEzMjQxMCxsYXZlbmRlcmJsdXNoOjE2NzczMzY1LGxhd25ncmVlbjo4MTkwOTc2LGxlbW9uY2hpZmZvbjoxNjc3NTg4NSxsaWdodGJsdWU6MTEzOTMyNTQsbGlnaHRjb3JhbDoxNTc2MTUzNixsaWdodGN5YW46MTQ3NDU1OTksbGlnaHRnb2xkZW5yb2R5ZWxsb3c6MTY0NDgyMTAsbGlnaHRncmF5OjEzODgyMzIzLGxpZ2h0Z3JlZW46OTQ5ODI1NixsaWdodGdyZXk6MTM4ODIzMjMsbGlnaHRwaW5rOjE2NzU4NDY1LGxpZ2h0c2FsbW9uOjE2NzUyNzYyLGxpZ2h0c2VhZ3JlZW46MjE0Mjg5MCxsaWdodHNreWJsdWU6ODkwMDM0NixsaWdodHNsYXRlZ3JheTo3ODMzNzUzLGxpZ2h0c2xhdGVncmV5Ojc4MzM3NTMsbGlnaHRzdGVlbGJsdWU6MTE1ODQ3MzQsbGlnaHR5ZWxsb3c6MTY3NzcxODQsbGltZTo2NTI4MCxsaW1lZ3JlZW46MzMyOTMzMCxsaW5lbjoxNjQ0NTY3MCxtYWdlbnRhOjE2NzExOTM1LG1hcm9vbjo4Mzg4NjA4LG1lZGl1bWFxdWFtYXJpbmU6NjczNzMyMixtZWRpdW1ibHVlOjIwNSxtZWRpdW1vcmNoaWQ6MTIyMTE2NjcsbWVkaXVtcHVycGxlOjk2NjI2ODMsbWVkaXVtc2VhZ3JlZW46Mzk3ODA5NyxtZWRpdW1zbGF0ZWJsdWU6ODA4Nzc5MCxtZWRpdW1zcHJpbmdncmVlbjo2NDE1NCxtZWRpdW10dXJxdW9pc2U6NDc3MjMwMCxtZWRpdW12aW9sZXRyZWQ6MTMwNDcxNzMsbWlkbmlnaHRibHVlOjE2NDQ5MTIsbWludGNyZWFtOjE2MTIxODUwLG1pc3R5cm9zZToxNjc3MDI3Myxtb2NjYXNpbjoxNjc3MDIyOSxuYXZham93aGl0ZToxNjc2ODY4NSxuYXZ5OjEyOCxvbGRsYWNlOjE2NjQzNTU4LG9saXZlOjg0MjEzNzYsb2xpdmVkcmFiOjcwNDg3Mzksb3JhbmdlOjE2NzUzOTIwLG9yYW5nZXJlZDoxNjcyOTM0NCxvcmNoaWQ6MTQzMTU3MzQscGFsZWdvbGRlbnJvZDoxNTY1NzEzMCxwYWxlZ3JlZW46MTAwMjU4ODAscGFsZXR1cnF1b2lzZToxMTUyOTk2NixwYWxldmlvbGV0cmVkOjE0MzgxMjAzLHBhcGF5YXdoaXA6MTY3NzMwNzcscGVhY2hwdWZmOjE2NzY3NjczLHBlcnU6MTM0Njg5OTEscGluazoxNjc2MTAzNSxwbHVtOjE0NTI0NjM3LHBvd2RlcmJsdWU6MTE1OTE5MTAscHVycGxlOjgzODg3MzYscmViZWNjYXB1cnBsZTo2Njk3ODgxLHJlZDoxNjcxMTY4MCxyb3N5YnJvd246MTIzNTc1MTkscm95YWxibHVlOjQyODY5NDUsc2FkZGxlYnJvd246OTEyNzE4NyxzYWxtb246MTY0MTY4ODIsc2FuZHlicm93bjoxNjAzMjg2NCxzZWFncmVlbjozMDUwMzI3LHNlYXNoZWxsOjE2Nzc0NjM4LHNpZW5uYToxMDUwNjc5NyxzaWx2ZXI6MTI2MzIyNTYsc2t5Ymx1ZTo4OTAwMzMxLHNsYXRlYmx1ZTo2OTcwMDYxLHNsYXRlZ3JheTo3MzcyOTQ0LHNsYXRlZ3JleTo3MzcyOTQ0LHNub3c6MTY3NzU5MzAsc3ByaW5nZ3JlZW46NjU0MDcsc3RlZWxibHVlOjQ2MjA5ODAsdGFuOjEzODA4NzgwLHRlYWw6MzI4OTYsdGhpc3RsZToxNDIwNDg4OCx0b21hdG86MTY3MzcwOTUsdHVycXVvaXNlOjQyNTE4NTYsdmlvbGV0OjE1NjMxMDg2LHdoZWF0OjE2MTEzMzMxLHdoaXRlOjE2Nzc3MjE1LHdoaXRlc21va2U6MTYxMTkyODUseWVsbG93OjE2Nzc2OTYwLHllbGxvd2dyZWVuOjEwMTQ1MDc0fTtpdyhlMSxQZyx7Y29weTpmdW5jdGlvbihlKXtyZXR1cm4gT2JqZWN0LmFzc2lnbihuZXcgdGhpcy5jb25zdHJ1Y3Rvcix0aGlzLGUpfSxkaXNwbGF5YWJsZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLnJnYigpLmRpc3BsYXlhYmxlKCl9LGhleDpHTnQsZm9ybWF0SGV4OkdOdCxmb3JtYXRIc2w6QjZlLGZvcm1hdFJnYjpXTnQsdG9TdHJpbmc6V050fSk7aXcoaGEsYXcsY0MoZTEse2JyaWdodGVyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/dDE6TWF0aC5wb3codDEsZSksbmV3IGhhKHRoaXMuciplLHRoaXMuZyplLHRoaXMuYiplLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihlKXtyZXR1cm4gZT1lPT1udWxsP0FnOk1hdGgucG93KEFnLGUpLG5ldyBoYSh0aGlzLnIqZSx0aGlzLmcqZSx0aGlzLmIqZSx0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpc30sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4tLjU8PXRoaXMuciYmdGhpcy5yPDI1NS41JiYtLjU8PXRoaXMuZyYmdGhpcy5nPDI1NS41JiYtLjU8PXRoaXMuYiYmdGhpcy5iPDI1NS41JiYwPD10aGlzLm9wYWNpdHkmJnRoaXMub3BhY2l0eTw9MX0saGV4OmpOdCxmb3JtYXRIZXg6ak50LGZvcm1hdFJnYjpYTnQsdG9TdHJpbmc6WE50fSkpO2l3KGdmLFpOdCxjQyhlMSx7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD90MTpNYXRoLnBvdyh0MSxlKSxuZXcgZ2YodGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9BZzpNYXRoLnBvdyhBZyxlKSxuZXcgZ2YodGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLmglMzYwKyh0aGlzLmg8MCkqMzYwLHQ9aXNOYU4oZSl8fGlzTmFOKHRoaXMucyk/MDp0aGlzLnMscj10aGlzLmwsbj1yKyhyPC41P3I6MS1yKSp0LGk9MipyLW47cmV0dXJuIG5ldyBoYShNdHQoZT49MjQwP2UtMjQwOmUrMTIwLGksbiksTXR0KGUsaSxuKSxNdHQoZTwxMjA/ZSsyNDA6ZS0xMjAsaSxuKSx0aGlzLm9wYWNpdHkpfSxkaXNwbGF5YWJsZTpmdW5jdGlvbigpe3JldHVybigwPD10aGlzLnMmJnRoaXMuczw9MXx8aXNOYU4odGhpcy5zKSkmJjA8PXRoaXMubCYmdGhpcy5sPD0xJiYwPD10aGlzLm9wYWNpdHkmJnRoaXMub3BhY2l0eTw9MX0sZm9ybWF0SHNsOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5vcGFjaXR5O3JldHVybiBlPWlzTmFOKGUpPzE6TWF0aC5tYXgoMCxNYXRoLm1pbigxLGUpKSwoZT09PTE/ImhzbCgiOiJoc2xhKCIpKyh0aGlzLmh8fDApKyIsICIrKHRoaXMuc3x8MCkqMTAwKyIlLCAiKyh0aGlzLmx8fDApKjEwMCsiJSIrKGU9PT0xPyIpIjoiLCAiK2UrIikiKX19KSl9KTt2YXIgSk50LFFOdCx0RHQ9TSgoKT0+e0pOdD1NYXRoLlBJLzE4MCxRTnQ9MTgwL01hdGguUEl9KTtmdW5jdGlvbiBINmUoZSl7aWYoZSBpbnN0YW5jZW9mIHIxKXJldHVybiBuZXcgcjEoZS5oLGUucyxlLmwsZS5vcGFjaXR5KTtlIGluc3RhbmNlb2YgaGF8fChlPUV0dChlKSk7dmFyIHQ9ZS5yLzI1NSxyPWUuZy8yNTUsbj1lLmIvMjU1LGk9KG5EdCpuK2VEdCp0LXJEdCpyKS8obkR0K2VEdC1yRHQpLG89bi1pLGE9KGhDKihyLWkpLUF0dCpvKS9STyxzPU1hdGguc3FydChhKmErbypvKS8oaEMqaSooMS1pKSksbD1zP01hdGguYXRhbjIoYSxvKSpRTnQtMTIwOk5hTjtyZXR1cm4gbmV3IHIxKGw8MD9sKzM2MDpsLHMsaSxlLm9wYWNpdHkpfWZ1bmN0aW9uICRhKGUsdCxyLG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MT9INmUoZSk6bmV3IHIxKGUsdCxyLG49PW51bGw/MTpuKX1mdW5jdGlvbiByMShlLHQscixuKXt0aGlzLmg9K2UsdGhpcy5zPSt0LHRoaXMubD0rcix0aGlzLm9wYWNpdHk9K259dmFyIGlEdCxDdHQsQXR0LFJPLGhDLGVEdCxyRHQsbkR0LG9EdD1NKCgpPT57d3R0KCk7VHR0KCk7dER0KCk7aUR0PS0uMTQ4NjEsQ3R0PTEuNzgyNzcsQXR0PS0uMjkyMjcsUk89LS45MDY0OSxoQz0xLjk3Mjk0LGVEdD1oQypSTyxyRHQ9aEMqQ3R0LG5EdD1DdHQqQXR0LVJPKmlEdDtpdyhyMSwkYSxjQyhlMSx7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD90MTpNYXRoLnBvdyh0MSxlKSxuZXcgcjEodGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9BZzpNYXRoLnBvdyhBZyxlKSxuZXcgcjEodGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXt2YXIgZT1pc05hTih0aGlzLmgpPzA6KHRoaXMuaCsxMjApKkpOdCx0PSt0aGlzLmwscj1pc05hTih0aGlzLnMpPzA6dGhpcy5zKnQqKDEtdCksbj1NYXRoLmNvcyhlKSxpPU1hdGguc2luKGUpO3JldHVybiBuZXcgaGEoMjU1Kih0K3IqKGlEdCpuK0N0dCppKSksMjU1Kih0K3IqKEF0dCpuK1JPKmkpKSwyNTUqKHQrciooaEMqbikpLHRoaXMub3BhY2l0eSl9fSkpfSk7dmFyIHN3PU0oKCk9PntUdHQoKTtvRHQoKX0pO2Z1bmN0aW9uIFB0dChlLHQscixuLGkpe3ZhciBvPWUqZSxhPW8qZTtyZXR1cm4oKDEtMyplKzMqby1hKSp0Kyg0LTYqbyszKmEpKnIrKDErMyplKzMqby0zKmEpKm4rYSppKS82fWZ1bmN0aW9uIGFEdChlKXt2YXIgdD1lLmxlbmd0aC0xO3JldHVybiBmdW5jdGlvbihyKXt2YXIgbj1yPD0wP3I9MDpyPj0xPyhyPTEsdC0xKTpNYXRoLmZsb29yKHIqdCksaT1lW25dLG89ZVtuKzFdLGE9bj4wP2Vbbi0xXToyKmktbyxzPW48dC0xP2VbbisyXToyKm8taTtyZXR1cm4gUHR0KChyLW4vdCkqdCxhLGksbyxzKX19dmFyIEl0dD1NKCgpPT57fSk7ZnVuY3Rpb24gc0R0KGUpe3ZhciB0PWUubGVuZ3RoO3JldHVybiBmdW5jdGlvbihyKXt2YXIgbj1NYXRoLmZsb29yKCgociU9MSk8MD8rK3I6cikqdCksaT1lWyhuK3QtMSkldF0sbz1lW24ldF0sYT1lWyhuKzEpJXRdLHM9ZVsobisyKSV0XTtyZXR1cm4gUHR0KChyLW4vdCkqdCxpLG8sYSxzKX19dmFyIGxEdD1NKCgpPT57SXR0KCl9KTtmdW5jdGlvbiBsdyhlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIEx0dD1NKCgpPT57fSk7ZnVuY3Rpb24gY0R0KGUsdCl7cmV0dXJuIGZ1bmN0aW9uKHIpe3JldHVybiBlK3IqdH19ZnVuY3Rpb24gVjZlKGUsdCxyKXtyZXR1cm4gZT1NYXRoLnBvdyhlLHIpLHQ9TWF0aC5wb3codCxyKS1lLHI9MS9yLGZ1bmN0aW9uKG4pe3JldHVybiBNYXRoLnBvdyhlK24qdCxyKX19ZnVuY3Rpb24gdUR0KGUsdCl7dmFyIHI9dC1lO3JldHVybiByP2NEdChlLHI+MTgwfHxyPC0xODA/ci0zNjAqTWF0aC5yb3VuZChyLzM2MCk6cik6bHcoaXNOYU4oZSk/dDplKX1mdW5jdGlvbiBoRHQoZSl7cmV0dXJuKGU9K2UpPT0xP25kOmZ1bmN0aW9uKHQscil7cmV0dXJuIHItdD9WNmUodCxyLGUpOmx3KGlzTmFOKHQpP3I6dCl9fWZ1bmN0aW9uIG5kKGUsdCl7dmFyIHI9dC1lO3JldHVybiByP2NEdChlLHIpOmx3KGlzTmFOKGUpP3Q6ZSl9dmFyIGt0dD1NKCgpPT57THR0KCl9KTtmdW5jdGlvbiBmRHQoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3ZhciByPXQubGVuZ3RoLG49bmV3IEFycmF5KHIpLGk9bmV3IEFycmF5KHIpLG89bmV3IEFycmF5KHIpLGEscztmb3IoYT0wO2E8cjsrK2Epcz1hdyh0W2FdKSxuW2FdPXMucnx8MCxpW2FdPXMuZ3x8MCxvW2FdPXMuYnx8MDtyZXR1cm4gbj1lKG4pLGk9ZShpKSxvPWUobykscy5vcGFjaXR5PTEsZnVuY3Rpb24obCl7cmV0dXJuIHMucj1uKGwpLHMuZz1pKGwpLHMuYj1vKGwpLHMrIiJ9fX12YXIgUnR0LGE1bixzNW4scER0PU0oKCk9PntzdygpO0l0dCgpO2xEdCgpO2t0dCgpO1J0dD1mdW5jdGlvbiBlKHQpe3ZhciByPWhEdCh0KTtmdW5jdGlvbiBuKGksbyl7dmFyIGE9cigoaT1hdyhpKSkuciwobz1hdyhvKSkucikscz1yKGkuZyxvLmcpLGw9cihpLmIsby5iKSxjPW5kKGkub3BhY2l0eSxvLm9wYWNpdHkpO3JldHVybiBmdW5jdGlvbih1KXtyZXR1cm4gaS5yPWEodSksaS5nPXModSksaS5iPWwodSksaS5vcGFjaXR5PWModSksaSsiIn19cmV0dXJuIG4uZ2FtbWE9ZSxufSgxKTthNW49ZkR0KGFEdCksczVuPWZEdChzRHQpfSk7ZnVuY3Rpb24gZER0KGUsdCl7dHx8KHQ9W10pO3ZhciByPWU/TWF0aC5taW4odC5sZW5ndGgsZS5sZW5ndGgpOjAsbj10LnNsaWNlKCksaTtyZXR1cm4gZnVuY3Rpb24obyl7Zm9yKGk9MDtpPHI7KytpKW5baV09ZVtpXSooMS1vKSt0W2ldKm87cmV0dXJuIG59fWZ1bmN0aW9uIG1EdChlKXtyZXR1cm4gQXJyYXlCdWZmZXIuaXNWaWV3KGUpJiYhKGUgaW5zdGFuY2VvZiBEYXRhVmlldyl9dmFyIGdEdD1NKCgpPT57fSk7ZnVuY3Rpb24gX0R0KGUsdCl7dmFyIHI9dD90Lmxlbmd0aDowLG49ZT9NYXRoLm1pbihyLGUubGVuZ3RoKTowLGk9bmV3IEFycmF5KG4pLG89bmV3IEFycmF5KHIpLGE7Zm9yKGE9MDthPG47KythKWlbYV09bjEoZVthXSx0W2FdKTtmb3IoO2E8cjsrK2Epb1thXT10W2FdO3JldHVybiBmdW5jdGlvbihzKXtmb3IoYT0wO2E8bjsrK2Epb1thXT1pW2FdKHMpO3JldHVybiBvfX12YXIgeUR0PU0oKCk9PntOTygpfSk7ZnVuY3Rpb24gdkR0KGUsdCl7dmFyIHI9bmV3IERhdGU7cmV0dXJuIGU9K2UsdD0rdCxmdW5jdGlvbihuKXtyZXR1cm4gci5zZXRUaW1lKGUqKDEtbikrdCpuKSxyfX12YXIgeER0PU0oKCk9Pnt9KTtmdW5jdGlvbiB5ZihlLHQpe3JldHVybiBlPStlLHQ9K3QsZnVuY3Rpb24ocil7cmV0dXJuIGUqKDEtcikrdCpyfX12YXIgRE89TSgoKT0+e30pO2Z1bmN0aW9uIGJEdChlLHQpe3ZhciByPXt9LG49e30saTsoZT09PW51bGx8fHR5cGVvZiBlIT0ib2JqZWN0IikmJihlPXt9KSwodD09PW51bGx8fHR5cGVvZiB0IT0ib2JqZWN0IikmJih0PXt9KTtmb3IoaSBpbiB0KWkgaW4gZT9yW2ldPW4xKGVbaV0sdFtpXSk6bltpXT10W2ldO3JldHVybiBmdW5jdGlvbihvKXtmb3IoaSBpbiByKW5baV09cltpXShvKTtyZXR1cm4gbn19dmFyIHdEdD1NKCgpPT57Tk8oKX0pO2Z1bmN0aW9uIFU2ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19ZnVuY3Rpb24gcTZlKGUpe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4gZSh0KSsiIn19ZnVuY3Rpb24gU0R0KGUsdCl7dmFyIHI9RHR0Lmxhc3RJbmRleD1OdHQubGFzdEluZGV4PTAsbixpLG8sYT0tMSxzPVtdLGw9W107Zm9yKGU9ZSsiIix0PXQrIiI7KG49RHR0LmV4ZWMoZSkpJiYoaT1OdHQuZXhlYyh0KSk7KShvPWkuaW5kZXgpPnImJihvPXQuc2xpY2UocixvKSxzW2FdP3NbYV0rPW86c1srK2FdPW8pLChuPW5bMF0pPT09KGk9aVswXSk/c1thXT9zW2FdKz1pOnNbKythXT1pOihzWysrYV09bnVsbCxsLnB1c2goe2k6YSx4OnlmKG4saSl9KSkscj1OdHQubGFzdEluZGV4O3JldHVybiByPHQubGVuZ3RoJiYobz10LnNsaWNlKHIpLHNbYV0/c1thXSs9bzpzWysrYV09bykscy5sZW5ndGg8Mj9sWzBdP3E2ZShsWzBdLngpOlU2ZSh0KToodD1sLmxlbmd0aCxmdW5jdGlvbihjKXtmb3IodmFyIHU9MCxoO3U8dDsrK3Upc1soaD1sW3VdKS5pXT1oLngoYyk7cmV0dXJuIHMuam9pbigiIil9KX12YXIgRHR0LE50dCxNRHQ9TSgoKT0+e0RPKCk7RHR0PS9bLStdPyg/OlxkK1wuP1xkKnxcLj9cZCspKD86W2VFXVstK10/XGQrKT8vZyxOdHQ9bmV3IFJlZ0V4cChEdHQuc291cmNlLCJnIil9KTtmdW5jdGlvbiBuMShlLHQpe3ZhciByPXR5cGVvZiB0LG47cmV0dXJuIHQ9PW51bGx8fHI9PT0iYm9vbGVhbiI/bHcodCk6KHI9PT0ibnVtYmVyIj95ZjpyPT09InN0cmluZyI/KG49UGcodCkpPyh0PW4sUnR0KTpTRHQ6dCBpbnN0YW5jZW9mIFBnP1J0dDp0IGluc3RhbmNlb2YgRGF0ZT92RHQ6bUR0KHQpP2REdDpBcnJheS5pc0FycmF5KHQpP19EdDp0eXBlb2YgdC52YWx1ZU9mIT0iZnVuY3Rpb24iJiZ0eXBlb2YgdC50b1N0cmluZyE9ImZ1bmN0aW9uInx8aXNOYU4odCk/YkR0OnlmKShlLHQpfXZhciBOTz1NKCgpPT57c3coKTtwRHQoKTt5RHQoKTt4RHQoKTtETygpO3dEdCgpO01EdCgpO0x0dCgpO2dEdCgpfSk7ZnVuY3Rpb24gT3R0KGUsdCl7cmV0dXJuIGU9K2UsdD0rdCxmdW5jdGlvbihyKXtyZXR1cm4gTWF0aC5yb3VuZChlKigxLXIpK3Qqcil9fXZhciBFRHQ9TSgoKT0+e30pO2Z1bmN0aW9uIFREdChlKXtyZXR1cm4gZnVuY3Rpb24gdChyKXtyPStyO2Z1bmN0aW9uIG4oaSxvKXt2YXIgYT1lKChpPSRhKGkpKS5oLChvPSRhKG8pKS5oKSxzPW5kKGkucyxvLnMpLGw9bmQoaS5sLG8ubCksYz1uZChpLm9wYWNpdHksby5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24odSl7cmV0dXJuIGkuaD1hKHUpLGkucz1zKHUpLGkubD1sKE1hdGgucG93KHUscikpLGkub3BhY2l0eT1jKHUpLGkrIiJ9fXJldHVybiBuLmdhbW1hPXQsbn0oMSl9dmFyIEc2ZSxjdyxDRHQ9TSgoKT0+e3N3KCk7a3R0KCk7RzZlPVREdCh1RHQpLGN3PVREdChuZCl9KTt2YXIgdXc9TSgoKT0+e05PKCk7RE8oKTtFRHQoKTtDRHQoKX0pO2Z1bmN0aW9uIGh3KGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX12YXIgT089TSgoKT0+e30pO2Z1bmN0aW9uIHpPKGUpe3JldHVybitlfXZhciB6dHQ9TSgoKT0+e30pO2Z1bmN0aW9uIGZDKGUsdCl7cmV0dXJuKHQtPWU9K2UpP2Z1bmN0aW9uKHIpe3JldHVybihyLWUpL3R9Omh3KHQpfWZ1bmN0aW9uIFc2ZShlKXtyZXR1cm4gZnVuY3Rpb24odCxyKXt2YXIgbj1lKHQ9K3Qscj0rcik7cmV0dXJuIGZ1bmN0aW9uKGkpe3JldHVybiBpPD10PzA6aT49cj8xOm4oaSl9fX1mdW5jdGlvbiBZNmUoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7dmFyIG49ZSh0PSt0LHI9K3IpO3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4gaTw9MD90Omk+PTE/cjpuKGkpfX19ZnVuY3Rpb24gajZlKGUsdCxyLG4pe3ZhciBpPWVbMF0sbz1lWzFdLGE9dFswXSxzPXRbMV07cmV0dXJuIG88aT8oaT1yKG8saSksYT1uKHMsYSkpOihpPXIoaSxvKSxhPW4oYSxzKSksZnVuY3Rpb24obCl7cmV0dXJuIGEoaShsKSl9fWZ1bmN0aW9uIFg2ZShlLHQscixuKXt2YXIgaT1NYXRoLm1pbihlLmxlbmd0aCx0Lmxlbmd0aCktMSxvPW5ldyBBcnJheShpKSxhPW5ldyBBcnJheShpKSxzPS0xO2ZvcihlW2ldPGVbMF0mJihlPWUuc2xpY2UoKS5yZXZlcnNlKCksdD10LnNsaWNlKCkucmV2ZXJzZSgpKTsrK3M8aTspb1tzXT1yKGVbc10sZVtzKzFdKSxhW3NdPW4odFtzXSx0W3MrMV0pO3JldHVybiBmdW5jdGlvbihsKXt2YXIgYz1kZihlLGwsMSxpKS0xO3JldHVybiBhW2NdKG9bY10obCkpfX1mdW5jdGlvbiBJZyhlLHQpe3JldHVybiB0LmRvbWFpbihlLmRvbWFpbigpKS5yYW5nZShlLnJhbmdlKCkpLmludGVycG9sYXRlKGUuaW50ZXJwb2xhdGUoKSkuY2xhbXAoZS5jbGFtcCgpKX1mdW5jdGlvbiBpZChlLHQpe3ZhciByPUFEdCxuPUFEdCxpPW4xLG89ITEsYSxzLGw7ZnVuY3Rpb24gYygpe3JldHVybiBhPU1hdGgubWluKHIubGVuZ3RoLG4ubGVuZ3RoKT4yP1g2ZTpqNmUscz1sPW51bGwsdX1mdW5jdGlvbiB1KGgpe3JldHVybihzfHwocz1hKHIsbixvP1c2ZShlKTplLGkpKSkoK2gpfXJldHVybiB1LmludmVydD1mdW5jdGlvbihoKXtyZXR1cm4obHx8KGw9YShuLHIsZkMsbz9ZNmUodCk6dCkpKSgraCl9LHUuZG9tYWluPWZ1bmN0aW9uKGgpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPXJ3LmNhbGwoaCx6TyksYygpKTpyLnNsaWNlKCl9LHUucmFuZ2U9ZnVuY3Rpb24oaCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49cGwuY2FsbChoKSxjKCkpOm4uc2xpY2UoKX0sdS5yYW5nZVJvdW5kPWZ1bmN0aW9uKGgpe3JldHVybiBuPXBsLmNhbGwoaCksaT1PdHQsYygpfSx1LmNsYW1wPWZ1bmN0aW9uKGgpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPSEhaCxjKCkpOm99LHUuaW50ZXJwb2xhdGU9ZnVuY3Rpb24oaCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9aCxjKCkpOml9LGMoKX12YXIgQUR0LHBDPU0oKCk9PnttZigpO3V3KCk7Q2coKTtPTygpO3p0dCgpO0FEdD1bMCwxXX0pO2Z1bmN0aW9uIFBEdChlKXtyZXR1cm4gTWF0aC5hYnMoZT1NYXRoLnJvdW5kKGUpKT49MWUyMT9lLnRvTG9jYWxlU3RyaW5nKCJlbiIpLnJlcGxhY2UoLywvZywiIik6ZS50b1N0cmluZygxMCl9ZnVuY3Rpb24gaTEoZSx0KXtpZigocj0oZT10P2UudG9FeHBvbmVudGlhbCh0LTEpOmUudG9FeHBvbmVudGlhbCgpKS5pbmRleE9mKCJlIikpPDApcmV0dXJuIG51bGw7dmFyIHIsbj1lLnNsaWNlKDAscik7cmV0dXJuW24ubGVuZ3RoPjE/blswXStuLnNsaWNlKDIpOm4sK2Uuc2xpY2UocisxKV19dmFyIGRDPU0oKCk9Pnt9KTtmdW5jdGlvbiB2ZihlKXtyZXR1cm4gZT1pMShNYXRoLmFicyhlKSksZT9lWzFdOk5hTn12YXIgbUM9TSgoKT0+e2RDKCl9KTtmdW5jdGlvbiBJRHQoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocixuKXtmb3IodmFyIGk9ci5sZW5ndGgsbz1bXSxhPTAscz1lWzBdLGw9MDtpPjAmJnM+MCYmKGwrcysxPm4mJihzPU1hdGgubWF4KDEsbi1sKSksby5wdXNoKHIuc3Vic3RyaW5nKGktPXMsaStzKSksISgobCs9cysxKT5uKSk7KXM9ZVthPShhKzEpJWUubGVuZ3RoXTtyZXR1cm4gby5yZXZlcnNlKCkuam9pbih0KX19dmFyIExEdD1NKCgpPT57fSk7ZnVuY3Rpb24ga0R0KGUpe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4gdC5yZXBsYWNlKC9bMC05XS9nLGZ1bmN0aW9uKHIpe3JldHVybiBlWytyXX0pfX12YXIgUkR0PU0oKCk9Pnt9KTtmdW5jdGlvbiBMZyhlKXtpZighKHQ9JDZlLmV4ZWMoZSkpKXRocm93IG5ldyBFcnJvcigiaW52YWxpZCBmb3JtYXQ6ICIrZSk7dmFyIHQ7cmV0dXJuIG5ldyBGTyh7ZmlsbDp0WzFdLGFsaWduOnRbMl0sc2lnbjp0WzNdLHN5bWJvbDp0WzRdLHplcm86dFs1XSx3aWR0aDp0WzZdLGNvbW1hOnRbN10scHJlY2lzaW9uOnRbOF0mJnRbOF0uc2xpY2UoMSksdHJpbTp0WzldLHR5cGU6dFsxMF19KX1mdW5jdGlvbiBGTyhlKXt0aGlzLmZpbGw9ZS5maWxsPT09dm9pZCAwPyIgIjplLmZpbGwrIiIsdGhpcy5hbGlnbj1lLmFsaWduPT09dm9pZCAwPyI+IjplLmFsaWduKyIiLHRoaXMuc2lnbj1lLnNpZ249PT12b2lkIDA/Ii0iOmUuc2lnbisiIix0aGlzLnN5bWJvbD1lLnN5bWJvbD09PXZvaWQgMD8iIjplLnN5bWJvbCsiIix0aGlzLnplcm89ISFlLnplcm8sdGhpcy53aWR0aD1lLndpZHRoPT09dm9pZCAwP3ZvaWQgMDorZS53aWR0aCx0aGlzLmNvbW1hPSEhZS5jb21tYSx0aGlzLnByZWNpc2lvbj1lLnByZWNpc2lvbj09PXZvaWQgMD92b2lkIDA6K2UucHJlY2lzaW9uLHRoaXMudHJpbT0hIWUudHJpbSx0aGlzLnR5cGU9ZS50eXBlPT09dm9pZCAwPyIiOmUudHlwZSsiIn12YXIgJDZlLEZ0dD1NKCgpPT57JDZlPS9eKD86KC4pPyhbPD49Xl0pKT8oWytcLSggXSk/KFskI10pPygwKT8oXGQrKT8oLCk/KFwuXGQrKT8ofik/KFthLXolXSk/JC9pO0xnLnByb3RvdHlwZT1GTy5wcm90b3R5cGU7Rk8ucHJvdG90eXBlLnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZmlsbCt0aGlzLmFsaWduK3RoaXMuc2lnbit0aGlzLnN5bWJvbCsodGhpcy56ZXJvPyIwIjoiIikrKHRoaXMud2lkdGg9PT12b2lkIDA/IiI6TWF0aC5tYXgoMSx0aGlzLndpZHRofDApKSsodGhpcy5jb21tYT8iLCI6IiIpKyh0aGlzLnByZWNpc2lvbj09PXZvaWQgMD8iIjoiLiIrTWF0aC5tYXgoMCx0aGlzLnByZWNpc2lvbnwwKSkrKHRoaXMudHJpbT8ifiI6IiIpK3RoaXMudHlwZX19KTtmdW5jdGlvbiBORHQoZSl7dDpmb3IodmFyIHQ9ZS5sZW5ndGgscj0xLG49LTEsaTtyPHQ7KytyKXN3aXRjaChlW3JdKXtjYXNlIi4iOm49aT1yO2JyZWFrO2Nhc2UiMCI6bj09PTAmJihuPXIpLGk9cjticmVhaztkZWZhdWx0OmlmKCErZVtyXSlicmVhayB0O24+MCYmKG49MCk7YnJlYWt9cmV0dXJuIG4+MD9lLnNsaWNlKDAsbikrZS5zbGljZShpKzEpOmV9dmFyIEREdD1NKCgpPT57fSk7ZnVuY3Rpb24gT0R0KGUsdCl7dmFyIHI9aTEoZSx0KTtpZighcilyZXR1cm4gZSsiIjt2YXIgbj1yWzBdLGk9clsxXSxvPWktKEJ0dD1NYXRoLm1heCgtOCxNYXRoLm1pbig4LE1hdGguZmxvb3IoaS8zKSkpKjMpKzEsYT1uLmxlbmd0aDtyZXR1cm4gbz09PWE/bjpvPmE/bituZXcgQXJyYXkoby1hKzEpLmpvaW4oIjAiKTpvPjA/bi5zbGljZSgwLG8pKyIuIituLnNsaWNlKG8pOiIwLiIrbmV3IEFycmF5KDEtbykuam9pbigiMCIpK2kxKGUsTWF0aC5tYXgoMCx0K28tMSkpWzBdfXZhciBCdHQsSHR0PU0oKCk9PntkQygpfSk7ZnVuY3Rpb24gVnR0KGUsdCl7dmFyIHI9aTEoZSx0KTtpZighcilyZXR1cm4gZSsiIjt2YXIgbj1yWzBdLGk9clsxXTtyZXR1cm4gaTwwPyIwLiIrbmV3IEFycmF5KC1pKS5qb2luKCIwIikrbjpuLmxlbmd0aD5pKzE/bi5zbGljZSgwLGkrMSkrIi4iK24uc2xpY2UoaSsxKTpuK25ldyBBcnJheShpLW4ubGVuZ3RoKzIpLmpvaW4oIjAiKX12YXIgekR0PU0oKCk9PntkQygpfSk7dmFyIFV0dCxGRHQ9TSgoKT0+e2RDKCk7SHR0KCk7ekR0KCk7VXR0PXsiJSI6ZnVuY3Rpb24oZSx0KXtyZXR1cm4oZSoxMDApLnRvRml4ZWQodCl9LGI6ZnVuY3Rpb24oZSl7cmV0dXJuIE1hdGgucm91bmQoZSkudG9TdHJpbmcoMil9LGM6ZnVuY3Rpb24oZSl7cmV0dXJuIGUrIiJ9LGQ6UER0LGU6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gZS50b0V4cG9uZW50aWFsKHQpfSxmOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIGUudG9GaXhlZCh0KX0sZzpmdW5jdGlvbihlLHQpe3JldHVybiBlLnRvUHJlY2lzaW9uKHQpfSxvOmZ1bmN0aW9uKGUpe3JldHVybiBNYXRoLnJvdW5kKGUpLnRvU3RyaW5nKDgpfSxwOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIFZ0dChlKjEwMCx0KX0scjpWdHQsczpPRHQsWDpmdW5jdGlvbihlKXtyZXR1cm4gTWF0aC5yb3VuZChlKS50b1N0cmluZygxNikudG9VcHBlckNhc2UoKX0seDpmdW5jdGlvbihlKXtyZXR1cm4gTWF0aC5yb3VuZChlKS50b1N0cmluZygxNil9fX0pO2Z1bmN0aW9uIHF0dChlKXtyZXR1cm4gZX12YXIgQkR0PU0oKCk9Pnt9KTtmdW5jdGlvbiBVRHQoZSl7dmFyIHQ9ZS5ncm91cGluZz09PXZvaWQgMHx8ZS50aG91c2FuZHM9PT12b2lkIDA/cXR0OklEdChIRHQuY2FsbChlLmdyb3VwaW5nLE51bWJlciksZS50aG91c2FuZHMrIiIpLHI9ZS5jdXJyZW5jeT09PXZvaWQgMD8iIjplLmN1cnJlbmN5WzBdKyIiLG49ZS5jdXJyZW5jeT09PXZvaWQgMD8iIjplLmN1cnJlbmN5WzFdKyIiLGk9ZS5kZWNpbWFsPT09dm9pZCAwPyIuIjplLmRlY2ltYWwrIiIsbz1lLm51bWVyYWxzPT09dm9pZCAwP3F0dDprRHQoSER0LmNhbGwoZS5udW1lcmFscyxTdHJpbmcpKSxhPWUucGVyY2VudD09PXZvaWQgMD8iJSI6ZS5wZXJjZW50KyIiLHM9ZS5taW51cz09PXZvaWQgMD8iLSI6ZS5taW51cysiIixsPWUubmFuPT09dm9pZCAwPyJOYU4iOmUubmFuKyIiO2Z1bmN0aW9uIGMoaCl7aD1MZyhoKTt2YXIgZj1oLmZpbGwscD1oLmFsaWduLGQ9aC5zaWduLGc9aC5zeW1ib2wsXz1oLnplcm8seT1oLndpZHRoLHg9aC5jb21tYSxiPWgucHJlY2lzaW9uLFM9aC50cmltLEM9aC50eXBlO0M9PT0ibiI/KHg9ITAsQz0iZyIpOlV0dFtDXXx8KGI9PT12b2lkIDAmJihiPTEyKSxTPSEwLEM9ImciKSwoX3x8Zj09PSIwIiYmcD09PSI9IikmJihfPSEwLGY9IjAiLHA9Ij0iKTt2YXIgUD1nPT09IiQiP3I6Zz09PSIjIiYmL1tib3hYXS8udGVzdChDKT8iMCIrQy50b0xvd2VyQ2FzZSgpOiIiLGs9Zz09PSIkIj9uOi9bJXBdLy50ZXN0KEMpP2E6IiIsTz1VdHRbQ10sRD0vW2RlZmdwcnMlXS8udGVzdChDKTtiPWI9PT12b2lkIDA/NjovW2dwcnNdLy50ZXN0KEMpP01hdGgubWF4KDEsTWF0aC5taW4oMjEsYikpOk1hdGgubWF4KDAsTWF0aC5taW4oMjAsYikpO2Z1bmN0aW9uIEIoSSl7dmFyIEw9UCxSPWssRix6LFU7aWYoQz09PSJjIilSPU8oSSkrUixJPSIiO2Vsc2V7ST0rSTt2YXIgVz1JPDB8fDEvSTwwO2lmKEk9aXNOYU4oSSk/bDpPKE1hdGguYWJzKEkpLGIpLFMmJihJPU5EdChJKSksVyYmK0k9PTAmJmQhPT0iKyImJihXPSExKSxMPShXP2Q9PT0iKCI/ZDpzOmQ9PT0iLSJ8fGQ9PT0iKCI/IiI6ZCkrTCxSPShDPT09InMiP1ZEdFs4K0J0dC8zXToiIikrUisoVyYmZD09PSIoIj8iKSI6IiIpLEQpe2ZvcihGPS0xLHo9SS5sZW5ndGg7KytGPHo7KWlmKFU9SS5jaGFyQ29kZUF0KEYpLDQ4PlV8fFU+NTcpe1I9KFU9PT00Nj9pK0kuc2xpY2UoRisxKTpJLnNsaWNlKEYpKStSLEk9SS5zbGljZSgwLEYpO2JyZWFrfX19eCYmIV8mJihJPXQoSSwxLzApKTt2YXIgWj1MLmxlbmd0aCtJLmxlbmd0aCtSLmxlbmd0aCxydD1aPHk/bmV3IEFycmF5KHktWisxKS5qb2luKGYpOiIiO3N3aXRjaCh4JiZfJiYoST10KHJ0K0kscnQubGVuZ3RoP3ktUi5sZW5ndGg6MS8wKSxydD0iIikscCl7Y2FzZSI8IjpJPUwrSStSK3J0O2JyZWFrO2Nhc2UiPSI6ST1MK3J0K0krUjticmVhaztjYXNlIl4iOkk9cnQuc2xpY2UoMCxaPXJ0Lmxlbmd0aD4+MSkrTCtJK1IrcnQuc2xpY2UoWik7YnJlYWs7ZGVmYXVsdDpJPXJ0K0wrSStSO2JyZWFrfXJldHVybiBvKEkpfXJldHVybiBCLnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIGgrIiJ9LEJ9ZnVuY3Rpb24gdShoLGYpe3ZhciBwPWMoKGg9TGcoaCksaC50eXBlPSJmIixoKSksZD1NYXRoLm1heCgtOCxNYXRoLm1pbig4LE1hdGguZmxvb3IodmYoZikvMykpKSozLGc9TWF0aC5wb3coMTAsLWQpLF89VkR0WzgrZC8zXTtyZXR1cm4gZnVuY3Rpb24oeSl7cmV0dXJuIHAoZyp5KStffX1yZXR1cm57Zm9ybWF0OmMsZm9ybWF0UHJlZml4OnV9fXZhciBIRHQsVkR0LHFEdD1NKCgpPT57bUMoKTtMRHQoKTtSRHQoKTtGdHQoKTtERHQoKTtGRHQoKTtIdHQoKTtCRHQoKTtIRHQ9QXJyYXkucHJvdG90eXBlLm1hcCxWRHQ9WyJ5IiwieiIsImEiLCJmIiwicCIsIm4iLCJceEI1IiwibSIsIiIsImsiLCJNIiwiRyIsIlQiLCJQIiwiRSIsIloiLCJZIl19KTtmdW5jdGlvbiBHdHQoZSl7cmV0dXJuIEJPPVVEdChlKSxmdz1CTy5mb3JtYXQsSE89Qk8uZm9ybWF0UHJlZml4LEJPfXZhciBCTyxmdyxITyxHRHQ9TSgoKT0+e3FEdCgpO0d0dCh7ZGVjaW1hbDoiLiIsdGhvdXNhbmRzOiIsIixncm91cGluZzpbM10sY3VycmVuY3k6WyIkIiwiIl0sbWludXM6Ii0ifSl9KTtmdW5jdGlvbiBXdHQoZSl7cmV0dXJuIE1hdGgubWF4KDAsLXZmKE1hdGguYWJzKGUpKSl9dmFyIFdEdD1NKCgpPT57bUMoKX0pO2Z1bmN0aW9uIFl0dChlLHQpe3JldHVybiBNYXRoLm1heCgwLE1hdGgubWF4KC04LE1hdGgubWluKDgsTWF0aC5mbG9vcih2Zih0KS8zKSkpKjMtdmYoTWF0aC5hYnMoZSkpKX12YXIgWUR0PU0oKCk9PnttQygpfSk7ZnVuY3Rpb24ganR0KGUsdCl7cmV0dXJuIGU9TWF0aC5hYnMoZSksdD1NYXRoLmFicyh0KS1lLE1hdGgubWF4KDAsdmYodCktdmYoZSkpKzF9dmFyIGpEdD1NKCgpPT57bUMoKX0pO3ZhciBYdHQ9TSgoKT0+e0dEdCgpO0Z0dCgpO1dEdCgpO1lEdCgpO2pEdCgpfSk7ZnVuY3Rpb24gWER0KGUsdCxyKXt2YXIgbj1lWzBdLGk9ZVtlLmxlbmd0aC0xXSxvPVp5KG4saSx0PT1udWxsPzEwOnQpLGE7c3dpdGNoKHI9TGcocj09bnVsbD8iLGYiOnIpLHIudHlwZSl7Y2FzZSJzIjp7dmFyIHM9TWF0aC5tYXgoTWF0aC5hYnMobiksTWF0aC5hYnMoaSkpO3JldHVybiByLnByZWNpc2lvbj09bnVsbCYmIWlzTmFOKGE9WXR0KG8scykpJiYoci5wcmVjaXNpb249YSksSE8ocixzKX1jYXNlIiI6Y2FzZSJlIjpjYXNlImciOmNhc2UicCI6Y2FzZSJyIjp7ci5wcmVjaXNpb249PW51bGwmJiFpc05hTihhPWp0dChvLE1hdGgubWF4KE1hdGguYWJzKG4pLE1hdGguYWJzKGkpKSkpJiYoci5wcmVjaXNpb249YS0oci50eXBlPT09ImUiKSk7YnJlYWt9Y2FzZSJmIjpjYXNlIiUiOntyLnByZWNpc2lvbj09bnVsbCYmIWlzTmFOKGE9V3R0KG8pKSYmKHIucHJlY2lzaW9uPWEtKHIudHlwZT09PSIlIikqMik7YnJlYWt9fXJldHVybiBmdyhyKX12YXIgJER0PU0oKCk9PnttZigpO1h0dCgpfSk7ZnVuY3Rpb24gb2QoZSl7dmFyIHQ9ZS5kb21haW47cmV0dXJuIGUudGlja3M9ZnVuY3Rpb24ocil7dmFyIG49dCgpO3JldHVybiBhQyhuWzBdLG5bbi5sZW5ndGgtMV0scj09bnVsbD8xMDpyKX0sZS50aWNrRm9ybWF0PWZ1bmN0aW9uKHIsbil7cmV0dXJuIFhEdCh0KCkscixuKX0sZS5uaWNlPWZ1bmN0aW9uKHIpe3I9PW51bGwmJihyPTEwKTt2YXIgbj10KCksaT0wLG89bi5sZW5ndGgtMSxhPW5baV0scz1uW29dLGw7cmV0dXJuIHM8YSYmKGw9YSxhPXMscz1sLGw9aSxpPW8sbz1sKSxsPWV3KGEscyxyKSxsPjA/KGE9TWF0aC5mbG9vcihhL2wpKmwscz1NYXRoLmNlaWwocy9sKSpsLGw9ZXcoYSxzLHIpKTpsPDAmJihhPU1hdGguY2VpbChhKmwpL2wscz1NYXRoLmZsb29yKHMqbCkvbCxsPWV3KGEscyxyKSksbD4wPyhuW2ldPU1hdGguZmxvb3IoYS9sKSpsLG5bb109TWF0aC5jZWlsKHMvbCkqbCx0KG4pKTpsPDAmJihuW2ldPU1hdGguY2VpbChhKmwpL2wsbltvXT1NYXRoLmZsb29yKHMqbCkvbCx0KG4pKSxlfSxlfWZ1bmN0aW9uIFZPKCl7dmFyIGU9aWQoZkMseWYpO3JldHVybiBlLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gSWcoZSxWTygpKX0sb2QoZSl9dmFyIHB3PU0oKCk9PnttZigpO3V3KCk7cEMoKTskRHQoKX0pO2Z1bmN0aW9uIFVPKCl7dmFyIGU9WzAsMV07ZnVuY3Rpb24gdChyKXtyZXR1cm4rcn1yZXR1cm4gdC5pbnZlcnQ9dCx0LmRvbWFpbj10LnJhbmdlPWZ1bmN0aW9uKHIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXJ3LmNhbGwocix6TyksdCk6ZS5zbGljZSgpfSx0LmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gVU8oKS5kb21haW4oZSl9LG9kKHQpfXZhciBLRHQ9TSgoKT0+e0NnKCk7cHcoKTt6dHQoKX0pO2Z1bmN0aW9uIHFPKGUsdCl7ZT1lLnNsaWNlKCk7dmFyIHI9MCxuPWUubGVuZ3RoLTEsaT1lW3JdLG89ZVtuXSxhO3JldHVybiBvPGkmJihhPXIscj1uLG49YSxhPWksaT1vLG89YSksZVtyXT10LmZsb29yKGkpLGVbbl09dC5jZWlsKG8pLGV9dmFyICR0dD1NKCgpPT57fSk7ZnVuY3Rpb24gSzZlKGUsdCl7cmV0dXJuKHQ9TWF0aC5sb2codC9lKSk/ZnVuY3Rpb24ocil7cmV0dXJuIE1hdGgubG9nKHIvZSkvdH06aHcodCl9ZnVuY3Rpb24gWjZlKGUsdCl7cmV0dXJuIGU8MD9mdW5jdGlvbihyKXtyZXR1cm4tTWF0aC5wb3coLXQscikqTWF0aC5wb3coLWUsMS1yKX06ZnVuY3Rpb24ocil7cmV0dXJuIE1hdGgucG93KHQscikqTWF0aC5wb3coZSwxLXIpfX1mdW5jdGlvbiBKNmUoZSl7cmV0dXJuIGlzRmluaXRlKGUpPysoIjFlIitlKTplPDA/MDplfWZ1bmN0aW9uIFpEdChlKXtyZXR1cm4gZT09PTEwP0o2ZTplPT09TWF0aC5FP01hdGguZXhwOmZ1bmN0aW9uKHQpe3JldHVybiBNYXRoLnBvdyhlLHQpfX1mdW5jdGlvbiBKRHQoZSl7cmV0dXJuIGU9PT1NYXRoLkU/TWF0aC5sb2c6ZT09PTEwJiZNYXRoLmxvZzEwfHxlPT09MiYmTWF0aC5sb2cyfHwoZT1NYXRoLmxvZyhlKSxmdW5jdGlvbih0KXtyZXR1cm4gTWF0aC5sb2codCkvZX0pfWZ1bmN0aW9uIFFEdChlKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuLWUoLXQpfX1mdW5jdGlvbiBHTygpe3ZhciBlPWlkKEs2ZSxaNmUpLmRvbWFpbihbMSwxMF0pLHQ9ZS5kb21haW4scj0xMCxuPUpEdCgxMCksaT1aRHQoMTApO2Z1bmN0aW9uIG8oKXtyZXR1cm4gbj1KRHQociksaT1aRHQociksdCgpWzBdPDAmJihuPVFEdChuKSxpPVFEdChpKSksZX1yZXR1cm4gZS5iYXNlPWZ1bmN0aW9uKGEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPSthLG8oKSk6cn0sZS5kb21haW49ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQoYSksbygpKTp0KCl9LGUudGlja3M9ZnVuY3Rpb24oYSl7dmFyIHM9dCgpLGw9c1swXSxjPXNbcy5sZW5ndGgtMV0sdTsodT1jPGwpJiYoaD1sLGw9YyxjPWgpO3ZhciBoPW4obCksZj1uKGMpLHAsZCxnLF89YT09bnVsbD8xMDorYSx5PVtdO2lmKCEociUxKSYmZi1oPF8pe2lmKGg9TWF0aC5yb3VuZChoKS0xLGY9TWF0aC5yb3VuZChmKSsxLGw+MCl7Zm9yKDtoPGY7KytoKWZvcihkPTEscD1pKGgpO2Q8cjsrK2QpaWYoZz1wKmQsIShnPGwpKXtpZihnPmMpYnJlYWs7eS5wdXNoKGcpfX1lbHNlIGZvcig7aDxmOysraClmb3IoZD1yLTEscD1pKGgpO2Q+PTE7LS1kKWlmKGc9cCpkLCEoZzxsKSl7aWYoZz5jKWJyZWFrO3kucHVzaChnKX19ZWxzZSB5PWFDKGgsZixNYXRoLm1pbihmLWgsXykpLm1hcChpKTtyZXR1cm4gdT95LnJldmVyc2UoKTp5fSxlLnRpY2tGb3JtYXQ9ZnVuY3Rpb24oYSxzKXtpZihzPT1udWxsJiYocz1yPT09MTA/Ii4wZSI6IiwiKSx0eXBlb2YgcyE9ImZ1bmN0aW9uIiYmKHM9ZncocykpLGE9PT0xLzApcmV0dXJuIHM7YT09bnVsbCYmKGE9MTApO3ZhciBsPU1hdGgubWF4KDEsciphL2UudGlja3MoKS5sZW5ndGgpO3JldHVybiBmdW5jdGlvbihjKXt2YXIgdT1jL2koTWF0aC5yb3VuZChuKGMpKSk7cmV0dXJuIHUqcjxyLS41JiYodSo9ciksdTw9bD9zKGMpOiIifX0sZS5uaWNlPWZ1bmN0aW9uKCl7cmV0dXJuIHQocU8odCgpLHtmbG9vcjpmdW5jdGlvbihhKXtyZXR1cm4gaShNYXRoLmZsb29yKG4oYSkpKX0sY2VpbDpmdW5jdGlvbihhKXtyZXR1cm4gaShNYXRoLmNlaWwobihhKSkpfX0pKX0sZS5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIElnKGUsR08oKS5iYXNlKHIpKX0sZX12YXIgdE90PU0oKCk9PnttZigpO1h0dCgpO09PKCk7JHR0KCk7cEMoKX0pO2Z1bmN0aW9uIGR3KGUsdCl7cmV0dXJuIGU8MD8tTWF0aC5wb3coLWUsdCk6TWF0aC5wb3coZSx0KX1mdW5jdGlvbiBnQygpe3ZhciBlPTEsdD1pZChuLGkpLHI9dC5kb21haW47ZnVuY3Rpb24gbihvLGEpe3JldHVybihhPWR3KGEsZSktKG89ZHcobyxlKSkpP2Z1bmN0aW9uKHMpe3JldHVybihkdyhzLGUpLW8pL2F9Omh3KGEpfWZ1bmN0aW9uIGkobyxhKXtyZXR1cm4gYT1kdyhhLGUpLShvPWR3KG8sZSkpLGZ1bmN0aW9uKHMpe3JldHVybiBkdyhvK2EqcywxL2UpfX1yZXR1cm4gdC5leHBvbmVudD1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT0rbyxyKHIoKSkpOmV9LHQuY29weT1mdW5jdGlvbigpe3JldHVybiBJZyh0LGdDKCkuZXhwb25lbnQoZSkpfSxvZCh0KX1mdW5jdGlvbiBlT3QoKXtyZXR1cm4gZ0MoKS5leHBvbmVudCguNSl9dmFyIHJPdD1NKCgpPT57T08oKTtwdygpO3BDKCl9KTtmdW5jdGlvbiBXTygpe3ZhciBlPVtdLHQ9W10scj1bXTtmdW5jdGlvbiBuKCl7dmFyIG89MCxhPU1hdGgubWF4KDEsdC5sZW5ndGgpO2ZvcihyPW5ldyBBcnJheShhLTEpOysrbzxhOylyW28tMV09c0MoZSxvL2EpO3JldHVybiBpfWZ1bmN0aW9uIGkobyl7aWYoIWlzTmFOKG89K28pKXJldHVybiB0W2RmKHIsbyldfXJldHVybiBpLmludmVydEV4dGVudD1mdW5jdGlvbihvKXt2YXIgYT10LmluZGV4T2Yobyk7cmV0dXJuIGE8MD9bTmFOLE5hTl06W2E+MD9yW2EtMV06ZVswXSxhPHIubGVuZ3RoP3JbYV06ZVtlLmxlbmd0aC0xXV19LGkuZG9tYWluPWZ1bmN0aW9uKG8pe2lmKCFhcmd1bWVudHMubGVuZ3RoKXJldHVybiBlLnNsaWNlKCk7ZT1bXTtmb3IodmFyIGE9MCxzPW8ubGVuZ3RoLGw7YTxzOysrYSlsPW9bYV0sbCE9bnVsbCYmIWlzTmFOKGw9K2wpJiZlLnB1c2gobCk7cmV0dXJuIGUuc29ydChwZiksbigpfSxpLnJhbmdlPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXBsLmNhbGwobyksbigpKTp0LnNsaWNlKCl9LGkucXVhbnRpbGVzPWZ1bmN0aW9uKCl7cmV0dXJuIHIuc2xpY2UoKX0saS5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIFdPKCkuZG9tYWluKGUpLnJhbmdlKHQpfSxpfXZhciBuT3Q9TSgoKT0+e21mKCk7Q2coKX0pO2Z1bmN0aW9uIFlPKCl7dmFyIGU9MCx0PTEscj0xLG49Wy41XSxpPVswLDFdO2Z1bmN0aW9uIG8ocyl7aWYoczw9cylyZXR1cm4gaVtkZihuLHMsMCxyKV19ZnVuY3Rpb24gYSgpe3ZhciBzPS0xO2ZvcihuPW5ldyBBcnJheShyKTsrK3M8cjspbltzXT0oKHMrMSkqdC0ocy1yKSplKS8ocisxKTtyZXR1cm4gb31yZXR1cm4gby5kb21haW49ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9K3NbMF0sdD0rc1sxXSxhKCkpOltlLHRdfSxvLnJhbmdlPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPShpPXBsLmNhbGwocykpLmxlbmd0aC0xLGEoKSk6aS5zbGljZSgpfSxvLmludmVydEV4dGVudD1mdW5jdGlvbihzKXt2YXIgbD1pLmluZGV4T2Yocyk7cmV0dXJuIGw8MD9bTmFOLE5hTl06bDwxP1tlLG5bMF1dOmw+PXI/W25bci0xXSx0XTpbbltsLTFdLG5bbF1dfSxvLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gWU8oKS5kb21haW4oW2UsdF0pLnJhbmdlKGkpfSxvZChvKX12YXIgaU90PU0oKCk9PnttZigpO0NnKCk7cHcoKX0pO2Z1bmN0aW9uIGpPKCl7dmFyIGU9Wy41XSx0PVswLDFdLHI9MTtmdW5jdGlvbiBuKGkpe2lmKGk8PWkpcmV0dXJuIHRbZGYoZSxpLDAscildfXJldHVybiBuLmRvbWFpbj1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT1wbC5jYWxsKGkpLHI9TWF0aC5taW4oZS5sZW5ndGgsdC5sZW5ndGgtMSksbik6ZS5zbGljZSgpfSxuLnJhbmdlPWZ1bmN0aW9uKGkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXBsLmNhbGwoaSkscj1NYXRoLm1pbihlLmxlbmd0aCx0Lmxlbmd0aC0xKSxuKTp0LnNsaWNlKCl9LG4uaW52ZXJ0RXh0ZW50PWZ1bmN0aW9uKGkpe3ZhciBvPXQuaW5kZXhPZihpKTtyZXR1cm5bZVtvLTFdLGVbb11dfSxuLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gak8oKS5kb21haW4oZSkucmFuZ2UodCl9LG59dmFyIG9PdD1NKCgpPT57bWYoKTtDZygpfSk7ZnVuY3Rpb24gTnIoZSx0LHIsbil7ZnVuY3Rpb24gaShvKXtyZXR1cm4gZShvPWFyZ3VtZW50cy5sZW5ndGg9PT0wP25ldyBEYXRlOm5ldyBEYXRlKCtvKSksb31yZXR1cm4gaS5mbG9vcj1mdW5jdGlvbihvKXtyZXR1cm4gZShvPW5ldyBEYXRlKCtvKSksb30saS5jZWlsPWZ1bmN0aW9uKG8pe3JldHVybiBlKG89bmV3IERhdGUoby0xKSksdChvLDEpLGUobyksb30saS5yb3VuZD1mdW5jdGlvbihvKXt2YXIgYT1pKG8pLHM9aS5jZWlsKG8pO3JldHVybiBvLWE8cy1vP2E6c30saS5vZmZzZXQ9ZnVuY3Rpb24obyxhKXtyZXR1cm4gdChvPW5ldyBEYXRlKCtvKSxhPT1udWxsPzE6TWF0aC5mbG9vcihhKSksb30saS5yYW5nZT1mdW5jdGlvbihvLGEscyl7dmFyIGw9W10sYztpZihvPWkuY2VpbChvKSxzPXM9PW51bGw/MTpNYXRoLmZsb29yKHMpLCEobzxhKXx8IShzPjApKXJldHVybiBsO2RvIGwucHVzaChjPW5ldyBEYXRlKCtvKSksdChvLHMpLGUobyk7d2hpbGUoYzxvJiZvPGEpO3JldHVybiBsfSxpLmZpbHRlcj1mdW5jdGlvbihvKXtyZXR1cm4gTnIoZnVuY3Rpb24oYSl7aWYoYT49YSlmb3IoO2UoYSksIW8oYSk7KWEuc2V0VGltZShhLTEpfSxmdW5jdGlvbihhLHMpe2lmKGE+PWEpaWYoczwwKWZvcig7KytzPD0wOylmb3IoO3QoYSwtMSksIW8oYSk7KTtlbHNlIGZvcig7LS1zPj0wOylmb3IoO3QoYSwxKSwhbyhhKTspO30pfSxyJiYoaS5jb3VudD1mdW5jdGlvbihvLGEpe3JldHVybiBLdHQuc2V0VGltZSgrbyksWnR0LnNldFRpbWUoK2EpLGUoS3R0KSxlKFp0dCksTWF0aC5mbG9vcihyKEt0dCxadHQpKX0saS5ldmVyeT1mdW5jdGlvbihvKXtyZXR1cm4gbz1NYXRoLmZsb29yKG8pLCFpc0Zpbml0ZShvKXx8IShvPjApP251bGw6bz4xP2kuZmlsdGVyKG4/ZnVuY3Rpb24oYSl7cmV0dXJuIG4oYSklbz09PTB9OmZ1bmN0aW9uKGEpe3JldHVybiBpLmNvdW50KDAsYSklbz09PTB9KTppfSksaX12YXIgS3R0LFp0dCxLYT1NKCgpPT57S3R0PW5ldyBEYXRlLFp0dD1uZXcgRGF0ZX0pO3ZhciBYTyxtdyxhT3Qsc090PU0oKCk9PntLYSgpO1hPPU5yKGZ1bmN0aW9uKCl7fSxmdW5jdGlvbihlLHQpe2Uuc2V0VGltZSgrZSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4gdC1lfSk7WE8uZXZlcnk9ZnVuY3Rpb24oZSl7cmV0dXJuIGU9TWF0aC5mbG9vcihlKSwhaXNGaW5pdGUoZSl8fCEoZT4wKT9udWxsOmU+MT9OcihmdW5jdGlvbih0KXt0LnNldFRpbWUoTWF0aC5mbG9vcih0L2UpKmUpfSxmdW5jdGlvbih0LHIpe3Quc2V0VGltZSgrdCtyKmUpfSxmdW5jdGlvbih0LHIpe3JldHVybihyLXQpL2V9KTpYT307bXc9WE8sYU90PVhPLnJhbmdlfSk7dmFyIG8xLHdjLGd3LCRPLEtPLHhmPU0oKCk9PntvMT0xZTMsd2M9NmU0LGd3PTM2ZTUsJE89ODY0ZTUsS089NjA0OGU1fSk7dmFyIGxPdCxfdyxjT3QsdU90PU0oKCk9PntLYSgpO3hmKCk7bE90PU5yKGZ1bmN0aW9uKGUpe2Uuc2V0VGltZShlLWUuZ2V0TWlsbGlzZWNvbmRzKCkpfSxmdW5jdGlvbihlLHQpe2Uuc2V0VGltZSgrZSt0Km8xKX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4odC1lKS9vMX0sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0VVRDU2Vjb25kcygpfSksX3c9bE90LGNPdD1sT3QucmFuZ2V9KTt2YXIgaE90LEp0dCxRNmUsZk90PU0oKCk9PntLYSgpO3hmKCk7aE90PU5yKGZ1bmN0aW9uKGUpe2Uuc2V0VGltZShlLWUuZ2V0TWlsbGlzZWNvbmRzKCktZS5nZXRTZWNvbmRzKCkqbzEpfSxmdW5jdGlvbihlLHQpe2Uuc2V0VGltZSgrZSt0KndjKX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4odC1lKS93Y30sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0TWludXRlcygpfSksSnR0PWhPdCxRNmU9aE90LnJhbmdlfSk7dmFyIHBPdCxRdHQsdEllLGRPdD1NKCgpPT57S2EoKTt4ZigpO3BPdD1OcihmdW5jdGlvbihlKXtlLnNldFRpbWUoZS1lLmdldE1pbGxpc2Vjb25kcygpLWUuZ2V0U2Vjb25kcygpKm8xLWUuZ2V0TWludXRlcygpKndjKX0sZnVuY3Rpb24oZSx0KXtlLnNldFRpbWUoK2UrdCpndyl9LGZ1bmN0aW9uKGUsdCl7cmV0dXJuKHQtZSkvZ3d9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldEhvdXJzKCl9KSxRdHQ9cE90LHRJZT1wT3QucmFuZ2V9KTt2YXIgbU90LHl3LGVJZSxnT3Q9TSgoKT0+e0thKCk7eGYoKTttT3Q9TnIoZnVuY3Rpb24oZSl7ZS5zZXRIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24oZSx0KXtlLnNldERhdGUoZS5nZXREYXRlKCkrdCl9LGZ1bmN0aW9uKGUsdCl7cmV0dXJuKHQtZS0odC5nZXRUaW1lem9uZU9mZnNldCgpLWUuZ2V0VGltZXpvbmVPZmZzZXQoKSkqd2MpLyRPfSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXREYXRlKCktMX0pLHl3PW1PdCxlSWU9bU90LnJhbmdlfSk7ZnVuY3Rpb24gYTEoZSl7cmV0dXJuIE5yKGZ1bmN0aW9uKHQpe3Quc2V0RGF0ZSh0LmdldERhdGUoKS0odC5nZXREYXkoKSs3LWUpJTcpLHQuc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKHQscil7dC5zZXREYXRlKHQuZ2V0RGF0ZSgpK3IqNyl9LGZ1bmN0aW9uKHQscil7cmV0dXJuKHItdC0oci5nZXRUaW1lem9uZU9mZnNldCgpLXQuZ2V0VGltZXpvbmVPZmZzZXQoKSkqd2MpL0tPfSl9dmFyIHMxLHZ3LF9PdCx5T3Qsa2csdk90LHhPdCxiT3QsckllLG5JZSxpSWUsb0llLGFJZSxzSWUsd090PU0oKCk9PntLYSgpO3hmKCk7czE9YTEoMCksdnc9YTEoMSksX090PWExKDIpLHlPdD1hMSgzKSxrZz1hMSg0KSx2T3Q9YTEoNSkseE90PWExKDYpLGJPdD1zMS5yYW5nZSxySWU9dncucmFuZ2UsbkllPV9PdC5yYW5nZSxpSWU9eU90LnJhbmdlLG9JZT1rZy5yYW5nZSxhSWU9dk90LnJhbmdlLHNJZT14T3QucmFuZ2V9KTt2YXIgU090LHRldCxsSWUsTU90PU0oKCk9PntLYSgpO1NPdD1OcihmdW5jdGlvbihlKXtlLnNldERhdGUoMSksZS5zZXRIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24oZSx0KXtlLnNldE1vbnRoKGUuZ2V0TW9udGgoKSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4gdC5nZXRNb250aCgpLWUuZ2V0TW9udGgoKSsodC5nZXRGdWxsWWVhcigpLWUuZ2V0RnVsbFllYXIoKSkqMTJ9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldE1vbnRoKCl9KSx0ZXQ9U090LGxJZT1TT3QucmFuZ2V9KTt2YXIgZWV0LGFkLGNJZSxFT3Q9TSgoKT0+e0thKCk7ZWV0PU5yKGZ1bmN0aW9uKGUpe2Uuc2V0TW9udGgoMCwxKSxlLnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0RnVsbFllYXIoZS5nZXRGdWxsWWVhcigpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybiB0LmdldEZ1bGxZZWFyKCktZS5nZXRGdWxsWWVhcigpfSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRGdWxsWWVhcigpfSk7ZWV0LmV2ZXJ5PWZ1bmN0aW9uKGUpe3JldHVybiFpc0Zpbml0ZShlPU1hdGguZmxvb3IoZSkpfHwhKGU+MCk/bnVsbDpOcihmdW5jdGlvbih0KXt0LnNldEZ1bGxZZWFyKE1hdGguZmxvb3IodC5nZXRGdWxsWWVhcigpL2UpKmUpLHQuc2V0TW9udGgoMCwxKSx0LnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbih0LHIpe3Quc2V0RnVsbFllYXIodC5nZXRGdWxsWWVhcigpK3IqZSl9KX07YWQ9ZWV0LGNJZT1lZXQucmFuZ2V9KTt2YXIgVE90LHJldCx1SWUsQ090PU0oKCk9PntLYSgpO3hmKCk7VE90PU5yKGZ1bmN0aW9uKGUpe2Uuc2V0VVRDU2Vjb25kcygwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0VGltZSgrZSt0KndjKX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4odC1lKS93Y30sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0VVRDTWludXRlcygpfSkscmV0PVRPdCx1SWU9VE90LnJhbmdlfSk7dmFyIEFPdCxuZXQsaEllLFBPdD1NKCgpPT57S2EoKTt4ZigpO0FPdD1OcihmdW5jdGlvbihlKXtlLnNldFVUQ01pbnV0ZXMoMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0VGltZSgrZSt0Kmd3KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4odC1lKS9nd30sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0VVRDSG91cnMoKX0pLG5ldD1BT3QsaEllPUFPdC5yYW5nZX0pO3ZhciBJT3QseHcsZkllLExPdD1NKCgpPT57S2EoKTt4ZigpO0lPdD1OcihmdW5jdGlvbihlKXtlLnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0VVRDRGF0ZShlLmdldFVUQ0RhdGUoKSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4odC1lKS8kT30sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0VVRDRGF0ZSgpLTF9KSx4dz1JT3QsZkllPUlPdC5yYW5nZX0pO2Z1bmN0aW9uIGwxKGUpe3JldHVybiBOcihmdW5jdGlvbih0KXt0LnNldFVUQ0RhdGUodC5nZXRVVENEYXRlKCktKHQuZ2V0VVRDRGF5KCkrNy1lKSU3KSx0LnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbih0LHIpe3Quc2V0VVRDRGF0ZSh0LmdldFVUQ0RhdGUoKStyKjcpfSxmdW5jdGlvbih0LHIpe3JldHVybihyLXQpL0tPfSl9dmFyIGMxLGJ3LGtPdCxST3QsUmcsTk90LERPdCxPT3QscEllLGRJZSxtSWUsZ0llLF9JZSx5SWUsek90PU0oKCk9PntLYSgpO3hmKCk7YzE9bDEoMCksYnc9bDEoMSksa090PWwxKDIpLFJPdD1sMSgzKSxSZz1sMSg0KSxOT3Q9bDEoNSksRE90PWwxKDYpLE9PdD1jMS5yYW5nZSxwSWU9YncucmFuZ2UsZEllPWtPdC5yYW5nZSxtSWU9Uk90LnJhbmdlLGdJZT1SZy5yYW5nZSxfSWU9Tk90LnJhbmdlLHlJZT1ET3QucmFuZ2V9KTt2YXIgRk90LGlldCx2SWUsQk90PU0oKCk9PntLYSgpO0ZPdD1OcihmdW5jdGlvbihlKXtlLnNldFVUQ0RhdGUoMSksZS5zZXRVVENIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24oZSx0KXtlLnNldFVUQ01vbnRoKGUuZ2V0VVRDTW9udGgoKSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4gdC5nZXRVVENNb250aCgpLWUuZ2V0VVRDTW9udGgoKSsodC5nZXRVVENGdWxsWWVhcigpLWUuZ2V0VVRDRnVsbFllYXIoKSkqMTJ9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldFVUQ01vbnRoKCl9KSxpZXQ9Rk90LHZJZT1GT3QucmFuZ2V9KTt2YXIgb2V0LHNkLHhJZSxIT3Q9TSgoKT0+e0thKCk7b2V0PU5yKGZ1bmN0aW9uKGUpe2Uuc2V0VVRDTW9udGgoMCwxKSxlLnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0VVRDRnVsbFllYXIoZS5nZXRVVENGdWxsWWVhcigpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybiB0LmdldFVUQ0Z1bGxZZWFyKCktZS5nZXRVVENGdWxsWWVhcigpfSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRVVENGdWxsWWVhcigpfSk7b2V0LmV2ZXJ5PWZ1bmN0aW9uKGUpe3JldHVybiFpc0Zpbml0ZShlPU1hdGguZmxvb3IoZSkpfHwhKGU+MCk/bnVsbDpOcihmdW5jdGlvbih0KXt0LnNldFVUQ0Z1bGxZZWFyKE1hdGguZmxvb3IodC5nZXRVVENGdWxsWWVhcigpL2UpKmUpLHQuc2V0VVRDTW9udGgoMCwxKSx0LnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbih0LHIpe3Quc2V0VVRDRnVsbFllYXIodC5nZXRVVENGdWxsWWVhcigpK3IqZSl9KX07c2Q9b2V0LHhJZT1vZXQucmFuZ2V9KTt2YXIgWk89TSgoKT0+e3NPdCgpO3VPdCgpO2ZPdCgpO2RPdCgpO2dPdCgpO3dPdCgpO01PdCgpO0VPdCgpO0NPdCgpO1BPdCgpO0xPdCgpO3pPdCgpO0JPdCgpO0hPdCgpfSk7ZnVuY3Rpb24gYWV0KGUpe2lmKDA8PWUueSYmZS55PDEwMCl7dmFyIHQ9bmV3IERhdGUoLTEsZS5tLGUuZCxlLkgsZS5NLGUuUyxlLkwpO3JldHVybiB0LnNldEZ1bGxZZWFyKGUueSksdH1yZXR1cm4gbmV3IERhdGUoZS55LGUubSxlLmQsZS5ILGUuTSxlLlMsZS5MKX1mdW5jdGlvbiBzZXQoZSl7aWYoMDw9ZS55JiZlLnk8MTAwKXt2YXIgdD1uZXcgRGF0ZShEYXRlLlVUQygtMSxlLm0sZS5kLGUuSCxlLk0sZS5TLGUuTCkpO3JldHVybiB0LnNldFVUQ0Z1bGxZZWFyKGUueSksdH1yZXR1cm4gbmV3IERhdGUoRGF0ZS5VVEMoZS55LGUubSxlLmQsZS5ILGUuTSxlLlMsZS5MKSl9ZnVuY3Rpb24gX0MoZSx0LHIpe3JldHVybnt5OmUsbTp0LGQ6cixIOjAsTTowLFM6MCxMOjB9fWZ1bmN0aW9uIGNldChlKXt2YXIgdD1lLmRhdGVUaW1lLHI9ZS5kYXRlLG49ZS50aW1lLGk9ZS5wZXJpb2RzLG89ZS5kYXlzLGE9ZS5zaG9ydERheXMscz1lLm1vbnRocyxsPWUuc2hvcnRNb250aHMsYz15QyhpKSx1PXZDKGkpLGg9eUMobyksZj12QyhvKSxwPXlDKGEpLGQ9dkMoYSksZz15QyhzKSxfPXZDKHMpLHk9eUMobCkseD12QyhsKSxiPXthOlcsQTpaLGI6cnQsQjpvdCxjOm51bGwsZDpZT3QsZTpZT3QsZjpxSWUsZzpRSWUsRzplOWUsSDpISWUsSTpWSWUsajpVSWUsTDpaT3QsbTpHSWUsTTpXSWUscDpzdCxxOlN0LFE6JE90LHM6S090LFM6WUllLHU6akllLFU6WEllLFY6JEllLHc6S0llLFc6WkllLHg6bnVsbCxYOm51bGwseTpKSWUsWTp0OWUsWjpyOWUsIiUiOlhPdH0sUz17YTpidCxBOk10LGI6bHQsQjpLdCxjOm51bGwsZDpqT3QsZTpqT3QsZjphOWUsZzpnOWUsRzp5OWUsSDpuOWUsSTppOWUsajpvOWUsTDpRT3QsbTpzOWUsTTpsOWUscDpfdCxxOmN0LFE6JE90LHM6S090LFM6YzllLHU6dTllLFU6aDllLFY6ZjllLHc6cDllLFc6ZDllLHg6bnVsbCxYOm51bGwseTptOWUsWTpfOWUsWjp2OWUsIiUiOlhPdH0sQz17YTpCLEE6SSxiOkwsQjpSLGM6RixkOkdPdCxlOkdPdCxmOk9JZSxnOnFPdCxHOlVPdCxIOldPdCxJOldPdCxqOmtJZSxMOkRJZSxtOkxJZSxNOlJJZSxwOkQscTpJSWUsUTpGSWUsczpCSWUsUzpOSWUsdTpFSWUsVTpUSWUsVjpDSWUsdzpNSWUsVzpBSWUseDp6LFg6VSx5OnFPdCxZOlVPdCxaOlBJZSwiJSI6ekllfTtiLng9UChyLGIpLGIuWD1QKG4sYiksYi5jPVAodCxiKSxTLng9UChyLFMpLFMuWD1QKG4sUyksUy5jPVAodCxTKTtmdW5jdGlvbiBQKFgsZXQpe3JldHVybiBmdW5jdGlvbihkdCl7dmFyIHE9W10scHQ9LTEsaHQ9MCx3dD1YLmxlbmd0aCxrdCxpZSxlZTtmb3IoZHQgaW5zdGFuY2VvZiBEYXRlfHwoZHQ9bmV3IERhdGUoK2R0KSk7KytwdDx3dDspWC5jaGFyQ29kZUF0KHB0KT09PTM3JiYocS5wdXNoKFguc2xpY2UoaHQscHQpKSwoaWU9Vk90W2t0PVguY2hhckF0KCsrcHQpXSkhPW51bGw/a3Q9WC5jaGFyQXQoKytwdCk6aWU9a3Q9PT0iZSI/IiAiOiIwIiwoZWU9ZXRba3RdKSYmKGt0PWVlKGR0LGllKSkscS5wdXNoKGt0KSxodD1wdCsxKTtyZXR1cm4gcS5wdXNoKFguc2xpY2UoaHQscHQpKSxxLmpvaW4oIiIpfX1mdW5jdGlvbiBrKFgsZXQpe3JldHVybiBmdW5jdGlvbihkdCl7dmFyIHE9X0MoMTkwMCx2b2lkIDAsMSkscHQ9TyhxLFgsZHQrPSIiLDApLGh0LHd0O2lmKHB0IT1kdC5sZW5ndGgpcmV0dXJuIG51bGw7aWYoIlEiaW4gcSlyZXR1cm4gbmV3IERhdGUocS5RKTtpZigicyJpbiBxKXJldHVybiBuZXcgRGF0ZShxLnMqMWUzKygiTCJpbiBxP3EuTDowKSk7aWYoZXQmJiEoIloiaW4gcSkmJihxLlo9MCksInAiaW4gcSYmKHEuSD1xLkglMTIrcS5wKjEyKSxxLm09PT12b2lkIDAmJihxLm09InEiaW4gcT9xLnE6MCksIlYiaW4gcSl7aWYocS5WPDF8fHEuVj41MylyZXR1cm4gbnVsbDsidyJpbiBxfHwocS53PTEpLCJaImluIHE/KGh0PXNldChfQyhxLnksMCwxKSksd3Q9aHQuZ2V0VVRDRGF5KCksaHQ9d3Q+NHx8d3Q9PT0wP2J3LmNlaWwoaHQpOmJ3KGh0KSxodD14dy5vZmZzZXQoaHQsKHEuVi0xKSo3KSxxLnk9aHQuZ2V0VVRDRnVsbFllYXIoKSxxLm09aHQuZ2V0VVRDTW9udGgoKSxxLmQ9aHQuZ2V0VVRDRGF0ZSgpKyhxLncrNiklNyk6KGh0PWFldChfQyhxLnksMCwxKSksd3Q9aHQuZ2V0RGF5KCksaHQ9d3Q+NHx8d3Q9PT0wP3Z3LmNlaWwoaHQpOnZ3KGh0KSxodD15dy5vZmZzZXQoaHQsKHEuVi0xKSo3KSxxLnk9aHQuZ2V0RnVsbFllYXIoKSxxLm09aHQuZ2V0TW9udGgoKSxxLmQ9aHQuZ2V0RGF0ZSgpKyhxLncrNiklNyl9ZWxzZSgiVyJpbiBxfHwiVSJpbiBxKSYmKCJ3ImluIHF8fChxLnc9InUiaW4gcT9xLnUlNzoiVyJpbiBxPzE6MCksd3Q9IloiaW4gcT9zZXQoX0MocS55LDAsMSkpLmdldFVUQ0RheSgpOmFldChfQyhxLnksMCwxKSkuZ2V0RGF5KCkscS5tPTAscS5kPSJXImluIHE/KHEudys2KSU3K3EuVyo3LSh3dCs1KSU3OnEudytxLlUqNy0od3QrNiklNyk7cmV0dXJuIloiaW4gcT8ocS5IKz1xLlovMTAwfDAscS5NKz1xLlolMTAwLHNldChxKSk6YWV0KHEpfX1mdW5jdGlvbiBPKFgsZXQsZHQscSl7Zm9yKHZhciBwdD0wLGh0PWV0Lmxlbmd0aCx3dD1kdC5sZW5ndGgsa3QsaWU7cHQ8aHQ7KXtpZihxPj13dClyZXR1cm4tMTtpZihrdD1ldC5jaGFyQ29kZUF0KHB0KyspLGt0PT09Mzcpe2lmKGt0PWV0LmNoYXJBdChwdCsrKSxpZT1DW2t0IGluIFZPdD9ldC5jaGFyQXQocHQrKyk6a3RdLCFpZXx8KHE9aWUoWCxkdCxxKSk8MClyZXR1cm4tMX1lbHNlIGlmKGt0IT1kdC5jaGFyQ29kZUF0KHErKykpcmV0dXJuLTF9cmV0dXJuIHF9ZnVuY3Rpb24gRChYLGV0LGR0KXt2YXIgcT1jLmV4ZWMoZXQuc2xpY2UoZHQpKTtyZXR1cm4gcT8oWC5wPXVbcVswXS50b0xvd2VyQ2FzZSgpXSxkdCtxWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gQihYLGV0LGR0KXt2YXIgcT1wLmV4ZWMoZXQuc2xpY2UoZHQpKTtyZXR1cm4gcT8oWC53PWRbcVswXS50b0xvd2VyQ2FzZSgpXSxkdCtxWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gSShYLGV0LGR0KXt2YXIgcT1oLmV4ZWMoZXQuc2xpY2UoZHQpKTtyZXR1cm4gcT8oWC53PWZbcVswXS50b0xvd2VyQ2FzZSgpXSxkdCtxWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gTChYLGV0LGR0KXt2YXIgcT15LmV4ZWMoZXQuc2xpY2UoZHQpKTtyZXR1cm4gcT8oWC5tPXhbcVswXS50b0xvd2VyQ2FzZSgpXSxkdCtxWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gUihYLGV0LGR0KXt2YXIgcT1nLmV4ZWMoZXQuc2xpY2UoZHQpKTtyZXR1cm4gcT8oWC5tPV9bcVswXS50b0xvd2VyQ2FzZSgpXSxkdCtxWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gRihYLGV0LGR0KXtyZXR1cm4gTyhYLHQsZXQsZHQpfWZ1bmN0aW9uIHooWCxldCxkdCl7cmV0dXJuIE8oWCxyLGV0LGR0KX1mdW5jdGlvbiBVKFgsZXQsZHQpe3JldHVybiBPKFgsbixldCxkdCl9ZnVuY3Rpb24gVyhYKXtyZXR1cm4gYVtYLmdldERheSgpXX1mdW5jdGlvbiBaKFgpe3JldHVybiBvW1guZ2V0RGF5KCldfWZ1bmN0aW9uIHJ0KFgpe3JldHVybiBsW1guZ2V0TW9udGgoKV19ZnVuY3Rpb24gb3QoWCl7cmV0dXJuIHNbWC5nZXRNb250aCgpXX1mdW5jdGlvbiBzdChYKXtyZXR1cm4gaVsrKFguZ2V0SG91cnMoKT49MTIpXX1mdW5jdGlvbiBTdChYKXtyZXR1cm4gMSt+fihYLmdldE1vbnRoKCkvMyl9ZnVuY3Rpb24gYnQoWCl7cmV0dXJuIGFbWC5nZXRVVENEYXkoKV19ZnVuY3Rpb24gTXQoWCl7cmV0dXJuIG9bWC5nZXRVVENEYXkoKV19ZnVuY3Rpb24gbHQoWCl7cmV0dXJuIGxbWC5nZXRVVENNb250aCgpXX1mdW5jdGlvbiBLdChYKXtyZXR1cm4gc1tYLmdldFVUQ01vbnRoKCldfWZ1bmN0aW9uIF90KFgpe3JldHVybiBpWysoWC5nZXRVVENIb3VycygpPj0xMildfWZ1bmN0aW9uIGN0KFgpe3JldHVybiAxK35+KFguZ2V0VVRDTW9udGgoKS8zKX1yZXR1cm57Zm9ybWF0OmZ1bmN0aW9uKFgpe3ZhciBldD1QKFgrPSIiLGIpO3JldHVybiBldC50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiBYfSxldH0scGFyc2U6ZnVuY3Rpb24oWCl7dmFyIGV0PWsoWCs9IiIsITEpO3JldHVybiBldC50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiBYfSxldH0sdXRjRm9ybWF0OmZ1bmN0aW9uKFgpe3ZhciBldD1QKFgrPSIiLFMpO3JldHVybiBldC50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiBYfSxldH0sdXRjUGFyc2U6ZnVuY3Rpb24oWCl7dmFyIGV0PWsoWCs9IiIsITApO3JldHVybiBldC50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiBYfSxldH19fWZ1bmN0aW9uIEdyKGUsdCxyKXt2YXIgbj1lPDA/Ii0iOiIiLGk9KG4/LWU6ZSkrIiIsbz1pLmxlbmd0aDtyZXR1cm4gbisobzxyP25ldyBBcnJheShyLW8rMSkuam9pbih0KStpOmkpfWZ1bmN0aW9uIFNJZShlKXtyZXR1cm4gZS5yZXBsYWNlKHdJZSwiXFwkJiIpfWZ1bmN0aW9uIHlDKGUpe3JldHVybiBuZXcgUmVnRXhwKCJeKD86IitlLm1hcChTSWUpLmpvaW4oInwiKSsiKSIsImkiKX1mdW5jdGlvbiB2QyhlKXtmb3IodmFyIHQ9e30scj0tMSxuPWUubGVuZ3RoOysrcjxuOyl0W2Vbcl0udG9Mb3dlckNhc2UoKV09cjtyZXR1cm4gdH1mdW5jdGlvbiBNSWUoZSx0LHIpe3ZhciBuPVRvLmV4ZWModC5zbGljZShyLHIrMSkpO3JldHVybiBuPyhlLnc9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gRUllKGUsdCxyKXt2YXIgbj1Uby5leGVjKHQuc2xpY2UocixyKzEpKTtyZXR1cm4gbj8oZS51PStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIFRJZShlLHQscil7dmFyIG49VG8uZXhlYyh0LnNsaWNlKHIscisyKSk7cmV0dXJuIG4/KGUuVT0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBDSWUoZSx0LHIpe3ZhciBuPVRvLmV4ZWModC5zbGljZShyLHIrMikpO3JldHVybiBuPyhlLlY9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gQUllKGUsdCxyKXt2YXIgbj1Uby5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS5XPStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIFVPdChlLHQscil7dmFyIG49VG8uZXhlYyh0LnNsaWNlKHIscis0KSk7cmV0dXJuIG4/KGUueT0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBxT3QoZSx0LHIpe3ZhciBuPVRvLmV4ZWModC5zbGljZShyLHIrMikpO3JldHVybiBuPyhlLnk9K25bMF0rKCtuWzBdPjY4PzE5MDA6MmUzKSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBQSWUoZSx0LHIpe3ZhciBuPS9eKFopfChbKy1dXGRcZCkoPzo6PyhcZFxkKSk/Ly5leGVjKHQuc2xpY2UocixyKzYpKTtyZXR1cm4gbj8oZS5aPW5bMV0/MDotKG5bMl0rKG5bM118fCIwMCIpKSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBJSWUoZSx0LHIpe3ZhciBuPVRvLmV4ZWModC5zbGljZShyLHIrMSkpO3JldHVybiBuPyhlLnE9blswXSozLTMscituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gTEllKGUsdCxyKXt2YXIgbj1Uby5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS5tPW5bMF0tMSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBHT3QoZSx0LHIpe3ZhciBuPVRvLmV4ZWModC5zbGljZShyLHIrMikpO3JldHVybiBuPyhlLmQ9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24ga0llKGUsdCxyKXt2YXIgbj1Uby5leGVjKHQuc2xpY2UocixyKzMpKTtyZXR1cm4gbj8oZS5tPTAsZS5kPStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIFdPdChlLHQscil7dmFyIG49VG8uZXhlYyh0LnNsaWNlKHIscisyKSk7cmV0dXJuIG4/KGUuSD0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBSSWUoZSx0LHIpe3ZhciBuPVRvLmV4ZWModC5zbGljZShyLHIrMikpO3JldHVybiBuPyhlLk09K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gTkllKGUsdCxyKXt2YXIgbj1Uby5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS5TPStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIERJZShlLHQscil7dmFyIG49VG8uZXhlYyh0LnNsaWNlKHIsciszKSk7cmV0dXJuIG4/KGUuTD0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBPSWUoZSx0LHIpe3ZhciBuPVRvLmV4ZWModC5zbGljZShyLHIrNikpO3JldHVybiBuPyhlLkw9TWF0aC5mbG9vcihuWzBdLzFlMykscituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gekllKGUsdCxyKXt2YXIgbj1iSWUuZXhlYyh0LnNsaWNlKHIscisxKSk7cmV0dXJuIG4/cituWzBdLmxlbmd0aDotMX1mdW5jdGlvbiBGSWUoZSx0LHIpe3ZhciBuPVRvLmV4ZWModC5zbGljZShyKSk7cmV0dXJuIG4/KGUuUT0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBCSWUoZSx0LHIpe3ZhciBuPVRvLmV4ZWModC5zbGljZShyKSk7cmV0dXJuIG4/KGUucz0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBZT3QoZSx0KXtyZXR1cm4gR3IoZS5nZXREYXRlKCksdCwyKX1mdW5jdGlvbiBISWUoZSx0KXtyZXR1cm4gR3IoZS5nZXRIb3VycygpLHQsMil9ZnVuY3Rpb24gVkllKGUsdCl7cmV0dXJuIEdyKGUuZ2V0SG91cnMoKSUxMnx8MTIsdCwyKX1mdW5jdGlvbiBVSWUoZSx0KXtyZXR1cm4gR3IoMSt5dy5jb3VudChhZChlKSxlKSx0LDMpfWZ1bmN0aW9uIFpPdChlLHQpe3JldHVybiBHcihlLmdldE1pbGxpc2Vjb25kcygpLHQsMyl9ZnVuY3Rpb24gcUllKGUsdCl7cmV0dXJuIFpPdChlLHQpKyIwMDAifWZ1bmN0aW9uIEdJZShlLHQpe3JldHVybiBHcihlLmdldE1vbnRoKCkrMSx0LDIpfWZ1bmN0aW9uIFdJZShlLHQpe3JldHVybiBHcihlLmdldE1pbnV0ZXMoKSx0LDIpfWZ1bmN0aW9uIFlJZShlLHQpe3JldHVybiBHcihlLmdldFNlY29uZHMoKSx0LDIpfWZ1bmN0aW9uIGpJZShlKXt2YXIgdD1lLmdldERheSgpO3JldHVybiB0PT09MD83OnR9ZnVuY3Rpb24gWEllKGUsdCl7cmV0dXJuIEdyKHMxLmNvdW50KGFkKGUpLTEsZSksdCwyKX1mdW5jdGlvbiBKT3QoZSl7dmFyIHQ9ZS5nZXREYXkoKTtyZXR1cm4gdD49NHx8dD09PTA/a2coZSk6a2cuY2VpbChlKX1mdW5jdGlvbiAkSWUoZSx0KXtyZXR1cm4gZT1KT3QoZSksR3Ioa2cuY291bnQoYWQoZSksZSkrKGFkKGUpLmdldERheSgpPT09NCksdCwyKX1mdW5jdGlvbiBLSWUoZSl7cmV0dXJuIGUuZ2V0RGF5KCl9ZnVuY3Rpb24gWkllKGUsdCl7cmV0dXJuIEdyKHZ3LmNvdW50KGFkKGUpLTEsZSksdCwyKX1mdW5jdGlvbiBKSWUoZSx0KXtyZXR1cm4gR3IoZS5nZXRGdWxsWWVhcigpJTEwMCx0LDIpfWZ1bmN0aW9uIFFJZShlLHQpe3JldHVybiBlPUpPdChlKSxHcihlLmdldEZ1bGxZZWFyKCklMTAwLHQsMil9ZnVuY3Rpb24gdDllKGUsdCl7cmV0dXJuIEdyKGUuZ2V0RnVsbFllYXIoKSUxZTQsdCw0KX1mdW5jdGlvbiBlOWUoZSx0KXt2YXIgcj1lLmdldERheSgpO3JldHVybiBlPXI+PTR8fHI9PT0wP2tnKGUpOmtnLmNlaWwoZSksR3IoZS5nZXRGdWxsWWVhcigpJTFlNCx0LDQpfWZ1bmN0aW9uIHI5ZShlKXt2YXIgdD1lLmdldFRpbWV6b25lT2Zmc2V0KCk7cmV0dXJuKHQ+MD8iLSI6KHQqPS0xLCIrIikpK0dyKHQvNjB8MCwiMCIsMikrR3IodCU2MCwiMCIsMil9ZnVuY3Rpb24gak90KGUsdCl7cmV0dXJuIEdyKGUuZ2V0VVRDRGF0ZSgpLHQsMil9ZnVuY3Rpb24gbjllKGUsdCl7cmV0dXJuIEdyKGUuZ2V0VVRDSG91cnMoKSx0LDIpfWZ1bmN0aW9uIGk5ZShlLHQpe3JldHVybiBHcihlLmdldFVUQ0hvdXJzKCklMTJ8fDEyLHQsMil9ZnVuY3Rpb24gbzllKGUsdCl7cmV0dXJuIEdyKDEreHcuY291bnQoc2QoZSksZSksdCwzKX1mdW5jdGlvbiBRT3QoZSx0KXtyZXR1cm4gR3IoZS5nZXRVVENNaWxsaXNlY29uZHMoKSx0LDMpfWZ1bmN0aW9uIGE5ZShlLHQpe3JldHVybiBRT3QoZSx0KSsiMDAwIn1mdW5jdGlvbiBzOWUoZSx0KXtyZXR1cm4gR3IoZS5nZXRVVENNb250aCgpKzEsdCwyKX1mdW5jdGlvbiBsOWUoZSx0KXtyZXR1cm4gR3IoZS5nZXRVVENNaW51dGVzKCksdCwyKX1mdW5jdGlvbiBjOWUoZSx0KXtyZXR1cm4gR3IoZS5nZXRVVENTZWNvbmRzKCksdCwyKX1mdW5jdGlvbiB1OWUoZSl7dmFyIHQ9ZS5nZXRVVENEYXkoKTtyZXR1cm4gdD09PTA/Nzp0fWZ1bmN0aW9uIGg5ZShlLHQpe3JldHVybiBHcihjMS5jb3VudChzZChlKS0xLGUpLHQsMil9ZnVuY3Rpb24gdDd0KGUpe3ZhciB0PWUuZ2V0VVRDRGF5KCk7cmV0dXJuIHQ+PTR8fHQ9PT0wP1JnKGUpOlJnLmNlaWwoZSl9ZnVuY3Rpb24gZjllKGUsdCl7cmV0dXJuIGU9dDd0KGUpLEdyKFJnLmNvdW50KHNkKGUpLGUpKyhzZChlKS5nZXRVVENEYXkoKT09PTQpLHQsMil9ZnVuY3Rpb24gcDllKGUpe3JldHVybiBlLmdldFVUQ0RheSgpfWZ1bmN0aW9uIGQ5ZShlLHQpe3JldHVybiBHcihidy5jb3VudChzZChlKS0xLGUpLHQsMil9ZnVuY3Rpb24gbTllKGUsdCl7cmV0dXJuIEdyKGUuZ2V0VVRDRnVsbFllYXIoKSUxMDAsdCwyKX1mdW5jdGlvbiBnOWUoZSx0KXtyZXR1cm4gZT10N3QoZSksR3IoZS5nZXRVVENGdWxsWWVhcigpJTEwMCx0LDIpfWZ1bmN0aW9uIF85ZShlLHQpe3JldHVybiBHcihlLmdldFVUQ0Z1bGxZZWFyKCklMWU0LHQsNCl9ZnVuY3Rpb24geTllKGUsdCl7dmFyIHI9ZS5nZXRVVENEYXkoKTtyZXR1cm4gZT1yPj00fHxyPT09MD9SZyhlKTpSZy5jZWlsKGUpLEdyKGUuZ2V0VVRDRnVsbFllYXIoKSUxZTQsdCw0KX1mdW5jdGlvbiB2OWUoKXtyZXR1cm4iKzAwMDAifWZ1bmN0aW9uIFhPdCgpe3JldHVybiIlIn1mdW5jdGlvbiAkT3QoZSl7cmV0dXJuK2V9ZnVuY3Rpb24gS090KGUpe3JldHVybiBNYXRoLmZsb29yKCtlLzFlMyl9dmFyIFZPdCxUbyxiSWUsd0llLGU3dD1NKCgpPT57Wk8oKTtWT3Q9eyItIjoiIixfOiIgIiwwOiIwIn0sVG89L15ccypcZCsvLGJJZT0vXiUvLHdJZT0vW1xcXiQqKz98W1xdKCkue31dL2d9KTtmdW5jdGlvbiB1ZXQoZSl7cmV0dXJuIHd3PWNldChlKSxKTz13dy5mb3JtYXQscjd0PXd3LnBhcnNlLFFPPXd3LnV0Y0Zvcm1hdCxuN3Q9d3cudXRjUGFyc2Usd3d9dmFyIHd3LEpPLHI3dCxRTyxuN3QsaTd0PU0oKCk9PntlN3QoKTt1ZXQoe2RhdGVUaW1lOiIleCwgJVgiLGRhdGU6IiUtbS8lLWQvJVkiLHRpbWU6IiUtSTolTTolUyAlcCIscGVyaW9kczpbIkFNIiwiUE0iXSxkYXlzOlsiU3VuZGF5IiwiTW9uZGF5IiwiVHVlc2RheSIsIldlZG5lc2RheSIsIlRodXJzZGF5IiwiRnJpZGF5IiwiU2F0dXJkYXkiXSxzaG9ydERheXM6WyJTdW4iLCJNb24iLCJUdWUiLCJXZWQiLCJUaHUiLCJGcmkiLCJTYXQiXSxtb250aHM6WyJKYW51YXJ5IiwiRmVicnVhcnkiLCJNYXJjaCIsIkFwcmlsIiwiTWF5IiwiSnVuZSIsIkp1bHkiLCJBdWd1c3QiLCJTZXB0ZW1iZXIiLCJPY3RvYmVyIiwiTm92ZW1iZXIiLCJEZWNlbWJlciJdLHNob3J0TW9udGhzOlsiSmFuIiwiRmViIiwiTWFyIiwiQXByIiwiTWF5IiwiSnVuIiwiSnVsIiwiQXVnIiwiU2VwIiwiT2N0IiwiTm92IiwiRGVjIl19KX0pO3ZhciBoZXQ9TSgoKT0+e2k3dCgpfSk7ZnVuY3Rpb24gYjllKGUpe3JldHVybiBuZXcgRGF0ZShlKX1mdW5jdGlvbiB3OWUoZSl7cmV0dXJuIGUgaW5zdGFuY2VvZiBEYXRlPytlOituZXcgRGF0ZSgrZSl9ZnVuY3Rpb24gdDcoZSx0LHIsbixpLG8sYSxzLGwpe3ZhciBjPWlkKGZDLHlmKSx1PWMuaW52ZXJ0LGg9Yy5kb21haW4sZj1sKCIuJUwiKSxwPWwoIjolUyIpLGQ9bCgiJUk6JU0iKSxnPWwoIiVJICVwIiksXz1sKCIlYSAlZCIpLHk9bCgiJWIgJWQiKSx4PWwoIiVCIiksYj1sKCIlWSIpLFM9W1thLDEseENdLFthLDUsNSp4Q10sW2EsMTUsMTUqeENdLFthLDMwLDMwKnhDXSxbbywxLGJDXSxbbyw1LDUqYkNdLFtvLDE1LDE1KmJDXSxbbywzMCwzMCpiQ10sW2ksMSx3Q10sW2ksMywzKndDXSxbaSw2LDYqd0NdLFtpLDEyLDEyKndDXSxbbiwxLFNDXSxbbiwyLDIqU0NdLFtyLDEseDllXSxbdCwxLG83dF0sW3QsMywzKm83dF0sW2UsMSxmZXRdXTtmdW5jdGlvbiBDKGspe3JldHVybihhKGspPGs/ZjpvKGspPGs/cDppKGspPGs/ZDpuKGspPGs/Zzp0KGspPGs/cihrKTxrP186eTplKGspPGs/eDpiKShrKX1mdW5jdGlvbiBQKGssTyxELEIpe2lmKGs9PW51bGwmJihrPTEwKSx0eXBlb2Ygaz09Im51bWJlciIpe3ZhciBJPU1hdGguYWJzKEQtTykvayxMPW9DKGZ1bmN0aW9uKFIpe3JldHVybiBSWzJdfSkucmlnaHQoUyxJKTtMPT09Uy5sZW5ndGg/KEI9WnkoTy9mZXQsRC9mZXQsayksaz1lKTpMPyhMPVNbSS9TW0wtMV1bMl08U1tMXVsyXS9JP0wtMTpMXSxCPUxbMV0saz1MWzBdKTooQj1NYXRoLm1heChaeShPLEQsayksMSksaz1zKX1yZXR1cm4gQj09bnVsbD9rOmsuZXZlcnkoQil9cmV0dXJuIGMuaW52ZXJ0PWZ1bmN0aW9uKGspe3JldHVybiBuZXcgRGF0ZSh1KGspKX0sYy5kb21haW49ZnVuY3Rpb24oayl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/aChydy5jYWxsKGssdzllKSk6aCgpLm1hcChiOWUpfSxjLnRpY2tzPWZ1bmN0aW9uKGssTyl7dmFyIEQ9aCgpLEI9RFswXSxJPURbRC5sZW5ndGgtMV0sTD1JPEIsUjtyZXR1cm4gTCYmKFI9QixCPUksST1SKSxSPVAoayxCLEksTyksUj1SP1IucmFuZ2UoQixJKzEpOltdLEw/Ui5yZXZlcnNlKCk6Un0sYy50aWNrRm9ybWF0PWZ1bmN0aW9uKGssTyl7cmV0dXJuIE89PW51bGw/QzpsKE8pfSxjLm5pY2U9ZnVuY3Rpb24oayxPKXt2YXIgRD1oKCk7cmV0dXJuKGs9UChrLERbMF0sRFtELmxlbmd0aC0xXSxPKSk/aChxTyhELGspKTpjfSxjLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gSWcoYyx0NyhlLHQscixuLGksbyxhLHMsbCkpfSxjfWZ1bmN0aW9uIGE3dCgpe3JldHVybiB0NyhhZCx0ZXQsczEseXcsUXR0LEp0dCxfdyxtdyxKTykuZG9tYWluKFtuZXcgRGF0ZSgyZTMsMCwxKSxuZXcgRGF0ZSgyZTMsMCwyKV0pfXZhciB4QyxiQyx3QyxTQyx4OWUsbzd0LGZldCxwZXQ9TSgoKT0+e21mKCk7dXcoKTtaTygpO2hldCgpO0NnKCk7cEMoKTskdHQoKTt4Qz0xZTMsYkM9eEMqNjAsd0M9YkMqNjAsU0M9d0MqMjQseDllPVNDKjcsbzd0PVNDKjMwLGZldD1TQyozNjV9KTtmdW5jdGlvbiBzN3QoKXtyZXR1cm4gdDcoc2QsaWV0LGMxLHh3LG5ldCxyZXQsX3csbXcsUU8pLmRvbWFpbihbRGF0ZS5VVEMoMmUzLDAsMSksRGF0ZS5VVEMoMmUzLDAsMildKX12YXIgbDd0PU0oKCk9PntwZXQoKTtoZXQoKTtaTygpfSk7ZnVuY3Rpb24gZGwoZSl7cmV0dXJuIGUubWF0Y2goLy57Nn0vZykubWFwKGZ1bmN0aW9uKHQpe3JldHVybiIjIit0fSl9dmFyIFN3PU0oKCk9Pnt9KTt2YXIgYzd0LHU3dD1NKCgpPT57U3coKTtjN3Q9ZGwoIjFmNzdiNGZmN2YwZTJjYTAyY2Q2MjcyODk0NjdiZDhjNTY0YmUzNzdjMjdmN2Y3ZmJjYmQyMjE3YmVjZiIpfSk7dmFyIGg3dCxmN3Q9TSgoKT0+e1N3KCk7aDd0PWRsKCIzOTNiNzk1MjU0YTM2YjZlY2Y5YzllZGU2Mzc5Mzk4Y2EyNTJiNWNmNmJjZWRiOWM4YzZkMzFiZDllMzllN2JhNTJlN2NiOTQ4NDNjMzlhZDQ5NGFkNjYxNmJlNzk2OWM3YjQxNzNhNTUxOTRjZTZkYmRkZTllZDYiKX0pO3ZhciBwN3QsZDd0PU0oKCk9PntTdygpO3A3dD1kbCgiMzE4MmJkNmJhZWQ2OWVjYWUxYzZkYmVmZTY1NTBkZmQ4ZDNjZmRhZTZiZmRkMGEyMzFhMzU0NzRjNDc2YTFkOTliYzdlOWMwNzU2YmIxOWU5YWM4YmNiZGRjZGFkYWViNjM2MzYzOTY5Njk2YmRiZGJkZDlkOWQ5Iil9KTt2YXIgbTd0LGc3dD1NKCgpPT57U3coKTttN3Q9ZGwoIjFmNzdiNGFlYzdlOGZmN2YwZWZmYmI3ODJjYTAyYzk4ZGY4YWQ2MjcyOGZmOTg5Njk0NjdiZGM1YjBkNThjNTY0YmM0OWM5NGUzNzdjMmY3YjZkMjdmN2Y3ZmM3YzdjN2JjYmQyMmRiZGI4ZDE3YmVjZjllZGFlNSIpfSk7dmFyIF83dCx5N3Q9TSgoKT0+e3N3KCk7dXcoKTtfN3Q9Y3coJGEoMzAwLC41LDApLCRhKC0yNDAsLjUsMSkpfSk7ZnVuY3Rpb24gYjd0KGUpeyhlPDB8fGU+MSkmJihlLT1NYXRoLmZsb29yKGUpKTt2YXIgdD1NYXRoLmFicyhlLS41KTtyZXR1cm4gZTcuaD0zNjAqZS0xMDAsZTcucz0xLjUtMS41KnQsZTcubD0uOC0uOSp0LGU3KyIifXZhciB2N3QseDd0LGU3LHc3dD1NKCgpPT57c3coKTt1dygpO3Y3dD1jdygkYSgtMTAwLC43NSwuMzUpLCRhKDgwLDEuNSwuOCkpLHg3dD1jdygkYSgyNjAsLjc1LC4zNSksJGEoODAsMS41LC44KSksZTc9JGEoKX0pO2Z1bmN0aW9uIHI3KGUpe3ZhciB0PWUubGVuZ3RoO3JldHVybiBmdW5jdGlvbihyKXtyZXR1cm4gZVtNYXRoLm1heCgwLE1hdGgubWluKHQtMSxNYXRoLmZsb29yKHIqdCkpKV19fXZhciBTN3QsTTd0LEU3dCxUN3QsQzd0PU0oKCk9PntTdygpO1M3dD1yNyhkbCgiNDQwMTU0NDQwMjU2NDUwNDU3NDUwNTU5NDYwNzVhNDYwODVjNDYwYTVkNDYwYjVlNDcwZDYwNDcwZTYxNDcxMDYzNDcxMTY0NDcxMzY1NDgxNDY3NDgxNjY4NDgxNzY5NDgxODZhNDgxYTZjNDgxYjZkNDgxYzZlNDgxZDZmNDgxZjcwNDgyMDcxNDgyMTczNDgyMzc0NDgyNDc1NDgyNTc2NDgyNjc3NDgyODc4NDgyOTc5NDcyYTdhNDcyYzdhNDcyZDdiNDcyZTdjNDcyZjdkNDYzMDdlNDYzMjdlNDYzMzdmNDYzNDgwNDUzNTgxNDUzNzgxNDUzODgyNDQzOTgzNDQzYTgzNDQzYjg0NDMzZDg0NDMzZTg1NDIzZjg1NDI0MDg2NDI0MTg2NDE0Mjg3NDE0NDg3NDA0NTg4NDA0Njg4M2Y0Nzg4M2Y0ODg5M2U0OTg5M2U0YTg5M2U0YzhhM2Q0ZDhhM2Q0ZThhM2M0ZjhhM2M1MDhiM2I1MThiM2I1MjhiM2E1MzhiM2E1NDhjMzk1NThjMzk1NjhjMzg1ODhjMzg1OThjMzc1YThjMzc1YjhkMzY1YzhkMzY1ZDhkMzU1ZThkMzU1ZjhkMzQ2MDhkMzQ2MThkMzM2MjhkMzM2MzhkMzI2NDhlMzI2NThlMzE2NjhlMzE2NzhlMzE2ODhlMzA2OThlMzA2YThlMmY2YjhlMmY2YzhlMmU2ZDhlMmU2ZThlMmU2ZjhlMmQ3MDhlMmQ3MThlMmM3MThlMmM3MjhlMmM3MzhlMmI3NDhlMmI3NThlMmE3NjhlMmE3NzhlMmE3ODhlMjk3OThlMjk3YThlMjk3YjhlMjg3YzhlMjg3ZDhlMjc3ZThlMjc3ZjhlMjc4MDhlMjY4MThlMjY4MjhlMjY4MjhlMjU4MzhlMjU4NDhlMjU4NThlMjQ4NjhlMjQ4NzhlMjM4ODhlMjM4OThlMjM4YThkMjI4YjhkMjI4YzhkMjI4ZDhkMjE4ZThkMjE4ZjhkMjE5MDhkMjE5MThjMjA5MjhjMjA5MjhjMjA5MzhjMWY5NDhjMWY5NThiMWY5NjhiMWY5NzhiMWY5ODhiMWY5OThhMWY5YThhMWU5YjhhMWU5Yzg5MWU5ZDg5MWY5ZTg5MWY5Zjg4MWZhMDg4MWZhMTg4MWZhMTg3MWZhMjg3MjBhMzg2MjBhNDg2MjFhNTg1MjFhNjg1MjJhNzg1MjJhODg0MjNhOTgzMjRhYTgzMjVhYjgyMjVhYzgyMjZhZDgxMjdhZDgxMjhhZTgwMjlhZjdmMmFiMDdmMmNiMTdlMmRiMjdkMmViMzdjMmZiNDdjMzFiNTdiMzJiNjdhMzRiNjc5MzViNzc5MzdiODc4MzhiOTc3M2FiYTc2M2JiYjc1M2RiYzc0M2ZiYzczNDBiZDcyNDJiZTcxNDRiZjcwNDZjMDZmNDhjMTZlNGFjMTZkNGNjMjZjNGVjMzZiNTBjNDZhNTJjNTY5NTRjNTY4NTZjNjY3NThjNzY1NWFjODY0NWNjODYzNWVjOTYyNjBjYTYwNjNjYjVmNjVjYjVlNjdjYzVjNjljZDViNmNjZDVhNmVjZTU4NzBjZjU3NzNkMDU2NzVkMDU0NzdkMTUzN2FkMTUxN2NkMjUwN2ZkMzRlODFkMzRkODRkNDRiODZkNTQ5ODlkNTQ4OGJkNjQ2OGVkNjQ1OTBkNzQzOTNkNzQxOTVkODQwOThkODNlOWJkOTNjOWRkOTNiYTBkYTM5YTJkYTM3YTVkYjM2YThkYjM0YWFkYzMyYWRkYzMwYjBkZDJmYjJkZDJkYjVkZTJiYjhkZTI5YmFkZTI4YmRkZjI2YzBkZjI1YzJkZjIzYzVlMDIxYzhlMDIwY2FlMTFmY2RlMTFkZDBlMTFjZDJlMjFiZDVlMjFhZDhlMjE5ZGFlMzE5ZGRlMzE4ZGZlMzE4ZTJlNDE4ZTVlNDE5ZTdlNDE5ZWFlNTFhZWNlNTFiZWZlNTFjZjFlNTFkZjRlNjFlZjZlNjIwZjhlNjIxZmJlNzIzZmRlNzI1IikpLE03dD1yNyhkbCgiMDAwMDA0MDEwMDA1MDEwMTA2MDEwMTA4MDIwMTA5MDIwMjBiMDIwMjBkMDMwMzBmMDMwMzEyMDQwNDE0MDUwNDE2MDYwNTE4MDYwNTFhMDcwNjFjMDgwNzFlMDkwNzIwMGEwODIyMGIwOTI0MGMwOTI2MGQwYTI5MGUwYjJiMTAwYjJkMTEwYzJmMTIwZDMxMTMwZDM0MTQwZTM2MTUwZTM4MTYwZjNiMTgwZjNkMTkxMDNmMWExMDQyMWMxMDQ0MWQxMTQ3MWUxMTQ5MjAxMTRiMjExMTRlMjIxMTUwMjQxMjUzMjUxMjU1MjcxMjU4MjkxMTVhMmExMTVjMmMxMTVmMmQxMTYxMmYxMTYzMzExMTY1MzMxMDY3MzQxMDY5MzYxMDZiMzgxMDZjMzkwZjZlM2IwZjcwM2QwZjcxM2YwZjcyNDAwZjc0NDIwZjc1NDQwZjc2NDUxMDc3NDcxMDc4NDkxMDc4NGExMDc5NGMxMTdhNGUxMTdiNGYxMjdiNTExMjdjNTIxMzdjNTQxMzdkNTYxNDdkNTcxNTdlNTkxNTdlNWExNjdlNWMxNjdmNWQxNzdmNWYxODdmNjAxODgwNjIxOTgwNjQxYTgwNjUxYTgwNjcxYjgwNjgxYzgxNmExYzgxNmIxZDgxNmQxZDgxNmUxZTgxNzAxZjgxNzIxZjgxNzMyMDgxNzUyMTgxNzYyMTgxNzgyMjgxNzkyMjgyN2IyMzgyN2MyMzgyN2UyNDgyODAyNTgyODEyNTgxODMyNjgxODQyNjgxODYyNzgxODgyNzgxODkyODgxOGIyOTgxOGMyOTgxOGUyYTgxOTAyYTgxOTEyYjgxOTMyYjgwOTQyYzgwOTYyYzgwOTgyZDgwOTkyZDgwOWIyZTdmOWMyZTdmOWUyZjdmYTAyZjdmYTEzMDdlYTMzMDdlYTUzMTdlYTYzMTdkYTgzMjdkYWEzMzdkYWIzMzdjYWQzNDdjYWUzNDdiYjAzNTdiYjIzNTdiYjMzNjdhYjUzNjdhYjczNzc5YjgzNzc5YmEzODc4YmMzOTc4YmQzOTc3YmYzYTc3YzAzYTc2YzIzYjc1YzQzYzc1YzUzYzc0YzczZDczYzgzZTczY2EzZTcyY2MzZjcxY2Q0MDcxY2Y0MDcwZDA0MTZmZDI0MjZmZDM0MzZlZDU0NDZkZDY0NTZjZDg0NTZjZDk0NjZiZGI0NzZhZGM0ODY5ZGU0OTY4ZGY0YTY4ZTA0YzY3ZTI0ZDY2ZTM0ZTY1ZTQ0ZjY0ZTU1MDY0ZTc1MjYzZTg1MzYyZTk1NDYyZWE1NjYxZWI1NzYwZWM1ODYwZWQ1YTVmZWU1YjVlZWY1ZDVlZjA1ZjVlZjE2MDVkZjI2MjVkZjI2NDVjZjM2NTVjZjQ2NzVjZjQ2OTVjZjU2YjVjZjY2YzVjZjY2ZTVjZjc3MDVjZjc3MjVjZjg3NDVjZjg3NjVjZjk3ODVkZjk3OTVkZjk3YjVkZmE3ZDVlZmE3ZjVlZmE4MTVmZmI4MzVmZmI4NTYwZmI4NzYxZmM4OTYxZmM4YTYyZmM4YzYzZmM4ZTY0ZmM5MDY1ZmQ5MjY2ZmQ5NDY3ZmQ5NjY4ZmQ5ODY5ZmQ5YTZhZmQ5YjZiZmU5ZDZjZmU5ZjZkZmVhMTZlZmVhMzZmZmVhNTcxZmVhNzcyZmVhOTczZmVhYTc0ZmVhYzc2ZmVhZTc3ZmViMDc4ZmViMjdhZmViNDdiZmViNjdjZmViNzdlZmViOTdmZmViYjgxZmViZDgyZmViZjg0ZmVjMTg1ZmVjMjg3ZmVjNDg4ZmVjNjhhZmVjODhjZmVjYThkZmVjYzhmZmVjZDkwZmVjZjkyZmVkMTk0ZmVkMzk1ZmVkNTk3ZmVkNzk5ZmVkODlhZmRkYTljZmRkYzllZmRkZWEwZmRlMGExZmRlMmEzZmRlM2E1ZmRlNWE3ZmRlN2E5ZmRlOWFhZmRlYmFjZmNlY2FlZmNlZWIwZmNmMGIyZmNmMmI0ZmNmNGI2ZmNmNmI4ZmNmN2I5ZmNmOWJiZmNmYmJkZmNmZGJmIikpLEU3dD1yNyhkbCgiMDAwMDA0MDEwMDA1MDEwMTA2MDEwMTA4MDIwMTBhMDIwMjBjMDIwMjBlMDMwMjEwMDQwMzEyMDQwMzE0MDUwNDE3MDYwNDE5MDcwNTFiMDgwNTFkMDkwNjFmMGEwNzIyMGIwNzI0MGMwODI2MGQwODI5MGUwOTJiMTAwOTJkMTEwYTMwMTIwYTMyMTQwYjM0MTUwYjM3MTYwYjM5MTgwYzNjMTkwYzNlMWIwYzQxMWMwYzQzMWUwYzQ1MWYwYzQ4MjEwYzRhMjMwYzRjMjQwYzRmMjYwYzUxMjgwYjUzMjkwYjU1MmIwYjU3MmQwYjU5MmYwYTViMzEwYTVjMzIwYTVlMzQwYTVmMzYwOTYxMzgwOTYyMzkwOTYzM2IwOTY0M2QwOTY1M2UwOTY2NDAwYTY3NDIwYTY4NDQwYTY4NDUwYTY5NDcwYjZhNDkwYjZhNGEwYzZiNGMwYzZiNGQwZDZjNGYwZDZjNTEwZTZjNTIwZTZkNTQwZjZkNTUwZjZkNTcxMDZlNTkxMDZlNWExMTZlNWMxMjZlNWQxMjZlNWYxMzZlNjExMzZlNjIxNDZlNjQxNTZlNjUxNTZlNjcxNjZlNjkxNjZlNmExNzZlNmMxODZlNmQxODZlNmYxOTZlNzExOTZlNzIxYTZlNzQxYTZlNzUxYjZlNzcxYzZkNzgxYzZkN2ExZDZkN2MxZDZkN2QxZTZkN2YxZTZjODAxZjZjODIyMDZjODQyMDZiODUyMTZiODcyMTZiODgyMjZhOGEyMjZhOGMyMzY5OGQyMzY5OGYyNDY5OTAyNTY4OTIyNTY4OTMyNjY3OTUyNjY3OTcyNzY2OTgyNzY2OWEyODY1OWIyOTY0OWQyOTY0OWYyYTYzYTAyYTYzYTIyYjYyYTMyYzYxYTUyYzYwYTYyZDYwYTgyZTVmYTkyZTVlYWIyZjVlYWQzMDVkYWUzMDVjYjAzMTViYjEzMjVhYjMzMjVhYjQzMzU5YjYzNDU4YjczNTU3YjkzNTU2YmEzNjU1YmMzNzU0YmQzODUzYmYzOTUyYzAzYTUxYzEzYTUwYzMzYjRmYzQzYzRlYzYzZDRkYzczZTRjYzgzZjRiY2E0MDRhY2I0MTQ5Y2M0MjQ4Y2U0MzQ3Y2Y0NDQ2ZDA0NTQ1ZDI0NjQ0ZDM0NzQzZDQ0ODQyZDU0YTQxZDc0YjNmZDg0YzNlZDk0ZDNkZGE0ZTNjZGI1MDNiZGQ1MTNhZGU1MjM4ZGY1MzM3ZTA1NTM2ZTE1NjM1ZTI1NzM0ZTM1OTMzZTQ1YTMxZTU1YzMwZTY1ZDJmZTc1ZTJlZTg2MDJkZTk2MTJiZWE2MzJhZWI2NDI5ZWI2NjI4ZWM2NzI2ZWQ2OTI1ZWU2YTI0ZWY2YzIzZWY2ZTIxZjA2ZjIwZjE3MTFmZjE3MzFkZjI3NDFjZjM3NjFiZjM3ODE5ZjQ3OTE4ZjU3YjE3ZjU3ZDE1ZjY3ZTE0ZjY4MDEzZjc4MjEyZjc4NDEwZjg4NTBmZjg4NzBlZjg4OTBjZjk4YjBiZjk4YzBhZjk4ZTA5ZmE5MDA4ZmE5MjA3ZmE5NDA3ZmI5NjA2ZmI5NzA2ZmI5OTA2ZmI5YjA2ZmI5ZDA3ZmM5ZjA3ZmNhMTA4ZmNhMzA5ZmNhNTBhZmNhNjBjZmNhODBkZmNhYTBmZmNhYzExZmNhZTEyZmNiMDE0ZmNiMjE2ZmNiNDE4ZmJiNjFhZmJiODFkZmJiYTFmZmJiYzIxZmJiZTIzZmFjMDI2ZmFjMjI4ZmFjNDJhZmFjNjJkZjljNzJmZjljOTMyZjljYjM1ZjhjZDM3ZjhjZjNhZjdkMTNkZjdkMzQwZjZkNTQzZjZkNzQ2ZjVkOTQ5ZjVkYjRjZjRkZDRmZjRkZjUzZjRlMTU2ZjNlMzVhZjNlNTVkZjJlNjYxZjJlODY1ZjJlYTY5ZjFlYzZkZjFlZDcxZjFlZjc1ZjFmMTc5ZjJmMjdkZjJmNDgyZjNmNTg2ZjNmNjhhZjRmODhlZjVmOTkyZjZmYTk2ZjhmYjlhZjlmYzlkZmFmZGExZmNmZmE0IikpLFQ3dD1yNyhkbCgiMGQwODg3MTAwNzg4MTMwNzg5MTYwNzhhMTkwNjhjMWIwNjhkMWQwNjhlMjAwNjhmMjIwNjkwMjQwNjkxMjYwNTkxMjgwNTkyMmEwNTkzMmMwNTk0MmUwNTk1MmYwNTk2MzEwNTk3MzMwNTk3MzUwNDk4MzcwNDk5MzgwNDlhM2EwNDlhM2MwNDliM2UwNDljM2YwNDljNDEwNDlkNDMwMzllNDQwMzllNDYwMzlmNDgwMzlmNDkwM2EwNGIwM2ExNGMwMmExNGUwMmEyNTAwMmEyNTEwMmEzNTMwMmEzNTUwMmE0NTYwMWE0NTgwMWE0NTkwMWE1NWIwMWE1NWMwMWE2NWUwMWE2NjAwMWE2NjEwMGE3NjMwMGE3NjQwMGE3NjYwMGE3NjcwMGE4NjkwMGE4NmEwMGE4NmMwMGE4NmUwMGE4NmYwMGE4NzEwMGE4NzIwMWE4NzQwMWE4NzUwMWE4NzcwMWE4NzgwMWE4N2EwMmE4N2IwMmE4N2QwM2E4N2UwM2E4ODAwNGE4ODEwNGE3ODMwNWE3ODQwNWE3ODYwNmE2ODcwN2E2ODgwOGE2OGEwOWE1OGIwYWE1OGQwYmE1OGUwY2E0OGYwZGE0OTEwZWEzOTIwZmEzOTQxMGEyOTUxMWExOTYxM2ExOTgxNGEwOTkxNTlmOWExNjlmOWMxNzllOWQxODlkOWUxOTlkYTAxYTljYTExYjliYTIxZDlhYTMxZTlhYTUxZjk5YTYyMDk4YTcyMTk3YTgyMjk2YWEyMzk1YWIyNDk0YWMyNjk0YWQyNzkzYWUyODkyYjAyOTkxYjEyYTkwYjIyYjhmYjMyYzhlYjQyZThkYjUyZjhjYjYzMDhiYjczMThhYjgzMjg5YmEzMzg4YmIzNDg4YmMzNTg3YmQzNzg2YmUzODg1YmYzOTg0YzAzYTgzYzEzYjgyYzIzYzgxYzMzZDgwYzQzZTdmYzU0MDdlYzY0MTdkYzc0MjdjYzg0MzdiYzk0NDdhY2E0NTdhY2I0Njc5Y2M0Nzc4Y2M0OTc3Y2Q0YTc2Y2U0Yjc1Y2Y0Yzc0ZDA0ZDczZDE0ZTcyZDI0ZjcxZDM1MTcxZDQ1MjcwZDU1MzZmZDU1NDZlZDY1NTZkZDc1NjZjZDg1NzZiZDk1ODZhZGE1YTZhZGE1YjY5ZGI1YzY4ZGM1ZDY3ZGQ1ZTY2ZGU1ZjY1ZGU2MTY0ZGY2MjYzZTA2MzYzZTE2NDYyZTI2NTYxZTI2NjYwZTM2ODVmZTQ2OTVlZTU2YTVkZTU2YjVkZTY2YzVjZTc2ZTViZTc2ZjVhZTg3MDU5ZTk3MTU4ZTk3MjU3ZWE3NDU3ZWI3NTU2ZWI3NjU1ZWM3NzU0ZWQ3OTUzZWQ3YTUyZWU3YjUxZWY3YzUxZWY3ZTUwZjA3ZjRmZjA4MDRlZjE4MTRkZjE4MzRjZjI4NDRiZjM4NTRiZjM4NzRhZjQ4ODQ5ZjQ4OTQ4ZjU4YjQ3ZjU4YzQ2ZjY4ZDQ1ZjY4ZjQ0Zjc5MDQ0Zjc5MTQzZjc5MzQyZjg5NDQxZjg5NTQwZjk5NzNmZjk5ODNlZjk5YTNlZmE5YjNkZmE5YzNjZmE5ZTNiZmI5ZjNhZmJhMTM5ZmJhMjM4ZmNhMzM4ZmNhNTM3ZmNhNjM2ZmNhODM1ZmNhOTM0ZmRhYjMzZmRhYzMzZmRhZTMyZmRhZjMxZmRiMTMwZmRiMjJmZmRiNDJmZmRiNTJlZmViNzJkZmViODJjZmViYTJjZmViYjJiZmViZDJhZmViZTJhZmVjMDI5ZmRjMjI5ZmRjMzI4ZmRjNTI3ZmRjNjI3ZmRjODI3ZmRjYTI2ZmRjYjI2ZmNjZDI1ZmNjZTI1ZmNkMDI1ZmNkMjI1ZmJkMzI0ZmJkNTI0ZmJkNzI0ZmFkODI0ZmFkYTI0ZjlkYzI0ZjlkZDI1ZjhkZjI1ZjhlMTI1ZjdlMjI1ZjdlNDI1ZjZlNjI2ZjZlODI2ZjVlOTI2ZjVlYjI3ZjRlZDI3ZjNlZTI3ZjNmMDI3ZjJmMjI3ZjFmNDI2ZjFmNTI1ZjBmNzI0ZjBmOTIxIikpfSk7ZnVuY3Rpb24gbjcoZSl7dmFyIHQ9MCxyPTEsbj0hMTtmdW5jdGlvbiBpKG8pe3ZhciBhPShvLXQpLyhyLXQpO3JldHVybiBlKG4/TWF0aC5tYXgoMCxNYXRoLm1pbigxLGEpKTphKX1yZXR1cm4gaS5kb21haW49ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9K29bMF0scj0rb1sxXSxpKTpbdCxyXX0saS5jbGFtcD1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj0hIW8saSk6bn0saS5pbnRlcnBvbGF0b3I9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9byxpKTplfSxpLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gbjcoZSkuZG9tYWluKFt0LHJdKS5jbGFtcChuKX0sb2QoaSl9dmFyIEE3dD1NKCgpPT57cHcoKX0pO3ZhciBQN3Q9TSgoKT0+e1VOdCgpO0tEdCgpO3B3KCk7dE90KCk7YnR0KCk7ck90KCk7bk90KCk7aU90KCk7b090KCk7cGV0KCk7bDd0KCk7dTd0KCk7Zjd0KCk7ZDd0KCk7Zzd0KCk7eTd0KCk7dzd0KCk7Qzd0KCk7QTd0KCl9KTt2YXIgaTcsTUMsbzc9TSgoKT0+e2k3PSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIixNQz17c3ZnOiJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIseGh0bWw6aTcseGxpbms6Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiLHhtbDoiaHR0cDovL3d3dy53My5vcmcvWE1MLzE5OTgvbmFtZXNwYWNlIix4bWxuczoiaHR0cDovL3d3dy53My5vcmcvMjAwMC94bWxucy8ifX0pO2Z1bmN0aW9uIE13KGUpe3ZhciB0PWUrPSIiLHI9dC5pbmRleE9mKCI6Iik7cmV0dXJuIHI+PTAmJih0PWUuc2xpY2UoMCxyKSkhPT0ieG1sbnMiJiYoZT1lLnNsaWNlKHIrMSkpLE1DLmhhc093blByb3BlcnR5KHQpP3tzcGFjZTpNQ1t0XSxsb2NhbDplfTplfXZhciBhNz1NKCgpPT57bzcoKX0pO2Z1bmN0aW9uIFM5ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD10aGlzLm93bmVyRG9jdW1lbnQscj10aGlzLm5hbWVzcGFjZVVSSTtyZXR1cm4gcj09PWk3JiZ0LmRvY3VtZW50RWxlbWVudC5uYW1lc3BhY2VVUkk9PT1pNz90LmNyZWF0ZUVsZW1lbnQoZSk6dC5jcmVhdGVFbGVtZW50TlMocixlKX19ZnVuY3Rpb24gTTllKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiB0aGlzLm93bmVyRG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKGUuc3BhY2UsZS5sb2NhbCl9fWZ1bmN0aW9uIE5nKGUpe3ZhciB0PU13KGUpO3JldHVybih0LmxvY2FsP005ZTpTOWUpKHQpfXZhciBFQz1NKCgpPT57YTcoKTtvNygpfSk7ZnVuY3Rpb24gRTllKCl7fWZ1bmN0aW9uIEV3KGUpe3JldHVybiBlPT1udWxsP0U5ZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLnF1ZXJ5U2VsZWN0b3IoZSl9fXZhciBzNz1NKCgpPT57fSk7ZnVuY3Rpb24gSTd0KGUpe3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1FdyhlKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPXQubGVuZ3RoLG49bmV3IEFycmF5KHIpLGk9MDtpPHI7KytpKWZvcih2YXIgbz10W2ldLGE9by5sZW5ndGgscz1uW2ldPW5ldyBBcnJheShhKSxsLGMsdT0wO3U8YTsrK3UpKGw9b1t1XSkmJihjPWUuY2FsbChsLGwuX19kYXRhX18sdSxvKSkmJigiX19kYXRhX18iaW4gbCYmKGMuX19kYXRhX189bC5fX2RhdGFfXyksc1t1XT1jKTtyZXR1cm4gbmV3IEhuKG4sdGhpcy5fcGFyZW50cyl9dmFyIEw3dD1NKCgpPT57U2MoKTtzNygpfSk7ZnVuY3Rpb24gVDllKCl7cmV0dXJuW119ZnVuY3Rpb24gbDcoZSl7cmV0dXJuIGU9PW51bGw/VDllOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMucXVlcnlTZWxlY3RvckFsbChlKX19dmFyIGRldD1NKCgpPT57fSk7ZnVuY3Rpb24gazd0KGUpe3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1sNyhlKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPXQubGVuZ3RoLG49W10saT1bXSxvPTA7bzxyOysrbylmb3IodmFyIGE9dFtvXSxzPWEubGVuZ3RoLGwsYz0wO2M8czsrK2MpKGw9YVtjXSkmJihuLnB1c2goZS5jYWxsKGwsbC5fX2RhdGFfXyxjLGEpKSxpLnB1c2gobCkpO3JldHVybiBuZXcgSG4obixpKX12YXIgUjd0PU0oKCk9PntTYygpO2RldCgpfSk7dmFyIEQ3dCxUdyxON3QsYzcsbWV0PU0oKCk9PntEN3Q9ZnVuY3Rpb24oZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMubWF0Y2hlcyhlKX19O3R5cGVvZiBkb2N1bWVudCE9InVuZGVmaW5lZCImJihUdz1kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsVHcubWF0Y2hlc3x8KE43dD1Udy53ZWJraXRNYXRjaGVzU2VsZWN0b3J8fFR3Lm1zTWF0Y2hlc1NlbGVjdG9yfHxUdy5tb3pNYXRjaGVzU2VsZWN0b3J8fFR3Lm9NYXRjaGVzU2VsZWN0b3IsRDd0PWZ1bmN0aW9uKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBON3QuY2FsbCh0aGlzLGUpfX0pKTtjNz1EN3R9KTtmdW5jdGlvbiBPN3QoZSl7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPWM3KGUpKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9dC5sZW5ndGgsbj1uZXcgQXJyYXkociksaT0wO2k8cjsrK2kpZm9yKHZhciBvPXRbaV0sYT1vLmxlbmd0aCxzPW5baV09W10sbCxjPTA7YzxhOysrYykobD1vW2NdKSYmZS5jYWxsKGwsbC5fX2RhdGFfXyxjLG8pJiZzLnB1c2gobCk7cmV0dXJuIG5ldyBIbihuLHRoaXMuX3BhcmVudHMpfXZhciB6N3Q9TSgoKT0+e1NjKCk7bWV0KCl9KTtmdW5jdGlvbiB1NyhlKXtyZXR1cm4gbmV3IEFycmF5KGUubGVuZ3RoKX12YXIgZ2V0PU0oKCk9Pnt9KTtmdW5jdGlvbiBGN3QoKXtyZXR1cm4gbmV3IEhuKHRoaXMuX2VudGVyfHx0aGlzLl9ncm91cHMubWFwKHU3KSx0aGlzLl9wYXJlbnRzKX1mdW5jdGlvbiBUQyhlLHQpe3RoaXMub3duZXJEb2N1bWVudD1lLm93bmVyRG9jdW1lbnQsdGhpcy5uYW1lc3BhY2VVUkk9ZS5uYW1lc3BhY2VVUkksdGhpcy5fbmV4dD1udWxsLHRoaXMuX3BhcmVudD1lLHRoaXMuX19kYXRhX189dH12YXIgX2V0PU0oKCk9PntnZXQoKTtTYygpO1RDLnByb3RvdHlwZT17Y29uc3RydWN0b3I6VEMsYXBwZW5kQ2hpbGQ6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX3BhcmVudC5pbnNlcnRCZWZvcmUoZSx0aGlzLl9uZXh0KX0saW5zZXJ0QmVmb3JlOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIHRoaXMuX3BhcmVudC5pbnNlcnRCZWZvcmUoZSx0KX0scXVlcnlTZWxlY3RvcjpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fcGFyZW50LnF1ZXJ5U2VsZWN0b3IoZSl9LHF1ZXJ5U2VsZWN0b3JBbGw6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX3BhcmVudC5xdWVyeVNlbGVjdG9yQWxsKGUpfX19KTtmdW5jdGlvbiBCN3QoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fXZhciBIN3Q9TSgoKT0+e30pO2Z1bmN0aW9uIEM5ZShlLHQscixuLGksbyl7Zm9yKHZhciBhPTAscyxsPXQubGVuZ3RoLGM9by5sZW5ndGg7YTxjOysrYSkocz10W2FdKT8ocy5fX2RhdGFfXz1vW2FdLG5bYV09cyk6clthXT1uZXcgVEMoZSxvW2FdKTtmb3IoO2E8bDsrK2EpKHM9dFthXSkmJihpW2FdPXMpfWZ1bmN0aW9uIEE5ZShlLHQscixuLGksbyxhKXt2YXIgcyxsLGM9e30sdT10Lmxlbmd0aCxoPW8ubGVuZ3RoLGY9bmV3IEFycmF5KHUpLHA7Zm9yKHM9MDtzPHU7KytzKShsPXRbc10pJiYoZltzXT1wPVY3dCthLmNhbGwobCxsLl9fZGF0YV9fLHMsdCkscCBpbiBjP2lbc109bDpjW3BdPWwpO2ZvcihzPTA7czxoOysrcylwPVY3dCthLmNhbGwoZSxvW3NdLHMsbyksKGw9Y1twXSk/KG5bc109bCxsLl9fZGF0YV9fPW9bc10sY1twXT1udWxsKTpyW3NdPW5ldyBUQyhlLG9bc10pO2ZvcihzPTA7czx1OysrcykobD10W3NdKSYmY1tmW3NdXT09PWwmJihpW3NdPWwpfWZ1bmN0aW9uIFU3dChlLHQpe2lmKCFlKXJldHVybiBwPW5ldyBBcnJheSh0aGlzLnNpemUoKSksYz0tMSx0aGlzLmVhY2goZnVuY3Rpb24oUCl7cFsrK2NdPVB9KSxwO3ZhciByPXQ/QTllOkM5ZSxuPXRoaXMuX3BhcmVudHMsaT10aGlzLl9ncm91cHM7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPUI3dChlKSk7Zm9yKHZhciBvPWkubGVuZ3RoLGE9bmV3IEFycmF5KG8pLHM9bmV3IEFycmF5KG8pLGw9bmV3IEFycmF5KG8pLGM9MDtjPG87KytjKXt2YXIgdT1uW2NdLGg9aVtjXSxmPWgubGVuZ3RoLHA9ZS5jYWxsKHUsdSYmdS5fX2RhdGFfXyxjLG4pLGQ9cC5sZW5ndGgsZz1zW2NdPW5ldyBBcnJheShkKSxfPWFbY109bmV3IEFycmF5KGQpLHk9bFtjXT1uZXcgQXJyYXkoZik7cih1LGgsZyxfLHkscCx0KTtmb3IodmFyIHg9MCxiPTAsUyxDO3g8ZDsrK3gpaWYoUz1nW3hdKXtmb3IoeD49YiYmKGI9eCsxKTshKEM9X1tiXSkmJisrYjxkOyk7Uy5fbmV4dD1DfHxudWxsfX1yZXR1cm4gYT1uZXcgSG4oYSxuKSxhLl9lbnRlcj1zLGEuX2V4aXQ9bCxhfXZhciBWN3QscTd0PU0oKCk9PntTYygpO19ldCgpO0g3dCgpO1Y3dD0iJCJ9KTtmdW5jdGlvbiBHN3QoKXtyZXR1cm4gbmV3IEhuKHRoaXMuX2V4aXR8fHRoaXMuX2dyb3Vwcy5tYXAodTcpLHRoaXMuX3BhcmVudHMpfXZhciBXN3Q9TSgoKT0+e2dldCgpO1NjKCl9KTtmdW5jdGlvbiBZN3QoZSl7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPWUuX2dyb3VwcyxuPXQubGVuZ3RoLGk9ci5sZW5ndGgsbz1NYXRoLm1pbihuLGkpLGE9bmV3IEFycmF5KG4pLHM9MDtzPG87KytzKWZvcih2YXIgbD10W3NdLGM9cltzXSx1PWwubGVuZ3RoLGg9YVtzXT1uZXcgQXJyYXkodSksZixwPTA7cDx1OysrcCkoZj1sW3BdfHxjW3BdKSYmKGhbcF09Zik7Zm9yKDtzPG47KytzKWFbc109dFtzXTtyZXR1cm4gbmV3IEhuKGEsdGhpcy5fcGFyZW50cyl9dmFyIGo3dD1NKCgpPT57U2MoKX0pO2Z1bmN0aW9uIFg3dCgpe2Zvcih2YXIgZT10aGlzLl9ncm91cHMsdD0tMSxyPWUubGVuZ3RoOysrdDxyOylmb3IodmFyIG49ZVt0XSxpPW4ubGVuZ3RoLTEsbz1uW2ldLGE7LS1pPj0wOykoYT1uW2ldKSYmKG8mJm8hPT1hLm5leHRTaWJsaW5nJiZvLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGEsbyksbz1hKTtyZXR1cm4gdGhpc312YXIgJDd0PU0oKCk9Pnt9KTtmdW5jdGlvbiBLN3QoZSl7ZXx8KGU9UDllKTtmdW5jdGlvbiB0KGgsZil7cmV0dXJuIGgmJmY/ZShoLl9fZGF0YV9fLGYuX19kYXRhX18pOiFoLSFmfWZvcih2YXIgcj10aGlzLl9ncm91cHMsbj1yLmxlbmd0aCxpPW5ldyBBcnJheShuKSxvPTA7bzxuOysrbyl7Zm9yKHZhciBhPXJbb10scz1hLmxlbmd0aCxsPWlbb109bmV3IEFycmF5KHMpLGMsdT0wO3U8czsrK3UpKGM9YVt1XSkmJihsW3VdPWMpO2wuc29ydCh0KX1yZXR1cm4gbmV3IEhuKGksdGhpcy5fcGFyZW50cykub3JkZXIoKX1mdW5jdGlvbiBQOWUoZSx0KXtyZXR1cm4gZTx0Py0xOmU+dD8xOmU+PXQ/MDpOYU59dmFyIFo3dD1NKCgpPT57U2MoKX0pO2Z1bmN0aW9uIEo3dCgpe3ZhciBlPWFyZ3VtZW50c1swXTtyZXR1cm4gYXJndW1lbnRzWzBdPXRoaXMsZS5hcHBseShudWxsLGFyZ3VtZW50cyksdGhpc312YXIgUTd0PU0oKCk9Pnt9KTtmdW5jdGlvbiB0enQoKXt2YXIgZT1uZXcgQXJyYXkodGhpcy5zaXplKCkpLHQ9LTE7cmV0dXJuIHRoaXMuZWFjaChmdW5jdGlvbigpe2VbKyt0XT10aGlzfSksZX12YXIgZXp0PU0oKCk9Pnt9KTtmdW5jdGlvbiByenQoKXtmb3IodmFyIGU9dGhpcy5fZ3JvdXBzLHQ9MCxyPWUubGVuZ3RoO3Q8cjsrK3QpZm9yKHZhciBuPWVbdF0saT0wLG89bi5sZW5ndGg7aTxvOysraSl7dmFyIGE9bltpXTtpZihhKXJldHVybiBhfXJldHVybiBudWxsfXZhciBuenQ9TSgoKT0+e30pO2Z1bmN0aW9uIGl6dCgpe3ZhciBlPTA7cmV0dXJuIHRoaXMuZWFjaChmdW5jdGlvbigpeysrZX0pLGV9dmFyIG96dD1NKCgpPT57fSk7ZnVuY3Rpb24gYXp0KCl7cmV0dXJuIXRoaXMubm9kZSgpfXZhciBzenQ9TSgoKT0+e30pO2Z1bmN0aW9uIGx6dChlKXtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9MCxuPXQubGVuZ3RoO3I8bjsrK3IpZm9yKHZhciBpPXRbcl0sbz0wLGE9aS5sZW5ndGgscztvPGE7KytvKShzPWlbb10pJiZlLmNhbGwocyxzLl9fZGF0YV9fLG8saSk7cmV0dXJuIHRoaXN9dmFyIGN6dD1NKCgpPT57fSk7ZnVuY3Rpb24gSTllKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlKGUpfX1mdW5jdGlvbiBMOWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpfX1mdW5jdGlvbiBrOWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZShlLHQpfX1mdW5jdGlvbiBSOWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCx0KX19ZnVuY3Rpb24gTjllKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHI9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cj09bnVsbD90aGlzLnJlbW92ZUF0dHJpYnV0ZShlKTp0aGlzLnNldEF0dHJpYnV0ZShlLHIpfX1mdW5jdGlvbiBEOWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgcj10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyPT1udWxsP3RoaXMucmVtb3ZlQXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKTp0aGlzLnNldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCxyKX19ZnVuY3Rpb24gdXp0KGUsdCl7dmFyIHI9TXcoZSk7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXt2YXIgbj10aGlzLm5vZGUoKTtyZXR1cm4gci5sb2NhbD9uLmdldEF0dHJpYnV0ZU5TKHIuc3BhY2Usci5sb2NhbCk6bi5nZXRBdHRyaWJ1dGUocil9cmV0dXJuIHRoaXMuZWFjaCgodD09bnVsbD9yLmxvY2FsP0w5ZTpJOWU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/ci5sb2NhbD9EOWU6TjllOnIubG9jYWw/UjllOms5ZSkocix0KSl9dmFyIGh6dD1NKCgpPT57YTcoKX0pO2Z1bmN0aW9uIEN3KGUpe3JldHVybiBlLm93bmVyRG9jdW1lbnQmJmUub3duZXJEb2N1bWVudC5kZWZhdWx0Vmlld3x8ZS5kb2N1bWVudCYmZXx8ZS5kZWZhdWx0Vmlld312YXIgaDc9TSgoKT0+e30pO2Z1bmN0aW9uIE85ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KGUpfX1mdW5jdGlvbiB6OWUoZSx0LHIpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc3R5bGUuc2V0UHJvcGVydHkoZSx0LHIpfX1mdW5jdGlvbiBGOWUoZSx0LHIpe3JldHVybiBmdW5jdGlvbigpe3ZhciBuPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO249PW51bGw/dGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShlKTp0aGlzLnN0eWxlLnNldFByb3BlcnR5KGUsbixyKX19ZnVuY3Rpb24gZnp0KGUsdCxyKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD4xP3RoaXMuZWFjaCgodD09bnVsbD9POWU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/RjllOno5ZSkoZSx0LHI9PW51bGw/IiI6cikpOnlldCh0aGlzLm5vZGUoKSxlKX1mdW5jdGlvbiB5ZXQoZSx0KXtyZXR1cm4gZS5zdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKHQpfHxDdyhlKS5nZXRDb21wdXRlZFN0eWxlKGUsbnVsbCkuZ2V0UHJvcGVydHlWYWx1ZSh0KX12YXIgdmV0PU0oKCk9PntoNygpfSk7ZnVuY3Rpb24gQjllKGUpe3JldHVybiBmdW5jdGlvbigpe2RlbGV0ZSB0aGlzW2VdfX1mdW5jdGlvbiBIOWUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzW2VdPXR9fWZ1bmN0aW9uIFY5ZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3ZhciByPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3I9PW51bGw/ZGVsZXRlIHRoaXNbZV06dGhpc1tlXT1yfX1mdW5jdGlvbiBwenQoZSx0KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD4xP3RoaXMuZWFjaCgodD09bnVsbD9COWU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/VjllOkg5ZSkoZSx0KSk6dGhpcy5ub2RlKClbZV19dmFyIGR6dD1NKCgpPT57fSk7ZnVuY3Rpb24gbXp0KGUpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKX1mdW5jdGlvbiB4ZXQoZSl7cmV0dXJuIGUuY2xhc3NMaXN0fHxuZXcgZ3p0KGUpfWZ1bmN0aW9uIGd6dChlKXt0aGlzLl9ub2RlPWUsdGhpcy5fbmFtZXM9bXp0KGUuZ2V0QXR0cmlidXRlKCJjbGFzcyIpfHwiIil9ZnVuY3Rpb24gX3p0KGUsdCl7Zm9yKHZhciByPXhldChlKSxuPS0xLGk9dC5sZW5ndGg7KytuPGk7KXIuYWRkKHRbbl0pfWZ1bmN0aW9uIHl6dChlLHQpe2Zvcih2YXIgcj14ZXQoZSksbj0tMSxpPXQubGVuZ3RoOysrbjxpOylyLnJlbW92ZSh0W25dKX1mdW5jdGlvbiBVOWUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7X3p0KHRoaXMsZSl9fWZ1bmN0aW9uIHE5ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt5enQodGhpcyxlKX19ZnVuY3Rpb24gRzllKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7KHQuYXBwbHkodGhpcyxhcmd1bWVudHMpP196dDp5enQpKHRoaXMsZSl9fWZ1bmN0aW9uIHZ6dChlLHQpe3ZhciByPW16dChlKyIiKTtpZihhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcih2YXIgbj14ZXQodGhpcy5ub2RlKCkpLGk9LTEsbz1yLmxlbmd0aDsrK2k8bzspaWYoIW4uY29udGFpbnMocltpXSkpcmV0dXJuITE7cmV0dXJuITB9cmV0dXJuIHRoaXMuZWFjaCgodHlwZW9mIHQ9PSJmdW5jdGlvbiI/RzllOnQ/VTllOnE5ZSkocix0KSl9dmFyIHh6dD1NKCgpPT57Z3p0LnByb3RvdHlwZT17YWRkOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX25hbWVzLmluZGV4T2YoZSk7dDwwJiYodGhpcy5fbmFtZXMucHVzaChlKSx0aGlzLl9ub2RlLnNldEF0dHJpYnV0ZSgiY2xhc3MiLHRoaXMuX25hbWVzLmpvaW4oIiAiKSkpfSxyZW1vdmU6ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy5fbmFtZXMuaW5kZXhPZihlKTt0Pj0wJiYodGhpcy5fbmFtZXMuc3BsaWNlKHQsMSksdGhpcy5fbm9kZS5zZXRBdHRyaWJ1dGUoImNsYXNzIix0aGlzLl9uYW1lcy5qb2luKCIgIikpKX0sY29udGFpbnM6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX25hbWVzLmluZGV4T2YoZSk+PTB9fX0pO2Z1bmN0aW9uIFc5ZSgpe3RoaXMudGV4dENvbnRlbnQ9IiJ9ZnVuY3Rpb24gWTllKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMudGV4dENvbnRlbnQ9ZX19ZnVuY3Rpb24gajllKGUpe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PWUuYXBwbHkodGhpcyxhcmd1bWVudHMpO3RoaXMudGV4dENvbnRlbnQ9dD09bnVsbD8iIjp0fX1mdW5jdGlvbiBienQoZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5lYWNoKGU9PW51bGw/VzllOih0eXBlb2YgZT09ImZ1bmN0aW9uIj9qOWU6WTllKShlKSk6dGhpcy5ub2RlKCkudGV4dENvbnRlbnR9dmFyIHd6dD1NKCgpPT57fSk7ZnVuY3Rpb24gWDllKCl7dGhpcy5pbm5lckhUTUw9IiJ9ZnVuY3Rpb24gJDllKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuaW5uZXJIVE1MPWV9fWZ1bmN0aW9uIEs5ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD1lLmFwcGx5KHRoaXMsYXJndW1lbnRzKTt0aGlzLmlubmVySFRNTD10PT1udWxsPyIiOnR9fWZ1bmN0aW9uIFN6dChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2goZT09bnVsbD9YOWU6KHR5cGVvZiBlPT0iZnVuY3Rpb24iP0s5ZTokOWUpKGUpKTp0aGlzLm5vZGUoKS5pbm5lckhUTUx9dmFyIE16dD1NKCgpPT57fSk7ZnVuY3Rpb24gWjllKCl7dGhpcy5uZXh0U2libGluZyYmdGhpcy5wYXJlbnROb2RlLmFwcGVuZENoaWxkKHRoaXMpfWZ1bmN0aW9uIEV6dCgpe3JldHVybiB0aGlzLmVhY2goWjllKX12YXIgVHp0PU0oKCk9Pnt9KTtmdW5jdGlvbiBKOWUoKXt0aGlzLnByZXZpb3VzU2libGluZyYmdGhpcy5wYXJlbnROb2RlLmluc2VydEJlZm9yZSh0aGlzLHRoaXMucGFyZW50Tm9kZS5maXJzdENoaWxkKX1mdW5jdGlvbiBDenQoKXtyZXR1cm4gdGhpcy5lYWNoKEo5ZSl9dmFyIEF6dD1NKCgpPT57fSk7ZnVuY3Rpb24gUHp0KGUpe3ZhciB0PXR5cGVvZiBlPT0iZnVuY3Rpb24iP2U6TmcoZSk7cmV0dXJuIHRoaXMuc2VsZWN0KGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuYXBwZW5kQ2hpbGQodC5hcHBseSh0aGlzLGFyZ3VtZW50cykpfSl9dmFyIEl6dD1NKCgpPT57RUMoKX0pO2Z1bmN0aW9uIFE5ZSgpe3JldHVybiBudWxsfWZ1bmN0aW9uIEx6dChlLHQpe3ZhciByPXR5cGVvZiBlPT0iZnVuY3Rpb24iP2U6TmcoZSksbj10PT1udWxsP1E5ZTp0eXBlb2YgdD09ImZ1bmN0aW9uIj90OkV3KHQpO3JldHVybiB0aGlzLnNlbGVjdChmdW5jdGlvbigpe3JldHVybiB0aGlzLmluc2VydEJlZm9yZShyLmFwcGx5KHRoaXMsYXJndW1lbnRzKSxuLmFwcGx5KHRoaXMsYXJndW1lbnRzKXx8bnVsbCl9KX12YXIga3p0PU0oKCk9PntFQygpO3M3KCl9KTtmdW5jdGlvbiB0TGUoKXt2YXIgZT10aGlzLnBhcmVudE5vZGU7ZSYmZS5yZW1vdmVDaGlsZCh0aGlzKX1mdW5jdGlvbiBSenQoKXtyZXR1cm4gdGhpcy5lYWNoKHRMZSl9dmFyIE56dD1NKCgpPT57fSk7ZnVuY3Rpb24gZUxlKCl7cmV0dXJuIHRoaXMucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUodGhpcy5jbG9uZU5vZGUoITEpLHRoaXMubmV4dFNpYmxpbmcpfWZ1bmN0aW9uIHJMZSgpe3JldHVybiB0aGlzLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKHRoaXMuY2xvbmVOb2RlKCEwKSx0aGlzLm5leHRTaWJsaW5nKX1mdW5jdGlvbiBEenQoZSl7cmV0dXJuIHRoaXMuc2VsZWN0KGU/ckxlOmVMZSl9dmFyIE96dD1NKCgpPT57fSk7ZnVuY3Rpb24genp0KGUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMucHJvcGVydHkoIl9fZGF0YV9fIixlKTp0aGlzLm5vZGUoKS5fX2RhdGFfX312YXIgRnp0PU0oKCk9Pnt9KTtmdW5jdGlvbiBuTGUoZSx0LHIpe3JldHVybiBlPVZ6dChlLHQsciksZnVuY3Rpb24obil7dmFyIGk9bi5yZWxhdGVkVGFyZ2V0OyghaXx8aSE9PXRoaXMmJiEoaS5jb21wYXJlRG9jdW1lbnRQb3NpdGlvbih0aGlzKSY4KSkmJmUuY2FsbCh0aGlzLG4pfX1mdW5jdGlvbiBWenQoZSx0LHIpe3JldHVybiBmdW5jdGlvbihuKXt2YXIgaT1QdTtQdT1uO3RyeXtlLmNhbGwodGhpcyx0aGlzLl9fZGF0YV9fLHQscil9ZmluYWxseXtQdT1pfX19ZnVuY3Rpb24gaUxlKGUpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKS5tYXAoZnVuY3Rpb24odCl7dmFyIHI9IiIsbj10LmluZGV4T2YoIi4iKTtyZXR1cm4gbj49MCYmKHI9dC5zbGljZShuKzEpLHQ9dC5zbGljZSgwLG4pKSx7dHlwZTp0LG5hbWU6cn19KX1mdW5jdGlvbiBvTGUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5fX29uO2lmKCEhdCl7Zm9yKHZhciByPTAsbj0tMSxpPXQubGVuZ3RoLG87cjxpOysrcilvPXRbcl0sKCFlLnR5cGV8fG8udHlwZT09PWUudHlwZSkmJm8ubmFtZT09PWUubmFtZT90aGlzLnJlbW92ZUV2ZW50TGlzdGVuZXIoby50eXBlLG8ubGlzdGVuZXIsby5jYXB0dXJlKTp0Wysrbl09bzsrK24/dC5sZW5ndGg9bjpkZWxldGUgdGhpcy5fX29ufX19ZnVuY3Rpb24gYUxlKGUsdCxyKXt2YXIgbj1IenQuaGFzT3duUHJvcGVydHkoZS50eXBlKT9uTGU6Vnp0O3JldHVybiBmdW5jdGlvbihpLG8sYSl7dmFyIHM9dGhpcy5fX29uLGwsYz1uKHQsbyxhKTtpZihzKXtmb3IodmFyIHU9MCxoPXMubGVuZ3RoO3U8aDsrK3UpaWYoKGw9c1t1XSkudHlwZT09PWUudHlwZSYmbC5uYW1lPT09ZS5uYW1lKXt0aGlzLnJlbW92ZUV2ZW50TGlzdGVuZXIobC50eXBlLGwubGlzdGVuZXIsbC5jYXB0dXJlKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIobC50eXBlLGwubGlzdGVuZXI9YyxsLmNhcHR1cmU9ciksbC52YWx1ZT10O3JldHVybn19dGhpcy5hZGRFdmVudExpc3RlbmVyKGUudHlwZSxjLHIpLGw9e3R5cGU6ZS50eXBlLG5hbWU6ZS5uYW1lLHZhbHVlOnQsbGlzdGVuZXI6YyxjYXB0dXJlOnJ9LHM/cy5wdXNoKGwpOnRoaXMuX19vbj1bbF19fWZ1bmN0aW9uIFV6dChlLHQscil7dmFyIG49aUxlKGUrIiIpLGksbz1uLmxlbmd0aCxhO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7dmFyIHM9dGhpcy5ub2RlKCkuX19vbjtpZihzKXtmb3IodmFyIGw9MCxjPXMubGVuZ3RoLHU7bDxjOysrbClmb3IoaT0wLHU9c1tsXTtpPG87KytpKWlmKChhPW5baV0pLnR5cGU9PT11LnR5cGUmJmEubmFtZT09PXUubmFtZSlyZXR1cm4gdS52YWx1ZX1yZXR1cm59Zm9yKHM9dD9hTGU6b0xlLHI9PW51bGwmJihyPSExKSxpPTA7aTxvOysraSl0aGlzLmVhY2gocyhuW2ldLHQscikpO3JldHVybiB0aGlzfWZ1bmN0aW9uIHF6dChlLHQscixuKXt2YXIgaT1QdTtlLnNvdXJjZUV2ZW50PVB1LFB1PWU7dHJ5e3JldHVybiB0LmFwcGx5KHIsbil9ZmluYWxseXtQdT1pfX12YXIgSHp0LFB1LEJ6dCxmNz1NKCgpPT57SHp0PXt9LFB1PW51bGw7dHlwZW9mIGRvY3VtZW50IT0idW5kZWZpbmVkIiYmKEJ6dD1kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsIm9ubW91c2VlbnRlciJpbiBCenR8fChIenQ9e21vdXNlZW50ZXI6Im1vdXNlb3ZlciIsbW91c2VsZWF2ZToibW91c2VvdXQifSkpfSk7ZnVuY3Rpb24gR3p0KGUsdCxyKXt2YXIgbj1DdyhlKSxpPW4uQ3VzdG9tRXZlbnQ7dHlwZW9mIGk9PSJmdW5jdGlvbiI/aT1uZXcgaSh0LHIpOihpPW4uZG9jdW1lbnQuY3JlYXRlRXZlbnQoIkV2ZW50Iikscj8oaS5pbml0RXZlbnQodCxyLmJ1YmJsZXMsci5jYW5jZWxhYmxlKSxpLmRldGFpbD1yLmRldGFpbCk6aS5pbml0RXZlbnQodCwhMSwhMSkpLGUuZGlzcGF0Y2hFdmVudChpKX1mdW5jdGlvbiBzTGUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gR3p0KHRoaXMsZSx0KX19ZnVuY3Rpb24gbExlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIEd6dCh0aGlzLGUsdC5hcHBseSh0aGlzLGFyZ3VtZW50cykpfX1mdW5jdGlvbiBXenQoZSx0KXtyZXR1cm4gdGhpcy5lYWNoKCh0eXBlb2YgdD09ImZ1bmN0aW9uIj9sTGU6c0xlKShlLHQpKX12YXIgWXp0PU0oKCk9PntoNygpfSk7ZnVuY3Rpb24gSG4oZSx0KXt0aGlzLl9ncm91cHM9ZSx0aGlzLl9wYXJlbnRzPXR9ZnVuY3Rpb24ganp0KCl7cmV0dXJuIG5ldyBIbihbW2RvY3VtZW50LmRvY3VtZW50RWxlbWVudF1dLENDKX12YXIgQ0MsWHp0LFNjPU0oKCk9PntMN3QoKTtSN3QoKTt6N3QoKTtxN3QoKTtfZXQoKTtXN3QoKTtqN3QoKTskN3QoKTtaN3QoKTtRN3QoKTtlenQoKTtuenQoKTtvenQoKTtzenQoKTtjenQoKTtoenQoKTt2ZXQoKTtkenQoKTt4enQoKTt3enQoKTtNenQoKTtUenQoKTtBenQoKTtJenQoKTtrenQoKTtOenQoKTtPenQoKTtGenQoKTtmNygpO1l6dCgpO0NDPVtudWxsXTtIbi5wcm90b3R5cGU9anp0LnByb3RvdHlwZT17Y29uc3RydWN0b3I6SG4sc2VsZWN0Okk3dCxzZWxlY3RBbGw6azd0LGZpbHRlcjpPN3QsZGF0YTpVN3QsZW50ZXI6Rjd0LGV4aXQ6Rzd0LG1lcmdlOlk3dCxvcmRlcjpYN3Qsc29ydDpLN3QsY2FsbDpKN3Qsbm9kZXM6dHp0LG5vZGU6cnp0LHNpemU6aXp0LGVtcHR5OmF6dCxlYWNoOmx6dCxhdHRyOnV6dCxzdHlsZTpmenQscHJvcGVydHk6cHp0LGNsYXNzZWQ6dnp0LHRleHQ6Ynp0LGh0bWw6U3p0LHJhaXNlOkV6dCxsb3dlcjpDenQsYXBwZW5kOlB6dCxpbnNlcnQ6THp0LHJlbW92ZTpSenQsY2xvbmU6RHp0LGRhdHVtOnp6dCxvbjpVenQsZGlzcGF0Y2g6V3p0fTtYenQ9anp0fSk7ZnVuY3Rpb24gcDcoZSl7cmV0dXJuIHR5cGVvZiBlPT0ic3RyaW5nIj9uZXcgSG4oW1tkb2N1bWVudC5xdWVyeVNlbGVjdG9yKGUpXV0sW2RvY3VtZW50LmRvY3VtZW50RWxlbWVudF0pOm5ldyBIbihbW2VdXSxDQyl9dmFyIGJldD1NKCgpPT57U2MoKX0pO2Z1bmN0aW9uICR6dChlKXtyZXR1cm4gcDcoTmcoZSkuY2FsbChkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQpKX12YXIgS3p0PU0oKCk9PntFQygpO2JldCgpfSk7ZnVuY3Rpb24gZDcoKXtyZXR1cm4gbmV3IHdldH1mdW5jdGlvbiB3ZXQoKXt0aGlzLl89IkAiKygrK2NMZSkudG9TdHJpbmcoMzYpfXZhciBjTGUsWnp0PU0oKCk9PntjTGU9MDt3ZXQucHJvdG90eXBlPWQ3LnByb3RvdHlwZT17Y29uc3RydWN0b3I6d2V0LGdldDpmdW5jdGlvbihlKXtmb3IodmFyIHQ9dGhpcy5fOyEodCBpbiBlKTspaWYoIShlPWUucGFyZW50Tm9kZSkpcmV0dXJuO3JldHVybiBlW3RdfSxzZXQ6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gZVt0aGlzLl9dPXR9LHJlbW92ZTpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fIGluIGUmJmRlbGV0ZSBlW3RoaXMuX119LHRvU3RyaW5nOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX319fSk7ZnVuY3Rpb24gQXcoKXtmb3IodmFyIGU9UHUsdDt0PWUuc291cmNlRXZlbnQ7KWU9dDtyZXR1cm4gZX12YXIgbTc9TSgoKT0+e2Y3KCl9KTtmdW5jdGlvbiBEZyhlLHQpe3ZhciByPWUub3duZXJTVkdFbGVtZW50fHxlO2lmKHIuY3JlYXRlU1ZHUG9pbnQpe3ZhciBuPXIuY3JlYXRlU1ZHUG9pbnQoKTtyZXR1cm4gbi54PXQuY2xpZW50WCxuLnk9dC5jbGllbnRZLG49bi5tYXRyaXhUcmFuc2Zvcm0oZS5nZXRTY3JlZW5DVE0oKS5pbnZlcnNlKCkpLFtuLngsbi55XX12YXIgaT1lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO3JldHVyblt0LmNsaWVudFgtaS5sZWZ0LWUuY2xpZW50TGVmdCx0LmNsaWVudFktaS50b3AtZS5jbGllbnRUb3BdfXZhciBBQz1NKCgpPT57fSk7ZnVuY3Rpb24gSnp0KGUpe3ZhciB0PUF3KCk7cmV0dXJuIHQuY2hhbmdlZFRvdWNoZXMmJih0PXQuY2hhbmdlZFRvdWNoZXNbMF0pLERnKGUsdCl9dmFyIFF6dD1NKCgpPT57bTcoKTtBQygpfSk7ZnVuY3Rpb24gdEZ0KGUpe3JldHVybiB0eXBlb2YgZT09InN0cmluZyI/bmV3IEhuKFtkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKGUpXSxbZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50XSk6bmV3IEhuKFtlPT1udWxsP1tdOmVdLENDKX12YXIgZUZ0PU0oKCk9PntTYygpfSk7ZnVuY3Rpb24gckZ0KGUsdCxyKXthcmd1bWVudHMubGVuZ3RoPDMmJihyPXQsdD1BdygpLmNoYW5nZWRUb3VjaGVzKTtmb3IodmFyIG49MCxpPXQ/dC5sZW5ndGg6MCxvO248aTsrK24paWYoKG89dFtuXSkuaWRlbnRpZmllcj09PXIpcmV0dXJuIERnKGUsbyk7cmV0dXJuIG51bGx9dmFyIG5GdD1NKCgpPT57bTcoKTtBQygpfSk7ZnVuY3Rpb24gaUZ0KGUsdCl7dD09bnVsbCYmKHQ9QXcoKS50b3VjaGVzKTtmb3IodmFyIHI9MCxuPXQ/dC5sZW5ndGg6MCxpPW5ldyBBcnJheShuKTtyPG47KytyKWlbcl09RGcoZSx0W3JdKTtyZXR1cm4gaX12YXIgb0Z0PU0oKCk9PnttNygpO0FDKCl9KTt2YXIgYUZ0PU0oKCk9PntLenQoKTtFQygpO1p6dCgpO21ldCgpO1F6dCgpO2E3KCk7bzcoKTtBQygpO2JldCgpO2VGdCgpO1NjKCk7czcoKTtkZXQoKTt2ZXQoKTtuRnQoKTtvRnQoKTtoNygpO2Y3KCl9KTtmdW5jdGlvbiBUZXQoKXt0aGlzLl94MD10aGlzLl95MD10aGlzLl94MT10aGlzLl95MT1udWxsLHRoaXMuXz0iIn1mdW5jdGlvbiBzRnQoKXtyZXR1cm4gbmV3IFRldH12YXIgTWV0LEVldCx1MSx1TGUsSXUsbEZ0PU0oKCk9PntNZXQ9TWF0aC5QSSxFZXQ9MipNZXQsdTE9MWUtNix1TGU9RWV0LXUxO1RldC5wcm90b3R5cGU9c0Z0LnByb3RvdHlwZT17Y29uc3RydWN0b3I6VGV0LG1vdmVUbzpmdW5jdGlvbihlLHQpe3RoaXMuXys9Ik0iKyh0aGlzLl94MD10aGlzLl94MT0rZSkrIiwiKyh0aGlzLl95MD10aGlzLl95MT0rdCl9LGNsb3NlUGF0aDpmdW5jdGlvbigpe3RoaXMuX3gxIT09bnVsbCYmKHRoaXMuX3gxPXRoaXMuX3gwLHRoaXMuX3kxPXRoaXMuX3kwLHRoaXMuXys9IloiKX0sbGluZVRvOmZ1bmN0aW9uKGUsdCl7dGhpcy5fKz0iTCIrKHRoaXMuX3gxPStlKSsiLCIrKHRoaXMuX3kxPSt0KX0scXVhZHJhdGljQ3VydmVUbzpmdW5jdGlvbihlLHQscixuKXt0aGlzLl8rPSJRIisgK2UrIiwiKyArdCsiLCIrKHRoaXMuX3gxPStyKSsiLCIrKHRoaXMuX3kxPStuKX0sYmV6aWVyQ3VydmVUbzpmdW5jdGlvbihlLHQscixuLGksbyl7dGhpcy5fKz0iQyIrICtlKyIsIisgK3QrIiwiKyArcisiLCIrICtuKyIsIisodGhpcy5feDE9K2kpKyIsIisodGhpcy5feTE9K28pfSxhcmNUbzpmdW5jdGlvbihlLHQscixuLGkpe2U9K2UsdD0rdCxyPStyLG49K24saT0raTt2YXIgbz10aGlzLl94MSxhPXRoaXMuX3kxLHM9ci1lLGw9bi10LGM9by1lLHU9YS10LGg9YypjK3UqdTtpZihpPDApdGhyb3cgbmV3IEVycm9yKCJuZWdhdGl2ZSByYWRpdXM6ICIraSk7aWYodGhpcy5feDE9PT1udWxsKXRoaXMuXys9Ik0iKyh0aGlzLl94MT1lKSsiLCIrKHRoaXMuX3kxPXQpO2Vsc2UgaWYoaD51MSlpZighKE1hdGguYWJzKHUqcy1sKmMpPnUxKXx8IWkpdGhpcy5fKz0iTCIrKHRoaXMuX3gxPWUpKyIsIisodGhpcy5feTE9dCk7ZWxzZXt2YXIgZj1yLW8scD1uLWEsZD1zKnMrbCpsLGc9ZipmK3AqcCxfPU1hdGguc3FydChkKSx5PU1hdGguc3FydChoKSx4PWkqTWF0aC50YW4oKE1ldC1NYXRoLmFjb3MoKGQraC1nKS8oMipfKnkpKSkvMiksYj14L3ksUz14L187TWF0aC5hYnMoYi0xKT51MSYmKHRoaXMuXys9IkwiKyhlK2IqYykrIiwiKyh0K2IqdSkpLHRoaXMuXys9IkEiK2krIiwiK2krIiwwLDAsIisgKyh1KmY+YypwKSsiLCIrKHRoaXMuX3gxPWUrUypzKSsiLCIrKHRoaXMuX3kxPXQrUypsKX19LGFyYzpmdW5jdGlvbihlLHQscixuLGksbyl7ZT0rZSx0PSt0LHI9K3Isbz0hIW87dmFyIGE9cipNYXRoLmNvcyhuKSxzPXIqTWF0aC5zaW4obiksbD1lK2EsYz10K3MsdT0xXm8saD1vP24taTppLW47aWYocjwwKXRocm93IG5ldyBFcnJvcigibmVnYXRpdmUgcmFkaXVzOiAiK3IpO3RoaXMuX3gxPT09bnVsbD90aGlzLl8rPSJNIitsKyIsIitjOihNYXRoLmFicyh0aGlzLl94MS1sKT51MXx8TWF0aC5hYnModGhpcy5feTEtYyk+dTEpJiYodGhpcy5fKz0iTCIrbCsiLCIrYyksciYmKGg8MCYmKGg9aCVFZXQrRWV0KSxoPnVMZT90aGlzLl8rPSJBIityKyIsIityKyIsMCwxLCIrdSsiLCIrKGUtYSkrIiwiKyh0LXMpKyJBIityKyIsIityKyIsMCwxLCIrdSsiLCIrKHRoaXMuX3gxPWwpKyIsIisodGhpcy5feTE9Yyk6aD51MSYmKHRoaXMuXys9IkEiK3IrIiwiK3IrIiwwLCIrICsoaD49TWV0KSsiLCIrdSsiLCIrKHRoaXMuX3gxPWUrcipNYXRoLmNvcyhpKSkrIiwiKyh0aGlzLl95MT10K3IqTWF0aC5zaW4oaSkpKSl9LHJlY3Q6ZnVuY3Rpb24oZSx0LHIsbil7dGhpcy5fKz0iTSIrKHRoaXMuX3gwPXRoaXMuX3gxPStlKSsiLCIrKHRoaXMuX3kwPXRoaXMuX3kxPSt0KSsiaCIrICtyKyJ2IisgK24rImgiKy1yKyJaIn0sdG9TdHJpbmc6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5ffX07SXU9c0Z0fSk7dmFyIFB3PU0oKCk9PntsRnQoKX0pO2Z1bmN0aW9uIEdlKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX12YXIgT2c9TSgoKT0+e30pO2Z1bmN0aW9uIHVGdChlKXtyZXR1cm4gZT4xPzA6ZTwtMT9rdTpNYXRoLmFjb3MoZSl9ZnVuY3Rpb24gQWV0KGUpe3JldHVybiBlPj0xP1BDOmU8PS0xPy1QQzpNYXRoLmFzaW4oZSl9dmFyIENldCxIbyx6ZyxjRnQsZzcsTHUsaDEsQ28sa3UsUEMsTWMsSXc9TSgoKT0+e0NldD1NYXRoLmFicyxIbz1NYXRoLmF0YW4yLHpnPU1hdGguY29zLGNGdD1NYXRoLm1heCxnNz1NYXRoLm1pbixMdT1NYXRoLnNpbixoMT1NYXRoLnNxcnQsQ289MWUtMTIsa3U9TWF0aC5QSSxQQz1rdS8yLE1jPTIqa3V9KTtmdW5jdGlvbiBoTGUoZSl7cmV0dXJuIGUuaW5uZXJSYWRpdXN9ZnVuY3Rpb24gZkxlKGUpe3JldHVybiBlLm91dGVyUmFkaXVzfWZ1bmN0aW9uIHBMZShlKXtyZXR1cm4gZS5zdGFydEFuZ2xlfWZ1bmN0aW9uIGRMZShlKXtyZXR1cm4gZS5lbmRBbmdsZX1mdW5jdGlvbiBtTGUoZSl7cmV0dXJuIGUmJmUucGFkQW5nbGV9ZnVuY3Rpb24gZ0xlKGUsdCxyLG4saSxvLGEscyl7dmFyIGw9ci1lLGM9bi10LHU9YS1pLGg9cy1vLGY9KHUqKHQtbyktaCooZS1pKSkvKGgqbC11KmMpO3JldHVybltlK2YqbCx0K2YqY119ZnVuY3Rpb24gXzcoZSx0LHIsbixpLG8sYSl7dmFyIHM9ZS1yLGw9dC1uLGM9KGE/bzotbykvaDEocypzK2wqbCksdT1jKmwsaD0tYypzLGY9ZSt1LHA9dCtoLGQ9cit1LGc9bitoLF89KGYrZCkvMix5PShwK2cpLzIseD1kLWYsYj1nLXAsUz14KngrYipiLEM9aS1vLFA9ZipnLWQqcCxrPShiPDA/LTE6MSkqaDEoY0Z0KDAsQypDKlMtUCpQKSksTz0oUCpiLXgqaykvUyxEPSgtUCp4LWIqaykvUyxCPShQKmIreCprKS9TLEk9KC1QKngrYiprKS9TLEw9Ty1fLFI9RC15LEY9Qi1fLHo9SS15O3JldHVybiBMKkwrUipSPkYqRit6KnomJihPPUIsRD1JKSx7Y3g6TyxjeTpELHgwMTotdSx5MDE6LWgseDExOk8qKGkvQy0xKSx5MTE6RCooaS9DLTEpfX1mdW5jdGlvbiBoRnQoKXt2YXIgZT1oTGUsdD1mTGUscj1HZSgwKSxuPW51bGwsaT1wTGUsbz1kTGUsYT1tTGUscz1udWxsO2Z1bmN0aW9uIGwoKXt2YXIgYyx1LGg9K2UuYXBwbHkodGhpcyxhcmd1bWVudHMpLGY9K3QuYXBwbHkodGhpcyxhcmd1bWVudHMpLHA9aS5hcHBseSh0aGlzLGFyZ3VtZW50cyktUEMsZD1vLmFwcGx5KHRoaXMsYXJndW1lbnRzKS1QQyxnPUNldChkLXApLF89ZD5wO2lmKHN8fChzPWM9SXUoKSksZjxoJiYodT1mLGY9aCxoPXUpLCEoZj5Dbykpcy5tb3ZlVG8oMCwwKTtlbHNlIGlmKGc+TWMtQ28pcy5tb3ZlVG8oZip6ZyhwKSxmKkx1KHApKSxzLmFyYygwLDAsZixwLGQsIV8pLGg+Q28mJihzLm1vdmVUbyhoKnpnKGQpLGgqTHUoZCkpLHMuYXJjKDAsMCxoLGQscCxfKSk7ZWxzZXt2YXIgeT1wLHg9ZCxiPXAsUz1kLEM9ZyxQPWcsaz1hLmFwcGx5KHRoaXMsYXJndW1lbnRzKS8yLE89az5DbyYmKG4/K24uYXBwbHkodGhpcyxhcmd1bWVudHMpOmgxKGgqaCtmKmYpKSxEPWc3KENldChmLWgpLzIsK3IuYXBwbHkodGhpcyxhcmd1bWVudHMpKSxCPUQsST1ELEwsUjtpZihPPkNvKXt2YXIgRj1BZXQoTy9oKkx1KGspKSx6PUFldChPL2YqTHUoaykpOyhDLT1GKjIpPkNvPyhGKj1fPzE6LTEsYis9RixTLT1GKTooQz0wLGI9Uz0ocCtkKS8yKSwoUC09eioyKT5Dbz8oeio9Xz8xOi0xLHkrPXoseC09eik6KFA9MCx5PXg9KHArZCkvMil9dmFyIFU9Zip6Zyh5KSxXPWYqTHUoeSksWj1oKnpnKFMpLHJ0PWgqTHUoUyk7aWYoRD5Dbyl7dmFyIG90PWYqemcoeCksc3Q9ZipMdSh4KSxTdD1oKnpnKGIpLGJ0PWgqTHUoYik7aWYoZzxrdSl7dmFyIE10PUM+Q28/Z0xlKFUsVyxTdCxidCxvdCxzdCxaLHJ0KTpbWixydF0sbHQ9VS1NdFswXSxLdD1XLU10WzFdLF90PW90LU10WzBdLGN0PXN0LU10WzFdLFg9MS9MdSh1RnQoKGx0Kl90K0t0KmN0KS8oaDEobHQqbHQrS3QqS3QpKmgxKF90Kl90K2N0KmN0KSkpLzIpLGV0PWgxKE10WzBdKk10WzBdK010WzFdKk10WzFdKTtCPWc3KEQsKGgtZXQpLyhYLTEpKSxJPWc3KEQsKGYtZXQpLyhYKzEpKX19UD5Dbz9JPkNvPyhMPV83KFN0LGJ0LFUsVyxmLEksXyksUj1fNyhvdCxzdCxaLHJ0LGYsSSxfKSxzLm1vdmVUbyhMLmN4K0wueDAxLEwuY3krTC55MDEpLEk8RD9zLmFyYyhMLmN4LEwuY3ksSSxIbyhMLnkwMSxMLngwMSksSG8oUi55MDEsUi54MDEpLCFfKToocy5hcmMoTC5jeCxMLmN5LEksSG8oTC55MDEsTC54MDEpLEhvKEwueTExLEwueDExKSwhXykscy5hcmMoMCwwLGYsSG8oTC5jeStMLnkxMSxMLmN4K0wueDExKSxIbyhSLmN5K1IueTExLFIuY3grUi54MTEpLCFfKSxzLmFyYyhSLmN4LFIuY3ksSSxIbyhSLnkxMSxSLngxMSksSG8oUi55MDEsUi54MDEpLCFfKSkpOihzLm1vdmVUbyhVLFcpLHMuYXJjKDAsMCxmLHkseCwhXykpOnMubW92ZVRvKFUsVyksIShoPkNvKXx8IShDPkNvKT9zLmxpbmVUbyhaLHJ0KTpCPkNvPyhMPV83KFoscnQsb3Qsc3QsaCwtQixfKSxSPV83KFUsVyxTdCxidCxoLC1CLF8pLHMubGluZVRvKEwuY3grTC54MDEsTC5jeStMLnkwMSksQjxEP3MuYXJjKEwuY3gsTC5jeSxCLEhvKEwueTAxLEwueDAxKSxIbyhSLnkwMSxSLngwMSksIV8pOihzLmFyYyhMLmN4LEwuY3ksQixIbyhMLnkwMSxMLngwMSksSG8oTC55MTEsTC54MTEpLCFfKSxzLmFyYygwLDAsaCxIbyhMLmN5K0wueTExLEwuY3grTC54MTEpLEhvKFIuY3krUi55MTEsUi5jeCtSLngxMSksXykscy5hcmMoUi5jeCxSLmN5LEIsSG8oUi55MTEsUi54MTEpLEhvKFIueTAxLFIueDAxKSwhXykpKTpzLmFyYygwLDAsaCxTLGIsXyl9aWYocy5jbG9zZVBhdGgoKSxjKXJldHVybiBzPW51bGwsYysiInx8bnVsbH1yZXR1cm4gbC5jZW50cm9pZD1mdW5jdGlvbigpe3ZhciBjPSgrZS5hcHBseSh0aGlzLGFyZ3VtZW50cykrICt0LmFwcGx5KHRoaXMsYXJndW1lbnRzKSkvMix1PSgraS5hcHBseSh0aGlzLGFyZ3VtZW50cykrICtvLmFwcGx5KHRoaXMsYXJndW1lbnRzKSkvMi1rdS8yO3JldHVyblt6Zyh1KSpjLEx1KHUpKmNdfSxsLmlubmVyUmFkaXVzPWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiBjPT0iZnVuY3Rpb24iP2M6R2UoK2MpLGwpOmV9LGwub3V0ZXJSYWRpdXM9ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9dHlwZW9mIGM9PSJmdW5jdGlvbiI/YzpHZSgrYyksbCk6dH0sbC5jb3JuZXJSYWRpdXM9ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9dHlwZW9mIGM9PSJmdW5jdGlvbiI/YzpHZSgrYyksbCk6cn0sbC5wYWRSYWRpdXM9ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49Yz09bnVsbD9udWxsOnR5cGVvZiBjPT0iZnVuY3Rpb24iP2M6R2UoK2MpLGwpOm59LGwuc3RhcnRBbmdsZT1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT10eXBlb2YgYz09ImZ1bmN0aW9uIj9jOkdlKCtjKSxsKTppfSxsLmVuZEFuZ2xlPWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPXR5cGVvZiBjPT0iZnVuY3Rpb24iP2M6R2UoK2MpLGwpOm99LGwucGFkQW5nbGU9ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGE9dHlwZW9mIGM9PSJmdW5jdGlvbiI/YzpHZSgrYyksbCk6YX0sbC5jb250ZXh0PWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhzPWM9PW51bGw/bnVsbDpjLGwpOnN9LGx9dmFyIGZGdD1NKCgpPT57UHcoKTtPZygpO0l3KCl9KTtmdW5jdGlvbiBwRnQoZSl7dGhpcy5fY29udGV4dD1lfWZ1bmN0aW9uIEZnKGUpe3JldHVybiBuZXcgcEZ0KGUpfXZhciBJQz1NKCgpPT57cEZ0LnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXsodGhpcy5fbGluZXx8dGhpcy5fbGluZSE9PTAmJnRoaXMuX3BvaW50PT09MSkmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCksdGhpcy5fbGluZT0xLXRoaXMuX2xpbmV9LHBvaW50OmZ1bmN0aW9uKGUsdCl7c3dpdGNoKGU9K2UsdD0rdCx0aGlzLl9wb2ludCl7Y2FzZSAwOnRoaXMuX3BvaW50PTEsdGhpcy5fbGluZT90aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpOnRoaXMuX2NvbnRleHQubW92ZVRvKGUsdCk7YnJlYWs7Y2FzZSAxOnRoaXMuX3BvaW50PTI7ZGVmYXVsdDp0aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpO2JyZWFrfX19fSk7ZnVuY3Rpb24gTHcoZSl7cmV0dXJuIGVbMF19ZnVuY3Rpb24ga3coZSl7cmV0dXJuIGVbMV19dmFyIHk3PU0oKCk9Pnt9KTtmdW5jdGlvbiBSdygpe3ZhciBlPUx3LHQ9a3cscj1HZSghMCksbj1udWxsLGk9Rmcsbz1udWxsO2Z1bmN0aW9uIGEocyl7dmFyIGwsYz1zLmxlbmd0aCx1LGg9ITEsZjtmb3Iobj09bnVsbCYmKG89aShmPUl1KCkpKSxsPTA7bDw9YzsrK2wpIShsPGMmJnIodT1zW2xdLGwscykpPT09aCYmKChoPSFoKT9vLmxpbmVTdGFydCgpOm8ubGluZUVuZCgpKSxoJiZvLnBvaW50KCtlKHUsbCxzKSwrdCh1LGwscykpO2lmKGYpcmV0dXJuIG89bnVsbCxmKyIifHxudWxsfXJldHVybiBhLng9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpHZSgrcyksYSk6ZX0sYS55PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBzPT0iZnVuY3Rpb24iP3M6R2UoK3MpLGEpOnR9LGEuZGVmaW5lZD1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOkdlKCEhcyksYSk6cn0sYS5jdXJ2ZT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT1zLG4hPW51bGwmJihvPWkobikpLGEpOml9LGEuY29udGV4dD1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocz09bnVsbD9uPW89bnVsbDpvPWkobj1zKSxhKTpufSxhfXZhciB2Nz1NKCgpPT57UHcoKTtPZygpO0lDKCk7eTcoKX0pO2Z1bmN0aW9uIHg3KCl7dmFyIGU9THcsdD1udWxsLHI9R2UoMCksbj1rdyxpPUdlKCEwKSxvPW51bGwsYT1GZyxzPW51bGw7ZnVuY3Rpb24gbCh1KXt2YXIgaCxmLHAsZD11Lmxlbmd0aCxnLF89ITEseSx4PW5ldyBBcnJheShkKSxiPW5ldyBBcnJheShkKTtmb3Iobz09bnVsbCYmKHM9YSh5PUl1KCkpKSxoPTA7aDw9ZDsrK2gpe2lmKCEoaDxkJiZpKGc9dVtoXSxoLHUpKT09PV8paWYoXz0hXylmPWgscy5hcmVhU3RhcnQoKSxzLmxpbmVTdGFydCgpO2Vsc2V7Zm9yKHMubGluZUVuZCgpLHMubGluZVN0YXJ0KCkscD1oLTE7cD49ZjstLXApcy5wb2ludCh4W3BdLGJbcF0pO3MubGluZUVuZCgpLHMuYXJlYUVuZCgpfV8mJih4W2hdPStlKGcsaCx1KSxiW2hdPStyKGcsaCx1KSxzLnBvaW50KHQ/K3QoZyxoLHUpOnhbaF0sbj8rbihnLGgsdSk6YltoXSkpfWlmKHkpcmV0dXJuIHM9bnVsbCx5KyIifHxudWxsfWZ1bmN0aW9uIGMoKXtyZXR1cm4gUncoKS5kZWZpbmVkKGkpLmN1cnZlKGEpLmNvbnRleHQobyl9cmV0dXJuIGwueD1mdW5jdGlvbih1KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2YgdT09ImZ1bmN0aW9uIj91OkdlKCt1KSx0PW51bGwsbCk6ZX0sbC54MD1mdW5jdGlvbih1KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2YgdT09ImZ1bmN0aW9uIj91OkdlKCt1KSxsKTplfSxsLngxPWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXU9PW51bGw/bnVsbDp0eXBlb2YgdT09ImZ1bmN0aW9uIj91OkdlKCt1KSxsKTp0fSxsLnk9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9dHlwZW9mIHU9PSJmdW5jdGlvbiI/dTpHZSgrdSksbj1udWxsLGwpOnJ9LGwueTA9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9dHlwZW9mIHU9PSJmdW5jdGlvbiI/dTpHZSgrdSksbCk6cn0sbC55MT1mdW5jdGlvbih1KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj11PT1udWxsP251bGw6dHlwZW9mIHU9PSJmdW5jdGlvbiI/dTpHZSgrdSksbCk6bn0sbC5saW5lWDA9bC5saW5lWTA9ZnVuY3Rpb24oKXtyZXR1cm4gYygpLngoZSkueShyKX0sbC5saW5lWTE9ZnVuY3Rpb24oKXtyZXR1cm4gYygpLngoZSkueShuKX0sbC5saW5lWDE9ZnVuY3Rpb24oKXtyZXR1cm4gYygpLngodCkueShyKX0sbC5kZWZpbmVkPWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPXR5cGVvZiB1PT0iZnVuY3Rpb24iP3U6R2UoISF1KSxsKTppfSxsLmN1cnZlPWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhhPXUsbyE9bnVsbCYmKHM9YShvKSksbCk6YX0sbC5jb250ZXh0PWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh1PT1udWxsP289cz1udWxsOnM9YShvPXUpLGwpOm99LGx9dmFyIFBldD1NKCgpPT57UHcoKTtPZygpO0lDKCk7djcoKTt5NygpfSk7ZnVuY3Rpb24gZEZ0KGUsdCl7cmV0dXJuIHQ8ZT8tMTp0PmU/MTp0Pj1lPzA6TmFOfXZhciBtRnQ9TSgoKT0+e30pO2Z1bmN0aW9uIGdGdChlKXtyZXR1cm4gZX12YXIgX0Z0PU0oKCk9Pnt9KTtmdW5jdGlvbiB5RnQoKXt2YXIgZT1nRnQsdD1kRnQscj1udWxsLG49R2UoMCksaT1HZShNYyksbz1HZSgwKTtmdW5jdGlvbiBhKHMpe3ZhciBsLGM9cy5sZW5ndGgsdSxoLGY9MCxwPW5ldyBBcnJheShjKSxkPW5ldyBBcnJheShjKSxnPStuLmFwcGx5KHRoaXMsYXJndW1lbnRzKSxfPU1hdGgubWluKE1jLE1hdGgubWF4KC1NYyxpLmFwcGx5KHRoaXMsYXJndW1lbnRzKS1nKSkseSx4PU1hdGgubWluKE1hdGguYWJzKF8pL2Msby5hcHBseSh0aGlzLGFyZ3VtZW50cykpLGI9eCooXzwwPy0xOjEpLFM7Zm9yKGw9MDtsPGM7KytsKShTPWRbcFtsXT1sXT0rZShzW2xdLGwscykpPjAmJihmKz1TKTtmb3IodCE9bnVsbD9wLnNvcnQoZnVuY3Rpb24oQyxQKXtyZXR1cm4gdChkW0NdLGRbUF0pfSk6ciE9bnVsbCYmcC5zb3J0KGZ1bmN0aW9uKEMsUCl7cmV0dXJuIHIoc1tDXSxzW1BdKX0pLGw9MCxoPWY/KF8tYypiKS9mOjA7bDxjOysrbCxnPXkpdT1wW2xdLFM9ZFt1XSx5PWcrKFM+MD9TKmg6MCkrYixkW3VdPXtkYXRhOnNbdV0saW5kZXg6bCx2YWx1ZTpTLHN0YXJ0QW5nbGU6ZyxlbmRBbmdsZTp5LHBhZEFuZ2xlOnh9O3JldHVybiBkfXJldHVybiBhLnZhbHVlPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiBzPT0iZnVuY3Rpb24iP3M6R2UoK3MpLGEpOmV9LGEuc29ydFZhbHVlcz1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD1zLHI9bnVsbCxhKTp0fSxhLnNvcnQ9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9cyx0PW51bGwsYSk6cn0sYS5zdGFydEFuZ2xlPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPXR5cGVvZiBzPT0iZnVuY3Rpb24iP3M6R2UoK3MpLGEpOm59LGEuZW5kQW5nbGU9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpHZSgrcyksYSk6aX0sYS5wYWRBbmdsZT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOkdlKCtzKSxhKTpvfSxhfXZhciB2RnQ9TSgoKT0+e09nKCk7bUZ0KCk7X0Z0KCk7SXcoKX0pO2Z1bmN0aW9uIHhGdChlKXt0aGlzLl9jdXJ2ZT1lfWZ1bmN0aW9uIE53KGUpe2Z1bmN0aW9uIHQocil7cmV0dXJuIG5ldyB4RnQoZShyKSl9cmV0dXJuIHQuX2N1cnZlPWUsdH12YXIgYjcsSWV0PU0oKCk9PntJQygpO2I3PU53KEZnKTt4RnQucHJvdG90eXBlPXthcmVhU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9jdXJ2ZS5hcmVhU3RhcnQoKX0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2N1cnZlLmFyZWFFbmQoKX0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fY3VydmUubGluZVN0YXJ0KCl9LGxpbmVFbmQ6ZnVuY3Rpb24oKXt0aGlzLl9jdXJ2ZS5saW5lRW5kKCl9LHBvaW50OmZ1bmN0aW9uKGUsdCl7dGhpcy5fY3VydmUucG9pbnQodCpNYXRoLnNpbihlKSx0Ki1NYXRoLmNvcyhlKSl9fX0pO2Z1bmN0aW9uIER3KGUpe3ZhciB0PWUuY3VydmU7cmV0dXJuIGUuYW5nbGU9ZS54LGRlbGV0ZSBlLngsZS5yYWRpdXM9ZS55LGRlbGV0ZSBlLnksZS5jdXJ2ZT1mdW5jdGlvbihyKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90KE53KHIpKTp0KCkuX2N1cnZlfSxlfWZ1bmN0aW9uIExldCgpe3JldHVybiBEdyhSdygpLmN1cnZlKGI3KSl9dmFyIGtldD1NKCgpPT57SWV0KCk7djcoKX0pO2Z1bmN0aW9uIFJldCgpe3ZhciBlPXg3KCkuY3VydmUoYjcpLHQ9ZS5jdXJ2ZSxyPWUubGluZVgwLG49ZS5saW5lWDEsaT1lLmxpbmVZMCxvPWUubGluZVkxO3JldHVybiBlLmFuZ2xlPWUueCxkZWxldGUgZS54LGUuc3RhcnRBbmdsZT1lLngwLGRlbGV0ZSBlLngwLGUuZW5kQW5nbGU9ZS54MSxkZWxldGUgZS54MSxlLnJhZGl1cz1lLnksZGVsZXRlIGUueSxlLmlubmVyUmFkaXVzPWUueTAsZGVsZXRlIGUueTAsZS5vdXRlclJhZGl1cz1lLnkxLGRlbGV0ZSBlLnkxLGUubGluZVN0YXJ0QW5nbGU9ZnVuY3Rpb24oKXtyZXR1cm4gRHcocigpKX0sZGVsZXRlIGUubGluZVgwLGUubGluZUVuZEFuZ2xlPWZ1bmN0aW9uKCl7cmV0dXJuIER3KG4oKSl9LGRlbGV0ZSBlLmxpbmVYMSxlLmxpbmVJbm5lclJhZGl1cz1mdW5jdGlvbigpe3JldHVybiBEdyhpKCkpfSxkZWxldGUgZS5saW5lWTAsZS5saW5lT3V0ZXJSYWRpdXM9ZnVuY3Rpb24oKXtyZXR1cm4gRHcobygpKX0sZGVsZXRlIGUubGluZVkxLGUuY3VydmU9ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dChOdyhhKSk6dCgpLl9jdXJ2ZX0sZX12YXIgYkZ0PU0oKCk9PntJZXQoKTtQZXQoKTtrZXQoKX0pO2Z1bmN0aW9uIGYxKGUsdCl7cmV0dXJuWyh0PSt0KSpNYXRoLmNvcyhlLT1NYXRoLlBJLzIpLHQqTWF0aC5zaW4oZSldfXZhciBOZXQ9TSgoKT0+e30pO3ZhciBMQyxEZXQ9TSgoKT0+e0xDPUFycmF5LnByb3RvdHlwZS5zbGljZX0pO2Z1bmN0aW9uIF9MZShlKXtyZXR1cm4gZS5zb3VyY2V9ZnVuY3Rpb24geUxlKGUpe3JldHVybiBlLnRhcmdldH1mdW5jdGlvbiBPZXQoZSl7dmFyIHQ9X0xlLHI9eUxlLG49THcsaT1rdyxvPW51bGw7ZnVuY3Rpb24gYSgpe3ZhciBzLGw9TEMuY2FsbChhcmd1bWVudHMpLGM9dC5hcHBseSh0aGlzLGwpLHU9ci5hcHBseSh0aGlzLGwpO2lmKG98fChvPXM9SXUoKSksZShvLCtuLmFwcGx5KHRoaXMsKGxbMF09YyxsKSksK2kuYXBwbHkodGhpcyxsKSwrbi5hcHBseSh0aGlzLChsWzBdPXUsbCkpLCtpLmFwcGx5KHRoaXMsbCkpLHMpcmV0dXJuIG89bnVsbCxzKyIifHxudWxsfXJldHVybiBhLnNvdXJjZT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD1zLGEpOnR9LGEudGFyZ2V0PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPXMsYSk6cn0sYS54PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPXR5cGVvZiBzPT0iZnVuY3Rpb24iP3M6R2UoK3MpLGEpOm59LGEueT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOkdlKCtzKSxhKTppfSxhLmNvbnRleHQ9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG89cz09bnVsbD9udWxsOnMsYSk6b30sYX1mdW5jdGlvbiB2TGUoZSx0LHIsbixpKXtlLm1vdmVUbyh0LHIpLGUuYmV6aWVyQ3VydmVUbyh0PSh0K24pLzIscix0LGksbixpKX1mdW5jdGlvbiB4TGUoZSx0LHIsbixpKXtlLm1vdmVUbyh0LHIpLGUuYmV6aWVyQ3VydmVUbyh0LHI9KHIraSkvMixuLHIsbixpKX1mdW5jdGlvbiBiTGUoZSx0LHIsbixpKXt2YXIgbz1mMSh0LHIpLGE9ZjEodCxyPShyK2kpLzIpLHM9ZjEobixyKSxsPWYxKG4saSk7ZS5tb3ZlVG8ob1swXSxvWzFdKSxlLmJlemllckN1cnZlVG8oYVswXSxhWzFdLHNbMF0sc1sxXSxsWzBdLGxbMV0pfWZ1bmN0aW9uIHdGdCgpe3JldHVybiBPZXQodkxlKX1mdW5jdGlvbiBTRnQoKXtyZXR1cm4gT2V0KHhMZSl9ZnVuY3Rpb24gTUZ0KCl7dmFyIGU9T2V0KGJMZSk7cmV0dXJuIGUuYW5nbGU9ZS54LGRlbGV0ZSBlLngsZS5yYWRpdXM9ZS55LGRlbGV0ZSBlLnksZX12YXIgRUZ0PU0oKCk9PntQdygpO0RldCgpO09nKCk7eTcoKTtOZXQoKX0pO3ZhciBrQyx6ZXQ9TSgoKT0+e0l3KCk7a0M9e2RyYXc6ZnVuY3Rpb24oZSx0KXt2YXIgcj1NYXRoLnNxcnQodC9rdSk7ZS5tb3ZlVG8ociwwKSxlLmFyYygwLDAsciwwLE1jKX19fSk7dmFyIHc3LEZldD1NKCgpPT57dzc9e2RyYXc6ZnVuY3Rpb24oZSx0KXt2YXIgcj1NYXRoLnNxcnQodC81KS8yO2UubW92ZVRvKC0zKnIsLXIpLGUubGluZVRvKC1yLC1yKSxlLmxpbmVUbygtciwtMypyKSxlLmxpbmVUbyhyLC0zKnIpLGUubGluZVRvKHIsLXIpLGUubGluZVRvKDMqciwtciksZS5saW5lVG8oMypyLHIpLGUubGluZVRvKHIsciksZS5saW5lVG8ociwzKnIpLGUubGluZVRvKC1yLDMqciksZS5saW5lVG8oLXIsciksZS5saW5lVG8oLTMqcixyKSxlLmNsb3NlUGF0aCgpfX19KTt2YXIgVEZ0LHdMZSxTNyxCZXQ9TSgoKT0+e1RGdD1NYXRoLnNxcnQoLjMzMzMzMzMzMzMzMzMzMzMpLHdMZT1URnQqMixTNz17ZHJhdzpmdW5jdGlvbihlLHQpe3ZhciByPU1hdGguc3FydCh0L3dMZSksbj1yKlRGdDtlLm1vdmVUbygwLC1yKSxlLmxpbmVUbyhuLDApLGUubGluZVRvKDAsciksZS5saW5lVG8oLW4sMCksZS5jbG9zZVBhdGgoKX19fSk7dmFyIFNMZSxDRnQsTUxlLEVMZSxNNyxIZXQ9TSgoKT0+e0l3KCk7U0xlPS44OTA4MTMwOTE1MjkyODUyLENGdD1NYXRoLnNpbihrdS8xMCkvTWF0aC5zaW4oNyprdS8xMCksTUxlPU1hdGguc2luKE1jLzEwKSpDRnQsRUxlPS1NYXRoLmNvcyhNYy8xMCkqQ0Z0LE03PXtkcmF3OmZ1bmN0aW9uKGUsdCl7dmFyIHI9TWF0aC5zcXJ0KHQqU0xlKSxuPU1MZSpyLGk9RUxlKnI7ZS5tb3ZlVG8oMCwtciksZS5saW5lVG8obixpKTtmb3IodmFyIG89MTtvPDU7KytvKXt2YXIgYT1NYypvLzUscz1NYXRoLmNvcyhhKSxsPU1hdGguc2luKGEpO2UubGluZVRvKGwqciwtcypyKSxlLmxpbmVUbyhzKm4tbCppLGwqbitzKmkpfWUuY2xvc2VQYXRoKCl9fX0pO3ZhciBFNyxWZXQ9TSgoKT0+e0U3PXtkcmF3OmZ1bmN0aW9uKGUsdCl7dmFyIHI9TWF0aC5zcXJ0KHQpLG49LXIvMjtlLnJlY3QobixuLHIscil9fX0pO3ZhciBVZXQsVDcscWV0PU0oKCk9PntVZXQ9TWF0aC5zcXJ0KDMpLFQ3PXtkcmF3OmZ1bmN0aW9uKGUsdCl7dmFyIHI9LU1hdGguc3FydCh0LyhVZXQqMykpO2UubW92ZVRvKDAscioyKSxlLmxpbmVUbygtVWV0KnIsLXIpLGUubGluZVRvKFVldCpyLC1yKSxlLmNsb3NlUGF0aCgpfX19KTt2YXIgRWMsVGMsR2V0LFRMZSxDNyxXZXQ9TSgoKT0+e0VjPS0uNSxUYz1NYXRoLnNxcnQoMykvMixHZXQ9MS9NYXRoLnNxcnQoMTIpLFRMZT0oR2V0LzIrMSkqMyxDNz17ZHJhdzpmdW5jdGlvbihlLHQpe3ZhciByPU1hdGguc3FydCh0L1RMZSksbj1yLzIsaT1yKkdldCxvPW4sYT1yKkdldCtyLHM9LW8sbD1hO2UubW92ZVRvKG4saSksZS5saW5lVG8obyxhKSxlLmxpbmVUbyhzLGwpLGUubGluZVRvKEVjKm4tVGMqaSxUYypuK0VjKmkpLGUubGluZVRvKEVjKm8tVGMqYSxUYypvK0VjKmEpLGUubGluZVRvKEVjKnMtVGMqbCxUYypzK0VjKmwpLGUubGluZVRvKEVjKm4rVGMqaSxFYyppLVRjKm4pLGUubGluZVRvKEVjKm8rVGMqYSxFYyphLVRjKm8pLGUubGluZVRvKEVjKnMrVGMqbCxFYypsLVRjKnMpLGUuY2xvc2VQYXRoKCl9fX0pO2Z1bmN0aW9uIFBGdCgpe3ZhciBlPUdlKGtDKSx0PUdlKDY0KSxyPW51bGw7ZnVuY3Rpb24gbigpe3ZhciBpO2lmKHJ8fChyPWk9SXUoKSksZS5hcHBseSh0aGlzLGFyZ3VtZW50cykuZHJhdyhyLCt0LmFwcGx5KHRoaXMsYXJndW1lbnRzKSksaSlyZXR1cm4gcj1udWxsLGkrIiJ8fG51bGx9cmV0dXJuIG4udHlwZT1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2YgaT09ImZ1bmN0aW9uIj9pOkdlKGkpLG4pOmV9LG4uc2l6ZT1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD10eXBlb2YgaT09ImZ1bmN0aW9uIj9pOkdlKCtpKSxuKTp0fSxuLmNvbnRleHQ9ZnVuY3Rpb24oaSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9aT09bnVsbD9udWxsOmksbik6cn0sbn12YXIgQUZ0LElGdD1NKCgpPT57UHcoKTt6ZXQoKTtGZXQoKTtCZXQoKTtIZXQoKTtWZXQoKTtxZXQoKTtXZXQoKTtPZygpO0FGdD1ba0MsdzcsUzcsRTcsTTcsVDcsQzddfSk7ZnVuY3Rpb24gQ2MoKXt9dmFyIFJDPU0oKCk9Pnt9KTtmdW5jdGlvbiBPdyhlLHQscil7ZS5fY29udGV4dC5iZXppZXJDdXJ2ZVRvKCgyKmUuX3gwK2UuX3gxKS8zLCgyKmUuX3kwK2UuX3kxKS8zLChlLl94MCsyKmUuX3gxKS8zLChlLl95MCsyKmUuX3kxKS8zLChlLl94MCs0KmUuX3gxK3QpLzYsKGUuX3kwKzQqZS5feTErcikvNil9ZnVuY3Rpb24gTkMoZSl7dGhpcy5fY29udGV4dD1lfWZ1bmN0aW9uIExGdChlKXtyZXR1cm4gbmV3IE5DKGUpfXZhciBEQz1NKCgpPT57TkMucHJvdG90eXBlPXthcmVhU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPTB9LGFyZWFFbmQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPU5hTn0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5feDA9dGhpcy5feDE9dGhpcy5feTA9dGhpcy5feTE9TmFOLHRoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMzpPdyh0aGlzLHRoaXMuX3gxLHRoaXMuX3kxKTtjYXNlIDI6dGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feDEsdGhpcy5feTEpO2JyZWFrfSh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0xKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2goZT0rZSx0PSt0LHRoaXMuX3BvaW50KXtjYXNlIDA6dGhpcy5fcG9pbnQ9MSx0aGlzLl9saW5lP3RoaXMuX2NvbnRleHQubGluZVRvKGUsdCk6dGhpcy5fY29udGV4dC5tb3ZlVG8oZSx0KTticmVhaztjYXNlIDE6dGhpcy5fcG9pbnQ9MjticmVhaztjYXNlIDI6dGhpcy5fcG9pbnQ9Myx0aGlzLl9jb250ZXh0LmxpbmVUbygoNSp0aGlzLl94MCt0aGlzLl94MSkvNiwoNSp0aGlzLl95MCt0aGlzLl95MSkvNik7ZGVmYXVsdDpPdyh0aGlzLGUsdCk7YnJlYWt9dGhpcy5feDA9dGhpcy5feDEsdGhpcy5feDE9ZSx0aGlzLl95MD10aGlzLl95MSx0aGlzLl95MT10fX19KTtmdW5jdGlvbiBrRnQoZSl7dGhpcy5fY29udGV4dD1lfWZ1bmN0aW9uIFJGdChlKXtyZXR1cm4gbmV3IGtGdChlKX12YXIgTkZ0PU0oKCk9PntSQygpO0RDKCk7a0Z0LnByb3RvdHlwZT17YXJlYVN0YXJ0OkNjLGFyZWFFbmQ6Q2MsbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5feDA9dGhpcy5feDE9dGhpcy5feDI9dGhpcy5feDM9dGhpcy5feDQ9dGhpcy5feTA9dGhpcy5feTE9dGhpcy5feTI9dGhpcy5feTM9dGhpcy5feTQ9TmFOLHRoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMTp7dGhpcy5fY29udGV4dC5tb3ZlVG8odGhpcy5feDIsdGhpcy5feTIpLHRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCk7YnJlYWt9Y2FzZSAyOnt0aGlzLl9jb250ZXh0Lm1vdmVUbygodGhpcy5feDIrMip0aGlzLl94MykvMywodGhpcy5feTIrMip0aGlzLl95MykvMyksdGhpcy5fY29udGV4dC5saW5lVG8oKHRoaXMuX3gzKzIqdGhpcy5feDIpLzMsKHRoaXMuX3kzKzIqdGhpcy5feTIpLzMpLHRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCk7YnJlYWt9Y2FzZSAzOnt0aGlzLnBvaW50KHRoaXMuX3gyLHRoaXMuX3kyKSx0aGlzLnBvaW50KHRoaXMuX3gzLHRoaXMuX3kzKSx0aGlzLnBvaW50KHRoaXMuX3g0LHRoaXMuX3k0KTticmVha319fSxwb2ludDpmdW5jdGlvbihlLHQpe3N3aXRjaChlPStlLHQ9K3QsdGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xLHRoaXMuX3gyPWUsdGhpcy5feTI9dDticmVhaztjYXNlIDE6dGhpcy5fcG9pbnQ9Mix0aGlzLl94Mz1lLHRoaXMuX3kzPXQ7YnJlYWs7Y2FzZSAyOnRoaXMuX3BvaW50PTMsdGhpcy5feDQ9ZSx0aGlzLl95ND10LHRoaXMuX2NvbnRleHQubW92ZVRvKCh0aGlzLl94MCs0KnRoaXMuX3gxK2UpLzYsKHRoaXMuX3kwKzQqdGhpcy5feTErdCkvNik7YnJlYWs7ZGVmYXVsdDpPdyh0aGlzLGUsdCk7YnJlYWt9dGhpcy5feDA9dGhpcy5feDEsdGhpcy5feDE9ZSx0aGlzLl95MD10aGlzLl95MSx0aGlzLl95MT10fX19KTtmdW5jdGlvbiBERnQoZSl7dGhpcy5fY29udGV4dD1lfWZ1bmN0aW9uIE9GdChlKXtyZXR1cm4gbmV3IERGdChlKX12YXIgekZ0PU0oKCk9PntEQygpO0RGdC5wcm90b3R5cGU9e2FyZWFTdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94MD10aGlzLl94MT10aGlzLl95MD10aGlzLl95MT1OYU4sdGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpeyh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0zKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2goZT0rZSx0PSt0LHRoaXMuX3BvaW50KXtjYXNlIDA6dGhpcy5fcG9pbnQ9MTticmVhaztjYXNlIDE6dGhpcy5fcG9pbnQ9MjticmVhaztjYXNlIDI6dGhpcy5fcG9pbnQ9Mzt2YXIgcj0odGhpcy5feDArNCp0aGlzLl94MStlKS82LG49KHRoaXMuX3kwKzQqdGhpcy5feTErdCkvNjt0aGlzLl9saW5lP3RoaXMuX2NvbnRleHQubGluZVRvKHIsbik6dGhpcy5fY29udGV4dC5tb3ZlVG8ocixuKTticmVhaztjYXNlIDM6dGhpcy5fcG9pbnQ9NDtkZWZhdWx0Ok93KHRoaXMsZSx0KTticmVha310aGlzLl94MD10aGlzLl94MSx0aGlzLl94MT1lLHRoaXMuX3kwPXRoaXMuX3kxLHRoaXMuX3kxPXR9fX0pO2Z1bmN0aW9uIEZGdChlLHQpe3RoaXMuX2Jhc2lzPW5ldyBOQyhlKSx0aGlzLl9iZXRhPXR9dmFyIEJGdCxIRnQ9TSgoKT0+e0RDKCk7RkZ0LnByb3RvdHlwZT17bGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5feD1bXSx0aGlzLl95PVtdLHRoaXMuX2Jhc2lzLmxpbmVTdGFydCgpfSxsaW5lRW5kOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5feCx0PXRoaXMuX3kscj1lLmxlbmd0aC0xO2lmKHI+MClmb3IodmFyIG49ZVswXSxpPXRbMF0sbz1lW3JdLW4sYT10W3JdLWkscz0tMSxsOysrczw9cjspbD1zL3IsdGhpcy5fYmFzaXMucG9pbnQodGhpcy5fYmV0YSplW3NdKygxLXRoaXMuX2JldGEpKihuK2wqbyksdGhpcy5fYmV0YSp0W3NdKygxLXRoaXMuX2JldGEpKihpK2wqYSkpO3RoaXMuX3g9dGhpcy5feT1udWxsLHRoaXMuX2Jhc2lzLmxpbmVFbmQoKX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXt0aGlzLl94LnB1c2goK2UpLHRoaXMuX3kucHVzaCgrdCl9fTtCRnQ9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4pe3JldHVybiB0PT09MT9uZXcgTkMobik6bmV3IEZGdChuLHQpfXJldHVybiByLmJldGE9ZnVuY3Rpb24obil7cmV0dXJuIGUoK24pfSxyfSguODUpfSk7ZnVuY3Rpb24gencoZSx0LHIpe2UuX2NvbnRleHQuYmV6aWVyQ3VydmVUbyhlLl94MStlLl9rKihlLl94Mi1lLl94MCksZS5feTErZS5fayooZS5feTItZS5feTApLGUuX3gyK2UuX2sqKGUuX3gxLXQpLGUuX3kyK2UuX2sqKGUuX3kxLXIpLGUuX3gyLGUuX3kyKX1mdW5jdGlvbiBBNyhlLHQpe3RoaXMuX2NvbnRleHQ9ZSx0aGlzLl9rPSgxLXQpLzZ9dmFyIFZGdCxPQz1NKCgpPT57QTcucHJvdG90eXBlPXthcmVhU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPTB9LGFyZWFFbmQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPU5hTn0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5feDA9dGhpcy5feDE9dGhpcy5feDI9dGhpcy5feTA9dGhpcy5feTE9dGhpcy5feTI9TmFOLHRoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMjp0aGlzLl9jb250ZXh0LmxpbmVUbyh0aGlzLl94Mix0aGlzLl95Mik7YnJlYWs7Y2FzZSAzOnp3KHRoaXMsdGhpcy5feDEsdGhpcy5feTEpO2JyZWFrfSh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0xKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2goZT0rZSx0PSt0LHRoaXMuX3BvaW50KXtjYXNlIDA6dGhpcy5fcG9pbnQ9MSx0aGlzLl9saW5lP3RoaXMuX2NvbnRleHQubGluZVRvKGUsdCk6dGhpcy5fY29udGV4dC5tb3ZlVG8oZSx0KTticmVhaztjYXNlIDE6dGhpcy5fcG9pbnQ9Mix0aGlzLl94MT1lLHRoaXMuX3kxPXQ7YnJlYWs7Y2FzZSAyOnRoaXMuX3BvaW50PTM7ZGVmYXVsdDp6dyh0aGlzLGUsdCk7YnJlYWt9dGhpcy5feDA9dGhpcy5feDEsdGhpcy5feDE9dGhpcy5feDIsdGhpcy5feDI9ZSx0aGlzLl95MD10aGlzLl95MSx0aGlzLl95MT10aGlzLl95Mix0aGlzLl95Mj10fX07VkZ0PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuKXtyZXR1cm4gbmV3IEE3KG4sdCl9cmV0dXJuIHIudGVuc2lvbj1mdW5jdGlvbihuKXtyZXR1cm4gZSgrbil9LHJ9KDApfSk7ZnVuY3Rpb24gUDcoZSx0KXt0aGlzLl9jb250ZXh0PWUsdGhpcy5faz0oMS10KS82fXZhciBVRnQsWWV0PU0oKCk9PntSQygpO09DKCk7UDcucHJvdG90eXBlPXthcmVhU3RhcnQ6Q2MsYXJlYUVuZDpDYyxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94MD10aGlzLl94MT10aGlzLl94Mj10aGlzLl94Mz10aGlzLl94ND10aGlzLl94NT10aGlzLl95MD10aGlzLl95MT10aGlzLl95Mj10aGlzLl95Mz10aGlzLl95ND10aGlzLl95NT1OYU4sdGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpe3N3aXRjaCh0aGlzLl9wb2ludCl7Y2FzZSAxOnt0aGlzLl9jb250ZXh0Lm1vdmVUbyh0aGlzLl94Myx0aGlzLl95MyksdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKTticmVha31jYXNlIDI6e3RoaXMuX2NvbnRleHQubGluZVRvKHRoaXMuX3gzLHRoaXMuX3kzKSx0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpO2JyZWFrfWNhc2UgMzp7dGhpcy5wb2ludCh0aGlzLl94Myx0aGlzLl95MyksdGhpcy5wb2ludCh0aGlzLl94NCx0aGlzLl95NCksdGhpcy5wb2ludCh0aGlzLl94NSx0aGlzLl95NSk7YnJlYWt9fX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2goZT0rZSx0PSt0LHRoaXMuX3BvaW50KXtjYXNlIDA6dGhpcy5fcG9pbnQ9MSx0aGlzLl94Mz1lLHRoaXMuX3kzPXQ7YnJlYWs7Y2FzZSAxOnRoaXMuX3BvaW50PTIsdGhpcy5fY29udGV4dC5tb3ZlVG8odGhpcy5feDQ9ZSx0aGlzLl95ND10KTticmVhaztjYXNlIDI6dGhpcy5fcG9pbnQ9Myx0aGlzLl94NT1lLHRoaXMuX3k1PXQ7YnJlYWs7ZGVmYXVsdDp6dyh0aGlzLGUsdCk7YnJlYWt9dGhpcy5feDA9dGhpcy5feDEsdGhpcy5feDE9dGhpcy5feDIsdGhpcy5feDI9ZSx0aGlzLl95MD10aGlzLl95MSx0aGlzLl95MT10aGlzLl95Mix0aGlzLl95Mj10fX07VUZ0PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuKXtyZXR1cm4gbmV3IFA3KG4sdCl9cmV0dXJuIHIudGVuc2lvbj1mdW5jdGlvbihuKXtyZXR1cm4gZSgrbil9LHJ9KDApfSk7ZnVuY3Rpb24gSTcoZSx0KXt0aGlzLl9jb250ZXh0PWUsdGhpcy5faz0oMS10KS82fXZhciBxRnQsamV0PU0oKCk9PntPQygpO0k3LnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3gwPXRoaXMuX3gxPXRoaXMuX3gyPXRoaXMuX3kwPXRoaXMuX3kxPXRoaXMuX3kyPU5hTix0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7KHRoaXMuX2xpbmV8fHRoaXMuX2xpbmUhPT0wJiZ0aGlzLl9wb2ludD09PTMpJiZ0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpLHRoaXMuX2xpbmU9MS10aGlzLl9saW5lfSxwb2ludDpmdW5jdGlvbihlLHQpe3N3aXRjaChlPStlLHQ9K3QsdGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xO2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yO2JyZWFrO2Nhc2UgMjp0aGlzLl9wb2ludD0zLHRoaXMuX2xpbmU/dGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feDIsdGhpcy5feTIpOnRoaXMuX2NvbnRleHQubW92ZVRvKHRoaXMuX3gyLHRoaXMuX3kyKTticmVhaztjYXNlIDM6dGhpcy5fcG9pbnQ9NDtkZWZhdWx0Onp3KHRoaXMsZSx0KTticmVha310aGlzLl94MD10aGlzLl94MSx0aGlzLl94MT10aGlzLl94Mix0aGlzLl94Mj1lLHRoaXMuX3kwPXRoaXMuX3kxLHRoaXMuX3kxPXRoaXMuX3kyLHRoaXMuX3kyPXR9fTtxRnQ9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4pe3JldHVybiBuZXcgSTcobix0KX1yZXR1cm4gci50ZW5zaW9uPWZ1bmN0aW9uKG4pe3JldHVybiBlKCtuKX0scn0oMCl9KTtmdW5jdGlvbiB6QyhlLHQscil7dmFyIG49ZS5feDEsaT1lLl95MSxvPWUuX3gyLGE9ZS5feTI7aWYoZS5fbDAxX2E+Q28pe3ZhciBzPTIqZS5fbDAxXzJhKzMqZS5fbDAxX2EqZS5fbDEyX2ErZS5fbDEyXzJhLGw9MyplLl9sMDFfYSooZS5fbDAxX2ErZS5fbDEyX2EpO249KG4qcy1lLl94MCplLl9sMTJfMmErZS5feDIqZS5fbDAxXzJhKS9sLGk9KGkqcy1lLl95MCplLl9sMTJfMmErZS5feTIqZS5fbDAxXzJhKS9sfWlmKGUuX2wyM19hPkNvKXt2YXIgYz0yKmUuX2wyM18yYSszKmUuX2wyM19hKmUuX2wxMl9hK2UuX2wxMl8yYSx1PTMqZS5fbDIzX2EqKGUuX2wyM19hK2UuX2wxMl9hKTtvPShvKmMrZS5feDEqZS5fbDIzXzJhLXQqZS5fbDEyXzJhKS91LGE9KGEqYytlLl95MSplLl9sMjNfMmEtciplLl9sMTJfMmEpL3V9ZS5fY29udGV4dC5iZXppZXJDdXJ2ZVRvKG4saSxvLGEsZS5feDIsZS5feTIpfWZ1bmN0aW9uIEdGdChlLHQpe3RoaXMuX2NvbnRleHQ9ZSx0aGlzLl9hbHBoYT10fXZhciBXRnQsTDc9TSgoKT0+e0l3KCk7T0MoKTtHRnQucHJvdG90eXBlPXthcmVhU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPTB9LGFyZWFFbmQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPU5hTn0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5feDA9dGhpcy5feDE9dGhpcy5feDI9dGhpcy5feTA9dGhpcy5feTE9dGhpcy5feTI9TmFOLHRoaXMuX2wwMV9hPXRoaXMuX2wxMl9hPXRoaXMuX2wyM19hPXRoaXMuX2wwMV8yYT10aGlzLl9sMTJfMmE9dGhpcy5fbDIzXzJhPXRoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMjp0aGlzLl9jb250ZXh0LmxpbmVUbyh0aGlzLl94Mix0aGlzLl95Mik7YnJlYWs7Y2FzZSAzOnRoaXMucG9pbnQodGhpcy5feDIsdGhpcy5feTIpO2JyZWFrfSh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0xKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtpZihlPStlLHQ9K3QsdGhpcy5fcG9pbnQpe3ZhciByPXRoaXMuX3gyLWUsbj10aGlzLl95Mi10O3RoaXMuX2wyM19hPU1hdGguc3FydCh0aGlzLl9sMjNfMmE9TWF0aC5wb3cocipyK24qbix0aGlzLl9hbHBoYSkpfXN3aXRjaCh0aGlzLl9wb2ludCl7Y2FzZSAwOnRoaXMuX3BvaW50PTEsdGhpcy5fbGluZT90aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpOnRoaXMuX2NvbnRleHQubW92ZVRvKGUsdCk7YnJlYWs7Y2FzZSAxOnRoaXMuX3BvaW50PTI7YnJlYWs7Y2FzZSAyOnRoaXMuX3BvaW50PTM7ZGVmYXVsdDp6Qyh0aGlzLGUsdCk7YnJlYWt9dGhpcy5fbDAxX2E9dGhpcy5fbDEyX2EsdGhpcy5fbDEyX2E9dGhpcy5fbDIzX2EsdGhpcy5fbDAxXzJhPXRoaXMuX2wxMl8yYSx0aGlzLl9sMTJfMmE9dGhpcy5fbDIzXzJhLHRoaXMuX3gwPXRoaXMuX3gxLHRoaXMuX3gxPXRoaXMuX3gyLHRoaXMuX3gyPWUsdGhpcy5feTA9dGhpcy5feTEsdGhpcy5feTE9dGhpcy5feTIsdGhpcy5feTI9dH19O1dGdD1mdW5jdGlvbiBlKHQpe2Z1bmN0aW9uIHIobil7cmV0dXJuIHQ/bmV3IEdGdChuLHQpOm5ldyBBNyhuLDApfXJldHVybiByLmFscGhhPWZ1bmN0aW9uKG4pe3JldHVybiBlKCtuKX0scn0oLjUpfSk7ZnVuY3Rpb24gWUZ0KGUsdCl7dGhpcy5fY29udGV4dD1lLHRoaXMuX2FscGhhPXR9dmFyIGpGdCxYRnQ9TSgoKT0+e1lldCgpO1JDKCk7TDcoKTtZRnQucHJvdG90eXBlPXthcmVhU3RhcnQ6Q2MsYXJlYUVuZDpDYyxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94MD10aGlzLl94MT10aGlzLl94Mj10aGlzLl94Mz10aGlzLl94ND10aGlzLl94NT10aGlzLl95MD10aGlzLl95MT10aGlzLl95Mj10aGlzLl95Mz10aGlzLl95ND10aGlzLl95NT1OYU4sdGhpcy5fbDAxX2E9dGhpcy5fbDEyX2E9dGhpcy5fbDIzX2E9dGhpcy5fbDAxXzJhPXRoaXMuX2wxMl8yYT10aGlzLl9sMjNfMmE9dGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpe3N3aXRjaCh0aGlzLl9wb2ludCl7Y2FzZSAxOnt0aGlzLl9jb250ZXh0Lm1vdmVUbyh0aGlzLl94Myx0aGlzLl95MyksdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKTticmVha31jYXNlIDI6e3RoaXMuX2NvbnRleHQubGluZVRvKHRoaXMuX3gzLHRoaXMuX3kzKSx0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpO2JyZWFrfWNhc2UgMzp7dGhpcy5wb2ludCh0aGlzLl94Myx0aGlzLl95MyksdGhpcy5wb2ludCh0aGlzLl94NCx0aGlzLl95NCksdGhpcy5wb2ludCh0aGlzLl94NSx0aGlzLl95NSk7YnJlYWt9fX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtpZihlPStlLHQ9K3QsdGhpcy5fcG9pbnQpe3ZhciByPXRoaXMuX3gyLWUsbj10aGlzLl95Mi10O3RoaXMuX2wyM19hPU1hdGguc3FydCh0aGlzLl9sMjNfMmE9TWF0aC5wb3cocipyK24qbix0aGlzLl9hbHBoYSkpfXN3aXRjaCh0aGlzLl9wb2ludCl7Y2FzZSAwOnRoaXMuX3BvaW50PTEsdGhpcy5feDM9ZSx0aGlzLl95Mz10O2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yLHRoaXMuX2NvbnRleHQubW92ZVRvKHRoaXMuX3g0PWUsdGhpcy5feTQ9dCk7YnJlYWs7Y2FzZSAyOnRoaXMuX3BvaW50PTMsdGhpcy5feDU9ZSx0aGlzLl95NT10O2JyZWFrO2RlZmF1bHQ6ekModGhpcyxlLHQpO2JyZWFrfXRoaXMuX2wwMV9hPXRoaXMuX2wxMl9hLHRoaXMuX2wxMl9hPXRoaXMuX2wyM19hLHRoaXMuX2wwMV8yYT10aGlzLl9sMTJfMmEsdGhpcy5fbDEyXzJhPXRoaXMuX2wyM18yYSx0aGlzLl94MD10aGlzLl94MSx0aGlzLl94MT10aGlzLl94Mix0aGlzLl94Mj1lLHRoaXMuX3kwPXRoaXMuX3kxLHRoaXMuX3kxPXRoaXMuX3kyLHRoaXMuX3kyPXR9fTtqRnQ9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4pe3JldHVybiB0P25ldyBZRnQobix0KTpuZXcgUDcobiwwKX1yZXR1cm4gci5hbHBoYT1mdW5jdGlvbihuKXtyZXR1cm4gZSgrbil9LHJ9KC41KX0pO2Z1bmN0aW9uICRGdChlLHQpe3RoaXMuX2NvbnRleHQ9ZSx0aGlzLl9hbHBoYT10fXZhciBLRnQsWkZ0PU0oKCk9PntqZXQoKTtMNygpOyRGdC5wcm90b3R5cGU9e2FyZWFTdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94MD10aGlzLl94MT10aGlzLl94Mj10aGlzLl95MD10aGlzLl95MT10aGlzLl95Mj1OYU4sdGhpcy5fbDAxX2E9dGhpcy5fbDEyX2E9dGhpcy5fbDIzX2E9dGhpcy5fbDAxXzJhPXRoaXMuX2wxMl8yYT10aGlzLl9sMjNfMmE9dGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpeyh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0zKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtpZihlPStlLHQ9K3QsdGhpcy5fcG9pbnQpe3ZhciByPXRoaXMuX3gyLWUsbj10aGlzLl95Mi10O3RoaXMuX2wyM19hPU1hdGguc3FydCh0aGlzLl9sMjNfMmE9TWF0aC5wb3cocipyK24qbix0aGlzLl9hbHBoYSkpfXN3aXRjaCh0aGlzLl9wb2ludCl7Y2FzZSAwOnRoaXMuX3BvaW50PTE7YnJlYWs7Y2FzZSAxOnRoaXMuX3BvaW50PTI7YnJlYWs7Y2FzZSAyOnRoaXMuX3BvaW50PTMsdGhpcy5fbGluZT90aGlzLl9jb250ZXh0LmxpbmVUbyh0aGlzLl94Mix0aGlzLl95Mik6dGhpcy5fY29udGV4dC5tb3ZlVG8odGhpcy5feDIsdGhpcy5feTIpO2JyZWFrO2Nhc2UgMzp0aGlzLl9wb2ludD00O2RlZmF1bHQ6ekModGhpcyxlLHQpO2JyZWFrfXRoaXMuX2wwMV9hPXRoaXMuX2wxMl9hLHRoaXMuX2wxMl9hPXRoaXMuX2wyM19hLHRoaXMuX2wwMV8yYT10aGlzLl9sMTJfMmEsdGhpcy5fbDEyXzJhPXRoaXMuX2wyM18yYSx0aGlzLl94MD10aGlzLl94MSx0aGlzLl94MT10aGlzLl94Mix0aGlzLl94Mj1lLHRoaXMuX3kwPXRoaXMuX3kxLHRoaXMuX3kxPXRoaXMuX3kyLHRoaXMuX3kyPXR9fTtLRnQ9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4pe3JldHVybiB0P25ldyAkRnQobix0KTpuZXcgSTcobiwwKX1yZXR1cm4gci5hbHBoYT1mdW5jdGlvbihuKXtyZXR1cm4gZSgrbil9LHJ9KC41KX0pO2Z1bmN0aW9uIEpGdChlKXt0aGlzLl9jb250ZXh0PWV9ZnVuY3Rpb24gUUZ0KGUpe3JldHVybiBuZXcgSkZ0KGUpfXZhciB0QnQ9TSgoKT0+e1JDKCk7SkZ0LnByb3RvdHlwZT17YXJlYVN0YXJ0OkNjLGFyZWFFbmQ6Q2MsbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpe3RoaXMuX3BvaW50JiZ0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpfSxwb2ludDpmdW5jdGlvbihlLHQpe2U9K2UsdD0rdCx0aGlzLl9wb2ludD90aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpOih0aGlzLl9wb2ludD0xLHRoaXMuX2NvbnRleHQubW92ZVRvKGUsdCkpfX19KTtmdW5jdGlvbiBlQnQoZSl7cmV0dXJuIGU8MD8tMToxfWZ1bmN0aW9uIHJCdChlLHQscil7dmFyIG49ZS5feDEtZS5feDAsaT10LWUuX3gxLG89KGUuX3kxLWUuX3kwKS8obnx8aTwwJiYtMCksYT0oci1lLl95MSkvKGl8fG48MCYmLTApLHM9KG8qaSthKm4pLyhuK2kpO3JldHVybihlQnQobykrZUJ0KGEpKSpNYXRoLm1pbihNYXRoLmFicyhvKSxNYXRoLmFicyhhKSwuNSpNYXRoLmFicyhzKSl8fDB9ZnVuY3Rpb24gbkJ0KGUsdCl7dmFyIHI9ZS5feDEtZS5feDA7cmV0dXJuIHI/KDMqKGUuX3kxLWUuX3kwKS9yLXQpLzI6dH1mdW5jdGlvbiBYZXQoZSx0LHIpe3ZhciBuPWUuX3gwLGk9ZS5feTAsbz1lLl94MSxhPWUuX3kxLHM9KG8tbikvMztlLl9jb250ZXh0LmJlemllckN1cnZlVG8obitzLGkrcyp0LG8tcyxhLXMqcixvLGEpfWZ1bmN0aW9uIGs3KGUpe3RoaXMuX2NvbnRleHQ9ZX1mdW5jdGlvbiBpQnQoZSl7dGhpcy5fY29udGV4dD1uZXcgb0J0KGUpfWZ1bmN0aW9uIG9CdChlKXt0aGlzLl9jb250ZXh0PWV9ZnVuY3Rpb24gYUJ0KGUpe3JldHVybiBuZXcgazcoZSl9ZnVuY3Rpb24gc0J0KGUpe3JldHVybiBuZXcgaUJ0KGUpfXZhciBsQnQ9TSgoKT0+e2s3LnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3gwPXRoaXMuX3gxPXRoaXMuX3kwPXRoaXMuX3kxPXRoaXMuX3QwPU5hTix0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7c3dpdGNoKHRoaXMuX3BvaW50KXtjYXNlIDI6dGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feDEsdGhpcy5feTEpO2JyZWFrO2Nhc2UgMzpYZXQodGhpcyx0aGlzLl90MCxuQnQodGhpcyx0aGlzLl90MCkpO2JyZWFrfSh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0xKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXt2YXIgcj1OYU47aWYoZT0rZSx0PSt0LCEoZT09PXRoaXMuX3gxJiZ0PT09dGhpcy5feTEpKXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xLHRoaXMuX2xpbmU/dGhpcy5fY29udGV4dC5saW5lVG8oZSx0KTp0aGlzLl9jb250ZXh0Lm1vdmVUbyhlLHQpO2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yO2JyZWFrO2Nhc2UgMjp0aGlzLl9wb2ludD0zLFhldCh0aGlzLG5CdCh0aGlzLHI9ckJ0KHRoaXMsZSx0KSkscik7YnJlYWs7ZGVmYXVsdDpYZXQodGhpcyx0aGlzLl90MCxyPXJCdCh0aGlzLGUsdCkpO2JyZWFrfXRoaXMuX3gwPXRoaXMuX3gxLHRoaXMuX3gxPWUsdGhpcy5feTA9dGhpcy5feTEsdGhpcy5feTE9dCx0aGlzLl90MD1yfX19OyhpQnQucHJvdG90eXBlPU9iamVjdC5jcmVhdGUoazcucHJvdG90eXBlKSkucG9pbnQ9ZnVuY3Rpb24oZSx0KXtrNy5wcm90b3R5cGUucG9pbnQuY2FsbCh0aGlzLHQsZSl9O29CdC5wcm90b3R5cGU9e21vdmVUbzpmdW5jdGlvbihlLHQpe3RoaXMuX2NvbnRleHQubW92ZVRvKHQsZSl9LGNsb3NlUGF0aDpmdW5jdGlvbigpe3RoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCl9LGxpbmVUbzpmdW5jdGlvbihlLHQpe3RoaXMuX2NvbnRleHQubGluZVRvKHQsZSl9LGJlemllckN1cnZlVG86ZnVuY3Rpb24oZSx0LHIsbixpLG8pe3RoaXMuX2NvbnRleHQuYmV6aWVyQ3VydmVUbyh0LGUsbixyLG8saSl9fX0pO2Z1bmN0aW9uIHVCdChlKXt0aGlzLl9jb250ZXh0PWV9ZnVuY3Rpb24gY0J0KGUpe3ZhciB0LHI9ZS5sZW5ndGgtMSxuLGk9bmV3IEFycmF5KHIpLG89bmV3IEFycmF5KHIpLGE9bmV3IEFycmF5KHIpO2ZvcihpWzBdPTAsb1swXT0yLGFbMF09ZVswXSsyKmVbMV0sdD0xO3Q8ci0xOysrdClpW3RdPTEsb1t0XT00LGFbdF09NCplW3RdKzIqZVt0KzFdO2ZvcihpW3ItMV09MixvW3ItMV09NyxhW3ItMV09OCplW3ItMV0rZVtyXSx0PTE7dDxyOysrdCluPWlbdF0vb1t0LTFdLG9bdF0tPW4sYVt0XS09biphW3QtMV07Zm9yKGlbci0xXT1hW3ItMV0vb1tyLTFdLHQ9ci0yO3Q+PTA7LS10KWlbdF09KGFbdF0taVt0KzFdKS9vW3RdO2ZvcihvW3ItMV09KGVbcl0raVtyLTFdKS8yLHQ9MDt0PHItMTsrK3Qpb1t0XT0yKmVbdCsxXS1pW3QrMV07cmV0dXJuW2ksb119ZnVuY3Rpb24gaEJ0KGUpe3JldHVybiBuZXcgdUJ0KGUpfXZhciBmQnQ9TSgoKT0+e3VCdC5wcm90b3R5cGU9e2FyZWFTdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94PVtdLHRoaXMuX3k9W119LGxpbmVFbmQ6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLl94LHQ9dGhpcy5feSxyPWUubGVuZ3RoO2lmKHIpaWYodGhpcy5fbGluZT90aGlzLl9jb250ZXh0LmxpbmVUbyhlWzBdLHRbMF0pOnRoaXMuX2NvbnRleHQubW92ZVRvKGVbMF0sdFswXSkscj09PTIpdGhpcy5fY29udGV4dC5saW5lVG8oZVsxXSx0WzFdKTtlbHNlIGZvcih2YXIgbj1jQnQoZSksaT1jQnQodCksbz0wLGE9MTthPHI7KytvLCsrYSl0aGlzLl9jb250ZXh0LmJlemllckN1cnZlVG8oblswXVtvXSxpWzBdW29dLG5bMV1bb10saVsxXVtvXSxlW2FdLHRbYV0pOyh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmcj09PTEpJiZ0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpLHRoaXMuX2xpbmU9MS10aGlzLl9saW5lLHRoaXMuX3g9dGhpcy5feT1udWxsfSxwb2ludDpmdW5jdGlvbihlLHQpe3RoaXMuX3gucHVzaCgrZSksdGhpcy5feS5wdXNoKCt0KX19fSk7ZnVuY3Rpb24gUjcoZSx0KXt0aGlzLl9jb250ZXh0PWUsdGhpcy5fdD10fWZ1bmN0aW9uIHBCdChlKXtyZXR1cm4gbmV3IFI3KGUsLjUpfWZ1bmN0aW9uIGRCdChlKXtyZXR1cm4gbmV3IFI3KGUsMCl9ZnVuY3Rpb24gbUJ0KGUpe3JldHVybiBuZXcgUjcoZSwxKX12YXIgZ0J0PU0oKCk9PntSNy5wcm90b3R5cGU9e2FyZWFTdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94PXRoaXMuX3k9TmFOLHRoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXswPHRoaXMuX3QmJnRoaXMuX3Q8MSYmdGhpcy5fcG9pbnQ9PT0yJiZ0aGlzLl9jb250ZXh0LmxpbmVUbyh0aGlzLl94LHRoaXMuX3kpLCh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0xKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPj0wJiYodGhpcy5fdD0xLXRoaXMuX3QsdGhpcy5fbGluZT0xLXRoaXMuX2xpbmUpfSxwb2ludDpmdW5jdGlvbihlLHQpe3N3aXRjaChlPStlLHQ9K3QsdGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xLHRoaXMuX2xpbmU/dGhpcy5fY29udGV4dC5saW5lVG8oZSx0KTp0aGlzLl9jb250ZXh0Lm1vdmVUbyhlLHQpO2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yO2RlZmF1bHQ6e2lmKHRoaXMuX3Q8PTApdGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feCx0KSx0aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpO2Vsc2V7dmFyIHI9dGhpcy5feCooMS10aGlzLl90KStlKnRoaXMuX3Q7dGhpcy5fY29udGV4dC5saW5lVG8ocix0aGlzLl95KSx0aGlzLl9jb250ZXh0LmxpbmVUbyhyLHQpfWJyZWFrfX10aGlzLl94PWUsdGhpcy5feT10fX19KTtmdW5jdGlvbiBSdShlLHQpe2lmKChhPWUubGVuZ3RoKT4xKWZvcih2YXIgcj0xLG4saSxvPWVbdFswXV0sYSxzPW8ubGVuZ3RoO3I8YTsrK3IpZm9yKGk9byxvPWVbdFtyXV0sbj0wO248czsrK24pb1tuXVsxXSs9b1tuXVswXT1pc05hTihpW25dWzFdKT9pW25dWzBdOmlbbl1bMV19dmFyIEZ3PU0oKCk9Pnt9KTtmdW5jdGlvbiBOdShlKXtmb3IodmFyIHQ9ZS5sZW5ndGgscj1uZXcgQXJyYXkodCk7LS10Pj0wOylyW3RdPXQ7cmV0dXJuIHJ9dmFyIEJ3PU0oKCk9Pnt9KTtmdW5jdGlvbiBDTGUoZSx0KXtyZXR1cm4gZVt0XX1mdW5jdGlvbiBfQnQoKXt2YXIgZT1HZShbXSksdD1OdSxyPVJ1LG49Q0xlO2Z1bmN0aW9uIGkobyl7dmFyIGE9ZS5hcHBseSh0aGlzLGFyZ3VtZW50cykscyxsPW8ubGVuZ3RoLGM9YS5sZW5ndGgsdT1uZXcgQXJyYXkoYyksaDtmb3Iocz0wO3M8YzsrK3Mpe2Zvcih2YXIgZj1hW3NdLHA9dVtzXT1uZXcgQXJyYXkobCksZD0wLGc7ZDxsOysrZClwW2RdPWc9WzAsK24ob1tkXSxmLGQsbyldLGcuZGF0YT1vW2RdO3Aua2V5PWZ9Zm9yKHM9MCxoPXQodSk7czxjOysrcyl1W2hbc11dLmluZGV4PXM7cmV0dXJuIHIodSxoKSx1fXJldHVybiBpLmtleXM9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIG89PSJmdW5jdGlvbiI/bzpHZShMQy5jYWxsKG8pKSxpKTplfSxpLnZhbHVlPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPXR5cGVvZiBvPT0iZnVuY3Rpb24iP286R2UoK28pLGkpOm59LGkub3JkZXI9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9bz09bnVsbD9OdTp0eXBlb2Ygbz09ImZ1bmN0aW9uIj9vOkdlKExDLmNhbGwobykpLGkpOnR9LGkub2Zmc2V0PWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPW89PW51bGw/UnU6byxpKTpyfSxpfXZhciB5QnQ9TSgoKT0+e0RldCgpO09nKCk7RncoKTtCdygpfSk7ZnVuY3Rpb24gdkJ0KGUsdCl7aWYoKG49ZS5sZW5ndGgpPjApe2Zvcih2YXIgcixuLGk9MCxvPWVbMF0ubGVuZ3RoLGE7aTxvOysraSl7Zm9yKGE9cj0wO3I8bjsrK3IpYSs9ZVtyXVtpXVsxXXx8MDtpZihhKWZvcihyPTA7cjxuOysrcillW3JdW2ldWzFdLz1hfVJ1KGUsdCl9fXZhciB4QnQ9TSgoKT0+e0Z3KCl9KTtmdW5jdGlvbiBiQnQoZSx0KXtpZigobD1lLmxlbmd0aCk+MSlmb3IodmFyIHIsbj0wLGksbyxhLHMsbCxjPWVbdFswXV0ubGVuZ3RoO248YzsrK24pZm9yKGE9cz0wLHI9MDtyPGw7KytyKShvPShpPWVbdFtyXV1bbl0pWzFdLWlbMF0pPj0wPyhpWzBdPWEsaVsxXT1hKz1vKTpvPDA/KGlbMV09cyxpWzBdPXMrPW8pOmlbMF09YX12YXIgd0J0PU0oKCk9Pnt9KTtmdW5jdGlvbiBTQnQoZSx0KXtpZigoaT1lLmxlbmd0aCk+MCl7Zm9yKHZhciByPTAsbj1lW3RbMF1dLGksbz1uLmxlbmd0aDtyPG87KytyKXtmb3IodmFyIGE9MCxzPTA7YTxpOysrYSlzKz1lW2FdW3JdWzFdfHwwO25bcl1bMV0rPW5bcl1bMF09LXMvMn1SdShlLHQpfX12YXIgTUJ0PU0oKCk9PntGdygpfSk7ZnVuY3Rpb24gRUJ0KGUsdCl7aWYoISghKChhPWUubGVuZ3RoKT4wKXx8ISgobz0oaT1lW3RbMF1dKS5sZW5ndGgpPjApKSl7Zm9yKHZhciByPTAsbj0xLGksbyxhO248bzsrK24pe2Zvcih2YXIgcz0wLGw9MCxjPTA7czxhOysrcyl7Zm9yKHZhciB1PWVbdFtzXV0saD11W25dWzFdfHwwLGY9dVtuLTFdWzFdfHwwLHA9KGgtZikvMixkPTA7ZDxzOysrZCl7dmFyIGc9ZVt0W2RdXSxfPWdbbl1bMV18fDAseT1nW24tMV1bMV18fDA7cCs9Xy15fWwrPWgsYys9cCpofWlbbi0xXVsxXSs9aVtuLTFdWzBdPXIsbCYmKHItPWMvbCl9aVtuLTFdWzFdKz1pW24tMV1bMF09cixSdShlLHQpfX12YXIgVEJ0PU0oKCk9PntGdygpfSk7ZnVuY3Rpb24gTjcoZSl7dmFyIHQ9ZS5tYXAoJGV0KTtyZXR1cm4gTnUoZSkuc29ydChmdW5jdGlvbihyLG4pe3JldHVybiB0W3JdLXRbbl19KX1mdW5jdGlvbiAkZXQoZSl7Zm9yKHZhciB0PTAscj0tMSxuPWUubGVuZ3RoLGk7KytyPG47KShpPStlW3JdWzFdKSYmKHQrPWkpO3JldHVybiB0fXZhciBENz1NKCgpPT57QncoKX0pO2Z1bmN0aW9uIENCdChlKXtyZXR1cm4gTjcoZSkucmV2ZXJzZSgpfXZhciBBQnQ9TSgoKT0+e0Q3KCl9KTtmdW5jdGlvbiBQQnQoZSl7dmFyIHQ9ZS5sZW5ndGgscixuLGk9ZS5tYXAoJGV0KSxvPU51KGUpLnNvcnQoZnVuY3Rpb24odSxoKXtyZXR1cm4gaVtoXS1pW3VdfSksYT0wLHM9MCxsPVtdLGM9W107Zm9yKHI9MDtyPHQ7KytyKW49b1tyXSxhPHM/KGErPWlbbl0sbC5wdXNoKG4pKToocys9aVtuXSxjLnB1c2gobikpO3JldHVybiBjLnJldmVyc2UoKS5jb25jYXQobCl9dmFyIElCdD1NKCgpPT57QncoKTtENygpfSk7ZnVuY3Rpb24gTEJ0KGUpe3JldHVybiBOdShlKS5yZXZlcnNlKCl9dmFyIGtCdD1NKCgpPT57QncoKX0pO3ZhciBSQnQ9TSgoKT0+e2ZGdCgpO1BldCgpO3Y3KCk7dkZ0KCk7YkZ0KCk7a2V0KCk7TmV0KCk7RUZ0KCk7SUZ0KCk7emV0KCk7RmV0KCk7QmV0KCk7VmV0KCk7SGV0KCk7cWV0KCk7V2V0KCk7TkZ0KCk7ekZ0KCk7REMoKTtIRnQoKTtZZXQoKTtqZXQoKTtPQygpO1hGdCgpO1pGdCgpO0w3KCk7dEJ0KCk7SUMoKTtsQnQoKTtmQnQoKTtnQnQoKTt5QnQoKTt4QnQoKTt3QnQoKTtGdygpO01CdCgpO1RCdCgpO0Q3KCk7QUJ0KCk7SUJ0KCk7QncoKTtrQnQoKX0pO2Z1bmN0aW9uIFNyKGUsdCxyLG4pe2Z1bmN0aW9uIGkobyl7cmV0dXJuIGUobz1uZXcgRGF0ZSgrbykpLG99cmV0dXJuIGkuZmxvb3I9aSxpLmNlaWw9ZnVuY3Rpb24obyl7cmV0dXJuIGUobz1uZXcgRGF0ZShvLTEpKSx0KG8sMSksZShvKSxvfSxpLnJvdW5kPWZ1bmN0aW9uKG8pe3ZhciBhPWkobykscz1pLmNlaWwobyk7cmV0dXJuIG8tYTxzLW8/YTpzfSxpLm9mZnNldD1mdW5jdGlvbihvLGEpe3JldHVybiB0KG89bmV3IERhdGUoK28pLGE9PW51bGw/MTpNYXRoLmZsb29yKGEpKSxvfSxpLnJhbmdlPWZ1bmN0aW9uKG8sYSxzKXt2YXIgbD1bXSxjO2lmKG89aS5jZWlsKG8pLHM9cz09bnVsbD8xOk1hdGguZmxvb3IocyksIShvPGEpfHwhKHM+MCkpcmV0dXJuIGw7ZG8gbC5wdXNoKGM9bmV3IERhdGUoK28pKSx0KG8scyksZShvKTt3aGlsZShjPG8mJm88YSk7cmV0dXJuIGx9LGkuZmlsdGVyPWZ1bmN0aW9uKG8pe3JldHVybiBTcihmdW5jdGlvbihhKXtpZihhPj1hKWZvcig7ZShhKSwhbyhhKTspYS5zZXRUaW1lKGEtMSl9LGZ1bmN0aW9uKGEscyl7aWYoYT49YSlpZihzPDApZm9yKDsrK3M8PTA7KWZvcig7dChhLC0xKSwhbyhhKTspO2Vsc2UgZm9yKDstLXM+PTA7KWZvcig7dChhLDEpLCFvKGEpOyk7fSl9LHImJihpLmNvdW50PWZ1bmN0aW9uKG8sYSl7cmV0dXJuIEtldC5zZXRUaW1lKCtvKSxaZXQuc2V0VGltZSgrYSksZShLZXQpLGUoWmV0KSxNYXRoLmZsb29yKHIoS2V0LFpldCkpfSxpLmV2ZXJ5PWZ1bmN0aW9uKG8pe3JldHVybiBvPU1hdGguZmxvb3IobyksIWlzRmluaXRlKG8pfHwhKG8+MCk/bnVsbDpvPjE/aS5maWx0ZXIobj9mdW5jdGlvbihhKXtyZXR1cm4gbihhKSVvPT09MH06ZnVuY3Rpb24oYSl7cmV0dXJuIGkuY291bnQoMCxhKSVvPT09MH0pOml9KSxpfXZhciBLZXQsWmV0LGZhPU0oKCk9PntLZXQ9bmV3IERhdGUsWmV0PW5ldyBEYXRlfSk7dmFyIE83LEpldCxRZXQsTkJ0PU0oKCk9PntmYSgpO083PVNyKGZ1bmN0aW9uKCl7fSxmdW5jdGlvbihlLHQpe2Uuc2V0VGltZSgrZSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4gdC1lfSk7TzcuZXZlcnk9ZnVuY3Rpb24oZSl7cmV0dXJuIGU9TWF0aC5mbG9vcihlKSwhaXNGaW5pdGUoZSl8fCEoZT4wKT9udWxsOmU+MT9TcihmdW5jdGlvbih0KXt0LnNldFRpbWUoTWF0aC5mbG9vcih0L2UpKmUpfSxmdW5jdGlvbih0LHIpe3Quc2V0VGltZSgrdCtyKmUpfSxmdW5jdGlvbih0LHIpe3JldHVybihyLXQpL2V9KTpPN307SmV0PU83LFFldD1PNy5yYW5nZX0pO3ZhciBGQyxQcyxiZix6NyxGNyx3Zj1NKCgpPT57RkM9MWUzLFBzPTZlNCxiZj0zNmU1LHo3PTg2NGU1LEY3PTYwNDhlNX0pO3ZhciBEQnQsdHJ0LGVydCxPQnQ9TSgoKT0+e2ZhKCk7d2YoKTtEQnQ9U3IoZnVuY3Rpb24oZSl7ZS5zZXRUaW1lKE1hdGguZmxvb3IoZS9GQykqRkMpfSxmdW5jdGlvbihlLHQpe2Uuc2V0VGltZSgrZSt0KkZDKX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4odC1lKS9GQ30sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0VVRDU2Vjb25kcygpfSksdHJ0PURCdCxlcnQ9REJ0LnJhbmdlfSk7dmFyIHpCdCxGQnQsQkJ0LEhCdD1NKCgpPT57ZmEoKTt3ZigpO3pCdD1TcihmdW5jdGlvbihlKXtlLnNldFRpbWUoTWF0aC5mbG9vcihlL1BzKSpQcyl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXRUaW1lKCtlK3QqUHMpfSxmdW5jdGlvbihlLHQpe3JldHVybih0LWUpL1BzfSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRNaW51dGVzKCl9KSxGQnQ9ekJ0LEJCdD16QnQucmFuZ2V9KTt2YXIgVkJ0LFVCdCxxQnQsR0J0PU0oKCk9PntmYSgpO3dmKCk7VkJ0PVNyKGZ1bmN0aW9uKGUpe3ZhciB0PWUuZ2V0VGltZXpvbmVPZmZzZXQoKSpQcyViZjt0PDAmJih0Kz1iZiksZS5zZXRUaW1lKE1hdGguZmxvb3IoKCtlLXQpL2JmKSpiZit0KX0sZnVuY3Rpb24oZSx0KXtlLnNldFRpbWUoK2UrdCpiZil9LGZ1bmN0aW9uKGUsdCl7cmV0dXJuKHQtZSkvYmZ9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldEhvdXJzKCl9KSxVQnQ9VkJ0LHFCdD1WQnQucmFuZ2V9KTt2YXIgV0J0LFlCdCxqQnQsWEJ0PU0oKCk9PntmYSgpO3dmKCk7V0J0PVNyKGZ1bmN0aW9uKGUpe2Uuc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXREYXRlKGUuZ2V0RGF0ZSgpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybih0LWUtKHQuZ2V0VGltZXpvbmVPZmZzZXQoKS1lLmdldFRpbWV6b25lT2Zmc2V0KCkpKlBzKS96N30sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0RGF0ZSgpLTF9KSxZQnQ9V0J0LGpCdD1XQnQucmFuZ2V9KTtmdW5jdGlvbiBwMShlKXtyZXR1cm4gU3IoZnVuY3Rpb24odCl7dC5zZXREYXRlKHQuZ2V0RGF0ZSgpLSh0LmdldERheSgpKzctZSklNyksdC5zZXRIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24odCxyKXt0LnNldERhdGUodC5nZXREYXRlKCkrcio3KX0sZnVuY3Rpb24odCxyKXtyZXR1cm4oci10LShyLmdldFRpbWV6b25lT2Zmc2V0KCktdC5nZXRUaW1lem9uZU9mZnNldCgpKSpQcykvRjd9KX12YXIgQjcscnJ0LG5ydCxpcnQsb3J0LGFydCxzcnQsbHJ0LCRCdCxLQnQsWkJ0LEpCdCxRQnQsdEh0LGVIdD1NKCgpPT57ZmEoKTt3ZigpO0I3PXAxKDApLHJydD1wMSgxKSxucnQ9cDEoMiksaXJ0PXAxKDMpLG9ydD1wMSg0KSxhcnQ9cDEoNSksc3J0PXAxKDYpLGxydD1CNy5yYW5nZSwkQnQ9cnJ0LnJhbmdlLEtCdD1ucnQucmFuZ2UsWkJ0PWlydC5yYW5nZSxKQnQ9b3J0LnJhbmdlLFFCdD1hcnQucmFuZ2UsdEh0PXNydC5yYW5nZX0pO3ZhciBySHQsbkh0LGlIdCxvSHQ9TSgoKT0+e2ZhKCk7ckh0PVNyKGZ1bmN0aW9uKGUpe2Uuc2V0RGF0ZSgxKSxlLnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0TW9udGgoZS5nZXRNb250aCgpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybiB0LmdldE1vbnRoKCktZS5nZXRNb250aCgpKyh0LmdldEZ1bGxZZWFyKCktZS5nZXRGdWxsWWVhcigpKSoxMn0sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0TW9udGgoKX0pLG5IdD1ySHQsaUh0PXJIdC5yYW5nZX0pO3ZhciBjcnQsYUh0LHNIdCxsSHQ9TSgoKT0+e2ZhKCk7Y3J0PVNyKGZ1bmN0aW9uKGUpe2Uuc2V0TW9udGgoMCwxKSxlLnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0RnVsbFllYXIoZS5nZXRGdWxsWWVhcigpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybiB0LmdldEZ1bGxZZWFyKCktZS5nZXRGdWxsWWVhcigpfSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRGdWxsWWVhcigpfSk7Y3J0LmV2ZXJ5PWZ1bmN0aW9uKGUpe3JldHVybiFpc0Zpbml0ZShlPU1hdGguZmxvb3IoZSkpfHwhKGU+MCk/bnVsbDpTcihmdW5jdGlvbih0KXt0LnNldEZ1bGxZZWFyKE1hdGguZmxvb3IodC5nZXRGdWxsWWVhcigpL2UpKmUpLHQuc2V0TW9udGgoMCwxKSx0LnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbih0LHIpe3Quc2V0RnVsbFllYXIodC5nZXRGdWxsWWVhcigpK3IqZSl9KX07YUh0PWNydCxzSHQ9Y3J0LnJhbmdlfSk7dmFyIGNIdCx1SHQsaEh0LGZIdD1NKCgpPT57ZmEoKTt3ZigpO2NIdD1TcihmdW5jdGlvbihlKXtlLnNldFVUQ1NlY29uZHMoMCwwKX0sZnVuY3Rpb24oZSx0KXtlLnNldFRpbWUoK2UrdCpQcyl9LGZ1bmN0aW9uKGUsdCl7cmV0dXJuKHQtZSkvUHN9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldFVUQ01pbnV0ZXMoKX0pLHVIdD1jSHQsaEh0PWNIdC5yYW5nZX0pO3ZhciBwSHQsZEh0LG1IdCxnSHQ9TSgoKT0+e2ZhKCk7d2YoKTtwSHQ9U3IoZnVuY3Rpb24oZSl7ZS5zZXRVVENNaW51dGVzKDAsMCwwKX0sZnVuY3Rpb24oZSx0KXtlLnNldFRpbWUoK2UrdCpiZil9LGZ1bmN0aW9uKGUsdCl7cmV0dXJuKHQtZSkvYmZ9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldFVUQ0hvdXJzKCl9KSxkSHQ9cEh0LG1IdD1wSHQucmFuZ2V9KTt2YXIgX0h0LHlIdCx2SHQseEh0PU0oKCk9PntmYSgpO3dmKCk7X0h0PVNyKGZ1bmN0aW9uKGUpe2Uuc2V0VVRDSG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXRVVENEYXRlKGUuZ2V0VVRDRGF0ZSgpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybih0LWUpL3o3fSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRVVENEYXRlKCktMX0pLHlIdD1fSHQsdkh0PV9IdC5yYW5nZX0pO2Z1bmN0aW9uIGQxKGUpe3JldHVybiBTcihmdW5jdGlvbih0KXt0LnNldFVUQ0RhdGUodC5nZXRVVENEYXRlKCktKHQuZ2V0VVRDRGF5KCkrNy1lKSU3KSx0LnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbih0LHIpe3Quc2V0VVRDRGF0ZSh0LmdldFVUQ0RhdGUoKStyKjcpfSxmdW5jdGlvbih0LHIpe3JldHVybihyLXQpL0Y3fSl9dmFyIEg3LHVydCxocnQsZnJ0LHBydCxkcnQsbXJ0LGdydCxiSHQsd0h0LFNIdCxNSHQsRUh0LFRIdCxDSHQ9TSgoKT0+e2ZhKCk7d2YoKTtINz1kMSgwKSx1cnQ9ZDEoMSksaHJ0PWQxKDIpLGZydD1kMSgzKSxwcnQ9ZDEoNCksZHJ0PWQxKDUpLG1ydD1kMSg2KSxncnQ9SDcucmFuZ2UsYkh0PXVydC5yYW5nZSx3SHQ9aHJ0LnJhbmdlLFNIdD1mcnQucmFuZ2UsTUh0PXBydC5yYW5nZSxFSHQ9ZHJ0LnJhbmdlLFRIdD1tcnQucmFuZ2V9KTt2YXIgQUh0LFBIdCxJSHQsTEh0PU0oKCk9PntmYSgpO0FIdD1TcihmdW5jdGlvbihlKXtlLnNldFVUQ0RhdGUoMSksZS5zZXRVVENIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24oZSx0KXtlLnNldFVUQ01vbnRoKGUuZ2V0VVRDTW9udGgoKSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4gdC5nZXRVVENNb250aCgpLWUuZ2V0VVRDTW9udGgoKSsodC5nZXRVVENGdWxsWWVhcigpLWUuZ2V0VVRDRnVsbFllYXIoKSkqMTJ9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldFVUQ01vbnRoKCl9KSxQSHQ9QUh0LElIdD1BSHQucmFuZ2V9KTt2YXIgX3J0LGtIdCxSSHQsTkh0PU0oKCk9PntmYSgpO19ydD1TcihmdW5jdGlvbihlKXtlLnNldFVUQ01vbnRoKDAsMSksZS5zZXRVVENIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24oZSx0KXtlLnNldFVUQ0Z1bGxZZWFyKGUuZ2V0VVRDRnVsbFllYXIoKSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4gdC5nZXRVVENGdWxsWWVhcigpLWUuZ2V0VVRDRnVsbFllYXIoKX0sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0VVRDRnVsbFllYXIoKX0pO19ydC5ldmVyeT1mdW5jdGlvbihlKXtyZXR1cm4haXNGaW5pdGUoZT1NYXRoLmZsb29yKGUpKXx8IShlPjApP251bGw6U3IoZnVuY3Rpb24odCl7dC5zZXRVVENGdWxsWWVhcihNYXRoLmZsb29yKHQuZ2V0VVRDRnVsbFllYXIoKS9lKSplKSx0LnNldFVUQ01vbnRoKDAsMSksdC5zZXRVVENIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24odCxyKXt0LnNldFVUQ0Z1bGxZZWFyKHQuZ2V0VVRDRnVsbFllYXIoKStyKmUpfSl9O2tIdD1fcnQsUkh0PV9ydC5yYW5nZX0pO3ZhciBESHQ9TSgoKT0+e2ZhKCk7TkJ0KCk7T0J0KCk7SEJ0KCk7R0J0KCk7WEJ0KCk7ZUh0KCk7b0h0KCk7bEh0KCk7Zkh0KCk7Z0h0KCk7eEh0KCk7Q0h0KCk7TEh0KCk7Tkh0KCl9KTtmdW5jdGlvbiBwYShlLHQscixuKXtmdW5jdGlvbiBpKG8pe3JldHVybiBlKG89YXJndW1lbnRzLmxlbmd0aD09PTA/bmV3IERhdGU6bmV3IERhdGUoK28pKSxvfXJldHVybiBpLmZsb29yPWZ1bmN0aW9uKG8pe3JldHVybiBlKG89bmV3IERhdGUoK28pKSxvfSxpLmNlaWw9ZnVuY3Rpb24obyl7cmV0dXJuIGUobz1uZXcgRGF0ZShvLTEpKSx0KG8sMSksZShvKSxvfSxpLnJvdW5kPWZ1bmN0aW9uKG8pe3ZhciBhPWkobykscz1pLmNlaWwobyk7cmV0dXJuIG8tYTxzLW8/YTpzfSxpLm9mZnNldD1mdW5jdGlvbihvLGEpe3JldHVybiB0KG89bmV3IERhdGUoK28pLGE9PW51bGw/MTpNYXRoLmZsb29yKGEpKSxvfSxpLnJhbmdlPWZ1bmN0aW9uKG8sYSxzKXt2YXIgbD1bXSxjO2lmKG89aS5jZWlsKG8pLHM9cz09bnVsbD8xOk1hdGguZmxvb3IocyksIShvPGEpfHwhKHM+MCkpcmV0dXJuIGw7ZG8gbC5wdXNoKGM9bmV3IERhdGUoK28pKSx0KG8scyksZShvKTt3aGlsZShjPG8mJm88YSk7cmV0dXJuIGx9LGkuZmlsdGVyPWZ1bmN0aW9uKG8pe3JldHVybiBwYShmdW5jdGlvbihhKXtpZihhPj1hKWZvcig7ZShhKSwhbyhhKTspYS5zZXRUaW1lKGEtMSl9LGZ1bmN0aW9uKGEscyl7aWYoYT49YSlpZihzPDApZm9yKDsrK3M8PTA7KWZvcig7dChhLC0xKSwhbyhhKTspO2Vsc2UgZm9yKDstLXM+PTA7KWZvcig7dChhLDEpLCFvKGEpOyk7fSl9LHImJihpLmNvdW50PWZ1bmN0aW9uKG8sYSl7cmV0dXJuIHlydC5zZXRUaW1lKCtvKSx2cnQuc2V0VGltZSgrYSksZSh5cnQpLGUodnJ0KSxNYXRoLmZsb29yKHIoeXJ0LHZydCkpfSxpLmV2ZXJ5PWZ1bmN0aW9uKG8pe3JldHVybiBvPU1hdGguZmxvb3IobyksIWlzRmluaXRlKG8pfHwhKG8+MCk/bnVsbDpvPjE/aS5maWx0ZXIobj9mdW5jdGlvbihhKXtyZXR1cm4gbihhKSVvPT09MH06ZnVuY3Rpb24oYSl7cmV0dXJuIGkuY291bnQoMCxhKSVvPT09MH0pOml9KSxpfXZhciB5cnQsdnJ0LG0xPU0oKCk9Pnt5cnQ9bmV3IERhdGUsdnJ0PW5ldyBEYXRlfSk7dmFyIFY3LFU3LHE3LEJDPU0oKCk9PntWNz02ZTQsVTc9ODY0ZTUscTc9NjA0OGU1fSk7dmFyIE9IdCxHNyxBTGUsekh0PU0oKCk9PnttMSgpO0JDKCk7T0h0PXBhKGZ1bmN0aW9uKGUpe2Uuc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXREYXRlKGUuZ2V0RGF0ZSgpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybih0LWUtKHQuZ2V0VGltZXpvbmVPZmZzZXQoKS1lLmdldFRpbWV6b25lT2Zmc2V0KCkpKlY3KS9VN30sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0RGF0ZSgpLTF9KSxHNz1PSHQsQUxlPU9IdC5yYW5nZX0pO2Z1bmN0aW9uIGcxKGUpe3JldHVybiBwYShmdW5jdGlvbih0KXt0LnNldERhdGUodC5nZXREYXRlKCktKHQuZ2V0RGF5KCkrNy1lKSU3KSx0LnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbih0LHIpe3Quc2V0RGF0ZSh0LmdldERhdGUoKStyKjcpfSxmdW5jdGlvbih0LHIpe3JldHVybihyLXQtKHIuZ2V0VGltZXpvbmVPZmZzZXQoKS10LmdldFRpbWV6b25lT2Zmc2V0KCkpKlY3KS9xN30pfXZhciBIQyxIdyxGSHQsQkh0LFZ3LEhIdCxWSHQsVUh0LFBMZSxJTGUsTExlLGtMZSxSTGUsTkxlLHFIdD1NKCgpPT57bTEoKTtCQygpO0hDPWcxKDApLEh3PWcxKDEpLEZIdD1nMSgyKSxCSHQ9ZzEoMyksVnc9ZzEoNCksSEh0PWcxKDUpLFZIdD1nMSg2KSxVSHQ9SEMucmFuZ2UsUExlPUh3LnJhbmdlLElMZT1GSHQucmFuZ2UsTExlPUJIdC5yYW5nZSxrTGU9VncucmFuZ2UsUkxlPUhIdC5yYW5nZSxOTGU9Vkh0LnJhbmdlfSk7dmFyIHhydCxfMSxETGUsR0h0PU0oKCk9PnttMSgpO3hydD1wYShmdW5jdGlvbihlKXtlLnNldE1vbnRoKDAsMSksZS5zZXRIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24oZSx0KXtlLnNldEZ1bGxZZWFyKGUuZ2V0RnVsbFllYXIoKSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4gdC5nZXRGdWxsWWVhcigpLWUuZ2V0RnVsbFllYXIoKX0sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0RnVsbFllYXIoKX0pO3hydC5ldmVyeT1mdW5jdGlvbihlKXtyZXR1cm4haXNGaW5pdGUoZT1NYXRoLmZsb29yKGUpKXx8IShlPjApP251bGw6cGEoZnVuY3Rpb24odCl7dC5zZXRGdWxsWWVhcihNYXRoLmZsb29yKHQuZ2V0RnVsbFllYXIoKS9lKSplKSx0LnNldE1vbnRoKDAsMSksdC5zZXRIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24odCxyKXt0LnNldEZ1bGxZZWFyKHQuZ2V0RnVsbFllYXIoKStyKmUpfSl9O18xPXhydCxETGU9eHJ0LnJhbmdlfSk7dmFyIFdIdCxXNyxPTGUsWUh0PU0oKCk9PnttMSgpO0JDKCk7V0h0PXBhKGZ1bmN0aW9uKGUpe2Uuc2V0VVRDSG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXRVVENEYXRlKGUuZ2V0VVRDRGF0ZSgpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybih0LWUpL1U3fSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRVVENEYXRlKCktMX0pLFc3PVdIdCxPTGU9V0h0LnJhbmdlfSk7ZnVuY3Rpb24geTEoZSl7cmV0dXJuIHBhKGZ1bmN0aW9uKHQpe3Quc2V0VVRDRGF0ZSh0LmdldFVUQ0RhdGUoKS0odC5nZXRVVENEYXkoKSs3LWUpJTcpLHQuc2V0VVRDSG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKHQscil7dC5zZXRVVENEYXRlKHQuZ2V0VVRDRGF0ZSgpK3IqNyl9LGZ1bmN0aW9uKHQscil7cmV0dXJuKHItdCkvcTd9KX12YXIgVkMsVXcsakh0LFhIdCxxdywkSHQsS0h0LFpIdCx6TGUsRkxlLEJMZSxITGUsVkxlLFVMZSxKSHQ9TSgoKT0+e20xKCk7QkMoKTtWQz15MSgwKSxVdz15MSgxKSxqSHQ9eTEoMiksWEh0PXkxKDMpLHF3PXkxKDQpLCRIdD15MSg1KSxLSHQ9eTEoNiksWkh0PVZDLnJhbmdlLHpMZT1Vdy5yYW5nZSxGTGU9akh0LnJhbmdlLEJMZT1YSHQucmFuZ2UsSExlPXF3LnJhbmdlLFZMZT0kSHQucmFuZ2UsVUxlPUtIdC5yYW5nZX0pO3ZhciBicnQsdjEscUxlLFFIdD1NKCgpPT57bTEoKTticnQ9cGEoZnVuY3Rpb24oZSl7ZS5zZXRVVENNb250aCgwLDEpLGUuc2V0VVRDSG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXRVVENGdWxsWWVhcihlLmdldFVUQ0Z1bGxZZWFyKCkrdCl9LGZ1bmN0aW9uKGUsdCl7cmV0dXJuIHQuZ2V0VVRDRnVsbFllYXIoKS1lLmdldFVUQ0Z1bGxZZWFyKCl9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldFVUQ0Z1bGxZZWFyKCl9KTticnQuZXZlcnk9ZnVuY3Rpb24oZSl7cmV0dXJuIWlzRmluaXRlKGU9TWF0aC5mbG9vcihlKSl8fCEoZT4wKT9udWxsOnBhKGZ1bmN0aW9uKHQpe3Quc2V0VVRDRnVsbFllYXIoTWF0aC5mbG9vcih0LmdldFVUQ0Z1bGxZZWFyKCkvZSkqZSksdC5zZXRVVENNb250aCgwLDEpLHQuc2V0VVRDSG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKHQscil7dC5zZXRVVENGdWxsWWVhcih0LmdldFVUQ0Z1bGxZZWFyKCkrciplKX0pfTt2MT1icnQscUxlPWJydC5yYW5nZX0pO3ZhciB0VnQ9TSgoKT0+e3pIdCgpO3FIdCgpO0dIdCgpO1lIdCgpO0pIdCgpO1FIdCgpfSk7ZnVuY3Rpb24gR0xlKGUpe2lmKDA8PWUueSYmZS55PDEwMCl7dmFyIHQ9bmV3IERhdGUoLTEsZS5tLGUuZCxlLkgsZS5NLGUuUyxlLkwpO3JldHVybiB0LnNldEZ1bGxZZWFyKGUueSksdH1yZXR1cm4gbmV3IERhdGUoZS55LGUubSxlLmQsZS5ILGUuTSxlLlMsZS5MKX1mdW5jdGlvbiBZNyhlKXtpZigwPD1lLnkmJmUueTwxMDApe3ZhciB0PW5ldyBEYXRlKERhdGUuVVRDKC0xLGUubSxlLmQsZS5ILGUuTSxlLlMsZS5MKSk7cmV0dXJuIHQuc2V0VVRDRnVsbFllYXIoZS55KSx0fXJldHVybiBuZXcgRGF0ZShEYXRlLlVUQyhlLnksZS5tLGUuZCxlLkgsZS5NLGUuUyxlLkwpKX1mdW5jdGlvbiBVQyhlKXtyZXR1cm57eTplLG06MCxkOjEsSDowLE06MCxTOjAsTDowfX1mdW5jdGlvbiBXQyhlKXt2YXIgdD1lLmRhdGVUaW1lLHI9ZS5kYXRlLG49ZS50aW1lLGk9ZS5wZXJpb2RzLG89ZS5kYXlzLGE9ZS5zaG9ydERheXMscz1lLm1vbnRocyxsPWUuc2hvcnRNb250aHMsYz1xQyhpKSx1PUdDKGkpLGg9cUMobyksZj1HQyhvKSxwPXFDKGEpLGQ9R0MoYSksZz1xQyhzKSxfPUdDKHMpLHk9cUMobCkseD1HQyhsKSxiPXthOlcsQTpaLGI6cnQsQjpvdCxjOm51bGwsZDppVnQsZTppVnQsZjpka2UsSDpoa2UsSTpma2Usajpwa2UsTDpjVnQsbTpta2UsTTpna2UscDpzdCxROnNWdCxzOmxWdCxTOl9rZSx1OnlrZSxVOnZrZSxWOnhrZSx3OmJrZSxXOndrZSx4Om51bGwsWDpudWxsLHk6U2tlLFk6TWtlLFo6RWtlLCIlIjphVnR9LFM9e2E6U3QsQTpidCxiOk10LEI6bHQsYzpudWxsLGQ6b1Z0LGU6b1Z0LGY6UGtlLEg6VGtlLEk6Q2tlLGo6QWtlLEw6dVZ0LG06SWtlLE06TGtlLHA6S3QsUTpzVnQsczpsVnQsUzpra2UsdTpSa2UsVTpOa2UsVjpEa2UsdzpPa2UsVzp6a2UseDpudWxsLFg6bnVsbCx5OkZrZSxZOkJrZSxaOkhrZSwiJSI6YVZ0fSxDPXthOkIsQTpJLGI6TCxCOlIsYzpGLGQ6clZ0LGU6clZ0LGY6c2tlLEg6blZ0LEk6blZ0LGo6bmtlLEw6YWtlLG06cmtlLE06aWtlLHA6RCxROmNrZSxzOnVrZSxTOm9rZSx1OiRMZSxVOktMZSxWOlpMZSx3OlhMZSxXOkpMZSx4OnosWDpVLHk6dGtlLFk6UUxlLFo6ZWtlLCIlIjpsa2V9O2IueD1QKHIsYiksYi5YPVAobixiKSxiLmM9UCh0LGIpLFMueD1QKHIsUyksUy5YPVAobixTKSxTLmM9UCh0LFMpO2Z1bmN0aW9uIFAoX3QsY3Qpe3JldHVybiBmdW5jdGlvbihYKXt2YXIgZXQ9W10sZHQ9LTEscT0wLHB0PV90Lmxlbmd0aCxodCx3dCxrdDtmb3IoWCBpbnN0YW5jZW9mIERhdGV8fChYPW5ldyBEYXRlKCtYKSk7KytkdDxwdDspX3QuY2hhckNvZGVBdChkdCk9PT0zNyYmKGV0LnB1c2goX3Quc2xpY2UocSxkdCkpLCh3dD1lVnRbaHQ9X3QuY2hhckF0KCsrZHQpXSkhPW51bGw/aHQ9X3QuY2hhckF0KCsrZHQpOnd0PWh0PT09ImUiPyIgIjoiMCIsKGt0PWN0W2h0XSkmJihodD1rdChYLHd0KSksZXQucHVzaChodCkscT1kdCsxKTtyZXR1cm4gZXQucHVzaChfdC5zbGljZShxLGR0KSksZXQuam9pbigiIil9fWZ1bmN0aW9uIGsoX3QsY3Qpe3JldHVybiBmdW5jdGlvbihYKXt2YXIgZXQ9VUMoMTkwMCksZHQ9TyhldCxfdCxYKz0iIiwwKSxxLHB0O2lmKGR0IT1YLmxlbmd0aClyZXR1cm4gbnVsbDtpZigiUSJpbiBldClyZXR1cm4gbmV3IERhdGUoZXQuUSk7aWYoInAiaW4gZXQmJihldC5IPWV0LkglMTIrZXQucCoxMiksIlYiaW4gZXQpe2lmKGV0LlY8MXx8ZXQuVj41MylyZXR1cm4gbnVsbDsidyJpbiBldHx8KGV0Lnc9MSksIloiaW4gZXQ/KHE9WTcoVUMoZXQueSkpLHB0PXEuZ2V0VVRDRGF5KCkscT1wdD40fHxwdD09PTA/VXcuY2VpbChxKTpVdyhxKSxxPVc3Lm9mZnNldChxLChldC5WLTEpKjcpLGV0Lnk9cS5nZXRVVENGdWxsWWVhcigpLGV0Lm09cS5nZXRVVENNb250aCgpLGV0LmQ9cS5nZXRVVENEYXRlKCkrKGV0LncrNiklNyk6KHE9Y3QoVUMoZXQueSkpLHB0PXEuZ2V0RGF5KCkscT1wdD40fHxwdD09PTA/SHcuY2VpbChxKTpIdyhxKSxxPUc3Lm9mZnNldChxLChldC5WLTEpKjcpLGV0Lnk9cS5nZXRGdWxsWWVhcigpLGV0Lm09cS5nZXRNb250aCgpLGV0LmQ9cS5nZXREYXRlKCkrKGV0LncrNiklNyl9ZWxzZSgiVyJpbiBldHx8IlUiaW4gZXQpJiYoInciaW4gZXR8fChldC53PSJ1ImluIGV0P2V0LnUlNzoiVyJpbiBldD8xOjApLHB0PSJaImluIGV0P1k3KFVDKGV0LnkpKS5nZXRVVENEYXkoKTpjdChVQyhldC55KSkuZ2V0RGF5KCksZXQubT0wLGV0LmQ9IlciaW4gZXQ/KGV0LncrNiklNytldC5XKjctKHB0KzUpJTc6ZXQudytldC5VKjctKHB0KzYpJTcpO3JldHVybiJaImluIGV0PyhldC5IKz1ldC5aLzEwMHwwLGV0Lk0rPWV0LlolMTAwLFk3KGV0KSk6Y3QoZXQpfX1mdW5jdGlvbiBPKF90LGN0LFgsZXQpe2Zvcih2YXIgZHQ9MCxxPWN0Lmxlbmd0aCxwdD1YLmxlbmd0aCxodCx3dDtkdDxxOyl7aWYoZXQ+PXB0KXJldHVybi0xO2lmKGh0PWN0LmNoYXJDb2RlQXQoZHQrKyksaHQ9PT0zNyl7aWYoaHQ9Y3QuY2hhckF0KGR0KyspLHd0PUNbaHQgaW4gZVZ0P2N0LmNoYXJBdChkdCsrKTpodF0sIXd0fHwoZXQ9d3QoX3QsWCxldCkpPDApcmV0dXJuLTF9ZWxzZSBpZihodCE9WC5jaGFyQ29kZUF0KGV0KyspKXJldHVybi0xfXJldHVybiBldH1mdW5jdGlvbiBEKF90LGN0LFgpe3ZhciBldD1jLmV4ZWMoY3Quc2xpY2UoWCkpO3JldHVybiBldD8oX3QucD11W2V0WzBdLnRvTG93ZXJDYXNlKCldLFgrZXRbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBCKF90LGN0LFgpe3ZhciBldD1wLmV4ZWMoY3Quc2xpY2UoWCkpO3JldHVybiBldD8oX3Qudz1kW2V0WzBdLnRvTG93ZXJDYXNlKCldLFgrZXRbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBJKF90LGN0LFgpe3ZhciBldD1oLmV4ZWMoY3Quc2xpY2UoWCkpO3JldHVybiBldD8oX3Qudz1mW2V0WzBdLnRvTG93ZXJDYXNlKCldLFgrZXRbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBMKF90LGN0LFgpe3ZhciBldD15LmV4ZWMoY3Quc2xpY2UoWCkpO3JldHVybiBldD8oX3QubT14W2V0WzBdLnRvTG93ZXJDYXNlKCldLFgrZXRbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBSKF90LGN0LFgpe3ZhciBldD1nLmV4ZWMoY3Quc2xpY2UoWCkpO3JldHVybiBldD8oX3QubT1fW2V0WzBdLnRvTG93ZXJDYXNlKCldLFgrZXRbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBGKF90LGN0LFgpe3JldHVybiBPKF90LHQsY3QsWCl9ZnVuY3Rpb24geihfdCxjdCxYKXtyZXR1cm4gTyhfdCxyLGN0LFgpfWZ1bmN0aW9uIFUoX3QsY3QsWCl7cmV0dXJuIE8oX3QsbixjdCxYKX1mdW5jdGlvbiBXKF90KXtyZXR1cm4gYVtfdC5nZXREYXkoKV19ZnVuY3Rpb24gWihfdCl7cmV0dXJuIG9bX3QuZ2V0RGF5KCldfWZ1bmN0aW9uIHJ0KF90KXtyZXR1cm4gbFtfdC5nZXRNb250aCgpXX1mdW5jdGlvbiBvdChfdCl7cmV0dXJuIHNbX3QuZ2V0TW9udGgoKV19ZnVuY3Rpb24gc3QoX3Qpe3JldHVybiBpWysoX3QuZ2V0SG91cnMoKT49MTIpXX1mdW5jdGlvbiBTdChfdCl7cmV0dXJuIGFbX3QuZ2V0VVRDRGF5KCldfWZ1bmN0aW9uIGJ0KF90KXtyZXR1cm4gb1tfdC5nZXRVVENEYXkoKV19ZnVuY3Rpb24gTXQoX3Qpe3JldHVybiBsW190LmdldFVUQ01vbnRoKCldfWZ1bmN0aW9uIGx0KF90KXtyZXR1cm4gc1tfdC5nZXRVVENNb250aCgpXX1mdW5jdGlvbiBLdChfdCl7cmV0dXJuIGlbKyhfdC5nZXRVVENIb3VycygpPj0xMildfXJldHVybntmb3JtYXQ6ZnVuY3Rpb24oX3Qpe3ZhciBjdD1QKF90Kz0iIixiKTtyZXR1cm4gY3QudG9TdHJpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gX3R9LGN0fSxwYXJzZTpmdW5jdGlvbihfdCl7dmFyIGN0PWsoX3QrPSIiLEdMZSk7cmV0dXJuIGN0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIF90fSxjdH0sdXRjRm9ybWF0OmZ1bmN0aW9uKF90KXt2YXIgY3Q9UChfdCs9IiIsUyk7cmV0dXJuIGN0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIF90fSxjdH0sdXRjUGFyc2U6ZnVuY3Rpb24oX3Qpe3ZhciBjdD1rKF90LFk3KTtyZXR1cm4gY3QudG9TdHJpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gX3R9LGN0fX19ZnVuY3Rpb24gZ24oZSx0LHIpe3ZhciBuPWU8MD8iLSI6IiIsaT0obj8tZTplKSsiIixvPWkubGVuZ3RoO3JldHVybiBuKyhvPHI/bmV3IEFycmF5KHItbysxKS5qb2luKHQpK2k6aSl9ZnVuY3Rpb24gakxlKGUpe3JldHVybiBlLnJlcGxhY2UoWUxlLCJcXCQmIil9ZnVuY3Rpb24gcUMoZSl7cmV0dXJuIG5ldyBSZWdFeHAoIl4oPzoiK2UubWFwKGpMZSkuam9pbigifCIpKyIpIiwiaSIpfWZ1bmN0aW9uIEdDKGUpe2Zvcih2YXIgdD17fSxyPS0xLG49ZS5sZW5ndGg7KytyPG47KXRbZVtyXS50b0xvd2VyQ2FzZSgpXT1yO3JldHVybiB0fWZ1bmN0aW9uIFhMZShlLHQscil7dmFyIG49Vm8uZXhlYyh0LnNsaWNlKHIscisxKSk7cmV0dXJuIG4/KGUudz0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiAkTGUoZSx0LHIpe3ZhciBuPVZvLmV4ZWModC5zbGljZShyLHIrMSkpO3JldHVybiBuPyhlLnU9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gS0xlKGUsdCxyKXt2YXIgbj1Wby5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS5VPStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIFpMZShlLHQscil7dmFyIG49Vm8uZXhlYyh0LnNsaWNlKHIscisyKSk7cmV0dXJuIG4/KGUuVj0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBKTGUoZSx0LHIpe3ZhciBuPVZvLmV4ZWModC5zbGljZShyLHIrMikpO3JldHVybiBuPyhlLlc9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gUUxlKGUsdCxyKXt2YXIgbj1Wby5leGVjKHQuc2xpY2UocixyKzQpKTtyZXR1cm4gbj8oZS55PStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIHRrZShlLHQscil7dmFyIG49Vm8uZXhlYyh0LnNsaWNlKHIscisyKSk7cmV0dXJuIG4/KGUueT0rblswXSsoK25bMF0+Njg/MTkwMDoyZTMpLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIGVrZShlLHQscil7dmFyIG49L14oWil8KFsrLV1cZFxkKSg/Ojo/KFxkXGQpKT8vLmV4ZWModC5zbGljZShyLHIrNikpO3JldHVybiBuPyhlLlo9blsxXT8wOi0oblsyXSsoblszXXx8IjAwIikpLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIHJrZShlLHQscil7dmFyIG49Vm8uZXhlYyh0LnNsaWNlKHIscisyKSk7cmV0dXJuIG4/KGUubT1uWzBdLTEscituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gclZ0KGUsdCxyKXt2YXIgbj1Wby5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS5kPStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIG5rZShlLHQscil7dmFyIG49Vm8uZXhlYyh0LnNsaWNlKHIsciszKSk7cmV0dXJuIG4/KGUubT0wLGUuZD0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBuVnQoZSx0LHIpe3ZhciBuPVZvLmV4ZWModC5zbGljZShyLHIrMikpO3JldHVybiBuPyhlLkg9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gaWtlKGUsdCxyKXt2YXIgbj1Wby5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS5NPStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIG9rZShlLHQscil7dmFyIG49Vm8uZXhlYyh0LnNsaWNlKHIscisyKSk7cmV0dXJuIG4/KGUuUz0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBha2UoZSx0LHIpe3ZhciBuPVZvLmV4ZWModC5zbGljZShyLHIrMykpO3JldHVybiBuPyhlLkw9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gc2tlKGUsdCxyKXt2YXIgbj1Wby5leGVjKHQuc2xpY2UocixyKzYpKTtyZXR1cm4gbj8oZS5MPU1hdGguZmxvb3IoblswXS8xZTMpLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIGxrZShlLHQscil7dmFyIG49V0xlLmV4ZWModC5zbGljZShyLHIrMSkpO3JldHVybiBuP3IrblswXS5sZW5ndGg6LTF9ZnVuY3Rpb24gY2tlKGUsdCxyKXt2YXIgbj1Wby5leGVjKHQuc2xpY2UocikpO3JldHVybiBuPyhlLlE9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gdWtlKGUsdCxyKXt2YXIgbj1Wby5leGVjKHQuc2xpY2UocikpO3JldHVybiBuPyhlLlE9K25bMF0qMWUzLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIGlWdChlLHQpe3JldHVybiBnbihlLmdldERhdGUoKSx0LDIpfWZ1bmN0aW9uIGhrZShlLHQpe3JldHVybiBnbihlLmdldEhvdXJzKCksdCwyKX1mdW5jdGlvbiBma2UoZSx0KXtyZXR1cm4gZ24oZS5nZXRIb3VycygpJTEyfHwxMix0LDIpfWZ1bmN0aW9uIHBrZShlLHQpe3JldHVybiBnbigxK0c3LmNvdW50KF8xKGUpLGUpLHQsMyl9ZnVuY3Rpb24gY1Z0KGUsdCl7cmV0dXJuIGduKGUuZ2V0TWlsbGlzZWNvbmRzKCksdCwzKX1mdW5jdGlvbiBka2UoZSx0KXtyZXR1cm4gY1Z0KGUsdCkrIjAwMCJ9ZnVuY3Rpb24gbWtlKGUsdCl7cmV0dXJuIGduKGUuZ2V0TW9udGgoKSsxLHQsMil9ZnVuY3Rpb24gZ2tlKGUsdCl7cmV0dXJuIGduKGUuZ2V0TWludXRlcygpLHQsMil9ZnVuY3Rpb24gX2tlKGUsdCl7cmV0dXJuIGduKGUuZ2V0U2Vjb25kcygpLHQsMil9ZnVuY3Rpb24geWtlKGUpe3ZhciB0PWUuZ2V0RGF5KCk7cmV0dXJuIHQ9PT0wPzc6dH1mdW5jdGlvbiB2a2UoZSx0KXtyZXR1cm4gZ24oSEMuY291bnQoXzEoZSksZSksdCwyKX1mdW5jdGlvbiB4a2UoZSx0KXt2YXIgcj1lLmdldERheSgpO3JldHVybiBlPXI+PTR8fHI9PT0wP1Z3KGUpOlZ3LmNlaWwoZSksZ24oVncuY291bnQoXzEoZSksZSkrKF8xKGUpLmdldERheSgpPT09NCksdCwyKX1mdW5jdGlvbiBia2UoZSl7cmV0dXJuIGUuZ2V0RGF5KCl9ZnVuY3Rpb24gd2tlKGUsdCl7cmV0dXJuIGduKEh3LmNvdW50KF8xKGUpLGUpLHQsMil9ZnVuY3Rpb24gU2tlKGUsdCl7cmV0dXJuIGduKGUuZ2V0RnVsbFllYXIoKSUxMDAsdCwyKX1mdW5jdGlvbiBNa2UoZSx0KXtyZXR1cm4gZ24oZS5nZXRGdWxsWWVhcigpJTFlNCx0LDQpfWZ1bmN0aW9uIEVrZShlKXt2YXIgdD1lLmdldFRpbWV6b25lT2Zmc2V0KCk7cmV0dXJuKHQ+MD8iLSI6KHQqPS0xLCIrIikpK2duKHQvNjB8MCwiMCIsMikrZ24odCU2MCwiMCIsMil9ZnVuY3Rpb24gb1Z0KGUsdCl7cmV0dXJuIGduKGUuZ2V0VVRDRGF0ZSgpLHQsMil9ZnVuY3Rpb24gVGtlKGUsdCl7cmV0dXJuIGduKGUuZ2V0VVRDSG91cnMoKSx0LDIpfWZ1bmN0aW9uIENrZShlLHQpe3JldHVybiBnbihlLmdldFVUQ0hvdXJzKCklMTJ8fDEyLHQsMil9ZnVuY3Rpb24gQWtlKGUsdCl7cmV0dXJuIGduKDErVzcuY291bnQodjEoZSksZSksdCwzKX1mdW5jdGlvbiB1VnQoZSx0KXtyZXR1cm4gZ24oZS5nZXRVVENNaWxsaXNlY29uZHMoKSx0LDMpfWZ1bmN0aW9uIFBrZShlLHQpe3JldHVybiB1VnQoZSx0KSsiMDAwIn1mdW5jdGlvbiBJa2UoZSx0KXtyZXR1cm4gZ24oZS5nZXRVVENNb250aCgpKzEsdCwyKX1mdW5jdGlvbiBMa2UoZSx0KXtyZXR1cm4gZ24oZS5nZXRVVENNaW51dGVzKCksdCwyKX1mdW5jdGlvbiBra2UoZSx0KXtyZXR1cm4gZ24oZS5nZXRVVENTZWNvbmRzKCksdCwyKX1mdW5jdGlvbiBSa2UoZSl7dmFyIHQ9ZS5nZXRVVENEYXkoKTtyZXR1cm4gdD09PTA/Nzp0fWZ1bmN0aW9uIE5rZShlLHQpe3JldHVybiBnbihWQy5jb3VudCh2MShlKSxlKSx0LDIpfWZ1bmN0aW9uIERrZShlLHQpe3ZhciByPWUuZ2V0VVRDRGF5KCk7cmV0dXJuIGU9cj49NHx8cj09PTA/cXcoZSk6cXcuY2VpbChlKSxnbihxdy5jb3VudCh2MShlKSxlKSsodjEoZSkuZ2V0VVRDRGF5KCk9PT00KSx0LDIpfWZ1bmN0aW9uIE9rZShlKXtyZXR1cm4gZS5nZXRVVENEYXkoKX1mdW5jdGlvbiB6a2UoZSx0KXtyZXR1cm4gZ24oVXcuY291bnQodjEoZSksZSksdCwyKX1mdW5jdGlvbiBGa2UoZSx0KXtyZXR1cm4gZ24oZS5nZXRVVENGdWxsWWVhcigpJTEwMCx0LDIpfWZ1bmN0aW9uIEJrZShlLHQpe3JldHVybiBnbihlLmdldFVUQ0Z1bGxZZWFyKCklMWU0LHQsNCl9ZnVuY3Rpb24gSGtlKCl7cmV0dXJuIiswMDAwIn1mdW5jdGlvbiBhVnQoKXtyZXR1cm4iJSJ9ZnVuY3Rpb24gc1Z0KGUpe3JldHVybitlfWZ1bmN0aW9uIGxWdChlKXtyZXR1cm4gTWF0aC5mbG9vcigrZS8xZTMpfXZhciBlVnQsVm8sV0xlLFlMZSx3cnQ9TSgoKT0+e3RWdCgpO2VWdD17Ii0iOiIiLF86IiAiLDA6IjAifSxWbz0vXlxzKlxkKy8sV0xlPS9eJS8sWUxlPS9bXFxeJCorP3xbXF0oKS57fV0vZ30pO2Z1bmN0aW9uIGo3KGUpe3JldHVybiBHdz1XQyhlKSxTcnQ9R3cuZm9ybWF0LE1ydD1Hdy5wYXJzZSxZQz1Hdy51dGNGb3JtYXQsakM9R3cudXRjUGFyc2UsR3d9dmFyIEd3LFNydCxNcnQsWUMsakMsWDc9TSgoKT0+e3dydCgpO2o3KHtkYXRlVGltZToiJXgsICVYIixkYXRlOiIlLW0vJS1kLyVZIix0aW1lOiIlLUk6JU06JVMgJXAiLHBlcmlvZHM6WyJBTSIsIlBNIl0sZGF5czpbIlN1bmRheSIsIk1vbmRheSIsIlR1ZXNkYXkiLCJXZWRuZXNkYXkiLCJUaHVyc2RheSIsIkZyaWRheSIsIlNhdHVyZGF5Il0sc2hvcnREYXlzOlsiU3VuIiwiTW9uIiwiVHVlIiwiV2VkIiwiVGh1IiwiRnJpIiwiU2F0Il0sbW9udGhzOlsiSmFudWFyeSIsIkZlYnJ1YXJ5IiwiTWFyY2giLCJBcHJpbCIsIk1heSIsIkp1bmUiLCJKdWx5IiwiQXVndXN0IiwiU2VwdGVtYmVyIiwiT2N0b2JlciIsIk5vdmVtYmVyIiwiRGVjZW1iZXIiXSxzaG9ydE1vbnRoczpbIkphbiIsIkZlYiIsIk1hciIsIkFwciIsIk1heSIsIkp1biIsIkp1bCIsIkF1ZyIsIlNlcCIsIk9jdCIsIk5vdiIsIkRlYyJdfSl9KTtmdW5jdGlvbiBWa2UoZSl7cmV0dXJuIGUudG9JU09TdHJpbmcoKX12YXIgRXJ0LFVrZSxoVnQsVHJ0PU0oKCk9PntYNygpO0VydD0iJVktJW0tJWRUJUg6JU06JVMuJUxaIjtVa2U9RGF0ZS5wcm90b3R5cGUudG9JU09TdHJpbmc/VmtlOllDKEVydCksaFZ0PVVrZX0pO2Z1bmN0aW9uIHFrZShlKXt2YXIgdD1uZXcgRGF0ZShlKTtyZXR1cm4gaXNOYU4odCk/bnVsbDp0fXZhciBHa2UsZlZ0LHBWdD1NKCgpPT57VHJ0KCk7WDcoKTtHa2U9K25ldyBEYXRlKCIyMDAwLTAxLTAxVDAwOjAwOjAwLjAwMFoiKT9xa2U6akMoRXJ0KSxmVnQ9R2tlfSk7dmFyIGRWdD1NKCgpPT57WDcoKTt3cnQoKTtUcnQoKTtwVnQoKX0pO2Z1bmN0aW9uIFl3KCl7cmV0dXJuIHgxfHwoX1Z0KFdrZSkseDE9WkMubm93KCkrWjcpfWZ1bmN0aW9uIFdrZSgpe3gxPTB9ZnVuY3Rpb24gYjEoKXt0aGlzLl9jYWxsPXRoaXMuX3RpbWU9dGhpcy5fbmV4dD1udWxsfWZ1bmN0aW9uIEFydChlLHQscil7dmFyIG49bmV3IGIxO3JldHVybiBuLnJlc3RhcnQoZSx0LHIpLG59ZnVuY3Rpb24gUHJ0KCl7WXcoKSwrK1d3O2Zvcih2YXIgZT0kNyx0O2U7KSh0PXgxLWUuX3RpbWUpPj0wJiZlLl9jYWxsLmNhbGwobnVsbCx0KSxlPWUuX25leHQ7LS1Xd31mdW5jdGlvbiBtVnQoKXt4MT0oSzc9WkMubm93KCkpK1o3LFd3PSRDPTA7dHJ5e1BydCgpfWZpbmFsbHl7V3c9MCxqa2UoKSx4MT0wfX1mdW5jdGlvbiBZa2UoKXt2YXIgZT1aQy5ub3coKSx0PWUtSzc7dD5nVnQmJihaNy09dCxLNz1lKX1mdW5jdGlvbiBqa2UoKXtmb3IodmFyIGUsdD0kNyxyLG49MS8wO3Q7KXQuX2NhbGw/KG4+dC5fdGltZSYmKG49dC5fdGltZSksZT10LHQ9dC5fbmV4dCk6KHI9dC5fbmV4dCx0Ll9uZXh0PW51bGwsdD1lP2UuX25leHQ9cjokNz1yKTtLQz1lLENydChuKX1mdW5jdGlvbiBDcnQoZSl7aWYoIVd3KXskQyYmKCRDPWNsZWFyVGltZW91dCgkQykpO3ZhciB0PWUteDE7dD4yND8oZTwxLzAmJigkQz1zZXRUaW1lb3V0KG1WdCxlLVpDLm5vdygpLVo3KSksWEMmJihYQz1jbGVhckludGVydmFsKFhDKSkpOihYQ3x8KEs3PVpDLm5vdygpLFhDPXNldEludGVydmFsKFlrZSxnVnQpKSxXdz0xLF9WdChtVnQpKX19dmFyIFd3LCRDLFhDLGdWdCwkNyxLQyxLNyx4MSxaNyxaQyxfVnQsSjc9TSgoKT0+e1d3PTAsJEM9MCxYQz0wLGdWdD0xZTMsSzc9MCx4MT0wLFo3PTAsWkM9dHlwZW9mIHBlcmZvcm1hbmNlPT0ib2JqZWN0IiYmcGVyZm9ybWFuY2Uubm93P3BlcmZvcm1hbmNlOkRhdGUsX1Z0PXR5cGVvZiB3aW5kb3c9PSJvYmplY3QiJiZ3aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lP3dpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUuYmluZCh3aW5kb3cpOmZ1bmN0aW9uKGUpe3NldFRpbWVvdXQoZSwxNyl9O2IxLnByb3RvdHlwZT1BcnQucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpiMSxyZXN0YXJ0OmZ1bmN0aW9uKGUsdCxyKXtpZih0eXBlb2YgZSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgVHlwZUVycm9yKCJjYWxsYmFjayBpcyBub3QgYSBmdW5jdGlvbiIpO3I9KHI9PW51bGw/WXcoKTorcikrKHQ9PW51bGw/MDordCksIXRoaXMuX25leHQmJktDIT09dGhpcyYmKEtDP0tDLl9uZXh0PXRoaXM6JDc9dGhpcyxLQz10aGlzKSx0aGlzLl9jYWxsPWUsdGhpcy5fdGltZT1yLENydCgpfSxzdG9wOmZ1bmN0aW9uKCl7dGhpcy5fY2FsbCYmKHRoaXMuX2NhbGw9bnVsbCx0aGlzLl90aW1lPTEvMCxDcnQoKSl9fX0pO2Z1bmN0aW9uIHlWdChlLHQscil7dmFyIG49bmV3IGIxO3JldHVybiB0PXQ9PW51bGw/MDordCxuLnJlc3RhcnQoZnVuY3Rpb24oaSl7bi5zdG9wKCksZShpK3QpfSx0LHIpLG59dmFyIHZWdD1NKCgpPT57SjcoKX0pO2Z1bmN0aW9uIHhWdChlLHQscil7dmFyIG49bmV3IGIxLGk9dDtyZXR1cm4gdD09bnVsbD8obi5yZXN0YXJ0KGUsdCxyKSxuKToodD0rdCxyPXI9PW51bGw/WXcoKTorcixuLnJlc3RhcnQoZnVuY3Rpb24gbyhhKXthKz1pLG4ucmVzdGFydChvLGkrPXQsciksZShhKX0sdCxyKSxuKX12YXIgYlZ0PU0oKCk9PntKNygpfSk7dmFyIHdWdD1NKCgpPT57SjcoKTt2VnQoKTtiVnQoKX0pO3ZhciBRNyxJcnQsTHJ0PU0oKCk9PntRNz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIsSXJ0PXtzdmc6Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIix4aHRtbDpRNyx4bGluazoiaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIseG1sOiJodHRwOi8vd3d3LnczLm9yZy9YTUwvMTk5OC9uYW1lc3BhY2UiLHhtbG5zOiJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3htbG5zLyJ9fSk7ZnVuY3Rpb24gbGQoZSl7dmFyIHQ9ZSs9IiIscj10LmluZGV4T2YoIjoiKTtyZXR1cm4gcj49MCYmKHQ9ZS5zbGljZSgwLHIpKSE9PSJ4bWxucyImJihlPWUuc2xpY2UocisxKSksSXJ0Lmhhc093blByb3BlcnR5KHQpP3tzcGFjZTpJcnRbdF0sbG9jYWw6ZX06ZX12YXIgdHo9TSgoKT0+e0xydCgpfSk7ZnVuY3Rpb24gWGtlKGUpe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PXRoaXMub3duZXJEb2N1bWVudCxyPXRoaXMubmFtZXNwYWNlVVJJO3JldHVybiByPT09UTcmJnQuZG9jdW1lbnRFbGVtZW50Lm5hbWVzcGFjZVVSST09PVE3P3QuY3JlYXRlRWxlbWVudChlKTp0LmNyZWF0ZUVsZW1lbnROUyhyLGUpfX1mdW5jdGlvbiAka2UoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMub3duZXJEb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoZS5zcGFjZSxlLmxvY2FsKX19ZnVuY3Rpb24gZXooZSl7dmFyIHQ9bGQoZSk7cmV0dXJuKHQubG9jYWw/JGtlOlhrZSkodCl9dmFyIGtydD1NKCgpPT57dHooKTtMcnQoKX0pO2Z1bmN0aW9uIEtrZSgpe31mdW5jdGlvbiB3MShlKXtyZXR1cm4gZT09bnVsbD9La2U6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5xdWVyeVNlbGVjdG9yKGUpfX12YXIgcno9TSgoKT0+e30pO2Z1bmN0aW9uIFNWdChlKXt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9dzEoZSkpO2Zvcih2YXIgdD10aGlzLl9ncm91cHMscj10Lmxlbmd0aCxuPW5ldyBBcnJheShyKSxpPTA7aTxyOysraSlmb3IodmFyIG89dFtpXSxhPW8ubGVuZ3RoLHM9bltpXT1uZXcgQXJyYXkoYSksbCxjLHU9MDt1PGE7Kyt1KShsPW9bdV0pJiYoYz1lLmNhbGwobCxsLl9fZGF0YV9fLHUsbykpJiYoIl9fZGF0YV9fImluIGwmJihjLl9fZGF0YV9fPWwuX19kYXRhX18pLHNbdV09Yyk7cmV0dXJuIG5ldyBubyhuLHRoaXMuX3BhcmVudHMpfXZhciBNVnQ9TSgoKT0+e1NmKCk7cnooKX0pO2Z1bmN0aW9uIFprZSgpe3JldHVybltdfWZ1bmN0aW9uIEpDKGUpe3JldHVybiBlPT1udWxsP1prZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLnF1ZXJ5U2VsZWN0b3JBbGwoZSl9fXZhciBScnQ9TSgoKT0+e30pO2Z1bmN0aW9uIEVWdChlKXt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9SkMoZSkpO2Zvcih2YXIgdD10aGlzLl9ncm91cHMscj10Lmxlbmd0aCxuPVtdLGk9W10sbz0wO288cjsrK28pZm9yKHZhciBhPXRbb10scz1hLmxlbmd0aCxsLGM9MDtjPHM7KytjKShsPWFbY10pJiYobi5wdXNoKGUuY2FsbChsLGwuX19kYXRhX18sYyxhKSksaS5wdXNoKGwpKTtyZXR1cm4gbmV3IG5vKG4saSl9dmFyIFRWdD1NKCgpPT57U2YoKTtScnQoKX0pO2Z1bmN0aW9uIFFDKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiB0aGlzLm1hdGNoZXMoZSl9fXZhciBOcnQ9TSgoKT0+e30pO2Z1bmN0aW9uIENWdChlKXt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9UUMoZSkpO2Zvcih2YXIgdD10aGlzLl9ncm91cHMscj10Lmxlbmd0aCxuPW5ldyBBcnJheShyKSxpPTA7aTxyOysraSlmb3IodmFyIG89dFtpXSxhPW8ubGVuZ3RoLHM9bltpXT1bXSxsLGM9MDtjPGE7KytjKShsPW9bY10pJiZlLmNhbGwobCxsLl9fZGF0YV9fLGMsbykmJnMucHVzaChsKTtyZXR1cm4gbmV3IG5vKG4sdGhpcy5fcGFyZW50cyl9dmFyIEFWdD1NKCgpPT57U2YoKTtOcnQoKX0pO2Z1bmN0aW9uIG56KGUpe3JldHVybiBuZXcgQXJyYXkoZS5sZW5ndGgpfXZhciBEcnQ9TSgoKT0+e30pO2Z1bmN0aW9uIFBWdCgpe3JldHVybiBuZXcgbm8odGhpcy5fZW50ZXJ8fHRoaXMuX2dyb3Vwcy5tYXAobnopLHRoaXMuX3BhcmVudHMpfWZ1bmN0aW9uIHRBKGUsdCl7dGhpcy5vd25lckRvY3VtZW50PWUub3duZXJEb2N1bWVudCx0aGlzLm5hbWVzcGFjZVVSST1lLm5hbWVzcGFjZVVSSSx0aGlzLl9uZXh0PW51bGwsdGhpcy5fcGFyZW50PWUsdGhpcy5fX2RhdGFfXz10fXZhciBPcnQ9TSgoKT0+e0RydCgpO1NmKCk7dEEucHJvdG90eXBlPXtjb25zdHJ1Y3Rvcjp0QSxhcHBlbmRDaGlsZDpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fcGFyZW50Lmluc2VydEJlZm9yZShlLHRoaXMuX25leHQpfSxpbnNlcnRCZWZvcmU6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gdGhpcy5fcGFyZW50Lmluc2VydEJlZm9yZShlLHQpfSxxdWVyeVNlbGVjdG9yOmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLl9wYXJlbnQucXVlcnlTZWxlY3RvcihlKX0scXVlcnlTZWxlY3RvckFsbDpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fcGFyZW50LnF1ZXJ5U2VsZWN0b3JBbGwoZSl9fX0pO2Z1bmN0aW9uIElWdChlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIExWdD1NKCgpPT57fSk7ZnVuY3Rpb24gSmtlKGUsdCxyLG4saSxvKXtmb3IodmFyIGE9MCxzLGw9dC5sZW5ndGgsYz1vLmxlbmd0aDthPGM7KythKShzPXRbYV0pPyhzLl9fZGF0YV9fPW9bYV0sblthXT1zKTpyW2FdPW5ldyB0QShlLG9bYV0pO2Zvcig7YTxsOysrYSkocz10W2FdKSYmKGlbYV09cyl9ZnVuY3Rpb24gUWtlKGUsdCxyLG4saSxvLGEpe3ZhciBzLGwsYz17fSx1PXQubGVuZ3RoLGg9by5sZW5ndGgsZj1uZXcgQXJyYXkodSkscDtmb3Iocz0wO3M8dTsrK3MpKGw9dFtzXSkmJihmW3NdPXA9a1Z0K2EuY2FsbChsLGwuX19kYXRhX18scyx0KSxwIGluIGM/aVtzXT1sOmNbcF09bCk7Zm9yKHM9MDtzPGg7KytzKXA9a1Z0K2EuY2FsbChlLG9bc10scyxvKSwobD1jW3BdKT8obltzXT1sLGwuX19kYXRhX189b1tzXSxjW3BdPW51bGwpOnJbc109bmV3IHRBKGUsb1tzXSk7Zm9yKHM9MDtzPHU7KytzKShsPXRbc10pJiZjW2Zbc11dPT09bCYmKGlbc109bCl9ZnVuY3Rpb24gUlZ0KGUsdCl7aWYoIWUpcmV0dXJuIHA9bmV3IEFycmF5KHRoaXMuc2l6ZSgpKSxjPS0xLHRoaXMuZWFjaChmdW5jdGlvbihQKXtwWysrY109UH0pLHA7dmFyIHI9dD9Ra2U6SmtlLG49dGhpcy5fcGFyZW50cyxpPXRoaXMuX2dyb3Vwczt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9SVZ0KGUpKTtmb3IodmFyIG89aS5sZW5ndGgsYT1uZXcgQXJyYXkobykscz1uZXcgQXJyYXkobyksbD1uZXcgQXJyYXkobyksYz0wO2M8bzsrK2Mpe3ZhciB1PW5bY10saD1pW2NdLGY9aC5sZW5ndGgscD1lLmNhbGwodSx1JiZ1Ll9fZGF0YV9fLGMsbiksZD1wLmxlbmd0aCxnPXNbY109bmV3IEFycmF5KGQpLF89YVtjXT1uZXcgQXJyYXkoZCkseT1sW2NdPW5ldyBBcnJheShmKTtyKHUsaCxnLF8seSxwLHQpO2Zvcih2YXIgeD0wLGI9MCxTLEM7eDxkOysreClpZihTPWdbeF0pe2Zvcih4Pj1iJiYoYj14KzEpOyEoQz1fW2JdKSYmKytiPGQ7KTtTLl9uZXh0PUN8fG51bGx9fXJldHVybiBhPW5ldyBubyhhLG4pLGEuX2VudGVyPXMsYS5fZXhpdD1sLGF9dmFyIGtWdCxOVnQ9TSgoKT0+e1NmKCk7T3J0KCk7TFZ0KCk7a1Z0PSIkIn0pO2Z1bmN0aW9uIERWdCgpe3JldHVybiBuZXcgbm8odGhpcy5fZXhpdHx8dGhpcy5fZ3JvdXBzLm1hcChueiksdGhpcy5fcGFyZW50cyl9dmFyIE9WdD1NKCgpPT57RHJ0KCk7U2YoKX0pO2Z1bmN0aW9uIHpWdChlLHQscil7dmFyIG49dGhpcy5lbnRlcigpLGk9dGhpcyxvPXRoaXMuZXhpdCgpO3JldHVybiBuPXR5cGVvZiBlPT0iZnVuY3Rpb24iP2Uobik6bi5hcHBlbmQoZSsiIiksdCE9bnVsbCYmKGk9dChpKSkscj09bnVsbD9vLnJlbW92ZSgpOnIobyksbiYmaT9uLm1lcmdlKGkpLm9yZGVyKCk6aX12YXIgRlZ0PU0oKCk9Pnt9KTtmdW5jdGlvbiBCVnQoZSl7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPWUuX2dyb3VwcyxuPXQubGVuZ3RoLGk9ci5sZW5ndGgsbz1NYXRoLm1pbihuLGkpLGE9bmV3IEFycmF5KG4pLHM9MDtzPG87KytzKWZvcih2YXIgbD10W3NdLGM9cltzXSx1PWwubGVuZ3RoLGg9YVtzXT1uZXcgQXJyYXkodSksZixwPTA7cDx1OysrcCkoZj1sW3BdfHxjW3BdKSYmKGhbcF09Zik7Zm9yKDtzPG47KytzKWFbc109dFtzXTtyZXR1cm4gbmV3IG5vKGEsdGhpcy5fcGFyZW50cyl9dmFyIEhWdD1NKCgpPT57U2YoKX0pO2Z1bmN0aW9uIFZWdCgpe2Zvcih2YXIgZT10aGlzLl9ncm91cHMsdD0tMSxyPWUubGVuZ3RoOysrdDxyOylmb3IodmFyIG49ZVt0XSxpPW4ubGVuZ3RoLTEsbz1uW2ldLGE7LS1pPj0wOykoYT1uW2ldKSYmKG8mJmEuY29tcGFyZURvY3VtZW50UG9zaXRpb24obyleNCYmby5wYXJlbnROb2RlLmluc2VydEJlZm9yZShhLG8pLG89YSk7cmV0dXJuIHRoaXN9dmFyIFVWdD1NKCgpPT57fSk7ZnVuY3Rpb24gcVZ0KGUpe2V8fChlPXQ4ZSk7ZnVuY3Rpb24gdChoLGYpe3JldHVybiBoJiZmP2UoaC5fX2RhdGFfXyxmLl9fZGF0YV9fKTohaC0hZn1mb3IodmFyIHI9dGhpcy5fZ3JvdXBzLG49ci5sZW5ndGgsaT1uZXcgQXJyYXkobiksbz0wO288bjsrK28pe2Zvcih2YXIgYT1yW29dLHM9YS5sZW5ndGgsbD1pW29dPW5ldyBBcnJheShzKSxjLHU9MDt1PHM7Kyt1KShjPWFbdV0pJiYobFt1XT1jKTtsLnNvcnQodCl9cmV0dXJuIG5ldyBubyhpLHRoaXMuX3BhcmVudHMpLm9yZGVyKCl9ZnVuY3Rpb24gdDhlKGUsdCl7cmV0dXJuIGU8dD8tMTplPnQ/MTplPj10PzA6TmFOfXZhciBHVnQ9TSgoKT0+e1NmKCl9KTtmdW5jdGlvbiBXVnQoKXt2YXIgZT1hcmd1bWVudHNbMF07cmV0dXJuIGFyZ3VtZW50c1swXT10aGlzLGUuYXBwbHkobnVsbCxhcmd1bWVudHMpLHRoaXN9dmFyIFlWdD1NKCgpPT57fSk7ZnVuY3Rpb24galZ0KCl7dmFyIGU9bmV3IEFycmF5KHRoaXMuc2l6ZSgpKSx0PS0xO3JldHVybiB0aGlzLmVhY2goZnVuY3Rpb24oKXtlWysrdF09dGhpc30pLGV9dmFyIFhWdD1NKCgpPT57fSk7ZnVuY3Rpb24gJFZ0KCl7Zm9yKHZhciBlPXRoaXMuX2dyb3Vwcyx0PTAscj1lLmxlbmd0aDt0PHI7Kyt0KWZvcih2YXIgbj1lW3RdLGk9MCxvPW4ubGVuZ3RoO2k8bzsrK2kpe3ZhciBhPW5baV07aWYoYSlyZXR1cm4gYX1yZXR1cm4gbnVsbH12YXIgS1Z0PU0oKCk9Pnt9KTtmdW5jdGlvbiBaVnQoKXt2YXIgZT0wO3JldHVybiB0aGlzLmVhY2goZnVuY3Rpb24oKXsrK2V9KSxlfXZhciBKVnQ9TSgoKT0+e30pO2Z1bmN0aW9uIFFWdCgpe3JldHVybiF0aGlzLm5vZGUoKX12YXIgdFV0PU0oKCk9Pnt9KTtmdW5jdGlvbiBlVXQoZSl7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPTAsbj10Lmxlbmd0aDtyPG47KytyKWZvcih2YXIgaT10W3JdLG89MCxhPWkubGVuZ3RoLHM7bzxhOysrbykocz1pW29dKSYmZS5jYWxsKHMscy5fX2RhdGFfXyxvLGkpO3JldHVybiB0aGlzfXZhciByVXQ9TSgoKT0+e30pO2Z1bmN0aW9uIGU4ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnJlbW92ZUF0dHJpYnV0ZShlKX19ZnVuY3Rpb24gcjhlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKX19ZnVuY3Rpb24gbjhlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zZXRBdHRyaWJ1dGUoZSx0KX19ZnVuY3Rpb24gaThlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zZXRBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwsdCl9fWZ1bmN0aW9uIG84ZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3ZhciByPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3I9PW51bGw/dGhpcy5yZW1vdmVBdHRyaWJ1dGUoZSk6dGhpcy5zZXRBdHRyaWJ1dGUoZSxyKX19ZnVuY3Rpb24gYThlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHI9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cj09bnVsbD90aGlzLnJlbW92ZUF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCk6dGhpcy5zZXRBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwscil9fWZ1bmN0aW9uIG5VdChlLHQpe3ZhciByPWxkKGUpO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7dmFyIG49dGhpcy5ub2RlKCk7cmV0dXJuIHIubG9jYWw/bi5nZXRBdHRyaWJ1dGVOUyhyLnNwYWNlLHIubG9jYWwpOm4uZ2V0QXR0cmlidXRlKHIpfXJldHVybiB0aGlzLmVhY2goKHQ9PW51bGw/ci5sb2NhbD9yOGU6ZThlOnR5cGVvZiB0PT0iZnVuY3Rpb24iP3IubG9jYWw/YThlOm84ZTpyLmxvY2FsP2k4ZTpuOGUpKHIsdCkpfXZhciBpVXQ9TSgoKT0+e3R6KCl9KTtmdW5jdGlvbiBpeihlKXtyZXR1cm4gZS5vd25lckRvY3VtZW50JiZlLm93bmVyRG9jdW1lbnQuZGVmYXVsdFZpZXd8fGUuZG9jdW1lbnQmJmV8fGUuZGVmYXVsdFZpZXd9dmFyIHpydD1NKCgpPT57fSk7ZnVuY3Rpb24gczhlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkoZSl9fWZ1bmN0aW9uIGw4ZShlLHQscil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zdHlsZS5zZXRQcm9wZXJ0eShlLHQscil9fWZ1bmN0aW9uIGM4ZShlLHQscil7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIG49dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7bj09bnVsbD90aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KGUpOnRoaXMuc3R5bGUuc2V0UHJvcGVydHkoZSxuLHIpfX1mdW5jdGlvbiBvVXQoZSx0LHIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPjE/dGhpcy5lYWNoKCh0PT1udWxsP3M4ZTp0eXBlb2YgdD09ImZ1bmN0aW9uIj9jOGU6bDhlKShlLHQscj09bnVsbD8iIjpyKSk6QmcodGhpcy5ub2RlKCksZSl9ZnVuY3Rpb24gQmcoZSx0KXtyZXR1cm4gZS5zdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKHQpfHxpeihlKS5nZXRDb21wdXRlZFN0eWxlKGUsbnVsbCkuZ2V0UHJvcGVydHlWYWx1ZSh0KX12YXIgRnJ0PU0oKCk9Pnt6cnQoKX0pO2Z1bmN0aW9uIHU4ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtkZWxldGUgdGhpc1tlXX19ZnVuY3Rpb24gaDhlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpc1tlXT10fX1mdW5jdGlvbiBmOGUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgcj10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyPT1udWxsP2RlbGV0ZSB0aGlzW2VdOnRoaXNbZV09cn19ZnVuY3Rpb24gYVV0KGUsdCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg+MT90aGlzLmVhY2goKHQ9PW51bGw/dThlOnR5cGVvZiB0PT0iZnVuY3Rpb24iP2Y4ZTpoOGUpKGUsdCkpOnRoaXMubm9kZSgpW2VdfXZhciBzVXQ9TSgoKT0+e30pO2Z1bmN0aW9uIGxVdChlKXtyZXR1cm4gZS50cmltKCkuc3BsaXQoL158XHMrLyl9ZnVuY3Rpb24gQnJ0KGUpe3JldHVybiBlLmNsYXNzTGlzdHx8bmV3IGNVdChlKX1mdW5jdGlvbiBjVXQoZSl7dGhpcy5fbm9kZT1lLHRoaXMuX25hbWVzPWxVdChlLmdldEF0dHJpYnV0ZSgiY2xhc3MiKXx8IiIpfWZ1bmN0aW9uIHVVdChlLHQpe2Zvcih2YXIgcj1CcnQoZSksbj0tMSxpPXQubGVuZ3RoOysrbjxpOylyLmFkZCh0W25dKX1mdW5jdGlvbiBoVXQoZSx0KXtmb3IodmFyIHI9QnJ0KGUpLG49LTEsaT10Lmxlbmd0aDsrK248aTspci5yZW1vdmUodFtuXSl9ZnVuY3Rpb24gcDhlKGUpe3JldHVybiBmdW5jdGlvbigpe3VVdCh0aGlzLGUpfX1mdW5jdGlvbiBkOGUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7aFV0KHRoaXMsZSl9fWZ1bmN0aW9uIG04ZShlLHQpe3JldHVybiBmdW5jdGlvbigpeyh0LmFwcGx5KHRoaXMsYXJndW1lbnRzKT91VXQ6aFV0KSh0aGlzLGUpfX1mdW5jdGlvbiBmVXQoZSx0KXt2YXIgcj1sVXQoZSsiIik7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXtmb3IodmFyIG49QnJ0KHRoaXMubm9kZSgpKSxpPS0xLG89ci5sZW5ndGg7KytpPG87KWlmKCFuLmNvbnRhaW5zKHJbaV0pKXJldHVybiExO3JldHVybiEwfXJldHVybiB0aGlzLmVhY2goKHR5cGVvZiB0PT0iZnVuY3Rpb24iP204ZTp0P3A4ZTpkOGUpKHIsdCkpfXZhciBwVXQ9TSgoKT0+e2NVdC5wcm90b3R5cGU9e2FkZDpmdW5jdGlvbihlKXt2YXIgdD10aGlzLl9uYW1lcy5pbmRleE9mKGUpO3Q8MCYmKHRoaXMuX25hbWVzLnB1c2goZSksdGhpcy5fbm9kZS5zZXRBdHRyaWJ1dGUoImNsYXNzIix0aGlzLl9uYW1lcy5qb2luKCIgIikpKX0scmVtb3ZlOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX25hbWVzLmluZGV4T2YoZSk7dD49MCYmKHRoaXMuX25hbWVzLnNwbGljZSh0LDEpLHRoaXMuX25vZGUuc2V0QXR0cmlidXRlKCJjbGFzcyIsdGhpcy5fbmFtZXMuam9pbigiICIpKSl9LGNvbnRhaW5zOmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLl9uYW1lcy5pbmRleE9mKGUpPj0wfX19KTtmdW5jdGlvbiBnOGUoKXt0aGlzLnRleHRDb250ZW50PSIifWZ1bmN0aW9uIF84ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnRleHRDb250ZW50PWV9fWZ1bmN0aW9uIHk4ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD1lLmFwcGx5KHRoaXMsYXJndW1lbnRzKTt0aGlzLnRleHRDb250ZW50PXQ9PW51bGw/IiI6dH19ZnVuY3Rpb24gZFV0KGUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaChlPT1udWxsP2c4ZToodHlwZW9mIGU9PSJmdW5jdGlvbiI/eThlOl84ZSkoZSkpOnRoaXMubm9kZSgpLnRleHRDb250ZW50fXZhciBtVXQ9TSgoKT0+e30pO2Z1bmN0aW9uIHY4ZSgpe3RoaXMuaW5uZXJIVE1MPSIifWZ1bmN0aW9uIHg4ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLmlubmVySFRNTD1lfX1mdW5jdGlvbiBiOGUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyk7dGhpcy5pbm5lckhUTUw9dD09bnVsbD8iIjp0fX1mdW5jdGlvbiBnVXQoZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5lYWNoKGU9PW51bGw/djhlOih0eXBlb2YgZT09ImZ1bmN0aW9uIj9iOGU6eDhlKShlKSk6dGhpcy5ub2RlKCkuaW5uZXJIVE1MfXZhciBfVXQ9TSgoKT0+e30pO2Z1bmN0aW9uIHc4ZSgpe3RoaXMubmV4dFNpYmxpbmcmJnRoaXMucGFyZW50Tm9kZS5hcHBlbmRDaGlsZCh0aGlzKX1mdW5jdGlvbiB5VXQoKXtyZXR1cm4gdGhpcy5lYWNoKHc4ZSl9dmFyIHZVdD1NKCgpPT57fSk7ZnVuY3Rpb24gUzhlKCl7dGhpcy5wcmV2aW91c1NpYmxpbmcmJnRoaXMucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUodGhpcyx0aGlzLnBhcmVudE5vZGUuZmlyc3RDaGlsZCl9ZnVuY3Rpb24geFV0KCl7cmV0dXJuIHRoaXMuZWFjaChTOGUpfXZhciBiVXQ9TSgoKT0+e30pO2Z1bmN0aW9uIHdVdChlKXt2YXIgdD10eXBlb2YgZT09ImZ1bmN0aW9uIj9lOmV6KGUpO3JldHVybiB0aGlzLnNlbGVjdChmdW5jdGlvbigpe3JldHVybiB0aGlzLmFwcGVuZENoaWxkKHQuYXBwbHkodGhpcyxhcmd1bWVudHMpKX0pfXZhciBTVXQ9TSgoKT0+e2tydCgpfSk7ZnVuY3Rpb24gTThlKCl7cmV0dXJuIG51bGx9ZnVuY3Rpb24gTVV0KGUsdCl7dmFyIHI9dHlwZW9mIGU9PSJmdW5jdGlvbiI/ZTpleihlKSxuPXQ9PW51bGw/TThlOnR5cGVvZiB0PT0iZnVuY3Rpb24iP3Q6dzEodCk7cmV0dXJuIHRoaXMuc2VsZWN0KGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuaW5zZXJ0QmVmb3JlKHIuYXBwbHkodGhpcyxhcmd1bWVudHMpLG4uYXBwbHkodGhpcyxhcmd1bWVudHMpfHxudWxsKX0pfXZhciBFVXQ9TSgoKT0+e2tydCgpO3J6KCl9KTtmdW5jdGlvbiBFOGUoKXt2YXIgZT10aGlzLnBhcmVudE5vZGU7ZSYmZS5yZW1vdmVDaGlsZCh0aGlzKX1mdW5jdGlvbiBUVXQoKXtyZXR1cm4gdGhpcy5lYWNoKEU4ZSl9dmFyIENVdD1NKCgpPT57fSk7ZnVuY3Rpb24gVDhlKCl7dmFyIGU9dGhpcy5jbG9uZU5vZGUoITEpLHQ9dGhpcy5wYXJlbnROb2RlO3JldHVybiB0P3QuaW5zZXJ0QmVmb3JlKGUsdGhpcy5uZXh0U2libGluZyk6ZX1mdW5jdGlvbiBDOGUoKXt2YXIgZT10aGlzLmNsb25lTm9kZSghMCksdD10aGlzLnBhcmVudE5vZGU7cmV0dXJuIHQ/dC5pbnNlcnRCZWZvcmUoZSx0aGlzLm5leHRTaWJsaW5nKTplfWZ1bmN0aW9uIEFVdChlKXtyZXR1cm4gdGhpcy5zZWxlY3QoZT9DOGU6VDhlKX12YXIgUFV0PU0oKCk9Pnt9KTtmdW5jdGlvbiBJVXQoZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5wcm9wZXJ0eSgiX19kYXRhX18iLGUpOnRoaXMubm9kZSgpLl9fZGF0YV9ffXZhciBMVXQ9TSgoKT0+e30pO2Z1bmN0aW9uIEE4ZShlLHQscil7cmV0dXJuIGU9TlV0KGUsdCxyKSxmdW5jdGlvbihuKXt2YXIgaT1uLnJlbGF0ZWRUYXJnZXQ7KCFpfHxpIT09dGhpcyYmIShpLmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKHRoaXMpJjgpKSYmZS5jYWxsKHRoaXMsbil9fWZ1bmN0aW9uIE5VdChlLHQscil7cmV0dXJuIGZ1bmN0aW9uKG4pe3ZhciBpPUhydDtIcnQ9bjt0cnl7ZS5jYWxsKHRoaXMsdGhpcy5fX2RhdGFfXyx0LHIpfWZpbmFsbHl7SHJ0PWl9fX1mdW5jdGlvbiBQOGUoZSl7cmV0dXJuIGUudHJpbSgpLnNwbGl0KC9efFxzKy8pLm1hcChmdW5jdGlvbih0KXt2YXIgcj0iIixuPXQuaW5kZXhPZigiLiIpO3JldHVybiBuPj0wJiYocj10LnNsaWNlKG4rMSksdD10LnNsaWNlKDAsbikpLHt0eXBlOnQsbmFtZTpyfX0pfWZ1bmN0aW9uIEk4ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD10aGlzLl9fb247aWYoISF0KXtmb3IodmFyIHI9MCxuPS0xLGk9dC5sZW5ndGgsbztyPGk7KytyKW89dFtyXSwoIWUudHlwZXx8by50eXBlPT09ZS50eXBlKSYmby5uYW1lPT09ZS5uYW1lP3RoaXMucmVtb3ZlRXZlbnRMaXN0ZW5lcihvLnR5cGUsby5saXN0ZW5lcixvLmNhcHR1cmUpOnRbKytuXT1vOysrbj90Lmxlbmd0aD1uOmRlbGV0ZSB0aGlzLl9fb259fX1mdW5jdGlvbiBMOGUoZSx0LHIpe3ZhciBuPVJVdC5oYXNPd25Qcm9wZXJ0eShlLnR5cGUpP0E4ZTpOVXQ7cmV0dXJuIGZ1bmN0aW9uKGksbyxhKXt2YXIgcz10aGlzLl9fb24sbCxjPW4odCxvLGEpO2lmKHMpe2Zvcih2YXIgdT0wLGg9cy5sZW5ndGg7dTxoOysrdSlpZigobD1zW3VdKS50eXBlPT09ZS50eXBlJiZsLm5hbWU9PT1lLm5hbWUpe3RoaXMucmVtb3ZlRXZlbnRMaXN0ZW5lcihsLnR5cGUsbC5saXN0ZW5lcixsLmNhcHR1cmUpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcihsLnR5cGUsbC5saXN0ZW5lcj1jLGwuY2FwdHVyZT1yKSxsLnZhbHVlPXQ7cmV0dXJufX10aGlzLmFkZEV2ZW50TGlzdGVuZXIoZS50eXBlLGMsciksbD17dHlwZTplLnR5cGUsbmFtZTplLm5hbWUsdmFsdWU6dCxsaXN0ZW5lcjpjLGNhcHR1cmU6cn0scz9zLnB1c2gobCk6dGhpcy5fX29uPVtsXX19ZnVuY3Rpb24gRFV0KGUsdCxyKXt2YXIgbj1QOGUoZSsiIiksaSxvPW4ubGVuZ3RoLGE7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXt2YXIgcz10aGlzLm5vZGUoKS5fX29uO2lmKHMpe2Zvcih2YXIgbD0wLGM9cy5sZW5ndGgsdTtsPGM7KytsKWZvcihpPTAsdT1zW2xdO2k8bzsrK2kpaWYoKGE9bltpXSkudHlwZT09PXUudHlwZSYmYS5uYW1lPT09dS5uYW1lKXJldHVybiB1LnZhbHVlfXJldHVybn1mb3Iocz10P0w4ZTpJOGUscj09bnVsbCYmKHI9ITEpLGk9MDtpPG87KytpKXRoaXMuZWFjaChzKG5baV0sdCxyKSk7cmV0dXJuIHRoaXN9dmFyIFJVdCxIcnQsa1V0LE9VdD1NKCgpPT57UlV0PXt9LEhydD1udWxsO3R5cGVvZiBkb2N1bWVudCE9InVuZGVmaW5lZCImJihrVXQ9ZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LCJvbm1vdXNlZW50ZXIiaW4ga1V0fHwoUlV0PXttb3VzZWVudGVyOiJtb3VzZW92ZXIiLG1vdXNlbGVhdmU6Im1vdXNlb3V0In0pKX0pO2Z1bmN0aW9uIHpVdChlLHQscil7dmFyIG49aXooZSksaT1uLkN1c3RvbUV2ZW50O3R5cGVvZiBpPT0iZnVuY3Rpb24iP2k9bmV3IGkodCxyKTooaT1uLmRvY3VtZW50LmNyZWF0ZUV2ZW50KCJFdmVudCIpLHI/KGkuaW5pdEV2ZW50KHQsci5idWJibGVzLHIuY2FuY2VsYWJsZSksaS5kZXRhaWw9ci5kZXRhaWwpOmkuaW5pdEV2ZW50KHQsITEsITEpKSxlLmRpc3BhdGNoRXZlbnQoaSl9ZnVuY3Rpb24gazhlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIHpVdCh0aGlzLGUsdCl9fWZ1bmN0aW9uIFI4ZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiB6VXQodGhpcyxlLHQuYXBwbHkodGhpcyxhcmd1bWVudHMpKX19ZnVuY3Rpb24gRlV0KGUsdCl7cmV0dXJuIHRoaXMuZWFjaCgodHlwZW9mIHQ9PSJmdW5jdGlvbiI/UjhlOms4ZSkoZSx0KSl9dmFyIEJVdD1NKCgpPT57enJ0KCl9KTtmdW5jdGlvbiBubyhlLHQpe3RoaXMuX2dyb3Vwcz1lLHRoaXMuX3BhcmVudHM9dH1mdW5jdGlvbiBIVXQoKXtyZXR1cm4gbmV3IG5vKFtbZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50XV0sTjhlKX12YXIgTjhlLGNkLFNmPU0oKCk9PntNVnQoKTtUVnQoKTtBVnQoKTtOVnQoKTtPcnQoKTtPVnQoKTtGVnQoKTtIVnQoKTtVVnQoKTtHVnQoKTtZVnQoKTtYVnQoKTtLVnQoKTtKVnQoKTt0VXQoKTtyVXQoKTtpVXQoKTtGcnQoKTtzVXQoKTtwVXQoKTttVXQoKTtfVXQoKTt2VXQoKTtiVXQoKTtTVXQoKTtFVXQoKTtDVXQoKTtQVXQoKTtMVXQoKTtPVXQoKTtCVXQoKTtOOGU9W251bGxdO25vLnByb3RvdHlwZT1IVXQucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpubyxzZWxlY3Q6U1Z0LHNlbGVjdEFsbDpFVnQsZmlsdGVyOkNWdCxkYXRhOlJWdCxlbnRlcjpQVnQsZXhpdDpEVnQsam9pbjp6VnQsbWVyZ2U6QlZ0LG9yZGVyOlZWdCxzb3J0OnFWdCxjYWxsOldWdCxub2RlczpqVnQsbm9kZTokVnQsc2l6ZTpaVnQsZW1wdHk6UVZ0LGVhY2g6ZVV0LGF0dHI6blV0LHN0eWxlOm9VdCxwcm9wZXJ0eTphVXQsY2xhc3NlZDpmVXQsdGV4dDpkVXQsaHRtbDpnVXQscmFpc2U6eVV0LGxvd2VyOnhVdCxhcHBlbmQ6d1V0LGluc2VydDpNVXQscmVtb3ZlOlRVdCxjbG9uZTpBVXQsZGF0dW06SVV0LG9uOkRVdCxkaXNwYXRjaDpGVXR9O2NkPUhVdH0pO3ZhciBNZj1NKCgpPT57TnJ0KCk7dHooKTtTZigpO3J6KCk7UnJ0KCk7RnJ0KCl9KTtmdW5jdGlvbiBVVXQoKXtmb3IodmFyIGU9MCx0PWFyZ3VtZW50cy5sZW5ndGgscj17fSxuO2U8dDsrK2Upe2lmKCEobj1hcmd1bWVudHNbZV0rIiIpfHxuIGluIHJ8fC9bXHMuXS8udGVzdChuKSl0aHJvdyBuZXcgRXJyb3IoImlsbGVnYWwgdHlwZTogIituKTtyW25dPVtdfXJldHVybiBuZXcgb3oocil9ZnVuY3Rpb24gb3ooZSl7dGhpcy5fPWV9ZnVuY3Rpb24gTzhlKGUsdCl7cmV0dXJuIGUudHJpbSgpLnNwbGl0KC9efFxzKy8pLm1hcChmdW5jdGlvbihyKXt2YXIgbj0iIixpPXIuaW5kZXhPZigiLiIpO2lmKGk+PTAmJihuPXIuc2xpY2UoaSsxKSxyPXIuc2xpY2UoMCxpKSksciYmIXQuaGFzT3duUHJvcGVydHkocikpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrcik7cmV0dXJue3R5cGU6cixuYW1lOm59fSl9ZnVuY3Rpb24gejhlKGUsdCl7Zm9yKHZhciByPTAsbj1lLmxlbmd0aCxpO3I8bjsrK3IpaWYoKGk9ZVtyXSkubmFtZT09PXQpcmV0dXJuIGkudmFsdWV9ZnVuY3Rpb24gVlV0KGUsdCxyKXtmb3IodmFyIG49MCxpPWUubGVuZ3RoO248aTsrK24paWYoZVtuXS5uYW1lPT09dCl7ZVtuXT1EOGUsZT1lLnNsaWNlKDAsbikuY29uY2F0KGUuc2xpY2UobisxKSk7YnJlYWt9cmV0dXJuIHIhPW51bGwmJmUucHVzaCh7bmFtZTp0LHZhbHVlOnJ9KSxlfXZhciBEOGUsVnJ0LHFVdD1NKCgpPT57RDhlPXt2YWx1ZTpmdW5jdGlvbigpe319O296LnByb3RvdHlwZT1VVXQucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpveixvbjpmdW5jdGlvbihlLHQpe3ZhciByPXRoaXMuXyxuPU84ZShlKyIiLHIpLGksbz0tMSxhPW4ubGVuZ3RoO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7Zm9yKDsrK288YTspaWYoKGk9KGU9bltvXSkudHlwZSkmJihpPXo4ZShyW2ldLGUubmFtZSkpKXJldHVybiBpO3JldHVybn1pZih0IT1udWxsJiZ0eXBlb2YgdCE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgY2FsbGJhY2s6ICIrdCk7Zm9yKDsrK288YTspaWYoaT0oZT1uW29dKS50eXBlKXJbaV09VlV0KHJbaV0sZS5uYW1lLHQpO2Vsc2UgaWYodD09bnVsbClmb3IoaSBpbiByKXJbaV09VlV0KHJbaV0sZS5uYW1lLG51bGwpO3JldHVybiB0aGlzfSxjb3B5OmZ1bmN0aW9uKCl7dmFyIGU9e30sdD10aGlzLl87Zm9yKHZhciByIGluIHQpZVtyXT10W3JdLnNsaWNlKCk7cmV0dXJuIG5ldyBveihlKX0sY2FsbDpmdW5jdGlvbihlLHQpe2lmKChpPWFyZ3VtZW50cy5sZW5ndGgtMik+MClmb3IodmFyIHI9bmV3IEFycmF5KGkpLG49MCxpLG87bjxpOysrbilyW25dPWFyZ3VtZW50c1tuKzJdO2lmKCF0aGlzLl8uaGFzT3duUHJvcGVydHkoZSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrZSk7Zm9yKG89dGhpcy5fW2VdLG49MCxpPW8ubGVuZ3RoO248aTsrK24pb1tuXS52YWx1ZS5hcHBseSh0LHIpfSxhcHBseTpmdW5jdGlvbihlLHQscil7aWYoIXRoaXMuXy5oYXNPd25Qcm9wZXJ0eShlKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gdHlwZTogIitlKTtmb3IodmFyIG49dGhpcy5fW2VdLGk9MCxvPW4ubGVuZ3RoO2k8bzsrK2kpbltpXS52YWx1ZS5hcHBseSh0LHIpfX07VnJ0PVVVdH0pO3ZhciBHVXQ9TSgoKT0+e3FVdCgpfSk7ZnVuY3Rpb24gWHcoKXtyZXR1cm4gUzF8fChqVXQoRjhlKSxTMT1pQS5ub3coKStseil9ZnVuY3Rpb24gRjhlKCl7UzE9MH1mdW5jdGlvbiBvQSgpe3RoaXMuX2NhbGw9dGhpcy5fdGltZT10aGlzLl9uZXh0PW51bGx9ZnVuY3Rpb24gY3ooZSx0LHIpe3ZhciBuPW5ldyBvQTtyZXR1cm4gbi5yZXN0YXJ0KGUsdCxyKSxufWZ1bmN0aW9uIFhVdCgpe1h3KCksKytqdztmb3IodmFyIGU9YXosdDtlOykodD1TMS1lLl90aW1lKT49MCYmZS5fY2FsbC5jYWxsKG51bGwsdCksZT1lLl9uZXh0Oy0tand9ZnVuY3Rpb24gV1V0KCl7UzE9KHN6PWlBLm5vdygpKStseixqdz1yQT0wO3RyeXtYVXQoKX1maW5hbGx5e2p3PTAsSDhlKCksUzE9MH19ZnVuY3Rpb24gQjhlKCl7dmFyIGU9aUEubm93KCksdD1lLXN6O3Q+WVV0JiYobHotPXQsc3o9ZSl9ZnVuY3Rpb24gSDhlKCl7Zm9yKHZhciBlLHQ9YXoscixuPTEvMDt0Oyl0Ll9jYWxsPyhuPnQuX3RpbWUmJihuPXQuX3RpbWUpLGU9dCx0PXQuX25leHQpOihyPXQuX25leHQsdC5fbmV4dD1udWxsLHQ9ZT9lLl9uZXh0PXI6YXo9cik7bkE9ZSxVcnQobil9ZnVuY3Rpb24gVXJ0KGUpe2lmKCFqdyl7ckEmJihyQT1jbGVhclRpbWVvdXQockEpKTt2YXIgdD1lLVMxO3Q+MjQ/KGU8MS8wJiYockE9c2V0VGltZW91dChXVXQsZS1pQS5ub3coKS1seikpLGVBJiYoZUE9Y2xlYXJJbnRlcnZhbChlQSkpKTooZUF8fChzej1pQS5ub3coKSxlQT1zZXRJbnRlcnZhbChCOGUsWVV0KSksanc9MSxqVXQoV1V0KSl9fXZhciBqdyxyQSxlQSxZVXQsYXosbkEsc3osUzEsbHosaUEsalV0LHFydD1NKCgpPT57anc9MCxyQT0wLGVBPTAsWVV0PTFlMyxzej0wLFMxPTAsbHo9MCxpQT10eXBlb2YgcGVyZm9ybWFuY2U9PSJvYmplY3QiJiZwZXJmb3JtYW5jZS5ub3c/cGVyZm9ybWFuY2U6RGF0ZSxqVXQ9dHlwZW9mIHdpbmRvdz09Im9iamVjdCImJndpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWU/d2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZS5iaW5kKHdpbmRvdyk6ZnVuY3Rpb24oZSl7c2V0VGltZW91dChlLDE3KX07b0EucHJvdG90eXBlPWN6LnByb3RvdHlwZT17Y29uc3RydWN0b3I6b0EscmVzdGFydDpmdW5jdGlvbihlLHQscil7aWYodHlwZW9mIGUhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IFR5cGVFcnJvcigiY2FsbGJhY2sgaXMgbm90IGEgZnVuY3Rpb24iKTtyPShyPT1udWxsP1h3KCk6K3IpKyh0PT1udWxsPzA6K3QpLCF0aGlzLl9uZXh0JiZuQSE9PXRoaXMmJihuQT9uQS5fbmV4dD10aGlzOmF6PXRoaXMsbkE9dGhpcyksdGhpcy5fY2FsbD1lLHRoaXMuX3RpbWU9cixVcnQoKX0sc3RvcDpmdW5jdGlvbigpe3RoaXMuX2NhbGwmJih0aGlzLl9jYWxsPW51bGwsdGhpcy5fdGltZT0xLzAsVXJ0KCkpfX19KTtmdW5jdGlvbiB1eihlLHQscil7dmFyIG49bmV3IG9BO3JldHVybiB0PXQ9PW51bGw/MDordCxuLnJlc3RhcnQoZnVuY3Rpb24oaSl7bi5zdG9wKCksZShpK3QpfSx0LHIpLG59dmFyICRVdD1NKCgpPT57cXJ0KCl9KTt2YXIgR3J0PU0oKCk9PntxcnQoKTskVXQoKX0pO2Z1bmN0aW9uIEhnKGUsdCxyLG4saSxvKXt2YXIgYT1lLl9fdHJhbnNpdGlvbjtpZighYSllLl9fdHJhbnNpdGlvbj17fTtlbHNlIGlmKHIgaW4gYSlyZXR1cm47cThlKGUscix7bmFtZTp0LGluZGV4Om4sZ3JvdXA6aSxvbjpWOGUsdHdlZW46VThlLHRpbWU6by50aW1lLGRlbGF5Om8uZGVsYXksZHVyYXRpb246by5kdXJhdGlvbixlYXNlOm8uZWFzZSx0aW1lcjpudWxsLHN0YXRlOlpVdH0pfWZ1bmN0aW9uIGxBKGUsdCl7dmFyIHI9aW8oZSx0KTtpZihyLnN0YXRlPlpVdCl0aHJvdyBuZXcgRXJyb3IoInRvbyBsYXRlOyBhbHJlYWR5IHNjaGVkdWxlZCIpO3JldHVybiByfWZ1bmN0aW9uIER1KGUsdCl7dmFyIHI9aW8oZSx0KTtpZihyLnN0YXRlPnNBKXRocm93IG5ldyBFcnJvcigidG9vIGxhdGU7IGFscmVhZHkgc3RhcnRlZCIpO3JldHVybiByfWZ1bmN0aW9uIGlvKGUsdCl7dmFyIHI9ZS5fX3RyYW5zaXRpb247aWYoIXJ8fCEocj1yW3RdKSl0aHJvdyBuZXcgRXJyb3IoInRyYW5zaXRpb24gbm90IGZvdW5kIik7cmV0dXJuIHJ9ZnVuY3Rpb24gcThlKGUsdCxyKXt2YXIgbj1lLl9fdHJhbnNpdGlvbixpO25bdF09cixyLnRpbWVyPWN6KG8sMCxyLnRpbWUpO2Z1bmN0aW9uIG8oYyl7ci5zdGF0ZT1oeixyLnRpbWVyLnJlc3RhcnQoYSxyLmRlbGF5LHIudGltZSksci5kZWxheTw9YyYmYShjLXIuZGVsYXkpfWZ1bmN0aW9uIGEoYyl7dmFyIHUsaCxmLHA7aWYoci5zdGF0ZSE9PWh6KXJldHVybiBsKCk7Zm9yKHUgaW4gbilpZihwPW5bdV0scC5uYW1lPT09ci5uYW1lKXtpZihwLnN0YXRlPT09V3J0KXJldHVybiB1eihhKTtwLnN0YXRlPT09S1V0PyhwLnN0YXRlPWFBLHAudGltZXIuc3RvcCgpLHAub24uY2FsbCgiaW50ZXJydXB0IixlLGUuX19kYXRhX18scC5pbmRleCxwLmdyb3VwKSxkZWxldGUgblt1XSk6K3U8dCYmKHAuc3RhdGU9YUEscC50aW1lci5zdG9wKCksZGVsZXRlIG5bdV0pfWlmKHV6KGZ1bmN0aW9uKCl7ci5zdGF0ZT09PVdydCYmKHIuc3RhdGU9S1V0LHIudGltZXIucmVzdGFydChzLHIuZGVsYXksci50aW1lKSxzKGMpKX0pLHIuc3RhdGU9c0Esci5vbi5jYWxsKCJzdGFydCIsZSxlLl9fZGF0YV9fLHIuaW5kZXgsci5ncm91cCksci5zdGF0ZT09PXNBKXtmb3Ioci5zdGF0ZT1XcnQsaT1uZXcgQXJyYXkoZj1yLnR3ZWVuLmxlbmd0aCksdT0wLGg9LTE7dTxmOysrdSkocD1yLnR3ZWVuW3VdLnZhbHVlLmNhbGwoZSxlLl9fZGF0YV9fLHIuaW5kZXgsci5ncm91cCkpJiYoaVsrK2hdPXApO2kubGVuZ3RoPWgrMX19ZnVuY3Rpb24gcyhjKXtmb3IodmFyIHU9YzxyLmR1cmF0aW9uP3IuZWFzZS5jYWxsKG51bGwsYy9yLmR1cmF0aW9uKTooci50aW1lci5yZXN0YXJ0KGwpLHIuc3RhdGU9ZnosMSksaD0tMSxmPWkubGVuZ3RoOysraDxmOylpW2hdLmNhbGwobnVsbCx1KTtyLnN0YXRlPT09ZnomJihyLm9uLmNhbGwoImVuZCIsZSxlLl9fZGF0YV9fLHIuaW5kZXgsci5ncm91cCksbCgpKX1mdW5jdGlvbiBsKCl7ci5zdGF0ZT1hQSxyLnRpbWVyLnN0b3AoKSxkZWxldGUgblt0XTtmb3IodmFyIGMgaW4gbilyZXR1cm47ZGVsZXRlIGUuX190cmFuc2l0aW9ufX12YXIgVjhlLFU4ZSxaVXQsaHosc0EsV3J0LEtVdCxmeixhQSxBYz1NKCgpPT57R1V0KCk7R3J0KCk7VjhlPVZydCgic3RhcnQiLCJlbmQiLCJpbnRlcnJ1cHQiKSxVOGU9W10sWlV0PTAsaHo9MSxzQT0yLFdydD0zLEtVdD00LGZ6PTUsYUE9Nn0pO2Z1bmN0aW9uIHB6KGUsdCl7dmFyIHI9ZS5fX3RyYW5zaXRpb24sbixpLG89ITAsYTtpZighIXIpe3Q9dD09bnVsbD9udWxsOnQrIiI7Zm9yKGEgaW4gcil7aWYoKG49clthXSkubmFtZSE9PXQpe289ITE7Y29udGludWV9aT1uLnN0YXRlPnNBJiZuLnN0YXRlPGZ6LG4uc3RhdGU9YUEsbi50aW1lci5zdG9wKCksaSYmbi5vbi5jYWxsKCJpbnRlcnJ1cHQiLGUsZS5fX2RhdGFfXyxuLmluZGV4LG4uZ3JvdXApLGRlbGV0ZSByW2FdfW8mJmRlbGV0ZSBlLl9fdHJhbnNpdGlvbn19dmFyIFlydD1NKCgpPT57QWMoKX0pO2Z1bmN0aW9uIEpVdChlKXtyZXR1cm4gdGhpcy5lYWNoKGZ1bmN0aW9uKCl7cHoodGhpcyxlKX0pfXZhciBRVXQ9TSgoKT0+e1lydCgpfSk7ZnVuY3Rpb24gZHooZSx0LHIpe2UucHJvdG90eXBlPXQucHJvdG90eXBlPXIsci5jb25zdHJ1Y3Rvcj1lfWZ1bmN0aW9uIGpydChlLHQpe3ZhciByPU9iamVjdC5jcmVhdGUoZS5wcm90b3R5cGUpO2Zvcih2YXIgbiBpbiB0KXJbbl09dFtuXTtyZXR1cm4gcn12YXIgdHF0PU0oKCk9Pnt9KTtmdW5jdGlvbiBoQSgpe31mdW5jdGlvbiBycXQoKXtyZXR1cm4gdGhpcy5yZ2IoKS5mb3JtYXRIZXgoKX1mdW5jdGlvbiBaOGUoKXtyZXR1cm4gbHF0KHRoaXMpLmZvcm1hdEhzbCgpfWZ1bmN0aW9uIG5xdCgpe3JldHVybiB0aGlzLnJnYigpLmZvcm1hdFJnYigpfWZ1bmN0aW9uIFZnKGUpe3ZhciB0LHI7cmV0dXJuIGU9KGUrIiIpLnRyaW0oKS50b0xvd2VyQ2FzZSgpLCh0PUc4ZS5leGVjKGUpKT8ocj10WzFdLmxlbmd0aCx0PXBhcnNlSW50KHRbMV0sMTYpLHI9PT02P2lxdCh0KTpyPT09Mz9uZXcgbWwodD4+OCYxNXx0Pj40JjI0MCx0Pj40JjE1fHQmMjQwLCh0JjE1KTw8NHx0JjE1LDEpOnI9PT04P216KHQ+PjI0JjI1NSx0Pj4xNiYyNTUsdD4+OCYyNTUsKHQmMjU1KS8yNTUpOnI9PT00P216KHQ+PjEyJjE1fHQ+PjgmMjQwLHQ+PjgmMTV8dD4+NCYyNDAsdD4+NCYxNXx0JjI0MCwoKHQmMTUpPDw0fHQmMTUpLzI1NSk6bnVsbCk6KHQ9VzhlLmV4ZWMoZSkpP25ldyBtbCh0WzFdLHRbMl0sdFszXSwxKToodD1ZOGUuZXhlYyhlKSk/bmV3IG1sKHRbMV0qMjU1LzEwMCx0WzJdKjI1NS8xMDAsdFszXSoyNTUvMTAwLDEpOih0PWo4ZS5leGVjKGUpKT9teih0WzFdLHRbMl0sdFszXSx0WzRdKToodD1YOGUuZXhlYyhlKSk/bXoodFsxXSoyNTUvMTAwLHRbMl0qMjU1LzEwMCx0WzNdKjI1NS8xMDAsdFs0XSk6KHQ9JDhlLmV4ZWMoZSkpP3NxdCh0WzFdLHRbMl0vMTAwLHRbM10vMTAwLDEpOih0PUs4ZS5leGVjKGUpKT9zcXQodFsxXSx0WzJdLzEwMCx0WzNdLzEwMCx0WzRdKTplcXQuaGFzT3duUHJvcGVydHkoZSk/aXF0KGVxdFtlXSk6ZT09PSJ0cmFuc3BhcmVudCI/bmV3IG1sKE5hTixOYU4sTmFOLDApOm51bGx9ZnVuY3Rpb24gaXF0KGUpe3JldHVybiBuZXcgbWwoZT4+MTYmMjU1LGU+PjgmMjU1LGUmMjU1LDEpfWZ1bmN0aW9uIG16KGUsdCxyLG4pe3JldHVybiBuPD0wJiYoZT10PXI9TmFOKSxuZXcgbWwoZSx0LHIsbil9ZnVuY3Rpb24gSjhlKGUpe3JldHVybiBlIGluc3RhbmNlb2YgaEF8fChlPVZnKGUpKSxlPyhlPWUucmdiKCksbmV3IG1sKGUucixlLmcsZS5iLGUub3BhY2l0eSkpOm5ldyBtbH1mdW5jdGlvbiBLdyhlLHQscixuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTE/SjhlKGUpOm5ldyBtbChlLHQscixuPT1udWxsPzE6bil9ZnVuY3Rpb24gbWwoZSx0LHIsbil7dGhpcy5yPStlLHRoaXMuZz0rdCx0aGlzLmI9K3IsdGhpcy5vcGFjaXR5PStufWZ1bmN0aW9uIG9xdCgpe3JldHVybiIjIitYcnQodGhpcy5yKStYcnQodGhpcy5nKStYcnQodGhpcy5iKX1mdW5jdGlvbiBhcXQoKXt2YXIgZT10aGlzLm9wYWNpdHk7cmV0dXJuIGU9aXNOYU4oZSk/MTpNYXRoLm1heCgwLE1hdGgubWluKDEsZSkpLChlPT09MT8icmdiKCI6InJnYmEoIikrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCh0aGlzLnIpfHwwKSkrIiwgIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKHRoaXMuZyl8fDApKSsiLCAiK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQodGhpcy5iKXx8MCkpKyhlPT09MT8iKSI6IiwgIitlKyIpIil9ZnVuY3Rpb24gWHJ0KGUpe3JldHVybiBlPU1hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQoZSl8fDApKSwoZTwxNj8iMCI6IiIpK2UudG9TdHJpbmcoMTYpfWZ1bmN0aW9uIHNxdChlLHQscixuKXtyZXR1cm4gbjw9MD9lPXQ9cj1OYU46cjw9MHx8cj49MT9lPXQ9TmFOOnQ8PTAmJihlPU5hTiksbmV3IEVmKGUsdCxyLG4pfWZ1bmN0aW9uIGxxdChlKXtpZihlIGluc3RhbmNlb2YgRWYpcmV0dXJuIG5ldyBFZihlLmgsZS5zLGUubCxlLm9wYWNpdHkpO2lmKGUgaW5zdGFuY2VvZiBoQXx8KGU9VmcoZSkpLCFlKXJldHVybiBuZXcgRWY7aWYoZSBpbnN0YW5jZW9mIEVmKXJldHVybiBlO2U9ZS5yZ2IoKTt2YXIgdD1lLnIvMjU1LHI9ZS5nLzI1NSxuPWUuYi8yNTUsaT1NYXRoLm1pbih0LHIsbiksbz1NYXRoLm1heCh0LHIsbiksYT1OYU4scz1vLWksbD0obytpKS8yO3JldHVybiBzPyh0PT09bz9hPShyLW4pL3MrKHI8bikqNjpyPT09bz9hPShuLXQpL3MrMjphPSh0LXIpL3MrNCxzLz1sPC41P28raToyLW8taSxhKj02MCk6cz1sPjAmJmw8MT8wOmEsbmV3IEVmKGEscyxsLGUub3BhY2l0eSl9ZnVuY3Rpb24gY3F0KGUsdCxyLG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MT9scXQoZSk6bmV3IEVmKGUsdCxyLG49PW51bGw/MTpuKX1mdW5jdGlvbiBFZihlLHQscixuKXt0aGlzLmg9K2UsdGhpcy5zPSt0LHRoaXMubD0rcix0aGlzLm9wYWNpdHk9K259ZnVuY3Rpb24gJHJ0KGUsdCxyKXtyZXR1cm4oZTw2MD90KyhyLXQpKmUvNjA6ZTwxODA/cjplPDI0MD90KyhyLXQpKigyNDAtZSkvNjA6dCkqMjU1fXZhciBjQSxneiwkdyx1QSxUZixHOGUsVzhlLFk4ZSxqOGUsWDhlLCQ4ZSxLOGUsZXF0LHVxdD1NKCgpPT57dHF0KCk7Y0E9LjcsZ3o9MS9jQSwkdz0iXFxzKihbKy1dP1xcZCspXFxzKiIsdUE9IlxccyooWystXT9cXGQqXFwuP1xcZCsoPzpbZUVdWystXT9cXGQrKT8pXFxzKiIsVGY9IlxccyooWystXT9cXGQqXFwuP1xcZCsoPzpbZUVdWystXT9cXGQrKT8pJVxccyoiLEc4ZT0vXiMoWzAtOWEtZl17Myw4fSkkLyxXOGU9bmV3IFJlZ0V4cCgiXnJnYlxcKCIrWyR3LCR3LCR3XSsiXFwpJCIpLFk4ZT1uZXcgUmVnRXhwKCJecmdiXFwoIitbVGYsVGYsVGZdKyJcXCkkIiksajhlPW5ldyBSZWdFeHAoIl5yZ2JhXFwoIitbJHcsJHcsJHcsdUFdKyJcXCkkIiksWDhlPW5ldyBSZWdFeHAoIl5yZ2JhXFwoIitbVGYsVGYsVGYsdUFdKyJcXCkkIiksJDhlPW5ldyBSZWdFeHAoIl5oc2xcXCgiK1t1QSxUZixUZl0rIlxcKSQiKSxLOGU9bmV3IFJlZ0V4cCgiXmhzbGFcXCgiK1t1QSxUZixUZix1QV0rIlxcKSQiKSxlcXQ9e2FsaWNlYmx1ZToxNTc5MjM4MyxhbnRpcXVld2hpdGU6MTY0NDQzNzUsYXF1YTo2NTUzNSxhcXVhbWFyaW5lOjgzODg1NjQsYXp1cmU6MTU3OTQxNzUsYmVpZ2U6MTYxMTkyNjAsYmlzcXVlOjE2NzcwMjQ0LGJsYWNrOjAsYmxhbmNoZWRhbG1vbmQ6MTY3NzIwNDUsYmx1ZToyNTUsYmx1ZXZpb2xldDo5MDU1MjAyLGJyb3duOjEwODI0MjM0LGJ1cmx5d29vZDoxNDU5NjIzMSxjYWRldGJsdWU6NjI2NjUyOCxjaGFydHJldXNlOjgzODgzNTIsY2hvY29sYXRlOjEzNzg5NDcwLGNvcmFsOjE2NzQ0MjcyLGNvcm5mbG93ZXJibHVlOjY1OTE5ODEsY29ybnNpbGs6MTY3NzUzODgsY3JpbXNvbjoxNDQyMzEwMCxjeWFuOjY1NTM1LGRhcmtibHVlOjEzOSxkYXJrY3lhbjozNTcyMyxkYXJrZ29sZGVucm9kOjEyMDkyOTM5LGRhcmtncmF5OjExMTE5MDE3LGRhcmtncmVlbjoyNTYwMCxkYXJrZ3JleToxMTExOTAxNyxkYXJra2hha2k6MTI0MzMyNTksZGFya21hZ2VudGE6OTEwOTY0MyxkYXJrb2xpdmVncmVlbjo1NTk3OTk5LGRhcmtvcmFuZ2U6MTY3NDc1MjAsZGFya29yY2hpZDoxMDA0MDAxMixkYXJrcmVkOjkxMDk1MDQsZGFya3NhbG1vbjoxNTMwODQxMCxkYXJrc2VhZ3JlZW46OTQxOTkxOSxkYXJrc2xhdGVibHVlOjQ3MzQzNDcsZGFya3NsYXRlZ3JheTozMTAwNDk1LGRhcmtzbGF0ZWdyZXk6MzEwMDQ5NSxkYXJrdHVycXVvaXNlOjUyOTQ1LGRhcmt2aW9sZXQ6OTY5OTUzOSxkZWVwcGluazoxNjcxNjk0NyxkZWVwc2t5Ymx1ZTo0OTE1MSxkaW1ncmF5OjY5MDgyNjUsZGltZ3JleTo2OTA4MjY1LGRvZGdlcmJsdWU6MjAwMzE5OSxmaXJlYnJpY2s6MTE2NzQxNDYsZmxvcmFsd2hpdGU6MTY3NzU5MjAsZm9yZXN0Z3JlZW46MjI2Mzg0MixmdWNoc2lhOjE2NzExOTM1LGdhaW5zYm9ybzoxNDQ3NDQ2MCxnaG9zdHdoaXRlOjE2MzE2NjcxLGdvbGQ6MTY3NjY3MjAsZ29sZGVucm9kOjE0MzI5MTIwLGdyYXk6ODQyMTUwNCxncmVlbjozMjc2OCxncmVlbnllbGxvdzoxMTQwMzA1NSxncmV5Ojg0MjE1MDQsaG9uZXlkZXc6MTU3OTQxNjAsaG90cGluazoxNjczODc0MCxpbmRpYW5yZWQ6MTM0NTg1MjQsaW5kaWdvOjQ5MTUzMzAsaXZvcnk6MTY3NzcyMDAsa2hha2k6MTU3ODc2NjAsbGF2ZW5kZXI6MTUxMzI0MTAsbGF2ZW5kZXJibHVzaDoxNjc3MzM2NSxsYXduZ3JlZW46ODE5MDk3NixsZW1vbmNoaWZmb246MTY3NzU4ODUsbGlnaHRibHVlOjExMzkzMjU0LGxpZ2h0Y29yYWw6MTU3NjE1MzYsbGlnaHRjeWFuOjE0NzQ1NTk5LGxpZ2h0Z29sZGVucm9keWVsbG93OjE2NDQ4MjEwLGxpZ2h0Z3JheToxMzg4MjMyMyxsaWdodGdyZWVuOjk0OTgyNTYsbGlnaHRncmV5OjEzODgyMzIzLGxpZ2h0cGluazoxNjc1ODQ2NSxsaWdodHNhbG1vbjoxNjc1Mjc2MixsaWdodHNlYWdyZWVuOjIxNDI4OTAsbGlnaHRza3libHVlOjg5MDAzNDYsbGlnaHRzbGF0ZWdyYXk6NzgzMzc1MyxsaWdodHNsYXRlZ3JleTo3ODMzNzUzLGxpZ2h0c3RlZWxibHVlOjExNTg0NzM0LGxpZ2h0eWVsbG93OjE2Nzc3MTg0LGxpbWU6NjUyODAsbGltZWdyZWVuOjMzMjkzMzAsbGluZW46MTY0NDU2NzAsbWFnZW50YToxNjcxMTkzNSxtYXJvb246ODM4ODYwOCxtZWRpdW1hcXVhbWFyaW5lOjY3MzczMjIsbWVkaXVtYmx1ZToyMDUsbWVkaXVtb3JjaGlkOjEyMjExNjY3LG1lZGl1bXB1cnBsZTo5NjYyNjgzLG1lZGl1bXNlYWdyZWVuOjM5NzgwOTcsbWVkaXVtc2xhdGVibHVlOjgwODc3OTAsbWVkaXVtc3ByaW5nZ3JlZW46NjQxNTQsbWVkaXVtdHVycXVvaXNlOjQ3NzIzMDAsbWVkaXVtdmlvbGV0cmVkOjEzMDQ3MTczLG1pZG5pZ2h0Ymx1ZToxNjQ0OTEyLG1pbnRjcmVhbToxNjEyMTg1MCxtaXN0eXJvc2U6MTY3NzAyNzMsbW9jY2FzaW46MTY3NzAyMjksbmF2YWpvd2hpdGU6MTY3Njg2ODUsbmF2eToxMjgsb2xkbGFjZToxNjY0MzU1OCxvbGl2ZTo4NDIxMzc2LG9saXZlZHJhYjo3MDQ4NzM5LG9yYW5nZToxNjc1MzkyMCxvcmFuZ2VyZWQ6MTY3MjkzNDQsb3JjaGlkOjE0MzE1NzM0LHBhbGVnb2xkZW5yb2Q6MTU2NTcxMzAscGFsZWdyZWVuOjEwMDI1ODgwLHBhbGV0dXJxdW9pc2U6MTE1Mjk5NjYscGFsZXZpb2xldHJlZDoxNDM4MTIwMyxwYXBheWF3aGlwOjE2NzczMDc3LHBlYWNocHVmZjoxNjc2NzY3MyxwZXJ1OjEzNDY4OTkxLHBpbms6MTY3NjEwMzUscGx1bToxNDUyNDYzNyxwb3dkZXJibHVlOjExNTkxOTEwLHB1cnBsZTo4Mzg4NzM2LHJlYmVjY2FwdXJwbGU6NjY5Nzg4MSxyZWQ6MTY3MTE2ODAscm9zeWJyb3duOjEyMzU3NTE5LHJveWFsYmx1ZTo0Mjg2OTQ1LHNhZGRsZWJyb3duOjkxMjcxODcsc2FsbW9uOjE2NDE2ODgyLHNhbmR5YnJvd246MTYwMzI4NjQsc2VhZ3JlZW46MzA1MDMyNyxzZWFzaGVsbDoxNjc3NDYzOCxzaWVubmE6MTA1MDY3OTcsc2lsdmVyOjEyNjMyMjU2LHNreWJsdWU6ODkwMDMzMSxzbGF0ZWJsdWU6Njk3MDA2MSxzbGF0ZWdyYXk6NzM3Mjk0NCxzbGF0ZWdyZXk6NzM3Mjk0NCxzbm93OjE2Nzc1OTMwLHNwcmluZ2dyZWVuOjY1NDA3LHN0ZWVsYmx1ZTo0NjIwOTgwLHRhbjoxMzgwODc4MCx0ZWFsOjMyODk2LHRoaXN0bGU6MTQyMDQ4ODgsdG9tYXRvOjE2NzM3MDk1LHR1cnF1b2lzZTo0MjUxODU2LHZpb2xldDoxNTYzMTA4Nix3aGVhdDoxNjExMzMzMSx3aGl0ZToxNjc3NzIxNSx3aGl0ZXNtb2tlOjE2MTE5Mjg1LHllbGxvdzoxNjc3Njk2MCx5ZWxsb3dncmVlbjoxMDE0NTA3NH07ZHooaEEsVmcse2NvcHk6ZnVuY3Rpb24oZSl7cmV0dXJuIE9iamVjdC5hc3NpZ24obmV3IHRoaXMuY29uc3RydWN0b3IsdGhpcyxlKX0sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5yZ2IoKS5kaXNwbGF5YWJsZSgpfSxoZXg6cnF0LGZvcm1hdEhleDpycXQsZm9ybWF0SHNsOlo4ZSxmb3JtYXRSZ2I6bnF0LHRvU3RyaW5nOm5xdH0pO2R6KG1sLEt3LGpydChoQSx7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9nejpNYXRoLnBvdyhneixlKSxuZXcgbWwodGhpcy5yKmUsdGhpcy5nKmUsdGhpcy5iKmUsdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/Y0E6TWF0aC5wb3coY0EsZSksbmV3IG1sKHRoaXMuciplLHRoaXMuZyplLHRoaXMuYiplLHRoaXMub3BhY2l0eSl9LHJnYjpmdW5jdGlvbigpe3JldHVybiB0aGlzfSxkaXNwbGF5YWJsZTpmdW5jdGlvbigpe3JldHVybi0uNTw9dGhpcy5yJiZ0aGlzLnI8MjU1LjUmJi0uNTw9dGhpcy5nJiZ0aGlzLmc8MjU1LjUmJi0uNTw9dGhpcy5iJiZ0aGlzLmI8MjU1LjUmJjA8PXRoaXMub3BhY2l0eSYmdGhpcy5vcGFjaXR5PD0xfSxoZXg6b3F0LGZvcm1hdEhleDpvcXQsZm9ybWF0UmdiOmFxdCx0b1N0cmluZzphcXR9KSk7ZHooRWYsY3F0LGpydChoQSx7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9nejpNYXRoLnBvdyhneixlKSxuZXcgRWYodGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9jQTpNYXRoLnBvdyhjQSxlKSxuZXcgRWYodGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLmglMzYwKyh0aGlzLmg8MCkqMzYwLHQ9aXNOYU4oZSl8fGlzTmFOKHRoaXMucyk/MDp0aGlzLnMscj10aGlzLmwsbj1yKyhyPC41P3I6MS1yKSp0LGk9MipyLW47cmV0dXJuIG5ldyBtbCgkcnQoZT49MjQwP2UtMjQwOmUrMTIwLGksbiksJHJ0KGUsaSxuKSwkcnQoZTwxMjA/ZSsyNDA6ZS0xMjAsaSxuKSx0aGlzLm9wYWNpdHkpfSxkaXNwbGF5YWJsZTpmdW5jdGlvbigpe3JldHVybigwPD10aGlzLnMmJnRoaXMuczw9MXx8aXNOYU4odGhpcy5zKSkmJjA8PXRoaXMubCYmdGhpcy5sPD0xJiYwPD10aGlzLm9wYWNpdHkmJnRoaXMub3BhY2l0eTw9MX0sZm9ybWF0SHNsOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5vcGFjaXR5O3JldHVybiBlPWlzTmFOKGUpPzE6TWF0aC5tYXgoMCxNYXRoLm1pbigxLGUpKSwoZT09PTE/ImhzbCgiOiJoc2xhKCIpKyh0aGlzLmh8fDApKyIsICIrKHRoaXMuc3x8MCkqMTAwKyIlLCAiKyh0aGlzLmx8fDApKjEwMCsiJSIrKGU9PT0xPyIpIjoiLCAiK2UrIikiKX19KSl9KTt2YXIgS3J0PU0oKCk9Pnt1cXQoKX0pO2Z1bmN0aW9uIFpydChlLHQscixuLGkpe3ZhciBvPWUqZSxhPW8qZTtyZXR1cm4oKDEtMyplKzMqby1hKSp0Kyg0LTYqbyszKmEpKnIrKDErMyplKzMqby0zKmEpKm4rYSppKS82fWZ1bmN0aW9uIGhxdChlKXt2YXIgdD1lLmxlbmd0aC0xO3JldHVybiBmdW5jdGlvbihyKXt2YXIgbj1yPD0wP3I9MDpyPj0xPyhyPTEsdC0xKTpNYXRoLmZsb29yKHIqdCksaT1lW25dLG89ZVtuKzFdLGE9bj4wP2Vbbi0xXToyKmktbyxzPW48dC0xP2VbbisyXToyKm8taTtyZXR1cm4gWnJ0KChyLW4vdCkqdCxhLGksbyxzKX19dmFyIEpydD1NKCgpPT57fSk7ZnVuY3Rpb24gZnF0KGUpe3ZhciB0PWUubGVuZ3RoO3JldHVybiBmdW5jdGlvbihyKXt2YXIgbj1NYXRoLmZsb29yKCgociU9MSk8MD8rK3I6cikqdCksaT1lWyhuK3QtMSkldF0sbz1lW24ldF0sYT1lWyhuKzEpJXRdLHM9ZVsobisyKSV0XTtyZXR1cm4gWnJ0KChyLW4vdCkqdCxpLG8sYSxzKX19dmFyIHBxdD1NKCgpPT57SnJ0KCl9KTtmdW5jdGlvbiBRcnQoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fXZhciBkcXQ9TSgoKT0+e30pO2Z1bmN0aW9uIFE4ZShlLHQpe3JldHVybiBmdW5jdGlvbihyKXtyZXR1cm4gZStyKnR9fWZ1bmN0aW9uIHRSZShlLHQscil7cmV0dXJuIGU9TWF0aC5wb3coZSxyKSx0PU1hdGgucG93KHQsciktZSxyPTEvcixmdW5jdGlvbihuKXtyZXR1cm4gTWF0aC5wb3coZStuKnQscil9fWZ1bmN0aW9uIG1xdChlKXtyZXR1cm4oZT0rZSk9PTE/X3o6ZnVuY3Rpb24odCxyKXtyZXR1cm4gci10P3RSZSh0LHIsZSk6UXJ0KGlzTmFOKHQpP3I6dCl9fWZ1bmN0aW9uIF96KGUsdCl7dmFyIHI9dC1lO3JldHVybiByP1E4ZShlLHIpOlFydChpc05hTihlKT90OmUpfXZhciBncXQ9TSgoKT0+e2RxdCgpfSk7ZnVuY3Rpb24gX3F0KGUpe3JldHVybiBmdW5jdGlvbih0KXt2YXIgcj10Lmxlbmd0aCxuPW5ldyBBcnJheShyKSxpPW5ldyBBcnJheShyKSxvPW5ldyBBcnJheShyKSxhLHM7Zm9yKGE9MDthPHI7KythKXM9S3codFthXSksblthXT1zLnJ8fDAsaVthXT1zLmd8fDAsb1thXT1zLmJ8fDA7cmV0dXJuIG49ZShuKSxpPWUoaSksbz1lKG8pLHMub3BhY2l0eT0xLGZ1bmN0aW9uKGwpe3JldHVybiBzLnI9bihsKSxzLmc9aShsKSxzLmI9byhsKSxzKyIifX19dmFyIHl6LGVSZSxyUmUseXF0PU0oKCk9PntLcnQoKTtKcnQoKTtwcXQoKTtncXQoKTt5ej1mdW5jdGlvbiBlKHQpe3ZhciByPW1xdCh0KTtmdW5jdGlvbiBuKGksbyl7dmFyIGE9cigoaT1LdyhpKSkuciwobz1LdyhvKSkucikscz1yKGkuZyxvLmcpLGw9cihpLmIsby5iKSxjPV96KGkub3BhY2l0eSxvLm9wYWNpdHkpO3JldHVybiBmdW5jdGlvbih1KXtyZXR1cm4gaS5yPWEodSksaS5nPXModSksaS5iPWwodSksaS5vcGFjaXR5PWModSksaSsiIn19cmV0dXJuIG4uZ2FtbWE9ZSxufSgxKTtlUmU9X3F0KGhxdCksclJlPV9xdChmcXQpfSk7ZnVuY3Rpb24gUGMoZSx0KXtyZXR1cm4gZT0rZSx0PSt0LGZ1bmN0aW9uKHIpe3JldHVybiBlKigxLXIpK3Qqcn19dmFyIHZ6PU0oKCk9Pnt9KTtmdW5jdGlvbiBuUmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fWZ1bmN0aW9uIGlSZShlKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuIGUodCkrIiJ9fWZ1bmN0aW9uIHJudChlLHQpe3ZhciByPWVudC5sYXN0SW5kZXg9dG50Lmxhc3RJbmRleD0wLG4saSxvLGE9LTEscz1bXSxsPVtdO2ZvcihlPWUrIiIsdD10KyIiOyhuPWVudC5leGVjKGUpKSYmKGk9dG50LmV4ZWModCkpOykobz1pLmluZGV4KT5yJiYobz10LnNsaWNlKHIsbyksc1thXT9zW2FdKz1vOnNbKythXT1vKSwobj1uWzBdKT09PShpPWlbMF0pP3NbYV0/c1thXSs9aTpzWysrYV09aTooc1srK2FdPW51bGwsbC5wdXNoKHtpOmEseDpQYyhuLGkpfSkpLHI9dG50Lmxhc3RJbmRleDtyZXR1cm4gcjx0Lmxlbmd0aCYmKG89dC5zbGljZShyKSxzW2FdP3NbYV0rPW86c1srK2FdPW8pLHMubGVuZ3RoPDI/bFswXT9pUmUobFswXS54KTpuUmUodCk6KHQ9bC5sZW5ndGgsZnVuY3Rpb24oYyl7Zm9yKHZhciB1PTAsaDt1PHQ7Kyt1KXNbKGg9bFt1XSkuaV09aC54KGMpO3JldHVybiBzLmpvaW4oIiIpfSl9dmFyIGVudCx0bnQsdnF0PU0oKCk9Pnt2eigpO2VudD0vWy0rXT8oPzpcZCtcLj9cZCp8XC4/XGQrKSg/OltlRV1bLStdP1xkKyk/L2csdG50PW5ldyBSZWdFeHAoZW50LnNvdXJjZSwiZyIpfSk7ZnVuY3Rpb24gbm50KGUsdCxyLG4saSxvKXt2YXIgYSxzLGw7cmV0dXJuKGE9TWF0aC5zcXJ0KGUqZSt0KnQpKSYmKGUvPWEsdC89YSksKGw9ZSpyK3QqbikmJihyLT1lKmwsbi09dCpsKSwocz1NYXRoLnNxcnQocipyK24qbikpJiYoci89cyxuLz1zLGwvPXMpLGUqbjx0KnImJihlPS1lLHQ9LXQsbD0tbCxhPS1hKSx7dHJhbnNsYXRlWDppLHRyYW5zbGF0ZVk6byxyb3RhdGU6TWF0aC5hdGFuMih0LGUpKnhxdCxza2V3WDpNYXRoLmF0YW4obCkqeHF0LHNjYWxlWDphLHNjYWxlWTpzfX12YXIgeHF0LHh6LGJxdD1NKCgpPT57eHF0PTE4MC9NYXRoLlBJLHh6PXt0cmFuc2xhdGVYOjAsdHJhbnNsYXRlWTowLHJvdGF0ZTowLHNrZXdYOjAsc2NhbGVYOjEsc2NhbGVZOjF9fSk7ZnVuY3Rpb24gU3F0KGUpe3JldHVybiBlPT09Im5vbmUiP3h6OihmQXx8KGZBPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkRJViIpLGludD1kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsd3F0PWRvY3VtZW50LmRlZmF1bHRWaWV3KSxmQS5zdHlsZS50cmFuc2Zvcm09ZSxlPXdxdC5nZXRDb21wdXRlZFN0eWxlKGludC5hcHBlbmRDaGlsZChmQSksbnVsbCkuZ2V0UHJvcGVydHlWYWx1ZSgidHJhbnNmb3JtIiksaW50LnJlbW92ZUNoaWxkKGZBKSxlPWUuc2xpY2UoNywtMSkuc3BsaXQoIiwiKSxubnQoK2VbMF0sK2VbMV0sK2VbMl0sK2VbM10sK2VbNF0sK2VbNV0pKX1mdW5jdGlvbiBNcXQoZSl7cmV0dXJuIGU9PW51bGw/eHo6KGJ6fHwoYno9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIsImciKSksYnouc2V0QXR0cmlidXRlKCJ0cmFuc2Zvcm0iLGUpLChlPWJ6LnRyYW5zZm9ybS5iYXNlVmFsLmNvbnNvbGlkYXRlKCkpPyhlPWUubWF0cml4LG5udChlLmEsZS5iLGUuYyxlLmQsZS5lLGUuZikpOnh6KX12YXIgZkEsaW50LHdxdCxieixFcXQ9TSgoKT0+e2JxdCgpfSk7ZnVuY3Rpb24gVHF0KGUsdCxyLG4pe2Z1bmN0aW9uIGkoYyl7cmV0dXJuIGMubGVuZ3RoP2MucG9wKCkrIiAiOiIifWZ1bmN0aW9uIG8oYyx1LGgsZixwLGQpe2lmKGMhPT1ofHx1IT09Zil7dmFyIGc9cC5wdXNoKCJ0cmFuc2xhdGUoIixudWxsLHQsbnVsbCxyKTtkLnB1c2goe2k6Zy00LHg6UGMoYyxoKX0se2k6Zy0yLHg6UGModSxmKX0pfWVsc2UoaHx8ZikmJnAucHVzaCgidHJhbnNsYXRlKCIraCt0K2Yrcil9ZnVuY3Rpb24gYShjLHUsaCxmKXtjIT09dT8oYy11PjE4MD91Kz0zNjA6dS1jPjE4MCYmKGMrPTM2MCksZi5wdXNoKHtpOmgucHVzaChpKGgpKyJyb3RhdGUoIixudWxsLG4pLTIseDpQYyhjLHUpfSkpOnUmJmgucHVzaChpKGgpKyJyb3RhdGUoIit1K24pfWZ1bmN0aW9uIHMoYyx1LGgsZil7YyE9PXU/Zi5wdXNoKHtpOmgucHVzaChpKGgpKyJza2V3WCgiLG51bGwsbiktMix4OlBjKGMsdSl9KTp1JiZoLnB1c2goaShoKSsic2tld1goIit1K24pfWZ1bmN0aW9uIGwoYyx1LGgsZixwLGQpe2lmKGMhPT1ofHx1IT09Zil7dmFyIGc9cC5wdXNoKGkocCkrInNjYWxlKCIsbnVsbCwiLCIsbnVsbCwiKSIpO2QucHVzaCh7aTpnLTQseDpQYyhjLGgpfSx7aTpnLTIseDpQYyh1LGYpfSl9ZWxzZShoIT09MXx8ZiE9PTEpJiZwLnB1c2goaShwKSsic2NhbGUoIitoKyIsIitmKyIpIil9cmV0dXJuIGZ1bmN0aW9uKGMsdSl7dmFyIGg9W10sZj1bXTtyZXR1cm4gYz1lKGMpLHU9ZSh1KSxvKGMudHJhbnNsYXRlWCxjLnRyYW5zbGF0ZVksdS50cmFuc2xhdGVYLHUudHJhbnNsYXRlWSxoLGYpLGEoYy5yb3RhdGUsdS5yb3RhdGUsaCxmKSxzKGMuc2tld1gsdS5za2V3WCxoLGYpLGwoYy5zY2FsZVgsYy5zY2FsZVksdS5zY2FsZVgsdS5zY2FsZVksaCxmKSxjPXU9bnVsbCxmdW5jdGlvbihwKXtmb3IodmFyIGQ9LTEsZz1mLmxlbmd0aCxfOysrZDxnOyloWyhfPWZbZF0pLmldPV8ueChwKTtyZXR1cm4gaC5qb2luKCIiKX19fXZhciBvbnQsYW50LENxdD1NKCgpPT57dnooKTtFcXQoKTtvbnQ9VHF0KFNxdCwicHgsICIsInB4KSIsImRlZykiKSxhbnQ9VHF0KE1xdCwiLCAiLCIpIiwiKSIpfSk7dmFyIHd6PU0oKCk9Pnt2eigpO3ZxdCgpO0NxdCgpO3lxdCgpfSk7ZnVuY3Rpb24gb1JlKGUsdCl7dmFyIHIsbjtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgaT1EdSh0aGlzLGUpLG89aS50d2VlbjtpZihvIT09cil7bj1yPW87Zm9yKHZhciBhPTAscz1uLmxlbmd0aDthPHM7KythKWlmKG5bYV0ubmFtZT09PXQpe249bi5zbGljZSgpLG4uc3BsaWNlKGEsMSk7YnJlYWt9fWkudHdlZW49bn19ZnVuY3Rpb24gYVJlKGUsdCxyKXt2YXIgbixpO2lmKHR5cGVvZiByIT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbz1EdSh0aGlzLGUpLGE9by50d2VlbjtpZihhIT09bil7aT0obj1hKS5zbGljZSgpO2Zvcih2YXIgcz17bmFtZTp0LHZhbHVlOnJ9LGw9MCxjPWkubGVuZ3RoO2w8YzsrK2wpaWYoaVtsXS5uYW1lPT09dCl7aVtsXT1zO2JyZWFrfWw9PT1jJiZpLnB1c2gocyl9by50d2Vlbj1pfX1mdW5jdGlvbiBBcXQoZSx0KXt2YXIgcj10aGlzLl9pZDtpZihlKz0iIixhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcih2YXIgbj1pbyh0aGlzLm5vZGUoKSxyKS50d2VlbixpPTAsbz1uLmxlbmd0aCxhO2k8bzsrK2kpaWYoKGE9bltpXSkubmFtZT09PWUpcmV0dXJuIGEudmFsdWU7cmV0dXJuIG51bGx9cmV0dXJuIHRoaXMuZWFjaCgodD09bnVsbD9vUmU6YVJlKShyLGUsdCkpfWZ1bmN0aW9uIFp3KGUsdCxyKXt2YXIgbj1lLl9pZDtyZXR1cm4gZS5lYWNoKGZ1bmN0aW9uKCl7dmFyIGk9RHUodGhpcyxuKTsoaS52YWx1ZXx8KGkudmFsdWU9e30pKVt0XT1yLmFwcGx5KHRoaXMsYXJndW1lbnRzKX0pLGZ1bmN0aW9uKGkpe3JldHVybiBpbyhpLG4pLnZhbHVlW3RdfX12YXIgcEE9TSgoKT0+e0FjKCl9KTtmdW5jdGlvbiBTeihlLHQpe3ZhciByO3JldHVybih0eXBlb2YgdD09Im51bWJlciI/UGM6dCBpbnN0YW5jZW9mIFZnP3l6OihyPVZnKHQpKT8odD1yLHl6KTpybnQpKGUsdCl9dmFyIHNudD1NKCgpPT57S3J0KCk7d3ooKX0pO2Z1bmN0aW9uIHNSZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnJlbW92ZUF0dHJpYnV0ZShlKX19ZnVuY3Rpb24gbFJlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKX19ZnVuY3Rpb24gY1JlKGUsdCxyKXt2YXIgbixpO3JldHVybiBmdW5jdGlvbigpe3ZhciBvPXRoaXMuZ2V0QXR0cmlidXRlKGUpO3JldHVybiBvPT09cj9udWxsOm89PT1uP2k6aT10KG49byxyKX19ZnVuY3Rpb24gdVJlKGUsdCxyKXt2YXIgbixpO3JldHVybiBmdW5jdGlvbigpe3ZhciBvPXRoaXMuZ2V0QXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKTtyZXR1cm4gbz09PXI/bnVsbDpvPT09bj9pOmk9dChuPW8scil9fWZ1bmN0aW9uIGhSZShlLHQscil7dmFyIG4saSxvO3JldHVybiBmdW5jdGlvbigpe3ZhciBhLHM9cih0aGlzKTtyZXR1cm4gcz09bnVsbD92b2lkIHRoaXMucmVtb3ZlQXR0cmlidXRlKGUpOihhPXRoaXMuZ2V0QXR0cmlidXRlKGUpLGE9PT1zP251bGw6YT09PW4mJnM9PT1pP286bz10KG49YSxpPXMpKX19ZnVuY3Rpb24gZlJlKGUsdCxyKXt2YXIgbixpLG87cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGEscz1yKHRoaXMpO3JldHVybiBzPT1udWxsP3ZvaWQgdGhpcy5yZW1vdmVBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpOihhPXRoaXMuZ2V0QXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKSxhPT09cz9udWxsOmE9PT1uJiZzPT09aT9vOm89dChuPWEsaT1zKSl9fWZ1bmN0aW9uIFBxdChlLHQpe3ZhciByPWxkKGUpLG49cj09PSJ0cmFuc2Zvcm0iP2FudDpTejtyZXR1cm4gdGhpcy5hdHRyVHdlZW4oZSx0eXBlb2YgdD09ImZ1bmN0aW9uIj8oci5sb2NhbD9mUmU6aFJlKShyLG4sWncodGhpcywiYXR0ci4iK2UsdCkpOnQ9PW51bGw/KHIubG9jYWw/bFJlOnNSZSkocik6KHIubG9jYWw/dVJlOmNSZSkocixuLHQrIiIpKX12YXIgSXF0PU0oKCk9Pnt3eigpO01mKCk7cEEoKTtzbnQoKX0pO2Z1bmN0aW9uIHBSZShlLHQpe2Z1bmN0aW9uIHIoKXt2YXIgbj10aGlzLGk9dC5hcHBseShuLGFyZ3VtZW50cyk7cmV0dXJuIGkmJmZ1bmN0aW9uKG8pe24uc2V0QXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsLGkobykpfX1yZXR1cm4gci5fdmFsdWU9dCxyfWZ1bmN0aW9uIGRSZShlLHQpe2Z1bmN0aW9uIHIoKXt2YXIgbj10aGlzLGk9dC5hcHBseShuLGFyZ3VtZW50cyk7cmV0dXJuIGkmJmZ1bmN0aW9uKG8pe24uc2V0QXR0cmlidXRlKGUsaShvKSl9fXJldHVybiByLl92YWx1ZT10LHJ9ZnVuY3Rpb24gTHF0KGUsdCl7dmFyIHI9ImF0dHIuIitlO2lmKGFyZ3VtZW50cy5sZW5ndGg8MilyZXR1cm4ocj10aGlzLnR3ZWVuKHIpKSYmci5fdmFsdWU7aWYodD09bnVsbClyZXR1cm4gdGhpcy50d2VlbihyLG51bGwpO2lmKHR5cGVvZiB0IT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjt2YXIgbj1sZChlKTtyZXR1cm4gdGhpcy50d2VlbihyLChuLmxvY2FsP3BSZTpkUmUpKG4sdCkpfXZhciBrcXQ9TSgoKT0+e01mKCl9KTtmdW5jdGlvbiBtUmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXtsQSh0aGlzLGUpLmRlbGF5PSt0LmFwcGx5KHRoaXMsYXJndW1lbnRzKX19ZnVuY3Rpb24gZ1JlKGUsdCl7cmV0dXJuIHQ9K3QsZnVuY3Rpb24oKXtsQSh0aGlzLGUpLmRlbGF5PXR9fWZ1bmN0aW9uIFJxdChlKXt2YXIgdD10aGlzLl9pZDtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2goKHR5cGVvZiBlPT0iZnVuY3Rpb24iP21SZTpnUmUpKHQsZSkpOmlvKHRoaXMubm9kZSgpLHQpLmRlbGF5fXZhciBOcXQ9TSgoKT0+e0FjKCl9KTtmdW5jdGlvbiBfUmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXtEdSh0aGlzLGUpLmR1cmF0aW9uPSt0LmFwcGx5KHRoaXMsYXJndW1lbnRzKX19ZnVuY3Rpb24geVJlKGUsdCl7cmV0dXJuIHQ9K3QsZnVuY3Rpb24oKXtEdSh0aGlzLGUpLmR1cmF0aW9uPXR9fWZ1bmN0aW9uIERxdChlKXt2YXIgdD10aGlzLl9pZDtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2goKHR5cGVvZiBlPT0iZnVuY3Rpb24iP19SZTp5UmUpKHQsZSkpOmlvKHRoaXMubm9kZSgpLHQpLmR1cmF0aW9ufXZhciBPcXQ9TSgoKT0+e0FjKCl9KTtmdW5jdGlvbiB2UmUoZSx0KXtpZih0eXBlb2YgdCE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3I7cmV0dXJuIGZ1bmN0aW9uKCl7RHUodGhpcyxlKS5lYXNlPXR9fWZ1bmN0aW9uIHpxdChlKXt2YXIgdD10aGlzLl9pZDtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2godlJlKHQsZSkpOmlvKHRoaXMubm9kZSgpLHQpLmVhc2V9dmFyIEZxdD1NKCgpPT57QWMoKX0pO2Z1bmN0aW9uIEJxdChlKXt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9UUMoZSkpO2Zvcih2YXIgdD10aGlzLl9ncm91cHMscj10Lmxlbmd0aCxuPW5ldyBBcnJheShyKSxpPTA7aTxyOysraSlmb3IodmFyIG89dFtpXSxhPW8ubGVuZ3RoLHM9bltpXT1bXSxsLGM9MDtjPGE7KytjKShsPW9bY10pJiZlLmNhbGwobCxsLl9fZGF0YV9fLGMsbykmJnMucHVzaChsKTtyZXR1cm4gbmV3IFVvKG4sdGhpcy5fcGFyZW50cyx0aGlzLl9uYW1lLHRoaXMuX2lkKX12YXIgSHF0PU0oKCk9PntNZigpO3VkKCl9KTtmdW5jdGlvbiBWcXQoZSl7aWYoZS5faWQhPT10aGlzLl9pZCl0aHJvdyBuZXcgRXJyb3I7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPWUuX2dyb3VwcyxuPXQubGVuZ3RoLGk9ci5sZW5ndGgsbz1NYXRoLm1pbihuLGkpLGE9bmV3IEFycmF5KG4pLHM9MDtzPG87KytzKWZvcih2YXIgbD10W3NdLGM9cltzXSx1PWwubGVuZ3RoLGg9YVtzXT1uZXcgQXJyYXkodSksZixwPTA7cDx1OysrcCkoZj1sW3BdfHxjW3BdKSYmKGhbcF09Zik7Zm9yKDtzPG47KytzKWFbc109dFtzXTtyZXR1cm4gbmV3IFVvKGEsdGhpcy5fcGFyZW50cyx0aGlzLl9uYW1lLHRoaXMuX2lkKX12YXIgVXF0PU0oKCk9Pnt1ZCgpfSk7ZnVuY3Rpb24geFJlKGUpe3JldHVybihlKyIiKS50cmltKCkuc3BsaXQoL158XHMrLykuZXZlcnkoZnVuY3Rpb24odCl7dmFyIHI9dC5pbmRleE9mKCIuIik7cmV0dXJuIHI+PTAmJih0PXQuc2xpY2UoMCxyKSksIXR8fHQ9PT0ic3RhcnQifSl9ZnVuY3Rpb24gYlJlKGUsdCxyKXt2YXIgbixpLG89eFJlKHQpP2xBOkR1O3JldHVybiBmdW5jdGlvbigpe3ZhciBhPW8odGhpcyxlKSxzPWEub247cyE9PW4mJihpPShuPXMpLmNvcHkoKSkub24odCxyKSxhLm9uPWl9fWZ1bmN0aW9uIHFxdChlLHQpe3ZhciByPXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoPDI/aW8odGhpcy5ub2RlKCkscikub24ub24oZSk6dGhpcy5lYWNoKGJSZShyLGUsdCkpfXZhciBHcXQ9TSgoKT0+e0FjKCl9KTtmdW5jdGlvbiB3UmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5wYXJlbnROb2RlO2Zvcih2YXIgciBpbiB0aGlzLl9fdHJhbnNpdGlvbilpZigrciE9PWUpcmV0dXJuO3QmJnQucmVtb3ZlQ2hpbGQodGhpcyl9fWZ1bmN0aW9uIFdxdCgpe3JldHVybiB0aGlzLm9uKCJlbmQucmVtb3ZlIix3UmUodGhpcy5faWQpKX12YXIgWXF0PU0oKCk9Pnt9KTtmdW5jdGlvbiBqcXQoZSl7dmFyIHQ9dGhpcy5fbmFtZSxyPXRoaXMuX2lkO3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT13MShlKSk7Zm9yKHZhciBuPXRoaXMuX2dyb3VwcyxpPW4ubGVuZ3RoLG89bmV3IEFycmF5KGkpLGE9MDthPGk7KythKWZvcih2YXIgcz1uW2FdLGw9cy5sZW5ndGgsYz1vW2FdPW5ldyBBcnJheShsKSx1LGgsZj0wO2Y8bDsrK2YpKHU9c1tmXSkmJihoPWUuY2FsbCh1LHUuX19kYXRhX18sZixzKSkmJigiX19kYXRhX18iaW4gdSYmKGguX19kYXRhX189dS5fX2RhdGFfXyksY1tmXT1oLEhnKGNbZl0sdCxyLGYsYyxpbyh1LHIpKSk7cmV0dXJuIG5ldyBVbyhvLHRoaXMuX3BhcmVudHMsdCxyKX12YXIgWHF0PU0oKCk9PntNZigpO3VkKCk7QWMoKX0pO2Z1bmN0aW9uICRxdChlKXt2YXIgdD10aGlzLl9uYW1lLHI9dGhpcy5faWQ7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPUpDKGUpKTtmb3IodmFyIG49dGhpcy5fZ3JvdXBzLGk9bi5sZW5ndGgsbz1bXSxhPVtdLHM9MDtzPGk7KytzKWZvcih2YXIgbD1uW3NdLGM9bC5sZW5ndGgsdSxoPTA7aDxjOysraClpZih1PWxbaF0pe2Zvcih2YXIgZj1lLmNhbGwodSx1Ll9fZGF0YV9fLGgsbCkscCxkPWlvKHUsciksZz0wLF89Zi5sZW5ndGg7ZzxfOysrZykocD1mW2ddKSYmSGcocCx0LHIsZyxmLGQpO28ucHVzaChmKSxhLnB1c2godSl9cmV0dXJuIG5ldyBVbyhvLGEsdCxyKX12YXIgS3F0PU0oKCk9PntNZigpO3VkKCk7QWMoKX0pO2Z1bmN0aW9uIFpxdCgpe3JldHVybiBuZXcgU1JlKHRoaXMuX2dyb3Vwcyx0aGlzLl9wYXJlbnRzKX12YXIgU1JlLEpxdD1NKCgpPT57TWYoKTtTUmU9Y2QucHJvdG90eXBlLmNvbnN0cnVjdG9yfSk7ZnVuY3Rpb24gTVJlKGUsdCl7dmFyIHIsbixpO3JldHVybiBmdW5jdGlvbigpe3ZhciBvPUJnKHRoaXMsZSksYT0odGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShlKSxCZyh0aGlzLGUpKTtyZXR1cm4gbz09PWE/bnVsbDpvPT09ciYmYT09PW4/aTppPXQocj1vLG49YSl9fWZ1bmN0aW9uIEVSZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KGUpfX1mdW5jdGlvbiBUUmUoZSx0LHIpe3ZhciBuLGk7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIG89QmcodGhpcyxlKTtyZXR1cm4gbz09PXI/bnVsbDpvPT09bj9pOmk9dChuPW8scil9fWZ1bmN0aW9uIENSZShlLHQscil7dmFyIG4saSxvO3JldHVybiBmdW5jdGlvbigpe3ZhciBhPUJnKHRoaXMsZSkscz1yKHRoaXMpO3JldHVybiBzPT1udWxsJiYocz0odGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShlKSxCZyh0aGlzLGUpKSksYT09PXM/bnVsbDphPT09biYmcz09PWk/bzpvPXQobj1hLGk9cyl9fWZ1bmN0aW9uIFFxdChlLHQscil7dmFyIG49KGUrPSIiKT09InRyYW5zZm9ybSI/b250OlN6O3JldHVybiB0PT1udWxsP3RoaXMuc3R5bGVUd2VlbihlLE1SZShlLG4pKS5vbigiZW5kLnN0eWxlLiIrZSxFUmUoZSkpOnRoaXMuc3R5bGVUd2VlbihlLHR5cGVvZiB0PT0iZnVuY3Rpb24iP0NSZShlLG4sWncodGhpcywic3R5bGUuIitlLHQpKTpUUmUoZSxuLHQrIiIpLHIpfXZhciB0R3Q9TSgoKT0+e3d6KCk7TWYoKTtwQSgpO3NudCgpfSk7ZnVuY3Rpb24gQVJlKGUsdCxyKXtmdW5jdGlvbiBuKCl7dmFyIGk9dGhpcyxvPXQuYXBwbHkoaSxhcmd1bWVudHMpO3JldHVybiBvJiZmdW5jdGlvbihhKXtpLnN0eWxlLnNldFByb3BlcnR5KGUsbyhhKSxyKX19cmV0dXJuIG4uX3ZhbHVlPXQsbn1mdW5jdGlvbiBlR3QoZSx0LHIpe3ZhciBuPSJzdHlsZS4iKyhlKz0iIik7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXJldHVybihuPXRoaXMudHdlZW4obikpJiZuLl92YWx1ZTtpZih0PT1udWxsKXJldHVybiB0aGlzLnR3ZWVuKG4sbnVsbCk7aWYodHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3JldHVybiB0aGlzLnR3ZWVuKG4sQVJlKGUsdCxyPT1udWxsPyIiOnIpKX12YXIgckd0PU0oKCk9Pnt9KTtmdW5jdGlvbiBQUmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy50ZXh0Q29udGVudD1lfX1mdW5jdGlvbiBJUmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9ZSh0aGlzKTt0aGlzLnRleHRDb250ZW50PXQ9PW51bGw/IiI6dH19ZnVuY3Rpb24gbkd0KGUpe3JldHVybiB0aGlzLnR3ZWVuKCJ0ZXh0Iix0eXBlb2YgZT09ImZ1bmN0aW9uIj9JUmUoWncodGhpcywidGV4dCIsZSkpOlBSZShlPT1udWxsPyIiOmUrIiIpKX12YXIgaUd0PU0oKCk9PntwQSgpfSk7ZnVuY3Rpb24gb0d0KCl7Zm9yKHZhciBlPXRoaXMuX25hbWUsdD10aGlzLl9pZCxyPU16KCksbj10aGlzLl9ncm91cHMsaT1uLmxlbmd0aCxvPTA7bzxpOysrbylmb3IodmFyIGE9bltvXSxzPWEubGVuZ3RoLGwsYz0wO2M8czsrK2MpaWYobD1hW2NdKXt2YXIgdT1pbyhsLHQpO0hnKGwsZSxyLGMsYSx7dGltZTp1LnRpbWUrdS5kZWxheSt1LmR1cmF0aW9uLGRlbGF5OjAsZHVyYXRpb246dS5kdXJhdGlvbixlYXNlOnUuZWFzZX0pfXJldHVybiBuZXcgVW8obix0aGlzLl9wYXJlbnRzLGUscil9dmFyIGFHdD1NKCgpPT57dWQoKTtBYygpfSk7ZnVuY3Rpb24gVW8oZSx0LHIsbil7dGhpcy5fZ3JvdXBzPWUsdGhpcy5fcGFyZW50cz10LHRoaXMuX25hbWU9cix0aGlzLl9pZD1ufWZ1bmN0aW9uIEV6KGUpe3JldHVybiBjZCgpLnRyYW5zaXRpb24oZSl9ZnVuY3Rpb24gTXooKXtyZXR1cm4rK0xSZX12YXIgTFJlLEp3LHVkPU0oKCk9PntNZigpO0lxdCgpO2txdCgpO05xdCgpO09xdCgpO0ZxdCgpO0hxdCgpO1VxdCgpO0dxdCgpO1lxdCgpO1hxdCgpO0txdCgpO0pxdCgpO3RHdCgpO3JHdCgpO2lHdCgpO2FHdCgpO3BBKCk7TFJlPTA7Snc9Y2QucHJvdG90eXBlO1VvLnByb3RvdHlwZT1Fei5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOlVvLHNlbGVjdDpqcXQsc2VsZWN0QWxsOiRxdCxmaWx0ZXI6QnF0LG1lcmdlOlZxdCxzZWxlY3Rpb246WnF0LHRyYW5zaXRpb246b0d0LGNhbGw6SncuY2FsbCxub2RlczpKdy5ub2Rlcyxub2RlOkp3Lm5vZGUsc2l6ZTpKdy5zaXplLGVtcHR5Okp3LmVtcHR5LGVhY2g6SncuZWFjaCxvbjpxcXQsYXR0cjpQcXQsYXR0clR3ZWVuOkxxdCxzdHlsZTpRcXQsc3R5bGVUd2VlbjplR3QsdGV4dDpuR3QscmVtb3ZlOldxdCx0d2VlbjpBcXQsZGVsYXk6UnF0LGR1cmF0aW9uOkRxdCxlYXNlOnpxdH19KTtmdW5jdGlvbiBrUmUoZSx0KXtmb3IodmFyIHI7IShyPWUuX190cmFuc2l0aW9uKXx8IShyPXJbdF0pOylpZighKGU9ZS5wYXJlbnROb2RlKSlyZXR1cm4gbG50LnRpbWU9WHcoKSxsbnQ7cmV0dXJuIHJ9ZnVuY3Rpb24gc0d0KGUpe3ZhciB0LHI7ZSBpbnN0YW5jZW9mIFVvPyh0PWUuX2lkLGU9ZS5fbmFtZSk6KHQ9TXooKSwocj1sbnQpLnRpbWU9WHcoKSxlPWU9PW51bGw/bnVsbDplKyIiKTtmb3IodmFyIG49dGhpcy5fZ3JvdXBzLGk9bi5sZW5ndGgsbz0wO288aTsrK28pZm9yKHZhciBhPW5bb10scz1hLmxlbmd0aCxsLGM9MDtjPHM7KytjKShsPWFbY10pJiZIZyhsLGUsdCxjLGEscnx8a1JlKGwsdCkpO3JldHVybiBuZXcgVW8obix0aGlzLl9wYXJlbnRzLGUsdCl9dmFyIGxudCxsR3Q9TSgoKT0+e3VkKCk7QWMoKTtJXygpO0dydCgpO2xudD17dGltZTpudWxsLGRlbGF5OjAsZHVyYXRpb246MjUwLGVhc2U6eHN9fSk7dmFyIGNHdD1NKCgpPT57TWYoKTtRVXQoKTtsR3QoKTtjZC5wcm90b3R5cGUuaW50ZXJydXB0PUpVdDtjZC5wcm90b3R5cGUudHJhbnNpdGlvbj1zR3R9KTtmdW5jdGlvbiB1R3QoZSx0KXt2YXIgcj1lLl9fdHJhbnNpdGlvbixuLGk7aWYocil7dD10PT1udWxsP251bGw6dCsiIjtmb3IoaSBpbiByKWlmKChuPXJbaV0pLnN0YXRlPmh6JiZuLm5hbWU9PT10KXJldHVybiBuZXcgVW8oW1tlXV0sUlJlLHQsK2kpfXJldHVybiBudWxsfXZhciBSUmUsaEd0PU0oKCk9Pnt1ZCgpO0FjKCk7UlJlPVtudWxsXX0pO3ZhciBmR3Q9TSgoKT0+e2NHdCgpO3VkKCk7aEd0KCk7WXJ0KCl9KTtmdW5jdGlvbiBjbnQoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fXZhciBwR3Q9TSgoKT0+e30pO2Z1bmN0aW9uIGRHdChlKXtyZXR1cm4gZVswXX1mdW5jdGlvbiBtR3QoZSl7cmV0dXJuIGVbMV19dmFyIGdHdD1NKCgpPT57fSk7ZnVuY3Rpb24gdW50KCl7dGhpcy5fPW51bGx9ZnVuY3Rpb24gUXcoZSl7ZS5VPWUuQz1lLkw9ZS5SPWUuUD1lLk49bnVsbH1mdW5jdGlvbiBkQShlLHQpe3ZhciByPXQsbj10LlIsaT1yLlU7aT9pLkw9PT1yP2kuTD1uOmkuUj1uOmUuXz1uLG4uVT1pLHIuVT1uLHIuUj1uLkwsci5SJiYoci5SLlU9ciksbi5MPXJ9ZnVuY3Rpb24gbUEoZSx0KXt2YXIgcj10LG49dC5MLGk9ci5VO2k/aS5MPT09cj9pLkw9bjppLlI9bjplLl89bixuLlU9aSxyLlU9bixyLkw9bi5SLHIuTCYmKHIuTC5VPXIpLG4uUj1yfWZ1bmN0aW9uIF9HdChlKXtmb3IoO2UuTDspZT1lLkw7cmV0dXJuIGV9dmFyIGhudCxUej1NKCgpPT57dW50LnByb3RvdHlwZT17Y29uc3RydWN0b3I6dW50LGluc2VydDpmdW5jdGlvbihlLHQpe3ZhciByLG4saTtpZihlKXtpZih0LlA9ZSx0Lk49ZS5OLGUuTiYmKGUuTi5QPXQpLGUuTj10LGUuUil7Zm9yKGU9ZS5SO2UuTDspZT1lLkw7ZS5MPXR9ZWxzZSBlLlI9dDtyPWV9ZWxzZSB0aGlzLl8/KGU9X0d0KHRoaXMuXyksdC5QPW51bGwsdC5OPWUsZS5QPWUuTD10LHI9ZSk6KHQuUD10Lk49bnVsbCx0aGlzLl89dCxyPW51bGwpO2Zvcih0Lkw9dC5SPW51bGwsdC5VPXIsdC5DPSEwLGU9dDtyJiZyLkM7KW49ci5VLHI9PT1uLkw/KGk9bi5SLGkmJmkuQz8oci5DPWkuQz0hMSxuLkM9ITAsZT1uKTooZT09PXIuUiYmKGRBKHRoaXMsciksZT1yLHI9ZS5VKSxyLkM9ITEsbi5DPSEwLG1BKHRoaXMsbikpKTooaT1uLkwsaSYmaS5DPyhyLkM9aS5DPSExLG4uQz0hMCxlPW4pOihlPT09ci5MJiYobUEodGhpcyxyKSxlPXIscj1lLlUpLHIuQz0hMSxuLkM9ITAsZEEodGhpcyxuKSkpLHI9ZS5VO3RoaXMuXy5DPSExfSxyZW1vdmU6ZnVuY3Rpb24oZSl7ZS5OJiYoZS5OLlA9ZS5QKSxlLlAmJihlLlAuTj1lLk4pLGUuTj1lLlA9bnVsbDt2YXIgdD1lLlUscixuPWUuTCxpPWUuUixvLGE7aWYobj9pP289X0d0KGkpOm89bjpvPWksdD90Lkw9PT1lP3QuTD1vOnQuUj1vOnRoaXMuXz1vLG4mJmk/KGE9by5DLG8uQz1lLkMsby5MPW4sbi5VPW8sbyE9PWk/KHQ9by5VLG8uVT1lLlUsZT1vLlIsdC5MPWUsby5SPWksaS5VPW8pOihvLlU9dCx0PW8sZT1vLlIpKTooYT1lLkMsZT1vKSxlJiYoZS5VPXQpLCFhKXtpZihlJiZlLkMpe2UuQz0hMTtyZXR1cm59ZG97aWYoZT09PXRoaXMuXylicmVhaztpZihlPT09dC5MKXtpZihyPXQuUixyLkMmJihyLkM9ITEsdC5DPSEwLGRBKHRoaXMsdCkscj10LlIpLHIuTCYmci5MLkN8fHIuUiYmci5SLkMpeyghci5SfHwhci5SLkMpJiYoci5MLkM9ITEsci5DPSEwLG1BKHRoaXMscikscj10LlIpLHIuQz10LkMsdC5DPXIuUi5DPSExLGRBKHRoaXMsdCksZT10aGlzLl87YnJlYWt9fWVsc2UgaWYocj10Lkwsci5DJiYoci5DPSExLHQuQz0hMCxtQSh0aGlzLHQpLHI9dC5MKSxyLkwmJnIuTC5DfHxyLlImJnIuUi5DKXsoIXIuTHx8IXIuTC5DKSYmKHIuUi5DPSExLHIuQz0hMCxkQSh0aGlzLHIpLHI9dC5MKSxyLkM9dC5DLHQuQz1yLkwuQz0hMSxtQSh0aGlzLHQpLGU9dGhpcy5fO2JyZWFrfXIuQz0hMCxlPXQsdD10LlV9d2hpbGUoIWUuQyk7ZSYmKGUuQz0hMSl9fX07aG50PXVudH0pO2Z1bmN0aW9uIHRTKGUsdCxyLG4pe3ZhciBpPVtudWxsLG51bGxdLG89QW8ucHVzaChpKS0xO3JldHVybiBpLmxlZnQ9ZSxpLnJpZ2h0PXQsciYmZ0EoaSxlLHQsciksbiYmZ0EoaSx0LGUsbiksWmFbZS5pbmRleF0uaGFsZmVkZ2VzLnB1c2gobyksWmFbdC5pbmRleF0uaGFsZmVkZ2VzLnB1c2gobyksaX1mdW5jdGlvbiBlUyhlLHQscil7dmFyIG49W3Qscl07cmV0dXJuIG4ubGVmdD1lLG59ZnVuY3Rpb24gZ0EoZSx0LHIsbil7IWVbMF0mJiFlWzFdPyhlWzBdPW4sZS5sZWZ0PXQsZS5yaWdodD1yKTplLmxlZnQ9PT1yP2VbMV09bjplWzBdPW59ZnVuY3Rpb24gTlJlKGUsdCxyLG4saSl7dmFyIG89ZVswXSxhPWVbMV0scz1vWzBdLGw9b1sxXSxjPWFbMF0sdT1hWzFdLGg9MCxmPTEscD1jLXMsZD11LWwsZztpZihnPXQtcywhKCFwJiZnPjApKXtpZihnLz1wLHA8MCl7aWYoZzxoKXJldHVybjtnPGYmJihmPWcpfWVsc2UgaWYocD4wKXtpZihnPmYpcmV0dXJuO2c+aCYmKGg9Zyl9aWYoZz1uLXMsISghcCYmZzwwKSl7aWYoZy89cCxwPDApe2lmKGc+ZilyZXR1cm47Zz5oJiYoaD1nKX1lbHNlIGlmKHA+MCl7aWYoZzxoKXJldHVybjtnPGYmJihmPWcpfWlmKGc9ci1sLCEoIWQmJmc+MCkpe2lmKGcvPWQsZDwwKXtpZihnPGgpcmV0dXJuO2c8ZiYmKGY9Zyl9ZWxzZSBpZihkPjApe2lmKGc+ZilyZXR1cm47Zz5oJiYoaD1nKX1pZihnPWktbCwhKCFkJiZnPDApKXtpZihnLz1kLGQ8MCl7aWYoZz5mKXJldHVybjtnPmgmJihoPWcpfWVsc2UgaWYoZD4wKXtpZihnPGgpcmV0dXJuO2c8ZiYmKGY9Zyl9cmV0dXJuIShoPjApJiYhKGY8MSl8fChoPjAmJihlWzBdPVtzK2gqcCxsK2gqZF0pLGY8MSYmKGVbMV09W3MrZipwLGwrZipkXSkpLCEwfX19fX1mdW5jdGlvbiBEUmUoZSx0LHIsbixpKXt2YXIgbz1lWzFdO2lmKG8pcmV0dXJuITA7dmFyIGE9ZVswXSxzPWUubGVmdCxsPWUucmlnaHQsYz1zWzBdLHU9c1sxXSxoPWxbMF0sZj1sWzFdLHA9KGMraCkvMixkPSh1K2YpLzIsZyxfO2lmKGY9PT11KXtpZihwPHR8fHA+PW4pcmV0dXJuO2lmKGM+aCl7aWYoIWEpYT1bcCxyXTtlbHNlIGlmKGFbMV0+PWkpcmV0dXJuO289W3AsaV19ZWxzZXtpZighYSlhPVtwLGldO2Vsc2UgaWYoYVsxXTxyKXJldHVybjtvPVtwLHJdfX1lbHNlIGlmKGc9KGMtaCkvKGYtdSksXz1kLWcqcCxnPC0xfHxnPjEpaWYoYz5oKXtpZighYSlhPVsoci1fKS9nLHJdO2Vsc2UgaWYoYVsxXT49aSlyZXR1cm47bz1bKGktXykvZyxpXX1lbHNle2lmKCFhKWE9WyhpLV8pL2csaV07ZWxzZSBpZihhWzFdPHIpcmV0dXJuO289WyhyLV8pL2cscl19ZWxzZSBpZih1PGYpe2lmKCFhKWE9W3QsZyp0K19dO2Vsc2UgaWYoYVswXT49bilyZXR1cm47bz1bbixnKm4rX119ZWxzZXtpZighYSlhPVtuLGcqbitfXTtlbHNlIGlmKGFbMF08dClyZXR1cm47bz1bdCxnKnQrX119cmV0dXJuIGVbMF09YSxlWzFdPW8sITB9ZnVuY3Rpb24geUd0KGUsdCxyLG4pe2Zvcih2YXIgaT1Bby5sZW5ndGgsbztpLS07KSghRFJlKG89QW9baV0sZSx0LHIsbil8fCFOUmUobyxlLHQscixuKXx8IShNYXRoLmFicyhvWzBdWzBdLW9bMV1bMF0pPldyfHxNYXRoLmFicyhvWzBdWzFdLW9bMV1bMV0pPldyKSkmJmRlbGV0ZSBBb1tpXX12YXIgQ3o9TSgoKT0+e3JTKCl9KTtmdW5jdGlvbiB2R3QoZSl7cmV0dXJuIFphW2UuaW5kZXhdPXtzaXRlOmUsaGFsZmVkZ2VzOltdfX1mdW5jdGlvbiBPUmUoZSx0KXt2YXIgcj1lLnNpdGUsbj10LmxlZnQsaT10LnJpZ2h0O3JldHVybiByPT09aSYmKGk9bixuPXIpLGk/TWF0aC5hdGFuMihpWzFdLW5bMV0saVswXS1uWzBdKToocj09PW4/KG49dFsxXSxpPXRbMF0pOihuPXRbMF0saT10WzFdKSxNYXRoLmF0YW4yKG5bMF0taVswXSxpWzFdLW5bMV0pKX1mdW5jdGlvbiBmbnQoZSx0KXtyZXR1cm4gdFsrKHQubGVmdCE9PWUuc2l0ZSldfWZ1bmN0aW9uIHpSZShlLHQpe3JldHVybiB0WysodC5sZWZ0PT09ZS5zaXRlKV19ZnVuY3Rpb24geEd0KCl7Zm9yKHZhciBlPTAsdD1aYS5sZW5ndGgscixuLGksbztlPHQ7KytlKWlmKChyPVphW2VdKSYmKG89KG49ci5oYWxmZWRnZXMpLmxlbmd0aCkpe3ZhciBhPW5ldyBBcnJheShvKSxzPW5ldyBBcnJheShvKTtmb3IoaT0wO2k8bzsrK2kpYVtpXT1pLHNbaV09T1JlKHIsQW9bbltpXV0pO2ZvcihhLnNvcnQoZnVuY3Rpb24obCxjKXtyZXR1cm4gc1tjXS1zW2xdfSksaT0wO2k8bzsrK2kpc1tpXT1uW2FbaV1dO2ZvcihpPTA7aTxvOysraSluW2ldPXNbaV19fWZ1bmN0aW9uIGJHdChlLHQscixuKXt2YXIgaT1aYS5sZW5ndGgsbyxhLHMsbCxjLHUsaCxmLHAsZCxnLF8seT0hMDtmb3Iobz0wO288aTsrK28paWYoYT1aYVtvXSl7Zm9yKHM9YS5zaXRlLGM9YS5oYWxmZWRnZXMsbD1jLmxlbmd0aDtsLS07KUFvW2NbbF1dfHxjLnNwbGljZShsLDEpO2ZvcihsPTAsdT1jLmxlbmd0aDtsPHU7KWQ9elJlKGEsQW9bY1tsXV0pLGc9ZFswXSxfPWRbMV0saD1mbnQoYSxBb1tjWysrbCV1XV0pLGY9aFswXSxwPWhbMV0sKE1hdGguYWJzKGctZik+V3J8fE1hdGguYWJzKF8tcCk+V3IpJiYoYy5zcGxpY2UobCwwLEFvLnB1c2goZVMocyxkLE1hdGguYWJzKGctZSk8V3ImJm4tXz5Xcj9bZSxNYXRoLmFicyhmLWUpPFdyP3A6bl06TWF0aC5hYnMoXy1uKTxXciYmci1nPldyP1tNYXRoLmFicyhwLW4pPFdyP2Y6cixuXTpNYXRoLmFicyhnLXIpPFdyJiZfLXQ+V3I/W3IsTWF0aC5hYnMoZi1yKTxXcj9wOnRdOk1hdGguYWJzKF8tdCk8V3ImJmctZT5Xcj9bTWF0aC5hYnMocC10KTxXcj9mOmUsdF06bnVsbCkpLTEpLCsrdSk7dSYmKHk9ITEpfWlmKHkpe3ZhciB4LGIsUyxDPTEvMDtmb3Iobz0wLHk9bnVsbDtvPGk7KytvKShhPVphW29dKSYmKHM9YS5zaXRlLHg9c1swXS1lLGI9c1sxXS10LFM9eCp4K2IqYixTPEMmJihDPVMseT1hKSk7aWYoeSl7dmFyIFA9W2UsdF0saz1bZSxuXSxPPVtyLG5dLEQ9W3IsdF07eS5oYWxmZWRnZXMucHVzaChBby5wdXNoKGVTKHM9eS5zaXRlLFAsaykpLTEsQW8ucHVzaChlUyhzLGssTykpLTEsQW8ucHVzaChlUyhzLE8sRCkpLTEsQW8ucHVzaChlUyhzLEQsUCkpLTEpfX1mb3Iobz0wO288aTsrK28pKGE9WmFbb10pJiYoYS5oYWxmZWRnZXMubGVuZ3RofHxkZWxldGUgWmFbb10pfXZhciBwbnQ9TSgoKT0+e0N6KCk7clMoKX0pO2Z1bmN0aW9uIEZSZSgpe1F3KHRoaXMpLHRoaXMueD10aGlzLnk9dGhpcy5hcmM9dGhpcy5zaXRlPXRoaXMuY3k9bnVsbH1mdW5jdGlvbiBNMShlKXt2YXIgdD1lLlAscj1lLk47aWYoISghdHx8IXIpKXt2YXIgbj10LnNpdGUsaT1lLnNpdGUsbz1yLnNpdGU7aWYobiE9PW8pe3ZhciBhPWlbMF0scz1pWzFdLGw9blswXS1hLGM9blsxXS1zLHU9b1swXS1hLGg9b1sxXS1zLGY9MioobCpoLWMqdSk7aWYoIShmPj0tU0d0KSl7dmFyIHA9bCpsK2MqYyxkPXUqdStoKmgsZz0oaCpwLWMqZCkvZixfPShsKmQtdSpwKS9mLHk9d0d0LnBvcCgpfHxuZXcgRlJlO3kuYXJjPWUseS5zaXRlPWkseS54PWcrYSx5Lnk9KHkuY3k9XytzKStNYXRoLnNxcnQoZypnK18qXyksZS5jaXJjbGU9eTtmb3IodmFyIHg9bnVsbCxiPW5TLl87YjspaWYoeS55PGIueXx8eS55PT09Yi55JiZ5Lng8PWIueClpZihiLkwpYj1iLkw7ZWxzZXt4PWIuUDticmVha31lbHNlIGlmKGIuUiliPWIuUjtlbHNle3g9YjticmVha31uUy5pbnNlcnQoeCx5KSx4fHwoQXo9eSl9fX19ZnVuY3Rpb24gRTEoZSl7dmFyIHQ9ZS5jaXJjbGU7dCYmKHQuUHx8KEF6PXQuTiksblMucmVtb3ZlKHQpLHdHdC5wdXNoKHQpLFF3KHQpLGUuY2lyY2xlPW51bGwpfXZhciB3R3QsQXosZG50PU0oKCk9PntUeigpO3JTKCk7d0d0PVtdfSk7ZnVuY3Rpb24gQlJlKCl7UXcodGhpcyksdGhpcy5lZGdlPXRoaXMuc2l0ZT10aGlzLmNpcmNsZT1udWxsfWZ1bmN0aW9uIE1HdChlKXt2YXIgdD1FR3QucG9wKCl8fG5ldyBCUmU7cmV0dXJuIHQuc2l0ZT1lLHR9ZnVuY3Rpb24gbW50KGUpe0UxKGUpLFQxLnJlbW92ZShlKSxFR3QucHVzaChlKSxRdyhlKX1mdW5jdGlvbiBUR3QoZSl7dmFyIHQ9ZS5jaXJjbGUscj10Lngsbj10LmN5LGk9W3Isbl0sbz1lLlAsYT1lLk4scz1bZV07bW50KGUpO2Zvcih2YXIgbD1vO2wuY2lyY2xlJiZNYXRoLmFicyhyLWwuY2lyY2xlLngpPFdyJiZNYXRoLmFicyhuLWwuY2lyY2xlLmN5KTxXcjspbz1sLlAscy51bnNoaWZ0KGwpLG1udChsKSxsPW87cy51bnNoaWZ0KGwpLEUxKGwpO2Zvcih2YXIgYz1hO2MuY2lyY2xlJiZNYXRoLmFicyhyLWMuY2lyY2xlLngpPFdyJiZNYXRoLmFicyhuLWMuY2lyY2xlLmN5KTxXcjspYT1jLk4scy5wdXNoKGMpLG1udChjKSxjPWE7cy5wdXNoKGMpLEUxKGMpO3ZhciB1PXMubGVuZ3RoLGg7Zm9yKGg9MTtoPHU7KytoKWM9c1toXSxsPXNbaC0xXSxnQShjLmVkZ2UsbC5zaXRlLGMuc2l0ZSxpKTtsPXNbMF0sYz1zW3UtMV0sYy5lZGdlPXRTKGwuc2l0ZSxjLnNpdGUsbnVsbCxpKSxNMShsKSxNMShjKX1mdW5jdGlvbiBDR3QoZSl7Zm9yKHZhciB0PWVbMF0scj1lWzFdLG4saSxvLGEscz1UMS5fO3M7KWlmKG89QUd0KHMsciktdCxvPldyKXM9cy5MO2Vsc2UgaWYoYT10LUhSZShzLHIpLGE+V3Ipe2lmKCFzLlIpe249czticmVha31zPXMuUn1lbHNle28+LVdyPyhuPXMuUCxpPXMpOmE+LVdyPyhuPXMsaT1zLk4pOm49aT1zO2JyZWFrfXZHdChlKTt2YXIgbD1NR3QoZSk7aWYoVDEuaW5zZXJ0KG4sbCksISghbiYmIWkpKXtpZihuPT09aSl7RTEobiksaT1NR3Qobi5zaXRlKSxUMS5pbnNlcnQobCxpKSxsLmVkZ2U9aS5lZGdlPXRTKG4uc2l0ZSxsLnNpdGUpLE0xKG4pLE0xKGkpO3JldHVybn1pZighaSl7bC5lZGdlPXRTKG4uc2l0ZSxsLnNpdGUpO3JldHVybn1FMShuKSxFMShpKTt2YXIgYz1uLnNpdGUsdT1jWzBdLGg9Y1sxXSxmPWVbMF0tdSxwPWVbMV0taCxkPWkuc2l0ZSxnPWRbMF0tdSxfPWRbMV0taCx5PTIqKGYqXy1wKmcpLHg9ZipmK3AqcCxiPWcqZytfKl8sUz1bKF8qeC1wKmIpL3krdSwoZipiLWcqeCkveStoXTtnQShpLmVkZ2UsYyxkLFMpLGwuZWRnZT10UyhjLGUsbnVsbCxTKSxpLmVkZ2U9dFMoZSxkLG51bGwsUyksTTEobiksTTEoaSl9fWZ1bmN0aW9uIEFHdChlLHQpe3ZhciByPWUuc2l0ZSxuPXJbMF0saT1yWzFdLG89aS10O2lmKCFvKXJldHVybiBuO3ZhciBhPWUuUDtpZighYSlyZXR1cm4tMS8wO3I9YS5zaXRlO3ZhciBzPXJbMF0sbD1yWzFdLGM9bC10O2lmKCFjKXJldHVybiBzO3ZhciB1PXMtbixoPTEvby0xL2MsZj11L2M7cmV0dXJuIGg/KC1mK01hdGguc3FydChmKmYtMipoKih1KnUvKC0yKmMpLWwrYy8yK2ktby8yKSkpL2grbjoobitzKS8yfWZ1bmN0aW9uIEhSZShlLHQpe3ZhciByPWUuTjtpZihyKXJldHVybiBBR3Qocix0KTt2YXIgbj1lLnNpdGU7cmV0dXJuIG5bMV09PT10P25bMF06MS8wfXZhciBFR3QsUEd0PU0oKCk9PntUeigpO3BudCgpO2RudCgpO0N6KCk7clMoKTtFR3Q9W119KTtmdW5jdGlvbiBWUmUoZSx0LHIpe3JldHVybihlWzBdLXJbMF0pKih0WzFdLWVbMV0pLShlWzBdLXRbMF0pKihyWzFdLWVbMV0pfWZ1bmN0aW9uIFVSZShlLHQpe3JldHVybiB0WzFdLWVbMV18fHRbMF0tZVswXX1mdW5jdGlvbiBfQShlLHQpe3ZhciByPWUuc29ydChVUmUpLnBvcCgpLG4saSxvO2ZvcihBbz1bXSxaYT1uZXcgQXJyYXkoZS5sZW5ndGgpLFQxPW5ldyBobnQsblM9bmV3IGhudDs7KWlmKG89QXosciYmKCFvfHxyWzFdPG8ueXx8clsxXT09PW8ueSYmclswXTxvLngpKShyWzBdIT09bnx8clsxXSE9PWkpJiYoQ0d0KHIpLG49clswXSxpPXJbMV0pLHI9ZS5wb3AoKTtlbHNlIGlmKG8pVEd0KG8uYXJjKTtlbHNlIGJyZWFrO2lmKHhHdCgpLHQpe3ZhciBhPSt0WzBdWzBdLHM9K3RbMF1bMV0sbD0rdFsxXVswXSxjPSt0WzFdWzFdO3lHdChhLHMsbCxjKSxiR3QoYSxzLGwsYyl9dGhpcy5lZGdlcz1Bbyx0aGlzLmNlbGxzPVphLFQxPW5TPUFvPVphPW51bGx9dmFyIFdyLFNHdCxUMSxaYSxuUyxBbyxyUz1NKCgpPT57UEd0KCk7cG50KCk7ZG50KCk7Q3ooKTtUeigpO1dyPTFlLTYsU0d0PTFlLTEyO19BLnByb3RvdHlwZT17Y29uc3RydWN0b3I6X0EscG9seWdvbnM6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLmVkZ2VzO3JldHVybiB0aGlzLmNlbGxzLm1hcChmdW5jdGlvbih0KXt2YXIgcj10LmhhbGZlZGdlcy5tYXAoZnVuY3Rpb24obil7cmV0dXJuIGZudCh0LGVbbl0pfSk7cmV0dXJuIHIuZGF0YT10LnNpdGUuZGF0YSxyfSl9LHRyaWFuZ2xlczpmdW5jdGlvbigpe3ZhciBlPVtdLHQ9dGhpcy5lZGdlcztyZXR1cm4gdGhpcy5jZWxscy5mb3JFYWNoKGZ1bmN0aW9uKHIsbil7aWYoISEocz0obz1yLmhhbGZlZGdlcykubGVuZ3RoKSlmb3IodmFyIGk9ci5zaXRlLG8sYT0tMSxzLGwsYz10W29bcy0xXV0sdT1jLmxlZnQ9PT1pP2MucmlnaHQ6Yy5sZWZ0OysrYTxzOylsPXUsYz10W29bYV1dLHU9Yy5sZWZ0PT09aT9jLnJpZ2h0OmMubGVmdCxsJiZ1JiZuPGwuaW5kZXgmJm48dS5pbmRleCYmVlJlKGksbCx1KTwwJiZlLnB1c2goW2kuZGF0YSxsLmRhdGEsdS5kYXRhXSl9KSxlfSxsaW5rczpmdW5jdGlvbigpe3JldHVybiB0aGlzLmVkZ2VzLmZpbHRlcihmdW5jdGlvbihlKXtyZXR1cm4gZS5yaWdodH0pLm1hcChmdW5jdGlvbihlKXtyZXR1cm57c291cmNlOmUubGVmdC5kYXRhLHRhcmdldDplLnJpZ2h0LmRhdGF9fSl9LGZpbmQ6ZnVuY3Rpb24oZSx0LHIpe2Zvcih2YXIgbj10aGlzLGksbz1uLl9mb3VuZHx8MCxhPW4uY2VsbHMubGVuZ3RoLHM7IShzPW4uY2VsbHNbb10pOylpZigrK28+PWEpcmV0dXJuIG51bGw7dmFyIGw9ZS1zLnNpdGVbMF0sYz10LXMuc2l0ZVsxXSx1PWwqbCtjKmM7ZG8gcz1uLmNlbGxzW2k9b10sbz1udWxsLHMuaGFsZmVkZ2VzLmZvckVhY2goZnVuY3Rpb24oaCl7dmFyIGY9bi5lZGdlc1toXSxwPWYubGVmdDtpZighKChwPT09cy5zaXRlfHwhcCkmJiEocD1mLnJpZ2h0KSkpe3ZhciBkPWUtcFswXSxnPXQtcFsxXSxfPWQqZCtnKmc7Xzx1JiYodT1fLG89cC5pbmRleCl9fSk7d2hpbGUobyE9PW51bGwpO3JldHVybiBuLl9mb3VuZD1pLHI9PW51bGx8fHU8PXIqcj9zLnNpdGU6bnVsbH19fSk7ZnVuY3Rpb24gSUd0KCl7dmFyIGU9ZEd0LHQ9bUd0LHI9bnVsbDtmdW5jdGlvbiBuKGkpe3JldHVybiBuZXcgX0EoaS5tYXAoZnVuY3Rpb24obyxhKXt2YXIgcz1bTWF0aC5yb3VuZChlKG8sYSxpKS9XcikqV3IsTWF0aC5yb3VuZCh0KG8sYSxpKS9XcikqV3JdO3JldHVybiBzLmluZGV4PWEscy5kYXRhPW8sc30pLHIpfXJldHVybiBuLnBvbHlnb25zPWZ1bmN0aW9uKGkpe3JldHVybiBuKGkpLnBvbHlnb25zKCl9LG4ubGlua3M9ZnVuY3Rpb24oaSl7cmV0dXJuIG4oaSkubGlua3MoKX0sbi50cmlhbmdsZXM9ZnVuY3Rpb24oaSl7cmV0dXJuIG4oaSkudHJpYW5nbGVzKCl9LG4ueD1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2YgaT09ImZ1bmN0aW9uIj9pOmNudCgraSksbik6ZX0sbi55PWZ1bmN0aW9uKGkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBpPT0iZnVuY3Rpb24iP2k6Y250KCtpKSxuKTp0fSxuLmV4dGVudD1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj1pPT1udWxsP251bGw6W1sraVswXVswXSwraVswXVsxXV0sWytpWzFdWzBdLCtpWzFdWzFdXV0sbik6ciYmW1tyWzBdWzBdLHJbMF1bMV1dLFtyWzFdWzBdLHJbMV1bMV1dXX0sbi5zaXplPWZ1bmN0aW9uKGkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPWk9PW51bGw/bnVsbDpbWzAsMF0sWytpWzBdLCtpWzFdXV0sbik6ciYmW3JbMV1bMF0tclswXVswXSxyWzFdWzFdLXJbMF1bMV1dfSxufXZhciBMR3Q9TSgoKT0+e3BHdCgpO2dHdCgpO3JTKCl9KTt2YXIga0d0PU0oKCk9PntMR3QoKX0pO2Z1bmN0aW9uIE5HdCgpe2Zvcih2YXIgZT0wLHQ9YXJndW1lbnRzLmxlbmd0aCxyPXt9LG47ZTx0OysrZSl7aWYoIShuPWFyZ3VtZW50c1tlXSsiIil8fG4gaW4gcnx8L1tccy5dLy50ZXN0KG4pKXRocm93IG5ldyBFcnJvcigiaWxsZWdhbCB0eXBlOiAiK24pO3Jbbl09W119cmV0dXJuIG5ldyBQeihyKX1mdW5jdGlvbiBQeihlKXt0aGlzLl89ZX1mdW5jdGlvbiBHUmUoZSx0KXtyZXR1cm4gZS50cmltKCkuc3BsaXQoL158XHMrLykubWFwKGZ1bmN0aW9uKHIpe3ZhciBuPSIiLGk9ci5pbmRleE9mKCIuIik7aWYoaT49MCYmKG49ci5zbGljZShpKzEpLHI9ci5zbGljZSgwLGkpKSxyJiYhdC5oYXNPd25Qcm9wZXJ0eShyKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gdHlwZTogIityKTtyZXR1cm57dHlwZTpyLG5hbWU6bn19KX1mdW5jdGlvbiBXUmUoZSx0KXtmb3IodmFyIHI9MCxuPWUubGVuZ3RoLGk7cjxuOysrcilpZigoaT1lW3JdKS5uYW1lPT09dClyZXR1cm4gaS52YWx1ZX1mdW5jdGlvbiBSR3QoZSx0LHIpe2Zvcih2YXIgbj0wLGk9ZS5sZW5ndGg7bjxpOysrbilpZihlW25dLm5hbWU9PT10KXtlW25dPXFSZSxlPWUuc2xpY2UoMCxuKS5jb25jYXQoZS5zbGljZShuKzEpKTticmVha31yZXR1cm4gciE9bnVsbCYmZS5wdXNoKHtuYW1lOnQsdmFsdWU6cn0pLGV9dmFyIHFSZSx5QSxER3Q9TSgoKT0+e3FSZT17dmFsdWU6ZnVuY3Rpb24oKXt9fTtQei5wcm90b3R5cGU9Tkd0LnByb3RvdHlwZT17Y29uc3RydWN0b3I6UHosb246ZnVuY3Rpb24oZSx0KXt2YXIgcj10aGlzLl8sbj1HUmUoZSsiIixyKSxpLG89LTEsYT1uLmxlbmd0aDtpZihhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcig7KytvPGE7KWlmKChpPShlPW5bb10pLnR5cGUpJiYoaT1XUmUocltpXSxlLm5hbWUpKSlyZXR1cm4gaTtyZXR1cm59aWYodCE9bnVsbCYmdHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIGNhbGxiYWNrOiAiK3QpO2Zvcig7KytvPGE7KWlmKGk9KGU9bltvXSkudHlwZSlyW2ldPVJHdChyW2ldLGUubmFtZSx0KTtlbHNlIGlmKHQ9PW51bGwpZm9yKGkgaW4gcilyW2ldPVJHdChyW2ldLGUubmFtZSxudWxsKTtyZXR1cm4gdGhpc30sY29weTpmdW5jdGlvbigpe3ZhciBlPXt9LHQ9dGhpcy5fO2Zvcih2YXIgciBpbiB0KWVbcl09dFtyXS5zbGljZSgpO3JldHVybiBuZXcgUHooZSl9LGNhbGw6ZnVuY3Rpb24oZSx0KXtpZigoaT1hcmd1bWVudHMubGVuZ3RoLTIpPjApZm9yKHZhciByPW5ldyBBcnJheShpKSxuPTAsaSxvO248aTsrK24pcltuXT1hcmd1bWVudHNbbisyXTtpZighdGhpcy5fLmhhc093blByb3BlcnR5KGUpKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK2UpO2ZvcihvPXRoaXMuX1tlXSxuPTAsaT1vLmxlbmd0aDtuPGk7KytuKW9bbl0udmFsdWUuYXBwbHkodCxyKX0sYXBwbHk6ZnVuY3Rpb24oZSx0LHIpe2lmKCF0aGlzLl8uaGFzT3duUHJvcGVydHkoZSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrZSk7Zm9yKHZhciBuPXRoaXMuX1tlXSxpPTAsbz1uLmxlbmd0aDtpPG87KytpKW5baV0udmFsdWUuYXBwbHkodCxyKX19O3lBPU5HdH0pO3ZhciBnbnQ9TSgoKT0+e0RHdCgpfSk7dmFyIEl6LF9udCx5bnQ9TSgoKT0+e0l6PSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIixfbnQ9e3N2ZzoiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLHhodG1sOkl6LHhsaW5rOiJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIix4bWw6Imh0dHA6Ly93d3cudzMub3JnL1hNTC8xOTk4L25hbWVzcGFjZSIseG1sbnM6Imh0dHA6Ly93d3cudzMub3JnLzIwMDAveG1sbnMvIn19KTtmdW5jdGlvbiBoZChlKXt2YXIgdD1lKz0iIixyPXQuaW5kZXhPZigiOiIpO3JldHVybiByPj0wJiYodD1lLnNsaWNlKDAscikpIT09InhtbG5zIiYmKGU9ZS5zbGljZShyKzEpKSxfbnQuaGFzT3duUHJvcGVydHkodCk/e3NwYWNlOl9udFt0XSxsb2NhbDplfTplfXZhciBMej1NKCgpPT57eW50KCl9KTtmdW5jdGlvbiBZUmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5vd25lckRvY3VtZW50LHI9dGhpcy5uYW1lc3BhY2VVUkk7cmV0dXJuIHI9PT1JeiYmdC5kb2N1bWVudEVsZW1lbnQubmFtZXNwYWNlVVJJPT09SXo/dC5jcmVhdGVFbGVtZW50KGUpOnQuY3JlYXRlRWxlbWVudE5TKHIsZSl9fWZ1bmN0aW9uIGpSZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5vd25lckRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyhlLnNwYWNlLGUubG9jYWwpfX1mdW5jdGlvbiBreihlKXt2YXIgdD1oZChlKTtyZXR1cm4odC5sb2NhbD9qUmU6WVJlKSh0KX12YXIgdm50PU0oKCk9PntMeigpO3ludCgpfSk7ZnVuY3Rpb24gWFJlKCl7fWZ1bmN0aW9uIEMxKGUpe3JldHVybiBlPT1udWxsP1hSZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLnF1ZXJ5U2VsZWN0b3IoZSl9fXZhciBSej1NKCgpPT57fSk7ZnVuY3Rpb24gT0d0KGUpe3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1DMShlKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPXQubGVuZ3RoLG49bmV3IEFycmF5KHIpLGk9MDtpPHI7KytpKWZvcih2YXIgbz10W2ldLGE9by5sZW5ndGgscz1uW2ldPW5ldyBBcnJheShhKSxsLGMsdT0wO3U8YTsrK3UpKGw9b1t1XSkmJihjPWUuY2FsbChsLGwuX19kYXRhX18sdSxvKSkmJigiX19kYXRhX18iaW4gbCYmKGMuX19kYXRhX189bC5fX2RhdGFfXyksc1t1XT1jKTtyZXR1cm4gbmV3IGdpKG4sdGhpcy5fcGFyZW50cyl9dmFyIHpHdD1NKCgpPT57T3UoKTtSeigpfSk7ZnVuY3Rpb24gJFJlKCl7cmV0dXJuW119ZnVuY3Rpb24gdkEoZSl7cmV0dXJuIGU9PW51bGw/JFJlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMucXVlcnlTZWxlY3RvckFsbChlKX19dmFyIHhudD1NKCgpPT57fSk7ZnVuY3Rpb24gRkd0KGUpe3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT12QShlKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPXQubGVuZ3RoLG49W10saT1bXSxvPTA7bzxyOysrbylmb3IodmFyIGE9dFtvXSxzPWEubGVuZ3RoLGwsYz0wO2M8czsrK2MpKGw9YVtjXSkmJihuLnB1c2goZS5jYWxsKGwsbC5fX2RhdGFfXyxjLGEpKSxpLnB1c2gobCkpO3JldHVybiBuZXcgZ2kobixpKX12YXIgQkd0PU0oKCk9PntPdSgpO3hudCgpfSk7ZnVuY3Rpb24geEEoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMubWF0Y2hlcyhlKX19dmFyIGJudD1NKCgpPT57fSk7ZnVuY3Rpb24gSEd0KGUpe3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT14QShlKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPXQubGVuZ3RoLG49bmV3IEFycmF5KHIpLGk9MDtpPHI7KytpKWZvcih2YXIgbz10W2ldLGE9by5sZW5ndGgscz1uW2ldPVtdLGwsYz0wO2M8YTsrK2MpKGw9b1tjXSkmJmUuY2FsbChsLGwuX19kYXRhX18sYyxvKSYmcy5wdXNoKGwpO3JldHVybiBuZXcgZ2kobix0aGlzLl9wYXJlbnRzKX12YXIgVkd0PU0oKCk9PntPdSgpO2JudCgpfSk7ZnVuY3Rpb24gTnooZSl7cmV0dXJuIG5ldyBBcnJheShlLmxlbmd0aCl9dmFyIHdudD1NKCgpPT57fSk7ZnVuY3Rpb24gVUd0KCl7cmV0dXJuIG5ldyBnaSh0aGlzLl9lbnRlcnx8dGhpcy5fZ3JvdXBzLm1hcChOeiksdGhpcy5fcGFyZW50cyl9ZnVuY3Rpb24gYkEoZSx0KXt0aGlzLm93bmVyRG9jdW1lbnQ9ZS5vd25lckRvY3VtZW50LHRoaXMubmFtZXNwYWNlVVJJPWUubmFtZXNwYWNlVVJJLHRoaXMuX25leHQ9bnVsbCx0aGlzLl9wYXJlbnQ9ZSx0aGlzLl9fZGF0YV9fPXR9dmFyIFNudD1NKCgpPT57d250KCk7T3UoKTtiQS5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOmJBLGFwcGVuZENoaWxkOmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLl9wYXJlbnQuaW5zZXJ0QmVmb3JlKGUsdGhpcy5fbmV4dCl9LGluc2VydEJlZm9yZTpmdW5jdGlvbihlLHQpe3JldHVybiB0aGlzLl9wYXJlbnQuaW5zZXJ0QmVmb3JlKGUsdCl9LHF1ZXJ5U2VsZWN0b3I6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX3BhcmVudC5xdWVyeVNlbGVjdG9yKGUpfSxxdWVyeVNlbGVjdG9yQWxsOmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLl9wYXJlbnQucXVlcnlTZWxlY3RvckFsbChlKX19fSk7ZnVuY3Rpb24gcUd0KGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX12YXIgR0d0PU0oKCk9Pnt9KTtmdW5jdGlvbiBLUmUoZSx0LHIsbixpLG8pe2Zvcih2YXIgYT0wLHMsbD10Lmxlbmd0aCxjPW8ubGVuZ3RoO2E8YzsrK2EpKHM9dFthXSk/KHMuX19kYXRhX189b1thXSxuW2FdPXMpOnJbYV09bmV3IGJBKGUsb1thXSk7Zm9yKDthPGw7KythKShzPXRbYV0pJiYoaVthXT1zKX1mdW5jdGlvbiBaUmUoZSx0LHIsbixpLG8sYSl7dmFyIHMsbCxjPXt9LHU9dC5sZW5ndGgsaD1vLmxlbmd0aCxmPW5ldyBBcnJheSh1KSxwO2ZvcihzPTA7czx1OysrcykobD10W3NdKSYmKGZbc109cD1XR3QrYS5jYWxsKGwsbC5fX2RhdGFfXyxzLHQpLHAgaW4gYz9pW3NdPWw6Y1twXT1sKTtmb3Iocz0wO3M8aDsrK3MpcD1XR3QrYS5jYWxsKGUsb1tzXSxzLG8pLChsPWNbcF0pPyhuW3NdPWwsbC5fX2RhdGFfXz1vW3NdLGNbcF09bnVsbCk6cltzXT1uZXcgYkEoZSxvW3NdKTtmb3Iocz0wO3M8dTsrK3MpKGw9dFtzXSkmJmNbZltzXV09PT1sJiYoaVtzXT1sKX1mdW5jdGlvbiBZR3QoZSx0KXtpZighZSlyZXR1cm4gcD1uZXcgQXJyYXkodGhpcy5zaXplKCkpLGM9LTEsdGhpcy5lYWNoKGZ1bmN0aW9uKFApe3BbKytjXT1QfSkscDt2YXIgcj10P1pSZTpLUmUsbj10aGlzLl9wYXJlbnRzLGk9dGhpcy5fZ3JvdXBzO3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1xR3QoZSkpO2Zvcih2YXIgbz1pLmxlbmd0aCxhPW5ldyBBcnJheShvKSxzPW5ldyBBcnJheShvKSxsPW5ldyBBcnJheShvKSxjPTA7YzxvOysrYyl7dmFyIHU9bltjXSxoPWlbY10sZj1oLmxlbmd0aCxwPWUuY2FsbCh1LHUmJnUuX19kYXRhX18sYyxuKSxkPXAubGVuZ3RoLGc9c1tjXT1uZXcgQXJyYXkoZCksXz1hW2NdPW5ldyBBcnJheShkKSx5PWxbY109bmV3IEFycmF5KGYpO3IodSxoLGcsXyx5LHAsdCk7Zm9yKHZhciB4PTAsYj0wLFMsQzt4PGQ7Kyt4KWlmKFM9Z1t4XSl7Zm9yKHg+PWImJihiPXgrMSk7IShDPV9bYl0pJiYrK2I8ZDspO1MuX25leHQ9Q3x8bnVsbH19cmV0dXJuIGE9bmV3IGdpKGEsbiksYS5fZW50ZXI9cyxhLl9leGl0PWwsYX12YXIgV0d0LGpHdD1NKCgpPT57T3UoKTtTbnQoKTtHR3QoKTtXR3Q9IiQifSk7ZnVuY3Rpb24gWEd0KCl7cmV0dXJuIG5ldyBnaSh0aGlzLl9leGl0fHx0aGlzLl9ncm91cHMubWFwKE56KSx0aGlzLl9wYXJlbnRzKX12YXIgJEd0PU0oKCk9Pnt3bnQoKTtPdSgpfSk7ZnVuY3Rpb24gS0d0KGUsdCxyKXt2YXIgbj10aGlzLmVudGVyKCksaT10aGlzLG89dGhpcy5leGl0KCk7cmV0dXJuIG49dHlwZW9mIGU9PSJmdW5jdGlvbiI/ZShuKTpuLmFwcGVuZChlKyIiKSx0IT1udWxsJiYoaT10KGkpKSxyPT1udWxsP28ucmVtb3ZlKCk6cihvKSxuJiZpP24ubWVyZ2UoaSkub3JkZXIoKTppfXZhciBaR3Q9TSgoKT0+e30pO2Z1bmN0aW9uIEpHdChlKXtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9ZS5fZ3JvdXBzLG49dC5sZW5ndGgsaT1yLmxlbmd0aCxvPU1hdGgubWluKG4saSksYT1uZXcgQXJyYXkobikscz0wO3M8bzsrK3MpZm9yKHZhciBsPXRbc10sYz1yW3NdLHU9bC5sZW5ndGgsaD1hW3NdPW5ldyBBcnJheSh1KSxmLHA9MDtwPHU7KytwKShmPWxbcF18fGNbcF0pJiYoaFtwXT1mKTtmb3IoO3M8bjsrK3MpYVtzXT10W3NdO3JldHVybiBuZXcgZ2koYSx0aGlzLl9wYXJlbnRzKX12YXIgUUd0PU0oKCk9PntPdSgpfSk7ZnVuY3Rpb24gdFd0KCl7Zm9yKHZhciBlPXRoaXMuX2dyb3Vwcyx0PS0xLHI9ZS5sZW5ndGg7Kyt0PHI7KWZvcih2YXIgbj1lW3RdLGk9bi5sZW5ndGgtMSxvPW5baV0sYTstLWk+PTA7KShhPW5baV0pJiYobyYmYS5jb21wYXJlRG9jdW1lbnRQb3NpdGlvbihvKV40JiZvLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGEsbyksbz1hKTtyZXR1cm4gdGhpc312YXIgZVd0PU0oKCk9Pnt9KTtmdW5jdGlvbiByV3QoZSl7ZXx8KGU9SlJlKTtmdW5jdGlvbiB0KGgsZil7cmV0dXJuIGgmJmY/ZShoLl9fZGF0YV9fLGYuX19kYXRhX18pOiFoLSFmfWZvcih2YXIgcj10aGlzLl9ncm91cHMsbj1yLmxlbmd0aCxpPW5ldyBBcnJheShuKSxvPTA7bzxuOysrbyl7Zm9yKHZhciBhPXJbb10scz1hLmxlbmd0aCxsPWlbb109bmV3IEFycmF5KHMpLGMsdT0wO3U8czsrK3UpKGM9YVt1XSkmJihsW3VdPWMpO2wuc29ydCh0KX1yZXR1cm4gbmV3IGdpKGksdGhpcy5fcGFyZW50cykub3JkZXIoKX1mdW5jdGlvbiBKUmUoZSx0KXtyZXR1cm4gZTx0Py0xOmU+dD8xOmU+PXQ/MDpOYU59dmFyIG5XdD1NKCgpPT57T3UoKX0pO2Z1bmN0aW9uIGlXdCgpe3ZhciBlPWFyZ3VtZW50c1swXTtyZXR1cm4gYXJndW1lbnRzWzBdPXRoaXMsZS5hcHBseShudWxsLGFyZ3VtZW50cyksdGhpc312YXIgb1d0PU0oKCk9Pnt9KTtmdW5jdGlvbiBhV3QoKXt2YXIgZT1uZXcgQXJyYXkodGhpcy5zaXplKCkpLHQ9LTE7cmV0dXJuIHRoaXMuZWFjaChmdW5jdGlvbigpe2VbKyt0XT10aGlzfSksZX12YXIgc1d0PU0oKCk9Pnt9KTtmdW5jdGlvbiBsV3QoKXtmb3IodmFyIGU9dGhpcy5fZ3JvdXBzLHQ9MCxyPWUubGVuZ3RoO3Q8cjsrK3QpZm9yKHZhciBuPWVbdF0saT0wLG89bi5sZW5ndGg7aTxvOysraSl7dmFyIGE9bltpXTtpZihhKXJldHVybiBhfXJldHVybiBudWxsfXZhciBjV3Q9TSgoKT0+e30pO2Z1bmN0aW9uIHVXdCgpe3ZhciBlPTA7cmV0dXJuIHRoaXMuZWFjaChmdW5jdGlvbigpeysrZX0pLGV9dmFyIGhXdD1NKCgpPT57fSk7ZnVuY3Rpb24gZld0KCl7cmV0dXJuIXRoaXMubm9kZSgpfXZhciBwV3Q9TSgoKT0+e30pO2Z1bmN0aW9uIGRXdChlKXtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9MCxuPXQubGVuZ3RoO3I8bjsrK3IpZm9yKHZhciBpPXRbcl0sbz0wLGE9aS5sZW5ndGgscztvPGE7KytvKShzPWlbb10pJiZlLmNhbGwocyxzLl9fZGF0YV9fLG8saSk7cmV0dXJuIHRoaXN9dmFyIG1XdD1NKCgpPT57fSk7ZnVuY3Rpb24gUVJlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlKGUpfX1mdW5jdGlvbiB0TmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpfX1mdW5jdGlvbiBlTmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZShlLHQpfX1mdW5jdGlvbiByTmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCx0KX19ZnVuY3Rpb24gbk5lKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHI9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cj09bnVsbD90aGlzLnJlbW92ZUF0dHJpYnV0ZShlKTp0aGlzLnNldEF0dHJpYnV0ZShlLHIpfX1mdW5jdGlvbiBpTmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgcj10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyPT1udWxsP3RoaXMucmVtb3ZlQXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKTp0aGlzLnNldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCxyKX19ZnVuY3Rpb24gZ1d0KGUsdCl7dmFyIHI9aGQoZSk7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXt2YXIgbj10aGlzLm5vZGUoKTtyZXR1cm4gci5sb2NhbD9uLmdldEF0dHJpYnV0ZU5TKHIuc3BhY2Usci5sb2NhbCk6bi5nZXRBdHRyaWJ1dGUocil9cmV0dXJuIHRoaXMuZWFjaCgodD09bnVsbD9yLmxvY2FsP3ROZTpRUmU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/ci5sb2NhbD9pTmU6bk5lOnIubG9jYWw/ck5lOmVOZSkocix0KSl9dmFyIF9XdD1NKCgpPT57THooKX0pO2Z1bmN0aW9uIER6KGUpe3JldHVybiBlLm93bmVyRG9jdW1lbnQmJmUub3duZXJEb2N1bWVudC5kZWZhdWx0Vmlld3x8ZS5kb2N1bWVudCYmZXx8ZS5kZWZhdWx0Vmlld312YXIgTW50PU0oKCk9Pnt9KTtmdW5jdGlvbiBvTmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShlKX19ZnVuY3Rpb24gYU5lKGUsdCxyKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnN0eWxlLnNldFByb3BlcnR5KGUsdCxyKX19ZnVuY3Rpb24gc05lKGUsdCxyKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbj10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtuPT1udWxsP3RoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkoZSk6dGhpcy5zdHlsZS5zZXRQcm9wZXJ0eShlLG4scil9fWZ1bmN0aW9uIHlXdChlLHQscil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg+MT90aGlzLmVhY2goKHQ9PW51bGw/b05lOnR5cGVvZiB0PT0iZnVuY3Rpb24iP3NOZTphTmUpKGUsdCxyPT1udWxsPyIiOnIpKTpVZyh0aGlzLm5vZGUoKSxlKX1mdW5jdGlvbiBVZyhlLHQpe3JldHVybiBlLnN0eWxlLmdldFByb3BlcnR5VmFsdWUodCl8fER6KGUpLmdldENvbXB1dGVkU3R5bGUoZSxudWxsKS5nZXRQcm9wZXJ0eVZhbHVlKHQpfXZhciBFbnQ9TSgoKT0+e01udCgpfSk7ZnVuY3Rpb24gbE5lKGUpe3JldHVybiBmdW5jdGlvbigpe2RlbGV0ZSB0aGlzW2VdfX1mdW5jdGlvbiBjTmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzW2VdPXR9fWZ1bmN0aW9uIHVOZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3ZhciByPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3I9PW51bGw/ZGVsZXRlIHRoaXNbZV06dGhpc1tlXT1yfX1mdW5jdGlvbiB2V3QoZSx0KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD4xP3RoaXMuZWFjaCgodD09bnVsbD9sTmU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/dU5lOmNOZSkoZSx0KSk6dGhpcy5ub2RlKClbZV19dmFyIHhXdD1NKCgpPT57fSk7ZnVuY3Rpb24gYld0KGUpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKX1mdW5jdGlvbiBUbnQoZSl7cmV0dXJuIGUuY2xhc3NMaXN0fHxuZXcgd1d0KGUpfWZ1bmN0aW9uIHdXdChlKXt0aGlzLl9ub2RlPWUsdGhpcy5fbmFtZXM9Yld0KGUuZ2V0QXR0cmlidXRlKCJjbGFzcyIpfHwiIil9ZnVuY3Rpb24gU1d0KGUsdCl7Zm9yKHZhciByPVRudChlKSxuPS0xLGk9dC5sZW5ndGg7KytuPGk7KXIuYWRkKHRbbl0pfWZ1bmN0aW9uIE1XdChlLHQpe2Zvcih2YXIgcj1UbnQoZSksbj0tMSxpPXQubGVuZ3RoOysrbjxpOylyLnJlbW92ZSh0W25dKX1mdW5jdGlvbiBoTmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7U1d0KHRoaXMsZSl9fWZ1bmN0aW9uIGZOZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtNV3QodGhpcyxlKX19ZnVuY3Rpb24gcE5lKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7KHQuYXBwbHkodGhpcyxhcmd1bWVudHMpP1NXdDpNV3QpKHRoaXMsZSl9fWZ1bmN0aW9uIEVXdChlLHQpe3ZhciByPWJXdChlKyIiKTtpZihhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcih2YXIgbj1UbnQodGhpcy5ub2RlKCkpLGk9LTEsbz1yLmxlbmd0aDsrK2k8bzspaWYoIW4uY29udGFpbnMocltpXSkpcmV0dXJuITE7cmV0dXJuITB9cmV0dXJuIHRoaXMuZWFjaCgodHlwZW9mIHQ9PSJmdW5jdGlvbiI/cE5lOnQ/aE5lOmZOZSkocix0KSl9dmFyIFRXdD1NKCgpPT57d1d0LnByb3RvdHlwZT17YWRkOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX25hbWVzLmluZGV4T2YoZSk7dDwwJiYodGhpcy5fbmFtZXMucHVzaChlKSx0aGlzLl9ub2RlLnNldEF0dHJpYnV0ZSgiY2xhc3MiLHRoaXMuX25hbWVzLmpvaW4oIiAiKSkpfSxyZW1vdmU6ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy5fbmFtZXMuaW5kZXhPZihlKTt0Pj0wJiYodGhpcy5fbmFtZXMuc3BsaWNlKHQsMSksdGhpcy5fbm9kZS5zZXRBdHRyaWJ1dGUoImNsYXNzIix0aGlzLl9uYW1lcy5qb2luKCIgIikpKX0sY29udGFpbnM6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX25hbWVzLmluZGV4T2YoZSk+PTB9fX0pO2Z1bmN0aW9uIGROZSgpe3RoaXMudGV4dENvbnRlbnQ9IiJ9ZnVuY3Rpb24gbU5lKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMudGV4dENvbnRlbnQ9ZX19ZnVuY3Rpb24gZ05lKGUpe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PWUuYXBwbHkodGhpcyxhcmd1bWVudHMpO3RoaXMudGV4dENvbnRlbnQ9dD09bnVsbD8iIjp0fX1mdW5jdGlvbiBDV3QoZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5lYWNoKGU9PW51bGw/ZE5lOih0eXBlb2YgZT09ImZ1bmN0aW9uIj9nTmU6bU5lKShlKSk6dGhpcy5ub2RlKCkudGV4dENvbnRlbnR9dmFyIEFXdD1NKCgpPT57fSk7ZnVuY3Rpb24gX05lKCl7dGhpcy5pbm5lckhUTUw9IiJ9ZnVuY3Rpb24geU5lKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuaW5uZXJIVE1MPWV9fWZ1bmN0aW9uIHZOZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD1lLmFwcGx5KHRoaXMsYXJndW1lbnRzKTt0aGlzLmlubmVySFRNTD10PT1udWxsPyIiOnR9fWZ1bmN0aW9uIFBXdChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2goZT09bnVsbD9fTmU6KHR5cGVvZiBlPT0iZnVuY3Rpb24iP3ZOZTp5TmUpKGUpKTp0aGlzLm5vZGUoKS5pbm5lckhUTUx9dmFyIElXdD1NKCgpPT57fSk7ZnVuY3Rpb24geE5lKCl7dGhpcy5uZXh0U2libGluZyYmdGhpcy5wYXJlbnROb2RlLmFwcGVuZENoaWxkKHRoaXMpfWZ1bmN0aW9uIExXdCgpe3JldHVybiB0aGlzLmVhY2goeE5lKX12YXIga1d0PU0oKCk9Pnt9KTtmdW5jdGlvbiBiTmUoKXt0aGlzLnByZXZpb3VzU2libGluZyYmdGhpcy5wYXJlbnROb2RlLmluc2VydEJlZm9yZSh0aGlzLHRoaXMucGFyZW50Tm9kZS5maXJzdENoaWxkKX1mdW5jdGlvbiBSV3QoKXtyZXR1cm4gdGhpcy5lYWNoKGJOZSl9dmFyIE5XdD1NKCgpPT57fSk7ZnVuY3Rpb24gRFd0KGUpe3ZhciB0PXR5cGVvZiBlPT0iZnVuY3Rpb24iP2U6a3ooZSk7cmV0dXJuIHRoaXMuc2VsZWN0KGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuYXBwZW5kQ2hpbGQodC5hcHBseSh0aGlzLGFyZ3VtZW50cykpfSl9dmFyIE9XdD1NKCgpPT57dm50KCl9KTtmdW5jdGlvbiB3TmUoKXtyZXR1cm4gbnVsbH1mdW5jdGlvbiB6V3QoZSx0KXt2YXIgcj10eXBlb2YgZT09ImZ1bmN0aW9uIj9lOmt6KGUpLG49dD09bnVsbD93TmU6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/dDpDMSh0KTtyZXR1cm4gdGhpcy5zZWxlY3QoZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5pbnNlcnRCZWZvcmUoci5hcHBseSh0aGlzLGFyZ3VtZW50cyksbi5hcHBseSh0aGlzLGFyZ3VtZW50cyl8fG51bGwpfSl9dmFyIEZXdD1NKCgpPT57dm50KCk7UnooKX0pO2Z1bmN0aW9uIFNOZSgpe3ZhciBlPXRoaXMucGFyZW50Tm9kZTtlJiZlLnJlbW92ZUNoaWxkKHRoaXMpfWZ1bmN0aW9uIEJXdCgpe3JldHVybiB0aGlzLmVhY2goU05lKX12YXIgSFd0PU0oKCk9Pnt9KTtmdW5jdGlvbiBNTmUoKXt2YXIgZT10aGlzLmNsb25lTm9kZSghMSksdD10aGlzLnBhcmVudE5vZGU7cmV0dXJuIHQ/dC5pbnNlcnRCZWZvcmUoZSx0aGlzLm5leHRTaWJsaW5nKTplfWZ1bmN0aW9uIEVOZSgpe3ZhciBlPXRoaXMuY2xvbmVOb2RlKCEwKSx0PXRoaXMucGFyZW50Tm9kZTtyZXR1cm4gdD90Lmluc2VydEJlZm9yZShlLHRoaXMubmV4dFNpYmxpbmcpOmV9ZnVuY3Rpb24gVld0KGUpe3JldHVybiB0aGlzLnNlbGVjdChlP0VOZTpNTmUpfXZhciBVV3Q9TSgoKT0+e30pO2Z1bmN0aW9uIHFXdChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLnByb3BlcnR5KCJfX2RhdGFfXyIsZSk6dGhpcy5ub2RlKCkuX19kYXRhX199dmFyIEdXdD1NKCgpPT57fSk7ZnVuY3Rpb24gVE5lKGUsdCxyKXtyZXR1cm4gZT1qV3QoZSx0LHIpLGZ1bmN0aW9uKG4pe3ZhciBpPW4ucmVsYXRlZFRhcmdldDsoIWl8fGkhPT10aGlzJiYhKGkuY29tcGFyZURvY3VtZW50UG9zaXRpb24odGhpcykmOCkpJiZlLmNhbGwodGhpcyxuKX19ZnVuY3Rpb24gald0KGUsdCxyKXtyZXR1cm4gZnVuY3Rpb24obil7dmFyIGk9WXI7WXI9bjt0cnl7ZS5jYWxsKHRoaXMsdGhpcy5fX2RhdGFfXyx0LHIpfWZpbmFsbHl7WXI9aX19fWZ1bmN0aW9uIENOZShlKXtyZXR1cm4gZS50cmltKCkuc3BsaXQoL158XHMrLykubWFwKGZ1bmN0aW9uKHQpe3ZhciByPSIiLG49dC5pbmRleE9mKCIuIik7cmV0dXJuIG4+PTAmJihyPXQuc2xpY2UobisxKSx0PXQuc2xpY2UoMCxuKSkse3R5cGU6dCxuYW1lOnJ9fSl9ZnVuY3Rpb24gQU5lKGUpe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PXRoaXMuX19vbjtpZighIXQpe2Zvcih2YXIgcj0wLG49LTEsaT10Lmxlbmd0aCxvO3I8aTsrK3Ipbz10W3JdLCghZS50eXBlfHxvLnR5cGU9PT1lLnR5cGUpJiZvLm5hbWU9PT1lLm5hbWU/dGhpcy5yZW1vdmVFdmVudExpc3RlbmVyKG8udHlwZSxvLmxpc3RlbmVyLG8uY2FwdHVyZSk6dFsrK25dPW87KytuP3QubGVuZ3RoPW46ZGVsZXRlIHRoaXMuX19vbn19fWZ1bmN0aW9uIFBOZShlLHQscil7dmFyIG49WVd0Lmhhc093blByb3BlcnR5KGUudHlwZSk/VE5lOmpXdDtyZXR1cm4gZnVuY3Rpb24oaSxvLGEpe3ZhciBzPXRoaXMuX19vbixsLGM9bih0LG8sYSk7aWYocyl7Zm9yKHZhciB1PTAsaD1zLmxlbmd0aDt1PGg7Kyt1KWlmKChsPXNbdV0pLnR5cGU9PT1lLnR5cGUmJmwubmFtZT09PWUubmFtZSl7dGhpcy5yZW1vdmVFdmVudExpc3RlbmVyKGwudHlwZSxsLmxpc3RlbmVyLGwuY2FwdHVyZSksdGhpcy5hZGRFdmVudExpc3RlbmVyKGwudHlwZSxsLmxpc3RlbmVyPWMsbC5jYXB0dXJlPXIpLGwudmFsdWU9dDtyZXR1cm59fXRoaXMuYWRkRXZlbnRMaXN0ZW5lcihlLnR5cGUsYyxyKSxsPXt0eXBlOmUudHlwZSxuYW1lOmUubmFtZSx2YWx1ZTp0LGxpc3RlbmVyOmMsY2FwdHVyZTpyfSxzP3MucHVzaChsKTp0aGlzLl9fb249W2xdfX1mdW5jdGlvbiBYV3QoZSx0LHIpe3ZhciBuPUNOZShlKyIiKSxpLG89bi5sZW5ndGgsYTtpZihhcmd1bWVudHMubGVuZ3RoPDIpe3ZhciBzPXRoaXMubm9kZSgpLl9fb247aWYocyl7Zm9yKHZhciBsPTAsYz1zLmxlbmd0aCx1O2w8YzsrK2wpZm9yKGk9MCx1PXNbbF07aTxvOysraSlpZigoYT1uW2ldKS50eXBlPT09dS50eXBlJiZhLm5hbWU9PT11Lm5hbWUpcmV0dXJuIHUudmFsdWV9cmV0dXJufWZvcihzPXQ/UE5lOkFOZSxyPT1udWxsJiYocj0hMSksaT0wO2k8bzsrK2kpdGhpcy5lYWNoKHMobltpXSx0LHIpKTtyZXR1cm4gdGhpc31mdW5jdGlvbiBDbnQoZSx0LHIsbil7dmFyIGk9WXI7ZS5zb3VyY2VFdmVudD1ZcixZcj1lO3RyeXtyZXR1cm4gdC5hcHBseShyLG4pfWZpbmFsbHl7WXI9aX19dmFyIFlXdCxZcixXV3QsT3o9TSgoKT0+e1lXdD17fSxZcj1udWxsO3R5cGVvZiBkb2N1bWVudCE9InVuZGVmaW5lZCImJihXV3Q9ZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LCJvbm1vdXNlZW50ZXIiaW4gV1d0fHwoWVd0PXttb3VzZWVudGVyOiJtb3VzZW92ZXIiLG1vdXNlbGVhdmU6Im1vdXNlb3V0In0pKX0pO2Z1bmN0aW9uICRXdChlLHQscil7dmFyIG49RHooZSksaT1uLkN1c3RvbUV2ZW50O3R5cGVvZiBpPT0iZnVuY3Rpb24iP2k9bmV3IGkodCxyKTooaT1uLmRvY3VtZW50LmNyZWF0ZUV2ZW50KCJFdmVudCIpLHI/KGkuaW5pdEV2ZW50KHQsci5idWJibGVzLHIuY2FuY2VsYWJsZSksaS5kZXRhaWw9ci5kZXRhaWwpOmkuaW5pdEV2ZW50KHQsITEsITEpKSxlLmRpc3BhdGNoRXZlbnQoaSl9ZnVuY3Rpb24gSU5lKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuICRXdCh0aGlzLGUsdCl9fWZ1bmN0aW9uIExOZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiAkV3QodGhpcyxlLHQuYXBwbHkodGhpcyxhcmd1bWVudHMpKX19ZnVuY3Rpb24gS1d0KGUsdCl7cmV0dXJuIHRoaXMuZWFjaCgodHlwZW9mIHQ9PSJmdW5jdGlvbiI/TE5lOklOZSkoZSx0KSl9dmFyIFpXdD1NKCgpPT57TW50KCl9KTtmdW5jdGlvbiBnaShlLHQpe3RoaXMuX2dyb3Vwcz1lLHRoaXMuX3BhcmVudHM9dH1mdW5jdGlvbiBKV3QoKXtyZXR1cm4gbmV3IGdpKFtbZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50XV0sQW50KX12YXIgQW50LGZkLE91PU0oKCk9Pnt6R3QoKTtCR3QoKTtWR3QoKTtqR3QoKTtTbnQoKTskR3QoKTtaR3QoKTtRR3QoKTtlV3QoKTtuV3QoKTtvV3QoKTtzV3QoKTtjV3QoKTtoV3QoKTtwV3QoKTttV3QoKTtfV3QoKTtFbnQoKTt4V3QoKTtUV3QoKTtBV3QoKTtJV3QoKTtrV3QoKTtOV3QoKTtPV3QoKTtGV3QoKTtIV3QoKTtVV3QoKTtHV3QoKTtPeigpO1pXdCgpO0FudD1bbnVsbF07Z2kucHJvdG90eXBlPUpXdC5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOmdpLHNlbGVjdDpPR3Qsc2VsZWN0QWxsOkZHdCxmaWx0ZXI6SEd0LGRhdGE6WUd0LGVudGVyOlVHdCxleGl0OlhHdCxqb2luOktHdCxtZXJnZTpKR3Qsb3JkZXI6dFd0LHNvcnQ6cld0LGNhbGw6aVd0LG5vZGVzOmFXdCxub2RlOmxXdCxzaXplOnVXdCxlbXB0eTpmV3QsZWFjaDpkV3QsYXR0cjpnV3Qsc3R5bGU6eVd0LHByb3BlcnR5OnZXdCxjbGFzc2VkOkVXdCx0ZXh0OkNXdCxodG1sOlBXdCxyYWlzZTpMV3QsbG93ZXI6Uld0LGFwcGVuZDpEV3QsaW5zZXJ0OnpXdCxyZW1vdmU6Qld0LGNsb25lOlZXdCxkYXR1bTpxV3Qsb246WFd0LGRpc3BhdGNoOktXdH07ZmQ9Sld0fSk7ZnVuY3Rpb24gcGQoZSl7cmV0dXJuIHR5cGVvZiBlPT0ic3RyaW5nIj9uZXcgZ2koW1tkb2N1bWVudC5xdWVyeVNlbGVjdG9yKGUpXV0sW2RvY3VtZW50LmRvY3VtZW50RWxlbWVudF0pOm5ldyBnaShbW2VdXSxBbnQpfXZhciBRV3Q9TSgoKT0+e091KCl9KTtmdW5jdGlvbiB6eigpe2Zvcih2YXIgZT1Zcix0O3Q9ZS5zb3VyY2VFdmVudDspZT10O3JldHVybiBlfXZhciBQbnQ9TSgoKT0+e096KCl9KTtmdW5jdGlvbiBGeihlLHQpe3ZhciByPWUub3duZXJTVkdFbGVtZW50fHxlO2lmKHIuY3JlYXRlU1ZHUG9pbnQpe3ZhciBuPXIuY3JlYXRlU1ZHUG9pbnQoKTtyZXR1cm4gbi54PXQuY2xpZW50WCxuLnk9dC5jbGllbnRZLG49bi5tYXRyaXhUcmFuc2Zvcm0oZS5nZXRTY3JlZW5DVE0oKS5pbnZlcnNlKCkpLFtuLngsbi55XX12YXIgaT1lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO3JldHVyblt0LmNsaWVudFgtaS5sZWZ0LWUuY2xpZW50TGVmdCx0LmNsaWVudFktaS50b3AtZS5jbGllbnRUb3BdfXZhciBJbnQ9TSgoKT0+e30pO2Z1bmN0aW9uIGlTKGUpe3ZhciB0PXp6KCk7cmV0dXJuIHQuY2hhbmdlZFRvdWNoZXMmJih0PXQuY2hhbmdlZFRvdWNoZXNbMF0pLEZ6KGUsdCl9dmFyIHRZdD1NKCgpPT57UG50KCk7SW50KCl9KTtmdW5jdGlvbiBCeihlLHQscil7YXJndW1lbnRzLmxlbmd0aDwzJiYocj10LHQ9enooKS5jaGFuZ2VkVG91Y2hlcyk7Zm9yKHZhciBuPTAsaT10P3QubGVuZ3RoOjAsbztuPGk7KytuKWlmKChvPXRbbl0pLmlkZW50aWZpZXI9PT1yKXJldHVybiBGeihlLG8pO3JldHVybiBudWxsfXZhciBlWXQ9TSgoKT0+e1BudCgpO0ludCgpfSk7dmFyIElzPU0oKCk9PntibnQoKTt0WXQoKTtMeigpO1FXdCgpO091KCk7UnooKTt4bnQoKTtFbnQoKTtlWXQoKTtPeigpfSk7ZnVuY3Rpb24gSHooKXtZci5wcmV2ZW50RGVmYXVsdCgpLFlyLnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpfXZhciByWXQ9TSgoKT0+e0lzKCl9KTtmdW5jdGlvbiBMbnQoZSl7dmFyIHQ9ZS5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQscj1wZChlKS5vbigiZHJhZ3N0YXJ0LmRyYWciLEh6LCEwKTsib25zZWxlY3RzdGFydCJpbiB0P3Iub24oInNlbGVjdHN0YXJ0LmRyYWciLEh6LCEwKToodC5fX25vc2VsZWN0PXQuc3R5bGUuTW96VXNlclNlbGVjdCx0LnN0eWxlLk1velVzZXJTZWxlY3Q9Im5vbmUiKX1mdW5jdGlvbiBrbnQoZSx0KXt2YXIgcj1lLmRvY3VtZW50LmRvY3VtZW50RWxlbWVudCxuPXBkKGUpLm9uKCJkcmFnc3RhcnQuZHJhZyIsbnVsbCk7dCYmKG4ub24oImNsaWNrLmRyYWciLEh6LCEwKSxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7bi5vbigiY2xpY2suZHJhZyIsbnVsbCl9LDApKSwib25zZWxlY3RzdGFydCJpbiByP24ub24oInNlbGVjdHN0YXJ0LmRyYWciLG51bGwpOihyLnN0eWxlLk1velVzZXJTZWxlY3Q9ci5fX25vc2VsZWN0LGRlbGV0ZSByLl9fbm9zZWxlY3QpfXZhciBuWXQ9TSgoKT0+e0lzKCk7cll0KCl9KTt2YXIgaVl0PU0oKCk9PntuWXQoKX0pO2Z1bmN0aW9uIFZ6KGUsdCxyKXtlLnByb3RvdHlwZT10LnByb3RvdHlwZT1yLHIuY29uc3RydWN0b3I9ZX1mdW5jdGlvbiBSbnQoZSx0KXt2YXIgcj1PYmplY3QuY3JlYXRlKGUucHJvdG90eXBlKTtmb3IodmFyIG4gaW4gdClyW25dPXRbbl07cmV0dXJuIHJ9dmFyIG9ZdD1NKCgpPT57fSk7ZnVuY3Rpb24gTUEoKXt9ZnVuY3Rpb24gc1l0KCl7cmV0dXJuIHRoaXMucmdiKCkuZm9ybWF0SGV4KCl9ZnVuY3Rpb24gQk5lKCl7cmV0dXJuIHBZdCh0aGlzKS5mb3JtYXRIc2woKX1mdW5jdGlvbiBsWXQoKXtyZXR1cm4gdGhpcy5yZ2IoKS5mb3JtYXRSZ2IoKX1mdW5jdGlvbiBxZyhlKXt2YXIgdCxyO3JldHVybiBlPShlKyIiKS50cmltKCkudG9Mb3dlckNhc2UoKSwodD1rTmUuZXhlYyhlKSk/KHI9dFsxXS5sZW5ndGgsdD1wYXJzZUludCh0WzFdLDE2KSxyPT09Nj9jWXQodCk6cj09PTM/bmV3IGdsKHQ+PjgmMTV8dD4+NCYyNDAsdD4+NCYxNXx0JjI0MCwodCYxNSk8PDR8dCYxNSwxKTpyPT09OD9Veih0Pj4yNCYyNTUsdD4+MTYmMjU1LHQ+PjgmMjU1LCh0JjI1NSkvMjU1KTpyPT09ND9Veih0Pj4xMiYxNXx0Pj44JjI0MCx0Pj44JjE1fHQ+PjQmMjQwLHQ+PjQmMTV8dCYyNDAsKCh0JjE1KTw8NHx0JjE1KS8yNTUpOm51bGwpOih0PVJOZS5leGVjKGUpKT9uZXcgZ2wodFsxXSx0WzJdLHRbM10sMSk6KHQ9Tk5lLmV4ZWMoZSkpP25ldyBnbCh0WzFdKjI1NS8xMDAsdFsyXSoyNTUvMTAwLHRbM10qMjU1LzEwMCwxKToodD1ETmUuZXhlYyhlKSk/VXoodFsxXSx0WzJdLHRbM10sdFs0XSk6KHQ9T05lLmV4ZWMoZSkpP1V6KHRbMV0qMjU1LzEwMCx0WzJdKjI1NS8xMDAsdFszXSoyNTUvMTAwLHRbNF0pOih0PXpOZS5leGVjKGUpKT9mWXQodFsxXSx0WzJdLzEwMCx0WzNdLzEwMCwxKToodD1GTmUuZXhlYyhlKSk/Zll0KHRbMV0sdFsyXS8xMDAsdFszXS8xMDAsdFs0XSk6YVl0Lmhhc093blByb3BlcnR5KGUpP2NZdChhWXRbZV0pOmU9PT0idHJhbnNwYXJlbnQiP25ldyBnbChOYU4sTmFOLE5hTiwwKTpudWxsfWZ1bmN0aW9uIGNZdChlKXtyZXR1cm4gbmV3IGdsKGU+PjE2JjI1NSxlPj44JjI1NSxlJjI1NSwxKX1mdW5jdGlvbiBVeihlLHQscixuKXtyZXR1cm4gbjw9MCYmKGU9dD1yPU5hTiksbmV3IGdsKGUsdCxyLG4pfWZ1bmN0aW9uIEhOZShlKXtyZXR1cm4gZSBpbnN0YW5jZW9mIE1BfHwoZT1xZyhlKSksZT8oZT1lLnJnYigpLG5ldyBnbChlLnIsZS5nLGUuYixlLm9wYWNpdHkpKTpuZXcgZ2x9ZnVuY3Rpb24gYVMoZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP0hOZShlKTpuZXcgZ2woZSx0LHIsbj09bnVsbD8xOm4pfWZ1bmN0aW9uIGdsKGUsdCxyLG4pe3RoaXMucj0rZSx0aGlzLmc9K3QsdGhpcy5iPStyLHRoaXMub3BhY2l0eT0rbn1mdW5jdGlvbiB1WXQoKXtyZXR1cm4iIyIrTm50KHRoaXMucikrTm50KHRoaXMuZykrTm50KHRoaXMuYil9ZnVuY3Rpb24gaFl0KCl7dmFyIGU9dGhpcy5vcGFjaXR5O3JldHVybiBlPWlzTmFOKGUpPzE6TWF0aC5tYXgoMCxNYXRoLm1pbigxLGUpKSwoZT09PTE/InJnYigiOiJyZ2JhKCIpK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQodGhpcy5yKXx8MCkpKyIsICIrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCh0aGlzLmcpfHwwKSkrIiwgIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKHRoaXMuYil8fDApKSsoZT09PTE/IikiOiIsICIrZSsiKSIpfWZ1bmN0aW9uIE5udChlKXtyZXR1cm4gZT1NYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKGUpfHwwKSksKGU8MTY/IjAiOiIiKStlLnRvU3RyaW5nKDE2KX1mdW5jdGlvbiBmWXQoZSx0LHIsbil7cmV0dXJuIG48PTA/ZT10PXI9TmFOOnI8PTB8fHI+PTE/ZT10PU5hTjp0PD0wJiYoZT1OYU4pLG5ldyBDZihlLHQscixuKX1mdW5jdGlvbiBwWXQoZSl7aWYoZSBpbnN0YW5jZW9mIENmKXJldHVybiBuZXcgQ2YoZS5oLGUucyxlLmwsZS5vcGFjaXR5KTtpZihlIGluc3RhbmNlb2YgTUF8fChlPXFnKGUpKSwhZSlyZXR1cm4gbmV3IENmO2lmKGUgaW5zdGFuY2VvZiBDZilyZXR1cm4gZTtlPWUucmdiKCk7dmFyIHQ9ZS5yLzI1NSxyPWUuZy8yNTUsbj1lLmIvMjU1LGk9TWF0aC5taW4odCxyLG4pLG89TWF0aC5tYXgodCxyLG4pLGE9TmFOLHM9by1pLGw9KG8raSkvMjtyZXR1cm4gcz8odD09PW8/YT0oci1uKS9zKyhyPG4pKjY6cj09PW8/YT0obi10KS9zKzI6YT0odC1yKS9zKzQscy89bDwuNT9vK2k6Mi1vLWksYSo9NjApOnM9bD4wJiZsPDE/MDphLG5ldyBDZihhLHMsbCxlLm9wYWNpdHkpfWZ1bmN0aW9uIGRZdChlLHQscixuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTE/cFl0KGUpOm5ldyBDZihlLHQscixuPT1udWxsPzE6bil9ZnVuY3Rpb24gQ2YoZSx0LHIsbil7dGhpcy5oPStlLHRoaXMucz0rdCx0aGlzLmw9K3IsdGhpcy5vcGFjaXR5PStufWZ1bmN0aW9uIERudChlLHQscil7cmV0dXJuKGU8NjA/dCsoci10KSplLzYwOmU8MTgwP3I6ZTwyNDA/dCsoci10KSooMjQwLWUpLzYwOnQpKjI1NX12YXIgd0EscXosb1MsU0EsQWYsa05lLFJOZSxOTmUsRE5lLE9OZSx6TmUsRk5lLGFZdCxtWXQ9TSgoKT0+e29ZdCgpO3dBPS43LHF6PTEvd0Esb1M9IlxccyooWystXT9cXGQrKVxccyoiLFNBPSJcXHMqKFsrLV0/XFxkKlxcLj9cXGQrKD86W2VFXVsrLV0/XFxkKyk/KVxccyoiLEFmPSJcXHMqKFsrLV0/XFxkKlxcLj9cXGQrKD86W2VFXVsrLV0/XFxkKyk/KSVcXHMqIixrTmU9L14jKFswLTlhLWZdezMsOH0pJC8sUk5lPW5ldyBSZWdFeHAoIl5yZ2JcXCgiK1tvUyxvUyxvU10rIlxcKSQiKSxOTmU9bmV3IFJlZ0V4cCgiXnJnYlxcKCIrW0FmLEFmLEFmXSsiXFwpJCIpLEROZT1uZXcgUmVnRXhwKCJecmdiYVxcKCIrW29TLG9TLG9TLFNBXSsiXFwpJCIpLE9OZT1uZXcgUmVnRXhwKCJecmdiYVxcKCIrW0FmLEFmLEFmLFNBXSsiXFwpJCIpLHpOZT1uZXcgUmVnRXhwKCJeaHNsXFwoIitbU0EsQWYsQWZdKyJcXCkkIiksRk5lPW5ldyBSZWdFeHAoIl5oc2xhXFwoIitbU0EsQWYsQWYsU0FdKyJcXCkkIiksYVl0PXthbGljZWJsdWU6MTU3OTIzODMsYW50aXF1ZXdoaXRlOjE2NDQ0Mzc1LGFxdWE6NjU1MzUsYXF1YW1hcmluZTo4Mzg4NTY0LGF6dXJlOjE1Nzk0MTc1LGJlaWdlOjE2MTE5MjYwLGJpc3F1ZToxNjc3MDI0NCxibGFjazowLGJsYW5jaGVkYWxtb25kOjE2NzcyMDQ1LGJsdWU6MjU1LGJsdWV2aW9sZXQ6OTA1NTIwMixicm93bjoxMDgyNDIzNCxidXJseXdvb2Q6MTQ1OTYyMzEsY2FkZXRibHVlOjYyNjY1MjgsY2hhcnRyZXVzZTo4Mzg4MzUyLGNob2NvbGF0ZToxMzc4OTQ3MCxjb3JhbDoxNjc0NDI3Mixjb3JuZmxvd2VyYmx1ZTo2NTkxOTgxLGNvcm5zaWxrOjE2Nzc1Mzg4LGNyaW1zb246MTQ0MjMxMDAsY3lhbjo2NTUzNSxkYXJrYmx1ZToxMzksZGFya2N5YW46MzU3MjMsZGFya2dvbGRlbnJvZDoxMjA5MjkzOSxkYXJrZ3JheToxMTExOTAxNyxkYXJrZ3JlZW46MjU2MDAsZGFya2dyZXk6MTExMTkwMTcsZGFya2toYWtpOjEyNDMzMjU5LGRhcmttYWdlbnRhOjkxMDk2NDMsZGFya29saXZlZ3JlZW46NTU5Nzk5OSxkYXJrb3JhbmdlOjE2NzQ3NTIwLGRhcmtvcmNoaWQ6MTAwNDAwMTIsZGFya3JlZDo5MTA5NTA0LGRhcmtzYWxtb246MTUzMDg0MTAsZGFya3NlYWdyZWVuOjk0MTk5MTksZGFya3NsYXRlYmx1ZTo0NzM0MzQ3LGRhcmtzbGF0ZWdyYXk6MzEwMDQ5NSxkYXJrc2xhdGVncmV5OjMxMDA0OTUsZGFya3R1cnF1b2lzZTo1Mjk0NSxkYXJrdmlvbGV0Ojk2OTk1MzksZGVlcHBpbms6MTY3MTY5NDcsZGVlcHNreWJsdWU6NDkxNTEsZGltZ3JheTo2OTA4MjY1LGRpbWdyZXk6NjkwODI2NSxkb2RnZXJibHVlOjIwMDMxOTksZmlyZWJyaWNrOjExNjc0MTQ2LGZsb3JhbHdoaXRlOjE2Nzc1OTIwLGZvcmVzdGdyZWVuOjIyNjM4NDIsZnVjaHNpYToxNjcxMTkzNSxnYWluc2Jvcm86MTQ0NzQ0NjAsZ2hvc3R3aGl0ZToxNjMxNjY3MSxnb2xkOjE2NzY2NzIwLGdvbGRlbnJvZDoxNDMyOTEyMCxncmF5Ojg0MjE1MDQsZ3JlZW46MzI3NjgsZ3JlZW55ZWxsb3c6MTE0MDMwNTUsZ3JleTo4NDIxNTA0LGhvbmV5ZGV3OjE1Nzk0MTYwLGhvdHBpbms6MTY3Mzg3NDAsaW5kaWFucmVkOjEzNDU4NTI0LGluZGlnbzo0OTE1MzMwLGl2b3J5OjE2Nzc3MjAwLGtoYWtpOjE1Nzg3NjYwLGxhdmVuZGVyOjE1MTMyNDEwLGxhdmVuZGVyYmx1c2g6MTY3NzMzNjUsbGF3bmdyZWVuOjgxOTA5NzYsbGVtb25jaGlmZm9uOjE2Nzc1ODg1LGxpZ2h0Ymx1ZToxMTM5MzI1NCxsaWdodGNvcmFsOjE1NzYxNTM2LGxpZ2h0Y3lhbjoxNDc0NTU5OSxsaWdodGdvbGRlbnJvZHllbGxvdzoxNjQ0ODIxMCxsaWdodGdyYXk6MTM4ODIzMjMsbGlnaHRncmVlbjo5NDk4MjU2LGxpZ2h0Z3JleToxMzg4MjMyMyxsaWdodHBpbms6MTY3NTg0NjUsbGlnaHRzYWxtb246MTY3NTI3NjIsbGlnaHRzZWFncmVlbjoyMTQyODkwLGxpZ2h0c2t5Ymx1ZTo4OTAwMzQ2LGxpZ2h0c2xhdGVncmF5Ojc4MzM3NTMsbGlnaHRzbGF0ZWdyZXk6NzgzMzc1MyxsaWdodHN0ZWVsYmx1ZToxMTU4NDczNCxsaWdodHllbGxvdzoxNjc3NzE4NCxsaW1lOjY1MjgwLGxpbWVncmVlbjozMzI5MzMwLGxpbmVuOjE2NDQ1NjcwLG1hZ2VudGE6MTY3MTE5MzUsbWFyb29uOjgzODg2MDgsbWVkaXVtYXF1YW1hcmluZTo2NzM3MzIyLG1lZGl1bWJsdWU6MjA1LG1lZGl1bW9yY2hpZDoxMjIxMTY2NyxtZWRpdW1wdXJwbGU6OTY2MjY4MyxtZWRpdW1zZWFncmVlbjozOTc4MDk3LG1lZGl1bXNsYXRlYmx1ZTo4MDg3NzkwLG1lZGl1bXNwcmluZ2dyZWVuOjY0MTU0LG1lZGl1bXR1cnF1b2lzZTo0NzcyMzAwLG1lZGl1bXZpb2xldHJlZDoxMzA0NzE3MyxtaWRuaWdodGJsdWU6MTY0NDkxMixtaW50Y3JlYW06MTYxMjE4NTAsbWlzdHlyb3NlOjE2NzcwMjczLG1vY2Nhc2luOjE2NzcwMjI5LG5hdmFqb3doaXRlOjE2NzY4Njg1LG5hdnk6MTI4LG9sZGxhY2U6MTY2NDM1NTgsb2xpdmU6ODQyMTM3NixvbGl2ZWRyYWI6NzA0ODczOSxvcmFuZ2U6MTY3NTM5MjAsb3JhbmdlcmVkOjE2NzI5MzQ0LG9yY2hpZDoxNDMxNTczNCxwYWxlZ29sZGVucm9kOjE1NjU3MTMwLHBhbGVncmVlbjoxMDAyNTg4MCxwYWxldHVycXVvaXNlOjExNTI5OTY2LHBhbGV2aW9sZXRyZWQ6MTQzODEyMDMscGFwYXlhd2hpcDoxNjc3MzA3NyxwZWFjaHB1ZmY6MTY3Njc2NzMscGVydToxMzQ2ODk5MSxwaW5rOjE2NzYxMDM1LHBsdW06MTQ1MjQ2MzcscG93ZGVyYmx1ZToxMTU5MTkxMCxwdXJwbGU6ODM4ODczNixyZWJlY2NhcHVycGxlOjY2OTc4ODEscmVkOjE2NzExNjgwLHJvc3licm93bjoxMjM1NzUxOSxyb3lhbGJsdWU6NDI4Njk0NSxzYWRkbGVicm93bjo5MTI3MTg3LHNhbG1vbjoxNjQxNjg4MixzYW5keWJyb3duOjE2MDMyODY0LHNlYWdyZWVuOjMwNTAzMjcsc2Vhc2hlbGw6MTY3NzQ2Mzgsc2llbm5hOjEwNTA2Nzk3LHNpbHZlcjoxMjYzMjI1Nixza3libHVlOjg5MDAzMzEsc2xhdGVibHVlOjY5NzAwNjEsc2xhdGVncmF5OjczNzI5NDQsc2xhdGVncmV5OjczNzI5NDQsc25vdzoxNjc3NTkzMCxzcHJpbmdncmVlbjo2NTQwNyxzdGVlbGJsdWU6NDYyMDk4MCx0YW46MTM4MDg3ODAsdGVhbDozMjg5Nix0aGlzdGxlOjE0MjA0ODg4LHRvbWF0bzoxNjczNzA5NSx0dXJxdW9pc2U6NDI1MTg1Nix2aW9sZXQ6MTU2MzEwODYsd2hlYXQ6MTYxMTMzMzEsd2hpdGU6MTY3NzcyMTUsd2hpdGVzbW9rZToxNjExOTI4NSx5ZWxsb3c6MTY3NzY5NjAseWVsbG93Z3JlZW46MTAxNDUwNzR9O1Z6KE1BLHFnLHtjb3B5OmZ1bmN0aW9uKGUpe3JldHVybiBPYmplY3QuYXNzaWduKG5ldyB0aGlzLmNvbnN0cnVjdG9yLHRoaXMsZSl9LGRpc3BsYXlhYmxlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMucmdiKCkuZGlzcGxheWFibGUoKX0saGV4OnNZdCxmb3JtYXRIZXg6c1l0LGZvcm1hdEhzbDpCTmUsZm9ybWF0UmdiOmxZdCx0b1N0cmluZzpsWXR9KTtWeihnbCxhUyxSbnQoTUEse2JyaWdodGVyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/cXo6TWF0aC5wb3cocXosZSksbmV3IGdsKHRoaXMuciplLHRoaXMuZyplLHRoaXMuYiplLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihlKXtyZXR1cm4gZT1lPT1udWxsP3dBOk1hdGgucG93KHdBLGUpLG5ldyBnbCh0aGlzLnIqZSx0aGlzLmcqZSx0aGlzLmIqZSx0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpc30sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4tLjU8PXRoaXMuciYmdGhpcy5yPDI1NS41JiYtLjU8PXRoaXMuZyYmdGhpcy5nPDI1NS41JiYtLjU8PXRoaXMuYiYmdGhpcy5iPDI1NS41JiYwPD10aGlzLm9wYWNpdHkmJnRoaXMub3BhY2l0eTw9MX0saGV4OnVZdCxmb3JtYXRIZXg6dVl0LGZvcm1hdFJnYjpoWXQsdG9TdHJpbmc6aFl0fSkpO1Z6KENmLGRZdCxSbnQoTUEse2JyaWdodGVyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/cXo6TWF0aC5wb3cocXosZSksbmV3IENmKHRoaXMuaCx0aGlzLnMsdGhpcy5sKmUsdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKGUpe3JldHVybiBlPWU9PW51bGw/d0E6TWF0aC5wb3cod0EsZSksbmV3IENmKHRoaXMuaCx0aGlzLnMsdGhpcy5sKmUsdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5oJTM2MCsodGhpcy5oPDApKjM2MCx0PWlzTmFOKGUpfHxpc05hTih0aGlzLnMpPzA6dGhpcy5zLHI9dGhpcy5sLG49cisocjwuNT9yOjEtcikqdCxpPTIqci1uO3JldHVybiBuZXcgZ2woRG50KGU+PTI0MD9lLTI0MDplKzEyMCxpLG4pLERudChlLGksbiksRG50KGU8MTIwP2UrMjQwOmUtMTIwLGksbiksdGhpcy5vcGFjaXR5KX0sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4oMDw9dGhpcy5zJiZ0aGlzLnM8PTF8fGlzTmFOKHRoaXMucykpJiYwPD10aGlzLmwmJnRoaXMubDw9MSYmMDw9dGhpcy5vcGFjaXR5JiZ0aGlzLm9wYWNpdHk8PTF9LGZvcm1hdEhzbDpmdW5jdGlvbigpe3ZhciBlPXRoaXMub3BhY2l0eTtyZXR1cm4gZT1pc05hTihlKT8xOk1hdGgubWF4KDAsTWF0aC5taW4oMSxlKSksKGU9PT0xPyJoc2woIjoiaHNsYSgiKSsodGhpcy5ofHwwKSsiLCAiKyh0aGlzLnN8fDApKjEwMCsiJSwgIisodGhpcy5sfHwwKSoxMDArIiUiKyhlPT09MT8iKSI6IiwgIitlKyIpIil9fSkpfSk7dmFyIE9udD1NKCgpPT57bVl0KCl9KTtmdW5jdGlvbiB6bnQoZSx0LHIsbixpKXt2YXIgbz1lKmUsYT1vKmU7cmV0dXJuKCgxLTMqZSszKm8tYSkqdCsoNC02Km8rMyphKSpyKygxKzMqZSszKm8tMyphKSpuK2EqaSkvNn1mdW5jdGlvbiBnWXQoZSl7dmFyIHQ9ZS5sZW5ndGgtMTtyZXR1cm4gZnVuY3Rpb24ocil7dmFyIG49cjw9MD9yPTA6cj49MT8ocj0xLHQtMSk6TWF0aC5mbG9vcihyKnQpLGk9ZVtuXSxvPWVbbisxXSxhPW4+MD9lW24tMV06MippLW8scz1uPHQtMT9lW24rMl06MipvLWk7cmV0dXJuIHpudCgoci1uL3QpKnQsYSxpLG8scyl9fXZhciBGbnQ9TSgoKT0+e30pO2Z1bmN0aW9uIF9ZdChlKXt2YXIgdD1lLmxlbmd0aDtyZXR1cm4gZnVuY3Rpb24ocil7dmFyIG49TWF0aC5mbG9vcigoKHIlPTEpPDA/KytyOnIpKnQpLGk9ZVsobit0LTEpJXRdLG89ZVtuJXRdLGE9ZVsobisxKSV0XSxzPWVbKG4rMikldF07cmV0dXJuIHpudCgoci1uL3QpKnQsaSxvLGEscyl9fXZhciB5WXQ9TSgoKT0+e0ZudCgpfSk7ZnVuY3Rpb24gQm50KGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX12YXIgdll0PU0oKCk9Pnt9KTtmdW5jdGlvbiBWTmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocil7cmV0dXJuIGUrcip0fX1mdW5jdGlvbiBVTmUoZSx0LHIpe3JldHVybiBlPU1hdGgucG93KGUsciksdD1NYXRoLnBvdyh0LHIpLWUscj0xL3IsZnVuY3Rpb24obil7cmV0dXJuIE1hdGgucG93KGUrbip0LHIpfX1mdW5jdGlvbiB4WXQoZSl7cmV0dXJuKGU9K2UpPT0xP0d6OmZ1bmN0aW9uKHQscil7cmV0dXJuIHItdD9VTmUodCxyLGUpOkJudChpc05hTih0KT9yOnQpfX1mdW5jdGlvbiBHeihlLHQpe3ZhciByPXQtZTtyZXR1cm4gcj9WTmUoZSxyKTpCbnQoaXNOYU4oZSk/dDplKX12YXIgYll0PU0oKCk9Pnt2WXQoKX0pO2Z1bmN0aW9uIHdZdChlKXtyZXR1cm4gZnVuY3Rpb24odCl7dmFyIHI9dC5sZW5ndGgsbj1uZXcgQXJyYXkociksaT1uZXcgQXJyYXkociksbz1uZXcgQXJyYXkociksYSxzO2ZvcihhPTA7YTxyOysrYSlzPWFTKHRbYV0pLG5bYV09cy5yfHwwLGlbYV09cy5nfHwwLG9bYV09cy5ifHwwO3JldHVybiBuPWUobiksaT1lKGkpLG89ZShvKSxzLm9wYWNpdHk9MSxmdW5jdGlvbihsKXtyZXR1cm4gcy5yPW4obCkscy5nPWkobCkscy5iPW8obCkscysiIn19fXZhciBXeixxTmUsR05lLFNZdD1NKCgpPT57T250KCk7Rm50KCk7eVl0KCk7Yll0KCk7V3o9ZnVuY3Rpb24gZSh0KXt2YXIgcj14WXQodCk7ZnVuY3Rpb24gbihpLG8pe3ZhciBhPXIoKGk9YVMoaSkpLnIsKG89YVMobykpLnIpLHM9cihpLmcsby5nKSxsPXIoaS5iLG8uYiksYz1HeihpLm9wYWNpdHksby5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24odSl7cmV0dXJuIGkucj1hKHUpLGkuZz1zKHUpLGkuYj1sKHUpLGkub3BhY2l0eT1jKHUpLGkrIiJ9fXJldHVybiBuLmdhbW1hPWUsbn0oMSk7cU5lPXdZdChnWXQpLEdOZT13WXQoX1l0KX0pO2Z1bmN0aW9uIEljKGUsdCl7cmV0dXJuIGU9K2UsdD0rdCxmdW5jdGlvbihyKXtyZXR1cm4gZSooMS1yKSt0KnJ9fXZhciBZej1NKCgpPT57fSk7ZnVuY3Rpb24gV05lKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX1mdW5jdGlvbiBZTmUoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3JldHVybiBlKHQpKyIifX1mdW5jdGlvbiBVbnQoZSx0KXt2YXIgcj1WbnQubGFzdEluZGV4PUhudC5sYXN0SW5kZXg9MCxuLGksbyxhPS0xLHM9W10sbD1bXTtmb3IoZT1lKyIiLHQ9dCsiIjsobj1WbnQuZXhlYyhlKSkmJihpPUhudC5leGVjKHQpKTspKG89aS5pbmRleCk+ciYmKG89dC5zbGljZShyLG8pLHNbYV0/c1thXSs9bzpzWysrYV09byksKG49blswXSk9PT0oaT1pWzBdKT9zW2FdP3NbYV0rPWk6c1srK2FdPWk6KHNbKythXT1udWxsLGwucHVzaCh7aTphLHg6SWMobixpKX0pKSxyPUhudC5sYXN0SW5kZXg7cmV0dXJuIHI8dC5sZW5ndGgmJihvPXQuc2xpY2Uociksc1thXT9zW2FdKz1vOnNbKythXT1vKSxzLmxlbmd0aDwyP2xbMF0/WU5lKGxbMF0ueCk6V05lKHQpOih0PWwubGVuZ3RoLGZ1bmN0aW9uKGMpe2Zvcih2YXIgdT0wLGg7dTx0OysrdSlzWyhoPWxbdV0pLmldPWgueChjKTtyZXR1cm4gcy5qb2luKCIiKX0pfXZhciBWbnQsSG50LE1ZdD1NKCgpPT57WXooKTtWbnQ9L1stK10/KD86XGQrXC4/XGQqfFwuP1xkKykoPzpbZUVdWy0rXT9cZCspPy9nLEhudD1uZXcgUmVnRXhwKFZudC5zb3VyY2UsImciKX0pO2Z1bmN0aW9uIHFudChlLHQscixuLGksbyl7dmFyIGEscyxsO3JldHVybihhPU1hdGguc3FydChlKmUrdCp0KSkmJihlLz1hLHQvPWEpLChsPWUqcit0Km4pJiYoci09ZSpsLG4tPXQqbCksKHM9TWF0aC5zcXJ0KHIqcituKm4pKSYmKHIvPXMsbi89cyxsLz1zKSxlKm48dCpyJiYoZT0tZSx0PS10LGw9LWwsYT0tYSkse3RyYW5zbGF0ZVg6aSx0cmFuc2xhdGVZOm8scm90YXRlOk1hdGguYXRhbjIodCxlKSpFWXQsc2tld1g6TWF0aC5hdGFuKGwpKkVZdCxzY2FsZVg6YSxzY2FsZVk6c319dmFyIEVZdCxqeixUWXQ9TSgoKT0+e0VZdD0xODAvTWF0aC5QSSxqej17dHJhbnNsYXRlWDowLHRyYW5zbGF0ZVk6MCxyb3RhdGU6MCxza2V3WDowLHNjYWxlWDoxLHNjYWxlWToxfX0pO2Z1bmN0aW9uIEFZdChlKXtyZXR1cm4gZT09PSJub25lIj9qejooRUF8fChFQT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJESVYiKSxHbnQ9ZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LENZdD1kb2N1bWVudC5kZWZhdWx0VmlldyksRUEuc3R5bGUudHJhbnNmb3JtPWUsZT1DWXQuZ2V0Q29tcHV0ZWRTdHlsZShHbnQuYXBwZW5kQ2hpbGQoRUEpLG51bGwpLmdldFByb3BlcnR5VmFsdWUoInRyYW5zZm9ybSIpLEdudC5yZW1vdmVDaGlsZChFQSksZT1lLnNsaWNlKDcsLTEpLnNwbGl0KCIsIikscW50KCtlWzBdLCtlWzFdLCtlWzJdLCtlWzNdLCtlWzRdLCtlWzVdKSl9ZnVuY3Rpb24gUFl0KGUpe3JldHVybiBlPT1udWxsP2p6OihYenx8KFh6PWRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUygiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLCJnIikpLFh6LnNldEF0dHJpYnV0ZSgidHJhbnNmb3JtIixlKSwoZT1Yei50cmFuc2Zvcm0uYmFzZVZhbC5jb25zb2xpZGF0ZSgpKT8oZT1lLm1hdHJpeCxxbnQoZS5hLGUuYixlLmMsZS5kLGUuZSxlLmYpKTpqeil9dmFyIEVBLEdudCxDWXQsWHosSVl0PU0oKCk9PntUWXQoKX0pO2Z1bmN0aW9uIExZdChlLHQscixuKXtmdW5jdGlvbiBpKGMpe3JldHVybiBjLmxlbmd0aD9jLnBvcCgpKyIgIjoiIn1mdW5jdGlvbiBvKGMsdSxoLGYscCxkKXtpZihjIT09aHx8dSE9PWYpe3ZhciBnPXAucHVzaCgidHJhbnNsYXRlKCIsbnVsbCx0LG51bGwscik7ZC5wdXNoKHtpOmctNCx4OkljKGMsaCl9LHtpOmctMix4OkljKHUsZil9KX1lbHNlKGh8fGYpJiZwLnB1c2goInRyYW5zbGF0ZSgiK2grdCtmK3IpfWZ1bmN0aW9uIGEoYyx1LGgsZil7YyE9PXU/KGMtdT4xODA/dSs9MzYwOnUtYz4xODAmJihjKz0zNjApLGYucHVzaCh7aTpoLnB1c2goaShoKSsicm90YXRlKCIsbnVsbCxuKS0yLHg6SWMoYyx1KX0pKTp1JiZoLnB1c2goaShoKSsicm90YXRlKCIrdStuKX1mdW5jdGlvbiBzKGMsdSxoLGYpe2MhPT11P2YucHVzaCh7aTpoLnB1c2goaShoKSsic2tld1goIixudWxsLG4pLTIseDpJYyhjLHUpfSk6dSYmaC5wdXNoKGkoaCkrInNrZXdYKCIrdStuKX1mdW5jdGlvbiBsKGMsdSxoLGYscCxkKXtpZihjIT09aHx8dSE9PWYpe3ZhciBnPXAucHVzaChpKHApKyJzY2FsZSgiLG51bGwsIiwiLG51bGwsIikiKTtkLnB1c2goe2k6Zy00LHg6SWMoYyxoKX0se2k6Zy0yLHg6SWModSxmKX0pfWVsc2UoaCE9PTF8fGYhPT0xKSYmcC5wdXNoKGkocCkrInNjYWxlKCIraCsiLCIrZisiKSIpfXJldHVybiBmdW5jdGlvbihjLHUpe3ZhciBoPVtdLGY9W107cmV0dXJuIGM9ZShjKSx1PWUodSksbyhjLnRyYW5zbGF0ZVgsYy50cmFuc2xhdGVZLHUudHJhbnNsYXRlWCx1LnRyYW5zbGF0ZVksaCxmKSxhKGMucm90YXRlLHUucm90YXRlLGgsZikscyhjLnNrZXdYLHUuc2tld1gsaCxmKSxsKGMuc2NhbGVYLGMuc2NhbGVZLHUuc2NhbGVYLHUuc2NhbGVZLGgsZiksYz11PW51bGwsZnVuY3Rpb24ocCl7Zm9yKHZhciBkPS0xLGc9Zi5sZW5ndGgsXzsrK2Q8ZzspaFsoXz1mW2RdKS5pXT1fLngocCk7cmV0dXJuIGguam9pbigiIil9fX12YXIgV250LFludCxrWXQ9TSgoKT0+e1l6KCk7SVl0KCk7V250PUxZdChBWXQsInB4LCAiLCJweCkiLCJkZWcpIiksWW50PUxZdChQWXQsIiwgIiwiKSIsIikiKX0pO2Z1bmN0aW9uIE5ZdChlKXtyZXR1cm4oKGU9TWF0aC5leHAoZSkpKzEvZSkvMn1mdW5jdGlvbiBYTmUoZSl7cmV0dXJuKChlPU1hdGguZXhwKGUpKS0xL2UpLzJ9ZnVuY3Rpb24gJE5lKGUpe3JldHVybigoZT1NYXRoLmV4cCgyKmUpKS0xKS8oZSsxKX1mdW5jdGlvbiBYbnQoZSx0KXt2YXIgcj1lWzBdLG49ZVsxXSxpPWVbMl0sbz10WzBdLGE9dFsxXSxzPXRbMl0sbD1vLXIsYz1hLW4sdT1sKmwrYypjLGgsZjtpZih1PGpOZSlmPU1hdGgubG9nKHMvaSkvVEEsaD1mdW5jdGlvbih4KXtyZXR1cm5bcit4Kmwsbit4KmMsaSpNYXRoLmV4cChUQSp4KmYpXX07ZWxzZXt2YXIgcD1NYXRoLnNxcnQodSksZD0ocypzLWkqaStSWXQqdSkvKDIqaSpqbnQqcCksZz0ocypzLWkqaS1SWXQqdSkvKDIqcypqbnQqcCksXz1NYXRoLmxvZyhNYXRoLnNxcnQoZCpkKzEpLWQpLHk9TWF0aC5sb2coTWF0aC5zcXJ0KGcqZysxKS1nKTtmPSh5LV8pL1RBLGg9ZnVuY3Rpb24oeCl7dmFyIGI9eCpmLFM9Tll0KF8pLEM9aS8oam50KnApKihTKiROZShUQSpiK18pLVhOZShfKSk7cmV0dXJuW3IrQypsLG4rQypjLGkqUy9OWXQoVEEqYitfKV19fXJldHVybiBoLmR1cmF0aW9uPWYqMWUzLGh9dmFyIFRBLGpudCxSWXQsak5lLERZdD1NKCgpPT57VEE9TWF0aC5TUVJUMixqbnQ9MixSWXQ9NCxqTmU9MWUtMTJ9KTt2YXIgQ0E9TSgoKT0+e1l6KCk7TVl0KCk7a1l0KCk7RFl0KCk7U1l0KCl9KTtmdW5jdGlvbiBsUygpe3JldHVybiBBMXx8KEZZdChLTmUpLEExPUxBLm5vdygpK1p6KX1mdW5jdGlvbiBLTmUoKXtBMT0wfWZ1bmN0aW9uIGtBKCl7dGhpcy5fY2FsbD10aGlzLl90aW1lPXRoaXMuX25leHQ9bnVsbH1mdW5jdGlvbiBKeihlLHQscil7dmFyIG49bmV3IGtBO3JldHVybiBuLnJlc3RhcnQoZSx0LHIpLG59ZnVuY3Rpb24gQll0KCl7bFMoKSwrK3NTO2Zvcih2YXIgZT0keix0O2U7KSh0PUExLWUuX3RpbWUpPj0wJiZlLl9jYWxsLmNhbGwobnVsbCx0KSxlPWUuX25leHQ7LS1zU31mdW5jdGlvbiBPWXQoKXtBMT0oS3o9TEEubm93KCkpK1p6LHNTPVBBPTA7dHJ5e0JZdCgpfWZpbmFsbHl7c1M9MCxKTmUoKSxBMT0wfX1mdW5jdGlvbiBaTmUoKXt2YXIgZT1MQS5ub3coKSx0PWUtS3o7dD56WXQmJihaei09dCxLej1lKX1mdW5jdGlvbiBKTmUoKXtmb3IodmFyIGUsdD0keixyLG49MS8wO3Q7KXQuX2NhbGw/KG4+dC5fdGltZSYmKG49dC5fdGltZSksZT10LHQ9dC5fbmV4dCk6KHI9dC5fbmV4dCx0Ll9uZXh0PW51bGwsdD1lP2UuX25leHQ9cjokej1yKTtJQT1lLCRudChuKX1mdW5jdGlvbiAkbnQoZSl7aWYoIXNTKXtQQSYmKFBBPWNsZWFyVGltZW91dChQQSkpO3ZhciB0PWUtQTE7dD4yND8oZTwxLzAmJihQQT1zZXRUaW1lb3V0KE9ZdCxlLUxBLm5vdygpLVp6KSksQUEmJihBQT1jbGVhckludGVydmFsKEFBKSkpOihBQXx8KEt6PUxBLm5vdygpLEFBPXNldEludGVydmFsKFpOZSx6WXQpKSxzUz0xLEZZdChPWXQpKX19dmFyIHNTLFBBLEFBLHpZdCwkeixJQSxLeixBMSxaeixMQSxGWXQsS250PU0oKCk9PntzUz0wLFBBPTAsQUE9MCx6WXQ9MWUzLEt6PTAsQTE9MCxaej0wLExBPXR5cGVvZiBwZXJmb3JtYW5jZT09Im9iamVjdCImJnBlcmZvcm1hbmNlLm5vdz9wZXJmb3JtYW5jZTpEYXRlLEZZdD10eXBlb2Ygd2luZG93PT0ib2JqZWN0IiYmd2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZT93aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lLmJpbmQod2luZG93KTpmdW5jdGlvbihlKXtzZXRUaW1lb3V0KGUsMTcpfTtrQS5wcm90b3R5cGU9SnoucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjprQSxyZXN0YXJ0OmZ1bmN0aW9uKGUsdCxyKXtpZih0eXBlb2YgZSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgVHlwZUVycm9yKCJjYWxsYmFjayBpcyBub3QgYSBmdW5jdGlvbiIpO3I9KHI9PW51bGw/bFMoKTorcikrKHQ9PW51bGw/MDordCksIXRoaXMuX25leHQmJklBIT09dGhpcyYmKElBP0lBLl9uZXh0PXRoaXM6JHo9dGhpcyxJQT10aGlzKSx0aGlzLl9jYWxsPWUsdGhpcy5fdGltZT1yLCRudCgpfSxzdG9wOmZ1bmN0aW9uKCl7dGhpcy5fY2FsbCYmKHRoaXMuX2NhbGw9bnVsbCx0aGlzLl90aW1lPTEvMCwkbnQoKSl9fX0pO2Z1bmN0aW9uIFF6KGUsdCxyKXt2YXIgbj1uZXcga0E7cmV0dXJuIHQ9dD09bnVsbD8wOit0LG4ucmVzdGFydChmdW5jdGlvbihpKXtuLnN0b3AoKSxlKGkrdCl9LHQsciksbn12YXIgSFl0PU0oKCk9PntLbnQoKX0pO3ZhciBabnQ9TSgoKT0+e0tudCgpO0hZdCgpfSk7ZnVuY3Rpb24gR2coZSx0LHIsbixpLG8pe3ZhciBhPWUuX190cmFuc2l0aW9uO2lmKCFhKWUuX190cmFuc2l0aW9uPXt9O2Vsc2UgaWYociBpbiBhKXJldHVybjtlRGUoZSxyLHtuYW1lOnQsaW5kZXg6bixncm91cDppLG9uOlFOZSx0d2Vlbjp0RGUsdGltZTpvLnRpbWUsZGVsYXk6by5kZWxheSxkdXJhdGlvbjpvLmR1cmF0aW9uLGVhc2U6by5lYXNlLHRpbWVyOm51bGwsc3RhdGU6VVl0fSl9ZnVuY3Rpb24gTkEoZSx0KXt2YXIgcj1vbyhlLHQpO2lmKHIuc3RhdGU+VVl0KXRocm93IG5ldyBFcnJvcigidG9vIGxhdGU7IGFscmVhZHkgc2NoZWR1bGVkIik7cmV0dXJuIHJ9ZnVuY3Rpb24gSmEoZSx0KXt2YXIgcj1vbyhlLHQpO2lmKHIuc3RhdGU+dEYpdGhyb3cgbmV3IEVycm9yKCJ0b28gbGF0ZTsgYWxyZWFkeSBydW5uaW5nIik7cmV0dXJuIHJ9ZnVuY3Rpb24gb28oZSx0KXt2YXIgcj1lLl9fdHJhbnNpdGlvbjtpZighcnx8IShyPXJbdF0pKXRocm93IG5ldyBFcnJvcigidHJhbnNpdGlvbiBub3QgZm91bmQiKTtyZXR1cm4gcn1mdW5jdGlvbiBlRGUoZSx0LHIpe3ZhciBuPWUuX190cmFuc2l0aW9uLGk7blt0XT1yLHIudGltZXI9SnoobywwLHIudGltZSk7ZnVuY3Rpb24gbyhjKXtyLnN0YXRlPUpudCxyLnRpbWVyLnJlc3RhcnQoYSxyLmRlbGF5LHIudGltZSksci5kZWxheTw9YyYmYShjLXIuZGVsYXkpfWZ1bmN0aW9uIGEoYyl7dmFyIHUsaCxmLHA7aWYoci5zdGF0ZSE9PUpudClyZXR1cm4gbCgpO2Zvcih1IGluIG4paWYocD1uW3VdLHAubmFtZT09PXIubmFtZSl7aWYocC5zdGF0ZT09PXRGKXJldHVybiBReihhKTtwLnN0YXRlPT09Vll0PyhwLnN0YXRlPVJBLHAudGltZXIuc3RvcCgpLHAub24uY2FsbCgiaW50ZXJydXB0IixlLGUuX19kYXRhX18scC5pbmRleCxwLmdyb3VwKSxkZWxldGUgblt1XSk6K3U8dCYmKHAuc3RhdGU9UkEscC50aW1lci5zdG9wKCkscC5vbi5jYWxsKCJjYW5jZWwiLGUsZS5fX2RhdGFfXyxwLmluZGV4LHAuZ3JvdXApLGRlbGV0ZSBuW3VdKX1pZihReihmdW5jdGlvbigpe3Iuc3RhdGU9PT10RiYmKHIuc3RhdGU9Vll0LHIudGltZXIucmVzdGFydChzLHIuZGVsYXksci50aW1lKSxzKGMpKX0pLHIuc3RhdGU9ZUYsci5vbi5jYWxsKCJzdGFydCIsZSxlLl9fZGF0YV9fLHIuaW5kZXgsci5ncm91cCksci5zdGF0ZT09PWVGKXtmb3Ioci5zdGF0ZT10RixpPW5ldyBBcnJheShmPXIudHdlZW4ubGVuZ3RoKSx1PTAsaD0tMTt1PGY7Kyt1KShwPXIudHdlZW5bdV0udmFsdWUuY2FsbChlLGUuX19kYXRhX18sci5pbmRleCxyLmdyb3VwKSkmJihpWysraF09cCk7aS5sZW5ndGg9aCsxfX1mdW5jdGlvbiBzKGMpe2Zvcih2YXIgdT1jPHIuZHVyYXRpb24/ci5lYXNlLmNhbGwobnVsbCxjL3IuZHVyYXRpb24pOihyLnRpbWVyLnJlc3RhcnQobCksci5zdGF0ZT1yRiwxKSxoPS0xLGY9aS5sZW5ndGg7KytoPGY7KWlbaF0uY2FsbChlLHUpO3Iuc3RhdGU9PT1yRiYmKHIub24uY2FsbCgiZW5kIixlLGUuX19kYXRhX18sci5pbmRleCxyLmdyb3VwKSxsKCkpfWZ1bmN0aW9uIGwoKXtyLnN0YXRlPVJBLHIudGltZXIuc3RvcCgpLGRlbGV0ZSBuW3RdO2Zvcih2YXIgYyBpbiBuKXJldHVybjtkZWxldGUgZS5fX3RyYW5zaXRpb259fXZhciBRTmUsdERlLFVZdCxKbnQsZUYsdEYsVll0LHJGLFJBLExzPU0oKCk9PntnbnQoKTtabnQoKTtRTmU9eUEoInN0YXJ0IiwiZW5kIiwiY2FuY2VsIiwiaW50ZXJydXB0IiksdERlPVtdLFVZdD0wLEpudD0xLGVGPTIsdEY9MyxWWXQ9NCxyRj01LFJBPTZ9KTtmdW5jdGlvbiBQMShlLHQpe3ZhciByPWUuX190cmFuc2l0aW9uLG4saSxvPSEwLGE7aWYoISFyKXt0PXQ9PW51bGw/bnVsbDp0KyIiO2ZvcihhIGluIHIpe2lmKChuPXJbYV0pLm5hbWUhPT10KXtvPSExO2NvbnRpbnVlfWk9bi5zdGF0ZT5lRiYmbi5zdGF0ZTxyRixuLnN0YXRlPVJBLG4udGltZXIuc3RvcCgpLG4ub24uY2FsbChpPyJpbnRlcnJ1cHQiOiJjYW5jZWwiLGUsZS5fX2RhdGFfXyxuLmluZGV4LG4uZ3JvdXApLGRlbGV0ZSByW2FdfW8mJmRlbGV0ZSBlLl9fdHJhbnNpdGlvbn19dmFyIFFudD1NKCgpPT57THMoKX0pO2Z1bmN0aW9uIHFZdChlKXtyZXR1cm4gdGhpcy5lYWNoKGZ1bmN0aW9uKCl7UDEodGhpcyxlKX0pfXZhciBHWXQ9TSgoKT0+e1FudCgpfSk7ZnVuY3Rpb24gckRlKGUsdCl7dmFyIHIsbjtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgaT1KYSh0aGlzLGUpLG89aS50d2VlbjtpZihvIT09cil7bj1yPW87Zm9yKHZhciBhPTAscz1uLmxlbmd0aDthPHM7KythKWlmKG5bYV0ubmFtZT09PXQpe249bi5zbGljZSgpLG4uc3BsaWNlKGEsMSk7YnJlYWt9fWkudHdlZW49bn19ZnVuY3Rpb24gbkRlKGUsdCxyKXt2YXIgbixpO2lmKHR5cGVvZiByIT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbz1KYSh0aGlzLGUpLGE9by50d2VlbjtpZihhIT09bil7aT0obj1hKS5zbGljZSgpO2Zvcih2YXIgcz17bmFtZTp0LHZhbHVlOnJ9LGw9MCxjPWkubGVuZ3RoO2w8YzsrK2wpaWYoaVtsXS5uYW1lPT09dCl7aVtsXT1zO2JyZWFrfWw9PT1jJiZpLnB1c2gocyl9by50d2Vlbj1pfX1mdW5jdGlvbiBXWXQoZSx0KXt2YXIgcj10aGlzLl9pZDtpZihlKz0iIixhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcih2YXIgbj1vbyh0aGlzLm5vZGUoKSxyKS50d2VlbixpPTAsbz1uLmxlbmd0aCxhO2k8bzsrK2kpaWYoKGE9bltpXSkubmFtZT09PWUpcmV0dXJuIGEudmFsdWU7cmV0dXJuIG51bGx9cmV0dXJuIHRoaXMuZWFjaCgodD09bnVsbD9yRGU6bkRlKShyLGUsdCkpfWZ1bmN0aW9uIGNTKGUsdCxyKXt2YXIgbj1lLl9pZDtyZXR1cm4gZS5lYWNoKGZ1bmN0aW9uKCl7dmFyIGk9SmEodGhpcyxuKTsoaS52YWx1ZXx8KGkudmFsdWU9e30pKVt0XT1yLmFwcGx5KHRoaXMsYXJndW1lbnRzKX0pLGZ1bmN0aW9uKGkpe3JldHVybiBvbyhpLG4pLnZhbHVlW3RdfX12YXIgREE9TSgoKT0+e0xzKCl9KTtmdW5jdGlvbiBuRihlLHQpe3ZhciByO3JldHVybih0eXBlb2YgdD09Im51bWJlciI/SWM6dCBpbnN0YW5jZW9mIHFnP1d6OihyPXFnKHQpKT8odD1yLFd6KTpVbnQpKGUsdCl9dmFyIHRpdD1NKCgpPT57T250KCk7Q0EoKX0pO2Z1bmN0aW9uIGlEZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnJlbW92ZUF0dHJpYnV0ZShlKX19ZnVuY3Rpb24gb0RlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKX19ZnVuY3Rpb24gYURlKGUsdCxyKXt2YXIgbixpPXIrIiIsbztyZXR1cm4gZnVuY3Rpb24oKXt2YXIgYT10aGlzLmdldEF0dHJpYnV0ZShlKTtyZXR1cm4gYT09PWk/bnVsbDphPT09bj9vOm89dChuPWEscil9fWZ1bmN0aW9uIHNEZShlLHQscil7dmFyIG4saT1yKyIiLG87cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGE9dGhpcy5nZXRBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpO3JldHVybiBhPT09aT9udWxsOmE9PT1uP286bz10KG49YSxyKX19ZnVuY3Rpb24gbERlKGUsdCxyKXt2YXIgbixpLG87cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGEscz1yKHRoaXMpLGw7cmV0dXJuIHM9PW51bGw/dm9pZCB0aGlzLnJlbW92ZUF0dHJpYnV0ZShlKTooYT10aGlzLmdldEF0dHJpYnV0ZShlKSxsPXMrIiIsYT09PWw/bnVsbDphPT09biYmbD09PWk/bzooaT1sLG89dChuPWEscykpKX19ZnVuY3Rpb24gY0RlKGUsdCxyKXt2YXIgbixpLG87cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGEscz1yKHRoaXMpLGw7cmV0dXJuIHM9PW51bGw/dm9pZCB0aGlzLnJlbW92ZUF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCk6KGE9dGhpcy5nZXRBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpLGw9cysiIixhPT09bD9udWxsOmE9PT1uJiZsPT09aT9vOihpPWwsbz10KG49YSxzKSkpfX1mdW5jdGlvbiBZWXQoZSx0KXt2YXIgcj1oZChlKSxuPXI9PT0idHJhbnNmb3JtIj9ZbnQ6bkY7cmV0dXJuIHRoaXMuYXR0clR3ZWVuKGUsdHlwZW9mIHQ9PSJmdW5jdGlvbiI/KHIubG9jYWw/Y0RlOmxEZSkocixuLGNTKHRoaXMsImF0dHIuIitlLHQpKTp0PT1udWxsPyhyLmxvY2FsP29EZTppRGUpKHIpOihyLmxvY2FsP3NEZTphRGUpKHIsbix0KSl9dmFyIGpZdD1NKCgpPT57Q0EoKTtJcygpO0RBKCk7dGl0KCl9KTtmdW5jdGlvbiB1RGUoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocil7dGhpcy5zZXRBdHRyaWJ1dGUoZSx0LmNhbGwodGhpcyxyKSl9fWZ1bmN0aW9uIGhEZShlLHQpe3JldHVybiBmdW5jdGlvbihyKXt0aGlzLnNldEF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCx0LmNhbGwodGhpcyxyKSl9fWZ1bmN0aW9uIGZEZShlLHQpe3ZhciByLG47ZnVuY3Rpb24gaSgpe3ZhciBvPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiBvIT09biYmKHI9KG49bykmJmhEZShlLG8pKSxyfXJldHVybiBpLl92YWx1ZT10LGl9ZnVuY3Rpb24gcERlKGUsdCl7dmFyIHIsbjtmdW5jdGlvbiBpKCl7dmFyIG89dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cmV0dXJuIG8hPT1uJiYocj0obj1vKSYmdURlKGUsbykpLHJ9cmV0dXJuIGkuX3ZhbHVlPXQsaX1mdW5jdGlvbiBYWXQoZSx0KXt2YXIgcj0iYXR0ci4iK2U7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXJldHVybihyPXRoaXMudHdlZW4ocikpJiZyLl92YWx1ZTtpZih0PT1udWxsKXJldHVybiB0aGlzLnR3ZWVuKHIsbnVsbCk7aWYodHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3ZhciBuPWhkKGUpO3JldHVybiB0aGlzLnR3ZWVuKHIsKG4ubG9jYWw/ZkRlOnBEZSkobix0KSl9dmFyICRZdD1NKCgpPT57SXMoKX0pO2Z1bmN0aW9uIGREZShlLHQpe3JldHVybiBmdW5jdGlvbigpe05BKHRoaXMsZSkuZGVsYXk9K3QuYXBwbHkodGhpcyxhcmd1bWVudHMpfX1mdW5jdGlvbiBtRGUoZSx0KXtyZXR1cm4gdD0rdCxmdW5jdGlvbigpe05BKHRoaXMsZSkuZGVsYXk9dH19ZnVuY3Rpb24gS1l0KGUpe3ZhciB0PXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaCgodHlwZW9mIGU9PSJmdW5jdGlvbiI/ZERlOm1EZSkodCxlKSk6b28odGhpcy5ub2RlKCksdCkuZGVsYXl9dmFyIFpZdD1NKCgpPT57THMoKX0pO2Z1bmN0aW9uIGdEZShlLHQpe3JldHVybiBmdW5jdGlvbigpe0phKHRoaXMsZSkuZHVyYXRpb249K3QuYXBwbHkodGhpcyxhcmd1bWVudHMpfX1mdW5jdGlvbiBfRGUoZSx0KXtyZXR1cm4gdD0rdCxmdW5jdGlvbigpe0phKHRoaXMsZSkuZHVyYXRpb249dH19ZnVuY3Rpb24gSll0KGUpe3ZhciB0PXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaCgodHlwZW9mIGU9PSJmdW5jdGlvbiI/Z0RlOl9EZSkodCxlKSk6b28odGhpcy5ub2RlKCksdCkuZHVyYXRpb259dmFyIFFZdD1NKCgpPT57THMoKX0pO2Z1bmN0aW9uIHlEZShlLHQpe2lmKHR5cGVvZiB0IT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gZnVuY3Rpb24oKXtKYSh0aGlzLGUpLmVhc2U9dH19ZnVuY3Rpb24gdGp0KGUpe3ZhciB0PXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaCh5RGUodCxlKSk6b28odGhpcy5ub2RlKCksdCkuZWFzZX12YXIgZWp0PU0oKCk9PntMcygpfSk7ZnVuY3Rpb24gcmp0KGUpe3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT14QShlKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPXQubGVuZ3RoLG49bmV3IEFycmF5KHIpLGk9MDtpPHI7KytpKWZvcih2YXIgbz10W2ldLGE9by5sZW5ndGgscz1uW2ldPVtdLGwsYz0wO2M8YTsrK2MpKGw9b1tjXSkmJmUuY2FsbChsLGwuX19kYXRhX18sYyxvKSYmcy5wdXNoKGwpO3JldHVybiBuZXcgZGEobix0aGlzLl9wYXJlbnRzLHRoaXMuX25hbWUsdGhpcy5faWQpfXZhciBuanQ9TSgoKT0+e0lzKCk7ZGQoKX0pO2Z1bmN0aW9uIGlqdChlKXtpZihlLl9pZCE9PXRoaXMuX2lkKXRocm93IG5ldyBFcnJvcjtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9ZS5fZ3JvdXBzLG49dC5sZW5ndGgsaT1yLmxlbmd0aCxvPU1hdGgubWluKG4saSksYT1uZXcgQXJyYXkobikscz0wO3M8bzsrK3MpZm9yKHZhciBsPXRbc10sYz1yW3NdLHU9bC5sZW5ndGgsaD1hW3NdPW5ldyBBcnJheSh1KSxmLHA9MDtwPHU7KytwKShmPWxbcF18fGNbcF0pJiYoaFtwXT1mKTtmb3IoO3M8bjsrK3MpYVtzXT10W3NdO3JldHVybiBuZXcgZGEoYSx0aGlzLl9wYXJlbnRzLHRoaXMuX25hbWUsdGhpcy5faWQpfXZhciBvanQ9TSgoKT0+e2RkKCl9KTtmdW5jdGlvbiB2RGUoZSl7cmV0dXJuKGUrIiIpLnRyaW0oKS5zcGxpdCgvXnxccysvKS5ldmVyeShmdW5jdGlvbih0KXt2YXIgcj10LmluZGV4T2YoIi4iKTtyZXR1cm4gcj49MCYmKHQ9dC5zbGljZSgwLHIpKSwhdHx8dD09PSJzdGFydCJ9KX1mdW5jdGlvbiB4RGUoZSx0LHIpe3ZhciBuLGksbz12RGUodCk/TkE6SmE7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGE9byh0aGlzLGUpLHM9YS5vbjtzIT09biYmKGk9KG49cykuY29weSgpKS5vbih0LHIpLGEub249aX19ZnVuY3Rpb24gYWp0KGUsdCl7dmFyIHI9dGhpcy5faWQ7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg8Mj9vbyh0aGlzLm5vZGUoKSxyKS5vbi5vbihlKTp0aGlzLmVhY2goeERlKHIsZSx0KSl9dmFyIHNqdD1NKCgpPT57THMoKX0pO2Z1bmN0aW9uIGJEZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD10aGlzLnBhcmVudE5vZGU7Zm9yKHZhciByIGluIHRoaXMuX190cmFuc2l0aW9uKWlmKCtyIT09ZSlyZXR1cm47dCYmdC5yZW1vdmVDaGlsZCh0aGlzKX19ZnVuY3Rpb24gbGp0KCl7cmV0dXJuIHRoaXMub24oImVuZC5yZW1vdmUiLGJEZSh0aGlzLl9pZCkpfXZhciBjanQ9TSgoKT0+e30pO2Z1bmN0aW9uIHVqdChlKXt2YXIgdD10aGlzLl9uYW1lLHI9dGhpcy5faWQ7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPUMxKGUpKTtmb3IodmFyIG49dGhpcy5fZ3JvdXBzLGk9bi5sZW5ndGgsbz1uZXcgQXJyYXkoaSksYT0wO2E8aTsrK2EpZm9yKHZhciBzPW5bYV0sbD1zLmxlbmd0aCxjPW9bYV09bmV3IEFycmF5KGwpLHUsaCxmPTA7ZjxsOysrZikodT1zW2ZdKSYmKGg9ZS5jYWxsKHUsdS5fX2RhdGFfXyxmLHMpKSYmKCJfX2RhdGFfXyJpbiB1JiYoaC5fX2RhdGFfXz11Ll9fZGF0YV9fKSxjW2ZdPWgsR2coY1tmXSx0LHIsZixjLG9vKHUscikpKTtyZXR1cm4gbmV3IGRhKG8sdGhpcy5fcGFyZW50cyx0LHIpfXZhciBoanQ9TSgoKT0+e0lzKCk7ZGQoKTtMcygpfSk7ZnVuY3Rpb24gZmp0KGUpe3ZhciB0PXRoaXMuX25hbWUscj10aGlzLl9pZDt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9dkEoZSkpO2Zvcih2YXIgbj10aGlzLl9ncm91cHMsaT1uLmxlbmd0aCxvPVtdLGE9W10scz0wO3M8aTsrK3MpZm9yKHZhciBsPW5bc10sYz1sLmxlbmd0aCx1LGg9MDtoPGM7KytoKWlmKHU9bFtoXSl7Zm9yKHZhciBmPWUuY2FsbCh1LHUuX19kYXRhX18saCxsKSxwLGQ9b28odSxyKSxnPTAsXz1mLmxlbmd0aDtnPF87KytnKShwPWZbZ10pJiZHZyhwLHQscixnLGYsZCk7by5wdXNoKGYpLGEucHVzaCh1KX1yZXR1cm4gbmV3IGRhKG8sYSx0LHIpfXZhciBwanQ9TSgoKT0+e0lzKCk7ZGQoKTtMcygpfSk7ZnVuY3Rpb24gZGp0KCl7cmV0dXJuIG5ldyB3RGUodGhpcy5fZ3JvdXBzLHRoaXMuX3BhcmVudHMpfXZhciB3RGUsbWp0PU0oKCk9PntJcygpO3dEZT1mZC5wcm90b3R5cGUuY29uc3RydWN0b3J9KTtmdW5jdGlvbiBTRGUoZSx0KXt2YXIgcixuLGk7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIG89VWcodGhpcyxlKSxhPSh0aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KGUpLFVnKHRoaXMsZSkpO3JldHVybiBvPT09YT9udWxsOm89PT1yJiZhPT09bj9pOmk9dChyPW8sbj1hKX19ZnVuY3Rpb24gZ2p0KGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkoZSl9fWZ1bmN0aW9uIE1EZShlLHQscil7dmFyIG4saT1yKyIiLG87cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGE9VWcodGhpcyxlKTtyZXR1cm4gYT09PWk/bnVsbDphPT09bj9vOm89dChuPWEscil9fWZ1bmN0aW9uIEVEZShlLHQscil7dmFyIG4saSxvO3JldHVybiBmdW5jdGlvbigpe3ZhciBhPVVnKHRoaXMsZSkscz1yKHRoaXMpLGw9cysiIjtyZXR1cm4gcz09bnVsbCYmKGw9cz0odGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShlKSxVZyh0aGlzLGUpKSksYT09PWw/bnVsbDphPT09biYmbD09PWk/bzooaT1sLG89dChuPWEscykpfX1mdW5jdGlvbiBURGUoZSx0KXt2YXIgcixuLGksbz0ic3R5bGUuIit0LGE9ImVuZC4iK28scztyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbD1KYSh0aGlzLGUpLGM9bC5vbix1PWwudmFsdWVbb109PW51bGw/c3x8KHM9Z2p0KHQpKTp2b2lkIDA7KGMhPT1yfHxpIT09dSkmJihuPShyPWMpLmNvcHkoKSkub24oYSxpPXUpLGwub249bn19ZnVuY3Rpb24gX2p0KGUsdCxyKXt2YXIgbj0oZSs9IiIpPT0idHJhbnNmb3JtIj9XbnQ6bkY7cmV0dXJuIHQ9PW51bGw/dGhpcy5zdHlsZVR3ZWVuKGUsU0RlKGUsbikpLm9uKCJlbmQuc3R5bGUuIitlLGdqdChlKSk6dHlwZW9mIHQ9PSJmdW5jdGlvbiI/dGhpcy5zdHlsZVR3ZWVuKGUsRURlKGUsbixjUyh0aGlzLCJzdHlsZS4iK2UsdCkpKS5lYWNoKFREZSh0aGlzLl9pZCxlKSk6dGhpcy5zdHlsZVR3ZWVuKGUsTURlKGUsbix0KSxyKS5vbigiZW5kLnN0eWxlLiIrZSxudWxsKX12YXIgeWp0PU0oKCk9PntDQSgpO0lzKCk7THMoKTtEQSgpO3RpdCgpfSk7ZnVuY3Rpb24gQ0RlKGUsdCxyKXtyZXR1cm4gZnVuY3Rpb24obil7dGhpcy5zdHlsZS5zZXRQcm9wZXJ0eShlLHQuY2FsbCh0aGlzLG4pLHIpfX1mdW5jdGlvbiBBRGUoZSx0LHIpe3ZhciBuLGk7ZnVuY3Rpb24gbygpe3ZhciBhPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiBhIT09aSYmKG49KGk9YSkmJkNEZShlLGEscikpLG59cmV0dXJuIG8uX3ZhbHVlPXQsb31mdW5jdGlvbiB2anQoZSx0LHIpe3ZhciBuPSJzdHlsZS4iKyhlKz0iIik7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXJldHVybihuPXRoaXMudHdlZW4obikpJiZuLl92YWx1ZTtpZih0PT1udWxsKXJldHVybiB0aGlzLnR3ZWVuKG4sbnVsbCk7aWYodHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3JldHVybiB0aGlzLnR3ZWVuKG4sQURlKGUsdCxyPT1udWxsPyIiOnIpKX12YXIgeGp0PU0oKCk9Pnt9KTtmdW5jdGlvbiBQRGUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy50ZXh0Q29udGVudD1lfX1mdW5jdGlvbiBJRGUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9ZSh0aGlzKTt0aGlzLnRleHRDb250ZW50PXQ9PW51bGw/IiI6dH19ZnVuY3Rpb24gYmp0KGUpe3JldHVybiB0aGlzLnR3ZWVuKCJ0ZXh0Iix0eXBlb2YgZT09ImZ1bmN0aW9uIj9JRGUoY1ModGhpcywidGV4dCIsZSkpOlBEZShlPT1udWxsPyIiOmUrIiIpKX12YXIgd2p0PU0oKCk9PntEQSgpfSk7ZnVuY3Rpb24gTERlKGUpe3JldHVybiBmdW5jdGlvbih0KXt0aGlzLnRleHRDb250ZW50PWUuY2FsbCh0aGlzLHQpfX1mdW5jdGlvbiBrRGUoZSl7dmFyIHQscjtmdW5jdGlvbiBuKCl7dmFyIGk9ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cmV0dXJuIGkhPT1yJiYodD0ocj1pKSYmTERlKGkpKSx0fXJldHVybiBuLl92YWx1ZT1lLG59ZnVuY3Rpb24gU2p0KGUpe3ZhciB0PSJ0ZXh0IjtpZihhcmd1bWVudHMubGVuZ3RoPDEpcmV0dXJuKHQ9dGhpcy50d2Vlbih0KSkmJnQuX3ZhbHVlO2lmKGU9PW51bGwpcmV0dXJuIHRoaXMudHdlZW4odCxudWxsKTtpZih0eXBlb2YgZSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3I7cmV0dXJuIHRoaXMudHdlZW4odCxrRGUoZSkpfXZhciBNanQ9TSgoKT0+e30pO2Z1bmN0aW9uIEVqdCgpe2Zvcih2YXIgZT10aGlzLl9uYW1lLHQ9dGhpcy5faWQscj1pRigpLG49dGhpcy5fZ3JvdXBzLGk9bi5sZW5ndGgsbz0wO288aTsrK28pZm9yKHZhciBhPW5bb10scz1hLmxlbmd0aCxsLGM9MDtjPHM7KytjKWlmKGw9YVtjXSl7dmFyIHU9b28obCx0KTtHZyhsLGUscixjLGEse3RpbWU6dS50aW1lK3UuZGVsYXkrdS5kdXJhdGlvbixkZWxheTowLGR1cmF0aW9uOnUuZHVyYXRpb24sZWFzZTp1LmVhc2V9KX1yZXR1cm4gbmV3IGRhKG4sdGhpcy5fcGFyZW50cyxlLHIpfXZhciBUanQ9TSgoKT0+e2RkKCk7THMoKX0pO2Z1bmN0aW9uIENqdCgpe3ZhciBlLHQscj10aGlzLG49ci5faWQsaT1yLnNpemUoKTtyZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24obyxhKXt2YXIgcz17dmFsdWU6YX0sbD17dmFsdWU6ZnVuY3Rpb24oKXstLWk9PT0wJiZvKCl9fTtyLmVhY2goZnVuY3Rpb24oKXt2YXIgYz1KYSh0aGlzLG4pLHU9Yy5vbjt1IT09ZSYmKHQ9KGU9dSkuY29weSgpLHQuXy5jYW5jZWwucHVzaChzKSx0Ll8uaW50ZXJydXB0LnB1c2gocyksdC5fLmVuZC5wdXNoKGwpKSxjLm9uPXR9KX0pfXZhciBBanQ9TSgoKT0+e0xzKCl9KTtmdW5jdGlvbiBkYShlLHQscixuKXt0aGlzLl9ncm91cHM9ZSx0aGlzLl9wYXJlbnRzPXQsdGhpcy5fbmFtZT1yLHRoaXMuX2lkPW59ZnVuY3Rpb24gZWl0KGUpe3JldHVybiBmZCgpLnRyYW5zaXRpb24oZSl9ZnVuY3Rpb24gaUYoKXtyZXR1cm4rK1JEZX12YXIgUkRlLHVTLGRkPU0oKCk9PntJcygpO2pZdCgpOyRZdCgpO1pZdCgpO1FZdCgpO2VqdCgpO25qdCgpO29qdCgpO3NqdCgpO2NqdCgpO2hqdCgpO3BqdCgpO21qdCgpO3lqdCgpO3hqdCgpO3dqdCgpO01qdCgpO1RqdCgpO0RBKCk7QWp0KCk7UkRlPTA7dVM9ZmQucHJvdG90eXBlO2RhLnByb3RvdHlwZT1laXQucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpkYSxzZWxlY3Q6dWp0LHNlbGVjdEFsbDpmanQsZmlsdGVyOnJqdCxtZXJnZTppanQsc2VsZWN0aW9uOmRqdCx0cmFuc2l0aW9uOkVqdCxjYWxsOnVTLmNhbGwsbm9kZXM6dVMubm9kZXMsbm9kZTp1Uy5ub2RlLHNpemU6dVMuc2l6ZSxlbXB0eTp1Uy5lbXB0eSxlYWNoOnVTLmVhY2gsb246YWp0LGF0dHI6WVl0LGF0dHJUd2VlbjpYWXQsc3R5bGU6X2p0LHN0eWxlVHdlZW46dmp0LHRleHQ6Ymp0LHRleHRUd2VlbjpTanQscmVtb3ZlOmxqdCx0d2VlbjpXWXQsZGVsYXk6S1l0LGR1cmF0aW9uOkpZdCxlYXNlOnRqdCxlbmQ6Q2p0fX0pO2Z1bmN0aW9uIE5EZShlLHQpe2Zvcih2YXIgcjshKHI9ZS5fX3RyYW5zaXRpb24pfHwhKHI9clt0XSk7KWlmKCEoZT1lLnBhcmVudE5vZGUpKXJldHVybiByaXQudGltZT1sUygpLHJpdDtyZXR1cm4gcn1mdW5jdGlvbiBQanQoZSl7dmFyIHQscjtlIGluc3RhbmNlb2YgZGE/KHQ9ZS5faWQsZT1lLl9uYW1lKToodD1pRigpLChyPXJpdCkudGltZT1sUygpLGU9ZT09bnVsbD9udWxsOmUrIiIpO2Zvcih2YXIgbj10aGlzLl9ncm91cHMsaT1uLmxlbmd0aCxvPTA7bzxpOysrbylmb3IodmFyIGE9bltvXSxzPWEubGVuZ3RoLGwsYz0wO2M8czsrK2MpKGw9YVtjXSkmJkdnKGwsZSx0LGMsYSxyfHxORGUobCx0KSk7cmV0dXJuIG5ldyBkYShuLHRoaXMuX3BhcmVudHMsZSx0KX12YXIgcml0LElqdD1NKCgpPT57ZGQoKTtMcygpO0lfKCk7Wm50KCk7cml0PXt0aW1lOm51bGwsZGVsYXk6MCxkdXJhdGlvbjoyNTAsZWFzZTp4c319KTt2YXIgTGp0PU0oKCk9PntJcygpO0dZdCgpO0lqdCgpO2ZkLnByb3RvdHlwZS5pbnRlcnJ1cHQ9cVl0O2ZkLnByb3RvdHlwZS50cmFuc2l0aW9uPVBqdH0pO3ZhciBranQ9TSgoKT0+e2RkKCk7THMoKX0pO3ZhciBSanQ9TSgoKT0+e0xqdCgpO2RkKCk7a2p0KCk7UW50KCl9KTtmdW5jdGlvbiBPQShlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIE5qdD1NKCgpPT57fSk7ZnVuY3Rpb24gbml0KGUsdCxyKXt0aGlzLnRhcmdldD1lLHRoaXMudHlwZT10LHRoaXMudHJhbnNmb3JtPXJ9dmFyIERqdD1NKCgpPT57fSk7ZnVuY3Rpb24gUGYoZSx0LHIpe3RoaXMuaz1lLHRoaXMueD10LHRoaXMueT1yfWZ1bmN0aW9uIG9GKGUpe3JldHVybiBlLl9fem9vbXx8aFN9dmFyIGhTLGlpdD1NKCgpPT57UGYucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpQZixzY2FsZTpmdW5jdGlvbihlKXtyZXR1cm4gZT09PTE/dGhpczpuZXcgUGYodGhpcy5rKmUsdGhpcy54LHRoaXMueSl9LHRyYW5zbGF0ZTpmdW5jdGlvbihlLHQpe3JldHVybiBlPT09MCZ0PT09MD90aGlzOm5ldyBQZih0aGlzLmssdGhpcy54K3RoaXMuayplLHRoaXMueSt0aGlzLmsqdCl9LGFwcGx5OmZ1bmN0aW9uKGUpe3JldHVybltlWzBdKnRoaXMuayt0aGlzLngsZVsxXSp0aGlzLmsrdGhpcy55XX0sYXBwbHlYOmZ1bmN0aW9uKGUpe3JldHVybiBlKnRoaXMuayt0aGlzLnh9LGFwcGx5WTpmdW5jdGlvbihlKXtyZXR1cm4gZSp0aGlzLmsrdGhpcy55fSxpbnZlcnQ6ZnVuY3Rpb24oZSl7cmV0dXJuWyhlWzBdLXRoaXMueCkvdGhpcy5rLChlWzFdLXRoaXMueSkvdGhpcy5rXX0saW52ZXJ0WDpmdW5jdGlvbihlKXtyZXR1cm4oZS10aGlzLngpL3RoaXMua30saW52ZXJ0WTpmdW5jdGlvbihlKXtyZXR1cm4oZS10aGlzLnkpL3RoaXMua30scmVzY2FsZVg6ZnVuY3Rpb24oZSl7cmV0dXJuIGUuY29weSgpLmRvbWFpbihlLnJhbmdlKCkubWFwKHRoaXMuaW52ZXJ0WCx0aGlzKS5tYXAoZS5pbnZlcnQsZSkpfSxyZXNjYWxlWTpmdW5jdGlvbihlKXtyZXR1cm4gZS5jb3B5KCkuZG9tYWluKGUucmFuZ2UoKS5tYXAodGhpcy5pbnZlcnRZLHRoaXMpLm1hcChlLmludmVydCxlKSl9LHRvU3RyaW5nOmZ1bmN0aW9uKCl7cmV0dXJuInRyYW5zbGF0ZSgiK3RoaXMueCsiLCIrdGhpcy55KyIpIHNjYWxlKCIrdGhpcy5rKyIpIn19O2hTPW5ldyBQZigxLDAsMCk7b0YucHJvdG90eXBlPVBmLnByb3RvdHlwZX0pO2Z1bmN0aW9uIGFGKCl7WXIuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCl9ZnVuY3Rpb24gZlMoKXtZci5wcmV2ZW50RGVmYXVsdCgpLFlyLnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpfXZhciBPanQ9TSgoKT0+e0lzKCl9KTtmdW5jdGlvbiBERGUoKXtyZXR1cm4hWXIuYnV0dG9ufWZ1bmN0aW9uIE9EZSgpe3ZhciBlPXRoaXMsdCxyO3JldHVybiBlIGluc3RhbmNlb2YgU1ZHRWxlbWVudD8oZT1lLm93bmVyU1ZHRWxlbWVudHx8ZSx0PWUud2lkdGguYmFzZVZhbC52YWx1ZSxyPWUuaGVpZ2h0LmJhc2VWYWwudmFsdWUpOih0PWUuY2xpZW50V2lkdGgscj1lLmNsaWVudEhlaWdodCksW1swLDBdLFt0LHJdXX1mdW5jdGlvbiB6anQoKXtyZXR1cm4gdGhpcy5fX3pvb218fGhTfWZ1bmN0aW9uIHpEZSgpe3JldHVybi1Zci5kZWx0YVkqKFlyLmRlbHRhTW9kZT8xMjA6MSkvNTAwfWZ1bmN0aW9uIEZEZSgpe3JldHVybiJvbnRvdWNoc3RhcnQiaW4gdGhpc31mdW5jdGlvbiBCRGUoZSx0LHIpe3ZhciBuPWUuaW52ZXJ0WCh0WzBdWzBdKS1yWzBdWzBdLGk9ZS5pbnZlcnRYKHRbMV1bMF0pLXJbMV1bMF0sbz1lLmludmVydFkodFswXVsxXSktclswXVsxXSxhPWUuaW52ZXJ0WSh0WzFdWzFdKS1yWzFdWzFdO3JldHVybiBlLnRyYW5zbGF0ZShpPm4/KG4raSkvMjpNYXRoLm1pbigwLG4pfHxNYXRoLm1heCgwLGkpLGE+bz8obythKS8yOk1hdGgubWluKDAsbyl8fE1hdGgubWF4KDAsYSkpfWZ1bmN0aW9uIEZqdCgpe3ZhciBlPUREZSx0PU9EZSxyPUJEZSxuPXpEZSxpPUZEZSxvPVswLDEvMF0sYT1bWy0xLzAsLTEvMF0sWzEvMCwxLzBdXSxzPTI1MCxsPVhudCxjPVtdLHU9eUEoInN0YXJ0Iiwiem9vbSIsImVuZCIpLGgsZixwPTUwMCxkPTE1MCxnPTA7ZnVuY3Rpb24gXyhSKXtSLnByb3BlcnR5KCJfX3pvb20iLHpqdCkub24oIndoZWVsLnpvb20iLGspLm9uKCJtb3VzZWRvd24uem9vbSIsTykub24oImRibGNsaWNrLnpvb20iLEQpLmZpbHRlcihpKS5vbigidG91Y2hzdGFydC56b29tIixCKS5vbigidG91Y2htb3ZlLnpvb20iLEkpLm9uKCJ0b3VjaGVuZC56b29tIHRvdWNoY2FuY2VsLnpvb20iLEwpLnN0eWxlKCJ0b3VjaC1hY3Rpb24iLCJub25lIikuc3R5bGUoIi13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvciIsInJnYmEoMCwwLDAsMCkiKX1fLnRyYW5zZm9ybT1mdW5jdGlvbihSLEYpe3ZhciB6PVIuc2VsZWN0aW9uP1Iuc2VsZWN0aW9uKCk6Ujt6LnByb3BlcnR5KCJfX3pvb20iLHpqdCksUiE9PXo/UyhSLEYpOnouaW50ZXJydXB0KCkuZWFjaChmdW5jdGlvbigpe0ModGhpcyxhcmd1bWVudHMpLnN0YXJ0KCkuem9vbShudWxsLHR5cGVvZiBGPT0iZnVuY3Rpb24iP0YuYXBwbHkodGhpcyxhcmd1bWVudHMpOkYpLmVuZCgpfSl9LF8uc2NhbGVCeT1mdW5jdGlvbihSLEYpe18uc2NhbGVUbyhSLGZ1bmN0aW9uKCl7dmFyIHo9dGhpcy5fX3pvb20uayxVPXR5cGVvZiBGPT0iZnVuY3Rpb24iP0YuYXBwbHkodGhpcyxhcmd1bWVudHMpOkY7cmV0dXJuIHoqVX0pfSxfLnNjYWxlVG89ZnVuY3Rpb24oUixGKXtfLnRyYW5zZm9ybShSLGZ1bmN0aW9uKCl7dmFyIHo9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyksVT10aGlzLl9fem9vbSxXPWIoeiksWj1VLmludmVydChXKSxydD10eXBlb2YgRj09ImZ1bmN0aW9uIj9GLmFwcGx5KHRoaXMsYXJndW1lbnRzKTpGO3JldHVybiByKHgoeShVLHJ0KSxXLFopLHosYSl9KX0sXy50cmFuc2xhdGVCeT1mdW5jdGlvbihSLEYseil7Xy50cmFuc2Zvcm0oUixmdW5jdGlvbigpe3JldHVybiByKHRoaXMuX196b29tLnRyYW5zbGF0ZSh0eXBlb2YgRj09ImZ1bmN0aW9uIj9GLmFwcGx5KHRoaXMsYXJndW1lbnRzKTpGLHR5cGVvZiB6PT0iZnVuY3Rpb24iP3ouYXBwbHkodGhpcyxhcmd1bWVudHMpOnopLHQuYXBwbHkodGhpcyxhcmd1bWVudHMpLGEpfSl9LF8udHJhbnNsYXRlVG89ZnVuY3Rpb24oUixGLHope18udHJhbnNmb3JtKFIsZnVuY3Rpb24oKXt2YXIgVT10LmFwcGx5KHRoaXMsYXJndW1lbnRzKSxXPXRoaXMuX196b29tLFo9YihVKTtyZXR1cm4gcihoUy50cmFuc2xhdGUoWlswXSxaWzFdKS5zY2FsZShXLmspLnRyYW5zbGF0ZSh0eXBlb2YgRj09ImZ1bmN0aW9uIj8tRi5hcHBseSh0aGlzLGFyZ3VtZW50cyk6LUYsdHlwZW9mIHo9PSJmdW5jdGlvbiI/LXouYXBwbHkodGhpcyxhcmd1bWVudHMpOi16KSxVLGEpfSl9O2Z1bmN0aW9uIHkoUixGKXtyZXR1cm4gRj1NYXRoLm1heChvWzBdLE1hdGgubWluKG9bMV0sRikpLEY9PT1SLms/UjpuZXcgUGYoRixSLngsUi55KX1mdW5jdGlvbiB4KFIsRix6KXt2YXIgVT1GWzBdLXpbMF0qUi5rLFc9RlsxXS16WzFdKlIuaztyZXR1cm4gVT09PVIueCYmVz09PVIueT9SOm5ldyBQZihSLmssVSxXKX1mdW5jdGlvbiBiKFIpe3JldHVyblsoK1JbMF1bMF0rICtSWzFdWzBdKS8yLCgrUlswXVsxXSsgK1JbMV1bMV0pLzJdfWZ1bmN0aW9uIFMoUixGLHope1Iub24oInN0YXJ0Lnpvb20iLGZ1bmN0aW9uKCl7Qyh0aGlzLGFyZ3VtZW50cykuc3RhcnQoKX0pLm9uKCJpbnRlcnJ1cHQuem9vbSBlbmQuem9vbSIsZnVuY3Rpb24oKXtDKHRoaXMsYXJndW1lbnRzKS5lbmQoKX0pLnR3ZWVuKCJ6b29tIixmdW5jdGlvbigpe3ZhciBVPXRoaXMsVz1hcmd1bWVudHMsWj1DKFUsVykscnQ9dC5hcHBseShVLFcpLG90PXp8fGIocnQpLHN0PU1hdGgubWF4KHJ0WzFdWzBdLXJ0WzBdWzBdLHJ0WzFdWzFdLXJ0WzBdWzFdKSxTdD1VLl9fem9vbSxidD10eXBlb2YgRj09ImZ1bmN0aW9uIj9GLmFwcGx5KFUsVyk6RixNdD1sKFN0LmludmVydChvdCkuY29uY2F0KHN0L1N0LmspLGJ0LmludmVydChvdCkuY29uY2F0KHN0L2J0LmspKTtyZXR1cm4gZnVuY3Rpb24obHQpe2lmKGx0PT09MSlsdD1idDtlbHNle3ZhciBLdD1NdChsdCksX3Q9c3QvS3RbMl07bHQ9bmV3IFBmKF90LG90WzBdLUt0WzBdKl90LG90WzFdLUt0WzFdKl90KX1aLnpvb20obnVsbCxsdCl9fSl9ZnVuY3Rpb24gQyhSLEYpe2Zvcih2YXIgej0wLFU9Yy5sZW5ndGgsVzt6PFU7Kyt6KWlmKChXPWNbel0pLnRoYXQ9PT1SKXJldHVybiBXO3JldHVybiBuZXcgUChSLEYpfWZ1bmN0aW9uIFAoUixGKXt0aGlzLnRoYXQ9Uix0aGlzLmFyZ3M9Rix0aGlzLmluZGV4PS0xLHRoaXMuYWN0aXZlPTAsdGhpcy5leHRlbnQ9dC5hcHBseShSLEYpfVAucHJvdG90eXBlPXtzdGFydDpmdW5jdGlvbigpe3JldHVybisrdGhpcy5hY3RpdmU9PT0xJiYodGhpcy5pbmRleD1jLnB1c2godGhpcyktMSx0aGlzLmVtaXQoInN0YXJ0IikpLHRoaXN9LHpvb206ZnVuY3Rpb24oUixGKXtyZXR1cm4gdGhpcy5tb3VzZSYmUiE9PSJtb3VzZSImJih0aGlzLm1vdXNlWzFdPUYuaW52ZXJ0KHRoaXMubW91c2VbMF0pKSx0aGlzLnRvdWNoMCYmUiE9PSJ0b3VjaCImJih0aGlzLnRvdWNoMFsxXT1GLmludmVydCh0aGlzLnRvdWNoMFswXSkpLHRoaXMudG91Y2gxJiZSIT09InRvdWNoIiYmKHRoaXMudG91Y2gxWzFdPUYuaW52ZXJ0KHRoaXMudG91Y2gxWzBdKSksdGhpcy50aGF0Ll9fem9vbT1GLHRoaXMuZW1pdCgiem9vbSIpLHRoaXN9LGVuZDpmdW5jdGlvbigpe3JldHVybi0tdGhpcy5hY3RpdmU9PT0wJiYoYy5zcGxpY2UodGhpcy5pbmRleCwxKSx0aGlzLmluZGV4PS0xLHRoaXMuZW1pdCgiZW5kIikpLHRoaXN9LGVtaXQ6ZnVuY3Rpb24oUil7Q250KG5ldyBuaXQoXyxSLHRoaXMudGhhdC5fX3pvb20pLHUuYXBwbHksdSxbUix0aGlzLnRoYXQsdGhpcy5hcmdzXSl9fTtmdW5jdGlvbiBrKCl7aWYoIWUuYXBwbHkodGhpcyxhcmd1bWVudHMpKXJldHVybjt2YXIgUj1DKHRoaXMsYXJndW1lbnRzKSxGPXRoaXMuX196b29tLHo9TWF0aC5tYXgob1swXSxNYXRoLm1pbihvWzFdLEYuaypNYXRoLnBvdygyLG4uYXBwbHkodGhpcyxhcmd1bWVudHMpKSkpLFU9aVModGhpcyk7aWYoUi53aGVlbCkoUi5tb3VzZVswXVswXSE9PVVbMF18fFIubW91c2VbMF1bMV0hPT1VWzFdKSYmKFIubW91c2VbMV09Ri5pbnZlcnQoUi5tb3VzZVswXT1VKSksY2xlYXJUaW1lb3V0KFIud2hlZWwpO2Vsc2V7aWYoRi5rPT09eilyZXR1cm47Ui5tb3VzZT1bVSxGLmludmVydChVKV0sUDEodGhpcyksUi5zdGFydCgpfWZTKCksUi53aGVlbD1zZXRUaW1lb3V0KFcsZCksUi56b29tKCJtb3VzZSIscih4KHkoRix6KSxSLm1vdXNlWzBdLFIubW91c2VbMV0pLFIuZXh0ZW50LGEpKTtmdW5jdGlvbiBXKCl7Ui53aGVlbD1udWxsLFIuZW5kKCl9fWZ1bmN0aW9uIE8oKXtpZihmfHwhZS5hcHBseSh0aGlzLGFyZ3VtZW50cykpcmV0dXJuO3ZhciBSPUModGhpcyxhcmd1bWVudHMpLEY9cGQoWXIudmlldykub24oIm1vdXNlbW92ZS56b29tIixaLCEwKS5vbigibW91c2V1cC56b29tIixydCwhMCksej1pUyh0aGlzKSxVPVlyLmNsaWVudFgsVz1Zci5jbGllbnRZO0xudChZci52aWV3KSxhRigpLFIubW91c2U9W3osdGhpcy5fX3pvb20uaW52ZXJ0KHopXSxQMSh0aGlzKSxSLnN0YXJ0KCk7ZnVuY3Rpb24gWigpe2lmKGZTKCksIVIubW92ZWQpe3ZhciBvdD1Zci5jbGllbnRYLVUsc3Q9WXIuY2xpZW50WS1XO1IubW92ZWQ9b3Qqb3Qrc3Qqc3Q+Z31SLnpvb20oIm1vdXNlIixyKHgoUi50aGF0Ll9fem9vbSxSLm1vdXNlWzBdPWlTKFIudGhhdCksUi5tb3VzZVsxXSksUi5leHRlbnQsYSkpfWZ1bmN0aW9uIHJ0KCl7Ri5vbigibW91c2Vtb3ZlLnpvb20gbW91c2V1cC56b29tIixudWxsKSxrbnQoWXIudmlldyxSLm1vdmVkKSxmUygpLFIuZW5kKCl9fWZ1bmN0aW9uIEQoKXtpZighIWUuYXBwbHkodGhpcyxhcmd1bWVudHMpKXt2YXIgUj10aGlzLl9fem9vbSxGPWlTKHRoaXMpLHo9Ui5pbnZlcnQoRiksVT1SLmsqKFlyLnNoaWZ0S2V5Py41OjIpLFc9cih4KHkoUixVKSxGLHopLHQuYXBwbHkodGhpcyxhcmd1bWVudHMpLGEpO2ZTKCkscz4wP3BkKHRoaXMpLnRyYW5zaXRpb24oKS5kdXJhdGlvbihzKS5jYWxsKFMsVyxGKTpwZCh0aGlzKS5jYWxsKF8udHJhbnNmb3JtLFcpfX1mdW5jdGlvbiBCKCl7aWYoISFlLmFwcGx5KHRoaXMsYXJndW1lbnRzKSl7dmFyIFI9Qyh0aGlzLGFyZ3VtZW50cyksRj1Zci5jaGFuZ2VkVG91Y2hlcyx6LFU9Ri5sZW5ndGgsVyxaLHJ0O2ZvcihhRigpLFc9MDtXPFU7KytXKVo9RltXXSxydD1Ceih0aGlzLEYsWi5pZGVudGlmaWVyKSxydD1bcnQsdGhpcy5fX3pvb20uaW52ZXJ0KHJ0KSxaLmlkZW50aWZpZXJdLFIudG91Y2gwP1IudG91Y2gxfHwoUi50b3VjaDE9cnQpOihSLnRvdWNoMD1ydCx6PSEwKTtpZihoJiYoaD1jbGVhclRpbWVvdXQoaCksIVIudG91Y2gxKSl7Ui5lbmQoKSxydD1wZCh0aGlzKS5vbigiZGJsY2xpY2suem9vbSIpLHJ0JiZydC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cmV0dXJufXomJihoPXNldFRpbWVvdXQoZnVuY3Rpb24oKXtoPW51bGx9LHApLFAxKHRoaXMpLFIuc3RhcnQoKSl9fWZ1bmN0aW9uIEkoKXt2YXIgUj1DKHRoaXMsYXJndW1lbnRzKSxGPVlyLmNoYW5nZWRUb3VjaGVzLHo9Ri5sZW5ndGgsVSxXLFoscnQ7Zm9yKGZTKCksaCYmKGg9Y2xlYXJUaW1lb3V0KGgpKSxVPTA7VTx6OysrVSlXPUZbVV0sWj1Ceih0aGlzLEYsVy5pZGVudGlmaWVyKSxSLnRvdWNoMCYmUi50b3VjaDBbMl09PT1XLmlkZW50aWZpZXI/Ui50b3VjaDBbMF09WjpSLnRvdWNoMSYmUi50b3VjaDFbMl09PT1XLmlkZW50aWZpZXImJihSLnRvdWNoMVswXT1aKTtpZihXPVIudGhhdC5fX3pvb20sUi50b3VjaDEpe3ZhciBvdD1SLnRvdWNoMFswXSxzdD1SLnRvdWNoMFsxXSxTdD1SLnRvdWNoMVswXSxidD1SLnRvdWNoMVsxXSxNdD0oTXQ9U3RbMF0tb3RbMF0pKk10KyhNdD1TdFsxXS1vdFsxXSkqTXQsbHQ9KGx0PWJ0WzBdLXN0WzBdKSpsdCsobHQ9YnRbMV0tc3RbMV0pKmx0O1c9eShXLE1hdGguc3FydChNdC9sdCkpLFo9WyhvdFswXStTdFswXSkvMiwob3RbMV0rU3RbMV0pLzJdLHJ0PVsoc3RbMF0rYnRbMF0pLzIsKHN0WzFdK2J0WzFdKS8yXX1lbHNlIGlmKFIudG91Y2gwKVo9Ui50b3VjaDBbMF0scnQ9Ui50b3VjaDBbMV07ZWxzZSByZXR1cm47Ui56b29tKCJ0b3VjaCIscih4KFcsWixydCksUi5leHRlbnQsYSkpfWZ1bmN0aW9uIEwoKXt2YXIgUj1DKHRoaXMsYXJndW1lbnRzKSxGPVlyLmNoYW5nZWRUb3VjaGVzLHo9Ri5sZW5ndGgsVSxXO2ZvcihhRigpLGYmJmNsZWFyVGltZW91dChmKSxmPXNldFRpbWVvdXQoZnVuY3Rpb24oKXtmPW51bGx9LHApLFU9MDtVPHo7KytVKVc9RltVXSxSLnRvdWNoMCYmUi50b3VjaDBbMl09PT1XLmlkZW50aWZpZXI/ZGVsZXRlIFIudG91Y2gwOlIudG91Y2gxJiZSLnRvdWNoMVsyXT09PVcuaWRlbnRpZmllciYmZGVsZXRlIFIudG91Y2gxO1IudG91Y2gxJiYhUi50b3VjaDAmJihSLnRvdWNoMD1SLnRvdWNoMSxkZWxldGUgUi50b3VjaDEpLFIudG91Y2gwP1IudG91Y2gwWzFdPXRoaXMuX196b29tLmludmVydChSLnRvdWNoMFswXSk6Ui5lbmQoKX1yZXR1cm4gXy53aGVlbERlbHRhPWZ1bmN0aW9uKFIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPXR5cGVvZiBSPT0iZnVuY3Rpb24iP1I6T0EoK1IpLF8pOm59LF8uZmlsdGVyPWZ1bmN0aW9uKFIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiBSPT0iZnVuY3Rpb24iP1I6T0EoISFSKSxfKTplfSxfLnRvdWNoYWJsZT1mdW5jdGlvbihSKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT10eXBlb2YgUj09ImZ1bmN0aW9uIj9SOk9BKCEhUiksXyk6aX0sXy5leHRlbnQ9ZnVuY3Rpb24oUil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9dHlwZW9mIFI9PSJmdW5jdGlvbiI/UjpPQShbWytSWzBdWzBdLCtSWzBdWzFdXSxbK1JbMV1bMF0sK1JbMV1bMV1dXSksXyk6dH0sXy5zY2FsZUV4dGVudD1mdW5jdGlvbihSKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ob1swXT0rUlswXSxvWzFdPStSWzFdLF8pOltvWzBdLG9bMV1dfSxfLnRyYW5zbGF0ZUV4dGVudD1mdW5jdGlvbihSKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYVswXVswXT0rUlswXVswXSxhWzFdWzBdPStSWzFdWzBdLGFbMF1bMV09K1JbMF1bMV0sYVsxXVsxXT0rUlsxXVsxXSxfKTpbW2FbMF1bMF0sYVswXVsxXV0sW2FbMV1bMF0sYVsxXVsxXV1dfSxfLmNvbnN0cmFpbj1mdW5jdGlvbihSKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj1SLF8pOnJ9LF8uZHVyYXRpb249ZnVuY3Rpb24oUil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHM9K1IsXyk6c30sXy5pbnRlcnBvbGF0ZT1mdW5jdGlvbihSKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obD1SLF8pOmx9LF8ub249ZnVuY3Rpb24oKXt2YXIgUj11Lm9uLmFwcGx5KHUsYXJndW1lbnRzKTtyZXR1cm4gUj09PXU/XzpSfSxfLmNsaWNrRGlzdGFuY2U9ZnVuY3Rpb24oUil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGc9KFI9K1IpKlIsXyk6TWF0aC5zcXJ0KGcpfSxffXZhciBCanQ9TSgoKT0+e2dudCgpO2lZdCgpO0NBKCk7SXMoKTtSanQoKTtOanQoKTtEanQoKTtpaXQoKTtPanQoKX0pO3ZhciBIanQ9TSgoKT0+e0JqdCgpO2lpdCgpfSk7dmFyIE1yPXt9O0tzKE1yLHthY3RpdmU6KCk9PnVHdCxhcmM6KCk9PmhGdCxhcmVhOigpPT54NyxhcmVhUmFkaWFsOigpPT5SZXQsYXNjZW5kaW5nOigpPT5fYyxheGlzQm90dG9tOigpPT5ZTXQsYXhpc0xlZnQ6KCk9PmpNdCxheGlzUmlnaHQ6KCk9PldNdCxheGlzVG9wOigpPT5HTXQsYmlzZWN0OigpPT5hUixiaXNlY3RMZWZ0OigpPT5yTXQsYmlzZWN0UmlnaHQ6KCk9PmIkLGJpc2VjdG9yOigpPT5vUixicnVzaDooKT0+cEN0LGJydXNoU2VsZWN0aW9uOigpPT51Q3QsYnJ1c2hYOigpPT5oQ3QsYnJ1c2hZOigpPT5mQ3QsY2hvcmQ6KCk9PnpDdCxjbGllbnRQb2ludDooKT0+RGcsY2x1c3RlcjooKT0+Qkx0LGNvbG9yOigpPT54MixjcmVhdGU6KCk9PiR6dCxjcmVhdG9yOigpPT5OZyxjcm9zczooKT0+aU10LGNzdjooKT0+bE50LGNzdkZvcm1hdDooKT0+cFB0LGNzdkZvcm1hdFJvd3M6KCk9PmRQdCxjc3ZQYXJzZTooKT0+aFB0LGNzdlBhcnNlUm93czooKT0+ZlB0LGN1YmVoZWxpeDooKT0+Y04sY3VydmVCYXNpczooKT0+TEZ0LGN1cnZlQmFzaXNDbG9zZWQ6KCk9PlJGdCxjdXJ2ZUJhc2lzT3BlbjooKT0+T0Z0LGN1cnZlQnVuZGxlOigpPT5CRnQsY3VydmVDYXJkaW5hbDooKT0+VkZ0LGN1cnZlQ2FyZGluYWxDbG9zZWQ6KCk9PlVGdCxjdXJ2ZUNhcmRpbmFsT3BlbjooKT0+cUZ0LGN1cnZlQ2F0bXVsbFJvbTooKT0+V0Z0LGN1cnZlQ2F0bXVsbFJvbUNsb3NlZDooKT0+akZ0LGN1cnZlQ2F0bXVsbFJvbU9wZW46KCk9PktGdCxjdXJ2ZUxpbmVhcjooKT0+RmcsY3VydmVMaW5lYXJDbG9zZWQ6KCk9PlFGdCxjdXJ2ZU1vbm90b25lWDooKT0+YUJ0LGN1cnZlTW9ub3RvbmVZOigpPT5zQnQsY3VydmVOYXR1cmFsOigpPT5oQnQsY3VydmVTdGVwOigpPT5wQnQsY3VydmVTdGVwQWZ0ZXI6KCk9Pm1CdCxjdXJ2ZVN0ZXBCZWZvcmU6KCk9PmRCdCxjdXN0b21FdmVudDooKT0+cXp0LGRlc2NlbmRpbmc6KCk9PmFNdCxkZXZpYXRpb246KCk9PmxSLGRpc3BhdGNoOigpPT5DQXQsZHJhZzooKT0+YVB0LGRyYWdEaXNhYmxlOigpPT5NTixkcmFnRW5hYmxlOigpPT5FTixkc3ZGb3JtYXQ6KCk9PncyLGVhc2VCYWNrOigpPT56WixlYXNlQmFja0luOigpPT5qUHQsZWFzZUJhY2tJbk91dDooKT0+elosZWFzZUJhY2tPdXQ6KCk9PlhQdCxlYXNlQm91bmNlOigpPT5TMixlYXNlQm91bmNlSW46KCk9PkdQdCxlYXNlQm91bmNlSW5PdXQ6KCk9PldQdCxlYXNlQm91bmNlT3V0OigpPT5TMixlYXNlQ2lyY2xlOigpPT5OWixlYXNlQ2lyY2xlSW46KCk9PlZQdCxlYXNlQ2lyY2xlSW5PdXQ6KCk9Pk5aLGVhc2VDaXJjbGVPdXQ6KCk9PlVQdCxlYXNlQ3ViaWM6KCk9PlBaLGVhc2VDdWJpY0luOigpPT5DUHQsZWFzZUN1YmljSW5PdXQ6KCk9PlBaLGVhc2VDdWJpY091dDooKT0+QVB0LGVhc2VFbGFzdGljOigpPT5IWixlYXNlRWxhc3RpY0luOigpPT5LUHQsZWFzZUVsYXN0aWNJbk91dDooKT0+WlB0LGVhc2VFbGFzdGljT3V0OigpPT5IWixlYXNlRXhwOigpPT5SWixlYXNlRXhwSW46KCk9PkZQdCxlYXNlRXhwSW5PdXQ6KCk9PlJaLGVhc2VFeHBPdXQ6KCk9PkJQdCxlYXNlTGluZWFyOigpPT53UHQsZWFzZVBvbHk6KCk9PkxaLGVhc2VQb2x5SW46KCk9PklQdCxlYXNlUG9seUluT3V0OigpPT5MWixlYXNlUG9seU91dDooKT0+TFB0LGVhc2VRdWFkOigpPT5BWixlYXNlUXVhZEluOigpPT5NUHQsZWFzZVF1YWRJbk91dDooKT0+QVosZWFzZVF1YWRPdXQ6KCk9PkVQdCxlYXNlU2luOigpPT5rWixlYXNlU2luSW46KCk9PkRQdCxlYXNlU2luSW5PdXQ6KCk9PmtaLGVhc2VTaW5PdXQ6KCk9Pk9QdCxlbnRyaWVzOigpPT5hQXQsZXZlbnQ6KCk9PlB1LGV4dGVudDooKT0+Y1IsZm9yY2VDZW50ZXI6KCk9PnQ2dCxmb3JjZUNvbGxpZGU6KCk9PlI2dCxmb3JjZUxpbms6KCk9PlU2dCxmb3JjZU1hbnlCb2R5OigpPT5uSXQsZm9yY2VSYWRpYWw6KCk9Pm9JdCxmb3JjZVNpbXVsYXRpb246KCk9PnJJdCxmb3JjZVg6KCk9PnNJdCxmb3JjZVk6KCk9PmNJdCxmb3JtYXQ6KCk9Pm5KLGZvcm1hdERlZmF1bHRMb2NhbGU6KCk9PkdOLGZvcm1hdExvY2FsZTooKT0+VU4sZm9ybWF0UHJlZml4OigpPT5pSixmb3JtYXRTcGVjaWZpZXI6KCk9Pkx5LGdlb0FsYmVyczooKT0+TkQsZ2VvQWxiZXJzVXNhOigpPT5kTHQsZ2VvQXJlYTooKT0+ekl0LGdlb0F6aW11dGhhbEVxdWFsQXJlYTooKT0+Z0x0LGdlb0F6aW11dGhhbEVxdWFsQXJlYVJhdzooKT0+T0QsZ2VvQXppbXV0aGFsRXF1aWRpc3RhbnQ6KCk9PnlMdCxnZW9BemltdXRoYWxFcXVpZGlzdGFudFJhdzooKT0+ekQsZ2VvQm91bmRzOigpPT5XSXQsZ2VvQ2VudHJvaWQ6KCk9PkpJdCxnZW9DaXJjbGU6KCk9Pm85dCxnZW9DbGlwQW50aW1lcmlkaWFuOigpPT5PVCxnZW9DbGlwQ2lyY2xlOigpPT5nRCxnZW9DbGlwRXh0ZW50OigpPT5BOXQsZ2VvQ2xpcFJlY3RhbmdsZTooKT0+UXAsZ2VvQ29uaWNDb25mb3JtYWw6KCk9PmJMdCxnZW9Db25pY0NvbmZvcm1hbFJhdzooKT0+cFEsZ2VvQ29uaWNFcXVhbEFyZWE6KCk9PlV5LGdlb0NvbmljRXF1YWxBcmVhUmF3OigpPT51USxnZW9Db25pY0VxdWlkaXN0YW50OigpPT5NTHQsZ2VvQ29uaWNFcXVpZGlzdGFudFJhdzooKT0+bVEsZ2VvQ29udGFpbnM6KCk9Pk85dCxnZW9EaXN0YW5jZTooKT0+QnksZ2VvRXF1aXJlY3Rhbmd1bGFyOigpPT5TTHQsZ2VvRXF1aXJlY3Rhbmd1bGFyUmF3OigpPT5HeSxnZW9Hbm9tb25pYzooKT0+VEx0LGdlb0dub21vbmljUmF3OigpPT5IRCxnZW9HcmF0aWN1bGU6KCk9PndELGdlb0dyYXRpY3VsZTEwOigpPT5IOXQsZ2VvSWRlbnRpdHk6KCk9PkFMdCxnZW9JbnRlcnBvbGF0ZTooKT0+VTl0LGdlb0xlbmd0aDooKT0+eEQsZ2VvTWVyY2F0b3I6KCk9PnhMdCxnZW9NZXJjYXRvclJhdzooKT0+cXksZ2VvTmF0dXJhbEVhcnRoMTooKT0+SUx0LGdlb05hdHVyYWxFYXJ0aDFSYXc6KCk9PlVELGdlb09ydGhvZ3JhcGhpYzooKT0+a0x0LGdlb09ydGhvZ3JhcGhpY1JhdzooKT0+cUQsZ2VvUGF0aDooKT0+c0x0LGdlb1Byb2plY3Rpb246KCk9PmVvLGdlb1Byb2plY3Rpb25NdXRhdG9yOigpPT5XVCxnZW9Sb3RhdGlvbjooKT0+c0QsZ2VvU3RlcmVvZ3JhcGhpYzooKT0+Tkx0LGdlb1N0ZXJlb2dyYXBoaWNSYXc6KCk9PkdELGdlb1N0cmVhbTooKT0+TW8sZ2VvVHJhbnNmb3JtOigpPT5jTHQsZ2VvVHJhbnN2ZXJzZU1lcmNhdG9yOigpPT5PTHQsZ2VvVHJhbnN2ZXJzZU1lcmNhdG9yUmF3OigpPT5XRCxoY2w6KCk9Pm9aLGhpZXJhcmNoeTooKT0+WVQsaGlzdG9ncmFtOigpPT5tTXQsaHNsOigpPT5KSyxodG1sOigpPT50TnQsaW50ZXJwb2xhdGU6KCk9PloyLGludGVycG9sYXRlQXJyYXk6KCk9PmhPLGludGVycG9sYXRlQmFzaXM6KCk9PmxPLGludGVycG9sYXRlQmFzaXNDbG9zZWQ6KCk9PnVPLGludGVycG9sYXRlQ29vbDooKT0+eDd0LGludGVycG9sYXRlQ3ViZWhlbGl4OigpPT5GOHQsaW50ZXJwb2xhdGVDdWJlaGVsaXhEZWZhdWx0OigpPT5fN3QsaW50ZXJwb2xhdGVDdWJlaGVsaXhMb25nOigpPT5COHQsaW50ZXJwb2xhdGVEYXRlOigpPT5wTyxpbnRlcnBvbGF0ZUhjbDooKT0+Tjh0LGludGVycG9sYXRlSGNsTG9uZzooKT0+RDh0LGludGVycG9sYXRlSHNsOigpPT5QOHQsaW50ZXJwb2xhdGVIc2xMb25nOigpPT5JOHQsaW50ZXJwb2xhdGVJbmZlcm5vOigpPT5FN3QsaW50ZXJwb2xhdGVMYWI6KCk9PktRLGludGVycG9sYXRlTWFnbWE6KCk9Pk03dCxpbnRlcnBvbGF0ZU51bWJlcjooKT0+QXMsaW50ZXJwb2xhdGVPYmplY3Q6KCk9PmRPLGludGVycG9sYXRlUGxhc21hOigpPT5UN3QsaW50ZXJwb2xhdGVSYWluYm93OigpPT5iN3QsaW50ZXJwb2xhdGVSZ2I6KCk9PnRDLGludGVycG9sYXRlUmdiQmFzaXM6KCk9PnU4dCxpbnRlcnBvbGF0ZVJnYkJhc2lzQ2xvc2VkOigpPT5oOHQsaW50ZXJwb2xhdGVSb3VuZDooKT0+Zjh0LGludGVycG9sYXRlU3RyaW5nOigpPT5tTyxpbnRlcnBvbGF0ZVRyYW5zZm9ybUNzczooKT0+Yjh0LGludGVycG9sYXRlVHJhbnNmb3JtU3ZnOigpPT53OHQsaW50ZXJwb2xhdGVWaXJpZGlzOigpPT5TN3QsaW50ZXJwb2xhdGVXYXJtOigpPT52N3QsaW50ZXJwb2xhdGVab29tOigpPT5UOHQsaW50ZXJydXB0OigpPT5weixpbnRlcnZhbDooKT0+eFZ0LGlzb0Zvcm1hdDooKT0+aFZ0LGlzb1BhcnNlOigpPT5mVnQsanNvbjooKT0+ck50LGtleXM6KCk9PnJBdCxsYWI6KCk9PnNOLGxpbmU6KCk9PlJ3LGxpbmVSYWRpYWw6KCk9PkxldCxsaW5rSG9yaXpvbnRhbDooKT0+d0Z0LGxpbmtSYWRpYWw6KCk9Pk1GdCxsaW5rVmVydGljYWw6KCk9PlNGdCxsb2NhbDooKT0+ZDcsbWFwOigpPT5ieSxtYXRjaGVyOigpPT5jNyxtYXg6KCk9PmJNdCxtZWFuOigpPT5TTXQsbWVkaWFuOigpPT5FTXQsbWVyZ2U6KCk9PkNNdCxtaW46KCk9PmRSLG1vdXNlOigpPT5KenQsbmFtZXNwYWNlOigpPT5NdyxuYW1lc3BhY2VzOigpPT5NQyxuZXN0OigpPT5aQ3Qsbm93OigpPT5ZdyxwYWNrOigpPT5ia3QscGFja0VuY2xvc2U6KCk9PlhELHBhY2tTaWJsaW5nczooKT0+X2t0LHBhaXJzOigpPT5uTXQscGFydGl0aW9uOigpPT5Ta3QscGF0aDooKT0+Vzh0LHBlcm11dGU6KCk9PlBNdCxwaWU6KCk9PnlGdCxwb2ludFJhZGlhbDooKT0+ZjEscG9seWdvbkFyZWE6KCk9Plg4dCxwb2x5Z29uQ2VudHJvaWQ6KCk9Pks4dCxwb2x5Z29uQ29udGFpbnM6KCk9Pm5SdCxwb2x5Z29uSHVsbDooKT0+ZVJ0LHBvbHlnb25MZW5ndGg6KCk9Pm9SdCxwcmVjaXNpb25GaXhlZDooKT0+U0l0LHByZWNpc2lvblByZWZpeDooKT0+RUl0LHByZWNpc2lvblJvdW5kOigpPT5DSXQscXVhZHRyZWU6KCk9PnZPLHF1YW50aWxlOigpPT5keSxxdWFudGl6ZTooKT0+Vjh0LHF1ZXVlOigpPT5iTyxyYWRpYWxBcmVhOigpPT5SZXQscmFkaWFsTGluZTooKT0+TGV0LHJhbmRvbUJhdGVzOigpPT4kUnQscmFuZG9tRXhwb25lbnRpYWw6KCk9PlpSdCxyYW5kb21JcndpbkhhbGw6KCk9PlNPLHJhbmRvbUxvZ05vcm1hbDooKT0+alJ0LHJhbmRvbU5vcm1hbDooKT0+d08scmFuZG9tVW5pZm9ybTooKT0+V1J0LHJhbmdlOigpPT51UixyZXF1ZXN0OigpPT5KMixyZ2I6KCk9PlpLLHJpYmJvbjooKT0+V0N0LHNjYWxlQmFuZDooKT0+bEMsc2NhbGVJZGVudGl0eTooKT0+VU8sc2NhbGVJbXBsaWNpdDooKT0+TE8sc2NhbGVMaW5lYXI6KCk9PlZPLHNjYWxlTG9nOigpPT5HTyxzY2FsZU9yZGluYWw6KCk9Pm53LHNjYWxlUG9pbnQ6KCk9PlZOdCxzY2FsZVBvdzooKT0+Z0Msc2NhbGVRdWFudGlsZTooKT0+V08sc2NhbGVRdWFudGl6ZTooKT0+WU8sc2NhbGVTZXF1ZW50aWFsOigpPT5uNyxzY2FsZVNxcnQ6KCk9PmVPdCxzY2FsZVRocmVzaG9sZDooKT0+ak8sc2NhbGVUaW1lOigpPT5hN3Qsc2NhbGVVdGM6KCk9PnM3dCxzY2FuOigpPT5MTXQsc2NoZW1lQ2F0ZWdvcnkxMDooKT0+Yzd0LHNjaGVtZUNhdGVnb3J5MjA6KCk9Pm03dCxzY2hlbWVDYXRlZ29yeTIwYjooKT0+aDd0LHNjaGVtZUNhdGVnb3J5MjBjOigpPT5wN3Qsc2VsZWN0OigpPT5wNyxzZWxlY3RBbGw6KCk9PnRGdCxzZWxlY3Rpb246KCk9Plh6dCxzZWxlY3RvcjooKT0+RXcsc2VsZWN0b3JBbGw6KCk9Pmw3LHNldDooKT0+dEF0LHNodWZmbGU6KCk9PlJNdCxzdGFjazooKT0+X0J0LHN0YWNrT2Zmc2V0RGl2ZXJnaW5nOigpPT5iQnQsc3RhY2tPZmZzZXRFeHBhbmQ6KCk9PnZCdCxzdGFja09mZnNldE5vbmU6KCk9PlJ1LHN0YWNrT2Zmc2V0U2lsaG91ZXR0ZTooKT0+U0J0LHN0YWNrT2Zmc2V0V2lnZ2xlOigpPT5FQnQsc3RhY2tPcmRlckFzY2VuZGluZzooKT0+Tjcsc3RhY2tPcmRlckRlc2NlbmRpbmc6KCk9PkNCdCxzdGFja09yZGVySW5zaWRlT3V0OigpPT5QQnQsc3RhY2tPcmRlck5vbmU6KCk9Pk51LHN0YWNrT3JkZXJSZXZlcnNlOigpPT5MQnQsc3RyYXRpZnk6KCk9PkNrdCxzdHlsZTooKT0+eWV0LHN1bTooKT0+RE10LHN5bWJvbDooKT0+UEZ0LHN5bWJvbENpcmNsZTooKT0+a0Msc3ltYm9sQ3Jvc3M6KCk9Pnc3LHN5bWJvbERpYW1vbmQ6KCk9PlM3LHN5bWJvbFNxdWFyZTooKT0+RTcsc3ltYm9sU3RhcjooKT0+TTcsc3ltYm9sVHJpYW5nbGU6KCk9PlQ3LHN5bWJvbFd5ZTooKT0+Qzcsc3ltYm9sczooKT0+QUZ0LHRleHQ6KCk9PmlOdCx0aHJlc2hvbGRGcmVlZG1hbkRpYWNvbmlzOigpPT5fTXQsdGhyZXNob2xkU2NvdHQ6KCk9PnZNdCx0aHJlc2hvbGRTdHVyZ2VzOigpPT5mUix0aWNrSW5jcmVtZW50OigpPT5SJCx0aWNrU3RlcDooKT0+aFIsdGlja3M6KCk9PmRNdCx0aW1lRGF5OigpPT5ZQnQsdGltZURheXM6KCk9PmpCdCx0aW1lRm9ybWF0OigpPT5TcnQsdGltZUZvcm1hdERlZmF1bHRMb2NhbGU6KCk9Pmo3LHRpbWVGb3JtYXRMb2NhbGU6KCk9PldDLHRpbWVGcmlkYXk6KCk9PmFydCx0aW1lRnJpZGF5czooKT0+UUJ0LHRpbWVIb3VyOigpPT5VQnQsdGltZUhvdXJzOigpPT5xQnQsdGltZUludGVydmFsOigpPT5Tcix0aW1lTWlsbGlzZWNvbmQ6KCk9PkpldCx0aW1lTWlsbGlzZWNvbmRzOigpPT5RZXQsdGltZU1pbnV0ZTooKT0+RkJ0LHRpbWVNaW51dGVzOigpPT5CQnQsdGltZU1vbmRheTooKT0+cnJ0LHRpbWVNb25kYXlzOigpPT4kQnQsdGltZU1vbnRoOigpPT5uSHQsdGltZU1vbnRoczooKT0+aUh0LHRpbWVQYXJzZTooKT0+TXJ0LHRpbWVTYXR1cmRheTooKT0+c3J0LHRpbWVTYXR1cmRheXM6KCk9PnRIdCx0aW1lU2Vjb25kOigpPT50cnQsdGltZVNlY29uZHM6KCk9PmVydCx0aW1lU3VuZGF5OigpPT5CNyx0aW1lU3VuZGF5czooKT0+bHJ0LHRpbWVUaHVyc2RheTooKT0+b3J0LHRpbWVUaHVyc2RheXM6KCk9PkpCdCx0aW1lVHVlc2RheTooKT0+bnJ0LHRpbWVUdWVzZGF5czooKT0+S0J0LHRpbWVXZWRuZXNkYXk6KCk9PmlydCx0aW1lV2VkbmVzZGF5czooKT0+WkJ0LHRpbWVXZWVrOigpPT5CNyx0aW1lV2Vla3M6KCk9PmxydCx0aW1lWWVhcjooKT0+YUh0LHRpbWVZZWFyczooKT0+c0h0LHRpbWVvdXQ6KCk9PnlWdCx0aW1lcjooKT0+QXJ0LHRpbWVyRmx1c2g6KCk9PlBydCx0b3VjaDooKT0+ckZ0LHRvdWNoZXM6KCk9PmlGdCx0cmFuc2l0aW9uOigpPT5Feix0cmFuc3Bvc2U6KCk9Pm1SLHRyZWU6KCk9PlBrdCx0cmVlbWFwOigpPT5Ma3QsdHJlZW1hcEJpbmFyeTooKT0+Umt0LHRyZWVtYXBEaWNlOigpPT5sZix0cmVlbWFwUmVzcXVhcmlmeTooKT0+emt0LHRyZWVtYXBTbGljZTooKT0+d2csdHJlZW1hcFNsaWNlRGljZTooKT0+RGt0LHRyZWVtYXBTcXVhcmlmeTooKT0+UUQsdHN2OigpPT51TnQsdHN2Rm9ybWF0OigpPT55UHQsdHN2Rm9ybWF0Um93czooKT0+dlB0LHRzdlBhcnNlOigpPT5nUHQsdHN2UGFyc2VSb3dzOigpPT5fUHQsdXRjRGF5OigpPT55SHQsdXRjRGF5czooKT0+dkh0LHV0Y0Zvcm1hdDooKT0+WUMsdXRjRnJpZGF5OigpPT5kcnQsdXRjRnJpZGF5czooKT0+RUh0LHV0Y0hvdXI6KCk9PmRIdCx1dGNIb3VyczooKT0+bUh0LHV0Y01pbGxpc2Vjb25kOigpPT5KZXQsdXRjTWlsbGlzZWNvbmRzOigpPT5RZXQsdXRjTWludXRlOigpPT51SHQsdXRjTWludXRlczooKT0+aEh0LHV0Y01vbmRheTooKT0+dXJ0LHV0Y01vbmRheXM6KCk9PmJIdCx1dGNNb250aDooKT0+UEh0LHV0Y01vbnRoczooKT0+SUh0LHV0Y1BhcnNlOigpPT5qQyx1dGNTYXR1cmRheTooKT0+bXJ0LHV0Y1NhdHVyZGF5czooKT0+VEh0LHV0Y1NlY29uZDooKT0+dHJ0LHV0Y1NlY29uZHM6KCk9PmVydCx1dGNTdW5kYXk6KCk9Pkg3LHV0Y1N1bmRheXM6KCk9PmdydCx1dGNUaHVyc2RheTooKT0+cHJ0LHV0Y1RodXJzZGF5czooKT0+TUh0LHV0Y1R1ZXNkYXk6KCk9PmhydCx1dGNUdWVzZGF5czooKT0+d0h0LHV0Y1dlZG5lc2RheTooKT0+ZnJ0LHV0Y1dlZG5lc2RheXM6KCk9PlNIdCx1dGNXZWVrOigpPT5INyx1dGNXZWVrczooKT0+Z3J0LHV0Y1llYXI6KCk9PmtIdCx1dGNZZWFyczooKT0+Ukh0LHZhbHVlczooKT0+aUF0LHZhcmlhbmNlOigpPT5zUix2ZXJzaW9uOigpPT5RM3Qsdm9yb25vaTooKT0+SUd0LHdpbmRvdzooKT0+Q3cseG1sOigpPT5hTnQsemlwOigpPT56TXQsem9vbTooKT0+Rmp0LHpvb21JZGVudGl0eTooKT0+aFMsem9vbVRyYW5zZm9ybTooKT0+b0Z9KTt2YXIgRXI9TSgoKT0+e3RNdCgpO0JNdCgpOyRNdCgpO21DdCgpO2pDdCgpO2xBdCgpO01BdCgpO1BBdCgpO2xQdCgpO2JQdCgpO1FQdCgpO2hJdCgpO1BJdCgpO0ZMdCgpO0JrdCgpO3E4dCgpO2o4dCgpO3NSdCgpO0JSdCgpO0dSdCgpO1FSdCgpO2ZOdCgpO1A3dCgpO2FGdCgpO1JCdCgpO0RIdCgpO2RWdCgpO3dWdCgpO2ZHdCgpO2tHdCgpO0hqdCgpfSk7dmFyIEdqdD1IKHFqdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkocWp0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgVmp0PShFcigpLFV0KE1yKSksV2c9Vmp0LFVqdD1WanQ7ZnVuY3Rpb24gSERlKGUsdCl7cmV0dXJuIGUuZWFjaChmdW5jdGlvbigpe3ZhciByPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpLG49V2cuc2VsZWN0KHRoaXMpO2Zvcih2YXIgaSBpbiByKW4uYXR0cihpLHJbaV0pfSl9ZnVuY3Rpb24gVkRlKGUsdCl7Zm9yKHZhciByIGluIHQpZS5hdHRyKHIsdFtyXSk7cmV0dXJuIGV9ZnVuY3Rpb24gVURlKGUpe3JldHVybih0eXBlb2YgZT09ImZ1bmN0aW9uIj9IRGU6VkRlKSh0aGlzLGUpfWZ1bmN0aW9uIHFEZShlLHQscil7cmV0dXJuIGUuZWFjaChmdW5jdGlvbigpe3ZhciBuPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpLGk9V2cuc2VsZWN0KHRoaXMpO2Zvcih2YXIgbyBpbiBuKWkuc3R5bGUobyxuW29dLHIpfSl9ZnVuY3Rpb24gR0RlKGUsdCxyKXtmb3IodmFyIG4gaW4gdCllLnN0eWxlKG4sdFtuXSxyKTtyZXR1cm4gZX1mdW5jdGlvbiBXRGUoZSx0KXtyZXR1cm4odHlwZW9mIGU9PSJmdW5jdGlvbiI/cURlOkdEZSkodGhpcyxlLHQ9PW51bGw/IiI6dCl9ZnVuY3Rpb24gWURlKGUsdCl7cmV0dXJuIGUuZWFjaChmdW5jdGlvbigpe3ZhciByPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpLG49V2cuc2VsZWN0KHRoaXMpO2Zvcih2YXIgaSBpbiByKW4ucHJvcGVydHkoaSxyW2ldKX0pfWZ1bmN0aW9uIGpEZShlLHQpe2Zvcih2YXIgciBpbiB0KWUucHJvcGVydHkocix0W3JdKTtyZXR1cm4gZX1mdW5jdGlvbiBYRGUoZSl7cmV0dXJuKHR5cGVvZiBlPT0iZnVuY3Rpb24iP1lEZTpqRGUpKHRoaXMsZSl9ZnVuY3Rpb24gJERlKGUsdCl7cmV0dXJuIGUuZWFjaChmdW5jdGlvbigpe3ZhciByPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpLG49V2cuc2VsZWN0KHRoaXMpLnRyYW5zaXRpb24oZSk7Zm9yKHZhciBpIGluIHIpbi5hdHRyKGkscltpXSl9KX1mdW5jdGlvbiBLRGUoZSx0KXtmb3IodmFyIHIgaW4gdCllLmF0dHIocix0W3JdKTtyZXR1cm4gZX1mdW5jdGlvbiBaRGUoZSl7cmV0dXJuKHR5cGVvZiBlPT0iZnVuY3Rpb24iPyREZTpLRGUpKHRoaXMsZSl9ZnVuY3Rpb24gSkRlKGUsdCxyKXtyZXR1cm4gZS5lYWNoKGZ1bmN0aW9uKCl7dmFyIG49dC5hcHBseSh0aGlzLGFyZ3VtZW50cyksaT1XZy5zZWxlY3QodGhpcykudHJhbnNpdGlvbihlKTtmb3IodmFyIG8gaW4gbilpLnN0eWxlKG8sbltvXSxyKX0pfWZ1bmN0aW9uIFFEZShlLHQscil7Zm9yKHZhciBuIGluIHQpZS5zdHlsZShuLHRbbl0scik7cmV0dXJuIGV9ZnVuY3Rpb24gdE9lKGUsdCl7cmV0dXJuKHR5cGVvZiBlPT0iZnVuY3Rpb24iP0pEZTpRRGUpKHRoaXMsZSx0PT1udWxsPyIiOnQpfVdnLnNlbGVjdGlvbi5wcm90b3R5cGUuYXR0cnM9VURlO1dnLnNlbGVjdGlvbi5wcm90b3R5cGUuc3R5bGVzPVdEZTtXZy5zZWxlY3Rpb24ucHJvdG90eXBlLnByb3BlcnRpZXM9WERlO1VqdC50cmFuc2l0aW9uLnByb3RvdHlwZS5hdHRycz1aRGU7VWp0LnRyYW5zaXRpb24ucHJvdG90eXBlLnN0eWxlcz10T2V9KTt2YXIgWWc9SChvaXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KG9pdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFdqdD0oRXIoKSxVdChNcikpO2Z1bmN0aW9uIGVPZShlKXtpZihlLmF0dHJzPT1udWxsKWlmKGUubm9kZXM9PW51bGwpe3ZhciB0PVtdO3JldHVybiBlLmVhY2goZnVuY3Rpb24oKXt0LnB1c2godGhpcyl9KSxXanQuc2VsZWN0QWxsKHQpfWVsc2UgcmV0dXJuIFdqdC5zZWxlY3RBbGwoZS5ub2RlcygpKTtlbHNlIHJldHVybiBlfW9pdC5jb2VyY2VFeHRlcm5hbEQzPWVPZX0pO3ZhciBJZj1IKGFpdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoYWl0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTtmdW5jdGlvbiByT2UoZSl7cmV0dXJuIGUucmVkdWNlKGZ1bmN0aW9uKHQscil7cmV0dXJuIHRbcl09cix0fSx7fSl9YWl0Lm1ha2VFbnVtPXJPZX0pO3ZhciBqanQ9SChzRj0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoc0YsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBkcj0oSV8oKSxVdChOMXQpKSxuT2U9WWcoKSxpT2U9SWYoKSxZanQ9e2xpbmVhcjpkci5lYXNlTGluZWFyLHF1YWQ6ZHIuZWFzZVF1YWQscXVhZEluOmRyLmVhc2VRdWFkSW4scXVhZE91dDpkci5lYXNlUXVhZE91dCxxdWFkSW5PdXQ6ZHIuZWFzZVF1YWRJbk91dCxjdWJpYzpkci5lYXNlQ3ViaWMsY3ViaWNJbjpkci5lYXNlQ3ViaWNJbixjdWJpY091dDpkci5lYXNlQ3ViaWNPdXQsY3ViaWNJbk91dDpkci5lYXNlQ3ViaWNJbk91dCxwb2x5OmRyLmVhc2VQb2x5LHBvbHlJbjpkci5lYXNlUG9seUluLHBvbHlPdXQ6ZHIuZWFzZVBvbHlPdXQscG9seUluT3V0OmRyLmVhc2VQb2x5SW5PdXQsc2luOmRyLmVhc2VTaW4sc2luSW46ZHIuZWFzZVNpbkluLHNpbk91dDpkci5lYXNlU2luT3V0LHNpbkluT3V0OmRyLmVhc2VTaW5Jbk91dCxleHA6ZHIuZWFzZUV4cCxleHBJbjpkci5lYXNlRXhwSW4sZXhwT3V0OmRyLmVhc2VFeHBPdXQsZXhwSW5PdXQ6ZHIuZWFzZUV4cEluT3V0LGNpcmNsZTpkci5lYXNlQ2lyY2xlLGNpcmNsZUluOmRyLmVhc2VDaXJjbGVJbixjaXJjbGVPdXQ6ZHIuZWFzZUNpcmNsZU91dCxjaXJjbGVJbk91dDpkci5lYXNlQ2lyY2xlSW5PdXQsYm91bmNlOmRyLmVhc2VCb3VuY2UsYm91bmNlSW46ZHIuZWFzZUJvdW5jZUluLGJvdW5jZU91dDpkci5lYXNlQm91bmNlT3V0LGJvdW5jZUluT3V0OmRyLmVhc2VCb3VuY2VJbk91dCxiYWNrOmRyLmVhc2VCYWNrLGJhY2tJbjpkci5lYXNlQmFja0luLGJhY2tPdXQ6ZHIuZWFzZUJhY2tPdXQsYmFja0luT3V0OmRyLmVhc2VCYWNrSW5PdXQsZWxhc3RpYzpkci5lYXNlRWxhc3RpYyxlbGFzdGljSW46ZHIuZWFzZUVsYXN0aWNJbixlbGFzdGljT3V0OmRyLmVhc2VFbGFzdGljT3V0LGVsYXN0aWNJbk91dDpkci5lYXNlRWxhc3RpY0luT3V0fTtzRi5FYXNlTmFtZT1pT2UubWFrZUVudW0oWyJsaW5lYXIiLCJxdWFkIiwicXVhZEluIiwicXVhZE91dCIsInF1YWRJbk91dCIsImN1YmljIiwiY3ViaWNJbiIsImN1YmljT3V0IiwiY3ViaWNJbk91dCIsInBvbHkiLCJwb2x5SW4iLCJwb2x5T3V0IiwicG9seUluT3V0Iiwic2luIiwic2luSW4iLCJzaW5PdXQiLCJzaW5Jbk91dCIsImV4cCIsImV4cEluIiwiZXhwT3V0IiwiZXhwSW5PdXQiLCJjaXJjbGUiLCJjaXJjbGVJbiIsImNpcmNsZU91dCIsImNpcmNsZUluT3V0IiwiYm91bmNlIiwiYm91bmNlSW4iLCJib3VuY2VPdXQiLCJib3VuY2VJbk91dCIsImJhY2siLCJiYWNrSW4iLCJiYWNrT3V0IiwiYmFja0luT3V0IiwiZWxhc3RpYyIsImVsYXN0aWNJbiIsImVsYXN0aWNPdXQiLCJlbGFzdGljSW5PdXQiXSk7dmFyIG9PZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUoKXt0aGlzLl9zdGFydERlbGF5PWUuX0RFRkFVTFRfU1RBUlRfREVMQVlfTUlMTElTRUNPTkRTLHRoaXMuX3N0ZXBEdXJhdGlvbj1lLl9ERUZBVUxUX1NURVBfRFVSQVRJT05fTUlMTElTRUNPTkRTLHRoaXMuX3N0ZXBEZWxheT1lLl9ERUZBVUxUX0lURVJBVElWRV9ERUxBWV9NSUxMSVNFQ09ORFMsdGhpcy5fbWF4VG90YWxEdXJhdGlvbj1lLl9ERUZBVUxUX01BWF9UT1RBTF9EVVJBVElPTl9NSUxMSVNFQ09ORFMsdGhpcy5fZWFzaW5nTW9kZT1lLl9ERUZBVUxUX0VBU0lOR19NT0RFfXJldHVybiBlLnByb3RvdHlwZS50b3RhbFRpbWU9ZnVuY3Rpb24odCl7dmFyIHI9dGhpcy5fZ2V0QWRqdXN0ZWRJdGVyYXRpdmVEZWxheSh0KTtyZXR1cm4gdGhpcy5zdGFydERlbGF5KCkrcipNYXRoLm1heCh0LTEsMCkrdGhpcy5zdGVwRHVyYXRpb24oKX0sZS5wcm90b3R5cGUuYW5pbWF0ZT1mdW5jdGlvbih0LHIpe3ZhciBuPXRoaXM7dD1uT2UuY29lcmNlRXh0ZXJuYWxEMyh0KTt2YXIgaT10LnNpemUoKSxvPXRoaXMuX2dldEFkanVzdGVkSXRlcmF0aXZlRGVsYXkoaSk7cmV0dXJuIHQudHJhbnNpdGlvbigpLmVhc2UodGhpcy5fZ2V0RWFzZUZhY3RvcnkoKSkuZHVyYXRpb24odGhpcy5zdGVwRHVyYXRpb24oKSkuZGVsYXkoZnVuY3Rpb24oYSxzKXtyZXR1cm4gbi5zdGFydERlbGF5KCkrbypzfSkuYXR0cnMocil9LGUucHJvdG90eXBlLnN0YXJ0RGVsYXk9ZnVuY3Rpb24odCl7cmV0dXJuIHQ9PW51bGw/dGhpcy5fc3RhcnREZWxheToodGhpcy5fc3RhcnREZWxheT10LHRoaXMpfSxlLnByb3RvdHlwZS5zdGVwRHVyYXRpb249ZnVuY3Rpb24odCl7cmV0dXJuIHQ9PW51bGw/TWF0aC5taW4odGhpcy5fc3RlcER1cmF0aW9uLHRoaXMuX21heFRvdGFsRHVyYXRpb24pOih0aGlzLl9zdGVwRHVyYXRpb249dCx0aGlzKX0sZS5wcm90b3R5cGUuc3RlcERlbGF5PWZ1bmN0aW9uKHQpe3JldHVybiB0PT1udWxsP3RoaXMuX3N0ZXBEZWxheToodGhpcy5fc3RlcERlbGF5PXQsdGhpcyl9LGUucHJvdG90eXBlLm1heFRvdGFsRHVyYXRpb249ZnVuY3Rpb24odCl7cmV0dXJuIHQ9PW51bGw/dGhpcy5fbWF4VG90YWxEdXJhdGlvbjoodGhpcy5fbWF4VG90YWxEdXJhdGlvbj10LHRoaXMpfSxlLnByb3RvdHlwZS5lYXNpbmdNb2RlPWZ1bmN0aW9uKHQpe3JldHVybiB0PT1udWxsP3RoaXMuX2Vhc2luZ01vZGU6KHRoaXMuX2Vhc2luZ01vZGU9dCx0aGlzKX0sZS5wcm90b3R5cGUuX2dldEVhc2VGYWN0b3J5PWZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5lYXNpbmdNb2RlKCk7aWYodHlwZW9mIHQ9PSJzdHJpbmciKXt2YXIgcj1ZanRbdF07cmV0dXJuIHI9PW51bGw/WWp0LmxpbmVhcjpyfWVsc2UgcmV0dXJuIHR9LGUucHJvdG90eXBlLl9nZXRBZGp1c3RlZEl0ZXJhdGl2ZURlbGF5PWZ1bmN0aW9uKHQpe3ZhciByPXRoaXMubWF4VG90YWxEdXJhdGlvbigpLXRoaXMuc3RlcER1cmF0aW9uKCk7cj1NYXRoLm1heChyLDApO3ZhciBuPXIvTWF0aC5tYXgodC0xLDEpO3JldHVybiBNYXRoLm1pbih0aGlzLnN0ZXBEZWxheSgpLG4pfSxlLl9ERUZBVUxUX1NUQVJUX0RFTEFZX01JTExJU0VDT05EUz0wLGUuX0RFRkFVTFRfU1RFUF9EVVJBVElPTl9NSUxMSVNFQ09ORFM9MzAwLGUuX0RFRkFVTFRfSVRFUkFUSVZFX0RFTEFZX01JTExJU0VDT05EUz0xNSxlLl9ERUZBVUxUX01BWF9UT1RBTF9EVVJBVElPTl9NSUxMSVNFQ09ORFM9MS8wLGUuX0RFRkFVTFRfRUFTSU5HX01PREU9ImV4cE91dCIsZX0oKTtzRi5FYXNpbmc9b09lfSk7dmFyIFhqdD1IKHNpdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoc2l0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgYU9lPVlnKCksc09lPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe31yZXR1cm4gZS5wcm90b3R5cGUudG90YWxUaW1lPWZ1bmN0aW9uKHQpe3JldHVybiAwfSxlLnByb3RvdHlwZS5hbmltYXRlPWZ1bmN0aW9uKHQscil7cmV0dXJuIHQ9YU9lLmNvZXJjZUV4dGVybmFsRDModCksdC5hdHRycyhyKX0sZX0oKTtzaXQuTnVsbD1zT2V9KTt2YXIgTGY9SChsRj0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkobEYsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciAkanQ9KGRlKCksVXQocGUpKTskanQuX19leHBvcnRTdGFyKGpqdCgpLGxGKTskanQuX19leHBvcnRTdGFyKFhqdCgpLGxGKX0pO3ZhciBLanQ9SChsaXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGxpdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIGxPZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUodCl7dGhpcy5jYWNoZT17fSx0aGlzLmNvbXB1dGU9dH1yZXR1cm4gZS5wcm90b3R5cGUuZ2V0PWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLmNhY2hlLmhhc093blByb3BlcnR5KHQpfHwodGhpcy5jYWNoZVt0XT10aGlzLmNvbXB1dGUodCkpLHRoaXMuY2FjaGVbdF19LGUucHJvdG90eXBlLmNsZWFyPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuY2FjaGU9e30sdGhpc30sZX0oKTtsaXQuQ2FjaGU9bE9lfSk7dmFyIFpqdD1IKGNpdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoY2l0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgY09lPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe31yZXR1cm4gZS5hcnJheUVxPWZ1bmN0aW9uKHQscil7aWYodD09bnVsbHx8cj09bnVsbClyZXR1cm4gdD09PXI7aWYodC5sZW5ndGghPT1yLmxlbmd0aClyZXR1cm4hMTtmb3IodmFyIG49MDtuPHQubGVuZ3RoO24rKylpZih0W25dIT09cltuXSlyZXR1cm4hMTtyZXR1cm4hMH0sZS5vYmpFcT1mdW5jdGlvbih0LHIpe2lmKHQ9PW51bGx8fHI9PW51bGwpcmV0dXJuIHQ9PT1yO3ZhciBuPU9iamVjdC5rZXlzKHQpLnNvcnQoKSxpPU9iamVjdC5rZXlzKHIpLnNvcnQoKSxvPW4ubWFwKGZ1bmN0aW9uKHMpe3JldHVybiB0W3NdfSksYT1pLm1hcChmdW5jdGlvbihzKXtyZXR1cm4gcltzXX0pO3JldHVybiBlLmFycmF5RXEobixpKSYmZS5hcnJheUVxKG8sYSl9LGUuc3RyaWN0RXE9ZnVuY3Rpb24odCxyKXtyZXR1cm4gdD09PXJ9LGUuZGVmYXVsdHM9ZnVuY3Rpb24odCl7Zm9yKHZhciByPVtdLG49MTtuPGFyZ3VtZW50cy5sZW5ndGg7bisrKXJbbi0xXT1hcmd1bWVudHNbbl07aWYodD09bnVsbCl0aHJvdyBuZXcgVHlwZUVycm9yKCJDYW5ub3QgY29udmVydCB1bmRlZmluZWQgb3IgbnVsbCB0byBvYmplY3QiKTt2YXIgaT1PYmplY3QodCk7cmV0dXJuIHIuZm9yRWFjaChmdW5jdGlvbihvKXtpZihvIT1udWxsKWZvcih2YXIgYSBpbiBvKU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChvLGEpJiYoaVthXT1vW2FdKX0pLGl9LGV9KCk7Y2l0Lk1ldGhvZHM9Y09lfSk7dmFyIEpqdD1IKHVpdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkodWl0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgdU9lPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe31yZXR1cm4gZS5jb21iaW5lV2hpdGVzcGFjZT1mdW5jdGlvbih0KXtyZXR1cm4gdC5yZXBsYWNlKC9bIFx0XSsvZywiICIpfSxlLmlzTm90RW1wdHlTdHJpbmc9ZnVuY3Rpb24odCl7cmV0dXJuIHQmJnQudHJpbSgpIT09IiJ9LGUudHJpbVN0YXJ0PWZ1bmN0aW9uKHQscil7aWYoIXQpcmV0dXJuIHQ7dmFyIG49dC5zcGxpdCgiIiksaT1yP2Z1bmN0aW9uKG8pe3JldHVybiBvLnNwbGl0KHIpLnNvbWUoZS5pc05vdEVtcHR5U3RyaW5nKX06ZS5pc05vdEVtcHR5U3RyaW5nO3JldHVybiBuLnJlZHVjZShmdW5jdGlvbihvLGEpe3JldHVybiBpKG8rYSk/bythOm99LCIiKX0sZS50cmltRW5kPWZ1bmN0aW9uKHQscil7aWYoIXQpcmV0dXJuIHQ7dmFyIG49dC5zcGxpdCgiIik7cmV0dXJuIG4ucmV2ZXJzZSgpLG49ZS50cmltU3RhcnQobi5qb2luKCIiKSxyKS5zcGxpdCgiIiksbi5yZXZlcnNlKCksbi5qb2luKCIiKX0sZX0oKTt1aXQuU3RyaW5nTWV0aG9kcz11T2V9KTt2YXIgUWp0PUgoaGl0PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShoaXQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBoT2U9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKCl7dGhpcy5Xb3JkRGl2aWRlclJlZ0V4cD1uZXcgUmVnRXhwKCJcXFciKSx0aGlzLldoaXRlc3BhY2VSZWdFeHA9bmV3IFJlZ0V4cCgiXFxzIil9cmV0dXJuIGUucHJvdG90eXBlLnRva2VuaXplPWZ1bmN0aW9uKHQpe3ZhciByPXRoaXM7cmV0dXJuIHQuc3BsaXQoIiIpLnJlZHVjZShmdW5jdGlvbihuLGkpe3JldHVybiBuLnNsaWNlKDAsLTEpLmNvbmNhdChyLnNob3VsZENyZWF0ZU5ld1Rva2VuKG5bbi5sZW5ndGgtMV0saSkpfSxbIiJdKX0sZS5wcm90b3R5cGUuc2hvdWxkQ3JlYXRlTmV3VG9rZW49ZnVuY3Rpb24odCxyKXtpZighdClyZXR1cm5bcl07dmFyIG49dFt0Lmxlbmd0aC0xXTtyZXR1cm4gdGhpcy5XaGl0ZXNwYWNlUmVnRXhwLnRlc3QobikmJnRoaXMuV2hpdGVzcGFjZVJlZ0V4cC50ZXN0KHIpP1t0K3JdOnRoaXMuV2hpdGVzcGFjZVJlZ0V4cC50ZXN0KG4pfHx0aGlzLldoaXRlc3BhY2VSZWdFeHAudGVzdChyKT9bdCxyXTp0aGlzLldvcmREaXZpZGVyUmVnRXhwLnRlc3Qobik/bj09PXI/W3Qrcl06W3Qscl06W3Qrcl19LGV9KCk7aGl0LlRva2VuaXplcj1oT2V9KTt2YXIgcFM9SChjRj0+eyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiB1RihlKXtmb3IodmFyIHQgaW4gZSljRi5oYXNPd25Qcm9wZXJ0eSh0KXx8KGNGW3RdPWVbdF0pfU9iamVjdC5kZWZpbmVQcm9wZXJ0eShjRiwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dUYoS2p0KCkpO3VGKFpqdCgpKTt1RihKanQoKSk7dUYoUWp0KCkpfSk7dmFyIGVYdD1IKGZpdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoZml0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgdFh0PXBTKCksZk9lPXt0ZXh0Um90YXRpb246MCx0ZXh0U2hlYXI6MCx4QWxpZ246ImxlZnQiLHlBbGlnbjoidG9wIn0saEY9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKHQscixuKXt0aGlzLl9tZWFzdXJlcj10LHRoaXMuX3BlbkZhY3Rvcnk9cix0aGlzLl93cmFwcGVyPW59cmV0dXJuIGUucHJvdG90eXBlLm1lYXN1cmVyPWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLl9tZWFzdXJlcj10LHRoaXN9LGUucHJvdG90eXBlLndyYXBwZXI9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX3dyYXBwZXI9dCx0aGlzfSxlLnByb3RvdHlwZS5wZW5GYWN0b3J5PWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLl9wZW5GYWN0b3J5PXQsdGhpc30sZS5wcm90b3R5cGUud3JpdGU9ZnVuY3Rpb24odCxyLG4saSxvKXtpZihpPT09dm9pZCAwJiYoaT17fSksaT10WHQuTWV0aG9kcy5kZWZhdWx0cyh7fSxmT2UsaSksZS5TdXBwb3J0ZWRSb3RhdGlvbi5pbmRleE9mKGkudGV4dFJvdGF0aW9uKT09PS0xKXRocm93IG5ldyBFcnJvcigidW5zdXBwb3J0ZWQgcm90YXRpb24gLSAiK2kudGV4dFJvdGF0aW9uKyIuIFN1cHBvcnRlZCByb3RhdGlvbnMgYXJlICIrZS5TdXBwb3J0ZWRSb3RhdGlvbi5qb2luKCIsICIpKTtpZihpLnRleHRTaGVhciE9bnVsbCYmaS50ZXh0U2hlYXI8LTgwfHxpLnRleHRTaGVhcj44MCl0aHJvdyBuZXcgRXJyb3IoInVuc3VwcG9ydGVkIHNoZWFyIGFuZ2xlIC0gIitpLnRleHRTaGVhcisiLiBNdXN0IGJlIGJldHdlZW4gLTgwIGFuZCA4MCIpO3ZhciBhPU1hdGguYWJzKE1hdGguYWJzKGkudGV4dFJvdGF0aW9uKS05MCk+NDUscz1hP3I6bixsPWE/bjpyLGM9aS50ZXh0U2hlYXIsdT1jKk1hdGguUEkvMTgwLGg9dGhpcy5fbWVhc3VyZXIubWVhc3VyZSgpLmhlaWdodCxmPWgqTWF0aC50YW4odSkscD1zL01hdGguY29zKHUpLU1hdGguYWJzKGYpLGQ9bCpNYXRoLmNvcyh1KSxnPXRYdC5TdHJpbmdNZXRob2RzLmNvbWJpbmVXaGl0ZXNwYWNlKHQpLF89dGhpcy5fd3JhcHBlcj90aGlzLl93cmFwcGVyLndyYXAoZyx0aGlzLl9tZWFzdXJlcixwLGQpLndyYXBwZWRUZXh0OmcseT1fLnNwbGl0KGAKYCkseD1lLlhPZmZzZXRGYWN0b3JbaS54QWxpZ25dKnAqTWF0aC5zaW4odSksYj1lLllPZmZzZXRGYWN0b3JbaS55QWxpZ25dKihkLXkubGVuZ3RoKmgpLFM9eC1iLEM9WzAsMF0sUD1pLnRleHRSb3RhdGlvbitjO3N3aXRjaChpLnRleHRSb3RhdGlvbil7Y2FzZSA5MDpDPVtyK1MsMF07YnJlYWs7Y2FzZS05MDpDPVstUyxuXTticmVhaztjYXNlIDE4MDpDPVtyLG4rU107YnJlYWs7ZGVmYXVsdDpDPVswLC1TXTticmVha312YXIgaz10aGlzLl9wZW5GYWN0b3J5LmNyZWF0ZVBlbih0LHt0cmFuc2xhdGU6Qyxyb3RhdGU6UH0sbyk7dGhpcy53cml0ZUxpbmVzKHksayxwLGgsZixpLnhBbGlnbiksay5kZXN0cm95IT1udWxsJiZrLmRlc3Ryb3koKX0sZS5wcm90b3R5cGUud3JpdGVMaW5lcz1mdW5jdGlvbih0LHIsbixpLG8sYSl7dC5mb3JFYWNoKGZ1bmN0aW9uKHMsbCl7dmFyIGM9bz4wPyhsKzEpKm86bCpvO3Iud3JpdGUocyxuLGEsYywobCsxKSppKX0pfSxlfSgpO2hGLlhPZmZzZXRGYWN0b3I9e2NlbnRlcjouNSxsZWZ0OjAscmlnaHQ6MX07aEYuWU9mZnNldEZhY3Rvcj17Ym90dG9tOjEsY2VudGVyOi41LHRvcDowfTtoRi5TdXBwb3J0ZWRSb3RhdGlvbj1bLTkwLDAsMTgwLDkwXTtmaXQuV3JpdGVyPWhGfSk7dmFyIHpBPUgoZkY9PnsidXNlIHN0cmljdCI7ZnVuY3Rpb24gcE9lKGUpe2Zvcih2YXIgdCBpbiBlKWZGLmhhc093blByb3BlcnR5KHQpfHwoZkZbdF09ZVt0XSl9T2JqZWN0LmRlZmluZVByb3BlcnR5KGZGLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTtwT2UoZVh0KCkpfSk7dmFyIHBpdD1IKHBGPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShwRiwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIEZBPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe31yZXR1cm4gZS5hcHBlbmQ9ZnVuY3Rpb24odCxyKXtmb3IodmFyIG49W10saT0yO2k8YXJndW1lbnRzLmxlbmd0aDtpKyspbltpLTJdPWFyZ3VtZW50c1tpXTt2YXIgbz1lLmNyZWF0ZS5hcHBseShlLFtyXS5jb25jYXQobikpO3JldHVybiB0LmFwcGVuZENoaWxkKG8pLG99LGUuY3JlYXRlPWZ1bmN0aW9uKHQpe2Zvcih2YXIgcj1bXSxuPTE7bjxhcmd1bWVudHMubGVuZ3RoO24rKylyW24tMV09YXJndW1lbnRzW25dO3ZhciBpPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQodCk7cmV0dXJuIGUuYWRkQ2xhc3Nlcy5hcHBseShlLFtpXS5jb25jYXQocikpLGl9LGUuYWRkQ2xhc3Nlcz1mdW5jdGlvbih0KXtmb3IodmFyIHI9W10sbj0xO248YXJndW1lbnRzLmxlbmd0aDtuKyspcltuLTFdPWFyZ3VtZW50c1tuXTtyPXIuZmlsdGVyKGZ1bmN0aW9uKGkpe3JldHVybiBpIT1udWxsfSksdC5jbGFzc0xpc3QhPW51bGw/ci5mb3JFYWNoKGZ1bmN0aW9uKGkpe3QuY2xhc3NMaXN0LmFkZChpKX0pOnQuc2V0QXR0cmlidXRlKCJjbGFzcyIsci5qb2luKCIgIikpfSxlLmdldERpbWVuc2lvbnM9ZnVuY3Rpb24odCl7aWYodC5nZXRCb3VuZGluZ0NsaWVudFJlY3QpdHJ5e3ZhciByPXQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksbj1yLndpZHRoLGk9ci5oZWlnaHQ7cmV0dXJue3dpZHRoOm4saGVpZ2h0Oml9fWNhdGNoKG8pe31yZXR1cm57aGVpZ2h0OjAsd2lkdGg6MH19LGV9KCk7cEYuSHRtbFV0aWxzPUZBO3ZhciBkT2U9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKHQscixuKXtuPT09dm9pZCAwJiYobj0hMSk7dmFyIGk9dGhpczt0aGlzLmVsZW1lbnQ9dCx0aGlzLmNsYXNzTmFtZT1yLHRoaXMuYWRkVGl0bGU9bix0aGlzLmNyZWF0ZVJ1bGVyPWZ1bmN0aW9uKCl7cmV0dXJuIGZ1bmN0aW9uKG8pe3ZhciBhPUZBLmFwcGVuZChpLmVsZW1lbnQsInNwYW4iLCJ0ZXh0LXRtcCIsaS5jbGFzc05hbWUpO2EudGV4dENvbnRlbnQ9bzt2YXIgcz1GQS5nZXREaW1lbnNpb25zKGEpO3JldHVybiBpLmVsZW1lbnQucmVtb3ZlQ2hpbGQoYSksc319LHRoaXMuY3JlYXRlUGVuPWZ1bmN0aW9uKG8sYSxzKXtzPT1udWxsJiYocz1pLmVsZW1lbnQpO3ZhciBsPUZBLmFwcGVuZChzLCJkaXYiLCJ0ZXh0LWJsb2NrIixpLmNsYXNzTmFtZSk7cmV0dXJuIGwuc3R5bGUucG9zaXRpb249InJlbGF0aXZlIixsLnN0eWxlLnRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIC0xZW0pICIrKCJ0cmFuc2xhdGUoIithLnRyYW5zbGF0ZVswXSsicHgsICIrYS50cmFuc2xhdGVbMV0rInB4KSAiKSsoInJvdGF0ZSgiK2Eucm90YXRlKyJkZWcpIiksbC5zdHlsZS50cmFuc2Zvcm1PcmlnaW49IjAgMS4yZW0iLGkuYWRkVGl0bGUmJmwuc2V0QXR0cmlidXRlKCJ0aXRsZSIsbyksaS5jcmVhdGVIdG1sTGluZVBlbihsKX19cmV0dXJuIGUucHJvdG90eXBlLnNldEFkZFRpdGxlPWZ1bmN0aW9uKHQpe3RoaXMuYWRkVGl0bGU9dH0sZS5wcm90b3R5cGUuY3JlYXRlSHRtbExpbmVQZW49ZnVuY3Rpb24odCl7cmV0dXJue3dyaXRlOmZ1bmN0aW9uKHIsbixpLG8sYSl7dmFyIHM9RkEuYXBwZW5kKHQsImRpdiIsInRleHQtbGluZSIpO3MudGV4dENvbnRlbnQ9cixzLnN0eWxlLndpZHRoPW4rInB4IixzLnN0eWxlLnRleHRBbGlnbj1pLHMuc3R5bGUucG9zaXRpb249ImFic29sdXRlIixzLnN0eWxlLndoaXRlU3BhY2U9Im5vd3JhcCIscy5zdHlsZS50b3A9YSsicHgiLHMuc3R5bGUubGVmdD1vKyJweCJ9fX0sZX0oKTtwRi5IdG1sQ29udGV4dD1kT2V9KTt2YXIgaVh0PUgoZEY9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGRGLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgbU9lPXpBKCksclh0PXBpdCgpLGpnPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe31yZXR1cm4gZS5hcHBlbmQ9ZnVuY3Rpb24odCxyKXtmb3IodmFyIG49W10saT0yO2k8YXJndW1lbnRzLmxlbmd0aDtpKyspbltpLTJdPWFyZ3VtZW50c1tpXTt2YXIgbz1lLmNyZWF0ZS5hcHBseShlLFtyXS5jb25jYXQobikpO3JldHVybiB0LmFwcGVuZENoaWxkKG8pLG99LGUuY3JlYXRlPWZ1bmN0aW9uKHQpe2Zvcih2YXIgcj1bXSxuPTE7bjxhcmd1bWVudHMubGVuZ3RoO24rKylyW24tMV09YXJndW1lbnRzW25dO3ZhciBpPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyhlLlNWR19OUyx0KTtyZXR1cm4gclh0Lkh0bWxVdGlscy5hZGRDbGFzc2VzLmFwcGx5KHJYdC5IdG1sVXRpbHMsW2ldLmNvbmNhdChyKSksaX0sZS5nZXREaW1lbnNpb25zPWZ1bmN0aW9uKHQpe2lmKHQuZ2V0QkJveCl0cnl7dmFyIHI9dC5nZXRCQm94KCksbj1yLndpZHRoLGk9ci5oZWlnaHQ7cmV0dXJue3dpZHRoOm4saGVpZ2h0Oml9fWNhdGNoKG8pe31yZXR1cm57aGVpZ2h0OjAsd2lkdGg6MH19LGV9KCk7amcuU1ZHX05TPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI7ZEYuU3ZnVXRpbHM9amc7dmFyIG5YdD1mdW5jdGlvbigpe2Z1bmN0aW9uIGUodCxyLG4pe249PT12b2lkIDAmJihuPSExKTt2YXIgaT10aGlzO3RoaXMuZWxlbWVudD10LHRoaXMuY2xhc3NOYW1lPXIsdGhpcy5hZGRUaXRsZUVsZW1lbnQ9bix0aGlzLmNyZWF0ZVJ1bGVyPWZ1bmN0aW9uKCl7dmFyIG89aS5nZXRUZXh0RWxlbWVudHMoaS5lbGVtZW50KSxhPW8ucGFyZW50RWxlbWVudCxzPW8uY29udGFpbmVyRWxlbWVudCxsPW8udGV4dEVsZW1lbnQ7cmV0dXJuIGZ1bmN0aW9uKGMpe2EuYXBwZW5kQ2hpbGQocyksbC50ZXh0Q29udGVudD1jO3ZhciB1PWpnLmdldERpbWVuc2lvbnMobCk7cmV0dXJuIGEucmVtb3ZlQ2hpbGQocyksdX19LHRoaXMuY3JlYXRlUGVuPWZ1bmN0aW9uKG8sYSxzKXtzPT1udWxsJiYocz1pLmVsZW1lbnQpO3ZhciBsPWpnLmFwcGVuZChzLCJnIiwidGV4dC1jb250YWluZXIiLGkuY2xhc3NOYW1lKTtpLmFkZFRpdGxlRWxlbWVudCYmKGpnLmFwcGVuZChsLCJ0aXRsZSIpLnRleHRDb250ZW50PW8sbC5zZXRBdHRyaWJ1dGUoInRpdGxlIixvKSk7dmFyIGM9amcuYXBwZW5kKGwsImciLCJ0ZXh0LWFyZWEiKTtyZXR1cm4gYy5zZXRBdHRyaWJ1dGUoInRyYW5zZm9ybSIsInRyYW5zbGF0ZSgiK2EudHJhbnNsYXRlWzBdKyIsIithLnRyYW5zbGF0ZVsxXSsiKSIrKCJyb3RhdGUoIithLnJvdGF0ZSsiKSIpKSxpLmNyZWF0ZVN2Z0xpbmVQZW4oYyl9fXJldHVybiBlLnByb3RvdHlwZS5zZXRBZGRUaXRsZUVsZW1lbnQ9ZnVuY3Rpb24odCl7dGhpcy5hZGRUaXRsZUVsZW1lbnQ9dH0sZS5wcm90b3R5cGUuY3JlYXRlU3ZnTGluZVBlbj1mdW5jdGlvbih0KXtyZXR1cm57d3JpdGU6ZnVuY3Rpb24ocixuLGksbyxhKXtvKz1uKm1PZS5Xcml0ZXIuWE9mZnNldEZhY3RvcltpXTt2YXIgcz1qZy5hcHBlbmQodCwidGV4dCIsInRleHQtbGluZSIpO3MudGV4dENvbnRlbnQ9cixzLnNldEF0dHJpYnV0ZSgidGV4dC1hbmNob3IiLGUuQW5jaG9yTWFwW2ldKSxzLnNldEF0dHJpYnV0ZSgidHJhbnNmb3JtIiwidHJhbnNsYXRlKCIrbysiLCIrYSsiKSIpLHMuc2V0QXR0cmlidXRlKCJ5IiwiLTAuMjVlbSIpfX19LGUucHJvdG90eXBlLmdldFRleHRFbGVtZW50cz1mdW5jdGlvbih0KXtpZih0LnRhZ05hbWU9PT0idGV4dCIpe3ZhciByPXQucGFyZW50RWxlbWVudDtyZXR1cm4gcj09bnVsbCYmKHI9dC5wYXJlbnROb2RlKSxyLnJlbW92ZUNoaWxkKHQpLHtjb250YWluZXJFbGVtZW50OnQscGFyZW50RWxlbWVudDpyLHRleHRFbGVtZW50OnR9fXZhciBuPXQucXVlcnlTZWxlY3RvcigidGV4dCIpO2lmKG4hPW51bGwpe3ZhciByPW4ucGFyZW50RWxlbWVudDtyZXR1cm4gcj09bnVsbCYmKHI9bi5wYXJlbnROb2RlKSxyLnJlbW92ZUNoaWxkKG4pLHtjb250YWluZXJFbGVtZW50Om4scGFyZW50RWxlbWVudDpyLHRleHRFbGVtZW50Om59fXZhciBpPWpnLmNyZWF0ZSgidGV4dCIsdGhpcy5jbGFzc05hbWUpO3JldHVybntjb250YWluZXJFbGVtZW50OmkscGFyZW50RWxlbWVudDp0LHRleHRFbGVtZW50Oml9fSxlfSgpO25YdC5BbmNob3JNYXA9e2NlbnRlcjoibWlkZGxlIixsZWZ0OiJzdGFydCIscmlnaHQ6ImVuZCJ9O2RGLlN2Z0NvbnRleHQ9blh0fSk7dmFyIG9YdD1IKGRpdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoZGl0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgZ09lPXpBKCksX09lPSIjNDQ0Iix5T2U9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKHQscixuKXtyPT09dm9pZCAwJiYocj0xMCksbj09PXZvaWQgMCYmKG49e30pO3ZhciBpPXRoaXM7dGhpcy5jdHg9dCx0aGlzLmxpbmVIZWlnaHQ9cix0aGlzLnN0eWxlPW4sdGhpcy5jcmVhdGVSdWxlcj1mdW5jdGlvbigpe3JldHVybiBmdW5jdGlvbihvKXtpLmN0eC5mb250PWkuc3R5bGUuZm9udDt2YXIgYT1pLmN0eC5tZWFzdXJlVGV4dChvKS53aWR0aDtyZXR1cm57d2lkdGg6YSxoZWlnaHQ6aS5saW5lSGVpZ2h0fX19LHRoaXMuY3JlYXRlUGVuPWZ1bmN0aW9uKG8sYSxzKXtyZXR1cm4gcz09bnVsbCYmKHM9aS5jdHgpLHMuc2F2ZSgpLHMudHJhbnNsYXRlKGEudHJhbnNsYXRlWzBdLGEudHJhbnNsYXRlWzFdKSxzLnJvdGF0ZShhLnJvdGF0ZSpNYXRoLlBJLzE4MCksaS5jcmVhdGVDYW52YXNQZW4ocyl9LHRoaXMuc3R5bGUuZmlsbD09PXZvaWQgMCYmKHRoaXMuc3R5bGUuZmlsbD1fT2UpfXJldHVybiBlLnByb3RvdHlwZS5jcmVhdGVDYW52YXNQZW49ZnVuY3Rpb24odCl7dmFyIHI9dGhpcztyZXR1cm57ZGVzdHJveTpmdW5jdGlvbigpe3QucmVzdG9yZSgpfSx3cml0ZTpmdW5jdGlvbihuLGksbyxhLHMpe2ErPWkqZ09lLldyaXRlci5YT2Zmc2V0RmFjdG9yW29dLHQudGV4dEFsaWduPW8sci5zdHlsZS5mb250IT1udWxsJiYodC5mb250PXIuc3R5bGUuZm9udCksci5zdHlsZS5maWxsIT1udWxsJiYodC5maWxsU3R5bGU9ci5zdHlsZS5maWxsLHQuZmlsbFRleHQobixhLHMpKSxyLnN0eWxlLnN0cm9rZSE9bnVsbCYmKHQuc3Ryb2tlU3R5bGU9ci5zdHlsZS5maWxsLHQuc3Ryb2tlVGV4dChuLGEscykpfX19LGV9KCk7ZGl0LkNhbnZhc0NvbnRleHQ9eU9lfSk7dmFyIGdpdD1IKG1GPT57InVzZSBzdHJpY3QiO2Z1bmN0aW9uIG1pdChlKXtmb3IodmFyIHQgaW4gZSltRi5oYXNPd25Qcm9wZXJ0eSh0KXx8KG1GW3RdPWVbdF0pfU9iamVjdC5kZWZpbmVQcm9wZXJ0eShtRiwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7bWl0KGlYdCgpKTttaXQob1h0KCkpO21pdChwaXQoKSl9KTt2YXIgZ0Y9SChfaXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KF9pdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIGFYdD1mdW5jdGlvbigpe2Z1bmN0aW9uIGUodCl7dC5jcmVhdGVSdWxlciE9bnVsbD90aGlzLnJ1bGVyPXQuY3JlYXRlUnVsZXIoKTp0aGlzLnJ1bGVyPXR9cmV0dXJuIGUucHJvdG90eXBlLm1lYXN1cmU9ZnVuY3Rpb24odCl7cmV0dXJuIHQ9PT12b2lkIDAmJih0PWUuSEVJR0hUX1RFWFQpLHRoaXMucnVsZXIodCl9LGV9KCk7YVh0LkhFSUdIVF9URVhUPSJiZHBxbCI7X2l0LkFic3RyYWN0TWVhc3VyZXI9YVh0fSk7dmFyIHlpdD1IKEJBPT57InVzZSBzdHJpY3QiO3ZhciB2T2U9QkEmJkJBLl9fZXh0ZW5kc3x8ZnVuY3Rpb24oKXt2YXIgZT1PYmplY3Quc2V0UHJvdG90eXBlT2Z8fHtfX3Byb3RvX186W119aW5zdGFuY2VvZiBBcnJheSYmZnVuY3Rpb24odCxyKXt0Ll9fcHJvdG9fXz1yfXx8ZnVuY3Rpb24odCxyKXtmb3IodmFyIG4gaW4gcilyLmhhc093blByb3BlcnR5KG4pJiYodFtuXT1yW25dKX07cmV0dXJuIGZ1bmN0aW9uKHQscil7ZSh0LHIpO2Z1bmN0aW9uIG4oKXt0aGlzLmNvbnN0cnVjdG9yPXR9dC5wcm90b3R5cGU9cj09PW51bGw/T2JqZWN0LmNyZWF0ZShyKToobi5wcm90b3R5cGU9ci5wcm90b3R5cGUsbmV3IG4pfX0oKTtPYmplY3QuZGVmaW5lUHJvcGVydHkoQkEsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBfRj1nRigpLHhPZT1mdW5jdGlvbihlKXt2T2UodCxlKTtmdW5jdGlvbiB0KHIsbil7bj09PXZvaWQgMCYmKG49ITEpO3ZhciBpPWUuY2FsbCh0aGlzLHIpfHx0aGlzO3JldHVybiBpLnVzZUd1YXJkcz1uLGl9cmV0dXJuIHQucHJvdG90eXBlLl9hZGRHdWFyZHM9ZnVuY3Rpb24ocil7cmV0dXJuIF9GLkFic3RyYWN0TWVhc3VyZXIuSEVJR0hUX1RFWFQrcitfRi5BYnN0cmFjdE1lYXN1cmVyLkhFSUdIVF9URVhUfSx0LnByb3RvdHlwZS5fbWVhc3VyZUxpbmU9ZnVuY3Rpb24ocixuKXtuPT09dm9pZCAwJiYobj0hMSk7dmFyIGk9dGhpcy51c2VHdWFyZHN8fG58fC9eW1x0IF0kLy50ZXN0KHIpLG89aT90aGlzLl9hZGRHdWFyZHMocik6cixhPWUucHJvdG90eXBlLm1lYXN1cmUuY2FsbCh0aGlzLG8pO3JldHVybiBhLndpZHRoLT1pPzIqdGhpcy5nZXRHdWFyZFdpZHRoKCk6MCxhfSx0LnByb3RvdHlwZS5tZWFzdXJlPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXM7aWYocj09PXZvaWQgMCYmKHI9X0YuQWJzdHJhY3RNZWFzdXJlci5IRUlHSFRfVEVYVCksci50cmltKCk9PT0iIilyZXR1cm57d2lkdGg6MCxoZWlnaHQ6MH07dmFyIGk9ci50cmltKCkuc3BsaXQoYApgKS5tYXAoZnVuY3Rpb24obyl7cmV0dXJuIG4uX21lYXN1cmVMaW5lKG8pfSk7cmV0dXJue2hlaWdodDppLnJlZHVjZShmdW5jdGlvbihvLGEpe3JldHVybiBvK2EuaGVpZ2h0fSwwKSx3aWR0aDppLnJlZHVjZShmdW5jdGlvbihvLGEpe3JldHVybiBNYXRoLm1heChvLGEud2lkdGgpfSwwKX19LHQucHJvdG90eXBlLmdldEd1YXJkV2lkdGg9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5ndWFyZFdpZHRoPT1udWxsJiYodGhpcy5ndWFyZFdpZHRoPWUucHJvdG90eXBlLm1lYXN1cmUuY2FsbCh0aGlzKS53aWR0aCksdGhpcy5ndWFyZFdpZHRofSx0fShfRi5BYnN0cmFjdE1lYXN1cmVyKTtCQS5NZWFzdXJlcj14T2V9KTt2YXIgdml0PUgoSEE9PnsidXNlIHN0cmljdCI7dmFyIGJPZT1IQSYmSEEuX19leHRlbmRzfHxmdW5jdGlvbigpe3ZhciBlPU9iamVjdC5zZXRQcm90b3R5cGVPZnx8e19fcHJvdG9fXzpbXX1pbnN0YW5jZW9mIEFycmF5JiZmdW5jdGlvbih0LHIpe3QuX19wcm90b19fPXJ9fHxmdW5jdGlvbih0LHIpe2Zvcih2YXIgbiBpbiByKXIuaGFzT3duUHJvcGVydHkobikmJih0W25dPXJbbl0pfTtyZXR1cm4gZnVuY3Rpb24odCxyKXtlKHQscik7ZnVuY3Rpb24gbigpe3RoaXMuY29uc3RydWN0b3I9dH10LnByb3RvdHlwZT1yPT09bnVsbD9PYmplY3QuY3JlYXRlKHIpOihuLnByb3RvdHlwZT1yLnByb3RvdHlwZSxuZXcgbil9fSgpO09iamVjdC5kZWZpbmVQcm9wZXJ0eShIQSwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIHdPZT15aXQoKSxTT2U9ZnVuY3Rpb24oZSl7Yk9lKHQsZSk7ZnVuY3Rpb24gdCgpe3JldHVybiBlIT09bnVsbCYmZS5hcHBseSh0aGlzLGFyZ3VtZW50cyl8fHRoaXN9cmV0dXJuIHQucHJvdG90eXBlLl9tZWFzdXJlQ2hhcmFjdGVyPWZ1bmN0aW9uKHIpe3JldHVybiBlLnByb3RvdHlwZS5fbWVhc3VyZUxpbmUuY2FsbCh0aGlzLHIpfSx0LnByb3RvdHlwZS5fbWVhc3VyZUxpbmU9ZnVuY3Rpb24ocil7dmFyIG49dGhpcyxpPXIuc3BsaXQoIiIpLm1hcChmdW5jdGlvbihvKXtyZXR1cm4gbi5fbWVhc3VyZUNoYXJhY3RlcihvKX0pO3JldHVybntoZWlnaHQ6aS5yZWR1Y2UoZnVuY3Rpb24obyxhKXtyZXR1cm4gTWF0aC5tYXgobyxhLmhlaWdodCl9LDApLHdpZHRoOmkucmVkdWNlKGZ1bmN0aW9uKG8sYSl7cmV0dXJuIG8rYS53aWR0aH0sMCl9fSx0fSh3T2UuTWVhc3VyZXIpO0hBLkNoYXJhY3Rlck1lYXN1cmVyPVNPZX0pO3ZhciB4aXQ9SChWQT0+eyJ1c2Ugc3RyaWN0Ijt2YXIgTU9lPVZBJiZWQS5fX2V4dGVuZHN8fGZ1bmN0aW9uKCl7dmFyIGU9T2JqZWN0LnNldFByb3RvdHlwZU9mfHx7X19wcm90b19fOltdfWluc3RhbmNlb2YgQXJyYXkmJmZ1bmN0aW9uKHQscil7dC5fX3Byb3RvX189cn18fGZ1bmN0aW9uKHQscil7Zm9yKHZhciBuIGluIHIpci5oYXNPd25Qcm9wZXJ0eShuKSYmKHRbbl09cltuXSl9O3JldHVybiBmdW5jdGlvbih0LHIpe2UodCxyKTtmdW5jdGlvbiBuKCl7dGhpcy5jb25zdHJ1Y3Rvcj10fXQucHJvdG90eXBlPXI9PT1udWxsP09iamVjdC5jcmVhdGUocik6KG4ucHJvdG90eXBlPXIucHJvdG90eXBlLG5ldyBuKX19KCk7T2JqZWN0LmRlZmluZVByb3BlcnR5KFZBLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgRU9lPXBTKCksVE9lPXZpdCgpLENPZT1mdW5jdGlvbihlKXtNT2UodCxlKTtmdW5jdGlvbiB0KHIsbil7dmFyIGk9ZS5jYWxsKHRoaXMscixuKXx8dGhpcztyZXR1cm4gaS5jYWNoZT1uZXcgRU9lLkNhY2hlKGZ1bmN0aW9uKG8pe3JldHVybiBpLl9tZWFzdXJlQ2hhcmFjdGVyTm90RnJvbUNhY2hlKG8pfSksaX1yZXR1cm4gdC5wcm90b3R5cGUuX21lYXN1cmVDaGFyYWN0ZXJOb3RGcm9tQ2FjaGU9ZnVuY3Rpb24ocil7cmV0dXJuIGUucHJvdG90eXBlLl9tZWFzdXJlQ2hhcmFjdGVyLmNhbGwodGhpcyxyKX0sdC5wcm90b3R5cGUuX21lYXN1cmVDaGFyYWN0ZXI9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuY2FjaGUuZ2V0KHIpfSx0LnByb3RvdHlwZS5yZXNldD1mdW5jdGlvbigpe3RoaXMuY2FjaGUuY2xlYXIoKX0sdH0oVE9lLkNoYXJhY3Rlck1lYXN1cmVyKTtWQS5DYWNoZUNoYXJhY3Rlck1lYXN1cmVyPUNPZX0pO3ZhciBzWHQ9SChVQT0+eyJ1c2Ugc3RyaWN0Ijt2YXIgQU9lPVVBJiZVQS5fX2V4dGVuZHN8fGZ1bmN0aW9uKCl7dmFyIGU9T2JqZWN0LnNldFByb3RvdHlwZU9mfHx7X19wcm90b19fOltdfWluc3RhbmNlb2YgQXJyYXkmJmZ1bmN0aW9uKHQscil7dC5fX3Byb3RvX189cn18fGZ1bmN0aW9uKHQscil7Zm9yKHZhciBuIGluIHIpci5oYXNPd25Qcm9wZXJ0eShuKSYmKHRbbl09cltuXSl9O3JldHVybiBmdW5jdGlvbih0LHIpe2UodCxyKTtmdW5jdGlvbiBuKCl7dGhpcy5jb25zdHJ1Y3Rvcj10fXQucHJvdG90eXBlPXI9PT1udWxsP09iamVjdC5jcmVhdGUocik6KG4ucHJvdG90eXBlPXIucHJvdG90eXBlLG5ldyBuKX19KCk7T2JqZWN0LmRlZmluZVByb3BlcnR5KFVBLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgUE9lPXBTKCksSU9lPWdGKCksTE9lPXhpdCgpLGtPZT1mdW5jdGlvbihlKXtBT2UodCxlKTtmdW5jdGlvbiB0KHIpe3ZhciBuPWUuY2FsbCh0aGlzLHIpfHx0aGlzO3JldHVybiBuLmRpbUNhY2hlPW5ldyBQT2UuQ2FjaGUoZnVuY3Rpb24oaSl7cmV0dXJuIG4uX21lYXN1cmVOb3RGcm9tQ2FjaGUoaSl9KSxufXJldHVybiB0LnByb3RvdHlwZS5fbWVhc3VyZU5vdEZyb21DYWNoZT1mdW5jdGlvbihyKXtyZXR1cm4gZS5wcm90b3R5cGUubWVhc3VyZS5jYWxsKHRoaXMscil9LHQucHJvdG90eXBlLm1lYXN1cmU9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PT12b2lkIDAmJihyPUlPZS5BYnN0cmFjdE1lYXN1cmVyLkhFSUdIVF9URVhUKSx0aGlzLmRpbUNhY2hlLmdldChyKX0sdC5wcm90b3R5cGUucmVzZXQ9ZnVuY3Rpb24oKXt0aGlzLmRpbUNhY2hlLmNsZWFyKCksZS5wcm90b3R5cGUucmVzZXQuY2FsbCh0aGlzKX0sdH0oTE9lLkNhY2hlQ2hhcmFjdGVyTWVhc3VyZXIpO1VBLkNhY2hlTWVhc3VyZXI9a09lfSk7dmFyIGJpdD1IKHlGPT57InVzZSBzdHJpY3QiO2Z1bmN0aW9uIHFBKGUpe2Zvcih2YXIgdCBpbiBlKXlGLmhhc093blByb3BlcnR5KHQpfHwoeUZbdF09ZVt0XSl9T2JqZWN0LmRlZmluZVByb3BlcnR5KHlGLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTtxQShnRigpKTtxQSh4aXQoKSk7cUEoc1h0KCkpO3FBKHZpdCgpKTtxQSh5aXQoKSl9KTt2YXIgU2l0PUgod2l0PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eSh3aXQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBHQT1wUygpLFJPZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUoKXt0aGlzLm1heExpbmVzKDEvMCksdGhpcy50ZXh0VHJpbW1pbmcoImVsbGlwc2lzIiksdGhpcy5hbGxvd0JyZWFraW5nV29yZHMoITEpLHRoaXMuX3Rva2VuaXplcj1uZXcgR0EuVG9rZW5pemVyLHRoaXMuX2JyZWFraW5nQ2hhcmFjdGVyPSItIn1yZXR1cm4gZS5wcm90b3R5cGUubWF4TGluZXM9ZnVuY3Rpb24odCl7cmV0dXJuIHQ9PW51bGw/dGhpcy5fbWF4TGluZXM6KHRoaXMuX21heExpbmVzPXQsdGhpcyl9LGUucHJvdG90eXBlLnRleHRUcmltbWluZz1mdW5jdGlvbih0KXtpZih0PT1udWxsKXJldHVybiB0aGlzLl90ZXh0VHJpbW1pbmc7aWYodCE9PSJlbGxpcHNpcyImJnQhPT0ibm9uZSIpdGhyb3cgbmV3IEVycm9yKHQrIiAtIHVuc3VwcG9ydGVkIHRleHQgdHJpbW1pbmcgb3B0aW9uLiIpO3JldHVybiB0aGlzLl90ZXh0VHJpbW1pbmc9dCx0aGlzfSxlLnByb3RvdHlwZS5hbGxvd0JyZWFraW5nV29yZHM9ZnVuY3Rpb24odCl7cmV0dXJuIHQ9PW51bGw/dGhpcy5fYWxsb3dCcmVha2luZ1dvcmRzOih0aGlzLl9hbGxvd0JyZWFraW5nV29yZHM9dCx0aGlzKX0sZS5wcm90b3R5cGUud3JhcD1mdW5jdGlvbih0LHIsbixpKXt2YXIgbz10aGlzO2k9PT12b2lkIDAmJihpPTEvMCk7dmFyIGE9e25vQnJva2VXb3JkczowLG5vTGluZXM6MCxvcmlnaW5hbFRleHQ6dCx0cnVuY2F0ZWRUZXh0OiIiLHdyYXBwZWRUZXh0OiIifSxzPXthdmFpbGFibGVMaW5lczpNYXRoLm1pbihNYXRoLmZsb29yKGkvci5tZWFzdXJlKCkuaGVpZ2h0KSx0aGlzLl9tYXhMaW5lcyksYXZhaWxhYmxlV2lkdGg6bixjYW5GaXRUZXh0OiEwLGN1cnJlbnRMaW5lOiIiLHdyYXBwaW5nOmF9LGw9dC5zcGxpdChgCmApO3JldHVybiBsLnJlZHVjZShmdW5jdGlvbihjLHUsaCl7cmV0dXJuIG8uYnJlYWtMaW5lVG9GaXRXaWR0aChjLHUsaCE9PWwubGVuZ3RoLTEscil9LHMpLndyYXBwaW5nfSxlLnByb3RvdHlwZS5icmVha0xpbmVUb0ZpdFdpZHRoPWZ1bmN0aW9uKHQscixuLGkpe3ZhciBvPXRoaXM7IXQuY2FuRml0VGV4dCYmdC53cmFwcGluZy50cnVuY2F0ZWRUZXh0IT09IiImJih0LndyYXBwaW5nLnRydW5jYXRlZFRleHQrPWAKYCk7dmFyIGE9dGhpcy5fdG9rZW5pemVyLnRva2VuaXplKHIpO3Q9YS5yZWR1Y2UoZnVuY3Rpb24obCxjKXtyZXR1cm4gby53cmFwTmV4dFRva2VuKGMsbCxpKX0sdCk7dmFyIHM9R0EuU3RyaW5nTWV0aG9kcy50cmltRW5kKHQuY3VycmVudExpbmUpO3JldHVybiB0LndyYXBwaW5nLm5vTGluZXMrPSsocyE9PSIiKSx0LndyYXBwaW5nLm5vTGluZXM9PT10LmF2YWlsYWJsZUxpbmVzJiZ0aGlzLl90ZXh0VHJpbW1pbmchPT0ibm9uZSImJm4/dC5jYW5GaXRUZXh0PSExOnQud3JhcHBpbmcud3JhcHBlZFRleHQrPXMsdC5jdXJyZW50TGluZT1gCmAsdH0sZS5wcm90b3R5cGUuY2FuRml0VG9rZW49ZnVuY3Rpb24odCxyLG4pe3ZhciBpPXRoaXMsbz10LnNwbGl0KCIiKS5tYXAoZnVuY3Rpb24oYSxzKXtyZXR1cm4gcyE9PXQubGVuZ3RoLTE/YStpLl9icmVha2luZ0NoYXJhY3RlcjphfSk7cmV0dXJuIG4ubWVhc3VyZSh0KS53aWR0aDw9cnx8by5ldmVyeShmdW5jdGlvbihhKXtyZXR1cm4gbi5tZWFzdXJlKGEpLndpZHRoPD1yfSl9LGUucHJvdG90eXBlLmFkZEVsbGlwc2lzPWZ1bmN0aW9uKHQscixuKXtpZih0aGlzLl90ZXh0VHJpbW1pbmc9PT0ibm9uZSIpcmV0dXJue3JlbWFpbmluZ1Rva2VuOiIiLHdyYXBwZWRUb2tlbjp0fTt2YXIgaT10LnN1YnN0cmluZygwKS50cmltKCksbz1uLm1lYXN1cmUoaSkud2lkdGgsYT1uLm1lYXN1cmUoIi4uLiIpLndpZHRoLHM9dC5sZW5ndGg+MCYmdFswXT09PWAKYD9gCmA6IiI7aWYocjw9YSl7dmFyIGw9YS8zLGM9TWF0aC5mbG9vcihyL2wpO3JldHVybntyZW1haW5pbmdUb2tlbjp0LHdyYXBwZWRUb2tlbjpzKyIuLi4iLnN1YnN0cigwLGMpfX1mb3IoO28rYT5yOylpPUdBLlN0cmluZ01ldGhvZHMudHJpbUVuZChpLnN1YnN0cigwLGkubGVuZ3RoLTEpKSxvPW4ubWVhc3VyZShpKS53aWR0aDtyZXR1cm57cmVtYWluaW5nVG9rZW46R0EuU3RyaW5nTWV0aG9kcy50cmltRW5kKHQuc3Vic3RyaW5nKGkubGVuZ3RoKSwiLSIpLnRyaW0oKSx3cmFwcGVkVG9rZW46cytpKyIuLi4ifX0sZS5wcm90b3R5cGUud3JhcE5leHRUb2tlbj1mdW5jdGlvbih0LHIsbil7aWYoIXIuY2FuRml0VGV4dHx8ci5hdmFpbGFibGVMaW5lcz09PXIud3JhcHBpbmcubm9MaW5lc3x8IXRoaXMuY2FuRml0VG9rZW4odCxyLmF2YWlsYWJsZVdpZHRoLG4pKXJldHVybiB0aGlzLmZpbmlzaFdyYXBwaW5nKHQscixuKTtmb3IodmFyIGk9dDtpOyl7dmFyIG89dGhpcy5icmVha1Rva2VuVG9GaXRJbldpZHRoKGksci5jdXJyZW50TGluZSxyLmF2YWlsYWJsZVdpZHRoLG4pO2lmKHIuY3VycmVudExpbmU9by5saW5lLGk9by5yZW1haW5pbmdUb2tlbixpIT1udWxsKWlmKHIud3JhcHBpbmcubm9Ccm9rZVdvcmRzKz0rby5icmVha1dvcmQsKytyLndyYXBwaW5nLm5vTGluZXMsci5hdmFpbGFibGVMaW5lcz09PXIud3JhcHBpbmcubm9MaW5lcyl7dmFyIGE9dGhpcy5hZGRFbGxpcHNpcyhyLmN1cnJlbnRMaW5lLHIuYXZhaWxhYmxlV2lkdGgsbik7cmV0dXJuIHIud3JhcHBpbmcud3JhcHBlZFRleHQrPWEud3JhcHBlZFRva2VuLHIud3JhcHBpbmcudHJ1bmNhdGVkVGV4dCs9YS5yZW1haW5pbmdUb2tlbitpLHIuY3VycmVudExpbmU9YApgLHJ9ZWxzZSByLndyYXBwaW5nLndyYXBwZWRUZXh0Kz1HQS5TdHJpbmdNZXRob2RzLnRyaW1FbmQoci5jdXJyZW50TGluZSksci5jdXJyZW50TGluZT1gCmB9cmV0dXJuIHJ9LGUucHJvdG90eXBlLmZpbmlzaFdyYXBwaW5nPWZ1bmN0aW9uKHQscixuKXtpZihyLmNhbkZpdFRleHQmJnIuYXZhaWxhYmxlTGluZXMhPT1yLndyYXBwaW5nLm5vTGluZXMmJnRoaXMuX3RleHRUcmltbWluZyE9PSJub25lIil7dmFyIGk9dGhpcy5hZGRFbGxpcHNpcyhyLmN1cnJlbnRMaW5lK3Qsci5hdmFpbGFibGVXaWR0aCxuKTtyLndyYXBwaW5nLndyYXBwZWRUZXh0Kz1pLndyYXBwZWRUb2tlbixyLndyYXBwaW5nLnRydW5jYXRlZFRleHQrPWkucmVtYWluaW5nVG9rZW4sci53cmFwcGluZy5ub0Jyb2tlV29yZHMrPSsoaS5yZW1haW5pbmdUb2tlbi5sZW5ndGg8dC5sZW5ndGgpLHIud3JhcHBpbmcubm9MaW5lcys9KyhpLndyYXBwZWRUb2tlbi5sZW5ndGg+MCksci5jdXJyZW50TGluZT0iIn1lbHNlIHIud3JhcHBpbmcudHJ1bmNhdGVkVGV4dCs9dDtyZXR1cm4gci5jYW5GaXRUZXh0PSExLHJ9LGUucHJvdG90eXBlLmJyZWFrVG9rZW5Ub0ZpdEluV2lkdGg9ZnVuY3Rpb24odCxyLG4saSxvKXtpZihvPT09dm9pZCAwJiYobz10aGlzLl9icmVha2luZ0NoYXJhY3RlciksaS5tZWFzdXJlKHIrdCkud2lkdGg8PW4pcmV0dXJue2JyZWFrV29yZDohMSxsaW5lOnIrdCxyZW1haW5pbmdUb2tlbjpudWxsfTtpZih0LnRyaW0oKT09PSIiKXJldHVybnticmVha1dvcmQ6ITEsbGluZTpyLHJlbWFpbmluZ1Rva2VuOiIifTtpZighdGhpcy5fYWxsb3dCcmVha2luZ1dvcmRzJiZyLnRyaW0oKSE9PSIiKXJldHVybnticmVha1dvcmQ6ITEsbGluZTpyLHJlbWFpbmluZ1Rva2VuOnR9O2Zvcih2YXIgYT0wO2E8dC5sZW5ndGgmJmkubWVhc3VyZShyK3Quc3Vic3RyaW5nKDAsYSsxKStvKS53aWR0aDw9bjspKythO3ZhciBzPSIiO3JldHVybiBhPjAmJihzPW8pLHticmVha1dvcmQ6YT4wLGxpbmU6cit0LnN1YnN0cmluZygwLGEpK3MscmVtYWluaW5nVG9rZW46dC5zdWJzdHJpbmcoYSl9fSxlfSgpO3dpdC5XcmFwcGVyPVJPZX0pO3ZhciBjWHQ9SChXQT0+eyJ1c2Ugc3RyaWN0Ijt2YXIgTk9lPVdBJiZXQS5fX2V4dGVuZHN8fGZ1bmN0aW9uKCl7dmFyIGU9T2JqZWN0LnNldFByb3RvdHlwZU9mfHx7X19wcm90b19fOltdfWluc3RhbmNlb2YgQXJyYXkmJmZ1bmN0aW9uKHQscil7dC5fX3Byb3RvX189cn18fGZ1bmN0aW9uKHQscil7Zm9yKHZhciBuIGluIHIpci5oYXNPd25Qcm9wZXJ0eShuKSYmKHRbbl09cltuXSl9O3JldHVybiBmdW5jdGlvbih0LHIpe2UodCxyKTtmdW5jdGlvbiBuKCl7dGhpcy5jb25zdHJ1Y3Rvcj10fXQucHJvdG90eXBlPXI9PT1udWxsP09iamVjdC5jcmVhdGUocik6KG4ucHJvdG90eXBlPXIucHJvdG90eXBlLG5ldyBuKX19KCk7T2JqZWN0LmRlZmluZVByb3BlcnR5KFdBLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgRE9lPVNpdCgpLGxYdD1mdW5jdGlvbihlKXtOT2UodCxlKTtmdW5jdGlvbiB0KCl7cmV0dXJuIGUhPT1udWxsJiZlLmFwcGx5KHRoaXMsYXJndW1lbnRzKXx8dGhpc31yZXR1cm4gdC5wcm90b3R5cGUud3JhcD1mdW5jdGlvbihyLG4saSxvKXt2YXIgYT10aGlzO289PT12b2lkIDAmJihvPTEvMCk7dmFyIHM9ci5zcGxpdChgCmApO2lmKHMubGVuZ3RoPjEpdGhyb3cgbmV3IEVycm9yKCJTaW5nbGVMaW5lV3JhcHBlciBpcyBkZXNpZ25lZCB0byB3b3JrIG9ubHkgb24gc2luZ2xlIGxpbmUiKTt2YXIgbD1mdW5jdGlvbihnKXtyZXR1cm4gZS5wcm90b3R5cGUud3JhcC5jYWxsKGEscixuLGcsbyl9LGM9bChpKTtpZihjLm5vTGluZXM8MilyZXR1cm4gYztmb3IodmFyIHU9MCxoPWksZj0wO2Y8dC5OT19XUkFQX0lURVJBVElPTlMmJmg+dTsrK2Ype3ZhciBwPShoK3UpLzIsZD1sKHApO3RoaXMuYXJlU2FtZVJlc3VsdHMoYyxkKT8oaD1wLGM9ZCk6dT1wfXJldHVybiBjfSx0LnByb3RvdHlwZS5hcmVTYW1lUmVzdWx0cz1mdW5jdGlvbihyLG4pe3JldHVybiByLm5vTGluZXM9PT1uLm5vTGluZXMmJnIudHJ1bmNhdGVkVGV4dD09PW4udHJ1bmNhdGVkVGV4dH0sdH0oRE9lLldyYXBwZXIpO2xYdC5OT19XUkFQX0lURVJBVElPTlM9NTtXQS5TaW5nbGVMaW5lV3JhcHBlcj1sWHR9KTt2YXIgTWl0PUgodkY9PnsidXNlIHN0cmljdCI7ZnVuY3Rpb24gdVh0KGUpe2Zvcih2YXIgdCBpbiBlKXZGLmhhc093blByb3BlcnR5KHQpfHwodkZbdF09ZVt0XSl9T2JqZWN0LmRlZmluZVByb3BlcnR5KHZGLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt1WHQoY1h0KCkpO3VYdChTaXQoKSl9KTt2YXIgaFh0PUgoVGl0PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShUaXQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBFaXQ9Z2l0KCksT09lPWJpdCgpLHpPZT1NaXQoKSxGT2U9ekEoKSxCT2U9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKHQpe3RoaXMuY29udGV4dD10LHRoaXMubWVhc3VyZXI9bmV3IE9PZS5DYWNoZU1lYXN1cmVyKHRoaXMuY29udGV4dCksdGhpcy53cmFwcGVyPW5ldyB6T2UuV3JhcHBlcix0aGlzLndyaXRlcj1uZXcgRk9lLldyaXRlcih0aGlzLm1lYXN1cmVyLHRoaXMuY29udGV4dCx0aGlzLndyYXBwZXIpfXJldHVybiBlLnN2Zz1mdW5jdGlvbih0LHIsbil7cmV0dXJuIG5ldyBlKG5ldyBFaXQuU3ZnQ29udGV4dCh0LHIsbikpfSxlLmNhbnZhcz1mdW5jdGlvbih0LHIsbil7cmV0dXJuIG5ldyBlKG5ldyBFaXQuQ2FudmFzQ29udGV4dCh0LHIsbikpfSxlLmh0bWw9ZnVuY3Rpb24odCxyLG4pe3JldHVybiBuZXcgZShuZXcgRWl0Lkh0bWxDb250ZXh0KHQscixuKSl9LGUucHJvdG90eXBlLndyaXRlPWZ1bmN0aW9uKHQscixuLGksbyl7dGhpcy53cml0ZXIud3JpdGUodCxyLG4saSxvKX0sZS5wcm90b3R5cGUuY2xlYXJNZWFzdXJlckNhY2hlPWZ1bmN0aW9uKCl7dGhpcy5tZWFzdXJlci5yZXNldCgpfSxlfSgpO1RpdC5UeXBlc2V0dGVyPUJPZX0pO3ZhciBfbD1IKHhGPT57InVzZSBzdHJpY3QiO2Z1bmN0aW9uIGRTKGUpe2Zvcih2YXIgdCBpbiBlKXhGLmhhc093blByb3BlcnR5KHQpfHwoeEZbdF09ZVt0XSl9T2JqZWN0LmRlZmluZVByb3BlcnR5KHhGLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTtkUyhnaXQoKSk7ZFMoYml0KCkpO2RTKGhYdCgpKTtkUyhwUygpKTtkUyhNaXQoKSk7ZFMoekEoKSl9KTt2YXIgZlh0PUgobVM9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KG1TLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgSE9lPShFcigpLFV0KE1yKSksVk9lPXdpbmRvdy5BcnJheTtmdW5jdGlvbiBVT2UoZSx0KXtpZihlLmxlbmd0aCE9PXQubGVuZ3RoKXRocm93IG5ldyBFcnJvcigiYXR0ZW1wdGVkIHRvIGFkZCBhcnJheXMgb2YgdW5lcXVhbCBsZW5ndGgiKTtyZXR1cm4gZS5tYXAoZnVuY3Rpb24ocixuKXtyZXR1cm4gZVtuXSt0W25dfSl9bVMuYWRkPVVPZTtmdW5jdGlvbiBxT2UoZSl7dmFyIHQ9SE9lLnNldCgpLHI9W107cmV0dXJuIGUuZm9yRWFjaChmdW5jdGlvbihuKXt0LmhhcyhTdHJpbmcobikpfHwodC5hZGQoU3RyaW5nKG4pKSxyLnB1c2gobikpfSkscn1tUy51bmlxPXFPZTtmdW5jdGlvbiBHT2UoZSl7cmV0dXJuIFZPZS5wcm90b3R5cGUuY29uY2F0LmFwcGx5KFtdLGUpfW1TLmZsYXR0ZW49R09lO2Z1bmN0aW9uIFdPZShlLHQpe2Zvcih2YXIgcj1bXSxuPTA7bjx0O24rKylyW25dPXR5cGVvZiBlPT0iZnVuY3Rpb24iP2Uobik6ZTtyZXR1cm4gcn1tUy5jcmVhdGVGaWxsZWRBcnJheT1XT2V9KTt2YXIgbVh0PUgoWUE9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFlBLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgZFh0PShFcigpLFV0KE1yKSksWU9lPXdpbmRvdy5NYXRoO2Z1bmN0aW9uIGpPZShlLHQpe3ZhciByPXBYdChlKSsuMDUsbj1wWHQodCkrLjA1O3JldHVybiByPm4/ci9uOm4vcn1ZQS5jb250cmFzdD1qT2U7ZnVuY3Rpb24gWE9lKGUsdCl7dmFyIHI9ZFh0LmNvbG9yKGUpLmJyaWdodGVyKHQpO3JldHVybiByLnJnYigpLnRvU3RyaW5nKCl9WUEubGlnaHRlbkNvbG9yPVhPZTtmdW5jdGlvbiAkT2UoZSx0KXtlLmNsYXNzZWQodCwhMCk7dmFyIHI9ZS5zdHlsZSgiYmFja2dyb3VuZC1jb2xvciIpO2lmKHI9PT0idHJhbnNwYXJlbnQiKXJldHVybiBudWxsO3ZhciBuPS9cKCguKylcKS8uZXhlYyhyKTtpZighbilyZXR1cm4gbnVsbDt2YXIgaT1uWzFdLnNwbGl0KCIsIikubWFwKGZ1bmN0aW9uKGEpe3ZhciBzPSthLGw9cy50b1N0cmluZygxNik7cmV0dXJuIHM8MTY/IjAiK2w6bH0pO2lmKGkubGVuZ3RoPT09NCYmaVszXT09PSIwMCIpcmV0dXJuIG51bGw7dmFyIG89IiMiK2kuam9pbigiIik7cmV0dXJuIGUuY2xhc3NlZCh0LCExKSxvfVlBLmNvbG9yVGVzdD0kT2U7ZnVuY3Rpb24gcFh0KGUpe3ZhciB0PWRYdC5yZ2IoZSkscj1mdW5jdGlvbihhKXtyZXR1cm4gYT1hLzI1NSxhPD0uMDM5Mjg/YS8xMi45MjpZT2UucG93KChhKy4wNTUpLzEuMDU1LDIuNCl9LG49cih0LnIpLGk9cih0LmcpLG89cih0LmIpO3JldHVybiAuMjEyNipuKy43MTUyKmkrLjA3MjIqb319KTt2YXIgQWl0PUgoYW89PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGFvLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgQ2l0PShFcigpLFV0KE1yKSksbWE9d2luZG93Lk1hdGg7ZnVuY3Rpb24gS09lKGUsdCl7Zm9yKHZhciByPXQ7ciE9bnVsbCYmciE9PWU7KXI9ci5wYXJlbnROb2RlO3JldHVybiByPT09ZX1hby5jb250YWlucz1LT2U7ZnVuY3Rpb24gX1h0KGUpe3ZhciB0O3RyeXt0PWUubm9kZSgpLmdldEJCb3goKX1jYXRjaChyKXt0PXt4OjAseTowLHdpZHRoOjAsaGVpZ2h0OjB9fXJldHVybiB0fWFvLmVsZW1lbnRCQm94PV9YdDtmdW5jdGlvbiBaT2UoZSl7aWYoZSBpbnN0YW5jZW9mIFNWR0VsZW1lbnQpcmV0dXJuIF9YdChDaXQuc2VsZWN0KGUpKTtpZihlIGluc3RhbmNlb2YgSFRNTEVsZW1lbnQpe3ZhciB0PWUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7cmV0dXJue3g6dC5sZWZ0LHk6dC50b3Asd2lkdGg6dC53aWR0aCxoZWlnaHQ6dC5oZWlnaHR9fWVsc2UgcmV0dXJue3g6MCx5OjAsd2lkdGg6MCxoZWlnaHQ6MH19YW8uZW50aXR5Qm91bmRzPVpPZTthby5TQ1JFRU5fUkVGUkVTSF9SQVRFX01JTExJU0VDT05EUz0xZTMvNjA7ZnVuY3Rpb24gSk9lKGUpe3dpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUhPW51bGw/d2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZShlKTpzZXRUaW1lb3V0KGUsYW8uU0NSRUVOX1JFRlJFU0hfUkFURV9NSUxMSVNFQ09ORFMpfWFvLnJlcXVlc3RBbmltYXRpb25GcmFtZVBvbHlmaWxsPUpPZTtmdW5jdGlvbiBRT2UoZSl7dmFyIHQ9ZSBpbnN0YW5jZW9mIENpdC5zZWxlY3Rpb24/ZS5ub2RlKCk6ZSxyPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHQpO3JldHVybiBrZihyLCJ3aWR0aCIpK2tmKHIsInBhZGRpbmctbGVmdCIpK2tmKHIsInBhZGRpbmctcmlnaHQiKStrZihyLCJib3JkZXItbGVmdC13aWR0aCIpK2tmKHIsImJvcmRlci1yaWdodC13aWR0aCIpfWFvLmVsZW1lbnRXaWR0aD1RT2U7ZnVuY3Rpb24gdDdlKGUpe3ZhciB0PWUgaW5zdGFuY2VvZiBDaXQuc2VsZWN0aW9uP2Uubm9kZSgpOmUscj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0KTtyZXR1cm4ga2YociwiaGVpZ2h0Iikra2YociwicGFkZGluZy10b3AiKStrZihyLCJwYWRkaW5nLWJvdHRvbSIpK2tmKHIsImJvcmRlci10b3Atd2lkdGgiKStrZihyLCJib3JkZXItYm90dG9tLXdpZHRoIil9YW8uZWxlbWVudEhlaWdodD10N2U7dmFyIExjPSJcXHMiLGpBPSIoPzpbLStdP1swLTldKlxcLj9bMC05XSspIix5WHQ9Iig/Oig/OiIrTGMrIissPyIrTGMrIiopfCg/OiwiK0xjKyIqKSkiLGU3ZT1uZXcgUmVnRXhwKCJ0cmFuc2xhdGUiK0xjKyIqXFwoIitMYysiKigiK2pBKyIpKD86Iit5WHQrIigiK2pBKyIpKT8iK0xjKyIqXFwpIikscjdlPW5ldyBSZWdFeHAoInJvdGF0ZSIrTGMrIipcXCgiK0xjKyIqKCIrakErIikiK0xjKyIqXFwpIiksbjdlPW5ldyBSZWdFeHAoInNjYWxlIitMYysiKlxcKCIrTGMrIiooIitqQSsiKSg/OiIreVh0KyIoIitqQSsiKSk/IitMYysiKlxcKSIpO2Z1bmN0aW9uIGk3ZShlKXt2YXIgdD1lN2UuZXhlYyhlLmF0dHIoInRyYW5zZm9ybSIpKTtpZih0IT1udWxsKXt2YXIgcj10WzFdLG49dFsyXSxpPW49PT12b2lkIDA/MDpuO3JldHVyblsrciwraV19ZWxzZSByZXR1cm5bMCwwXX1hby5nZXRUcmFuc2xhdGVWYWx1ZXM9aTdlO2Z1bmN0aW9uIG83ZShlKXt2YXIgdD1yN2UuZXhlYyhlLmF0dHIoInRyYW5zZm9ybSIpKTtpZih0IT1udWxsKXt2YXIgcj10WzFdO3JldHVybityfWVsc2UgcmV0dXJuIDB9YW8uZ2V0Um90YXRlPW83ZTtmdW5jdGlvbiBhN2UoZSl7dmFyIHQ9bjdlLmV4ZWMoZS5hdHRyKCJ0cmFuc2Zvcm0iKSk7aWYodCE9bnVsbCl7dmFyIHI9dFsxXSxuPXRbMl07cmV0dXJuWytyLG49PW51bGw/K3I6K25dfWVsc2UgcmV0dXJuWzAsMF19YW8uZ2V0U2NhbGVWYWx1ZXM9YTdlO2Z1bmN0aW9uIHM3ZShlLHQpe3JldHVybiEobWEuZmxvb3IoZS5yaWdodCk8PW1hLmNlaWwodC5sZWZ0KXx8bWEuY2VpbChlLmxlZnQpPj1tYS5mbG9vcih0LnJpZ2h0KXx8bWEuZmxvb3IoZS5ib3R0b20pPD1tYS5jZWlsKHQudG9wKXx8bWEuY2VpbChlLnRvcCk+PW1hLmZsb29yKHQuYm90dG9tKSl9YW8uY2xpZW50UmVjdHNPdmVybGFwPXM3ZTtmdW5jdGlvbiBsN2UoZSx0KXtyZXR1cm57bGVmdDplLmxlZnQtdCx0b3A6ZS50b3AtdCxyaWdodDplLnJpZ2h0K3QsYm90dG9tOmUuYm90dG9tK3Qsd2lkdGg6ZS53aWR0aCt0KjIsaGVpZ2h0OmUuaGVpZ2h0K3QqMn19YW8uZXhwYW5kUmVjdD1sN2U7ZnVuY3Rpb24gYzdlKGUsdCl7cmV0dXJuIG1hLmZsb29yKHQubGVmdCk8PW1hLmNlaWwoZS5sZWZ0KSYmbWEuZmxvb3IodC50b3ApPD1tYS5jZWlsKGUudG9wKSYmbWEuZmxvb3IoZS5yaWdodCk8PW1hLmNlaWwodC5yaWdodCkmJm1hLmZsb29yKGUuYm90dG9tKTw9bWEuY2VpbCh0LmJvdHRvbSl9YW8uY2xpZW50UmVjdEluc2lkZT1jN2U7ZnVuY3Rpb24gdTdlKGUsdCxyLG4pe249PT12b2lkIDAmJihuPS41KTt2YXIgaT1nWHQoZSksbz1nWHQodCk7cmV0dXJuIHIueCtyLndpZHRoPj1pLm1pbi1uJiZyLng8PWkubWF4K24mJnIueStyLmhlaWdodD49by5taW4tbiYmci55PD1vLm1heCtufWFvLmludGVyc2VjdHNCQm94PXU3ZTtmdW5jdGlvbiBnWHQoZSl7aWYodHlwZW9mIGU9PSJudW1iZXIiKXt2YXIgdD1lO3JldHVybnttaW46dCxtYXg6dH19dmFyIHI9ZTtpZihyIGluc3RhbmNlb2YgT2JqZWN0JiYibWluImluIHImJiJtYXgiaW4gcilyZXR1cm4gcjt0aHJvdyBuZXcgRXJyb3IoImlucHV0ICciK2UrIicgY2FuJ3QgYmUgcGFyc2VkIGFzIGFuIFJhbmdlIil9ZnVuY3Rpb24ga2YoZSx0KXt2YXIgcj1lLmdldFByb3BlcnR5VmFsdWUodCksbj1wYXJzZUZsb2F0KHIpO3JldHVybiBufHwwfWZ1bmN0aW9uIGg3ZShlKXtmb3IodmFyIHQ9W107ZSYmZSBpbnN0YW5jZW9mIEhUTUxFbGVtZW50Oyl0LnB1c2goZSksZT1lLnBhcmVudEVsZW1lbnQ7cmV0dXJuIHR9YW8uZ2V0SHRtbEVsZW1lbnRBbmNlc3RvcnM9aDdlO2Z1bmN0aW9uIGY3ZShlKXt2YXIgdD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShlLG51bGwpLHI9dC5nZXRQcm9wZXJ0eVZhbHVlKCItd2Via2l0LXRyYW5zZm9ybSIpfHx0LmdldFByb3BlcnR5VmFsdWUoIi1tb3otdHJhbnNmb3JtIil8fHQuZ2V0UHJvcGVydHlWYWx1ZSgiLW1zLXRyYW5zZm9ybSIpfHx0LmdldFByb3BlcnR5VmFsdWUoIi1vLXRyYW5zZm9ybSIpfHx0LmdldFByb3BlcnR5VmFsdWUoInRyYW5zZm9ybSIpO3JldHVybiBtN2Uocil9YW8uZ2V0RWxlbWVudFRyYW5zZm9ybT1mN2U7dmFyIHA3ZT0vXm1hdHJpeFwoKFteKV0rKVwpJC8sZDdlPS9bLCBdKy87ZnVuY3Rpb24gbTdlKGUpe2lmKGU9PW51bGx8fGU9PT0ibm9uZSIpcmV0dXJuIG51bGw7dmFyIHQ9ZS5tYXRjaChwN2UpO2lmKHQ9PW51bGx8fHQubGVuZ3RoPDIpcmV0dXJuIG51bGw7dmFyIHI9dFsxXS5zcGxpdChkN2UpLm1hcChmdW5jdGlvbihuKXtyZXR1cm4gcGFyc2VGbG9hdChuKX0pO3JldHVybiByLmxlbmd0aCE9Nj9udWxsOnJ9fSk7dmFyIFBpdD1IKHNvPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShzbywiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIHdGPShFcigpLFV0KE1yKSksdlh0PUFpdCgpLG1kPXdpbmRvdy5NYXRoLGc3ZT1bMSwwLDAsMSwwLDBdO2Z1bmN0aW9uIF83ZShlLHQscil7cmV0dXJuIG1kLm1pbih0LHIpPD1lJiZlPD1tZC5tYXgodCxyKX1zby5pblJhbmdlPV83ZTtmdW5jdGlvbiB5N2UoZSx0LHIpe3JldHVybiBtZC5taW4obWQubWF4KHQsZSkscil9c28uY2xhbXA9eTdlO2Z1bmN0aW9uIHY3ZShlLHQscil7dmFyIG49dHlwZW9mIHQ9PSJmdW5jdGlvbiI/dDpudWxsLGk9bj09bnVsbD90OnIsbz1uPT1udWxsP3dGLm1heChlKTp3Ri5tYXgoZSxuKTtyZXR1cm4gbyE9PXZvaWQgMD9vOml9c28ubWF4PXY3ZTtmdW5jdGlvbiB4N2UoZSx0LHIpe3ZhciBuPXR5cGVvZiB0PT0iZnVuY3Rpb24iP3Q6bnVsbCxpPW49PW51bGw/dDpyLG89bj09bnVsbD93Ri5taW4oZSk6d0YubWluKGUsbik7cmV0dXJuIG8hPT12b2lkIDA/bzppfXNvLm1pbj14N2U7ZnVuY3Rpb24gYjdlKGUpe3JldHVybiBlIT09ZX1zby5pc05hTj1iN2U7ZnVuY3Rpb24gdzdlKGUpe3JldHVybiB0eXBlb2YgZT09Im51bWJlciImJmUtZTwxfXNvLmlzVmFsaWROdW1iZXI9dzdlO2Z1bmN0aW9uIFM3ZShlLHQscil7aWYocj09PXZvaWQgMCYmKHI9MSkscj09PTApdGhyb3cgbmV3IEVycm9yKCJzdGVwIGNhbm5vdCBiZSAwIik7Zm9yKHZhciBuPW1kLm1heChtZC5jZWlsKCh0LWUpL3IpLDApLGk9W10sbz0wO288bjsrK28paVtvXT1lK3IqbztyZXR1cm4gaX1zby5yYW5nZT1TN2U7ZnVuY3Rpb24gTTdlKGUsdCl7cmV0dXJuIG1kLnBvdyh0LnktZS55LDIpK21kLnBvdyh0LngtZS54LDIpfXNvLmRpc3RhbmNlU3F1YXJlZD1NN2U7ZnVuY3Rpb24gRTdlKGUpe3JldHVybiBlLzM2MCptZC5QSSoyfXNvLmRlZ3JlZXNUb1JhZGlhbnM9RTdlO2Z1bmN0aW9uIFQ3ZShlLHQpe3JldHVybiB0LnRvcExlZnQueDw9ZS54JiZ0LmJvdHRvbVJpZ2h0Lng+PWUueCYmdC50b3BMZWZ0Lnk8PWUueSYmdC5ib3R0b21SaWdodC55Pj1lLnl9c28ud2l0aGluPVQ3ZTtmdW5jdGlvbiBDN2UoZSx0LHIsbixpLG8sYSxzKXtyZXR1cm4gZTw9aSthJiZpPD1lK3ImJnQ8PW8rcyYmbzw9dCtufXNvLmJvdW5kc0ludGVyc2VjdHM9QzdlO2Z1bmN0aW9uIEE3ZShlKXtmb3IodmFyIHQ9dlh0LmdldEh0bWxFbGVtZW50QW5jZXN0b3JzKGUpLHI9ZzdlLG49bnVsbCxpPTAsbz10O2k8by5sZW5ndGg7aSsrKXt2YXIgYT1vW2ldLHM9dlh0LmdldEVsZW1lbnRUcmFuc2Zvcm0oYSk7aWYocyE9bnVsbCl7dmFyIGw9YS5jbGllbnRXaWR0aC8yLGM9YS5jbGllbnRIZWlnaHQvMjtyPWJGKHIsW2wsY10pLHI9eFh0KHIsYlh0KHMpKSxyPWJGKHIsWy1sLC1jXSl9dmFyIHU9YS5zY3JvbGxMZWZ0LGg9YS5zY3JvbGxUb3A7KG49PT1udWxsfHxhPT09bikmJih1LT1hLm9mZnNldExlZnQrYS5jbGllbnRMZWZ0LGgtPWEub2Zmc2V0VG9wK2EuY2xpZW50VG9wLG49YS5vZmZzZXRQYXJlbnQpLHI9YkYocixbdSxoXSl9cmV0dXJuIHJ9c28uZ2V0Q3VtdWxhdGl2ZVRyYW5zZm9ybT1BN2U7ZnVuY3Rpb24geFh0KGUsdCl7cmV0dXJuW2VbMF0qdFswXStlWzJdKnRbMV0sZVsxXSp0WzBdK2VbM10qdFsxXSxlWzBdKnRbMl0rZVsyXSp0WzNdLGVbMV0qdFsyXStlWzNdKnRbM10sZVswXSp0WzRdK2VbMl0qdFs1XStlWzRdLGVbMV0qdFs0XStlWzNdKnRbNV0rZVs1XV19c28ubXVsdGlwbHlNYXRyaXg9eFh0O2Z1bmN0aW9uIFA3ZShlLHQpe3JldHVyblt0WzBdLHRbMV0sdFsyXSx0WzNdLHRbNF0rZVswXSx0WzVdK2VbMV1dfXNvLnByZW11bHRpcGx5VHJhbnNsYXRlPVA3ZTtmdW5jdGlvbiBiRihlLHQpe3JldHVybltlWzBdLGVbMV0sZVsyXSxlWzNdLGVbMF0qdFswXStlWzJdKnRbMV0rZVs0XSxlWzFdKnRbMF0rZVszXSp0WzFdK2VbNV1dfXNvLm11bHRpcGx5VHJhbnNsYXRlPWJGO2Z1bmN0aW9uIGJYdChlKXt2YXIgdD1lWzBdKmVbM10tZVsxXSplWzJdO2lmKHQ9PT0wKXRocm93IG5ldyBFcnJvcigic2luZ3VsYXIgbWF0cml4Iik7dmFyIHI9MS90O3JldHVybltyKmVbM10sciotZVsxXSxyKi1lWzJdLHIqZVswXSxyKigtZVszXSplWzRdK2VbMl0qZVs1XSksciooZVsxXSplWzRdKy1lWzBdKmVbNV0pXX1zby5pbnZlcnRNYXRyaXg9Ylh0O2Z1bmN0aW9uIEk3ZShlLHQpe3JldHVybnt4OmVbMF0qdC54K2VbMl0qdC55K2VbNF0seTplWzFdKnQueCtlWzNdKnQueStlWzVdfX1zby5hcHBseVRyYW5zZm9ybT1JN2V9KTt2YXIgd1h0PUgoU0Y9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFNGLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgTDdlPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe31yZXR1cm4gZS5wcm90b3R5cGUuc3BsaXQ9ZnVuY3Rpb24odCxyKXtmb3IodmFyIG49TWF0aC5jZWlsKHQubGVuZ3RoLzIpLGk9MDtpPG47aSsrKXJbMF0uaW5zZXJ0KHRbaV0pO2Zvcih2YXIgaT1uO2k8dC5sZW5ndGg7aSsrKXJbMV0uaW5zZXJ0KHRbaV0pfSxlfSgpO1NGLlNwbGl0U3RyYXRlZ3lUcml2aWFsPUw3ZTt2YXIgazdlPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe31yZXR1cm4gZS5wcm90b3R5cGUuc3BsaXQ9ZnVuY3Rpb24odCxyKXtmb3IodD10LnNsaWNlKCksdGhpcy5jaG9vc2VGaXJzdFNwbGl0KHQscik7dC5sZW5ndGg+MDspdGhpcy5hZGROZXh0KHQscil9LGUucHJvdG90eXBlLmNob29zZUZpcnN0U3BsaXQ9ZnVuY3Rpb24odCxyKXtmb3IodmFyIG49MCxpPTAsbz10Lmxlbmd0aC0xLGE9dC5sZW5ndGgtMSxzPTE7czx0Lmxlbmd0aC0xO3MrKyl7dmFyIGw9dFtzXTtsLmJvdW5kcy54bD50W29dLmJvdW5kcy54bD9vPXM6bC5ib3VuZHMueGg8dFtuXS5ib3VuZHMueGgmJihuPXMpLGwuYm91bmRzLnlsPnRbYV0uYm91bmRzLnlsP2E9czpsLmJvdW5kcy55aDx0W2ldLmJvdW5kcy55aCYmKGk9cyl9dmFyIGM9TWF0aC5hYnModFtuXS5ib3VuZHMueGgtdFtvXS5ib3VuZHMueGwpLHU9TWF0aC5hYnModFtpXS5ib3VuZHMueWgtdFthXS5ib3VuZHMueWwpLGg9Yz51P1tuLG9dOltpLGFdLGY9aFswXSxwPWhbMV07Zj09PXAmJihmPTAscD10Lmxlbmd0aC0xKSxyWzBdLmluc2VydCh0LnNwbGljZShNYXRoLm1heChmLHApLDEpWzBdKSxyWzFdLmluc2VydCh0LnNwbGljZShNYXRoLm1pbihmLHApLDEpWzBdKX0sZS5wcm90b3R5cGUuYWRkTmV4dD1mdW5jdGlvbih0LHIpe2Zvcih2YXIgbj1udWxsLGk9bnVsbCxvPW51bGwsYT0wO2E8dC5sZW5ndGg7YSsrKXt2YXIgcz10W2FdLGw9clswXS51bmlvbkFyZWFEaWZmZXJlbmNlKHMuYm91bmRzKSxjPXJbMV0udW5pb25BcmVhRGlmZmVyZW5jZShzLmJvdW5kcyk7KGw8aXx8bj09bnVsbCkmJihuPWEsaT1sLG89clswXSksYzxpJiYobj1hLGk9YyxvPXJbMV0pfW8uaW5zZXJ0KHQuc3BsaWNlKG4sMSlbMF0pfSxlfSgpO1NGLlNwbGl0U3RyYXRlZ3lMaW5lYXI9azdlfSk7dmFyIExpdD1IKGdkPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShnZCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFI3ZT13WHQoKSxON2U9NSxEN2U9bmV3IFI3ZS5TcGxpdFN0cmF0ZWd5TGluZWFyLFJmOyhmdW5jdGlvbihlKXtlW2UuUEFTUz0wXT0iUEFTUyIsZVtlLkZBSUw9MV09IkZBSUwiLGVbZS5QQVNTX0FORF9PVkVSV1JJVEU9Ml09IlBBU1NfQU5EX09WRVJXUklURSJ9KShSZj1nZC5RdWVyeVByZWRpY2F0ZVJlc3VsdHx8KGdkLlF1ZXJ5UHJlZGljYXRlUmVzdWx0PXt9KSk7ZnVuY3Rpb24gTUYoZSx0LHIpe3ZhciBuPTEvMCxpPTEvMCxvPTEvMDtyZXR1cm4gZnVuY3Rpb24oYSl7dmFyIHM9dChhLmJvdW5kcyxlKSxsPXIoYS5ib3VuZHMsZSk7cmV0dXJuIGEudmFsdWUhPW51bGw/czxuPyhuPXMsaT1zLG89bCxSZi5QQVNTX0FORF9PVkVSV1JJVEUpOnM9PT1uP1JmLlBBU1M6UmYuRkFJTDpzPm8/UmYuRkFJTDooaT1NYXRoLm1pbihzLGkpLG89TWF0aC5tYXgobCxvKSxSZi5QQVNTKX19Z2QuY3JlYXRlTWluaW1pemluZ05vZGVQcmVkaWNhdGU9TUY7ZnVuY3Rpb24gSWl0KGUsdCl7cmV0dXJuIGZ1bmN0aW9uKHIsbil7cmV0dXJuIHQobi5ib3VuZHMsZSktdChyLmJvdW5kcyxlKX19Z2QuY3JlYXRlTm9kZVNvcnQ9SWl0O3ZhciBPN2U9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKHQscil7dD09PXZvaWQgMCYmKHQ9TjdlKSxyPT09dm9pZCAwJiYocj1EN2UpLHRoaXMubWF4Tm9kZUNoaWxkcmVuPXQsdGhpcy5zcGxpdFN0cmF0ZWd5PXIsdGhpcy5yb290PW5ldyBFRighMCksdGhpcy5zaXplPTB9cmV0dXJuIGUucHJvdG90eXBlLmdldFJvb3Q9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5yb290fSxlLnByb3RvdHlwZS5jbGVhcj1mdW5jdGlvbigpe3RoaXMucm9vdD1uZXcgRUYoITApLHRoaXMuc2l6ZT0wfSxlLnByb3RvdHlwZS5pbnNlcnQ9ZnVuY3Rpb24odCxyKXtmb3IodmFyIG49dGhpcy5yb290OyFuLmxlYWY7KW49bi5zdWJ0cmVlKHQpO3ZhciBpPUVGLnZhbHVlTm9kZSh0LHIpO2ZvcihuLmluc2VydChpKSx0aGlzLnNpemUrPTE7bi5vdmVyZmxvdyh0aGlzLm1heE5vZGVDaGlsZHJlbik7KW49bi5zcGxpdCh0aGlzLnNwbGl0U3RyYXRlZ3kpLG4ucGFyZW50PT1udWxsJiYodGhpcy5yb290PW4pO3JldHVybiBpfSxlLnByb3RvdHlwZS5sb2NhdGU9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMucXVlcnkoZnVuY3Rpb24ocil7cmV0dXJuIHIuY29udGFpbnModCl9KX0sZS5wcm90b3R5cGUubG9jYXRlTmVhcmVzdD1mdW5jdGlvbih0KXt2YXIgcj1NRih0LFFhLmRpc3RhbmNlU3F1YXJlZFRvTmVhckVkZ2UsUWEuZGlzdGFuY2VTcXVhcmVkVG9GYXJFZGdlKSxuPXRoaXMucXVlcnlOb2RlcyhyKTtyZXR1cm4gbi5tYXAoZnVuY3Rpb24oaSl7cmV0dXJuIGkudmFsdWV9KX0sZS5wcm90b3R5cGUubG9jYXRlTmVhcmVzdFg9ZnVuY3Rpb24odCl7dmFyIHI9TUYodCxRYS5hYnNvbHV0ZURpc3RhbmNlVG9OZWFyRWRnZVgsUWEuYWJzb2x1dGVEaXN0YW5jZVRvRmFyRWRnZVgpLG49dGhpcy5xdWVyeU5vZGVzKHIpO3JldHVybiBuLnNvcnQoSWl0KHQsUWEuYWJzb2x1dGVEaXN0YW5jZVRvTmVhckVkZ2VZKSksbi5tYXAoZnVuY3Rpb24oaSl7cmV0dXJuIGkudmFsdWV9KX0sZS5wcm90b3R5cGUubG9jYXRlTmVhcmVzdFk9ZnVuY3Rpb24odCl7dmFyIHI9TUYodCxRYS5hYnNvbHV0ZURpc3RhbmNlVG9OZWFyRWRnZVksUWEuYWJzb2x1dGVEaXN0YW5jZVRvRmFyRWRnZVkpLG49dGhpcy5xdWVyeU5vZGVzKHIpO3JldHVybiBuLnNvcnQoSWl0KHQsUWEuYWJzb2x1dGVEaXN0YW5jZVRvTmVhckVkZ2VYKSksbi5tYXAoZnVuY3Rpb24oaSl7cmV0dXJuIGkudmFsdWV9KX0sZS5wcm90b3R5cGUuaW50ZXJzZWN0PWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLnF1ZXJ5KGZ1bmN0aW9uKHIpe3JldHVybiBRYS5pc0JvdW5kc092ZXJsYXBCb3VuZHMocix0KX0pfSxlLnByb3RvdHlwZS5pbnRlcnNlY3RYPWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLnF1ZXJ5KGZ1bmN0aW9uKHIpe3JldHVybiBRYS5pc0JvdW5kc092ZXJsYXBYKHIsdCl9KX0sZS5wcm90b3R5cGUuaW50ZXJzZWN0WT1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5xdWVyeShmdW5jdGlvbihyKXtyZXR1cm4gUWEuaXNCb3VuZHNPdmVybGFwWShyLHQpfSl9LGUucHJvdG90eXBlLnF1ZXJ5PWZ1bmN0aW9uKHQpe3ZhciByPVtdO2lmKHRoaXMucm9vdC5ib3VuZHMhPW51bGwmJiF0KHRoaXMucm9vdC5ib3VuZHMpKXJldHVybiByO2Zvcih2YXIgbj1bdGhpcy5yb290XTtuLmxlbmd0aD4wOylmb3IodmFyIGk9bi5zaGlmdCgpLG89MDtvPGkuZW50cmllcy5sZW5ndGg7bysrKXt2YXIgYT1pLmVudHJpZXNbb107dChhLmJvdW5kcykmJihpLmxlYWY/ci5wdXNoKGEudmFsdWUpOm4ucHVzaChhKSl9cmV0dXJuIHJ9LGUucHJvdG90eXBlLnF1ZXJ5Tm9kZXM9ZnVuY3Rpb24odCl7dmFyIHI9W107aWYodGhpcy5yb290LmJvdW5kcyE9bnVsbCYmdCh0aGlzLnJvb3QpPT09UmYuRkFJTClyZXR1cm4gcjtmb3IodmFyIG49W3RoaXMucm9vdF07bi5sZW5ndGg+MDspZm9yKHZhciBpPW4uc2hpZnQoKSxvPTA7bzxpLmVudHJpZXMubGVuZ3RoO28rKyl7dmFyIGE9aS5lbnRyaWVzW29dLHM9dChhKTtzPT09UmYuUEFTU19BTkRfT1ZFUldSSVRFJiYocj1bXSksKHM9PT1SZi5QQVNTfHxzPT09UmYuUEFTU19BTkRfT1ZFUldSSVRFKSYmKGkubGVhZj9yLnB1c2goYSk6bi5wdXNoKGEpKX1yZXR1cm4gcn0sZX0oKTtnZC5SVHJlZT1PN2U7dmFyIEVGPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSh0KXt0aGlzLmxlYWY9dCx0aGlzLmJvdW5kcz1udWxsLHRoaXMuZW50cmllcz1bXSx0aGlzLnBhcmVudD1udWxsLHRoaXMudmFsdWU9bnVsbH1yZXR1cm4gZS52YWx1ZU5vZGU9ZnVuY3Rpb24odCxyKXt2YXIgbj1uZXcgZSghMCk7cmV0dXJuIG4uYm91bmRzPXQsbi52YWx1ZT1yLG59LGUucHJvdG90eXBlLm92ZXJmbG93PWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLmVudHJpZXMubGVuZ3RoPnR9LGUucHJvdG90eXBlLmluc2VydD1mdW5jdGlvbih0KXt0aGlzLmVudHJpZXMucHVzaCh0KSx0LnBhcmVudD10aGlzO2Zvcih2YXIgcj10aGlzO3IhPW51bGw7KXIuYm91bmRzPVFhLnVuaW9uQWxsKFtyLmJvdW5kcyx0LmJvdW5kc10pLHI9ci5wYXJlbnQ7cmV0dXJuIHRoaXN9LGUucHJvdG90eXBlLnJlbW92ZT1mdW5jdGlvbih0KXt2YXIgcj10aGlzLmVudHJpZXMuaW5kZXhPZih0KTtpZihyPj0wKXt0aGlzLmVudHJpZXMuc3BsaWNlKHIsMSk7Zm9yKHZhciBuPXRoaXM7biE9bnVsbDspbi5ib3VuZHM9UWEudW5pb25BbGwobi5lbnRyaWVzLm1hcChmdW5jdGlvbihpKXtyZXR1cm4gaS5ib3VuZHN9KSksbj1uLnBhcmVudH1yZXR1cm4gdGhpc30sZS5wcm90b3R5cGUuc3VidHJlZT1mdW5jdGlvbih0KXtmb3IodmFyIHI9MS8wLG49bnVsbCxpPTA7aTx0aGlzLmVudHJpZXMubGVuZ3RoO2krKyl7dmFyIG89dGhpcy5lbnRyaWVzW2ldLGE9by51bmlvbkFyZWFEaWZmZXJlbmNlKHQpOyhhPHJ8fGE9PT1yJiZuIT1udWxsJiZvLmVudHJpZXMubGVuZ3RoPG4uZW50cmllcy5sZW5ndGgpJiYobj1vKX1yZXR1cm4gbn0sZS5wcm90b3R5cGUuc3BsaXQ9ZnVuY3Rpb24odCl7dGhpcy5wYXJlbnQhPW51bGwmJnRoaXMucGFyZW50LnJlbW92ZSh0aGlzKTt2YXIgcj1bbmV3IGUodGhpcy5sZWFmKSxuZXcgZSh0aGlzLmxlYWYpXTt0LnNwbGl0KHRoaXMuZW50cmllcyxyKTt2YXIgbj10aGlzLnBhcmVudCE9bnVsbD90aGlzLnBhcmVudDpuZXcgZSghMSk7cmV0dXJuIG4uaW5zZXJ0KHJbMF0pLG4uaW5zZXJ0KHJbMV0pLG4ubGVhZj0hMSxufSxlLnByb3RvdHlwZS51bmlvbkFyZWFEaWZmZXJlbmNlPWZ1bmN0aW9uKHQpe3JldHVybiBNYXRoLmFicyhRYS51bmlvbih0aGlzLmJvdW5kcyx0KS5hcmVhKCktdGhpcy5ib3VuZHMuYXJlYSgpKX0sZS5wcm90b3R5cGUubWF4RGVwdGg9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5sZWFmPzE6MSt0aGlzLmVudHJpZXMubWFwKGZ1bmN0aW9uKHQpe3JldHVybiB0Lm1heERlcHRoKCl9KS5yZWR1Y2UoZnVuY3Rpb24odCxyKXtyZXR1cm4gTWF0aC5tYXgodCxyKX0pfSxlfSgpO2dkLlJUcmVlTm9kZT1FRjt2YXIgUWE9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKHQscixuLGkpe3RoaXMueGw9dCx0aGlzLnlsPXIsdGhpcy54aD1uLHRoaXMueWg9aSx0aGlzLndpZHRoPXRoaXMueGgtdGhpcy54bCx0aGlzLmhlaWdodD10aGlzLnloLXRoaXMueWx9cmV0dXJuIGUueHl3aD1mdW5jdGlvbih0LHIsbixpKXtyZXR1cm4gbmV3IGUodCxyLHQrbixyK2kpfSxlLmVudGl0eUJvdW5kcz1mdW5jdGlvbih0KXtyZXR1cm4gbmV3IGUodC54LHQueSx0LngrdC53aWR0aCx0LnkrdC5oZWlnaHQpfSxlLmJvdW5kcz1mdW5jdGlvbih0KXtyZXR1cm4gZS5wb2ludFBhaXIodC50b3BMZWZ0LHQuYm90dG9tUmlnaHQpfSxlLnBvaW50UGFpcj1mdW5jdGlvbih0LHIpe3JldHVybiBuZXcgZShNYXRoLm1pbih0Lngsci54KSxNYXRoLm1pbih0Lnksci55KSxNYXRoLm1heCh0Lngsci54KSxNYXRoLm1heCh0Lnksci55KSl9LGUucG9pbnRzPWZ1bmN0aW9uKHQpe2lmKHQubGVuZ3RoPDIpdGhyb3cgbmV3IEVycm9yKCJuZWVkIGF0IGxlYXN0IDIgcG9pbnRzIHRvIGNyZWF0ZSBib3VuZHMiKTt2YXIgcj10Lm1hcChmdW5jdGlvbihpKXtyZXR1cm4gaS54fSksbj10Lm1hcChmdW5jdGlvbihpKXtyZXR1cm4gaS55fSk7cmV0dXJuIG5ldyBlKHIucmVkdWNlKGZ1bmN0aW9uKGksbyl7cmV0dXJuIE1hdGgubWluKGksbyl9KSxuLnJlZHVjZShmdW5jdGlvbihpLG8pe3JldHVybiBNYXRoLm1pbihpLG8pfSksci5yZWR1Y2UoZnVuY3Rpb24oaSxvKXtyZXR1cm4gTWF0aC5tYXgoaSxvKX0pLG4ucmVkdWNlKGZ1bmN0aW9uKGksbyl7cmV0dXJuIE1hdGgubWF4KGksbyl9KSl9LGUudW5pb249ZnVuY3Rpb24odCxyKXtyZXR1cm4gbmV3IGUoTWF0aC5taW4odC54bCxyLnhsKSxNYXRoLm1pbih0LnlsLHIueWwpLE1hdGgubWF4KHQueGgsci54aCksTWF0aC5tYXgodC55aCxyLnloKSl9LGUudW5pb25BbGw9ZnVuY3Rpb24odCl7cmV0dXJuIHQ9dC5maWx0ZXIoZnVuY3Rpb24ocil7cmV0dXJuIHIhPW51bGx9KSx0Lmxlbmd0aD09PTA/bnVsbDp0LnJlZHVjZShmdW5jdGlvbihyLG4pe3JldHVybiBlLnVuaW9uKHIsbil9KX0sZS5pc0JvdW5kc092ZXJsYXBCb3VuZHM9ZnVuY3Rpb24odCxyKXtyZXR1cm4gZS5pc0JvdW5kc092ZXJsYXBYKHQscikmJmUuaXNCb3VuZHNPdmVybGFwWSh0LHIpfSxlLmlzQm91bmRzT3ZlcmxhcFg9ZnVuY3Rpb24odCxyKXtyZXR1cm4hKHQueGg8ci54bCkmJiEodC54bD5yLnhoKX0sZS5pc0JvdW5kc092ZXJsYXBZPWZ1bmN0aW9uKHQscil7cmV0dXJuISh0LnloPHIueWwpJiYhKHQueWw+ci55aCl9LGUuYWJzb2x1dGVEaXN0YW5jZVRvTmVhckVkZ2VYPWZ1bmN0aW9uKHQscil7dmFyIG49dC53aWR0aC8yLGk9dC54bCtuO3JldHVybiBNYXRoLm1heChNYXRoLmFicyhyLngtaSktbiwwKX0sZS5hYnNvbHV0ZURpc3RhbmNlVG9OZWFyRWRnZVk9ZnVuY3Rpb24odCxyKXt2YXIgbj10LmhlaWdodC8yLGk9dC55bCtuO3JldHVybiBNYXRoLm1heChNYXRoLmFicyhyLnktaSktbiwwKX0sZS5hYnNvbHV0ZURpc3RhbmNlVG9GYXJFZGdlWD1mdW5jdGlvbih0LHIpe3ZhciBuPWUuYWJzb2x1dGVEaXN0YW5jZVRvTmVhckVkZ2VYKHQscik7cmV0dXJuIG49PT0wPzA6bit0LndpZHRofSxlLmFic29sdXRlRGlzdGFuY2VUb0ZhckVkZ2VZPWZ1bmN0aW9uKHQscil7dmFyIG49ZS5hYnNvbHV0ZURpc3RhbmNlVG9OZWFyRWRnZVkodCxyKTtyZXR1cm4gbj09PTA/MDpuK3QuaGVpZ2h0fSxlLmRpc3RhbmNlU3F1YXJlZFRvTmVhckVkZ2U9ZnVuY3Rpb24odCxyKXt2YXIgbj1lLmFic29sdXRlRGlzdGFuY2VUb05lYXJFZGdlWCh0LHIpLGk9ZS5hYnNvbHV0ZURpc3RhbmNlVG9OZWFyRWRnZVkodCxyKTtyZXR1cm4gbipuK2kqaX0sZS5kaXN0YW5jZVNxdWFyZWRUb0ZhckVkZ2U9ZnVuY3Rpb24odCxyKXt2YXIgbj1lLmFic29sdXRlRGlzdGFuY2VUb0ZhckVkZ2VYKHQsciksaT1lLmFic29sdXRlRGlzdGFuY2VUb0ZhckVkZ2VZKHQscik7cmV0dXJuIG4qbitpKml9LGUucHJvdG90eXBlLmFyZWE9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5hcmVhQ2FjaGVkPT1udWxsJiYodGhpcy5hcmVhQ2FjaGVkPSh0aGlzLnhoLXRoaXMueGwpKih0aGlzLnloLXRoaXMueWwpKSx0aGlzLmFyZWFDYWNoZWR9LGUucHJvdG90eXBlLmNvbnRhaW5zPWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLnhsPD10LngmJnRoaXMueGg+PXQueCYmdGhpcy55bDw9dC55JiZ0aGlzLnloPj10Lnl9LGV9KCk7Z2QuUlRyZWVCb3VuZHM9UWF9KTt2YXIgRVh0PUgoX2Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KF9kLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgU1h0PShFcigpLFV0KE1yKSksejdlPU9lKCksWGc9RmUoKSxGN2U9SWYoKTtfZC5JU3RhY2tpbmdPcmRlcj1GN2UubWFrZUVudW0oWyJ0b3Bkb3duIiwiYm90dG9tdXAiXSk7dmFyIE1YdD13aW5kb3cuTWF0aDtmdW5jdGlvbiBCN2UoZSx0LHIsbil7bj09PXZvaWQgMCYmKG49ImJvdHRvbXVwIik7dmFyIGk9U1h0Lm1hcCgpLG89U1h0Lm1hcCgpLGE9bmV3IFhnLk1hcDtuPT09InRvcGRvd24iJiYoZT1lLnNsaWNlKCksZS5yZXZlcnNlKCkpO2Zvcih2YXIgcz0wLGw9ZTtzPGwubGVuZ3RoO3MrKyl7Zm9yKHZhciBjPWxbc10sdT1uZXcgWGcuTWFwLGg9Yy5kYXRhKCksZj1oLmxlbmd0aCxwPTA7cDxmO3ArKyl7dmFyIGQ9aFtwXSxnPXQoZCxwLGMpLF89X2Qubm9ybWFsaXplS2V5KGcpLHk9K3IoZCxwLGMpLHg9dm9pZCAwLGI9eT49MD9pOm87Yi5oYXMoXyk/KHg9Yi5nZXQoXyksYi5zZXQoXyx4K3kpKTooeD0wLGIuc2V0KF8seSkpLHUuc2V0KF8se29mZnNldDp4LHZhbHVlOnksYXhpc1ZhbHVlOmcsb3JpZ2luYWxEYXR1bTpkLG9yaWdpbmFsRGF0YXNldDpjLG9yaWdpbmFsSW5kZXg6cH0pfWEuc2V0KGMsdSl9cmV0dXJuIGF9X2Quc3RhY2s9QjdlO2Z1bmN0aW9uIEg3ZShlKXt2YXIgdD1uZXcgWGcuTWFwLHI9bmV3IFhnLk1hcDtyZXR1cm4gZS5mb3JFYWNoKGZ1bmN0aW9uKG4pe24uZm9yRWFjaChmdW5jdGlvbihpLG8pe3ZhciBhPWkub2Zmc2V0K2kudmFsdWUscz1YZy5NYXRoLm1heChbYSxpLm9mZnNldF0saS5vZmZzZXQpLGw9WGcuTWF0aC5taW4oW2EsaS5vZmZzZXRdLGkub2Zmc2V0KSxjPWkuYXhpc1ZhbHVlO3QuaGFzKG8pP3QuZ2V0KG8pLmV4dGVudDxzJiZ0LnNldChvLHtleHRlbnQ6cyxheGlzVmFsdWU6YyxzdGFja2VkRGF0dW06aX0pOnQuc2V0KG8se2V4dGVudDpzLGF4aXNWYWx1ZTpjLHN0YWNrZWREYXR1bTppfSksci5oYXMobyk/ci5nZXQobykuZXh0ZW50PmwmJnIuc2V0KG8se2V4dGVudDpsLGF4aXNWYWx1ZTpjLHN0YWNrZWREYXR1bTppfSk6ci5zZXQobyx7ZXh0ZW50OmwsYXhpc1ZhbHVlOmMsc3RhY2tlZERhdHVtOml9KX0pfSkse21heGltdW1FeHRlbnRzOnQsbWluaW11bUV4dGVudHM6cn19X2Quc3RhY2tlZEV4dGVudHM9SDdlO2Z1bmN0aW9uIFY3ZShlLHQscil7dmFyIG49W107ZS5mb3JFYWNoKGZ1bmN0aW9uKGEscyl7Zm9yKHZhciBsPXMuZGF0YSgpLGM9bC5sZW5ndGgsdT0wO3U8Yzt1Kyspe3ZhciBoPWxbdV07aWYoIShyIT1udWxsJiYhcihoLHUscykpKXt2YXIgZj1hLmdldChfZC5ub3JtYWxpemVLZXkodChoLHUscykpKTtuLnB1c2goZi52YWx1ZStmLm9mZnNldCl9fX0pO3ZhciBpPVhnLk1hdGgubWF4KG4sMCksbz1YZy5NYXRoLm1pbihuLDApO3JldHVybltNWHQubWluKG8sMCksTVh0Lm1heCgwLGkpXX1fZC5zdGFja2VkRXh0ZW50PVY3ZTtfZC5ub3JtYWxpemVLZXk9ejdlLm1lbW9pemUoZnVuY3Rpb24oZSl7cmV0dXJuIFN0cmluZyhlKX0pfSk7dmFyIFhBPUgoVEY9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFRGLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTtURi5TSE9XX1dBUk5JTkdTPSEwO1RGLkFERF9USVRMRV9FTEVNRU5UUz0hMH0pO3ZhciBDRj1IKGdTPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShnUywiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFU3ZT1YQSgpO2Z1bmN0aW9uIFRYdChlKXshVTdlLlNIT1dfV0FSTklOR1N8fGNvbnNvbGUud2FybihlKX1nUy53YXJuPVRYdDtmdW5jdGlvbiBDWHQoZSx0KXtmb3IodmFyIHI9W10sbj0yO248YXJndW1lbnRzLmxlbmd0aDtuKyspcltuLTJdPWFyZ3VtZW50c1tuXTtyZXR1cm4gdD09PTA/KGUociksLTEpOndpbmRvdy5zZXRUaW1lb3V0KGUsdCxyKX1nUy5zZXRUaW1lb3V0PUNYdDtmdW5jdGlvbiBxN2UoZSx0LHIpe3ZhciBuPW51bGwsaT1bXSxvPWZ1bmN0aW9uKCl7dC5hcHBseShyLGkpfTtyZXR1cm4gZnVuY3Rpb24oKXtpPUFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGFyZ3VtZW50cyksY2xlYXJUaW1lb3V0KG4pLG49Q1h0KG8sZSl9fWdTLmRlYm91bmNlPXE3ZTtmdW5jdGlvbiBHN2UoZSx0LHIpe3I9PT12b2lkIDAmJihyPSIiKSxUWHQoIk1ldGhvZCAiK2UrIiBoYXMgYmVlbiBkZXByZWNhdGVkIGluIHZlcnNpb24gIit0KyIuIFBsZWFzZSByZWZlciB0byB0aGUgcmVsZWFzZSBub3Rlcy4gIityKX1nUy5kZXByZWNhdGVkPUc3ZX0pO3ZhciBBWHQ9SChraXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGtpdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFc3ZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUodCxyLG4pe3RoaXMuZW50cnlJbmRleD10LHRoaXMuZXhpdEluZGV4PXQsdGhpcy5taW5JbmRleD10LHRoaXMubWF4SW5kZXg9dCx0aGlzLmJ1Y2tldFZhbHVlPXIsdGhpcy5taW5WYWx1ZT1uLHRoaXMubWF4VmFsdWU9bn1yZXR1cm4gZS5wcm90b3R5cGUuaXNJbkJ1Y2tldD1mdW5jdGlvbih0KXtyZXR1cm4gdD09dGhpcy5idWNrZXRWYWx1ZX0sZS5wcm90b3R5cGUuYWRkVG9CdWNrZXQ9ZnVuY3Rpb24odCxyKXt0PHRoaXMubWluVmFsdWUmJih0aGlzLm1pblZhbHVlPXQsdGhpcy5taW5JbmRleD1yKSx0PnRoaXMubWF4VmFsdWUmJih0aGlzLm1heFZhbHVlPXQsdGhpcy5tYXhJbmRleD1yKSx0aGlzLmV4aXRJbmRleD1yfSxlLnByb3RvdHlwZS5nZXRVbmlxdWVJbmRpY2VzPWZ1bmN0aW9uKCl7dmFyIHQ9W3RoaXMuZW50cnlJbmRleCx0aGlzLm1heEluZGV4LHRoaXMubWluSW5kZXgsdGhpcy5leGl0SW5kZXhdO3JldHVybiB0LmZpbHRlcihmdW5jdGlvbihyLG4pe3JldHVybiBuPT0wfHxyIT10W24tMV19KX0sZX0oKTtraXQuQnVja2V0PVc3ZX0pO3ZhciBOaXQ9SChSaXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFJpdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFk3ZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUoKXt0eXBlb2Ygd2luZG93LlNldD09ImZ1bmN0aW9uIj90aGlzLl9lczZTZXQ9bmV3IHdpbmRvdy5TZXQ6dGhpcy5fdmFsdWVzPVtdLHRoaXMuc2l6ZT0wfXJldHVybiBlLnByb3RvdHlwZS5hZGQ9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX2VzNlNldCE9bnVsbD8odGhpcy5fZXM2U2V0LmFkZCh0KSx0aGlzLnNpemU9dGhpcy5fZXM2U2V0LnNpemUsdGhpcyk6KHRoaXMuaGFzKHQpfHwodGhpcy5fdmFsdWVzLnB1c2godCksdGhpcy5zaXplPXRoaXMuX3ZhbHVlcy5sZW5ndGgpLHRoaXMpfSxlLnByb3RvdHlwZS5kZWxldGU9ZnVuY3Rpb24odCl7aWYodGhpcy5fZXM2U2V0IT1udWxsKXt2YXIgcj10aGlzLl9lczZTZXQuZGVsZXRlKHQpO3JldHVybiB0aGlzLnNpemU9dGhpcy5fZXM2U2V0LnNpemUscn12YXIgbj10aGlzLl92YWx1ZXMuaW5kZXhPZih0KTtyZXR1cm4gbiE9PS0xPyh0aGlzLl92YWx1ZXMuc3BsaWNlKG4sMSksdGhpcy5zaXplPXRoaXMuX3ZhbHVlcy5sZW5ndGgsITApOiExfSxlLnByb3RvdHlwZS5oYXM9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX2VzNlNldCE9bnVsbD90aGlzLl9lczZTZXQuaGFzKHQpOnRoaXMuX3ZhbHVlcy5pbmRleE9mKHQpIT09LTF9LGUucHJvdG90eXBlLmZvckVhY2g9ZnVuY3Rpb24odCxyKXt2YXIgbj10aGlzO2lmKHRoaXMuX2VzNlNldCE9bnVsbCl7dmFyIGk9ZnVuY3Rpb24obyxhKXtyZXR1cm4gdC5jYWxsKHIsbyxhLG4pfTt0aGlzLl9lczZTZXQuZm9yRWFjaChpLHIpO3JldHVybn10aGlzLl92YWx1ZXMuZm9yRWFjaChmdW5jdGlvbihvKXt0LmNhbGwocixvLG8sbil9KX0sZX0oKTtSaXQuU2V0PVk3ZX0pO3ZhciBQWHQ9SChEaXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KERpdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIGo3ZT0oZGUoKSxVdChwZSkpLFg3ZT1OaXQoKSwkN2U9ZnVuY3Rpb24oZSl7ajdlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXtyZXR1cm4gZSE9PW51bGwmJmUuYXBwbHkodGhpcyxhcmd1bWVudHMpfHx0aGlzfXJldHVybiB0LnByb3RvdHlwZS5jYWxsQ2FsbGJhY2tzPWZ1bmN0aW9uKCl7Zm9yKHZhciByPXRoaXMsbj1bXSxpPTA7aTxhcmd1bWVudHMubGVuZ3RoO2krKyluW2ldPWFyZ3VtZW50c1tpXTtyZXR1cm4gdGhpcy5mb3JFYWNoKGZ1bmN0aW9uKG8pe28uYXBwbHkocixuKX0pLHRoaXN9LHR9KFg3ZS5TZXQpO0RpdC5DYWxsYmFja1NldD0kN2V9KTt2YXIgSVh0PUgoT2l0PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShPaXQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciAkZz1MaXQoKSxLN2U9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKCl7dGhpcy5fZW50aXRpZXM9W10sdGhpcy5fcnRyZWU9bmV3ICRnLlJUcmVlfXJldHVybiBlLnByb3RvdHlwZS5hZGRBbGw9ZnVuY3Rpb24odCxyLG4pe2lmKHRoaXMuX2VudGl0aWVzPXRoaXMuX2VudGl0aWVzLmNvbmNhdCh0KSxuIT09dm9pZCAwKWZvcih2YXIgaT0kZy5SVHJlZUJvdW5kcy5ib3VuZHMobiksbz0wO288dC5sZW5ndGg7bysrKXt2YXIgYT10W29dLHM9JGcuUlRyZWVCb3VuZHMuZW50aXR5Qm91bmRzKHIoYSkpOyRnLlJUcmVlQm91bmRzLmlzQm91bmRzT3ZlcmxhcEJvdW5kcyhpLHMpJiZ0aGlzLl9ydHJlZS5pbnNlcnQocyxhKX1lbHNlIGZvcih2YXIgbz0wO288dC5sZW5ndGg7bysrKXt2YXIgYT10W29dLHM9JGcuUlRyZWVCb3VuZHMuZW50aXR5Qm91bmRzKHIoYSkpO3RoaXMuX3J0cmVlLmluc2VydChzLGEpfX0sZS5wcm90b3R5cGUuZW50aXR5TmVhcmVzdD1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5fcnRyZWUubG9jYXRlTmVhcmVzdCh0KS5wb3AoKX0sZS5wcm90b3R5cGUuZW50aXR5TmVhcmVzdFg9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX3J0cmVlLmxvY2F0ZU5lYXJlc3RYKHQpLnBvcCgpfSxlLnByb3RvdHlwZS5lbnRpdHlOZWFyZXN0WT1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5fcnRyZWUubG9jYXRlTmVhcmVzdFkodCkucG9wKCl9LGUucHJvdG90eXBlLmVudGl0aWVzSW5Cb3VuZHM9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX3J0cmVlLmludGVyc2VjdCgkZy5SVHJlZUJvdW5kcy5lbnRpdHlCb3VuZHModCkpfSxlLnByb3RvdHlwZS5lbnRpdGllc0luWEJvdW5kcz1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5fcnRyZWUuaW50ZXJzZWN0WCgkZy5SVHJlZUJvdW5kcy5lbnRpdHlCb3VuZHModCkpfSxlLnByb3RvdHlwZS5lbnRpdGllc0luWUJvdW5kcz1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5fcnRyZWUuaW50ZXJzZWN0WSgkZy5SVHJlZUJvdW5kcy5lbnRpdHlCb3VuZHModCkpfSxlLnByb3RvdHlwZS5lbnRpdGllcz1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9lbnRpdGllc30sZX0oKTtPaXQuRW50aXR5U3RvcmU9SzdlfSk7dmFyIExYdD1IKHppdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoeml0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgWjdlPVBpdCgpLEo3ZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUoKXt0eXBlb2Ygd2luZG93Lk1hcD09ImZ1bmN0aW9uIj90aGlzLl9lczZNYXA9bmV3IHdpbmRvdy5NYXA6dGhpcy5fa2V5VmFsdWVQYWlycz1bXX1yZXR1cm4gZS5wcm90b3R5cGUuc2V0PWZ1bmN0aW9uKHQscil7aWYoWjdlLmlzTmFOKHQpKXRocm93IG5ldyBFcnJvcigiTmFOIG1heSBub3QgYmUgdXNlZCBhcyBhIGtleSB0byB0aGUgTWFwIik7aWYodGhpcy5fZXM2TWFwIT1udWxsKXJldHVybiB0aGlzLl9lczZNYXAuc2V0KHQsciksdGhpcztmb3IodmFyIG49MDtuPHRoaXMuX2tleVZhbHVlUGFpcnMubGVuZ3RoO24rKylpZih0aGlzLl9rZXlWYWx1ZVBhaXJzW25dLmtleT09PXQpcmV0dXJuIHRoaXMuX2tleVZhbHVlUGFpcnNbbl0udmFsdWU9cix0aGlzO3JldHVybiB0aGlzLl9rZXlWYWx1ZVBhaXJzLnB1c2goe2tleTp0LHZhbHVlOnJ9KSx0aGlzfSxlLnByb3RvdHlwZS5nZXQ9ZnVuY3Rpb24odCl7aWYodGhpcy5fZXM2TWFwIT1udWxsKXJldHVybiB0aGlzLl9lczZNYXAuZ2V0KHQpO2Zvcih2YXIgcj0wO3I8dGhpcy5fa2V5VmFsdWVQYWlycy5sZW5ndGg7cisrKWlmKHRoaXMuX2tleVZhbHVlUGFpcnNbcl0ua2V5PT09dClyZXR1cm4gdGhpcy5fa2V5VmFsdWVQYWlyc1tyXS52YWx1ZX0sZS5wcm90b3R5cGUuaGFzPWZ1bmN0aW9uKHQpe2lmKHRoaXMuX2VzNk1hcCE9bnVsbClyZXR1cm4gdGhpcy5fZXM2TWFwLmhhcyh0KTtmb3IodmFyIHI9MDtyPHRoaXMuX2tleVZhbHVlUGFpcnMubGVuZ3RoO3IrKylpZih0aGlzLl9rZXlWYWx1ZVBhaXJzW3JdLmtleT09PXQpcmV0dXJuITA7cmV0dXJuITF9LGUucHJvdG90eXBlLmZvckVhY2g9ZnVuY3Rpb24odCxyKXt2YXIgbj10aGlzO2lmKHRoaXMuX2VzNk1hcCE9bnVsbCl7dmFyIGk9ZnVuY3Rpb24obyxhKXtyZXR1cm4gdC5jYWxsKHIsbyxhLG4pfTt0aGlzLl9lczZNYXAuZm9yRWFjaChpLHIpO3JldHVybn10aGlzLl9rZXlWYWx1ZVBhaXJzLmZvckVhY2goZnVuY3Rpb24obyl7dC5jYWxsKHIsby52YWx1ZSxvLmtleSxuKX0pfSxlLnByb3RvdHlwZS5kZWxldGU9ZnVuY3Rpb24odCl7aWYodGhpcy5fZXM2TWFwIT1udWxsKXJldHVybiB0aGlzLl9lczZNYXAuZGVsZXRlKHQpO2Zvcih2YXIgcj0wO3I8dGhpcy5fa2V5VmFsdWVQYWlycy5sZW5ndGg7cisrKWlmKHRoaXMuX2tleVZhbHVlUGFpcnNbcl0ua2V5PT09dClyZXR1cm4gdGhpcy5fa2V5VmFsdWVQYWlycy5zcGxpY2UociwxKSwhMDtyZXR1cm4hMX0sZX0oKTt6aXQuTWFwPUo3ZX0pO3ZhciBrWHQ9SChGaXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KEZpdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7ZnVuY3Rpb24gUTdlKCl7Zm9yKHZhciBlPVtdLHQ9MDt0PGFyZ3VtZW50cy5sZW5ndGg7dCsrKWVbdF09YXJndW1lbnRzW3RdO2Zvcih2YXIgcj17fSxuPTAsaT1lO248aS5sZW5ndGg7bisrKWZvcih2YXIgbz1pW25dLGE9T2JqZWN0LmtleXMobykscz0wLGw9YTtzPGwubGVuZ3RoO3MrKyl7dmFyIGM9bFtzXTtyW2NdPW9bY119cmV0dXJuIHJ9Rml0LmFzc2lnbj1RN2V9KTt2YXIgRFh0PUgoQUY9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KEFGLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgQml0PUZlKCksUlh0PSJfX1Bsb3R0YWJsZV9DbGllbnRUcmFuc2xhdG9yIjtmdW5jdGlvbiB0emUoZSl7dmFyIHQ9ZS5yb290KCkucm9vdEVsZW1lbnQoKS5ub2RlKCkscj10W1JYdF07cmV0dXJuIHI9PW51bGwmJihyPW5ldyBOWHQodCksdFtSWHRdPXIpLHJ9QUYuZ2V0VHJhbnNsYXRvcj10emU7dmFyIE5YdD1mdW5jdGlvbigpe2Z1bmN0aW9uIGUodCl7dGhpcy5fcm9vdEVsZW1lbnQ9dH1yZXR1cm4gZS5wcm90b3R5cGUuY29tcHV0ZVBvc2l0aW9uPWZ1bmN0aW9uKHQscil7dmFyIG49e3g6dCx5OnJ9LGk9Qml0Lk1hdGguZ2V0Q3VtdWxhdGl2ZVRyYW5zZm9ybSh0aGlzLl9yb290RWxlbWVudCk7aWYoaT09bnVsbClyZXR1cm4gbjt2YXIgbz1CaXQuTWF0aC5hcHBseVRyYW5zZm9ybShpLG4pO3JldHVybiBvfSxlLmlzRXZlbnRJbnNpZGU9ZnVuY3Rpb24odCxyKXtyZXR1cm4gQml0LkRPTS5jb250YWlucyh0LnJvb3QoKS5yb290RWxlbWVudCgpLm5vZGUoKSxyLnRhcmdldCl9LGV9KCk7QUYuVHJhbnNsYXRvcj1OWHR9KTt2YXIgRmU9SChxbz0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkocW8sIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBLZz0oZGUoKSxVdChwZSkpLGV6ZT1mWHQoKTtxby5BcnJheT1lemU7dmFyIHJ6ZT1tWHQoKTtxby5Db2xvcj1yemU7dmFyIG56ZT1BaXQoKTtxby5ET009bnplO3ZhciBpemU9UGl0KCk7cW8uTWF0aD1pemU7dmFyIG96ZT1MaXQoKTtxby5SVHJlZT1vemU7dmFyIGF6ZT1FWHQoKTtxby5TdGFja2luZz1hemU7dmFyIHN6ZT1DRigpO3FvLldpbmRvdz1zemU7S2cuX19leHBvcnRTdGFyKEFYdCgpLHFvKTtLZy5fX2V4cG9ydFN0YXIoUFh0KCkscW8pO0tnLl9fZXhwb3J0U3RhcihZZygpLHFvKTtLZy5fX2V4cG9ydFN0YXIoSVh0KCkscW8pO0tnLl9fZXhwb3J0U3RhcihMWHQoKSxxbyk7S2cuX19leHBvcnRTdGFyKGtYdCgpLHFvKTtLZy5fX2V4cG9ydFN0YXIoTml0KCkscW8pO0tnLl9fZXhwb3J0U3RhcihEWHQoKSxxbyl9KTt2YXIgVml0PUgoJEE9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KCRBLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgT1h0PUZlKCksSGl0PVBGKCksbHplPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe31yZXR1cm4gZS5wcm90b3R5cGUucmVuZGVyPWZ1bmN0aW9uKCl7SGl0LmZsdXNoKCl9LGV9KCk7JEEuSW1tZWRpYXRlPWx6ZTt2YXIgY3plPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe31yZXR1cm4gZS5wcm90b3R5cGUucmVuZGVyPWZ1bmN0aW9uKCl7T1h0LkRPTS5yZXF1ZXN0QW5pbWF0aW9uRnJhbWVQb2x5ZmlsbChIaXQuZmx1c2gpfSxlfSgpOyRBLkFuaW1hdGlvbkZyYW1lPWN6ZTt2YXIgdXplPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe3RoaXMuX3RpbWVvdXRNc2VjPU9YdC5ET00uU0NSRUVOX1JFRlJFU0hfUkFURV9NSUxMSVNFQ09ORFN9cmV0dXJuIGUucHJvdG90eXBlLnJlbmRlcj1mdW5jdGlvbigpe3NldFRpbWVvdXQoSGl0LmZsdXNoLHRoaXMuX3RpbWVvdXRNc2VjKX0sZX0oKTskQS5UaW1lb3V0PXV6ZX0pO3ZhciBQRj1IKHp1PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eSh6dSwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIF9TPUZlKCksaHplPUlmKCksSUY9Vml0KCksWkE9bmV3IF9TLlNldCxVaXQ9bmV3IF9TLlNldCxMRj0hMSxxaXQ9ITE7enUuUG9saWN5PWh6ZS5tYWtlRW51bShbImltbWVkaWF0ZSIsImFuaW1hdGlvbkZyYW1lIiwidGltZW91dCJdKTt2YXIgS0E9bmV3IElGLkFuaW1hdGlvbkZyYW1lO2Z1bmN0aW9uIGZ6ZShlKXtpZihlPT1udWxsKXJldHVybiBLQTtzd2l0Y2goZSl7Y2FzZSB6dS5Qb2xpY3kuaW1tZWRpYXRlOktBPW5ldyBJRi5JbW1lZGlhdGU7YnJlYWs7Y2FzZSB6dS5Qb2xpY3kuYW5pbWF0aW9uRnJhbWU6S0E9bmV3IElGLkFuaW1hdGlvbkZyYW1lO2JyZWFrO2Nhc2UgenUuUG9saWN5LnRpbWVvdXQ6S0E9bmV3IElGLlRpbWVvdXQ7YnJlYWs7ZGVmYXVsdDpfUy5XaW5kb3cud2FybigiVW5yZWNvZ25pemVkIHJlbmRlclBvbGljeTogIitlKX19enUucmVuZGVyUG9saWN5PWZ6ZTtmdW5jdGlvbiBwemUoZSl7cWl0JiZfUy5XaW5kb3cud2FybigiUmVnaXN0ZXJlZCB0byByZW5kZXIgd2hpbGUgb3RoZXIgY29tcG9uZW50cyBhcmUgZmx1c2hpbmc6IHJlcXVlc3QgbWF5IGJlIGlnbm9yZWQiKSxaQS5hZGQoZSksRlh0KCl9enUucmVnaXN0ZXJUb1JlbmRlcj1wemU7ZnVuY3Rpb24gelh0KGUpe1VpdC5hZGQoZSksWkEuYWRkKGUpLEZYdCgpfXp1LnJlZ2lzdGVyVG9Db21wdXRlTGF5b3V0QW5kUmVuZGVyPXpYdDtmdW5jdGlvbiBkemUoZSl7elh0KGUpfXp1LnJlZ2lzdGVyVG9Db21wdXRlTGF5b3V0PWR6ZTtmdW5jdGlvbiBGWHQoKXtMRnx8KExGPSEwLEtBLnJlbmRlcigpKX1mdW5jdGlvbiBtemUoKXtpZihMRil7VWl0LmZvckVhY2goZnVuY3Rpb24odCl7cmV0dXJuIHQuY29tcHV0ZUxheW91dCgpfSksWkEuZm9yRWFjaChmdW5jdGlvbih0KXtyZXR1cm4gdC5yZW5kZXIoKX0pLHFpdD0hMDt2YXIgZT1uZXcgX1MuU2V0O1pBLmZvckVhY2goZnVuY3Rpb24odCl7dHJ5e3QucmVuZGVySW1tZWRpYXRlbHkoKX1jYXRjaChyKXt3aW5kb3cuc2V0VGltZW91dChmdW5jdGlvbigpe3Rocm93IHJ9LDApLGUuYWRkKHQpfX0pLFVpdD1uZXcgX1MuU2V0LFpBPWUsTEY9ITEscWl0PSExfX16dS5mbHVzaD1temV9KTt2YXIga2M9SChKQT0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoSkEsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBCWHQ9KEVyKCksVXQoTXIpKSxrRj1QRigpLHlTPUZlKCksZ3plPU9lKCksSFh0PVlnKCksVlh0PUlmKCk7SkEuWEFsaWdubWVudD1WWHQubWFrZUVudW0oWyJsZWZ0IiwiY2VudGVyIiwicmlnaHQiXSk7SkEuWUFsaWdubWVudD1WWHQubWFrZUVudW0oWyJ0b3AiLCJjZW50ZXIiLCJib3R0b20iXSk7dmFyIF96ZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUoKXt0aGlzLl9vdmVyZmxvd0hpZGRlbj0hMSx0aGlzLl9vcmlnaW49e3g6MCx5OjB9LHRoaXMuX3hBbGlnbm1lbnQ9ImxlZnQiLHRoaXMuX3lBbGlnbm1lbnQ9InRvcCIsdGhpcy5faXNTZXR1cD0hMSx0aGlzLl9pc0FuY2hvcmVkPSExLHRoaXMuX2Nzc0NsYXNzZXM9bmV3IHlTLlNldCx0aGlzLl9kZXN0cm95ZWQ9ITEsdGhpcy5fb25BbmNob3JDYWxsYmFja3M9bmV3IHlTLkNhbGxiYWNrU2V0LHRoaXMuX29uRGV0YWNoQ2FsbGJhY2tzPW5ldyB5Uy5DYWxsYmFja1NldCx0aGlzLl9jc3NDbGFzc2VzLmFkZCgiY29tcG9uZW50Iil9cmV0dXJuIGUucHJvdG90eXBlLmFuY2hvcj1mdW5jdGlvbih0KXtpZih0PUhYdC5jb2VyY2VFeHRlcm5hbEQzKHQpLHRoaXMuX2Rlc3Ryb3llZCl0aHJvdyBuZXcgRXJyb3IoIkNhbid0IHJldXNlIGRlc3Ryb3koKS1lZCBDb21wb25lbnRzISIpO3JldHVybiB0aGlzLmlzUm9vdCgpJiYodGhpcy5fcm9vdEVsZW1lbnQ9dCx0aGlzLl9yb290RWxlbWVudC5jbGFzc2VkKCJwbG90dGFibGUiLCEwKSksdGhpcy5fZWxlbWVudCE9bnVsbD90Lm5vZGUoKS5hcHBlbmRDaGlsZCh0aGlzLl9lbGVtZW50Lm5vZGUoKSk6KHRoaXMuX2VsZW1lbnQ9dC5hcHBlbmQoImRpdiIpLHRoaXMuX3NldHVwKCkpLHRoaXMuX2lzQW5jaG9yZWQ9ITAsdGhpcy5fb25BbmNob3JDYWxsYmFja3MuY2FsbENhbGxiYWNrcyh0aGlzKSx0aGlzfSxlLnByb3RvdHlwZS5vbkFuY2hvcj1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5faXNBbmNob3JlZCYmdCh0aGlzKSx0aGlzLl9vbkFuY2hvckNhbGxiYWNrcy5hZGQodCksdGhpc30sZS5wcm90b3R5cGUub2ZmQW5jaG9yPWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLl9vbkFuY2hvckNhbGxiYWNrcy5kZWxldGUodCksdGhpc30sZS5wcm90b3R5cGUuX3NldHVwPWZ1bmN0aW9uKCl7dmFyIHQ9dGhpczt0aGlzLl9pc1NldHVwfHwodGhpcy5fY3NzQ2xhc3Nlcy5mb3JFYWNoKGZ1bmN0aW9uKHIpe3QuX2VsZW1lbnQuY2xhc3NlZChyLCEwKX0pLHRoaXMuX2Nzc0NsYXNzZXM9bmV3IHlTLlNldCx0aGlzLl9iYWNrZ3JvdW5kQ29udGFpbmVyPXRoaXMuX2VsZW1lbnQuYXBwZW5kKCJzdmciKS5jbGFzc2VkKCJiYWNrZ3JvdW5kLWNvbnRhaW5lciIsITApLHRoaXMuX2NvbnRlbnQ9dGhpcy5fZWxlbWVudC5hcHBlbmQoInN2ZyIpLmNsYXNzZWQoImNvbnRlbnQiLCEwKSx0aGlzLl9mb3JlZ3JvdW5kQ29udGFpbmVyPXRoaXMuX2VsZW1lbnQuYXBwZW5kKCJzdmciKS5jbGFzc2VkKCJmb3JlZ3JvdW5kLWNvbnRhaW5lciIsITApLHRoaXMuX292ZXJmbG93SGlkZGVuP3RoaXMuX2NvbnRlbnQuY2xhc3NlZCgiY29tcG9uZW50LW92ZXJmbG93LWhpZGRlbiIsITApOnRoaXMuX2NvbnRlbnQuY2xhc3NlZCgiY29tcG9uZW50LW92ZXJmbG93LXZpc2libGUiLCEwKSx0aGlzLl9pc1NldHVwPSEwKX0sZS5wcm90b3R5cGUucmVxdWVzdGVkU3BhY2U9ZnVuY3Rpb24odCxyKXtyZXR1cm57bWluV2lkdGg6MCxtaW5IZWlnaHQ6MH19LGUucHJvdG90eXBlLmNvbXB1dGVMYXlvdXQ9ZnVuY3Rpb24odCxyLG4pe2lmKHQ9PW51bGx8fHI9PW51bGx8fG49PW51bGwpe2lmKHRoaXMuX2VsZW1lbnQ9PW51bGwpdGhyb3cgbmV3IEVycm9yKCJhbmNob3IoKSBtdXN0IGJlIGNhbGxlZCBiZWZvcmUgY29tcHV0ZUxheW91dCgpIik7aWYodGhpcy5fcm9vdEVsZW1lbnQhPW51bGwpe3Q9e3g6MCx5OjB9O3ZhciBpPXRoaXMuX3Jvb3RFbGVtZW50Lm5vZGUoKTtyPXlTLkRPTS5lbGVtZW50V2lkdGgoaSksbj15Uy5ET00uZWxlbWVudEhlaWdodChpKX1lbHNlIHRocm93IG5ldyBFcnJvcigibnVsbCBhcmd1bWVudHMgY2Fubm90IGJlIHBhc3NlZCB0byBjb21wdXRlTGF5b3V0KCkgb24gYSBub24tcm9vdCwgdW5hbmNob3JlZCBub2RlIil9dmFyIG89dGhpcy5fc2l6ZUZyb21PZmZlcihyLG4pLGE9by5oZWlnaHQscz1vLndpZHRoLGw9ZS5feEFsaWduVG9Qcm9wb3J0aW9uW3RoaXMuX3hBbGlnbm1lbnRdLGM9ZS5feUFsaWduVG9Qcm9wb3J0aW9uW3RoaXMuX3lBbGlnbm1lbnRdLHU9dC54KyhyLXMpKmwsaD10LnkrKG4tYSkqYztyZXR1cm4gdGhpcy5zZXRCb3VuZHMocyxhLHUsaCksdGhpc30sZS5wcm90b3R5cGUuc2V0Qm91bmRzPWZ1bmN0aW9uKHQscixuLGkpe3JldHVybiBuPT09dm9pZCAwJiYobj0wKSxpPT09dm9pZCAwJiYoaT0wKSx0aGlzLl93aWR0aD10LHRoaXMuX2hlaWdodD1yLHRoaXMuX29yaWdpbj17eDpuLHk6aX0sdGhpcy5fZWxlbWVudCE9bnVsbCYmdGhpcy5fZWxlbWVudC5zdHlsZXMoe2xlZnQ6bisicHgiLGhlaWdodDpyKyJweCIsdG9wOmkrInB4Iix3aWR0aDp0KyJweCJ9KSx0aGlzLl9yZXNpemVIYW5kbGVyIT1udWxsJiZ0aGlzLl9yZXNpemVIYW5kbGVyKHt3aWR0aDp0LGhlaWdodDpyfSksdGhpc30sZS5wcm90b3R5cGUuX3NpemVGcm9tT2ZmZXI9ZnVuY3Rpb24odCxyKXt2YXIgbj10aGlzLnJlcXVlc3RlZFNwYWNlKHQscik7cmV0dXJue3dpZHRoOnRoaXMuZml4ZWRXaWR0aCgpP01hdGgubWluKHQsbi5taW5XaWR0aCk6dCxoZWlnaHQ6dGhpcy5maXhlZEhlaWdodCgpP01hdGgubWluKHIsbi5taW5IZWlnaHQpOnJ9fSxlLnByb3RvdHlwZS5yZW5kZXI9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5faXNBbmNob3JlZCYmdGhpcy5faXNTZXR1cCYmdGhpcy53aWR0aCgpPj0wJiZ0aGlzLmhlaWdodCgpPj0wJiZrRi5yZWdpc3RlclRvUmVuZGVyKHRoaXMpLHRoaXN9LGUucHJvdG90eXBlLnJlbmRlckxvd1ByaW9yaXR5PWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMucmVuZGVyKCl9LGUucHJvdG90eXBlLl9zY2hlZHVsZUNvbXB1dGVMYXlvdXQ9ZnVuY3Rpb24oKXt0aGlzLl9pc0FuY2hvcmVkJiZ0aGlzLl9pc1NldHVwJiZrRi5yZWdpc3RlclRvQ29tcHV0ZUxheW91dEFuZFJlbmRlcih0aGlzKX0sZS5wcm90b3R5cGUub25SZXNpemU9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX3Jlc2l6ZUhhbmRsZXI9dCx0aGlzfSxlLnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseT1mdW5jdGlvbigpe3JldHVybiB0aGlzfSxlLnByb3RvdHlwZS5yZWRyYXc9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5faXNBbmNob3JlZCYmdGhpcy5faXNTZXR1cCYmKHRoaXMuaXNSb290KCk/dGhpcy5fc2NoZWR1bGVDb21wdXRlTGF5b3V0KCk6dGhpcy5wYXJlbnQoKS5yZWRyYXcoKSksdGhpc30sZS5wcm90b3R5cGUuaW52YWxpZGF0ZUNhY2hlPWZ1bmN0aW9uKCl7fSxlLnByb3RvdHlwZS5yZW5kZXJUbz1mdW5jdGlvbih0KXtpZih0aGlzLmRldGFjaCgpLHQhPW51bGwpe3ZhciByPXZvaWQgMDtpZih0eXBlb2YgdD09InN0cmluZyJ8fGd6ZS5pc0VsZW1lbnQodCk/cj1CWHQuc2VsZWN0KHQpOnI9SFh0LmNvZXJjZUV4dGVybmFsRDModCksIXIubm9kZSgpfHxyLm5vZGUoKS5ub2RlTmFtZT09bnVsbCl0aHJvdyBuZXcgRXJyb3IoIlBsb3R0YWJsZSByZXF1aXJlcyBhIHZhbGlkIEVsZW1lbnQgdG8gcmVuZGVyVG8iKTtpZihyLm5vZGUoKS5ub2RlTmFtZT09PSJzdmciKXRocm93IG5ldyBFcnJvcigiUGxvdHRhYmxlIDMueCBhbmQgbGF0ZXIgY2FuIG9ubHkgcmVuZGVyVG8gYW4gSFRNTCBjb21wb25lbnQ7IHBhc3MgYSBkaXYgaW5zdGVhZCEiKTt0aGlzLmFuY2hvcihyKX1pZih0aGlzLl9lbGVtZW50PT1udWxsKXRocm93IG5ldyBFcnJvcigiSWYgYSBDb21wb25lbnQgaGFzIG5ldmVyIGJlZW4gcmVuZGVyZWQgYmVmb3JlLCB0aGVuIHJlbmRlclRvIG11c3QgYmUgZ2l2ZW4gYSBub2RlIHRvIHJlbmRlciB0bywgb3IgYSBkMy5TZWxlY3Rpb24sIG9yIGEgc2VsZWN0b3Igc3RyaW5nIik7cmV0dXJuIGtGLnJlZ2lzdGVyVG9Db21wdXRlTGF5b3V0QW5kUmVuZGVyKHRoaXMpLGtGLmZsdXNoKCksdGhpc30sZS5wcm90b3R5cGUueEFsaWdubWVudD1mdW5jdGlvbih0KXtpZih0PT1udWxsKXJldHVybiB0aGlzLl94QWxpZ25tZW50O2lmKHQ9dC50b0xvd2VyQ2FzZSgpLGUuX3hBbGlnblRvUHJvcG9ydGlvblt0XT09bnVsbCl0aHJvdyBuZXcgRXJyb3IoIlVuc3VwcG9ydGVkIGFsaWdubWVudDogIit0KTtyZXR1cm4gdGhpcy5feEFsaWdubWVudD10LHRoaXMucmVkcmF3KCksdGhpc30sZS5wcm90b3R5cGUueUFsaWdubWVudD1mdW5jdGlvbih0KXtpZih0PT1udWxsKXJldHVybiB0aGlzLl95QWxpZ25tZW50O2lmKHQ9dC50b0xvd2VyQ2FzZSgpLGUuX3lBbGlnblRvUHJvcG9ydGlvblt0XT09bnVsbCl0aHJvdyBuZXcgRXJyb3IoIlVuc3VwcG9ydGVkIGFsaWdubWVudDogIit0KTtyZXR1cm4gdGhpcy5feUFsaWdubWVudD10LHRoaXMucmVkcmF3KCksdGhpc30sZS5wcm90b3R5cGUuaGFzQ2xhc3M9ZnVuY3Rpb24odCl7cmV0dXJuIHQ9PW51bGw/ITE6dGhpcy5fZWxlbWVudD09bnVsbD90aGlzLl9jc3NDbGFzc2VzLmhhcyh0KTp0aGlzLl9lbGVtZW50LmNsYXNzZWQodCl9LGUucHJvdG90eXBlLmFkZENsYXNzPWZ1bmN0aW9uKHQpe3JldHVybiB0PT1udWxsP3RoaXM6KHRoaXMuX2VsZW1lbnQ9PW51bGw/dGhpcy5fY3NzQ2xhc3Nlcy5hZGQodCk6dGhpcy5fZWxlbWVudC5jbGFzc2VkKHQsITApLHRoaXMpfSxlLnByb3RvdHlwZS5yZW1vdmVDbGFzcz1mdW5jdGlvbih0KXtyZXR1cm4gdD09bnVsbD90aGlzOih0aGlzLl9lbGVtZW50PT1udWxsP3RoaXMuX2Nzc0NsYXNzZXMuZGVsZXRlKHQpOnRoaXMuX2VsZW1lbnQuY2xhc3NlZCh0LCExKSx0aGlzKX0sZS5wcm90b3R5cGUuZml4ZWRXaWR0aD1mdW5jdGlvbigpe3JldHVybiExfSxlLnByb3RvdHlwZS5maXhlZEhlaWdodD1mdW5jdGlvbigpe3JldHVybiExfSxlLnByb3RvdHlwZS5kZXRhY2g9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5wYXJlbnQobnVsbCksdGhpcy5faXNBbmNob3JlZCYmdGhpcy5fZWxlbWVudC5yZW1vdmUoKSx0aGlzLl9pc0FuY2hvcmVkPSExLHRoaXMuX29uRGV0YWNoQ2FsbGJhY2tzLmNhbGxDYWxsYmFja3ModGhpcyksdGhpc30sZS5wcm90b3R5cGUub25EZXRhY2g9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX29uRGV0YWNoQ2FsbGJhY2tzLmFkZCh0KSx0aGlzfSxlLnByb3RvdHlwZS5vZmZEZXRhY2g9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX29uRGV0YWNoQ2FsbGJhY2tzLmRlbGV0ZSh0KSx0aGlzfSxlLnByb3RvdHlwZS5wYXJlbnQ9ZnVuY3Rpb24odCl7aWYodD09PXZvaWQgMClyZXR1cm4gdGhpcy5fcGFyZW50O2lmKHQhPT1udWxsJiYhdC5oYXModGhpcykpdGhyb3cgbmV3IEVycm9yKCJQYXNzZWQgaW52YWxpZCBwYXJlbnQiKTtyZXR1cm4gdGhpcy5fcGFyZW50PXQsdGhpc30sZS5wcm90b3R5cGUuYm91bmRzPWZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5vcmlnaW4oKTtyZXR1cm57dG9wTGVmdDp0LGJvdHRvbVJpZ2h0Ont4OnQueCt0aGlzLndpZHRoKCkseTp0LnkrdGhpcy5oZWlnaHQoKX19fSxlLnByb3RvdHlwZS5kZXN0cm95PWZ1bmN0aW9uKCl7dGhpcy5fZGVzdHJveWVkPSEwLHRoaXMuZGV0YWNoKCl9LGUucHJvdG90eXBlLndpZHRoPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3dpZHRofSxlLnByb3RvdHlwZS5oZWlnaHQ9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5faGVpZ2h0fSxlLnByb3RvdHlwZS5vcmlnaW49ZnVuY3Rpb24oKXtyZXR1cm57eDp0aGlzLl9vcmlnaW4ueCx5OnRoaXMuX29yaWdpbi55fX0sZS5wcm90b3R5cGUub3JpZ2luVG9Sb290PWZ1bmN0aW9uKCl7Zm9yKHZhciB0PXRoaXMub3JpZ2luKCkscj10aGlzLnBhcmVudCgpO3IhPW51bGw7KXt2YXIgbj1yLm9yaWdpbigpO3QueCs9bi54LHQueSs9bi55LHI9ci5wYXJlbnQoKX1yZXR1cm4gdH0sZS5wcm90b3R5cGUucm9vdD1mdW5jdGlvbigpe2Zvcih2YXIgdD10aGlzOyF0LmlzUm9vdCgpOyl0PXQucGFyZW50KCk7cmV0dXJuIHR9LGUucHJvdG90eXBlLmlzUm9vdD1mdW5jdGlvbigpe3JldHVybiB0aGlzLnBhcmVudCgpPT1udWxsfSxlLnByb3RvdHlwZS5mb3JlZ3JvdW5kPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2ZvcmVncm91bmRDb250YWluZXJ9LGUucHJvdG90eXBlLmNvbnRlbnQ9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fY29udGVudH0sZS5wcm90b3R5cGUuZWxlbWVudD1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9lbGVtZW50fSxlLnByb3RvdHlwZS5yb290RWxlbWVudD1mdW5jdGlvbigpe3JldHVybiB0aGlzLnJvb3QoKS5fcm9vdEVsZW1lbnR9LGUucHJvdG90eXBlLmJhY2tncm91bmQ9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fYmFja2dyb3VuZENvbnRhaW5lcn0sZS5feEFsaWduVG9Qcm9wb3J0aW9uPXtsZWZ0OjAsY2VudGVyOi41LHJpZ2h0OjF9LGUuX3lBbGlnblRvUHJvcG9ydGlvbj17dG9wOjAsY2VudGVyOi41LGJvdHRvbToxfSxlfSgpO0pBLkNvbXBvbmVudD1femV9KTt2YXIgQnU9SChGdT0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoRnUsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciB2Uz0oRXIoKSxVdChNcikpLHl6ZT0hMTtmdW5jdGlvbiB2emUoZSx0LHIpe2U9PT12b2lkIDAmJihlPTIpLHQ9PT12b2lkIDAmJih0PSIkIikscj09PXZvaWQgMCYmKHI9ITApO3ZhciBuPUdpdChlKTtyZXR1cm4gZnVuY3Rpb24oaSl7dmFyIG89bihNYXRoLmFicyhpKSk7cmV0dXJuIG8hPT0iIiYmKHI/bz10K286bys9dCxpPDAmJihvPSItIitvKSksb319RnUuY3VycmVuY3k9dnplO2Z1bmN0aW9uIEdpdChlKXtyZXR1cm4gZT09PXZvaWQgMCYmKGU9MyksUkYoZSksZnVuY3Rpb24odCl7cmV0dXJuIHQudG9GaXhlZChlKX19RnUuZml4ZWQ9R2l0O2Z1bmN0aW9uIHh6ZShlKXtyZXR1cm4gZT09PXZvaWQgMCYmKGU9MyksUkYoZSksZnVuY3Rpb24odCl7aWYodHlwZW9mIHQ9PSJudW1iZXIiKXt2YXIgcj1NYXRoLnBvdygxMCxlKTtyZXR1cm4gU3RyaW5nKE1hdGgucm91bmQodCpyKS9yKX1lbHNlIHJldHVybiBTdHJpbmcodCl9fUZ1LmdlbmVyYWw9eHplO2Z1bmN0aW9uIGJ6ZSgpe3JldHVybiBmdW5jdGlvbihlKXtyZXR1cm4gU3RyaW5nKGUpfX1GdS5pZGVudGl0eT1iemU7ZnVuY3Rpb24gd3plKGUpe2U9PT12b2lkIDAmJihlPTApO3ZhciB0PUdpdChlKTtyZXR1cm4gZnVuY3Rpb24ocil7dmFyIG49cioxMDAsaT1yLnRvU3RyaW5nKCksbz1NYXRoLnBvdygxMCxpLmxlbmd0aC0oaS5pbmRleE9mKCIuIikrMSkpO3JldHVybiBuPXBhcnNlSW50KChuKm8pLnRvU3RyaW5nKCksMTApL28sdChuKSsiJSJ9fUZ1LnBlcmNlbnRhZ2U9d3plO2Z1bmN0aW9uIFN6ZShlKXtyZXR1cm4gZT09PXZvaWQgMCYmKGU9MyksUkYoZSksZnVuY3Rpb24odCl7cmV0dXJuIHZTLmZvcm1hdCgiLiIrZSsicyIpKHQpfX1GdS5zaVN1ZmZpeD1TemU7ZnVuY3Rpb24gTXplKGUpe2U9PT12b2lkIDAmJihlPTMpLFJGKGUpO3ZhciB0PSJLTUJUUSIscj12Uy5mb3JtYXQoIi4iK2UrImUiKSxuPXZTLmZvcm1hdCgiLiIrZSsiZiIpLGk9TWF0aC5wb3coMTAsMyoodC5sZW5ndGgrMSkpLG89TWF0aC5wb3coMTAsLWUpO3JldHVybiBmdW5jdGlvbihhKXt2YXIgcz1NYXRoLmFicyhhKTtpZigoczxvfHxzPj1pKSYmcyE9PTApcmV0dXJuIHIoYSk7Zm9yKHZhciBsPS0xO3M+PU1hdGgucG93KDFlMyxsKzIpJiZsPHQubGVuZ3RoLTE7KWwrKzt2YXIgYz0iIjtyZXR1cm4gbD09PS0xP2M9bihhKTpjPW4oYS9NYXRoLnBvdygxZTMsbCsxKSkrdFtsXSwoYT4wJiZjLnN1YnN0cigwLDQpPT09IjEwMDAifHxhPDAmJmMuc3Vic3RyKDAsNSk9PT0iLTEwMDAiKSYmKGw8dC5sZW5ndGgtMT8obCsrLGM9bihhL01hdGgucG93KDFlMyxsKzEpKSt0W2xdKTpjPXIoYSkpLGN9fUZ1LnNob3J0U2NhbGU9TXplO2Z1bmN0aW9uIEV6ZSgpe3ZhciBlPVt7c3BlY2lmaWVyOiIuJUwiLHByZWRpY2F0ZTpmdW5jdGlvbih0KXtyZXR1cm4gdC5nZXRNaWxsaXNlY29uZHMoKSE9PTB9fSx7c3BlY2lmaWVyOiI6JVMiLHByZWRpY2F0ZTpmdW5jdGlvbih0KXtyZXR1cm4gdC5nZXRTZWNvbmRzKCkhPT0wfX0se3NwZWNpZmllcjoiJUk6JU0iLHByZWRpY2F0ZTpmdW5jdGlvbih0KXtyZXR1cm4gdC5nZXRNaW51dGVzKCkhPT0wfX0se3NwZWNpZmllcjoiJUkgJXAiLHByZWRpY2F0ZTpmdW5jdGlvbih0KXtyZXR1cm4gdC5nZXRIb3VycygpIT09MH19LHtzcGVjaWZpZXI6IiVhICVkIixwcmVkaWNhdGU6ZnVuY3Rpb24odCl7cmV0dXJuIHQuZ2V0RGF5KCkhPT0wJiZ0LmdldERhdGUoKSE9PTF9fSx7c3BlY2lmaWVyOiIlYiAlZCIscHJlZGljYXRlOmZ1bmN0aW9uKHQpe3JldHVybiB0LmdldERhdGUoKSE9PTF9fSx7c3BlY2lmaWVyOiIlYiIscHJlZGljYXRlOmZ1bmN0aW9uKHQpe3JldHVybiB0LmdldE1vbnRoKCkhPT0wfX1dO3JldHVybiBmdW5jdGlvbih0KXt2YXIgcj1lLmZpbHRlcihmdW5jdGlvbihpKXtyZXR1cm4gaS5wcmVkaWNhdGUodCl9KSxuPXIubGVuZ3RoPjA/clswXS5zcGVjaWZpZXI6IiVZIjtyZXR1cm4gdlMudGltZUZvcm1hdChuKSh0KX19RnUubXVsdGlUaW1lPUV6ZTtmdW5jdGlvbiBUemUoZSx0KXtyZXR1cm4gdD09PXZvaWQgMCYmKHQ9eXplKSx0P3ZTLnV0Y0Zvcm1hdChlKTp2Uy50aW1lRm9ybWF0KGUpfUZ1LnRpbWU9VHplO2Z1bmN0aW9uIFJGKGUpe2lmKGU8MHx8ZT4yMCl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiRm9ybWF0dGVyIHByZWNpc2lvbiBtdXN0IGJlIGJldHdlZW4gMCBhbmQgMjAiKTtpZihlIT09TWF0aC5mbG9vcihlKSl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiRm9ybWF0dGVyIHByZWNpc2lvbiBtdXN0IGJlIGFuIGludGVnZXIiKX19KTt2YXIgUUE9SChORj0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoTkYsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBDemU9KGRlKCksVXQocGUpKSx5ZD0oRXIoKSxVdChNcikpLFdpdD1fbCgpLEF6ZT1rYygpLFVYdD1CdSgpLHhTPUZlKCksUHplPUlmKCk7TkYuQXhpc09yaWVudGF0aW9uPVB6ZS5tYWtlRW51bShbImJvdHRvbSIsImxlZnQiLCJyaWdodCIsInRvcCJdKTt2YXIgSXplPWZ1bmN0aW9uKGUpe0N6ZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIsbil7dmFyIGk9ZS5jYWxsKHRoaXMpfHx0aGlzO2lmKGkuX2VuZFRpY2tMZW5ndGg9NSxpLl9pbm5lclRpY2tMZW5ndGg9NSxpLl90aWNrTGFiZWxQYWRkaW5nPTEwLGkuX21hcmdpbj0xNSxpLl9zaG93RW5kVGlja0xhYmVscz0hMSxpLl9hbm5vdGF0aW9uc0VuYWJsZWQ9ITEsaS5fYW5ub3RhdGlvblRpZXJDb3VudD0xLHI9PW51bGx8fG49PW51bGwpdGhyb3cgbmV3IEVycm9yKCJBeGlzIHJlcXVpcmVzIGEgc2NhbGUgYW5kIG9yaWVudGF0aW9uIik7cmV0dXJuIGkuX3NjYWxlPXIsaS5vcmllbnRhdGlvbihuKSxpLl9zZXREZWZhdWx0QWxpZ25tZW50KCksaS5hZGRDbGFzcygiYXhpcyIpLGkuaXNIb3Jpem9udGFsKCk/aS5hZGRDbGFzcygieC1heGlzIik6aS5hZGRDbGFzcygieS1heGlzIiksaS5mb3JtYXR0ZXIoVVh0LmlkZW50aXR5KCkpLGkuX3Jlc2NhbGVDYWxsYmFjaz1mdW5jdGlvbihvKXtyZXR1cm4gaS5fcmVzY2FsZSgpfSxpLl9zY2FsZS5vblVwZGF0ZShpLl9yZXNjYWxlQ2FsbGJhY2spLGkuX2Fubm90YXRlZFRpY2tzPVtdLGkuX2Fubm90YXRpb25Gb3JtYXR0ZXI9VVh0LmlkZW50aXR5KCksaX1yZXR1cm4gdC5wcm90b3R5cGUuZGVzdHJveT1mdW5jdGlvbigpe2UucHJvdG90eXBlLmRlc3Ryb3kuY2FsbCh0aGlzKSx0aGlzLl9zY2FsZS5vZmZVcGRhdGUodGhpcy5fcmVzY2FsZUNhbGxiYWNrKX0sdC5wcm90b3R5cGUudGlja0xhYmVsRGF0YU9uRWxlbWVudD1mdW5jdGlvbihyKXtpZihyIT1udWxsKXtmb3IodmFyIG47ciE9bnVsbCYmci5jbGFzc0xpc3QmJm49PT12b2lkIDA7KXIuY2xhc3NMaXN0LmNvbnRhaW5zKHQuVElDS19MQUJFTF9DTEFTUyk/bj1yOnI9ci5wYXJlbnROb2RlO3JldHVybiByPT09dm9pZCAwP3ZvaWQgMDp5ZC5zZWxlY3QocikuZGF0dW0oKX19LHQucHJvdG90eXBlLl9jb21wdXRlV2lkdGg9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fbWF4TGFiZWxUaWNrTGVuZ3RoKCl9LHQucHJvdG90eXBlLl9jb21wdXRlSGVpZ2h0PWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX21heExhYmVsVGlja0xlbmd0aCgpfSx0LnByb3RvdHlwZS5yZXF1ZXN0ZWRTcGFjZT1mdW5jdGlvbihyLG4pe3ZhciBpPTAsbz0wO2lmKHRoaXMuaXNIb3Jpem9udGFsKCkpe2lmKG89dGhpcy5fY29tcHV0ZUhlaWdodCgpK3RoaXMuX21hcmdpbix0aGlzLmFubm90YXRpb25zRW5hYmxlZCgpKXt2YXIgYT10aGlzLl9hbm5vdGF0aW9uTWVhc3VyZXIubWVhc3VyZSgpLmhlaWdodCsyKnQuX0FOTk9UQVRJT05fTEFCRUxfUEFERElORztvKz1hKnRoaXMuYW5ub3RhdGlvblRpZXJDb3VudCgpfX1lbHNlIGlmKGk9dGhpcy5fY29tcHV0ZVdpZHRoKCkrdGhpcy5fbWFyZ2luLHRoaXMuYW5ub3RhdGlvbnNFbmFibGVkKCkpe3ZhciBhPXRoaXMuX2Fubm90YXRpb25NZWFzdXJlci5tZWFzdXJlKCkuaGVpZ2h0KzIqdC5fQU5OT1RBVElPTl9MQUJFTF9QQURESU5HO2krPWEqdGhpcy5hbm5vdGF0aW9uVGllckNvdW50KCl9cmV0dXJue21pbldpZHRoOmksbWluSGVpZ2h0Om99fSx0LnByb3RvdHlwZS5maXhlZEhlaWdodD1mdW5jdGlvbigpe3JldHVybiB0aGlzLmlzSG9yaXpvbnRhbCgpfSx0LnByb3RvdHlwZS5maXhlZFdpZHRoPWZ1bmN0aW9uKCl7cmV0dXJuIXRoaXMuaXNIb3Jpem9udGFsKCl9LHQucHJvdG90eXBlLl9yZXNjYWxlPWZ1bmN0aW9uKCl7dGhpcy5yZW5kZXIoKX0sdC5wcm90b3R5cGUuY29tcHV0ZUxheW91dD1mdW5jdGlvbihyLG4saSl7cmV0dXJuIGUucHJvdG90eXBlLmNvbXB1dGVMYXlvdXQuY2FsbCh0aGlzLHIsbixpKSx0aGlzLmlzSG9yaXpvbnRhbCgpP3RoaXMuX3NjYWxlLnJhbmdlKFswLHRoaXMud2lkdGgoKV0pOnRoaXMuX3NjYWxlLnJhbmdlKFt0aGlzLmhlaWdodCgpLDBdKSx0aGlzfSx0LnByb3RvdHlwZS5fc2l6ZUZyb21PZmZlcj1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXMucmVxdWVzdGVkU3BhY2UocixuKTtyZXR1cm4gdGhpcy5pc0hvcml6b250YWwoKT97d2lkdGg6cixoZWlnaHQ6aS5taW5IZWlnaHR9OntoZWlnaHQ6bix3aWR0aDppLm1pbldpZHRofX0sdC5wcm90b3R5cGUuX3NldHVwPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX3NldHVwLmNhbGwodGhpcyksdGhpcy5fdGlja01hcmtDb250YWluZXI9dGhpcy5jb250ZW50KCkuYXBwZW5kKCJnIikuY2xhc3NlZCh0LlRJQ0tfTUFSS19DTEFTUysiLWNvbnRhaW5lciIsITApLHRoaXMuX3RpY2tMYWJlbENvbnRhaW5lcj10aGlzLmNvbnRlbnQoKS5hcHBlbmQoImciKS5jbGFzc2VkKHQuVElDS19MQUJFTF9DTEFTUysiLWNvbnRhaW5lciIsITApLHRoaXMuX2Jhc2VsaW5lPXRoaXMuY29udGVudCgpLmFwcGVuZCgibGluZSIpLmNsYXNzZWQoImJhc2VsaW5lIiwhMCksdGhpcy5fYW5ub3RhdGlvbkNvbnRhaW5lcj10aGlzLmNvbnRlbnQoKS5hcHBlbmQoImciKS5jbGFzc2VkKCJhbm5vdGF0aW9uLWNvbnRhaW5lciIsITApLHRoaXMuX2Fubm90YXRpb25Db250YWluZXIuYXBwZW5kKCJnIikuY2xhc3NlZCgiYW5ub3RhdGlvbi1saW5lLWNvbnRhaW5lciIsITApLHRoaXMuX2Fubm90YXRpb25Db250YWluZXIuYXBwZW5kKCJnIikuY2xhc3NlZCgiYW5ub3RhdGlvbi1jaXJjbGUtY29udGFpbmVyIiwhMCksdGhpcy5fYW5ub3RhdGlvbkNvbnRhaW5lci5hcHBlbmQoImciKS5jbGFzc2VkKCJhbm5vdGF0aW9uLXJlY3QtY29udGFpbmVyIiwhMCk7dmFyIHI9dGhpcy5fYW5ub3RhdGlvbkNvbnRhaW5lci5hcHBlbmQoImciKS5jbGFzc2VkKCJhbm5vdGF0aW9uLWxhYmVsLWNvbnRhaW5lciIsITApLG49bmV3IFdpdC5TdmdDb250ZXh0KHIubm9kZSgpKTt0aGlzLl9hbm5vdGF0aW9uTWVhc3VyZXI9bmV3IFdpdC5DYWNoZU1lYXN1cmVyKG4pLHRoaXMuX2Fubm90YXRpb25Xcml0ZXI9bmV3IFdpdC5Xcml0ZXIodGhpcy5fYW5ub3RhdGlvbk1lYXN1cmVyLG4pfSx0LnByb3RvdHlwZS5fZ2V0VGlja1ZhbHVlcz1mdW5jdGlvbigpe3JldHVybltdfSx0LnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseT1mdW5jdGlvbigpe3ZhciByPXRoaXMuX2dldFRpY2tWYWx1ZXMoKSxuPXRoaXMuX3RpY2tNYXJrQ29udGFpbmVyLnNlbGVjdEFsbCgiLiIrdC5USUNLX01BUktfQ0xBU1MpLmRhdGEociksaT1uLmVudGVyKCkuYXBwZW5kKCJsaW5lIikuY2xhc3NlZCh0LlRJQ0tfTUFSS19DTEFTUywhMCkubWVyZ2Uobik7cmV0dXJuIGkuYXR0cnModGhpcy5fZ2VuZXJhdGVUaWNrTWFya0F0dHJIYXNoKCkpLHlkLnNlbGVjdChpLm5vZGVzKClbMF0pLmNsYXNzZWQodC5FTkRfVElDS19NQVJLX0NMQVNTLCEwKS5hdHRycyh0aGlzLl9nZW5lcmF0ZVRpY2tNYXJrQXR0ckhhc2goITApKSx5ZC5zZWxlY3QoaS5ub2RlcygpW3IubGVuZ3RoLTFdKS5jbGFzc2VkKHQuRU5EX1RJQ0tfTUFSS19DTEFTUywhMCkuYXR0cnModGhpcy5fZ2VuZXJhdGVUaWNrTWFya0F0dHJIYXNoKCEwKSksbi5leGl0KCkucmVtb3ZlKCksdGhpcy5fYmFzZWxpbmUuYXR0cnModGhpcy5fZ2VuZXJhdGVCYXNlbGluZUF0dHJIYXNoKCkpLHRoaXMuYW5ub3RhdGlvbnNFbmFibGVkKCk/dGhpcy5fZHJhd0Fubm90YXRpb25zKCk6dGhpcy5fcmVtb3ZlQW5ub3RhdGlvbnMoKSx0aGlzfSx0LnByb3RvdHlwZS5hbm5vdGF0ZWRUaWNrcz1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9hbm5vdGF0ZWRUaWNrczoodGhpcy5fYW5ub3RhdGVkVGlja3M9cix0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5hbm5vdGF0aW9uRm9ybWF0dGVyPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2Fubm90YXRpb25Gb3JtYXR0ZXI6KHRoaXMuX2Fubm90YXRpb25Gb3JtYXR0ZXI9cix0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5hbm5vdGF0aW9uc0VuYWJsZWQ9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fYW5ub3RhdGlvbnNFbmFibGVkOih0aGlzLl9hbm5vdGF0aW9uc0VuYWJsZWQ9cix0aGlzLnJlZHJhdygpLHRoaXMpfSx0LnByb3RvdHlwZS5hbm5vdGF0aW9uVGllckNvdW50PWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX2Fubm90YXRpb25UaWVyQ291bnQ7aWYocjwwKXRocm93IG5ldyBFcnJvcigiYW5ub3RhdGlvblRpZXJDb3VudCBjYW5ub3QgYmUgbmVnYXRpdmUiKTtyZXR1cm4gdGhpcy5fYW5ub3RhdGlvblRpZXJDb3VudD1yLHRoaXMucmVkcmF3KCksdGhpc30sdC5wcm90b3R5cGUuX2RyYXdBbm5vdGF0aW9ucz1mdW5jdGlvbigpe3ZhciByPXRoaXMsbj10Ll9BTk5PVEFUSU9OX0xBQkVMX1BBRERJTkcsaT1uZXcgeFMuTWFwLG89dGhpcy5fYW5ub3RhdGVkVGlja3NUb1JlbmRlcigpO28uZm9yRWFjaChmdW5jdGlvbihQKXt2YXIgaz1yLl9hbm5vdGF0aW9uTWVhc3VyZXIubWVhc3VyZShyLmFubm90YXRpb25Gb3JtYXR0ZXIoKShQKSksTz17d2lkdGg6ay53aWR0aCsyKm4saGVpZ2h0OmsuaGVpZ2h0KzIqbn07aS5zZXQoUCxPKX0pO3ZhciBhPXRoaXMuX2Fubm90YXRpb25NZWFzdXJlci5tZWFzdXJlKCkuaGVpZ2h0KzIqbixzPXRoaXMuX2Fubm90YXRpb25Ub1RpZXIoaSksbD1uZXcgeFMuU2V0LGM9dGhpcy5pc0hvcml6b250YWwoKT90aGlzLmhlaWdodCgpOnRoaXMud2lkdGgoKSx1PXRoaXMuX2NvcmVTaXplKCksaD1NYXRoLm1pbih0aGlzLmFubm90YXRpb25UaWVyQ291bnQoKSxNYXRoLmZsb29yKChjLXUpL2EpKTtzLmZvckVhY2goZnVuY3Rpb24oUCxrKXsoUD09PS0xfHxQPj1oKSYmbC5hZGQoayl9KTt2YXIgZj1mdW5jdGlvbihQLGssTyl7dmFyIEQ9UC5zZWxlY3RBbGwoIi4iK08pLmRhdGEobyksQj1ELmVudGVyKCkuYXBwZW5kKGspLmNsYXNzZWQoTywhMCkubWVyZ2UoRCk7cmV0dXJuIEQuZXhpdCgpLnJlbW92ZSgpLEJ9LHA9ZnVuY3Rpb24oUCl7c3dpdGNoKHIub3JpZW50YXRpb24oKSl7Y2FzZSJib3R0b20iOmNhc2UicmlnaHQiOnJldHVybiBzLmdldChQKSphK3U7Y2FzZSJ0b3AiOmNhc2UibGVmdCI6cmV0dXJuIGMtdS1zLmdldChQKSphfX0sZD1mdW5jdGlvbihQKXtyZXR1cm4gci5fc2NhbGUuc2NhbGUoUCl9LGc9ZnVuY3Rpb24oUCl7cmV0dXJuIGwuaGFzKFApPyJoaWRkZW4iOiJ2aXNpYmxlIn0sXztzd2l0Y2godGhpcy5vcmllbnRhdGlvbigpKXtjYXNlImJvdHRvbSI6Y2FzZSJyaWdodCI6Xz0wO2JyZWFrO2Nhc2UidG9wIjpfPXRoaXMuaGVpZ2h0KCk7YnJlYWs7Y2FzZSJsZWZ0IjpfPXRoaXMud2lkdGgoKTticmVha312YXIgeT10aGlzLmlzSG9yaXpvbnRhbCgpO2YodGhpcy5fYW5ub3RhdGlvbkNvbnRhaW5lci5zZWxlY3QoIi5hbm5vdGF0aW9uLWxpbmUtY29udGFpbmVyIiksImxpbmUiLHQuQU5OT1RBVElPTl9MSU5FX0NMQVNTKS5hdHRycyh7eDE6eT9kOl8seDI6eT9kOnAseTE6eT9fOmQseTI6eT9wOmQsdmlzaWJpbGl0eTpnfSksZih0aGlzLl9hbm5vdGF0aW9uQ29udGFpbmVyLnNlbGVjdCgiLmFubm90YXRpb24tY2lyY2xlLWNvbnRhaW5lciIpLCJjaXJjbGUiLHQuQU5OT1RBVElPTl9DSVJDTEVfQ0xBU1MpLmF0dHJzKHtjeDp5P2Q6XyxjeTp5P186ZCxyOjN9KTt2YXIgeD1mdW5jdGlvbihQKXtzd2l0Y2goci5vcmllbnRhdGlvbigpKXtjYXNlImJvdHRvbSI6Y2FzZSJyaWdodCI6cmV0dXJuIHAoUCk7Y2FzZSJ0b3AiOmNhc2UibGVmdCI6cmV0dXJuIHAoUCktaS5nZXQoUCkuaGVpZ2h0fX07Zih0aGlzLl9hbm5vdGF0aW9uQ29udGFpbmVyLnNlbGVjdCgiLmFubm90YXRpb24tcmVjdC1jb250YWluZXIiKSwicmVjdCIsdC5BTk5PVEFUSU9OX1JFQ1RfQ0xBU1MpLmF0dHJzKHt4Onk/ZDp4LHk6eT94OmQsd2lkdGg6eT9mdW5jdGlvbihQKXtyZXR1cm4gaS5nZXQoUCkud2lkdGh9OmZ1bmN0aW9uKFApe3JldHVybiBpLmdldChQKS5oZWlnaHR9LGhlaWdodDp5P2Z1bmN0aW9uKFApe3JldHVybiBpLmdldChQKS5oZWlnaHR9OmZ1bmN0aW9uKFApe3JldHVybiBpLmdldChQKS53aWR0aH0sdmlzaWJpbGl0eTpnfSk7dmFyIGI9dGhpcy5fYW5ub3RhdGlvbldyaXRlcixTPXRoaXMuYW5ub3RhdGlvbkZvcm1hdHRlcigpLEM9Zih0aGlzLl9hbm5vdGF0aW9uQ29udGFpbmVyLnNlbGVjdCgiLmFubm90YXRpb24tbGFiZWwtY29udGFpbmVyIiksImciLHQuQU5OT1RBVElPTl9MQUJFTF9DTEFTUyk7Qy5zZWxlY3RBbGwoIi50ZXh0LWNvbnRhaW5lciIpLnJlbW92ZSgpLEMuYXR0cnMoe3RyYW5zZm9ybTpmdW5jdGlvbihQKXt2YXIgaz15P2QoUCk6eChQKSxPPXk/eChQKTpkKFApO3JldHVybiJ0cmFuc2xhdGUoIitrKyIsIitPKyIpIn0sdmlzaWJpbGl0eTpnfSkuZWFjaChmdW5jdGlvbihQKXtiLndyaXRlKFMoUCkseT9pLmdldChQKS53aWR0aDppLmdldChQKS5oZWlnaHQseT9pLmdldChQKS5oZWlnaHQ6aS5nZXQoUCkud2lkdGgse3hBbGlnbjoiY2VudGVyIix5QWxpZ246ImNlbnRlciIsdGV4dFJvdGF0aW9uOnk/MDo5MH0seWQuc2VsZWN0KHRoaXMpLm5vZGUoKSl9KX0sdC5wcm90b3R5cGUuX2Fubm90YXRlZFRpY2tzVG9SZW5kZXI9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLG49dGhpcy5fc2NhbGUucmFuZ2UoKTtyZXR1cm4geFMuQXJyYXkudW5pcSh0aGlzLmFubm90YXRlZFRpY2tzKCkuZmlsdGVyKGZ1bmN0aW9uKGkpe3JldHVybiBpPT1udWxsPyExOnhTLk1hdGguaW5SYW5nZShyLl9zY2FsZS5zY2FsZShpKSxuWzBdLG5bMV0pfSkpfSx0LnByb3RvdHlwZS5fY29yZVNpemU9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLmlzSG9yaXpvbnRhbCgpP3RoaXMuaGVpZ2h0KCk6dGhpcy53aWR0aCgpLG49dGhpcy5pc0hvcml6b250YWwoKT90aGlzLl9jb21wdXRlSGVpZ2h0KCk6dGhpcy5fY29tcHV0ZVdpZHRoKCk7cmV0dXJuIE1hdGgubWluKG4scil9LHQucHJvdG90eXBlLl9hbm5vdGF0aW9uVGllckhlaWdodD1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9hbm5vdGF0aW9uTWVhc3VyZXIubWVhc3VyZSgpLmhlaWdodCsyKnQuX0FOTk9UQVRJT05fTEFCRUxfUEFERElOR30sdC5wcm90b3R5cGUuX2Fubm90YXRpb25Ub1RpZXI9ZnVuY3Rpb24ocil7dmFyIG49dGhpcyxpPVtbXV0sbz1uZXcgeFMuTWFwLGE9dGhpcy5pc0hvcml6b250YWwoKT90aGlzLndpZHRoKCk6dGhpcy5oZWlnaHQoKTtyZXR1cm4gdGhpcy5fYW5ub3RhdGVkVGlja3NUb1JlbmRlcigpLmZvckVhY2goZnVuY3Rpb24ocyl7dmFyIGw9bi5fc2NhbGUuc2NhbGUocyksYz1yLmdldChzKS53aWR0aDtpZihsPDB8fGwrYz5hKXtvLnNldChzLC0xKTtyZXR1cm59Zm9yKHZhciB1PWZ1bmN0aW9uKGYpe3JldHVybiBpW2ZdLnNvbWUoZnVuY3Rpb24ocCl7dmFyIGQ9bi5fc2NhbGUuc2NhbGUocCksZz1yLmdldChwKS53aWR0aDtyZXR1cm4gbCtjPj1kJiZsPD1kK2d9KX0saD0wO3UoaCk7KWgrKyxpLmxlbmd0aD09PWgmJmkucHVzaChbXSk7aVtoXS5wdXNoKHMpLG8uc2V0KHMsaCl9KSxvfSx0LnByb3RvdHlwZS5fcmVtb3ZlQW5ub3RhdGlvbnM9ZnVuY3Rpb24oKXt0aGlzLl9hbm5vdGF0aW9uQ29udGFpbmVyLnNlbGVjdEFsbCgiLmFubm90YXRpb24tbGluZSIpLnJlbW92ZSgpLHRoaXMuX2Fubm90YXRpb25Db250YWluZXIuc2VsZWN0QWxsKCIuYW5ub3RhdGlvbi1jaXJjbGUiKS5yZW1vdmUoKSx0aGlzLl9hbm5vdGF0aW9uQ29udGFpbmVyLnNlbGVjdEFsbCgiLmFubm90YXRpb24tcmVjdCIpLnJlbW92ZSgpLHRoaXMuX2Fubm90YXRpb25Db250YWluZXIuc2VsZWN0QWxsKCIuYW5ub3RhdGlvbi1sYWJlbCIpLnJlbW92ZSgpfSx0LnByb3RvdHlwZS5fZ2VuZXJhdGVCYXNlbGluZUF0dHJIYXNoPWZ1bmN0aW9uKCl7dmFyIHI9e3gxOjAseTE6MCx4MjowLHkyOjB9O3N3aXRjaCh0aGlzLl9vcmllbnRhdGlvbil7Y2FzZSJib3R0b20iOnIueDI9dGhpcy53aWR0aCgpO2JyZWFrO2Nhc2UidG9wIjpyLngyPXRoaXMud2lkdGgoKSxyLnkxPXRoaXMuaGVpZ2h0KCksci55Mj10aGlzLmhlaWdodCgpO2JyZWFrO2Nhc2UibGVmdCI6ci54MT10aGlzLndpZHRoKCksci54Mj10aGlzLndpZHRoKCksci55Mj10aGlzLmhlaWdodCgpO2JyZWFrO2Nhc2UicmlnaHQiOnIueTI9dGhpcy5oZWlnaHQoKTticmVha31yZXR1cm4gcn0sdC5wcm90b3R5cGUuX2dlbmVyYXRlVGlja01hcmtBdHRySGFzaD1mdW5jdGlvbihyKXt2YXIgbj10aGlzO3I9PT12b2lkIDAmJihyPSExKTt2YXIgaT17eDE6MCx5MTowLHgyOjAseTI6MH0sbz1mdW5jdGlvbihzKXtyZXR1cm4gbi5fc2NhbGUuc2NhbGUocyl9O3RoaXMuaXNIb3Jpem9udGFsKCk/KGkueDE9byxpLngyPW8pOihpLnkxPW8saS55Mj1vKTt2YXIgYT1yP3RoaXMuX2VuZFRpY2tMZW5ndGg6dGhpcy5faW5uZXJUaWNrTGVuZ3RoO3N3aXRjaCh0aGlzLl9vcmllbnRhdGlvbil7Y2FzZSJib3R0b20iOmkueTI9YTticmVhaztjYXNlInRvcCI6aS55MT10aGlzLmhlaWdodCgpLGkueTI9dGhpcy5oZWlnaHQoKS1hO2JyZWFrO2Nhc2UibGVmdCI6aS54MT10aGlzLndpZHRoKCksaS54Mj10aGlzLndpZHRoKCktYTticmVhaztjYXNlInJpZ2h0IjppLngyPWE7YnJlYWt9cmV0dXJuIGl9LHQucHJvdG90eXBlLl9zZXREZWZhdWx0QWxpZ25tZW50PWZ1bmN0aW9uKCl7c3dpdGNoKHRoaXMuX29yaWVudGF0aW9uKXtjYXNlImJvdHRvbSI6dGhpcy55QWxpZ25tZW50KCJ0b3AiKTticmVhaztjYXNlInRvcCI6dGhpcy55QWxpZ25tZW50KCJib3R0b20iKTticmVhaztjYXNlImxlZnQiOnRoaXMueEFsaWdubWVudCgicmlnaHQiKTticmVhaztjYXNlInJpZ2h0Ijp0aGlzLnhBbGlnbm1lbnQoImxlZnQiKTticmVha319LHQucHJvdG90eXBlLmlzSG9yaXpvbnRhbD1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9vcmllbnRhdGlvbj09PSJ0b3AifHx0aGlzLl9vcmllbnRhdGlvbj09PSJib3R0b20ifSx0LnByb3RvdHlwZS5nZXRTY2FsZT1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9zY2FsZX0sdC5wcm90b3R5cGUuZm9ybWF0dGVyPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2Zvcm1hdHRlcjoodGhpcy5fZm9ybWF0dGVyPXIsdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5wcm90b3R5cGUuaW5uZXJUaWNrTGVuZ3RoPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX2lubmVyVGlja0xlbmd0aDtpZihyPDApdGhyb3cgbmV3IEVycm9yKCJpbm5lciB0aWNrIGxlbmd0aCBtdXN0IGJlIHBvc2l0aXZlIik7cmV0dXJuIHRoaXMuX2lubmVyVGlja0xlbmd0aD1yLHRoaXMucmVkcmF3KCksdGhpc30sdC5wcm90b3R5cGUuZW5kVGlja0xlbmd0aD1mdW5jdGlvbihyKXtpZihyPT1udWxsKXJldHVybiB0aGlzLl9lbmRUaWNrTGVuZ3RoO2lmKHI8MCl0aHJvdyBuZXcgRXJyb3IoImVuZCB0aWNrIGxlbmd0aCBtdXN0IGJlIHBvc2l0aXZlIik7cmV0dXJuIHRoaXMuX2VuZFRpY2tMZW5ndGg9cix0aGlzLnJlZHJhdygpLHRoaXN9LHQucHJvdG90eXBlLl9tYXhMYWJlbFRpY2tMZW5ndGg9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5zaG93RW5kVGlja0xhYmVscygpP01hdGgubWF4KHRoaXMuaW5uZXJUaWNrTGVuZ3RoKCksdGhpcy5lbmRUaWNrTGVuZ3RoKCkpOnRoaXMuaW5uZXJUaWNrTGVuZ3RoKCl9LHQucHJvdG90eXBlLnRpY2tMYWJlbFBhZGRpbmc9ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fdGlja0xhYmVsUGFkZGluZztpZihyPDApdGhyb3cgbmV3IEVycm9yKCJ0aWNrIGxhYmVsIHBhZGRpbmcgbXVzdCBiZSBwb3NpdGl2ZSIpO3JldHVybiB0aGlzLl90aWNrTGFiZWxQYWRkaW5nPXIsdGhpcy5yZWRyYXcoKSx0aGlzfSx0LnByb3RvdHlwZS5tYXJnaW49ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fbWFyZ2luO2lmKHI8MCl0aHJvdyBuZXcgRXJyb3IoIm1hcmdpbiBzaXplIG11c3QgYmUgcG9zaXRpdmUiKTtyZXR1cm4gdGhpcy5fbWFyZ2luPXIsdGhpcy5yZWRyYXcoKSx0aGlzfSx0LnByb3RvdHlwZS5vcmllbnRhdGlvbj1mdW5jdGlvbihyKXtpZihyPT1udWxsKXJldHVybiB0aGlzLl9vcmllbnRhdGlvbjt2YXIgbj1yLnRvTG93ZXJDYXNlKCk7aWYobiE9PSJ0b3AiJiZuIT09ImJvdHRvbSImJm4hPT0ibGVmdCImJm4hPT0icmlnaHQiKXRocm93IG5ldyBFcnJvcigidW5zdXBwb3J0ZWQgb3JpZW50YXRpb24iKTtyZXR1cm4gdGhpcy5fb3JpZW50YXRpb249bix0aGlzLnJlZHJhdygpLHRoaXN9LHQucHJvdG90eXBlLnNob3dFbmRUaWNrTGFiZWxzPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX3Nob3dFbmRUaWNrTGFiZWxzOih0aGlzLl9zaG93RW5kVGlja0xhYmVscz1yLHRoaXMucmVuZGVyKCksdGhpcyl9LHQucHJvdG90eXBlLl9zaG93QWxsVGlja01hcmtzPWZ1bmN0aW9uKCl7dGhpcy5fdGlja01hcmtDb250YWluZXIuc2VsZWN0QWxsKCIuIit0LlRJQ0tfTUFSS19DTEFTUykuZWFjaChmdW5jdGlvbigpe3lkLnNlbGVjdCh0aGlzKS5zdHlsZSgidmlzaWJpbGl0eSIsImluaGVyaXQiKX0pfSx0LnByb3RvdHlwZS5fc2hvd0FsbFRpY2tMYWJlbHM9ZnVuY3Rpb24oKXt0aGlzLl90aWNrTGFiZWxDb250YWluZXIuc2VsZWN0QWxsKCIuIit0LlRJQ0tfTEFCRUxfQ0xBU1MpLmVhY2goZnVuY3Rpb24oKXt5ZC5zZWxlY3QodGhpcykuc3R5bGUoInZpc2liaWxpdHkiLCJpbmhlcml0Iil9KX0sdC5wcm90b3R5cGUuX2hpZGVPdmVyZmxvd2luZ1RpY2tMYWJlbHM9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLmVsZW1lbnQoKS5ub2RlKCkuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksbj10aGlzLl90aWNrTGFiZWxDb250YWluZXIuc2VsZWN0QWxsKCIuIit0LlRJQ0tfTEFCRUxfQ0xBU1MpO24uZW1wdHkoKXx8bi5lYWNoKGZ1bmN0aW9uKGksbyl7eFMuRE9NLmNsaWVudFJlY3RJbnNpZGUodGhpcy5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxyKXx8eWQuc2VsZWN0KHRoaXMpLnN0eWxlKCJ2aXNpYmlsaXR5IiwiaGlkZGVuIil9KX0sdC5wcm90b3R5cGUuX2hpZGVUaWNrTWFya3NXaXRob3V0TGFiZWw9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLl90aWNrTWFya0NvbnRhaW5lci5zZWxlY3RBbGwoIi4iK3QuVElDS19NQVJLX0NMQVNTKSxuPXRoaXMuX3RpY2tMYWJlbENvbnRhaW5lci5zZWxlY3RBbGwoIi4iK3QuVElDS19MQUJFTF9DTEFTUykuZmlsdGVyKGZ1bmN0aW9uKG8sYSl7dmFyIHM9eWQuc2VsZWN0KHRoaXMpLnN0eWxlKCJ2aXNpYmlsaXR5Iik7cmV0dXJuIHM9PT0iaW5oZXJpdCJ8fHM9PT0idmlzaWJsZSJ9KSxpPW4uZGF0YSgpO3IuZWFjaChmdW5jdGlvbihvLGEpe2kuaW5kZXhPZihvKT09PS0xJiZ5ZC5zZWxlY3QodGhpcykuc3R5bGUoInZpc2liaWxpdHkiLCJoaWRkZW4iKX0pfSx0LnByb3RvdHlwZS5pbnZhbGlkYXRlQ2FjaGU9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5pbnZhbGlkYXRlQ2FjaGUuY2FsbCh0aGlzKSx0aGlzLl9hbm5vdGF0aW9uTWVhc3VyZXIucmVzZXQoKX0sdC5FTkRfVElDS19NQVJLX0NMQVNTPSJlbmQtdGljay1tYXJrIix0LlRJQ0tfTUFSS19DTEFTUz0idGljay1tYXJrIix0LlRJQ0tfTEFCRUxfQ0xBU1M9InRpY2stbGFiZWwiLHQuQU5OT1RBVElPTl9MSU5FX0NMQVNTPSJhbm5vdGF0aW9uLWxpbmUiLHQuQU5OT1RBVElPTl9SRUNUX0NMQVNTPSJhbm5vdGF0aW9uLXJlY3QiLHQuQU5OT1RBVElPTl9DSVJDTEVfQ0xBU1M9ImFubm90YXRpb24tY2lyY2xlIix0LkFOTk9UQVRJT05fTEFCRUxfQ0xBU1M9ImFubm90YXRpb24tbGFiZWwiLHQuX0FOTk9UQVRJT05fTEFCRUxfUEFERElORz00LHR9KEF6ZS5Db21wb25lbnQpO05GLkF4aXM9SXplfSk7dmFyIEdYdD1IKFhpdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoWGl0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgTHplPShkZSgpLFV0KHBlKSksWWl0PShFcigpLFV0KE1yKSksREY9X2woKSxremU9a2MoKSxxWHQ9RmUoKSxqaXQ9UUEoKSxSemU9ZnVuY3Rpb24oZSl7THplLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQocixuKXtuPT09dm9pZCAwJiYobj0iYm90dG9tIik7dmFyIGk9ZS5jYWxsKHRoaXMscixuKXx8dGhpcztyZXR1cm4gaS5fdGlja0xhYmVsQW5nbGU9MCxpLl90aWNrTGFiZWxTaGVhckFuZ2xlPTAsaS5hZGRDbGFzcygiY2F0ZWdvcnktYXhpcyIpLGl9cmV0dXJuIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0LnByb3RvdHlwZSwiX3dyYXBwZXIiLHtnZXQ6ZnVuY3Rpb24oKXt2YXIgcj1uZXcgREYuV3JhcHBlcjtyZXR1cm4gdGhpcy5fdGlja0xhYmVsTWF4TGluZXMhPW51bGwmJnIubWF4TGluZXModGhpcy5fdGlja0xhYmVsTWF4TGluZXMpLHJ9LGVudW1lcmFibGU6ITAsY29uZmlndXJhYmxlOiEwfSksT2JqZWN0LmRlZmluZVByb3BlcnR5KHQucHJvdG90eXBlLCJfd3JpdGVyIix7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIG5ldyBERi5Xcml0ZXIodGhpcy5fbWVhc3VyZXIsdGhpcy5fdHlwZXNldHRlckNvbnRleHQsdGhpcy5fd3JhcHBlcil9LGVudW1lcmFibGU6ITAsY29uZmlndXJhYmxlOiEwfSksdC5wcm90b3R5cGUuX3NldHVwPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX3NldHVwLmNhbGwodGhpcyksdGhpcy5fdHlwZXNldHRlckNvbnRleHQ9bmV3IERGLlN2Z0NvbnRleHQodGhpcy5fdGlja0xhYmVsQ29udGFpbmVyLm5vZGUoKSksdGhpcy5fbWVhc3VyZXI9bmV3IERGLkNhY2hlTWVhc3VyZXIodGhpcy5fdHlwZXNldHRlckNvbnRleHQpfSx0LnByb3RvdHlwZS5fcmVzY2FsZT1mdW5jdGlvbigpe3JldHVybiB0aGlzLnJlZHJhdygpfSx0LnByb3RvdHlwZS5yZXF1ZXN0ZWRTcGFjZT1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXMuaXNIb3Jpem9udGFsKCk/MDp0aGlzLl90aWNrU3BhY2VSZXF1aXJlZCgpK3RoaXMubWFyZ2luKCksbz10aGlzLmlzSG9yaXpvbnRhbCgpP3RoaXMuX3RpY2tTcGFjZVJlcXVpcmVkKCkrdGhpcy5tYXJnaW4oKTowO2lmKHRoaXMuX3NjYWxlLmRvbWFpbigpLmxlbmd0aD09PTApcmV0dXJue21pbldpZHRoOjAsbWluSGVpZ2h0OjB9O2lmKHRoaXMuYW5ub3RhdGlvbnNFbmFibGVkKCkpe3ZhciBhPXRoaXMuX2Fubm90YXRpb25UaWVySGVpZ2h0KCkqdGhpcy5hbm5vdGF0aW9uVGllckNvdW50KCk7dGhpcy5pc0hvcml6b250YWwoKT9vKz1hOmkrPWF9dmFyIHM9dGhpcy5fbWVhc3VyZVRpY2tMYWJlbHMocixuKTtyZXR1cm57bWluV2lkdGg6cy51c2VkV2lkdGgraSxtaW5IZWlnaHQ6cy51c2VkSGVpZ2h0K299fSx0LnByb3RvdHlwZS5fY29yZVNpemU9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLmlzSG9yaXpvbnRhbCgpP3RoaXMuaGVpZ2h0KCk6dGhpcy53aWR0aCgpLG49dGhpcy5pc0hvcml6b250YWwoKT90aGlzLnJlcXVlc3RlZFNwYWNlKHRoaXMud2lkdGgoKSx0aGlzLmhlaWdodCgpKS5taW5IZWlnaHQ6dGhpcy5yZXF1ZXN0ZWRTcGFjZSh0aGlzLndpZHRoKCksdGhpcy5oZWlnaHQoKSkubWluV2lkdGgsaT10aGlzLm1hcmdpbigpK3RoaXMuX2Fubm90YXRpb25UaWVySGVpZ2h0KCksbz1uLWk7cmV0dXJuIE1hdGgubWluKG8scil9LHQucHJvdG90eXBlLl9nZXRUaWNrVmFsdWVzPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZ2V0RG93bnNhbXBsZUluZm8oKS5kb21haW59LHQucHJvdG90eXBlLl9zaXplRnJvbU9mZmVyPWZ1bmN0aW9uKHIsbil7cmV0dXJuIGt6ZS5Db21wb25lbnQucHJvdG90eXBlLl9zaXplRnJvbU9mZmVyLmNhbGwodGhpcyxyLG4pfSx0LnByb3RvdHlwZS5nZXREb3duc2FtcGxlSW5mbz1mdW5jdGlvbihyLG4pe3I9PT12b2lkIDAmJihyPXRoaXMuX3NjYWxlKSxuPT09dm9pZCAwJiYobj1yLmludmVydFJhbmdlKCkpO3ZhciBpPXRoaXMuX3RpY2tMYWJlbEFuZ2xlPT09MD8xOjEvTWF0aC5jb3ModGhpcy5fdGlja0xhYmVsU2hlYXJBbmdsZS8xODAqTWF0aC5QSSksbz10Ll9NSU5JTVVNX1dJRFRIX1BFUl9MQUJFTF9QWCppLGE9TWF0aC5jZWlsKG8vci5zdGVwV2lkdGgoKSk7cmV0dXJue2RvbWFpbjpuLmZpbHRlcihmdW5jdGlvbihzLGwpe3JldHVybiBsJWE9PT0wfSksc3RlcFdpZHRoOmEqci5zdGVwV2lkdGgoKX19LHQucHJvdG90eXBlLnRpY2tMYWJlbEFuZ2xlPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX3RpY2tMYWJlbEFuZ2xlO2lmKHIhPT0wJiZyIT09OTAmJnIhPT0tOTApdGhyb3cgbmV3IEVycm9yKCJBbmdsZSAiK3IrIiBub3Qgc3VwcG9ydGVkOyBvbmx5IDAsIDkwLCBhbmQgLTkwIGFyZSB2YWxpZCB2YWx1ZXMiKTtyZXR1cm4gdGhpcy5fdGlja0xhYmVsQW5nbGU9cix0aGlzLnJlZHJhdygpLHRoaXN9LHQucHJvdG90eXBlLnRpY2tMYWJlbFNoZWFyQW5nbGU9ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fdGlja0xhYmVsU2hlYXJBbmdsZTtpZihyPC04MHx8cj44MCl0aHJvdyBuZXcgRXJyb3IoIkFuZ2xlICIrcisiIG5vdCBzdXBwb3J0ZWQ7IE11c3QgYmUgYmV0d2VlbiBbLTgwLCA4MF0iKTtyZXR1cm4gdGhpcy5fdGlja0xhYmVsU2hlYXJBbmdsZT1yLHRoaXMucmVkcmF3KCksdGhpc30sdC5wcm90b3R5cGUudGlja0xhYmVsTWF4V2lkdGg9ZnVuY3Rpb24ocil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0wP3RoaXMuX3RpY2tMYWJlbE1heFdpZHRoOih0aGlzLl90aWNrTGFiZWxNYXhXaWR0aD1yLHRoaXMucmVkcmF3KCksdGhpcyl9LHQucHJvdG90eXBlLnRpY2tMYWJlbE1heExpbmVzPWZ1bmN0aW9uKHIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MD90aGlzLl90aWNrTGFiZWxNYXhMaW5lczoodGhpcy5fdGlja0xhYmVsTWF4TGluZXM9cix0aGlzLnJlZHJhdygpLHRoaXMpfSx0LnByb3RvdHlwZS5fdGlja1NwYWNlUmVxdWlyZWQ9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fbWF4TGFiZWxUaWNrTGVuZ3RoKCkrdGhpcy50aWNrTGFiZWxQYWRkaW5nKCl9LHQucHJvdG90eXBlLl9kcmF3VGlja3M9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzLG8sYTtzd2l0Y2godGhpcy50aWNrTGFiZWxBbmdsZSgpKXtjYXNlIDA6bz17bGVmdDoicmlnaHQiLHJpZ2h0OiJsZWZ0Iix0b3A6ImNlbnRlciIsYm90dG9tOiJjZW50ZXIifSxhPXtsZWZ0OiJjZW50ZXIiLHJpZ2h0OiJjZW50ZXIiLHRvcDoiYm90dG9tIixib3R0b206InRvcCJ9O2JyZWFrO2Nhc2UgOTA6bz17bGVmdDoiY2VudGVyIixyaWdodDoiY2VudGVyIix0b3A6InJpZ2h0Iixib3R0b206ImxlZnQifSxhPXtsZWZ0OiJ0b3AiLHJpZ2h0OiJib3R0b20iLHRvcDoiY2VudGVyIixib3R0b206ImNlbnRlciJ9O2JyZWFrO2Nhc2UtOTA6bz17bGVmdDoiY2VudGVyIixyaWdodDoiY2VudGVyIix0b3A6ImxlZnQiLGJvdHRvbToicmlnaHQifSxhPXtsZWZ0OiJib3R0b20iLHJpZ2h0OiJ0b3AiLHRvcDoiY2VudGVyIixib3R0b206ImNlbnRlciJ9O2JyZWFrfW4uZWFjaChmdW5jdGlvbihzKXt2YXIgbD1ZaXQuc2VsZWN0KHRoaXMpLGM9aS5pc0hvcml6b250YWwoKT9yOmkud2lkdGgoKS1pLl90aWNrU3BhY2VSZXF1aXJlZCgpLHU9aS5pc0hvcml6b250YWwoKT9pLmhlaWdodCgpLWkuX3RpY2tTcGFjZVJlcXVpcmVkKCk6cixoPXt4QWxpZ246b1tpLm9yaWVudGF0aW9uKCldLHlBbGlnbjphW2kub3JpZW50YXRpb24oKV0sdGV4dFJvdGF0aW9uOmkudGlja0xhYmVsQW5nbGUoKSx0ZXh0U2hlYXI6aS50aWNrTGFiZWxTaGVhckFuZ2xlKCl9O2lmKGkuX3RpY2tMYWJlbE1heFdpZHRoIT1udWxsKXtpZihpLm9yaWVudGF0aW9uKCk9PT0ibGVmdCImJmM+aS5fdGlja0xhYmVsTWF4V2lkdGgpe3ZhciBmPWMtaS5fdGlja0xhYmVsTWF4V2lkdGgscD1sLmF0dHIoInRyYW5zZm9ybSIpKyIgdHJhbnNsYXRlKCIrZisiLCAwKSI7bC5hdHRyKCJ0cmFuc2Zvcm0iLHApfWM9TWF0aC5taW4oYyxpLl90aWNrTGFiZWxNYXhXaWR0aCl9aS5fd3JpdGVyLndyaXRlKGkuZm9ybWF0dGVyKCkocyksYyx1LGgsbC5ub2RlKCkpfSl9LHQucHJvdG90eXBlLl9tZWFzdXJlVGlja0xhYmVscz1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXMsbz10aGlzLl9zY2FsZSxhPW8uY2xvbmVXaXRob3V0UHJvdmlkZXJzKCkucmFuZ2UoWzAsdGhpcy5pc0hvcml6b250YWwoKT9yOm5dKSxzPXRoaXMuZ2V0RG93bnNhbXBsZUluZm8oYSksbD1zLmRvbWFpbixjPXMuc3RlcFdpZHRoLHU9ci10aGlzLl90aWNrU3BhY2VSZXF1aXJlZCgpO3RoaXMuaXNIb3Jpem9udGFsKCkmJih1PWMsdGhpcy5fdGlja0xhYmVsQW5nbGUhPT0wJiYodT1uLXRoaXMuX3RpY2tTcGFjZVJlcXVpcmVkKCkpLHU9TWF0aC5tYXgodSwwKSk7dmFyIGg9Yzt0aGlzLmlzSG9yaXpvbnRhbCgpJiYoaD1uLXRoaXMuX3RpY2tTcGFjZVJlcXVpcmVkKCksdGhpcy5fdGlja0xhYmVsQW5nbGUhPT0wJiYoaD1yLXRoaXMuX3RpY2tTcGFjZVJlcXVpcmVkKCkpLGg9TWF0aC5tYXgoaCwwKSksdGhpcy5fdGlja0xhYmVsTWF4V2lkdGghPW51bGwmJih1PU1hdGgubWluKHUsdGhpcy5fdGlja0xhYmVsTWF4V2lkdGgpKTt2YXIgZj1sLm1hcChmdW5jdGlvbih4KXtyZXR1cm4gaS5fd3JhcHBlci53cmFwKGkuZm9ybWF0dGVyKCkoeCksaS5fbWVhc3VyZXIsdSxoKX0pLHA9dGhpcy5pc0hvcml6b250YWwoKSYmdGhpcy5fdGlja0xhYmVsQW5nbGU9PT0wP1lpdC5zdW06cVh0Lk1hdGgubWF4LGQ9dGhpcy5pc0hvcml6b250YWwoKSYmdGhpcy5fdGlja0xhYmVsQW5nbGU9PT0wP3FYdC5NYXRoLm1heDpZaXQuc3VtLGc9cChmLGZ1bmN0aW9uKHgpe3JldHVybiBpLl9tZWFzdXJlci5tZWFzdXJlKHgud3JhcHBlZFRleHQpLndpZHRofSwwKSxfPWQoZixmdW5jdGlvbih4KXtyZXR1cm4gaS5fbWVhc3VyZXIubWVhc3VyZSh4LndyYXBwZWRUZXh0KS5oZWlnaHR9LDApO3JldHVybiB0aGlzLl90aWNrTGFiZWxBbmdsZSE9PTAmJih5PVtfLGddLGc9eVswXSxfPXlbMV0pLHt1c2VkV2lkdGg6Zyx1c2VkSGVpZ2h0Ol99O3ZhciB5fSx0LnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseT1mdW5jdGlvbigpe3ZhciByPXRoaXM7ZS5wcm90b3R5cGUucmVuZGVySW1tZWRpYXRlbHkuY2FsbCh0aGlzKTt2YXIgbj10aGlzLl9zY2FsZSxpPXRoaXMuZ2V0RG93bnNhbXBsZUluZm8obiksbz1pLmRvbWFpbixhPWkuc3RlcFdpZHRoLHM9YTt0aGlzLmlzSG9yaXpvbnRhbCgpJiZ0aGlzLl90aWNrTGFiZWxNYXhXaWR0aCE9bnVsbCYmKHM9TWF0aC5taW4ocyx0aGlzLl90aWNrTGFiZWxNYXhXaWR0aCkpO3ZhciBsPWZ1bmN0aW9uKHAsZCl7dmFyIGc9bi5zY2FsZShwKS1zLzIsXz1yLmlzSG9yaXpvbnRhbCgpP2c6MCx5PXIuaXNIb3Jpem9udGFsKCk/MDpnO3JldHVybiJ0cmFuc2xhdGUoIitfKyIsIit5KyIpIn0sYz10aGlzLl90aWNrTGFiZWxDb250YWluZXIuc2VsZWN0QWxsKCIuIitqaXQuQXhpcy5USUNLX0xBQkVMX0NMQVNTKS5kYXRhKG8pLHU9Yy5lbnRlcigpLmFwcGVuZCgiZyIpLmNsYXNzZWQoaml0LkF4aXMuVElDS19MQUJFTF9DTEFTUywhMCkubWVyZ2UoYyk7Yy5leGl0KCkucmVtb3ZlKCksdS5hdHRyKCJ0cmFuc2Zvcm0iLGwpLHUudGV4dCgiIiksdGhpcy5fZHJhd1RpY2tzKGEsdSk7dmFyIGg9dGhpcy5vcmllbnRhdGlvbigpPT09InJpZ2h0Ij90aGlzLl90aWNrU3BhY2VSZXF1aXJlZCgpOjAsZj10aGlzLm9yaWVudGF0aW9uKCk9PT0iYm90dG9tIj90aGlzLl90aWNrU3BhY2VSZXF1aXJlZCgpOjA7cmV0dXJuIHRoaXMuX3RpY2tMYWJlbENvbnRhaW5lci5hdHRyKCJ0cmFuc2Zvcm0iLCJ0cmFuc2xhdGUoIitoKyIsIitmKyIpIiksdGhpcy5fc2hvd0FsbFRpY2tNYXJrcygpLHRoaXMuX3Nob3dBbGxUaWNrTGFiZWxzKCksdGhpcy5faGlkZVRpY2tNYXJrc1dpdGhvdXRMYWJlbCgpLHRoaXN9LHQucHJvdG90eXBlLmNvbXB1dGVMYXlvdXQ9ZnVuY3Rpb24ocixuLGkpe3JldHVybiBlLnByb3RvdHlwZS5jb21wdXRlTGF5b3V0LmNhbGwodGhpcyxyLG4saSksdGhpcy5pc0hvcml6b250YWwoKXx8dGhpcy5fc2NhbGUucmFuZ2UoWzAsdGhpcy5oZWlnaHQoKV0pLHRoaXN9LHQucHJvdG90eXBlLmludmFsaWRhdGVDYWNoZT1mdW5jdGlvbigpe2UucHJvdG90eXBlLmludmFsaWRhdGVDYWNoZS5jYWxsKHRoaXMpLHRoaXMuX21lYXN1cmVyLnJlc2V0KCl9LHQuX01JTklNVU1fV0lEVEhfUEVSX0xBQkVMX1BYPTE1LHR9KGppdC5BeGlzKTtYaXQuQ2F0ZWdvcnk9UnplfSk7dmFyIFdYdD1IKEtpdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoS2l0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgTnplPShkZSgpLFV0KHBlKSksT0Y9KEVyKCksVXQoTXIpKSwkaXQ9X2woKSxEemU9QnUoKSxiUz1GZSgpLHdTPVFBKCksT3plPWZ1bmN0aW9uKGUpe056ZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIsbil7dmFyIGk9ZS5jYWxsKHRoaXMscixuKXx8dGhpcztyZXR1cm4gaS5fdGlja0xhYmVsUG9zaXRpb25pbmc9ImNlbnRlciIsaS5fdXNlc1RleHRXaWR0aEFwcHJveGltYXRpb249ITEsaS5mb3JtYXR0ZXIoRHplLmdlbmVyYWwoKSksaX1yZXR1cm4gdC5wcm90b3R5cGUuX3NldHVwPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX3NldHVwLmNhbGwodGhpcyk7dmFyIHI9bmV3ICRpdC5TdmdDb250ZXh0KHRoaXMuX3RpY2tMYWJlbENvbnRhaW5lci5ub2RlKCksd1MuQXhpcy5USUNLX0xBQkVMX0NMQVNTKTt0aGlzLl9tZWFzdXJlcj1uZXcgJGl0LkNhY2hlTWVhc3VyZXIociksdGhpcy5fd3JhcHBlcj1uZXcgJGl0LldyYXBwZXIoKS5tYXhMaW5lcygxKX0sdC5wcm90b3R5cGUuX2NvbXB1dGVXaWR0aD1mdW5jdGlvbigpe3ZhciByPXRoaXMuX3VzZXNUZXh0V2lkdGhBcHByb3hpbWF0aW9uP3RoaXMuX2NvbXB1dGVBcHByb3hpbWF0ZVRleHRXaWR0aCgpOnRoaXMuX2NvbXB1dGVFeGFjdFRleHRXaWR0aCgpO3JldHVybiB0aGlzLl90aWNrTGFiZWxQb3NpdGlvbmluZz09PSJjZW50ZXIiP3RoaXMuX21heExhYmVsVGlja0xlbmd0aCgpK3RoaXMudGlja0xhYmVsUGFkZGluZygpK3I6TWF0aC5tYXgodGhpcy5fbWF4TGFiZWxUaWNrTGVuZ3RoKCksdGhpcy50aWNrTGFiZWxQYWRkaW5nKCkrcil9LHQucHJvdG90eXBlLl9jb21wdXRlRXhhY3RUZXh0V2lkdGg9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLG49dGhpcy5fZ2V0VGlja1ZhbHVlcygpLGk9bi5tYXAoZnVuY3Rpb24obyl7dmFyIGE9ci5mb3JtYXR0ZXIoKShvKTtyZXR1cm4gci5fbWVhc3VyZXIubWVhc3VyZShhKS53aWR0aH0pO3JldHVybiBiUy5NYXRoLm1heChpLDApfSx0LnByb3RvdHlwZS5fY29tcHV0ZUFwcHJveGltYXRlVGV4dFdpZHRoPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcyxuPXRoaXMuX2dldFRpY2tWYWx1ZXMoKSxpPXRoaXMuX21lYXN1cmVyLm1lYXN1cmUoIk0iKS53aWR0aCxvPW4ubWFwKGZ1bmN0aW9uKGEpe3ZhciBzPXIuZm9ybWF0dGVyKCkoYSk7cmV0dXJuIHMubGVuZ3RoKml9KTtyZXR1cm4gYlMuTWF0aC5tYXgobywwKX0sdC5wcm90b3R5cGUuX2NvbXB1dGVIZWlnaHQ9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLl9tZWFzdXJlci5tZWFzdXJlKCkuaGVpZ2h0O3JldHVybiB0aGlzLl90aWNrTGFiZWxQb3NpdGlvbmluZz09PSJjZW50ZXIiP3RoaXMuX21heExhYmVsVGlja0xlbmd0aCgpK3RoaXMudGlja0xhYmVsUGFkZGluZygpK3I6TWF0aC5tYXgodGhpcy5fbWF4TGFiZWxUaWNrTGVuZ3RoKCksdGhpcy50aWNrTGFiZWxQYWRkaW5nKCkrcil9LHQucHJvdG90eXBlLl9nZXRUaWNrVmFsdWVzPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcy5fc2NhbGUsbj1yLmRvbWFpbigpLGk9blswXTw9blsxXT9uWzBdOm5bMV0sbz1uWzBdPj1uWzFdP25bMF06blsxXTtyZXR1cm4gci50aWNrcygpLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYT49aSYmYTw9b30pfSx0LnByb3RvdHlwZS5fcmVzY2FsZT1mdW5jdGlvbigpe2lmKCEhdGhpcy5faXNTZXR1cCl7aWYoIXRoaXMuaXNIb3Jpem9udGFsKCkpe3ZhciByPXRoaXMuX2NvbXB1dGVXaWR0aCgpO2lmKHI+dGhpcy53aWR0aCgpfHxyPHRoaXMud2lkdGgoKS10aGlzLm1hcmdpbigpKXt0aGlzLnJlZHJhdygpO3JldHVybn19dGhpcy5yZW5kZXIoKX19LHQucHJvdG90eXBlLnJlbmRlckltbWVkaWF0ZWx5PWZ1bmN0aW9uKCl7dmFyIHI9dGhpcztlLnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseS5jYWxsKHRoaXMpO3ZhciBuPXt4OjAseTowLGR4OiIwZW0iLGR5OiIwLjNlbSJ9LGk9dGhpcy5fbWF4TGFiZWxUaWNrTGVuZ3RoKCksbz10aGlzLnRpY2tMYWJlbFBhZGRpbmcoKSxhPSJtaWRkbGUiLHM9MCxsPTAsYz0wLHU9MDtpZih0aGlzLmlzSG9yaXpvbnRhbCgpKXN3aXRjaCh0aGlzLl90aWNrTGFiZWxQb3NpdGlvbmluZyl7Y2FzZSJsZWZ0IjphPSJlbmQiLHM9LW8sdT1vO2JyZWFrO2Nhc2UiY2VudGVyIjp1PWkrbzticmVhaztjYXNlInJpZ2h0IjphPSJzdGFydCIscz1vLHU9bzticmVha31lbHNlIHN3aXRjaCh0aGlzLl90aWNrTGFiZWxQb3NpdGlvbmluZyl7Y2FzZSJ0b3AiOm4uZHk9Ii0wLjNlbSIsYz1vLGw9LW87YnJlYWs7Y2FzZSJjZW50ZXIiOmM9aStvO2JyZWFrO2Nhc2UiYm90dG9tIjpuLmR5PSIxZW0iLGM9byxsPW87YnJlYWt9dmFyIGg9dGhpcy5fZ2VuZXJhdGVUaWNrTWFya0F0dHJIYXNoKCk7c3dpdGNoKHRoaXMub3JpZW50YXRpb24oKSl7Y2FzZSJib3R0b20iOm4ueD1oLngxLG4uZHk9IjAuOTVlbSIsbD1oLnkxK3U7YnJlYWs7Y2FzZSJ0b3AiOm4ueD1oLngxLG4uZHk9Ii0uMjVlbSIsbD1oLnkxLXU7YnJlYWs7Y2FzZSJsZWZ0IjphPSJlbmQiLHM9aC54MS1jLG4ueT1oLnkxO2JyZWFrO2Nhc2UicmlnaHQiOmE9InN0YXJ0IixzPWgueDErYyxuLnk9aC55MTticmVha312YXIgZj10aGlzLl9nZXRUaWNrVmFsdWVzKCkscD10aGlzLl90aWNrTGFiZWxDb250YWluZXIuc2VsZWN0QWxsKCIuIit3Uy5BeGlzLlRJQ0tfTEFCRUxfQ0xBU1MpLmRhdGEoZik7cC5leGl0KCkucmVtb3ZlKCk7dmFyIGQ9cC5lbnRlcigpLmFwcGVuZCgidGV4dCIpLmNsYXNzZWQod1MuQXhpcy5USUNLX0xBQkVMX0NMQVNTLCEwKS5tZXJnZShwKTtkLnN0eWxlKCJ0ZXh0LWFuY2hvciIsYSkuc3R5bGUoInZpc2liaWxpdHkiLCJpbmhlcml0IikuYXR0cnMobikudGV4dChmdW5jdGlvbihfKXtyZXR1cm4gci5mb3JtYXR0ZXIoKShfKX0pO3ZhciBnPSJ0cmFuc2xhdGUoIitzKyIsICIrbCsiKSI7cmV0dXJuIHRoaXMuX3RpY2tMYWJlbENvbnRhaW5lci5hdHRyKCJ0cmFuc2Zvcm0iLGcpLHRoaXMuX3Nob3dBbGxUaWNrTWFya3MoKSx0aGlzLnNob3dFbmRUaWNrTGFiZWxzKCl8fHRoaXMuX2hpZGVFbmRUaWNrTGFiZWxzKCksdGhpcy5faGlkZU92ZXJmbG93aW5nVGlja0xhYmVscygpLHRoaXMuX2hpZGVPdmVybGFwcGluZ1RpY2tMYWJlbHMoKSx0aGlzLl90aWNrTGFiZWxQb3NpdGlvbmluZyE9PSJjZW50ZXIiJiZ0aGlzLl9oaWRlVGlja01hcmtzV2l0aG91dExhYmVsKCksdGhpc30sdC5wcm90b3R5cGUudGlja0xhYmVsUG9zaXRpb249ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fdGlja0xhYmVsUG9zaXRpb25pbmc7dmFyIG49ci50b0xvd2VyQ2FzZSgpO2lmKHRoaXMuaXNIb3Jpem9udGFsKCkpe2lmKCEobj09PSJsZWZ0Inx8bj09PSJjZW50ZXIifHxuPT09InJpZ2h0IikpdGhyb3cgbmV3IEVycm9yKG4rIiBpcyBub3QgYSB2YWxpZCB0aWNrIGxhYmVsIHBvc2l0aW9uIGZvciBhIGhvcml6b250YWwgTnVtZXJpY0F4aXMiKX1lbHNlIGlmKCEobj09PSJ0b3AifHxuPT09ImNlbnRlciJ8fG49PT0iYm90dG9tIikpdGhyb3cgbmV3IEVycm9yKG4rIiBpcyBub3QgYSB2YWxpZCB0aWNrIGxhYmVsIHBvc2l0aW9uIGZvciBhIHZlcnRpY2FsIE51bWVyaWNBeGlzIik7cmV0dXJuIHRoaXMuX3RpY2tMYWJlbFBvc2l0aW9uaW5nPW4sdGhpcy5yZWRyYXcoKSx0aGlzfSx0LnByb3RvdHlwZS51c2VzVGV4dFdpZHRoQXBwcm94aW1hdGlvbj1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl91c2VzVGV4dFdpZHRoQXBwcm94aW1hdGlvbjoodGhpcy5fdXNlc1RleHRXaWR0aEFwcHJveGltYXRpb249cix0aGlzKX0sdC5wcm90b3R5cGUuX2hpZGVFbmRUaWNrTGFiZWxzPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcy5lbGVtZW50KCkubm9kZSgpLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLG49dGhpcy5fdGlja0xhYmVsQ29udGFpbmVyLnNlbGVjdEFsbCgiLiIrd1MuQXhpcy5USUNLX0xBQkVMX0NMQVNTKTtpZihuLnNpemUoKSE9PTApe3ZhciBpPW4ubm9kZXMoKVswXTtiUy5ET00uY2xpZW50UmVjdEluc2lkZShpLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLHIpfHxPRi5zZWxlY3QoaSkuc3R5bGUoInZpc2liaWxpdHkiLCJoaWRkZW4iKTt2YXIgbz1uLm5vZGVzKClbbi5zaXplKCktMV07YlMuRE9NLmNsaWVudFJlY3RJbnNpZGUoby5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxyKXx8T0Yuc2VsZWN0KG8pLnN0eWxlKCJ2aXNpYmlsaXR5IiwiaGlkZGVuIil9fSx0LnByb3RvdHlwZS5faGlkZU92ZXJsYXBwaW5nVGlja0xhYmVscz1mdW5jdGlvbigpe2Zvcih2YXIgcj10aGlzLl90aWNrTGFiZWxDb250YWluZXIuc2VsZWN0QWxsKCIuIit3Uy5BeGlzLlRJQ0tfTEFCRUxfQ0xBU1MpLmZpbHRlcihmdW5jdGlvbihvLGEpe3ZhciBzPU9GLnNlbGVjdCh0aGlzKS5zdHlsZSgidmlzaWJpbGl0eSIpO3JldHVybiBzPT09ImluaGVyaXQifHxzPT09InZpc2libGUifSksbj1yLm5vZGVzKCkubWFwKGZ1bmN0aW9uKG8pe3JldHVybiBvLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpfSksaT0xOyF0aGlzLl9oYXNPdmVybGFwV2l0aEludGVydmFsKGksbikmJmk8bi5sZW5ndGg7KWkrPTE7ci5lYWNoKGZ1bmN0aW9uKG8sYSl7dmFyIHM9T0Yuc2VsZWN0KHRoaXMpO2ElaSE9PTAmJnMuc3R5bGUoInZpc2liaWxpdHkiLCJoaWRkZW4iKX0pfSx0LnByb3RvdHlwZS5faGFzT3ZlcmxhcFdpdGhJbnRlcnZhbD1mdW5jdGlvbihyLG4pe2Zvcih2YXIgaT10aGlzLl90aWNrTGFiZWxQb3NpdGlvbmluZz09PSJjZW50ZXIiP3RoaXMudGlja0xhYmVsUGFkZGluZygpOnRoaXMudGlja0xhYmVsUGFkZGluZygpKjMsbz1uLm1hcChmdW5jdGlvbihjKXtyZXR1cm4gYlMuRE9NLmV4cGFuZFJlY3QoYyxpKX0pLGE9MDthPG8ubGVuZ3RoLXI7YSs9cil7dmFyIHM9b1thXSxsPW9bYStyXTtpZihiUy5ET00uY2xpZW50UmVjdHNPdmVybGFwKHMsbCkpcmV0dXJuITF9cmV0dXJuITB9LHQucHJvdG90eXBlLmludmFsaWRhdGVDYWNoZT1mdW5jdGlvbigpe2UucHJvdG90eXBlLmludmFsaWRhdGVDYWNoZS5jYWxsKHRoaXMpLHRoaXMuX21lYXN1cmVyLnJlc2V0KCl9LHR9KHdTLkF4aXMpO0tpdC5OdW1lcmljPU96ZX0pO3ZhciBZWHQ9SCh6Rj0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoekYsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciB6emU9RmUoKTtmdW5jdGlvbiBGemUoZSl7aWYoZTw9MCl0aHJvdyBuZXcgRXJyb3IoImludGVydmFsIG11c3QgYmUgcG9zaXRpdmUgbnVtYmVyIik7cmV0dXJuIGZ1bmN0aW9uKHQpe3ZhciByPXQuZG9tYWluKCksbj1NYXRoLm1pbihyWzBdLHJbMV0pLGk9TWF0aC5tYXgoclswXSxyWzFdKSxvPU1hdGguY2VpbChuL2UpKmUsYT1NYXRoLmZsb29yKChpLW8pL2UpKzEscz1uJWU9PT0wP1tdOltuXSxsPXp6ZS5NYXRoLnJhbmdlKDAsYSkubWFwKGZ1bmN0aW9uKHUpe3JldHVybiBvK3UqZX0pLGM9aSVlPT09MD9bXTpbaV07cmV0dXJuIHMuY29uY2F0KGwpLmNvbmNhdChjKX19ekYuaW50ZXJ2YWxUaWNrR2VuZXJhdG9yPUZ6ZTtmdW5jdGlvbiBCemUoKXtyZXR1cm4gZnVuY3Rpb24oZSl7dmFyIHQ9ZS5kZWZhdWx0VGlja3MoKTtyZXR1cm4gdC5maWx0ZXIoZnVuY3Rpb24ocixuKXtyZXR1cm4gciUxPT09MHx8bj09PTB8fG49PT10Lmxlbmd0aC0xfSl9fXpGLmludGVnZXJUaWNrR2VuZXJhdG9yPUJ6ZX0pO3ZhciB0ND1IKEkxPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShJMSwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7ZnVuY3Rpb24gSml0KGUsdCxyKXtyZXR1cm4gci0oci1lKSp0fUkxLnpvb21PdXQ9Sml0O2Z1bmN0aW9uIFppdChlLHQscil7cmV0dXJuKGUqdC1yKS8odC0xKX1mdW5jdGlvbiBIemUoZSx0LHIsbixpLG8sYSl7cmV0dXJuIHQ9alh0KGUsdCxuLGkpLFhYdChlLHQscixvLGEpfUkxLmNvbnN0cmFpbmVkWm9vbT1IemU7ZnVuY3Rpb24galh0KGUsdCxyLG4pe3ZhciBpPXQ+MSxvPWk/bjpyO2lmKG89PW51bGwpcmV0dXJuIHQ7dmFyIGE9ZS5nZXRUcmFuc2Zvcm1hdGlvbkRvbWFpbigpLHM9YVswXSxsPWFbMV0sYz1NYXRoLmFicyhsLXMpLHU9aT9NYXRoLm1pbjpNYXRoLm1heDtyZXR1cm4gdSh0LG8vYyl9STEuY29uc3RyYWluWm9vbUV4dGVudHM9alh0O2Z1bmN0aW9uIFhYdChlLHQscixuLGkpe2lmKHQ8PTEpcmV0dXJue2NlbnRlclBvaW50OnIsem9vbUFtb3VudDp0fTtpZihuPT1udWxsJiZpPT1udWxsKXJldHVybntjZW50ZXJQb2ludDpyLHpvb21BbW91bnQ6dH07dmFyIG89JFh0KGUpLGE9VXplKGUpLHM9YT8xLzA6LTEvMCxsPWE/LTEvMDoxLzA7bj1uPT1udWxsP3M6bixpPWk9PW51bGw/bDppO3ZhciBjPWUuZ2V0VHJhbnNmb3JtYXRpb25Eb21haW4oKSx1PWNbMF0saD1jWzFdLGY9ZS5zY2FsZVRyYW5zZm9ybWF0aW9uKGkpLHA9ZS5zY2FsZVRyYW5zZm9ybWF0aW9uKGgpLGQ9Sml0KHAsdCxyKSxnPWUuc2NhbGVUcmFuc2Zvcm1hdGlvbihuKSxfPWUuc2NhbGVUcmFuc2Zvcm1hdGlvbih1KSx5PUppdChfLHQscikseD1NYXRoLmFicyhmLWcpLGI9TWF0aC5hYnMoZC15KTtpZihiPngpe3ZhciBTPShmLWcpLyhwLV8pO2lmKFMhPT0xKXt2YXIgQz1aaXQocCxTLGYpO3JldHVybntjZW50ZXJQb2ludDpDLHpvb21BbW91bnQ6U319ZWxzZSByZXR1cm57Y2VudGVyUG9pbnQ6cix6b29tQW1vdW50OlN9fWVsc2UgcmV0dXJuIGQ+ZiE9bz97Y2VudGVyUG9pbnQ6Wml0KHAsdCxmKSx6b29tQW1vdW50OnR9Onk8ZyE9bz97Y2VudGVyUG9pbnQ6Wml0KF8sdCxnKSx6b29tQW1vdW50OnR9OntjZW50ZXJQb2ludDpyLHpvb21BbW91bnQ6dH19STEuY29uc3RyYWluWm9vbVZhbHVlcz1YWHQ7ZnVuY3Rpb24gVnplKGUsdCxyLG4pe3ZhciBpPWUuZ2V0VHJhbnNmb3JtYXRpb25Eb21haW4oKSxvPWlbMF0sYT1pWzFdLHM9JFh0KGUpO2lmKHQ+MCE9PXMpe3ZhciBsPW47aWYobCE9bnVsbCl7dmFyIGM9ZS5zY2FsZVRyYW5zZm9ybWF0aW9uKGEpLHU9ZS5zY2FsZVRyYW5zZm9ybWF0aW9uKGwpO3Q9KHM/TWF0aC5tYXg6TWF0aC5taW4pKGMrdCx1KS1jfX1lbHNle3ZhciBsPXI7aWYobCE9bnVsbCl7dmFyIGg9ZS5zY2FsZVRyYW5zZm9ybWF0aW9uKG8pLGY9ZS5zY2FsZVRyYW5zZm9ybWF0aW9uKGwpO3Q9KHM/TWF0aC5taW46TWF0aC5tYXgpKGgrdCxmKS1ofX1yZXR1cm4gdH1JMS5jb25zdHJhaW5lZFRyYW5zbGF0aW9uPVZ6ZTtmdW5jdGlvbiAkWHQoZSl7dmFyIHQ9ZS5yYW5nZSgpO3JldHVybiB0WzFdPHRbMF19ZnVuY3Rpb24gVXplKGUpe3ZhciB0PWUuZ2V0VHJhbnNmb3JtYXRpb25Eb21haW4oKTtyZXR1cm4gdFsxXTx0WzBdfX0pO3ZhciBMMT1IKFFpdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoUWl0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgS1h0PUZlKCkscXplPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe3RoaXMuX2F1dG9Eb21haW5BdXRvbWF0aWNhbGx5PSEwLHRoaXMuX2RvbWFpbk1vZGlmaWNhdGlvbkluUHJvZ3Jlc3M9ITEsdGhpcy5fdXBkYXRlSWQ9MCx0aGlzLl9jYWxsYmFja3M9bmV3IEtYdC5DYWxsYmFja1NldCx0aGlzLl9pbmNsdWRlZFZhbHVlc1Byb3ZpZGVycz1uZXcgS1h0LlNldH1yZXR1cm4gZS5wcm90b3R5cGUuZXh0ZW50T2ZWYWx1ZXM9ZnVuY3Rpb24odCl7cmV0dXJuW119LGUucHJvdG90eXBlLl9nZXRBbGxJbmNsdWRlZFZhbHVlcz1mdW5jdGlvbih0KXt2YXIgcj10aGlzO3Q9PT12b2lkIDAmJih0PSExKTt2YXIgbj1bXTtyZXR1cm4gdGhpcy5faW5jbHVkZWRWYWx1ZXNQcm92aWRlcnMuZm9yRWFjaChmdW5jdGlvbihpKXt2YXIgbz1pKHIsdCk7bj1uLmNvbmNhdChvKX0pLG59LGUucHJvdG90eXBlLl9nZXRFeHRlbnQ9ZnVuY3Rpb24oKXtyZXR1cm5bXX0sZS5wcm90b3R5cGUub25VcGRhdGU9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX2NhbGxiYWNrcy5hZGQodCksdGhpc30sZS5wcm90b3R5cGUub2ZmVXBkYXRlPWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLl9jYWxsYmFja3MuZGVsZXRlKHQpLHRoaXN9LGUucHJvdG90eXBlLl9kaXNwYXRjaFVwZGF0ZT1mdW5jdGlvbigpe3RoaXMuX3VwZGF0ZUlkKyssdGhpcy5fY2FsbGJhY2tzLmNhbGxDYWxsYmFja3ModGhpcyl9LGUucHJvdG90eXBlLmF1dG9Eb21haW49ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fYXV0b0RvbWFpbkF1dG9tYXRpY2FsbHk9ITAsdGhpcy5fc2V0RG9tYWluKHRoaXMuX2dldEV4dGVudCgpKSx0aGlzfSxlLnByb3RvdHlwZS5hdXRvRG9tYWluSWZBdXRvbWF0aWNNb2RlPWZ1bmN0aW9uKCl7dGhpcy5fYXV0b0RvbWFpbkF1dG9tYXRpY2FsbHkmJnRoaXMuYXV0b0RvbWFpbigpfSxlLnByb3RvdHlwZS5zY2FsZT1mdW5jdGlvbih0KXt0aHJvdyBuZXcgRXJyb3IoIlN1YmNsYXNzZXMgc2hvdWxkIG92ZXJyaWRlIHNjYWxlIil9LGUucHJvdG90eXBlLnRpY2tzPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZG9tYWluKCl9LGUucHJvdG90eXBlLmRvbWFpbj1mdW5jdGlvbih0KXtyZXR1cm4gdD09bnVsbD90aGlzLl9nZXREb21haW4oKToodGhpcy5fYXV0b0RvbWFpbkF1dG9tYXRpY2FsbHk9ITEsdGhpcy5fc2V0RG9tYWluKHQpLHRoaXMpfSxlLnByb3RvdHlwZS5fZ2V0RG9tYWluPWZ1bmN0aW9uKCl7dGhyb3cgbmV3IEVycm9yKCJTdWJjbGFzc2VzIHNob3VsZCBvdmVycmlkZSBfZ2V0RG9tYWluIil9LGUucHJvdG90eXBlLl9zZXREb21haW49ZnVuY3Rpb24odCl7dGhpcy5fZG9tYWluTW9kaWZpY2F0aW9uSW5Qcm9ncmVzc3x8KHRoaXMuX2RvbWFpbk1vZGlmaWNhdGlvbkluUHJvZ3Jlc3M9ITAsdGhpcy5fYmFja2luZ1NjYWxlRG9tYWluKHQpLHRoaXMuX2Rpc3BhdGNoVXBkYXRlKCksdGhpcy5fZG9tYWluTW9kaWZpY2F0aW9uSW5Qcm9ncmVzcz0hMSl9LGUucHJvdG90eXBlLl9iYWNraW5nU2NhbGVEb21haW49ZnVuY3Rpb24odCl7dGhyb3cgbmV3IEVycm9yKCJTdWJjbGFzc2VzIHNob3VsZCBvdmVycmlkZSBfYmFja2luZ0RvbWFpbiIpfSxlLnByb3RvdHlwZS5yYW5nZT1mdW5jdGlvbih0KXtyZXR1cm4gdD09bnVsbD90aGlzLl9nZXRSYW5nZSgpOih0aGlzLl9zZXRSYW5nZSh0KSx0aGlzKX0sZS5wcm90b3R5cGUuX2dldFJhbmdlPWZ1bmN0aW9uKCl7dGhyb3cgbmV3IEVycm9yKCJTdWJjbGFzc2VzIHNob3VsZCBvdmVycmlkZSBfZ2V0UmFuZ2UiKX0sZS5wcm90b3R5cGUuX3NldFJhbmdlPWZ1bmN0aW9uKHQpe3Rocm93IG5ldyBFcnJvcigiU3ViY2xhc3NlcyBzaG91bGQgb3ZlcnJpZGUgX3NldFJhbmdlIil9LGUucHJvdG90eXBlLmFkZEluY2x1ZGVkVmFsdWVzUHJvdmlkZXI9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX2luY2x1ZGVkVmFsdWVzUHJvdmlkZXJzLmFkZCh0KSx0aGlzLmF1dG9Eb21haW5JZkF1dG9tYXRpY01vZGUoKSx0aGlzfSxlLnByb3RvdHlwZS5yZW1vdmVJbmNsdWRlZFZhbHVlc1Byb3ZpZGVyPWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLl9pbmNsdWRlZFZhbHVlc1Byb3ZpZGVycy5kZWxldGUodCksdGhpcy5hdXRvRG9tYWluSWZBdXRvbWF0aWNNb2RlKCksdGhpc30sZS5wcm90b3R5cGUudXBkYXRlSWQ9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fdXBkYXRlSWR9LGV9KCk7UWl0LlNjYWxlPXF6ZX0pO3ZhciByb3Q9SChlb3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGVvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIEd6ZT0oZGUoKSxVdChwZSkpLEZGPShFcigpLFV0KE1yKSksV3plPXQ0KCksWlh0PUZlKCksWXplPUwxKCksdG90PVswLDFdLGp6ZT1mdW5jdGlvbihlKXtHemUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3ZhciByPWUuY2FsbCh0aGlzKXx8dGhpcztyLl9yYW5nZT1bMCwxXSxyLl9kM1NjYWxlPUZGLnNjYWxlQmFuZCgpLHIuX2QzU2NhbGUucmFuZ2UodG90KSxyLl9kM1RyYW5zZm9ybWF0aW9uU2NhbGU9RkYuc2NhbGVMaW5lYXIoKSxyLl9kM1RyYW5zZm9ybWF0aW9uU2NhbGUuZG9tYWluKHRvdCk7dmFyIG49LjM7cmV0dXJuIHIuX2lubmVyUGFkZGluZz10Ll9jb252ZXJ0VG9QbG90dGFibGVJbm5lclBhZGRpbmcobiksci5fb3V0ZXJQYWRkaW5nPXQuX2NvbnZlcnRUb1Bsb3R0YWJsZU91dGVyUGFkZGluZyguNSxuKSxyfXJldHVybiB0LnByb3RvdHlwZS5jbG9uZVdpdGhvdXRQcm92aWRlcnM9ZnVuY3Rpb24oKXt2YXIgcj1uZXcgdCgpLmRvbWFpbih0aGlzLmRvbWFpbigpKS5yYW5nZSh0aGlzLnJhbmdlKCkpLmlubmVyUGFkZGluZyh0aGlzLmlubmVyUGFkZGluZygpKS5vdXRlclBhZGRpbmcodGhpcy5vdXRlclBhZGRpbmcoKSk7cmV0dXJuIHIuX2QzVHJhbnNmb3JtYXRpb25TY2FsZS5kb21haW4odGhpcy5fZDNUcmFuc2Zvcm1hdGlvblNjYWxlLmRvbWFpbigpKSxyfSx0LnByb3RvdHlwZS5leHRlbnRPZlZhbHVlcz1mdW5jdGlvbihyKXtyZXR1cm4gWlh0LkFycmF5LnVuaXEocil9LHQucHJvdG90eXBlLl9nZXRFeHRlbnQ9ZnVuY3Rpb24oKXtyZXR1cm4gWlh0LkFycmF5LnVuaXEodGhpcy5fZ2V0QWxsSW5jbHVkZWRWYWx1ZXMoKSl9LHQucHJvdG90eXBlLmRvbWFpbj1mdW5jdGlvbihyKXtyZXR1cm4gZS5wcm90b3R5cGUuZG9tYWluLmNhbGwodGhpcyxyKX0sdC5wcm90b3R5cGUuaW52ZXJ0UmFuZ2U9ZnVuY3Rpb24ocil7dmFyIG49dGhpcztyPT09dm9pZCAwJiYocj10aGlzLnJhbmdlKCkpO3ZhciBpPXRoaXMuX2QzU2NhbGUuYmFuZHdpZHRoKCksbz10aGlzLmludmVydGVkVHJhbnNmb3JtYXRpb24oclswXSksYT10aGlzLmludmVydGVkVHJhbnNmb3JtYXRpb24oclsxXSkscz10aGlzLl9kM1NjYWxlLmRvbWFpbigpLGw9cy5tYXAoZnVuY3Rpb24oaCl7cmV0dXJuIG4uX2QzU2NhbGUoaCkraS8yfSksYz1GRi5iaXNlY3QobCxvKSx1PUZGLmJpc2VjdChsLGEpO3JldHVybiBzLnNsaWNlKGMsdSl9LHQucHJvdG90eXBlLnJhbmdlPWZ1bmN0aW9uKHIpe3JldHVybiBlLnByb3RvdHlwZS5yYW5nZS5jYWxsKHRoaXMscil9LHQuX2NvbnZlcnRUb1Bsb3R0YWJsZUlubmVyUGFkZGluZz1mdW5jdGlvbihyKXtyZXR1cm4gMS8oMS1yKS0xfSx0Ll9jb252ZXJ0VG9QbG90dGFibGVPdXRlclBhZGRpbmc9ZnVuY3Rpb24ocixuKXtyZXR1cm4gci8oMS1uKX0sdC5wcm90b3R5cGUuX3NldEJhbmRzPWZ1bmN0aW9uKCl7dmFyIHI9MS0xLygxK3RoaXMuaW5uZXJQYWRkaW5nKCkpLG49dGhpcy5vdXRlclBhZGRpbmcoKS8oMSt0aGlzLmlubmVyUGFkZGluZygpKTt0aGlzLl9kM1NjYWxlLnBhZGRpbmdJbm5lcihyKSx0aGlzLl9kM1NjYWxlLnBhZGRpbmdPdXRlcihuKX0sdC5wcm90b3R5cGUucmFuZ2VCYW5kPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3Jlc2NhbGVCYW5kKHRoaXMuX2QzU2NhbGUuYmFuZHdpZHRoKCkpfSx0LnByb3RvdHlwZS5zdGVwV2lkdGg9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fcmVzY2FsZUJhbmQodGhpcy5fZDNTY2FsZS5iYW5kd2lkdGgoKSooMSt0aGlzLmlubmVyUGFkZGluZygpKSl9LHQucHJvdG90eXBlLnRpY2tzPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZG9tYWluKCl9LHQucHJvdG90eXBlLmlubmVyUGFkZGluZz1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9pbm5lclBhZGRpbmc6KHRoaXMuX2lubmVyUGFkZGluZz1yLHRoaXMucmFuZ2UodGhpcy5yYW5nZSgpKSx0aGlzLl9kaXNwYXRjaFVwZGF0ZSgpLHRoaXMpfSx0LnByb3RvdHlwZS5vdXRlclBhZGRpbmc9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fb3V0ZXJQYWRkaW5nOih0aGlzLl9vdXRlclBhZGRpbmc9cix0aGlzLnJhbmdlKHRoaXMucmFuZ2UoKSksdGhpcy5fZGlzcGF0Y2hVcGRhdGUoKSx0aGlzKX0sdC5wcm90b3R5cGUuc2NhbGU9ZnVuY3Rpb24ocil7dmFyIG49dGhpcy5fZDNTY2FsZShyKSt0aGlzLl9kM1NjYWxlLmJhbmR3aWR0aCgpLzI7cmV0dXJuIHRoaXMuX2QzVHJhbnNmb3JtYXRpb25TY2FsZShuKX0sdC5wcm90b3R5cGUuem9vbT1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXMsbz1mdW5jdGlvbihhKXtyZXR1cm4gaS5fZDNUcmFuc2Zvcm1hdGlvblNjYWxlLmludmVydChXemUuem9vbU91dChhLHIsbikpfTt0aGlzLl9kM1RyYW5zZm9ybWF0aW9uU2NhbGUuZG9tYWluKHRoaXMuX2QzVHJhbnNmb3JtYXRpb25TY2FsZS5yYW5nZSgpLm1hcChvKSksdGhpcy5fZGlzcGF0Y2hVcGRhdGUoKX0sdC5wcm90b3R5cGUucGFuPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMsaT1mdW5jdGlvbihvKXtyZXR1cm4gbi5fZDNUcmFuc2Zvcm1hdGlvblNjYWxlLmludmVydChvK3IpfTt0aGlzLl9kM1RyYW5zZm9ybWF0aW9uU2NhbGUuZG9tYWluKHRoaXMuX2QzVHJhbnNmb3JtYXRpb25TY2FsZS5yYW5nZSgpLm1hcChpKSksdGhpcy5fZGlzcGF0Y2hVcGRhdGUoKX0sdC5wcm90b3R5cGUuc2NhbGVUcmFuc2Zvcm1hdGlvbj1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fZDNUcmFuc2Zvcm1hdGlvblNjYWxlKHIpfSx0LnByb3RvdHlwZS5pbnZlcnRlZFRyYW5zZm9ybWF0aW9uPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kM1RyYW5zZm9ybWF0aW9uU2NhbGUuaW52ZXJ0KHIpfSx0LnByb3RvdHlwZS5nZXRUcmFuc2Zvcm1hdGlvbkV4dGVudD1mdW5jdGlvbigpe3JldHVybiB0b3R9LHQucHJvdG90eXBlLmdldFRyYW5zZm9ybWF0aW9uRG9tYWluPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2QzVHJhbnNmb3JtYXRpb25TY2FsZS5kb21haW4oKX0sdC5wcm90b3R5cGUuc2V0VHJhbnNmb3JtYXRpb25Eb21haW49ZnVuY3Rpb24ocil7dGhpcy5fZDNUcmFuc2Zvcm1hdGlvblNjYWxlLmRvbWFpbihyKSx0aGlzLl9kaXNwYXRjaFVwZGF0ZSgpfSx0LnByb3RvdHlwZS5fZ2V0RG9tYWluPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2JhY2tpbmdTY2FsZURvbWFpbigpfSx0LnByb3RvdHlwZS5fYmFja2luZ1NjYWxlRG9tYWluPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2QzU2NhbGUuZG9tYWluKCk6KHRoaXMuX2QzU2NhbGUuZG9tYWluKHIpLHRoaXMuX3NldEJhbmRzKCksdGhpcyl9LHQucHJvdG90eXBlLl9nZXRSYW5nZT1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9yYW5nZX0sdC5wcm90b3R5cGUuX3NldFJhbmdlPWZ1bmN0aW9uKHIpe3RoaXMuX3JhbmdlPXIsdGhpcy5fZDNUcmFuc2Zvcm1hdGlvblNjYWxlLnJhbmdlKHIpLHRoaXMuX3NldEJhbmRzKCl9LHQucHJvdG90eXBlLl9yZXNjYWxlQmFuZD1mdW5jdGlvbihyKXtyZXR1cm4gTWF0aC5hYnModGhpcy5fZDNUcmFuc2Zvcm1hdGlvblNjYWxlKHIpLXRoaXMuX2QzVHJhbnNmb3JtYXRpb25TY2FsZSgwKSl9LHR9KFl6ZS5TY2FsZSk7ZW90LkNhdGVnb3J5PWp6ZX0pO3ZhciBKWHQ9SChub3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KG5vdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFh6ZT0oZGUoKSxVdChwZSkpLE5mPShFcigpLFV0KE1yKSksU1M9RmUoKSwkemU9TDEoKSxLemU9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKCl7dGhpcy5jb3VudD0wLHRoaXMudHJhY2tlcj17fX1yZXR1cm4gZS5wcm90b3R5cGUuZ2V0SW5kZXg9ZnVuY3Rpb24odCl7aWYodGhpcy50cmFja2VyW3RdIT1udWxsKXJldHVybiB0aGlzLnRyYWNrZXJbdF07dmFyIHI9dGhpcy5jb3VudDtyZXR1cm4gdGhpcy50cmFja2VyW3RdPXIsdGhpcy5jb3VudCs9MSxyfSxlLnByb3RvdHlwZS5jbGVhcj1mdW5jdGlvbigpe3RoaXMuY291bnQ9MCx0aGlzLnRyYWNrZXI9e319LGV9KCksWnplPWZ1bmN0aW9uKGUpe1h6ZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3ZhciBuPWUuY2FsbCh0aGlzKXx8dGhpcztuLl9yYW5nZUxlbmd0aD0xLG4uX3RyYWNrZXI9bmV3IEt6ZTt2YXIgaTtzd2l0Y2gocil7Y2FzZSBudWxsOmNhc2Ugdm9pZCAwOnQuX3Bsb3R0YWJsZUNvbG9yQ2FjaGU9PW51bGwmJih0Ll9wbG90dGFibGVDb2xvckNhY2hlPXQuX2dldFBsb3R0YWJsZUNvbG9ycygpKSxpPU5mLnNjYWxlT3JkaW5hbCgpLnJhbmdlKHQuX3Bsb3R0YWJsZUNvbG9yQ2FjaGUpO2JyZWFrO2Nhc2UiQ2F0ZWdvcnkxMCI6Y2FzZSJjYXRlZ29yeTEwIjpjYXNlIjEwIjppPU5mLnNjYWxlT3JkaW5hbChOZi5zY2hlbWVDYXRlZ29yeTEwKTticmVhaztjYXNlIkNhdGVnb3J5MjAiOmNhc2UiY2F0ZWdvcnkyMCI6Y2FzZSIyMCI6aT1OZi5zY2FsZU9yZGluYWwoTmYuc2NoZW1lQ2F0ZWdvcnkyMCk7YnJlYWs7Y2FzZSJDYXRlZ29yeTIwYiI6Y2FzZSJjYXRlZ29yeTIwYiI6Y2FzZSIyMGIiOmk9TmYuc2NhbGVPcmRpbmFsKE5mLnNjaGVtZUNhdGVnb3J5MjBiKTticmVhaztjYXNlIkNhdGVnb3J5MjBjIjpjYXNlImNhdGVnb3J5MjBjIjpjYXNlIjIwYyI6aT1OZi5zY2FsZU9yZGluYWwoTmYuc2NoZW1lQ2F0ZWdvcnkyMGMpO2JyZWFrO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKCJVbnN1cHBvcnRlZCBDb2xvclNjYWxlIHR5cGUiKX1yZXR1cm4gbi5fZDNTY2FsZT1pLG4uX3JhbmdlTGVuZ3RoPW4uX2QzU2NhbGUucmFuZ2UoKS5sZW5ndGgsbn1yZXR1cm4gdC5wcm90b3R5cGUuZXh0ZW50T2ZWYWx1ZXM9ZnVuY3Rpb24ocil7cmV0dXJuIFNTLkFycmF5LnVuaXEocil9LHQucHJvdG90eXBlLl9nZXRFeHRlbnQ9ZnVuY3Rpb24oKXtyZXR1cm4gU1MuQXJyYXkudW5pcSh0aGlzLl9nZXRBbGxJbmNsdWRlZFZhbHVlcygpKX0sdC5pbnZhbGlkYXRlQ29sb3JDYWNoZT1mdW5jdGlvbigpe3QuX3Bsb3R0YWJsZUNvbG9yQ2FjaGU9bnVsbH0sdC5fZ2V0UGxvdHRhYmxlQ29sb3JzPWZ1bmN0aW9uKCl7Zm9yKHZhciByPVtdLG49TmYuc2VsZWN0KCJib2R5IikuYXBwZW5kKCJwbG90dGFibGUtY29sb3ItdGVzdGVyIiksaT1TUy5Db2xvci5jb2xvclRlc3QobiwiIiksbz0wLGE9U1MuQ29sb3IuY29sb3JUZXN0KG4sInBsb3R0YWJsZS1jb2xvcnMtMCIpO2EhPW51bGwmJm88dGhpcy5fTUFYSU1VTV9DT0xPUlNfRlJPTV9DU1MmJiEoYT09PWkmJmE9PT1yW3IubGVuZ3RoLTFdKTspci5wdXNoKGEpLG8rKyxhPVNTLkNvbG9yLmNvbG9yVGVzdChuLCJwbG90dGFibGUtY29sb3JzLSIrbyk7cmV0dXJuIG4ucmVtb3ZlKCkscn0sdC5wcm90b3R5cGUuc2NhbGU9ZnVuY3Rpb24ocil7dmFyIG49dGhpcy5fZDNTY2FsZShyKSxpPXRoaXMuX3RyYWNrZXIuZ2V0SW5kZXgociksbz1NYXRoLmZsb29yKGkvdGhpcy5fcmFuZ2VMZW5ndGgpO2lmKG89PT0wKXJldHVybiBuO3ZhciBhPU1hdGgubG9nKG8qdC5fTE9PUF9MSUdIVEVOX0ZBQ1RPUisxKTtyZXR1cm4gU1MuQ29sb3IubGlnaHRlbkNvbG9yKG4sYSl9LHQucHJvdG90eXBlLl9nZXREb21haW49ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fYmFja2luZ1NjYWxlRG9tYWluKCl9LHQucHJvdG90eXBlLl9iYWNraW5nU2NhbGVEb21haW49ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fZDNTY2FsZS5kb21haW4oKToodGhpcy5fZDNTY2FsZS5kb21haW4ociksdGhpcy5fdHJhY2tlci5jbGVhcigpLHRoaXMpfSx0LnByb3RvdHlwZS5fZ2V0UmFuZ2U9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZDNTY2FsZS5yYW5nZSgpfSx0LnByb3RvdHlwZS5fc2V0UmFuZ2U9ZnVuY3Rpb24ocil7dGhpcy5fZDNTY2FsZS5yYW5nZShyKSx0aGlzLl9yYW5nZUxlbmd0aD1yLmxlbmd0aH0sdC5fTE9PUF9MSUdIVEVOX0ZBQ1RPUj0xLjYsdC5fTUFYSU1VTV9DT0xPUlNfRlJPTV9DU1M9MjU2LHR9KCR6ZS5TY2FsZSk7bm90LkNvbG9yPVp6ZX0pO3ZhciB0JHQ9SChpb3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGlvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIEp6ZT0oZGUoKSxVdChwZSkpLE1TPShFcigpLFV0KE1yKSksUVh0PUZlKCksUXplPUwxKCksdEZlPWZ1bmN0aW9uKGUpe0p6ZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3I9PT12b2lkIDAmJihyPSJsaW5lYXIiKTt2YXIgbj1lLmNhbGwodGhpcyl8fHRoaXM7c3dpdGNoKHIpe2Nhc2UibGluZWFyIjpuLl9jb2xvclNjYWxlPU1TLnNjYWxlTGluZWFyKCk7YnJlYWs7Y2FzZSJsb2ciOm4uX2NvbG9yU2NhbGU9TVMuc2NhbGVMb2coKTticmVhaztjYXNlInNxcnQiOm4uX2NvbG9yU2NhbGU9TVMuc2NhbGVTcXJ0KCk7YnJlYWs7Y2FzZSJwb3ciOm4uX2NvbG9yU2NhbGU9TVMuc2NhbGVQb3coKTticmVha31pZihuLl9jb2xvclNjYWxlPT1udWxsKXRocm93IG5ldyBFcnJvcigidW5rbm93biBRdWFudGl0YXRpdmVTY2FsZSBzY2FsZSB0eXBlICIrcik7cmV0dXJuIG4ucmFuZ2UodC5SRURTKSxufXJldHVybiB0LnByb3RvdHlwZS5leHRlbnRPZlZhbHVlcz1mdW5jdGlvbihyKXt2YXIgbj1NUy5leHRlbnQocik7cmV0dXJuIG5bMF09PW51bGx8fG5bMV09PW51bGw/W106bn0sdC5wcm90b3R5cGUuX2QzSW50ZXJwb2xhdGVkU2NhbGU9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fY29sb3JTY2FsZS5yYW5nZShbMCwxXSkuaW50ZXJwb2xhdGUodGhpcy5faW50ZXJwb2xhdGVDb2xvcnMoKSl9LHQucHJvdG90eXBlLl9pbnRlcnBvbGF0ZUNvbG9ycz1mdW5jdGlvbigpe3ZhciByPXRoaXMuX2NvbG9yUmFuZ2U7aWYoci5sZW5ndGg8Mil0aHJvdyBuZXcgRXJyb3IoIkNvbG9yIHNjYWxlIGFycmF5cyBtdXN0IGhhdmUgYXQgbGVhc3QgdHdvIGVsZW1lbnRzLiIpO3JldHVybiBmdW5jdGlvbihuLGkpe3JldHVybiBmdW5jdGlvbihvKXtvPU1hdGgubWF4KDAsTWF0aC5taW4oMSxvKSk7dmFyIGE9byooci5sZW5ndGgtMSkscz1NYXRoLmZsb29yKGEpLGw9TWF0aC5jZWlsKGEpLGM9YS1zO3JldHVybiBNUy5pbnRlcnBvbGF0ZUxhYihyW3NdLHJbbF0pKGMpfX19LHQucHJvdG90eXBlLl9yZXNldFNjYWxlPWZ1bmN0aW9uKCl7dGhpcy5fZDNTY2FsZT10aGlzLl9kM0ludGVycG9sYXRlZFNjYWxlKCksdGhpcy5hdXRvRG9tYWluSWZBdXRvbWF0aWNNb2RlKCksdGhpcy5fZGlzcGF0Y2hVcGRhdGUoKX0sdC5wcm90b3R5cGUuYXV0b0RvbWFpbj1mdW5jdGlvbigpe3ZhciByPXRoaXMuX2dldEFsbEluY2x1ZGVkVmFsdWVzKCk7cmV0dXJuIHIubGVuZ3RoPjAmJnRoaXMuX3NldERvbWFpbihbUVh0Lk1hdGgubWluKHIsMCksUVh0Lk1hdGgubWF4KHIsMCldKSx0aGlzfSx0LnByb3RvdHlwZS5zY2FsZT1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fZDNTY2FsZShyKX0sdC5wcm90b3R5cGUuX2dldERvbWFpbj1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9iYWNraW5nU2NhbGVEb21haW4oKX0sdC5wcm90b3R5cGUuX2JhY2tpbmdTY2FsZURvbWFpbj1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9kM1NjYWxlLmRvbWFpbigpOih0aGlzLl9kM1NjYWxlLmRvbWFpbihyKSx0aGlzKX0sdC5wcm90b3R5cGUuX2dldFJhbmdlPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2NvbG9yUmFuZ2V9LHQucHJvdG90eXBlLl9zZXRSYW5nZT1mdW5jdGlvbihyKXt0aGlzLl9jb2xvclJhbmdlPXIsdGhpcy5fcmVzZXRTY2FsZSgpfSx0LlJFRFM9WyIjRkZGRkZGIiwiI0ZGRjZFMSIsIiNGRUY0QzAiLCIjRkVEOTc2IiwiI0ZFQjI0QyIsIiNGRDhEM0MiLCIjRkM0RTJBIiwiI0UzMUExQyIsIiNCMTAwMjYiXSx0LkJMVUVTPVsiI0ZGRkZGRiIsIiNDQ0ZGRkYiLCIjQTVGRkZEIiwiIzg1RjdGQiIsIiM2RUQzRUYiLCIjNTVBN0UwIiwiIzQxN0ZEMCIsIiMyNTQ1RDMiLCIjMEIwMkUxIl0sdC5QT1NORUc9WyIjMEIwMkUxIiwiIzI1NDVEMyIsIiM0MTdGRDAiLCIjNTVBN0UwIiwiIzZFRDNFRiIsIiM4NUY3RkIiLCIjQTVGRkZEIiwiI0NDRkZGRiIsIiNGRkZGRkYiLCIjRkZGNkUxIiwiI0ZFRjRDMCIsIiNGRUQ5NzYiLCIjRkVCMjRDIiwiI0ZEOEQzQyIsIiNGQzRFMkEiLCIjRTMxQTFDIiwiI0IxMDAyNiJdLHR9KFF6ZS5TY2FsZSk7aW90LkludGVycG9sYXRlZENvbG9yPXRGZX0pO3ZhciB2ZD1IKG9vdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkob290LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgZUZlPShkZSgpLFV0KHBlKSksckZlPShFcigpLFV0KE1yKSksbkZlPXQ0KCksRVM9RmUoKSxpRmU9TDEoKSxvRmU9ZnVuY3Rpb24oZSl7ZUZlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXt2YXIgcj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIHIuX3RpY2tHZW5lcmF0b3I9ZnVuY3Rpb24obil7cmV0dXJuIG4uZGVmYXVsdFRpY2tzKCl9LHIuX3BhZFByb3BvcnRpb249LjA1LHIuX3NuYXBwaW5nRG9tYWluRW5hYmxlZD0hMCxyLl9wYWRkaW5nRXhjZXB0aW9uc1Byb3ZpZGVycz1uZXcgRVMuU2V0LHJ9cmV0dXJuIHQucHJvdG90eXBlLmF1dG9Eb21haW49ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZG9tYWluTWluPW51bGwsdGhpcy5fZG9tYWluTWF4PW51bGwsZS5wcm90b3R5cGUuYXV0b0RvbWFpbi5jYWxsKHRoaXMpLHRoaXN9LHQucHJvdG90eXBlLmF1dG9Eb21haW5JZkF1dG9tYXRpY01vZGU9ZnVuY3Rpb24oKXtpZih0aGlzLl9kb21haW5NaW4hPW51bGwmJnRoaXMuX2RvbWFpbk1heCE9bnVsbCl7dGhpcy5fc2V0RG9tYWluKFt0aGlzLl9kb21haW5NaW4sdGhpcy5fZG9tYWluTWF4XSk7cmV0dXJufXZhciByPXRoaXMuX2dldEV4dGVudCgpO2lmKHRoaXMuX2RvbWFpbk1pbiE9bnVsbCl7dmFyIG49clsxXTt0aGlzLl9kb21haW5NaW4+PW4mJihuPXRoaXMuX2V4cGFuZFNpbmdsZVZhbHVlRG9tYWluKFt0aGlzLl9kb21haW5NaW4sdGhpcy5fZG9tYWluTWluXSlbMV0pLHRoaXMuX3NldERvbWFpbihbdGhpcy5fZG9tYWluTWluLG5dKTtyZXR1cm59aWYodGhpcy5fZG9tYWluTWF4IT1udWxsKXt2YXIgaT1yWzBdO3RoaXMuX2RvbWFpbk1heDw9aSYmKGk9dGhpcy5fZXhwYW5kU2luZ2xlVmFsdWVEb21haW4oW3RoaXMuX2RvbWFpbk1heCx0aGlzLl9kb21haW5NYXhdKVswXSksdGhpcy5fc2V0RG9tYWluKFtpLHRoaXMuX2RvbWFpbk1heF0pO3JldHVybn1lLnByb3RvdHlwZS5hdXRvRG9tYWluSWZBdXRvbWF0aWNNb2RlLmNhbGwodGhpcyl9LHQucHJvdG90eXBlLl9nZXRVbmJvdW5kZWRFeHRlbnQ9ZnVuY3Rpb24ocil7cj09PXZvaWQgMCYmKHI9ITEpO3ZhciBuPXRoaXMuX2dldEFsbEluY2x1ZGVkVmFsdWVzKHIpLGk9dGhpcy5fZGVmYXVsdEV4dGVudCgpO2lmKG4ubGVuZ3RoIT09MCl7dmFyIG89W0VTLk1hdGgubWluKG4saVswXSksRVMuTWF0aC5tYXgobixpWzFdKV07aT10aGlzLl9wYWREb21haW4obyl9cmV0dXJuIGl9LHQucHJvdG90eXBlLl9nZXRFeHRlbnQ9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLl9nZXRVbmJvdW5kZWRFeHRlbnQoKTtyZXR1cm4gdGhpcy5fZG9tYWluTWluIT1udWxsJiYoclswXT10aGlzLl9kb21haW5NaW4pLHRoaXMuX2RvbWFpbk1heCE9bnVsbCYmKHJbMV09dGhpcy5fZG9tYWluTWF4KSxyfSx0LnByb3RvdHlwZS5hZGRQYWRkaW5nRXhjZXB0aW9uc1Byb3ZpZGVyPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9wYWRkaW5nRXhjZXB0aW9uc1Byb3ZpZGVycy5hZGQociksdGhpcy5hdXRvRG9tYWluSWZBdXRvbWF0aWNNb2RlKCksdGhpc30sdC5wcm90b3R5cGUucmVtb3ZlUGFkZGluZ0V4Y2VwdGlvbnNQcm92aWRlcj1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcGFkZGluZ0V4Y2VwdGlvbnNQcm92aWRlcnMuZGVsZXRlKHIpLHRoaXMuYXV0b0RvbWFpbklmQXV0b21hdGljTW9kZSgpLHRoaXN9LHQucHJvdG90eXBlLnBhZFByb3BvcnRpb249ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fcGFkUHJvcG9ydGlvbjtpZihyPDApdGhyb3cgbmV3IEVycm9yKCJwYWRQcm9wb3J0aW9uIG11c3QgYmUgbm9uLW5lZ2F0aXZlIik7cmV0dXJuIHRoaXMuX3BhZFByb3BvcnRpb249cix0aGlzLmF1dG9Eb21haW5JZkF1dG9tYXRpY01vZGUoKSx0aGlzfSx0LnByb3RvdHlwZS5fcGFkRG9tYWluPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXM7aWYoclswXS52YWx1ZU9mKCk9PT1yWzFdLnZhbHVlT2YoKSlyZXR1cm4gdGhpcy5fZXhwYW5kU2luZ2xlVmFsdWVEb21haW4ocik7aWYodGhpcy5fcGFkUHJvcG9ydGlvbj09PTApcmV0dXJuIHI7dmFyIGk9dGhpcy5fcGFkUHJvcG9ydGlvbi8yLG89clswXSxhPXJbMV0scz0hMSxsPSExO3RoaXMuX3BhZGRpbmdFeGNlcHRpb25zUHJvdmlkZXJzLmZvckVhY2goZnVuY3Rpb24oZil7dmFyIHA9ZihuKTtwLmZvckVhY2goZnVuY3Rpb24oZCl7ZC52YWx1ZU9mKCk9PT1vLnZhbHVlT2YoKSYmKHM9ITApLGQudmFsdWVPZigpPT09YS52YWx1ZU9mKCkmJihsPSEwKX0pfSk7dmFyIGM9dGhpcy5fYmFja2luZ1NjYWxlRG9tYWluKCk7dGhpcy5fYmFja2luZ1NjYWxlRG9tYWluKHIpO3ZhciB1PXM/bzp0aGlzLmludmVydCh0aGlzLnNjYWxlKG8pLSh0aGlzLnNjYWxlKGEpLXRoaXMuc2NhbGUobykpKmkpLGg9bD9hOnRoaXMuaW52ZXJ0KHRoaXMuc2NhbGUoYSkrKHRoaXMuc2NhbGUoYSktdGhpcy5zY2FsZShvKSkqaSk7cmV0dXJuIHRoaXMuX2JhY2tpbmdTY2FsZURvbWFpbihjKSx0aGlzLl9zbmFwcGluZ0RvbWFpbkVuYWJsZWQ/dGhpcy5fbmljZURvbWFpbihbdSxoXSk6W3UsaF19LHQucHJvdG90eXBlLnNuYXBwaW5nRG9tYWluRW5hYmxlZD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9zbmFwcGluZ0RvbWFpbkVuYWJsZWQ6KHRoaXMuX3NuYXBwaW5nRG9tYWluRW5hYmxlZD1yLHRoaXMuYXV0b0RvbWFpbklmQXV0b21hdGljTW9kZSgpLHRoaXMpfSx0LnByb3RvdHlwZS5fZXhwYW5kU2luZ2xlVmFsdWVEb21haW49ZnVuY3Rpb24ocil7cmV0dXJuIHJ9LHQucHJvdG90eXBlLmludmVydD1mdW5jdGlvbihyKXt0aHJvdyBuZXcgRXJyb3IoIlN1YmNsYXNzZXMgc2hvdWxkIG92ZXJyaWRlIGludmVydCIpfSx0LnByb3RvdHlwZS5kb21haW49ZnVuY3Rpb24ocil7cmV0dXJuIHIhPW51bGwmJih0aGlzLl9kb21haW5NaW49clswXSx0aGlzLl9kb21haW5NYXg9clsxXSksZS5wcm90b3R5cGUuZG9tYWluLmNhbGwodGhpcyxyKX0sdC5wcm90b3R5cGUuZG9tYWluTWluPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuZG9tYWluKClbMF06KHRoaXMuX2RvbWFpbk1pbj1yLHRoaXMuYXV0b0RvbWFpbklmQXV0b21hdGljTW9kZSgpLHRoaXMpfSx0LnByb3RvdHlwZS5kb21haW5NYXg9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5kb21haW4oKVsxXToodGhpcy5fZG9tYWluTWF4PXIsdGhpcy5hdXRvRG9tYWluSWZBdXRvbWF0aWNNb2RlKCksdGhpcyl9LHQucHJvdG90eXBlLmV4dGVudE9mVmFsdWVzPWZ1bmN0aW9uKHIpe3ZhciBuPXJGZS5leHRlbnQoci5maWx0ZXIoZnVuY3Rpb24oaSl7cmV0dXJuIEVTLk1hdGguaXNWYWxpZE51bWJlcigraSl9KSk7cmV0dXJuIG5bMF09PW51bGx8fG5bMV09PW51bGw/W106bn0sdC5wcm90b3R5cGUuem9vbT1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXMsbz1mdW5jdGlvbihhKXtyZXR1cm4gaS5pbnZlcnQobkZlLnpvb21PdXQoYSxyLG4pKX07dGhpcy5kb21haW4odGhpcy5yYW5nZSgpLm1hcChvKSl9LHQucHJvdG90eXBlLnBhbj1mdW5jdGlvbihyKXt2YXIgbj10aGlzLGk9ZnVuY3Rpb24obyl7cmV0dXJuIG4uaW52ZXJ0KG8rcil9O3RoaXMuZG9tYWluKHRoaXMucmFuZ2UoKS5tYXAoaSkpfSx0LnByb3RvdHlwZS5zY2FsZVRyYW5zZm9ybWF0aW9uPWZ1bmN0aW9uKHIpe3Rocm93IG5ldyBFcnJvcigiU3ViY2xhc3NlcyBzaG91bGQgb3ZlcnJpZGUgc2NhbGVUcmFuc2Zvcm1hdGlvbiIpfSx0LnByb3RvdHlwZS5pbnZlcnRlZFRyYW5zZm9ybWF0aW9uPWZ1bmN0aW9uKHIpe3Rocm93IG5ldyBFcnJvcigiU3ViY2xhc3NlcyBzaG91bGQgb3ZlcnJpZGUgaW52ZXJ0ZWRUcmFuc2Zvcm1hdGlvbiIpfSx0LnByb3RvdHlwZS5nZXRUcmFuc2Zvcm1hdGlvbkV4dGVudD1mdW5jdGlvbigpe3Rocm93IG5ldyBFcnJvcigiU3ViY2xhc3NlcyBzaG91bGQgb3ZlcnJpZGUgZ2V0VHJhbnNmb3JtYXRpb25FeHRlbnQiKX0sdC5wcm90b3R5cGUuZ2V0VHJhbnNmb3JtYXRpb25Eb21haW49ZnVuY3Rpb24oKXt0aHJvdyBuZXcgRXJyb3IoIlN1YmNsYXNzZXMgc2hvdWxkIG92ZXJyaWRlIGdldFRyYW5zZm9ybWF0aW9uRG9tYWluIil9LHQucHJvdG90eXBlLnNldFRyYW5zZm9ybWF0aW9uRG9tYWluPWZ1bmN0aW9uKHIpe3Rocm93IG5ldyBFcnJvcigiU3ViY2xhc3NlcyBzaG91bGQgb3ZlcnJpZGUgc2V0VHJhbnNmb3JtYXRpb25Eb21haW4iKX0sdC5wcm90b3R5cGUuX3NldERvbWFpbj1mdW5jdGlvbihyKXt2YXIgbj1mdW5jdGlvbihpKXtyZXR1cm4gRVMuTWF0aC5pc05hTihpKXx8aT09PTEvMHx8aT09PS0xLzB9O2lmKG4oclswXSl8fG4oclsxXSkpe0VTLldpbmRvdy53YXJuKCJXYXJuaW5nOiBRdWFudGl0YXRpdmVTY2FsZXMgY2Fubm90IHRha2UgTmFOIG9yIEluZmluaXR5IGFzIGEgZG9tYWluIHZhbHVlLiBJZ25vcmluZy4iKTtyZXR1cm59ZS5wcm90b3R5cGUuX3NldERvbWFpbi5jYWxsKHRoaXMscil9LHQucHJvdG90eXBlLmRlZmF1bHRUaWNrcz1mdW5jdGlvbigpe3Rocm93IG5ldyBFcnJvcigiU3ViY2xhc3NlcyBzaG91bGQgb3ZlcnJpZGUgX2dldERlZmF1bHRUaWNrcyIpfSx0LnByb3RvdHlwZS50aWNrcz1mdW5jdGlvbigpe3JldHVybiB0aGlzLl90aWNrR2VuZXJhdG9yKHRoaXMpfSx0LnByb3RvdHlwZS5fbmljZURvbWFpbj1mdW5jdGlvbihyLG4pe3Rocm93IG5ldyBFcnJvcigiU3ViY2xhc3NlcyBzaG91bGQgb3ZlcnJpZGUgX25pY2VEb21haW4iKX0sdC5wcm90b3R5cGUuX2RlZmF1bHRFeHRlbnQ9ZnVuY3Rpb24oKXt0aHJvdyBuZXcgRXJyb3IoIlN1YmNsYXNzZXMgc2hvdWxkIG92ZXJyaWRlIF9kZWZhdWx0RXh0ZW50Iil9LHQucHJvdG90eXBlLnRpY2tHZW5lcmF0b3I9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fdGlja0dlbmVyYXRvcjoodGhpcy5fdGlja0dlbmVyYXRvcj1yLHRoaXMpfSx0Ll9ERUZBVUxUX05VTV9USUNLUz0xMCx0fShpRmUuU2NhbGUpO29vdC5RdWFudGl0YXRpdmVTY2FsZT1vRmV9KTt2YXIgZSR0PUgoYW90PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShhb3QsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBhRmU9KGRlKCksVXQocGUpKSxzRmU9KEVyKCksVXQoTXIpKSxsRmU9dmQoKSxjRmU9ZnVuY3Rpb24oZSl7YUZlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXt2YXIgcj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIHIuX2QzU2NhbGU9c0ZlLnNjYWxlTGluZWFyKCkscn1yZXR1cm4gdC5wcm90b3R5cGUuX2RlZmF1bHRFeHRlbnQ9ZnVuY3Rpb24oKXtyZXR1cm5bMCwxXX0sdC5wcm90b3R5cGUuX2V4cGFuZFNpbmdsZVZhbHVlRG9tYWluPWZ1bmN0aW9uKHIpe3JldHVybiByWzBdPT09clsxXT9bclswXS0xLHJbMV0rMV06cn0sdC5wcm90b3R5cGUuc2NhbGU9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2QzU2NhbGUocil9LHQucHJvdG90eXBlLnNjYWxlVHJhbnNmb3JtYXRpb249ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuc2NhbGUocil9LHQucHJvdG90eXBlLmludmVydGVkVHJhbnNmb3JtYXRpb249ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuaW52ZXJ0KHIpfSx0LnByb3RvdHlwZS5nZXRUcmFuc2Zvcm1hdGlvbkV4dGVudD1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9nZXRVbmJvdW5kZWRFeHRlbnQoITApfSx0LnByb3RvdHlwZS5nZXRUcmFuc2Zvcm1hdGlvbkRvbWFpbj1mdW5jdGlvbigpe3JldHVybiB0aGlzLmRvbWFpbigpfSx0LnByb3RvdHlwZS5zZXRUcmFuc2Zvcm1hdGlvbkRvbWFpbj1mdW5jdGlvbihyKXt0aGlzLmRvbWFpbihyKX0sdC5wcm90b3R5cGUuX2dldERvbWFpbj1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9iYWNraW5nU2NhbGVEb21haW4oKX0sdC5wcm90b3R5cGUuX2JhY2tpbmdTY2FsZURvbWFpbj1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9kM1NjYWxlLmRvbWFpbigpOih0aGlzLl9kM1NjYWxlLmRvbWFpbihyKSx0aGlzKX0sdC5wcm90b3R5cGUuX2dldFJhbmdlPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2QzU2NhbGUucmFuZ2UoKX0sdC5wcm90b3R5cGUuX3NldFJhbmdlPWZ1bmN0aW9uKHIpe3RoaXMuX2QzU2NhbGUucmFuZ2Uocil9LHQucHJvdG90eXBlLmludmVydD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fZDNTY2FsZS5pbnZlcnQocil9LHQucHJvdG90eXBlLmRlZmF1bHRUaWNrcz1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9kM1NjYWxlLnRpY2tzKHQuX0RFRkFVTFRfTlVNX1RJQ0tTKX0sdC5wcm90b3R5cGUuX25pY2VEb21haW49ZnVuY3Rpb24ocixuKXtyZXR1cm4gdGhpcy5fZDNTY2FsZS5jb3B5KCkuZG9tYWluKHIpLm5pY2UobikuZG9tYWluKCl9LHR9KGxGZS5RdWFudGl0YXRpdmVTY2FsZSk7YW90LkxpbmVhcj1jRmV9KTt2YXIgciR0PUgoc290PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShzb3QsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciB1RmU9KGRlKCksVXQocGUpKSxoRmU9KEVyKCksVXQoTXIpKSxmRmU9dmQoKSxwRmU9ZnVuY3Rpb24oZSl7dUZlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQocil7cj09PXZvaWQgMCYmKHI9MTApO3ZhciBuPWUuY2FsbCh0aGlzKXx8dGhpcztyZXR1cm4gbi5fZDNTY2FsZT1oRmUuc2NhbGVMb2coKS5iYXNlKHIpLG4uX3NldERvbWFpbihuLl9kZWZhdWx0RXh0ZW50KCkpLG59cmV0dXJuIHQucHJvdG90eXBlLl9kZWZhdWx0RXh0ZW50PWZ1bmN0aW9uKCl7cmV0dXJuWzEsdGhpcy5fZDNTY2FsZS5iYXNlKCldfSx0LnByb3RvdHlwZS5fZXhwYW5kU2luZ2xlVmFsdWVEb21haW49ZnVuY3Rpb24ocil7cmV0dXJuIHJbMF09PT1yWzFdP1tyWzBdL3RoaXMuX2QzU2NhbGUuYmFzZSgpLHJbMV0qdGhpcy5fZDNTY2FsZS5iYXNlKCldOnJ9LHQucHJvdG90eXBlLnNjYWxlPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kM1NjYWxlKHIpfSx0LnByb3RvdHlwZS5zY2FsZVRyYW5zZm9ybWF0aW9uPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLnNjYWxlKHIpfSx0LnByb3RvdHlwZS5pbnZlcnRlZFRyYW5zZm9ybWF0aW9uPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLmludmVydChyKX0sdC5wcm90b3R5cGUuZ2V0VHJhbnNmb3JtYXRpb25FeHRlbnQ9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZ2V0VW5ib3VuZGVkRXh0ZW50KCEwKX0sdC5wcm90b3R5cGUuZ2V0VHJhbnNmb3JtYXRpb25Eb21haW49ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5kb21haW4oKX0sdC5wcm90b3R5cGUuc2V0VHJhbnNmb3JtYXRpb25Eb21haW49ZnVuY3Rpb24ocil7dGhpcy5kb21haW4ocil9LHQucHJvdG90eXBlLl9nZXREb21haW49ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fYmFja2luZ1NjYWxlRG9tYWluKCl9LHQucHJvdG90eXBlLl9iYWNraW5nU2NhbGVEb21haW49ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fZDNTY2FsZS5kb21haW4oKToodGhpcy5fZDNTY2FsZS5kb21haW4ociksdGhpcyl9LHQucHJvdG90eXBlLl9nZXRSYW5nZT1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9kM1NjYWxlLnJhbmdlKCl9LHQucHJvdG90eXBlLl9zZXRSYW5nZT1mdW5jdGlvbihyKXt0aGlzLl9kM1NjYWxlLnJhbmdlKHIpfSx0LnByb3RvdHlwZS5pbnZlcnQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2QzU2NhbGUuaW52ZXJ0KHIpfSx0LnByb3RvdHlwZS5kZWZhdWx0VGlja3M9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZDNTY2FsZS50aWNrcyh0Ll9ERUZBVUxUX05VTV9USUNLUyl9LHQucHJvdG90eXBlLl9uaWNlRG9tYWluPWZ1bmN0aW9uKHIsbil7cmV0dXJuIHRoaXMuX2QzU2NhbGUuY29weSgpLmRvbWFpbihyKS5uaWNlKCkuZG9tYWluKCl9LHR9KGZGZS5RdWFudGl0YXRpdmVTY2FsZSk7c290LkxvZz1wRmV9KTt2YXIgbiR0PUgoY290PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShjb3QsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBkRmU9KGRlKCksVXQocGUpKSxlND0oRXIoKSxVdChNcikpLFRTPUZlKCksbG90PWtzKCksbUZlPXZkKCksZ0ZlPWZ1bmN0aW9uKGUpe2RGZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3I9PT12b2lkIDAmJihyPTEwKTt2YXIgbj1lLmNhbGwodGhpcyl8fHRoaXM7aWYobi5fbG9nVGlja0dlbmVyYXRvcj1mdW5jdGlvbihpKXt2YXIgbz1mdW5jdGlvbih4LGIsUyl7cmV0dXJuW3gsYixTXS5zb3J0KGZ1bmN0aW9uKEMsUCl7cmV0dXJuIEMtUH0pWzFdfSxhPVRTLk1hdGgubWluKG4uX3VudHJhbnNmb3JtZWREb21haW4sMCkscz1UUy5NYXRoLm1heChuLl91bnRyYW5zZm9ybWVkRG9tYWluLDApLGw9YSxjPW8oYSxzLC1uLl9waXZvdCksdT1vKGEscyxuLl9waXZvdCksaD1zLGY9bi5fbG9nVGlja3MoLWMsLWwpLm1hcChmdW5jdGlvbih4KXtyZXR1cm4teH0pLnJldmVyc2UoKSxwPW4uX2xvZ1RpY2tzKHUsaCksZD1NYXRoLm1heChhLC1uLl9waXZvdCksZz1NYXRoLm1pbihzLG4uX3Bpdm90KSxfPWU0LnNjYWxlTGluZWFyKCkuZG9tYWluKFtkLGddKS50aWNrcyhuLl9ob3dNYW55VGlja3MoZCxnKSkseT1mLmNvbmNhdChfKS5jb25jYXQocCk7cmV0dXJuIHkubGVuZ3RoPD0xJiYoeT1lNC5zY2FsZUxpbmVhcigpLmRvbWFpbihbYSxzXSkudGlja3MobG90Lk1vZGlmaWVkTG9nLl9ERUZBVUxUX05VTV9USUNLUykpLHl9LG4uX2QzU2NhbGU9ZTQuc2NhbGVMaW5lYXIoKSxuLl9iYXNlPXIsbi5fcGl2b3Q9bi5fYmFzZSxuLl9zZXREb21haW4obi5fZGVmYXVsdEV4dGVudCgpKSxuLnRpY2tHZW5lcmF0b3Iobi5fbG9nVGlja0dlbmVyYXRvcikscjw9MSl0aHJvdyBuZXcgRXJyb3IoIk1vZGlmaWVkTG9nU2NhbGU6IFRoZSBiYXNlIG11c3QgYmUgPiAxIik7cmV0dXJuIG59cmV0dXJuIHQucHJvdG90eXBlLl9hZGp1c3RlZExvZz1mdW5jdGlvbihyKXt2YXIgbj1yPDA/LTE6MTtyZXR1cm4gcio9bixyPHRoaXMuX3Bpdm90JiYocis9KHRoaXMuX3Bpdm90LXIpL3RoaXMuX3Bpdm90KSxyPU1hdGgubG9nKHIpL01hdGgubG9nKHRoaXMuX2Jhc2UpLHIqPW4scn0sdC5wcm90b3R5cGUuX2ludmVydGVkQWRqdXN0ZWRMb2c9ZnVuY3Rpb24ocil7dmFyIG49cjwwPy0xOjE7cmV0dXJuIHIqPW4scj1NYXRoLnBvdyh0aGlzLl9iYXNlLHIpLHI8dGhpcy5fcGl2b3QmJihyPXRoaXMuX3Bpdm90KihyLTEpLyh0aGlzLl9waXZvdC0xKSkscio9bixyfSx0LnByb3RvdHlwZS5zY2FsZT1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fZDNTY2FsZSh0aGlzLl9hZGp1c3RlZExvZyhyKSl9LHQucHJvdG90eXBlLmludmVydD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5faW52ZXJ0ZWRBZGp1c3RlZExvZyh0aGlzLl9kM1NjYWxlLmludmVydChyKSl9LHQucHJvdG90eXBlLnNjYWxlVHJhbnNmb3JtYXRpb249ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuc2NhbGUocil9LHQucHJvdG90eXBlLmludmVydGVkVHJhbnNmb3JtYXRpb249ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuaW52ZXJ0KHIpfSx0LnByb3RvdHlwZS5nZXRUcmFuc2Zvcm1hdGlvbkV4dGVudD1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9nZXRVbmJvdW5kZWRFeHRlbnQoITApfSx0LnByb3RvdHlwZS5nZXRUcmFuc2Zvcm1hdGlvbkRvbWFpbj1mdW5jdGlvbigpe3JldHVybiB0aGlzLmRvbWFpbigpfSx0LnByb3RvdHlwZS5zZXRUcmFuc2Zvcm1hdGlvbkRvbWFpbj1mdW5jdGlvbihyKXt0aGlzLmRvbWFpbihyKX0sdC5wcm90b3R5cGUuX2dldERvbWFpbj1mdW5jdGlvbigpe3JldHVybiB0aGlzLl91bnRyYW5zZm9ybWVkRG9tYWlufSx0LnByb3RvdHlwZS5fc2V0RG9tYWluPWZ1bmN0aW9uKHIpe3RoaXMuX3VudHJhbnNmb3JtZWREb21haW49cjt2YXIgbj1bdGhpcy5fYWRqdXN0ZWRMb2coclswXSksdGhpcy5fYWRqdXN0ZWRMb2coclsxXSldO2UucHJvdG90eXBlLl9zZXREb21haW4uY2FsbCh0aGlzLG4pfSx0LnByb3RvdHlwZS5fYmFja2luZ1NjYWxlRG9tYWluPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2QzU2NhbGUuZG9tYWluKCk6KHRoaXMuX2QzU2NhbGUuZG9tYWluKHIpLHRoaXMpfSx0LnByb3RvdHlwZS5fbG9nVGlja3M9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzLG89dGhpcy5faG93TWFueVRpY2tzKHIsbik7aWYobz09PTApcmV0dXJuW107dmFyIGE9TWF0aC5mbG9vcihNYXRoLmxvZyhyKS9NYXRoLmxvZyh0aGlzLl9iYXNlKSkscz1NYXRoLmNlaWwoTWF0aC5sb2cobikvTWF0aC5sb2codGhpcy5fYmFzZSkpLGw9ZTQucmFuZ2UocyxhLC1NYXRoLmNlaWwoKHMtYSkvbykpLGM9ZTQucmFuZ2UodGhpcy5fYmFzZSwxLC0odGhpcy5fYmFzZS0xKSkubWFwKE1hdGguZmxvb3IpLHU9VFMuQXJyYXkudW5pcShjKSxoPWwubWFwKGZ1bmN0aW9uKGcpe3JldHVybiB1Lm1hcChmdW5jdGlvbihfKXtyZXR1cm4gTWF0aC5wb3coaS5fYmFzZSxnLTEpKl99KX0pLGY9VFMuQXJyYXkuZmxhdHRlbihoKSxwPWYuZmlsdGVyKGZ1bmN0aW9uKGcpe3JldHVybiByPD1nJiZnPD1ufSksZD1wLnNvcnQoZnVuY3Rpb24oZyxfKXtyZXR1cm4gZy1ffSk7cmV0dXJuIGR9LHQucHJvdG90eXBlLl9ob3dNYW55VGlja3M9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzLl9hZGp1c3RlZExvZyhUUy5NYXRoLm1pbih0aGlzLl91bnRyYW5zZm9ybWVkRG9tYWluLDApKSxvPXRoaXMuX2FkanVzdGVkTG9nKFRTLk1hdGgubWF4KHRoaXMuX3VudHJhbnNmb3JtZWREb21haW4sMCkpLGE9dGhpcy5fYWRqdXN0ZWRMb2cocikscz10aGlzLl9hZGp1c3RlZExvZyhuKSxsPShzLWEpLyhvLWkpLGM9TWF0aC5jZWlsKGwqbG90Lk1vZGlmaWVkTG9nLl9ERUZBVUxUX05VTV9USUNLUyk7cmV0dXJuIGN9LHQucHJvdG90eXBlLl9uaWNlRG9tYWluPWZ1bmN0aW9uKHIsbil7cmV0dXJuIHJ9LHQucHJvdG90eXBlLl9kZWZhdWx0RXh0ZW50PWZ1bmN0aW9uKCl7cmV0dXJuWzAsdGhpcy5fYmFzZV19LHQucHJvdG90eXBlLl9leHBhbmRTaW5nbGVWYWx1ZURvbWFpbj1mdW5jdGlvbihyKXtpZihyWzBdPT09clsxXSl7dmFyIG49clswXTtyZXR1cm4gbj4wP1tuL3RoaXMuX2Jhc2Usbip0aGlzLl9iYXNlXTpuPT09MD9bLXRoaXMuX2Jhc2UsdGhpcy5fYmFzZV06W24qdGhpcy5fYmFzZSxuL3RoaXMuX2Jhc2VdfXJldHVybiByfSx0LnByb3RvdHlwZS5fZ2V0UmFuZ2U9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZDNTY2FsZS5yYW5nZSgpfSx0LnByb3RvdHlwZS5fc2V0UmFuZ2U9ZnVuY3Rpb24ocil7dGhpcy5fZDNTY2FsZS5yYW5nZShyKX0sdC5wcm90b3R5cGUuZGVmYXVsdFRpY2tzPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2QzU2NhbGUudGlja3MobG90Lk1vZGlmaWVkTG9nLl9ERUZBVUxUX05VTV9USUNLUyl9LHR9KG1GZS5RdWFudGl0YXRpdmVTY2FsZSk7Y290Lk1vZGlmaWVkTG9nPWdGZX0pO3ZhciBpJHQ9SCh1b3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHVvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIF9GZT0oZGUoKSxVdChwZSkpLGdhPShFcigpLFV0KE1yKSksazE9QkYoKSx5RmU9dmQoKSx2RmU9ZnVuY3Rpb24oZSl7X0ZlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXt2YXIgcj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIHIuX2QzU2NhbGU9Z2Euc2NhbGVUaW1lKCksci5hdXRvRG9tYWluKCkscn1yZXR1cm4gdC5wcm90b3R5cGUudGlja0ludGVydmFsPWZ1bmN0aW9uKHIsbixpKXtuPT09dm9pZCAwJiYobj0xKSxpPT09dm9pZCAwJiYoaT0hMSk7dmFyIG89Z2Euc2NhbGVUaW1lKCksYT10LnRpbWVJbnRlcnZhbFRvRDNUaW1lKHIsaSkuZXZlcnkobik7cmV0dXJuIG8uZG9tYWluKHRoaXMuZG9tYWluKCkpLG8ucmFuZ2UodGhpcy5yYW5nZSgpKSxvLnRpY2tzKGEpfSx0LnByb3RvdHlwZS5fc2V0RG9tYWluPWZ1bmN0aW9uKHIpe2lmKHJbMV08clswXSl0aHJvdyBuZXcgRXJyb3IoIlNjYWxlLlRpbWUgZG9tYWluIHZhbHVlcyBtdXN0IGJlIGluIGNocm9ub2xvZ2ljYWwgb3JkZXIiKTtyZXR1cm4gZS5wcm90b3R5cGUuX3NldERvbWFpbi5jYWxsKHRoaXMscil9LHQucHJvdG90eXBlLl9kZWZhdWx0RXh0ZW50PWZ1bmN0aW9uKCl7cmV0dXJuW25ldyBEYXRlKCIxOTcwLTAxLTAxIiksbmV3IERhdGUoIjE5NzAtMDEtMDIiKV19LHQucHJvdG90eXBlLl9leHBhbmRTaW5nbGVWYWx1ZURvbWFpbj1mdW5jdGlvbihyKXt2YXIgbj1yWzBdLmdldFRpbWUoKSxpPXJbMV0uZ2V0VGltZSgpO2lmKG49PT1pKXt2YXIgbz1uZXcgRGF0ZShuKTtvLnNldERhdGUoby5nZXREYXRlKCktMSk7dmFyIGE9bmV3IERhdGUoaSk7cmV0dXJuIGEuc2V0RGF0ZShhLmdldERhdGUoKSsxKSxbbyxhXX1yZXR1cm4gcn0sdC5wcm90b3R5cGUuc2NhbGU9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2QzU2NhbGUocil9LHQucHJvdG90eXBlLnNjYWxlVHJhbnNmb3JtYXRpb249ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuc2NhbGUobmV3IERhdGUocikpfSx0LnByb3RvdHlwZS5pbnZlcnRlZFRyYW5zZm9ybWF0aW9uPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLmludmVydChyKS5nZXRUaW1lKCl9LHQucHJvdG90eXBlLmdldFRyYW5zZm9ybWF0aW9uRXh0ZW50PWZ1bmN0aW9uKCl7dmFyIHI9dGhpcy5fZ2V0VW5ib3VuZGVkRXh0ZW50KCEwKTtyZXR1cm5bclswXS52YWx1ZU9mKCksclsxXS52YWx1ZU9mKCldfSx0LnByb3RvdHlwZS5nZXRUcmFuc2Zvcm1hdGlvbkRvbWFpbj1mdW5jdGlvbigpe3ZhciByPXRoaXMuZG9tYWluKCk7cmV0dXJuW3JbMF0udmFsdWVPZigpLHJbMV0udmFsdWVPZigpXX0sdC5wcm90b3R5cGUuc2V0VHJhbnNmb3JtYXRpb25Eb21haW49ZnVuY3Rpb24ocil7dmFyIG49clswXSxpPXJbMV07dGhpcy5kb21haW4oW25ldyBEYXRlKG4pLG5ldyBEYXRlKGkpXSl9LHQucHJvdG90eXBlLl9nZXREb21haW49ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fYmFja2luZ1NjYWxlRG9tYWluKCl9LHQucHJvdG90eXBlLl9iYWNraW5nU2NhbGVEb21haW49ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fZDNTY2FsZS5kb21haW4oKToodGhpcy5fZDNTY2FsZS5kb21haW4ociksdGhpcyl9LHQucHJvdG90eXBlLl9nZXRSYW5nZT1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9kM1NjYWxlLnJhbmdlKCl9LHQucHJvdG90eXBlLl9zZXRSYW5nZT1mdW5jdGlvbihyKXt0aGlzLl9kM1NjYWxlLnJhbmdlKHIpfSx0LnByb3RvdHlwZS5pbnZlcnQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2QzU2NhbGUuaW52ZXJ0KHIpfSx0LnByb3RvdHlwZS5kZWZhdWx0VGlja3M9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZDNTY2FsZS50aWNrcyh0Ll9ERUZBVUxUX05VTV9USUNLUyl9LHQucHJvdG90eXBlLl9uaWNlRG9tYWluPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kM1NjYWxlLmNvcHkoKS5kb21haW4ocikubmljZSgpLmRvbWFpbigpfSx0LnRpbWVJbnRlcnZhbFRvRDNUaW1lPWZ1bmN0aW9uKHIsbil7c3dpdGNoKHIpe2Nhc2UgazEuVGltZUludGVydmFsLnNlY29uZDpyZXR1cm4gbj9nYS51dGNTZWNvbmQ6Z2EudGltZVNlY29uZDtjYXNlIGsxLlRpbWVJbnRlcnZhbC5taW51dGU6cmV0dXJuIG4/Z2EudXRjTWludXRlOmdhLnRpbWVNaW51dGU7Y2FzZSBrMS5UaW1lSW50ZXJ2YWwuaG91cjpyZXR1cm4gbj9nYS51dGNIb3VyOmdhLnRpbWVIb3VyO2Nhc2UgazEuVGltZUludGVydmFsLmRheTpyZXR1cm4gbj9nYS51dGNEYXk6Z2EudGltZURheTtjYXNlIGsxLlRpbWVJbnRlcnZhbC53ZWVrOnJldHVybiBuP2dhLnV0Y1dlZWs6Z2EudGltZVdlZWs7Y2FzZSBrMS5UaW1lSW50ZXJ2YWwubW9udGg6cmV0dXJuIG4/Z2EudXRjTW9udGg6Z2EudGltZU1vbnRoO2Nhc2UgazEuVGltZUludGVydmFsLnllYXI6cmV0dXJuIG4/Z2EudXRjWWVhcjpnYS50aW1lWWVhcjtkZWZhdWx0OnRocm93IEVycm9yKCJUaW1lSW50ZXJ2YWwgc3BlY2lmaWVkIGRvZXMgbm90IGV4aXN0OiAiK3IpfX0sdH0oeUZlLlF1YW50aXRhdGl2ZVNjYWxlKTt1b3QuVGltZT12RmV9KTt2YXIga3M9SChIdT0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoSHUsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBSMT0oZGUoKSxVdChwZSkpLHhGZT1ZWHQoKTtIdS5UaWNrR2VuZXJhdG9ycz14RmU7UjEuX19leHBvcnRTdGFyKHJvdCgpLEh1KTtSMS5fX2V4cG9ydFN0YXIoSlh0KCksSHUpO1IxLl9fZXhwb3J0U3Rhcih0JHQoKSxIdSk7UjEuX19leHBvcnRTdGFyKGUkdCgpLEh1KTtSMS5fX2V4cG9ydFN0YXIociR0KCksSHUpO1IxLl9fZXhwb3J0U3RhcihuJHQoKSxIdSk7UjEuX19leHBvcnRTdGFyKGkkdCgpLEh1KTt2YXIgYkZlPXJvdCgpLHdGZT12ZCgpO2Z1bmN0aW9uIFNGZShlKXtyZXR1cm4gZSBpbnN0YW5jZW9mIHdGZS5RdWFudGl0YXRpdmVTY2FsZXx8ZSBpbnN0YW5jZW9mIGJGZS5DYXRlZ29yeX1IdS5pc1RyYW5zZm9ybWFibGU9U0ZlfSk7dmFyIEJGPUgoU2U9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFNlLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgTUZlPShkZSgpLFV0KHBlKSksVnU9KEVyKCksVXQoTXIpKSxvJHQ9X2woKSxhJHQ9QnUoKSxFRmU9a3MoKSxyND1GZSgpLGhvdD1JZigpLHRzPVFBKCk7U2UuVGltZUludGVydmFsPWhvdC5tYWtlRW51bShbInNlY29uZCIsIm1pbnV0ZSIsImhvdXIiLCJkYXkiLCJ3ZWVrIiwibW9udGgiLCJ5ZWFyIl0pO1NlLlRpbWVBeGlzT3JpZW50YXRpb249aG90Lm1ha2VFbnVtKFsidG9wIiwiYm90dG9tIl0pO1NlLlRpZXJMYWJlbFBvc2l0aW9uPWhvdC5tYWtlRW51bShbImJldHdlZW4iLCJjZW50ZXIiXSk7dmFyIFRGZT1mdW5jdGlvbihlKXtNRmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdChyLG4saSl7dmFyIG89ZS5jYWxsKHRoaXMscixuKXx8dGhpcztyZXR1cm4gby5fbWF4VGltZUludGVydmFsUHJlY2lzaW9uPW51bGwsby5fdGllckxhYmVsUG9zaXRpb25zPVtdLG8uX3VzZVVUQz1pLG8uYWRkQ2xhc3MoInRpbWUtYXhpcyIpLG8udGlja0xhYmVsUGFkZGluZyg1KSxvLmF4aXNDb25maWd1cmF0aW9ucyh0Ll9ERUZBVUxUX1RJTUVfQVhJU19DT05GSUdVUkFUSU9OUyhvLl91c2VVVEMpKSxvLmFubm90YXRpb25Gb3JtYXR0ZXIoYSR0LnRpbWUoIiVhICViICVkLCAlWSIsby5fdXNlVVRDKSksb31yZXR1cm4gdC5wcm90b3R5cGUudGllckxhYmVsUG9zaXRpb25zPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX3RpZXJMYWJlbFBvc2l0aW9ucztpZighci5ldmVyeShmdW5jdGlvbihuKXtyZXR1cm4gbi50b0xvd2VyQ2FzZSgpPT09ImJldHdlZW4ifHxuLnRvTG93ZXJDYXNlKCk9PT0iY2VudGVyIn0pKXRocm93IG5ldyBFcnJvcigiVW5zdXBwb3J0ZWQgcG9zaXRpb24gZm9yIHRpZXIgbGFiZWxzIik7cmV0dXJuIHRoaXMuX3RpZXJMYWJlbFBvc2l0aW9ucz1yLHRoaXMucmVkcmF3KCksdGhpc30sdC5wcm90b3R5cGUubWF4VGltZUludGVydmFsUHJlY2lzaW9uPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX21heFRpbWVJbnRlcnZhbFByZWNpc2lvbjoodGhpcy5fbWF4VGltZUludGVydmFsUHJlY2lzaW9uPXIsdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5wcm90b3R5cGUuY3VycmVudEF4aXNDb25maWd1cmF0aW9uPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3Bvc3NpYmxlVGltZUF4aXNDb25maWd1cmF0aW9uc1t0aGlzLl9tb3N0UHJlY2lzZUNvbmZpZ0luZGV4XX0sdC5wcm90b3R5cGUuYXhpc0NvbmZpZ3VyYXRpb25zPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX3Bvc3NpYmxlVGltZUF4aXNDb25maWd1cmF0aW9uczt0aGlzLl9wb3NzaWJsZVRpbWVBeGlzQ29uZmlndXJhdGlvbnM9cix0aGlzLl9udW1UaWVycz1yNC5NYXRoLm1heCh0aGlzLl9wb3NzaWJsZVRpbWVBeGlzQ29uZmlndXJhdGlvbnMubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLmxlbmd0aH0pLDApLHRoaXMuX2lzQW5jaG9yZWQmJnRoaXMuX3NldHVwRG9tRWxlbWVudHMoKTtmb3IodmFyIG49dGhpcy50aWVyTGFiZWxQb3NpdGlvbnMoKSxpPVtdLG89MDtvPHRoaXMuX251bVRpZXJzO28rKylpLnB1c2gobltvXXx8ImJldHdlZW4iKTtyZXR1cm4gdGhpcy50aWVyTGFiZWxQb3NpdGlvbnMoaSksdGhpcy5yZWRyYXcoKSx0aGlzfSx0LnByb3RvdHlwZS5fZ2V0TW9zdFByZWNpc2VDb25maWd1cmF0aW9uSW5kZXg9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLG49dGhpcy5fcG9zc2libGVUaW1lQXhpc0NvbmZpZ3VyYXRpb25zLmxlbmd0aDtyZXR1cm4gdGhpcy5fcG9zc2libGVUaW1lQXhpc0NvbmZpZ3VyYXRpb25zLmZvckVhY2goZnVuY3Rpb24oaSxvKXtvPG4mJmkuZXZlcnkoZnVuY3Rpb24oYSl7cmV0dXJuIHIuX2NoZWNrVGltZUF4aXNUaWVyQ29uZmlndXJhdGlvbihhKX0pJiYobj1vKX0pLG49PT10aGlzLl9wb3NzaWJsZVRpbWVBeGlzQ29uZmlndXJhdGlvbnMubGVuZ3RoJiYocjQuV2luZG93Lndhcm4oInpvb21lZCBvdXQgdG9vIGZhcjogY291bGQgbm90IGZpbmQgc3VpdGFibGUgaW50ZXJ2YWwgdG8gZGlzcGxheSBsYWJlbHMiKSwtLW4pLG59LHQucHJvdG90eXBlLm9yaWVudGF0aW9uPWZ1bmN0aW9uKHIpe2lmKHImJihyLnRvTG93ZXJDYXNlKCk9PT0icmlnaHQifHxyLnRvTG93ZXJDYXNlKCk9PT0ibGVmdCIpKXRocm93IG5ldyBFcnJvcihyKyIgaXMgbm90IGEgc3VwcG9ydGVkIG9yaWVudGF0aW9uIGZvciBUaW1lQXhpcyAtIG9ubHkgaG9yaXpvbnRhbCBvcmllbnRhdGlvbnMgYXJlIHN1cHBvcnRlZCIpO3JldHVybiBlLnByb3RvdHlwZS5vcmllbnRhdGlvbi5jYWxsKHRoaXMscil9LHQucHJvdG90eXBlLl9jb21wdXRlSGVpZ2h0PWZ1bmN0aW9uKCl7dmFyIHI9dGhpcy5fbWVhc3VyZXIubWVhc3VyZSgpLmhlaWdodDt0aGlzLl90aWVySGVpZ2h0cz1bXTtmb3IodmFyIG49MDtuPHRoaXMuX251bVRpZXJzO24rKyl0aGlzLl90aWVySGVpZ2h0cy5wdXNoKHIrdGhpcy50aWNrTGFiZWxQYWRkaW5nKCkrKHRoaXMuX3RpZXJMYWJlbFBvc2l0aW9uc1tuXT09PSJiZXR3ZWVuIj8wOnRoaXMuX21heExhYmVsVGlja0xlbmd0aCgpKSk7cmV0dXJuIFZ1LnN1bSh0aGlzLl90aWVySGVpZ2h0cyl9LHQucHJvdG90eXBlLl9nZXRJbnRlcnZhbExlbmd0aD1mdW5jdGlvbihyKXt2YXIgbj10aGlzLl9zY2FsZS5kb21haW4oKVswXSxpPUVGZS5UaW1lLnRpbWVJbnRlcnZhbFRvRDNUaW1lKHIuaW50ZXJ2YWwsdGhpcy5fdXNlVVRDKSxvPWkub2Zmc2V0KG4sci5zdGVwKTtpZihvPnRoaXMuX3NjYWxlLmRvbWFpbigpWzFdKXJldHVybiB0aGlzLndpZHRoKCk7dmFyIGE9TWF0aC5hYnModGhpcy5fc2NhbGUuc2NhbGUobyktdGhpcy5fc2NhbGUuc2NhbGUobikpO3JldHVybiBhfSx0LnByb3RvdHlwZS5fbWF4V2lkdGhGb3JJbnRlcnZhbD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fbWVhc3VyZXIubWVhc3VyZShyLmZvcm1hdHRlcih0Ll9MT05HX0RBVEUpKS53aWR0aH0sdC5wcm90b3R5cGUuX2NoZWNrVGltZUF4aXNUaWVyQ29uZmlndXJhdGlvbj1mdW5jdGlvbihyKXtpZih0aGlzLl9tYXhUaW1lSW50ZXJ2YWxQcmVjaXNpb24hPW51bGwpe3ZhciBuPXQuX1NPUlRFRF9USU1FX0lOVEVSVkFMX0lOREVYW3RoaXMuX21heFRpbWVJbnRlcnZhbFByZWNpc2lvbl0saT10Ll9TT1JURURfVElNRV9JTlRFUlZBTF9JTkRFWFtyLmludGVydmFsXTtpZihuIT1udWxsJiZpIT1udWxsJiZpPG4pcmV0dXJuITF9dmFyIG89dGhpcy5fbWF4V2lkdGhGb3JJbnRlcnZhbChyKSsyKnRoaXMudGlja0xhYmVsUGFkZGluZygpO3JldHVybiBNYXRoLm1pbih0aGlzLl9nZXRJbnRlcnZhbExlbmd0aChyKSx0aGlzLndpZHRoKCkpPj1vfSx0LnByb3RvdHlwZS5fc2l6ZUZyb21PZmZlcj1mdW5jdGlvbihyLG4pe3ZhciBpPWUucHJvdG90eXBlLl9zaXplRnJvbU9mZmVyLmNhbGwodGhpcyxyLG4pLG89dGhpcy5fdGllckhlaWdodHMucmVkdWNlKGZ1bmN0aW9uKHMsbCxjLHUpe3JldHVybiBzK2w+aS5oZWlnaHQ/czpzK2x9KSxhPXRoaXMubWFyZ2luKCkrKHRoaXMuYW5ub3RhdGlvbnNFbmFibGVkKCk/dGhpcy5hbm5vdGF0aW9uVGllckNvdW50KCkqdGhpcy5fYW5ub3RhdGlvblRpZXJIZWlnaHQoKTowKTtyZXR1cm4gaS5oZWlnaHQ9TWF0aC5taW4oaS5oZWlnaHQsbythKSxpfSx0LnByb3RvdHlwZS5fc2V0dXA9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5fc2V0dXAuY2FsbCh0aGlzKSx0aGlzLl9zZXR1cERvbUVsZW1lbnRzKCl9LHQucHJvdG90eXBlLl9zZXR1cERvbUVsZW1lbnRzPWZ1bmN0aW9uKCl7dGhpcy5jb250ZW50KCkuc2VsZWN0QWxsKCIuIit0LlRJTUVfQVhJU19USUVSX0NMQVNTKS5yZW1vdmUoKSx0aGlzLl90aWVyTGFiZWxDb250YWluZXJzPVtdLHRoaXMuX3RpZXJNYXJrQ29udGFpbmVycz1bXSx0aGlzLl90aWVyQmFzZWxpbmVzPVtdLHRoaXMuX3RpY2tMYWJlbENvbnRhaW5lci5yZW1vdmUoKSx0aGlzLl9iYXNlbGluZS5yZW1vdmUoKTtmb3IodmFyIHI9MDtyPHRoaXMuX251bVRpZXJzOysrcil7dmFyIG49dGhpcy5jb250ZW50KCkuYXBwZW5kKCJnIikuY2xhc3NlZCh0LlRJTUVfQVhJU19USUVSX0NMQVNTLCEwKTt0aGlzLl90aWVyTGFiZWxDb250YWluZXJzLnB1c2gobi5hcHBlbmQoImciKS5jbGFzc2VkKHRzLkF4aXMuVElDS19MQUJFTF9DTEFTUysiLWNvbnRhaW5lciIsITApKSx0aGlzLl90aWVyTWFya0NvbnRhaW5lcnMucHVzaChuLmFwcGVuZCgiZyIpLmNsYXNzZWQodHMuQXhpcy5USUNLX01BUktfQ0xBU1MrIi1jb250YWluZXIiLCEwKSksdGhpcy5fdGllckJhc2VsaW5lcy5wdXNoKG4uYXBwZW5kKCJsaW5lIikuY2xhc3NlZCgiYmFzZWxpbmUiLCEwKSl9dmFyIGk9bmV3IG8kdC5TdmdDb250ZXh0KHRoaXMuX3RpZXJMYWJlbENvbnRhaW5lcnNbMF0ubm9kZSgpKTt0aGlzLl9tZWFzdXJlcj1uZXcgbyR0LkNhY2hlTWVhc3VyZXIoaSl9LHQucHJvdG90eXBlLl9nZXRUaWNrSW50ZXJ2YWxWYWx1ZXM9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3NjYWxlLnRpY2tJbnRlcnZhbChyLmludGVydmFsLHIuc3RlcCx0aGlzLl91c2VVVEMpfSx0LnByb3RvdHlwZS5fZ2V0VGlja1ZhbHVlcz1mdW5jdGlvbigpe3ZhciByPXRoaXM7cmV0dXJuIHRoaXMuX3Bvc3NpYmxlVGltZUF4aXNDb25maWd1cmF0aW9uc1t0aGlzLl9tb3N0UHJlY2lzZUNvbmZpZ0luZGV4XS5yZWR1Y2UoZnVuY3Rpb24obixpKXtyZXR1cm4gbi5jb25jYXQoci5fZ2V0VGlja0ludGVydmFsVmFsdWVzKGkpKX0sW10pfSx0LnByb3RvdHlwZS5fY2xlYW5UaWVycz1mdW5jdGlvbigpe2Zvcih2YXIgcj0wO3I8dGhpcy5fdGllckxhYmVsQ29udGFpbmVycy5sZW5ndGg7cisrKXRoaXMuX3RpZXJMYWJlbENvbnRhaW5lcnNbcl0uc2VsZWN0QWxsKCIuIit0cy5BeGlzLlRJQ0tfTEFCRUxfQ0xBU1MpLnJlbW92ZSgpLHRoaXMuX3RpZXJNYXJrQ29udGFpbmVyc1tyXS5zZWxlY3RBbGwoIi4iK3RzLkF4aXMuVElDS19NQVJLX0NMQVNTKS5yZW1vdmUoKSx0aGlzLl90aWVyQmFzZWxpbmVzW3JdLnN0eWxlKCJ2aXNpYmlsaXR5IiwiaGlkZGVuIil9LHQucHJvdG90eXBlLl9nZXRUaWNrVmFsdWVzRm9yQ29uZmlndXJhdGlvbj1mdW5jdGlvbihyKXt2YXIgbj10aGlzLl9zY2FsZS50aWNrSW50ZXJ2YWwoci5pbnRlcnZhbCxyLnN0ZXAsdGhpcy5fdXNlVVRDKSxpPXRoaXMuX3NjYWxlLmRvbWFpbigpLG89bi5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGEudmFsdWVPZigpfSk7cmV0dXJuIG8uaW5kZXhPZihpWzBdLnZhbHVlT2YoKSk9PT0tMSYmbi51bnNoaWZ0KGlbMF0pLG8uaW5kZXhPZihpWzFdLnZhbHVlT2YoKSk9PT0tMSYmbi5wdXNoKGlbMV0pLG59LHQucHJvdG90eXBlLl9yZW5kZXJUaWVyTGFiZWxzPWZ1bmN0aW9uKHIsbixpKXt2YXIgbz10aGlzLGE9dGhpcy5fZ2V0VGlja1ZhbHVlc0ZvckNvbmZpZ3VyYXRpb24obikscz1bXTt0aGlzLl90aWVyTGFiZWxQb3NpdGlvbnNbaV09PT0iYmV0d2VlbiImJm4uc3RlcD09PTE/YS5tYXAoZnVuY3Rpb24oZyxfKXtfKzE+PWEubGVuZ3RofHxzLnB1c2gobmV3IERhdGUoKGFbXysxXS52YWx1ZU9mKCktYVtfXS52YWx1ZU9mKCkpLzIrYVtfXS52YWx1ZU9mKCkpKX0pOnM9YTt2YXIgbD1yLnNlbGVjdEFsbCgiLiIrdHMuQXhpcy5USUNLX0xBQkVMX0NMQVNTKS5kYXRhKHMsZnVuY3Rpb24oZyl7cmV0dXJuIFN0cmluZyhnLnZhbHVlT2YoKSl9KSxjPWwuZW50ZXIoKS5hcHBlbmQoImciKS5jbGFzc2VkKHRzLkF4aXMuVElDS19MQUJFTF9DTEFTUywhMCk7Yy5hcHBlbmQoInRleHQiKTt2YXIgdT10aGlzLl90aWVyTGFiZWxQb3NpdGlvbnNbaV09PT0iY2VudGVyInx8bi5zdGVwPT09MT8wOnRoaXMudGlja0xhYmVsUGFkZGluZygpLGg7dGhpcy5vcmllbnRhdGlvbigpPT09ImJvdHRvbSI/aD1WdS5zdW0odGhpcy5fdGllckhlaWdodHMuc2xpY2UoMCxpKzEpKS10aGlzLnRpY2tMYWJlbFBhZGRpbmcoKTp0aGlzLl90aWVyTGFiZWxQb3NpdGlvbnNbaV09PT0iY2VudGVyIj9oPXRoaXMuaGVpZ2h0KCktVnUuc3VtKHRoaXMuX3RpZXJIZWlnaHRzLnNsaWNlKDAsaSkpLXRoaXMudGlja0xhYmVsUGFkZGluZygpLXRoaXMuX21heExhYmVsVGlja0xlbmd0aCgpOmg9dGhpcy5oZWlnaHQoKS1WdS5zdW0odGhpcy5fdGllckhlaWdodHMuc2xpY2UoMCxpKSktdGhpcy50aWNrTGFiZWxQYWRkaW5nKCk7dmFyIGY9bC5tZXJnZShjKSxwPWYuc2VsZWN0QWxsKCJ0ZXh0Iik7cC5zaXplKCk+MCYmcC5hdHRyKCJ0cmFuc2Zvcm0iLCJ0cmFuc2xhdGUoIit1KyIsIitoKyIpIiksbC5leGl0KCkucmVtb3ZlKCksZi5hdHRyKCJ0cmFuc2Zvcm0iLGZ1bmN0aW9uKGcpe3JldHVybiJ0cmFuc2xhdGUoIitvLl9zY2FsZS5zY2FsZShnKSsiLDApIn0pO3ZhciBkPXRoaXMuX3RpZXJMYWJlbFBvc2l0aW9uc1tpXT09PSJjZW50ZXIifHxuLnN0ZXA9PT0xPyJtaWRkbGUiOiJzdGFydCI7Zi5zZWxlY3RBbGwoInRleHQiKS50ZXh0KG4uZm9ybWF0dGVyKS5zdHlsZSgidGV4dC1hbmNob3IiLGQpfSx0LnByb3RvdHlwZS5fcmVuZGVyVGlja01hcmtzPWZ1bmN0aW9uKHIsbil7dmFyIGk9dGhpcy5fdGllck1hcmtDb250YWluZXJzW25dLnNlbGVjdEFsbCgiLiIrdHMuQXhpcy5USUNLX01BUktfQ0xBU1MpLmRhdGEociksbz1pLmVudGVyKCkuYXBwZW5kKCJsaW5lIikuY2xhc3NlZCh0cy5BeGlzLlRJQ0tfTUFSS19DTEFTUywhMCkubWVyZ2UoaSksYT10aGlzLl9nZW5lcmF0ZVRpY2tNYXJrQXR0ckhhc2goKSxzPXRoaXMuX3RpZXJIZWlnaHRzLnNsaWNlKDAsbikucmVkdWNlKGZ1bmN0aW9uKGwsYyl7cmV0dXJuIGwrY30sMCk7dGhpcy5vcmllbnRhdGlvbigpPT09ImJvdHRvbSI/KGEueTE9cyxhLnkyPXMrKHRoaXMuX3RpZXJMYWJlbFBvc2l0aW9uc1tuXT09PSJjZW50ZXIiP3RoaXMuaW5uZXJUaWNrTGVuZ3RoKCk6dGhpcy5fdGllckhlaWdodHNbbl0pKTooYS55MT10aGlzLmhlaWdodCgpLXMsYS55Mj10aGlzLmhlaWdodCgpLShzKyh0aGlzLl90aWVyTGFiZWxQb3NpdGlvbnNbbl09PT0iY2VudGVyIj90aGlzLmlubmVyVGlja0xlbmd0aCgpOnRoaXMuX3RpZXJIZWlnaHRzW25dKSkpLG8uYXR0cnMoYSksdGhpcy5vcmllbnRhdGlvbigpPT09ImJvdHRvbSI/KGEueTE9cyxhLnkyPXMrKHRoaXMuX3RpZXJMYWJlbFBvc2l0aW9uc1tuXT09PSJjZW50ZXIiP3RoaXMuZW5kVGlja0xlbmd0aCgpOnRoaXMuX3RpZXJIZWlnaHRzW25dKSk6KGEueTE9dGhpcy5oZWlnaHQoKS1zLGEueTI9dGhpcy5oZWlnaHQoKS0ocysodGhpcy5fdGllckxhYmVsUG9zaXRpb25zW25dPT09ImNlbnRlciI/dGhpcy5lbmRUaWNrTGVuZ3RoKCk6dGhpcy5fdGllckhlaWdodHNbbl0pKSksVnUuc2VsZWN0KG8ubm9kZXMoKVswXSkuYXR0cnMoYSksVnUuc2VsZWN0KG8ubm9kZXMoKVtvLnNpemUoKS0xXSkuYXR0cnMoYSksVnUuc2VsZWN0KG8ubm9kZXMoKVswXSkuY2xhc3NlZCh0cy5BeGlzLkVORF9USUNLX01BUktfQ0xBU1MsITApLFZ1LnNlbGVjdChvLm5vZGVzKClbby5zaXplKCktMV0pLmNsYXNzZWQodHMuQXhpcy5FTkRfVElDS19NQVJLX0NMQVNTLCEwKSxpLmV4aXQoKS5yZW1vdmUoKX0sdC5wcm90b3R5cGUuX3JlbmRlckxhYmVsbGVzc1RpY2tNYXJrcz1mdW5jdGlvbihyKXt2YXIgbj10aGlzLl90aWNrTWFya0NvbnRhaW5lci5zZWxlY3RBbGwoIi4iK3RzLkF4aXMuVElDS19NQVJLX0NMQVNTKS5kYXRhKHIpLGk9bi5lbnRlcigpLmFwcGVuZCgibGluZSIpLmNsYXNzZWQodHMuQXhpcy5USUNLX01BUktfQ0xBU1MsITApLm1lcmdlKG4pLG89dGhpcy5fZ2VuZXJhdGVUaWNrTWFya0F0dHJIYXNoKCk7by55Mj10aGlzLm9yaWVudGF0aW9uKCk9PT0iYm90dG9tIj90aGlzLnRpY2tMYWJlbFBhZGRpbmcoKTp0aGlzLmhlaWdodCgpLXRoaXMudGlja0xhYmVsUGFkZGluZygpLGkuYXR0cnMobyksbi5leGl0KCkucmVtb3ZlKCl9LHQucHJvdG90eXBlLl9nZW5lcmF0ZUxhYmVsbGVzc1RpY2tzPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX21vc3RQcmVjaXNlQ29uZmlnSW5kZXg8MT9bXTp0aGlzLl9nZXRUaWNrSW50ZXJ2YWxWYWx1ZXModGhpcy5fcG9zc2libGVUaW1lQXhpc0NvbmZpZ3VyYXRpb25zW3RoaXMuX21vc3RQcmVjaXNlQ29uZmlnSW5kZXgtMV1bMF0pfSx0LnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseT1mdW5jdGlvbigpe3ZhciByPXRoaXM7dGhpcy5fbW9zdFByZWNpc2VDb25maWdJbmRleD10aGlzLl9nZXRNb3N0UHJlY2lzZUNvbmZpZ3VyYXRpb25JbmRleCgpO3ZhciBuPXRoaXMuX3Bvc3NpYmxlVGltZUF4aXNDb25maWd1cmF0aW9uc1t0aGlzLl9tb3N0UHJlY2lzZUNvbmZpZ0luZGV4XTt0aGlzLl9jbGVhblRpZXJzKCksbi5mb3JFYWNoKGZ1bmN0aW9uKGgsZil7cmV0dXJuIHIuX3JlbmRlclRpZXJMYWJlbHMoci5fdGllckxhYmVsQ29udGFpbmVyc1tmXSxoLGYpfSk7Zm9yKHZhciBpPW4ubWFwKGZ1bmN0aW9uKGgsZil7cmV0dXJuIHIuX2dldFRpY2tWYWx1ZXNGb3JDb25maWd1cmF0aW9uKGgpfSksbz0wLGE9MDthPE1hdGgubWF4KG4ubGVuZ3RoLDEpOysrYSl7dmFyIHM9dGhpcy5fZ2VuZXJhdGVCYXNlbGluZUF0dHJIYXNoKCk7cy55MSs9dGhpcy5vcmllbnRhdGlvbigpPT09ImJvdHRvbSI/bzotbyxzLnkyPXMueTEsdGhpcy5fdGllckJhc2VsaW5lc1thXS5hdHRycyhzKS5zdHlsZSgidmlzaWJpbGl0eSIsImluaGVyaXQiKSxvKz10aGlzLl90aWVySGVpZ2h0c1thXX12YXIgbD1bXSxjPXRoaXMuX3NjYWxlLmRvbWFpbigpLHU9dGhpcy5fc2NhbGUuc2NhbGUoY1sxXSktdGhpcy5fc2NhbGUuc2NhbGUoY1swXSk7dGhpcy5fZ2V0SW50ZXJ2YWxMZW5ndGgoblswXSkqMS41Pj11JiYobD10aGlzLl9nZW5lcmF0ZUxhYmVsbGVzc1RpY2tzKCkpLHRoaXMuX3JlbmRlckxhYmVsbGVzc1RpY2tNYXJrcyhsKSx0aGlzLl9oaWRlT3ZlcmZsb3dpbmdUaWVycygpO2Zvcih2YXIgYT0wO2E8bi5sZW5ndGg7KythKXRoaXMuX3JlbmRlclRpY2tNYXJrcyhpW2FdLGEpLHRoaXMuX2hpZGVPdmVybGFwcGluZ0FuZEN1dE9mZkxhYmVscyhhKTtyZXR1cm4gdGhpcy5hbm5vdGF0aW9uc0VuYWJsZWQoKT90aGlzLl9kcmF3QW5ub3RhdGlvbnMoKTp0aGlzLl9yZW1vdmVBbm5vdGF0aW9ucygpLHRoaXN9LHQucHJvdG90eXBlLl9oaWRlT3ZlcmZsb3dpbmdUaWVycz1mdW5jdGlvbigpe3ZhciByPXRoaXMsbj10aGlzLmhlaWdodCgpLGk9MDt0aGlzLmNvbnRlbnQoKS5zZWxlY3RBbGwoIi4iK3QuVElNRV9BWElTX1RJRVJfQ0xBU1MpLmF0dHIoInZpc2liaWxpdHkiLGZ1bmN0aW9uKG8sYSl7cmV0dXJuIGkrPXIuX3RpZXJIZWlnaHRzW2FdLGk8PW4/ImluaGVyaXQiOiJoaWRkZW4ifSl9LHQucHJvdG90eXBlLl9oaWRlT3ZlcmxhcHBpbmdBbmRDdXRPZmZMYWJlbHM9ZnVuY3Rpb24ocil7dmFyIG49dGhpcyxpPXRoaXMuZWxlbWVudCgpLm5vZGUoKS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxvPWZ1bmN0aW9uKHUpe3JldHVybiBNYXRoLmZsb29yKGkubGVmdCk8PU1hdGguY2VpbCh1LmxlZnQpJiZNYXRoLmZsb29yKGkudG9wKTw9TWF0aC5jZWlsKHUudG9wKSYmTWF0aC5mbG9vcih1LnJpZ2h0KTw9TWF0aC5jZWlsKGkubGVmdCtuLndpZHRoKCkpJiZNYXRoLmZsb29yKHUuYm90dG9tKTw9TWF0aC5jZWlsKGkudG9wK24uaGVpZ2h0KCkpfSxhPXRoaXMuX3RpZXJNYXJrQ29udGFpbmVyc1tyXS5zZWxlY3RBbGwoIi4iK3RzLkF4aXMuVElDS19NQVJLX0NMQVNTKS5maWx0ZXIoZnVuY3Rpb24odSxoKXt2YXIgZj1WdS5zZWxlY3QodGhpcykuc3R5bGUoInZpc2liaWxpdHkiKTtyZXR1cm4gZj09PSJ2aXNpYmxlInx8Zj09PSJpbmhlcml0In0pLHM9YS5ub2RlcygpLm1hcChmdW5jdGlvbih1KXtyZXR1cm4gdS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKX0pLGw9dGhpcy5fdGllckxhYmVsQ29udGFpbmVyc1tyXS5zZWxlY3RBbGwoIi4iK3RzLkF4aXMuVElDS19MQUJFTF9DTEFTUykuZmlsdGVyKGZ1bmN0aW9uKHUsaCl7dmFyIGY9VnUuc2VsZWN0KHRoaXMpLnN0eWxlKCJ2aXNpYmlsaXR5Iik7cmV0dXJuIGY9PT0idmlzaWJsZSJ8fGY9PT0iaW5oZXJpdCJ9KSxjO2wuZWFjaChmdW5jdGlvbih1LGgpe3ZhciBmPXRoaXMuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkscD1WdS5zZWxlY3QodGhpcyksZD1zW2hdLGc9c1toKzFdLF89YyE9bnVsbCYmcjQuRE9NLmNsaWVudFJlY3RzT3ZlcmxhcChmLGMpLHk9ZCE9bnVsbCYmcjQuRE9NLmNsaWVudFJlY3RzT3ZlcmxhcChmLGQpLHg9ZyE9bnVsbCYmcjQuRE9NLmNsaWVudFJlY3RzT3ZlcmxhcChmLGcpOyFvKGYpfHxffHx5fHx4P3Auc3R5bGUoInZpc2liaWxpdHkiLCJoaWRkZW4iKTooYz1mLHAuc3R5bGUoInZpc2liaWxpdHkiLCJpbmhlcml0IikpfSl9LHQucHJvdG90eXBlLmludmFsaWRhdGVDYWNoZT1mdW5jdGlvbigpe2UucHJvdG90eXBlLmludmFsaWRhdGVDYWNoZS5jYWxsKHRoaXMpLHRoaXMuX21lYXN1cmVyLnJlc2V0KCl9LHQuVElNRV9BWElTX1RJRVJfQ0xBU1M9InRpbWUtYXhpcy10aWVyIix0Ll9TT1JURURfVElNRV9JTlRFUlZBTF9JTkRFWD0oeGQ9e30seGRbU2UuVGltZUludGVydmFsLnNlY29uZF09MCx4ZFtTZS5UaW1lSW50ZXJ2YWwubWludXRlXT0xLHhkW1NlLlRpbWVJbnRlcnZhbC5ob3VyXT0yLHhkW1NlLlRpbWVJbnRlcnZhbC5kYXldPTMseGRbU2UuVGltZUludGVydmFsLndlZWtdPTQseGRbU2UuVGltZUludGVydmFsLm1vbnRoXT01LHhkW1NlLlRpbWVJbnRlcnZhbC55ZWFyXT02LHhkKSx0Ll9ERUZBVUxUX1RJTUVfQVhJU19DT05GSUdVUkFUSU9OUz1mdW5jdGlvbihyKXt2YXIgbj1mdW5jdGlvbihpKXtyZXR1cm4gYSR0LnRpbWUoaSxyKX07cmV0dXJuW1t7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLnNlY29uZCxzdGVwOjEsZm9ybWF0dGVyOm4oIiVJOiVNOiVTICVwIil9LHtpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwuZGF5LHN0ZXA6MSxmb3JtYXR0ZXI6bigiJUIgJWUsICVZIil9XSxbe2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5zZWNvbmQsc3RlcDo1LGZvcm1hdHRlcjpuKCIlSTolTTolUyAlcCIpfSx7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLmRheSxzdGVwOjEsZm9ybWF0dGVyOm4oIiVCICVlLCAlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwuc2Vjb25kLHN0ZXA6MTAsZm9ybWF0dGVyOm4oIiVJOiVNOiVTICVwIil9LHtpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwuZGF5LHN0ZXA6MSxmb3JtYXR0ZXI6bigiJUIgJWUsICVZIil9XSxbe2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5zZWNvbmQsc3RlcDoxNSxmb3JtYXR0ZXI6bigiJUk6JU06JVMgJXAiKX0se2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5kYXksc3RlcDoxLGZvcm1hdHRlcjpuKCIlQiAlZSwgJVkiKX1dLFt7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLnNlY29uZCxzdGVwOjMwLGZvcm1hdHRlcjpuKCIlSTolTTolUyAlcCIpfSx7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLmRheSxzdGVwOjEsZm9ybWF0dGVyOm4oIiVCICVlLCAlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwubWludXRlLHN0ZXA6MSxmb3JtYXR0ZXI6bigiJUk6JU0gJXAiKX0se2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5kYXksc3RlcDoxLGZvcm1hdHRlcjpuKCIlQiAlZSwgJVkiKX1dLFt7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLm1pbnV0ZSxzdGVwOjUsZm9ybWF0dGVyOm4oIiVJOiVNICVwIil9LHtpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwuZGF5LHN0ZXA6MSxmb3JtYXR0ZXI6bigiJUIgJWUsICVZIil9XSxbe2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5taW51dGUsc3RlcDoxMCxmb3JtYXR0ZXI6bigiJUk6JU0gJXAiKX0se2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5kYXksc3RlcDoxLGZvcm1hdHRlcjpuKCIlQiAlZSwgJVkiKX1dLFt7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLm1pbnV0ZSxzdGVwOjE1LGZvcm1hdHRlcjpuKCIlSTolTSAlcCIpfSx7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLmRheSxzdGVwOjEsZm9ybWF0dGVyOm4oIiVCICVlLCAlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwubWludXRlLHN0ZXA6MzAsZm9ybWF0dGVyOm4oIiVJOiVNICVwIil9LHtpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwuZGF5LHN0ZXA6MSxmb3JtYXR0ZXI6bigiJUIgJWUsICVZIil9XSxbe2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5ob3VyLHN0ZXA6MSxmb3JtYXR0ZXI6bigiJUkgJXAiKX0se2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5kYXksc3RlcDoxLGZvcm1hdHRlcjpuKCIlQiAlZSwgJVkiKX1dLFt7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLmhvdXIsc3RlcDozLGZvcm1hdHRlcjpuKCIlSSAlcCIpfSx7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLmRheSxzdGVwOjEsZm9ybWF0dGVyOm4oIiVCICVlLCAlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwuaG91cixzdGVwOjYsZm9ybWF0dGVyOm4oIiVJICVwIil9LHtpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwuZGF5LHN0ZXA6MSxmb3JtYXR0ZXI6bigiJUIgJWUsICVZIil9XSxbe2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5ob3VyLHN0ZXA6MTIsZm9ybWF0dGVyOm4oIiVJICVwIil9LHtpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwuZGF5LHN0ZXA6MSxmb3JtYXR0ZXI6bigiJUIgJWUsICVZIil9XSxbe2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC5kYXksc3RlcDoxLGZvcm1hdHRlcjpuKCIlYSAlZSIpfSx7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLm1vbnRoLHN0ZXA6MSxmb3JtYXR0ZXI6bigiJUIgJVkiKX1dLFt7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLmRheSxzdGVwOjEsZm9ybWF0dGVyOm4oIiVlIil9LHtpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwubW9udGgsc3RlcDoxLGZvcm1hdHRlcjpuKCIlQiAlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwubW9udGgsc3RlcDoxLGZvcm1hdHRlcjpuKCIlQiIpfSx7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLnllYXIsc3RlcDoxLGZvcm1hdHRlcjpuKCIlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwubW9udGgsc3RlcDoxLGZvcm1hdHRlcjpuKCIlYiIpfSx7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLnllYXIsc3RlcDoxLGZvcm1hdHRlcjpuKCIlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwubW9udGgsc3RlcDozLGZvcm1hdHRlcjpuKCIlYiIpfSx7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLnllYXIsc3RlcDoxLGZvcm1hdHRlcjpuKCIlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwubW9udGgsc3RlcDo2LGZvcm1hdHRlcjpuKCIlYiIpfSx7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLnllYXIsc3RlcDoxLGZvcm1hdHRlcjpuKCIlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwueWVhcixzdGVwOjEsZm9ybWF0dGVyOm4oIiVZIil9XSxbe2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC55ZWFyLHN0ZXA6MSxmb3JtYXR0ZXI6bigiJXkiKX1dLFt7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLnllYXIsc3RlcDo1LGZvcm1hdHRlcjpuKCIlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwueWVhcixzdGVwOjI1LGZvcm1hdHRlcjpuKCIlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwueWVhcixzdGVwOjUwLGZvcm1hdHRlcjpuKCIlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwueWVhcixzdGVwOjEwMCxmb3JtYXR0ZXI6bigiJVkiKX1dLFt7aW50ZXJ2YWw6U2UuVGltZUludGVydmFsLnllYXIsc3RlcDoyMDAsZm9ybWF0dGVyOm4oIiVZIil9XSxbe2ludGVydmFsOlNlLlRpbWVJbnRlcnZhbC55ZWFyLHN0ZXA6NTAwLGZvcm1hdHRlcjpuKCIlWSIpfV0sW3tpbnRlcnZhbDpTZS5UaW1lSW50ZXJ2YWwueWVhcixzdGVwOjFlMyxmb3JtYXR0ZXI6bigiJVkiKX1dXX0sdC5fTE9OR19EQVRFPW5ldyBEYXRlKDk5OTksOCwyOSwxMiw1OSw5OTk5KSx0fSh0cy5BeGlzKTtTZS5UaW1lPVRGZTt2YXIgeGR9KTt2YXIgcyR0PUgobjQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KG40LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgZm90PShkZSgpLFV0KHBlKSk7Zm90Ll9fZXhwb3J0U3RhcihHWHQoKSxuNCk7Zm90Ll9fZXhwb3J0U3RhcihXWHQoKSxuNCk7Zm90Ll9fZXhwb3J0U3RhcihCRigpLG40KX0pO3ZhciBpND1IKHBvdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkocG90LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgQ0ZlPUZlKCksQUZlPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSgpe3RoaXMuX2V2ZW50VG9Qcm9jZXNzaW5nRnVuY3Rpb249e30sdGhpcy5fZXZlbnRUYXJnZXQ9ZG9jdW1lbnQsdGhpcy5fZXZlbnROYW1lVG9DYWxsYmFja1NldD17fSx0aGlzLl9jb25uZWN0ZWQ9ITF9cmV0dXJuIGUucHJvdG90eXBlLl9oYXNOb0NhbGxiYWNrcz1mdW5jdGlvbigpe2Zvcih2YXIgdD1PYmplY3Qua2V5cyh0aGlzLl9ldmVudE5hbWVUb0NhbGxiYWNrU2V0KSxyPTA7cjx0Lmxlbmd0aDtyKyspaWYodGhpcy5fZXZlbnROYW1lVG9DYWxsYmFja1NldFt0W3JdXS5zaXplIT09MClyZXR1cm4hMTtyZXR1cm4hMH0sZS5wcm90b3R5cGUuX2Nvbm5lY3Q9ZnVuY3Rpb24oKXt2YXIgdD10aGlzO3RoaXMuX2Nvbm5lY3RlZHx8KE9iamVjdC5rZXlzKHRoaXMuX2V2ZW50VG9Qcm9jZXNzaW5nRnVuY3Rpb24pLmZvckVhY2goZnVuY3Rpb24ocil7dmFyIG49dC5fZXZlbnRUb1Byb2Nlc3NpbmdGdW5jdGlvbltyXSxpPXI9PT0id2hlZWwiP3twYXNzaXZlOiExfTp2b2lkIDA7dC5fZXZlbnRUYXJnZXQuYWRkRXZlbnRMaXN0ZW5lcihyLG4saSl9KSx0aGlzLl9jb25uZWN0ZWQ9ITApfSxlLnByb3RvdHlwZS5fZGlzY29ubmVjdD1mdW5jdGlvbigpe3ZhciB0PXRoaXM7dGhpcy5fY29ubmVjdGVkJiZ0aGlzLl9oYXNOb0NhbGxiYWNrcygpJiYoT2JqZWN0LmtleXModGhpcy5fZXZlbnRUb1Byb2Nlc3NpbmdGdW5jdGlvbikuZm9yRWFjaChmdW5jdGlvbihyKXt2YXIgbj10Ll9ldmVudFRvUHJvY2Vzc2luZ0Z1bmN0aW9uW3JdO3QuX2V2ZW50VGFyZ2V0LnJlbW92ZUV2ZW50TGlzdGVuZXIocixuKX0pLHRoaXMuX2Nvbm5lY3RlZD0hMSl9LGUucHJvdG90eXBlLl9hZGRDYWxsYmFja0ZvckV2ZW50PWZ1bmN0aW9uKHQscil7dGhpcy5fZXZlbnROYW1lVG9DYWxsYmFja1NldFt0XT09bnVsbCYmKHRoaXMuX2V2ZW50TmFtZVRvQ2FsbGJhY2tTZXRbdF09bmV3IENGZS5DYWxsYmFja1NldCksdGhpcy5fZXZlbnROYW1lVG9DYWxsYmFja1NldFt0XS5hZGQociksdGhpcy5fY29ubmVjdCgpfSxlLnByb3RvdHlwZS5fcmVtb3ZlQ2FsbGJhY2tGb3JFdmVudD1mdW5jdGlvbih0LHIpe3RoaXMuX2V2ZW50TmFtZVRvQ2FsbGJhY2tTZXRbdF0hPW51bGwmJnRoaXMuX2V2ZW50TmFtZVRvQ2FsbGJhY2tTZXRbdF0uZGVsZXRlKHIpLHRoaXMuX2Rpc2Nvbm5lY3QoKX0sZS5wcm90b3R5cGUuX2NhbGxDYWxsYmFja3NGb3JFdmVudD1mdW5jdGlvbih0KXtmb3IodmFyIHI9W10sbj0xO248YXJndW1lbnRzLmxlbmd0aDtuKyspcltuLTFdPWFyZ3VtZW50c1tuXTt2YXIgaT10aGlzLl9ldmVudE5hbWVUb0NhbGxiYWNrU2V0W3RdO2khPW51bGwmJmkuY2FsbENhbGxiYWNrcy5hcHBseShpLHIpfSxlfSgpO3BvdC5EaXNwYXRjaGVyPUFGZX0pO3ZhciBsJHQ9SChkb3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGRvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFBGZT0oZGUoKSxVdChwZSkpLElGZT1pNCgpLExGZT1mdW5jdGlvbihlKXtQRmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3ZhciByPWUuY2FsbCh0aGlzKXx8dGhpcztyZXR1cm4gci5fZXZlbnRUb1Byb2Nlc3NpbmdGdW5jdGlvblt0Ll9LRVlET1dOX0VWRU5UX05BTUVdPWZ1bmN0aW9uKG4pe3JldHVybiByLl9wcm9jZXNzS2V5ZG93bihuKX0sci5fZXZlbnRUb1Byb2Nlc3NpbmdGdW5jdGlvblt0Ll9LRVlVUF9FVkVOVF9OQU1FXT1mdW5jdGlvbihuKXtyZXR1cm4gci5fcHJvY2Vzc0tleXVwKG4pfSxyfXJldHVybiB0LmdldERpc3BhdGNoZXI9ZnVuY3Rpb24oKXt2YXIgcj1kb2N1bWVudFt0Ll9ESVNQQVRDSEVSX0tFWV07cmV0dXJuIHI9PW51bGwmJihyPW5ldyB0LGRvY3VtZW50W3QuX0RJU1BBVENIRVJfS0VZXT1yKSxyfSx0LnByb3RvdHlwZS5fcHJvY2Vzc0tleWRvd249ZnVuY3Rpb24ocil7dGhpcy5fY2FsbENhbGxiYWNrc0ZvckV2ZW50KHQuX0tFWURPV05fRVZFTlRfTkFNRSxyLmtleUNvZGUscil9LHQucHJvdG90eXBlLl9wcm9jZXNzS2V5dXA9ZnVuY3Rpb24ocil7dGhpcy5fY2FsbENhbGxiYWNrc0ZvckV2ZW50KHQuX0tFWVVQX0VWRU5UX05BTUUsci5rZXlDb2RlLHIpfSx0LnByb3RvdHlwZS5vbktleURvd249ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2FkZENhbGxiYWNrRm9yRXZlbnQodC5fS0VZRE9XTl9FVkVOVF9OQU1FLHIpLHRoaXN9LHQucHJvdG90eXBlLm9mZktleURvd249ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3JlbW92ZUNhbGxiYWNrRm9yRXZlbnQodC5fS0VZRE9XTl9FVkVOVF9OQU1FLHIpLHRoaXN9LHQucHJvdG90eXBlLm9uS2V5VXA9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2FkZENhbGxiYWNrRm9yRXZlbnQodC5fS0VZVVBfRVZFTlRfTkFNRSxyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZLZXlVcD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcmVtb3ZlQ2FsbGJhY2tGb3JFdmVudCh0Ll9LRVlVUF9FVkVOVF9OQU1FLHIpLHRoaXN9LHQuX0RJU1BBVENIRVJfS0VZPSJfX1Bsb3R0YWJsZV9EaXNwYXRjaGVyX0tleSIsdC5fS0VZRE9XTl9FVkVOVF9OQU1FPSJrZXlkb3duIix0Ll9LRVlVUF9FVkVOVF9OQU1FPSJrZXl1cCIsdH0oSUZlLkRpc3BhdGNoZXIpO2RvdC5LZXk9TEZlfSk7dmFyIHUkdD1IKG1vdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkobW90LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIga0ZlPShkZSgpLFV0KHBlKSksYyR0PUZlKCksUkZlPWk0KCksTkZlPWZ1bmN0aW9uKGUpe2tGZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3ZhciBuPWUuY2FsbCh0aGlzKXx8dGhpcztuLl9sYXN0TW91c2VQb3NpdGlvbj17eDotMSx5Oi0xfSxuLl90cmFuc2xhdG9yPWMkdC5nZXRUcmFuc2xhdG9yKHIpO3ZhciBpPWZ1bmN0aW9uKG8pe3JldHVybiBuLl9tZWFzdXJlQW5kRGlzcGF0Y2gocixvLHQuX01PVVNFTU9WRV9FVkVOVF9OQU1FLCJwYWdlIil9O3JldHVybiBuLl9ldmVudFRvUHJvY2Vzc2luZ0Z1bmN0aW9uW3QuX01PVVNFT1ZFUl9FVkVOVF9OQU1FXT1pLG4uX2V2ZW50VG9Qcm9jZXNzaW5nRnVuY3Rpb25bdC5fTU9VU0VNT1ZFX0VWRU5UX05BTUVdPWksbi5fZXZlbnRUb1Byb2Nlc3NpbmdGdW5jdGlvblt0Ll9NT1VTRU9VVF9FVkVOVF9OQU1FXT1pLG4uX2V2ZW50VG9Qcm9jZXNzaW5nRnVuY3Rpb25bdC5fTU9VU0VET1dOX0VWRU5UX05BTUVdPWZ1bmN0aW9uKG8pe3JldHVybiBuLl9tZWFzdXJlQW5kRGlzcGF0Y2gocixvLHQuX01PVVNFRE9XTl9FVkVOVF9OQU1FKX0sbi5fZXZlbnRUb1Byb2Nlc3NpbmdGdW5jdGlvblt0Ll9NT1VTRVVQX0VWRU5UX05BTUVdPWZ1bmN0aW9uKG8pe3JldHVybiBuLl9tZWFzdXJlQW5kRGlzcGF0Y2gocixvLHQuX01PVVNFVVBfRVZFTlRfTkFNRSwicGFnZSIpfSxuLl9ldmVudFRvUHJvY2Vzc2luZ0Z1bmN0aW9uW3QuX1dIRUVMX0VWRU5UX05BTUVdPWZ1bmN0aW9uKG8pe3JldHVybiBuLl9tZWFzdXJlQW5kRGlzcGF0Y2gocixvLHQuX1dIRUVMX0VWRU5UX05BTUUpfSxuLl9ldmVudFRvUHJvY2Vzc2luZ0Z1bmN0aW9uW3QuX0RCTENMSUNLX0VWRU5UX05BTUVdPWZ1bmN0aW9uKG8pe3JldHVybiBuLl9tZWFzdXJlQW5kRGlzcGF0Y2gocixvLHQuX0RCTENMSUNLX0VWRU5UX05BTUUpfSxufXJldHVybiB0LmdldERpc3BhdGNoZXI9ZnVuY3Rpb24ocil7dmFyIG49ci5yb290KCkucm9vdEVsZW1lbnQoKSxpPW5bdC5fRElTUEFUQ0hFUl9LRVldO3JldHVybiBpPT1udWxsJiYoaT1uZXcgdChyKSxuW3QuX0RJU1BBVENIRVJfS0VZXT1pKSxpfSx0LnByb3RvdHlwZS5vbk1vdXNlTW92ZT1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fYWRkQ2FsbGJhY2tGb3JFdmVudCh0Ll9NT1VTRU1PVkVfRVZFTlRfTkFNRSxyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZNb3VzZU1vdmU9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3JlbW92ZUNhbGxiYWNrRm9yRXZlbnQodC5fTU9VU0VNT1ZFX0VWRU5UX05BTUUsciksdGhpc30sdC5wcm90b3R5cGUub25Nb3VzZURvd249ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2FkZENhbGxiYWNrRm9yRXZlbnQodC5fTU9VU0VET1dOX0VWRU5UX05BTUUsciksdGhpc30sdC5wcm90b3R5cGUub2ZmTW91c2VEb3duPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9yZW1vdmVDYWxsYmFja0ZvckV2ZW50KHQuX01PVVNFRE9XTl9FVkVOVF9OQU1FLHIpLHRoaXN9LHQucHJvdG90eXBlLm9uTW91c2VVcD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fYWRkQ2FsbGJhY2tGb3JFdmVudCh0Ll9NT1VTRVVQX0VWRU5UX05BTUUsciksdGhpc30sdC5wcm90b3R5cGUub2ZmTW91c2VVcD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcmVtb3ZlQ2FsbGJhY2tGb3JFdmVudCh0Ll9NT1VTRVVQX0VWRU5UX05BTUUsciksdGhpc30sdC5wcm90b3R5cGUub25XaGVlbD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fYWRkQ2FsbGJhY2tGb3JFdmVudCh0Ll9XSEVFTF9FVkVOVF9OQU1FLHIpLHRoaXN9LHQucHJvdG90eXBlLm9mZldoZWVsPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9yZW1vdmVDYWxsYmFja0ZvckV2ZW50KHQuX1dIRUVMX0VWRU5UX05BTUUsciksdGhpc30sdC5wcm90b3R5cGUub25EYmxDbGljaz1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fYWRkQ2FsbGJhY2tGb3JFdmVudCh0Ll9EQkxDTElDS19FVkVOVF9OQU1FLHIpLHRoaXN9LHQucHJvdG90eXBlLm9mZkRibENsaWNrPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9yZW1vdmVDYWxsYmFja0ZvckV2ZW50KHQuX0RCTENMSUNLX0VWRU5UX05BTUUsciksdGhpc30sdC5wcm90b3R5cGUuX21lYXN1cmVBbmREaXNwYXRjaD1mdW5jdGlvbihyLG4saSxvKXtpZihvPT09dm9pZCAwJiYobz0iZWxlbWVudCIpLG8hPT0icGFnZSImJm8hPT0iZWxlbWVudCIpdGhyb3cgbmV3IEVycm9yKCJJbnZhbGlkIHNjb3BlICciK28rIicsIG11c3QgYmUgJ2VsZW1lbnQnIG9yICdwYWdlJyIpO2lmKG89PT0icGFnZSJ8fHRoaXMuZXZlbnRJbnNpZGUocixuKSl7dmFyIGE9dGhpcy5fdHJhbnNsYXRvci5jb21wdXRlUG9zaXRpb24obi5jbGllbnRYLG4uY2xpZW50WSk7dGhpcy5fbGFzdE1vdXNlUG9zaXRpb249YSx0aGlzLl9jYWxsQ2FsbGJhY2tzRm9yRXZlbnQoaSx0aGlzLmxhc3RNb3VzZVBvc2l0aW9uKCksbil9fSx0LnByb3RvdHlwZS5ldmVudEluc2lkZT1mdW5jdGlvbihyLG4pe3JldHVybiBjJHQuVHJhbnNsYXRvci5pc0V2ZW50SW5zaWRlKHIsbil9LHQucHJvdG90eXBlLmxhc3RNb3VzZVBvc2l0aW9uPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2xhc3RNb3VzZVBvc2l0aW9ufSx0Ll9ESVNQQVRDSEVSX0tFWT0iX19QbG90dGFibGVfRGlzcGF0Y2hlcl9Nb3VzZSIsdC5fTU9VU0VPVkVSX0VWRU5UX05BTUU9Im1vdXNlb3ZlciIsdC5fTU9VU0VNT1ZFX0VWRU5UX05BTUU9Im1vdXNlbW92ZSIsdC5fTU9VU0VPVVRfRVZFTlRfTkFNRT0ibW91c2VvdXQiLHQuX01PVVNFRE9XTl9FVkVOVF9OQU1FPSJtb3VzZWRvd24iLHQuX01PVVNFVVBfRVZFTlRfTkFNRT0ibW91c2V1cCIsdC5fV0hFRUxfRVZFTlRfTkFNRT0id2hlZWwiLHQuX0RCTENMSUNLX0VWRU5UX05BTUU9ImRibGNsaWNrIix0fShSRmUuRGlzcGF0Y2hlcik7bW90Lk1vdXNlPU5GZX0pO3ZhciBmJHQ9SChnb3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGdvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIERGZT0oZGUoKSxVdChwZSkpLGgkdD1GZSgpLE9GZT1pNCgpLHpGZT1mdW5jdGlvbihlKXtERmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdChyKXt2YXIgbj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIG4uX3RyYW5zbGF0b3I9aCR0LmdldFRyYW5zbGF0b3Iociksbi5fZXZlbnRUb1Byb2Nlc3NpbmdGdW5jdGlvblt0Ll9UT1VDSFNUQVJUX0VWRU5UX05BTUVdPWZ1bmN0aW9uKGkpe3JldHVybiBuLl9tZWFzdXJlQW5kRGlzcGF0Y2gocixpLHQuX1RPVUNIU1RBUlRfRVZFTlRfTkFNRSwicGFnZSIpfSxuLl9ldmVudFRvUHJvY2Vzc2luZ0Z1bmN0aW9uW3QuX1RPVUNITU9WRV9FVkVOVF9OQU1FXT1mdW5jdGlvbihpKXtyZXR1cm4gbi5fbWVhc3VyZUFuZERpc3BhdGNoKHIsaSx0Ll9UT1VDSE1PVkVfRVZFTlRfTkFNRSwicGFnZSIpfSxuLl9ldmVudFRvUHJvY2Vzc2luZ0Z1bmN0aW9uW3QuX1RPVUNIRU5EX0VWRU5UX05BTUVdPWZ1bmN0aW9uKGkpe3JldHVybiBuLl9tZWFzdXJlQW5kRGlzcGF0Y2gocixpLHQuX1RPVUNIRU5EX0VWRU5UX05BTUUsInBhZ2UiKX0sbi5fZXZlbnRUb1Byb2Nlc3NpbmdGdW5jdGlvblt0Ll9UT1VDSENBTkNFTF9FVkVOVF9OQU1FXT1mdW5jdGlvbihpKXtyZXR1cm4gbi5fbWVhc3VyZUFuZERpc3BhdGNoKHIsaSx0Ll9UT1VDSENBTkNFTF9FVkVOVF9OQU1FLCJwYWdlIil9LG59cmV0dXJuIHQuZ2V0RGlzcGF0Y2hlcj1mdW5jdGlvbihyKXt2YXIgbj1yLnJvb3QoKS5yb290RWxlbWVudCgpLGk9blt0Ll9ESVNQQVRDSEVSX0tFWV07cmV0dXJuIGk9PW51bGwmJihpPW5ldyB0KHIpLG5bdC5fRElTUEFUQ0hFUl9LRVldPWkpLGl9LHQucHJvdG90eXBlLm9uVG91Y2hTdGFydD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fYWRkQ2FsbGJhY2tGb3JFdmVudCh0Ll9UT1VDSFNUQVJUX0VWRU5UX05BTUUsciksdGhpc30sdC5wcm90b3R5cGUub2ZmVG91Y2hTdGFydD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcmVtb3ZlQ2FsbGJhY2tGb3JFdmVudCh0Ll9UT1VDSFNUQVJUX0VWRU5UX05BTUUsciksdGhpc30sdC5wcm90b3R5cGUub25Ub3VjaE1vdmU9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2FkZENhbGxiYWNrRm9yRXZlbnQodC5fVE9VQ0hNT1ZFX0VWRU5UX05BTUUsciksdGhpc30sdC5wcm90b3R5cGUub2ZmVG91Y2hNb3ZlPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9yZW1vdmVDYWxsYmFja0ZvckV2ZW50KHQuX1RPVUNITU9WRV9FVkVOVF9OQU1FLHIpLHRoaXN9LHQucHJvdG90eXBlLm9uVG91Y2hFbmQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2FkZENhbGxiYWNrRm9yRXZlbnQodC5fVE9VQ0hFTkRfRVZFTlRfTkFNRSxyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZUb3VjaEVuZD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcmVtb3ZlQ2FsbGJhY2tGb3JFdmVudCh0Ll9UT1VDSEVORF9FVkVOVF9OQU1FLHIpLHRoaXN9LHQucHJvdG90eXBlLm9uVG91Y2hDYW5jZWw9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2FkZENhbGxiYWNrRm9yRXZlbnQodC5fVE9VQ0hDQU5DRUxfRVZFTlRfTkFNRSxyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZUb3VjaENhbmNlbD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcmVtb3ZlQ2FsbGJhY2tGb3JFdmVudCh0Ll9UT1VDSENBTkNFTF9FVkVOVF9OQU1FLHIpLHRoaXN9LHQucHJvdG90eXBlLl9tZWFzdXJlQW5kRGlzcGF0Y2g9ZnVuY3Rpb24ocixuLGksbyl7aWYobz09PXZvaWQgMCYmKG89ImVsZW1lbnQiKSxvIT09InBhZ2UiJiZvIT09ImVsZW1lbnQiKXRocm93IG5ldyBFcnJvcigiSW52YWxpZCBzY29wZSAnIitvKyInLCBtdXN0IGJlICdlbGVtZW50JyBvciAncGFnZSciKTtpZighKG89PT0iZWxlbWVudCImJiF0aGlzLmV2ZW50SW5zaWRlKHIsbikpKXtmb3IodmFyIGE9bi5jaGFuZ2VkVG91Y2hlcyxzPXt9LGw9W10sYz0wO2M8YS5sZW5ndGg7YysrKXt2YXIgdT1hW2NdLGg9dS5pZGVudGlmaWVyLGY9dGhpcy5fdHJhbnNsYXRvci5jb21wdXRlUG9zaXRpb24odS5jbGllbnRYLHUuY2xpZW50WSk7ZiE9bnVsbCYmKHNbaF09ZixsLnB1c2goaCkpfWwubGVuZ3RoPjAmJnRoaXMuX2NhbGxDYWxsYmFja3NGb3JFdmVudChpLGwscyxuKX19LHQucHJvdG90eXBlLmV2ZW50SW5zaWRlPWZ1bmN0aW9uKHIsbil7cmV0dXJuIGgkdC5UcmFuc2xhdG9yLmlzRXZlbnRJbnNpZGUocixuKX0sdC5fRElTUEFUQ0hFUl9LRVk9Il9fUGxvdHRhYmxlX0Rpc3BhdGNoZXJfVG91Y2giLHQuX1RPVUNIU1RBUlRfRVZFTlRfTkFNRT0idG91Y2hzdGFydCIsdC5fVE9VQ0hNT1ZFX0VWRU5UX05BTUU9InRvdWNobW92ZSIsdC5fVE9VQ0hFTkRfRVZFTlRfTkFNRT0idG91Y2hlbmQiLHQuX1RPVUNIQ0FOQ0VMX0VWRU5UX05BTUU9InRvdWNoY2FuY2VsIix0fShPRmUuRGlzcGF0Y2hlcik7Z290LlRvdWNoPXpGZX0pO3ZhciBOMT1IKG80PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShvNCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIF9vdD0oZGUoKSxVdChwZSkpO19vdC5fX2V4cG9ydFN0YXIobCR0KCksbzQpO19vdC5fX2V4cG9ydFN0YXIodSR0KCksbzQpO19vdC5fX2V4cG9ydFN0YXIoZiR0KCksbzQpfSk7dmFyIEQxPUgoeW90PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eSh5b3QsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBGRmU9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKCl7dmFyIHQ9dGhpczt0aGlzLl9hbmNob3JDYWxsYmFjaz1mdW5jdGlvbihyKXtyZXR1cm4gdC5fYW5jaG9yKHIpfSx0aGlzLl9lbmFibGVkPSEwfXJldHVybiBlLnByb3RvdHlwZS5hdHRhY2hUbz1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5fZGlzY29ubmVjdCgpLHRoaXMuX2NvbXBvbmVudEF0dGFjaGVkVG89dCx0aGlzLl9jb25uZWN0KCksdGhpc30sZS5wcm90b3R5cGUuZGV0YWNoRnJvbT1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5kZXRhY2goKX0sZS5wcm90b3R5cGUuZGV0YWNoPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2Rpc2Nvbm5lY3QoKSx0aGlzLl9jb21wb25lbnRBdHRhY2hlZFRvPW51bGwsdGhpc30sZS5wcm90b3R5cGUuZW5hYmxlZD1mdW5jdGlvbih0KXtyZXR1cm4gdD09bnVsbD90aGlzLl9lbmFibGVkOih0aGlzLl9lbmFibGVkPXQsdGhpcy5fZW5hYmxlZD90aGlzLl9jb25uZWN0KCk6dGhpcy5fZGlzY29ubmVjdCgpLHRoaXMpfSxlLnByb3RvdHlwZS5fYW5jaG9yPWZ1bmN0aW9uKHQpe3RoaXMuX2lzQW5jaG9yZWQ9ITB9LGUucHJvdG90eXBlLl91bmFuY2hvcj1mdW5jdGlvbigpe3RoaXMuX2lzQW5jaG9yZWQ9ITF9LGUucHJvdG90eXBlLl90cmFuc2xhdGVUb0NvbXBvbmVudFNwYWNlPWZ1bmN0aW9uKHQpe3ZhciByPXRoaXMuX2NvbXBvbmVudEF0dGFjaGVkVG8ub3JpZ2luVG9Sb290KCk7cmV0dXJue3g6dC54LXIueCx5OnQueS1yLnl9fSxlLnByb3RvdHlwZS5faXNJbnNpZGVDb21wb25lbnQ9ZnVuY3Rpb24odCl7cmV0dXJuIDA8PXQueCYmMDw9dC55JiZ0Lng8PXRoaXMuX2NvbXBvbmVudEF0dGFjaGVkVG8ud2lkdGgoKSYmdC55PD10aGlzLl9jb21wb25lbnRBdHRhY2hlZFRvLmhlaWdodCgpfSxlLnByb3RvdHlwZS5fY29ubmVjdD1mdW5jdGlvbigpe3RoaXMuZW5hYmxlZCgpJiZ0aGlzLl9jb21wb25lbnRBdHRhY2hlZFRvIT1udWxsJiYhdGhpcy5faXNBbmNob3JlZCYmdGhpcy5fY29tcG9uZW50QXR0YWNoZWRUby5vbkFuY2hvcih0aGlzLl9hbmNob3JDYWxsYmFjayl9LGUucHJvdG90eXBlLl9kaXNjb25uZWN0PWZ1bmN0aW9uKCl7dGhpcy5faXNBbmNob3JlZCYmdGhpcy5fdW5hbmNob3IoKSx0aGlzLl9jb21wb25lbnRBdHRhY2hlZFRvIT1udWxsJiZ0aGlzLl9jb21wb25lbnRBdHRhY2hlZFRvLm9mZkFuY2hvcih0aGlzLl9hbmNob3JDYWxsYmFjayl9LGV9KCk7eW90LkludGVyYWN0aW9uPUZGZX0pO3ZhciBtJHQ9SCh2b3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHZvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIEJGZT0oZGUoKSxVdChwZSkpLHAkdD1OMSgpLGQkdD1GZSgpLEhGZT1EMSgpLFZGZT1mdW5jdGlvbihlKXtCRmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3ZhciByPWUhPT1udWxsJiZlLmFwcGx5KHRoaXMsYXJndW1lbnRzKXx8dGhpcztyZXR1cm4gci5fY2xpY2tlZERvd249ITEsci5fZG91YmxlQ2xpY2tpbmc9ITEsci5fb25DbGlja0NhbGxiYWNrcz1uZXcgZCR0LkNhbGxiYWNrU2V0LHIuX29uRG91YmxlQ2xpY2tDYWxsYmFja3M9bmV3IGQkdC5DYWxsYmFja1NldCxyLl9tb3VzZURvd25DYWxsYmFjaz1mdW5jdGlvbihuLGkpe3JldHVybiByLl9oYW5kbGVDbGlja0Rvd24obixpKX0sci5fbW91c2VVcENhbGxiYWNrPWZ1bmN0aW9uKG4saSl7cmV0dXJuIHIuX2hhbmRsZUNsaWNrVXAobixpKX0sci5fZGJsQ2xpY2tDYWxsYmFjaz1mdW5jdGlvbihuLGkpe3JldHVybiByLl9oYW5kbGVEYmxDbGljayhuLGkpfSxyLl90b3VjaFN0YXJ0Q2FsbGJhY2s9ZnVuY3Rpb24obixpLG8pe3JldHVybiByLl9oYW5kbGVDbGlja0Rvd24oaVtuWzBdXSxvKX0sci5fdG91Y2hFbmRDYWxsYmFjaz1mdW5jdGlvbihuLGksbyl7cmV0dXJuIHIuX2hhbmRsZUNsaWNrVXAoaVtuWzBdXSxvKX0sci5fdG91Y2hDYW5jZWxDYWxsYmFjaz1mdW5jdGlvbihuLGkpe3JldHVybiByLl9jbGlja2VkRG93bj0hMX0scn1yZXR1cm4gdC5wcm90b3R5cGUuX2FuY2hvcj1mdW5jdGlvbihyKXtlLnByb3RvdHlwZS5fYW5jaG9yLmNhbGwodGhpcyxyKSx0aGlzLl9tb3VzZURpc3BhdGNoZXI9cCR0Lk1vdXNlLmdldERpc3BhdGNoZXIociksdGhpcy5fbW91c2VEaXNwYXRjaGVyLm9uTW91c2VEb3duKHRoaXMuX21vdXNlRG93bkNhbGxiYWNrKSx0aGlzLl9tb3VzZURpc3BhdGNoZXIub25Nb3VzZVVwKHRoaXMuX21vdXNlVXBDYWxsYmFjayksdGhpcy5fbW91c2VEaXNwYXRjaGVyLm9uRGJsQ2xpY2sodGhpcy5fZGJsQ2xpY2tDYWxsYmFjayksdGhpcy5fdG91Y2hEaXNwYXRjaGVyPXAkdC5Ub3VjaC5nZXREaXNwYXRjaGVyKHIpLHRoaXMuX3RvdWNoRGlzcGF0Y2hlci5vblRvdWNoU3RhcnQodGhpcy5fdG91Y2hTdGFydENhbGxiYWNrKSx0aGlzLl90b3VjaERpc3BhdGNoZXIub25Ub3VjaEVuZCh0aGlzLl90b3VjaEVuZENhbGxiYWNrKSx0aGlzLl90b3VjaERpc3BhdGNoZXIub25Ub3VjaENhbmNlbCh0aGlzLl90b3VjaENhbmNlbENhbGxiYWNrKX0sdC5wcm90b3R5cGUuX3VuYW5jaG9yPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX3VuYW5jaG9yLmNhbGwodGhpcyksdGhpcy5fbW91c2VEaXNwYXRjaGVyLm9mZk1vdXNlRG93bih0aGlzLl9tb3VzZURvd25DYWxsYmFjayksdGhpcy5fbW91c2VEaXNwYXRjaGVyLm9mZk1vdXNlVXAodGhpcy5fbW91c2VVcENhbGxiYWNrKSx0aGlzLl9tb3VzZURpc3BhdGNoZXIub2ZmRGJsQ2xpY2sodGhpcy5fZGJsQ2xpY2tDYWxsYmFjayksdGhpcy5fbW91c2VEaXNwYXRjaGVyPW51bGwsdGhpcy5fdG91Y2hEaXNwYXRjaGVyLm9mZlRvdWNoU3RhcnQodGhpcy5fdG91Y2hTdGFydENhbGxiYWNrKSx0aGlzLl90b3VjaERpc3BhdGNoZXIub2ZmVG91Y2hFbmQodGhpcy5fdG91Y2hFbmRDYWxsYmFjayksdGhpcy5fdG91Y2hEaXNwYXRjaGVyLm9mZlRvdWNoQ2FuY2VsKHRoaXMuX3RvdWNoQ2FuY2VsQ2FsbGJhY2spLHRoaXMuX3RvdWNoRGlzcGF0Y2hlcj1udWxsfSx0LnByb3RvdHlwZS5faGFuZGxlQ2xpY2tEb3duPWZ1bmN0aW9uKHIsbil7dmFyIGk9dGhpcy5fdHJhbnNsYXRlVG9Db21wb25lbnRTcGFjZShyKTt0aGlzLl9pc0luc2lkZUNvbXBvbmVudChpKSYmKHRoaXMuX2NsaWNrZWREb3duPSEwLHRoaXMuX2NsaWNrZWRQb2ludD1pKX0sdC5wcm90b3R5cGUuX2hhbmRsZUNsaWNrVXA9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzLG89dGhpcy5fdHJhbnNsYXRlVG9Db21wb25lbnRTcGFjZShyKTt0aGlzLl9jbGlja2VkRG93biYmdC5fcG9pbnRzRXF1YWwobyx0aGlzLl9jbGlja2VkUG9pbnQpJiZzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7aS5fZG91YmxlQ2xpY2tpbmd8fGkuX29uQ2xpY2tDYWxsYmFja3MuY2FsbENhbGxiYWNrcyhvLG4pfSwwKSx0aGlzLl9jbGlja2VkRG93bj0hMX0sdC5wcm90b3R5cGUuX2hhbmRsZURibENsaWNrPWZ1bmN0aW9uKHIsbil7dmFyIGk9dGhpcyxvPXRoaXMuX3RyYW5zbGF0ZVRvQ29tcG9uZW50U3BhY2Uocik7dGhpcy5fZG91YmxlQ2xpY2tpbmc9ITAsdGhpcy5fb25Eb3VibGVDbGlja0NhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKG8sbiksc2V0VGltZW91dChmdW5jdGlvbigpe3JldHVybiBpLl9kb3VibGVDbGlja2luZz0hMX0sMCl9LHQuX3BvaW50c0VxdWFsPWZ1bmN0aW9uKHIsbil7cmV0dXJuIHIueD09PW4ueCYmci55PT09bi55fSx0LnByb3RvdHlwZS5vbkNsaWNrPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9vbkNsaWNrQ2FsbGJhY2tzLmFkZChyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZDbGljaz1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fb25DbGlja0NhbGxiYWNrcy5kZWxldGUociksdGhpc30sdC5wcm90b3R5cGUub25Eb3VibGVDbGljaz1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fb25Eb3VibGVDbGlja0NhbGxiYWNrcy5hZGQociksdGhpc30sdC5wcm90b3R5cGUub2ZmRG91YmxlQ2xpY2s9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX29uRG91YmxlQ2xpY2tDYWxsYmFja3MuZGVsZXRlKHIpLHRoaXN9LHR9KEhGZS5JbnRlcmFjdGlvbik7dm90LkNsaWNrPVZGZX0pO3ZhciBfJHQ9SCh4b3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHhvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFVGZT0oZGUoKSxVdChwZSkpLGckdD1OMSgpLGE0PUZlKCkscUZlPUQxKCksR0ZlPWZ1bmN0aW9uKGUpe1VGZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3ZhciBuPWUuY2FsbCh0aGlzKXx8dGhpcztyZXR1cm4gbi5fZHJhZ2dpbmc9ITEsbi5fY29uc3RyYWluZWRUb0NvbXBvbmVudD0hMCxuLl9tb3VzZUZpbHRlcj10Ll9ERUZBVUxUX01PVVNFX0ZJTFRFUixuLl9kcmFnU3RhcnRDYWxsYmFja3M9bmV3IGE0LkNhbGxiYWNrU2V0LG4uX2RyYWdDYWxsYmFja3M9bmV3IGE0LkNhbGxiYWNrU2V0LG4uX2RyYWdFbmRDYWxsYmFja3M9bmV3IGE0LkNhbGxiYWNrU2V0LG4uX21vdXNlRG93bkNhbGxiYWNrPWZ1bmN0aW9uKGksbyl7cmV0dXJuIG4uX3N0YXJ0RHJhZyhpLG8pfSxuLl9tb3VzZU1vdmVDYWxsYmFjaz1mdW5jdGlvbihpLG8pe3JldHVybiBuLl9kb0RyYWcoaSxvKX0sbi5fbW91c2VVcENhbGxiYWNrPWZ1bmN0aW9uKGksbyl7cmV0dXJuIG4uX2VuZERyYWcoaSxvKX0sbi5fdG91Y2hTdGFydENhbGxiYWNrPWZ1bmN0aW9uKGksbyxhKXtyZXR1cm4gbi5fc3RhcnREcmFnKG9baVswXV0sYSl9LG4uX3RvdWNoTW92ZUNhbGxiYWNrPWZ1bmN0aW9uKGksbyxhKXtyZXR1cm4gbi5fZG9EcmFnKG9baVswXV0sYSl9LG4uX3RvdWNoRW5kQ2FsbGJhY2s9ZnVuY3Rpb24oaSxvLGEpe3JldHVybiBuLl9lbmREcmFnKG9baVswXV0sYSl9LG4uX21vdXNlQnV0dG9uPXIhPT12b2lkIDA/cjowLG59cmV0dXJuIHQucHJvdG90eXBlLl9hbmNob3I9ZnVuY3Rpb24ocil7ZS5wcm90b3R5cGUuX2FuY2hvci5jYWxsKHRoaXMsciksdGhpcy5fbW91c2VEaXNwYXRjaGVyPWckdC5Nb3VzZS5nZXREaXNwYXRjaGVyKHRoaXMuX2NvbXBvbmVudEF0dGFjaGVkVG8pLHRoaXMuX21vdXNlRGlzcGF0Y2hlci5vbk1vdXNlRG93bih0aGlzLl9tb3VzZURvd25DYWxsYmFjayksdGhpcy5fbW91c2VEaXNwYXRjaGVyLm9uTW91c2VNb3ZlKHRoaXMuX21vdXNlTW92ZUNhbGxiYWNrKSx0aGlzLl9tb3VzZURpc3BhdGNoZXIub25Nb3VzZVVwKHRoaXMuX21vdXNlVXBDYWxsYmFjayksdGhpcy5fdG91Y2hEaXNwYXRjaGVyPWckdC5Ub3VjaC5nZXREaXNwYXRjaGVyKHRoaXMuX2NvbXBvbmVudEF0dGFjaGVkVG8pLHRoaXMuX3RvdWNoRGlzcGF0Y2hlci5vblRvdWNoU3RhcnQodGhpcy5fdG91Y2hTdGFydENhbGxiYWNrKSx0aGlzLl90b3VjaERpc3BhdGNoZXIub25Ub3VjaE1vdmUodGhpcy5fdG91Y2hNb3ZlQ2FsbGJhY2spLHRoaXMuX3RvdWNoRGlzcGF0Y2hlci5vblRvdWNoRW5kKHRoaXMuX3RvdWNoRW5kQ2FsbGJhY2spfSx0LnByb3RvdHlwZS5fdW5hbmNob3I9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5fdW5hbmNob3IuY2FsbCh0aGlzKSx0aGlzLl9tb3VzZURpc3BhdGNoZXIub2ZmTW91c2VEb3duKHRoaXMuX21vdXNlRG93bkNhbGxiYWNrKSx0aGlzLl9tb3VzZURpc3BhdGNoZXIub2ZmTW91c2VNb3ZlKHRoaXMuX21vdXNlTW92ZUNhbGxiYWNrKSx0aGlzLl9tb3VzZURpc3BhdGNoZXIub2ZmTW91c2VVcCh0aGlzLl9tb3VzZVVwQ2FsbGJhY2spLHRoaXMuX21vdXNlRGlzcGF0Y2hlcj1udWxsLHRoaXMuX3RvdWNoRGlzcGF0Y2hlci5vZmZUb3VjaFN0YXJ0KHRoaXMuX3RvdWNoU3RhcnRDYWxsYmFjayksdGhpcy5fdG91Y2hEaXNwYXRjaGVyLm9mZlRvdWNoTW92ZSh0aGlzLl90b3VjaE1vdmVDYWxsYmFjayksdGhpcy5fdG91Y2hEaXNwYXRjaGVyLm9mZlRvdWNoRW5kKHRoaXMuX3RvdWNoRW5kQ2FsbGJhY2spLHRoaXMuX3RvdWNoRGlzcGF0Y2hlcj1udWxsfSx0LnByb3RvdHlwZS5fdHJhbnNsYXRlQW5kQ29uc3RyYWluPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMuX3RyYW5zbGF0ZVRvQ29tcG9uZW50U3BhY2Uocik7cmV0dXJuIHRoaXMuX2NvbnN0cmFpbmVkVG9Db21wb25lbnQ/e3g6YTQuTWF0aC5jbGFtcChuLngsMCx0aGlzLl9jb21wb25lbnRBdHRhY2hlZFRvLndpZHRoKCkpLHk6YTQuTWF0aC5jbGFtcChuLnksMCx0aGlzLl9jb21wb25lbnRBdHRhY2hlZFRvLmhlaWdodCgpKX06bn0sdC5wcm90b3R5cGUuX3N0YXJ0RHJhZz1mdW5jdGlvbihyLG4pe2lmKCEobiBpbnN0YW5jZW9mIE1vdXNlRXZlbnQmJiF0aGlzLl9tb3VzZUZpbHRlcihuKSkpe3ZhciBpPXRoaXMuX3RyYW5zbGF0ZVRvQ29tcG9uZW50U3BhY2Uocik7dGhpcy5faXNJbnNpZGVDb21wb25lbnQoaSkmJihuLnByZXZlbnREZWZhdWx0KCksdGhpcy5fZHJhZ2dpbmc9ITAsdGhpcy5fZHJhZ09yaWdpbj1pLHRoaXMuX2RyYWdTdGFydENhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKHRoaXMuX2RyYWdPcmlnaW4pKX19LHQucHJvdG90eXBlLl9kb0RyYWc9ZnVuY3Rpb24ocixuKXt0aGlzLl9kcmFnZ2luZyYmdGhpcy5fZHJhZ0NhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKHRoaXMuX2RyYWdPcmlnaW4sdGhpcy5fdHJhbnNsYXRlQW5kQ29uc3RyYWluKHIpKX0sdC5wcm90b3R5cGUuX2VuZERyYWc9ZnVuY3Rpb24ocixuKXtuIGluc3RhbmNlb2YgTW91c2VFdmVudCYmbi5idXR0b24hPT10aGlzLl9tb3VzZUJ1dHRvbnx8dGhpcy5fZHJhZ2dpbmcmJih0aGlzLl9kcmFnZ2luZz0hMSx0aGlzLl9kcmFnRW5kQ2FsbGJhY2tzLmNhbGxDYWxsYmFja3ModGhpcy5fZHJhZ09yaWdpbix0aGlzLl90cmFuc2xhdGVBbmRDb25zdHJhaW4ocikpKX0sdC5wcm90b3R5cGUuY29uc3RyYWluZWRUb0NvbXBvbmVudD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9jb25zdHJhaW5lZFRvQ29tcG9uZW50Oih0aGlzLl9jb25zdHJhaW5lZFRvQ29tcG9uZW50PXIsdGhpcyl9LHQucHJvdG90eXBlLm1vdXNlRmlsdGVyPWZ1bmN0aW9uKHIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MD90aGlzLl9tb3VzZUZpbHRlcjoodGhpcy5fbW91c2VGaWx0ZXI9cix0aGlzKX0sdC5wcm90b3R5cGUub25EcmFnU3RhcnQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2RyYWdTdGFydENhbGxiYWNrcy5hZGQociksdGhpc30sdC5wcm90b3R5cGUub2ZmRHJhZ1N0YXJ0PWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kcmFnU3RhcnRDYWxsYmFja3MuZGVsZXRlKHIpLHRoaXN9LHQucHJvdG90eXBlLm9uRHJhZz1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fZHJhZ0NhbGxiYWNrcy5hZGQociksdGhpc30sdC5wcm90b3R5cGUub2ZmRHJhZz1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fZHJhZ0NhbGxiYWNrcy5kZWxldGUociksdGhpc30sdC5wcm90b3R5cGUub25EcmFnRW5kPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kcmFnRW5kQ2FsbGJhY2tzLmFkZChyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZEcmFnRW5kPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kcmFnRW5kQ2FsbGJhY2tzLmRlbGV0ZShyKSx0aGlzfSx0Ll9ERUZBVUxUX01PVVNFX0ZJTFRFUj1mdW5jdGlvbihyKXtyZXR1cm4gci5idXR0b249PT0wfSx0fShxRmUuSW50ZXJhY3Rpb24pO3hvdC5EcmFnPUdGZX0pO3ZhciBTb3Q9SCh3b3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHdvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFdGZT0oZGUoKSxVdChwZSkpLHkkdD1OMSgpLGJvdD1GZSgpLFlGZT1EMSgpLGpGZT1mdW5jdGlvbihlKXtXRmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3ZhciByPWUhPT1udWxsJiZlLmFwcGx5KHRoaXMsYXJndW1lbnRzKXx8dGhpcztyZXR1cm4gci5fa2V5UHJlc3NDYWxsYmFja3M9e30sci5fa2V5UmVsZWFzZUNhbGxiYWNrcz17fSxyLl9tb3VzZU1vdmVDYWxsYmFjaz1mdW5jdGlvbihuKXtyZXR1cm4hMX0sci5fZG93bmVkS2V5cz1uZXcgYm90LlNldCxyLl9rZXlEb3duQ2FsbGJhY2s9ZnVuY3Rpb24obixpKXtyZXR1cm4gci5faGFuZGxlS2V5RG93bkV2ZW50KG4saSl9LHIuX2tleVVwQ2FsbGJhY2s9ZnVuY3Rpb24obil7cmV0dXJuIHIuX2hhbmRsZUtleVVwRXZlbnQobil9LHJ9cmV0dXJuIHQucHJvdG90eXBlLl9hbmNob3I9ZnVuY3Rpb24ocil7ZS5wcm90b3R5cGUuX2FuY2hvci5jYWxsKHRoaXMsciksdGhpcy5fcG9zaXRpb25EaXNwYXRjaGVyPXkkdC5Nb3VzZS5nZXREaXNwYXRjaGVyKHRoaXMuX2NvbXBvbmVudEF0dGFjaGVkVG8pLHRoaXMuX3Bvc2l0aW9uRGlzcGF0Y2hlci5vbk1vdXNlTW92ZSh0aGlzLl9tb3VzZU1vdmVDYWxsYmFjayksdGhpcy5fa2V5RGlzcGF0Y2hlcj15JHQuS2V5LmdldERpc3BhdGNoZXIoKSx0aGlzLl9rZXlEaXNwYXRjaGVyLm9uS2V5RG93bih0aGlzLl9rZXlEb3duQ2FsbGJhY2spLHRoaXMuX2tleURpc3BhdGNoZXIub25LZXlVcCh0aGlzLl9rZXlVcENhbGxiYWNrKX0sdC5wcm90b3R5cGUuX3VuYW5jaG9yPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX3VuYW5jaG9yLmNhbGwodGhpcyksdGhpcy5fcG9zaXRpb25EaXNwYXRjaGVyLm9mZk1vdXNlTW92ZSh0aGlzLl9tb3VzZU1vdmVDYWxsYmFjayksdGhpcy5fcG9zaXRpb25EaXNwYXRjaGVyPW51bGwsdGhpcy5fa2V5RGlzcGF0Y2hlci5vZmZLZXlEb3duKHRoaXMuX2tleURvd25DYWxsYmFjayksdGhpcy5fa2V5RGlzcGF0Y2hlci5vZmZLZXlVcCh0aGlzLl9rZXlVcENhbGxiYWNrKSx0aGlzLl9rZXlEaXNwYXRjaGVyPW51bGx9LHQucHJvdG90eXBlLl9oYW5kbGVLZXlEb3duRXZlbnQ9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzLl90cmFuc2xhdGVUb0NvbXBvbmVudFNwYWNlKHRoaXMuX3Bvc2l0aW9uRGlzcGF0Y2hlci5sYXN0TW91c2VQb3NpdGlvbigpKTt0aGlzLl9pc0luc2lkZUNvbXBvbmVudChpKSYmIW4ucmVwZWF0JiYodGhpcy5fa2V5UHJlc3NDYWxsYmFja3Nbcl0mJnRoaXMuX2tleVByZXNzQ2FsbGJhY2tzW3JdLmNhbGxDYWxsYmFja3MociksdGhpcy5fZG93bmVkS2V5cy5hZGQocikpfSx0LnByb3RvdHlwZS5faGFuZGxlS2V5VXBFdmVudD1mdW5jdGlvbihyKXt0aGlzLl9kb3duZWRLZXlzLmhhcyhyKSYmdGhpcy5fa2V5UmVsZWFzZUNhbGxiYWNrc1tyXSYmdGhpcy5fa2V5UmVsZWFzZUNhbGxiYWNrc1tyXS5jYWxsQ2FsbGJhY2tzKHIpLHRoaXMuX2Rvd25lZEtleXMuZGVsZXRlKHIpfSx0LnByb3RvdHlwZS5vbktleVByZXNzPWZ1bmN0aW9uKHIsbil7cmV0dXJuIHRoaXMuX2tleVByZXNzQ2FsbGJhY2tzW3JdfHwodGhpcy5fa2V5UHJlc3NDYWxsYmFja3Nbcl09bmV3IGJvdC5DYWxsYmFja1NldCksdGhpcy5fa2V5UHJlc3NDYWxsYmFja3Nbcl0uYWRkKG4pLHRoaXN9LHQucHJvdG90eXBlLm9mZktleVByZXNzPWZ1bmN0aW9uKHIsbil7cmV0dXJuIHRoaXMuX2tleVByZXNzQ2FsbGJhY2tzW3JdLmRlbGV0ZShuKSx0aGlzLl9rZXlQcmVzc0NhbGxiYWNrc1tyXS5zaXplPT09MCYmZGVsZXRlIHRoaXMuX2tleVByZXNzQ2FsbGJhY2tzW3JdLHRoaXN9LHQucHJvdG90eXBlLm9uS2V5UmVsZWFzZT1mdW5jdGlvbihyLG4pe3JldHVybiB0aGlzLl9rZXlSZWxlYXNlQ2FsbGJhY2tzW3JdfHwodGhpcy5fa2V5UmVsZWFzZUNhbGxiYWNrc1tyXT1uZXcgYm90LkNhbGxiYWNrU2V0KSx0aGlzLl9rZXlSZWxlYXNlQ2FsbGJhY2tzW3JdLmFkZChuKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZLZXlSZWxlYXNlPWZ1bmN0aW9uKHIsbil7cmV0dXJuIHRoaXMuX2tleVJlbGVhc2VDYWxsYmFja3Nbcl0uZGVsZXRlKG4pLHRoaXMuX2tleVJlbGVhc2VDYWxsYmFja3Nbcl0uc2l6ZT09PTAmJmRlbGV0ZSB0aGlzLl9rZXlSZWxlYXNlQ2FsbGJhY2tzW3JdLHRoaXN9LHR9KFlGZS5JbnRlcmFjdGlvbik7d290LktleT1qRmV9KTt2YXIgdyR0PUgoTW90PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShNb3QsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBYRmU9KGRlKCksVXQocGUpKSwkRmU9KEVyKCksVXQoTXIpKSx2JHQ9TjEoKSx4JHQ9a3MoKSx5bD1GZSgpLEtGZT1zNCgpLFpGZT1EMSgpLGIkdD10NCgpLEpGZT1mdW5jdGlvbihlKXtYRmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdChyLG4pe3ZhciBpPWUuY2FsbCh0aGlzKXx8dGhpcztyZXR1cm4gaS5fd2hlZWxGaWx0ZXI9ZnVuY3Rpb24obyl7cmV0dXJuITB9LGkuX3doZWVsQ2FsbGJhY2s9ZnVuY3Rpb24obyxhKXtyZXR1cm4gaS5faGFuZGxlV2hlZWxFdmVudChvLGEpfSxpLl90b3VjaFN0YXJ0Q2FsbGJhY2s9ZnVuY3Rpb24obyxhLHMpe3JldHVybiBpLl9oYW5kbGVUb3VjaFN0YXJ0KG8sYSxzKX0saS5fdG91Y2hNb3ZlQ2FsbGJhY2s9ZnVuY3Rpb24obyxhLHMpe3JldHVybiBpLl9oYW5kbGVQaW5jaChvLGEscyl9LGkuX3RvdWNoRW5kQ2FsbGJhY2s9ZnVuY3Rpb24obyxhLHMpe3JldHVybiBpLl9oYW5kbGVUb3VjaEVuZChvLGEscyl9LGkuX3RvdWNoQ2FuY2VsQ2FsbGJhY2s9ZnVuY3Rpb24obyxhLHMpe3JldHVybiBpLl9oYW5kbGVUb3VjaEVuZChvLGEscyl9LGkuX3BhbkVuZENhbGxiYWNrcz1uZXcgeWwuQ2FsbGJhY2tTZXQsaS5fem9vbUVuZENhbGxiYWNrcz1uZXcgeWwuQ2FsbGJhY2tTZXQsaS5fcGFuWm9vbVVwZGF0ZUNhbGxiYWNrcz1uZXcgeWwuQ2FsbGJhY2tTZXQsaS5feFNjYWxlcz1uZXcgeWwuU2V0LGkuX3lTY2FsZXM9bmV3IHlsLlNldCxpLl9kcmFnSW50ZXJhY3Rpb249bmV3IEtGZS5EcmFnLGkuX3NldHVwRHJhZ0ludGVyYWN0aW9uKCksaS5fdG91Y2hJZHM9JEZlLm1hcCgpLGkuX21pbkRvbWFpbkV4dGVudHM9bmV3IHlsLk1hcCxpLl9tYXhEb21haW5FeHRlbnRzPW5ldyB5bC5NYXAsaS5fbWluRG9tYWluVmFsdWVzPW5ldyB5bC5NYXAsaS5fbWF4RG9tYWluVmFsdWVzPW5ldyB5bC5NYXAsciE9bnVsbCYmaS5hZGRYU2NhbGUociksbiE9bnVsbCYmaS5hZGRZU2NhbGUobiksaX1yZXR1cm4gdC5wcm90b3R5cGUuZHJhZ0ludGVyYWN0aW9uPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2RyYWdJbnRlcmFjdGlvbn0sdC5wcm90b3R5cGUud2hlZWxGaWx0ZXI9ZnVuY3Rpb24ocil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0wP3RoaXMuX3doZWVsRmlsdGVyOih0aGlzLl93aGVlbEZpbHRlcj1yLHRoaXMpfSx0LnByb3RvdHlwZS5wYW49ZnVuY3Rpb24ocil7dmFyIG49dGhpczt0aGlzLnhTY2FsZXMoKS5mb3JFYWNoKGZ1bmN0aW9uKGkpe2kucGFuKG4uX2NvbnN0cmFpbmVkVHJhbnNsYXRpb24oaSxyLngpKX0pLHRoaXMueVNjYWxlcygpLmZvckVhY2goZnVuY3Rpb24oaSl7aS5wYW4obi5fY29uc3RyYWluZWRUcmFuc2xhdGlvbihpLHIueSkpfSksdGhpcy5fcGFuWm9vbVVwZGF0ZUNhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKCl9LHQucHJvdG90eXBlLnpvb209ZnVuY3Rpb24ocixuLGkpe3ZhciBvPXRoaXM7aT09PXZvaWQgMCYmKGk9ITApO3ZhciBhLHM7cmV0dXJuIG4hPW51bGwmJihhPW4ueCxzPW4ueSxpJiYodGhpcy54U2NhbGVzKCkuZm9yRWFjaChmdW5jdGlvbihsKXt2YXIgYz1vLl9jb25zdHJhaW5lZFpvb20obCxyLGEpO2E9Yy5jZW50ZXJQb2ludCxyPWMuem9vbUFtb3VudH0pLHRoaXMueVNjYWxlcygpLmZvckVhY2goZnVuY3Rpb24obCl7dmFyIGM9by5fY29uc3RyYWluZWRab29tKGwscixzKTtzPWMuY2VudGVyUG9pbnQscj1jLnpvb21BbW91bnR9KSkpLHRoaXMueFNjYWxlcygpLmZvckVhY2goZnVuY3Rpb24obCl7dmFyIGM9bC5yYW5nZSgpLHU9YT09bnVsbD8oY1sxXStjWzBdKS8yOmE7bC56b29tKHIsdSl9KSx0aGlzLnlTY2FsZXMoKS5mb3JFYWNoKGZ1bmN0aW9uKGwpe3ZhciBjPWwucmFuZ2UoKSx1PXM9PW51bGw/KGNbMV0rY1swXSkvMjpzO2wuem9vbShyLHUpfSksdGhpcy5fcGFuWm9vbVVwZGF0ZUNhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKCkse3pvb21BbW91bnQ6cixjZW50ZXJWYWx1ZTp7Y2VudGVyWDphLGNlbnRlclk6c319fSx0LnByb3RvdHlwZS5fYW5jaG9yPWZ1bmN0aW9uKHIpe2UucHJvdG90eXBlLl9hbmNob3IuY2FsbCh0aGlzLHIpLHRoaXMuX2RyYWdJbnRlcmFjdGlvbi5hdHRhY2hUbyhyKSx0aGlzLl9tb3VzZURpc3BhdGNoZXI9diR0Lk1vdXNlLmdldERpc3BhdGNoZXIodGhpcy5fY29tcG9uZW50QXR0YWNoZWRUbyksdGhpcy5fbW91c2VEaXNwYXRjaGVyLm9uV2hlZWwodGhpcy5fd2hlZWxDYWxsYmFjayksdGhpcy5fdG91Y2hEaXNwYXRjaGVyPXYkdC5Ub3VjaC5nZXREaXNwYXRjaGVyKHRoaXMuX2NvbXBvbmVudEF0dGFjaGVkVG8pLHRoaXMuX3RvdWNoRGlzcGF0Y2hlci5vblRvdWNoU3RhcnQodGhpcy5fdG91Y2hTdGFydENhbGxiYWNrKSx0aGlzLl90b3VjaERpc3BhdGNoZXIub25Ub3VjaE1vdmUodGhpcy5fdG91Y2hNb3ZlQ2FsbGJhY2spLHRoaXMuX3RvdWNoRGlzcGF0Y2hlci5vblRvdWNoRW5kKHRoaXMuX3RvdWNoRW5kQ2FsbGJhY2spLHRoaXMuX3RvdWNoRGlzcGF0Y2hlci5vblRvdWNoQ2FuY2VsKHRoaXMuX3RvdWNoQ2FuY2VsQ2FsbGJhY2spfSx0LnByb3RvdHlwZS5fdW5hbmNob3I9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5fdW5hbmNob3IuY2FsbCh0aGlzKSx0aGlzLl9tb3VzZURpc3BhdGNoZXIub2ZmV2hlZWwodGhpcy5fd2hlZWxDYWxsYmFjayksdGhpcy5fbW91c2VEaXNwYXRjaGVyPW51bGwsdGhpcy5fdG91Y2hEaXNwYXRjaGVyLm9mZlRvdWNoU3RhcnQodGhpcy5fdG91Y2hTdGFydENhbGxiYWNrKSx0aGlzLl90b3VjaERpc3BhdGNoZXIub2ZmVG91Y2hNb3ZlKHRoaXMuX3RvdWNoTW92ZUNhbGxiYWNrKSx0aGlzLl90b3VjaERpc3BhdGNoZXIub2ZmVG91Y2hFbmQodGhpcy5fdG91Y2hFbmRDYWxsYmFjayksdGhpcy5fdG91Y2hEaXNwYXRjaGVyLm9mZlRvdWNoQ2FuY2VsKHRoaXMuX3RvdWNoQ2FuY2VsQ2FsbGJhY2spLHRoaXMuX3RvdWNoRGlzcGF0Y2hlcj1udWxsLHRoaXMuX2RyYWdJbnRlcmFjdGlvbi5kZXRhY2goKX0sdC5wcm90b3R5cGUuX2hhbmRsZVRvdWNoU3RhcnQ9ZnVuY3Rpb24ocixuLGkpe2Zvcih2YXIgbz0wO288ci5sZW5ndGgmJnRoaXMuX3RvdWNoSWRzLnNpemUoKTwyO28rKyl7dmFyIGE9cltvXTt0aGlzLl90b3VjaElkcy5zZXQoYS50b1N0cmluZygpLHRoaXMuX3RyYW5zbGF0ZVRvQ29tcG9uZW50U3BhY2UoblthXSkpfX0sdC5wcm90b3R5cGUuX2hhbmRsZVBpbmNoPWZ1bmN0aW9uKHIsbixpKXt2YXIgbz10aGlzO2lmKCEodGhpcy5fdG91Y2hJZHMuc2l6ZSgpPDIpKXt2YXIgYT10aGlzLl90b3VjaElkcy52YWx1ZXMoKTtpZighKCF0aGlzLl9pc0luc2lkZUNvbXBvbmVudCh0aGlzLl90cmFuc2xhdGVUb0NvbXBvbmVudFNwYWNlKGFbMF0pKXx8IXRoaXMuX2lzSW5zaWRlQ29tcG9uZW50KHRoaXMuX3RyYW5zbGF0ZVRvQ29tcG9uZW50U3BhY2UoYVsxXSkpKSl7dmFyIHM9dC5fcG9pbnREaXN0YW5jZShhWzBdLGFbMV0pO2lmKHMhPT0wKXtyLmZvckVhY2goZnVuY3Rpb24oUyl7by5fdG91Y2hJZHMuaGFzKFMudG9TdHJpbmcoKSkmJm8uX3RvdWNoSWRzLnNldChTLnRvU3RyaW5nKCksby5fdHJhbnNsYXRlVG9Db21wb25lbnRTcGFjZShuW1NdKSl9KTt2YXIgbD10aGlzLl90b3VjaElkcy52YWx1ZXMoKSxjPXQuX3BvaW50RGlzdGFuY2UobFswXSxsWzFdKTtpZihjIT09MCl7dmFyIHU9cy9jLGg9bC5tYXAoZnVuY3Rpb24oUyxDKXtyZXR1cm57eDooUy54LWFbQ10ueCkvdSx5OihTLnktYVtDXS55KS91fX0pLGY9dC5jZW50ZXJQb2ludChhWzBdLGFbMV0pLHA9dGhpcy56b29tKHUsZiksZD1wLmNlbnRlclZhbHVlLGc9cC56b29tQW1vdW50LF89ZC5jZW50ZXJYLHk9ZC5jZW50ZXJZLHg9YS5tYXAoZnVuY3Rpb24oUyxDKXtyZXR1cm57eDpoW0NdLngqZytTLngseTpoW0NdLnkqZytTLnl9fSksYj17eDpfLSh4WzBdLngreFsxXS54KS8yLHk6eS0oeFswXS55K3hbMV0ueSkvMn07dGhpcy5wYW4oYil9fX19fSx0LmNlbnRlclBvaW50PWZ1bmN0aW9uKHIsbil7dmFyIGk9TWF0aC5taW4oci54LG4ueCksbz1NYXRoLm1heChyLngsbi54KSxhPU1hdGgubWluKHIueSxuLnkpLHM9TWF0aC5tYXgoci55LG4ueSk7cmV0dXJue3g6KGkrbykvMix5OihzK2EpLzJ9fSx0Ll9wb2ludERpc3RhbmNlPWZ1bmN0aW9uKHIsbil7dmFyIGk9TWF0aC5taW4oci54LG4ueCksbz1NYXRoLm1heChyLngsbi54KSxhPU1hdGgubWluKHIueSxuLnkpLHM9TWF0aC5tYXgoci55LG4ueSk7cmV0dXJuIE1hdGguc3FydChNYXRoLnBvdyhvLWksMikrTWF0aC5wb3cocy1hLDIpKX0sdC5wcm90b3R5cGUuX2hhbmRsZVRvdWNoRW5kPWZ1bmN0aW9uKHIsbixpKXt2YXIgbz10aGlzO3IuZm9yRWFjaChmdW5jdGlvbihhKXtvLl90b3VjaElkcy5yZW1vdmUoYS50b1N0cmluZygpKX0pLHRoaXMuX3RvdWNoSWRzLnNpemUoKT4wJiZ0aGlzLl96b29tRW5kQ2FsbGJhY2tzLmNhbGxDYWxsYmFja3MoKX0sdC5wcm90b3R5cGUuX2hhbmRsZVdoZWVsRXZlbnQ9ZnVuY3Rpb24ocixuKXtpZighIXRoaXMuX3doZWVsRmlsdGVyKG4pKXt2YXIgaT10aGlzLl90cmFuc2xhdGVUb0NvbXBvbmVudFNwYWNlKHIpO2lmKHRoaXMuX2lzSW5zaWRlQ29tcG9uZW50KGkpKXtuLnByZXZlbnREZWZhdWx0KCk7dmFyIG89bi5kZWx0YVkhPT0wP24uZGVsdGFZOm4uZGVsdGFYLGE9byoobi5kZWx0YU1vZGU/dC5fUElYRUxTX1BFUl9MSU5FOjEpLHM9TWF0aC5wb3coMixhKi4wMDIpO3RoaXMuem9vbShzLGkpLHRoaXMuX3pvb21FbmRDYWxsYmFja3MuY2FsbENhbGxiYWNrcygpfX19LHQucHJvdG90eXBlLl9jb25zdHJhaW5lZFpvb209ZnVuY3Rpb24ocixuLGkpe3JldHVybiBiJHQuY29uc3RyYWluZWRab29tKHIsbixpLHRoaXMubWluRG9tYWluRXh0ZW50KHIpLHRoaXMubWF4RG9tYWluRXh0ZW50KHIpLHRoaXMubWluRG9tYWluVmFsdWUociksdGhpcy5tYXhEb21haW5WYWx1ZShyKSl9LHQucHJvdG90eXBlLl9jb25zdHJhaW5lZFRyYW5zbGF0aW9uPWZ1bmN0aW9uKHIsbil7cmV0dXJuIGIkdC5jb25zdHJhaW5lZFRyYW5zbGF0aW9uKHIsbix0aGlzLm1pbkRvbWFpblZhbHVlKHIpLHRoaXMubWF4RG9tYWluVmFsdWUocikpfSx0LnByb3RvdHlwZS5fc2V0dXBEcmFnSW50ZXJhY3Rpb249ZnVuY3Rpb24oKXt2YXIgcj10aGlzO3RoaXMuX2RyYWdJbnRlcmFjdGlvbi5jb25zdHJhaW5lZFRvQ29tcG9uZW50KCExKTt2YXIgbjt0aGlzLl9kcmFnSW50ZXJhY3Rpb24ub25EcmFnU3RhcnQoZnVuY3Rpb24oKXtyZXR1cm4gbj1udWxsfSksdGhpcy5fZHJhZ0ludGVyYWN0aW9uLm9uRHJhZyhmdW5jdGlvbihpLG8pe2lmKCEoci5fdG91Y2hJZHMuc2l6ZSgpPj0yKSl7dmFyIGE9e3g6KG49PW51bGw/aS54Om4ueCktby54LHk6KG49PW51bGw/aS55Om4ueSktby55fTtyLnBhbihhKSxuPW99fSksdGhpcy5fZHJhZ0ludGVyYWN0aW9uLm9uRHJhZ0VuZChmdW5jdGlvbigpe3JldHVybiByLl9wYW5FbmRDYWxsYmFja3MuY2FsbENhbGxiYWNrcygpfSl9LHQucHJvdG90eXBlLl9ub25MaW5lYXJTY2FsZVdpdGhFeHRlbnRzPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLm1pbkRvbWFpbkV4dGVudChyKSE9bnVsbCYmdGhpcy5tYXhEb21haW5FeHRlbnQocikhPW51bGwmJiEociBpbnN0YW5jZW9mIHgkdC5MaW5lYXIpJiYhKHIgaW5zdGFuY2VvZiB4JHQuVGltZSl9LHQucHJvdG90eXBlLnhTY2FsZXM9ZnVuY3Rpb24ocil7dmFyIG49dGhpcztpZihyPT1udWxsKXt2YXIgaT1bXTtyZXR1cm4gdGhpcy5feFNjYWxlcy5mb3JFYWNoKGZ1bmN0aW9uKG8pe2kucHVzaChvKX0pLGl9cmV0dXJuIHRoaXMuX3hTY2FsZXM9bmV3IHlsLlNldCxyLmZvckVhY2goZnVuY3Rpb24obyl7bi5hZGRYU2NhbGUobyl9KSx0aGlzfSx0LnByb3RvdHlwZS55U2NhbGVzPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXM7aWYocj09bnVsbCl7dmFyIGk9W107cmV0dXJuIHRoaXMuX3lTY2FsZXMuZm9yRWFjaChmdW5jdGlvbihvKXtpLnB1c2gobyl9KSxpfXJldHVybiB0aGlzLl95U2NhbGVzPW5ldyB5bC5TZXQsci5mb3JFYWNoKGZ1bmN0aW9uKG8pe24uYWRkWVNjYWxlKG8pfSksdGhpc30sdC5wcm90b3R5cGUuYWRkWFNjYWxlPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl94U2NhbGVzLmFkZChyKSx0aGlzfSx0LnByb3RvdHlwZS5yZW1vdmVYU2NhbGU9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3hTY2FsZXMuZGVsZXRlKHIpLHRoaXMuX21pbkRvbWFpbkV4dGVudHMuZGVsZXRlKHIpLHRoaXMuX21heERvbWFpbkV4dGVudHMuZGVsZXRlKHIpLHRoaXMuX21pbkRvbWFpblZhbHVlcy5kZWxldGUociksdGhpcy5fbWF4RG9tYWluVmFsdWVzLmRlbGV0ZShyKSx0aGlzfSx0LnByb3RvdHlwZS5hZGRZU2NhbGU9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3lTY2FsZXMuYWRkKHIpLHRoaXN9LHQucHJvdG90eXBlLnJlbW92ZVlTY2FsZT1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5feVNjYWxlcy5kZWxldGUociksdGhpcy5fbWluRG9tYWluRXh0ZW50cy5kZWxldGUociksdGhpcy5fbWF4RG9tYWluRXh0ZW50cy5kZWxldGUociksdGhpcy5fbWluRG9tYWluVmFsdWVzLmRlbGV0ZShyKSx0aGlzLl9tYXhEb21haW5WYWx1ZXMuZGVsZXRlKHIpLHRoaXN9LHQucHJvdG90eXBlLm1pbkRvbWFpbkV4dGVudD1mdW5jdGlvbihyLG4pe2lmKG49PW51bGwpcmV0dXJuIHRoaXMuX21pbkRvbWFpbkV4dGVudHMuZ2V0KHIpO2lmKG4udmFsdWVPZigpPDApdGhyb3cgbmV3IEVycm9yKCJleHRlbnQgbXVzdCBiZSBub24tbmVnYXRpdmUiKTt2YXIgaT10aGlzLm1heERvbWFpbkV4dGVudChyKTtpZihpIT1udWxsJiZpLnZhbHVlT2YoKTxuLnZhbHVlT2YoKSl0aHJvdyBuZXcgRXJyb3IoIm1pbkRvbWFpbkV4dGVudCBtdXN0IGJlIHNtYWxsZXIgdGhhbiBtYXhEb21haW5FeHRlbnQgZm9yIHRoZSBzYW1lIFNjYWxlIik7cmV0dXJuIHRoaXMuX25vbkxpbmVhclNjYWxlV2l0aEV4dGVudHMocikmJnlsLldpbmRvdy53YXJuKCJQYW5uaW5nIGFuZCB6b29taW5nIHdpdGggZXh0ZW50cyBvbiBhIG5vbmxpbmVhciBzY2FsZSBtYXkgaGF2ZSB1bmludGVuZGVkIGJlaGF2aW9yLiIpLHRoaXMuX21pbkRvbWFpbkV4dGVudHMuc2V0KHIsbiksdGhpc30sdC5wcm90b3R5cGUubWF4RG9tYWluRXh0ZW50PWZ1bmN0aW9uKHIsbil7aWYobj09bnVsbClyZXR1cm4gdGhpcy5fbWF4RG9tYWluRXh0ZW50cy5nZXQocik7aWYobi52YWx1ZU9mKCk8PTApdGhyb3cgbmV3IEVycm9yKCJleHRlbnQgbXVzdCBiZSBwb3NpdGl2ZSIpO3ZhciBpPXRoaXMubWluRG9tYWluRXh0ZW50KHIpO2lmKGkhPW51bGwmJm4udmFsdWVPZigpPGkudmFsdWVPZigpKXRocm93IG5ldyBFcnJvcigibWF4RG9tYWluRXh0ZW50IG11c3QgYmUgbGFyZ2VyIHRoYW4gbWluRG9tYWluRXh0ZW50IGZvciB0aGUgc2FtZSBTY2FsZSIpO3JldHVybiB0aGlzLl9ub25MaW5lYXJTY2FsZVdpdGhFeHRlbnRzKHIpJiZ5bC5XaW5kb3cud2FybigiUGFubmluZyBhbmQgem9vbWluZyB3aXRoIGV4dGVudHMgb24gYSBub25saW5lYXIgc2NhbGUgbWF5IGhhdmUgdW5pbnRlbmRlZCBiZWhhdmlvci4iKSx0aGlzLl9tYXhEb21haW5FeHRlbnRzLnNldChyLG4pLHRoaXN9LHQucHJvdG90eXBlLm1pbkRvbWFpblZhbHVlPWZ1bmN0aW9uKHIsbil7cmV0dXJuIG49PW51bGw/dGhpcy5fbWluRG9tYWluVmFsdWVzLmdldChyKToodGhpcy5fbWluRG9tYWluVmFsdWVzLnNldChyLG4pLHRoaXMpfSx0LnByb3RvdHlwZS5tYXhEb21haW5WYWx1ZT1mdW5jdGlvbihyLG4pe3JldHVybiBuPT1udWxsP3RoaXMuX21heERvbWFpblZhbHVlcy5nZXQocik6KHRoaXMuX21heERvbWFpblZhbHVlcy5zZXQocixuKSx0aGlzKX0sdC5wcm90b3R5cGUuc2V0TWluTWF4RG9tYWluVmFsdWVzVG89ZnVuY3Rpb24ocil7dGhpcy5fbWluRG9tYWluVmFsdWVzLmRlbGV0ZShyKSx0aGlzLl9tYXhEb21haW5WYWx1ZXMuZGVsZXRlKHIpO3ZhciBuPXIuZ2V0VHJhbnNmb3JtYXRpb25Eb21haW4oKSxpPW5bMF0sbz1uWzFdO3JldHVybiB0aGlzLm1pbkRvbWFpblZhbHVlKHIsaSksdGhpcy5tYXhEb21haW5WYWx1ZShyLG8pLHRoaXN9LHQucHJvdG90eXBlLm9uUGFuRW5kPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9wYW5FbmRDYWxsYmFja3MuYWRkKHIpLHRoaXN9LHQucHJvdG90eXBlLm9mZlBhbkVuZD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcGFuRW5kQ2FsbGJhY2tzLmRlbGV0ZShyKSx0aGlzfSx0LnByb3RvdHlwZS5vblpvb21FbmQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3pvb21FbmRDYWxsYmFja3MuYWRkKHIpLHRoaXN9LHQucHJvdG90eXBlLm9mZlpvb21FbmQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3pvb21FbmRDYWxsYmFja3MuZGVsZXRlKHIpLHRoaXN9LHQucHJvdG90eXBlLm9uUGFuWm9vbVVwZGF0ZT1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcGFuWm9vbVVwZGF0ZUNhbGxiYWNrcy5hZGQociksdGhpc30sdC5wcm90b3R5cGUub2ZmUGFuWm9vbVVwZGF0ZT1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcGFuWm9vbVVwZGF0ZUNhbGxiYWNrcy5kZWxldGUociksdGhpc30sdC5fUElYRUxTX1BFUl9MSU5FPTEyMCx0fShaRmUuSW50ZXJhY3Rpb24pO01vdC5QYW5ab29tPUpGZX0pO3ZhciBNJHQ9SChUb3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFRvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFFGZT0oZGUoKSxVdChwZSkpLFMkdD1OMSgpLEVvdD1GZSgpLHRCZT1EMSgpLGVCZT1mdW5jdGlvbihlKXtRRmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3ZhciByPWUhPT1udWxsJiZlLmFwcGx5KHRoaXMsYXJndW1lbnRzKXx8dGhpcztyZXR1cm4gci5fb3ZlckNvbXBvbmVudD0hMSxyLl9wb2ludGVyRW50ZXJDYWxsYmFja3M9bmV3IEVvdC5DYWxsYmFja1NldCxyLl9wb2ludGVyTW92ZUNhbGxiYWNrcz1uZXcgRW90LkNhbGxiYWNrU2V0LHIuX3BvaW50ZXJFeGl0Q2FsbGJhY2tzPW5ldyBFb3QuQ2FsbGJhY2tTZXQsci5fbW91c2VNb3ZlQ2FsbGJhY2s9ZnVuY3Rpb24obixpKXtyZXR1cm4gci5faGFuZGxlTW91c2VFdmVudChuLGkpfSxyLl90b3VjaFN0YXJ0Q2FsbGJhY2s9ZnVuY3Rpb24obixpLG8pe3JldHVybiByLl9oYW5kbGVUb3VjaEV2ZW50KGlbblswXV0sbyl9LHJ9cmV0dXJuIHQucHJvdG90eXBlLl9hbmNob3I9ZnVuY3Rpb24ocil7ZS5wcm90b3R5cGUuX2FuY2hvci5jYWxsKHRoaXMsciksdGhpcy5fbW91c2VEaXNwYXRjaGVyPVMkdC5Nb3VzZS5nZXREaXNwYXRjaGVyKHRoaXMuX2NvbXBvbmVudEF0dGFjaGVkVG8pLHRoaXMuX21vdXNlRGlzcGF0Y2hlci5vbk1vdXNlTW92ZSh0aGlzLl9tb3VzZU1vdmVDYWxsYmFjayksdGhpcy5fdG91Y2hEaXNwYXRjaGVyPVMkdC5Ub3VjaC5nZXREaXNwYXRjaGVyKHRoaXMuX2NvbXBvbmVudEF0dGFjaGVkVG8pLHRoaXMuX3RvdWNoRGlzcGF0Y2hlci5vblRvdWNoU3RhcnQodGhpcy5fdG91Y2hTdGFydENhbGxiYWNrKX0sdC5wcm90b3R5cGUuX3VuYW5jaG9yPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX3VuYW5jaG9yLmNhbGwodGhpcyksdGhpcy5fbW91c2VEaXNwYXRjaGVyLm9mZk1vdXNlTW92ZSh0aGlzLl9tb3VzZU1vdmVDYWxsYmFjayksdGhpcy5fbW91c2VEaXNwYXRjaGVyPW51bGwsdGhpcy5fdG91Y2hEaXNwYXRjaGVyLm9mZlRvdWNoU3RhcnQodGhpcy5fdG91Y2hTdGFydENhbGxiYWNrKSx0aGlzLl90b3VjaERpc3BhdGNoZXI9bnVsbH0sdC5wcm90b3R5cGUuX2hhbmRsZU1vdXNlRXZlbnQ9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzLl9tb3VzZURpc3BhdGNoZXIuZXZlbnRJbnNpZGUodGhpcy5fY29tcG9uZW50QXR0YWNoZWRUbyxuKTt0aGlzLl9oYW5kbGVQb2ludGVyRXZlbnQocixpKX0sdC5wcm90b3R5cGUuX2hhbmRsZVRvdWNoRXZlbnQ9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzLl90b3VjaERpc3BhdGNoZXIuZXZlbnRJbnNpZGUodGhpcy5fY29tcG9uZW50QXR0YWNoZWRUbyxuKTt0aGlzLl9oYW5kbGVQb2ludGVyRXZlbnQocixpKX0sdC5wcm90b3R5cGUuX2hhbmRsZVBvaW50ZXJFdmVudD1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXMuX3RyYW5zbGF0ZVRvQ29tcG9uZW50U3BhY2Uociksbz10aGlzLl9pc0luc2lkZUNvbXBvbmVudChpKTtvJiZuPyh0aGlzLl9vdmVyQ29tcG9uZW50fHx0aGlzLl9wb2ludGVyRW50ZXJDYWxsYmFja3MuY2FsbENhbGxiYWNrcyhpKSx0aGlzLl9wb2ludGVyTW92ZUNhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKGkpKTp0aGlzLl9vdmVyQ29tcG9uZW50JiZ0aGlzLl9wb2ludGVyRXhpdENhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKGkpLHRoaXMuX292ZXJDb21wb25lbnQ9byYmbn0sdC5wcm90b3R5cGUub25Qb2ludGVyRW50ZXI9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3BvaW50ZXJFbnRlckNhbGxiYWNrcy5hZGQociksdGhpc30sdC5wcm90b3R5cGUub2ZmUG9pbnRlckVudGVyPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9wb2ludGVyRW50ZXJDYWxsYmFja3MuZGVsZXRlKHIpLHRoaXN9LHQucHJvdG90eXBlLm9uUG9pbnRlck1vdmU9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3BvaW50ZXJNb3ZlQ2FsbGJhY2tzLmFkZChyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZQb2ludGVyTW92ZT1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcG9pbnRlck1vdmVDYWxsYmFja3MuZGVsZXRlKHIpLHRoaXN9LHQucHJvdG90eXBlLm9uUG9pbnRlckV4aXQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3BvaW50ZXJFeGl0Q2FsbGJhY2tzLmFkZChyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZQb2ludGVyRXhpdD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fcG9pbnRlckV4aXRDYWxsYmFja3MuZGVsZXRlKHIpLHRoaXN9LHR9KHRCZS5JbnRlcmFjdGlvbik7VG90LlBvaW50ZXI9ZUJlfSk7dmFyIHM0PUgoWmc9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFpnLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgbDQ9KGRlKCksVXQocGUpKTtsNC5fX2V4cG9ydFN0YXIobSR0KCksWmcpO2w0Ll9fZXhwb3J0U3RhcihfJHQoKSxaZyk7bDQuX19leHBvcnRTdGFyKFNvdCgpLFpnKTtsNC5fX2V4cG9ydFN0YXIodyR0KCksWmcpO2w0Ll9fZXhwb3J0U3RhcihNJHQoKSxaZyk7dmFyIHJCZT10NCgpO1pnLnpvb21PdXQ9ckJlLnpvb21PdXR9KTt2YXIgQ290PUgoYzQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGM0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgbkJlPShkZSgpLFV0KHBlKSksSEY9RmUoKSxpQmU9a2MoKSxlczsoZnVuY3Rpb24oZSl7ZVtlLlZBTFVFPTBdPSJWQUxVRSIsZVtlLlBJWEVMPTFdPSJQSVhFTCJ9KShlcz1jNC5Qcm9wZXJ0eU1vZGV8fChjNC5Qcm9wZXJ0eU1vZGU9e30pKTt2YXIgb0JlPWZ1bmN0aW9uKGUpe25CZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7dmFyIHI9ZS5jYWxsKHRoaXMpfHx0aGlzO3JldHVybiByLl9ib3hWaXNpYmxlPSExLHIuX2JveEJvdW5kcz17dG9wTGVmdDp7eDowLHk6MH0sYm90dG9tUmlnaHQ6e3g6MCx5OjB9fSxyLl94Qm91bmRzTW9kZT1lcy5QSVhFTCxyLl95Qm91bmRzTW9kZT1lcy5QSVhFTCxyLmFkZENsYXNzKCJzZWxlY3Rpb24tYm94LWxheWVyIiksci5fYWRqdXN0Qm91bmRzQ2FsbGJhY2s9ZnVuY3Rpb24oKXtyLnJlbmRlcigpfSxyLl9vdmVyZmxvd0hpZGRlbj0hMCxyLl94RXh0ZW50PVt2b2lkIDAsdm9pZCAwXSxyLl95RXh0ZW50PVt2b2lkIDAsdm9pZCAwXSxyfXJldHVybiB0LnByb3RvdHlwZS5fc2V0dXA9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5fc2V0dXAuY2FsbCh0aGlzKSx0aGlzLl9ib3g9dGhpcy5jb250ZW50KCkuYXBwZW5kKCJnIikuY2xhc3NlZCgic2VsZWN0aW9uLWJveCIsITApLnJlbW92ZSgpLHRoaXMuX2JveEFyZWE9dGhpcy5fYm94LmFwcGVuZCgicmVjdCIpLmNsYXNzZWQoInNlbGVjdGlvbi1hcmVhIiwhMCl9LHQucHJvdG90eXBlLl9zaXplRnJvbU9mZmVyPWZ1bmN0aW9uKHIsbil7cmV0dXJue3dpZHRoOnIsaGVpZ2h0Om59fSx0LnByb3RvdHlwZS5ib3VuZHM9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fZ2V0Qm91bmRzKCk6KHRoaXMuX3NldEJvdW5kcyhyKSx0aGlzLl94Qm91bmRzTW9kZT1lcy5QSVhFTCx0aGlzLl95Qm91bmRzTW9kZT1lcy5QSVhFTCx0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5fc2V0Qm91bmRzPWZ1bmN0aW9uKHIpe3ZhciBuPXt4Ok1hdGgubWluKHIudG9wTGVmdC54LHIuYm90dG9tUmlnaHQueCkseTpNYXRoLm1pbihyLnRvcExlZnQueSxyLmJvdHRvbVJpZ2h0LnkpfSxpPXt4Ok1hdGgubWF4KHIudG9wTGVmdC54LHIuYm90dG9tUmlnaHQueCkseTpNYXRoLm1heChyLnRvcExlZnQueSxyLmJvdHRvbVJpZ2h0LnkpfTt0aGlzLl9ib3hCb3VuZHM9e3RvcExlZnQ6bixib3R0b21SaWdodDppfX0sdC5wcm90b3R5cGUuX2dldEJvdW5kcz1mdW5jdGlvbigpe3JldHVybnt0b3BMZWZ0Ont4OnRoaXMuX3hCb3VuZHNNb2RlPT09ZXMuUElYRUw/dGhpcy5fYm94Qm91bmRzLnRvcExlZnQueDp0aGlzLl94U2NhbGU9PW51bGw/MDpNYXRoLm1pbih0aGlzLnhTY2FsZSgpLnNjYWxlKHRoaXMueEV4dGVudCgpWzBdKSx0aGlzLnhTY2FsZSgpLnNjYWxlKHRoaXMueEV4dGVudCgpWzFdKSkseTp0aGlzLl95Qm91bmRzTW9kZT09PWVzLlBJWEVMP3RoaXMuX2JveEJvdW5kcy50b3BMZWZ0Lnk6dGhpcy5feVNjYWxlPT1udWxsPzA6TWF0aC5taW4odGhpcy55U2NhbGUoKS5zY2FsZSh0aGlzLnlFeHRlbnQoKVswXSksdGhpcy55U2NhbGUoKS5zY2FsZSh0aGlzLnlFeHRlbnQoKVsxXSkpfSxib3R0b21SaWdodDp7eDp0aGlzLl94Qm91bmRzTW9kZT09PWVzLlBJWEVMP3RoaXMuX2JveEJvdW5kcy5ib3R0b21SaWdodC54OnRoaXMuX3hTY2FsZT09bnVsbD8wOk1hdGgubWF4KHRoaXMueFNjYWxlKCkuc2NhbGUodGhpcy54RXh0ZW50KClbMF0pLHRoaXMueFNjYWxlKCkuc2NhbGUodGhpcy54RXh0ZW50KClbMV0pKSx5OnRoaXMuX3lCb3VuZHNNb2RlPT09ZXMuUElYRUw/dGhpcy5fYm94Qm91bmRzLmJvdHRvbVJpZ2h0Lnk6dGhpcy5feVNjYWxlPT1udWxsPzA6TWF0aC5tYXgodGhpcy55U2NhbGUoKS5zY2FsZSh0aGlzLnlFeHRlbnQoKVswXSksdGhpcy55U2NhbGUoKS5zY2FsZSh0aGlzLnlFeHRlbnQoKVsxXSkpfX19LHQucHJvdG90eXBlLnJlbmRlckltbWVkaWF0ZWx5PWZ1bmN0aW9uKCl7aWYoZS5wcm90b3R5cGUucmVuZGVySW1tZWRpYXRlbHkuY2FsbCh0aGlzKSx0aGlzLl9ib3hWaXNpYmxlKXt2YXIgcj10aGlzLmJvdW5kcygpLG49ci50b3BMZWZ0LnksaT1yLmJvdHRvbVJpZ2h0Lnksbz1yLnRvcExlZnQueCxhPXIuYm90dG9tUmlnaHQueDtpZighKEhGLk1hdGguaXNWYWxpZE51bWJlcihuKSYmSEYuTWF0aC5pc1ZhbGlkTnVtYmVyKGkpJiZIRi5NYXRoLmlzVmFsaWROdW1iZXIobykmJkhGLk1hdGguaXNWYWxpZE51bWJlcihhKSkpdGhyb3cgbmV3IEVycm9yKCJib3VuZHMgaGF2ZSBub3QgYmVlbiBwcm9wZXJseSBzZXQiKTt0aGlzLl9ib3hBcmVhLmF0dHJzKHt4Om8seTpuLHdpZHRoOmEtbyxoZWlnaHQ6aS1ufSksdGhpcy5jb250ZW50KCkubm9kZSgpLmFwcGVuZENoaWxkKHRoaXMuX2JveC5ub2RlKCkpfWVsc2UgdGhpcy5fYm94LnJlbW92ZSgpO3JldHVybiB0aGlzfSx0LnByb3RvdHlwZS5ib3hWaXNpYmxlPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2JveFZpc2libGU6KHRoaXMuX2JveFZpc2libGU9cix0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5maXhlZFdpZHRoPWZ1bmN0aW9uKCl7cmV0dXJuITB9LHQucHJvdG90eXBlLmZpeGVkSGVpZ2h0PWZ1bmN0aW9uKCl7cmV0dXJuITB9LHQucHJvdG90eXBlLnhTY2FsZT1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl94U2NhbGU6KHRoaXMuX3hTY2FsZSE9bnVsbCYmdGhpcy5feFNjYWxlLm9mZlVwZGF0ZSh0aGlzLl9hZGp1c3RCb3VuZHNDYWxsYmFjayksdGhpcy5feFNjYWxlPXIsdGhpcy5feEJvdW5kc01vZGU9ZXMuVkFMVUUsdGhpcy5feFNjYWxlLm9uVXBkYXRlKHRoaXMuX2FkanVzdEJvdW5kc0NhbGxiYWNrKSx0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS55U2NhbGU9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5feVNjYWxlOih0aGlzLl95U2NhbGUhPW51bGwmJnRoaXMuX3lTY2FsZS5vZmZVcGRhdGUodGhpcy5fYWRqdXN0Qm91bmRzQ2FsbGJhY2spLHRoaXMuX3lTY2FsZT1yLHRoaXMuX3lCb3VuZHNNb2RlPWVzLlZBTFVFLHRoaXMuX3lTY2FsZS5vblVwZGF0ZSh0aGlzLl9hZGp1c3RCb3VuZHNDYWxsYmFjayksdGhpcy5yZW5kZXIoKSx0aGlzKX0sdC5wcm90b3R5cGUueEV4dGVudD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9nZXRYRXh0ZW50KCk6KHRoaXMuX3NldFhFeHRlbnQociksdGhpcy5feEJvdW5kc01vZGU9ZXMuVkFMVUUsdGhpcy5yZW5kZXIoKSx0aGlzKX0sdC5wcm90b3R5cGUuX2dldFhFeHRlbnQ9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5feEJvdW5kc01vZGU9PT1lcy5WQUxVRT90aGlzLl94RXh0ZW50OnRoaXMuX3hTY2FsZT09bnVsbD9bdm9pZCAwLHZvaWQgMF06W3RoaXMuX3hTY2FsZS5pbnZlcnQodGhpcy5fYm94Qm91bmRzLnRvcExlZnQueCksdGhpcy5feFNjYWxlLmludmVydCh0aGlzLl9ib3hCb3VuZHMuYm90dG9tUmlnaHQueCldfSx0LnByb3RvdHlwZS5fc2V0WEV4dGVudD1mdW5jdGlvbihyKXt0aGlzLl94RXh0ZW50PXJ9LHQucHJvdG90eXBlLnlFeHRlbnQ9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fZ2V0WUV4dGVudCgpOih0aGlzLl9zZXRZRXh0ZW50KHIpLHRoaXMuX3lCb3VuZHNNb2RlPWVzLlZBTFVFLHRoaXMucmVuZGVyKCksdGhpcyl9LHQucHJvdG90eXBlLl9nZXRZRXh0ZW50PWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3lCb3VuZHNNb2RlPT09ZXMuVkFMVUU/dGhpcy5feUV4dGVudDp0aGlzLl95U2NhbGU9PW51bGw/W3ZvaWQgMCx2b2lkIDBdOlt0aGlzLl95U2NhbGUuaW52ZXJ0KHRoaXMuX2JveEJvdW5kcy50b3BMZWZ0LnkpLHRoaXMuX3lTY2FsZS5pbnZlcnQodGhpcy5fYm94Qm91bmRzLmJvdHRvbVJpZ2h0LnkpXX0sdC5wcm90b3R5cGUuX3NldFlFeHRlbnQ9ZnVuY3Rpb24ocil7dGhpcy5feUV4dGVudD1yfSx0LnByb3RvdHlwZS5kZXN0cm95PWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuZGVzdHJveS5jYWxsKHRoaXMpLHRoaXMuX3hTY2FsZSE9bnVsbCYmdGhpcy54U2NhbGUoKS5vZmZVcGRhdGUodGhpcy5fYWRqdXN0Qm91bmRzQ2FsbGJhY2spLHRoaXMuX3lTY2FsZSE9bnVsbCYmdGhpcy55U2NhbGUoKS5vZmZVcGRhdGUodGhpcy5fYWRqdXN0Qm91bmRzQ2FsbGJhY2spfSx0fShpQmUuQ29tcG9uZW50KTtjNC5TZWxlY3Rpb25Cb3hMYXllcj1vQmV9KTt2YXIgVUY9SChQb3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFBvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIGFCZT0oZGUoKSxVdChwZSkpLHNCZT1zNCgpLEFvdD1GZSgpLGxCZT1ZZygpLFZGPUlvdCgpLGNCZT1Db3QoKSx1QmU9ZnVuY3Rpb24oZSl7YUJlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXt2YXIgcj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIHIuX2RldGVjdGlvblJhZGl1cz0zLHIuX3Jlc2l6YWJsZT0hMSxyLl9tb3ZhYmxlPSExLHIuX2hhc0Nvcm5lcnM9ITAsci5hZGRDbGFzcygiZHJhZy1ib3gtbGF5ZXIiKSxyLl9kcmFnSW50ZXJhY3Rpb249bmV3IHNCZS5EcmFnLHIuX3NldFVwQ2FsbGJhY2tzKCksci5fZHJhZ0ludGVyYWN0aW9uLmF0dGFjaFRvKHIpLHIuX2RyYWdTdGFydENhbGxiYWNrcz1uZXcgQW90LkNhbGxiYWNrU2V0LHIuX2RyYWdDYWxsYmFja3M9bmV3IEFvdC5DYWxsYmFja1NldCxyLl9kcmFnRW5kQ2FsbGJhY2tzPW5ldyBBb3QuQ2FsbGJhY2tTZXQscn1yZXR1cm4gdC5wcm90b3R5cGUuX3NldFVwQ2FsbGJhY2tzPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcyxuLGksbyxhLHM9e25ld0JveDowLHJlc2l6ZToxLG1vdmU6Mn0sbD1zLm5ld0JveCxjPWZ1bmN0aW9uKGYpe249ci5fZ2V0UmVzaXppbmdFZGdlcyhmKTt2YXIgcD1yLmJvdW5kcygpLGQ9cC50b3BMZWZ0Lng8PWYueCYmZi54PD1wLmJvdHRvbVJpZ2h0LngmJnAudG9wTGVmdC55PD1mLnkmJmYueTw9cC5ib3R0b21SaWdodC55O3IuYm94VmlzaWJsZSgpJiYobi50b3B8fG4uYm90dG9tfHxuLmxlZnR8fG4ucmlnaHQpP2w9cy5yZXNpemU6ci5ib3hWaXNpYmxlKCkmJnIubW92YWJsZSgpJiZkP2w9cy5tb3ZlOihsPXMubmV3Qm94LHIuX3NldEJvdW5kcyh7dG9wTGVmdDpmLGJvdHRvbVJpZ2h0OmZ9KSxyLl94Qm91bmRzTW9kZT09PVZGLlByb3BlcnR5TW9kZS5WQUxVRSYmci54U2NhbGUoKSE9bnVsbCYmci5fc2V0WEV4dGVudChbci54U2NhbGUoKS5pbnZlcnQoZi54KSxyLnhTY2FsZSgpLmludmVydChmLngpXSksci5feUJvdW5kc01vZGU9PT1WRi5Qcm9wZXJ0eU1vZGUuVkFMVUUmJnIueVNjYWxlKCkhPW51bGwmJnIuX3NldFlFeHRlbnQoW3IueVNjYWxlKCkuaW52ZXJ0KGYueSksci55U2NhbGUoKS5pbnZlcnQoZi55KV0pLHIucmVuZGVyKCkpLHIuYm94VmlzaWJsZSghMCkscD1yLmJvdW5kcygpLGk9e3g6cC50b3BMZWZ0LngseTpwLnRvcExlZnQueX0sbz17eDpwLmJvdHRvbVJpZ2h0LngseTpwLmJvdHRvbVJpZ2h0Lnl9LGE9ZixyLl9kcmFnU3RhcnRDYWxsYmFja3MuY2FsbENhbGxiYWNrcyhwKX0sdT1mdW5jdGlvbihmLHApe3N3aXRjaChsKXtjYXNlIHMubmV3Qm94Om8ueD1wLngsby55PXAueTticmVhaztjYXNlIHMucmVzaXplOm4uYm90dG9tP28ueT1wLnk6bi50b3AmJihpLnk9cC55KSxuLnJpZ2h0P28ueD1wLng6bi5sZWZ0JiYoaS54PXAueCk7YnJlYWs7Y2FzZSBzLm1vdmU6dmFyIGQ9cC54LWEueCxnPXAueS1hLnk7aS54Kz1kLGkueSs9ZyxvLngrPWQsby55Kz1nLGE9cDticmVha31yLl9zZXRCb3VuZHMoe3RvcExlZnQ6aSxib3R0b21SaWdodDpvfSksci5feEJvdW5kc01vZGU9PT1WRi5Qcm9wZXJ0eU1vZGUuVkFMVUUmJnIueFNjYWxlKCkhPW51bGwmJnIuX3NldFhFeHRlbnQoW3IueFNjYWxlKCkuaW52ZXJ0KGkueCksci54U2NhbGUoKS5pbnZlcnQoby54KV0pLHIuX3lCb3VuZHNNb2RlPT09VkYuUHJvcGVydHlNb2RlLlZBTFVFJiZyLnlTY2FsZSgpIT1udWxsJiZyLl9zZXRZRXh0ZW50KFtyLnlTY2FsZSgpLmludmVydChpLnkpLHIueVNjYWxlKCkuaW52ZXJ0KG8ueSldKSxyLnJlbmRlcigpLHIuX2RyYWdDYWxsYmFja3MuY2FsbENhbGxiYWNrcyhyLmJvdW5kcygpKX0saD1mdW5jdGlvbihmLHApe2w9PT1zLm5ld0JveCYmZi54PT09cC54JiZmLnk9PT1wLnkmJnIuYm94VmlzaWJsZSghMSksci5fZHJhZ0VuZENhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKHIuYm91bmRzKCkpfTt0aGlzLl9kcmFnSW50ZXJhY3Rpb24ub25EcmFnU3RhcnQoYyksdGhpcy5fZHJhZ0ludGVyYWN0aW9uLm9uRHJhZyh1KSx0aGlzLl9kcmFnSW50ZXJhY3Rpb24ub25EcmFnRW5kKGgpLHRoaXMuX2Rpc2Nvbm5lY3RJbnRlcmFjdGlvbj1mdW5jdGlvbigpe3IuX2RyYWdJbnRlcmFjdGlvbi5vZmZEcmFnU3RhcnQoYyksci5fZHJhZ0ludGVyYWN0aW9uLm9mZkRyYWcodSksci5fZHJhZ0ludGVyYWN0aW9uLm9mZkRyYWdFbmQoaCksci5fZHJhZ0ludGVyYWN0aW9uLmRldGFjaCgpfX0sdC5wcm90b3R5cGUuX3NldHVwPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcztlLnByb3RvdHlwZS5fc2V0dXAuY2FsbCh0aGlzKTt2YXIgbj1mdW5jdGlvbigpe3JldHVybiByLl9ib3guYXBwZW5kKCJsaW5lIikuc3R5bGVzKHtvcGFjaXR5OjAsc3Ryb2tlOiJwaW5rIiwicG9pbnRlci1ldmVudHMiOiJ2aXNpYmxlU3Ryb2tlIn0pfTtpZih0aGlzLl9kZXRlY3Rpb25FZGdlVD1uKCkuY2xhc3NlZCgiZHJhZy1lZGdlLXRiIiwhMCksdGhpcy5fZGV0ZWN0aW9uRWRnZUI9bigpLmNsYXNzZWQoImRyYWctZWRnZS10YiIsITApLHRoaXMuX2RldGVjdGlvbkVkZ2VMPW4oKS5jbGFzc2VkKCJkcmFnLWVkZ2UtbHIiLCEwKSx0aGlzLl9kZXRlY3Rpb25FZGdlUj1uKCkuY2xhc3NlZCgiZHJhZy1lZGdlLWxyIiwhMCksdGhpcy5faGFzQ29ybmVycyl7dmFyIGk9ZnVuY3Rpb24oKXtyZXR1cm4gci5fYm94LmFwcGVuZCgiY2lyY2xlIikuc3R5bGVzKHtvcGFjaXR5OjAsZmlsbDoicGluayIsInBvaW50ZXItZXZlbnRzIjoidmlzaWJsZUZpbGwifSl9O3RoaXMuX2RldGVjdGlvbkNvcm5lclRMPWkoKS5jbGFzc2VkKCJkcmFnLWNvcm5lci10bCIsITApLHRoaXMuX2RldGVjdGlvbkNvcm5lclRSPWkoKS5jbGFzc2VkKCJkcmFnLWNvcm5lci10ciIsITApLHRoaXMuX2RldGVjdGlvbkNvcm5lckJMPWkoKS5jbGFzc2VkKCJkcmFnLWNvcm5lci1ibCIsITApLHRoaXMuX2RldGVjdGlvbkNvcm5lckJSPWkoKS5jbGFzc2VkKCJkcmFnLWNvcm5lci1iciIsITApfX0sdC5wcm90b3R5cGUuX2dldFJlc2l6aW5nRWRnZXM9ZnVuY3Rpb24ocil7dmFyIG49e3RvcDohMSxib3R0b206ITEsbGVmdDohMSxyaWdodDohMX07aWYoIXRoaXMucmVzaXphYmxlKCkpcmV0dXJuIG47dmFyIGk9dGhpcy5ib3VuZHMoKSxvPWkudG9wTGVmdC55LGE9aS5ib3R0b21SaWdodC55LHM9aS50b3BMZWZ0LngsbD1pLmJvdHRvbVJpZ2h0LngsYz10aGlzLl9kZXRlY3Rpb25SYWRpdXM7cmV0dXJuIHMtYzw9ci54JiZyLng8PWwrYyYmKG4udG9wPW8tYzw9ci55JiZyLnk8PW8rYyxuLmJvdHRvbT1hLWM8PXIueSYmci55PD1hK2MpLG8tYzw9ci55JiZyLnk8PWErYyYmKG4ubGVmdD1zLWM8PXIueCYmci54PD1zK2Msbi5yaWdodD1sLWM8PXIueCYmci54PD1sK2MpLG59LHQucHJvdG90eXBlLnJlbmRlckltbWVkaWF0ZWx5PWZ1bmN0aW9uKCl7aWYoZS5wcm90b3R5cGUucmVuZGVySW1tZWRpYXRlbHkuY2FsbCh0aGlzKSx0aGlzLmJveFZpc2libGUoKSl7dmFyIHI9dGhpcy5ib3VuZHMoKSxuPXIudG9wTGVmdC55LGk9ci5ib3R0b21SaWdodC55LG89ci50b3BMZWZ0LngsYT1yLmJvdHRvbVJpZ2h0Lng7dGhpcy5fZGV0ZWN0aW9uRWRnZVQuYXR0cnMoe3gxOm8seTE6bix4MjphLHkyOm4sInN0cm9rZS13aWR0aCI6dGhpcy5fZGV0ZWN0aW9uUmFkaXVzKjJ9KSx0aGlzLl9kZXRlY3Rpb25FZGdlQi5hdHRycyh7eDE6byx5MTppLHgyOmEseTI6aSwic3Ryb2tlLXdpZHRoIjp0aGlzLl9kZXRlY3Rpb25SYWRpdXMqMn0pLHRoaXMuX2RldGVjdGlvbkVkZ2VMLmF0dHJzKHt4MTpvLHkxOm4seDI6byx5MjppLCJzdHJva2Utd2lkdGgiOnRoaXMuX2RldGVjdGlvblJhZGl1cyoyfSksdGhpcy5fZGV0ZWN0aW9uRWRnZVIuYXR0cnMoe3gxOmEseTE6bix4MjphLHkyOmksInN0cm9rZS13aWR0aCI6dGhpcy5fZGV0ZWN0aW9uUmFkaXVzKjJ9KSx0aGlzLl9oYXNDb3JuZXJzJiYodGhpcy5fZGV0ZWN0aW9uQ29ybmVyVEwuYXR0cnMoe2N4Om8sY3k6bixyOnRoaXMuX2RldGVjdGlvblJhZGl1c30pLHRoaXMuX2RldGVjdGlvbkNvcm5lclRSLmF0dHJzKHtjeDphLGN5Om4scjp0aGlzLl9kZXRlY3Rpb25SYWRpdXN9KSx0aGlzLl9kZXRlY3Rpb25Db3JuZXJCTC5hdHRycyh7Y3g6byxjeTppLHI6dGhpcy5fZGV0ZWN0aW9uUmFkaXVzfSksdGhpcy5fZGV0ZWN0aW9uQ29ybmVyQlIuYXR0cnMoe2N4OmEsY3k6aSxyOnRoaXMuX2RldGVjdGlvblJhZGl1c30pKX1yZXR1cm4gdGhpc30sdC5wcm90b3R5cGUuZGV0ZWN0aW9uUmFkaXVzPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX2RldGVjdGlvblJhZGl1cztpZihyPDApdGhyb3cgbmV3IEVycm9yKCJkZXRlY3Rpb24gcmFkaXVzIGNhbm5vdCBiZSBuZWdhdGl2ZS4iKTtyZXR1cm4gdGhpcy5fZGV0ZWN0aW9uUmFkaXVzPXIsdGhpcy5yZW5kZXIoKSx0aGlzfSx0LnByb3RvdHlwZS5yZXNpemFibGU9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fcmVzaXphYmxlOih0aGlzLl9yZXNpemFibGU9cix0aGlzLl9zZXRSZXNpemFibGVDbGFzc2VzKHIpLHRoaXMpfSx0LnByb3RvdHlwZS5fc2V0UmVzaXphYmxlQ2xhc3Nlcz1mdW5jdGlvbihyKXtyJiZ0aGlzLmVuYWJsZWQoKT8odGhpcy5hZGRDbGFzcygieC1yZXNpemFibGUiKSx0aGlzLmFkZENsYXNzKCJ5LXJlc2l6YWJsZSIpKToodGhpcy5yZW1vdmVDbGFzcygieC1yZXNpemFibGUiKSx0aGlzLnJlbW92ZUNsYXNzKCJ5LXJlc2l6YWJsZSIpKX0sdC5wcm90b3R5cGUubW92YWJsZT1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9tb3ZhYmxlOih0aGlzLl9tb3ZhYmxlPXIsdGhpcy5fc2V0TW92YWJsZUNsYXNzKCksdGhpcyl9LHQucHJvdG90eXBlLl9zZXRNb3ZhYmxlQ2xhc3M9ZnVuY3Rpb24oKXt0aGlzLm1vdmFibGUoKSYmdGhpcy5lbmFibGVkKCk/dGhpcy5hZGRDbGFzcygibW92YWJsZSIpOnRoaXMucmVtb3ZlQ2xhc3MoIm1vdmFibGUiKX0sdC5wcm90b3R5cGUub25EcmFnU3RhcnQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2RyYWdTdGFydENhbGxiYWNrcy5hZGQociksdGhpc30sdC5wcm90b3R5cGUub2ZmRHJhZ1N0YXJ0PWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kcmFnU3RhcnRDYWxsYmFja3MuZGVsZXRlKHIpLHRoaXN9LHQucHJvdG90eXBlLm9uRHJhZz1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fZHJhZ0NhbGxiYWNrcy5hZGQociksdGhpc30sdC5wcm90b3R5cGUub2ZmRHJhZz1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fZHJhZ0NhbGxiYWNrcy5kZWxldGUociksdGhpc30sdC5wcm90b3R5cGUub25EcmFnRW5kPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kcmFnRW5kQ2FsbGJhY2tzLmFkZChyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZEcmFnRW5kPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kcmFnRW5kQ2FsbGJhY2tzLmRlbGV0ZShyKSx0aGlzfSx0LnByb3RvdHlwZS5kcmFnSW50ZXJhY3Rpb249ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZHJhZ0ludGVyYWN0aW9ufSx0LnByb3RvdHlwZS5lbmFibGVkPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2RyYWdJbnRlcmFjdGlvbi5lbmFibGVkKCk6KHRoaXMuX2RyYWdJbnRlcmFjdGlvbi5lbmFibGVkKHIpLHRoaXMuX3NldFJlc2l6YWJsZUNsYXNzZXModGhpcy5yZXNpemFibGUoKSksdGhpcy5fc2V0TW92YWJsZUNsYXNzKCksdGhpcyl9LHQucHJvdG90eXBlLmRlc3Ryb3k9ZnVuY3Rpb24oKXt2YXIgcj10aGlzO2UucHJvdG90eXBlLmRlc3Ryb3kuY2FsbCh0aGlzKSx0aGlzLl9kcmFnU3RhcnRDYWxsYmFja3MuZm9yRWFjaChmdW5jdGlvbihuKXtyZXR1cm4gci5fZHJhZ0NhbGxiYWNrcy5kZWxldGUobil9KSx0aGlzLl9kcmFnQ2FsbGJhY2tzLmZvckVhY2goZnVuY3Rpb24obil7cmV0dXJuIHIuX2RyYWdDYWxsYmFja3MuZGVsZXRlKG4pfSksdGhpcy5fZHJhZ0VuZENhbGxiYWNrcy5mb3JFYWNoKGZ1bmN0aW9uKG4pe3JldHVybiByLl9kcmFnRW5kQ2FsbGJhY2tzLmRlbGV0ZShuKX0pLHRoaXMuX2Rpc2Nvbm5lY3RJbnRlcmFjdGlvbigpfSx0LnByb3RvdHlwZS5kZXRhY2g9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fcmVzZXRTdGF0ZSgpLHRoaXMuX2RyYWdJbnRlcmFjdGlvbi5kZXRhY2goKSxlLnByb3RvdHlwZS5kZXRhY2guY2FsbCh0aGlzKSx0aGlzfSx0LnByb3RvdHlwZS5hbmNob3I9ZnVuY3Rpb24ocil7cmV0dXJuIHI9bEJlLmNvZXJjZUV4dGVybmFsRDMociksdGhpcy5fZHJhZ0ludGVyYWN0aW9uLmF0dGFjaFRvKHRoaXMpLGUucHJvdG90eXBlLmFuY2hvci5jYWxsKHRoaXMsciksdGhpc30sdC5wcm90b3R5cGUuX3Jlc2V0U3RhdGU9ZnVuY3Rpb24oKXt0aGlzLmJvdW5kcyh7dG9wTGVmdDp7eDowLHk6MH0sYm90dG9tUmlnaHQ6e3g6MCx5OjB9fSl9LHR9KGNCZS5TZWxlY3Rpb25Cb3hMYXllcik7UG90LkRyYWdCb3hMYXllcj11QmV9KTt2YXIga290PUgoTG90PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShMb3QsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBoQmU9KGRlKCksVXQocGUpKSxmQmU9RmUoKSxwQmU9a2MoKSxPMTsoZnVuY3Rpb24oZSl7ZVtlLlZBTFVFPTBdPSJWQUxVRSIsZVtlLlBJWEVMPTFdPSJQSVhFTCJ9KShPMXx8KE8xPXt9KSk7dmFyIGRCZT1mdW5jdGlvbihlKXtoQmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdChyKXt2YXIgbj1lLmNhbGwodGhpcyl8fHRoaXM7aWYobi5fbW9kZT1PMS5WQUxVRSxyIT09dC5PUklFTlRBVElPTl9WRVJUSUNBTCYmciE9PXQuT1JJRU5UQVRJT05fSE9SSVpPTlRBTCl0aHJvdyBuZXcgRXJyb3IocisiIGlzIG5vdCBhIHZhbGlkIG9yaWVudGF0aW9uIGZvciBHdWlkZUxpbmVMYXllciIpO3JldHVybiBuLl9vcmllbnRhdGlvbj1yLG4uX292ZXJmbG93SGlkZGVuPSEwLG4uYWRkQ2xhc3MoImd1aWRlLWxpbmUtbGF5ZXIiKSxuLl9pc1ZlcnRpY2FsKCk/bi5hZGRDbGFzcygidmVydGljYWwiKTpuLmFkZENsYXNzKCJob3Jpem9udGFsIiksbi5fc2NhbGVVcGRhdGVDYWxsYmFjaz1mdW5jdGlvbigpe24uX3N5bmNQaXhlbFBvc2l0aW9uQW5kVmFsdWUoKSxuLnJlbmRlcigpfSxufXJldHVybiB0LnByb3RvdHlwZS5fc2V0dXA9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5fc2V0dXAuY2FsbCh0aGlzKSx0aGlzLl9ndWlkZUxpbmU9dGhpcy5jb250ZW50KCkuYXBwZW5kKCJsaW5lIikuY2xhc3NlZCgiZ3VpZGUtbGluZSIsITApfSx0LnByb3RvdHlwZS5fc2l6ZUZyb21PZmZlcj1mdW5jdGlvbihyLG4pe3JldHVybnt3aWR0aDpyLGhlaWdodDpufX0sdC5wcm90b3R5cGUuX2lzVmVydGljYWw9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fb3JpZW50YXRpb249PT10Lk9SSUVOVEFUSU9OX1ZFUlRJQ0FMfSx0LnByb3RvdHlwZS5maXhlZFdpZHRoPWZ1bmN0aW9uKCl7cmV0dXJuITB9LHQucHJvdG90eXBlLmZpeGVkSGVpZ2h0PWZ1bmN0aW9uKCl7cmV0dXJuITB9LHQucHJvdG90eXBlLmNvbXB1dGVMYXlvdXQ9ZnVuY3Rpb24ocixuLGkpe3JldHVybiBlLnByb3RvdHlwZS5jb21wdXRlTGF5b3V0LmNhbGwodGhpcyxyLG4saSksdGhpcy5zY2FsZSgpIT1udWxsJiYodGhpcy5faXNWZXJ0aWNhbCgpP3RoaXMuc2NhbGUoKS5yYW5nZShbMCx0aGlzLndpZHRoKCldKTp0aGlzLnNjYWxlKCkucmFuZ2UoW3RoaXMuaGVpZ2h0KCksMF0pKSx0aGlzfSx0LnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseT1mdW5jdGlvbigpe3JldHVybiBlLnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseS5jYWxsKHRoaXMpLHRoaXMuX3N5bmNQaXhlbFBvc2l0aW9uQW5kVmFsdWUoKSx0aGlzLl9ndWlkZUxpbmUuYXR0cnMoe3gxOnRoaXMuX2lzVmVydGljYWwoKT90aGlzLnBpeGVsUG9zaXRpb24oKTowLHkxOnRoaXMuX2lzVmVydGljYWwoKT8wOnRoaXMucGl4ZWxQb3NpdGlvbigpLHgyOnRoaXMuX2lzVmVydGljYWwoKT90aGlzLnBpeGVsUG9zaXRpb24oKTp0aGlzLndpZHRoKCkseTI6dGhpcy5faXNWZXJ0aWNhbCgpP3RoaXMuaGVpZ2h0KCk6dGhpcy5waXhlbFBvc2l0aW9uKCl9KSx0aGlzfSx0LnByb3RvdHlwZS5fc3luY1BpeGVsUG9zaXRpb25BbmRWYWx1ZT1mdW5jdGlvbigpe3RoaXMuc2NhbGUoKSE9bnVsbCYmKHRoaXMuX21vZGU9PT1PMS5WQUxVRSYmdGhpcy52YWx1ZSgpIT1udWxsP3RoaXMuX3BpeGVsUG9zaXRpb249dGhpcy5zY2FsZSgpLnNjYWxlKHRoaXMudmFsdWUoKSk6dGhpcy5fbW9kZT09PU8xLlBJWEVMJiZ0aGlzLnBpeGVsUG9zaXRpb24oKSE9bnVsbCYmKHRoaXMuX3ZhbHVlPXRoaXMuc2NhbGUoKS5pbnZlcnQodGhpcy5waXhlbFBvc2l0aW9uKCkpKSl9LHQucHJvdG90eXBlLl9zZXRQaXhlbFBvc2l0aW9uV2l0aG91dENoYW5naW5nTW9kZT1mdW5jdGlvbihyKXt0aGlzLl9waXhlbFBvc2l0aW9uPXIsdGhpcy5zY2FsZSgpIT1udWxsJiYodGhpcy5fdmFsdWU9dGhpcy5zY2FsZSgpLmludmVydCh0aGlzLnBpeGVsUG9zaXRpb24oKSkpLHRoaXMucmVuZGVyKCl9LHQucHJvdG90eXBlLnNjYWxlPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX3NjYWxlO3ZhciBuPXRoaXMuX3NjYWxlO3JldHVybiBuIT1udWxsJiZuLm9mZlVwZGF0ZSh0aGlzLl9zY2FsZVVwZGF0ZUNhbGxiYWNrKSx0aGlzLl9zY2FsZT1yLHRoaXMuX3NjYWxlLm9uVXBkYXRlKHRoaXMuX3NjYWxlVXBkYXRlQ2FsbGJhY2spLHRoaXMuX3N5bmNQaXhlbFBvc2l0aW9uQW5kVmFsdWUoKSx0aGlzLnJlZHJhdygpLHRoaXN9LHQucHJvdG90eXBlLnZhbHVlPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX3ZhbHVlOih0aGlzLl92YWx1ZT1yLHRoaXMuX21vZGU9TzEuVkFMVUUsdGhpcy5fc3luY1BpeGVsUG9zaXRpb25BbmRWYWx1ZSgpLHRoaXMucmVuZGVyKCksdGhpcyl9LHQucHJvdG90eXBlLnBpeGVsUG9zaXRpb249ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fcGl4ZWxQb3NpdGlvbjtpZighZkJlLk1hdGguaXNWYWxpZE51bWJlcihyKSl0aHJvdyBuZXcgRXJyb3IoInBpeGVsUG9zaXRpb24gbXVzdCBiZSBhIGZpbml0ZSBudW1iZXIiKTtyZXR1cm4gdGhpcy5fcGl4ZWxQb3NpdGlvbj1yLHRoaXMuX21vZGU9TzEuUElYRUwsdGhpcy5fc3luY1BpeGVsUG9zaXRpb25BbmRWYWx1ZSgpLHRoaXMucmVuZGVyKCksdGhpc30sdC5wcm90b3R5cGUuZGVzdHJveT1mdW5jdGlvbigpe2UucHJvdG90eXBlLmRlc3Ryb3kuY2FsbCh0aGlzKSx0aGlzLnNjYWxlKCkhPW51bGwmJnRoaXMuc2NhbGUoKS5vZmZVcGRhdGUodGhpcy5fc2NhbGVVcGRhdGVDYWxsYmFjayl9LHQuT1JJRU5UQVRJT05fVkVSVElDQUw9InZlcnRpY2FsIix0Lk9SSUVOVEFUSU9OX0hPUklaT05UQUw9Imhvcml6b250YWwiLHR9KHBCZS5Db21wb25lbnQpO0xvdC5HdWlkZUxpbmVMYXllcj1kQmV9KTt2YXIgRSR0PUgoTm90PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShOb3QsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBtQmU9KGRlKCksVXQocGUpKSxnQmU9a290KCksX0JlPXM0KCksUm90PUZlKCkseUJlPWZ1bmN0aW9uKGUpe21CZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3ZhciBuPWUuY2FsbCh0aGlzLHIpfHx0aGlzO24uX2RldGVjdGlvblJhZGl1cz0zLG4uX2VuYWJsZWQ9ITAsbi5hZGRDbGFzcygiZHJhZy1saW5lLWxheWVyIiksbi5hZGRDbGFzcygiZW5hYmxlZCIpLG4uX2RyYWdJbnRlcmFjdGlvbj1uZXcgX0JlLkRyYWcsbi5fZHJhZ0ludGVyYWN0aW9uLmF0dGFjaFRvKG4pO3ZhciBpPWZ1bmN0aW9uKGMpe3JldHVybiBuLl9pc1ZlcnRpY2FsKCkmJm4ucGl4ZWxQb3NpdGlvbigpLW4uZGV0ZWN0aW9uUmFkaXVzKCk8PWMueCYmYy54PD1uLnBpeGVsUG9zaXRpb24oKStuLmRldGVjdGlvblJhZGl1cygpfHwhbi5faXNWZXJ0aWNhbCgpJiZuLnBpeGVsUG9zaXRpb24oKS1uLmRldGVjdGlvblJhZGl1cygpPD1jLnkmJmMueTw9bi5waXhlbFBvc2l0aW9uKCkrbi5kZXRlY3Rpb25SYWRpdXMoKX0sbz0hMSxhPWZ1bmN0aW9uKGMpe2koYykmJihvPSEwLG4uX2RyYWdTdGFydENhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKG4pKX07bi5fZHJhZ0ludGVyYWN0aW9uLm9uRHJhZ1N0YXJ0KGEpO3ZhciBzPWZ1bmN0aW9uKGMsdSl7byYmKG4uX3NldFBpeGVsUG9zaXRpb25XaXRob3V0Q2hhbmdpbmdNb2RlKG4uX2lzVmVydGljYWwoKT91Lng6dS55KSxuLl9kcmFnQ2FsbGJhY2tzLmNhbGxDYWxsYmFja3MobikpfTtuLl9kcmFnSW50ZXJhY3Rpb24ub25EcmFnKHMpO3ZhciBsPWZ1bmN0aW9uKGMsdSl7byYmKG89ITEsbi5fZHJhZ0VuZENhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKG4pKX07cmV0dXJuIG4uX2RyYWdJbnRlcmFjdGlvbi5vbkRyYWdFbmQobCksbi5fZGlzY29ubmVjdEludGVyYWN0aW9uPWZ1bmN0aW9uKCl7bi5fZHJhZ0ludGVyYWN0aW9uLm9mZkRyYWdTdGFydChhKSxuLl9kcmFnSW50ZXJhY3Rpb24ub2ZmRHJhZyhzKSxuLl9kcmFnSW50ZXJhY3Rpb24ub2ZmRHJhZ0VuZChsKSxuLl9kcmFnSW50ZXJhY3Rpb24uZGV0YWNoKCl9LG4uX2RyYWdTdGFydENhbGxiYWNrcz1uZXcgUm90LkNhbGxiYWNrU2V0LG4uX2RyYWdDYWxsYmFja3M9bmV3IFJvdC5DYWxsYmFja1NldCxuLl9kcmFnRW5kQ2FsbGJhY2tzPW5ldyBSb3QuQ2FsbGJhY2tTZXQsbn1yZXR1cm4gdC5wcm90b3R5cGUuX3NldHVwPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX3NldHVwLmNhbGwodGhpcyksdGhpcy5fZGV0ZWN0aW9uRWRnZT10aGlzLmNvbnRlbnQoKS5hcHBlbmQoImxpbmUiKS5zdHlsZXMoe29wYWNpdHk6MCxzdHJva2U6InBpbmsiLCJwb2ludGVyLWV2ZW50cyI6InZpc2libGVTdHJva2UifSkuY2xhc3NlZCgiZHJhZy1lZGdlIiwhMCl9LHQucHJvdG90eXBlLnJlbmRlckltbWVkaWF0ZWx5PWZ1bmN0aW9uKCl7cmV0dXJuIGUucHJvdG90eXBlLnJlbmRlckltbWVkaWF0ZWx5LmNhbGwodGhpcyksdGhpcy5fZGV0ZWN0aW9uRWRnZS5hdHRycyh7eDE6dGhpcy5faXNWZXJ0aWNhbCgpP3RoaXMucGl4ZWxQb3NpdGlvbigpOjAseTE6dGhpcy5faXNWZXJ0aWNhbCgpPzA6dGhpcy5waXhlbFBvc2l0aW9uKCkseDI6dGhpcy5faXNWZXJ0aWNhbCgpP3RoaXMucGl4ZWxQb3NpdGlvbigpOnRoaXMud2lkdGgoKSx5Mjp0aGlzLl9pc1ZlcnRpY2FsKCk/dGhpcy5oZWlnaHQoKTp0aGlzLnBpeGVsUG9zaXRpb24oKSwic3Ryb2tlLXdpZHRoIjp0aGlzLl9kZXRlY3Rpb25SYWRpdXMqMn0pLHRoaXN9LHQucHJvdG90eXBlLmRldGVjdGlvblJhZGl1cz1mdW5jdGlvbihyKXtpZihyPT1udWxsKXJldHVybiB0aGlzLl9kZXRlY3Rpb25SYWRpdXM7aWYocjwwKXRocm93IG5ldyBFcnJvcigiZGV0ZWN0aW9uIHJhZGl1cyBjYW5ub3QgYmUgbmVnYXRpdmUuIik7cmV0dXJuIHRoaXMuX2RldGVjdGlvblJhZGl1cz1yLHRoaXMucmVuZGVyKCksdGhpc30sdC5wcm90b3R5cGUuZW5hYmxlZD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9lbmFibGVkOih0aGlzLl9lbmFibGVkPXIscj90aGlzLmFkZENsYXNzKCJlbmFibGVkIik6dGhpcy5yZW1vdmVDbGFzcygiZW5hYmxlZCIpLHRoaXMuX2RyYWdJbnRlcmFjdGlvbi5lbmFibGVkKHIpLHRoaXMpfSx0LnByb3RvdHlwZS5vbkRyYWdTdGFydD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fZHJhZ1N0YXJ0Q2FsbGJhY2tzLmFkZChyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZEcmFnU3RhcnQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2RyYWdTdGFydENhbGxiYWNrcy5kZWxldGUociksdGhpc30sdC5wcm90b3R5cGUub25EcmFnPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kcmFnQ2FsbGJhY2tzLmFkZChyKSx0aGlzfSx0LnByb3RvdHlwZS5vZmZEcmFnPWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9kcmFnQ2FsbGJhY2tzLmRlbGV0ZShyKSx0aGlzfSx0LnByb3RvdHlwZS5vbkRyYWdFbmQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2RyYWdFbmRDYWxsYmFja3MuYWRkKHIpLHRoaXN9LHQucHJvdG90eXBlLm9mZkRyYWdFbmQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2RyYWdFbmRDYWxsYmFja3MuZGVsZXRlKHIpLHRoaXN9LHQucHJvdG90eXBlLmRlc3Ryb3k9ZnVuY3Rpb24oKXt2YXIgcj10aGlzO2UucHJvdG90eXBlLmRlc3Ryb3kuY2FsbCh0aGlzKSx0aGlzLl9kcmFnU3RhcnRDYWxsYmFja3MuZm9yRWFjaChmdW5jdGlvbihuKXtyZXR1cm4gci5fZHJhZ1N0YXJ0Q2FsbGJhY2tzLmRlbGV0ZShuKX0pLHRoaXMuX2RyYWdDYWxsYmFja3MuZm9yRWFjaChmdW5jdGlvbihuKXtyZXR1cm4gci5fZHJhZ0NhbGxiYWNrcy5kZWxldGUobil9KSx0aGlzLl9kcmFnRW5kQ2FsbGJhY2tzLmZvckVhY2goZnVuY3Rpb24obil7cmV0dXJuIHIuX2RyYWdFbmRDYWxsYmFja3MuZGVsZXRlKG4pfSksdGhpcy5fZGlzY29ubmVjdEludGVyYWN0aW9uKCl9LHR9KGdCZS5HdWlkZUxpbmVMYXllcik7Tm90LkRyYWdMaW5lTGF5ZXI9eUJlfSk7dmFyIFQkdD1IKERvdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoRG90LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgdkJlPShkZSgpLFV0KHBlKSkseEJlPWtjKCk7ZnVuY3Rpb24gcUYoZSx0LHIpe3ZhciBuPXt9O2lmKHIhPT12b2lkIDApZm9yKHZhciBpPTA7aTxyLmxlbmd0aDtpKyspe3ZhciBvPXJbaS0xXSxhPXJbaV07blthXT1vfXJldHVybiBmdW5jdGlvbihzKXt2YXIgbD1lLnNjYWxlKHMpO2lmKCF0KXJldHVybiBsO3ZhciBjLHU9bltzXT09PXZvaWQgMD92b2lkIDA6ZS5zY2FsZShuW3NdKTtyZXR1cm4gdSE9PXZvaWQgMCYmKGM9dSsobC11KS8yKSxjfX12YXIgYkJlPWZ1bmN0aW9uKGUpe3ZCZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIsbil7dmFyIGk9ZS5jYWxsKHRoaXMpfHx0aGlzO3JldHVybiBpLmFkZENsYXNzKCJncmlkbGluZXMiKSxpLl94U2NhbGU9cixpLl95U2NhbGU9bixpLl9yZW5kZXJDYWxsYmFjaz1mdW5jdGlvbihvKXtyZXR1cm4gaS5yZW5kZXIoKX0saS5feFNjYWxlJiZpLl94U2NhbGUub25VcGRhdGUoaS5fcmVuZGVyQ2FsbGJhY2spLGkuX3lTY2FsZSYmaS5feVNjYWxlLm9uVXBkYXRlKGkuX3JlbmRlckNhbGxiYWNrKSxpfXJldHVybiB0LnByb3RvdHlwZS5iZXR3ZWVuWD1mdW5jdGlvbihyKXtyZXR1cm4gcj09PXZvaWQgMD90aGlzLl9iZXR3ZWVuWDoociE9PXRoaXMuX2JldHdlZW5YJiYodGhpcy5fYmV0d2Vlblg9cix0aGlzLnJlbmRlcigpKSx0aGlzKX0sdC5wcm90b3R5cGUuYmV0d2Vlblk9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PT12b2lkIDA/dGhpcy5fYmV0d2Vlblk6KHIhPT10aGlzLl9iZXR3ZWVuWSYmKHRoaXMuX2JldHdlZW5ZPXIsdGhpcy5yZW5kZXIoKSksdGhpcyl9LHQucHJvdG90eXBlLmRlc3Ryb3k9ZnVuY3Rpb24oKXtyZXR1cm4gZS5wcm90b3R5cGUuZGVzdHJveS5jYWxsKHRoaXMpLHRoaXMuX3hTY2FsZSYmdGhpcy5feFNjYWxlLm9mZlVwZGF0ZSh0aGlzLl9yZW5kZXJDYWxsYmFjayksdGhpcy5feVNjYWxlJiZ0aGlzLl95U2NhbGUub2ZmVXBkYXRlKHRoaXMuX3JlbmRlckNhbGxiYWNrKSx0aGlzfSx0LnByb3RvdHlwZS5fc2V0dXA9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5fc2V0dXAuY2FsbCh0aGlzKSx0aGlzLl94TGluZXNDb250YWluZXI9dGhpcy5jb250ZW50KCkuYXBwZW5kKCJnIikuY2xhc3NlZCgieC1ncmlkbGluZXMiLCEwKSx0aGlzLl95TGluZXNDb250YWluZXI9dGhpcy5jb250ZW50KCkuYXBwZW5kKCJnIikuY2xhc3NlZCgieS1ncmlkbGluZXMiLCEwKX0sdC5wcm90b3R5cGUucmVuZGVySW1tZWRpYXRlbHk9ZnVuY3Rpb24oKXtyZXR1cm4gZS5wcm90b3R5cGUucmVuZGVySW1tZWRpYXRlbHkuY2FsbCh0aGlzKSx0aGlzLl9yZWRyYXdYTGluZXMoKSx0aGlzLl9yZWRyYXdZTGluZXMoKSx0aGlzfSx0LnByb3RvdHlwZS5jb21wdXRlTGF5b3V0PWZ1bmN0aW9uKHIsbixpKXtyZXR1cm4gZS5wcm90b3R5cGUuY29tcHV0ZUxheW91dC5jYWxsKHRoaXMscixuLGkpLHRoaXMuX3hTY2FsZSE9bnVsbCYmdGhpcy5feFNjYWxlLnJhbmdlKFswLHRoaXMud2lkdGgoKV0pLHRoaXMuX3lTY2FsZSE9bnVsbCYmdGhpcy5feVNjYWxlLnJhbmdlKFt0aGlzLmhlaWdodCgpLDBdKSx0aGlzfSx0LnByb3RvdHlwZS5fcmVkcmF3WExpbmVzPWZ1bmN0aW9uKCl7aWYodGhpcy5feFNjYWxlKXt2YXIgcj10aGlzLmJldHdlZW5YKCksbj10aGlzLl94U2NhbGUudGlja3MoKS5zbGljZShyPzE6MCksaT10aGlzLl94TGluZXNDb250YWluZXIuc2VsZWN0QWxsKCJsaW5lIikuZGF0YShuKSxvPWkuZW50ZXIoKS5hcHBlbmQoImxpbmUiKS5tZXJnZShpKTtvLmF0dHIoIngxIixxRih0aGlzLl94U2NhbGUscix0aGlzLl94U2NhbGUudGlja3MoKSkpLmF0dHIoInkxIiwwKS5hdHRyKCJ4MiIscUYodGhpcy5feFNjYWxlLHIsdGhpcy5feFNjYWxlLnRpY2tzKCkpKS5hdHRyKCJ5MiIsdGhpcy5oZWlnaHQoKSkuY2xhc3NlZCgiYmV0d2VlbmxpbmUiLHIpLmNsYXNzZWQoInplcm9saW5lIixmdW5jdGlvbihhKXtyZXR1cm4gYT09PTB9KSxpLmV4aXQoKS5yZW1vdmUoKX19LHQucHJvdG90eXBlLl9yZWRyYXdZTGluZXM9ZnVuY3Rpb24oKXtpZih0aGlzLl95U2NhbGUpe3ZhciByPXRoaXMuYmV0d2VlblkoKSxuPXRoaXMuX3lTY2FsZS50aWNrcygpLnNsaWNlKHI/MTowKSxpPXRoaXMuX3lMaW5lc0NvbnRhaW5lci5zZWxlY3RBbGwoImxpbmUiKS5kYXRhKG4pLG89aS5lbnRlcigpLmFwcGVuZCgibGluZSIpLm1lcmdlKGkpO28uYXR0cigieDEiLDApLmF0dHIoInkxIixxRih0aGlzLl95U2NhbGUscix0aGlzLl95U2NhbGUudGlja3MoKSkpLmF0dHIoIngyIix0aGlzLndpZHRoKCkpLmF0dHIoInkyIixxRih0aGlzLl95U2NhbGUscix0aGlzLl95U2NhbGUudGlja3MoKSkpLmNsYXNzZWQoImJldHdlZW5saW5lIixyKS5jbGFzc2VkKCJ6ZXJvbGluZSIsZnVuY3Rpb24oYSl7cmV0dXJuIGE9PT0wfSksaS5leGl0KCkucmVtb3ZlKCl9fSx0fSh4QmUuQ29tcG9uZW50KTtEb3QuR3JpZGxpbmVzPWJCZX0pO3ZhciBHRj1IKE9vdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoT290LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgd0JlPShkZSgpLFV0KHBlKSksU0JlPVlnKCksTUJlPWtjKCksRUJlPWZ1bmN0aW9uKGUpe3dCZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7dmFyIHI9ZS5jYWxsKHRoaXMpfHx0aGlzO3JldHVybiByLl9kZXRhY2hDYWxsYmFjaz1mdW5jdGlvbihuKXtyZXR1cm4gci5yZW1vdmUobil9LHJ9cmV0dXJuIHQucHJvdG90eXBlLmFuY2hvcj1mdW5jdGlvbihyKXt2YXIgbj10aGlzO3JldHVybiByPVNCZS5jb2VyY2VFeHRlcm5hbEQzKHIpLGUucHJvdG90eXBlLmFuY2hvci5jYWxsKHRoaXMsciksdGhpcy5fZm9yRWFjaChmdW5jdGlvbihpKXtyZXR1cm4gaS5hbmNob3Iobi5lbGVtZW50KCkpfSksdGhpc30sdC5wcm90b3R5cGUucmVuZGVyPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2ZvckVhY2goZnVuY3Rpb24ocil7cmV0dXJuIHIucmVuZGVyKCl9KSx0aGlzfSx0LnByb3RvdHlwZS5oYXM9ZnVuY3Rpb24ocil7dGhyb3cgbmV3IEVycm9yKCJoYXMoKSBpcyBub3QgaW1wbGVtZW50ZWQgb24gQ29tcG9uZW50Q29udGFpbmVyIil9LHQucHJvdG90eXBlLl9hZG9wdEFuZEFuY2hvcj1mdW5jdGlvbihyKXtyLnBhcmVudCh0aGlzKSxyLm9uRGV0YWNoKHRoaXMuX2RldGFjaENhbGxiYWNrKSx0aGlzLl9pc0FuY2hvcmVkJiZyLmFuY2hvcih0aGlzLmVsZW1lbnQoKSl9LHQucHJvdG90eXBlLnJlbW92ZT1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5oYXMocikmJihyLm9mZkRldGFjaCh0aGlzLl9kZXRhY2hDYWxsYmFjayksdGhpcy5fcmVtb3ZlKHIpLHIuZGV0YWNoKCksdGhpcy5yZWRyYXcoKSksdGhpc30sdC5wcm90b3R5cGUuX3JlbW92ZT1mdW5jdGlvbihyKXtyZXR1cm4hMX0sdC5wcm90b3R5cGUuX2ZvckVhY2g9ZnVuY3Rpb24ocil7dGhyb3cgbmV3IEVycm9yKCJfZm9yRWFjaCgpIGlzIG5vdCBpbXBsZW1lbnRlZCBvbiBDb21wb25lbnRDb250YWluZXIiKX0sdC5wcm90b3R5cGUuZGVzdHJveT1mdW5jdGlvbigpe2UucHJvdG90eXBlLmRlc3Ryb3kuY2FsbCh0aGlzKSx0aGlzLl9mb3JFYWNoKGZ1bmN0aW9uKHIpe3JldHVybiByLmRlc3Ryb3koKX0pfSx0LnByb3RvdHlwZS5pbnZhbGlkYXRlQ2FjaGU9ZnVuY3Rpb24oKXt0aGlzLl9mb3JFYWNoKGZ1bmN0aW9uKHIpe3JldHVybiByLmludmFsaWRhdGVDYWNoZSgpfSl9LHR9KE1CZS5Db21wb25lbnQpO09vdC5Db21wb25lbnRDb250YWluZXI9RUJlfSk7dmFyIEZvdD1IKHpvdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoem90LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgVEJlPShkZSgpLFV0KHBlKSksQyR0PUZlKCksQ0JlPUdGKCksQUJlPWZ1bmN0aW9uKGUpe1RCZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3I9PT12b2lkIDAmJihyPVtdKTt2YXIgbj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIG4uX2NvbXBvbmVudHM9W10sbi5hZGRDbGFzcygiY29tcG9uZW50LWdyb3VwIiksci5mb3JFYWNoKGZ1bmN0aW9uKGkpe3JldHVybiBuLmFwcGVuZChpKX0pLG59cmV0dXJuIHQucHJvdG90eXBlLl9mb3JFYWNoPWZ1bmN0aW9uKHIpe3RoaXMuY29tcG9uZW50cygpLmZvckVhY2gocil9LHQucHJvdG90eXBlLmhhcz1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5fY29tcG9uZW50cy5pbmRleE9mKHIpPj0wfSx0LnByb3RvdHlwZS5yZXF1ZXN0ZWRTcGFjZT1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXMuX2NvbXBvbmVudHMubWFwKGZ1bmN0aW9uKG8pe3JldHVybiBvLnJlcXVlc3RlZFNwYWNlKHIsbil9KTtyZXR1cm57bWluV2lkdGg6QyR0Lk1hdGgubWF4KGksZnVuY3Rpb24obyl7cmV0dXJuIG8ubWluV2lkdGh9LDApLG1pbkhlaWdodDpDJHQuTWF0aC5tYXgoaSxmdW5jdGlvbihvKXtyZXR1cm4gby5taW5IZWlnaHR9LDApfX0sdC5wcm90b3R5cGUuY29tcHV0ZUxheW91dD1mdW5jdGlvbihyLG4saSl7dmFyIG89dGhpcztyZXR1cm4gZS5wcm90b3R5cGUuY29tcHV0ZUxheW91dC5jYWxsKHRoaXMscixuLGkpLHRoaXMuX2ZvckVhY2goZnVuY3Rpb24oYSl7YS5jb21wdXRlTGF5b3V0KHt4OjAseTowfSxvLndpZHRoKCksby5oZWlnaHQoKSl9KSx0aGlzfSx0LnByb3RvdHlwZS5fc2l6ZUZyb21PZmZlcj1mdW5jdGlvbihyLG4pe3JldHVybnt3aWR0aDpyLGhlaWdodDpufX0sdC5wcm90b3R5cGUuZml4ZWRXaWR0aD1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9jb21wb25lbnRzLmV2ZXJ5KGZ1bmN0aW9uKHIpe3JldHVybiByLmZpeGVkV2lkdGgoKX0pfSx0LnByb3RvdHlwZS5maXhlZEhlaWdodD1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9jb21wb25lbnRzLmV2ZXJ5KGZ1bmN0aW9uKHIpe3JldHVybiByLmZpeGVkSGVpZ2h0KCl9KX0sdC5wcm90b3R5cGUuY29tcG9uZW50cz1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9jb21wb25lbnRzLnNsaWNlKCl9LHQucHJvdG90eXBlLmFwcGVuZD1mdW5jdGlvbihyKXtyZXR1cm4gciE9bnVsbCYmIXRoaXMuaGFzKHIpJiYoci5kZXRhY2goKSx0aGlzLl9jb21wb25lbnRzLnB1c2gociksdGhpcy5fYWRvcHRBbmRBbmNob3IociksdGhpcy5yZWRyYXcoKSksdGhpc30sdC5wcm90b3R5cGUuX3JlbW92ZT1mdW5jdGlvbihyKXt2YXIgbj10aGlzLl9jb21wb25lbnRzLmluZGV4T2Yocik7cmV0dXJuIG4+PTA/KHRoaXMuX2NvbXBvbmVudHMuc3BsaWNlKG4sMSksITApOiExfSx0fShDQmUuQ29tcG9uZW50Q29udGFpbmVyKTt6b3QuR3JvdXA9QUJlfSk7dmFyIEEkdD1IKEJvdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoQm90LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgUEJlPShkZSgpLFV0KHBlKSksV0Y9X2woKSxJQmU9WEEoKSxMQmU9QnUoKSxrQmU9RmUoKSxSQmU9a2MoKSxOQmU9ZnVuY3Rpb24oZSl7UEJlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQocil7dmFyIG49ZS5jYWxsKHRoaXMpfHx0aGlzO2lmKG4uX3RleHRQYWRkaW5nPTUscj09bnVsbCl0aHJvdyBuZXcgRXJyb3IoIkludGVycG9sYXRlZENvbG9yTGVnZW5kIHJlcXVpcmVzIGEgaW50ZXJwb2xhdGVkQ29sb3JTY2FsZSIpO3JldHVybiBuLl9zY2FsZT1yLG4uX3JlZHJhd0NhbGxiYWNrPWZ1bmN0aW9uKGkpe3JldHVybiBuLnJlZHJhdygpfSxuLl9zY2FsZS5vblVwZGF0ZShuLl9yZWRyYXdDYWxsYmFjayksbi5fZm9ybWF0dGVyPUxCZS5nZW5lcmFsKCksbi5fb3JpZW50YXRpb249Imhvcml6b250YWwiLG4uX2V4cGFuZHM9ITEsbi5hZGRDbGFzcygibGVnZW5kIiksbi5hZGRDbGFzcygiaW50ZXJwb2xhdGVkLWNvbG9yLWxlZ2VuZCIpLG59cmV0dXJuIHQucHJvdG90eXBlLmRlc3Ryb3k9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5kZXN0cm95LmNhbGwodGhpcyksdGhpcy5fc2NhbGUub2ZmVXBkYXRlKHRoaXMuX3JlZHJhd0NhbGxiYWNrKX0sdC5wcm90b3R5cGUuZm9ybWF0dGVyPWZ1bmN0aW9uKHIpe3JldHVybiByPT09dm9pZCAwP3RoaXMuX2Zvcm1hdHRlcjoodGhpcy5fZm9ybWF0dGVyPXIsdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5wcm90b3R5cGUuZXhwYW5kcz1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9leHBhbmRzOih0aGlzLl9leHBhbmRzPXIsdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5fZW5zdXJlT3JpZW50YXRpb249ZnVuY3Rpb24ocil7aWYocj1yLnRvTG93ZXJDYXNlKCkscj09PSJob3Jpem9udGFsInx8cj09PSJsZWZ0Inx8cj09PSJyaWdodCIpcmV0dXJuIHI7dGhyb3cgbmV3IEVycm9yKCciJytyKyciIGlzIG5vdCBhIHZhbGlkIG9yaWVudGF0aW9uIGZvciBJbnRlcnBvbGF0ZWRDb2xvckxlZ2VuZCcpfSx0LnByb3RvdHlwZS5vcmllbnRhdGlvbj1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9vcmllbnRhdGlvbjoodGhpcy5fb3JpZW50YXRpb249dC5fZW5zdXJlT3JpZW50YXRpb24ociksdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5wcm90b3R5cGUuZml4ZWRXaWR0aD1mdW5jdGlvbigpe3JldHVybiF0aGlzLmV4cGFuZHMoKXx8dGhpcy5faXNWZXJ0aWNhbCgpfSx0LnByb3RvdHlwZS5maXhlZEhlaWdodD1mdW5jdGlvbigpe3JldHVybiF0aGlzLmV4cGFuZHMoKXx8IXRoaXMuX2lzVmVydGljYWwoKX0sdC5wcm90b3R5cGUuX2dlbmVyYXRlVGlja3M9ZnVuY3Rpb24ocil7cj09PXZvaWQgMCYmKHI9dC5fREVGQVVMVF9OVU1fU1dBVENIRVMpO3ZhciBuPXRoaXMuX3NjYWxlLmRvbWFpbigpO2lmKHI9PT0xKXJldHVybltuWzBdXTtmb3IodmFyIGk9KG5bMV0tblswXSkvKHItMSksbz1bXSxhPTA7YTxyO2ErKylvLnB1c2goblswXStpKmEpO3JldHVybiBvfSx0LnByb3RvdHlwZS5fc2V0dXA9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5fc2V0dXAuY2FsbCh0aGlzKSx0aGlzLl9zd2F0Y2hDb250YWluZXI9dGhpcy5jb250ZW50KCkuYXBwZW5kKCJnIikuY2xhc3NlZCgic3dhdGNoLWNvbnRhaW5lciIsITApLHRoaXMuX3N3YXRjaEJvdW5kaW5nQm94PXRoaXMuY29udGVudCgpLmFwcGVuZCgicmVjdCIpLmNsYXNzZWQoInN3YXRjaC1ib3VuZGluZy1ib3giLCEwKSx0aGlzLl9sb3dlckxhYmVsPXRoaXMuY29udGVudCgpLmFwcGVuZCgiZyIpLmNsYXNzZWQodC5MRUdFTkRfTEFCRUxfQ0xBU1MsITApLHRoaXMuX3VwcGVyTGFiZWw9dGhpcy5jb250ZW50KCkuYXBwZW5kKCJnIikuY2xhc3NlZCh0LkxFR0VORF9MQUJFTF9DTEFTUywhMCk7dmFyIHI9bmV3IFdGLlN2Z0NvbnRleHQodGhpcy5jb250ZW50KCkubm9kZSgpKTt0aGlzLl9tZWFzdXJlcj1uZXcgV0YuTWVhc3VyZXIociksdGhpcy5fd3JhcHBlcj1uZXcgV0YuV3JhcHBlcix0aGlzLl93cml0ZXI9bmV3IFdGLldyaXRlcih0aGlzLl9tZWFzdXJlcixyLHRoaXMuX3dyYXBwZXIpfSx0LnByb3RvdHlwZS5yZXF1ZXN0ZWRTcGFjZT1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXMsbz10aGlzLl9tZWFzdXJlci5tZWFzdXJlKCkuaGVpZ2h0LGE9byxzPXRoaXMuX3NjYWxlLmRvbWFpbigpLGw9cy5tYXAoZnVuY3Rpb24ocCl7cmV0dXJuIGkuX21lYXN1cmVyLm1lYXN1cmUoaS5fZm9ybWF0dGVyKHApKS53aWR0aH0pLGMsdSxoPXQuX0RFRkFVTFRfTlVNX1NXQVRDSEVTO2lmKHRoaXMuX2lzVmVydGljYWwoKSl7dmFyIGY9a0JlLk1hdGgubWF4KGwsMCk7dT1hK28rdGhpcy5fdGV4dFBhZGRpbmcrZit0aGlzLl90ZXh0UGFkZGluZyxjPWgqb31lbHNlIGM9YStvK2EsdT10aGlzLl90ZXh0UGFkZGluZytsWzBdK2gqbytsWzFdK3RoaXMuX3RleHRQYWRkaW5nO3JldHVybnttaW5XaWR0aDp1LG1pbkhlaWdodDpjfX0sdC5wcm90b3R5cGUuX2lzVmVydGljYWw9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fb3JpZW50YXRpb24hPT0iaG9yaXpvbnRhbCJ9LHQucHJvdG90eXBlLnJlbmRlckltbWVkaWF0ZWx5PWZ1bmN0aW9uKCl7dmFyIHI9dGhpcztlLnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseS5jYWxsKHRoaXMpO3ZhciBuPXRoaXMuX3NjYWxlLmRvbWFpbigpLGk9dGhpcy5fZm9ybWF0dGVyKG5bMF0pLG89dGhpcy5fbWVhc3VyZXIubWVhc3VyZShpKS53aWR0aCxhPXRoaXMuX2Zvcm1hdHRlcihuWzFdKSxzPXRoaXMuX21lYXN1cmVyLm1lYXN1cmUoYSkud2lkdGgsbD10aGlzLl9tZWFzdXJlci5tZWFzdXJlKCkuaGVpZ2h0LGM9dGhpcy5fdGV4dFBhZGRpbmcsdT17eDowLHk6MH0saD17eDowLHk6MH0sZj17eEFsaWduOiJjZW50ZXIiLHlBbGlnbjoiY2VudGVyIix0ZXh0Um90YXRpb246MH0scD17eEFsaWduOiJjZW50ZXIiLHlBbGlnbjoiY2VudGVyIix0ZXh0Um90YXRpb246MH0sZCxnLF8seSx4PXt4OjAseTowLHdpZHRoOjAsaGVpZ2h0OjB9LGIsUztpZih0aGlzLl9pc1ZlcnRpY2FsKCkpe1M9TWF0aC5mbG9vcih0aGlzLmhlaWdodCgpKTt2YXIgQz1NYXRoLm1heChvLHMpO2I9KHRoaXMud2lkdGgoKS1DLTIqdGhpcy5fdGV4dFBhZGRpbmcpLzIsZD1NYXRoLm1heCh0aGlzLndpZHRoKCktYi0yKmMtQywwKSxnPTEseT1mdW5jdGlvbihMLFIpe3JldHVybiByLmhlaWdodCgpLShSKzEpfSxwLnlBbGlnbj0idG9wIix1Lnk9MCxmLnlBbGlnbj0iYm90dG9tIixoLnk9MCx0aGlzLl9vcmllbnRhdGlvbj09PSJsZWZ0Ij8oXz1mdW5jdGlvbihMLFIpe3JldHVybiBjK0MrY30scC54QWxpZ249InJpZ2h0Iix1Lng9LShiK2QrYyksZi54QWxpZ249InJpZ2h0IixoLng9LShiK2QrYykpOihfPWZ1bmN0aW9uKEwsUil7cmV0dXJuIGJ9LHAueEFsaWduPSJsZWZ0Iix1Lng9YitkK2MsZi54QWxpZ249ImxlZnQiLGgueD1iK2QrYykseC53aWR0aD1kLHguaGVpZ2h0PVMqZ31lbHNlIGI9TWF0aC5tYXgoYywodGhpcy5oZWlnaHQoKS1sKS8yKSxTPU1hdGgubWF4KE1hdGguZmxvb3IodGhpcy53aWR0aCgpLWMqNC1vLXMpLDApLGQ9MSxnPU1hdGgubWF4KHRoaXMuaGVpZ2h0KCktMipiLDApLF89ZnVuY3Rpb24oTCxSKXtyZXR1cm4gTWF0aC5mbG9vcihvKzIqYykrUn0seT1mdW5jdGlvbihMLFIpe3JldHVybiBifSxwLnhBbGlnbj0icmlnaHQiLHUueD0tYyxmLnhBbGlnbj0ibGVmdCIsaC54PWMseC55PWIseC53aWR0aD1TKmQseC5oZWlnaHQ9Zzt4Lng9XyhudWxsLDApLHRoaXMuX3VwcGVyTGFiZWwudGV4dCgiIiksdGhpcy5fd3JpdGVyLndyaXRlKGEsdGhpcy53aWR0aCgpLHRoaXMuaGVpZ2h0KCkscCx0aGlzLl91cHBlckxhYmVsLm5vZGUoKSk7dmFyIFA9InRyYW5zbGF0ZSgiK3UueCsiLCAiK3UueSsiKSI7dGhpcy5fdXBwZXJMYWJlbC5hdHRyKCJ0cmFuc2Zvcm0iLFApLHRoaXMuX2xvd2VyTGFiZWwudGV4dCgiIiksdGhpcy5fd3JpdGVyLndyaXRlKGksdGhpcy53aWR0aCgpLHRoaXMuaGVpZ2h0KCksZix0aGlzLl9sb3dlckxhYmVsLm5vZGUoKSk7dmFyIGs9InRyYW5zbGF0ZSgiK2gueCsiLCAiK2gueSsiKSI7dGhpcy5fbG93ZXJMYWJlbC5hdHRyKCJ0cmFuc2Zvcm0iLGspLHRoaXMuX3N3YXRjaEJvdW5kaW5nQm94LmF0dHJzKHgpO3ZhciBPPXRoaXMuX2dlbmVyYXRlVGlja3MoUyksRD10aGlzLl9zd2F0Y2hDb250YWluZXIuc2VsZWN0QWxsKCJyZWN0LnN3YXRjaCIpLmRhdGEoTyksQj1ELmVudGVyKCkuYXBwZW5kKCJyZWN0IikuY2xhc3NlZCgic3dhdGNoIiwhMCksST1ELm1lcmdlKEIpO3JldHVybiBELmV4aXQoKS5yZW1vdmUoKSxJLmF0dHJzKHtmaWxsOmZ1bmN0aW9uKEwsUil7cmV0dXJuIHIuX3NjYWxlLnNjYWxlKEwpfSx3aWR0aDpkLGhlaWdodDpnLHg6Xyx5LCJzaGFwZS1yZW5kZXJpbmciOiJjcmlzcEVkZ2VzIn0pLElCZS5BRERfVElUTEVfRUxFTUVOVFMmJkIuYXBwZW5kKCJ0aXRsZSIpLnRleHQoZnVuY3Rpb24oTCl7cmV0dXJuIHIuX2Zvcm1hdHRlcihMKX0pLHRoaXN9LHQuX0RFRkFVTFRfTlVNX1NXQVRDSEVTPTExLHQuTEVHRU5EX0xBQkVMX0NMQVNTPSJsZWdlbmQtbGFiZWwiLHR9KFJCZS5Db21wb25lbnQpO0JvdC5JbnRlcnBvbGF0ZWRDb2xvckxlZ2VuZD1OQmV9KTt2YXIgVW90PUgodTQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHU0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgSG90PShkZSgpLFV0KHBlKSksWUY9X2woKSxEQmU9a2MoKSxWb3Q9ZnVuY3Rpb24oZSl7SG90Ll9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQocixuKXtyPT09dm9pZCAwJiYocj0iIiksbj09PXZvaWQgMCYmKG49MCk7dmFyIGk9ZS5jYWxsKHRoaXMpfHx0aGlzO3JldHVybiBpLmFkZENsYXNzKCJsYWJlbCIpLGkudGV4dChyKSxpLmFuZ2xlKG4pLGkueEFsaWdubWVudCgiY2VudGVyIikueUFsaWdubWVudCgiY2VudGVyIiksaS5fcGFkZGluZz0wLGl9cmV0dXJuIHQucHJvdG90eXBlLnJlcXVlc3RlZFNwYWNlPWZ1bmN0aW9uKHIsbil7dmFyIGk9dGhpcy5fbWVhc3VyZXIubWVhc3VyZSh0aGlzLl90ZXh0KSxvPSh0aGlzLmFuZ2xlKCk9PT0wP2kud2lkdGg6aS5oZWlnaHQpKzIqdGhpcy5wYWRkaW5nKCksYT0odGhpcy5hbmdsZSgpPT09MD9pLmhlaWdodDppLndpZHRoKSsyKnRoaXMucGFkZGluZygpO3JldHVybnttaW5XaWR0aDpvLG1pbkhlaWdodDphfX0sdC5wcm90b3R5cGUuX3NldHVwPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX3NldHVwLmNhbGwodGhpcyksdGhpcy5fdGV4dENvbnRhaW5lcj10aGlzLmNvbnRlbnQoKS5hcHBlbmQoImciKTt2YXIgcj1uZXcgWUYuU3ZnQ29udGV4dCh0aGlzLl90ZXh0Q29udGFpbmVyLm5vZGUoKSk7dGhpcy5fbWVhc3VyZXI9bmV3IFlGLkNhY2hlTWVhc3VyZXIociksdGhpcy5fd3JhcHBlcj1uZXcgWUYuV3JhcHBlcix0aGlzLl93cml0ZXI9bmV3IFlGLldyaXRlcih0aGlzLl9tZWFzdXJlcixyLHRoaXMuX3dyYXBwZXIpLHRoaXMudGV4dCh0aGlzLl90ZXh0KX0sdC5wcm90b3R5cGUudGV4dD1mdW5jdGlvbihyKXtpZihyPT1udWxsKXJldHVybiB0aGlzLl90ZXh0O2lmKHR5cGVvZiByIT0ic3RyaW5nIil0aHJvdyBuZXcgRXJyb3IoIkxhYmVsLnRleHQoKSBvbmx5IHRha2VzIHN0cmluZ3MgYXMgaW5wdXQiKTtyZXR1cm4gdGhpcy5fdGV4dD1yLHRoaXMucmVkcmF3KCksdGhpc30sdC5wcm90b3R5cGUuYW5nbGU9ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fYW5nbGU7aWYociU9MzYwLHI+MTgwP3ItPTM2MDpyPC0xODAmJihyKz0zNjApLHI9PT0tOTB8fHI9PT0wfHxyPT09OTApdGhpcy5fYW5nbGU9cjtlbHNlIHRocm93IG5ldyBFcnJvcihyKyIgaXMgbm90IGEgdmFsaWQgYW5nbGUgZm9yIExhYmVsIik7cmV0dXJuIHRoaXMucmVkcmF3KCksdGhpc30sdC5wcm90b3R5cGUucGFkZGluZz1mdW5jdGlvbihyKXtpZihyPT1udWxsKXJldHVybiB0aGlzLl9wYWRkaW5nO2lmKHI9K3IscjwwKXRocm93IG5ldyBFcnJvcihyKyIgaXMgbm90IGEgdmFsaWQgcGFkZGluZyB2YWx1ZS4gQ2Fubm90IGJlIGxlc3MgdGhhbiAwLiIpO3JldHVybiB0aGlzLl9wYWRkaW5nPXIsdGhpcy5yZWRyYXcoKSx0aGlzfSx0LnByb3RvdHlwZS5maXhlZFdpZHRoPWZ1bmN0aW9uKCl7cmV0dXJuITB9LHQucHJvdG90eXBlLmZpeGVkSGVpZ2h0PWZ1bmN0aW9uKCl7cmV0dXJuITB9LHQucHJvdG90eXBlLnJlbmRlckltbWVkaWF0ZWx5PWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUucmVuZGVySW1tZWRpYXRlbHkuY2FsbCh0aGlzKSx0aGlzLl90ZXh0Q29udGFpbmVyLnNlbGVjdEFsbCgiZyIpLnJlbW92ZSgpO3ZhciByPXRoaXMuX21lYXN1cmVyLm1lYXN1cmUodGhpcy5fdGV4dCksbj1NYXRoLm1heChNYXRoLm1pbigodGhpcy5oZWlnaHQoKS1yLmhlaWdodCkvMix0aGlzLnBhZGRpbmcoKSksMCksaT1NYXRoLm1heChNYXRoLm1pbigodGhpcy53aWR0aCgpLXIud2lkdGgpLzIsdGhpcy5wYWRkaW5nKCkpLDApO3RoaXMuX3RleHRDb250YWluZXIuYXR0cigidHJhbnNmb3JtIiwidHJhbnNsYXRlKCIraSsiLCIrbisiKSIpO3ZhciBvPXRoaXMud2lkdGgoKS0yKmksYT10aGlzLmhlaWdodCgpLTIqbixzPXt4QWxpZ246dGhpcy54QWxpZ25tZW50KCkseUFsaWduOnRoaXMueUFsaWdubWVudCgpLHRleHRSb3RhdGlvbjp0aGlzLmFuZ2xlKCl9O3JldHVybiB0aGlzLl93cml0ZXIud3JpdGUodGhpcy5fdGV4dCxvLGEscyksdGhpc30sdC5wcm90b3R5cGUuaW52YWxpZGF0ZUNhY2hlPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuaW52YWxpZGF0ZUNhY2hlLmNhbGwodGhpcyksdGhpcy5fbWVhc3VyZXIucmVzZXQoKX0sdH0oREJlLkNvbXBvbmVudCk7dTQuTGFiZWw9Vm90O3ZhciBPQmU9ZnVuY3Rpb24oZSl7SG90Ll9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQocixuKXt2YXIgaT1lLmNhbGwodGhpcyxyLG4pfHx0aGlzO3JldHVybiBpLmFkZENsYXNzKHQuVElUTEVfTEFCRUxfQ0xBU1MpLGl9cmV0dXJuIHQuVElUTEVfTEFCRUxfQ0xBU1M9InRpdGxlLWxhYmVsIix0fShWb3QpO3U0LlRpdGxlTGFiZWw9T0JlO3ZhciB6QmU9ZnVuY3Rpb24oZSl7SG90Ll9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQocixuKXt2YXIgaT1lLmNhbGwodGhpcyxyLG4pfHx0aGlzO3JldHVybiBpLmFkZENsYXNzKHQuQVhJU19MQUJFTF9DTEFTUyksaX1yZXR1cm4gdC5BWElTX0xBQkVMX0NMQVNTPSJheGlzLWxhYmVsIix0fShWb3QpO3U0LkF4aXNMYWJlbD16QmV9KTt2YXIgakY9SChiZD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoYmQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBScz0oRXIoKSxVdChNcikpO2Z1bmN0aW9uIEZCZSgpe3JldHVybiBmdW5jdGlvbihlKXtyZXR1cm4gUnMuc3ltYm9sKCkudHlwZShScy5zeW1ib2xDaXJjbGUpLnNpemUoTWF0aC5QSSpNYXRoLnBvdyhlLzIsMikpfX1iZC5jaXJjbGU9RkJlO2Z1bmN0aW9uIEJCZSgpe3JldHVybiBmdW5jdGlvbihlKXtyZXR1cm4gUnMuc3ltYm9sKCkudHlwZShScy5zeW1ib2xTcXVhcmUpLnNpemUoTWF0aC5wb3coZSwyKSl9fWJkLnNxdWFyZT1CQmU7ZnVuY3Rpb24gSEJlKCl7cmV0dXJuIGZ1bmN0aW9uKGUpe3JldHVybiBScy5zeW1ib2woKS50eXBlKFJzLnN5bWJvbENyb3NzKS5zaXplKDUvOSpNYXRoLnBvdyhlLDIpKX19YmQuY3Jvc3M9SEJlO2Z1bmN0aW9uIFZCZSgpe3JldHVybiBmdW5jdGlvbihlKXtyZXR1cm4gUnMuc3ltYm9sKCkudHlwZShScy5zeW1ib2xEaWFtb25kKS5zaXplKE1hdGgudGFuKE1hdGguUEkvNikqTWF0aC5wb3coZSwyKS8yKX19YmQuZGlhbW9uZD1WQmU7ZnVuY3Rpb24gVUJlKCl7cmV0dXJuIGZ1bmN0aW9uKGUpe3JldHVybiBScy5zeW1ib2woKS50eXBlKFJzLnN5bWJvbFRyaWFuZ2xlKS5zaXplKE1hdGguc3FydCgzKSpNYXRoLnBvdyhlLzIsMikpfX1iZC50cmlhbmdsZT1VQmU7dmFyIHFCZT0uODkwODEzMDkxNTI5Mjg1MjtmdW5jdGlvbiBHQmUoKXtyZXR1cm4gZnVuY3Rpb24oZSl7cmV0dXJuIFJzLnN5bWJvbCgpLnR5cGUoUnMuc3ltYm9sU3Rhcikuc2l6ZShxQmUqTWF0aC5wb3coZS8yLDIpKX19YmQuc3Rhcj1HQmU7dmFyIFdCZT0oMS9NYXRoLnNxcnQoMTIpLzIrMSkqMztmdW5jdGlvbiBZQmUoKXtyZXR1cm4gZnVuY3Rpb24oZSl7cmV0dXJuIFJzLnN5bWJvbCgpLnR5cGUoUnMuc3ltYm9sV3llKS5zaXplKFdCZSpNYXRoLnBvdyhlLzIuNCwyKSl9fWJkLnd5ZT1ZQmV9KTt2YXIgUCR0PUgoR290PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShHb3QsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBqQmU9KGRlKCksVXQocGUpKSx6MT0oRXIoKSxVdChNcikpLFhGPV9sKCksWEJlPVhBKCksJEJlPUJ1KCksS0JlPWpGKCksSmc9RmUoKSxaQmU9a2MoKSxxb3Q9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKHQscixuKXt0PT09dm9pZCAwJiYodD1bXSkscj09PXZvaWQgMCYmKHI9MCksbj09PXZvaWQgMCYmKG49MS8wKSx0aGlzLmNvbHVtbnM9dCx0aGlzLmJvdHRvbVBhZGRpbmc9cix0aGlzLm1heFdpZHRoPW59cmV0dXJuIGUucHJvdG90eXBlLmFkZENvbHVtbj1mdW5jdGlvbih0KXt2YXIgcj10LndpZHRoLG49dGhpcy5nZXRXaWR0aEF2YWlsYWJsZSgpO3Qud2lkdGg9TWF0aC5taW4obixyKSx0aGlzLmNvbHVtbnMucHVzaCh0KX0sZS5wcm90b3R5cGUuZ2V0Qm91bmRzPWZ1bmN0aW9uKHQpe2Zvcih2YXIgcj10aGlzLmNvbHVtbnNbdF0sbj0wLGk9MDtpPHQ7aSsrKW4rPXRoaXMuY29sdW1uc1tpXS53aWR0aDtyZXR1cm57dG9wTGVmdDp7eDpuLHk6MH0sYm90dG9tUmlnaHQ6e3g6bityLndpZHRoLHk6ci5oZWlnaHR9fX0sZS5wcm90b3R5cGUuZ2V0SGVpZ2h0PWZ1bmN0aW9uKCl7cmV0dXJuIEpnLk1hdGgubWF4KHRoaXMuY29sdW1ucy5tYXAoZnVuY3Rpb24odCl7dmFyIHI9dC5oZWlnaHQ7cmV0dXJuIHJ9KSwwKSt0aGlzLmJvdHRvbVBhZGRpbmd9LGUucHJvdG90eXBlLmdldFdpZHRoPWZ1bmN0aW9uKCl7cmV0dXJuIE1hdGgubWluKHRoaXMuY29sdW1ucy5yZWR1Y2UoZnVuY3Rpb24odCxyKXt2YXIgbj1yLndpZHRoO3JldHVybiB0K259LDApLHRoaXMubWF4V2lkdGgpfSxlLnByb3RvdHlwZS5nZXRXaWR0aEF2YWlsYWJsZT1mdW5jdGlvbigpe3ZhciB0PXRoaXMuZ2V0V2lkdGgoKTtyZXR1cm4gTWF0aC5tYXgodGhpcy5tYXhXaWR0aC10LDApfSxlfSgpLEpCZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUodCxyLG4saSl7dD09PXZvaWQgMCYmKHQ9MS8wKSxyPT09dm9pZCAwJiYocj0xLzApLG49PT12b2lkIDAmJihuPTApLGk9PT12b2lkIDAmJihpPVtdKSx0aGlzLm1heFdpZHRoPXQsdGhpcy5tYXhIZWlnaHQ9cix0aGlzLnBhZGRpbmc9bix0aGlzLnJvd3M9aX1yZXR1cm4gZS5wcm90b3R5cGUuYWRkUm93PWZ1bmN0aW9uKHQpe3QubWF4V2lkdGg9dGhpcy5tYXhXaWR0aC10aGlzLnBhZGRpbmcqMix0aGlzLnJvd3MucHVzaCh0KX0sZS5wcm90b3R5cGUuZ2V0Q29sdW1uQm91bmRzPWZ1bmN0aW9uKHQscil7dmFyIG49dGhpcy5nZXRSb3dCb3VuZHModCksaT10aGlzLnJvd3NbdF0uZ2V0Qm91bmRzKHIpO3JldHVybiBpLnRvcExlZnQueCs9bi50b3BMZWZ0LngsaS5ib3R0b21SaWdodC54Kz1uLnRvcExlZnQueCxpLnRvcExlZnQueSs9bi50b3BMZWZ0LnksaS5ib3R0b21SaWdodC55Kz1uLnRvcExlZnQueSxpfSxlLnByb3RvdHlwZS5nZXRSb3dCb3VuZHM9ZnVuY3Rpb24odCl7Zm9yKHZhciByPXRoaXMucGFkZGluZyxuPXRoaXMucGFkZGluZyxpPTA7aTx0O2krKyluKz10aGlzLnJvd3NbaV0uZ2V0SGVpZ2h0KCk7dmFyIG89e3RvcExlZnQ6e3g6cix5Om59LGJvdHRvbVJpZ2h0Ont4OnIrdGhpcy5yb3dzW3RdLmdldFdpZHRoKCkseTpuK3RoaXMucm93c1t0XS5nZXRIZWlnaHQoKX19O3JldHVybiBvfSxlLnByb3RvdHlwZS5nZXRIZWlnaHQ9ZnVuY3Rpb24oKXtyZXR1cm4gTWF0aC5taW4odGhpcy5yb3dzLnJlZHVjZShmdW5jdGlvbih0LHIpe3JldHVybiB0K3IuZ2V0SGVpZ2h0KCl9LDApK3RoaXMucGFkZGluZyoyLHRoaXMubWF4SGVpZ2h0KX0sZS5wcm90b3R5cGUuZ2V0V2lkdGg9ZnVuY3Rpb24oKXtyZXR1cm4gTWF0aC5taW4oSmcuTWF0aC5tYXgodGhpcy5yb3dzLm1hcChmdW5jdGlvbih0KXtyZXR1cm4gdC5nZXRXaWR0aCgpfSksMCkrdGhpcy5wYWRkaW5nKjIsdGhpcy5tYXhXaWR0aCl9LGV9KCksUUJlPWZ1bmN0aW9uKGUpe2pCZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3ZhciBuPWUuY2FsbCh0aGlzKXx8dGhpcztpZihuLl9wYWRkaW5nPTUsbi5fcm93Qm90dG9tUGFkZGluZz0zLG4uYWRkQ2xhc3MoImxlZ2VuZCIpLG4ubWF4RW50cmllc1BlclJvdygxKSxyPT1udWxsKXRocm93IG5ldyBFcnJvcigiTGVnZW5kIHJlcXVpcmVzIGEgY29sb3JTY2FsZSIpO3JldHVybiBuLl9jb2xvclNjYWxlPXIsbi5fcmVkcmF3Q2FsbGJhY2s9ZnVuY3Rpb24oaSl7cmV0dXJuIG4ucmVkcmF3KCl9LG4uX2NvbG9yU2NhbGUub25VcGRhdGUobi5fcmVkcmF3Q2FsbGJhY2spLG4uX2Zvcm1hdHRlcj0kQmUuaWRlbnRpdHkoKSxuLm1heExpbmVzUGVyRW50cnkoMSksbi54QWxpZ25tZW50KCJyaWdodCIpLnlBbGlnbm1lbnQoInRvcCIpLG4uY29tcGFyYXRvcihmdW5jdGlvbihpLG8pe3ZhciBhPW4uX2NvbG9yU2NhbGUuZG9tYWluKCkuc2xpY2UoKS5tYXAoZnVuY3Rpb24ocyl7cmV0dXJuIG4uX2Zvcm1hdHRlcihzKX0pO3JldHVybiBhLmluZGV4T2YoaSktYS5pbmRleE9mKG8pfSksbi5fc3ltYm9sRmFjdG9yeUFjY2Vzc29yPWZ1bmN0aW9uKCl7cmV0dXJuIEtCZS5jaXJjbGUoKX0sbi5fc3ltYm9sT3BhY2l0eUFjY2Vzc29yPWZ1bmN0aW9uKCl7cmV0dXJuIDF9LG59cmV0dXJuIHQucHJvdG90eXBlLl9zZXR1cD1mdW5jdGlvbigpe2UucHJvdG90eXBlLl9zZXR1cC5jYWxsKHRoaXMpO3ZhciByPXRoaXMuY29udGVudCgpLmFwcGVuZCgiZyIpLmNsYXNzZWQodC5MRUdFTkRfUk9XX0NMQVNTLCEwKSxuPXIuYXBwZW5kKCJnIikuY2xhc3NlZCh0LkxFR0VORF9FTlRSWV9DTEFTUywhMCk7bi5hcHBlbmQoInRleHQiKTt2YXIgaT1uZXcgWEYuU3ZnQ29udGV4dChyLm5vZGUoKSxudWxsLFhCZS5BRERfVElUTEVfRUxFTUVOVFMpO3RoaXMuX21lYXN1cmVyPW5ldyBYRi5DYWNoZU1lYXN1cmVyKGkpLHRoaXMuX3dyYXBwZXI9bmV3IFhGLldyYXBwZXIoKS5tYXhMaW5lcyh0aGlzLm1heExpbmVzUGVyRW50cnkoKSksdGhpcy5fd3JpdGVyPW5ldyBYRi5Xcml0ZXIodGhpcy5fbWVhc3VyZXIsaSx0aGlzLl93cmFwcGVyKX0sdC5wcm90b3R5cGUuZm9ybWF0dGVyPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2Zvcm1hdHRlcjoodGhpcy5fZm9ybWF0dGVyPXIsdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5wcm90b3R5cGUubWF4RW50cmllc1BlclJvdz1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9tYXhFbnRyaWVzUGVyUm93Oih0aGlzLl9tYXhFbnRyaWVzUGVyUm93PXIsdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5wcm90b3R5cGUubWF4TGluZXNQZXJFbnRyeT1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9tYXhMaW5lc1BlckVudHJ5Oih0aGlzLl9tYXhMaW5lc1BlckVudHJ5PXIsdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5wcm90b3R5cGUubWF4V2lkdGg9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fbWF4V2lkdGg6KHRoaXMuX21heFdpZHRoPXIsdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5wcm90b3R5cGUuY29tcGFyYXRvcj1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9jb21wYXJhdG9yOih0aGlzLl9jb21wYXJhdG9yPXIsdGhpcy5yZWRyYXcoKSx0aGlzKX0sdC5wcm90b3R5cGUuY29sb3JTY2FsZT1mdW5jdGlvbihyKXtyZXR1cm4gciE9bnVsbD8odGhpcy5fY29sb3JTY2FsZS5vZmZVcGRhdGUodGhpcy5fcmVkcmF3Q2FsbGJhY2spLHRoaXMuX2NvbG9yU2NhbGU9cix0aGlzLl9jb2xvclNjYWxlLm9uVXBkYXRlKHRoaXMuX3JlZHJhd0NhbGxiYWNrKSx0aGlzLnJlZHJhdygpLHRoaXMpOnRoaXMuX2NvbG9yU2NhbGV9LHQucHJvdG90eXBlLmRlc3Ryb3k9ZnVuY3Rpb24oKXtlLnByb3RvdHlwZS5kZXN0cm95LmNhbGwodGhpcyksdGhpcy5fY29sb3JTY2FsZS5vZmZVcGRhdGUodGhpcy5fcmVkcmF3Q2FsbGJhY2spfSx0LnByb3RvdHlwZS5fYnVpbGRMZWdlbmRUYWJsZT1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXMsbz10aGlzLl9tZWFzdXJlci5tZWFzdXJlKCkuaGVpZ2h0LGE9bmV3IEpCZShyLG4sdGhpcy5fcGFkZGluZykscz10aGlzLl9jb2xvclNjYWxlLmRvbWFpbigpLnNsaWNlKCkuc29ydChmdW5jdGlvbihjLHUpe3JldHVybiBpLl9jb21wYXJhdG9yKGkuX2Zvcm1hdHRlcihjKSxpLl9mb3JtYXR0ZXIodSkpfSksbD1uZXcgcW90O3JldHVybiBhLmFkZFJvdyhsKSxsLmJvdHRvbVBhZGRpbmc9dGhpcy5fcm93Qm90dG9tUGFkZGluZyxzLmZvckVhY2goZnVuY3Rpb24oYyx1KXtsLmNvbHVtbnMubGVuZ3RoLzI9PT1pLm1heEVudHJpZXNQZXJSb3coKSYmKGw9bmV3IHFvdCxsLmJvdHRvbVBhZGRpbmc9aS5fcm93Qm90dG9tUGFkZGluZyxhLmFkZFJvdyhsKSk7dmFyIGg9bC5nZXRXaWR0aEF2YWlsYWJsZSgpLGY9aS5fZm9ybWF0dGVyKGMpLHA9aS5fbWVhc3VyZXIubWVhc3VyZShmKS53aWR0aCxkPWgtby1wPDA7ZCYmbC5jb2x1bW5zLmxlbmd0aD4xJiYobD1uZXcgcW90LGwuYm90dG9tUGFkZGluZz1pLl9yb3dCb3R0b21QYWRkaW5nLGEuYWRkUm93KGwpKTt2YXIgZz17d2lkdGg6byxoZWlnaHQ6byxkYXRhOntuYW1lOmMsdHlwZToic3ltYm9sIn19O2wuYWRkQ29sdW1uKGcpLGg9bC5nZXRXaWR0aEF2YWlsYWJsZSgpO3ZhciBfPU1hdGgubWluKGgscCk7aS5fd3JhcHBlci5tYXhMaW5lcyhpLm1heExpbmVzUGVyRW50cnkoKSk7dmFyIHk9aS5fd3JhcHBlci53cmFwKGYsaS5fbWVhc3VyZXIsXykubm9MaW5lcyx4PXkqbyxiPXt3aWR0aDpfLGhlaWdodDp4LGRhdGE6e25hbWU6Yyx0eXBlOiJ0ZXh0In19O2wuYWRkQ29sdW1uKGIpfSksYX0sdC5wcm90b3R5cGUucmVxdWVzdGVkU3BhY2U9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzLl9idWlsZExlZ2VuZFRhYmxlKEpnLk1hdGgubWluKFt0aGlzLm1heFdpZHRoKCkscl0sciksbik7cmV0dXJue21pbkhlaWdodDppLmdldEhlaWdodCgpLG1pbldpZHRoOmkuZ2V0V2lkdGgoKX19LHQucHJvdG90eXBlLmVudGl0aWVzQXQ9ZnVuY3Rpb24ocil7dmFyIG49dGhpcztpZighdGhpcy5faXNTZXR1cClyZXR1cm5bXTt2YXIgaT10aGlzLl9idWlsZExlZ2VuZFRhYmxlKHRoaXMud2lkdGgoKSx0aGlzLmhlaWdodCgpKTtyZXR1cm4gaS5yb3dzLnJlZHVjZShmdW5jdGlvbihvLGEscyl7aWYoby5sZW5ndGghPT0wKXJldHVybiBvO3ZhciBsPWkuZ2V0Um93Qm91bmRzKHMpLGM9SmcuTWF0aC53aXRoaW4ocixsKTtyZXR1cm4gYz9hLmNvbHVtbnMucmVkdWNlKGZ1bmN0aW9uKHUsaCxmKXt2YXIgcD1pLmdldENvbHVtbkJvdW5kcyhzLGYpLGQ9SmcuTWF0aC53aXRoaW4ocixwKTtpZihkKXt2YXIgZz1uLmNvbnRlbnQoKS5zZWxlY3RBbGwoIi4iK3QuTEVHRU5EX1JPV19DTEFTUykubm9kZXMoKVtzXSxfPXoxLnNlbGVjdChnKS5zZWxlY3RBbGwoIi4iK3QuTEVHRU5EX0VOVFJZX0NMQVNTKS5ub2RlcygpW01hdGguZmxvb3IoZi8yKV0seT16MS5zZWxlY3QoXykuc2VsZWN0KCIuIit0LkxFR0VORF9TWU1CT0xfQ0xBU1MpLHg9SmcuRE9NLmdldFRyYW5zbGF0ZVZhbHVlcyh6MS5zZWxlY3QoZykpLGI9SmcuRE9NLmdldFRyYW5zbGF0ZVZhbHVlcyh5KTtyZXR1cm5be2JvdW5kczpKZy5ET00uZWxlbWVudEJCb3goejEuc2VsZWN0KGcpKSxkYXR1bTpoLmRhdGEubmFtZSxwb3NpdGlvbjp7eDp4WzBdK2JbMF0seTp4WzFdK2JbMV19LHNlbGVjdGlvbjp6MS5zZWxlY3QoXyksY29tcG9uZW50Om59XX1yZXR1cm4gdX0sbyk6b30sW10pfSx0LnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseT1mdW5jdGlvbigpe2UucHJvdG90eXBlLnJlbmRlckltbWVkaWF0ZWx5LmNhbGwodGhpcyk7dmFyIHI9dGhpcy5fYnVpbGRMZWdlbmRUYWJsZSh0aGlzLndpZHRoKCksdGhpcy5oZWlnaHQoKSk7dGhpcy5jb250ZW50KCkuc2VsZWN0QWxsKCIqIikucmVtb3ZlKCk7dmFyIG49dGhpcy5jb250ZW50KCkuc2VsZWN0QWxsKCJnLiIrdC5MRUdFTkRfUk9XX0NMQVNTKS5kYXRhKHIucm93cyksaT1uLmVudGVyKCkuYXBwZW5kKCJnIikuY2xhc3NlZCh0LkxFR0VORF9ST1dfQ0xBU1MsITApLm1lcmdlKG4pO24uZXhpdCgpLnJlbW92ZSgpLGkuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihhLHMpe3ZhciBsPXIuZ2V0Um93Qm91bmRzKHMpO3JldHVybiJ0cmFuc2xhdGUoIitsLnRvcExlZnQueCsiLCAiK2wudG9wTGVmdC55KyIpIn0pO3ZhciBvPXRoaXM7cmV0dXJuIGkuZWFjaChmdW5jdGlvbihhLHMpe2Zvcih2YXIgbD1bXSxjPTA7YzxhLmNvbHVtbnMubGVuZ3RoO2MrPTIpbC5wdXNoKFthLmNvbHVtbnNbY10sYS5jb2x1bW5zW2MrMV1dKTt2YXIgdT16MS5zZWxlY3QodGhpcykuc2VsZWN0QWxsKCJnLiIrdC5MRUdFTkRfRU5UUllfQ0xBU1MpLmRhdGEobCksaD11LmVudGVyKCkuYXBwZW5kKCJnIikuY2xhc3NlZCh0LkxFR0VORF9FTlRSWV9DTEFTUywhMCkubWVyZ2UodSk7aC5hcHBlbmQoInBhdGgiKS5hdHRyKCJkIixmdW5jdGlvbihmLHApe3ZhciBkPWZbMF07cmV0dXJuIG8uc3ltYm9sKCkoZC5kYXRhLm5hbWUscykoZC5oZWlnaHQqLjYpKG51bGwpfSkuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihmLHApe3ZhciBkPWZbMF0sZz1yLnJvd3Nbc10uY29sdW1ucy5pbmRleE9mKGQpLF89ci5nZXRDb2x1bW5Cb3VuZHMocyxnKTtyZXR1cm4idHJhbnNsYXRlKCIrKF8udG9wTGVmdC54K2Qud2lkdGgvMikrIiwgIitkLmhlaWdodC8yKyIpIn0pLmF0dHIoImZpbGwiLGZ1bmN0aW9uKGYpe3JldHVybiBvLl9jb2xvclNjYWxlLnNjYWxlKGZbMF0uZGF0YS5uYW1lKX0pLmF0dHIoIm9wYWNpdHkiLGZ1bmN0aW9uKGYscCl7cmV0dXJuIG8uc3ltYm9sT3BhY2l0eSgpKGZbMF0uZGF0YS5uYW1lLHMpfSkuY2xhc3NlZCh0LkxFR0VORF9TWU1CT0xfQ0xBU1MsITApLGguYXBwZW5kKCJnIikuY2xhc3NlZCgidGV4dC1jb250YWluZXIiLCEwKS5hdHRyKCJ0cmFuc2Zvcm0iLGZ1bmN0aW9uKGYscCl7dmFyIGQ9ZlsxXSxnPXIucm93c1tzXS5jb2x1bW5zLmluZGV4T2YoZCksXz1yLmdldENvbHVtbkJvdW5kcyhzLGcpO3JldHVybiJ0cmFuc2xhdGUoIitfLnRvcExlZnQueCsiLCAwKSJ9KS5lYWNoKGZ1bmN0aW9uKGYscCxkKXt2YXIgZz16MS5zZWxlY3QodGhpcyksXz1mWzFdLHk9e3hBbGlnbjoibGVmdCIseUFsaWduOiJ0b3AiLHRleHRSb3RhdGlvbjowfTtvLl93cml0ZXIud3JpdGUoby5fZm9ybWF0dGVyKF8uZGF0YS5uYW1lKSxfLndpZHRoLG8uaGVpZ2h0KCkseSxnLm5vZGUoKSl9KSx1LmV4aXQoKS5yZW1vdmUoKX0pLHRoaXN9LHQucHJvdG90eXBlLnN5bWJvbD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9zeW1ib2xGYWN0b3J5QWNjZXNzb3I6KHRoaXMuX3N5bWJvbEZhY3RvcnlBY2Nlc3Nvcj1yLHRoaXMucmVuZGVyKCksdGhpcyl9LHQucHJvdG90eXBlLnN5bWJvbE9wYWNpdHk9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fc3ltYm9sT3BhY2l0eUFjY2Vzc29yOih0eXBlb2Ygcj09Im51bWJlciI/dGhpcy5fc3ltYm9sT3BhY2l0eUFjY2Vzc29yPWZ1bmN0aW9uKCl7cmV0dXJuIHJ9OnRoaXMuX3N5bWJvbE9wYWNpdHlBY2Nlc3Nvcj1yLHRoaXMucmVuZGVyKCksdGhpcyl9LHQucHJvdG90eXBlLmZpeGVkV2lkdGg9ZnVuY3Rpb24oKXtyZXR1cm4hMH0sdC5wcm90b3R5cGUuZml4ZWRIZWlnaHQ9ZnVuY3Rpb24oKXtyZXR1cm4hMH0sdC5wcm90b3R5cGUuaW52YWxpZGF0ZUNhY2hlPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuaW52YWxpZGF0ZUNhY2hlLmNhbGwodGhpcyksdGhpcy5fbWVhc3VyZXIucmVzZXQoKX0sdC5MRUdFTkRfUk9XX0NMQVNTPSJsZWdlbmQtcm93Iix0LkxFR0VORF9FTlRSWV9DTEFTUz0ibGVnZW5kLWVudHJ5Iix0LkxFR0VORF9TWU1CT0xfQ0xBU1M9ImxlZ2VuZC1zeW1ib2wiLHR9KFpCZS5Db21wb25lbnQpO0dvdC5MZWdlbmQ9UUJlfSk7dmFyIFdvdD1IKCRGPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eSgkRiwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIHRIZTsoZnVuY3Rpb24oZSl7ZS5NQUlOPSJtYWluIixlLlJFU0VUPSJyZXNldCJ9KSh0SGU9JEYuQW5pbWF0b3J8fCgkRi5BbmltYXRvcj17fSkpfSk7dmFyIEwkdD1IKChHcW4sSSR0KT0+eyJ1c2Ugc3RyaWN0IjtJJHQuZXhwb3J0cz1mdW5jdGlvbih0KXtyZXR1cm4gdCE9bnVsbCYmdHlwZW9mIHQ9PSJvYmplY3QiJiZBcnJheS5pc0FycmF5KHQpPT09ITF9fSk7dmFyIE4kdD1IKChXcW4sUiR0KT0+eyJ1c2Ugc3RyaWN0Ijt2YXIgZUhlPUwkdCgpO2Z1bmN0aW9uIGskdChlKXtyZXR1cm4gZUhlKGUpPT09ITAmJk9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChlKT09PSJbb2JqZWN0IE9iamVjdF0ifVIkdC5leHBvcnRzPWZ1bmN0aW9uKHQpe3ZhciByLG47cmV0dXJuIShrJHQodCk9PT0hMXx8KHI9dC5jb25zdHJ1Y3Rvcix0eXBlb2YgciE9ImZ1bmN0aW9uIil8fChuPXIucHJvdG90eXBlLGskdChuKT09PSExKXx8bi5oYXNPd25Qcm9wZXJ0eSgiaXNQcm90b3R5cGVPZiIpPT09ITEpfX0pO3ZhciBqb3Q9SChZb3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFlvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIHJIZT1GZSgpLEQkdD0wLG5IZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUodCxyKXt0PT09dm9pZCAwJiYodD1bXSkscj09PXZvaWQgMCYmKHI9e30pLHRoaXMuX3VwZGF0ZUlkPUQkdCsrLHRoaXMuX2RhdGE9dCx0aGlzLl9tZXRhZGF0YT1yLHRoaXMuX2NhbGxiYWNrcz1uZXcgckhlLkNhbGxiYWNrU2V0fXJldHVybiBlLnByb3RvdHlwZS5vblVwZGF0ZT1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5fY2FsbGJhY2tzLmFkZCh0KSx0aGlzfSxlLnByb3RvdHlwZS5vZmZVcGRhdGU9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX2NhbGxiYWNrcy5kZWxldGUodCksdGhpc30sZS5wcm90b3R5cGUuZGF0YT1mdW5jdGlvbih0KXtyZXR1cm4gdD09bnVsbD90aGlzLl9kYXRhOih0aGlzLl9kYXRhPXQsdGhpcy5fZGlzcGF0Y2hVcGRhdGUoKSx0aGlzKX0sZS5wcm90b3R5cGUubWV0YWRhdGE9ZnVuY3Rpb24odCl7cmV0dXJuIHQ9PW51bGw/dGhpcy5fbWV0YWRhdGE6KHRoaXMuX21ldGFkYXRhPXQsdGhpcy5fZGlzcGF0Y2hVcGRhdGUoKSx0aGlzKX0sZS5wcm90b3R5cGUudXBkYXRlSWQ9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fdXBkYXRlSWR9LGUucHJvdG90eXBlLl9kaXNwYXRjaFVwZGF0ZT1mdW5jdGlvbigpe3RoaXMuX3VwZGF0ZUlkPUQkdCsrLHRoaXMuX2NhbGxiYWNrcy5jYWxsQ2FsbGJhY2tzKHRoaXMpfSxlfSgpO1lvdC5EYXRhc2V0PW5IZX0pO3ZhciBLb3Q9SChSYz0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoUmMsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBYb3Q9KGRlKCksVXQocGUpKSxpSGU9TiR0KCksb0hlPWpvdCgpLGFIZT1MMSgpO2Z1bmN0aW9uICRvdChlKXtyZXR1cm4gZSBpbnN0YW5jZW9mIGY0P2U6ZSBpbnN0YW5jZW9mIERhdGU/aDQoZS52YWx1ZU9mKCkpOmUgaW5zdGFuY2VvZiBhSGUuU2NhbGU/TyR0KGUpOmUgaW5zdGFuY2VvZiBvSGUuRGF0YXNldD96JHQoZSk6aUhlKGUpP0tGKGUpOkFycmF5LmlzQXJyYXkoZSk/RiR0KGUpOmg0KGUpfVJjLnNpZ249JG90O2Z1bmN0aW9uIE8kdChlKXt2YXIgdD17ZG9tYWluOmUuZG9tYWluKCkscmFuZ2U6ZS5yYW5nZSgpLHVwZGF0ZUlkOmUudXBkYXRlSWQoKSxyZWY6aDQoZSl9O3JldHVybiBLRih0KX1SYy5zaWduU2NhbGU9TyR0O2Z1bmN0aW9uIHokdChlKXt2YXIgdD17cmVmOmg0KGUpLHVwZGF0ZUlkOmUudXBkYXRlSWQoKX07cmV0dXJuIEtGKHQpfVJjLnNpZ25EYXRhc2V0PXokdDtmdW5jdGlvbiBoNChlKXtyZXR1cm4gbmV3IEgkdChlKX1SYy5zaWduUmVmPWg0O2Z1bmN0aW9uIEYkdChlKXtyZXR1cm4gbmV3IEIkdChlLm1hcChmdW5jdGlvbih0KXtyZXR1cm4gJG90KHQpfSkpfVJjLnNpZ25BcnJheT1GJHQ7ZnVuY3Rpb24gS0YoZSl7dmFyIHQ9e307Zm9yKHZhciByIGluIGUpZS5oYXNPd25Qcm9wZXJ0eShyKSYmKHRbcl09JG90KGVbcl0pKTtyZXR1cm4gbmV3IFYkdCh0KX1SYy5zaWduT2JqPUtGO3ZhciBmND1mdW5jdGlvbigpe2Z1bmN0aW9uIGUoKXt9cmV0dXJuIGUucHJvdG90eXBlLmlzRGlmZmVyZW50PWZ1bmN0aW9uKHQpe3JldHVybiB0IGluc3RhbmNlb2YgdGhpcy5jb25zdHJ1Y3Rvcj90aGlzLmlzU2lnbmF0dXJlRGlmZmVyZW50KHQpOiEwfSxlfSgpO1JjLlNpZ25hdHVyZT1mNDt2YXIgQiR0PWZ1bmN0aW9uKGUpe1hvdC5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3ZhciBuPWUuY2FsbCh0aGlzKXx8dGhpcztyZXR1cm4gbi5hcnJheT1yLG59cmV0dXJuIHQucHJvdG90eXBlLmlzU2lnbmF0dXJlRGlmZmVyZW50PWZ1bmN0aW9uKHIpe2lmKHIuYXJyYXkubGVuZ3RoIT09dGhpcy5hcnJheS5sZW5ndGgpcmV0dXJuITA7Zm9yKHZhciBuPTA7bjx0aGlzLmFycmF5Lmxlbmd0aDtuKyspaWYodGhpcy5hcnJheVtuXS5pc0RpZmZlcmVudChyLmFycmF5W25dKSlyZXR1cm4hMDtyZXR1cm4hMX0sdH0oZjQpO1JjLkFycmF5U2lnbmF0dXJlPUIkdDt2YXIgSCR0PWZ1bmN0aW9uKGUpe1hvdC5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3ZhciBuPWUuY2FsbCh0aGlzKXx8dGhpcztyZXR1cm4gbi5yZWY9cixufXJldHVybiB0LnByb3RvdHlwZS5pc1NpZ25hdHVyZURpZmZlcmVudD1mdW5jdGlvbihyKXtyZXR1cm4gdGhpcy5yZWYhPT1yLnJlZn0sdH0oZjQpO1JjLlJlZmVyZW5jZVNpZ25hdHVyZT1IJHQ7dmFyIFYkdD1mdW5jdGlvbihlKXtYb3QuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdChyKXt2YXIgbj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIG4ub2JqPXIsbn1yZXR1cm4gdC5wcm90b3R5cGUuaXNTaWduYXR1cmVEaWZmZXJlbnQ9ZnVuY3Rpb24ocil7dmFyIG49T2JqZWN0LmtleXModGhpcy5vYmopLGk9T2JqZWN0LmtleXMoci5vYmopO2lmKG4ubGVuZ3RoIT09aS5sZW5ndGgpcmV0dXJuITA7Zm9yKHZhciBvPTAsYT1uO288YS5sZW5ndGg7bysrKXt2YXIgcz1hW29dO2lmKCFyLm9iai5oYXNPd25Qcm9wZXJ0eShzKXx8dGhpcy5vYmpbc10uaXNEaWZmZXJlbnQoci5vYmpbc10pKXJldHVybiEwfXJldHVybiExfSx0fShmNCk7UmMuT2JqZWN0U2lnbmF0dXJlPVYkdH0pO3ZhciBVJHQ9SChab3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFpvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIHNIZT1Lb3QoKTtmdW5jdGlvbiBsSGUoZSl7dmFyIHQ9dm9pZCAwLHIsbj0hMSxpPSExLG89ZnVuY3Rpb24oKXtmb3IodmFyIGE9W10scz0wO3M8YXJndW1lbnRzLmxlbmd0aDtzKyspYVtzXT1hcmd1bWVudHNbc107aWYobilyZXR1cm4gcjt2YXIgbD1zSGUuc2lnbkFycmF5KGEpO3JldHVybiB0PT09dm9pZCAwfHx0LmlzRGlmZmVyZW50KGwpPyhpJiZjb25zb2xlLndhcm4oImNhY2hlIG1pc3MhIGNvbXB1dGluZyIpLHQ9bCxyPWUuYXBwbHkodGhpcyxhKSk6aSYmY29uc29sZS53YXJuKCJjYWNoZSBoaXQhIikscn07cmV0dXJuIG8uZG9Mb2NrZWQ9ZnVuY3Rpb24oYSl7aWYobil0aHJvdyBuZXcgRXJyb3IoIkxvY2tpbmcgYW4gYWxyZWFkeSBsb2NrZWQgbWVtb2l6ZSBmdW5jdGlvbiEiKTtuPSEwO3ZhciBzPWEuYXBwbHkodGhpcyk7cmV0dXJuIG49ITEsc30sby5sb2dQZXJmb3JtYW5jZT1mdW5jdGlvbihhKXtyZXR1cm4gYT09PXZvaWQgMCYmKGE9ITApLGk9YSx0aGlzfSxvfVpvdC5tZW1vaXplPWxIZX0pO3ZhciBXJHQ9SChaRj0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoWkYsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBjSGU9T2UoKSxKb3Q9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKCl7dGhpcy5tYXA9T2JqZWN0LmNyZWF0ZShudWxsKSx0aGlzLmV4aXN0cz1PYmplY3QuY3JlYXRlKG51bGwpfXJldHVybiBlLnByb3RvdHlwZS5kZWxldGU9ZnVuY3Rpb24odCl7cmV0dXJuIGRlbGV0ZSB0aGlzLm1hcFt0XSxkZWxldGUgdGhpcy5leGlzdHNbdF0sITB9LGUucHJvdG90eXBlLmdldD1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5tYXBbdF19LGUucHJvdG90eXBlLmhhcz1mdW5jdGlvbih0KXtyZXR1cm4hIXRoaXMuZXhpc3RzW3RdfSxlLnByb3RvdHlwZS5zZXQ9ZnVuY3Rpb24odCxyKXtyZXR1cm4gdGhpcy5tYXBbdF09cix0aGlzLmV4aXN0c1t0XT0hMCx0aGlzfSxlfSgpLHEkdD1mdW5jdGlvbigpe2Z1bmN0aW9uIGUoKXt0aGlzLm1hcD1uZXcgSm90fXJldHVybiBlLnByb3RvdHlwZS5nZXQ9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMubWFwLmdldCh0WzBdKS5nZXQodFsxXSl9LGUucHJvdG90eXBlLmhhcz1mdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5tYXAuaGFzKHRbMF0pJiZ0aGlzLm1hcC5nZXQodFswXSkuaGFzKHRbMV0pfSxlLnByb3RvdHlwZS5zZXQ9ZnVuY3Rpb24odCxyKXtyZXR1cm4gdGhpcy5tYXAuaGFzKHRbMF0pfHx0aGlzLm1hcC5zZXQodFswXSxuZXcgSm90KSx0aGlzLm1hcC5nZXQodFswXSkuc2V0KHRbMV0sciksdGhpc30sZS5wcm90b3R5cGUuZGVsZXRlPWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLm1hcC5oYXModFswXSkmJnRoaXMubWFwLmdldCh0WzBdKS5kZWxldGUodFsxXSksITB9LGUucHJvdG90eXBlLmNsZWFyPWZ1bmN0aW9uKCl7dGhpcy5tYXA9bmV3IEpvdH0sZS5yZXNvbHZlcj1mdW5jdGlvbih0LHIsbil7cmV0dXJuW24udXBkYXRlSWQoKSxyXX0sZX0oKTtmdW5jdGlvbiBHJHQoZSl7dmFyIHQ9Y0hlLm1lbW9pemUoZSxxJHQucmVzb2x2ZXIpO3JldHVybiB0LmNhY2hlPW5ldyBxJHQsdH1aRi5tZW1vaXplUHJvamVjdG9yPUckdDtmdW5jdGlvbiB1SGUoZSl7cmV0dXJuIE9iamVjdC5rZXlzKGUpLmZvckVhY2goZnVuY3Rpb24odCl7ZVt0XT1HJHQoZVt0XSl9KSxlfVpGLm1lbW9pemVQcm9qZWN0b3JzPXVIZX0pO3ZhciBZJHQ9SChRb3Q9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFFvdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIGhIZT1DUygpO2Z1bmN0aW9uIGZIZSgpe2Zvcih2YXIgZT1bXSx0PTA7dDxhcmd1bWVudHMubGVuZ3RoO3QrKyllW3RdPWFyZ3VtZW50c1t0XTt2YXIgcj1lLnNsaWNlKDAsLTEpLG49ZVtlLmxlbmd0aC0xXSxpPWhIZS5tZW1vaXplKG4pLG89ZnVuY3Rpb24oKXt2YXIgYT10aGlzLHM9ci5tYXAoZnVuY3Rpb24obCl7cmV0dXJuIGwuYXBwbHkoYSl9KTtyZXR1cm4gaS5hcHBseSh2b2lkIDAscyl9O3JldHVybiBvfVFvdC5tZW1UaHVuaz1mSGV9KTt2YXIgQ1M9SChBUz0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoQVMsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciB0YXQ9KGRlKCksVXQocGUpKTt0YXQuX19leHBvcnRTdGFyKFUkdCgpLEFTKTt0YXQuX19leHBvcnRTdGFyKFckdCgpLEFTKTt0YXQuX19leHBvcnRTdGFyKFkkdCgpLEFTKTt2YXIgcEhlPUtvdCgpO0FTLnNpZ249cEhlLnNpZ259KTt2YXIgRjE9SChOYz0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoTmMsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBqJHQ9KEVyKCksVXQoTXIpKSxkSGU9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKHQscil7dGhpcy5fY29udGV4dD10LHRoaXMuX2RyYXdTdGVwPXJ9cmV0dXJuIGUucHJvdG90eXBlLmdldERyYXdTdGVwPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2RyYXdTdGVwfSxlLnByb3RvdHlwZS5kcmF3PWZ1bmN0aW9uKHQscil7dmFyIG49cltyLmxlbmd0aC0xXS5hdHRyVG9BcHBsaWVkUHJvamVjdG9yO3RoaXMuX2NvbnRleHQuc2F2ZSgpLHRoaXMuX2RyYXdTdGVwKHRoaXMuX2NvbnRleHQsdCxuKSx0aGlzLl9jb250ZXh0LnJlc3RvcmUoKX0sZS5wcm90b3R5cGUuZ2V0VmlzdWFsUHJpbWl0aXZlcz1mdW5jdGlvbigpe3JldHVybltdfSxlLnByb3RvdHlwZS5nZXRWaXN1YWxQcmltaXRpdmVBdEluZGV4PWZ1bmN0aW9uKHQpe3JldHVybiBudWxsfSxlLnByb3RvdHlwZS5yZW1vdmU9ZnVuY3Rpb24oKXt9LGV9KCk7TmMuQ2FudmFzRHJhd2VyPWRIZTtOYy5Db250ZXh0U3R5bGVBdHRycz1bImZpbGwtb3BhY2l0eSIsImZpbGwiLCJvcGFjaXR5Iiwic3Ryb2tlLW9wYWNpdHkiLCJzdHJva2Utd2lkdGgiLCJzdHJva2UiLCJzdHJva2UtZGFzaGFycmF5Il07ZnVuY3Rpb24gbUhlKGUsdCxyLG4pe3ZhciBpPU5jLkNvbnRleHRTdHlsZUF0dHJzLmNvbmNhdCh0KTtyZXR1cm4gWCR0KGUsaSxyLG4pfU5jLnJlc29sdmVBdHRyaWJ1dGVzU3Vic2V0V2l0aFN0eWxlcz1tSGU7ZnVuY3Rpb24gWCR0KGUsdCxyLG4pe2Zvcih2YXIgaT17fSxvPTAsYT10O288YS5sZW5ndGg7bysrKXt2YXIgcz1hW29dO2UuaGFzT3duUHJvcGVydHkocykmJihpW3NdPWVbc10ocixuKSl9cmV0dXJuIGl9TmMucmVzb2x2ZUF0dHJpYnV0ZXM9WCR0O2Z1bmN0aW9uIGdIZShlKXt2YXIgdD1lLm9wYWNpdHkhPW51bGw/cGFyc2VGbG9hdChlLm9wYWNpdHkpOjEscj1lWyJzdHJva2Utb3BhY2l0eSJdIT1udWxsP3BhcnNlRmxvYXQoZVsic3Ryb2tlLW9wYWNpdHkiXSk6MTtyZXR1cm4gcip0fWZ1bmN0aW9uIF9IZShlKXt2YXIgdD1lLm9wYWNpdHkhPW51bGw/cGFyc2VGbG9hdChlLm9wYWNpdHkpOjEscj1lWyJmaWxsLW9wYWNpdHkiXSE9bnVsbD9wYXJzZUZsb2F0KGVbImZpbGwtb3BhY2l0eSJdKToxO3JldHVybiByKnR9ZnVuY3Rpb24gJCR0KGUpe3JldHVybiBlWyJzdHJva2Utd2lkdGgiXSE9bnVsbD9wYXJzZUZsb2F0KGVbInN0cm9rZS13aWR0aCJdKToxfU5jLmdldFN0cm9rZVdpZHRoPSQkdDtmdW5jdGlvbiBLJHQoZSl7dmFyIHQ9ZVsic3Ryb2tlLWRhc2hhcnJheSJdO2lmKHQhPW51bGwpdHJ5e3JldHVybiB0LnNwbGl0KC9bICxdKy8pLm1hcChmdW5jdGlvbihyKXtyZXR1cm4gcGFyc2VJbnQociwxMCl9KX1jYXRjaChyKXtyZXR1cm4gY29uc29sZS5lcnJvcigiZ2V0U3Ryb2tlRGFzaEFycmF5IGZhaWxlZCB3aXRoOiAiK3IpLFtdfXJldHVybltdfU5jLmdldFN0cm9rZURhc2hBcnJheT1LJHQ7ZnVuY3Rpb24geUhlKGUsdCxyLG4pe2Uuc2F2ZSgpLGUuYmVnaW5QYXRoKCksdC5jb250ZXh0KGUpLHQociksZS5saW5lSm9pbj0icm91bmQiLGVhdChlLG4pLGUucmVzdG9yZSgpfU5jLnJlbmRlckFyZWE9eUhlO2Z1bmN0aW9uIHZIZShlLHQscixuKXtlLnNhdmUoKSxlLmJlZ2luUGF0aCgpLHQuY29udGV4dChlKSx0KHIpLGUubGluZUpvaW49InJvdW5kIixlYXQoZSxuKSxlLnJlc3RvcmUoKX1OYy5yZW5kZXJMaW5lPXZIZTtmdW5jdGlvbiBlYXQoZSx0KXtpZih0LnN0cm9rZSl7ZS5saW5lV2lkdGg9JCR0KHQpO3ZhciByPWokdC5jb2xvcih0LnN0cm9rZSksbj1LJHQodCk7ZS5zZXRMaW5lRGFzaChuKSxyLm9wYWNpdHkqPWdIZSh0KSxlLnN0cm9rZVN0eWxlPXIudG9TdHJpbmcoKSxlLnN0cm9rZSgpfWlmKHQuZmlsbCl7dmFyIGk9aiR0LmNvbG9yKHQuZmlsbCk7aS5vcGFjaXR5Kj1fSGUodCksZS5maWxsU3R5bGU9aS50b1N0cmluZygpLGUuZmlsbCgpfX1OYy5yZW5kZXJQYXRoV2l0aFN0eWxlPWVhdH0pO3ZhciBVdT1IKHJhdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkocmF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgeEhlPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSh0LHIpe3RoaXMuX3N2Z0RyYXdlckZhY3Rvcnk9dCx0aGlzLl9jYW52YXNEcmF3ZXJGYWN0b3J5PXJ9cmV0dXJuIGUucHJvdG90eXBlLnVzZVNWRz1mdW5jdGlvbih0KXt0aGlzLl9jdXJyZW50RHJhd2VyIT1udWxsJiZ0aGlzLl9jdXJyZW50RHJhd2VyLnJlbW92ZSgpO3ZhciByPXRoaXMuX3N2Z0RyYXdlckZhY3RvcnkoKTtyLmF0dGFjaFRvKHQpLHRoaXMuX2N1cnJlbnREcmF3ZXI9cn0sZS5wcm90b3R5cGUudXNlQ2FudmFzPWZ1bmN0aW9uKHQpe3RoaXMuX2N1cnJlbnREcmF3ZXIhPW51bGwmJnRoaXMuX2N1cnJlbnREcmF3ZXIucmVtb3ZlKCksdGhpcy5fY3VycmVudERyYXdlcj10aGlzLl9jYW52YXNEcmF3ZXJGYWN0b3J5KHQubm9kZSgpLmdldENvbnRleHQoIjJkIikpfSxlLnByb3RvdHlwZS5nZXREcmF3ZXI9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fY3VycmVudERyYXdlcn0sZS5wcm90b3R5cGUucmVtb3ZlPWZ1bmN0aW9uKCl7dGhpcy5fY3VycmVudERyYXdlciE9bnVsbCYmdGhpcy5fY3VycmVudERyYXdlci5yZW1vdmUoKX0sZS5wcm90b3R5cGUuZHJhdz1mdW5jdGlvbih0LHIpe3RoaXMuX2N1cnJlbnREcmF3ZXIuZHJhdyh0LHIpfSxlLnByb3RvdHlwZS5nZXRWaXN1YWxQcmltaXRpdmVzPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2N1cnJlbnREcmF3ZXIuZ2V0VmlzdWFsUHJpbWl0aXZlcygpfSxlLnByb3RvdHlwZS5nZXRWaXN1YWxQcmltaXRpdmVBdEluZGV4PWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLl9jdXJyZW50RHJhd2VyLmdldFZpc3VhbFByaW1pdGl2ZUF0SW5kZXgodCl9LGV9KCk7cmF0LlByb3h5RHJhd2VyPXhIZX0pO3ZhciBEZj1IKG5hdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkobmF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgYkhlPShFcigpLFV0KE1yKSksWiR0PUZlKCksd0hlPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gZSh0LHIpe3RoaXMuX3Jvb3Q9YkhlLnNlbGVjdChkb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwiZyIpKSx0aGlzLl9jbGFzc05hbWU9cix0aGlzLl9zdmdFbGVtZW50TmFtZT10fXJldHVybiBlLnByb3RvdHlwZS5kcmF3PWZ1bmN0aW9uKHQscil7dmFyIG49dGhpczt0aGlzLl9jcmVhdGVBbmREZXN0cm95RE9NRWxlbWVudHModCk7Zm9yKHZhciBpPTAsbz1yLmxlbmd0aCxhPWZ1bmN0aW9uKGwpe3ZhciBjPXJbbF07WiR0LldpbmRvdy5zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7cmV0dXJuIG4uX2RyYXdTdGVwKGMpfSxpKSxpKz1jLmFuaW1hdG9yLnRvdGFsVGltZSh0Lmxlbmd0aCl9LHM9MDtzPG87cysrKWEocyl9LGUucHJvdG90eXBlLmdldFZpc3VhbFByaW1pdGl2ZXM9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fY2FjaGVkVmlzdWFsUHJpbWl0aXZlc05vZGVzPT1udWxsJiYodGhpcy5fY2FjaGVkVmlzdWFsUHJpbWl0aXZlc05vZGVzPXRoaXMuX3NlbGVjdGlvbi5ub2RlcygpKSx0aGlzLl9jYWNoZWRWaXN1YWxQcmltaXRpdmVzTm9kZXN9LGUucHJvdG90eXBlLmdldFZpc3VhbFByaW1pdGl2ZUF0SW5kZXg9ZnVuY3Rpb24odCl7cmV0dXJuIHRoaXMuX2NhY2hlZFZpc3VhbFByaW1pdGl2ZXNOb2RlTWFwPT1udWxsP251bGw6dGhpcy5fY2FjaGVkVmlzdWFsUHJpbWl0aXZlc05vZGVNYXAuZ2V0KHQpfSxlLnByb3RvdHlwZS5yZW1vdmU9ZnVuY3Rpb24oKXt0aGlzLl9yb290LnJlbW92ZSgpfSxlLnByb3RvdHlwZS5hdHRhY2hUbz1mdW5jdGlvbih0KXt0Lm5vZGUoKS5hcHBlbmRDaGlsZCh0aGlzLl9yb290Lm5vZGUoKSl9LGUucHJvdG90eXBlLmdldFJvb3Q9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fcm9vdH0sZS5wcm90b3R5cGUuc2VsZWN0b3I9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fc3ZnRWxlbWVudE5hbWV9LGUucHJvdG90eXBlLl9hcHBseURlZmF1bHRBdHRyaWJ1dGVzPWZ1bmN0aW9uKHQpe30sZS5wcm90b3R5cGUuX2NyZWF0ZUFuZERlc3Ryb3lET01FbGVtZW50cz1mdW5jdGlvbih0KXt2YXIgcj10Lm1hcChmdW5jdGlvbihhLHMpe3JldHVybiBhIT1udWxsP3tkOmEsaTpzfTpudWxsfSksbj1yLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYSE9bnVsbH0pLGk9dGhpcy5fcm9vdC5zZWxlY3RBbGwodGhpcy5zZWxlY3RvcigpKS5kYXRhKG4pO3RoaXMuX3NlbGVjdGlvbj1pLmVudGVyKCkuYXBwZW5kKHRoaXMuX3N2Z0VsZW1lbnROYW1lKS5tZXJnZShpKSxpLmV4aXQoKS5yZW1vdmUoKTt2YXIgbz1uZXcgWiR0Lk1hcDt0aGlzLl9zZWxlY3Rpb24uZWFjaChmdW5jdGlvbihhKXtvLnNldChhLmksdGhpcyl9KSx0aGlzLl9jYWNoZWRWaXN1YWxQcmltaXRpdmVzTm9kZU1hcD1vLHRoaXMuX2NhY2hlZFZpc3VhbFByaW1pdGl2ZXNOb2Rlcz1udWxsLHRoaXMuX3NlbGVjdGlvbi5kYXRhKHRoaXMuX3NlbGVjdGlvbi5kYXRhKCkubWFwKGZ1bmN0aW9uKGEpe3ZhciBzPWEuZDtyZXR1cm4gc30pKSx0aGlzLl9jbGFzc05hbWUhPW51bGwmJnRoaXMuX3NlbGVjdGlvbi5jbGFzc2VkKHRoaXMuX2NsYXNzTmFtZSwhMCksdGhpcy5fYXBwbHlEZWZhdWx0QXR0cmlidXRlcyh0aGlzLl9zZWxlY3Rpb24pfSxlLnByb3RvdHlwZS5fZHJhd1N0ZXA9ZnVuY3Rpb24odCl7dmFyIHI9dGhpcyxuPVsiZmlsbCIsInN0cm9rZSJdO24uZm9yRWFjaChmdW5jdGlvbihpKXt0LmF0dHJUb0FwcGxpZWRQcm9qZWN0b3JbaV0hPW51bGwmJnIuX3NlbGVjdGlvbi5hdHRyKGksdC5hdHRyVG9BcHBsaWVkUHJvamVjdG9yW2ldKX0pLHQuYW5pbWF0b3IuYW5pbWF0ZSh0aGlzLl9zZWxlY3Rpb24sdC5hdHRyVG9BcHBsaWVkUHJvamVjdG9yKSx0aGlzLl9jbGFzc05hbWUhPW51bGwmJnRoaXMuX3NlbGVjdGlvbi5jbGFzc2VkKHRoaXMuX2NsYXNzTmFtZSwhMCl9LGV9KCk7bmF0LlNWR0RyYXdlcj13SGV9KTt2YXIgb2F0PUgoaWF0PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShpYXQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBKJHQ9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKCl7dmFyIHQ9dGhpczt0aGlzLnNjYWxlPTAsdGhpcy50cmFuc2xhdGU9MCx0aGlzLmNhY2hlZERvbWFpbj1bbnVsbCxudWxsXSx0aGlzLmxhc3RTZWVuRG9tYWluPVtudWxsLG51bGxdLHRoaXMudXBkYXRlRG9tYWluPWZ1bmN0aW9uKHIpe3QubGFzdFNlZW5Eb21haW49ci5nZXRUcmFuc2Zvcm1hdGlvbkRvbWFpbigpO3ZhciBuPXIuc2NhbGVUcmFuc2Zvcm1hdGlvbih0LmNhY2hlZERvbWFpblsxXSktci5zY2FsZVRyYW5zZm9ybWF0aW9uKHQuY2FjaGVkRG9tYWluWzBdKSxpPXIuc2NhbGVUcmFuc2Zvcm1hdGlvbih0Lmxhc3RTZWVuRG9tYWluWzFdKS1yLnNjYWxlVHJhbnNmb3JtYXRpb24odC5sYXN0U2VlbkRvbWFpblswXSk7dC5zY2FsZT1uL2l8fDEsdC50cmFuc2xhdGU9ci5zY2FsZVRyYW5zZm9ybWF0aW9uKHQuY2FjaGVkRG9tYWluWzBdKS1yLnNjYWxlVHJhbnNmb3JtYXRpb24odC5sYXN0U2VlbkRvbWFpblswXSkqdC5zY2FsZXx8MH19cmV0dXJuIGUucHJvdG90eXBlLnJlc2V0PWZ1bmN0aW9uKCl7dGhpcy5zY2FsZT0xLHRoaXMudHJhbnNsYXRlPTAsdGhpcy5jYWNoZWREb21haW49dGhpcy5sYXN0U2VlbkRvbWFpbn0sZS5wcm90b3R5cGUuc2V0RG9tYWluPWZ1bmN0aW9uKHQpe3RoaXMuY2FjaGVkRG9tYWluPXQuZ2V0VHJhbnNmb3JtYXRpb25Eb21haW4oKX0sZX0oKSxTSGU9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKHQscil7dmFyIG49dGhpczt0aGlzLnJlbmRlckNhbGxiYWNrPXQsdGhpcy5hcHBseVRyYW5zZm9ybUNhbGxiYWNrPXIsdGhpcy5kb21haW5UcmFuc2Zvcm1YPW5ldyBKJHQsdGhpcy5kb21haW5UcmFuc2Zvcm1ZPW5ldyBKJHQsdGhpcy5yZW5kZXJEZWZlcnJlZD1mdW5jdGlvbigpe24uYXBwbHlUcmFuc2Zvcm0oKSxjbGVhclRpbWVvdXQobi50aW1lb3V0VG9rZW4pLG4udGltZW91dFRva2VuPXNldFRpbWVvdXQoZnVuY3Rpb24oKXtuLnJlbmRlckNhbGxiYWNrKCl9LGUuREVGRVJSRURfUkVOREVSSU5HX0RFTEFZKX19cmV0dXJuIGUucHJvdG90eXBlLnNldERvbWFpbnM9ZnVuY3Rpb24odCxyKXt0JiZ0aGlzLmRvbWFpblRyYW5zZm9ybVguc2V0RG9tYWluKHQpLHImJnRoaXMuZG9tYWluVHJhbnNmb3JtWS5zZXREb21haW4ociksdGhpcy5yZW5kZXJEZWZlcnJlZCgpfSxlLnByb3RvdHlwZS51cGRhdGVEb21haW5zPWZ1bmN0aW9uKHQscil7dCYmdGhpcy5kb21haW5UcmFuc2Zvcm1YLnVwZGF0ZURvbWFpbih0KSxyJiZ0aGlzLmRvbWFpblRyYW5zZm9ybVkudXBkYXRlRG9tYWluKHIpLHRoaXMucmVuZGVyRGVmZXJyZWQoKX0sZS5wcm90b3R5cGUucmVzZXRUcmFuc2Zvcm1zPWZ1bmN0aW9uKCl7dGhpcy5kb21haW5UcmFuc2Zvcm1YLnJlc2V0KCksdGhpcy5kb21haW5UcmFuc2Zvcm1ZLnJlc2V0KCksdGhpcy5hcHBseVRyYW5zZm9ybSgpfSxlLnByb3RvdHlwZS5hcHBseVRyYW5zZm9ybT1mdW5jdGlvbigpe3RoaXMuYXBwbHlUcmFuc2Zvcm1DYWxsYmFjayh0aGlzLmRvbWFpblRyYW5zZm9ybVgudHJhbnNsYXRlLHRoaXMuZG9tYWluVHJhbnNmb3JtWS50cmFuc2xhdGUsdGhpcy5kb21haW5UcmFuc2Zvcm1YLnNjYWxlLHRoaXMuZG9tYWluVHJhbnNmb3JtWS5zY2FsZSl9LGUuREVGRVJSRURfUkVOREVSSU5HX0RFTEFZPTIwMCxlfSgpO2lhdC5EZWZlcnJlZFJlbmRlcmVyPVNIZX0pO3ZhciBycz1IKEpGPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShKRiwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIE1IZT0oZGUoKSxVdChwZSkpLHdkPShFcigpLFV0KE1yKSkscDQ9TGYoKSxTZD1GZSgpLFEkdD1Xb3QoKSxhYXQ9Q1MoKSxFSGU9a2MoKSxUSGU9RjEoKSxDSGU9VXUoKSxBSGU9RGYoKSxQSGU9WWcoKSxJSGU9SWYoKSxMSGU9b2F0KCk7SkYuUmVuZGVyZXI9SUhlLm1ha2VFbnVtKFsic3ZnIiwiY2FudmFzIl0pO3ZhciBrSGU9ZnVuY3Rpb24oZSl7TUhlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXt2YXIgcj1lLmNhbGwodGhpcyl8fHRoaXM7ci5fZGF0YUNoYW5nZWQ9ITEsci5fYXR0ckV4dGVudHM9e30sci5fYW5pbWF0ZT0hMSxyLl9hbmltYXRvcnM9e30sci5fcHJvcGVydHlFeHRlbnRzPXt9LHIuX3Jlc2V0RW50aXR5U3RvcmU9ZnVuY3Rpb24oKXtyLl9jYWNoZWRFbnRpdHlTdG9yZT12b2lkIDB9LHIuX292ZXJmbG93SGlkZGVuPSEwLHIuYWRkQ2xhc3MoInBsb3QiKSxyLl9kYXRhc2V0VG9EcmF3ZXI9bmV3IFNkLk1hcCxyLl9hdHRyQmluZGluZ3M9d2QubWFwKCksci5faW5jbHVkZWRWYWx1ZXNQcm92aWRlcj1mdW5jdGlvbihpLG8pe3JldHVybiByLl9pbmNsdWRlZFZhbHVlc0ZvclNjYWxlKGksbyl9LHIuX3JlbmRlckNhbGxiYWNrPWZ1bmN0aW9uKCl7cmV0dXJuIHIucmVuZGVyKCl9LHIuX29uRGF0YXNldFVwZGF0ZUNhbGxiYWNrPWZ1bmN0aW9uKCl7cmV0dXJuIHIuX29uRGF0YXNldFVwZGF0ZSgpfSxyLl9wcm9wZXJ0eUJpbmRpbmdzPXdkLm1hcCgpO3ZhciBuPW5ldyBwNC5FYXNpbmcoKS5tYXhUb3RhbER1cmF0aW9uKHQuX0FOSU1BVElPTl9NQVhfRFVSQVRJT04pO3JldHVybiByLmFuaW1hdG9yKFEkdC5BbmltYXRvci5NQUlOLG4pLHIuYW5pbWF0b3IoUSR0LkFuaW1hdG9yLlJFU0VULG5ldyBwNC5OdWxsKSxyLl9kZWZlcnJlZFJlc2V0RW50aXR5U3RvcmU9U2QuV2luZG93LmRlYm91bmNlKExIZS5EZWZlcnJlZFJlbmRlcmVyLkRFRkVSUkVEX1JFTkRFUklOR19ERUxBWSxyLl9yZXNldEVudGl0eVN0b3JlKSxyfXJldHVybiB0LmdldFRvdGFsRHJhd1RpbWU9ZnVuY3Rpb24ocixuKXtyZXR1cm4gbi5yZWR1Y2UoZnVuY3Rpb24oaSxvKXtyZXR1cm4gaStvLmFuaW1hdG9yLnRvdGFsVGltZShyLmxlbmd0aCl9LDApfSx0LmFwcGx5RHJhd1N0ZXBzPWZ1bmN0aW9uKHIsbil7dmFyIGk9ci5tYXAoZnVuY3Rpb24obyl7dmFyIGE9by5hdHRyVG9Qcm9qZWN0b3Iscz17fTtyZXR1cm4gT2JqZWN0LmtleXMoYSkuZm9yRWFjaChmdW5jdGlvbihsKXtzW2xdPWZ1bmN0aW9uKGMsdSl7cmV0dXJuIGFbbF0oYyx1LG4pfX0pLHthdHRyVG9BcHBsaWVkUHJvamVjdG9yOnMsYW5pbWF0b3I6by5hbmltYXRvcn19KTtyZXR1cm4gaX0sdC5wcm90b3R5cGUuYW5jaG9yPWZ1bmN0aW9uKHIpe3JldHVybiByPVBIZS5jb2VyY2VFeHRlcm5hbEQzKHIpLGUucHJvdG90eXBlLmFuY2hvci5jYWxsKHRoaXMsciksdGhpcy5fZGF0YUNoYW5nZWQ9ITAsdGhpcy5fcmVzZXRFbnRpdHlTdG9yZSgpLHRoaXMuX3VwZGF0ZUV4dGVudHMoKSx0aGlzfSx0LnByb3RvdHlwZS5fc2V0dXA9ZnVuY3Rpb24oKXt2YXIgcj10aGlzO3RoaXMuX2lzU2V0dXB8fChlLnByb3RvdHlwZS5fc2V0dXAuY2FsbCh0aGlzKSx0aGlzLl9jYW52YXMhPW51bGwmJnRoaXMuX2FwcGVuZENhbnZhc05vZGUoKSx0aGlzLl9yZW5kZXJBcmVhPXRoaXMuY29udGVudCgpLmFwcGVuZCgiZyIpLmNsYXNzZWQoInJlbmRlci1hcmVhIiwhMCksdGhpcy5kYXRhc2V0cygpLmZvckVhY2goZnVuY3Rpb24obil7cmV0dXJuIHIuX2NyZWF0ZU5vZGVzRm9yRGF0YXNldChuKX0pKX0sdC5wcm90b3R5cGUuX2FwcGVuZENhbnZhc05vZGU9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLmVsZW1lbnQoKS5zZWxlY3QoIi5wbG90LWNhbnZhcy1jb250YWluZXIiKTtyLmVtcHR5KCkmJihyPXRoaXMuZWxlbWVudCgpLmFwcGVuZCgiZGl2IikuY2xhc3NlZCgicGxvdC1jYW52YXMtY29udGFpbmVyIiwhMCksci5ub2RlKCkuYXBwZW5kQ2hpbGQodGhpcy5fY2FudmFzLm5vZGUoKSkpfSx0LnByb3RvdHlwZS5zZXRCb3VuZHM9ZnVuY3Rpb24ocixuLGksbyl7aWYoZS5wcm90b3R5cGUuc2V0Qm91bmRzLmNhbGwodGhpcyxyLG4saSxvKSx0aGlzLl91cGRhdGVFeHRlbnRzKCksdGhpcy5fY2FudmFzIT1udWxsKXtpZih0aGlzLl9idWZmZXJDYW52YXMmJiF0aGlzLl9idWZmZXJDYW52YXNWYWxpZCl7dGhpcy5fYnVmZmVyQ2FudmFzLmF0dHIoIndpZHRoIix0aGlzLl9jYW52YXMuYXR0cigid2lkdGgiKSksdGhpcy5fYnVmZmVyQ2FudmFzLmF0dHIoImhlaWdodCIsdGhpcy5fY2FudmFzLmF0dHIoImhlaWdodCIpKTt2YXIgYT10aGlzLl9idWZmZXJDYW52YXMubm9kZSgpLmdldENvbnRleHQoIjJkIik7aWYoYSl7dmFyIHM9dGhpcy5fY2FudmFzLm5vZGUoKTtzLndpZHRoPjAmJnMuaGVpZ2h0PjA/YS5jYW52YXMud2lkdGg+MCYmYS5jYW52YXMuaGVpZ2h0PjAmJmEuZHJhd0ltYWdlKHMsMCwwKTpjb25zb2xlLndhcm4oIkZhaWxlZCB0byBmaWxsIGJ1ZmZlciBjYW52YXMgd2l0aCB3aXRoIDB4MCBjYW52YXMiKX10aGlzLl9idWZmZXJDYW52YXNWYWxpZD0hMH12YXIgbD13aW5kb3cuZGV2aWNlUGl4ZWxSYXRpbyE9bnVsbD93aW5kb3cuZGV2aWNlUGl4ZWxSYXRpbzoxO3RoaXMuX2NhbnZhcy5hdHRyKCJ3aWR0aCIscipsKSx0aGlzLl9jYW52YXMuYXR0cigiaGVpZ2h0IixuKmwpO3ZhciBjPXRoaXMuX2NhbnZhcy5ub2RlKCkuZ2V0Q29udGV4dCgiMmQiKTtpZihjJiYoYy5zZXRUcmFuc2Zvcm0obCwwLDAsbCwwLDApLHRoaXMuX2J1ZmZlckNhbnZhcykpe3ZhciB1PXRoaXMuX2J1ZmZlckNhbnZhcy5ub2RlKCk7dS53aWR0aD4wJiZ1LmhlaWdodD4wP2MuY2FudmFzLndpZHRoPjAmJmMuY2FudmFzLmhlaWdodD4wJiZjLmRyYXdJbWFnZSh1LDAsMCxyLG4pOmNvbnNvbGUud2FybigiRmFpbGVkIHRvIGZpbGwgY2FudmFzIHdpdGggMHgwIGJ1ZmZlciBjYW52YXMiKX19cmV0dXJuIHRoaXN9LHQucHJvdG90eXBlLmRlc3Ryb3k9ZnVuY3Rpb24oKXt2YXIgcj10aGlzO2UucHJvdG90eXBlLmRlc3Ryb3kuY2FsbCh0aGlzKSx0aGlzLl9zY2FsZXMoKS5mb3JFYWNoKGZ1bmN0aW9uKG4pe3JldHVybiBuLm9mZlVwZGF0ZShyLl9yZW5kZXJDYWxsYmFjayl9KSx0aGlzLmRhdGFzZXRzKFtdKX0sdC5wcm90b3R5cGUuX2NyZWF0ZU5vZGVzRm9yRGF0YXNldD1mdW5jdGlvbihyKXt2YXIgbj10aGlzLl9kYXRhc2V0VG9EcmF3ZXIuZ2V0KHIpO3JldHVybiB0aGlzLnJlbmRlcmVyKCk9PT0ic3ZnIj9uLnVzZVNWRyh0aGlzLl9yZW5kZXJBcmVhKTpuLnVzZUNhbnZhcyh0aGlzLl9jYW52YXMpLG59LHQucHJvdG90eXBlLl9jcmVhdGVEcmF3ZXI9ZnVuY3Rpb24ocil7cmV0dXJuIG5ldyBDSGUuUHJveHlEcmF3ZXIoZnVuY3Rpb24oKXtyZXR1cm4gbmV3IEFIZS5TVkdEcmF3ZXIoInBhdGgiLCIiKX0sZnVuY3Rpb24obil7cmV0dXJuIG5ldyBUSGUuQ2FudmFzRHJhd2VyKG4sZnVuY3Rpb24oKXt9KX0pfSx0LnByb3RvdHlwZS5fZ2V0QW5pbWF0b3I9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX2FuaW1hdGVPbk5leHRSZW5kZXIoKT90aGlzLl9hbmltYXRvcnNbcl18fG5ldyBwNC5OdWxsOm5ldyBwNC5OdWxsfSx0LnByb3RvdHlwZS5fb25EYXRhc2V0VXBkYXRlPWZ1bmN0aW9uKCl7dGhpcy5fdXBkYXRlRXh0ZW50cygpLHRoaXMuX2RhdGFDaGFuZ2VkPSEwLHRoaXMuX3Jlc2V0RW50aXR5U3RvcmUoKSx0aGlzLnJlbmRlckxvd1ByaW9yaXR5KCl9LHQucHJvdG90eXBlLmF0dHI9ZnVuY3Rpb24ocixuLGkpe3JldHVybiBuPT1udWxsP3RoaXMuX2F0dHJCaW5kaW5ncy5nZXQocik6KHRoaXMuX2JpbmRBdHRyKHIsbixpKSx0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5fYmluZFByb3BlcnR5PWZ1bmN0aW9uKHIsbixpLG8pe3ZhciBhPXRoaXMuX3Byb3BlcnR5QmluZGluZ3MuZ2V0KHIpLHM9YSE9bnVsbD9hLnNjYWxlOm51bGwsbD10eXBlb2Ygbj09ImZ1bmN0aW9uIj9uOmZ1bmN0aW9uKCl7cmV0dXJuIG59O3RoaXMuX3Byb3BlcnR5QmluZGluZ3Muc2V0KHIse2FjY2Vzc29yOmwsc2NhbGU6aSxwb3N0U2NhbGU6b30pLHMhPW51bGwmJnRoaXMuX3VuaW5zdGFsbFNjYWxlRm9yS2V5KHMsciksaSE9bnVsbCYmdGhpcy5faW5zdGFsbFNjYWxlRm9yS2V5KGksciksdGhpcy5fY2xlYXJBdHRyVG9Qcm9qZWN0b3JDYWNoZSgpfSx0LnByb3RvdHlwZS5fYmluZEF0dHI9ZnVuY3Rpb24ocixuLGkpe3ZhciBvPXRoaXMuX2F0dHJCaW5kaW5ncy5nZXQociksYT1vIT1udWxsP28uc2NhbGU6bnVsbCxzPXR5cGVvZiBuPT0iZnVuY3Rpb24iP246ZnVuY3Rpb24oKXtyZXR1cm4gbn07dGhpcy5fYXR0ckJpbmRpbmdzLnNldChyLHthY2Nlc3NvcjpzLHNjYWxlOml9KSxhIT1udWxsJiZ0aGlzLl91bmluc3RhbGxTY2FsZUZvcktleShhLHIpLGkhPW51bGwmJnRoaXMuX2luc3RhbGxTY2FsZUZvcktleShpLHIpLHRoaXMuX2NsZWFyQXR0clRvUHJvamVjdG9yQ2FjaGUoKX0sdC5wcm90b3R5cGUuX2NsZWFyQXR0clRvUHJvamVjdG9yQ2FjaGU9ZnVuY3Rpb24oKXtkZWxldGUgdGhpcy5fY2FjaGVkQXR0clRvUHJvamVjdG9yfSx0LnByb3RvdHlwZS5fZ2V0QXR0clRvUHJvamVjdG9yPWZ1bmN0aW9uKCl7aWYodGhpcy5fY2FjaGVkQXR0clRvUHJvamVjdG9yPT1udWxsKXt2YXIgcj10aGlzLl9nZW5lcmF0ZUF0dHJUb1Byb2plY3RvcigpO3QuT1BUSU1JWkVfTUVNT0laRV9QUk9KRUNUT1JTJiYocj1hYXQubWVtb2l6ZVByb2plY3RvcnMocikpLHRoaXMuX2NhY2hlZEF0dHJUb1Byb2plY3Rvcj1yfXJldHVybiBTZC5hc3NpZ24oe30sdGhpcy5fY2FjaGVkQXR0clRvUHJvamVjdG9yKX0sdC5wcm90b3R5cGUuX2dlbmVyYXRlQXR0clRvUHJvamVjdG9yPWZ1bmN0aW9uKCl7dmFyIHI9e307dGhpcy5fYXR0ckJpbmRpbmdzLmVhY2goZnVuY3Rpb24oaSxvKXtyW29dPXQuX3NjYWxlZEFjY2Vzc29yKGkpfSk7dmFyIG49dGhpcy5fcHJvcGVydHlQcm9qZWN0b3JzKCk7cmV0dXJuIE9iamVjdC5rZXlzKG4pLmZvckVhY2goZnVuY3Rpb24oaSl7cltpXT09bnVsbCYmKHJbaV09bltpXSl9KSxyfSx0LnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseT1mdW5jdGlvbigpe3JldHVybiBlLnByb3RvdHlwZS5yZW5kZXJJbW1lZGlhdGVseS5jYWxsKHRoaXMpLHRoaXMuX2lzQW5jaG9yZWQmJih0aGlzLl9wYWludCgpLHRoaXMuX2RhdGFDaGFuZ2VkPSExKSx0aGlzfSx0LnByb3RvdHlwZS5yZW5kZXJMb3dQcmlvcml0eT1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9yZW5kZXJDYWxsYmFjaygpLHRoaXN9LHQucHJvdG90eXBlLmFuaW1hdGVkPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2FuaW1hdGU6KHRoaXMuX2FuaW1hdGU9cix0aGlzKX0sdC5wcm90b3R5cGUuZGV0YWNoPWZ1bmN0aW9uKCl7cmV0dXJuIGUucHJvdG90eXBlLmRldGFjaC5jYWxsKHRoaXMpLHRoaXMuX3VwZGF0ZUV4dGVudHMoKSx0aGlzfSx0LnByb3RvdHlwZS5fc2NhbGVzPWZ1bmN0aW9uKCl7dmFyIHI9W107cmV0dXJuIHRoaXMuX2F0dHJCaW5kaW5ncy5lYWNoKGZ1bmN0aW9uKG4saSl7dmFyIG89bi5zY2FsZTtvIT1udWxsJiZyLmluZGV4T2Yobyk9PT0tMSYmci5wdXNoKG8pfSksdGhpcy5fcHJvcGVydHlCaW5kaW5ncy5lYWNoKGZ1bmN0aW9uKG4saSl7dmFyIG89bi5zY2FsZTtvIT1udWxsJiZyLmluZGV4T2Yobyk9PT0tMSYmci5wdXNoKG8pfSkscn0sdC5wcm90b3R5cGUuX3VwZGF0ZUV4dGVudHM9ZnVuY3Rpb24oKXt2YXIgcj10aGlzO3RoaXMuX3Jlc2V0RW50aXR5U3RvcmUoKSx0aGlzLl9zY2FsZXMoKS5mb3JFYWNoKGZ1bmN0aW9uKG4pe3JldHVybiBuLmFkZEluY2x1ZGVkVmFsdWVzUHJvdmlkZXIoci5faW5jbHVkZWRWYWx1ZXNQcm92aWRlcil9KX0sdC5wcm90b3R5cGUuX2ZpbHRlckZvclByb3BlcnR5PWZ1bmN0aW9uKHIpe3JldHVybiBudWxsfSx0LnByb3RvdHlwZS5nZXRFeHRlbnRzRm9yQXR0cj1mdW5jdGlvbihyKXt2YXIgbj10aGlzO2lmKHRoaXMuX2F0dHJFeHRlbnRzW3JdPT1udWxsKXt2YXIgaT1hYXQubWVtVGh1bmsoZnVuY3Rpb24oKXtyZXR1cm4gbi5kYXRhc2V0cygpfSxmdW5jdGlvbigpe3JldHVybiBuLl9hdHRyQmluZGluZ3MuZ2V0KHIpfSxmdW5jdGlvbihvLGEpe3JldHVybiBhPT1udWxsfHxhLmFjY2Vzc29yPT1udWxsP251bGw6by5tYXAoZnVuY3Rpb24ocyl7cmV0dXJuIHRLdChzLGEsbnVsbCl9KX0pO3RoaXMuX2F0dHJFeHRlbnRzW3JdPWl9cmV0dXJuIHRoaXMuX2F0dHJFeHRlbnRzW3JdKCl9LHQucHJvdG90eXBlLmdldEV4dGVudHNGb3JQcm9wZXJ0eT1mdW5jdGlvbihyKXt2YXIgbj10aGlzO2lmKHRoaXMuX3Byb3BlcnR5RXh0ZW50c1tyXT09bnVsbCl7dmFyIGk9YWF0Lm1lbVRodW5rKGZ1bmN0aW9uKCl7cmV0dXJuIG4uZGF0YXNldHMoKX0sZnVuY3Rpb24oKXtyZXR1cm4gbi5fcHJvcGVydHlCaW5kaW5ncy5nZXQocil9LGZ1bmN0aW9uKCl7cmV0dXJuIG4uX2ZpbHRlckZvclByb3BlcnR5KHIpfSxmdW5jdGlvbihvLGEscyl7cmV0dXJuIGE9PW51bGx8fGEuYWNjZXNzb3I9PW51bGw/bnVsbDpvLm1hcChmdW5jdGlvbihsKXtyZXR1cm4gdEt0KGwsYSxzKX0pfSk7dGhpcy5fcHJvcGVydHlFeHRlbnRzW3JdPWl9cmV0dXJuIHRoaXMuX3Byb3BlcnR5RXh0ZW50c1tyXSgpfSx0LnByb3RvdHlwZS5faW5jbHVkZWRWYWx1ZXNGb3JTY2FsZT1mdW5jdGlvbihyLG4pe3ZhciBpPXRoaXM7aWYoIXRoaXMuX2lzQW5jaG9yZWQmJiFuKXJldHVybltdO3ZhciBvPVtdO3JldHVybiB0aGlzLl9hdHRyQmluZGluZ3MuZWFjaChmdW5jdGlvbihhLHMpe2lmKGEuc2NhbGU9PT1yKXt2YXIgbD1pLmdldEV4dGVudHNGb3JBdHRyKHMpO2whPW51bGwmJihvPW8uY29uY2F0KHdkLm1lcmdlKGwpKSl9fSksdGhpcy5fcHJvcGVydHlCaW5kaW5ncy5lYWNoKGZ1bmN0aW9uKGEscyl7aWYoYS5zY2FsZT09PXIpe3ZhciBsPWkuZ2V0RXh0ZW50c0ZvclByb3BlcnR5KHMpO2whPW51bGwmJihvPW8uY29uY2F0KHdkLm1lcmdlKGwpKSl9fSksb30sdC5wcm90b3R5cGUuYW5pbWF0b3I9ZnVuY3Rpb24ocixuKXtyZXR1cm4gbj09PXZvaWQgMD90aGlzLl9hbmltYXRvcnNbcl06KHRoaXMuX2FuaW1hdG9yc1tyXT1uLHRoaXMpfSx0LnByb3RvdHlwZS5yZW5kZXJlcj1mdW5jdGlvbihyKXt2YXIgbj10aGlzO3JldHVybiByPT09dm9pZCAwP3RoaXMuX2NhbnZhcz09bnVsbD8ic3ZnIjoiY2FudmFzIjoodGhpcy5fY2FudmFzPT1udWxsJiZyPT09ImNhbnZhcyI/KHRoaXMuX2NhbnZhcz13ZC5zZWxlY3QoZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiY2FudmFzIikpLmNsYXNzZWQoInBsb3QtY2FudmFzIiwhMCksdGhpcy5fYnVmZmVyQ2FudmFzPXdkLnNlbGVjdChkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKSksdGhpcy5lbGVtZW50KCkhPW51bGwmJnRoaXMuX2FwcGVuZENhbnZhc05vZGUoKSx0aGlzLl9kYXRhc2V0VG9EcmF3ZXIuZm9yRWFjaChmdW5jdGlvbihpKXtpLnVzZUNhbnZhcyhuLl9jYW52YXMpfSksdGhpcy5yZW5kZXIoKSk6dGhpcy5fY2FudmFzIT1udWxsJiZyPT0ic3ZnIiYmKHRoaXMuX2NhbnZhcy5yZW1vdmUoKSx0aGlzLl9jYW52YXM9bnVsbCx0aGlzLl9idWZmZXJDYW52YXM9bnVsbCx0aGlzLl9kYXRhc2V0VG9EcmF3ZXIuZm9yRWFjaChmdW5jdGlvbihpKXtpLnVzZVNWRyhuLl9yZW5kZXJBcmVhKX0pLHRoaXMucmVuZGVyKCkpLHRoaXMpfSx0LnByb3RvdHlwZS5hZGREYXRhc2V0PWZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLl9hZGREYXRhc2V0KHIpLHRoaXMuX29uRGF0YXNldFVwZGF0ZSgpLHRoaXN9LHQucHJvdG90eXBlLl9hZGREYXRhc2V0PWZ1bmN0aW9uKHIpe3RoaXMuX3JlbW92ZURhdGFzZXQocik7dmFyIG49dGhpcy5fY3JlYXRlRHJhd2VyKHIpO3JldHVybiB0aGlzLl9kYXRhc2V0VG9EcmF3ZXIuc2V0KHIsbiksdGhpcy5faXNTZXR1cCYmdGhpcy5fY3JlYXRlTm9kZXNGb3JEYXRhc2V0KHIpLHIub25VcGRhdGUodGhpcy5fb25EYXRhc2V0VXBkYXRlQ2FsbGJhY2spLHRoaXN9LHQucHJvdG90eXBlLnJlbW92ZURhdGFzZXQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuX3JlbW92ZURhdGFzZXQociksdGhpcy5fb25EYXRhc2V0VXBkYXRlKCksdGhpc30sdC5wcm90b3R5cGUuX3JlbW92ZURhdGFzZXQ9ZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMuZGF0YXNldHMoKS5pbmRleE9mKHIpPT09LTE/dGhpczoodGhpcy5fcmVtb3ZlRGF0YXNldE5vZGVzKHIpLHIub2ZmVXBkYXRlKHRoaXMuX29uRGF0YXNldFVwZGF0ZUNhbGxiYWNrKSx0aGlzLl9kYXRhc2V0VG9EcmF3ZXIuZGVsZXRlKHIpLHRoaXMpfSx0LnByb3RvdHlwZS5fcmVtb3ZlRGF0YXNldE5vZGVzPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMuX2RhdGFzZXRUb0RyYXdlci5nZXQocik7bi5yZW1vdmUoKX0sdC5wcm90b3R5cGUuZGF0YXNldHM9ZnVuY3Rpb24ocil7dmFyIG49dGhpcyxpPVtdO3JldHVybiB0aGlzLl9kYXRhc2V0VG9EcmF3ZXIuZm9yRWFjaChmdW5jdGlvbihvLGEpe3JldHVybiBpLnB1c2goYSl9KSxyPT1udWxsP2k6KGkuZm9yRWFjaChmdW5jdGlvbihvKXtyZXR1cm4gbi5fcmVtb3ZlRGF0YXNldChvKX0pLHIuZm9yRWFjaChmdW5jdGlvbihvKXtyZXR1cm4gbi5fYWRkRGF0YXNldChvKX0pLHRoaXMuX29uRGF0YXNldFVwZGF0ZSgpLHRoaXMpfSx0LnByb3RvdHlwZS5fZ2VuZXJhdGVEcmF3U3RlcHM9ZnVuY3Rpb24oKXtyZXR1cm5be2F0dHJUb1Byb2plY3Rvcjp0aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKSxhbmltYXRvcjpuZXcgcDQuTnVsbH1dfSx0LnByb3RvdHlwZS5fYWRkaXRpb25hbFBhaW50PWZ1bmN0aW9uKHIpe30sdC5wcm90b3R5cGUuX2J1aWxkTGlnaHR3ZWlnaHRQbG90RW50aXRpZXM9ZnVuY3Rpb24ocil7dmFyIG49dGhpcyxpPVtdO3JldHVybiByLmZvckVhY2goZnVuY3Rpb24obyxhKXtmb3IodmFyIHM9bi5fZGF0YXNldFRvRHJhd2VyLmdldChvKSxsPTAsYz1vLmRhdGEoKSx1PWMubGVuZ3RoLGg9ZnVuY3Rpb24ocCl7dmFyIGQ9Y1twXSxnPW4uX3BpeGVsUG9pbnQoZCxwLG8pO2lmKFNkLk1hdGguaXNOYU4oZy54KXx8U2QuTWF0aC5pc05hTihnLnkpKXJldHVybiJjb250aW51ZSI7dmFyIF89bjtpLnB1c2goe2RhdHVtOmQsZ2V0IHBvc2l0aW9uKCl7cmV0dXJuIF8uX3BpeGVsUG9pbnQuY2FsbChfLGQscCxvKX0saW5kZXg6cCxkYXRhc2V0Om8sZGF0YXNldEluZGV4OmEsY29tcG9uZW50Om4sZHJhd2VyOnMsdmFsaWREYXR1bUluZGV4Omx9KSxsKyt9LGY9MDtmPHU7ZisrKWgoZil9KSxpfSx0LnByb3RvdHlwZS5fZ2V0RGF0YVRvRHJhdz1mdW5jdGlvbigpe3ZhciByPW5ldyBTZC5NYXA7cmV0dXJuIHRoaXMuZGF0YXNldHMoKS5mb3JFYWNoKGZ1bmN0aW9uKG4pe3JldHVybiByLnNldChuLG4uZGF0YSgpKX0pLHJ9LHQucHJvdG90eXBlLl9wYWludD1mdW5jdGlvbigpe3ZhciByPXRoaXM7ZGVsZXRlIHRoaXMuX2NhY2hlZEF0dHJUb1Byb2plY3Rvcjt2YXIgbj10aGlzLl9nZW5lcmF0ZURyYXdTdGVwcygpLGk9dGhpcy5fZ2V0RGF0YVRvRHJhdygpLG89dGhpcy5kYXRhc2V0cygpLm1hcChmdW5jdGlvbih1KXtyZXR1cm4gci5fZGF0YXNldFRvRHJhd2VyLmdldCh1KX0pO2lmKHRoaXMucmVuZGVyZXIoKT09PSJjYW52YXMiKXt2YXIgYT10aGlzLl9jYW52YXMubm9kZSgpLHM9YS5nZXRDb250ZXh0KCIyZCIpO3MuY2xlYXJSZWN0KDAsMCxhLmNsaWVudFdpZHRoLGEuY2xpZW50SGVpZ2h0KSx0aGlzLl9idWZmZXJDYW52YXNWYWxpZD0hMX10aGlzLmRhdGFzZXRzKCkuZm9yRWFjaChmdW5jdGlvbih1LGgpe3ZhciBmPXQuYXBwbHlEcmF3U3RlcHMobix1KTtvW2hdLmRyYXcoaS5nZXQodSksZil9KTt2YXIgbD10aGlzLmRhdGFzZXRzKCkubWFwKGZ1bmN0aW9uKHUsaCl7cmV0dXJuIHQuZ2V0VG90YWxEcmF3VGltZShpLmdldCh1KSxuKX0pLGM9U2QuTWF0aC5tYXgobCwwKTt0aGlzLl9hZGRpdGlvbmFsUGFpbnQoYyl9LHQucHJvdG90eXBlLnNlbGVjdGlvbnM9ZnVuY3Rpb24ocil7dmFyIG49dGhpcztpZihyPT09dm9pZCAwJiYocj10aGlzLmRhdGFzZXRzKCkpLHRoaXMucmVuZGVyZXIoKT09PSJjYW52YXMiKXJldHVybiB3ZC5zZWxlY3RBbGwoKTt2YXIgaT1bXTtyZXR1cm4gci5mb3JFYWNoKGZ1bmN0aW9uKG8pe3ZhciBhPW4uX2RhdGFzZXRUb0RyYXdlci5nZXQobyk7aWYoYSE9bnVsbCl7dmFyIHM9YS5nZXRWaXN1YWxQcmltaXRpdmVzKCk7aS5wdXNoLmFwcGx5KGkscyl9fSksd2Quc2VsZWN0QWxsKGkpfSx0LnByb3RvdHlwZS5lbnRpdGllcz1mdW5jdGlvbihyKXt2YXIgbj10aGlzO3JldHVybiB0aGlzLl9nZXRFbnRpdHlTdG9yZShyKS5lbnRpdGllcygpLm1hcChmdW5jdGlvbihpKXtyZXR1cm4gbi5fbGlnaHR3ZWlnaHRQbG90RW50aXR5VG9QbG90RW50aXR5KGkpfSl9LHQucHJvdG90eXBlLmZpbHRlckVudGl0aWVzPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXM7cmV0dXJuIHRoaXMuX2dldEVudGl0eVN0b3JlKCkuZW50aXRpZXMoKS5maWx0ZXIocikubWFwKGZ1bmN0aW9uKGkpe3JldHVybiBuLl9saWdodHdlaWdodFBsb3RFbnRpdHlUb1Bsb3RFbnRpdHkoaSl9KX0sdC5wcm90b3R5cGUuX2dldEVudGl0eVN0b3JlPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMsaT1mdW5jdGlvbihhKXtyZXR1cm4gbi5fZW50aXR5Qm91bmRzKGEpfTtpZihyIT09dm9pZCAwKXt2YXIgbz1uZXcgU2QuRW50aXR5U3RvcmU7cmV0dXJuIG8uYWRkQWxsKHRoaXMuX2J1aWxkTGlnaHR3ZWlnaHRQbG90RW50aXRpZXMociksaSx0aGlzLl9sb2NhbE9yaWdpbkJvdW5kcygpKSxvfWVsc2UgaWYodGhpcy5fY2FjaGVkRW50aXR5U3RvcmU9PT12b2lkIDApe3ZhciBvPW5ldyBTZC5FbnRpdHlTdG9yZTtvLmFkZEFsbCh0aGlzLl9idWlsZExpZ2h0d2VpZ2h0UGxvdEVudGl0aWVzKHRoaXMuZGF0YXNldHMoKSksaSx0aGlzLl9sb2NhbE9yaWdpbkJvdW5kcygpKSx0aGlzLl9jYWNoZWRFbnRpdHlTdG9yZT1vfXJldHVybiB0aGlzLl9jYWNoZWRFbnRpdHlTdG9yZX0sdC5wcm90b3R5cGUuX2xvY2FsT3JpZ2luQm91bmRzPWZ1bmN0aW9uKCl7cmV0dXJue3RvcExlZnQ6e3g6MCx5OjB9LGJvdHRvbVJpZ2h0Ont4OnRoaXMud2lkdGgoKSx5OnRoaXMuaGVpZ2h0KCl9fX0sdC5wcm90b3R5cGUuX2VudGl0eUJvdW5kcz1mdW5jdGlvbihyKXt2YXIgbj1yLmRhdHVtLGk9ci5pbmRleCxvPXIuZGF0YXNldCxhPXRoaXMuX3BpeGVsUG9pbnQobixpLG8pLHM9YS54LGw9YS55O3JldHVybnt4OnMseTpsLHdpZHRoOjAsaGVpZ2h0OjB9fSx0LnByb3RvdHlwZS5fbGlnaHR3ZWlnaHRQbG90RW50aXR5VG9QbG90RW50aXR5PWZ1bmN0aW9uKHIpe3ZhciBuPXtib3VuZHM6dGhpcy5fZW50aXR5Qm91bmRzKHIpLGNvbXBvbmVudDpyLmNvbXBvbmVudCxkYXRhc2V0OnIuZGF0YXNldCxkYXRhc2V0SW5kZXg6ci5kYXRhc2V0SW5kZXgsZGF0dW06ci5kYXR1bSxpbmRleDpyLmluZGV4LHBvc2l0aW9uOnIucG9zaXRpb24sc2VsZWN0aW9uOndkLnNlbGVjdChyLmRyYXdlci5nZXRWaXN1YWxQcmltaXRpdmVBdEluZGV4KHIudmFsaWREYXR1bUluZGV4KSl9O3JldHVybiBufSx0LnByb3RvdHlwZS5lbnRpdGllc0F0PWZ1bmN0aW9uKHIpe3Rocm93IG5ldyBFcnJvcigicGxvdHMgbXVzdCBpbXBsZW1lbnQgZW50aXRpZXNBdCIpfSx0LnByb3RvdHlwZS5lbnRpdHlOZWFyZXN0PWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMuX2dldEVudGl0eVN0b3JlKCkuZW50aXR5TmVhcmVzdChyKTtyZXR1cm4gbj09PXZvaWQgMD92b2lkIDA6dGhpcy5fbGlnaHR3ZWlnaHRQbG90RW50aXR5VG9QbG90RW50aXR5KG4pfSx0LnByb3RvdHlwZS5lbnRpdGllc0luPWZ1bmN0aW9uKHIsbil7dmFyIGk7aWYobj09bnVsbCl7dmFyIG89cjtpPXt4Om8udG9wTGVmdC54LHk6by50b3BMZWZ0Lnksd2lkdGg6by5ib3R0b21SaWdodC54LW8udG9wTGVmdC54LGhlaWdodDpvLmJvdHRvbVJpZ2h0Lnktby50b3BMZWZ0Lnl9fWVsc2V7dmFyIGE9cjtpPXt4OmEubWluLHk6bi5taW4sd2lkdGg6YS5tYXgtYS5taW4saGVpZ2h0Om4ubWF4LW4ubWlufX1yZXR1cm4gdGhpcy5lbnRpdGllc0luQm91bmRzKGkpfSx0LnByb3RvdHlwZS5lbnRpdGllc0luQm91bmRzPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMsaT10aGlzLl9nZXRFbnRpdHlTdG9yZSgpLmVudGl0aWVzSW5Cb3VuZHMocik7aWYoISFpKXJldHVybiBpLm1hcChmdW5jdGlvbihvKXtyZXR1cm4gbi5fbGlnaHR3ZWlnaHRQbG90RW50aXR5VG9QbG90RW50aXR5KG8pfSl9LHQucHJvdG90eXBlLmVudGl0aWVzSW5YQm91bmRzPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMsaT10aGlzLl9nZXRFbnRpdHlTdG9yZSgpLmVudGl0aWVzSW5YQm91bmRzKHIpO2lmKCEhaSlyZXR1cm4gaS5tYXAoZnVuY3Rpb24obyl7cmV0dXJuIG4uX2xpZ2h0d2VpZ2h0UGxvdEVudGl0eVRvUGxvdEVudGl0eShvKX0pfSx0LnByb3RvdHlwZS5lbnRpdGllc0luWUJvdW5kcz1mdW5jdGlvbihyKXt2YXIgbj10aGlzLGk9dGhpcy5fZ2V0RW50aXR5U3RvcmUoKS5lbnRpdGllc0luWUJvdW5kcyhyKTtpZighIWkpcmV0dXJuIGkubWFwKGZ1bmN0aW9uKG8pe3JldHVybiBuLl9saWdodHdlaWdodFBsb3RFbnRpdHlUb1Bsb3RFbnRpdHkobyl9KX0sdC5wcm90b3R5cGUuX3VuaW5zdGFsbFNjYWxlRm9yS2V5PWZ1bmN0aW9uKHIsbil7ci5vZmZVcGRhdGUodGhpcy5fcmVuZGVyQ2FsbGJhY2spLHIub2ZmVXBkYXRlKHRoaXMuX2RlZmVycmVkUmVzZXRFbnRpdHlTdG9yZSksci5yZW1vdmVJbmNsdWRlZFZhbHVlc1Byb3ZpZGVyKHRoaXMuX2luY2x1ZGVkVmFsdWVzUHJvdmlkZXIpfSx0LnByb3RvdHlwZS5faW5zdGFsbFNjYWxlRm9yS2V5PWZ1bmN0aW9uKHIsbil7ci5vblVwZGF0ZSh0aGlzLl9yZW5kZXJDYWxsYmFjayksci5vblVwZGF0ZSh0aGlzLl9kZWZlcnJlZFJlc2V0RW50aXR5U3RvcmUpLHIuYWRkSW5jbHVkZWRWYWx1ZXNQcm92aWRlcih0aGlzLl9pbmNsdWRlZFZhbHVlc1Byb3ZpZGVyKX0sdC5wcm90b3R5cGUuX3Byb3BlcnR5UHJvamVjdG9ycz1mdW5jdGlvbigpe3JldHVybnt9fSx0Ll9zY2FsZWRBY2Nlc3Nvcj1mdW5jdGlvbihyKXt2YXIgbj1yLnNjYWxlLGk9ci5hY2Nlc3NvcixvPXIucG9zdFNjYWxlLGE9bj09bnVsbD9pOmZ1bmN0aW9uKGwsYyx1KXtyZXR1cm4gbi5zY2FsZShpKGwsYyx1KSl9LHM9bz09bnVsbD9hOmZ1bmN0aW9uKGwsYyx1KXtyZXR1cm4gbyhhKGwsYyx1KSxsLGMsdSl9O3JldHVybiBzfSx0LnByb3RvdHlwZS5fcGl4ZWxQb2ludD1mdW5jdGlvbihyLG4saSl7cmV0dXJue3g6MCx5OjB9fSx0LnByb3RvdHlwZS5fYW5pbWF0ZU9uTmV4dFJlbmRlcj1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9hbmltYXRlJiZ0aGlzLl9kYXRhQ2hhbmdlZH0sdC5PUFRJTUlaRV9NRU1PSVpFX1BST0pFQ1RPUlM9ITEsdC5fQU5JTUFUSU9OX01BWF9EVVJBVElPTj02MDAsdH0oRUhlLkNvbXBvbmVudCk7SkYuUGxvdD1rSGU7ZnVuY3Rpb24gdEt0KGUsdCxyKXt2YXIgbj10LmFjY2Vzc29yLGk9dC5zY2FsZTtpZihpPT1udWxsKXJldHVybltdO3ZhciBvPWUuZGF0YSgpO3IhPW51bGwmJihvPW8uZmlsdGVyKGZ1bmN0aW9uKGwsYyl7cmV0dXJuIHIobCxjLGUpfSkpO3ZhciBhPWZ1bmN0aW9uKGwsYyl7cmV0dXJuIG4obCxjLGUpfSxzPW8ubWFwKGEpO3JldHVybiBpLmV4dGVudE9mVmFsdWVzKHMpfX0pO3ZhciBlS3Q9SChzYXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHNhdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFJIZT0oZGUoKSxVdChwZSkpLE5IZT1ycygpLERIZT1GZSgpLE9IZT1Gb3QoKSx6SGU9ZnVuY3Rpb24oZSl7UkhlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXtyZXR1cm4gZSE9PW51bGwmJmUuYXBwbHkodGhpcyxhcmd1bWVudHMpfHx0aGlzfXJldHVybiB0LnByb3RvdHlwZS5lbnRpdHlOZWFyZXN0PWZ1bmN0aW9uKHIpe3ZhciBuLGk9MS8wO3JldHVybiB0aGlzLmNvbXBvbmVudHMoKS5mb3JFYWNoKGZ1bmN0aW9uKG8pe3ZhciBhPW8scz1hLmVudGl0eU5lYXJlc3Qocik7aWYocyE9bnVsbCl7dmFyIGw9REhlLk1hdGguZGlzdGFuY2VTcXVhcmVkKHMucG9zaXRpb24scik7bDw9aSYmKGk9bCxuPXMpfX0pLG59LHQucHJvdG90eXBlLmFwcGVuZD1mdW5jdGlvbihyKXtpZihyIT1udWxsJiYhKHIgaW5zdGFuY2VvZiBOSGUuUGxvdCkpdGhyb3cgbmV3IEVycm9yKCJQbG90IEdyb3VwIG9ubHkgYWNjZXB0cyBwbG90cyIpO3JldHVybiBlLnByb3RvdHlwZS5hcHBlbmQuY2FsbCh0aGlzLHIpLHRoaXN9LHR9KE9IZS5Hcm91cCk7c2F0LlBsb3RHcm91cD16SGV9KTt2YXIgckt0PUgobGF0PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShsYXQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBGSGU9KGRlKCksVXQocGUpKSxxdT0oRXIoKSxVdChNcikpLEdvPUZlKCksQkhlPUdGKCksSEhlPWZ1bmN0aW9uKGUpe0ZIZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3I9PT12b2lkIDAmJihyPVtdKTt2YXIgbj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIG4uX3Jvd1BhZGRpbmc9MCxuLl9jb2x1bW5QYWRkaW5nPTAsbi5fcm93cz1bXSxuLl9yb3dXZWlnaHRzPVtdLG4uX2NvbHVtbldlaWdodHM9W10sbi5fblJvd3M9MCxuLl9uQ29scz0wLG4uX2NhbGN1bGF0ZWRMYXlvdXQ9bnVsbCxuLmFkZENsYXNzKCJ0YWJsZSIpLHIuZm9yRWFjaChmdW5jdGlvbihpLG8pe2kuZm9yRWFjaChmdW5jdGlvbihhLHMpe2EhPW51bGwmJm4uYWRkKGEsbyxzKX0pfSksbn1yZXR1cm4gdC5wcm90b3R5cGUuX2ZvckVhY2g9ZnVuY3Rpb24ocil7Zm9yKHZhciBuPTA7bjx0aGlzLl9uUm93cztuKyspZm9yKHZhciBpPTA7aTx0aGlzLl9uQ29scztpKyspdGhpcy5fcm93c1tuXVtpXSE9bnVsbCYmcih0aGlzLl9yb3dzW25dW2ldKX0sdC5wcm90b3R5cGUuaGFzPWZ1bmN0aW9uKHIpe2Zvcih2YXIgbj0wO248dGhpcy5fblJvd3M7bisrKWZvcih2YXIgaT0wO2k8dGhpcy5fbkNvbHM7aSsrKWlmKHRoaXMuX3Jvd3Nbbl1baV09PT1yKXJldHVybiEwO3JldHVybiExfSx0LnByb3RvdHlwZS5jb21wb25lbnRBdD1mdW5jdGlvbihyLG4pe3JldHVybiByPDB8fHI+PXRoaXMuX25Sb3dzfHxuPDB8fG4+PXRoaXMuX25Db2xzP251bGw6dGhpcy5fcm93c1tyXVtuXX0sdC5wcm90b3R5cGUuYWRkPWZ1bmN0aW9uKHIsbixpKXtpZihyPT1udWxsKXRocm93IEVycm9yKCJDYW5ub3QgYWRkIG51bGwgdG8gYSB0YWJsZSBjZWxsIik7aWYoIXRoaXMuaGFzKHIpKXt2YXIgbz10aGlzLl9yb3dzW25dJiZ0aGlzLl9yb3dzW25dW2ldO2lmKG8hPW51bGwpdGhyb3cgbmV3IEVycm9yKCJjZWxsIGlzIG9jY3VwaWVkIik7ci5kZXRhY2goKSx0aGlzLl9uUm93cz1NYXRoLm1heChuKzEsdGhpcy5fblJvd3MpLHRoaXMuX25Db2xzPU1hdGgubWF4KGkrMSx0aGlzLl9uQ29scyksdGhpcy5fcGFkVGFibGVUb1NpemUodGhpcy5fblJvd3MsdGhpcy5fbkNvbHMpLHRoaXMuX3Jvd3Nbbl1baV09cix0aGlzLl9hZG9wdEFuZEFuY2hvcihyKSx0aGlzLnJlZHJhdygpfXJldHVybiB0aGlzfSx0LnByb3RvdHlwZS5fcmVtb3ZlPWZ1bmN0aW9uKHIpe2Zvcih2YXIgbj0wO248dGhpcy5fblJvd3M7bisrKWZvcih2YXIgaT0wO2k8dGhpcy5fbkNvbHM7aSsrKWlmKHRoaXMuX3Jvd3Nbbl1baV09PT1yKXJldHVybiB0aGlzLl9yb3dzW25dW2ldPW51bGwsITA7cmV0dXJuITF9LHQucHJvdG90eXBlLl9pdGVyYXRlTGF5b3V0PWZ1bmN0aW9uKHIsbixpKXtpPT09dm9pZCAwJiYoaT0hMSk7Zm9yKHZhciBvPXRoaXMuX3Jvd3MsYT1xdS50cmFuc3Bvc2UodGhpcy5fcm93cykscz1yLXRoaXMuX2NvbHVtblBhZGRpbmcqKHRoaXMuX25Db2xzLTEpLGw9bi10aGlzLl9yb3dQYWRkaW5nKih0aGlzLl9uUm93cy0xKSxjPXQuX2NhbGNDb21wb25lbnRXZWlnaHRzKHRoaXMuX3Jvd1dlaWdodHMsbyxmdW5jdGlvbih6KXtyZXR1cm4gej09bnVsbHx8ei5maXhlZEhlaWdodCgpfSksdT10Ll9jYWxjQ29tcG9uZW50V2VpZ2h0cyh0aGlzLl9jb2x1bW5XZWlnaHRzLGEsZnVuY3Rpb24oeil7cmV0dXJuIHo9PW51bGx8fHouZml4ZWRXaWR0aCgpfSksaD11Lm1hcChmdW5jdGlvbih6KXtyZXR1cm4gej09PTA/LjU6en0pLGY9Yy5tYXAoZnVuY3Rpb24oeil7cmV0dXJuIHo9PT0wPy41Onp9KSxwPXQuX2NhbGNQcm9wb3J0aW9uYWxTcGFjZShoLHMpLGQ9dC5fY2FsY1Byb3BvcnRpb25hbFNwYWNlKGYsbCksZz1Hby5BcnJheS5jcmVhdGVGaWxsZWRBcnJheSgwLHRoaXMuX25Db2xzKSxfPUdvLkFycmF5LmNyZWF0ZUZpbGxlZEFycmF5KDAsdGhpcy5fblJvd3MpLHkseCxiPTAsUyxDLFA7Oyl7dmFyIGs9R28uQXJyYXkuYWRkKF8sZCksTz1Hby5BcnJheS5hZGQoZyxwKTtTPXRoaXMuX2RldGVybWluZUd1YXJhbnRlZXMoTyxrLGkpLGc9Uy5ndWFyYW50ZWVkV2lkdGhzLF89Uy5ndWFyYW50ZWVkSGVpZ2h0cyxDPVMud2FudHNXaWR0aEFyci5zb21lKGZ1bmN0aW9uKHope3JldHVybiB6fSksUD1TLndhbnRzSGVpZ2h0QXJyLnNvbWUoZnVuY3Rpb24oeil7cmV0dXJuIHp9KTt2YXIgRD15LEI9eDt5PXMtcXUuc3VtKFMuZ3VhcmFudGVlZFdpZHRocykseD1sLXF1LnN1bShTLmd1YXJhbnRlZWRIZWlnaHRzKTt2YXIgST12b2lkIDA7Qz8oST1TLndhbnRzV2lkdGhBcnIubWFwKGZ1bmN0aW9uKHope3JldHVybiB6Py4xOjB9KSxJPUdvLkFycmF5LmFkZChJLHUpKTpJPXU7dmFyIEw9dm9pZCAwO1A/KEw9Uy53YW50c0hlaWdodEFyci5tYXAoZnVuY3Rpb24oeil7cmV0dXJuIHo/LjE6MH0pLEw9R28uQXJyYXkuYWRkKEwsYykpOkw9YyxwPXQuX2NhbGNQcm9wb3J0aW9uYWxTcGFjZShJLHkpLGQ9dC5fY2FsY1Byb3BvcnRpb25hbFNwYWNlKEwseCksYisrO3ZhciBSPXk+MCYmeSE9PUQsRj14PjAmJnghPT1CO2lmKCEoUnx8Ril8fGI+NSlicmVha31yZXR1cm4geT1zLXF1LnN1bShTLmd1YXJhbnRlZWRXaWR0aHMpLHg9bC1xdS5zdW0oUy5ndWFyYW50ZWVkSGVpZ2h0cykscD10Ll9jYWxjUHJvcG9ydGlvbmFsU3BhY2UodSx5KSxkPXQuX2NhbGNQcm9wb3J0aW9uYWxTcGFjZShjLHgpLHtjb2xQcm9wb3J0aW9uYWxTcGFjZTpwLHJvd1Byb3BvcnRpb25hbFNwYWNlOmQsZ3VhcmFudGVlZFdpZHRoczpTLmd1YXJhbnRlZWRXaWR0aHMsZ3VhcmFudGVlZEhlaWdodHM6Uy5ndWFyYW50ZWVkSGVpZ2h0cyx3YW50c1dpZHRoOkMsd2FudHNIZWlnaHQ6UH19LHQucHJvdG90eXBlLl9kZXRlcm1pbmVHdWFyYW50ZWVzPWZ1bmN0aW9uKHIsbixpKXtpPT09dm9pZCAwJiYoaT0hMSk7dmFyIG89R28uQXJyYXkuY3JlYXRlRmlsbGVkQXJyYXkoMCx0aGlzLl9uQ29scyksYT1Hby5BcnJheS5jcmVhdGVGaWxsZWRBcnJheSgwLHRoaXMuX25Sb3dzKSxzPUdvLkFycmF5LmNyZWF0ZUZpbGxlZEFycmF5KCExLHRoaXMuX25Db2xzKSxsPUdvLkFycmF5LmNyZWF0ZUZpbGxlZEFycmF5KCExLHRoaXMuX25Sb3dzKTtyZXR1cm4gdGhpcy5fcm93cy5mb3JFYWNoKGZ1bmN0aW9uKGMsdSl7Yy5mb3JFYWNoKGZ1bmN0aW9uKGgsZil7dmFyIHA7aCE9bnVsbD9wPWgucmVxdWVzdGVkU3BhY2UocltmXSxuW3VdKTpwPXttaW5XaWR0aDowLG1pbkhlaWdodDowfTt2YXIgZD1pP01hdGgubWluKHAubWluV2lkdGgscltmXSk6cC5taW5XaWR0aDtvW2ZdPU1hdGgubWF4KG9bZl0sZCk7dmFyIGc9aT9NYXRoLm1pbihwLm1pbkhlaWdodCxuW3VdKTpwLm1pbkhlaWdodDthW3VdPU1hdGgubWF4KGFbdV0sZyk7dmFyIF89cC5taW5XaWR0aD5yW2ZdO3NbZl09c1tmXXx8Xzt2YXIgeT1wLm1pbkhlaWdodD5uW3VdO2xbdV09bFt1XXx8eX0pfSkse2d1YXJhbnRlZWRXaWR0aHM6byxndWFyYW50ZWVkSGVpZ2h0czphLHdhbnRzV2lkdGhBcnI6cyx3YW50c0hlaWdodEFycjpsfX0sdC5wcm90b3R5cGUucmVxdWVzdGVkU3BhY2U9ZnVuY3Rpb24ocixuKXtyZXR1cm4gdGhpcy5fY2FsY3VsYXRlZExheW91dD10aGlzLl9pdGVyYXRlTGF5b3V0KHIsbikse21pbldpZHRoOnF1LnN1bSh0aGlzLl9jYWxjdWxhdGVkTGF5b3V0Lmd1YXJhbnRlZWRXaWR0aHMpLG1pbkhlaWdodDpxdS5zdW0odGhpcy5fY2FsY3VsYXRlZExheW91dC5ndWFyYW50ZWVkSGVpZ2h0cyl9fSx0LnByb3RvdHlwZS5jb21wdXRlTGF5b3V0PWZ1bmN0aW9uKHIsbixpKXt2YXIgbz10aGlzO2UucHJvdG90eXBlLmNvbXB1dGVMYXlvdXQuY2FsbCh0aGlzLHIsbixpKTt2YXIgYT1xdS5zdW0odGhpcy5fY2FsY3VsYXRlZExheW91dC5ndWFyYW50ZWVkV2lkdGhzKSxzPXF1LnN1bSh0aGlzLl9jYWxjdWxhdGVkTGF5b3V0Lmd1YXJhbnRlZWRIZWlnaHRzKSxsPXRoaXMuX2NhbGN1bGF0ZWRMYXlvdXQ7KGE+dGhpcy53aWR0aCgpfHxzPnRoaXMuaGVpZ2h0KCkpJiYobD10aGlzLl9pdGVyYXRlTGF5b3V0KHRoaXMud2lkdGgoKSx0aGlzLmhlaWdodCgpLCEwKSk7dmFyIGM9MCx1PUdvLkFycmF5LmFkZChsLnJvd1Byb3BvcnRpb25hbFNwYWNlLGwuZ3VhcmFudGVlZEhlaWdodHMpLGg9R28uQXJyYXkuYWRkKGwuY29sUHJvcG9ydGlvbmFsU3BhY2UsbC5ndWFyYW50ZWVkV2lkdGhzKTtyZXR1cm4gdGhpcy5fcm93cy5mb3JFYWNoKGZ1bmN0aW9uKGYscCl7dmFyIGQ9MDtmLmZvckVhY2goZnVuY3Rpb24oZyxfKXtnIT1udWxsJiZnLmNvbXB1dGVMYXlvdXQoe3g6ZCx5OmN9LGhbX10sdVtwXSksZCs9aFtfXStvLl9jb2x1bW5QYWRkaW5nfSksYys9dVtwXStvLl9yb3dQYWRkaW5nfSksdGhpc30sdC5wcm90b3R5cGUucm93UGFkZGluZz1mdW5jdGlvbihyKXtpZihyPT1udWxsKXJldHVybiB0aGlzLl9yb3dQYWRkaW5nO2lmKCFHby5NYXRoLmlzVmFsaWROdW1iZXIocil8fHI8MCl0aHJvdyBFcnJvcigicm93UGFkZGluZyBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIGZpbml0ZSB2YWx1ZSIpO3JldHVybiB0aGlzLl9yb3dQYWRkaW5nPXIsdGhpcy5yZWRyYXcoKSx0aGlzfSx0LnByb3RvdHlwZS5jb2x1bW5QYWRkaW5nPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX2NvbHVtblBhZGRpbmc7aWYoIUdvLk1hdGguaXNWYWxpZE51bWJlcihyKXx8cjwwKXRocm93IEVycm9yKCJjb2x1bW5QYWRkaW5nIG11c3QgYmUgYSBub24tbmVnYXRpdmUgZmluaXRlIHZhbHVlIik7cmV0dXJuIHRoaXMuX2NvbHVtblBhZGRpbmc9cix0aGlzLnJlZHJhdygpLHRoaXN9LHQucHJvdG90eXBlLnJvd1dlaWdodD1mdW5jdGlvbihyLG4pe2lmKG49PW51bGwpcmV0dXJuIHRoaXMuX3Jvd1dlaWdodHNbcl07aWYoIUdvLk1hdGguaXNWYWxpZE51bWJlcihuKXx8bjwwKXRocm93IEVycm9yKCJyb3dXZWlnaHQgbXVzdCBiZSBhIG5vbi1uZWdhdGl2ZSBmaW5pdGUgdmFsdWUiKTtyZXR1cm4gdGhpcy5fcm93V2VpZ2h0c1tyXT1uLHRoaXMucmVkcmF3KCksdGhpc30sdC5wcm90b3R5cGUuY29sdW1uV2VpZ2h0PWZ1bmN0aW9uKHIsbil7aWYobj09bnVsbClyZXR1cm4gdGhpcy5fY29sdW1uV2VpZ2h0c1tyXTtpZighR28uTWF0aC5pc1ZhbGlkTnVtYmVyKG4pfHxuPDApdGhyb3cgRXJyb3IoImNvbHVtbldlaWdodCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIGZpbml0ZSB2YWx1ZSIpO3JldHVybiB0aGlzLl9jb2x1bW5XZWlnaHRzW3JdPW4sdGhpcy5yZWRyYXcoKSx0aGlzfSx0LnByb3RvdHlwZS5maXhlZFdpZHRoPWZ1bmN0aW9uKCl7dmFyIHI9cXUudHJhbnNwb3NlKHRoaXMuX3Jvd3MpO3JldHVybiB0Ll9maXhlZFNwYWNlKHIsZnVuY3Rpb24obil7cmV0dXJuIG49PW51bGx8fG4uZml4ZWRXaWR0aCgpfSl9LHQucHJvdG90eXBlLmZpeGVkSGVpZ2h0PWZ1bmN0aW9uKCl7cmV0dXJuIHQuX2ZpeGVkU3BhY2UodGhpcy5fcm93cyxmdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbHx8ci5maXhlZEhlaWdodCgpfSl9LHQucHJvdG90eXBlLl9wYWRUYWJsZVRvU2l6ZT1mdW5jdGlvbihyLG4pe2Zvcih2YXIgaT0wO2k8cjtpKyspe3RoaXMuX3Jvd3NbaV09PT12b2lkIDAmJih0aGlzLl9yb3dzW2ldPVtdLHRoaXMuX3Jvd1dlaWdodHNbaV09bnVsbCk7Zm9yKHZhciBvPTA7bzxuO28rKyl0aGlzLl9yb3dzW2ldW29dPT09dm9pZCAwJiYodGhpcy5fcm93c1tpXVtvXT1udWxsKX1mb3IodmFyIG89MDtvPG47bysrKXRoaXMuX2NvbHVtbldlaWdodHNbb109PT12b2lkIDAmJih0aGlzLl9jb2x1bW5XZWlnaHRzW29dPW51bGwpfSx0Ll9jYWxjQ29tcG9uZW50V2VpZ2h0cz1mdW5jdGlvbihyLG4saSl7cmV0dXJuIHIubWFwKGZ1bmN0aW9uKG8sYSl7aWYobyE9bnVsbClyZXR1cm4gbzt2YXIgcz1uW2FdLm1hcChpKSxsPXMucmVkdWNlKGZ1bmN0aW9uKGMsdSl7cmV0dXJuIGMmJnV9LCEwKTtyZXR1cm4gbD8wOjF9KX0sdC5fY2FsY1Byb3BvcnRpb25hbFNwYWNlPWZ1bmN0aW9uKHIsbil7dmFyIGk9cXUuc3VtKHIpO3JldHVybiBpPT09MD9Hby5BcnJheS5jcmVhdGVGaWxsZWRBcnJheSgwLHIubGVuZ3RoKTpyLm1hcChmdW5jdGlvbihvKXtyZXR1cm4gbipvL2l9KX0sdC5fZml4ZWRTcGFjZT1mdW5jdGlvbihyLG4pe3ZhciBpPWZ1bmN0aW9uKGEpe3JldHVybiBhLnJlZHVjZShmdW5jdGlvbihzLGwpe3JldHVybiBzJiZsfSwhMCl9LG89ZnVuY3Rpb24oYSl7cmV0dXJuIGkoYS5tYXAobikpfTtyZXR1cm4gaShyLm1hcChvKSl9LHR9KEJIZS5Db21wb25lbnRDb250YWluZXIpO2xhdC5UYWJsZT1ISGV9KTt2YXIgbkt0PUgoY2F0PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShjYXQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBWSGU9KGRlKCksVXQocGUpKSxVSGU9VW90KCkscUhlPTIsR0hlPWZ1bmN0aW9uKGUpe1ZIZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7dmFyIHI9ZSE9PW51bGwmJmUuYXBwbHkodGhpcyxhcmd1bWVudHMpfHx0aGlzO3JldHVybiByLl9tYXhMaW5lcz1xSGUscn1yZXR1cm4gdC5wcm90b3R5cGUucmVxdWVzdGVkU3BhY2U9ZnVuY3Rpb24ocixuKXt0aGlzLl93cmFwcGVyLm1heExpbmVzKHRoaXMuX21heExpbmVzKTt2YXIgaT10aGlzLmFuZ2xlKCk9PT0wP3I6bjtpPT09MCYmKGk9MS8wKTt2YXIgbz10aGlzLl93cmFwcGVyLndyYXAodGhpcy5fdGV4dCx0aGlzLl9tZWFzdXJlcixpKSxhPXRoaXMuX21lYXN1cmVyLm1lYXN1cmUoby53cmFwcGVkVGV4dCkscz0odGhpcy5hbmdsZSgpPT09MD9hLndpZHRoOmEuaGVpZ2h0KSsyKnRoaXMucGFkZGluZygpLGw9KHRoaXMuYW5nbGUoKT09PTA/YS5oZWlnaHQ6YS53aWR0aCkrMip0aGlzLnBhZGRpbmcoKTtyZXR1cm57bWluV2lkdGg6cyxtaW5IZWlnaHQ6bH19LHQucHJvdG90eXBlLm1heExpbmVzPWZ1bmN0aW9uKHIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MD90aGlzLl9tYXhMaW5lczoodGhpcy5fbWF4TGluZXM9cix0aGlzLnJlZHJhdygpLHRoaXMpfSx0fShVSGUuTGFiZWwpO2NhdC5XcmFwcGVkTGFiZWw9R0hlfSk7dmFyIGlLdD1IKHVhdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkodWF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgV0hlPShkZSgpLFV0KHBlKSksWUhlPVVGKCksakhlPWZ1bmN0aW9uKGUpe1dIZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7dmFyIHI9ZS5jYWxsKHRoaXMpfHx0aGlzO3JldHVybiByLmFkZENsYXNzKCJ4LWRyYWctYm94LWxheWVyIiksci5faGFzQ29ybmVycz0hMSxyfXJldHVybiB0LnByb3RvdHlwZS5jb21wdXRlTGF5b3V0PWZ1bmN0aW9uKHIsbixpKXtyZXR1cm4gZS5wcm90b3R5cGUuY29tcHV0ZUxheW91dC5jYWxsKHRoaXMscixuLGkpLHRoaXMuX3NldEJvdW5kcyh0aGlzLmJvdW5kcygpKSx0aGlzfSx0LnByb3RvdHlwZS5fc2V0Qm91bmRzPWZ1bmN0aW9uKHIpe2UucHJvdG90eXBlLl9zZXRCb3VuZHMuY2FsbCh0aGlzLHt0b3BMZWZ0Ont4OnIudG9wTGVmdC54LHk6MH0sYm90dG9tUmlnaHQ6e3g6ci5ib3R0b21SaWdodC54LHk6dGhpcy5oZWlnaHQoKX19KX0sdC5wcm90b3R5cGUuX3NldFJlc2l6YWJsZUNsYXNzZXM9ZnVuY3Rpb24ocil7ciYmdGhpcy5lbmFibGVkKCk/dGhpcy5hZGRDbGFzcygieC1yZXNpemFibGUiKTp0aGlzLnJlbW92ZUNsYXNzKCJ4LXJlc2l6YWJsZSIpfSx0LnByb3RvdHlwZS55U2NhbGU9ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gZS5wcm90b3R5cGUueVNjYWxlLmNhbGwodGhpcyk7dGhyb3cgbmV3IEVycm9yKCJ5U2NhbGVzIGNhbm5vdCBiZSBzZXQgb24gYW4gWERyYWdCb3hMYXllciIpfSx0LnByb3RvdHlwZS55RXh0ZW50PWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIGUucHJvdG90eXBlLnlFeHRlbnQuY2FsbCh0aGlzKTt0aHJvdyBuZXcgRXJyb3IoIlhEcmFnQm94TGF5ZXIgaGFzIG5vIHlFeHRlbnQiKX0sdH0oWUhlLkRyYWdCb3hMYXllcik7dWF0LlhEcmFnQm94TGF5ZXI9akhlfSk7dmFyIG9LdD1IKGhhdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoaGF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgWEhlPShkZSgpLFV0KHBlKSksJEhlPVVGKCksS0hlPWZ1bmN0aW9uKGUpe1hIZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7dmFyIHI9ZS5jYWxsKHRoaXMpfHx0aGlzO3JldHVybiByLmFkZENsYXNzKCJ5LWRyYWctYm94LWxheWVyIiksci5faGFzQ29ybmVycz0hMSxyfXJldHVybiB0LnByb3RvdHlwZS5jb21wdXRlTGF5b3V0PWZ1bmN0aW9uKHIsbixpKXtyZXR1cm4gZS5wcm90b3R5cGUuY29tcHV0ZUxheW91dC5jYWxsKHRoaXMscixuLGkpLHRoaXMuX3NldEJvdW5kcyh0aGlzLmJvdW5kcygpKSx0aGlzfSx0LnByb3RvdHlwZS5fc2V0Qm91bmRzPWZ1bmN0aW9uKHIpe2UucHJvdG90eXBlLl9zZXRCb3VuZHMuY2FsbCh0aGlzLHt0b3BMZWZ0Ont4OjAseTpyLnRvcExlZnQueX0sYm90dG9tUmlnaHQ6e3g6dGhpcy53aWR0aCgpLHk6ci5ib3R0b21SaWdodC55fX0pfSx0LnByb3RvdHlwZS5fc2V0UmVzaXphYmxlQ2xhc3Nlcz1mdW5jdGlvbihyKXtyJiZ0aGlzLmVuYWJsZWQoKT90aGlzLmFkZENsYXNzKCJ5LXJlc2l6YWJsZSIpOnRoaXMucmVtb3ZlQ2xhc3MoInktcmVzaXphYmxlIil9LHQucHJvdG90eXBlLnhTY2FsZT1mdW5jdGlvbihyKXtpZihyPT1udWxsKXJldHVybiBlLnByb3RvdHlwZS54U2NhbGUuY2FsbCh0aGlzKTt0aHJvdyBuZXcgRXJyb3IoInhTY2FsZXMgY2Fubm90IGJlIHNldCBvbiBhbiBZRHJhZ0JveExheWVyIil9LHQucHJvdG90eXBlLnhFeHRlbnQ9ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gZS5wcm90b3R5cGUueEV4dGVudC5jYWxsKHRoaXMpO3Rocm93IG5ldyBFcnJvcigiWURyYWdCb3hMYXllciBoYXMgbm8geEV4dGVudCIpfSx0fSgkSGUuRHJhZ0JveExheWVyKTtoYXQuWURyYWdCb3hMYXllcj1LSGV9KTt2YXIgSW90PUgoX2E9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KF9hLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgTnM9KGRlKCksVXQocGUpKTtOcy5fX2V4cG9ydFN0YXIoVUYoKSxfYSk7TnMuX19leHBvcnRTdGFyKEUkdCgpLF9hKTtOcy5fX2V4cG9ydFN0YXIoVCR0KCksX2EpO05zLl9fZXhwb3J0U3RhcihGb3QoKSxfYSk7TnMuX19leHBvcnRTdGFyKGtvdCgpLF9hKTtOcy5fX2V4cG9ydFN0YXIoQSR0KCksX2EpO05zLl9fZXhwb3J0U3RhcihVb3QoKSxfYSk7TnMuX19leHBvcnRTdGFyKFAkdCgpLF9hKTtOcy5fX2V4cG9ydFN0YXIoZUt0KCksX2EpO05zLl9fZXhwb3J0U3RhcihDb3QoKSxfYSk7TnMuX19leHBvcnRTdGFyKHJLdCgpLF9hKTtOcy5fX2V4cG9ydFN0YXIobkt0KCksX2EpO05zLl9fZXhwb3J0U3RhcihpS3QoKSxfYSk7TnMuX19leHBvcnRTdGFyKG9LdCgpLF9hKX0pO3ZhciBwYXQ9SChmYXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGZhdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFpIZT0oZGUoKSxVdChwZSkpLEpIZT1EZigpLFFIZT1mdW5jdGlvbihlKXtaSGUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3JldHVybiBlLmNhbGwodGhpcywicGF0aCIsImFyYyBmaWxsIil8fHRoaXN9cmV0dXJuIHQucHJvdG90eXBlLl9hcHBseURlZmF1bHRBdHRyaWJ1dGVzPWZ1bmN0aW9uKHIpe3Iuc3R5bGUoInN0cm9rZSIsIm5vbmUiKX0sdH0oSkhlLlNWR0RyYXdlcik7ZmF0LkFyY1NWR0RyYXdlcj1RSGV9KTt2YXIgbWF0PUgoZGF0PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShkYXQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciB0VmU9KGRlKCksVXQocGUpKSxlVmU9RGYoKSxyVmU9ZnVuY3Rpb24oZSl7dFZlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXtyZXR1cm4gZS5jYWxsKHRoaXMsInBhdGgiLCJhcmMgb3V0bGluZSIpfHx0aGlzfXJldHVybiB0LnByb3RvdHlwZS5fYXBwbHlEZWZhdWx0QXR0cmlidXRlcz1mdW5jdGlvbihyKXtyLnN0eWxlKCJmaWxsIiwibm9uZSIpfSx0fShlVmUuU1ZHRHJhd2VyKTtkYXQuQXJjT3V0bGluZVNWR0RyYXdlcj1yVmV9KTt2YXIgZ2F0PUgodEI9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHRCLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgblZlPShkZSgpLFV0KHBlKSksUUY9RjEoKSxpVmU9RGYoKSxvVmU9ZnVuY3Rpb24oZSl7blZlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXtyZXR1cm4gZS5jYWxsKHRoaXMsInBhdGgiLCJhcmVhIil8fHRoaXN9cmV0dXJuIHQucHJvdG90eXBlLl9hcHBseURlZmF1bHRBdHRyaWJ1dGVzPWZ1bmN0aW9uKHIpe3Iuc3R5bGUoInN0cm9rZSIsIm5vbmUiKX0sdC5wcm90b3R5cGUuZ2V0VmlzdWFsUHJpbWl0aXZlQXRJbmRleD1mdW5jdGlvbihyKXtyZXR1cm4gZS5wcm90b3R5cGUuZ2V0VmlzdWFsUHJpbWl0aXZlQXRJbmRleC5jYWxsKHRoaXMsMCl9LHR9KGlWZS5TVkdEcmF3ZXIpO3RCLkFyZWFTVkdEcmF3ZXI9b1ZlO3ZhciBhVmU9WyJvcGFjaXR5IiwiZmlsbCIsImZpbGwtb3BhY2l0eSJdLHNWZT1bIm9wYWNpdHkiLCJzdHJva2UiLCJzdHJva2Utd2lkdGgiXTtmdW5jdGlvbiBsVmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocixuLGkpe3ZhciBvPVFGLnJlc29sdmVBdHRyaWJ1dGVzKGksYVZlLG5bMF0sMCk7UUYucmVuZGVyQXJlYShyLGUoKSxuWzBdLG8pO3ZhciBhPVFGLnJlc29sdmVBdHRyaWJ1dGVzKGksc1ZlLG5bMF0sMCk7UUYucmVuZGVyTGluZShyLHQoKSxuWzBdLGEpfX10Qi5tYWtlQXJlYUNhbnZhc0RyYXdTdGVwPWxWZX0pO3ZhciByQj1IKGVCPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShlQiwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIGNWZT0oZGUoKSxVdChwZSkpLGFLdD1GMSgpLHVWZT1EZigpLGhWZT1mdW5jdGlvbihlKXtjVmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3JldHVybiBlLmNhbGwodGhpcywicGF0aCIsImxpbmUiKXx8dGhpc31yZXR1cm4gdC5wcm90b3R5cGUuX2FwcGx5RGVmYXVsdEF0dHJpYnV0ZXM9ZnVuY3Rpb24ocil7ci5zdHlsZSgiZmlsbCIsIm5vbmUiKX0sdC5wcm90b3R5cGUuZ2V0VmlzdWFsUHJpbWl0aXZlQXRJbmRleD1mdW5jdGlvbihyKXtyZXR1cm4gZS5wcm90b3R5cGUuZ2V0VmlzdWFsUHJpbWl0aXZlQXRJbmRleC5jYWxsKHRoaXMsMCl9LHR9KHVWZS5TVkdEcmF3ZXIpO2VCLkxpbmVTVkdEcmF3ZXI9aFZlO3ZhciBmVmU9WyJvcGFjaXR5Iiwic3Ryb2tlLW9wYWNpdHkiLCJzdHJva2Utd2lkdGgiLCJzdHJva2UiLCJzdHJva2UtZGFzaGFycmF5Il07ZnVuY3Rpb24gcFZlKGUpe3JldHVybiBmdW5jdGlvbih0LHIsbil7dmFyIGk9YUt0LnJlc29sdmVBdHRyaWJ1dGVzKG4sZlZlLHJbMF0sMCk7YUt0LnJlbmRlckxpbmUodCxlKCksclswXSxpKX19ZUIubWFrZUxpbmVDYW52YXNEcmF3U3RlcD1wVmV9KTt2YXIgaUI9SChQUz0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoUFMsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBzS3Q9KGRlKCksVXQocGUpKSxuQj1GMSgpLGRWZT1EZigpLG1WZT1mdW5jdGlvbihlKXtzS3QuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdChyKXtyPT09dm9pZCAwJiYocj0iIik7dmFyIG49ZS5jYWxsKHRoaXMsInJlY3QiLCIiKXx8dGhpcztyZXR1cm4gbi5fcm9vdENsYXNzTmFtZT1yLG4uX3Jvb3QuY2xhc3NlZChuLl9yb290Q2xhc3NOYW1lLCEwKSxufXJldHVybiB0fShkVmUuU1ZHRHJhd2VyKTtQUy5SZWN0YW5nbGVTVkdEcmF3ZXI9bVZlO3ZhciBnVmU9bkIuQ29udGV4dFN0eWxlQXR0cnMuY29uY2F0KFsieCIsInkiLCJ3aWR0aCIsImhlaWdodCJdKTtQUy5SZWN0YW5nbGVDYW52YXNEcmF3U3RlcD1mdW5jdGlvbihlLHQscil7ZS5zYXZlKCk7Zm9yKHZhciBuPXQubGVuZ3RoLGk9MDtpPG47aSsrKXt2YXIgbz10W2ldO2lmKG8hPW51bGwpe3ZhciBhPW5CLnJlc29sdmVBdHRyaWJ1dGVzKHIsZ1ZlLG8saSk7ZS5iZWdpblBhdGgoKSxlLnJlY3QoYS54LGEueSxhLndpZHRoLGEuaGVpZ2h0KSxuQi5yZW5kZXJQYXRoV2l0aFN0eWxlKGUsYSl9fWUucmVzdG9yZSgpfTt2YXIgX1ZlPWZ1bmN0aW9uKGUpe3NLdC5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3JldHVybiBlLmNhbGwodGhpcyxyLFBTLlJlY3RhbmdsZUNhbnZhc0RyYXdTdGVwKXx8dGhpc31yZXR1cm4gdH0obkIuQ2FudmFzRHJhd2VyKTtQUy5SZWN0YW5nbGVDYW52YXNEcmF3ZXI9X1ZlfSk7dmFyIHlhdD1IKF9hdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoX2F0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgeVZlPShkZSgpLFV0KHBlKSksdlZlPURmKCkseFZlPWZ1bmN0aW9uKGUpe3lWZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7cmV0dXJuIGUuY2FsbCh0aGlzLCJsaW5lIiwiIil8fHRoaXN9cmV0dXJuIHR9KHZWZS5TVkdEcmF3ZXIpO19hdC5TZWdtZW50U1ZHRHJhd2VyPXhWZX0pO3ZhciBsS3Q9SCh2YXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHZhdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIGJWZT1mdW5jdGlvbigpe2Z1bmN0aW9uIGUodCxyLG4pe249PT12b2lkIDAmJihuPXdpbmRvdy5kZXZpY2VQaXhlbFJhdGlvKSx0aGlzLnNjcmVlbldpZHRoPXQsdGhpcy5zY3JlZW5IZWlnaHQ9cix0aGlzLmRldmljZVBpeGVsUmF0aW89bix0aGlzLnBpeGVsV2lkdGg9dCpuLHRoaXMucGl4ZWxIZWlnaHQ9cipuLHRoaXMuY2FudmFzPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImNhbnZhcyIpLHRoaXMuY3R4PXRoaXMuY2FudmFzLmdldENvbnRleHQoIjJkIiksZS5zaXplUGl4ZWxzKHRoaXMuY3R4LHQscixuKX1yZXR1cm4gZS5zaXplUGl4ZWxzPWZ1bmN0aW9uKHQscixuLGkpe3ZhciBvPXQuY2FudmFzO28ud2lkdGg9cippLG8uaGVpZ2h0PW4qaSxvLnN0eWxlLndpZHRoPXIrInB4IixvLnN0eWxlLmhlaWdodD1uKyJweCIsdC5zZXRUcmFuc2Zvcm0oMSwwLDAsMSwwLDApLHQuc2NhbGUoaSxpKX0sZS5wcm90b3R5cGUuYmxpdD1mdW5jdGlvbih0LHIsbil7cj09PXZvaWQgMCYmKHI9MCksbj09PXZvaWQgMCYmKG49MCksdC5kcmF3SW1hZ2UodGhpcy5jYW52YXMscixuLHRoaXMuc2NyZWVuV2lkdGgsdGhpcy5zY3JlZW5IZWlnaHQpfSxlLnByb3RvdHlwZS5ibGl0Q2VudGVyPWZ1bmN0aW9uKHQscixuKXtyPT09dm9pZCAwJiYocj0wKSxuPT09dm9pZCAwJiYobj0wKSx0aGlzLmJsaXQodCxNYXRoLmZsb29yKHItdGhpcy5zY3JlZW5XaWR0aC8yKSxNYXRoLmZsb29yKG4tdGhpcy5zY3JlZW5IZWlnaHQvMikpfSxlLnByb3RvdHlwZS5yZXNpemU9ZnVuY3Rpb24odCxyLG4pe249PT12b2lkIDAmJihuPSExKTt2YXIgaT10aGlzLmRldmljZVBpeGVsUmF0aW87cmV0dXJuIHRoaXMuc2NyZWVuV2lkdGg9dCx0aGlzLnNjcmVlbkhlaWdodD1yLHRoaXMucGl4ZWxXaWR0aD10KmksdGhpcy5waXhlbEhlaWdodD1yKmksZS5zaXplUGl4ZWxzKHRoaXMuY3R4LHQscixpKSxuJiZ0aGlzLmN0eC50cmFuc2xhdGUodC8yLHQvMiksdGhpc30sZS5wcm90b3R5cGUuY2xlYXI9ZnVuY3Rpb24odCl7dmFyIHI9dGhpcyxuPXIucGl4ZWxXaWR0aCxpPXIucGl4ZWxIZWlnaHQsbz1yLmN0eDtyZXR1cm4gby5zYXZlKCksby5zZXRUcmFuc2Zvcm0oMSwwLDAsMSwwLDApLHQ9PW51bGw/by5jbGVhclJlY3QoMCwwLG4saSk6KG8uZmlsbFN0eWxlPXQsby5maWxsUmVjdCgwLDAsbixpKSksby5yZXN0b3JlKCksdGhpc30sZS5wcm90b3R5cGUuZ2V0SW1hZ2VEYXRhPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuY3R4LmdldEltYWdlRGF0YSgwLDAsdGhpcy5waXhlbFdpZHRoLHRoaXMucGl4ZWxIZWlnaHQpfSxlfSgpO3ZhdC5DYW52YXNCdWZmZXI9YlZlfSk7dmFyIHhhdD1IKG9CPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShvQiwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIHdWZT0oZGUoKSxVdChwZSkpLGQ0PUYxKCksU1ZlPWxLdCgpLE1WZT1EZigpLEVWZT1mdW5jdGlvbihlKXt3VmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3JldHVybiBlLmNhbGwodGhpcywicGF0aCIsInN5bWJvbCIpfHx0aGlzfXJldHVybiB0fShNVmUuU1ZHRHJhd2VyKTtvQi5TeW1ib2xTVkdEcmF3ZXI9RVZlO3ZhciBUVmU9ZDQuQ29udGV4dFN0eWxlQXR0cnMuY29uY2F0KFsieCIsInkiXSk7ZnVuY3Rpb24gQ1ZlKGUsdCxyLG4pe3ZhciBpPXRoaXM7cmV0dXJuIGZ1bmN0aW9uKG8sYSxzKXtmb3IodmFyIGw9by5jYW52YXMsYz1sLmNsaWVudFdpZHRoLHU9bC5jbGllbnRIZWlnaHQsaD1uPT09dm9pZCAwP25ldyBTVmUuQ2FudmFzQnVmZmVyKDAsMCk6bixmPXQoKSxwPXIoKSxkPW51bGwsZz1udWxsLF89bnVsbCx5PTA7eTxhLmxlbmd0aDt5Kyspe3ZhciB4PWFbeV07aWYoeCE9bnVsbCl7dmFyIGI9ZDQucmVzb2x2ZUF0dHJpYnV0ZXMocyxUVmUseCx5KSxTPXAoeCx5LGUpO2lmKCEhQVZlKGMsdSxiLngsYi55LFMpKXt2YXIgQz1QVmUoZCxiLGQ0LkNvbnRleHRTdHlsZUF0dHJzKSxQPWYoeCx5LGkuX2RhdGFzZXQpO2lmKCEoQyYmXz09UyYmZz09UCkpe3ZhciBrPWQ0LmdldFN0cm9rZVdpZHRoKGIpLE89UytrKzE7KE8+aC5zY3JlZW5XaWR0aHx8Tz5oLnNjcmVlbkhlaWdodCkmJmgucmVzaXplKE8sTywhMCksaC5jbGVhcigpO3ZhciBEPWguY3R4O0QuYmVnaW5QYXRoKCksUChTKS5jb250ZXh0KEQpKG51bGwpLEQuY2xvc2VQYXRoKCksZDQucmVuZGVyUGF0aFdpdGhTdHlsZShELGIpLGc9UCxfPVMsZD1ifWguYmxpdENlbnRlcihvLGIueCxiLnkpfX19fX1vQi5tYWtlU3ltYm9sQ2FudmFzRHJhd1N0ZXA9Q1ZlO2Z1bmN0aW9uIEFWZShlLHQscixuLGkpe3JldHVybiByK2k+PTAmJnItaTw9ZSYmbitpPj0wJiZuLWk8PXR9ZnVuY3Rpb24gUFZlKGUsdCxyKXtpZihlPT1udWxsKXJldHVybiExO2Zvcih2YXIgbj0wO248ci5sZW5ndGg7bisrKXt2YXIgaT1yW25dO2lmKGVbaV0hPXRbaV0pcmV0dXJuITF9cmV0dXJuITB9fSk7dmFyIEIxPUgoRGM9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KERjLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgT2Y9KGRlKCksVXQocGUpKTtPZi5fX2V4cG9ydFN0YXIocGF0KCksRGMpO09mLl9fZXhwb3J0U3RhcihtYXQoKSxEYyk7T2YuX19leHBvcnRTdGFyKGdhdCgpLERjKTtPZi5fX2V4cG9ydFN0YXIoRjEoKSxEYyk7T2YuX19leHBvcnRTdGFyKFV1KCksRGMpO09mLl9fZXhwb3J0U3RhcihyQigpLERjKTtPZi5fX2V4cG9ydFN0YXIoaUIoKSxEYyk7T2YuX19leHBvcnRTdGFyKHlhdCgpLERjKTtPZi5fX2V4cG9ydFN0YXIoRGYoKSxEYyk7T2YuX19leHBvcnRTdGFyKHhhdCgpLERjKX0pO3ZhciBIMT1IKHdhdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkod2F0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgSVZlPShkZSgpLFV0KHBlKSksY0t0PWtzKCksYmF0PUZlKCksTFZlPW9hdCgpLG00PXJzKCksa1ZlPWZ1bmN0aW9uKGUpe0lWZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7dmFyIHI9ZS5jYWxsKHRoaXMpfHx0aGlzO3JldHVybiByLl9hdXRvQWRqdXN0WFNjYWxlRG9tYWluPSExLHIuX2F1dG9BZGp1c3RZU2NhbGVEb21haW49ITEsci5fZGVmZXJyZWRSZW5kZXJpbmc9ITEsci5fYXBwbHlEZWZlcnJlZFJlbmRlcmluZ1RyYW5zZm9ybT1mdW5jdGlvbihuLGksbyxhKXshci5faXNBbmNob3JlZHx8KHIuX3JlbmRlckFyZWEhPW51bGwmJnIuX3JlbmRlckFyZWEuYXR0cigidHJhbnNmb3JtIiwidHJhbnNsYXRlKCIrbisiLCAiK2krIikgc2NhbGUoIitvKyIsICIrYSsiKSIpLHIuX2NhbnZhcyE9bnVsbCYmci5fY2FudmFzLnN0eWxlKCJ0cmFuc2Zvcm0iLCJ0cmFuc2xhdGUoIituKyJweCwgIitpKyJweCkgc2NhbGUoIitvKyIsICIrYSsiKSIpKX0sci5hZGRDbGFzcygieHktcGxvdCIpLHIuX2FkanVzdFlEb21haW5PbkNoYW5nZUZyb21YQ2FsbGJhY2s9ZnVuY3Rpb24obil7cmV0dXJuIHIuX2FkanVzdFlEb21haW5PbkNoYW5nZUZyb21YKCl9LHIuX2FkanVzdFhEb21haW5PbkNoYW5nZUZyb21ZQ2FsbGJhY2s9ZnVuY3Rpb24obil7cmV0dXJuIHIuX2FkanVzdFhEb21haW5PbkNoYW5nZUZyb21ZKCl9LHIuX3JlbmRlckNhbGxiYWNrPWZ1bmN0aW9uKCl7aWYoci5kZWZlcnJlZFJlbmRlcmluZygpKXt2YXIgbj1yLngoKSYmci54KCkuc2NhbGUsaT1yLnkoKSYmci55KCkuc2NhbGU7ci5fZGVmZXJyZWRSZW5kZXJlci51cGRhdGVEb21haW5zKG4saSl9ZWxzZSByLnJlbmRlcigpfSxyLl9kZWZlcnJlZFJlbmRlcmVyPW5ldyBMVmUuRGVmZXJyZWRSZW5kZXJlcihmdW5jdGlvbigpe3JldHVybiByLnJlbmRlcigpfSxyLl9hcHBseURlZmVycmVkUmVuZGVyaW5nVHJhbnNmb3JtKSxyfXJldHVybiB0LnByb3RvdHlwZS5yZW5kZXI9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5kZWZlcnJlZFJlbmRlcmluZygpJiZ0aGlzLl9kZWZlcnJlZFJlbmRlcmVyLnJlc2V0VHJhbnNmb3JtcygpLGUucHJvdG90eXBlLnJlbmRlci5jYWxsKHRoaXMpfSx0LnByb3RvdHlwZS5kZWZlcnJlZFJlbmRlcmluZz1mdW5jdGlvbihyKXtpZihyPT1udWxsKXJldHVybiB0aGlzLl9kZWZlcnJlZFJlbmRlcmluZztpZihyKXt2YXIgbj10aGlzLngoKSYmdGhpcy54KCkuc2NhbGUsaT10aGlzLnkoKSYmdGhpcy55KCkuc2NhbGU7dGhpcy5fZGVmZXJyZWRSZW5kZXJlci5zZXREb21haW5zKG4saSl9cmV0dXJuIHRoaXMuX2RlZmVycmVkUmVuZGVyaW5nPXIsdGhpc30sdC5wcm90b3R5cGUueD1mdW5jdGlvbihyLG4saSl7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fcHJvcGVydHlCaW5kaW5ncy5nZXQodC5fWF9LRVkpO3RoaXMuX2JpbmRQcm9wZXJ0eSh0Ll9YX0tFWSxyLG4saSk7dmFyIG89dGhpcy53aWR0aCgpO3JldHVybiBuIT1udWxsJiZvIT1udWxsJiZuLnJhbmdlKFswLG9dKSx0aGlzLl9hdXRvQWRqdXN0WVNjYWxlRG9tYWluJiZ0aGlzLl91cGRhdGVZRXh0ZW50c0FuZEF1dG9kb21haW4oKSx0aGlzLnJlbmRlcigpLHRoaXN9LHQucHJvdG90eXBlLnk9ZnVuY3Rpb24ocixuLGkpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX3Byb3BlcnR5QmluZGluZ3MuZ2V0KHQuX1lfS0VZKTt0aGlzLl9iaW5kUHJvcGVydHkodC5fWV9LRVkscixuLGkpO3ZhciBvPXRoaXMuaGVpZ2h0KCk7cmV0dXJuIG4hPW51bGwmJm8hPW51bGwmJihuIGluc3RhbmNlb2YgY0t0LkNhdGVnb3J5P24ucmFuZ2UoWzAsb10pOm4ucmFuZ2UoW28sMF0pKSx0aGlzLl9hdXRvQWRqdXN0WFNjYWxlRG9tYWluJiZ0aGlzLl91cGRhdGVYRXh0ZW50c0FuZEF1dG9kb21haW4oKSx0aGlzLnJlbmRlcigpLHRoaXN9LHQucHJvdG90eXBlLl9maWx0ZXJGb3JQcm9wZXJ0eT1mdW5jdGlvbihyKXtyZXR1cm4gcj09PSJ4IiYmdGhpcy5fYXV0b0FkanVzdFhTY2FsZURvbWFpbj90aGlzLl9tYWtlRmlsdGVyQnlQcm9wZXJ0eSgieSIpOihyPT09InkifHxyPT09InkwIikmJnRoaXMuX2F1dG9BZGp1c3RZU2NhbGVEb21haW4/dGhpcy5fbWFrZUZpbHRlckJ5UHJvcGVydHkoIngiKTpudWxsfSx0LnByb3RvdHlwZS5fbWFrZUZpbHRlckJ5UHJvcGVydHk9ZnVuY3Rpb24ocil7dmFyIG49dGhpcy5fcHJvcGVydHlCaW5kaW5ncy5nZXQocik7aWYobiE9bnVsbCl7dmFyIGk9bi5hY2Nlc3NvcixvPW4uc2NhbGU7aWYobyE9bnVsbClyZXR1cm4gZnVuY3Rpb24oYSxzLGwpe3ZhciBjPW8ucmFuZ2UoKTtyZXR1cm4gYmF0Lk1hdGguaW5SYW5nZShvLnNjYWxlKGkoYSxzLGwpKSxjWzBdLGNbMV0pfX1yZXR1cm4gbnVsbH0sdC5wcm90b3R5cGUuX3VuaW5zdGFsbFNjYWxlRm9yS2V5PWZ1bmN0aW9uKHIsbil7ZS5wcm90b3R5cGUuX3VuaW5zdGFsbFNjYWxlRm9yS2V5LmNhbGwodGhpcyxyLG4pO3ZhciBpPW49PT10Ll9YX0tFWT90aGlzLl9hZGp1c3RZRG9tYWluT25DaGFuZ2VGcm9tWENhbGxiYWNrOnRoaXMuX2FkanVzdFhEb21haW5PbkNoYW5nZUZyb21ZQ2FsbGJhY2s7ci5vZmZVcGRhdGUoaSl9LHQucHJvdG90eXBlLl9pbnN0YWxsU2NhbGVGb3JLZXk9ZnVuY3Rpb24ocixuKXtlLnByb3RvdHlwZS5faW5zdGFsbFNjYWxlRm9yS2V5LmNhbGwodGhpcyxyLG4pO3ZhciBpPW49PT10Ll9YX0tFWT90aGlzLl9hZGp1c3RZRG9tYWluT25DaGFuZ2VGcm9tWENhbGxiYWNrOnRoaXMuX2FkanVzdFhEb21haW5PbkNoYW5nZUZyb21ZQ2FsbGJhY2s7ci5vblVwZGF0ZShpKX0sdC5wcm90b3R5cGUuZGVzdHJveT1mdW5jdGlvbigpe3JldHVybiBlLnByb3RvdHlwZS5kZXN0cm95LmNhbGwodGhpcyksdGhpcy54KCkuc2NhbGUmJnRoaXMueCgpLnNjYWxlLm9mZlVwZGF0ZSh0aGlzLl9hZGp1c3RZRG9tYWluT25DaGFuZ2VGcm9tWENhbGxiYWNrKSx0aGlzLnkoKS5zY2FsZSYmdGhpcy55KCkuc2NhbGUub2ZmVXBkYXRlKHRoaXMuX2FkanVzdFhEb21haW5PbkNoYW5nZUZyb21ZQ2FsbGJhY2spLHRoaXN9LHQucHJvdG90eXBlLmF1dG9yYW5nZU1vZGU9ZnVuY3Rpb24ocil7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fYXV0b0FkanVzdFhTY2FsZURvbWFpbj8ieCI6dGhpcy5fYXV0b0FkanVzdFlTY2FsZURvbWFpbj8ieSI6Im5vbmUiO3N3aXRjaChyKXtjYXNlIngiOnRoaXMuX2F1dG9BZGp1c3RYU2NhbGVEb21haW49ITAsdGhpcy5fYXV0b0FkanVzdFlTY2FsZURvbWFpbj0hMSx0aGlzLl9hZGp1c3RYRG9tYWluT25DaGFuZ2VGcm9tWSgpO2JyZWFrO2Nhc2UieSI6dGhpcy5fYXV0b0FkanVzdFhTY2FsZURvbWFpbj0hMSx0aGlzLl9hdXRvQWRqdXN0WVNjYWxlRG9tYWluPSEwLHRoaXMuX2FkanVzdFlEb21haW5PbkNoYW5nZUZyb21YKCk7YnJlYWs7Y2FzZSJub25lIjp0aGlzLl9hdXRvQWRqdXN0WFNjYWxlRG9tYWluPSExLHRoaXMuX2F1dG9BZGp1c3RZU2NhbGVEb21haW49ITE7YnJlYWs7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoIkludmFsaWQgc2NhbGUgbmFtZSAnIityKyInLCBtdXN0IGJlICd4JywgJ3knIG9yICdub25lJyIpfXJldHVybiB0aGlzfSx0LnByb3RvdHlwZS5jb21wdXRlTGF5b3V0PWZ1bmN0aW9uKHIsbixpKXtlLnByb3RvdHlwZS5jb21wdXRlTGF5b3V0LmNhbGwodGhpcyxyLG4saSk7dmFyIG89dGhpcy54KCksYT1vJiZvLnNjYWxlO2EhPW51bGwmJmEucmFuZ2UoWzAsdGhpcy53aWR0aCgpXSk7dmFyIHM9dGhpcy55KCksbD1zJiZzLnNjYWxlO3JldHVybiBsIT1udWxsJiYobCBpbnN0YW5jZW9mIGNLdC5DYXRlZ29yeT9sLnJhbmdlKFswLHRoaXMuaGVpZ2h0KCldKTpsLnJhbmdlKFt0aGlzLmhlaWdodCgpLDBdKSksdGhpc30sdC5wcm90b3R5cGUuX3VwZGF0ZVhFeHRlbnRzQW5kQXV0b2RvbWFpbj1mdW5jdGlvbigpe3ZhciByPXRoaXMueCgpLnNjYWxlO3IhPW51bGwmJnIuYXV0b0RvbWFpbigpfSx0LnByb3RvdHlwZS5fdXBkYXRlWUV4dGVudHNBbmRBdXRvZG9tYWluPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcy55KCkuc2NhbGU7ciE9bnVsbCYmci5hdXRvRG9tYWluKCl9LHQucHJvdG90eXBlLnNob3dBbGxEYXRhPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3VwZGF0ZVhFeHRlbnRzQW5kQXV0b2RvbWFpbigpLHRoaXMuX3VwZGF0ZVlFeHRlbnRzQW5kQXV0b2RvbWFpbigpLHRoaXN9LHQucHJvdG90eXBlLl9hZGp1c3RZRG9tYWluT25DaGFuZ2VGcm9tWD1mdW5jdGlvbigpeyF0aGlzLl9wcm9qZWN0b3JzUmVhZHkoKXx8dGhpcy5fYXV0b0FkanVzdFlTY2FsZURvbWFpbiYmdGhpcy5fdXBkYXRlWUV4dGVudHNBbmRBdXRvZG9tYWluKCl9LHQucHJvdG90eXBlLl9hZGp1c3RYRG9tYWluT25DaGFuZ2VGcm9tWT1mdW5jdGlvbigpeyF0aGlzLl9wcm9qZWN0b3JzUmVhZHkoKXx8dGhpcy5fYXV0b0FkanVzdFhTY2FsZURvbWFpbiYmdGhpcy5fdXBkYXRlWEV4dGVudHNBbmRBdXRvZG9tYWluKCl9LHQucHJvdG90eXBlLl9wcm9qZWN0b3JzUmVhZHk9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLngoKSxuPXRoaXMueSgpO3JldHVybiByIT1udWxsJiZyLmFjY2Vzc29yIT1udWxsJiZuIT1udWxsJiZuLmFjY2Vzc29yIT1udWxsfSx0LnByb3RvdHlwZS5fcGl4ZWxQb2ludD1mdW5jdGlvbihyLG4saSl7dmFyIG89bTQuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpLGE9bTQuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55KCkpO3JldHVybnt4Om8ocixuLGkpLHk6YShyLG4saSl9fSx0LnByb3RvdHlwZS5fZ2V0RGF0YVRvRHJhdz1mdW5jdGlvbigpe3ZhciByPXRoaXMsbj1lLnByb3RvdHlwZS5fZ2V0RGF0YVRvRHJhdy5jYWxsKHRoaXMpLGk9dGhpcy5hdHRyKCJkZWZpbmVkIiksbz1mdW5jdGlvbihhLHMsbCl7dmFyIGM9bTQuUGxvdC5fc2NhbGVkQWNjZXNzb3Ioci54KCkpKGEscyxsKSx1PW00LlBsb3QuX3NjYWxlZEFjY2Vzc29yKHIueSgpKShhLHMsbCk7cmV0dXJuIGkmJmkuYWNjZXNzb3IoYSxzLGwpPT09ITE/ITE6YmF0Lk1hdGguaXNWYWxpZE51bWJlcihjKSYmYmF0Lk1hdGguaXNWYWxpZE51bWJlcih1KX07cmV0dXJuIHRoaXMuZGF0YXNldHMoKS5mb3JFYWNoKGZ1bmN0aW9uKGEpe24uc2V0KGEsbi5nZXQoYSkuZmlsdGVyKGZ1bmN0aW9uKHMsbCl7cmV0dXJuIG8ocyxsLGEpfSkpfSksbn0sdC5fWF9LRVk9IngiLHQuX1lfS0VZPSJ5Iix0fShtNC5QbG90KTt3YXQuWFlQbG90PWtWZX0pO3ZhciBNYXQ9SChzQj0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoc0IsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBSVmU9KGRlKCksVXQocGUpKSx5YT0oRXIoKSxVdChNcikpLE5WZT1MZigpLERWZT1CMSgpLE9WZT1VdSgpLHVLdD1yQigpLHpWZT1rcygpLGFCPXZkKCksUWc9RmUoKSxGVmU9SWYoKSxTYXQ9SVMoKSx2bD1ycygpLEJWZT1IMSgpLGhLdD17bGluZWFyOnlhLmN1cnZlTGluZWFyLGxpbmVhckNsb3NlZDp5YS5jdXJ2ZUxpbmVhckNsb3NlZCxzdGVwOnlhLmN1cnZlU3RlcCxzdGVwQmVmb3JlOnlhLmN1cnZlU3RlcEJlZm9yZSxzdGVwQWZ0ZXI6eWEuY3VydmVTdGVwQWZ0ZXIsYmFzaXM6eWEuY3VydmVCYXNpcyxiYXNpc09wZW46eWEuY3VydmVCYXNpc09wZW4sYmFzaXNDbG9zZWQ6eWEuY3VydmVCYXNpc0Nsb3NlZCxidW5kbGU6eWEuY3VydmVCdW5kbGUsY2FyZGluYWw6eWEuY3VydmVDYXJkaW5hbCxjYXJkaW5hbE9wZW46eWEuY3VydmVDYXJkaW5hbE9wZW4sY2FyZGluYWxDbG9zZWQ6eWEuY3VydmVDYXJkaW5hbENsb3NlZCxtb25vdG9uZTp5YS5jdXJ2ZU1vbm90b25lWH07c0IuQ3VydmVOYW1lPUZWZS5tYWtlRW51bShbImxpbmVhciIsImxpbmVhckNsb3NlZCIsInN0ZXAiLCJzdGVwQmVmb3JlIiwic3RlcEFmdGVyIiwiYmFzaXMiLCJiYXNpc09wZW4iLCJiYXNpc0Nsb3NlZCIsImJ1bmRsZSIsImNhcmRpbmFsIiwiY2FyZGluYWxPcGVuIiwiY2FyZGluYWxDbG9zZWQiLCJtb25vdG9uZSJdKTt2YXIgSFZlPWZ1bmN0aW9uKGUpe1JWZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7dmFyIHI9ZS5jYWxsKHRoaXMpfHx0aGlzO3IuX2N1cnZlPSJsaW5lYXIiLHIuX2F1dG9yYW5nZVNtb290aD0hMSxyLl9jcm9wcGVkUmVuZGVyaW5nRW5hYmxlZD0hMCxyLl9jb2xsYXBzZURlbnNlVmVydGljYWxMaW5lc0VuYWJsZWQ9ITEsci5fZG93bnNhbXBsaW5nRW5hYmxlZD0hMSxyLmFkZENsYXNzKCJsaW5lLXBsb3QiKTt2YXIgbj1uZXcgTlZlLkVhc2luZztyZXR1cm4gbi5zdGVwRHVyYXRpb24odmwuUGxvdC5fQU5JTUFUSU9OX01BWF9EVVJBVElPTiksbi5lYXNpbmdNb2RlKCJleHBJbk91dCIpLG4ubWF4VG90YWxEdXJhdGlvbih2bC5QbG90Ll9BTklNQVRJT05fTUFYX0RVUkFUSU9OKSxyLmFuaW1hdG9yKFNhdC5BbmltYXRvci5NQUlOLG4pLHIuYXR0cigic3Ryb2tlIixuZXcgelZlLkNvbG9yKCkucmFuZ2UoKVswXSksci5hdHRyKCJzdHJva2Utd2lkdGgiLCIycHgiKSxyfXJldHVybiB0LnByb3RvdHlwZS54PWZ1bmN0aW9uKHIsbixpKXtyZXR1cm4gcj09bnVsbD9lLnByb3RvdHlwZS54LmNhbGwodGhpcyk6KGUucHJvdG90eXBlLnguY2FsbCh0aGlzLHIsbixpKSx0aGlzLl9zZXRTY2FsZVNuYXBwaW5nKCksdGhpcyl9LHQucHJvdG90eXBlLnk9ZnVuY3Rpb24ocixuLGkpe3JldHVybiByPT1udWxsP2UucHJvdG90eXBlLnkuY2FsbCh0aGlzKTooZS5wcm90b3R5cGUueS5jYWxsKHRoaXMscixuLGkpLHRoaXMuX3NldFNjYWxlU25hcHBpbmcoKSx0aGlzKX0sdC5wcm90b3R5cGUuYXV0b3JhbmdlTW9kZT1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD9lLnByb3RvdHlwZS5hdXRvcmFuZ2VNb2RlLmNhbGwodGhpcyk6KGUucHJvdG90eXBlLmF1dG9yYW5nZU1vZGUuY2FsbCh0aGlzLHIpLHRoaXMuX3NldFNjYWxlU25hcHBpbmcoKSx0aGlzKX0sdC5wcm90b3R5cGUuYXV0b3JhbmdlU21vb3RoPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2F1dG9yYW5nZVNtb290aDoodGhpcy5fYXV0b3JhbmdlU21vb3RoPXIsdGhpcy5fc2V0U2NhbGVTbmFwcGluZygpLHRoaXMpfSx0LnByb3RvdHlwZS5fc2V0U2NhbGVTbmFwcGluZz1mdW5jdGlvbigpe3RoaXMuYXV0b3JhbmdlTW9kZSgpPT09IngiJiZ0aGlzLngoKSYmdGhpcy54KCkuc2NhbGUmJnRoaXMueCgpLnNjYWxlIGluc3RhbmNlb2YgYUIuUXVhbnRpdGF0aXZlU2NhbGUmJnRoaXMueCgpLnNjYWxlLnNuYXBwaW5nRG9tYWluRW5hYmxlZCghdGhpcy5hdXRvcmFuZ2VTbW9vdGgoKSksdGhpcy5hdXRvcmFuZ2VNb2RlKCk9PT0ieSImJnRoaXMueSgpJiZ0aGlzLnkoKS5zY2FsZSYmdGhpcy55KCkuc2NhbGUgaW5zdGFuY2VvZiBhQi5RdWFudGl0YXRpdmVTY2FsZSYmdGhpcy55KCkuc2NhbGUuc25hcHBpbmdEb21haW5FbmFibGVkKCF0aGlzLmF1dG9yYW5nZVNtb290aCgpKX0sdC5wcm90b3R5cGUuY3VydmU9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fY3VydmU6KHRoaXMuX2N1cnZlPXIsdGhpcy5yZW5kZXIoKSx0aGlzKX0sdC5wcm90b3R5cGUuZG93bnNhbXBsaW5nRW5hYmxlZD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9kb3duc2FtcGxpbmdFbmFibGVkOih0aGlzLl9kb3duc2FtcGxpbmdFbmFibGVkPXIsdGhpcyl9LHQucHJvdG90eXBlLmNyb3BwZWRSZW5kZXJpbmdFbmFibGVkPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2Nyb3BwZWRSZW5kZXJpbmdFbmFibGVkOih0aGlzLl9jcm9wcGVkUmVuZGVyaW5nRW5hYmxlZD1yLHRoaXMucmVuZGVyKCksdGhpcyl9LHQucHJvdG90eXBlLmNvbGxhcHNlRGVuc2VMaW5lc0VuYWJsZWQ9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fY29sbGFwc2VEZW5zZVZlcnRpY2FsTGluZXNFbmFibGVkOih0aGlzLl9jb2xsYXBzZURlbnNlVmVydGljYWxMaW5lc0VuYWJsZWQ9cix0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5fY3JlYXRlRHJhd2VyPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXM7cmV0dXJuIG5ldyBPVmUuUHJveHlEcmF3ZXIoZnVuY3Rpb24oKXtyZXR1cm4gbmV3IHVLdC5MaW5lU1ZHRHJhd2VyfSxmdW5jdGlvbihpKXtyZXR1cm4gbmV3IERWZS5DYW52YXNEcmF3ZXIoaSx1S3QubWFrZUxpbmVDYW52YXNEcmF3U3RlcChmdW5jdGlvbigpe3JldHVybiBuLl9kM0xpbmVGYWN0b3J5KHIpfSkpfSl9LHQucHJvdG90eXBlLmdldEV4dGVudHNGb3JQcm9wZXJ0eT1mdW5jdGlvbihyKXt2YXIgbj1lLnByb3RvdHlwZS5nZXRFeHRlbnRzRm9yUHJvcGVydHkuY2FsbCh0aGlzLHIpO2lmKCF0aGlzLl9hdXRvcmFuZ2VTbW9vdGh8fHRoaXMuYXV0b3JhbmdlTW9kZSgpIT09cnx8dGhpcy5hdXRvcmFuZ2VNb2RlKCkhPT0ieCImJnRoaXMuYXV0b3JhbmdlTW9kZSgpIT09InkiKXJldHVybiBuO3ZhciBpPXRoaXMuX2dldEVkZ2VJbnRlcnNlY3Rpb25Qb2ludHMoKSxvO3JldHVybiB0aGlzLmF1dG9yYW5nZU1vZGUoKT09PSJ5Ij9vPWkubGVmdC5jb25jYXQoaS5yaWdodCkubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLnl9KTpvPWkudG9wLmNvbmNhdChpLmJvdHRvbSkubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLnh9KSxuLm1hcChmdW5jdGlvbihhKXtyZXR1cm4geWEuZXh0ZW50KHlhLm1lcmdlKFthLG9dKSl9KX0sdC5wcm90b3R5cGUuX2dldEVkZ2VJbnRlcnNlY3Rpb25Qb2ludHM9ZnVuY3Rpb24oKXt2YXIgcj10aGlzO2lmKCEodGhpcy55KCkuc2NhbGUgaW5zdGFuY2VvZiBhQi5RdWFudGl0YXRpdmVTY2FsZSYmdGhpcy54KCkuc2NhbGUgaW5zdGFuY2VvZiBhQi5RdWFudGl0YXRpdmVTY2FsZSkpcmV0dXJue2xlZnQ6W10scmlnaHQ6W10sdG9wOltdLGJvdHRvbTpbXX07dmFyIG49dGhpcy55KCkuc2NhbGUsaT10aGlzLngoKS5zY2FsZSxvPXtsZWZ0OltdLHJpZ2h0OltdLHRvcDpbXSxib3R0b206W119LGE9aS5zY2FsZShpLmRvbWFpbigpWzBdKSxzPWkuc2NhbGUoaS5kb21haW4oKVsxXSksbD1uLnNjYWxlKG4uZG9tYWluKClbMF0pLGM9bi5zY2FsZShuLmRvbWFpbigpWzFdKTtyZXR1cm4gdGhpcy5kYXRhc2V0cygpLmZvckVhY2goZnVuY3Rpb24odSl7Zm9yKHZhciBoPXUuZGF0YSgpLGYscCxkLGcsXyx5LHgsYixTPTE7UzxoLmxlbmd0aDtTKyspXz14fHxpLnNjYWxlKHIueCgpLmFjY2Vzc29yKGhbUy0xXSxTLTEsdSkpLHk9Ynx8bi5zY2FsZShyLnkoKS5hY2Nlc3NvcihoW1MtMV0sUy0xLHUpKSx4PWkuc2NhbGUoci54KCkuYWNjZXNzb3IoaFtTXSxTLHUpKSxiPW4uc2NhbGUoci55KCkuYWNjZXNzb3IoaFtTXSxTLHUpKSxfPGE9PWE8PXgmJihmPWEtXyxwPXgtXyxnPWIteSxkPWYqZy9wLG8ubGVmdC5wdXNoKHt4OmEseTpuLmludmVydCh5K2QpfSkpLF88cz09czw9eCYmKGY9cy1fLHA9eC1fLGc9Yi15LGQ9ZipnL3Asby5yaWdodC5wdXNoKHt4OnMseTpuLmludmVydCh5K2QpfSkpLHk8Yz09Yzw9YiYmKHA9eC1fLGQ9Yy15LGc9Yi15LGY9ZCpwL2csby50b3AucHVzaCh7eDppLmludmVydChfK2YpLHk6Y30pKSx5PGw9PWw8PWImJihwPXgtXyxkPWwteSxnPWIteSxmPWQqcC9nLG8uYm90dG9tLnB1c2goe3g6aS5pbnZlcnQoXytmKSx5Omx9KSl9KSxvfSx0LnByb3RvdHlwZS5fZ2V0UmVzZXRZRnVuY3Rpb249ZnVuY3Rpb24oKXt2YXIgcj10aGlzLnkoKS5zY2FsZS5kb21haW4oKSxuPU1hdGgubWF4KHJbMF0sclsxXSksaT1NYXRoLm1pbihyWzBdLHJbMV0pLG89bjwwJiZufHxpPjAmJml8fDAsYT10aGlzLnkoKS5zY2FsZS5zY2FsZShvKTtyZXR1cm4gZnVuY3Rpb24ocyxsLGMpe3JldHVybiBhfX0sdC5wcm90b3R5cGUuX2dlbmVyYXRlRHJhd1N0ZXBzPWZ1bmN0aW9uKCl7dmFyIHI9W107aWYodGhpcy5fYW5pbWF0ZU9uTmV4dFJlbmRlcigpKXt2YXIgbj10aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKTtuLmQ9dGhpcy5fY29uc3RydWN0TGluZVByb2plY3Rvcih2bC5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLngoKSksdGhpcy5fZ2V0UmVzZXRZRnVuY3Rpb24oKSksci5wdXNoKHthdHRyVG9Qcm9qZWN0b3I6bixhbmltYXRvcjp0aGlzLl9nZXRBbmltYXRvcihTYXQuQW5pbWF0b3IuUkVTRVQpfSl9cmV0dXJuIHIucHVzaCh7YXR0clRvUHJvamVjdG9yOnRoaXMuX2dldEF0dHJUb1Byb2plY3RvcigpLGFuaW1hdG9yOnRoaXMuX2dldEFuaW1hdG9yKFNhdC5BbmltYXRvci5NQUlOKX0pLHJ9LHQucHJvdG90eXBlLl9nZW5lcmF0ZUF0dHJUb1Byb2plY3Rvcj1mdW5jdGlvbigpe3ZhciByPWUucHJvdG90eXBlLl9nZW5lcmF0ZUF0dHJUb1Byb2plY3Rvci5jYWxsKHRoaXMpO3JldHVybiBPYmplY3Qua2V5cyhyKS5mb3JFYWNoKGZ1bmN0aW9uKG4pe2lmKG4hPT0iZCIpe3ZhciBpPXJbbl07cltuXT1mdW5jdGlvbihvLGEscyl7cmV0dXJuIG8ubGVuZ3RoPjA/aShvWzBdLGEscyk6bnVsbH19fSkscn0sdC5wcm90b3R5cGUuZW50aXRpZXNBdD1mdW5jdGlvbihyKXt2YXIgbj10aGlzLmVudGl0eU5lYXJlc3RCeVhUaGVuWShyKTtyZXR1cm4gbiE9bnVsbD9bbl06W119LHQucHJvdG90eXBlLmVudGl0eU5lYXJlc3RCeVhUaGVuWT1mdW5jdGlvbihyKXtmb3IodmFyIG49MS8wLGk9MS8wLG8sYT10aGlzLmJvdW5kcygpLHM9dGhpcy5lbnRpdGllcygpLGw9cy5sZW5ndGgsYz0wO2M8bDtjKyspe3ZhciB1PXNbY107aWYoISFRZy5NYXRoLndpdGhpbih1LnBvc2l0aW9uLGEpKXt2YXIgaD1NYXRoLmFicyhyLngtdS5wb3NpdGlvbi54KSxmPU1hdGguYWJzKHIueS11LnBvc2l0aW9uLnkpOyhoPG58fGg9PT1uJiZmPGkpJiYobz11LG49aCxpPWYpfX1yZXR1cm4gb30sdC5wcm90b3R5cGUuX3Byb3BlcnR5UHJvamVjdG9ycz1mdW5jdGlvbigpe3ZhciByPWUucHJvdG90eXBlLl9wcm9wZXJ0eVByb2plY3RvcnMuY2FsbCh0aGlzKTtyZXR1cm4gci5kPXRoaXMuX2NvbnN0cnVjdExpbmVQcm9qZWN0b3IodmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpLHZsLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMueSgpKSkscn0sdC5wcm90b3R5cGUuX2NvbnN0cnVjdExpbmVQcm9qZWN0b3I9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzO3JldHVybiBmdW5jdGlvbihvLGEscyl7cmV0dXJuIGkuX2QzTGluZUZhY3RvcnkocyxyLG4pKG8pfX0sdC5wcm90b3R5cGUuX2QzTGluZUZhY3Rvcnk9ZnVuY3Rpb24ocixuLGkpe249PT12b2lkIDAmJihuPXZsLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMueCgpKSksaT09PXZvaWQgMCYmKGk9dmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55KCkpKTt2YXIgbz1mdW5jdGlvbihhLHMsbCl7dmFyIGM9bihhLHMsbCksdT1pKGEscyxsKTtyZXR1cm4gUWcuTWF0aC5pc1ZhbGlkTnVtYmVyKGMpJiZRZy5NYXRoLmlzVmFsaWROdW1iZXIodSl9O3JldHVybiB5YS5saW5lKCkueChmdW5jdGlvbihhLHMpe3JldHVybiBuKGEscyxyKX0pLnkoZnVuY3Rpb24oYSxzKXtyZXR1cm4gaShhLHMscil9KS5jdXJ2ZSh0aGlzLl9nZXRDdXJ2ZUZhY3RvcnkoKSkuZGVmaW5lZChmdW5jdGlvbihhLHMpe3JldHVybiBvKGEscyxyKX0pfSx0LnByb3RvdHlwZS5fZ2V0Q3VydmVGYWN0b3J5PWZ1bmN0aW9uKCl7dmFyIHI9dGhpcy5jdXJ2ZSgpO2lmKHR5cGVvZiByPT0ic3RyaW5nIil7dmFyIG49aEt0W3JdO3JldHVybiBuPT1udWxsP2hLdC5saW5lYXI6bn1lbHNlIHJldHVybiByfSx0LnByb3RvdHlwZS5fZ2V0RGF0YVRvRHJhdz1mdW5jdGlvbigpe3ZhciByPXRoaXMsbj1uZXcgUWcuTWFwO3JldHVybiB0aGlzLmRhdGFzZXRzKCkuZm9yRWFjaChmdW5jdGlvbihpKXt2YXIgbz1pLmRhdGEoKTtpZighci5fY3JvcHBlZFJlbmRlcmluZ0VuYWJsZWQmJiFyLl9kb3duc2FtcGxpbmdFbmFibGVkKXtuLnNldChpLFtvXSk7cmV0dXJufWZvcih2YXIgYT1bXSxzPW8ubGVuZ3RoLGw9MDtsPHM7bCsrKWFbbF09bDtyLl9jcm9wcGVkUmVuZGVyaW5nRW5hYmxlZCYmKGE9ci5fZmlsdGVyQ3JvcHBlZFJlbmRlcmluZyhpLGEpKSxyLl9kb3duc2FtcGxpbmdFbmFibGVkJiYoYT1yLl9maWx0ZXJEb3duc2FtcGxpbmcoaSxhKSksci5fY29sbGFwc2VEZW5zZVZlcnRpY2FsTGluZXNFbmFibGVkJiYoYT1yLl9maWx0ZXJEZW5zZUxpbmVzKGksYSkpO2Zvcih2YXIgYz1bXSx1PWEubGVuZ3RoLGw9MDtsPHU7bCsrKXt2YXIgaD1hW2xdO2NbbF09b1toXX1uLnNldChpLFtjXSl9KSxufSx0LnByb3RvdHlwZS5fZmlsdGVyQ3JvcHBlZFJlbmRlcmluZz1mdW5jdGlvbihyLG4pe2Zvcih2YXIgaT10aGlzLG89dmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpLGE9dmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55KCkpLHM9ci5kYXRhKCksbD1bXSxjPWZ1bmN0aW9uKHgsYil7cmV0dXJuIFFnLk1hdGguaW5SYW5nZSh4LDAsaS53aWR0aCgpKSYmUWcuTWF0aC5pblJhbmdlKGIsMCxpLmhlaWdodCgpKX0sdT0wO3U8bi5sZW5ndGg7dSsrKXt2YXIgaD1vKHNbblt1XV0sblt1XSxyKSxmPWEoc1tuW3VdXSxuW3VdLHIpLHA9YyhoLGYpO2lmKCFwJiZuW3UtMV0hPW51bGwmJnNbblt1LTFdXSE9bnVsbCl7dmFyIGQ9byhzW25bdS0xXV0sblt1LTFdLHIpLGc9YShzW25bdS0xXV0sblt1LTFdLHIpO3A9cHx8YyhkLGcpfWlmKCFwJiZuW3UrMV0hPW51bGwmJnNbblt1KzFdXSE9bnVsbCl7dmFyIF89byhzW25bdSsxXV0sblt1KzFdLHIpLHk9YShzW25bdSsxXV0sblt1KzFdLHIpO3A9cHx8YyhfLHkpfXAmJmwucHVzaChuW3VdKX1yZXR1cm4gbH0sdC5wcm90b3R5cGUuX2ZpbHRlckRvd25zYW1wbGluZz1mdW5jdGlvbihyLG4pe2lmKG4ubGVuZ3RoPT09MClyZXR1cm5bXTtmb3IodmFyIGk9ci5kYXRhKCksbz12bC5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLngoKSksYT12bC5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnkoKSkscz1bblswXV0sbD1mdW5jdGlvbihrLE8pe3ZhciBEPW8oaVtuW2tdXSxuW2tdLHIpLEI9YShpW25ba11dLG5ba10sciksST1vKGlbbltrKzFdXSxuW2srMV0sciksTD1hKGlbbltrKzFdXSxuW2srMV0scik7aWYoTz09PTEvMClyZXR1cm4gTWF0aC5mbG9vcihEKT09PU1hdGguZmxvb3IoSSk7dmFyIFI9QisoSS1EKSpPO3JldHVybiBNYXRoLmZsb29yKEwpPT09TWF0aC5mbG9vcihSKX0sYz0wO2M8bi5sZW5ndGgtMTspe2Zvcih2YXIgdT1uW2NdLGg9byhpW25bY11dLG5bY10sciksZj1hKGlbbltjXV0sbltjXSxyKSxwPW8oaVtuW2MrMV1dLG5bYysxXSxyKSxkPWEoaVtuW2MrMV1dLG5bYysxXSxyKSxnPU1hdGguZmxvb3IoaCk9PT1NYXRoLmZsb29yKHApPzEvMDooZC1mKS8ocC1oKSxfPW5bY10seT1nPT09MS8wP2Y6aCx4PV8sYj15LFM9ITA7YzxuLmxlbmd0aC0xJiYoU3x8bChjLGcpKTspe2MrKyxTPSExO3ZhciBDPWc9PT0xLzA/YShpW25bY11dLG5bY10scik6byhpW25bY11dLG5bY10scik7Qz5iJiYoYj1DLHg9bltjXSksQzx5JiYoeT1DLF89bltjXSl9dmFyIFA9bltjXTtfIT09dSYmcy5wdXNoKF8pLHghPT1fJiZ4IT09dSYmcy5wdXNoKHgpLFAhPT11JiZQIT09XyYmUCE9PXgmJnMucHVzaChQKX1yZXR1cm4gc30sdC5wcm90b3R5cGUuX2ZpbHRlckRlbnNlTGluZXM9ZnVuY3Rpb24ocixuKXtpZihuLmxlbmd0aD09PTApcmV0dXJuW107dmFyIGk9ci5kYXRhKCksbz12bC5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLngoKSksYT12bC5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnkoKSkscz1mdW5jdGlvbihjKXtyZXR1cm4gbyhpW2NdLGMscil9LGw9ZnVuY3Rpb24oYyl7cmV0dXJuIGEoaVtjXSxjLHIpfTtyZXR1cm4gdGhpcy5fYnVja2V0QnlYKHIsbixzLGwpfSx0LnByb3RvdHlwZS5fYnVja2V0QnlYPWZ1bmN0aW9uKHIsbixpLG8pe2Zvcih2YXIgYT1bXSxzPXIuZGF0YSgpLGw9bnVsbCxjPW4ubGVuZ3RoLHU9MDt1PD1jOysrdSl7dmFyIGg9blt1XTtpZihzW2hdIT1udWxsKXt2YXIgZj1NYXRoLmZsb29yKGkoaCkpLHA9byhoKTtsPT1udWxsP2w9bmV3IFFnLkJ1Y2tldChoLGYscCk6bC5pc0luQnVja2V0KGYpP2wuYWRkVG9CdWNrZXQocCxoKTooYS5wdXNoLmFwcGx5KGEsbC5nZXRVbmlxdWVJbmRpY2VzKCkpLGw9bmV3IFFnLkJ1Y2tldChoLGYscCkpfX1yZXR1cm4gbCE9bnVsbCYmYS5wdXNoLmFwcGx5KGEsbC5nZXRVbmlxdWVJbmRpY2VzKCkpLGF9LHR9KEJWZS5YWVBsb3QpO3NCLkxpbmU9SFZlfSk7dmFyIENhdD1IKFRhdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoVGF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgVlZlPShkZSgpLFV0KHBlKSksbEI9KEVyKCksVXQoTXIpKSxVVmU9a3MoKSxnND1GZSgpLEVhdD1CMSgpLGZLdD1nYXQoKSxxVmU9VXUoKSxwS3Q9ckIoKSxjQj1JUygpLEdWZT1NYXQoKSxEcz1ycygpLFdWZT1mdW5jdGlvbihlKXtWVmUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3ZhciByPWUuY2FsbCh0aGlzKXx8dGhpcztyZXR1cm4gci5hZGRDbGFzcygiYXJlYS1wbG90Iiksci55MCgwKSxyLmF0dHIoImZpbGwtb3BhY2l0eSIsLjI1KSxyLmF0dHIoImZpbGwiLG5ldyBVVmUuQ29sb3IoKS5yYW5nZSgpWzBdKSxyLl9saW5lRHJhd2Vycz1uZXcgZzQuTWFwLHJ9cmV0dXJuIHQucHJvdG90eXBlLnk9ZnVuY3Rpb24ocixuKXtpZihyPT1udWxsKXJldHVybiBlLnByb3RvdHlwZS55LmNhbGwodGhpcyk7aWYobj09bnVsbD9lLnByb3RvdHlwZS55LmNhbGwodGhpcyxyKTplLnByb3RvdHlwZS55LmNhbGwodGhpcyxyLG4pLG4hPW51bGwpe3ZhciBpPXRoaXMueTAoKS5hY2Nlc3NvcjtpIT1udWxsJiZ0aGlzLl9iaW5kUHJvcGVydHkodC5fWTBfS0VZLGksbiksdGhpcy5fdXBkYXRlWVNjYWxlKCl9cmV0dXJuIHRoaXN9LHQucHJvdG90eXBlLnkwPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX3Byb3BlcnR5QmluZGluZ3MuZ2V0KHQuX1kwX0tFWSk7dmFyIG49dGhpcy55KCksaT1uJiZuLnNjYWxlO3JldHVybiB0aGlzLl9iaW5kUHJvcGVydHkodC5fWTBfS0VZLHIsaSksdGhpcy5fdXBkYXRlWVNjYWxlKCksdGhpcy5yZW5kZXIoKSx0aGlzfSx0LnByb3RvdHlwZS5fb25EYXRhc2V0VXBkYXRlPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX29uRGF0YXNldFVwZGF0ZS5jYWxsKHRoaXMpLHRoaXMuX3VwZGF0ZVlTY2FsZSgpfSx0LnByb3RvdHlwZS5fYWRkRGF0YXNldD1mdW5jdGlvbihyKXt2YXIgbj10aGlzO3JldHVybiB0aGlzLl9saW5lRHJhd2Vycy5zZXQocixuZXcgRWF0LlByb3h5RHJhd2VyKGZ1bmN0aW9uKCl7cmV0dXJuIG5ldyBwS3QuTGluZVNWR0RyYXdlcn0sZnVuY3Rpb24oaSl7cmV0dXJuIG5ldyBFYXQuQ2FudmFzRHJhd2VyKGkscEt0Lm1ha2VMaW5lQ2FudmFzRHJhd1N0ZXAoZnVuY3Rpb24oKXt2YXIgbz1Ecy5QbG90Ll9zY2FsZWRBY2Nlc3NvcihuLngoKSksYT1Ecy5QbG90Ll9zY2FsZWRBY2Nlc3NvcihuLnkoKSk7cmV0dXJuIG4uX2QzTGluZUZhY3RvcnkocixvLGEpfSkpfSkpLGUucHJvdG90eXBlLl9hZGREYXRhc2V0LmNhbGwodGhpcyxyKSx0aGlzfSx0LnByb3RvdHlwZS5fY3JlYXRlTm9kZXNGb3JEYXRhc2V0PWZ1bmN0aW9uKHIpe2UucHJvdG90eXBlLl9jcmVhdGVOb2Rlc0ZvckRhdGFzZXQuY2FsbCh0aGlzLHIpO3ZhciBuPXRoaXMuX2xpbmVEcmF3ZXJzLmdldChyKTtyZXR1cm4gdGhpcy5yZW5kZXJlcigpPT09InN2ZyI/bi51c2VTVkcodGhpcy5fcmVuZGVyQXJlYSk6bi51c2VDYW52YXModGhpcy5fY2FudmFzKSxufSx0LnByb3RvdHlwZS5fcmVtb3ZlRGF0YXNldE5vZGVzPWZ1bmN0aW9uKHIpe2UucHJvdG90eXBlLl9yZW1vdmVEYXRhc2V0Tm9kZXMuY2FsbCh0aGlzLHIpLHRoaXMuX2xpbmVEcmF3ZXJzLmdldChyKS5yZW1vdmUoKX0sdC5wcm90b3R5cGUuX2FkZGl0aW9uYWxQYWludD1mdW5jdGlvbigpe3ZhciByPXRoaXMsbj10aGlzLl9nZW5lcmF0ZUxpbmVEcmF3U3RlcHMoKSxpPXRoaXMuX2dldERhdGFUb0RyYXcoKTt0aGlzLmRhdGFzZXRzKCkuZm9yRWFjaChmdW5jdGlvbihvKXt2YXIgYT1Ecy5QbG90LmFwcGx5RHJhd1N0ZXBzKG4sbyk7ci5fbGluZURyYXdlcnMuZ2V0KG8pLmRyYXcoaS5nZXQobyksYSl9KX0sdC5wcm90b3R5cGUuX2dlbmVyYXRlTGluZURyYXdTdGVwcz1mdW5jdGlvbigpe3ZhciByPVtdO2lmKHRoaXMuX2FuaW1hdGVPbk5leHRSZW5kZXIoKSl7dmFyIG49dGhpcy5fZ2VuZXJhdGVMaW5lQXR0clRvUHJvamVjdG9yKCk7bi5kPXRoaXMuX2NvbnN0cnVjdExpbmVQcm9qZWN0b3IoRHMuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpLHRoaXMuX2dldFJlc2V0WUZ1bmN0aW9uKCkpLHIucHVzaCh7YXR0clRvUHJvamVjdG9yOm4sYW5pbWF0b3I6dGhpcy5fZ2V0QW5pbWF0b3IoY0IuQW5pbWF0b3IuUkVTRVQpfSl9cmV0dXJuIHIucHVzaCh7YXR0clRvUHJvamVjdG9yOnRoaXMuX2dlbmVyYXRlTGluZUF0dHJUb1Byb2plY3RvcigpLGFuaW1hdG9yOnRoaXMuX2dldEFuaW1hdG9yKGNCLkFuaW1hdG9yLk1BSU4pfSkscn0sdC5wcm90b3R5cGUuX2dlbmVyYXRlTGluZUF0dHJUb1Byb2plY3Rvcj1mdW5jdGlvbigpe3ZhciByPXRoaXMuX2dldEF0dHJUb1Byb2plY3RvcigpO3JldHVybiByLmQ9dGhpcy5fY29uc3RydWN0TGluZVByb2plY3RvcihEcy5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLngoKSksRHMuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55KCkpKSxyfSx0LnByb3RvdHlwZS5fY3JlYXRlRHJhd2VyPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXM7cmV0dXJuIG5ldyBxVmUuUHJveHlEcmF3ZXIoZnVuY3Rpb24oKXtyZXR1cm4gbmV3IGZLdC5BcmVhU1ZHRHJhd2VyfSxmdW5jdGlvbihpKXtyZXR1cm4gbmV3IEVhdC5DYW52YXNEcmF3ZXIoaSxmS3QubWFrZUFyZWFDYW52YXNEcmF3U3RlcChmdW5jdGlvbigpe3ZhciBvPW4uX2Nvb3JkaW5hdGVQcm9qZWN0b3JzKCksYT1vWzBdLHM9b1sxXSxsPW9bMl0sYz1uLl9jcmVhdGVEZWZpbmVkUHJvamVjdG9yKGEscyk7cmV0dXJuIG4uX2NyZWF0ZUFyZWFHZW5lcmF0b3IoYSxzLGwsYyxyKX0sZnVuY3Rpb24oKXt2YXIgbz1uLl9jb29yZGluYXRlUHJvamVjdG9ycygpLGE9b1swXSxzPW9bMV0sbD1uLl9jcmVhdGVEZWZpbmVkUHJvamVjdG9yKGEscyk7cmV0dXJuIG4uX2NyZWF0ZVRvcExpbmVHZW5lcmF0b3IoYSxzLGwscil9KSl9KX0sdC5wcm90b3R5cGUuX2dlbmVyYXRlRHJhd1N0ZXBzPWZ1bmN0aW9uKCl7dmFyIHI9W107aWYodGhpcy5fYW5pbWF0ZU9uTmV4dFJlbmRlcigpKXt2YXIgbj10aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKTtuLmQ9dGhpcy5fY29uc3RydWN0QXJlYVByb2plY3RvcihEcy5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLngoKSksdGhpcy5fZ2V0UmVzZXRZRnVuY3Rpb24oKSxEcy5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnkwKCkpKSxyLnB1c2goe2F0dHJUb1Byb2plY3RvcjpuLGFuaW1hdG9yOnRoaXMuX2dldEFuaW1hdG9yKGNCLkFuaW1hdG9yLlJFU0VUKX0pfXJldHVybiByLnB1c2goe2F0dHJUb1Byb2plY3Rvcjp0aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKSxhbmltYXRvcjp0aGlzLl9nZXRBbmltYXRvcihjQi5BbmltYXRvci5NQUlOKX0pLHJ9LHQucHJvdG90eXBlLl91cGRhdGVZU2NhbGU9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLmdldEV4dGVudHNGb3JQcm9wZXJ0eSgieTAiKSxuPWc0LkFycmF5LmZsYXR0ZW4ociksaT1nNC5BcnJheS51bmlxKG4pLG89aS5sZW5ndGg9PT0xP2lbMF06bnVsbCxhPXRoaXMueSgpLHM9YSYmYS5zY2FsZTtzIT1udWxsJiYodGhpcy5fY29uc3RhbnRCYXNlbGluZVZhbHVlUHJvdmlkZXIhPW51bGwmJihzLnJlbW92ZVBhZGRpbmdFeGNlcHRpb25zUHJvdmlkZXIodGhpcy5fY29uc3RhbnRCYXNlbGluZVZhbHVlUHJvdmlkZXIpLHRoaXMuX2NvbnN0YW50QmFzZWxpbmVWYWx1ZVByb3ZpZGVyPW51bGwpLG8hPW51bGwmJih0aGlzLl9jb25zdGFudEJhc2VsaW5lVmFsdWVQcm92aWRlcj1mdW5jdGlvbigpe3JldHVybltvXX0scy5hZGRQYWRkaW5nRXhjZXB0aW9uc1Byb3ZpZGVyKHRoaXMuX2NvbnN0YW50QmFzZWxpbmVWYWx1ZVByb3ZpZGVyKSkpfSx0LnByb3RvdHlwZS5fZ2V0UmVzZXRZRnVuY3Rpb249ZnVuY3Rpb24oKXtyZXR1cm4gRHMuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55MCgpKX0sdC5wcm90b3R5cGUuX2Nvb3JkaW5hdGVQcm9qZWN0b3JzPWZ1bmN0aW9uKCl7cmV0dXJuW0RzLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMueCgpKSxEcy5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnkoKSksRHMuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55MCgpKV19LHQucHJvdG90eXBlLl9wcm9wZXJ0eVByb2plY3RvcnM9ZnVuY3Rpb24oKXt2YXIgcj1lLnByb3RvdHlwZS5fcHJvcGVydHlQcm9qZWN0b3JzLmNhbGwodGhpcyksbj10aGlzLl9jb29yZGluYXRlUHJvamVjdG9ycygpLGk9blswXSxvPW5bMV0sYT1uWzJdO3JldHVybiByLmQ9dGhpcy5fY29uc3RydWN0QXJlYVByb2plY3RvcihpLG8sYSkscn0sdC5wcm90b3R5cGUuc2VsZWN0aW9ucz1mdW5jdGlvbihyKXt2YXIgbj10aGlzO2lmKHI9PT12b2lkIDAmJihyPXRoaXMuZGF0YXNldHMoKSksdGhpcy5yZW5kZXJlcigpPT09ImNhbnZhcyIpcmV0dXJuIGxCLnNlbGVjdEFsbCgpO3ZhciBpPWUucHJvdG90eXBlLnNlbGVjdGlvbnMuY2FsbCh0aGlzLHIpLm5vZGVzKCksbz1yLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gbi5fbGluZURyYXdlcnMuZ2V0KGEpfSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhIT1udWxsfSk7cmV0dXJuIG8uZm9yRWFjaChmdW5jdGlvbihhKXtyZXR1cm4gaS5wdXNoLmFwcGx5KGksYS5nZXRWaXN1YWxQcmltaXRpdmVzKCkpfSksbEIuc2VsZWN0QWxsKGkpfSx0LnByb3RvdHlwZS5fY29uc3RydWN0QXJlYVByb2plY3Rvcj1mdW5jdGlvbihyLG4saSl7dmFyIG89dGhpcyxhPXRoaXMuX2NyZWF0ZURlZmluZWRQcm9qZWN0b3IoRHMuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpLERzLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMueSgpKSk7cmV0dXJuIGZ1bmN0aW9uKHMsbCxjKXt2YXIgdT1vLl9jcmVhdGVBcmVhR2VuZXJhdG9yKHIsbixpLGEsYyk7cmV0dXJuIHUocyl9fSx0LnByb3RvdHlwZS5fY3JlYXRlRGVmaW5lZFByb2plY3Rvcj1mdW5jdGlvbihyLG4pe3JldHVybiBmdW5jdGlvbihpLG8sYSl7dmFyIHM9cihpLG8sYSksbD1uKGksbyxhKTtyZXR1cm4gZzQuTWF0aC5pc1ZhbGlkTnVtYmVyKHMpJiZnNC5NYXRoLmlzVmFsaWROdW1iZXIobCl9fSx0LnByb3RvdHlwZS5fY3JlYXRlQXJlYUdlbmVyYXRvcj1mdW5jdGlvbihyLG4saSxvLGEpe3ZhciBzPXRoaXMuX2dldEN1cnZlRmFjdG9yeSgpLGw9bEIuYXJlYSgpLngoZnVuY3Rpb24oYyx1KXtyZXR1cm4gcihjLHUsYSl9KS55MShmdW5jdGlvbihjLHUpe3JldHVybiBuKGMsdSxhKX0pLnkwKGZ1bmN0aW9uKGMsdSl7cmV0dXJuIGkoYyx1LGEpfSkuY3VydmUocykuZGVmaW5lZChmdW5jdGlvbihjLHUpe3JldHVybiBvKGMsdSxhKX0pO3JldHVybiBsfSx0LnByb3RvdHlwZS5fY3JlYXRlVG9wTGluZUdlbmVyYXRvcj1mdW5jdGlvbihyLG4saSxvKXt2YXIgYT10aGlzLl9nZXRDdXJ2ZUZhY3RvcnkoKSxzPWxCLmxpbmUoKS54KGZ1bmN0aW9uKGwsYyl7cmV0dXJuIHIobCxjLG8pfSkueShmdW5jdGlvbihsLGMpe3JldHVybiBuKGwsYyxvKX0pLmN1cnZlKGEpLmRlZmluZWQoZnVuY3Rpb24obCxjKXtyZXR1cm4gaShsLGMsbyl9KTtyZXR1cm4gc30sdC5fWTBfS0VZPSJ5MCIsdH0oR1ZlLkxpbmUpO1RhdC5BcmVhPVdWZX0pO3ZhciBfND1IKEd1PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShHdSwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFlWZT0oZGUoKSxVdChwZSkpLFBhdD0oRXIoKSxVdChNcikpLEFhdD1fbCgpLGpWZT1MZigpLFhWZT1CdSgpLCRWZT1CMSgpLEtWZT1VdSgpLFpWZT1pQigpLEpWZT1DUygpLElhdD1rcygpLGRLdD12ZCgpLHhsPUZlKCksa2F0PUlmKCksbUt0PUlTKCksdUI9cnMoKSxRVmU9SDEoKTtHdS5CYXJPcmllbnRhdGlvbj1rYXQubWFrZUVudW0oWyJ2ZXJ0aWNhbCIsImhvcml6b250YWwiXSk7R3UuTGFiZWxzUG9zaXRpb249a2F0Lm1ha2VFbnVtKFsic3RhcnQiLCJtaWRkbGUiLCJlbmQiLCJvdXRzaWRlIl0pO0d1LkJhckFsaWdubWVudD1rYXQubWFrZUVudW0oWyJzdGFydCIsIm1pZGRsZSIsImVuZCJdKTt2YXIgTGF0PWZ1bmN0aW9uKGUpe1lWZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3I9PT12b2lkIDAmJihyPSJ2ZXJ0aWNhbCIpO3ZhciBuPWUuY2FsbCh0aGlzKXx8dGhpcztpZihuLl9sYWJlbEZvcm1hdHRlcj1YVmUuaWRlbnRpdHkoKSxuLl9sYWJlbHNFbmFibGVkPSExLG4uX2xhYmVsc1Bvc2l0aW9uPUd1LkxhYmVsc1Bvc2l0aW9uLmVuZCxuLl9oaWRlQmFyc0lmQW55QXJlVG9vV2lkZT0hMCxuLl9iYXJBbGlnbm1lbnQ9Im1pZGRsZSIsbi5fY29tcHV0ZUJhclBpeGVsVGhpY2tuZXNzPUpWZS5tZW1vaXplKHRVZSksbi5fZml4ZWRCYXJQaXhlbFRoaWNrbmVzcz0hMCxuLmFkZENsYXNzKCJiYXItcGxvdCIpLHIhPT0idmVydGljYWwiJiZyIT09Imhvcml6b250YWwiKXRocm93IG5ldyBFcnJvcihyKyIgaXMgbm90IGEgdmFsaWQgb3JpZW50YXRpb24gZm9yIFBsb3RzLkJhciIpO3JldHVybiBuLl9pc1ZlcnRpY2FsPXI9PT0idmVydGljYWwiLG4uYW5pbWF0b3IoImJhc2VsaW5lIixuZXcgalZlLk51bGwpLG4uYXR0cigiZmlsbCIsbmV3IElhdC5Db2xvcigpLnJhbmdlKClbMF0pLG4uYXR0cih0Ll9CQVJfVEhJQ0tORVNTX0tFWSxmdW5jdGlvbigpe3JldHVybiBuLl9iYXJQaXhlbFRoaWNrbmVzcygpfSksbi5fbGFiZWxDb25maWc9bmV3IHhsLk1hcCxuLl9iYXNlbGluZVZhbHVlUHJvdmlkZXI9ZnVuY3Rpb24oKXtyZXR1cm5bbi5iYXNlbGluZVZhbHVlKCldfSxufXJldHVybiB0LnByb3RvdHlwZS5jb21wdXRlTGF5b3V0PWZ1bmN0aW9uKHIsbixpKXtyZXR1cm4gZS5wcm90b3R5cGUuY29tcHV0ZUxheW91dC5jYWxsKHRoaXMscixuLGkpLHRoaXMuX3VwZGF0ZUV4dGVudHMoKSx0aGlzfSx0LnByb3RvdHlwZS54PWZ1bmN0aW9uKHIsbil7cmV0dXJuIHI9PW51bGw/ZS5wcm90b3R5cGUueC5jYWxsKHRoaXMpOihuPT1udWxsP2UucHJvdG90eXBlLnguY2FsbCh0aGlzLHIpOmUucHJvdG90eXBlLnguY2FsbCh0aGlzLHIsbiksdGhpcy5fdXBkYXRlVGhpY2tuZXNzQXR0cigpLHRoaXMuX3VwZGF0ZUxlbmd0aFNjYWxlKCksdGhpcyl9LHQucHJvdG90eXBlLnk9ZnVuY3Rpb24ocixuKXtyZXR1cm4gcj09bnVsbD9lLnByb3RvdHlwZS55LmNhbGwodGhpcyk6KG49PW51bGw/ZS5wcm90b3R5cGUueS5jYWxsKHRoaXMscik6ZS5wcm90b3R5cGUueS5jYWxsKHRoaXMscixuKSx0aGlzLl91cGRhdGVMZW5ndGhTY2FsZSgpLHRoaXMpfSx0LnByb3RvdHlwZS5sZW5ndGg9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5faXNWZXJ0aWNhbD90aGlzLnkoKTp0aGlzLngoKX0sdC5wcm90b3R5cGUucG9zaXRpb249ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5faXNWZXJ0aWNhbD90aGlzLngoKTp0aGlzLnkoKX0sdC5wcm90b3R5cGUuYmFyRW5kPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX3Byb3BlcnR5QmluZGluZ3MuZ2V0KHQuX0JBUl9FTkRfS0VZKTt2YXIgbj10aGlzLnBvc2l0aW9uKCksaT1uJiZuLnNjYWxlO3JldHVybiB0aGlzLl9iaW5kUHJvcGVydHkodC5fQkFSX0VORF9LRVkscixpKSx0aGlzLl91cGRhdGVUaGlja25lc3NBdHRyKCksdGhpcy5fdXBkYXRlTGVuZ3RoU2NhbGUoKSx0aGlzLnJlbmRlcigpLHRoaXN9LHQucHJvdG90eXBlLmJhckFsaWdubWVudD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9iYXJBbGlnbm1lbnQ6KHRoaXMuX2JhckFsaWdubWVudD1yLHRoaXMuX2NsZWFyQXR0clRvUHJvamVjdG9yQ2FjaGUoKSx0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5vcmllbnRhdGlvbj1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9pc1ZlcnRpY2FsPyJ2ZXJ0aWNhbCI6Imhvcml6b250YWwifSx0LnByb3RvdHlwZS5fY3JlYXRlRHJhd2VyPWZ1bmN0aW9uKCl7cmV0dXJuIG5ldyBLVmUuUHJveHlEcmF3ZXIoZnVuY3Rpb24oKXtyZXR1cm4gbmV3IFpWZS5SZWN0YW5nbGVTVkdEcmF3ZXIodC5fQkFSX0FSRUFfQ0xBU1MpfSxmdW5jdGlvbihyKXtyZXR1cm4gbmV3ICRWZS5SZWN0YW5nbGVDYW52YXNEcmF3ZXIocil9KX0sdC5wcm90b3R5cGUuX3NldHVwPWZ1bmN0aW9uKCl7ZS5wcm90b3R5cGUuX3NldHVwLmNhbGwodGhpcyksdGhpcy5fYmFzZWxpbmU9dGhpcy5fcmVuZGVyQXJlYS5hcHBlbmQoImxpbmUiKS5jbGFzc2VkKCJiYXNlbGluZSIsITApfSx0LnByb3RvdHlwZS5iYXNlbGluZVZhbHVlPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpe2lmKHRoaXMuX2Jhc2VsaW5lVmFsdWUhPW51bGwpcmV0dXJuIHRoaXMuX2Jhc2VsaW5lVmFsdWU7aWYoIXRoaXMuX3Byb2plY3RvcnNSZWFkeSgpKXJldHVybiAwO3ZhciBuPXRoaXMubGVuZ3RoKCkuc2NhbGU7cmV0dXJuIG4mJm4gaW5zdGFuY2VvZiBJYXQuVGltZT9uZXcgRGF0ZSgwKTowfXJldHVybiB0aGlzLl9iYXNlbGluZVZhbHVlPXIsdGhpcy5fdXBkYXRlTGVuZ3RoU2NhbGUoKSx0aGlzLl9jbGVhckF0dHJUb1Byb2plY3RvckNhY2hlKCksdGhpcy5yZW5kZXIoKSx0aGlzfSx0LnByb3RvdHlwZS5hZGREYXRhc2V0PWZ1bmN0aW9uKHIpe3JldHVybiBlLnByb3RvdHlwZS5hZGREYXRhc2V0LmNhbGwodGhpcyxyKSx0aGlzfSx0LnByb3RvdHlwZS5fYWRkRGF0YXNldD1mdW5jdGlvbihyKXtyZXR1cm4gZS5wcm90b3R5cGUuX2FkZERhdGFzZXQuY2FsbCh0aGlzLHIpLHRoaXN9LHQucHJvdG90eXBlLnJlbW92ZURhdGFzZXQ9ZnVuY3Rpb24ocil7cmV0dXJuIGUucHJvdG90eXBlLnJlbW92ZURhdGFzZXQuY2FsbCh0aGlzLHIpLHRoaXN9LHQucHJvdG90eXBlLl9yZW1vdmVEYXRhc2V0PWZ1bmN0aW9uKHIpe3JldHVybiBlLnByb3RvdHlwZS5fcmVtb3ZlRGF0YXNldC5jYWxsKHRoaXMsciksdGhpc30sdC5wcm90b3R5cGUuZGF0YXNldHM9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/ZS5wcm90b3R5cGUuZGF0YXNldHMuY2FsbCh0aGlzKTooZS5wcm90b3R5cGUuZGF0YXNldHMuY2FsbCh0aGlzLHIpLHRoaXMpfSx0LnByb3RvdHlwZS5sYWJlbHNFbmFibGVkPWZ1bmN0aW9uKHIsbil7cmV0dXJuIHI9PW51bGw/dGhpcy5fbGFiZWxzRW5hYmxlZDoodGhpcy5fbGFiZWxzRW5hYmxlZD1yLG4hPW51bGwmJih0aGlzLl9sYWJlbHNQb3NpdGlvbj1uKSx0aGlzLl9jbGVhckF0dHJUb1Byb2plY3RvckNhY2hlKCksdGhpcy5yZW5kZXIoKSx0aGlzKX0sdC5wcm90b3R5cGUubGFiZWxGb3JtYXR0ZXI9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fbGFiZWxGb3JtYXR0ZXI6KHRoaXMuX2xhYmVsRm9ybWF0dGVyPXIsdGhpcy5fY2xlYXJBdHRyVG9Qcm9qZWN0b3JDYWNoZSgpLHRoaXMucmVuZGVyKCksdGhpcyl9LHQucHJvdG90eXBlLl9jcmVhdGVOb2Rlc0ZvckRhdGFzZXQ9ZnVuY3Rpb24ocil7dmFyIG49ZS5wcm90b3R5cGUuX2NyZWF0ZU5vZGVzRm9yRGF0YXNldC5jYWxsKHRoaXMsciksaT10aGlzLl9yZW5kZXJBcmVhLmFwcGVuZCgiZyIpLmNsYXNzZWQodC5fTEFCRUxfQVJFQV9DTEFTUywhMCksbz1uZXcgQWF0LlN2Z0NvbnRleHQoaS5ub2RlKCkpLGE9bmV3IEFhdC5DYWNoZU1lYXN1cmVyKG8pLHM9bmV3IEFhdC5Xcml0ZXIoYSxvKTtyZXR1cm4gdGhpcy5fbGFiZWxDb25maWcuc2V0KHIse2xhYmVsQXJlYTppLG1lYXN1cmVyOmEsd3JpdGVyOnN9KSxufSx0LnByb3RvdHlwZS5fcmVtb3ZlRGF0YXNldE5vZGVzPWZ1bmN0aW9uKHIpe2UucHJvdG90eXBlLl9yZW1vdmVEYXRhc2V0Tm9kZXMuY2FsbCh0aGlzLHIpO3ZhciBuPXRoaXMuX2xhYmVsQ29uZmlnLmdldChyKTtuIT1udWxsJiYobi5sYWJlbEFyZWEucmVtb3ZlKCksdGhpcy5fbGFiZWxDb25maWcuZGVsZXRlKHIpKX0sdC5wcm90b3R5cGUuZW50aXR5TmVhcmVzdD1mdW5jdGlvbihyKXt2YXIgbj10aGlzLGk9ZnVuY3Rpb24oKXt2YXIgbz1uLl9pc1ZlcnRpY2FsP24uX2dldEVudGl0eVN0b3JlKCkuZW50aXR5TmVhcmVzdFgocik6bi5fZ2V0RW50aXR5U3RvcmUoKS5lbnRpdHlOZWFyZXN0WShyKTtyZXR1cm4gbz09PXZvaWQgMD92b2lkIDA6bi5fbGlnaHR3ZWlnaHRQbG90RW50aXR5VG9QbG90RW50aXR5KG8pfTtyZXR1cm4gdGhpcy5fZml4ZWRCYXJQaXhlbFRoaWNrbmVzcz90aGlzLl9jb21wdXRlQmFyUGl4ZWxUaGlja25lc3MuZG9Mb2NrZWQoaSk6aSgpfSx0LnByb3RvdHlwZS5lbnRpdGllc0F0PWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMsaT1mdW5jdGlvbigpe3JldHVybiBuLl9lbnRpdGllc0ludGVyc2VjdGluZyhyLngsci55KX07cmV0dXJuIHRoaXMuX2ZpeGVkQmFyUGl4ZWxUaGlja25lc3M/dGhpcy5fY29tcHV0ZUJhclBpeGVsVGhpY2tuZXNzLmRvTG9ja2VkKGkpOmkoKX0sdC5wcm90b3R5cGUuZW50aXRpZXNJbkJvdW5kcz1mdW5jdGlvbihyKXt2YXIgbj10aGlzLGk9ZnVuY3Rpb24oKXtyZXR1cm4gZS5wcm90b3R5cGUuZW50aXRpZXNJbkJvdW5kcy5jYWxsKG4scil9O3JldHVybiB0aGlzLl9maXhlZEJhclBpeGVsVGhpY2tuZXNzP3RoaXMuX2NvbXB1dGVCYXJQaXhlbFRoaWNrbmVzcy5kb0xvY2tlZChpKTppKCl9LHQucHJvdG90eXBlLmVudGl0aWVzSW5YQm91bmRzPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMsaT1mdW5jdGlvbigpe3JldHVybiBlLnByb3RvdHlwZS5lbnRpdGllc0luWEJvdW5kcy5jYWxsKG4scil9O3JldHVybiB0aGlzLl9maXhlZEJhclBpeGVsVGhpY2tuZXNzP3RoaXMuX2NvbXB1dGVCYXJQaXhlbFRoaWNrbmVzcy5kb0xvY2tlZChpKTppKCl9LHQucHJvdG90eXBlLmVudGl0aWVzSW5ZQm91bmRzPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMsaT1mdW5jdGlvbigpe3JldHVybiBlLnByb3RvdHlwZS5lbnRpdGllc0luWUJvdW5kcy5jYWxsKG4scil9O3JldHVybiB0aGlzLl9maXhlZEJhclBpeGVsVGhpY2tuZXNzP3RoaXMuX2NvbXB1dGVCYXJQaXhlbFRoaWNrbmVzcy5kb0xvY2tlZChpKTppKCl9LHQucHJvdG90eXBlLl9lbnRpdGllc0ludGVyc2VjdGluZz1mdW5jdGlvbihyLG4pe2Zvcih2YXIgaT1bXSxvPXRoaXMuX2dldEVudGl0eVN0b3JlKCkuZW50aXRpZXMoKSxhPW8ubGVuZ3RoLHM9MDtzPGE7cysrKXt2YXIgbD1vW3NdO3hsLkRPTS5pbnRlcnNlY3RzQkJveChyLG4sdGhpcy5fZW50aXR5Qm91bmRzKGwpKSYmaS5wdXNoKHRoaXMuX2xpZ2h0d2VpZ2h0UGxvdEVudGl0eVRvUGxvdEVudGl0eShsKSl9cmV0dXJuIGl9LHQucHJvdG90eXBlLl91cGRhdGVMZW5ndGhTY2FsZT1mdW5jdGlvbigpe2lmKCEhdGhpcy5fcHJvamVjdG9yc1JlYWR5KCkpe3ZhciByPXRoaXMubGVuZ3RoKCkuc2NhbGU7ciBpbnN0YW5jZW9mIGRLdC5RdWFudGl0YXRpdmVTY2FsZSYmKHIuYWRkUGFkZGluZ0V4Y2VwdGlvbnNQcm92aWRlcih0aGlzLl9iYXNlbGluZVZhbHVlUHJvdmlkZXIpLHIuYWRkSW5jbHVkZWRWYWx1ZXNQcm92aWRlcih0aGlzLl9iYXNlbGluZVZhbHVlUHJvdmlkZXIpKX19LHQucHJvdG90eXBlLnJlbmRlckltbWVkaWF0ZWx5PWZ1bmN0aW9uKCl7dmFyIHI9dGhpcztyZXR1cm4gdGhpcy5fYmFyUGl4ZWxUaGlja25lc3MoKSx0aGlzLl9jb21wdXRlQmFyUGl4ZWxUaGlja25lc3MuZG9Mb2NrZWQoZnVuY3Rpb24oKXtyZXR1cm4gZS5wcm90b3R5cGUucmVuZGVySW1tZWRpYXRlbHkuY2FsbChyKX0pfSx0LnByb3RvdHlwZS5fYWRkaXRpb25hbFBhaW50PWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMsaT10aGlzLmxlbmd0aCgpLnNjYWxlLG89aS5zY2FsZSh0aGlzLmJhc2VsaW5lVmFsdWUoKSksYT17eDE6dGhpcy5faXNWZXJ0aWNhbD8wOm8seTE6dGhpcy5faXNWZXJ0aWNhbD9vOjAseDI6dGhpcy5faXNWZXJ0aWNhbD90aGlzLndpZHRoKCk6byx5Mjp0aGlzLl9pc1ZlcnRpY2FsP286dGhpcy5oZWlnaHQoKX07dGhpcy5fZ2V0QW5pbWF0b3IoImJhc2VsaW5lIikuYW5pbWF0ZSh0aGlzLl9iYXNlbGluZSxhKSx0aGlzLmRhdGFzZXRzKCkuZm9yRWFjaChmdW5jdGlvbihzKXtyZXR1cm4gbi5fbGFiZWxDb25maWcuZ2V0KHMpLmxhYmVsQXJlYS5zZWxlY3RBbGwoImciKS5yZW1vdmUoKX0pLHRoaXMuX2xhYmVsc0VuYWJsZWQmJnhsLldpbmRvdy5zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7cmV0dXJuIG4uX2RyYXdMYWJlbHMoKX0scil9LHQucHJvdG90eXBlLmdldEV4dGVudHNGb3JQcm9wZXJ0eT1mdW5jdGlvbihyKXt2YXIgbj10aGlzLGk9ZS5wcm90b3R5cGUuZ2V0RXh0ZW50c0ZvclByb3BlcnR5LmNhbGwodGhpcyxyKSxvO2lmKHI9PT0ieCImJnRoaXMuX2lzVmVydGljYWwpbz10aGlzLngoKTtlbHNlIGlmKHI9PT0ieSImJiF0aGlzLl9pc1ZlcnRpY2FsKW89dGhpcy55KCk7ZWxzZSByZXR1cm4gaTtpZighKG8mJm8uc2NhbGUmJm8uc2NhbGUgaW5zdGFuY2VvZiBkS3QuUXVhbnRpdGF0aXZlU2NhbGUpKXJldHVybiBpO3ZhciBhPW8uc2NhbGUscz10aGlzLl9iYXJQaXhlbFRoaWNrbmVzcygpO3JldHVybiBpPWkubWFwKGZ1bmN0aW9uKGwpe3JldHVybiBQYXQuZXh0ZW50KFthLmludmVydChuLl9nZXRQb3NpdGlvbkF0dHIoYS5zY2FsZShsWzBdKSxzKSksYS5pbnZlcnQobi5fZ2V0UG9zaXRpb25BdHRyKGEuc2NhbGUobFswXSkscykrcyksYS5pbnZlcnQobi5fZ2V0UG9zaXRpb25BdHRyKGEuc2NhbGUobFsxXSkscykpLGEuaW52ZXJ0KG4uX2dldFBvc2l0aW9uQXR0cihhLnNjYWxlKGxbMV0pLHMpK3MpXSl9KSxpfSx0LnByb3RvdHlwZS5fZ2V0UG9zaXRpb25BdHRyPWZ1bmN0aW9uKHIsbil7c3dpdGNoKHRoaXMuX2lzVmVydGljYWx8fChyLT1uLG4qPS0xKSx0aGlzLl9iYXJBbGlnbm1lbnQpe2Nhc2Uic3RhcnQiOnJldHVybiByO2Nhc2UiZW5kIjpyZXR1cm4gci1uO2Nhc2UibWlkZGxlIjpkZWZhdWx0OnJldHVybiByLW4vMn19LHQucHJvdG90eXBlLl9kcmF3TGFiZWxzPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcyxuPXRoaXMuX2dldERhdGFUb0RyYXcoKSxpPXRoaXMuX2dldEF0dHJUb1Byb2plY3RvcigpLG89dGhpcy5kYXRhc2V0cygpLnNvbWUoZnVuY3Rpb24oYSl7cmV0dXJuIG4uZ2V0KGEpLnNvbWUoZnVuY3Rpb24ocyxsKXtyZXR1cm4gcz09bnVsbD8hMTpyLl9kcmF3TGFiZWwocyxsLGEsaSl9KX0pO3RoaXMuX2hpZGVCYXJzSWZBbnlBcmVUb29XaWRlJiZvJiZ0aGlzLmRhdGFzZXRzKCkuZm9yRWFjaChmdW5jdGlvbihhKXtyZXR1cm4gci5fbGFiZWxDb25maWcuZ2V0KGEpLmxhYmVsQXJlYS5zZWxlY3RBbGwoImciKS5yZW1vdmUoKX0pfSx0LnByb3RvdHlwZS5fZHJhd0xhYmVsPWZ1bmN0aW9uKHIsbixpLG8pe3ZhciBhPXRoaXMuX2xhYmVsQ29uZmlnLmdldChpKSxzPWEubGFiZWxBcmVhLGw9YS5tZWFzdXJlcixjPWEud3JpdGVyLHU9dGhpcy5sZW5ndGgoKS5hY2Nlc3NvcixoPXUocixuLGkpLGY9dGhpcy5sZW5ndGgoKS5zY2FsZSxwPWYhPW51bGw/Zi5zY2FsZShoKTpoLGQ9ZiE9bnVsbD9mLnNjYWxlKHRoaXMuYmFzZWxpbmVWYWx1ZSgpKTp0aGlzLmJhc2VsaW5lVmFsdWUoKSxnPXt4Om8ueChyLG4saSkseTpvLnkocixuLGkpfSxfPXt3aWR0aDpvLndpZHRoKHIsbixpKSxoZWlnaHQ6by5oZWlnaHQocixuLGkpfSx5PXRoaXMuX2xhYmVsRm9ybWF0dGVyKGgscixuLGkpLHg9bC5tZWFzdXJlKHkpLGI9dGhpcy5fc2hvdWxkU2hvd0xhYmVsT25CYXIoZyxfLHgpLFM9dGhpcy5faXNWZXJ0aWNhbD9wPD1kOnA8ZCxDPXRoaXMuX2NhbGN1bGF0ZUxhYmVsUHJvcGVydGllcyhnLF8seCxiLFMpLFA9Qy5jb250YWluZXJEaW1lbnNpb25zLGs9Qy5sYWJlbENvbnRhaW5lck9yaWdpbixPPUMubGFiZWxPcmlnaW4sRD1DLmFsaWdubWVudCxCPW8uZmlsbChyLG4saSksST10aGlzLl9jcmVhdGVMYWJlbENvbnRhaW5lcihzLGssTyx4LGIsQiksTD17eEFsaWduOkQueCx5QWxpZ246RC55fTtjLndyaXRlKHksUC53aWR0aCxQLmhlaWdodCxMLEkubm9kZSgpKTt2YXIgUj10aGlzLl9pc1ZlcnRpY2FsP18ud2lkdGg8eC53aWR0aDpfLmhlaWdodDx4LmhlaWdodDtyZXR1cm4gUn0sdC5wcm90b3R5cGUuX3Nob3VsZFNob3dMYWJlbE9uQmFyPWZ1bmN0aW9uKHIsbixpKXtpZih0aGlzLl9sYWJlbHNQb3NpdGlvbj09PUd1LkxhYmVsc1Bvc2l0aW9uLm91dHNpZGUpcmV0dXJuITE7dmFyIG89dGhpcy5faXNWZXJ0aWNhbD9yLnk6ci54LGE9dGhpcy5faXNWZXJ0aWNhbD9uLmhlaWdodDpuLndpZHRoLHM9dGhpcy5faXNWZXJ0aWNhbD90aGlzLmhlaWdodCgpOnRoaXMud2lkdGgoKSxsPXRoaXMuX2lzVmVydGljYWw/aS5oZWlnaHQ6aS53aWR0aCxjPW8rYSx1PWE7cmV0dXJuIGM+cz91PXMtbzpvPDAmJih1PWMpLGwrdC5fTEFCRUxfTUFSR0lOX0lOU0lERV9CQVI8PXV9LHQucHJvdG90eXBlLl9jYWxjdWxhdGVMYWJlbFByb3BlcnRpZXM9ZnVuY3Rpb24ocixuLGksbyxhKXt2YXIgcz10aGlzLGw9dGhpcy5faXNWZXJ0aWNhbD9yLnk6ci54LGM9dGhpcy5faXNWZXJ0aWNhbD9uLmhlaWdodDpuLndpZHRoLHU9dGhpcy5faXNWZXJ0aWNhbD9pLmhlaWdodDppLndpZHRoLGg9ImNlbnRlciIsZj1jLHA9bCxkPWwsZz1mdW5jdGlvbihfKXtzd2l0Y2goXyl7Y2FzZSJ0b3BMZWZ0IjpoPXMuX2lzVmVydGljYWw/InRvcCI6ImxlZnQiLHArPXQuX0xBQkVMX01BUkdJTl9JTlNJREVfQkFSLGQrPXQuX0xBQkVMX01BUkdJTl9JTlNJREVfQkFSO3JldHVybjtjYXNlImNlbnRlciI6ZCs9KGMrdSkvMjtyZXR1cm47Y2FzZSJib3R0b21SaWdodCI6aD1zLl9pc1ZlcnRpY2FsPyJib3R0b20iOiJyaWdodCIscC09dC5fTEFCRUxfTUFSR0lOX0lOU0lERV9CQVIsZCs9Zi10Ll9MQUJFTF9NQVJHSU5fSU5TSURFX0JBUi11O3JldHVybn19O2lmKG8pc3dpdGNoKHRoaXMuX2xhYmVsc1Bvc2l0aW9uKXtjYXNlIEd1LkxhYmVsc1Bvc2l0aW9uLnN0YXJ0OmcoYT8iYm90dG9tUmlnaHQiOiJ0b3BMZWZ0Iik7YnJlYWs7Y2FzZSBHdS5MYWJlbHNQb3NpdGlvbi5taWRkbGU6ZygiY2VudGVyIik7YnJlYWs7Y2FzZSBHdS5MYWJlbHNQb3NpdGlvbi5lbmQ6ZyhhPyJ0b3BMZWZ0IjoiYm90dG9tUmlnaHQiKTticmVha31lbHNlIGE/KGg9dGhpcy5faXNWZXJ0aWNhbD8idG9wIjoibGVmdCIsZj1jK3QuX0xBQkVMX01BUkdJTl9JTlNJREVfQkFSK3UscC09dC5fTEFCRUxfTUFSR0lOX0lOU0lERV9CQVIrdSxkLT10Ll9MQUJFTF9NQVJHSU5fSU5TSURFX0JBUit1KTooaD10aGlzLl9pc1ZlcnRpY2FsPyJib3R0b20iOiJyaWdodCIsZj1jK3QuX0xBQkVMX01BUkdJTl9JTlNJREVfQkFSK3UsZCs9Yyt0Ll9MQUJFTF9NQVJHSU5fSU5TSURFX0JBUik7cmV0dXJue2NvbnRhaW5lckRpbWVuc2lvbnM6e3dpZHRoOnRoaXMuX2lzVmVydGljYWw/bi53aWR0aDpmLGhlaWdodDp0aGlzLl9pc1ZlcnRpY2FsP2Y6bi5oZWlnaHR9LGxhYmVsQ29udGFpbmVyT3JpZ2luOnt4OnRoaXMuX2lzVmVydGljYWw/ci54OnAseTp0aGlzLl9pc1ZlcnRpY2FsP3A6ci55fSxsYWJlbE9yaWdpbjp7eDp0aGlzLl9pc1ZlcnRpY2FsP3IueCtuLndpZHRoLzItaS53aWR0aC8yOmQseTp0aGlzLl9pc1ZlcnRpY2FsP2Q6ci55K24uaGVpZ2h0LzItaS5oZWlnaHQvMn0sYWxpZ25tZW50Ont4OnRoaXMuX2lzVmVydGljYWw/ImNlbnRlciI6aCx5OnRoaXMuX2lzVmVydGljYWw/aDoiY2VudGVyIn19fSx0LnByb3RvdHlwZS5fY3JlYXRlTGFiZWxDb250YWluZXI9ZnVuY3Rpb24ocixuLGksbyxhLHMpe3ZhciBsPXIuYXBwZW5kKCJnIikuYXR0cigidHJhbnNmb3JtIiwidHJhbnNsYXRlKCIrbi54KyIsICIrbi55KyIpIik7aWYoYSl7bC5jbGFzc2VkKCJvbi1iYXItbGFiZWwiLCEwKTt2YXIgYz14bC5Db2xvci5jb250cmFzdCgid2hpdGUiLHMpKjEuNjx4bC5Db2xvci5jb250cmFzdCgiYmxhY2siLHMpO2wuY2xhc3NlZChjPyJkYXJrLWxhYmVsIjoibGlnaHQtbGFiZWwiLCEwKX1lbHNlIGwuY2xhc3NlZCgib2ZmLWJhci1sYWJlbCIsITApO3JldHVybiBsfSx0LnByb3RvdHlwZS5fZ2VuZXJhdGVEcmF3U3RlcHM9ZnVuY3Rpb24oKXt2YXIgcj1bXTtpZih0aGlzLl9hbmltYXRlT25OZXh0UmVuZGVyKCkpe3ZhciBuPXRoaXMuX2dldEF0dHJUb1Byb2plY3RvcigpLGk9dGhpcy5sZW5ndGgoKS5zY2FsZSxvPWkuc2NhbGUodGhpcy5iYXNlbGluZVZhbHVlKCkpLGE9dGhpcy5faXNWZXJ0aWNhbD8ieSI6IngiLHM9dGhpcy5faXNWZXJ0aWNhbD8iaGVpZ2h0Ijoid2lkdGgiO25bYV09ZnVuY3Rpb24oKXtyZXR1cm4gb30sbltzXT1mdW5jdGlvbigpe3JldHVybiAwfSxyLnB1c2goe2F0dHJUb1Byb2plY3RvcjpuLGFuaW1hdG9yOnRoaXMuX2dldEFuaW1hdG9yKG1LdC5BbmltYXRvci5SRVNFVCl9KX1yZXR1cm4gci5wdXNoKHthdHRyVG9Qcm9qZWN0b3I6dGhpcy5fZ2V0QXR0clRvUHJvamVjdG9yKCksYW5pbWF0b3I6dGhpcy5fZ2V0QW5pbWF0b3IobUt0LkFuaW1hdG9yLk1BSU4pfSkscn0sdC5wcm90b3R5cGUuX2dlbmVyYXRlQXR0clRvUHJvamVjdG9yPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcyxuPWUucHJvdG90eXBlLl9nZW5lcmF0ZUF0dHJUb1Byb2plY3Rvci5jYWxsKHRoaXMpLGk9dGhpcy5sZW5ndGgoKS5zY2FsZSxvPWkuc2NhbGUodGhpcy5iYXNlbGluZVZhbHVlKCkpLGE9dGhpcy5faXNWZXJ0aWNhbD8ieSI6IngiLHM9dGhpcy5faXNWZXJ0aWNhbD8ieCI6InkiLGw9dUIuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy5wb3NpdGlvbigpKSxjPXVCLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMubGVuZ3RoKCkpLHU9ZnVuY3Rpb24oZCxnLF8pe3JldHVybiBNYXRoLmFicyhvLWMoZCxnLF8pKX0saD1uW3QuX0JBUl9USElDS05FU1NfS0VZXSxmPW4uZ2FwLHA9Zj09bnVsbD9oOmZ1bmN0aW9uKGQsZyxfKXt2YXIgeT1oKGQsZyxfKTtyZXR1cm4geTx0Ll9CQVJfR0FQTEVTU19USFJFU0hPTERfUFg/eTp5LWYoZCxnLF8pfTtyZXR1cm4gbi53aWR0aD10aGlzLl9pc1ZlcnRpY2FsP3A6dSxuLmhlaWdodD10aGlzLl9pc1ZlcnRpY2FsP3U6cCxuW2FdPWZ1bmN0aW9uKGQsZyxfKXt2YXIgeT1jKGQsZyxfKTtyZXR1cm4geT5vP286eX0sbltzXT1mdW5jdGlvbihkLGcsXyl7cmV0dXJuIHIuX2dldFBvc2l0aW9uQXR0cihsKGQsZyxfKSxoKGQsZyxfKSl9LG59LHQucHJvdG90eXBlLl91cGRhdGVUaGlja25lc3NBdHRyPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcyxuPXRoaXMucG9zaXRpb24oKSxpPXRoaXMuYmFyRW5kKCk7biE9bnVsbCYmaSE9bnVsbD8odGhpcy5fZml4ZWRCYXJQaXhlbFRoaWNrbmVzcz0hMSx0aGlzLmF0dHIodC5fQkFSX1RISUNLTkVTU19LRVksZnVuY3Rpb24obyxhLHMpe3ZhciBsPW4uYWNjZXNzb3IobyxhLHMpLGM9aS5hY2Nlc3NvcihvLGEscyk7cmV0dXJuIGw9bi5zY2FsZT9uLnNjYWxlLnNjYWxlKGwpOmwsYz1pLnNjYWxlP2kuc2NhbGUuc2NhbGUoYyk6YyxNYXRoLmFicyhjLWwpfSkpOih0aGlzLl9maXhlZEJhclBpeGVsVGhpY2tuZXNzPSEwLHRoaXMuYXR0cih0Ll9CQVJfVEhJQ0tORVNTX0tFWSxmdW5jdGlvbigpe3JldHVybiByLl9iYXJQaXhlbFRoaWNrbmVzcygpfSkpfSx0LnByb3RvdHlwZS5fYmFyUGl4ZWxUaGlja25lc3M9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZml4ZWRCYXJQaXhlbFRoaWNrbmVzcyYmdGhpcy5fcHJvamVjdG9yc1JlYWR5KCk/dGhpcy5fY29tcHV0ZUJhclBpeGVsVGhpY2tuZXNzKHRoaXMucG9zaXRpb24oKSx0aGlzLmRhdGFzZXRzKCksdGhpcy5faXNWZXJ0aWNhbD90aGlzLndpZHRoKCk6dGhpcy5oZWlnaHQoKSk6MH0sdC5wcm90b3R5cGUuZW50aXRpZXM9ZnVuY3Rpb24ocil7aWYocj09PXZvaWQgMCYmKHI9dGhpcy5kYXRhc2V0cygpKSwhdGhpcy5fcHJvamVjdG9yc1JlYWR5KCkpcmV0dXJuW107dmFyIG49ZS5wcm90b3R5cGUuZW50aXRpZXMuY2FsbCh0aGlzLHIpO3JldHVybiBufSx0LnByb3RvdHlwZS5fZW50aXR5Qm91bmRzPWZ1bmN0aW9uKHIpe3ZhciBuPXIuZGF0dW0saT1yLmluZGV4LG89ci5kYXRhc2V0O3JldHVybiB0aGlzLl9waXhlbEJvdW5kcyhuLGksbyl9LHQucHJvdG90eXBlLl9waXhlbEJvdW5kcz1mdW5jdGlvbihyLG4saSl7dmFyIG89dGhpcy5fZ2V0QXR0clRvUHJvamVjdG9yKCk7cmV0dXJue3g6by54KHIsbixpKSx5Om8ueShyLG4saSksd2lkdGg6by53aWR0aChyLG4saSksaGVpZ2h0Om8uaGVpZ2h0KHIsbixpKX19LHQucHJvdG90eXBlLl9waXhlbFBvaW50PWZ1bmN0aW9uKHIsbixpKXt2YXIgbz10aGlzLl9waXhlbEJvdW5kcyhyLG4saSksYT0odGhpcy5faXNWZXJ0aWNhbD91Qi5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnkoKSk6dUIuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpKShyLG4saSkscz0odGhpcy5faXNWZXJ0aWNhbD90aGlzLnkoKS5zY2FsZTp0aGlzLngoKS5zY2FsZSkuc2NhbGUodGhpcy5iYXNlbGluZVZhbHVlKCkpO3JldHVybiB0aGlzLl9waXhlbFBvaW50QmFyKGEscyxvKX0sdC5wcm90b3R5cGUuX3BpeGVsUG9pbnRCYXI9ZnVuY3Rpb24ocixuLGkpe3ZhciBvLGE7cmV0dXJuIHRoaXMuX2lzVmVydGljYWw/KG89aS54K2kud2lkdGgvMixhPXI8PW4/aS55OmkueStpLmhlaWdodCk6KG89cj49bj9pLngraS53aWR0aDppLngsYT1pLnkraS5oZWlnaHQvMikse3g6byx5OmF9fSx0LnByb3RvdHlwZS5fdW5pbnN0YWxsU2NhbGVGb3JLZXk9ZnVuY3Rpb24ocixuKXtlLnByb3RvdHlwZS5fdW5pbnN0YWxsU2NhbGVGb3JLZXkuY2FsbCh0aGlzLHIsbil9LHQucHJvdG90eXBlLl9nZXREYXRhVG9EcmF3PWZ1bmN0aW9uKCl7dmFyIHI9dGhpcyxuPW5ldyB4bC5NYXAsaT10aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKSxvPXRoaXMud2lkdGgoKSxhPXRoaXMuaGVpZ2h0KCk7cmV0dXJuIHRoaXMuZGF0YXNldHMoKS5mb3JFYWNoKGZ1bmN0aW9uKHMpe3ZhciBsPXMuZGF0YSgpLm1hcChmdW5jdGlvbihjLHUpe3ZhciBoPXIuX2lzRGF0dW1PblNjcmVlbihpLG8sYSxjLHUscyk7cmV0dXJuIGg/YzpudWxsfSk7bi5zZXQocyxsKX0pLG59LHQucHJvdG90eXBlLl9pc0RhdHVtT25TY3JlZW49ZnVuY3Rpb24ocixuLGksbyxhLHMpe3ZhciBsPXIueChvLGEscyksYz1yLnkobyxhLHMpLHU9ci53aWR0aChvLGEscyksaD1yLmhlaWdodChvLGEscyksZj14bC5NYXRoLmlzVmFsaWROdW1iZXIobCkmJnhsLk1hdGguaXNWYWxpZE51bWJlcihjKSYmeGwuTWF0aC5pc1ZhbGlkTnVtYmVyKHUpJiZ4bC5NYXRoLmlzVmFsaWROdW1iZXIoaCk7cmV0dXJuIGY/eGwuTWF0aC5ib3VuZHNJbnRlcnNlY3RzKGwsYyx1LGgsMCwwLG4saSk6ITF9LHQucHJvdG90eXBlLmludmFsaWRhdGVDYWNoZT1mdW5jdGlvbigpe3ZhciByPXRoaXM7ZS5wcm90b3R5cGUuaW52YWxpZGF0ZUNhY2hlLmNhbGwodGhpcyksdGhpcy5kYXRhc2V0cygpLmZvckVhY2goZnVuY3Rpb24obil7cmV0dXJuIHIuX2xhYmVsQ29uZmlnLmdldChuKS5tZWFzdXJlci5yZXNldCgpfSl9LHQuX0JBUl9USElDS05FU1NfUkFUSU89Ljk1LHQuX0JBUl9HQVBMRVNTX1RIUkVTSE9MRF9QWD0zLHQuX1NJTkdMRV9CQVJfRElNRU5TSU9OX1JBVElPPS40LHQuX0JBUl9BUkVBX0NMQVNTPSJiYXItYXJlYSIsdC5fQkFSX0VORF9LRVk9ImJhckVuZCIsdC5fQkFSX1RISUNLTkVTU19LRVk9IndpZHRoIix0Ll9MQUJFTF9BUkVBX0NMQVNTPSJiYXItbGFiZWwtdGV4dC1hcmVhIix0Ll9MQUJFTF9NQVJHSU5fSU5TSURFX0JBUj0xMCx0fShRVmUuWFlQbG90KTtHdS5CYXI9TGF0O2Z1bmN0aW9uIHRVZShlLHQscil7dmFyIG4saT1lLnNjYWxlO2lmKGkgaW5zdGFuY2VvZiBJYXQuQ2F0ZWdvcnkpbj1pLnJhbmdlQmFuZCgpO2Vsc2V7dmFyIG89ZS5hY2Nlc3NvcixhPVBhdC5zZXQoeGwuQXJyYXkuZmxhdHRlbih0Lm1hcChmdW5jdGlvbihjKXtyZXR1cm4gYy5kYXRhKCkubWFwKGZ1bmN0aW9uKHUsaCl7cmV0dXJuIG8odSxoLGMpfSkuZmlsdGVyKGZ1bmN0aW9uKHUpe3JldHVybiB1IT1udWxsfSkubWFwKGZ1bmN0aW9uKHUpe3JldHVybiB1LnZhbHVlT2YoKX0pfSkpKS52YWx1ZXMoKS5tYXAoZnVuY3Rpb24oYyl7cmV0dXJuK2N9KTthLnNvcnQoZnVuY3Rpb24oYyx1KXtyZXR1cm4gYy11fSk7dmFyIHM9YS5tYXAoZnVuY3Rpb24oYyl7cmV0dXJuIGkuc2NhbGUoYyl9KSxsPVBhdC5wYWlycyhzKTtuPXhsLk1hdGgubWluKGwsZnVuY3Rpb24oYyx1KXtyZXR1cm4gTWF0aC5hYnMoY1sxXS1jWzBdKX0scipMYXQuX1NJTkdMRV9CQVJfRElNRU5TSU9OX1JBVElPKSxuKj1MYXQuX0JBUl9USElDS05FU1NfUkFUSU99cmV0dXJuIG59fSk7dmFyIF9LdD1IKFJhdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoUmF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgZVVlPShkZSgpLFV0KHBlKSksclVlPWtzKCksblVlPUZlKCksZ0t0PV80KCksaVVlPXJzKCksb1VlPWZ1bmN0aW9uKGUpe2VVZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3I9PT12b2lkIDAmJihyPSJ2ZXJ0aWNhbCIpO3ZhciBuPWUuY2FsbCh0aGlzLHIpfHx0aGlzO3JldHVybiBuLl9jbHVzdGVyT2Zmc2V0cz1uZXcgblVlLk1hcCxufXJldHVybiB0LnByb3RvdHlwZS5fZ2VuZXJhdGVBdHRyVG9Qcm9qZWN0b3I9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLG49ZS5wcm90b3R5cGUuX2dlbmVyYXRlQXR0clRvUHJvamVjdG9yLmNhbGwodGhpcyksaT10aGlzLl9tYWtlSW5uZXJTY2FsZSgpLG89ZnVuY3Rpb24obCxjKXtyZXR1cm4gaS5yYW5nZUJhbmQoKX07bi53aWR0aD10aGlzLl9pc1ZlcnRpY2FsP286bi53aWR0aCxuLmhlaWdodD10aGlzLl9pc1ZlcnRpY2FsP24uaGVpZ2h0Om87dmFyIGE9bi54LHM9bi55O3JldHVybiBuLng9dGhpcy5faXNWZXJ0aWNhbD9mdW5jdGlvbihsLGMsdSl7cmV0dXJuIGEobCxjLHUpK3IuX2NsdXN0ZXJPZmZzZXRzLmdldCh1KX06ZnVuY3Rpb24obCxjLHUpe3JldHVybiBhKGwsYyx1KX0sbi55PXRoaXMuX2lzVmVydGljYWw/ZnVuY3Rpb24obCxjLHUpe3JldHVybiBzKGwsYyx1KX06ZnVuY3Rpb24obCxjLHUpe3JldHVybiBzKGwsYyx1KStyLl9jbHVzdGVyT2Zmc2V0cy5nZXQodSl9LG59LHQucHJvdG90eXBlLl91cGRhdGVDbHVzdGVyUG9zaXRpb249ZnVuY3Rpb24oKXt2YXIgcj10aGlzLG49dGhpcy5fbWFrZUlubmVyU2NhbGUoKTt0aGlzLmRhdGFzZXRzKCkuZm9yRWFjaChmdW5jdGlvbihpLG8pe3JldHVybiByLl9jbHVzdGVyT2Zmc2V0cy5zZXQoaSxuLnNjYWxlKFN0cmluZyhvKSktbi5yYW5nZUJhbmQoKS8yKX0pfSx0LnByb3RvdHlwZS5fbWFrZUlubmVyU2NhbGU9ZnVuY3Rpb24oKXt2YXIgcj1uZXcgclVlLkNhdGVnb3J5O3IuZG9tYWluKHRoaXMuZGF0YXNldHMoKS5tYXAoZnVuY3Rpb24oaSxvKXtyZXR1cm4gU3RyaW5nKG8pfSkpO3ZhciBuPWlVZS5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLmF0dHIoZ0t0LkJhci5fQkFSX1RISUNLTkVTU19LRVkpKTtyZXR1cm4gci5yYW5nZShbMCxuKG51bGwsMCxudWxsKV0pLHJ9LHQucHJvdG90eXBlLl9nZXREYXRhVG9EcmF3PWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3VwZGF0ZUNsdXN0ZXJQb3NpdGlvbigpLGUucHJvdG90eXBlLl9nZXREYXRhVG9EcmF3LmNhbGwodGhpcyl9LHR9KGdLdC5CYXIpO1JhdC5DbHVzdGVyZWRCYXI9b1VlfSk7dmFyIHlLdD1IKERhdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoRGF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgYVVlPShkZSgpLFV0KHBlKSkseTQ9KEVyKCksVXQoTXIpKSxOYXQ9X2woKSxzVWU9TGYoKSxsVWU9QnUoKSxjVWU9a3MoKSxWMT1GZSgpLHVVZT1wYXQoKSxoVWU9bWF0KCksZlVlPVV1KCkscFVlPUNGKCksTWQ9cnMoKSxkVWU9ZnVuY3Rpb24oZSl7YVVlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXt2YXIgcj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIHIuX3N0YXJ0QW5nbGU9MCxyLl9lbmRBbmdsZT0yKk1hdGguUEksci5fbGFiZWxGb3JtYXR0ZXI9bFVlLmlkZW50aXR5KCksci5fbGFiZWxzRW5hYmxlZD0hMSxyLmlubmVyUmFkaXVzKDApLHIub3V0ZXJSYWRpdXMoZnVuY3Rpb24oKXt2YXIgbj1yLl9waWVDZW50ZXIoKTtyZXR1cm4gTWF0aC5taW4oTWF0aC5tYXgoci53aWR0aCgpLW4ueCxuLngpLE1hdGgubWF4KHIuaGVpZ2h0KCktbi55LG4ueSkpfSksci5hZGRDbGFzcygicGllLXBsb3QiKSxyLmF0dHIoImZpbGwiLGZ1bmN0aW9uKG4saSl7cmV0dXJuIFN0cmluZyhpKX0sbmV3IGNVZS5Db2xvciksci5fc3Ryb2tlRHJhd2Vycz1uZXcgVjEuTWFwLHJ9cmV0dXJuIHQucHJvdG90eXBlLl9zZXR1cD1mdW5jdGlvbigpe3ZhciByPXRoaXM7ZS5wcm90b3R5cGUuX3NldHVwLmNhbGwodGhpcyksdGhpcy5fc3Ryb2tlRHJhd2Vycy5mb3JFYWNoKGZ1bmN0aW9uKG4pe3JldHVybiBuLmF0dGFjaFRvKHIuX3JlbmRlckFyZWEpfSl9LHQucHJvdG90eXBlLmNvbXB1dGVMYXlvdXQ9ZnVuY3Rpb24ocixuLGkpe2UucHJvdG90eXBlLmNvbXB1dGVMYXlvdXQuY2FsbCh0aGlzLHIsbixpKTt2YXIgbz10aGlzLl9waWVDZW50ZXIoKTt0aGlzLl9yZW5kZXJBcmVhLmF0dHIoInRyYW5zZm9ybSIsInRyYW5zbGF0ZSgiK28ueCsiLCIrby55KyIpIik7dmFyIGE9TWF0aC5taW4oTWF0aC5tYXgodGhpcy53aWR0aCgpLW8ueCxvLngpLE1hdGgubWF4KHRoaXMuaGVpZ2h0KCktby55LG8ueSkpO3JldHVybiB0aGlzLmlubmVyUmFkaXVzKCkuc2NhbGUhPW51bGwmJnRoaXMuaW5uZXJSYWRpdXMoKS5zY2FsZS5yYW5nZShbMCxhXSksdGhpcy5vdXRlclJhZGl1cygpLnNjYWxlIT1udWxsJiZ0aGlzLm91dGVyUmFkaXVzKCkuc2NhbGUucmFuZ2UoWzAsYV0pLHRoaXN9LHQucHJvdG90eXBlLmFkZERhdGFzZXQ9ZnVuY3Rpb24ocil7cmV0dXJuIGUucHJvdG90eXBlLmFkZERhdGFzZXQuY2FsbCh0aGlzLHIpLHRoaXN9LHQucHJvdG90eXBlLl9hZGREYXRhc2V0PWZ1bmN0aW9uKHIpe2lmKHRoaXMuZGF0YXNldHMoKS5sZW5ndGg9PT0xKXJldHVybiBWMS5XaW5kb3cud2FybigiT25seSBvbmUgZGF0YXNldCBpcyBzdXBwb3J0ZWQgaW4gUGllIHBsb3RzIiksdGhpczt0aGlzLl91cGRhdGVQaWVBbmdsZXMoKSxlLnByb3RvdHlwZS5fYWRkRGF0YXNldC5jYWxsKHRoaXMscik7dmFyIG49bmV3IGhVZS5BcmNPdXRsaW5lU1ZHRHJhd2VyO3JldHVybiB0aGlzLl9pc1NldHVwJiZuLmF0dGFjaFRvKHRoaXMuX3JlbmRlckFyZWEpLHRoaXMuX3N0cm9rZURyYXdlcnMuc2V0KHIsbiksdGhpc30sdC5wcm90b3R5cGUucmVtb3ZlRGF0YXNldD1mdW5jdGlvbihyKXtyZXR1cm4gZS5wcm90b3R5cGUucmVtb3ZlRGF0YXNldC5jYWxsKHRoaXMsciksdGhpc30sdC5wcm90b3R5cGUuX3JlbW92ZURhdGFzZXROb2Rlcz1mdW5jdGlvbihyKXtlLnByb3RvdHlwZS5fcmVtb3ZlRGF0YXNldE5vZGVzLmNhbGwodGhpcyxyKSx0aGlzLl9zdHJva2VEcmF3ZXJzLmdldChyKS5yZW1vdmUoKX0sdC5wcm90b3R5cGUuX3JlbW92ZURhdGFzZXQ9ZnVuY3Rpb24ocil7cmV0dXJuIGUucHJvdG90eXBlLl9yZW1vdmVEYXRhc2V0LmNhbGwodGhpcyxyKSx0aGlzLl9zdHJva2VEcmF3ZXJzLmRlbGV0ZShyKSx0aGlzLl9zdGFydEFuZ2xlcz1bXSx0aGlzLl9lbmRBbmdsZXM9W10sdGhpc30sdC5wcm90b3R5cGUuc2VsZWN0aW9ucz1mdW5jdGlvbihyKXt2YXIgbj10aGlzO3I9PT12b2lkIDAmJihyPXRoaXMuZGF0YXNldHMoKSk7dmFyIGk9ZS5wcm90b3R5cGUuc2VsZWN0aW9ucy5jYWxsKHRoaXMscikubm9kZXMoKTtyZXR1cm4gci5mb3JFYWNoKGZ1bmN0aW9uKG8pe3ZhciBhPW4uX3N0cm9rZURyYXdlcnMuZ2V0KG8pO2EhPW51bGwmJmkucHVzaC5hcHBseShpLGEuZ2V0VmlzdWFsUHJpbWl0aXZlcygpKX0pLHk0LnNlbGVjdEFsbChpKX0sdC5wcm90b3R5cGUuX29uRGF0YXNldFVwZGF0ZT1mdW5jdGlvbigpe2UucHJvdG90eXBlLl9vbkRhdGFzZXRVcGRhdGUuY2FsbCh0aGlzKSx0aGlzLl91cGRhdGVQaWVBbmdsZXMoKSx0aGlzLnJlbmRlcigpfSx0LnByb3RvdHlwZS5fY3JlYXRlRHJhd2VyPWZ1bmN0aW9uKCl7cmV0dXJuIG5ldyBmVWUuUHJveHlEcmF3ZXIoZnVuY3Rpb24oKXtyZXR1cm4gbmV3IHVVZS5BcmNTVkdEcmF3ZXJ9LGZ1bmN0aW9uKCl7cmV0dXJuIHBVZS53YXJuKCJjYW52YXMgcmVuZGVyZXIgaXMgbm90IHN1cHBvcnRlZCBvbiBQaWUgUGxvdCEiKSxudWxsfSl9LHQucHJvdG90eXBlLmVudGl0aWVzPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXM7cj09PXZvaWQgMCYmKHI9dGhpcy5kYXRhc2V0cygpKTt2YXIgaT1lLnByb3RvdHlwZS5lbnRpdGllcy5jYWxsKHRoaXMscik7cmV0dXJuIGkubWFwKGZ1bmN0aW9uKG8pe28ucG9zaXRpb24ueCs9bi53aWR0aCgpLzIsby5wb3NpdGlvbi55Kz1uLmhlaWdodCgpLzI7dmFyIGE9eTQuc2VsZWN0KG4uX3N0cm9rZURyYXdlcnMuZ2V0KG8uZGF0YXNldCkuZ2V0VmlzdWFsUHJpbWl0aXZlQXRJbmRleChvLmluZGV4KSkscz1vO3JldHVybiBzLnN0cm9rZVNlbGVjdGlvbj1hLHN9KX0sdC5wcm90b3R5cGUuc2VjdG9yVmFsdWU9ZnVuY3Rpb24ocixuKXtyZXR1cm4gcj09bnVsbD90aGlzLl9wcm9wZXJ0eUJpbmRpbmdzLmdldCh0Ll9TRUNUT1JfVkFMVUVfS0VZKToodGhpcy5fYmluZFByb3BlcnR5KHQuX1NFQ1RPUl9WQUxVRV9LRVkscixuKSx0aGlzLl91cGRhdGVQaWVBbmdsZXMoKSx0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5pbm5lclJhZGl1cz1mdW5jdGlvbihyLG4pe3JldHVybiByPT1udWxsP3RoaXMuX3Byb3BlcnR5QmluZGluZ3MuZ2V0KHQuX0lOTkVSX1JBRElVU19LRVkpOih0aGlzLl9iaW5kUHJvcGVydHkodC5fSU5ORVJfUkFESVVTX0tFWSxyLG4pLHRoaXMucmVuZGVyKCksdGhpcyl9LHQucHJvdG90eXBlLm91dGVyUmFkaXVzPWZ1bmN0aW9uKHIsbil7cmV0dXJuIHI9PW51bGw/dGhpcy5fcHJvcGVydHlCaW5kaW5ncy5nZXQodC5fT1VURVJfUkFESVVTX0tFWSk6KHRoaXMuX2JpbmRQcm9wZXJ0eSh0Ll9PVVRFUl9SQURJVVNfS0VZLHIsbiksdGhpcy5yZW5kZXIoKSx0aGlzKX0sdC5wcm90b3R5cGUuc3RhcnRBbmdsZT1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9zdGFydEFuZ2xlOih0aGlzLl9zdGFydEFuZ2xlPXIsdGhpcy5fdXBkYXRlUGllQW5nbGVzKCksdGhpcy5yZW5kZXIoKSx0aGlzKX0sdC5wcm90b3R5cGUuZW5kQW5nbGU9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fZW5kQW5nbGU6KHRoaXMuX2VuZEFuZ2xlPXIsdGhpcy5fdXBkYXRlUGllQW5nbGVzKCksdGhpcy5yZW5kZXIoKSx0aGlzKX0sdC5wcm90b3R5cGUubGFiZWxzRW5hYmxlZD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9sYWJlbHNFbmFibGVkOih0aGlzLl9sYWJlbHNFbmFibGVkPXIsdGhpcy5yZW5kZXIoKSx0aGlzKX0sdC5wcm90b3R5cGUubGFiZWxGb3JtYXR0ZXI9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fbGFiZWxGb3JtYXR0ZXI6KHRoaXMuX2xhYmVsRm9ybWF0dGVyPXIsdGhpcy5yZW5kZXIoKSx0aGlzKX0sdC5wcm90b3R5cGUuZW50aXRpZXNBdD1mdW5jdGlvbihyKXt2YXIgbj17eDp0aGlzLndpZHRoKCkvMix5OnRoaXMuaGVpZ2h0KCkvMn0saT17eDpyLngtbi54LHk6ci55LW4ueX0sbz10aGlzLl9zbGljZUluZGV4Rm9yUG9pbnQoaSk7cmV0dXJuIG89PW51bGw/W106W3RoaXMuZW50aXRpZXMoKVtvXV19LHQucHJvdG90eXBlLl9wcm9wZXJ0eVByb2plY3RvcnM9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLG49ZS5wcm90b3R5cGUuX3Byb3BlcnR5UHJvamVjdG9ycy5jYWxsKHRoaXMpLGk9TWQuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy5pbm5lclJhZGl1cygpKSxvPU1kLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMub3V0ZXJSYWRpdXMoKSk7cmV0dXJuIG4uZD1mdW5jdGlvbihhLHMsbCl7cmV0dXJuIHk0LmFyYygpLmlubmVyUmFkaXVzKGkoYSxzLGwpKS5vdXRlclJhZGl1cyhvKGEscyxsKSkuc3RhcnRBbmdsZShyLl9zdGFydEFuZ2xlc1tzXSkuZW5kQW5nbGUoci5fZW5kQW5nbGVzW3NdKShhLHMpfSxufSx0LnByb3RvdHlwZS5fdXBkYXRlUGllQW5nbGVzPWZ1bmN0aW9uKCl7aWYodGhpcy5zZWN0b3JWYWx1ZSgpIT1udWxsJiZ0aGlzLmRhdGFzZXRzKCkubGVuZ3RoIT09MCl7dmFyIHI9TWQuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy5zZWN0b3JWYWx1ZSgpKSxuPXRoaXMuZGF0YXNldHMoKVswXSxpPXRoaXMuX2dldERhdGFUb0RyYXcoKS5nZXQobiksbz15NC5waWUoKS5zb3J0KG51bGwpLnN0YXJ0QW5nbGUodGhpcy5fc3RhcnRBbmdsZSkuZW5kQW5nbGUodGhpcy5fZW5kQW5nbGUpLnZhbHVlKGZ1bmN0aW9uKGEscyl7cmV0dXJuIHIoYSxzLG4pfSkoaSk7dGhpcy5fc3RhcnRBbmdsZXM9by5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGEuc3RhcnRBbmdsZX0pLHRoaXMuX2VuZEFuZ2xlcz1vLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYS5lbmRBbmdsZX0pfX0sdC5wcm90b3R5cGUuX3BpZUNlbnRlcj1mdW5jdGlvbigpe3ZhciByPXRoaXMuX3N0YXJ0QW5nbGU8dGhpcy5fZW5kQW5nbGU/dGhpcy5fc3RhcnRBbmdsZTp0aGlzLl9lbmRBbmdsZSxuPXRoaXMuX3N0YXJ0QW5nbGU8dGhpcy5fZW5kQW5nbGU/dGhpcy5fZW5kQW5nbGU6dGhpcy5fc3RhcnRBbmdsZSxpPU1hdGguc2luKHIpLG89TWF0aC5jb3MociksYT1NYXRoLnNpbihuKSxzPU1hdGguY29zKG4pLGwsYyx1LGg7cmV0dXJuIGk+PTAmJmE+PTA/bz49MCYmcz49MD8obD1vLGM9MCxoPTAsdT1hKTpvPDAmJnM8MD8obD0wLGM9LXMsaD0wLHU9aSk6bz49MCYmczwwPyhsPW8sYz0tcyxoPTAsdT1pKTpvPDAmJnM+PTAmJihsPTEsYz0xLGg9MSx1PU1hdGgubWF4KGksYSkpOmk+PTAmJmE8MD9vPj0wJiZzPj0wPyhsPU1hdGgubWF4KG8scyksYz0xLGg9MSx1PTEpOm88MCYmczwwPyhsPTAsYz0xLGg9LWEsdT1pKTpvPj0wJiZzPDA/KGw9byxjPTEsaD0tYSx1PTEpOm88MCYmcz49MCYmKGw9cyxjPTEsaD0xLHU9aSk6aTwwJiZhPj0wP28+PTAmJnM+PTA/KGw9MSxjPTAsaD0taSx1PWEpOm88MCYmczwwPyhsPTEsYz1NYXRoLm1heCgtbywtcyksaD0xLHU9MSk6bz49MCYmczwwPyhsPTEsYz0tcyxoPS1pLHU9MSk6bzwwJiZzPj0wJiYobD0xLGM9LW8saD0xLHU9YSk6aTwwJiZhPDAmJihvPj0wJiZzPj0wPyhsPXMsYz0wLGg9LWksdT0wKTpvPDAmJnM8MD8obD0wLGM9LW8saD0tYSx1PTApOm8+PTAmJnM8MD8obD0xLGM9MSxoPU1hdGgubWF4KG8sLXMpLHU9MSk6bzwwJiZzPj0wJiYobD1zLGM9LW8saD0xLHU9MCkpLHt4OmgrdT09MD8wOmgvKGgrdSkqdGhpcy53aWR0aCgpLHk6bCtjPT0wPzA6bC8obCtjKSp0aGlzLmhlaWdodCgpfX0sdC5wcm90b3R5cGUuX2dldERhdGFUb0RyYXc9ZnVuY3Rpb24oKXt2YXIgcj1lLnByb3RvdHlwZS5fZ2V0RGF0YVRvRHJhdy5jYWxsKHRoaXMpO2lmKHRoaXMuZGF0YXNldHMoKS5sZW5ndGg9PT0wKXJldHVybiByO3ZhciBuPU1kLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMuc2VjdG9yVmFsdWUoKSksaT10aGlzLmRhdGFzZXRzKClbMF0sbz1yLmdldChpKSxhPW8uZmlsdGVyKGZ1bmN0aW9uKHMsbCl7cmV0dXJuIHQuX2lzVmFsaWREYXRhKG4ocyxsLGkpKX0pO3JldHVybiByLnNldChpLGEpLHJ9LHQuX2lzVmFsaWREYXRhPWZ1bmN0aW9uKHIpe3JldHVybiBWMS5NYXRoLmlzVmFsaWROdW1iZXIocikmJnI+PTB9LHQucHJvdG90eXBlLl9waXhlbFBvaW50PWZ1bmN0aW9uKHIsbixpKXt2YXIgbz1NZC5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnNlY3RvclZhbHVlKCkpO2lmKCF0Ll9pc1ZhbGlkRGF0YShvKHIsbixpKSkpcmV0dXJue3g6TmFOLHk6TmFOfTt2YXIgYT1NZC5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLmlubmVyUmFkaXVzKCkpKHIsbixpKSxzPU1kLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMub3V0ZXJSYWRpdXMoKSkocixuLGkpLGw9KGErcykvMixjPXk0LnBpZSgpLnNvcnQobnVsbCkudmFsdWUoZnVuY3Rpb24ocCxkKXt2YXIgZz1vKHAsZCxpKTtyZXR1cm4gdC5faXNWYWxpZERhdGEoZyk/ZzowfSkuc3RhcnRBbmdsZSh0aGlzLl9zdGFydEFuZ2xlKS5lbmRBbmdsZSh0aGlzLl9lbmRBbmdsZSkoaS5kYXRhKCkpLHU9Y1tuXS5zdGFydEFuZ2xlLGg9Y1tuXS5lbmRBbmdsZSxmPSh1K2gpLzI7cmV0dXJue3g6bCpNYXRoLnNpbihmKSx5Oi1sKk1hdGguY29zKGYpfX0sdC5wcm90b3R5cGUuX2FkZGl0aW9uYWxQYWludD1mdW5jdGlvbihyKXt2YXIgbj10aGlzO3RoaXMuX3JlbmRlckFyZWEuc2VsZWN0KCIubGFiZWwtYXJlYSIpLnJlbW92ZSgpLHRoaXMuX2xhYmVsc0VuYWJsZWQmJlYxLldpbmRvdy5zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7cmV0dXJuIG4uX2RyYXdMYWJlbHMoKX0scik7dmFyIGk9dGhpcy5fZ2VuZXJhdGVTdHJva2VEcmF3U3RlcHMoKSxvPXRoaXMuX2dldERhdGFUb0RyYXcoKTt0aGlzLmRhdGFzZXRzKCkuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgcz1NZC5QbG90LmFwcGx5RHJhd1N0ZXBzKGksYSk7bi5fc3Ryb2tlRHJhd2Vycy5nZXQoYSkuZHJhdyhvLmdldChhKSxzKX0pfSx0LnByb3RvdHlwZS5fZ2VuZXJhdGVTdHJva2VEcmF3U3RlcHM9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKTtyZXR1cm5be2F0dHJUb1Byb2plY3RvcjpyLGFuaW1hdG9yOm5ldyBzVWUuTnVsbH1dfSx0LnByb3RvdHlwZS5fc2xpY2VJbmRleEZvclBvaW50PWZ1bmN0aW9uKHIpe3ZhciBuPU1hdGguc3FydChNYXRoLnBvdyhyLngsMikrTWF0aC5wb3coci55LDIpKSxpPU1hdGguYWNvcygtci55L24pO3IueDwwJiYoaT1NYXRoLlBJKjItaSk7Zm9yKHZhciBvLGE9MDthPHRoaXMuX3N0YXJ0QW5nbGVzLmxlbmd0aDthKyspaWYodGhpcy5fc3RhcnRBbmdsZXNbYV08aSYmdGhpcy5fZW5kQW5nbGVzW2FdPmkpe289YTticmVha31pZihvIT09dm9pZCAwKXt2YXIgcz10aGlzLmRhdGFzZXRzKClbMF0sbD1zLmRhdGEoKVtvXSxjPXRoaXMuaW5uZXJSYWRpdXMoKS5hY2Nlc3NvcihsLG8scyksdT10aGlzLm91dGVyUmFkaXVzKCkuYWNjZXNzb3IobCxvLHMpO2lmKG4+YyYmbjx1KXJldHVybiBvfXJldHVybiBudWxsfSx0LnByb3RvdHlwZS5fZHJhd0xhYmVscz1mdW5jdGlvbigpe2Zvcih2YXIgcj10aGlzLG49dGhpcy5fZ2V0QXR0clRvUHJvamVjdG9yKCksaT10aGlzLl9yZW5kZXJBcmVhLmFwcGVuZCgiZyIpLmNsYXNzZWQoImxhYmVsLWFyZWEiLCEwKSxvPW5ldyBOYXQuU3ZnQ29udGV4dChpLm5vZGUoKSksYT1uZXcgTmF0LkNhY2hlTWVhc3VyZXIobykscz1uZXcgTmF0LldyaXRlcihhLG8pLGw9dGhpcy5kYXRhc2V0cygpWzBdLGM9dGhpcy5fZ2V0RGF0YVRvRHJhdygpLmdldChsKSx1PWMubGVuZ3RoLGg9ZnVuY3Rpb24oZCl7dmFyIGc9Y1tkXSxfPWYuc2VjdG9yVmFsdWUoKS5hY2Nlc3NvcihnLGQsbCk7aWYoIVYxLk1hdGguaXNWYWxpZE51bWJlcihfKSlyZXR1cm4iY29udGludWUiO189Zi5fbGFiZWxGb3JtYXR0ZXIoXyxnLGQsbCk7dmFyIHk9YS5tZWFzdXJlKF8pLHg9KGYuX2VuZEFuZ2xlc1tkXStmLl9zdGFydEFuZ2xlc1tkXSkvMixiPWYub3V0ZXJSYWRpdXMoKS5hY2Nlc3NvcihnLGQsbCk7Zi5vdXRlclJhZGl1cygpLnNjYWxlJiYoYj1mLm91dGVyUmFkaXVzKCkuc2NhbGUuc2NhbGUoYikpO3ZhciBTPWYuaW5uZXJSYWRpdXMoKS5hY2Nlc3NvcihnLGQsbCk7Zi5pbm5lclJhZGl1cygpLnNjYWxlJiYoUz1mLmlubmVyUmFkaXVzKCkuc2NhbGUuc2NhbGUoUykpO3ZhciBDPShiK1MpLzIsUD1NYXRoLnNpbih4KSpDLXkud2lkdGgvMixrPS1NYXRoLmNvcyh4KSpDLXkuaGVpZ2h0LzIsTz1be3g6UCx5Omt9LHt4OlAseTprK3kuaGVpZ2h0fSx7eDpQK3kud2lkdGgseTprfSx7eDpQK3kud2lkdGgseTprK3kuaGVpZ2h0fV0sRD1PLmV2ZXJ5KGZ1bmN0aW9uKHope3JldHVybiBNYXRoLmFicyh6LngpPD1yLndpZHRoKCkvMiYmTWF0aC5hYnMoei55KTw9ci5oZWlnaHQoKS8yfSk7aWYoRCl7dmFyIEI9Ty5tYXAoZnVuY3Rpb24oeil7cmV0dXJuIHIuX3NsaWNlSW5kZXhGb3JQb2ludCh6KX0pO0Q9Qi5ldmVyeShmdW5jdGlvbih6KXtyZXR1cm4gej09PWR9KX12YXIgST1uLmZpbGwoZyxkLGwpLEw9VjEuQ29sb3IuY29udHJhc3QoIndoaXRlIixJKSoxLjY8VjEuQ29sb3IuY29udHJhc3QoImJsYWNrIixJKSxSPWkuYXBwZW5kKCJnIikuYXR0cigidHJhbnNmb3JtIiwidHJhbnNsYXRlKCIrUCsiLCIraysiKSIpLEY9TD8iZGFyay1sYWJlbCI6ImxpZ2h0LWxhYmVsIjtSLmNsYXNzZWQoRiwhMCksUi5zdHlsZSgidmlzaWJpbGl0eSIsRD8iaW5oZXJpdCI6ImhpZGRlbiIpLHMud3JpdGUoXyx5LndpZHRoLHkuaGVpZ2h0LHt4QWxpZ246ImNlbnRlciIseUFsaWduOiJjZW50ZXIifSxSLm5vZGUoKSl9LGY9dGhpcyxwPTA7cDx1O3ArKyloKHApfSx0Ll9JTk5FUl9SQURJVVNfS0VZPSJpbm5lci1yYWRpdXMiLHQuX09VVEVSX1JBRElVU19LRVk9Im91dGVyLXJhZGl1cyIsdC5fU0VDVE9SX1ZBTFVFX0tFWT0ic2VjdG9yLXZhbHVlIix0fShNZC5QbG90KTtEYXQuUGllPWRVZX0pO3ZhciB2S3Q9SCh6YXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHphdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIG1VZT0oZGUoKSxVdChwZSkpLGdVZT0oRXIoKSxVdChNcikpLE9hdD1fbCgpLF9VZT1MZigpLHlVZT1CMSgpLHZVZT1VdSgpLHhVZT1pQigpLGhCPWtzKCksT2M9RmUoKSxmQj1ycygpLGJVZT1IMSgpLHdVZT1mdW5jdGlvbihlKXttVWUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3ZhciByPWUuY2FsbCh0aGlzKXx8dGhpcztyZXR1cm4gci5fbGFiZWxzRW5hYmxlZD0hMSxyLl9sYWJlbD1udWxsLHIuYW5pbWF0b3IoInJlY3RhbmdsZXMiLG5ldyBfVWUuTnVsbCksci5hZGRDbGFzcygicmVjdGFuZ2xlLXBsb3QiKSxyLmF0dHIoImZpbGwiLG5ldyBoQi5Db2xvcigpLnJhbmdlKClbMF0pLHJ9cmV0dXJuIHQucHJvdG90eXBlLl9jcmVhdGVEcmF3ZXI9ZnVuY3Rpb24oKXtyZXR1cm4gbmV3IHZVZS5Qcm94eURyYXdlcihmdW5jdGlvbigpe3JldHVybiBuZXcgeFVlLlJlY3RhbmdsZVNWR0RyYXdlcn0sZnVuY3Rpb24ocil7cmV0dXJuIG5ldyB5VWUuUmVjdGFuZ2xlQ2FudmFzRHJhd2VyKHIpfSl9LHQucHJvdG90eXBlLl9nZW5lcmF0ZUF0dHJUb1Byb2plY3Rvcj1mdW5jdGlvbigpe3ZhciByPXRoaXMsbj1lLnByb3RvdHlwZS5fZ2VuZXJhdGVBdHRyVG9Qcm9qZWN0b3IuY2FsbCh0aGlzKSxpPWZCLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMueCgpKSxvPW5bdC5fWDJfS0VZXSxhPWZCLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMueSgpKSxzPW5bdC5fWTJfS0VZXSxsPXRoaXMueCgpLnNjYWxlLGM9dGhpcy55KCkuc2NhbGU7cmV0dXJuIG8hPW51bGw/KG4ud2lkdGg9ZnVuY3Rpb24odSxoLGYpe3JldHVybiBNYXRoLmFicyhvKHUsaCxmKS1pKHUsaCxmKSl9LG4ueD1mdW5jdGlvbih1LGgsZil7cmV0dXJuIE1hdGgubWluKG8odSxoLGYpLGkodSxoLGYpKX0pOihuLndpZHRoPWZ1bmN0aW9uKHUsaCxmKXtyZXR1cm4gci5fcmVjdGFuZ2xlV2lkdGgobCl9LG4ueD1mdW5jdGlvbih1LGgsZil7cmV0dXJuIGkodSxoLGYpLS41Km4ud2lkdGgodSxoLGYpfSkscyE9bnVsbD8obi5oZWlnaHQ9ZnVuY3Rpb24odSxoLGYpe3JldHVybiBNYXRoLmFicyhzKHUsaCxmKS1hKHUsaCxmKSl9LG4ueT1mdW5jdGlvbih1LGgsZil7cmV0dXJuIE1hdGgubWF4KHModSxoLGYpLGEodSxoLGYpKS1uLmhlaWdodCh1LGgsZil9KToobi5oZWlnaHQ9ZnVuY3Rpb24odSxoLGYpe3JldHVybiByLl9yZWN0YW5nbGVXaWR0aChjKX0sbi55PWZ1bmN0aW9uKHUsaCxmKXtyZXR1cm4gYSh1LGgsZiktLjUqbi5oZWlnaHQodSxoLGYpfSksZGVsZXRlIG5bdC5fWDJfS0VZXSxkZWxldGUgblt0Ll9ZMl9LRVldLG59LHQucHJvdG90eXBlLl9nZW5lcmF0ZURyYXdTdGVwcz1mdW5jdGlvbigpe3JldHVyblt7YXR0clRvUHJvamVjdG9yOnRoaXMuX2dldEF0dHJUb1Byb2plY3RvcigpLGFuaW1hdG9yOnRoaXMuX2dldEFuaW1hdG9yKCJyZWN0YW5nbGVzIil9XX0sdC5wcm90b3R5cGUuX2ZpbHRlckZvclByb3BlcnR5PWZ1bmN0aW9uKHIpe3JldHVybiByPT09IngyIj9lLnByb3RvdHlwZS5fZmlsdGVyRm9yUHJvcGVydHkuY2FsbCh0aGlzLCJ4Iik6cj09PSJ5MiI/ZS5wcm90b3R5cGUuX2ZpbHRlckZvclByb3BlcnR5LmNhbGwodGhpcywieSIpOmUucHJvdG90eXBlLl9maWx0ZXJGb3JQcm9wZXJ0eS5jYWxsKHRoaXMscil9LHQucHJvdG90eXBlLng9ZnVuY3Rpb24ocixuLGkpe2lmKHI9PW51bGwpcmV0dXJuIGUucHJvdG90eXBlLnguY2FsbCh0aGlzKTtpZihuPT1udWxsP2UucHJvdG90eXBlLnguY2FsbCh0aGlzLHIpOmUucHJvdG90eXBlLnguY2FsbCh0aGlzLHIsbixpKSxuIT1udWxsKXt2YXIgbz10aGlzLngyKCksYT1vJiZvLmFjY2Vzc29yO2EhPW51bGwmJnRoaXMuX2JpbmRQcm9wZXJ0eSh0Ll9YMl9LRVksYSxuLG8ucG9zdFNjYWxlKX1yZXR1cm4gbiBpbnN0YW5jZW9mIGhCLkNhdGVnb3J5JiZuLmlubmVyUGFkZGluZygwKS5vdXRlclBhZGRpbmcoMCksdGhpc30sdC5wcm90b3R5cGUueDI9ZnVuY3Rpb24ocixuKXtpZihyPT1udWxsKXJldHVybiB0aGlzLl9wcm9wZXJ0eUJpbmRpbmdzLmdldCh0Ll9YMl9LRVkpO3ZhciBpPXRoaXMueCgpLG89aSYmaS5zY2FsZTtyZXR1cm4gdGhpcy5fYmluZFByb3BlcnR5KHQuX1gyX0tFWSxyLG8sbiksdGhpcy5yZW5kZXIoKSx0aGlzfSx0LnByb3RvdHlwZS55PWZ1bmN0aW9uKHIsbixpKXtpZihyPT1udWxsKXJldHVybiBlLnByb3RvdHlwZS55LmNhbGwodGhpcyk7aWYobj09bnVsbD9lLnByb3RvdHlwZS55LmNhbGwodGhpcyxyKTplLnByb3RvdHlwZS55LmNhbGwodGhpcyxyLG4saSksbiE9bnVsbCl7dmFyIG89dGhpcy55MigpLGE9byYmby5hY2Nlc3NvcjthIT1udWxsJiZ0aGlzLl9iaW5kUHJvcGVydHkodC5fWTJfS0VZLGEsbixvLnBvc3RTY2FsZSl9cmV0dXJuIG4gaW5zdGFuY2VvZiBoQi5DYXRlZ29yeSYmbi5pbm5lclBhZGRpbmcoMCkub3V0ZXJQYWRkaW5nKDApLHRoaXN9LHQucHJvdG90eXBlLnkyPWZ1bmN0aW9uKHIsbil7aWYocj09bnVsbClyZXR1cm4gdGhpcy5fcHJvcGVydHlCaW5kaW5ncy5nZXQodC5fWTJfS0VZKTt2YXIgaT10aGlzLnkoKSxvPWkmJmkuc2NhbGU7cmV0dXJuIHRoaXMuX2JpbmRQcm9wZXJ0eSh0Ll9ZMl9LRVkscixvLG4pLHRoaXMucmVuZGVyKCksdGhpc30sdC5wcm90b3R5cGUuZW50aXRpZXNBdD1mdW5jdGlvbihyKXt2YXIgbj10aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKTtyZXR1cm4gdGhpcy5lbnRpdGllcygpLmZpbHRlcihmdW5jdGlvbihpKXt2YXIgbz1pLmRhdHVtLGE9aS5pbmRleCxzPWkuZGF0YXNldCxsPW4ueChvLGEscyksYz1uLnkobyxhLHMpLHU9bi53aWR0aChvLGEscyksaD1uLmhlaWdodChvLGEscyk7cmV0dXJuIGw8PXIueCYmci54PD1sK3UmJmM8PXIueSYmci55PD1jK2h9KX0sdC5wcm90b3R5cGUuX2VudGl0eUJvdW5kcz1mdW5jdGlvbihyKXt2YXIgbj1yLmRhdHVtLGk9ci5pbmRleCxvPXIuZGF0YXNldDtyZXR1cm4gdGhpcy5fZW50aXR5QkJveChuLGksbyx0aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKSl9LHQucHJvdG90eXBlLl9lbnRpdHlCQm94PWZ1bmN0aW9uKHIsbixpLG8pe3JldHVybnt4Om8ueChyLG4saSkseTpvLnkocixuLGkpLHdpZHRoOm8ud2lkdGgocixuLGkpLGhlaWdodDpvLmhlaWdodChyLG4saSl9fSx0LnByb3RvdHlwZS5sYWJlbD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9sYWJlbDoodGhpcy5fbGFiZWw9cix0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5sYWJlbHNFbmFibGVkPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2xhYmVsc0VuYWJsZWQ6KHRoaXMuX2xhYmVsc0VuYWJsZWQ9cix0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5fcHJvcGVydHlQcm9qZWN0b3JzPWZ1bmN0aW9uKCl7dmFyIHI9ZS5wcm90b3R5cGUuX3Byb3BlcnR5UHJvamVjdG9ycy5jYWxsKHRoaXMpO3JldHVybiB0aGlzLngyKCkhPW51bGwmJihyLngyPWZCLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMueDIoKSkpLHRoaXMueTIoKSE9bnVsbCYmKHIueTI9ZkIuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55MigpKSkscn0sdC5wcm90b3R5cGUuX3BpeGVsUG9pbnQ9ZnVuY3Rpb24ocixuLGkpe3ZhciBvPXRoaXMuX2dldEF0dHJUb1Byb2plY3RvcigpLGE9by54KHIsbixpKSxzPW8ueShyLG4saSksbD1vLndpZHRoKHIsbixpKSxjPW8uaGVpZ2h0KHIsbixpKSx1PWErbC8yLGg9cytjLzI7cmV0dXJue3g6dSx5Omh9fSx0LnByb3RvdHlwZS5fcmVjdGFuZ2xlV2lkdGg9ZnVuY3Rpb24ocil7aWYociBpbnN0YW5jZW9mIGhCLkNhdGVnb3J5KXJldHVybiByLnJhbmdlQmFuZCgpO3ZhciBuPXI9PT10aGlzLngoKS5zY2FsZT90aGlzLngoKS5hY2Nlc3Nvcjp0aGlzLnkoKS5hY2Nlc3NvcixpPWdVZS5zZXQoT2MuQXJyYXkuZmxhdHRlbih0aGlzLmRhdGFzZXRzKCkubWFwKGZ1bmN0aW9uKGMpe3JldHVybiBjLmRhdGEoKS5tYXAoZnVuY3Rpb24odSxoKXtyZXR1cm4gbih1LGgsYykudmFsdWVPZigpfSl9KSkpLnZhbHVlcygpLm1hcChmdW5jdGlvbihjKXtyZXR1cm4rY30pLG89T2MuTWF0aC5taW4oaSwwKSxhPU9jLk1hdGgubWF4KGksMCkscz1yLnNjYWxlKG8pLGw9ci5zY2FsZShhKTtyZXR1cm4obC1zKS9NYXRoLmFicyhhLW8pfSx0LnByb3RvdHlwZS5fZ2V0RGF0YVRvRHJhdz1mdW5jdGlvbigpe3ZhciByPW5ldyBPYy5NYXAsbj10aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKTtyZXR1cm4gdGhpcy5kYXRhc2V0cygpLmZvckVhY2goZnVuY3Rpb24oaSl7dmFyIG89aS5kYXRhKCkubWFwKGZ1bmN0aW9uKGEscyl7dmFyIGw9T2MuTWF0aC5pc1ZhbGlkTnVtYmVyKG4ueChhLHMsaSkpJiZPYy5NYXRoLmlzVmFsaWROdW1iZXIobi55KGEscyxpKSkmJk9jLk1hdGguaXNWYWxpZE51bWJlcihuLndpZHRoKGEscyxpKSkmJk9jLk1hdGguaXNWYWxpZE51bWJlcihuLmhlaWdodChhLHMsaSkpO3JldHVybiBsP2E6bnVsbH0pO3Iuc2V0KGksbyl9KSxyfSx0LnByb3RvdHlwZS5fYWRkaXRpb25hbFBhaW50PWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXM7dGhpcy5fcmVuZGVyQXJlYS5zZWxlY3RBbGwoIi5sYWJlbC1hcmVhIikucmVtb3ZlKCksdGhpcy5fbGFiZWxzRW5hYmxlZCYmdGhpcy5sYWJlbCgpIT1udWxsJiZPYy5XaW5kb3cuc2V0VGltZW91dChmdW5jdGlvbigpe3JldHVybiBuLl9kcmF3TGFiZWxzKCl9LHIpfSx0LnByb3RvdHlwZS5fZHJhd0xhYmVscz1mdW5jdGlvbigpe3ZhciByPXRoaXMsbj10aGlzLl9nZXREYXRhVG9EcmF3KCk7dGhpcy5kYXRhc2V0cygpLmZvckVhY2goZnVuY3Rpb24oaSxvKXtyZXR1cm4gci5fZHJhd0xhYmVsKG4saSxvKX0pfSx0LnByb3RvdHlwZS5fZHJhd0xhYmVsPWZ1bmN0aW9uKHIsbixpKXtmb3IodmFyIG89dGhpcy5fZ2V0QXR0clRvUHJvamVjdG9yKCksYT10aGlzLl9yZW5kZXJBcmVhLmFwcGVuZCgiZyIpLmNsYXNzZWQoImxhYmVsLWFyZWEiLCEwKSxzPW5ldyBPYXQuU3ZnQ29udGV4dChhLm5vZGUoKSksbD1uZXcgT2F0LkNhY2hlTWVhc3VyZXIocyksYz1uZXcgT2F0LldyaXRlcihsLHMpLHU9dGhpcy54KCkuc2NhbGUucmFuZ2UoKSxoPXRoaXMueSgpLnNjYWxlLnJhbmdlKCksZj1NYXRoLm1pbi5hcHBseShudWxsLHUpLHA9TWF0aC5tYXguYXBwbHkobnVsbCx1KSxkPU1hdGgubWluLmFwcGx5KG51bGwsaCksZz1NYXRoLm1heC5hcHBseShudWxsLGgpLF89ci5nZXQobikseT1fLmxlbmd0aCx4PTA7eDx5O3grKyl7dmFyIGI9X1t4XTtpZihiIT1udWxsKXt2YXIgUz0iIit0aGlzLmxhYmVsKCkoYix4LG4pLEM9bC5tZWFzdXJlKFMpLFA9by54KGIseCxuKSxrPW8ueShiLHgsbiksTz1vLndpZHRoKGIseCxuKSxEPW8uaGVpZ2h0KGIseCxuKTtpZihDLmhlaWdodDw9RCYmQy53aWR0aDw9Tyl7dmFyIEI9KE8tQy53aWR0aCkvMixJPShELUMuaGVpZ2h0KS8yO1ArPUIsays9STt2YXIgTD17bWluOlAsbWF4OlArQy53aWR0aH0sUj17bWluOmssbWF4OmsrQy5oZWlnaHR9O2lmKEwubWluPGZ8fEwubWF4PnB8fFIubWluPGR8fFIubWF4Pmd8fHRoaXMuX292ZXJsYXlMYWJlbChMLFIseCxpLHIpKWNvbnRpbnVlO3ZhciBGPW8uZmlsbChiLHgsbiksej1PYy5Db2xvci5jb250cmFzdCgid2hpdGUiLEYpKjEuNjxPYy5Db2xvci5jb250cmFzdCgiYmxhY2siLEYpLFU9YS5hcHBlbmQoImciKS5hdHRyKCJ0cmFuc2Zvcm0iLCJ0cmFuc2xhdGUoIitQKyIsIitrKyIpIiksVz16PyJkYXJrLWxhYmVsIjoibGlnaHQtbGFiZWwiO1UuY2xhc3NlZChXLCEwKSxjLndyaXRlKFMsQy53aWR0aCxDLmhlaWdodCx7eEFsaWduOiJjZW50ZXIiLHlBbGlnbjoiY2VudGVyIn0sVS5ub2RlKCkpfX19fSx0LnByb3RvdHlwZS5fb3ZlcmxheUxhYmVsPWZ1bmN0aW9uKHIsbixpLG8sYSl7Zm9yKHZhciBzPXRoaXMuX2dldEF0dHJUb1Byb2plY3RvcigpLGw9dGhpcy5kYXRhc2V0cygpLGM9bztjPGwubGVuZ3RoO2MrKylmb3IodmFyIHU9bFtjXSxoPWEuZ2V0KHUpLGY9aC5sZW5ndGgscD1jPT09bz9pKzE6MDtwPGY7cCsrKWlmKE9jLkRPTS5pbnRlcnNlY3RzQkJveChyLG4sdGhpcy5fZW50aXR5QkJveChoW3BdLHAsdSxzKSkpcmV0dXJuITA7cmV0dXJuITF9LHQuX1gyX0tFWT0ieDIiLHQuX1kyX0tFWT0ieTIiLHR9KGJVZS5YWVBsb3QpO3phdC5SZWN0YW5nbGU9d1VlfSk7dmFyIGJLdD1IKFZhdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoVmF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgU1VlPShkZSgpLFV0KHBlKSksRmF0PV9sKCksTVVlPUJ1KCksRVVlPWpGKCksVFVlPVV1KCkseEt0PXhhdCgpLENVZT1MZigpLEFVZT1CMSgpLFBVZT1rcygpLEJhdD1GZSgpLEhhdD1JUygpLGJsPXJzKCksSVVlPUgxKCksTFVlPWZ1bmN0aW9uKGUpe1NVZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7dmFyIHI9ZS5jYWxsKHRoaXMpfHx0aGlzO3IuX2xhYmVsRm9ybWF0dGVyPU1VZS5pZGVudGl0eSgpLHIuX2xhYmVsc0VuYWJsZWQ9ITEsci5hZGRDbGFzcygic2NhdHRlci1wbG90Iik7dmFyIG49bmV3IENVZS5FYXNpbmc7bi5zdGFydERlbGF5KDUpLG4uc3RlcER1cmF0aW9uKDI1MCksbi5tYXhUb3RhbER1cmF0aW9uKGJsLlBsb3QuX0FOSU1BVElPTl9NQVhfRFVSQVRJT04pLHIuYW5pbWF0b3IoSGF0LkFuaW1hdG9yLk1BSU4sbiksci5hdHRyKCJvcGFjaXR5IiwuNiksci5hdHRyKCJmaWxsIixuZXcgUFVlLkNvbG9yKCkucmFuZ2UoKVswXSksci5zaXplKDYpO3ZhciBpPUVVZS5jaXJjbGUoKTtyZXR1cm4gci5zeW1ib2woZnVuY3Rpb24oKXtyZXR1cm4gaX0pLHIuX2xhYmVsQ29uZmlnPW5ldyBCYXQuTWFwLHJ9cmV0dXJuIHQucHJvdG90eXBlLl9idWlsZExpZ2h0d2VpZ2h0UGxvdEVudGl0aWVzPWZ1bmN0aW9uKHIpe3ZhciBuPXRoaXMsaT1lLnByb3RvdHlwZS5fYnVpbGRMaWdodHdlaWdodFBsb3RFbnRpdGllcy5jYWxsKHRoaXMscik7cmV0dXJuIGkubWFwKGZ1bmN0aW9uKG8pe3ZhciBhPWJsLlBsb3QuX3NjYWxlZEFjY2Vzc29yKG4uc2l6ZSgpKShvLmRhdHVtLG8uaW5kZXgsby5kYXRhc2V0KTtyZXR1cm4gby5kaWFtZXRlcj1hLG99KX0sdC5wcm90b3R5cGUuX2NyZWF0ZURyYXdlcj1mdW5jdGlvbihyKXt2YXIgbj10aGlzO3JldHVybiBuZXcgVFVlLlByb3h5RHJhd2VyKGZ1bmN0aW9uKCl7cmV0dXJuIG5ldyB4S3QuU3ltYm9sU1ZHRHJhd2VyfSxmdW5jdGlvbihpKXtyZXR1cm4gbmV3IEFVZS5DYW52YXNEcmF3ZXIoaSx4S3QubWFrZVN5bWJvbENhbnZhc0RyYXdTdGVwKHIsZnVuY3Rpb24oKXtyZXR1cm4gYmwuUGxvdC5fc2NhbGVkQWNjZXNzb3Iobi5zeW1ib2woKSl9LGZ1bmN0aW9uKCl7cmV0dXJuIGJsLlBsb3QuX3NjYWxlZEFjY2Vzc29yKG4uc2l6ZSgpKX0pKX0pfSx0LnByb3RvdHlwZS5zaXplPWZ1bmN0aW9uKHIsbil7cmV0dXJuIHI9PW51bGw/dGhpcy5fcHJvcGVydHlCaW5kaW5ncy5nZXQodC5fU0laRV9LRVkpOih0aGlzLl9iaW5kUHJvcGVydHkodC5fU0laRV9LRVkscixuKSx0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5zeW1ib2w9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fcHJvcGVydHlCaW5kaW5ncy5nZXQodC5fU1lNQk9MX0tFWSk6KHRoaXMuX3Byb3BlcnR5QmluZGluZ3Muc2V0KHQuX1NZTUJPTF9LRVkse2FjY2Vzc29yOnJ9KSx0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5fZ2VuZXJhdGVEcmF3U3RlcHM9ZnVuY3Rpb24oKXt2YXIgcj1bXTtpZih0aGlzLl9hbmltYXRlT25OZXh0UmVuZGVyKCkpe3ZhciBuPXRoaXMuX2dldEF0dHJUb1Byb2plY3RvcigpLGk9YmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy5zeW1ib2woKSk7bi5kPWZ1bmN0aW9uKG8sYSxzKXtyZXR1cm4gaShvLGEscykoMCkobnVsbCl9LHIucHVzaCh7YXR0clRvUHJvamVjdG9yOm4sYW5pbWF0b3I6dGhpcy5fZ2V0QW5pbWF0b3IoSGF0LkFuaW1hdG9yLlJFU0VUKX0pfXJldHVybiByLnB1c2goe2F0dHJUb1Byb2plY3Rvcjp0aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKSxhbmltYXRvcjp0aGlzLl9nZXRBbmltYXRvcihIYXQuQW5pbWF0b3IuTUFJTil9KSxyfSx0LnByb3RvdHlwZS5fcHJvcGVydHlQcm9qZWN0b3JzPWZ1bmN0aW9uKCl7dmFyIHI9ZS5wcm90b3R5cGUuX3Byb3BlcnR5UHJvamVjdG9ycy5jYWxsKHRoaXMpLG49YmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpLGk9YmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55KCkpO3JldHVybiByLng9bixyLnk9aSxyLnRyYW5zZm9ybT1mdW5jdGlvbihvLGEscyl7cmV0dXJuInRyYW5zbGF0ZSgiK24obyxhLHMpKyIsIitpKG8sYSxzKSsiKSJ9LHIuZD10aGlzLl9jb25zdHJ1Y3RTeW1ib2xHZW5lcmF0b3IoKSxyfSx0LnByb3RvdHlwZS5fY29uc3RydWN0U3ltYm9sR2VuZXJhdG9yPWZ1bmN0aW9uKCl7dmFyIHI9YmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy5zeW1ib2woKSksbj1ibC5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnNpemUoKSk7cmV0dXJuIGZ1bmN0aW9uKGksbyxhKXtyZXR1cm4gcihpLG8sYSkobihpLG8sYSkpKG51bGwpfX0sdC5wcm90b3R5cGUuX2VudGl0eUJvdW5kcz1mdW5jdGlvbihyKXtyZXR1cm57eDpyLnBvc2l0aW9uLngtci5kaWFtZXRlci8yLHk6ci5wb3NpdGlvbi55LXIuZGlhbWV0ZXIvMix3aWR0aDpyLmRpYW1ldGVyLGhlaWdodDpyLmRpYW1ldGVyfX0sdC5wcm90b3R5cGUuX2VudGl0eVZpc2libGVPblBsb3Q9ZnVuY3Rpb24ocixuKXt2YXIgaT17bWluOm4udG9wTGVmdC54LG1heDpuLmJvdHRvbVJpZ2h0Lnh9LG89e21pbjpuLnRvcExlZnQueSxtYXg6bi5ib3R0b21SaWdodC55fSxhPXRoaXMuX2VudGl0eUJvdW5kcyhyKTtyZXR1cm4gQmF0LkRPTS5pbnRlcnNlY3RzQkJveChpLG8sYSl9LHQucHJvdG90eXBlLmVudGl0aWVzQXQ9ZnVuY3Rpb24ocil7dmFyIG49YmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpLGk9YmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55KCkpLG89YmwuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy5zaXplKCkpO3JldHVybiB0aGlzLmVudGl0aWVzKCkuZmlsdGVyKGZ1bmN0aW9uKGEpe3ZhciBzPWEuZGF0dW0sbD1hLmluZGV4LGM9YS5kYXRhc2V0LHU9bihzLGwsYyksaD1pKHMsbCxjKSxmPW8ocyxsLGMpO3JldHVybiB1LWYvMjw9ci54JiZyLng8PXUrZi8yJiZoLWYvMjw9ci55JiZyLnk8PWgrZi8yfSl9LHQucHJvdG90eXBlLmxhYmVsc0VuYWJsZWQ9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/dGhpcy5fbGFiZWxzRW5hYmxlZDoodGhpcy5fbGFiZWxzRW5hYmxlZD1yLHRoaXMuX2NsZWFyQXR0clRvUHJvamVjdG9yQ2FjaGUoKSx0aGlzLnJlbmRlcigpLHRoaXMpfSx0LnByb3RvdHlwZS5fY3JlYXRlTm9kZXNGb3JEYXRhc2V0PWZ1bmN0aW9uKHIpe3ZhciBuPWUucHJvdG90eXBlLl9jcmVhdGVOb2Rlc0ZvckRhdGFzZXQuY2FsbCh0aGlzLHIpLGk9dGhpcy5fcmVuZGVyQXJlYS5hcHBlbmQoImciKS5jbGFzc2VkKHQuX0xBQkVMX0FSRUFfQ0xBU1MsITApLG89bmV3IEZhdC5TdmdDb250ZXh0KGkubm9kZSgpKSxhPW5ldyBGYXQuQ2FjaGVNZWFzdXJlcihvKSxzPW5ldyBGYXQuV3JpdGVyKGEsbyk7cmV0dXJuIHRoaXMuX2xhYmVsQ29uZmlnLnNldChyLHtsYWJlbEFyZWE6aSxtZWFzdXJlcjphLHdyaXRlcjpzfSksbn0sdC5wcm90b3R5cGUuX3JlbW92ZURhdGFzZXROb2Rlcz1mdW5jdGlvbihyKXtlLnByb3RvdHlwZS5fcmVtb3ZlRGF0YXNldE5vZGVzLmNhbGwodGhpcyxyKTt2YXIgbj10aGlzLl9sYWJlbENvbmZpZy5nZXQocik7biE9bnVsbCYmKG4ubGFiZWxBcmVhLnJlbW92ZSgpLHRoaXMuX2xhYmVsQ29uZmlnLmRlbGV0ZShyKSl9LHQucHJvdG90eXBlLl9hZGRpdGlvbmFsUGFpbnQ9ZnVuY3Rpb24ocil7dmFyIG49dGhpczt0aGlzLmRhdGFzZXRzKCkuZm9yRWFjaChmdW5jdGlvbihpKXtyZXR1cm4gbi5fbGFiZWxDb25maWcuZ2V0KGkpLmxhYmVsQXJlYS5zZWxlY3RBbGwoImciKS5yZW1vdmUoKX0pLHRoaXMuX2xhYmVsc0VuYWJsZWQmJkJhdC5XaW5kb3cuc2V0VGltZW91dChmdW5jdGlvbigpe3JldHVybiBuLl9kcmF3TGFiZWxzKCl9LHIpfSx0LnByb3RvdHlwZS5fZHJhd0xhYmVscz1mdW5jdGlvbigpe3ZhciByPXRoaXMsbj10aGlzLl9nZXREYXRhVG9EcmF3KCksaT10aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKTt0aGlzLmRhdGFzZXRzKCkuZm9yRWFjaChmdW5jdGlvbihvKXtmb3IodmFyIGE9bi5nZXQobykscz1hLmxlbmd0aCxsPTA7bDxzO2wrKyl7dmFyIGM9YVtsXTtjIT1udWxsJiZyLl9kcmF3TGFiZWwoYyxsLG8saSl9fSl9LHQucHJvdG90eXBlLl9kcmF3TGFiZWw9ZnVuY3Rpb24ocixuLGksbyl7aWYoci5sYWJlbCE9bnVsbCl7dmFyIGE9dGhpcy5fbGFiZWxDb25maWcuZ2V0KGkpLHM9YS5sYWJlbEFyZWEsbD1hLm1lYXN1cmVyLGM9YS53cml0ZXIsdT17eDpvLngocixuLGkpLHk6by55KHIsbixpKX0saD1ibC5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnNpemUoKSksZj1oKHIsbixpKSxwPXRoaXMuX2xhYmVsRm9ybWF0dGVyKHIubGFiZWwscixuLGkpLGQ9bC5tZWFzdXJlKHApLGc9dGhpcy5fY2FsY3VsYXRlTGFiZWxQcm9wZXJ0aWVzKHUsZixkKSxfPWcuY29udGFpbmVyRGltZW5zaW9ucyx5PWcubGFiZWxDb250YWluZXJPcmlnaW4seD1nLmxhYmVsT3JpZ2luLGI9Zy5hbGlnbm1lbnQsUz10aGlzLl9jcmVhdGVMYWJlbENvbnRhaW5lcihzLHkseCxkKSxDPXt4QWxpZ246Yi54LHlBbGlnbjpiLnl9O2Mud3JpdGUocCxfLndpZHRoLF8uaGVpZ2h0LEMsUy5ub2RlKCkpfX0sdC5wcm90b3R5cGUuX2NhbGN1bGF0ZUxhYmVsUHJvcGVydGllcz1mdW5jdGlvbihyLG4saSl7dmFyIG89bjxpLmhlaWdodD9uLzIrdC5fTEFCRUxfTUFSR0lOX0ZST01fQlVCQkxFOjA7cmV0dXJue2NvbnRhaW5lckRpbWVuc2lvbnM6e3dpZHRoOmkud2lkdGgsaGVpZ2h0OmkuaGVpZ2h0fSxsYWJlbENvbnRhaW5lck9yaWdpbjp7eDpyLngtaS53aWR0aC8yLHk6ci55LWkuaGVpZ2h0LzIrb30sbGFiZWxPcmlnaW46e3g6ci54LHk6ci55fSxhbGlnbm1lbnQ6e3g6ImNlbnRlciIseToiY2VudGVyIn19fSx0LnByb3RvdHlwZS5fY3JlYXRlTGFiZWxDb250YWluZXI9ZnVuY3Rpb24ocixuLGksbyl7dmFyIGE9ci5hcHBlbmQoImciKS5hdHRyKCJ0cmFuc2Zvcm0iLCJ0cmFuc2xhdGUoIituLngrIiwgIituLnkrIikiKTtyZXR1cm4gYS5jbGFzc2VkKCJvbi1iYXItbGFiZWwiLCEwKSxhfSx0Ll9TSVpFX0tFWT0ic2l6ZSIsdC5fU1lNQk9MX0tFWT0ic3ltYm9sIix0Ll9MQUJFTF9BUkVBX0NMQVNTPSJzY2F0dGVyLWxhYmVsLXRleHQtYXJlYSIsdC5fTEFCRUxfTUFSR0lOX0ZST01fQlVCQkxFPTE1LHR9KElVZS5YWVBsb3QpO1ZhdC5TY2F0dGVyPUxVZX0pO3ZhciB3S3Q9SChVYXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFVhdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIGtVZT0oZGUoKSxVdChwZSkpLFJVZT1MZigpLE5VZT1VdSgpLERVZT15YXQoKSxPVWU9a3MoKSx6VWU9Q0YoKSxMUz1ycygpLEZVZT1IMSgpLEJVZT1mdW5jdGlvbihlKXtrVWUuX19leHRlbmRzKHQsZSk7ZnVuY3Rpb24gdCgpe3ZhciByPWUuY2FsbCh0aGlzKXx8dGhpcztyZXR1cm4gci5hZGRDbGFzcygic2VnbWVudC1wbG90Iiksci5hdHRyKCJzdHJva2UiLG5ldyBPVWUuQ29sb3IoKS5yYW5nZSgpWzBdKSxyLmF0dHIoInN0cm9rZS13aWR0aCIsIjJweCIpLHJ9cmV0dXJuIHQucHJvdG90eXBlLl9jcmVhdGVEcmF3ZXI9ZnVuY3Rpb24oKXtyZXR1cm4gbmV3IE5VZS5Qcm94eURyYXdlcihmdW5jdGlvbigpe3JldHVybiBuZXcgRFVlLlNlZ21lbnRTVkdEcmF3ZXJ9LGZ1bmN0aW9uKCl7cmV0dXJuIHpVZS53YXJuKCJjYW52YXMgcmVuZGVyZXIgaXMgbm90IHN1cHBvcnRlZCBvbiBTZWdtZW50IFBsb3QhIiksbnVsbH0pfSx0LnByb3RvdHlwZS5fZ2VuZXJhdGVEcmF3U3RlcHM9ZnVuY3Rpb24oKXtyZXR1cm5be2F0dHJUb1Byb2plY3Rvcjp0aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKSxhbmltYXRvcjpuZXcgUlVlLk51bGx9XX0sdC5wcm90b3R5cGUuX2ZpbHRlckZvclByb3BlcnR5PWZ1bmN0aW9uKHIpe3JldHVybiByPT09IngyIj9lLnByb3RvdHlwZS5fZmlsdGVyRm9yUHJvcGVydHkuY2FsbCh0aGlzLCJ4Iik6cj09PSJ5MiI/ZS5wcm90b3R5cGUuX2ZpbHRlckZvclByb3BlcnR5LmNhbGwodGhpcywieSIpOmUucHJvdG90eXBlLl9maWx0ZXJGb3JQcm9wZXJ0eS5jYWxsKHRoaXMscil9LHQucHJvdG90eXBlLng9ZnVuY3Rpb24ocixuKXtpZihyPT1udWxsKXJldHVybiBlLnByb3RvdHlwZS54LmNhbGwodGhpcyk7aWYobj09bnVsbCllLnByb3RvdHlwZS54LmNhbGwodGhpcyxyKTtlbHNle2UucHJvdG90eXBlLnguY2FsbCh0aGlzLHIsbik7dmFyIGk9dGhpcy54MigpLG89aSYmaS5hY2Nlc3NvcjtvIT1udWxsJiZ0aGlzLl9iaW5kUHJvcGVydHkodC5fWDJfS0VZLG8sbil9cmV0dXJuIHRoaXN9LHQucHJvdG90eXBlLngyPWZ1bmN0aW9uKHIpe2lmKHI9PW51bGwpcmV0dXJuIHRoaXMuX3Byb3BlcnR5QmluZGluZ3MuZ2V0KHQuX1gyX0tFWSk7dmFyIG49dGhpcy54KCksaT1uJiZuLnNjYWxlO3JldHVybiB0aGlzLl9iaW5kUHJvcGVydHkodC5fWDJfS0VZLHIsaSksdGhpcy5yZW5kZXIoKSx0aGlzfSx0LnByb3RvdHlwZS55PWZ1bmN0aW9uKHIsbil7aWYocj09bnVsbClyZXR1cm4gZS5wcm90b3R5cGUueS5jYWxsKHRoaXMpO2lmKG49PW51bGwpZS5wcm90b3R5cGUueS5jYWxsKHRoaXMscik7ZWxzZXtlLnByb3RvdHlwZS55LmNhbGwodGhpcyxyLG4pO3ZhciBpPXRoaXMueTIoKSxvPWkmJmkuYWNjZXNzb3I7byE9bnVsbCYmdGhpcy5fYmluZFByb3BlcnR5KHQuX1kyX0tFWSxvLG4pfXJldHVybiB0aGlzfSx0LnByb3RvdHlwZS55Mj1mdW5jdGlvbihyKXtpZihyPT1udWxsKXJldHVybiB0aGlzLl9wcm9wZXJ0eUJpbmRpbmdzLmdldCh0Ll9ZMl9LRVkpO3ZhciBuPXRoaXMueSgpLGk9biYmbi5zY2FsZTtyZXR1cm4gdGhpcy5fYmluZFByb3BlcnR5KHQuX1kyX0tFWSxyLGkpLHRoaXMucmVuZGVyKCksdGhpc30sdC5wcm90b3R5cGUuX3Byb3BlcnR5UHJvamVjdG9ycz1mdW5jdGlvbigpe3ZhciByPWUucHJvdG90eXBlLl9wcm9wZXJ0eVByb2plY3RvcnMuY2FsbCh0aGlzKTtyZXR1cm4gci54MT1MUy5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLngoKSksci54Mj10aGlzLngyKCk9PW51bGw/TFMuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpOkxTLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMueDIoKSksci55MT1MUy5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnkoKSksci55Mj10aGlzLnkyKCk9PW51bGw/TFMuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy55KCkpOkxTLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHRoaXMueTIoKSkscn0sdC5wcm90b3R5cGUuZW50aXRpZXNBdD1mdW5jdGlvbihyKXt2YXIgbj10aGlzLmVudGl0eU5lYXJlc3Qocik7cmV0dXJuIG4hPW51bGw/W25dOltdfSx0LnByb3RvdHlwZS5lbnRpdGllc0luPWZ1bmN0aW9uKHIsbil7dmFyIGksbztpZihuPT1udWxsKXt2YXIgYT1yO2k9e21pbjphLnRvcExlZnQueCxtYXg6YS5ib3R0b21SaWdodC54fSxvPXttaW46YS50b3BMZWZ0LnksbWF4OmEuYm90dG9tUmlnaHQueX19ZWxzZSBpPXIsbz1uO3JldHVybiB0aGlzLl9lbnRpdGllc0ludGVyc2VjdGluZyhpLG8pfSx0LnByb3RvdHlwZS5fZW50aXRpZXNJbnRlcnNlY3Rpbmc9ZnVuY3Rpb24ocixuKXtmb3IodmFyIGk9W10sbz10aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKSxhPXRoaXMuZW50aXRpZXMoKSxzPWEubGVuZ3RoLGw9MDtsPHM7bCsrKXt2YXIgYz1hW2xdO3RoaXMuX2xpbmVJbnRlcnNlY3RzQm94KGMscixuLG8pJiZpLnB1c2goYyl9cmV0dXJuIGl9LHQucHJvdG90eXBlLl9saW5lSW50ZXJzZWN0c0JveD1mdW5jdGlvbihyLG4saSxvKXt2YXIgYT10aGlzLHM9by54MShyLmRhdHVtLHIuaW5kZXgsci5kYXRhc2V0KSxsPW8ueDIoci5kYXR1bSxyLmluZGV4LHIuZGF0YXNldCksYz1vLnkxKHIuZGF0dW0sci5pbmRleCxyLmRhdGFzZXQpLHU9by55MihyLmRhdHVtLHIuaW5kZXgsci5kYXRhc2V0KTtpZihuLm1pbjw9cyYmczw9bi5tYXgmJmkubWluPD1jJiZjPD1pLm1heHx8bi5taW48PWwmJmw8PW4ubWF4JiZpLm1pbjw9dSYmdTw9aS5tYXgpcmV0dXJuITA7dmFyIGg9e3g6cyx5OmN9LGY9e3g6bCx5OnV9LHA9W3t4Om4ubWluLHk6aS5taW59LHt4Om4ubWluLHk6aS5tYXh9LHt4Om4ubWF4LHk6aS5tYXh9LHt4Om4ubWF4LHk6aS5taW59XSxkPXAuZmlsdGVyKGZ1bmN0aW9uKGcsXyl7cmV0dXJuIF8hPT0wP2EuX2xpbmVJbnRlcnNlY3RzU2VnbWVudChoLGYsZyxwW18tMV0pJiZhLl9saW5lSW50ZXJzZWN0c1NlZ21lbnQoZyxwW18tMV0saCxmKTohMX0pO3JldHVybiBkLmxlbmd0aD4wfSx0LnByb3RvdHlwZS5fbGluZUludGVyc2VjdHNTZWdtZW50PWZ1bmN0aW9uKHIsbixpLG8pe3ZhciBhPWZ1bmN0aW9uKHMsbCxjKXtyZXR1cm4obC54LXMueCkqKGMueS1sLnkpLShsLnktcy55KSooYy54LWwueCl9O3JldHVybiBhKHIsbixpKSphKHIsbixvKTwwfSx0Ll9YMl9LRVk9IngyIix0Ll9ZMl9LRVk9InkyIix0fShGVWUuWFlQbG90KTtVYXQuU2VnbWVudD1CVWV9KTt2YXIgRUt0PUgocWF0PT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eShxYXQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBIVWU9KGRlKCksVXQocGUpKSxTS3Q9KEVyKCksVXQoTXIpKSxWVWU9TGYoKSxNS3Q9Q1MoKSx0MD1GZSgpLFVVZT1DYXQoKSxxVWU9cnMoKSxHVWU9ZnVuY3Rpb24oZSl7SFVlLl9fZXh0ZW5kcyh0LGUpO2Z1bmN0aW9uIHQoKXt2YXIgcj1lLmNhbGwodGhpcyl8fHRoaXM7cmV0dXJuIHIuX3N0YWNraW5nUmVzdWx0PU1LdC5tZW1UaHVuayhmdW5jdGlvbigpe3JldHVybiByLmRhdGFzZXRzKCl9LGZ1bmN0aW9uKCl7cmV0dXJuIHIueCgpLmFjY2Vzc29yfSxmdW5jdGlvbigpe3JldHVybiByLnkoKS5hY2Nlc3Nvcn0sZnVuY3Rpb24oKXtyZXR1cm4gci5fc3RhY2tpbmdPcmRlcn0sZnVuY3Rpb24obixpLG8sYSl7cmV0dXJuIHQwLlN0YWNraW5nLnN0YWNrKG4saSxvLGEpfSksci5fc3RhY2tlZEV4dGVudD1NS3QubWVtVGh1bmsoci5fc3RhY2tpbmdSZXN1bHQsZnVuY3Rpb24oKXtyZXR1cm4gci54KCkuYWNjZXNzb3J9LGZ1bmN0aW9uKCl7cmV0dXJuIHIuX2ZpbHRlckZvclByb3BlcnR5KCJ5Iil9LGZ1bmN0aW9uKG4saSxvKXtyZXR1cm4gdDAuU3RhY2tpbmcuc3RhY2tlZEV4dGVudChuLGksbyl9KSxyLl9iYXNlbGluZVZhbHVlPTAsci5fc3RhY2tpbmdPcmRlcj0iYm90dG9tdXAiLHIuYWRkQ2xhc3MoInN0YWNrZWQtYXJlYS1wbG90Iiksci5fYmFzZWxpbmVWYWx1ZVByb3ZpZGVyPWZ1bmN0aW9uKCl7cmV0dXJuW3IuX2Jhc2VsaW5lVmFsdWVdfSxyLmNyb3BwZWRSZW5kZXJpbmdFbmFibGVkKCExKSxyfXJldHVybiB0LnByb3RvdHlwZS5jcm9wcGVkUmVuZGVyaW5nRW5hYmxlZD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD9lLnByb3RvdHlwZS5jcm9wcGVkUmVuZGVyaW5nRW5hYmxlZC5jYWxsKHRoaXMpOnI/KHQwLldpbmRvdy53YXJuKCJXYXJuaW5nOiBTdGFja2VkIEFyZWEgUGxvdCBkb2VzIG5vdCBzdXBwb3J0IGNyb3BwZWQgcmVuZGVyaW5nLiIpLHRoaXMpOmUucHJvdG90eXBlLmNyb3BwZWRSZW5kZXJpbmdFbmFibGVkLmNhbGwodGhpcyxyKX0sdC5wcm90b3R5cGUuX2dldEFuaW1hdG9yPWZ1bmN0aW9uKHIpe3JldHVybiBuZXcgVlVlLk51bGx9LHQucHJvdG90eXBlLl9zZXR1cD1mdW5jdGlvbigpe2UucHJvdG90eXBlLl9zZXR1cC5jYWxsKHRoaXMpLHRoaXMuX2Jhc2VsaW5lPXRoaXMuX3JlbmRlckFyZWEuYXBwZW5kKCJsaW5lIikuY2xhc3NlZCgiYmFzZWxpbmUiLCEwKX0sdC5wcm90b3R5cGUueD1mdW5jdGlvbihyLG4pe3JldHVybiByPT1udWxsP2UucHJvdG90eXBlLnguY2FsbCh0aGlzKToobj09bnVsbD9lLnByb3RvdHlwZS54LmNhbGwodGhpcyxyKTplLnByb3RvdHlwZS54LmNhbGwodGhpcyxyLG4pLHRoaXMuX2NoZWNrU2FtZURvbWFpbigpLHRoaXMpfSx0LnByb3RvdHlwZS55PWZ1bmN0aW9uKHIsbil7cmV0dXJuIHI9PW51bGw/ZS5wcm90b3R5cGUueS5jYWxsKHRoaXMpOihuPT1udWxsP2UucHJvdG90eXBlLnkuY2FsbCh0aGlzLHIpOmUucHJvdG90eXBlLnkuY2FsbCh0aGlzLHIsbiksdGhpcy5fY2hlY2tTYW1lRG9tYWluKCksdGhpcyl9LHQucHJvdG90eXBlLnlPZmZzZXQ9ZnVuY3Rpb24ocixuKXt2YXIgaT10aGlzLl9zdGFja2luZ1Jlc3VsdCgpO2lmKGkhPW51bGwpe3ZhciBvPWkuZ2V0KHIpO2lmKG8hPW51bGwpe3ZhciBhPW8uZ2V0KFN0cmluZyhuKSk7aWYoYSE9bnVsbClyZXR1cm4gYS5vZmZzZXR9fX0sdC5wcm90b3R5cGUuc3RhY2tpbmdPcmRlcj1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9zdGFja2luZ09yZGVyOih0aGlzLl9zdGFja2luZ09yZGVyPXIsdGhpcy5fb25EYXRhc2V0VXBkYXRlKCksdGhpcyl9LHQucHJvdG90eXBlLmRvd25zYW1wbGluZ0VuYWJsZWQ9ZnVuY3Rpb24ocil7cmV0dXJuIHI9PW51bGw/ZS5wcm90b3R5cGUuZG93bnNhbXBsaW5nRW5hYmxlZC5jYWxsKHRoaXMpOih0MC5XaW5kb3cud2FybigiV2FybmluZzogU3RhY2tlZCBBcmVhIFBsb3QgZG9lcyBub3Qgc3VwcG9ydCBkb3duc2FtcGxpbmciKSx0aGlzKX0sdC5wcm90b3R5cGUuX2FkZGl0aW9uYWxQYWludD1mdW5jdGlvbigpe3ZhciByPXRoaXMueSgpLnNjYWxlLnNjYWxlKHRoaXMuX2Jhc2VsaW5lVmFsdWUpLG49e3gxOjAseTE6cix4Mjp0aGlzLndpZHRoKCkseTI6cn07dGhpcy5fZ2V0QW5pbWF0b3IoImJhc2VsaW5lIikuYW5pbWF0ZSh0aGlzLl9iYXNlbGluZSxuKX0sdC5wcm90b3R5cGUuX3VwZGF0ZVlTY2FsZT1mdW5jdGlvbigpe3ZhciByPXRoaXMueSgpLG49ciYmci5zY2FsZTtuIT1udWxsJiYobi5hZGRQYWRkaW5nRXhjZXB0aW9uc1Byb3ZpZGVyKHRoaXMuX2Jhc2VsaW5lVmFsdWVQcm92aWRlciksbi5hZGRJbmNsdWRlZFZhbHVlc1Byb3ZpZGVyKHRoaXMuX2Jhc2VsaW5lVmFsdWVQcm92aWRlcikpfSx0LnByb3RvdHlwZS5fb25EYXRhc2V0VXBkYXRlPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2NoZWNrU2FtZURvbWFpbigpLGUucHJvdG90eXBlLl9vbkRhdGFzZXRVcGRhdGUuY2FsbCh0aGlzKSx0aGlzfSx0LnByb3RvdHlwZS5nZXRFeHRlbnRzRm9yUHJvcGVydHk9ZnVuY3Rpb24ocil7dmFyIG49InkiO3JldHVybiByPT09bj9bdGhpcy5fc3RhY2tlZEV4dGVudCgpXTplLnByb3RvdHlwZS5nZXRFeHRlbnRzRm9yUHJvcGVydHkuY2FsbCh0aGlzLHIpfSx0LnByb3RvdHlwZS5fY2hlY2tTYW1lRG9tYWluPWZ1bmN0aW9uKCl7aWYoISF0aGlzLl9wcm9qZWN0b3JzUmVhZHkoKSl7dmFyIHI9dGhpcy5kYXRhc2V0cygpLG49dGhpcy54KCkuYWNjZXNzb3IsaT1yLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gU0t0LnNldChhLmRhdGEoKS5tYXAoZnVuY3Rpb24ocyxsKXtyZXR1cm4gdDAuU3RhY2tpbmcubm9ybWFsaXplS2V5KG4ocyxsLGEpKX0pKS52YWx1ZXMoKX0pLG89dC5fZG9tYWluS2V5cyhyLG4pO2kuc29tZShmdW5jdGlvbihhKXtyZXR1cm4gYS5sZW5ndGghPT1vLmxlbmd0aH0pJiZ0MC5XaW5kb3cud2FybigidGhlIGRvbWFpbnMgYWNyb3NzIHRoZSBkYXRhc2V0cyBhcmUgbm90IHRoZSBzYW1lLiBQbG90IG1heSBwcm9kdWNlIHVuaW50ZW5kZWQgYmVoYXZpb3IuIil9fSx0Ll9kb21haW5LZXlzPWZ1bmN0aW9uKHIsbil7dmFyIGk9U0t0LnNldCgpO3JldHVybiByLmZvckVhY2goZnVuY3Rpb24obyl7Zm9yKHZhciBhPW8uZGF0YSgpLHM9YS5sZW5ndGgsbD0wO2w8cztsKyspe3ZhciBjPWFbbF07aS5hZGQobihjLGwsbykpfX0pLGkudmFsdWVzKCl9LHQucHJvdG90eXBlLl9jb29yZGluYXRlUHJvamVjdG9ycz1mdW5jdGlvbigpe3ZhciByPXRoaXMsbj1xVWUuUGxvdC5fc2NhbGVkQWNjZXNzb3IodGhpcy54KCkpLGk9dGhpcy55KCkuYWNjZXNzb3Isbz10aGlzLngoKS5hY2Nlc3NvcixhPWZ1bmN0aW9uKHUsaCxmKXtyZXR1cm4gdDAuU3RhY2tpbmcubm9ybWFsaXplS2V5KG8odSxoLGYpKX0scz10aGlzLl9zdGFja2luZ1Jlc3VsdCgpLGw9ZnVuY3Rpb24odSxoLGYpe3ZhciBwPStpKHUsaCxmKSxkPXMuZ2V0KGYpLmdldChhKHUsaCxmKSkub2Zmc2V0O3JldHVybiByLnkoKS5zY2FsZS5zY2FsZShwK2QpfSxjPWZ1bmN0aW9uKHUsaCxmKXt2YXIgcD1zLmdldChmKS5nZXQoYSh1LGgsZikpLm9mZnNldDtyZXR1cm4gci55KCkuc2NhbGUuc2NhbGUocCl9O3JldHVybltuLGwsY119LHQucHJvdG90eXBlLl9wcm9wZXJ0eVByb2plY3RvcnM9ZnVuY3Rpb24oKXt2YXIgcj1lLnByb3RvdHlwZS5fcHJvcGVydHlQcm9qZWN0b3JzLmNhbGwodGhpcyksbj10aGlzLl9jb29yZGluYXRlUHJvamVjdG9ycygpLGk9blswXSxvPW5bMV0sYT1uWzJdO3JldHVybiByLmQ9dGhpcy5fY29uc3RydWN0QXJlYVByb2plY3RvcihpLG8sYSkscn0sdC5wcm90b3R5cGUuX3BpeGVsUG9pbnQ9ZnVuY3Rpb24ocixuLGkpe3ZhciBvPWUucHJvdG90eXBlLl9waXhlbFBvaW50LmNhbGwodGhpcyxyLG4saSksYT10aGlzLngoKS5hY2Nlc3NvcihyLG4saSkscz10aGlzLnkoKS5hY2Nlc3NvcihyLG4saSksbD10aGlzLnkoKS5zY2FsZS5zY2FsZSgrcyt0aGlzLl9zdGFja2luZ1Jlc3VsdCgpLmdldChpKS5nZXQodDAuU3RhY2tpbmcubm9ybWFsaXplS2V5KGEpKS5vZmZzZXQpO3JldHVybnt4Om8ueCx5Omx9fSx0fShVVWUuQXJlYSk7cWF0LlN0YWNrZWRBcmVhPUdVZX0pO3ZhciBDS3Q9SChZYXQ9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KFlhdCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIFdVZT0oZGUoKSxVdChwZSkpLEdhdD1fbCgpLFlVZT1CdSgpLFRLdD1DUygpLHBCPUZlKCksV2F0PV80KCksalVlPXJzKCksWFVlPWZ1bmN0aW9uKGUpe1dVZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KHIpe3I9PT12b2lkIDAmJihyPSJ2ZXJ0aWNhbCIpO3ZhciBuPWUuY2FsbCh0aGlzLHIpfHx0aGlzO3JldHVybiBuLl9leHRyZW1hRm9ybWF0dGVyPVlVZS5pZGVudGl0eSgpLG4uX3N0YWNraW5nUmVzdWx0PVRLdC5tZW1UaHVuayhmdW5jdGlvbigpe3JldHVybiBuLmRhdGFzZXRzKCl9LGZ1bmN0aW9uKCl7cmV0dXJuIG4ucG9zaXRpb24oKS5hY2Nlc3Nvcn0sZnVuY3Rpb24oKXtyZXR1cm4gbi5sZW5ndGgoKS5hY2Nlc3Nvcn0sZnVuY3Rpb24oKXtyZXR1cm4gbi5fc3RhY2tpbmdPcmRlcn0sZnVuY3Rpb24oaSxvLGEscyl7cmV0dXJuIHBCLlN0YWNraW5nLnN0YWNrKGksbyxhLHMpfSksbi5fc3RhY2tlZEV4dGVudD1US3QubWVtVGh1bmsobi5fc3RhY2tpbmdSZXN1bHQsZnVuY3Rpb24oKXtyZXR1cm4gbi5wb3NpdGlvbigpLmFjY2Vzc29yfSxmdW5jdGlvbigpe3JldHVybiBuLl9maWx0ZXJGb3JQcm9wZXJ0eShuLl9pc1ZlcnRpY2FsPyJ5IjoieCIpfSxmdW5jdGlvbihpLG8sYSl7cmV0dXJuIHBCLlN0YWNraW5nLnN0YWNrZWRFeHRlbnQoaSxvLGEpfSksbi5hZGRDbGFzcygic3RhY2tlZC1iYXItcGxvdCIpLG4uX3N0YWNraW5nT3JkZXI9ImJvdHRvbXVwIixufXJldHVybiB0LnByb3RvdHlwZS5zdGFja2luZ09yZGVyPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX3N0YWNraW5nT3JkZXI6KHRoaXMuX3N0YWNraW5nT3JkZXI9cix0aGlzLl9vbkRhdGFzZXRVcGRhdGUoKSx0aGlzKX0sdC5wcm90b3R5cGUuZXh0cmVtYUZvcm1hdHRlcj1mdW5jdGlvbihyKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTA/dGhpcy5fZXh0cmVtYUZvcm1hdHRlcjoodGhpcy5fZXh0cmVtYUZvcm1hdHRlcj1yLHRoaXMucmVuZGVyKCksdGhpcyl9LHQucHJvdG90eXBlLl9zZXR1cD1mdW5jdGlvbigpe2UucHJvdG90eXBlLl9zZXR1cC5jYWxsKHRoaXMpLHRoaXMuX2xhYmVsQXJlYT10aGlzLl9yZW5kZXJBcmVhLmFwcGVuZCgiZyIpLmNsYXNzZWQoV2F0LkJhci5fTEFCRUxfQVJFQV9DTEFTUywhMCk7dmFyIHI9bmV3IEdhdC5TdmdDb250ZXh0KHRoaXMuX2xhYmVsQXJlYS5ub2RlKCkpO3RoaXMuX21lYXN1cmVyPW5ldyBHYXQuQ2FjaGVNZWFzdXJlcihyKSx0aGlzLl93cml0ZXI9bmV3IEdhdC5Xcml0ZXIodGhpcy5fbWVhc3VyZXIscil9LHQucHJvdG90eXBlLl9kcmF3TGFiZWxzPWZ1bmN0aW9uKCl7dmFyIHI9dGhpcztlLnByb3RvdHlwZS5fZHJhd0xhYmVscy5jYWxsKHRoaXMpLHRoaXMuX2xhYmVsQXJlYS5zZWxlY3RBbGwoImciKS5yZW1vdmUoKTt2YXIgbj0rdGhpcy5iYXNlbGluZVZhbHVlKCksaT10aGlzLnBvc2l0aW9uKCkuc2NhbGUsbz10aGlzLmxlbmd0aCgpLnNjYWxlLGE9cEIuU3RhY2tpbmcuc3RhY2tlZEV4dGVudHModGhpcy5fc3RhY2tpbmdSZXN1bHQoKSkscz1hLm1heGltdW1FeHRlbnRzLGw9YS5taW5pbXVtRXh0ZW50cyxjPVtdLHU9ZnVuY3Rpb24oZixwLGQpe3ZhciBnPXAudG9wTGVmdCxfPWcueCx5PWcueSx4PXAuYm90dG9tUmlnaHQueC1wLnRvcExlZnQueCxiPXAuYm90dG9tUmlnaHQueS1wLnRvcExlZnQueSxTPXIuX2lzVmVydGljYWw/eD5kOmI+ZDtpZighUyl7dmFyIEM9ci5fbGFiZWxBcmVhLmFwcGVuZCgiZyIpLmF0dHIoInRyYW5zZm9ybSIsInRyYW5zbGF0ZSgiK18rIiwgIit5KyIpIik7Qy5jbGFzc2VkKCJzdGFja2VkLWJhci1sYWJlbCIsITApO3ZhciBQPXt4QWxpZ246ImNlbnRlciIseUFsaWduOiJjZW50ZXIifTtyLl93cml0ZXIud3JpdGUoZix4LGIsUCxDLm5vZGUoKSl9cmV0dXJuIFN9LGg9ZnVuY3Rpb24oZixwKXt2YXIgZD1yLl9nZW5lcmF0ZUF0dHJUb1Byb2plY3RvcigpLGc9ci53aWR0aCgpLF89ci5oZWlnaHQoKTtmLmZvckVhY2goZnVuY3Rpb24oeSl7aWYoeS5leHRlbnQhPT1uKXt2YXIgeD1yLmV4dHJlbWFGb3JtYXR0ZXIoKSh5LmV4dGVudCksYj1yLl9tZWFzdXJlci5tZWFzdXJlKHgpLFM9eS5zdGFja2VkRGF0dW0sQz1TLm9yaWdpbmFsRGF0dW0sUD1TLm9yaWdpbmFsSW5kZXgsaz1TLm9yaWdpbmFsRGF0YXNldDtpZighci5faXNEYXR1bU9uU2NyZWVuKGQsZyxfLEMsUCxrKSlyZXR1cm47dmFyIE89alVlLlBsb3QuX3NjYWxlZEFjY2Vzc29yKHIuYXR0cihXYXQuQmFyLl9CQVJfVEhJQ0tORVNTX0tFWSkpKEMsUCxrKSxEPW8uc2NhbGUoeS5leHRlbnQpLEI9ci5fZ2V0UG9zaXRpb25BdHRyKGkuc2NhbGUoeS5heGlzVmFsdWUpLE8pK08vMixJPXIuX2lzVmVydGljYWw/e3g6Qix5OkR9Ont4OkQseTpCfSxMPXAoSSxiLE8pLFI9dSh4LHt0b3BMZWZ0OkwsYm90dG9tUmlnaHQ6e3g6TC54K2Iud2lkdGgseTpMLnkrYi5oZWlnaHR9fSxPKTtjLnB1c2goUil9fSl9O2gocyxmdW5jdGlvbihmLHAsZCl7dmFyIGc9ci5faXNWZXJ0aWNhbD9wLndpZHRoOnAuaGVpZ2h0LF89ci5faXNWZXJ0aWNhbD9wLmhlaWdodDpwLndpZHRoO3JldHVybnt4OnIuX2lzVmVydGljYWw/Zi54LWcvMjpmLngrdC5fRVhUUkVNQV9MQUJFTF9NQVJHSU5fRlJPTV9CQVIseTpyLl9pc1ZlcnRpY2FsP2YueS1fOmYueS1nLzJ9fSksaChsLGZ1bmN0aW9uKGYscCxkKXt2YXIgZz1yLl9pc1ZlcnRpY2FsP3Aud2lkdGg6cC5oZWlnaHQsXz1yLl9pc1ZlcnRpY2FsP3AuaGVpZ2h0OnAud2lkdGg7cmV0dXJue3g6ci5faXNWZXJ0aWNhbD9mLngtZy8yOmYueC1fLHk6ci5faXNWZXJ0aWNhbD9mLnkrdC5fRVhUUkVNQV9MQUJFTF9NQVJHSU5fRlJPTV9CQVI6Zi55LWcvMn19KSxjLnNvbWUoZnVuY3Rpb24oZil7cmV0dXJuIGZ9KSYmdGhpcy5fbGFiZWxBcmVhLnNlbGVjdEFsbCgiZyIpLnJlbW92ZSgpfSx0LnByb3RvdHlwZS5fZ2VuZXJhdGVBdHRyVG9Qcm9qZWN0b3I9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLG49ZS5wcm90b3R5cGUuX2dlbmVyYXRlQXR0clRvUHJvamVjdG9yLmNhbGwodGhpcyksaT10aGlzLl9pc1ZlcnRpY2FsPyJ5IjoieCIsbz10aGlzLmxlbmd0aCgpLnNjYWxlLGE9dGhpcy5sZW5ndGgoKS5hY2Nlc3NvcixzPXRoaXMucG9zaXRpb24oKS5hY2Nlc3NvcixsPWZ1bmN0aW9uKGQsZyxfKXtyZXR1cm4gcEIuU3RhY2tpbmcubm9ybWFsaXplS2V5KHMoZCxnLF8pKX0sYz10aGlzLl9zdGFja2luZ1Jlc3VsdCgpLHU9ZnVuY3Rpb24oZCxnLF8pe3JldHVybiBvLnNjYWxlKGMuZ2V0KF8pLmdldChsKGQsZyxfKSkub2Zmc2V0KX0saD1mdW5jdGlvbihkLGcsXyl7cmV0dXJuIG8uc2NhbGUoK2EoZCxnLF8pK2MuZ2V0KF8pLmdldChsKGQsZyxfKSkub2Zmc2V0KX0sZj1mdW5jdGlvbihkLGcsXyl7cmV0dXJuIE1hdGguYWJzKGgoZCxnLF8pLXUoZCxnLF8pKX07blt0aGlzLl9pc1ZlcnRpY2FsPyJoZWlnaHQiOiJ3aWR0aCJdPWY7dmFyIHA9ZnVuY3Rpb24oZCxnLF8pe3JldHVybithKGQsZyxfKTwwP3UoZCxnLF8pOmgoZCxnLF8pfTtyZXR1cm4gbltpXT1mdW5jdGlvbihkLGcsXyl7cmV0dXJuIHIuX2lzVmVydGljYWw/cChkLGcsXyk6cChkLGcsXyktZihkLGcsXyl9LG59LHQucHJvdG90eXBlLmdldEV4dGVudHNGb3JQcm9wZXJ0eT1mdW5jdGlvbihyKXt2YXIgbj10aGlzLl9pc1ZlcnRpY2FsPyJ5IjoieCI7cmV0dXJuIHI9PT1uP1t0aGlzLl9zdGFja2VkRXh0ZW50KCldOmUucHJvdG90eXBlLmdldEV4dGVudHNGb3JQcm9wZXJ0eS5jYWxsKHRoaXMscil9LHQucHJvdG90eXBlLmludmFsaWRhdGVDYWNoZT1mdW5jdGlvbigpe2UucHJvdG90eXBlLmludmFsaWRhdGVDYWNoZS5jYWxsKHRoaXMpLHRoaXMuX21lYXN1cmVyLnJlc2V0KCl9LHQuX0VYVFJFTUFfTEFCRUxfTUFSR0lOX0ZST01fQkFSPTUsdH0oV2F0LkJhcik7WWF0LlN0YWNrZWRCYXI9WFVlfSk7dmFyIEFLdD1IKGphdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoamF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgJFVlPShkZSgpLFV0KHBlKSksS1VlPUZlKCksWlVlPV80KCksSlVlPXJzKCksUVVlPWZ1bmN0aW9uKGUpeyRVZS5fX2V4dGVuZHModCxlKTtmdW5jdGlvbiB0KCl7dmFyIHI9ZS5jYWxsKHRoaXMpfHx0aGlzO3JldHVybiByLl9jb25uZWN0b3JzRW5hYmxlZD0hMSxyLmFkZENsYXNzKCJ3YXRlcmZhbGwtcGxvdCIpLHJ9cmV0dXJuIHQucHJvdG90eXBlLmNvbm5lY3RvcnNFbmFibGVkPWZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsP3RoaXMuX2Nvbm5lY3RvcnNFbmFibGVkOih0aGlzLl9jb25uZWN0b3JzRW5hYmxlZD1yLHRoaXMpfSx0LnByb3RvdHlwZS50b3RhbD1mdW5jdGlvbihyKXtyZXR1cm4gcj09bnVsbD90aGlzLl9wcm9wZXJ0eUJpbmRpbmdzLmdldCh0Ll9UT1RBTF9LRVkpOih0aGlzLl9iaW5kUHJvcGVydHkodC5fVE9UQUxfS0VZLHIsbnVsbCksdGhpcyl9LHQucHJvdG90eXBlLl9hZGRpdGlvbmFsUGFpbnQ9ZnVuY3Rpb24ocil7dmFyIG49dGhpczt0aGlzLl9jb25uZWN0b3JBcmVhLnNlbGVjdEFsbCgibGluZSIpLnJlbW92ZSgpLHRoaXMuX2Nvbm5lY3RvcnNFbmFibGVkJiZLVWUuV2luZG93LnNldFRpbWVvdXQoZnVuY3Rpb24oKXtyZXR1cm4gbi5fZHJhd0Nvbm5lY3RvcnMoKX0scil9LHQucHJvdG90eXBlLl9jcmVhdGVOb2Rlc0ZvckRhdGFzZXQ9ZnVuY3Rpb24ocil7dmFyIG49ZS5wcm90b3R5cGUuX2NyZWF0ZU5vZGVzRm9yRGF0YXNldC5jYWxsKHRoaXMscik7cmV0dXJuIHRoaXMuX2Nvbm5lY3RvckFyZWE9dGhpcy5fcmVuZGVyQXJlYS5hcHBlbmQoImciKS5jbGFzc2VkKHQuX0NPTk5FQ1RPUl9BUkVBX0NMQVNTLCEwKSxufSx0LnByb3RvdHlwZS5nZXRFeHRlbnRzRm9yUHJvcGVydHk9ZnVuY3Rpb24ocil7dmFyIG49InkiO3JldHVybiByPT09bj9bdGhpcy5fZXh0ZW50XTplLnByb3RvdHlwZS5nZXRFeHRlbnRzRm9yUHJvcGVydHkuY2FsbCh0aGlzLHIpfSx0LnByb3RvdHlwZS5fZ2VuZXJhdGVBdHRyVG9Qcm9qZWN0b3I9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLG49ZS5wcm90b3R5cGUuX2dlbmVyYXRlQXR0clRvUHJvamVjdG9yLmNhbGwodGhpcyksaT10aGlzLnkoKS5zY2FsZSxvPUpVZS5QbG90Ll9zY2FsZWRBY2Nlc3Nvcih0aGlzLnRvdGFsKCkpLGE9dGhpcy5hdHRyKCJ5Iik7YT09bnVsbCYmKG4ueT1mdW5jdGlvbihsLGMsdSl7dmFyIGg9ci55KCkuYWNjZXNzb3IobCxjLHUpLGY9byhsLGMsdSk7aWYoZilyZXR1cm4gTWF0aC5taW4oaS5zY2FsZShoKSxpLnNjYWxlKDApKTt2YXIgcD1yLl9zdWJ0b3RhbHNbY107aWYoYz09PTApcmV0dXJuIGg8MD9pLnNjYWxlKHAtaCk6aS5zY2FsZShwKTt2YXIgZD1yLl9zdWJ0b3RhbHNbYy0xXTtyZXR1cm4gcD5kP2kuc2NhbGUocCk6aS5zY2FsZShkKX0pO3ZhciBzPXRoaXMuYXR0cigiaGVpZ2h0Iik7cmV0dXJuIHM9PW51bGwmJihuLmhlaWdodD1mdW5jdGlvbihsLGMsdSl7dmFyIGg9byhsLGMsdSksZj1yLnkoKS5hY2Nlc3NvcihsLGMsdSk7aWYoaClyZXR1cm4gTWF0aC5hYnMoaS5zY2FsZShmKS1pLnNjYWxlKDApKTt2YXIgcD1yLl9zdWJ0b3RhbHNbY107aWYoYz09PTApcmV0dXJuIE1hdGguYWJzKGkuc2NhbGUocCktaS5zY2FsZShwLWYpKTt2YXIgZD1yLl9zdWJ0b3RhbHNbYy0xXTtyZXR1cm4gTWF0aC5hYnMoaS5zY2FsZShwKS1pLnNjYWxlKGQpKX0pLG4uY2xhc3M9ZnVuY3Rpb24obCxjLHUpe3ZhciBoPSIiO3IuYXR0cigiY2xhc3MiKSE9bnVsbCYmKGg9ci5hdHRyKCJjbGFzcyIpLmFjY2Vzc29yKGwsYyx1KSsiICIpO3ZhciBmPW8obCxjLHUpO2lmKGYpcmV0dXJuIGgrdC5fQkFSX1RPVEFMX0NMQVNTO3ZhciBwPXIueSgpLmFjY2Vzc29yKGwsYyx1KTtyZXR1cm4gaCsocD4wP3QuX0JBUl9HUk9XVEhfQ0xBU1M6dC5fQkFSX0RFQ0xJTkVfQ0xBU1MpfSxufSx0LnByb3RvdHlwZS5fb25EYXRhc2V0VXBkYXRlPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3VwZGF0ZVN1YnRvdGFscygpLGUucHJvdG90eXBlLl9vbkRhdGFzZXRVcGRhdGUuY2FsbCh0aGlzKSx0aGlzfSx0LnByb3RvdHlwZS5fY2FsY3VsYXRlU3VidG90YWxzQW5kRXh0ZW50PWZ1bmN0aW9uKHIpe2Zvcih2YXIgbj1OdW1iZXIuTUFYX1ZBTFVFLGk9TnVtYmVyLk1JTl9WQUxVRSxvPTAsYT0hMSxzPXIuZGF0YSgpLGw9cy5sZW5ndGgsYz0wO2M8bDtjKyspe3ZhciB1PXNbY10saD10aGlzLnkoKS5hY2Nlc3Nvcih1LGMsciksZj10aGlzLnRvdGFsKCkuYWNjZXNzb3IodSxjLHIpO2lmKCghZnx8Yz09PTApJiYobys9aCksdGhpcy5fc3VidG90YWxzLnB1c2gobyksbzxuJiYobj1vKSxvPmkmJihpPW8pLGYmJihoPG4mJihuPWgpLGg+aSYmKGk9aCkpLCFhJiZmKXtmb3IodmFyIHA9aC1vLGQ9MDtkPHRoaXMuX3N1YnRvdGFscy5sZW5ndGg7ZCsrKXRoaXMuX3N1YnRvdGFsc1tkXSs9cDthPSEwLG8rPXAsbis9cCxpKz1wfX10aGlzLl9leHRlbnQ9W24saV19LHQucHJvdG90eXBlLl9kcmF3Q29ubmVjdG9ycz1mdW5jdGlvbigpe2Zvcih2YXIgcj10aGlzLl9nZXRBdHRyVG9Qcm9qZWN0b3IoKSxuPXRoaXMuZGF0YXNldHMoKVswXSxpPTE7aTxuLmRhdGEoKS5sZW5ndGg7aSsrKXt2YXIgbz1pLTEsYT1uLmRhdGEoKVtpXSxzPW4uZGF0YSgpW29dLGw9ci54KHMsbyxuKSxjPXIueChhLGksbikrci53aWR0aChhLGksbiksdT1yLnkoYSxpLG4pOyh0aGlzLl9zdWJ0b3RhbHNbaV0+MCYmdGhpcy5fc3VidG90YWxzW2ldPnRoaXMuX3N1YnRvdGFsc1tvXXx8dGhpcy5fc3VidG90YWxzW2ldPDAmJnRoaXMuX3N1YnRvdGFsc1tpXT49dGhpcy5fc3VidG90YWxzW29dKSYmKHU9ci55KGEsaSxuKStyLmhlaWdodChhLGksbikpLHRoaXMuX2Nvbm5lY3RvckFyZWEuYXBwZW5kKCJsaW5lIikuY2xhc3NlZCh0Ll9DT05ORUNUT1JfQ0xBU1MsITApLmF0dHIoIngxIixsKS5hdHRyKCJ4MiIsYykuYXR0cigieTEiLHUpLmF0dHIoInkyIix1KX19LHQucHJvdG90eXBlLl91cGRhdGVTdWJ0b3RhbHM9ZnVuY3Rpb24oKXt2YXIgcj10aGlzLmRhdGFzZXRzKCk7aWYoci5sZW5ndGg+MCl7dmFyIG49cltyLmxlbmd0aC0xXTt0aGlzLl9zdWJ0b3RhbHM9bmV3IEFycmF5LHRoaXMuX2NhbGN1bGF0ZVN1YnRvdGFsc0FuZEV4dGVudChuKX19LHQuX0JBUl9ERUNMSU5FX0NMQVNTPSJ3YXRlcmZhbGwtZGVjbGluZSIsdC5fQkFSX0dST1dUSF9DTEFTUz0id2F0ZXJmYWxsLWdyb3d0aCIsdC5fQkFSX1RPVEFMX0NMQVNTPSJ3YXRlcmZhbGwtdG90YWwiLHQuX0NPTk5FQ1RPUl9DTEFTUz0iY29ubmVjdG9yIix0Ll9DT05ORUNUT1JfQVJFQV9DTEFTUz0iY29ubmVjdG9yLWFyZWEiLHQuX1RPVEFMX0tFWT0idG90YWwiLHR9KFpVZS5CYXIpO2phdC5XYXRlcmZhbGw9UVVlfSk7dmFyIElTPUgoT3M9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KE9zLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgemM9KGRlKCksVXQocGUpKTt6Yy5fX2V4cG9ydFN0YXIoQ2F0KCksT3MpO3pjLl9fZXhwb3J0U3RhcihfNCgpLE9zKTt6Yy5fX2V4cG9ydFN0YXIoV290KCksT3MpO3pjLl9fZXhwb3J0U3RhcihfS3QoKSxPcyk7emMuX19leHBvcnRTdGFyKE1hdCgpLE9zKTt6Yy5fX2V4cG9ydFN0YXIoeUt0KCksT3MpO3pjLl9fZXhwb3J0U3Rhcih2S3QoKSxPcyk7emMuX19leHBvcnRTdGFyKGJLdCgpLE9zKTt6Yy5fX2V4cG9ydFN0YXIod0t0KCksT3MpO3pjLl9fZXhwb3J0U3RhcihFS3QoKSxPcyk7emMuX19leHBvcnRTdGFyKENLdCgpLE9zKTt6Yy5fX2V4cG9ydFN0YXIoQUt0KCksT3MpfSk7dmFyIFBLdD1IKFhhdD0+eyJ1c2Ugc3RyaWN0IjtPYmplY3QuZGVmaW5lUHJvcGVydHkoWGF0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTtYYXQudmVyc2lvbj0iMy45LjAifSk7dmFyIHdsPUgobG49PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KGxuLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgRmM9KGRlKCksVXQocGUpKTtHanQoKTt2YXIgdHFlPUxmKCk7bG4uQW5pbWF0b3JzPXRxZTt2YXIgZXFlPXMkdCgpO2xuLkF4ZXM9ZXFlO3ZhciBycWU9SW90KCk7bG4uQ29tcG9uZW50cz1ycWU7dmFyIG5xZT1YQSgpO2xuLkNvbmZpZ3M9bnFlO3ZhciBpcWU9QnUoKTtsbi5Gb3JtYXR0ZXJzPWlxZTt2YXIgb3FlPVBGKCk7bG4uUmVuZGVyQ29udHJvbGxlcj1vcWU7dmFyIGFxZT1WaXQoKTtsbi5SZW5kZXJQb2xpY2llcz1hcWU7dmFyIHNxZT1qRigpO2xuLlN5bWJvbEZhY3Rvcmllcz1zcWU7dmFyIGxxZT1OMSgpO2xuLkRpc3BhdGNoZXJzPWxxZTt2YXIgY3FlPUIxKCk7bG4uRHJhd2Vycz1jcWU7dmFyIHVxZT1zNCgpO2xuLkludGVyYWN0aW9ucz11cWU7dmFyIGhxZT1JUygpO2xuLlBsb3RzPWhxZTt2YXIgZnFlPWtzKCk7bG4uU2NhbGVzPWZxZTt2YXIgcHFlPUZlKCk7bG4uVXRpbHM9cHFlO0ZjLl9fZXhwb3J0U3RhcihRQSgpLGxuKTt2YXIgZHFlPUJGKCk7bG4uVGltZUludGVydmFsPWRxZS5UaW1lSW50ZXJ2YWw7RmMuX19leHBvcnRTdGFyKGtjKCksbG4pO0ZjLl9fZXhwb3J0U3RhcihHRigpLGxuKTtGYy5fX2V4cG9ydFN0YXIoam90KCksbG4pO3ZhciBtcWU9UEt0KCk7bG4udmVyc2lvbj1tcWUudmVyc2lvbjtGYy5fX2V4cG9ydFN0YXIoaTQoKSxsbik7RmMuX19leHBvcnRTdGFyKFV1KCksbG4pO0ZjLl9fZXhwb3J0U3RhcihEMSgpLGxuKTtGYy5fX2V4cG9ydFN0YXIoU290KCksbG4pO0ZjLl9fZXhwb3J0U3RhcihIMSgpLGxuKTtGYy5fX2V4cG9ydFN0YXIocnMoKSxsbik7RmMuX19leHBvcnRTdGFyKHZkKCksbG4pO0ZjLl9fZXhwb3J0U3RhcihMMSgpLGxuKX0pO3ZhciBYS3Q9SCgoS2puLGpLdCk9PntmdW5jdGlvbiBOcWUoKXt0aGlzLl9fZGF0YV9fPVtdLHRoaXMuc2l6ZT0wfWpLdC5leHBvcnRzPU5xZX0pO3ZhciBZMT1IKChaam4sJEt0KT0+e2Z1bmN0aW9uIERxZShlLHQpe3JldHVybiBlPT09dHx8ZSE9PWUmJnQhPT10fSRLdC5leHBvcnRzPURxZX0pO3ZhciBTND1IKChKam4sS0t0KT0+e3ZhciBPcWU9WTEoKTtmdW5jdGlvbiB6cWUoZSx0KXtmb3IodmFyIHI9ZS5sZW5ndGg7ci0tOylpZihPcWUoZVtyXVswXSx0KSlyZXR1cm4gcjtyZXR1cm4tMX1LS3QuZXhwb3J0cz16cWV9KTt2YXIgSkt0PUgoKFFqbixaS3QpPT57dmFyIEZxZT1TNCgpLEJxZT1BcnJheS5wcm90b3R5cGUsSHFlPUJxZS5zcGxpY2U7ZnVuY3Rpb24gVnFlKGUpe3ZhciB0PXRoaXMuX19kYXRhX18scj1GcWUodCxlKTtpZihyPDApcmV0dXJuITE7dmFyIG49dC5sZW5ndGgtMTtyZXR1cm4gcj09bj90LnBvcCgpOkhxZS5jYWxsKHQsciwxKSwtLXRoaXMuc2l6ZSwhMH1aS3QuZXhwb3J0cz1WcWV9KTt2YXIgdFp0PUgoKHRYbixRS3QpPT57dmFyIFVxZT1TNCgpO2Z1bmN0aW9uIHFxZShlKXt2YXIgdD10aGlzLl9fZGF0YV9fLHI9VXFlKHQsZSk7cmV0dXJuIHI8MD92b2lkIDA6dFtyXVsxXX1RS3QuZXhwb3J0cz1xcWV9KTt2YXIgclp0PUgoKGVYbixlWnQpPT57dmFyIEdxZT1TNCgpO2Z1bmN0aW9uIFdxZShlKXtyZXR1cm4gR3FlKHRoaXMuX19kYXRhX18sZSk+LTF9ZVp0LmV4cG9ydHM9V3FlfSk7dmFyIGladD1IKChyWG4sblp0KT0+e3ZhciBZcWU9UzQoKTtmdW5jdGlvbiBqcWUoZSx0KXt2YXIgcj10aGlzLl9fZGF0YV9fLG49WXFlKHIsZSk7cmV0dXJuIG48MD8oKyt0aGlzLnNpemUsci5wdXNoKFtlLHRdKSk6cltuXVsxXT10LHRoaXN9blp0LmV4cG9ydHM9anFlfSk7dmFyIE00PUgoKG5YbixvWnQpPT57dmFyIFhxZT1YS3QoKSwkcWU9Skt0KCksS3FlPXRadCgpLFpxZT1yWnQoKSxKcWU9aVp0KCk7ZnVuY3Rpb24gelMoZSl7dmFyIHQ9LTEscj1lPT1udWxsPzA6ZS5sZW5ndGg7Zm9yKHRoaXMuY2xlYXIoKTsrK3Q8cjspe3ZhciBuPWVbdF07dGhpcy5zZXQoblswXSxuWzFdKX19elMucHJvdG90eXBlLmNsZWFyPVhxZTt6Uy5wcm90b3R5cGUuZGVsZXRlPSRxZTt6Uy5wcm90b3R5cGUuZ2V0PUtxZTt6Uy5wcm90b3R5cGUuaGFzPVpxZTt6Uy5wcm90b3R5cGUuc2V0PUpxZTtvWnQuZXhwb3J0cz16U30pO3ZhciBzWnQ9SCgoaVhuLGFadCk9Pnt2YXIgUXFlPU00KCk7ZnVuY3Rpb24gdEdlKCl7dGhpcy5fX2RhdGFfXz1uZXcgUXFlLHRoaXMuc2l6ZT0wfWFadC5leHBvcnRzPXRHZX0pO3ZhciBjWnQ9SCgob1huLGxadCk9PntmdW5jdGlvbiBlR2UoZSl7dmFyIHQ9dGhpcy5fX2RhdGFfXyxyPXQuZGVsZXRlKGUpO3JldHVybiB0aGlzLnNpemU9dC5zaXplLHJ9bFp0LmV4cG9ydHM9ZUdlfSk7dmFyIGhadD1IKChhWG4sdVp0KT0+e2Z1bmN0aW9uIHJHZShlKXtyZXR1cm4gdGhpcy5fX2RhdGFfXy5nZXQoZSl9dVp0LmV4cG9ydHM9ckdlfSk7dmFyIHBadD1IKChzWG4sZlp0KT0+e2Z1bmN0aW9uIG5HZShlKXtyZXR1cm4gdGhpcy5fX2RhdGFfXy5oYXMoZSl9Zlp0LmV4cG9ydHM9bkdlfSk7dmFyIHVzdD1IKChsWG4sZFp0KT0+e3ZhciBpR2U9dHlwZW9mIGdsb2JhbD09Im9iamVjdCImJmdsb2JhbCYmZ2xvYmFsLk9iamVjdD09PU9iamVjdCYmZ2xvYmFsO2RadC5leHBvcnRzPWlHZX0pO3ZhciBIYz1IKChjWG4sbVp0KT0+e3ZhciBvR2U9dXN0KCksYUdlPXR5cGVvZiBzZWxmPT0ib2JqZWN0IiYmc2VsZiYmc2VsZi5PYmplY3Q9PT1PYmplY3QmJnNlbGYsc0dlPW9HZXx8YUdlfHxGdW5jdGlvbigicmV0dXJuIHRoaXMiKSgpO21adC5leHBvcnRzPXNHZX0pO3ZhciBqMT1IKCh1WG4sZ1p0KT0+e3ZhciBsR2U9SGMoKSxjR2U9bEdlLlN5bWJvbDtnWnQuZXhwb3J0cz1jR2V9KTt2YXIgeFp0PUgoKGhYbix2WnQpPT57dmFyIF9adD1qMSgpLHladD1PYmplY3QucHJvdG90eXBlLHVHZT15WnQuaGFzT3duUHJvcGVydHksaEdlPXladC50b1N0cmluZyxFND1fWnQ/X1p0LnRvU3RyaW5nVGFnOnZvaWQgMDtmdW5jdGlvbiBmR2UoZSl7dmFyIHQ9dUdlLmNhbGwoZSxFNCkscj1lW0U0XTt0cnl7ZVtFNF09dm9pZCAwO3ZhciBuPSEwfWNhdGNoKG8pe312YXIgaT1oR2UuY2FsbChlKTtyZXR1cm4gbiYmKHQ/ZVtFNF09cjpkZWxldGUgZVtFNF0pLGl9dlp0LmV4cG9ydHM9ZkdlfSk7dmFyIHdadD1IKChmWG4sYlp0KT0+e3ZhciBwR2U9T2JqZWN0LnByb3RvdHlwZSxkR2U9cEdlLnRvU3RyaW5nO2Z1bmN0aW9uIG1HZShlKXtyZXR1cm4gZEdlLmNhbGwoZSl9Ylp0LmV4cG9ydHM9bUdlfSk7dmFyIHMwPUgoKHBYbixFWnQpPT57dmFyIFNadD1qMSgpLGdHZT14WnQoKSxfR2U9d1p0KCkseUdlPSJbb2JqZWN0IE51bGxdIix2R2U9IltvYmplY3QgVW5kZWZpbmVkXSIsTVp0PVNadD9TWnQudG9TdHJpbmdUYWc6dm9pZCAwO2Z1bmN0aW9uIHhHZShlKXtyZXR1cm4gZT09bnVsbD9lPT09dm9pZCAwP3ZHZTp5R2U6TVp0JiZNWnQgaW4gT2JqZWN0KGUpP2dHZShlKTpfR2UoZSl9RVp0LmV4cG9ydHM9eEdlfSk7dmFyIE1sPUgoKGRYbixUWnQpPT57ZnVuY3Rpb24gYkdlKGUpe3ZhciB0PXR5cGVvZiBlO3JldHVybiBlIT1udWxsJiYodD09Im9iamVjdCJ8fHQ9PSJmdW5jdGlvbiIpfVRadC5leHBvcnRzPWJHZX0pO3ZhciBGUz1IKChtWG4sQ1p0KT0+e3ZhciB3R2U9czAoKSxTR2U9TWwoKSxNR2U9IltvYmplY3QgQXN5bmNGdW5jdGlvbl0iLEVHZT0iW29iamVjdCBGdW5jdGlvbl0iLFRHZT0iW29iamVjdCBHZW5lcmF0b3JGdW5jdGlvbl0iLENHZT0iW29iamVjdCBQcm94eV0iO2Z1bmN0aW9uIEFHZShlKXtpZighU0dlKGUpKXJldHVybiExO3ZhciB0PXdHZShlKTtyZXR1cm4gdD09RUdlfHx0PT1UR2V8fHQ9PU1HZXx8dD09Q0dlfUNadC5leHBvcnRzPUFHZX0pO3ZhciBQWnQ9SCgoZ1huLEFadCk9Pnt2YXIgUEdlPUhjKCksSUdlPVBHZVsiX19jb3JlLWpzX3NoYXJlZF9fIl07QVp0LmV4cG9ydHM9SUdlfSk7dmFyIGtadD1IKChfWG4sTFp0KT0+e3ZhciBoc3Q9UFp0KCksSVp0PWZ1bmN0aW9uKCl7dmFyIGU9L1teLl0rJC8uZXhlYyhoc3QmJmhzdC5rZXlzJiZoc3Qua2V5cy5JRV9QUk9UT3x8IiIpO3JldHVybiBlPyJTeW1ib2woc3JjKV8xLiIrZToiIn0oKTtmdW5jdGlvbiBMR2UoZSl7cmV0dXJuISFJWnQmJkladCBpbiBlfUxadC5leHBvcnRzPUxHZX0pO3ZhciBmc3Q9SCgoeVhuLFJadCk9Pnt2YXIga0dlPUZ1bmN0aW9uLnByb3RvdHlwZSxSR2U9a0dlLnRvU3RyaW5nO2Z1bmN0aW9uIE5HZShlKXtpZihlIT1udWxsKXt0cnl7cmV0dXJuIFJHZS5jYWxsKGUpfWNhdGNoKHQpe310cnl7cmV0dXJuIGUrIiJ9Y2F0Y2godCl7fX1yZXR1cm4iIn1SWnQuZXhwb3J0cz1OR2V9KTt2YXIgRFp0PUgoKHZYbixOWnQpPT57dmFyIERHZT1GUygpLE9HZT1rWnQoKSx6R2U9TWwoKSxGR2U9ZnN0KCksQkdlPS9bXFxeJC4qKz8oKVtcXXt9fF0vZyxIR2U9L15cW29iamVjdCAuKz9Db25zdHJ1Y3RvclxdJC8sVkdlPUZ1bmN0aW9uLnByb3RvdHlwZSxVR2U9T2JqZWN0LnByb3RvdHlwZSxxR2U9VkdlLnRvU3RyaW5nLEdHZT1VR2UuaGFzT3duUHJvcGVydHksV0dlPVJlZ0V4cCgiXiIrcUdlLmNhbGwoR0dlKS5yZXBsYWNlKEJHZSwiXFwkJiIpLnJlcGxhY2UoL2hhc093blByb3BlcnR5fChmdW5jdGlvbikuKj8oPz1cXFwoKXwgZm9yIC4rPyg/PVxcXF0pL2csIiQxLio/IikrIiQiKTtmdW5jdGlvbiBZR2UoZSl7aWYoIXpHZShlKXx8T0dlKGUpKXJldHVybiExO3ZhciB0PURHZShlKT9XR2U6SEdlO3JldHVybiB0LnRlc3QoRkdlKGUpKX1OWnQuZXhwb3J0cz1ZR2V9KTt2YXIgelp0PUgoKHhYbixPWnQpPT57ZnVuY3Rpb24gakdlKGUsdCl7cmV0dXJuIGU9PW51bGw/dm9pZCAwOmVbdF19T1p0LmV4cG9ydHM9akdlfSk7dmFyIGwwPUgoKGJYbixGWnQpPT57dmFyIFhHZT1EWnQoKSwkR2U9elp0KCk7ZnVuY3Rpb24gS0dlKGUsdCl7dmFyIHI9JEdlKGUsdCk7cmV0dXJuIFhHZShyKT9yOnZvaWQgMH1GWnQuZXhwb3J0cz1LR2V9KTt2YXIgT0I9SCgod1huLEJadCk9Pnt2YXIgWkdlPWwwKCksSkdlPUhjKCksUUdlPVpHZShKR2UsIk1hcCIpO0JadC5leHBvcnRzPVFHZX0pO3ZhciBUND1IKChTWG4sSFp0KT0+e3ZhciB0V2U9bDAoKSxlV2U9dFdlKE9iamVjdCwiY3JlYXRlIik7SFp0LmV4cG9ydHM9ZVdlfSk7dmFyIHFadD1IKChNWG4sVVp0KT0+e3ZhciBWWnQ9VDQoKTtmdW5jdGlvbiByV2UoKXt0aGlzLl9fZGF0YV9fPVZadD9WWnQobnVsbCk6e30sdGhpcy5zaXplPTB9VVp0LmV4cG9ydHM9cldlfSk7dmFyIFdadD1IKChFWG4sR1p0KT0+e2Z1bmN0aW9uIG5XZShlKXt2YXIgdD10aGlzLmhhcyhlKSYmZGVsZXRlIHRoaXMuX19kYXRhX19bZV07cmV0dXJuIHRoaXMuc2l6ZS09dD8xOjAsdH1HWnQuZXhwb3J0cz1uV2V9KTt2YXIgalp0PUgoKFRYbixZWnQpPT57dmFyIGlXZT1UNCgpLG9XZT0iX19sb2Rhc2hfaGFzaF91bmRlZmluZWRfXyIsYVdlPU9iamVjdC5wcm90b3R5cGUsc1dlPWFXZS5oYXNPd25Qcm9wZXJ0eTtmdW5jdGlvbiBsV2UoZSl7dmFyIHQ9dGhpcy5fX2RhdGFfXztpZihpV2Upe3ZhciByPXRbZV07cmV0dXJuIHI9PT1vV2U/dm9pZCAwOnJ9cmV0dXJuIHNXZS5jYWxsKHQsZSk/dFtlXTp2b2lkIDB9WVp0LmV4cG9ydHM9bFdlfSk7dmFyICRadD1IKChDWG4sWFp0KT0+e3ZhciBjV2U9VDQoKSx1V2U9T2JqZWN0LnByb3RvdHlwZSxoV2U9dVdlLmhhc093blByb3BlcnR5O2Z1bmN0aW9uIGZXZShlKXt2YXIgdD10aGlzLl9fZGF0YV9fO3JldHVybiBjV2U/dFtlXSE9PXZvaWQgMDpoV2UuY2FsbCh0LGUpfVhadC5leHBvcnRzPWZXZX0pO3ZhciBaWnQ9SCgoQVhuLEtadCk9Pnt2YXIgcFdlPVQ0KCksZFdlPSJfX2xvZGFzaF9oYXNoX3VuZGVmaW5lZF9fIjtmdW5jdGlvbiBtV2UoZSx0KXt2YXIgcj10aGlzLl9fZGF0YV9fO3JldHVybiB0aGlzLnNpemUrPXRoaXMuaGFzKGUpPzA6MSxyW2VdPXBXZSYmdD09PXZvaWQgMD9kV2U6dCx0aGlzfUtadC5leHBvcnRzPW1XZX0pO3ZhciBRWnQ9SCgoUFhuLEpadCk9Pnt2YXIgZ1dlPXFadCgpLF9XZT1XWnQoKSx5V2U9alp0KCksdldlPSRadCgpLHhXZT1aWnQoKTtmdW5jdGlvbiBCUyhlKXt2YXIgdD0tMSxyPWU9PW51bGw/MDplLmxlbmd0aDtmb3IodGhpcy5jbGVhcigpOysrdDxyOyl7dmFyIG49ZVt0XTt0aGlzLnNldChuWzBdLG5bMV0pfX1CUy5wcm90b3R5cGUuY2xlYXI9Z1dlO0JTLnByb3RvdHlwZS5kZWxldGU9X1dlO0JTLnByb3RvdHlwZS5nZXQ9eVdlO0JTLnByb3RvdHlwZS5oYXM9dldlO0JTLnByb3RvdHlwZS5zZXQ9eFdlO0padC5leHBvcnRzPUJTfSk7dmFyIHJKdD1IKChJWG4sZUp0KT0+e3ZhciB0SnQ9UVp0KCksYldlPU00KCksd1dlPU9CKCk7ZnVuY3Rpb24gU1dlKCl7dGhpcy5zaXplPTAsdGhpcy5fX2RhdGFfXz17aGFzaDpuZXcgdEp0LG1hcDpuZXcod1dlfHxiV2UpLHN0cmluZzpuZXcgdEp0fX1lSnQuZXhwb3J0cz1TV2V9KTt2YXIgaUp0PUgoKExYbixuSnQpPT57ZnVuY3Rpb24gTVdlKGUpe3ZhciB0PXR5cGVvZiBlO3JldHVybiB0PT0ic3RyaW5nInx8dD09Im51bWJlciJ8fHQ9PSJzeW1ib2wifHx0PT0iYm9vbGVhbiI/ZSE9PSJfX3Byb3RvX18iOmU9PT1udWxsfW5KdC5leHBvcnRzPU1XZX0pO3ZhciBDND1IKChrWG4sb0p0KT0+e3ZhciBFV2U9aUp0KCk7ZnVuY3Rpb24gVFdlKGUsdCl7dmFyIHI9ZS5fX2RhdGFfXztyZXR1cm4gRVdlKHQpP3JbdHlwZW9mIHQ9PSJzdHJpbmciPyJzdHJpbmciOiJoYXNoIl06ci5tYXB9b0p0LmV4cG9ydHM9VFdlfSk7dmFyIHNKdD1IKChSWG4sYUp0KT0+e3ZhciBDV2U9QzQoKTtmdW5jdGlvbiBBV2UoZSl7dmFyIHQ9Q1dlKHRoaXMsZSkuZGVsZXRlKGUpO3JldHVybiB0aGlzLnNpemUtPXQ/MTowLHR9YUp0LmV4cG9ydHM9QVdlfSk7dmFyIGNKdD1IKChOWG4sbEp0KT0+e3ZhciBQV2U9QzQoKTtmdW5jdGlvbiBJV2UoZSl7cmV0dXJuIFBXZSh0aGlzLGUpLmdldChlKX1sSnQuZXhwb3J0cz1JV2V9KTt2YXIgaEp0PUgoKERYbix1SnQpPT57dmFyIExXZT1DNCgpO2Z1bmN0aW9uIGtXZShlKXtyZXR1cm4gTFdlKHRoaXMsZSkuaGFzKGUpfXVKdC5leHBvcnRzPWtXZX0pO3ZhciBwSnQ9SCgoT1huLGZKdCk9Pnt2YXIgUldlPUM0KCk7ZnVuY3Rpb24gTldlKGUsdCl7dmFyIHI9UldlKHRoaXMsZSksbj1yLnNpemU7cmV0dXJuIHIuc2V0KGUsdCksdGhpcy5zaXplKz1yLnNpemU9PW4/MDoxLHRoaXN9Zkp0LmV4cG9ydHM9TldlfSk7dmFyIHpCPUgoKHpYbixkSnQpPT57dmFyIERXZT1ySnQoKSxPV2U9c0p0KCkseldlPWNKdCgpLEZXZT1oSnQoKSxCV2U9cEp0KCk7ZnVuY3Rpb24gSFMoZSl7dmFyIHQ9LTEscj1lPT1udWxsPzA6ZS5sZW5ndGg7Zm9yKHRoaXMuY2xlYXIoKTsrK3Q8cjspe3ZhciBuPWVbdF07dGhpcy5zZXQoblswXSxuWzFdKX19SFMucHJvdG90eXBlLmNsZWFyPURXZTtIUy5wcm90b3R5cGUuZGVsZXRlPU9XZTtIUy5wcm90b3R5cGUuZ2V0PXpXZTtIUy5wcm90b3R5cGUuaGFzPUZXZTtIUy5wcm90b3R5cGUuc2V0PUJXZTtkSnQuZXhwb3J0cz1IU30pO3ZhciBnSnQ9SCgoRlhuLG1KdCk9Pnt2YXIgSFdlPU00KCksVldlPU9CKCksVVdlPXpCKCkscVdlPTIwMDtmdW5jdGlvbiBHV2UoZSx0KXt2YXIgcj10aGlzLl9fZGF0YV9fO2lmKHIgaW5zdGFuY2VvZiBIV2Upe3ZhciBuPXIuX19kYXRhX187aWYoIVZXZXx8bi5sZW5ndGg8cVdlLTEpcmV0dXJuIG4ucHVzaChbZSx0XSksdGhpcy5zaXplPSsrci5zaXplLHRoaXM7cj10aGlzLl9fZGF0YV9fPW5ldyBVV2Uobil9cmV0dXJuIHIuc2V0KGUsdCksdGhpcy5zaXplPXIuc2l6ZSx0aGlzfW1KdC5leHBvcnRzPUdXZX0pO3ZhciBBND1IKChCWG4sX0p0KT0+e3ZhciBXV2U9TTQoKSxZV2U9c1p0KCksaldlPWNadCgpLFhXZT1oWnQoKSwkV2U9cFp0KCksS1dlPWdKdCgpO2Z1bmN0aW9uIFZTKGUpe3ZhciB0PXRoaXMuX19kYXRhX189bmV3IFdXZShlKTt0aGlzLnNpemU9dC5zaXplfVZTLnByb3RvdHlwZS5jbGVhcj1ZV2U7VlMucHJvdG90eXBlLmRlbGV0ZT1qV2U7VlMucHJvdG90eXBlLmdldD1YV2U7VlMucHJvdG90eXBlLmhhcz0kV2U7VlMucHJvdG90eXBlLnNldD1LV2U7X0p0LmV4cG9ydHM9VlN9KTt2YXIgRkI9SCgoSFhuLHlKdCk9PntmdW5jdGlvbiBaV2UoZSx0KXtmb3IodmFyIHI9LTEsbj1lPT1udWxsPzA6ZS5sZW5ndGg7KytyPG4mJnQoZVtyXSxyLGUpIT09ITE7KTtyZXR1cm4gZX15SnQuZXhwb3J0cz1aV2V9KTt2YXIgcHN0PUgoKFZYbix2SnQpPT57dmFyIEpXZT1sMCgpLFFXZT1mdW5jdGlvbigpe3RyeXt2YXIgZT1KV2UoT2JqZWN0LCJkZWZpbmVQcm9wZXJ0eSIpO3JldHVybiBlKHt9LCIiLHt9KSxlfWNhdGNoKHQpe319KCk7dkp0LmV4cG9ydHM9UVdlfSk7dmFyIFA0PUgoKFVYbixiSnQpPT57dmFyIHhKdD1wc3QoKTtmdW5jdGlvbiB0WWUoZSx0LHIpe3Q9PSJfX3Byb3RvX18iJiZ4SnQ/eEp0KGUsdCx7Y29uZmlndXJhYmxlOiEwLGVudW1lcmFibGU6ITAsdmFsdWU6cix3cml0YWJsZTohMH0pOmVbdF09cn1iSnQuZXhwb3J0cz10WWV9KTt2YXIgSTQ9SCgocVhuLHdKdCk9Pnt2YXIgZVllPVA0KCksclllPVkxKCksblllPU9iamVjdC5wcm90b3R5cGUsaVllPW5ZZS5oYXNPd25Qcm9wZXJ0eTtmdW5jdGlvbiBvWWUoZSx0LHIpe3ZhciBuPWVbdF07KCEoaVllLmNhbGwoZSx0KSYmclllKG4scikpfHxyPT09dm9pZCAwJiYhKHQgaW4gZSkpJiZlWWUoZSx0LHIpfXdKdC5leHBvcnRzPW9ZZX0pO3ZhciBVUz1IKChHWG4sU0p0KT0+e3ZhciBhWWU9STQoKSxzWWU9UDQoKTtmdW5jdGlvbiBsWWUoZSx0LHIsbil7dmFyIGk9IXI7cnx8KHI9e30pO2Zvcih2YXIgbz0tMSxhPXQubGVuZ3RoOysrbzxhOyl7dmFyIHM9dFtvXSxsPW4/bihyW3NdLGVbc10scyxyLGUpOnZvaWQgMDtsPT09dm9pZCAwJiYobD1lW3NdKSxpP3NZZShyLHMsbCk6YVllKHIscyxsKX1yZXR1cm4gcn1TSnQuZXhwb3J0cz1sWWV9KTt2YXIgRUp0PUgoKFdYbixNSnQpPT57ZnVuY3Rpb24gY1llKGUsdCl7Zm9yKHZhciByPS0xLG49QXJyYXkoZSk7KytyPGU7KW5bcl09dChyKTtyZXR1cm4gbn1NSnQuZXhwb3J0cz1jWWV9KTt2YXIgWXU9SCgoWVhuLFRKdCk9PntmdW5jdGlvbiB1WWUoZSl7cmV0dXJuIGUhPW51bGwmJnR5cGVvZiBlPT0ib2JqZWN0In1USnQuZXhwb3J0cz11WWV9KTt2YXIgQUp0PUgoKGpYbixDSnQpPT57dmFyIGhZZT1zMCgpLGZZZT1ZdSgpLHBZZT0iW29iamVjdCBBcmd1bWVudHNdIjtmdW5jdGlvbiBkWWUoZSl7cmV0dXJuIGZZZShlKSYmaFllKGUpPT1wWWV9Q0p0LmV4cG9ydHM9ZFllfSk7dmFyIHFTPUgoKFhYbixMSnQpPT57dmFyIFBKdD1BSnQoKSxtWWU9WXUoKSxJSnQ9T2JqZWN0LnByb3RvdHlwZSxnWWU9SUp0Lmhhc093blByb3BlcnR5LF9ZZT1JSnQucHJvcGVydHlJc0VudW1lcmFibGUseVllPVBKdChmdW5jdGlvbigpe3JldHVybiBhcmd1bWVudHN9KCkpP1BKdDpmdW5jdGlvbihlKXtyZXR1cm4gbVllKGUpJiZnWWUuY2FsbChlLCJjYWxsZWUiKSYmIV9ZZS5jYWxsKGUsImNhbGxlZSIpfTtMSnQuZXhwb3J0cz15WWV9KTt2YXIgVGk9SCgoJFhuLGtKdCk9Pnt2YXIgdlllPUFycmF5LmlzQXJyYXk7a0p0LmV4cG9ydHM9dlllfSk7dmFyIE5KdD1IKChLWG4sUkp0KT0+e2Z1bmN0aW9uIHhZZSgpe3JldHVybiExfVJKdC5leHBvcnRzPXhZZX0pO3ZhciBYMT1IKChMNCxHUyk9Pnt2YXIgYlllPUhjKCksd1llPU5KdCgpLHpKdD10eXBlb2YgTDQ9PSJvYmplY3QiJiZMNCYmIUw0Lm5vZGVUeXBlJiZMNCxESnQ9ekp0JiZ0eXBlb2YgR1M9PSJvYmplY3QiJiZHUyYmIUdTLm5vZGVUeXBlJiZHUyxTWWU9REp0JiZESnQuZXhwb3J0cz09PXpKdCxPSnQ9U1llP2JZZS5CdWZmZXI6dm9pZCAwLE1ZZT1PSnQ/T0p0LmlzQnVmZmVyOnZvaWQgMCxFWWU9TVllfHx3WWU7R1MuZXhwb3J0cz1FWWV9KTt2YXIgazQ9SCgoWlhuLEZKdCk9Pnt2YXIgVFllPTkwMDcxOTkyNTQ3NDA5OTEsQ1llPS9eKD86MHxbMS05XVxkKikkLztmdW5jdGlvbiBBWWUoZSx0KXt2YXIgcj10eXBlb2YgZTtyZXR1cm4gdD10PT1udWxsP1RZZTp0LCEhdCYmKHI9PSJudW1iZXIifHxyIT0ic3ltYm9sIiYmQ1llLnRlc3QoZSkpJiZlPi0xJiZlJTE9PTAmJmU8dH1GSnQuZXhwb3J0cz1BWWV9KTt2YXIgQkI9SCgoSlhuLEJKdCk9Pnt2YXIgUFllPTkwMDcxOTkyNTQ3NDA5OTE7ZnVuY3Rpb24gSVllKGUpe3JldHVybiB0eXBlb2YgZT09Im51bWJlciImJmU+LTEmJmUlMT09MCYmZTw9UFllfUJKdC5leHBvcnRzPUlZZX0pO3ZhciBWSnQ9SCgoUVhuLEhKdCk9Pnt2YXIgTFllPXMwKCksa1llPUJCKCksUlllPVl1KCksTlllPSJbb2JqZWN0IEFyZ3VtZW50c10iLERZZT0iW29iamVjdCBBcnJheV0iLE9ZZT0iW29iamVjdCBCb29sZWFuXSIselllPSJbb2JqZWN0IERhdGVdIixGWWU9IltvYmplY3QgRXJyb3JdIixCWWU9IltvYmplY3QgRnVuY3Rpb25dIixIWWU9IltvYmplY3QgTWFwXSIsVlllPSJbb2JqZWN0IE51bWJlcl0iLFVZZT0iW29iamVjdCBPYmplY3RdIixxWWU9IltvYmplY3QgUmVnRXhwXSIsR1llPSJbb2JqZWN0IFNldF0iLFdZZT0iW29iamVjdCBTdHJpbmddIixZWWU9IltvYmplY3QgV2Vha01hcF0iLGpZZT0iW29iamVjdCBBcnJheUJ1ZmZlcl0iLFhZZT0iW29iamVjdCBEYXRhVmlld10iLCRZZT0iW29iamVjdCBGbG9hdDMyQXJyYXldIixLWWU9IltvYmplY3QgRmxvYXQ2NEFycmF5XSIsWlllPSJbb2JqZWN0IEludDhBcnJheV0iLEpZZT0iW29iamVjdCBJbnQxNkFycmF5XSIsUVllPSJbb2JqZWN0IEludDMyQXJyYXldIix0amU9IltvYmplY3QgVWludDhBcnJheV0iLGVqZT0iW29iamVjdCBVaW50OENsYW1wZWRBcnJheV0iLHJqZT0iW29iamVjdCBVaW50MTZBcnJheV0iLG5qZT0iW29iamVjdCBVaW50MzJBcnJheV0iLFVuPXt9O1VuWyRZZV09VW5bS1llXT1VbltaWWVdPVVuW0pZZV09VW5bUVllXT1Vblt0amVdPVVuW2VqZV09VW5bcmplXT1VbltuamVdPSEwO1VuW05ZZV09VW5bRFllXT1VbltqWWVdPVVuW09ZZV09VW5bWFllXT1Vblt6WWVdPVVuW0ZZZV09VW5bQlllXT1VbltIWWVdPVVuW1ZZZV09VW5bVVllXT1VbltxWWVdPVVuW0dZZV09VW5bV1llXT1VbltZWWVdPSExO2Z1bmN0aW9uIGlqZShlKXtyZXR1cm4gUlllKGUpJiZrWWUoZS5sZW5ndGgpJiYhIVVuW0xZZShlKV19SEp0LmV4cG9ydHM9aWplfSk7dmFyIFI0PUgoKHQkbixVSnQpPT57ZnVuY3Rpb24gb2plKGUpe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4gZSh0KX19VUp0LmV4cG9ydHM9b2plfSk7dmFyIEhCPUgoKE40LFdTKT0+e3ZhciBhamU9dXN0KCkscUp0PXR5cGVvZiBOND09Im9iamVjdCImJk40JiYhTjQubm9kZVR5cGUmJk40LEQ0PXFKdCYmdHlwZW9mIFdTPT0ib2JqZWN0IiYmV1MmJiFXUy5ub2RlVHlwZSYmV1Msc2plPUQ0JiZENC5leHBvcnRzPT09cUp0LGRzdD1zamUmJmFqZS5wcm9jZXNzLGxqZT1mdW5jdGlvbigpe3RyeXt2YXIgZT1ENCYmRDQucmVxdWlyZSYmRDQucmVxdWlyZSgidXRpbCIpLnR5cGVzO3JldHVybiBlfHxkc3QmJmRzdC5iaW5kaW5nJiZkc3QuYmluZGluZygidXRpbCIpfWNhdGNoKHQpe319KCk7V1MuZXhwb3J0cz1samV9KTt2YXIgWVM9SCgoZSRuLFlKdCk9Pnt2YXIgY2plPVZKdCgpLHVqZT1SNCgpLEdKdD1IQigpLFdKdD1HSnQmJkdKdC5pc1R5cGVkQXJyYXksaGplPVdKdD91amUoV0p0KTpjamU7WUp0LmV4cG9ydHM9aGplfSk7dmFyIG1zdD1IKChyJG4sakp0KT0+e3ZhciBmamU9RUp0KCkscGplPXFTKCksZGplPVRpKCksbWplPVgxKCksZ2plPWs0KCksX2plPVlTKCkseWplPU9iamVjdC5wcm90b3R5cGUsdmplPXlqZS5oYXNPd25Qcm9wZXJ0eTtmdW5jdGlvbiB4amUoZSx0KXt2YXIgcj1kamUoZSksbj0hciYmcGplKGUpLGk9IXImJiFuJiZtamUoZSksbz0hciYmIW4mJiFpJiZfamUoZSksYT1yfHxufHxpfHxvLHM9YT9mamUoZS5sZW5ndGgsU3RyaW5nKTpbXSxsPXMubGVuZ3RoO2Zvcih2YXIgYyBpbiBlKSh0fHx2amUuY2FsbChlLGMpKSYmIShhJiYoYz09Imxlbmd0aCJ8fGkmJihjPT0ib2Zmc2V0Inx8Yz09InBhcmVudCIpfHxvJiYoYz09ImJ1ZmZlciJ8fGM9PSJieXRlTGVuZ3RoInx8Yz09ImJ5dGVPZmZzZXQiKXx8Z2plKGMsbCkpKSYmcy5wdXNoKGMpO3JldHVybiBzfWpKdC5leHBvcnRzPXhqZX0pO3ZhciBPND1IKChuJG4sWEp0KT0+e3ZhciBiamU9T2JqZWN0LnByb3RvdHlwZTtmdW5jdGlvbiB3amUoZSl7dmFyIHQ9ZSYmZS5jb25zdHJ1Y3RvcixyPXR5cGVvZiB0PT0iZnVuY3Rpb24iJiZ0LnByb3RvdHlwZXx8YmplO3JldHVybiBlPT09cn1YSnQuZXhwb3J0cz13amV9KTt2YXIgZ3N0PUgoKGkkbiwkSnQpPT57ZnVuY3Rpb24gU2plKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKHIpe3JldHVybiBlKHQocikpfX0kSnQuZXhwb3J0cz1TamV9KTt2YXIgWkp0PUgoKG8kbixLSnQpPT57dmFyIE1qZT1nc3QoKSxFamU9TWplKE9iamVjdC5rZXlzLE9iamVjdCk7S0p0LmV4cG9ydHM9RWplfSk7dmFyIFZCPUgoKGEkbixKSnQpPT57dmFyIFRqZT1PNCgpLENqZT1aSnQoKSxBamU9T2JqZWN0LnByb3RvdHlwZSxQamU9QWplLmhhc093blByb3BlcnR5O2Z1bmN0aW9uIElqZShlKXtpZighVGplKGUpKXJldHVybiBDamUoZSk7dmFyIHQ9W107Zm9yKHZhciByIGluIE9iamVjdChlKSlQamUuY2FsbChlLHIpJiZyIT0iY29uc3RydWN0b3IiJiZ0LnB1c2gocik7cmV0dXJuIHR9Skp0LmV4cG9ydHM9SWplfSk7dmFyIEJmPUgoKHMkbixRSnQpPT57dmFyIExqZT1GUygpLGtqZT1CQigpO2Z1bmN0aW9uIFJqZShlKXtyZXR1cm4gZSE9bnVsbCYma2plKGUubGVuZ3RoKSYmIUxqZShlKX1RSnQuZXhwb3J0cz1SamV9KTt2YXIgQWQ9SCgobCRuLHRRdCk9Pnt2YXIgTmplPW1zdCgpLERqZT1WQigpLE9qZT1CZigpO2Z1bmN0aW9uIHpqZShlKXtyZXR1cm4gT2plKGUpP05qZShlKTpEamUoZSl9dFF0LmV4cG9ydHM9emplfSk7dmFyIHJRdD1IKChjJG4sZVF0KT0+e3ZhciBGamU9VVMoKSxCamU9QWQoKTtmdW5jdGlvbiBIamUoZSx0KXtyZXR1cm4gZSYmRmplKHQsQmplKHQpLGUpfWVRdC5leHBvcnRzPUhqZX0pO3ZhciBpUXQ9SCgodSRuLG5RdCk9PntmdW5jdGlvbiBWamUoZSl7dmFyIHQ9W107aWYoZSE9bnVsbClmb3IodmFyIHIgaW4gT2JqZWN0KGUpKXQucHVzaChyKTtyZXR1cm4gdH1uUXQuZXhwb3J0cz1WamV9KTt2YXIgYVF0PUgoKGgkbixvUXQpPT57dmFyIFVqZT1NbCgpLHFqZT1PNCgpLEdqZT1pUXQoKSxXamU9T2JqZWN0LnByb3RvdHlwZSxZamU9V2plLmhhc093blByb3BlcnR5O2Z1bmN0aW9uIGpqZShlKXtpZighVWplKGUpKXJldHVybiBHamUoZSk7dmFyIHQ9cWplKGUpLHI9W107Zm9yKHZhciBuIGluIGUpbj09ImNvbnN0cnVjdG9yIiYmKHR8fCFZamUuY2FsbChlLG4pKXx8ci5wdXNoKG4pO3JldHVybiByfW9RdC5leHBvcnRzPWpqZX0pO3ZhciBjMD1IKChmJG4sc1F0KT0+e3ZhciBYamU9bXN0KCksJGplPWFRdCgpLEtqZT1CZigpO2Z1bmN0aW9uIFpqZShlKXtyZXR1cm4gS2plKGUpP1hqZShlLCEwKTokamUoZSl9c1F0LmV4cG9ydHM9WmplfSk7dmFyIGNRdD1IKChwJG4sbFF0KT0+e3ZhciBKamU9VVMoKSxRamU9YzAoKTtmdW5jdGlvbiB0WGUoZSx0KXtyZXR1cm4gZSYmSmplKHQsUWplKHQpLGUpfWxRdC5leHBvcnRzPXRYZX0pO3ZhciBfc3Q9SCgoejQsalMpPT57dmFyIGVYZT1IYygpLHBRdD10eXBlb2YgejQ9PSJvYmplY3QiJiZ6NCYmIXo0Lm5vZGVUeXBlJiZ6NCx1UXQ9cFF0JiZ0eXBlb2YgalM9PSJvYmplY3QiJiZqUyYmIWpTLm5vZGVUeXBlJiZqUyxyWGU9dVF0JiZ1UXQuZXhwb3J0cz09PXBRdCxoUXQ9clhlP2VYZS5CdWZmZXI6dm9pZCAwLGZRdD1oUXQ/aFF0LmFsbG9jVW5zYWZlOnZvaWQgMDtmdW5jdGlvbiBuWGUoZSx0KXtpZih0KXJldHVybiBlLnNsaWNlKCk7dmFyIHI9ZS5sZW5ndGgsbj1mUXQ/ZlF0KHIpOm5ldyBlLmNvbnN0cnVjdG9yKHIpO3JldHVybiBlLmNvcHkobiksbn1qUy5leHBvcnRzPW5YZX0pO3ZhciB5c3Q9SCgoZCRuLGRRdCk9PntmdW5jdGlvbiBpWGUoZSx0KXt2YXIgcj0tMSxuPWUubGVuZ3RoO2Zvcih0fHwodD1BcnJheShuKSk7KytyPG47KXRbcl09ZVtyXTtyZXR1cm4gdH1kUXQuZXhwb3J0cz1pWGV9KTt2YXIgdnN0PUgoKG0kbixtUXQpPT57ZnVuY3Rpb24gb1hlKGUsdCl7Zm9yKHZhciByPS0xLG49ZT09bnVsbD8wOmUubGVuZ3RoLGk9MCxvPVtdOysrcjxuOyl7dmFyIGE9ZVtyXTt0KGEscixlKSYmKG9baSsrXT1hKX1yZXR1cm4gb31tUXQuZXhwb3J0cz1vWGV9KTt2YXIgeHN0PUgoKGckbixnUXQpPT57ZnVuY3Rpb24gYVhlKCl7cmV0dXJuW119Z1F0LmV4cG9ydHM9YVhlfSk7dmFyIFVCPUgoKF8kbix5UXQpPT57dmFyIHNYZT12c3QoKSxsWGU9eHN0KCksY1hlPU9iamVjdC5wcm90b3R5cGUsdVhlPWNYZS5wcm9wZXJ0eUlzRW51bWVyYWJsZSxfUXQ9T2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scyxoWGU9X1F0P2Z1bmN0aW9uKGUpe3JldHVybiBlPT1udWxsP1tdOihlPU9iamVjdChlKSxzWGUoX1F0KGUpLGZ1bmN0aW9uKHQpe3JldHVybiB1WGUuY2FsbChlLHQpfSkpfTpsWGU7eVF0LmV4cG9ydHM9aFhlfSk7dmFyIHhRdD1IKCh5JG4sdlF0KT0+e3ZhciBmWGU9VVMoKSxwWGU9VUIoKTtmdW5jdGlvbiBkWGUoZSx0KXtyZXR1cm4gZlhlKGUscFhlKGUpLHQpfXZRdC5leHBvcnRzPWRYZX0pO3ZhciBxQj1IKCh2JG4sYlF0KT0+e2Z1bmN0aW9uIG1YZShlLHQpe2Zvcih2YXIgcj0tMSxuPXQubGVuZ3RoLGk9ZS5sZW5ndGg7KytyPG47KWVbaStyXT10W3JdO3JldHVybiBlfWJRdC5leHBvcnRzPW1YZX0pO3ZhciBGND1IKCh4JG4sd1F0KT0+e3ZhciBnWGU9Z3N0KCksX1hlPWdYZShPYmplY3QuZ2V0UHJvdG90eXBlT2YsT2JqZWN0KTt3UXQuZXhwb3J0cz1fWGV9KTt2YXIgYnN0PUgoKGIkbixTUXQpPT57dmFyIHlYZT1xQigpLHZYZT1GNCgpLHhYZT1VQigpLGJYZT14c3QoKSx3WGU9T2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scyxTWGU9d1hlP2Z1bmN0aW9uKGUpe2Zvcih2YXIgdD1bXTtlOyl5WGUodCx4WGUoZSkpLGU9dlhlKGUpO3JldHVybiB0fTpiWGU7U1F0LmV4cG9ydHM9U1hlfSk7dmFyIEVRdD1IKCh3JG4sTVF0KT0+e3ZhciBNWGU9VVMoKSxFWGU9YnN0KCk7ZnVuY3Rpb24gVFhlKGUsdCl7cmV0dXJuIE1YZShlLEVYZShlKSx0KX1NUXQuZXhwb3J0cz1UWGV9KTt2YXIgd3N0PUgoKFMkbixUUXQpPT57dmFyIENYZT1xQigpLEFYZT1UaSgpO2Z1bmN0aW9uIFBYZShlLHQscil7dmFyIG49dChlKTtyZXR1cm4gQVhlKGUpP246Q1hlKG4scihlKSl9VFF0LmV4cG9ydHM9UFhlfSk7dmFyIFNzdD1IKChNJG4sQ1F0KT0+e3ZhciBJWGU9d3N0KCksTFhlPVVCKCksa1hlPUFkKCk7ZnVuY3Rpb24gUlhlKGUpe3JldHVybiBJWGUoZSxrWGUsTFhlKX1DUXQuZXhwb3J0cz1SWGV9KTt2YXIgUFF0PUgoKEUkbixBUXQpPT57dmFyIE5YZT13c3QoKSxEWGU9YnN0KCksT1hlPWMwKCk7ZnVuY3Rpb24gelhlKGUpe3JldHVybiBOWGUoZSxPWGUsRFhlKX1BUXQuZXhwb3J0cz16WGV9KTt2YXIgTFF0PUgoKFQkbixJUXQpPT57dmFyIEZYZT1sMCgpLEJYZT1IYygpLEhYZT1GWGUoQlhlLCJEYXRhVmlldyIpO0lRdC5leHBvcnRzPUhYZX0pO3ZhciBSUXQ9SCgoQyRuLGtRdCk9Pnt2YXIgVlhlPWwwKCksVVhlPUhjKCkscVhlPVZYZShVWGUsIlByb21pc2UiKTtrUXQuZXhwb3J0cz1xWGV9KTt2YXIgTXN0PUgoKEEkbixOUXQpPT57dmFyIEdYZT1sMCgpLFdYZT1IYygpLFlYZT1HWGUoV1hlLCJTZXQiKTtOUXQuZXhwb3J0cz1ZWGV9KTt2YXIgT1F0PUgoKFAkbixEUXQpPT57dmFyIGpYZT1sMCgpLFhYZT1IYygpLCRYZT1qWGUoWFhlLCJXZWFrTWFwIik7RFF0LmV4cG9ydHM9JFhlfSk7dmFyIEsxPUgoKEkkbixxUXQpPT57dmFyIEVzdD1MUXQoKSxUc3Q9T0IoKSxDc3Q9UlF0KCksQXN0PU1zdCgpLFBzdD1PUXQoKSxVUXQ9czAoKSxYUz1mc3QoKSx6UXQ9IltvYmplY3QgTWFwXSIsS1hlPSJbb2JqZWN0IE9iamVjdF0iLEZRdD0iW29iamVjdCBQcm9taXNlXSIsQlF0PSJbb2JqZWN0IFNldF0iLEhRdD0iW29iamVjdCBXZWFrTWFwXSIsVlF0PSJbb2JqZWN0IERhdGFWaWV3XSIsWlhlPVhTKEVzdCksSlhlPVhTKFRzdCksUVhlPVhTKENzdCksdCRlPVhTKEFzdCksZSRlPVhTKFBzdCksJDE9VVF0OyhFc3QmJiQxKG5ldyBFc3QobmV3IEFycmF5QnVmZmVyKDEpKSkhPVZRdHx8VHN0JiYkMShuZXcgVHN0KSE9elF0fHxDc3QmJiQxKENzdC5yZXNvbHZlKCkpIT1GUXR8fEFzdCYmJDEobmV3IEFzdCkhPUJRdHx8UHN0JiYkMShuZXcgUHN0KSE9SFF0KSYmKCQxPWZ1bmN0aW9uKGUpe3ZhciB0PVVRdChlKSxyPXQ9PUtYZT9lLmNvbnN0cnVjdG9yOnZvaWQgMCxuPXI/WFMocik6IiI7aWYobilzd2l0Y2gobil7Y2FzZSBaWGU6cmV0dXJuIFZRdDtjYXNlIEpYZTpyZXR1cm4gelF0O2Nhc2UgUVhlOnJldHVybiBGUXQ7Y2FzZSB0JGU6cmV0dXJuIEJRdDtjYXNlIGUkZTpyZXR1cm4gSFF0fXJldHVybiB0fSk7cVF0LmV4cG9ydHM9JDF9KTt2YXIgV1F0PUgoKEwkbixHUXQpPT57dmFyIHIkZT1PYmplY3QucHJvdG90eXBlLG4kZT1yJGUuaGFzT3duUHJvcGVydHk7ZnVuY3Rpb24gaSRlKGUpe3ZhciB0PWUubGVuZ3RoLHI9bmV3IGUuY29uc3RydWN0b3IodCk7cmV0dXJuIHQmJnR5cGVvZiBlWzBdPT0ic3RyaW5nIiYmbiRlLmNhbGwoZSwiaW5kZXgiKSYmKHIuaW5kZXg9ZS5pbmRleCxyLmlucHV0PWUuaW5wdXQpLHJ9R1F0LmV4cG9ydHM9aSRlfSk7dmFyIElzdD1IKChrJG4sWVF0KT0+e3ZhciBvJGU9SGMoKSxhJGU9byRlLlVpbnQ4QXJyYXk7WVF0LmV4cG9ydHM9YSRlfSk7dmFyIEdCPUgoKFIkbixYUXQpPT57dmFyIGpRdD1Jc3QoKTtmdW5jdGlvbiBzJGUoZSl7dmFyIHQ9bmV3IGUuY29uc3RydWN0b3IoZS5ieXRlTGVuZ3RoKTtyZXR1cm4gbmV3IGpRdCh0KS5zZXQobmV3IGpRdChlKSksdH1YUXQuZXhwb3J0cz1zJGV9KTt2YXIgS1F0PUgoKE4kbiwkUXQpPT57dmFyIGwkZT1HQigpO2Z1bmN0aW9uIGMkZShlLHQpe3ZhciByPXQ/bCRlKGUuYnVmZmVyKTplLmJ1ZmZlcjtyZXR1cm4gbmV3IGUuY29uc3RydWN0b3IocixlLmJ5dGVPZmZzZXQsZS5ieXRlTGVuZ3RoKX0kUXQuZXhwb3J0cz1jJGV9KTt2YXIgSlF0PUgoKEQkbixaUXQpPT57dmFyIHUkZT0vXHcqJC87ZnVuY3Rpb24gaCRlKGUpe3ZhciB0PW5ldyBlLmNvbnN0cnVjdG9yKGUuc291cmNlLHUkZS5leGVjKGUpKTtyZXR1cm4gdC5sYXN0SW5kZXg9ZS5sYXN0SW5kZXgsdH1aUXQuZXhwb3J0cz1oJGV9KTt2YXIgbnRlPUgoKE8kbixydGUpPT57dmFyIFFRdD1qMSgpLHR0ZT1RUXQ/UVF0LnByb3RvdHlwZTp2b2lkIDAsZXRlPXR0ZT90dGUudmFsdWVPZjp2b2lkIDA7ZnVuY3Rpb24gZiRlKGUpe3JldHVybiBldGU/T2JqZWN0KGV0ZS5jYWxsKGUpKTp7fX1ydGUuZXhwb3J0cz1mJGV9KTt2YXIgTHN0PUgoKHokbixpdGUpPT57dmFyIHAkZT1HQigpO2Z1bmN0aW9uIGQkZShlLHQpe3ZhciByPXQ/cCRlKGUuYnVmZmVyKTplLmJ1ZmZlcjtyZXR1cm4gbmV3IGUuY29uc3RydWN0b3IocixlLmJ5dGVPZmZzZXQsZS5sZW5ndGgpfWl0ZS5leHBvcnRzPWQkZX0pO3ZhciBhdGU9SCgoRiRuLG90ZSk9Pnt2YXIgbSRlPUdCKCksZyRlPUtRdCgpLF8kZT1KUXQoKSx5JGU9bnRlKCksdiRlPUxzdCgpLHgkZT0iW29iamVjdCBCb29sZWFuXSIsYiRlPSJbb2JqZWN0IERhdGVdIix3JGU9IltvYmplY3QgTWFwXSIsUyRlPSJbb2JqZWN0IE51bWJlcl0iLE0kZT0iW29iamVjdCBSZWdFeHBdIixFJGU9IltvYmplY3QgU2V0XSIsVCRlPSJbb2JqZWN0IFN0cmluZ10iLEMkZT0iW29iamVjdCBTeW1ib2xdIixBJGU9IltvYmplY3QgQXJyYXlCdWZmZXJdIixQJGU9IltvYmplY3QgRGF0YVZpZXddIixJJGU9IltvYmplY3QgRmxvYXQzMkFycmF5XSIsTCRlPSJbb2JqZWN0IEZsb2F0NjRBcnJheV0iLGskZT0iW29iamVjdCBJbnQ4QXJyYXldIixSJGU9IltvYmplY3QgSW50MTZBcnJheV0iLE4kZT0iW29iamVjdCBJbnQzMkFycmF5XSIsRCRlPSJbb2JqZWN0IFVpbnQ4QXJyYXldIixPJGU9IltvYmplY3QgVWludDhDbGFtcGVkQXJyYXldIix6JGU9IltvYmplY3QgVWludDE2QXJyYXldIixGJGU9IltvYmplY3QgVWludDMyQXJyYXldIjtmdW5jdGlvbiBCJGUoZSx0LHIpe3ZhciBuPWUuY29uc3RydWN0b3I7c3dpdGNoKHQpe2Nhc2UgQSRlOnJldHVybiBtJGUoZSk7Y2FzZSB4JGU6Y2FzZSBiJGU6cmV0dXJuIG5ldyBuKCtlKTtjYXNlIFAkZTpyZXR1cm4gZyRlKGUscik7Y2FzZSBJJGU6Y2FzZSBMJGU6Y2FzZSBrJGU6Y2FzZSBSJGU6Y2FzZSBOJGU6Y2FzZSBEJGU6Y2FzZSBPJGU6Y2FzZSB6JGU6Y2FzZSBGJGU6cmV0dXJuIHYkZShlLHIpO2Nhc2UgdyRlOnJldHVybiBuZXcgbjtjYXNlIFMkZTpjYXNlIFQkZTpyZXR1cm4gbmV3IG4oZSk7Y2FzZSBNJGU6cmV0dXJuIF8kZShlKTtjYXNlIEUkZTpyZXR1cm4gbmV3IG47Y2FzZSBDJGU6cmV0dXJuIHkkZShlKX19b3RlLmV4cG9ydHM9QiRlfSk7dmFyIGtzdD1IKChCJG4sbHRlKT0+e3ZhciBIJGU9TWwoKSxzdGU9T2JqZWN0LmNyZWF0ZSxWJGU9ZnVuY3Rpb24oKXtmdW5jdGlvbiBlKCl7fXJldHVybiBmdW5jdGlvbih0KXtpZighSCRlKHQpKXJldHVybnt9O2lmKHN0ZSlyZXR1cm4gc3RlKHQpO2UucHJvdG90eXBlPXQ7dmFyIHI9bmV3IGU7cmV0dXJuIGUucHJvdG90eXBlPXZvaWQgMCxyfX0oKTtsdGUuZXhwb3J0cz1WJGV9KTt2YXIgUnN0PUgoKEgkbixjdGUpPT57dmFyIFUkZT1rc3QoKSxxJGU9RjQoKSxHJGU9TzQoKTtmdW5jdGlvbiBXJGUoZSl7cmV0dXJuIHR5cGVvZiBlLmNvbnN0cnVjdG9yPT0iZnVuY3Rpb24iJiYhRyRlKGUpP1UkZShxJGUoZSkpOnt9fWN0ZS5leHBvcnRzPVckZX0pO3ZhciBodGU9SCgoViRuLHV0ZSk9Pnt2YXIgWSRlPUsxKCksaiRlPVl1KCksWCRlPSJbb2JqZWN0IE1hcF0iO2Z1bmN0aW9uICQkZShlKXtyZXR1cm4gaiRlKGUpJiZZJGUoZSk9PVgkZX11dGUuZXhwb3J0cz0kJGV9KTt2YXIgbXRlPUgoKFUkbixkdGUpPT57dmFyIEskZT1odGUoKSxaJGU9UjQoKSxmdGU9SEIoKSxwdGU9ZnRlJiZmdGUuaXNNYXAsSiRlPXB0ZT9aJGUocHRlKTpLJGU7ZHRlLmV4cG9ydHM9SiRlfSk7dmFyIF90ZT1IKChxJG4sZ3RlKT0+e3ZhciBRJGU9SzEoKSx0S2U9WXUoKSxlS2U9IltvYmplY3QgU2V0XSI7ZnVuY3Rpb24gcktlKGUpe3JldHVybiB0S2UoZSkmJlEkZShlKT09ZUtlfWd0ZS5leHBvcnRzPXJLZX0pO3ZhciBidGU9SCgoRyRuLHh0ZSk9Pnt2YXIgbktlPV90ZSgpLGlLZT1SNCgpLHl0ZT1IQigpLHZ0ZT15dGUmJnl0ZS5pc1NldCxvS2U9dnRlP2lLZSh2dGUpOm5LZTt4dGUuZXhwb3J0cz1vS2V9KTt2YXIgTnN0PUgoKFckbixFdGUpPT57dmFyIGFLZT1BNCgpLHNLZT1GQigpLGxLZT1JNCgpLGNLZT1yUXQoKSx1S2U9Y1F0KCksaEtlPV9zdCgpLGZLZT15c3QoKSxwS2U9eFF0KCksZEtlPUVRdCgpLG1LZT1Tc3QoKSxnS2U9UFF0KCksX0tlPUsxKCkseUtlPVdRdCgpLHZLZT1hdGUoKSx4S2U9UnN0KCksYktlPVRpKCksd0tlPVgxKCksU0tlPW10ZSgpLE1LZT1NbCgpLEVLZT1idGUoKSxUS2U9QWQoKSxDS2U9YzAoKSxBS2U9MSxQS2U9MixJS2U9NCx3dGU9IltvYmplY3QgQXJndW1lbnRzXSIsTEtlPSJbb2JqZWN0IEFycmF5XSIsa0tlPSJbb2JqZWN0IEJvb2xlYW5dIixSS2U9IltvYmplY3QgRGF0ZV0iLE5LZT0iW29iamVjdCBFcnJvcl0iLFN0ZT0iW29iamVjdCBGdW5jdGlvbl0iLERLZT0iW29iamVjdCBHZW5lcmF0b3JGdW5jdGlvbl0iLE9LZT0iW29iamVjdCBNYXBdIix6S2U9IltvYmplY3QgTnVtYmVyXSIsTXRlPSJbb2JqZWN0IE9iamVjdF0iLEZLZT0iW29iamVjdCBSZWdFeHBdIixCS2U9IltvYmplY3QgU2V0XSIsSEtlPSJbb2JqZWN0IFN0cmluZ10iLFZLZT0iW29iamVjdCBTeW1ib2xdIixVS2U9IltvYmplY3QgV2Vha01hcF0iLHFLZT0iW29iamVjdCBBcnJheUJ1ZmZlcl0iLEdLZT0iW29iamVjdCBEYXRhVmlld10iLFdLZT0iW29iamVjdCBGbG9hdDMyQXJyYXldIixZS2U9IltvYmplY3QgRmxvYXQ2NEFycmF5XSIsaktlPSJbb2JqZWN0IEludDhBcnJheV0iLFhLZT0iW29iamVjdCBJbnQxNkFycmF5XSIsJEtlPSJbb2JqZWN0IEludDMyQXJyYXldIixLS2U9IltvYmplY3QgVWludDhBcnJheV0iLFpLZT0iW29iamVjdCBVaW50OENsYW1wZWRBcnJheV0iLEpLZT0iW29iamVjdCBVaW50MTZBcnJheV0iLFFLZT0iW29iamVjdCBVaW50MzJBcnJheV0iLEFuPXt9O0FuW3d0ZV09QW5bTEtlXT1BbltxS2VdPUFuW0dLZV09QW5ba0tlXT1BbltSS2VdPUFuW1dLZV09QW5bWUtlXT1BbltqS2VdPUFuW1hLZV09QW5bJEtlXT1BbltPS2VdPUFuW3pLZV09QW5bTXRlXT1BbltGS2VdPUFuW0JLZV09QW5bSEtlXT1BbltWS2VdPUFuW0tLZV09QW5bWktlXT1BbltKS2VdPUFuW1FLZV09ITA7QW5bTktlXT1BbltTdGVdPUFuW1VLZV09ITE7ZnVuY3Rpb24gV0IoZSx0LHIsbixpLG8pe3ZhciBhLHM9dCZBS2UsbD10JlBLZSxjPXQmSUtlO2lmKHImJihhPWk/cihlLG4saSxvKTpyKGUpKSxhIT09dm9pZCAwKXJldHVybiBhO2lmKCFNS2UoZSkpcmV0dXJuIGU7dmFyIHU9YktlKGUpO2lmKHUpe2lmKGE9eUtlKGUpLCFzKXJldHVybiBmS2UoZSxhKX1lbHNle3ZhciBoPV9LZShlKSxmPWg9PVN0ZXx8aD09REtlO2lmKHdLZShlKSlyZXR1cm4gaEtlKGUscyk7aWYoaD09TXRlfHxoPT13dGV8fGYmJiFpKXtpZihhPWx8fGY/e306eEtlKGUpLCFzKXJldHVybiBsP2RLZShlLHVLZShhLGUpKTpwS2UoZSxjS2UoYSxlKSl9ZWxzZXtpZighQW5baF0pcmV0dXJuIGk/ZTp7fTthPXZLZShlLGgscyl9fW98fChvPW5ldyBhS2UpO3ZhciBwPW8uZ2V0KGUpO2lmKHApcmV0dXJuIHA7by5zZXQoZSxhKSxFS2UoZSk/ZS5mb3JFYWNoKGZ1bmN0aW9uKF8pe2EuYWRkKFdCKF8sdCxyLF8sZSxvKSl9KTpTS2UoZSkmJmUuZm9yRWFjaChmdW5jdGlvbihfLHkpe2Euc2V0KHksV0IoXyx0LHIseSxlLG8pKX0pO3ZhciBkPWM/bD9nS2U6bUtlOmw/Q0tlOlRLZSxnPXU/dm9pZCAwOmQoZSk7cmV0dXJuIHNLZShnfHxlLGZ1bmN0aW9uKF8seSl7ZyYmKHk9XyxfPWVbeV0pLGxLZShhLHksV0IoXyx0LHIseSxlLG8pKX0pLGF9RXRlLmV4cG9ydHM9V0J9KTt2YXIgQ3RlPUgoKFkkbixUdGUpPT57dmFyIHRaZT1Oc3QoKSxlWmU9NDtmdW5jdGlvbiByWmUoZSl7cmV0dXJuIHRaZShlLGVaZSl9VHRlLmV4cG9ydHM9clplfSk7dmFyIFlCPUgoKGokbixBdGUpPT57ZnVuY3Rpb24gblplKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX1BdGUuZXhwb3J0cz1uWmV9KTt2YXIgSXRlPUgoKFgkbixQdGUpPT57ZnVuY3Rpb24gaVplKGUpe3JldHVybiBmdW5jdGlvbih0LHIsbil7Zm9yKHZhciBpPS0xLG89T2JqZWN0KHQpLGE9bih0KSxzPWEubGVuZ3RoO3MtLTspe3ZhciBsPWFbZT9zOisraV07aWYocihvW2xdLGwsbyk9PT0hMSlicmVha31yZXR1cm4gdH19UHRlLmV4cG9ydHM9aVplfSk7dmFyIGpCPUgoKCQkbixMdGUpPT57dmFyIG9aZT1JdGUoKSxhWmU9b1plKCk7THRlLmV4cG9ydHM9YVplfSk7dmFyIFhCPUgoKEskbixrdGUpPT57dmFyIHNaZT1qQigpLGxaZT1BZCgpO2Z1bmN0aW9uIGNaZShlLHQpe3JldHVybiBlJiZzWmUoZSx0LGxaZSl9a3RlLmV4cG9ydHM9Y1plfSk7dmFyIE50ZT1IKChaJG4sUnRlKT0+e3ZhciB1WmU9QmYoKTtmdW5jdGlvbiBoWmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocixuKXtpZihyPT1udWxsKXJldHVybiByO2lmKCF1WmUocikpcmV0dXJuIGUocixuKTtmb3IodmFyIGk9ci5sZW5ndGgsbz10P2k6LTEsYT1PYmplY3Qocik7KHQ/by0tOisrbzxpKSYmbihhW29dLG8sYSkhPT0hMTspO3JldHVybiByfX1SdGUuZXhwb3J0cz1oWmV9KTt2YXIgQjQ9SCgoSiRuLER0ZSk9Pnt2YXIgZlplPVhCKCkscFplPU50ZSgpLGRaZT1wWmUoZlplKTtEdGUuZXhwb3J0cz1kWmV9KTt2YXIgdTA9SCgoUSRuLE90ZSk9PntmdW5jdGlvbiBtWmUoZSl7cmV0dXJuIGV9T3RlLmV4cG9ydHM9bVplfSk7dmFyIERzdD1IKCh0S24senRlKT0+e3ZhciBnWmU9dTAoKTtmdW5jdGlvbiBfWmUoZSl7cmV0dXJuIHR5cGVvZiBlPT0iZnVuY3Rpb24iP2U6Z1plfXp0ZS5leHBvcnRzPV9aZX0pO3ZhciBPc3Q9SCgoZUtuLEZ0ZSk9Pnt2YXIgeVplPUZCKCksdlplPUI0KCkseFplPURzdCgpLGJaZT1UaSgpO2Z1bmN0aW9uIHdaZShlLHQpe3ZhciByPWJaZShlKT95WmU6dlplO3JldHVybiByKGUseFplKHQpKX1GdGUuZXhwb3J0cz13WmV9KTt2YXIgenN0PUgoKHJLbixCdGUpPT57QnRlLmV4cG9ydHM9T3N0KCl9KTt2YXIgVnRlPUgoKG5LbixIdGUpPT57dmFyIFNaZT1CNCgpO2Z1bmN0aW9uIE1aZShlLHQpe3ZhciByPVtdO3JldHVybiBTWmUoZSxmdW5jdGlvbihuLGksbyl7dChuLGksbykmJnIucHVzaChuKX0pLHJ9SHRlLmV4cG9ydHM9TVplfSk7dmFyIHF0ZT1IKChpS24sVXRlKT0+e3ZhciBFWmU9Il9fbG9kYXNoX2hhc2hfdW5kZWZpbmVkX18iO2Z1bmN0aW9uIFRaZShlKXtyZXR1cm4gdGhpcy5fX2RhdGFfXy5zZXQoZSxFWmUpLHRoaXN9VXRlLmV4cG9ydHM9VFplfSk7dmFyIFd0ZT1IKChvS24sR3RlKT0+e2Z1bmN0aW9uIENaZShlKXtyZXR1cm4gdGhpcy5fX2RhdGFfXy5oYXMoZSl9R3RlLmV4cG9ydHM9Q1plfSk7dmFyIEZzdD1IKChhS24sWXRlKT0+e3ZhciBBWmU9ekIoKSxQWmU9cXRlKCksSVplPVd0ZSgpO2Z1bmN0aW9uICRCKGUpe3ZhciB0PS0xLHI9ZT09bnVsbD8wOmUubGVuZ3RoO2Zvcih0aGlzLl9fZGF0YV9fPW5ldyBBWmU7Kyt0PHI7KXRoaXMuYWRkKGVbdF0pfSRCLnByb3RvdHlwZS5hZGQ9JEIucHJvdG90eXBlLnB1c2g9UFplOyRCLnByb3RvdHlwZS5oYXM9SVplO1l0ZS5leHBvcnRzPSRCfSk7dmFyIFh0ZT1IKChzS24sanRlKT0+e2Z1bmN0aW9uIExaZShlLHQpe2Zvcih2YXIgcj0tMSxuPWU9PW51bGw/MDplLmxlbmd0aDsrK3I8bjspaWYodChlW3JdLHIsZSkpcmV0dXJuITA7cmV0dXJuITF9anRlLmV4cG9ydHM9TFplfSk7dmFyIEJzdD1IKChsS24sJHRlKT0+e2Z1bmN0aW9uIGtaZShlLHQpe3JldHVybiBlLmhhcyh0KX0kdGUuZXhwb3J0cz1rWmV9KTt2YXIgSHN0PUgoKGNLbixLdGUpPT57dmFyIFJaZT1Gc3QoKSxOWmU9WHRlKCksRFplPUJzdCgpLE9aZT0xLHpaZT0yO2Z1bmN0aW9uIEZaZShlLHQscixuLGksbyl7dmFyIGE9ciZPWmUscz1lLmxlbmd0aCxsPXQubGVuZ3RoO2lmKHMhPWwmJiEoYSYmbD5zKSlyZXR1cm4hMTt2YXIgYz1vLmdldChlKSx1PW8uZ2V0KHQpO2lmKGMmJnUpcmV0dXJuIGM9PXQmJnU9PWU7dmFyIGg9LTEsZj0hMCxwPXImelplP25ldyBSWmU6dm9pZCAwO2ZvcihvLnNldChlLHQpLG8uc2V0KHQsZSk7KytoPHM7KXt2YXIgZD1lW2hdLGc9dFtoXTtpZihuKXZhciBfPWE/bihnLGQsaCx0LGUsbyk6bihkLGcsaCxlLHQsbyk7aWYoXyE9PXZvaWQgMCl7aWYoXyljb250aW51ZTtmPSExO2JyZWFrfWlmKHApe2lmKCFOWmUodCxmdW5jdGlvbih5LHgpe2lmKCFEWmUocCx4KSYmKGQ9PT15fHxpKGQseSxyLG4sbykpKXJldHVybiBwLnB1c2goeCl9KSl7Zj0hMTticmVha319ZWxzZSBpZighKGQ9PT1nfHxpKGQsZyxyLG4sbykpKXtmPSExO2JyZWFrfX1yZXR1cm4gby5kZWxldGUoZSksby5kZWxldGUodCksZn1LdGUuZXhwb3J0cz1GWmV9KTt2YXIgSnRlPUgoKHVLbixadGUpPT57ZnVuY3Rpb24gQlplKGUpe3ZhciB0PS0xLHI9QXJyYXkoZS5zaXplKTtyZXR1cm4gZS5mb3JFYWNoKGZ1bmN0aW9uKG4saSl7clsrK3RdPVtpLG5dfSkscn1adGUuZXhwb3J0cz1CWmV9KTt2YXIgS0I9SCgoaEtuLFF0ZSk9PntmdW5jdGlvbiBIWmUoZSl7dmFyIHQ9LTEscj1BcnJheShlLnNpemUpO3JldHVybiBlLmZvckVhY2goZnVuY3Rpb24obil7clsrK3RdPW59KSxyfVF0ZS5leHBvcnRzPUhaZX0pO3ZhciBpZWU9SCgoZktuLG5lZSk9Pnt2YXIgdGVlPWoxKCksZWVlPUlzdCgpLFZaZT1ZMSgpLFVaZT1Ic3QoKSxxWmU9SnRlKCksR1plPUtCKCksV1plPTEsWVplPTIsalplPSJbb2JqZWN0IEJvb2xlYW5dIixYWmU9IltvYmplY3QgRGF0ZV0iLCRaZT0iW29iamVjdCBFcnJvcl0iLEtaZT0iW29iamVjdCBNYXBdIixaWmU9IltvYmplY3QgTnVtYmVyXSIsSlplPSJbb2JqZWN0IFJlZ0V4cF0iLFFaZT0iW29iamVjdCBTZXRdIix0SmU9IltvYmplY3QgU3RyaW5nXSIsZUplPSJbb2JqZWN0IFN5bWJvbF0iLHJKZT0iW29iamVjdCBBcnJheUJ1ZmZlcl0iLG5KZT0iW29iamVjdCBEYXRhVmlld10iLHJlZT10ZWU/dGVlLnByb3RvdHlwZTp2b2lkIDAsVnN0PXJlZT9yZWUudmFsdWVPZjp2b2lkIDA7ZnVuY3Rpb24gaUplKGUsdCxyLG4saSxvLGEpe3N3aXRjaChyKXtjYXNlIG5KZTppZihlLmJ5dGVMZW5ndGghPXQuYnl0ZUxlbmd0aHx8ZS5ieXRlT2Zmc2V0IT10LmJ5dGVPZmZzZXQpcmV0dXJuITE7ZT1lLmJ1ZmZlcix0PXQuYnVmZmVyO2Nhc2UgckplOnJldHVybiEoZS5ieXRlTGVuZ3RoIT10LmJ5dGVMZW5ndGh8fCFvKG5ldyBlZWUoZSksbmV3IGVlZSh0KSkpO2Nhc2UgalplOmNhc2UgWFplOmNhc2UgWlplOnJldHVybiBWWmUoK2UsK3QpO2Nhc2UgJFplOnJldHVybiBlLm5hbWU9PXQubmFtZSYmZS5tZXNzYWdlPT10Lm1lc3NhZ2U7Y2FzZSBKWmU6Y2FzZSB0SmU6cmV0dXJuIGU9PXQrIiI7Y2FzZSBLWmU6dmFyIHM9cVplO2Nhc2UgUVplOnZhciBsPW4mV1plO2lmKHN8fChzPUdaZSksZS5zaXplIT10LnNpemUmJiFsKXJldHVybiExO3ZhciBjPWEuZ2V0KGUpO2lmKGMpcmV0dXJuIGM9PXQ7bnw9WVplLGEuc2V0KGUsdCk7dmFyIHU9VVplKHMoZSkscyh0KSxuLGksbyxhKTtyZXR1cm4gYS5kZWxldGUoZSksdTtjYXNlIGVKZTppZihWc3QpcmV0dXJuIFZzdC5jYWxsKGUpPT1Wc3QuY2FsbCh0KX1yZXR1cm4hMX1uZWUuZXhwb3J0cz1pSmV9KTt2YXIgc2VlPUgoKHBLbixhZWUpPT57dmFyIG9lZT1Tc3QoKSxvSmU9MSxhSmU9T2JqZWN0LnByb3RvdHlwZSxzSmU9YUplLmhhc093blByb3BlcnR5O2Z1bmN0aW9uIGxKZShlLHQscixuLGksbyl7dmFyIGE9ciZvSmUscz1vZWUoZSksbD1zLmxlbmd0aCxjPW9lZSh0KSx1PWMubGVuZ3RoO2lmKGwhPXUmJiFhKXJldHVybiExO2Zvcih2YXIgaD1sO2gtLTspe3ZhciBmPXNbaF07aWYoIShhP2YgaW4gdDpzSmUuY2FsbCh0LGYpKSlyZXR1cm4hMX12YXIgcD1vLmdldChlKSxkPW8uZ2V0KHQpO2lmKHAmJmQpcmV0dXJuIHA9PXQmJmQ9PWU7dmFyIGc9ITA7by5zZXQoZSx0KSxvLnNldCh0LGUpO2Zvcih2YXIgXz1hOysraDxsOyl7Zj1zW2hdO3ZhciB5PWVbZl0seD10W2ZdO2lmKG4pdmFyIGI9YT9uKHgseSxmLHQsZSxvKTpuKHkseCxmLGUsdCxvKTtpZighKGI9PT12b2lkIDA/eT09PXh8fGkoeSx4LHIsbixvKTpiKSl7Zz0hMTticmVha31ffHwoXz1mPT0iY29uc3RydWN0b3IiKX1pZihnJiYhXyl7dmFyIFM9ZS5jb25zdHJ1Y3RvcixDPXQuY29uc3RydWN0b3I7UyE9QyYmImNvbnN0cnVjdG9yImluIGUmJiJjb25zdHJ1Y3RvciJpbiB0JiYhKHR5cGVvZiBTPT0iZnVuY3Rpb24iJiZTIGluc3RhbmNlb2YgUyYmdHlwZW9mIEM9PSJmdW5jdGlvbiImJkMgaW5zdGFuY2VvZiBDKSYmKGc9ITEpfXJldHVybiBvLmRlbGV0ZShlKSxvLmRlbGV0ZSh0KSxnfWFlZS5leHBvcnRzPWxKZX0pO3ZhciBtZWU9SCgoZEtuLGRlZSk9Pnt2YXIgVXN0PUE0KCksY0plPUhzdCgpLHVKZT1pZWUoKSxoSmU9c2VlKCksbGVlPUsxKCksY2VlPVRpKCksdWVlPVgxKCksZkplPVlTKCkscEplPTEsaGVlPSJbb2JqZWN0IEFyZ3VtZW50c10iLGZlZT0iW29iamVjdCBBcnJheV0iLFpCPSJbb2JqZWN0IE9iamVjdF0iLGRKZT1PYmplY3QucHJvdG90eXBlLHBlZT1kSmUuaGFzT3duUHJvcGVydHk7ZnVuY3Rpb24gbUplKGUsdCxyLG4saSxvKXt2YXIgYT1jZWUoZSkscz1jZWUodCksbD1hP2ZlZTpsZWUoZSksYz1zP2ZlZTpsZWUodCk7bD1sPT1oZWU/WkI6bCxjPWM9PWhlZT9aQjpjO3ZhciB1PWw9PVpCLGg9Yz09WkIsZj1sPT1jO2lmKGYmJnVlZShlKSl7aWYoIXVlZSh0KSlyZXR1cm4hMTthPSEwLHU9ITF9aWYoZiYmIXUpcmV0dXJuIG98fChvPW5ldyBVc3QpLGF8fGZKZShlKT9jSmUoZSx0LHIsbixpLG8pOnVKZShlLHQsbCxyLG4saSxvKTtpZighKHImcEplKSl7dmFyIHA9dSYmcGVlLmNhbGwoZSwiX193cmFwcGVkX18iKSxkPWgmJnBlZS5jYWxsKHQsIl9fd3JhcHBlZF9fIik7aWYocHx8ZCl7dmFyIGc9cD9lLnZhbHVlKCk6ZSxfPWQ/dC52YWx1ZSgpOnQ7cmV0dXJuIG98fChvPW5ldyBVc3QpLGkoZyxfLHIsbixvKX19cmV0dXJuIGY/KG98fChvPW5ldyBVc3QpLGhKZShlLHQscixuLGksbykpOiExfWRlZS5leHBvcnRzPW1KZX0pO3ZhciBxc3Q9SCgobUtuLHllZSk9Pnt2YXIgZ0plPW1lZSgpLGdlZT1ZdSgpO2Z1bmN0aW9uIF9lZShlLHQscixuLGkpe3JldHVybiBlPT09dD8hMDplPT1udWxsfHx0PT1udWxsfHwhZ2VlKGUpJiYhZ2VlKHQpP2UhPT1lJiZ0IT09dDpnSmUoZSx0LHIsbixfZWUsaSl9eWVlLmV4cG9ydHM9X2VlfSk7dmFyIHhlZT1IKChnS24sdmVlKT0+e3ZhciBfSmU9QTQoKSx5SmU9cXN0KCksdkplPTEseEplPTI7ZnVuY3Rpb24gYkplKGUsdCxyLG4pe3ZhciBpPXIubGVuZ3RoLG89aSxhPSFuO2lmKGU9PW51bGwpcmV0dXJuIW87Zm9yKGU9T2JqZWN0KGUpO2ktLTspe3ZhciBzPXJbaV07aWYoYSYmc1syXT9zWzFdIT09ZVtzWzBdXTohKHNbMF1pbiBlKSlyZXR1cm4hMX1mb3IoOysraTxvOyl7cz1yW2ldO3ZhciBsPXNbMF0sYz1lW2xdLHU9c1sxXTtpZihhJiZzWzJdKXtpZihjPT09dm9pZCAwJiYhKGwgaW4gZSkpcmV0dXJuITF9ZWxzZXt2YXIgaD1uZXcgX0plO2lmKG4pdmFyIGY9bihjLHUsbCxlLHQsaCk7aWYoIShmPT09dm9pZCAwP3lKZSh1LGMsdkplfHhKZSxuLGgpOmYpKXJldHVybiExfX1yZXR1cm4hMH12ZWUuZXhwb3J0cz1iSmV9KTt2YXIgR3N0PUgoKF9LbixiZWUpPT57dmFyIHdKZT1NbCgpO2Z1bmN0aW9uIFNKZShlKXtyZXR1cm4gZT09PWUmJiF3SmUoZSl9YmVlLmV4cG9ydHM9U0plfSk7dmFyIFNlZT1IKCh5S24sd2VlKT0+e3ZhciBNSmU9R3N0KCksRUplPUFkKCk7ZnVuY3Rpb24gVEplKGUpe2Zvcih2YXIgdD1FSmUoZSkscj10Lmxlbmd0aDtyLS07KXt2YXIgbj10W3JdLGk9ZVtuXTt0W3JdPVtuLGksTUplKGkpXX1yZXR1cm4gdH13ZWUuZXhwb3J0cz1USmV9KTt2YXIgV3N0PUgoKHZLbixNZWUpPT57ZnVuY3Rpb24gQ0plKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKHIpe3JldHVybiByPT1udWxsPyExOnJbZV09PT10JiYodCE9PXZvaWQgMHx8ZSBpbiBPYmplY3QocikpfX1NZWUuZXhwb3J0cz1DSmV9KTt2YXIgVGVlPUgoKHhLbixFZWUpPT57dmFyIEFKZT14ZWUoKSxQSmU9U2VlKCksSUplPVdzdCgpO2Z1bmN0aW9uIExKZShlKXt2YXIgdD1QSmUoZSk7cmV0dXJuIHQubGVuZ3RoPT0xJiZ0WzBdWzJdP0lKZSh0WzBdWzBdLHRbMF1bMV0pOmZ1bmN0aW9uKHIpe3JldHVybiByPT09ZXx8QUplKHIsZSx0KX19RWVlLmV4cG9ydHM9TEplfSk7dmFyIFoxPUgoKGJLbixDZWUpPT57dmFyIGtKZT1zMCgpLFJKZT1ZdSgpLE5KZT0iW29iamVjdCBTeW1ib2xdIjtmdW5jdGlvbiBESmUoZSl7cmV0dXJuIHR5cGVvZiBlPT0ic3ltYm9sInx8UkplKGUpJiZrSmUoZSk9PU5KZX1DZWUuZXhwb3J0cz1ESmV9KTt2YXIgSkI9SCgod0tuLEFlZSk9Pnt2YXIgT0plPVRpKCksekplPVoxKCksRkplPS9cLnxcWyg/OlteW1xdXSp8KFsiJ10pKD86KD8hXDEpW15cXF18XFwuKSo/XDEpXF0vLEJKZT0vXlx3KiQvO2Z1bmN0aW9uIEhKZShlLHQpe2lmKE9KZShlKSlyZXR1cm4hMTt2YXIgcj10eXBlb2YgZTtyZXR1cm4gcj09Im51bWJlciJ8fHI9PSJzeW1ib2wifHxyPT0iYm9vbGVhbiJ8fGU9PW51bGx8fHpKZShlKT8hMDpCSmUudGVzdChlKXx8IUZKZS50ZXN0KGUpfHx0IT1udWxsJiZlIGluIE9iamVjdCh0KX1BZWUuZXhwb3J0cz1ISmV9KTt2YXIgTGVlPUgoKFNLbixJZWUpPT57dmFyIFBlZT16QigpLFZKZT0iRXhwZWN0ZWQgYSBmdW5jdGlvbiI7ZnVuY3Rpb24gWXN0KGUsdCl7aWYodHlwZW9mIGUhPSJmdW5jdGlvbiJ8fHQhPW51bGwmJnR5cGVvZiB0IT0iZnVuY3Rpb24iKXRocm93IG5ldyBUeXBlRXJyb3IoVkplKTt2YXIgcj1mdW5jdGlvbigpe3ZhciBuPWFyZ3VtZW50cyxpPXQ/dC5hcHBseSh0aGlzLG4pOm5bMF0sbz1yLmNhY2hlO2lmKG8uaGFzKGkpKXJldHVybiBvLmdldChpKTt2YXIgYT1lLmFwcGx5KHRoaXMsbik7cmV0dXJuIHIuY2FjaGU9by5zZXQoaSxhKXx8byxhfTtyZXR1cm4gci5jYWNoZT1uZXcoWXN0LkNhY2hlfHxQZWUpLHJ9WXN0LkNhY2hlPVBlZTtJZWUuZXhwb3J0cz1Zc3R9KTt2YXIgUmVlPUgoKE1LbixrZWUpPT57dmFyIFVKZT1MZWUoKSxxSmU9NTAwO2Z1bmN0aW9uIEdKZShlKXt2YXIgdD1VSmUoZSxmdW5jdGlvbihuKXtyZXR1cm4gci5zaXplPT09cUplJiZyLmNsZWFyKCksbn0pLHI9dC5jYWNoZTtyZXR1cm4gdH1rZWUuZXhwb3J0cz1HSmV9KTt2YXIgRGVlPUgoKEVLbixOZWUpPT57dmFyIFdKZT1SZWUoKSxZSmU9L1teLltcXV0rfFxbKD86KC0/XGQrKD86XC5cZCspPyl8KFsiJ10pKCg/Oig/IVwyKVteXFxdfFxcLikqPylcMilcXXwoPz0oPzpcLnxcW1xdKSg/OlwufFxbXF18JCkpL2csakplPS9cXChcXCk/L2csWEplPVdKZShmdW5jdGlvbihlKXt2YXIgdD1bXTtyZXR1cm4gZS5jaGFyQ29kZUF0KDApPT09NDYmJnQucHVzaCgiIiksZS5yZXBsYWNlKFlKZSxmdW5jdGlvbihyLG4saSxvKXt0LnB1c2goaT9vLnJlcGxhY2UoakplLCIkMSIpOm58fHIpfSksdH0pO05lZS5leHBvcnRzPVhKZX0pO3ZhciBIND1IKChUS24sT2VlKT0+e2Z1bmN0aW9uICRKZShlLHQpe2Zvcih2YXIgcj0tMSxuPWU9PW51bGw/MDplLmxlbmd0aCxpPUFycmF5KG4pOysrcjxuOylpW3JdPXQoZVtyXSxyLGUpO3JldHVybiBpfU9lZS5leHBvcnRzPSRKZX0pO3ZhciBVZWU9SCgoQ0tuLFZlZSk9Pnt2YXIgemVlPWoxKCksS0plPUg0KCksWkplPVRpKCksSkplPVoxKCksUUplPTEvMCxGZWU9emVlP3plZS5wcm90b3R5cGU6dm9pZCAwLEJlZT1GZWU/RmVlLnRvU3RyaW5nOnZvaWQgMDtmdW5jdGlvbiBIZWUoZSl7aWYodHlwZW9mIGU9PSJzdHJpbmciKXJldHVybiBlO2lmKFpKZShlKSlyZXR1cm4gS0plKGUsSGVlKSsiIjtpZihKSmUoZSkpcmV0dXJuIEJlZT9CZWUuY2FsbChlKToiIjt2YXIgdD1lKyIiO3JldHVybiB0PT0iMCImJjEvZT09LVFKZT8iLTAiOnR9VmVlLmV4cG9ydHM9SGVlfSk7dmFyIGpzdD1IKChBS24scWVlKT0+e3ZhciB0UWU9VWVlKCk7ZnVuY3Rpb24gZVFlKGUpe3JldHVybiBlPT1udWxsPyIiOnRRZShlKX1xZWUuZXhwb3J0cz1lUWV9KTt2YXIgVjQ9SCgoUEtuLEdlZSk9Pnt2YXIgclFlPVRpKCksblFlPUpCKCksaVFlPURlZSgpLG9RZT1qc3QoKTtmdW5jdGlvbiBhUWUoZSx0KXtyZXR1cm4gclFlKGUpP2U6blFlKGUsdCk/W2VdOmlRZShvUWUoZSkpfUdlZS5leHBvcnRzPWFRZX0pO3ZhciAkUz1IKChJS24sV2VlKT0+e3ZhciBzUWU9WjEoKSxsUWU9MS8wO2Z1bmN0aW9uIGNRZShlKXtpZih0eXBlb2YgZT09InN0cmluZyJ8fHNRZShlKSlyZXR1cm4gZTt2YXIgdD1lKyIiO3JldHVybiB0PT0iMCImJjEvZT09LWxRZT8iLTAiOnR9V2VlLmV4cG9ydHM9Y1FlfSk7dmFyIFU0PUgoKExLbixZZWUpPT57dmFyIHVRZT1WNCgpLGhRZT0kUygpO2Z1bmN0aW9uIGZRZShlLHQpe3Q9dVFlKHQsZSk7Zm9yKHZhciByPTAsbj10Lmxlbmd0aDtlIT1udWxsJiZyPG47KWU9ZVtoUWUodFtyKytdKV07cmV0dXJuIHImJnI9PW4/ZTp2b2lkIDB9WWVlLmV4cG9ydHM9ZlFlfSk7dmFyIFhlZT1IKChrS24samVlKT0+e3ZhciBwUWU9VTQoKTtmdW5jdGlvbiBkUWUoZSx0LHIpe3ZhciBuPWU9PW51bGw/dm9pZCAwOnBRZShlLHQpO3JldHVybiBuPT09dm9pZCAwP3I6bn1qZWUuZXhwb3J0cz1kUWV9KTt2YXIgS2VlPUgoKFJLbiwkZWUpPT57ZnVuY3Rpb24gbVFlKGUsdCl7cmV0dXJuIGUhPW51bGwmJnQgaW4gT2JqZWN0KGUpfSRlZS5leHBvcnRzPW1RZX0pO3ZhciBYc3Q9SCgoTktuLFplZSk9Pnt2YXIgZ1FlPVY0KCksX1FlPXFTKCkseVFlPVRpKCksdlFlPWs0KCkseFFlPUJCKCksYlFlPSRTKCk7ZnVuY3Rpb24gd1FlKGUsdCxyKXt0PWdRZSh0LGUpO2Zvcih2YXIgbj0tMSxpPXQubGVuZ3RoLG89ITE7KytuPGk7KXt2YXIgYT1iUWUodFtuXSk7aWYoIShvPWUhPW51bGwmJnIoZSxhKSkpYnJlYWs7ZT1lW2FdfXJldHVybiBvfHwrK24hPWk/bzooaT1lPT1udWxsPzA6ZS5sZW5ndGgsISFpJiZ4UWUoaSkmJnZRZShhLGkpJiYoeVFlKGUpfHxfUWUoZSkpKX1aZWUuZXhwb3J0cz13UWV9KTt2YXIgJHN0PUgoKERLbixKZWUpPT57dmFyIFNRZT1LZWUoKSxNUWU9WHN0KCk7ZnVuY3Rpb24gRVFlKGUsdCl7cmV0dXJuIGUhPW51bGwmJk1RZShlLHQsU1FlKX1KZWUuZXhwb3J0cz1FUWV9KTt2YXIgdHJlPUgoKE9LbixRZWUpPT57dmFyIFRRZT1xc3QoKSxDUWU9WGVlKCksQVFlPSRzdCgpLFBRZT1KQigpLElRZT1Hc3QoKSxMUWU9V3N0KCksa1FlPSRTKCksUlFlPTEsTlFlPTI7ZnVuY3Rpb24gRFFlKGUsdCl7cmV0dXJuIFBRZShlKSYmSVFlKHQpP0xRZShrUWUoZSksdCk6ZnVuY3Rpb24ocil7dmFyIG49Q1FlKHIsZSk7cmV0dXJuIG49PT12b2lkIDAmJm49PT10P0FRZShyLGUpOlRRZSh0LG4sUlFlfE5RZSl9fVFlZS5leHBvcnRzPURRZX0pO3ZhciBLc3Q9SCgoektuLGVyZSk9PntmdW5jdGlvbiBPUWUoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3JldHVybiB0PT1udWxsP3ZvaWQgMDp0W2VdfX1lcmUuZXhwb3J0cz1PUWV9KTt2YXIgbnJlPUgoKEZLbixycmUpPT57dmFyIHpRZT1VNCgpO2Z1bmN0aW9uIEZRZShlKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuIHpRZSh0LGUpfX1ycmUuZXhwb3J0cz1GUWV9KTt2YXIgb3JlPUgoKEJLbixpcmUpPT57dmFyIEJRZT1Lc3QoKSxIUWU9bnJlKCksVlFlPUpCKCksVVFlPSRTKCk7ZnVuY3Rpb24gcVFlKGUpe3JldHVybiBWUWUoZSk/QlFlKFVRZShlKSk6SFFlKGUpfWlyZS5leHBvcnRzPXFRZX0pO3ZhciBIZj1IKChIS24sYXJlKT0+e3ZhciBHUWU9VGVlKCksV1FlPXRyZSgpLFlRZT11MCgpLGpRZT1UaSgpLFhRZT1vcmUoKTtmdW5jdGlvbiAkUWUoZSl7cmV0dXJuIHR5cGVvZiBlPT0iZnVuY3Rpb24iP2U6ZT09bnVsbD9ZUWU6dHlwZW9mIGU9PSJvYmplY3QiP2pRZShlKT9XUWUoZVswXSxlWzFdKTpHUWUoZSk6WFFlKGUpfWFyZS5leHBvcnRzPSRRZX0pO3ZhciBac3Q9SCgoVktuLHNyZSk9Pnt2YXIgS1FlPXZzdCgpLFpRZT1WdGUoKSxKUWU9SGYoKSxRUWU9VGkoKTtmdW5jdGlvbiB0dHIoZSx0KXt2YXIgcj1RUWUoZSk/S1FlOlpRZTtyZXR1cm4gcihlLEpRZSh0LDMpKX1zcmUuZXhwb3J0cz10dHJ9KTt2YXIgY3JlPUgoKFVLbixscmUpPT57dmFyIGV0cj1PYmplY3QucHJvdG90eXBlLHJ0cj1ldHIuaGFzT3duUHJvcGVydHk7ZnVuY3Rpb24gbnRyKGUsdCl7cmV0dXJuIGUhPW51bGwmJnJ0ci5jYWxsKGUsdCl9bHJlLmV4cG9ydHM9bnRyfSk7dmFyIEpzdD1IKChxS24sdXJlKT0+e3ZhciBpdHI9Y3JlKCksb3RyPVhzdCgpO2Z1bmN0aW9uIGF0cihlLHQpe3JldHVybiBlIT1udWxsJiZvdHIoZSx0LGl0cil9dXJlLmV4cG9ydHM9YXRyfSk7dmFyIGZyZT1IKChHS24saHJlKT0+e3ZhciBzdHI9VkIoKSxsdHI9SzEoKSxjdHI9cVMoKSx1dHI9VGkoKSxodHI9QmYoKSxmdHI9WDEoKSxwdHI9TzQoKSxkdHI9WVMoKSxtdHI9IltvYmplY3QgTWFwXSIsZ3RyPSJbb2JqZWN0IFNldF0iLF90cj1PYmplY3QucHJvdG90eXBlLHl0cj1fdHIuaGFzT3duUHJvcGVydHk7ZnVuY3Rpb24gdnRyKGUpe2lmKGU9PW51bGwpcmV0dXJuITA7aWYoaHRyKGUpJiYodXRyKGUpfHx0eXBlb2YgZT09InN0cmluZyJ8fHR5cGVvZiBlLnNwbGljZT09ImZ1bmN0aW9uInx8ZnRyKGUpfHxkdHIoZSl8fGN0cihlKSkpcmV0dXJuIWUubGVuZ3RoO3ZhciB0PWx0cihlKTtpZih0PT1tdHJ8fHQ9PWd0cilyZXR1cm4hZS5zaXplO2lmKHB0cihlKSlyZXR1cm4hc3RyKGUpLmxlbmd0aDtmb3IodmFyIHIgaW4gZSlpZih5dHIuY2FsbChlLHIpKXJldHVybiExO3JldHVybiEwfWhyZS5leHBvcnRzPXZ0cn0pO3ZhciBRc3Q9SCgoV0tuLHByZSk9PntmdW5jdGlvbiB4dHIoZSl7cmV0dXJuIGU9PT12b2lkIDB9cHJlLmV4cG9ydHM9eHRyfSk7dmFyIHRsdD1IKChZS24sZHJlKT0+e3ZhciBidHI9QjQoKSx3dHI9QmYoKTtmdW5jdGlvbiBTdHIoZSx0KXt2YXIgcj0tMSxuPXd0cihlKT9BcnJheShlLmxlbmd0aCk6W107cmV0dXJuIGJ0cihlLGZ1bmN0aW9uKGksbyxhKXtuWysrcl09dChpLG8sYSl9KSxufWRyZS5leHBvcnRzPVN0cn0pO3ZhciBlbHQ9SCgoaktuLG1yZSk9Pnt2YXIgTXRyPUg0KCksRXRyPUhmKCksVHRyPXRsdCgpLEN0cj1UaSgpO2Z1bmN0aW9uIEF0cihlLHQpe3ZhciByPUN0cihlKT9NdHI6VHRyO3JldHVybiByKGUsRXRyKHQsMykpfW1yZS5leHBvcnRzPUF0cn0pO3ZhciBfcmU9SCgoWEtuLGdyZSk9PntmdW5jdGlvbiBQdHIoZSx0LHIsbil7dmFyIGk9LTEsbz1lPT1udWxsPzA6ZS5sZW5ndGg7Zm9yKG4mJm8mJihyPWVbKytpXSk7KytpPG87KXI9dChyLGVbaV0saSxlKTtyZXR1cm4gcn1ncmUuZXhwb3J0cz1QdHJ9KTt2YXIgdnJlPUgoKCRLbix5cmUpPT57ZnVuY3Rpb24gSXRyKGUsdCxyLG4saSl7cmV0dXJuIGkoZSxmdW5jdGlvbihvLGEscyl7cj1uPyhuPSExLG8pOnQocixvLGEscyl9KSxyfXlyZS5leHBvcnRzPUl0cn0pO3ZhciBybHQ9SCgoS0tuLHhyZSk9Pnt2YXIgTHRyPV9yZSgpLGt0cj1CNCgpLFJ0cj1IZigpLE50cj12cmUoKSxEdHI9VGkoKTtmdW5jdGlvbiBPdHIoZSx0LHIpe3ZhciBuPUR0cihlKT9MdHI6TnRyLGk9YXJndW1lbnRzLmxlbmd0aDwzO3JldHVybiBuKGUsUnRyKHQsNCkscixpLGt0cil9eHJlLmV4cG9ydHM9T3RyfSk7dmFyIHdyZT1IKChaS24sYnJlKT0+e3ZhciB6dHI9czAoKSxGdHI9VGkoKSxCdHI9WXUoKSxIdHI9IltvYmplY3QgU3RyaW5nXSI7ZnVuY3Rpb24gVnRyKGUpe3JldHVybiB0eXBlb2YgZT09InN0cmluZyJ8fCFGdHIoZSkmJkJ0cihlKSYmenRyKGUpPT1IdHJ9YnJlLmV4cG9ydHM9VnRyfSk7dmFyIE1yZT1IKChKS24sU3JlKT0+e3ZhciBVdHI9S3N0KCkscXRyPVV0cigibGVuZ3RoIik7U3JlLmV4cG9ydHM9cXRyfSk7dmFyIFRyZT1IKChRS24sRXJlKT0+e3ZhciBHdHI9IlxcdWQ4MDAtXFx1ZGZmZiIsV3RyPSJcXHUwMzAwLVxcdTAzNmYiLFl0cj0iXFx1ZmUyMC1cXHVmZTJmIixqdHI9IlxcdTIwZDAtXFx1MjBmZiIsWHRyPVd0citZdHIranRyLCR0cj0iXFx1ZmUwZVxcdWZlMGYiLEt0cj0iXFx1MjAwZCIsWnRyPVJlZ0V4cCgiWyIrS3RyK0d0citYdHIrJHRyKyJdIik7ZnVuY3Rpb24gSnRyKGUpe3JldHVybiBadHIudGVzdChlKX1FcmUuZXhwb3J0cz1KdHJ9KTt2YXIgRHJlPUgoKHRabixOcmUpPT57dmFyIEFyZT0iXFx1ZDgwMC1cXHVkZmZmIixRdHI9IlxcdTAzMDAtXFx1MDM2ZiIsdGVyPSJcXHVmZTIwLVxcdWZlMmYiLGVlcj0iXFx1MjBkMC1cXHUyMGZmIixyZXI9UXRyK3RlcitlZXIsbmVyPSJcXHVmZTBlXFx1ZmUwZiIsaWVyPSJbIitBcmUrIl0iLG5sdD0iWyIrcmVyKyJdIixpbHQ9IlxcdWQ4M2NbXFx1ZGZmYi1cXHVkZmZmXSIsb2VyPSIoPzoiK25sdCsifCIraWx0KyIpIixQcmU9IlteIitBcmUrIl0iLElyZT0iKD86XFx1ZDgzY1tcXHVkZGU2LVxcdWRkZmZdKXsyfSIsTHJlPSJbXFx1ZDgwMC1cXHVkYmZmXVtcXHVkYzAwLVxcdWRmZmZdIixhZXI9IlxcdTIwMGQiLGtyZT1vZXIrIj8iLFJyZT0iWyIrbmVyKyJdPyIsc2VyPSIoPzoiK2FlcisiKD86IitbUHJlLElyZSxMcmVdLmpvaW4oInwiKSsiKSIrUnJlK2tyZSsiKSoiLGxlcj1ScmUra3JlK3NlcixjZXI9Iig/OiIrW1ByZStubHQrIj8iLG5sdCxJcmUsTHJlLGllcl0uam9pbigifCIpKyIpIixDcmU9UmVnRXhwKGlsdCsiKD89IitpbHQrIil8IitjZXIrbGVyLCJnIik7ZnVuY3Rpb24gdWVyKGUpe2Zvcih2YXIgdD1DcmUubGFzdEluZGV4PTA7Q3JlLnRlc3QoZSk7KSsrdDtyZXR1cm4gdH1OcmUuZXhwb3J0cz11ZXJ9KTt2YXIgenJlPUgoKGVabixPcmUpPT57dmFyIGhlcj1NcmUoKSxmZXI9VHJlKCkscGVyPURyZSgpO2Z1bmN0aW9uIGRlcihlKXtyZXR1cm4gZmVyKGUpP3BlcihlKTpoZXIoZSl9T3JlLmV4cG9ydHM9ZGVyfSk7dmFyIEJyZT1IKChyWm4sRnJlKT0+e3ZhciBtZXI9VkIoKSxnZXI9SzEoKSxfZXI9QmYoKSx5ZXI9d3JlKCksdmVyPXpyZSgpLHhlcj0iW29iamVjdCBNYXBdIixiZXI9IltvYmplY3QgU2V0XSI7ZnVuY3Rpb24gd2VyKGUpe2lmKGU9PW51bGwpcmV0dXJuIDA7aWYoX2VyKGUpKXJldHVybiB5ZXIoZSk/dmVyKGUpOmUubGVuZ3RoO3ZhciB0PWdlcihlKTtyZXR1cm4gdD09eGVyfHx0PT1iZXI/ZS5zaXplOm1lcihlKS5sZW5ndGh9RnJlLmV4cG9ydHM9d2VyfSk7dmFyIFZyZT1IKChuWm4sSHJlKT0+e3ZhciBTZXI9RkIoKSxNZXI9a3N0KCksRWVyPVhCKCksVGVyPUhmKCksQ2VyPUY0KCksQWVyPVRpKCksUGVyPVgxKCksSWVyPUZTKCksTGVyPU1sKCksa2VyPVlTKCk7ZnVuY3Rpb24gUmVyKGUsdCxyKXt2YXIgbj1BZXIoZSksaT1ufHxQZXIoZSl8fGtlcihlKTtpZih0PVRlcih0LDQpLHI9PW51bGwpe3ZhciBvPWUmJmUuY29uc3RydWN0b3I7aT9yPW4/bmV3IG86W106TGVyKGUpP3I9SWVyKG8pP01lcihDZXIoZSkpOnt9OnI9e319cmV0dXJuKGk/U2VyOkVlcikoZSxmdW5jdGlvbihhLHMsbCl7cmV0dXJuIHQocixhLHMsbCl9KSxyfUhyZS5leHBvcnRzPVJlcn0pO3ZhciBXcmU9SCgoaVpuLEdyZSk9Pnt2YXIgVXJlPWoxKCksTmVyPXFTKCksRGVyPVRpKCkscXJlPVVyZT9VcmUuaXNDb25jYXRTcHJlYWRhYmxlOnZvaWQgMDtmdW5jdGlvbiBPZXIoZSl7cmV0dXJuIERlcihlKXx8TmVyKGUpfHwhIShxcmUmJmUmJmVbcXJlXSl9R3JlLmV4cG9ydHM9T2VyfSk7dmFyIFFCPUgoKG9abixqcmUpPT57dmFyIHplcj1xQigpLEZlcj1XcmUoKTtmdW5jdGlvbiBZcmUoZSx0LHIsbixpKXt2YXIgbz0tMSxhPWUubGVuZ3RoO2ZvcihyfHwocj1GZXIpLGl8fChpPVtdKTsrK288YTspe3ZhciBzPWVbb107dD4wJiZyKHMpP3Q+MT9ZcmUocyx0LTEscixuLGkpOnplcihpLHMpOm58fChpW2kubGVuZ3RoXT1zKX1yZXR1cm4gaX1qcmUuZXhwb3J0cz1ZcmV9KTt2YXIgJHJlPUgoKGFabixYcmUpPT57ZnVuY3Rpb24gQmVyKGUsdCxyKXtzd2l0Y2goci5sZW5ndGgpe2Nhc2UgMDpyZXR1cm4gZS5jYWxsKHQpO2Nhc2UgMTpyZXR1cm4gZS5jYWxsKHQsclswXSk7Y2FzZSAyOnJldHVybiBlLmNhbGwodCxyWzBdLHJbMV0pO2Nhc2UgMzpyZXR1cm4gZS5jYWxsKHQsclswXSxyWzFdLHJbMl0pfXJldHVybiBlLmFwcGx5KHQscil9WHJlLmV4cG9ydHM9QmVyfSk7dmFyIG9sdD1IKChzWm4sWnJlKT0+e3ZhciBIZXI9JHJlKCksS3JlPU1hdGgubWF4O2Z1bmN0aW9uIFZlcihlLHQscil7cmV0dXJuIHQ9S3JlKHQ9PT12b2lkIDA/ZS5sZW5ndGgtMTp0LDApLGZ1bmN0aW9uKCl7Zm9yKHZhciBuPWFyZ3VtZW50cyxpPS0xLG89S3JlKG4ubGVuZ3RoLXQsMCksYT1BcnJheShvKTsrK2k8bzspYVtpXT1uW3QraV07aT0tMTtmb3IodmFyIHM9QXJyYXkodCsxKTsrK2k8dDspc1tpXT1uW2ldO3JldHVybiBzW3RdPXIoYSksSGVyKGUsdGhpcyxzKX19WnJlLmV4cG9ydHM9VmVyfSk7dmFyIHRuZT1IKChsWm4sUXJlKT0+e3ZhciBVZXI9WUIoKSxKcmU9cHN0KCkscWVyPXUwKCksR2VyPUpyZT9mdW5jdGlvbihlLHQpe3JldHVybiBKcmUoZSwidG9TdHJpbmciLHtjb25maWd1cmFibGU6ITAsZW51bWVyYWJsZTohMSx2YWx1ZTpVZXIodCksd3JpdGFibGU6ITB9KX06cWVyO1FyZS5leHBvcnRzPUdlcn0pO3ZhciBybmU9SCgoY1puLGVuZSk9Pnt2YXIgV2VyPTgwMCxZZXI9MTYsamVyPURhdGUubm93O2Z1bmN0aW9uIFhlcihlKXt2YXIgdD0wLHI9MDtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbj1qZXIoKSxpPVllci0obi1yKTtpZihyPW4saT4wKXtpZigrK3Q+PVdlcilyZXR1cm4gYXJndW1lbnRzWzBdfWVsc2UgdD0wO3JldHVybiBlLmFwcGx5KHZvaWQgMCxhcmd1bWVudHMpfX1lbmUuZXhwb3J0cz1YZXJ9KTt2YXIgYWx0PUgoKHVabixubmUpPT57dmFyICRlcj10bmUoKSxLZXI9cm5lKCksWmVyPUtlcigkZXIpO25uZS5leHBvcnRzPVplcn0pO3ZhciBxND1IKChoWm4saW5lKT0+e3ZhciBKZXI9dTAoKSxRZXI9b2x0KCksdHJyPWFsdCgpO2Z1bmN0aW9uIGVycihlLHQpe3JldHVybiB0cnIoUWVyKGUsdCxKZXIpLGUrIiIpfWluZS5leHBvcnRzPWVycn0pO3ZhciBzbHQ9SCgoZlpuLG9uZSk9PntmdW5jdGlvbiBycnIoZSx0LHIsbil7Zm9yKHZhciBpPWUubGVuZ3RoLG89cisobj8xOi0xKTtuP28tLTorK288aTspaWYodChlW29dLG8sZSkpcmV0dXJuIG87cmV0dXJuLTF9b25lLmV4cG9ydHM9cnJyfSk7dmFyIHNuZT1IKChwWm4sYW5lKT0+e2Z1bmN0aW9uIG5ycihlKXtyZXR1cm4gZSE9PWV9YW5lLmV4cG9ydHM9bnJyfSk7dmFyIGNuZT1IKChkWm4sbG5lKT0+e2Z1bmN0aW9uIGlycihlLHQscil7Zm9yKHZhciBuPXItMSxpPWUubGVuZ3RoOysrbjxpOylpZihlW25dPT09dClyZXR1cm4gbjtyZXR1cm4tMX1sbmUuZXhwb3J0cz1pcnJ9KTt2YXIgaG5lPUgoKG1abix1bmUpPT57dmFyIG9ycj1zbHQoKSxhcnI9c25lKCksc3JyPWNuZSgpO2Z1bmN0aW9uIGxycihlLHQscil7cmV0dXJuIHQ9PT10P3NycihlLHQscik6b3JyKGUsYXJyLHIpfXVuZS5leHBvcnRzPWxycn0pO3ZhciBwbmU9SCgoZ1puLGZuZSk9Pnt2YXIgY3JyPWhuZSgpO2Z1bmN0aW9uIHVycihlLHQpe3ZhciByPWU9PW51bGw/MDplLmxlbmd0aDtyZXR1cm4hIXImJmNycihlLHQsMCk+LTF9Zm5lLmV4cG9ydHM9dXJyfSk7dmFyIG1uZT1IKChfWm4sZG5lKT0+e2Z1bmN0aW9uIGhycihlLHQscil7Zm9yKHZhciBuPS0xLGk9ZT09bnVsbD8wOmUubGVuZ3RoOysrbjxpOylpZihyKHQsZVtuXSkpcmV0dXJuITA7cmV0dXJuITF9ZG5lLmV4cG9ydHM9aHJyfSk7dmFyIF9uZT1IKCh5Wm4sZ25lKT0+e2Z1bmN0aW9uIGZycigpe31nbmUuZXhwb3J0cz1mcnJ9KTt2YXIgdm5lPUgoKHZabix5bmUpPT57dmFyIGxsdD1Nc3QoKSxwcnI9X25lKCksZHJyPUtCKCksbXJyPTEvMCxncnI9bGx0JiYxL2RycihuZXcgbGx0KFssLTBdKSlbMV09PW1ycj9mdW5jdGlvbihlKXtyZXR1cm4gbmV3IGxsdChlKX06cHJyO3luZS5leHBvcnRzPWdycn0pO3ZhciBibmU9SCgoeFpuLHhuZSk9Pnt2YXIgX3JyPUZzdCgpLHlycj1wbmUoKSx2cnI9bW5lKCkseHJyPUJzdCgpLGJycj12bmUoKSx3cnI9S0IoKSxTcnI9MjAwO2Z1bmN0aW9uIE1ycihlLHQscil7dmFyIG49LTEsaT15cnIsbz1lLmxlbmd0aCxhPSEwLHM9W10sbD1zO2lmKHIpYT0hMSxpPXZycjtlbHNlIGlmKG8+PVNycil7dmFyIGM9dD9udWxsOmJycihlKTtpZihjKXJldHVybiB3cnIoYyk7YT0hMSxpPXhycixsPW5ldyBfcnJ9ZWxzZSBsPXQ/W106czt0OmZvcig7KytuPG87KXt2YXIgdT1lW25dLGg9dD90KHUpOnU7aWYodT1yfHx1IT09MD91OjAsYSYmaD09PWgpe2Zvcih2YXIgZj1sLmxlbmd0aDtmLS07KWlmKGxbZl09PT1oKWNvbnRpbnVlIHQ7dCYmbC5wdXNoKGgpLHMucHVzaCh1KX1lbHNlIGkobCxoLHIpfHwobCE9PXMmJmwucHVzaChoKSxzLnB1c2godSkpfXJldHVybiBzfXhuZS5leHBvcnRzPU1ycn0pO3ZhciBjbHQ9SCgoYlpuLHduZSk9Pnt2YXIgRXJyPUJmKCksVHJyPVl1KCk7ZnVuY3Rpb24gQ3JyKGUpe3JldHVybiBUcnIoZSkmJkVycihlKX13bmUuZXhwb3J0cz1DcnJ9KTt2YXIgTW5lPUgoKHdabixTbmUpPT57dmFyIEFycj1RQigpLFBycj1xNCgpLElycj1ibmUoKSxMcnI9Y2x0KCksa3JyPVBycihmdW5jdGlvbihlKXtyZXR1cm4gSXJyKEFycihlLDEsTHJyLCEwKSl9KTtTbmUuZXhwb3J0cz1rcnJ9KTt2YXIgVG5lPUgoKFNabixFbmUpPT57dmFyIFJycj1INCgpO2Z1bmN0aW9uIE5ycihlLHQpe3JldHVybiBScnIodCxmdW5jdGlvbihyKXtyZXR1cm4gZVtyXX0pfUVuZS5leHBvcnRzPU5ycn0pO3ZhciB1bHQ9SCgoTVpuLENuZSk9Pnt2YXIgRHJyPVRuZSgpLE9ycj1BZCgpO2Z1bmN0aW9uIHpycihlKXtyZXR1cm4gZT09bnVsbD9bXTpEcnIoZSxPcnIoZSkpfUNuZS5leHBvcnRzPXpycn0pO3ZhciBFbD1IKChFWm4sQW5lKT0+e3ZhciB0SDtpZih0eXBlb2YgRXg9PSJmdW5jdGlvbiIpdHJ5e3RIPXtjbG9uZTpDdGUoKSxjb25zdGFudDpZQigpLGVhY2g6enN0KCksZmlsdGVyOlpzdCgpLGhhczpKc3QoKSxpc0FycmF5OlRpKCksaXNFbXB0eTpmcmUoKSxpc0Z1bmN0aW9uOkZTKCksaXNVbmRlZmluZWQ6UXN0KCksa2V5czpBZCgpLG1hcDplbHQoKSxyZWR1Y2U6cmx0KCksc2l6ZTpCcmUoKSx0cmFuc2Zvcm06VnJlKCksdW5pb246TW5lKCksdmFsdWVzOnVsdCgpfX1jYXRjaChlKXt9dEh8fCh0SD13aW5kb3cuXyk7QW5lLmV4cG9ydHM9dEh9KTt2YXIgZUg9SCgoQ1puLGtuZSk9PnsidXNlIHN0cmljdCI7dmFyIGplPUVsKCk7a25lLmV4cG9ydHM9Y3I7dmFyIEZycj0iXDAiLEoxPSJcMCIsUG5lPSIBIjtmdW5jdGlvbiBjcihlKXt0aGlzLl9pc0RpcmVjdGVkPWplLmhhcyhlLCJkaXJlY3RlZCIpP2UuZGlyZWN0ZWQ6ITAsdGhpcy5faXNNdWx0aWdyYXBoPWplLmhhcyhlLCJtdWx0aWdyYXBoIik/ZS5tdWx0aWdyYXBoOiExLHRoaXMuX2lzQ29tcG91bmQ9amUuaGFzKGUsImNvbXBvdW5kIik/ZS5jb21wb3VuZDohMSx0aGlzLl9sYWJlbD12b2lkIDAsdGhpcy5fZGVmYXVsdE5vZGVMYWJlbEZuPWplLmNvbnN0YW50KHZvaWQgMCksdGhpcy5fZGVmYXVsdEVkZ2VMYWJlbEZuPWplLmNvbnN0YW50KHZvaWQgMCksdGhpcy5fbm9kZXM9e30sdGhpcy5faXNDb21wb3VuZCYmKHRoaXMuX3BhcmVudD17fSx0aGlzLl9jaGlsZHJlbj17fSx0aGlzLl9jaGlsZHJlbltKMV09e30pLHRoaXMuX2luPXt9LHRoaXMuX3ByZWRzPXt9LHRoaXMuX291dD17fSx0aGlzLl9zdWNzPXt9LHRoaXMuX2VkZ2VPYmpzPXt9LHRoaXMuX2VkZ2VMYWJlbHM9e319Y3IucHJvdG90eXBlLl9ub2RlQ291bnQ9MDtjci5wcm90b3R5cGUuX2VkZ2VDb3VudD0wO2NyLnByb3RvdHlwZS5pc0RpcmVjdGVkPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2lzRGlyZWN0ZWR9O2NyLnByb3RvdHlwZS5pc011bHRpZ3JhcGg9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5faXNNdWx0aWdyYXBofTtjci5wcm90b3R5cGUuaXNDb21wb3VuZD1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9pc0NvbXBvdW5kfTtjci5wcm90b3R5cGUuc2V0R3JhcGg9ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX2xhYmVsPWUsdGhpc307Y3IucHJvdG90eXBlLmdyYXBoPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2xhYmVsfTtjci5wcm90b3R5cGUuc2V0RGVmYXVsdE5vZGVMYWJlbD1mdW5jdGlvbihlKXtyZXR1cm4gamUuaXNGdW5jdGlvbihlKXx8KGU9amUuY29uc3RhbnQoZSkpLHRoaXMuX2RlZmF1bHROb2RlTGFiZWxGbj1lLHRoaXN9O2NyLnByb3RvdHlwZS5ub2RlQ291bnQ9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fbm9kZUNvdW50fTtjci5wcm90b3R5cGUubm9kZXM9ZnVuY3Rpb24oKXtyZXR1cm4gamUua2V5cyh0aGlzLl9ub2Rlcyl9O2NyLnByb3RvdHlwZS5zb3VyY2VzPWZ1bmN0aW9uKCl7dmFyIGU9dGhpcztyZXR1cm4gamUuZmlsdGVyKHRoaXMubm9kZXMoKSxmdW5jdGlvbih0KXtyZXR1cm4gamUuaXNFbXB0eShlLl9pblt0XSl9KX07Y3IucHJvdG90eXBlLnNpbmtzPWZ1bmN0aW9uKCl7dmFyIGU9dGhpcztyZXR1cm4gamUuZmlsdGVyKHRoaXMubm9kZXMoKSxmdW5jdGlvbih0KXtyZXR1cm4gamUuaXNFbXB0eShlLl9vdXRbdF0pfSl9O2NyLnByb3RvdHlwZS5zZXROb2Rlcz1mdW5jdGlvbihlLHQpe3ZhciByPWFyZ3VtZW50cyxuPXRoaXM7cmV0dXJuIGplLmVhY2goZSxmdW5jdGlvbihpKXtyLmxlbmd0aD4xP24uc2V0Tm9kZShpLHQpOm4uc2V0Tm9kZShpKX0pLHRoaXN9O2NyLnByb3RvdHlwZS5zZXROb2RlPWZ1bmN0aW9uKGUsdCl7cmV0dXJuIGplLmhhcyh0aGlzLl9ub2RlcyxlKT8oYXJndW1lbnRzLmxlbmd0aD4xJiYodGhpcy5fbm9kZXNbZV09dCksdGhpcyk6KHRoaXMuX25vZGVzW2VdPWFyZ3VtZW50cy5sZW5ndGg+MT90OnRoaXMuX2RlZmF1bHROb2RlTGFiZWxGbihlKSx0aGlzLl9pc0NvbXBvdW5kJiYodGhpcy5fcGFyZW50W2VdPUoxLHRoaXMuX2NoaWxkcmVuW2VdPXt9LHRoaXMuX2NoaWxkcmVuW0oxXVtlXT0hMCksdGhpcy5faW5bZV09e30sdGhpcy5fcHJlZHNbZV09e30sdGhpcy5fb3V0W2VdPXt9LHRoaXMuX3N1Y3NbZV09e30sKyt0aGlzLl9ub2RlQ291bnQsdGhpcyl9O2NyLnByb3RvdHlwZS5ub2RlPWZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLl9ub2Rlc1tlXX07Y3IucHJvdG90eXBlLmhhc05vZGU9ZnVuY3Rpb24oZSl7cmV0dXJuIGplLmhhcyh0aGlzLl9ub2RlcyxlKX07Y3IucHJvdG90eXBlLnJlbW92ZU5vZGU9ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcztpZihqZS5oYXModGhpcy5fbm9kZXMsZSkpe3ZhciByPWZ1bmN0aW9uKG4pe3QucmVtb3ZlRWRnZSh0Ll9lZGdlT2Jqc1tuXSl9O2RlbGV0ZSB0aGlzLl9ub2Rlc1tlXSx0aGlzLl9pc0NvbXBvdW5kJiYodGhpcy5fcmVtb3ZlRnJvbVBhcmVudHNDaGlsZExpc3QoZSksZGVsZXRlIHRoaXMuX3BhcmVudFtlXSxqZS5lYWNoKHRoaXMuY2hpbGRyZW4oZSksZnVuY3Rpb24obil7dC5zZXRQYXJlbnQobil9KSxkZWxldGUgdGhpcy5fY2hpbGRyZW5bZV0pLGplLmVhY2goamUua2V5cyh0aGlzLl9pbltlXSksciksZGVsZXRlIHRoaXMuX2luW2VdLGRlbGV0ZSB0aGlzLl9wcmVkc1tlXSxqZS5lYWNoKGplLmtleXModGhpcy5fb3V0W2VdKSxyKSxkZWxldGUgdGhpcy5fb3V0W2VdLGRlbGV0ZSB0aGlzLl9zdWNzW2VdLC0tdGhpcy5fbm9kZUNvdW50fXJldHVybiB0aGlzfTtjci5wcm90b3R5cGUuc2V0UGFyZW50PWZ1bmN0aW9uKGUsdCl7aWYoIXRoaXMuX2lzQ29tcG91bmQpdGhyb3cgbmV3IEVycm9yKCJDYW5ub3Qgc2V0IHBhcmVudCBpbiBhIG5vbi1jb21wb3VuZCBncmFwaCIpO2lmKGplLmlzVW5kZWZpbmVkKHQpKXQ9SjE7ZWxzZXt0Kz0iIjtmb3IodmFyIHI9dDshamUuaXNVbmRlZmluZWQocik7cj10aGlzLnBhcmVudChyKSlpZihyPT09ZSl0aHJvdyBuZXcgRXJyb3IoIlNldHRpbmcgIit0KyIgYXMgcGFyZW50IG9mICIrZSsiIHdvdWxkIGNyZWF0ZSBhIGN5Y2xlIik7dGhpcy5zZXROb2RlKHQpfXJldHVybiB0aGlzLnNldE5vZGUoZSksdGhpcy5fcmVtb3ZlRnJvbVBhcmVudHNDaGlsZExpc3QoZSksdGhpcy5fcGFyZW50W2VdPXQsdGhpcy5fY2hpbGRyZW5bdF1bZV09ITAsdGhpc307Y3IucHJvdG90eXBlLl9yZW1vdmVGcm9tUGFyZW50c0NoaWxkTGlzdD1mdW5jdGlvbihlKXtkZWxldGUgdGhpcy5fY2hpbGRyZW5bdGhpcy5fcGFyZW50W2VdXVtlXX07Y3IucHJvdG90eXBlLnBhcmVudD1mdW5jdGlvbihlKXtpZih0aGlzLl9pc0NvbXBvdW5kKXt2YXIgdD10aGlzLl9wYXJlbnRbZV07aWYodCE9PUoxKXJldHVybiB0fX07Y3IucHJvdG90eXBlLmNoaWxkcmVuPWZ1bmN0aW9uKGUpe2lmKGplLmlzVW5kZWZpbmVkKGUpJiYoZT1KMSksdGhpcy5faXNDb21wb3VuZCl7dmFyIHQ9dGhpcy5fY2hpbGRyZW5bZV07aWYodClyZXR1cm4gamUua2V5cyh0KX1lbHNle2lmKGU9PT1KMSlyZXR1cm4gdGhpcy5ub2RlcygpO2lmKHRoaXMuaGFzTm9kZShlKSlyZXR1cm5bXX19O2NyLnByb3RvdHlwZS5wcmVkZWNlc3NvcnM9ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy5fcHJlZHNbZV07aWYodClyZXR1cm4gamUua2V5cyh0KX07Y3IucHJvdG90eXBlLnN1Y2Nlc3NvcnM9ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy5fc3Vjc1tlXTtpZih0KXJldHVybiBqZS5rZXlzKHQpfTtjci5wcm90b3R5cGUubmVpZ2hib3JzPWZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMucHJlZGVjZXNzb3JzKGUpO2lmKHQpcmV0dXJuIGplLnVuaW9uKHQsdGhpcy5zdWNjZXNzb3JzKGUpKX07Y3IucHJvdG90eXBlLmlzTGVhZj1mdW5jdGlvbihlKXt2YXIgdDtyZXR1cm4gdGhpcy5pc0RpcmVjdGVkKCk/dD10aGlzLnN1Y2Nlc3NvcnMoZSk6dD10aGlzLm5laWdoYm9ycyhlKSx0Lmxlbmd0aD09PTB9O2NyLnByb3RvdHlwZS5maWx0ZXJOb2Rlcz1mdW5jdGlvbihlKXt2YXIgdD1uZXcgdGhpcy5jb25zdHJ1Y3Rvcih7ZGlyZWN0ZWQ6dGhpcy5faXNEaXJlY3RlZCxtdWx0aWdyYXBoOnRoaXMuX2lzTXVsdGlncmFwaCxjb21wb3VuZDp0aGlzLl9pc0NvbXBvdW5kfSk7dC5zZXRHcmFwaCh0aGlzLmdyYXBoKCkpO3ZhciByPXRoaXM7amUuZWFjaCh0aGlzLl9ub2RlcyxmdW5jdGlvbihvLGEpe2UoYSkmJnQuc2V0Tm9kZShhLG8pfSksamUuZWFjaCh0aGlzLl9lZGdlT2JqcyxmdW5jdGlvbihvKXt0Lmhhc05vZGUoby52KSYmdC5oYXNOb2RlKG8udykmJnQuc2V0RWRnZShvLHIuZWRnZShvKSl9KTt2YXIgbj17fTtmdW5jdGlvbiBpKG8pe3ZhciBhPXIucGFyZW50KG8pO3JldHVybiBhPT09dm9pZCAwfHx0Lmhhc05vZGUoYSk/KG5bb109YSxhKTphIGluIG4/blthXTppKGEpfXJldHVybiB0aGlzLl9pc0NvbXBvdW5kJiZqZS5lYWNoKHQubm9kZXMoKSxmdW5jdGlvbihvKXt0LnNldFBhcmVudChvLGkobykpfSksdH07Y3IucHJvdG90eXBlLnNldERlZmF1bHRFZGdlTGFiZWw9ZnVuY3Rpb24oZSl7cmV0dXJuIGplLmlzRnVuY3Rpb24oZSl8fChlPWplLmNvbnN0YW50KGUpKSx0aGlzLl9kZWZhdWx0RWRnZUxhYmVsRm49ZSx0aGlzfTtjci5wcm90b3R5cGUuZWRnZUNvdW50PWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2VkZ2VDb3VudH07Y3IucHJvdG90eXBlLmVkZ2VzPWZ1bmN0aW9uKCl7cmV0dXJuIGplLnZhbHVlcyh0aGlzLl9lZGdlT2Jqcyl9O2NyLnByb3RvdHlwZS5zZXRQYXRoPWZ1bmN0aW9uKGUsdCl7dmFyIHI9dGhpcyxuPWFyZ3VtZW50cztyZXR1cm4gamUucmVkdWNlKGUsZnVuY3Rpb24oaSxvKXtyZXR1cm4gbi5sZW5ndGg+MT9yLnNldEVkZ2UoaSxvLHQpOnIuc2V0RWRnZShpLG8pLG99KSx0aGlzfTtjci5wcm90b3R5cGUuc2V0RWRnZT1mdW5jdGlvbigpe3ZhciBlLHQscixuLGk9ITEsbz1hcmd1bWVudHNbMF07dHlwZW9mIG89PSJvYmplY3QiJiZvIT09bnVsbCYmInYiaW4gbz8oZT1vLnYsdD1vLncscj1vLm5hbWUsYXJndW1lbnRzLmxlbmd0aD09PTImJihuPWFyZ3VtZW50c1sxXSxpPSEwKSk6KGU9byx0PWFyZ3VtZW50c1sxXSxyPWFyZ3VtZW50c1szXSxhcmd1bWVudHMubGVuZ3RoPjImJihuPWFyZ3VtZW50c1syXSxpPSEwKSksZT0iIitlLHQ9IiIrdCxqZS5pc1VuZGVmaW5lZChyKXx8KHI9IiIrcik7dmFyIGE9RzQodGhpcy5faXNEaXJlY3RlZCxlLHQscik7aWYoamUuaGFzKHRoaXMuX2VkZ2VMYWJlbHMsYSkpcmV0dXJuIGkmJih0aGlzLl9lZGdlTGFiZWxzW2FdPW4pLHRoaXM7aWYoIWplLmlzVW5kZWZpbmVkKHIpJiYhdGhpcy5faXNNdWx0aWdyYXBoKXRocm93IG5ldyBFcnJvcigiQ2Fubm90IHNldCBhIG5hbWVkIGVkZ2Ugd2hlbiBpc011bHRpZ3JhcGggPSBmYWxzZSIpO3RoaXMuc2V0Tm9kZShlKSx0aGlzLnNldE5vZGUodCksdGhpcy5fZWRnZUxhYmVsc1thXT1pP246dGhpcy5fZGVmYXVsdEVkZ2VMYWJlbEZuKGUsdCxyKTt2YXIgcz1CcnIodGhpcy5faXNEaXJlY3RlZCxlLHQscik7cmV0dXJuIGU9cy52LHQ9cy53LE9iamVjdC5mcmVlemUocyksdGhpcy5fZWRnZU9ianNbYV09cyxJbmUodGhpcy5fcHJlZHNbdF0sZSksSW5lKHRoaXMuX3N1Y3NbZV0sdCksdGhpcy5faW5bdF1bYV09cyx0aGlzLl9vdXRbZV1bYV09cyx0aGlzLl9lZGdlQ291bnQrKyx0aGlzfTtjci5wcm90b3R5cGUuZWRnZT1mdW5jdGlvbihlLHQscil7dmFyIG49YXJndW1lbnRzLmxlbmd0aD09PTE/aGx0KHRoaXMuX2lzRGlyZWN0ZWQsYXJndW1lbnRzWzBdKTpHNCh0aGlzLl9pc0RpcmVjdGVkLGUsdCxyKTtyZXR1cm4gdGhpcy5fZWRnZUxhYmVsc1tuXX07Y3IucHJvdG90eXBlLmhhc0VkZ2U9ZnVuY3Rpb24oZSx0LHIpe3ZhciBuPWFyZ3VtZW50cy5sZW5ndGg9PT0xP2hsdCh0aGlzLl9pc0RpcmVjdGVkLGFyZ3VtZW50c1swXSk6RzQodGhpcy5faXNEaXJlY3RlZCxlLHQscik7cmV0dXJuIGplLmhhcyh0aGlzLl9lZGdlTGFiZWxzLG4pfTtjci5wcm90b3R5cGUucmVtb3ZlRWRnZT1mdW5jdGlvbihlLHQscil7dmFyIG49YXJndW1lbnRzLmxlbmd0aD09PTE/aGx0KHRoaXMuX2lzRGlyZWN0ZWQsYXJndW1lbnRzWzBdKTpHNCh0aGlzLl9pc0RpcmVjdGVkLGUsdCxyKSxpPXRoaXMuX2VkZ2VPYmpzW25dO3JldHVybiBpJiYoZT1pLnYsdD1pLncsZGVsZXRlIHRoaXMuX2VkZ2VMYWJlbHNbbl0sZGVsZXRlIHRoaXMuX2VkZ2VPYmpzW25dLExuZSh0aGlzLl9wcmVkc1t0XSxlKSxMbmUodGhpcy5fc3Vjc1tlXSx0KSxkZWxldGUgdGhpcy5faW5bdF1bbl0sZGVsZXRlIHRoaXMuX291dFtlXVtuXSx0aGlzLl9lZGdlQ291bnQtLSksdGhpc307Y3IucHJvdG90eXBlLmluRWRnZXM9ZnVuY3Rpb24oZSx0KXt2YXIgcj10aGlzLl9pbltlXTtpZihyKXt2YXIgbj1qZS52YWx1ZXMocik7cmV0dXJuIHQ/amUuZmlsdGVyKG4sZnVuY3Rpb24oaSl7cmV0dXJuIGkudj09PXR9KTpufX07Y3IucHJvdG90eXBlLm91dEVkZ2VzPWZ1bmN0aW9uKGUsdCl7dmFyIHI9dGhpcy5fb3V0W2VdO2lmKHIpe3ZhciBuPWplLnZhbHVlcyhyKTtyZXR1cm4gdD9qZS5maWx0ZXIobixmdW5jdGlvbihpKXtyZXR1cm4gaS53PT09dH0pOm59fTtjci5wcm90b3R5cGUubm9kZUVkZ2VzPWZ1bmN0aW9uKGUsdCl7dmFyIHI9dGhpcy5pbkVkZ2VzKGUsdCk7aWYocilyZXR1cm4gci5jb25jYXQodGhpcy5vdXRFZGdlcyhlLHQpKX07ZnVuY3Rpb24gSW5lKGUsdCl7ZVt0XT9lW3RdKys6ZVt0XT0xfWZ1bmN0aW9uIExuZShlLHQpey0tZVt0XXx8ZGVsZXRlIGVbdF19ZnVuY3Rpb24gRzQoZSx0LHIsbil7dmFyIGk9IiIrdCxvPSIiK3I7aWYoIWUmJmk+byl7dmFyIGE9aTtpPW8sbz1hfXJldHVybiBpK1BuZStvK1BuZSsoamUuaXNVbmRlZmluZWQobik/RnJyOm4pfWZ1bmN0aW9uIEJycihlLHQscixuKXt2YXIgaT0iIit0LG89IiIrcjtpZighZSYmaT5vKXt2YXIgYT1pO2k9byxvPWF9dmFyIHM9e3Y6aSx3Om99O3JldHVybiBuJiYocy5uYW1lPW4pLHN9ZnVuY3Rpb24gaGx0KGUsdCl7cmV0dXJuIEc0KGUsdC52LHQudyx0Lm5hbWUpfX0pO3ZhciBObmU9SCgoQVpuLFJuZSk9PntSbmUuZXhwb3J0cz0iMi4xLjgifSk7dmFyIE9uZT1IKChQWm4sRG5lKT0+e0RuZS5leHBvcnRzPXtHcmFwaDplSCgpLHZlcnNpb246Tm5lKCl9fSk7dmFyIEZuZT1IKChJWm4sem5lKT0+e3ZhciBWZj1FbCgpLEhycj1lSCgpO3puZS5leHBvcnRzPXt3cml0ZTpWcnIscmVhZDpHcnJ9O2Z1bmN0aW9uIFZycihlKXt2YXIgdD17b3B0aW9uczp7ZGlyZWN0ZWQ6ZS5pc0RpcmVjdGVkKCksbXVsdGlncmFwaDplLmlzTXVsdGlncmFwaCgpLGNvbXBvdW5kOmUuaXNDb21wb3VuZCgpfSxub2RlczpVcnIoZSksZWRnZXM6cXJyKGUpfTtyZXR1cm4gVmYuaXNVbmRlZmluZWQoZS5ncmFwaCgpKXx8KHQudmFsdWU9VmYuY2xvbmUoZS5ncmFwaCgpKSksdH1mdW5jdGlvbiBVcnIoZSl7cmV0dXJuIFZmLm1hcChlLm5vZGVzKCksZnVuY3Rpb24odCl7dmFyIHI9ZS5ub2RlKHQpLG49ZS5wYXJlbnQodCksaT17djp0fTtyZXR1cm4gVmYuaXNVbmRlZmluZWQocil8fChpLnZhbHVlPXIpLFZmLmlzVW5kZWZpbmVkKG4pfHwoaS5wYXJlbnQ9biksaX0pfWZ1bmN0aW9uIHFycihlKXtyZXR1cm4gVmYubWFwKGUuZWRnZXMoKSxmdW5jdGlvbih0KXt2YXIgcj1lLmVkZ2UodCksbj17djp0LnYsdzp0Lnd9O3JldHVybiBWZi5pc1VuZGVmaW5lZCh0Lm5hbWUpfHwobi5uYW1lPXQubmFtZSksVmYuaXNVbmRlZmluZWQocil8fChuLnZhbHVlPXIpLG59KX1mdW5jdGlvbiBHcnIoZSl7dmFyIHQ9bmV3IEhycihlLm9wdGlvbnMpLnNldEdyYXBoKGUudmFsdWUpO3JldHVybiBWZi5lYWNoKGUubm9kZXMsZnVuY3Rpb24ocil7dC5zZXROb2RlKHIudixyLnZhbHVlKSxyLnBhcmVudCYmdC5zZXRQYXJlbnQoci52LHIucGFyZW50KX0pLFZmLmVhY2goZS5lZGdlcyxmdW5jdGlvbihyKXt0LnNldEVkZ2Uoe3Y6ci52LHc6ci53LG5hbWU6ci5uYW1lfSxyLnZhbHVlKX0pLHR9fSk7dmFyIEhuZT1IKChMWm4sQm5lKT0+e3ZhciBySD1FbCgpO0JuZS5leHBvcnRzPVdycjtmdW5jdGlvbiBXcnIoZSl7dmFyIHQ9e30scj1bXSxuO2Z1bmN0aW9uIGkobyl7ckguaGFzKHQsbyl8fCh0W29dPSEwLG4ucHVzaChvKSxySC5lYWNoKGUuc3VjY2Vzc29ycyhvKSxpKSxySC5lYWNoKGUucHJlZGVjZXNzb3JzKG8pLGkpKX1yZXR1cm4gckguZWFjaChlLm5vZGVzKCksZnVuY3Rpb24obyl7bj1bXSxpKG8pLG4ubGVuZ3RoJiZyLnB1c2gobil9KSxyfX0pO3ZhciBmbHQ9SCgoa1puLFVuZSk9Pnt2YXIgVm5lPUVsKCk7VW5lLmV4cG9ydHM9VmM7ZnVuY3Rpb24gVmMoKXt0aGlzLl9hcnI9W10sdGhpcy5fa2V5SW5kaWNlcz17fX1WYy5wcm90b3R5cGUuc2l6ZT1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9hcnIubGVuZ3RofTtWYy5wcm90b3R5cGUua2V5cz1mdW5jdGlvbigpe3JldHVybiB0aGlzLl9hcnIubWFwKGZ1bmN0aW9uKGUpe3JldHVybiBlLmtleX0pfTtWYy5wcm90b3R5cGUuaGFzPWZ1bmN0aW9uKGUpe3JldHVybiBWbmUuaGFzKHRoaXMuX2tleUluZGljZXMsZSl9O1ZjLnByb3RvdHlwZS5wcmlvcml0eT1mdW5jdGlvbihlKXt2YXIgdD10aGlzLl9rZXlJbmRpY2VzW2VdO2lmKHQhPT12b2lkIDApcmV0dXJuIHRoaXMuX2Fyclt0XS5wcmlvcml0eX07VmMucHJvdG90eXBlLm1pbj1mdW5jdGlvbigpe2lmKHRoaXMuc2l6ZSgpPT09MCl0aHJvdyBuZXcgRXJyb3IoIlF1ZXVlIHVuZGVyZmxvdyIpO3JldHVybiB0aGlzLl9hcnJbMF0ua2V5fTtWYy5wcm90b3R5cGUuYWRkPWZ1bmN0aW9uKGUsdCl7dmFyIHI9dGhpcy5fa2V5SW5kaWNlcztpZihlPVN0cmluZyhlKSwhVm5lLmhhcyhyLGUpKXt2YXIgbj10aGlzLl9hcnIsaT1uLmxlbmd0aDtyZXR1cm4gcltlXT1pLG4ucHVzaCh7a2V5OmUscHJpb3JpdHk6dH0pLHRoaXMuX2RlY3JlYXNlKGkpLCEwfXJldHVybiExfTtWYy5wcm90b3R5cGUucmVtb3ZlTWluPWZ1bmN0aW9uKCl7dGhpcy5fc3dhcCgwLHRoaXMuX2Fyci5sZW5ndGgtMSk7dmFyIGU9dGhpcy5fYXJyLnBvcCgpO3JldHVybiBkZWxldGUgdGhpcy5fa2V5SW5kaWNlc1tlLmtleV0sdGhpcy5faGVhcGlmeSgwKSxlLmtleX07VmMucHJvdG90eXBlLmRlY3JlYXNlPWZ1bmN0aW9uKGUsdCl7dmFyIHI9dGhpcy5fa2V5SW5kaWNlc1tlXTtpZih0PnRoaXMuX2FycltyXS5wcmlvcml0eSl0aHJvdyBuZXcgRXJyb3IoIk5ldyBwcmlvcml0eSBpcyBncmVhdGVyIHRoYW4gY3VycmVudCBwcmlvcml0eS4gS2V5OiAiK2UrIiBPbGQ6ICIrdGhpcy5fYXJyW3JdLnByaW9yaXR5KyIgTmV3OiAiK3QpO3RoaXMuX2FycltyXS5wcmlvcml0eT10LHRoaXMuX2RlY3JlYXNlKHIpfTtWYy5wcm90b3R5cGUuX2hlYXBpZnk9ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy5fYXJyLHI9MiplLG49cisxLGk9ZTtyPHQubGVuZ3RoJiYoaT10W3JdLnByaW9yaXR5PHRbaV0ucHJpb3JpdHk/cjppLG48dC5sZW5ndGgmJihpPXRbbl0ucHJpb3JpdHk8dFtpXS5wcmlvcml0eT9uOmkpLGkhPT1lJiYodGhpcy5fc3dhcChlLGkpLHRoaXMuX2hlYXBpZnkoaSkpKX07VmMucHJvdG90eXBlLl9kZWNyZWFzZT1mdW5jdGlvbihlKXtmb3IodmFyIHQ9dGhpcy5fYXJyLHI9dFtlXS5wcmlvcml0eSxuO2UhPT0wJiYobj1lPj4xLCEodFtuXS5wcmlvcml0eTxyKSk7KXRoaXMuX3N3YXAoZSxuKSxlPW59O1ZjLnByb3RvdHlwZS5fc3dhcD1mdW5jdGlvbihlLHQpe3ZhciByPXRoaXMuX2FycixuPXRoaXMuX2tleUluZGljZXMsaT1yW2VdLG89clt0XTtyW2VdPW8sclt0XT1pLG5bby5rZXldPWUsbltpLmtleV09dH19KTt2YXIgcGx0PUgoKFJabixxbmUpPT57dmFyIFlycj1FbCgpLGpycj1mbHQoKTtxbmUuZXhwb3J0cz0kcnI7dmFyIFhycj1ZcnIuY29uc3RhbnQoMSk7ZnVuY3Rpb24gJHJyKGUsdCxyLG4pe3JldHVybiBLcnIoZSxTdHJpbmcodCkscnx8WHJyLG58fGZ1bmN0aW9uKGkpe3JldHVybiBlLm91dEVkZ2VzKGkpfSl9ZnVuY3Rpb24gS3JyKGUsdCxyLG4pe3ZhciBpPXt9LG89bmV3IGpycixhLHMsbD1mdW5jdGlvbihjKXt2YXIgdT1jLnYhPT1hP2MudjpjLncsaD1pW3VdLGY9cihjKSxwPXMuZGlzdGFuY2UrZjtpZihmPDApdGhyb3cgbmV3IEVycm9yKCJkaWprc3RyYSBkb2VzIG5vdCBhbGxvdyBuZWdhdGl2ZSBlZGdlIHdlaWdodHMuIEJhZCBlZGdlOiAiK2MrIiBXZWlnaHQ6ICIrZik7cDxoLmRpc3RhbmNlJiYoaC5kaXN0YW5jZT1wLGgucHJlZGVjZXNzb3I9YSxvLmRlY3JlYXNlKHUscCkpfTtmb3IoZS5ub2RlcygpLmZvckVhY2goZnVuY3Rpb24oYyl7dmFyIHU9Yz09PXQ/MDpOdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFk7aVtjXT17ZGlzdGFuY2U6dX0sby5hZGQoYyx1KX0pO28uc2l6ZSgpPjAmJihhPW8ucmVtb3ZlTWluKCkscz1pW2FdLHMuZGlzdGFuY2UhPT1OdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFkpOyluKGEpLmZvckVhY2gobCk7cmV0dXJuIGl9fSk7dmFyIFduZT1IKChOWm4sR25lKT0+e3ZhciBacnI9cGx0KCksSnJyPUVsKCk7R25lLmV4cG9ydHM9UXJyO2Z1bmN0aW9uIFFycihlLHQscil7cmV0dXJuIEpyci50cmFuc2Zvcm0oZS5ub2RlcygpLGZ1bmN0aW9uKG4saSl7bltpXT1acnIoZSxpLHQscil9LHt9KX19KTt2YXIgZGx0PUgoKERabixqbmUpPT57dmFyIFluZT1FbCgpO2puZS5leHBvcnRzPXRucjtmdW5jdGlvbiB0bnIoZSl7dmFyIHQ9MCxyPVtdLG49e30saT1bXTtmdW5jdGlvbiBvKGEpe3ZhciBzPW5bYV09e29uU3RhY2s6ITAsbG93bGluazp0LGluZGV4OnQrK307aWYoci5wdXNoKGEpLGUuc3VjY2Vzc29ycyhhKS5mb3JFYWNoKGZ1bmN0aW9uKHUpe1luZS5oYXMobix1KT9uW3VdLm9uU3RhY2smJihzLmxvd2xpbms9TWF0aC5taW4ocy5sb3dsaW5rLG5bdV0uaW5kZXgpKToobyh1KSxzLmxvd2xpbms9TWF0aC5taW4ocy5sb3dsaW5rLG5bdV0ubG93bGluaykpfSkscy5sb3dsaW5rPT09cy5pbmRleCl7dmFyIGw9W10sYztkbyBjPXIucG9wKCksbltjXS5vblN0YWNrPSExLGwucHVzaChjKTt3aGlsZShhIT09Yyk7aS5wdXNoKGwpfX1yZXR1cm4gZS5ub2RlcygpLmZvckVhY2goZnVuY3Rpb24oYSl7WW5lLmhhcyhuLGEpfHxvKGEpfSksaX19KTt2YXIgJG5lPUgoKE9abixYbmUpPT57dmFyIGVucj1FbCgpLHJucj1kbHQoKTtYbmUuZXhwb3J0cz1ubnI7ZnVuY3Rpb24gbm5yKGUpe3JldHVybiBlbnIuZmlsdGVyKHJucihlKSxmdW5jdGlvbih0KXtyZXR1cm4gdC5sZW5ndGg+MXx8dC5sZW5ndGg9PT0xJiZlLmhhc0VkZ2UodFswXSx0WzBdKX0pfX0pO3ZhciBabmU9SCgoelpuLEtuZSk9Pnt2YXIgaW5yPUVsKCk7S25lLmV4cG9ydHM9YW5yO3ZhciBvbnI9aW5yLmNvbnN0YW50KDEpO2Z1bmN0aW9uIGFucihlLHQscil7cmV0dXJuIHNucihlLHR8fG9ucixyfHxmdW5jdGlvbihuKXtyZXR1cm4gZS5vdXRFZGdlcyhuKX0pfWZ1bmN0aW9uIHNucihlLHQscil7dmFyIG49e30saT1lLm5vZGVzKCk7cmV0dXJuIGkuZm9yRWFjaChmdW5jdGlvbihvKXtuW29dPXt9LG5bb11bb109e2Rpc3RhbmNlOjB9LGkuZm9yRWFjaChmdW5jdGlvbihhKXtvIT09YSYmKG5bb11bYV09e2Rpc3RhbmNlOk51bWJlci5QT1NJVElWRV9JTkZJTklUWX0pfSkscihvKS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBzPWEudj09PW8/YS53OmEudixsPXQoYSk7bltvXVtzXT17ZGlzdGFuY2U6bCxwcmVkZWNlc3NvcjpvfX0pfSksaS5mb3JFYWNoKGZ1bmN0aW9uKG8pe3ZhciBhPW5bb107aS5mb3JFYWNoKGZ1bmN0aW9uKHMpe3ZhciBsPW5bc107aS5mb3JFYWNoKGZ1bmN0aW9uKGMpe3ZhciB1PWxbb10saD1hW2NdLGY9bFtjXSxwPXUuZGlzdGFuY2UraC5kaXN0YW5jZTtwPGYuZGlzdGFuY2UmJihmLmRpc3RhbmNlPXAsZi5wcmVkZWNlc3Nvcj1oLnByZWRlY2Vzc29yKX0pfSl9KSxufX0pO3ZhciBtbHQ9SCgoRlpuLFFuZSk9Pnt2YXIgVzQ9RWwoKTtRbmUuZXhwb3J0cz1KbmU7Sm5lLkN5Y2xlRXhjZXB0aW9uPW5IO2Z1bmN0aW9uIEpuZShlKXt2YXIgdD17fSxyPXt9LG49W107ZnVuY3Rpb24gaShvKXtpZihXNC5oYXMocixvKSl0aHJvdyBuZXcgbkg7VzQuaGFzKHQsbyl8fChyW29dPSEwLHRbb109ITAsVzQuZWFjaChlLnByZWRlY2Vzc29ycyhvKSxpKSxkZWxldGUgcltvXSxuLnB1c2gobykpfWlmKFc0LmVhY2goZS5zaW5rcygpLGkpLFc0LnNpemUodCkhPT1lLm5vZGVDb3VudCgpKXRocm93IG5ldyBuSDtyZXR1cm4gbn1mdW5jdGlvbiBuSCgpe31uSC5wcm90b3R5cGU9bmV3IEVycm9yfSk7dmFyIHJpZT1IKChCWm4sZWllKT0+e3ZhciB0aWU9bWx0KCk7ZWllLmV4cG9ydHM9bG5yO2Z1bmN0aW9uIGxucihlKXt0cnl7dGllKGUpfWNhdGNoKHQpe2lmKHQgaW5zdGFuY2VvZiB0aWUuQ3ljbGVFeGNlcHRpb24pcmV0dXJuITE7dGhyb3cgdH1yZXR1cm4hMH19KTt2YXIgZ2x0PUgoKEhabixpaWUpPT57dmFyIGlIPUVsKCk7aWllLmV4cG9ydHM9Y25yO2Z1bmN0aW9uIGNucihlLHQscil7aUguaXNBcnJheSh0KXx8KHQ9W3RdKTt2YXIgbj0oZS5pc0RpcmVjdGVkKCk/ZS5zdWNjZXNzb3JzOmUubmVpZ2hib3JzKS5iaW5kKGUpLGk9W10sbz17fTtyZXR1cm4gaUguZWFjaCh0LGZ1bmN0aW9uKGEpe2lmKCFlLmhhc05vZGUoYSkpdGhyb3cgbmV3IEVycm9yKCJHcmFwaCBkb2VzIG5vdCBoYXZlIG5vZGU6ICIrYSk7bmllKGUsYSxyPT09InBvc3QiLG8sbixpKX0pLGl9ZnVuY3Rpb24gbmllKGUsdCxyLG4saSxvKXtpSC5oYXMobix0KXx8KG5bdF09ITAscnx8by5wdXNoKHQpLGlILmVhY2goaSh0KSxmdW5jdGlvbihhKXtuaWUoZSxhLHIsbixpLG8pfSksciYmby5wdXNoKHQpKX19KTt2YXIgYWllPUgoKFZabixvaWUpPT57dmFyIHVucj1nbHQoKTtvaWUuZXhwb3J0cz1obnI7ZnVuY3Rpb24gaG5yKGUsdCl7cmV0dXJuIHVucihlLHQsInBvc3QiKX19KTt2YXIgbGllPUgoKFVabixzaWUpPT57dmFyIGZucj1nbHQoKTtzaWUuZXhwb3J0cz1wbnI7ZnVuY3Rpb24gcG5yKGUsdCl7cmV0dXJuIGZucihlLHQsInByZSIpfX0pO3ZhciBoaWU9SCgocVpuLHVpZSk9Pnt2YXIgY2llPUVsKCksZG5yPWVIKCksbW5yPWZsdCgpO3VpZS5leHBvcnRzPWducjtmdW5jdGlvbiBnbnIoZSx0KXt2YXIgcj1uZXcgZG5yLG49e30saT1uZXcgbW5yLG87ZnVuY3Rpb24gYShsKXt2YXIgYz1sLnY9PT1vP2wudzpsLnYsdT1pLnByaW9yaXR5KGMpO2lmKHUhPT12b2lkIDApe3ZhciBoPXQobCk7aDx1JiYobltjXT1vLGkuZGVjcmVhc2UoYyxoKSl9fWlmKGUubm9kZUNvdW50KCk9PT0wKXJldHVybiByO2NpZS5lYWNoKGUubm9kZXMoKSxmdW5jdGlvbihsKXtpLmFkZChsLE51bWJlci5QT1NJVElWRV9JTkZJTklUWSksci5zZXROb2RlKGwpfSksaS5kZWNyZWFzZShlLm5vZGVzKClbMF0sMCk7Zm9yKHZhciBzPSExO2kuc2l6ZSgpPjA7KXtpZihvPWkucmVtb3ZlTWluKCksY2llLmhhcyhuLG8pKXIuc2V0RWRnZShvLG5bb10pO2Vsc2V7aWYocyl0aHJvdyBuZXcgRXJyb3IoIklucHV0IGdyYXBoIGlzIG5vdCBjb25uZWN0ZWQ6ICIrZSk7cz0hMH1lLm5vZGVFZGdlcyhvKS5mb3JFYWNoKGEpfXJldHVybiByfX0pO3ZhciBwaWU9SCgoR1puLGZpZSk9PntmaWUuZXhwb3J0cz17Y29tcG9uZW50czpIbmUoKSxkaWprc3RyYTpwbHQoKSxkaWprc3RyYUFsbDpXbmUoKSxmaW5kQ3ljbGVzOiRuZSgpLGZsb3lkV2Fyc2hhbGw6Wm5lKCksaXNBY3ljbGljOnJpZSgpLHBvc3RvcmRlcjphaWUoKSxwcmVvcmRlcjpsaWUoKSxwcmltOmhpZSgpLHRhcmphbjpkbHQoKSx0b3Bzb3J0Om1sdCgpfX0pO3ZhciBnaWU9SCgoV1puLG1pZSk9Pnt2YXIgZGllPU9uZSgpO21pZS5leHBvcnRzPXtHcmFwaDpkaWUuR3JhcGgsanNvbjpGbmUoKSxhbGc6cGllKCksdmVyc2lvbjpkaWUudmVyc2lvbn19KTt2YXIgVWM9SCgoWVpuLF9pZSk9Pnt2YXIgb0g7aWYodHlwZW9mIEV4PT0iZnVuY3Rpb24iKXRyeXtvSD1naWUoKX1jYXRjaChlKXt9b0h8fChvSD13aW5kb3cuZ3JhcGhsaWIpO19pZS5leHBvcnRzPW9IfSk7dmFyIHZpZT1IKChYWm4seWllKT0+e3ZhciBfbnI9TnN0KCkseW5yPTEsdm5yPTQ7ZnVuY3Rpb24geG5yKGUpe3JldHVybiBfbnIoZSx5bnJ8dm5yKX15aWUuZXhwb3J0cz14bnJ9KTt2YXIgWTQ9SCgoJFpuLHhpZSk9Pnt2YXIgYm5yPVkxKCksd25yPUJmKCksU25yPWs0KCksTW5yPU1sKCk7ZnVuY3Rpb24gRW5yKGUsdCxyKXtpZighTW5yKHIpKXJldHVybiExO3ZhciBuPXR5cGVvZiB0O3JldHVybihuPT0ibnVtYmVyIj93bnIocikmJlNucih0LHIubGVuZ3RoKTpuPT0ic3RyaW5nIiYmdCBpbiByKT9ibnIoclt0XSxlKTohMX14aWUuZXhwb3J0cz1FbnJ9KTt2YXIgU2llPUgoKEtabix3aWUpPT57dmFyIFRucj1xNCgpLENucj1ZMSgpLEFucj1ZNCgpLFBucj1jMCgpLGJpZT1PYmplY3QucHJvdG90eXBlLElucj1iaWUuaGFzT3duUHJvcGVydHksTG5yPVRucihmdW5jdGlvbihlLHQpe2U9T2JqZWN0KGUpO3ZhciByPS0xLG49dC5sZW5ndGgsaT1uPjI/dFsyXTp2b2lkIDA7Zm9yKGkmJkFucih0WzBdLHRbMV0saSkmJihuPTEpOysrcjxuOylmb3IodmFyIG89dFtyXSxhPVBucihvKSxzPS0xLGw9YS5sZW5ndGg7KytzPGw7KXt2YXIgYz1hW3NdLHU9ZVtjXTsodT09PXZvaWQgMHx8Q25yKHUsYmllW2NdKSYmIUluci5jYWxsKGUsYykpJiYoZVtjXT1vW2NdKX1yZXR1cm4gZX0pO3dpZS5leHBvcnRzPUxucn0pO3ZhciBFaWU9SCgoWlpuLE1pZSk9Pnt2YXIga25yPUhmKCksUm5yPUJmKCksTm5yPUFkKCk7ZnVuY3Rpb24gRG5yKGUpe3JldHVybiBmdW5jdGlvbih0LHIsbil7dmFyIGk9T2JqZWN0KHQpO2lmKCFSbnIodCkpe3ZhciBvPWtucihyLDMpO3Q9Tm5yKHQpLHI9ZnVuY3Rpb24ocyl7cmV0dXJuIG8oaVtzXSxzLGkpfX12YXIgYT1lKHQscixuKTtyZXR1cm4gYT4tMT9pW28/dFthXTphXTp2b2lkIDB9fU1pZS5leHBvcnRzPURucn0pO3ZhciBDaWU9SCgoSlpuLFRpZSk9Pnt2YXIgT25yPS9ccy87ZnVuY3Rpb24gem5yKGUpe2Zvcih2YXIgdD1lLmxlbmd0aDt0LS0mJk9uci50ZXN0KGUuY2hhckF0KHQpKTspO3JldHVybiB0fVRpZS5leHBvcnRzPXpucn0pO3ZhciBQaWU9SCgoUVpuLEFpZSk9Pnt2YXIgRm5yPUNpZSgpLEJucj0vXlxzKy87ZnVuY3Rpb24gSG5yKGUpe3JldHVybiBlJiZlLnNsaWNlKDAsRm5yKGUpKzEpLnJlcGxhY2UoQm5yLCIiKX1BaWUuZXhwb3J0cz1IbnJ9KTt2YXIgUmllPUgoKHRKbixraWUpPT57dmFyIFZucj1QaWUoKSxJaWU9TWwoKSxVbnI9WjEoKSxMaWU9MC8wLHFucj0vXlstK10weFswLTlhLWZdKyQvaSxHbnI9L14wYlswMV0rJC9pLFducj0vXjBvWzAtN10rJC9pLFlucj1wYXJzZUludDtmdW5jdGlvbiBqbnIoZSl7aWYodHlwZW9mIGU9PSJudW1iZXIiKXJldHVybiBlO2lmKFVucihlKSlyZXR1cm4gTGllO2lmKElpZShlKSl7dmFyIHQ9dHlwZW9mIGUudmFsdWVPZj09ImZ1bmN0aW9uIj9lLnZhbHVlT2YoKTplO2U9SWllKHQpP3QrIiI6dH1pZih0eXBlb2YgZSE9InN0cmluZyIpcmV0dXJuIGU9PT0wP2U6K2U7ZT1WbnIoZSk7dmFyIHI9R25yLnRlc3QoZSk7cmV0dXJuIHJ8fFduci50ZXN0KGUpP1lucihlLnNsaWNlKDIpLHI/Mjo4KTpxbnIudGVzdChlKT9MaWU6K2V9a2llLmV4cG9ydHM9am5yfSk7dmFyIF9sdD1IKChlSm4sRGllKT0+e3ZhciBYbnI9UmllKCksTmllPTEvMCwkbnI9MTc5NzY5MzEzNDg2MjMxNTdlMjkyO2Z1bmN0aW9uIEtucihlKXtpZighZSlyZXR1cm4gZT09PTA/ZTowO2lmKGU9WG5yKGUpLGU9PT1OaWV8fGU9PT0tTmllKXt2YXIgdD1lPDA/LTE6MTtyZXR1cm4gdCokbnJ9cmV0dXJuIGU9PT1lP2U6MH1EaWUuZXhwb3J0cz1LbnJ9KTt2YXIgemllPUgoKHJKbixPaWUpPT57dmFyIFpucj1fbHQoKTtmdW5jdGlvbiBKbnIoZSl7dmFyIHQ9Wm5yKGUpLHI9dCUxO3JldHVybiB0PT09dD9yP3Qtcjp0OjB9T2llLmV4cG9ydHM9Sm5yfSk7dmFyIEJpZT1IKChuSm4sRmllKT0+e3ZhciBRbnI9c2x0KCksdGlyPUhmKCksZWlyPXppZSgpLHJpcj1NYXRoLm1heDtmdW5jdGlvbiBuaXIoZSx0LHIpe3ZhciBuPWU9PW51bGw/MDplLmxlbmd0aDtpZighbilyZXR1cm4tMTt2YXIgaT1yPT1udWxsPzA6ZWlyKHIpO3JldHVybiBpPDAmJihpPXJpcihuK2ksMCkpLFFucihlLHRpcih0LDMpLGkpfUZpZS5leHBvcnRzPW5pcn0pO3ZhciBWaWU9SCgoaUpuLEhpZSk9Pnt2YXIgaWlyPUVpZSgpLG9pcj1CaWUoKSxhaXI9aWlyKG9pcik7SGllLmV4cG9ydHM9YWlyfSk7dmFyIHlsdD1IKChvSm4sVWllKT0+e3ZhciBzaXI9UUIoKTtmdW5jdGlvbiBsaXIoZSl7dmFyIHQ9ZT09bnVsbD8wOmUubGVuZ3RoO3JldHVybiB0P3NpcihlLDEpOltdfVVpZS5leHBvcnRzPWxpcn0pO3ZhciBHaWU9SCgoYUpuLHFpZSk9Pnt2YXIgY2lyPWpCKCksdWlyPURzdCgpLGhpcj1jMCgpO2Z1bmN0aW9uIGZpcihlLHQpe3JldHVybiBlPT1udWxsP2U6Y2lyKGUsdWlyKHQpLGhpcil9cWllLmV4cG9ydHM9ZmlyfSk7dmFyIFlpZT1IKChzSm4sV2llKT0+e2Z1bmN0aW9uIHBpcihlKXt2YXIgdD1lPT1udWxsPzA6ZS5sZW5ndGg7cmV0dXJuIHQ/ZVt0LTFdOnZvaWQgMH1XaWUuZXhwb3J0cz1waXJ9KTt2YXIgWGllPUgoKGxKbixqaWUpPT57dmFyIGRpcj1QNCgpLG1pcj1YQigpLGdpcj1IZigpO2Z1bmN0aW9uIF9pcihlLHQpe3ZhciByPXt9O3JldHVybiB0PWdpcih0LDMpLG1pcihlLGZ1bmN0aW9uKG4saSxvKXtkaXIocixpLHQobixpLG8pKX0pLHJ9amllLmV4cG9ydHM9X2lyfSk7dmFyIGFIPUgoKGNKbiwkaWUpPT57dmFyIHlpcj1aMSgpO2Z1bmN0aW9uIHZpcihlLHQscil7Zm9yKHZhciBuPS0xLGk9ZS5sZW5ndGg7KytuPGk7KXt2YXIgbz1lW25dLGE9dChvKTtpZihhIT1udWxsJiYocz09PXZvaWQgMD9hPT09YSYmIXlpcihhKTpyKGEscykpKXZhciBzPWEsbD1vfXJldHVybiBsfSRpZS5leHBvcnRzPXZpcn0pO3ZhciBaaWU9SCgodUpuLEtpZSk9PntmdW5jdGlvbiB4aXIoZSx0KXtyZXR1cm4gZT50fUtpZS5leHBvcnRzPXhpcn0pO3ZhciBRaWU9SCgoaEpuLEppZSk9Pnt2YXIgYmlyPWFIKCksd2lyPVppZSgpLFNpcj11MCgpO2Z1bmN0aW9uIE1pcihlKXtyZXR1cm4gZSYmZS5sZW5ndGg/YmlyKGUsU2lyLHdpcik6dm9pZCAwfUppZS5leHBvcnRzPU1pcn0pO3ZhciB2bHQ9SCgoZkpuLHRvZSk9Pnt2YXIgRWlyPVA0KCksVGlyPVkxKCk7ZnVuY3Rpb24gQ2lyKGUsdCxyKXsociE9PXZvaWQgMCYmIVRpcihlW3RdLHIpfHxyPT09dm9pZCAwJiYhKHQgaW4gZSkpJiZFaXIoZSx0LHIpfXRvZS5leHBvcnRzPUNpcn0pO3ZhciBub2U9SCgocEpuLHJvZSk9Pnt2YXIgQWlyPXMwKCksUGlyPUY0KCksSWlyPVl1KCksTGlyPSJbb2JqZWN0IE9iamVjdF0iLGtpcj1GdW5jdGlvbi5wcm90b3R5cGUsUmlyPU9iamVjdC5wcm90b3R5cGUsZW9lPWtpci50b1N0cmluZyxOaXI9UmlyLmhhc093blByb3BlcnR5LERpcj1lb2UuY2FsbChPYmplY3QpO2Z1bmN0aW9uIE9pcihlKXtpZighSWlyKGUpfHxBaXIoZSkhPUxpcilyZXR1cm4hMTt2YXIgdD1QaXIoZSk7aWYodD09PW51bGwpcmV0dXJuITA7dmFyIHI9TmlyLmNhbGwodCwiY29uc3RydWN0b3IiKSYmdC5jb25zdHJ1Y3RvcjtyZXR1cm4gdHlwZW9mIHI9PSJmdW5jdGlvbiImJnIgaW5zdGFuY2VvZiByJiZlb2UuY2FsbChyKT09RGlyfXJvZS5leHBvcnRzPU9pcn0pO3ZhciB4bHQ9SCgoZEpuLGlvZSk9PntmdW5jdGlvbiB6aXIoZSx0KXtpZighKHQ9PT0iY29uc3RydWN0b3IiJiZ0eXBlb2YgZVt0XT09ImZ1bmN0aW9uIikmJnQhPSJfX3Byb3RvX18iKXJldHVybiBlW3RdfWlvZS5leHBvcnRzPXppcn0pO3ZhciBhb2U9SCgobUpuLG9vZSk9Pnt2YXIgRmlyPVVTKCksQmlyPWMwKCk7ZnVuY3Rpb24gSGlyKGUpe3JldHVybiBGaXIoZSxCaXIoZSkpfW9vZS5leHBvcnRzPUhpcn0pO3ZhciBmb2U9SCgoZ0puLGhvZSk9Pnt2YXIgc29lPXZsdCgpLFZpcj1fc3QoKSxVaXI9THN0KCkscWlyPXlzdCgpLEdpcj1Sc3QoKSxsb2U9cVMoKSxjb2U9VGkoKSxXaXI9Y2x0KCksWWlyPVgxKCksamlyPUZTKCksWGlyPU1sKCksJGlyPW5vZSgpLEtpcj1ZUygpLHVvZT14bHQoKSxaaXI9YW9lKCk7ZnVuY3Rpb24gSmlyKGUsdCxyLG4saSxvLGEpe3ZhciBzPXVvZShlLHIpLGw9dW9lKHQsciksYz1hLmdldChsKTtpZihjKXtzb2UoZSxyLGMpO3JldHVybn12YXIgdT1vP28ocyxsLHIrIiIsZSx0LGEpOnZvaWQgMCxoPXU9PT12b2lkIDA7aWYoaCl7dmFyIGY9Y29lKGwpLHA9IWYmJllpcihsKSxkPSFmJiYhcCYmS2lyKGwpO3U9bCxmfHxwfHxkP2NvZShzKT91PXM6V2lyKHMpP3U9cWlyKHMpOnA/KGg9ITEsdT1WaXIobCwhMCkpOmQ/KGg9ITEsdT1VaXIobCwhMCkpOnU9W106JGlyKGwpfHxsb2UobCk/KHU9cyxsb2Uocyk/dT1aaXIocyk6KCFYaXIocyl8fGppcihzKSkmJih1PUdpcihsKSkpOmg9ITF9aCYmKGEuc2V0KGwsdSksaSh1LGwsbixvLGEpLGEuZGVsZXRlKGwpKSxzb2UoZSxyLHUpfWhvZS5leHBvcnRzPUppcn0pO3ZhciBtb2U9SCgoX0puLGRvZSk9Pnt2YXIgUWlyPUE0KCksdG9yPXZsdCgpLGVvcj1qQigpLHJvcj1mb2UoKSxub3I9TWwoKSxpb3I9YzAoKSxvb3I9eGx0KCk7ZnVuY3Rpb24gcG9lKGUsdCxyLG4saSl7ZSE9PXQmJmVvcih0LGZ1bmN0aW9uKG8sYSl7aWYoaXx8KGk9bmV3IFFpciksbm9yKG8pKXJvcihlLHQsYSxyLHBvZSxuLGkpO2Vsc2V7dmFyIHM9bj9uKG9vcihlLGEpLG8sYSsiIixlLHQsaSk6dm9pZCAwO3M9PT12b2lkIDAmJihzPW8pLHRvcihlLGEscyl9fSxpb3IpfWRvZS5leHBvcnRzPXBvZX0pO3ZhciBfb2U9SCgoeUpuLGdvZSk9Pnt2YXIgYW9yPXE0KCksc29yPVk0KCk7ZnVuY3Rpb24gbG9yKGUpe3JldHVybiBhb3IoZnVuY3Rpb24odCxyKXt2YXIgbj0tMSxpPXIubGVuZ3RoLG89aT4xP3JbaS0xXTp2b2lkIDAsYT1pPjI/clsyXTp2b2lkIDA7Zm9yKG89ZS5sZW5ndGg+MyYmdHlwZW9mIG89PSJmdW5jdGlvbiI/KGktLSxvKTp2b2lkIDAsYSYmc29yKHJbMF0sclsxXSxhKSYmKG89aTwzP3ZvaWQgMDpvLGk9MSksdD1PYmplY3QodCk7KytuPGk7KXt2YXIgcz1yW25dO3MmJmUodCxzLG4sbyl9cmV0dXJuIHR9KX1nb2UuZXhwb3J0cz1sb3J9KTt2YXIgdm9lPUgoKHZKbix5b2UpPT57dmFyIGNvcj1tb2UoKSx1b3I9X29lKCksaG9yPXVvcihmdW5jdGlvbihlLHQscil7Y29yKGUsdCxyKX0pO3lvZS5leHBvcnRzPWhvcn0pO3ZhciBibHQ9SCgoeEpuLHhvZSk9PntmdW5jdGlvbiBwb3IoZSx0KXtyZXR1cm4gZTx0fXhvZS5leHBvcnRzPXBvcn0pO3ZhciB3b2U9SCgoYkpuLGJvZSk9Pnt2YXIgZG9yPWFIKCksbW9yPWJsdCgpLGdvcj11MCgpO2Z1bmN0aW9uIF9vcihlKXtyZXR1cm4gZSYmZS5sZW5ndGg/ZG9yKGUsZ29yLG1vcik6dm9pZCAwfWJvZS5leHBvcnRzPV9vcn0pO3ZhciBNb2U9SCgod0puLFNvZSk9Pnt2YXIgeW9yPWFIKCksdm9yPUhmKCkseG9yPWJsdCgpO2Z1bmN0aW9uIGJvcihlLHQpe3JldHVybiBlJiZlLmxlbmd0aD95b3IoZSx2b3IodCwyKSx4b3IpOnZvaWQgMH1Tb2UuZXhwb3J0cz1ib3J9KTt2YXIgVG9lPUgoKFNKbixFb2UpPT57dmFyIHdvcj1IYygpLFNvcj1mdW5jdGlvbigpe3JldHVybiB3b3IuRGF0ZS5ub3coKX07RW9lLmV4cG9ydHM9U29yfSk7dmFyIFBvZT1IKChNSm4sQW9lKT0+e3ZhciBNb3I9STQoKSxFb3I9VjQoKSxUb3I9azQoKSxDb2U9TWwoKSxDb3I9JFMoKTtmdW5jdGlvbiBBb3IoZSx0LHIsbil7aWYoIUNvZShlKSlyZXR1cm4gZTt0PUVvcih0LGUpO2Zvcih2YXIgaT0tMSxvPXQubGVuZ3RoLGE9by0xLHM9ZTtzIT1udWxsJiYrK2k8bzspe3ZhciBsPUNvcih0W2ldKSxjPXI7aWYobD09PSJfX3Byb3RvX18ifHxsPT09ImNvbnN0cnVjdG9yInx8bD09PSJwcm90b3R5cGUiKXJldHVybiBlO2lmKGkhPWEpe3ZhciB1PXNbbF07Yz1uP24odSxsLHMpOnZvaWQgMCxjPT09dm9pZCAwJiYoYz1Db2UodSk/dTpUb3IodFtpKzFdKT9bXTp7fSl9TW9yKHMsbCxjKSxzPXNbbF19cmV0dXJuIGV9QW9lLmV4cG9ydHM9QW9yfSk7dmFyIExvZT1IKChFSm4sSW9lKT0+e3ZhciBQb3I9VTQoKSxJb3I9UG9lKCksTG9yPVY0KCk7ZnVuY3Rpb24ga29yKGUsdCxyKXtmb3IodmFyIG49LTEsaT10Lmxlbmd0aCxvPXt9OysrbjxpOyl7dmFyIGE9dFtuXSxzPVBvcihlLGEpO3IocyxhKSYmSW9yKG8sTG9yKGEsZSkscyl9cmV0dXJuIG99SW9lLmV4cG9ydHM9a29yfSk7dmFyIFJvZT1IKChUSm4sa29lKT0+e3ZhciBSb3I9TG9lKCksTm9yPSRzdCgpO2Z1bmN0aW9uIERvcihlLHQpe3JldHVybiBSb3IoZSx0LGZ1bmN0aW9uKHIsbil7cmV0dXJuIE5vcihlLG4pfSl9a29lLmV4cG9ydHM9RG9yfSk7dmFyIERvZT1IKChDSm4sTm9lKT0+e3ZhciBPb3I9eWx0KCksem9yPW9sdCgpLEZvcj1hbHQoKTtmdW5jdGlvbiBCb3IoZSl7cmV0dXJuIEZvcih6b3IoZSx2b2lkIDAsT29yKSxlKyIiKX1Ob2UuZXhwb3J0cz1Cb3J9KTt2YXIgem9lPUgoKEFKbixPb2UpPT57dmFyIEhvcj1Sb2UoKSxWb3I9RG9lKCksVW9yPVZvcihmdW5jdGlvbihlLHQpe3JldHVybiBlPT1udWxsP3t9OkhvcihlLHQpfSk7T29lLmV4cG9ydHM9VW9yfSk7dmFyIEJvZT1IKChQSm4sRm9lKT0+e3ZhciBxb3I9TWF0aC5jZWlsLEdvcj1NYXRoLm1heDtmdW5jdGlvbiBXb3IoZSx0LHIsbil7Zm9yKHZhciBpPS0xLG89R29yKHFvcigodC1lKS8ocnx8MSkpLDApLGE9QXJyYXkobyk7by0tOylhW24/bzorK2ldPWUsZSs9cjtyZXR1cm4gYX1Gb2UuZXhwb3J0cz1Xb3J9KTt2YXIgVm9lPUgoKElKbixIb2UpPT57dmFyIFlvcj1Cb2UoKSxqb3I9WTQoKSx3bHQ9X2x0KCk7ZnVuY3Rpb24gWG9yKGUpe3JldHVybiBmdW5jdGlvbih0LHIsbil7cmV0dXJuIG4mJnR5cGVvZiBuIT0ibnVtYmVyIiYmam9yKHQscixuKSYmKHI9bj12b2lkIDApLHQ9d2x0KHQpLHI9PT12b2lkIDA/KHI9dCx0PTApOnI9d2x0KHIpLG49bj09PXZvaWQgMD90PHI/MTotMTp3bHQobiksWW9yKHQscixuLGUpfX1Ib2UuZXhwb3J0cz1Yb3J9KTt2YXIgcW9lPUgoKExKbixVb2UpPT57dmFyICRvcj1Wb2UoKSxLb3I9JG9yKCk7VW9lLmV4cG9ydHM9S29yfSk7dmFyIFdvZT1IKChrSm4sR29lKT0+e2Z1bmN0aW9uIFpvcihlLHQpe3ZhciByPWUubGVuZ3RoO2ZvcihlLnNvcnQodCk7ci0tOyllW3JdPWVbcl0udmFsdWU7cmV0dXJuIGV9R29lLmV4cG9ydHM9Wm9yfSk7dmFyIFhvZT1IKChSSm4sam9lKT0+e3ZhciBZb2U9WjEoKTtmdW5jdGlvbiBKb3IoZSx0KXtpZihlIT09dCl7dmFyIHI9ZSE9PXZvaWQgMCxuPWU9PT1udWxsLGk9ZT09PWUsbz1Zb2UoZSksYT10IT09dm9pZCAwLHM9dD09PW51bGwsbD10PT09dCxjPVlvZSh0KTtpZighcyYmIWMmJiFvJiZlPnR8fG8mJmEmJmwmJiFzJiYhY3x8biYmYSYmbHx8IXImJmx8fCFpKXJldHVybiAxO2lmKCFuJiYhbyYmIWMmJmU8dHx8YyYmciYmaSYmIW4mJiFvfHxzJiZyJiZpfHwhYSYmaXx8IWwpcmV0dXJuLTF9cmV0dXJuIDB9am9lLmV4cG9ydHM9Sm9yfSk7dmFyIEtvZT1IKChOSm4sJG9lKT0+e3ZhciBRb3I9WG9lKCk7ZnVuY3Rpb24gdGFyKGUsdCxyKXtmb3IodmFyIG49LTEsaT1lLmNyaXRlcmlhLG89dC5jcml0ZXJpYSxhPWkubGVuZ3RoLHM9ci5sZW5ndGg7KytuPGE7KXt2YXIgbD1Rb3IoaVtuXSxvW25dKTtpZihsKXtpZihuPj1zKXJldHVybiBsO3ZhciBjPXJbbl07cmV0dXJuIGwqKGM9PSJkZXNjIj8tMToxKX19cmV0dXJuIGUuaW5kZXgtdC5pbmRleH0kb2UuZXhwb3J0cz10YXJ9KTt2YXIgSm9lPUgoKERKbixab2UpPT57dmFyIFNsdD1INCgpLGVhcj1VNCgpLHJhcj1IZigpLG5hcj10bHQoKSxpYXI9V29lKCksb2FyPVI0KCksYWFyPUtvZSgpLHNhcj11MCgpLGxhcj1UaSgpO2Z1bmN0aW9uIGNhcihlLHQscil7dC5sZW5ndGg/dD1TbHQodCxmdW5jdGlvbihvKXtyZXR1cm4gbGFyKG8pP2Z1bmN0aW9uKGEpe3JldHVybiBlYXIoYSxvLmxlbmd0aD09PTE/b1swXTpvKX06b30pOnQ9W3Nhcl07dmFyIG49LTE7dD1TbHQodCxvYXIocmFyKSk7dmFyIGk9bmFyKGUsZnVuY3Rpb24obyxhLHMpe3ZhciBsPVNsdCh0LGZ1bmN0aW9uKGMpe3JldHVybiBjKG8pfSk7cmV0dXJue2NyaXRlcmlhOmwsaW5kZXg6KytuLHZhbHVlOm99fSk7cmV0dXJuIGlhcihpLGZ1bmN0aW9uKG8sYSl7cmV0dXJuIGFhcihvLGEscil9KX1ab2UuZXhwb3J0cz1jYXJ9KTt2YXIgZWFlPUgoKE9Kbix0YWUpPT57dmFyIHVhcj1RQigpLGhhcj1Kb2UoKSxmYXI9cTQoKSxRb2U9WTQoKSxwYXI9ZmFyKGZ1bmN0aW9uKGUsdCl7aWYoZT09bnVsbClyZXR1cm5bXTt2YXIgcj10Lmxlbmd0aDtyZXR1cm4gcj4xJiZRb2UoZSx0WzBdLHRbMV0pP3Q9W106cj4yJiZRb2UodFswXSx0WzFdLHRbMl0pJiYodD1bdFswXV0pLGhhcihlLHVhcih0LDEpLFtdKX0pO3RhZS5leHBvcnRzPXBhcn0pO3ZhciBuYWU9SCgoekpuLHJhZSk9Pnt2YXIgZGFyPWpzdCgpLG1hcj0wO2Z1bmN0aW9uIGdhcihlKXt2YXIgdD0rK21hcjtyZXR1cm4gZGFyKGUpK3R9cmFlLmV4cG9ydHM9Z2FyfSk7dmFyIG9hZT1IKChGSm4saWFlKT0+e2Z1bmN0aW9uIF9hcihlLHQscil7Zm9yKHZhciBuPS0xLGk9ZS5sZW5ndGgsbz10Lmxlbmd0aCxhPXt9OysrbjxpOyl7dmFyIHM9bjxvP3Rbbl06dm9pZCAwO3IoYSxlW25dLHMpfXJldHVybiBhfWlhZS5leHBvcnRzPV9hcn0pO3ZhciBzYWU9SCgoQkpuLGFhZSk9Pnt2YXIgeWFyPUk0KCkseGFyPW9hZSgpO2Z1bmN0aW9uIGJhcihlLHQpe3JldHVybiB4YXIoZXx8W10sdHx8W10seWFyKX1hYWUuZXhwb3J0cz1iYXJ9KTt2YXIgcW49SCgoSEpuLGxhZSk9Pnt2YXIgc0g7aWYodHlwZW9mIEV4PT0iZnVuY3Rpb24iKXRyeXtzSD17Y2xvbmVEZWVwOnZpZSgpLGNvbnN0YW50OllCKCksZGVmYXVsdHM6U2llKCksZWFjaDp6c3QoKSxmaWx0ZXI6WnN0KCksZmluZDpWaWUoKSxmbGF0dGVuOnlsdCgpLGZvckVhY2g6T3N0KCksZm9ySW46R2llKCksaGFzOkpzdCgpLGlzVW5kZWZpbmVkOlFzdCgpLGxhc3Q6WWllKCksbWFwOmVsdCgpLG1hcFZhbHVlczpYaWUoKSxtYXg6UWllKCksbWVyZ2U6dm9lKCksbWluOndvZSgpLG1pbkJ5Ok1vZSgpLG5vdzpUb2UoKSxwaWNrOnpvZSgpLHJhbmdlOnFvZSgpLHJlZHVjZTpybHQoKSxzb3J0Qnk6ZWFlKCksdW5pcXVlSWQ6bmFlKCksdmFsdWVzOnVsdCgpLHppcE9iamVjdDpzYWUoKX19Y2F0Y2goZSl7fXNIfHwoc0g9d2luZG93Ll8pO2xhZS5leHBvcnRzPXNIfSk7dmFyIGhhZT1IKChVSm4sdWFlKT0+e3VhZS5leHBvcnRzPWxIO2Z1bmN0aW9uIGxIKCl7dmFyIGU9e307ZS5fbmV4dD1lLl9wcmV2PWUsdGhpcy5fc2VudGluZWw9ZX1sSC5wcm90b3R5cGUuZGVxdWV1ZT1mdW5jdGlvbigpe3ZhciBlPXRoaXMuX3NlbnRpbmVsLHQ9ZS5fcHJldjtpZih0IT09ZSlyZXR1cm4gY2FlKHQpLHR9O2xILnByb3RvdHlwZS5lbnF1ZXVlPWZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX3NlbnRpbmVsO2UuX3ByZXYmJmUuX25leHQmJmNhZShlKSxlLl9uZXh0PXQuX25leHQsdC5fbmV4dC5fcHJldj1lLHQuX25leHQ9ZSxlLl9wcmV2PXR9O2xILnByb3RvdHlwZS50b1N0cmluZz1mdW5jdGlvbigpe2Zvcih2YXIgZT1bXSx0PXRoaXMuX3NlbnRpbmVsLHI9dC5fcHJldjtyIT09dDspZS5wdXNoKEpTT04uc3RyaW5naWZ5KHIsd2FyKSkscj1yLl9wcmV2O3JldHVybiJbIitlLmpvaW4oIiwgIikrIl0ifTtmdW5jdGlvbiBjYWUoZSl7ZS5fcHJldi5fbmV4dD1lLl9uZXh0LGUuX25leHQuX3ByZXY9ZS5fcHJldixkZWxldGUgZS5fbmV4dCxkZWxldGUgZS5fcHJldn1mdW5jdGlvbiB3YXIoZSx0KXtpZihlIT09Il9uZXh0IiYmZSE9PSJfcHJldiIpcmV0dXJuIHR9fSk7dmFyIHBhZT1IKChxSm4sZmFlKT0+e3ZhciBQZD1xbigpLFNhcj1VYygpLkdyYXBoLE1hcj1oYWUoKTtmYWUuZXhwb3J0cz1UYXI7dmFyIEVhcj1QZC5jb25zdGFudCgxKTtmdW5jdGlvbiBUYXIoZSx0KXtpZihlLm5vZGVDb3VudCgpPD0xKXJldHVybltdO3ZhciByPUFhcihlLHR8fEVhciksbj1DYXIoci5ncmFwaCxyLmJ1Y2tldHMsci56ZXJvSWR4KTtyZXR1cm4gUGQuZmxhdHRlbihQZC5tYXAobixmdW5jdGlvbihpKXtyZXR1cm4gZS5vdXRFZGdlcyhpLnYsaS53KX0pLCEwKX1mdW5jdGlvbiBDYXIoZSx0LHIpe2Zvcih2YXIgbj1bXSxpPXRbdC5sZW5ndGgtMV0sbz10WzBdLGE7ZS5ub2RlQ291bnQoKTspe2Zvcig7YT1vLmRlcXVldWUoKTspTWx0KGUsdCxyLGEpO2Zvcig7YT1pLmRlcXVldWUoKTspTWx0KGUsdCxyLGEpO2lmKGUubm9kZUNvdW50KCkpe2Zvcih2YXIgcz10Lmxlbmd0aC0yO3M+MDstLXMpaWYoYT10W3NdLmRlcXVldWUoKSxhKXtuPW4uY29uY2F0KE1sdChlLHQscixhLCEwKSk7YnJlYWt9fX1yZXR1cm4gbn1mdW5jdGlvbiBNbHQoZSx0LHIsbixpKXt2YXIgbz1pP1tdOnZvaWQgMDtyZXR1cm4gUGQuZm9yRWFjaChlLmluRWRnZXMobi52KSxmdW5jdGlvbihhKXt2YXIgcz1lLmVkZ2UoYSksbD1lLm5vZGUoYS52KTtpJiZvLnB1c2goe3Y6YS52LHc6YS53fSksbC5vdXQtPXMsRWx0KHQscixsKX0pLFBkLmZvckVhY2goZS5vdXRFZGdlcyhuLnYpLGZ1bmN0aW9uKGEpe3ZhciBzPWUuZWRnZShhKSxsPWEudyxjPWUubm9kZShsKTtjLmluLT1zLEVsdCh0LHIsYyl9KSxlLnJlbW92ZU5vZGUobi52KSxvfWZ1bmN0aW9uIEFhcihlLHQpe3ZhciByPW5ldyBTYXIsbj0wLGk9MDtQZC5mb3JFYWNoKGUubm9kZXMoKSxmdW5jdGlvbihzKXtyLnNldE5vZGUocyx7djpzLGluOjAsb3V0OjB9KX0pLFBkLmZvckVhY2goZS5lZGdlcygpLGZ1bmN0aW9uKHMpe3ZhciBsPXIuZWRnZShzLnYscy53KXx8MCxjPXQocyksdT1sK2M7ci5zZXRFZGdlKHMudixzLncsdSksaT1NYXRoLm1heChpLHIubm9kZShzLnYpLm91dCs9Yyksbj1NYXRoLm1heChuLHIubm9kZShzLncpLmluKz1jKX0pO3ZhciBvPVBkLnJhbmdlKGkrbiszKS5tYXAoZnVuY3Rpb24oKXtyZXR1cm4gbmV3IE1hcn0pLGE9bisxO3JldHVybiBQZC5mb3JFYWNoKHIubm9kZXMoKSxmdW5jdGlvbihzKXtFbHQobyxhLHIubm9kZShzKSl9KSx7Z3JhcGg6cixidWNrZXRzOm8semVyb0lkeDphfX1mdW5jdGlvbiBFbHQoZSx0LHIpe3Iub3V0P3IuaW4/ZVtyLm91dC1yLmluK3RdLmVucXVldWUocik6ZVtlLmxlbmd0aC0xXS5lbnF1ZXVlKHIpOmVbMF0uZW5xdWV1ZShyKX19KTt2YXIgbWFlPUgoKEdKbixkYWUpPT57InVzZSBzdHJpY3QiO3ZhciBRMT1xbigpLFBhcj1wYWUoKTtkYWUuZXhwb3J0cz17cnVuOklhcix1bmRvOmthcn07ZnVuY3Rpb24gSWFyKGUpe3ZhciB0PWUuZ3JhcGgoKS5hY3ljbGljZXI9PT0iZ3JlZWR5Ij9QYXIoZSxyKGUpKTpMYXIoZSk7UTEuZm9yRWFjaCh0LGZ1bmN0aW9uKG4pe3ZhciBpPWUuZWRnZShuKTtlLnJlbW92ZUVkZ2UobiksaS5mb3J3YXJkTmFtZT1uLm5hbWUsaS5yZXZlcnNlZD0hMCxlLnNldEVkZ2Uobi53LG4udixpLFExLnVuaXF1ZUlkKCJyZXYiKSl9KTtmdW5jdGlvbiByKG4pe3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4gbi5lZGdlKGkpLndlaWdodH19fWZ1bmN0aW9uIExhcihlKXt2YXIgdD1bXSxyPXt9LG49e307ZnVuY3Rpb24gaShvKXtRMS5oYXMobixvKXx8KG5bb109ITAscltvXT0hMCxRMS5mb3JFYWNoKGUub3V0RWRnZXMobyksZnVuY3Rpb24oYSl7UTEuaGFzKHIsYS53KT90LnB1c2goYSk6aShhLncpfSksZGVsZXRlIHJbb10pfXJldHVybiBRMS5mb3JFYWNoKGUubm9kZXMoKSxpKSx0fWZ1bmN0aW9uIGthcihlKXtRMS5mb3JFYWNoKGUuZWRnZXMoKSxmdW5jdGlvbih0KXt2YXIgcj1lLmVkZ2UodCk7aWYoci5yZXZlcnNlZCl7ZS5yZW1vdmVFZGdlKHQpO3ZhciBuPXIuZm9yd2FyZE5hbWU7ZGVsZXRlIHIucmV2ZXJzZWQsZGVsZXRlIHIuZm9yd2FyZE5hbWUsZS5zZXRFZGdlKHQudyx0LnYscixuKX19KX19KTt2YXIgbnM9SCgoV0puLHZhZSk9PnsidXNlIHN0cmljdCI7dmFyIFFyPXFuKCksZ2FlPVVjKCkuR3JhcGg7dmFlLmV4cG9ydHM9e2FkZER1bW15Tm9kZTpfYWUsc2ltcGxpZnk6UmFyLGFzTm9uQ29tcG91bmRHcmFwaDpOYXIsc3VjY2Vzc29yV2VpZ2h0czpEYXIscHJlZGVjZXNzb3JXZWlnaHRzOk9hcixpbnRlcnNlY3RSZWN0OnphcixidWlsZExheWVyTWF0cml4OkZhcixub3JtYWxpemVSYW5rczpCYXIscmVtb3ZlRW1wdHlSYW5rczpIYXIsYWRkQm9yZGVyTm9kZTpWYXIsbWF4UmFuazp5YWUscGFydGl0aW9uOlVhcix0aW1lOnFhcixub3RpbWU6R2FyfTtmdW5jdGlvbiBfYWUoZSx0LHIsbil7dmFyIGk7ZG8gaT1Rci51bmlxdWVJZChuKTt3aGlsZShlLmhhc05vZGUoaSkpO3JldHVybiByLmR1bW15PXQsZS5zZXROb2RlKGksciksaX1mdW5jdGlvbiBSYXIoZSl7dmFyIHQ9bmV3IGdhZSgpLnNldEdyYXBoKGUuZ3JhcGgoKSk7cmV0dXJuIFFyLmZvckVhY2goZS5ub2RlcygpLGZ1bmN0aW9uKHIpe3Quc2V0Tm9kZShyLGUubm9kZShyKSl9KSxRci5mb3JFYWNoKGUuZWRnZXMoKSxmdW5jdGlvbihyKXt2YXIgbj10LmVkZ2Uoci52LHIudyl8fHt3ZWlnaHQ6MCxtaW5sZW46MX0saT1lLmVkZ2Uocik7dC5zZXRFZGdlKHIudixyLncse3dlaWdodDpuLndlaWdodCtpLndlaWdodCxtaW5sZW46TWF0aC5tYXgobi5taW5sZW4saS5taW5sZW4pfSl9KSx0fWZ1bmN0aW9uIE5hcihlKXt2YXIgdD1uZXcgZ2FlKHttdWx0aWdyYXBoOmUuaXNNdWx0aWdyYXBoKCl9KS5zZXRHcmFwaChlLmdyYXBoKCkpO3JldHVybiBRci5mb3JFYWNoKGUubm9kZXMoKSxmdW5jdGlvbihyKXtlLmNoaWxkcmVuKHIpLmxlbmd0aHx8dC5zZXROb2RlKHIsZS5ub2RlKHIpKX0pLFFyLmZvckVhY2goZS5lZGdlcygpLGZ1bmN0aW9uKHIpe3Quc2V0RWRnZShyLGUuZWRnZShyKSl9KSx0fWZ1bmN0aW9uIERhcihlKXt2YXIgdD1Rci5tYXAoZS5ub2RlcygpLGZ1bmN0aW9uKHIpe3ZhciBuPXt9O3JldHVybiBRci5mb3JFYWNoKGUub3V0RWRnZXMociksZnVuY3Rpb24oaSl7bltpLnddPShuW2kud118fDApK2UuZWRnZShpKS53ZWlnaHR9KSxufSk7cmV0dXJuIFFyLnppcE9iamVjdChlLm5vZGVzKCksdCl9ZnVuY3Rpb24gT2FyKGUpe3ZhciB0PVFyLm1hcChlLm5vZGVzKCksZnVuY3Rpb24ocil7dmFyIG49e307cmV0dXJuIFFyLmZvckVhY2goZS5pbkVkZ2VzKHIpLGZ1bmN0aW9uKGkpe25baS52XT0obltpLnZdfHwwKStlLmVkZ2UoaSkud2VpZ2h0fSksbn0pO3JldHVybiBRci56aXBPYmplY3QoZS5ub2RlcygpLHQpfWZ1bmN0aW9uIHphcihlLHQpe3ZhciByPWUueCxuPWUueSxpPXQueC1yLG89dC55LW4sYT1lLndpZHRoLzIscz1lLmhlaWdodC8yO2lmKCFpJiYhbyl0aHJvdyBuZXcgRXJyb3IoIk5vdCBwb3NzaWJsZSB0byBmaW5kIGludGVyc2VjdGlvbiBpbnNpZGUgb2YgdGhlIHJlY3RhbmdsZSIpO3ZhciBsLGM7cmV0dXJuIE1hdGguYWJzKG8pKmE+TWF0aC5hYnMoaSkqcz8obzwwJiYocz0tcyksbD1zKmkvbyxjPXMpOihpPDAmJihhPS1hKSxsPWEsYz1hKm8vaSkse3g6citsLHk6bitjfX1mdW5jdGlvbiBGYXIoZSl7dmFyIHQ9UXIubWFwKFFyLnJhbmdlKHlhZShlKSsxKSxmdW5jdGlvbigpe3JldHVybltdfSk7cmV0dXJuIFFyLmZvckVhY2goZS5ub2RlcygpLGZ1bmN0aW9uKHIpe3ZhciBuPWUubm9kZShyKSxpPW4ucmFuaztRci5pc1VuZGVmaW5lZChpKXx8KHRbaV1bbi5vcmRlcl09cil9KSx0fWZ1bmN0aW9uIEJhcihlKXt2YXIgdD1Rci5taW4oUXIubWFwKGUubm9kZXMoKSxmdW5jdGlvbihyKXtyZXR1cm4gZS5ub2RlKHIpLnJhbmt9KSk7UXIuZm9yRWFjaChlLm5vZGVzKCksZnVuY3Rpb24ocil7dmFyIG49ZS5ub2RlKHIpO1FyLmhhcyhuLCJyYW5rIikmJihuLnJhbmstPXQpfSl9ZnVuY3Rpb24gSGFyKGUpe3ZhciB0PVFyLm1pbihRci5tYXAoZS5ub2RlcygpLGZ1bmN0aW9uKG8pe3JldHVybiBlLm5vZGUobykucmFua30pKSxyPVtdO1FyLmZvckVhY2goZS5ub2RlcygpLGZ1bmN0aW9uKG8pe3ZhciBhPWUubm9kZShvKS5yYW5rLXQ7clthXXx8KHJbYV09W10pLHJbYV0ucHVzaChvKX0pO3ZhciBuPTAsaT1lLmdyYXBoKCkubm9kZVJhbmtGYWN0b3I7UXIuZm9yRWFjaChyLGZ1bmN0aW9uKG8sYSl7UXIuaXNVbmRlZmluZWQobykmJmElaSE9PTA/LS1uOm4mJlFyLmZvckVhY2gobyxmdW5jdGlvbihzKXtlLm5vZGUocykucmFuays9bn0pfSl9ZnVuY3Rpb24gVmFyKGUsdCxyLG4pe3ZhciBpPXt3aWR0aDowLGhlaWdodDowfTtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD49NCYmKGkucmFuaz1yLGkub3JkZXI9biksX2FlKGUsImJvcmRlciIsaSx0KX1mdW5jdGlvbiB5YWUoZSl7cmV0dXJuIFFyLm1heChRci5tYXAoZS5ub2RlcygpLGZ1bmN0aW9uKHQpe3ZhciByPWUubm9kZSh0KS5yYW5rO2lmKCFRci5pc1VuZGVmaW5lZChyKSlyZXR1cm4gcn0pKX1mdW5jdGlvbiBVYXIoZSx0KXt2YXIgcj17bGhzOltdLHJoczpbXX07cmV0dXJuIFFyLmZvckVhY2goZSxmdW5jdGlvbihuKXt0KG4pP3IubGhzLnB1c2gobik6ci5yaHMucHVzaChuKX0pLHJ9ZnVuY3Rpb24gcWFyKGUsdCl7dmFyIHI9UXIubm93KCk7dHJ5e3JldHVybiB0KCl9ZmluYWxseXtjb25zb2xlLmxvZyhlKyIgdGltZTogIisoUXIubm93KCktcikrIm1zIil9fWZ1bmN0aW9uIEdhcihlLHQpe3JldHVybiB0KCl9fSk7dmFyIHdhZT1IKChZSm4sYmFlKT0+eyJ1c2Ugc3RyaWN0Ijt2YXIgeGFlPXFuKCksV2FyPW5zKCk7YmFlLmV4cG9ydHM9e3J1bjpZYXIsdW5kbzpYYXJ9O2Z1bmN0aW9uIFlhcihlKXtlLmdyYXBoKCkuZHVtbXlDaGFpbnM9W10seGFlLmZvckVhY2goZS5lZGdlcygpLGZ1bmN0aW9uKHQpe2phcihlLHQpfSl9ZnVuY3Rpb24gamFyKGUsdCl7dmFyIHI9dC52LG49ZS5ub2RlKHIpLnJhbmssaT10Lncsbz1lLm5vZGUoaSkucmFuayxhPXQubmFtZSxzPWUuZWRnZSh0KSxsPXMubGFiZWxSYW5rO2lmKG8hPT1uKzEpe2UucmVtb3ZlRWRnZSh0KTt2YXIgYyx1LGg7Zm9yKGg9MCwrK247bjxvOysraCwrK24pcy5wb2ludHM9W10sdT17d2lkdGg6MCxoZWlnaHQ6MCxlZGdlTGFiZWw6cyxlZGdlT2JqOnQscmFuazpufSxjPVdhci5hZGREdW1teU5vZGUoZSwiZWRnZSIsdSwiX2QiKSxuPT09bCYmKHUud2lkdGg9cy53aWR0aCx1LmhlaWdodD1zLmhlaWdodCx1LmR1bW15PSJlZGdlLWxhYmVsIix1LmxhYmVscG9zPXMubGFiZWxwb3MpLGUuc2V0RWRnZShyLGMse3dlaWdodDpzLndlaWdodH0sYSksaD09PTAmJmUuZ3JhcGgoKS5kdW1teUNoYWlucy5wdXNoKGMpLHI9YztlLnNldEVkZ2UocixpLHt3ZWlnaHQ6cy53ZWlnaHR9LGEpfX1mdW5jdGlvbiBYYXIoZSl7eGFlLmZvckVhY2goZS5ncmFwaCgpLmR1bW15Q2hhaW5zLGZ1bmN0aW9uKHQpe3ZhciByPWUubm9kZSh0KSxuPXIuZWRnZUxhYmVsLGk7Zm9yKGUuc2V0RWRnZShyLmVkZ2VPYmosbik7ci5kdW1teTspaT1lLnN1Y2Nlc3NvcnModClbMF0sZS5yZW1vdmVOb2RlKHQpLG4ucG9pbnRzLnB1c2goe3g6ci54LHk6ci55fSksci5kdW1teT09PSJlZGdlLWxhYmVsIiYmKG4ueD1yLngsbi55PXIueSxuLndpZHRoPXIud2lkdGgsbi5oZWlnaHQ9ci5oZWlnaHQpLHQ9aSxyPWUubm9kZSh0KX0pfX0pO3ZhciBqND1IKChqSm4sU2FlKT0+eyJ1c2Ugc3RyaWN0Ijt2YXIgY0g9cW4oKTtTYWUuZXhwb3J0cz17bG9uZ2VzdFBhdGg6JGFyLHNsYWNrOkthcn07ZnVuY3Rpb24gJGFyKGUpe3ZhciB0PXt9O2Z1bmN0aW9uIHIobil7dmFyIGk9ZS5ub2RlKG4pO2lmKGNILmhhcyh0LG4pKXJldHVybiBpLnJhbms7dFtuXT0hMDt2YXIgbz1jSC5taW4oY0gubWFwKGUub3V0RWRnZXMobiksZnVuY3Rpb24oYSl7cmV0dXJuIHIoYS53KS1lLmVkZ2UoYSkubWlubGVufSkpO3JldHVybihvPT09TnVtYmVyLlBPU0lUSVZFX0lORklOSVRZfHxvPT09dm9pZCAwfHxvPT09bnVsbCkmJihvPTApLGkucmFuaz1vfWNILmZvckVhY2goZS5zb3VyY2VzKCkscil9ZnVuY3Rpb24gS2FyKGUsdCl7cmV0dXJuIGUubm9kZSh0LncpLnJhbmstZS5ub2RlKHQudikucmFuay1lLmVkZ2UodCkubWlubGVufX0pO3ZhciBUbHQ9SCgoWEpuLE1hZSk9PnsidXNlIHN0cmljdCI7dmFyIHVIPXFuKCksWmFyPVVjKCkuR3JhcGgsaEg9ajQoKS5zbGFjaztNYWUuZXhwb3J0cz1KYXI7ZnVuY3Rpb24gSmFyKGUpe3ZhciB0PW5ldyBaYXIoe2RpcmVjdGVkOiExfSkscj1lLm5vZGVzKClbMF0sbj1lLm5vZGVDb3VudCgpO3Quc2V0Tm9kZShyLHt9KTtmb3IodmFyIGksbztRYXIodCxlKTxuOylpPXRzcih0LGUpLG89dC5oYXNOb2RlKGkudik/aEgoZSxpKTotaEgoZSxpKSxlc3IodCxlLG8pO3JldHVybiB0fWZ1bmN0aW9uIFFhcihlLHQpe2Z1bmN0aW9uIHIobil7dUguZm9yRWFjaCh0Lm5vZGVFZGdlcyhuKSxmdW5jdGlvbihpKXt2YXIgbz1pLnYsYT1uPT09bz9pLnc6bzshZS5oYXNOb2RlKGEpJiYhaEgodCxpKSYmKGUuc2V0Tm9kZShhLHt9KSxlLnNldEVkZ2UobixhLHt9KSxyKGEpKX0pfXJldHVybiB1SC5mb3JFYWNoKGUubm9kZXMoKSxyKSxlLm5vZGVDb3VudCgpfWZ1bmN0aW9uIHRzcihlLHQpe3JldHVybiB1SC5taW5CeSh0LmVkZ2VzKCksZnVuY3Rpb24ocil7aWYoZS5oYXNOb2RlKHIudikhPT1lLmhhc05vZGUoci53KSlyZXR1cm4gaEgodCxyKX0pfWZ1bmN0aW9uIGVzcihlLHQscil7dUguZm9yRWFjaChlLm5vZGVzKCksZnVuY3Rpb24obil7dC5ub2RlKG4pLnJhbmsrPXJ9KX19KTt2YXIga2FlPUgoKCRKbixMYWUpPT57InVzZSBzdHJpY3QiO3ZhciBJZD1xbigpLHJzcj1UbHQoKSxuc3I9ajQoKS5zbGFjayxpc3I9ajQoKS5sb25nZXN0UGF0aCxvc3I9VWMoKS5hbGcucHJlb3JkZXIsYXNyPVVjKCkuYWxnLnBvc3RvcmRlcixzc3I9bnMoKS5zaW1wbGlmeTtMYWUuZXhwb3J0cz10djt0di5pbml0TG93TGltVmFsdWVzPUFsdDt0di5pbml0Q3V0VmFsdWVzPUNsdDt0di5jYWxjQ3V0VmFsdWU9VGFlO3R2LmxlYXZlRWRnZT1BYWU7dHYuZW50ZXJFZGdlPVBhZTt0di5leGNoYW5nZUVkZ2VzPUlhZTtmdW5jdGlvbiB0dihlKXtlPXNzcihlKSxpc3IoZSk7dmFyIHQ9cnNyKGUpO0FsdCh0KSxDbHQodCxlKTtmb3IodmFyIHIsbjtyPUFhZSh0KTspbj1QYWUodCxlLHIpLElhZSh0LGUscixuKX1mdW5jdGlvbiBDbHQoZSx0KXt2YXIgcj1hc3IoZSxlLm5vZGVzKCkpO3I9ci5zbGljZSgwLHIubGVuZ3RoLTEpLElkLmZvckVhY2gocixmdW5jdGlvbihuKXtsc3IoZSx0LG4pfSl9ZnVuY3Rpb24gbHNyKGUsdCxyKXt2YXIgbj1lLm5vZGUociksaT1uLnBhcmVudDtlLmVkZ2UocixpKS5jdXR2YWx1ZT1UYWUoZSx0LHIpfWZ1bmN0aW9uIFRhZShlLHQscil7dmFyIG49ZS5ub2RlKHIpLGk9bi5wYXJlbnQsbz0hMCxhPXQuZWRnZShyLGkpLHM9MDtyZXR1cm4gYXx8KG89ITEsYT10LmVkZ2UoaSxyKSkscz1hLndlaWdodCxJZC5mb3JFYWNoKHQubm9kZUVkZ2VzKHIpLGZ1bmN0aW9uKGwpe3ZhciBjPWwudj09PXIsdT1jP2wudzpsLnY7aWYodSE9PWkpe3ZhciBoPWM9PT1vLGY9dC5lZGdlKGwpLndlaWdodDtpZihzKz1oP2Y6LWYsdXNyKGUscix1KSl7dmFyIHA9ZS5lZGdlKHIsdSkuY3V0dmFsdWU7cys9aD8tcDpwfX19KSxzfWZ1bmN0aW9uIEFsdChlLHQpe2FyZ3VtZW50cy5sZW5ndGg8MiYmKHQ9ZS5ub2RlcygpWzBdKSxDYWUoZSx7fSwxLHQpfWZ1bmN0aW9uIENhZShlLHQscixuLGkpe3ZhciBvPXIsYT1lLm5vZGUobik7cmV0dXJuIHRbbl09ITAsSWQuZm9yRWFjaChlLm5laWdoYm9ycyhuKSxmdW5jdGlvbihzKXtJZC5oYXModCxzKXx8KHI9Q2FlKGUsdCxyLHMsbikpfSksYS5sb3c9byxhLmxpbT1yKyssaT9hLnBhcmVudD1pOmRlbGV0ZSBhLnBhcmVudCxyfWZ1bmN0aW9uIEFhZShlKXtyZXR1cm4gSWQuZmluZChlLmVkZ2VzKCksZnVuY3Rpb24odCl7cmV0dXJuIGUuZWRnZSh0KS5jdXR2YWx1ZTwwfSl9ZnVuY3Rpb24gUGFlKGUsdCxyKXt2YXIgbj1yLnYsaT1yLnc7dC5oYXNFZGdlKG4saSl8fChuPXIudyxpPXIudik7dmFyIG89ZS5ub2RlKG4pLGE9ZS5ub2RlKGkpLHM9byxsPSExO28ubGltPmEubGltJiYocz1hLGw9ITApO3ZhciBjPUlkLmZpbHRlcih0LmVkZ2VzKCksZnVuY3Rpb24odSl7cmV0dXJuIGw9PT1FYWUoZSxlLm5vZGUodS52KSxzKSYmbCE9PUVhZShlLGUubm9kZSh1LncpLHMpfSk7cmV0dXJuIElkLm1pbkJ5KGMsZnVuY3Rpb24odSl7cmV0dXJuIG5zcih0LHUpfSl9ZnVuY3Rpb24gSWFlKGUsdCxyLG4pe3ZhciBpPXIudixvPXIudztlLnJlbW92ZUVkZ2UoaSxvKSxlLnNldEVkZ2Uobi52LG4udyx7fSksQWx0KGUpLENsdChlLHQpLGNzcihlLHQpfWZ1bmN0aW9uIGNzcihlLHQpe3ZhciByPUlkLmZpbmQoZS5ub2RlcygpLGZ1bmN0aW9uKGkpe3JldHVybiF0Lm5vZGUoaSkucGFyZW50fSksbj1vc3IoZSxyKTtuPW4uc2xpY2UoMSksSWQuZm9yRWFjaChuLGZ1bmN0aW9uKGkpe3ZhciBvPWUubm9kZShpKS5wYXJlbnQsYT10LmVkZ2UoaSxvKSxzPSExO2F8fChhPXQuZWRnZShvLGkpLHM9ITApLHQubm9kZShpKS5yYW5rPXQubm9kZShvKS5yYW5rKyhzP2EubWlubGVuOi1hLm1pbmxlbil9KX1mdW5jdGlvbiB1c3IoZSx0LHIpe3JldHVybiBlLmhhc0VkZ2UodCxyKX1mdW5jdGlvbiBFYWUoZSx0LHIpe3JldHVybiByLmxvdzw9dC5saW0mJnQubGltPD1yLmxpbX19KTt2YXIgT2FlPUgoKEtKbixEYWUpPT57InVzZSBzdHJpY3QiO3ZhciBoc3I9ajQoKSxOYWU9aHNyLmxvbmdlc3RQYXRoLGZzcj1UbHQoKSxwc3I9a2FlKCk7RGFlLmV4cG9ydHM9ZHNyO2Z1bmN0aW9uIGRzcihlKXtzd2l0Y2goZS5ncmFwaCgpLnJhbmtlcil7Y2FzZSJuZXR3b3JrLXNpbXBsZXgiOlJhZShlKTticmVhaztjYXNlInRpZ2h0LXRyZWUiOmdzcihlKTticmVhaztjYXNlImxvbmdlc3QtcGF0aCI6bXNyKGUpO2JyZWFrO2RlZmF1bHQ6UmFlKGUpfX12YXIgbXNyPU5hZTtmdW5jdGlvbiBnc3IoZSl7TmFlKGUpLGZzcihlKX1mdW5jdGlvbiBSYWUoZSl7cHNyKGUpfX0pO3ZhciBGYWU9SCgoWkpuLHphZSk9Pnt2YXIgUGx0PXFuKCk7emFlLmV4cG9ydHM9X3NyO2Z1bmN0aW9uIF9zcihlKXt2YXIgdD12c3IoZSk7UGx0LmZvckVhY2goZS5ncmFwaCgpLmR1bW15Q2hhaW5zLGZ1bmN0aW9uKHIpe2Zvcih2YXIgbj1lLm5vZGUociksaT1uLmVkZ2VPYmosbz15c3IoZSx0LGkudixpLncpLGE9by5wYXRoLHM9by5sY2EsbD0wLGM9YVtsXSx1PSEwO3IhPT1pLnc7KXtpZihuPWUubm9kZShyKSx1KXtmb3IoOyhjPWFbbF0pIT09cyYmZS5ub2RlKGMpLm1heFJhbms8bi5yYW5rOylsKys7Yz09PXMmJih1PSExKX1pZighdSl7Zm9yKDtsPGEubGVuZ3RoLTEmJmUubm9kZShjPWFbbCsxXSkubWluUmFuazw9bi5yYW5rOylsKys7Yz1hW2xdfWUuc2V0UGFyZW50KHIsYykscj1lLnN1Y2Nlc3NvcnMocilbMF19fSl9ZnVuY3Rpb24geXNyKGUsdCxyLG4pe3ZhciBpPVtdLG89W10sYT1NYXRoLm1pbih0W3JdLmxvdyx0W25dLmxvdykscz1NYXRoLm1heCh0W3JdLmxpbSx0W25dLmxpbSksbCxjO2w9cjtkbyBsPWUucGFyZW50KGwpLGkucHVzaChsKTt3aGlsZShsJiYodFtsXS5sb3c+YXx8cz50W2xdLmxpbSkpO2ZvcihjPWwsbD1uOyhsPWUucGFyZW50KGwpKSE9PWM7KW8ucHVzaChsKTtyZXR1cm57cGF0aDppLmNvbmNhdChvLnJldmVyc2UoKSksbGNhOmN9fWZ1bmN0aW9uIHZzcihlKXt2YXIgdD17fSxyPTA7ZnVuY3Rpb24gbihpKXt2YXIgbz1yO1BsdC5mb3JFYWNoKGUuY2hpbGRyZW4oaSksbiksdFtpXT17bG93Om8sbGltOnIrK319cmV0dXJuIFBsdC5mb3JFYWNoKGUuY2hpbGRyZW4oKSxuKSx0fX0pO3ZhciBWYWU9SCgoSkpuLEhhZSk9Pnt2YXIgTGQ9cW4oKSxJbHQ9bnMoKTtIYWUuZXhwb3J0cz17cnVuOnhzcixjbGVhbnVwOlNzcn07ZnVuY3Rpb24geHNyKGUpe3ZhciB0PUlsdC5hZGREdW1teU5vZGUoZSwicm9vdCIse30sIl9yb290Iikscj1ic3IoZSksbj1MZC5tYXgoTGQudmFsdWVzKHIpKS0xLGk9MipuKzE7ZS5ncmFwaCgpLm5lc3RpbmdSb290PXQsTGQuZm9yRWFjaChlLmVkZ2VzKCksZnVuY3Rpb24oYSl7ZS5lZGdlKGEpLm1pbmxlbio9aX0pO3ZhciBvPXdzcihlKSsxO0xkLmZvckVhY2goZS5jaGlsZHJlbigpLGZ1bmN0aW9uKGEpe0JhZShlLHQsaSxvLG4scixhKX0pLGUuZ3JhcGgoKS5ub2RlUmFua0ZhY3Rvcj1pfWZ1bmN0aW9uIEJhZShlLHQscixuLGksbyxhKXt2YXIgcz1lLmNoaWxkcmVuKGEpO2lmKCFzLmxlbmd0aCl7YSE9PXQmJmUuc2V0RWRnZSh0LGEse3dlaWdodDowLG1pbmxlbjpyfSk7cmV0dXJufXZhciBsPUlsdC5hZGRCb3JkZXJOb2RlKGUsIl9idCIpLGM9SWx0LmFkZEJvcmRlck5vZGUoZSwiX2JiIiksdT1lLm5vZGUoYSk7ZS5zZXRQYXJlbnQobCxhKSx1LmJvcmRlclRvcD1sLGUuc2V0UGFyZW50KGMsYSksdS5ib3JkZXJCb3R0b209YyxMZC5mb3JFYWNoKHMsZnVuY3Rpb24oaCl7QmFlKGUsdCxyLG4saSxvLGgpO3ZhciBmPWUubm9kZShoKSxwPWYuYm9yZGVyVG9wP2YuYm9yZGVyVG9wOmgsZD1mLmJvcmRlckJvdHRvbT9mLmJvcmRlckJvdHRvbTpoLGc9Zi5ib3JkZXJUb3A/bjoyKm4sXz1wIT09ZD8xOmktb1thXSsxO2Uuc2V0RWRnZShsLHAse3dlaWdodDpnLG1pbmxlbjpfLG5lc3RpbmdFZGdlOiEwfSksZS5zZXRFZGdlKGQsYyx7d2VpZ2h0OmcsbWlubGVuOl8sbmVzdGluZ0VkZ2U6ITB9KX0pLGUucGFyZW50KGEpfHxlLnNldEVkZ2UodCxsLHt3ZWlnaHQ6MCxtaW5sZW46aStvW2FdfSl9ZnVuY3Rpb24gYnNyKGUpe3ZhciB0PXt9O2Z1bmN0aW9uIHIobixpKXt2YXIgbz1lLmNoaWxkcmVuKG4pO28mJm8ubGVuZ3RoJiZMZC5mb3JFYWNoKG8sZnVuY3Rpb24oYSl7cihhLGkrMSl9KSx0W25dPWl9cmV0dXJuIExkLmZvckVhY2goZS5jaGlsZHJlbigpLGZ1bmN0aW9uKG4pe3IobiwxKX0pLHR9ZnVuY3Rpb24gd3NyKGUpe3JldHVybiBMZC5yZWR1Y2UoZS5lZGdlcygpLGZ1bmN0aW9uKHQscil7cmV0dXJuIHQrZS5lZGdlKHIpLndlaWdodH0sMCl9ZnVuY3Rpb24gU3NyKGUpe3ZhciB0PWUuZ3JhcGgoKTtlLnJlbW92ZU5vZGUodC5uZXN0aW5nUm9vdCksZGVsZXRlIHQubmVzdGluZ1Jvb3QsTGQuZm9yRWFjaChlLmVkZ2VzKCksZnVuY3Rpb24ocil7dmFyIG49ZS5lZGdlKHIpO24ubmVzdGluZ0VkZ2UmJmUucmVtb3ZlRWRnZShyKX0pfX0pO3ZhciBHYWU9SCgoUUpuLHFhZSk9Pnt2YXIgTGx0PXFuKCksTXNyPW5zKCk7cWFlLmV4cG9ydHM9RXNyO2Z1bmN0aW9uIEVzcihlKXtmdW5jdGlvbiB0KHIpe3ZhciBuPWUuY2hpbGRyZW4ociksaT1lLm5vZGUocik7aWYobi5sZW5ndGgmJkxsdC5mb3JFYWNoKG4sdCksTGx0LmhhcyhpLCJtaW5SYW5rIikpe2kuYm9yZGVyTGVmdD1bXSxpLmJvcmRlclJpZ2h0PVtdO2Zvcih2YXIgbz1pLm1pblJhbmssYT1pLm1heFJhbmsrMTtvPGE7KytvKVVhZShlLCJib3JkZXJMZWZ0IiwiX2JsIixyLGksbyksVWFlKGUsImJvcmRlclJpZ2h0IiwiX2JyIixyLGksbyl9fUxsdC5mb3JFYWNoKGUuY2hpbGRyZW4oKSx0KX1mdW5jdGlvbiBVYWUoZSx0LHIsbixpLG8pe3ZhciBhPXt3aWR0aDowLGhlaWdodDowLHJhbms6byxib3JkZXJUeXBlOnR9LHM9aVt0XVtvLTFdLGw9TXNyLmFkZER1bW15Tm9kZShlLCJib3JkZXIiLGEscik7aVt0XVtvXT1sLGUuc2V0UGFyZW50KGwsbikscyYmZS5zZXRFZGdlKHMsbCx7d2VpZ2h0OjF9KX19KTt2YXIgWGFlPUgoKHRRbixqYWUpPT57InVzZSBzdHJpY3QiO3ZhciBVZj1xbigpO2phZS5leHBvcnRzPXthZGp1c3Q6VHNyLHVuZG86Q3NyfTtmdW5jdGlvbiBUc3IoZSl7dmFyIHQ9ZS5ncmFwaCgpLnJhbmtkaXIudG9Mb3dlckNhc2UoKTsodD09PSJsciJ8fHQ9PT0icmwiKSYmWWFlKGUpfWZ1bmN0aW9uIENzcihlKXt2YXIgdD1lLmdyYXBoKCkucmFua2Rpci50b0xvd2VyQ2FzZSgpOyh0PT09ImJ0Inx8dD09PSJybCIpJiZBc3IoZSksKHQ9PT0ibHIifHx0PT09InJsIikmJihQc3IoZSksWWFlKGUpKX1mdW5jdGlvbiBZYWUoZSl7VWYuZm9yRWFjaChlLm5vZGVzKCksZnVuY3Rpb24odCl7V2FlKGUubm9kZSh0KSl9KSxVZi5mb3JFYWNoKGUuZWRnZXMoKSxmdW5jdGlvbih0KXtXYWUoZS5lZGdlKHQpKX0pfWZ1bmN0aW9uIFdhZShlKXt2YXIgdD1lLndpZHRoO2Uud2lkdGg9ZS5oZWlnaHQsZS5oZWlnaHQ9dH1mdW5jdGlvbiBBc3IoZSl7VWYuZm9yRWFjaChlLm5vZGVzKCksZnVuY3Rpb24odCl7a2x0KGUubm9kZSh0KSl9KSxVZi5mb3JFYWNoKGUuZWRnZXMoKSxmdW5jdGlvbih0KXt2YXIgcj1lLmVkZ2UodCk7VWYuZm9yRWFjaChyLnBvaW50cyxrbHQpLFVmLmhhcyhyLCJ5IikmJmtsdChyKX0pfWZ1bmN0aW9uIGtsdChlKXtlLnk9LWUueX1mdW5jdGlvbiBQc3IoZSl7VWYuZm9yRWFjaChlLm5vZGVzKCksZnVuY3Rpb24odCl7Umx0KGUubm9kZSh0KSl9KSxVZi5mb3JFYWNoKGUuZWRnZXMoKSxmdW5jdGlvbih0KXt2YXIgcj1lLmVkZ2UodCk7VWYuZm9yRWFjaChyLnBvaW50cyxSbHQpLFVmLmhhcyhyLCJ4IikmJlJsdChyKX0pfWZ1bmN0aW9uIFJsdChlKXt2YXIgdD1lLng7ZS54PWUueSxlLnk9dH19KTt2YXIgS2FlPUgoKGVRbiwkYWUpPT57InVzZSBzdHJpY3QiO3ZhciBrZD1xbigpOyRhZS5leHBvcnRzPUlzcjtmdW5jdGlvbiBJc3IoZSl7dmFyIHQ9e30scj1rZC5maWx0ZXIoZS5ub2RlcygpLGZ1bmN0aW9uKHMpe3JldHVybiFlLmNoaWxkcmVuKHMpLmxlbmd0aH0pLG49a2QubWF4KGtkLm1hcChyLGZ1bmN0aW9uKHMpe3JldHVybiBlLm5vZGUocykucmFua30pKSxpPWtkLm1hcChrZC5yYW5nZShuKzEpLGZ1bmN0aW9uKCl7cmV0dXJuW119KTtmdW5jdGlvbiBvKHMpe2lmKCFrZC5oYXModCxzKSl7dFtzXT0hMDt2YXIgbD1lLm5vZGUocyk7aVtsLnJhbmtdLnB1c2gocyksa2QuZm9yRWFjaChlLnN1Y2Nlc3NvcnMocyksbyl9fXZhciBhPWtkLnNvcnRCeShyLGZ1bmN0aW9uKHMpe3JldHVybiBlLm5vZGUocykucmFua30pO3JldHVybiBrZC5mb3JFYWNoKGEsbyksaX19KTt2YXIgSmFlPUgoKHJRbixaYWUpPT57InVzZSBzdHJpY3QiO3ZhciBoMD1xbigpO1phZS5leHBvcnRzPUxzcjtmdW5jdGlvbiBMc3IoZSx0KXtmb3IodmFyIHI9MCxuPTE7bjx0Lmxlbmd0aDsrK24pcis9a3NyKGUsdFtuLTFdLHRbbl0pO3JldHVybiByfWZ1bmN0aW9uIGtzcihlLHQscil7Zm9yKHZhciBuPWgwLnppcE9iamVjdChyLGgwLm1hcChyLGZ1bmN0aW9uKGMsdSl7cmV0dXJuIHV9KSksaT1oMC5mbGF0dGVuKGgwLm1hcCh0LGZ1bmN0aW9uKGMpe3JldHVybiBoMC5zb3J0QnkoaDAubWFwKGUub3V0RWRnZXMoYyksZnVuY3Rpb24odSl7cmV0dXJue3BvczpuW3Uud10sd2VpZ2h0OmUuZWRnZSh1KS53ZWlnaHR9fSksInBvcyIpfSksITApLG89MTtvPHIubGVuZ3RoOylvPDw9MTt2YXIgYT0yKm8tMTtvLT0xO3ZhciBzPWgwLm1hcChuZXcgQXJyYXkoYSksZnVuY3Rpb24oKXtyZXR1cm4gMH0pLGw9MDtyZXR1cm4gaDAuZm9yRWFjaChpLmZvckVhY2goZnVuY3Rpb24oYyl7dmFyIHU9Yy5wb3MrbztzW3VdKz1jLndlaWdodDtmb3IodmFyIGg9MDt1PjA7KXUlMiYmKGgrPXNbdSsxXSksdT11LTE+PjEsc1t1XSs9Yy53ZWlnaHQ7bCs9Yy53ZWlnaHQqaH0pKSxsfX0pO3ZhciBlc2U9SCgoblFuLHRzZSk9Pnt2YXIgUWFlPXFuKCk7dHNlLmV4cG9ydHM9UnNyO2Z1bmN0aW9uIFJzcihlLHQpe3JldHVybiBRYWUubWFwKHQsZnVuY3Rpb24ocil7dmFyIG49ZS5pbkVkZ2VzKHIpO2lmKG4ubGVuZ3RoKXt2YXIgaT1RYWUucmVkdWNlKG4sZnVuY3Rpb24obyxhKXt2YXIgcz1lLmVkZ2UoYSksbD1lLm5vZGUoYS52KTtyZXR1cm57c3VtOm8uc3VtK3Mud2VpZ2h0Kmwub3JkZXIsd2VpZ2h0Om8ud2VpZ2h0K3Mud2VpZ2h0fX0se3N1bTowLHdlaWdodDowfSk7cmV0dXJue3Y6cixiYXJ5Y2VudGVyOmkuc3VtL2kud2VpZ2h0LHdlaWdodDppLndlaWdodH19ZWxzZSByZXR1cm57djpyfX0pfX0pO3ZhciBuc2U9SCgoaVFuLHJzZSk9PnsidXNlIHN0cmljdCI7dmFyIFRsPXFuKCk7cnNlLmV4cG9ydHM9TnNyO2Z1bmN0aW9uIE5zcihlLHQpe3ZhciByPXt9O1RsLmZvckVhY2goZSxmdW5jdGlvbihpLG8pe3ZhciBhPXJbaS52XT17aW5kZWdyZWU6MCxpbjpbXSxvdXQ6W10sdnM6W2kudl0saTpvfTtUbC5pc1VuZGVmaW5lZChpLmJhcnljZW50ZXIpfHwoYS5iYXJ5Y2VudGVyPWkuYmFyeWNlbnRlcixhLndlaWdodD1pLndlaWdodCl9KSxUbC5mb3JFYWNoKHQuZWRnZXMoKSxmdW5jdGlvbihpKXt2YXIgbz1yW2kudl0sYT1yW2kud107IVRsLmlzVW5kZWZpbmVkKG8pJiYhVGwuaXNVbmRlZmluZWQoYSkmJihhLmluZGVncmVlKyssby5vdXQucHVzaChyW2kud10pKX0pO3ZhciBuPVRsLmZpbHRlcihyLGZ1bmN0aW9uKGkpe3JldHVybiFpLmluZGVncmVlfSk7cmV0dXJuIERzcihuKX1mdW5jdGlvbiBEc3IoZSl7dmFyIHQ9W107ZnVuY3Rpb24gcihvKXtyZXR1cm4gZnVuY3Rpb24oYSl7YS5tZXJnZWR8fChUbC5pc1VuZGVmaW5lZChhLmJhcnljZW50ZXIpfHxUbC5pc1VuZGVmaW5lZChvLmJhcnljZW50ZXIpfHxhLmJhcnljZW50ZXI+PW8uYmFyeWNlbnRlcikmJk9zcihvLGEpfX1mdW5jdGlvbiBuKG8pe3JldHVybiBmdW5jdGlvbihhKXthLmluLnB1c2gobyksLS1hLmluZGVncmVlPT09MCYmZS5wdXNoKGEpfX1mb3IoO2UubGVuZ3RoOyl7dmFyIGk9ZS5wb3AoKTt0LnB1c2goaSksVGwuZm9yRWFjaChpLmluLnJldmVyc2UoKSxyKGkpKSxUbC5mb3JFYWNoKGkub3V0LG4oaSkpfXJldHVybiBUbC5tYXAoVGwuZmlsdGVyKHQsZnVuY3Rpb24obyl7cmV0dXJuIW8ubWVyZ2VkfSksZnVuY3Rpb24obyl7cmV0dXJuIFRsLnBpY2sobyxbInZzIiwiaSIsImJhcnljZW50ZXIiLCJ3ZWlnaHQiXSl9KX1mdW5jdGlvbiBPc3IoZSx0KXt2YXIgcj0wLG49MDtlLndlaWdodCYmKHIrPWUuYmFyeWNlbnRlciplLndlaWdodCxuKz1lLndlaWdodCksdC53ZWlnaHQmJihyKz10LmJhcnljZW50ZXIqdC53ZWlnaHQsbis9dC53ZWlnaHQpLGUudnM9dC52cy5jb25jYXQoZS52cyksZS5iYXJ5Y2VudGVyPXIvbixlLndlaWdodD1uLGUuaT1NYXRoLm1pbih0LmksZS5pKSx0Lm1lcmdlZD0hMH19KTt2YXIgYXNlPUgoKG9Rbixvc2UpPT57dmFyIFg0PXFuKCksenNyPW5zKCk7b3NlLmV4cG9ydHM9RnNyO2Z1bmN0aW9uIEZzcihlLHQpe3ZhciByPXpzci5wYXJ0aXRpb24oZSxmdW5jdGlvbih1KXtyZXR1cm4gWDQuaGFzKHUsImJhcnljZW50ZXIiKX0pLG49ci5saHMsaT1YNC5zb3J0Qnkoci5yaHMsZnVuY3Rpb24odSl7cmV0dXJuLXUuaX0pLG89W10sYT0wLHM9MCxsPTA7bi5zb3J0KEJzcighIXQpKSxsPWlzZShvLGksbCksWDQuZm9yRWFjaChuLGZ1bmN0aW9uKHUpe2wrPXUudnMubGVuZ3RoLG8ucHVzaCh1LnZzKSxhKz11LmJhcnljZW50ZXIqdS53ZWlnaHQscys9dS53ZWlnaHQsbD1pc2UobyxpLGwpfSk7dmFyIGM9e3ZzOlg0LmZsYXR0ZW4obywhMCl9O3JldHVybiBzJiYoYy5iYXJ5Y2VudGVyPWEvcyxjLndlaWdodD1zKSxjfWZ1bmN0aW9uIGlzZShlLHQscil7Zm9yKHZhciBuO3QubGVuZ3RoJiYobj1YNC5sYXN0KHQpKS5pPD1yOyl0LnBvcCgpLGUucHVzaChuLnZzKSxyKys7cmV0dXJuIHJ9ZnVuY3Rpb24gQnNyKGUpe3JldHVybiBmdW5jdGlvbih0LHIpe3JldHVybiB0LmJhcnljZW50ZXI8ci5iYXJ5Y2VudGVyPy0xOnQuYmFyeWNlbnRlcj5yLmJhcnljZW50ZXI/MTplP3IuaS10Lmk6dC5pLXIuaX19fSk7dmFyIGNzZT1IKChhUW4sbHNlKT0+e3ZhciBmMD1xbigpLEhzcj1lc2UoKSxWc3I9bnNlKCksVXNyPWFzZSgpO2xzZS5leHBvcnRzPXNzZTtmdW5jdGlvbiBzc2UoZSx0LHIsbil7dmFyIGk9ZS5jaGlsZHJlbih0KSxvPWUubm9kZSh0KSxhPW8/by5ib3JkZXJMZWZ0OnZvaWQgMCxzPW8/by5ib3JkZXJSaWdodDp2b2lkIDAsbD17fTthJiYoaT1mMC5maWx0ZXIoaSxmdW5jdGlvbihkKXtyZXR1cm4gZCE9PWEmJmQhPT1zfSkpO3ZhciBjPUhzcihlLGkpO2YwLmZvckVhY2goYyxmdW5jdGlvbihkKXtpZihlLmNoaWxkcmVuKGQudikubGVuZ3RoKXt2YXIgZz1zc2UoZSxkLnYscixuKTtsW2Qudl09ZyxmMC5oYXMoZywiYmFyeWNlbnRlciIpJiZHc3IoZCxnKX19KTt2YXIgdT1Wc3IoYyxyKTtxc3IodSxsKTt2YXIgaD1Vc3IodSxuKTtpZihhJiYoaC52cz1mMC5mbGF0dGVuKFthLGgudnMsc10sITApLGUucHJlZGVjZXNzb3JzKGEpLmxlbmd0aCkpe3ZhciBmPWUubm9kZShlLnByZWRlY2Vzc29ycyhhKVswXSkscD1lLm5vZGUoZS5wcmVkZWNlc3NvcnMocylbMF0pO2YwLmhhcyhoLCJiYXJ5Y2VudGVyIil8fChoLmJhcnljZW50ZXI9MCxoLndlaWdodD0wKSxoLmJhcnljZW50ZXI9KGguYmFyeWNlbnRlcipoLndlaWdodCtmLm9yZGVyK3Aub3JkZXIpLyhoLndlaWdodCsyKSxoLndlaWdodCs9Mn1yZXR1cm4gaH1mdW5jdGlvbiBxc3IoZSx0KXtmMC5mb3JFYWNoKGUsZnVuY3Rpb24ocil7ci52cz1mMC5mbGF0dGVuKHIudnMubWFwKGZ1bmN0aW9uKG4pe3JldHVybiB0W25dP3Rbbl0udnM6bn0pLCEwKX0pfWZ1bmN0aW9uIEdzcihlLHQpe2YwLmlzVW5kZWZpbmVkKGUuYmFyeWNlbnRlcik/KGUuYmFyeWNlbnRlcj10LmJhcnljZW50ZXIsZS53ZWlnaHQ9dC53ZWlnaHQpOihlLmJhcnljZW50ZXI9KGUuYmFyeWNlbnRlciplLndlaWdodCt0LmJhcnljZW50ZXIqdC53ZWlnaHQpLyhlLndlaWdodCt0LndlaWdodCksZS53ZWlnaHQrPXQud2VpZ2h0KX19KTt2YXIgaHNlPUgoKHNRbix1c2UpPT57dmFyICQ0PXFuKCksV3NyPVVjKCkuR3JhcGg7dXNlLmV4cG9ydHM9WXNyO2Z1bmN0aW9uIFlzcihlLHQscil7dmFyIG49anNyKGUpLGk9bmV3IFdzcih7Y29tcG91bmQ6ITB9KS5zZXRHcmFwaCh7cm9vdDpufSkuc2V0RGVmYXVsdE5vZGVMYWJlbChmdW5jdGlvbihvKXtyZXR1cm4gZS5ub2RlKG8pfSk7cmV0dXJuICQ0LmZvckVhY2goZS5ub2RlcygpLGZ1bmN0aW9uKG8pe3ZhciBhPWUubm9kZShvKSxzPWUucGFyZW50KG8pOyhhLnJhbms9PT10fHxhLm1pblJhbms8PXQmJnQ8PWEubWF4UmFuaykmJihpLnNldE5vZGUobyksaS5zZXRQYXJlbnQobyxzfHxuKSwkNC5mb3JFYWNoKGVbcl0obyksZnVuY3Rpb24obCl7dmFyIGM9bC52PT09bz9sLnc6bC52LHU9aS5lZGdlKGMsbyksaD0kNC5pc1VuZGVmaW5lZCh1KT8wOnUud2VpZ2h0O2kuc2V0RWRnZShjLG8se3dlaWdodDplLmVkZ2UobCkud2VpZ2h0K2h9KX0pLCQ0LmhhcyhhLCJtaW5SYW5rIikmJmkuc2V0Tm9kZShvLHtib3JkZXJMZWZ0OmEuYm9yZGVyTGVmdFt0XSxib3JkZXJSaWdodDphLmJvcmRlclJpZ2h0W3RdfSkpfSksaX1mdW5jdGlvbiBqc3IoZSl7Zm9yKHZhciB0O2UuaGFzTm9kZSh0PSQ0LnVuaXF1ZUlkKCJfcm9vdCIpKTspO3JldHVybiB0fX0pO3ZhciBwc2U9SCgobFFuLGZzZSk9Pnt2YXIgWHNyPXFuKCk7ZnNlLmV4cG9ydHM9JHNyO2Z1bmN0aW9uICRzcihlLHQscil7dmFyIG49e30saTtYc3IuZm9yRWFjaChyLGZ1bmN0aW9uKG8pe2Zvcih2YXIgYT1lLnBhcmVudChvKSxzLGw7YTspe2lmKHM9ZS5wYXJlbnQoYSkscz8obD1uW3NdLG5bc109YSk6KGw9aSxpPWEpLGwmJmwhPT1hKXt0LnNldEVkZ2UobCxhKTtyZXR1cm59YT1zfX0pfX0pO3ZhciB5c2U9SCgoY1FuLF9zZSk9PnsidXNlIHN0cmljdCI7dmFyIHAwPXFuKCksS3NyPUthZSgpLFpzcj1KYWUoKSxKc3I9Y3NlKCksUXNyPWhzZSgpLHRscj1wc2UoKSxlbHI9VWMoKS5HcmFwaCxkc2U9bnMoKTtfc2UuZXhwb3J0cz1ybHI7ZnVuY3Rpb24gcmxyKGUpe3ZhciB0PWRzZS5tYXhSYW5rKGUpLHI9bXNlKGUscDAucmFuZ2UoMSx0KzEpLCJpbkVkZ2VzIiksbj1tc2UoZSxwMC5yYW5nZSh0LTEsLTEsLTEpLCJvdXRFZGdlcyIpLGk9S3NyKGUpO2dzZShlLGkpO2Zvcih2YXIgbz1OdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFksYSxzPTAsbD0wO2w8NDsrK3MsKytsKXtubHIocyUyP3I6bixzJTQ+PTIpLGk9ZHNlLmJ1aWxkTGF5ZXJNYXRyaXgoZSk7dmFyIGM9WnNyKGUsaSk7YzxvJiYobD0wLGE9cDAuY2xvbmVEZWVwKGkpLG89Yyl9Z3NlKGUsYSl9ZnVuY3Rpb24gbXNlKGUsdCxyKXtyZXR1cm4gcDAubWFwKHQsZnVuY3Rpb24obil7cmV0dXJuIFFzcihlLG4scil9KX1mdW5jdGlvbiBubHIoZSx0KXt2YXIgcj1uZXcgZWxyO3AwLmZvckVhY2goZSxmdW5jdGlvbihuKXt2YXIgaT1uLmdyYXBoKCkucm9vdCxvPUpzcihuLGkscix0KTtwMC5mb3JFYWNoKG8udnMsZnVuY3Rpb24oYSxzKXtuLm5vZGUoYSkub3JkZXI9c30pLHRscihuLHIsby52cyl9KX1mdW5jdGlvbiBnc2UoZSx0KXtwMC5mb3JFYWNoKHQsZnVuY3Rpb24ocil7cDAuZm9yRWFjaChyLGZ1bmN0aW9uKG4saSl7ZS5ub2RlKG4pLm9yZGVyPWl9KX0pfX0pO3ZhciBBc2U9SCgodVFuLENzZSk9PnsidXNlIHN0cmljdCI7dmFyIFhlPXFuKCksaWxyPVVjKCkuR3JhcGgsb2xyPW5zKCk7Q3NlLmV4cG9ydHM9e3Bvc2l0aW9uWDpsbHIsZmluZFR5cGUxQ29uZmxpY3RzOnZzZSxmaW5kVHlwZTJDb25mbGljdHM6eHNlLGFkZENvbmZsaWN0Ok5sdCxoYXNDb25mbGljdDpic2UsdmVydGljYWxBbGlnbm1lbnQ6d3NlLGhvcml6b250YWxDb21wYWN0aW9uOlNzZSxhbGlnbkNvb3JkaW5hdGVzOkVzZSxmaW5kU21hbGxlc3RXaWR0aEFsaWdubWVudDpNc2UsYmFsYW5jZTpUc2V9O2Z1bmN0aW9uIHZzZShlLHQpe3ZhciByPXt9O2Z1bmN0aW9uIG4oaSxvKXt2YXIgYT0wLHM9MCxsPWkubGVuZ3RoLGM9WGUubGFzdChvKTtyZXR1cm4gWGUuZm9yRWFjaChvLGZ1bmN0aW9uKHUsaCl7dmFyIGY9YWxyKGUsdSkscD1mP2Uubm9kZShmKS5vcmRlcjpsOyhmfHx1PT09YykmJihYZS5mb3JFYWNoKG8uc2xpY2UocyxoKzEpLGZ1bmN0aW9uKGQpe1hlLmZvckVhY2goZS5wcmVkZWNlc3NvcnMoZCksZnVuY3Rpb24oZyl7dmFyIF89ZS5ub2RlKGcpLHk9Xy5vcmRlcjsoeTxhfHxwPHkpJiYhKF8uZHVtbXkmJmUubm9kZShkKS5kdW1teSkmJk5sdChyLGcsZCl9KX0pLHM9aCsxLGE9cCl9KSxvfXJldHVybiBYZS5yZWR1Y2UodCxuKSxyfWZ1bmN0aW9uIHhzZShlLHQpe3ZhciByPXt9O2Z1bmN0aW9uIG4obyxhLHMsbCxjKXt2YXIgdTtYZS5mb3JFYWNoKFhlLnJhbmdlKGEscyksZnVuY3Rpb24oaCl7dT1vW2hdLGUubm9kZSh1KS5kdW1teSYmWGUuZm9yRWFjaChlLnByZWRlY2Vzc29ycyh1KSxmdW5jdGlvbihmKXt2YXIgcD1lLm5vZGUoZik7cC5kdW1teSYmKHAub3JkZXI8bHx8cC5vcmRlcj5jKSYmTmx0KHIsZix1KX0pfSl9ZnVuY3Rpb24gaShvLGEpe3ZhciBzPS0xLGwsYz0wO3JldHVybiBYZS5mb3JFYWNoKGEsZnVuY3Rpb24odSxoKXtpZihlLm5vZGUodSkuZHVtbXk9PT0iYm9yZGVyIil7dmFyIGY9ZS5wcmVkZWNlc3NvcnModSk7Zi5sZW5ndGgmJihsPWUubm9kZShmWzBdKS5vcmRlcixuKGEsYyxoLHMsbCksYz1oLHM9bCl9bihhLGMsYS5sZW5ndGgsbCxvLmxlbmd0aCl9KSxhfXJldHVybiBYZS5yZWR1Y2UodCxpKSxyfWZ1bmN0aW9uIGFscihlLHQpe2lmKGUubm9kZSh0KS5kdW1teSlyZXR1cm4gWGUuZmluZChlLnByZWRlY2Vzc29ycyh0KSxmdW5jdGlvbihyKXtyZXR1cm4gZS5ub2RlKHIpLmR1bW15fSl9ZnVuY3Rpb24gTmx0KGUsdCxyKXtpZih0PnIpe3ZhciBuPXQ7dD1yLHI9bn12YXIgaT1lW3RdO2l8fChlW3RdPWk9e30pLGlbcl09ITB9ZnVuY3Rpb24gYnNlKGUsdCxyKXtpZih0PnIpe3ZhciBuPXQ7dD1yLHI9bn1yZXR1cm4gWGUuaGFzKGVbdF0scil9ZnVuY3Rpb24gd3NlKGUsdCxyLG4pe3ZhciBpPXt9LG89e30sYT17fTtyZXR1cm4gWGUuZm9yRWFjaCh0LGZ1bmN0aW9uKHMpe1hlLmZvckVhY2gocyxmdW5jdGlvbihsLGMpe2lbbF09bCxvW2xdPWwsYVtsXT1jfSl9KSxYZS5mb3JFYWNoKHQsZnVuY3Rpb24ocyl7dmFyIGw9LTE7WGUuZm9yRWFjaChzLGZ1bmN0aW9uKGMpe3ZhciB1PW4oYyk7aWYodS5sZW5ndGgpe3U9WGUuc29ydEJ5KHUsZnVuY3Rpb24oZyl7cmV0dXJuIGFbZ119KTtmb3IodmFyIGg9KHUubGVuZ3RoLTEpLzIsZj1NYXRoLmZsb29yKGgpLHA9TWF0aC5jZWlsKGgpO2Y8PXA7KytmKXt2YXIgZD11W2ZdO29bY109PT1jJiZsPGFbZF0mJiFic2UocixjLGQpJiYob1tkXT1jLG9bY109aVtjXT1pW2RdLGw9YVtkXSl9fX0pfSkse3Jvb3Q6aSxhbGlnbjpvfX1mdW5jdGlvbiBTc2UoZSx0LHIsbixpKXt2YXIgbz17fSxhPXNscihlLHQscixpKSxzPWk/ImJvcmRlckxlZnQiOiJib3JkZXJSaWdodCI7ZnVuY3Rpb24gbChoLGYpe2Zvcih2YXIgcD1hLm5vZGVzKCksZD1wLnBvcCgpLGc9e307ZDspZ1tkXT9oKGQpOihnW2RdPSEwLHAucHVzaChkKSxwPXAuY29uY2F0KGYoZCkpKSxkPXAucG9wKCl9ZnVuY3Rpb24gYyhoKXtvW2hdPWEuaW5FZGdlcyhoKS5yZWR1Y2UoZnVuY3Rpb24oZixwKXtyZXR1cm4gTWF0aC5tYXgoZixvW3Audl0rYS5lZGdlKHApKX0sMCl9ZnVuY3Rpb24gdShoKXt2YXIgZj1hLm91dEVkZ2VzKGgpLnJlZHVjZShmdW5jdGlvbihkLGcpe3JldHVybiBNYXRoLm1pbihkLG9bZy53XS1hLmVkZ2UoZykpfSxOdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFkpLHA9ZS5ub2RlKGgpO2YhPT1OdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFkmJnAuYm9yZGVyVHlwZSE9PXMmJihvW2hdPU1hdGgubWF4KG9baF0sZikpfXJldHVybiBsKGMsYS5wcmVkZWNlc3NvcnMuYmluZChhKSksbCh1LGEuc3VjY2Vzc29ycy5iaW5kKGEpKSxYZS5mb3JFYWNoKG4sZnVuY3Rpb24oaCl7b1toXT1vW3JbaF1dfSksb31mdW5jdGlvbiBzbHIoZSx0LHIsbil7dmFyIGk9bmV3IGlscixvPWUuZ3JhcGgoKSxhPWNscihvLm5vZGVzZXAsby5lZGdlc2VwLG4pO3JldHVybiBYZS5mb3JFYWNoKHQsZnVuY3Rpb24ocyl7dmFyIGw7WGUuZm9yRWFjaChzLGZ1bmN0aW9uKGMpe3ZhciB1PXJbY107aWYoaS5zZXROb2RlKHUpLGwpe3ZhciBoPXJbbF0sZj1pLmVkZ2UoaCx1KTtpLnNldEVkZ2UoaCx1LE1hdGgubWF4KGEoZSxjLGwpLGZ8fDApKX1sPWN9KX0pLGl9ZnVuY3Rpb24gTXNlKGUsdCl7cmV0dXJuIFhlLm1pbkJ5KFhlLnZhbHVlcyh0KSxmdW5jdGlvbihyKXt2YXIgbj1OdW1iZXIuTkVHQVRJVkVfSU5GSU5JVFksaT1OdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFk7cmV0dXJuIFhlLmZvckluKHIsZnVuY3Rpb24obyxhKXt2YXIgcz11bHIoZSxhKS8yO249TWF0aC5tYXgobytzLG4pLGk9TWF0aC5taW4oby1zLGkpfSksbi1pfSl9ZnVuY3Rpb24gRXNlKGUsdCl7dmFyIHI9WGUudmFsdWVzKHQpLG49WGUubWluKHIpLGk9WGUubWF4KHIpO1hlLmZvckVhY2goWyJ1IiwiZCJdLGZ1bmN0aW9uKG8pe1hlLmZvckVhY2goWyJsIiwiciJdLGZ1bmN0aW9uKGEpe3ZhciBzPW8rYSxsPWVbc10sYztpZihsIT09dCl7dmFyIHU9WGUudmFsdWVzKGwpO2M9YT09PSJsIj9uLVhlLm1pbih1KTppLVhlLm1heCh1KSxjJiYoZVtzXT1YZS5tYXBWYWx1ZXMobCxmdW5jdGlvbihoKXtyZXR1cm4gaCtjfSkpfX0pfSl9ZnVuY3Rpb24gVHNlKGUsdCl7cmV0dXJuIFhlLm1hcFZhbHVlcyhlLnVsLGZ1bmN0aW9uKHIsbil7aWYodClyZXR1cm4gZVt0LnRvTG93ZXJDYXNlKCldW25dO3ZhciBpPVhlLnNvcnRCeShYZS5tYXAoZSxuKSk7cmV0dXJuKGlbMV0raVsyXSkvMn0pfWZ1bmN0aW9uIGxscihlKXt2YXIgdD1vbHIuYnVpbGRMYXllck1hdHJpeChlKSxyPVhlLm1lcmdlKHZzZShlLHQpLHhzZShlLHQpKSxuPXt9LGk7WGUuZm9yRWFjaChbInUiLCJkIl0sZnVuY3Rpb24oYSl7aT1hPT09InUiP3Q6WGUudmFsdWVzKHQpLnJldmVyc2UoKSxYZS5mb3JFYWNoKFsibCIsInIiXSxmdW5jdGlvbihzKXtzPT09InIiJiYoaT1YZS5tYXAoaSxmdW5jdGlvbihoKXtyZXR1cm4gWGUudmFsdWVzKGgpLnJldmVyc2UoKX0pKTt2YXIgbD0oYT09PSJ1Ij9lLnByZWRlY2Vzc29yczplLnN1Y2Nlc3NvcnMpLmJpbmQoZSksYz13c2UoZSxpLHIsbCksdT1Tc2UoZSxpLGMucm9vdCxjLmFsaWduLHM9PT0iciIpO3M9PT0iciImJih1PVhlLm1hcFZhbHVlcyh1LGZ1bmN0aW9uKGgpe3JldHVybi1ofSkpLG5bYStzXT11fSl9KTt2YXIgbz1Nc2UoZSxuKTtyZXR1cm4gRXNlKG4sbyksVHNlKG4sZS5ncmFwaCgpLmFsaWduKX1mdW5jdGlvbiBjbHIoZSx0LHIpe3JldHVybiBmdW5jdGlvbihuLGksbyl7dmFyIGE9bi5ub2RlKGkpLHM9bi5ub2RlKG8pLGw9MCxjO2lmKGwrPWEud2lkdGgvMixYZS5oYXMoYSwibGFiZWxwb3MiKSlzd2l0Y2goYS5sYWJlbHBvcy50b0xvd2VyQ2FzZSgpKXtjYXNlImwiOmM9LWEud2lkdGgvMjticmVhaztjYXNlInIiOmM9YS53aWR0aC8yO2JyZWFrfWlmKGMmJihsKz1yP2M6LWMpLGM9MCxsKz0oYS5kdW1teT90OmUpLzIsbCs9KHMuZHVtbXk/dDplKS8yLGwrPXMud2lkdGgvMixYZS5oYXMocywibGFiZWxwb3MiKSlzd2l0Y2gocy5sYWJlbHBvcy50b0xvd2VyQ2FzZSgpKXtjYXNlImwiOmM9cy53aWR0aC8yO2JyZWFrO2Nhc2UiciI6Yz0tcy53aWR0aC8yO2JyZWFrfXJldHVybiBjJiYobCs9cj9jOi1jKSxjPTAsbH19ZnVuY3Rpb24gdWxyKGUsdCl7cmV0dXJuIGUubm9kZSh0KS53aWR0aH19KTt2YXIgTHNlPUgoKGhRbixJc2UpPT57InVzZSBzdHJpY3QiO3ZhciBLND1xbigpLFBzZT1ucygpLGhscj1Bc2UoKS5wb3NpdGlvblg7SXNlLmV4cG9ydHM9ZmxyO2Z1bmN0aW9uIGZscihlKXtlPVBzZS5hc05vbkNvbXBvdW5kR3JhcGgoZSkscGxyKGUpLEs0LmZvckVhY2goaGxyKGUpLGZ1bmN0aW9uKHQscil7ZS5ub2RlKHIpLng9dH0pfWZ1bmN0aW9uIHBscihlKXt2YXIgdD1Qc2UuYnVpbGRMYXllck1hdHJpeChlKSxyPWUuZ3JhcGgoKS5yYW5rc2VwLG49MDtLNC5mb3JFYWNoKHQsZnVuY3Rpb24oaSl7dmFyIG89SzQubWF4KEs0Lm1hcChpLGZ1bmN0aW9uKGEpe3JldHVybiBlLm5vZGUoYSkuaGVpZ2h0fSkpO0s0LmZvckVhY2goaSxmdW5jdGlvbihhKXtlLm5vZGUoYSkueT1uK28vMn0pLG4rPW8rcn0pfX0pO3ZhciB6c2U9SCgoZlFuLE9zZSk9PnsidXNlIHN0cmljdCI7dmFyIGlyPXFuKCksa3NlPW1hZSgpLFJzZT13YWUoKSxkbHI9T2FlKCksbWxyPW5zKCkubm9ybWFsaXplUmFua3MsZ2xyPUZhZSgpLF9scj1ucygpLnJlbW92ZUVtcHR5UmFua3MsTnNlPVZhZSgpLHlscj1HYWUoKSxEc2U9WGFlKCksdmxyPXlzZSgpLHhscj1Mc2UoKSxkMD1ucygpLGJscj1VYygpLkdyYXBoO09zZS5leHBvcnRzPXdscjtmdW5jdGlvbiB3bHIoZSx0KXt2YXIgcj10JiZ0LmRlYnVnVGltaW5nP2QwLnRpbWU6ZDAubm90aW1lO3IoImxheW91dCIsZnVuY3Rpb24oKXt2YXIgbj1yKCIgIGJ1aWxkTGF5b3V0R3JhcGgiLGZ1bmN0aW9uKCl7cmV0dXJuIFJscihlKX0pO3IoIiAgcnVuTGF5b3V0IixmdW5jdGlvbigpe1NscihuLHIpfSkscigiICB1cGRhdGVJbnB1dEdyYXBoIixmdW5jdGlvbigpe01scihlLG4pfSl9KX1mdW5jdGlvbiBTbHIoZSx0KXt0KCIgICAgbWFrZVNwYWNlRm9yRWRnZUxhYmVscyIsZnVuY3Rpb24oKXtObHIoZSl9KSx0KCIgICAgcmVtb3ZlU2VsZkVkZ2VzIixmdW5jdGlvbigpe3FscihlKX0pLHQoIiAgICBhY3ljbGljIixmdW5jdGlvbigpe2tzZS5ydW4oZSl9KSx0KCIgICAgbmVzdGluZ0dyYXBoLnJ1biIsZnVuY3Rpb24oKXtOc2UucnVuKGUpfSksdCgiICAgIHJhbmsiLGZ1bmN0aW9uKCl7ZGxyKGQwLmFzTm9uQ29tcG91bmRHcmFwaChlKSl9KSx0KCIgICAgaW5qZWN0RWRnZUxhYmVsUHJveGllcyIsZnVuY3Rpb24oKXtEbHIoZSl9KSx0KCIgICAgcmVtb3ZlRW1wdHlSYW5rcyIsZnVuY3Rpb24oKXtfbHIoZSl9KSx0KCIgICAgbmVzdGluZ0dyYXBoLmNsZWFudXAiLGZ1bmN0aW9uKCl7TnNlLmNsZWFudXAoZSl9KSx0KCIgICAgbm9ybWFsaXplUmFua3MiLGZ1bmN0aW9uKCl7bWxyKGUpfSksdCgiICAgIGFzc2lnblJhbmtNaW5NYXgiLGZ1bmN0aW9uKCl7T2xyKGUpfSksdCgiICAgIHJlbW92ZUVkZ2VMYWJlbFByb3hpZXMiLGZ1bmN0aW9uKCl7emxyKGUpfSksdCgiICAgIG5vcm1hbGl6ZS5ydW4iLGZ1bmN0aW9uKCl7UnNlLnJ1bihlKX0pLHQoIiAgICBwYXJlbnREdW1teUNoYWlucyIsZnVuY3Rpb24oKXtnbHIoZSl9KSx0KCIgICAgYWRkQm9yZGVyU2VnbWVudHMiLGZ1bmN0aW9uKCl7eWxyKGUpfSksdCgiICAgIG9yZGVyIixmdW5jdGlvbigpe3ZscihlKX0pLHQoIiAgICBpbnNlcnRTZWxmRWRnZXMiLGZ1bmN0aW9uKCl7R2xyKGUpfSksdCgiICAgIGFkanVzdENvb3JkaW5hdGVTeXN0ZW0iLGZ1bmN0aW9uKCl7RHNlLmFkanVzdChlKX0pLHQoIiAgICBwb3NpdGlvbiIsZnVuY3Rpb24oKXt4bHIoZSl9KSx0KCIgICAgcG9zaXRpb25TZWxmRWRnZXMiLGZ1bmN0aW9uKCl7V2xyKGUpfSksdCgiICAgIHJlbW92ZUJvcmRlck5vZGVzIixmdW5jdGlvbigpe1VscihlKX0pLHQoIiAgICBub3JtYWxpemUudW5kbyIsZnVuY3Rpb24oKXtSc2UudW5kbyhlKX0pLHQoIiAgICBmaXh1cEVkZ2VMYWJlbENvb3JkcyIsZnVuY3Rpb24oKXtIbHIoZSl9KSx0KCIgICAgdW5kb0Nvb3JkaW5hdGVTeXN0ZW0iLGZ1bmN0aW9uKCl7RHNlLnVuZG8oZSl9KSx0KCIgICAgdHJhbnNsYXRlR3JhcGgiLGZ1bmN0aW9uKCl7RmxyKGUpfSksdCgiICAgIGFzc2lnbk5vZGVJbnRlcnNlY3RzIixmdW5jdGlvbigpe0JscihlKX0pLHQoIiAgICByZXZlcnNlUG9pbnRzIixmdW5jdGlvbigpe1ZscihlKX0pLHQoIiAgICBhY3ljbGljLnVuZG8iLGZ1bmN0aW9uKCl7a3NlLnVuZG8oZSl9KX1mdW5jdGlvbiBNbHIoZSx0KXtpci5mb3JFYWNoKGUubm9kZXMoKSxmdW5jdGlvbihyKXt2YXIgbj1lLm5vZGUociksaT10Lm5vZGUocik7biYmKG4ueD1pLngsbi55PWkueSx0LmNoaWxkcmVuKHIpLmxlbmd0aCYmKG4ud2lkdGg9aS53aWR0aCxuLmhlaWdodD1pLmhlaWdodCkpfSksaXIuZm9yRWFjaChlLmVkZ2VzKCksZnVuY3Rpb24ocil7dmFyIG49ZS5lZGdlKHIpLGk9dC5lZGdlKHIpO24ucG9pbnRzPWkucG9pbnRzLGlyLmhhcyhpLCJ4IikmJihuLng9aS54LG4ueT1pLnkpfSksZS5ncmFwaCgpLndpZHRoPXQuZ3JhcGgoKS53aWR0aCxlLmdyYXBoKCkuaGVpZ2h0PXQuZ3JhcGgoKS5oZWlnaHR9dmFyIEVscj1bIm5vZGVzZXAiLCJlZGdlc2VwIiwicmFua3NlcCIsIm1hcmdpbngiLCJtYXJnaW55Il0sVGxyPXtyYW5rc2VwOjUwLGVkZ2VzZXA6MjAsbm9kZXNlcDo1MCxyYW5rZGlyOiJ0YiJ9LENscj1bImFjeWNsaWNlciIsInJhbmtlciIsInJhbmtkaXIiLCJhbGlnbiJdLEFscj1bIndpZHRoIiwiaGVpZ2h0Il0sUGxyPXt3aWR0aDowLGhlaWdodDowfSxJbHI9WyJtaW5sZW4iLCJ3ZWlnaHQiLCJ3aWR0aCIsImhlaWdodCIsImxhYmVsb2Zmc2V0Il0sTGxyPXttaW5sZW46MSx3ZWlnaHQ6MSx3aWR0aDowLGhlaWdodDowLGxhYmVsb2Zmc2V0OjEwLGxhYmVscG9zOiJyIn0sa2xyPVsibGFiZWxwb3MiXTtmdW5jdGlvbiBSbHIoZSl7dmFyIHQ9bmV3IGJscih7bXVsdGlncmFwaDohMCxjb21wb3VuZDohMH0pLHI9T2x0KGUuZ3JhcGgoKSk7cmV0dXJuIHQuc2V0R3JhcGgoaXIubWVyZ2Uoe30sVGxyLERsdChyLEVsciksaXIucGljayhyLENscikpKSxpci5mb3JFYWNoKGUubm9kZXMoKSxmdW5jdGlvbihuKXt2YXIgaT1PbHQoZS5ub2RlKG4pKTt0LnNldE5vZGUobixpci5kZWZhdWx0cyhEbHQoaSxBbHIpLFBscikpLHQuc2V0UGFyZW50KG4sZS5wYXJlbnQobikpfSksaXIuZm9yRWFjaChlLmVkZ2VzKCksZnVuY3Rpb24obil7dmFyIGk9T2x0KGUuZWRnZShuKSk7dC5zZXRFZGdlKG4saXIubWVyZ2Uoe30sTGxyLERsdChpLElsciksaXIucGljayhpLGtscikpKX0pLHR9ZnVuY3Rpb24gTmxyKGUpe3ZhciB0PWUuZ3JhcGgoKTt0LnJhbmtzZXAvPTIsaXIuZm9yRWFjaChlLmVkZ2VzKCksZnVuY3Rpb24ocil7dmFyIG49ZS5lZGdlKHIpO24ubWlubGVuKj0yLG4ubGFiZWxwb3MudG9Mb3dlckNhc2UoKSE9PSJjIiYmKHQucmFua2Rpcj09PSJUQiJ8fHQucmFua2Rpcj09PSJCVCI/bi53aWR0aCs9bi5sYWJlbG9mZnNldDpuLmhlaWdodCs9bi5sYWJlbG9mZnNldCl9KX1mdW5jdGlvbiBEbHIoZSl7aXIuZm9yRWFjaChlLmVkZ2VzKCksZnVuY3Rpb24odCl7dmFyIHI9ZS5lZGdlKHQpO2lmKHIud2lkdGgmJnIuaGVpZ2h0KXt2YXIgbj1lLm5vZGUodC52KSxpPWUubm9kZSh0LncpLG89e3Jhbms6KGkucmFuay1uLnJhbmspLzIrbi5yYW5rLGU6dH07ZDAuYWRkRHVtbXlOb2RlKGUsImVkZ2UtcHJveHkiLG8sIl9lcCIpfX0pfWZ1bmN0aW9uIE9scihlKXt2YXIgdD0wO2lyLmZvckVhY2goZS5ub2RlcygpLGZ1bmN0aW9uKHIpe3ZhciBuPWUubm9kZShyKTtuLmJvcmRlclRvcCYmKG4ubWluUmFuaz1lLm5vZGUobi5ib3JkZXJUb3ApLnJhbmssbi5tYXhSYW5rPWUubm9kZShuLmJvcmRlckJvdHRvbSkucmFuayx0PWlyLm1heCh0LG4ubWF4UmFuaykpfSksZS5ncmFwaCgpLm1heFJhbms9dH1mdW5jdGlvbiB6bHIoZSl7aXIuZm9yRWFjaChlLm5vZGVzKCksZnVuY3Rpb24odCl7dmFyIHI9ZS5ub2RlKHQpO3IuZHVtbXk9PT0iZWRnZS1wcm94eSImJihlLmVkZ2Uoci5lKS5sYWJlbFJhbms9ci5yYW5rLGUucmVtb3ZlTm9kZSh0KSl9KX1mdW5jdGlvbiBGbHIoZSl7dmFyIHQ9TnVtYmVyLlBPU0lUSVZFX0lORklOSVRZLHI9MCxuPU51bWJlci5QT1NJVElWRV9JTkZJTklUWSxpPTAsbz1lLmdyYXBoKCksYT1vLm1hcmdpbnh8fDAscz1vLm1hcmdpbnl8fDA7ZnVuY3Rpb24gbChjKXt2YXIgdT1jLngsaD1jLnksZj1jLndpZHRoLHA9Yy5oZWlnaHQ7dD1NYXRoLm1pbih0LHUtZi8yKSxyPU1hdGgubWF4KHIsdStmLzIpLG49TWF0aC5taW4obixoLXAvMiksaT1NYXRoLm1heChpLGgrcC8yKX1pci5mb3JFYWNoKGUubm9kZXMoKSxmdW5jdGlvbihjKXtsKGUubm9kZShjKSl9KSxpci5mb3JFYWNoKGUuZWRnZXMoKSxmdW5jdGlvbihjKXt2YXIgdT1lLmVkZ2UoYyk7aXIuaGFzKHUsIngiKSYmbCh1KX0pLHQtPWEsbi09cyxpci5mb3JFYWNoKGUubm9kZXMoKSxmdW5jdGlvbihjKXt2YXIgdT1lLm5vZGUoYyk7dS54LT10LHUueS09bn0pLGlyLmZvckVhY2goZS5lZGdlcygpLGZ1bmN0aW9uKGMpe3ZhciB1PWUuZWRnZShjKTtpci5mb3JFYWNoKHUucG9pbnRzLGZ1bmN0aW9uKGgpe2gueC09dCxoLnktPW59KSxpci5oYXModSwieCIpJiYodS54LT10KSxpci5oYXModSwieSIpJiYodS55LT1uKX0pLG8ud2lkdGg9ci10K2Esby5oZWlnaHQ9aS1uK3N9ZnVuY3Rpb24gQmxyKGUpe2lyLmZvckVhY2goZS5lZGdlcygpLGZ1bmN0aW9uKHQpe3ZhciByPWUuZWRnZSh0KSxuPWUubm9kZSh0LnYpLGk9ZS5ub2RlKHQudyksbyxhO3IucG9pbnRzPyhvPXIucG9pbnRzWzBdLGE9ci5wb2ludHNbci5wb2ludHMubGVuZ3RoLTFdKTooci5wb2ludHM9W10sbz1pLGE9biksci5wb2ludHMudW5zaGlmdChkMC5pbnRlcnNlY3RSZWN0KG4sbykpLHIucG9pbnRzLnB1c2goZDAuaW50ZXJzZWN0UmVjdChpLGEpKX0pfWZ1bmN0aW9uIEhscihlKXtpci5mb3JFYWNoKGUuZWRnZXMoKSxmdW5jdGlvbih0KXt2YXIgcj1lLmVkZ2UodCk7aWYoaXIuaGFzKHIsIngiKSlzd2l0Y2goKHIubGFiZWxwb3M9PT0ibCJ8fHIubGFiZWxwb3M9PT0iciIpJiYoci53aWR0aC09ci5sYWJlbG9mZnNldCksci5sYWJlbHBvcyl7Y2FzZSJsIjpyLngtPXIud2lkdGgvMityLmxhYmVsb2Zmc2V0O2JyZWFrO2Nhc2UiciI6ci54Kz1yLndpZHRoLzIrci5sYWJlbG9mZnNldDticmVha319KX1mdW5jdGlvbiBWbHIoZSl7aXIuZm9yRWFjaChlLmVkZ2VzKCksZnVuY3Rpb24odCl7dmFyIHI9ZS5lZGdlKHQpO3IucmV2ZXJzZWQmJnIucG9pbnRzLnJldmVyc2UoKX0pfWZ1bmN0aW9uIFVscihlKXtpci5mb3JFYWNoKGUubm9kZXMoKSxmdW5jdGlvbih0KXtpZihlLmNoaWxkcmVuKHQpLmxlbmd0aCl7dmFyIHI9ZS5ub2RlKHQpLG49ZS5ub2RlKHIuYm9yZGVyVG9wKSxpPWUubm9kZShyLmJvcmRlckJvdHRvbSksbz1lLm5vZGUoaXIubGFzdChyLmJvcmRlckxlZnQpKSxhPWUubm9kZShpci5sYXN0KHIuYm9yZGVyUmlnaHQpKTtyLndpZHRoPU1hdGguYWJzKGEueC1vLngpLHIuaGVpZ2h0PU1hdGguYWJzKGkueS1uLnkpLHIueD1vLngrci53aWR0aC8yLHIueT1uLnkrci5oZWlnaHQvMn19KSxpci5mb3JFYWNoKGUubm9kZXMoKSxmdW5jdGlvbih0KXtlLm5vZGUodCkuZHVtbXk9PT0iYm9yZGVyIiYmZS5yZW1vdmVOb2RlKHQpfSl9ZnVuY3Rpb24gcWxyKGUpe2lyLmZvckVhY2goZS5lZGdlcygpLGZ1bmN0aW9uKHQpe2lmKHQudj09PXQudyl7dmFyIHI9ZS5ub2RlKHQudik7ci5zZWxmRWRnZXN8fChyLnNlbGZFZGdlcz1bXSksci5zZWxmRWRnZXMucHVzaCh7ZTp0LGxhYmVsOmUuZWRnZSh0KX0pLGUucmVtb3ZlRWRnZSh0KX19KX1mdW5jdGlvbiBHbHIoZSl7dmFyIHQ9ZDAuYnVpbGRMYXllck1hdHJpeChlKTtpci5mb3JFYWNoKHQsZnVuY3Rpb24ocil7dmFyIG49MDtpci5mb3JFYWNoKHIsZnVuY3Rpb24oaSxvKXt2YXIgYT1lLm5vZGUoaSk7YS5vcmRlcj1vK24saXIuZm9yRWFjaChhLnNlbGZFZGdlcyxmdW5jdGlvbihzKXtkMC5hZGREdW1teU5vZGUoZSwic2VsZmVkZ2UiLHt3aWR0aDpzLmxhYmVsLndpZHRoLGhlaWdodDpzLmxhYmVsLmhlaWdodCxyYW5rOmEucmFuayxvcmRlcjpvKyArK24sZTpzLmUsbGFiZWw6cy5sYWJlbH0sIl9zZSIpfSksZGVsZXRlIGEuc2VsZkVkZ2VzfSl9KX1mdW5jdGlvbiBXbHIoZSl7aXIuZm9yRWFjaChlLm5vZGVzKCksZnVuY3Rpb24odCl7dmFyIHI9ZS5ub2RlKHQpO2lmKHIuZHVtbXk9PT0ic2VsZmVkZ2UiKXt2YXIgbj1lLm5vZGUoci5lLnYpLGk9bi54K24ud2lkdGgvMixvPW4ueSxhPXIueC1pLHM9bi5oZWlnaHQvMjtlLnNldEVkZ2Uoci5lLHIubGFiZWwpLGUucmVtb3ZlTm9kZSh0KSxyLmxhYmVsLnBvaW50cz1be3g6aSsyKmEvMyx5Om8tc30se3g6aSs1KmEvNix5Om8tc30se3g6aSthLHk6b30se3g6aSs1KmEvNix5Om8rc30se3g6aSsyKmEvMyx5Om8rc31dLHIubGFiZWwueD1yLngsci5sYWJlbC55PXIueX19KX1mdW5jdGlvbiBEbHQoZSx0KXtyZXR1cm4gaXIubWFwVmFsdWVzKGlyLnBpY2soZSx0KSxOdW1iZXIpfWZ1bmN0aW9uIE9sdChlKXt2YXIgdD17fTtyZXR1cm4gaXIuZm9yRWFjaChlLGZ1bmN0aW9uKHIsbil7dFtuLnRvTG93ZXJDYXNlKCldPXJ9KSx0fX0pO3ZhciBCc2U9SCgocFFuLEZzZSk9Pnt2YXIgZkg9cW4oKSxZbHI9bnMoKSxqbHI9VWMoKS5HcmFwaDtGc2UuZXhwb3J0cz17ZGVidWdPcmRlcmluZzpYbHJ9O2Z1bmN0aW9uIFhscihlKXt2YXIgdD1ZbHIuYnVpbGRMYXllck1hdHJpeChlKSxyPW5ldyBqbHIoe2NvbXBvdW5kOiEwLG11bHRpZ3JhcGg6ITB9KS5zZXRHcmFwaCh7fSk7cmV0dXJuIGZILmZvckVhY2goZS5ub2RlcygpLGZ1bmN0aW9uKG4pe3Iuc2V0Tm9kZShuLHtsYWJlbDpufSksci5zZXRQYXJlbnQobiwibGF5ZXIiK2Uubm9kZShuKS5yYW5rKX0pLGZILmZvckVhY2goZS5lZGdlcygpLGZ1bmN0aW9uKG4pe3Iuc2V0RWRnZShuLnYsbi53LHt9LG4ubmFtZSl9KSxmSC5mb3JFYWNoKHQsZnVuY3Rpb24obixpKXt2YXIgbz0ibGF5ZXIiK2k7ci5zZXROb2RlKG8se3Jhbms6InNhbWUifSksZkgucmVkdWNlKG4sZnVuY3Rpb24oYSxzKXtyZXR1cm4gci5zZXRFZGdlKGEscyx7c3R5bGU6ImludmlzIn0pLHN9KX0pLHJ9fSk7dmFyIFZzZT1IKChkUW4sSHNlKT0+e0hzZS5leHBvcnRzPSIwLjguNSJ9KTt2YXIgemx0PUgoKG1RbixVc2UpPT57VXNlLmV4cG9ydHM9e2dyYXBobGliOlVjKCksbGF5b3V0OnpzZSgpLGRlYnVnOkJzZSgpLHV0aWw6e3RpbWU6bnMoKS50aW1lLG5vdGltZTpucygpLm5vdGltZX0sdmVyc2lvbjpWc2UoKX19KTt2YXIgemR0PUVlKE9kdCgpLDEpLHtfX2V4dGVuZHM6Y19yLF9fYXNzaWduOnVfcixfX3Jlc3Q6aF9yLF9fZGVjb3JhdGU6RSxfX3BhcmFtOmZfcixfX21ldGFkYXRhOncsX19hd2FpdGVyOnBfcixfX2dlbmVyYXRvcjpkX3IsX19leHBvcnRTdGFyOm1fcixfX2NyZWF0ZUJpbmRpbmc6Z19yLF9fdmFsdWVzOl9fcixfX3JlYWQ6eV9yLF9fc3ByZWFkOnZfcixfX3NwcmVhZEFycmF5czp4X3IsX19zcHJlYWRBcnJheTpiX3IsX19hd2FpdDp3X3IsX19hc3luY0dlbmVyYXRvcjpTX3IsX19hc3luY0RlbGVnYXRvcjpNX3IsX19hc3luY1ZhbHVlczpFX3IsX19tYWtlVGVtcGxhdGVPYmplY3Q6VF9yLF9faW1wb3J0U3RhcjpDX3IsX19pbXBvcnREZWZhdWx0OkFfcixfX2NsYXNzUHJpdmF0ZUZpZWxkR2V0OlBfcixfX2NsYXNzUHJpdmF0ZUZpZWxkU2V0OklfcixfX2NsYXNzUHJpdmF0ZUZpZWxkSW46TF9yfT16ZHQuZGVmYXVsdDtmdW5jdGlvbiB5dChlKXtyZXR1cm4gdD0+e2lmKGUpaWYodC5oYXNPd25Qcm9wZXJ0eSgiaXMiKSl7aWYoZSE9PXQuaXMpdGhyb3cgbmV3IEVycm9yKGBjdXN0b20gZWxlbWVudCB0YWcgbmFtZXMgZG8gbm90IG1hdGNoOiAoJHtlfSAhPT0gJHt0LmlzfSlgKX1lbHNlIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0LCJpcyIse3ZhbHVlOmV9KTt3aW5kb3cuY3VzdG9tRWxlbWVudHMuZGVmaW5lKHQuaXMsdCl9fWZ1bmN0aW9uIEZkdChlLHQscil7ZS5jb25zdHJ1Y3Rvci5oYXNPd25Qcm9wZXJ0eSgicHJvcGVydGllcyIpfHxPYmplY3QuZGVmaW5lUHJvcGVydHkoZS5jb25zdHJ1Y3RvciwicHJvcGVydGllcyIse3ZhbHVlOnt9fSksZS5jb25zdHJ1Y3Rvci5wcm9wZXJ0aWVzW3RdPU9iamVjdC5hc3NpZ24oe30sZS5jb25zdHJ1Y3Rvci5wcm9wZXJ0aWVzW3RdLHIpfWZ1bmN0aW9uIEEoZSl7cmV0dXJuKHQscik9PntGZHQodCxyLGUpfX1mdW5jdGlvbiBCdCguLi5lKXtyZXR1cm4odCxyKT0+e3QuY29uc3RydWN0b3IuaGFzT3duUHJvcGVydHkoIm9ic2VydmVycyIpfHxPYmplY3QuZGVmaW5lUHJvcGVydHkodC5jb25zdHJ1Y3Rvciwib2JzZXJ2ZXJzIix7dmFsdWU6W119KSx0LmNvbnN0cnVjdG9yLm9ic2VydmVycy5wdXNoKGAke3J9KCR7ZS5qb2luKCIsIil9KWApfX1mdW5jdGlvbiBSdChlLC4uLnQpe3JldHVybihyLG4saSk9PntsZXQgbz1gX19jb21wdXRlJHtufWA7T2JqZWN0LmRlZmluZVByb3BlcnR5KHIsbyx7dmFsdWU6aS5nZXR9KSxpLmdldD12b2lkIDA7bGV0IGE9W2UsLi4udF0uam9pbigiLCIpO0ZkdChyLG4se2NvbXB1dGVkOmAke299KCR7YX0pYH0pfX12YXIgUl9yPUJkdCgoZSx0KT0+ZS5xdWVyeVNlbGVjdG9yKHQpKSxOX3I9QmR0KChlLHQpPT5lLnF1ZXJ5U2VsZWN0b3JBbGwodCkpO2Z1bmN0aW9uIEJkdChlKXtyZXR1cm4gdD0+KHIsbik9PntPYmplY3QuZGVmaW5lUHJvcGVydHkocixuLHtnZXQoKXtyZXR1cm4gZSh0aGlzLnNoYWRvd1Jvb3QsdCl9LGVudW1lcmFibGU6ITAsY29uZmlndXJhYmxlOiEwfSl9fXdpbmRvdy5KU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5PWZ1bmN0aW9uKGUsdCl7cmV0dXJuIGV9O3ZhciBiMWU9Lyh1cmxcKCkoW14pXSopKFwpKS9nLHcxZT0vKF5cL1teXC9dKXwoXiMpfCheW1x3LVxkXSo6KS8sUEksWnM7ZnVuY3Rpb24gbF8oZSx0KXtpZihlJiZ3MWUudGVzdChlKXx8ZT09PSIvLyIpcmV0dXJuIGU7aWYoUEk9PT12b2lkIDApe1BJPSExO3RyeXtsZXQgcj1uZXcgVVJMKCJiIiwiaHR0cDovL2EiKTtyLnBhdGhuYW1lPSJjJTIwZCIsUEk9ci5ocmVmPT09Imh0dHA6Ly9hL2MlMjBkIn1jYXRjaChyKXt9fWlmKHR8fCh0PWRvY3VtZW50LmJhc2VVUkl8fHdpbmRvdy5sb2NhdGlvbi5ocmVmKSxQSSl0cnl7cmV0dXJuIG5ldyBVUkwoZSx0KS5ocmVmfWNhdGNoKHIpe3JldHVybiBlfXJldHVybiBac3x8KFpzPWRvY3VtZW50LmltcGxlbWVudGF0aW9uLmNyZWF0ZUhUTUxEb2N1bWVudCgidGVtcCIpLFpzLmJhc2U9WnMuY3JlYXRlRWxlbWVudCgiYmFzZSIpLFpzLmhlYWQuYXBwZW5kQ2hpbGQoWnMuYmFzZSksWnMuYW5jaG9yPVpzLmNyZWF0ZUVsZW1lbnQoImEiKSxacy5ib2R5LmFwcGVuZENoaWxkKFpzLmFuY2hvcikpLFpzLmJhc2UuaHJlZj10LFpzLmFuY2hvci5ocmVmPWUsWnMuYW5jaG9yLmhyZWZ8fGV9ZnVuY3Rpb24gSE0oZSx0KXtyZXR1cm4gZS5yZXBsYWNlKGIxZSxmdW5jdGlvbihyLG4saSxvKXtyZXR1cm4gbisiJyIrbF8oaS5yZXBsYWNlKC9bIiddL2csIiIpLHQpKyInIitvfSl9ZnVuY3Rpb24gQ3goZSl7cmV0dXJuIGUuc3Vic3RyaW5nKDAsZS5sYXN0SW5kZXhPZigiLyIpKzEpfXZhciBjXz0hd2luZG93LlNoYWR5RE9NfHwhd2luZG93LlNoYWR5RE9NLmluVXNlLEhfcj1Cb29sZWFuKCF3aW5kb3cuU2hhZHlDU1N8fHdpbmRvdy5TaGFkeUNTUy5uYXRpdmVDc3MpLFZfcj0hd2luZG93LmN1c3RvbUVsZW1lbnRzLnBvbHlmaWxsV3JhcEZsdXNoQ2FsbGJhY2ssSGR0PWNfJiYiYWRvcHRlZFN0eWxlU2hlZXRzImluIERvY3VtZW50LnByb3RvdHlwZSYmInJlcGxhY2VTeW5jImluIENTU1N0eWxlU2hlZXQucHJvdG90eXBlJiYoKCk9Pnt0cnl7bGV0IGU9bmV3IENTU1N0eWxlU2hlZXQ7ZS5yZXBsYWNlU3luYygiIik7bGV0IHQ9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7cmV0dXJuIHQuYXR0YWNoU2hhZG93KHttb2RlOiJvcGVuIn0pLHQuc2hhZG93Um9vdC5hZG9wdGVkU3R5bGVTaGVldHM9W2VdLHQuc2hhZG93Um9vdC5hZG9wdGVkU3R5bGVTaGVldHNbMF09PT1lfWNhdGNoKGUpe3JldHVybiExfX0pKCksVmR0PXdpbmRvdy5Qb2x5bWVyJiZ3aW5kb3cuUG9seW1lci5yb290UGF0aHx8Q3goZG9jdW1lbnQuYmFzZVVSSXx8d2luZG93LmxvY2F0aW9uLmhyZWYpO3ZhciBWTT13aW5kb3cuUG9seW1lciYmd2luZG93LlBvbHltZXIuc2FuaXRpemVET01WYWx1ZXx8dm9pZCAwO3ZhciBVZHQ9d2luZG93LlBvbHltZXImJndpbmRvdy5Qb2x5bWVyLnNldFBhc3NpdmVUb3VjaEdlc3R1cmVzfHwhMTt2YXIgaXU9d2luZG93LlBvbHltZXImJndpbmRvdy5Qb2x5bWVyLnN0cmljdFRlbXBsYXRlUG9saWN5fHwhMTt2YXIgcWR0PXdpbmRvdy5Qb2x5bWVyJiZ3aW5kb3cuUG9seW1lci5hbGxvd1RlbXBsYXRlRnJvbURvbU1vZHVsZXx8ITE7dmFyIHBwPXdpbmRvdy5Qb2x5bWVyJiZ3aW5kb3cuUG9seW1lci5sZWdhY3lPcHRpbWl6YXRpb25zfHwhMTt2YXIgSUk9d2luZG93LlBvbHltZXImJndpbmRvdy5Qb2x5bWVyLmxlZ2FjeVdhcm5pbmdzfHwhMTt2YXIgR2R0PXdpbmRvdy5Qb2x5bWVyJiZ3aW5kb3cuUG9seW1lci5zeW5jSW5pdGlhbFJlbmRlcnx8ITE7dmFyIExJPXdpbmRvdy5Qb2x5bWVyJiZ3aW5kb3cuUG9seW1lci5sZWdhY3lVbmRlZmluZWR8fCExO3ZhciBXZHQ9d2luZG93LlBvbHltZXImJndpbmRvdy5Qb2x5bWVyLm9yZGVyZWRDb21wdXRlZHx8ITE7dmFyIGhHPSEwO3ZhciBmRz13aW5kb3cuUG9seW1lciYmd2luZG93LlBvbHltZXIucmVtb3ZlTmVzdGVkVGVtcGxhdGVzfHwhMTt2YXIga0k9d2luZG93LlBvbHltZXImJndpbmRvdy5Qb2x5bWVyLmZhc3REb21JZnx8ITE7dmFyIFVNPXdpbmRvdy5Qb2x5bWVyJiZ3aW5kb3cuUG9seW1lci5zdXBwcmVzc1RlbXBsYXRlTm90aWZpY2F0aW9uc3x8ITE7dmFyIHFNPXdpbmRvdy5Qb2x5bWVyJiZ3aW5kb3cuUG9seW1lci5sZWdhY3lOb09ic2VydmVkQXR0cmlidXRlc3x8ITE7dmFyIFlkdD13aW5kb3cuUG9seW1lciYmd2luZG93LlBvbHltZXIudXNlQWRvcHRlZFN0eWxlU2hlZXRzV2l0aEJ1aWx0Q1NTfHwhMTt2YXIgUzFlPTA7ZnVuY3Rpb24gamR0KCl7fWpkdC5wcm90b3R5cGUuX19taXhpbkFwcGxpY2F0aW9ucztqZHQucHJvdG90eXBlLl9fbWl4aW5TZXQ7dmFyIE5uPWZ1bmN0aW9uKGUpe2xldCB0PWUuX19taXhpbkFwcGxpY2F0aW9uczt0fHwodD1uZXcgV2Vha01hcCxlLl9fbWl4aW5BcHBsaWNhdGlvbnM9dCk7bGV0IHI9UzFlKys7ZnVuY3Rpb24gbihpKXtsZXQgbz1pLl9fbWl4aW5TZXQ7aWYobyYmb1tyXSlyZXR1cm4gaTtsZXQgYT10LHM9YS5nZXQoaSk7aWYoIXMpe3M9ZShpKSxhLnNldChpLHMpO2xldCBsPU9iamVjdC5jcmVhdGUocy5fX21peGluU2V0fHxvfHxudWxsKTtsW3JdPSEwLHMuX19taXhpblNldD1sfXJldHVybiBzfXJldHVybiBufTt2YXIgcEc9e30sS2R0PXt9O2Z1bmN0aW9uIFhkdChlLHQpe3BHW2VdPUtkdFtlLnRvTG93ZXJDYXNlKCldPXR9ZnVuY3Rpb24gJGR0KGUpe3JldHVybiBwR1tlXXx8S2R0W2UudG9Mb3dlckNhc2UoKV19ZnVuY3Rpb24gTTFlKGUpe2UucXVlcnlTZWxlY3Rvcigic3R5bGUiKSYmY29uc29sZS53YXJuKCJkb20tbW9kdWxlICVzIGhhcyBzdHlsZSBvdXRzaWRlIHRlbXBsYXRlIixlLmlkKX12YXIgb3U9Y2xhc3MgZXh0ZW5kcyBIVE1MRWxlbWVudHtzdGF0aWMgZ2V0IG9ic2VydmVkQXR0cmlidXRlcygpe3JldHVyblsiaWQiXX1zdGF0aWMgaW1wb3J0KHQscil7aWYodCl7bGV0IG49JGR0KHQpO3JldHVybiBuJiZyP24ucXVlcnlTZWxlY3RvcihyKTpufXJldHVybiBudWxsfWF0dHJpYnV0ZUNoYW5nZWRDYWxsYmFjayh0LHIsbixpKXtyIT09biYmdGhpcy5yZWdpc3RlcigpfWdldCBhc3NldHBhdGgoKXtpZighdGhpcy5fX2Fzc2V0cGF0aCl7bGV0IHQ9d2luZG93LkhUTUxJbXBvcnRzJiZIVE1MSW1wb3J0cy5pbXBvcnRGb3JFbGVtZW50P0hUTUxJbXBvcnRzLmltcG9ydEZvckVsZW1lbnQodGhpcyl8fGRvY3VtZW50OnRoaXMub3duZXJEb2N1bWVudCxyPWxfKHRoaXMuZ2V0QXR0cmlidXRlKCJhc3NldHBhdGgiKXx8IiIsdC5iYXNlVVJJKTt0aGlzLl9fYXNzZXRwYXRoPUN4KHIpfXJldHVybiB0aGlzLl9fYXNzZXRwYXRofXJlZ2lzdGVyKHQpe2lmKHQ9dHx8dGhpcy5pZCx0KXtpZihpdSYmJGR0KHQpIT09dm9pZCAwKXRocm93IFhkdCh0LG51bGwpLG5ldyBFcnJvcihgc3RyaWN0VGVtcGxhdGVQb2xpY3k6IGRvbS1tb2R1bGUgJHt0fSByZS1yZWdpc3RlcmVkYCk7dGhpcy5pZD10LFhkdCh0LHRoaXMpLE0xZSh0aGlzKX19fTtvdS5wcm90b3R5cGUubW9kdWxlcz1wRztjdXN0b21FbGVtZW50cy5kZWZpbmUoImRvbS1tb2R1bGUiLG91KTt2YXIgRTFlPSJsaW5rW3JlbD1pbXBvcnRdW3R5cGV+PWNzc10iLFQxZT0iaW5jbHVkZSIsWmR0PSJzaGFkeS11bnNjb3BlZCI7ZnVuY3Rpb24gZEcoZSl7cmV0dXJuIG91LmltcG9ydChlKX1mdW5jdGlvbiBKZHQoZSl7bGV0IHQ9ZS5ib2R5P2UuYm9keTplLHI9SE0odC50ZXh0Q29udGVudCxlLmJhc2VVUkkpLG49ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3R5bGUiKTtyZXR1cm4gbi50ZXh0Q29udGVudD1yLG59ZnVuY3Rpb24gQzFlKGUpe2xldCB0PWUudHJpbSgpLnNwbGl0KC9ccysvKSxyPVtdO2ZvcihsZXQgbj0wO248dC5sZW5ndGg7bisrKXIucHVzaCguLi5BMWUodFtuXSkpO3JldHVybiByfWZ1bmN0aW9uIEExZShlKXtsZXQgdD1kRyhlKTtpZighdClyZXR1cm4gY29uc29sZS53YXJuKCJDb3VsZCBub3QgZmluZCBzdHlsZSBkYXRhIGluIG1vZHVsZSBuYW1lZCIsZSksW107aWYodC5fc3R5bGVzPT09dm9pZCAwKXtsZXQgcj1bXTtyLnB1c2goLi4ubUcodCkpO2xldCBuPXQucXVlcnlTZWxlY3RvcigidGVtcGxhdGUiKTtuJiZyLnB1c2goLi4uUkkobix0LmFzc2V0cGF0aCkpLHQuX3N0eWxlcz1yfXJldHVybiB0Ll9zdHlsZXN9ZnVuY3Rpb24gUkkoZSx0KXtpZighZS5fc3R5bGVzKXtsZXQgcj1bXSxuPWUuY29udGVudC5xdWVyeVNlbGVjdG9yQWxsKCJzdHlsZSIpO2ZvcihsZXQgaT0wO2k8bi5sZW5ndGg7aSsrKXtsZXQgbz1uW2ldLGE9by5nZXRBdHRyaWJ1dGUoVDFlKTthJiZyLnB1c2goLi4uQzFlKGEpLmZpbHRlcihmdW5jdGlvbihzLGwsYyl7cmV0dXJuIGMuaW5kZXhPZihzKT09PWx9KSksdCYmKG8udGV4dENvbnRlbnQ9SE0oby50ZXh0Q29udGVudCx0KSksci5wdXNoKG8pfWUuX3N0eWxlcz1yfXJldHVybiBlLl9zdHlsZXN9ZnVuY3Rpb24gUWR0KGUpe2xldCB0PWRHKGUpO3JldHVybiB0P21HKHQpOltdfWZ1bmN0aW9uIG1HKGUpe2xldCB0PVtdLHI9ZS5xdWVyeVNlbGVjdG9yQWxsKEUxZSk7Zm9yKGxldCBuPTA7bjxyLmxlbmd0aDtuKyspe2xldCBpPXJbbl07aWYoaS5pbXBvcnQpe2xldCBvPWkuaW1wb3J0LGE9aS5oYXNBdHRyaWJ1dGUoWmR0KTtpZihhJiYhby5fdW5zY29wZWRTdHlsZSl7bGV0IHM9SmR0KG8pO3Muc2V0QXR0cmlidXRlKFpkdCwiIiksby5fdW5zY29wZWRTdHlsZT1zfWVsc2Ugby5fc3R5bGV8fChvLl9zdHlsZT1KZHQobykpO3QucHVzaChhP28uX3Vuc2NvcGVkU3R5bGU6by5fc3R5bGUpfX1yZXR1cm4gdH1mdW5jdGlvbiB0bXQoZSl7bGV0IHQ9ZS50cmltKCkuc3BsaXQoL1xzKy8pLHI9IiI7Zm9yKGxldCBuPTA7bjx0Lmxlbmd0aDtuKyspcis9UDFlKHRbbl0pO3JldHVybiByfWZ1bmN0aW9uIFAxZShlKXtsZXQgdD1kRyhlKTtpZih0JiZ0Ll9jc3NUZXh0PT09dm9pZCAwKXtsZXQgcj1MMWUodCksbj10LnF1ZXJ5U2VsZWN0b3IoInRlbXBsYXRlIik7biYmKHIrPUkxZShuLHQuYXNzZXRwYXRoKSksdC5fY3NzVGV4dD1yfHxudWxsfXJldHVybiB0fHxjb25zb2xlLndhcm4oIkNvdWxkIG5vdCBmaW5kIHN0eWxlIGRhdGEgaW4gbW9kdWxlIG5hbWVkIixlKSx0JiZ0Ll9jc3NUZXh0fHwiIn1mdW5jdGlvbiBJMWUoZSx0KXtsZXQgcj0iIixuPVJJKGUsdCk7Zm9yKGxldCBpPTA7aTxuLmxlbmd0aDtpKyspe2xldCBvPW5baV07by5wYXJlbnROb2RlJiZvLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQobykscis9by50ZXh0Q29udGVudH1yZXR1cm4gcn1mdW5jdGlvbiBMMWUoZSl7bGV0IHQ9IiIscj1tRyhlKTtmb3IobGV0IG49MDtuPHIubGVuZ3RoO24rKyl0Kz1yW25dLnRleHRDb250ZW50O3JldHVybiB0fXZhciB1ZT13aW5kb3cuU2hhZHlET00mJndpbmRvdy5TaGFkeURPTS5ub1BhdGNoJiZ3aW5kb3cuU2hhZHlET00ud3JhcD93aW5kb3cuU2hhZHlET00ud3JhcDp3aW5kb3cuU2hhZHlET00/ZT0+U2hhZHlET00ucGF0Y2goZSk6ZT0+ZTtmdW5jdGlvbiBOSShlKXtyZXR1cm4gZS5pbmRleE9mKCIuIik+PTB9ZnVuY3Rpb24gYXUoZSl7bGV0IHQ9ZS5pbmRleE9mKCIuIik7cmV0dXJuIHQ9PT0tMT9lOmUuc2xpY2UoMCx0KX1mdW5jdGlvbiBnRyhlLHQpe3JldHVybiBlLmluZGV4T2YodCsiLiIpPT09MH1mdW5jdGlvbiBBeChlLHQpe3JldHVybiB0LmluZGV4T2YoZSsiLiIpPT09MH1mdW5jdGlvbiBkcChlLHQscil7cmV0dXJuIHQrci5zbGljZShlLmxlbmd0aCl9ZnVuY3Rpb24gREkoZSx0KXtyZXR1cm4gZT09PXR8fGdHKGUsdCl8fEF4KGUsdCl9ZnVuY3Rpb24gUHgoZSl7aWYoQXJyYXkuaXNBcnJheShlKSl7bGV0IHQ9W107Zm9yKGxldCByPTA7cjxlLmxlbmd0aDtyKyspe2xldCBuPWVbcl0udG9TdHJpbmcoKS5zcGxpdCgiLiIpO2ZvcihsZXQgaT0wO2k8bi5sZW5ndGg7aSsrKXQucHVzaChuW2ldKX1yZXR1cm4gdC5qb2luKCIuIil9ZWxzZSByZXR1cm4gZX1mdW5jdGlvbiBlbXQoZSl7cmV0dXJuIEFycmF5LmlzQXJyYXkoZSk/UHgoZSkuc3BsaXQoIi4iKTplLnRvU3RyaW5nKCkuc3BsaXQoIi4iKX1mdW5jdGlvbiBObyhlLHQscil7bGV0IG49ZSxpPWVtdCh0KTtmb3IobGV0IG89MDtvPGkubGVuZ3RoO28rKyl7aWYoIW4pcmV0dXJuO2xldCBhPWlbb107bj1uW2FdfXJldHVybiByJiYoci5wYXRoPWkuam9pbigiLiIpKSxufWZ1bmN0aW9uIF9HKGUsdCxyKXtsZXQgbj1lLGk9ZW10KHQpLG89aVtpLmxlbmd0aC0xXTtpZihpLmxlbmd0aD4xKXtmb3IobGV0IGE9MDthPGkubGVuZ3RoLTE7YSsrKXtsZXQgcz1pW2FdO2lmKG49bltzXSwhbilyZXR1cm59bltvXT1yfWVsc2Ugblt0XT1yO3JldHVybiBpLmpvaW4oIi4iKX12YXIgT0k9e30sazFlPS8tW2Etel0vZyxSMWU9LyhbQS1aXSkvZztmdW5jdGlvbiB3bShlKXtyZXR1cm4gT0lbZV18fChPSVtlXT1lLmluZGV4T2YoIi0iKTwwP2U6ZS5yZXBsYWNlKGsxZSx0PT50WzFdLnRvVXBwZXJDYXNlKCkpKX1mdW5jdGlvbiBJeChlKXtyZXR1cm4gT0lbZV18fChPSVtlXT1lLnJlcGxhY2UoUjFlLCItJDEiKS50b0xvd2VyQ2FzZSgpKX12YXIgTjFlPTAscm10PTAsTHg9W10sRDFlPTAseUc9ITEsbm10PWRvY3VtZW50LmNyZWF0ZVRleHROb2RlKCIiKTtuZXcgd2luZG93Lk11dGF0aW9uT2JzZXJ2ZXIoTzFlKS5vYnNlcnZlKG5tdCx7Y2hhcmFjdGVyRGF0YTohMH0pO2Z1bmN0aW9uIE8xZSgpe3lHPSExO2xldCBlPUx4Lmxlbmd0aDtmb3IobGV0IHQ9MDt0PGU7dCsrKXtsZXQgcj1MeFt0XTtpZihyKXRyeXtyKCl9Y2F0Y2gobil7c2V0VGltZW91dCgoKT0+e3Rocm93IG59KX19THguc3BsaWNlKDAsZSkscm10Kz1lfXZhciBtbz17YWZ0ZXIoZSl7cmV0dXJue3J1bih0KXtyZXR1cm4gd2luZG93LnNldFRpbWVvdXQodCxlKX0sY2FuY2VsKHQpe3dpbmRvdy5jbGVhclRpbWVvdXQodCl9fX0scnVuKGUsdCl7cmV0dXJuIHdpbmRvdy5zZXRUaW1lb3V0KGUsdCl9LGNhbmNlbChlKXt3aW5kb3cuY2xlYXJUaW1lb3V0KGUpfX07dmFyIE5pPXtydW4oZSl7cmV0dXJuIHdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUoZSl9LGNhbmNlbChlKXt3aW5kb3cuY2FuY2VsQW5pbWF0aW9uRnJhbWUoZSl9fTt2YXIga3g9e3J1bihlKXtyZXR1cm4gd2luZG93LnJlcXVlc3RJZGxlQ2FsbGJhY2s/d2luZG93LnJlcXVlc3RJZGxlQ2FsbGJhY2soZSk6d2luZG93LnNldFRpbWVvdXQoZSwxNil9LGNhbmNlbChlKXt3aW5kb3cuY2FuY2VsSWRsZUNhbGxiYWNrP3dpbmRvdy5jYW5jZWxJZGxlQ2FsbGJhY2soZSk6d2luZG93LmNsZWFyVGltZW91dChlKX19O3ZhciBjaT17cnVuKGUpe3JldHVybiB5R3x8KHlHPSEwLG5tdC50ZXh0Q29udGVudD1EMWUrKyksTHgucHVzaChlKSxOMWUrK30sY2FuY2VsKGUpe2xldCB0PWUtcm10O2lmKHQ+PTApe2lmKCFMeFt0XSl0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgYXN5bmMgaGFuZGxlOiAiK2UpO0x4W3RdPW51bGx9fX07dmFyIHoxZT1jaSx6ST1ObihlPT57Y2xhc3MgdCBleHRlbmRzIGV7c3RhdGljIGNyZWF0ZVByb3BlcnRpZXMobil7bGV0IGk9dGhpcy5wcm90b3R5cGU7Zm9yKGxldCBvIGluIG4pbyBpbiBpfHxpLl9jcmVhdGVQcm9wZXJ0eUFjY2Vzc29yKG8pfXN0YXRpYyBhdHRyaWJ1dGVOYW1lRm9yUHJvcGVydHkobil7cmV0dXJuIG4udG9Mb3dlckNhc2UoKX1zdGF0aWMgdHlwZUZvclByb3BlcnR5KG4pe31fY3JlYXRlUHJvcGVydHlBY2Nlc3NvcihuLGkpe3RoaXMuX2FkZFByb3BlcnR5VG9BdHRyaWJ1dGVNYXAobiksdGhpcy5oYXNPd25Qcm9wZXJ0eShKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJfX2RhdGFIYXNBY2Nlc3NvciIsdGhpcykpfHwodGhpcy5fX2RhdGFIYXNBY2Nlc3Nvcj1PYmplY3QuYXNzaWduKHt9LHRoaXMuX19kYXRhSGFzQWNjZXNzb3IpKSx0aGlzLl9fZGF0YUhhc0FjY2Vzc29yW25dfHwodGhpcy5fX2RhdGFIYXNBY2Nlc3NvcltuXT0hMCx0aGlzLl9kZWZpbmVQcm9wZXJ0eUFjY2Vzc29yKG4saSkpfV9hZGRQcm9wZXJ0eVRvQXR0cmlidXRlTWFwKG4pe3RoaXMuaGFzT3duUHJvcGVydHkoSlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiX19kYXRhQXR0cmlidXRlcyIsdGhpcykpfHwodGhpcy5fX2RhdGFBdHRyaWJ1dGVzPU9iamVjdC5hc3NpZ24oe30sdGhpcy5fX2RhdGFBdHRyaWJ1dGVzKSk7bGV0IGk9dGhpcy5fX2RhdGFBdHRyaWJ1dGVzW25dO3JldHVybiBpfHwoaT10aGlzLmNvbnN0cnVjdG9yLmF0dHJpYnV0ZU5hbWVGb3JQcm9wZXJ0eShuKSx0aGlzLl9fZGF0YUF0dHJpYnV0ZXNbaV09biksaX1fZGVmaW5lUHJvcGVydHlBY2Nlc3NvcihuLGkpe09iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLG4se2dldCgpe3JldHVybiB0aGlzLl9fZGF0YVtuXX0sc2V0Omk/ZnVuY3Rpb24oKXt9OmZ1bmN0aW9uKG8pe3RoaXMuX3NldFBlbmRpbmdQcm9wZXJ0eShuLG8sITApJiZ0aGlzLl9pbnZhbGlkYXRlUHJvcGVydGllcygpfX0pfWNvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLl9fZGF0YUVuYWJsZWQ9ITEsdGhpcy5fX2RhdGFSZWFkeT0hMSx0aGlzLl9fZGF0YUludmFsaWQ9ITEsdGhpcy5fX2RhdGE9e30sdGhpcy5fX2RhdGFQZW5kaW5nPW51bGwsdGhpcy5fX2RhdGFPbGQ9bnVsbCx0aGlzLl9fZGF0YUluc3RhbmNlUHJvcHM9bnVsbCx0aGlzLl9fZGF0YUNvdW50ZXI9MCx0aGlzLl9fc2VyaWFsaXppbmc9ITEsdGhpcy5faW5pdGlhbGl6ZVByb3BlcnRpZXMoKX1yZWFkeSgpe3RoaXMuX19kYXRhUmVhZHk9ITAsdGhpcy5fZmx1c2hQcm9wZXJ0aWVzKCl9X2luaXRpYWxpemVQcm9wZXJ0aWVzKCl7Zm9yKGxldCBuIGluIHRoaXMuX19kYXRhSGFzQWNjZXNzb3IpdGhpcy5oYXNPd25Qcm9wZXJ0eShuKSYmKHRoaXMuX19kYXRhSW5zdGFuY2VQcm9wcz10aGlzLl9fZGF0YUluc3RhbmNlUHJvcHN8fHt9LHRoaXMuX19kYXRhSW5zdGFuY2VQcm9wc1tuXT10aGlzW25dLGRlbGV0ZSB0aGlzW25dKX1faW5pdGlhbGl6ZUluc3RhbmNlUHJvcGVydGllcyhuKXtPYmplY3QuYXNzaWduKHRoaXMsbil9X3NldFByb3BlcnR5KG4saSl7dGhpcy5fc2V0UGVuZGluZ1Byb3BlcnR5KG4saSkmJnRoaXMuX2ludmFsaWRhdGVQcm9wZXJ0aWVzKCl9X2dldFByb3BlcnR5KG4pe3JldHVybiB0aGlzLl9fZGF0YVtuXX1fc2V0UGVuZGluZ1Byb3BlcnR5KG4saSxvKXtsZXQgYT10aGlzLl9fZGF0YVtuXSxzPXRoaXMuX3Nob3VsZFByb3BlcnR5Q2hhbmdlKG4saSxhKTtyZXR1cm4gcyYmKHRoaXMuX19kYXRhUGVuZGluZ3x8KHRoaXMuX19kYXRhUGVuZGluZz17fSx0aGlzLl9fZGF0YU9sZD17fSksdGhpcy5fX2RhdGFPbGQmJiEobiBpbiB0aGlzLl9fZGF0YU9sZCkmJih0aGlzLl9fZGF0YU9sZFtuXT1hKSx0aGlzLl9fZGF0YVtuXT1pLHRoaXMuX19kYXRhUGVuZGluZ1tuXT1pKSxzfV9pc1Byb3BlcnR5UGVuZGluZyhuKXtyZXR1cm4hISh0aGlzLl9fZGF0YVBlbmRpbmcmJnRoaXMuX19kYXRhUGVuZGluZy5oYXNPd25Qcm9wZXJ0eShuKSl9X2ludmFsaWRhdGVQcm9wZXJ0aWVzKCl7IXRoaXMuX19kYXRhSW52YWxpZCYmdGhpcy5fX2RhdGFSZWFkeSYmKHRoaXMuX19kYXRhSW52YWxpZD0hMCx6MWUucnVuKCgpPT57dGhpcy5fX2RhdGFJbnZhbGlkJiYodGhpcy5fX2RhdGFJbnZhbGlkPSExLHRoaXMuX2ZsdXNoUHJvcGVydGllcygpKX0pKX1fZW5hYmxlUHJvcGVydGllcygpe3RoaXMuX19kYXRhRW5hYmxlZHx8KHRoaXMuX19kYXRhRW5hYmxlZD0hMCx0aGlzLl9fZGF0YUluc3RhbmNlUHJvcHMmJih0aGlzLl9pbml0aWFsaXplSW5zdGFuY2VQcm9wZXJ0aWVzKHRoaXMuX19kYXRhSW5zdGFuY2VQcm9wcyksdGhpcy5fX2RhdGFJbnN0YW5jZVByb3BzPW51bGwpLHRoaXMucmVhZHkoKSl9X2ZsdXNoUHJvcGVydGllcygpe3RoaXMuX19kYXRhQ291bnRlcisrO2xldCBuPXRoaXMuX19kYXRhLGk9dGhpcy5fX2RhdGFQZW5kaW5nLG89dGhpcy5fX2RhdGFPbGQ7dGhpcy5fc2hvdWxkUHJvcGVydGllc0NoYW5nZShuLGksbykmJih0aGlzLl9fZGF0YVBlbmRpbmc9bnVsbCx0aGlzLl9fZGF0YU9sZD1udWxsLHRoaXMuX3Byb3BlcnRpZXNDaGFuZ2VkKG4saSxvKSksdGhpcy5fX2RhdGFDb3VudGVyLS19X3Nob3VsZFByb3BlcnRpZXNDaGFuZ2UobixpLG8pe3JldHVybiBCb29sZWFuKGkpfV9wcm9wZXJ0aWVzQ2hhbmdlZChuLGksbyl7fV9zaG91bGRQcm9wZXJ0eUNoYW5nZShuLGksbyl7cmV0dXJuIG8hPT1pJiYobz09PW98fGk9PT1pKX1hdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2sobixpLG8sYSl7aSE9PW8mJnRoaXMuX2F0dHJpYnV0ZVRvUHJvcGVydHkobixvKSxzdXBlci5hdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2smJnN1cGVyLmF0dHJpYnV0ZUNoYW5nZWRDYWxsYmFjayhuLGksbyxhKX1fYXR0cmlidXRlVG9Qcm9wZXJ0eShuLGksbyl7aWYoIXRoaXMuX19zZXJpYWxpemluZyl7bGV0IGE9dGhpcy5fX2RhdGFBdHRyaWJ1dGVzLHM9YSYmYVtuXXx8bjt0aGlzW3NdPXRoaXMuX2Rlc2VyaWFsaXplVmFsdWUoaSxvfHx0aGlzLmNvbnN0cnVjdG9yLnR5cGVGb3JQcm9wZXJ0eShzKSl9fV9wcm9wZXJ0eVRvQXR0cmlidXRlKG4saSxvKXt0aGlzLl9fc2VyaWFsaXppbmc9ITAsbz1hcmd1bWVudHMubGVuZ3RoPDM/dGhpc1tuXTpvLHRoaXMuX3ZhbHVlVG9Ob2RlQXR0cmlidXRlKHRoaXMsbyxpfHx0aGlzLmNvbnN0cnVjdG9yLmF0dHJpYnV0ZU5hbWVGb3JQcm9wZXJ0eShuKSksdGhpcy5fX3NlcmlhbGl6aW5nPSExfV92YWx1ZVRvTm9kZUF0dHJpYnV0ZShuLGksbyl7bGV0IGE9dGhpcy5fc2VyaWFsaXplVmFsdWUoaSk7KG89PT0iY2xhc3MifHxvPT09Im5hbWUifHxvPT09InNsb3QiKSYmKG49dWUobikpLGE9PT12b2lkIDA/bi5yZW1vdmVBdHRyaWJ1dGUobyk6bi5zZXRBdHRyaWJ1dGUobyxhKX1fc2VyaWFsaXplVmFsdWUobil7c3dpdGNoKHR5cGVvZiBuKXtjYXNlImJvb2xlYW4iOnJldHVybiBuPyIiOnZvaWQgMDtkZWZhdWx0OnJldHVybiBuIT1udWxsP24udG9TdHJpbmcoKTp2b2lkIDB9fV9kZXNlcmlhbGl6ZVZhbHVlKG4saSl7c3dpdGNoKGkpe2Nhc2UgQm9vbGVhbjpyZXR1cm4gbiE9PW51bGw7Y2FzZSBOdW1iZXI6cmV0dXJuIE51bWJlcihuKTtkZWZhdWx0OnJldHVybiBufX19cmV0dXJuIHR9KTt2YXIgaW10PXt9LEZJPUhUTUxFbGVtZW50LnByb3RvdHlwZTtmb3IoO0ZJOyl7bGV0IGU9T2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMoRkkpO2ZvcihsZXQgdD0wO3Q8ZS5sZW5ndGg7dCsrKWltdFtlW3RdXT0hMDtGST1PYmplY3QuZ2V0UHJvdG90eXBlT2YoRkkpfWZ1bmN0aW9uIEYxZShlLHQpe2lmKCFpbXRbdF0pe2xldCByPWVbdF07ciE9PXZvaWQgMCYmKGUuX19kYXRhP2UuX3NldFBlbmRpbmdQcm9wZXJ0eSh0LHIpOihlLl9fZGF0YVByb3RvP2UuaGFzT3duUHJvcGVydHkoSlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiX19kYXRhUHJvdG8iLGUpKXx8KGUuX19kYXRhUHJvdG89T2JqZWN0LmNyZWF0ZShlLl9fZGF0YVByb3RvKSk6ZS5fX2RhdGFQcm90bz17fSxlLl9fZGF0YVByb3RvW3RdPXIpKX19dmFyIEJJPU5uKGU9PntsZXQgdD16SShlKTtjbGFzcyByIGV4dGVuZHMgdHtzdGF0aWMgY3JlYXRlUHJvcGVydGllc0ZvckF0dHJpYnV0ZXMoKXtsZXQgaT10aGlzLm9ic2VydmVkQXR0cmlidXRlcztmb3IobGV0IG89MDtvPGkubGVuZ3RoO28rKyl0aGlzLnByb3RvdHlwZS5fY3JlYXRlUHJvcGVydHlBY2Nlc3Nvcih3bShpW29dKSl9c3RhdGljIGF0dHJpYnV0ZU5hbWVGb3JQcm9wZXJ0eShpKXtyZXR1cm4gSXgoaSl9X2luaXRpYWxpemVQcm9wZXJ0aWVzKCl7dGhpcy5fX2RhdGFQcm90byYmKHRoaXMuX2luaXRpYWxpemVQcm90b1Byb3BlcnRpZXModGhpcy5fX2RhdGFQcm90byksdGhpcy5fX2RhdGFQcm90bz1udWxsKSxzdXBlci5faW5pdGlhbGl6ZVByb3BlcnRpZXMoKX1faW5pdGlhbGl6ZVByb3RvUHJvcGVydGllcyhpKXtmb3IobGV0IG8gaW4gaSl0aGlzLl9zZXRQcm9wZXJ0eShvLGlbb10pfV9lbnN1cmVBdHRyaWJ1dGUoaSxvKXtsZXQgYT10aGlzO2EuaGFzQXR0cmlidXRlKGkpfHx0aGlzLl92YWx1ZVRvTm9kZUF0dHJpYnV0ZShhLG8saSl9X3NlcmlhbGl6ZVZhbHVlKGkpe3N3aXRjaCh0eXBlb2YgaSl7Y2FzZSJvYmplY3QiOmlmKGkgaW5zdGFuY2VvZiBEYXRlKXJldHVybiBpLnRvU3RyaW5nKCk7aWYoaSl0cnl7cmV0dXJuIEpTT04uc3RyaW5naWZ5KGkpfWNhdGNoKG8pe3JldHVybiIifWRlZmF1bHQ6cmV0dXJuIHN1cGVyLl9zZXJpYWxpemVWYWx1ZShpKX19X2Rlc2VyaWFsaXplVmFsdWUoaSxvKXtsZXQgYTtzd2l0Y2gobyl7Y2FzZSBPYmplY3Q6dHJ5e2E9SlNPTi5wYXJzZShpKX1jYXRjaChzKXthPWl9YnJlYWs7Y2FzZSBBcnJheTp0cnl7YT1KU09OLnBhcnNlKGkpfWNhdGNoKHMpe2E9bnVsbCxjb25zb2xlLndhcm4oYFBvbHltZXI6OkF0dHJpYnV0ZXM6IGNvdWxkbid0IGRlY29kZSBBcnJheSBhcyBKU09OOiAke2l9YCl9YnJlYWs7Y2FzZSBEYXRlOmE9aXNOYU4oaSk/U3RyaW5nKGkpOk51bWJlcihpKSxhPW5ldyBEYXRlKGEpO2JyZWFrO2RlZmF1bHQ6YT1zdXBlci5fZGVzZXJpYWxpemVWYWx1ZShpLG8pO2JyZWFrfXJldHVybiBhfV9kZWZpbmVQcm9wZXJ0eUFjY2Vzc29yKGksbyl7RjFlKHRoaXMsaSksc3VwZXIuX2RlZmluZVByb3BlcnR5QWNjZXNzb3IoaSxvKX1faGFzQWNjZXNzb3IoaSl7cmV0dXJuIHRoaXMuX19kYXRhSGFzQWNjZXNzb3ImJnRoaXMuX19kYXRhSGFzQWNjZXNzb3JbaV19X2lzUHJvcGVydHlQZW5kaW5nKGkpe3JldHVybiBCb29sZWFuKHRoaXMuX19kYXRhUGVuZGluZyYmaSBpbiB0aGlzLl9fZGF0YVBlbmRpbmcpfX1yZXR1cm4gcn0pO3ZhciBCMWU9eyJkb20taWYiOiEwLCJkb20tcmVwZWF0IjohMH0sb210PSExLGFtdD0hMTtmdW5jdGlvbiBIMWUoKXtpZighb210KXtvbXQ9ITA7bGV0IGU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGV4dGFyZWEiKTtlLnBsYWNlaG9sZGVyPSJhIixhbXQ9ZS5wbGFjZWhvbGRlcj09PWUudGV4dENvbnRlbnR9cmV0dXJuIGFtdH1mdW5jdGlvbiBWMWUoZSl7SDFlKCkmJmUubG9jYWxOYW1lPT09InRleHRhcmVhIiYmZS5wbGFjZWhvbGRlciYmZS5wbGFjZWhvbGRlcj09PWUudGV4dENvbnRlbnQmJihlLnRleHRDb250ZW50PW51bGwpfWZ1bmN0aW9uIFUxZShlKXtsZXQgdD1lLmdldEF0dHJpYnV0ZSgiaXMiKTtpZih0JiZCMWVbdF0pe2xldCByPWU7Zm9yKHIucmVtb3ZlQXR0cmlidXRlKCJpcyIpLGU9ci5vd25lckRvY3VtZW50LmNyZWF0ZUVsZW1lbnQodCksci5wYXJlbnROb2RlLnJlcGxhY2VDaGlsZChlLHIpLGUuYXBwZW5kQ2hpbGQocik7ci5hdHRyaWJ1dGVzLmxlbmd0aDspZS5zZXRBdHRyaWJ1dGUoci5hdHRyaWJ1dGVzWzBdLm5hbWUsci5hdHRyaWJ1dGVzWzBdLnZhbHVlKSxyLnJlbW92ZUF0dHJpYnV0ZShyLmF0dHJpYnV0ZXNbMF0ubmFtZSl9cmV0dXJuIGV9ZnVuY3Rpb24gc210KGUsdCl7bGV0IHI9dC5wYXJlbnRJbmZvJiZzbXQoZSx0LnBhcmVudEluZm8pO2lmKHIpe2ZvcihsZXQgbj1yLmZpcnN0Q2hpbGQsaT0wO247bj1uLm5leHRTaWJsaW5nKWlmKHQucGFyZW50SW5kZXg9PT1pKyspcmV0dXJuIG59ZWxzZSByZXR1cm4gZX1mdW5jdGlvbiBxMWUoZSx0LHIsbil7bi5pZCYmKHRbbi5pZF09cil9ZnVuY3Rpb24gRzFlKGUsdCxyKXtpZihyLmV2ZW50cyYmci5ldmVudHMubGVuZ3RoKWZvcihsZXQgbj0wLGk9ci5ldmVudHMsbztuPGkubGVuZ3RoJiYobz1pW25dKTtuKyspZS5fYWRkTWV0aG9kRXZlbnRMaXN0ZW5lclRvTm9kZSh0LG8ubmFtZSxvLnZhbHVlLGUpfWZ1bmN0aW9uIFcxZShlLHQscixuKXtyLnRlbXBsYXRlSW5mbyYmKHQuX3RlbXBsYXRlSW5mbz1yLnRlbXBsYXRlSW5mbyx0Ll9wYXJlbnRUZW1wbGF0ZUluZm89bil9ZnVuY3Rpb24gWTFlKGUsdCxyKXtyZXR1cm4gZT1lLl9tZXRob2RIb3N0fHxlLGZ1bmN0aW9uKGkpe2Vbcl0/ZVtyXShpLGkuZGV0YWlsKTpjb25zb2xlLndhcm4oImxpc3RlbmVyIG1ldGhvZCBgIityKyJgIG5vdCBkZWZpbmVkIil9fXZhciBsbXQ9Tm4oZT0+e2NsYXNzIHQgZXh0ZW5kcyBle3N0YXRpYyBfcGFyc2VUZW1wbGF0ZShuLGkpe2lmKCFuLl90ZW1wbGF0ZUluZm8pe2xldCBvPW4uX3RlbXBsYXRlSW5mbz17fTtvLm5vZGVJbmZvTGlzdD1bXSxvLm5lc3RlZFRlbXBsYXRlPUJvb2xlYW4oaSksby5zdHJpcFdoaXRlU3BhY2U9aSYmaS5zdHJpcFdoaXRlU3BhY2V8fG4uaGFzQXR0cmlidXRlKCJzdHJpcC13aGl0ZXNwYWNlIiksdGhpcy5fcGFyc2VUZW1wbGF0ZUNvbnRlbnQobixvLHtwYXJlbnQ6bnVsbH0pfXJldHVybiBuLl90ZW1wbGF0ZUluZm99c3RhdGljIF9wYXJzZVRlbXBsYXRlQ29udGVudChuLGksbyl7cmV0dXJuIHRoaXMuX3BhcnNlVGVtcGxhdGVOb2RlKG4uY29udGVudCxpLG8pfXN0YXRpYyBfcGFyc2VUZW1wbGF0ZU5vZGUobixpLG8pe2xldCBhPSExLHM9bjtyZXR1cm4gcy5sb2NhbE5hbWU9PSJ0ZW1wbGF0ZSImJiFzLmhhc0F0dHJpYnV0ZSgicHJlc2VydmUtY29udGVudCIpP2E9dGhpcy5fcGFyc2VUZW1wbGF0ZU5lc3RlZFRlbXBsYXRlKHMsaSxvKXx8YTpzLmxvY2FsTmFtZT09PSJzbG90IiYmKGkuaGFzSW5zZXJ0aW9uUG9pbnQ9ITApLFYxZShzKSxzLmZpcnN0Q2hpbGQmJnRoaXMuX3BhcnNlVGVtcGxhdGVDaGlsZE5vZGVzKHMsaSxvKSxzLmhhc0F0dHJpYnV0ZXMmJnMuaGFzQXR0cmlidXRlcygpJiYoYT10aGlzLl9wYXJzZVRlbXBsYXRlTm9kZUF0dHJpYnV0ZXMocyxpLG8pfHxhKSxhfHxvLm5vdGVkfXN0YXRpYyBfcGFyc2VUZW1wbGF0ZUNoaWxkTm9kZXMobixpLG8pe2lmKCEobi5sb2NhbE5hbWU9PT0ic2NyaXB0Inx8bi5sb2NhbE5hbWU9PT0ic3R5bGUiKSlmb3IobGV0IGE9bi5maXJzdENoaWxkLHM9MCxsO2E7YT1sKXtpZihhLmxvY2FsTmFtZT09InRlbXBsYXRlIiYmKGE9VTFlKGEpKSxsPWEubmV4dFNpYmxpbmcsYS5ub2RlVHlwZT09PU5vZGUuVEVYVF9OT0RFKXtsZXQgdT1sO2Zvcig7dSYmdS5ub2RlVHlwZT09PU5vZGUuVEVYVF9OT0RFOylhLnRleHRDb250ZW50Kz11LnRleHRDb250ZW50LGw9dS5uZXh0U2libGluZyxuLnJlbW92ZUNoaWxkKHUpLHU9bDtpZihpLnN0cmlwV2hpdGVTcGFjZSYmIWEudGV4dENvbnRlbnQudHJpbSgpKXtuLnJlbW92ZUNoaWxkKGEpO2NvbnRpbnVlfX1sZXQgYz17cGFyZW50SW5kZXg6cyxwYXJlbnRJbmZvOm99O3RoaXMuX3BhcnNlVGVtcGxhdGVOb2RlKGEsaSxjKSYmKGMuaW5mb0luZGV4PWkubm9kZUluZm9MaXN0LnB1c2goYyktMSksYS5wYXJlbnROb2RlJiZzKyt9fXN0YXRpYyBfcGFyc2VUZW1wbGF0ZU5lc3RlZFRlbXBsYXRlKG4saSxvKXtsZXQgYT1uLHM9dGhpcy5fcGFyc2VUZW1wbGF0ZShhLGkpO3JldHVybihzLmNvbnRlbnQ9YS5jb250ZW50Lm93bmVyRG9jdW1lbnQuY3JlYXRlRG9jdW1lbnRGcmFnbWVudCgpKS5hcHBlbmRDaGlsZChhLmNvbnRlbnQpLG8udGVtcGxhdGVJbmZvPXMsITB9c3RhdGljIF9wYXJzZVRlbXBsYXRlTm9kZUF0dHJpYnV0ZXMobixpLG8pe2xldCBhPSExLHM9QXJyYXkuZnJvbShuLmF0dHJpYnV0ZXMpO2ZvcihsZXQgbD1zLmxlbmd0aC0xLGM7Yz1zW2xdO2wtLSlhPXRoaXMuX3BhcnNlVGVtcGxhdGVOb2RlQXR0cmlidXRlKG4saSxvLGMubmFtZSxjLnZhbHVlKXx8YTtyZXR1cm4gYX1zdGF0aWMgX3BhcnNlVGVtcGxhdGVOb2RlQXR0cmlidXRlKG4saSxvLGEscyl7cmV0dXJuIGEuc2xpY2UoMCwzKT09PSJvbi0iPyhuLnJlbW92ZUF0dHJpYnV0ZShhKSxvLmV2ZW50cz1vLmV2ZW50c3x8W10sby5ldmVudHMucHVzaCh7bmFtZTphLnNsaWNlKDMpLHZhbHVlOnN9KSwhMCk6YT09PSJpZCI/KG8uaWQ9cywhMCk6ITF9c3RhdGljIF9jb250ZW50Rm9yVGVtcGxhdGUobil7bGV0IGk9bi5fdGVtcGxhdGVJbmZvO3JldHVybiBpJiZpLmNvbnRlbnR8fG4uY29udGVudH1fc3RhbXBUZW1wbGF0ZShuLGkpe24mJiFuLmNvbnRlbnQmJndpbmRvdy5IVE1MVGVtcGxhdGVFbGVtZW50JiZIVE1MVGVtcGxhdGVFbGVtZW50LmRlY29yYXRlJiZIVE1MVGVtcGxhdGVFbGVtZW50LmRlY29yYXRlKG4pLGk9aXx8dGhpcy5jb25zdHJ1Y3Rvci5fcGFyc2VUZW1wbGF0ZShuKTtsZXQgbz1pLm5vZGVJbmZvTGlzdCxhPWkuY29udGVudHx8bi5jb250ZW50LHM9ZG9jdW1lbnQuaW1wb3J0Tm9kZShhLCEwKTtzLl9fbm9JbnNlcnRpb25Qb2ludD0haS5oYXNJbnNlcnRpb25Qb2ludDtsZXQgbD1zLm5vZGVMaXN0PW5ldyBBcnJheShvLmxlbmd0aCk7cy4kPXt9O2ZvcihsZXQgYz0wLHU9by5sZW5ndGgsaDtjPHUmJihoPW9bY10pO2MrKyl7bGV0IGY9bFtjXT1zbXQocyxoKTtxMWUodGhpcyxzLiQsZixoKSxXMWUodGhpcyxmLGgsaSksRzFlKHRoaXMsZixoKX1yZXR1cm4gcz1zLHN9X2FkZE1ldGhvZEV2ZW50TGlzdGVuZXJUb05vZGUobixpLG8sYSl7YT1hfHxuO2xldCBzPVkxZShhLGksbyk7cmV0dXJuIHRoaXMuX2FkZEV2ZW50TGlzdGVuZXJUb05vZGUobixpLHMpLHN9X2FkZEV2ZW50TGlzdGVuZXJUb05vZGUobixpLG8pe24uYWRkRXZlbnRMaXN0ZW5lcihpLG8pfV9yZW1vdmVFdmVudExpc3RlbmVyRnJvbU5vZGUobixpLG8pe24ucmVtb3ZlRXZlbnRMaXN0ZW5lcihpLG8pfX1yZXR1cm4gdH0pO3ZhciBqTT0wLFhNPVtdLHpyPXtDT01QVVRFOiJfX2NvbXB1dGVFZmZlY3RzIixSRUZMRUNUOiJfX3JlZmxlY3RFZmZlY3RzIixOT1RJRlk6Il9fbm90aWZ5RWZmZWN0cyIsUFJPUEFHQVRFOiJfX3Byb3BhZ2F0ZUVmZmVjdHMiLE9CU0VSVkU6Il9fb2JzZXJ2ZUVmZmVjdHMiLFJFQURfT05MWToiX19yZWFkT25seSJ9LGdtdD0iX19jb21wdXRlSW5mbyIsajFlPS9bQS1aXS87ZnVuY3Rpb24gdkcoZSx0LHIpe2xldCBuPWVbdF07aWYoIW4pbj1lW3RdPXt9O2Vsc2UgaWYoIWUuaGFzT3duUHJvcGVydHkodCkmJihuPWVbdF09T2JqZWN0LmNyZWF0ZShlW3RdKSxyKSlmb3IobGV0IGkgaW4gbil7bGV0IG89bltpXSxhPW5baV09QXJyYXkoby5sZW5ndGgpO2ZvcihsZXQgcz0wO3M8by5sZW5ndGg7cysrKWFbc109b1tzXX1yZXR1cm4gbn1mdW5jdGlvbiBZTShlLHQscixuLGksbyl7aWYodCl7bGV0IGE9ITEscz1qTSsrO2ZvcihsZXQgbCBpbiByKXtsZXQgYz1pP2F1KGwpOmwsdT10W2NdO2lmKHUpZm9yKGxldCBoPTAsZj11Lmxlbmd0aCxwO2g8ZiYmKHA9dVtoXSk7aCsrKSghcC5pbmZvfHxwLmluZm8ubGFzdFJ1biE9PXMpJiYoIWl8fFNHKGwscC50cmlnZ2VyKSkmJihwLmluZm8mJihwLmluZm8ubGFzdFJ1bj1zKSxwLmZuKGUsbCxyLG4scC5pbmZvLGksbyksYT0hMCl9cmV0dXJuIGF9cmV0dXJuITF9ZnVuY3Rpb24gWDFlKGUsdCxyLG4saSxvLGEscyl7bGV0IGw9ITEsYz1hP2F1KG4pOm4sdT10W2NdO2lmKHUpZm9yKGxldCBoPTAsZj11Lmxlbmd0aCxwO2g8ZiYmKHA9dVtoXSk7aCsrKSghcC5pbmZvfHxwLmluZm8ubGFzdFJ1biE9PXIpJiYoIWF8fFNHKG4scC50cmlnZ2VyKSkmJihwLmluZm8mJihwLmluZm8ubGFzdFJ1bj1yKSxwLmZuKGUsbixpLG8scC5pbmZvLGEscyksbD0hMCk7cmV0dXJuIGx9ZnVuY3Rpb24gU0coZSx0KXtpZih0KXtsZXQgcj10Lm5hbWU7cmV0dXJuIHI9PWV8fCEhKHQuc3RydWN0dXJlZCYmZ0cocixlKSl8fCEhKHQud2lsZGNhcmQmJkF4KHIsZSkpfWVsc2UgcmV0dXJuITB9ZnVuY3Rpb24gY210KGUsdCxyLG4saSl7bGV0IG89dHlwZW9mIGkubWV0aG9kPT0ic3RyaW5nIj9lW2kubWV0aG9kXTppLm1ldGhvZCxhPWkucHJvcGVydHk7bz9vLmNhbGwoZSxlLl9fZGF0YVthXSxuW2FdKTppLmR5bmFtaWNGbnx8Y29uc29sZS53YXJuKCJvYnNlcnZlciBtZXRob2QgYCIraS5tZXRob2QrImAgbm90IGRlZmluZWQiKX1mdW5jdGlvbiAkMWUoZSx0LHIsbixpKXtsZXQgbz1lW3pyLk5PVElGWV0sYSxzPWpNKys7Zm9yKGxldCBjIGluIHQpdFtjXSYmKG8mJlgxZShlLG8scyxjLHIsbixpKXx8aSYmSzFlKGUsYyxyKSkmJihhPSEwKTtsZXQgbDthJiYobD1lLl9fZGF0YUhvc3QpJiZsLl9pbnZhbGlkYXRlUHJvcGVydGllcyYmbC5faW52YWxpZGF0ZVByb3BlcnRpZXMoKX1mdW5jdGlvbiBLMWUoZSx0LHIpe2xldCBuPWF1KHQpO2lmKG4hPT10KXtsZXQgaT1JeChuKSsiLWNoYW5nZWQiO3JldHVybiBfbXQoZSxpLHJbdF0sdCksITB9cmV0dXJuITF9ZnVuY3Rpb24gX210KGUsdCxyLG4pe2xldCBpPXt2YWx1ZTpyLHF1ZXVlUHJvcGVydHk6ITB9O24mJihpLnBhdGg9biksdWUoZSkuZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQodCx7ZGV0YWlsOml9KSl9ZnVuY3Rpb24gWjFlKGUsdCxyLG4saSxvKXtsZXQgcz0obz9hdSh0KTp0KSE9dD90Om51bGwsbD1zP05vKGUscyk6ZS5fX2RhdGFbdF07cyYmbD09PXZvaWQgMCYmKGw9clt0XSksX210KGUsaS5ldmVudE5hbWUsbCxzKX1mdW5jdGlvbiBKMWUoZSx0LHIsbixpKXtsZXQgbyxhPWUuZGV0YWlsLHM9YSYmYS5wYXRoO3M/KG49ZHAocixuLHMpLG89YSYmYS52YWx1ZSk6bz1lLmN1cnJlbnRUYXJnZXRbcl0sbz1pPyFvOm8sKCF0W3pyLlJFQURfT05MWV18fCF0W3pyLlJFQURfT05MWV1bbl0pJiZ0Ll9zZXRQZW5kaW5nUHJvcGVydHlPclBhdGgobixvLCEwLEJvb2xlYW4ocykpJiYoIWF8fCFhLnF1ZXVlUHJvcGVydHkpJiZ0Ll9pbnZhbGlkYXRlUHJvcGVydGllcygpfWZ1bmN0aW9uIFExZShlLHQscixuLGkpe2xldCBvPWUuX19kYXRhW3RdO1ZNJiYobz1WTShvLGkuYXR0ck5hbWUsImF0dHJpYnV0ZSIsZSkpLGUuX3Byb3BlcnR5VG9BdHRyaWJ1dGUodCxpLmF0dHJOYW1lLG8pfWZ1bmN0aW9uIHR2ZShlLHQscixuKXtsZXQgaT1lW3pyLkNPTVBVVEVdO2lmKGkpaWYoV2R0KXtqTSsrO2xldCBvPXJ2ZShlKSxhPVtdO2ZvcihsZXQgbCBpbiB0KXVtdChsLGksYSxvLG4pO2xldCBzO2Zvcig7cz1hLnNoaWZ0KCk7KXltdChlLCIiLHQscixzKSYmdW10KHMubWV0aG9kSW5mbyxpLGEsbyxuKTtPYmplY3QuYXNzaWduKHIsZS5fX2RhdGFPbGQpLE9iamVjdC5hc3NpZ24odCxlLl9fZGF0YVBlbmRpbmcpLGUuX19kYXRhUGVuZGluZz1udWxsfWVsc2V7bGV0IG89dDtmb3IoO1lNKGUsaSxvLHIsbik7KU9iamVjdC5hc3NpZ24ocixlLl9fZGF0YU9sZCksT2JqZWN0LmFzc2lnbih0LGUuX19kYXRhUGVuZGluZyksbz1lLl9fZGF0YVBlbmRpbmcsZS5fX2RhdGFQZW5kaW5nPW51bGx9fXZhciBldmU9KGUsdCxyKT0+e2xldCBuPTAsaT10Lmxlbmd0aC0xLG89LTE7Zm9yKDtuPD1pOyl7bGV0IGE9bitpPj4xLHM9ci5nZXQodFthXS5tZXRob2RJbmZvKS1yLmdldChlLm1ldGhvZEluZm8pO2lmKHM8MCluPWErMTtlbHNlIGlmKHM+MClpPWEtMTtlbHNle289YTticmVha319bzwwJiYobz1pKzEpLHQuc3BsaWNlKG8sMCxlKX0sdW10PShlLHQscixuLGkpPT57bGV0IG89aT9hdShlKTplLGE9dFtvXTtpZihhKWZvcihsZXQgcz0wO3M8YS5sZW5ndGg7cysrKXtsZXQgbD1hW3NdO2wuaW5mby5sYXN0UnVuIT09ak0mJighaXx8U0coZSxsLnRyaWdnZXIpKSYmKGwuaW5mby5sYXN0UnVuPWpNLGV2ZShsLmluZm8scixuKSl9fTtmdW5jdGlvbiBydmUoZSl7bGV0IHQ9ZS5jb25zdHJ1Y3Rvci5fX29yZGVyZWRDb21wdXRlZERlcHM7aWYoIXQpe3Q9bmV3IE1hcDtsZXQgcj1lW3pyLkNPTVBVVEVdLHtjb3VudHM6bixyZWFkeTppLHRvdGFsOm99PW52ZShlKSxhO2Zvcig7YT1pLnNoaWZ0KCk7KXt0LnNldChhLHQuc2l6ZSk7bGV0IHM9clthXTtzJiZzLmZvckVhY2gobD0+e2xldCBjPWwuaW5mby5tZXRob2RJbmZvOy0tbywtLW5bY109PT0wJiZpLnB1c2goYyl9KX1vIT09MCYmY29uc29sZS53YXJuKGBDb21wdXRlZCBncmFwaCBmb3IgJHtlLmxvY2FsTmFtZX0gaW5jb21wbGV0ZTsgY2lyY3VsYXI/YCksZS5jb25zdHJ1Y3Rvci5fX29yZGVyZWRDb21wdXRlZERlcHM9dH1yZXR1cm4gdH1mdW5jdGlvbiBudmUoZSl7bGV0IHQ9ZVtnbXRdLHI9e30sbj1lW3pyLkNPTVBVVEVdLGk9W10sbz0wO2ZvcihsZXQgYSBpbiB0KXtsZXQgcz10W2FdO28rPXJbYV09cy5hcmdzLmZpbHRlcihsPT4hbC5saXRlcmFsKS5sZW5ndGgrKHMuZHluYW1pY0ZuPzE6MCl9Zm9yKGxldCBhIGluIG4pdFthXXx8aS5wdXNoKGEpO3JldHVybntjb3VudHM6cixyZWFkeTppLHRvdGFsOm99fWZ1bmN0aW9uIHltdChlLHQscixuLGkpe2xldCBvPXdHKGUsdCxyLG4saSk7aWYobz09PVhNKXJldHVybiExO2xldCBhPWkubWV0aG9kSW5mbztyZXR1cm4gZS5fX2RhdGFIYXNBY2Nlc3NvciYmZS5fX2RhdGFIYXNBY2Nlc3NvclthXT9lLl9zZXRQZW5kaW5nUHJvcGVydHkoYSxvLCEwKTooZVthXT1vLCExKX1mdW5jdGlvbiBpdmUoZSx0LHIpe2xldCBuPWUuX19kYXRhTGlua2VkUGF0aHM7aWYobil7bGV0IGk7Zm9yKGxldCBvIGluIG4pe2xldCBhPW5bb107QXgobyx0KT8oaT1kcChvLGEsdCksZS5fc2V0UGVuZGluZ1Byb3BlcnR5T3JQYXRoKGksciwhMCwhMCkpOkF4KGEsdCkmJihpPWRwKGEsbyx0KSxlLl9zZXRQZW5kaW5nUHJvcGVydHlPclBhdGgoaSxyLCEwLCEwKSl9fX1mdW5jdGlvbiB4RyhlLHQscixuLGksbyxhKXtyLmJpbmRpbmdzPXIuYmluZGluZ3N8fFtdO2xldCBzPXtraW5kOm4sdGFyZ2V0OmkscGFydHM6byxsaXRlcmFsOmEsaXNDb21wb3VuZDpvLmxlbmd0aCE9PTF9O2lmKHIuYmluZGluZ3MucHVzaChzKSxjdmUocykpe2xldHtldmVudDpjLG5lZ2F0ZTp1fT1zLnBhcnRzWzBdO3MubGlzdGVuZXJFdmVudD1jfHxJeChpKSsiLWNoYW5nZWQiLHMubGlzdGVuZXJOZWdhdGU9dX1sZXQgbD10Lm5vZGVJbmZvTGlzdC5sZW5ndGg7Zm9yKGxldCBjPTA7YzxzLnBhcnRzLmxlbmd0aDtjKyspe2xldCB1PXMucGFydHNbY107dS5jb21wb3VuZEluZGV4PWMsb3ZlKGUsdCxzLHUsbCl9fWZ1bmN0aW9uIG92ZShlLHQscixuLGkpe2lmKCFuLmxpdGVyYWwpaWYoci5raW5kPT09ImF0dHJpYnV0ZSImJnIudGFyZ2V0WzBdPT09Ii0iKWNvbnNvbGUud2FybigiQ2Fubm90IHNldCBhdHRyaWJ1dGUgIityLnRhcmdldCsnIGJlY2F1c2UgIi0iIGlzIG5vdCBhIHZhbGlkIGF0dHJpYnV0ZSBzdGFydGluZyBjaGFyYWN0ZXInKTtlbHNle2xldCBvPW4uZGVwZW5kZW5jaWVzLGE9e2luZGV4OmksYmluZGluZzpyLHBhcnQ6bixldmFsdWF0b3I6ZX07Zm9yKGxldCBzPTA7czxvLmxlbmd0aDtzKyspe2xldCBsPW9bc107dHlwZW9mIGw9PSJzdHJpbmciJiYobD14bXQobCksbC53aWxkY2FyZD0hMCksZS5fYWRkVGVtcGxhdGVQcm9wZXJ0eUVmZmVjdCh0LGwucm9vdFByb3BlcnR5LHtmbjphdmUsaW5mbzphLHRyaWdnZXI6bH0pfX19ZnVuY3Rpb24gYXZlKGUsdCxyLG4saSxvLGEpe2xldCBzPWFbaS5pbmRleF0sbD1pLmJpbmRpbmcsYz1pLnBhcnQ7aWYobyYmYy5zb3VyY2UmJnQubGVuZ3RoPmMuc291cmNlLmxlbmd0aCYmbC5raW5kPT0icHJvcGVydHkiJiYhbC5pc0NvbXBvdW5kJiZzLl9faXNQcm9wZXJ0eUVmZmVjdHNDbGllbnQmJnMuX19kYXRhSGFzQWNjZXNzb3ImJnMuX19kYXRhSGFzQWNjZXNzb3JbbC50YXJnZXRdKXtsZXQgdT1yW3RdO3Q9ZHAoYy5zb3VyY2UsbC50YXJnZXQsdCkscy5fc2V0UGVuZGluZ1Byb3BlcnR5T3JQYXRoKHQsdSwhMSwhMCkmJmUuX2VucXVldWVDbGllbnQocyl9ZWxzZXtsZXQgdT1pLmV2YWx1YXRvci5fZXZhbHVhdGVCaW5kaW5nKGUsYyx0LHIsbixvKTt1IT09WE0mJnN2ZShlLHMsbCxjLHUpfX1mdW5jdGlvbiBzdmUoZSx0LHIsbixpKXtpZihpPWx2ZSh0LGkscixuKSxWTSYmKGk9Vk0oaSxyLnRhcmdldCxyLmtpbmQsdCkpLHIua2luZD09ImF0dHJpYnV0ZSIpZS5fdmFsdWVUb05vZGVBdHRyaWJ1dGUodCxpLHIudGFyZ2V0KTtlbHNle2xldCBvPXIudGFyZ2V0O3QuX19pc1Byb3BlcnR5RWZmZWN0c0NsaWVudCYmdC5fX2RhdGFIYXNBY2Nlc3NvciYmdC5fX2RhdGFIYXNBY2Nlc3NvcltvXT8oIXRbenIuUkVBRF9PTkxZXXx8IXRbenIuUkVBRF9PTkxZXVtvXSkmJnQuX3NldFBlbmRpbmdQcm9wZXJ0eShvLGkpJiZlLl9lbnF1ZXVlQ2xpZW50KHQpOmUuX3NldFVubWFuYWdlZFByb3BlcnR5VG9Ob2RlKHQsbyxpKX19ZnVuY3Rpb24gbHZlKGUsdCxyLG4pe2lmKHIuaXNDb21wb3VuZCl7bGV0IGk9ZS5fX2RhdGFDb21wb3VuZFN0b3JhZ2Vbci50YXJnZXRdO2lbbi5jb21wb3VuZEluZGV4XT10LHQ9aS5qb2luKCIiKX1yZXR1cm4gci5raW5kIT09ImF0dHJpYnV0ZSImJihyLnRhcmdldD09PSJ0ZXh0Q29udGVudCJ8fHIudGFyZ2V0PT09InZhbHVlIiYmKGUubG9jYWxOYW1lPT09ImlucHV0Inx8ZS5sb2NhbE5hbWU9PT0idGV4dGFyZWEiKSkmJih0PXQ9PW51bGw/IiI6dCksdH1mdW5jdGlvbiBjdmUoZSl7cmV0dXJuIEJvb2xlYW4oZS50YXJnZXQpJiZlLmtpbmQhPSJhdHRyaWJ1dGUiJiZlLmtpbmQhPSJ0ZXh0IiYmIWUuaXNDb21wb3VuZCYmZS5wYXJ0c1swXS5tb2RlPT09InsifWZ1bmN0aW9uIHV2ZShlLHQpe2xldHtub2RlTGlzdDpyLG5vZGVJbmZvTGlzdDpufT10O2lmKG4ubGVuZ3RoKWZvcihsZXQgaT0wO2k8bi5sZW5ndGg7aSsrKXtsZXQgbz1uW2ldLGE9cltpXSxzPW8uYmluZGluZ3M7aWYocylmb3IobGV0IGw9MDtsPHMubGVuZ3RoO2wrKyl7bGV0IGM9c1tsXTtodmUoYSxjKSxmdmUoYSxlLGMpfWEuX19kYXRhSG9zdD1lfX1mdW5jdGlvbiBodmUoZSx0KXtpZih0LmlzQ29tcG91bmQpe2xldCByPWUuX19kYXRhQ29tcG91bmRTdG9yYWdlfHwoZS5fX2RhdGFDb21wb3VuZFN0b3JhZ2U9e30pLG49dC5wYXJ0cyxpPW5ldyBBcnJheShuLmxlbmd0aCk7Zm9yKGxldCBhPTA7YTxuLmxlbmd0aDthKyspaVthXT1uW2FdLmxpdGVyYWw7bGV0IG89dC50YXJnZXQ7cltvXT1pLHQubGl0ZXJhbCYmdC5raW5kPT0icHJvcGVydHkiJiYobz09PSJjbGFzc05hbWUiJiYoZT11ZShlKSksZVtvXT10LmxpdGVyYWwpfX1mdW5jdGlvbiBmdmUoZSx0LHIpe2lmKHIubGlzdGVuZXJFdmVudCl7bGV0IG49ci5wYXJ0c1swXTtlLmFkZEV2ZW50TGlzdGVuZXIoci5saXN0ZW5lckV2ZW50LGZ1bmN0aW9uKGkpe0oxZShpLHQsci50YXJnZXQsbi5zb3VyY2Usbi5uZWdhdGUpfSl9fWZ1bmN0aW9uIGhtdChlLHQscixuLGksbyl7bz10LnN0YXRpY3x8byYmKHR5cGVvZiBvIT0ib2JqZWN0Inx8b1t0Lm1ldGhvZE5hbWVdKTtsZXQgYT17bWV0aG9kTmFtZTp0Lm1ldGhvZE5hbWUsYXJnczp0LmFyZ3MsbWV0aG9kSW5mbzppLGR5bmFtaWNGbjpvfTtmb3IobGV0IHM9MCxsO3M8dC5hcmdzLmxlbmd0aCYmKGw9dC5hcmdzW3NdKTtzKyspbC5saXRlcmFsfHxlLl9hZGRQcm9wZXJ0eUVmZmVjdChsLnJvb3RQcm9wZXJ0eSxyLHtmbjpuLGluZm86YSx0cmlnZ2VyOmx9KTtyZXR1cm4gbyYmZS5fYWRkUHJvcGVydHlFZmZlY3QodC5tZXRob2ROYW1lLHIse2ZuOm4saW5mbzphfSksYX1mdW5jdGlvbiB3RyhlLHQscixuLGkpe2xldCBvPWUuX21ldGhvZEhvc3R8fGUsYT1vW2kubWV0aG9kTmFtZV07aWYoYSl7bGV0IHM9ZS5fbWFyc2hhbEFyZ3MoaS5hcmdzLHQscik7cmV0dXJuIHM9PT1YTT9YTTphLmFwcGx5KG8scyl9ZWxzZSBpLmR5bmFtaWNGbnx8Y29uc29sZS53YXJuKCJtZXRob2QgYCIraS5tZXRob2ROYW1lKyJgIG5vdCBkZWZpbmVkIil9dmFyIHB2ZT1bXSx2bXQ9Iig/OlthLXpBLVpfJF1bXFx3LjokXFwtKl0qKSIsZHZlPSIoPzpbLStdP1swLTldKlxcLj9bMC05XSsoPzpbZUVdWy0rXT9bMC05XSspPykiLG12ZT0iKD86Jyg/OlteJ1xcXFxdfFxcXFwuKSonKSIsZ3ZlPScoPzoiKD86W14iXFxcXF18XFxcXC4pKiIpJyxfdmU9Iig/OiIrbXZlKyJ8IitndmUrIikiLGZtdD0iKD86KCIrdm10KyJ8IitkdmUrInwiK192ZSsiKVxccyopIix5dmU9Iig/OiIrZm10KyIoPzosXFxzKiIrZm10KyIpKikiLHZ2ZT0iKD86XFwoXFxzKig/OiIreXZlKyI/KVxcKVxccyopIix4dmU9IigiK3ZtdCsiXFxzKiIrdnZlKyI/KSIsYnZlPSIoXFxbXFxbfHt7KVxccyoiLHd2ZT0iKD86XV18fX0pIixTdmU9Iig/OighKVxccyopPyIsTXZlPWJ2ZStTdmUreHZlK3d2ZSxwbXQ9bmV3IFJlZ0V4cChNdmUsImciKTtmdW5jdGlvbiBkbXQoZSl7bGV0IHQ9IiI7Zm9yKGxldCByPTA7cjxlLmxlbmd0aDtyKyspdCs9ZVtyXS5saXRlcmFsfHwiIjtyZXR1cm4gdH1mdW5jdGlvbiBiRyhlKXtsZXQgdD1lLm1hdGNoKC8oW15cc10rPylcKChbXHNcU10qKVwpLyk7aWYodCl7bGV0IG49e21ldGhvZE5hbWU6dFsxXSxzdGF0aWM6ITAsYXJnczpwdmV9O2lmKHRbMl0udHJpbSgpKXtsZXQgaT10WzJdLnJlcGxhY2UoL1xcLC9nLCImY29tbWE7Iikuc3BsaXQoIiwiKTtyZXR1cm4gRXZlKGksbil9ZWxzZSByZXR1cm4gbn1yZXR1cm4gbnVsbH1mdW5jdGlvbiBFdmUoZSx0KXtyZXR1cm4gdC5hcmdzPWUubWFwKGZ1bmN0aW9uKHIpe2xldCBuPXhtdChyKTtyZXR1cm4gbi5saXRlcmFsfHwodC5zdGF0aWM9ITEpLG59LHRoaXMpLHR9ZnVuY3Rpb24geG10KGUpe2xldCB0PWUudHJpbSgpLnJlcGxhY2UoLyZjb21tYTsvZywiLCIpLnJlcGxhY2UoL1xcKC4pL2csIiQxIikscj17bmFtZTp0LHZhbHVlOiIiLGxpdGVyYWw6ITF9LG49dFswXTtzd2l0Y2gobj09PSItIiYmKG49dFsxXSksbj49IjAiJiZuPD0iOSImJihuPSIjIiksbil7Y2FzZSInIjpjYXNlJyInOnIudmFsdWU9dC5zbGljZSgxLC0xKSxyLmxpdGVyYWw9ITA7YnJlYWs7Y2FzZSIjIjpyLnZhbHVlPU51bWJlcih0KSxyLmxpdGVyYWw9ITA7YnJlYWt9cmV0dXJuIHIubGl0ZXJhbHx8KHIucm9vdFByb3BlcnR5PWF1KHQpLHIuc3RydWN0dXJlZD1OSSh0KSxyLnN0cnVjdHVyZWQmJihyLndpbGRjYXJkPXQuc2xpY2UoLTIpPT0iLioiLHIud2lsZGNhcmQmJihyLm5hbWU9dC5zbGljZSgwLC0yKSkpKSxyfWZ1bmN0aW9uIG1tdChlLHQscil7bGV0IG49Tm8oZSxyKTtyZXR1cm4gbj09PXZvaWQgMCYmKG49dFtyXSksbn1mdW5jdGlvbiBibXQoZSx0LHIsbil7bGV0IGk9e2luZGV4U3BsaWNlczpufTtMSSYmIWUuX292ZXJyaWRlTGVnYWN5VW5kZWZpbmVkJiYodC5zcGxpY2VzPWkpLGUubm90aWZ5UGF0aChyKyIuc3BsaWNlcyIsaSksZS5ub3RpZnlQYXRoKHIrIi5sZW5ndGgiLHQubGVuZ3RoKSxMSSYmIWUuX292ZXJyaWRlTGVnYWN5VW5kZWZpbmVkJiYoaS5pbmRleFNwbGljZXM9W10pfWZ1bmN0aW9uIEdNKGUsdCxyLG4saSxvKXtibXQoZSx0LHIsW3tpbmRleDpuLGFkZGVkQ291bnQ6aSxyZW1vdmVkOm8sb2JqZWN0OnQsdHlwZToic3BsaWNlIn1dKX1mdW5jdGlvbiBUdmUoZSl7cmV0dXJuIGVbMF0udG9VcHBlckNhc2UoKStlLnN1YnN0cmluZygxKX12YXIgdV89Tm4oZT0+e2xldCB0PWxtdChCSShlKSk7Y2xhc3MgciBleHRlbmRzIHR7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX19pc1Byb3BlcnR5RWZmZWN0c0NsaWVudD0hMCx0aGlzLl9fZGF0YUNsaWVudHNSZWFkeSx0aGlzLl9fZGF0YVBlbmRpbmdDbGllbnRzLHRoaXMuX19kYXRhVG9Ob3RpZnksdGhpcy5fX2RhdGFMaW5rZWRQYXRocyx0aGlzLl9fZGF0YUhhc1BhdGhzLHRoaXMuX19kYXRhQ29tcG91bmRTdG9yYWdlLHRoaXMuX19kYXRhSG9zdCx0aGlzLl9fZGF0YVRlbXAsdGhpcy5fX2RhdGFDbGllbnRzSW5pdGlhbGl6ZWQsdGhpcy5fX2RhdGEsdGhpcy5fX2RhdGFQZW5kaW5nLHRoaXMuX19kYXRhT2xkLHRoaXMuX19jb21wdXRlRWZmZWN0cyx0aGlzLl9fY29tcHV0ZUluZm8sdGhpcy5fX3JlZmxlY3RFZmZlY3RzLHRoaXMuX19ub3RpZnlFZmZlY3RzLHRoaXMuX19wcm9wYWdhdGVFZmZlY3RzLHRoaXMuX19vYnNlcnZlRWZmZWN0cyx0aGlzLl9fcmVhZE9ubHksdGhpcy5fX3RlbXBsYXRlSW5mbyx0aGlzLl9vdmVycmlkZUxlZ2FjeVVuZGVmaW5lZH1nZXQgUFJPUEVSVFlfRUZGRUNUX1RZUEVTKCl7cmV0dXJuIHpyfV9pbml0aWFsaXplUHJvcGVydGllcygpe3N1cGVyLl9pbml0aWFsaXplUHJvcGVydGllcygpLHRoaXMuX3JlZ2lzdGVySG9zdCgpLHRoaXMuX19kYXRhQ2xpZW50c1JlYWR5PSExLHRoaXMuX19kYXRhUGVuZGluZ0NsaWVudHM9bnVsbCx0aGlzLl9fZGF0YVRvTm90aWZ5PW51bGwsdGhpcy5fX2RhdGFMaW5rZWRQYXRocz1udWxsLHRoaXMuX19kYXRhSGFzUGF0aHM9ITEsdGhpcy5fX2RhdGFDb21wb3VuZFN0b3JhZ2U9dGhpcy5fX2RhdGFDb21wb3VuZFN0b3JhZ2V8fG51bGwsdGhpcy5fX2RhdGFIb3N0PXRoaXMuX19kYXRhSG9zdHx8bnVsbCx0aGlzLl9fZGF0YVRlbXA9e30sdGhpcy5fX2RhdGFDbGllbnRzSW5pdGlhbGl6ZWQ9ITF9X3JlZ2lzdGVySG9zdCgpe2lmKFdNLmxlbmd0aCl7bGV0IGk9V01bV00ubGVuZ3RoLTFdO2kuX2VucXVldWVDbGllbnQodGhpcyksdGhpcy5fX2RhdGFIb3N0PWl9fV9pbml0aWFsaXplUHJvdG9Qcm9wZXJ0aWVzKGkpe3RoaXMuX19kYXRhPU9iamVjdC5jcmVhdGUoaSksdGhpcy5fX2RhdGFQZW5kaW5nPU9iamVjdC5jcmVhdGUoaSksdGhpcy5fX2RhdGFPbGQ9e319X2luaXRpYWxpemVJbnN0YW5jZVByb3BlcnRpZXMoaSl7bGV0IG89dGhpc1t6ci5SRUFEX09OTFldO2ZvcihsZXQgYSBpbiBpKSghb3x8IW9bYV0pJiYodGhpcy5fX2RhdGFQZW5kaW5nPXRoaXMuX19kYXRhUGVuZGluZ3x8e30sdGhpcy5fX2RhdGFPbGQ9dGhpcy5fX2RhdGFPbGR8fHt9LHRoaXMuX19kYXRhW2FdPXRoaXMuX19kYXRhUGVuZGluZ1thXT1pW2FdKX1fYWRkUHJvcGVydHlFZmZlY3QoaSxvLGEpe3RoaXMuX2NyZWF0ZVByb3BlcnR5QWNjZXNzb3IoaSxvPT16ci5SRUFEX09OTFkpO2xldCBzPXZHKHRoaXMsbywhMClbaV07c3x8KHM9dGhpc1tvXVtpXT1bXSkscy5wdXNoKGEpfV9yZW1vdmVQcm9wZXJ0eUVmZmVjdChpLG8sYSl7bGV0IHM9dkcodGhpcyxvLCEwKVtpXSxsPXMuaW5kZXhPZihhKTtsPj0wJiZzLnNwbGljZShsLDEpfV9oYXNQcm9wZXJ0eUVmZmVjdChpLG8pe2xldCBhPXRoaXNbb107cmV0dXJuIEJvb2xlYW4oYSYmYVtpXSl9X2hhc1JlYWRPbmx5RWZmZWN0KGkpe3JldHVybiB0aGlzLl9oYXNQcm9wZXJ0eUVmZmVjdChpLHpyLlJFQURfT05MWSl9X2hhc05vdGlmeUVmZmVjdChpKXtyZXR1cm4gdGhpcy5faGFzUHJvcGVydHlFZmZlY3QoaSx6ci5OT1RJRlkpfV9oYXNSZWZsZWN0RWZmZWN0KGkpe3JldHVybiB0aGlzLl9oYXNQcm9wZXJ0eUVmZmVjdChpLHpyLlJFRkxFQ1QpfV9oYXNDb21wdXRlZEVmZmVjdChpKXtyZXR1cm4gdGhpcy5faGFzUHJvcGVydHlFZmZlY3QoaSx6ci5DT01QVVRFKX1fc2V0UGVuZGluZ1Byb3BlcnR5T3JQYXRoKGksbyxhLHMpe2lmKHN8fGF1KEFycmF5LmlzQXJyYXkoaSk/aVswXTppKSE9PWkpe2lmKCFzKXtsZXQgbD1Obyh0aGlzLGkpO2lmKGk9X0codGhpcyxpLG8pLCFpfHwhc3VwZXIuX3Nob3VsZFByb3BlcnR5Q2hhbmdlKGksbyxsKSlyZXR1cm4hMX1pZih0aGlzLl9fZGF0YUhhc1BhdGhzPSEwLHRoaXMuX3NldFBlbmRpbmdQcm9wZXJ0eShpLG8sYSkpcmV0dXJuIGl2ZSh0aGlzLGksbyksITB9ZWxzZXtpZih0aGlzLl9fZGF0YUhhc0FjY2Vzc29yJiZ0aGlzLl9fZGF0YUhhc0FjY2Vzc29yW2ldKXJldHVybiB0aGlzLl9zZXRQZW5kaW5nUHJvcGVydHkoaSxvLGEpO3RoaXNbaV09b31yZXR1cm4hMX1fc2V0VW5tYW5hZ2VkUHJvcGVydHlUb05vZGUoaSxvLGEpeyhhIT09aVtvXXx8dHlwZW9mIGE9PSJvYmplY3QiKSYmKG89PT0iY2xhc3NOYW1lIiYmKGk9dWUoaSkpLGlbb109YSl9X3NldFBlbmRpbmdQcm9wZXJ0eShpLG8sYSl7bGV0IHM9dGhpcy5fX2RhdGFIYXNQYXRocyYmTkkoaSksbD1zP3RoaXMuX19kYXRhVGVtcDp0aGlzLl9fZGF0YTtyZXR1cm4gdGhpcy5fc2hvdWxkUHJvcGVydHlDaGFuZ2UoaSxvLGxbaV0pPyh0aGlzLl9fZGF0YVBlbmRpbmd8fCh0aGlzLl9fZGF0YVBlbmRpbmc9e30sdGhpcy5fX2RhdGFPbGQ9e30pLGkgaW4gdGhpcy5fX2RhdGFPbGR8fCh0aGlzLl9fZGF0YU9sZFtpXT10aGlzLl9fZGF0YVtpXSkscz90aGlzLl9fZGF0YVRlbXBbaV09bzp0aGlzLl9fZGF0YVtpXT1vLHRoaXMuX19kYXRhUGVuZGluZ1tpXT1vLChzfHx0aGlzW3pyLk5PVElGWV0mJnRoaXNbenIuTk9USUZZXVtpXSkmJih0aGlzLl9fZGF0YVRvTm90aWZ5PXRoaXMuX19kYXRhVG9Ob3RpZnl8fHt9LHRoaXMuX19kYXRhVG9Ob3RpZnlbaV09YSksITApOiExfV9zZXRQcm9wZXJ0eShpLG8pe3RoaXMuX3NldFBlbmRpbmdQcm9wZXJ0eShpLG8sITApJiZ0aGlzLl9pbnZhbGlkYXRlUHJvcGVydGllcygpfV9pbnZhbGlkYXRlUHJvcGVydGllcygpe3RoaXMuX19kYXRhUmVhZHkmJnRoaXMuX2ZsdXNoUHJvcGVydGllcygpfV9lbnF1ZXVlQ2xpZW50KGkpe3RoaXMuX19kYXRhUGVuZGluZ0NsaWVudHM9dGhpcy5fX2RhdGFQZW5kaW5nQ2xpZW50c3x8W10saSE9PXRoaXMmJnRoaXMuX19kYXRhUGVuZGluZ0NsaWVudHMucHVzaChpKX1fZmx1c2hDbGllbnRzKCl7dGhpcy5fX2RhdGFDbGllbnRzUmVhZHk/dGhpcy5fX2VuYWJsZU9yRmx1c2hDbGllbnRzKCk6KHRoaXMuX19kYXRhQ2xpZW50c1JlYWR5PSEwLHRoaXMuX3JlYWR5Q2xpZW50cygpLHRoaXMuX19kYXRhUmVhZHk9ITApfV9fZW5hYmxlT3JGbHVzaENsaWVudHMoKXtsZXQgaT10aGlzLl9fZGF0YVBlbmRpbmdDbGllbnRzO2lmKGkpe3RoaXMuX19kYXRhUGVuZGluZ0NsaWVudHM9bnVsbDtmb3IobGV0IG89MDtvPGkubGVuZ3RoO28rKyl7bGV0IGE9aVtvXTthLl9fZGF0YUVuYWJsZWQ/YS5fX2RhdGFQZW5kaW5nJiZhLl9mbHVzaFByb3BlcnRpZXMoKTphLl9lbmFibGVQcm9wZXJ0aWVzKCl9fX1fcmVhZHlDbGllbnRzKCl7dGhpcy5fX2VuYWJsZU9yRmx1c2hDbGllbnRzKCl9c2V0UHJvcGVydGllcyhpLG8pe2ZvcihsZXQgYSBpbiBpKShvfHwhdGhpc1t6ci5SRUFEX09OTFldfHwhdGhpc1t6ci5SRUFEX09OTFldW2FdKSYmdGhpcy5fc2V0UGVuZGluZ1Byb3BlcnR5T3JQYXRoKGEsaVthXSwhMCk7dGhpcy5faW52YWxpZGF0ZVByb3BlcnRpZXMoKX1yZWFkeSgpe3RoaXMuX2ZsdXNoUHJvcGVydGllcygpLHRoaXMuX19kYXRhQ2xpZW50c1JlYWR5fHx0aGlzLl9mbHVzaENsaWVudHMoKSx0aGlzLl9fZGF0YVBlbmRpbmcmJnRoaXMuX2ZsdXNoUHJvcGVydGllcygpfV9wcm9wZXJ0aWVzQ2hhbmdlZChpLG8sYSl7bGV0IHM9dGhpcy5fX2RhdGFIYXNQYXRoczt0aGlzLl9fZGF0YUhhc1BhdGhzPSExO2xldCBsO3R2ZSh0aGlzLG8sYSxzKSxsPXRoaXMuX19kYXRhVG9Ob3RpZnksdGhpcy5fX2RhdGFUb05vdGlmeT1udWxsLHRoaXMuX3Byb3BhZ2F0ZVByb3BlcnR5Q2hhbmdlcyhvLGEscyksdGhpcy5fZmx1c2hDbGllbnRzKCksWU0odGhpcyx0aGlzW3pyLlJFRkxFQ1RdLG8sYSxzKSxZTSh0aGlzLHRoaXNbenIuT0JTRVJWRV0sbyxhLHMpLGwmJiQxZSh0aGlzLGwsbyxhLHMpLHRoaXMuX19kYXRhQ291bnRlcj09MSYmKHRoaXMuX19kYXRhVGVtcD17fSl9X3Byb3BhZ2F0ZVByb3BlcnR5Q2hhbmdlcyhpLG8sYSl7dGhpc1t6ci5QUk9QQUdBVEVdJiZZTSh0aGlzLHRoaXNbenIuUFJPUEFHQVRFXSxpLG8sYSksdGhpcy5fX3RlbXBsYXRlSW5mbyYmdGhpcy5fcnVuRWZmZWN0c0ZvclRlbXBsYXRlKHRoaXMuX190ZW1wbGF0ZUluZm8saSxvLGEpfV9ydW5FZmZlY3RzRm9yVGVtcGxhdGUoaSxvLGEscyl7bGV0IGw9KGMsdSk9PntZTSh0aGlzLGkucHJvcGVydHlFZmZlY3RzLGMsYSx1LGkubm9kZUxpc3QpO2ZvcihsZXQgaD1pLmZpcnN0Q2hpbGQ7aDtoPWgubmV4dFNpYmxpbmcpdGhpcy5fcnVuRWZmZWN0c0ZvclRlbXBsYXRlKGgsYyxhLHUpfTtpLnJ1bkVmZmVjdHM/aS5ydW5FZmZlY3RzKGwsbyxzKTpsKG8scyl9bGlua1BhdGhzKGksbyl7aT1QeChpKSxvPVB4KG8pLHRoaXMuX19kYXRhTGlua2VkUGF0aHM9dGhpcy5fX2RhdGFMaW5rZWRQYXRoc3x8e30sdGhpcy5fX2RhdGFMaW5rZWRQYXRoc1tpXT1vfXVubGlua1BhdGhzKGkpe2k9UHgoaSksdGhpcy5fX2RhdGFMaW5rZWRQYXRocyYmZGVsZXRlIHRoaXMuX19kYXRhTGlua2VkUGF0aHNbaV19bm90aWZ5U3BsaWNlcyhpLG8pe2xldCBhPXtwYXRoOiIifSxzPU5vKHRoaXMsaSxhKTtibXQodGhpcyxzLGEucGF0aCxvKX1nZXQoaSxvKXtyZXR1cm4gTm8ob3x8dGhpcyxpKX1zZXQoaSxvLGEpe2E/X0coYSxpLG8pOighdGhpc1t6ci5SRUFEX09OTFldfHwhdGhpc1t6ci5SRUFEX09OTFldW2ldKSYmdGhpcy5fc2V0UGVuZGluZ1Byb3BlcnR5T3JQYXRoKGksbywhMCkmJnRoaXMuX2ludmFsaWRhdGVQcm9wZXJ0aWVzKCl9cHVzaChpLC4uLm8pe2xldCBhPXtwYXRoOiIifSxzPU5vKHRoaXMsaSxhKSxsPXMubGVuZ3RoLGM9cy5wdXNoKC4uLm8pO3JldHVybiBvLmxlbmd0aCYmR00odGhpcyxzLGEucGF0aCxsLG8ubGVuZ3RoLFtdKSxjfXBvcChpKXtsZXQgbz17cGF0aDoiIn0sYT1Obyh0aGlzLGksbykscz1Cb29sZWFuKGEubGVuZ3RoKSxsPWEucG9wKCk7cmV0dXJuIHMmJkdNKHRoaXMsYSxvLnBhdGgsYS5sZW5ndGgsMCxbbF0pLGx9c3BsaWNlKGksbyxhLC4uLnMpe2xldCBsPXtwYXRoOiIifSxjPU5vKHRoaXMsaSxsKTtvPDA/bz1jLmxlbmd0aC1NYXRoLmZsb29yKC1vKTpvJiYobz1NYXRoLmZsb29yKG8pKTtsZXQgdTtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTI/dT1jLnNwbGljZShvKTp1PWMuc3BsaWNlKG8sYSwuLi5zKSwocy5sZW5ndGh8fHUubGVuZ3RoKSYmR00odGhpcyxjLGwucGF0aCxvLHMubGVuZ3RoLHUpLHV9c2hpZnQoaSl7bGV0IG89e3BhdGg6IiJ9LGE9Tm8odGhpcyxpLG8pLHM9Qm9vbGVhbihhLmxlbmd0aCksbD1hLnNoaWZ0KCk7cmV0dXJuIHMmJkdNKHRoaXMsYSxvLnBhdGgsMCwwLFtsXSksbH11bnNoaWZ0KGksLi4ubyl7bGV0IGE9e3BhdGg6IiJ9LHM9Tm8odGhpcyxpLGEpLGw9cy51bnNoaWZ0KC4uLm8pO3JldHVybiBvLmxlbmd0aCYmR00odGhpcyxzLGEucGF0aCwwLG8ubGVuZ3RoLFtdKSxsfW5vdGlmeVBhdGgoaSxvKXtsZXQgYTtpZihhcmd1bWVudHMubGVuZ3RoPT0xKXtsZXQgcz17cGF0aDoiIn07bz1Obyh0aGlzLGkscyksYT1zLnBhdGh9ZWxzZSBBcnJheS5pc0FycmF5KGkpP2E9UHgoaSk6YT1pO3RoaXMuX3NldFBlbmRpbmdQcm9wZXJ0eU9yUGF0aChhLG8sITAsITApJiZ0aGlzLl9pbnZhbGlkYXRlUHJvcGVydGllcygpfV9jcmVhdGVSZWFkT25seVByb3BlcnR5KGksbyl7dGhpcy5fYWRkUHJvcGVydHlFZmZlY3QoaSx6ci5SRUFEX09OTFkpLG8mJih0aGlzWyJfc2V0IitUdmUoaSldPWZ1bmN0aW9uKGEpe3RoaXMuX3NldFByb3BlcnR5KGksYSl9KX1fY3JlYXRlUHJvcGVydHlPYnNlcnZlcihpLG8sYSl7bGV0IHM9e3Byb3BlcnR5OmksbWV0aG9kOm8sZHluYW1pY0ZuOkJvb2xlYW4oYSl9O3RoaXMuX2FkZFByb3BlcnR5RWZmZWN0KGksenIuT0JTRVJWRSx7Zm46Y210LGluZm86cyx0cmlnZ2VyOntuYW1lOml9fSksYSYmdGhpcy5fYWRkUHJvcGVydHlFZmZlY3Qobyx6ci5PQlNFUlZFLHtmbjpjbXQsaW5mbzpzLHRyaWdnZXI6e25hbWU6b319KX1fY3JlYXRlTWV0aG9kT2JzZXJ2ZXIoaSxvKXtsZXQgYT1iRyhpKTtpZighYSl0aHJvdyBuZXcgRXJyb3IoIk1hbGZvcm1lZCBvYnNlcnZlciBleHByZXNzaW9uICciK2krIiciKTtobXQodGhpcyxhLHpyLk9CU0VSVkUsd0csbnVsbCxvKX1fY3JlYXRlTm90aWZ5aW5nUHJvcGVydHkoaSl7dGhpcy5fYWRkUHJvcGVydHlFZmZlY3QoaSx6ci5OT1RJRlkse2ZuOloxZSxpbmZvOntldmVudE5hbWU6SXgoaSkrIi1jaGFuZ2VkIixwcm9wZXJ0eTppfX0pfV9jcmVhdGVSZWZsZWN0ZWRQcm9wZXJ0eShpKXtsZXQgbz10aGlzLmNvbnN0cnVjdG9yLmF0dHJpYnV0ZU5hbWVGb3JQcm9wZXJ0eShpKTtvWzBdPT09Ii0iP2NvbnNvbGUud2FybigiUHJvcGVydHkgIitpKyIgY2Fubm90IGJlIHJlZmxlY3RlZCB0byBhdHRyaWJ1dGUgIitvKycgYmVjYXVzZSAiLSIgaXMgbm90IGEgdmFsaWQgc3RhcnRpbmcgYXR0cmlidXRlIG5hbWUuIFVzZSBhIGxvd2VyY2FzZSBmaXJzdCBsZXR0ZXIgZm9yIHRoZSBwcm9wZXJ0eSBpbnN0ZWFkLicpOnRoaXMuX2FkZFByb3BlcnR5RWZmZWN0KGksenIuUkVGTEVDVCx7Zm46UTFlLGluZm86e2F0dHJOYW1lOm99fSl9X2NyZWF0ZUNvbXB1dGVkUHJvcGVydHkoaSxvLGEpe2xldCBzPWJHKG8pO2lmKCFzKXRocm93IG5ldyBFcnJvcigiTWFsZm9ybWVkIGNvbXB1dGVkIGV4cHJlc3Npb24gJyIrbysiJyIpO2xldCBsPWhtdCh0aGlzLHMsenIuQ09NUFVURSx5bXQsaSxhKTt2Ryh0aGlzLGdtdClbaV09bH1fbWFyc2hhbEFyZ3MoaSxvLGEpe2xldCBzPXRoaXMuX19kYXRhLGw9W107Zm9yKGxldCBjPTAsdT1pLmxlbmd0aDtjPHU7YysrKXtsZXR7bmFtZTpoLHN0cnVjdHVyZWQ6Zix3aWxkY2FyZDpwLHZhbHVlOmQsbGl0ZXJhbDpnfT1pW2NdO2lmKCFnKWlmKHApe2xldCBfPUF4KGgsbykseT1tbXQocyxhLF8/bzpoKTtkPXtwYXRoOl8/bzpoLHZhbHVlOnksYmFzZTpfP05vKHMsaCk6eX19ZWxzZSBkPWY/bW10KHMsYSxoKTpzW2hdO2lmKExJJiYhdGhpcy5fb3ZlcnJpZGVMZWdhY3lVbmRlZmluZWQmJmQ9PT12b2lkIDAmJmkubGVuZ3RoPjEpcmV0dXJuIFhNO2xbY109ZH1yZXR1cm4gbH1zdGF0aWMgYWRkUHJvcGVydHlFZmZlY3QoaSxvLGEpe3RoaXMucHJvdG90eXBlLl9hZGRQcm9wZXJ0eUVmZmVjdChpLG8sYSl9c3RhdGljIGNyZWF0ZVByb3BlcnR5T2JzZXJ2ZXIoaSxvLGEpe3RoaXMucHJvdG90eXBlLl9jcmVhdGVQcm9wZXJ0eU9ic2VydmVyKGksbyxhKX1zdGF0aWMgY3JlYXRlTWV0aG9kT2JzZXJ2ZXIoaSxvKXt0aGlzLnByb3RvdHlwZS5fY3JlYXRlTWV0aG9kT2JzZXJ2ZXIoaSxvKX1zdGF0aWMgY3JlYXRlTm90aWZ5aW5nUHJvcGVydHkoaSl7dGhpcy5wcm90b3R5cGUuX2NyZWF0ZU5vdGlmeWluZ1Byb3BlcnR5KGkpfXN0YXRpYyBjcmVhdGVSZWFkT25seVByb3BlcnR5KGksbyl7dGhpcy5wcm90b3R5cGUuX2NyZWF0ZVJlYWRPbmx5UHJvcGVydHkoaSxvKX1zdGF0aWMgY3JlYXRlUmVmbGVjdGVkUHJvcGVydHkoaSl7dGhpcy5wcm90b3R5cGUuX2NyZWF0ZVJlZmxlY3RlZFByb3BlcnR5KGkpfXN0YXRpYyBjcmVhdGVDb21wdXRlZFByb3BlcnR5KGksbyxhKXt0aGlzLnByb3RvdHlwZS5fY3JlYXRlQ29tcHV0ZWRQcm9wZXJ0eShpLG8sYSl9c3RhdGljIGJpbmRUZW1wbGF0ZShpKXtyZXR1cm4gdGhpcy5wcm90b3R5cGUuX2JpbmRUZW1wbGF0ZShpKX1fYmluZFRlbXBsYXRlKGksbyl7bGV0IGE9dGhpcy5jb25zdHJ1Y3Rvci5fcGFyc2VUZW1wbGF0ZShpKSxzPXRoaXMuX19wcmVCb3VuZFRlbXBsYXRlSW5mbz09YTtpZighcylmb3IobGV0IGwgaW4gYS5wcm9wZXJ0eUVmZmVjdHMpdGhpcy5fY3JlYXRlUHJvcGVydHlBY2Nlc3NvcihsKTtpZihvKWlmKGE9T2JqZWN0LmNyZWF0ZShhKSxhLndhc1ByZUJvdW5kPXMsIXRoaXMuX190ZW1wbGF0ZUluZm8pdGhpcy5fX3RlbXBsYXRlSW5mbz1hO2Vsc2V7bGV0IGw9aS5fcGFyZW50VGVtcGxhdGVJbmZvfHx0aGlzLl9fdGVtcGxhdGVJbmZvLGM9bC5sYXN0Q2hpbGQ7YS5wYXJlbnQ9bCxsLmxhc3RDaGlsZD1hLGEucHJldmlvdXNTaWJsaW5nPWMsYz9jLm5leHRTaWJsaW5nPWE6bC5maXJzdENoaWxkPWF9ZWxzZSB0aGlzLl9fcHJlQm91bmRUZW1wbGF0ZUluZm89YTtyZXR1cm4gYX1zdGF0aWMgX2FkZFRlbXBsYXRlUHJvcGVydHlFZmZlY3QoaSxvLGEpe2xldCBzPWkuaG9zdFByb3BzPWkuaG9zdFByb3BzfHx7fTtzW29dPSEwO2xldCBsPWkucHJvcGVydHlFZmZlY3RzPWkucHJvcGVydHlFZmZlY3RzfHx7fTsobFtvXT1sW29dfHxbXSkucHVzaChhKX1fc3RhbXBUZW1wbGF0ZShpLG8pe289b3x8dGhpcy5fYmluZFRlbXBsYXRlKGksITApLFdNLnB1c2godGhpcyk7bGV0IGE9c3VwZXIuX3N0YW1wVGVtcGxhdGUoaSxvKTtpZihXTS5wb3AoKSxvLm5vZGVMaXN0PWEubm9kZUxpc3QsIW8ud2FzUHJlQm91bmQpe2xldCBzPW8uY2hpbGROb2Rlcz1bXTtmb3IobGV0IGw9YS5maXJzdENoaWxkO2w7bD1sLm5leHRTaWJsaW5nKXMucHVzaChsKX1yZXR1cm4gYS50ZW1wbGF0ZUluZm89byx1dmUodGhpcyxvKSx0aGlzLl9fZGF0YUNsaWVudHNSZWFkeSYmKHRoaXMuX3J1bkVmZmVjdHNGb3JUZW1wbGF0ZShvLHRoaXMuX19kYXRhLG51bGwsITEpLHRoaXMuX2ZsdXNoQ2xpZW50cygpKSxhfV9yZW1vdmVCb3VuZERvbShpKXtsZXQgbz1pLnRlbXBsYXRlSW5mbyx7cHJldmlvdXNTaWJsaW5nOmEsbmV4dFNpYmxpbmc6cyxwYXJlbnQ6bH09bzthP2EubmV4dFNpYmxpbmc9czpsJiYobC5maXJzdENoaWxkPXMpLHM/cy5wcmV2aW91c1NpYmxpbmc9YTpsJiYobC5sYXN0Q2hpbGQ9YSksby5uZXh0U2libGluZz1vLnByZXZpb3VzU2libGluZz1udWxsO2xldCBjPW8uY2hpbGROb2Rlcztmb3IobGV0IHU9MDt1PGMubGVuZ3RoO3UrKyl7bGV0IGg9Y1t1XTt1ZSh1ZShoKS5wYXJlbnROb2RlKS5yZW1vdmVDaGlsZChoKX19c3RhdGljIF9wYXJzZVRlbXBsYXRlTm9kZShpLG8sYSl7bGV0IHM9dC5fcGFyc2VUZW1wbGF0ZU5vZGUuY2FsbCh0aGlzLGksbyxhKTtpZihpLm5vZGVUeXBlPT09Tm9kZS5URVhUX05PREUpe2xldCBsPXRoaXMuX3BhcnNlQmluZGluZ3MoaS50ZXh0Q29udGVudCxvKTtsJiYoaS50ZXh0Q29udGVudD1kbXQobCl8fCIgIix4Ryh0aGlzLG8sYSwidGV4dCIsInRleHRDb250ZW50IixsKSxzPSEwKX1yZXR1cm4gc31zdGF0aWMgX3BhcnNlVGVtcGxhdGVOb2RlQXR0cmlidXRlKGksbyxhLHMsbCl7bGV0IGM9dGhpcy5fcGFyc2VCaW5kaW5ncyhsLG8pO2lmKGMpe2xldCB1PXMsaD0icHJvcGVydHkiO2oxZS50ZXN0KHMpP2g9ImF0dHJpYnV0ZSI6c1tzLmxlbmd0aC0xXT09IiQiJiYocz1zLnNsaWNlKDAsLTEpLGg9ImF0dHJpYnV0ZSIpO2xldCBmPWRtdChjKTtyZXR1cm4gZiYmaD09ImF0dHJpYnV0ZSImJihzPT0iY2xhc3MiJiZpLmhhc0F0dHJpYnV0ZSgiY2xhc3MiKSYmKGYrPSIgIitpLmdldEF0dHJpYnV0ZShzKSksaS5zZXRBdHRyaWJ1dGUocyxmKSksaD09ImF0dHJpYnV0ZSImJnU9PSJkaXNhYmxlLXVwZ3JhZGUkIiYmaS5zZXRBdHRyaWJ1dGUocywiIiksaS5sb2NhbE5hbWU9PT0iaW5wdXQiJiZ1PT09InZhbHVlIiYmaS5zZXRBdHRyaWJ1dGUodSwiIiksaS5yZW1vdmVBdHRyaWJ1dGUodSksaD09PSJwcm9wZXJ0eSImJihzPXdtKHMpKSx4Ryh0aGlzLG8sYSxoLHMsYyxmKSwhMH1lbHNlIHJldHVybiB0Ll9wYXJzZVRlbXBsYXRlTm9kZUF0dHJpYnV0ZS5jYWxsKHRoaXMsaSxvLGEscyxsKX1zdGF0aWMgX3BhcnNlVGVtcGxhdGVOZXN0ZWRUZW1wbGF0ZShpLG8sYSl7bGV0IHM9dC5fcGFyc2VUZW1wbGF0ZU5lc3RlZFRlbXBsYXRlLmNhbGwodGhpcyxpLG8sYSksbD1pLnBhcmVudE5vZGUsYz1hLnRlbXBsYXRlSW5mbyx1PWwubG9jYWxOYW1lPT09ImRvbS1pZiIsaD1sLmxvY2FsTmFtZT09PSJkb20tcmVwZWF0IjtmRyYmKHV8fGgpJiYobC5yZW1vdmVDaGlsZChpKSxhPWEucGFyZW50SW5mbyxhLnRlbXBsYXRlSW5mbz1jLGEubm90ZWQ9ITAscz0hMSk7bGV0IGY9Yy5ob3N0UHJvcHM7aWYoa0kmJnUpZiYmKG8uaG9zdFByb3BzPU9iamVjdC5hc3NpZ24oby5ob3N0UHJvcHN8fHt9LGYpLGZHfHwoYS5wYXJlbnRJbmZvLm5vdGVkPSEwKSk7ZWxzZXtsZXQgcD0ieyI7Zm9yKGxldCBkIGluIGYpe2xldCBnPVt7bW9kZTpwLHNvdXJjZTpkLGRlcGVuZGVuY2llczpbZF0saG9zdFByb3A6ITB9XTt4Ryh0aGlzLG8sYSwicHJvcGVydHkiLCJfaG9zdF8iK2QsZyl9fXJldHVybiBzfXN0YXRpYyBfcGFyc2VCaW5kaW5ncyhpLG8pe2xldCBhPVtdLHM9MCxsO2Zvcig7KGw9cG10LmV4ZWMoaSkpIT09bnVsbDspe2wuaW5kZXg+cyYmYS5wdXNoKHtsaXRlcmFsOmkuc2xpY2UocyxsLmluZGV4KX0pO2xldCBjPWxbMV1bMF0sdT1Cb29sZWFuKGxbMl0pLGg9bFszXS50cmltKCksZj0hMSxwPSIiLGQ9LTE7Yz09InsiJiYoZD1oLmluZGV4T2YoIjo6IikpPjAmJihwPWguc3Vic3RyaW5nKGQrMiksaD1oLnN1YnN0cmluZygwLGQpLGY9ITApO2xldCBnPWJHKGgpLF89W107aWYoZyl7bGV0e2FyZ3M6eSxtZXRob2ROYW1lOnh9PWc7Zm9yKGxldCBTPTA7Uzx5Lmxlbmd0aDtTKyspe2xldCBDPXlbU107Qy5saXRlcmFsfHxfLnB1c2goQyl9bGV0IGI9by5keW5hbWljRm5zOyhiJiZiW3hdfHxnLnN0YXRpYykmJihfLnB1c2goeCksZy5keW5hbWljRm49ITApfWVsc2UgXy5wdXNoKGgpO2EucHVzaCh7c291cmNlOmgsbW9kZTpjLG5lZ2F0ZTp1LGN1c3RvbUV2ZW50OmYsc2lnbmF0dXJlOmcsZGVwZW5kZW5jaWVzOl8sZXZlbnQ6cH0pLHM9cG10Lmxhc3RJbmRleH1pZihzJiZzPGkubGVuZ3RoKXtsZXQgYz1pLnN1YnN0cmluZyhzKTtjJiZhLnB1c2goe2xpdGVyYWw6Y30pfXJldHVybiBhLmxlbmd0aD9hOm51bGx9c3RhdGljIF9ldmFsdWF0ZUJpbmRpbmcoaSxvLGEscyxsLGMpe2xldCB1O3JldHVybiBvLnNpZ25hdHVyZT91PXdHKGksYSxzLGwsby5zaWduYXR1cmUpOmEhPW8uc291cmNlP3U9Tm8oaSxvLnNvdXJjZSk6YyYmTkkoYSk/dT1ObyhpLGEpOnU9aS5fX2RhdGFbYV0sby5uZWdhdGUmJih1PSF1KSx1fX1yZXR1cm4gcn0pLFdNPVtdO3ZhciBDdmU9MDtmdW5jdGlvbiB3bXQoKXtDdmUrK312YXIgQXZlPVtdO2Z1bmN0aW9uIEhJKGUpe0F2ZS5wdXNoKGUpfWZ1bmN0aW9uIFB2ZShlKXtsZXQgdD17fTtmb3IobGV0IHIgaW4gZSl7bGV0IG49ZVtyXTt0W3JdPXR5cGVvZiBuPT0iZnVuY3Rpb24iP3t0eXBlOm59Om59cmV0dXJuIHR9dmFyIFNtdD1ObihlPT57bGV0IHQ9ekkoZSk7ZnVuY3Rpb24gcihvKXtsZXQgYT1PYmplY3QuZ2V0UHJvdG90eXBlT2Yobyk7cmV0dXJuIGEucHJvdG90eXBlIGluc3RhbmNlb2YgaT9hOm51bGx9ZnVuY3Rpb24gbihvKXtpZighby5oYXNPd25Qcm9wZXJ0eShKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJfX293blByb3BlcnRpZXMiLG8pKSl7bGV0IGE9bnVsbDtpZihvLmhhc093blByb3BlcnR5KEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoInByb3BlcnRpZXMiLG8pKSl7bGV0IHM9by5wcm9wZXJ0aWVzO3MmJihhPVB2ZShzKSl9by5fX293blByb3BlcnRpZXM9YX1yZXR1cm4gby5fX293blByb3BlcnRpZXN9Y2xhc3MgaSBleHRlbmRzIHR7c3RhdGljIGdldCBvYnNlcnZlZEF0dHJpYnV0ZXMoKXtpZighdGhpcy5oYXNPd25Qcm9wZXJ0eShKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJfX29ic2VydmVkQXR0cmlidXRlcyIsdGhpcykpKXtISSh0aGlzLnByb3RvdHlwZSk7bGV0IGE9dGhpcy5fcHJvcGVydGllczt0aGlzLl9fb2JzZXJ2ZWRBdHRyaWJ1dGVzPWE/T2JqZWN0LmtleXMoYSkubWFwKHM9PnRoaXMucHJvdG90eXBlLl9hZGRQcm9wZXJ0eVRvQXR0cmlidXRlTWFwKHMpKTpbXX1yZXR1cm4gdGhpcy5fX29ic2VydmVkQXR0cmlidXRlc31zdGF0aWMgZmluYWxpemUoKXtpZighdGhpcy5oYXNPd25Qcm9wZXJ0eShKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJfX2ZpbmFsaXplZCIsdGhpcykpKXtsZXQgYT1yKHRoaXMpO2EmJmEuZmluYWxpemUoKSx0aGlzLl9fZmluYWxpemVkPSEwLHRoaXMuX2ZpbmFsaXplQ2xhc3MoKX19c3RhdGljIF9maW5hbGl6ZUNsYXNzKCl7bGV0IGE9bih0aGlzKTthJiZ0aGlzLmNyZWF0ZVByb3BlcnRpZXMoYSl9c3RhdGljIGdldCBfcHJvcGVydGllcygpe2lmKCF0aGlzLmhhc093blByb3BlcnR5KEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoIl9fcHJvcGVydGllcyIsdGhpcykpKXtsZXQgYT1yKHRoaXMpO3RoaXMuX19wcm9wZXJ0aWVzPU9iamVjdC5hc3NpZ24oe30sYSYmYS5fcHJvcGVydGllcyxuKHRoaXMpKX1yZXR1cm4gdGhpcy5fX3Byb3BlcnRpZXN9c3RhdGljIHR5cGVGb3JQcm9wZXJ0eShhKXtsZXQgcz10aGlzLl9wcm9wZXJ0aWVzW2FdO3JldHVybiBzJiZzLnR5cGV9X2luaXRpYWxpemVQcm9wZXJ0aWVzKCl7d210KCksdGhpcy5jb25zdHJ1Y3Rvci5maW5hbGl6ZSgpLHN1cGVyLl9pbml0aWFsaXplUHJvcGVydGllcygpfWNvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuY29ubmVjdGVkQ2FsbGJhY2smJnN1cGVyLmNvbm5lY3RlZENhbGxiYWNrKCksdGhpcy5fZW5hYmxlUHJvcGVydGllcygpfWRpc2Nvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuZGlzY29ubmVjdGVkQ2FsbGJhY2smJnN1cGVyLmRpc2Nvbm5lY3RlZENhbGxiYWNrKCl9fXJldHVybiBpfSk7dmFyIE1tdD0iMy40LjEiLFZJPXdpbmRvdy5TaGFkeUNTUyYmd2luZG93LlNoYWR5Q1NTLmNzc0J1aWxkLFNtPU5uKGU9PntsZXQgdD1TbXQodV8oZSkpO2Z1bmN0aW9uIHIobCl7aWYoIWwuaGFzT3duUHJvcGVydHkoSlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiX19wcm9wZXJ0eURlZmF1bHRzIixsKSkpe2wuX19wcm9wZXJ0eURlZmF1bHRzPW51bGw7bGV0IGM9bC5fcHJvcGVydGllcztmb3IobGV0IHUgaW4gYyl7bGV0IGg9Y1t1XTsidmFsdWUiaW4gaCYmKGwuX19wcm9wZXJ0eURlZmF1bHRzPWwuX19wcm9wZXJ0eURlZmF1bHRzfHx7fSxsLl9fcHJvcGVydHlEZWZhdWx0c1t1XT1oKX19cmV0dXJuIGwuX19wcm9wZXJ0eURlZmF1bHRzfWZ1bmN0aW9uIG4obCl7cmV0dXJuIGwuaGFzT3duUHJvcGVydHkoSlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiX19vd25PYnNlcnZlcnMiLGwpKXx8KGwuX19vd25PYnNlcnZlcnM9bC5oYXNPd25Qcm9wZXJ0eShKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJvYnNlcnZlcnMiLGwpKT9sLm9ic2VydmVyczpudWxsKSxsLl9fb3duT2JzZXJ2ZXJzfWZ1bmN0aW9uIGkobCxjLHUsaCl7dS5jb21wdXRlZCYmKHUucmVhZE9ubHk9ITApLHUuY29tcHV0ZWQmJihsLl9oYXNSZWFkT25seUVmZmVjdChjKT9jb25zb2xlLndhcm4oYENhbm5vdCByZWRlZmluZSBjb21wdXRlZCBwcm9wZXJ0eSAnJHtjfScuYCk6bC5fY3JlYXRlQ29tcHV0ZWRQcm9wZXJ0eShjLHUuY29tcHV0ZWQsaCkpLHUucmVhZE9ubHkmJiFsLl9oYXNSZWFkT25seUVmZmVjdChjKT9sLl9jcmVhdGVSZWFkT25seVByb3BlcnR5KGMsIXUuY29tcHV0ZWQpOnUucmVhZE9ubHk9PT0hMSYmbC5faGFzUmVhZE9ubHlFZmZlY3QoYykmJmNvbnNvbGUud2FybihgQ2Fubm90IG1ha2UgcmVhZE9ubHkgcHJvcGVydHkgJyR7Y30nIG5vbi1yZWFkT25seS5gKSx1LnJlZmxlY3RUb0F0dHJpYnV0ZSYmIWwuX2hhc1JlZmxlY3RFZmZlY3QoYyk/bC5fY3JlYXRlUmVmbGVjdGVkUHJvcGVydHkoYyk6dS5yZWZsZWN0VG9BdHRyaWJ1dGU9PT0hMSYmbC5faGFzUmVmbGVjdEVmZmVjdChjKSYmY29uc29sZS53YXJuKGBDYW5ub3QgbWFrZSByZWZsZWN0ZWQgcHJvcGVydHkgJyR7Y30nIG5vbi1yZWZsZWN0ZWQuYCksdS5ub3RpZnkmJiFsLl9oYXNOb3RpZnlFZmZlY3QoYyk/bC5fY3JlYXRlTm90aWZ5aW5nUHJvcGVydHkoYyk6dS5ub3RpZnk9PT0hMSYmbC5faGFzTm90aWZ5RWZmZWN0KGMpJiZjb25zb2xlLndhcm4oYENhbm5vdCBtYWtlIG5vdGlmeSBwcm9wZXJ0eSAnJHtjfScgbm9uLW5vdGlmeS5gKSx1Lm9ic2VydmVyJiZsLl9jcmVhdGVQcm9wZXJ0eU9ic2VydmVyKGMsdS5vYnNlcnZlcixoW3Uub2JzZXJ2ZXJdKSxsLl9hZGRQcm9wZXJ0eVRvQXR0cmlidXRlTWFwKGMpfWZ1bmN0aW9uIG8obCxjLHUsaCl7aWYoIVZJKXtsZXQgZj1jLmNvbnRlbnQucXVlcnlTZWxlY3RvckFsbCgic3R5bGUiKSxwPVJJKGMpLGQ9UWR0KHUpLGc9Yy5jb250ZW50LmZpcnN0RWxlbWVudENoaWxkO2ZvcihsZXQgeT0wO3k8ZC5sZW5ndGg7eSsrKXtsZXQgeD1kW3ldO3gudGV4dENvbnRlbnQ9bC5fcHJvY2Vzc1N0eWxlVGV4dCh4LnRleHRDb250ZW50LGgpLGMuY29udGVudC5pbnNlcnRCZWZvcmUoeCxnKX1sZXQgXz0wO2ZvcihsZXQgeT0wO3k8cC5sZW5ndGg7eSsrKXtsZXQgeD1wW3ldLGI9ZltfXTtiIT09eD8oeD14LmNsb25lTm9kZSghMCksYi5wYXJlbnROb2RlLmluc2VydEJlZm9yZSh4LGIpKTpfKysseC50ZXh0Q29udGVudD1sLl9wcm9jZXNzU3R5bGVUZXh0KHgudGV4dENvbnRlbnQsaCl9fWlmKHdpbmRvdy5TaGFkeUNTUyYmd2luZG93LlNoYWR5Q1NTLnByZXBhcmVUZW1wbGF0ZShjLHUpLFlkdCYmVkkmJkhkdCl7bGV0IGY9Yy5jb250ZW50LnF1ZXJ5U2VsZWN0b3JBbGwoInN0eWxlIik7aWYoZil7bGV0IHA9IiI7QXJyYXkuZnJvbShmKS5mb3JFYWNoKGQ9PntwKz1kLnRleHRDb250ZW50LGQucGFyZW50Tm9kZS5yZW1vdmVDaGlsZChkKX0pLGwuX3N0eWxlU2hlZXQ9bmV3IENTU1N0eWxlU2hlZXQsbC5fc3R5bGVTaGVldC5yZXBsYWNlU3luYyhwKX19fWZ1bmN0aW9uIGEobCl7bGV0IGM9bnVsbDtpZihsJiYoIWl1fHxxZHQpJiYoYz1vdS5pbXBvcnQobCwidGVtcGxhdGUiKSxpdSYmIWMpKXRocm93IG5ldyBFcnJvcihgc3RyaWN0VGVtcGxhdGVQb2xpY3k6IGV4cGVjdGluZyBkb20tbW9kdWxlIG9yIG51bGwgdGVtcGxhdGUgZm9yICR7bH1gKTtyZXR1cm4gY31jbGFzcyBzIGV4dGVuZHMgdHtzdGF0aWMgZ2V0IHBvbHltZXJFbGVtZW50VmVyc2lvbigpe3JldHVybiBNbXR9c3RhdGljIF9maW5hbGl6ZUNsYXNzKCl7dC5fZmluYWxpemVDbGFzcy5jYWxsKHRoaXMpO2xldCBjPW4odGhpcyk7YyYmdGhpcy5jcmVhdGVPYnNlcnZlcnMoYyx0aGlzLl9wcm9wZXJ0aWVzKSx0aGlzLl9wcmVwYXJlVGVtcGxhdGUoKX1zdGF0aWMgX3ByZXBhcmVUZW1wbGF0ZSgpe2xldCBjPXRoaXMudGVtcGxhdGU7YyYmKHR5cGVvZiBjPT0ic3RyaW5nIj8oY29uc29sZS5lcnJvcigidGVtcGxhdGUgZ2V0dGVyIG11c3QgcmV0dXJuIEhUTUxUZW1wbGF0ZUVsZW1lbnQiKSxjPW51bGwpOnBwfHwoYz1jLmNsb25lTm9kZSghMCkpKSx0aGlzLnByb3RvdHlwZS5fdGVtcGxhdGU9Y31zdGF0aWMgY3JlYXRlUHJvcGVydGllcyhjKXtmb3IobGV0IHUgaW4gYylpKHRoaXMucHJvdG90eXBlLHUsY1t1XSxjKX1zdGF0aWMgY3JlYXRlT2JzZXJ2ZXJzKGMsdSl7bGV0IGg9dGhpcy5wcm90b3R5cGU7Zm9yKGxldCBmPTA7ZjxjLmxlbmd0aDtmKyspaC5fY3JlYXRlTWV0aG9kT2JzZXJ2ZXIoY1tmXSx1KX1zdGF0aWMgZ2V0IHRlbXBsYXRlKCl7aWYoIXRoaXMuaGFzT3duUHJvcGVydHkoSlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiX3RlbXBsYXRlIix0aGlzKSkpe2xldCBjPXRoaXMucHJvdG90eXBlLmhhc093blByb3BlcnR5KEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoIl90ZW1wbGF0ZSIsdGhpcy5wcm90b3R5cGUpKT90aGlzLnByb3RvdHlwZS5fdGVtcGxhdGU6dm9pZCAwO3RoaXMuX3RlbXBsYXRlPWMhPT12b2lkIDA/Yzp0aGlzLmhhc093blByb3BlcnR5KEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoImlzIix0aGlzKSkmJmEodGhpcy5pcyl8fE9iamVjdC5nZXRQcm90b3R5cGVPZih0aGlzLnByb3RvdHlwZSkuY29uc3RydWN0b3IudGVtcGxhdGV9cmV0dXJuIHRoaXMuX3RlbXBsYXRlfXN0YXRpYyBzZXQgdGVtcGxhdGUoYyl7dGhpcy5fdGVtcGxhdGU9Y31zdGF0aWMgZ2V0IGltcG9ydFBhdGgoKXtpZighdGhpcy5oYXNPd25Qcm9wZXJ0eShKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJfaW1wb3J0UGF0aCIsdGhpcykpKXtsZXQgYz10aGlzLmltcG9ydE1ldGE7aWYoYyl0aGlzLl9pbXBvcnRQYXRoPUN4KGMudXJsKTtlbHNle2xldCB1PW91LmltcG9ydCh0aGlzLmlzKTt0aGlzLl9pbXBvcnRQYXRoPXUmJnUuYXNzZXRwYXRofHxPYmplY3QuZ2V0UHJvdG90eXBlT2YodGhpcy5wcm90b3R5cGUpLmNvbnN0cnVjdG9yLmltcG9ydFBhdGh9fXJldHVybiB0aGlzLl9pbXBvcnRQYXRofWNvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLl90ZW1wbGF0ZSx0aGlzLl9pbXBvcnRQYXRoLHRoaXMucm9vdFBhdGgsdGhpcy5pbXBvcnRQYXRoLHRoaXMucm9vdCx0aGlzLiR9X2luaXRpYWxpemVQcm9wZXJ0aWVzKCl7dGhpcy5jb25zdHJ1Y3Rvci5maW5hbGl6ZSgpLHRoaXMuY29uc3RydWN0b3IuX2ZpbmFsaXplVGVtcGxhdGUodGhpcy5sb2NhbE5hbWUpLHN1cGVyLl9pbml0aWFsaXplUHJvcGVydGllcygpLHRoaXMucm9vdFBhdGg9VmR0LHRoaXMuaW1wb3J0UGF0aD10aGlzLmNvbnN0cnVjdG9yLmltcG9ydFBhdGg7bGV0IGM9cih0aGlzLmNvbnN0cnVjdG9yKTtpZighIWMpZm9yKGxldCB1IGluIGMpe2xldCBoPWNbdV07aWYodGhpcy5fY2FuQXBwbHlQcm9wZXJ0eURlZmF1bHQodSkpe2xldCBmPXR5cGVvZiBoLnZhbHVlPT0iZnVuY3Rpb24iP2gudmFsdWUuY2FsbCh0aGlzKTpoLnZhbHVlO3RoaXMuX2hhc0FjY2Vzc29yKHUpP3RoaXMuX3NldFBlbmRpbmdQcm9wZXJ0eSh1LGYsITApOnRoaXNbdV09Zn19fV9jYW5BcHBseVByb3BlcnR5RGVmYXVsdChjKXtyZXR1cm4hdGhpcy5oYXNPd25Qcm9wZXJ0eShjKX1zdGF0aWMgX3Byb2Nlc3NTdHlsZVRleHQoYyx1KXtyZXR1cm4gSE0oYyx1KX1zdGF0aWMgX2ZpbmFsaXplVGVtcGxhdGUoYyl7bGV0IHU9dGhpcy5wcm90b3R5cGUuX3RlbXBsYXRlO2lmKHUmJiF1Ll9fcG9seW1lckZpbmFsaXplZCl7dS5fX3BvbHltZXJGaW5hbGl6ZWQ9ITA7bGV0IGg9dGhpcy5pbXBvcnRQYXRoLGY9aD9sXyhoKToiIjtvKHRoaXMsdSxjLGYpLHRoaXMucHJvdG90eXBlLl9iaW5kVGVtcGxhdGUodSl9fWNvbm5lY3RlZENhbGxiYWNrKCl7d2luZG93LlNoYWR5Q1NTJiZ0aGlzLl90ZW1wbGF0ZSYmd2luZG93LlNoYWR5Q1NTLnN0eWxlRWxlbWVudCh0aGlzKSxzdXBlci5jb25uZWN0ZWRDYWxsYmFjaygpfXJlYWR5KCl7dGhpcy5fdGVtcGxhdGUmJih0aGlzLnJvb3Q9dGhpcy5fc3RhbXBUZW1wbGF0ZSh0aGlzLl90ZW1wbGF0ZSksdGhpcy4kPXRoaXMucm9vdC4kKSxzdXBlci5yZWFkeSgpfV9yZWFkeUNsaWVudHMoKXt0aGlzLl90ZW1wbGF0ZSYmKHRoaXMucm9vdD10aGlzLl9hdHRhY2hEb20odGhpcy5yb290KSksc3VwZXIuX3JlYWR5Q2xpZW50cygpfV9hdHRhY2hEb20oYyl7bGV0IHU9dWUodGhpcyk7aWYodS5hdHRhY2hTaGFkb3cpcmV0dXJuIGM/KHUuc2hhZG93Um9vdHx8KHUuYXR0YWNoU2hhZG93KHttb2RlOiJvcGVuIixzaGFkeVVwZ3JhZGVGcmFnbWVudDpjfSksdS5zaGFkb3dSb290LmFwcGVuZENoaWxkKGMpLHRoaXMuY29uc3RydWN0b3IuX3N0eWxlU2hlZXQmJih1LnNoYWRvd1Jvb3QuYWRvcHRlZFN0eWxlU2hlZXRzPVt0aGlzLmNvbnN0cnVjdG9yLl9zdHlsZVNoZWV0XSkpLEdkdCYmd2luZG93LlNoYWR5RE9NJiZ3aW5kb3cuU2hhZHlET00uZmx1c2hJbml0aWFsKHUuc2hhZG93Um9vdCksdS5zaGFkb3dSb290KTpudWxsO3Rocm93IG5ldyBFcnJvcigiU2hhZG93RE9NIG5vdCBhdmFpbGFibGUuIFBvbHltZXJFbGVtZW50IGNhbiBjcmVhdGUgZG9tIGFzIGNoaWxkcmVuIGluc3RlYWQgb2YgaW4gU2hhZG93RE9NIGJ5IHNldHRpbmcgYHRoaXMucm9vdCA9IHRoaXM7YCBiZWZvcmUgYHJlYWR5YC4iKX11cGRhdGVTdHlsZXMoYyl7d2luZG93LlNoYWR5Q1NTJiZ3aW5kb3cuU2hhZHlDU1Muc3R5bGVTdWJ0cmVlKHRoaXMsYyl9cmVzb2x2ZVVybChjLHUpe3JldHVybiF1JiZ0aGlzLmltcG9ydFBhdGgmJih1PWxfKHRoaXMuaW1wb3J0UGF0aCkpLGxfKGMsdSl9c3RhdGljIF9wYXJzZVRlbXBsYXRlQ29udGVudChjLHUsaCl7cmV0dXJuIHUuZHluYW1pY0Zucz11LmR5bmFtaWNGbnN8fHRoaXMuX3Byb3BlcnRpZXMsdC5fcGFyc2VUZW1wbGF0ZUNvbnRlbnQuY2FsbCh0aGlzLGMsdSxoKX1zdGF0aWMgX2FkZFRlbXBsYXRlUHJvcGVydHlFZmZlY3QoYyx1LGgpe3JldHVybiBJSSYmISh1IGluIHRoaXMuX3Byb3BlcnRpZXMpJiYhKGguaW5mby5wYXJ0LnNpZ25hdHVyZSYmaC5pbmZvLnBhcnQuc2lnbmF0dXJlLnN0YXRpYykmJiFoLmluZm8ucGFydC5ob3N0UHJvcCYmIWMubmVzdGVkVGVtcGxhdGUmJmNvbnNvbGUud2FybihgUHJvcGVydHkgJyR7dX0nIHVzZWQgaW4gdGVtcGxhdGUgYnV0IG5vdCBkZWNsYXJlZCBpbiAncHJvcGVydGllcyc7IGF0dHJpYnV0ZSB3aWxsIG5vdCBiZSBvYnNlcnZlZC5gKSx0Ll9hZGRUZW1wbGF0ZVByb3BlcnR5RWZmZWN0LmNhbGwodGhpcyxjLHUsaCl9fXJldHVybiBzfSk7dmFyIFVJPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMudmFsdWU9dC50b1N0cmluZygpfXRvU3RyaW5nKCl7cmV0dXJuIHRoaXMudmFsdWV9fTtmdW5jdGlvbiBJdmUoZSl7aWYoZSBpbnN0YW5jZW9mIFVJKXJldHVybiBlLnZhbHVlO3Rocm93IG5ldyBFcnJvcihgbm9uLWxpdGVyYWwgdmFsdWUgcGFzc2VkIHRvIFBvbHltZXIncyBodG1sTGl0ZXJhbCBmdW5jdGlvbjogJHtlfWApfWZ1bmN0aW9uIEx2ZShlKXtpZihlIGluc3RhbmNlb2YgSFRNTFRlbXBsYXRlRWxlbWVudClyZXR1cm4gZS5pbm5lckhUTUw7aWYoZSBpbnN0YW5jZW9mIFVJKXJldHVybiBJdmUoZSk7dGhyb3cgbmV3IEVycm9yKGBub24tdGVtcGxhdGUgdmFsdWUgcGFzc2VkIHRvIFBvbHltZXIncyBodG1sIGZ1bmN0aW9uOiAke2V9YCl9dmFyIFE9ZnVuY3Rpb24odCwuLi5yKXtsZXQgbj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZW1wbGF0ZSIpO3JldHVybiBuLmlubmVySFRNTD1yLnJlZHVjZSgoaSxvLGEpPT5pK0x2ZShvKSt0W2ErMV0sdFswXSksbn07dmFyIG10PVNtKEhUTUxFbGVtZW50KTt2YXIgZnk9RWUoT2UoKSwxKTt2YXIgaF89ISh3aW5kb3cuU2hhZHlET00mJndpbmRvdy5TaGFkeURPTS5pblVzZSkscUk7ZnVuY3Rpb24gRW10KGUpe2UmJmUuc2hpbWNzc3Byb3BlcnRpZXM/cUk9ITE6cUk9aF98fEJvb2xlYW4oIW5hdmlnYXRvci51c2VyQWdlbnQubWF0Y2goL0FwcGxlV2ViS2l0XC82MDF8RWRnZVwvMTUvKSYmd2luZG93LkNTUyYmQ1NTLnN1cHBvcnRzJiZDU1Muc3VwcG9ydHMoImJveC1zaGFkb3ciLCIwIDAgMCB2YXIoLS1mb28pIikpfXZhciBNbTt3aW5kb3cuU2hhZHlDU1MmJndpbmRvdy5TaGFkeUNTUy5jc3NCdWlsZCE9PXZvaWQgMCYmKE1tPXdpbmRvdy5TaGFkeUNTUy5jc3NCdWlsZCk7dmFyIEdJPUJvb2xlYW4od2luZG93LlNoYWR5Q1NTJiZ3aW5kb3cuU2hhZHlDU1MuZGlzYWJsZVJ1bnRpbWUpO3dpbmRvdy5TaGFkeUNTUyYmd2luZG93LlNoYWR5Q1NTLm5hdGl2ZUNzcyE9PXZvaWQgMD9xST13aW5kb3cuU2hhZHlDU1MubmF0aXZlQ3NzOndpbmRvdy5TaGFkeUNTUz8oRW10KHdpbmRvdy5TaGFkeUNTUyksd2luZG93LlNoYWR5Q1NTPXZvaWQgMCk6RW10KHdpbmRvdy5XZWJDb21wb25lbnRzJiZ3aW5kb3cuV2ViQ29tcG9uZW50cy5mbGFncyk7dmFyIE54PXFJO3ZhciBXST1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuc3RhcnQ9MCx0aGlzLmVuZD0wLHRoaXMucHJldmlvdXM9bnVsbCx0aGlzLnBhcmVudD1udWxsLHRoaXMucnVsZXM9bnVsbCx0aGlzLnBhcnNlZENzc1RleHQ9IiIsdGhpcy5jc3NUZXh0PSIiLHRoaXMuYXRSdWxlPSExLHRoaXMudHlwZT0wLHRoaXMua2V5ZnJhbWVzTmFtZT0iIix0aGlzLnNlbGVjdG9yPSIiLHRoaXMucGFyc2VkU2VsZWN0b3I9IiJ9fTtmdW5jdGlvbiBNRyhlKXtyZXR1cm4gZT1rdmUoZSksVG10KFJ2ZShlKSxlKX1mdW5jdGlvbiBrdmUoZSl7cmV0dXJuIGUucmVwbGFjZShncC5jb21tZW50cywiIikucmVwbGFjZShncC5wb3J0LCIiKX1mdW5jdGlvbiBSdmUoZSl7bGV0IHQ9bmV3IFdJO3Quc3RhcnQ9MCx0LmVuZD1lLmxlbmd0aDtsZXQgcj10O2ZvcihsZXQgbj0wLGk9ZS5sZW5ndGg7bjxpO24rKylpZihlW25dPT09Q210KXtyLnJ1bGVzfHwoci5ydWxlcz1bXSk7bGV0IG89cixhPW8ucnVsZXNbby5ydWxlcy5sZW5ndGgtMV18fG51bGw7cj1uZXcgV0ksci5zdGFydD1uKzEsci5wYXJlbnQ9byxyLnByZXZpb3VzPWEsby5ydWxlcy5wdXNoKHIpfWVsc2UgZVtuXT09PUFtdCYmKHIuZW5kPW4rMSxyPXIucGFyZW50fHx0KTtyZXR1cm4gdH1mdW5jdGlvbiBUbXQoZSx0KXtsZXQgcj10LnN1YnN0cmluZyhlLnN0YXJ0LGUuZW5kLTEpO2lmKGUucGFyc2VkQ3NzVGV4dD1lLmNzc1RleHQ9ci50cmltKCksZS5wYXJlbnQpe2xldCBpPWUucHJldmlvdXM/ZS5wcmV2aW91cy5lbmQ6ZS5wYXJlbnQuc3RhcnQ7cj10LnN1YnN0cmluZyhpLGUuc3RhcnQtMSkscj1OdmUocikscj1yLnJlcGxhY2UoZ3AubXVsdGlwbGVTcGFjZXMsIiAiKSxyPXIuc3Vic3RyaW5nKHIubGFzdEluZGV4T2YoIjsiKSsxKTtsZXQgbz1lLnBhcnNlZFNlbGVjdG9yPWUuc2VsZWN0b3I9ci50cmltKCk7ZS5hdFJ1bGU9by5pbmRleE9mKEh2ZSk9PT0wLGUuYXRSdWxlP28uaW5kZXhPZihCdmUpPT09MD9lLnR5cGU9bXAuTUVESUFfUlVMRTpvLm1hdGNoKGdwLmtleWZyYW1lc1J1bGUpJiYoZS50eXBlPW1wLktFWUZSQU1FU19SVUxFLGUua2V5ZnJhbWVzTmFtZT1lLnNlbGVjdG9yLnNwbGl0KGdwLm11bHRpcGxlU3BhY2VzKS5wb3AoKSk6by5pbmRleE9mKFBtdCk9PT0wP2UudHlwZT1tcC5NSVhJTl9SVUxFOmUudHlwZT1tcC5TVFlMRV9SVUxFfWxldCBuPWUucnVsZXM7aWYobilmb3IobGV0IGk9MCxvPW4ubGVuZ3RoLGE7aTxvJiYoYT1uW2ldKTtpKyspVG10KGEsdCk7cmV0dXJuIGV9ZnVuY3Rpb24gTnZlKGUpe3JldHVybiBlLnJlcGxhY2UoL1xcKFswLTlhLWZdezEsNn0pXHMvZ2ksZnVuY3Rpb24oKXtsZXQgdD1hcmd1bWVudHNbMV0scj02LXQubGVuZ3RoO2Zvcig7ci0tOyl0PSIwIit0O3JldHVybiJcXCIrdH0pfWZ1bmN0aW9uIEVHKGUsdCxyPSIiKXtsZXQgbj0iIjtpZihlLmNzc1RleHR8fGUucnVsZXMpe2xldCBpPWUucnVsZXM7aWYoaSYmIUR2ZShpKSlmb3IobGV0IG89MCxhPWkubGVuZ3RoLHM7bzxhJiYocz1pW29dKTtvKyspbj1FRyhzLHQsbik7ZWxzZSBuPXQ/ZS5jc3NUZXh0Ok92ZShlLmNzc1RleHQpLG49bi50cmltKCksbiYmKG49IiAgIituK2AKYCl9cmV0dXJuIG4mJihlLnNlbGVjdG9yJiYocis9ZS5zZWxlY3RvcisiICIrQ210K2AKYCkscis9bixlLnNlbGVjdG9yJiYocis9QW10K2AKCmApKSxyfWZ1bmN0aW9uIER2ZShlKXtsZXQgdD1lWzBdO3JldHVybiBCb29sZWFuKHQpJiZCb29sZWFuKHQuc2VsZWN0b3IpJiZ0LnNlbGVjdG9yLmluZGV4T2YoUG10KT09PTB9ZnVuY3Rpb24gT3ZlKGUpe3JldHVybiBlPXp2ZShlKSxGdmUoZSl9ZnVuY3Rpb24genZlKGUpe3JldHVybiBlLnJlcGxhY2UoZ3AuY3VzdG9tUHJvcCwiIikucmVwbGFjZShncC5taXhpblByb3AsIiIpfWZ1bmN0aW9uIEZ2ZShlKXtyZXR1cm4gZS5yZXBsYWNlKGdwLm1peGluQXBwbHksIiIpLnJlcGxhY2UoZ3AudmFyQXBwbHksIiIpfXZhciBtcD17U1RZTEVfUlVMRToxLEtFWUZSQU1FU19SVUxFOjcsTUVESUFfUlVMRTo0LE1JWElOX1JVTEU6MWUzfSxDbXQ9InsiLEFtdD0ifSIsZ3A9e2NvbW1lbnRzOi9cL1wqW14qXSpcKisoW14vKl1bXipdKlwqKykqXC8vZ2ltLHBvcnQ6L0BpbXBvcnRbXjtdKjsvZ2ltLGN1c3RvbVByb3A6Lyg/Ol5bXjtcLVxzfV0rKT8tLVteO3t9XSo/Oltee307XSo/KD86Wztcbl18JCkvZ2ltLG1peGluUHJvcDovKD86XlteO1wtXHN9XSspPy0tW147e31dKj86W157fTtdKj97W159XSo/fSg/Ols7XG5dfCQpPy9naW0sbWl4aW5BcHBseTovQGFwcGx5XHMqXCg/W14pO10qXCk/XHMqKD86Wztcbl18JCk/L2dpbSx2YXJBcHBseTovW147Ol0qPzpbXjtdKj92YXJcKFteO10qXCkoPzpbO1xuXXwkKT8vZ2ltLGtleWZyYW1lc1J1bGU6L15AW15cc10qa2V5ZnJhbWVzLyxtdWx0aXBsZVNwYWNlczovXHMrL2d9LFBtdD0iLS0iLEJ2ZT0iQG1lZGlhIixIdmU9IkAiO3ZhciBLTT0vKD86XnxbO1xze11ccyopKC0tW1x3LV0qPylccyo6XHMqKD86KCg/OicoPzpcXCd8LikqPyd8Iig/OlxcInwuKSo/InxcKFteKV0qP1wpfFtefTt7XSkrKXxceyhbXn1dKilcfSg/Oig/PVs7XHN9XSl8JCkpL2dpLER4PS8oPzpefFxXKylAYXBwbHlccypcKD8oW14pO1xuXSopXCk/L2dpO3ZhciBJbXQ9L0BtZWRpYVxzKC4qKS87dmFyIExtdD1uZXcgU2V0LFZ2ZT0ic2hhZHktdW5zY29wZWQiO2Z1bmN0aW9uIGttdChlKXtsZXQgdD1lLnRleHRDb250ZW50O2lmKCFMbXQuaGFzKHQpKXtMbXQuYWRkKHQpO2xldCByPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInN0eWxlIik7ci5zZXRBdHRyaWJ1dGUoInNoYWR5LXVuc2NvcGVkIiwiIiksci50ZXh0Q29udGVudD10LGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQocil9fWZ1bmN0aW9uIFJtdChlKXtyZXR1cm4gZS5oYXNBdHRyaWJ1dGUoVnZlKX1mdW5jdGlvbiBaTShlLHQpe3JldHVybiBlPyh0eXBlb2YgZT09InN0cmluZyImJihlPU1HKGUpKSx0JiZPeChlLHQpLEVHKGUsTngpKToiIn1mdW5jdGlvbiBURyhlKXtyZXR1cm4hZS5fX2Nzc1J1bGVzJiZlLnRleHRDb250ZW50JiYoZS5fX2Nzc1J1bGVzPU1HKGUudGV4dENvbnRlbnQpKSxlLl9fY3NzUnVsZXN8fG51bGx9ZnVuY3Rpb24gT3goZSx0LHIsbil7aWYoIWUpcmV0dXJuO2xldCBpPSExLG89ZS50eXBlO2lmKG4mJm89PT1tcC5NRURJQV9SVUxFKXtsZXQgcz1lLnNlbGVjdG9yLm1hdGNoKEltdCk7cyYmKHdpbmRvdy5tYXRjaE1lZGlhKHNbMV0pLm1hdGNoZXN8fChpPSEwKSl9bz09PW1wLlNUWUxFX1JVTEU/dChlKTpyJiZvPT09bXAuS0VZRlJBTUVTX1JVTEU/cihlKTpvPT09bXAuTUlYSU5fUlVMRSYmKGk9ITApO2xldCBhPWUucnVsZXM7aWYoYSYmIWkpZm9yKGxldCBzPTAsbD1hLmxlbmd0aCxjO3M8bCYmKGM9YVtzXSk7cysrKU94KGMsdCxyLG4pfWZ1bmN0aW9uIFV2ZShlLHQpe2xldCByPTA7Zm9yKGxldCBuPXQsaT1lLmxlbmd0aDtuPGk7bisrKWlmKGVbbl09PT0iKCIpcisrO2Vsc2UgaWYoZVtuXT09PSIpIiYmLS1yPT09MClyZXR1cm4gbjtyZXR1cm4tMX1mdW5jdGlvbiBDRyhlLHQpe2xldCByPWUuaW5kZXhPZigidmFyKCIpO2lmKHI9PT0tMSlyZXR1cm4gdChlLCIiLCIiLCIiKTtsZXQgbj1VdmUoZSxyKzMpLGk9ZS5zdWJzdHJpbmcocis0LG4pLG89ZS5zdWJzdHJpbmcoMCxyKSxhPUNHKGUuc3Vic3RyaW5nKG4rMSksdCkscz1pLmluZGV4T2YoIiwiKTtpZihzPT09LTEpcmV0dXJuIHQobyxpLnRyaW0oKSwiIixhKTtsZXQgbD1pLnN1YnN0cmluZygwLHMpLnRyaW0oKSxjPWkuc3Vic3RyaW5nKHMrMSkudHJpbSgpO3JldHVybiB0KG8sbCxjLGEpfXZhciBuMXI9d2luZG93LlNoYWR5RE9NJiZ3aW5kb3cuU2hhZHlET00ud3JhcHx8KGU9PmUpO2Z1bmN0aW9uIE5tdChlKXtsZXQgdD1lLmxvY2FsTmFtZSxyPSIiLG49IiI7cmV0dXJuIHQ/dC5pbmRleE9mKCItIik+LTE/cj10OihuPXQscj1lLmdldEF0dHJpYnV0ZSYmZS5nZXRBdHRyaWJ1dGUoImlzIil8fCIiKToocj1lLmlzLG49ZS5leHRlbmRzKSx7aXM6cix0eXBlRXh0ZW5zaW9uOm59fWZ1bmN0aW9uIERtdChlKXtsZXQgdD1bXSxyPWUucXVlcnlTZWxlY3RvckFsbCgic3R5bGUiKTtmb3IobGV0IG49MDtuPHIubGVuZ3RoO24rKyl7bGV0IGk9cltuXTtSbXQoaSk/aF98fChrbXQoaSksaS5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKGkpKToodC5wdXNoKGkudGV4dENvbnRlbnQpLGkucGFyZW50Tm9kZS5yZW1vdmVDaGlsZChpKSl9cmV0dXJuIHQuam9pbigiIikudHJpbSgpfXZhciBPbXQ9ImNzcy1idWlsZCI7ZnVuY3Rpb24gcXZlKGUpe2lmKE1tIT09dm9pZCAwKXJldHVybiBNbTtpZihlLl9fY3NzQnVpbGQ9PT12b2lkIDApe2xldCB0PWUuZ2V0QXR0cmlidXRlKE9tdCk7aWYodCllLl9fY3NzQnVpbGQ9dDtlbHNle2xldCByPUd2ZShlKTtyIT09IiImJld2ZShlKSxlLl9fY3NzQnVpbGQ9cn19cmV0dXJuIGUuX19jc3NCdWlsZHx8IiJ9ZnVuY3Rpb24gQUcoZSl7cmV0dXJuIHF2ZShlKSE9PSIifWZ1bmN0aW9uIEd2ZShlKXtsZXQgdD1lLmxvY2FsTmFtZT09PSJ0ZW1wbGF0ZSI/ZS5jb250ZW50LmZpcnN0Q2hpbGQ6ZS5maXJzdENoaWxkO2lmKHQgaW5zdGFuY2VvZiBDb21tZW50KXtsZXQgcj10LnRleHRDb250ZW50LnRyaW0oKS5zcGxpdCgiOiIpO2lmKHJbMF09PT1PbXQpcmV0dXJuIHJbMV19cmV0dXJuIiJ9ZnVuY3Rpb24gV3ZlKGUpe2xldCB0PWUubG9jYWxOYW1lPT09InRlbXBsYXRlIj9lLmNvbnRlbnQuZmlyc3RDaGlsZDplLmZpcnN0Q2hpbGQ7dC5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKHQpfWZ1bmN0aW9uIEpNKGUsdCl7Zm9yKGxldCByIGluIHQpcj09PW51bGw/ZS5zdHlsZS5yZW1vdmVQcm9wZXJ0eShyKTplLnN0eWxlLnNldFByb3BlcnR5KHIsdFtyXSl9ZnVuY3Rpb24gWUkoZSx0KXtsZXQgcj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShlKS5nZXRQcm9wZXJ0eVZhbHVlKHQpO3JldHVybiByP3IudHJpbSgpOiIifWZ1bmN0aW9uIHptdChlKXtsZXQgdD1EeC50ZXN0KGUpfHxLTS50ZXN0KGUpO3JldHVybiBEeC5sYXN0SW5kZXg9MCxLTS5sYXN0SW5kZXg9MCx0fXZhciBZdmU9LztccyovbSxqdmU9L15ccyooaW5pdGlhbCl8KGluaGVyaXQpXHMqJC8sRm10PS9ccyohaW1wb3J0YW50LyxQRz0iXy1fIjt2YXIgSUc9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLl9tYXA9e319c2V0KHQscil7dD10LnRyaW0oKSx0aGlzLl9tYXBbdF09e3Byb3BlcnRpZXM6cixkZXBlbmRhbnRzOnt9fX1nZXQodCl7cmV0dXJuIHQ9dC50cmltKCksdGhpcy5fbWFwW3RdfHxudWxsfX0sakk9bnVsbCxEbz1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuX2N1cnJlbnRFbGVtZW50PW51bGwsdGhpcy5fbWVhc3VyZUVsZW1lbnQ9bnVsbCx0aGlzLl9tYXA9bmV3IElHfWRldGVjdE1peGluKHQpe3JldHVybiB6bXQodCl9Z2F0aGVyU3R5bGVzKHQpe2xldCByPURtdCh0LmNvbnRlbnQpO2lmKHIpe2xldCBuPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInN0eWxlIik7cmV0dXJuIG4udGV4dENvbnRlbnQ9cix0LmNvbnRlbnQuaW5zZXJ0QmVmb3JlKG4sdC5jb250ZW50LmZpcnN0Q2hpbGQpLG59cmV0dXJuIG51bGx9dHJhbnNmb3JtVGVtcGxhdGUodCxyKXt0Ll9nYXRoZXJlZFN0eWxlPT09dm9pZCAwJiYodC5fZ2F0aGVyZWRTdHlsZT10aGlzLmdhdGhlclN0eWxlcyh0KSk7bGV0IG49dC5fZ2F0aGVyZWRTdHlsZTtyZXR1cm4gbj90aGlzLnRyYW5zZm9ybVN0eWxlKG4scik6bnVsbH10cmFuc2Zvcm1TdHlsZSh0LHI9IiIpe2xldCBuPVRHKHQpO3JldHVybiB0aGlzLnRyYW5zZm9ybVJ1bGVzKG4sciksdC50ZXh0Q29udGVudD1aTShuKSxufXRyYW5zZm9ybUN1c3RvbVN0eWxlKHQpe2xldCByPVRHKHQpO3JldHVybiBPeChyLG49PntuLnNlbGVjdG9yPT09Ijpyb290IiYmKG4uc2VsZWN0b3I9Imh0bWwiKSx0aGlzLnRyYW5zZm9ybVJ1bGUobil9KSx0LnRleHRDb250ZW50PVpNKHIpLHJ9dHJhbnNmb3JtUnVsZXModCxyKXt0aGlzLl9jdXJyZW50RWxlbWVudD1yLE94KHQsbj0+e3RoaXMudHJhbnNmb3JtUnVsZShuKX0pLHRoaXMuX2N1cnJlbnRFbGVtZW50PW51bGx9dHJhbnNmb3JtUnVsZSh0KXt0LmNzc1RleHQ9dGhpcy50cmFuc2Zvcm1Dc3NUZXh0KHQucGFyc2VkQ3NzVGV4dCx0KSx0LnNlbGVjdG9yPT09Ijpyb290IiYmKHQuc2VsZWN0b3I9Ijpob3N0ID4gKiIpfXRyYW5zZm9ybUNzc1RleHQodCxyKXtyZXR1cm4gdD10LnJlcGxhY2UoS00sKG4saSxvLGEpPT50aGlzLl9wcm9kdWNlQ3NzUHJvcGVydGllcyhuLGksbyxhLHIpKSx0aGlzLl9jb25zdW1lQ3NzUHJvcGVydGllcyh0LHIpfV9nZXRJbml0aWFsVmFsdWVGb3JQcm9wZXJ0eSh0KXtyZXR1cm4gdGhpcy5fbWVhc3VyZUVsZW1lbnR8fCh0aGlzLl9tZWFzdXJlRWxlbWVudD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJtZXRhIiksdGhpcy5fbWVhc3VyZUVsZW1lbnQuc2V0QXR0cmlidXRlKCJhcHBseS1zaGltLW1lYXN1cmUiLCIiKSx0aGlzLl9tZWFzdXJlRWxlbWVudC5zdHlsZS5hbGw9ImluaXRpYWwiLGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQodGhpcy5fbWVhc3VyZUVsZW1lbnQpKSx3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzLl9tZWFzdXJlRWxlbWVudCkuZ2V0UHJvcGVydHlWYWx1ZSh0KX1fZmFsbGJhY2tzRnJvbVByZXZpb3VzUnVsZXModCl7bGV0IHI9dDtmb3IoO3IucGFyZW50OylyPXIucGFyZW50O2xldCBuPXt9LGk9ITE7cmV0dXJuIE94KHIsbz0+e2k9aXx8bz09PXQsIWkmJm8uc2VsZWN0b3I9PT10LnNlbGVjdG9yJiZPYmplY3QuYXNzaWduKG4sdGhpcy5fY3NzVGV4dFRvTWFwKG8ucGFyc2VkQ3NzVGV4dCkpfSksbn1fY29uc3VtZUNzc1Byb3BlcnRpZXModCxyKXtsZXQgbj1udWxsO2Zvcig7bj1EeC5leGVjKHQpOyl7bGV0IGk9blswXSxvPW5bMV0sYT1uLmluZGV4LHM9YStpLmluZGV4T2YoIkBhcHBseSIpLGw9YStpLmxlbmd0aCxjPXQuc2xpY2UoMCxzKSx1PXQuc2xpY2UobCksaD1yP3RoaXMuX2ZhbGxiYWNrc0Zyb21QcmV2aW91c1J1bGVzKHIpOnt9O09iamVjdC5hc3NpZ24oaCx0aGlzLl9jc3NUZXh0VG9NYXAoYykpO2xldCBmPXRoaXMuX2F0QXBwbHlUb0Nzc1Byb3BlcnRpZXMobyxoKTt0PWAke2N9JHtmfSR7dX1gLER4Lmxhc3RJbmRleD1hK2YubGVuZ3RofXJldHVybiB0fV9hdEFwcGx5VG9Dc3NQcm9wZXJ0aWVzKHQscil7dD10LnJlcGxhY2UoWXZlLCIiKTtsZXQgbj1bXSxpPXRoaXMuX21hcC5nZXQodCk7aWYoaXx8KHRoaXMuX21hcC5zZXQodCx7fSksaT10aGlzLl9tYXAuZ2V0KHQpKSxpKXt0aGlzLl9jdXJyZW50RWxlbWVudCYmKGkuZGVwZW5kYW50c1t0aGlzLl9jdXJyZW50RWxlbWVudF09ITApO2xldCBvLGEscyxsPWkucHJvcGVydGllcztmb3IobyBpbiBsKXM9ciYmcltvXSxhPVtvLCI6IHZhcigiLHQsUEcsb10scyYmYS5wdXNoKCIsIixzLnJlcGxhY2UoRm10LCIiKSksYS5wdXNoKCIpIiksRm10LnRlc3QobFtvXSkmJmEucHVzaCgiICFpbXBvcnRhbnQiKSxuLnB1c2goYS5qb2luKCIiKSl9cmV0dXJuIG4uam9pbigiOyAiKX1fcmVwbGFjZUluaXRpYWxPckluaGVyaXQodCxyKXtsZXQgbj1qdmUuZXhlYyhyKTtyZXR1cm4gbiYmKG5bMV0/cj10aGlzLl9nZXRJbml0aWFsVmFsdWVGb3JQcm9wZXJ0eSh0KTpyPSJhcHBseS1zaGltLWluaGVyaXQiKSxyfV9jc3NUZXh0VG9NYXAodCxyPSExKXtsZXQgbj10LnNwbGl0KCI7IiksaSxvLGE9e307Zm9yKGxldCBzPTAsbCxjO3M8bi5sZW5ndGg7cysrKWw9bltzXSxsJiYoYz1sLnNwbGl0KCI6IiksYy5sZW5ndGg+MSYmKGk9Y1swXS50cmltKCksbz1jLnNsaWNlKDEpLmpvaW4oIjoiKSxyJiYobz10aGlzLl9yZXBsYWNlSW5pdGlhbE9ySW5oZXJpdChpLG8pKSxhW2ldPW8pKTtyZXR1cm4gYX1faW52YWxpZGF0ZU1peGluRW50cnkodCl7aWYoISFqSSlmb3IobGV0IHIgaW4gdC5kZXBlbmRhbnRzKXIhPT10aGlzLl9jdXJyZW50RWxlbWVudCYmakkocil9X3Byb2R1Y2VDc3NQcm9wZXJ0aWVzKHQscixuLGksbyl7aWYobiYmQ0cobiwoXyx5KT0+e3kmJnRoaXMuX21hcC5nZXQoeSkmJihpPWBAYXBwbHkgJHt5fTtgKX0pLCFpKXJldHVybiB0O2xldCBhPXRoaXMuX2NvbnN1bWVDc3NQcm9wZXJ0aWVzKCIiK2ksbykscz10LnNsaWNlKDAsdC5pbmRleE9mKCItLSIpKSxsPXRoaXMuX2Nzc1RleHRUb01hcChhLCEwKSxjPWwsdT10aGlzLl9tYXAuZ2V0KHIpLGg9dSYmdS5wcm9wZXJ0aWVzO2g/Yz1PYmplY3QuYXNzaWduKE9iamVjdC5jcmVhdGUoaCksbCk6dGhpcy5fbWFwLnNldChyLGMpO2xldCBmPVtdLHAsZCxnPSExO2ZvcihwIGluIGMpZD1sW3BdLGQ9PT12b2lkIDAmJihkPSJpbml0aWFsIiksaCYmIShwIGluIGgpJiYoZz0hMCksZi5wdXNoKGAke3J9JHtQR30ke3B9OiAke2R9YCk7cmV0dXJuIGcmJnRoaXMuX2ludmFsaWRhdGVNaXhpbkVudHJ5KHUpLHUmJih1LnByb3BlcnRpZXM9YyksbiYmKHM9YCR7dH07JHtzfWApLGAke3N9JHtmLmpvaW4oIjsgIil9O2B9fTtEby5wcm90b3R5cGUuZGV0ZWN0TWl4aW49RG8ucHJvdG90eXBlLmRldGVjdE1peGluO0RvLnByb3RvdHlwZS50cmFuc2Zvcm1TdHlsZT1Eby5wcm90b3R5cGUudHJhbnNmb3JtU3R5bGU7RG8ucHJvdG90eXBlLnRyYW5zZm9ybUN1c3RvbVN0eWxlPURvLnByb3RvdHlwZS50cmFuc2Zvcm1DdXN0b21TdHlsZTtEby5wcm90b3R5cGUudHJhbnNmb3JtUnVsZXM9RG8ucHJvdG90eXBlLnRyYW5zZm9ybVJ1bGVzO0RvLnByb3RvdHlwZS50cmFuc2Zvcm1SdWxlPURvLnByb3RvdHlwZS50cmFuc2Zvcm1SdWxlO0RvLnByb3RvdHlwZS50cmFuc2Zvcm1UZW1wbGF0ZT1Eby5wcm90b3R5cGUudHJhbnNmb3JtVGVtcGxhdGU7RG8ucHJvdG90eXBlLl9zZXBhcmF0b3I9UEc7T2JqZWN0LmRlZmluZVByb3BlcnR5KERvLnByb3RvdHlwZSwiaW52YWxpZENhbGxiYWNrIix7Z2V0KCl7cmV0dXJuIGpJfSxzZXQoZSl7akk9ZX19KTt2YXIgQm10PURvO3ZhciBYdmU9e30sUU09WHZlO3ZhciBYST0iX2FwcGx5U2hpbUN1cnJlbnRWZXJzaW9uIix6eD0iX2FwcGx5U2hpbU5leHRWZXJzaW9uIiwkST0iX2FwcGx5U2hpbVZhbGlkYXRpbmdWZXJzaW9uIiwkdmU9UHJvbWlzZS5yZXNvbHZlKCk7ZnVuY3Rpb24gSG10KGUpe2xldCB0PVFNW2VdO3QmJkt2ZSh0KX1mdW5jdGlvbiBLdmUoZSl7ZVtYSV09ZVtYSV18fDAsZVskSV09ZVskSV18fDAsZVt6eF09KGVbenhdfHwwKSsxfWZ1bmN0aW9uIExHKGUpe3JldHVybiBlW1hJXT09PWVbenhdfWZ1bmN0aW9uIFZtdChlKXtyZXR1cm4hTEcoZSkmJmVbJEldPT09ZVt6eF19ZnVuY3Rpb24gVW10KGUpe2VbJEldPWVbenhdLGUuX3ZhbGlkYXRpbmd8fChlLl92YWxpZGF0aW5nPSEwLCR2ZS50aGVuKGZ1bmN0aW9uKCl7ZVtYSV09ZVt6eF0sZS5fdmFsaWRhdGluZz0hMX0pKX12YXIga0c9bnVsbCxxbXQ9d2luZG93LkhUTUxJbXBvcnRzJiZ3aW5kb3cuSFRNTEltcG9ydHMud2hlblJlYWR5fHxudWxsLFJHO2Z1bmN0aW9uIEtJKGUpe3JlcXVlc3RBbmltYXRpb25GcmFtZShmdW5jdGlvbigpe3FtdD9xbXQoZSk6KGtHfHwoa0c9bmV3IFByb21pc2UodD0+e1JHPXR9KSxkb2N1bWVudC5yZWFkeVN0YXRlPT09ImNvbXBsZXRlIj9SRygpOmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoInJlYWR5c3RhdGVjaGFuZ2UiLCgpPT57ZG9jdW1lbnQucmVhZHlTdGF0ZT09PSJjb21wbGV0ZSImJlJHKCl9KSksa0cudGhlbihmdW5jdGlvbigpe2UmJmUoKX0pKX0pfXZhciBHbXQ9Il9fc2VlbkJ5U2hhZHlDU1MiLFpJPSJfX3NoYWR5Q1NTQ2FjaGVkU3R5bGUiLEpJPW51bGwsdEU9bnVsbCxabD1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuY3VzdG9tU3R5bGVzPVtdLHRoaXMuZW5xdWV1ZWQ9ITEsS0koKCk9Pnt3aW5kb3cuU2hhZHlDU1MuZmx1c2hDdXN0b21TdHlsZXMmJndpbmRvdy5TaGFkeUNTUy5mbHVzaEN1c3RvbVN0eWxlcygpfSl9ZW5xdWV1ZURvY3VtZW50VmFsaWRhdGlvbigpe3RoaXMuZW5xdWV1ZWR8fCF0RXx8KHRoaXMuZW5xdWV1ZWQ9ITAsS0kodEUpKX1hZGRDdXN0b21TdHlsZSh0KXt0W0dtdF18fCh0W0dtdF09ITAsdGhpcy5jdXN0b21TdHlsZXMucHVzaCh0KSx0aGlzLmVucXVldWVEb2N1bWVudFZhbGlkYXRpb24oKSl9Z2V0U3R5bGVGb3JDdXN0b21TdHlsZSh0KXtpZih0W1pJXSlyZXR1cm4gdFtaSV07bGV0IHI7cmV0dXJuIHQuZ2V0U3R5bGU/cj10LmdldFN0eWxlKCk6cj10LHJ9cHJvY2Vzc1N0eWxlcygpe2xldCB0PXRoaXMuY3VzdG9tU3R5bGVzO2ZvcihsZXQgcj0wO3I8dC5sZW5ndGg7cisrKXtsZXQgbj10W3JdO2lmKG5bWkldKWNvbnRpbnVlO2xldCBpPXRoaXMuZ2V0U3R5bGVGb3JDdXN0b21TdHlsZShuKTtpZihpKXtsZXQgbz1pLl9fYXBwbGllZEVsZW1lbnR8fGk7SkkmJkpJKG8pLG5bWkldPW99fXJldHVybiB0fX07WmwucHJvdG90eXBlLmFkZEN1c3RvbVN0eWxlPVpsLnByb3RvdHlwZS5hZGRDdXN0b21TdHlsZTtabC5wcm90b3R5cGUuZ2V0U3R5bGVGb3JDdXN0b21TdHlsZT1abC5wcm90b3R5cGUuZ2V0U3R5bGVGb3JDdXN0b21TdHlsZTtabC5wcm90b3R5cGUucHJvY2Vzc1N0eWxlcz1abC5wcm90b3R5cGUucHJvY2Vzc1N0eWxlcztPYmplY3QuZGVmaW5lUHJvcGVydGllcyhabC5wcm90b3R5cGUse3RyYW5zZm9ybUNhbGxiYWNrOntnZXQoKXtyZXR1cm4gSkl9LHNldChlKXtKST1lfX0sdmFsaWRhdGVDYWxsYmFjazp7Z2V0KCl7cmV0dXJuIHRFfSxzZXQoZSl7bGV0IHQ9ITE7dEV8fCh0PSEwKSx0RT1lLHQmJnRoaXMuZW5xdWV1ZURvY3VtZW50VmFsaWRhdGlvbigpfX19KTt2YXIgZUU9bmV3IEJtdCxORz1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuY3VzdG9tU3R5bGVJbnRlcmZhY2U9bnVsbCxlRS5pbnZhbGlkQ2FsbGJhY2s9SG10fWVuc3VyZSgpe3RoaXMuY3VzdG9tU3R5bGVJbnRlcmZhY2V8fHdpbmRvdy5TaGFkeUNTUy5DdXN0b21TdHlsZUludGVyZmFjZSYmKHRoaXMuY3VzdG9tU3R5bGVJbnRlcmZhY2U9d2luZG93LlNoYWR5Q1NTLkN1c3RvbVN0eWxlSW50ZXJmYWNlLHRoaXMuY3VzdG9tU3R5bGVJbnRlcmZhY2UudHJhbnNmb3JtQ2FsbGJhY2s9dD0+e2VFLnRyYW5zZm9ybUN1c3RvbVN0eWxlKHQpfSx0aGlzLmN1c3RvbVN0eWxlSW50ZXJmYWNlLnZhbGlkYXRlQ2FsbGJhY2s9KCk9PntyZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCk9Pnt0aGlzLmN1c3RvbVN0eWxlSW50ZXJmYWNlLmVucXVldWVkJiZ0aGlzLmZsdXNoQ3VzdG9tU3R5bGVzKCl9KX0pfXByZXBhcmVUZW1wbGF0ZSh0LHIpe2lmKHRoaXMuZW5zdXJlKCksQUcodCkpcmV0dXJuO1FNW3JdPXQ7bGV0IG49ZUUudHJhbnNmb3JtVGVtcGxhdGUodCxyKTt0Ll9zdHlsZUFzdD1ufWZsdXNoQ3VzdG9tU3R5bGVzKCl7aWYodGhpcy5lbnN1cmUoKSwhdGhpcy5jdXN0b21TdHlsZUludGVyZmFjZSlyZXR1cm47bGV0IHQ9dGhpcy5jdXN0b21TdHlsZUludGVyZmFjZS5wcm9jZXNzU3R5bGVzKCk7aWYoISF0aGlzLmN1c3RvbVN0eWxlSW50ZXJmYWNlLmVucXVldWVkKXtmb3IobGV0IHI9MDtyPHQubGVuZ3RoO3IrKyl7bGV0IG49dFtyXSxpPXRoaXMuY3VzdG9tU3R5bGVJbnRlcmZhY2UuZ2V0U3R5bGVGb3JDdXN0b21TdHlsZShuKTtpJiZlRS50cmFuc2Zvcm1DdXN0b21TdHlsZShpKX10aGlzLmN1c3RvbVN0eWxlSW50ZXJmYWNlLmVucXVldWVkPSExfX1zdHlsZVN1YnRyZWUodCxyKXtpZih0aGlzLmVuc3VyZSgpLHImJkpNKHQsciksdC5zaGFkb3dSb290KXt0aGlzLnN0eWxlRWxlbWVudCh0KTtsZXQgbj10LnNoYWRvd1Jvb3QuY2hpbGRyZW58fHQuc2hhZG93Um9vdC5jaGlsZE5vZGVzO2ZvcihsZXQgaT0wO2k8bi5sZW5ndGg7aSsrKXRoaXMuc3R5bGVTdWJ0cmVlKG5baV0pfWVsc2V7bGV0IG49dC5jaGlsZHJlbnx8dC5jaGlsZE5vZGVzO2ZvcihsZXQgaT0wO2k8bi5sZW5ndGg7aSsrKXRoaXMuc3R5bGVTdWJ0cmVlKG5baV0pfX1zdHlsZUVsZW1lbnQodCl7dGhpcy5lbnN1cmUoKTtsZXR7aXM6cn09Tm10KHQpLG49UU1bcl07aWYoIShuJiZBRyhuKSkmJm4mJiFMRyhuKSl7Vm10KG4pfHwodGhpcy5wcmVwYXJlVGVtcGxhdGUobixyKSxVbXQobikpO2xldCBpPXQuc2hhZG93Um9vdDtpZihpKXtsZXQgbz1pLnF1ZXJ5U2VsZWN0b3IoInN0eWxlIik7byYmKG8uX19jc3NSdWxlcz1uLl9zdHlsZUFzdCxvLnRleHRDb250ZW50PVpNKG4uX3N0eWxlQXN0KSl9fX1zdHlsZURvY3VtZW50KHQpe3RoaXMuZW5zdXJlKCksdGhpcy5zdHlsZVN1YnRyZWUoZG9jdW1lbnQuYm9keSx0KX19O2lmKCF3aW5kb3cuU2hhZHlDU1N8fCF3aW5kb3cuU2hhZHlDU1MuU2NvcGluZ1NoaW0pe2xldCBlPW5ldyBORyx0PXdpbmRvdy5TaGFkeUNTUyYmd2luZG93LlNoYWR5Q1NTLkN1c3RvbVN0eWxlSW50ZXJmYWNlO3dpbmRvdy5TaGFkeUNTUz17cHJlcGFyZVRlbXBsYXRlKHIsbixpKXtlLmZsdXNoQ3VzdG9tU3R5bGVzKCksZS5wcmVwYXJlVGVtcGxhdGUocixuKX0scHJlcGFyZVRlbXBsYXRlU3R5bGVzKHIsbixpKXt3aW5kb3cuU2hhZHlDU1MucHJlcGFyZVRlbXBsYXRlKHIsbixpKX0scHJlcGFyZVRlbXBsYXRlRG9tKHIsbil7fSxzdHlsZVN1YnRyZWUocixuKXtlLmZsdXNoQ3VzdG9tU3R5bGVzKCksZS5zdHlsZVN1YnRyZWUocixuKX0sc3R5bGVFbGVtZW50KHIpe2UuZmx1c2hDdXN0b21TdHlsZXMoKSxlLnN0eWxlRWxlbWVudChyKX0sc3R5bGVEb2N1bWVudChyKXtlLmZsdXNoQ3VzdG9tU3R5bGVzKCksZS5zdHlsZURvY3VtZW50KHIpfSxnZXRDb21wdXRlZFN0eWxlVmFsdWUocixuKXtyZXR1cm4gWUkocixuKX0sZmx1c2hDdXN0b21TdHlsZXMoKXtlLmZsdXNoQ3VzdG9tU3R5bGVzKCl9LG5hdGl2ZUNzczpOeCxuYXRpdmVTaGFkb3c6aF8sY3NzQnVpbGQ6TW0sZGlzYWJsZVJ1bnRpbWU6R0l9LHQmJih3aW5kb3cuU2hhZHlDU1MuQ3VzdG9tU3R5bGVJbnRlcmZhY2U9dCl9d2luZG93LlNoYWR5Q1NTLkFwcGx5U2hpbT1lRTt2YXIgc3I9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLl9hc3luY01vZHVsZT1udWxsLHRoaXMuX2NhbGxiYWNrPW51bGwsdGhpcy5fdGltZXI9bnVsbH1zZXRDb25maWcodCxyKXt0aGlzLl9hc3luY01vZHVsZT10LHRoaXMuX2NhbGxiYWNrPXIsdGhpcy5fdGltZXI9dGhpcy5fYXN5bmNNb2R1bGUucnVuKCgpPT57dGhpcy5fdGltZXI9bnVsbCxyRS5kZWxldGUodGhpcyksdGhpcy5fY2FsbGJhY2soKX0pfWNhbmNlbCgpe3RoaXMuaXNBY3RpdmUoKSYmKHRoaXMuX2NhbmNlbEFzeW5jKCksckUuZGVsZXRlKHRoaXMpKX1fY2FuY2VsQXN5bmMoKXt0aGlzLmlzQWN0aXZlKCkmJih0aGlzLl9hc3luY01vZHVsZS5jYW5jZWwodGhpcy5fdGltZXIpLHRoaXMuX3RpbWVyPW51bGwpfWZsdXNoKCl7dGhpcy5pc0FjdGl2ZSgpJiYodGhpcy5jYW5jZWwoKSx0aGlzLl9jYWxsYmFjaygpKX1pc0FjdGl2ZSgpe3JldHVybiB0aGlzLl90aW1lciE9bnVsbH1zdGF0aWMgZGVib3VuY2UodCxyLG4pe3JldHVybiB0IGluc3RhbmNlb2Ygc3I/dC5fY2FuY2VsQXN5bmMoKTp0PW5ldyBzcix0LnNldENvbmZpZyhyLG4pLHR9fSxyRT1uZXcgU2V0LEpsPWZ1bmN0aW9uKGUpe3JFLmFkZChlKX0sV210PWZ1bmN0aW9uKCl7bGV0IGU9Qm9vbGVhbihyRS5zaXplKTtyZXR1cm4gckUuZm9yRWFjaCh0PT57dHJ5e3QuZmx1c2goKX1jYXRjaChyKXtzZXRUaW1lb3V0KCgpPT57dGhyb3cgcn0pfX0pLGV9O3ZhciBGRz10eXBlb2YgZG9jdW1lbnQuaGVhZC5zdHlsZS50b3VjaEFjdGlvbj09InN0cmluZyIsdDk9Il9fcG9seW1lckdlc3R1cmVzIixRST0iX19wb2x5bWVyR2VzdHVyZXNIYW5kbGVkIixPRz0iX19wb2x5bWVyR2VzdHVyZXNUb3VjaEFjdGlvbiIsWW10PTI1LGptdD01LEp2ZT0yLFF2ZT0yNTAwLEptdD1bIm1vdXNlZG93biIsIm1vdXNlbW92ZSIsIm1vdXNldXAiLCJjbGljayJdLHR4ZT1bMCwxLDQsMl0sZXhlPWZ1bmN0aW9uKCl7dHJ5e3JldHVybiBuZXcgTW91c2VFdmVudCgidGVzdCIse2J1dHRvbnM6MX0pLmJ1dHRvbnM9PT0xfWNhdGNoKGUpe3JldHVybiExfX0oKTtmdW5jdGlvbiBCRyhlKXtyZXR1cm4gSm10LmluZGV4T2YoZSk+LTF9dmFyIEhHPSExOyhmdW5jdGlvbigpe3RyeXtsZXQgZT1PYmplY3QuZGVmaW5lUHJvcGVydHkoe30sInBhc3NpdmUiLHtnZXQoKXtIRz0hMH19KTt3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigidGVzdCIsbnVsbCxlKSx3aW5kb3cucmVtb3ZlRXZlbnRMaXN0ZW5lcigidGVzdCIsbnVsbCxlKX1jYXRjaChlKXt9fSkoKTtmdW5jdGlvbiBRbXQoZSl7aWYoIShCRyhlKXx8ZT09PSJ0b3VjaGVuZCIpJiZGRyYmSEcmJlVkdClyZXR1cm57cGFzc2l2ZTohMH19dmFyIHRndD1uYXZpZ2F0b3IudXNlckFnZW50Lm1hdGNoKC9pUCg/OltvYV1kfGhvbmUpfEFuZHJvaWQvKSx6Rz1bXSxyeGU9e2J1dHRvbjohMCxpbnB1dDohMCxrZXlnZW46ITAsbWV0ZXI6ITAsb3V0cHV0OiEwLHRleHRhcmVhOiEwLHByb2dyZXNzOiEwLHNlbGVjdDohMH0sbnhlPXtidXR0b246ITAsY29tbWFuZDohMCxmaWVsZHNldDohMCxpbnB1dDohMCxrZXlnZW46ITAsb3B0Z3JvdXA6ITAsb3B0aW9uOiEwLHNlbGVjdDohMCx0ZXh0YXJlYTohMH07ZnVuY3Rpb24gaXhlKGUpe3JldHVybiByeGVbZS5sb2NhbE5hbWVdfHwhMX1mdW5jdGlvbiBveGUoZSl7bGV0IHQ9QXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoZS5sYWJlbHN8fFtdKTtpZighdC5sZW5ndGgpe3Q9W107bGV0IHI9ZS5nZXRSb290Tm9kZSgpO2lmKGUuaWQpe2xldCBuPXIucXVlcnlTZWxlY3RvckFsbChgbGFiZWxbZm9yID0gJHtlLmlkfV1gKTtmb3IobGV0IGk9MDtpPG4ubGVuZ3RoO2krKyl0LnB1c2gobltpXSl9fXJldHVybiB0fXZhciBYbXQ9ZnVuY3Rpb24oZSl7bGV0IHQ9ZS5zb3VyY2VDYXBhYmlsaXRpZXM7aWYoISh0JiYhdC5maXJlc1RvdWNoRXZlbnRzKSYmKGVbUUldPXtza2lwOiEwfSxlLnR5cGU9PT0iY2xpY2siKSl7bGV0IHI9ITEsbj1yOShlKTtmb3IobGV0IGk9MDtpPG4ubGVuZ3RoO2krKyl7aWYobltpXS5ub2RlVHlwZT09PU5vZGUuRUxFTUVOVF9OT0RFKXtpZihuW2ldLmxvY2FsTmFtZT09PSJsYWJlbCIpekcucHVzaChuW2ldKTtlbHNlIGlmKGl4ZShuW2ldKSl7bGV0IG89b3hlKG5baV0pO2ZvcihsZXQgYT0wO2E8by5sZW5ndGg7YSsrKXI9cnx8ekcuaW5kZXhPZihvW2FdKT4tMX19aWYobltpXT09PWlhLm1vdXNlLnRhcmdldClyZXR1cm59aWYocilyZXR1cm47ZS5wcmV2ZW50RGVmYXVsdCgpLGUuc3RvcFByb3BhZ2F0aW9uKCl9fTtmdW5jdGlvbiAkbXQoZSl7bGV0IHQ9dGd0P1siY2xpY2siXTpKbXQ7Zm9yKGxldCByPTAsbjtyPHQubGVuZ3RoO3IrKyluPXRbcl0sZT8oekcubGVuZ3RoPTAsZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcihuLFhtdCwhMCkpOmRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIobixYbXQsITApfWZ1bmN0aW9uIGF4ZShlKXtpZighaEcpcmV0dXJuO2lhLm1vdXNlLm1vdXNlSWdub3JlSm9ifHwkbXQoITApO2xldCB0PWZ1bmN0aW9uKCl7JG10KCksaWEubW91c2UudGFyZ2V0PW51bGwsaWEubW91c2UubW91c2VJZ25vcmVKb2I9bnVsbH07aWEubW91c2UudGFyZ2V0PXI5KGUpWzBdLGlhLm1vdXNlLm1vdXNlSWdub3JlSm9iPXNyLmRlYm91bmNlKGlhLm1vdXNlLm1vdXNlSWdub3JlSm9iLG1vLmFmdGVyKFF2ZSksdCl9ZnVuY3Rpb24gcF8oZSl7bGV0IHQ9ZS50eXBlO2lmKCFCRyh0KSlyZXR1cm4hMTtpZih0PT09Im1vdXNlbW92ZSIpe2xldCByPWUuYnV0dG9ucz09PXZvaWQgMD8xOmUuYnV0dG9ucztyZXR1cm4gZSBpbnN0YW5jZW9mIHdpbmRvdy5Nb3VzZUV2ZW50JiYhZXhlJiYocj10eGVbZS53aGljaF18fDApLEJvb2xlYW4ociYxKX1lbHNlIHJldHVybihlLmJ1dHRvbj09PXZvaWQgMD8wOmUuYnV0dG9uKT09PTB9ZnVuY3Rpb24gc3hlKGUpe2lmKGUudHlwZT09PSJjbGljayIpe2lmKGUuZGV0YWlsPT09MClyZXR1cm4hMDtsZXQgdD1fcChlKTtpZighdC5ub2RlVHlwZXx8dC5ub2RlVHlwZSE9PU5vZGUuRUxFTUVOVF9OT0RFKXJldHVybiEwO2xldCByPXQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksbj1lLnBhZ2VYLGk9ZS5wYWdlWTtyZXR1cm4hKG4+PXIubGVmdCYmbjw9ci5yaWdodCYmaT49ci50b3AmJmk8PXIuYm90dG9tKX1yZXR1cm4hMX12YXIgaWE9e21vdXNlOnt0YXJnZXQ6bnVsbCxtb3VzZUlnbm9yZUpvYjpudWxsfSx0b3VjaDp7eDowLHk6MCxpZDotMSxzY3JvbGxEZWNpZGVkOiExfX07ZnVuY3Rpb24gbHhlKGUpe2xldCB0PSJhdXRvIixyPXI5KGUpO2ZvcihsZXQgbj0wLGk7bjxyLmxlbmd0aDtuKyspaWYoaT1yW25dLGlbT0ddKXt0PWlbT0ddO2JyZWFrfXJldHVybiB0fWZ1bmN0aW9uIGVndChlLHQscil7ZS5tb3ZlZm49dCxlLnVwZm49cixkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCJtb3VzZW1vdmUiLHQpLGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoIm1vdXNldXAiLHIpfWZ1bmN0aW9uIEZ4KGUpe2RvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoIm1vdXNlbW92ZSIsZS5tb3ZlZm4pLGRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoIm1vdXNldXAiLGUudXBmbiksZS5tb3ZlZm49bnVsbCxlLnVwZm49bnVsbH1oRyYmZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigidG91Y2hlbmQiLGF4ZSxIRz97cGFzc2l2ZTohMH06ITEpO3ZhciByOT13aW5kb3cuU2hhZHlET00mJndpbmRvdy5TaGFkeURPTS5ub1BhdGNoP3dpbmRvdy5TaGFkeURPTS5jb21wb3NlZFBhdGg6ZT0+ZS5jb21wb3NlZFBhdGgmJmUuY29tcG9zZWRQYXRoKCl8fFtdLGlFPXt9LGZfPVtdO2Z1bmN0aW9uIGN4ZShlLHQpe2xldCByPWRvY3VtZW50LmVsZW1lbnRGcm9tUG9pbnQoZSx0KSxuPXI7Zm9yKDtuJiZuLnNoYWRvd1Jvb3QmJiF3aW5kb3cuU2hhZHlET007KXtsZXQgaT1uO2lmKG49bi5zaGFkb3dSb290LmVsZW1lbnRGcm9tUG9pbnQoZSx0KSxpPT09bilicmVhaztuJiYocj1uKX1yZXR1cm4gcn1mdW5jdGlvbiBfcChlKXtsZXQgdD1yOShlKTtyZXR1cm4gdC5sZW5ndGg+MD90WzBdOmUudGFyZ2V0fWZ1bmN0aW9uIHJndChlKXtsZXQgdCxyPWUudHlwZSxpPWUuY3VycmVudFRhcmdldFt0OV07aWYoIWkpcmV0dXJuO2xldCBvPWlbcl07aWYoISFvKXtpZighZVtRSV0mJihlW1FJXT17fSxyLnNsaWNlKDAsNSk9PT0idG91Y2giKSl7ZT1lO2xldCBhPWUuY2hhbmdlZFRvdWNoZXNbMF07aWYocj09PSJ0b3VjaHN0YXJ0IiYmZS50b3VjaGVzLmxlbmd0aD09PTEmJihpYS50b3VjaC5pZD1hLmlkZW50aWZpZXIpLGlhLnRvdWNoLmlkIT09YS5pZGVudGlmaWVyKXJldHVybjtGR3x8KHI9PT0idG91Y2hzdGFydCJ8fHI9PT0idG91Y2htb3ZlIikmJnV4ZShlKX1pZih0PWVbUUldLCF0LnNraXApe2ZvcihsZXQgYT0wLHM7YTxmXy5sZW5ndGg7YSsrKXM9Zl9bYV0sb1tzLm5hbWVdJiYhdFtzLm5hbWVdJiZzLmZsb3cmJnMuZmxvdy5zdGFydC5pbmRleE9mKGUudHlwZSk+LTEmJnMucmVzZXQmJnMucmVzZXQoKTtmb3IobGV0IGE9MCxzO2E8Zl8ubGVuZ3RoO2ErKylzPWZfW2FdLG9bcy5uYW1lXSYmIXRbcy5uYW1lXSYmKHRbcy5uYW1lXT0hMCxzW3JdKGUpKX19fWZ1bmN0aW9uIHV4ZShlKXtsZXQgdD1lLmNoYW5nZWRUb3VjaGVzWzBdLHI9ZS50eXBlO2lmKHI9PT0idG91Y2hzdGFydCIpaWEudG91Y2gueD10LmNsaWVudFgsaWEudG91Y2gueT10LmNsaWVudFksaWEudG91Y2guc2Nyb2xsRGVjaWRlZD0hMTtlbHNlIGlmKHI9PT0idG91Y2htb3ZlIil7aWYoaWEudG91Y2guc2Nyb2xsRGVjaWRlZClyZXR1cm47aWEudG91Y2guc2Nyb2xsRGVjaWRlZD0hMDtsZXQgbj1seGUoZSksaT0hMSxvPU1hdGguYWJzKGlhLnRvdWNoLngtdC5jbGllbnRYKSxhPU1hdGguYWJzKGlhLnRvdWNoLnktdC5jbGllbnRZKTtlLmNhbmNlbGFibGUmJihuPT09Im5vbmUiP2k9ITA6bj09PSJwYW4teCI/aT1hPm86bj09PSJwYW4teSImJihpPW8+YSkpLGk/ZS5wcmV2ZW50RGVmYXVsdCgpOmU5KCJ0cmFjayIpfX1mdW5jdGlvbiBFbShlLHQscil7cmV0dXJuIGlFW3RdPyhoeGUoZSx0LHIpLCEwKTohMX1mdW5jdGlvbiBuZ3QoZSx0LHIpe3JldHVybiBpRVt0XT8oZnhlKGUsdCxyKSwhMCk6ITF9ZnVuY3Rpb24gaHhlKGUsdCxyKXtsZXQgbj1pRVt0XSxpPW4uZGVwcyxvPW4ubmFtZSxhPWVbdDldO2F8fChlW3Q5XT1hPXt9KTtmb3IobGV0IHM9MCxsLGM7czxpLmxlbmd0aDtzKyspbD1pW3NdLCEodGd0JiZCRyhsKSYmbCE9PSJjbGljayIpJiYoYz1hW2xdLGN8fChhW2xdPWM9e19jb3VudDowfSksYy5fY291bnQ9PT0wJiZlLmFkZEV2ZW50TGlzdGVuZXIobCxyZ3QsUW10KGwpKSxjW29dPShjW29dfHwwKSsxLGMuX2NvdW50PShjLl9jb3VudHx8MCkrMSk7ZS5hZGRFdmVudExpc3RlbmVyKHQsciksbi50b3VjaEFjdGlvbiYmZF8oZSxuLnRvdWNoQWN0aW9uKX1mdW5jdGlvbiBmeGUoZSx0LHIpe2xldCBuPWlFW3RdLGk9bi5kZXBzLG89bi5uYW1lLGE9ZVt0OV07aWYoYSlmb3IobGV0IHM9MCxsLGM7czxpLmxlbmd0aDtzKyspbD1pW3NdLGM9YVtsXSxjJiZjW29dJiYoY1tvXT0oY1tvXXx8MSktMSxjLl9jb3VudD0oYy5fY291bnR8fDEpLTEsYy5fY291bnQ9PT0wJiZlLnJlbW92ZUV2ZW50TGlzdGVuZXIobCxyZ3QsUW10KGwpKSk7ZS5yZW1vdmVFdmVudExpc3RlbmVyKHQscil9ZnVuY3Rpb24gVkcoZSl7Zl8ucHVzaChlKTtmb3IobGV0IHQ9MDt0PGUuZW1pdHMubGVuZ3RoO3QrKylpRVtlLmVtaXRzW3RdXT1lfWZ1bmN0aW9uIHB4ZShlKXtmb3IobGV0IHQ9MCxyO3Q8Zl8ubGVuZ3RoO3QrKyl7cj1mX1t0XTtmb3IobGV0IG49MCxpO248ci5lbWl0cy5sZW5ndGg7bisrKWlmKGk9ci5lbWl0c1tuXSxpPT09ZSlyZXR1cm4gcn1yZXR1cm4gbnVsbH1mdW5jdGlvbiBkXyhlLHQpe0ZHJiZlIGluc3RhbmNlb2YgSFRNTEVsZW1lbnQmJmNpLnJ1bigoKT0+e2Uuc3R5bGUudG91Y2hBY3Rpb249dH0pLGVbT0ddPXR9ZnVuY3Rpb24gVUcoZSx0LHIpe2xldCBuPW5ldyBFdmVudCh0LHtidWJibGVzOiEwLGNhbmNlbGFibGU6ITAsY29tcG9zZWQ6ITB9KTtpZihuLmRldGFpbD1yLHVlKGUpLmRpc3BhdGNoRXZlbnQobiksbi5kZWZhdWx0UHJldmVudGVkKXtsZXQgaT1yLnByZXZlbnRlcnx8ci5zb3VyY2VFdmVudDtpJiZpLnByZXZlbnREZWZhdWx0JiZpLnByZXZlbnREZWZhdWx0KCl9fWZ1bmN0aW9uIGU5KGUpe2xldCB0PXB4ZShlKTt0LmluZm8mJih0LmluZm8ucHJldmVudD0hMCl9Vkcoe25hbWU6ImRvd251cCIsZGVwczpbIm1vdXNlZG93biIsInRvdWNoc3RhcnQiLCJ0b3VjaGVuZCJdLGZsb3c6e3N0YXJ0OlsibW91c2Vkb3duIiwidG91Y2hzdGFydCJdLGVuZDpbIm1vdXNldXAiLCJ0b3VjaGVuZCJdfSxlbWl0czpbImRvd24iLCJ1cCJdLGluZm86e21vdmVmbjpudWxsLHVwZm46bnVsbH0scmVzZXQ6ZnVuY3Rpb24oKXtGeCh0aGlzLmluZm8pfSxtb3VzZWRvd246ZnVuY3Rpb24oZSl7aWYoIXBfKGUpKXJldHVybjtsZXQgdD1fcChlKSxyPXRoaXMsbj1mdW5jdGlvbihhKXtwXyhhKXx8KG5FKCJ1cCIsdCxhKSxGeChyLmluZm8pKX0saT1mdW5jdGlvbihhKXtwXyhhKSYmbkUoInVwIix0LGEpLEZ4KHIuaW5mbyl9O2VndCh0aGlzLmluZm8sbixpKSxuRSgiZG93biIsdCxlKX0sdG91Y2hzdGFydDpmdW5jdGlvbihlKXtuRSgiZG93biIsX3AoZSksZS5jaGFuZ2VkVG91Y2hlc1swXSxlKX0sdG91Y2hlbmQ6ZnVuY3Rpb24oZSl7bkUoInVwIixfcChlKSxlLmNoYW5nZWRUb3VjaGVzWzBdLGUpfX0pO2Z1bmN0aW9uIG5FKGUsdCxyLG4peyF0fHxVRyh0LGUse3g6ci5jbGllbnRYLHk6ci5jbGllbnRZLHNvdXJjZUV2ZW50OnIscHJldmVudGVyOm4scHJldmVudDpmdW5jdGlvbihpKXtyZXR1cm4gZTkoaSl9fSl9Vkcoe25hbWU6InRyYWNrIix0b3VjaEFjdGlvbjoibm9uZSIsZGVwczpbIm1vdXNlZG93biIsInRvdWNoc3RhcnQiLCJ0b3VjaG1vdmUiLCJ0b3VjaGVuZCJdLGZsb3c6e3N0YXJ0OlsibW91c2Vkb3duIiwidG91Y2hzdGFydCJdLGVuZDpbIm1vdXNldXAiLCJ0b3VjaGVuZCJdfSxlbWl0czpbInRyYWNrIl0saW5mbzp7eDowLHk6MCxzdGF0ZToic3RhcnQiLHN0YXJ0ZWQ6ITEsbW92ZXM6W10sYWRkTW92ZTpmdW5jdGlvbihlKXt0aGlzLm1vdmVzLmxlbmd0aD5KdmUmJnRoaXMubW92ZXMuc2hpZnQoKSx0aGlzLm1vdmVzLnB1c2goZSl9LG1vdmVmbjpudWxsLHVwZm46bnVsbCxwcmV2ZW50OiExfSxyZXNldDpmdW5jdGlvbigpe3RoaXMuaW5mby5zdGF0ZT0ic3RhcnQiLHRoaXMuaW5mby5zdGFydGVkPSExLHRoaXMuaW5mby5tb3Zlcz1bXSx0aGlzLmluZm8ueD0wLHRoaXMuaW5mby55PTAsdGhpcy5pbmZvLnByZXZlbnQ9ITEsRngodGhpcy5pbmZvKX0sbW91c2Vkb3duOmZ1bmN0aW9uKGUpe2lmKCFwXyhlKSlyZXR1cm47bGV0IHQ9X3AoZSkscj10aGlzLG49ZnVuY3Rpb24oYSl7bGV0IHM9YS5jbGllbnRYLGw9YS5jbGllbnRZO0ttdChyLmluZm8scyxsKSYmKHIuaW5mby5zdGF0ZT1yLmluZm8uc3RhcnRlZD9hLnR5cGU9PT0ibW91c2V1cCI/ImVuZCI6InRyYWNrIjoic3RhcnQiLHIuaW5mby5zdGF0ZT09PSJzdGFydCImJmU5KCJ0YXAiKSxyLmluZm8uYWRkTW92ZSh7eDpzLHk6bH0pLHBfKGEpfHwoci5pbmZvLnN0YXRlPSJlbmQiLEZ4KHIuaW5mbykpLHQmJkRHKHIuaW5mbyx0LGEpLHIuaW5mby5zdGFydGVkPSEwKX0saT1mdW5jdGlvbihhKXtyLmluZm8uc3RhcnRlZCYmbihhKSxGeChyLmluZm8pfTtlZ3QodGhpcy5pbmZvLG4saSksdGhpcy5pbmZvLng9ZS5jbGllbnRYLHRoaXMuaW5mby55PWUuY2xpZW50WX0sdG91Y2hzdGFydDpmdW5jdGlvbihlKXtsZXQgdD1lLmNoYW5nZWRUb3VjaGVzWzBdO3RoaXMuaW5mby54PXQuY2xpZW50WCx0aGlzLmluZm8ueT10LmNsaWVudFl9LHRvdWNobW92ZTpmdW5jdGlvbihlKXtsZXQgdD1fcChlKSxyPWUuY2hhbmdlZFRvdWNoZXNbMF0sbj1yLmNsaWVudFgsaT1yLmNsaWVudFk7S210KHRoaXMuaW5mbyxuLGkpJiYodGhpcy5pbmZvLnN0YXRlPT09InN0YXJ0IiYmZTkoInRhcCIpLHRoaXMuaW5mby5hZGRNb3ZlKHt4Om4seTppfSksREcodGhpcy5pbmZvLHQsciksdGhpcy5pbmZvLnN0YXRlPSJ0cmFjayIsdGhpcy5pbmZvLnN0YXJ0ZWQ9ITApfSx0b3VjaGVuZDpmdW5jdGlvbihlKXtsZXQgdD1fcChlKSxyPWUuY2hhbmdlZFRvdWNoZXNbMF07dGhpcy5pbmZvLnN0YXJ0ZWQmJih0aGlzLmluZm8uc3RhdGU9ImVuZCIsdGhpcy5pbmZvLmFkZE1vdmUoe3g6ci5jbGllbnRYLHk6ci5jbGllbnRZfSksREcodGhpcy5pbmZvLHQscikpfX0pO2Z1bmN0aW9uIEttdChlLHQscil7aWYoZS5wcmV2ZW50KXJldHVybiExO2lmKGUuc3RhcnRlZClyZXR1cm4hMDtsZXQgbj1NYXRoLmFicyhlLngtdCksaT1NYXRoLmFicyhlLnktcik7cmV0dXJuIG4+PWptdHx8aT49am10fWZ1bmN0aW9uIERHKGUsdCxyKXtpZighdClyZXR1cm47bGV0IG49ZS5tb3Zlc1tlLm1vdmVzLmxlbmd0aC0yXSxpPWUubW92ZXNbZS5tb3Zlcy5sZW5ndGgtMV0sbz1pLngtZS54LGE9aS55LWUueSxzLGw9MDtuJiYocz1pLngtbi54LGw9aS55LW4ueSksVUcodCwidHJhY2siLHtzdGF0ZTplLnN0YXRlLHg6ci5jbGllbnRYLHk6ci5jbGllbnRZLGR4Om8sZHk6YSxkZHg6cyxkZHk6bCxzb3VyY2VFdmVudDpyLGhvdmVyOmZ1bmN0aW9uKCl7cmV0dXJuIGN4ZShyLmNsaWVudFgsci5jbGllbnRZKX19KX1WRyh7bmFtZToidGFwIixkZXBzOlsibW91c2Vkb3duIiwiY2xpY2siLCJ0b3VjaHN0YXJ0IiwidG91Y2hlbmQiXSxmbG93OntzdGFydDpbIm1vdXNlZG93biIsInRvdWNoc3RhcnQiXSxlbmQ6WyJjbGljayIsInRvdWNoZW5kIl19LGVtaXRzOlsidGFwIl0saW5mbzp7eDpOYU4seTpOYU4scHJldmVudDohMX0scmVzZXQ6ZnVuY3Rpb24oKXt0aGlzLmluZm8ueD1OYU4sdGhpcy5pbmZvLnk9TmFOLHRoaXMuaW5mby5wcmV2ZW50PSExfSxtb3VzZWRvd246ZnVuY3Rpb24oZSl7cF8oZSkmJih0aGlzLmluZm8ueD1lLmNsaWVudFgsdGhpcy5pbmZvLnk9ZS5jbGllbnRZKX0sY2xpY2s6ZnVuY3Rpb24oZSl7cF8oZSkmJlptdCh0aGlzLmluZm8sZSl9LHRvdWNoc3RhcnQ6ZnVuY3Rpb24oZSl7bGV0IHQ9ZS5jaGFuZ2VkVG91Y2hlc1swXTt0aGlzLmluZm8ueD10LmNsaWVudFgsdGhpcy5pbmZvLnk9dC5jbGllbnRZfSx0b3VjaGVuZDpmdW5jdGlvbihlKXtabXQodGhpcy5pbmZvLGUuY2hhbmdlZFRvdWNoZXNbMF0sZSl9fSk7ZnVuY3Rpb24gWm10KGUsdCxyKXtsZXQgbj1NYXRoLmFicyh0LmNsaWVudFgtZS54KSxpPU1hdGguYWJzKHQuY2xpZW50WS1lLnkpLG89X3Aocnx8dCk7IW98fG54ZVtvLmxvY2FsTmFtZV0mJm8uaGFzQXR0cmlidXRlKCJkaXNhYmxlZCIpfHwoaXNOYU4obil8fGlzTmFOKGkpfHxuPD1ZbXQmJmk8PVltdHx8c3hlKHQpKSYmKGUucHJldmVudHx8VUcobywidGFwIix7eDp0LmNsaWVudFgseTp0LmNsaWVudFksc291cmNlRXZlbnQ6dCxwcmV2ZW50ZXI6cn0pKX12YXIgaWd0PV9wO3ZhciB5aD1ObihlPT57Y2xhc3MgdCBleHRlbmRzIGV7X2FkZEV2ZW50TGlzdGVuZXJUb05vZGUobixpLG8pe0VtKG4saSxvKXx8c3VwZXIuX2FkZEV2ZW50TGlzdGVuZXJUb05vZGUobixpLG8pfV9yZW1vdmVFdmVudExpc3RlbmVyRnJvbU5vZGUobixpLG8pe25ndChuLGksbyl8fHN1cGVyLl9yZW1vdmVFdmVudExpc3RlbmVyRnJvbU5vZGUobixpLG8pfX1yZXR1cm4gdH0pO3ZhciBkeGU9Lzpob3N0XCg6ZGlyXCgobHRyfHJ0bClcKVwpL2csbXhlPSc6aG9zdChbZGlyPSIkMSJdKScsZ3hlPS8oW1xzXHctI1wuXFtcXVwqXSopOmRpclwoKGx0cnxydGwpXCkvZyxfeGU9Jzpob3N0KFtkaXI9IiQyIl0pICQxJyx5eGU9LzpkaXJcKCg/Omx0cnxydGwpXCkvLGFndD1Cb29sZWFuKHdpbmRvdy5TaGFkeURPTSYmd2luZG93LlNoYWR5RE9NLmluVXNlKSxvRT1bXSxhRT1udWxsLHFHPSIiO2Z1bmN0aW9uIHNndCgpe3FHPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5nZXRBdHRyaWJ1dGUoImRpciIpfWZ1bmN0aW9uIGxndChlKXtlLl9fYXV0b0Rpck9wdE91dHx8ZS5zZXRBdHRyaWJ1dGUoImRpciIscUcpfWZ1bmN0aW9uIGNndCgpe3NndCgpLHFHPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5nZXRBdHRyaWJ1dGUoImRpciIpO2ZvcihsZXQgZT0wO2U8b0UubGVuZ3RoO2UrKylsZ3Qob0VbZV0pfWZ1bmN0aW9uIHZ4ZSgpe2FFJiZhRS50YWtlUmVjb3JkcygpLmxlbmd0aCYmY2d0KCl9dmFyIHVndD1ObihlPT57YWd0fHxhRXx8KHNndCgpLGFFPW5ldyBNdXRhdGlvbk9ic2VydmVyKGNndCksYUUub2JzZXJ2ZShkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQse2F0dHJpYnV0ZXM6ITAsYXR0cmlidXRlRmlsdGVyOlsiZGlyIl19KSk7bGV0IHQ9QkkoZSk7Y2xhc3MgciBleHRlbmRzIHR7c3RhdGljIF9wcm9jZXNzU3R5bGVUZXh0KGksbyl7cmV0dXJuIGk9dC5fcHJvY2Vzc1N0eWxlVGV4dC5jYWxsKHRoaXMsaSxvKSwhYWd0JiZ5eGUudGVzdChpKSYmKGk9dGhpcy5fcmVwbGFjZURpckluQ3NzVGV4dChpKSx0aGlzLl9fYWN0aXZhdGVEaXI9ITApLGl9c3RhdGljIF9yZXBsYWNlRGlySW5Dc3NUZXh0KGkpe2xldCBvPWk7cmV0dXJuIG89by5yZXBsYWNlKGR4ZSxteGUpLG89by5yZXBsYWNlKGd4ZSxfeGUpLG99Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX19hdXRvRGlyT3B0T3V0PSExfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLl9fYXV0b0Rpck9wdE91dD10aGlzLmhhc0F0dHJpYnV0ZSgiZGlyIil9Y29ubmVjdGVkQ2FsbGJhY2soKXt0LnByb3RvdHlwZS5jb25uZWN0ZWRDYWxsYmFjayYmc3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLmNvbnN0cnVjdG9yLl9fYWN0aXZhdGVEaXImJih2eGUoKSxvRS5wdXNoKHRoaXMpLGxndCh0aGlzKSl9ZGlzY29ubmVjdGVkQ2FsbGJhY2soKXtpZih0LnByb3RvdHlwZS5kaXNjb25uZWN0ZWRDYWxsYmFjayYmc3VwZXIuZGlzY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLmNvbnN0cnVjdG9yLl9fYWN0aXZhdGVEaXIpe2xldCBpPW9FLmluZGV4T2YodGhpcyk7aT4tMSYmb0Uuc3BsaWNlKGksMSl9fX1yZXR1cm4gci5fX2FjdGl2YXRlRGlyPSExLHJ9KTt2YXIgbjk9ITEsaGd0PVtdLGZndD1bXTtmdW5jdGlvbiBwZ3QoKXtuOT0hMCxyZXF1ZXN0QW5pbWF0aW9uRnJhbWUoZnVuY3Rpb24oKXtuOT0hMSx4eGUoaGd0KSxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7YnhlKGZndCl9KX0pfWZ1bmN0aW9uIHh4ZShlKXtmb3IoO2UubGVuZ3RoOylkZ3QoZS5zaGlmdCgpKX1mdW5jdGlvbiBieGUoZSl7Zm9yKGxldCB0PTAscj1lLmxlbmd0aDt0PHI7dCsrKWRndChlLnNoaWZ0KCkpfWZ1bmN0aW9uIGRndChlKXtsZXQgdD1lWzBdLHI9ZVsxXSxuPWVbMl07dHJ5e3IuYXBwbHkodCxuKX1jYXRjaChpKXtzZXRUaW1lb3V0KCgpPT57dGhyb3cgaX0pfX1mdW5jdGlvbiBtZ3QoZSx0LHIpe245fHxwZ3QoKSxoZ3QucHVzaChbZSx0LHJdKX1mdW5jdGlvbiBUbShlLHQscil7bjl8fHBndCgpLGZndC5wdXNoKFtlLHQscl0pfWZ1bmN0aW9uIGdndCgpe2RvY3VtZW50LmJvZHkucmVtb3ZlQXR0cmlidXRlKCJ1bnJlc29sdmVkIil9ZG9jdW1lbnQucmVhZHlTdGF0ZT09PSJpbnRlcmFjdGl2ZSJ8fGRvY3VtZW50LnJlYWR5U3RhdGU9PT0iY29tcGxldGUiP2dndCgpOndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCJET01Db250ZW50TG9hZGVkIixnZ3QpO2Z1bmN0aW9uIHNFKGUsdCxyKXtyZXR1cm57aW5kZXg6ZSxyZW1vdmVkOnQsYWRkZWRDb3VudDpyfX12YXIgX2d0PTAseWd0PTEsR0c9MixXRz0zO2Z1bmN0aW9uIHd4ZShlLHQscixuLGksbyl7bGV0IGE9by1pKzEscz1yLXQrMSxsPW5ldyBBcnJheShhKTtmb3IobGV0IGM9MDtjPGE7YysrKWxbY109bmV3IEFycmF5KHMpLGxbY11bMF09Yztmb3IobGV0IGM9MDtjPHM7YysrKWxbMF1bY109Yztmb3IobGV0IGM9MTtjPGE7YysrKWZvcihsZXQgdT0xO3U8czt1KyspaWYoWUcoZVt0K3UtMV0sbltpK2MtMV0pKWxbY11bdV09bFtjLTFdW3UtMV07ZWxzZXtsZXQgaD1sW2MtMV1bdV0rMSxmPWxbY11bdS0xXSsxO2xbY11bdV09aDxmP2g6Zn1yZXR1cm4gbH1mdW5jdGlvbiBTeGUoZSl7bGV0IHQ9ZS5sZW5ndGgtMSxyPWVbMF0ubGVuZ3RoLTEsbj1lW3RdW3JdLGk9W107Zm9yKDt0PjB8fHI+MDspe2lmKHQ9PTApe2kucHVzaChHRyksci0tO2NvbnRpbnVlfWlmKHI9PTApe2kucHVzaChXRyksdC0tO2NvbnRpbnVlfWxldCBvPWVbdC0xXVtyLTFdLGE9ZVt0LTFdW3JdLHM9ZVt0XVtyLTFdLGw7YTxzP2w9YTxvP2E6bzpsPXM8bz9zOm8sbD09bz8obz09bj9pLnB1c2goX2d0KTooaS5wdXNoKHlndCksbj1vKSx0LS0sci0tKTpsPT1hPyhpLnB1c2goV0cpLHQtLSxuPWEpOihpLnB1c2goR0cpLHItLSxuPXMpfXJldHVybiBpLnJldmVyc2UoKSxpfWZ1bmN0aW9uIE14ZShlLHQscixuLGksbyl7bGV0IGE9MCxzPTAsbCxjPU1hdGgubWluKHItdCxvLWkpO2lmKHQ9PTAmJmk9PTAmJihhPUV4ZShlLG4sYykpLHI9PWUubGVuZ3RoJiZvPT1uLmxlbmd0aCYmKHM9VHhlKGUsbixjLWEpKSx0Kz1hLGkrPWEsci09cyxvLT1zLHItdD09MCYmby1pPT0wKXJldHVybltdO2lmKHQ9PXIpe2ZvcihsPXNFKHQsW10sMCk7aTxvOylsLnJlbW92ZWQucHVzaChuW2krK10pO3JldHVybltsXX1lbHNlIGlmKGk9PW8pcmV0dXJuW3NFKHQsW10sci10KV07bGV0IHU9U3hlKHd4ZShlLHQscixuLGksbykpO2w9dm9pZCAwO2xldCBoPVtdLGY9dCxwPWk7Zm9yKGxldCBkPTA7ZDx1Lmxlbmd0aDtkKyspc3dpdGNoKHVbZF0pe2Nhc2UgX2d0OmwmJihoLnB1c2gobCksbD12b2lkIDApLGYrKyxwKys7YnJlYWs7Y2FzZSB5Z3Q6bHx8KGw9c0UoZixbXSwwKSksbC5hZGRlZENvdW50KyssZisrLGwucmVtb3ZlZC5wdXNoKG5bcF0pLHArKzticmVhaztjYXNlIEdHOmx8fChsPXNFKGYsW10sMCkpLGwuYWRkZWRDb3VudCsrLGYrKzticmVhaztjYXNlIFdHOmx8fChsPXNFKGYsW10sMCkpLGwucmVtb3ZlZC5wdXNoKG5bcF0pLHArKzticmVha31yZXR1cm4gbCYmaC5wdXNoKGwpLGh9ZnVuY3Rpb24gRXhlKGUsdCxyKXtmb3IobGV0IG49MDtuPHI7bisrKWlmKCFZRyhlW25dLHRbbl0pKXJldHVybiBuO3JldHVybiByfWZ1bmN0aW9uIFR4ZShlLHQscil7bGV0IG49ZS5sZW5ndGgsaT10Lmxlbmd0aCxvPTA7Zm9yKDtvPHImJllHKGVbLS1uXSx0Wy0taV0pOylvKys7cmV0dXJuIG99ZnVuY3Rpb24gaTkoZSx0KXtyZXR1cm4gTXhlKGUsMCxlLmxlbmd0aCx0LDAsdC5sZW5ndGgpfWZ1bmN0aW9uIFlHKGUsdCl7cmV0dXJuIGU9PT10fWZ1bmN0aW9uIEJ4KGUpe3JldHVybiBlLmxvY2FsTmFtZT09PSJzbG90In12YXIgdmg9Y2xhc3N7c3RhdGljIGdldEZsYXR0ZW5lZE5vZGVzKGUpe2xldCB0PXVlKGUpO3JldHVybiBCeChlKT8oZT1lLHQuYXNzaWduZWROb2Rlcyh7ZmxhdHRlbjohMH0pKTpBcnJheS5mcm9tKHQuY2hpbGROb2RlcykubWFwKHI9PkJ4KHIpPyhyPXIsdWUocikuYXNzaWduZWROb2Rlcyh7ZmxhdHRlbjohMH0pKTpbcl0pLnJlZHVjZSgocixuKT0+ci5jb25jYXQobiksW10pfWNvbnN0cnVjdG9yKGUsdCl7dGhpcy5fc2hhZHlDaGlsZHJlbk9ic2VydmVyPW51bGwsdGhpcy5fbmF0aXZlQ2hpbGRyZW5PYnNlcnZlcj1udWxsLHRoaXMuX2Nvbm5lY3RlZD0hMSx0aGlzLl90YXJnZXQ9ZSx0aGlzLmNhbGxiYWNrPXQsdGhpcy5fZWZmZWN0aXZlTm9kZXM9W10sdGhpcy5fb2JzZXJ2ZXI9bnVsbCx0aGlzLl9zY2hlZHVsZWQ9ITEsdGhpcy5fYm91bmRTY2hlZHVsZT0oKT0+e3RoaXMuX3NjaGVkdWxlKCl9LHRoaXMuY29ubmVjdCgpLHRoaXMuX3NjaGVkdWxlKCl9Y29ubmVjdCgpe0J4KHRoaXMuX3RhcmdldCk/dGhpcy5fbGlzdGVuU2xvdHMoW3RoaXMuX3RhcmdldF0pOnVlKHRoaXMuX3RhcmdldCkuY2hpbGRyZW4mJih0aGlzLl9saXN0ZW5TbG90cyh1ZSh0aGlzLl90YXJnZXQpLmNoaWxkcmVuKSx3aW5kb3cuU2hhZHlET00/dGhpcy5fc2hhZHlDaGlsZHJlbk9ic2VydmVyPXdpbmRvdy5TaGFkeURPTS5vYnNlcnZlQ2hpbGRyZW4odGhpcy5fdGFyZ2V0LGU9Pnt0aGlzLl9wcm9jZXNzTXV0YXRpb25zKGUpfSk6KHRoaXMuX25hdGl2ZUNoaWxkcmVuT2JzZXJ2ZXI9bmV3IE11dGF0aW9uT2JzZXJ2ZXIoZT0+e3RoaXMuX3Byb2Nlc3NNdXRhdGlvbnMoZSl9KSx0aGlzLl9uYXRpdmVDaGlsZHJlbk9ic2VydmVyLm9ic2VydmUodGhpcy5fdGFyZ2V0LHtjaGlsZExpc3Q6ITB9KSkpLHRoaXMuX2Nvbm5lY3RlZD0hMH1kaXNjb25uZWN0KCl7QngodGhpcy5fdGFyZ2V0KT90aGlzLl91bmxpc3RlblNsb3RzKFt0aGlzLl90YXJnZXRdKTp1ZSh0aGlzLl90YXJnZXQpLmNoaWxkcmVuJiYodGhpcy5fdW5saXN0ZW5TbG90cyh1ZSh0aGlzLl90YXJnZXQpLmNoaWxkcmVuKSx3aW5kb3cuU2hhZHlET00mJnRoaXMuX3NoYWR5Q2hpbGRyZW5PYnNlcnZlcj8od2luZG93LlNoYWR5RE9NLnVub2JzZXJ2ZUNoaWxkcmVuKHRoaXMuX3NoYWR5Q2hpbGRyZW5PYnNlcnZlciksdGhpcy5fc2hhZHlDaGlsZHJlbk9ic2VydmVyPW51bGwpOnRoaXMuX25hdGl2ZUNoaWxkcmVuT2JzZXJ2ZXImJih0aGlzLl9uYXRpdmVDaGlsZHJlbk9ic2VydmVyLmRpc2Nvbm5lY3QoKSx0aGlzLl9uYXRpdmVDaGlsZHJlbk9ic2VydmVyPW51bGwpKSx0aGlzLl9jb25uZWN0ZWQ9ITF9X3NjaGVkdWxlKCl7dGhpcy5fc2NoZWR1bGVkfHwodGhpcy5fc2NoZWR1bGVkPSEwLGNpLnJ1bigoKT0+dGhpcy5mbHVzaCgpKSl9X3Byb2Nlc3NNdXRhdGlvbnMoZSl7dGhpcy5fcHJvY2Vzc1Nsb3RNdXRhdGlvbnMoZSksdGhpcy5mbHVzaCgpfV9wcm9jZXNzU2xvdE11dGF0aW9ucyhlKXtpZihlKWZvcihsZXQgdD0wO3Q8ZS5sZW5ndGg7dCsrKXtsZXQgcj1lW3RdO3IuYWRkZWROb2RlcyYmdGhpcy5fbGlzdGVuU2xvdHMoci5hZGRlZE5vZGVzKSxyLnJlbW92ZWROb2RlcyYmdGhpcy5fdW5saXN0ZW5TbG90cyhyLnJlbW92ZWROb2Rlcyl9fWZsdXNoKCl7aWYoIXRoaXMuX2Nvbm5lY3RlZClyZXR1cm4hMTt3aW5kb3cuU2hhZHlET00mJlNoYWR5RE9NLmZsdXNoKCksdGhpcy5fbmF0aXZlQ2hpbGRyZW5PYnNlcnZlcj90aGlzLl9wcm9jZXNzU2xvdE11dGF0aW9ucyh0aGlzLl9uYXRpdmVDaGlsZHJlbk9ic2VydmVyLnRha2VSZWNvcmRzKCkpOnRoaXMuX3NoYWR5Q2hpbGRyZW5PYnNlcnZlciYmdGhpcy5fcHJvY2Vzc1Nsb3RNdXRhdGlvbnModGhpcy5fc2hhZHlDaGlsZHJlbk9ic2VydmVyLnRha2VSZWNvcmRzKCkpLHRoaXMuX3NjaGVkdWxlZD0hMTtsZXQgZT17dGFyZ2V0OnRoaXMuX3RhcmdldCxhZGRlZE5vZGVzOltdLHJlbW92ZWROb2RlczpbXX0sdD10aGlzLmNvbnN0cnVjdG9yLmdldEZsYXR0ZW5lZE5vZGVzKHRoaXMuX3RhcmdldCkscj1pOSh0LHRoaXMuX2VmZmVjdGl2ZU5vZGVzKTtmb3IobGV0IGk9MCxvO2k8ci5sZW5ndGgmJihvPXJbaV0pO2krKylmb3IobGV0IGE9MCxzO2E8by5yZW1vdmVkLmxlbmd0aCYmKHM9by5yZW1vdmVkW2FdKTthKyspZS5yZW1vdmVkTm9kZXMucHVzaChzKTtmb3IobGV0IGk9MCxvO2k8ci5sZW5ndGgmJihvPXJbaV0pO2krKylmb3IobGV0IGE9by5pbmRleDthPG8uaW5kZXgrby5hZGRlZENvdW50O2ErKyllLmFkZGVkTm9kZXMucHVzaCh0W2FdKTt0aGlzLl9lZmZlY3RpdmVOb2Rlcz10O2xldCBuPSExO3JldHVybihlLmFkZGVkTm9kZXMubGVuZ3RofHxlLnJlbW92ZWROb2Rlcy5sZW5ndGgpJiYobj0hMCx0aGlzLmNhbGxiYWNrLmNhbGwodGhpcy5fdGFyZ2V0LGUpKSxufV9saXN0ZW5TbG90cyhlKXtmb3IobGV0IHQ9MDt0PGUubGVuZ3RoO3QrKyl7bGV0IHI9ZVt0XTtCeChyKSYmci5hZGRFdmVudExpc3RlbmVyKCJzbG90Y2hhbmdlIix0aGlzLl9ib3VuZFNjaGVkdWxlKX19X3VubGlzdGVuU2xvdHMoZSl7Zm9yKGxldCB0PTA7dDxlLmxlbmd0aDt0Kyspe2xldCByPWVbdF07QngocikmJnIucmVtb3ZlRXZlbnRMaXN0ZW5lcigic2xvdGNoYW5nZSIsdGhpcy5fYm91bmRTY2hlZHVsZSl9fX07dmFyIHVpPWZ1bmN0aW9uKCl7bGV0IGUsdDtkbyBlPXdpbmRvdy5TaGFkeURPTSYmU2hhZHlET00uZmx1c2goKSx3aW5kb3cuU2hhZHlDU1MmJndpbmRvdy5TaGFkeUNTUy5TY29waW5nU2hpbSYmd2luZG93LlNoYWR5Q1NTLlNjb3BpbmdTaGltLmZsdXNoKCksdD1XbXQoKTt3aGlsZShlfHx0KX07dmFyIEh4PUVsZW1lbnQucHJvdG90eXBlLEN4ZT1IeC5tYXRjaGVzfHxIeC5tYXRjaGVzU2VsZWN0b3J8fEh4Lm1vek1hdGNoZXNTZWxlY3Rvcnx8SHgubXNNYXRjaGVzU2VsZWN0b3J8fEh4Lm9NYXRjaGVzU2VsZWN0b3J8fEh4LndlYmtpdE1hdGNoZXNTZWxlY3RvcixYRz1mdW5jdGlvbihlLHQpe3JldHVybiBDeGUuY2FsbChlLHQpfSxLcj1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt3aW5kb3cuU2hhZHlET00mJndpbmRvdy5TaGFkeURPTS5pblVzZSYmd2luZG93LlNoYWR5RE9NLnBhdGNoKHQpLHRoaXMubm9kZT10fW9ic2VydmVOb2Rlcyh0KXtyZXR1cm4gbmV3IHZoKHRoaXMubm9kZSx0KX11bm9ic2VydmVOb2Rlcyh0KXt0LmRpc2Nvbm5lY3QoKX1ub3RpZnlPYnNlcnZlcigpe31kZWVwQ29udGFpbnModCl7aWYodWUodGhpcy5ub2RlKS5jb250YWlucyh0KSlyZXR1cm4hMDtsZXQgcj10LG49dC5vd25lckRvY3VtZW50O2Zvcig7ciYmciE9PW4mJnIhPT10aGlzLm5vZGU7KXI9dWUocikucGFyZW50Tm9kZXx8dWUocikuaG9zdDtyZXR1cm4gcj09PXRoaXMubm9kZX1nZXRPd25lclJvb3QoKXtyZXR1cm4gdWUodGhpcy5ub2RlKS5nZXRSb290Tm9kZSgpfWdldERpc3RyaWJ1dGVkTm9kZXMoKXtyZXR1cm4gdGhpcy5ub2RlLmxvY2FsTmFtZT09PSJzbG90Ij91ZSh0aGlzLm5vZGUpLmFzc2lnbmVkTm9kZXMoe2ZsYXR0ZW46ITB9KTpbXX1nZXREZXN0aW5hdGlvbkluc2VydGlvblBvaW50cygpe2xldCB0PVtdLHI9dWUodGhpcy5ub2RlKS5hc3NpZ25lZFNsb3Q7Zm9yKDtyOyl0LnB1c2gocikscj11ZShyKS5hc3NpZ25lZFNsb3Q7cmV0dXJuIHR9aW1wb3J0Tm9kZSh0LHIpe2xldCBuPXRoaXMubm9kZSBpbnN0YW5jZW9mIERvY3VtZW50P3RoaXMubm9kZTp0aGlzLm5vZGUub3duZXJEb2N1bWVudDtyZXR1cm4gdWUobikuaW1wb3J0Tm9kZSh0LHIpfWdldEVmZmVjdGl2ZUNoaWxkTm9kZXMoKXtyZXR1cm4gdmguZ2V0RmxhdHRlbmVkTm9kZXModGhpcy5ub2RlKX1xdWVyeURpc3RyaWJ1dGVkRWxlbWVudHModCl7bGV0IHI9dGhpcy5nZXRFZmZlY3RpdmVDaGlsZE5vZGVzKCksbj1bXTtmb3IobGV0IGk9MCxvPXIubGVuZ3RoLGE7aTxvJiYoYT1yW2ldKTtpKyspYS5ub2RlVHlwZT09PU5vZGUuRUxFTUVOVF9OT0RFJiZYRyhhLHQpJiZuLnB1c2goYSk7cmV0dXJuIG59Z2V0IGFjdGl2ZUVsZW1lbnQoKXtsZXQgdD10aGlzLm5vZGU7cmV0dXJuIHQuX2FjdGl2ZUVsZW1lbnQhPT12b2lkIDA/dC5fYWN0aXZlRWxlbWVudDp0LmFjdGl2ZUVsZW1lbnR9fTtmdW5jdGlvbiBBeGUoZSx0KXtmb3IobGV0IHI9MDtyPHQubGVuZ3RoO3IrKyl7bGV0IG49dFtyXTtlW25dPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMubm9kZVtuXS5hcHBseSh0aGlzLm5vZGUsYXJndW1lbnRzKX19fWZ1bmN0aW9uIHZndChlLHQpe2ZvcihsZXQgcj0wO3I8dC5sZW5ndGg7cisrKXtsZXQgbj10W3JdO09iamVjdC5kZWZpbmVQcm9wZXJ0eShlLG4se2dldDpmdW5jdGlvbigpe3JldHVybiB0aGlzLm5vZGVbbl19LGNvbmZpZ3VyYWJsZTohMH0pfX1mdW5jdGlvbiBQeGUoZSx0KXtmb3IobGV0IHI9MDtyPHQubGVuZ3RoO3IrKyl7bGV0IG49dFtyXTtPYmplY3QuZGVmaW5lUHJvcGVydHkoZSxuLHtnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5ub2RlW25dfSxzZXQ6ZnVuY3Rpb24oaSl7dGhpcy5ub2RlW25dPWl9LGNvbmZpZ3VyYWJsZTohMH0pfX12YXIgbEU9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5ldmVudD10fWdldCByb290VGFyZ2V0KCl7cmV0dXJuIHRoaXMucGF0aFswXX1nZXQgbG9jYWxUYXJnZXQoKXtyZXR1cm4gdGhpcy5ldmVudC50YXJnZXR9Z2V0IHBhdGgoKXtyZXR1cm4gdGhpcy5ldmVudC5jb21wb3NlZFBhdGgoKX19O0tyLnByb3RvdHlwZS5jbG9uZU5vZGU7S3IucHJvdG90eXBlLmFwcGVuZENoaWxkO0tyLnByb3RvdHlwZS5pbnNlcnRCZWZvcmU7S3IucHJvdG90eXBlLnJlbW92ZUNoaWxkO0tyLnByb3RvdHlwZS5yZXBsYWNlQ2hpbGQ7S3IucHJvdG90eXBlLnNldEF0dHJpYnV0ZTtLci5wcm90b3R5cGUucmVtb3ZlQXR0cmlidXRlO0tyLnByb3RvdHlwZS5xdWVyeVNlbGVjdG9yO0tyLnByb3RvdHlwZS5xdWVyeVNlbGVjdG9yQWxsO0tyLnByb3RvdHlwZS5wYXJlbnROb2RlO0tyLnByb3RvdHlwZS5maXJzdENoaWxkO0tyLnByb3RvdHlwZS5sYXN0Q2hpbGQ7S3IucHJvdG90eXBlLm5leHRTaWJsaW5nO0tyLnByb3RvdHlwZS5wcmV2aW91c1NpYmxpbmc7S3IucHJvdG90eXBlLmZpcnN0RWxlbWVudENoaWxkO0tyLnByb3RvdHlwZS5sYXN0RWxlbWVudENoaWxkO0tyLnByb3RvdHlwZS5uZXh0RWxlbWVudFNpYmxpbmc7S3IucHJvdG90eXBlLnByZXZpb3VzRWxlbWVudFNpYmxpbmc7S3IucHJvdG90eXBlLmNoaWxkTm9kZXM7S3IucHJvdG90eXBlLmNoaWxkcmVuO0tyLnByb3RvdHlwZS5jbGFzc0xpc3Q7S3IucHJvdG90eXBlLnRleHRDb250ZW50O0tyLnByb3RvdHlwZS5pbm5lckhUTUw7dmFyIGpHPUtyO2lmKHdpbmRvdy5TaGFkeURPTSYmd2luZG93LlNoYWR5RE9NLmluVXNlJiZ3aW5kb3cuU2hhZHlET00ubm9QYXRjaCYmd2luZG93LlNoYWR5RE9NLldyYXBwZXIpe2NsYXNzIGUgZXh0ZW5kcyB3aW5kb3cuU2hhZHlET00uV3JhcHBlcnt9T2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMoS3IucHJvdG90eXBlKS5mb3JFYWNoKHQ9Pnt0IT0iYWN0aXZlRWxlbWVudCImJihlLnByb3RvdHlwZVt0XT1Lci5wcm90b3R5cGVbdF0pfSksdmd0KGUucHJvdG90eXBlLFsiY2xhc3NMaXN0Il0pLGpHPWUsT2JqZWN0LmRlZmluZVByb3BlcnRpZXMobEUucHJvdG90eXBlLHtsb2NhbFRhcmdldDp7Z2V0KCl7bGV0IHQ9dGhpcy5ldmVudC5jdXJyZW50VGFyZ2V0LHI9dCYmenQodCkuZ2V0T3duZXJSb290KCksbj10aGlzLnBhdGg7Zm9yKGxldCBpPTA7aTxuLmxlbmd0aDtpKyspe2xldCBvPW5baV07aWYoenQobykuZ2V0T3duZXJSb290KCk9PT1yKXJldHVybiBvfX0sY29uZmlndXJhYmxlOiEwfSxwYXRoOntnZXQoKXtyZXR1cm4gd2luZG93LlNoYWR5RE9NLmNvbXBvc2VkUGF0aCh0aGlzLmV2ZW50KX0sY29uZmlndXJhYmxlOiEwfX0pfWVsc2UgQXhlKEtyLnByb3RvdHlwZSxbImNsb25lTm9kZSIsImFwcGVuZENoaWxkIiwiaW5zZXJ0QmVmb3JlIiwicmVtb3ZlQ2hpbGQiLCJyZXBsYWNlQ2hpbGQiLCJzZXRBdHRyaWJ1dGUiLCJyZW1vdmVBdHRyaWJ1dGUiLCJxdWVyeVNlbGVjdG9yIiwicXVlcnlTZWxlY3RvckFsbCJdKSx2Z3QoS3IucHJvdG90eXBlLFsicGFyZW50Tm9kZSIsImZpcnN0Q2hpbGQiLCJsYXN0Q2hpbGQiLCJuZXh0U2libGluZyIsInByZXZpb3VzU2libGluZyIsImZpcnN0RWxlbWVudENoaWxkIiwibGFzdEVsZW1lbnRDaGlsZCIsIm5leHRFbGVtZW50U2libGluZyIsInByZXZpb3VzRWxlbWVudFNpYmxpbmciLCJjaGlsZE5vZGVzIiwiY2hpbGRyZW4iLCJjbGFzc0xpc3QiXSksUHhlKEtyLnByb3RvdHlwZSxbInRleHRDb250ZW50IiwiaW5uZXJIVE1MIiwiY2xhc3NOYW1lIl0pO3ZhciB6dD1mdW5jdGlvbihlKXtpZihlPWV8fGRvY3VtZW50LGUgaW5zdGFuY2VvZiBqR3x8ZSBpbnN0YW5jZW9mIGxFKXJldHVybiBlO2xldCB0PWUuX19kb21BcGk7cmV0dXJuIHR8fChlIGluc3RhbmNlb2YgRXZlbnQ/dD1uZXcgbEUoZSk6dD1uZXcgakcoZSksZS5fX2RvbUFwaT10KSx0fTt2YXIgJEc9d2luZG93LlNoYWR5RE9NLHhndD13aW5kb3cuU2hhZHlDU1M7ZnVuY3Rpb24gYmd0KGUsdCl7cmV0dXJuIHVlKGUpLmdldFJvb3ROb2RlKCk9PT10fWZ1bmN0aW9uIHdndChlLHQ9ITEpe2lmKCEkR3x8IXhndHx8ISRHLmhhbmRsZXNEeW5hbWljU2NvcGluZylyZXR1cm4gbnVsbDtsZXQgcj14Z3QuU2NvcGluZ1NoaW07aWYoIXIpcmV0dXJuIG51bGw7bGV0IG49ci5zY29wZUZvck5vZGUoZSksaT11ZShlKS5nZXRSb290Tm9kZSgpLG89YT0+e2lmKCFiZ3QoYSxpKSlyZXR1cm47bGV0IHM9QXJyYXkuZnJvbSgkRy5uYXRpdmVNZXRob2RzLnF1ZXJ5U2VsZWN0b3JBbGwuY2FsbChhLCIqIikpO3MucHVzaChhKTtmb3IobGV0IGw9MDtsPHMubGVuZ3RoO2wrKyl7bGV0IGM9c1tsXTtpZighYmd0KGMsaSkpY29udGludWU7bGV0IHU9ci5jdXJyZW50U2NvcGVGb3JOb2RlKGMpO3UhPT1uJiYodSE9PSIiJiZyLnVuc2NvcGVOb2RlKGMsdSksci5zY29wZU5vZGUoYyxuKSl9fTtpZihvKGUpLHQpe2xldCBhPW5ldyBNdXRhdGlvbk9ic2VydmVyKHM9Pntmb3IobGV0IGw9MDtsPHMubGVuZ3RoO2wrKyl7bGV0IGM9c1tsXTtmb3IobGV0IHU9MDt1PGMuYWRkZWROb2Rlcy5sZW5ndGg7dSsrKXtsZXQgaD1jLmFkZGVkTm9kZXNbdV07aC5ub2RlVHlwZT09PU5vZGUuRUxFTUVOVF9OT0RFJiZvKGgpfX19KTtyZXR1cm4gYS5vYnNlcnZlKGUse2NoaWxkTGlzdDohMCxzdWJ0cmVlOiEwfSksYX1lbHNlIHJldHVybiBudWxsfXZhciBLRz0iZGlzYWJsZS11cGdyYWRlIixaRz1lPT57Zm9yKDtlOyl7bGV0IHQ9T2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcihlLCJvYnNlcnZlZEF0dHJpYnV0ZXMiKTtpZih0KXJldHVybiB0LmdldDtlPU9iamVjdC5nZXRQcm90b3R5cGVPZihlLnByb3RvdHlwZSkuY29uc3RydWN0b3J9cmV0dXJuKCk9PltdfSxndnI9Tm4oZT0+e2xldCB0PVNtKGUpLHI9WkcodCk7Y2xhc3MgbiBleHRlbmRzIHR7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX19pc1VwZ3JhZGVEaXNhYmxlZH1zdGF0aWMgZ2V0IG9ic2VydmVkQXR0cmlidXRlcygpe3JldHVybiByLmNhbGwodGhpcykuY29uY2F0KEtHKX1faW5pdGlhbGl6ZVByb3BlcnRpZXMoKXt0aGlzLmhhc0F0dHJpYnV0ZShLRyk/dGhpcy5fX2lzVXBncmFkZURpc2FibGVkPSEwOnN1cGVyLl9pbml0aWFsaXplUHJvcGVydGllcygpfV9lbmFibGVQcm9wZXJ0aWVzKCl7dGhpcy5fX2lzVXBncmFkZURpc2FibGVkfHxzdXBlci5fZW5hYmxlUHJvcGVydGllcygpfV9jYW5BcHBseVByb3BlcnR5RGVmYXVsdChvKXtyZXR1cm4gc3VwZXIuX2NhbkFwcGx5UHJvcGVydHlEZWZhdWx0KG8pJiYhKHRoaXMuX19pc1VwZ3JhZGVEaXNhYmxlZCYmdGhpcy5faXNQcm9wZXJ0eVBlbmRpbmcobykpfWF0dHJpYnV0ZUNoYW5nZWRDYWxsYmFjayhvLGEscyxsKXtvPT1LRz90aGlzLl9faXNVcGdyYWRlRGlzYWJsZWQmJnM9PW51bGwmJihzdXBlci5faW5pdGlhbGl6ZVByb3BlcnRpZXMoKSx0aGlzLl9faXNVcGdyYWRlRGlzYWJsZWQ9ITEsdWUodGhpcykuaXNDb25uZWN0ZWQmJnN1cGVyLmNvbm5lY3RlZENhbGxiYWNrKCkpOnN1cGVyLmF0dHJpYnV0ZUNoYW5nZWRDYWxsYmFjayhvLGEscyxsKX1jb25uZWN0ZWRDYWxsYmFjaygpe3RoaXMuX19pc1VwZ3JhZGVEaXNhYmxlZHx8c3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKX1kaXNjb25uZWN0ZWRDYWxsYmFjaygpe3RoaXMuX19pc1VwZ3JhZGVEaXNhYmxlZHx8c3VwZXIuZGlzY29ubmVjdGVkQ2FsbGJhY2soKX19cmV0dXJuIG59KTt2YXIgbzk9ImRpc2FibGUtdXBncmFkZSIsSXhlPXdpbmRvdy5TaGFkeUNTUyxHdD1ObihlPT57bGV0IHQ9eWgoU20oZSkpLHI9Vkk/dDp1Z3QodCksbj1aRyhyKSxpPXt4OiJwYW4teCIseToicGFuLXkiLG5vbmU6Im5vbmUiLGFsbDoiYXV0byJ9O2NsYXNzIG8gZXh0ZW5kcyBye2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLmlzQXR0YWNoZWQsdGhpcy5fX2JvdW5kTGlzdGVuZXJzLHRoaXMuX2RlYm91bmNlcnMsdGhpcy5fX2lzVXBncmFkZURpc2FibGVkLHRoaXMuX19uZWVkc0F0dHJpYnV0ZXNBdENvbm5lY3RlZCx0aGlzLl9sZWdhY3lGb3JjZU9ic2VydmVkQXR0cmlidXRlc31zdGF0aWMgZ2V0IGltcG9ydE1ldGEoKXtyZXR1cm4gdGhpcy5wcm90b3R5cGUuaW1wb3J0TWV0YX1jcmVhdGVkKCl7fV9fYXR0cmlidXRlUmVhY3Rpb24ocyxsLGMpeyh0aGlzLl9fZGF0YUF0dHJpYnV0ZXMmJnRoaXMuX19kYXRhQXR0cmlidXRlc1tzXXx8cz09PW85KSYmdGhpcy5hdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2socyxsLGMsbnVsbCl9c2V0QXR0cmlidXRlKHMsbCl7aWYocU0mJiF0aGlzLl9sZWdhY3lGb3JjZU9ic2VydmVkQXR0cmlidXRlcyl7bGV0IGM9dGhpcy5nZXRBdHRyaWJ1dGUocyk7c3VwZXIuc2V0QXR0cmlidXRlKHMsbCksdGhpcy5fX2F0dHJpYnV0ZVJlYWN0aW9uKHMsYyxTdHJpbmcobCkpfWVsc2Ugc3VwZXIuc2V0QXR0cmlidXRlKHMsbCl9cmVtb3ZlQXR0cmlidXRlKHMpe2lmKHFNJiYhdGhpcy5fbGVnYWN5Rm9yY2VPYnNlcnZlZEF0dHJpYnV0ZXMpe2xldCBsPXRoaXMuZ2V0QXR0cmlidXRlKHMpO3N1cGVyLnJlbW92ZUF0dHJpYnV0ZShzKSx0aGlzLl9fYXR0cmlidXRlUmVhY3Rpb24ocyxsLG51bGwpfWVsc2Ugc3VwZXIucmVtb3ZlQXR0cmlidXRlKHMpfXN0YXRpYyBnZXQgb2JzZXJ2ZWRBdHRyaWJ1dGVzKCl7cmV0dXJuIHFNJiYhdGhpcy5wcm90b3R5cGUuX2xlZ2FjeUZvcmNlT2JzZXJ2ZWRBdHRyaWJ1dGVzPyh0aGlzLmhhc093blByb3BlcnR5KEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoIl9fb2JzZXJ2ZWRBdHRyaWJ1dGVzIix0aGlzKSl8fCh0aGlzLl9fb2JzZXJ2ZWRBdHRyaWJ1dGVzPVtdLEhJKHRoaXMucHJvdG90eXBlKSksdGhpcy5fX29ic2VydmVkQXR0cmlidXRlcyk6bi5jYWxsKHRoaXMpLmNvbmNhdChvOSl9X2VuYWJsZVByb3BlcnRpZXMoKXt0aGlzLl9faXNVcGdyYWRlRGlzYWJsZWR8fHN1cGVyLl9lbmFibGVQcm9wZXJ0aWVzKCl9X2NhbkFwcGx5UHJvcGVydHlEZWZhdWx0KHMpe3JldHVybiBzdXBlci5fY2FuQXBwbHlQcm9wZXJ0eURlZmF1bHQocykmJiEodGhpcy5fX2lzVXBncmFkZURpc2FibGVkJiZ0aGlzLl9pc1Byb3BlcnR5UGVuZGluZyhzKSl9Y29ubmVjdGVkQ2FsbGJhY2soKXt0aGlzLl9fbmVlZHNBdHRyaWJ1dGVzQXRDb25uZWN0ZWQmJnRoaXMuX3Rha2VBdHRyaWJ1dGVzKCksdGhpcy5fX2lzVXBncmFkZURpc2FibGVkfHwoc3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLmlzQXR0YWNoZWQ9ITAsdGhpcy5hdHRhY2hlZCgpKX1hdHRhY2hlZCgpe31kaXNjb25uZWN0ZWRDYWxsYmFjaygpe3RoaXMuX19pc1VwZ3JhZGVEaXNhYmxlZHx8KHN1cGVyLmRpc2Nvbm5lY3RlZENhbGxiYWNrKCksdGhpcy5pc0F0dGFjaGVkPSExLHRoaXMuZGV0YWNoZWQoKSl9ZGV0YWNoZWQoKXt9YXR0cmlidXRlQ2hhbmdlZENhbGxiYWNrKHMsbCxjLHUpe2whPT1jJiYocz09bzk/dGhpcy5fX2lzVXBncmFkZURpc2FibGVkJiZjPT1udWxsJiYodGhpcy5faW5pdGlhbGl6ZVByb3BlcnRpZXMoKSx0aGlzLl9faXNVcGdyYWRlRGlzYWJsZWQ9ITEsdWUodGhpcykuaXNDb25uZWN0ZWQmJnRoaXMuY29ubmVjdGVkQ2FsbGJhY2soKSk6KHN1cGVyLmF0dHJpYnV0ZUNoYW5nZWRDYWxsYmFjayhzLGwsYyx1KSx0aGlzLmF0dHJpYnV0ZUNoYW5nZWQocyxsLGMpKSl9YXR0cmlidXRlQ2hhbmdlZChzLGwsYyl7fV9pbml0aWFsaXplUHJvcGVydGllcygpe2lmKHBwJiZ0aGlzLmhhc0F0dHJpYnV0ZShvOSkpdGhpcy5fX2lzVXBncmFkZURpc2FibGVkPSEwO2Vsc2V7bGV0IHM9T2JqZWN0LmdldFByb3RvdHlwZU9mKHRoaXMpO3MuaGFzT3duUHJvcGVydHkoSlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiX19oYXNSZWdpc3RlckZpbmlzaGVkIixzKSl8fCh0aGlzLl9yZWdpc3RlcmVkKCkscy5fX2hhc1JlZ2lzdGVyRmluaXNoZWQ9ITApLHN1cGVyLl9pbml0aWFsaXplUHJvcGVydGllcygpLHRoaXMucm9vdD10aGlzLHRoaXMuY3JlYXRlZCgpLHFNJiYhdGhpcy5fbGVnYWN5Rm9yY2VPYnNlcnZlZEF0dHJpYnV0ZXMmJih0aGlzLmhhc0F0dHJpYnV0ZXMoKT90aGlzLl90YWtlQXR0cmlidXRlcygpOnRoaXMucGFyZW50Tm9kZXx8KHRoaXMuX19uZWVkc0F0dHJpYnV0ZXNBdENvbm5lY3RlZD0hMCkpLHRoaXMuX2FwcGx5TGlzdGVuZXJzKCl9fV90YWtlQXR0cmlidXRlcygpe2xldCBzPXRoaXMuYXR0cmlidXRlcztmb3IobGV0IGw9MCxjPXMubGVuZ3RoO2w8YztsKyspe2xldCB1PXNbbF07dGhpcy5fX2F0dHJpYnV0ZVJlYWN0aW9uKHUubmFtZSxudWxsLHUudmFsdWUpfX1fcmVnaXN0ZXJlZCgpe31yZWFkeSgpe3RoaXMuX2Vuc3VyZUF0dHJpYnV0ZXMoKSxzdXBlci5yZWFkeSgpfV9lbnN1cmVBdHRyaWJ1dGVzKCl7fV9hcHBseUxpc3RlbmVycygpe31zZXJpYWxpemUocyl7cmV0dXJuIHRoaXMuX3NlcmlhbGl6ZVZhbHVlKHMpfWRlc2VyaWFsaXplKHMsbCl7cmV0dXJuIHRoaXMuX2Rlc2VyaWFsaXplVmFsdWUocyxsKX1yZWZsZWN0UHJvcGVydHlUb0F0dHJpYnV0ZShzLGwsYyl7dGhpcy5fcHJvcGVydHlUb0F0dHJpYnV0ZShzLGwsYyl9c2VyaWFsaXplVmFsdWVUb0F0dHJpYnV0ZShzLGwsYyl7dGhpcy5fdmFsdWVUb05vZGVBdHRyaWJ1dGUoY3x8dGhpcyxzLGwpfWV4dGVuZChzLGwpe2lmKCEocyYmbCkpcmV0dXJuIHN8fGw7bGV0IGM9T2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMobCk7Zm9yKGxldCB1PTAsaDt1PGMubGVuZ3RoJiYoaD1jW3VdKTt1Kyspe2xldCBmPU9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IobCxoKTtmJiZPYmplY3QuZGVmaW5lUHJvcGVydHkocyxoLGYpfXJldHVybiBzfW1peGluKHMsbCl7Zm9yKGxldCBjIGluIGwpc1tjXT1sW2NdO3JldHVybiBzfWNoYWluT2JqZWN0KHMsbCl7cmV0dXJuIHMmJmwmJnMhPT1sJiYocy5fX3Byb3RvX189bCksc31pbnN0YW5jZVRlbXBsYXRlKHMpe2xldCBsPXRoaXMuY29uc3RydWN0b3IuX2NvbnRlbnRGb3JUZW1wbGF0ZShzKTtyZXR1cm4gZG9jdW1lbnQuaW1wb3J0Tm9kZShsLCEwKX1maXJlKHMsbCxjKXtjPWN8fHt9LGw9bD09bnVsbD97fTpsO2xldCB1PW5ldyBFdmVudChzLHtidWJibGVzOmMuYnViYmxlcz09PXZvaWQgMD8hMDpjLmJ1YmJsZXMsY2FuY2VsYWJsZTpCb29sZWFuKGMuY2FuY2VsYWJsZSksY29tcG9zZWQ6Yy5jb21wb3NlZD09PXZvaWQgMD8hMDpjLmNvbXBvc2VkfSk7dS5kZXRhaWw9bDtsZXQgaD1jLm5vZGV8fHRoaXM7cmV0dXJuIHVlKGgpLmRpc3BhdGNoRXZlbnQodSksdX1saXN0ZW4ocyxsLGMpe3M9c3x8dGhpcztsZXQgdT10aGlzLl9fYm91bmRMaXN0ZW5lcnN8fCh0aGlzLl9fYm91bmRMaXN0ZW5lcnM9bmV3IFdlYWtNYXApLGg9dS5nZXQocyk7aHx8KGg9e30sdS5zZXQocyxoKSk7bGV0IGY9bCtjO2hbZl18fChoW2ZdPXRoaXMuX2FkZE1ldGhvZEV2ZW50TGlzdGVuZXJUb05vZGUocyxsLGMsdGhpcykpfXVubGlzdGVuKHMsbCxjKXtzPXN8fHRoaXM7bGV0IHU9dGhpcy5fX2JvdW5kTGlzdGVuZXJzJiZ0aGlzLl9fYm91bmRMaXN0ZW5lcnMuZ2V0KHMpLGg9bCtjLGY9dSYmdVtoXTtmJiYodGhpcy5fcmVtb3ZlRXZlbnRMaXN0ZW5lckZyb21Ob2RlKHMsbCxmKSx1W2hdPW51bGwpfXNldFNjcm9sbERpcmVjdGlvbihzLGwpe2RfKGx8fHRoaXMsaVtzXXx8ImF1dG8iKX0kJChzKXtyZXR1cm4gdGhpcy5yb290LnF1ZXJ5U2VsZWN0b3Iocyl9Z2V0IGRvbUhvc3QoKXtsZXQgcz11ZSh0aGlzKS5nZXRSb290Tm9kZSgpO3JldHVybiBzIGluc3RhbmNlb2YgRG9jdW1lbnRGcmFnbWVudD9zLmhvc3Q6c31kaXN0cmlidXRlQ29udGVudCgpe2xldCBsPXp0KHRoaXMpO3dpbmRvdy5TaGFkeURPTSYmbC5zaGFkb3dSb290JiZTaGFkeURPTS5mbHVzaCgpfWdldEVmZmVjdGl2ZUNoaWxkTm9kZXMoKXtyZXR1cm4genQodGhpcykuZ2V0RWZmZWN0aXZlQ2hpbGROb2RlcygpfXF1ZXJ5RGlzdHJpYnV0ZWRFbGVtZW50cyhzKXtyZXR1cm4genQodGhpcykucXVlcnlEaXN0cmlidXRlZEVsZW1lbnRzKHMpfWdldEVmZmVjdGl2ZUNoaWxkcmVuKCl7cmV0dXJuIHRoaXMuZ2V0RWZmZWN0aXZlQ2hpbGROb2RlcygpLmZpbHRlcihmdW5jdGlvbihsKXtyZXR1cm4gbC5ub2RlVHlwZT09PU5vZGUuRUxFTUVOVF9OT0RFfSl9Z2V0RWZmZWN0aXZlVGV4dENvbnRlbnQoKXtsZXQgcz10aGlzLmdldEVmZmVjdGl2ZUNoaWxkTm9kZXMoKSxsPVtdO2ZvcihsZXQgYz0wLHU7dT1zW2NdO2MrKyl1Lm5vZGVUeXBlIT09Tm9kZS5DT01NRU5UX05PREUmJmwucHVzaCh1LnRleHRDb250ZW50KTtyZXR1cm4gbC5qb2luKCIiKX1xdWVyeUVmZmVjdGl2ZUNoaWxkcmVuKHMpe2xldCBsPXRoaXMucXVlcnlEaXN0cmlidXRlZEVsZW1lbnRzKHMpO3JldHVybiBsJiZsWzBdfXF1ZXJ5QWxsRWZmZWN0aXZlQ2hpbGRyZW4ocyl7cmV0dXJuIHRoaXMucXVlcnlEaXN0cmlidXRlZEVsZW1lbnRzKHMpfWdldENvbnRlbnRDaGlsZE5vZGVzKHMpe2xldCBsPXRoaXMucm9vdC5xdWVyeVNlbGVjdG9yKHN8fCJzbG90Iik7cmV0dXJuIGw/enQobCkuZ2V0RGlzdHJpYnV0ZWROb2RlcygpOltdfWdldENvbnRlbnRDaGlsZHJlbihzKXtyZXR1cm4gdGhpcy5nZXRDb250ZW50Q2hpbGROb2RlcyhzKS5maWx0ZXIoZnVuY3Rpb24oYyl7cmV0dXJuIGMubm9kZVR5cGU9PT1Ob2RlLkVMRU1FTlRfTk9ERX0pfWlzTGlnaHREZXNjZW5kYW50KHMpe2xldCBsPXRoaXM7cmV0dXJuIGwhPT1zJiZ1ZShsKS5jb250YWlucyhzKSYmdWUobCkuZ2V0Um9vdE5vZGUoKT09PXVlKHMpLmdldFJvb3ROb2RlKCl9aXNMb2NhbERlc2NlbmRhbnQocyl7cmV0dXJuIHRoaXMucm9vdD09PXVlKHMpLmdldFJvb3ROb2RlKCl9c2NvcGVTdWJ0cmVlKHMsbD0hMSl7cmV0dXJuIHdndChzLGwpfWdldENvbXB1dGVkU3R5bGVWYWx1ZShzKXtyZXR1cm4gSXhlLmdldENvbXB1dGVkU3R5bGVWYWx1ZSh0aGlzLHMpfWRlYm91bmNlKHMsbCxjKXtyZXR1cm4gdGhpcy5fZGVib3VuY2Vycz10aGlzLl9kZWJvdW5jZXJzfHx7fSx0aGlzLl9kZWJvdW5jZXJzW3NdPXNyLmRlYm91bmNlKHRoaXMuX2RlYm91bmNlcnNbc10sYz4wP21vLmFmdGVyKGMpOmNpLGwuYmluZCh0aGlzKSl9aXNEZWJvdW5jZXJBY3RpdmUocyl7dGhpcy5fZGVib3VuY2Vycz10aGlzLl9kZWJvdW5jZXJzfHx7fTtsZXQgbD10aGlzLl9kZWJvdW5jZXJzW3NdO3JldHVybiEhKGwmJmwuaXNBY3RpdmUoKSl9Zmx1c2hEZWJvdW5jZXIocyl7dGhpcy5fZGVib3VuY2Vycz10aGlzLl9kZWJvdW5jZXJzfHx7fTtsZXQgbD10aGlzLl9kZWJvdW5jZXJzW3NdO2wmJmwuZmx1c2goKX1jYW5jZWxEZWJvdW5jZXIocyl7dGhpcy5fZGVib3VuY2Vycz10aGlzLl9kZWJvdW5jZXJzfHx7fTtsZXQgbD10aGlzLl9kZWJvdW5jZXJzW3NdO2wmJmwuY2FuY2VsKCl9YXN5bmMocyxsKXtyZXR1cm4gbD4wP21vLnJ1bihzLmJpbmQodGhpcyksbCk6fmNpLnJ1bihzLmJpbmQodGhpcykpfWNhbmNlbEFzeW5jKHMpe3M8MD9jaS5jYW5jZWwofnMpOm1vLmNhbmNlbChzKX1jcmVhdGUocyxsKXtsZXQgYz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KHMpO2lmKGwpaWYoYy5zZXRQcm9wZXJ0aWVzKWMuc2V0UHJvcGVydGllcyhsKTtlbHNlIGZvcihsZXQgdSBpbiBsKWNbdV09bFt1XTtyZXR1cm4gY31lbGVtZW50TWF0Y2hlcyhzLGwpe3JldHVybiBYRyhsfHx0aGlzLHMpfXRvZ2dsZUF0dHJpYnV0ZShzLGwpe2xldCBjPXRoaXM7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0zJiYoYz1hcmd1bWVudHNbMl0pLGFyZ3VtZW50cy5sZW5ndGg9PTEmJihsPSFjLmhhc0F0dHJpYnV0ZShzKSksbD8odWUoYykuc2V0QXR0cmlidXRlKHMsIiIpLCEwKToodWUoYykucmVtb3ZlQXR0cmlidXRlKHMpLCExKX10b2dnbGVDbGFzcyhzLGwsYyl7Yz1jfHx0aGlzLGFyZ3VtZW50cy5sZW5ndGg9PTEmJihsPSFjLmNsYXNzTGlzdC5jb250YWlucyhzKSksbD9jLmNsYXNzTGlzdC5hZGQocyk6Yy5jbGFzc0xpc3QucmVtb3ZlKHMpfXRyYW5zZm9ybShzLGwpe2w9bHx8dGhpcyxsLnN0eWxlLndlYmtpdFRyYW5zZm9ybT1zLGwuc3R5bGUudHJhbnNmb3JtPXN9dHJhbnNsYXRlM2QocyxsLGMsdSl7dT11fHx0aGlzLHRoaXMudHJhbnNmb3JtKCJ0cmFuc2xhdGUzZCgiK3MrIiwiK2wrIiwiK2MrIikiLHUpfWFycmF5RGVsZXRlKHMsbCl7bGV0IGM7aWYoQXJyYXkuaXNBcnJheShzKSl7aWYoYz1zLmluZGV4T2YobCksYz49MClyZXR1cm4gcy5zcGxpY2UoYywxKX1lbHNlIGlmKGM9Tm8odGhpcyxzKS5pbmRleE9mKGwpLGM+PTApcmV0dXJuIHRoaXMuc3BsaWNlKHMsYywxKTtyZXR1cm4gbnVsbH1fbG9nZ2VyKHMsbCl7c3dpdGNoKEFycmF5LmlzQXJyYXkobCkmJmwubGVuZ3RoPT09MSYmQXJyYXkuaXNBcnJheShsWzBdKSYmKGw9bFswXSkscyl7Y2FzZSJsb2ciOmNhc2Uid2FybiI6Y2FzZSJlcnJvciI6Y29uc29sZVtzXSguLi5sKX19X2xvZyguLi5zKXt0aGlzLl9sb2dnZXIoImxvZyIscyl9X3dhcm4oLi4ucyl7dGhpcy5fbG9nZ2VyKCJ3YXJuIixzKX1fZXJyb3IoLi4ucyl7dGhpcy5fbG9nZ2VyKCJlcnJvciIscyl9X2xvZ2YocywuLi5sKXtyZXR1cm5bIlslczo6JXNdIix0aGlzLmlzLHMsLi4ubF19fXJldHVybiBvLnByb3RvdHlwZS5pcz0iIixvfSk7dmFyIENtPUVlKE9lKCksMSk7ZnVuY3Rpb24geGgoZSx0KXtsZXQgcj0wLG49MDtmb3IoOzspe2lmKHI9PT1lLmxlbmd0aClyZXR1cm4gbj09PXQubGVuZ3RoPzA6LTE7aWYobj09PXQubGVuZ3RoKXJldHVybiAxO2lmKG1fKGVbcl0pJiZtXyh0W25dKSl7bGV0IGk9cixvPW47cj1TZ3QoZSxyKzEpLG49U2d0KHQsbisxKTtsZXQgYT1wYXJzZUZsb2F0KGUuc2xpY2UoaSxyKSkscz1wYXJzZUZsb2F0KHQuc2xpY2UobyxuKSk7aWYoYTxzKXJldHVybi0xO2lmKGE+cylyZXR1cm4gMTtjb250aW51ZX1pZihKRyhlW3JdKSl7aWYoIUpHKHRbbl0pKXJldHVybi0xfWVsc2V7aWYoSkcodFtuXSkpcmV0dXJuIDE7aWYoZVtyXTx0W25dKXJldHVybi0xO2lmKGVbcl0+dFtuXSlyZXR1cm4gMX1yKyssbisrfX1mdW5jdGlvbiBTZ3QoZSx0KXtsZXQgcjsoZnVuY3Rpb24oaSl7aVtpLk5BVFVSQUw9MF09Ik5BVFVSQUwiLGlbaS5SRUFMPTFdPSJSRUFMIixpW2kuRVhQT05FTlRfU0lHTj0yXT0iRVhQT05FTlRfU0lHTiIsaVtpLkVYUE9ORU5UPTNdPSJFWFBPTkVOVCJ9KShyfHwocj17fSkpO2xldCBuPXIuTkFUVVJBTDtmb3IoO3Q8ZS5sZW5ndGg7dCsrKWlmKG49PT1yLk5BVFVSQUwpe2lmKGVbdF09PT0iLiIpbj1yLlJFQUw7ZWxzZSBpZihlW3RdPT09ImUifHxlW3RdPT09IkUiKW49ci5FWFBPTkVOVF9TSUdOO2Vsc2UgaWYoIW1fKGVbdF0pKWJyZWFrfWVsc2UgaWYobj09PXIuUkVBTCl7aWYoZVt0XT09PSJlInx8ZVt0XT09PSJFIiluPXIuRVhQT05FTlRfU0lHTjtlbHNlIGlmKCFtXyhlW3RdKSlicmVha31lbHNlIGlmKG49PT1yLkVYUE9ORU5UX1NJR04paWYobV8oZVt0XSl8fGVbdF09PT0iKyJ8fGVbdF09PT0iLSIpbj1yLkVYUE9ORU5UO2Vsc2UgYnJlYWs7ZWxzZSBpZihuPT09ci5FWFBPTkVOVCYmIW1fKGVbdF0pKWJyZWFrO3JldHVybiB0fWZ1bmN0aW9uIG1fKGUpe3JldHVybiIwIjw9ZSYmZTw9IjkifWZ1bmN0aW9uIEpHKGUpe3JldHVybiBlPT09Ii8ifHxlPT09Il8ifHxtXyhlKX12YXIgTHhlPVtdO2Z1bmN0aW9uIGt4ZShlKXtyZXR1cm4gQ20ua2V5cyhlKS5zb3J0KHhoKX1mdW5jdGlvbiAkaShlKXtyZXR1cm4gQ20udW5pb24uYXBwbHkobnVsbCxDbS52YWx1ZXMoZSkpLnNvcnQoeGgpfWZ1bmN0aW9uIFJ4ZShlLHQpe2xldCByPVtdO3JldHVybiB0LmZvckVhY2gobj0+cj1yLmNvbmNhdChlW25dKSksQ20udW5pcShyKS5zb3J0KHhoKX12YXIgRWd0PSJYLVRlbnNvckJvYXJkLUZlYXR1cmUtRmxhZ3MiO3ZhciB0Vz17fTtLcyh0Vyx7Z2V0RmVhdHVyZUZsYWdzOigpPT5EeGUsZ2V0RmVhdHVyZUZsYWdzVG9TZW5kVG9TZXJ2ZXI6KCk9PlFHLGluaXRpYWxpemVGZWF0dXJlRmxhZ3M6KCk9PlRndCxzZXRGZWF0dXJlRmxhZ3M6KCk9Pk54ZX0pO3ZhciBhOSxzOTtUZ3QoKTtmdW5jdGlvbiBUZ3QoKXthOT1udWxsLHM5PW51bGx9ZnVuY3Rpb24gTnhlKGUsdCl7YTk9ZSxzOT10fWZ1bmN0aW9uIER4ZSgpe2lmKGE5PT09bnVsbCl0aHJvdyBFcnJvcigiRmVhdHVyZUZsYWdzIGhhdmUgbm90IHlldCBiZWVuIGRldGVybWluZWQgYnkgVGVuc29yQm9hcmQuIik7cmV0dXJuIGE5fWZ1bmN0aW9uIFFHKCl7aWYoczk9PT1udWxsKXRocm93IEVycm9yKCJGZWF0dXJlRmxhZ3MgaGF2ZSBub3QgeWV0IGJlZW4gZGV0ZXJtaW5lZCBieSBUZW5zb3JCb2FyZC4iKTtyZXR1cm4gczl9dmFyIGw5PWNsYXNzIGV4dGVuZHMgRXJyb3J7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMubmFtZT0iUmVxdWVzdENhbmNlbGxhdGlvbkVycm9yIn19LFZ4PWNsYXNzIGV4dGVuZHMgRXJyb3J7Y29uc3RydWN0b3IodCl7c3VwZXIodCksdGhpcy5uYW1lPSJJbnZhbGlkUmVxdWVzdE9wdGlvbnNFcnJvciIsT2JqZWN0LnNldFByb3RvdHlwZU9mKHRoaXMsVngucHJvdG90eXBlKX19LGNFPWNsYXNzIGV4dGVuZHMgRXJyb3J7Y29uc3RydWN0b3IodCxyKXtzdXBlcigpLHRoaXMubWVzc2FnZT1gUmVxdWVzdE5ldHdvcmtFcnJvcjogJHt0LnN0YXR1c30gYXQgJHtyfWAsdGhpcy5uYW1lPSJSZXF1ZXN0TmV0d29ya0Vycm9yIix0aGlzLnJlcT10LHRoaXMudXJsPXJ9fSxBbTsoZnVuY3Rpb24oZSl7ZS5HRVQ9IkdFVCIsZS5QT1NUPSJQT1NUIn0pKEFtfHwoQW09e30pKTt2YXIgVXg9Y2xhc3N7dmFsaWRhdGUoKXtpZih0aGlzLm1ldGhvZFR5cGU9PT1BbS5HRVQmJnRoaXMuYm9keSl0aHJvdyBuZXcgVngoImJvZHkgbXVzdCBiZSBtaXNzaW5nIGZvciBhIEdFVCByZXF1ZXN0LiIpfX0sQWU9Y2xhc3N7Y29uc3RydWN0b3IodD0xZTMscj0zKXt0aGlzLl9xdWV1ZT1bXSx0aGlzLl9uQWN0aXZlUmVxdWVzdHM9MCx0aGlzLl9uU2ltdWx0YW5lb3VzUmVxdWVzdHM9dCx0aGlzLl9tYXhSZXRyaWVzPXJ9cmVxdWVzdCh0LHIpe2xldCBuPXp4ZShyKTtyZXR1cm4gdGhpcy5yZXF1ZXN0V2l0aE9wdGlvbnModCxuKX1yZXF1ZXN0V2l0aE9wdGlvbnModCxyKXtyZXR1cm4gci52YWxpZGF0ZSgpLG5ldyBQcm9taXNlKChpLG8pPT57bGV0IGE9e3Jlc29sdmU6aSxyZWplY3Q6b307dGhpcy5fcXVldWUucHVzaChhKSx0aGlzLmxhdW5jaFJlcXVlc3RzKCl9KS50aGVuKCgpPT50aGlzLnByb21pc2VXaXRoUmV0cmllcyh0LHRoaXMuX21heFJldHJpZXMscikpLnRoZW4oaT0+KHRoaXMuX25BY3RpdmVSZXF1ZXN0cy0tLHRoaXMubGF1bmNoUmVxdWVzdHMoKSxpKSxpPT4oaS5uYW1lPT09IlJlcXVlc3ROZXR3b3JrRXJyb3IiJiYodGhpcy5fbkFjdGl2ZVJlcXVlc3RzLS0sdGhpcy5sYXVuY2hSZXF1ZXN0cygpKSxQcm9taXNlLnJlamVjdChpKSkpfWZldGNoKHQscil7cmV0dXJuIG5ldyBQcm9taXNlKChuLGkpPT57bGV0IG89e3Jlc29sdmU6bixyZWplY3Q6aX07dGhpcy5fcXVldWUucHVzaChvKSx0aGlzLmxhdW5jaFJlcXVlc3RzKCl9KS50aGVuKCgpPT57bGV0IG49MTtyZXR1cm4gbmV3IFByb21pc2UoaT0+e2xldCBvPSgpPT57ZmV0Y2godCxyKS50aGVuKGE9PntpZighYS5vayYmdGhpcy5fbWF4UmV0cmllcz5uKXtuKyssbygpO3JldHVybn1pKGEpLHRoaXMuX25BY3RpdmVSZXF1ZXN0cy0tLHRoaXMubGF1bmNoUmVxdWVzdHMoKX0pfTtvKCl9KX0pfWNsZWFyUXVldWUoKXt2YXIgdDtmb3IoO3RoaXMuX3F1ZXVlLmxlbmd0aD4wOykodD10aGlzLl9xdWV1ZS5wb3AoKSk9PW51bGx8fHQucmVqZWN0KG5ldyBsOSgiUmVxdWVzdCBjYW5jZWxsZWQgYnkgY2xlYXJRdWV1ZSIpKX1hY3RpdmVSZXF1ZXN0cygpe3JldHVybiB0aGlzLl9uQWN0aXZlUmVxdWVzdHN9b3V0c3RhbmRpbmdSZXF1ZXN0cygpe3JldHVybiB0aGlzLl9uQWN0aXZlUmVxdWVzdHMrdGhpcy5fcXVldWUubGVuZ3RofWxhdW5jaFJlcXVlc3RzKCl7Zm9yKDt0aGlzLl9uQWN0aXZlUmVxdWVzdHM8dGhpcy5fblNpbXVsdGFuZW91c1JlcXVlc3RzJiZ0aGlzLl9xdWV1ZS5sZW5ndGg+MDspdGhpcy5fbkFjdGl2ZVJlcXVlc3RzKyssdGhpcy5fcXVldWUucG9wKCkucmVzb2x2ZSh2b2lkIDApfXByb21pc2VXaXRoUmV0cmllcyh0LHIsbil7dmFyIGk9YT0+YSxvPWE9PnI+MD90aGlzLnByb21pc2VXaXRoUmV0cmllcyh0LHItMSxuKTpQcm9taXNlLnJlamVjdChhKTtyZXR1cm4gdGhpcy5fcHJvbWlzZUZyb21VcmwodCxuKS50aGVuKGksbyl9X3Byb21pc2VGcm9tVXJsKHQscil7cmV0dXJuIG5ldyBQcm9taXNlKChuLGkpPT57bGV0IG89T3hlKHIubWV0aG9kVHlwZSx0LHIud2l0aENyZWRlbnRpYWxzLHIuY29udGVudFR5cGUpO28uc2V0UmVxdWVzdEhlYWRlcihFZ3QsSlNPTi5zdHJpbmdpZnkoUUcoKSkpLG8ub25sb2FkPWZ1bmN0aW9uKCl7by5zdGF0dXM9PT0yMDA/bihKU09OLnBhcnNlKG8ucmVzcG9uc2VUZXh0KSk6aShuZXcgY0Uobyx0KSl9LG8ub25lcnJvcj1mdW5jdGlvbigpe2kobmV3IGNFKG8sdCkpfSxyLmJvZHk/by5zZW5kKHIuYm9keSk6by5zZW5kKCl9KX19O2Z1bmN0aW9uIE94ZShlLHQscixuKXtsZXQgaT1uZXcgWE1MSHR0cFJlcXVlc3Q7cmV0dXJuIGkub3BlbihlLHQpLHImJihpLndpdGhDcmVkZW50aWFscz1yKSxuJiZpLnNldFJlcXVlc3RIZWFkZXIoIkNvbnRlbnQtVHlwZSIsbiksaX1mdW5jdGlvbiB6eGUoZSl7bGV0IHQ9bmV3IFV4O3JldHVybiBlPyh0Lm1ldGhvZFR5cGU9QW0uUE9TVCx0LmJvZHk9RnhlKGUpLHQpOih0Lm1ldGhvZFR5cGU9QW0uR0VULHQpfWZ1bmN0aW9uIEZ4ZShlKXtsZXQgdD1uZXcgRm9ybURhdGE7Zm9yKGxldFtyLG5db2YgT2JqZWN0LmVudHJpZXMoZSkpe2xldCBpPUFycmF5LmlzQXJyYXkobik/bjpbbl07Zm9yKGxldCBvIG9mIGkpdC5hcHBlbmQocixvKX1yZXR1cm4gdH12YXIgQ2d0PSJleHBlcmltZW50YWxQbHVnaW4iLEJ4ZT1uZXcgVVJMU2VhcmNoUGFyYW1zKHdpbmRvdy5sb2NhdGlvbi5zZWFyY2gpLEFndD1QZ3QoKTtmdW5jdGlvbiBQZ3QoZT0iZGF0YSIsdD1CeGUpe3JldHVybiBlW2UubGVuZ3RoLTFdPT09Ii8iJiYoZT1lLnNsaWNlKDAsZS5sZW5ndGgtMSkpLHtlbnZpcm9ubWVudDooKT0+cXgoZSwiL2Vudmlyb25tZW50IiksZXhwZXJpbWVudHM6KCk9PnF4KGUsIi9leHBlcmltZW50cyIpLHBsdWdpblJvdXRlOihyLG4saSk9PnF4KGUrIi9wbHVnaW4iLGAvJHtyfSR7bn1gLGkpLHBsdWdpbnNMaXN0aW5nOigpPT5xeChlLCIvcGx1Z2luc19saXN0aW5nIixlVyh7W0NndF06dC5nZXRBbGwoQ2d0KX0pKSxydW5zOigpPT5xeChlLCIvcnVucyIpLHJ1bnNGb3JFeHBlcmltZW50OnI9PnF4KGUsIi9leHBlcmltZW50X3J1bnMiLGVXKHtleHBlcmltZW50OlN0cmluZyhyKX0pKX19ZnVuY3Rpb24gdmUoKXtyZXR1cm4gQWd0fWZ1bmN0aW9uIEh4ZShlKXtpZihlPT1udWxsKXRocm93IG5ldyBFcnJvcigiUm91dGVyIHJlcXVpcmVkLCBidXQgZ290OiAiK2UpO0FndD1lfWZ1bmN0aW9uIHF4KGUsdCxyPW5ldyBVUkxTZWFyY2hQYXJhbXMpe2xldCBuPWUrdDtyZXR1cm4gU3RyaW5nKHIpJiYobis9KHQuaW5jbHVkZXMoIj8iKT8iJiI6Ij8iKStTdHJpbmcocikpLG59ZnVuY3Rpb24gZVcoZT17fSl7bGV0IHQ9T2JqZWN0LmtleXMoZSkuc29ydCgpLmZpbHRlcihuPT5lW25dKSxyPW5ldyBVUkxTZWFyY2hQYXJhbXM7cmV0dXJuIHQuZm9yRWFjaChuPT57bGV0IGk9ZVtuXTsoQXJyYXkuaXNBcnJheShpKT9pOltpXSkuZm9yRWFjaChhPT5yLmFwcGVuZChuLGEpKX0pLHJ9dmFyIGM5PUVlKE9lKCksMSk7dmFyIE5hOyhmdW5jdGlvbihlKXtlW2UuU0VBUkNIX1JFU1VMVFM9MF09IlNFQVJDSF9SRVNVTFRTIixlW2UuUFJFRklYX0dST1VQPTFdPSJQUkVGSVhfR1JPVVAifSkoTmF8fChOYT17fSkpO2Z1bmN0aW9uIFZ4ZShlLHQpe2xldCByPSgoKT0+e3RyeXtyZXR1cm4gbmV3IFJlZ0V4cCh0KX1jYXRjaChuKXtyZXR1cm4gbnVsbH19KSgpO3JldHVybntuYW1lOnQsbWV0YWRhdGE6e3R5cGU6TmEuU0VBUkNIX1JFU1VMVFMsdmFsaWRSZWdleDohIXIsdW5pdmVyc2FsUmVnZXg6dD09PSIuKiJ9LGl0ZW1zOnI/ZS5maWx0ZXIobj0+bi5tYXRjaChyKSk6W119fWZ1bmN0aW9uIFV4ZShlLHQ9Ii8iKXtsZXQgcj1bXSxuPXt9O3JldHVybiBlLmZvckVhY2goaT0+e2xldCBvPWkuaW5kZXhPZih0KSxhPW8+PTA/aS5zbGljZSgwLG8pOmk7aWYoIW5bYV0pe2xldCBzPXtuYW1lOmEsbWV0YWRhdGE6e3R5cGU6TmEuUFJFRklYX0dST1VQfSxpdGVtczpbXX07blthXT1zLHIucHVzaChzKX1uW2FdLml0ZW1zLnB1c2goaSl9KSxyfWZ1bmN0aW9uIHF4ZShlLHQ9IiIpe2xldCByPVtWeGUoZSx0KV0sbj1VeGUoZSk7cmV0dXJuIEFycmF5KCkuY29uY2F0KHIsbil9ZnVuY3Rpb24gdUUoZSx0LHIpe2xldCBuPSRpKGUpLGk9cXhlKG4sciksbz1HeGUoYzkucGljayhlLHQpKTtyZXR1cm4gaS5tYXAoKHtuYW1lOmEsbWV0YWRhdGE6cyxpdGVtczpsfSk9Pih7bmFtZTphLG1ldGFkYXRhOnMsaXRlbXM6bC5tYXAoYz0+KHt0YWc6YyxydW5zOihvLmdldChjKXx8W10pLnNsaWNlKCl9KSl9KSl9ZnVuY3Rpb24gR3hlKGUpe2xldCB0PW5ldyBNYXA7cmV0dXJuIE9iamVjdC5rZXlzKGUpLmZvckVhY2gocj0+e2Vbcl0uZm9yRWFjaChuPT57bGV0IGk9dC5nZXQobil8fFtdO2kucHVzaChyKSx0LnNldChuLGkpfSl9KSx0fWZ1bmN0aW9uIFd4ZShlLHQpe2xldCByPXhoKGUudGFnLHQudGFnKTtyZXR1cm4gciE9MD9yOnhoKGUucnVuLHQucnVuKX1mdW5jdGlvbiBRbChlLHQscil7bGV0IG49dUUoZSx0LHIpO2Z1bmN0aW9uIGkobyl7bGV0IGE9YzkuZmxhdHRlbihvLml0ZW1zLm1hcCgoe3RhZzpzLHJ1bnM6bH0pPT5sLm1hcChjPT4oe3RhZzpzLHJ1bjpjfSkpKSk7cmV0dXJuIGEuc29ydChXeGUpLHtuYW1lOm8ubmFtZSxtZXRhZGF0YTpvLm1ldGFkYXRhLGl0ZW1zOmF9fXJldHVybiBuLm1hcChpKX12YXIgdVc9e307S3ModVcse0lyb25SZXNpemFibGVCZWhhdmlvcjooKT0+SnN9KTt2YXIgWXhlPXthdHRhY2hlZDohMCxkZXRhY2hlZDohMCxyZWFkeTohMCxjcmVhdGVkOiEwLGJlZm9yZVJlZ2lzdGVyOiEwLHJlZ2lzdGVyZWQ6ITAsYXR0cmlidXRlQ2hhbmdlZDohMCxsaXN0ZW5lcnM6ITAsaG9zdEF0dHJpYnV0ZXM6ITB9LFJndD17YXR0YWNoZWQ6ITAsZGV0YWNoZWQ6ITAscmVhZHk6ITAsY3JlYXRlZDohMCxiZWZvcmVSZWdpc3RlcjohMCxyZWdpc3RlcmVkOiEwLGF0dHJpYnV0ZUNoYW5nZWQ6ITAsYmVoYXZpb3JzOiEwLF9ub0FjY2Vzc29yczohMH0sanhlPU9iamVjdC5hc3NpZ24oe2xpc3RlbmVyczohMCxob3N0QXR0cmlidXRlczohMCxwcm9wZXJ0aWVzOiEwLG9ic2VydmVyczohMH0sUmd0KTtmdW5jdGlvbiBYeGUoZSx0LHIpe2xldCBuPWUuX25vQWNjZXNzb3JzLGk9T2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMoZSk7Zm9yKGxldCBvPTA7bzxpLmxlbmd0aDtvKyspe2xldCBhPWlbb107aWYoIShhIGluIHIpKWlmKG4pdFthXT1lW2FdO2Vsc2V7bGV0IHM9T2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcihlLGEpO3MmJihzLmNvbmZpZ3VyYWJsZT0hMCxPYmplY3QuZGVmaW5lUHJvcGVydHkodCxhLHMpKX19fWZ1bmN0aW9uIE5ndChlLHQpe3JldHVybiB6Z3Qoe30sR3QodCksZSl9ZnVuY3Rpb24gJHhlKGUsdCxyKXtmb3IobGV0IG49MDtuPHQubGVuZ3RoO24rKylEZ3QoZSx0W25dLHIsanhlKX1mdW5jdGlvbiBEZ3QoZSx0LHIsbil7WHhlKHQsZSxuKTtmb3IobGV0IGkgaW4gWXhlKXRbaV0mJihyW2ldPXJbaV18fFtdLHJbaV0ucHVzaCh0W2ldKSl9ZnVuY3Rpb24gT2d0KGUsdCxyKXt0PXR8fFtdO2ZvcihsZXQgbj1lLmxlbmd0aC0xO24+PTA7bi0tKXtsZXQgaT1lW25dO2k/QXJyYXkuaXNBcnJheShpKT9PZ3QoaSx0KTp0LmluZGV4T2YoaSk8MCYmKCFyfHxyLmluZGV4T2YoaSk8MCkmJnQudW5zaGlmdChpKTpjb25zb2xlLndhcm4oImJlaGF2aW9yIGlzIG51bGwsIGNoZWNrIGZvciBtaXNzaW5nIG9yIDQwNCBpbXBvcnQiKX1yZXR1cm4gdH1mdW5jdGlvbiBMZ3QoZSx0KXtmb3IobGV0IHIgaW4gdCl7bGV0IG49ZVtyXSxpPXRbcl07ISgidmFsdWUiaW4gaSkmJm4mJiJ2YWx1ZSJpbiBuP2Vbcl09T2JqZWN0LmFzc2lnbih7dmFsdWU6bi52YWx1ZX0saSk6ZVtyXT1pfX12YXIga2d0PUd0KEhUTUxFbGVtZW50KTtmdW5jdGlvbiB6Z3QoZSx0LHIpe2xldCBuLGk9e307Y2xhc3MgbyBleHRlbmRzIHR7c3RhdGljIF9maW5hbGl6ZUNsYXNzKCl7aWYoIXRoaXMuaGFzT3duUHJvcGVydHkoSlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiZ2VuZXJhdGVkRnJvbSIsdGhpcykpKXQuX2ZpbmFsaXplQ2xhc3MuY2FsbCh0aGlzKTtlbHNle2lmKG4pZm9yKGxldCBsPTAsYztsPG4ubGVuZ3RoO2wrKyljPW5bbF0sYy5wcm9wZXJ0aWVzJiZ0aGlzLmNyZWF0ZVByb3BlcnRpZXMoYy5wcm9wZXJ0aWVzKSxjLm9ic2VydmVycyYmdGhpcy5jcmVhdGVPYnNlcnZlcnMoYy5vYnNlcnZlcnMsYy5wcm9wZXJ0aWVzKTtlLnByb3BlcnRpZXMmJnRoaXMuY3JlYXRlUHJvcGVydGllcyhlLnByb3BlcnRpZXMpLGUub2JzZXJ2ZXJzJiZ0aGlzLmNyZWF0ZU9ic2VydmVycyhlLm9ic2VydmVycyxlLnByb3BlcnRpZXMpLHRoaXMuX3ByZXBhcmVUZW1wbGF0ZSgpfX1zdGF0aWMgZ2V0IHByb3BlcnRpZXMoKXtsZXQgbD17fTtpZihuKWZvcihsZXQgYz0wO2M8bi5sZW5ndGg7YysrKUxndChsLG5bY10ucHJvcGVydGllcyk7cmV0dXJuIExndChsLGUucHJvcGVydGllcyksbH1zdGF0aWMgZ2V0IG9ic2VydmVycygpe2xldCBsPVtdO2lmKG4pZm9yKGxldCBjPTAsdTtjPG4ubGVuZ3RoO2MrKyl1PW5bY10sdS5vYnNlcnZlcnMmJihsPWwuY29uY2F0KHUub2JzZXJ2ZXJzKSk7cmV0dXJuIGUub2JzZXJ2ZXJzJiYobD1sLmNvbmNhdChlLm9ic2VydmVycykpLGx9Y3JlYXRlZCgpe3N1cGVyLmNyZWF0ZWQoKTtsZXQgbD1pLmNyZWF0ZWQ7aWYobClmb3IobGV0IGM9MDtjPGwubGVuZ3RoO2MrKylsW2NdLmNhbGwodGhpcyl9X3JlZ2lzdGVyZWQoKXtsZXQgbD1vLnByb3RvdHlwZTtpZighbC5oYXNPd25Qcm9wZXJ0eShKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJfX2hhc1JlZ2lzdGVyRmluaXNoZWQiLGwpKSl7bC5fX2hhc1JlZ2lzdGVyRmluaXNoZWQ9ITAsc3VwZXIuX3JlZ2lzdGVyZWQoKSxwcCYmYShsKTtsZXQgYz1PYmplY3QuZ2V0UHJvdG90eXBlT2YodGhpcyksdT1pLmJlZm9yZVJlZ2lzdGVyO2lmKHUpZm9yKGxldCBoPTA7aDx1Lmxlbmd0aDtoKyspdVtoXS5jYWxsKGMpO2lmKHU9aS5yZWdpc3RlcmVkLHUpZm9yKGxldCBoPTA7aDx1Lmxlbmd0aDtoKyspdVtoXS5jYWxsKGMpfX1fYXBwbHlMaXN0ZW5lcnMoKXtzdXBlci5fYXBwbHlMaXN0ZW5lcnMoKTtsZXQgbD1pLmxpc3RlbmVycztpZihsKWZvcihsZXQgYz0wO2M8bC5sZW5ndGg7YysrKXtsZXQgdT1sW2NdO2lmKHUpZm9yKGxldCBoIGluIHUpdGhpcy5fYWRkTWV0aG9kRXZlbnRMaXN0ZW5lclRvTm9kZSh0aGlzLGgsdVtoXSl9fV9lbnN1cmVBdHRyaWJ1dGVzKCl7bGV0IGw9aS5ob3N0QXR0cmlidXRlcztpZihsKWZvcihsZXQgYz1sLmxlbmd0aC0xO2M+PTA7Yy0tKXtsZXQgdT1sW2NdO2ZvcihsZXQgaCBpbiB1KXRoaXMuX2Vuc3VyZUF0dHJpYnV0ZShoLHVbaF0pfXN1cGVyLl9lbnN1cmVBdHRyaWJ1dGVzKCl9cmVhZHkoKXtzdXBlci5yZWFkeSgpO2xldCBsPWkucmVhZHk7aWYobClmb3IobGV0IGM9MDtjPGwubGVuZ3RoO2MrKylsW2NdLmNhbGwodGhpcyl9YXR0YWNoZWQoKXtzdXBlci5hdHRhY2hlZCgpO2xldCBsPWkuYXR0YWNoZWQ7aWYobClmb3IobGV0IGM9MDtjPGwubGVuZ3RoO2MrKylsW2NdLmNhbGwodGhpcyl9ZGV0YWNoZWQoKXtzdXBlci5kZXRhY2hlZCgpO2xldCBsPWkuZGV0YWNoZWQ7aWYobClmb3IobGV0IGM9MDtjPGwubGVuZ3RoO2MrKylsW2NdLmNhbGwodGhpcyl9YXR0cmlidXRlQ2hhbmdlZChsLGMsdSl7c3VwZXIuYXR0cmlidXRlQ2hhbmdlZCgpO2xldCBoPWkuYXR0cmlidXRlQ2hhbmdlZDtpZihoKWZvcihsZXQgZj0wO2Y8aC5sZW5ndGg7ZisrKWhbZl0uY2FsbCh0aGlzLGwsYyx1KX19aWYocil7QXJyYXkuaXNBcnJheShyKXx8KHI9W3JdKTtsZXQgcz10LnByb3RvdHlwZS5iZWhhdmlvcnM7bj1PZ3QocixudWxsLHMpLG8ucHJvdG90eXBlLmJlaGF2aW9ycz1zP3MuY29uY2F0KHIpOm59bGV0IGE9cz0+e24mJiR4ZShzLG4saSksRGd0KHMsZSxpLFJndCl9O3JldHVybiBwcHx8YShvLnByb3RvdHlwZSksby5nZW5lcmF0ZWRGcm9tPWUsb312YXIgdTk9ZnVuY3Rpb24oZSx0KXtlfHxjb25zb2xlLndhcm4oIlBvbHltZXIuQ2xhc3MgcmVxdWlyZXMgYGluZm9gIGFyZ3VtZW50Iik7bGV0IHI9dD90KGtndCk6a2d0O3JldHVybiByPXpndChlLHIsZS5iZWhhdmlvcnMpLHIuaXM9ci5wcm90b3R5cGUuaXM9ZS5pcyxyfTt2YXIgWXQ9ZnVuY3Rpb24oZSl7bGV0IHQ7cmV0dXJuIHR5cGVvZiBlPT0iZnVuY3Rpb24iP3Q9ZTp0PVl0LkNsYXNzKGUpLGUuX2xlZ2FjeUZvcmNlT2JzZXJ2ZWRBdHRyaWJ1dGVzJiYodC5wcm90b3R5cGUuX2xlZ2FjeUZvcmNlT2JzZXJ2ZWRBdHRyaWJ1dGVzPWUuX2xlZ2FjeUZvcmNlT2JzZXJ2ZWRBdHRyaWJ1dGVzKSxjdXN0b21FbGVtZW50cy5kZWZpbmUodC5pcyx0KSx0fTtZdC5DbGFzcz11OTtmdW5jdGlvbiByVyhlLHQscixuLGkpe2xldCBvO2kmJihvPXR5cGVvZiByPT0ib2JqZWN0IiYmciE9PW51bGwsbyYmKG49ZS5fX2RhdGFUZW1wW3RdKSk7bGV0IGE9biE9PXImJihuPT09bnx8cj09PXIpO3JldHVybiBvJiZhJiYoZS5fX2RhdGFUZW1wW3RdPXIpLGF9dmFyIEd4PU5uKGU9PntjbGFzcyB0IGV4dGVuZHMgZXtfc2hvdWxkUHJvcGVydHlDaGFuZ2UobixpLG8pe3JldHVybiByVyh0aGlzLG4saSxvLCEwKX19cmV0dXJuIHR9KSxoOT1ObihlPT57Y2xhc3MgdCBleHRlbmRzIGV7c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue211dGFibGVEYXRhOkJvb2xlYW59fV9zaG91bGRQcm9wZXJ0eUNoYW5nZShuLGksbyl7cmV0dXJuIHJXKHRoaXMsbixpLG8sdGhpcy5tdXRhYmxlRGF0YSl9fXJldHVybiB0fSk7R3guX211dGFibGVQcm9wZXJ0eUNoYW5nZT1yVzt2YXIgblc9bnVsbDtmdW5jdGlvbiBpVygpe3JldHVybiBuV31pVy5wcm90b3R5cGU9T2JqZWN0LmNyZWF0ZShIVE1MVGVtcGxhdGVFbGVtZW50LnByb3RvdHlwZSx7Y29uc3RydWN0b3I6e3ZhbHVlOmlXLHdyaXRhYmxlOiEwfX0pO3ZhciBCZ3Q9dV8oaVcpLEt4ZT1HeChCZ3QpO2Z1bmN0aW9uIFp4ZShlLHQpe25XPWUsT2JqZWN0LnNldFByb3RvdHlwZU9mKGUsdC5wcm90b3R5cGUpLG5ldyB0LG5XPW51bGx9dmFyIEp4ZT11XyhjbGFzc3t9KTtmdW5jdGlvbiBvVyhlLHQpe2ZvcihsZXQgcj0wO3I8dC5sZW5ndGg7cisrKXtsZXQgbj10W3JdO2lmKEJvb2xlYW4oZSkhPUJvb2xlYW4obi5fX2hpZGVUZW1wbGF0ZUNoaWxkcmVuX18pKWlmKG4ubm9kZVR5cGU9PT1Ob2RlLlRFWFRfTk9ERSllPyhuLl9fcG9seW1lclRleHRDb250ZW50X189bi50ZXh0Q29udGVudCxuLnRleHRDb250ZW50PSIiKTpuLnRleHRDb250ZW50PW4uX19wb2x5bWVyVGV4dENvbnRlbnRfXztlbHNlIGlmKG4ubG9jYWxOYW1lPT09InNsb3QiKWlmKGUpbi5fX3BvbHltZXJSZXBsYWNlZF9fPWRvY3VtZW50LmNyZWF0ZUNvbW1lbnQoImhpZGRlbi1zbG90IiksdWUodWUobikucGFyZW50Tm9kZSkucmVwbGFjZUNoaWxkKG4uX19wb2x5bWVyUmVwbGFjZWRfXyxuKTtlbHNle2xldCBpPW4uX19wb2x5bWVyUmVwbGFjZWRfXztpJiZ1ZSh1ZShpKS5wYXJlbnROb2RlKS5yZXBsYWNlQ2hpbGQobixpKX1lbHNlIG4uc3R5bGUmJihlPyhuLl9fcG9seW1lckRpc3BsYXlfXz1uLnN0eWxlLmRpc3BsYXksbi5zdHlsZS5kaXNwbGF5PSJub25lIik6bi5zdHlsZS5kaXNwbGF5PW4uX19wb2x5bWVyRGlzcGxheV9fKTtuLl9faGlkZVRlbXBsYXRlQ2hpbGRyZW5fXz1lLG4uX3Nob3dIaWRlQ2hpbGRyZW4mJm4uX3Nob3dIaWRlQ2hpbGRyZW4oZSl9fXZhciBiaD1jbGFzcyBleHRlbmRzIEp4ZXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMuX2NvbmZpZ3VyZVByb3BlcnRpZXModCksdGhpcy5yb290PXRoaXMuX3N0YW1wVGVtcGxhdGUodGhpcy5fX2RhdGFIb3N0KTtsZXQgcj1bXTt0aGlzLmNoaWxkcmVuPXI7Zm9yKGxldCBpPXRoaXMucm9vdC5maXJzdENoaWxkO2k7aT1pLm5leHRTaWJsaW5nKXIucHVzaChpKSxpLl9fdGVtcGxhdGl6ZUluc3RhbmNlPXRoaXM7dGhpcy5fX3RlbXBsYXRpemVPd25lciYmdGhpcy5fX3RlbXBsYXRpemVPd25lci5fX2hpZGVUZW1wbGF0ZUNoaWxkcmVuX18mJnRoaXMuX3Nob3dIaWRlQ2hpbGRyZW4oITApO2xldCBuPXRoaXMuX190ZW1wbGF0aXplT3B0aW9uczsodCYmbi5pbnN0YW5jZVByb3BzfHwhbi5pbnN0YW5jZVByb3BzKSYmdGhpcy5fZW5hYmxlUHJvcGVydGllcygpfV9jb25maWd1cmVQcm9wZXJ0aWVzKHQpe2lmKHRoaXMuX190ZW1wbGF0aXplT3B0aW9ucy5mb3J3YXJkSG9zdFByb3ApZm9yKGxldCBuIGluIHRoaXMuX19ob3N0UHJvcHMpdGhpcy5fc2V0UGVuZGluZ1Byb3BlcnR5KG4sdGhpcy5fX2RhdGFIb3N0WyJfaG9zdF8iK25dKTtmb3IobGV0IG4gaW4gdCl0aGlzLl9zZXRQZW5kaW5nUHJvcGVydHkobix0W25dKX1mb3J3YXJkSG9zdFByb3AodCxyKXt0aGlzLl9zZXRQZW5kaW5nUHJvcGVydHlPclBhdGgodCxyLCExLCEwKSYmdGhpcy5fX2RhdGFIb3N0Ll9lbnF1ZXVlQ2xpZW50KHRoaXMpfV9hZGRFdmVudExpc3RlbmVyVG9Ob2RlKHQscixuKXtpZih0aGlzLl9tZXRob2RIb3N0JiZ0aGlzLl9fdGVtcGxhdGl6ZU9wdGlvbnMucGFyZW50TW9kZWwpdGhpcy5fbWV0aG9kSG9zdC5fYWRkRXZlbnRMaXN0ZW5lclRvTm9kZSh0LHIsaT0+e2kubW9kZWw9dGhpcyxuKGkpfSk7ZWxzZXtsZXQgaT10aGlzLl9fZGF0YUhvc3QuX19kYXRhSG9zdDtpJiZpLl9hZGRFdmVudExpc3RlbmVyVG9Ob2RlKHQscixuKX19X3Nob3dIaWRlQ2hpbGRyZW4odCl7b1codCx0aGlzLmNoaWxkcmVuKX1fc2V0VW5tYW5hZ2VkUHJvcGVydHlUb05vZGUodCxyLG4pe3QuX19oaWRlVGVtcGxhdGVDaGlsZHJlbl9fJiZ0Lm5vZGVUeXBlPT1Ob2RlLlRFWFRfTk9ERSYmcj09InRleHRDb250ZW50Ij90Ll9fcG9seW1lclRleHRDb250ZW50X189bjpzdXBlci5fc2V0VW5tYW5hZ2VkUHJvcGVydHlUb05vZGUodCxyLG4pfWdldCBwYXJlbnRNb2RlbCgpe2xldCB0PXRoaXMuX19wYXJlbnRNb2RlbDtpZighdCl7bGV0IHI7dD10aGlzO2RvIHQ9dC5fX2RhdGFIb3N0Ll9fZGF0YUhvc3Q7d2hpbGUoKHI9dC5fX3RlbXBsYXRpemVPcHRpb25zKSYmIXIucGFyZW50TW9kZWwpO3RoaXMuX19wYXJlbnRNb2RlbD10fXJldHVybiB0fWRpc3BhdGNoRXZlbnQodCl7cmV0dXJuITB9fTtiaC5wcm90b3R5cGUuX19kYXRhSG9zdDtiaC5wcm90b3R5cGUuX190ZW1wbGF0aXplT3B0aW9ucztiaC5wcm90b3R5cGUuX21ldGhvZEhvc3Q7YmgucHJvdG90eXBlLl9fdGVtcGxhdGl6ZU93bmVyO2JoLnByb3RvdHlwZS5fX2hvc3RQcm9wczt2YXIgUXhlPUd4KGJoKTtmdW5jdGlvbiBGZ3QoZSl7bGV0IHQ9ZS5fX2RhdGFIb3N0O3JldHVybiB0JiZ0Ll9tZXRob2RIb3N0fHx0fWZ1bmN0aW9uIHRiZShlLHQscil7bGV0IG49ci5tdXRhYmxlRGF0YT9ReGU6Ymg7dGMubWl4aW4mJihuPXRjLm1peGluKG4pKTtsZXQgaT1jbGFzcyBleHRlbmRzIG57fTtyZXR1cm4gaS5wcm90b3R5cGUuX190ZW1wbGF0aXplT3B0aW9ucz1yLGkucHJvdG90eXBlLl9iaW5kVGVtcGxhdGUoZSksbmJlKGksZSx0LHIpLGl9ZnVuY3Rpb24gZWJlKGUsdCxyLG4pe2xldCBpPXIuZm9yd2FyZEhvc3RQcm9wO2lmKGkmJnQuaGFzSG9zdFByb3BzKXtsZXQgbz1lLmxvY2FsTmFtZT09InRlbXBsYXRlIixhPXQudGVtcGxhdGl6ZVRlbXBsYXRlQ2xhc3M7aWYoIWEpe2lmKG8pe2xldCBsPXIubXV0YWJsZURhdGE/S3hlOkJndDtjbGFzcyBjIGV4dGVuZHMgbHt9YT10LnRlbXBsYXRpemVUZW1wbGF0ZUNsYXNzPWN9ZWxzZXtsZXQgbD1lLmNvbnN0cnVjdG9yO2NsYXNzIGMgZXh0ZW5kcyBse31hPXQudGVtcGxhdGl6ZVRlbXBsYXRlQ2xhc3M9Y31sZXQgcz10Lmhvc3RQcm9wcztmb3IobGV0IGwgaW4gcylhLnByb3RvdHlwZS5fYWRkUHJvcGVydHlFZmZlY3QoIl9ob3N0XyIrbCxhLnByb3RvdHlwZS5QUk9QRVJUWV9FRkZFQ1RfVFlQRVMuUFJPUEFHQVRFLHtmbjpyYmUobCxpKX0pLGEucHJvdG90eXBlLl9jcmVhdGVOb3RpZnlpbmdQcm9wZXJ0eSgiX2hvc3RfIitsKTtJSSYmbiYmYWJlKHQscixuKX1pZihlLl9fZGF0YVByb3RvJiZPYmplY3QuYXNzaWduKGUuX19kYXRhLGUuX19kYXRhUHJvdG8pLG8pWnhlKGUsYSksZS5fX2RhdGFUZW1wPXt9LGUuX19kYXRhUGVuZGluZz1udWxsLGUuX19kYXRhT2xkPW51bGwsZS5fZW5hYmxlUHJvcGVydGllcygpO2Vsc2V7T2JqZWN0LnNldFByb3RvdHlwZU9mKGUsYS5wcm90b3R5cGUpO2xldCBzPXQuaG9zdFByb3BzO2ZvcihsZXQgbCBpbiBzKWlmKGw9Il9ob3N0XyIrbCxsIGluIGUpe2xldCBjPWVbbF07ZGVsZXRlIGVbbF0sZS5fX2RhdGFbbF09Y319fX1mdW5jdGlvbiByYmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24obixpLG8pe3QuY2FsbChuLl9fdGVtcGxhdGl6ZU93bmVyLGkuc3Vic3RyaW5nKDYpLG9baV0pfX1mdW5jdGlvbiBuYmUoZSx0LHIsbil7bGV0IGk9ci5ob3N0UHJvcHN8fHt9O2ZvcihsZXQgbyBpbiBuLmluc3RhbmNlUHJvcHMpe2RlbGV0ZSBpW29dO2xldCBhPW4ubm90aWZ5SW5zdGFuY2VQcm9wO2EmJmUucHJvdG90eXBlLl9hZGRQcm9wZXJ0eUVmZmVjdChvLGUucHJvdG90eXBlLlBST1BFUlRZX0VGRkVDVF9UWVBFUy5OT1RJRlkse2ZuOmliZShvLGEpfSl9aWYobi5mb3J3YXJkSG9zdFByb3AmJnQuX19kYXRhSG9zdClmb3IobGV0IG8gaW4gaSlyLmhhc0hvc3RQcm9wc3x8KHIuaGFzSG9zdFByb3BzPSEwKSxlLnByb3RvdHlwZS5fYWRkUHJvcGVydHlFZmZlY3QobyxlLnByb3RvdHlwZS5QUk9QRVJUWV9FRkZFQ1RfVFlQRVMuTk9USUZZLHtmbjpvYmUoKX0pfWZ1bmN0aW9uIGliZShlLHQpe3JldHVybiBmdW5jdGlvbihuLGksbyl7dC5jYWxsKG4uX190ZW1wbGF0aXplT3duZXIsbixpLG9baV0pfX1mdW5jdGlvbiBvYmUoKXtyZXR1cm4gZnVuY3Rpb24odCxyLG4pe3QuX19kYXRhSG9zdC5fc2V0UGVuZGluZ1Byb3BlcnR5T3JQYXRoKCJfaG9zdF8iK3IsbltyXSwhMCwhMCl9fWZ1bmN0aW9uIHRjKGUsdCxyKXtpZihpdSYmIUZndChlKSl0aHJvdyBuZXcgRXJyb3IoInN0cmljdFRlbXBsYXRlUG9saWN5OiB0ZW1wbGF0ZSBvd25lciBub3QgdHJ1c3RlZCIpO2lmKHI9cnx8e30sZS5fX3RlbXBsYXRpemVPd25lcil0aHJvdyBuZXcgRXJyb3IoIkEgPHRlbXBsYXRlPiBjYW4gb25seSBiZSB0ZW1wbGF0aXplZCBvbmNlIik7ZS5fX3RlbXBsYXRpemVPd25lcj10O2xldCBpPSh0P3QuY29uc3RydWN0b3I6YmgpLl9wYXJzZVRlbXBsYXRlKGUpLG89aS50ZW1wbGF0aXplSW5zdGFuY2VDbGFzcztvfHwobz10YmUoZSxpLHIpLGkudGVtcGxhdGl6ZUluc3RhbmNlQ2xhc3M9byk7bGV0IGE9Rmd0KGUpO2ViZShlLGkscixhKTtsZXQgcz1jbGFzcyBleHRlbmRzIG97fTtyZXR1cm4gcy5wcm90b3R5cGUuX21ldGhvZEhvc3Q9YSxzLnByb3RvdHlwZS5fX2RhdGFIb3N0PWUscy5wcm90b3R5cGUuX190ZW1wbGF0aXplT3duZXI9dCxzLnByb3RvdHlwZS5fX2hvc3RQcm9wcz1pLmhvc3RQcm9wcyxzPXMsc31mdW5jdGlvbiBhYmUoZSx0LHIpe2xldCBuPXIuY29uc3RydWN0b3IuX3Byb3BlcnRpZXMse3Byb3BlcnR5RWZmZWN0czppfT1lLHtpbnN0YW5jZVByb3BzOm99PXQ7Zm9yKGxldCBhIGluIGkpaWYoIW5bYV0mJiEobyYmb1thXSkpe2xldCBzPWlbYV07Zm9yKGxldCBsPTA7bDxzLmxlbmd0aDtsKyspe2xldHtwYXJ0OmN9PXNbbF0uaW5mbztpZighKGMuc2lnbmF0dXJlJiZjLnNpZ25hdHVyZS5zdGF0aWMpKXtjb25zb2xlLndhcm4oYFByb3BlcnR5ICcke2F9JyB1c2VkIGluIHRlbXBsYXRlIGJ1dCBub3QgZGVjbGFyZWQgaW4gJ3Byb3BlcnRpZXMnOyBhdHRyaWJ1dGUgd2lsbCBub3QgYmUgb2JzZXJ2ZWQuYCk7YnJlYWt9fX19ZnVuY3Rpb24gZjkoZSx0KXtsZXQgcjtmb3IoO3Q7KWlmKHI9dC5fX2RhdGFIb3N0P3Q6dC5fX3RlbXBsYXRpemVJbnN0YW5jZSlpZihyLl9fZGF0YUhvc3QhPWUpdD1yLl9fZGF0YUhvc3Q7ZWxzZSByZXR1cm4gcjtlbHNlIHQ9dWUodCkucGFyZW50Tm9kZTtyZXR1cm4gbnVsbH12YXIgSGd0PXt0ZW1wbGF0aXplKGUsdCl7dGhpcy5fdGVtcGxhdGl6ZXJUZW1wbGF0ZT1lLHRoaXMuY3Rvcj10YyhlLHRoaXMse211dGFibGVEYXRhOkJvb2xlYW4odCkscGFyZW50TW9kZWw6dGhpcy5fcGFyZW50TW9kZWwsaW5zdGFuY2VQcm9wczp0aGlzLl9pbnN0YW5jZVByb3BzLGZvcndhcmRIb3N0UHJvcDp0aGlzLl9mb3J3YXJkSG9zdFByb3BWMixub3RpZnlJbnN0YW5jZVByb3A6dGhpcy5fbm90aWZ5SW5zdGFuY2VQcm9wVjJ9KX0sc3RhbXAoZSl7cmV0dXJuIG5ldyB0aGlzLmN0b3IoZSl9LG1vZGVsRm9yRWxlbWVudChlKXtyZXR1cm4gZjkodGhpcy5fdGVtcGxhdGl6ZXJUZW1wbGF0ZSxlKX19O3ZhciBWZ3Q9ITE7ZnVuY3Rpb24gV3goKXtpZihwcCYmIWNfKXtpZighVmd0KXtWZ3Q9ITA7bGV0IGU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3R5bGUiKTtlLnRleHRDb250ZW50PSJkb20tYmluZCxkb20taWYsZG9tLXJlcGVhdHtkaXNwbGF5Om5vbmU7fSIsZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChlKX1yZXR1cm4hMH1yZXR1cm4hMX12YXIgc2JlPXloKGg5KHVfKEhUTUxFbGVtZW50KSkpLGFXPWNsYXNzIGV4dGVuZHMgc2Jle3N0YXRpYyBnZXQgb2JzZXJ2ZWRBdHRyaWJ1dGVzKCl7cmV0dXJuWyJtdXRhYmxlLWRhdGEiXX1jb25zdHJ1Y3Rvcigpe2lmKHN1cGVyKCksaXUpdGhyb3cgbmV3IEVycm9yKCJzdHJpY3RUZW1wbGF0ZVBvbGljeTogZG9tLWJpbmQgbm90IGFsbG93ZWQiKTt0aGlzLnJvb3Q9bnVsbCx0aGlzLiQ9bnVsbCx0aGlzLl9fY2hpbGRyZW49bnVsbH1hdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2sodCxyLG4saSl7dGhpcy5tdXRhYmxlRGF0YT0hMH1jb25uZWN0ZWRDYWxsYmFjaygpe1d4KCl8fCh0aGlzLnN0eWxlLmRpc3BsYXk9Im5vbmUiKSx0aGlzLnJlbmRlcigpfWRpc2Nvbm5lY3RlZENhbGxiYWNrKCl7dGhpcy5fX3JlbW92ZUNoaWxkcmVuKCl9X19pbnNlcnRDaGlsZHJlbigpe3VlKHVlKHRoaXMpLnBhcmVudE5vZGUpLmluc2VydEJlZm9yZSh0aGlzLnJvb3QsdGhpcyl9X19yZW1vdmVDaGlsZHJlbigpe2lmKHRoaXMuX19jaGlsZHJlbilmb3IobGV0IHQ9MDt0PHRoaXMuX19jaGlsZHJlbi5sZW5ndGg7dCsrKXRoaXMucm9vdC5hcHBlbmRDaGlsZCh0aGlzLl9fY2hpbGRyZW5bdF0pfXJlbmRlcigpe2xldCB0O2lmKCF0aGlzLl9fY2hpbGRyZW4pe2lmKHQ9dHx8dGhpcy5xdWVyeVNlbGVjdG9yKCJ0ZW1wbGF0ZSIpLCF0KXtsZXQgcj1uZXcgTXV0YXRpb25PYnNlcnZlcigoKT0+e2lmKHQ9dGhpcy5xdWVyeVNlbGVjdG9yKCJ0ZW1wbGF0ZSIpLHQpci5kaXNjb25uZWN0KCksdGhpcy5yZW5kZXIoKTtlbHNlIHRocm93IG5ldyBFcnJvcigiZG9tLWJpbmQgcmVxdWlyZXMgYSA8dGVtcGxhdGU+IGNoaWxkIil9KTtyLm9ic2VydmUodGhpcyx7Y2hpbGRMaXN0OiEwfSk7cmV0dXJufXRoaXMucm9vdD10aGlzLl9zdGFtcFRlbXBsYXRlKHQpLHRoaXMuJD10aGlzLnJvb3QuJCx0aGlzLl9fY2hpbGRyZW49W107Zm9yKGxldCByPXRoaXMucm9vdC5maXJzdENoaWxkO3I7cj1yLm5leHRTaWJsaW5nKXRoaXMuX19jaGlsZHJlblt0aGlzLl9fY2hpbGRyZW4ubGVuZ3RoXT1yO3RoaXMuX2VuYWJsZVByb3BlcnRpZXMoKX10aGlzLl9faW5zZXJ0Q2hpbGRyZW4oKSx0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCJkb20tY2hhbmdlIix7YnViYmxlczohMCxjb21wb3NlZDohMH0pKX19O2N1c3RvbUVsZW1lbnRzLmRlZmluZSgiZG9tLWJpbmQiLGFXKTt2YXIgbGJlPWg5KG10KSxwOT1jbGFzcyBleHRlbmRzIGxiZXtzdGF0aWMgZ2V0IGlzKCl7cmV0dXJuImRvbS1yZXBlYXQifXN0YXRpYyBnZXQgdGVtcGxhdGUoKXtyZXR1cm4gbnVsbH1zdGF0aWMgZ2V0IHByb3BlcnRpZXMoKXtyZXR1cm57aXRlbXM6e3R5cGU6QXJyYXl9LGFzOnt0eXBlOlN0cmluZyx2YWx1ZToiaXRlbSJ9LGluZGV4QXM6e3R5cGU6U3RyaW5nLHZhbHVlOiJpbmRleCJ9LGl0ZW1zSW5kZXhBczp7dHlwZTpTdHJpbmcsdmFsdWU6Iml0ZW1zSW5kZXgifSxzb3J0Ont0eXBlOkZ1bmN0aW9uLG9ic2VydmVyOiJfX3NvcnRDaGFuZ2VkIn0sZmlsdGVyOnt0eXBlOkZ1bmN0aW9uLG9ic2VydmVyOiJfX2ZpbHRlckNoYW5nZWQifSxvYnNlcnZlOnt0eXBlOlN0cmluZyxvYnNlcnZlcjoiX19vYnNlcnZlQ2hhbmdlZCJ9LGRlbGF5Ok51bWJlcixyZW5kZXJlZEl0ZW1Db3VudDp7dHlwZTpOdW1iZXIsbm90aWZ5OiFVTSxyZWFkT25seTohMH0saW5pdGlhbENvdW50Ont0eXBlOk51bWJlcn0sdGFyZ2V0RnJhbWVyYXRlOnt0eXBlOk51bWJlcix2YWx1ZToyMH0sX3RhcmdldEZyYW1lVGltZTp7dHlwZTpOdW1iZXIsY29tcHV0ZWQ6Il9fY29tcHV0ZUZyYW1lVGltZSh0YXJnZXRGcmFtZXJhdGUpIn0sbm90aWZ5RG9tQ2hhbmdlOnt0eXBlOkJvb2xlYW59LHJldXNlQ2h1bmtlZEluc3RhbmNlczp7dHlwZTpCb29sZWFufX19c3RhdGljIGdldCBvYnNlcnZlcnMoKXtyZXR1cm5bIl9faXRlbXNDaGFuZ2VkKGl0ZW1zLiopIl19Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX19pbnN0YW5jZXM9W10sdGhpcy5fX3JlbmRlckRlYm91bmNlcj1udWxsLHRoaXMuX19pdGVtc0lkeFRvSW5zdElkeD17fSx0aGlzLl9fY2h1bmtDb3VudD1udWxsLHRoaXMuX19yZW5kZXJTdGFydFRpbWU9bnVsbCx0aGlzLl9faXRlbXNBcnJheUNoYW5nZWQ9ITEsdGhpcy5fX3Nob3VsZE1lYXN1cmVDaHVuaz0hMSx0aGlzLl9fc2hvdWxkQ29udGludWVDaHVua2luZz0hMSx0aGlzLl9fY2h1bmtpbmdJZD0wLHRoaXMuX19zb3J0Rm49bnVsbCx0aGlzLl9fZmlsdGVyRm49bnVsbCx0aGlzLl9fb2JzZXJ2ZVBhdGhzPW51bGwsdGhpcy5fX2N0b3I9bnVsbCx0aGlzLl9faXNEZXRhY2hlZD0hMCx0aGlzLnRlbXBsYXRlPW51bGwsdGhpcy5fdGVtcGxhdGVJbmZvfWRpc2Nvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuZGlzY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLl9faXNEZXRhY2hlZD0hMDtmb3IobGV0IHQ9MDt0PHRoaXMuX19pbnN0YW5jZXMubGVuZ3RoO3QrKyl0aGlzLl9fZGV0YWNoSW5zdGFuY2UodCl9Y29ubmVjdGVkQ2FsbGJhY2soKXtpZihzdXBlci5jb25uZWN0ZWRDYWxsYmFjaygpLFd4KCl8fCh0aGlzLnN0eWxlLmRpc3BsYXk9Im5vbmUiKSx0aGlzLl9faXNEZXRhY2hlZCl7dGhpcy5fX2lzRGV0YWNoZWQ9ITE7bGV0IHQ9dWUodWUodGhpcykucGFyZW50Tm9kZSk7Zm9yKGxldCByPTA7cjx0aGlzLl9faW5zdGFuY2VzLmxlbmd0aDtyKyspdGhpcy5fX2F0dGFjaEluc3RhbmNlKHIsdCl9fV9fZW5zdXJlVGVtcGxhdGl6ZWQoKXtpZighdGhpcy5fX2N0b3Ipe2xldCB0PXRoaXMscj10aGlzLnRlbXBsYXRlPXQuX3RlbXBsYXRlSW5mbz90OnRoaXMucXVlcnlTZWxlY3RvcigidGVtcGxhdGUiKTtpZighcil7bGV0IGk9bmV3IE11dGF0aW9uT2JzZXJ2ZXIoKCk9PntpZih0aGlzLnF1ZXJ5U2VsZWN0b3IoInRlbXBsYXRlIikpaS5kaXNjb25uZWN0KCksdGhpcy5fX3JlbmRlcigpO2Vsc2UgdGhyb3cgbmV3IEVycm9yKCJkb20tcmVwZWF0IHJlcXVpcmVzIGEgPHRlbXBsYXRlPiBjaGlsZCIpfSk7cmV0dXJuIGkub2JzZXJ2ZSh0aGlzLHtjaGlsZExpc3Q6ITB9KSwhMX1sZXQgbj17fTtuW3RoaXMuYXNdPSEwLG5bdGhpcy5pbmRleEFzXT0hMCxuW3RoaXMuaXRlbXNJbmRleEFzXT0hMCx0aGlzLl9fY3Rvcj10YyhyLHRoaXMse211dGFibGVEYXRhOnRoaXMubXV0YWJsZURhdGEscGFyZW50TW9kZWw6ITAsaW5zdGFuY2VQcm9wczpuLGZvcndhcmRIb3N0UHJvcDpmdW5jdGlvbihpLG8pe2xldCBhPXRoaXMuX19pbnN0YW5jZXM7Zm9yKGxldCBzPTAsbDtzPGEubGVuZ3RoJiYobD1hW3NdKTtzKyspbC5mb3J3YXJkSG9zdFByb3AoaSxvKX0sbm90aWZ5SW5zdGFuY2VQcm9wOmZ1bmN0aW9uKGksbyxhKXtpZihESSh0aGlzLmFzLG8pKXtsZXQgcz1pW3RoaXMuaXRlbXNJbmRleEFzXTtvPT10aGlzLmFzJiYodGhpcy5pdGVtc1tzXT1hKTtsZXQgbD1kcCh0aGlzLmFzLGAke0pTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoIml0ZW1zIix0aGlzKX0uJHtzfWAsbyk7dGhpcy5ub3RpZnlQYXRoKGwsYSl9fX0pfXJldHVybiEwfV9fZ2V0TWV0aG9kSG9zdCgpe3JldHVybiB0aGlzLl9fZGF0YUhvc3QuX21ldGhvZEhvc3R8fHRoaXMuX19kYXRhSG9zdH1fX2Z1bmN0aW9uRnJvbVByb3BlcnR5VmFsdWUodCl7aWYodHlwZW9mIHQ9PSJzdHJpbmciKXtsZXQgcj10LG49dGhpcy5fX2dldE1ldGhvZEhvc3QoKTtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gbltyXS5hcHBseShuLGFyZ3VtZW50cyl9fXJldHVybiB0fV9fc29ydENoYW5nZWQodCl7dGhpcy5fX3NvcnRGbj10aGlzLl9fZnVuY3Rpb25Gcm9tUHJvcGVydHlWYWx1ZSh0KSx0aGlzLml0ZW1zJiZ0aGlzLl9fZGVib3VuY2VSZW5kZXIodGhpcy5fX3JlbmRlcil9X19maWx0ZXJDaGFuZ2VkKHQpe3RoaXMuX19maWx0ZXJGbj10aGlzLl9fZnVuY3Rpb25Gcm9tUHJvcGVydHlWYWx1ZSh0KSx0aGlzLml0ZW1zJiZ0aGlzLl9fZGVib3VuY2VSZW5kZXIodGhpcy5fX3JlbmRlcil9X19jb21wdXRlRnJhbWVUaW1lKHQpe3JldHVybiBNYXRoLmNlaWwoMWUzL3QpfV9fb2JzZXJ2ZUNoYW5nZWQoKXt0aGlzLl9fb2JzZXJ2ZVBhdGhzPXRoaXMub2JzZXJ2ZSYmdGhpcy5vYnNlcnZlLnJlcGxhY2UoIi4qIiwiLiIpLnNwbGl0KCIgIil9X19oYW5kbGVPYnNlcnZlZFBhdGhzKHQpe2lmKHRoaXMuX19zb3J0Rm58fHRoaXMuX19maWx0ZXJGbil7aWYoIXQpdGhpcy5fX2RlYm91bmNlUmVuZGVyKHRoaXMuX19yZW5kZXIsdGhpcy5kZWxheSk7ZWxzZSBpZih0aGlzLl9fb2JzZXJ2ZVBhdGhzKXtsZXQgcj10aGlzLl9fb2JzZXJ2ZVBhdGhzO2ZvcihsZXQgbj0wO248ci5sZW5ndGg7bisrKXQuaW5kZXhPZihyW25dKT09PTAmJnRoaXMuX19kZWJvdW5jZVJlbmRlcih0aGlzLl9fcmVuZGVyLHRoaXMuZGVsYXkpfX19X19pdGVtc0NoYW5nZWQodCl7dGhpcy5pdGVtcyYmIUFycmF5LmlzQXJyYXkodGhpcy5pdGVtcykmJmNvbnNvbGUud2FybigiZG9tLXJlcGVhdCBleHBlY3RlZCBhcnJheSBmb3IgYGl0ZW1zYCwgZm91bmQiLHRoaXMuaXRlbXMpLHRoaXMuX19oYW5kbGVJdGVtUGF0aCh0LnBhdGgsdC52YWx1ZSl8fCh0LnBhdGg9PT0iaXRlbXMiJiYodGhpcy5fX2l0ZW1zQXJyYXlDaGFuZ2VkPSEwKSx0aGlzLl9fZGVib3VuY2VSZW5kZXIodGhpcy5fX3JlbmRlcikpfV9fZGVib3VuY2VSZW5kZXIodCxyPTApe3RoaXMuX19yZW5kZXJEZWJvdW5jZXI9c3IuZGVib3VuY2UodGhpcy5fX3JlbmRlckRlYm91bmNlcixyPjA/bW8uYWZ0ZXIocik6Y2ksdC5iaW5kKHRoaXMpKSxKbCh0aGlzLl9fcmVuZGVyRGVib3VuY2VyKX1yZW5kZXIoKXt0aGlzLl9fZGVib3VuY2VSZW5kZXIodGhpcy5fX3JlbmRlciksdWkoKX1fX3JlbmRlcigpe2lmKCF0aGlzLl9fZW5zdXJlVGVtcGxhdGl6ZWQoKSlyZXR1cm47bGV0IHQ9dGhpcy5pdGVtc3x8W10scj10aGlzLl9fc29ydEFuZEZpbHRlckl0ZW1zKHQpLG49dGhpcy5fX2NhbGN1bGF0ZUxpbWl0KHIubGVuZ3RoKTt0aGlzLl9fdXBkYXRlSW5zdGFuY2VzKHQsbixyKSx0aGlzLmluaXRpYWxDb3VudCYmKHRoaXMuX19zaG91bGRNZWFzdXJlQ2h1bmt8fHRoaXMuX19zaG91bGRDb250aW51ZUNodW5raW5nKSYmKGNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuX19jaHVua2luZ0lkKSx0aGlzLl9fY2h1bmtpbmdJZD1yZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCk9PnRoaXMuX19jb250aW51ZUNodW5raW5nKCkpKSx0aGlzLl9zZXRSZW5kZXJlZEl0ZW1Db3VudCh0aGlzLl9faW5zdGFuY2VzLmxlbmd0aCksKCFVTXx8dGhpcy5ub3RpZnlEb21DaGFuZ2UpJiZ0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCJkb20tY2hhbmdlIix7YnViYmxlczohMCxjb21wb3NlZDohMH0pKX1fX3NvcnRBbmRGaWx0ZXJJdGVtcyh0KXtsZXQgcj1uZXcgQXJyYXkodC5sZW5ndGgpO2ZvcihsZXQgbj0wO248dC5sZW5ndGg7bisrKXJbbl09bjtyZXR1cm4gdGhpcy5fX2ZpbHRlckZuJiYocj1yLmZpbHRlcigobixpLG8pPT50aGlzLl9fZmlsdGVyRm4odFtuXSxpLG8pKSksdGhpcy5fX3NvcnRGbiYmci5zb3J0KChuLGkpPT50aGlzLl9fc29ydEZuKHRbbl0sdFtpXSkpLHJ9X19jYWxjdWxhdGVMaW1pdCh0KXtsZXQgcj10LG49dGhpcy5fX2luc3RhbmNlcy5sZW5ndGg7aWYodGhpcy5pbml0aWFsQ291bnQpe2xldCBpOyF0aGlzLl9fY2h1bmtDb3VudHx8dGhpcy5fX2l0ZW1zQXJyYXlDaGFuZ2VkJiYhdGhpcy5yZXVzZUNodW5rZWRJbnN0YW5jZXM/KHI9TWF0aC5taW4odCx0aGlzLmluaXRpYWxDb3VudCksaT1NYXRoLm1heChyLW4sMCksdGhpcy5fX2NodW5rQ291bnQ9aXx8MSk6KGk9TWF0aC5taW4oTWF0aC5tYXgodC1uLDApLHRoaXMuX19jaHVua0NvdW50KSxyPU1hdGgubWluKG4raSx0KSksdGhpcy5fX3Nob3VsZE1lYXN1cmVDaHVuaz1pPT09dGhpcy5fX2NodW5rQ291bnQsdGhpcy5fX3Nob3VsZENvbnRpbnVlQ2h1bmtpbmc9cjx0LHRoaXMuX19yZW5kZXJTdGFydFRpbWU9cGVyZm9ybWFuY2Uubm93KCl9cmV0dXJuIHRoaXMuX19pdGVtc0FycmF5Q2hhbmdlZD0hMSxyfV9fY29udGludWVDaHVua2luZygpe2lmKHRoaXMuX19zaG91bGRNZWFzdXJlQ2h1bmspe2xldCB0PXBlcmZvcm1hbmNlLm5vdygpLXRoaXMuX19yZW5kZXJTdGFydFRpbWUscj10aGlzLl90YXJnZXRGcmFtZVRpbWUvdDt0aGlzLl9fY2h1bmtDb3VudD1NYXRoLnJvdW5kKHRoaXMuX19jaHVua0NvdW50KnIpfHwxfXRoaXMuX19zaG91bGRDb250aW51ZUNodW5raW5nJiZ0aGlzLl9fZGVib3VuY2VSZW5kZXIodGhpcy5fX3JlbmRlcil9X191cGRhdGVJbnN0YW5jZXModCxyLG4pe2xldCBpPXRoaXMuX19pdGVtc0lkeFRvSW5zdElkeD17fSxvO2ZvcihvPTA7bzxyO28rKyl7bGV0IGE9dGhpcy5fX2luc3RhbmNlc1tvXSxzPW5bb10sbD10W3NdO2lbc109byxhPyhhLl9zZXRQZW5kaW5nUHJvcGVydHkodGhpcy5hcyxsKSxhLl9zZXRQZW5kaW5nUHJvcGVydHkodGhpcy5pbmRleEFzLG8pLGEuX3NldFBlbmRpbmdQcm9wZXJ0eSh0aGlzLml0ZW1zSW5kZXhBcyxzKSxhLl9mbHVzaFByb3BlcnRpZXMoKSk6dGhpcy5fX2luc2VydEluc3RhbmNlKGwsbyxzKX1mb3IobGV0IGE9dGhpcy5fX2luc3RhbmNlcy5sZW5ndGgtMTthPj1vO2EtLSl0aGlzLl9fZGV0YWNoQW5kUmVtb3ZlSW5zdGFuY2UoYSl9X19kZXRhY2hJbnN0YW5jZSh0KXtsZXQgcj10aGlzLl9faW5zdGFuY2VzW3RdLG49dWUoci5yb290KTtmb3IobGV0IGk9MDtpPHIuY2hpbGRyZW4ubGVuZ3RoO2krKyl7bGV0IG89ci5jaGlsZHJlbltpXTtuLmFwcGVuZENoaWxkKG8pfXJldHVybiByfV9fYXR0YWNoSW5zdGFuY2UodCxyKXtsZXQgbj10aGlzLl9faW5zdGFuY2VzW3RdO3IuaW5zZXJ0QmVmb3JlKG4ucm9vdCx0aGlzKX1fX2RldGFjaEFuZFJlbW92ZUluc3RhbmNlKHQpe3RoaXMuX19kZXRhY2hJbnN0YW5jZSh0KSx0aGlzLl9faW5zdGFuY2VzLnNwbGljZSh0LDEpfV9fc3RhbXBJbnN0YW5jZSh0LHIsbil7bGV0IGk9e307cmV0dXJuIGlbdGhpcy5hc109dCxpW3RoaXMuaW5kZXhBc109cixpW3RoaXMuaXRlbXNJbmRleEFzXT1uLG5ldyB0aGlzLl9fY3RvcihpKX1fX2luc2VydEluc3RhbmNlKHQscixuKXtsZXQgaT10aGlzLl9fc3RhbXBJbnN0YW5jZSh0LHIsbiksbz10aGlzLl9faW5zdGFuY2VzW3IrMV0sYT1vP28uY2hpbGRyZW5bMF06dGhpcztyZXR1cm4gdWUodWUodGhpcykucGFyZW50Tm9kZSkuaW5zZXJ0QmVmb3JlKGkucm9vdCxhKSx0aGlzLl9faW5zdGFuY2VzW3JdPWksaX1fc2hvd0hpZGVDaGlsZHJlbih0KXtmb3IobGV0IHI9MDtyPHRoaXMuX19pbnN0YW5jZXMubGVuZ3RoO3IrKyl0aGlzLl9faW5zdGFuY2VzW3JdLl9zaG93SGlkZUNoaWxkcmVuKHQpfV9faGFuZGxlSXRlbVBhdGgodCxyKXtsZXQgbj10LnNsaWNlKDYpLGk9bi5pbmRleE9mKCIuIiksbz1pPDA/bjpuLnN1YnN0cmluZygwLGkpO2lmKG89PXBhcnNlSW50KG8sMTApKXtsZXQgYT1pPDA/IiI6bi5zdWJzdHJpbmcoaSsxKTt0aGlzLl9faGFuZGxlT2JzZXJ2ZWRQYXRocyhhKTtsZXQgcz10aGlzLl9faXRlbXNJZHhUb0luc3RJZHhbb10sbD10aGlzLl9faW5zdGFuY2VzW3NdO2lmKGwpe2xldCBjPXRoaXMuYXMrKGE/Ii4iK2E6IiIpO2wuX3NldFBlbmRpbmdQcm9wZXJ0eU9yUGF0aChjLHIsITEsITApLGwuX2ZsdXNoUHJvcGVydGllcygpfXJldHVybiEwfX1pdGVtRm9yRWxlbWVudCh0KXtsZXQgcj10aGlzLm1vZGVsRm9yRWxlbWVudCh0KTtyZXR1cm4gciYmclt0aGlzLmFzXX1pbmRleEZvckVsZW1lbnQodCl7bGV0IHI9dGhpcy5tb2RlbEZvckVsZW1lbnQodCk7cmV0dXJuIHImJnJbdGhpcy5pbmRleEFzXX1tb2RlbEZvckVsZW1lbnQodCl7cmV0dXJuIGY5KHRoaXMudGVtcGxhdGUsdCl9fTtjdXN0b21FbGVtZW50cy5kZWZpbmUocDkuaXMscDkpO3ZhciBkOT1jbGFzcyBleHRlbmRzIG10e3N0YXRpYyBnZXQgaXMoKXtyZXR1cm4iZG9tLWlmIn1zdGF0aWMgZ2V0IHRlbXBsYXRlKCl7cmV0dXJuIG51bGx9c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue2lmOnt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9fZGVib3VuY2VSZW5kZXIifSxyZXN0YW1wOnt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9fZGVib3VuY2VSZW5kZXIifSxub3RpZnlEb21DaGFuZ2U6e3R5cGU6Qm9vbGVhbn19fWNvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLl9fcmVuZGVyRGVib3VuY2VyPW51bGwsdGhpcy5fbGFzdElmPSExLHRoaXMuX19oaWRlVGVtcGxhdGVDaGlsZHJlbl9fPSExLHRoaXMuX190ZW1wbGF0ZSx0aGlzLl90ZW1wbGF0ZUluZm99X19kZWJvdW5jZVJlbmRlcigpe3RoaXMuX19yZW5kZXJEZWJvdW5jZXI9c3IuZGVib3VuY2UodGhpcy5fX3JlbmRlckRlYm91bmNlcixjaSwoKT0+dGhpcy5fX3JlbmRlcigpKSxKbCh0aGlzLl9fcmVuZGVyRGVib3VuY2VyKX1kaXNjb25uZWN0ZWRDYWxsYmFjaygpe3N1cGVyLmRpc2Nvbm5lY3RlZENhbGxiYWNrKCk7bGV0IHQ9dWUodGhpcykucGFyZW50Tm9kZTsoIXR8fHQubm9kZVR5cGU9PU5vZGUuRE9DVU1FTlRfRlJBR01FTlRfTk9ERSYmIXVlKHQpLmhvc3QpJiZ0aGlzLl9fdGVhcmRvd25JbnN0YW5jZSgpfWNvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKSxXeCgpfHwodGhpcy5zdHlsZS5kaXNwbGF5PSJub25lIiksdGhpcy5pZiYmdGhpcy5fX2RlYm91bmNlUmVuZGVyKCl9X19lbnN1cmVUZW1wbGF0ZSgpe2lmKCF0aGlzLl9fdGVtcGxhdGUpe2xldCB0PXRoaXMscj10Ll90ZW1wbGF0ZUluZm8/dDp1ZSh0KS5xdWVyeVNlbGVjdG9yKCJ0ZW1wbGF0ZSIpO2lmKCFyKXtsZXQgbj1uZXcgTXV0YXRpb25PYnNlcnZlcigoKT0+e2lmKHVlKHRoaXMpLnF1ZXJ5U2VsZWN0b3IoInRlbXBsYXRlIikpbi5kaXNjb25uZWN0KCksdGhpcy5fX3JlbmRlcigpO2Vsc2UgdGhyb3cgbmV3IEVycm9yKCJkb20taWYgcmVxdWlyZXMgYSA8dGVtcGxhdGU+IGNoaWxkIil9KTtyZXR1cm4gbi5vYnNlcnZlKHRoaXMse2NoaWxkTGlzdDohMH0pLCExfXRoaXMuX190ZW1wbGF0ZT1yfXJldHVybiEwfV9fZW5zdXJlSW5zdGFuY2UoKXtsZXQgdD11ZSh0aGlzKS5wYXJlbnROb2RlO2lmKHRoaXMuX19oYXNJbnN0YW5jZSgpKXtsZXQgcj10aGlzLl9fZ2V0SW5zdGFuY2VOb2RlcygpO2lmKHImJnIubGVuZ3RoJiZ1ZSh0aGlzKS5wcmV2aW91c1NpYmxpbmchPT1yW3IubGVuZ3RoLTFdKWZvcihsZXQgaT0wLG87aTxyLmxlbmd0aCYmKG89cltpXSk7aSsrKXVlKHQpLmluc2VydEJlZm9yZShvLHRoaXMpfWVsc2V7aWYoIXR8fCF0aGlzLl9fZW5zdXJlVGVtcGxhdGUoKSlyZXR1cm4hMTt0aGlzLl9fY3JlYXRlQW5kSW5zZXJ0SW5zdGFuY2UodCl9cmV0dXJuITB9cmVuZGVyKCl7dWkoKX1fX3JlbmRlcigpe2lmKHRoaXMuaWYpe2lmKCF0aGlzLl9fZW5zdXJlSW5zdGFuY2UoKSlyZXR1cm59ZWxzZSB0aGlzLnJlc3RhbXAmJnRoaXMuX190ZWFyZG93bkluc3RhbmNlKCk7dGhpcy5fc2hvd0hpZGVDaGlsZHJlbigpLCghVU18fHRoaXMubm90aWZ5RG9tQ2hhbmdlKSYmdGhpcy5pZiE9dGhpcy5fbGFzdElmJiYodGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBDdXN0b21FdmVudCgiZG9tLWNoYW5nZSIse2J1YmJsZXM6ITAsY29tcG9zZWQ6ITB9KSksdGhpcy5fbGFzdElmPXRoaXMuaWYpfV9faGFzSW5zdGFuY2UoKXt9X19nZXRJbnN0YW5jZU5vZGVzKCl7fV9fY3JlYXRlQW5kSW5zZXJ0SW5zdGFuY2UodCl7fV9fdGVhcmRvd25JbnN0YW5jZSgpe31fc2hvd0hpZGVDaGlsZHJlbigpe319LHNXPWNsYXNzIGV4dGVuZHMgZDl7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX19pbnN0YW5jZT1udWxsLHRoaXMuX19zeW5jSW5mbz1udWxsfV9faGFzSW5zdGFuY2UoKXtyZXR1cm4gQm9vbGVhbih0aGlzLl9faW5zdGFuY2UpfV9fZ2V0SW5zdGFuY2VOb2Rlcygpe3JldHVybiB0aGlzLl9faW5zdGFuY2UudGVtcGxhdGVJbmZvLmNoaWxkTm9kZXN9X19jcmVhdGVBbmRJbnNlcnRJbnN0YW5jZSh0KXtsZXQgcj10aGlzLl9fZGF0YUhvc3R8fHRoaXM7aWYoaXUmJiF0aGlzLl9fZGF0YUhvc3QpdGhyb3cgbmV3IEVycm9yKCJzdHJpY3RUZW1wbGF0ZVBvbGljeTogdGVtcGxhdGUgb3duZXIgbm90IHRydXN0ZWQiKTtsZXQgbj1yLl9iaW5kVGVtcGxhdGUodGhpcy5fX3RlbXBsYXRlLCEwKTtuLnJ1bkVmZmVjdHM9KGksbyxhKT0+e2xldCBzPXRoaXMuX19zeW5jSW5mbztpZih0aGlzLmlmKXMmJih0aGlzLl9fc3luY0luZm89bnVsbCx0aGlzLl9zaG93SGlkZUNoaWxkcmVuKCksbz1PYmplY3QuYXNzaWduKHMuY2hhbmdlZFByb3BzLG8pKSxpKG8sYSk7ZWxzZSBpZih0aGlzLl9faW5zdGFuY2UpaWYoc3x8KHM9dGhpcy5fX3N5bmNJbmZvPXtydW5FZmZlY3RzOmksY2hhbmdlZFByb3BzOnt9fSksYSlmb3IobGV0IGwgaW4gbyl7bGV0IGM9YXUobCk7cy5jaGFuZ2VkUHJvcHNbY109dGhpcy5fX2RhdGFIb3N0W2NdfWVsc2UgT2JqZWN0LmFzc2lnbihzLmNoYW5nZWRQcm9wcyxvKX0sdGhpcy5fX2luc3RhbmNlPXIuX3N0YW1wVGVtcGxhdGUodGhpcy5fX3RlbXBsYXRlLG4pLHVlKHQpLmluc2VydEJlZm9yZSh0aGlzLl9faW5zdGFuY2UsdGhpcyl9X19zeW5jSG9zdFByb3BlcnRpZXMoKXtsZXQgdD10aGlzLl9fc3luY0luZm87dCYmKHRoaXMuX19zeW5jSW5mbz1udWxsLHQucnVuRWZmZWN0cyh0LmNoYW5nZWRQcm9wcywhMSkpfV9fdGVhcmRvd25JbnN0YW5jZSgpe2xldCB0PXRoaXMuX19kYXRhSG9zdHx8dGhpczt0aGlzLl9faW5zdGFuY2UmJih0Ll9yZW1vdmVCb3VuZERvbSh0aGlzLl9faW5zdGFuY2UpLHRoaXMuX19pbnN0YW5jZT1udWxsLHRoaXMuX19zeW5jSW5mbz1udWxsKX1fc2hvd0hpZGVDaGlsZHJlbigpe2xldCB0PXRoaXMuX19oaWRlVGVtcGxhdGVDaGlsZHJlbl9ffHwhdGhpcy5pZjt0aGlzLl9faW5zdGFuY2UmJkJvb2xlYW4odGhpcy5fX2luc3RhbmNlLl9faGlkZGVuKSE9PXQmJih0aGlzLl9faW5zdGFuY2UuX19oaWRkZW49dCxvVyh0LHRoaXMuX19pbnN0YW5jZS50ZW1wbGF0ZUluZm8uY2hpbGROb2RlcykpLHR8fHRoaXMuX19zeW5jSG9zdFByb3BlcnRpZXMoKX19LGxXPWNsYXNzIGV4dGVuZHMgZDl7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX19jdG9yPW51bGwsdGhpcy5fX2luc3RhbmNlPW51bGwsdGhpcy5fX2ludmFsaWRQcm9wcz1udWxsfV9faGFzSW5zdGFuY2UoKXtyZXR1cm4gQm9vbGVhbih0aGlzLl9faW5zdGFuY2UpfV9fZ2V0SW5zdGFuY2VOb2Rlcygpe3JldHVybiB0aGlzLl9faW5zdGFuY2UuY2hpbGRyZW59X19jcmVhdGVBbmRJbnNlcnRJbnN0YW5jZSh0KXt0aGlzLl9fY3Rvcnx8KHRoaXMuX19jdG9yPXRjKHRoaXMuX190ZW1wbGF0ZSx0aGlzLHttdXRhYmxlRGF0YTohMCxmb3J3YXJkSG9zdFByb3A6ZnVuY3Rpb24ocixuKXt0aGlzLl9faW5zdGFuY2UmJih0aGlzLmlmP3RoaXMuX19pbnN0YW5jZS5mb3J3YXJkSG9zdFByb3AocixuKToodGhpcy5fX2ludmFsaWRQcm9wcz10aGlzLl9faW52YWxpZFByb3BzfHxPYmplY3QuY3JlYXRlKG51bGwpLHRoaXMuX19pbnZhbGlkUHJvcHNbYXUocildPSEwKSl9fSkpLHRoaXMuX19pbnN0YW5jZT1uZXcgdGhpcy5fX2N0b3IsdWUodCkuaW5zZXJ0QmVmb3JlKHRoaXMuX19pbnN0YW5jZS5yb290LHRoaXMpfV9fdGVhcmRvd25JbnN0YW5jZSgpe2lmKHRoaXMuX19pbnN0YW5jZSl7bGV0IHQ9dGhpcy5fX2luc3RhbmNlLmNoaWxkcmVuO2lmKHQmJnQubGVuZ3RoKXtsZXQgcj11ZSh0WzBdKS5wYXJlbnROb2RlO2lmKHIpe3I9dWUocik7Zm9yKGxldCBuPTAsaTtuPHQubGVuZ3RoJiYoaT10W25dKTtuKyspci5yZW1vdmVDaGlsZChpKX19dGhpcy5fX2ludmFsaWRQcm9wcz1udWxsLHRoaXMuX19pbnN0YW5jZT1udWxsfX1fX3N5bmNIb3N0UHJvcGVydGllcygpe2xldCB0PXRoaXMuX19pbnZhbGlkUHJvcHM7aWYodCl7dGhpcy5fX2ludmFsaWRQcm9wcz1udWxsO2ZvcihsZXQgciBpbiB0KXRoaXMuX19pbnN0YW5jZS5fc2V0UGVuZGluZ1Byb3BlcnR5KHIsdGhpcy5fX2RhdGFIb3N0W3JdKTt0aGlzLl9faW5zdGFuY2UuX2ZsdXNoUHJvcGVydGllcygpfX1fc2hvd0hpZGVDaGlsZHJlbigpe2xldCB0PXRoaXMuX19oaWRlVGVtcGxhdGVDaGlsZHJlbl9ffHwhdGhpcy5pZjt0aGlzLl9faW5zdGFuY2UmJkJvb2xlYW4odGhpcy5fX2luc3RhbmNlLl9faGlkZGVuKSE9PXQmJih0aGlzLl9faW5zdGFuY2UuX19oaWRkZW49dCx0aGlzLl9faW5zdGFuY2UuX3Nob3dIaWRlQ2hpbGRyZW4odCkpLHR8fHRoaXMuX19zeW5jSG9zdFByb3BlcnRpZXMoKX19LFVndD1rST9zVzpsVztjdXN0b21FbGVtZW50cy5kZWZpbmUoVWd0LmlzLFVndCk7dmFyIGNiZT1ObihlPT57bGV0IHQ9U20oZSk7Y2xhc3MgciBleHRlbmRzIHR7c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue2l0ZW1zOnt0eXBlOkFycmF5fSxtdWx0aTp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxzZWxlY3RlZDp7dHlwZTpPYmplY3Qsbm90aWZ5OiEwfSxzZWxlY3RlZEl0ZW06e3R5cGU6T2JqZWN0LG5vdGlmeTohMH0sdG9nZ2xlOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9fX1zdGF0aWMgZ2V0IG9ic2VydmVycygpe3JldHVyblsiX191cGRhdGVTZWxlY3Rpb24obXVsdGksIGl0ZW1zLiopIl19Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX19sYXN0SXRlbXM9bnVsbCx0aGlzLl9fbGFzdE11bHRpPW51bGwsdGhpcy5fX3NlbGVjdGVkTWFwPW51bGx9X191cGRhdGVTZWxlY3Rpb24oaSxvKXtsZXQgYT1vLnBhdGg7aWYoYT09SlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiaXRlbXMiLHRoaXMpKXtsZXQgcz1vLmJhc2V8fFtdLGw9dGhpcy5fX2xhc3RJdGVtcyxjPXRoaXMuX19sYXN0TXVsdGk7aWYoaSE9PWMmJnRoaXMuY2xlYXJTZWxlY3Rpb24oKSxsKXtsZXQgdT1pOShzLGwpO3RoaXMuX19hcHBseVNwbGljZXModSl9dGhpcy5fX2xhc3RJdGVtcz1zLHRoaXMuX19sYXN0TXVsdGk9aX1lbHNlIGlmKG8ucGF0aD09YCR7SlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiaXRlbXMiLHRoaXMpfS5zcGxpY2VzYCl0aGlzLl9fYXBwbHlTcGxpY2VzKG8udmFsdWUuaW5kZXhTcGxpY2VzKTtlbHNle2xldCBzPWEuc2xpY2UoYCR7SlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiaXRlbXMiLHRoaXMpfS5gLmxlbmd0aCksbD1wYXJzZUludChzLDEwKTtzLmluZGV4T2YoIi4iKTwwJiZzPT1sJiZ0aGlzLl9fZGVzZWxlY3RDaGFuZ2VkSWR4KGwpfX1fX2FwcGx5U3BsaWNlcyhpKXtsZXQgbz10aGlzLl9fc2VsZWN0ZWRNYXA7Zm9yKGxldCBzPTA7czxpLmxlbmd0aDtzKyspe2xldCBsPWlbc107by5mb3JFYWNoKChjLHUpPT57YzxsLmluZGV4fHwoYz49bC5pbmRleCtsLnJlbW92ZWQubGVuZ3RoP28uc2V0KHUsYytsLmFkZGVkQ291bnQtbC5yZW1vdmVkLmxlbmd0aCk6by5zZXQodSwtMSkpfSk7Zm9yKGxldCBjPTA7YzxsLmFkZGVkQ291bnQ7YysrKXtsZXQgdT1sLmluZGV4K2M7by5oYXModGhpcy5pdGVtc1t1XSkmJm8uc2V0KHRoaXMuaXRlbXNbdV0sdSl9fXRoaXMuX191cGRhdGVMaW5rcygpO2xldCBhPTA7by5mb3JFYWNoKChzLGwpPT57czwwPyh0aGlzLm11bHRpP3RoaXMuc3BsaWNlKEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoInNlbGVjdGVkIix0aGlzKSxhLDEpOnRoaXMuc2VsZWN0ZWQ9dGhpcy5zZWxlY3RlZEl0ZW09bnVsbCxvLmRlbGV0ZShsKSk6YSsrfSl9X191cGRhdGVMaW5rcygpe2lmKHRoaXMuX19kYXRhTGlua2VkUGF0aHM9e30sdGhpcy5tdWx0aSl7bGV0IGk9MDt0aGlzLl9fc2VsZWN0ZWRNYXAuZm9yRWFjaChvPT57bz49MCYmdGhpcy5saW5rUGF0aHMoYCR7SlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiaXRlbXMiLHRoaXMpfS4ke299YCxgJHtKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJzZWxlY3RlZCIsdGhpcyl9LiR7aSsrfWApfSl9ZWxzZSB0aGlzLl9fc2VsZWN0ZWRNYXAuZm9yRWFjaChpPT57dGhpcy5saW5rUGF0aHMoSlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgic2VsZWN0ZWQiLHRoaXMpLGAke0pTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoIml0ZW1zIix0aGlzKX0uJHtpfWApLHRoaXMubGlua1BhdGhzKEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoInNlbGVjdGVkSXRlbSIsdGhpcyksYCR7SlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiaXRlbXMiLHRoaXMpfS4ke2l9YCl9KX1jbGVhclNlbGVjdGlvbigpe3RoaXMuX19kYXRhTGlua2VkUGF0aHM9e30sdGhpcy5fX3NlbGVjdGVkTWFwPW5ldyBNYXAsdGhpcy5zZWxlY3RlZD10aGlzLm11bHRpP1tdOm51bGwsdGhpcy5zZWxlY3RlZEl0ZW09bnVsbH1pc1NlbGVjdGVkKGkpe3JldHVybiB0aGlzLl9fc2VsZWN0ZWRNYXAuaGFzKGkpfWlzSW5kZXhTZWxlY3RlZChpKXtyZXR1cm4gdGhpcy5pc1NlbGVjdGVkKHRoaXMuaXRlbXNbaV0pfV9fZGVzZWxlY3RDaGFuZ2VkSWR4KGkpe2xldCBvPXRoaXMuX19zZWxlY3RlZEluZGV4Rm9ySXRlbUluZGV4KGkpO2lmKG8+PTApe2xldCBhPTA7dGhpcy5fX3NlbGVjdGVkTWFwLmZvckVhY2goKHMsbCk9PntvPT1hKysmJnRoaXMuZGVzZWxlY3QobCl9KX19X19zZWxlY3RlZEluZGV4Rm9ySXRlbUluZGV4KGkpe2xldCBvPXRoaXMuX19kYXRhTGlua2VkUGF0aHNbYCR7SlNDb21waWxlcl9yZW5hbWVQcm9wZXJ0eSgiaXRlbXMiLHRoaXMpfS4ke2l9YF07aWYobylyZXR1cm4gcGFyc2VJbnQoby5zbGljZShgJHtKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJzZWxlY3RlZCIsdGhpcyl9LmAubGVuZ3RoKSwxMCl9ZGVzZWxlY3QoaSl7bGV0IG89dGhpcy5fX3NlbGVjdGVkTWFwLmdldChpKTtpZihvPj0wKXt0aGlzLl9fc2VsZWN0ZWRNYXAuZGVsZXRlKGkpO2xldCBhO3RoaXMubXVsdGkmJihhPXRoaXMuX19zZWxlY3RlZEluZGV4Rm9ySXRlbUluZGV4KG8pKSx0aGlzLl9fdXBkYXRlTGlua3MoKSx0aGlzLm11bHRpP3RoaXMuc3BsaWNlKEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoInNlbGVjdGVkIix0aGlzKSxhLDEpOnRoaXMuc2VsZWN0ZWQ9dGhpcy5zZWxlY3RlZEl0ZW09bnVsbH19ZGVzZWxlY3RJbmRleChpKXt0aGlzLmRlc2VsZWN0KHRoaXMuaXRlbXNbaV0pfXNlbGVjdChpKXt0aGlzLnNlbGVjdEluZGV4KHRoaXMuaXRlbXMuaW5kZXhPZihpKSl9c2VsZWN0SW5kZXgoaSl7bGV0IG89dGhpcy5pdGVtc1tpXTt0aGlzLmlzU2VsZWN0ZWQobyk/dGhpcy50b2dnbGUmJnRoaXMuZGVzZWxlY3RJbmRleChpKToodGhpcy5tdWx0aXx8dGhpcy5fX3NlbGVjdGVkTWFwLmNsZWFyKCksdGhpcy5fX3NlbGVjdGVkTWFwLnNldChvLGkpLHRoaXMuX191cGRhdGVMaW5rcygpLHRoaXMubXVsdGk/dGhpcy5wdXNoKEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoInNlbGVjdGVkIix0aGlzKSxvKTp0aGlzLnNlbGVjdGVkPXRoaXMuc2VsZWN0ZWRJdGVtPW8pfX1yZXR1cm4gcn0pO3ZhciB1YmU9Y2JlKG10KSxtOT1jbGFzcyBleHRlbmRzIHViZXtzdGF0aWMgZ2V0IGlzKCl7cmV0dXJuImFycmF5LXNlbGVjdG9yIn1zdGF0aWMgZ2V0IHRlbXBsYXRlKCl7cmV0dXJuIG51bGx9fTtjdXN0b21FbGVtZW50cy5kZWZpbmUobTkuaXMsbTkpO3ZhciBnOT1uZXcgWmw7d2luZG93LlNoYWR5Q1NTfHwod2luZG93LlNoYWR5Q1NTPXtwcmVwYXJlVGVtcGxhdGUoZSx0LHIpe30scHJlcGFyZVRlbXBsYXRlRG9tKGUsdCl7fSxwcmVwYXJlVGVtcGxhdGVTdHlsZXMoZSx0LHIpe30sc3R5bGVTdWJ0cmVlKGUsdCl7ZzkucHJvY2Vzc1N0eWxlcygpLEpNKGUsdCl9LHN0eWxlRWxlbWVudChlKXtnOS5wcm9jZXNzU3R5bGVzKCl9LHN0eWxlRG9jdW1lbnQoZSl7ZzkucHJvY2Vzc1N0eWxlcygpLEpNKGRvY3VtZW50LmJvZHksZSl9LGdldENvbXB1dGVkU3R5bGVWYWx1ZShlLHQpe3JldHVybiBZSShlLHQpfSxmbHVzaEN1c3RvbVN0eWxlcygpe30sbmF0aXZlQ3NzOk54LG5hdGl2ZVNoYWRvdzpoXyxjc3NCdWlsZDpNbSxkaXNhYmxlUnVudGltZTpHSX0pO3dpbmRvdy5TaGFkeUNTUy5DdXN0b21TdHlsZUludGVyZmFjZT1nOTt2YXIgcWd0PSJpbmNsdWRlIixoYmU9d2luZG93LlNoYWR5Q1NTLkN1c3RvbVN0eWxlSW50ZXJmYWNlLGNXPWNsYXNzIGV4dGVuZHMgSFRNTEVsZW1lbnR7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX3N0eWxlPW51bGwsaGJlLmFkZEN1c3RvbVN0eWxlKHRoaXMpfWdldFN0eWxlKCl7aWYodGhpcy5fc3R5bGUpcmV0dXJuIHRoaXMuX3N0eWxlO2xldCB0PXRoaXMucXVlcnlTZWxlY3Rvcigic3R5bGUiKTtpZighdClyZXR1cm4gbnVsbDt0aGlzLl9zdHlsZT10O2xldCByPXQuZ2V0QXR0cmlidXRlKHFndCk7cmV0dXJuIHImJih0LnJlbW92ZUF0dHJpYnV0ZShxZ3QpLHQudGV4dENvbnRlbnQ9dG10KHIpK3QudGV4dENvbnRlbnQpLHRoaXMub3duZXJEb2N1bWVudCE9PXdpbmRvdy5kb2N1bWVudCYmd2luZG93LmRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQodGhpcyksdGhpcy5fc3R5bGV9fTt3aW5kb3cuY3VzdG9tRWxlbWVudHMuZGVmaW5lKCJjdXN0b20tc3R5bGUiLGNXKTt2YXIgR2d0O0dndD1HeC5fbXV0YWJsZVByb3BlcnR5Q2hhbmdlO3ZhciBXZ3Q9e3Byb3BlcnRpZXM6e211dGFibGVEYXRhOkJvb2xlYW59LF9zaG91bGRQcm9wZXJ0eUNoYW5nZShlLHQscil7cmV0dXJuIEdndCh0aGlzLGUsdCxyLHRoaXMubXV0YWJsZURhdGEpfX07dmFyIERhPUd0KEhUTUxFbGVtZW50KS5wcm90b3R5cGU7dmFyIF85PW5ldyBTZXQsSnM9e3Byb3BlcnRpZXM6e19wYXJlbnRSZXNpemFibGU6e3R5cGU6T2JqZWN0LG9ic2VydmVyOiJfcGFyZW50UmVzaXphYmxlQ2hhbmdlZCJ9LF9ub3RpZnlpbmdEZXNjZW5kYW50Ont0eXBlOkJvb2xlYW4sdmFsdWU6ITF9fSxsaXN0ZW5lcnM6eyJpcm9uLXJlcXVlc3QtcmVzaXplLW5vdGlmaWNhdGlvbnMiOiJfb25Jcm9uUmVxdWVzdFJlc2l6ZU5vdGlmaWNhdGlvbnMifSxjcmVhdGVkOmZ1bmN0aW9uKCl7dGhpcy5faW50ZXJlc3RlZFJlc2l6YWJsZXM9W10sdGhpcy5fYm91bmROb3RpZnlSZXNpemU9dGhpcy5ub3RpZnlSZXNpemUuYmluZCh0aGlzKSx0aGlzLl9ib3VuZE9uRGVzY2VuZGFudElyb25SZXNpemU9dGhpcy5fb25EZXNjZW5kYW50SXJvblJlc2l6ZS5iaW5kKHRoaXMpfSxhdHRhY2hlZDpmdW5jdGlvbigpe3RoaXMuX3JlcXVlc3RSZXNpemVOb3RpZmljYXRpb25zKCl9LGRldGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5fcGFyZW50UmVzaXphYmxlP3RoaXMuX3BhcmVudFJlc2l6YWJsZS5zdG9wUmVzaXplTm90aWZpY2F0aW9uc0Zvcih0aGlzKTooXzkuZGVsZXRlKHRoaXMpLHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCJyZXNpemUiLHRoaXMuX2JvdW5kTm90aWZ5UmVzaXplKSksdGhpcy5fcGFyZW50UmVzaXphYmxlPW51bGx9LG5vdGlmeVJlc2l6ZTpmdW5jdGlvbigpeyF0aGlzLmlzQXR0YWNoZWR8fCh0aGlzLl9pbnRlcmVzdGVkUmVzaXphYmxlcy5mb3JFYWNoKGZ1bmN0aW9uKGUpe3RoaXMucmVzaXplclNob3VsZE5vdGlmeShlKSYmdGhpcy5fbm90aWZ5RGVzY2VuZGFudChlKX0sdGhpcyksdGhpcy5fZmlyZVJlc2l6ZSgpKX0sYXNzaWduUGFyZW50UmVzaXphYmxlOmZ1bmN0aW9uKGUpe3RoaXMuX3BhcmVudFJlc2l6YWJsZSYmdGhpcy5fcGFyZW50UmVzaXphYmxlLnN0b3BSZXNpemVOb3RpZmljYXRpb25zRm9yKHRoaXMpLHRoaXMuX3BhcmVudFJlc2l6YWJsZT1lLGUmJmUuX2ludGVyZXN0ZWRSZXNpemFibGVzLmluZGV4T2YodGhpcyk9PT0tMSYmKGUuX2ludGVyZXN0ZWRSZXNpemFibGVzLnB1c2godGhpcyksZS5fc3Vic2NyaWJlSXJvblJlc2l6ZSh0aGlzKSl9LHN0b3BSZXNpemVOb3RpZmljYXRpb25zRm9yOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX2ludGVyZXN0ZWRSZXNpemFibGVzLmluZGV4T2YoZSk7dD4tMSYmKHRoaXMuX2ludGVyZXN0ZWRSZXNpemFibGVzLnNwbGljZSh0LDEpLHRoaXMuX3Vuc3Vic2NyaWJlSXJvblJlc2l6ZShlKSl9LF9zdWJzY3JpYmVJcm9uUmVzaXplOmZ1bmN0aW9uKGUpe2UuYWRkRXZlbnRMaXN0ZW5lcigiaXJvbi1yZXNpemUiLHRoaXMuX2JvdW5kT25EZXNjZW5kYW50SXJvblJlc2l6ZSl9LF91bnN1YnNjcmliZUlyb25SZXNpemU6ZnVuY3Rpb24oZSl7ZS5yZW1vdmVFdmVudExpc3RlbmVyKCJpcm9uLXJlc2l6ZSIsdGhpcy5fYm91bmRPbkRlc2NlbmRhbnRJcm9uUmVzaXplKX0scmVzaXplclNob3VsZE5vdGlmeTpmdW5jdGlvbihlKXtyZXR1cm4hMH0sX29uRGVzY2VuZGFudElyb25SZXNpemU6ZnVuY3Rpb24oZSl7aWYodGhpcy5fbm90aWZ5aW5nRGVzY2VuZGFudCl7ZS5zdG9wUHJvcGFnYXRpb24oKTtyZXR1cm59Y198fHRoaXMuX2ZpcmVSZXNpemUoKX0sX2ZpcmVSZXNpemU6ZnVuY3Rpb24oKXt0aGlzLmZpcmUoImlyb24tcmVzaXplIixudWxsLHtub2RlOnRoaXMsYnViYmxlczohMX0pfSxfb25Jcm9uUmVxdWVzdFJlc2l6ZU5vdGlmaWNhdGlvbnM6ZnVuY3Rpb24oZSl7dmFyIHQ9enQoZSkucm9vdFRhcmdldDt0IT09dGhpcyYmKHQuYXNzaWduUGFyZW50UmVzaXphYmxlKHRoaXMpLHRoaXMuX25vdGlmeURlc2NlbmRhbnQodCksZS5zdG9wUHJvcGFnYXRpb24oKSl9LF9wYXJlbnRSZXNpemFibGVDaGFuZ2VkOmZ1bmN0aW9uKGUpe2UmJndpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCJyZXNpemUiLHRoaXMuX2JvdW5kTm90aWZ5UmVzaXplKX0sX25vdGlmeURlc2NlbmRhbnQ6ZnVuY3Rpb24oZSl7IXRoaXMuaXNBdHRhY2hlZHx8KHRoaXMuX25vdGlmeWluZ0Rlc2NlbmRhbnQ9ITAsZS5ub3RpZnlSZXNpemUoKSx0aGlzLl9ub3RpZnlpbmdEZXNjZW5kYW50PSExKX0sX3JlcXVlc3RSZXNpemVOb3RpZmljYXRpb25zOmZ1bmN0aW9uKCl7aWYoISF0aGlzLmlzQXR0YWNoZWQpaWYoZG9jdW1lbnQucmVhZHlTdGF0ZT09PSJsb2FkaW5nIil7dmFyIGU9dGhpcy5fcmVxdWVzdFJlc2l6ZU5vdGlmaWNhdGlvbnMuYmluZCh0aGlzKTtkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCJyZWFkeXN0YXRlY2hhbmdlIixmdW5jdGlvbiB0KCl7ZG9jdW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcigicmVhZHlzdGF0ZWNoYW5nZSIsdCksZSgpfSl9ZWxzZSB0aGlzLl9maW5kUGFyZW50KCksdGhpcy5fcGFyZW50UmVzaXphYmxlP3RoaXMuX3BhcmVudFJlc2l6YWJsZS5faW50ZXJlc3RlZFJlc2l6YWJsZXMuZm9yRWFjaChmdW5jdGlvbih0KXt0IT09dGhpcyYmdC5fZmluZFBhcmVudCgpfSx0aGlzKTooXzkuZm9yRWFjaChmdW5jdGlvbih0KXt0IT09dGhpcyYmdC5fZmluZFBhcmVudCgpfSx0aGlzKSx3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigicmVzaXplIix0aGlzLl9ib3VuZE5vdGlmeVJlc2l6ZSksdGhpcy5ub3RpZnlSZXNpemUoKSl9LF9maW5kUGFyZW50OmZ1bmN0aW9uKCl7dGhpcy5hc3NpZ25QYXJlbnRSZXNpemFibGUobnVsbCksdGhpcy5maXJlKCJpcm9uLXJlcXVlc3QtcmVzaXplLW5vdGlmaWNhdGlvbnMiLG51bGwse25vZGU6dGhpcyxidWJibGVzOiEwLGNhbmNlbGFibGU6ITB9KSx0aGlzLl9wYXJlbnRSZXNpemFibGU/XzkuZGVsZXRlKHRoaXMpOl85LmFkZCh0aGlzKX19O1l0KHtfdGVtcGxhdGU6UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgIHRyYW5zaXRpb24tZHVyYXRpb246IHZhcigtLWlyb24tY29sbGFwc2UtdHJhbnNpdGlvbi1kdXJhdGlvbiwgMzAwbXMpOwogICAgICAgIC8qIFNhZmFyaSAxMCBuZWVkcyB0aGlzIHByb3BlcnR5IHByZWZpeGVkIHRvIGNvcnJlY3RseSBhcHBseSB0aGUgY3VzdG9tIHByb3BlcnR5ICovCiAgICAgICAgLXdlYmtpdC10cmFuc2l0aW9uLWR1cmF0aW9uOiB2YXIoLS1pcm9uLWNvbGxhcHNlLXRyYW5zaXRpb24tZHVyYXRpb24sIDMwMG1zKTsKICAgICAgICBvdmVyZmxvdzogdmlzaWJsZTsKICAgICAgfQoKICAgICAgOmhvc3QoLmlyb24tY29sbGFwc2UtY2xvc2VkKSB7CiAgICAgICAgZGlzcGxheTogbm9uZTsKICAgICAgfQoKICAgICAgOmhvc3QoOm5vdCguaXJvbi1jb2xsYXBzZS1vcGVuZWQpKSB7CiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgfQogICAgPC9zdHlsZT4KCiAgICA8c2xvdD48L3Nsb3Q+CmAsaXM6Imlyb24tY29sbGFwc2UiLGJlaGF2aW9yczpbSnNdLHByb3BlcnRpZXM6e2hvcml6b250YWw6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxvYnNlcnZlcjoiX2hvcml6b250YWxDaGFuZ2VkIn0sb3BlbmVkOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITEsbm90aWZ5OiEwLG9ic2VydmVyOiJfb3BlbmVkQ2hhbmdlZCJ9LHRyYW5zaXRpb25pbmc6e3R5cGU6Qm9vbGVhbixub3RpZnk6ITAscmVhZE9ubHk6ITB9LG5vQW5pbWF0aW9uOnt0eXBlOkJvb2xlYW59LF9kZXNpcmVkU2l6ZTp7dHlwZTpTdHJpbmcsdmFsdWU6IiJ9fSxnZXQgZGltZW5zaW9uKCl7cmV0dXJuIHRoaXMuaG9yaXpvbnRhbD8id2lkdGgiOiJoZWlnaHQifSxnZXQgX2RpbWVuc2lvbk1heCgpe3JldHVybiB0aGlzLmhvcml6b250YWw/Im1heFdpZHRoIjoibWF4SGVpZ2h0In0sZ2V0IF9kaW1lbnNpb25NYXhDc3MoKXtyZXR1cm4gdGhpcy5ob3Jpem9udGFsPyJtYXgtd2lkdGgiOiJtYXgtaGVpZ2h0In0saG9zdEF0dHJpYnV0ZXM6e3JvbGU6Imdyb3VwIiwiYXJpYS1oaWRkZW4iOiJ0cnVlIn0sbGlzdGVuZXJzOnt0cmFuc2l0aW9uZW5kOiJfb25UcmFuc2l0aW9uRW5kIn0sdG9nZ2xlOmZ1bmN0aW9uKCl7dGhpcy5vcGVuZWQ9IXRoaXMub3BlbmVkfSxzaG93OmZ1bmN0aW9uKCl7dGhpcy5vcGVuZWQ9ITB9LGhpZGU6ZnVuY3Rpb24oKXt0aGlzLm9wZW5lZD0hMX0sdXBkYXRlU2l6ZTpmdW5jdGlvbihlLHQpe2U9ZT09PSJhdXRvIj8iIjplO3ZhciByPXQmJiF0aGlzLm5vQW5pbWF0aW9uJiZ0aGlzLmlzQXR0YWNoZWQmJnRoaXMuX2Rlc2lyZWRTaXplIT09ZTtpZih0aGlzLl9kZXNpcmVkU2l6ZT1lLHRoaXMuX3VwZGF0ZVRyYW5zaXRpb24oITEpLHIpe3ZhciBuPXRoaXMuX2NhbGNTaXplKCk7ZT09PSIiJiYodGhpcy5zdHlsZVt0aGlzLl9kaW1lbnNpb25NYXhdPSIiLGU9dGhpcy5fY2FsY1NpemUoKSksdGhpcy5zdHlsZVt0aGlzLl9kaW1lbnNpb25NYXhdPW4sdGhpcy5zY3JvbGxUb3A9dGhpcy5zY3JvbGxUb3AsdGhpcy5fdXBkYXRlVHJhbnNpdGlvbighMCkscj1lIT09bn10aGlzLnN0eWxlW3RoaXMuX2RpbWVuc2lvbk1heF09ZSxyfHx0aGlzLl90cmFuc2l0aW9uRW5kKCl9LGVuYWJsZVRyYW5zaXRpb246ZnVuY3Rpb24oZSl7RGEuX3dhcm4oImBlbmFibGVUcmFuc2l0aW9uKClgIGlzIGRlcHJlY2F0ZWQsIHVzZSBgbm9BbmltYXRpb25gIGluc3RlYWQuIiksdGhpcy5ub0FuaW1hdGlvbj0hZX0sX3VwZGF0ZVRyYW5zaXRpb246ZnVuY3Rpb24oZSl7dGhpcy5zdHlsZS50cmFuc2l0aW9uRHVyYXRpb249ZSYmIXRoaXMubm9BbmltYXRpb24/IiI6IjBzIn0sX2hvcml6b250YWxDaGFuZ2VkOmZ1bmN0aW9uKCl7dGhpcy5zdHlsZS50cmFuc2l0aW9uUHJvcGVydHk9dGhpcy5fZGltZW5zaW9uTWF4Q3NzO3ZhciBlPXRoaXMuX2RpbWVuc2lvbk1heD09PSJtYXhXaWR0aCI/Im1heEhlaWdodCI6Im1heFdpZHRoIjt0aGlzLnN0eWxlW2VdPSIiLHRoaXMudXBkYXRlU2l6ZSh0aGlzLm9wZW5lZD8iYXV0byI6IjBweCIsITEpfSxfb3BlbmVkQ2hhbmdlZDpmdW5jdGlvbigpe3RoaXMuc2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIsIXRoaXMub3BlbmVkKSx0aGlzLl9zZXRUcmFuc2l0aW9uaW5nKCEwKSx0aGlzLnRvZ2dsZUNsYXNzKCJpcm9uLWNvbGxhcHNlLWNsb3NlZCIsITEpLHRoaXMudG9nZ2xlQ2xhc3MoImlyb24tY29sbGFwc2Utb3BlbmVkIiwhMSksdGhpcy51cGRhdGVTaXplKHRoaXMub3BlbmVkPyJhdXRvIjoiMHB4IiwhMCksdGhpcy5vcGVuZWQmJnRoaXMuZm9jdXMoKX0sX3RyYW5zaXRpb25FbmQ6ZnVuY3Rpb24oKXt0aGlzLnN0eWxlW3RoaXMuX2RpbWVuc2lvbk1heF09dGhpcy5fZGVzaXJlZFNpemUsdGhpcy50b2dnbGVDbGFzcygiaXJvbi1jb2xsYXBzZS1jbG9zZWQiLCF0aGlzLm9wZW5lZCksdGhpcy50b2dnbGVDbGFzcygiaXJvbi1jb2xsYXBzZS1vcGVuZWQiLHRoaXMub3BlbmVkKSx0aGlzLl91cGRhdGVUcmFuc2l0aW9uKCExKSx0aGlzLm5vdGlmeVJlc2l6ZSgpLHRoaXMuX3NldFRyYW5zaXRpb25pbmcoITEpfSxfb25UcmFuc2l0aW9uRW5kOmZ1bmN0aW9uKGUpe3p0KGUpLnJvb3RUYXJnZXQ9PT10aGlzJiZ0aGlzLl90cmFuc2l0aW9uRW5kKCl9LF9jYWxjU2l6ZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpW3RoaXMuZGltZW5zaW9uXSsicHgifX0pO3ZhciBZZ3Q9UWAKLyogTW9zdCBjb21tb24gdXNlZCBmbGV4IHN0eWxlcyovCjxkb20tbW9kdWxlIGlkPSJpcm9uLWZsZXgiPgogIDx0ZW1wbGF0ZT4KICAgIDxzdHlsZT4KICAgICAgLmxheW91dC5ob3Jpem9udGFsLAogICAgICAubGF5b3V0LnZlcnRpY2FsIHsKICAgICAgICBkaXNwbGF5OiAtbXMtZmxleGJveDsKICAgICAgICBkaXNwbGF5OiAtd2Via2l0LWZsZXg7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgfQoKICAgICAgLmxheW91dC5pbmxpbmUgewogICAgICAgIGRpc3BsYXk6IC1tcy1pbmxpbmUtZmxleGJveDsKICAgICAgICBkaXNwbGF5OiAtd2Via2l0LWlubGluZS1mbGV4OwogICAgICAgIGRpc3BsYXk6IGlubGluZS1mbGV4OwogICAgICB9CgogICAgICAubGF5b3V0Lmhvcml6b250YWwgewogICAgICAgIC1tcy1mbGV4LWRpcmVjdGlvbjogcm93OwogICAgICAgIC13ZWJraXQtZmxleC1kaXJlY3Rpb246IHJvdzsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogcm93OwogICAgICB9CgogICAgICAubGF5b3V0LnZlcnRpY2FsIHsKICAgICAgICAtbXMtZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgICAtd2Via2l0LWZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgfQoKICAgICAgLmxheW91dC53cmFwIHsKICAgICAgICAtbXMtZmxleC13cmFwOiB3cmFwOwogICAgICAgIC13ZWJraXQtZmxleC13cmFwOiB3cmFwOwogICAgICAgIGZsZXgtd3JhcDogd3JhcDsKICAgICAgfQoKICAgICAgLmxheW91dC5uby13cmFwIHsKICAgICAgICAtbXMtZmxleC13cmFwOiBub3dyYXA7CiAgICAgICAgLXdlYmtpdC1mbGV4LXdyYXA6IG5vd3JhcDsKICAgICAgICBmbGV4LXdyYXA6IG5vd3JhcDsKICAgICAgfQoKICAgICAgLmxheW91dC5jZW50ZXIsCiAgICAgIC5sYXlvdXQuY2VudGVyLWNlbnRlciB7CiAgICAgICAgLW1zLWZsZXgtYWxpZ246IGNlbnRlcjsKICAgICAgICAtd2Via2l0LWFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgfQoKICAgICAgLmxheW91dC5jZW50ZXItanVzdGlmaWVkLAogICAgICAubGF5b3V0LmNlbnRlci1jZW50ZXIgewogICAgICAgIC1tcy1mbGV4LXBhY2s6IGNlbnRlcjsKICAgICAgICAtd2Via2l0LWp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICAgIGp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICB9CgogICAgICAuZmxleCB7CiAgICAgICAgLW1zLWZsZXg6IDEgMSAwLjAwMDAwMDAwMXB4OwogICAgICAgIC13ZWJraXQtZmxleDogMTsKICAgICAgICBmbGV4OiAxOwogICAgICAgIC13ZWJraXQtZmxleC1iYXNpczogMC4wMDAwMDAwMDFweDsKICAgICAgICBmbGV4LWJhc2lzOiAwLjAwMDAwMDAwMXB4OwogICAgICB9CgogICAgICAuZmxleC1hdXRvIHsKICAgICAgICAtbXMtZmxleDogMSAxIGF1dG87CiAgICAgICAgLXdlYmtpdC1mbGV4OiAxIDEgYXV0bzsKICAgICAgICBmbGV4OiAxIDEgYXV0bzsKICAgICAgfQoKICAgICAgLmZsZXgtbm9uZSB7CiAgICAgICAgLW1zLWZsZXg6IG5vbmU7CiAgICAgICAgLXdlYmtpdC1mbGV4OiBub25lOwogICAgICAgIGZsZXg6IG5vbmU7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC90ZW1wbGF0ZT4KPC9kb20tbW9kdWxlPgovKiBCYXNpYyBmbGV4Ym94IHJldmVyc2Ugc3R5bGVzICovCjxkb20tbW9kdWxlIGlkPSJpcm9uLWZsZXgtcmV2ZXJzZSI+CiAgPHRlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICAubGF5b3V0Lmhvcml6b250YWwtcmV2ZXJzZSwKICAgICAgLmxheW91dC52ZXJ0aWNhbC1yZXZlcnNlIHsKICAgICAgICBkaXNwbGF5OiAtbXMtZmxleGJveDsKICAgICAgICBkaXNwbGF5OiAtd2Via2l0LWZsZXg7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgfQoKICAgICAgLmxheW91dC5ob3Jpem9udGFsLXJldmVyc2UgewogICAgICAgIC1tcy1mbGV4LWRpcmVjdGlvbjogcm93LXJldmVyc2U7CiAgICAgICAgLXdlYmtpdC1mbGV4LWRpcmVjdGlvbjogcm93LXJldmVyc2U7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IHJvdy1yZXZlcnNlOwogICAgICB9CgogICAgICAubGF5b3V0LnZlcnRpY2FsLXJldmVyc2UgewogICAgICAgIC1tcy1mbGV4LWRpcmVjdGlvbjogY29sdW1uLXJldmVyc2U7CiAgICAgICAgLXdlYmtpdC1mbGV4LWRpcmVjdGlvbjogY29sdW1uLXJldmVyc2U7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbi1yZXZlcnNlOwogICAgICB9CgogICAgICAubGF5b3V0LndyYXAtcmV2ZXJzZSB7CiAgICAgICAgLW1zLWZsZXgtd3JhcDogd3JhcC1yZXZlcnNlOwogICAgICAgIC13ZWJraXQtZmxleC13cmFwOiB3cmFwLXJldmVyc2U7CiAgICAgICAgZmxleC13cmFwOiB3cmFwLXJldmVyc2U7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC90ZW1wbGF0ZT4KPC9kb20tbW9kdWxlPgovKiBGbGV4Ym94IGFsaWdubWVudCAqLwo8ZG9tLW1vZHVsZSBpZD0iaXJvbi1mbGV4LWFsaWdubWVudCI+CiAgPHRlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICAvKioKICAgICAgICogQWxpZ25tZW50IGluIGNyb3NzIGF4aXMuCiAgICAgICAqLwogICAgICAubGF5b3V0LnN0YXJ0IHsKICAgICAgICAtbXMtZmxleC1hbGlnbjogc3RhcnQ7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1pdGVtczogZmxleC1zdGFydDsKICAgICAgICBhbGlnbi1pdGVtczogZmxleC1zdGFydDsKICAgICAgfQoKICAgICAgLmxheW91dC5jZW50ZXIsCiAgICAgIC5sYXlvdXQuY2VudGVyLWNlbnRlciB7CiAgICAgICAgLW1zLWZsZXgtYWxpZ246IGNlbnRlcjsKICAgICAgICAtd2Via2l0LWFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgfQoKICAgICAgLmxheW91dC5lbmQgewogICAgICAgIC1tcy1mbGV4LWFsaWduOiBlbmQ7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1pdGVtczogZmxleC1lbmQ7CiAgICAgICAgYWxpZ24taXRlbXM6IGZsZXgtZW5kOwogICAgICB9CgogICAgICAubGF5b3V0LmJhc2VsaW5lIHsKICAgICAgICAtbXMtZmxleC1hbGlnbjogYmFzZWxpbmU7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1pdGVtczogYmFzZWxpbmU7CiAgICAgICAgYWxpZ24taXRlbXM6IGJhc2VsaW5lOwogICAgICB9CgogICAgICAvKioKICAgICAgICogQWxpZ25tZW50IGluIG1haW4gYXhpcy4KICAgICAgICovCiAgICAgIC5sYXlvdXQuc3RhcnQtanVzdGlmaWVkIHsKICAgICAgICAtbXMtZmxleC1wYWNrOiBzdGFydDsKICAgICAgICAtd2Via2l0LWp1c3RpZnktY29udGVudDogZmxleC1zdGFydDsKICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGZsZXgtc3RhcnQ7CiAgICAgIH0KCiAgICAgIC5sYXlvdXQuY2VudGVyLWp1c3RpZmllZCwKICAgICAgLmxheW91dC5jZW50ZXItY2VudGVyIHsKICAgICAgICAtbXMtZmxleC1wYWNrOiBjZW50ZXI7CiAgICAgICAgLXdlYmtpdC1qdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgfQoKICAgICAgLmxheW91dC5lbmQtanVzdGlmaWVkIHsKICAgICAgICAtbXMtZmxleC1wYWNrOiBlbmQ7CiAgICAgICAgLXdlYmtpdC1qdXN0aWZ5LWNvbnRlbnQ6IGZsZXgtZW5kOwogICAgICAgIGp1c3RpZnktY29udGVudDogZmxleC1lbmQ7CiAgICAgIH0KCiAgICAgIC5sYXlvdXQuYXJvdW5kLWp1c3RpZmllZCB7CiAgICAgICAgLW1zLWZsZXgtcGFjazogZGlzdHJpYnV0ZTsKICAgICAgICAtd2Via2l0LWp1c3RpZnktY29udGVudDogc3BhY2UtYXJvdW5kOwogICAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYXJvdW5kOwogICAgICB9CgogICAgICAubGF5b3V0Lmp1c3RpZmllZCB7CiAgICAgICAgLW1zLWZsZXgtcGFjazoganVzdGlmeTsKICAgICAgICAtd2Via2l0LWp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsKICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgIH0KCiAgICAgIC8qKgogICAgICAgKiBTZWxmIGFsaWdubWVudC4KICAgICAgICovCiAgICAgIC5zZWxmLXN0YXJ0IHsKICAgICAgICAtbXMtYWxpZ24tc2VsZjogZmxleC1zdGFydDsKICAgICAgICAtd2Via2l0LWFsaWduLXNlbGY6IGZsZXgtc3RhcnQ7CiAgICAgICAgYWxpZ24tc2VsZjogZmxleC1zdGFydDsKICAgICAgfQoKICAgICAgLnNlbGYtY2VudGVyIHsKICAgICAgICAtbXMtYWxpZ24tc2VsZjogY2VudGVyOwogICAgICAgIC13ZWJraXQtYWxpZ24tc2VsZjogY2VudGVyOwogICAgICAgIGFsaWduLXNlbGY6IGNlbnRlcjsKICAgICAgfQoKICAgICAgLnNlbGYtZW5kIHsKICAgICAgICAtbXMtYWxpZ24tc2VsZjogZmxleC1lbmQ7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1zZWxmOiBmbGV4LWVuZDsKICAgICAgICBhbGlnbi1zZWxmOiBmbGV4LWVuZDsKICAgICAgfQoKICAgICAgLnNlbGYtc3RyZXRjaCB7CiAgICAgICAgLW1zLWFsaWduLXNlbGY6IHN0cmV0Y2g7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1zZWxmOiBzdHJldGNoOwogICAgICAgIGFsaWduLXNlbGY6IHN0cmV0Y2g7CiAgICAgIH0KCiAgICAgIC5zZWxmLWJhc2VsaW5lIHsKICAgICAgICAtbXMtYWxpZ24tc2VsZjogYmFzZWxpbmU7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1zZWxmOiBiYXNlbGluZTsKICAgICAgICBhbGlnbi1zZWxmOiBiYXNlbGluZTsKICAgICAgfQoKICAgICAgLyoqCiAgICAgICAqIG11bHRpLWxpbmUgYWxpZ25tZW50IGluIG1haW4gYXhpcy4KICAgICAgICovCiAgICAgIC5sYXlvdXQuc3RhcnQtYWxpZ25lZCB7CiAgICAgICAgLW1zLWZsZXgtbGluZS1wYWNrOiBzdGFydDsgIC8qIElFMTAgKi8KICAgICAgICAtbXMtYWxpZ24tY29udGVudDogZmxleC1zdGFydDsKICAgICAgICAtd2Via2l0LWFsaWduLWNvbnRlbnQ6IGZsZXgtc3RhcnQ7CiAgICAgICAgYWxpZ24tY29udGVudDogZmxleC1zdGFydDsKICAgICAgfQoKICAgICAgLmxheW91dC5lbmQtYWxpZ25lZCB7CiAgICAgICAgLW1zLWZsZXgtbGluZS1wYWNrOiBlbmQ7ICAvKiBJRTEwICovCiAgICAgICAgLW1zLWFsaWduLWNvbnRlbnQ6IGZsZXgtZW5kOwogICAgICAgIC13ZWJraXQtYWxpZ24tY29udGVudDogZmxleC1lbmQ7CiAgICAgICAgYWxpZ24tY29udGVudDogZmxleC1lbmQ7CiAgICAgIH0KCiAgICAgIC5sYXlvdXQuY2VudGVyLWFsaWduZWQgewogICAgICAgIC1tcy1mbGV4LWxpbmUtcGFjazogY2VudGVyOyAgLyogSUUxMCAqLwogICAgICAgIC1tcy1hbGlnbi1jb250ZW50OiBjZW50ZXI7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1jb250ZW50OiBjZW50ZXI7CiAgICAgICAgYWxpZ24tY29udGVudDogY2VudGVyOwogICAgICB9CgogICAgICAubGF5b3V0LmJldHdlZW4tYWxpZ25lZCB7CiAgICAgICAgLW1zLWZsZXgtbGluZS1wYWNrOiBqdXN0aWZ5OyAgLyogSUUxMCAqLwogICAgICAgIC1tcy1hbGlnbi1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICAgIC13ZWJraXQtYWxpZ24tY29udGVudDogc3BhY2UtYmV0d2VlbjsKICAgICAgICBhbGlnbi1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICB9CgogICAgICAubGF5b3V0LmFyb3VuZC1hbGlnbmVkIHsKICAgICAgICAtbXMtZmxleC1saW5lLXBhY2s6IGRpc3RyaWJ1dGU7ICAvKiBJRTEwICovCiAgICAgICAgLW1zLWFsaWduLWNvbnRlbnQ6IHNwYWNlLWFyb3VuZDsKICAgICAgICAtd2Via2l0LWFsaWduLWNvbnRlbnQ6IHNwYWNlLWFyb3VuZDsKICAgICAgICBhbGlnbi1jb250ZW50OiBzcGFjZS1hcm91bmQ7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC90ZW1wbGF0ZT4KPC9kb20tbW9kdWxlPgovKiBOb24tZmxleGJveCBwb3NpdGlvbmluZyBoZWxwZXIgc3R5bGVzICovCjxkb20tbW9kdWxlIGlkPSJpcm9uLWZsZXgtZmFjdG9ycyI+CiAgPHRlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICAuZmxleCwKICAgICAgLmZsZXgtMSB7CiAgICAgICAgLW1zLWZsZXg6IDEgMSAwLjAwMDAwMDAwMXB4OwogICAgICAgIC13ZWJraXQtZmxleDogMTsKICAgICAgICBmbGV4OiAxOwogICAgICAgIC13ZWJraXQtZmxleC1iYXNpczogMC4wMDAwMDAwMDFweDsKICAgICAgICBmbGV4LWJhc2lzOiAwLjAwMDAwMDAwMXB4OwogICAgICB9CgogICAgICAuZmxleC0yIHsKICAgICAgICAtbXMtZmxleDogMjsKICAgICAgICAtd2Via2l0LWZsZXg6IDI7CiAgICAgICAgZmxleDogMjsKICAgICAgfQoKICAgICAgLmZsZXgtMyB7CiAgICAgICAgLW1zLWZsZXg6IDM7CiAgICAgICAgLXdlYmtpdC1mbGV4OiAzOwogICAgICAgIGZsZXg6IDM7CiAgICAgIH0KCiAgICAgIC5mbGV4LTQgewogICAgICAgIC1tcy1mbGV4OiA0OwogICAgICAgIC13ZWJraXQtZmxleDogNDsKICAgICAgICBmbGV4OiA0OwogICAgICB9CgogICAgICAuZmxleC01IHsKICAgICAgICAtbXMtZmxleDogNTsKICAgICAgICAtd2Via2l0LWZsZXg6IDU7CiAgICAgICAgZmxleDogNTsKICAgICAgfQoKICAgICAgLmZsZXgtNiB7CiAgICAgICAgLW1zLWZsZXg6IDY7CiAgICAgICAgLXdlYmtpdC1mbGV4OiA2OwogICAgICAgIGZsZXg6IDY7CiAgICAgIH0KCiAgICAgIC5mbGV4LTcgewogICAgICAgIC1tcy1mbGV4OiA3OwogICAgICAgIC13ZWJraXQtZmxleDogNzsKICAgICAgICBmbGV4OiA3OwogICAgICB9CgogICAgICAuZmxleC04IHsKICAgICAgICAtbXMtZmxleDogODsKICAgICAgICAtd2Via2l0LWZsZXg6IDg7CiAgICAgICAgZmxleDogODsKICAgICAgfQoKICAgICAgLmZsZXgtOSB7CiAgICAgICAgLW1zLWZsZXg6IDk7CiAgICAgICAgLXdlYmtpdC1mbGV4OiA5OwogICAgICAgIGZsZXg6IDk7CiAgICAgIH0KCiAgICAgIC5mbGV4LTEwIHsKICAgICAgICAtbXMtZmxleDogMTA7CiAgICAgICAgLXdlYmtpdC1mbGV4OiAxMDsKICAgICAgICBmbGV4OiAxMDsKICAgICAgfQoKICAgICAgLmZsZXgtMTEgewogICAgICAgIC1tcy1mbGV4OiAxMTsKICAgICAgICAtd2Via2l0LWZsZXg6IDExOwogICAgICAgIGZsZXg6IDExOwogICAgICB9CgogICAgICAuZmxleC0xMiB7CiAgICAgICAgLW1zLWZsZXg6IDEyOwogICAgICAgIC13ZWJraXQtZmxleDogMTI7CiAgICAgICAgZmxleDogMTI7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC90ZW1wbGF0ZT4KPC9kb20tbW9kdWxlPgo8ZG9tLW1vZHVsZSBpZD0iaXJvbi1wb3NpdGlvbmluZyI+CiAgPHRlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICAuYmxvY2sgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICB9CgogICAgICBbaGlkZGVuXSB7CiAgICAgICAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OwogICAgICB9CgogICAgICAuaW52aXNpYmxlIHsKICAgICAgICB2aXNpYmlsaXR5OiBoaWRkZW4gIWltcG9ydGFudDsKICAgICAgfQoKICAgICAgLnJlbGF0aXZlIHsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgIH0KCiAgICAgIC5maXQgewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICB0b3A6IDA7CiAgICAgICAgcmlnaHQ6IDA7CiAgICAgICAgYm90dG9tOiAwOwogICAgICAgIGxlZnQ6IDA7CiAgICAgIH0KCiAgICAgIGJvZHkuZnVsbGJsZWVkIHsKICAgICAgICBtYXJnaW46IDA7CiAgICAgICAgaGVpZ2h0OiAxMDB2aDsKICAgICAgfQoKICAgICAgLnNjcm9sbCB7CiAgICAgICAgLXdlYmtpdC1vdmVyZmxvdy1zY3JvbGxpbmc6IHRvdWNoOwogICAgICAgIG92ZXJmbG93OiBhdXRvOwogICAgICB9CgogICAgICAvKiBmaXhlZCBwb3NpdGlvbiAqLwogICAgICAuZml4ZWQtYm90dG9tLAogICAgICAuZml4ZWQtbGVmdCwKICAgICAgLmZpeGVkLXJpZ2h0LAogICAgICAuZml4ZWQtdG9wIHsKICAgICAgICBwb3NpdGlvbjogZml4ZWQ7CiAgICAgIH0KCiAgICAgIC5maXhlZC10b3AgewogICAgICAgIHRvcDogMDsKICAgICAgICBsZWZ0OiAwOwogICAgICAgIHJpZ2h0OiAwOwogICAgICB9CgogICAgICAuZml4ZWQtcmlnaHQgewogICAgICAgIHRvcDogMDsKICAgICAgICByaWdodDogMDsKICAgICAgICBib3R0b206IDA7CiAgICAgIH0KCiAgICAgIC5maXhlZC1ib3R0b20gewogICAgICAgIHJpZ2h0OiAwOwogICAgICAgIGJvdHRvbTogMDsKICAgICAgICBsZWZ0OiAwOwogICAgICB9CgogICAgICAuZml4ZWQtbGVmdCB7CiAgICAgICAgdG9wOiAwOwogICAgICAgIGJvdHRvbTogMDsKICAgICAgICBsZWZ0OiAwOwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvdGVtcGxhdGU+CjwvZG9tLW1vZHVsZT4KYDtZZ3Quc2V0QXR0cmlidXRlKCJzdHlsZSIsImRpc3BsYXk6IG5vbmU7Iik7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChZZ3QuY29udGVudCk7dmFyIGpndD1RYAo8Y3VzdG9tLXN0eWxlPgogIDxzdHlsZSBpcz0iY3VzdG9tLXN0eWxlIj4KICAgIFtoaWRkZW5dIHsKICAgICAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OwogICAgfQogIDwvc3R5bGU+CjwvY3VzdG9tLXN0eWxlPgo8Y3VzdG9tLXN0eWxlPgogIDxzdHlsZSBpcz0iY3VzdG9tLXN0eWxlIj4KICAgIGh0bWwgewoKICAgICAgLS1sYXlvdXQ6IHsKICAgICAgICBkaXNwbGF5OiAtbXMtZmxleGJveDsKICAgICAgICBkaXNwbGF5OiAtd2Via2l0LWZsZXg7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWlubGluZTogewogICAgICAgIGRpc3BsYXk6IC1tcy1pbmxpbmUtZmxleGJveDsKICAgICAgICBkaXNwbGF5OiAtd2Via2l0LWlubGluZS1mbGV4OwogICAgICAgIGRpc3BsYXk6IGlubGluZS1mbGV4OwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtaG9yaXpvbnRhbDogewogICAgICAgIEBhcHBseSAtLWxheW91dDsKCiAgICAgICAgLW1zLWZsZXgtZGlyZWN0aW9uOiByb3c7CiAgICAgICAgLXdlYmtpdC1mbGV4LWRpcmVjdGlvbjogcm93OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiByb3c7CiAgICAgIH07CgogICAgICAtLWxheW91dC1ob3Jpem9udGFsLXJldmVyc2U6IHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQ7CgogICAgICAgIC1tcy1mbGV4LWRpcmVjdGlvbjogcm93LXJldmVyc2U7CiAgICAgICAgLXdlYmtpdC1mbGV4LWRpcmVjdGlvbjogcm93LXJldmVyc2U7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IHJvdy1yZXZlcnNlOwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtdmVydGljYWw6IHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQ7CgogICAgICAgIC1tcy1mbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgIC13ZWJraXQtZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtdmVydGljYWwtcmV2ZXJzZTogewogICAgICAgIEBhcHBseSAtLWxheW91dDsKCiAgICAgICAgLW1zLWZsZXgtZGlyZWN0aW9uOiBjb2x1bW4tcmV2ZXJzZTsKICAgICAgICAtd2Via2l0LWZsZXgtZGlyZWN0aW9uOiBjb2x1bW4tcmV2ZXJzZTsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uLXJldmVyc2U7CiAgICAgIH07CgogICAgICAtLWxheW91dC13cmFwOiB7CiAgICAgICAgLW1zLWZsZXgtd3JhcDogd3JhcDsKICAgICAgICAtd2Via2l0LWZsZXgtd3JhcDogd3JhcDsKICAgICAgICBmbGV4LXdyYXA6IHdyYXA7CiAgICAgIH07CgogICAgICAtLWxheW91dC13cmFwLXJldmVyc2U6IHsKICAgICAgICAtbXMtZmxleC13cmFwOiB3cmFwLXJldmVyc2U7CiAgICAgICAgLXdlYmtpdC1mbGV4LXdyYXA6IHdyYXAtcmV2ZXJzZTsKICAgICAgICBmbGV4LXdyYXA6IHdyYXAtcmV2ZXJzZTsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWZsZXgtYXV0bzogewogICAgICAgIC1tcy1mbGV4OiAxIDEgYXV0bzsKICAgICAgICAtd2Via2l0LWZsZXg6IDEgMSBhdXRvOwogICAgICAgIGZsZXg6IDEgMSBhdXRvOwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtZmxleC1ub25lOiB7CiAgICAgICAgLW1zLWZsZXg6IG5vbmU7CiAgICAgICAgLXdlYmtpdC1mbGV4OiBub25lOwogICAgICAgIGZsZXg6IG5vbmU7CiAgICAgIH07CgogICAgICAtLWxheW91dC1mbGV4OiB7CiAgICAgICAgLW1zLWZsZXg6IDEgMSAwLjAwMDAwMDAwMXB4OwogICAgICAgIC13ZWJraXQtZmxleDogMTsKICAgICAgICBmbGV4OiAxOwogICAgICAgIC13ZWJraXQtZmxleC1iYXNpczogMC4wMDAwMDAwMDFweDsKICAgICAgICBmbGV4LWJhc2lzOiAwLjAwMDAwMDAwMXB4OwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtZmxleC0yOiB7CiAgICAgICAgLW1zLWZsZXg6IDI7CiAgICAgICAgLXdlYmtpdC1mbGV4OiAyOwogICAgICAgIGZsZXg6IDI7CiAgICAgIH07CgogICAgICAtLWxheW91dC1mbGV4LTM6IHsKICAgICAgICAtbXMtZmxleDogMzsKICAgICAgICAtd2Via2l0LWZsZXg6IDM7CiAgICAgICAgZmxleDogMzsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWZsZXgtNDogewogICAgICAgIC1tcy1mbGV4OiA0OwogICAgICAgIC13ZWJraXQtZmxleDogNDsKICAgICAgICBmbGV4OiA0OwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtZmxleC01OiB7CiAgICAgICAgLW1zLWZsZXg6IDU7CiAgICAgICAgLXdlYmtpdC1mbGV4OiA1OwogICAgICAgIGZsZXg6IDU7CiAgICAgIH07CgogICAgICAtLWxheW91dC1mbGV4LTY6IHsKICAgICAgICAtbXMtZmxleDogNjsKICAgICAgICAtd2Via2l0LWZsZXg6IDY7CiAgICAgICAgZmxleDogNjsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWZsZXgtNzogewogICAgICAgIC1tcy1mbGV4OiA3OwogICAgICAgIC13ZWJraXQtZmxleDogNzsKICAgICAgICBmbGV4OiA3OwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtZmxleC04OiB7CiAgICAgICAgLW1zLWZsZXg6IDg7CiAgICAgICAgLXdlYmtpdC1mbGV4OiA4OwogICAgICAgIGZsZXg6IDg7CiAgICAgIH07CgogICAgICAtLWxheW91dC1mbGV4LTk6IHsKICAgICAgICAtbXMtZmxleDogOTsKICAgICAgICAtd2Via2l0LWZsZXg6IDk7CiAgICAgICAgZmxleDogOTsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWZsZXgtMTA6IHsKICAgICAgICAtbXMtZmxleDogMTA7CiAgICAgICAgLXdlYmtpdC1mbGV4OiAxMDsKICAgICAgICBmbGV4OiAxMDsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWZsZXgtMTE6IHsKICAgICAgICAtbXMtZmxleDogMTE7CiAgICAgICAgLXdlYmtpdC1mbGV4OiAxMTsKICAgICAgICBmbGV4OiAxMTsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWZsZXgtMTI6IHsKICAgICAgICAtbXMtZmxleDogMTI7CiAgICAgICAgLXdlYmtpdC1mbGV4OiAxMjsKICAgICAgICBmbGV4OiAxMjsKICAgICAgfTsKCiAgICAgIC8qIGFsaWdubWVudCBpbiBjcm9zcyBheGlzICovCgogICAgICAtLWxheW91dC1zdGFydDogewogICAgICAgIC1tcy1mbGV4LWFsaWduOiBzdGFydDsKICAgICAgICAtd2Via2l0LWFsaWduLWl0ZW1zOiBmbGV4LXN0YXJ0OwogICAgICAgIGFsaWduLWl0ZW1zOiBmbGV4LXN0YXJ0OwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtY2VudGVyOiB7CiAgICAgICAgLW1zLWZsZXgtYWxpZ246IGNlbnRlcjsKICAgICAgICAtd2Via2l0LWFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWVuZDogewogICAgICAgIC1tcy1mbGV4LWFsaWduOiBlbmQ7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1pdGVtczogZmxleC1lbmQ7CiAgICAgICAgYWxpZ24taXRlbXM6IGZsZXgtZW5kOwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtYmFzZWxpbmU6IHsKICAgICAgICAtbXMtZmxleC1hbGlnbjogYmFzZWxpbmU7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1pdGVtczogYmFzZWxpbmU7CiAgICAgICAgYWxpZ24taXRlbXM6IGJhc2VsaW5lOwogICAgICB9OwoKICAgICAgLyogYWxpZ25tZW50IGluIG1haW4gYXhpcyAqLwoKICAgICAgLS1sYXlvdXQtc3RhcnQtanVzdGlmaWVkOiB7CiAgICAgICAgLW1zLWZsZXgtcGFjazogc3RhcnQ7CiAgICAgICAgLXdlYmtpdC1qdXN0aWZ5LWNvbnRlbnQ6IGZsZXgtc3RhcnQ7CiAgICAgICAganVzdGlmeS1jb250ZW50OiBmbGV4LXN0YXJ0OwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtY2VudGVyLWp1c3RpZmllZDogewogICAgICAgIC1tcy1mbGV4LXBhY2s6IGNlbnRlcjsKICAgICAgICAtd2Via2l0LWp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICAgIGp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtZW5kLWp1c3RpZmllZDogewogICAgICAgIC1tcy1mbGV4LXBhY2s6IGVuZDsKICAgICAgICAtd2Via2l0LWp1c3RpZnktY29udGVudDogZmxleC1lbmQ7CiAgICAgICAganVzdGlmeS1jb250ZW50OiBmbGV4LWVuZDsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWFyb3VuZC1qdXN0aWZpZWQ6IHsKICAgICAgICAtbXMtZmxleC1wYWNrOiBkaXN0cmlidXRlOwogICAgICAgIC13ZWJraXQtanVzdGlmeS1jb250ZW50OiBzcGFjZS1hcm91bmQ7CiAgICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1hcm91bmQ7CiAgICAgIH07CgogICAgICAtLWxheW91dC1qdXN0aWZpZWQ6IHsKICAgICAgICAtbXMtZmxleC1wYWNrOiBqdXN0aWZ5OwogICAgICAgIC13ZWJraXQtanVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWNlbnRlci1jZW50ZXI6IHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtY2VudGVyOwogICAgICAgIEBhcHBseSAtLWxheW91dC1jZW50ZXItanVzdGlmaWVkOwogICAgICB9OwoKICAgICAgLyogc2VsZiBhbGlnbm1lbnQgKi8KCiAgICAgIC0tbGF5b3V0LXNlbGYtc3RhcnQ6IHsKICAgICAgICAtbXMtYWxpZ24tc2VsZjogZmxleC1zdGFydDsKICAgICAgICAtd2Via2l0LWFsaWduLXNlbGY6IGZsZXgtc3RhcnQ7CiAgICAgICAgYWxpZ24tc2VsZjogZmxleC1zdGFydDsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LXNlbGYtY2VudGVyOiB7CiAgICAgICAgLW1zLWFsaWduLXNlbGY6IGNlbnRlcjsKICAgICAgICAtd2Via2l0LWFsaWduLXNlbGY6IGNlbnRlcjsKICAgICAgICBhbGlnbi1zZWxmOiBjZW50ZXI7CiAgICAgIH07CgogICAgICAtLWxheW91dC1zZWxmLWVuZDogewogICAgICAgIC1tcy1hbGlnbi1zZWxmOiBmbGV4LWVuZDsKICAgICAgICAtd2Via2l0LWFsaWduLXNlbGY6IGZsZXgtZW5kOwogICAgICAgIGFsaWduLXNlbGY6IGZsZXgtZW5kOwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtc2VsZi1zdHJldGNoOiB7CiAgICAgICAgLW1zLWFsaWduLXNlbGY6IHN0cmV0Y2g7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1zZWxmOiBzdHJldGNoOwogICAgICAgIGFsaWduLXNlbGY6IHN0cmV0Y2g7CiAgICAgIH07CgogICAgICAtLWxheW91dC1zZWxmLWJhc2VsaW5lOiB7CiAgICAgICAgLW1zLWFsaWduLXNlbGY6IGJhc2VsaW5lOwogICAgICAgIC13ZWJraXQtYWxpZ24tc2VsZjogYmFzZWxpbmU7CiAgICAgICAgYWxpZ24tc2VsZjogYmFzZWxpbmU7CiAgICAgIH07CgogICAgICAvKiBtdWx0aS1saW5lIGFsaWdubWVudCBpbiBtYWluIGF4aXMgKi8KCiAgICAgIC0tbGF5b3V0LXN0YXJ0LWFsaWduZWQ6IHsKICAgICAgICAtbXMtZmxleC1saW5lLXBhY2s6IHN0YXJ0OyAgLyogSUUxMCAqLwogICAgICAgIC1tcy1hbGlnbi1jb250ZW50OiBmbGV4LXN0YXJ0OwogICAgICAgIC13ZWJraXQtYWxpZ24tY29udGVudDogZmxleC1zdGFydDsKICAgICAgICBhbGlnbi1jb250ZW50OiBmbGV4LXN0YXJ0OwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtZW5kLWFsaWduZWQ6IHsKICAgICAgICAtbXMtZmxleC1saW5lLXBhY2s6IGVuZDsgIC8qIElFMTAgKi8KICAgICAgICAtbXMtYWxpZ24tY29udGVudDogZmxleC1lbmQ7CiAgICAgICAgLXdlYmtpdC1hbGlnbi1jb250ZW50OiBmbGV4LWVuZDsKICAgICAgICBhbGlnbi1jb250ZW50OiBmbGV4LWVuZDsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWNlbnRlci1hbGlnbmVkOiB7CiAgICAgICAgLW1zLWZsZXgtbGluZS1wYWNrOiBjZW50ZXI7ICAvKiBJRTEwICovCiAgICAgICAgLW1zLWFsaWduLWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgICAtd2Via2l0LWFsaWduLWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgICBhbGlnbi1jb250ZW50OiBjZW50ZXI7CiAgICAgIH07CgogICAgICAtLWxheW91dC1iZXR3ZWVuLWFsaWduZWQ6IHsKICAgICAgICAtbXMtZmxleC1saW5lLXBhY2s6IGp1c3RpZnk7ICAvKiBJRTEwICovCiAgICAgICAgLW1zLWFsaWduLWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgICAgLXdlYmtpdC1hbGlnbi1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICAgIGFsaWduLWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgIH07CgogICAgICAtLWxheW91dC1hcm91bmQtYWxpZ25lZDogewogICAgICAgIC1tcy1mbGV4LWxpbmUtcGFjazogZGlzdHJpYnV0ZTsgIC8qIElFMTAgKi8KICAgICAgICAtbXMtYWxpZ24tY29udGVudDogc3BhY2UtYXJvdW5kOwogICAgICAgIC13ZWJraXQtYWxpZ24tY29udGVudDogc3BhY2UtYXJvdW5kOwogICAgICAgIGFsaWduLWNvbnRlbnQ6IHNwYWNlLWFyb3VuZDsKICAgICAgfTsKCiAgICAgIC8qKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqCiAgICAgICAgICAgICAgICBPdGhlciBMYXlvdXQKICAgICAgKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKi8KCiAgICAgIC0tbGF5b3V0LWJsb2NrOiB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgIH07CgogICAgICAtLWxheW91dC1pbnZpc2libGU6IHsKICAgICAgICB2aXNpYmlsaXR5OiBoaWRkZW4gIWltcG9ydGFudDsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LXJlbGF0aXZlOiB7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICB9OwoKICAgICAgLS1sYXlvdXQtZml0OiB7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIHRvcDogMDsKICAgICAgICByaWdodDogMDsKICAgICAgICBib3R0b206IDA7CiAgICAgICAgbGVmdDogMDsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LXNjcm9sbDogewogICAgICAgIC13ZWJraXQtb3ZlcmZsb3ctc2Nyb2xsaW5nOiB0b3VjaDsKICAgICAgICBvdmVyZmxvdzogYXV0bzsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWZ1bGxibGVlZDogewogICAgICAgIG1hcmdpbjogMDsKICAgICAgICBoZWlnaHQ6IDEwMHZoOwogICAgICB9OwoKICAgICAgLyogZml4ZWQgcG9zaXRpb24gKi8KCiAgICAgIC0tbGF5b3V0LWZpeGVkLXRvcDogewogICAgICAgIHBvc2l0aW9uOiBmaXhlZDsKICAgICAgICB0b3A6IDA7CiAgICAgICAgbGVmdDogMDsKICAgICAgICByaWdodDogMDsKICAgICAgfTsKCiAgICAgIC0tbGF5b3V0LWZpeGVkLXJpZ2h0OiB7CiAgICAgICAgcG9zaXRpb246IGZpeGVkOwogICAgICAgIHRvcDogMDsKICAgICAgICByaWdodDogMDsKICAgICAgICBib3R0b206IDA7CiAgICAgIH07CgogICAgICAtLWxheW91dC1maXhlZC1ib3R0b206IHsKICAgICAgICBwb3NpdGlvbjogZml4ZWQ7CiAgICAgICAgcmlnaHQ6IDA7CiAgICAgICAgYm90dG9tOiAwOwogICAgICAgIGxlZnQ6IDA7CiAgICAgIH07CgogICAgICAtLWxheW91dC1maXhlZC1sZWZ0OiB7CiAgICAgICAgcG9zaXRpb246IGZpeGVkOwogICAgICAgIHRvcDogMDsKICAgICAgICBib3R0b206IDA7CiAgICAgICAgbGVmdDogMDsKICAgICAgfTsKCiAgICB9CiAgPC9zdHlsZT4KPC9jdXN0b20tc3R5bGU+YDtqZ3Quc2V0QXR0cmlidXRlKCJzdHlsZSIsImRpc3BsYXk6IG5vbmU7Iik7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChqZ3QuY29udGVudCk7dmFyIFhndD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzdHlsZSIpO1hndC50ZXh0Q29udGVudD0iW2hpZGRlbl0geyBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7IH0iO2RvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoWGd0KTt2YXIgZ289Y2xhc3N7Y29uc3RydWN0b3IodCl7Z29bIiAiXSh0KSx0aGlzLnR5cGU9dCYmdC50eXBlfHwiZGVmYXVsdCIsdGhpcy5rZXk9dCYmdC5rZXksdCYmInZhbHVlImluIHQmJih0aGlzLnZhbHVlPXQudmFsdWUpfWdldCB2YWx1ZSgpe3ZhciB0PXRoaXMudHlwZSxyPXRoaXMua2V5O2lmKHQmJnIpcmV0dXJuIGdvLnR5cGVzW3RdJiZnby50eXBlc1t0XVtyXX1zZXQgdmFsdWUodCl7dmFyIHI9dGhpcy50eXBlLG49dGhpcy5rZXk7ciYmbiYmKHI9Z28udHlwZXNbcl09Z28udHlwZXNbcl18fHt9LHQ9PW51bGw/ZGVsZXRlIHJbbl06cltuXT10KX1nZXQgbGlzdCgpe3ZhciB0PXRoaXMudHlwZTtpZih0KXt2YXIgcj1nby50eXBlc1t0aGlzLnR5cGVdO3JldHVybiByP09iamVjdC5rZXlzKHIpLm1hcChmdW5jdGlvbihuKXtyZXR1cm4gZmJlW3RoaXMudHlwZV1bbl19LHRoaXMpOltdfX1ieUtleSh0KXtyZXR1cm4gdGhpcy5rZXk9dCx0aGlzLnZhbHVlfX07Z29bIiAiXT1mdW5jdGlvbigpe307Z28udHlwZXM9e307dmFyIGZiZT1nby50eXBlcztZdCh7aXM6Imlyb24tbWV0YSIscHJvcGVydGllczp7dHlwZTp7dHlwZTpTdHJpbmcsdmFsdWU6ImRlZmF1bHQifSxrZXk6e3R5cGU6U3RyaW5nfSx2YWx1ZTp7dHlwZTpTdHJpbmcsbm90aWZ5OiEwfSxzZWxmOnt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9zZWxmQ2hhbmdlZCJ9LF9fbWV0YTp7dHlwZTpCb29sZWFuLGNvbXB1dGVkOiJfX2NvbXB1dGVNZXRhKHR5cGUsIGtleSwgdmFsdWUpIn19LGhvc3RBdHRyaWJ1dGVzOntoaWRkZW46ITB9LF9fY29tcHV0ZU1ldGE6ZnVuY3Rpb24oZSx0LHIpe3ZhciBuPW5ldyBnbyh7dHlwZTplLGtleTp0fSk7cmV0dXJuIHIhPT12b2lkIDAmJnIhPT1uLnZhbHVlP24udmFsdWU9cjp0aGlzLnZhbHVlIT09bi52YWx1ZSYmKHRoaXMudmFsdWU9bi52YWx1ZSksbn0sZ2V0IGxpc3QoKXtyZXR1cm4gdGhpcy5fX21ldGEmJnRoaXMuX19tZXRhLmxpc3R9LF9zZWxmQ2hhbmdlZDpmdW5jdGlvbihlKXtlJiYodGhpcy52YWx1ZT10aGlzKX0sYnlLZXk6ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyBnbyh7dHlwZTp0aGlzLnR5cGUsa2V5OmV9KS52YWx1ZX19KTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtaW5saW5lOwogICAgICAgIEBhcHBseSAtLWxheW91dC1jZW50ZXItY2VudGVyOwogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKCiAgICAgICAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsKCiAgICAgICAgZmlsbDogdmFyKC0taXJvbi1pY29uLWZpbGwtY29sb3IsIGN1cnJlbnRjb2xvcik7CiAgICAgICAgc3Ryb2tlOiB2YXIoLS1pcm9uLWljb24tc3Ryb2tlLWNvbG9yLCBub25lKTsKCiAgICAgICAgd2lkdGg6IHZhcigtLWlyb24taWNvbi13aWR0aCwgMjRweCk7CiAgICAgICAgaGVpZ2h0OiB2YXIoLS1pcm9uLWljb24taGVpZ2h0LCAyNHB4KTsKICAgICAgICBAYXBwbHkgLS1pcm9uLWljb247CiAgICAgIH0KCiAgICAgIDpob3N0KFtoaWRkZW5dKSB7CiAgICAgICAgZGlzcGxheTogbm9uZTsKICAgICAgfQogICAgPC9zdHlsZT4KYCxpczoiaXJvbi1pY29uIixwcm9wZXJ0aWVzOntpY29uOnt0eXBlOlN0cmluZ30sdGhlbWU6e3R5cGU6U3RyaW5nfSxzcmM6e3R5cGU6U3RyaW5nfSxfbWV0YTp7dmFsdWU6RGEuY3JlYXRlKCJpcm9uLW1ldGEiLHt0eXBlOiJpY29uc2V0In0pfX0sb2JzZXJ2ZXJzOlsiX3VwZGF0ZUljb24oX21ldGEsIGlzQXR0YWNoZWQpIiwiX3VwZGF0ZUljb24odGhlbWUsIGlzQXR0YWNoZWQpIiwiX3NyY0NoYW5nZWQoc3JjLCBpc0F0dGFjaGVkKSIsIl9pY29uQ2hhbmdlZChpY29uLCBpc0F0dGFjaGVkKSJdLF9ERUZBVUxUX0lDT05TRVQ6Imljb25zIixfaWNvbkNoYW5nZWQ6ZnVuY3Rpb24oZSl7dmFyIHQ9KGV8fCIiKS5zcGxpdCgiOiIpO3RoaXMuX2ljb25OYW1lPXQucG9wKCksdGhpcy5faWNvbnNldE5hbWU9dC5wb3AoKXx8dGhpcy5fREVGQVVMVF9JQ09OU0VULHRoaXMuX3VwZGF0ZUljb24oKX0sX3NyY0NoYW5nZWQ6ZnVuY3Rpb24oZSl7dGhpcy5fdXBkYXRlSWNvbigpfSxfdXNlc0ljb25zZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5pY29ufHwhdGhpcy5zcmN9LF91cGRhdGVJY29uOmZ1bmN0aW9uKCl7dGhpcy5fdXNlc0ljb25zZXQoKT8odGhpcy5faW1nJiZ0aGlzLl9pbWcucGFyZW50Tm9kZSYmenQodGhpcy5yb290KS5yZW1vdmVDaGlsZCh0aGlzLl9pbWcpLHRoaXMuX2ljb25OYW1lPT09IiI/dGhpcy5faWNvbnNldCYmdGhpcy5faWNvbnNldC5yZW1vdmVJY29uKHRoaXMpOnRoaXMuX2ljb25zZXROYW1lJiZ0aGlzLl9tZXRhJiYodGhpcy5faWNvbnNldD10aGlzLl9tZXRhLmJ5S2V5KHRoaXMuX2ljb25zZXROYW1lKSx0aGlzLl9pY29uc2V0Pyh0aGlzLl9pY29uc2V0LmFwcGx5SWNvbih0aGlzLHRoaXMuX2ljb25OYW1lLHRoaXMudGhlbWUpLHRoaXMudW5saXN0ZW4od2luZG93LCJpcm9uLWljb25zZXQtYWRkZWQiLCJfdXBkYXRlSWNvbiIpKTp0aGlzLmxpc3Rlbih3aW5kb3csImlyb24taWNvbnNldC1hZGRlZCIsIl91cGRhdGVJY29uIikpKToodGhpcy5faWNvbnNldCYmdGhpcy5faWNvbnNldC5yZW1vdmVJY29uKHRoaXMpLHRoaXMuX2ltZ3x8KHRoaXMuX2ltZz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJpbWciKSx0aGlzLl9pbWcuc3R5bGUud2lkdGg9IjEwMCUiLHRoaXMuX2ltZy5zdHlsZS5oZWlnaHQ9IjEwMCUiLHRoaXMuX2ltZy5kcmFnZ2FibGU9ITEpLHRoaXMuX2ltZy5zcmM9dGhpcy5zcmMsenQodGhpcy5yb290KS5hcHBlbmRDaGlsZCh0aGlzLl9pbWcpKX19KTtZdCh7aXM6Imlyb24taWNvbnNldC1zdmciLHByb3BlcnRpZXM6e25hbWU6e3R5cGU6U3RyaW5nLG9ic2VydmVyOiJfbmFtZUNoYW5nZWQifSxzaXplOnt0eXBlOk51bWJlcix2YWx1ZToyNH0scnRsTWlycm9yaW5nOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LHVzZUdsb2JhbFJ0bEF0dHJpYnV0ZTp7dHlwZTpCb29sZWFuLHZhbHVlOiExfX0sY3JlYXRlZDpmdW5jdGlvbigpe3RoaXMuX21ldGE9bmV3IGdvKHt0eXBlOiJpY29uc2V0IixrZXk6bnVsbCx2YWx1ZTpudWxsfSl9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5zdHlsZS5kaXNwbGF5PSJub25lIn0sZ2V0SWNvbk5hbWVzOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX2ljb25zPXRoaXMuX2NyZWF0ZUljb25NYXAoKSxPYmplY3Qua2V5cyh0aGlzLl9pY29ucykubWFwKGZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLm5hbWUrIjoiK2V9LHRoaXMpfSxhcHBseUljb246ZnVuY3Rpb24oZSx0KXt0aGlzLnJlbW92ZUljb24oZSk7dmFyIHI9dGhpcy5fY2xvbmVJY29uKHQsdGhpcy5ydGxNaXJyb3JpbmcmJnRoaXMuX3RhcmdldElzUlRMKGUpKTtpZihyKXt2YXIgbj16dChlLnJvb3R8fGUpO3JldHVybiBuLmluc2VydEJlZm9yZShyLG4uY2hpbGROb2Rlc1swXSksZS5fc3ZnSWNvbj1yfXJldHVybiBudWxsfSxyZW1vdmVJY29uOmZ1bmN0aW9uKGUpe2UuX3N2Z0ljb24mJih6dChlLnJvb3R8fGUpLnJlbW92ZUNoaWxkKGUuX3N2Z0ljb24pLGUuX3N2Z0ljb249bnVsbCl9LF90YXJnZXRJc1JUTDpmdW5jdGlvbihlKXtpZih0aGlzLl9fdGFyZ2V0SXNSVEw9PW51bGwpaWYodGhpcy51c2VHbG9iYWxSdGxBdHRyaWJ1dGUpe3ZhciB0PWRvY3VtZW50LmJvZHkmJmRvY3VtZW50LmJvZHkuaGFzQXR0cmlidXRlKCJkaXIiKT9kb2N1bWVudC5ib2R5OmRvY3VtZW50LmRvY3VtZW50RWxlbWVudDt0aGlzLl9fdGFyZ2V0SXNSVEw9dC5nZXRBdHRyaWJ1dGUoImRpciIpPT09InJ0bCJ9ZWxzZSBlJiZlLm5vZGVUeXBlIT09Tm9kZS5FTEVNRU5UX05PREUmJihlPWUuaG9zdCksdGhpcy5fX3RhcmdldElzUlRMPWUmJndpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUpLmRpcmVjdGlvbj09PSJydGwiO3JldHVybiB0aGlzLl9fdGFyZ2V0SXNSVEx9LF9uYW1lQ2hhbmdlZDpmdW5jdGlvbigpe3RoaXMuX21ldGEudmFsdWU9bnVsbCx0aGlzLl9tZXRhLmtleT10aGlzLm5hbWUsdGhpcy5fbWV0YS52YWx1ZT10aGlzLHRoaXMuYXN5bmMoZnVuY3Rpb24oKXt0aGlzLmZpcmUoImlyb24taWNvbnNldC1hZGRlZCIsdGhpcyx7bm9kZTp3aW5kb3d9KX0pfSxfY3JlYXRlSWNvbk1hcDpmdW5jdGlvbigpe3ZhciBlPU9iamVjdC5jcmVhdGUobnVsbCk7cmV0dXJuIHp0KHRoaXMpLnF1ZXJ5U2VsZWN0b3JBbGwoIltpZF0iKS5mb3JFYWNoKGZ1bmN0aW9uKHQpe2VbdC5pZF09dH0pLGV9LF9jbG9uZUljb246ZnVuY3Rpb24oZSx0KXtyZXR1cm4gdGhpcy5faWNvbnM9dGhpcy5faWNvbnN8fHRoaXMuX2NyZWF0ZUljb25NYXAoKSx0aGlzLl9wcmVwYXJlU3ZnQ2xvbmUodGhpcy5faWNvbnNbZV0sdGhpcy5zaXplLHQpfSxfcHJlcGFyZVN2Z0Nsb25lOmZ1bmN0aW9uKGUsdCxyKXtpZihlKXt2YXIgbj1lLmNsb25lTm9kZSghMCksaT1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwic3ZnIiksbz1uLmdldEF0dHJpYnV0ZSgidmlld0JveCIpfHwiMCAwICIrdCsiICIrdCxhPSJwb2ludGVyLWV2ZW50czogbm9uZTsgZGlzcGxheTogYmxvY2s7IHdpZHRoOiAxMDAlOyBoZWlnaHQ6IDEwMCU7IjtyZXR1cm4gciYmbi5oYXNBdHRyaWJ1dGUoIm1pcnJvci1pbi1ydGwiKSYmKGErPSItd2Via2l0LXRyYW5zZm9ybTpzY2FsZSgtMSwxKTt0cmFuc2Zvcm06c2NhbGUoLTEsMSk7dHJhbnNmb3JtLW9yaWdpbjpjZW50ZXI7IiksaS5zZXRBdHRyaWJ1dGUoInZpZXdCb3giLG8pLGkuc2V0QXR0cmlidXRlKCJwcmVzZXJ2ZUFzcGVjdFJhdGlvIiwieE1pZFlNaWQgbWVldCIpLGkuc2V0QXR0cmlidXRlKCJmb2N1c2FibGUiLCJmYWxzZSIpLGkuc3R5bGUuY3NzVGV4dD1hLGkuYXBwZW5kQ2hpbGQobikucmVtb3ZlQXR0cmlidXRlKCJpZCIpLGl9cmV0dXJuIG51bGx9fSk7dmFyIHBiZT1RYDxpcm9uLWljb25zZXQtc3ZnIG5hbWU9ImltYWdlIiBzaXplPSIyNCI+Cjxzdmc+PGRlZnM+CjxnIGlkPSJhZGQtYS1waG90byI+PHBhdGggZD0iTTMgNFYxaDJ2M2gzdjJINXYzSDNWNkgwVjRoM3ptMyA2VjdoM1Y0aDdsMS44MyAySDIxYzEuMSAwIDIgLjkgMiAydjEyYzAgMS4xLS45IDItMiAySDVjLTEuMSAwLTItLjktMi0yVjEwaDN6bTcgOWMyLjc2IDAgNS0yLjI0IDUtNXMtMi4yNC01LTUtNS01IDIuMjQtNSA1IDIuMjQgNSA1IDV6bS0zLjItNWMwIDEuNzcgMS40MyAzLjIgMy4yIDMuMnMzLjItMS40MyAzLjItMy4yLTEuNDMtMy4yLTMuMi0zLjItMy4yIDEuNDMtMy4yIDMuMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJhZGQtdG8tcGhvdG9zIj48cGF0aCBkPSJNNCA2SDJ2MTRjMCAxLjEuOSAyIDIgMmgxNHYtMkg0VjZ6bTE2LTRIOGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlY0YzAtMS4xLS45LTItMi0yem0tMSA5aC00djRoLTJ2LTRIOVY5aDRWNWgydjRoNHYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFkanVzdCI+PHBhdGggZD0iTTEyIDJDNi40OSAyIDIgNi40OSAyIDEyczQuNDkgMTAgMTAgMTAgMTAtNC40OSAxMC0xMFMxNy41MSAyIDEyIDJ6bTAgMThjLTQuNDEgMC04LTMuNTktOC04czMuNTktOCA4LTggOCAzLjU5IDggOC0zLjU5IDgtOCA4em0zLThjMCAxLjY2LTEuMzQgMy0zIDNzLTMtMS4zNC0zLTMgMS4zNC0zIDMtMyAzIDEuMzQgMyAzeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFzc2lzdGFudCI+PHBhdGggZD0iTTE5IDJINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmg0bDMgMyAzLTNoNGMxLjEgMCAyLS45IDItMlY0YzAtMS4xLS45LTItMi0yem0tNS4xMiAxMC44OEwxMiAxN2wtMS44OC00LjEyTDYgMTFsNC4xMi0xLjg4TDEyIDVsMS44OCA0LjEyTDE4IDExbC00LjEyIDEuODh6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYXNzaXN0YW50LXBob3RvIj48cGF0aCBkPSJNMTQuNCA2TDE0IDRINXYxN2gydi03aDUuNmwuNCAyaDdWNnoiPjwvcGF0aD48L2c+CjxnIGlkPSJhdWRpb3RyYWNrIj48cGF0aCBkPSJNMTIgM3Y5LjI4Yy0uNDctLjE3LS45Ny0uMjgtMS41LS4yOEM4LjAxIDEyIDYgMTQuMDEgNiAxNi41UzguMDEgMjEgMTAuNSAyMWMyLjMxIDAgNC4yLTEuNzUgNC40NS00SDE1VjZoNFYzaC03eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJsdXItY2lyY3VsYXIiPjxwYXRoIGQ9Ik0xMCA5Yy0uNTUgMC0xIC40NS0xIDFzLjQ1IDEgMSAxIDEtLjQ1IDEtMS0uNDUtMS0xLTF6bTAgNGMtLjU1IDAtMSAuNDUtMSAxcy40NSAxIDEgMSAxLS40NSAxLTEtLjQ1LTEtMS0xek03IDkuNWMtLjI4IDAtLjUuMjItLjUuNXMuMjIuNS41LjUuNS0uMjIuNS0uNS0uMjItLjUtLjUtLjV6bTMgN2MtLjI4IDAtLjUuMjItLjUuNXMuMjIuNS41LjUuNS0uMjIuNS0uNS0uMjItLjUtLjUtLjV6bS0zLTNjLS4yOCAwLS41LjIyLS41LjVzLjIyLjUuNS41LjUtLjIyLjUtLjUtLjIyLS41LS41LS41em0zLTZjLjI4IDAgLjUtLjIyLjUtLjVzLS4yMi0uNS0uNS0uNS0uNS4yMi0uNS41LjIyLjUuNS41ek0xNCA5Yy0uNTUgMC0xIC40NS0xIDFzLjQ1IDEgMSAxIDEtLjQ1IDEtMS0uNDUtMS0xLTF6bTAtMS41Yy4yOCAwIC41LS4yMi41LS41cy0uMjItLjUtLjUtLjUtLjUuMjItLjUuNS4yMi41LjUuNXptMyA2Yy0uMjggMC0uNS4yMi0uNS41cy4yMi41LjUuNS41LS4yMi41LS41LS4yMi0uNS0uNS0uNXptMC00Yy0uMjggMC0uNS4yMi0uNS41cy4yMi41LjUuNS41LS4yMi41LS41LS4yMi0uNS0uNS0uNXpNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MiAwLTgtMy41OC04LThzMy41OC04IDgtOCA4IDMuNTggOCA4LTMuNTggOC04IDh6bTItMy41Yy0uMjggMC0uNS4yMi0uNS41cy4yMi41LjUuNS41LS4yMi41LS41LS4yMi0uNS0uNS0uNXptMC0zLjVjLS41NSAwLTEgLjQ1LTEgMXMuNDUgMSAxIDEgMS0uNDUgMS0xLS40NS0xLTEtMXoiPjwvcGF0aD48L2c+CjxnIGlkPSJibHVyLWxpbmVhciI+PHBhdGggZD0iTTUgMTcuNWMuODMgMCAxLjUtLjY3IDEuNS0xLjVzLS42Ny0xLjUtMS41LTEuNS0xLjUuNjctMS41IDEuNS42NyAxLjUgMS41IDEuNXpNOSAxM2MuNTUgMCAxLS40NSAxLTFzLS40NS0xLTEtMS0xIC40NS0xIDEgLjQ1IDEgMSAxem0wLTRjLjU1IDAgMS0uNDUgMS0xcy0uNDUtMS0xLTEtMSAuNDUtMSAxIC40NSAxIDEgMXpNMyAyMWgxOHYtMkgzdjJ6TTUgOS41Yy44MyAwIDEuNS0uNjcgMS41LTEuNVM1LjgzIDYuNSA1IDYuNSAzLjUgNy4xNyAzLjUgOCA0LjE3IDkuNSA1IDkuNXptMCA0Yy44MyAwIDEuNS0uNjcgMS41LTEuNXMtLjY3LTEuNS0xLjUtMS41LTEuNS42Ny0xLjUgMS41LjY3IDEuNSAxLjUgMS41ek05IDE3Yy41NSAwIDEtLjQ1IDEtMXMtLjQ1LTEtMS0xLTEgLjQ1LTEgMSAuNDUgMSAxIDF6bTgtLjVjLjI4IDAgLjUtLjIyLjUtLjVzLS4yMi0uNS0uNS0uNS0uNS4yMi0uNS41LjIyLjUuNS41ek0zIDN2MmgxOFYzSDN6bTE0IDUuNWMuMjggMCAuNS0uMjIuNS0uNXMtLjIyLS41LS41LS41LS41LjIyLS41LjUuMjIuNS41LjV6bTAgNGMuMjggMCAuNS0uMjIuNS0uNXMtLjIyLS41LS41LS41LS41LjIyLS41LjUuMjIuNS41LjV6TTEzIDljLjU1IDAgMS0uNDUgMS0xcy0uNDUtMS0xLTEtMSAuNDUtMSAxIC40NSAxIDEgMXptMCA0Yy41NSAwIDEtLjQ1IDEtMXMtLjQ1LTEtMS0xLTEgLjQ1LTEgMSAuNDUgMSAxIDF6bTAgNGMuNTUgMCAxLS40NSAxLTFzLS40NS0xLTEtMS0xIC40NS0xIDEgLjQ1IDEgMSAxeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJsdXItb2ZmIj48cGF0aCBkPSJNMTQgN2MuNTUgMCAxLS40NSAxLTFzLS40NS0xLTEtMS0xIC40NS0xIDEgLjQ1IDEgMSAxem0tLjIgNC40OGwuMi4wMmMuODMgMCAxLjUtLjY3IDEuNS0xLjVzLS42Ny0xLjUtMS41LTEuNS0xLjUuNjctMS41IDEuNWwuMDIuMmMuMDkuNjcuNjEgMS4xOSAxLjI4IDEuMjh6TTE0IDMuNWMuMjggMCAuNS0uMjIuNS0uNXMtLjIyLS41LS41LS41LS41LjIyLS41LjUuMjIuNS41LjV6bS00IDBjLjI4IDAgLjUtLjIyLjUtLjVzLS4yMi0uNS0uNS0uNS0uNS4yMi0uNS41LjIyLjUuNS41em0xMSA3Yy4yOCAwIC41LS4yMi41LS41cy0uMjItLjUtLjUtLjUtLjUuMjItLjUuNS4yMi41LjUuNXpNMTAgN2MuNTUgMCAxLS40NSAxLTFzLS40NS0xLTEtMS0xIC40NS0xIDEgLjQ1IDEgMSAxem04IDhjLjU1IDAgMS0uNDUgMS0xcy0uNDUtMS0xLTEtMSAuNDUtMSAxIC40NSAxIDEgMXptMC00Yy41NSAwIDEtLjQ1IDEtMXMtLjQ1LTEtMS0xLTEgLjQ1LTEgMSAuNDUgMSAxIDF6bTAtNGMuNTUgMCAxLS40NSAxLTFzLS40NS0xLTEtMS0xIC40NS0xIDEgLjQ1IDEgMSAxem0tNCAxMy41Yy0uMjggMC0uNS4yMi0uNS41cy4yMi41LjUuNS41LS4yMi41LS41LS4yMi0uNS0uNS0uNXpNMi41IDUuMjdsMy43OCAzLjc4TDYgOWMtLjU1IDAtMSAuNDUtMSAxcy40NSAxIDEgMSAxLS40NSAxLTFjMC0uMS0uMDMtLjE5LS4wNi0uMjhsMi44MSAyLjgxYy0uNzEuMTEtMS4yNS43My0xLjI1IDEuNDcgMCAuODMuNjcgMS41IDEuNSAxLjUuNzQgMCAxLjM2LS41NCAxLjQ3LTEuMjVsMi44MSAyLjgxYy0uMDktLjAzLS4xOC0uMDYtLjI4LS4wNi0uNTUgMC0xIC40NS0xIDFzLjQ1IDEgMSAxIDEtLjQ1IDEtMWMwLS4xLS4wMy0uMTktLjA2LS4yOGwzLjc4IDMuNzhMMjAgMjAuMjMgMy43NyA0IDIuNSA1LjI3ek0xMCAxN2MtLjU1IDAtMSAuNDUtMSAxcy40NSAxIDEgMSAxLS40NSAxLTEtLjQ1LTEtMS0xem0xMS0zLjVjLS4yOCAwLS41LjIyLS41LjVzLjIyLjUuNS41LjUtLjIyLjUtLjUtLjIyLS41LS41LS41ek02IDEzYy0uNTUgMC0xIC40NS0xIDFzLjQ1IDEgMSAxIDEtLjQ1IDEtMS0uNDUtMS0xLTF6TTMgOS41Yy0uMjggMC0uNS4yMi0uNS41cy4yMi41LjUuNS41LS4yMi41LS41LS4yMi0uNS0uNS0uNXptNyAxMWMtLjI4IDAtLjUuMjItLjUuNXMuMjIuNS41LjUuNS0uMjIuNS0uNS0uMjItLjUtLjUtLjV6TTYgMTdjLS41NSAwLTEgLjQ1LTEgMXMuNDUgMSAxIDEgMS0uNDUgMS0xLS40NS0xLTEtMXptLTMtMy41Yy0uMjggMC0uNS4yMi0uNS41cy4yMi41LjUuNS41LS4yMi41LS41LS4yMi0uNS0uNS0uNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJibHVyLW9uIj48cGF0aCBkPSJNNiAxM2MtLjU1IDAtMSAuNDUtMSAxcy40NSAxIDEgMSAxLS40NSAxLTEtLjQ1LTEtMS0xem0wIDRjLS41NSAwLTEgLjQ1LTEgMXMuNDUgMSAxIDEgMS0uNDUgMS0xLS40NS0xLTEtMXptMC04Yy0uNTUgMC0xIC40NS0xIDFzLjQ1IDEgMSAxIDEtLjQ1IDEtMS0uNDUtMS0xLTF6bS0zIC41Yy0uMjggMC0uNS4yMi0uNS41cy4yMi41LjUuNS41LS4yMi41LS41LS4yMi0uNS0uNS0uNXpNNiA1Yy0uNTUgMC0xIC40NS0xIDFzLjQ1IDEgMSAxIDEtLjQ1IDEtMS0uNDUtMS0xLTF6bTE1IDUuNWMuMjggMCAuNS0uMjIuNS0uNXMtLjIyLS41LS41LS41LS41LjIyLS41LjUuMjIuNS41LjV6TTE0IDdjLjU1IDAgMS0uNDUgMS0xcy0uNDUtMS0xLTEtMSAuNDUtMSAxIC40NSAxIDEgMXptMC0zLjVjLjI4IDAgLjUtLjIyLjUtLjVzLS4yMi0uNS0uNS0uNS0uNS4yMi0uNS41LjIyLjUuNS41em0tMTEgMTBjLS4yOCAwLS41LjIyLS41LjVzLjIyLjUuNS41LjUtLjIyLjUtLjUtLjIyLS41LS41LS41em03IDdjLS4yOCAwLS41LjIyLS41LjVzLjIyLjUuNS41LjUtLjIyLjUtLjUtLjIyLS41LS41LS41em0wLTE3Yy4yOCAwIC41LS4yMi41LS41cy0uMjItLjUtLjUtLjUtLjUuMjItLjUuNS4yMi41LjUuNXpNMTAgN2MuNTUgMCAxLS40NSAxLTFzLS40NS0xLTEtMS0xIC40NS0xIDEgLjQ1IDEgMSAxem0wIDUuNWMtLjgzIDAtMS41LjY3LTEuNSAxLjVzLjY3IDEuNSAxLjUgMS41IDEuNS0uNjcgMS41LTEuNS0uNjctMS41LTEuNS0xLjV6bTggLjVjLS41NSAwLTEgLjQ1LTEgMXMuNDUgMSAxIDEgMS0uNDUgMS0xLS40NS0xLTEtMXptMCA0Yy0uNTUgMC0xIC40NS0xIDFzLjQ1IDEgMSAxIDEtLjQ1IDEtMS0uNDUtMS0xLTF6bTAtOGMtLjU1IDAtMSAuNDUtMSAxcy40NSAxIDEgMSAxLS40NSAxLTEtLjQ1LTEtMS0xem0wLTRjLS41NSAwLTEgLjQ1LTEgMXMuNDUgMSAxIDEgMS0uNDUgMS0xLS40NS0xLTEtMXptMyA4LjVjLS4yOCAwLS41LjIyLS41LjVzLjIyLjUuNS41LjUtLjIyLjUtLjUtLjIyLS41LS41LS41ek0xNCAxN2MtLjU1IDAtMSAuNDUtMSAxcy40NSAxIDEgMSAxLS40NSAxLTEtLjQ1LTEtMS0xem0wIDMuNWMtLjI4IDAtLjUuMjItLjUuNXMuMjIuNS41LjUuNS0uMjIuNS0uNS0uMjItLjUtLjUtLjV6bS00LTEyYy0uODMgMC0xLjUuNjctMS41IDEuNXMuNjcgMS41IDEuNSAxLjUgMS41LS42NyAxLjUtMS41LS42Ny0xLjUtMS41LTEuNXptMCA4LjVjLS41NSAwLTEgLjQ1LTEgMXMuNDUgMSAxIDEgMS0uNDUgMS0xLS40NS0xLTEtMXptNC00LjVjLS44MyAwLTEuNS42Ny0xLjUgMS41cy42NyAxLjUgMS41IDEuNSAxLjUtLjY3IDEuNS0xLjUtLjY3LTEuNS0xLjUtMS41em0wLTRjLS44MyAwLTEuNS42Ny0xLjUgMS41cy42NyAxLjUgMS41IDEuNSAxLjUtLjY3IDEuNS0xLjUtLjY3LTEuNS0xLjUtMS41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJyaWdodG5lc3MtMSI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiPjwvY2lyY2xlPjwvZz4KPGcgaWQ9ImJyaWdodG5lc3MtMiI+PHBhdGggZD0iTTEwIDJjLTEuODIgMC0zLjUzLjUtNSAxLjM1QzcuOTkgNS4wOCAxMCA4LjMgMTAgMTJzLTIuMDEgNi45Mi01IDguNjVDNi40NyAyMS41IDguMTggMjIgMTAgMjJjNS41MiAwIDEwLTQuNDggMTAtMTBTMTUuNTIgMiAxMCAyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJyaWdodG5lc3MtMyI+PHBhdGggZD0iTTkgMmMtMS4wNSAwLTIuMDUuMTYtMyAuNDYgNC4wNiAxLjI3IDcgNS4wNiA3IDkuNTQgMCA0LjQ4LTIuOTQgOC4yNy03IDkuNTQuOTUuMyAxLjk1LjQ2IDMgLjQ2IDUuNTIgMCAxMC00LjQ4IDEwLTEwUzE0LjUyIDIgOSAyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJyaWdodG5lc3MtNCI+PHBhdGggZD0iTTIwIDguNjlWNGgtNC42OUwxMiAuNjkgOC42OSA0SDR2NC42OUwuNjkgMTIgNCAxNS4zMVYyMGg0LjY5TDEyIDIzLjMxIDE1LjMxIDIwSDIwdi00LjY5TDIzLjMxIDEyIDIwIDguNjl6TTEyIDE4Yy0uODkgMC0xLjc0LS4yLTIuNS0uNTVDMTEuNTYgMTYuNSAxMyAxNC40MiAxMyAxMnMtMS40NC00LjUtMy41LTUuNDVDMTAuMjYgNi4yIDExLjExIDYgMTIgNmMzLjMxIDAgNiAyLjY5IDYgNnMtMi42OSA2LTYgNnoiPjwvcGF0aD48L2c+CjxnIGlkPSJicmlnaHRuZXNzLTUiPjxwYXRoIGQ9Ik0yMCAxNS4zMUwyMy4zMSAxMiAyMCA4LjY5VjRoLTQuNjlMMTIgLjY5IDguNjkgNEg0djQuNjlMLjY5IDEyIDQgMTUuMzFWMjBoNC42OUwxMiAyMy4zMSAxNS4zMSAyMEgyMHYtNC42OXpNMTIgMThjLTMuMzEgMC02LTIuNjktNi02czIuNjktNiA2LTYgNiAyLjY5IDYgNi0yLjY5IDYtNiA2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJyaWdodG5lc3MtNiI+PHBhdGggZD0iTTIwIDE1LjMxTDIzLjMxIDEyIDIwIDguNjlWNGgtNC42OUwxMiAuNjkgOC42OSA0SDR2NC42OUwuNjkgMTIgNCAxNS4zMVYyMGg0LjY5TDEyIDIzLjMxIDE1LjMxIDIwSDIwdi00LjY5ek0xMiAxOFY2YzMuMzEgMCA2IDIuNjkgNiA2cy0yLjY5IDYtNiA2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJyaWdodG5lc3MtNyI+PHBhdGggZD0iTTIwIDguNjlWNGgtNC42OUwxMiAuNjkgOC42OSA0SDR2NC42OUwuNjkgMTIgNCAxNS4zMVYyMGg0LjY5TDEyIDIzLjMxIDE1LjMxIDIwSDIwdi00LjY5TDIzLjMxIDEyIDIwIDguNjl6TTEyIDE4Yy0zLjMxIDAtNi0yLjY5LTYtNnMyLjY5LTYgNi02IDYgMi42OSA2IDYtMi42OSA2LTYgNnptMC0xMGMtMi4yMSAwLTQgMS43OS00IDRzMS43OSA0IDQgNCA0LTEuNzkgNC00LTEuNzktNC00LTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYnJva2VuLWltYWdlIj48cGF0aCBkPSJNMjEgNXY2LjU5bC0zLTMuMDEtNCA0LjAxLTQtNC00IDQtMy0zLjAxVjVjMC0xLjEuOS0yIDItMmgxNGMxLjEgMCAyIC45IDIgMnptLTMgNi40MmwzIDMuMDFWMTljMCAxLjEtLjkgMi0yIDJINWMtMS4xIDAtMi0uOS0yLTJ2LTYuNThsMyAyLjk5IDQtNCA0IDQgNC0zLjk5eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJydXNoIj48cGF0aCBkPSJNNyAxNGMtMS42NiAwLTMgMS4zNC0zIDMgMCAxLjMxLTEuMTYgMi0yIDIgLjkyIDEuMjIgMi40OSAyIDQgMiAyLjIxIDAgNC0xLjc5IDQtNCAwLTEuNjYtMS4zNC0zLTMtM3ptMTMuNzEtOS4zN2wtMS4zNC0xLjM0Yy0uMzktLjM5LTEuMDItLjM5LTEuNDEgMEw5IDEyLjI1IDExLjc1IDE1bDguOTYtOC45NmMuMzktLjM5LjM5LTEuMDIgMC0xLjQxeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJ1cnN0LW1vZGUiPjxwYXRoIGQ9Ik0xIDVoMnYxNEgxem00IDBoMnYxNEg1em0xNyAwSDEwYy0uNTUgMC0xIC40NS0xIDF2MTJjMCAuNTUuNDUgMSAxIDFoMTJjLjU1IDAgMS0uNDUgMS0xVjZjMC0uNTUtLjQ1LTEtMS0xek0xMSAxN2wyLjUtMy4xNUwxNS4yOSAxNmwyLjUtMy4yMkwyMSAxN0gxMXoiPjwvcGF0aD48L2c+CjxnIGlkPSJjYW1lcmEiPjxwYXRoIGQ9Ik05LjQgMTAuNWw0Ljc3LTguMjZDMTMuNDcgMi4wOSAxMi43NSAyIDEyIDJjLTIuNCAwLTQuNi44NS02LjMyIDIuMjVsMy42NiA2LjM1LjA2LS4xek0yMS41NCA5Yy0uOTItMi45Mi0zLjE1LTUuMjYtNi02LjM0TDExLjg4IDloOS42NnptLjI2IDFoLTcuNDlsLjI5LjUgNC43NiA4LjI1QzIxIDE2Ljk3IDIyIDE0LjYxIDIyIDEyYzAtLjY5LS4wNy0xLjM1LS4yLTJ6TTguNTQgMTJsLTMuOS02Ljc1QzMuMDEgNy4wMyAyIDkuMzkgMiAxMmMwIC42OS4wNyAxLjM1LjIgMmg3LjQ5bC0xLjE1LTJ6bS02LjA4IDNjLjkyIDIuOTIgMy4xNSA1LjI2IDYgNi4zNEwxMi4xMiAxNUgyLjQ2em0xMS4yNyAwbC0zLjkgNi43NmMuNy4xNSAxLjQyLjI0IDIuMTcuMjQgMi40IDAgNC42LS44NSA2LjMyLTIuMjVsLTMuNjYtNi4zNS0uOTMgMS42eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNhbWVyYS1hbHQiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjMuMiI+PC9jaXJjbGU+PHBhdGggZD0iTTkgMkw3LjE3IDRINGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY2YzAtMS4xLS45LTItMi0yaC0zLjE3TDE1IDJIOXptMyAxNWMtMi43NiAwLTUtMi4yNC01LTVzMi4yNC01IDUtNSA1IDIuMjQgNSA1LTIuMjQgNS01IDV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2FtZXJhLWZyb250Ij48cGF0aCBkPSJNMTAgMjBINXYyaDV2MmwzLTMtMy0zdjJ6bTQgMHYyaDV2LTJoLTV6TTEyIDhjMS4xIDAgMi0uOSAyLTJzLS45LTItMi0yLTEuOTkuOS0xLjk5IDJTMTAuOSA4IDEyIDh6bTUtOEg3QzUuOSAwIDUgLjkgNSAydjE0YzAgMS4xLjkgMiAyIDJoMTBjMS4xIDAgMi0uOSAyLTJWMmMwLTEuMS0uOS0yLTItMnpNNyAyaDEwdjEwLjVjMC0xLjY3LTMuMzMtMi41LTUtMi41cy01IC44My01IDIuNVYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNhbWVyYS1yZWFyIj48cGF0aCBkPSJNMTAgMjBINXYyaDV2MmwzLTMtMy0zdjJ6bTQgMHYyaDV2LTJoLTV6bTMtMjBIN0M1LjkgMCA1IC45IDUgMnYxNGMwIDEuMS45IDIgMiAyaDEwYzEuMSAwIDItLjkgMi0yVjJjMC0xLjEtLjktMi0yLTJ6bS01IDZjLTEuMTEgMC0yLS45LTItMnMuODktMiAxLjk5LTIgMiAuOSAyIDJDMTQgNS4xIDEzLjEgNiAxMiA2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNhbWVyYS1yb2xsIj48cGF0aCBkPSJNMTQgNWMwLTEuMS0uOS0yLTItMmgtMVYyYzAtLjU1LS40NS0xLTEtMUg2Yy0uNTUgMC0xIC40NS0xIDF2MUg0Yy0xLjEgMC0yIC45LTIgMnYxNWMwIDEuMS45IDIgMiAyaDhjMS4xIDAgMi0uOSAyLTJoOFY1aC04em0tMiAxM2gtMnYtMmgydjJ6bTAtOWgtMlY3aDJ2MnptNCA5aC0ydi0yaDJ2MnptMC05aC0yVjdoMnYyem00IDloLTJ2LTJoMnYyem0wLTloLTJWN2gydjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2VudGVyLWZvY3VzLXN0cm9uZyI+PHBhdGggZD0iTTEyIDhjLTIuMjEgMC00IDEuNzktNCA0czEuNzkgNCA0IDQgNC0xLjc5IDQtNC0xLjc5LTQtNC00em0tNyA3SDN2NGMwIDEuMS45IDIgMiAyaDR2LTJINXYtNHpNNSA1aDRWM0g1Yy0xLjEgMC0yIC45LTIgMnY0aDJWNXptMTQtMmgtNHYyaDR2NGgyVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZoLTR2Mmg0YzEuMSAwIDItLjkgMi0ydi00aC0ydjR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2VudGVyLWZvY3VzLXdlYWsiPjxwYXRoIGQ9Ik01IDE1SDN2NGMwIDEuMS45IDIgMiAyaDR2LTJINXYtNHpNNSA1aDRWM0g1Yy0xLjEgMC0yIC45LTIgMnY0aDJWNXptMTQtMmgtNHYyaDR2NGgyVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZoLTR2Mmg0YzEuMSAwIDItLjkgMi0ydi00aC0ydjR6TTEyIDhjLTIuMjEgMC00IDEuNzktNCA0czEuNzkgNCA0IDQgNC0xLjc5IDQtNC0xLjc5LTQtNC00em0wIDZjLTEuMSAwLTItLjktMi0ycy45LTIgMi0yIDIgLjkgMiAyLS45IDItMiAyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNvbGxlY3Rpb25zIj48cGF0aCBkPSJNMjIgMTZWNGMwLTEuMS0uOS0yLTItMkg4Yy0xLjEgMC0yIC45LTIgMnYxMmMwIDEuMS45IDIgMiAyaDEyYzEuMSAwIDItLjkgMi0yem0tMTEtNGwyLjAzIDIuNzFMMTYgMTFsNCA1SDhsMy00ek0yIDZ2MTRjMCAxLjEuOSAyIDIgMmgxNHYtMkg0VjZIMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJjb2xsZWN0aW9ucy1ib29rbWFyayI+PHBhdGggZD0iTTQgNkgydjE0YzAgMS4xLjkgMiAyIDJoMTR2LTJINFY2em0xNi00SDhjLTEuMSAwLTIgLjktMiAydjEyYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWNGMwLTEuMS0uOS0yLTItMnptMCAxMGwtMi41LTEuNUwxNSAxMlY0aDV2OHoiPjwvcGF0aD48L2c+CjxnIGlkPSJjb2xvci1sZW5zIj48cGF0aCBkPSJNMTIgM2MtNC45NyAwLTkgNC4wMy05IDlzNC4wMyA5IDkgOWMuODMgMCAxLjUtLjY3IDEuNS0xLjUgMC0uMzktLjE1LS43NC0uMzktMS4wMS0uMjMtLjI2LS4zOC0uNjEtLjM4LS45OSAwLS44My42Ny0xLjUgMS41LTEuNUgxNmMyLjc2IDAgNS0yLjI0IDUtNSAwLTQuNDItNC4wMy04LTktOHptLTUuNSA5Yy0uODMgMC0xLjUtLjY3LTEuNS0xLjVTNS42NyA5IDYuNSA5IDggOS42NyA4IDEwLjUgNy4zMyAxMiA2LjUgMTJ6bTMtNEM4LjY3IDggOCA3LjMzIDggNi41UzguNjcgNSA5LjUgNXMxLjUuNjcgMS41IDEuNVMxMC4zMyA4IDkuNSA4em01IDBjLS44MyAwLTEuNS0uNjctMS41LTEuNVMxMy42NyA1IDE0LjUgNXMxLjUuNjcgMS41IDEuNVMxNS4zMyA4IDE0LjUgOHptMyA0Yy0uODMgMC0xLjUtLjY3LTEuNS0xLjVTMTYuNjcgOSAxNy41IDlzMS41LjY3IDEuNSAxLjUtLjY3IDEuNS0xLjUgMS41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNvbG9yaXplIj48cGF0aCBkPSJNMjAuNzEgNS42M2wtMi4zNC0yLjM0Yy0uMzktLjM5LTEuMDItLjM5LTEuNDEgMGwtMy4xMiAzLjEyLTEuOTMtMS45MS0xLjQxIDEuNDEgMS40MiAxLjQyTDMgMTYuMjVWMjFoNC43NWw4LjkyLTguOTIgMS40MiAxLjQyIDEuNDEtMS40MS0xLjkyLTEuOTIgMy4xMi0zLjEyYy40LS40LjQtMS4wMy4wMS0xLjQyek02LjkyIDE5TDUgMTcuMDhsOC4wNi04LjA2IDEuOTIgMS45Mkw2LjkyIDE5eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNvbXBhcmUiPjxwYXRoIGQ9Ik0xMCAzSDVjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoNXYyaDJWMWgtMnYyem0wIDE1SDVsNS02djZ6bTktMTVoLTV2Mmg1djEzbC01LTZ2OWg1YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY29udHJvbC1wb2ludCI+PHBhdGggZD0iTTEzIDdoLTJ2NEg3djJoNHY0aDJ2LTRoNHYtMmgtNFY3em0tMS01QzYuNDkgMiAyIDYuNDkgMiAxMnM0LjQ5IDEwIDEwIDEwIDEwLTQuNDkgMTAtMTBTMTcuNTEgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDgtMy41OSA4LTggOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJjb250cm9sLXBvaW50LWR1cGxpY2F0ZSI+PHBhdGggZD0iTTE2IDhoLTJ2M2gtM3YyaDN2M2gydi0zaDN2LTJoLTN6TTIgMTJjMC0yLjc5IDEuNjQtNS4yIDQuMDEtNi4zMlYzLjUyQzIuNTIgNC43NiAwIDguMDkgMCAxMnMyLjUyIDcuMjQgNi4wMSA4LjQ4di0yLjE2QzMuNjQgMTcuMiAyIDE0Ljc5IDIgMTJ6bTEzLTljLTQuOTYgMC05IDQuMDQtOSA5czQuMDQgOSA5IDkgOS00LjA0IDktOS00LjA0LTktOS05em0wIDE2Yy0zLjg2IDAtNy0zLjE0LTctN3MzLjE0LTcgNy03IDcgMy4xNCA3IDctMy4xNCA3LTcgN3oiPjwvcGF0aD48L2c+CjxnIGlkPSJjcm9wIj48cGF0aCBkPSJNMTcgMTVoMlY3YzAtMS4xLS45LTItMi0ySDl2Mmg4djh6TTcgMTdWMUg1djRIMXYyaDR2MTBjMCAxLjEuOSAyIDIgMmgxMHY0aDJ2LTRoNHYtMkg3eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNyb3AtMTYtOSI+PHBhdGggZD0iTTE5IDZINWMtMS4xIDAtMiAuOS0yIDJ2OGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjhjMC0xLjEtLjktMi0yLTJ6bTAgMTBINVY4aDE0djh6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY3JvcC0zLTIiPjxwYXRoIGQ9Ik0xOSA0SDVjLTEuMSAwLTIgLjktMiAydjEyYzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uOS0yLTItMnptMCAxNEg1VjZoMTR2MTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY3JvcC01LTQiPjxwYXRoIGQ9Ik0xOSA1SDVjLTEuMSAwLTIgLjktMiAydjEwYzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWN2MwLTEuMS0uOS0yLTItMnptMCAxMkg1VjdoMTR2MTB6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY3JvcC03LTUiPjxwYXRoIGQ9Ik0xOSA3SDVjLTEuMSAwLTIgLjktMiAydjZjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY5YzAtMS4xLS45LTItMi0yem0wIDhINVY5aDE0djZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY3JvcC1kaW4iPjxwYXRoIGQ9Ik0xOSAzSDVjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnptMCAxNkg1VjVoMTR2MTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY3JvcC1mcmVlIj48cGF0aCBkPSJNMyA1djRoMlY1aDRWM0g1Yy0xLjEgMC0yIC45LTIgMnptMiAxMEgzdjRjMCAxLjEuOSAyIDIgMmg0di0ySDV2LTR6bTE0IDRoLTR2Mmg0YzEuMSAwIDItLjkgMi0ydi00aC0ydjR6bTAtMTZoLTR2Mmg0djRoMlY1YzAtMS4xLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNyb3AtbGFuZHNjYXBlIj48cGF0aCBkPSJNMTkgNUg1Yy0xLjEgMC0yIC45LTIgMnYxMGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjdjMC0xLjEtLjktMi0yLTJ6bTAgMTJINVY3aDE0djEweiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNyb3Atb3JpZ2luYWwiPjxwYXRoIGQ9Ik0xOSAzSDVjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnptMCAxNkg1VjVoMTR2MTR6bS01LjA0LTYuNzFsLTIuNzUgMy41NC0xLjk2LTIuMzZMNi41IDE3aDExbC0zLjU0LTQuNzF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY3JvcC1wb3J0cmFpdCI+PHBhdGggZD0iTTE3IDNIN2MtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxMGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yem0wIDE2SDdWNWgxMHYxNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJjcm9wLXJvdGF0ZSI+PHBhdGggZD0iTTcuNDcgMjEuNDlDNC4yIDE5LjkzIDEuODYgMTYuNzYgMS41IDEzSDBjLjUxIDYuMTYgNS42NiAxMSAxMS45NSAxMSAuMjMgMCAuNDQtLjAyLjY2LS4wM0w4LjggMjAuMTVsLTEuMzMgMS4zNHpNMTIuMDUgMGMtLjIzIDAtLjQ0LjAyLS42Ni4wNGwzLjgxIDMuODEgMS4zMy0xLjMzQzE5LjggNC4wNyAyMi4xNCA3LjI0IDIyLjUgMTFIMjRjLS41MS02LjE2LTUuNjYtMTEtMTEuOTUtMTF6TTE2IDE0aDJWOGMwLTEuMTEtLjktMi0yLTJoLTZ2Mmg2djZ6bS04IDJWNEg2djJINHYyaDJ2OGMwIDEuMS44OSAyIDIgMmg4djJoMnYtMmgydi0ySDh6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY3JvcC1zcXVhcmUiPjxwYXRoIGQ9Ik0xOCA0SDZjLTEuMSAwLTIgLjktMiAydjEyYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uOS0yLTItMnptMCAxNEg2VjZoMTJ2MTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZGVoYXplIj48cGF0aCBkPSJNMiAxNS41djJoMjB2LTJIMnptMC01djJoMjB2LTJIMnptMC01djJoMjB2LTJIMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJkZXRhaWxzIj48cGF0aCBkPSJNMyA0bDkgMTYgOS0xNkgzem0zLjM4IDJoMTEuMjVMMTIgMTYgNi4zOCA2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImVkaXQiPjxwYXRoIGQ9Ik0zIDE3LjI1VjIxaDMuNzVMMTcuODEgOS45NGwtMy43NS0zLjc1TDMgMTcuMjV6TTIwLjcxIDcuMDRjLjM5LS4zOS4zOS0xLjAyIDAtMS40MWwtMi4zNC0yLjM0Yy0uMzktLjM5LTEuMDItLjM5LTEuNDEgMGwtMS44MyAxLjgzIDMuNzUgMy43NSAxLjgzLTEuODN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZXhwb3N1cmUiPjxwYXRoIGQ9Ik0xNSAxN3YyaDJ2LTJoMnYtMmgtMnYtMmgtMnYyaC0ydjJoMnptNS0xNUg0Yy0xLjEgMC0yIC45LTIgMnYxNmMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjRjMC0xLjEtLjktMi0yLTJ6TTUgNWg2djJINVY1em0xNSAxNUg0TDIwIDR2MTZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZXhwb3N1cmUtbmVnLTEiPjxwYXRoIGQ9Ik00IDExdjJoOHYtMkg0em0xNSA3aC0yVjcuMzhMMTQgOC40VjYuN0wxOC43IDVoLjN2MTN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZXhwb3N1cmUtbmVnLTIiPjxwYXRoIGQ9Ik0xNS4wNSAxNi4yOWwyLjg2LTMuMDdjLjM4LS4zOS43Mi0uNzkgMS4wNC0xLjE4LjMyLS4zOS41OS0uNzguODItMS4xNy4yMy0uMzkuNDEtLjc4LjU0LTEuMTdzLjE5LS43OS4xOS0xLjE4YzAtLjUzLS4wOS0xLjAyLS4yNy0xLjQ2LS4xOC0uNDQtLjQ0LS44MS0uNzgtMS4xMS0uMzQtLjMxLS43Ny0uNTQtMS4yNi0uNzEtLjUxLS4xNi0xLjA4LS4yNC0xLjcyLS4yNC0uNjkgMC0xLjMxLjExLTEuODUuMzItLjU0LjIxLTEgLjUxLTEuMzYuODgtLjM3LjM3LS42NS44LS44NCAxLjMtLjE4LjQ3LS4yNy45Ny0uMjggMS41aDIuMTRjLjAxLS4zMS4wNS0uNi4xMy0uODcuMDktLjI5LjIzLS41NC40LS43NS4xOC0uMjEuNDEtLjM3LjY4LS40OS4yNy0uMTIuNi0uMTguOTYtLjE4LjMxIDAgLjU4LjA1LjgxLjE1LjIzLjEuNDMuMjUuNTkuNDMuMTYuMTguMjguNC4zNy42NS4wOC4yNS4xMy41Mi4xMy44MSAwIC4yMi0uMDMuNDMtLjA4LjY1LS4wNi4yMi0uMTUuNDUtLjI5LjctLjE0LjI1LS4zMi41My0uNTYuODMtLjIzLjMtLjUyLjY1LS44OCAxLjAzbC00LjE3IDQuNTVWMThIMjF2LTEuNzFoLTUuOTV6TTIgMTF2Mmg4di0ySDJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZXhwb3N1cmUtcGx1cy0xIj48cGF0aCBkPSJNMTAgN0g4djRINHYyaDR2NGgydi00aDR2LTJoLTRWN3ptMTAgMTFoLTJWNy4zOEwxNSA4LjRWNi43TDE5LjcgNWguM3YxM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJleHBvc3VyZS1wbHVzLTIiPjxwYXRoIGQ9Ik0xNi4wNSAxNi4yOWwyLjg2LTMuMDdjLjM4LS4zOS43Mi0uNzkgMS4wNC0xLjE4LjMyLS4zOS41OS0uNzguODItMS4xNy4yMy0uMzkuNDEtLjc4LjU0LTEuMTcuMTMtLjM5LjE5LS43OS4xOS0xLjE4IDAtLjUzLS4wOS0xLjAyLS4yNy0xLjQ2LS4xOC0uNDQtLjQ0LS44MS0uNzgtMS4xMS0uMzQtLjMxLS43Ny0uNTQtMS4yNi0uNzEtLjUxLS4xNi0xLjA4LS4yNC0xLjcyLS4yNC0uNjkgMC0xLjMxLjExLTEuODUuMzItLjU0LjIxLTEgLjUxLTEuMzYuODgtLjM3LjM3LS42NS44LS44NCAxLjMtLjE4LjQ3LS4yNy45Ny0uMjggMS41aDIuMTRjLjAxLS4zMS4wNS0uNi4xMy0uODcuMDktLjI5LjIzLS41NC40LS43NS4xOC0uMjEuNDEtLjM3LjY4LS40OS4yNy0uMTIuNi0uMTguOTYtLjE4LjMxIDAgLjU4LjA1LjgxLjE1LjIzLjEuNDMuMjUuNTkuNDMuMTYuMTguMjguNC4zNy42NS4wOC4yNS4xMy41Mi4xMy44MSAwIC4yMi0uMDMuNDMtLjA4LjY1LS4wNi4yMi0uMTUuNDUtLjI5LjctLjE0LjI1LS4zMi41My0uNTYuODMtLjIzLjMtLjUyLjY1LS44OCAxLjAzbC00LjE3IDQuNTVWMThIMjJ2LTEuNzFoLTUuOTV6TTggN0g2djRIMnYyaDR2NGgydi00aDR2LTJIOFY3eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImV4cG9zdXJlLXplcm8iPjxwYXRoIGQ9Ik0xNi4xNCAxMi41YzAgMS0uMSAxLjg1LS4zIDIuNTUtLjIuNy0uNDggMS4yNy0uODMgMS43LS4zNi40NC0uNzkuNzUtMS4zLjk1LS41MS4yLTEuMDcuMy0xLjcuMy0uNjIgMC0xLjE4LS4xLTEuNjktLjMtLjUxLS4yLS45NS0uNTEtMS4zMS0uOTUtLjM2LS40NC0uNjUtMS4wMS0uODUtMS43LS4yLS43LS4zLTEuNTUtLjMtMi41NXYtMi4wNGMwLTEgLjEtMS44NS4zLTIuNTUuMi0uNy40OC0xLjI2Ljg0LTEuNjkuMzYtLjQzLjgtLjc0IDEuMzEtLjkzQzEwLjgxIDUuMSAxMS4zOCA1IDEyIDVjLjYzIDAgMS4xOS4xIDEuNy4yOS41MS4xOS45NS41IDEuMzEuOTMuMzYuNDMuNjQuOTkuODQgMS42OS4yLjcuMyAxLjU0LjMgMi41NXYyLjA0em0tMi4xMS0yLjM2YzAtLjY0LS4wNS0xLjE4LS4xMy0xLjYyLS4wOS0uNDQtLjIyLS43OS0uNC0xLjA2LS4xNy0uMjctLjM5LS40Ni0uNjQtLjU4LS4yNS0uMTMtLjU0LS4xOS0uODYtLjE5LS4zMiAwLS42MS4wNi0uODYuMThzLS40Ny4zMS0uNjQuNThjLS4xNy4yNy0uMzEuNjItLjQgMS4wNnMtLjEzLjk4LS4xMyAxLjYydjIuNjdjMCAuNjQuMDUgMS4xOC4xNCAxLjYyLjA5LjQ1LjIzLjgxLjQgMS4wOXMuMzkuNDguNjQuNjEuNTQuMTkuODcuMTljLjMzIDAgLjYyLS4wNi44Ny0uMTlzLjQ2LS4zMy42My0uNjFjLjE3LS4yOC4zLS42NC4zOS0xLjA5LjA5LS40NS4xMy0uOTkuMTMtMS42MnYtMi42NnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXIiPjxwYXRoIGQ9Ik0xNS45NiAxMC4yOWwtMi43NSAzLjU0LTEuOTYtMi4zNkw4LjUgMTVoMTFsLTMuNTQtNC43MXpNMyA1SDF2MTZjMCAxLjEuOSAyIDIgMmgxNnYtMkgzVjV6bTE4LTRIN2MtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlYzYzAtMS4xLS45LTItMi0yem0wIDE2SDdWM2gxNHYxNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXItMSI+PHBhdGggZD0iTTMgNUgxdjE2YzAgMS4xLjkgMiAyIDJoMTZ2LTJIM1Y1em0xMSAxMGgyVjVoLTR2Mmgydjh6bTctMTRIN2MtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlYzYzAtMS4xLS45LTItMi0yem0wIDE2SDdWM2gxNHYxNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXItMiI+PHBhdGggZD0iTTMgNUgxdjE2YzAgMS4xLjkgMiAyIDJoMTZ2LTJIM1Y1em0xOC00SDdjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWM2MwLTEuMS0uOS0yLTItMnptMCAxNkg3VjNoMTR2MTR6bS00LTRoLTR2LTJoMmMxLjEgMCAyLS44OSAyLTJWN2MwLTEuMTEtLjktMi0yLTJoLTR2Mmg0djJoLTJjLTEuMSAwLTIgLjg5LTIgMnY0aDZ2LTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmlsdGVyLTMiPjxwYXRoIGQ9Ik0yMSAxSDdjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWM2MwLTEuMS0uOS0yLTItMnptMCAxNkg3VjNoMTR2MTR6TTMgNUgxdjE2YzAgMS4xLjkgMiAyIDJoMTZ2LTJIM1Y1em0xNCA4di0xLjVjMC0uODMtLjY3LTEuNS0xLjUtMS41LjgzIDAgMS41LS42NyAxLjUtMS41VjdjMC0xLjExLS45LTItMi0yaC00djJoNHYyaC0ydjJoMnYyaC00djJoNGMxLjEgMCAyLS44OSAyLTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmlsdGVyLTQiPjxwYXRoIGQ9Ik0zIDVIMXYxNmMwIDEuMS45IDIgMiAyaDE2di0ySDNWNXptMTIgMTBoMlY1aC0ydjRoLTJWNWgtMnY2aDR2NHptNi0xNEg3Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjNjMC0xLjEtLjktMi0yLTJ6bTAgMTZIN1YzaDE0djE0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZpbHRlci01Ij48cGF0aCBkPSJNMjEgMUg3Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjNjMC0xLjEtLjktMi0yLTJ6bTAgMTZIN1YzaDE0djE0ek0zIDVIMXYxNmMwIDEuMS45IDIgMiAyaDE2di0ySDNWNXptMTQgOHYtMmMwLTEuMTEtLjktMi0yLTJoLTJWN2g0VjVoLTZ2Nmg0djJoLTR2Mmg0YzEuMSAwIDItLjg5IDItMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXItNiI+PHBhdGggZD0iTTMgNUgxdjE2YzAgMS4xLjkgMiAyIDJoMTZ2LTJIM1Y1em0xOC00SDdjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWM2MwLTEuMS0uOS0yLTItMnptMCAxNkg3VjNoMTR2MTR6bS04LTJoMmMxLjEgMCAyLS44OSAyLTJ2LTJjMC0xLjExLS45LTItMi0yaC0yVjdoNFY1aC00Yy0xLjEgMC0yIC44OS0yIDJ2NmMwIDEuMTEuOSAyIDIgMnptMC00aDJ2MmgtMnYtMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXItNyI+PHBhdGggZD0iTTMgNUgxdjE2YzAgMS4xLjkgMiAyIDJoMTZ2LTJIM1Y1em0xOC00SDdjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWM2MwLTEuMS0uOS0yLTItMnptMCAxNkg3VjNoMTR2MTR6bS04LTJsNC04VjVoLTZ2Mmg0bC00IDhoMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXItOCI+PHBhdGggZD0iTTMgNUgxdjE2YzAgMS4xLjkgMiAyIDJoMTZ2LTJIM1Y1em0xOC00SDdjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWM2MwLTEuMS0uOS0yLTItMnptMCAxNkg3VjNoMTR2MTR6bS04LTJoMmMxLjEgMCAyLS44OSAyLTJ2LTEuNWMwLS44My0uNjctMS41LTEuNS0xLjUuODMgMCAxLjUtLjY3IDEuNS0xLjVWN2MwLTEuMTEtLjktMi0yLTJoLTJjLTEuMSAwLTIgLjg5LTIgMnYxLjVjMCAuODMuNjcgMS41IDEuNSAxLjUtLjgzIDAtMS41LjY3LTEuNSAxLjVWMTNjMCAxLjExLjkgMiAyIDJ6bTAtOGgydjJoLTJWN3ptMCA0aDJ2MmgtMnYtMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXItOSI+PHBhdGggZD0iTTMgNUgxdjE2YzAgMS4xLjkgMiAyIDJoMTZ2LTJIM1Y1em0xOC00SDdjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWM2MwLTEuMS0uOS0yLTItMnptMCAxNkg3VjNoMTR2MTR6TTE1IDVoLTJjLTEuMSAwLTIgLjg5LTIgMnYyYzAgMS4xMS45IDIgMiAyaDJ2MmgtNHYyaDRjMS4xIDAgMi0uODkgMi0yVjdjMC0xLjExLS45LTItMi0yem0wIDRoLTJWN2gydjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmlsdGVyLTktcGx1cyI+PHBhdGggZD0iTTMgNUgxdjE2YzAgMS4xLjkgMiAyIDJoMTZ2LTJIM1Y1em0xMSA3VjhjMC0xLjExLS45LTItMi0yaC0xYy0xLjEgMC0yIC44OS0yIDJ2MWMwIDEuMTEuOSAyIDIgMmgxdjFIOXYyaDNjMS4xIDAgMi0uODkgMi0yem0tMy0zVjhoMXYxaC0xem0xMC04SDdjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWM2MwLTEuMS0uOS0yLTItMnptMCA4aC0yVjdoLTJ2MmgtMnYyaDJ2Mmgydi0yaDJ2Nkg3VjNoMTR2NnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXItYi1hbmQtdyI+PHBhdGggZD0iTTE5IDNINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yem0wIDE2bC03LTh2OEg1bDctOFY1aDd2MTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmlsdGVyLWNlbnRlci1mb2N1cyI+PHBhdGggZD0iTTUgMTVIM3Y0YzAgMS4xLjkgMiAyIDJoNHYtMkg1di00ek01IDVoNFYzSDVjLTEuMSAwLTIgLjktMiAydjRoMlY1em0xNC0yaC00djJoNHY0aDJWNWMwLTEuMS0uOS0yLTItMnptMCAxNmgtNHYyaDRjMS4xIDAgMi0uOSAyLTJ2LTRoLTJ2NHpNMTIgOWMtMS42NiAwLTMgMS4zNC0zIDNzMS4zNCAzIDMgMyAzLTEuMzQgMy0zLTEuMzQtMy0zLTN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmlsdGVyLWRyYW1hIj48cGF0aCBkPSJNMTkuMzUgMTAuMDRDMTguNjcgNi41OSAxNS42NCA0IDEyIDQgOS4xMSA0IDYuNjEgNS42NCA1LjM2IDguMDQgMi4zNSA4LjM2IDAgMTAuOSAwIDE0YzAgMy4zMSAyLjY5IDYgNiA2aDEzYzIuNzYgMCA1LTIuMjQgNS01IDAtMi42NC0yLjA1LTQuNzgtNC42NS00Ljk2ek0xOSAxOEg2Yy0yLjIxIDAtNC0xLjc5LTQtNHMxLjc5LTQgNC00IDQgMS43OSA0IDRoMmMwLTIuNzYtMS44Ni01LjA4LTQuNC01Ljc4QzguNjEgNi44OCAxMC4yIDYgMTIgNmMzLjAzIDAgNS41IDIuNDcgNS41IDUuNXYuNUgxOWMxLjY1IDAgMyAxLjM1IDMgM3MtMS4zNSAzLTMgM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXItZnJhbWVzIj48cGF0aCBkPSJNMjAgNGgtNGwtNC00LTQgNEg0Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjZjMC0xLjEtLjktMi0yLTJ6bTAgMTZINFY2aDQuNTJsMy41Mi0zLjVMMTUuNTIgNkgyMHYxNHpNMTggOEg2djEwaDEyIj48L3BhdGg+PC9nPgo8ZyBpZD0iZmlsdGVyLWhkciI+PHBhdGggZD0iTTE0IDZsLTMuNzUgNSAyLjg1IDMuOC0xLjYgMS4yQzkuODEgMTMuNzUgNyAxMCA3IDEwbC02IDhoMjJMMTQgNnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWx0ZXItbm9uZSI+PHBhdGggZD0iTTMgNUgxdjE2YzAgMS4xLjkgMiAyIDJoMTZ2LTJIM1Y1em0xOC00SDdjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWM2MwLTEuMS0uOS0yLTItMnptMCAxNkg3VjNoMTR2MTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmlsdGVyLXRpbHQtc2hpZnQiPjxwYXRoIGQ9Ik0xMSA0LjA3VjIuMDVjLTIuMDEuMi0zLjg0IDEtNS4zMiAyLjIxTDcuMSA1LjY5YzEuMTEtLjg2IDIuNDQtMS40NCAzLjktMS42MnptNy4zMi4xOUMxNi44NCAzLjA1IDE1LjAxIDIuMjUgMTMgMi4wNXYyLjAyYzEuNDYuMTggMi43OS43NiAzLjkgMS42MmwxLjQyLTEuNDN6TTE5LjkzIDExaDIuMDJjLS4yLTIuMDEtMS0zLjg0LTIuMjEtNS4zMkwxOC4zMSA3LjFjLjg2IDEuMTEgMS40NCAyLjQ0IDEuNjIgMy45ek01LjY5IDcuMUw0LjI2IDUuNjhDMy4wNSA3LjE2IDIuMjUgOC45OSAyLjA1IDExaDIuMDJjLjE4LTEuNDYuNzYtMi43OSAxLjYyLTMuOXpNNC4wNyAxM0gyLjA1Yy4yIDIuMDEgMSAzLjg0IDIuMjEgNS4zMmwxLjQzLTEuNDNjLS44Ni0xLjEtMS40NC0yLjQzLTEuNjItMy44OXpNMTUgMTJjMC0xLjY2LTEuMzQtMy0zLTNzLTMgMS4zNC0zIDMgMS4zNCAzIDMgMyAzLTEuMzQgMy0zem0zLjMxIDQuOWwxLjQzIDEuNDNjMS4yMS0xLjQ4IDIuMDEtMy4zMiAyLjIxLTUuMzJoLTIuMDJjLS4xOCAxLjQ1LS43NiAyLjc4LTEuNjIgMy44OXpNMTMgMTkuOTN2Mi4wMmMyLjAxLS4yIDMuODQtMSA1LjMyLTIuMjFsLTEuNDMtMS40M2MtMS4xLjg2LTIuNDMgMS40NC0zLjg5IDEuNjJ6bS03LjMyLS4xOUM3LjE2IDIwLjk1IDkgMjEuNzUgMTEgMjEuOTV2LTIuMDJjLTEuNDYtLjE4LTIuNzktLjc2LTMuOS0xLjYybC0xLjQyIDEuNDN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmlsdGVyLXZpbnRhZ2UiPjxwYXRoIGQ9Ik0xOC43IDEyLjRjLS4yOC0uMTYtLjU3LS4yOS0uODYtLjQuMjktLjExLjU4LS4yNC44Ni0uNCAxLjkyLTEuMTEgMi45OS0zLjEyIDMtNS4xOS0xLjc5LTEuMDMtNC4wNy0xLjExLTYgMC0uMjguMTYtLjU0LjM1LS43OC41NC4wNS0uMzEuMDgtLjYzLjA4LS45NSAwLTIuMjItMS4yMS00LjE1LTMtNS4xOUMxMC4yMSAxLjg1IDkgMy43OCA5IDZjMCAuMzIuMDMuNjQuMDguOTUtLjI0LS4yLS41LS4zOS0uNzgtLjU1LTEuOTItMS4xMS00LjItMS4wMy02IDAgMCAyLjA3IDEuMDcgNC4wOCAzIDUuMTkuMjguMTYuNTcuMjkuODYuNC0uMjkuMTEtLjU4LjI0LS44Ni40LTEuOTIgMS4xMS0yLjk5IDMuMTItMyA1LjE5IDEuNzkgMS4wMyA0LjA3IDEuMTEgNiAwIC4yOC0uMTYuNTQtLjM1Ljc4LS41NC0uMDUuMzItLjA4LjY0LS4wOC45NiAwIDIuMjIgMS4yMSA0LjE1IDMgNS4xOSAxLjc5LTEuMDQgMy0yLjk3IDMtNS4xOSAwLS4zMi0uMDMtLjY0LS4wOC0uOTUuMjQuMi41LjM4Ljc4LjU0IDEuOTIgMS4xMSA0LjIgMS4wMyA2IDAtLjAxLTIuMDctMS4wOC00LjA4LTMtNS4xOXpNMTIgMTZjLTIuMjEgMC00LTEuNzktNC00czEuNzktNCA0LTQgNCAxLjc5IDQgNC0xLjc5IDQtNCA0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZsYXJlIj48cGF0aCBkPSJNNyAxMUgxdjJoNnYtMnptMi4xNy0zLjI0TDcuMDUgNS42NCA1LjY0IDcuMDVsMi4xMiAyLjEyIDEuNDEtMS40MXpNMTMgMWgtMnY2aDJWMXptNS4zNiA2LjA1bC0xLjQxLTEuNDEtMi4xMiAyLjEyIDEuNDEgMS40MSAyLjEyLTIuMTJ6TTE3IDExdjJoNnYtMmgtNnptLTUtMmMtMS42NiAwLTMgMS4zNC0zIDNzMS4zNCAzIDMgMyAzLTEuMzQgMy0zLTEuMzQtMy0zLTN6bTIuODMgNy4yNGwyLjEyIDIuMTIgMS40MS0xLjQxLTIuMTItMi4xMi0xLjQxIDEuNDF6bS05LjE5LjcxbDEuNDEgMS40MSAyLjEyLTIuMTItMS40MS0xLjQxLTIuMTIgMi4xMnpNMTEgMjNoMnYtNmgtMnY2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZsYXNoLWF1dG8iPjxwYXRoIGQ9Ik0zIDJ2MTJoM3Y5bDctMTJIOWw0LTlIM3ptMTYgMGgtMmwtMy4yIDloMS45bC43LTJoMy4ybC43IDJoMS45TDE5IDJ6bS0yLjE1IDUuNjVMMTggNGwxLjE1IDMuNjVoLTIuM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJmbGFzaC1vZmYiPjxwYXRoIGQ9Ik0zLjI3IDNMMiA0LjI3bDUgNVYxM2gzdjlsMy41OC02LjE0TDE3LjczIDIwIDE5IDE4LjczIDMuMjcgM3pNMTcgMTBoLTRsNC04SDd2Mi4xOGw4LjQ2IDguNDZMMTcgMTB6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmxhc2gtb24iPjxwYXRoIGQ9Ik03IDJ2MTFoM3Y5bDctMTJoLTRsNC04eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZsaXAiPjxwYXRoIGQ9Ik0xNSAyMWgydi0yaC0ydjJ6bTQtMTJoMlY3aC0ydjJ6TTMgNXYxNGMwIDEuMS45IDIgMiAyaDR2LTJINVY1aDRWM0g1Yy0xLjEgMC0yIC45LTIgMnptMTYtMnYyaDJjMC0xLjEtLjktMi0yLTJ6bS04IDIwaDJWMWgtMnYyMnptOC02aDJ2LTJoLTJ2MnpNMTUgNWgyVjNoLTJ2MnptNCA4aDJ2LTJoLTJ2MnptMCA4YzEuMSAwIDItLjkgMi0yaC0ydjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZ3JhZGllbnQiPjxwYXRoIGQ9Ik0xMSA5aDJ2MmgtMnptLTIgMmgydjJIOXptNCAwaDJ2MmgtMnptMi0yaDJ2MmgtMnpNNyA5aDJ2Mkg3em0xMi02SDVjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnpNOSAxOEg3di0yaDJ2MnptNCAwaC0ydi0yaDJ2MnptNCAwaC0ydi0yaDJ2MnptMi03aC0ydjJoMnYyaC0ydi0yaC0ydjJoLTJ2LTJoLTJ2Mkg5di0ySDd2Mkg1di0yaDJ2LTJINVY1aDE0djZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZ3JhaW4iPjxwYXRoIGQ9Ik0xMCAxMmMtMS4xIDAtMiAuOS0yIDJzLjkgMiAyIDIgMi0uOSAyLTItLjktMi0yLTJ6TTYgOGMtMS4xIDAtMiAuOS0yIDJzLjkgMiAyIDIgMi0uOSAyLTItLjktMi0yLTJ6bTAgOGMtMS4xIDAtMiAuOS0yIDJzLjkgMiAyIDIgMi0uOSAyLTItLjktMi0yLTJ6bTEyLThjMS4xIDAgMi0uOSAyLTJzLS45LTItMi0yLTIgLjktMiAyIC45IDIgMiAyem0tNCA4Yy0xLjEgMC0yIC45LTIgMnMuOSAyIDIgMiAyLS45IDItMi0uOS0yLTItMnptNC00Yy0xLjEgMC0yIC45LTIgMnMuOSAyIDIgMiAyLS45IDItMi0uOS0yLTItMnptLTQtNGMtMS4xIDAtMiAuOS0yIDJzLjkgMiAyIDIgMi0uOSAyLTItLjktMi0yLTJ6bS00LTRjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImdyaWQtb2ZmIj48cGF0aCBkPSJNOCA0djEuNDVsMiAyVjRoNHY0aC0zLjQ1bDIgMkgxNHYxLjQ1bDIgMlYxMGg0djRoLTMuNDVsMiAySDIwdjEuNDVsMiAyVjRjMC0xLjEtLjktMi0yLTJINC41NWwyIDJIOHptOCAwaDR2NGgtNFY0ek0xLjI3IDEuMjdMMCAyLjU1bDIgMlYyMGMwIDEuMS45IDIgMiAyaDE1LjQ2bDIgMiAxLjI3LTEuMjdMMS4yNyAxLjI3ek0xMCAxMi41NUwxMS40NSAxNEgxMHYtMS40NXptLTYtNkw1LjQ1IDhINFY2LjU1ek04IDIwSDR2LTRoNHY0em0wLTZINHYtNGgzLjQ1bC41NS41NVYxNHptNiA2aC00di00aDMuNDVsLjU1LjU0VjIwem0yIDB2LTEuNDZMMTcuNDYgMjBIMTZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZ3JpZC1vbiI+PHBhdGggZD0iTTIwIDJINGMtMS4xIDAtMiAuOS0yIDJ2MTZjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY0YzAtMS4xLS45LTItMi0yek04IDIwSDR2LTRoNHY0em0wLTZINHYtNGg0djR6bTAtNkg0VjRoNHY0em02IDEyaC00di00aDR2NHptMC02aC00di00aDR2NHptMC02aC00VjRoNHY0em02IDEyaC00di00aDR2NHptMC02aC00di00aDR2NHptMC02aC00VjRoNHY0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9Imhkci1vZmYiPjxwYXRoIGQ9Ik0xNy41IDE1di0yaDEuMWwuOSAySDIxbC0uOS0yLjFjLjUtLjIuOS0uOC45LTEuNHYtMWMwLS44LS43LTEuNS0xLjUtMS41SDE2djQuOWwxLjEgMS4xaC40em0wLTQuNWgydjFoLTJ2LTF6bS00LjUgMHYuNGwxLjUgMS41di0xLjljMC0uOC0uNy0xLjUtMS41LTEuNWgtMS45bDEuNSAxLjVoLjR6bS0zLjUtMWwtNy03LTEuMSAxTDYuOSA5aC0uNHYyaC0yVjlIM3Y2aDEuNXYtMi41aDJWMTVIOHYtNC45bDEuNSAxLjVWMTVoMy40bDcuNiA3LjYgMS4xLTEuMS0xMi4xLTEyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9Imhkci1vbiI+PHBhdGggZD0iTTIxIDExLjV2LTFjMC0uOC0uNy0xLjUtMS41LTEuNUgxNnY2aDEuNXYtMmgxLjFsLjkgMkgyMWwtLjktMi4xYy41LS4zLjktLjguOS0xLjR6bS0xLjUgMGgtMnYtMWgydjF6bS0xMy0uNWgtMlY5SDN2NmgxLjV2LTIuNWgyVjE1SDhWOUg2LjV2MnpNMTMgOUg5LjV2NkgxM2MuOCAwIDEuNS0uNyAxLjUtMS41di0zYzAtLjgtLjctMS41LTEuNS0xLjV6bTAgNC41aC0ydi0zaDJ2M3oiPjwvcGF0aD48L2c+CjxnIGlkPSJoZHItc3Ryb25nIj48cGF0aCBkPSJNMTcgNmMtMy4zMSAwLTYgMi42OS02IDZzMi42OSA2IDYgNiA2LTIuNjkgNi02LTIuNjktNi02LTZ6TTUgOGMtMi4yMSAwLTQgMS43OS00IDRzMS43OSA0IDQgNCA0LTEuNzkgNC00LTEuNzktNC00LTR6bTAgNmMtMS4xIDAtMi0uOS0yLTJzLjktMiAyLTIgMiAuOSAyIDItLjkgMi0yIDJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iaGRyLXdlYWsiPjxwYXRoIGQ9Ik01IDhjLTIuMjEgMC00IDEuNzktNCA0czEuNzkgNCA0IDQgNC0xLjc5IDQtNC0xLjc5LTQtNC00em0xMi0yYy0zLjMxIDAtNiAyLjY5LTYgNnMyLjY5IDYgNiA2IDYtMi42OSA2LTYtMi42OS02LTYtNnptMCAxMGMtMi4yMSAwLTQtMS43OS00LTRzMS43OS00IDQtNCA0IDEuNzkgNCA0LTEuNzkgNC00IDR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iaGVhbGluZyI+PHBhdGggZD0iTTE3LjczIDEyLjAybDMuOTgtMy45OGMuMzktLjM5LjM5LTEuMDIgMC0xLjQxbC00LjM0LTQuMzRjLS4zOS0uMzktMS4wMi0uMzktMS40MSAwbC0zLjk4IDMuOThMOCAyLjI5QzcuOCAyLjEgNy41NSAyIDcuMjkgMmMtLjI1IDAtLjUxLjEtLjcuMjlMMi4yNSA2LjYzYy0uMzkuMzktLjM5IDEuMDIgMCAxLjQxbDMuOTggMy45OEwyLjI1IDE2Yy0uMzkuMzktLjM5IDEuMDIgMCAxLjQxbDQuMzQgNC4zNGMuMzkuMzkgMS4wMi4zOSAxLjQxIDBsMy45OC0zLjk4IDMuOTggMy45OGMuMi4yLjQ1LjI5LjcxLjI5LjI2IDAgLjUxLS4xLjcxLS4yOWw0LjM0LTQuMzRjLjM5LS4zOS4zOS0xLjAyIDAtMS40MWwtMy45OS0zLjk4ek0xMiA5Yy41NSAwIDEgLjQ1IDEgMXMtLjQ1IDEtMSAxLTEtLjQ1LTEtMSAuNDUtMSAxLTF6bS00LjcxIDEuOTZMMy42NiA3LjM0bDMuNjMtMy42MyAzLjYyIDMuNjItMy42MiAzLjYzek0xMCAxM2MtLjU1IDAtMS0uNDUtMS0xcy40NS0xIDEtMSAxIC40NSAxIDEtLjQ1IDEtMSAxem0yIDJjLS41NSAwLTEtLjQ1LTEtMXMuNDUtMSAxLTEgMSAuNDUgMSAxLS40NSAxLTEgMXptMi00Yy41NSAwIDEgLjQ1IDEgMXMtLjQ1IDEtMSAxLTEtLjQ1LTEtMSAuNDUtMSAxLTF6bTIuNjYgOS4zNGwtMy42My0zLjYyIDMuNjMtMy42MyAzLjYyIDMuNjItMy42MiAzLjYzeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImltYWdlIj48cGF0aCBkPSJNMjEgMTlWNWMwLTEuMS0uOS0yLTItMkg1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yek04LjUgMTMuNWwyLjUgMy4wMUwxNC41IDEybDQuNSA2SDVsMy41LTQuNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJpbWFnZS1hc3BlY3QtcmF0aW8iPjxwYXRoIGQ9Ik0xNiAxMGgtMnYyaDJ2LTJ6bTAgNGgtMnYyaDJ2LTJ6bS04LTRINnYyaDJ2LTJ6bTQgMGgtMnYyaDJ2LTJ6bTgtNkg0Yy0xLjEgMC0yIC45LTIgMnYxMmMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjZjMC0xLjEtLjktMi0yLTJ6bTAgMTRINFY2aDE2djEyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImlzbyI+PHBhdGggZD0iTTE5IDNINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yek01LjUgNy41aDJ2LTJIOXYyaDJWOUg5djJINy41VjloLTJWNy41ek0xOSAxOUg1TDE5IDV2MTR6bS0yLTJ2LTEuNWgtNVYxN2g1eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImxhbmRzY2FwZSI+PHBhdGggZD0iTTE0IDZsLTMuNzUgNSAyLjg1IDMuOC0xLjYgMS4yQzkuODEgMTMuNzUgNyAxMCA3IDEwbC02IDhoMjJMMTQgNnoiPjwvcGF0aD48L2c+CjxnIGlkPSJsZWFrLWFkZCI+PHBhdGggZD0iTTYgM0gzdjNjMS42NiAwIDMtMS4zNCAzLTN6bTggMGgtMmMwIDQuOTctNC4wMyA5LTkgOXYyYzYuMDggMCAxMS00LjkzIDExLTExem0tNCAwSDhjMCAyLjc2LTIuMjQgNS01IDV2MmMzLjg3IDAgNy0zLjEzIDctN3ptMCAxOGgyYzAtNC45NyA0LjAzLTkgOS05di0yYy02LjA3IDAtMTEgNC45My0xMSAxMXptOCAwaDN2LTNjLTEuNjYgMC0zIDEuMzQtMyAzem0tNCAwaDJjMC0yLjc2IDIuMjQtNSA1LTV2LTJjLTMuODcgMC03IDMuMTMtNyA3eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImxlYWstcmVtb3ZlIj48cGF0aCBkPSJNMTAgM0g4YzAgLjM3LS4wNC43Mi0uMTIgMS4wNmwxLjU5IDEuNTlDOS44MSA0Ljg0IDEwIDMuOTQgMTAgM3pNMyA0LjI3bDIuODQgMi44NEM1LjAzIDcuNjcgNC4wNiA4IDMgOHYyYzEuNjEgMCAzLjA5LS41NSA0LjI3LTEuNDZMOC43IDkuOTdDNy4xNCAxMS4yNCA1LjE2IDEyIDMgMTJ2MmMyLjcxIDAgNS4xOS0uOTkgNy4xMS0yLjYybDIuNSAyLjVDMTAuOTkgMTUuODEgMTAgMTguMjkgMTAgMjFoMmMwLTIuMTYuNzYtNC4xNCAyLjAzLTUuNjlsMS40MyAxLjQzQzE0LjU1IDE3LjkxIDE0IDE5LjM5IDE0IDIxaDJjMC0xLjA2LjMzLTIuMDMuODktMi44NEwxOS43MyAyMSAyMSAxOS43MyA0LjI3IDMgMyA0LjI3ek0xNCAzaC0yYzAgMS41LS4zNyAyLjkxLTEuMDIgNC4xNmwxLjQ2IDEuNDZDMTMuNDIgNi45OCAxNCA1LjA2IDE0IDN6bTUuOTQgMTMuMTJjLjM0LS4wOC42OS0uMTIgMS4wNi0uMTJ2LTJjLS45NCAwLTEuODQuMTktMi42Ni41MmwxLjYgMS42em0tNC41Ni00LjU2bDEuNDYgMS40NkMxOC4wOSAxMi4zNyAxOS41IDEyIDIxIDEydi0yYy0yLjA2IDAtMy45OC41OC01LjYyIDEuNTZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ibGVucyI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ibGlua2VkLWNhbWVyYSI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxNCIgcj0iMy4yIj48L2NpcmNsZT48cGF0aCBkPSJNMTYgMy4zM2MyLjU4IDAgNC42NyAyLjA5IDQuNjcgNC42N0gyMmMwLTMuMzEtMi42OS02LTYtNnYxLjMzTTE2IDZjMS4xMSAwIDIgLjg5IDIgMmgxLjMzYzAtMS44NC0xLjQ5LTMuMzMtMy4zMy0zLjMzVjYiPjwvcGF0aD48cGF0aCBkPSJNMTcgOWMwLTEuMTEtLjg5LTItMi0yVjRIOUw3LjE3IDZINGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY5aC01em0tNSAxMGMtMi43NiAwLTUtMi4yNC01LTVzMi4yNC01IDUtNSA1IDIuMjQgNSA1LTIuMjQgNS01IDV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ibG9va3MiPjxwYXRoIGQ9Ik0xMiAxMGMtMy44NiAwLTcgMy4xNC03IDdoMmMwLTIuNzYgMi4yNC01IDUtNXM1IDIuMjQgNSA1aDJjMC0zLjg2LTMuMTQtNy03LTd6bTAtNEM1LjkzIDYgMSAxMC45MyAxIDE3aDJjMC00Ljk2IDQuMDQtOSA5LTlzOSA0LjA0IDkgOWgyYzAtNi4wNy00LjkzLTExLTExLTExeiI+PC9wYXRoPjwvZz4KPGcgaWQ9Imxvb2tzLTMiPjxwYXRoIGQ9Ik0xOS4wMSAzaC0xNGMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yem0tNCA3LjVjMCAuODMtLjY3IDEuNS0xLjUgMS41LjgzIDAgMS41LjY3IDEuNSAxLjVWMTVjMCAxLjExLS45IDItMiAyaC00di0yaDR2LTJoLTJ2LTJoMlY5aC00VjdoNGMxLjEgMCAyIC44OSAyIDJ2MS41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9Imxvb2tzLTQiPjxwYXRoIGQ9Ik0xOSAzSDVjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnptLTQgMTRoLTJ2LTRIOVY3aDJ2NGgyVjdoMnYxMHoiPjwvcGF0aD48L2c+CjxnIGlkPSJsb29rcy01Ij48cGF0aCBkPSJNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS00IDZoLTR2MmgyYzEuMSAwIDIgLjg5IDIgMnYyYzAgMS4xMS0uOSAyLTIgMkg5di0yaDR2LTJIOVY3aDZ2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJsb29rcy02Ij48cGF0aCBkPSJNMTEgMTVoMnYtMmgtMnYyem04LTEySDVjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnptLTQgNmgtNHYyaDJjMS4xIDAgMiAuODkgMiAydjJjMCAxLjExLS45IDItMiAyaC0yYy0xLjEgMC0yLS44OS0yLTJWOWMwLTEuMTEuOS0yIDItMmg0djJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ibG9va3Mtb25lIj48cGF0aCBkPSJNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS01IDE0aC0yVjloLTJWN2g0djEweiI+PC9wYXRoPjwvZz4KPGcgaWQ9Imxvb2tzLXR3byI+PHBhdGggZD0iTTE5IDNINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yem0tNCA4YzAgMS4xMS0uOSAyLTIgMmgtMnYyaDR2Mkg5di00YzAtMS4xMS45LTIgMi0yaDJWOUg5VjdoNGMxLjEgMCAyIC44OSAyIDJ2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJsb3VwZSI+PHBhdGggZD0iTTEzIDdoLTJ2NEg3djJoNHY0aDJ2LTRoNHYtMmgtNFY3em0tMS01QzYuNDkgMiAyIDYuNDkgMiAxMnM0LjQ5IDEwIDEwIDEwaDhjMS4xIDAgMi0uOSAyLTJ2LThjMC01LjUxLTQuNDktMTAtMTAtMTB6bTAgMThjLTQuNDEgMC04LTMuNTktOC04czMuNTktOCA4LTggOCAzLjU5IDggOC0zLjU5IDgtOCA4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9Im1vbm9jaHJvbWUtcGhvdG9zIj48cGF0aCBkPSJNMjAgNWgtMy4yTDE1IDNIOUw3LjIgNUg0Yy0xLjEgMC0yIC45LTIgMnYxMmMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjdjMC0xLjEtLjktMi0yLTJ6bTAgMTRoLTh2LTFjLTIuOCAwLTUtMi4yLTUtNXMyLjItNSA1LTVWN2g4djEyem0tMy02YzAtMi44LTIuMi01LTUtNXYxLjhjMS44IDAgMy4yIDEuNCAzLjIgMy4ycy0xLjQgMy4yLTMuMiAzLjJWMThjMi44IDAgNS0yLjIgNS01em0tOC4yIDBjMCAxLjggMS40IDMuMiAzLjIgMy4yVjkuOGMtMS44IDAtMy4yIDEuNC0zLjIgMy4yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9Im1vdmllLWNyZWF0aW9uIj48cGF0aCBkPSJNMTggNGwyIDRoLTNsLTItNGgtMmwyIDRoLTNsLTItNEg4bDIgNEg3TDUgNEg0Yy0xLjEgMC0xLjk5LjktMS45OSAyTDIgMThjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY0aC00eiI+PC9wYXRoPjwvZz4KPGcgaWQ9Im1vdmllLWZpbHRlciI+PHBhdGggZD0iTTE4IDRsMiAzaC0zbC0yLTNoLTJsMiAzaC0zbC0yLTNIOGwyIDNIN0w1IDRINGMtMS4xIDAtMS45OS45LTEuOTkgMkwyIDE4YzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWNGgtNHptLTYuNzUgMTEuMjVMMTAgMThsLTEuMjUtMi43NUw2IDE0bDIuNzUtMS4yNUwxMCAxMGwxLjI1IDIuNzVMMTQgMTRsLTIuNzUgMS4yNXptNS42OS0zLjMxTDE2IDE0bC0uOTQtMi4wNkwxMyAxMWwyLjA2LS45NEwxNiA4bC45NCAyLjA2TDE5IDExbC0yLjA2Ljk0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9Im11c2ljLW5vdGUiPjxwYXRoIGQ9Ik0xMiAzdjEwLjU1Yy0uNTktLjM0LTEuMjctLjU1LTItLjU1LTIuMjEgMC00IDEuNzktNCA0czEuNzkgNCA0IDQgNC0xLjc5IDQtNFY3aDRWM2gtNnoiPjwvcGF0aD48L2c+CjxnIGlkPSJuYXR1cmUiPjxwYXRoIGQ9Ik0xMyAxNi4xMmMzLjQ3LS40MSA2LjE3LTMuMzYgNi4xNy02Ljk1IDAtMy44Ny0zLjEzLTctNy03cy03IDMuMTMtNyA3YzAgMy40NyAyLjUyIDYuMzQgNS44MyA2Ljg5VjIwSDV2MmgxNHYtMmgtNnYtMy44OHoiPjwvcGF0aD48L2c+CjxnIGlkPSJuYXR1cmUtcGVvcGxlIj48cGF0aCBkPSJNMjIuMTcgOS4xN2MwLTMuODctMy4xMy03LTctN3MtNyAzLjEzLTcgN2MwIDMuNDcgMi41MiA2LjM0IDUuODMgNi44OVYyMEg2di0zaDF2LTRjMC0uNTUtLjQ1LTEtMS0xSDNjLS41NSAwLTEgLjQ1LTEgMXY0aDF2NWgxNnYtMmgtM3YtMy44OGMzLjQ3LS40MSA2LjE3LTMuMzYgNi4xNy02Ljk1ek00LjUgMTFjLjgzIDAgMS41LS42NyAxLjUtMS41UzUuMzMgOCA0LjUgOCAzIDguNjcgMyA5LjUgMy42NyAxMSA0LjUgMTF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ibmF2aWdhdGUtYmVmb3JlIj48cGF0aCBkPSJNMTUuNDEgNy40MUwxNCA2bC02IDYgNiA2IDEuNDEtMS40MUwxMC44MyAxMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJuYXZpZ2F0ZS1uZXh0Ij48cGF0aCBkPSJNMTAgNkw4LjU5IDcuNDEgMTMuMTcgMTJsLTQuNTggNC41OUwxMCAxOGw2LTZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGFsZXR0ZSI+PHBhdGggZD0iTTEyIDNjLTQuOTcgMC05IDQuMDMtOSA5czQuMDMgOSA5IDljLjgzIDAgMS41LS42NyAxLjUtMS41IDAtLjM5LS4xNS0uNzQtLjM5LTEuMDEtLjIzLS4yNi0uMzgtLjYxLS4zOC0uOTkgMC0uODMuNjctMS41IDEuNS0xLjVIMTZjMi43NiAwIDUtMi4yNCA1LTUgMC00LjQyLTQuMDMtOC05LTh6bS01LjUgOWMtLjgzIDAtMS41LS42Ny0xLjUtMS41UzUuNjcgOSA2LjUgOSA4IDkuNjcgOCAxMC41IDcuMzMgMTIgNi41IDEyem0zLTRDOC42NyA4IDggNy4zMyA4IDYuNVM4LjY3IDUgOS41IDVzMS41LjY3IDEuNSAxLjVTMTAuMzMgOCA5LjUgOHptNSAwYy0uODMgMC0xLjUtLjY3LTEuNS0xLjVTMTMuNjcgNSAxNC41IDVzMS41LjY3IDEuNSAxLjVTMTUuMzMgOCAxNC41IDh6bTMgNGMtLjgzIDAtMS41LS42Ny0xLjUtMS41UzE2LjY3IDkgMTcuNSA5czEuNS42NyAxLjUgMS41LS42NyAxLjUtMS41IDEuNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJwYW5vcmFtYSI+PHBhdGggZD0iTTIzIDE4VjZjMC0xLjEtLjktMi0yLTJIM2MtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxOGMxLjEgMCAyLS45IDItMnpNOC41IDEyLjVsMi41IDMuMDFMMTQuNSAxMWw0LjUgNkg1bDMuNS00LjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGFub3JhbWEtZmlzaC1leWUiPjxwYXRoIGQ9Ik0xMiAyQzYuNDcgMiAyIDYuNDcgMiAxMnM0LjQ3IDEwIDEwIDEwIDEwLTQuNDcgMTAtMTBTMTcuNTMgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDgtMy41OSA4LTggOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJwYW5vcmFtYS1ob3Jpem9udGFsIj48cGF0aCBkPSJNMjAgNi41NHYxMC45MWMtMi42LS43Ny01LjI4LTEuMTYtOC0xLjE2LTIuNzIgMC01LjQuMzktOCAxLjE2VjYuNTRjMi42Ljc3IDUuMjggMS4xNiA4IDEuMTYgMi43Mi4wMSA1LjQtLjM4IDgtMS4xNk0yMS40MyA0Yy0uMSAwLS4yLjAyLS4zMS4wNkMxOC4xOCA1LjE2IDE1LjA5IDUuNyAxMiA1LjdjLTMuMDkgMC02LjE4LS41NS05LjEyLTEuNjQtLjExLS4wNC0uMjItLjA2LS4zMS0uMDYtLjM0IDAtLjU3LjIzLS41Ny42M3YxNC43NWMwIC4zOS4yMy42Mi41Ny42Mi4xIDAgLjItLjAyLjMxLS4wNiAyLjk0LTEuMSA2LjAzLTEuNjQgOS4xMi0xLjY0IDMuMDkgMCA2LjE4LjU1IDkuMTIgMS42NC4xMS4wNC4yMS4wNi4zMS4wNi4zMyAwIC41Ny0uMjMuNTctLjYzVjQuNjNjMC0uNC0uMjQtLjYzLS41Ny0uNjN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGFub3JhbWEtdmVydGljYWwiPjxwYXRoIGQ9Ik0xOS45NCAyMS4xMmMtMS4xLTIuOTQtMS42NC02LjAzLTEuNjQtOS4xMiAwLTMuMDkuNTUtNi4xOCAxLjY0LTkuMTIuMDQtLjExLjA2LS4yMi4wNi0uMzEgMC0uMzQtLjIzLS41Ny0uNjMtLjU3SDQuNjNjLS40IDAtLjYzLjIzLS42My41NyAwIC4xLjAyLjIuMDYuMzFDNS4xNiA1LjgyIDUuNzEgOC45MSA1LjcxIDEyYzAgMy4wOS0uNTUgNi4xOC0xLjY0IDkuMTItLjA1LjExLS4wNy4yMi0uMDcuMzEgMCAuMzMuMjMuNTcuNjMuNTdoMTQuNzVjLjM5IDAgLjYzLS4yNC42My0uNTctLjAxLS4xLS4wMy0uMi0uMDctLjMxek02LjU0IDIwYy43Ny0yLjYgMS4xNi01LjI4IDEuMTYtOCAwLTIuNzItLjM5LTUuNC0xLjE2LThoMTAuOTFjLS43NyAyLjYtMS4xNiA1LjI4LTEuMTYgOCAwIDIuNzIuMzkgNS40IDEuMTYgOEg2LjU0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBhbm9yYW1hLXdpZGUtYW5nbGUiPjxwYXRoIGQ9Ik0xMiA2YzIuNDUgMCA0LjcxLjIgNy4yOS42NC40NyAxLjc4LjcxIDMuNTguNzEgNS4zNiAwIDEuNzgtLjI0IDMuNTgtLjcxIDUuMzYtMi41OC40NC00Ljg0LjY0LTcuMjkuNjRzLTQuNzEtLjItNy4yOS0uNjRDNC4yNCAxNS41OCA0IDEzLjc4IDQgMTJjMC0xLjc4LjI0LTMuNTguNzEtNS4zNkM3LjI5IDYuMiA5LjU1IDYgMTIgNm0wLTJjLTIuNzMgMC01LjIyLjI0LTcuOTUuNzJsLS45My4xNi0uMjUuOUMyLjI5IDcuODUgMiA5LjkzIDIgMTJzLjI5IDQuMTUuODcgNi4yMmwuMjUuODkuOTMuMTZjMi43My40OSA1LjIyLjczIDcuOTUuNzNzNS4yMi0uMjQgNy45NS0uNzJsLjkzLS4xNi4yNS0uODljLjU4LTIuMDguODctNC4xNi44Ny02LjIzcy0uMjktNC4xNS0uODctNi4yMmwtLjI1LS44OS0uOTMtLjE2QzE3LjIyIDQuMjQgMTQuNzMgNCAxMiA0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBob3RvIj48cGF0aCBkPSJNMjEgMTlWNWMwLTEuMS0uOS0yLTItMkg1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yek04LjUgMTMuNWwyLjUgMy4wMUwxNC41IDEybDQuNSA2SDVsMy41LTQuNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJwaG90by1hbGJ1bSI+PHBhdGggZD0iTTE4IDJINmMtMS4xIDAtMiAuOS0yIDJ2MTZjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlY0YzAtMS4xLS45LTItMi0yek02IDRoNXY4bC0yLjUtMS41TDYgMTJWNHptMCAxNWwzLTMuODYgMi4xNCAyLjU4IDMtMy44NkwxOCAxOUg2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBob3RvLWNhbWVyYSI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMy4yIj48L2NpcmNsZT48cGF0aCBkPSJNOSAyTDcuMTcgNEg0Yy0xLjEgMC0yIC45LTIgMnYxMmMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjZjMC0xLjEtLjktMi0yLTJoLTMuMTdMMTUgMkg5em0zIDE1Yy0yLjc2IDAtNS0yLjI0LTUtNXMyLjI0LTUgNS01IDUgMi4yNCA1IDUtMi4yNCA1LTUgNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJwaG90by1maWx0ZXIiPjxwYXRoIGQ9Ik0xOS4wMiAxMHY5SDVWNWg5VjNINS4wMmMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMnYtOWgtMnpNMTcgMTBsLjk0LTIuMDZMMjAgN2wtMi4wNi0uOTRMMTcgNGwtLjk0IDIuMDZMMTQgN2wyLjA2Ljk0em0tMy43NS43NUwxMiA4bC0xLjI1IDIuNzVMOCAxMmwyLjc1IDEuMjVMMTIgMTZsMS4yNS0yLjc1TDE2IDEyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBob3RvLWxpYnJhcnkiPjxwYXRoIGQ9Ik0yMiAxNlY0YzAtMS4xLS45LTItMi0ySDhjLTEuMSAwLTIgLjktMiAydjEyYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJ6bS0xMS00bDIuMDMgMi43MUwxNiAxMWw0IDVIOGwzLTR6TTIgNnYxNGMwIDEuMS45IDIgMiAyaDE0di0ySDRWNkgyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBob3RvLXNpemUtc2VsZWN0LWFjdHVhbCI+PHBhdGggZD0iTTIxIDNIM0MyIDMgMSA0IDEgNXYxNGMwIDEuMS45IDIgMiAyaDE4YzEgMCAyLTEgMi0yVjVjMC0xLTEtMi0yLTJ6TTUgMTdsMy41LTQuNSAyLjUgMy4wMUwxNC41IDExbDQuNSA2SDV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGhvdG8tc2l6ZS1zZWxlY3QtbGFyZ2UiPjxwYXRoIGQ9Ik0yMSAxNWgydjJoLTJ2LTJ6bTAtNGgydjJoLTJ2LTJ6bTIgOGgtMnYyYzEgMCAyLTEgMi0yek0xMyAzaDJ2MmgtMlYzem04IDRoMnYyaC0yVjd6bTAtNHYyaDJjMC0xLTEtMi0yLTJ6TTEgN2gydjJIMVY3em0xNi00aDJ2MmgtMlYzem0wIDE2aDJ2MmgtMnYtMnpNMyAzQzIgMyAxIDQgMSA1aDJWM3ptNiAwaDJ2Mkg5VjN6TTUgM2gydjJINVYzem0tNCA4djhjMCAxLjEuOSAyIDIgMmgxMlYxMUgxem0yIDhsMi41LTMuMjEgMS43OSAyLjE1IDIuNS0zLjIyTDEzIDE5SDN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGhvdG8tc2l6ZS1zZWxlY3Qtc21hbGwiPjxwYXRoIGQ9Ik0yMyAxNWgtMnYyaDJ2LTJ6bTAtNGgtMnYyaDJ2LTJ6bTAgOGgtMnYyYzEgMCAyLTEgMi0yek0xNSAzaC0ydjJoMlYzem04IDRoLTJ2MmgyVjd6bS0yLTR2MmgyYzAtMS0xLTItMi0yek0zIDIxaDh2LTZIMXY0YzAgMS4xLjkgMiAyIDJ6TTMgN0gxdjJoMlY3em0xMiAxMmgtMnYyaDJ2LTJ6bTQtMTZoLTJ2MmgyVjN6bTAgMTZoLTJ2Mmgydi0yek0zIDNDMiAzIDEgNCAxIDVoMlYzem0wIDhIMXYyaDJ2LTJ6bTgtOEg5djJoMlYzek03IDNINXYyaDJWM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJwaWN0dXJlLWFzLXBkZiI+PHBhdGggZD0iTTIwIDJIOGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlY0YzAtMS4xLS45LTItMi0yem0tOC41IDcuNWMwIC44My0uNjcgMS41LTEuNSAxLjVIOXYySDcuNVY3SDEwYy44MyAwIDEuNS42NyAxLjUgMS41djF6bTUgMmMwIC44My0uNjcgMS41LTEuNSAxLjVoLTIuNVY3SDE1Yy44MyAwIDEuNS42NyAxLjUgMS41djN6bTQtM0gxOXYxaDEuNVYxMUgxOXYyaC0xLjVWN2gzdjEuNXpNOSA5LjVoMXYtMUg5djF6TTQgNkgydjE0YzAgMS4xLjkgMiAyIDJoMTR2LTJINFY2em0xMCA1LjVoMXYtM2gtMXYzeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBvcnRyYWl0Ij48cGF0aCBkPSJNMTIgMTIuMjVjMS4yNCAwIDIuMjUtMS4wMSAyLjI1LTIuMjVTMTMuMjQgNy43NSAxMiA3Ljc1IDkuNzUgOC43NiA5Ljc1IDEwczEuMDEgMi4yNSAyLjI1IDIuMjV6bTQuNSA0YzAtMS41LTMtMi4yNS00LjUtMi4yNXMtNC41Ljc1LTQuNSAyLjI1VjE3aDl2LS43NXpNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY1aDE0djE0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJlbW92ZS1yZWQtZXllIj48cGF0aCBkPSJNMTIgNC41QzcgNC41IDIuNzMgNy42MSAxIDEyYzEuNzMgNC4zOSA2IDcuNSAxMSA3LjVzOS4yNy0zLjExIDExLTcuNWMtMS43My00LjM5LTYtNy41LTExLTcuNXpNMTIgMTdjLTIuNzYgMC01LTIuMjQtNS01czIuMjQtNSA1LTUgNSAyLjI0IDUgNS0yLjI0IDUtNSA1em0wLThjLTEuNjYgMC0zIDEuMzQtMyAzczEuMzQgMyAzIDMgMy0xLjM0IDMtMy0xLjM0LTMtMy0zeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJvdGF0ZS05MC1kZWdyZWVzLWNjdyI+PHBhdGggZD0iTTcuMzQgNi40MUwuODYgMTIuOWw2LjQ5IDYuNDggNi40OS02LjQ4LTYuNS02LjQ5ek0zLjY5IDEyLjlsMy42Ni0zLjY2TDExIDEyLjlsLTMuNjYgMy42Ni0zLjY1LTMuNjZ6bTE1LjY3LTYuMjZDMTcuNjEgNC44OCAxNS4zIDQgMTMgNFYuNzZMOC43NiA1IDEzIDkuMjRWNmMxLjc5IDAgMy41OC42OCA0Ljk1IDIuMDUgMi43MyAyLjczIDIuNzMgNy4xNyAwIDkuOUMxNi41OCAxOS4zMiAxNC43OSAyMCAxMyAyMGMtLjk3IDAtMS45NC0uMjEtMi44NC0uNjFsLTEuNDkgMS40OUMxMC4wMiAyMS42MiAxMS41MSAyMiAxMyAyMmMyLjMgMCA0LjYxLS44OCA2LjM2LTIuNjQgMy41Mi0zLjUxIDMuNTItOS4yMSAwLTEyLjcyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJvdGF0ZS1sZWZ0Ij48cGF0aCBkPSJNNy4xMSA4LjUzTDUuNyA3LjExQzQuOCA4LjI3IDQuMjQgOS42MSA0LjA3IDExaDIuMDJjLjE0LS44Ny40OS0xLjcyIDEuMDItMi40N3pNNi4wOSAxM0g0LjA3Yy4xNyAxLjM5LjcyIDIuNzMgMS42MiAzLjg5bDEuNDEtMS40MmMtLjUyLS43NS0uODctMS41OS0xLjAxLTIuNDd6bTEuMDEgNS4zMmMxLjE2LjkgMi41MSAxLjQ0IDMuOSAxLjYxVjE3LjljLS44Ny0uMTUtMS43MS0uNDktMi40Ni0xLjAzTDcuMSAxOC4zMnpNMTMgNC4wN1YxTDguNDUgNS41NSAxMyAxMFY2LjA5YzIuODQuNDggNSAyLjk0IDUgNS45MXMtMi4xNiA1LjQzLTUgNS45MXYyLjAyYzMuOTUtLjQ5IDctMy44NSA3LTcuOTNzLTMuMDUtNy40NC03LTcuOTN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icm90YXRlLXJpZ2h0Ij48cGF0aCBkPSJNMTUuNTUgNS41NUwxMSAxdjMuMDdDNy4wNiA0LjU2IDQgNy45MiA0IDEyczMuMDUgNy40NCA3IDcuOTN2LTIuMDJjLTIuODQtLjQ4LTUtMi45NC01LTUuOTFzMi4xNi01LjQzIDUtNS45MVYxMGw0LjU1LTQuNDV6TTE5LjkzIDExYy0uMTctMS4zOS0uNzItMi43My0xLjYyLTMuODlsLTEuNDIgMS40MmMuNTQuNzUuODggMS42IDEuMDIgMi40N2gyLjAyek0xMyAxNy45djIuMDJjMS4zOS0uMTcgMi43NC0uNzEgMy45LTEuNjFsLTEuNDQtMS40NGMtLjc1LjU0LTEuNTkuODktMi40NiAxLjAzem0zLjg5LTIuNDJsMS40MiAxLjQxYy45LTEuMTYgMS40NS0yLjUgMS42Mi0zLjg5aC0yLjAyYy0uMTQuODctLjQ4IDEuNzItMS4wMiAyLjQ4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InNsaWRlc2hvdyI+PHBhdGggZD0iTTEwIDh2OGw1LTQtNS00em05LTVINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yem0wIDE2SDVWNWgxNHYxNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJzdHJhaWdodGVuIj48cGF0aCBkPSJNMjEgNkgzYy0xLjEgMC0yIC45LTIgMnY4YzAgMS4xLjkgMiAyIDJoMThjMS4xIDAgMi0uOSAyLTJWOGMwLTEuMS0uOS0yLTItMnptMCAxMEgzVjhoMnY0aDJWOGgydjRoMlY4aDJ2NGgyVjhoMnY0aDJWOGgydjh6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic3R5bGUiPjxwYXRoIGQ9Ik0yLjUzIDE5LjY1bDEuMzQuNTZ2LTkuMDNsLTIuNDMgNS44NmMtLjQxIDEuMDIuMDggMi4xOSAxLjA5IDIuNjF6bTE5LjUtMy43TDE3LjA3IDMuOThjLS4zMS0uNzUtMS4wNC0xLjIxLTEuODEtMS4yMy0uMjYgMC0uNTMuMDQtLjc5LjE1TDcuMSA1Ljk1Yy0uNzUuMzEtMS4yMSAxLjAzLTEuMjMgMS44LS4wMS4yNy4wNC41NC4xNS44bDQuOTYgMTEuOTdjLjMxLjc2IDEuMDUgMS4yMiAxLjgzIDEuMjMuMjYgMCAuNTItLjA1Ljc3LS4xNWw3LjM2LTMuMDVjMS4wMi0uNDIgMS41MS0xLjU5IDEuMDktMi42ek03Ljg4IDguNzVjLS41NSAwLTEtLjQ1LTEtMXMuNDUtMSAxLTEgMSAuNDUgMSAxLS40NSAxLTEgMXptLTIgMTFjMCAxLjEuOSAyIDIgMmgxLjQ1bC0zLjQ1LTguMzR2Ni4zNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJzd2l0Y2gtY2FtZXJhIj48cGF0aCBkPSJNMjAgNGgtMy4xN0wxNSAySDlMNy4xNyA0SDRjLTEuMSAwLTIgLjktMiAydjEyYzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uOS0yLTItMnptLTUgMTEuNVYxM0g5djIuNUw1LjUgMTIgOSA4LjVWMTFoNlY4LjVsMy41IDMuNS0zLjUgMy41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InN3aXRjaC12aWRlbyI+PHBhdGggZD0iTTE4IDkuNVY2YzAtLjU1LS40NS0xLTEtMUgzYy0uNTUgMC0xIC40NS0xIDF2MTJjMCAuNTUuNDUgMSAxIDFoMTRjLjU1IDAgMS0uNDUgMS0xdi0zLjVsNCA0di0xM2wtNCA0em0tNSA2VjEzSDd2Mi41TDMuNSAxMiA3IDguNVYxMWg2VjguNWwzLjUgMy41LTMuNSAzLjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idGFnLWZhY2VzIj48cGF0aCBkPSJNMTEuOTkgMkM2LjQ3IDIgMiA2LjQ4IDIgMTJzNC40NyAxMCA5Ljk5IDEwQzE3LjUyIDIyIDIyIDE3LjUyIDIyIDEyUzE3LjUyIDIgMTEuOTkgMnpNMTIgMjBjLTQuNDIgMC04LTMuNTgtOC04czMuNTgtOCA4LTggOCAzLjU4IDggOC0zLjU4IDgtOCA4em0zLjUtOWMuODMgMCAxLjUtLjY3IDEuNS0xLjVTMTYuMzMgOCAxNS41IDggMTQgOC42NyAxNCA5LjVzLjY3IDEuNSAxLjUgMS41em0tNyAwYy44MyAwIDEuNS0uNjcgMS41LTEuNVM5LjMzIDggOC41IDggNyA4LjY3IDcgOS41IDcuNjcgMTEgOC41IDExem0zLjUgNi41YzIuMzMgMCA0LjMxLTEuNDYgNS4xMS0zLjVINi44OWMuOCAyLjA0IDIuNzggMy41IDUuMTEgMy41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InRleHR1cmUiPjxwYXRoIGQ9Ik0xOS41MSAzLjA4TDMuMDggMTkuNTFjLjA5LjM0LjI3LjY1LjUxLjkuMjUuMjQuNTYuNDIuOS41MUwyMC45MyA0LjQ5Yy0uMTktLjY5LS43My0xLjIzLTEuNDItMS40MXpNMTEuODggM0wzIDExLjg4djIuODNMMTQuNzEgM2gtMi44M3pNNSAzYy0xLjEgMC0yIC45LTIgMnYybDQtNEg1em0xNCAxOGMuNTUgMCAxLjA1LS4yMiAxLjQxLS41OS4zNy0uMzYuNTktLjg2LjU5LTEuNDF2LTJsLTQgNGgyem0tOS43MSAwaDIuODNMMjEgMTIuMTJWOS4yOUw5LjI5IDIxeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InRpbWVsYXBzZSI+PHBhdGggZD0iTTE2LjI0IDcuNzZDMTUuMDcgNi41OSAxMy41NCA2IDEyIDZ2NmwtNC4yNCA0LjI0YzIuMzQgMi4zNCA2LjE0IDIuMzQgOC40OSAwIDIuMzQtMi4zNCAyLjM0LTYuMTQtLjAxLTguNDh6TTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTAgMThjLTQuNDIgMC04LTMuNTgtOC04czMuNTgtOCA4LTggOCAzLjU4IDggOC0zLjU4IDgtOCA4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InRpbWVyIj48cGF0aCBkPSJNMTUgMUg5djJoNlYxem0tNCAxM2gyVjhoLTJ2NnptOC4wMy02LjYxbDEuNDItMS40MmMtLjQzLS41MS0uOS0uOTktMS40MS0xLjQxbC0xLjQyIDEuNDJDMTYuMDcgNC43NCAxNC4xMiA0IDEyIDRjLTQuOTcgMC05IDQuMDMtOSA5czQuMDIgOSA5IDkgOS00LjAzIDktOWMwLTIuMTItLjc0LTQuMDctMS45Ny01LjYxek0xMiAyMGMtMy44NyAwLTctMy4xMy03LTdzMy4xMy03IDctNyA3IDMuMTMgNyA3LTMuMTMgNy03IDd6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idGltZXItMTAiPjxwYXRoIGQ9Ik0wIDcuNzJWOS40bDMtMVYxOGgyVjZoLS4yNUwwIDcuNzJ6bTIzLjc4IDYuNjVjLS4xNC0uMjgtLjM1LS41My0uNjMtLjc0LS4yOC0uMjEtLjYxLS4zOS0xLjAxLS41M3MtLjg1LS4yNy0xLjM1LS4zOGMtLjM1LS4wNy0uNjQtLjE1LS44Ny0uMjMtLjIzLS4wOC0uNDEtLjE2LS41NS0uMjUtLjE0LS4wOS0uMjMtLjE5LS4yOC0uMy0uMDUtLjExLS4wOC0uMjQtLjA4LS4zOSAwLS4xNC4wMy0uMjguMDktLjQxLjA2LS4xMy4xNS0uMjUuMjctLjM0LjEyLS4xLjI3LS4xOC40NS0uMjRzLjQtLjA5LjY0LS4wOWMuMjUgMCAuNDcuMDQuNjYuMTEuMTkuMDcuMzUuMTcuNDguMjkuMTMuMTIuMjIuMjYuMjkuNDIuMDYuMTYuMS4zMi4xLjQ5aDEuOTVjMC0uMzktLjA4LS43NS0uMjQtMS4wOS0uMTYtLjM0LS4zOS0uNjMtLjY5LS44OC0uMy0uMjUtLjY2LS40NC0xLjA5LS41OUMyMS40OSA5LjA3IDIxIDkgMjAuNDYgOWMtLjUxIDAtLjk4LjA3LTEuMzkuMjEtLjQxLjE0LS43Ny4zMy0xLjA2LjU3LS4yOS4yNC0uNTEuNTItLjY3Ljg0LS4xNi4zMi0uMjMuNjUtLjIzIDEuMDFzLjA4LjY5LjIzLjk2Yy4xNS4yOC4zNi41Mi42NC43My4yNy4yMS42LjM4Ljk4LjUzLjM4LjE0LjgxLjI2IDEuMjcuMzYuMzkuMDguNzEuMTcuOTUuMjZzLjQzLjE5LjU3LjI5Yy4xMy4xLjIyLjIyLjI3LjM0LjA1LjEyLjA3LjI1LjA3LjM5IDAgLjMyLS4xMy41Ny0uNC43Ny0uMjcuMi0uNjYuMjktMS4xNy4yOS0uMjIgMC0uNDMtLjAyLS42NC0uMDgtLjIxLS4wNS0uNC0uMTMtLjU2LS4yNC0uMTctLjExLS4zLS4yNi0uNDEtLjQ0LS4xMS0uMTgtLjE3LS40MS0uMTgtLjY3aC0xLjg5YzAgLjM2LjA4LjcxLjI0IDEuMDUuMTYuMzQuMzkuNjUuNy45My4zMS4yNy42OS40OSAxLjE1LjY2LjQ2LjE3Ljk4LjI1IDEuNTguMjUuNTMgMCAxLjAxLS4wNiAxLjQ0LS4xOS40My0uMTMuOC0uMzEgMS4xMS0uNTQuMzEtLjIzLjU0LS41MS43MS0uODMuMTctLjMyLjI1LS42Ny4yNS0xLjA2LS4wMi0uNC0uMDktLjc0LS4yNC0xLjAyem0tOS45Ni03LjMyYy0uMzQtLjQtLjc1LS43LTEuMjMtLjg4LS40Ny0uMTgtMS4wMS0uMjctMS41OS0uMjctLjU4IDAtMS4xMS4wOS0xLjU5LjI3LS40OC4xOC0uODkuNDctMS4yMy44OC0uMzQuNDEtLjYuOTMtLjc5IDEuNTktLjE4LjY1LS4yOCAxLjQ1LS4yOCAyLjM5djEuOTJjMCAuOTQuMDkgMS43NC4yOCAyLjM5LjE5LjY2LjQ1IDEuMTkuOCAxLjYuMzQuNDEuNzUuNzEgMS4yMy44OS40OC4xOCAxLjAxLjI4IDEuNTkuMjguNTkgMCAxLjEyLS4wOSAxLjU5LS4yOC40OC0uMTguODgtLjQ4IDEuMjItLjg5LjM0LS40MS42LS45NC43OC0xLjYuMTgtLjY1LjI4LTEuNDUuMjgtMi4zOXYtMS45MmMwLS45NC0uMDktMS43NC0uMjgtMi4zOS0uMTgtLjY2LS40NC0xLjE5LS43OC0xLjU5em0tLjkyIDYuMTdjMCAuNi0uMDQgMS4xMS0uMTIgMS41My0uMDguNDItLjIuNzYtLjM2IDEuMDItLjE2LjI2LS4zNi40NS0uNTkuNTctLjIzLjEyLS41MS4xOC0uODIuMTgtLjMgMC0uNTgtLjA2LS44Mi0uMThzLS40NC0uMzEtLjYtLjU3Yy0uMTYtLjI2LS4yOS0uNi0uMzgtMS4wMi0uMDktLjQyLS4xMy0uOTMtLjEzLTEuNTN2LTIuNWMwLS42LjA0LTEuMTEuMTMtMS41Mi4wOS0uNDEuMjEtLjc0LjM4LTEgLjE2LS4yNS4zNi0uNDMuNi0uNTUuMjQtLjExLjUxLS4xNy44MS0uMTcuMzEgMCAuNTguMDYuODEuMTcuMjQuMTEuNDQuMjkuNi41NS4xNi4yNS4yOS41OC4zNy45OS4wOC40MS4xMy45Mi4xMyAxLjUydjIuNTF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idGltZXItMyI+PHBhdGggZD0iTTExLjYxIDEyLjk3Yy0uMTYtLjI0LS4zNi0uNDYtLjYyLS42NS0uMjUtLjE5LS41Ni0uMzUtLjkzLS40OC4zLS4xNC41Ny0uMy44LS41LjIzLS4yLjQyLS40MS41Ny0uNjQuMTUtLjIzLjI3LS40Ni4zNC0uNzEuMDgtLjI0LjExLS40OS4xMS0uNzMgMC0uNTUtLjA5LTEuMDQtLjI4LTEuNDYtLjE4LS40Mi0uNDQtLjc3LS43OC0xLjA2LS4zMy0uMjgtLjczLS41LTEuMi0uNjQtLjQ1LS4xMy0uOTctLjItMS41My0uMi0uNTUgMC0xLjA2LjA4LTEuNTIuMjQtLjQ3LjE3LS44Ny40LTEuMi42OS0uMzMuMjktLjYuNjMtLjc4IDEuMDMtLjIuMzktLjI5LjgzLS4yOSAxLjI5aDEuOThjMC0uMjYuMDUtLjQ5LjE0LS42OS4wOS0uMi4yMi0uMzguMzgtLjUyLjE3LS4xNC4zNi0uMjUuNTgtLjMzLjIyLS4wOC40Ni0uMTIuNzMtLjEyLjYxIDAgMS4wNi4xNiAxLjM2LjQ3LjMuMzEuNDQuNzUuNDQgMS4zMiAwIC4yNy0uMDQuNTItLjEyLjc0LS4wOC4yMi0uMjEuNDEtLjM4LjU3LS4xNy4xNi0uMzguMjgtLjYzLjM3LS4yNS4wOS0uNTUuMTMtLjg5LjEzSDYuNzJ2MS41N0g3LjljLjM0IDAgLjY0LjA0LjkxLjExLjI3LjA4LjUuMTkuNjkuMzUuMTkuMTYuMzQuMzYuNDQuNjEuMS4yNC4xNi41NC4xNi44NyAwIC42Mi0uMTggMS4wOS0uNTMgMS40Mi0uMzUuMzMtLjg0LjQ5LTEuNDUuNDktLjI5IDAtLjU2LS4wNC0uOC0uMTMtLjI0LS4wOC0uNDQtLjItLjYxLS4zNi0uMTctLjE2LS4zLS4zNC0uMzktLjU2LS4wOS0uMjItLjE0LS40Ni0uMTQtLjcySDQuMTljMCAuNTUuMTEgMS4wMy4zMiAxLjQ1LjIxLjQyLjUuNzcuODYgMS4wNXMuNzcuNDkgMS4yNC42My45Ni4yMSAxLjQ4LjIxYy41NyAwIDEuMDktLjA4IDEuNTgtLjIzLjQ5LS4xNS45MS0uMzggMS4yNi0uNjguMzYtLjMuNjQtLjY2Ljg0LTEuMS4yLS40My4zLS45My4zLTEuNDggMC0uMjktLjA0LS41OC0uMTEtLjg2LS4wOC0uMjUtLjE5LS41MS0uMzUtLjc2em05LjI2IDEuNGMtLjE0LS4yOC0uMzUtLjUzLS42My0uNzQtLjI4LS4yMS0uNjEtLjM5LTEuMDEtLjUzcy0uODUtLjI3LTEuMzUtLjM4Yy0uMzUtLjA3LS42NC0uMTUtLjg3LS4yMy0uMjMtLjA4LS40MS0uMTYtLjU1LS4yNS0uMTQtLjA5LS4yMy0uMTktLjI4LS4zLS4wNS0uMTEtLjA4LS4yNC0uMDgtLjM5cy4wMy0uMjguMDktLjQxYy4wNi0uMTMuMTUtLjI1LjI3LS4zNC4xMi0uMS4yNy0uMTguNDUtLjI0cy40LS4wOS42NC0uMDljLjI1IDAgLjQ3LjA0LjY2LjExLjE5LjA3LjM1LjE3LjQ4LjI5LjEzLjEyLjIyLjI2LjI5LjQyLjA2LjE2LjEuMzIuMS40OWgxLjk1YzAtLjM5LS4wOC0uNzUtLjI0LTEuMDktLjE2LS4zNC0uMzktLjYzLS42OS0uODgtLjMtLjI1LS42Ni0uNDQtMS4wOS0uNTktLjQzLS4xNS0uOTItLjIyLTEuNDYtLjIyLS41MSAwLS45OC4wNy0xLjM5LjIxLS40MS4xNC0uNzcuMzMtMS4wNi41Ny0uMjkuMjQtLjUxLjUyLS42Ny44NC0uMTYuMzItLjIzLjY1LS4yMyAxLjAxcy4wOC42OC4yMy45NmMuMTUuMjguMzcuNTIuNjQuNzMuMjcuMjEuNi4zOC45OC41My4zOC4xNC44MS4yNiAxLjI3LjM2LjM5LjA4LjcxLjE3Ljk1LjI2cy40My4xOS41Ny4yOWMuMTMuMS4yMi4yMi4yNy4zNC4wNS4xMi4wNy4yNS4wNy4zOSAwIC4zMi0uMTMuNTctLjQuNzctLjI3LjItLjY2LjI5LTEuMTcuMjktLjIyIDAtLjQzLS4wMi0uNjQtLjA4LS4yMS0uMDUtLjQtLjEzLS41Ni0uMjQtLjE3LS4xMS0uMy0uMjYtLjQxLS40NC0uMTEtLjE4LS4xNy0uNDEtLjE4LS42N2gtMS44OWMwIC4zNi4wOC43MS4yNCAxLjA1LjE2LjM0LjM5LjY1LjcuOTMuMzEuMjcuNjkuNDkgMS4xNS42Ni40Ni4xNy45OC4yNSAxLjU4LjI1LjUzIDAgMS4wMS0uMDYgMS40NC0uMTkuNDMtLjEzLjgtLjMxIDEuMTEtLjU0LjMxLS4yMy41NC0uNTEuNzEtLjgzLjE3LS4zMi4yNS0uNjcuMjUtMS4wNi0uMDItLjQtLjA5LS43NC0uMjQtMS4wMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJ0aW1lci1vZmYiPjxwYXRoIGQ9Ik0xOS4wNCA0LjU1bC0xLjQyIDEuNDJDMTYuMDcgNC43NCAxNC4xMiA0IDEyIDRjLTEuODMgMC0zLjUzLjU1LTQuOTUgMS40OGwxLjQ2IDEuNDZDOS41MyA2LjM1IDEwLjczIDYgMTIgNmMzLjg3IDAgNyAzLjEzIDcgNyAwIDEuMjctLjM1IDIuNDctLjk0IDMuNDlsMS40NSAxLjQ1QzIwLjQ1IDE2LjUzIDIxIDE0LjgzIDIxIDEzYzAtMi4xMi0uNzQtNC4wNy0xLjk3LTUuNjFsMS40Mi0xLjQyLTEuNDEtMS40MnpNMTUgMUg5djJoNlYxem0tNCA4LjQ0bDIgMlY4aC0ydjEuNDR6TTMuMDIgNEwxLjc1IDUuMjcgNC41IDguMDNDMy41NSA5LjQ1IDMgMTEuMTYgMyAxM2MwIDQuOTcgNC4wMiA5IDkgOSAxLjg0IDAgMy41NS0uNTUgNC45OC0xLjVsMi41IDIuNSAxLjI3LTEuMjctNy43MS03LjcxTDMuMDIgNHpNMTIgMjBjLTMuODcgMC03LTMuMTMtNy03IDAtMS4yOC4zNS0yLjQ4Ljk1LTMuNTJsOS41NiA5LjU2Yy0xLjAzLjYxLTIuMjMuOTYtMy41MS45NnoiPjwvcGF0aD48L2c+CjxnIGlkPSJ0b25hbGl0eSI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bS0xIDE3LjkzYy0zLjk0LS40OS03LTMuODUtNy03LjkzczMuMDUtNy40NCA3LTcuOTN2MTUuODZ6bTItMTUuODZjMS4wMy4xMyAyIC40NSAyLjg3LjkzSDEzdi0uOTN6TTEzIDdoNS4yNGMuMjUuMzEuNDguNjUuNjggMUgxM1Y3em0wIDNoNi43NGMuMDguMzMuMTUuNjYuMTkgMUgxM3YtMXptMCA5LjkzVjE5aDIuODdjLS44Ny40OC0xLjg0LjgtMi44Ny45M3pNMTguMjQgMTdIMTN2LTFoNS45MmMtLjIuMzUtLjQzLjY5LS42OCAxem0xLjUtM0gxM3YtMWg2LjkzYy0uMDQuMzQtLjExLjY3LS4xOSAxeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InRyYW5zZm9ybSI+PHBhdGggZD0iTTIyIDE4di0ySDhWNGgyTDcgMSA0IDRoMnYySDJ2Mmg0djhjMCAxLjEuOSAyIDIgMmg4djJoLTJsMyAzIDMtM2gtMnYtMmg0ek0xMCA4aDZ2NmgyVjhjMC0xLjEtLjktMi0yLTJoLTZ2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJ0dW5lIj48cGF0aCBkPSJNMyAxN3YyaDZ2LTJIM3pNMyA1djJoMTBWNUgzem0xMCAxNnYtMmg4di0yaC04di0yaC0ydjZoMnpNNyA5djJIM3YyaDR2MmgyVjlIN3ptMTQgNHYtMkgxMXYyaDEwem0tNi00aDJWN2g0VjVoLTRWM2gtMnY2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InZpZXctY29tZnkiPjxwYXRoIGQ9Ik0zIDloNFY1SDN2NHptMCA1aDR2LTRIM3Y0em01IDBoNHYtNEg4djR6bTUgMGg0di00aC00djR6TTggOWg0VjVIOHY0em01LTR2NGg0VjVoLTR6bTUgOWg0di00aC00djR6TTMgMTloNHYtNEgzdjR6bTUgMGg0di00SDh2NHptNSAwaDR2LTRoLTR2NHptNSAwaDR2LTRoLTR2NHptMC0xNHY0aDRWNWgtNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJ2aWV3LWNvbXBhY3QiPjxwYXRoIGQ9Ik0zIDE5aDZ2LTdIM3Y3em03IDBoMTJ2LTdIMTB2N3pNMyA1djZoMTlWNUgzeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InZpZ25ldHRlIj48cGF0aCBkPSJNMjEgM0gzYy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE4YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS05IDE1Yy00LjQyIDAtOC0yLjY5LTgtNnMzLjU4LTYgOC02IDggMi42OSA4IDYtMy41OCA2LTggNnoiPjwvcGF0aD48L2c+CjxnIGlkPSJ3Yi1hdXRvIj48cGF0aCBkPSJNNi44NSAxMi42NWgyLjNMOCA5bC0xLjE1IDMuNjV6TTIyIDdsLTEuMiA2LjI5TDE5LjMgN2gtMS42bC0xLjQ5IDYuMjlMMTUgN2gtLjc2QzEyLjc3IDUuMTcgMTAuNTMgNCA4IDRjLTQuNDIgMC04IDMuNTgtOCA4czMuNTggOCA4IDhjMy4xMyAwIDUuODQtMS44MSA3LjE1LTQuNDNsLjEuNDNIMTdsMS41LTYuMUwyMCAxNmgxLjc1bDIuMDUtOUgyMnptLTExLjcgOWwtLjctMkg2LjRsLS43IDJIMy44TDcgN2gybDMuMiA5aC0xLjl6Ij48L3BhdGg+PC9nPgo8ZyBpZD0id2ItY2xvdWR5Ij48cGF0aCBkPSJNMTkuMzYgMTAuMDRDMTguNjcgNi41OSAxNS42NCA0IDEyIDQgOS4xMSA0IDYuNiA1LjY0IDUuMzUgOC4wNCAyLjM0IDguMzYgMCAxMC45MSAwIDE0YzAgMy4zMSAyLjY5IDYgNiA2aDEzYzIuNzYgMCA1LTIuMjQgNS01IDAtMi42NC0yLjA1LTQuNzgtNC42NC00Ljk2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9IndiLWluY2FuZGVzY2VudCI+PHBhdGggZD0iTTMuNTUgMTguNTRsMS40MSAxLjQxIDEuNzktMS44LTEuNDEtMS40MS0xLjc5IDEuOHpNMTEgMjIuNDVoMlYxOS41aC0ydjIuOTV6TTQgMTAuNUgxdjJoM3YtMnptMTEtNC4xOVYxLjVIOXY0LjgxQzcuMjEgNy4zNSA2IDkuMjggNiAxMS41YzAgMy4zMSAyLjY5IDYgNiA2czYtMi42OSA2LTZjMC0yLjIyLTEuMjEtNC4xNS0zLTUuMTl6bTUgNC4xOXYyaDN2LTJoLTN6bS0yLjc2IDcuNjZsMS43OSAxLjggMS40MS0xLjQxLTEuOC0xLjc5LTEuNCAxLjR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0id2ItaXJpZGVzY2VudCI+PHBhdGggZD0iTTUgMTQuNWgxNHYtNkg1djZ6TTExIC41NVYzLjVoMlYuNTVoLTJ6bTguMDQgMi41bC0xLjc5IDEuNzkgMS40MSAxLjQxIDEuOC0xLjc5LTEuNDItMS40MXpNMTMgMjIuNDVWMTkuNWgtMnYyLjk1aDJ6bTcuNDUtMy45MWwtMS44LTEuNzktMS40MSAxLjQxIDEuNzkgMS44IDEuNDItMS40MnpNMy41NSA0LjQ2bDEuNzkgMS43OSAxLjQxLTEuNDEtMS43OS0xLjc5LTEuNDEgMS40MXptMS40MSAxNS40OWwxLjc5LTEuOC0xLjQxLTEuNDEtMS43OSAxLjc5IDEuNDEgMS40MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJ3Yi1zdW5ueSI+PHBhdGggZD0iTTYuNzYgNC44NGwtMS44LTEuNzktMS40MSAxLjQxIDEuNzkgMS43OSAxLjQyLTEuNDF6TTQgMTAuNUgxdjJoM3YtMnptOS05Ljk1aC0yVjMuNWgyVi41NXptNy40NSAzLjkxbC0xLjQxLTEuNDEtMS43OSAxLjc5IDEuNDEgMS40MSAxLjc5LTEuNzl6bS0zLjIxIDEzLjdsMS43OSAxLjggMS40MS0xLjQxLTEuOC0xLjc5LTEuNCAxLjR6TTIwIDEwLjV2Mmgzdi0yaC0zem0tOC01Yy0zLjMxIDAtNiAyLjY5LTYgNnMyLjY5IDYgNiA2IDYtMi42OSA2LTYtMi42OS02LTYtNnptLTEgMTYuOTVoMlYxOS41aC0ydjIuOTV6bS03LjQ1LTMuOTFsMS40MSAxLjQxIDEuNzktMS44LTEuNDEtMS40MS0xLjc5IDEuOHoiPjwvcGF0aD48L2c+CjwvZGVmcz48L3N2Zz4KPC9pcm9uLWljb25zZXQtc3ZnPmA7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChwYmUuY29udGVudCk7dmFyIGRiZT1RYDxpcm9uLWljb25zZXQtc3ZnIG5hbWU9Imljb25zIiBzaXplPSIyNCI+Cjxzdmc+PGRlZnM+CjxnIGlkPSIzZC1yb3RhdGlvbiI+PHBhdGggZD0iTTcuNTIgMjEuNDhDNC4yNSAxOS45NCAxLjkxIDE2Ljc2IDEuNTUgMTNILjA1Qy41NiAxOS4xNiA1LjcxIDI0IDEyIDI0bC42Ni0uMDMtMy44MS0zLjgxLTEuMzMgMS4zMnptLjg5LTYuNTJjLS4xOSAwLS4zNy0uMDMtLjUyLS4wOC0uMTYtLjA2LS4yOS0uMTMtLjQtLjI0LS4xMS0uMS0uMi0uMjItLjI2LS4zNy0uMDYtLjE0LS4wOS0uMy0uMDktLjQ3aC0xLjNjMCAuMzYuMDcuNjguMjEuOTUuMTQuMjcuMzMuNS41Ni42OS4yNC4xOC41MS4zMi44Mi40MS4zLjEuNjIuMTUuOTYuMTUuMzcgMCAuNzItLjA1IDEuMDMtLjE1LjMyLS4xLjYtLjI1LjgzLS40NHMuNDItLjQzLjU1LS43MmMuMTMtLjI5LjItLjYxLjItLjk3IDAtLjE5LS4wMi0uMzgtLjA3LS41Ni0uMDUtLjE4LS4xMi0uMzUtLjIzLS41MS0uMS0uMTYtLjI0LS4zLS40LS40My0uMTctLjEzLS4zNy0uMjMtLjYxLS4zMS4yLS4wOS4zNy0uMi41Mi0uMzMuMTUtLjEzLjI3LS4yNy4zNy0uNDIuMS0uMTUuMTctLjMuMjItLjQ2LjA1LS4xNi4wNy0uMzIuMDctLjQ4IDAtLjM2LS4wNi0uNjgtLjE4LS45Ni0uMTItLjI4LS4yOS0uNTEtLjUxLS42OS0uMi0uMTktLjQ3LS4zMy0uNzctLjQzQzkuMSA4LjA1IDguNzYgOCA4LjM5IDhjLS4zNiAwLS42OS4wNS0xIC4xNi0uMy4xMS0uNTcuMjYtLjc5LjQ1LS4yMS4xOS0uMzguNDEtLjUxLjY3LS4xMi4yNi0uMTguNTQtLjE4Ljg1aDEuM2MwLS4xNy4wMy0uMzIuMDktLjQ1cy4xNC0uMjUuMjUtLjM0Yy4xMS0uMDkuMjMtLjE3LjM4LS4yMi4xNS0uMDUuMy0uMDguNDgtLjA4LjQgMCAuNy4xLjg5LjMxLjE5LjIuMjkuNDkuMjkuODYgMCAuMTgtLjAzLjM0LS4wOC40OS0uMDUuMTUtLjE0LjI3LS4yNS4zNy0uMTEuMS0uMjUuMTgtLjQxLjI0LS4xNi4wNi0uMzYuMDktLjU4LjA5SDcuNXYxLjAzaC43N2MuMjIgMCAuNDIuMDIuNi4wN3MuMzMuMTMuNDUuMjNjLjEyLjExLjIyLjI0LjI5LjQuMDcuMTYuMS4zNS4xLjU3IDAgLjQxLS4xMi43Mi0uMzUuOTMtLjIzLjIzLS41NS4zMy0uOTUuMzN6bTguNTUtNS45MmMtLjMyLS4zMy0uNy0uNTktMS4xNC0uNzctLjQzLS4xOC0uOTItLjI3LTEuNDYtLjI3SDEydjhoMi4zYy41NSAwIDEuMDYtLjA5IDEuNTEtLjI3LjQ1LS4xOC44NC0uNDMgMS4xNi0uNzYuMzItLjMzLjU3LS43My43NC0xLjE5LjE3LS40Ny4yNi0uOTkuMjYtMS41N3YtLjRjMC0uNTgtLjA5LTEuMS0uMjYtMS41Ny0uMTgtLjQ3LS40My0uODctLjc1LTEuMnptLS4zOSAzLjE2YzAgLjQyLS4wNS43OS0uMTQgMS4xMy0uMS4zMy0uMjQuNjItLjQzLjg1LS4xOS4yMy0uNDMuNDEtLjcxLjUzLS4yOS4xMi0uNjIuMTgtLjk5LjE4aC0uOTFWOS4xMmguOTdjLjcyIDAgMS4yNy4yMyAxLjY0LjY5LjM4LjQ2LjU3IDEuMTIuNTcgMS45OXYuNHpNMTIgMGwtLjY2LjAzIDMuODEgMy44MSAxLjMzLTEuMzNjMy4yNyAxLjU1IDUuNjEgNC43MiA1Ljk2IDguNDhoMS41QzIzLjQ0IDQuODQgMTguMjkgMCAxMiAweiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFjY2Vzc2liaWxpdHkiPjxwYXRoIGQ9Ik0xMiAyYzEuMSAwIDIgLjkgMiAycy0uOSAyLTIgMi0yLS45LTItMiAuOS0yIDItMnptOSA3aC02djEzaC0ydi02aC0ydjZIOVY5SDNWN2gxOHYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFjY2Vzc2libGUiPjxjaXJjbGUgY3g9IjEyIiBjeT0iNCIgcj0iMiI+PC9jaXJjbGU+PHBhdGggZD0iTTE5IDEzdi0yYy0xLjU0LjAyLTMuMDktLjc1LTQuMDctMS44M2wtMS4yOS0xLjQzYy0uMTctLjE5LS4zOC0uMzQtLjYxLS40NS0uMDEgMC0uMDEtLjAxLS4wMi0uMDFIMTNjLS4zNS0uMi0uNzUtLjMtMS4xOS0uMjZDMTAuNzYgNy4xMSAxMCA4LjA0IDEwIDkuMDlWMTVjMCAxLjEuOSAyIDIgMmg1djVoMnYtNS41YzAtMS4xLS45LTItMi0yaC0zdi0zLjQ1YzEuMjkgMS4wNyAzLjI1IDEuOTQgNSAxLjk1em0tNi4xNyA1Yy0uNDEgMS4xNi0xLjUyIDItMi44MyAyLTEuNjYgMC0zLTEuMzQtMy0zIDAtMS4zMS44NC0yLjQxIDItMi44M1YxMi4xYy0yLjI4LjQ2LTQgMi40OC00IDQuOSAwIDIuNzYgMi4yNCA1IDUgNSAyLjQyIDAgNC40NC0xLjcyIDQuOS00aC0yLjA3eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFjY291bnQtYmFsYW5jZSI+PHBhdGggZD0iTTQgMTB2N2gzdi03SDR6bTYgMHY3aDN2LTdoLTN6TTIgMjJoMTl2LTNIMnYzem0xNC0xMnY3aDN2LTdoLTN6bS00LjUtOUwyIDZ2MmgxOVY2bC05LjUtNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJhY2NvdW50LWJhbGFuY2Utd2FsbGV0Ij48cGF0aCBkPSJNMjEgMTh2MWMwIDEuMS0uOSAyLTIgMkg1Yy0xLjExIDAtMi0uOS0yLTJWNWMwLTEuMS44OS0yIDItMmgxNGMxLjEgMCAyIC45IDIgMnYxaC05Yy0xLjExIDAtMiAuOS0yIDJ2OGMwIDEuMS44OSAyIDIgMmg5em0tOS0yaDEwVjhIMTJ2OHptNC0yLjVjLS44MyAwLTEuNS0uNjctMS41LTEuNXMuNjctMS41IDEuNS0xLjUgMS41LjY3IDEuNSAxLjUtLjY3IDEuNS0xLjUgMS41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFjY291bnQtYm94Ij48cGF0aCBkPSJNMyA1djE0YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJINWMtMS4xMSAwLTIgLjktMiAyem0xMiA0YzAgMS42Ni0xLjM0IDMtMyAzcy0zLTEuMzQtMy0zIDEuMzQtMyAzLTMgMyAxLjM0IDMgM3ptLTkgOGMwLTIgNC0zLjEgNi0zLjFzNiAxLjEgNiAzLjF2MUg2di0xeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFjY291bnQtY2lyY2xlIj48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAzYzEuNjYgMCAzIDEuMzQgMyAzcy0xLjM0IDMtMyAzLTMtMS4zNC0zLTMgMS4zNC0zIDMtM3ptMCAxNC4yYy0yLjUgMC00LjcxLTEuMjgtNi0zLjIyLjAzLTEuOTkgNC0zLjA4IDYtMy4wOCAxLjk5IDAgNS45NyAxLjA5IDYgMy4wOC0xLjI5IDEuOTQtMy41IDMuMjItNiAzLjIyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFkZCI+PHBhdGggZD0iTTE5IDEzaC02djZoLTJ2LTZINXYtMmg2VjVoMnY2aDZ2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJhZGQtYWxlcnQiPjxwYXRoIGQ9Ik0xMC4wMSAyMS4wMWMwIDEuMS44OSAxLjk5IDEuOTkgMS45OXMxLjk5LS44OSAxLjk5LTEuOTloLTMuOTh6bTguODctNC4xOVYxMWMwLTMuMjUtMi4yNS01Ljk3LTUuMjktNi42OXYtLjcyQzEzLjU5IDIuNzEgMTIuODggMiAxMiAycy0xLjU5LjcxLTEuNTkgMS41OXYuNzJDNy4zNyA1LjAzIDUuMTIgNy43NSA1LjEyIDExdjUuODJMMyAxOC45NFYyMGgxOHYtMS4wNmwtMi4xMi0yLjEyek0xNiAxMy4wMWgtM3YzaC0ydi0zSDhWMTFoM1Y4aDJ2M2gzdjIuMDF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYWRkLWJveCI+PHBhdGggZD0iTTE5IDNINWMtMS4xMSAwLTIgLjktMiAydjE0YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS0yIDEwaC00djRoLTJ2LTRIN3YtMmg0VjdoMnY0aDR2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJhZGQtY2lyY2xlIj48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptNSAxMWgtNHY0aC0ydi00SDd2LTJoNFY3aDJ2NGg0djJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYWRkLWNpcmNsZS1vdXRsaW5lIj48cGF0aCBkPSJNMTMgN2gtMnY0SDd2Mmg0djRoMnYtNGg0di0yaC00Vjd6bS0xLTVDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTAgMThjLTQuNDEgMC04LTMuNTktOC04czMuNTktOCA4LTggOCAzLjU5IDggOC0zLjU5IDgtOCA4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFkZC1zaG9wcGluZy1jYXJ0Ij48cGF0aCBkPSJNMTEgOWgyVjZoM1Y0aC0zVjFoLTJ2M0g4djJoM3Yzem0tNCA5Yy0xLjEgMC0xLjk5LjktMS45OSAyUzUuOSAyMiA3IDIyczItLjkgMi0yLS45LTItMi0yem0xMCAwYy0xLjEgMC0xLjk5LjktMS45OSAycy44OSAyIDEuOTkgMiAyLS45IDItMi0uOS0yLTItMnptLTkuODMtMy4yNWwuMDMtLjEyLjktMS42M2g3LjQ1Yy43NSAwIDEuNDEtLjQxIDEuNzUtMS4wM2wzLjg2LTcuMDFMMTkuNDIgNGgtLjAxbC0xLjEgMi0yLjc2IDVIOC41M2wtLjEzLS4yN0w2LjE2IDZsLS45NS0yLS45NC0ySDF2MmgybDMuNiA3LjU5LTEuMzUgMi40NWMtLjE2LjI4LS4yNS42MS0uMjUuOTYgMCAxLjEuOSAyIDIgMmgxMnYtMkg3LjQyYy0uMTMgMC0uMjUtLjExLS4yNS0uMjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYWxhcm0iPjxwYXRoIGQ9Ik0yMiA1LjcybC00LjYtMy44Ni0xLjI5IDEuNTMgNC42IDMuODZMMjIgNS43MnpNNy44OCAzLjM5TDYuNiAxLjg2IDIgNS43MWwxLjI5IDEuNTMgNC41OS0zLjg1ek0xMi41IDhIMTF2Nmw0Ljc1IDIuODUuNzUtMS4yMy00LTIuMzdWOHpNMTIgNGMtNC45NyAwLTkgNC4wMy05IDlzNC4wMiA5IDkgOWM0Ljk3IDAgOS00LjAzIDktOXMtNC4wMy05LTktOXptMCAxNmMtMy44NyAwLTctMy4xMy03LTdzMy4xMy03IDctNyA3IDMuMTMgNyA3LTMuMTMgNy03IDd6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYWxhcm0tYWRkIj48cGF0aCBkPSJNNy44OCAzLjM5TDYuNiAxLjg2IDIgNS43MWwxLjI5IDEuNTMgNC41OS0zLjg1ek0yMiA1LjcybC00LjYtMy44Ni0xLjI5IDEuNTMgNC42IDMuODZMMjIgNS43MnpNMTIgNGMtNC45NyAwLTkgNC4wMy05IDlzNC4wMiA5IDkgOWM0Ljk3IDAgOS00LjAzIDktOXMtNC4wMy05LTktOXptMCAxNmMtMy44NyAwLTctMy4xMy03LTdzMy4xMy03IDctNyA3IDMuMTMgNyA3LTMuMTMgNy03IDd6bTEtMTFoLTJ2M0g4djJoM3YzaDJ2LTNoM3YtMmgtM1Y5eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFsYXJtLW9mZiI+PHBhdGggZD0iTTEyIDZjMy44NyAwIDcgMy4xMyA3IDcgMCAuODQtLjE2IDEuNjUtLjQzIDIuNGwxLjUyIDEuNTJjLjU4LTEuMTkuOTEtMi41MS45MS0zLjkyIDAtNC45Ny00LjAzLTktOS05LTEuNDEgMC0yLjczLjMzLTMuOTIuOTFMOS42IDYuNDNDMTAuMzUgNi4xNiAxMS4xNiA2IDEyIDZ6bTEwLS4yOGwtNC42LTMuODYtMS4yOSAxLjUzIDQuNiAzLjg2TDIyIDUuNzJ6TTIuOTIgMi4yOUwxLjY1IDMuNTcgMi45OCA0LjlsLTEuMTEuOTMgMS40MiAxLjQyIDEuMTEtLjk0LjguOEMzLjgzIDguNjkgMyAxMC43NSAzIDEzYzAgNC45NyA0LjAyIDkgOSA5IDIuMjUgMCA0LjMxLS44MyA1Ljg5LTIuMmwyLjIgMi4yIDEuMjctMS4yN0wzLjg5IDMuMjdsLS45Ny0uOTh6bTEzLjU1IDE2LjFDMTUuMjYgMTkuMzkgMTMuNyAyMCAxMiAyMGMtMy44NyAwLTctMy4xMy03LTcgMC0xLjcuNjEtMy4yNiAxLjYxLTQuNDdsOS44NiA5Ljg2ek04LjAyIDMuMjhMNi42IDEuODZsLS44Ni43MSAxLjQyIDEuNDIuODYtLjcxeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFsYXJtLW9uIj48cGF0aCBkPSJNMjIgNS43MmwtNC42LTMuODYtMS4yOSAxLjUzIDQuNiAzLjg2TDIyIDUuNzJ6TTcuODggMy4zOUw2LjYgMS44NiAyIDUuNzFsMS4yOSAxLjUzIDQuNTktMy44NXpNMTIgNGMtNC45NyAwLTkgNC4wMy05IDlzNC4wMiA5IDkgOWM0Ljk3IDAgOS00LjAzIDktOXMtNC4wMy05LTktOXptMCAxNmMtMy44NyAwLTctMy4xMy03LTdzMy4xMy03IDctNyA3IDMuMTMgNyA3LTMuMTMgNy03IDd6bS0xLjQ2LTUuNDdMOC40MSAxMi40bC0xLjA2IDEuMDYgMy4xOCAzLjE4IDYtNi0xLjA2LTEuMDYtNC45MyA0Ljk1eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFsbC1vdXQiPjxwYXRoIGQ9Ik0xNi4yMSA0LjE2bDQgNHYtNHptNCAxMmwtNCA0aDR6bS0xMiA0bC00LTR2NHptLTQtMTJsNC00aC00em0xMi45NS0uOTVjLTIuNzMtMi43My03LjE3LTIuNzMtOS45IDBzLTIuNzMgNy4xNyAwIDkuOSA3LjE3IDIuNzMgOS45IDAgMi43My03LjE2IDAtOS45em0tMS4xIDguOGMtMi4xMyAyLjEzLTUuNTcgMi4xMy03LjcgMHMtMi4xMy01LjU3IDAtNy43IDUuNTctMi4xMyA3LjcgMCAyLjEzIDUuNTcgMCA3Ljd6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYW5kcm9pZCI+PHBhdGggZD0iTTYgMThjMCAuNTUuNDUgMSAxIDFoMXYzLjVjMCAuODMuNjcgMS41IDEuNSAxLjVzMS41LS42NyAxLjUtMS41VjE5aDJ2My41YzAgLjgzLjY3IDEuNSAxLjUgMS41czEuNS0uNjcgMS41LTEuNVYxOWgxYy41NSAwIDEtLjQ1IDEtMVY4SDZ2MTB6TTMuNSA4QzIuNjcgOCAyIDguNjcgMiA5LjV2N2MwIC44My42NyAxLjUgMS41IDEuNVM1IDE3LjMzIDUgMTYuNXYtN0M1IDguNjcgNC4zMyA4IDMuNSA4em0xNyAwYy0uODMgMC0xLjUuNjctMS41IDEuNXY3YzAgLjgzLjY3IDEuNSAxLjUgMS41czEuNS0uNjcgMS41LTEuNXYtN2MwLS44My0uNjctMS41LTEuNS0xLjV6bS00Ljk3LTUuODRsMS4zLTEuM2MuMi0uMi4yLS41MSAwLS43MS0uMi0uMi0uNTEtLjItLjcxIDBsLTEuNDggMS40OEMxMy44NSAxLjIzIDEyLjk1IDEgMTIgMWMtLjk2IDAtMS44Ni4yMy0yLjY2LjYzTDcuODUuMTVjLS4yLS4yLS41MS0uMi0uNzEgMC0uMi4yLS4yLjUxIDAgLjcxbDEuMzEgMS4zMUM2Ljk3IDMuMjYgNiA1LjAxIDYgN2gxMmMwLTEuOTktLjk3LTMuNzUtMi40Ny00Ljg0ek0xMCA1SDlWNGgxdjF6bTUgMGgtMVY0aDF2MXoiPjwvcGF0aD48L2c+CjxnIGlkPSJhbm5vdW5jZW1lbnQiPjxwYXRoIGQ9Ik0yMCAySDRjLTEuMSAwLTEuOTkuOS0xLjk5IDJMMiAyMmw0LTRoMTRjMS4xIDAgMi0uOSAyLTJWNGMwLTEuMS0uOS0yLTItMnptLTcgOWgtMlY1aDJ2NnptMCA0aC0ydi0yaDJ2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJhcHBzIj48cGF0aCBkPSJNNCA4aDRWNEg0djR6bTYgMTJoNHYtNGgtNHY0em0tNiAwaDR2LTRINHY0em0wLTZoNHYtNEg0djR6bTYgMGg0di00aC00djR6bTYtMTB2NGg0VjRoLTR6bS02IDRoNFY0aC00djR6bTYgNmg0di00aC00djR6bTAgNmg0di00aC00djR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYXJjaGl2ZSI+PHBhdGggZD0iTTIwLjU0IDUuMjNsLTEuMzktMS42OEMxOC44OCAzLjIxIDE4LjQ3IDMgMTggM0g2Yy0uNDcgMC0uODguMjEtMS4xNi41NUwzLjQ2IDUuMjNDMy4xNyA1LjU3IDMgNi4wMiAzIDYuNVYxOWMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjYuNWMwLS40OC0uMTctLjkzLS40Ni0xLjI3ek0xMiAxNy41TDYuNSAxMkgxMHYtMmg0djJoMy41TDEyIDE3LjV6TTUuMTIgNWwuODEtMWgxMmwuOTQgMUg1LjEyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFycm93LWJhY2siPjxwYXRoIGQ9Ik0yMCAxMUg3LjgzbDUuNTktNS41OUwxMiA0bC04IDggOCA4IDEuNDEtMS40MUw3LjgzIDEzSDIwdi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFycm93LWRvd253YXJkIj48cGF0aCBkPSJNMjAgMTJsLTEuNDEtMS40MUwxMyAxNi4xN1Y0aC0ydjEyLjE3bC01LjU4LTUuNTlMNCAxMmw4IDggOC04eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFycm93LWRyb3AtZG93biI+PHBhdGggZD0iTTcgMTBsNSA1IDUtNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJhcnJvdy1kcm9wLWRvd24tY2lyY2xlIj48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxMmwtNC00aDhsLTQgNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJhcnJvdy1kcm9wLXVwIj48cGF0aCBkPSJNNyAxNGw1LTUgNSA1eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFycm93LWZvcndhcmQiPjxwYXRoIGQ9Ik0xMiA0bC0xLjQxIDEuNDFMMTYuMTcgMTFINHYyaDEyLjE3bC01LjU4IDUuNTlMMTIgMjBsOC04eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFycm93LXVwd2FyZCI+PHBhdGggZD0iTTQgMTJsMS40MSAxLjQxTDExIDcuODNWMjBoMlY3LjgzbDUuNTggNS41OUwyMCAxMmwtOC04LTggOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJhc3BlY3QtcmF0aW8iPjxwYXRoIGQ9Ik0xOSAxMmgtMnYzaC0zdjJoNXYtNXpNNyA5aDNWN0g1djVoMlY5em0xNC02SDNjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMThjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnptMCAxNi4wMUgzVjQuOTloMTh2MTQuMDJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYXNzZXNzbWVudCI+PHBhdGggZD0iTTE5IDNINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yek05IDE3SDd2LTdoMnY3em00IDBoLTJWN2gydjEwem00IDBoLTJ2LTRoMnY0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFzc2lnbm1lbnQiPjxwYXRoIGQ9Ik0xOSAzaC00LjE4QzE0LjQgMS44NCAxMy4zIDEgMTIgMWMtMS4zIDAtMi40Ljg0LTIuODIgMkg1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS03IDBjLjU1IDAgMSAuNDUgMSAxcy0uNDUgMS0xIDEtMS0uNDUtMS0xIC40NS0xIDEtMXptMiAxNEg3di0yaDd2MnptMy00SDd2LTJoMTB2MnptMC00SDdWN2gxMHYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFzc2lnbm1lbnQtaW5kIj48cGF0aCBkPSJNMTkgM2gtNC4xOEMxNC40IDEuODQgMTMuMyAxIDEyIDFjLTEuMyAwLTIuNC44NC0yLjgyIDJINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yem0tNyAwYy41NSAwIDEgLjQ1IDEgMXMtLjQ1IDEtMSAxLTEtLjQ1LTEtMSAuNDUtMSAxLTF6bTAgNGMxLjY2IDAgMyAxLjM0IDMgM3MtMS4zNCAzLTMgMy0zLTEuMzQtMy0zIDEuMzQtMyAzLTN6bTYgMTJINnYtMS40YzAtMiA0LTMuMSA2LTMuMXM2IDEuMSA2IDMuMVYxOXoiPjwvcGF0aD48L2c+CjxnIGlkPSJhc3NpZ25tZW50LWxhdGUiPjxwYXRoIGQ9Ik0xOSAzaC00LjE4QzE0LjQgMS44NCAxMy4zIDEgMTIgMWMtMS4zIDAtMi40Ljg0LTIuODIgMkg1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS02IDE1aC0ydi0yaDJ2MnptMC00aC0yVjhoMnY2em0tMS05Yy0uNTUgMC0xLS40NS0xLTFzLjQ1LTEgMS0xIDEgLjQ1IDEgMS0uNDUgMS0xIDF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYXNzaWdubWVudC1yZXR1cm4iPjxwYXRoIGQ9Ik0xOSAzaC00LjE4QzE0LjQgMS44NCAxMy4zIDEgMTIgMWMtMS4zIDAtMi40Ljg0LTIuODIgMkg1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS03IDBjLjU1IDAgMSAuNDUgMSAxcy0uNDUgMS0xIDEtMS0uNDUtMS0xIC40NS0xIDEtMXptNCAxMmgtNHYzbC01LTUgNS01djNoNHY0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFzc2lnbm1lbnQtcmV0dXJuZWQiPjxwYXRoIGQ9Ik0xOSAzaC00LjE4QzE0LjQgMS44NCAxMy4zIDEgMTIgMWMtMS4zIDAtMi40Ljg0LTIuODIgMkg1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS03IDBjLjU1IDAgMSAuNDUgMSAxcy0uNDUgMS0xIDEtMS0uNDUtMS0xIC40NS0xIDEtMXptMCAxNWwtNS01aDNWOWg0djRoM2wtNSA1eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImFzc2lnbm1lbnQtdHVybmVkLWluIj48cGF0aCBkPSJNMTkgM2gtNC4xOEMxNC40IDEuODQgMTMuMyAxIDEyIDFjLTEuMyAwLTIuNC44NC0yLjgyIDJINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yem0tNyAwYy41NSAwIDEgLjQ1IDEgMXMtLjQ1IDEtMSAxLTEtLjQ1LTEtMSAuNDUtMSAxLTF6bS0yIDE0bC00LTQgMS40MS0xLjQxTDEwIDE0LjE3bDYuNTktNi41OUwxOCA5bC04IDh6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYXR0YWNobWVudCI+PHBhdGggZD0iTTIgMTIuNUMyIDkuNDYgNC40NiA3IDcuNSA3SDE4YzIuMjEgMCA0IDEuNzkgNCA0cy0xLjc5IDQtNCA0SDkuNUM4LjEyIDE1IDcgMTMuODggNyAxMi41UzguMTIgMTAgOS41IDEwSDE3djJIOS40MWMtLjU1IDAtLjU1IDEgMCAxSDE4YzEuMSAwIDItLjkgMi0ycy0uOS0yLTItMkg3LjVDNS41NyA5IDQgMTAuNTcgNCAxMi41UzUuNTcgMTYgNy41IDE2SDE3djJINy41QzQuNDYgMTggMiAxNS41NCAyIDEyLjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYXV0b3JlbmV3Ij48cGF0aCBkPSJNMTIgNnYzbDQtNC00LTR2M2MtNC40MiAwLTggMy41OC04IDggMCAxLjU3LjQ2IDMuMDMgMS4yNCA0LjI2TDYuNyAxNC44Yy0uNDUtLjgzLS43LTEuNzktLjctMi44IDAtMy4zMSAyLjY5LTYgNi02em02Ljc2IDEuNzRMMTcuMyA5LjJjLjQ0Ljg0LjcgMS43OS43IDIuOCAwIDMuMzEtMi42OSA2LTYgNnYtM2wtNCA0IDQgNHYtM2M0LjQyIDAgOC0zLjU4IDgtOCAwLTEuNTctLjQ2LTMuMDMtMS4yNC00LjI2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJhY2tzcGFjZSI+PHBhdGggZD0iTTIyIDNIN2MtLjY5IDAtMS4yMy4zNS0xLjU5Ljg4TDAgMTJsNS40MSA4LjExYy4zNi41My45Ljg5IDEuNTkuODloMTVjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnptLTMgMTIuNTlMMTcuNTkgMTcgMTQgMTMuNDEgMTAuNDEgMTcgOSAxNS41OSAxMi41OSAxMiA5IDguNDEgMTAuNDEgNyAxNCAxMC41OSAxNy41OSA3IDE5IDguNDEgMTUuNDEgMTIgMTkgMTUuNTl6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iYmFja3VwIj48cGF0aCBkPSJNMTkuMzUgMTAuMDRDMTguNjcgNi41OSAxNS42NCA0IDEyIDQgOS4xMSA0IDYuNiA1LjY0IDUuMzUgOC4wNCAyLjM0IDguMzYgMCAxMC45MSAwIDE0YzAgMy4zMSAyLjY5IDYgNiA2aDEzYzIuNzYgMCA1LTIuMjQgNS01IDAtMi42NC0yLjA1LTQuNzgtNC42NS00Ljk2ek0xNCAxM3Y0aC00di00SDdsNS01IDUgNWgtM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJibG9jayI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6TTQgMTJjMC00LjQyIDMuNTgtOCA4LTggMS44NSAwIDMuNTUuNjMgNC45IDEuNjlMNS42OSAxNi45QzQuNjMgMTUuNTUgNCAxMy44NSA0IDEyem04IDhjLTEuODUgMC0zLjU1LS42My00LjktMS42OUwxOC4zMSA3LjFDMTkuMzcgOC40NSAyMCAxMC4xNSAyMCAxMmMwIDQuNDItMy41OCA4LTggOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJib29rIj48cGF0aCBkPSJNMTggMkg2Yy0xLjEgMC0yIC45LTIgMnYxNmMwIDEuMS45IDIgMiAyaDEyYzEuMSAwIDItLjkgMi0yVjRjMC0xLjEtLjktMi0yLTJ6TTYgNGg1djhsLTIuNS0xLjVMNiAxMlY0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJvb2ttYXJrIj48cGF0aCBkPSJNMTcgM0g3Yy0xLjEgMC0xLjk5LjktMS45OSAyTDUgMjFsNy0zIDcgM1Y1YzAtMS4xLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJvb2ttYXJrLWJvcmRlciI+PHBhdGggZD0iTTE3IDNIN2MtMS4xIDAtMS45OS45LTEuOTkgMkw1IDIxbDctMyA3IDNWNWMwLTEuMS0uOS0yLTItMnptMCAxNWwtNS0yLjE4TDcgMThWNWgxMHYxM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJidWctcmVwb3J0Ij48cGF0aCBkPSJNMjAgOGgtMi44MWMtLjQ1LS43OC0xLjA3LTEuNDUtMS44Mi0xLjk2TDE3IDQuNDEgMTUuNTkgM2wtMi4xNyAyLjE3QzEyLjk2IDUuMDYgMTIuNDkgNSAxMiA1Yy0uNDkgMC0uOTYuMDYtMS40MS4xN0w4LjQxIDMgNyA0LjQxbDEuNjIgMS42M0M3Ljg4IDYuNTUgNy4yNiA3LjIyIDYuODEgOEg0djJoMi4wOWMtLjA1LjMzLS4wOS42Ni0uMDkgMXYxSDR2MmgydjFjMCAuMzQuMDQuNjcuMDkgMUg0djJoMi44MWMxLjA0IDEuNzkgMi45NyAzIDUuMTkgM3M0LjE1LTEuMjEgNS4xOS0zSDIwdi0yaC0yLjA5Yy4wNS0uMzMuMDktLjY2LjA5LTF2LTFoMnYtMmgtMnYtMWMwLS4zNC0uMDQtLjY3LS4wOS0xSDIwVjh6bS02IDhoLTR2LTJoNHYyem0wLTRoLTR2LTJoNHYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImJ1aWxkIj48cGF0aCBkPSJNMjIuNyAxOWwtOS4xLTkuMWMuOS0yLjMuNC01LTEuNS02LjktMi0yLTUtMi40LTcuNC0xLjNMOSA2IDYgOSAxLjYgNC43Qy40IDcuMS45IDEwLjEgMi45IDEyLjFjMS45IDEuOSA0LjYgMi40IDYuOSAxLjVsOS4xIDkuMWMuNC40IDEgLjQgMS40IDBsMi4zLTIuM2MuNS0uNC41LTEuMS4xLTEuNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJjYWNoZWQiPjxwYXRoIGQ9Ik0xOSA4bC00IDRoM2MwIDMuMzEtMi42OSA2LTYgNi0xLjAxIDAtMS45Ny0uMjUtMi44LS43bC0xLjQ2IDEuNDZDOC45NyAxOS41NCAxMC40MyAyMCAxMiAyMGM0LjQyIDAgOC0zLjU4IDgtOGgzbC00LTR6TTYgMTJjMC0zLjMxIDIuNjktNiA2LTYgMS4wMSAwIDEuOTcuMjUgMi44LjdsMS40Ni0xLjQ2QzE1LjAzIDQuNDYgMTMuNTcgNCAxMiA0Yy00LjQyIDAtOCAzLjU4LTggOEgxbDQgNCA0LTRINnoiPjwvcGF0aD48L2c+CjxnIGlkPSJjYW1lcmEtZW5oYW5jZSI+PHBhdGggZD0iTTkgM0w3LjE3IDVINGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY3YzAtMS4xLS45LTItMi0yaC0zLjE3TDE1IDNIOXptMyAxNWMtMi43NiAwLTUtMi4yNC01LTVzMi4yNC01IDUtNSA1IDIuMjQgNSA1LTIuMjQgNS01IDV6bTAtMWwxLjI1LTIuNzVMMTYgMTNsLTIuNzUtMS4yNUwxMiA5bC0xLjI1IDIuNzVMOCAxM2wyLjc1IDEuMjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2FuY2VsIj48cGF0aCBkPSJNMTIgMkM2LjQ3IDIgMiA2LjQ3IDIgMTJzNC40NyAxMCAxMCAxMCAxMC00LjQ3IDEwLTEwUzE3LjUzIDIgMTIgMnptNSAxMy41OUwxNS41OSAxNyAxMiAxMy40MSA4LjQxIDE3IDcgMTUuNTkgMTAuNTkgMTIgNyA4LjQxIDguNDEgNyAxMiAxMC41OSAxNS41OSA3IDE3IDguNDEgMTMuNDEgMTIgMTcgMTUuNTl6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2FyZC1naWZ0Y2FyZCI+PHBhdGggZD0iTTIwIDZoLTIuMThjLjExLS4zMS4xOC0uNjUuMTgtMSAwLTEuNjYtMS4zNC0zLTMtMy0xLjA1IDAtMS45Ni41NC0yLjUgMS4zNWwtLjUuNjctLjUtLjY4QzEwLjk2IDIuNTQgMTAuMDUgMiA5IDIgNy4zNCAyIDYgMy4zNCA2IDVjMCAuMzUuMDcuNjkuMTggMUg0Yy0xLjExIDAtMS45OS44OS0xLjk5IDJMMiAxOWMwIDEuMTEuODkgMiAyIDJoMTZjMS4xMSAwIDItLjg5IDItMlY4YzAtMS4xMS0uODktMi0yLTJ6bS01LTJjLjU1IDAgMSAuNDUgMSAxcy0uNDUgMS0xIDEtMS0uNDUtMS0xIC40NS0xIDEtMXpNOSA0Yy41NSAwIDEgLjQ1IDEgMXMtLjQ1IDEtMSAxLTEtLjQ1LTEtMSAuNDUtMSAxLTF6bTExIDE1SDR2LTJoMTZ2MnptMC01SDRWOGg1LjA4TDcgMTAuODMgOC42MiAxMiAxMSA4Ljc2bDEtMS4zNiAxIDEuMzZMMTUuMzggMTIgMTcgMTAuODMgMTQuOTIgOEgyMHY2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNhcmQtbWVtYmVyc2hpcCI+PHBhdGggZD0iTTIwIDJINGMtMS4xMSAwLTIgLjg5LTIgMnYxMWMwIDEuMTEuODkgMiAyIDJoNHY1bDQtMiA0IDJ2LTVoNGMxLjExIDAgMi0uODkgMi0yVjRjMC0xLjExLS44OS0yLTItMnptMCAxM0g0di0yaDE2djJ6bTAtNUg0VjRoMTZ2NnoiPjwvcGF0aD48L2c+CjxnIGlkPSJjYXJkLXRyYXZlbCI+PHBhdGggZD0iTTIwIDZoLTNWNGMwLTEuMTEtLjg5LTItMi0ySDljLTEuMTEgMC0yIC44OS0yIDJ2Mkg0Yy0xLjExIDAtMiAuODktMiAydjExYzAgMS4xMS44OSAyIDIgMmgxNmMxLjExIDAgMi0uODkgMi0yVjhjMC0xLjExLS44OS0yLTItMnpNOSA0aDZ2Mkg5VjR6bTExIDE1SDR2LTJoMTZ2MnptMC01SDRWOGgzdjJoMlY4aDZ2MmgyVjhoM3Y2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNoYW5nZS1oaXN0b3J5Ij48cGF0aCBkPSJNMTIgNy43N0wxOC4zOSAxOEg1LjYxTDEyIDcuNzdNMTIgNEwyIDIwaDIwTDEyIDR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2hlY2siPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiPjwvcGF0aD48L2c+CjxnIGlkPSJjaGVjay1ib3giPjxwYXRoIGQ9Ik0xOSAzSDVjLTEuMTEgMC0yIC45LTIgMnYxNGMwIDEuMS44OSAyIDIgMmgxNGMxLjExIDAgMi0uOSAyLTJWNWMwLTEuMS0uODktMi0yLTJ6bS05IDE0bC01LTUgMS40MS0xLjQxTDEwIDE0LjE3bDcuNTktNy41OUwxOSA4bC05IDl6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2hlY2stYm94LW91dGxpbmUtYmxhbmsiPjxwYXRoIGQ9Ik0xOSA1djE0SDVWNWgxNG0wLTJINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNoZWNrLWNpcmNsZSI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bS0yIDE1bC01LTUgMS40MS0xLjQxTDEwIDE0LjE3bDcuNTktNy41OUwxOSA4bC05IDl6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2hldnJvbi1sZWZ0Ij48cGF0aCBkPSJNMTUuNDEgNy40MUwxNCA2bC02IDYgNiA2IDEuNDEtMS40MUwxMC44MyAxMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJjaGV2cm9uLXJpZ2h0Ij48cGF0aCBkPSJNMTAgNkw4LjU5IDcuNDEgMTMuMTcgMTJsLTQuNTggNC41OUwxMCAxOGw2LTZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2hyb21lLXJlYWRlci1tb2RlIj48cGF0aCBkPSJNMTMgMTJoN3YxLjVoLTd6bTAtMi41aDdWMTFoLTd6bTAgNWg3VjE2aC03ek0yMSA0SDNjLTEuMSAwLTIgLjktMiAydjEzYzAgMS4xLjkgMiAyIDJoMThjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uOS0yLTItMnptMCAxNWgtOVY2aDl2MTN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2xhc3MiPjxwYXRoIGQ9Ik0xOCAySDZjLTEuMSAwLTIgLjktMiAydjE2YzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWNGMwLTEuMS0uOS0yLTItMnpNNiA0aDV2OGwtMi41LTEuNUw2IDEyVjR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2xlYXIiPjxwYXRoIGQ9Ik0xOSA2LjQxTDE3LjU5IDUgMTIgMTAuNTkgNi40MSA1IDUgNi40MSAxMC41OSAxMiA1IDE3LjU5IDYuNDEgMTkgMTIgMTMuNDEgMTcuNTkgMTkgMTkgMTcuNTkgMTMuNDEgMTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2xvc2UiPjxwYXRoIGQ9Ik0xOSA2LjQxTDE3LjU5IDUgMTIgMTAuNTkgNi40MSA1IDUgNi40MSAxMC41OSAxMiA1IDE3LjU5IDYuNDEgMTkgMTIgMTMuNDEgMTcuNTkgMTkgMTkgMTcuNTkgMTMuNDEgMTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2xvdWQiPjxwYXRoIGQ9Ik0xOS4zNSAxMC4wNEMxOC42NyA2LjU5IDE1LjY0IDQgMTIgNCA5LjExIDQgNi42IDUuNjQgNS4zNSA4LjA0IDIuMzQgOC4zNiAwIDEwLjkxIDAgMTRjMCAzLjMxIDIuNjkgNiA2IDZoMTNjMi43NiAwIDUtMi4yNCA1LTUgMC0yLjY0LTIuMDUtNC43OC00LjY1LTQuOTZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2xvdWQtY2lyY2xlIj48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptNC41IDE0SDhjLTEuNjYgMC0zLTEuMzQtMy0zczEuMzQtMyAzLTNsLjE0LjAxQzguNTggOC4yOCAxMC4xMyA3IDEyIDdjMi4yMSAwIDQgMS43OSA0IDRoLjVjMS4zOCAwIDIuNSAxLjEyIDIuNSAyLjVTMTcuODggMTYgMTYuNSAxNnoiPjwvcGF0aD48L2c+CjxnIGlkPSJjbG91ZC1kb25lIj48cGF0aCBkPSJNMTkuMzUgMTAuMDRDMTguNjcgNi41OSAxNS42NCA0IDEyIDQgOS4xMSA0IDYuNiA1LjY0IDUuMzUgOC4wNCAyLjM0IDguMzYgMCAxMC45MSAwIDE0YzAgMy4zMSAyLjY5IDYgNiA2aDEzYzIuNzYgMCA1LTIuMjQgNS01IDAtMi42NC0yLjA1LTQuNzgtNC42NS00Ljk2ek0xMCAxN2wtMy41LTMuNSAxLjQxLTEuNDFMMTAgMTQuMTcgMTUuMTggOWwxLjQxIDEuNDFMMTAgMTd6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2xvdWQtZG93bmxvYWQiPjxwYXRoIGQ9Ik0xOS4zNSAxMC4wNEMxOC42NyA2LjU5IDE1LjY0IDQgMTIgNCA5LjExIDQgNi42IDUuNjQgNS4zNSA4LjA0IDIuMzQgOC4zNiAwIDEwLjkxIDAgMTRjMCAzLjMxIDIuNjkgNiA2IDZoMTNjMi43NiAwIDUtMi4yNCA1LTUgMC0yLjY0LTIuMDUtNC43OC00LjY1LTQuOTZ6TTE3IDEzbC01IDUtNS01aDNWOWg0djRoM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJjbG91ZC1vZmYiPjxwYXRoIGQ9Ik0xOS4zNSAxMC4wNEMxOC42NyA2LjU5IDE1LjY0IDQgMTIgNGMtMS40OCAwLTIuODUuNDMtNC4wMSAxLjE3bDEuNDYgMS40NkMxMC4yMSA2LjIzIDExLjA4IDYgMTIgNmMzLjA0IDAgNS41IDIuNDYgNS41IDUuNXYuNUgxOWMxLjY2IDAgMyAxLjM0IDMgMyAwIDEuMTMtLjY0IDIuMTEtMS41NiAyLjYybDEuNDUgMS40NUMyMy4xNiAxOC4xNiAyNCAxNi42OCAyNCAxNWMwLTIuNjQtMi4wNS00Ljc4LTQuNjUtNC45NnpNMyA1LjI3bDIuNzUgMi43NEMyLjU2IDguMTUgMCAxMC43NyAwIDE0YzAgMy4zMSAyLjY5IDYgNiA2aDExLjczbDIgMkwyMSAyMC43MyA0LjI3IDQgMyA1LjI3ek03LjczIDEwbDggOEg2Yy0yLjIxIDAtNC0xLjc5LTQtNHMxLjc5LTQgNC00aDEuNzN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2xvdWQtcXVldWUiPjxwYXRoIGQ9Ik0xOS4zNSAxMC4wNEMxOC42NyA2LjU5IDE1LjY0IDQgMTIgNCA5LjExIDQgNi42IDUuNjQgNS4zNSA4LjA0IDIuMzQgOC4zNiAwIDEwLjkxIDAgMTRjMCAzLjMxIDIuNjkgNiA2IDZoMTNjMi43NiAwIDUtMi4yNCA1LTUgMC0yLjY0LTIuMDUtNC43OC00LjY1LTQuOTZ6TTE5IDE4SDZjLTIuMjEgMC00LTEuNzktNC00czEuNzktNCA0LTRoLjcxQzcuMzcgNy42OSA5LjQ4IDYgMTIgNmMzLjA0IDAgNS41IDIuNDYgNS41IDUuNXYuNUgxOWMxLjY2IDAgMyAxLjM0IDMgM3MtMS4zNCAzLTMgM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJjbG91ZC11cGxvYWQiPjxwYXRoIGQ9Ik0xOS4zNSAxMC4wNEMxOC42NyA2LjU5IDE1LjY0IDQgMTIgNCA5LjExIDQgNi42IDUuNjQgNS4zNSA4LjA0IDIuMzQgOC4zNiAwIDEwLjkxIDAgMTRjMCAzLjMxIDIuNjkgNiA2IDZoMTNjMi43NiAwIDUtMi4yNCA1LTUgMC0yLjY0LTIuMDUtNC43OC00LjY1LTQuOTZ6TTE0IDEzdjRoLTR2LTRIN2w1LTUgNSA1aC0zeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNvZGUiPjxwYXRoIGQ9Ik05LjQgMTYuNkw0LjggMTJsNC42LTQuNkw4IDZsLTYgNiA2IDYgMS40LTEuNHptNS4yIDBsNC42LTQuNi00LjYtNC42TDE2IDZsNiA2LTYgNi0xLjQtMS40eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNvbXBhcmUtYXJyb3dzIj48cGF0aCBkPSJNOS4wMSAxNEgydjJoNy4wMXYzTDEzIDE1bC0zLjk5LTR2M3ptNS45OC0xdi0zSDIyVjhoLTcuMDFWNUwxMSA5bDMuOTkgNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJjb250ZW50LWNvcHkiPjxwYXRoIGQ9Ik0xNiAxSDRjLTEuMSAwLTIgLjktMiAydjE0aDJWM2gxMlYxem0zIDRIOGMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxMWMxLjEgMCAyLS45IDItMlY3YzAtMS4xLS45LTItMi0yem0wIDE2SDhWN2gxMXYxNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJjb250ZW50LWN1dCI+PHBhdGggZD0iTTkuNjQgNy42NGMuMjMtLjUuMzYtMS4wNS4zNi0xLjY0IDAtMi4yMS0xLjc5LTQtNC00UzIgMy43OSAyIDZzMS43OSA0IDQgNGMuNTkgMCAxLjE0LS4xMyAxLjY0LS4zNkwxMCAxMmwtMi4zNiAyLjM2QzcuMTQgMTQuMTMgNi41OSAxNCA2IDE0Yy0yLjIxIDAtNCAxLjc5LTQgNHMxLjc5IDQgNCA0IDQtMS43OSA0LTRjMC0uNTktLjEzLTEuMTQtLjM2LTEuNjRMMTIgMTRsNyA3aDN2LTFMOS42NCA3LjY0ek02IDhjLTEuMSAwLTItLjg5LTItMnMuOS0yIDItMiAyIC44OSAyIDItLjkgMi0yIDJ6bTAgMTJjLTEuMSAwLTItLjg5LTItMnMuOS0yIDItMiAyIC44OSAyIDItLjkgMi0yIDJ6bTYtNy41Yy0uMjggMC0uNS0uMjItLjUtLjVzLjIyLS41LjUtLjUuNS4yMi41LjUtLjIyLjUtLjUuNXpNMTkgM2wtNiA2IDIgMiA3LTdWM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJjb250ZW50LXBhc3RlIj48cGF0aCBkPSJNMTkgMmgtNC4xOEMxNC40Ljg0IDEzLjMgMCAxMiAwYy0xLjMgMC0yLjQuODQtMi44MiAySDVjLTEuMSAwLTIgLjktMiAydjE2YzAgMS4xLjkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNGMwLTEuMS0uOS0yLTItMnptLTcgMGMuNTUgMCAxIC40NSAxIDFzLS40NSAxLTEgMS0xLS40NS0xLTEgLjQ1LTEgMS0xem03IDE4SDVWNGgydjNoMTBWNGgydjE2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImNvcHlyaWdodCI+PHBhdGggZD0iTTEwLjA4IDEwLjg2Yy4wNS0uMzMuMTYtLjYyLjMtLjg3cy4zNC0uNDYuNTktLjYyYy4yNC0uMTUuNTQtLjIyLjkxLS4yMy4yMy4wMS40NC4wNS42My4xMy4yLjA5LjM4LjIxLjUyLjM2cy4yNS4zMy4zNC41My4xMy40Mi4xNC42NGgxLjc5Yy0uMDItLjQ3LS4xMS0uOS0uMjgtMS4yOXMtLjQtLjczLS43LTEuMDEtLjY2LS41LTEuMDgtLjY2LS44OC0uMjMtMS4zOS0uMjNjLS42NSAwLTEuMjIuMTEtMS43LjM0cy0uODguNTMtMS4yLjkyLS41Ni44NC0uNzEgMS4zNlM4IDExLjI5IDggMTEuODd2LjI3YzAgLjU4LjA4IDEuMTIuMjMgMS42NHMuMzkuOTcuNzEgMS4zNS43Mi42OSAxLjIuOTEgMS4wNS4zNCAxLjcuMzRjLjQ3IDAgLjkxLS4wOCAxLjMyLS4yM3MuNzctLjM2IDEuMDgtLjYzLjU2LS41OC43NC0uOTQuMjktLjc0LjMtMS4xNWgtMS43OWMtLjAxLjIxLS4wNi40LS4xNS41OHMtLjIxLjMzLS4zNi40Ni0uMzIuMjMtLjUyLjNjLS4xOS4wNy0uMzkuMDktLjYuMS0uMzYtLjAxLS42Ni0uMDgtLjg5LS4yMy0uMjUtLjE2LS40NS0uMzctLjU5LS42MnMtLjI1LS41NS0uMy0uODgtLjA4LS42Ny0uMDgtMXYtLjI3YzAtLjM1LjAzLS42OC4wOC0xLjAxek0xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDgtMy41OSA4LTggOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJjcmVhdGUiPjxwYXRoIGQ9Ik0zIDE3LjI1VjIxaDMuNzVMMTcuODEgOS45NGwtMy43NS0zLjc1TDMgMTcuMjV6TTIwLjcxIDcuMDRjLjM5LS4zOS4zOS0xLjAyIDAtMS40MWwtMi4zNC0yLjM0Yy0uMzktLjM5LTEuMDItLjM5LTEuNDEgMGwtMS44MyAxLjgzIDMuNzUgMy43NSAxLjgzLTEuODN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY3JlYXRlLW5ldy1mb2xkZXIiPjxwYXRoIGQ9Ik0yMCA2aC04bC0yLTJINGMtMS4xMSAwLTEuOTkuODktMS45OSAyTDIgMThjMCAxLjExLjg5IDIgMiAyaDE2YzEuMTEgMCAyLS44OSAyLTJWOGMwLTEuMTEtLjg5LTItMi0yem0tMSA4aC0zdjNoLTJ2LTNoLTN2LTJoM1Y5aDJ2M2gzdjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY3JlZGl0LWNhcmQiPjxwYXRoIGQ9Ik0yMCA0SDRjLTEuMTEgMC0xLjk5Ljg5LTEuOTkgMkwyIDE4YzAgMS4xMS44OSAyIDIgMmgxNmMxLjExIDAgMi0uODkgMi0yVjZjMC0xLjExLS44OS0yLTItMnptMCAxNEg0di02aDE2djZ6bTAtMTBINFY2aDE2djJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZGFzaGJvYXJkIj48cGF0aCBkPSJNMyAxM2g4VjNIM3YxMHptMCA4aDh2LTZIM3Y2em0xMCAwaDhWMTFoLTh2MTB6bTAtMTh2Nmg4VjNoLTh6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZGF0ZS1yYW5nZSI+PHBhdGggZD0iTTkgMTFIN3YyaDJ2LTJ6bTQgMGgtMnYyaDJ2LTJ6bTQgMGgtMnYyaDJ2LTJ6bTItN2gtMVYyaC0ydjJIOFYySDZ2Mkg1Yy0xLjExIDAtMS45OS45LTEuOTkgMkwzIDIwYzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjZjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY5aDE0djExeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImRlbGV0ZSI+PHBhdGggZD0iTTYgMTljMCAxLjEuOSAyIDIgMmg4YzEuMSAwIDItLjkgMi0yVjdINnYxMnpNMTkgNGgtMy41bC0xLTFoLTVsLTEgMUg1djJoMTRWNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJkZWxldGUtZm9yZXZlciI+PHBhdGggZD0iTTYgMTljMCAxLjEuOSAyIDIgMmg4YzEuMSAwIDItLjkgMi0yVjdINnYxMnptMi40Ni03LjEybDEuNDEtMS40MUwxMiAxMi41OWwyLjEyLTIuMTIgMS40MSAxLjQxTDEzLjQxIDE0bDIuMTIgMi4xMi0xLjQxIDEuNDFMMTIgMTUuNDFsLTIuMTIgMi4xMi0xLjQxLTEuNDFMMTAuNTkgMTRsLTIuMTMtMi4xMnpNMTUuNSA0bC0xLTFoLTVsLTEgMUg1djJoMTRWNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJkZWxldGUtc3dlZXAiPjxwYXRoIGQ9Ik0xNSAxNmg0djJoLTR6bTAtOGg3djJoLTd6bTAgNGg2djJoLTZ6TTMgMThjMCAxLjEuOSAyIDIgMmg2YzEuMSAwIDItLjkgMi0yVjhIM3YxMHpNMTQgNWgtM2wtMS0xSDZMNSA1SDJ2MmgxMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJkZXNjcmlwdGlvbiI+PHBhdGggZD0iTTE0IDJINmMtMS4xIDAtMS45OS45LTEuOTkgMkw0IDIwYzAgMS4xLjg5IDIgMS45OSAySDE4YzEuMSAwIDItLjkgMi0yVjhsLTYtNnptMiAxNkg4di0yaDh2MnptMC00SDh2LTJoOHYyem0tMy01VjMuNUwxOC41IDlIMTN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZG5zIj48cGF0aCBkPSJNMjAgMTNINGMtLjU1IDAtMSAuNDUtMSAxdjZjMCAuNTUuNDUgMSAxIDFoMTZjLjU1IDAgMS0uNDUgMS0xdi02YzAtLjU1LS40NS0xLTEtMXpNNyAxOWMtMS4xIDAtMi0uOS0yLTJzLjktMiAyLTIgMiAuOSAyIDItLjkgMi0yIDJ6TTIwIDNINGMtLjU1IDAtMSAuNDUtMSAxdjZjMCAuNTUuNDUgMSAxIDFoMTZjLjU1IDAgMS0uNDUgMS0xVjRjMC0uNTUtLjQ1LTEtMS0xek03IDljLTEuMSAwLTItLjktMi0ycy45LTIgMi0yIDIgLjkgMiAyLS45IDItMiAyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImRvbmUiPjxwYXRoIGQ9Ik05IDE2LjJMNC44IDEybC0xLjQgMS40TDkgMTkgMjEgN2wtMS40LTEuNEw5IDE2LjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZG9uZS1hbGwiPjxwYXRoIGQ9Ik0xOCA3bC0xLjQxLTEuNDEtNi4zNCA2LjM0IDEuNDEgMS40MUwxOCA3em00LjI0LTEuNDFMMTEuNjYgMTYuMTcgNy40OCAxMmwtMS40MSAxLjQxTDExLjY2IDE5bDEyLTEyLTEuNDItMS40MXpNLjQxIDEzLjQxTDYgMTlsMS40MS0xLjQxTDEuODMgMTIgLjQxIDEzLjQxeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImRvbnV0LWxhcmdlIj48cGF0aCBkPSJNMTEgNS4wOFYyYy01IC41LTkgNC44MS05IDEwczQgOS41IDkgMTB2LTMuMDhjLTMtLjQ4LTYtMy40LTYtNi45MnMzLTYuNDQgNi02Ljkyek0xOC45NyAxMUgyMmMtLjQ3LTUtNC04LjUzLTktOXYzLjA4QzE2IDUuNTEgMTguNTQgOCAxOC45NyAxMXpNMTMgMTguOTJWMjJjNS0uNDcgOC41My00IDktOWgtMy4wM2MtLjQzIDMtMi45NyA1LjQ5LTUuOTcgNS45MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJkb251dC1zbWFsbCI+PHBhdGggZD0iTTExIDkuMTZWMmMtNSAuNS05IDQuNzktOSAxMHM0IDkuNSA5IDEwdi03LjE2Yy0xLS40MS0yLTEuNTItMi0yLjg0czEtMi40MyAyLTIuODR6TTE0Ljg2IDExSDIyYy0uNDgtNC43NS00LTguNTMtOS05djcuMTZjMSAuMyAxLjUyLjk4IDEuODYgMS44NHpNMTMgMTQuODRWMjJjNS0uNDcgOC41Mi00LjI1IDktOWgtNy4xNGMtLjM0Ljg2LS44NiAxLjU0LTEuODYgMS44NHoiPjwvcGF0aD48L2c+CjxnIGlkPSJkcmFmdHMiPjxwYXRoIGQ9Ik0yMS45OSA4YzAtLjcyLS4zNy0xLjM1LS45NC0xLjdMMTIgMSAyLjk1IDYuM0MyLjM4IDYuNjUgMiA3LjI4IDIgOHYxMGMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0ybC0uMDEtMTB6TTEyIDEzTDMuNzQgNy44NCAxMiAzbDguMjYgNC44NEwxMiAxM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJlamVjdCI+PHBhdGggZD0iTTUgMTdoMTR2Mkg1em03LTEyTDUuMzMgMTVoMTMuMzR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZXJyb3IiPjxwYXRoIGQ9Ik0xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyem0xIDE1aC0ydi0yaDJ2MnptMC00aC0yVjdoMnY2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImVycm9yLW91dGxpbmUiPjxwYXRoIGQ9Ik0xMSAxNWgydjJoLTJ6bTAtOGgydjZoLTJ6bS45OS01QzYuNDcgMiAyIDYuNDggMiAxMnM0LjQ3IDEwIDkuOTkgMTBDMTcuNTIgMjIgMjIgMTcuNTIgMjIgMTJTMTcuNTIgMiAxMS45OSAyek0xMiAyMGMtNC40MiAwLTgtMy41OC04LThzMy41OC04IDgtOCA4IDMuNTggOCA4LTMuNTggOC04IDh6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZXVyby1zeW1ib2wiPjxwYXRoIGQ9Ik0xNSAxOC41Yy0yLjUxIDAtNC42OC0xLjQyLTUuNzYtMy41SDE1di0ySDguNThjLS4wNS0uMzMtLjA4LS42Ni0uMDgtMXMuMDMtLjY3LjA4LTFIMTVWOUg5LjI0QzEwLjMyIDYuOTIgMTIuNSA1LjUgMTUgNS41YzEuNjEgMCAzLjA5LjU5IDQuMjMgMS41N0wyMSA1LjNDMTkuNDEgMy44NyAxNy4zIDMgMTUgM2MtMy45MiAwLTcuMjQgMi41MS04LjQ4IDZIM3YyaDMuMDZjLS4wNC4zMy0uMDYuNjYtLjA2IDEgMCAuMzQuMDIuNjcuMDYgMUgzdjJoMy41MmMxLjI0IDMuNDkgNC41NiA2IDguNDggNiAyLjMxIDAgNC40MS0uODcgNi0yLjNsLTEuNzgtMS43N2MtMS4xMy45OC0yLjYgMS41Ny00LjIyIDEuNTd6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZXZlbnQiPjxwYXRoIGQ9Ik0xNyAxMmgtNXY1aDV2LTV6TTE2IDF2Mkg4VjFINnYySDVjLTEuMTEgMC0xLjk5LjktMS45OSAyTDMgMTljMCAxLjEuODkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMmgtMVYxaC0yem0zIDE4SDVWOGgxNHYxMXoiPjwvcGF0aD48L2c+CjxnIGlkPSJldmVudC1zZWF0Ij48cGF0aCBkPSJNNCAxOHYzaDN2LTNoMTB2M2gzdi02SDR6bTE1LThoM3YzaC0zek0yIDEwaDN2M0gyem0xNSAzSDdWNWMwLTEuMS45LTIgMi0yaDZjMS4xIDAgMiAuOSAyIDJ2OHoiPjwvcGF0aD48L2c+CjxnIGlkPSJleGl0LXRvLWFwcCI+PHBhdGggZD0iTTEwLjA5IDE1LjU5TDExLjUgMTdsNS01LTUtNS0xLjQxIDEuNDFMMTIuNjcgMTFIM3YyaDkuNjdsLTIuNTggMi41OXpNMTkgM0g1Yy0xLjExIDAtMiAuOS0yIDJ2NGgyVjVoMTR2MTRINXYtNEgzdjRjMCAxLjEuODkgMiAyIDJoMTRjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJleHBhbmQtbGVzcyI+PHBhdGggZD0iTTEyIDhsLTYgNiAxLjQxIDEuNDFMMTIgMTAuODNsNC41OSA0LjU4TDE4IDE0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImV4cGFuZC1tb3JlIj48cGF0aCBkPSJNMTYuNTkgOC41OUwxMiAxMy4xNyA3LjQxIDguNTkgNiAxMGw2IDYgNi02eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImV4cGxvcmUiPjxwYXRoIGQ9Ik0xMiAxMC45Yy0uNjEgMC0xLjEuNDktMS4xIDEuMXMuNDkgMS4xIDEuMSAxLjFjLjYxIDAgMS4xLS40OSAxLjEtMS4xcy0uNDktMS4xLTEuMS0xLjF6TTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTIuMTkgMTIuMTlMNiAxOGwzLjgxLTguMTlMMTggNmwtMy44MSA4LjE5eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImV4dGVuc2lvbiI+PHBhdGggZD0iTTIwLjUgMTFIMTlWN2MwLTEuMS0uOS0yLTItMmgtNFYzLjVDMTMgMi4xMiAxMS44OCAxIDEwLjUgMVM4IDIuMTIgOCAzLjVWNUg0Yy0xLjEgMC0xLjk5LjktMS45OSAydjMuOEgzLjVjMS40OSAwIDIuNyAxLjIxIDIuNyAyLjdzLTEuMjEgMi43LTIuNyAyLjdIMlYyMGMwIDEuMS45IDIgMiAyaDMuOHYtMS41YzAtMS40OSAxLjIxLTIuNyAyLjctMi43IDEuNDkgMCAyLjcgMS4yMSAyLjcgMi43VjIySDE3YzEuMSAwIDItLjkgMi0ydi00aDEuNWMxLjM4IDAgMi41LTEuMTIgMi41LTIuNVMyMS44OCAxMSAyMC41IDExeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZhY2UiPjxwYXRoIGQ9Ik05IDExLjc1Yy0uNjkgMC0xLjI1LjU2LTEuMjUgMS4yNXMuNTYgMS4yNSAxLjI1IDEuMjUgMS4yNS0uNTYgMS4yNS0xLjI1LS41Ni0xLjI1LTEuMjUtMS4yNXptNiAwYy0uNjkgMC0xLjI1LjU2LTEuMjUgMS4yNXMuNTYgMS4yNSAxLjI1IDEuMjUgMS4yNS0uNTYgMS4yNS0xLjI1LS41Ni0xLjI1LTEuMjUtMS4yNXpNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LTggMC0uMjkuMDItLjU4LjA1LS44NiAyLjM2LTEuMDUgNC4yMy0yLjk4IDUuMjEtNS4zN0MxMS4wNyA4LjMzIDE0LjA1IDEwIDE3LjQyIDEwYy43OCAwIDEuNTMtLjA5IDIuMjUtLjI2LjIxLjcxLjMzIDEuNDcuMzMgMi4yNiAwIDQuNDEtMy41OSA4LTggOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJmYXZvcml0ZSI+PHBhdGggZD0iTTEyIDIxLjM1bC0xLjQ1LTEuMzJDNS40IDE1LjM2IDIgMTIuMjggMiA4LjUgMiA1LjQyIDQuNDIgMyA3LjUgM2MxLjc0IDAgMy40MS44MSA0LjUgMi4wOUMxMy4wOSAzLjgxIDE0Ljc2IDMgMTYuNSAzIDE5LjU4IDMgMjIgNS40MiAyMiA4LjVjMCAzLjc4LTMuNCA2Ljg2LTguNTUgMTEuNTRMMTIgMjEuMzV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmF2b3JpdGUtYm9yZGVyIj48cGF0aCBkPSJNMTYuNSAzYy0xLjc0IDAtMy40MS44MS00LjUgMi4wOUMxMC45MSAzLjgxIDkuMjQgMyA3LjUgMyA0LjQyIDMgMiA1LjQyIDIgOC41YzAgMy43OCAzLjQgNi44NiA4LjU1IDExLjU0TDEyIDIxLjM1bDEuNDUtMS4zMkMxOC42IDE1LjM2IDIyIDEyLjI4IDIyIDguNSAyMiA1LjQyIDE5LjU4IDMgMTYuNSAzem0tNC40IDE1LjU1bC0uMS4xLS4xLS4xQzcuMTQgMTQuMjQgNCAxMS4zOSA0IDguNSA0IDYuNSA1LjUgNSA3LjUgNWMxLjU0IDAgMy4wNC45OSAzLjU3IDIuMzZoMS44N0MxMy40NiA1Ljk5IDE0Ljk2IDUgMTYuNSA1YzIgMCAzLjUgMS41IDMuNSAzLjUgMCAyLjg5LTMuMTQgNS43NC03LjkgMTAuMDV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmVlZGJhY2siPjxwYXRoIGQ9Ik0yMCAySDRjLTEuMSAwLTEuOTkuOS0xLjk5IDJMMiAyMmw0LTRoMTRjMS4xIDAgMi0uOSAyLTJWNGMwLTEuMS0uOS0yLTItMnptLTcgMTJoLTJ2LTJoMnYyem0wLTRoLTJWNmgydjR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmlsZS1kb3dubG9hZCI+PHBhdGggZD0iTTE5IDloLTRWM0g5djZINWw3IDcgNy03ek01IDE4djJoMTR2LTJINXoiPjwvcGF0aD48L2c+CjxnIGlkPSJmaWxlLXVwbG9hZCI+PHBhdGggZD0iTTkgMTZoNnYtNmg0bC03LTctNyA3aDR6bS00IDJoMTR2Mkg1eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZpbHRlci1saXN0Ij48cGF0aCBkPSJNMTAgMThoNHYtMmgtNHYyek0zIDZ2MmgxOFY2SDN6bTMgN2gxMnYtMkg2djJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmluZC1pbi1wYWdlIj48cGF0aCBkPSJNMjAgMTkuNTlWOGwtNi02SDZjLTEuMSAwLTEuOTkuOS0xLjk5IDJMNCAyMGMwIDEuMS44OSAyIDEuOTkgMkgxOGMuNDUgMCAuODUtLjE1IDEuMTktLjRsLTQuNDMtNC40M2MtLjguNTItMS43NC44My0yLjc2LjgzLTIuNzYgMC01LTIuMjQtNS01czIuMjQtNSA1LTUgNSAyLjI0IDUgNWMwIDEuMDItLjMxIDEuOTYtLjgzIDIuNzVMMjAgMTkuNTl6TTkgMTNjMCAxLjY2IDEuMzQgMyAzIDNzMy0xLjM0IDMtMy0xLjM0LTMtMy0zLTMgMS4zNC0zIDN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmluZC1yZXBsYWNlIj48cGF0aCBkPSJNMTEgNmMxLjM4IDAgMi42My41NiAzLjU0IDEuNDZMMTIgMTBoNlY0bC0yLjA1IDIuMDVDMTQuNjggNC43OCAxMi45MyA0IDExIDRjLTMuNTMgMC02LjQzIDIuNjEtNi45MiA2SDYuMWMuNDYtMi4yOCAyLjQ4LTQgNC45LTR6bTUuNjQgOS4xNGMuNjYtLjkgMS4xMi0xLjk3IDEuMjgtMy4xNEgxNS45Yy0uNDYgMi4yOC0yLjQ4IDQtNC45IDQtMS4zOCAwLTIuNjMtLjU2LTMuNTQtMS40NkwxMCAxMkg0djZsMi4wNS0yLjA1QzcuMzIgMTcuMjIgOS4wNyAxOCAxMSAxOGMxLjU1IDAgMi45OC0uNTEgNC4xNC0xLjM2TDIwIDIxLjQ5IDIxLjQ5IDIwbC00Ljg1LTQuODZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmluZ2VycHJpbnQiPjxwYXRoIGQ9Ik0xNy44MSA0LjQ3Yy0uMDggMC0uMTYtLjAyLS4yMy0uMDZDMTUuNjYgMy40MiAxNCAzIDEyLjAxIDNjLTEuOTggMC0zLjg2LjQ3LTUuNTcgMS40MS0uMjQuMTMtLjU0LjA0LS42OC0uMi0uMTMtLjI0LS4wNC0uNTUuMi0uNjhDNy44MiAyLjUyIDkuODYgMiAxMi4wMSAyYzIuMTMgMCAzLjk5LjQ3IDYuMDMgMS41Mi4yNS4xMy4zNC40My4yMS42Ny0uMDkuMTgtLjI2LjI4LS40NC4yOHpNMy41IDkuNzJjLS4xIDAtLjItLjAzLS4yOS0uMDktLjIzLS4xNi0uMjgtLjQ3LS4xMi0uNy45OS0xLjQgMi4yNS0yLjUgMy43NS0zLjI3QzkuOTggNC4wNCAxNCA0LjAzIDE3LjE1IDUuNjVjMS41Ljc3IDIuNzYgMS44NiAzLjc1IDMuMjUuMTYuMjIuMTEuNTQtLjEyLjctLjIzLjE2LS41NC4xMS0uNy0uMTItLjktMS4yNi0yLjA0LTIuMjUtMy4zOS0yLjk0LTIuODctMS40Ny02LjU0LTEuNDctOS40LjAxLTEuMzYuNy0yLjUgMS43LTMuNCAyLjk2LS4wOC4xNC0uMjMuMjEtLjM5LjIxem02LjI1IDEyLjA3Yy0uMTMgMC0uMjYtLjA1LS4zNS0uMTUtLjg3LS44Ny0xLjM0LTEuNDMtMi4wMS0yLjY0LS42OS0xLjIzLTEuMDUtMi43My0xLjA1LTQuMzQgMC0yLjk3IDIuNTQtNS4zOSA1LjY2LTUuMzlzNS42NiAyLjQyIDUuNjYgNS4zOWMwIC4yOC0uMjIuNS0uNS41cy0uNS0uMjItLjUtLjVjMC0yLjQyLTIuMDktNC4zOS00LjY2LTQuMzktMi41NyAwLTQuNjYgMS45Ny00LjY2IDQuMzkgMCAxLjQ0LjMyIDIuNzcuOTMgMy44NS42NCAxLjE1IDEuMDggMS42NCAxLjg1IDIuNDIuMTkuMi4xOS41MSAwIC43MS0uMTEuMS0uMjQuMTUtLjM3LjE1em03LjE3LTEuODVjLTEuMTkgMC0yLjI0LS4zLTMuMS0uODktMS40OS0xLjAxLTIuMzgtMi42NS0yLjM4LTQuMzkgMC0uMjguMjItLjUuNS0uNXMuNS4yMi41LjVjMCAxLjQxLjcyIDIuNzQgMS45NCAzLjU2LjcxLjQ4IDEuNTQuNzEgMi41NC43MS4yNCAwIC42NC0uMDMgMS4wNC0uMS4yNy0uMDUuNTMuMTMuNTguNDEuMDUuMjctLjEzLjUzLS40MS41OC0uNTcuMTEtMS4wNy4xMi0xLjIxLjEyek0xNC45MSAyMmMtLjA0IDAtLjA5LS4wMS0uMTMtLjAyLTEuNTktLjQ0LTIuNjMtMS4wMy0zLjcyLTIuMS0xLjQtMS4zOS0yLjE3LTMuMjQtMi4xNy01LjIyIDAtMS42MiAxLjM4LTIuOTQgMy4wOC0yLjk0IDEuNyAwIDMuMDggMS4zMiAzLjA4IDIuOTQgMCAxLjA3LjkzIDEuOTQgMi4wOCAxLjk0czIuMDgtLjg3IDIuMDgtMS45NGMwLTMuNzctMy4yNS02LjgzLTcuMjUtNi44My0yLjg0IDAtNS40NCAxLjU4LTYuNjEgNC4wMy0uMzkuODEtLjU5IDEuNzYtLjU5IDIuOCAwIC43OC4wNyAyLjAxLjY3IDMuNjEuMS4yNi0uMDMuNTUtLjI5LjY0LS4yNi4xLS41NS0uMDQtLjY0LS4yOS0uNDktMS4zMS0uNzMtMi42MS0uNzMtMy45NiAwLTEuMi4yMy0yLjI5LjY4LTMuMjQgMS4zMy0yLjc5IDQuMjgtNC42IDcuNTEtNC42IDQuNTUgMCA4LjI1IDMuNTEgOC4yNSA3LjgzIDAgMS42Mi0xLjM4IDIuOTQtMy4wOCAyLjk0cy0zLjA4LTEuMzItMy4wOC0yLjk0YzAtMS4wNy0uOTMtMS45NC0yLjA4LTEuOTRzLTIuMDguODctMi4wOCAxLjk0YzAgMS43MS42NiAzLjMxIDEuODcgNC41MS45NS45NCAxLjg2IDEuNDYgMy4yNyAxLjg1LjI3LjA3LjQyLjM1LjM1LjYxLS4wNS4yMy0uMjYuMzgtLjQ3LjM4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZpcnN0LXBhZ2UiPjxwYXRoIGQ9Ik0xOC40MSAxNi41OUwxMy44MiAxMmw0LjU5LTQuNTlMMTcgNmwtNiA2IDYgNnpNNiA2aDJ2MTJINnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmbGFnIj48cGF0aCBkPSJNMTQuNCA2TDE0IDRINXYxN2gydi03aDUuNmwuNCAyaDdWNnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmbGlnaHQtbGFuZCI+PHBhdGggZD0iTTIuNSAxOWgxOXYyaC0xOXptNy4xOC01LjczbDQuMzUgMS4xNiA1LjMxIDEuNDJjLjguMjEgMS42Mi0uMjYgMS44NC0xLjA2LjIxLS44LS4yNi0xLjYyLTEuMDYtMS44NGwtNS4zMS0xLjQyLTIuNzYtOS4wMkwxMC4xMiAydjguMjhMNS4xNSA4Ljk1bC0uOTMtMi4zMi0xLjQ1LS4zOXY1LjE3bDEuNi40MyA1LjMxIDEuNDN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZmxpZ2h0LXRha2VvZmYiPjxwYXRoIGQ9Ik0yLjUgMTloMTl2MmgtMTl6bTE5LjU3LTkuMzZjLS4yMS0uOC0xLjA0LTEuMjgtMS44NC0xLjA2TDE0LjkyIDEwbC02LjktNi40My0xLjkzLjUxIDQuMTQgNy4xNy00Ljk3IDEuMzMtMS45Ny0xLjU0LTEuNDUuMzkgMS44MiAzLjE2Ljc3IDEuMzMgMS42LS40MyA1LjMxLTEuNDIgNC4zNS0xLjE2TDIxIDExLjQ5Yy44MS0uMjMgMS4yOC0xLjA1IDEuMDctMS44NXoiPjwvcGF0aD48L2c+CjxnIGlkPSJmbGlwLXRvLWJhY2siPjxwYXRoIGQ9Ik05IDdIN3YyaDJWN3ptMCA0SDd2Mmgydi0yem0wLThjLTEuMTEgMC0yIC45LTIgMmgyVjN6bTQgMTJoLTJ2Mmgydi0yem02LTEydjJoMmMwLTEuMS0uOS0yLTItMnptLTYgMGgtMnYyaDJWM3pNOSAxN3YtMkg3YzAgMS4xLjg5IDIgMiAyem0xMC00aDJ2LTJoLTJ2MnptMC00aDJWN2gtMnYyem0wIDhjMS4xIDAgMi0uOSAyLTJoLTJ2MnpNNSA3SDN2MTJjMCAxLjEuODkgMiAyIDJoMTJ2LTJINVY3em0xMC0yaDJWM2gtMnYyem0wIDEyaDJ2LTJoLTJ2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJmbGlwLXRvLWZyb250Ij48cGF0aCBkPSJNMyAxM2gydi0ySDN2MnptMCA0aDJ2LTJIM3Yyem0yIDR2LTJIM2MwIDEuMS44OSAyIDIgMnpNMyA5aDJWN0gzdjJ6bTEyIDEyaDJ2LTJoLTJ2MnptNC0xOEg5Yy0xLjExIDAtMiAuOS0yIDJ2MTBjMCAxLjEuODkgMiAyIDJoMTBjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnptMCAxMkg5VjVoMTB2MTB6bS04IDZoMnYtMmgtMnYyem0tNCAwaDJ2LTJIN3YyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZvbGRlciI+PHBhdGggZD0iTTEwIDRINGMtMS4xIDAtMS45OS45LTEuOTkgMkwyIDE4YzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWOGMwLTEuMS0uOS0yLTItMmgtOGwtMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZvbGRlci1vcGVuIj48cGF0aCBkPSJNMjAgNmgtOGwtMi0ySDRjLTEuMSAwLTEuOTkuOS0xLjk5IDJMMiAxOGMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjhjMC0xLjEtLjktMi0yLTJ6bTAgMTJINFY4aDE2djEweiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZvbGRlci1zaGFyZWQiPjxwYXRoIGQ9Ik0yMCA2aC04bC0yLTJINGMtMS4xIDAtMS45OS45LTEuOTkgMkwyIDE4YzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWOGMwLTEuMS0uOS0yLTItMnptLTUgM2MxLjEgMCAyIC45IDIgMnMtLjkgMi0yIDItMi0uOS0yLTIgLjktMiAyLTJ6bTQgOGgtOHYtMWMwLTEuMzMgMi42Ny0yIDQtMnM0IC42NyA0IDJ2MXoiPjwvcGF0aD48L2c+CjxnIGlkPSJmb250LWRvd25sb2FkIj48cGF0aCBkPSJNOS45MyAxMy41aDQuMTRMMTIgNy45OHpNMjAgMkg0Yy0xLjEgMC0yIC45LTIgMnYxNmMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjRjMC0xLjEtLjktMi0yLTJ6bS00LjA1IDE2LjVsLTEuMTQtM0g5LjE3bC0xLjEyIDNINS45Nmw1LjExLTEzaDEuODZsNS4xMSAxM2gtMi4wOXoiPjwvcGF0aD48L2c+CjxnIGlkPSJmb3J3YXJkIj48cGF0aCBkPSJNMTIgOFY0bDggOC04IDh2LTRINFY4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZ1bGxzY3JlZW4iPjxwYXRoIGQ9Ik03IDE0SDV2NWg1di0ySDd2LTN6bS0yLTRoMlY3aDNWNUg1djV6bTEyIDdoLTN2Mmg1di01aC0ydjN6TTE0IDV2MmgzdjNoMlY1aC01eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImZ1bGxzY3JlZW4tZXhpdCI+PHBhdGggZD0iTTUgMTZoM3YzaDJ2LTVINXYyem0zLThINXYyaDVWNUg4djN6bTYgMTFoMnYtM2gzdi0yaC01djV6bTItMTFWNWgtMnY1aDVWOGgtM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJnLXRyYW5zbGF0ZSI+PHBhdGggZD0iTTIwIDVoLTkuMTJMMTAgMkg0Yy0xLjEgMC0yIC45LTIgMnYxM2MwIDEuMS45IDIgMiAyaDdsMSAzaDhjMS4xIDAgMi0uOSAyLTJWN2MwLTEuMS0uOS0yLTItMnpNNy4xNyAxNC41OWMtMi4yNSAwLTQuMDktMS44My00LjA5LTQuMDlzMS44My00LjA5IDQuMDktNC4wOWMxLjA0IDAgMS45OS4zNyAyLjc0IDEuMDdsLjA3LjA2LTEuMjMgMS4xOC0uMDYtLjA1Yy0uMjktLjI3LS43OC0uNTktMS41Mi0uNTktMS4zMSAwLTIuMzggMS4wOS0yLjM4IDIuNDJzMS4wNyAyLjQyIDIuMzggMi40MmMxLjM3IDAgMS45Ni0uODcgMi4xMi0xLjQ2SDcuMDhWOS45MWgzLjk1bC4wMS4wN2MuMDQuMjEuMDUuNC4wNS42MSAwIDIuMzUtMS42MSA0LTMuOTIgNHptNi4wMy0xLjcxYy4zMy42Ljc0IDEuMTggMS4xOSAxLjdsLS41NC41My0uNjUtMi4yM3ptLjc3LS43NmgtLjk5bC0uMzEtMS4wNGgzLjk5cy0uMzQgMS4zMS0xLjU2IDIuNzRjLS41Mi0uNjItLjg5LTEuMjMtMS4xMy0xLjd6TTIxIDIwYzAgLjU1LS40NSAxLTEgMWgtN2wyLTItLjgxLTIuNzcuOTItLjkyTDE3Ljc5IDE4bC43My0uNzMtMi43MS0yLjY4Yy45LTEuMDMgMS42LTIuMjUgMS45Mi0zLjUxSDE5di0xLjA0aC0zLjY0VjloLTEuMDR2MS4wNGgtMS45NkwxMS4xOCA2SDIwYy41NSAwIDEgLjQ1IDEgMXYxM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJnYXZlbCI+PHBhdGggZD0iTTEgMjFoMTJ2Mkgxek01LjI0NSA4LjA3bDIuODMtMi44MjcgMTQuMTQgMTQuMTQyLTIuODI4IDIuODI4ek0xMi4zMTcgMWw1LjY1NyA1LjY1Ni0yLjgzIDIuODMtNS42NTQtNS42NnpNMy44MjUgOS40ODVsNS42NTcgNS42NTctMi44MjggMi44MjgtNS42NTctNS42NTd6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZ2VzdHVyZSI+PHBhdGggZD0iTTQuNTkgNi44OWMuNy0uNzEgMS40LTEuMzUgMS43MS0xLjIyLjUuMiAwIDEuMDMtLjMgMS41Mi0uMjUuNDItMi44NiAzLjg5LTIuODYgNi4zMSAwIDEuMjguNDggMi4zNCAxLjM0IDIuOTguNzUuNTYgMS43NC43MyAyLjY0LjQ2IDEuMDctLjMxIDEuOTUtMS40IDMuMDYtMi43NyAxLjIxLTEuNDkgMi44My0zLjQ0IDQuMDgtMy40NCAxLjYzIDAgMS42NSAxLjAxIDEuNzYgMS43OS0zLjc4LjY0LTUuMzggMy42Ny01LjM4IDUuMzcgMCAxLjcgMS40NCAzLjA5IDMuMjEgMy4wOSAxLjYzIDAgNC4yOS0xLjMzIDQuNjktNi4xSDIxdi0yLjVoLTIuNDdjLS4xNS0xLjY1LTEuMDktNC4yLTQuMDMtNC4yLTIuMjUgMC00LjE4IDEuOTEtNC45NCAyLjg0LS41OC43My0yLjA2IDIuNDgtMi4yOSAyLjcyLS4yNS4zLS42OC44NC0xLjExLjg0LS40NSAwLS43Mi0uODMtLjM2LTEuOTIuMzUtMS4wOSAxLjQtMi44NiAxLjg1LTMuNTIuNzgtMS4xNCAxLjMtMS45MiAxLjMtMy4yOEM4Ljk1IDMuNjkgNy4zMSAzIDYuNDQgMyA1LjEyIDMgMy45NyA0IDMuNzIgNC4yNWMtLjM2LjM2LS42Ni42Ni0uODguOTNsMS43NSAxLjcxem05LjI5IDExLjY2Yy0uMzEgMC0uNzQtLjI2LS43NC0uNzIgMC0uNi43My0yLjIgMi44Ny0yLjc2LS4zIDIuNjktMS40MyAzLjQ4LTIuMTMgMy40OHoiPjwvcGF0aD48L2c+CjxnIGlkPSJnZXQtYXBwIj48cGF0aCBkPSJNMTkgOWgtNFYzSDl2Nkg1bDcgNyA3LTd6TTUgMTh2MmgxNHYtMkg1eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImdpZiI+PHBhdGggZD0iTTExLjUgOUgxM3Y2aC0xLjV6TTkgOUg2Yy0uNiAwLTEgLjUtMSAxdjRjMCAuNS40IDEgMSAxaDNjLjYgMCAxLS41IDEtMXYtMkg4LjV2MS41aC0ydi0zSDEwVjEwYzAtLjUtLjQtMS0xLTF6bTEwIDEuNVY5aC00LjV2NkgxNnYtMmgydi0xLjVoLTJ2LTF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iZ3JhZGUiPjxwYXRoIGQ9Ik0xMiAxNy4yN0wxOC4xOCAyMWwtMS42NC03LjAzTDIyIDkuMjRsLTcuMTktLjYxTDEyIDIgOS4xOSA4LjYzIDIgOS4yNGw1LjQ2IDQuNzNMNS44MiAyMXoiPjwvcGF0aD48L2c+CjxnIGlkPSJncm91cC13b3JrIj48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnpNOCAxNy41Yy0xLjM4IDAtMi41LTEuMTItMi41LTIuNXMxLjEyLTIuNSAyLjUtMi41IDIuNSAxLjEyIDIuNSAyLjUtMS4xMiAyLjUtMi41IDIuNXpNOS41IDhjMC0xLjM4IDEuMTItMi41IDIuNS0yLjVzMi41IDEuMTIgMi41IDIuNS0xLjEyIDIuNS0yLjUgMi41UzkuNSA5LjM4IDkuNSA4em02LjUgOS41Yy0xLjM4IDAtMi41LTEuMTItMi41LTIuNXMxLjEyLTIuNSAyLjUtMi41IDIuNSAxLjEyIDIuNSAyLjUtMS4xMiAyLjUtMi41IDIuNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJoZWxwIj48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMSAxN2gtMnYtMmgydjJ6bTIuMDctNy43NWwtLjkuOTJDMTMuNDUgMTIuOSAxMyAxMy41IDEzIDE1aC0ydi0uNWMwLTEuMS40NS0yLjEgMS4xNy0yLjgzbDEuMjQtMS4yNmMuMzctLjM2LjU5LS44Ni41OS0xLjQxIDAtMS4xLS45LTItMi0ycy0yIC45LTIgMkg4YzAtMi4yMSAxLjc5LTQgNC00czQgMS43OSA0IDRjMCAuODgtLjM2IDEuNjgtLjkzIDIuMjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iaGVscC1vdXRsaW5lIj48cGF0aCBkPSJNMTEgMThoMnYtMmgtMnYyem0xLTE2QzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDgtMy41OSA4LTggOHptMC0xNGMtMi4yMSAwLTQgMS43OS00IDRoMmMwLTEuMS45LTIgMi0yczIgLjkgMiAyYzAgMi0zIDEuNzUtMyA1aDJjMC0yLjI1IDMtMi41IDMtNSAwLTIuMjEtMS43OS00LTQtNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJoaWdobGlnaHQtb2ZmIj48cGF0aCBkPSJNMTQuNTkgOEwxMiAxMC41OSA5LjQxIDggOCA5LjQxIDEwLjU5IDEyIDggMTQuNTkgOS40MSAxNiAxMiAxMy40MSAxNC41OSAxNiAxNiAxNC41OSAxMy40MSAxMiAxNiA5LjQxIDE0LjU5IDh6TTEyIDJDNi40NyAyIDIgNi40NyAyIDEyczQuNDcgMTAgMTAgMTAgMTAtNC40NyAxMC0xMFMxNy41MyAyIDEyIDJ6bTAgMThjLTQuNDEgMC04LTMuNTktOC04czMuNTktOCA4LTggOCAzLjU5IDggOC0zLjU5IDgtOCA4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9Imhpc3RvcnkiPjxwYXRoIGQ9Ik0xMyAzYy00Ljk3IDAtOSA0LjAzLTkgOUgxbDMuODkgMy44OS4wNy4xNEw5IDEySDZjMC0zLjg3IDMuMTMtNyA3LTdzNyAzLjEzIDcgNy0zLjEzIDctNyA3Yy0xLjkzIDAtMy42OC0uNzktNC45NC0yLjA2bC0xLjQyIDEuNDJDOC4yNyAxOS45OSAxMC41MSAyMSAxMyAyMWM0Ljk3IDAgOS00LjAzIDktOXMtNC4wMy05LTktOXptLTEgNXY1bDQuMjggMi41NC43Mi0xLjIxLTMuNS0yLjA4VjhIMTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iaG9tZSI+PHBhdGggZD0iTTEwIDIwdi02aDR2Nmg1di04aDNMMTIgMyAyIDEyaDN2OHoiPjwvcGF0aD48L2c+CjxnIGlkPSJob3VyZ2xhc3MtZW1wdHkiPjxwYXRoIGQ9Ik02IDJ2NmguMDFMNiA4LjAxIDEwIDEybC00IDQgLjAxLjAxSDZWMjJoMTJ2LTUuOTloLS4wMUwxOCAxNmwtNC00IDQtMy45OS0uMDEtLjAxSDE4VjJINnptMTAgMTQuNVYyMEg4di0zLjVsNC00IDQgNHptLTQtNWwtNC00VjRoOHYzLjVsLTQgNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJob3VyZ2xhc3MtZnVsbCI+PHBhdGggZD0iTTYgMnY2aC4wMUw2IDguMDEgMTAgMTJsLTQgNCAuMDEuMDFINlYyMmgxMnYtNS45OWgtLjAxTDE4IDE2bC00LTQgNC0zLjk5LS4wMS0uMDFIMThWMkg2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9Imh0dHAiPjxwYXRoIGQ9Ik00LjUgMTFoLTJWOUgxdjZoMS41di0yLjVoMlYxNUg2VjlINC41djJ6bTIuNS0uNWgxLjVWMTVIMTB2LTQuNWgxLjVWOUg3djEuNXptNS41IDBIMTRWMTVoMS41di00LjVIMTdWOWgtNC41djEuNXptOS0xLjVIMTh2NmgxLjV2LTJoMmMuOCAwIDEuNS0uNyAxLjUtMS41di0xYzAtLjgtLjctMS41LTEuNS0xLjV6bTAgMi41aC0ydi0xaDJ2MXoiPjwvcGF0aD48L2c+CjxnIGlkPSJodHRwcyI+PHBhdGggZD0iTTE4IDhoLTFWNmMwLTIuNzYtMi4yNC01LTUtNVM3IDMuMjQgNyA2djJINmMtMS4xIDAtMiAuOS0yIDJ2MTBjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlYxMGMwLTEuMS0uOS0yLTItMnptLTYgOWMtMS4xIDAtMi0uOS0yLTJzLjktMiAyLTIgMiAuOSAyIDItLjkgMi0yIDJ6bTMuMS05SDguOVY2YzAtMS43MSAxLjM5LTMuMSAzLjEtMy4xIDEuNzEgMCAzLjEgMS4zOSAzLjEgMy4xdjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iaW1wb3J0YW50LWRldmljZXMiPjxwYXRoIGQ9Ik0yMyAxMS4wMUwxOCAxMWMtLjU1IDAtMSAuNDUtMSAxdjljMCAuNTUuNDUgMSAxIDFoNWMuNTUgMCAxLS40NSAxLTF2LTljMC0uNTUtLjQ1LS45OS0xLS45OXpNMjMgMjBoLTV2LTdoNXY3ek0yMCAySDJDLjg5IDIgMCAyLjg5IDAgNHYxMmMwIDEuMS44OSAyIDIgMmg3djJIN3YyaDh2LTJoLTJ2LTJoMnYtMkgyVjRoMTh2NWgyVjRjMC0xLjExLS45LTItMi0yem0tOC4wMyA3TDExIDZsLS45NyAzSDdsMi40NyAxLjc2LS45NCAyLjkxIDIuNDctMS44IDIuNDcgMS44LS45NC0yLjkxTDE1IDloLTMuMDN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iaW5ib3giPjxwYXRoIGQ9Ik0xOSAzSDQuOTljLTEuMTEgMC0xLjk4Ljg5LTEuOTggMkwzIDE5YzAgMS4xLjg4IDIgMS45OSAySDE5YzEuMSAwIDItLjkgMi0yVjVjMC0xLjExLS45LTItMi0yem0wIDEyaC00YzAgMS42Ni0xLjM1IDMtMyAzcy0zLTEuMzQtMy0zSDQuOTlWNUgxOXYxMHoiPjwvcGF0aD48L2c+CjxnIGlkPSJpbmRldGVybWluYXRlLWNoZWNrLWJveCI+PHBhdGggZD0iTTE5IDNINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yem0tMiAxMEg3di0yaDEwdjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iaW5mbyI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTEgMTVoLTJ2LTZoMnY2em0wLThoLTJWN2gydjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iaW5mby1vdXRsaW5lIj48cGF0aCBkPSJNMTEgMTdoMnYtNmgtMnY2em0xLTE1QzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDgtMy41OSA4LTggOHpNMTEgOWgyVjdoLTJ2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJpbnB1dCI+PHBhdGggZD0iTTIxIDMuMDFIM2MtMS4xIDAtMiAuOS0yIDJWOWgyVjQuOTloMTh2MTQuMDNIM1YxNUgxdjQuMDFjMCAxLjEuOSAxLjk4IDIgMS45OGgxOGMxLjEgMCAyLS44OCAyLTEuOTh2LTE0YzAtMS4xMS0uOS0yLTItMnpNMTEgMTZsNC00LTQtNHYzSDF2MmgxMHYzeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImludmVydC1jb2xvcnMiPjxwYXRoIGQ9Ik0xNy42NiA3LjkzTDEyIDIuMjcgNi4zNCA3LjkzYy0zLjEyIDMuMTItMy4xMiA4LjE5IDAgMTEuMzFDNy45IDIwLjggOS45NSAyMS41OCAxMiAyMS41OGMyLjA1IDAgNC4xLS43OCA1LjY2LTIuMzQgMy4xMi0zLjEyIDMuMTItOC4xOSAwLTExLjMxek0xMiAxOS41OWMtMS42IDAtMy4xMS0uNjItNC4yNC0xLjc2QzYuNjIgMTYuNjkgNiAxNS4xOSA2IDEzLjU5cy42Mi0zLjExIDEuNzYtNC4yNEwxMiA1LjF2MTQuNDl6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ibGFiZWwiPjxwYXRoIGQ9Ik0xNy42MyA1Ljg0QzE3LjI3IDUuMzMgMTYuNjcgNSAxNiA1TDUgNS4wMUMzLjkgNS4wMSAzIDUuOSAzIDd2MTBjMCAxLjEuOSAxLjk5IDIgMS45OUwxNiAxOWMuNjcgMCAxLjI3LS4zMyAxLjYzLS44NEwyMiAxMmwtNC4zNy02LjE2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImxhYmVsLW91dGxpbmUiPjxwYXRoIGQ9Ik0xNy42MyA1Ljg0QzE3LjI3IDUuMzMgMTYuNjcgNSAxNiA1TDUgNS4wMUMzLjkgNS4wMSAzIDUuOSAzIDd2MTBjMCAxLjEuOSAxLjk5IDIgMS45OUwxNiAxOWMuNjcgMCAxLjI3LS4zMyAxLjYzLS44NEwyMiAxMmwtNC4zNy02LjE2ek0xNiAxN0g1VjdoMTFsMy41NSA1TDE2IDE3eiI+PC9wYXRoPjwvZz4KPGcgaWQ9Imxhbmd1YWdlIj48cGF0aCBkPSJNMTEuOTkgMkM2LjQ3IDIgMiA2LjQ4IDIgMTJzNC40NyAxMCA5Ljk5IDEwQzE3LjUyIDIyIDIyIDE3LjUyIDIyIDEyUzE3LjUyIDIgMTEuOTkgMnptNi45MyA2aC0yLjk1Yy0uMzItMS4yNS0uNzgtMi40NS0xLjM4LTMuNTYgMS44NC42MyAzLjM3IDEuOTEgNC4zMyAzLjU2ek0xMiA0LjA0Yy44MyAxLjIgMS40OCAyLjUzIDEuOTEgMy45NmgtMy44MmMuNDMtMS40MyAxLjA4LTIuNzYgMS45MS0zLjk2ek00LjI2IDE0QzQuMSAxMy4zNiA0IDEyLjY5IDQgMTJzLjEtMS4zNi4yNi0yaDMuMzhjLS4wOC42Ni0uMTQgMS4zMi0uMTQgMiAwIC42OC4wNiAxLjM0LjE0IDJINC4yNnptLjgyIDJoMi45NWMuMzIgMS4yNS43OCAyLjQ1IDEuMzggMy41Ni0xLjg0LS42My0zLjM3LTEuOS00LjMzLTMuNTZ6bTIuOTUtOEg1LjA4Yy45Ni0xLjY2IDIuNDktMi45MyA0LjMzLTMuNTZDOC44MSA1LjU1IDguMzUgNi43NSA4LjAzIDh6TTEyIDE5Ljk2Yy0uODMtMS4yLTEuNDgtMi41My0xLjkxLTMuOTZoMy44MmMtLjQzIDEuNDMtMS4wOCAyLjc2LTEuOTEgMy45NnpNMTQuMzQgMTRIOS42NmMtLjA5LS42Ni0uMTYtMS4zMi0uMTYtMiAwLS42OC4wNy0xLjM1LjE2LTJoNC42OGMuMDkuNjUuMTYgMS4zMi4xNiAyIDAgLjY4LS4wNyAxLjM0LS4xNiAyem0uMjUgNS41NmMuNi0xLjExIDEuMDYtMi4zMSAxLjM4LTMuNTZoMi45NWMtLjk2IDEuNjUtMi40OSAyLjkzLTQuMzMgMy41NnpNMTYuMzYgMTRjLjA4LS42Ni4xNC0xLjMyLjE0LTIgMC0uNjgtLjA2LTEuMzQtLjE0LTJoMy4zOGMuMTYuNjQuMjYgMS4zMS4yNiAycy0uMSAxLjM2LS4yNiAyaC0zLjM4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9Imxhc3QtcGFnZSI+PHBhdGggZD0iTTUuNTkgNy40MUwxMC4xOCAxMmwtNC41OSA0LjU5TDcgMThsNi02LTYtNnpNMTYgNmgydjEyaC0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImxhdW5jaCI+PHBhdGggZD0iTTE5IDE5SDVWNWg3VjNINWMtMS4xMSAwLTIgLjktMiAydjE0YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0ydi03aC0ydjd6TTE0IDN2MmgzLjU5bC05LjgzIDkuODMgMS40MSAxLjQxTDE5IDYuNDFWMTBoMlYzaC03eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImxpZ2h0YnVsYi1vdXRsaW5lIj48cGF0aCBkPSJNOSAyMWMwIC41NS40NSAxIDEgMWg0Yy41NSAwIDEtLjQ1IDEtMXYtMUg5djF6bTMtMTlDOC4xNCAyIDUgNS4xNCA1IDljMCAyLjM4IDEuMTkgNC40NyAzIDUuNzRWMTdjMCAuNTUuNDUgMSAxIDFoNmMuNTUgMCAxLS40NSAxLTF2LTIuMjZjMS44MS0xLjI3IDMtMy4zNiAzLTUuNzQgMC0zLjg2LTMuMTQtNy03LTd6bTIuODUgMTEuMWwtLjg1LjZWMTZoLTR2LTIuM2wtLjg1LS42QzcuOCAxMi4xNiA3IDEwLjYzIDcgOWMwLTIuNzYgMi4yNC01IDUtNXM1IDIuMjQgNSA1YzAgMS42My0uOCAzLjE2LTIuMTUgNC4xeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImxpbmUtc3R5bGUiPjxwYXRoIGQ9Ik0zIDE2aDV2LTJIM3Yyem02LjUgMGg1di0yaC01djJ6bTYuNSAwaDV2LTJoLTV2MnpNMyAyMGgydi0ySDN2MnptNCAwaDJ2LTJIN3Yyem00IDBoMnYtMmgtMnYyem00IDBoMnYtMmgtMnYyem00IDBoMnYtMmgtMnYyek0zIDEyaDh2LTJIM3Yyem0xMCAwaDh2LTJoLTh2MnpNMyA0djRoMThWNEgzeiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImxpbmUtd2VpZ2h0Ij48cGF0aCBkPSJNMyAxN2gxOHYtMkgzdjJ6bTAgM2gxOHYtMUgzdjF6bTAtN2gxOHYtM0gzdjN6bTAtOXY0aDE4VjRIM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJsaW5rIj48cGF0aCBkPSJNMy45IDEyYzAtMS43MSAxLjM5LTMuMSAzLjEtMy4xaDRWN0g3Yy0yLjc2IDAtNSAyLjI0LTUgNXMyLjI0IDUgNSA1aDR2LTEuOUg3Yy0xLjcxIDAtMy4xLTEuMzktMy4xLTMuMXpNOCAxM2g4di0ySDh2MnptOS02aC00djEuOWg0YzEuNzEgMCAzLjEgMS4zOSAzLjEgMy4xcy0xLjM5IDMuMS0zLjEgMy4xaC00VjE3aDRjMi43NiAwIDUtMi4yNCA1LTVzLTIuMjQtNS01LTV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ibGlzdCI+PHBhdGggZD0iTTMgMTNoMnYtMkgzdjJ6bTAgNGgydi0ySDN2MnptMC04aDJWN0gzdjJ6bTQgNGgxNHYtMkg3djJ6bTAgNGgxNHYtMkg3djJ6TTcgN3YyaDE0VjdIN3oiPjwvcGF0aD48L2c+CjxnIGlkPSJsb2NrIj48cGF0aCBkPSJNMTggOGgtMVY2YzAtMi43Ni0yLjI0LTUtNS01UzcgMy4yNCA3IDZ2Mkg2Yy0xLjEgMC0yIC45LTIgMnYxMGMwIDEuMS45IDIgMiAyaDEyYzEuMSAwIDItLjkgMi0yVjEwYzAtMS4xLS45LTItMi0yem0tNiA5Yy0xLjEgMC0yLS45LTItMnMuOS0yIDItMiAyIC45IDIgMi0uOSAyLTIgMnptMy4xLTlIOC45VjZjMC0xLjcxIDEuMzktMy4xIDMuMS0zLjEgMS43MSAwIDMuMSAxLjM5IDMuMSAzLjF2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJsb2NrLW9wZW4iPjxwYXRoIGQ9Ik0xMiAxN2MxLjEgMCAyLS45IDItMnMtLjktMi0yLTItMiAuOS0yIDIgLjkgMiAyIDJ6bTYtOWgtMVY2YzAtMi43Ni0yLjI0LTUtNS01UzcgMy4yNCA3IDZoMS45YzAtMS43MSAxLjM5LTMuMSAzLjEtMy4xIDEuNzEgMCAzLjEgMS4zOSAzLjEgMy4xdjJINmMtMS4xIDAtMiAuOS0yIDJ2MTBjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlYxMGMwLTEuMS0uOS0yLTItMnptMCAxMkg2VjEwaDEydjEweiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImxvY2stb3V0bGluZSI+PHBhdGggZD0iTTEyIDE3YzEuMSAwIDItLjkgMi0ycy0uOS0yLTItMi0yIC45LTIgMiAuOSAyIDIgMnptNi05aC0xVjZjMC0yLjc2LTIuMjQtNS01LTVTNyAzLjI0IDcgNnYySDZjLTEuMSAwLTIgLjktMiAydjEwYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWMTBjMC0xLjEtLjktMi0yLTJ6TTguOSA2YzAtMS43MSAxLjM5LTMuMSAzLjEtMy4xczMuMSAxLjM5IDMuMSAzLjF2Mkg4LjlWNnpNMTggMjBINlYxMGgxMnYxMHoiPjwvcGF0aD48L2c+CjxnIGlkPSJsb3ctcHJpb3JpdHkiPjxwYXRoIGQ9Ik0xNCA1aDh2MmgtOHptMCA1LjVoOHYyaC04em0wIDUuNWg4djJoLTh6TTIgMTEuNUMyIDE1LjA4IDQuOTIgMTggOC41IDE4SDl2MmwzLTMtMy0zdjJoLS41QzYuMDIgMTYgNCAxMy45OCA0IDExLjVTNi4wMiA3IDguNSA3SDEyVjVIOC41QzQuOTIgNSAyIDcuOTIgMiAxMS41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9ImxveWFsdHkiPjxwYXRoIGQ9Ik0yMS40MSAxMS41OGwtOS05QzEyLjA1IDIuMjIgMTEuNTUgMiAxMSAySDRjLTEuMSAwLTIgLjktMiAydjdjMCAuNTUuMjIgMS4wNS41OSAxLjQybDkgOWMuMzYuMzYuODYuNTggMS40MS41OC41NSAwIDEuMDUtLjIyIDEuNDEtLjU5bDctN2MuMzctLjM2LjU5LS44Ni41OS0xLjQxIDAtLjU1LS4yMy0xLjA2LS41OS0xLjQyek01LjUgN0M0LjY3IDcgNCA2LjMzIDQgNS41UzQuNjcgNCA1LjUgNCA3IDQuNjcgNyA1LjUgNi4zMyA3IDUuNSA3em0xMS43NyA4LjI3TDEzIDE5LjU0bC00LjI3LTQuMjdDOC4yOCAxNC44MSA4IDE0LjE5IDggMTMuNWMwLTEuMzggMS4xMi0yLjUgMi41LTIuNS42OSAwIDEuMzIuMjggMS43Ny43NGwuNzMuNzIuNzMtLjczYy40NS0uNDUgMS4wOC0uNzMgMS43Ny0uNzMgMS4zOCAwIDIuNSAxLjEyIDIuNSAyLjUgMCAuNjktLjI4IDEuMzItLjczIDEuNzd6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ibWFpbCI+PHBhdGggZD0iTTIwIDRINGMtMS4xIDAtMS45OS45LTEuOTkgMkwyIDE4YzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uOS0yLTItMnptMCA0bC04IDUtOC01VjZsOCA1IDgtNXYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9Im1hcmt1bnJlYWQiPjxwYXRoIGQ9Ik0yMCA0SDRjLTEuMSAwLTEuOTkuOS0xLjk5IDJMMiAxOGMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjZjMC0xLjEtLjktMi0yLTJ6bTAgNGwtOCA1LTgtNVY2bDggNSA4LTV2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJtYXJrdW5yZWFkLW1haWxib3giPjxwYXRoIGQ9Ik0yMCA2SDEwdjZIOFY0aDZWMEg2djZINGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY4YzAtMS4xLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9Im1lbnUiPjxwYXRoIGQ9Ik0zIDE4aDE4di0ySDN2MnptMC01aDE4di0ySDN2MnptMC03djJoMThWNkgzeiI+PC9wYXRoPjwvZz4KPGcgaWQ9Im1vcmUtaG9yaXoiPjxwYXRoIGQ9Ik02IDEwYy0xLjEgMC0yIC45LTIgMnMuOSAyIDIgMiAyLS45IDItMi0uOS0yLTItMnptMTIgMGMtMS4xIDAtMiAuOS0yIDJzLjkgMiAyIDIgMi0uOSAyLTItLjktMi0yLTJ6bS02IDBjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9Im1vcmUtdmVydCI+PHBhdGggZD0iTTEyIDhjMS4xIDAgMi0uOSAyLTJzLS45LTItMi0yLTIgLjktMiAyIC45IDIgMiAyem0wIDJjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yem0wIDZjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9Im1vdG9yY3ljbGUiPjxwYXRoIGQ9Ik0xOS40NCA5LjAzTDE1LjQxIDVIMTF2MmgzLjU5bDIgMkg1Yy0yLjggMC01IDIuMi01IDVzMi4yIDUgNSA1YzIuNDYgMCA0LjQ1LTEuNjkgNC45LTRoMS42NWwyLjc3LTIuNzdjLS4yMS41NC0uMzIgMS4xNC0uMzIgMS43NyAwIDIuOCAyLjIgNSA1IDVzNS0yLjIgNS01YzAtMi42NS0xLjk3LTQuNzctNC41Ni00Ljk3ek03LjgyIDE1QzcuNCAxNi4xNSA2LjI4IDE3IDUgMTdjLTEuNjMgMC0zLTEuMzctMy0zczEuMzctMyAzLTNjMS4yOCAwIDIuNC44NSAyLjgyIDJINXYyaDIuODJ6TTE5IDE3Yy0xLjY2IDAtMy0xLjM0LTMtM3MxLjM0LTMgMy0zIDMgMS4zNCAzIDMtMS4zNCAzLTMgM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJtb3ZlLXRvLWluYm94Ij48cGF0aCBkPSJNMTkgM0g0Ljk5Yy0xLjExIDAtMS45OC45LTEuOTggMkwzIDE5YzAgMS4xLjg4IDIgMS45OSAySDE5YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTJoLTRjMCAxLjY2LTEuMzUgMy0zIDNzLTMtMS4zNC0zLTNINC45OVY1SDE5djEwem0tMy01aC0yVjdoLTR2M0g4bDQgNCA0LTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ibmV4dC13ZWVrIj48cGF0aCBkPSJNMjAgN2gtNFY1YzAtLjU1LS4yMi0xLjA1LS41OS0xLjQxQzE1LjA1IDMuMjIgMTQuNTUgMyAxNCAzaC00Yy0xLjEgMC0yIC45LTIgMnYySDRjLTEuMSAwLTIgLjktMiAydjExYzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWOWMwLTEuMS0uOS0yLTItMnpNMTAgNWg0djJoLTRWNXptMSAxMy41bC0xLTEgMy0zLTMtMyAxLTEgNCA0LTQgNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJub3RlLWFkZCI+PHBhdGggZD0iTTE0IDJINmMtMS4xIDAtMS45OS45LTEuOTkgMkw0IDIwYzAgMS4xLjg5IDIgMS45OSAySDE4YzEuMSAwIDItLjkgMi0yVjhsLTYtNnptMiAxNGgtM3YzaC0ydi0zSDh2LTJoM3YtM2gydjNoM3Yyem0tMy03VjMuNUwxOC41IDlIMTN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ib2ZmbGluZS1waW4iPjxwYXRoIGQ9Ik0xMiAyQzYuNSAyIDIgNi41IDIgMTJzNC41IDEwIDEwIDEwIDEwLTQuNSAxMC0xMFMxNy41IDIgMTIgMnptNSAxNkg3di0yaDEwdjJ6bS02LjctNEw3IDEwLjdsMS40LTEuNCAxLjkgMS45IDUuMy01LjNMMTcgNy4zIDEwLjMgMTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ib3BhY2l0eSI+PHBhdGggZD0iTTE3LjY2IDhMMTIgMi4zNSA2LjM0IDhDNC43OCA5LjU2IDQgMTEuNjQgNCAxMy42NHMuNzggNC4xMSAyLjM0IDUuNjcgMy42MSAyLjM1IDUuNjYgMi4zNSA0LjEtLjc5IDUuNjYtMi4zNVMyMCAxNS42NCAyMCAxMy42NCAxOS4yMiA5LjU2IDE3LjY2IDh6TTYgMTRjLjAxLTIgLjYyLTMuMjcgMS43Ni00LjRMMTIgNS4yN2w0LjI0IDQuMzhDMTcuMzggMTAuNzcgMTcuOTkgMTIgMTggMTRINnoiPjwvcGF0aD48L2c+CjxnIGlkPSJvcGVuLWluLWJyb3dzZXIiPjxwYXRoIGQ9Ik0xOSA0SDVjLTEuMTEgMC0yIC45LTIgMnYxMmMwIDEuMS44OSAyIDIgMmg0di0ySDVWOGgxNHYxMGgtNHYyaDRjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uODktMi0yLTJ6bS03IDZsLTQgNGgzdjZoMnYtNmgzbC00LTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ib3Blbi1pbi1uZXciPjxwYXRoIGQ9Ik0xOSAxOUg1VjVoN1YzSDVjLTEuMTEgMC0yIC45LTIgMnYxNGMwIDEuMS44OSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMnYtN2gtMnY3ek0xNCAzdjJoMy41OWwtOS44MyA5LjgzIDEuNDEgMS40MUwxOSA2LjQxVjEwaDJWM2gtN3oiPjwvcGF0aD48L2c+CjxnIGlkPSJvcGVuLXdpdGgiPjxwYXRoIGQ9Ik0xMCA5aDRWNmgzbC01LTUtNSA1aDN2M3ptLTEgMUg2VjdsLTUgNSA1IDV2LTNoM3YtNHptMTQgMmwtNS01djNoLTN2NGgzdjNsNS01em0tOSAzaC00djNIN2w1IDUgNS01aC0zdi0zeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBhZ2V2aWV3Ij48cGF0aCBkPSJNMTEuNSA5QzEwLjEyIDkgOSAxMC4xMiA5IDExLjVzMS4xMiAyLjUgMi41IDIuNSAyLjUtMS4xMiAyLjUtMi41UzEyLjg4IDkgMTEuNSA5ek0yMCA0SDRjLTEuMSAwLTIgLjktMiAydjEyYzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uOS0yLTItMnptLTMuMjEgMTQuMjFsLTIuOTEtMi45MWMtLjY5LjQ0LTEuNTEuNy0yLjM5LjdDOS4wMSAxNiA3IDEzLjk5IDcgMTEuNVM5LjAxIDcgMTEuNSA3IDE2IDkuMDEgMTYgMTEuNWMwIC44OC0uMjYgMS42OS0uNyAyLjM5bDIuOTEgMi45LTEuNDIgMS40MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJwYW4tdG9vbCI+PHBhdGggZD0iTTIzIDUuNVYyMGMwIDIuMi0xLjggNC00IDRoLTcuM2MtMS4wOCAwLTIuMS0uNDMtMi44NS0xLjE5TDEgMTQuODNzMS4yNi0xLjIzIDEuMy0xLjI1Yy4yMi0uMTkuNDktLjI5Ljc5LS4yOS4yMiAwIC40Mi4wNi42LjE2LjA0LjAxIDQuMzEgMi40NiA0LjMxIDIuNDZWNGMwLS44My42Ny0xLjUgMS41LTEuNVMxMSAzLjE3IDExIDR2N2gxVjEuNWMwLS44My42Ny0xLjUgMS41LTEuNVMxNSAuNjcgMTUgMS41VjExaDFWMi41YzAtLjgzLjY3LTEuNSAxLjUtMS41czEuNS42NyAxLjUgMS41VjExaDFWNS41YzAtLjgzLjY3LTEuNSAxLjUtMS41czEuNS42NyAxLjUgMS41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBheW1lbnQiPjxwYXRoIGQ9Ik0yMCA0SDRjLTEuMTEgMC0xLjk5Ljg5LTEuOTkgMkwyIDE4YzAgMS4xMS44OSAyIDIgMmgxNmMxLjExIDAgMi0uODkgMi0yVjZjMC0xLjExLS44OS0yLTItMnptMCAxNEg0di02aDE2djZ6bTAtMTBINFY2aDE2djJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGVybS1jYW1lcmEtbWljIj48cGF0aCBkPSJNMjAgNWgtMy4xN0wxNSAzSDlMNy4xNyA1SDRjLTEuMSAwLTIgLjktMiAydjEyYzAgMS4xLjkgMiAyIDJoN3YtMi4wOWMtMi44My0uNDgtNS0yLjk0LTUtNS45MWgyYzAgMi4yMSAxLjc5IDQgNCA0czQtMS43OSA0LTRoMmMwIDIuOTctMi4xNyA1LjQzLTUgNS45MVYyMWg3YzEuMSAwIDItLjkgMi0yVjdjMC0xLjEtLjktMi0yLTJ6bS02IDhjMCAxLjEtLjkgMi0yIDJzLTItLjktMi0yVjljMC0xLjEuOS0yIDItMnMyIC45IDIgMnY0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBlcm0tY29udGFjdC1jYWxlbmRhciI+PHBhdGggZD0iTTE5IDNoLTFWMWgtMnYySDhWMUg2djJINWMtMS4xMSAwLTIgLjktMiAydjE0YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bS03IDNjMS42NiAwIDMgMS4zNCAzIDNzLTEuMzQgMy0zIDMtMy0xLjM0LTMtMyAxLjM0LTMgMy0zem02IDEySDZ2LTFjMC0yIDQtMy4xIDYtMy4xczYgMS4xIDYgMy4xdjF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGVybS1kYXRhLXNldHRpbmciPjxwYXRoIGQ9Ik0xOC45OSAxMS41Yy4zNCAwIC42Ny4wMyAxIC4wN0wyMCAwIDAgMjBoMTEuNTZjLS4wNC0uMzMtLjA3LS42Ni0uMDctMSAwLTQuMTQgMy4zNi03LjUgNy41LTcuNXptMy43MSA3Ljk5Yy4wMi0uMTYuMDQtLjMyLjA0LS40OSAwLS4xNy0uMDEtLjMzLS4wNC0uNDlsMS4wNi0uODNjLjA5LS4wOC4xMi0uMjEuMDYtLjMybC0xLTEuNzNjLS4wNi0uMTEtLjE5LS4xNS0uMzEtLjExbC0xLjI0LjVjLS4yNi0uMi0uNTQtLjM3LS44NS0uNDlsLS4xOS0xLjMyYy0uMDEtLjEyLS4xMi0uMjEtLjI0LS4yMWgtMmMtLjEyIDAtLjIzLjA5LS4yNS4yMWwtLjE5IDEuMzJjLS4zLjEzLS41OS4yOS0uODUuNDlsLTEuMjQtLjVjLS4xMS0uMDQtLjI0IDAtLjMxLjExbC0xIDEuNzNjLS4wNi4xMS0uMDQuMjQuMDYuMzJsMS4wNi44M2MtLjAyLjE2LS4wMy4zMi0uMDMuNDkgMCAuMTcuMDEuMzMuMDMuNDlsLTEuMDYuODNjLS4wOS4wOC0uMTIuMjEtLjA2LjMybDEgMS43M2MuMDYuMTEuMTkuMTUuMzEuMTFsMS4yNC0uNWMuMjYuMi41NC4zNy44NS40OWwuMTkgMS4zMmMuMDIuMTIuMTIuMjEuMjUuMjFoMmMuMTIgMCAuMjMtLjA5LjI1LS4yMWwuMTktMS4zMmMuMy0uMTMuNTktLjI5Ljg0LS40OWwxLjI1LjVjLjExLjA0LjI0IDAgLjMxLS4xMWwxLTEuNzNjLjA2LS4xMS4wMy0uMjQtLjA2LS4zMmwtMS4wNy0uODN6bS0zLjcxIDEuMDFjLS44MyAwLTEuNS0uNjctMS41LTEuNXMuNjctMS41IDEuNS0xLjUgMS41LjY3IDEuNSAxLjUtLjY3IDEuNS0xLjUgMS41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBlcm0tZGV2aWNlLWluZm9ybWF0aW9uIj48cGF0aCBkPSJNMTMgN2gtMnYyaDJWN3ptMCA0aC0ydjZoMnYtNnptNC05Ljk5TDcgMWMtMS4xIDAtMiAuOS0yIDJ2MThjMCAxLjEuOSAyIDIgMmgxMGMxLjEgMCAyLS45IDItMlYzYzAtMS4xLS45LTEuOTktMi0xLjk5ek0xNyAxOUg3VjVoMTB2MTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGVybS1pZGVudGl0eSI+PHBhdGggZD0iTTEyIDUuOWMxLjE2IDAgMi4xLjk0IDIuMSAyLjFzLS45NCAyLjEtMi4xIDIuMVM5LjkgOS4xNiA5LjkgOHMuOTQtMi4xIDIuMS0yLjFtMCA5YzIuOTcgMCA2LjEgMS40NiA2LjEgMi4xdjEuMUg1LjlWMTdjMC0uNjQgMy4xMy0yLjEgNi4xLTIuMU0xMiA0QzkuNzkgNCA4IDUuNzkgOCA4czEuNzkgNCA0IDQgNC0xLjc5IDQtNC0xLjc5LTQtNC00em0wIDljLTIuNjcgMC04IDEuMzQtOCA0djNoMTZ2LTNjMC0yLjY2LTUuMzMtNC04LTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGVybS1tZWRpYSI+PHBhdGggZD0iTTIgNkgwdjVoLjAxTDAgMjBjMCAxLjEuOSAyIDIgMmgxOHYtMkgyVjZ6bTIwLTJoLThsLTItMkg2Yy0xLjEgMC0xLjk5LjktMS45OSAyTDQgMTZjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY2YzAtMS4xLS45LTItMi0yek03IDE1bDQuNS02IDMuNSA0LjUxIDIuNS0zLjAxTDIxIDE1SDd6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGVybS1waG9uZS1tc2ciPjxwYXRoIGQ9Ik0yMCAxNS41Yy0xLjI1IDAtMi40NS0uMi0zLjU3LS41Ny0uMzUtLjExLS43NC0uMDMtMS4wMi4yNGwtMi4yIDIuMmMtMi44My0xLjQ0LTUuMTUtMy43NS02LjU5LTYuNThsMi4yLTIuMjFjLjI4LS4yNy4zNi0uNjYuMjUtMS4wMUM4LjcgNi40NSA4LjUgNS4yNSA4LjUgNGMwLS41NS0uNDUtMS0xLTFINGMtLjU1IDAtMSAuNDUtMSAxIDAgOS4zOSA3LjYxIDE3IDE3IDE3IC41NSAwIDEtLjQ1IDEtMXYtMy41YzAtLjU1LS40NS0xLTEtMXpNMTIgM3YxMGwzLTNoNlYzaC05eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBlcm0tc2Nhbi13aWZpIj48cGF0aCBkPSJNMTIgM0M2Ljk1IDMgMy4xNSA0Ljg1IDAgNy4yM0wxMiAyMiAyNCA3LjI1QzIwLjg1IDQuODcgMTcuMDUgMyAxMiAzem0xIDEzaC0ydi02aDJ2NnptLTItOFY2aDJ2MmgtMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJwZXRzIj48Y2lyY2xlIGN4PSI0LjUiIGN5PSI5LjUiIHI9IjIuNSI+PC9jaXJjbGU+PGNpcmNsZSBjeD0iOSIgY3k9IjUuNSIgcj0iMi41Ij48L2NpcmNsZT48Y2lyY2xlIGN4PSIxNSIgY3k9IjUuNSIgcj0iMi41Ij48L2NpcmNsZT48Y2lyY2xlIGN4PSIxOS41IiBjeT0iOS41IiByPSIyLjUiPjwvY2lyY2xlPjxwYXRoIGQ9Ik0xNy4zNCAxNC44NmMtLjg3LTEuMDItMS42LTEuODktMi40OC0yLjkxLS40Ni0uNTQtMS4wNS0xLjA4LTEuNzUtMS4zMi0uMTEtLjA0LS4yMi0uMDctLjMzLS4wOS0uMjUtLjA0LS41Mi0uMDQtLjc4LS4wNHMtLjUzIDAtLjc5LjA1Yy0uMTEuMDItLjIyLjA1LS4zMy4wOS0uNy4yNC0xLjI4Ljc4LTEuNzUgMS4zMi0uODcgMS4wMi0xLjYgMS44OS0yLjQ4IDIuOTEtMS4zMSAxLjMxLTIuOTIgMi43Ni0yLjYyIDQuNzkuMjkgMS4wMiAxLjAyIDIuMDMgMi4zMyAyLjMyLjczLjE1IDMuMDYtLjQ0IDUuNTQtLjQ0aC4xOGMyLjQ4IDAgNC44MS41OCA1LjU0LjQ0IDEuMzEtLjI5IDIuMDQtMS4zMSAyLjMzLTIuMzIuMzEtMi4wNC0xLjMtMy40OS0yLjYxLTQuOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJwaWN0dXJlLWluLXBpY3R1cmUiPjxwYXRoIGQ9Ik0xOSA3aC04djZoOFY3em0yLTRIM2MtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAxLjk4IDIgMS45OGgxOGMxLjEgMCAyLS44OCAyLTEuOThWNWMwLTEuMS0uOS0yLTItMnptMCAxNi4wMUgzVjQuOThoMTh2MTQuMDN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icGljdHVyZS1pbi1waWN0dXJlLWFsdCI+PHBhdGggZD0iTTE5IDExaC04djZoOHYtNnptNCA4VjQuOThDMjMgMy44OCAyMi4xIDMgMjEgM0gzYy0xLjEgMC0yIC44OC0yIDEuOThWMTljMCAxLjEuOSAyIDIgMmgxOGMxLjEgMCAyLS45IDItMnptLTIgLjAySDNWNC45N2gxOHYxNC4wNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJwbGF5LWZvci13b3JrIj48cGF0aCBkPSJNMTEgNXY1LjU5SDcuNWw0LjUgNC41IDQuNS00LjVIMTNWNWgtMnptLTUgOWMwIDMuMzEgMi42OSA2IDYgNnM2LTIuNjkgNi02aC0yYzAgMi4yMS0xLjc5IDQtNCA0cy00LTEuNzktNC00SDZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icG9seW1lciI+PHBhdGggZD0iTTE5IDRoLTRMNy4xMSAxNi42MyA0LjUgMTIgOSA0SDVMLjUgMTIgNSAyMGg0bDcuODktMTIuNjNMMTkuNSAxMiAxNSAyMGg0bDQuNS04eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InBvd2VyLXNldHRpbmdzLW5ldyI+PHBhdGggZD0iTTEzIDNoLTJ2MTBoMlYzem00LjgzIDIuMTdsLTEuNDIgMS40MkMxNy45OSA3Ljg2IDE5IDkuODEgMTkgMTJjMCAzLjg3LTMuMTMgNy03IDdzLTctMy4xMy03LTdjMC0yLjE5IDEuMDEtNC4xNCAyLjU4LTUuNDJMNi4xNyA1LjE3QzQuMjMgNi44MiAzIDkuMjYgMyAxMmMwIDQuOTcgNC4wMyA5IDkgOXM5LTQuMDMgOS05YzAtMi43NC0xLjIzLTUuMTgtMy4xNy02LjgzeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InByZWduYW50LXdvbWFuIj48cGF0aCBkPSJNOSA0YzAtMS4xMS44OS0yIDItMnMyIC44OSAyIDItLjg5IDItMiAyLTItLjg5LTItMnptNyA5Yy0uMDEtMS4zNC0uODMtMi41MS0yLTMgMC0xLjY2LTEuMzQtMy0zLTNzLTMgMS4zNC0zIDN2N2gydjVoM3YtNWgzdi00eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InByaW50Ij48cGF0aCBkPSJNMTkgOEg1Yy0xLjY2IDAtMyAxLjM0LTMgM3Y2aDR2NGgxMnYtNGg0di02YzAtMS42Ni0xLjM0LTMtMy0zem0tMyAxMUg4di01aDh2NXptMy03Yy0uNTUgMC0xLS40NS0xLTFzLjQ1LTEgMS0xIDEgLjQ1IDEgMS0uNDUgMS0xIDF6bS0xLTlINnY0aDEyVjN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icXVlcnktYnVpbGRlciI+PHBhdGggZD0iTTExLjk5IDJDNi40NyAyIDIgNi40OCAyIDEyczQuNDcgMTAgOS45OSAxMEMxNy41MiAyMiAyMiAxNy41MiAyMiAxMlMxNy41MiAyIDExLjk5IDJ6TTEyIDIwYy00LjQyIDAtOC0zLjU4LTgtOHMzLjU4LTggOC04IDggMy41OCA4IDgtMy41OCA4LTggOHptLjUtMTNIMTF2Nmw1LjI1IDMuMTUuNzUtMS4yMy00LjUtMi42N3oiPjwvcGF0aD48L2c+CjxnIGlkPSJxdWVzdGlvbi1hbnN3ZXIiPjxwYXRoIGQ9Ik0yMSA2aC0ydjlINnYyYzAgLjU1LjQ1IDEgMSAxaDExbDQgNFY3YzAtLjU1LS40NS0xLTEtMXptLTQgNlYzYzAtLjU1LS40NS0xLTEtMUgzYy0uNTUgMC0xIC40NS0xIDF2MTRsNC00aDEwYy41NSAwIDEtLjQ1IDEtMXoiPjwvcGF0aD48L2c+CjxnIGlkPSJyYWRpby1idXR0b24tY2hlY2tlZCI+PHBhdGggZD0iTTEyIDdjLTIuNzYgMC01IDIuMjQtNSA1czIuMjQgNSA1IDUgNS0yLjI0IDUtNS0yLjI0LTUtNS01em0wLTVDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTAgMThjLTQuNDIgMC04LTMuNTgtOC04czMuNTgtOCA4LTggOCAzLjU4IDggOC0zLjU4IDgtOCA4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJhZGlvLWJ1dHRvbi11bmNoZWNrZWQiPjxwYXRoIGQ9Ik0xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyem0wIDE4Yy00LjQyIDAtOC0zLjU4LTgtOHMzLjU4LTggOC04IDggMy41OCA4IDgtMy41OCA4LTggOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJyZWNlaXB0Ij48cGF0aCBkPSJNMTggMTdINnYtMmgxMnYyem0wLTRINnYtMmgxMnYyem0wLTRINlY3aDEydjJ6TTMgMjJsMS41LTEuNUw2IDIybDEuNS0xLjVMOSAyMmwxLjUtMS41TDEyIDIybDEuNS0xLjVMMTUgMjJsMS41LTEuNUwxOCAyMmwxLjUtMS41TDIxIDIyVjJsLTEuNSAxLjVMMTggMmwtMS41IDEuNUwxNSAybC0xLjUgMS41TDEyIDJsLTEuNSAxLjVMOSAyIDcuNSAzLjUgNiAyIDQuNSAzLjUgMyAydjIweiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJlY29yZC12b2ljZS1vdmVyIj48Y2lyY2xlIGN4PSI5IiBjeT0iOSIgcj0iNCI+PC9jaXJjbGU+PHBhdGggZD0iTTkgMTVjLTIuNjcgMC04IDEuMzQtOCA0djJoMTZ2LTJjMC0yLjY2LTUuMzMtNC04LTR6bTcuNzYtOS42NGwtMS42OCAxLjY5Yy44NCAxLjE4Ljg0IDIuNzEgMCAzLjg5bDEuNjggMS42OWMyLjAyLTIuMDIgMi4wMi01LjA3IDAtNy4yN3pNMjAuMDcgMmwtMS42MyAxLjYzYzIuNzcgMy4wMiAyLjc3IDcuNTYgMCAxMC43NEwyMC4wNyAxNmMzLjktMy44OSAzLjkxLTkuOTUgMC0xNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJyZWRlZW0iPjxwYXRoIGQ9Ik0yMCA2aC0yLjE4Yy4xMS0uMzEuMTgtLjY1LjE4LTEgMC0xLjY2LTEuMzQtMy0zLTMtMS4wNSAwLTEuOTYuNTQtMi41IDEuMzVsLS41LjY3LS41LS42OEMxMC45NiAyLjU0IDEwLjA1IDIgOSAyIDcuMzQgMiA2IDMuMzQgNiA1YzAgLjM1LjA3LjY5LjE4IDFINGMtMS4xMSAwLTEuOTkuODktMS45OSAyTDIgMTljMCAxLjExLjg5IDIgMiAyaDE2YzEuMTEgMCAyLS44OSAyLTJWOGMwLTEuMTEtLjg5LTItMi0yem0tNS0yYy41NSAwIDEgLjQ1IDEgMXMtLjQ1IDEtMSAxLTEtLjQ1LTEtMSAuNDUtMSAxLTF6TTkgNGMuNTUgMCAxIC40NSAxIDFzLS40NSAxLTEgMS0xLS40NS0xLTEgLjQ1LTEgMS0xem0xMSAxNUg0di0yaDE2djJ6bTAtNUg0VjhoNS4wOEw3IDEwLjgzIDguNjIgMTIgMTEgOC43NmwxLTEuMzYgMSAxLjM2TDE1LjM4IDEyIDE3IDEwLjgzIDE0LjkyIDhIMjB2NnoiPjwvcGF0aD48L2c+CjxnIGlkPSJyZWRvIj48cGF0aCBkPSJNMTguNCAxMC42QzE2LjU1IDguOTkgMTQuMTUgOCAxMS41IDhjLTQuNjUgMC04LjU4IDMuMDMtOS45NiA3LjIyTDMuOSAxNmMxLjA1LTMuMTkgNC4wNS01LjUgNy42LTUuNSAxLjk1IDAgMy43My43MiA1LjEyIDEuODhMMTMgMTZoOVY3bC0zLjYgMy42eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJlZnJlc2giPjxwYXRoIGQ9Ik0xNy42NSA2LjM1QzE2LjIgNC45IDE0LjIxIDQgMTIgNGMtNC40MiAwLTcuOTkgMy41OC03Ljk5IDhzMy41NyA4IDcuOTkgOGMzLjczIDAgNi44NC0yLjU1IDcuNzMtNmgtMi4wOGMtLjgyIDIuMzMtMy4wNCA0LTUuNjUgNC0zLjMxIDAtNi0yLjY5LTYtNnMyLjY5LTYgNi02YzEuNjYgMCAzLjE0LjY5IDQuMjIgMS43OEwxMyAxMWg3VjRsLTIuMzUgMi4zNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJyZW1vdmUiPjxwYXRoIGQ9Ik0xOSAxM0g1di0yaDE0djJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icmVtb3ZlLWNpcmNsZSI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTUgMTFIN3YtMmgxMHYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJlbW92ZS1jaXJjbGUtb3V0bGluZSI+PHBhdGggZD0iTTcgMTF2MmgxMHYtMkg3em01LTlDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTAgMThjLTQuNDEgMC04LTMuNTktOC04czMuNTktOCA4LTggOCAzLjU5IDggOC0zLjU5IDgtOCA4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJlbW92ZS1zaG9wcGluZy1jYXJ0Ij48cGF0aCBkPSJNMjIuNzMgMjIuNzNMMi43NyAyLjc3IDIgMmwtLjczLS43M0wwIDIuNTRsNC4zOSA0LjM5IDIuMjEgNC42Ni0xLjM1IDIuNDVjLS4xNi4yOC0uMjUuNjEtLjI1Ljk2IDAgMS4xLjkgMiAyIDJoNy40NmwxLjM4IDEuMzhjLS41LjM2LS44My45NS0uODMgMS42MiAwIDEuMS44OSAyIDEuOTkgMiAuNjcgMCAxLjI2LS4zMyAxLjYyLS44NEwyMS40NiAyNGwxLjI3LTEuMjd6TTcuNDIgMTVjLS4xNCAwLS4yNS0uMTEtLjI1LS4yNWwuMDMtLjEyLjktMS42M2gyLjM2bDIgMkg3LjQyem04LjEzLTJjLjc1IDAgMS40MS0uNDEgMS43NS0xLjAzbDMuNTgtNi40OWMuMDgtLjE0LjEyLS4zMS4xMi0uNDggMC0uNTUtLjQ1LTEtMS0xSDYuNTRsOS4wMSA5ek03IDE4Yy0xLjEgMC0xLjk5LjktMS45OSAyUzUuOSAyMiA3IDIyczItLjkgMi0yLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJlb3JkZXIiPjxwYXRoIGQ9Ik0zIDE1aDE4di0ySDN2MnptMCA0aDE4di0ySDN2MnptMC04aDE4VjlIM3Yyem0wLTZ2MmgxOFY1SDN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icmVwbHkiPjxwYXRoIGQ9Ik0xMCA5VjVsLTcgNyA3IDd2LTQuMWM1IDAgOC41IDEuNiAxMSA1LjEtMS01LTQtMTAtMTEtMTF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icmVwbHktYWxsIj48cGF0aCBkPSJNNyA4VjVsLTcgNyA3IDd2LTNsLTQtNCA0LTR6bTYgMVY1bC03IDcgNyA3di00LjFjNSAwIDguNSAxLjYgMTEgNS4xLTEtNS00LTEwLTExLTExeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJlcG9ydCI+PHBhdGggZD0iTTE1LjczIDNIOC4yN0wzIDguMjd2Ny40Nkw4LjI3IDIxaDcuNDZMMjEgMTUuNzNWOC4yN0wxNS43MyAzek0xMiAxNy4zYy0uNzIgMC0xLjMtLjU4LTEuMy0xLjMgMC0uNzIuNTgtMS4zIDEuMy0xLjMuNzIgMCAxLjMuNTggMS4zIDEuMyAwIC43Mi0uNTggMS4zLTEuMyAxLjN6bTEtNC4zaC0yVjdoMnY2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJlcG9ydC1wcm9ibGVtIj48cGF0aCBkPSJNMSAyMWgyMkwxMiAyIDEgMjF6bTEyLTNoLTJ2LTJoMnYyem0wLTRoLTJ2LTRoMnY0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJlc3RvcmUiPjxwYXRoIGQ9Ik0xMyAzYy00Ljk3IDAtOSA0LjAzLTkgOUgxbDMuODkgMy44OS4wNy4xNEw5IDEySDZjMC0zLjg3IDMuMTMtNyA3LTdzNyAzLjEzIDcgNy0zLjEzIDctNyA3Yy0xLjkzIDAtMy42OC0uNzktNC45NC0yLjA2bC0xLjQyIDEuNDJDOC4yNyAxOS45OSAxMC41MSAyMSAxMyAyMWM0Ljk3IDAgOS00LjAzIDktOXMtNC4wMy05LTktOXptLTEgNXY1bDQuMjggMi41NC43Mi0xLjIxLTMuNS0yLjA4VjhIMTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icmVzdG9yZS1wYWdlIj48cGF0aCBkPSJNMTQgMkg2Yy0xLjEgMC0xLjk5LjktMS45OSAyTDQgMjBjMCAxLjEuODkgMiAxLjk5IDJIMThjMS4xIDAgMi0uOSAyLTJWOGwtNi02em0tMiAxNmMtMi4wNSAwLTMuODEtMS4yNC00LjU4LTNoMS43MWMuNjMuOSAxLjY4IDEuNSAyLjg3IDEuNSAxLjkzIDAgMy41LTEuNTcgMy41LTMuNVMxMy45MyA5LjUgMTIgOS41Yy0xLjM1IDAtMi41Mi43OC0zLjEgMS45bDEuNiAxLjZoLTRWOWwxLjMgMS4zQzguNjkgOC45MiAxMC4yMyA4IDEyIDhjMi43NiAwIDUgMi4yNCA1IDVzLTIuMjQgNS01IDV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icm9vbSI+PHBhdGggZD0iTTEyIDJDOC4xMyAyIDUgNS4xMyA1IDljMCA1LjI1IDcgMTMgNyAxM3M3LTcuNzUgNy0xM2MwLTMuODctMy4xMy03LTctN3ptMCA5LjVjLTEuMzggMC0yLjUtMS4xMi0yLjUtMi41czEuMTItMi41IDIuNS0yLjUgMi41IDEuMTIgMi41IDIuNS0xLjEyIDIuNS0yLjUgMi41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InJvdW5kZWQtY29ybmVyIj48cGF0aCBkPSJNMTkgMTloMnYyaC0ydi0yem0wLTJoMnYtMmgtMnYyek0zIDEzaDJ2LTJIM3Yyem0wIDRoMnYtMkgzdjJ6bTAtOGgyVjdIM3Yyem0wLTRoMlYzSDN2MnptNCAwaDJWM0g3djJ6bTggMTZoMnYtMmgtMnYyem0tNCAwaDJ2LTJoLTJ2MnptNCAwaDJ2LTJoLTJ2MnptLTggMGgydi0ySDd2MnptLTQgMGgydi0ySDN2MnpNMjEgOGMwLTIuNzYtMi4yNC01LTUtNWgtNXYyaDVjMS42NSAwIDMgMS4zNSAzIDN2NWgyVjh6Ij48L3BhdGg+PC9nPgo8ZyBpZD0icm93aW5nIj48cGF0aCBkPSJNOC41IDE0LjVMNCAxOWwxLjUgMS41TDkgMTdoMmwtMi41LTIuNXpNMTUgMWMtMS4xIDAtMiAuOS0yIDJzLjkgMiAyIDIgMi0uOSAyLTItLjktMi0yLTJ6bTYgMjAuMDFMMTggMjRsLTIuOTktMy4wMVYxOS41bC03LjEtNy4wOWMtLjMxLjA1LS42MS4wNy0uOTEuMDd2LTIuMTZjMS42Ni4wMyAzLjYxLS44NyA0LjY3LTIuMDRsMS40LTEuNTVjLjE5LS4yMS40My0uMzguNjktLjUuMjktLjE0LjYyLS4yMy45Ni0uMjNoLjAzQzE1Ljk5IDYuMDEgMTcgNy4wMiAxNyA4LjI2djUuNzVjMCAuODQtLjM1IDEuNjEtLjkyIDIuMTZsLTMuNTgtMy41OHYtMi4yN2MtLjYzLjUyLTEuNDMgMS4wMi0yLjI5IDEuMzlMMTYuNSAxOEgxOGwzIDMuMDF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2F2ZSI+PHBhdGggZD0iTTE3IDNINWMtMS4xMSAwLTIgLjktMiAydjE0YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjdsLTQtNHptLTUgMTZjLTEuNjYgMC0zLTEuMzQtMy0zczEuMzQtMyAzLTMgMyAxLjM0IDMgMy0xLjM0IDMtMyAzem0zLTEwSDVWNWgxMHY0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InNjaGVkdWxlIj48cGF0aCBkPSJNMTEuOTkgMkM2LjQ3IDIgMiA2LjQ4IDIgMTJzNC40NyAxMCA5Ljk5IDEwQzE3LjUyIDIyIDIyIDE3LjUyIDIyIDEyUzE3LjUyIDIgMTEuOTkgMnpNMTIgMjBjLTQuNDIgMC04LTMuNTgtOC04czMuNTgtOCA4LTggOCAzLjU4IDggOC0zLjU4IDgtOCA4em0uNS0xM0gxMXY2bDUuMjUgMy4xNS43NS0xLjIzLTQuNS0yLjY3eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InNlYXJjaCI+PHBhdGggZD0iTTE1LjUgMTRoLS43OWwtLjI4LS4yN0MxNS40MSAxMi41OSAxNiAxMS4xMSAxNiA5LjUgMTYgNS45MSAxMy4wOSAzIDkuNSAzUzMgNS45MSAzIDkuNSA1LjkxIDE2IDkuNSAxNmMxLjYxIDAgMy4wOS0uNTkgNC4yMy0xLjU3bC4yNy4yOHYuNzlsNSA0Ljk5TDIwLjQ5IDE5bC00Ljk5LTV6bS02IDBDNy4wMSAxNCA1IDExLjk5IDUgOS41UzcuMDEgNSA5LjUgNSAxNCA3LjAxIDE0IDkuNSAxMS45OSAxNCA5LjUgMTR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2VsZWN0LWFsbCI+PHBhdGggZD0iTTMgNWgyVjNjLTEuMSAwLTIgLjktMiAyem0wIDhoMnYtMkgzdjJ6bTQgOGgydi0ySDd2MnpNMyA5aDJWN0gzdjJ6bTEwLTZoLTJ2MmgyVjN6bTYgMHYyaDJjMC0xLjEtLjktMi0yLTJ6TTUgMjF2LTJIM2MwIDEuMS45IDIgMiAyem0tMi00aDJ2LTJIM3Yyek05IDNIN3YyaDJWM3ptMiAxOGgydi0yaC0ydjJ6bTgtOGgydi0yaC0ydjJ6bTAgOGMxLjEgMCAyLS45IDItMmgtMnYyem0wLTEyaDJWN2gtMnYyem0wIDhoMnYtMmgtMnYyem0tNCA0aDJ2LTJoLTJ2MnptMC0xNmgyVjNoLTJ2MnpNNyAxN2gxMFY3SDd2MTB6bTItOGg2djZIOVY5eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InNlbmQiPjxwYXRoIGQ9Ik0yLjAxIDIxTDIzIDEyIDIuMDEgMyAyIDEwbDE1IDItMTUgMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJzZXR0aW5ncyI+PHBhdGggZD0iTTE5LjQzIDEyLjk4Yy4wNC0uMzIuMDctLjY0LjA3LS45OHMtLjAzLS42Ni0uMDctLjk4bDIuMTEtMS42NWMuMTktLjE1LjI0LS40Mi4xMi0uNjRsLTItMy40NmMtLjEyLS4yMi0uMzktLjMtLjYxLS4yMmwtMi40OSAxYy0uNTItLjQtMS4wOC0uNzMtMS42OS0uOThsLS4zOC0yLjY1QzE0LjQ2IDIuMTggMTQuMjUgMiAxNCAyaC00Yy0uMjUgMC0uNDYuMTgtLjQ5LjQybC0uMzggMi42NWMtLjYxLjI1LTEuMTcuNTktMS42OS45OGwtMi40OS0xYy0uMjMtLjA5LS40OSAwLS42MS4yMmwtMiAzLjQ2Yy0uMTMuMjItLjA3LjQ5LjEyLjY0bDIuMTEgMS42NWMtLjA0LjMyLS4wNy42NS0uMDcuOThzLjAzLjY2LjA3Ljk4bC0yLjExIDEuNjVjLS4xOS4xNS0uMjQuNDItLjEyLjY0bDIgMy40NmMuMTIuMjIuMzkuMy42MS4yMmwyLjQ5LTFjLjUyLjQgMS4wOC43MyAxLjY5Ljk4bC4zOCAyLjY1Yy4wMy4yNC4yNC40Mi40OS40Mmg0Yy4yNSAwIC40Ni0uMTguNDktLjQybC4zOC0yLjY1Yy42MS0uMjUgMS4xNy0uNTkgMS42OS0uOThsMi40OSAxYy4yMy4wOS40OSAwIC42MS0uMjJsMi0zLjQ2Yy4xMi0uMjIuMDctLjQ5LS4xMi0uNjRsLTIuMTEtMS42NXpNMTIgMTUuNWMtMS45MyAwLTMuNS0xLjU3LTMuNS0zLjVzMS41Ny0zLjUgMy41LTMuNSAzLjUgMS41NyAzLjUgMy41LTEuNTcgMy41LTMuNSAzLjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2V0dGluZ3MtYXBwbGljYXRpb25zIj48cGF0aCBkPSJNMTIgMTBjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yem03LTdINWMtMS4xMSAwLTIgLjktMiAydjE0YzAgMS4xLjg5IDIgMiAyaDE0YzEuMTEgMCAyLS45IDItMlY1YzAtMS4xLS44OS0yLTItMnptLTEuNzUgOWMwIC4yMy0uMDIuNDYtLjA1LjY4bDEuNDggMS4xNmMuMTMuMTEuMTcuMy4wOC40NWwtMS40IDIuNDJjLS4wOS4xNS0uMjcuMjEtLjQzLjE1bC0xLjc0LS43Yy0uMzYuMjgtLjc2LjUxLTEuMTguNjlsLS4yNiAxLjg1Yy0uMDMuMTctLjE4LjMtLjM1LjNoLTIuOGMtLjE3IDAtLjMyLS4xMy0uMzUtLjI5bC0uMjYtMS44NWMtLjQzLS4xOC0uODItLjQxLTEuMTgtLjY5bC0xLjc0LjdjLS4xNi4wNi0uMzQgMC0uNDMtLjE1bC0xLjQtMi40MmMtLjA5LS4xNS0uMDUtLjM0LjA4LS40NWwxLjQ4LTEuMTZjLS4wMy0uMjMtLjA1LS40Ni0uMDUtLjY5IDAtLjIzLjAyLS40Ni4wNS0uNjhsLTEuNDgtMS4xNmMtLjEzLS4xMS0uMTctLjMtLjA4LS40NWwxLjQtMi40MmMuMDktLjE1LjI3LS4yMS40My0uMTVsMS43NC43Yy4zNi0uMjguNzYtLjUxIDEuMTgtLjY5bC4yNi0xLjg1Yy4wMy0uMTcuMTgtLjMuMzUtLjNoMi44Yy4xNyAwIC4zMi4xMy4zNS4yOWwuMjYgMS44NWMuNDMuMTguODIuNDEgMS4xOC42OWwxLjc0LS43Yy4xNi0uMDYuMzQgMCAuNDMuMTVsMS40IDIuNDJjLjA5LjE1LjA1LjM0LS4wOC40NWwtMS40OCAxLjE2Yy4wMy4yMy4wNS40Ni4wNS42OXoiPjwvcGF0aD48L2c+CjxnIGlkPSJzZXR0aW5ncy1iYWNrdXAtcmVzdG9yZSI+PHBhdGggZD0iTTE0IDEyYzAtMS4xLS45LTItMi0ycy0yIC45LTIgMiAuOSAyIDIgMiAyLS45IDItMnptLTItOWMtNC45NyAwLTkgNC4wMy05IDlIMGw0IDQgNC00SDVjMC0zLjg3IDMuMTMtNyA3LTdzNyAzLjEzIDcgNy0zLjEzIDctNyA3Yy0xLjUxIDAtMi45MS0uNDktNC4wNi0xLjNsLTEuNDIgMS40NEM4LjA0IDIwLjMgOS45NCAyMSAxMiAyMWM0Ljk3IDAgOS00LjAzIDktOXMtNC4wMy05LTktOXoiPjwvcGF0aD48L2c+CjxnIGlkPSJzZXR0aW5ncy1ibHVldG9vdGgiPjxwYXRoIGQ9Ik0xMSAyNGgydi0yaC0ydjJ6bS00IDBoMnYtMkg3djJ6bTggMGgydi0yaC0ydjJ6bTIuNzEtMTguMjlMMTIgMGgtMXY3LjU5TDYuNDEgMyA1IDQuNDEgMTAuNTkgMTAgNSAxNS41OSA2LjQxIDE3IDExIDEyLjQxVjIwaDFsNS43MS01LjcxLTQuMy00LjI5IDQuMy00LjI5ek0xMyAzLjgzbDEuODggMS44OEwxMyA3LjU5VjMuODN6bTEuODggMTAuNDZMMTMgMTYuMTd2LTMuNzZsMS44OCAxLjg4eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InNldHRpbmdzLWJyaWdodG5lc3MiPjxwYXRoIGQ9Ik0yMSAzSDNjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMThjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnptMCAxNi4wMUgzVjQuOTloMTh2MTQuMDJ6TTggMTZoMi41bDEuNSAxLjUgMS41LTEuNUgxNnYtMi41bDEuNS0xLjUtMS41LTEuNVY4aC0yLjVMMTIgNi41IDEwLjUgOEg4djIuNUw2LjUgMTIgOCAxMy41VjE2em00LTdjMS42NiAwIDMgMS4zNCAzIDNzLTEuMzQgMy0zIDNWOXoiPjwvcGF0aD48L2c+CjxnIGlkPSJzZXR0aW5ncy1jZWxsIj48cGF0aCBkPSJNNyAyNGgydi0ySDd2MnptNCAwaDJ2LTJoLTJ2MnptNCAwaDJ2LTJoLTJ2MnpNMTYgLjAxTDggMEM2LjkgMCA2IC45IDYgMnYxNmMwIDEuMS45IDIgMiAyaDhjMS4xIDAgMi0uOSAyLTJWMmMwLTEuMS0uOS0xLjk5LTItMS45OXpNMTYgMTZIOFY0aDh2MTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2V0dGluZ3MtZXRoZXJuZXQiPjxwYXRoIGQ9Ik03Ljc3IDYuNzZMNi4yMyA1LjQ4LjgyIDEybDUuNDEgNi41MiAxLjU0LTEuMjhMMy40MiAxMmw0LjM1LTUuMjR6TTcgMTNoMnYtMkg3djJ6bTEwLTJoLTJ2Mmgydi0yem0tNiAyaDJ2LTJoLTJ2MnptNi43Ny03LjUybC0xLjU0IDEuMjhMMjAuNTggMTJsLTQuMzUgNS4yNCAxLjU0IDEuMjhMMjMuMTggMTJsLTUuNDEtNi41MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJzZXR0aW5ncy1pbnB1dC1hbnRlbm5hIj48cGF0aCBkPSJNMTIgNWMtMy44NyAwLTcgMy4xMy03IDdoMmMwLTIuNzYgMi4yNC01IDUtNXM1IDIuMjQgNSA1aDJjMC0zLjg3LTMuMTMtNy03LTd6bTEgOS4yOWMuODgtLjM5IDEuNS0xLjI2IDEuNS0yLjI5IDAtMS4zOC0xLjEyLTIuNS0yLjUtMi41UzkuNSAxMC42MiA5LjUgMTJjMCAxLjAyLjYyIDEuOSAxLjUgMi4yOXYzLjNMNy41OSAyMSA5IDIyLjQxbDMtMyAzIDNMMTYuNDEgMjEgMTMgMTcuNTl2LTMuM3pNMTIgMUM1LjkzIDEgMSA1LjkzIDEgMTJoMmMwLTQuOTcgNC4wMy05IDktOXM5IDQuMDMgOSA5aDJjMC02LjA3LTQuOTMtMTEtMTEtMTF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2V0dGluZ3MtaW5wdXQtY29tcG9uZW50Ij48cGF0aCBkPSJNNSAyYzAtLjU1LS40NS0xLTEtMXMtMSAuNDUtMSAxdjRIMXY2aDZWNkg1VjJ6bTQgMTRjMCAxLjMuODQgMi40IDIgMi44MlYyM2gydi00LjE4YzEuMTYtLjQxIDItMS41MSAyLTIuODJ2LTJIOXYyem0tOCAwYzAgMS4zLjg0IDIuNCAyIDIuODJWMjNoMnYtNC4xOEM2LjE2IDE4LjQgNyAxNy4zIDcgMTZ2LTJIMXYyek0yMSA2VjJjMC0uNTUtLjQ1LTEtMS0xcy0xIC40NS0xIDF2NGgtMnY2aDZWNmgtMnptLTgtNGMwLS41NS0uNDUtMS0xLTFzLTEgLjQ1LTEgMXY0SDl2Nmg2VjZoLTJWMnptNCAxNGMwIDEuMy44NCAyLjQgMiAyLjgyVjIzaDJ2LTQuMThjMS4xNi0uNDEgMi0xLjUxIDItMi44MnYtMmgtNnYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InNldHRpbmdzLWlucHV0LWNvbXBvc2l0ZSI+PHBhdGggZD0iTTUgMmMwLS41NS0uNDUtMS0xLTFzLTEgLjQ1LTEgMXY0SDF2Nmg2VjZINVYyem00IDE0YzAgMS4zLjg0IDIuNCAyIDIuODJWMjNoMnYtNC4xOGMxLjE2LS40MSAyLTEuNTEgMi0yLjgydi0ySDl2MnptLTggMGMwIDEuMy44NCAyLjQgMiAyLjgyVjIzaDJ2LTQuMThDNi4xNiAxOC40IDcgMTcuMyA3IDE2di0ySDF2MnpNMjEgNlYyYzAtLjU1LS40NS0xLTEtMXMtMSAuNDUtMSAxdjRoLTJ2Nmg2VjZoLTJ6bS04LTRjMC0uNTUtLjQ1LTEtMS0xcy0xIC40NS0xIDF2NEg5djZoNlY2aC0yVjJ6bTQgMTRjMCAxLjMuODQgMi40IDIgMi44MlYyM2gydi00LjE4YzEuMTYtLjQxIDItMS41MSAyLTIuODJ2LTJoLTZ2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJzZXR0aW5ncy1pbnB1dC1oZG1pIj48cGF0aCBkPSJNMTggN1Y0YzAtMS4xLS45LTItMi0ySDhjLTEuMSAwLTIgLjktMiAydjNINXY2bDMgNnYzaDh2LTNsMy02VjdoLTF6TTggNGg4djNoLTJWNWgtMXYyaC0yVjVoLTF2Mkg4VjR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2V0dGluZ3MtaW5wdXQtc3ZpZGVvIj48cGF0aCBkPSJNOCAxMS41YzAtLjgzLS42Ny0xLjUtMS41LTEuNVM1IDEwLjY3IDUgMTEuNSA1LjY3IDEzIDYuNSAxMyA4IDEyLjMzIDggMTEuNXptNy01YzAtLjgzLS42Ny0xLjUtMS41LTEuNWgtM0M5LjY3IDUgOSA1LjY3IDkgNi41UzkuNjcgOCAxMC41IDhoM2MuODMgMCAxLjUtLjY3IDEuNS0xLjV6TTguNSAxNWMtLjgzIDAtMS41LjY3LTEuNSAxLjVTNy42NyAxOCA4LjUgMThzMS41LS42NyAxLjUtMS41UzkuMzMgMTUgOC41IDE1ek0xMiAxQzUuOTMgMSAxIDUuOTMgMSAxMnM0LjkzIDExIDExIDExIDExLTQuOTMgMTEtMTFTMTguMDcgMSAxMiAxem0wIDIwYy00Ljk2IDAtOS00LjA0LTktOXM0LjA0LTkgOS05IDkgNC4wNCA5IDktNC4wNCA5LTkgOXptNS41LTExYy0uODMgMC0xLjUuNjctMS41IDEuNXMuNjcgMS41IDEuNSAxLjUgMS41LS42NyAxLjUtMS41LS42Ny0xLjUtMS41LTEuNXptLTIgNWMtLjgzIDAtMS41LjY3LTEuNSAxLjVzLjY3IDEuNSAxLjUgMS41IDEuNS0uNjcgMS41LTEuNS0uNjctMS41LTEuNS0xLjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2V0dGluZ3Mtb3ZlcnNjYW4iPjxwYXRoIGQ9Ik0xMi4wMSA1LjVMMTAgOGg0bC0xLjk5LTIuNXpNMTggMTB2NGwyLjUtMS45OUwxOCAxMHpNNiAxMGwtMi41IDIuMDFMNiAxNHYtNHptOCA2aC00bDIuMDEgMi41TDE0IDE2em03LTEzSDNjLTEuMSAwLTIgLjktMiAydjE0YzAgMS4xLjkgMiAyIDJoMThjMS4xIDAgMi0uOSAyLTJWNWMwLTEuMS0uOS0yLTItMnptMCAxNi4wMUgzVjQuOTloMTh2MTQuMDJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2V0dGluZ3MtcGhvbmUiPjxwYXRoIGQ9Ik0xMyA5aC0ydjJoMlY5em00IDBoLTJ2MmgyVjl6bTMgNi41Yy0xLjI1IDAtMi40NS0uMi0zLjU3LS41Ny0uMzUtLjExLS43NC0uMDMtMS4wMi4yNGwtMi4yIDIuMmMtMi44My0xLjQ0LTUuMTUtMy43NS02LjU5LTYuNThsMi4yLTIuMjFjLjI4LS4yNy4zNi0uNjYuMjUtMS4wMUM4LjcgNi40NSA4LjUgNS4yNSA4LjUgNGMwLS41NS0uNDUtMS0xLTFINGMtLjU1IDAtMSAuNDUtMSAxIDAgOS4zOSA3LjYxIDE3IDE3IDE3IC41NSAwIDEtLjQ1IDEtMXYtMy41YzAtLjU1LS40NS0xLTEtMXpNMTkgOXYyaDJWOWgtMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJzZXR0aW5ncy1wb3dlciI+PHBhdGggZD0iTTcgMjRoMnYtMkg3djJ6bTQgMGgydi0yaC0ydjJ6bTItMjJoLTJ2MTBoMlYyem0zLjU2IDIuNDRsLTEuNDUgMS40NUMxNi44NCA2Ljk0IDE4IDguODMgMTggMTFjMCAzLjMxLTIuNjkgNi02IDZzLTYtMi42OS02LTZjMC0yLjE3IDEuMTYtNC4wNiAyLjg4LTUuMTJMNy40NCA0LjQ0QzUuMzYgNS44OCA0IDguMjggNCAxMWMwIDQuNDIgMy41OCA4IDggOHM4LTMuNTggOC04YzAtMi43Mi0xLjM2LTUuMTItMy40NC02LjU2ek0xNSAyNGgydi0yaC0ydjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2V0dGluZ3MtcmVtb3RlIj48cGF0aCBkPSJNMTUgOUg5Yy0uNTUgMC0xIC40NS0xIDF2MTJjMCAuNTUuNDUgMSAxIDFoNmMuNTUgMCAxLS40NSAxLTFWMTBjMC0uNTUtLjQ1LTEtMS0xem0tMyA2Yy0xLjEgMC0yLS45LTItMnMuOS0yIDItMiAyIC45IDIgMi0uOSAyLTIgMnpNNy4wNSA2LjA1bDEuNDEgMS40MUM5LjM3IDYuNTYgMTAuNjIgNiAxMiA2czIuNjMuNTYgMy41NCAxLjQ2bDEuNDEtMS40MUMxNS42OCA0Ljc4IDEzLjkzIDQgMTIgNHMtMy42OC43OC00Ljk1IDIuMDV6TTEyIDBDOC45NiAwIDYuMjEgMS4yMyA0LjIyIDMuMjJsMS40MSAxLjQxQzcuMjYgMy4wMSA5LjUxIDIgMTIgMnM0Ljc0IDEuMDEgNi4zNiAyLjY0bDEuNDEtMS40MUMxNy43OSAxLjIzIDE1LjA0IDAgMTIgMHoiPjwvcGF0aD48L2c+CjxnIGlkPSJzZXR0aW5ncy12b2ljZSI+PHBhdGggZD0iTTcgMjRoMnYtMkg3djJ6bTUtMTFjMS42NiAwIDIuOTktMS4zNCAyLjk5LTNMMTUgNGMwLTEuNjYtMS4zNC0zLTMtM1M5IDIuMzQgOSA0djZjMCAxLjY2IDEuMzQgMyAzIDN6bS0xIDExaDJ2LTJoLTJ2MnptNCAwaDJ2LTJoLTJ2MnptNC0xNGgtMS43YzAgMy0yLjU0IDUuMS01LjMgNS4xUzYuNyAxMyA2LjcgMTBINWMwIDMuNDEgMi43MiA2LjIzIDYgNi43MlYyMGgydi0zLjI4YzMuMjgtLjQ5IDYtMy4zMSA2LTYuNzJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2hvcCI+PHBhdGggZD0iTTE2IDZWNGMwLTEuMTEtLjg5LTItMi0yaC00Yy0xLjExIDAtMiAuODktMiAydjJIMnYxM2MwIDEuMTEuODkgMiAyIDJoMTZjMS4xMSAwIDItLjg5IDItMlY2aC02em0tNi0yaDR2MmgtNFY0ek05IDE4VjlsNy41IDRMOSAxOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJzaG9wLXR3byI+PHBhdGggZD0iTTMgOUgxdjExYzAgMS4xMS44OSAyIDIgMmgxNGMxLjExIDAgMi0uODkgMi0ySDNWOXptMTUtNFYzYzAtMS4xMS0uODktMi0yLTJoLTRjLTEuMTEgMC0yIC44OS0yIDJ2Mkg1djExYzAgMS4xMS44OSAyIDIgMmgxNGMxLjExIDAgMi0uODkgMi0yVjVoLTV6bS02LTJoNHYyaC00VjN6bTAgMTJWOGw1LjUgMy01LjUgNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJzaG9wcGluZy1iYXNrZXQiPjxwYXRoIGQ9Ik0xNy4yMSA5bC00LjM4LTYuNTZjLS4xOS0uMjgtLjUxLS40Mi0uODMtLjQyLS4zMiAwLS42NC4xNC0uODMuNDNMNi43OSA5SDJjLS41NSAwLTEgLjQ1LTEgMSAwIC4wOS4wMS4xOC4wNC4yN2wyLjU0IDkuMjdjLjIzLjg0IDEgMS40NiAxLjkyIDEuNDZoMTNjLjkyIDAgMS42OS0uNjIgMS45My0xLjQ2bDIuNTQtOS4yN0wyMyAxMGMwLS41NS0uNDUtMS0xLTFoLTQuNzl6TTkgOWwzLTQuNEwxNSA5SDl6bTMgOGMtMS4xIDAtMi0uOS0yLTJzLjktMiAyLTIgMiAuOSAyIDItLjkgMi0yIDJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic2hvcHBpbmctY2FydCI+PHBhdGggZD0iTTcgMThjLTEuMSAwLTEuOTkuOS0xLjk5IDJTNS45IDIyIDcgMjJzMi0uOSAyLTItLjktMi0yLTJ6TTEgMnYyaDJsMy42IDcuNTktMS4zNSAyLjQ1Yy0uMTYuMjgtLjI1LjYxLS4yNS45NiAwIDEuMS45IDIgMiAyaDEydi0ySDcuNDJjLS4xNCAwLS4yNS0uMTEtLjI1LS4yNWwuMDMtLjEyLjktMS42M2g3LjQ1Yy43NSAwIDEuNDEtLjQxIDEuNzUtMS4wM2wzLjU4LTYuNDljLjA4LS4xNC4xMi0uMzEuMTItLjQ4IDAtLjU1LS40NS0xLTEtMUg1LjIxbC0uOTQtMkgxem0xNiAxNmMtMS4xIDAtMS45OS45LTEuOTkgMnMuODkgMiAxLjk5IDIgMi0uOSAyLTItLjktMi0yLTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic29ydCI+PHBhdGggZD0iTTMgMThoNnYtMkgzdjJ6TTMgNnYyaDE4VjZIM3ptMCA3aDEydi0ySDN2MnoiPjwvcGF0aD48L2c+CjxnIGlkPSJzcGVha2VyLW5vdGVzIj48cGF0aCBkPSJNMjAgMkg0Yy0xLjEgMC0xLjk5LjktMS45OSAyTDIgMjJsNC00aDE0YzEuMSAwIDItLjkgMi0yVjRjMC0xLjEtLjktMi0yLTJ6TTggMTRINnYtMmgydjJ6bTAtM0g2VjloMnYyem0wLTNINlY2aDJ2MnptNyA2aC01di0yaDV2MnptMy0zaC04VjloOHYyem0wLTNoLThWNmg4djJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic3BlYWtlci1ub3Rlcy1vZmYiPjxwYXRoIGQ9Ik0xMC41NCAxMWwtLjU0LS41NEw3LjU0IDggNiA2LjQ2IDIuMzggMi44NCAxLjI3IDEuNzMgMCAzbDIuMDEgMi4wMUwyIDIybDQtNGg5bDUuNzMgNS43M0wyMiAyMi40NiAxNy41NCAxOGwtNy03ek04IDE0SDZ2LTJoMnYyem0tMi0zVjlsMiAySDZ6bTE0LTlINC4wOEwxMCA3LjkyVjZoOHYyaC03LjkybDEgMUgxOHYyaC00LjkybDYuOTkgNi45OUMyMS4xNCAxNy45NSAyMiAxNy4wOCAyMiAxNlY0YzAtMS4xLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InNwZWxsY2hlY2siPjxwYXRoIGQ9Ik0xMi40NSAxNmgyLjA5TDkuNDMgM0g3LjU3TDIuNDYgMTZoMi4wOWwxLjEyLTNoNS42NGwxLjE0IDN6bS02LjAyLTVMOC41IDUuNDggMTAuNTcgMTFINi40M3ptMTUuMTYuNTlsLTguMDkgOC4wOUw5LjgzIDE2bC0xLjQxIDEuNDEgNS4wOSA1LjA5TDIzIDEzbC0xLjQxLTEuNDF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic3RhciI+PHBhdGggZD0iTTEyIDE3LjI3TDE4LjE4IDIxbC0xLjY0LTcuMDNMMjIgOS4yNGwtNy4xOS0uNjFMMTIgMiA5LjE5IDguNjMgMiA5LjI0bDUuNDYgNC43M0w1LjgyIDIxeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InN0YXItYm9yZGVyIj48cGF0aCBkPSJNMjIgOS4yNGwtNy4xOS0uNjJMMTIgMiA5LjE5IDguNjMgMiA5LjI0bDUuNDYgNC43M0w1LjgyIDIxIDEyIDE3LjI3IDE4LjE4IDIxbC0xLjYzLTcuMDNMMjIgOS4yNHpNMTIgMTUuNGwtMy43NiAyLjI3IDEtNC4yOC0zLjMyLTIuODggNC4zOC0uMzhMMTIgNi4xbDEuNzEgNC4wNCA0LjM4LjM4LTMuMzIgMi44OCAxIDQuMjhMMTIgMTUuNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJzdGFyLWhhbGYiPjxwYXRoIGQ9Ik0yMiA5LjI0bC03LjE5LS42MkwxMiAyIDkuMTkgOC42MyAyIDkuMjRsNS40NiA0LjczTDUuODIgMjEgMTIgMTcuMjcgMTguMTggMjFsLTEuNjMtNy4wM0wyMiA5LjI0ek0xMiAxNS40VjYuMWwxLjcxIDQuMDQgNC4zOC4zOC0zLjMyIDIuODggMSA0LjI4TDEyIDE1LjR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic3RhcnMiPjxwYXRoIGQ9Ik0xMS45OSAyQzYuNDcgMiAyIDYuNDggMiAxMnM0LjQ3IDEwIDkuOTkgMTBDMTcuNTIgMjIgMjIgMTcuNTIgMjIgMTJTMTcuNTIgMiAxMS45OSAyem00LjI0IDE2TDEyIDE1LjQ1IDcuNzcgMThsMS4xMi00LjgxLTMuNzMtMy4yMyA0LjkyLS40MkwxMiA1bDEuOTIgNC41MyA0LjkyLjQyLTMuNzMgMy4yM0wxNi4yMyAxOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJzdG9yZSI+PHBhdGggZD0iTTIwIDRINHYyaDE2VjR6bTEgMTB2LTJsLTEtNUg0bC0xIDV2MmgxdjZoMTB2LTZoNHY2aDJ2LTZoMXptLTkgNEg2di00aDZ2NHoiPjwvcGF0aD48L2c+CjxnIGlkPSJzdWJkaXJlY3RvcnktYXJyb3ctbGVmdCI+PHBhdGggZD0iTTExIDlsMS40MiAxLjQyTDguODMgMTRIMThWNGgydjEySDguODNsMy41OSAzLjU4TDExIDIxbC02LTYgNi02eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InN1YmRpcmVjdG9yeS1hcnJvdy1yaWdodCI+PHBhdGggZD0iTTE5IDE1bC02IDYtMS40Mi0xLjQyTDE1LjE3IDE2SDRWNGgydjEwaDkuMTdsLTMuNTktMy41OEwxMyA5bDYgNnoiPjwvcGF0aD48L2c+CjxnIGlkPSJzdWJqZWN0Ij48cGF0aCBkPSJNMTQgMTdINHYyaDEwdi0yem02LThINHYyaDE2Vjl6TTQgMTVoMTZ2LTJINHYyek00IDV2MmgxNlY1SDR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic3VwZXJ2aXNvci1hY2NvdW50Ij48cGF0aCBkPSJNMTYuNSAxMmMxLjM4IDAgMi40OS0xLjEyIDIuNDktMi41UzE3Ljg4IDcgMTYuNSA3QzE1LjEyIDcgMTQgOC4xMiAxNCA5LjVzMS4xMiAyLjUgMi41IDIuNXpNOSAxMWMxLjY2IDAgMi45OS0xLjM0IDIuOTktM1MxMC42NiA1IDkgNUM3LjM0IDUgNiA2LjM0IDYgOHMxLjM0IDMgMyAzem03LjUgM2MtMS44MyAwLTUuNS45Mi01LjUgMi43NVYxOWgxMXYtMi4yNWMwLTEuODMtMy42Ny0yLjc1LTUuNS0yLjc1ek05IDEzYy0yLjMzIDAtNyAxLjE3LTcgMy41VjE5aDd2LTIuMjVjMC0uODUuMzMtMi4zNCAyLjM3LTMuNDdDMTAuNSAxMy4xIDkuNjYgMTMgOSAxM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJzd2FwLWhvcml6Ij48cGF0aCBkPSJNNi45OSAxMUwzIDE1bDMuOTkgNHYtM0gxNHYtMkg2Ljk5di0zek0yMSA5bC0zLjk5LTR2M0gxMHYyaDcuMDF2M0wyMSA5eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InN3YXAtdmVydCI+PHBhdGggZD0iTTE2IDE3LjAxVjEwaC0ydjcuMDFoLTNMMTUgMjFsNC0zLjk5aC0zek05IDNMNSA2Ljk5aDNWMTRoMlY2Ljk5aDNMOSAzeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InN3YXAtdmVydGljYWwtY2lyY2xlIj48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnpNNi41IDlMMTAgNS41IDEzLjUgOUgxMXY0SDlWOUg2LjV6bTExIDZMMTQgMTguNSAxMC41IDE1SDEzdi00aDJ2NGgyLjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ic3lzdGVtLXVwZGF0ZS1hbHQiPjxwYXRoIGQ9Ik0xMiAxNi41bDQtNGgtM3YtOWgtMnY5SDhsNCA0em05LTEzaC02djEuOTloNnYxNC4wM0gzVjUuNDloNlYzLjVIM2MtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxOGMxLjEgMCAyLS45IDItMnYtMTRjMC0xLjEtLjktMi0yLTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idGFiIj48cGF0aCBkPSJNMjEgM0gzYy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE4YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZIM1Y1aDEwdjRoOHYxMHoiPjwvcGF0aD48L2c+CjxnIGlkPSJ0YWItdW5zZWxlY3RlZCI+PHBhdGggZD0iTTEgOWgyVjdIMXYyem0wIDRoMnYtMkgxdjJ6bTAtOGgyVjNjLTEuMSAwLTIgLjktMiAyem04IDE2aDJ2LTJIOXYyem0tOC00aDJ2LTJIMXYyem0yIDR2LTJIMWMwIDEuMS45IDIgMiAyek0yMSAzaC04djZoMTBWNWMwLTEuMS0uOS0yLTItMnptMCAxNGgydi0yaC0ydjJ6TTkgNWgyVjNIOXYyek01IDIxaDJ2LTJINXYyek01IDVoMlYzSDV2MnptMTYgMTZjMS4xIDAgMi0uOSAyLTJoLTJ2MnptMC04aDJ2LTJoLTJ2MnptLTggOGgydi0yaC0ydjJ6bTQgMGgydi0yaC0ydjJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idGV4dC1mb3JtYXQiPjxwYXRoIGQ9Ik01IDE3djJoMTR2LTJINXptNC41LTQuMmg1bC45IDIuMmgyLjFMMTIuNzUgNGgtMS41TDYuNSAxNWgyLjFsLjktMi4yek0xMiA1Ljk4TDEzLjg3IDExaC0zLjc0TDEyIDUuOTh6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idGhlYXRlcnMiPjxwYXRoIGQ9Ik0xOCAzdjJoLTJWM0g4djJINlYzSDR2MThoMnYtMmgydjJoOHYtMmgydjJoMlYzaC0yek04IDE3SDZ2LTJoMnYyem0wLTRINnYtMmgydjJ6bTAtNEg2VjdoMnYyem0xMCA4aC0ydi0yaDJ2MnptMC00aC0ydi0yaDJ2MnptMC00aC0yVjdoMnYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InRodW1iLWRvd24iPjxwYXRoIGQ9Ik0xNSAzSDZjLS44MyAwLTEuNTQuNS0xLjg0IDEuMjJsLTMuMDIgNy4wNWMtLjA5LjIzLS4xNC40Ny0uMTQuNzN2MS45MWwuMDEuMDFMMSAxNGMwIDEuMS45IDIgMiAyaDYuMzFsLS45NSA0LjU3LS4wMy4zMmMwIC40MS4xNy43OS40NCAxLjA2TDkuODMgMjNsNi41OS02LjU5Yy4zNi0uMzYuNTgtLjg2LjU4LTEuNDFWNWMwLTEuMS0uOS0yLTItMnptNCAwdjEyaDRWM2gtNHoiPjwvcGF0aD48L2c+CjxnIGlkPSJ0aHVtYi11cCI+PHBhdGggZD0iTTEgMjFoNFY5SDF2MTJ6bTIyLTExYzAtMS4xLS45LTItMi0yaC02LjMxbC45NS00LjU3LjAzLS4zMmMwLS40MS0uMTctLjc5LS40NC0xLjA2TDE0LjE3IDEgNy41OSA3LjU5QzcuMjIgNy45NSA3IDguNDUgNyA5djEwYzAgMS4xLjkgMiAyIDJoOWMuODMgMCAxLjU0LS41IDEuODQtMS4yMmwzLjAyLTcuMDVjLjA5LS4yMy4xNC0uNDcuMTQtLjczdi0xLjkxbC0uMDEtLjAxTDIzIDEweiI+PC9wYXRoPjwvZz4KPGcgaWQ9InRodW1icy11cC1kb3duIj48cGF0aCBkPSJNMTIgNmMwLS41NS0uNDUtMS0xLTFINS44MmwuNjYtMy4xOC4wMi0uMjNjMC0uMzEtLjEzLS41OS0uMzMtLjhMNS4zOCAwIC40NCA0Ljk0Qy4xNyA1LjIxIDAgNS41OSAwIDZ2Ni41YzAgLjgzLjY3IDEuNSAxLjUgMS41aDYuNzVjLjYyIDAgMS4xNS0uMzggMS4zOC0uOTFsMi4yNi01LjI5Yy4wNy0uMTcuMTEtLjM2LjExLS41NVY2em0xMC41IDRoLTYuNzVjLS42MiAwLTEuMTUuMzgtMS4zOC45MWwtMi4yNiA1LjI5Yy0uMDcuMTctLjExLjM2LS4xMS41NVYxOGMwIC41NS40NSAxIDEgMWg1LjE4bC0uNjYgMy4xOC0uMDIuMjRjMCAuMzEuMTMuNTkuMzMuOGwuNzkuNzggNC45NC00Ljk0Yy4yNy0uMjcuNDQtLjY1LjQ0LTEuMDZ2LTYuNWMwLS44My0uNjctMS41LTEuNS0xLjV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idGltZWxpbmUiPjxwYXRoIGQ9Ik0yMyA4YzAgMS4xLS45IDItMiAyLS4xOCAwLS4zNS0uMDItLjUxLS4wN2wtMy41NiAzLjU1Yy4wNS4xNi4wNy4zNC4wNy41MiAwIDEuMS0uOSAyLTIgMnMtMi0uOS0yLTJjMC0uMTguMDItLjM2LjA3LS41MmwtMi41NS0yLjU1Yy0uMTYuMDUtLjM0LjA3LS41Mi4wN3MtLjM2LS4wMi0uNTItLjA3bC00LjU1IDQuNTZjLjA1LjE2LjA3LjMzLjA3LjUxIDAgMS4xLS45IDItMiAycy0yLS45LTItMiAuOS0yIDItMmMuMTggMCAuMzUuMDIuNTEuMDdsNC41Ni00LjU1QzguMDIgOS4zNiA4IDkuMTggOCA5YzAtMS4xLjktMiAyLTJzMiAuOSAyIDJjMCAuMTgtLjAyLjM2LS4wNy41MmwyLjU1IDIuNTVjLjE2LS4wNS4zNC0uMDcuNTItLjA3cy4zNi4wMi41Mi4wN2wzLjU1LTMuNTZDMTkuMDIgOC4zNSAxOSA4LjE4IDE5IDhjMC0xLjEuOS0yIDItMnMyIC45IDIgMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJ0b2MiPjxwYXRoIGQ9Ik0zIDloMTRWN0gzdjJ6bTAgNGgxNHYtMkgzdjJ6bTAgNGgxNHYtMkgzdjJ6bTE2IDBoMnYtMmgtMnYyem0wLTEwdjJoMlY3aC0yem0wIDZoMnYtMmgtMnYyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InRvZGF5Ij48cGF0aCBkPSJNMTkgM2gtMVYxaC0ydjJIOFYxSDZ2Mkg1Yy0xLjExIDAtMS45OS45LTEuOTkgMkwzIDE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY4aDE0djExek03IDEwaDV2NUg3eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InRvbGwiPjxwYXRoIGQ9Ik0xNSA0Yy00LjQyIDAtOCAzLjU4LTggOHMzLjU4IDggOCA4IDgtMy41OCA4LTgtMy41OC04LTgtOHptMCAxNGMtMy4zMSAwLTYtMi42OS02LTZzMi42OS02IDYtNiA2IDIuNjkgNiA2LTIuNjkgNi02IDZ6TTMgMTJjMC0yLjYxIDEuNjctNC44MyA0LTUuNjVWNC4yNkMzLjU1IDUuMTUgMSA4LjI3IDEgMTJzMi41NSA2Ljg1IDYgNy43NHYtMi4wOWMtMi4zMy0uODItNC0zLjA0LTQtNS42NXoiPjwvcGF0aD48L2c+CjxnIGlkPSJ0b3VjaC1hcHAiPjxwYXRoIGQ9Ik05IDExLjI0VjcuNUM5IDYuMTIgMTAuMTIgNSAxMS41IDVTMTQgNi4xMiAxNCA3LjV2My43NGMxLjIxLS44MSAyLTIuMTggMi0zLjc0QzE2IDUuMDEgMTMuOTkgMyAxMS41IDNTNyA1LjAxIDcgNy41YzAgMS41Ni43OSAyLjkzIDIgMy43NHptOS44NCA0LjYzbC00LjU0LTIuMjZjLS4xNy0uMDctLjM1LS4xMS0uNTQtLjExSDEzdi02YzAtLjgzLS42Ny0xLjUtMS41LTEuNVMxMCA2LjY3IDEwIDcuNXYxMC43NGwtMy40My0uNzJjLS4wOC0uMDEtLjE1LS4wMy0uMjQtLjAzLS4zMSAwLS41OS4xMy0uNzkuMzNsLS43OS44IDQuOTQgNC45NGMuMjcuMjcuNjUuNDQgMS4wNi40NGg2Ljc5Yy43NSAwIDEuMzMtLjU1IDEuNDQtMS4yOGwuNzUtNS4yN2MuMDEtLjA3LjAyLS4xNC4wMi0uMiAwLS42Mi0uMzgtMS4xNi0uOTEtMS4zOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJ0cmFjay1jaGFuZ2VzIj48cGF0aCBkPSJNMTkuMDcgNC45M2wtMS40MSAxLjQxQzE5LjEgNy43OSAyMCA5Ljc5IDIwIDEyYzAgNC40Mi0zLjU4IDgtOCA4cy04LTMuNTgtOC04YzAtNC4wOCAzLjA1LTcuNDQgNy03LjkzdjIuMDJDOC4xNiA2LjU3IDYgOS4wMyA2IDEyYzAgMy4zMSAyLjY5IDYgNiA2czYtMi42OSA2LTZjMC0xLjY2LS42Ny0zLjE2LTEuNzYtNC4yNGwtMS40MSAxLjQxQzE1LjU1IDkuOSAxNiAxMC45IDE2IDEyYzAgMi4yMS0xLjc5IDQtNCA0cy00LTEuNzktNC00YzAtMS44NiAxLjI4LTMuNDEgMy0zLjg2djIuMTRjLS42LjM1LTEgLjk4LTEgMS43MiAwIDEuMS45IDIgMiAyczItLjkgMi0yYzAtLjc0LS40LTEuMzgtMS0xLjcyVjJoLTFDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMGMwLTIuNzYtMS4xMi01LjI2LTIuOTMtNy4wN3oiPjwvcGF0aD48L2c+CjxnIGlkPSJ0cmFuc2xhdGUiPjxwYXRoIGQ9Ik0xMi44NyAxNS4wN2wtMi41NC0yLjUxLjAzLS4wM2MxLjc0LTEuOTQgMi45OC00LjE3IDMuNzEtNi41M0gxN1Y0aC03VjJIOHYySDF2MS45OWgxMS4xN0MxMS41IDcuOTIgMTAuNDQgOS43NSA5IDExLjM1IDguMDcgMTAuMzIgNy4zIDkuMTkgNi42OSA4aC0yYy43MyAxLjYzIDEuNzMgMy4xNyAyLjk4IDQuNTZsLTUuMDkgNS4wMkw0IDE5bDUtNSAzLjExIDMuMTEuNzYtMi4wNHpNMTguNSAxMGgtMkwxMiAyMmgybDEuMTItM2g0Ljc1TDIxIDIyaDJsLTQuNS0xMnptLTIuNjIgN2wxLjYyLTQuMzNMMTkuMTIgMTdoLTMuMjR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idHJlbmRpbmctZG93biI+PHBhdGggZD0iTTE2IDE4bDIuMjktMi4yOS00Ljg4LTQuODgtNCA0TDIgNy40MSAzLjQxIDZsNiA2IDQtNCA2LjMgNi4yOUwyMiAxMnY2eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InRyZW5kaW5nLWZsYXQiPjxwYXRoIGQ9Ik0yMiAxMmwtNC00djNIM3YyaDE1djN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idHJlbmRpbmctdXAiPjxwYXRoIGQ9Ik0xNiA2bDIuMjkgMi4yOS00Ljg4IDQuODgtNC00TDIgMTYuNTkgMy40MSAxOGw2LTYgNCA0IDYuMy02LjI5TDIyIDEyVjZ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idHVybmVkLWluIj48cGF0aCBkPSJNMTcgM0g3Yy0xLjEgMC0xLjk5LjktMS45OSAyTDUgMjFsNy0zIDcgM1Y1YzAtMS4xLS45LTItMi0yeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InR1cm5lZC1pbi1ub3QiPjxwYXRoIGQ9Ik0xNyAzSDdjLTEuMSAwLTEuOTkuOS0xLjk5IDJMNSAyMWw3LTMgNyAzVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTVsLTUtMi4xOEw3IDE4VjVoMTB2MTN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idW5hcmNoaXZlIj48cGF0aCBkPSJNMjAuNTUgNS4yMmwtMS4zOS0xLjY4QzE4Ljg4IDMuMjEgMTguNDcgMyAxOCAzSDZjLS40NyAwLS44OC4yMS0xLjE1LjU1TDMuNDYgNS4yMkMzLjE3IDUuNTcgMyA2LjAxIDMgNi41VjE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjYuNWMwLS40OS0uMTctLjkzLS40NS0xLjI4ek0xMiA5LjVsNS41IDUuNUgxNHYyaC00di0ySDYuNUwxMiA5LjV6TTUuMTIgNWwuODItMWgxMmwuOTMgMUg1LjEyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InVuZG8iPjxwYXRoIGQ9Ik0xMi41IDhjLTIuNjUgMC01LjA1Ljk5LTYuOSAyLjZMMiA3djloOWwtMy42Mi0zLjYyYzEuMzktMS4xNiAzLjE2LTEuODggNS4xMi0xLjg4IDMuNTQgMCA2LjU1IDIuMzEgNy42IDUuNWwyLjM3LS43OEMyMS4wOCAxMS4wMyAxNy4xNSA4IDEyLjUgOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJ1bmZvbGQtbGVzcyI+PHBhdGggZD0iTTcuNDEgMTguNTlMOC44MyAyMCAxMiAxNi44MyAxNS4xNyAyMGwxLjQxLTEuNDFMMTIgMTRsLTQuNTkgNC41OXptOS4xOC0xMy4xOEwxNS4xNyA0IDEyIDcuMTcgOC44MyA0IDcuNDEgNS40MSAxMiAxMGw0LjU5LTQuNTl6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idW5mb2xkLW1vcmUiPjxwYXRoIGQ9Ik0xMiA1LjgzTDE1LjE3IDlsMS40MS0xLjQxTDEyIDMgNy40MSA3LjU5IDguODMgOSAxMiA1Ljgzem0wIDEyLjM0TDguODMgMTVsLTEuNDEgMS40MUwxMiAyMWw0LjU5LTQuNTlMMTUuMTcgMTUgMTIgMTguMTd6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idXBkYXRlIj48cGF0aCBkPSJNMjEgMTAuMTJoLTYuNzhsMi43NC0yLjgyYy0yLjczLTIuNy03LjE1LTIuOC05Ljg4LS4xLTIuNzMgMi43MS0yLjczIDcuMDggMCA5Ljc5IDIuNzMgMi43MSA3LjE1IDIuNzEgOS44OCAwQzE4LjMyIDE1LjY1IDE5IDE0LjA4IDE5IDEyLjFoMmMwIDEuOTgtLjg4IDQuNTUtMi42NCA2LjI5LTMuNTEgMy40OC05LjIxIDMuNDgtMTIuNzIgMC0zLjUtMy40Ny0zLjUzLTkuMTEtLjAyLTEyLjU4IDMuNTEtMy40NyA5LjE0LTMuNDcgMTIuNjUgMEwyMSAzdjcuMTJ6TTEyLjUgOHY0LjI1bDMuNSAyLjA4LS43MiAxLjIxTDExIDEzVjhoMS41eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InZlcmlmaWVkLXVzZXIiPjxwYXRoIGQ9Ik0xMiAxTDMgNXY2YzAgNS41NSAzLjg0IDEwLjc0IDkgMTIgNS4xNi0xLjI2IDktNi40NSA5LTEyVjVsLTktNHptLTIgMTZsLTQtNCAxLjQxLTEuNDFMMTAgMTQuMTdsNi41OS02LjU5TDE4IDlsLTggOHoiPjwvcGF0aD48L2c+CjxnIGlkPSJ2aWV3LWFnZW5kYSI+PHBhdGggZD0iTTIwIDEzSDNjLS41NSAwLTEgLjQ1LTEgMXY2YzAgLjU1LjQ1IDEgMSAxaDE3Yy41NSAwIDEtLjQ1IDEtMXYtNmMwLS41NS0uNDUtMS0xLTF6bTAtMTBIM2MtLjU1IDAtMSAuNDUtMSAxdjZjMCAuNTUuNDUgMSAxIDFoMTdjLjU1IDAgMS0uNDUgMS0xVjRjMC0uNTUtLjQ1LTEtMS0xeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InZpZXctYXJyYXkiPjxwYXRoIGQ9Ik00IDE4aDNWNUg0djEzek0xOCA1djEzaDNWNWgtM3pNOCAxOGg5VjVIOHYxM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJ2aWV3LWNhcm91c2VsIj48cGF0aCBkPSJNNyAxOWgxMFY0SDd2MTV6bS01LTJoNFY2SDJ2MTF6TTE4IDZ2MTFoNFY2aC00eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InZpZXctY29sdW1uIj48cGF0aCBkPSJNMTAgMThoNVY1aC01djEzem0tNiAwaDVWNUg0djEzek0xNiA1djEzaDVWNWgtNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJ2aWV3LWRheSI+PHBhdGggZD0iTTIgMjFoMTl2LTNIMnYzek0yMCA4SDNjLS41NSAwLTEgLjQ1LTEgMXY2YzAgLjU1LjQ1IDEgMSAxaDE3Yy41NSAwIDEtLjQ1IDEtMVY5YzAtLjU1LS40NS0xLTEtMXpNMiAzdjNoMTlWM0gyeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InZpZXctaGVhZGxpbmUiPjxwYXRoIGQ9Ik00IDE1aDE2di0ySDR2MnptMCA0aDE2di0ySDR2MnptMC04aDE2VjlINHYyem0wLTZ2MmgxNlY1SDR6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idmlldy1saXN0Ij48cGF0aCBkPSJNNCAxNGg0di00SDR2NHptMCA1aDR2LTRINHY0ek00IDloNFY1SDR2NHptNSA1aDEydi00SDl2NHptMCA1aDEydi00SDl2NHpNOSA1djRoMTJWNUg5eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InZpZXctbW9kdWxlIj48cGF0aCBkPSJNNCAxMWg1VjVINHY2em0wIDdoNXYtNkg0djZ6bTYgMGg1di02aC01djZ6bTYgMGg1di02aC01djZ6bS02LTdoNVY1aC01djZ6bTYtNnY2aDVWNWgtNXoiPjwvcGF0aD48L2c+CjxnIGlkPSJ2aWV3LXF1aWx0Ij48cGF0aCBkPSJNMTAgMThoNXYtNmgtNXY2em0tNiAwaDVWNUg0djEzem0xMiAwaDV2LTZoLTV2NnpNMTAgNXY2aDExVjVIMTB6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idmlldy1zdHJlYW0iPjxwYXRoIGQ9Ik00IDE4aDE3di02SDR2NnpNNCA1djZoMTdWNUg0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9InZpZXctd2VlayI+PHBhdGggZD0iTTYgNUgzYy0uNTUgMC0xIC40NS0xIDF2MTJjMCAuNTUuNDUgMSAxIDFoM2MuNTUgMCAxLS40NSAxLTFWNmMwLS41NS0uNDUtMS0xLTF6bTE0IDBoLTNjLS41NSAwLTEgLjQ1LTEgMXYxMmMwIC41NS40NSAxIDEgMWgzYy41NSAwIDEtLjQ1IDEtMVY2YzAtLjU1LS40NS0xLTEtMXptLTcgMGgtM2MtLjU1IDAtMSAuNDUtMSAxdjEyYzAgLjU1LjQ1IDEgMSAxaDNjLjU1IDAgMS0uNDUgMS0xVjZjMC0uNTUtLjQ1LTEtMS0xeiI+PC9wYXRoPjwvZz4KPGcgaWQ9InZpc2liaWxpdHkiPjxwYXRoIGQ9Ik0xMiA0LjVDNyA0LjUgMi43MyA3LjYxIDEgMTJjMS43MyA0LjM5IDYgNy41IDExIDcuNXM5LjI3LTMuMTEgMTEtNy41Yy0xLjczLTQuMzktNi03LjUtMTEtNy41ek0xMiAxN2MtMi43NiAwLTUtMi4yNC01LTVzMi4yNC01IDUtNSA1IDIuMjQgNSA1LTIuMjQgNS01IDV6bTAtOGMtMS42NiAwLTMgMS4zNC0zIDNzMS4zNCAzIDMgMyAzLTEuMzQgMy0zLTEuMzQtMy0zLTN6Ij48L3BhdGg+PC9nPgo8ZyBpZD0idmlzaWJpbGl0eS1vZmYiPjxwYXRoIGQ9Ik0xMiA3YzIuNzYgMCA1IDIuMjQgNSA1IDAgLjY1LS4xMyAxLjI2LS4zNiAxLjgzbDIuOTIgMi45MmMxLjUxLTEuMjYgMi43LTIuODkgMy40My00Ljc1LTEuNzMtNC4zOS02LTcuNS0xMS03LjUtMS40IDAtMi43NC4yNS0zLjk4LjdsMi4xNiAyLjE2QzEwLjc0IDcuMTMgMTEuMzUgNyAxMiA3ek0yIDQuMjdsMi4yOCAyLjI4LjQ2LjQ2QzMuMDggOC4zIDEuNzggMTAuMDIgMSAxMmMxLjczIDQuMzkgNiA3LjUgMTEgNy41IDEuNTUgMCAzLjAzLS4zIDQuMzgtLjg0bC40Mi40MkwxOS43MyAyMiAyMSAyMC43MyAzLjI3IDMgMiA0LjI3ek03LjUzIDkuOGwxLjU1IDEuNTVjLS4wNS4yMS0uMDguNDMtLjA4LjY1IDAgMS42NiAxLjM0IDMgMyAzIC4yMiAwIC40NC0uMDMuNjUtLjA4bDEuNTUgMS41NWMtLjY3LjMzLTEuNDEuNTMtMi4yLjUzLTIuNzYgMC01LTIuMjQtNS01IDAtLjc5LjItMS41My41My0yLjJ6bTQuMzEtLjc4bDMuMTUgMy4xNS4wMi0uMTZjMC0xLjY2LTEuMzQtMy0zLTNsLS4xNy4wMXoiPjwvcGF0aD48L2c+CjxnIGlkPSJ3YXJuaW5nIj48cGF0aCBkPSJNMSAyMWgyMkwxMiAyIDEgMjF6bTEyLTNoLTJ2LTJoMnYyem0wLTRoLTJ2LTRoMnY0eiI+PC9wYXRoPjwvZz4KPGcgaWQ9IndhdGNoLWxhdGVyIj48cGF0aCBkPSJNMTIgMkM2LjUgMiAyIDYuNSAyIDEyczQuNSAxMCAxMCAxMCAxMC00LjUgMTAtMTBTMTcuNSAyIDEyIDJ6bTQuMiAxNC4yTDExIDEzVjdoMS41djUuMmw0LjUgMi43LS44IDEuM3oiPjwvcGF0aD48L2c+CjxnIGlkPSJ3ZWVrZW5kIj48cGF0aCBkPSJNMjEgMTBjLTEuMSAwLTIgLjktMiAydjNINXYtM2MwLTEuMS0uOS0yLTItMnMtMiAuOS0yIDJ2NWMwIDEuMS45IDIgMiAyaDE4YzEuMSAwIDItLjkgMi0ydi01YzAtMS4xLS45LTItMi0yem0tMy01SDZjLTEuMSAwLTIgLjktMiAydjIuMTVjMS4xNi40MSAyIDEuNTEgMiAyLjgyVjE0aDEydi0yLjAzYzAtMS4zLjg0LTIuNCAyLTIuODJWN2MwLTEuMS0uOS0yLTItMnoiPjwvcGF0aD48L2c+CjxnIGlkPSJ3b3JrIj48cGF0aCBkPSJNMjAgNmgtNFY0YzAtMS4xMS0uODktMi0yLTJoLTRjLTEuMTEgMC0yIC44OS0yIDJ2Mkg0Yy0xLjExIDAtMS45OS44OS0xLjk5IDJMMiAxOWMwIDEuMTEuODkgMiAyIDJoMTZjMS4xMSAwIDItLjg5IDItMlY4YzAtMS4xMS0uODktMi0yLTJ6bS02IDBoLTRWNGg0djJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0ieW91dHViZS1zZWFyY2hlZC1mb3IiPjxwYXRoIGQ9Ik0xNy4wMSAxNGgtLjhsLS4yNy0uMjdjLjk4LTEuMTQgMS41Ny0yLjYxIDEuNTctNC4yMyAwLTMuNTktMi45MS02LjUtNi41LTYuNXMtNi41IDMtNi41IDYuNUgybDMuODQgNCA0LjE2LTRINi41MUM2LjUxIDcgOC41MyA1IDExLjAxIDVzNC41IDIuMDEgNC41IDQuNWMwIDIuNDgtMi4wMiA0LjUtNC41IDQuNS0uNjUgMC0xLjI2LS4xNC0xLjgyLS4zOEw3LjcxIDE1LjFjLjk3LjU3IDIuMDkuOSAzLjMuOSAxLjYxIDAgMy4wOC0uNTkgNC4yMi0xLjU3bC4yNy4yN3YuNzlsNS4wMSA0Ljk5TDIyIDE5bC00Ljk5LTV6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iem9vbS1pbiI+PHBhdGggZD0iTTE1LjUgMTRoLS43OWwtLjI4LS4yN0MxNS40MSAxMi41OSAxNiAxMS4xMSAxNiA5LjUgMTYgNS45MSAxMy4wOSAzIDkuNSAzUzMgNS45MSAzIDkuNSA1LjkxIDE2IDkuNSAxNmMxLjYxIDAgMy4wOS0uNTkgNC4yMy0xLjU3bC4yNy4yOHYuNzlsNSA0Ljk5TDIwLjQ5IDE5bC00Ljk5LTV6bS02IDBDNy4wMSAxNCA1IDExLjk5IDUgOS41UzcuMDEgNSA5LjUgNSAxNCA3LjAxIDE0IDkuNSAxMS45OSAxNCA5LjUgMTR6bTIuNS00aC0ydjJIOXYtMkg3VjloMlY3aDF2MmgydjF6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iem9vbS1vdXQiPjxwYXRoIGQ9Ik0xNS41IDE0aC0uNzlsLS4yOC0uMjdDMTUuNDEgMTIuNTkgMTYgMTEuMTEgMTYgOS41IDE2IDUuOTEgMTMuMDkgMyA5LjUgM1MzIDUuOTEgMyA5LjUgNS45MSAxNiA5LjUgMTZjMS42MSAwIDMuMDktLjU5IDQuMjMtMS41N2wuMjcuMjh2Ljc5bDUgNC45OUwyMC40OSAxOWwtNC45OS01em0tNiAwQzcuMDEgMTQgNSAxMS45OSA1IDkuNVM3LjAxIDUgOS41IDUgMTQgNy4wMSAxNCA5LjUgMTEuOTkgMTQgOS41IDE0ek03IDloNXYxSDd6Ij48L3BhdGg+PC9nPgo8L2RlZnM+PC9zdmc+CjwvaXJvbi1pY29uc2V0LXN2Zz5gO2RvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoZGJlLmNvbnRlbnQpO3ZhciAkZ3Q9eyJVKzAwMDgiOiJiYWNrc3BhY2UiLCJVKzAwMDkiOiJ0YWIiLCJVKzAwMUIiOiJlc2MiLCJVKzAwMjAiOiJzcGFjZSIsIlUrMDA3RiI6ImRlbCJ9LG1iZT17ODoiYmFja3NwYWNlIiw5OiJ0YWIiLDEzOiJlbnRlciIsMjc6ImVzYyIsMzM6InBhZ2V1cCIsMzQ6InBhZ2Vkb3duIiwzNToiZW5kIiwzNjoiaG9tZSIsMzI6InNwYWNlIiwzNzoibGVmdCIsMzg6InVwIiwzOToicmlnaHQiLDQwOiJkb3duIiw0NjoiZGVsIiwxMDY6IioifSxLZ3Q9e3NoaWZ0OiJzaGlmdEtleSIsY3RybDoiY3RybEtleSIsYWx0OiJhbHRLZXkiLG1ldGE6Im1ldGFLZXkifSxnYmU9L1thLXowLTkqXS8sX2JlPS9VXCsvLHliZT0vXmFycm93Lyx2YmU9L15zcGFjZShiYXIpPy8seGJlPS9eZXNjYXBlJC87ZnVuY3Rpb24gWmd0KGUsdCl7dmFyIHI9IiI7aWYoZSl7dmFyIG49ZS50b0xvd2VyQ2FzZSgpO249PT0iICJ8fHZiZS50ZXN0KG4pP3I9InNwYWNlIjp4YmUudGVzdChuKT9yPSJlc2MiOm4ubGVuZ3RoPT0xPyghdHx8Z2JlLnRlc3QobikpJiYocj1uKTp5YmUudGVzdChuKT9yPW4ucmVwbGFjZSgiYXJyb3ciLCIiKTpuPT0ibXVsdGlwbHkiP3I9IioiOnI9bn1yZXR1cm4gcn1mdW5jdGlvbiBiYmUoZSl7dmFyIHQ9IiI7cmV0dXJuIGUmJihlIGluICRndD90PSRndFtlXTpfYmUudGVzdChlKT8oZT1wYXJzZUludChlLnJlcGxhY2UoIlUrIiwiMHgiKSwxNiksdD1TdHJpbmcuZnJvbUNoYXJDb2RlKGUpLnRvTG93ZXJDYXNlKCkpOnQ9ZS50b0xvd2VyQ2FzZSgpKSx0fWZ1bmN0aW9uIHdiZShlKXt2YXIgdD0iIjtyZXR1cm4gTnVtYmVyKGUpJiYoZT49NjUmJmU8PTkwP3Q9U3RyaW5nLmZyb21DaGFyQ29kZSgzMitlKTplPj0xMTImJmU8PTEyMz90PSJmIisoZS0xMTIrMSk6ZT49NDgmJmU8PTU3P3Q9U3RyaW5nKGUtNDgpOmU+PTk2JiZlPD0xMDU/dD1TdHJpbmcoZS05Nik6dD1tYmVbZV0pLHR9ZnVuY3Rpb24gU2JlKGUsdCl7cmV0dXJuIGUua2V5P1pndChlLmtleSx0KTplLmRldGFpbCYmZS5kZXRhaWwua2V5P1pndChlLmRldGFpbC5rZXksdCk6YmJlKGUua2V5SWRlbnRpZmllcil8fHdiZShlLmtleUNvZGUpfHwiIn1mdW5jdGlvbiBKZ3QoZSx0KXt2YXIgcj1TYmUodCxlLmhhc01vZGlmaWVycyk7cmV0dXJuIHI9PT1lLmtleSYmKCFlLmhhc01vZGlmaWVyc3x8ISF0LnNoaWZ0S2V5PT0hIWUuc2hpZnRLZXkmJiEhdC5jdHJsS2V5PT0hIWUuY3RybEtleSYmISF0LmFsdEtleT09ISFlLmFsdEtleSYmISF0Lm1ldGFLZXk9PSEhZS5tZXRhS2V5KX1mdW5jdGlvbiBNYmUoZSl7cmV0dXJuIGUubGVuZ3RoPT09MT97Y29tYm86ZSxrZXk6ZSxldmVudDoia2V5ZG93biJ9OmUuc3BsaXQoIisiKS5yZWR1Y2UoZnVuY3Rpb24odCxyKXt2YXIgbj1yLnNwbGl0KCI6IiksaT1uWzBdLG89blsxXTtyZXR1cm4gaSBpbiBLZ3Q/KHRbS2d0W2ldXT0hMCx0Lmhhc01vZGlmaWVycz0hMCk6KHQua2V5PWksdC5ldmVudD1vfHwia2V5ZG93biIpLHR9LHtjb21ibzplLnNwbGl0KCI6Iikuc2hpZnQoKX0pfWZ1bmN0aW9uIFFndChlKXtyZXR1cm4gZS50cmltKCkuc3BsaXQoIiAiKS5tYXAoZnVuY3Rpb24odCl7cmV0dXJuIE1iZSh0KX0pfXZhciBPbz17cHJvcGVydGllczp7a2V5RXZlbnRUYXJnZXQ6e3R5cGU6T2JqZWN0LHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXN9fSxzdG9wS2V5Ym9hcmRFdmVudFByb3BhZ2F0aW9uOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LF9ib3VuZEtleUhhbmRsZXJzOnt0eXBlOkFycmF5LHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuW119fSxfaW1wZXJhdGl2ZUtleUJpbmRpbmdzOnt0eXBlOk9iamVjdCx2YWx1ZTpmdW5jdGlvbigpe3JldHVybnt9fX19LG9ic2VydmVyczpbIl9yZXNldEtleUV2ZW50TGlzdGVuZXJzKGtleUV2ZW50VGFyZ2V0LCBfYm91bmRLZXlIYW5kbGVycykiXSxrZXlCaW5kaW5nczp7fSxyZWdpc3RlcmVkOmZ1bmN0aW9uKCl7dGhpcy5fcHJlcEtleUJpbmRpbmdzKCl9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5fbGlzdGVuS2V5RXZlbnRMaXN0ZW5lcnMoKX0sZGV0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLl91bmxpc3RlbktleUV2ZW50TGlzdGVuZXJzKCl9LGFkZE93bktleUJpbmRpbmc6ZnVuY3Rpb24oZSx0KXt0aGlzLl9pbXBlcmF0aXZlS2V5QmluZGluZ3NbZV09dCx0aGlzLl9wcmVwS2V5QmluZGluZ3MoKSx0aGlzLl9yZXNldEtleUV2ZW50TGlzdGVuZXJzKCl9LHJlbW92ZU93bktleUJpbmRpbmdzOmZ1bmN0aW9uKCl7dGhpcy5faW1wZXJhdGl2ZUtleUJpbmRpbmdzPXt9LHRoaXMuX3ByZXBLZXlCaW5kaW5ncygpLHRoaXMuX3Jlc2V0S2V5RXZlbnRMaXN0ZW5lcnMoKX0sa2V5Ym9hcmRFdmVudE1hdGNoZXNLZXlzOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPVFndCh0KSxuPTA7bjxyLmxlbmd0aDsrK24paWYoSmd0KHJbbl0sZSkpcmV0dXJuITA7cmV0dXJuITF9LF9jb2xsZWN0S2V5QmluZGluZ3M6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLmJlaGF2aW9ycy5tYXAoZnVuY3Rpb24odCl7cmV0dXJuIHQua2V5QmluZGluZ3N9KTtyZXR1cm4gZS5pbmRleE9mKHRoaXMua2V5QmluZGluZ3MpPT09LTEmJmUucHVzaCh0aGlzLmtleUJpbmRpbmdzKSxlfSxfcHJlcEtleUJpbmRpbmdzOmZ1bmN0aW9uKCl7dGhpcy5fa2V5QmluZGluZ3M9e30sdGhpcy5fY29sbGVjdEtleUJpbmRpbmdzKCkuZm9yRWFjaChmdW5jdGlvbihyKXtmb3IodmFyIG4gaW4gcil0aGlzLl9hZGRLZXlCaW5kaW5nKG4scltuXSl9LHRoaXMpO2Zvcih2YXIgZSBpbiB0aGlzLl9pbXBlcmF0aXZlS2V5QmluZGluZ3MpdGhpcy5fYWRkS2V5QmluZGluZyhlLHRoaXMuX2ltcGVyYXRpdmVLZXlCaW5kaW5nc1tlXSk7Zm9yKHZhciB0IGluIHRoaXMuX2tleUJpbmRpbmdzKXRoaXMuX2tleUJpbmRpbmdzW3RdLnNvcnQoZnVuY3Rpb24ocixuKXt2YXIgaT1yWzBdLmhhc01vZGlmaWVycyxvPW5bMF0uaGFzTW9kaWZpZXJzO3JldHVybiBpPT09bz8wOmk/LTE6MX0pfSxfYWRkS2V5QmluZGluZzpmdW5jdGlvbihlLHQpe1FndChlKS5mb3JFYWNoKGZ1bmN0aW9uKHIpe3RoaXMuX2tleUJpbmRpbmdzW3IuZXZlbnRdPXRoaXMuX2tleUJpbmRpbmdzW3IuZXZlbnRdfHxbXSx0aGlzLl9rZXlCaW5kaW5nc1tyLmV2ZW50XS5wdXNoKFtyLHRdKX0sdGhpcyl9LF9yZXNldEtleUV2ZW50TGlzdGVuZXJzOmZ1bmN0aW9uKCl7dGhpcy5fdW5saXN0ZW5LZXlFdmVudExpc3RlbmVycygpLHRoaXMuaXNBdHRhY2hlZCYmdGhpcy5fbGlzdGVuS2V5RXZlbnRMaXN0ZW5lcnMoKX0sX2xpc3RlbktleUV2ZW50TGlzdGVuZXJzOmZ1bmN0aW9uKCl7IXRoaXMua2V5RXZlbnRUYXJnZXR8fE9iamVjdC5rZXlzKHRoaXMuX2tleUJpbmRpbmdzKS5mb3JFYWNoKGZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX2tleUJpbmRpbmdzW2VdLHI9dGhpcy5fb25LZXlCaW5kaW5nRXZlbnQuYmluZCh0aGlzLHQpO3RoaXMuX2JvdW5kS2V5SGFuZGxlcnMucHVzaChbdGhpcy5rZXlFdmVudFRhcmdldCxlLHJdKSx0aGlzLmtleUV2ZW50VGFyZ2V0LmFkZEV2ZW50TGlzdGVuZXIoZSxyKX0sdGhpcyl9LF91bmxpc3RlbktleUV2ZW50TGlzdGVuZXJzOmZ1bmN0aW9uKCl7Zm9yKHZhciBlLHQscixuO3RoaXMuX2JvdW5kS2V5SGFuZGxlcnMubGVuZ3RoOyllPXRoaXMuX2JvdW5kS2V5SGFuZGxlcnMucG9wKCksdD1lWzBdLHI9ZVsxXSxuPWVbMl0sdC5yZW1vdmVFdmVudExpc3RlbmVyKHIsbil9LF9vbktleUJpbmRpbmdFdmVudDpmdW5jdGlvbihlLHQpe2lmKHRoaXMuc3RvcEtleWJvYXJkRXZlbnRQcm9wYWdhdGlvbiYmdC5zdG9wUHJvcGFnYXRpb24oKSwhdC5kZWZhdWx0UHJldmVudGVkKWZvcih2YXIgcj0wO3I8ZS5sZW5ndGg7cisrKXt2YXIgbj1lW3JdWzBdLGk9ZVtyXVsxXTtpZihKZ3Qobix0KSYmKHRoaXMuX3RyaWdnZXJLZXlIYW5kbGVyKG4saSx0KSx0LmRlZmF1bHRQcmV2ZW50ZWQpKXJldHVybn19LF90cmlnZ2VyS2V5SGFuZGxlcjpmdW5jdGlvbihlLHQscil7dmFyIG49T2JqZWN0LmNyZWF0ZShlKTtuLmtleWJvYXJkRXZlbnQ9cjt2YXIgaT1uZXcgQ3VzdG9tRXZlbnQoZS5ldmVudCx7ZGV0YWlsOm4sY2FuY2VsYWJsZTohMH0pO3RoaXNbdF0uY2FsbCh0aGlzLGkpLGkuZGVmYXVsdFByZXZlbnRlZCYmci5wcmV2ZW50RGVmYXVsdCgpfX07dmFyIHk5PXtwcm9wZXJ0aWVzOntzY3JvbGxUYXJnZXQ6e3R5cGU6SFRNTEVsZW1lbnQsdmFsdWU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZGVmYXVsdFNjcm9sbFRhcmdldH19fSxvYnNlcnZlcnM6WyJfc2Nyb2xsVGFyZ2V0Q2hhbmdlZChzY3JvbGxUYXJnZXQsIGlzQXR0YWNoZWQpIl0sX3Nob3VsZEhhdmVMaXN0ZW5lcjohMCxfc2Nyb2xsVGFyZ2V0Q2hhbmdlZDpmdW5jdGlvbihlLHQpe3ZhciByO2lmKHRoaXMuX29sZFNjcm9sbFRhcmdldCYmKHRoaXMuX3RvZ2dsZVNjcm9sbExpc3RlbmVyKCExLHRoaXMuX29sZFNjcm9sbFRhcmdldCksdGhpcy5fb2xkU2Nyb2xsVGFyZ2V0PW51bGwpLCEhdClpZihlPT09ImRvY3VtZW50Iil0aGlzLnNjcm9sbFRhcmdldD10aGlzLl9kb2M7ZWxzZSBpZih0eXBlb2YgZT09InN0cmluZyIpe3ZhciBuPXRoaXMuZG9tSG9zdDt0aGlzLnNjcm9sbFRhcmdldD1uJiZuLiQ/bi4kW2VdOnp0KHRoaXMub3duZXJEb2N1bWVudCkucXVlcnlTZWxlY3RvcigiIyIrZSl9ZWxzZSB0aGlzLl9pc1ZhbGlkU2Nyb2xsVGFyZ2V0KCkmJih0aGlzLl9vbGRTY3JvbGxUYXJnZXQ9ZSx0aGlzLl90b2dnbGVTY3JvbGxMaXN0ZW5lcih0aGlzLl9zaG91bGRIYXZlTGlzdGVuZXIsZSkpfSxfc2Nyb2xsSGFuZGxlcjpmdW5jdGlvbigpe30sZ2V0IF9kZWZhdWx0U2Nyb2xsVGFyZ2V0KCl7cmV0dXJuIHRoaXMuX2RvY30sZ2V0IF9kb2MoKXtyZXR1cm4gdGhpcy5vd25lckRvY3VtZW50LmRvY3VtZW50RWxlbWVudH0sZ2V0IF9zY3JvbGxUb3AoKXtyZXR1cm4gdGhpcy5faXNWYWxpZFNjcm9sbFRhcmdldCgpP3RoaXMuc2Nyb2xsVGFyZ2V0PT09dGhpcy5fZG9jP3dpbmRvdy5wYWdlWU9mZnNldDp0aGlzLnNjcm9sbFRhcmdldC5zY3JvbGxUb3A6MH0sZ2V0IF9zY3JvbGxMZWZ0KCl7cmV0dXJuIHRoaXMuX2lzVmFsaWRTY3JvbGxUYXJnZXQoKT90aGlzLnNjcm9sbFRhcmdldD09PXRoaXMuX2RvYz93aW5kb3cucGFnZVhPZmZzZXQ6dGhpcy5zY3JvbGxUYXJnZXQuc2Nyb2xsTGVmdDowfSxzZXQgX3Njcm9sbFRvcChlKXt0aGlzLnNjcm9sbFRhcmdldD09PXRoaXMuX2RvYz93aW5kb3cuc2Nyb2xsVG8od2luZG93LnBhZ2VYT2Zmc2V0LGUpOnRoaXMuX2lzVmFsaWRTY3JvbGxUYXJnZXQoKSYmKHRoaXMuc2Nyb2xsVGFyZ2V0LnNjcm9sbFRvcD1lKX0sc2V0IF9zY3JvbGxMZWZ0KGUpe3RoaXMuc2Nyb2xsVGFyZ2V0PT09dGhpcy5fZG9jP3dpbmRvdy5zY3JvbGxUbyhlLHdpbmRvdy5wYWdlWU9mZnNldCk6dGhpcy5faXNWYWxpZFNjcm9sbFRhcmdldCgpJiYodGhpcy5zY3JvbGxUYXJnZXQuc2Nyb2xsTGVmdD1lKX0sc2Nyb2xsOmZ1bmN0aW9uKGUsdCl7dmFyIHI7dHlwZW9mIGU9PSJvYmplY3QiPyhyPWUubGVmdCx0PWUudG9wKTpyPWUscj1yfHwwLHQ9dHx8MCx0aGlzLnNjcm9sbFRhcmdldD09PXRoaXMuX2RvYz93aW5kb3cuc2Nyb2xsVG8ocix0KTp0aGlzLl9pc1ZhbGlkU2Nyb2xsVGFyZ2V0KCkmJih0aGlzLnNjcm9sbFRhcmdldC5zY3JvbGxMZWZ0PXIsdGhpcy5zY3JvbGxUYXJnZXQuc2Nyb2xsVG9wPXQpfSxnZXQgX3Njcm9sbFRhcmdldFdpZHRoKCl7cmV0dXJuIHRoaXMuX2lzVmFsaWRTY3JvbGxUYXJnZXQoKT90aGlzLnNjcm9sbFRhcmdldD09PXRoaXMuX2RvYz93aW5kb3cuaW5uZXJXaWR0aDp0aGlzLnNjcm9sbFRhcmdldC5vZmZzZXRXaWR0aDowfSxnZXQgX3Njcm9sbFRhcmdldEhlaWdodCgpe3JldHVybiB0aGlzLl9pc1ZhbGlkU2Nyb2xsVGFyZ2V0KCk/dGhpcy5zY3JvbGxUYXJnZXQ9PT10aGlzLl9kb2M/d2luZG93LmlubmVySGVpZ2h0OnRoaXMuc2Nyb2xsVGFyZ2V0Lm9mZnNldEhlaWdodDowfSxfaXNWYWxpZFNjcm9sbFRhcmdldDpmdW5jdGlvbigpe3JldHVybiB0aGlzLnNjcm9sbFRhcmdldCBpbnN0YW5jZW9mIEhUTUxFbGVtZW50fSxfdG9nZ2xlU2Nyb2xsTGlzdGVuZXI6ZnVuY3Rpb24oZSx0KXt2YXIgcj10PT09dGhpcy5fZG9jP3dpbmRvdzp0O2U/dGhpcy5fYm91bmRTY3JvbGxIYW5kbGVyfHwodGhpcy5fYm91bmRTY3JvbGxIYW5kbGVyPXRoaXMuX3Njcm9sbEhhbmRsZXIuYmluZCh0aGlzKSxyLmFkZEV2ZW50TGlzdGVuZXIoInNjcm9sbCIsdGhpcy5fYm91bmRTY3JvbGxIYW5kbGVyKSk6dGhpcy5fYm91bmRTY3JvbGxIYW5kbGVyJiYoci5yZW1vdmVFdmVudExpc3RlbmVyKCJzY3JvbGwiLHRoaXMuX2JvdW5kU2Nyb2xsSGFuZGxlciksdGhpcy5fYm91bmRTY3JvbGxIYW5kbGVyPW51bGwpfSx0b2dnbGVTY3JvbGxMaXN0ZW5lcjpmdW5jdGlvbihlKXt0aGlzLl9zaG91bGRIYXZlTGlzdGVuZXI9ZSx0aGlzLl90b2dnbGVTY3JvbGxMaXN0ZW5lcihlLHRoaXMuc2Nyb2xsVGFyZ2V0KX19O3ZhciB0MHQ9bmF2aWdhdG9yLnVzZXJBZ2VudC5tYXRjaCgvaVAoPzpob25lfGFkOyg/OiBVOyk/IENQVSkgT1MgKFxkKykvKSxFYmU9dDB0JiZ0MHRbMV0+PTgsZTB0PTMscjB0PSItMTAwMDBweCIsaEU9LTEwMDtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgfQoKICAgICAgQG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAoLXdlYmtpdC1tYXgtZGV2aWNlLXBpeGVsLXJhdGlvOiAxKSB7CiAgICAgICAgOmhvc3QgewogICAgICAgICAgd2lsbC1jaGFuZ2U6IHRyYW5zZm9ybTsKICAgICAgICB9CiAgICAgIH0KCiAgICAgICNpdGVtcyB7CiAgICAgICAgQGFwcGx5IC0taXJvbi1saXN0LWl0ZW1zLWNvbnRhaW5lcjsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgIH0KCiAgICAgIDpob3N0KDpub3QoW2dyaWRdKSkgI2l0ZW1zID4gOjpzbG90dGVkKCopIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgfQoKICAgICAgI2l0ZW1zID4gOjpzbG90dGVkKCopIHsKICAgICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICAgIG1hcmdpbjogMDsKICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgdG9wOiAwOwogICAgICAgIHdpbGwtY2hhbmdlOiB0cmFuc2Zvcm07CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPGFycmF5LXNlbGVjdG9yIGlkPSJzZWxlY3RvciIgaXRlbXM9Int7aXRlbXN9fSIgc2VsZWN0ZWQ9Int7c2VsZWN0ZWRJdGVtc319IiBzZWxlY3RlZC1pdGVtPSJ7e3NlbGVjdGVkSXRlbX19Ij48L2FycmF5LXNlbGVjdG9yPgoKICAgIDxkaXYgaWQ9Iml0ZW1zIj4KICAgICAgPHNsb3Q+PC9zbG90PgogICAgPC9kaXY+CmAsaXM6Imlyb24tbGlzdCIscHJvcGVydGllczp7aXRlbXM6e3R5cGU6QXJyYXl9LGFzOnt0eXBlOlN0cmluZyx2YWx1ZToiaXRlbSJ9LGluZGV4QXM6e3R5cGU6U3RyaW5nLHZhbHVlOiJpbmRleCJ9LHNlbGVjdGVkQXM6e3R5cGU6U3RyaW5nLHZhbHVlOiJzZWxlY3RlZCJ9LGdyaWQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxyZWZsZWN0VG9BdHRyaWJ1dGU6ITAsb2JzZXJ2ZXI6Il9ncmlkQ2hhbmdlZCJ9LHNlbGVjdGlvbkVuYWJsZWQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sc2VsZWN0ZWRJdGVtOnt0eXBlOk9iamVjdCxub3RpZnk6ITB9LHNlbGVjdGVkSXRlbXM6e3R5cGU6T2JqZWN0LG5vdGlmeTohMH0sbXVsdGlTZWxlY3Rpb246e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sc2Nyb2xsT2Zmc2V0Ont0eXBlOk51bWJlcix2YWx1ZTowfX0sb2JzZXJ2ZXJzOlsiX2l0ZW1zQ2hhbmdlZChpdGVtcy4qKSIsIl9zZWxlY3Rpb25FbmFibGVkQ2hhbmdlZChzZWxlY3Rpb25FbmFibGVkKSIsIl9tdWx0aVNlbGVjdGlvbkNoYW5nZWQobXVsdGlTZWxlY3Rpb24pIiwiX3NldE92ZXJmbG93KHNjcm9sbFRhcmdldCwgc2Nyb2xsT2Zmc2V0KSJdLGJlaGF2aW9yczpbSGd0LEpzLHk5LFdndF0sX3JhdGlvOi41LF9zY3JvbGxlclBhZGRpbmdUb3A6MCxfc2Nyb2xsUG9zaXRpb246MCxfcGh5c2ljYWxTaXplOjAsX3BoeXNpY2FsQXZlcmFnZTowLF9waHlzaWNhbEF2ZXJhZ2VDb3VudDowLF9waHlzaWNhbFRvcDowLF92aXJ0dWFsQ291bnQ6MCxfZXN0U2Nyb2xsSGVpZ2h0OjAsX3Njcm9sbEhlaWdodDowLF92aWV3cG9ydEhlaWdodDowLF92aWV3cG9ydFdpZHRoOjAsX3BoeXNpY2FsSXRlbXM6bnVsbCxfcGh5c2ljYWxTaXplczpudWxsLF9maXJzdFZpc2libGVJbmRleFZhbDpudWxsLF9sYXN0VmlzaWJsZUluZGV4VmFsOm51bGwsX21heFBhZ2VzOjIsX2ZvY3VzZWRJdGVtOm51bGwsX2ZvY3VzZWRWaXJ0dWFsSW5kZXg6LTEsX2ZvY3VzZWRQaHlzaWNhbEluZGV4Oi0xLF9vZmZzY3JlZW5Gb2N1c2VkSXRlbTpudWxsLF9mb2N1c0JhY2tmaWxsSXRlbTpudWxsLF9pdGVtc1BlclJvdzoxLF9pdGVtV2lkdGg6MCxfcm93SGVpZ2h0OjAsX3RlbXBsYXRlQ29zdDowLF9wYXJlbnRNb2RlbDohMCxnZXQgX3BoeXNpY2FsQm90dG9tKCl7cmV0dXJuIHRoaXMuX3BoeXNpY2FsVG9wK3RoaXMuX3BoeXNpY2FsU2l6ZX0sZ2V0IF9zY3JvbGxCb3R0b20oKXtyZXR1cm4gdGhpcy5fc2Nyb2xsUG9zaXRpb24rdGhpcy5fdmlld3BvcnRIZWlnaHR9LGdldCBfdmlydHVhbEVuZCgpe3JldHVybiB0aGlzLl92aXJ0dWFsU3RhcnQrdGhpcy5fcGh5c2ljYWxDb3VudC0xfSxnZXQgX2hpZGRlbkNvbnRlbnRTaXplKCl7dmFyIGU9dGhpcy5ncmlkP3RoaXMuX3BoeXNpY2FsUm93cyp0aGlzLl9yb3dIZWlnaHQ6dGhpcy5fcGh5c2ljYWxTaXplO3JldHVybiBlLXRoaXMuX3ZpZXdwb3J0SGVpZ2h0fSxnZXQgX2l0ZW1zUGFyZW50KCl7cmV0dXJuIHp0KHp0KHRoaXMuX3VzZXJUZW1wbGF0ZSkucGFyZW50Tm9kZSl9LGdldCBfbWF4U2Nyb2xsVG9wKCl7cmV0dXJuIHRoaXMuX2VzdFNjcm9sbEhlaWdodC10aGlzLl92aWV3cG9ydEhlaWdodCt0aGlzLl9zY3JvbGxPZmZzZXR9LGdldCBfbWF4VmlydHVhbFN0YXJ0KCl7dmFyIGU9dGhpcy5fY29udmVydEluZGV4VG9Db21wbGV0ZVJvdyh0aGlzLl92aXJ0dWFsQ291bnQpO3JldHVybiBNYXRoLm1heCgwLGUtdGhpcy5fcGh5c2ljYWxDb3VudCl9LHNldCBfdmlydHVhbFN0YXJ0KGUpe2U9dGhpcy5fY2xhbXAoZSwwLHRoaXMuX21heFZpcnR1YWxTdGFydCksdGhpcy5ncmlkJiYoZT1lLWUldGhpcy5faXRlbXNQZXJSb3cpLHRoaXMuX3ZpcnR1YWxTdGFydFZhbD1lfSxnZXQgX3ZpcnR1YWxTdGFydCgpe3JldHVybiB0aGlzLl92aXJ0dWFsU3RhcnRWYWx8fDB9LHNldCBfcGh5c2ljYWxTdGFydChlKXtlPWUldGhpcy5fcGh5c2ljYWxDb3VudCxlPDAmJihlPXRoaXMuX3BoeXNpY2FsQ291bnQrZSksdGhpcy5ncmlkJiYoZT1lLWUldGhpcy5faXRlbXNQZXJSb3cpLHRoaXMuX3BoeXNpY2FsU3RhcnRWYWw9ZX0sZ2V0IF9waHlzaWNhbFN0YXJ0KCl7cmV0dXJuIHRoaXMuX3BoeXNpY2FsU3RhcnRWYWx8fDB9LGdldCBfcGh5c2ljYWxFbmQoKXtyZXR1cm4odGhpcy5fcGh5c2ljYWxTdGFydCt0aGlzLl9waHlzaWNhbENvdW50LTEpJXRoaXMuX3BoeXNpY2FsQ291bnR9LHNldCBfcGh5c2ljYWxDb3VudChlKXt0aGlzLl9waHlzaWNhbENvdW50VmFsPWV9LGdldCBfcGh5c2ljYWxDb3VudCgpe3JldHVybiB0aGlzLl9waHlzaWNhbENvdW50VmFsfHwwfSxnZXQgX29wdFBoeXNpY2FsU2l6ZSgpe3JldHVybiB0aGlzLl92aWV3cG9ydEhlaWdodD09PTA/MS8wOnRoaXMuX3ZpZXdwb3J0SGVpZ2h0KnRoaXMuX21heFBhZ2VzfSxnZXQgX2lzVmlzaWJsZSgpe3JldHVybiBCb29sZWFuKHRoaXMub2Zmc2V0V2lkdGh8fHRoaXMub2Zmc2V0SGVpZ2h0KX0sZ2V0IGZpcnN0VmlzaWJsZUluZGV4KCl7dmFyIGU9dGhpcy5fZmlyc3RWaXNpYmxlSW5kZXhWYWw7aWYoZT09bnVsbCl7dmFyIHQ9dGhpcy5fcGh5c2ljYWxUb3ArdGhpcy5fc2Nyb2xsT2Zmc2V0O2U9dGhpcy5faXRlcmF0ZUl0ZW1zKGZ1bmN0aW9uKHIsbil7aWYodCs9dGhpcy5fZ2V0UGh5c2ljYWxTaXplSW5jcmVtZW50KHIpLHQ+dGhpcy5fc2Nyb2xsUG9zaXRpb24pcmV0dXJuIHRoaXMuZ3JpZD9uLW4ldGhpcy5faXRlbXNQZXJSb3c6bjtpZih0aGlzLmdyaWQmJnRoaXMuX3ZpcnR1YWxDb3VudC0xPT09bilyZXR1cm4gbi1uJXRoaXMuX2l0ZW1zUGVyUm93fSl8fDAsdGhpcy5fZmlyc3RWaXNpYmxlSW5kZXhWYWw9ZX1yZXR1cm4gZX0sZ2V0IGxhc3RWaXNpYmxlSW5kZXgoKXt2YXIgZT10aGlzLl9sYXN0VmlzaWJsZUluZGV4VmFsO2lmKGU9PW51bGwpe2lmKHRoaXMuZ3JpZCllPU1hdGgubWluKHRoaXMuX3ZpcnR1YWxDb3VudCx0aGlzLmZpcnN0VmlzaWJsZUluZGV4K3RoaXMuX2VzdFJvd3NJblZpZXcqdGhpcy5faXRlbXNQZXJSb3ctMSk7ZWxzZXt2YXIgdD10aGlzLl9waHlzaWNhbFRvcCt0aGlzLl9zY3JvbGxPZmZzZXQ7dGhpcy5faXRlcmF0ZUl0ZW1zKGZ1bmN0aW9uKHIsbil7dDx0aGlzLl9zY3JvbGxCb3R0b20mJihlPW4pLHQrPXRoaXMuX2dldFBoeXNpY2FsU2l6ZUluY3JlbWVudChyKX0pfXRoaXMuX2xhc3RWaXNpYmxlSW5kZXhWYWw9ZX1yZXR1cm4gZX0sZ2V0IF9kZWZhdWx0U2Nyb2xsVGFyZ2V0KCl7cmV0dXJuIHRoaXN9LGdldCBfdmlydHVhbFJvd0NvdW50KCl7cmV0dXJuIE1hdGguY2VpbCh0aGlzLl92aXJ0dWFsQ291bnQvdGhpcy5faXRlbXNQZXJSb3cpfSxnZXQgX2VzdFJvd3NJblZpZXcoKXtyZXR1cm4gTWF0aC5jZWlsKHRoaXMuX3ZpZXdwb3J0SGVpZ2h0L3RoaXMuX3Jvd0hlaWdodCl9LGdldCBfcGh5c2ljYWxSb3dzKCl7cmV0dXJuIE1hdGguY2VpbCh0aGlzLl9waHlzaWNhbENvdW50L3RoaXMuX2l0ZW1zUGVyUm93KX0sZ2V0IF9zY3JvbGxPZmZzZXQoKXtyZXR1cm4gdGhpcy5fc2Nyb2xsZXJQYWRkaW5nVG9wK3RoaXMuc2Nyb2xsT2Zmc2V0fSxyZWFkeTpmdW5jdGlvbigpe3RoaXMuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXMiLHRoaXMuX2RpZEZvY3VzLmJpbmQodGhpcyksITApfSxhdHRhY2hlZDpmdW5jdGlvbigpe3RoaXMuX2RlYm91bmNlKCJfcmVuZGVyIix0aGlzLl9yZW5kZXIsTmkpLHRoaXMubGlzdGVuKHRoaXMsImlyb24tcmVzaXplIiwiX3Jlc2l6ZUhhbmRsZXIiKSx0aGlzLmxpc3Rlbih0aGlzLCJrZXlkb3duIiwiX2tleWRvd25IYW5kbGVyIil9LGRldGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy51bmxpc3Rlbih0aGlzLCJpcm9uLXJlc2l6ZSIsIl9yZXNpemVIYW5kbGVyIiksdGhpcy51bmxpc3Rlbih0aGlzLCJrZXlkb3duIiwiX2tleWRvd25IYW5kbGVyIil9LF9zZXRPdmVyZmxvdzpmdW5jdGlvbihlKXt0aGlzLnN0eWxlLndlYmtpdE92ZXJmbG93U2Nyb2xsaW5nPWU9PT10aGlzPyJ0b3VjaCI6IiIsdGhpcy5zdHlsZS5vdmVyZmxvd1k9ZT09PXRoaXM/ImF1dG8iOiIiLHRoaXMuX2xhc3RWaXNpYmxlSW5kZXhWYWw9bnVsbCx0aGlzLl9maXJzdFZpc2libGVJbmRleFZhbD1udWxsLHRoaXMuX2RlYm91bmNlKCJfcmVuZGVyIix0aGlzLl9yZW5kZXIsTmkpfSx1cGRhdGVWaWV3cG9ydEJvdW5kYXJpZXM6ZnVuY3Rpb24oKXt2YXIgZT13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzKTt0aGlzLl9zY3JvbGxlclBhZGRpbmdUb3A9dGhpcy5zY3JvbGxUYXJnZXQ9PT10aGlzPzA6cGFyc2VJbnQoZVsicGFkZGluZy10b3AiXSwxMCksdGhpcy5faXNSVEw9Qm9vbGVhbihlLmRpcmVjdGlvbj09PSJydGwiKSx0aGlzLl92aWV3cG9ydFdpZHRoPXRoaXMuJC5pdGVtcy5vZmZzZXRXaWR0aCx0aGlzLl92aWV3cG9ydEhlaWdodD10aGlzLl9zY3JvbGxUYXJnZXRIZWlnaHQsdGhpcy5ncmlkJiZ0aGlzLl91cGRhdGVHcmlkTWV0cmljcygpfSxfc2Nyb2xsSGFuZGxlcjpmdW5jdGlvbigpe3ZhciBlPU1hdGgubWF4KDAsTWF0aC5taW4odGhpcy5fbWF4U2Nyb2xsVG9wLHRoaXMuX3Njcm9sbFRvcCkpLHQ9ZS10aGlzLl9zY3JvbGxQb3NpdGlvbixyPXQ+PTA7aWYodGhpcy5fc2Nyb2xsUG9zaXRpb249ZSx0aGlzLl9maXJzdFZpc2libGVJbmRleFZhbD1udWxsLHRoaXMuX2xhc3RWaXNpYmxlSW5kZXhWYWw9bnVsbCxNYXRoLmFicyh0KT50aGlzLl9waHlzaWNhbFNpemUmJnRoaXMuX3BoeXNpY2FsU2l6ZT4wKXt0PXQtdGhpcy5fc2Nyb2xsT2Zmc2V0O3ZhciBuPU1hdGgucm91bmQodC90aGlzLl9waHlzaWNhbEF2ZXJhZ2UpKnRoaXMuX2l0ZW1zUGVyUm93O3RoaXMuX3ZpcnR1YWxTdGFydD10aGlzLl92aXJ0dWFsU3RhcnQrbix0aGlzLl9waHlzaWNhbFN0YXJ0PXRoaXMuX3BoeXNpY2FsU3RhcnQrbix0aGlzLl9waHlzaWNhbFRvcD1NYXRoLm1pbihNYXRoLmZsb29yKHRoaXMuX3ZpcnR1YWxTdGFydC90aGlzLl9pdGVtc1BlclJvdykqdGhpcy5fcGh5c2ljYWxBdmVyYWdlLHRoaXMuX3Njcm9sbFBvc2l0aW9uKSx0aGlzLl91cGRhdGUoKX1lbHNlIGlmKHRoaXMuX3BoeXNpY2FsQ291bnQ+MCl7dmFyIGk9dGhpcy5fZ2V0UmV1c2FibGVzKHIpO3I/KHRoaXMuX3BoeXNpY2FsVG9wPWkucGh5c2ljYWxUb3AsdGhpcy5fdmlydHVhbFN0YXJ0PXRoaXMuX3ZpcnR1YWxTdGFydCtpLmluZGV4ZXMubGVuZ3RoLHRoaXMuX3BoeXNpY2FsU3RhcnQ9dGhpcy5fcGh5c2ljYWxTdGFydCtpLmluZGV4ZXMubGVuZ3RoKToodGhpcy5fdmlydHVhbFN0YXJ0PXRoaXMuX3ZpcnR1YWxTdGFydC1pLmluZGV4ZXMubGVuZ3RoLHRoaXMuX3BoeXNpY2FsU3RhcnQ9dGhpcy5fcGh5c2ljYWxTdGFydC1pLmluZGV4ZXMubGVuZ3RoKSx0aGlzLl91cGRhdGUoaS5pbmRleGVzLHI/bnVsbDppLmluZGV4ZXMpLHRoaXMuX2RlYm91bmNlKCJfaW5jcmVhc2VQb29sSWZOZWVkZWQiLHRoaXMuX2luY3JlYXNlUG9vbElmTmVlZGVkLmJpbmQodGhpcywwKSxjaSl9fSxfZ2V0UmV1c2FibGVzOmZ1bmN0aW9uKGUpe3ZhciB0LHIsbixpLG89W10sYT10aGlzLl9oaWRkZW5Db250ZW50U2l6ZSp0aGlzLl9yYXRpbyxzPXRoaXMuX3ZpcnR1YWxTdGFydCxsPXRoaXMuX3ZpcnR1YWxFbmQsYz10aGlzLl9waHlzaWNhbENvdW50LHU9dGhpcy5fcGh5c2ljYWxUb3ArdGhpcy5fc2Nyb2xsT2Zmc2V0LGg9dGhpcy5fcGh5c2ljYWxCb3R0b20rdGhpcy5fc2Nyb2xsT2Zmc2V0LGY9dGhpcy5fc2Nyb2xsUG9zaXRpb24scD10aGlzLl9zY3JvbGxCb3R0b207Zm9yKGU/KHQ9dGhpcy5fcGh5c2ljYWxTdGFydCxyPXRoaXMuX3BoeXNpY2FsRW5kLG49Zi11KToodD10aGlzLl9waHlzaWNhbEVuZCxyPXRoaXMuX3BoeXNpY2FsU3RhcnQsbj1oLXApO2k9dGhpcy5fZ2V0UGh5c2ljYWxTaXplSW5jcmVtZW50KHQpLG49bi1pLCEoby5sZW5ndGg+PWN8fG48PWEpOylpZihlKXtpZihsK28ubGVuZ3RoKzE+PXRoaXMuX3ZpcnR1YWxDb3VudHx8dStpPj1mLXRoaXMuX3Njcm9sbE9mZnNldClicmVhaztvLnB1c2godCksdT11K2ksdD0odCsxKSVjfWVsc2V7aWYocy1vLmxlbmd0aDw9MHx8dSt0aGlzLl9waHlzaWNhbFNpemUtaTw9cClicmVhaztvLnB1c2godCksdT11LWksdD10PT09MD9jLTE6dC0xfXJldHVybntpbmRleGVzOm8scGh5c2ljYWxUb3A6dS10aGlzLl9zY3JvbGxPZmZzZXR9fSxfdXBkYXRlOmZ1bmN0aW9uKGUsdCl7aWYoIShlJiZlLmxlbmd0aD09PTB8fHRoaXMuX3BoeXNpY2FsQ291bnQ9PT0wKSl7aWYodGhpcy5fbWFuYWdlRm9jdXMoKSx0aGlzLl9hc3NpZ25Nb2RlbHMoZSksdGhpcy5fdXBkYXRlTWV0cmljcyhlKSx0KWZvcig7dC5sZW5ndGg7KXt2YXIgcj10LnBvcCgpO3RoaXMuX3BoeXNpY2FsVG9wLT10aGlzLl9nZXRQaHlzaWNhbFNpemVJbmNyZW1lbnQocil9dGhpcy5fcG9zaXRpb25JdGVtcygpLHRoaXMuX3VwZGF0ZVNjcm9sbGVyU2l6ZSgpfX0sX2NyZWF0ZVBvb2w6ZnVuY3Rpb24oZSl7dGhpcy5fZW5zdXJlVGVtcGxhdGl6ZWQoKTt2YXIgdCxyLG49bmV3IEFycmF5KGUpO2Zvcih0PTA7dDxlO3QrKylyPXRoaXMuc3RhbXAobnVsbCksblt0XT1yLnJvb3QucXVlcnlTZWxlY3RvcigiKiIpLHRoaXMuX2l0ZW1zUGFyZW50LmFwcGVuZENoaWxkKHIucm9vdCk7cmV0dXJuIG59LF9pc0NsaWVudEZ1bGw6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fc2Nyb2xsQm90dG9tIT0wJiZ0aGlzLl9waHlzaWNhbEJvdHRvbS0xPj10aGlzLl9zY3JvbGxCb3R0b20mJnRoaXMuX3BoeXNpY2FsVG9wPD10aGlzLl9zY3JvbGxQb3NpdGlvbn0sX2luY3JlYXNlUG9vbElmTmVlZGVkOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX2NsYW1wKHRoaXMuX3BoeXNpY2FsQ291bnQrZSxlMHQsdGhpcy5fdmlydHVhbENvdW50LXRoaXMuX3ZpcnR1YWxTdGFydCk7aWYodD10aGlzLl9jb252ZXJ0SW5kZXhUb0NvbXBsZXRlUm93KHQpLHRoaXMuZ3JpZCl7dmFyIHI9dCV0aGlzLl9pdGVtc1BlclJvdztyJiZ0LXI8PXRoaXMuX3BoeXNpY2FsQ291bnQmJih0Kz10aGlzLl9pdGVtc1BlclJvdyksdC09cn12YXIgbj10LXRoaXMuX3BoeXNpY2FsQ291bnQsaT1NYXRoLnJvdW5kKHRoaXMuX3BoeXNpY2FsQ291bnQqLjUpO2lmKCEobjwwKSl7aWYobj4wKXt2YXIgbz13aW5kb3cucGVyZm9ybWFuY2Uubm93KCk7W10ucHVzaC5hcHBseSh0aGlzLl9waHlzaWNhbEl0ZW1zLHRoaXMuX2NyZWF0ZVBvb2wobikpO2Zvcih2YXIgYT0wO2E8bjthKyspdGhpcy5fcGh5c2ljYWxTaXplcy5wdXNoKDApO3RoaXMuX3BoeXNpY2FsQ291bnQ9dGhpcy5fcGh5c2ljYWxDb3VudCtuLHRoaXMuX3BoeXNpY2FsU3RhcnQ+dGhpcy5fcGh5c2ljYWxFbmQmJnRoaXMuX2lzSW5kZXhSZW5kZXJlZCh0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4KSYmdGhpcy5fZ2V0UGh5c2ljYWxJbmRleCh0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4KTx0aGlzLl9waHlzaWNhbEVuZCYmKHRoaXMuX3BoeXNpY2FsU3RhcnQ9dGhpcy5fcGh5c2ljYWxTdGFydCtuKSx0aGlzLl91cGRhdGUoKSx0aGlzLl90ZW1wbGF0ZUNvc3Q9KHdpbmRvdy5wZXJmb3JtYW5jZS5ub3coKS1vKS9uLGk9TWF0aC5yb3VuZCh0aGlzLl9waHlzaWNhbENvdW50Ki41KX10aGlzLl92aXJ0dWFsRW5kPj10aGlzLl92aXJ0dWFsQ291bnQtMXx8aT09PTB8fCh0aGlzLl9pc0NsaWVudEZ1bGwoKT90aGlzLl9waHlzaWNhbFNpemU8dGhpcy5fb3B0UGh5c2ljYWxTaXplJiZ0aGlzLl9kZWJvdW5jZSgiX2luY3JlYXNlUG9vbElmTmVlZGVkIix0aGlzLl9pbmNyZWFzZVBvb2xJZk5lZWRlZC5iaW5kKHRoaXMsdGhpcy5fY2xhbXAoTWF0aC5yb3VuZCg1MC90aGlzLl90ZW1wbGF0ZUNvc3QpLDEsaSkpLGt4KTp0aGlzLl9kZWJvdW5jZSgiX2luY3JlYXNlUG9vbElmTmVlZGVkIix0aGlzLl9pbmNyZWFzZVBvb2xJZk5lZWRlZC5iaW5kKHRoaXMsaSksY2kpKX19LF9yZW5kZXI6ZnVuY3Rpb24oKXtpZighKCF0aGlzLmlzQXR0YWNoZWR8fCF0aGlzLl9pc1Zpc2libGUpKWlmKHRoaXMuX3BoeXNpY2FsQ291bnQhPT0wKXt2YXIgZT10aGlzLl9nZXRSZXVzYWJsZXMoITApO3RoaXMuX3BoeXNpY2FsVG9wPWUucGh5c2ljYWxUb3AsdGhpcy5fdmlydHVhbFN0YXJ0PXRoaXMuX3ZpcnR1YWxTdGFydCtlLmluZGV4ZXMubGVuZ3RoLHRoaXMuX3BoeXNpY2FsU3RhcnQ9dGhpcy5fcGh5c2ljYWxTdGFydCtlLmluZGV4ZXMubGVuZ3RoLHRoaXMuX3VwZGF0ZShlLmluZGV4ZXMpLHRoaXMuX3VwZGF0ZSgpLHRoaXMuX2luY3JlYXNlUG9vbElmTmVlZGVkKDApfWVsc2UgdGhpcy5fdmlydHVhbENvdW50PjAmJih0aGlzLnVwZGF0ZVZpZXdwb3J0Qm91bmRhcmllcygpLHRoaXMuX2luY3JlYXNlUG9vbElmTmVlZGVkKGUwdCkpfSxfZW5zdXJlVGVtcGxhdGl6ZWQ6ZnVuY3Rpb24oKXtpZighdGhpcy5jdG9yKXt0aGlzLl91c2VyVGVtcGxhdGU9dGhpcy5xdWVyeUVmZmVjdGl2ZUNoaWxkcmVuKCJ0ZW1wbGF0ZSIpLHRoaXMuX3VzZXJUZW1wbGF0ZXx8Y29uc29sZS53YXJuKCJpcm9uLWxpc3QgcmVxdWlyZXMgYSB0ZW1wbGF0ZSB0byBiZSBwcm92aWRlZCBpbiBsaWdodC1kb20iKTt2YXIgZT17fTtlLl9fa2V5X189ITAsZVt0aGlzLmFzXT0hMCxlW3RoaXMuaW5kZXhBc109ITAsZVt0aGlzLnNlbGVjdGVkQXNdPSEwLGUudGFiSW5kZXg9ITAsdGhpcy5faW5zdGFuY2VQcm9wcz1lLHRoaXMudGVtcGxhdGl6ZSh0aGlzLl91c2VyVGVtcGxhdGUsdGhpcy5tdXRhYmxlRGF0YSl9fSxfZ3JpZENoYW5nZWQ6ZnVuY3Rpb24oZSx0KXt0eXBlb2YgdCE9InVuZGVmaW5lZCImJih0aGlzLm5vdGlmeVJlc2l6ZSgpLHVpKCksZSYmdGhpcy5fdXBkYXRlR3JpZE1ldHJpY3MoKSl9LF9pdGVtc0NoYW5nZWQ6ZnVuY3Rpb24oZSl7aWYoZS5wYXRoPT09Iml0ZW1zIil0aGlzLl92aXJ0dWFsU3RhcnQ9MCx0aGlzLl9waHlzaWNhbFRvcD0wLHRoaXMuX3ZpcnR1YWxDb3VudD10aGlzLml0ZW1zP3RoaXMuaXRlbXMubGVuZ3RoOjAsdGhpcy5fcGh5c2ljYWxJbmRleEZvcktleT17fSx0aGlzLl9maXJzdFZpc2libGVJbmRleFZhbD1udWxsLHRoaXMuX2xhc3RWaXNpYmxlSW5kZXhWYWw9bnVsbCx0aGlzLl9waHlzaWNhbENvdW50PXRoaXMuX3BoeXNpY2FsQ291bnR8fDAsdGhpcy5fcGh5c2ljYWxJdGVtcz10aGlzLl9waHlzaWNhbEl0ZW1zfHxbXSx0aGlzLl9waHlzaWNhbFNpemVzPXRoaXMuX3BoeXNpY2FsU2l6ZXN8fFtdLHRoaXMuX3BoeXNpY2FsU3RhcnQ9MCx0aGlzLl9zY3JvbGxUb3A+dGhpcy5fc2Nyb2xsT2Zmc2V0JiZ0aGlzLl9yZXNldFNjcm9sbFBvc2l0aW9uKDApLHRoaXMuX3JlbW92ZUZvY3VzZWRJdGVtKCksdGhpcy5fZGVib3VuY2UoIl9yZW5kZXIiLHRoaXMuX3JlbmRlcixOaSk7ZWxzZSBpZihlLnBhdGg9PT0iaXRlbXMuc3BsaWNlcyIpe3RoaXMuX2FkanVzdFZpcnR1YWxJbmRleChlLnZhbHVlLmluZGV4U3BsaWNlcyksdGhpcy5fdmlydHVhbENvdW50PXRoaXMuaXRlbXM/dGhpcy5pdGVtcy5sZW5ndGg6MDt2YXIgdD1lLnZhbHVlLmluZGV4U3BsaWNlcy5zb21lKGZ1bmN0aW9uKGkpe3JldHVybiBpLmFkZGVkQ291bnQ+MHx8aS5yZW1vdmVkLmxlbmd0aD4wfSk7aWYodCl7dmFyIHI9dGhpcy5fZ2V0QWN0aXZlRWxlbWVudCgpO3RoaXMuY29udGFpbnMocikmJnIuYmx1cigpfXZhciBuPWUudmFsdWUuaW5kZXhTcGxpY2VzLnNvbWUoZnVuY3Rpb24oaSl7cmV0dXJuIGkuaW5kZXgraS5hZGRlZENvdW50Pj10aGlzLl92aXJ0dWFsU3RhcnQmJmkuaW5kZXg8PXRoaXMuX3ZpcnR1YWxFbmR9LHRoaXMpOyghdGhpcy5faXNDbGllbnRGdWxsKCl8fG4pJiZ0aGlzLl9kZWJvdW5jZSgiX3JlbmRlciIsdGhpcy5fcmVuZGVyLE5pKX1lbHNlIGUucGF0aCE9PSJpdGVtcy5sZW5ndGgiJiZ0aGlzLl9mb3J3YXJkSXRlbVBhdGgoZS5wYXRoLGUudmFsdWUpfSxfZm9yd2FyZEl0ZW1QYXRoOmZ1bmN0aW9uKGUsdCl7ZT1lLnNsaWNlKDYpO3ZhciByPWUuaW5kZXhPZigiLiIpO3I9PT0tMSYmKHI9ZS5sZW5ndGgpO3ZhciBuLGksbyxhPXRoaXMubW9kZWxGb3JFbGVtZW50KHRoaXMuX29mZnNjcmVlbkZvY3VzZWRJdGVtKSxzPXBhcnNlSW50KGUuc3Vic3RyaW5nKDAsciksMTApO249dGhpcy5faXNJbmRleFJlbmRlcmVkKHMpLG4/KGk9dGhpcy5fZ2V0UGh5c2ljYWxJbmRleChzKSxvPXRoaXMubW9kZWxGb3JFbGVtZW50KHRoaXMuX3BoeXNpY2FsSXRlbXNbaV0pKTphJiYobz1hKSwhKCFvfHxvW3RoaXMuaW5kZXhBc10hPT1zKSYmKGU9ZS5zdWJzdHJpbmcocisxKSxlPXRoaXMuYXMrKGU/Ii4iK2U6IiIpLG8uX3NldFBlbmRpbmdQcm9wZXJ0eU9yUGF0aChlLHQsITEsITApLG8uX2ZsdXNoUHJvcGVydGllcyYmby5fZmx1c2hQcm9wZXJ0aWVzKCksbiYmKHRoaXMuX3VwZGF0ZU1ldHJpY3MoW2ldKSx0aGlzLl9wb3NpdGlvbkl0ZW1zKCksdGhpcy5fdXBkYXRlU2Nyb2xsZXJTaXplKCkpKX0sX2FkanVzdFZpcnR1YWxJbmRleDpmdW5jdGlvbihlKXtlLmZvckVhY2goZnVuY3Rpb24odCl7aWYodC5yZW1vdmVkLmZvckVhY2godGhpcy5fcmVtb3ZlSXRlbSx0aGlzKSx0LmluZGV4PHRoaXMuX3ZpcnR1YWxTdGFydCl7dmFyIHI9TWF0aC5tYXgodC5hZGRlZENvdW50LXQucmVtb3ZlZC5sZW5ndGgsdC5pbmRleC10aGlzLl92aXJ0dWFsU3RhcnQpO3RoaXMuX3ZpcnR1YWxTdGFydD10aGlzLl92aXJ0dWFsU3RhcnQrcix0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4Pj0wJiYodGhpcy5fZm9jdXNlZFZpcnR1YWxJbmRleD10aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4K3IpfX0sdGhpcyl9LF9yZW1vdmVJdGVtOmZ1bmN0aW9uKGUpe3RoaXMuJC5zZWxlY3Rvci5kZXNlbGVjdChlKSx0aGlzLl9mb2N1c2VkSXRlbSYmdGhpcy5tb2RlbEZvckVsZW1lbnQodGhpcy5fZm9jdXNlZEl0ZW0pW3RoaXMuYXNdPT09ZSYmdGhpcy5fcmVtb3ZlRm9jdXNlZEl0ZW0oKX0sX2l0ZXJhdGVJdGVtczpmdW5jdGlvbihlLHQpe3ZhciByLG4saSxvO2lmKGFyZ3VtZW50cy5sZW5ndGg9PT0yJiZ0KXtmb3Iobz0wO288dC5sZW5ndGg7bysrKWlmKHI9dFtvXSxuPXRoaXMuX2NvbXB1dGVWaWR4KHIpLChpPWUuY2FsbCh0aGlzLHIsbikpIT1udWxsKXJldHVybiBpfWVsc2V7Zm9yKHI9dGhpcy5fcGh5c2ljYWxTdGFydCxuPXRoaXMuX3ZpcnR1YWxTdGFydDtyPHRoaXMuX3BoeXNpY2FsQ291bnQ7cisrLG4rKylpZigoaT1lLmNhbGwodGhpcyxyLG4pKSE9bnVsbClyZXR1cm4gaTtmb3Iocj0wO3I8dGhpcy5fcGh5c2ljYWxTdGFydDtyKyssbisrKWlmKChpPWUuY2FsbCh0aGlzLHIsbikpIT1udWxsKXJldHVybiBpfX0sX2NvbXB1dGVWaWR4OmZ1bmN0aW9uKGUpe3JldHVybiBlPj10aGlzLl9waHlzaWNhbFN0YXJ0P3RoaXMuX3ZpcnR1YWxTdGFydCsoZS10aGlzLl9waHlzaWNhbFN0YXJ0KTp0aGlzLl92aXJ0dWFsU3RhcnQrKHRoaXMuX3BoeXNpY2FsQ291bnQtdGhpcy5fcGh5c2ljYWxTdGFydCkrZX0sX2Fzc2lnbk1vZGVsczpmdW5jdGlvbihlKXt0aGlzLl9pdGVyYXRlSXRlbXMoZnVuY3Rpb24odCxyKXt2YXIgbj10aGlzLl9waHlzaWNhbEl0ZW1zW3RdLGk9dGhpcy5pdGVtcyYmdGhpcy5pdGVtc1tyXTtpZihpIT1udWxsKXt2YXIgbz10aGlzLm1vZGVsRm9yRWxlbWVudChuKTtvLl9fa2V5X189bnVsbCx0aGlzLl9mb3J3YXJkUHJvcGVydHkobyx0aGlzLmFzLGkpLHRoaXMuX2ZvcndhcmRQcm9wZXJ0eShvLHRoaXMuc2VsZWN0ZWRBcyx0aGlzLiQuc2VsZWN0b3IuaXNTZWxlY3RlZChpKSksdGhpcy5fZm9yd2FyZFByb3BlcnR5KG8sdGhpcy5pbmRleEFzLHIpLHRoaXMuX2ZvcndhcmRQcm9wZXJ0eShvLCJ0YWJJbmRleCIsdGhpcy5fZm9jdXNlZFZpcnR1YWxJbmRleD09PXI/MDotMSksdGhpcy5fcGh5c2ljYWxJbmRleEZvcktleVtvLl9fa2V5X19dPXQsby5fZmx1c2hQcm9wZXJ0aWVzJiZvLl9mbHVzaFByb3BlcnRpZXMoITApLG4ucmVtb3ZlQXR0cmlidXRlKCJoaWRkZW4iKX1lbHNlIG4uc2V0QXR0cmlidXRlKCJoaWRkZW4iLCIiKX0sZSl9LF91cGRhdGVNZXRyaWNzOmZ1bmN0aW9uKGUpe3VpKCk7dmFyIHQ9MCxyPTAsbj10aGlzLl9waHlzaWNhbEF2ZXJhZ2VDb3VudCxpPXRoaXMuX3BoeXNpY2FsQXZlcmFnZTt0aGlzLl9pdGVyYXRlSXRlbXMoZnVuY3Rpb24obyxhKXtyKz10aGlzLl9waHlzaWNhbFNpemVzW29dLHRoaXMuX3BoeXNpY2FsU2l6ZXNbb109dGhpcy5fcGh5c2ljYWxJdGVtc1tvXS5vZmZzZXRIZWlnaHQsdCs9dGhpcy5fcGh5c2ljYWxTaXplc1tvXSx0aGlzLl9waHlzaWNhbEF2ZXJhZ2VDb3VudCs9dGhpcy5fcGh5c2ljYWxTaXplc1tvXT8xOjB9LGUpLHRoaXMuZ3JpZD8odGhpcy5fdXBkYXRlR3JpZE1ldHJpY3MoKSx0aGlzLl9waHlzaWNhbFNpemU9TWF0aC5jZWlsKHRoaXMuX3BoeXNpY2FsQ291bnQvdGhpcy5faXRlbXNQZXJSb3cpKnRoaXMuX3Jvd0hlaWdodCk6KHI9dGhpcy5faXRlbXNQZXJSb3c9PT0xP3I6TWF0aC5jZWlsKHRoaXMuX3BoeXNpY2FsQ291bnQvdGhpcy5faXRlbXNQZXJSb3cpKnRoaXMuX3Jvd0hlaWdodCx0aGlzLl9waHlzaWNhbFNpemU9dGhpcy5fcGh5c2ljYWxTaXplK3Qtcix0aGlzLl9pdGVtc1BlclJvdz0xKSx0aGlzLl9waHlzaWNhbEF2ZXJhZ2VDb3VudCE9PW4mJih0aGlzLl9waHlzaWNhbEF2ZXJhZ2U9TWF0aC5yb3VuZCgoaSpuK3QpL3RoaXMuX3BoeXNpY2FsQXZlcmFnZUNvdW50KSl9LF91cGRhdGVHcmlkTWV0cmljczpmdW5jdGlvbigpe3RoaXMuX2l0ZW1XaWR0aD10aGlzLl9waHlzaWNhbENvdW50PjA/dGhpcy5fcGh5c2ljYWxJdGVtc1swXS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS53aWR0aDoyMDAsdGhpcy5fcm93SGVpZ2h0PXRoaXMuX3BoeXNpY2FsQ291bnQ+MD90aGlzLl9waHlzaWNhbEl0ZW1zWzBdLm9mZnNldEhlaWdodDoyMDAsdGhpcy5faXRlbXNQZXJSb3c9dGhpcy5faXRlbVdpZHRoP01hdGguZmxvb3IodGhpcy5fdmlld3BvcnRXaWR0aC90aGlzLl9pdGVtV2lkdGgpOnRoaXMuX2l0ZW1zUGVyUm93fSxfcG9zaXRpb25JdGVtczpmdW5jdGlvbigpe3RoaXMuX2FkanVzdFNjcm9sbFBvc2l0aW9uKCk7dmFyIGU9dGhpcy5fcGh5c2ljYWxUb3A7aWYodGhpcy5ncmlkKXt2YXIgdD10aGlzLl9pdGVtc1BlclJvdyp0aGlzLl9pdGVtV2lkdGgscj0odGhpcy5fdmlld3BvcnRXaWR0aC10KS8yO3RoaXMuX2l0ZXJhdGVJdGVtcyhmdW5jdGlvbihuLGkpe3ZhciBvPWkldGhpcy5faXRlbXNQZXJSb3csYT1NYXRoLmZsb29yKG8qdGhpcy5faXRlbVdpZHRoK3IpO3RoaXMuX2lzUlRMJiYoYT1hKi0xKSx0aGlzLnRyYW5zbGF0ZTNkKGErInB4IixlKyJweCIsMCx0aGlzLl9waHlzaWNhbEl0ZW1zW25dKSx0aGlzLl9zaG91bGRSZW5kZXJOZXh0Um93KGkpJiYoZSs9dGhpcy5fcm93SGVpZ2h0KX0pfWVsc2V7bGV0IG49W107dGhpcy5faXRlcmF0ZUl0ZW1zKGZ1bmN0aW9uKGksbyl7bGV0IGE9dGhpcy5fcGh5c2ljYWxJdGVtc1tpXTt0aGlzLnRyYW5zbGF0ZTNkKDAsZSsicHgiLDAsYSksZSs9dGhpcy5fcGh5c2ljYWxTaXplc1tpXTtsZXQgcz1hLmlkO3MmJm4ucHVzaChzKX0pLG4ubGVuZ3RoJiZ0aGlzLnNldEF0dHJpYnV0ZSgiYXJpYS1vd25zIixuLmpvaW4oIiAiKSl9fSxfZ2V0UGh5c2ljYWxTaXplSW5jcmVtZW50OmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLmdyaWQ/dGhpcy5fY29tcHV0ZVZpZHgoZSkldGhpcy5faXRlbXNQZXJSb3chPT10aGlzLl9pdGVtc1BlclJvdy0xPzA6dGhpcy5fcm93SGVpZ2h0OnRoaXMuX3BoeXNpY2FsU2l6ZXNbZV19LF9zaG91bGRSZW5kZXJOZXh0Um93OmZ1bmN0aW9uKGUpe3JldHVybiBlJXRoaXMuX2l0ZW1zUGVyUm93PT09dGhpcy5faXRlbXNQZXJSb3ctMX0sX2FkanVzdFNjcm9sbFBvc2l0aW9uOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5fdmlydHVhbFN0YXJ0PT09MD90aGlzLl9waHlzaWNhbFRvcDpNYXRoLm1pbih0aGlzLl9zY3JvbGxQb3NpdGlvbit0aGlzLl9waHlzaWNhbFRvcCwwKTtpZihlIT09MCl7dGhpcy5fcGh5c2ljYWxUb3A9dGhpcy5fcGh5c2ljYWxUb3AtZTt2YXIgdD10aGlzLl9zY3JvbGxQb3NpdGlvbjshRWJlJiZ0PjAmJnRoaXMuX3Jlc2V0U2Nyb2xsUG9zaXRpb24odC1lKX19LF9yZXNldFNjcm9sbFBvc2l0aW9uOmZ1bmN0aW9uKGUpe3RoaXMuc2Nyb2xsVGFyZ2V0JiZlPj0wJiYodGhpcy5fc2Nyb2xsVG9wPWUsdGhpcy5fc2Nyb2xsUG9zaXRpb249dGhpcy5fc2Nyb2xsVG9wKX0sX3VwZGF0ZVNjcm9sbGVyU2l6ZTpmdW5jdGlvbihlKXt0aGlzLmdyaWQ/dGhpcy5fZXN0U2Nyb2xsSGVpZ2h0PXRoaXMuX3ZpcnR1YWxSb3dDb3VudCp0aGlzLl9yb3dIZWlnaHQ6dGhpcy5fZXN0U2Nyb2xsSGVpZ2h0PXRoaXMuX3BoeXNpY2FsQm90dG9tK01hdGgubWF4KHRoaXMuX3ZpcnR1YWxDb3VudC10aGlzLl9waHlzaWNhbENvdW50LXRoaXMuX3ZpcnR1YWxTdGFydCwwKSp0aGlzLl9waHlzaWNhbEF2ZXJhZ2UsZT1lfHx0aGlzLl9zY3JvbGxIZWlnaHQ9PT0wLGU9ZXx8dGhpcy5fc2Nyb2xsUG9zaXRpb24+PXRoaXMuX2VzdFNjcm9sbEhlaWdodC10aGlzLl9waHlzaWNhbFNpemUsZT1lfHx0aGlzLmdyaWQmJnRoaXMuJC5pdGVtcy5zdHlsZS5oZWlnaHQ8dGhpcy5fZXN0U2Nyb2xsSGVpZ2h0LChlfHxNYXRoLmFicyh0aGlzLl9lc3RTY3JvbGxIZWlnaHQtdGhpcy5fc2Nyb2xsSGVpZ2h0KT49dGhpcy5fdmlld3BvcnRIZWlnaHQpJiYodGhpcy4kLml0ZW1zLnN0eWxlLmhlaWdodD10aGlzLl9lc3RTY3JvbGxIZWlnaHQrInB4Iix0aGlzLl9zY3JvbGxIZWlnaHQ9dGhpcy5fZXN0U2Nyb2xsSGVpZ2h0KX0sc2Nyb2xsVG9JdGVtOmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLnNjcm9sbFRvSW5kZXgodGhpcy5pdGVtcy5pbmRleE9mKGUpKX0sc2Nyb2xsVG9JbmRleDpmdW5jdGlvbihlKXtpZighKHR5cGVvZiBlIT0ibnVtYmVyInx8ZTwwfHxlPnRoaXMuaXRlbXMubGVuZ3RoLTEpJiYodWkoKSx0aGlzLl9waHlzaWNhbENvdW50IT09MCkpe2U9dGhpcy5fY2xhbXAoZSwwLHRoaXMuX3ZpcnR1YWxDb3VudC0xKSwoIXRoaXMuX2lzSW5kZXhSZW5kZXJlZChlKXx8ZT49dGhpcy5fbWF4VmlydHVhbFN0YXJ0KSYmKHRoaXMuX3ZpcnR1YWxTdGFydD10aGlzLmdyaWQ/ZS10aGlzLl9pdGVtc1BlclJvdyoyOmUtMSksdGhpcy5fbWFuYWdlRm9jdXMoKSx0aGlzLl9hc3NpZ25Nb2RlbHMoKSx0aGlzLl91cGRhdGVNZXRyaWNzKCksdGhpcy5fcGh5c2ljYWxUb3A9TWF0aC5mbG9vcih0aGlzLl92aXJ0dWFsU3RhcnQvdGhpcy5faXRlbXNQZXJSb3cpKnRoaXMuX3BoeXNpY2FsQXZlcmFnZTtmb3IodmFyIHQ9dGhpcy5fcGh5c2ljYWxTdGFydCxyPXRoaXMuX3ZpcnR1YWxTdGFydCxuPTAsaT10aGlzLl9oaWRkZW5Db250ZW50U2l6ZTtyPGUmJm48PWk7KW49bit0aGlzLl9nZXRQaHlzaWNhbFNpemVJbmNyZW1lbnQodCksdD0odCsxKSV0aGlzLl9waHlzaWNhbENvdW50LHIrKzt0aGlzLl91cGRhdGVTY3JvbGxlclNpemUoITApLHRoaXMuX3Bvc2l0aW9uSXRlbXMoKSx0aGlzLl9yZXNldFNjcm9sbFBvc2l0aW9uKHRoaXMuX3BoeXNpY2FsVG9wK3RoaXMuX3Njcm9sbE9mZnNldCtuKSx0aGlzLl9pbmNyZWFzZVBvb2xJZk5lZWRlZCgwKSx0aGlzLl9maXJzdFZpc2libGVJbmRleFZhbD1udWxsLHRoaXMuX2xhc3RWaXNpYmxlSW5kZXhWYWw9bnVsbH19LF9yZXNldEF2ZXJhZ2U6ZnVuY3Rpb24oKXt0aGlzLl9waHlzaWNhbEF2ZXJhZ2U9MCx0aGlzLl9waHlzaWNhbEF2ZXJhZ2VDb3VudD0wfSxfcmVzaXplSGFuZGxlcjpmdW5jdGlvbigpe3RoaXMuX2RlYm91bmNlKCJfcmVuZGVyIixmdW5jdGlvbigpe3RoaXMuX2ZpcnN0VmlzaWJsZUluZGV4VmFsPW51bGwsdGhpcy5fbGFzdFZpc2libGVJbmRleFZhbD1udWxsLHRoaXMuX2lzVmlzaWJsZT8odGhpcy51cGRhdGVWaWV3cG9ydEJvdW5kYXJpZXMoKSx0aGlzLnRvZ2dsZVNjcm9sbExpc3RlbmVyKCEwKSx0aGlzLl9yZXNldEF2ZXJhZ2UoKSx0aGlzLl9yZW5kZXIoKSk6dGhpcy50b2dnbGVTY3JvbGxMaXN0ZW5lcighMSl9LE5pKX0sc2VsZWN0SXRlbTpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5zZWxlY3RJbmRleCh0aGlzLml0ZW1zLmluZGV4T2YoZSkpfSxzZWxlY3RJbmRleDpmdW5jdGlvbihlKXtpZighKGU8MHx8ZT49dGhpcy5fdmlydHVhbENvdW50KSl7aWYoIXRoaXMubXVsdGlTZWxlY3Rpb24mJnRoaXMuc2VsZWN0ZWRJdGVtJiZ0aGlzLmNsZWFyU2VsZWN0aW9uKCksdGhpcy5faXNJbmRleFJlbmRlcmVkKGUpKXt2YXIgdD10aGlzLm1vZGVsRm9yRWxlbWVudCh0aGlzLl9waHlzaWNhbEl0ZW1zW3RoaXMuX2dldFBoeXNpY2FsSW5kZXgoZSldKTt0JiYodFt0aGlzLnNlbGVjdGVkQXNdPSEwKSx0aGlzLnVwZGF0ZVNpemVGb3JJbmRleChlKX10aGlzLiQuc2VsZWN0b3Iuc2VsZWN0SW5kZXgoZSl9fSxkZXNlbGVjdEl0ZW06ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuZGVzZWxlY3RJbmRleCh0aGlzLml0ZW1zLmluZGV4T2YoZSkpfSxkZXNlbGVjdEluZGV4OmZ1bmN0aW9uKGUpe2lmKCEoZTwwfHxlPj10aGlzLl92aXJ0dWFsQ291bnQpKXtpZih0aGlzLl9pc0luZGV4UmVuZGVyZWQoZSkpe3ZhciB0PXRoaXMubW9kZWxGb3JFbGVtZW50KHRoaXMuX3BoeXNpY2FsSXRlbXNbdGhpcy5fZ2V0UGh5c2ljYWxJbmRleChlKV0pO3RbdGhpcy5zZWxlY3RlZEFzXT0hMSx0aGlzLnVwZGF0ZVNpemVGb3JJbmRleChlKX10aGlzLiQuc2VsZWN0b3IuZGVzZWxlY3RJbmRleChlKX19LHRvZ2dsZVNlbGVjdGlvbkZvckl0ZW06ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMudG9nZ2xlU2VsZWN0aW9uRm9ySW5kZXgodGhpcy5pdGVtcy5pbmRleE9mKGUpKX0sdG9nZ2xlU2VsZWN0aW9uRm9ySW5kZXg6ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy4kLnNlbGVjdG9yLmlzSW5kZXhTZWxlY3RlZD90aGlzLiQuc2VsZWN0b3IuaXNJbmRleFNlbGVjdGVkKGUpOnRoaXMuJC5zZWxlY3Rvci5pc1NlbGVjdGVkKHRoaXMuaXRlbXNbZV0pO3Q/dGhpcy5kZXNlbGVjdEluZGV4KGUpOnRoaXMuc2VsZWN0SW5kZXgoZSl9LGNsZWFyU2VsZWN0aW9uOmZ1bmN0aW9uKCl7dGhpcy5faXRlcmF0ZUl0ZW1zKGZ1bmN0aW9uKGUsdCl7dGhpcy5tb2RlbEZvckVsZW1lbnQodGhpcy5fcGh5c2ljYWxJdGVtc1tlXSlbdGhpcy5zZWxlY3RlZEFzXT0hMX0pLHRoaXMuJC5zZWxlY3Rvci5jbGVhclNlbGVjdGlvbigpfSxfc2VsZWN0aW9uRW5hYmxlZENoYW5nZWQ6ZnVuY3Rpb24oZSl7dmFyIHQ9ZT90aGlzLmxpc3Rlbjp0aGlzLnVubGlzdGVuO3QuY2FsbCh0aGlzLHRoaXMsInRhcCIsIl9zZWxlY3Rpb25IYW5kbGVyIil9LF9zZWxlY3Rpb25IYW5kbGVyOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMubW9kZWxGb3JFbGVtZW50KGUudGFyZ2V0KTtpZighIXQpe3ZhciByLG4saT16dChlKS5wYXRoWzBdLG89dGhpcy5fZ2V0QWN0aXZlRWxlbWVudCgpLGE9dGhpcy5fcGh5c2ljYWxJdGVtc1t0aGlzLl9nZXRQaHlzaWNhbEluZGV4KHRbdGhpcy5pbmRleEFzXSldO2kubG9jYWxOYW1lPT09ImlucHV0Inx8aS5sb2NhbE5hbWU9PT0iYnV0dG9uInx8aS5sb2NhbE5hbWU9PT0ic2VsZWN0Inx8KHI9dC50YWJJbmRleCx0LnRhYkluZGV4PWhFLG49bz9vLnRhYkluZGV4Oi0xLHQudGFiSW5kZXg9ciwhKG8mJmEhPT1vJiZhLmNvbnRhaW5zKG8pJiZuIT09aEUpJiZ0aGlzLnRvZ2dsZVNlbGVjdGlvbkZvckl0ZW0odFt0aGlzLmFzXSkpfX0sX211bHRpU2VsZWN0aW9uQ2hhbmdlZDpmdW5jdGlvbihlKXt0aGlzLmNsZWFyU2VsZWN0aW9uKCksdGhpcy4kLnNlbGVjdG9yLm11bHRpPWV9LHVwZGF0ZVNpemVGb3JJdGVtOmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLnVwZGF0ZVNpemVGb3JJbmRleCh0aGlzLml0ZW1zLmluZGV4T2YoZSkpfSx1cGRhdGVTaXplRm9ySW5kZXg6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX2lzSW5kZXhSZW5kZXJlZChlKSYmKHRoaXMuX3VwZGF0ZU1ldHJpY3MoW3RoaXMuX2dldFBoeXNpY2FsSW5kZXgoZSldKSx0aGlzLl9wb3NpdGlvbkl0ZW1zKCkpLG51bGx9LF9tYW5hZ2VGb2N1czpmdW5jdGlvbigpe3ZhciBlPXRoaXMuX2ZvY3VzZWRWaXJ0dWFsSW5kZXg7ZT49MCYmZTx0aGlzLl92aXJ0dWFsQ291bnQ/dGhpcy5faXNJbmRleFJlbmRlcmVkKGUpP3RoaXMuX3Jlc3RvcmVGb2N1c2VkSXRlbSgpOnRoaXMuX2NyZWF0ZUZvY3VzQmFja2ZpbGxJdGVtKCk6dGhpcy5fdmlydHVhbENvdW50PjAmJnRoaXMuX3BoeXNpY2FsQ291bnQ+MCYmKHRoaXMuX2ZvY3VzZWRQaHlzaWNhbEluZGV4PXRoaXMuX3BoeXNpY2FsU3RhcnQsdGhpcy5fZm9jdXNlZFZpcnR1YWxJbmRleD10aGlzLl92aXJ0dWFsU3RhcnQsdGhpcy5fZm9jdXNlZEl0ZW09dGhpcy5fcGh5c2ljYWxJdGVtc1t0aGlzLl9waHlzaWNhbFN0YXJ0XSl9LF9jb252ZXJ0SW5kZXhUb0NvbXBsZXRlUm93OmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLl9pdGVtc1BlclJvdz10aGlzLl9pdGVtc1BlclJvd3x8MSx0aGlzLmdyaWQ/TWF0aC5jZWlsKGUvdGhpcy5faXRlbXNQZXJSb3cpKnRoaXMuX2l0ZW1zUGVyUm93OmV9LF9pc0luZGV4UmVuZGVyZWQ6ZnVuY3Rpb24oZSl7cmV0dXJuIGU+PXRoaXMuX3ZpcnR1YWxTdGFydCYmZTw9dGhpcy5fdmlydHVhbEVuZH0sX2lzSW5kZXhWaXNpYmxlOmZ1bmN0aW9uKGUpe3JldHVybiBlPj10aGlzLmZpcnN0VmlzaWJsZUluZGV4JiZlPD10aGlzLmxhc3RWaXNpYmxlSW5kZXh9LF9nZXRQaHlzaWNhbEluZGV4OmZ1bmN0aW9uKGUpe3JldHVybih0aGlzLl9waHlzaWNhbFN0YXJ0KyhlLXRoaXMuX3ZpcnR1YWxTdGFydCkpJXRoaXMuX3BoeXNpY2FsQ291bnR9LGZvY3VzSXRlbTpmdW5jdGlvbihlKXt0aGlzLl9mb2N1c1BoeXNpY2FsSXRlbShlKX0sX2ZvY3VzUGh5c2ljYWxJdGVtOmZ1bmN0aW9uKGUpe2lmKCEoZTwwfHxlPj10aGlzLl92aXJ0dWFsQ291bnQpKXt0aGlzLl9yZXN0b3JlRm9jdXNlZEl0ZW0oKSx0aGlzLl9pc0luZGV4UmVuZGVyZWQoZSl8fHRoaXMuc2Nyb2xsVG9JbmRleChlKTt2YXIgdD10aGlzLl9waHlzaWNhbEl0ZW1zW3RoaXMuX2dldFBoeXNpY2FsSW5kZXgoZSldLHI9dGhpcy5tb2RlbEZvckVsZW1lbnQodCksbjtyLnRhYkluZGV4PWhFLHQudGFiSW5kZXg9PT1oRSYmKG49dCksbnx8KG49enQodCkucXVlcnlTZWxlY3RvcignW3RhYmluZGV4PSInK2hFKyciXScpKSxyLnRhYkluZGV4PTAsdGhpcy5fZm9jdXNlZFZpcnR1YWxJbmRleD1lLG4mJm4uZm9jdXMoKX19LF9yZW1vdmVGb2N1c2VkSXRlbTpmdW5jdGlvbigpe3RoaXMuX29mZnNjcmVlbkZvY3VzZWRJdGVtJiZ0aGlzLl9pdGVtc1BhcmVudC5yZW1vdmVDaGlsZCh0aGlzLl9vZmZzY3JlZW5Gb2N1c2VkSXRlbSksdGhpcy5fb2Zmc2NyZWVuRm9jdXNlZEl0ZW09bnVsbCx0aGlzLl9mb2N1c0JhY2tmaWxsSXRlbT1udWxsLHRoaXMuX2ZvY3VzZWRJdGVtPW51bGwsdGhpcy5fZm9jdXNlZFZpcnR1YWxJbmRleD0tMSx0aGlzLl9mb2N1c2VkUGh5c2ljYWxJbmRleD0tMX0sX2NyZWF0ZUZvY3VzQmFja2ZpbGxJdGVtOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5fZm9jdXNlZFBoeXNpY2FsSW5kZXg7aWYoISh0aGlzLl9vZmZzY3JlZW5Gb2N1c2VkSXRlbXx8dGhpcy5fZm9jdXNlZFZpcnR1YWxJbmRleDwwKSl7aWYoIXRoaXMuX2ZvY3VzQmFja2ZpbGxJdGVtKXt2YXIgdD10aGlzLnN0YW1wKG51bGwpO3RoaXMuX2ZvY3VzQmFja2ZpbGxJdGVtPXQucm9vdC5xdWVyeVNlbGVjdG9yKCIqIiksdGhpcy5faXRlbXNQYXJlbnQuYXBwZW5kQ2hpbGQodC5yb290KX10aGlzLl9vZmZzY3JlZW5Gb2N1c2VkSXRlbT10aGlzLl9waHlzaWNhbEl0ZW1zW2VdLHRoaXMubW9kZWxGb3JFbGVtZW50KHRoaXMuX29mZnNjcmVlbkZvY3VzZWRJdGVtKS50YWJJbmRleD0wLHRoaXMuX3BoeXNpY2FsSXRlbXNbZV09dGhpcy5fZm9jdXNCYWNrZmlsbEl0ZW0sdGhpcy5fZm9jdXNlZFBoeXNpY2FsSW5kZXg9ZSx0aGlzLnRyYW5zbGF0ZTNkKDAscjB0LDAsdGhpcy5fb2Zmc2NyZWVuRm9jdXNlZEl0ZW0pfX0sX3Jlc3RvcmVGb2N1c2VkSXRlbTpmdW5jdGlvbigpe2lmKCEoIXRoaXMuX29mZnNjcmVlbkZvY3VzZWRJdGVtfHx0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4PDApKXt0aGlzLl9hc3NpZ25Nb2RlbHMoKTt2YXIgZT10aGlzLl9mb2N1c2VkUGh5c2ljYWxJbmRleD10aGlzLl9nZXRQaHlzaWNhbEluZGV4KHRoaXMuX2ZvY3VzZWRWaXJ0dWFsSW5kZXgpLHQ9dGhpcy5fcGh5c2ljYWxJdGVtc1tlXTtpZighIXQpe3ZhciByPXRoaXMubW9kZWxGb3JFbGVtZW50KHQpLG49dGhpcy5tb2RlbEZvckVsZW1lbnQodGhpcy5fb2Zmc2NyZWVuRm9jdXNlZEl0ZW0pO3JbdGhpcy5hc109PT1uW3RoaXMuYXNdPyh0aGlzLl9mb2N1c0JhY2tmaWxsSXRlbT10LHIudGFiSW5kZXg9LTEsdGhpcy5fcGh5c2ljYWxJdGVtc1tlXT10aGlzLl9vZmZzY3JlZW5Gb2N1c2VkSXRlbSx0aGlzLnRyYW5zbGF0ZTNkKDAscjB0LDAsdGhpcy5fZm9jdXNCYWNrZmlsbEl0ZW0pKToodGhpcy5fcmVtb3ZlRm9jdXNlZEl0ZW0oKSx0aGlzLl9mb2N1c0JhY2tmaWxsSXRlbT1udWxsKSx0aGlzLl9vZmZzY3JlZW5Gb2N1c2VkSXRlbT1udWxsfX19LF9kaWRGb2N1czpmdW5jdGlvbihlKXt2YXIgdD10aGlzLm1vZGVsRm9yRWxlbWVudChlLnRhcmdldCkscj10aGlzLm1vZGVsRm9yRWxlbWVudCh0aGlzLl9mb2N1c2VkSXRlbSksbj10aGlzLl9vZmZzY3JlZW5Gb2N1c2VkSXRlbSE9PW51bGwsaT10aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4OyF0fHwocj09PXQ/dGhpcy5faXNJbmRleFZpc2libGUoaSl8fHRoaXMuc2Nyb2xsVG9JbmRleChpKToodGhpcy5fcmVzdG9yZUZvY3VzZWRJdGVtKCksciYmKHIudGFiSW5kZXg9LTEpLHQudGFiSW5kZXg9MCxpPXRbdGhpcy5pbmRleEFzXSx0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4PWksdGhpcy5fZm9jdXNlZFBoeXNpY2FsSW5kZXg9dGhpcy5fZ2V0UGh5c2ljYWxJbmRleChpKSx0aGlzLl9mb2N1c2VkSXRlbT10aGlzLl9waHlzaWNhbEl0ZW1zW3RoaXMuX2ZvY3VzZWRQaHlzaWNhbEluZGV4XSxuJiYhdGhpcy5fb2Zmc2NyZWVuRm9jdXNlZEl0ZW0mJnRoaXMuX3VwZGF0ZSgpKSl9LF9rZXlkb3duSGFuZGxlcjpmdW5jdGlvbihlKXtzd2l0Y2goZS5rZXlDb2RlKXtjYXNlIDQwOnRoaXMuX2ZvY3VzZWRWaXJ0dWFsSW5kZXg8dGhpcy5fdmlydHVhbENvdW50LTEmJmUucHJldmVudERlZmF1bHQoKSx0aGlzLl9mb2N1c1BoeXNpY2FsSXRlbSh0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4Kyh0aGlzLmdyaWQ/dGhpcy5faXRlbXNQZXJSb3c6MSkpO2JyZWFrO2Nhc2UgMzk6dGhpcy5ncmlkJiZ0aGlzLl9mb2N1c1BoeXNpY2FsSXRlbSh0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4Kyh0aGlzLl9pc1JUTD8tMToxKSk7YnJlYWs7Y2FzZSAzODp0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4PjAmJmUucHJldmVudERlZmF1bHQoKSx0aGlzLl9mb2N1c1BoeXNpY2FsSXRlbSh0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4LSh0aGlzLmdyaWQ/dGhpcy5faXRlbXNQZXJSb3c6MSkpO2JyZWFrO2Nhc2UgMzc6dGhpcy5ncmlkJiZ0aGlzLl9mb2N1c1BoeXNpY2FsSXRlbSh0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4Kyh0aGlzLl9pc1JUTD8xOi0xKSk7YnJlYWs7Y2FzZSAxMzp0aGlzLl9mb2N1c1BoeXNpY2FsSXRlbSh0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4KSx0aGlzLnNlbGVjdGlvbkVuYWJsZWQmJnRoaXMuX3NlbGVjdGlvbkhhbmRsZXIoZSk7YnJlYWt9fSxfY2xhbXA6ZnVuY3Rpb24oZSx0LHIpe3JldHVybiBNYXRoLm1pbihyLE1hdGgubWF4KHQsZSkpfSxfZGVib3VuY2U6ZnVuY3Rpb24oZSx0LHIpe3RoaXMuX2RlYm91bmNlcnM9dGhpcy5fZGVib3VuY2Vyc3x8e30sdGhpcy5fZGVib3VuY2Vyc1tlXT1zci5kZWJvdW5jZSh0aGlzLl9kZWJvdW5jZXJzW2VdLHIsdC5iaW5kKHRoaXMpKSxKbCh0aGlzLl9kZWJvdW5jZXJzW2VdKX0sX2ZvcndhcmRQcm9wZXJ0eTpmdW5jdGlvbihlLHQscil7ZS5fc2V0UGVuZGluZ1Byb3BlcnR5KHQscil9LF9mb3J3YXJkSG9zdFByb3BWMjpmdW5jdGlvbihlLHQpeyh0aGlzLl9waHlzaWNhbEl0ZW1zfHxbXSkuY29uY2F0KFt0aGlzLl9vZmZzY3JlZW5Gb2N1c2VkSXRlbSx0aGlzLl9mb2N1c0JhY2tmaWxsSXRlbV0pLmZvckVhY2goZnVuY3Rpb24ocil7ciYmdGhpcy5tb2RlbEZvckVsZW1lbnQocikuZm9yd2FyZEhvc3RQcm9wKGUsdCl9LHRoaXMpfSxfbm90aWZ5SW5zdGFuY2VQcm9wVjI6ZnVuY3Rpb24oZSx0LHIpe2lmKERJKHRoaXMuYXMsdCkpe3ZhciBuPWVbdGhpcy5pbmRleEFzXTt0PT10aGlzLmFzJiYodGhpcy5pdGVtc1tuXT1yKSx0aGlzLm5vdGlmeVBhdGgoZHAodGhpcy5hcywiaXRlbXMuIituLHQpLHIpfX0sX2dldFN0YW1wZWRDaGlsZHJlbjpmdW5jdGlvbigpe3JldHVybiB0aGlzLl9waHlzaWNhbEl0ZW1zfSxfZm9yd2FyZEluc3RhbmNlUGF0aDpmdW5jdGlvbihlLHQscil7dC5pbmRleE9mKHRoaXMuYXMrIi4iKT09PTAmJnRoaXMubm90aWZ5UGF0aCgiaXRlbXMuIitlLl9fa2V5X18rIi4iK3Quc2xpY2UodGhpcy5hcy5sZW5ndGgrMSkscil9LF9mb3J3YXJkUGFyZW50UGF0aDpmdW5jdGlvbihlLHQpeyh0aGlzLl9waHlzaWNhbEl0ZW1zfHxbXSkuY29uY2F0KFt0aGlzLl9vZmZzY3JlZW5Gb2N1c2VkSXRlbSx0aGlzLl9mb2N1c0JhY2tmaWxsSXRlbV0pLmZvckVhY2goZnVuY3Rpb24ocil7ciYmdGhpcy5tb2RlbEZvckVsZW1lbnQocikubm90aWZ5UGF0aChlLHQpfSx0aGlzKX0sX2ZvcndhcmRQYXJlbnRQcm9wOmZ1bmN0aW9uKGUsdCl7KHRoaXMuX3BoeXNpY2FsSXRlbXN8fFtdKS5jb25jYXQoW3RoaXMuX29mZnNjcmVlbkZvY3VzZWRJdGVtLHRoaXMuX2ZvY3VzQmFja2ZpbGxJdGVtXSkuZm9yRWFjaChmdW5jdGlvbihyKXtyJiYodGhpcy5tb2RlbEZvckVsZW1lbnQocilbZV09dCl9LHRoaXMpfSxfZ2V0QWN0aXZlRWxlbWVudDpmdW5jdGlvbigpe3ZhciBlPXRoaXMuX2l0ZW1zUGFyZW50Lm5vZGUuZG9tSG9zdDtyZXR1cm4genQoZT9lLnJvb3Q6ZG9jdW1lbnQpLmFjdGl2ZUVsZW1lbnR9fSk7dmFyIHY5PWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuc2VsZWN0aW9uPVtdLHRoaXMuc2VsZWN0Q2FsbGJhY2s9dH1nZXQoKXtyZXR1cm4gdGhpcy5tdWx0aT90aGlzLnNlbGVjdGlvbi5zbGljZSgpOnRoaXMuc2VsZWN0aW9uWzBdfWNsZWFyKHQpe3RoaXMuc2VsZWN0aW9uLnNsaWNlKCkuZm9yRWFjaChmdW5jdGlvbihyKXsoIXR8fHQuaW5kZXhPZihyKTwwKSYmdGhpcy5zZXRJdGVtU2VsZWN0ZWQociwhMSl9LHRoaXMpfWlzU2VsZWN0ZWQodCl7cmV0dXJuIHRoaXMuc2VsZWN0aW9uLmluZGV4T2YodCk+PTB9c2V0SXRlbVNlbGVjdGVkKHQscil7aWYodCE9bnVsbCYmciE9PXRoaXMuaXNTZWxlY3RlZCh0KSl7aWYocil0aGlzLnNlbGVjdGlvbi5wdXNoKHQpO2Vsc2V7dmFyIG49dGhpcy5zZWxlY3Rpb24uaW5kZXhPZih0KTtuPj0wJiZ0aGlzLnNlbGVjdGlvbi5zcGxpY2UobiwxKX10aGlzLnNlbGVjdENhbGxiYWNrJiZ0aGlzLnNlbGVjdENhbGxiYWNrKHQscil9fXNlbGVjdCh0KXt0aGlzLm11bHRpP3RoaXMudG9nZ2xlKHQpOnRoaXMuZ2V0KCkhPT10JiYodGhpcy5zZXRJdGVtU2VsZWN0ZWQodGhpcy5nZXQoKSwhMSksdGhpcy5zZXRJdGVtU2VsZWN0ZWQodCwhMCkpfXRvZ2dsZSh0KXt0aGlzLnNldEl0ZW1TZWxlY3RlZCh0LCF0aGlzLmlzU2VsZWN0ZWQodCkpfX07dmFyIHdoPXtwcm9wZXJ0aWVzOnthdHRyRm9yU2VsZWN0ZWQ6e3R5cGU6U3RyaW5nLHZhbHVlOm51bGx9LHNlbGVjdGVkOnt0eXBlOlN0cmluZyxub3RpZnk6ITB9LHNlbGVjdGVkSXRlbTp7dHlwZTpPYmplY3QscmVhZE9ubHk6ITAsbm90aWZ5OiEwfSxhY3RpdmF0ZUV2ZW50Ont0eXBlOlN0cmluZyx2YWx1ZToidGFwIixvYnNlcnZlcjoiX2FjdGl2YXRlRXZlbnRDaGFuZ2VkIn0sc2VsZWN0YWJsZTpTdHJpbmcsc2VsZWN0ZWRDbGFzczp7dHlwZTpTdHJpbmcsdmFsdWU6Imlyb24tc2VsZWN0ZWQifSxzZWxlY3RlZEF0dHJpYnV0ZTp7dHlwZTpTdHJpbmcsdmFsdWU6bnVsbH0sZmFsbGJhY2tTZWxlY3Rpb246e3R5cGU6U3RyaW5nLHZhbHVlOm51bGx9LGl0ZW1zOnt0eXBlOkFycmF5LHJlYWRPbmx5OiEwLG5vdGlmeTohMCx2YWx1ZTpmdW5jdGlvbigpe3JldHVybltdfX0sX2V4Y2x1ZGVkTG9jYWxOYW1lczp7dHlwZTpPYmplY3QsdmFsdWU6ZnVuY3Rpb24oKXtyZXR1cm57dGVtcGxhdGU6MSwiZG9tLWJpbmQiOjEsImRvbS1pZiI6MSwiZG9tLXJlcGVhdCI6MX19fX0sb2JzZXJ2ZXJzOlsiX3VwZGF0ZUF0dHJGb3JTZWxlY3RlZChhdHRyRm9yU2VsZWN0ZWQpIiwiX3VwZGF0ZVNlbGVjdGVkKHNlbGVjdGVkKSIsIl9jaGVja0ZhbGxiYWNrKGZhbGxiYWNrU2VsZWN0aW9uKSJdLGNyZWF0ZWQ6ZnVuY3Rpb24oKXt0aGlzLl9iaW5kRmlsdGVySXRlbT10aGlzLl9maWx0ZXJJdGVtLmJpbmQodGhpcyksdGhpcy5fc2VsZWN0aW9uPW5ldyB2OSh0aGlzLl9hcHBseVNlbGVjdGlvbi5iaW5kKHRoaXMpKX0sYXR0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLl9vYnNlcnZlcj10aGlzLl9vYnNlcnZlSXRlbXModGhpcyksdGhpcy5fYWRkTGlzdGVuZXIodGhpcy5hY3RpdmF0ZUV2ZW50KX0sZGV0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLl9vYnNlcnZlciYmenQodGhpcykudW5vYnNlcnZlTm9kZXModGhpcy5fb2JzZXJ2ZXIpLHRoaXMuX3JlbW92ZUxpc3RlbmVyKHRoaXMuYWN0aXZhdGVFdmVudCl9LGluZGV4T2Y6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuaXRlbXM/dGhpcy5pdGVtcy5pbmRleE9mKGUpOi0xfSxzZWxlY3Q6ZnVuY3Rpb24oZSl7dGhpcy5zZWxlY3RlZD1lfSxzZWxlY3RQcmV2aW91czpmdW5jdGlvbigpe3ZhciBlPXRoaXMuaXRlbXMubGVuZ3RoLHQ9ZS0xO3RoaXMuc2VsZWN0ZWQhPT12b2lkIDAmJih0PShOdW1iZXIodGhpcy5fdmFsdWVUb0luZGV4KHRoaXMuc2VsZWN0ZWQpKS0xK2UpJWUpLHRoaXMuc2VsZWN0ZWQ9dGhpcy5faW5kZXhUb1ZhbHVlKHQpfSxzZWxlY3ROZXh0OmZ1bmN0aW9uKCl7dmFyIGU9MDt0aGlzLnNlbGVjdGVkIT09dm9pZCAwJiYoZT0oTnVtYmVyKHRoaXMuX3ZhbHVlVG9JbmRleCh0aGlzLnNlbGVjdGVkKSkrMSkldGhpcy5pdGVtcy5sZW5ndGgpLHRoaXMuc2VsZWN0ZWQ9dGhpcy5faW5kZXhUb1ZhbHVlKGUpfSxzZWxlY3RJbmRleDpmdW5jdGlvbihlKXt0aGlzLnNlbGVjdCh0aGlzLl9pbmRleFRvVmFsdWUoZSkpfSxmb3JjZVN5bmNocm9ub3VzSXRlbVVwZGF0ZTpmdW5jdGlvbigpe3RoaXMuX29ic2VydmVyJiZ0eXBlb2YgdGhpcy5fb2JzZXJ2ZXIuZmx1c2g9PSJmdW5jdGlvbiI/dGhpcy5fb2JzZXJ2ZXIuZmx1c2goKTp0aGlzLl91cGRhdGVJdGVtcygpfSxnZXQgX3Nob3VsZFVwZGF0ZVNlbGVjdGlvbigpe3JldHVybiB0aGlzLnNlbGVjdGVkIT1udWxsfSxfY2hlY2tGYWxsYmFjazpmdW5jdGlvbigpe3RoaXMuX3VwZGF0ZVNlbGVjdGVkKCl9LF9hZGRMaXN0ZW5lcjpmdW5jdGlvbihlKXt0aGlzLmxpc3Rlbih0aGlzLGUsIl9hY3RpdmF0ZUhhbmRsZXIiKX0sX3JlbW92ZUxpc3RlbmVyOmZ1bmN0aW9uKGUpe3RoaXMudW5saXN0ZW4odGhpcyxlLCJfYWN0aXZhdGVIYW5kbGVyIil9LF9hY3RpdmF0ZUV2ZW50Q2hhbmdlZDpmdW5jdGlvbihlLHQpe3RoaXMuX3JlbW92ZUxpc3RlbmVyKHQpLHRoaXMuX2FkZExpc3RlbmVyKGUpfSxfdXBkYXRlSXRlbXM6ZnVuY3Rpb24oKXt2YXIgZT16dCh0aGlzKS5xdWVyeURpc3RyaWJ1dGVkRWxlbWVudHModGhpcy5zZWxlY3RhYmxlfHwiKiIpO2U9QXJyYXkucHJvdG90eXBlLmZpbHRlci5jYWxsKGUsdGhpcy5fYmluZEZpbHRlckl0ZW0pLHRoaXMuX3NldEl0ZW1zKGUpfSxfdXBkYXRlQXR0ckZvclNlbGVjdGVkOmZ1bmN0aW9uKCl7dGhpcy5zZWxlY3RlZEl0ZW0mJih0aGlzLnNlbGVjdGVkPXRoaXMuX3ZhbHVlRm9ySXRlbSh0aGlzLnNlbGVjdGVkSXRlbSkpfSxfdXBkYXRlU2VsZWN0ZWQ6ZnVuY3Rpb24oKXt0aGlzLl9zZWxlY3RTZWxlY3RlZCh0aGlzLnNlbGVjdGVkKX0sX3NlbGVjdFNlbGVjdGVkOmZ1bmN0aW9uKGUpe2lmKCEhdGhpcy5pdGVtcyl7dmFyIHQ9dGhpcy5fdmFsdWVUb0l0ZW0odGhpcy5zZWxlY3RlZCk7dD90aGlzLl9zZWxlY3Rpb24uc2VsZWN0KHQpOnRoaXMuX3NlbGVjdGlvbi5jbGVhcigpLHRoaXMuZmFsbGJhY2tTZWxlY3Rpb24mJnRoaXMuaXRlbXMubGVuZ3RoJiZ0aGlzLl9zZWxlY3Rpb24uZ2V0KCk9PT12b2lkIDAmJih0aGlzLnNlbGVjdGVkPXRoaXMuZmFsbGJhY2tTZWxlY3Rpb24pfX0sX2ZpbHRlckl0ZW06ZnVuY3Rpb24oZSl7cmV0dXJuIXRoaXMuX2V4Y2x1ZGVkTG9jYWxOYW1lc1tlLmxvY2FsTmFtZV19LF92YWx1ZVRvSXRlbTpmdW5jdGlvbihlKXtyZXR1cm4gZT09bnVsbD9udWxsOnRoaXMuaXRlbXNbdGhpcy5fdmFsdWVUb0luZGV4KGUpXX0sX3ZhbHVlVG9JbmRleDpmdW5jdGlvbihlKXtpZih0aGlzLmF0dHJGb3JTZWxlY3RlZCl7Zm9yKHZhciB0PTAscjtyPXRoaXMuaXRlbXNbdF07dCsrKWlmKHRoaXMuX3ZhbHVlRm9ySXRlbShyKT09ZSlyZXR1cm4gdH1lbHNlIHJldHVybiBOdW1iZXIoZSl9LF9pbmRleFRvVmFsdWU6ZnVuY3Rpb24oZSl7aWYodGhpcy5hdHRyRm9yU2VsZWN0ZWQpe3ZhciB0PXRoaXMuaXRlbXNbZV07aWYodClyZXR1cm4gdGhpcy5fdmFsdWVGb3JJdGVtKHQpfWVsc2UgcmV0dXJuIGV9LF92YWx1ZUZvckl0ZW06ZnVuY3Rpb24oZSl7aWYoIWUpcmV0dXJuIG51bGw7aWYoIXRoaXMuYXR0ckZvclNlbGVjdGVkKXt2YXIgdD10aGlzLmluZGV4T2YoZSk7cmV0dXJuIHQ9PT0tMT9udWxsOnR9dmFyIHI9ZVt3bSh0aGlzLmF0dHJGb3JTZWxlY3RlZCldO3JldHVybiByIT1udWxsP3I6ZS5nZXRBdHRyaWJ1dGUodGhpcy5hdHRyRm9yU2VsZWN0ZWQpfSxfYXBwbHlTZWxlY3Rpb246ZnVuY3Rpb24oZSx0KXt0aGlzLnNlbGVjdGVkQ2xhc3MmJnRoaXMudG9nZ2xlQ2xhc3ModGhpcy5zZWxlY3RlZENsYXNzLHQsZSksdGhpcy5zZWxlY3RlZEF0dHJpYnV0ZSYmdGhpcy50b2dnbGVBdHRyaWJ1dGUodGhpcy5zZWxlY3RlZEF0dHJpYnV0ZSx0LGUpLHRoaXMuX3NlbGVjdGlvbkNoYW5nZSgpLHRoaXMuZmlyZSgiaXJvbi0iKyh0PyJzZWxlY3QiOiJkZXNlbGVjdCIpLHtpdGVtOmV9KX0sX3NlbGVjdGlvbkNoYW5nZTpmdW5jdGlvbigpe3RoaXMuX3NldFNlbGVjdGVkSXRlbSh0aGlzLl9zZWxlY3Rpb24uZ2V0KCkpfSxfb2JzZXJ2ZUl0ZW1zOmZ1bmN0aW9uKGUpe3JldHVybiB6dChlKS5vYnNlcnZlTm9kZXMoZnVuY3Rpb24odCl7dGhpcy5fdXBkYXRlSXRlbXMoKSx0aGlzLl91cGRhdGVTZWxlY3RlZCgpLHRoaXMuZmlyZSgiaXJvbi1pdGVtcy1jaGFuZ2VkIix0LHtidWJibGVzOiExLGNhbmNlbGFibGU6ITF9KX0pfSxfYWN0aXZhdGVIYW5kbGVyOmZ1bmN0aW9uKGUpe2Zvcih2YXIgdD1lLnRhcmdldCxyPXRoaXMuaXRlbXM7dCYmdCE9dGhpczspe3ZhciBuPXIuaW5kZXhPZih0KTtpZihuPj0wKXt2YXIgaT10aGlzLl9pbmRleFRvVmFsdWUobik7dGhpcy5faXRlbUFjdGl2YXRlKGksdCk7cmV0dXJufXQ9dC5wYXJlbnROb2RlfX0sX2l0ZW1BY3RpdmF0ZTpmdW5jdGlvbihlLHQpe3RoaXMuZmlyZSgiaXJvbi1hY3RpdmF0ZSIse3NlbGVjdGVkOmUsaXRlbTp0fSx7Y2FuY2VsYWJsZTohMH0pLmRlZmF1bHRQcmV2ZW50ZWR8fHRoaXMuc2VsZWN0KGUpfX07WXQoe190ZW1wbGF0ZTpRYAogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgIH0KCiAgICAgIDpob3N0ID4gOjpzbG90dGVkKDpub3Qoc2xvdCk6bm90KC5pcm9uLXNlbGVjdGVkKSkgewogICAgICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICAgICAgfQogICAgPC9zdHlsZT4KCiAgICA8c2xvdD48L3Nsb3Q+CmAsaXM6Imlyb24tcGFnZXMiLGJlaGF2aW9yczpbSnMsd2hdLHByb3BlcnRpZXM6e2FjdGl2YXRlRXZlbnQ6e3R5cGU6U3RyaW5nLHZhbHVlOm51bGx9fSxvYnNlcnZlcnM6WyJfc2VsZWN0ZWRQYWdlQ2hhbmdlZChzZWxlY3RlZCkiXSxfc2VsZWN0ZWRQYWdlQ2hhbmdlZDpmdW5jdGlvbihlLHQpe3RoaXMuYXN5bmModGhpcy5ub3RpZnlSZXNpemUpfX0pO3ZhciBuMHQ9UWAKPGN1c3RvbS1zdHlsZT4KICA8c3R5bGUgaXM9ImN1c3RvbS1zdHlsZSI+CiAgICBodG1sIHsKCiAgICAgIC0tc2hhZG93LXRyYW5zaXRpb246IHsKICAgICAgICB0cmFuc2l0aW9uOiBib3gtc2hhZG93IDAuMjhzIGN1YmljLWJlemllcigwLjQsIDAsIDAuMiwgMSk7CiAgICAgIH07CgogICAgICAtLXNoYWRvdy1ub25lOiB7CiAgICAgICAgYm94LXNoYWRvdzogbm9uZTsKICAgICAgfTsKCiAgICAgIC8qIGZyb20gaHR0cDovL2NvZGVwZW4uaW8vc2h5bmRtYW4vcGVuL2M1Mzk0ZGRmMmU4YjJhNWM5MTg1OTA0YjU3NDIxY2RiICovCgogICAgICAtLXNoYWRvdy1lbGV2YXRpb24tMmRwOiB7CiAgICAgICAgYm94LXNoYWRvdzogMCAycHggMnB4IDAgcmdiYSgwLCAwLCAwLCAwLjE0KSwKICAgICAgICAgICAgICAgICAgICAwIDFweCA1cHggMCByZ2JhKDAsIDAsIDAsIDAuMTIpLAogICAgICAgICAgICAgICAgICAgIDAgM3B4IDFweCAtMnB4IHJnYmEoMCwgMCwgMCwgMC4yKTsKICAgICAgfTsKCiAgICAgIC0tc2hhZG93LWVsZXZhdGlvbi0zZHA6IHsKICAgICAgICBib3gtc2hhZG93OiAwIDNweCA0cHggMCByZ2JhKDAsIDAsIDAsIDAuMTQpLAogICAgICAgICAgICAgICAgICAgIDAgMXB4IDhweCAwIHJnYmEoMCwgMCwgMCwgMC4xMiksCiAgICAgICAgICAgICAgICAgICAgMCAzcHggM3B4IC0ycHggcmdiYSgwLCAwLCAwLCAwLjQpOwogICAgICB9OwoKICAgICAgLS1zaGFkb3ctZWxldmF0aW9uLTRkcDogewogICAgICAgIGJveC1zaGFkb3c6IDAgNHB4IDVweCAwIHJnYmEoMCwgMCwgMCwgMC4xNCksCiAgICAgICAgICAgICAgICAgICAgMCAxcHggMTBweCAwIHJnYmEoMCwgMCwgMCwgMC4xMiksCiAgICAgICAgICAgICAgICAgICAgMCAycHggNHB4IC0xcHggcmdiYSgwLCAwLCAwLCAwLjQpOwogICAgICB9OwoKICAgICAgLS1zaGFkb3ctZWxldmF0aW9uLTZkcDogewogICAgICAgIGJveC1zaGFkb3c6IDAgNnB4IDEwcHggMCByZ2JhKDAsIDAsIDAsIDAuMTQpLAogICAgICAgICAgICAgICAgICAgIDAgMXB4IDE4cHggMCByZ2JhKDAsIDAsIDAsIDAuMTIpLAogICAgICAgICAgICAgICAgICAgIDAgM3B4IDVweCAtMXB4IHJnYmEoMCwgMCwgMCwgMC40KTsKICAgICAgfTsKCiAgICAgIC0tc2hhZG93LWVsZXZhdGlvbi04ZHA6IHsKICAgICAgICBib3gtc2hhZG93OiAwIDhweCAxMHB4IDFweCByZ2JhKDAsIDAsIDAsIDAuMTQpLAogICAgICAgICAgICAgICAgICAgIDAgM3B4IDE0cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xMiksCiAgICAgICAgICAgICAgICAgICAgMCA1cHggNXB4IC0zcHggcmdiYSgwLCAwLCAwLCAwLjQpOwogICAgICB9OwoKICAgICAgLS1zaGFkb3ctZWxldmF0aW9uLTEyZHA6IHsKICAgICAgICBib3gtc2hhZG93OiAwIDEycHggMTZweCAxcHggcmdiYSgwLCAwLCAwLCAwLjE0KSwKICAgICAgICAgICAgICAgICAgICAwIDRweCAyMnB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMTIpLAogICAgICAgICAgICAgICAgICAgIDAgNnB4IDdweCAtNHB4IHJnYmEoMCwgMCwgMCwgMC40KTsKICAgICAgfTsKCiAgICAgIC0tc2hhZG93LWVsZXZhdGlvbi0xNmRwOiB7CiAgICAgICAgYm94LXNoYWRvdzogMCAxNnB4IDI0cHggMnB4IHJnYmEoMCwgMCwgMCwgMC4xNCksCiAgICAgICAgICAgICAgICAgICAgMCAgNnB4IDMwcHggNXB4IHJnYmEoMCwgMCwgMCwgMC4xMiksCiAgICAgICAgICAgICAgICAgICAgMCAgOHB4IDEwcHggLTVweCByZ2JhKDAsIDAsIDAsIDAuNCk7CiAgICAgIH07CgogICAgICAtLXNoYWRvdy1lbGV2YXRpb24tMjRkcDogewogICAgICAgIGJveC1zaGFkb3c6IDAgMjRweCAzOHB4IDNweCByZ2JhKDAsIDAsIDAsIDAuMTQpLAogICAgICAgICAgICAgICAgICAgIDAgOXB4IDQ2cHggOHB4IHJnYmEoMCwgMCwgMCwgMC4xMiksCiAgICAgICAgICAgICAgICAgICAgMCAxMXB4IDE1cHggLTdweCByZ2JhKDAsIDAsIDAsIDAuNCk7CiAgICAgIH07CiAgICB9CiAgPC9zdHlsZT4KPC9jdXN0b20tc3R5bGU+YDtuMHQuc2V0QXR0cmlidXRlKCJzdHlsZSIsImRpc3BsYXk6IG5vbmU7Iik7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChuMHQuY29udGVudCk7dmFyIGkwdD1RYAo8ZG9tLW1vZHVsZSBpZD0icGFwZXItbWF0ZXJpYWwtc3R5bGVzIj4KICA8dGVtcGxhdGU+CiAgICA8c3R5bGU+CiAgICAgIGh0bWwgewogICAgICAgIC0tcGFwZXItbWF0ZXJpYWw6IHsKICAgICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIH07CiAgICAgICAgLS1wYXBlci1tYXRlcmlhbC1lbGV2YXRpb24tMTogewogICAgICAgICAgQGFwcGx5IC0tc2hhZG93LWVsZXZhdGlvbi0yZHA7CiAgICAgICAgfTsKICAgICAgICAtLXBhcGVyLW1hdGVyaWFsLWVsZXZhdGlvbi0yOiB7CiAgICAgICAgICBAYXBwbHkgLS1zaGFkb3ctZWxldmF0aW9uLTRkcDsKICAgICAgICB9OwogICAgICAgIC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTM6IHsKICAgICAgICAgIEBhcHBseSAtLXNoYWRvdy1lbGV2YXRpb24tNmRwOwogICAgICAgIH07CiAgICAgICAgLS1wYXBlci1tYXRlcmlhbC1lbGV2YXRpb24tNDogewogICAgICAgICAgQGFwcGx5IC0tc2hhZG93LWVsZXZhdGlvbi04ZHA7CiAgICAgICAgfTsKICAgICAgICAtLXBhcGVyLW1hdGVyaWFsLWVsZXZhdGlvbi01OiB7CiAgICAgICAgICBAYXBwbHkgLS1zaGFkb3ctZWxldmF0aW9uLTE2ZHA7CiAgICAgICAgfTsKICAgICAgfQogICAgICAucGFwZXItbWF0ZXJpYWwgewogICAgICAgIEBhcHBseSAtLXBhcGVyLW1hdGVyaWFsOwogICAgICB9CiAgICAgIC5wYXBlci1tYXRlcmlhbFtlbGV2YXRpb249IjEiXSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTE7CiAgICAgIH0KICAgICAgLnBhcGVyLW1hdGVyaWFsW2VsZXZhdGlvbj0iMiJdIHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1tYXRlcmlhbC1lbGV2YXRpb24tMjsKICAgICAgfQogICAgICAucGFwZXItbWF0ZXJpYWxbZWxldmF0aW9uPSIzIl0gewogICAgICAgIEBhcHBseSAtLXBhcGVyLW1hdGVyaWFsLWVsZXZhdGlvbi0zOwogICAgICB9CiAgICAgIC5wYXBlci1tYXRlcmlhbFtlbGV2YXRpb249IjQiXSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTQ7CiAgICAgIH0KICAgICAgLnBhcGVyLW1hdGVyaWFsW2VsZXZhdGlvbj0iNSJdIHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1tYXRlcmlhbC1lbGV2YXRpb24tNTsKICAgICAgfQoKICAgICAgLyogRHVwbGljYXRlIHRoZSBzdHlsZXMgYmVjYXVzZSBvZiBodHRwczovL2dpdGh1Yi5jb20vd2ViY29tcG9uZW50cy9zaGFkeWNzcy9pc3N1ZXMvMTkzICovCiAgICAgIDpob3N0IHsKICAgICAgICAtLXBhcGVyLW1hdGVyaWFsOiB7CiAgICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICB9OwogICAgICAgIC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTE6IHsKICAgICAgICAgIEBhcHBseSAtLXNoYWRvdy1lbGV2YXRpb24tMmRwOwogICAgICAgIH07CiAgICAgICAgLS1wYXBlci1tYXRlcmlhbC1lbGV2YXRpb24tMjogewogICAgICAgICAgQGFwcGx5IC0tc2hhZG93LWVsZXZhdGlvbi00ZHA7CiAgICAgICAgfTsKICAgICAgICAtLXBhcGVyLW1hdGVyaWFsLWVsZXZhdGlvbi0zOiB7CiAgICAgICAgICBAYXBwbHkgLS1zaGFkb3ctZWxldmF0aW9uLTZkcDsKICAgICAgICB9OwogICAgICAgIC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTQ6IHsKICAgICAgICAgIEBhcHBseSAtLXNoYWRvdy1lbGV2YXRpb24tOGRwOwogICAgICAgIH07CiAgICAgICAgLS1wYXBlci1tYXRlcmlhbC1lbGV2YXRpb24tNTogewogICAgICAgICAgQGFwcGx5IC0tc2hhZG93LWVsZXZhdGlvbi0xNmRwOwogICAgICAgIH07CiAgICAgIH0KICAgICAgOmhvc3QoLnBhcGVyLW1hdGVyaWFsKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWw7CiAgICAgIH0KICAgICAgOmhvc3QoLnBhcGVyLW1hdGVyaWFsW2VsZXZhdGlvbj0iMSJdKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTE7CiAgICAgIH0KICAgICAgOmhvc3QoLnBhcGVyLW1hdGVyaWFsW2VsZXZhdGlvbj0iMiJdKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTI7CiAgICAgIH0KICAgICAgOmhvc3QoLnBhcGVyLW1hdGVyaWFsW2VsZXZhdGlvbj0iMyJdKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTM7CiAgICAgIH0KICAgICAgOmhvc3QoLnBhcGVyLW1hdGVyaWFsW2VsZXZhdGlvbj0iNCJdKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTQ7CiAgICAgIH0KICAgICAgOmhvc3QoLnBhcGVyLW1hdGVyaWFsW2VsZXZhdGlvbj0iNSJdKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTU7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC90ZW1wbGF0ZT4KPC9kb20tbW9kdWxlPmA7aTB0LnNldEF0dHJpYnV0ZSgic3R5bGUiLCJkaXNwbGF5OiBub25lOyIpO2RvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoaTB0LmNvbnRlbnQpO3ZhciBEaT17cHJvcGVydGllczp7Zm9jdXNlZDp7dHlwZTpCb29sZWFuLHZhbHVlOiExLG5vdGlmeTohMCxyZWFkT25seTohMCxyZWZsZWN0VG9BdHRyaWJ1dGU6ITB9LGRpc2FibGVkOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITEsbm90aWZ5OiEwLG9ic2VydmVyOiJfZGlzYWJsZWRDaGFuZ2VkIixyZWZsZWN0VG9BdHRyaWJ1dGU6ITB9LF9vbGRUYWJJbmRleDp7dHlwZTpTdHJpbmd9LF9ib3VuZEZvY3VzQmx1ckhhbmRsZXI6e3R5cGU6RnVuY3Rpb24sdmFsdWU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fZm9jdXNCbHVySGFuZGxlci5iaW5kKHRoaXMpfX19LG9ic2VydmVyczpbIl9jaGFuZ2VkQ29udHJvbFN0YXRlKGZvY3VzZWQsIGRpc2FibGVkKSJdLHJlYWR5OmZ1bmN0aW9uKCl7dGhpcy5hZGRFdmVudExpc3RlbmVyKCJmb2N1cyIsdGhpcy5fYm91bmRGb2N1c0JsdXJIYW5kbGVyLCEwKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImJsdXIiLHRoaXMuX2JvdW5kRm9jdXNCbHVySGFuZGxlciwhMCl9LF9mb2N1c0JsdXJIYW5kbGVyOmZ1bmN0aW9uKGUpe3RoaXMuX3NldEZvY3VzZWQoZS50eXBlPT09ImZvY3VzIil9LF9kaXNhYmxlZENoYW5nZWQ6ZnVuY3Rpb24oZSx0KXt0aGlzLnNldEF0dHJpYnV0ZSgiYXJpYS1kaXNhYmxlZCIsZT8idHJ1ZSI6ImZhbHNlIiksdGhpcy5zdHlsZS5wb2ludGVyRXZlbnRzPWU/Im5vbmUiOiIiLGU/KHRoaXMuX29sZFRhYkluZGV4PXRoaXMuZ2V0QXR0cmlidXRlKCJ0YWJpbmRleCIpLHRoaXMuX3NldEZvY3VzZWQoITEpLHRoaXMudGFiSW5kZXg9LTEsdGhpcy5ibHVyKCkpOnRoaXMuX29sZFRhYkluZGV4IT09dm9pZCAwJiYodGhpcy5fb2xkVGFiSW5kZXg9PT1udWxsP3RoaXMucmVtb3ZlQXR0cmlidXRlKCJ0YWJpbmRleCIpOnRoaXMuc2V0QXR0cmlidXRlKCJ0YWJpbmRleCIsdGhpcy5fb2xkVGFiSW5kZXgpKX0sX2NoYW5nZWRDb250cm9sU3RhdGU6ZnVuY3Rpb24oKXt0aGlzLl9jb250cm9sU3RhdGVDaGFuZ2VkJiZ0aGlzLl9jb250cm9sU3RhdGVDaGFuZ2VkKCl9fTt2YXIgWXg9e3Byb3BlcnRpZXM6e3ByZXNzZWQ6e3R5cGU6Qm9vbGVhbixyZWFkT25seTohMCx2YWx1ZTohMSxyZWZsZWN0VG9BdHRyaWJ1dGU6ITAsb2JzZXJ2ZXI6Il9wcmVzc2VkQ2hhbmdlZCJ9LHRvZ2dsZXM6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxyZWZsZWN0VG9BdHRyaWJ1dGU6ITB9LGFjdGl2ZTp7dHlwZTpCb29sZWFuLHZhbHVlOiExLG5vdGlmeTohMCxyZWZsZWN0VG9BdHRyaWJ1dGU6ITB9LHBvaW50ZXJEb3duOnt0eXBlOkJvb2xlYW4scmVhZE9ubHk6ITAsdmFsdWU6ITF9LHJlY2VpdmVkRm9jdXNGcm9tS2V5Ym9hcmQ6e3R5cGU6Qm9vbGVhbixyZWFkT25seTohMH0sYXJpYUFjdGl2ZUF0dHJpYnV0ZTp7dHlwZTpTdHJpbmcsdmFsdWU6ImFyaWEtcHJlc3NlZCIsb2JzZXJ2ZXI6Il9hcmlhQWN0aXZlQXR0cmlidXRlQ2hhbmdlZCJ9fSxsaXN0ZW5lcnM6e2Rvd246Il9kb3duSGFuZGxlciIsdXA6Il91cEhhbmRsZXIiLHRhcDoiX3RhcEhhbmRsZXIifSxvYnNlcnZlcnM6WyJfZm9jdXNDaGFuZ2VkKGZvY3VzZWQpIiwiX2FjdGl2ZUNoYW5nZWQoYWN0aXZlLCBhcmlhQWN0aXZlQXR0cmlidXRlKSJdLGtleUJpbmRpbmdzOnsiZW50ZXI6a2V5ZG93biI6Il9hc3luY0NsaWNrIiwic3BhY2U6a2V5ZG93biI6Il9zcGFjZUtleURvd25IYW5kbGVyIiwic3BhY2U6a2V5dXAiOiJfc3BhY2VLZXlVcEhhbmRsZXIifSxfbW91c2VFdmVudFJlOi9ebW91c2UvLF90YXBIYW5kbGVyOmZ1bmN0aW9uKCl7dGhpcy50b2dnbGVzP3RoaXMuX3VzZXJBY3RpdmF0ZSghdGhpcy5hY3RpdmUpOnRoaXMuYWN0aXZlPSExfSxfZm9jdXNDaGFuZ2VkOmZ1bmN0aW9uKGUpe3RoaXMuX2RldGVjdEtleWJvYXJkRm9jdXMoZSksZXx8dGhpcy5fc2V0UHJlc3NlZCghMSl9LF9kZXRlY3RLZXlib2FyZEZvY3VzOmZ1bmN0aW9uKGUpe3RoaXMuX3NldFJlY2VpdmVkRm9jdXNGcm9tS2V5Ym9hcmQoIXRoaXMucG9pbnRlckRvd24mJmUpfSxfdXNlckFjdGl2YXRlOmZ1bmN0aW9uKGUpe3RoaXMuYWN0aXZlIT09ZSYmKHRoaXMuYWN0aXZlPWUsdGhpcy5maXJlKCJjaGFuZ2UiKSl9LF9kb3duSGFuZGxlcjpmdW5jdGlvbihlKXt0aGlzLl9zZXRQb2ludGVyRG93bighMCksdGhpcy5fc2V0UHJlc3NlZCghMCksdGhpcy5fc2V0UmVjZWl2ZWRGb2N1c0Zyb21LZXlib2FyZCghMSl9LF91cEhhbmRsZXI6ZnVuY3Rpb24oKXt0aGlzLl9zZXRQb2ludGVyRG93bighMSksdGhpcy5fc2V0UHJlc3NlZCghMSl9LF9zcGFjZUtleURvd25IYW5kbGVyOmZ1bmN0aW9uKGUpe3ZhciB0PWUuZGV0YWlsLmtleWJvYXJkRXZlbnQscj16dCh0KS5sb2NhbFRhcmdldDt0aGlzLmlzTGlnaHREZXNjZW5kYW50KHIpfHwodC5wcmV2ZW50RGVmYXVsdCgpLHQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCksdGhpcy5fc2V0UHJlc3NlZCghMCkpfSxfc3BhY2VLZXlVcEhhbmRsZXI6ZnVuY3Rpb24oZSl7dmFyIHQ9ZS5kZXRhaWwua2V5Ym9hcmRFdmVudCxyPXp0KHQpLmxvY2FsVGFyZ2V0O3RoaXMuaXNMaWdodERlc2NlbmRhbnQocil8fCh0aGlzLnByZXNzZWQmJnRoaXMuX2FzeW5jQ2xpY2soKSx0aGlzLl9zZXRQcmVzc2VkKCExKSl9LF9hc3luY0NsaWNrOmZ1bmN0aW9uKCl7dGhpcy5hc3luYyhmdW5jdGlvbigpe3RoaXMuY2xpY2soKX0sMSl9LF9wcmVzc2VkQ2hhbmdlZDpmdW5jdGlvbihlKXt0aGlzLl9jaGFuZ2VkQnV0dG9uU3RhdGUoKX0sX2FyaWFBY3RpdmVBdHRyaWJ1dGVDaGFuZ2VkOmZ1bmN0aW9uKGUsdCl7dCYmdCE9ZSYmdGhpcy5oYXNBdHRyaWJ1dGUodCkmJnRoaXMucmVtb3ZlQXR0cmlidXRlKHQpfSxfYWN0aXZlQ2hhbmdlZDpmdW5jdGlvbihlLHQpe3RoaXMudG9nZ2xlcz90aGlzLnNldEF0dHJpYnV0ZSh0aGlzLmFyaWFBY3RpdmVBdHRyaWJ1dGUsZT8idHJ1ZSI6ImZhbHNlIik6dGhpcy5yZW1vdmVBdHRyaWJ1dGUodGhpcy5hcmlhQWN0aXZlQXR0cmlidXRlKSx0aGlzLl9jaGFuZ2VkQnV0dG9uU3RhdGUoKX0sX2NvbnRyb2xTdGF0ZUNoYW5nZWQ6ZnVuY3Rpb24oKXt0aGlzLmRpc2FibGVkP3RoaXMuX3NldFByZXNzZWQoITEpOnRoaXMuX2NoYW5nZWRCdXR0b25TdGF0ZSgpfSxfY2hhbmdlZEJ1dHRvblN0YXRlOmZ1bmN0aW9uKCl7dGhpcy5fYnV0dG9uU3RhdGVDaGFuZ2VkJiZ0aGlzLl9idXR0b25TdGF0ZUNoYW5nZWQoKX19LFNoPVtPbyxZeF07dmFyIE1oPXtkaXN0YW5jZTpmdW5jdGlvbihlLHQscixuKXt2YXIgaT1lLXIsbz10LW47cmV0dXJuIE1hdGguc3FydChpKmkrbypvKX0sbm93OndpbmRvdy5wZXJmb3JtYW5jZSYmd2luZG93LnBlcmZvcm1hbmNlLm5vdz93aW5kb3cucGVyZm9ybWFuY2Uubm93LmJpbmQod2luZG93LnBlcmZvcm1hbmNlKTpEYXRlLm5vd307ZnVuY3Rpb24gbzB0KGUpe3RoaXMuZWxlbWVudD1lLHRoaXMud2lkdGg9dGhpcy5ib3VuZGluZ1JlY3Qud2lkdGgsdGhpcy5oZWlnaHQ9dGhpcy5ib3VuZGluZ1JlY3QuaGVpZ2h0LHRoaXMuc2l6ZT1NYXRoLm1heCh0aGlzLndpZHRoLHRoaXMuaGVpZ2h0KX1vMHQucHJvdG90eXBlPXtnZXQgYm91bmRpbmdSZWN0KCl7cmV0dXJuIHRoaXMuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKX0sZnVydGhlc3RDb3JuZXJEaXN0YW5jZUZyb206ZnVuY3Rpb24oZSx0KXt2YXIgcj1NaC5kaXN0YW5jZShlLHQsMCwwKSxuPU1oLmRpc3RhbmNlKGUsdCx0aGlzLndpZHRoLDApLGk9TWguZGlzdGFuY2UoZSx0LDAsdGhpcy5oZWlnaHQpLG89TWguZGlzdGFuY2UoZSx0LHRoaXMud2lkdGgsdGhpcy5oZWlnaHQpO3JldHVybiBNYXRoLm1heChyLG4saSxvKX19O2Z1bmN0aW9uIGdfKGUpe3RoaXMuZWxlbWVudD1lLHRoaXMuY29sb3I9d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSkuY29sb3IsdGhpcy53YXZlPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpLHRoaXMud2F2ZUNvbnRhaW5lcj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKSx0aGlzLndhdmUuc3R5bGUuYmFja2dyb3VuZENvbG9yPXRoaXMuY29sb3IsdGhpcy53YXZlLmNsYXNzTGlzdC5hZGQoIndhdmUiKSx0aGlzLndhdmVDb250YWluZXIuY2xhc3NMaXN0LmFkZCgid2F2ZS1jb250YWluZXIiKSx6dCh0aGlzLndhdmVDb250YWluZXIpLmFwcGVuZENoaWxkKHRoaXMud2F2ZSksdGhpcy5yZXNldEludGVyYWN0aW9uU3RhdGUoKX1nXy5NQVhfUkFESVVTPTMwMDtnXy5wcm90b3R5cGU9e2dldCByZWNlbnRlcnMoKXtyZXR1cm4gdGhpcy5lbGVtZW50LnJlY2VudGVyc30sZ2V0IGNlbnRlcigpe3JldHVybiB0aGlzLmVsZW1lbnQuY2VudGVyfSxnZXQgbW91c2VEb3duRWxhcHNlZCgpe3ZhciBlO3JldHVybiB0aGlzLm1vdXNlRG93blN0YXJ0PyhlPU1oLm5vdygpLXRoaXMubW91c2VEb3duU3RhcnQsdGhpcy5tb3VzZVVwU3RhcnQmJihlLT10aGlzLm1vdXNlVXBFbGFwc2VkKSxlKTowfSxnZXQgbW91c2VVcEVsYXBzZWQoKXtyZXR1cm4gdGhpcy5tb3VzZVVwU3RhcnQ/TWgubm93KCktdGhpcy5tb3VzZVVwU3RhcnQ6MH0sZ2V0IG1vdXNlRG93bkVsYXBzZWRTZWNvbmRzKCl7cmV0dXJuIHRoaXMubW91c2VEb3duRWxhcHNlZC8xZTN9LGdldCBtb3VzZVVwRWxhcHNlZFNlY29uZHMoKXtyZXR1cm4gdGhpcy5tb3VzZVVwRWxhcHNlZC8xZTN9LGdldCBtb3VzZUludGVyYWN0aW9uU2Vjb25kcygpe3JldHVybiB0aGlzLm1vdXNlRG93bkVsYXBzZWRTZWNvbmRzK3RoaXMubW91c2VVcEVsYXBzZWRTZWNvbmRzfSxnZXQgaW5pdGlhbE9wYWNpdHkoKXtyZXR1cm4gdGhpcy5lbGVtZW50LmluaXRpYWxPcGFjaXR5fSxnZXQgb3BhY2l0eURlY2F5VmVsb2NpdHkoKXtyZXR1cm4gdGhpcy5lbGVtZW50Lm9wYWNpdHlEZWNheVZlbG9jaXR5fSxnZXQgcmFkaXVzKCl7dmFyIGU9dGhpcy5jb250YWluZXJNZXRyaWNzLndpZHRoKnRoaXMuY29udGFpbmVyTWV0cmljcy53aWR0aCx0PXRoaXMuY29udGFpbmVyTWV0cmljcy5oZWlnaHQqdGhpcy5jb250YWluZXJNZXRyaWNzLmhlaWdodCxyPU1hdGgubWluKE1hdGguc3FydChlK3QpLGdfLk1BWF9SQURJVVMpKjEuMSs1LG49MS4xLS4yKihyL2dfLk1BWF9SQURJVVMpLGk9dGhpcy5tb3VzZUludGVyYWN0aW9uU2Vjb25kcy9uLG89ciooMS1NYXRoLnBvdyg4MCwtaSkpO3JldHVybiBNYXRoLmFicyhvKX0sZ2V0IG9wYWNpdHkoKXtyZXR1cm4gdGhpcy5tb3VzZVVwU3RhcnQ/TWF0aC5tYXgoMCx0aGlzLmluaXRpYWxPcGFjaXR5LXRoaXMubW91c2VVcEVsYXBzZWRTZWNvbmRzKnRoaXMub3BhY2l0eURlY2F5VmVsb2NpdHkpOnRoaXMuaW5pdGlhbE9wYWNpdHl9LGdldCBvdXRlck9wYWNpdHkoKXt2YXIgZT10aGlzLm1vdXNlVXBFbGFwc2VkU2Vjb25kcyouMyx0PXRoaXMub3BhY2l0eTtyZXR1cm4gTWF0aC5tYXgoMCxNYXRoLm1pbihlLHQpKX0sZ2V0IGlzT3BhY2l0eUZ1bGx5RGVjYXllZCgpe3JldHVybiB0aGlzLm9wYWNpdHk8LjAxJiZ0aGlzLnJhZGl1cz49TWF0aC5taW4odGhpcy5tYXhSYWRpdXMsZ18uTUFYX1JBRElVUyl9LGdldCBpc1Jlc3RpbmdBdE1heFJhZGl1cygpe3JldHVybiB0aGlzLm9wYWNpdHk+PXRoaXMuaW5pdGlhbE9wYWNpdHkmJnRoaXMucmFkaXVzPj1NYXRoLm1pbih0aGlzLm1heFJhZGl1cyxnXy5NQVhfUkFESVVTKX0sZ2V0IGlzQW5pbWF0aW9uQ29tcGxldGUoKXtyZXR1cm4gdGhpcy5tb3VzZVVwU3RhcnQ/dGhpcy5pc09wYWNpdHlGdWxseURlY2F5ZWQ6dGhpcy5pc1Jlc3RpbmdBdE1heFJhZGl1c30sZ2V0IHRyYW5zbGF0aW9uRnJhY3Rpb24oKXtyZXR1cm4gTWF0aC5taW4oMSx0aGlzLnJhZGl1cy90aGlzLmNvbnRhaW5lck1ldHJpY3Muc2l6ZSoyL01hdGguc3FydCgyKSl9LGdldCB4Tm93KCl7cmV0dXJuIHRoaXMueEVuZD90aGlzLnhTdGFydCt0aGlzLnRyYW5zbGF0aW9uRnJhY3Rpb24qKHRoaXMueEVuZC10aGlzLnhTdGFydCk6dGhpcy54U3RhcnR9LGdldCB5Tm93KCl7cmV0dXJuIHRoaXMueUVuZD90aGlzLnlTdGFydCt0aGlzLnRyYW5zbGF0aW9uRnJhY3Rpb24qKHRoaXMueUVuZC10aGlzLnlTdGFydCk6dGhpcy55U3RhcnR9LGdldCBpc01vdXNlRG93bigpe3JldHVybiB0aGlzLm1vdXNlRG93blN0YXJ0JiYhdGhpcy5tb3VzZVVwU3RhcnR9LHJlc2V0SW50ZXJhY3Rpb25TdGF0ZTpmdW5jdGlvbigpe3RoaXMubWF4UmFkaXVzPTAsdGhpcy5tb3VzZURvd25TdGFydD0wLHRoaXMubW91c2VVcFN0YXJ0PTAsdGhpcy54U3RhcnQ9MCx0aGlzLnlTdGFydD0wLHRoaXMueEVuZD0wLHRoaXMueUVuZD0wLHRoaXMuc2xpZGVEaXN0YW5jZT0wLHRoaXMuY29udGFpbmVyTWV0cmljcz1uZXcgbzB0KHRoaXMuZWxlbWVudCl9LGRyYXc6ZnVuY3Rpb24oKXt2YXIgZSx0LHI7dGhpcy53YXZlLnN0eWxlLm9wYWNpdHk9dGhpcy5vcGFjaXR5LGU9dGhpcy5yYWRpdXMvKHRoaXMuY29udGFpbmVyTWV0cmljcy5zaXplLzIpLHQ9dGhpcy54Tm93LXRoaXMuY29udGFpbmVyTWV0cmljcy53aWR0aC8yLHI9dGhpcy55Tm93LXRoaXMuY29udGFpbmVyTWV0cmljcy5oZWlnaHQvMix0aGlzLndhdmVDb250YWluZXIuc3R5bGUud2Via2l0VHJhbnNmb3JtPSJ0cmFuc2xhdGUoIit0KyJweCwgIityKyJweCkiLHRoaXMud2F2ZUNvbnRhaW5lci5zdHlsZS50cmFuc2Zvcm09InRyYW5zbGF0ZTNkKCIrdCsicHgsICIrcisicHgsIDApIix0aGlzLndhdmUuc3R5bGUud2Via2l0VHJhbnNmb3JtPSJzY2FsZSgiK2UrIiwiK2UrIikiLHRoaXMud2F2ZS5zdHlsZS50cmFuc2Zvcm09InNjYWxlM2QoIitlKyIsIitlKyIsMSkifSxkb3duQWN0aW9uOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuY29udGFpbmVyTWV0cmljcy53aWR0aC8yLHI9dGhpcy5jb250YWluZXJNZXRyaWNzLmhlaWdodC8yO3RoaXMucmVzZXRJbnRlcmFjdGlvblN0YXRlKCksdGhpcy5tb3VzZURvd25TdGFydD1NaC5ub3coKSx0aGlzLmNlbnRlcj8odGhpcy54U3RhcnQ9dCx0aGlzLnlTdGFydD1yLHRoaXMuc2xpZGVEaXN0YW5jZT1NaC5kaXN0YW5jZSh0aGlzLnhTdGFydCx0aGlzLnlTdGFydCx0aGlzLnhFbmQsdGhpcy55RW5kKSk6KHRoaXMueFN0YXJ0PWU/ZS5kZXRhaWwueC10aGlzLmNvbnRhaW5lck1ldHJpY3MuYm91bmRpbmdSZWN0LmxlZnQ6dGhpcy5jb250YWluZXJNZXRyaWNzLndpZHRoLzIsdGhpcy55U3RhcnQ9ZT9lLmRldGFpbC55LXRoaXMuY29udGFpbmVyTWV0cmljcy5ib3VuZGluZ1JlY3QudG9wOnRoaXMuY29udGFpbmVyTWV0cmljcy5oZWlnaHQvMiksdGhpcy5yZWNlbnRlcnMmJih0aGlzLnhFbmQ9dCx0aGlzLnlFbmQ9cix0aGlzLnNsaWRlRGlzdGFuY2U9TWguZGlzdGFuY2UodGhpcy54U3RhcnQsdGhpcy55U3RhcnQsdGhpcy54RW5kLHRoaXMueUVuZCkpLHRoaXMubWF4UmFkaXVzPXRoaXMuY29udGFpbmVyTWV0cmljcy5mdXJ0aGVzdENvcm5lckRpc3RhbmNlRnJvbSh0aGlzLnhTdGFydCx0aGlzLnlTdGFydCksdGhpcy53YXZlQ29udGFpbmVyLnN0eWxlLnRvcD0odGhpcy5jb250YWluZXJNZXRyaWNzLmhlaWdodC10aGlzLmNvbnRhaW5lck1ldHJpY3Muc2l6ZSkvMisicHgiLHRoaXMud2F2ZUNvbnRhaW5lci5zdHlsZS5sZWZ0PSh0aGlzLmNvbnRhaW5lck1ldHJpY3Mud2lkdGgtdGhpcy5jb250YWluZXJNZXRyaWNzLnNpemUpLzIrInB4Iix0aGlzLndhdmVDb250YWluZXIuc3R5bGUud2lkdGg9dGhpcy5jb250YWluZXJNZXRyaWNzLnNpemUrInB4Iix0aGlzLndhdmVDb250YWluZXIuc3R5bGUuaGVpZ2h0PXRoaXMuY29udGFpbmVyTWV0cmljcy5zaXplKyJweCJ9LHVwQWN0aW9uOmZ1bmN0aW9uKGUpeyF0aGlzLmlzTW91c2VEb3dufHwodGhpcy5tb3VzZVVwU3RhcnQ9TWgubm93KCkpfSxyZW1vdmU6ZnVuY3Rpb24oKXt6dCh6dCh0aGlzLndhdmVDb250YWluZXIpLnBhcmVudE5vZGUpLnJlbW92ZUNoaWxkKHRoaXMud2F2ZUNvbnRhaW5lcil9fTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgYm9yZGVyLXJhZGl1czogaW5oZXJpdDsKICAgICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgICAgIHRvcDogMDsKICAgICAgICBsZWZ0OiAwOwogICAgICAgIHJpZ2h0OiAwOwogICAgICAgIGJvdHRvbTogMDsKCiAgICAgICAgLyogU2VlIFBvbHltZXJFbGVtZW50cy9wYXBlci1iZWhhdmlvcnMvaXNzdWVzLzM0LiBPbiBub24tQ2hyb21lIGJyb3dzZXJzLAogICAgICAgICAqIGNyZWF0aW5nIGEgbm9kZSAod2l0aCBhIHBvc2l0aW9uOmFic29sdXRlKSBpbiB0aGUgbWlkZGxlIG9mIGFuIGV2ZW50CiAgICAgICAgICogaGFuZGxlciAiaW50ZXJydXB0cyIgdGhhdCBldmVudCBoYW5kbGVyICh3aGljaCBoYXBwZW5zIHdoZW4gdGhlCiAgICAgICAgICogcmlwcGxlIGlzIGNyZWF0ZWQgb24gZGVtYW5kKSAqLwogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICB9CgogICAgICA6aG9zdChbYW5pbWF0aW5nXSkgewogICAgICAgIC8qIFRoaXMgcmVzb2x2ZXMgYSByZW5kZXJpbmcgaXNzdWUgaW4gQ2hyb21lIChhcyBvZiA0MCkgd2hlcmUgdGhlCiAgICAgICAgICAgcmlwcGxlIGlzIG5vdCBwcm9wZXJseSBjbGlwcGVkIGJ5IGl0cyBwYXJlbnQgKHdoaWNoIG1heSBoYXZlCiAgICAgICAgICAgcm91bmRlZCBjb3JuZXJzKS4gU2VlOiBodHRwOi8vanNiaW4uY29tL3RlbWV4YS80CgogICAgICAgICAgIE5vdGU6IFdlIG9ubHkgYXBwbHkgdGhpcyBzdHlsZSBjb25kaXRpb25hbGx5LiBPdGhlcndpc2UsIHRoZSBicm93c2VyCiAgICAgICAgICAgd2lsbCBjcmVhdGUgYSBuZXcgY29tcG9zaXRpbmcgbGF5ZXIgZm9yIGV2ZXJ5IHJpcHBsZSBlbGVtZW50IG9uIHRoZQogICAgICAgICAgIHBhZ2UsIGFuZCB0aGF0IHdvdWxkIGJlIGJhZC4gKi8KICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogdHJhbnNsYXRlKDAsIDApOwogICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgMCwgMCk7CiAgICAgIH0KCiAgICAgICNiYWNrZ3JvdW5kLAogICAgICAjd2F2ZXMsCiAgICAgIC53YXZlLWNvbnRhaW5lciwKICAgICAgLndhdmUgewogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICB0b3A6IDA7CiAgICAgICAgbGVmdDogMDsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgIH0KCiAgICAgICNiYWNrZ3JvdW5kLAogICAgICAud2F2ZSB7CiAgICAgICAgb3BhY2l0eTogMDsKICAgICAgfQoKICAgICAgI3dhdmVzLAogICAgICAud2F2ZSB7CiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgfQoKICAgICAgLndhdmUtY29udGFpbmVyLAogICAgICAud2F2ZSB7CiAgICAgICAgYm9yZGVyLXJhZGl1czogNTAlOwogICAgICB9CgogICAgICA6aG9zdCguY2lyY2xlKSAjYmFja2dyb3VuZCwKICAgICAgOmhvc3QoLmNpcmNsZSkgI3dhdmVzIHsKICAgICAgICBib3JkZXItcmFkaXVzOiA1MCU7CiAgICAgIH0KCiAgICAgIDpob3N0KC5jaXJjbGUpIC53YXZlLWNvbnRhaW5lciB7CiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgfQogICAgPC9zdHlsZT4KCiAgICA8ZGl2IGlkPSJiYWNrZ3JvdW5kIj48L2Rpdj4KICAgIDxkaXYgaWQ9IndhdmVzIj48L2Rpdj4KYCxpczoicGFwZXItcmlwcGxlIixiZWhhdmlvcnM6W09vXSxwcm9wZXJ0aWVzOntpbml0aWFsT3BhY2l0eTp7dHlwZTpOdW1iZXIsdmFsdWU6LjI1fSxvcGFjaXR5RGVjYXlWZWxvY2l0eTp7dHlwZTpOdW1iZXIsdmFsdWU6Ljh9LHJlY2VudGVyczp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxjZW50ZXI6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0scmlwcGxlczp7dHlwZTpBcnJheSx2YWx1ZTpmdW5jdGlvbigpe3JldHVybltdfX0sYW5pbWF0aW5nOnt0eXBlOkJvb2xlYW4scmVhZE9ubHk6ITAscmVmbGVjdFRvQXR0cmlidXRlOiEwLHZhbHVlOiExfSxob2xkRG93bjp7dHlwZTpCb29sZWFuLHZhbHVlOiExLG9ic2VydmVyOiJfaG9sZERvd25DaGFuZ2VkIn0sbm9pbms6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sX2FuaW1hdGluZzp7dHlwZTpCb29sZWFufSxfYm91bmRBbmltYXRlOnt0eXBlOkZ1bmN0aW9uLHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuYW5pbWF0ZS5iaW5kKHRoaXMpfX19LGdldCB0YXJnZXQoKXtyZXR1cm4gdGhpcy5rZXlFdmVudFRhcmdldH0sa2V5QmluZGluZ3M6eyJlbnRlcjprZXlkb3duIjoiX29uRW50ZXJLZXlkb3duIiwic3BhY2U6a2V5ZG93biI6Il9vblNwYWNlS2V5ZG93biIsInNwYWNlOmtleXVwIjoiX29uU3BhY2VLZXl1cCJ9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7enQodGhpcykucGFyZW50Tm9kZS5ub2RlVHlwZT09MTE/dGhpcy5rZXlFdmVudFRhcmdldD16dCh0aGlzKS5nZXRPd25lclJvb3QoKS5ob3N0OnRoaXMua2V5RXZlbnRUYXJnZXQ9enQodGhpcykucGFyZW50Tm9kZTt2YXIgZT10aGlzLmtleUV2ZW50VGFyZ2V0O3RoaXMubGlzdGVuKGUsInVwIiwidWlVcEFjdGlvbiIpLHRoaXMubGlzdGVuKGUsImRvd24iLCJ1aURvd25BY3Rpb24iKX0sZGV0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLnVubGlzdGVuKHRoaXMua2V5RXZlbnRUYXJnZXQsInVwIiwidWlVcEFjdGlvbiIpLHRoaXMudW5saXN0ZW4odGhpcy5rZXlFdmVudFRhcmdldCwiZG93biIsInVpRG93bkFjdGlvbiIpLHRoaXMua2V5RXZlbnRUYXJnZXQ9bnVsbH0sZ2V0IHNob3VsZEtlZXBBbmltYXRpbmcoKXtmb3IodmFyIGU9MDtlPHRoaXMucmlwcGxlcy5sZW5ndGg7KytlKWlmKCF0aGlzLnJpcHBsZXNbZV0uaXNBbmltYXRpb25Db21wbGV0ZSlyZXR1cm4hMDtyZXR1cm4hMX0sc2ltdWxhdGVkUmlwcGxlOmZ1bmN0aW9uKCl7dGhpcy5kb3duQWN0aW9uKG51bGwpLHRoaXMuYXN5bmMoZnVuY3Rpb24oKXt0aGlzLnVwQWN0aW9uKCl9LDEpfSx1aURvd25BY3Rpb246ZnVuY3Rpb24oZSl7dGhpcy5ub2lua3x8dGhpcy5kb3duQWN0aW9uKGUpfSxkb3duQWN0aW9uOmZ1bmN0aW9uKGUpe2lmKCEodGhpcy5ob2xkRG93biYmdGhpcy5yaXBwbGVzLmxlbmd0aD4wKSl7dmFyIHQ9dGhpcy5hZGRSaXBwbGUoKTt0LmRvd25BY3Rpb24oZSksdGhpcy5fYW5pbWF0aW5nfHwodGhpcy5fYW5pbWF0aW5nPSEwLHRoaXMuYW5pbWF0ZSgpKX19LHVpVXBBY3Rpb246ZnVuY3Rpb24oZSl7dGhpcy5ub2lua3x8dGhpcy51cEFjdGlvbihlKX0sdXBBY3Rpb246ZnVuY3Rpb24oZSl7dGhpcy5ob2xkRG93bnx8KHRoaXMucmlwcGxlcy5mb3JFYWNoKGZ1bmN0aW9uKHQpe3QudXBBY3Rpb24oZSl9KSx0aGlzLl9hbmltYXRpbmc9ITAsdGhpcy5hbmltYXRlKCkpfSxvbkFuaW1hdGlvbkNvbXBsZXRlOmZ1bmN0aW9uKCl7dGhpcy5fYW5pbWF0aW5nPSExLHRoaXMuJC5iYWNrZ3JvdW5kLnN0eWxlLmJhY2tncm91bmRDb2xvcj0iIix0aGlzLmZpcmUoInRyYW5zaXRpb25lbmQiKX0sYWRkUmlwcGxlOmZ1bmN0aW9uKCl7dmFyIGU9bmV3IGdfKHRoaXMpO3JldHVybiB6dCh0aGlzLiQud2F2ZXMpLmFwcGVuZENoaWxkKGUud2F2ZUNvbnRhaW5lciksdGhpcy4kLmJhY2tncm91bmQuc3R5bGUuYmFja2dyb3VuZENvbG9yPWUuY29sb3IsdGhpcy5yaXBwbGVzLnB1c2goZSksdGhpcy5fc2V0QW5pbWF0aW5nKCEwKSxlfSxyZW1vdmVSaXBwbGU6ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy5yaXBwbGVzLmluZGV4T2YoZSk7dDwwfHwodGhpcy5yaXBwbGVzLnNwbGljZSh0LDEpLGUucmVtb3ZlKCksdGhpcy5yaXBwbGVzLmxlbmd0aHx8dGhpcy5fc2V0QW5pbWF0aW5nKCExKSl9LGFuaW1hdGU6ZnVuY3Rpb24oKXtpZighIXRoaXMuX2FuaW1hdGluZyl7dmFyIGUsdDtmb3IoZT0wO2U8dGhpcy5yaXBwbGVzLmxlbmd0aDsrK2UpdD10aGlzLnJpcHBsZXNbZV0sdC5kcmF3KCksdGhpcy4kLmJhY2tncm91bmQuc3R5bGUub3BhY2l0eT10Lm91dGVyT3BhY2l0eSx0LmlzT3BhY2l0eUZ1bGx5RGVjYXllZCYmIXQuaXNSZXN0aW5nQXRNYXhSYWRpdXMmJnRoaXMucmVtb3ZlUmlwcGxlKHQpOyF0aGlzLnNob3VsZEtlZXBBbmltYXRpbmcmJnRoaXMucmlwcGxlcy5sZW5ndGg9PT0wP3RoaXMub25BbmltYXRpb25Db21wbGV0ZSgpOndpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUodGhpcy5fYm91bmRBbmltYXRlKX19LGFuaW1hdGVSaXBwbGU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5hbmltYXRlKCl9LF9vbkVudGVyS2V5ZG93bjpmdW5jdGlvbigpe3RoaXMudWlEb3duQWN0aW9uKCksdGhpcy5hc3luYyh0aGlzLnVpVXBBY3Rpb24sMSl9LF9vblNwYWNlS2V5ZG93bjpmdW5jdGlvbigpe3RoaXMudWlEb3duQWN0aW9uKCl9LF9vblNwYWNlS2V5dXA6ZnVuY3Rpb24oKXt0aGlzLnVpVXBBY3Rpb24oKX0sX2hvbGREb3duQ2hhbmdlZDpmdW5jdGlvbihlLHQpe3QhPT12b2lkIDAmJihlP3RoaXMuZG93bkFjdGlvbigpOnRoaXMudXBBY3Rpb24oKSl9fSk7dmFyIHN1PXtwcm9wZXJ0aWVzOntub2luazp7dHlwZTpCb29sZWFuLG9ic2VydmVyOiJfbm9pbmtDaGFuZ2VkIn0sX3JpcHBsZUNvbnRhaW5lcjp7dHlwZTpPYmplY3R9fSxfYnV0dG9uU3RhdGVDaGFuZ2VkOmZ1bmN0aW9uKCl7dGhpcy5mb2N1c2VkJiZ0aGlzLmVuc3VyZVJpcHBsZSgpfSxfZG93bkhhbmRsZXI6ZnVuY3Rpb24oZSl7WXguX2Rvd25IYW5kbGVyLmNhbGwodGhpcyxlKSx0aGlzLnByZXNzZWQmJnRoaXMuZW5zdXJlUmlwcGxlKGUpfSxlbnN1cmVSaXBwbGU6ZnVuY3Rpb24oZSl7aWYoIXRoaXMuaGFzUmlwcGxlKCkpe3RoaXMuX3JpcHBsZT10aGlzLl9jcmVhdGVSaXBwbGUoKSx0aGlzLl9yaXBwbGUubm9pbms9dGhpcy5ub2luazt2YXIgdD10aGlzLl9yaXBwbGVDb250YWluZXJ8fHRoaXMucm9vdDtpZih0JiZ6dCh0KS5hcHBlbmRDaGlsZCh0aGlzLl9yaXBwbGUpLGUpe3ZhciByPXp0KHRoaXMuX3JpcHBsZUNvbnRhaW5lcnx8dGhpcyksbj16dChlKS5yb290VGFyZ2V0O3IuZGVlcENvbnRhaW5zKG4pJiZ0aGlzLl9yaXBwbGUudWlEb3duQWN0aW9uKGUpfX19LGdldFJpcHBsZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLmVuc3VyZVJpcHBsZSgpLHRoaXMuX3JpcHBsZX0saGFzUmlwcGxlOmZ1bmN0aW9uKCl7cmV0dXJuIEJvb2xlYW4odGhpcy5fcmlwcGxlKX0sX2NyZWF0ZVJpcHBsZTpmdW5jdGlvbigpe3ZhciBlPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInBhcGVyLXJpcHBsZSIpO3JldHVybiBlfSxfbm9pbmtDaGFuZ2VkOmZ1bmN0aW9uKGUpe3RoaXMuaGFzUmlwcGxlKCkmJih0aGlzLl9yaXBwbGUubm9pbms9ZSl9fTt2YXIgaFc9e3Byb3BlcnRpZXM6e2VsZXZhdGlvbjp7dHlwZTpOdW1iZXIscmVmbGVjdFRvQXR0cmlidXRlOiEwLHJlYWRPbmx5OiEwfX0sb2JzZXJ2ZXJzOlsiX2NhbGN1bGF0ZUVsZXZhdGlvbihmb2N1c2VkLCBkaXNhYmxlZCwgYWN0aXZlLCBwcmVzc2VkLCByZWNlaXZlZEZvY3VzRnJvbUtleWJvYXJkKSIsIl9jb21wdXRlS2V5Ym9hcmRDbGFzcyhyZWNlaXZlZEZvY3VzRnJvbUtleWJvYXJkKSJdLGhvc3RBdHRyaWJ1dGVzOntyb2xlOiJidXR0b24iLHRhYmluZGV4OiIwIixhbmltYXRlZDohMH0sX2NhbGN1bGF0ZUVsZXZhdGlvbjpmdW5jdGlvbigpe3ZhciBlPTE7dGhpcy5kaXNhYmxlZD9lPTA6dGhpcy5hY3RpdmV8fHRoaXMucHJlc3NlZD9lPTQ6dGhpcy5yZWNlaXZlZEZvY3VzRnJvbUtleWJvYXJkJiYoZT0zKSx0aGlzLl9zZXRFbGV2YXRpb24oZSl9LF9jb21wdXRlS2V5Ym9hcmRDbGFzczpmdW5jdGlvbihlKXt0aGlzLnRvZ2dsZUNsYXNzKCJrZXlib2FyZC1mb2N1cyIsZSl9LF9zcGFjZUtleURvd25IYW5kbGVyOmZ1bmN0aW9uKGUpe1l4Ll9zcGFjZUtleURvd25IYW5kbGVyLmNhbGwodGhpcyxlKSx0aGlzLmhhc1JpcHBsZSgpJiZ0aGlzLmdldFJpcHBsZSgpLnJpcHBsZXMubGVuZ3RoPDEmJnRoaXMuX3JpcHBsZS51aURvd25BY3Rpb24oKX0sX3NwYWNlS2V5VXBIYW5kbGVyOmZ1bmN0aW9uKGUpe1l4Ll9zcGFjZUtleVVwSGFuZGxlci5jYWxsKHRoaXMsZSksdGhpcy5oYXNSaXBwbGUoKSYmdGhpcy5fcmlwcGxlLnVpVXBBY3Rpb24oKX19LGEwdD1bU2gsRGksc3UsaFddO3ZhciBzMHQ9UWAKICA8c3R5bGUgaW5jbHVkZT0icGFwZXItbWF0ZXJpYWwtc3R5bGVzIj4KICAgIC8qIE5lZWQgdG8gc3BlY2lmeSB0aGUgc2FtZSBzcGVjaWZpY2l0eSBhcyB0aGUgc3R5bGVzIGltcG9ydGVkIGZyb20gcGFwZXItbWF0ZXJpYWwuICovCiAgICA6aG9zdCB7CiAgICAgIEBhcHBseSAtLWxheW91dC1pbmxpbmU7CiAgICAgIEBhcHBseSAtLWxheW91dC1jZW50ZXItY2VudGVyOwogICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgIG1pbi13aWR0aDogNS4xNGVtOwogICAgICBtYXJnaW46IDAgMC4yOWVtOwogICAgICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDsKICAgICAgLXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yOiByZ2JhKDAsIDAsIDAsIDApOwogICAgICAtd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6IHRyYW5zcGFyZW50OwogICAgICBmb250OiBpbmhlcml0OwogICAgICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlOwogICAgICBvdXRsaW5lLXdpZHRoOiAwOwogICAgICBib3JkZXItcmFkaXVzOiAzcHg7CiAgICAgIC1tb3otdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgIC1tcy11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgICAgei1pbmRleDogMDsKICAgICAgcGFkZGluZzogMC43ZW0gMC41N2VtOwoKICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1jb21tb24tYmFzZTsKICAgICAgQGFwcGx5IC0tcGFwZXItYnV0dG9uOwogICAgfQoKICAgIDpob3N0KFtlbGV2YXRpb249IjEiXSkgewogICAgICBAYXBwbHkgLS1wYXBlci1tYXRlcmlhbC1lbGV2YXRpb24tMTsKICAgIH0KCiAgICA6aG9zdChbZWxldmF0aW9uPSIyIl0pIHsKICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTI7CiAgICB9CgogICAgOmhvc3QoW2VsZXZhdGlvbj0iMyJdKSB7CiAgICAgIEBhcHBseSAtLXBhcGVyLW1hdGVyaWFsLWVsZXZhdGlvbi0zOwogICAgfQoKICAgIDpob3N0KFtlbGV2YXRpb249IjQiXSkgewogICAgICBAYXBwbHkgLS1wYXBlci1tYXRlcmlhbC1lbGV2YXRpb24tNDsKICAgIH0KCiAgICA6aG9zdChbZWxldmF0aW9uPSI1Il0pIHsKICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWwtZWxldmF0aW9uLTU7CiAgICB9CgogICAgOmhvc3QoW2hpZGRlbl0pIHsKICAgICAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OwogICAgfQoKICAgIDpob3N0KFtyYWlzZWRdLmtleWJvYXJkLWZvY3VzKSB7CiAgICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgICBAYXBwbHkgLS1wYXBlci1idXR0b24tcmFpc2VkLWtleWJvYXJkLWZvY3VzOwogICAgfQoKICAgIDpob3N0KDpub3QoW3JhaXNlZF0pLmtleWJvYXJkLWZvY3VzKSB7CiAgICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgICBAYXBwbHkgLS1wYXBlci1idXR0b24tZmxhdC1rZXlib2FyZC1mb2N1czsKICAgIH0KCiAgICA6aG9zdChbZGlzYWJsZWRdKSB7CiAgICAgIGJhY2tncm91bmQ6IG5vbmU7CiAgICAgIGNvbG9yOiAjYThhOGE4OwogICAgICBjdXJzb3I6IGF1dG87CiAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwoKICAgICAgQGFwcGx5IC0tcGFwZXItYnV0dG9uLWRpc2FibGVkOwogICAgfQoKICAgIDpob3N0KFtkaXNhYmxlZF1bcmFpc2VkXSkgewogICAgICBiYWNrZ3JvdW5kOiAjZWFlYWVhOwogICAgfQoKCiAgICA6aG9zdChbYW5pbWF0ZWRdKSB7CiAgICAgIEBhcHBseSAtLXNoYWRvdy10cmFuc2l0aW9uOwogICAgfQoKICAgIHBhcGVyLXJpcHBsZSB7CiAgICAgIGNvbG9yOiB2YXIoLS1wYXBlci1idXR0b24taW5rLWNvbG9yKTsKICAgIH0KICA8L3N0eWxlPgoKICA8c2xvdD48L3Nsb3Q+YDtzMHQuc2V0QXR0cmlidXRlKCJzdHJpcC13aGl0ZXNwYWNlIiwiIik7WXQoe190ZW1wbGF0ZTpzMHQsaXM6InBhcGVyLWJ1dHRvbiIsYmVoYXZpb3JzOlthMHRdLHByb3BlcnRpZXM6e3JhaXNlZDp7dHlwZTpCb29sZWFuLHJlZmxlY3RUb0F0dHJpYnV0ZTohMCx2YWx1ZTohMSxvYnNlcnZlcjoiX2NhbGN1bGF0ZUVsZXZhdGlvbiJ9fSxfY2FsY3VsYXRlRWxldmF0aW9uOmZ1bmN0aW9uKCl7dGhpcy5yYWlzZWQ/aFcuX2NhbGN1bGF0ZUVsZXZhdGlvbi5hcHBseSh0aGlzKTp0aGlzLl9zZXRFbGV2YXRpb24oMCl9fSk7dmFyIGwwdD1RYAo8Y3VzdG9tLXN0eWxlPgogIDxzdHlsZSBpcz0iY3VzdG9tLXN0eWxlIj4KICAgIGh0bWwgewoKICAgICAgLyogTWF0ZXJpYWwgRGVzaWduIGNvbG9yIHBhbGV0dGUgZm9yIEdvb2dsZSBwcm9kdWN0cyAqLwoKICAgICAgLS1nb29nbGUtcmVkLTEwMDogI2Y0YzdjMzsKICAgICAgLS1nb29nbGUtcmVkLTMwMDogI2U2N2M3MzsKICAgICAgLS1nb29nbGUtcmVkLTUwMDogI2RiNDQzNzsKICAgICAgLS1nb29nbGUtcmVkLTcwMDogI2M1MzkyOTsKCiAgICAgIC0tZ29vZ2xlLWJsdWUtMTAwOiAjYzZkYWZjOwogICAgICAtLWdvb2dsZS1ibHVlLTMwMDogIzdiYWFmNzsKICAgICAgLS1nb29nbGUtYmx1ZS01MDA6ICM0Mjg1ZjQ7CiAgICAgIC0tZ29vZ2xlLWJsdWUtNzAwOiAjMzM2N2Q2OwoKICAgICAgLS1nb29nbGUtZ3JlZW4tMTAwOiAjYjdlMWNkOwogICAgICAtLWdvb2dsZS1ncmVlbi0zMDA6ICM1N2JiOGE7CiAgICAgIC0tZ29vZ2xlLWdyZWVuLTUwMDogIzBmOWQ1ODsKICAgICAgLS1nb29nbGUtZ3JlZW4tNzAwOiAjMGI4MDQzOwoKICAgICAgLS1nb29nbGUteWVsbG93LTEwMDogI2ZjZThiMjsKICAgICAgLS1nb29nbGUteWVsbG93LTMwMDogI2Y3Y2I0ZDsKICAgICAgLS1nb29nbGUteWVsbG93LTUwMDogI2Y0YjQwMDsKICAgICAgLS1nb29nbGUteWVsbG93LTcwMDogI2YwOTMwMDsKCiAgICAgIC0tZ29vZ2xlLWdyZXktMTAwOiAjZjVmNWY1OwogICAgICAtLWdvb2dsZS1ncmV5LTMwMDogI2UwZTBlMDsKICAgICAgLS1nb29nbGUtZ3JleS01MDA6ICM5ZTllOWU7CiAgICAgIC0tZ29vZ2xlLWdyZXktNzAwOiAjNjE2MTYxOwoKICAgICAgLyogTWF0ZXJpYWwgRGVzaWduIGNvbG9yIHBhbGV0dGUgZnJvbSBvbmxpbmUgc3BlYyBkb2N1bWVudCAqLwoKICAgICAgLS1wYXBlci1yZWQtNTA6ICNmZmViZWU7CiAgICAgIC0tcGFwZXItcmVkLTEwMDogI2ZmY2RkMjsKICAgICAgLS1wYXBlci1yZWQtMjAwOiAjZWY5YTlhOwogICAgICAtLXBhcGVyLXJlZC0zMDA6ICNlNTczNzM7CiAgICAgIC0tcGFwZXItcmVkLTQwMDogI2VmNTM1MDsKICAgICAgLS1wYXBlci1yZWQtNTAwOiAjZjQ0MzM2OwogICAgICAtLXBhcGVyLXJlZC02MDA6ICNlNTM5MzU7CiAgICAgIC0tcGFwZXItcmVkLTcwMDogI2QzMmYyZjsKICAgICAgLS1wYXBlci1yZWQtODAwOiAjYzYyODI4OwogICAgICAtLXBhcGVyLXJlZC05MDA6ICNiNzFjMWM7CiAgICAgIC0tcGFwZXItcmVkLWExMDA6ICNmZjhhODA7CiAgICAgIC0tcGFwZXItcmVkLWEyMDA6ICNmZjUyNTI7CiAgICAgIC0tcGFwZXItcmVkLWE0MDA6ICNmZjE3NDQ7CiAgICAgIC0tcGFwZXItcmVkLWE3MDA6ICNkNTAwMDA7CgogICAgICAtLXBhcGVyLXBpbmstNTA6ICNmY2U0ZWM7CiAgICAgIC0tcGFwZXItcGluay0xMDA6ICNmOGJiZDA7CiAgICAgIC0tcGFwZXItcGluay0yMDA6ICNmNDhmYjE7CiAgICAgIC0tcGFwZXItcGluay0zMDA6ICNmMDYyOTI7CiAgICAgIC0tcGFwZXItcGluay00MDA6ICNlYzQwN2E7CiAgICAgIC0tcGFwZXItcGluay01MDA6ICNlOTFlNjM7CiAgICAgIC0tcGFwZXItcGluay02MDA6ICNkODFiNjA7CiAgICAgIC0tcGFwZXItcGluay03MDA6ICNjMjE4NWI7CiAgICAgIC0tcGFwZXItcGluay04MDA6ICNhZDE0NTc7CiAgICAgIC0tcGFwZXItcGluay05MDA6ICM4ODBlNGY7CiAgICAgIC0tcGFwZXItcGluay1hMTAwOiAjZmY4MGFiOwogICAgICAtLXBhcGVyLXBpbmstYTIwMDogI2ZmNDA4MTsKICAgICAgLS1wYXBlci1waW5rLWE0MDA6ICNmNTAwNTc7CiAgICAgIC0tcGFwZXItcGluay1hNzAwOiAjYzUxMTYyOwoKICAgICAgLS1wYXBlci1wdXJwbGUtNTA6ICNmM2U1ZjU7CiAgICAgIC0tcGFwZXItcHVycGxlLTEwMDogI2UxYmVlNzsKICAgICAgLS1wYXBlci1wdXJwbGUtMjAwOiAjY2U5M2Q4OwogICAgICAtLXBhcGVyLXB1cnBsZS0zMDA6ICNiYTY4Yzg7CiAgICAgIC0tcGFwZXItcHVycGxlLTQwMDogI2FiNDdiYzsKICAgICAgLS1wYXBlci1wdXJwbGUtNTAwOiAjOWMyN2IwOwogICAgICAtLXBhcGVyLXB1cnBsZS02MDA6ICM4ZTI0YWE7CiAgICAgIC0tcGFwZXItcHVycGxlLTcwMDogIzdiMWZhMjsKICAgICAgLS1wYXBlci1wdXJwbGUtODAwOiAjNmExYjlhOwogICAgICAtLXBhcGVyLXB1cnBsZS05MDA6ICM0YTE0OGM7CiAgICAgIC0tcGFwZXItcHVycGxlLWExMDA6ICNlYTgwZmM7CiAgICAgIC0tcGFwZXItcHVycGxlLWEyMDA6ICNlMDQwZmI7CiAgICAgIC0tcGFwZXItcHVycGxlLWE0MDA6ICNkNTAwZjk7CiAgICAgIC0tcGFwZXItcHVycGxlLWE3MDA6ICNhYTAwZmY7CgogICAgICAtLXBhcGVyLWRlZXAtcHVycGxlLTUwOiAjZWRlN2Y2OwogICAgICAtLXBhcGVyLWRlZXAtcHVycGxlLTEwMDogI2QxYzRlOTsKICAgICAgLS1wYXBlci1kZWVwLXB1cnBsZS0yMDA6ICNiMzlkZGI7CiAgICAgIC0tcGFwZXItZGVlcC1wdXJwbGUtMzAwOiAjOTU3NWNkOwogICAgICAtLXBhcGVyLWRlZXAtcHVycGxlLTQwMDogIzdlNTdjMjsKICAgICAgLS1wYXBlci1kZWVwLXB1cnBsZS01MDA6ICM2NzNhYjc7CiAgICAgIC0tcGFwZXItZGVlcC1wdXJwbGUtNjAwOiAjNWUzNWIxOwogICAgICAtLXBhcGVyLWRlZXAtcHVycGxlLTcwMDogIzUxMmRhODsKICAgICAgLS1wYXBlci1kZWVwLXB1cnBsZS04MDA6ICM0NTI3YTA7CiAgICAgIC0tcGFwZXItZGVlcC1wdXJwbGUtOTAwOiAjMzExYjkyOwogICAgICAtLXBhcGVyLWRlZXAtcHVycGxlLWExMDA6ICNiMzg4ZmY7CiAgICAgIC0tcGFwZXItZGVlcC1wdXJwbGUtYTIwMDogIzdjNGRmZjsKICAgICAgLS1wYXBlci1kZWVwLXB1cnBsZS1hNDAwOiAjNjUxZmZmOwogICAgICAtLXBhcGVyLWRlZXAtcHVycGxlLWE3MDA6ICM2MjAwZWE7CgogICAgICAtLXBhcGVyLWluZGlnby01MDogI2U4ZWFmNjsKICAgICAgLS1wYXBlci1pbmRpZ28tMTAwOiAjYzVjYWU5OwogICAgICAtLXBhcGVyLWluZGlnby0yMDA6ICM5ZmE4ZGE7CiAgICAgIC0tcGFwZXItaW5kaWdvLTMwMDogIzc5ODZjYjsKICAgICAgLS1wYXBlci1pbmRpZ28tNDAwOiAjNWM2YmMwOwogICAgICAtLXBhcGVyLWluZGlnby01MDA6ICMzZjUxYjU7CiAgICAgIC0tcGFwZXItaW5kaWdvLTYwMDogIzM5NDlhYjsKICAgICAgLS1wYXBlci1pbmRpZ28tNzAwOiAjMzAzZjlmOwogICAgICAtLXBhcGVyLWluZGlnby04MDA6ICMyODM1OTM7CiAgICAgIC0tcGFwZXItaW5kaWdvLTkwMDogIzFhMjM3ZTsKICAgICAgLS1wYXBlci1pbmRpZ28tYTEwMDogIzhjOWVmZjsKICAgICAgLS1wYXBlci1pbmRpZ28tYTIwMDogIzUzNmRmZTsKICAgICAgLS1wYXBlci1pbmRpZ28tYTQwMDogIzNkNWFmZTsKICAgICAgLS1wYXBlci1pbmRpZ28tYTcwMDogIzMwNGZmZTsKCiAgICAgIC0tcGFwZXItYmx1ZS01MDogI2UzZjJmZDsKICAgICAgLS1wYXBlci1ibHVlLTEwMDogI2JiZGVmYjsKICAgICAgLS1wYXBlci1ibHVlLTIwMDogIzkwY2FmOTsKICAgICAgLS1wYXBlci1ibHVlLTMwMDogIzY0YjVmNjsKICAgICAgLS1wYXBlci1ibHVlLTQwMDogIzQyYTVmNTsKICAgICAgLS1wYXBlci1ibHVlLTUwMDogIzIxOTZmMzsKICAgICAgLS1wYXBlci1ibHVlLTYwMDogIzFlODhlNTsKICAgICAgLS1wYXBlci1ibHVlLTcwMDogIzE5NzZkMjsKICAgICAgLS1wYXBlci1ibHVlLTgwMDogIzE1NjVjMDsKICAgICAgLS1wYXBlci1ibHVlLTkwMDogIzBkNDdhMTsKICAgICAgLS1wYXBlci1ibHVlLWExMDA6ICM4MmIxZmY7CiAgICAgIC0tcGFwZXItYmx1ZS1hMjAwOiAjNDQ4YWZmOwogICAgICAtLXBhcGVyLWJsdWUtYTQwMDogIzI5NzlmZjsKICAgICAgLS1wYXBlci1ibHVlLWE3MDA6ICMyOTYyZmY7CgogICAgICAtLXBhcGVyLWxpZ2h0LWJsdWUtNTA6ICNlMWY1ZmU7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS0xMDA6ICNiM2U1ZmM7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS0yMDA6ICM4MWQ0ZmE7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS0zMDA6ICM0ZmMzZjc7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS00MDA6ICMyOWI2ZjY7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS01MDA6ICMwM2E5ZjQ7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS02MDA6ICMwMzliZTU7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS03MDA6ICMwMjg4ZDE7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS04MDA6ICMwMjc3YmQ7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS05MDA6ICMwMTU3OWI7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS1hMTAwOiAjODBkOGZmOwogICAgICAtLXBhcGVyLWxpZ2h0LWJsdWUtYTIwMDogIzQwYzRmZjsKICAgICAgLS1wYXBlci1saWdodC1ibHVlLWE0MDA6ICMwMGIwZmY7CiAgICAgIC0tcGFwZXItbGlnaHQtYmx1ZS1hNzAwOiAjMDA5MWVhOwoKICAgICAgLS1wYXBlci1jeWFuLTUwOiAjZTBmN2ZhOwogICAgICAtLXBhcGVyLWN5YW4tMTAwOiAjYjJlYmYyOwogICAgICAtLXBhcGVyLWN5YW4tMjAwOiAjODBkZWVhOwogICAgICAtLXBhcGVyLWN5YW4tMzAwOiAjNGRkMGUxOwogICAgICAtLXBhcGVyLWN5YW4tNDAwOiAjMjZjNmRhOwogICAgICAtLXBhcGVyLWN5YW4tNTAwOiAjMDBiY2Q0OwogICAgICAtLXBhcGVyLWN5YW4tNjAwOiAjMDBhY2MxOwogICAgICAtLXBhcGVyLWN5YW4tNzAwOiAjMDA5N2E3OwogICAgICAtLXBhcGVyLWN5YW4tODAwOiAjMDA4MzhmOwogICAgICAtLXBhcGVyLWN5YW4tOTAwOiAjMDA2MDY0OwogICAgICAtLXBhcGVyLWN5YW4tYTEwMDogIzg0ZmZmZjsKICAgICAgLS1wYXBlci1jeWFuLWEyMDA6ICMxOGZmZmY7CiAgICAgIC0tcGFwZXItY3lhbi1hNDAwOiAjMDBlNWZmOwogICAgICAtLXBhcGVyLWN5YW4tYTcwMDogIzAwYjhkNDsKCiAgICAgIC0tcGFwZXItdGVhbC01MDogI2UwZjJmMTsKICAgICAgLS1wYXBlci10ZWFsLTEwMDogI2IyZGZkYjsKICAgICAgLS1wYXBlci10ZWFsLTIwMDogIzgwY2JjNDsKICAgICAgLS1wYXBlci10ZWFsLTMwMDogIzRkYjZhYzsKICAgICAgLS1wYXBlci10ZWFsLTQwMDogIzI2YTY5YTsKICAgICAgLS1wYXBlci10ZWFsLTUwMDogIzAwOTY4ODsKICAgICAgLS1wYXBlci10ZWFsLTYwMDogIzAwODk3YjsKICAgICAgLS1wYXBlci10ZWFsLTcwMDogIzAwNzk2YjsKICAgICAgLS1wYXBlci10ZWFsLTgwMDogIzAwNjk1YzsKICAgICAgLS1wYXBlci10ZWFsLTkwMDogIzAwNGQ0MDsKICAgICAgLS1wYXBlci10ZWFsLWExMDA6ICNhN2ZmZWI7CiAgICAgIC0tcGFwZXItdGVhbC1hMjAwOiAjNjRmZmRhOwogICAgICAtLXBhcGVyLXRlYWwtYTQwMDogIzFkZTliNjsKICAgICAgLS1wYXBlci10ZWFsLWE3MDA6ICMwMGJmYTU7CgogICAgICAtLXBhcGVyLWdyZWVuLTUwOiAjZThmNWU5OwogICAgICAtLXBhcGVyLWdyZWVuLTEwMDogI2M4ZTZjOTsKICAgICAgLS1wYXBlci1ncmVlbi0yMDA6ICNhNWQ2YTc7CiAgICAgIC0tcGFwZXItZ3JlZW4tMzAwOiAjODFjNzg0OwogICAgICAtLXBhcGVyLWdyZWVuLTQwMDogIzY2YmI2YTsKICAgICAgLS1wYXBlci1ncmVlbi01MDA6ICM0Y2FmNTA7CiAgICAgIC0tcGFwZXItZ3JlZW4tNjAwOiAjNDNhMDQ3OwogICAgICAtLXBhcGVyLWdyZWVuLTcwMDogIzM4OGUzYzsKICAgICAgLS1wYXBlci1ncmVlbi04MDA6ICMyZTdkMzI7CiAgICAgIC0tcGFwZXItZ3JlZW4tOTAwOiAjMWI1ZTIwOwogICAgICAtLXBhcGVyLWdyZWVuLWExMDA6ICNiOWY2Y2E7CiAgICAgIC0tcGFwZXItZ3JlZW4tYTIwMDogIzY5ZjBhZTsKICAgICAgLS1wYXBlci1ncmVlbi1hNDAwOiAjMDBlNjc2OwogICAgICAtLXBhcGVyLWdyZWVuLWE3MDA6ICMwMGM4NTM7CgogICAgICAtLXBhcGVyLWxpZ2h0LWdyZWVuLTUwOiAjZjFmOGU5OwogICAgICAtLXBhcGVyLWxpZ2h0LWdyZWVuLTEwMDogI2RjZWRjODsKICAgICAgLS1wYXBlci1saWdodC1ncmVlbi0yMDA6ICNjNWUxYTU7CiAgICAgIC0tcGFwZXItbGlnaHQtZ3JlZW4tMzAwOiAjYWVkNTgxOwogICAgICAtLXBhcGVyLWxpZ2h0LWdyZWVuLTQwMDogIzljY2M2NTsKICAgICAgLS1wYXBlci1saWdodC1ncmVlbi01MDA6ICM4YmMzNGE7CiAgICAgIC0tcGFwZXItbGlnaHQtZ3JlZW4tNjAwOiAjN2NiMzQyOwogICAgICAtLXBhcGVyLWxpZ2h0LWdyZWVuLTcwMDogIzY4OWYzODsKICAgICAgLS1wYXBlci1saWdodC1ncmVlbi04MDA6ICM1NThiMmY7CiAgICAgIC0tcGFwZXItbGlnaHQtZ3JlZW4tOTAwOiAjMzM2OTFlOwogICAgICAtLXBhcGVyLWxpZ2h0LWdyZWVuLWExMDA6ICNjY2ZmOTA7CiAgICAgIC0tcGFwZXItbGlnaHQtZ3JlZW4tYTIwMDogI2IyZmY1OTsKICAgICAgLS1wYXBlci1saWdodC1ncmVlbi1hNDAwOiAjNzZmZjAzOwogICAgICAtLXBhcGVyLWxpZ2h0LWdyZWVuLWE3MDA6ICM2NGRkMTc7CgogICAgICAtLXBhcGVyLWxpbWUtNTA6ICNmOWZiZTc7CiAgICAgIC0tcGFwZXItbGltZS0xMDA6ICNmMGY0YzM7CiAgICAgIC0tcGFwZXItbGltZS0yMDA6ICNlNmVlOWM7CiAgICAgIC0tcGFwZXItbGltZS0zMDA6ICNkY2U3NzU7CiAgICAgIC0tcGFwZXItbGltZS00MDA6ICNkNGUxNTc7CiAgICAgIC0tcGFwZXItbGltZS01MDA6ICNjZGRjMzk7CiAgICAgIC0tcGFwZXItbGltZS02MDA6ICNjMGNhMzM7CiAgICAgIC0tcGFwZXItbGltZS03MDA6ICNhZmI0MmI7CiAgICAgIC0tcGFwZXItbGltZS04MDA6ICM5ZTlkMjQ7CiAgICAgIC0tcGFwZXItbGltZS05MDA6ICM4Mjc3MTc7CiAgICAgIC0tcGFwZXItbGltZS1hMTAwOiAjZjRmZjgxOwogICAgICAtLXBhcGVyLWxpbWUtYTIwMDogI2VlZmY0MTsKICAgICAgLS1wYXBlci1saW1lLWE0MDA6ICNjNmZmMDA7CiAgICAgIC0tcGFwZXItbGltZS1hNzAwOiAjYWVlYTAwOwoKICAgICAgLS1wYXBlci15ZWxsb3ctNTA6ICNmZmZkZTc7CiAgICAgIC0tcGFwZXIteWVsbG93LTEwMDogI2ZmZjljNDsKICAgICAgLS1wYXBlci15ZWxsb3ctMjAwOiAjZmZmNTlkOwogICAgICAtLXBhcGVyLXllbGxvdy0zMDA6ICNmZmYxNzY7CiAgICAgIC0tcGFwZXIteWVsbG93LTQwMDogI2ZmZWU1ODsKICAgICAgLS1wYXBlci15ZWxsb3ctNTAwOiAjZmZlYjNiOwogICAgICAtLXBhcGVyLXllbGxvdy02MDA6ICNmZGQ4MzU7CiAgICAgIC0tcGFwZXIteWVsbG93LTcwMDogI2ZiYzAyZDsKICAgICAgLS1wYXBlci15ZWxsb3ctODAwOiAjZjlhODI1OwogICAgICAtLXBhcGVyLXllbGxvdy05MDA6ICNmNTdmMTc7CiAgICAgIC0tcGFwZXIteWVsbG93LWExMDA6ICNmZmZmOGQ7CiAgICAgIC0tcGFwZXIteWVsbG93LWEyMDA6ICNmZmZmMDA7CiAgICAgIC0tcGFwZXIteWVsbG93LWE0MDA6ICNmZmVhMDA7CiAgICAgIC0tcGFwZXIteWVsbG93LWE3MDA6ICNmZmQ2MDA7CgogICAgICAtLXBhcGVyLWFtYmVyLTUwOiAjZmZmOGUxOwogICAgICAtLXBhcGVyLWFtYmVyLTEwMDogI2ZmZWNiMzsKICAgICAgLS1wYXBlci1hbWJlci0yMDA6ICNmZmUwODI7CiAgICAgIC0tcGFwZXItYW1iZXItMzAwOiAjZmZkNTRmOwogICAgICAtLXBhcGVyLWFtYmVyLTQwMDogI2ZmY2EyODsKICAgICAgLS1wYXBlci1hbWJlci01MDA6ICNmZmMxMDc7CiAgICAgIC0tcGFwZXItYW1iZXItNjAwOiAjZmZiMzAwOwogICAgICAtLXBhcGVyLWFtYmVyLTcwMDogI2ZmYTAwMDsKICAgICAgLS1wYXBlci1hbWJlci04MDA6ICNmZjhmMDA7CiAgICAgIC0tcGFwZXItYW1iZXItOTAwOiAjZmY2ZjAwOwogICAgICAtLXBhcGVyLWFtYmVyLWExMDA6ICNmZmU1N2Y7CiAgICAgIC0tcGFwZXItYW1iZXItYTIwMDogI2ZmZDc0MDsKICAgICAgLS1wYXBlci1hbWJlci1hNDAwOiAjZmZjNDAwOwogICAgICAtLXBhcGVyLWFtYmVyLWE3MDA6ICNmZmFiMDA7CgogICAgICAtLXBhcGVyLW9yYW5nZS01MDogI2ZmZjNlMDsKICAgICAgLS1wYXBlci1vcmFuZ2UtMTAwOiAjZmZlMGIyOwogICAgICAtLXBhcGVyLW9yYW5nZS0yMDA6ICNmZmNjODA7CiAgICAgIC0tcGFwZXItb3JhbmdlLTMwMDogI2ZmYjc0ZDsKICAgICAgLS1wYXBlci1vcmFuZ2UtNDAwOiAjZmZhNzI2OwogICAgICAtLXBhcGVyLW9yYW5nZS01MDA6ICNmZjk4MDA7CiAgICAgIC0tcGFwZXItb3JhbmdlLTYwMDogI2ZiOGMwMDsKICAgICAgLS1wYXBlci1vcmFuZ2UtNzAwOiAjZjU3YzAwOwogICAgICAtLXBhcGVyLW9yYW5nZS04MDA6ICNlZjZjMDA7CiAgICAgIC0tcGFwZXItb3JhbmdlLTkwMDogI2U2NTEwMDsKICAgICAgLS1wYXBlci1vcmFuZ2UtYTEwMDogI2ZmZDE4MDsKICAgICAgLS1wYXBlci1vcmFuZ2UtYTIwMDogI2ZmYWI0MDsKICAgICAgLS1wYXBlci1vcmFuZ2UtYTQwMDogI2ZmOTEwMDsKICAgICAgLS1wYXBlci1vcmFuZ2UtYTcwMDogI2ZmNjUwMDsKCiAgICAgIC0tcGFwZXItZGVlcC1vcmFuZ2UtNTA6ICNmYmU5ZTc7CiAgICAgIC0tcGFwZXItZGVlcC1vcmFuZ2UtMTAwOiAjZmZjY2JjOwogICAgICAtLXBhcGVyLWRlZXAtb3JhbmdlLTIwMDogI2ZmYWI5MTsKICAgICAgLS1wYXBlci1kZWVwLW9yYW5nZS0zMDA6ICNmZjhhNjU7CiAgICAgIC0tcGFwZXItZGVlcC1vcmFuZ2UtNDAwOiAjZmY3MDQzOwogICAgICAtLXBhcGVyLWRlZXAtb3JhbmdlLTUwMDogI2ZmNTcyMjsKICAgICAgLS1wYXBlci1kZWVwLW9yYW5nZS02MDA6ICNmNDUxMWU7CiAgICAgIC0tcGFwZXItZGVlcC1vcmFuZ2UtNzAwOiAjZTY0YTE5OwogICAgICAtLXBhcGVyLWRlZXAtb3JhbmdlLTgwMDogI2Q4NDMxNTsKICAgICAgLS1wYXBlci1kZWVwLW9yYW5nZS05MDA6ICNiZjM2MGM7CiAgICAgIC0tcGFwZXItZGVlcC1vcmFuZ2UtYTEwMDogI2ZmOWU4MDsKICAgICAgLS1wYXBlci1kZWVwLW9yYW5nZS1hMjAwOiAjZmY2ZTQwOwogICAgICAtLXBhcGVyLWRlZXAtb3JhbmdlLWE0MDA6ICNmZjNkMDA7CiAgICAgIC0tcGFwZXItZGVlcC1vcmFuZ2UtYTcwMDogI2RkMmMwMDsKCiAgICAgIC0tcGFwZXItYnJvd24tNTA6ICNlZmViZTk7CiAgICAgIC0tcGFwZXItYnJvd24tMTAwOiAjZDdjY2M4OwogICAgICAtLXBhcGVyLWJyb3duLTIwMDogI2JjYWFhNDsKICAgICAgLS1wYXBlci1icm93bi0zMDA6ICNhMTg4N2Y7CiAgICAgIC0tcGFwZXItYnJvd24tNDAwOiAjOGQ2ZTYzOwogICAgICAtLXBhcGVyLWJyb3duLTUwMDogIzc5NTU0ODsKICAgICAgLS1wYXBlci1icm93bi02MDA6ICM2ZDRjNDE7CiAgICAgIC0tcGFwZXItYnJvd24tNzAwOiAjNWQ0MDM3OwogICAgICAtLXBhcGVyLWJyb3duLTgwMDogIzRlMzQyZTsKICAgICAgLS1wYXBlci1icm93bi05MDA6ICMzZTI3MjM7CgogICAgICAtLXBhcGVyLWdyZXktNTA6ICNmYWZhZmE7CiAgICAgIC0tcGFwZXItZ3JleS0xMDA6ICNmNWY1ZjU7CiAgICAgIC0tcGFwZXItZ3JleS0yMDA6ICNlZWVlZWU7CiAgICAgIC0tcGFwZXItZ3JleS0zMDA6ICNlMGUwZTA7CiAgICAgIC0tcGFwZXItZ3JleS00MDA6ICNiZGJkYmQ7CiAgICAgIC0tcGFwZXItZ3JleS01MDA6ICM5ZTllOWU7CiAgICAgIC0tcGFwZXItZ3JleS02MDA6ICM3NTc1NzU7CiAgICAgIC0tcGFwZXItZ3JleS03MDA6ICM2MTYxNjE7CiAgICAgIC0tcGFwZXItZ3JleS04MDA6ICM0MjQyNDI7CiAgICAgIC0tcGFwZXItZ3JleS05MDA6ICMyMTIxMjE7CgogICAgICAtLXBhcGVyLWJsdWUtZ3JleS01MDogI2VjZWZmMTsKICAgICAgLS1wYXBlci1ibHVlLWdyZXktMTAwOiAjY2ZkOGRjOwogICAgICAtLXBhcGVyLWJsdWUtZ3JleS0yMDA6ICNiMGJlYzU7CiAgICAgIC0tcGFwZXItYmx1ZS1ncmV5LTMwMDogIzkwYTRhZTsKICAgICAgLS1wYXBlci1ibHVlLWdyZXktNDAwOiAjNzg5MDljOwogICAgICAtLXBhcGVyLWJsdWUtZ3JleS01MDA6ICM2MDdkOGI7CiAgICAgIC0tcGFwZXItYmx1ZS1ncmV5LTYwMDogIzU0NmU3YTsKICAgICAgLS1wYXBlci1ibHVlLWdyZXktNzAwOiAjNDU1YTY0OwogICAgICAtLXBhcGVyLWJsdWUtZ3JleS04MDA6ICMzNzQ3NGY7CiAgICAgIC0tcGFwZXItYmx1ZS1ncmV5LTkwMDogIzI2MzIzODsKCiAgICAgIC8qIG9wYWNpdHkgZm9yIGRhcmsgdGV4dCBvbiBhIGxpZ2h0IGJhY2tncm91bmQgKi8KICAgICAgLS1kYXJrLWRpdmlkZXItb3BhY2l0eTogMC4xMjsKICAgICAgLS1kYXJrLWRpc2FibGVkLW9wYWNpdHk6IDAuMzg7IC8qIG9yIGhpbnQgdGV4dCBvciBpY29uICovCiAgICAgIC0tZGFyay1zZWNvbmRhcnktb3BhY2l0eTogMC41NDsKICAgICAgLS1kYXJrLXByaW1hcnktb3BhY2l0eTogMC44NzsKCiAgICAgIC8qIG9wYWNpdHkgZm9yIGxpZ2h0IHRleHQgb24gYSBkYXJrIGJhY2tncm91bmQgKi8KICAgICAgLS1saWdodC1kaXZpZGVyLW9wYWNpdHk6IDAuMTI7CiAgICAgIC0tbGlnaHQtZGlzYWJsZWQtb3BhY2l0eTogMC4zOyAvKiBvciBoaW50IHRleHQgb3IgaWNvbiAqLwogICAgICAtLWxpZ2h0LXNlY29uZGFyeS1vcGFjaXR5OiAwLjc7CiAgICAgIC0tbGlnaHQtcHJpbWFyeS1vcGFjaXR5OiAxLjA7CgogICAgfQoKICA8L3N0eWxlPgo8L2N1c3RvbS1zdHlsZT4KYDtsMHQuc2V0QXR0cmlidXRlKCJzdHlsZSIsImRpc3BsYXk6IG5vbmU7Iik7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChsMHQuY29udGVudCk7dmFyIGMwdD1RYAo8Y3VzdG9tLXN0eWxlPgogIDxzdHlsZSBpcz0iY3VzdG9tLXN0eWxlIj4KICAgIGh0bWwgewogICAgICAvKgogICAgICAgKiBZb3UgY2FuIHVzZSB0aGVzZSBnZW5lcmljIHZhcmlhYmxlcyBpbiB5b3VyIGVsZW1lbnRzIGZvciBlYXN5IHRoZW1pbmcuCiAgICAgICAqIEZvciBleGFtcGxlLCBpZiBhbGwgeW91ciBlbGVtZW50cyB1c2UgXGAtLXByaW1hcnktdGV4dC1jb2xvclxgIGFzIGl0cyBtYWluCiAgICAgICAqIGNvbG9yLCB0aGVuIHN3aXRjaGluZyBmcm9tIGEgbGlnaHQgdG8gYSBkYXJrIHRoZW1lIGlzIGp1c3QgYSBtYXR0ZXIgb2YKICAgICAgICogY2hhbmdpbmcgdGhlIHZhbHVlIG9mIFxgLS1wcmltYXJ5LXRleHQtY29sb3JcYCBpbiB5b3VyIGFwcGxpY2F0aW9uLgogICAgICAgKi8KICAgICAgLS1wcmltYXJ5LXRleHQtY29sb3I6IHZhcigtLWxpZ2h0LXRoZW1lLXRleHQtY29sb3IpOwogICAgICAtLXByaW1hcnktYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tbGlnaHQtdGhlbWUtYmFja2dyb3VuZC1jb2xvcik7CiAgICAgIC0tc2Vjb25kYXJ5LXRleHQtY29sb3I6IHZhcigtLWxpZ2h0LXRoZW1lLXNlY29uZGFyeS1jb2xvcik7CiAgICAgIC0tZGlzYWJsZWQtdGV4dC1jb2xvcjogdmFyKC0tbGlnaHQtdGhlbWUtZGlzYWJsZWQtY29sb3IpOwogICAgICAtLWRpdmlkZXItY29sb3I6IHZhcigtLWxpZ2h0LXRoZW1lLWRpdmlkZXItY29sb3IpOwogICAgICAtLWVycm9yLWNvbG9yOiB2YXIoLS1wYXBlci1kZWVwLW9yYW5nZS1hNzAwKTsKCiAgICAgIC8qCiAgICAgICAqIFByaW1hcnkgYW5kIGFjY2VudCBjb2xvcnMuIEFsc28gc2VlIGNvbG9yLmpzIGZvciBtb3JlIGNvbG9ycy4KICAgICAgICovCiAgICAgIC0tcHJpbWFyeS1jb2xvcjogdmFyKC0tcGFwZXItaW5kaWdvLTUwMCk7CiAgICAgIC0tbGlnaHQtcHJpbWFyeS1jb2xvcjogdmFyKC0tcGFwZXItaW5kaWdvLTEwMCk7CiAgICAgIC0tZGFyay1wcmltYXJ5LWNvbG9yOiB2YXIoLS1wYXBlci1pbmRpZ28tNzAwKTsKCiAgICAgIC0tYWNjZW50LWNvbG9yOiB2YXIoLS1wYXBlci1waW5rLWEyMDApOwogICAgICAtLWxpZ2h0LWFjY2VudC1jb2xvcjogdmFyKC0tcGFwZXItcGluay1hMTAwKTsKICAgICAgLS1kYXJrLWFjY2VudC1jb2xvcjogdmFyKC0tcGFwZXItcGluay1hNDAwKTsKCgogICAgICAvKgogICAgICAgKiBNYXRlcmlhbCBEZXNpZ24gTGlnaHQgYmFja2dyb3VuZCB0aGVtZQogICAgICAgKi8KICAgICAgLS1saWdodC10aGVtZS1iYWNrZ3JvdW5kLWNvbG9yOiAjZmZmZmZmOwogICAgICAtLWxpZ2h0LXRoZW1lLWJhc2UtY29sb3I6ICMwMDAwMDA7CiAgICAgIC0tbGlnaHQtdGhlbWUtdGV4dC1jb2xvcjogdmFyKC0tcGFwZXItZ3JleS05MDApOwogICAgICAtLWxpZ2h0LXRoZW1lLXNlY29uZGFyeS1jb2xvcjogIzczNzM3MzsgIC8qIGZvciBzZWNvbmRhcnkgdGV4dCBhbmQgaWNvbnMgKi8KICAgICAgLS1saWdodC10aGVtZS1kaXNhYmxlZC1jb2xvcjogIzliOWI5YjsgIC8qIGRpc2FibGVkL2hpbnQgdGV4dCAqLwogICAgICAtLWxpZ2h0LXRoZW1lLWRpdmlkZXItY29sb3I6ICNkYmRiZGI7CgogICAgICAvKgogICAgICAgKiBNYXRlcmlhbCBEZXNpZ24gRGFyayBiYWNrZ3JvdW5kIHRoZW1lCiAgICAgICAqLwogICAgICAtLWRhcmstdGhlbWUtYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItZ3JleS05MDApOwogICAgICAtLWRhcmstdGhlbWUtYmFzZS1jb2xvcjogI2ZmZmZmZjsKICAgICAgLS1kYXJrLXRoZW1lLXRleHQtY29sb3I6ICNmZmZmZmY7CiAgICAgIC0tZGFyay10aGVtZS1zZWNvbmRhcnktY29sb3I6ICNiY2JjYmM7ICAvKiBmb3Igc2Vjb25kYXJ5IHRleHQgYW5kIGljb25zICovCiAgICAgIC0tZGFyay10aGVtZS1kaXNhYmxlZC1jb2xvcjogIzY0NjQ2NDsgIC8qIGRpc2FibGVkL2hpbnQgdGV4dCAqLwogICAgICAtLWRhcmstdGhlbWUtZGl2aWRlci1jb2xvcjogIzNjM2MzYzsKCiAgICAgIC8qCiAgICAgICAqIERlcHJlY2F0ZWQgdmFsdWVzIGJlY2F1c2Ugb2YgdGhlaXIgY29uZnVzaW5nIG5hbWVzLgogICAgICAgKi8KICAgICAgLS10ZXh0LXByaW1hcnktY29sb3I6IHZhcigtLWRhcmstdGhlbWUtdGV4dC1jb2xvcik7CiAgICAgIC0tZGVmYXVsdC1wcmltYXJ5LWNvbG9yOiB2YXIoLS1wcmltYXJ5LWNvbG9yKTsKICAgIH0KICA8L3N0eWxlPgo8L2N1c3RvbS1zdHlsZT5gO2MwdC5zZXRBdHRyaWJ1dGUoInN0eWxlIiwiZGlzcGxheTogbm9uZTsiKTtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKGMwdC5jb250ZW50KTt2YXIgRWg9e3Byb3BlcnRpZXM6e25hbWU6e3R5cGU6U3RyaW5nfSx2YWx1ZTp7bm90aWZ5OiEwLHR5cGU6U3RyaW5nfSxyZXF1aXJlZDp7dHlwZTpCb29sZWFuLHZhbHVlOiExfX0sYXR0YWNoZWQ6ZnVuY3Rpb24oKXt9LGRldGFjaGVkOmZ1bmN0aW9uKCl7fX07dmFyIGZXPW51bGwsVGg9e3Byb3BlcnRpZXM6e3ZhbGlkYXRvcjp7dHlwZTpTdHJpbmd9LGludmFsaWQ6e25vdGlmeTohMCxyZWZsZWN0VG9BdHRyaWJ1dGU6ITAsdHlwZTpCb29sZWFuLHZhbHVlOiExLG9ic2VydmVyOiJfaW52YWxpZENoYW5nZWQifX0scmVnaXN0ZXJlZDpmdW5jdGlvbigpe2ZXPW5ldyBnbyh7dHlwZToidmFsaWRhdG9yIn0pfSxfaW52YWxpZENoYW5nZWQ6ZnVuY3Rpb24oKXt0aGlzLmludmFsaWQ/dGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtaW52YWxpZCIsInRydWUiKTp0aGlzLnJlbW92ZUF0dHJpYnV0ZSgiYXJpYS1pbnZhbGlkIil9LGdldCBfdmFsaWRhdG9yKCl7cmV0dXJuIGZXJiZmVy5ieUtleSh0aGlzLnZhbGlkYXRvcil9LGhhc1ZhbGlkYXRvcjpmdW5jdGlvbigpe3JldHVybiB0aGlzLl92YWxpZGF0b3IhPW51bGx9LHZhbGlkYXRlOmZ1bmN0aW9uKGUpe3JldHVybiBlPT09dm9pZCAwJiZ0aGlzLnZhbHVlIT09dm9pZCAwP3RoaXMuaW52YWxpZD0hdGhpcy5fZ2V0VmFsaWRpdHkodGhpcy52YWx1ZSk6dGhpcy5pbnZhbGlkPSF0aGlzLl9nZXRWYWxpZGl0eShlKSwhdGhpcy5pbnZhbGlkfSxfZ2V0VmFsaWRpdHk6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuaGFzVmFsaWRhdG9yKCk/dGhpcy5fdmFsaWRhdG9yLnZhbGlkYXRlKGUpOiEwfX07dmFyIHBXPXtwcm9wZXJ0aWVzOntjaGVja2VkOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITEscmVmbGVjdFRvQXR0cmlidXRlOiEwLG5vdGlmeTohMCxvYnNlcnZlcjoiX2NoZWNrZWRDaGFuZ2VkIn0sdG9nZ2xlczp7dHlwZTpCb29sZWFuLHZhbHVlOiEwLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0sdmFsdWU6e3R5cGU6U3RyaW5nLHZhbHVlOiJvbiIsb2JzZXJ2ZXI6Il92YWx1ZUNoYW5nZWQifX0sb2JzZXJ2ZXJzOlsiX3JlcXVpcmVkQ2hhbmdlZChyZXF1aXJlZCkiXSxjcmVhdGVkOmZ1bmN0aW9uKCl7dGhpcy5faGFzSXJvbkNoZWNrZWRFbGVtZW50QmVoYXZpb3I9ITB9LF9nZXRWYWxpZGl0eTpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5kaXNhYmxlZHx8IXRoaXMucmVxdWlyZWR8fHRoaXMuY2hlY2tlZH0sX3JlcXVpcmVkQ2hhbmdlZDpmdW5jdGlvbigpe3RoaXMucmVxdWlyZWQ/dGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtcmVxdWlyZWQiLCJ0cnVlIik6dGhpcy5yZW1vdmVBdHRyaWJ1dGUoImFyaWEtcmVxdWlyZWQiKX0sX2NoZWNrZWRDaGFuZ2VkOmZ1bmN0aW9uKCl7dGhpcy5hY3RpdmU9dGhpcy5jaGVja2VkLHRoaXMuZmlyZSgiaXJvbi1jaGFuZ2UiKX0sX3ZhbHVlQ2hhbmdlZDpmdW5jdGlvbigpeyh0aGlzLnZhbHVlPT09dm9pZCAwfHx0aGlzLnZhbHVlPT09bnVsbCkmJih0aGlzLnZhbHVlPSJvbiIpfX0sdTB0PVtFaCxUaCxwV107dmFyIGZFPXtvYnNlcnZlcnM6WyJfZm9jdXNlZENoYW5nZWQocmVjZWl2ZWRGb2N1c0Zyb21LZXlib2FyZCkiXSxfZm9jdXNlZENoYW5nZWQ6ZnVuY3Rpb24oZSl7ZSYmdGhpcy5lbnN1cmVSaXBwbGUoKSx0aGlzLmhhc1JpcHBsZSgpJiYodGhpcy5fcmlwcGxlLmhvbGREb3duPWUpfSxfY3JlYXRlUmlwcGxlOmZ1bmN0aW9uKCl7dmFyIGU9c3UuX2NyZWF0ZVJpcHBsZSgpO3JldHVybiBlLmlkPSJpbmsiLGUuc2V0QXR0cmlidXRlKCJjZW50ZXIiLCIiKSxlLmNsYXNzTGlzdC5hZGQoImNpcmNsZSIpLGV9fSxqeD1bU2gsRGksc3UsZkVdO3ZhciBUYmU9e19jaGVja2VkQ2hhbmdlZDpmdW5jdGlvbigpe3BXLl9jaGVja2VkQ2hhbmdlZC5jYWxsKHRoaXMpLHRoaXMuaGFzUmlwcGxlKCkmJih0aGlzLmNoZWNrZWQ/dGhpcy5fcmlwcGxlLnNldEF0dHJpYnV0ZSgiY2hlY2tlZCIsIiIpOnRoaXMuX3JpcHBsZS5yZW1vdmVBdHRyaWJ1dGUoImNoZWNrZWQiKSl9LF9idXR0b25TdGF0ZUNoYW5nZWQ6ZnVuY3Rpb24oKXtzdS5fYnV0dG9uU3RhdGVDaGFuZ2VkLmNhbGwodGhpcyksIXRoaXMuZGlzYWJsZWQmJnRoaXMuaXNBdHRhY2hlZCYmKHRoaXMuY2hlY2tlZD10aGlzLmFjdGl2ZSl9fSxYeD1bangsdTB0LFRiZV07dmFyIGgwdD1RYDxzdHlsZT4KICA6aG9zdCB7CiAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgY3Vyc29yOiBwb2ludGVyOwogICAgLS1jYWxjdWxhdGVkLXBhcGVyLWNoZWNrYm94LXNpemU6IHZhcigtLXBhcGVyLWNoZWNrYm94LXNpemUsIDE4cHgpOwogICAgLyogLTFweCBpcyBhIHNlbnRpbmVsIGZvciB0aGUgZGVmYXVsdCBhbmQgaXMgcmVwbGFjZWQgaW4gXGBhdHRhY2hlZFxgLiAqLwogICAgLS1jYWxjdWxhdGVkLXBhcGVyLWNoZWNrYm94LWluay1zaXplOiB2YXIoLS1wYXBlci1jaGVja2JveC1pbmstc2l6ZSwgLTFweCk7CiAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwogICAgbGluZS1oZWlnaHQ6IDA7CiAgICAtd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6IHRyYW5zcGFyZW50OwogIH0KCiAgOmhvc3QoW2hpZGRlbl0pIHsKICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICB9CgogIDpob3N0KDpmb2N1cykgewogICAgb3V0bGluZTogbm9uZTsKICB9CgogIC5oaWRkZW4gewogICAgZGlzcGxheTogbm9uZTsKICB9CgogICNjaGVja2JveENvbnRhaW5lciB7CiAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICB3aWR0aDogdmFyKC0tY2FsY3VsYXRlZC1wYXBlci1jaGVja2JveC1zaXplKTsKICAgIGhlaWdodDogdmFyKC0tY2FsY3VsYXRlZC1wYXBlci1jaGVja2JveC1zaXplKTsKICAgIG1pbi13aWR0aDogdmFyKC0tY2FsY3VsYXRlZC1wYXBlci1jaGVja2JveC1zaXplKTsKICAgIG1hcmdpbjogdmFyKC0tcGFwZXItY2hlY2tib3gtbWFyZ2luLCBpbml0aWFsKTsKICAgIHZlcnRpY2FsLWFsaWduOiB2YXIoLS1wYXBlci1jaGVja2JveC12ZXJ0aWNhbC1hbGlnbiwgbWlkZGxlKTsKICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLXBhcGVyLWNoZWNrYm94LXVuY2hlY2tlZC1iYWNrZ3JvdW5kLWNvbG9yLCB0cmFuc3BhcmVudCk7CiAgfQoKICAjaW5rIHsKICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKCiAgICAvKiBDZW50ZXIgdGhlIHJpcHBsZSBpbiB0aGUgY2hlY2tib3ggYnkgbmVnYXRpdmUgb2Zmc2V0dGluZyBpdCBieQogICAgICogKGlua1dpZHRoIC0gcmlwcGxlV2lkdGgpIC8gMiAqLwogICAgdG9wOiBjYWxjKDBweCAtICh2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLWNoZWNrYm94LWluay1zaXplKSAtIHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItY2hlY2tib3gtc2l6ZSkpIC8gMik7CiAgICBsZWZ0OiBjYWxjKDBweCAtICh2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLWNoZWNrYm94LWluay1zaXplKSAtIHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItY2hlY2tib3gtc2l6ZSkpIC8gMik7CiAgICB3aWR0aDogdmFyKC0tY2FsY3VsYXRlZC1wYXBlci1jaGVja2JveC1pbmstc2l6ZSk7CiAgICBoZWlnaHQ6IHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItY2hlY2tib3gtaW5rLXNpemUpOwogICAgY29sb3I6IHZhcigtLXBhcGVyLWNoZWNrYm94LXVuY2hlY2tlZC1pbmstY29sb3IsIHZhcigtLXByaW1hcnktdGV4dC1jb2xvcikpOwogICAgb3BhY2l0eTogMC42OwogICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgfQoKICAjaW5rOmRpcihydGwpIHsKICAgIHJpZ2h0OiBjYWxjKDBweCAtICh2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLWNoZWNrYm94LWluay1zaXplKSAtIHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItY2hlY2tib3gtc2l6ZSkpIC8gMik7CiAgICBsZWZ0OiBhdXRvOwogIH0KCiAgI2lua1tjaGVja2VkXSB7CiAgICBjb2xvcjogdmFyKC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1pbmstY29sb3IsIHZhcigtLXByaW1hcnktY29sb3IpKTsKICB9CgogICNjaGVja2JveCB7CiAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgaGVpZ2h0OiAxMDAlOwogICAgYm9yZGVyOiBzb2xpZCAycHg7CiAgICBib3JkZXItY29sb3I6IHZhcigtLXBhcGVyLWNoZWNrYm94LXVuY2hlY2tlZC1jb2xvciwgdmFyKC0tcHJpbWFyeS10ZXh0LWNvbG9yKSk7CiAgICBib3JkZXItcmFkaXVzOiAycHg7CiAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgIC13ZWJraXQtdHJhbnNpdGlvbjogYmFja2dyb3VuZC1jb2xvciAxNDBtcywgYm9yZGVyLWNvbG9yIDE0MG1zOwogICAgdHJhbnNpdGlvbjogYmFja2dyb3VuZC1jb2xvciAxNDBtcywgYm9yZGVyLWNvbG9yIDE0MG1zOwoKICAgIC13ZWJraXQtdHJhbnNpdGlvbi1kdXJhdGlvbjogdmFyKC0tcGFwZXItY2hlY2tib3gtYW5pbWF0aW9uLWR1cmF0aW9uLCAxNDBtcyk7CiAgICB0cmFuc2l0aW9uLWR1cmF0aW9uOiB2YXIoLS1wYXBlci1jaGVja2JveC1hbmltYXRpb24tZHVyYXRpb24sIDE0MG1zKTsKICB9CgogIC8qIGNoZWNrYm94IGNoZWNrZWQgYW5pbWF0aW9ucyAqLwogICNjaGVja2JveC5jaGVja2VkICNjaGVja21hcmsgewogICAgLXdlYmtpdC1hbmltYXRpb246IGNoZWNrbWFyay1leHBhbmQgMTQwbXMgZWFzZS1vdXQgZm9yd2FyZHM7CiAgICBhbmltYXRpb246IGNoZWNrbWFyay1leHBhbmQgMTQwbXMgZWFzZS1vdXQgZm9yd2FyZHM7CgogICAgLXdlYmtpdC1hbmltYXRpb24tZHVyYXRpb246IHZhcigtLXBhcGVyLWNoZWNrYm94LWFuaW1hdGlvbi1kdXJhdGlvbiwgMTQwbXMpOwogICAgYW5pbWF0aW9uLWR1cmF0aW9uOiB2YXIoLS1wYXBlci1jaGVja2JveC1hbmltYXRpb24tZHVyYXRpb24sIDE0MG1zKTsKICB9CgogIEAtd2Via2l0LWtleWZyYW1lcyBjaGVja21hcmstZXhwYW5kIHsKICAgIDAlIHsKICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlKDAsIDApIHJvdGF0ZSg0NWRlZyk7CiAgICB9CiAgICAxMDAlIHsKICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlKDEsIDEpIHJvdGF0ZSg0NWRlZyk7CiAgICB9CiAgfQoKICBAa2V5ZnJhbWVzIGNoZWNrbWFyay1leHBhbmQgewogICAgMCUgewogICAgICB0cmFuc2Zvcm06IHNjYWxlKDAsIDApIHJvdGF0ZSg0NWRlZyk7CiAgICB9CiAgICAxMDAlIHsKICAgICAgdHJhbnNmb3JtOiBzY2FsZSgxLCAxKSByb3RhdGUoNDVkZWcpOwogICAgfQogIH0KCiAgI2NoZWNrYm94LmNoZWNrZWQgewogICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1jb2xvciwgdmFyKC0tcHJpbWFyeS1jb2xvcikpOwogICAgYm9yZGVyLWNvbG9yOiB2YXIoLS1wYXBlci1jaGVja2JveC1jaGVja2VkLWNvbG9yLCB2YXIoLS1wcmltYXJ5LWNvbG9yKSk7CiAgfQoKICAjY2hlY2ttYXJrIHsKICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgIHdpZHRoOiAzNiU7CiAgICBoZWlnaHQ6IDcwJTsKICAgIGJvcmRlci1zdHlsZTogc29saWQ7CiAgICBib3JkZXItdG9wOiBub25lOwogICAgYm9yZGVyLWxlZnQ6IG5vbmU7CiAgICBib3JkZXItcmlnaHQtd2lkdGg6IGNhbGMoMi8xNSAqIHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItY2hlY2tib3gtc2l6ZSkpOwogICAgYm9yZGVyLWJvdHRvbS13aWR0aDogY2FsYygyLzE1ICogdmFyKC0tY2FsY3VsYXRlZC1wYXBlci1jaGVja2JveC1zaXplKSk7CiAgICBib3JkZXItY29sb3I6IHZhcigtLXBhcGVyLWNoZWNrYm94LWNoZWNrbWFyay1jb2xvciwgd2hpdGUpOwogICAgLXdlYmtpdC10cmFuc2Zvcm0tb3JpZ2luOiA5NyUgODYlOwogICAgdHJhbnNmb3JtLW9yaWdpbjogOTclIDg2JTsKICAgIGJveC1zaXppbmc6IGNvbnRlbnQtYm94OyAvKiBwcm90ZWN0IGFnYWluc3QgcGFnZS1sZXZlbCBib3gtc2l6aW5nICovCiAgfQoKICAjY2hlY2ttYXJrOmRpcihydGwpIHsKICAgIC13ZWJraXQtdHJhbnNmb3JtLW9yaWdpbjogNTAlIDE0JTsKICAgIHRyYW5zZm9ybS1vcmlnaW46IDUwJSAxNCU7CiAgfQoKICAvKiBsYWJlbCAqLwogICNjaGVja2JveExhYmVsIHsKICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7CiAgICBwYWRkaW5nLWxlZnQ6IHZhcigtLXBhcGVyLWNoZWNrYm94LWxhYmVsLXNwYWNpbmcsIDhweCk7CiAgICB3aGl0ZS1zcGFjZTogbm9ybWFsOwogICAgbGluZS1oZWlnaHQ6IG5vcm1hbDsKICAgIGNvbG9yOiB2YXIoLS1wYXBlci1jaGVja2JveC1sYWJlbC1jb2xvciwgdmFyKC0tcHJpbWFyeS10ZXh0LWNvbG9yKSk7CiAgICBAYXBwbHkgLS1wYXBlci1jaGVja2JveC1sYWJlbDsKICB9CgogIDpob3N0KFtjaGVja2VkXSkgI2NoZWNrYm94TGFiZWwgewogICAgY29sb3I6IHZhcigtLXBhcGVyLWNoZWNrYm94LWxhYmVsLWNoZWNrZWQtY29sb3IsIHZhcigtLXBhcGVyLWNoZWNrYm94LWxhYmVsLWNvbG9yLCB2YXIoLS1wcmltYXJ5LXRleHQtY29sb3IpKSk7CiAgICBAYXBwbHkgLS1wYXBlci1jaGVja2JveC1sYWJlbC1jaGVja2VkOwogIH0KCiAgI2NoZWNrYm94TGFiZWw6ZGlyKHJ0bCkgewogICAgcGFkZGluZy1yaWdodDogdmFyKC0tcGFwZXItY2hlY2tib3gtbGFiZWwtc3BhY2luZywgOHB4KTsKICAgIHBhZGRpbmctbGVmdDogMDsKICB9CgogICNjaGVja2JveExhYmVsW2hpZGRlbl0gewogICAgZGlzcGxheTogbm9uZTsKICB9CgogIC8qIGRpc2FibGVkIHN0YXRlICovCgogIDpob3N0KFtkaXNhYmxlZF0pICNjaGVja2JveCB7CiAgICBvcGFjaXR5OiAwLjU7CiAgICBib3JkZXItY29sb3I6IHZhcigtLXBhcGVyLWNoZWNrYm94LXVuY2hlY2tlZC1jb2xvciwgdmFyKC0tcHJpbWFyeS10ZXh0LWNvbG9yKSk7CiAgfQoKICA6aG9zdChbZGlzYWJsZWRdW2NoZWNrZWRdKSAjY2hlY2tib3ggewogICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWNvbG9yLCB2YXIoLS1wcmltYXJ5LXRleHQtY29sb3IpKTsKICAgIG9wYWNpdHk6IDAuNTsKICB9CgogIDpob3N0KFtkaXNhYmxlZF0pICNjaGVja2JveExhYmVsICB7CiAgICBvcGFjaXR5OiAwLjY1OwogIH0KCiAgLyogaW52YWxpZCBzdGF0ZSAqLwogICNjaGVja2JveC5pbnZhbGlkOm5vdCguY2hlY2tlZCkgewogICAgYm9yZGVyLWNvbG9yOiB2YXIoLS1wYXBlci1jaGVja2JveC1lcnJvci1jb2xvciwgdmFyKC0tZXJyb3ItY29sb3IpKTsKICB9Cjwvc3R5bGU+Cgo8ZGl2IGlkPSJjaGVja2JveENvbnRhaW5lciI+CiAgPGRpdiBpZD0iY2hlY2tib3giIGNsYXNzJD0iW1tfY29tcHV0ZUNoZWNrYm94Q2xhc3MoY2hlY2tlZCwgaW52YWxpZCldXSI+CiAgICA8ZGl2IGlkPSJjaGVja21hcmsiIGNsYXNzJD0iW1tfY29tcHV0ZUNoZWNrbWFya0NsYXNzKGNoZWNrZWQpXV0iPjwvZGl2PgogIDwvZGl2Pgo8L2Rpdj4KCjxkaXYgaWQ9ImNoZWNrYm94TGFiZWwiPjxzbG90Pjwvc2xvdD48L2Rpdj5gO2gwdC5zZXRBdHRyaWJ1dGUoInN0cmlwLXdoaXRlc3BhY2UiLCIiKTtZdCh7X3RlbXBsYXRlOmgwdCxpczoicGFwZXItY2hlY2tib3giLGJlaGF2aW9yczpbWHhdLGhvc3RBdHRyaWJ1dGVzOntyb2xlOiJjaGVja2JveCIsImFyaWEtY2hlY2tlZCI6ITEsdGFiaW5kZXg6MH0scHJvcGVydGllczp7YXJpYUFjdGl2ZUF0dHJpYnV0ZTp7dHlwZTpTdHJpbmcsdmFsdWU6ImFyaWEtY2hlY2tlZCJ9fSxhdHRhY2hlZDpmdW5jdGlvbigpe1RtKHRoaXMsZnVuY3Rpb24oKXt2YXIgZT10aGlzLmdldENvbXB1dGVkU3R5bGVWYWx1ZSgiLS1jYWxjdWxhdGVkLXBhcGVyLWNoZWNrYm94LWluay1zaXplIikudHJpbSgpO2lmKGU9PT0iLTFweCIpe3ZhciB0PXRoaXMuZ2V0Q29tcHV0ZWRTdHlsZVZhbHVlKCItLWNhbGN1bGF0ZWQtcGFwZXItY2hlY2tib3gtc2l6ZSIpLnRyaW0oKSxyPSJweCIsbj10Lm1hdGNoKC9bQS1aYS16XSskLyk7biE9PW51bGwmJihyPW5bMF0pO3ZhciBpPXBhcnNlRmxvYXQodCksbz04LzMqaTtyPT09InB4IiYmKG89TWF0aC5mbG9vcihvKSxvJTIhPT1pJTImJm8rKyksdGhpcy51cGRhdGVTdHlsZXMoeyItLXBhcGVyLWNoZWNrYm94LWluay1zaXplIjpvK3J9KX19KX0sX2NvbXB1dGVDaGVja2JveENsYXNzOmZ1bmN0aW9uKGUsdCl7dmFyIHI9IiI7cmV0dXJuIGUmJihyKz0iY2hlY2tlZCAiKSx0JiYocis9ImludmFsaWQiKSxyfSxfY29tcHV0ZUNoZWNrbWFya0NsYXNzOmZ1bmN0aW9uKGUpe3JldHVybiBlPyIiOiJoaWRkZW4ifSxfY3JlYXRlUmlwcGxlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3JpcHBsZUNvbnRhaW5lcj10aGlzLiQuY2hlY2tib3hDb250YWluZXIsZkUuX2NyZWF0ZVJpcHBsZS5jYWxsKHRoaXMpfX0pO2lmKCF3aW5kb3cucG9seW1lclNraXBMb2FkaW5nRm9udFJvYm90byl7bGV0IGU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgibGluayIpO2UucmVsPSJzdHlsZXNoZWV0IixlLnR5cGU9InRleHQvY3NzIixlLmNyb3NzT3JpZ2luPSJhbm9ueW1vdXMiLGUuaHJlZj0iaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVJvYm90bytNb25vOjQwMCw3MDB8Um9ib3RvOjQwMCwzMDAsMzAwaXRhbGljLDQwMGl0YWxpYyw1MDAsNTAwaXRhbGljLDcwMCw3MDBpdGFsaWMiLGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoZSl9dmFyIGYwdD1RYDxjdXN0b20tc3R5bGU+CiAgPHN0eWxlIGlzPSJjdXN0b20tc3R5bGUiPgogICAgaHRtbCB7CgogICAgICAvKiBTaGFyZWQgU3R5bGVzICovCiAgICAgIC0tcGFwZXItZm9udC1jb21tb24tYmFzZTogewogICAgICAgIGZvbnQtZmFtaWx5OiAnUm9ib3RvJywgJ05vdG8nLCBzYW5zLXNlcmlmOwogICAgICAgIC13ZWJraXQtZm9udC1zbW9vdGhpbmc6IGFudGlhbGlhc2VkOwogICAgICB9OwoKICAgICAgLS1wYXBlci1mb250LWNvbW1vbi1jb2RlOiB7CiAgICAgICAgZm9udC1mYW1pbHk6ICdSb2JvdG8gTW9ubycsICdDb25zb2xhcycsICdNZW5sbycsIG1vbm9zcGFjZTsKICAgICAgICAtd2Via2l0LWZvbnQtc21vb3RoaW5nOiBhbnRpYWxpYXNlZDsKICAgICAgfTsKCiAgICAgIC0tcGFwZXItZm9udC1jb21tb24tZXhwZW5zaXZlLWtlcm5pbmc6IHsKICAgICAgICB0ZXh0LXJlbmRlcmluZzogb3B0aW1pemVMZWdpYmlsaXR5OwogICAgICB9OwoKICAgICAgLS1wYXBlci1mb250LWNvbW1vbi1ub3dyYXA6IHsKICAgICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgICAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7CiAgICAgIH07CgogICAgICAvKiBNYXRlcmlhbCBGb250IFN0eWxlcyAqLwoKICAgICAgLS1wYXBlci1mb250LWRpc3BsYXk0OiB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1jb21tb24tYmFzZTsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1ub3dyYXA7CgogICAgICAgIGZvbnQtc2l6ZTogMTEycHg7CiAgICAgICAgZm9udC13ZWlnaHQ6IDMwMDsKICAgICAgICBsZXR0ZXItc3BhY2luZzogLS4wNDRlbTsKICAgICAgICBsaW5lLWhlaWdodDogMTIwcHg7CiAgICAgIH07CgogICAgICAtLXBhcGVyLWZvbnQtZGlzcGxheTM6IHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY29tbW9uLW5vd3JhcDsKCiAgICAgICAgZm9udC1zaXplOiA1NnB4OwogICAgICAgIGZvbnQtd2VpZ2h0OiA0MDA7CiAgICAgICAgbGV0dGVyLXNwYWNpbmc6IC0uMDI2ZW07CiAgICAgICAgbGluZS1oZWlnaHQ6IDYwcHg7CiAgICAgIH07CgogICAgICAtLXBhcGVyLWZvbnQtZGlzcGxheTI6IHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwoKICAgICAgICBmb250LXNpemU6IDQ1cHg7CiAgICAgICAgZm9udC13ZWlnaHQ6IDQwMDsKICAgICAgICBsZXR0ZXItc3BhY2luZzogLS4wMThlbTsKICAgICAgICBsaW5lLWhlaWdodDogNDhweDsKICAgICAgfTsKCiAgICAgIC0tcGFwZXItZm9udC1kaXNwbGF5MTogewogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY29tbW9uLWJhc2U7CgogICAgICAgIGZvbnQtc2l6ZTogMzRweDsKICAgICAgICBmb250LXdlaWdodDogNDAwOwogICAgICAgIGxldHRlci1zcGFjaW5nOiAtLjAxZW07CiAgICAgICAgbGluZS1oZWlnaHQ6IDQwcHg7CiAgICAgIH07CgogICAgICAtLXBhcGVyLWZvbnQtaGVhZGxpbmU6IHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwoKICAgICAgICBmb250LXNpemU6IDI0cHg7CiAgICAgICAgZm9udC13ZWlnaHQ6IDQwMDsKICAgICAgICBsZXR0ZXItc3BhY2luZzogLS4wMTJlbTsKICAgICAgICBsaW5lLWhlaWdodDogMzJweDsKICAgICAgfTsKCiAgICAgIC0tcGFwZXItZm9udC10aXRsZTogewogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY29tbW9uLWJhc2U7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1jb21tb24tbm93cmFwOwoKICAgICAgICBmb250LXNpemU6IDIwcHg7CiAgICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgICAgICBsaW5lLWhlaWdodDogMjhweDsKICAgICAgfTsKCiAgICAgIC0tcGFwZXItZm9udC1zdWJoZWFkOiB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1jb21tb24tYmFzZTsKCiAgICAgICAgZm9udC1zaXplOiAxNnB4OwogICAgICAgIGZvbnQtd2VpZ2h0OiA0MDA7CiAgICAgICAgbGluZS1oZWlnaHQ6IDI0cHg7CiAgICAgIH07CgogICAgICAtLXBhcGVyLWZvbnQtYm9keTI6IHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwoKICAgICAgICBmb250LXNpemU6IDE0cHg7CiAgICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgICAgICBsaW5lLWhlaWdodDogMjRweDsKICAgICAgfTsKCiAgICAgIC0tcGFwZXItZm9udC1ib2R5MTogewogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY29tbW9uLWJhc2U7CgogICAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgICBmb250LXdlaWdodDogNDAwOwogICAgICAgIGxpbmUtaGVpZ2h0OiAyMHB4OwogICAgICB9OwoKICAgICAgLS1wYXBlci1mb250LWNhcHRpb246IHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY29tbW9uLW5vd3JhcDsKCiAgICAgICAgZm9udC1zaXplOiAxMnB4OwogICAgICAgIGZvbnQtd2VpZ2h0OiA0MDA7CiAgICAgICAgbGV0dGVyLXNwYWNpbmc6IDAuMDExZW07CiAgICAgICAgbGluZS1oZWlnaHQ6IDIwcHg7CiAgICAgIH07CgogICAgICAtLXBhcGVyLWZvbnQtbWVudTogewogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY29tbW9uLWJhc2U7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1jb21tb24tbm93cmFwOwoKICAgICAgICBmb250LXNpemU6IDEzcHg7CiAgICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgICAgICBsaW5lLWhlaWdodDogMjRweDsKICAgICAgfTsKCiAgICAgIC0tcGFwZXItZm9udC1idXR0b246IHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY29tbW9uLW5vd3JhcDsKCiAgICAgICAgZm9udC1zaXplOiAxNHB4OwogICAgICAgIGZvbnQtd2VpZ2h0OiA1MDA7CiAgICAgICAgbGV0dGVyLXNwYWNpbmc6IDAuMDE4ZW07CiAgICAgICAgbGluZS1oZWlnaHQ6IDI0cHg7CiAgICAgICAgdGV4dC10cmFuc2Zvcm06IHVwcGVyY2FzZTsKICAgICAgfTsKCiAgICAgIC0tcGFwZXItZm9udC1jb2RlMjogewogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY29tbW9uLWNvZGU7CgogICAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgICBmb250LXdlaWdodDogNzAwOwogICAgICAgIGxpbmUtaGVpZ2h0OiAyMHB4OwogICAgICB9OwoKICAgICAgLS1wYXBlci1mb250LWNvZGUxOiB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1jb21tb24tY29kZTsKCiAgICAgICAgZm9udC1zaXplOiAxNHB4OwogICAgICAgIGZvbnQtd2VpZ2h0OiA1MDA7CiAgICAgICAgbGluZS1oZWlnaHQ6IDIwcHg7CiAgICAgIH07CgogICAgfQoKICA8L3N0eWxlPgo8L2N1c3RvbS1zdHlsZT5gO2YwdC5zZXRBdHRyaWJ1dGUoInN0eWxlIiwiZGlzcGxheTogbm9uZTsiKTtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKGYwdC5jb250ZW50KTt2YXIgZFc9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGVtcGxhdGUiKTtkVy5zZXRBdHRyaWJ1dGUoInN0eWxlIiwiZGlzcGxheTogbm9uZTsiKTtkVy5pbm5lckhUTUw9YDxkb20tbW9kdWxlIGlkPSJwYXBlci1kaWFsb2ctc2hhcmVkLXN0eWxlcyI+CiAgPHRlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgbWFyZ2luOiAyNHB4IDQwcHg7CgogICAgICAgIGJhY2tncm91bmQ6IHZhcigtLXBhcGVyLWRpYWxvZy1iYWNrZ3JvdW5kLWNvbG9yLCB2YXIoLS1wcmltYXJ5LWJhY2tncm91bmQtY29sb3IpKTsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItZGlhbG9nLWNvbG9yLCB2YXIoLS1wcmltYXJ5LXRleHQtY29sb3IpKTsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1ib2R5MTsKICAgICAgICBAYXBwbHkgLS1zaGFkb3ctZWxldmF0aW9uLTE2ZHA7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZGlhbG9nOwogICAgICB9CgogICAgICA6aG9zdCA+IDo6c2xvdHRlZCgqKSB7CiAgICAgICAgbWFyZ2luLXRvcDogMjBweDsKICAgICAgICBwYWRkaW5nOiAwIDI0cHg7CiAgICAgIH0KCiAgICAgIDpob3N0ID4gOjpzbG90dGVkKC5uby1wYWRkaW5nKSB7CiAgICAgICAgcGFkZGluZzogMDsKICAgICAgfQoKICAgICAgCiAgICAgIDpob3N0ID4gOjpzbG90dGVkKCo6Zmlyc3QtY2hpbGQpIHsKICAgICAgICBtYXJnaW4tdG9wOiAyNHB4OwogICAgICB9CgogICAgICA6aG9zdCA+IDo6c2xvdHRlZCgqOmxhc3QtY2hpbGQpIHsKICAgICAgICBtYXJnaW4tYm90dG9tOiAyNHB4OwogICAgICB9CgogICAgICAvKiBJbiAxLngsIHRoaXMgc2VsZWN0b3Igd2FzIFxgOmhvc3QgPiA6OmNvbnRlbnQgaDJcYC4gSW4gMi54IDxzbG90PiBhbGxvd3MKICAgICAgdG8gc2VsZWN0IGRpcmVjdCBjaGlsZHJlbiBvbmx5LCB3aGljaCBpbmNyZWFzZXMgdGhlIHdlaWdodCBvZiB0aGlzCiAgICAgIHNlbGVjdG9yLCBzbyB3ZSBoYXZlIHRvIHJlLWRlZmluZSBmaXJzdC1jaGlsZC9sYXN0LWNoaWxkIG1hcmdpbnMgYmVsb3cuICovCiAgICAgIDpob3N0ID4gOjpzbG90dGVkKGgyKSB7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIG1hcmdpbjogMDsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC10aXRsZTsKICAgICAgICBAYXBwbHkgLS1wYXBlci1kaWFsb2ctdGl0bGU7CiAgICAgIH0KCiAgICAgIC8qIEFwcGx5IG1peGluIGFnYWluLCBpbiBjYXNlIGl0IHNldHMgbWFyZ2luLXRvcC4gKi8KICAgICAgOmhvc3QgPiA6OnNsb3R0ZWQoaDI6Zmlyc3QtY2hpbGQpIHsKICAgICAgICBtYXJnaW4tdG9wOiAyNHB4OwogICAgICAgIEBhcHBseSAtLXBhcGVyLWRpYWxvZy10aXRsZTsKICAgICAgfQoKICAgICAgLyogQXBwbHkgbWl4aW4gYWdhaW4sIGluIGNhc2UgaXQgc2V0cyBtYXJnaW4tYm90dG9tLiAqLwogICAgICA6aG9zdCA+IDo6c2xvdHRlZChoMjpsYXN0LWNoaWxkKSB7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogMjRweDsKICAgICAgICBAYXBwbHkgLS1wYXBlci1kaWFsb2ctdGl0bGU7CiAgICAgIH0KCiAgICAgIDpob3N0ID4gOjpzbG90dGVkKC5wYXBlci1kaWFsb2ctYnV0dG9ucyksCiAgICAgIDpob3N0ID4gOjpzbG90dGVkKC5idXR0b25zKSB7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIHBhZGRpbmc6IDhweCA4cHggOHB4IDI0cHg7CiAgICAgICAgbWFyZ2luOiAwOwoKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItZGlhbG9nLWJ1dHRvbi1jb2xvciwgdmFyKC0tcHJpbWFyeS1jb2xvcikpOwoKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtaG9yaXpvbnRhbDsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtZW5kLWp1c3RpZmllZDsKICAgICAgfQogICAgPC9zdHlsZT4KICA8L3RlbXBsYXRlPgo8L2RvbS1tb2R1bGU+YDtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKGRXLmNvbnRlbnQpO3ZhciBwMHQ9e3Byb3BlcnRpZXM6e2FuaW1hdGlvbkNvbmZpZzp7dHlwZTpPYmplY3R9LGVudHJ5QW5pbWF0aW9uOntvYnNlcnZlcjoiX2VudHJ5QW5pbWF0aW9uQ2hhbmdlZCIsdHlwZTpTdHJpbmd9LGV4aXRBbmltYXRpb246e29ic2VydmVyOiJfZXhpdEFuaW1hdGlvbkNoYW5nZWQiLHR5cGU6U3RyaW5nfX0sX2VudHJ5QW5pbWF0aW9uQ2hhbmdlZDpmdW5jdGlvbigpe3RoaXMuYW5pbWF0aW9uQ29uZmlnPXRoaXMuYW5pbWF0aW9uQ29uZmlnfHx7fSx0aGlzLmFuaW1hdGlvbkNvbmZpZy5lbnRyeT1be25hbWU6dGhpcy5lbnRyeUFuaW1hdGlvbixub2RlOnRoaXN9XX0sX2V4aXRBbmltYXRpb25DaGFuZ2VkOmZ1bmN0aW9uKCl7dGhpcy5hbmltYXRpb25Db25maWc9dGhpcy5hbmltYXRpb25Db25maWd8fHt9LHRoaXMuYW5pbWF0aW9uQ29uZmlnLmV4aXQ9W3tuYW1lOnRoaXMuZXhpdEFuaW1hdGlvbixub2RlOnRoaXN9XX0sX2NvcHlQcm9wZXJ0aWVzOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByIGluIHQpZVtyXT10W3JdfSxfY2xvbmVDb25maWc6ZnVuY3Rpb24oZSl7dmFyIHQ9e2lzQ2xvbmU6ITB9O3JldHVybiB0aGlzLl9jb3B5UHJvcGVydGllcyh0LGUpLHR9LF9nZXRBbmltYXRpb25Db25maWdSZWN1cnNpdmU6ZnVuY3Rpb24oZSx0LHIpe2lmKCEhdGhpcy5hbmltYXRpb25Db25maWcpe2lmKHRoaXMuYW5pbWF0aW9uQ29uZmlnLnZhbHVlJiZ0eXBlb2YgdGhpcy5hbmltYXRpb25Db25maWcudmFsdWU9PSJmdW5jdGlvbiIpe3RoaXMuX3dhcm4odGhpcy5fbG9nZigicGxheUFuaW1hdGlvbiIsIlBsZWFzZSBwdXQgJ2FuaW1hdGlvbkNvbmZpZycgaW5zaWRlIG9mIHlvdXIgY29tcG9uZW50cyAncHJvcGVydGllcycgb2JqZWN0IGluc3RlYWQgb2Ygb3V0c2lkZSBvZiBpdC4iKSk7cmV0dXJufXZhciBuO2lmKGU/bj10aGlzLmFuaW1hdGlvbkNvbmZpZ1tlXTpuPXRoaXMuYW5pbWF0aW9uQ29uZmlnLEFycmF5LmlzQXJyYXkobil8fChuPVtuXSksbilmb3IodmFyIGksbz0wO2k9bltvXTtvKyspaWYoaS5hbmltYXRhYmxlKWkuYW5pbWF0YWJsZS5fZ2V0QW5pbWF0aW9uQ29uZmlnUmVjdXJzaXZlKGkudHlwZXx8ZSx0LHIpO2Vsc2UgaWYoaS5pZCl7dmFyIGE9dFtpLmlkXTthPyhhLmlzQ2xvbmV8fCh0W2kuaWRdPXRoaXMuX2Nsb25lQ29uZmlnKGEpLGE9dFtpLmlkXSksdGhpcy5fY29weVByb3BlcnRpZXMoYSxpKSk6dFtpLmlkXT1pfWVsc2Ugci5wdXNoKGkpfX0sZ2V0QW5pbWF0aW9uQ29uZmlnOmZ1bmN0aW9uKGUpe3ZhciB0PXt9LHI9W107dGhpcy5fZ2V0QW5pbWF0aW9uQ29uZmlnUmVjdXJzaXZlKGUsdCxyKTtmb3IodmFyIG4gaW4gdClyLnB1c2godFtuXSk7cmV0dXJuIHJ9fTt2YXIgQ2JlPXtfY29uZmlndXJlQW5pbWF0aW9uczpmdW5jdGlvbihlKXt2YXIgdD1bXSxyPVtdO2lmKGUubGVuZ3RoPjApZm9yKGxldCBpLG89MDtpPWVbb107bysrKXtsZXQgYT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KGkubmFtZSk7aWYoYS5pc05lb25BbmltYXRpb24pe2xldCBzPW51bGw7YS5jb25maWd1cmV8fChhLmNvbmZpZ3VyZT1mdW5jdGlvbihsKXtyZXR1cm4gbnVsbH0pLHM9YS5jb25maWd1cmUoaSksci5wdXNoKHtyZXN1bHQ6cyxjb25maWc6aSxuZW9uQW5pbWF0aW9uOmF9KX1lbHNlIGNvbnNvbGUud2Fybih0aGlzLmlzKyI6IixpLm5hbWUsIm5vdCBmb3VuZCEiKX1mb3IodmFyIG49MDtuPHIubGVuZ3RoO24rKyl7bGV0IGk9cltuXS5yZXN1bHQsbz1yW25dLmNvbmZpZyxhPXJbbl0ubmVvbkFuaW1hdGlvbjt0cnl7dHlwZW9mIGkuY2FuY2VsIT0iZnVuY3Rpb24iJiYoaT1kb2N1bWVudC50aW1lbGluZS5wbGF5KGkpKX1jYXRjaChzKXtpPW51bGwsY29uc29sZS53YXJuKCJDb3VsZG50IHBsYXkiLCIoIixvLm5hbWUsIikuIixzKX1pJiZ0LnB1c2goe25lb25BbmltYXRpb246YSxjb25maWc6byxhbmltYXRpb246aX0pfXJldHVybiB0fSxfc2hvdWxkQ29tcGxldGU6ZnVuY3Rpb24oZSl7Zm9yKHZhciB0PSEwLHI9MDtyPGUubGVuZ3RoO3IrKylpZihlW3JdLmFuaW1hdGlvbi5wbGF5U3RhdGUhPSJmaW5pc2hlZCIpe3Q9ITE7YnJlYWt9cmV0dXJuIHR9LF9jb21wbGV0ZTpmdW5jdGlvbihlKXtmb3IodmFyIHQ9MDt0PGUubGVuZ3RoO3QrKyllW3RdLm5lb25BbmltYXRpb24uY29tcGxldGUoZVt0XS5jb25maWcpO2Zvcih2YXIgdD0wO3Q8ZS5sZW5ndGg7dCsrKWVbdF0uYW5pbWF0aW9uLmNhbmNlbCgpfSxwbGF5QW5pbWF0aW9uOmZ1bmN0aW9uKGUsdCl7dmFyIHI9dGhpcy5nZXRBbmltYXRpb25Db25maWcoZSk7aWYoISFyKXt0aGlzLl9hY3RpdmU9dGhpcy5fYWN0aXZlfHx7fSx0aGlzLl9hY3RpdmVbZV0mJih0aGlzLl9jb21wbGV0ZSh0aGlzLl9hY3RpdmVbZV0pLGRlbGV0ZSB0aGlzLl9hY3RpdmVbZV0pO3ZhciBuPXRoaXMuX2NvbmZpZ3VyZUFuaW1hdGlvbnMocik7aWYobi5sZW5ndGg9PTApe3RoaXMuZmlyZSgibmVvbi1hbmltYXRpb24tZmluaXNoIix0LHtidWJibGVzOiExfSk7cmV0dXJufXRoaXMuX2FjdGl2ZVtlXT1uO2Zvcih2YXIgaT0wO2k8bi5sZW5ndGg7aSsrKW5baV0uYW5pbWF0aW9uLm9uZmluaXNoPWZ1bmN0aW9uKCl7dGhpcy5fc2hvdWxkQ29tcGxldGUobikmJih0aGlzLl9jb21wbGV0ZShuKSxkZWxldGUgdGhpcy5fYWN0aXZlW2VdLHRoaXMuZmlyZSgibmVvbi1hbmltYXRpb24tZmluaXNoIix0LHtidWJibGVzOiExfSkpfS5iaW5kKHRoaXMpfX0sY2FuY2VsQW5pbWF0aW9uOmZ1bmN0aW9uKCl7Zm9yKHZhciBlIGluIHRoaXMuX2FjdGl2ZSl7dmFyIHQ9dGhpcy5fYWN0aXZlW2VdO2Zvcih2YXIgciBpbiB0KXRbcl0uYW5pbWF0aW9uLmNhbmNlbCgpfXRoaXMuX2FjdGl2ZT17fX19LHg5PVtwMHQsQ2JlXTt2YXIgYjksQWJlPSgpPT57aWYoYjkhPT12b2lkIDApcmV0dXJuIGI5O2xldCBlPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpO09iamVjdC5hc3NpZ24oZS5zdHlsZSx7b3ZlcmZsb3c6ImF1dG8iLHBvc2l0aW9uOiJmaXhlZCIsbGVmdDoiMHB4Iix0b3A6IjBweCIsbWF4V2lkdGg6IjEwMHB4IixtYXhIZWlnaHQ6IjEwMHB4In0pO2xldCB0PWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpO3JldHVybiB0LnN0eWxlLndpZHRoPSIyMDBweCIsdC5zdHlsZS5oZWlnaHQ9IjIwMHB4IixlLmFwcGVuZENoaWxkKHQpLGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoZSksYjk9TWF0aC5hYnMoZS5vZmZzZXRXaWR0aC0xMDApPjE/ZS5vZmZzZXRXaWR0aC1lLmNsaWVudFdpZHRoOjAsZG9jdW1lbnQuYm9keS5yZW1vdmVDaGlsZChlKSxiOX0sZDB0PXtwcm9wZXJ0aWVzOntzaXppbmdUYXJnZXQ6e3R5cGU6T2JqZWN0LHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXN9fSxmaXRJbnRvOnt0eXBlOk9iamVjdCx2YWx1ZTp3aW5kb3d9LG5vT3ZlcmxhcDp7dHlwZTpCb29sZWFufSxwb3NpdGlvblRhcmdldDp7dHlwZTpFbGVtZW50fSxob3Jpem9udGFsQWxpZ246e3R5cGU6U3RyaW5nfSx2ZXJ0aWNhbEFsaWduOnt0eXBlOlN0cmluZ30sZHluYW1pY0FsaWduOnt0eXBlOkJvb2xlYW59LGhvcml6b250YWxPZmZzZXQ6e3R5cGU6TnVtYmVyLHZhbHVlOjAsbm90aWZ5OiEwfSx2ZXJ0aWNhbE9mZnNldDp7dHlwZTpOdW1iZXIsdmFsdWU6MCxub3RpZnk6ITB9LGF1dG9GaXRPbkF0dGFjaDp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxleHBhbmRTaXppbmdUYXJnZXRGb3JTY3JvbGxiYXJzOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LF9maXRJbmZvOnt0eXBlOk9iamVjdH19LGdldCBfZml0V2lkdGgoKXt2YXIgZTtyZXR1cm4gdGhpcy5maXRJbnRvPT09d2luZG93P2U9dGhpcy5maXRJbnRvLmlubmVyV2lkdGg6ZT10aGlzLmZpdEludG8uZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkud2lkdGgsZX0sZ2V0IF9maXRIZWlnaHQoKXt2YXIgZTtyZXR1cm4gdGhpcy5maXRJbnRvPT09d2luZG93P2U9dGhpcy5maXRJbnRvLmlubmVySGVpZ2h0OmU9dGhpcy5maXRJbnRvLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLmhlaWdodCxlfSxnZXQgX2ZpdExlZnQoKXt2YXIgZTtyZXR1cm4gdGhpcy5maXRJbnRvPT09d2luZG93P2U9MDplPXRoaXMuZml0SW50by5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS5sZWZ0LGV9LGdldCBfZml0VG9wKCl7dmFyIGU7cmV0dXJuIHRoaXMuZml0SW50bz09PXdpbmRvdz9lPTA6ZT10aGlzLmZpdEludG8uZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wLGV9LGdldCBfZGVmYXVsdFBvc2l0aW9uVGFyZ2V0KCl7dmFyIGU9enQodGhpcykucGFyZW50Tm9kZTtyZXR1cm4gZSYmZS5ub2RlVHlwZT09PU5vZGUuRE9DVU1FTlRfRlJBR01FTlRfTk9ERSYmKGU9ZS5ob3N0KSxlfSxnZXQgX2xvY2FsZUhvcml6b250YWxBbGlnbigpe2lmKHRoaXMuX2lzUlRMKXtpZih0aGlzLmhvcml6b250YWxBbGlnbj09PSJyaWdodCIpcmV0dXJuImxlZnQiO2lmKHRoaXMuaG9yaXpvbnRhbEFsaWduPT09ImxlZnQiKXJldHVybiJyaWdodCJ9cmV0dXJuIHRoaXMuaG9yaXpvbnRhbEFsaWdufSxnZXQgX19zaG91bGRQb3NpdGlvbigpe3JldHVybih0aGlzLmhvcml6b250YWxBbGlnbnx8dGhpcy52ZXJ0aWNhbEFsaWduKSYmdGhpcy5wb3NpdGlvblRhcmdldH0sZ2V0IF9pc1JUTCgpe3JldHVybiB0eXBlb2YgdGhpcy5fbWVtb2l6ZWRJc1JUTD09InVuZGVmaW5lZCImJih0aGlzLl9tZW1vaXplZElzUlRMPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHRoaXMpLmRpcmVjdGlvbj09InJ0bCIpLHRoaXMuX21lbW9pemVkSXNSVEx9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5wb3NpdGlvblRhcmdldD10aGlzLnBvc2l0aW9uVGFyZ2V0fHx0aGlzLl9kZWZhdWx0UG9zaXRpb25UYXJnZXQsdGhpcy5hdXRvRml0T25BdHRhY2gmJih3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzKS5kaXNwbGF5PT09Im5vbmUiP3NldFRpbWVvdXQoZnVuY3Rpb24oKXt0aGlzLmZpdCgpfS5iaW5kKHRoaXMpKTood2luZG93LlNoYWR5RE9NJiZTaGFkeURPTS5mbHVzaCgpLHRoaXMuZml0KCkpKX0sZGV0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLl9fZGVmZXJyZWRGaXQmJihjbGVhclRpbWVvdXQodGhpcy5fX2RlZmVycmVkRml0KSx0aGlzLl9fZGVmZXJyZWRGaXQ9bnVsbCl9LGZpdDpmdW5jdGlvbigpe3RoaXMucG9zaXRpb24oKSx0aGlzLmNvbnN0cmFpbigpLHRoaXMuY2VudGVyKCl9LF9kaXNjb3ZlckluZm86ZnVuY3Rpb24oKXtpZighdGhpcy5fZml0SW5mbyl7dmFyIGU9d2luZG93LmdldENvbXB1dGVkU3R5bGUodGhpcyksdD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzLnNpemluZ1RhcmdldCk7dGhpcy5fZml0SW5mbz17aW5saW5lU3R5bGU6e3RvcDp0aGlzLnN0eWxlLnRvcHx8IiIsbGVmdDp0aGlzLnN0eWxlLmxlZnR8fCIiLHBvc2l0aW9uOnRoaXMuc3R5bGUucG9zaXRpb258fCIifSxzaXplcklubGluZVN0eWxlOnttYXhXaWR0aDp0aGlzLnNpemluZ1RhcmdldC5zdHlsZS5tYXhXaWR0aHx8IiIsbWF4SGVpZ2h0OnRoaXMuc2l6aW5nVGFyZ2V0LnN0eWxlLm1heEhlaWdodHx8IiIsYm94U2l6aW5nOnRoaXMuc2l6aW5nVGFyZ2V0LnN0eWxlLmJveFNpemluZ3x8IiJ9LHBvc2l0aW9uZWRCeTp7dmVydGljYWxseTplLnRvcCE9PSJhdXRvIj8idG9wIjplLmJvdHRvbSE9PSJhdXRvIj8iYm90dG9tIjpudWxsLGhvcml6b250YWxseTplLmxlZnQhPT0iYXV0byI/ImxlZnQiOmUucmlnaHQhPT0iYXV0byI/InJpZ2h0IjpudWxsfSxzaXplZEJ5OntoZWlnaHQ6dC5tYXhIZWlnaHQhPT0ibm9uZSIsd2lkdGg6dC5tYXhXaWR0aCE9PSJub25lIixtaW5XaWR0aDpwYXJzZUludCh0Lm1pbldpZHRoLDEwKXx8MCxtaW5IZWlnaHQ6cGFyc2VJbnQodC5taW5IZWlnaHQsMTApfHwwfSxtYXJnaW46e3RvcDpwYXJzZUludChlLm1hcmdpblRvcCwxMCl8fDAscmlnaHQ6cGFyc2VJbnQoZS5tYXJnaW5SaWdodCwxMCl8fDAsYm90dG9tOnBhcnNlSW50KGUubWFyZ2luQm90dG9tLDEwKXx8MCxsZWZ0OnBhcnNlSW50KGUubWFyZ2luTGVmdCwxMCl8fDB9fX19LHJlc2V0Rml0OmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5fZml0SW5mb3x8e307Zm9yKHZhciB0IGluIGUuc2l6ZXJJbmxpbmVTdHlsZSl0aGlzLnNpemluZ1RhcmdldC5zdHlsZVt0XT1lLnNpemVySW5saW5lU3R5bGVbdF07Zm9yKHZhciB0IGluIGUuaW5saW5lU3R5bGUpdGhpcy5zdHlsZVt0XT1lLmlubGluZVN0eWxlW3RdO3RoaXMuX2ZpdEluZm89bnVsbH0scmVmaXQ6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLnNpemluZ1RhcmdldC5zY3JvbGxMZWZ0LHQ9dGhpcy5zaXppbmdUYXJnZXQuc2Nyb2xsVG9wO3RoaXMucmVzZXRGaXQoKSx0aGlzLmZpdCgpLHRoaXMuc2l6aW5nVGFyZ2V0LnNjcm9sbExlZnQ9ZSx0aGlzLnNpemluZ1RhcmdldC5zY3JvbGxUb3A9dH0scG9zaXRpb246ZnVuY3Rpb24oKXtpZighdGhpcy5fX3Nob3VsZFBvc2l0aW9uKXJldHVybjt0aGlzLl9kaXNjb3ZlckluZm8oKSx3aW5kb3cuU2hhZHlET00mJndpbmRvdy5TaGFkeURPTS5mbHVzaCgpLHRoaXMuc3R5bGUucG9zaXRpb249ImZpeGVkIix0aGlzLnNpemluZ1RhcmdldC5zdHlsZS5ib3hTaXppbmc9ImJvcmRlci1ib3giLHRoaXMuc3R5bGUubGVmdD0iMHB4Iix0aGlzLnN0eWxlLnRvcD0iMHB4Ijt2YXIgZT10aGlzLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLHQ9dGhpcy5fX2dldE5vcm1hbGl6ZWRSZWN0KHRoaXMucG9zaXRpb25UYXJnZXQpLHI9dGhpcy5fX2dldE5vcm1hbGl6ZWRSZWN0KHRoaXMuZml0SW50byk7bGV0IG4saSxvLGE7dGhpcy5leHBhbmRTaXppbmdUYXJnZXRGb3JTY3JvbGxiYXJzJiYobj10aGlzLnNpemluZ1RhcmdldC5vZmZzZXRXaWR0aCxpPXRoaXMuc2l6aW5nVGFyZ2V0Lm9mZnNldEhlaWdodCxvPXRoaXMuc2l6aW5nVGFyZ2V0LmNsaWVudFdpZHRoLGE9dGhpcy5zaXppbmdUYXJnZXQuY2xpZW50SGVpZ2h0KTt2YXIgcz10aGlzLl9maXRJbmZvLm1hcmdpbixsPXt3aWR0aDplLndpZHRoK3MubGVmdCtzLnJpZ2h0LGhlaWdodDplLmhlaWdodCtzLnRvcCtzLmJvdHRvbX0sYz10aGlzLl9fZ2V0UG9zaXRpb24odGhpcy5fbG9jYWxlSG9yaXpvbnRhbEFsaWduLHRoaXMudmVydGljYWxBbGlnbixsLGUsdCxyKSx1PWMubGVmdCtzLmxlZnQsaD1jLnRvcCtzLnRvcCxmPU1hdGgubWluKHIucmlnaHQtcy5yaWdodCx1K2Uud2lkdGgpLHA9TWF0aC5taW4oci5ib3R0b20tcy5ib3R0b20saCtlLmhlaWdodCk7dT1NYXRoLm1heChyLmxlZnQrcy5sZWZ0LE1hdGgubWluKHUsZi10aGlzLl9maXRJbmZvLnNpemVkQnkubWluV2lkdGgpKSxoPU1hdGgubWF4KHIudG9wK3MudG9wLE1hdGgubWluKGgscC10aGlzLl9maXRJbmZvLnNpemVkQnkubWluSGVpZ2h0KSk7bGV0IGQ9TWF0aC5tYXgoZi11LHRoaXMuX2ZpdEluZm8uc2l6ZWRCeS5taW5XaWR0aCksZz1NYXRoLm1heChwLWgsdGhpcy5fZml0SW5mby5zaXplZEJ5Lm1pbkhlaWdodCk7dGhpcy5zaXppbmdUYXJnZXQuc3R5bGUubWF4V2lkdGg9ZCsicHgiLHRoaXMuc2l6aW5nVGFyZ2V0LnN0eWxlLm1heEhlaWdodD1nKyJweCI7bGV0IF89dS1lLmxlZnQseT1oLWUudG9wO2lmKHRoaXMuc3R5bGUubGVmdD1gJHtffXB4YCx0aGlzLnN0eWxlLnRvcD1gJHt5fXB4YCx0aGlzLmV4cGFuZFNpemluZ1RhcmdldEZvclNjcm9sbGJhcnMpe2xldCB4PXRoaXMuc2l6aW5nVGFyZ2V0Lm9mZnNldEhlaWdodCxiPXRoaXMuc2l6aW5nVGFyZ2V0LmNsaWVudEhlaWdodCxTPWktYSxQPXgtYi1TO2lmKFA+MCl7bGV0IEw9ci5oZWlnaHQtcy50b3Atcy5ib3R0b20sUj1NYXRoLm1pbihMLGcrUCk7dGhpcy5zaXppbmdUYXJnZXQuc3R5bGUubWF4SGVpZ2h0PWAke1J9cHhgO2xldCBGPXRoaXMuc2l6aW5nVGFyZ2V0Lm9mZnNldEhlaWdodCx6PUYteCxVO2MudmVydGljYWxBbGlnbj09PSJ0b3AiP1U9eTpjLnZlcnRpY2FsQWxpZ249PT0ibWlkZGxlIj9VPXktei8yOmMudmVydGljYWxBbGlnbj09PSJib3R0b20iJiYoVT15LXopLFU9TWF0aC5tYXgoci50b3Arcy50b3AsTWF0aC5taW4oVSxyLmJvdHRvbS1zLmJvdHRvbS1GKSksdGhpcy5zdHlsZS50b3A9YCR7VX1weGB9bGV0IGs9dGhpcy5zaXppbmdUYXJnZXQub2Zmc2V0V2lkdGgsTz10aGlzLnNpemluZ1RhcmdldC5jbGllbnRXaWR0aCxEPW4tbyxJPWstTy1EO2lmKEk+MCl7bGV0IEw9QWJlKCksUj1yLndpZHRoLXMubGVmdC1zLnJpZ2h0LEY9TWF0aC5taW4oUixkK0ktTCk7dGhpcy5zaXppbmdUYXJnZXQuc3R5bGUubWF4V2lkdGg9YCR7Rn1weGA7bGV0IHo9dGhpcy5zaXppbmdUYXJnZXQub2Zmc2V0V2lkdGgrTCxVPXotayxXO2MuaG9yaXpvbnRhbEFsaWduPT09ImxlZnQiP1c9XzpjLmhvcml6b250YWxBbGlnbj09PSJjZW50ZXIiP1c9Xy1VLzI6Yy5ob3Jpem9udGFsQWxpZ249PT0icmlnaHQiJiYoVz1fLVUpLFc9TWF0aC5tYXgoci5sZWZ0K3MubGVmdCxNYXRoLm1pbihXLHIucmlnaHQtcy5yaWdodC16KSksdGhpcy5zdHlsZS5sZWZ0PWAke1d9cHhgfX19LGNvbnN0cmFpbjpmdW5jdGlvbigpe2lmKCF0aGlzLl9fc2hvdWxkUG9zaXRpb24pe3RoaXMuX2Rpc2NvdmVySW5mbygpO3ZhciBlPXRoaXMuX2ZpdEluZm87ZS5wb3NpdGlvbmVkQnkudmVydGljYWxseXx8KHRoaXMuc3R5bGUucG9zaXRpb249ImZpeGVkIix0aGlzLnN0eWxlLnRvcD0iMHB4IiksZS5wb3NpdGlvbmVkQnkuaG9yaXpvbnRhbGx5fHwodGhpcy5zdHlsZS5wb3NpdGlvbj0iZml4ZWQiLHRoaXMuc3R5bGUubGVmdD0iMHB4IiksdGhpcy5zaXppbmdUYXJnZXQuc3R5bGUuYm94U2l6aW5nPSJib3JkZXItYm94Ijt2YXIgdD10aGlzLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO2Uuc2l6ZWRCeS5oZWlnaHR8fHRoaXMuX19zaXplRGltZW5zaW9uKHQsZS5wb3NpdGlvbmVkQnkudmVydGljYWxseSwidG9wIiwiYm90dG9tIiwiSGVpZ2h0IiksZS5zaXplZEJ5LndpZHRofHx0aGlzLl9fc2l6ZURpbWVuc2lvbih0LGUucG9zaXRpb25lZEJ5Lmhvcml6b250YWxseSwibGVmdCIsInJpZ2h0IiwiV2lkdGgiKX19LF9zaXplRGltZW5zaW9uOmZ1bmN0aW9uKGUsdCxyLG4saSl7dGhpcy5fX3NpemVEaW1lbnNpb24oZSx0LHIsbixpKX0sX19zaXplRGltZW5zaW9uOmZ1bmN0aW9uKGUsdCxyLG4saSl7dmFyIG89dGhpcy5fZml0SW5mbyxhPXRoaXMuX19nZXROb3JtYWxpemVkUmVjdCh0aGlzLmZpdEludG8pLHM9aT09PSJXaWR0aCI/YS53aWR0aDphLmhlaWdodCxsPXQ9PT1uLGM9bD9zLWVbbl06ZVtyXSx1PW8ubWFyZ2luW2w/cjpuXSxoPSJvZmZzZXQiK2ksZj10aGlzW2hdLXRoaXMuc2l6aW5nVGFyZ2V0W2hdO3RoaXMuc2l6aW5nVGFyZ2V0LnN0eWxlWyJtYXgiK2ldPXMtdS1jLWYrInB4In0sY2VudGVyOmZ1bmN0aW9uKCl7aWYoIXRoaXMuX19zaG91bGRQb3NpdGlvbil7dGhpcy5fZGlzY292ZXJJbmZvKCk7dmFyIGU9dGhpcy5fZml0SW5mby5wb3NpdGlvbmVkQnk7aWYoIShlLnZlcnRpY2FsbHkmJmUuaG9yaXpvbnRhbGx5KSl7dGhpcy5zdHlsZS5wb3NpdGlvbj0iZml4ZWQiLGUudmVydGljYWxseXx8KHRoaXMuc3R5bGUudG9wPSIwcHgiKSxlLmhvcml6b250YWxseXx8KHRoaXMuc3R5bGUubGVmdD0iMHB4Iik7dmFyIHQ9dGhpcy5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxyPXRoaXMuX19nZXROb3JtYWxpemVkUmVjdCh0aGlzLmZpdEludG8pO2lmKCFlLnZlcnRpY2FsbHkpe3ZhciBuPXIudG9wLXQudG9wKyhyLmhlaWdodC10LmhlaWdodCkvMjt0aGlzLnN0eWxlLnRvcD1uKyJweCJ9aWYoIWUuaG9yaXpvbnRhbGx5KXt2YXIgaT1yLmxlZnQtdC5sZWZ0KyhyLndpZHRoLXQud2lkdGgpLzI7dGhpcy5zdHlsZS5sZWZ0PWkrInB4In19fX0sX19nZXROb3JtYWxpemVkUmVjdDpmdW5jdGlvbihlKXtyZXR1cm4gZT09PWRvY3VtZW50LmRvY3VtZW50RWxlbWVudHx8ZT09PXdpbmRvdz97dG9wOjAsbGVmdDowLHdpZHRoOndpbmRvdy5pbm5lcldpZHRoLGhlaWdodDp3aW5kb3cuaW5uZXJIZWlnaHQscmlnaHQ6d2luZG93LmlubmVyV2lkdGgsYm90dG9tOndpbmRvdy5pbm5lckhlaWdodH06ZS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKX0sX19nZXRPZmZzY3JlZW5BcmVhOmZ1bmN0aW9uKGUsdCxyKXt2YXIgbj1NYXRoLm1pbigwLGUudG9wKStNYXRoLm1pbigwLHIuYm90dG9tLShlLnRvcCt0LmhlaWdodCkpLGk9TWF0aC5taW4oMCxlLmxlZnQpK01hdGgubWluKDAsci5yaWdodC0oZS5sZWZ0K3Qud2lkdGgpKTtyZXR1cm4gTWF0aC5hYnMobikqdC53aWR0aCtNYXRoLmFicyhpKSp0LmhlaWdodH0sX19nZXRQb3NpdGlvbjpmdW5jdGlvbihlLHQscixuLGksbyl7dmFyIGE9W3t2ZXJ0aWNhbEFsaWduOiJ0b3AiLGhvcml6b250YWxBbGlnbjoibGVmdCIsdG9wOmkudG9wK3RoaXMudmVydGljYWxPZmZzZXQsbGVmdDppLmxlZnQrdGhpcy5ob3Jpem9udGFsT2Zmc2V0fSx7dmVydGljYWxBbGlnbjoidG9wIixob3Jpem9udGFsQWxpZ246InJpZ2h0Iix0b3A6aS50b3ArdGhpcy52ZXJ0aWNhbE9mZnNldCxsZWZ0OmkucmlnaHQtci53aWR0aC10aGlzLmhvcml6b250YWxPZmZzZXR9LHt2ZXJ0aWNhbEFsaWduOiJib3R0b20iLGhvcml6b250YWxBbGlnbjoibGVmdCIsdG9wOmkuYm90dG9tLXIuaGVpZ2h0LXRoaXMudmVydGljYWxPZmZzZXQsbGVmdDppLmxlZnQrdGhpcy5ob3Jpem9udGFsT2Zmc2V0fSx7dmVydGljYWxBbGlnbjoiYm90dG9tIixob3Jpem9udGFsQWxpZ246InJpZ2h0Iix0b3A6aS5ib3R0b20tci5oZWlnaHQtdGhpcy52ZXJ0aWNhbE9mZnNldCxsZWZ0OmkucmlnaHQtci53aWR0aC10aGlzLmhvcml6b250YWxPZmZzZXR9XTtpZih0aGlzLm5vT3ZlcmxhcCl7Zm9yKHZhciBzPTAsbD1hLmxlbmd0aDtzPGw7cysrKXt2YXIgYz17fTtmb3IodmFyIHUgaW4gYVtzXSljW3VdPWFbc11bdV07YS5wdXNoKGMpfWFbMF0udG9wPWFbMV0udG9wKz1pLmhlaWdodCxhWzJdLnRvcD1hWzNdLnRvcC09aS5oZWlnaHQsYVs0XS5sZWZ0PWFbNl0ubGVmdCs9aS53aWR0aCxhWzVdLmxlZnQ9YVs3XS5sZWZ0LT1pLndpZHRofXQ9dD09PSJhdXRvIj9udWxsOnQsZT1lPT09ImF1dG8iP251bGw6ZSwoIWV8fGU9PT0iY2VudGVyIikmJihhLnB1c2goe3ZlcnRpY2FsQWxpZ246InRvcCIsaG9yaXpvbnRhbEFsaWduOiJjZW50ZXIiLHRvcDppLnRvcCt0aGlzLnZlcnRpY2FsT2Zmc2V0Kyh0aGlzLm5vT3ZlcmxhcD9pLmhlaWdodDowKSxsZWZ0OmkubGVmdC1uLndpZHRoLzIraS53aWR0aC8yK3RoaXMuaG9yaXpvbnRhbE9mZnNldH0pLGEucHVzaCh7dmVydGljYWxBbGlnbjoiYm90dG9tIixob3Jpem9udGFsQWxpZ246ImNlbnRlciIsdG9wOmkuYm90dG9tLXIuaGVpZ2h0LXRoaXMudmVydGljYWxPZmZzZXQtKHRoaXMubm9PdmVybGFwP2kuaGVpZ2h0OjApLGxlZnQ6aS5sZWZ0LW4ud2lkdGgvMitpLndpZHRoLzIrdGhpcy5ob3Jpem9udGFsT2Zmc2V0fSkpLCghdHx8dD09PSJtaWRkbGUiKSYmKGEucHVzaCh7dmVydGljYWxBbGlnbjoibWlkZGxlIixob3Jpem9udGFsQWxpZ246ImxlZnQiLHRvcDppLnRvcC1uLmhlaWdodC8yK2kuaGVpZ2h0LzIrdGhpcy52ZXJ0aWNhbE9mZnNldCxsZWZ0OmkubGVmdCt0aGlzLmhvcml6b250YWxPZmZzZXQrKHRoaXMubm9PdmVybGFwP2kud2lkdGg6MCl9KSxhLnB1c2goe3ZlcnRpY2FsQWxpZ246Im1pZGRsZSIsaG9yaXpvbnRhbEFsaWduOiJyaWdodCIsdG9wOmkudG9wLW4uaGVpZ2h0LzIraS5oZWlnaHQvMit0aGlzLnZlcnRpY2FsT2Zmc2V0LGxlZnQ6aS5yaWdodC1yLndpZHRoLXRoaXMuaG9yaXpvbnRhbE9mZnNldC0odGhpcy5ub092ZXJsYXA/aS53aWR0aDowKX0pKSx0PT09Im1pZGRsZSImJmU9PT0iY2VudGVyIiYmYS5wdXNoKHt2ZXJ0aWNhbEFsaWduOiJtaWRkbGUiLGhvcml6b250YWxBbGlnbjoiY2VudGVyIix0b3A6aS50b3Atbi5oZWlnaHQvMitpLmhlaWdodC8yK3RoaXMudmVydGljYWxPZmZzZXQsbGVmdDppLmxlZnQtbi53aWR0aC8yK2kud2lkdGgvMit0aGlzLmhvcml6b250YWxPZmZzZXR9KTtmb3IodmFyIGgscz0wO3M8YS5sZW5ndGg7cysrKXt2YXIgZj1hW3NdLHA9Zi52ZXJ0aWNhbEFsaWduPT09dCxkPWYuaG9yaXpvbnRhbEFsaWduPT09ZTtpZighdGhpcy5keW5hbWljQWxpZ24mJiF0aGlzLm5vT3ZlcmxhcCYmcCYmZCl7aD1mO2JyZWFrfXZhciBnPSghdHx8cCkmJighZXx8ZCk7aWYoISghdGhpcy5keW5hbWljQWxpZ24mJiFnKSl7aWYoZi5vZmZzY3JlZW5BcmVhPXRoaXMuX19nZXRPZmZzY3JlZW5BcmVhKGYscixvKSxmLm9mZnNjcmVlbkFyZWE9PT0wJiZnKXtoPWY7YnJlYWt9aD1ofHxmO3ZhciBfPWYub2Zmc2NyZWVuQXJlYS1oLm9mZnNjcmVlbkFyZWE7KF88MHx8Xz09PTAmJihwfHxkKSkmJihoPWYpfX1yZXR1cm4gaH19O3ZhciAkeD1FbGVtZW50LnByb3RvdHlwZSx3OT0keC5tYXRjaGVzfHwkeC5tYXRjaGVzU2VsZWN0b3J8fCR4Lm1vek1hdGNoZXNTZWxlY3Rvcnx8JHgubXNNYXRjaGVzU2VsZWN0b3J8fCR4Lm9NYXRjaGVzU2VsZWN0b3J8fCR4LndlYmtpdE1hdGNoZXNTZWxlY3RvcixtVz1jbGFzc3tnZXRUYWJiYWJsZU5vZGVzKHQpe3ZhciByPVtdLG49dGhpcy5fY29sbGVjdFRhYmJhYmxlTm9kZXModCxyKTtyZXR1cm4gbj90aGlzLl9zb3J0QnlUYWJJbmRleChyKTpyfWlzRm9jdXNhYmxlKHQpe3JldHVybiB3OS5jYWxsKHQsImlucHV0LCBzZWxlY3QsIHRleHRhcmVhLCBidXR0b24sIG9iamVjdCIpP3c5LmNhbGwodCwiOm5vdChbZGlzYWJsZWRdKSIpOnc5LmNhbGwodCwiYVtocmVmXSwgYXJlYVtocmVmXSwgaWZyYW1lLCBbdGFiaW5kZXhdLCBbY29udGVudEVkaXRhYmxlXSIpfWlzVGFiYmFibGUodCl7cmV0dXJuIHRoaXMuaXNGb2N1c2FibGUodCkmJnc5LmNhbGwodCwnOm5vdChbdGFiaW5kZXg9Ii0xIl0pJykmJnRoaXMuX2lzVmlzaWJsZSh0KX1fbm9ybWFsaXplZFRhYkluZGV4KHQpe2lmKHRoaXMuaXNGb2N1c2FibGUodCkpe3ZhciByPXQuZ2V0QXR0cmlidXRlKCJ0YWJpbmRleCIpfHwwO3JldHVybiBOdW1iZXIocil9cmV0dXJuLTF9X2NvbGxlY3RUYWJiYWJsZU5vZGVzKHQscil7aWYodC5ub2RlVHlwZSE9PU5vZGUuRUxFTUVOVF9OT0RFKXJldHVybiExO3ZhciBuPXQ7aWYoIXRoaXMuX2lzVmlzaWJsZShuKSlyZXR1cm4hMTt2YXIgaT10aGlzLl9ub3JtYWxpemVkVGFiSW5kZXgobiksbz1pPjA7aT49MCYmci5wdXNoKG4pO3ZhciBhO24ubG9jYWxOYW1lPT09ImNvbnRlbnQifHxuLmxvY2FsTmFtZT09PSJzbG90Ij9hPXp0KG4pLmdldERpc3RyaWJ1dGVkTm9kZXMoKTphPXp0KG4ucm9vdHx8bikuY2hpbGRyZW47Zm9yKHZhciBzPTA7czxhLmxlbmd0aDtzKyspbz10aGlzLl9jb2xsZWN0VGFiYmFibGVOb2RlcyhhW3NdLHIpfHxvO3JldHVybiBvfV9pc1Zpc2libGUodCl7dmFyIHI9dC5zdHlsZTtyZXR1cm4gci52aXNpYmlsaXR5IT09ImhpZGRlbiImJnIuZGlzcGxheSE9PSJub25lIj8ocj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0KSxyLnZpc2liaWxpdHkhPT0iaGlkZGVuIiYmci5kaXNwbGF5IT09Im5vbmUiKTohMX1fc29ydEJ5VGFiSW5kZXgodCl7dmFyIHI9dC5sZW5ndGg7aWYocjwyKXJldHVybiB0O3ZhciBuPU1hdGguY2VpbChyLzIpLGk9dGhpcy5fc29ydEJ5VGFiSW5kZXgodC5zbGljZSgwLG4pKSxvPXRoaXMuX3NvcnRCeVRhYkluZGV4KHQuc2xpY2UobikpO3JldHVybiB0aGlzLl9tZXJnZVNvcnRCeVRhYkluZGV4KGksbyl9X21lcmdlU29ydEJ5VGFiSW5kZXgodCxyKXtmb3IodmFyIG49W107dC5sZW5ndGg+MCYmci5sZW5ndGg+MDspdGhpcy5faGFzTG93ZXJUYWJPcmRlcih0WzBdLHJbMF0pP24ucHVzaChyLnNoaWZ0KCkpOm4ucHVzaCh0LnNoaWZ0KCkpO3JldHVybiBuLmNvbmNhdCh0LHIpfV9oYXNMb3dlclRhYk9yZGVyKHQscil7dmFyIG49TWF0aC5tYXgodC50YWJJbmRleCwwKSxpPU1hdGgubWF4KHIudGFiSW5kZXgsMCk7cmV0dXJuIG49PT0wfHxpPT09MD9pPm46bj5pfX0sbTB0PW5ldyBtVztZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBwb3NpdGlvbjogZml4ZWQ7CiAgICAgICAgdG9wOiAwOwogICAgICAgIGxlZnQ6IDA7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLWlyb24tb3ZlcmxheS1iYWNrZHJvcC1iYWNrZ3JvdW5kLWNvbG9yLCAjMDAwKTsKICAgICAgICBvcGFjaXR5OiAwOwogICAgICAgIHRyYW5zaXRpb246IG9wYWNpdHkgMC4yczsKICAgICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgICBAYXBwbHkgLS1pcm9uLW92ZXJsYXktYmFja2Ryb3A7CiAgICAgIH0KCiAgICAgIDpob3N0KC5vcGVuZWQpIHsKICAgICAgICBvcGFjaXR5OiB2YXIoLS1pcm9uLW92ZXJsYXktYmFja2Ryb3Atb3BhY2l0eSwgMC42KTsKICAgICAgICBwb2ludGVyLWV2ZW50czogYXV0bzsKICAgICAgICBAYXBwbHkgLS1pcm9uLW92ZXJsYXktYmFja2Ryb3Atb3BlbmVkOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxzbG90Pjwvc2xvdD4KYCxpczoiaXJvbi1vdmVybGF5LWJhY2tkcm9wIixwcm9wZXJ0aWVzOntvcGVuZWQ6e3JlZmxlY3RUb0F0dHJpYnV0ZTohMCx0eXBlOkJvb2xlYW4sdmFsdWU6ITEsb2JzZXJ2ZXI6Il9vcGVuZWRDaGFuZ2VkIn19LGxpc3RlbmVyczp7dHJhbnNpdGlvbmVuZDoiX29uVHJhbnNpdGlvbmVuZCJ9LGNyZWF0ZWQ6ZnVuY3Rpb24oKXt0aGlzLl9fb3BlbmVkUmFmPW51bGx9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5vcGVuZWQmJnRoaXMuX29wZW5lZENoYW5nZWQodGhpcy5vcGVuZWQpfSxwcmVwYXJlOmZ1bmN0aW9uKCl7dGhpcy5vcGVuZWQmJiF0aGlzLnBhcmVudE5vZGUmJnp0KGRvY3VtZW50LmJvZHkpLmFwcGVuZENoaWxkKHRoaXMpfSxvcGVuOmZ1bmN0aW9uKCl7dGhpcy5vcGVuZWQ9ITB9LGNsb3NlOmZ1bmN0aW9uKCl7dGhpcy5vcGVuZWQ9ITF9LGNvbXBsZXRlOmZ1bmN0aW9uKCl7IXRoaXMub3BlbmVkJiZ0aGlzLnBhcmVudE5vZGU9PT1kb2N1bWVudC5ib2R5JiZ6dCh0aGlzLnBhcmVudE5vZGUpLnJlbW92ZUNoaWxkKHRoaXMpfSxfb25UcmFuc2l0aW9uZW5kOmZ1bmN0aW9uKGUpe2UmJmUudGFyZ2V0PT09dGhpcyYmdGhpcy5jb21wbGV0ZSgpfSxfb3BlbmVkQ2hhbmdlZDpmdW5jdGlvbihlKXtpZihlKXRoaXMucHJlcGFyZSgpO2Vsc2V7dmFyIHQ9d2luZG93LmdldENvbXB1dGVkU3R5bGUodGhpcyk7KHQudHJhbnNpdGlvbkR1cmF0aW9uPT09IjBzInx8dC5vcGFjaXR5PT0wKSYmdGhpcy5jb21wbGV0ZSgpfSF0aGlzLmlzQXR0YWNoZWR8fCh0aGlzLl9fb3BlbmVkUmFmJiYod2luZG93LmNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuX19vcGVuZWRSYWYpLHRoaXMuX19vcGVuZWRSYWY9bnVsbCksdGhpcy5zY3JvbGxUb3A9dGhpcy5zY3JvbGxUb3AsdGhpcy5fX29wZW5lZFJhZj13aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lKGZ1bmN0aW9uKCl7dGhpcy5fX29wZW5lZFJhZj1udWxsLHRoaXMudG9nZ2xlQ2xhc3MoIm9wZW5lZCIsdGhpcy5vcGVuZWQpfS5iaW5kKHRoaXMpKSl9fSk7dmFyIGdXPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5fb3ZlcmxheXM9W10sdGhpcy5fbWluaW11bVo9MTAxLHRoaXMuX2JhY2tkcm9wRWxlbWVudD1udWxsLEVtKGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCwidGFwIixmdW5jdGlvbigpe30pLGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoInRhcCIsdGhpcy5fb25DYXB0dXJlQ2xpY2suYmluZCh0aGlzKSwhMCksZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXMiLHRoaXMuX29uQ2FwdHVyZUZvY3VzLmJpbmQodGhpcyksITApLGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoImtleWRvd24iLHRoaXMuX29uQ2FwdHVyZUtleURvd24uYmluZCh0aGlzKSwhMCl9Z2V0IGJhY2tkcm9wRWxlbWVudCgpe3JldHVybiB0aGlzLl9iYWNrZHJvcEVsZW1lbnR8fCh0aGlzLl9iYWNrZHJvcEVsZW1lbnQ9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiaXJvbi1vdmVybGF5LWJhY2tkcm9wIikpLHRoaXMuX2JhY2tkcm9wRWxlbWVudH1nZXQgZGVlcEFjdGl2ZUVsZW1lbnQoKXt2YXIgdD1kb2N1bWVudC5hY3RpdmVFbGVtZW50O2ZvcigoIXR8fCEodCBpbnN0YW5jZW9mIEVsZW1lbnQpKSYmKHQ9ZG9jdW1lbnQuYm9keSk7dC5yb290JiZ6dCh0LnJvb3QpLmFjdGl2ZUVsZW1lbnQ7KXQ9enQodC5yb290KS5hY3RpdmVFbGVtZW50O3JldHVybiB0fV9icmluZ092ZXJsYXlBdEluZGV4VG9Gcm9udCh0KXt2YXIgcj10aGlzLl9vdmVybGF5c1t0XTtpZighIXIpe3ZhciBuPXRoaXMuX292ZXJsYXlzLmxlbmd0aC0xLGk9dGhpcy5fb3ZlcmxheXNbbl07aWYoaSYmdGhpcy5fc2hvdWxkQmVCZWhpbmRPdmVybGF5KHIsaSkmJm4tLSwhKHQ+PW4pKXt2YXIgbz1NYXRoLm1heCh0aGlzLmN1cnJlbnRPdmVybGF5WigpLHRoaXMuX21pbmltdW1aKTtmb3IodGhpcy5fZ2V0WihyKTw9byYmdGhpcy5fYXBwbHlPdmVybGF5WihyLG8pO3Q8bjspdGhpcy5fb3ZlcmxheXNbdF09dGhpcy5fb3ZlcmxheXNbdCsxXSx0Kys7dGhpcy5fb3ZlcmxheXNbbl09cn19fWFkZE9yUmVtb3ZlT3ZlcmxheSh0KXt0Lm9wZW5lZD90aGlzLmFkZE92ZXJsYXkodCk6dGhpcy5yZW1vdmVPdmVybGF5KHQpfWFkZE92ZXJsYXkodCl7dmFyIHI9dGhpcy5fb3ZlcmxheXMuaW5kZXhPZih0KTtpZihyPj0wKXt0aGlzLl9icmluZ092ZXJsYXlBdEluZGV4VG9Gcm9udChyKSx0aGlzLnRyYWNrQmFja2Ryb3AoKTtyZXR1cm59dmFyIG49dGhpcy5fb3ZlcmxheXMubGVuZ3RoLGk9dGhpcy5fb3ZlcmxheXNbbi0xXSxvPU1hdGgubWF4KHRoaXMuX2dldFooaSksdGhpcy5fbWluaW11bVopLGE9dGhpcy5fZ2V0Wih0KTtpZihpJiZ0aGlzLl9zaG91bGRCZUJlaGluZE92ZXJsYXkodCxpKSl7dGhpcy5fYXBwbHlPdmVybGF5WihpLG8pLG4tLTt2YXIgcz10aGlzLl9vdmVybGF5c1tuLTFdO289TWF0aC5tYXgodGhpcy5fZ2V0WihzKSx0aGlzLl9taW5pbXVtWil9YTw9byYmdGhpcy5fYXBwbHlPdmVybGF5Wih0LG8pLHRoaXMuX292ZXJsYXlzLnNwbGljZShuLDAsdCksdGhpcy50cmFja0JhY2tkcm9wKCl9cmVtb3ZlT3ZlcmxheSh0KXt2YXIgcj10aGlzLl9vdmVybGF5cy5pbmRleE9mKHQpO3IhPT0tMSYmKHRoaXMuX292ZXJsYXlzLnNwbGljZShyLDEpLHRoaXMudHJhY2tCYWNrZHJvcCgpKX1jdXJyZW50T3ZlcmxheSgpe3ZhciB0PXRoaXMuX292ZXJsYXlzLmxlbmd0aC0xO3JldHVybiB0aGlzLl9vdmVybGF5c1t0XX1jdXJyZW50T3ZlcmxheVooKXtyZXR1cm4gdGhpcy5fZ2V0Wih0aGlzLmN1cnJlbnRPdmVybGF5KCkpfWVuc3VyZU1pbmltdW1aKHQpe3RoaXMuX21pbmltdW1aPU1hdGgubWF4KHRoaXMuX21pbmltdW1aLHQpfWZvY3VzT3ZlcmxheSgpe3ZhciB0PXRoaXMuY3VycmVudE92ZXJsYXkoKTt0JiZ0Ll9hcHBseUZvY3VzKCl9dHJhY2tCYWNrZHJvcCgpe3ZhciB0PXRoaXMuX292ZXJsYXlXaXRoQmFja2Ryb3AoKTshdCYmIXRoaXMuX2JhY2tkcm9wRWxlbWVudHx8KHRoaXMuYmFja2Ryb3BFbGVtZW50LnN0eWxlLnpJbmRleD10aGlzLl9nZXRaKHQpLTEsdGhpcy5iYWNrZHJvcEVsZW1lbnQub3BlbmVkPSEhdCx0aGlzLmJhY2tkcm9wRWxlbWVudC5wcmVwYXJlKCkpfWdldEJhY2tkcm9wcygpe2Zvcih2YXIgdD1bXSxyPTA7cjx0aGlzLl9vdmVybGF5cy5sZW5ndGg7cisrKXRoaXMuX292ZXJsYXlzW3JdLndpdGhCYWNrZHJvcCYmdC5wdXNoKHRoaXMuX292ZXJsYXlzW3JdKTtyZXR1cm4gdH1iYWNrZHJvcFooKXtyZXR1cm4gdGhpcy5fZ2V0Wih0aGlzLl9vdmVybGF5V2l0aEJhY2tkcm9wKCkpLTF9X292ZXJsYXlXaXRoQmFja2Ryb3AoKXtmb3IodmFyIHQ9dGhpcy5fb3ZlcmxheXMubGVuZ3RoLTE7dD49MDt0LS0paWYodGhpcy5fb3ZlcmxheXNbdF0ud2l0aEJhY2tkcm9wKXJldHVybiB0aGlzLl9vdmVybGF5c1t0XX1fZ2V0Wih0KXt2YXIgcj10aGlzLl9taW5pbXVtWjtpZih0KXt2YXIgbj1OdW1iZXIodC5zdHlsZS56SW5kZXh8fHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHQpLnpJbmRleCk7bj09PW4mJihyPW4pfXJldHVybiByfV9zZXRaKHQscil7dC5zdHlsZS56SW5kZXg9cn1fYXBwbHlPdmVybGF5Wih0LHIpe3RoaXMuX3NldFoodCxyKzIpfV9vdmVybGF5SW5QYXRoKHQpe3Q9dHx8W107Zm9yKHZhciByPTA7cjx0Lmxlbmd0aDtyKyspaWYodFtyXS5fbWFuYWdlcj09PXRoaXMpcmV0dXJuIHRbcl19X29uQ2FwdHVyZUNsaWNrKHQpe3ZhciByPXRoaXMuX292ZXJsYXlzLmxlbmd0aC0xO2lmKHIhPT0tMSlmb3IodmFyIG49enQodCkucGF0aCxpOyhpPXRoaXMuX292ZXJsYXlzW3JdKSYmdGhpcy5fb3ZlcmxheUluUGF0aChuKSE9PWkmJihpLl9vbkNhcHR1cmVDbGljayh0KSxpLmFsbG93Q2xpY2tUaHJvdWdoKTspci0tfV9vbkNhcHR1cmVGb2N1cyh0KXt2YXIgcj10aGlzLmN1cnJlbnRPdmVybGF5KCk7ciYmci5fb25DYXB0dXJlRm9jdXModCl9X29uQ2FwdHVyZUtleURvd24odCl7dmFyIHI9dGhpcy5jdXJyZW50T3ZlcmxheSgpO3ImJihPby5rZXlib2FyZEV2ZW50TWF0Y2hlc0tleXModCwiZXNjIik/ci5fb25DYXB0dXJlRXNjKHQpOk9vLmtleWJvYXJkRXZlbnRNYXRjaGVzS2V5cyh0LCJ0YWIiKSYmci5fb25DYXB0dXJlVGFiKHQpKX1fc2hvdWxkQmVCZWhpbmRPdmVybGF5KHQscil7cmV0dXJuIXQuYWx3YXlzT25Ub3AmJnIuYWx3YXlzT25Ub3B9fSxnMHQ9bmV3IGdXO3ZhciBNOT17cGFnZVg6MCxwYWdlWTowfSxfMHQ9bnVsbCxfVz1bXSxFOT1bIndoZWVsIiwibW91c2V3aGVlbCIsIkRPTU1vdXNlU2Nyb2xsIiwidG91Y2hzdGFydCIsInRvdWNobW92ZSJdLFM5LHlXO2Z1bmN0aW9uIHkwdChlKXtDaC5pbmRleE9mKGUpPj0wfHwoQ2gubGVuZ3RoPT09MCYmSWJlKCksQ2gucHVzaChlKSx5Vz1DaFtDaC5sZW5ndGgtMV0seDB0PVtdLGIwdD1bXSl9ZnVuY3Rpb24gdjB0KGUpe3ZhciB0PUNoLmluZGV4T2YoZSk7dCE9PS0xJiYoQ2guc3BsaWNlKHQsMSkseVc9Q2hbQ2gubGVuZ3RoLTFdLHgwdD1bXSxiMHQ9W10sQ2gubGVuZ3RoPT09MCYmTGJlKCkpfXZhciBDaD1bXSx4MHQ9bnVsbCxiMHQ9bnVsbDtmdW5jdGlvbiBQYmUoZSl7aWYoZS5jYW5jZWxhYmxlJiZrYmUoZSkmJmUucHJldmVudERlZmF1bHQoKSxlLnRhcmdldFRvdWNoZXMpe3ZhciB0PWUudGFyZ2V0VG91Y2hlc1swXTtNOS5wYWdlWD10LnBhZ2VYLE05LnBhZ2VZPXQucGFnZVl9fWZ1bmN0aW9uIEliZSgpe1M5PVM5fHxQYmUuYmluZCh2b2lkIDApO2Zvcih2YXIgZT0wLHQ9RTkubGVuZ3RoO2U8dDtlKyspZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcihFOVtlXSxTOSx7Y2FwdHVyZTohMCxwYXNzaXZlOiExfSl9ZnVuY3Rpb24gTGJlKCl7Zm9yKHZhciBlPTAsdD1FOS5sZW5ndGg7ZTx0O2UrKylkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKEU5W2VdLFM5LHtjYXB0dXJlOiEwLHBhc3NpdmU6ITF9KX1mdW5jdGlvbiBrYmUoZSl7dmFyIHQ9enQoZSkucm9vdFRhcmdldDtpZihlLnR5cGUhPT0idG91Y2htb3ZlIiYmXzB0IT09dCYmKF8wdD10LF9XPVJiZSh6dChlKS5wYXRoKSksIV9XLmxlbmd0aClyZXR1cm4hMDtpZihlLnR5cGU9PT0idG91Y2hzdGFydCIpcmV0dXJuITE7dmFyIHI9RGJlKGUpO3JldHVybiFOYmUoX1csci5kZWx0YVgsci5kZWx0YVkpfWZ1bmN0aW9uIFJiZShlKXtmb3IodmFyIHQ9W10scj1lLmluZGV4T2YoeVcpLG49MDtuPD1yO24rKylpZihlW25dLm5vZGVUeXBlPT09Tm9kZS5FTEVNRU5UX05PREUpe3ZhciBpPWVbbl0sbz1pLnN0eWxlO28ub3ZlcmZsb3chPT0ic2Nyb2xsIiYmby5vdmVyZmxvdyE9PSJhdXRvIiYmKG89d2luZG93LmdldENvbXB1dGVkU3R5bGUoaSkpLChvLm92ZXJmbG93PT09InNjcm9sbCJ8fG8ub3ZlcmZsb3c9PT0iYXV0byIpJiZ0LnB1c2goaSl9cmV0dXJuIHR9ZnVuY3Rpb24gTmJlKGUsdCxyKXtpZighKCF0JiYhcikpZm9yKHZhciBuPU1hdGguYWJzKHIpPj1NYXRoLmFicyh0KSxpPTA7aTxlLmxlbmd0aDtpKyspe3ZhciBvPWVbaV0sYT0hMTtpZihuP2E9cjwwP28uc2Nyb2xsVG9wPjA6by5zY3JvbGxUb3A8by5zY3JvbGxIZWlnaHQtby5jbGllbnRIZWlnaHQ6YT10PDA/by5zY3JvbGxMZWZ0PjA6by5zY3JvbGxMZWZ0PG8uc2Nyb2xsV2lkdGgtby5jbGllbnRXaWR0aCxhKXJldHVybiBvfX1mdW5jdGlvbiBEYmUoZSl7dmFyIHQ9e2RlbHRhWDplLmRlbHRhWCxkZWx0YVk6ZS5kZWx0YVl9O2lmKCEoImRlbHRhWCJpbiBlKSl7aWYoIndoZWVsRGVsdGFYImluIGUmJiJ3aGVlbERlbHRhWSJpbiBlKXQuZGVsdGFYPS1lLndoZWVsRGVsdGFYLHQuZGVsdGFZPS1lLndoZWVsRGVsdGFZO2Vsc2UgaWYoIndoZWVsRGVsdGEiaW4gZSl0LmRlbHRhWD0wLHQuZGVsdGFZPS1lLndoZWVsRGVsdGE7ZWxzZSBpZigiYXhpcyJpbiBlKXQuZGVsdGFYPWUuYXhpcz09PTE/ZS5kZXRhaWw6MCx0LmRlbHRhWT1lLmF4aXM9PT0yP2UuZGV0YWlsOjA7ZWxzZSBpZihlLnRhcmdldFRvdWNoZXMpe3ZhciByPWUudGFyZ2V0VG91Y2hlc1swXTt0LmRlbHRhWD1NOS5wYWdlWC1yLnBhZ2VYLHQuZGVsdGFZPU05LnBhZ2VZLXIucGFnZVl9fXJldHVybiB0fXZhciBQbT17cHJvcGVydGllczp7b3BlbmVkOntvYnNlcnZlcjoiX29wZW5lZENoYW5nZWQiLHR5cGU6Qm9vbGVhbix2YWx1ZTohMSxub3RpZnk6ITB9LGNhbmNlbGVkOntvYnNlcnZlcjoiX2NhbmNlbGVkQ2hhbmdlZCIscmVhZE9ubHk6ITAsdHlwZTpCb29sZWFuLHZhbHVlOiExfSx3aXRoQmFja2Ryb3A6e29ic2VydmVyOiJfd2l0aEJhY2tkcm9wQ2hhbmdlZCIsdHlwZTpCb29sZWFufSxub0F1dG9Gb2N1czp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxub0NhbmNlbE9uRXNjS2V5Ont0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LG5vQ2FuY2VsT25PdXRzaWRlQ2xpY2s6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sY2xvc2luZ1JlYXNvbjp7dHlwZTpPYmplY3R9LHJlc3RvcmVGb2N1c09uQ2xvc2U6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sYWxsb3dDbGlja1Rocm91Z2g6e3R5cGU6Qm9vbGVhbn0sYWx3YXlzT25Ub3A6e3R5cGU6Qm9vbGVhbn0sc2Nyb2xsQWN0aW9uOnt0eXBlOlN0cmluZ30sX21hbmFnZXI6e3R5cGU6T2JqZWN0LHZhbHVlOmcwdH0sX2ZvY3VzZWRDaGlsZDp7dHlwZTpPYmplY3R9fSxsaXN0ZW5lcnM6eyJpcm9uLXJlc2l6ZSI6Il9vbklyb25SZXNpemUifSxvYnNlcnZlcnM6WyJfX3VwZGF0ZVNjcm9sbE9ic2VydmVycyhpc0F0dGFjaGVkLCBvcGVuZWQsIHNjcm9sbEFjdGlvbikiXSxnZXQgYmFja2Ryb3BFbGVtZW50KCl7cmV0dXJuIHRoaXMuX21hbmFnZXIuYmFja2Ryb3BFbGVtZW50fSxnZXQgX2ZvY3VzTm9kZSgpe3JldHVybiB0aGlzLl9mb2N1c2VkQ2hpbGR8fHp0KHRoaXMpLnF1ZXJ5U2VsZWN0b3IoIlthdXRvZm9jdXNdIil8fHRoaXN9LGdldCBfZm9jdXNhYmxlTm9kZXMoKXtyZXR1cm4gbTB0LmdldFRhYmJhYmxlTm9kZXModGhpcyl9LHJlYWR5OmZ1bmN0aW9uKCl7dGhpcy5fX2lzQW5pbWF0aW5nPSExLHRoaXMuX19zaG91bGRSZW1vdmVUYWJJbmRleD0hMSx0aGlzLl9fZmlyc3RGb2N1c2FibGVOb2RlPXRoaXMuX19sYXN0Rm9jdXNhYmxlTm9kZT1udWxsLHRoaXMuX19yYWZzPXt9LHRoaXMuX19yZXN0b3JlRm9jdXNOb2RlPW51bGwsdGhpcy5fX3Njcm9sbFRvcD10aGlzLl9fc2Nyb2xsTGVmdD1udWxsLHRoaXMuX19vbkNhcHR1cmVTY3JvbGw9dGhpcy5fX29uQ2FwdHVyZVNjcm9sbC5iaW5kKHRoaXMpLHRoaXMuX19yb290Tm9kZXM9bnVsbCx0aGlzLl9lbnN1cmVTZXR1cCgpfSxhdHRhY2hlZDpmdW5jdGlvbigpe3RoaXMub3BlbmVkJiZ0aGlzLl9vcGVuZWRDaGFuZ2VkKHRoaXMub3BlbmVkKSx0aGlzLl9vYnNlcnZlcj16dCh0aGlzKS5vYnNlcnZlTm9kZXModGhpcy5fb25Ob2Rlc0NoYW5nZSl9LGRldGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5fb2JzZXJ2ZXImJnp0KHRoaXMpLnVub2JzZXJ2ZU5vZGVzKHRoaXMuX29ic2VydmVyKSx0aGlzLl9vYnNlcnZlcj1udWxsO2Zvcih2YXIgZSBpbiB0aGlzLl9fcmFmcyl0aGlzLl9fcmFmc1tlXSE9PW51bGwmJmNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuX19yYWZzW2VdKTt0aGlzLl9fcmFmcz17fSx0aGlzLl9tYW5hZ2VyLnJlbW92ZU92ZXJsYXkodGhpcyksdGhpcy5fX2lzQW5pbWF0aW5nJiYodGhpcy5vcGVuZWQ/dGhpcy5fZmluaXNoUmVuZGVyT3BlbmVkKCk6KHRoaXMuX2FwcGx5Rm9jdXMoKSx0aGlzLl9maW5pc2hSZW5kZXJDbG9zZWQoKSkpfSx0b2dnbGU6ZnVuY3Rpb24oKXt0aGlzLl9zZXRDYW5jZWxlZCghMSksdGhpcy5vcGVuZWQ9IXRoaXMub3BlbmVkfSxvcGVuOmZ1bmN0aW9uKCl7dGhpcy5fc2V0Q2FuY2VsZWQoITEpLHRoaXMub3BlbmVkPSEwfSxjbG9zZTpmdW5jdGlvbigpe3RoaXMuX3NldENhbmNlbGVkKCExKSx0aGlzLm9wZW5lZD0hMX0sY2FuY2VsOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuZmlyZSgiaXJvbi1vdmVybGF5LWNhbmNlbGVkIixlLHtjYW5jZWxhYmxlOiEwfSk7dC5kZWZhdWx0UHJldmVudGVkfHwodGhpcy5fc2V0Q2FuY2VsZWQoITApLHRoaXMub3BlbmVkPSExKX0saW52YWxpZGF0ZVRhYmJhYmxlczpmdW5jdGlvbigpe3RoaXMuX19maXJzdEZvY3VzYWJsZU5vZGU9dGhpcy5fX2xhc3RGb2N1c2FibGVOb2RlPW51bGx9LF9lbnN1cmVTZXR1cDpmdW5jdGlvbigpe3RoaXMuX292ZXJsYXlTZXR1cHx8KHRoaXMuX292ZXJsYXlTZXR1cD0hMCx0aGlzLnN0eWxlLm91dGxpbmU9Im5vbmUiLHRoaXMuc3R5bGUuZGlzcGxheT0ibm9uZSIpfSxfb3BlbmVkQ2hhbmdlZDpmdW5jdGlvbihlKXtlP3RoaXMucmVtb3ZlQXR0cmlidXRlKCJhcmlhLWhpZGRlbiIpOnRoaXMuc2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIsInRydWUiKSx0aGlzLmlzQXR0YWNoZWQmJih0aGlzLl9faXNBbmltYXRpbmc9ITAsdGhpcy5fX2RlcmFmKCJfX29wZW5lZENoYW5nZWQiLHRoaXMuX19vcGVuZWRDaGFuZ2VkKSl9LF9jYW5jZWxlZENoYW5nZWQ6ZnVuY3Rpb24oKXt0aGlzLmNsb3NpbmdSZWFzb249dGhpcy5jbG9zaW5nUmVhc29ufHx7fSx0aGlzLmNsb3NpbmdSZWFzb24uY2FuY2VsZWQ9dGhpcy5jYW5jZWxlZH0sX3dpdGhCYWNrZHJvcENoYW5nZWQ6ZnVuY3Rpb24oKXt0aGlzLndpdGhCYWNrZHJvcCYmIXRoaXMuaGFzQXR0cmlidXRlKCJ0YWJpbmRleCIpPyh0aGlzLnNldEF0dHJpYnV0ZSgidGFiaW5kZXgiLCItMSIpLHRoaXMuX19zaG91bGRSZW1vdmVUYWJJbmRleD0hMCk6dGhpcy5fX3Nob3VsZFJlbW92ZVRhYkluZGV4JiYodGhpcy5yZW1vdmVBdHRyaWJ1dGUoInRhYmluZGV4IiksdGhpcy5fX3Nob3VsZFJlbW92ZVRhYkluZGV4PSExKSx0aGlzLm9wZW5lZCYmdGhpcy5pc0F0dGFjaGVkJiZ0aGlzLl9tYW5hZ2VyLnRyYWNrQmFja2Ryb3AoKX0sX3ByZXBhcmVSZW5kZXJPcGVuZWQ6ZnVuY3Rpb24oKXt0aGlzLl9fcmVzdG9yZUZvY3VzTm9kZT10aGlzLl9tYW5hZ2VyLmRlZXBBY3RpdmVFbGVtZW50LHRoaXMuX3ByZXBhcmVQb3NpdGlvbmluZygpLHRoaXMucmVmaXQoKSx0aGlzLl9maW5pc2hQb3NpdGlvbmluZygpLHRoaXMubm9BdXRvRm9jdXMmJmRvY3VtZW50LmFjdGl2ZUVsZW1lbnQ9PT10aGlzLl9mb2N1c05vZGUmJih0aGlzLl9mb2N1c05vZGUuYmx1cigpLHRoaXMuX19yZXN0b3JlRm9jdXNOb2RlLmZvY3VzKCkpfSxfcmVuZGVyT3BlbmVkOmZ1bmN0aW9uKCl7dGhpcy5fZmluaXNoUmVuZGVyT3BlbmVkKCl9LF9yZW5kZXJDbG9zZWQ6ZnVuY3Rpb24oKXt0aGlzLl9maW5pc2hSZW5kZXJDbG9zZWQoKX0sX2ZpbmlzaFJlbmRlck9wZW5lZDpmdW5jdGlvbigpe3RoaXMubm90aWZ5UmVzaXplKCksdGhpcy5fX2lzQW5pbWF0aW5nPSExLHRoaXMuZmlyZSgiaXJvbi1vdmVybGF5LW9wZW5lZCIpfSxfZmluaXNoUmVuZGVyQ2xvc2VkOmZ1bmN0aW9uKCl7dGhpcy5zdHlsZS5kaXNwbGF5PSJub25lIix0aGlzLnN0eWxlLnpJbmRleD0iIix0aGlzLm5vdGlmeVJlc2l6ZSgpLHRoaXMuX19pc0FuaW1hdGluZz0hMSx0aGlzLmZpcmUoImlyb24tb3ZlcmxheS1jbG9zZWQiLHRoaXMuY2xvc2luZ1JlYXNvbil9LF9wcmVwYXJlUG9zaXRpb25pbmc6ZnVuY3Rpb24oKXt0aGlzLnN0eWxlLnRyYW5zaXRpb249dGhpcy5zdHlsZS53ZWJraXRUcmFuc2l0aW9uPSJub25lIix0aGlzLnN0eWxlLnRyYW5zZm9ybT10aGlzLnN0eWxlLndlYmtpdFRyYW5zZm9ybT0ibm9uZSIsdGhpcy5zdHlsZS5kaXNwbGF5PSIifSxfZmluaXNoUG9zaXRpb25pbmc6ZnVuY3Rpb24oKXt0aGlzLnN0eWxlLmRpc3BsYXk9Im5vbmUiLHRoaXMuc2Nyb2xsVG9wPXRoaXMuc2Nyb2xsVG9wLHRoaXMuc3R5bGUudHJhbnNpdGlvbj10aGlzLnN0eWxlLndlYmtpdFRyYW5zaXRpb249IiIsdGhpcy5zdHlsZS50cmFuc2Zvcm09dGhpcy5zdHlsZS53ZWJraXRUcmFuc2Zvcm09IiIsdGhpcy5zdHlsZS5kaXNwbGF5PSIiLHRoaXMuc2Nyb2xsVG9wPXRoaXMuc2Nyb2xsVG9wfSxfYXBwbHlGb2N1czpmdW5jdGlvbigpe2lmKHRoaXMub3BlbmVkKXRoaXMubm9BdXRvRm9jdXN8fHRoaXMuX2ZvY3VzTm9kZS5mb2N1cygpO2Vsc2V7aWYodGhpcy5yZXN0b3JlRm9jdXNPbkNsb3NlJiZ0aGlzLl9fcmVzdG9yZUZvY3VzTm9kZSl7dmFyIGU9dGhpcy5fbWFuYWdlci5kZWVwQWN0aXZlRWxlbWVudDsoZT09PWRvY3VtZW50LmJvZHl8fHpiZSh0aGlzLGUpKSYmdGhpcy5fX3Jlc3RvcmVGb2N1c05vZGUuZm9jdXMoKX10aGlzLl9fcmVzdG9yZUZvY3VzTm9kZT1udWxsLHRoaXMuX2ZvY3VzTm9kZS5ibHVyKCksdGhpcy5fZm9jdXNlZENoaWxkPW51bGx9fSxfb25DYXB0dXJlQ2xpY2s6ZnVuY3Rpb24oZSl7dGhpcy5ub0NhbmNlbE9uT3V0c2lkZUNsaWNrfHx0aGlzLmNhbmNlbChlKX0sX29uQ2FwdHVyZUZvY3VzOmZ1bmN0aW9uKGUpe2lmKCEhdGhpcy53aXRoQmFja2Ryb3Ape3ZhciB0PXp0KGUpLnBhdGg7dC5pbmRleE9mKHRoaXMpPT09LTE/KGUuc3RvcFByb3BhZ2F0aW9uKCksdGhpcy5fYXBwbHlGb2N1cygpKTp0aGlzLl9mb2N1c2VkQ2hpbGQ9dFswXX19LF9vbkNhcHR1cmVFc2M6ZnVuY3Rpb24oZSl7dGhpcy5ub0NhbmNlbE9uRXNjS2V5fHx0aGlzLmNhbmNlbChlKX0sX29uQ2FwdHVyZVRhYjpmdW5jdGlvbihlKXtpZighIXRoaXMud2l0aEJhY2tkcm9wKXt0aGlzLl9fZW5zdXJlRmlyc3RMYXN0Rm9jdXNhYmxlcygpO3ZhciB0PWUuc2hpZnRLZXkscj10P3RoaXMuX19maXJzdEZvY3VzYWJsZU5vZGU6dGhpcy5fX2xhc3RGb2N1c2FibGVOb2RlLG49dD90aGlzLl9fbGFzdEZvY3VzYWJsZU5vZGU6dGhpcy5fX2ZpcnN0Rm9jdXNhYmxlTm9kZSxpPSExO2lmKHI9PT1uKWk9ITA7ZWxzZXt2YXIgbz10aGlzLl9tYW5hZ2VyLmRlZXBBY3RpdmVFbGVtZW50O2k9bz09PXJ8fG89PT10aGlzfWkmJihlLnByZXZlbnREZWZhdWx0KCksdGhpcy5fZm9jdXNlZENoaWxkPW4sdGhpcy5fYXBwbHlGb2N1cygpKX19LF9vbklyb25SZXNpemU6ZnVuY3Rpb24oKXt0aGlzLm9wZW5lZCYmIXRoaXMuX19pc0FuaW1hdGluZyYmdGhpcy5fX2RlcmFmKCJyZWZpdCIsdGhpcy5yZWZpdCl9LF9vbk5vZGVzQ2hhbmdlOmZ1bmN0aW9uKCl7dGhpcy5vcGVuZWQmJiF0aGlzLl9faXNBbmltYXRpbmcmJih0aGlzLmludmFsaWRhdGVUYWJiYWJsZXMoKSx0aGlzLm5vdGlmeVJlc2l6ZSgpKX0sX19lbnN1cmVGaXJzdExhc3RGb2N1c2FibGVzOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5fZm9jdXNhYmxlTm9kZXM7dGhpcy5fX2ZpcnN0Rm9jdXNhYmxlTm9kZT1lWzBdLHRoaXMuX19sYXN0Rm9jdXNhYmxlTm9kZT1lW2UubGVuZ3RoLTFdfSxfX29wZW5lZENoYW5nZWQ6ZnVuY3Rpb24oKXt0aGlzLm9wZW5lZD8odGhpcy5fcHJlcGFyZVJlbmRlck9wZW5lZCgpLHRoaXMuX21hbmFnZXIuYWRkT3ZlcmxheSh0aGlzKSx0aGlzLl9hcHBseUZvY3VzKCksdGhpcy5fcmVuZGVyT3BlbmVkKCkpOih0aGlzLl9tYW5hZ2VyLnJlbW92ZU92ZXJsYXkodGhpcyksdGhpcy5fYXBwbHlGb2N1cygpLHRoaXMuX3JlbmRlckNsb3NlZCgpKX0sX19kZXJhZjpmdW5jdGlvbihlLHQpe3ZhciByPXRoaXMuX19yYWZzO3JbZV0hPT1udWxsJiZjYW5jZWxBbmltYXRpb25GcmFtZShyW2VdKSxyW2VdPXJlcXVlc3RBbmltYXRpb25GcmFtZShmdW5jdGlvbigpe3JbZV09bnVsbCx0LmNhbGwodGhpcyl9LmJpbmQodGhpcykpfSxfX3VwZGF0ZVNjcm9sbE9ic2VydmVyczpmdW5jdGlvbihlLHQscil7IWV8fCF0fHwhdGhpcy5fX2lzVmFsaWRTY3JvbGxBY3Rpb24ocik/KHYwdCh0aGlzKSx0aGlzLl9fcmVtb3ZlU2Nyb2xsTGlzdGVuZXJzKCkpOihyPT09ImxvY2siJiYodGhpcy5fX3NhdmVTY3JvbGxQb3NpdGlvbigpLHkwdCh0aGlzKSksdGhpcy5fX2FkZFNjcm9sbExpc3RlbmVycygpKX0sX19hZGRTY3JvbGxMaXN0ZW5lcnM6ZnVuY3Rpb24oKXtpZighdGhpcy5fX3Jvb3ROb2Rlcyl7aWYodGhpcy5fX3Jvb3ROb2Rlcz1bXSxjXylmb3IodmFyIGU9dGhpcztlOyllLm5vZGVUeXBlPT09Tm9kZS5ET0NVTUVOVF9GUkFHTUVOVF9OT0RFJiZlLmhvc3QmJnRoaXMuX19yb290Tm9kZXMucHVzaChlKSxlPWUuaG9zdHx8ZS5hc3NpZ25lZFNsb3R8fGUucGFyZW50Tm9kZTt0aGlzLl9fcm9vdE5vZGVzLnB1c2goZG9jdW1lbnQpfXRoaXMuX19yb290Tm9kZXMuZm9yRWFjaChmdW5jdGlvbih0KXt0LmFkZEV2ZW50TGlzdGVuZXIoInNjcm9sbCIsdGhpcy5fX29uQ2FwdHVyZVNjcm9sbCx7Y2FwdHVyZTohMCxwYXNzaXZlOiEwfSl9LHRoaXMpfSxfX3JlbW92ZVNjcm9sbExpc3RlbmVyczpmdW5jdGlvbigpe3RoaXMuX19yb290Tm9kZXMmJnRoaXMuX19yb290Tm9kZXMuZm9yRWFjaChmdW5jdGlvbihlKXtlLnJlbW92ZUV2ZW50TGlzdGVuZXIoInNjcm9sbCIsdGhpcy5fX29uQ2FwdHVyZVNjcm9sbCx7Y2FwdHVyZTohMCxwYXNzaXZlOiEwfSl9LHRoaXMpLHRoaXMuaXNBdHRhY2hlZHx8KHRoaXMuX19yb290Tm9kZXM9bnVsbCl9LF9faXNWYWxpZFNjcm9sbEFjdGlvbjpmdW5jdGlvbihlKXtyZXR1cm4gZT09PSJsb2NrInx8ZT09PSJyZWZpdCJ8fGU9PT0iY2FuY2VsIn0sX19vbkNhcHR1cmVTY3JvbGw6ZnVuY3Rpb24oZSl7aWYoIXRoaXMuX19pc0FuaW1hdGluZyYmISh6dChlKS5wYXRoLmluZGV4T2YodGhpcyk+PTApKXN3aXRjaCh0aGlzLnNjcm9sbEFjdGlvbil7Y2FzZSJsb2NrIjp0aGlzLl9fcmVzdG9yZVNjcm9sbFBvc2l0aW9uKCk7YnJlYWs7Y2FzZSJyZWZpdCI6dGhpcy5fX2RlcmFmKCJyZWZpdCIsdGhpcy5yZWZpdCk7YnJlYWs7Y2FzZSJjYW5jZWwiOnRoaXMuY2FuY2VsKGUpO2JyZWFrfX0sX19zYXZlU2Nyb2xsUG9zaXRpb246ZnVuY3Rpb24oKXtkb2N1bWVudC5zY3JvbGxpbmdFbGVtZW50Pyh0aGlzLl9fc2Nyb2xsVG9wPWRvY3VtZW50LnNjcm9sbGluZ0VsZW1lbnQuc2Nyb2xsVG9wLHRoaXMuX19zY3JvbGxMZWZ0PWRvY3VtZW50LnNjcm9sbGluZ0VsZW1lbnQuc2Nyb2xsTGVmdCk6KHRoaXMuX19zY3JvbGxUb3A9TWF0aC5tYXgoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbFRvcCxkb2N1bWVudC5ib2R5LnNjcm9sbFRvcCksdGhpcy5fX3Njcm9sbExlZnQ9TWF0aC5tYXgoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbExlZnQsZG9jdW1lbnQuYm9keS5zY3JvbGxMZWZ0KSl9LF9fcmVzdG9yZVNjcm9sbFBvc2l0aW9uOmZ1bmN0aW9uKCl7ZG9jdW1lbnQuc2Nyb2xsaW5nRWxlbWVudD8oZG9jdW1lbnQuc2Nyb2xsaW5nRWxlbWVudC5zY3JvbGxUb3A9dGhpcy5fX3Njcm9sbFRvcCxkb2N1bWVudC5zY3JvbGxpbmdFbGVtZW50LnNjcm9sbExlZnQ9dGhpcy5fX3Njcm9sbExlZnQpOihkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsVG9wPWRvY3VtZW50LmJvZHkuc2Nyb2xsVG9wPXRoaXMuX19zY3JvbGxUb3AsZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbExlZnQ9ZG9jdW1lbnQuYm9keS5zY3JvbGxMZWZ0PXRoaXMuX19zY3JvbGxMZWZ0KX19LE9iZT1lPT5lLmFzc2lnbmVkU2xvdHx8ZS5wYXJlbnROb2RlfHxlLmhvc3QsemJlPShlLHQpPT57Zm9yKGxldCByPXQ7cjtyPU9iZShyKSlpZihyPT09ZSlyZXR1cm4hMDtyZXR1cm4hMX0sS3g9W2QwdCxKcyxQbV07dmFyIHZXPXtob3N0QXR0cmlidXRlczp7cm9sZToiZGlhbG9nIix0YWJpbmRleDoiLTEifSxwcm9wZXJ0aWVzOnttb2RhbDp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxfX3JlYWRpZWQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX19LG9ic2VydmVyczpbIl9tb2RhbENoYW5nZWQobW9kYWwsIF9fcmVhZGllZCkiXSxsaXN0ZW5lcnM6e3RhcDoiX29uRGlhbG9nQ2xpY2sifSxyZWFkeTpmdW5jdGlvbigpe3RoaXMuX19wcmV2Tm9DYW5jZWxPbk91dHNpZGVDbGljaz10aGlzLm5vQ2FuY2VsT25PdXRzaWRlQ2xpY2ssdGhpcy5fX3ByZXZOb0NhbmNlbE9uRXNjS2V5PXRoaXMubm9DYW5jZWxPbkVzY0tleSx0aGlzLl9fcHJldldpdGhCYWNrZHJvcD10aGlzLndpdGhCYWNrZHJvcCx0aGlzLl9fcmVhZGllZD0hMH0sX21vZGFsQ2hhbmdlZDpmdW5jdGlvbihlLHQpeyF0fHwoZT8odGhpcy5fX3ByZXZOb0NhbmNlbE9uT3V0c2lkZUNsaWNrPXRoaXMubm9DYW5jZWxPbk91dHNpZGVDbGljayx0aGlzLl9fcHJldk5vQ2FuY2VsT25Fc2NLZXk9dGhpcy5ub0NhbmNlbE9uRXNjS2V5LHRoaXMuX19wcmV2V2l0aEJhY2tkcm9wPXRoaXMud2l0aEJhY2tkcm9wLHRoaXMubm9DYW5jZWxPbk91dHNpZGVDbGljaz0hMCx0aGlzLm5vQ2FuY2VsT25Fc2NLZXk9ITAsdGhpcy53aXRoQmFja2Ryb3A9ITApOih0aGlzLm5vQ2FuY2VsT25PdXRzaWRlQ2xpY2s9dGhpcy5ub0NhbmNlbE9uT3V0c2lkZUNsaWNrJiZ0aGlzLl9fcHJldk5vQ2FuY2VsT25PdXRzaWRlQ2xpY2ssdGhpcy5ub0NhbmNlbE9uRXNjS2V5PXRoaXMubm9DYW5jZWxPbkVzY0tleSYmdGhpcy5fX3ByZXZOb0NhbmNlbE9uRXNjS2V5LHRoaXMud2l0aEJhY2tkcm9wPXRoaXMud2l0aEJhY2tkcm9wJiZ0aGlzLl9fcHJldldpdGhCYWNrZHJvcCkpfSxfdXBkYXRlQ2xvc2luZ1JlYXNvbkNvbmZpcm1lZDpmdW5jdGlvbihlKXt0aGlzLmNsb3NpbmdSZWFzb249dGhpcy5jbG9zaW5nUmVhc29ufHx7fSx0aGlzLmNsb3NpbmdSZWFzb24uY29uZmlybWVkPWV9LF9vbkRpYWxvZ0NsaWNrOmZ1bmN0aW9uKGUpe2Zvcih2YXIgdD16dChlKS5wYXRoLHI9MCxuPXQuaW5kZXhPZih0aGlzKTtyPG47cisrKXt2YXIgaT10W3JdO2lmKGkuaGFzQXR0cmlidXRlJiYoaS5oYXNBdHRyaWJ1dGUoImRpYWxvZy1kaXNtaXNzIil8fGkuaGFzQXR0cmlidXRlKCJkaWFsb2ctY29uZmlybSIpKSl7dGhpcy5fdXBkYXRlQ2xvc2luZ1JlYXNvbkNvbmZpcm1lZChpLmhhc0F0dHJpYnV0ZSgiZGlhbG9nLWNvbmZpcm0iKSksdGhpcy5jbG9zZSgpLGUuc3RvcFByb3BhZ2F0aW9uKCk7YnJlYWt9fX19LHcwdD1bS3gsdlddO1l0KHtfdGVtcGxhdGU6UWAKICAgIDxzdHlsZSBpbmNsdWRlPSJwYXBlci1kaWFsb2ctc2hhcmVkLXN0eWxlcyI+PC9zdHlsZT4KICAgIDxzbG90Pjwvc2xvdD4KYCxpczoicGFwZXItZGlhbG9nIixiZWhhdmlvcnM6W3cwdCx4OV0sbGlzdGVuZXJzOnsibmVvbi1hbmltYXRpb24tZmluaXNoIjoiX29uTmVvbkFuaW1hdGlvbkZpbmlzaCJ9LF9yZW5kZXJPcGVuZWQ6ZnVuY3Rpb24oKXt0aGlzLmNhbmNlbEFuaW1hdGlvbigpLHRoaXMucGxheUFuaW1hdGlvbigiZW50cnkiKX0sX3JlbmRlckNsb3NlZDpmdW5jdGlvbigpe3RoaXMuY2FuY2VsQW5pbWF0aW9uKCksdGhpcy5wbGF5QW5pbWF0aW9uKCJleGl0Iil9LF9vbk5lb25BbmltYXRpb25GaW5pc2g6ZnVuY3Rpb24oKXt0aGlzLm9wZW5lZD90aGlzLl9maW5pc2hSZW5kZXJPcGVuZWQoKTp0aGlzLl9maW5pc2hSZW5kZXJDbG9zZWQoKX19KTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LXJlbGF0aXZlOwogICAgICB9CgogICAgICA6aG9zdCguaXMtc2Nyb2xsZWQ6bm90KDpmaXJzdC1jaGlsZCkpOjpiZWZvcmUgewogICAgICAgIGNvbnRlbnQ6ICcnOwogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICB0b3A6IDA7CiAgICAgICAgbGVmdDogMDsKICAgICAgICByaWdodDogMDsKICAgICAgICBoZWlnaHQ6IDFweDsKICAgICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1kaXZpZGVyLWNvbG9yKTsKICAgICAgfQoKICAgICAgOmhvc3QoLmNhbi1zY3JvbGw6bm90KC5zY3JvbGxlZC10by1ib3R0b20pOm5vdCg6bGFzdC1jaGlsZCkpOjphZnRlciB7CiAgICAgICAgY29udGVudDogJyc7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIGJvdHRvbTogMDsKICAgICAgICBsZWZ0OiAwOwogICAgICAgIHJpZ2h0OiAwOwogICAgICAgIGhlaWdodDogMXB4OwogICAgICAgIGJhY2tncm91bmQ6IHZhcigtLWRpdmlkZXItY29sb3IpOwogICAgICB9CgogICAgICAuc2Nyb2xsYWJsZSB7CiAgICAgICAgcGFkZGluZzogMCAyNHB4OwoKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtc2Nyb2xsOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWRpYWxvZy1zY3JvbGxhYmxlOwogICAgICB9CgogICAgICAuZml0IHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtZml0OwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxkaXYgaWQ9InNjcm9sbGFibGUiIGNsYXNzPSJzY3JvbGxhYmxlIiBvbi1zY3JvbGw9InVwZGF0ZVNjcm9sbFN0YXRlIj4KICAgICAgPHNsb3Q+PC9zbG90PgogICAgPC9kaXY+CmAsaXM6InBhcGVyLWRpYWxvZy1zY3JvbGxhYmxlIixwcm9wZXJ0aWVzOntkaWFsb2dFbGVtZW50Ont0eXBlOk9iamVjdH19LGdldCBzY3JvbGxUYXJnZXQoKXtyZXR1cm4gdGhpcy4kLnNjcm9sbGFibGV9LHJlYWR5OmZ1bmN0aW9uKCl7dGhpcy5fZW5zdXJlVGFyZ2V0KCksdGhpcy5jbGFzc0xpc3QuYWRkKCJuby1wYWRkaW5nIil9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5fZW5zdXJlVGFyZ2V0KCkscmVxdWVzdEFuaW1hdGlvbkZyYW1lKHRoaXMudXBkYXRlU2Nyb2xsU3RhdGUuYmluZCh0aGlzKSl9LHVwZGF0ZVNjcm9sbFN0YXRlOmZ1bmN0aW9uKCl7dGhpcy50b2dnbGVDbGFzcygiaXMtc2Nyb2xsZWQiLHRoaXMuc2Nyb2xsVGFyZ2V0LnNjcm9sbFRvcD4wKSx0aGlzLnRvZ2dsZUNsYXNzKCJjYW4tc2Nyb2xsIix0aGlzLnNjcm9sbFRhcmdldC5vZmZzZXRIZWlnaHQ8dGhpcy5zY3JvbGxUYXJnZXQuc2Nyb2xsSGVpZ2h0KSx0aGlzLnRvZ2dsZUNsYXNzKCJzY3JvbGxlZC10by1ib3R0b20iLHRoaXMuc2Nyb2xsVGFyZ2V0LnNjcm9sbFRvcCt0aGlzLnNjcm9sbFRhcmdldC5vZmZzZXRIZWlnaHQ+PXRoaXMuc2Nyb2xsVGFyZ2V0LnNjcm9sbEhlaWdodCl9LF9lbnN1cmVUYXJnZXQ6ZnVuY3Rpb24oKXt0aGlzLmRpYWxvZ0VsZW1lbnQ9dGhpcy5kaWFsb2dFbGVtZW50fHx0aGlzLnBhcmVudEVsZW1lbnQsdGhpcy5kaWFsb2dFbGVtZW50JiZ0aGlzLmRpYWxvZ0VsZW1lbnQuYmVoYXZpb3JzJiZ0aGlzLmRpYWxvZ0VsZW1lbnQuYmVoYXZpb3JzLmluZGV4T2YodlcpPj0wPyh0aGlzLmRpYWxvZ0VsZW1lbnQuc2l6aW5nVGFyZ2V0PXRoaXMuc2Nyb2xsVGFyZ2V0LHRoaXMuc2Nyb2xsVGFyZ2V0LmNsYXNzTGlzdC5yZW1vdmUoImZpdCIpKTp0aGlzLmRpYWxvZ0VsZW1lbnQmJnRoaXMuc2Nyb2xsVGFyZ2V0LmNsYXNzTGlzdC5hZGQoImZpdCIpfX0pO3ZhciBlYz1ZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgICAgcG9zaXRpb246IGZpeGVkOwogICAgICAgIGNsaXA6IHJlY3QoMHB4LDBweCwwcHgsMHB4KTsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxkaXYgYXJpYS1saXZlJD0iW1ttb2RlXV0iPltbX3RleHRdXTwvZGl2PgpgLGlzOiJpcm9uLWExMXktYW5ub3VuY2VyIixwcm9wZXJ0aWVzOnttb2RlOnt0eXBlOlN0cmluZyx2YWx1ZToicG9saXRlIn0sdGltZW91dDp7dHlwZTpOdW1iZXIsdmFsdWU6MTUwfSxfdGV4dDp7dHlwZTpTdHJpbmcsdmFsdWU6IiJ9fSxjcmVhdGVkOmZ1bmN0aW9uKCl7ZWMuaW5zdGFuY2V8fChlYy5pbnN0YW5jZT10aGlzKSxkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCJpcm9uLWFubm91bmNlIix0aGlzLl9vbklyb25Bbm5vdW5jZS5iaW5kKHRoaXMpKX0sYW5ub3VuY2U6ZnVuY3Rpb24oZSl7dGhpcy5fdGV4dD0iIix0aGlzLmFzeW5jKGZ1bmN0aW9uKCl7dGhpcy5fdGV4dD1lfSx0aGlzLnRpbWVvdXQpfSxfb25Jcm9uQW5ub3VuY2U6ZnVuY3Rpb24oZSl7ZS5kZXRhaWwmJmUuZGV0YWlsLnRleHQmJnRoaXMuYW5ub3VuY2UoZS5kZXRhaWwudGV4dCl9fSk7ZWMuaW5zdGFuY2U9bnVsbDtlYy5yZXF1ZXN0QXZhaWxhYmlsaXR5PWZ1bmN0aW9uKCl7ZWMuaW5zdGFuY2V8fChlYy5pbnN0YW5jZT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJpcm9uLWExMXktYW5ub3VuY2VyIikpLGRvY3VtZW50LmJvZHk/ZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChlYy5pbnN0YW5jZSk6ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigibG9hZCIsZnVuY3Rpb24oKXtkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGVjLmluc3RhbmNlKX0pfTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8c2xvdCBpZD0iY29udGVudCI+PC9zbG90PgpgLGlzOiJpcm9uLWlucHV0IixiZWhhdmlvcnM6W1RoXSxwcm9wZXJ0aWVzOntiaW5kVmFsdWU6e3R5cGU6U3RyaW5nLHZhbHVlOiIifSx2YWx1ZTp7dHlwZTpTdHJpbmcsY29tcHV0ZWQ6Il9jb21wdXRlVmFsdWUoYmluZFZhbHVlKSJ9LGFsbG93ZWRQYXR0ZXJuOnt0eXBlOlN0cmluZ30sYXV0b1ZhbGlkYXRlOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LF9pbnB1dEVsZW1lbnQ6T2JqZWN0fSxvYnNlcnZlcnM6WyJfYmluZFZhbHVlQ2hhbmdlZChiaW5kVmFsdWUsIF9pbnB1dEVsZW1lbnQpIl0sbGlzdGVuZXJzOntpbnB1dDoiX29uSW5wdXQiLGtleXByZXNzOiJfb25LZXlwcmVzcyJ9LGNyZWF0ZWQ6ZnVuY3Rpb24oKXtlYy5yZXF1ZXN0QXZhaWxhYmlsaXR5KCksdGhpcy5fcHJldmlvdXNWYWxpZElucHV0PSIiLHRoaXMuX3BhdHRlcm5BbHJlYWR5Q2hlY2tlZD0hMX0sYXR0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLl9vYnNlcnZlcj16dCh0aGlzKS5vYnNlcnZlTm9kZXMoZnVuY3Rpb24oZSl7dGhpcy5faW5pdFNsb3R0ZWRJbnB1dCgpfS5iaW5kKHRoaXMpKX0sZGV0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLl9vYnNlcnZlciYmKHp0KHRoaXMpLnVub2JzZXJ2ZU5vZGVzKHRoaXMuX29ic2VydmVyKSx0aGlzLl9vYnNlcnZlcj1udWxsKX0sZ2V0IGlucHV0RWxlbWVudCgpe3JldHVybiB0aGlzLl9pbnB1dEVsZW1lbnR9LF9pbml0U2xvdHRlZElucHV0OmZ1bmN0aW9uKCl7dGhpcy5faW5wdXRFbGVtZW50PXRoaXMuZ2V0RWZmZWN0aXZlQ2hpbGRyZW4oKVswXSx0aGlzLmlucHV0RWxlbWVudCYmdGhpcy5pbnB1dEVsZW1lbnQudmFsdWUmJih0aGlzLmJpbmRWYWx1ZT10aGlzLmlucHV0RWxlbWVudC52YWx1ZSksdGhpcy5maXJlKCJpcm9uLWlucHV0LXJlYWR5Iil9LGdldCBfcGF0dGVyblJlZ0V4cCgpe3ZhciBlO2lmKHRoaXMuYWxsb3dlZFBhdHRlcm4pZT1uZXcgUmVnRXhwKHRoaXMuYWxsb3dlZFBhdHRlcm4pO2Vsc2Ugc3dpdGNoKHRoaXMuaW5wdXRFbGVtZW50LnR5cGUpe2Nhc2UibnVtYmVyIjplPS9bMC05LixlLV0vO2JyZWFrfXJldHVybiBlfSxfYmluZFZhbHVlQ2hhbmdlZDpmdW5jdGlvbihlLHQpeyF0fHwoZT09PXZvaWQgMD90LnZhbHVlPW51bGw6ZSE9PXQudmFsdWUmJih0aGlzLmlucHV0RWxlbWVudC52YWx1ZT1lKSx0aGlzLmF1dG9WYWxpZGF0ZSYmdGhpcy52YWxpZGF0ZSgpLHRoaXMuZmlyZSgiYmluZC12YWx1ZS1jaGFuZ2VkIix7dmFsdWU6ZX0pKX0sX29uSW5wdXQ6ZnVuY3Rpb24oKXtpZih0aGlzLmFsbG93ZWRQYXR0ZXJuJiYhdGhpcy5fcGF0dGVybkFscmVhZHlDaGVja2VkKXt2YXIgZT10aGlzLl9jaGVja1BhdHRlcm5WYWxpZGl0eSgpO2V8fCh0aGlzLl9hbm5vdW5jZUludmFsaWRDaGFyYWN0ZXIoIkludmFsaWQgc3RyaW5nIG9mIGNoYXJhY3RlcnMgbm90IGVudGVyZWQuIiksdGhpcy5pbnB1dEVsZW1lbnQudmFsdWU9dGhpcy5fcHJldmlvdXNWYWxpZElucHV0KX10aGlzLmJpbmRWYWx1ZT10aGlzLl9wcmV2aW91c1ZhbGlkSW5wdXQ9dGhpcy5pbnB1dEVsZW1lbnQudmFsdWUsdGhpcy5fcGF0dGVybkFscmVhZHlDaGVja2VkPSExfSxfaXNQcmludGFibGU6ZnVuY3Rpb24oZSl7dmFyIHQ9ZS5rZXlDb2RlPT04fHxlLmtleUNvZGU9PTl8fGUua2V5Q29kZT09MTN8fGUua2V5Q29kZT09Mjcscj1lLmtleUNvZGU9PTE5fHxlLmtleUNvZGU9PTIwfHxlLmtleUNvZGU9PTQ1fHxlLmtleUNvZGU9PTQ2fHxlLmtleUNvZGU9PTE0NHx8ZS5rZXlDb2RlPT0xNDV8fGUua2V5Q29kZT4zMiYmZS5rZXlDb2RlPDQxfHxlLmtleUNvZGU+MTExJiZlLmtleUNvZGU8MTI0O3JldHVybiF0JiYhKGUuY2hhckNvZGU9PTAmJnIpfSxfb25LZXlwcmVzczpmdW5jdGlvbihlKXtpZighKCF0aGlzLmFsbG93ZWRQYXR0ZXJuJiZ0aGlzLmlucHV0RWxlbWVudC50eXBlIT09Im51bWJlciIpKXt2YXIgdD10aGlzLl9wYXR0ZXJuUmVnRXhwO2lmKCEhdCYmIShlLm1ldGFLZXl8fGUuY3RybEtleXx8ZS5hbHRLZXkpKXt0aGlzLl9wYXR0ZXJuQWxyZWFkeUNoZWNrZWQ9ITA7dmFyIHI9U3RyaW5nLmZyb21DaGFyQ29kZShlLmNoYXJDb2RlKTt0aGlzLl9pc1ByaW50YWJsZShlKSYmIXQudGVzdChyKSYmKGUucHJldmVudERlZmF1bHQoKSx0aGlzLl9hbm5vdW5jZUludmFsaWRDaGFyYWN0ZXIoIkludmFsaWQgY2hhcmFjdGVyICIrcisiIG5vdCBlbnRlcmVkLiIpKX19fSxfY2hlY2tQYXR0ZXJuVmFsaWRpdHk6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLl9wYXR0ZXJuUmVnRXhwO2lmKCFlKXJldHVybiEwO2Zvcih2YXIgdD0wO3Q8dGhpcy5pbnB1dEVsZW1lbnQudmFsdWUubGVuZ3RoO3QrKylpZighZS50ZXN0KHRoaXMuaW5wdXRFbGVtZW50LnZhbHVlW3RdKSlyZXR1cm4hMTtyZXR1cm4hMH0sdmFsaWRhdGU6ZnVuY3Rpb24oKXtpZighdGhpcy5pbnB1dEVsZW1lbnQpcmV0dXJuIHRoaXMuaW52YWxpZD0hMSwhMDt2YXIgZT10aGlzLmlucHV0RWxlbWVudC5jaGVja1ZhbGlkaXR5KCk7cmV0dXJuIGUmJih0aGlzLnJlcXVpcmVkJiZ0aGlzLmJpbmRWYWx1ZT09PSIiP2U9ITE6dGhpcy5oYXNWYWxpZGF0b3IoKSYmKGU9VGgudmFsaWRhdGUuY2FsbCh0aGlzLHRoaXMuYmluZFZhbHVlKSkpLHRoaXMuaW52YWxpZD0hZSx0aGlzLmZpcmUoImlyb24taW5wdXQtdmFsaWRhdGUiKSxlfSxfYW5ub3VuY2VJbnZhbGlkQ2hhcmFjdGVyOmZ1bmN0aW9uKGUpe3RoaXMuZmlyZSgiaXJvbi1hbm5vdW5jZSIse3RleHQ6ZX0pfSxfY29tcHV0ZVZhbHVlOmZ1bmN0aW9uKGUpe3JldHVybiBlfX0pO3ZhciBUOT17YXR0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLmZpcmUoImFkZG9uLWF0dGFjaGVkIil9LHVwZGF0ZTpmdW5jdGlvbihlKXt9fTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgICAgZmxvYXQ6IHJpZ2h0OwoKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNhcHRpb247CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtY2hhci1jb3VudGVyOwogICAgICB9CgogICAgICA6aG9zdChbaGlkZGVuXSkgewogICAgICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICAgICAgfQoKICAgICAgOmhvc3QoOmRpcihydGwpKSB7CiAgICAgICAgZmxvYXQ6IGxlZnQ7CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPHNwYW4+W1tfY2hhckNvdW50ZXJTdHJdXTwvc3Bhbj4KYCxpczoicGFwZXItaW5wdXQtY2hhci1jb3VudGVyIixiZWhhdmlvcnM6W1Q5XSxwcm9wZXJ0aWVzOntfY2hhckNvdW50ZXJTdHI6e3R5cGU6U3RyaW5nLHZhbHVlOiIwIn19LHVwZGF0ZTpmdW5jdGlvbihlKXtpZighIWUuaW5wdXRFbGVtZW50KXtlLnZhbHVlPWUudmFsdWV8fCIiO3ZhciB0PWUudmFsdWUudG9TdHJpbmcoKS5sZW5ndGgudG9TdHJpbmcoKTtlLmlucHV0RWxlbWVudC5oYXNBdHRyaWJ1dGUoIm1heGxlbmd0aCIpJiYodCs9Ii8iK2UuaW5wdXRFbGVtZW50LmdldEF0dHJpYnV0ZSgibWF4bGVuZ3RoIikpLHRoaXMuX2NoYXJDb3VudGVyU3RyPXR9fX0pO3ZhciBTMHQ9UWAKPGN1c3RvbS1zdHlsZT4KICA8c3R5bGUgaXM9ImN1c3RvbS1zdHlsZSI+CiAgICBodG1sIHsKICAgICAgLS1wYXBlci1pbnB1dC1jb250YWluZXItc2hhcmVkLWlucHV0LXN0eWxlOiB7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOyAvKiB0byBtYWtlIGEgc3RhY2tpbmcgY29udGV4dCAqLwogICAgICAgIG91dGxpbmU6IG5vbmU7CiAgICAgICAgYm94LXNoYWRvdzogbm9uZTsKICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgIG1hcmdpbjogMDsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBtYXgtd2lkdGg6IDEwMCU7CiAgICAgICAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7CiAgICAgICAgYm9yZGVyOiBub25lOwogICAgICAgIGNvbG9yOiB2YXIoLS1wYXBlci1pbnB1dC1jb250YWluZXItaW5wdXQtY29sb3IsIHZhcigtLXByaW1hcnktdGV4dC1jb2xvcikpOwogICAgICAgIC13ZWJraXQtYXBwZWFyYW5jZTogbm9uZTsKICAgICAgICB0ZXh0LWFsaWduOiBpbmhlcml0OwogICAgICAgIHZlcnRpY2FsLWFsaWduOiB2YXIoLS1wYXBlci1pbnB1dC1jb250YWluZXItaW5wdXQtYWxpZ24sIGJvdHRvbSk7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtc3ViaGVhZDsKICAgICAgfTsKICAgIH0KICA8L3N0eWxlPgo8L2N1c3RvbS1zdHlsZT4KYDtTMHQuc2V0QXR0cmlidXRlKCJzdHlsZSIsImRpc3BsYXk6IG5vbmU7Iik7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChTMHQuY29udGVudCk7WXQoe190ZW1wbGF0ZTpRYAogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgcGFkZGluZzogOHB4IDA7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtY29udGFpbmVyOwogICAgICB9CgogICAgICA6aG9zdChbaW5saW5lXSkgewogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgfQoKICAgICAgOmhvc3QoW2Rpc2FibGVkXSkgewogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICAgIG9wYWNpdHk6IDAuMzM7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1kaXNhYmxlZDsKICAgICAgfQoKICAgICAgOmhvc3QoW2hpZGRlbl0pIHsKICAgICAgICBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7CiAgICAgIH0KCiAgICAgIFtoaWRkZW5dIHsKICAgICAgICBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7CiAgICAgIH0KCiAgICAgIC5mbG9hdGVkLWxhYmVsLXBsYWNlaG9sZGVyIHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNhcHRpb247CiAgICAgIH0KCiAgICAgIC51bmRlcmxpbmUgewogICAgICAgIGhlaWdodDogMnB4OwogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgfQoKICAgICAgLmZvY3VzZWQtbGluZSB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZpdDsKICAgICAgICBib3JkZXItYm90dG9tOiAycHggc29saWQgdmFyKC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWZvY3VzLWNvbG9yLCB2YXIoLS1wcmltYXJ5LWNvbG9yKSk7CgogICAgICAgIC13ZWJraXQtdHJhbnNmb3JtLW9yaWdpbjogY2VudGVyIGNlbnRlcjsKICAgICAgICB0cmFuc2Zvcm0tb3JpZ2luOiBjZW50ZXIgY2VudGVyOwogICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiBzY2FsZTNkKDAsMSwxKTsKICAgICAgICB0cmFuc2Zvcm06IHNjYWxlM2QoMCwxLDEpOwoKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1jb250YWluZXItdW5kZXJsaW5lLWZvY3VzOwogICAgICB9CgogICAgICAudW5kZXJsaW5lLmlzLWhpZ2hsaWdodGVkIC5mb2N1c2VkLWxpbmUgewogICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiBub25lOwogICAgICAgIHRyYW5zZm9ybTogbm9uZTsKICAgICAgICAtd2Via2l0LXRyYW5zaXRpb246IC13ZWJraXQtdHJhbnNmb3JtIDAuMjVzOwogICAgICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAwLjI1czsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItdHJhbnNpdGlvbi1lYXNpbmc7CiAgICAgIH0KCiAgICAgIC51bmRlcmxpbmUuaXMtaW52YWxpZCAuZm9jdXNlZC1saW5lIHsKICAgICAgICBib3JkZXItY29sb3I6IHZhcigtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1pbnZhbGlkLWNvbG9yLCB2YXIoLS1lcnJvci1jb2xvcikpOwogICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiBub25lOwogICAgICAgIHRyYW5zZm9ybTogbm9uZTsKICAgICAgICAtd2Via2l0LXRyYW5zaXRpb246IC13ZWJraXQtdHJhbnNmb3JtIDAuMjVzOwogICAgICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAwLjI1czsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItdHJhbnNpdGlvbi1lYXNpbmc7CiAgICAgIH0KCiAgICAgIC51bmZvY3VzZWQtbGluZSB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZpdDsKICAgICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgdmFyKC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWNvbG9yLCB2YXIoLS1zZWNvbmRhcnktdGV4dC1jb2xvcikpOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci11bmRlcmxpbmU7CiAgICAgIH0KCiAgICAgIDpob3N0KFtkaXNhYmxlZF0pIC51bmZvY3VzZWQtbGluZSB7CiAgICAgICAgYm9yZGVyLWJvdHRvbTogMXB4IGRhc2hlZDsKICAgICAgICBib3JkZXItY29sb3I6IHZhcigtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1jb2xvciwgdmFyKC0tc2Vjb25kYXJ5LXRleHQtY29sb3IpKTsKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1jb250YWluZXItdW5kZXJsaW5lLWRpc2FibGVkOwogICAgICB9CgogICAgICAuaW5wdXQtd3JhcHBlciB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWhvcml6b250YWw7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWNlbnRlcjsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgIH0KCiAgICAgIC5pbnB1dC1jb250ZW50IHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtZmxleC1hdXRvOwogICAgICAgIEBhcHBseSAtLWxheW91dC1yZWxhdGl2ZTsKICAgICAgICBtYXgtd2lkdGg6IDEwMCU7CiAgICAgIH0KCiAgICAgIC5pbnB1dC1jb250ZW50IDo6c2xvdHRlZChsYWJlbCksCiAgICAgIC5pbnB1dC1jb250ZW50IDo6c2xvdHRlZCgucGFwZXItaW5wdXQtbGFiZWwpIHsKICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgdG9wOiAwOwogICAgICAgIGxlZnQ6IDA7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgZm9udDogaW5oZXJpdDsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWNvbG9yLCB2YXIoLS1zZWNvbmRhcnktdGV4dC1jb2xvcikpOwogICAgICAgIC13ZWJraXQtdHJhbnNpdGlvbjogLXdlYmtpdC10cmFuc2Zvcm0gMC4yNXMsIHdpZHRoIDAuMjVzOwogICAgICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAwLjI1cywgd2lkdGggMC4yNXM7CiAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm0tb3JpZ2luOiBsZWZ0IHRvcDsKICAgICAgICB0cmFuc2Zvcm0tb3JpZ2luOiBsZWZ0IHRvcDsKICAgICAgICAvKiBGaXggZm9yIHNhZmFyaSBub3QgZm9jdXNpbmcgMC1oZWlnaHQgZGF0ZS90aW1lIGlucHV0cyB3aXRoIC13ZWJraXQtYXBwZXJhbmNlOiBub25lOyAqLwogICAgICAgIG1pbi1oZWlnaHQ6IDFweDsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1jb21tb24tbm93cmFwOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtc3ViaGVhZDsKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1jb250YWluZXItbGFiZWw7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItdHJhbnNpdGlvbi1lYXNpbmc7CiAgICAgIH0KCgogICAgICAuaW5wdXQtY29udGVudCA6OnNsb3R0ZWQobGFiZWwpOmJlZm9yZSwKICAgICAgLmlucHV0LWNvbnRlbnQgOjpzbG90dGVkKC5wYXBlci1pbnB1dC1sYWJlbCk6YmVmb3JlIHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1jb250YWluZXItbGFiZWwtYmVmb3JlOwogICAgICB9CgogICAgICAuaW5wdXQtY29udGVudCA6OnNsb3R0ZWQobGFiZWwpOmFmdGVyLAogICAgICAuaW5wdXQtY29udGVudCA6OnNsb3R0ZWQoLnBhcGVyLWlucHV0LWxhYmVsKTphZnRlciB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWxhYmVsLWFmdGVyOwogICAgICB9CgogICAgICAuaW5wdXQtY29udGVudC5sYWJlbC1pcy1mbG9hdGluZyA6OnNsb3R0ZWQobGFiZWwpLAogICAgICAuaW5wdXQtY29udGVudC5sYWJlbC1pcy1mbG9hdGluZyA6OnNsb3R0ZWQoLnBhcGVyLWlucHV0LWxhYmVsKSB7CiAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTc1JSkgc2NhbGUoMC43NSk7CiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC03NSUpIHNjYWxlKDAuNzUpOwoKICAgICAgICAvKiBTaW5jZSB3ZSBzY2FsZSB0byA3NS8xMDAgb2YgdGhlIHNpemUsIHdlIGFjdHVhbGx5IGhhdmUgMTAwLzc1IG9mIHRoZQogICAgICAgIG9yaWdpbmFsIHNwYWNlIG5vdyBhdmFpbGFibGUgKi8KICAgICAgICB3aWR0aDogMTMzJTsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWxhYmVsLWZsb2F0aW5nOwogICAgICB9CgogICAgICA6aG9zdCg6ZGlyKHJ0bCkpIC5pbnB1dC1jb250ZW50LmxhYmVsLWlzLWZsb2F0aW5nIDo6c2xvdHRlZChsYWJlbCksCiAgICAgIDpob3N0KDpkaXIocnRsKSkgLmlucHV0LWNvbnRlbnQubGFiZWwtaXMtZmxvYXRpbmcgOjpzbG90dGVkKC5wYXBlci1pbnB1dC1sYWJlbCkgewogICAgICAgIHJpZ2h0OiAwOwogICAgICAgIGxlZnQ6IGF1dG87CiAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm0tb3JpZ2luOiByaWdodCB0b3A7CiAgICAgICAgdHJhbnNmb3JtLW9yaWdpbjogcmlnaHQgdG9wOwogICAgICB9CgogICAgICAuaW5wdXQtY29udGVudC5sYWJlbC1pcy1oaWdobGlnaHRlZCA6OnNsb3R0ZWQobGFiZWwpLAogICAgICAuaW5wdXQtY29udGVudC5sYWJlbC1pcy1oaWdobGlnaHRlZCA6OnNsb3R0ZWQoLnBhcGVyLWlucHV0LWxhYmVsKSB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1mb2N1cy1jb2xvciwgdmFyKC0tcHJpbWFyeS1jb2xvcikpOwoKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1jb250YWluZXItbGFiZWwtZm9jdXM7CiAgICAgIH0KCiAgICAgIC5pbnB1dC1jb250ZW50LmlzLWludmFsaWQgOjpzbG90dGVkKGxhYmVsKSwKICAgICAgLmlucHV0LWNvbnRlbnQuaXMtaW52YWxpZCA6OnNsb3R0ZWQoLnBhcGVyLWlucHV0LWxhYmVsKSB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1pbnZhbGlkLWNvbG9yLCB2YXIoLS1lcnJvci1jb2xvcikpOwogICAgICB9CgogICAgICAuaW5wdXQtY29udGVudC5sYWJlbC1pcy1oaWRkZW4gOjpzbG90dGVkKGxhYmVsKSwKICAgICAgLmlucHV0LWNvbnRlbnQubGFiZWwtaXMtaGlkZGVuIDo6c2xvdHRlZCgucGFwZXItaW5wdXQtbGFiZWwpIHsKICAgICAgICB2aXNpYmlsaXR5OiBoaWRkZW47CiAgICAgIH0KCiAgICAgIC5pbnB1dC1jb250ZW50IDo6c2xvdHRlZChpbnB1dCksCiAgICAgIC5pbnB1dC1jb250ZW50IDo6c2xvdHRlZChpcm9uLWlucHV0KSwKICAgICAgLmlucHV0LWNvbnRlbnQgOjpzbG90dGVkKHRleHRhcmVhKSwKICAgICAgLmlucHV0LWNvbnRlbnQgOjpzbG90dGVkKGlyb24tYXV0b2dyb3ctdGV4dGFyZWEpLAogICAgICAuaW5wdXQtY29udGVudCA6OnNsb3R0ZWQoLnBhcGVyLWlucHV0LWlucHV0KSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtY29udGFpbmVyLXNoYXJlZC1pbnB1dC1zdHlsZTsKICAgICAgICAvKiBUaGUgYXBwbHkgc2hpbSBkb2Vzbid0IGFwcGx5IHRoZSBuZXN0ZWQgY29sb3IgY3VzdG9tIHByb3BlcnR5LAogICAgICAgICAgc28gd2UgaGF2ZSB0byByZS1hcHBseSBpdCBoZXJlLiAqLwogICAgICAgIGNvbG9yOiB2YXIoLS1wYXBlci1pbnB1dC1jb250YWluZXItaW5wdXQtY29sb3IsIHZhcigtLXByaW1hcnktdGV4dC1jb2xvcikpOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1pbnB1dDsKICAgICAgfQoKICAgICAgLmlucHV0LWNvbnRlbnQgOjpzbG90dGVkKGlucHV0KTo6LXdlYmtpdC1vdXRlci1zcGluLWJ1dHRvbiwKICAgICAgLmlucHV0LWNvbnRlbnQgOjpzbG90dGVkKGlucHV0KTo6LXdlYmtpdC1pbm5lci1zcGluLWJ1dHRvbiB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWlucHV0LXdlYmtpdC1zcGlubmVyOwogICAgICB9CgogICAgICAuaW5wdXQtY29udGVudC5mb2N1c2VkIDo6c2xvdHRlZChpbnB1dCksCiAgICAgIC5pbnB1dC1jb250ZW50LmZvY3VzZWQgOjpzbG90dGVkKGlyb24taW5wdXQpLAogICAgICAuaW5wdXQtY29udGVudC5mb2N1c2VkIDo6c2xvdHRlZCh0ZXh0YXJlYSksCiAgICAgIC5pbnB1dC1jb250ZW50LmZvY3VzZWQgOjpzbG90dGVkKGlyb24tYXV0b2dyb3ctdGV4dGFyZWEpLAogICAgICAuaW5wdXQtY29udGVudC5mb2N1c2VkIDo6c2xvdHRlZCgucGFwZXItaW5wdXQtaW5wdXQpIHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1jb250YWluZXItaW5wdXQtZm9jdXM7CiAgICAgIH0KCiAgICAgIC5pbnB1dC1jb250ZW50LmlzLWludmFsaWQgOjpzbG90dGVkKGlucHV0KSwKICAgICAgLmlucHV0LWNvbnRlbnQuaXMtaW52YWxpZCA6OnNsb3R0ZWQoaXJvbi1pbnB1dCksCiAgICAgIC5pbnB1dC1jb250ZW50LmlzLWludmFsaWQgOjpzbG90dGVkKHRleHRhcmVhKSwKICAgICAgLmlucHV0LWNvbnRlbnQuaXMtaW52YWxpZCA6OnNsb3R0ZWQoaXJvbi1hdXRvZ3Jvdy10ZXh0YXJlYSksCiAgICAgIC5pbnB1dC1jb250ZW50LmlzLWludmFsaWQgOjpzbG90dGVkKC5wYXBlci1pbnB1dC1pbnB1dCkgewogICAgICAgIEBhcHBseSAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1pbnB1dC1pbnZhbGlkOwogICAgICB9CgogICAgICAucHJlZml4IDo6c2xvdHRlZCgqKSB7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtc3ViaGVhZDsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtZmxleC1ub25lOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWlucHV0LXByZWZpeDsKICAgICAgfQoKICAgICAgLnN1ZmZpeCA6OnNsb3R0ZWQoKikgewogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LXN1YmhlYWQ7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZsZXgtbm9uZTsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtc3VmZml4OwogICAgICB9CgogICAgICAvKiBGaXJlZm94IHNldHMgYSBtaW4td2lkdGggb24gdGhlIGlucHV0LCB3aGljaCBjYW4gY2F1c2UgbGF5b3V0IGlzc3VlcyAqLwogICAgICAuaW5wdXQtY29udGVudCA6OnNsb3R0ZWQoaW5wdXQpIHsKICAgICAgICBtaW4td2lkdGg6IDA7CiAgICAgIH0KCiAgICAgIC5pbnB1dC1jb250ZW50IDo6c2xvdHRlZCh0ZXh0YXJlYSkgewogICAgICAgIHJlc2l6ZTogbm9uZTsKICAgICAgfQoKICAgICAgLmFkZC1vbi1jb250ZW50IHsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgIH0KCiAgICAgIC5hZGQtb24tY29udGVudC5pcy1pbnZhbGlkIDo6c2xvdHRlZCgqKSB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1pbnZhbGlkLWNvbG9yLCB2YXIoLS1lcnJvci1jb2xvcikpOwogICAgICB9CgogICAgICAuYWRkLW9uLWNvbnRlbnQuaXMtaGlnaGxpZ2h0ZWQgOjpzbG90dGVkKCopIHsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWZvY3VzLWNvbG9yLCB2YXIoLS1wcmltYXJ5LWNvbG9yKSk7CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPGRpdiBjbGFzcz0iZmxvYXRlZC1sYWJlbC1wbGFjZWhvbGRlciIgYXJpYS1oaWRkZW49InRydWUiIGhpZGRlbj0iW1tub0xhYmVsRmxvYXRdXSI+Jm5ic3A7PC9kaXY+CgogICAgPGRpdiBjbGFzcz0iaW5wdXQtd3JhcHBlciI+CiAgICAgIDxzcGFuIGNsYXNzPSJwcmVmaXgiPjxzbG90IG5hbWU9InByZWZpeCI+PC9zbG90Pjwvc3Bhbj4KCiAgICAgIDxkaXYgY2xhc3MkPSJbW19jb21wdXRlSW5wdXRDb250ZW50Q2xhc3Mobm9MYWJlbEZsb2F0LGFsd2F5c0Zsb2F0TGFiZWwsZm9jdXNlZCxpbnZhbGlkLF9pbnB1dEhhc0NvbnRlbnQpXV0iIGlkPSJsYWJlbEFuZElucHV0Q29udGFpbmVyIj4KICAgICAgICA8c2xvdCBuYW1lPSJsYWJlbCI+PC9zbG90PgogICAgICAgIDxzbG90IG5hbWU9ImlucHV0Ij48L3Nsb3Q+CiAgICAgIDwvZGl2PgoKICAgICAgPHNwYW4gY2xhc3M9InN1ZmZpeCI+PHNsb3QgbmFtZT0ic3VmZml4Ij48L3Nsb3Q+PC9zcGFuPgogICAgPC9kaXY+CgogICAgPGRpdiBjbGFzcyQ9IltbX2NvbXB1dGVVbmRlcmxpbmVDbGFzcyhmb2N1c2VkLGludmFsaWQpXV0iPgogICAgICA8ZGl2IGNsYXNzPSJ1bmZvY3VzZWQtbGluZSI+PC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImZvY3VzZWQtbGluZSI+PC9kaXY+CiAgICA8L2Rpdj4KCiAgICA8ZGl2IGNsYXNzJD0iW1tfY29tcHV0ZUFkZE9uQ29udGVudENsYXNzKGZvY3VzZWQsaW52YWxpZCldXSI+CiAgICAgIDxzbG90IG5hbWU9ImFkZC1vbiI+PC9zbG90PgogICAgPC9kaXY+CmAsaXM6InBhcGVyLWlucHV0LWNvbnRhaW5lciIscHJvcGVydGllczp7bm9MYWJlbEZsb2F0Ont0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LGFsd2F5c0Zsb2F0TGFiZWw6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sYXR0ckZvclZhbHVlOnt0eXBlOlN0cmluZyx2YWx1ZToiYmluZC12YWx1ZSJ9LGF1dG9WYWxpZGF0ZTp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxpbnZhbGlkOntvYnNlcnZlcjoiX2ludmFsaWRDaGFuZ2VkIix0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LGZvY3VzZWQ6e3JlYWRPbmx5OiEwLHR5cGU6Qm9vbGVhbix2YWx1ZTohMSxub3RpZnk6ITB9LF9hZGRvbnM6e3R5cGU6QXJyYXl9LF9pbnB1dEhhc0NvbnRlbnQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sX2lucHV0U2VsZWN0b3I6e3R5cGU6U3RyaW5nLHZhbHVlOiJpbnB1dCxpcm9uLWlucHV0LHRleHRhcmVhLC5wYXBlci1pbnB1dC1pbnB1dCJ9LF9ib3VuZE9uRm9jdXM6e3R5cGU6RnVuY3Rpb24sdmFsdWU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fb25Gb2N1cy5iaW5kKHRoaXMpfX0sX2JvdW5kT25CbHVyOnt0eXBlOkZ1bmN0aW9uLHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX29uQmx1ci5iaW5kKHRoaXMpfX0sX2JvdW5kT25JbnB1dDp7dHlwZTpGdW5jdGlvbix2YWx1ZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLl9vbklucHV0LmJpbmQodGhpcyl9fSxfYm91bmRWYWx1ZUNoYW5nZWQ6e3R5cGU6RnVuY3Rpb24sdmFsdWU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fb25WYWx1ZUNoYW5nZWQuYmluZCh0aGlzKX19fSxsaXN0ZW5lcnM6eyJhZGRvbi1hdHRhY2hlZCI6Il9vbkFkZG9uQXR0YWNoZWQiLCJpcm9uLWlucHV0LXZhbGlkYXRlIjoiX29uSXJvbklucHV0VmFsaWRhdGUifSxnZXQgX3ZhbHVlQ2hhbmdlZEV2ZW50KCl7cmV0dXJuIHRoaXMuYXR0ckZvclZhbHVlKyItY2hhbmdlZCJ9LGdldCBfcHJvcGVydHlGb3JWYWx1ZSgpe3JldHVybiB3bSh0aGlzLmF0dHJGb3JWYWx1ZSl9LGdldCBfaW5wdXRFbGVtZW50KCl7cmV0dXJuIHp0KHRoaXMpLnF1ZXJ5U2VsZWN0b3IodGhpcy5faW5wdXRTZWxlY3Rvcil9LGdldCBfaW5wdXRFbGVtZW50VmFsdWUoKXtyZXR1cm4gdGhpcy5faW5wdXRFbGVtZW50W3RoaXMuX3Byb3BlcnR5Rm9yVmFsdWVdfHx0aGlzLl9pbnB1dEVsZW1lbnQudmFsdWV9LHJlYWR5OmZ1bmN0aW9uKCl7dGhpcy5fX2lzRmlyc3RWYWx1ZVVwZGF0ZT0hMCx0aGlzLl9hZGRvbnN8fCh0aGlzLl9hZGRvbnM9W10pLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXMiLHRoaXMuX2JvdW5kT25Gb2N1cywhMCksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJibHVyIix0aGlzLl9ib3VuZE9uQmx1ciwhMCl9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5hdHRyRm9yVmFsdWU/dGhpcy5faW5wdXRFbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIodGhpcy5fdmFsdWVDaGFuZ2VkRXZlbnQsdGhpcy5fYm91bmRWYWx1ZUNoYW5nZWQpOnRoaXMuYWRkRXZlbnRMaXN0ZW5lcigiaW5wdXQiLHRoaXMuX29uSW5wdXQpLHRoaXMuX2lucHV0RWxlbWVudFZhbHVlJiZ0aGlzLl9pbnB1dEVsZW1lbnRWYWx1ZSE9IiI/dGhpcy5faGFuZGxlVmFsdWVBbmRBdXRvVmFsaWRhdGUodGhpcy5faW5wdXRFbGVtZW50KTp0aGlzLl9oYW5kbGVWYWx1ZSh0aGlzLl9pbnB1dEVsZW1lbnQpfSxfb25BZGRvbkF0dGFjaGVkOmZ1bmN0aW9uKGUpe3RoaXMuX2FkZG9uc3x8KHRoaXMuX2FkZG9ucz1bXSk7dmFyIHQ9ZS50YXJnZXQ7dGhpcy5fYWRkb25zLmluZGV4T2YodCk9PT0tMSYmKHRoaXMuX2FkZG9ucy5wdXNoKHQpLHRoaXMuaXNBdHRhY2hlZCYmdGhpcy5faGFuZGxlVmFsdWUodGhpcy5faW5wdXRFbGVtZW50KSl9LF9vbkZvY3VzOmZ1bmN0aW9uKCl7dGhpcy5fc2V0Rm9jdXNlZCghMCl9LF9vbkJsdXI6ZnVuY3Rpb24oKXt0aGlzLl9zZXRGb2N1c2VkKCExKSx0aGlzLl9oYW5kbGVWYWx1ZUFuZEF1dG9WYWxpZGF0ZSh0aGlzLl9pbnB1dEVsZW1lbnQpfSxfb25JbnB1dDpmdW5jdGlvbihlKXt0aGlzLl9oYW5kbGVWYWx1ZUFuZEF1dG9WYWxpZGF0ZShlLnRhcmdldCl9LF9vblZhbHVlQ2hhbmdlZDpmdW5jdGlvbihlKXt2YXIgdD1lLnRhcmdldDt0aGlzLl9faXNGaXJzdFZhbHVlVXBkYXRlJiYodGhpcy5fX2lzRmlyc3RWYWx1ZVVwZGF0ZT0hMSx0LnZhbHVlPT09dm9pZCAwfHx0LnZhbHVlPT09IiIpfHx0aGlzLl9oYW5kbGVWYWx1ZUFuZEF1dG9WYWxpZGF0ZShlLnRhcmdldCl9LF9oYW5kbGVWYWx1ZTpmdW5jdGlvbihlKXt2YXIgdD10aGlzLl9pbnB1dEVsZW1lbnRWYWx1ZTt0fHx0PT09MHx8ZS50eXBlPT09Im51bWJlciImJiFlLmNoZWNrVmFsaWRpdHkoKT90aGlzLl9pbnB1dEhhc0NvbnRlbnQ9ITA6dGhpcy5faW5wdXRIYXNDb250ZW50PSExLHRoaXMudXBkYXRlQWRkb25zKHtpbnB1dEVsZW1lbnQ6ZSx2YWx1ZTp0LGludmFsaWQ6dGhpcy5pbnZhbGlkfSl9LF9oYW5kbGVWYWx1ZUFuZEF1dG9WYWxpZGF0ZTpmdW5jdGlvbihlKXtpZih0aGlzLmF1dG9WYWxpZGF0ZSYmZSl7dmFyIHQ7ZS52YWxpZGF0ZT90PWUudmFsaWRhdGUodGhpcy5faW5wdXRFbGVtZW50VmFsdWUpOnQ9ZS5jaGVja1ZhbGlkaXR5KCksdGhpcy5pbnZhbGlkPSF0fXRoaXMuX2hhbmRsZVZhbHVlKGUpfSxfb25Jcm9uSW5wdXRWYWxpZGF0ZTpmdW5jdGlvbihlKXt0aGlzLmludmFsaWQ9dGhpcy5faW5wdXRFbGVtZW50LmludmFsaWR9LF9pbnZhbGlkQ2hhbmdlZDpmdW5jdGlvbigpe3RoaXMuX2FkZG9ucyYmdGhpcy51cGRhdGVBZGRvbnMoe2ludmFsaWQ6dGhpcy5pbnZhbGlkfSl9LHVwZGF0ZUFkZG9uczpmdW5jdGlvbihlKXtmb3IodmFyIHQscj0wO3Q9dGhpcy5fYWRkb25zW3JdO3IrKyl0LnVwZGF0ZShlKX0sX2NvbXB1dGVJbnB1dENvbnRlbnRDbGFzczpmdW5jdGlvbihlLHQscixuLGkpe3ZhciBvPSJpbnB1dC1jb250ZW50IjtpZihlKWkmJihvKz0iIGxhYmVsLWlzLWhpZGRlbiIpLG4mJihvKz0iIGlzLWludmFsaWQiKTtlbHNle3ZhciBhPXRoaXMucXVlcnlTZWxlY3RvcigibGFiZWwiKTt0fHxpPyhvKz0iIGxhYmVsLWlzLWZsb2F0aW5nIix0aGlzLiQubGFiZWxBbmRJbnB1dENvbnRhaW5lci5zdHlsZS5wb3NpdGlvbj0ic3RhdGljIixuP28rPSIgaXMtaW52YWxpZCI6ciYmKG8rPSIgbGFiZWwtaXMtaGlnaGxpZ2h0ZWQiKSk6KGEmJih0aGlzLiQubGFiZWxBbmRJbnB1dENvbnRhaW5lci5zdHlsZS5wb3NpdGlvbj0icmVsYXRpdmUiKSxuJiYobys9IiBpcy1pbnZhbGlkIikpfXJldHVybiByJiYobys9IiBmb2N1c2VkIiksb30sX2NvbXB1dGVVbmRlcmxpbmVDbGFzczpmdW5jdGlvbihlLHQpe3ZhciByPSJ1bmRlcmxpbmUiO3JldHVybiB0P3IrPSIgaXMtaW52YWxpZCI6ZSYmKHIrPSIgaXMtaGlnaGxpZ2h0ZWQiKSxyfSxfY29tcHV0ZUFkZE9uQ29udGVudENsYXNzOmZ1bmN0aW9uKGUsdCl7dmFyIHI9ImFkZC1vbi1jb250ZW50IjtyZXR1cm4gdD9yKz0iIGlzLWludmFsaWQiOmUmJihyKz0iIGlzLWhpZ2hsaWdodGVkIikscn19KTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgICAgdmlzaWJpbGl0eTogaGlkZGVuOwoKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWludmFsaWQtY29sb3IsIHZhcigtLWVycm9yLWNvbG9yKSk7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY2FwdGlvbjsKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1lcnJvcjsKICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgbGVmdDowOwogICAgICAgIHJpZ2h0OjA7CiAgICAgIH0KCiAgICAgIDpob3N0KFtpbnZhbGlkXSkgewogICAgICAgIHZpc2liaWxpdHk6IHZpc2libGU7CiAgICAgIH0KCiAgICAgICNhMTF5V3JhcHBlciB7CiAgICAgICAgdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICB9CgogICAgICA6aG9zdChbaW52YWxpZF0pICNhMTF5V3JhcHBlciB7CiAgICAgICAgdmlzaWJpbGl0eTogdmlzaWJsZTsKICAgICAgfQogICAgPC9zdHlsZT4KCiAgICA8IS0tCiAgICBJZiB0aGUgcGFwZXItaW5wdXQtZXJyb3IgZWxlbWVudCBpcyBkaXJlY3RseSByZWZlcmVuY2VkIGJ5IGFuCiAgICBcYGFyaWEtZGVzY3JpYmVkYnlcYCBhdHRyaWJ1dGUsIHN1Y2ggYXMgd2hlbiB1c2VkIGFzIGEgcGFwZXItaW5wdXQgYWRkLW9uLAogICAgdGhlbiBhcHBseWluZyBcYHZpc2liaWxpdHk6IGhpZGRlbjtcYCB0byB0aGUgcGFwZXItaW5wdXQtZXJyb3IgZWxlbWVudCBpdHNlbGYKICAgIGRvZXMgbm90IGhpZGUgdGhlIGVycm9yLgoKICAgIEZvciBtb3JlIGluZm9ybWF0aW9uLCBzZWU6CiAgICBodHRwczovL3d3dy53My5vcmcvVFIvYWNjbmFtZS0xLjEvI21hcHBpbmdfYWRkaXRpb25hbF9uZF9kZXNjcmlwdGlvbgogICAgLS0+CiAgICA8ZGl2IGlkPSJhMTF5V3JhcHBlciI+CiAgICAgIDxzbG90Pjwvc2xvdD4KICAgIDwvZGl2PgpgLGlzOiJwYXBlci1pbnB1dC1lcnJvciIsYmVoYXZpb3JzOltUOV0scHJvcGVydGllczp7aW52YWxpZDp7cmVhZE9ubHk6ITAscmVmbGVjdFRvQXR0cmlidXRlOiEwLHR5cGU6Qm9vbGVhbn19LHVwZGF0ZTpmdW5jdGlvbihlKXt0aGlzLl9zZXRJbnZhbGlkKGUuaW52YWxpZCl9fSk7dmFyIFp4PXt9O1p4Lk5leHRMYWJlbElEPTE7WnguTmV4dEFkZG9uSUQ9MTtaeC5OZXh0SW5wdXRJRD0xO3ZhciBGYmU9e3Byb3BlcnRpZXM6e2xhYmVsOnt0eXBlOlN0cmluZ30sdmFsdWU6e25vdGlmeTohMCx0eXBlOlN0cmluZ30sZGlzYWJsZWQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0saW52YWxpZDp7dHlwZTpCb29sZWFuLHZhbHVlOiExLG5vdGlmeTohMH0sYWxsb3dlZFBhdHRlcm46e3R5cGU6U3RyaW5nfSx0eXBlOnt0eXBlOlN0cmluZ30sbGlzdDp7dHlwZTpTdHJpbmd9LHBhdHRlcm46e3R5cGU6U3RyaW5nfSxyZXF1aXJlZDp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxlcnJvck1lc3NhZ2U6e3R5cGU6U3RyaW5nfSxjaGFyQ291bnRlcjp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxub0xhYmVsRmxvYXQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sYWx3YXlzRmxvYXRMYWJlbDp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxhdXRvVmFsaWRhdGU6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sdmFsaWRhdG9yOnt0eXBlOlN0cmluZ30sYXV0b2NvbXBsZXRlOnt0eXBlOlN0cmluZyx2YWx1ZToib2ZmIn0sYXV0b2ZvY3VzOnt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9hdXRvZm9jdXNDaGFuZ2VkIn0saW5wdXRtb2RlOnt0eXBlOlN0cmluZ30sbWlubGVuZ3RoOnt0eXBlOk51bWJlcn0sbWF4bGVuZ3RoOnt0eXBlOk51bWJlcn0sbWluOnt0eXBlOlN0cmluZ30sbWF4Ont0eXBlOlN0cmluZ30sc3RlcDp7dHlwZTpTdHJpbmd9LG5hbWU6e3R5cGU6U3RyaW5nfSxwbGFjZWhvbGRlcjp7dHlwZTpTdHJpbmcsdmFsdWU6IiJ9LHJlYWRvbmx5Ont0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LHNpemU6e3R5cGU6TnVtYmVyfSxhdXRvY2FwaXRhbGl6ZTp7dHlwZTpTdHJpbmcsdmFsdWU6Im5vbmUifSxhdXRvY29ycmVjdDp7dHlwZTpTdHJpbmcsdmFsdWU6Im9mZiJ9LGF1dG9zYXZlOnt0eXBlOlN0cmluZ30scmVzdWx0czp7dHlwZTpOdW1iZXJ9LGFjY2VwdDp7dHlwZTpTdHJpbmd9LG11bHRpcGxlOnt0eXBlOkJvb2xlYW59LF9hcmlhRGVzY3JpYmVkQnk6e3R5cGU6U3RyaW5nLHZhbHVlOiIifSxfYXJpYUxhYmVsbGVkQnk6e3R5cGU6U3RyaW5nLHZhbHVlOiIifSxfaW5wdXRJZDp7dHlwZTpTdHJpbmcsdmFsdWU6IiJ9fSxsaXN0ZW5lcnM6eyJhZGRvbi1hdHRhY2hlZCI6Il9vbkFkZG9uQXR0YWNoZWQifSxrZXlCaW5kaW5nczp7InNoaWZ0K3RhYjprZXlkb3duIjoiX29uU2hpZnRUYWJEb3duIn0saG9zdEF0dHJpYnV0ZXM6e3RhYmluZGV4OjB9LGdldCBpbnB1dEVsZW1lbnQoKXtyZXR1cm4gdGhpcy4kfHwodGhpcy4kPXt9KSx0aGlzLiQuaW5wdXR8fCh0aGlzLl9nZW5lcmF0ZUlucHV0SWQoKSx0aGlzLiQuaW5wdXQ9dGhpcy4kJCgiIyIrdGhpcy5faW5wdXRJZCkpLHRoaXMuJC5pbnB1dH0sZ2V0IF9mb2N1c2FibGVFbGVtZW50KCl7cmV0dXJuIHRoaXMuaW5wdXRFbGVtZW50fSxjcmVhdGVkOmZ1bmN0aW9uKCl7dGhpcy5fdHlwZXNUaGF0SGF2ZVRleHQ9WyJkYXRlIiwiZGF0ZXRpbWUiLCJkYXRldGltZS1sb2NhbCIsIm1vbnRoIiwidGltZSIsIndlZWsiLCJmaWxlIl19LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5fdXBkYXRlQXJpYUxhYmVsbGVkQnkoKSwhbXQmJnRoaXMuaW5wdXRFbGVtZW50JiZ0aGlzLl90eXBlc1RoYXRIYXZlVGV4dC5pbmRleE9mKHRoaXMuaW5wdXRFbGVtZW50LnR5cGUpIT09LTEmJih0aGlzLmFsd2F5c0Zsb2F0TGFiZWw9ITApfSxfYXBwZW5kU3RyaW5nV2l0aFNwYWNlOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIGU/ZT1lKyIgIit0OmU9dCxlfSxfb25BZGRvbkF0dGFjaGVkOmZ1bmN0aW9uKGUpe3ZhciB0PXp0KGUpLnJvb3RUYXJnZXQ7aWYodC5pZCl0aGlzLl9hcmlhRGVzY3JpYmVkQnk9dGhpcy5fYXBwZW5kU3RyaW5nV2l0aFNwYWNlKHRoaXMuX2FyaWFEZXNjcmliZWRCeSx0LmlkKTtlbHNle3ZhciByPSJwYXBlci1pbnB1dC1hZGQtb24tIitaeC5OZXh0QWRkb25JRCsrO3QuaWQ9cix0aGlzLl9hcmlhRGVzY3JpYmVkQnk9dGhpcy5fYXBwZW5kU3RyaW5nV2l0aFNwYWNlKHRoaXMuX2FyaWFEZXNjcmliZWRCeSxyKX19LHZhbGlkYXRlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuaW5wdXRFbGVtZW50LnZhbGlkYXRlKCl9LF9mb2N1c0JsdXJIYW5kbGVyOmZ1bmN0aW9uKGUpe0RpLl9mb2N1c0JsdXJIYW5kbGVyLmNhbGwodGhpcyxlKSx0aGlzLmZvY3VzZWQmJiF0aGlzLl9zaGlmdFRhYlByZXNzZWQmJnRoaXMuX2ZvY3VzYWJsZUVsZW1lbnQmJnRoaXMuX2ZvY3VzYWJsZUVsZW1lbnQuZm9jdXMoKX0sX29uU2hpZnRUYWJEb3duOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuZ2V0QXR0cmlidXRlKCJ0YWJpbmRleCIpO3RoaXMuX3NoaWZ0VGFiUHJlc3NlZD0hMCx0aGlzLnNldEF0dHJpYnV0ZSgidGFiaW5kZXgiLCItMSIpLHRoaXMuYXN5bmMoZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZSgidGFiaW5kZXgiLHQpLHRoaXMuX3NoaWZ0VGFiUHJlc3NlZD0hMX0sMSl9LF9oYW5kbGVBdXRvVmFsaWRhdGU6ZnVuY3Rpb24oKXt0aGlzLmF1dG9WYWxpZGF0ZSYmdGhpcy52YWxpZGF0ZSgpfSx1cGRhdGVWYWx1ZUFuZFByZXNlcnZlQ2FyZXQ6ZnVuY3Rpb24oZSl7dHJ5e3ZhciB0PXRoaXMuaW5wdXRFbGVtZW50LnNlbGVjdGlvblN0YXJ0O3RoaXMudmFsdWU9ZSx0aGlzLmlucHV0RWxlbWVudC5zZWxlY3Rpb25TdGFydD10LHRoaXMuaW5wdXRFbGVtZW50LnNlbGVjdGlvbkVuZD10fWNhdGNoKHIpe3RoaXMudmFsdWU9ZX19LF9jb21wdXRlQWx3YXlzRmxvYXRMYWJlbDpmdW5jdGlvbihlLHQpe3JldHVybiB0fHxlfSxfdXBkYXRlQXJpYUxhYmVsbGVkQnk6ZnVuY3Rpb24oKXt2YXIgZT16dCh0aGlzLnJvb3QpLnF1ZXJ5U2VsZWN0b3IoImxhYmVsIik7aWYoIWUpe3RoaXMuX2FyaWFMYWJlbGxlZEJ5PSIiO3JldHVybn12YXIgdDtlLmlkP3Q9ZS5pZDoodD0icGFwZXItaW5wdXQtbGFiZWwtIitaeC5OZXh0TGFiZWxJRCsrLGUuaWQ9dCksdGhpcy5fYXJpYUxhYmVsbGVkQnk9dH0sX2dlbmVyYXRlSW5wdXRJZDpmdW5jdGlvbigpeyghdGhpcy5faW5wdXRJZHx8dGhpcy5faW5wdXRJZD09PSIiKSYmKHRoaXMuX2lucHV0SWQ9ImlucHV0LSIrWnguTmV4dElucHV0SUQrKyl9LF9vbkNoYW5nZTpmdW5jdGlvbihlKXt0aGlzLnNoYWRvd1Jvb3QmJnRoaXMuZmlyZShlLnR5cGUse3NvdXJjZUV2ZW50OmV9LHtub2RlOnRoaXMsYnViYmxlczplLmJ1YmJsZXMsY2FuY2VsYWJsZTplLmNhbmNlbGFibGV9KX0sX2F1dG9mb2N1c0NoYW5nZWQ6ZnVuY3Rpb24oKXtpZih0aGlzLmF1dG9mb2N1cyYmdGhpcy5fZm9jdXNhYmxlRWxlbWVudCl7dmFyIGU9ZG9jdW1lbnQuYWN0aXZlRWxlbWVudCx0PWUgaW5zdGFuY2VvZiBIVE1MRWxlbWVudCxyPXQmJmUhPT1kb2N1bWVudC5ib2R5JiZlIT09ZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50O3J8fHRoaXMuX2ZvY3VzYWJsZUVsZW1lbnQuZm9jdXMoKX19fSxDOT1bRGksT28sRmJlXTtZdCh7aXM6InBhcGVyLWlucHV0IixfdGVtcGxhdGU6UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICB9CgogICAgICA6aG9zdChbZm9jdXNlZF0pIHsKICAgICAgICBvdXRsaW5lOiBub25lOwogICAgICB9CgogICAgICA6aG9zdChbaGlkZGVuXSkgewogICAgICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICAgICAgfQoKICAgICAgaW5wdXQgewogICAgICAgIC8qIEZpcmVmb3ggc2V0cyBhIG1pbi13aWR0aCBvbiB0aGUgaW5wdXQsIHdoaWNoIGNhbiBjYXVzZSBsYXlvdXQgaXNzdWVzICovCiAgICAgICAgbWluLXdpZHRoOiAwOwogICAgICB9CgogICAgICAvKiBJbiAxLngsIHRoZSA8aW5wdXQ+IGlzIGRpc3RyaWJ1dGVkIHRvIHBhcGVyLWlucHV0LWNvbnRhaW5lciwgd2hpY2ggc3R5bGVzIGl0LgogICAgICBJbiAyLnggdGhlIDxpcm9uLWlucHV0PiBpcyBkaXN0cmlidXRlZCB0byBwYXBlci1pbnB1dC1jb250YWluZXIsIHdoaWNoIHN0eWxlcwogICAgICBpdCwgYnV0IGluIG9yZGVyIGZvciB0aGlzIHRvIHdvcmsgY29ycmVjdGx5LCB3ZSBuZWVkIHRvIHJlc2V0IHNvbWUKICAgICAgb2YgdGhlIG5hdGl2ZSBpbnB1dCdzIHByb3BlcnRpZXMgdG8gaW5oZXJpdCAoZnJvbSB0aGUgaXJvbi1pbnB1dCkgKi8KICAgICAgaXJvbi1pbnB1dCA+IGlucHV0IHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1jb250YWluZXItc2hhcmVkLWlucHV0LXN0eWxlOwogICAgICAgIGZvbnQtZmFtaWx5OiBpbmhlcml0OwogICAgICAgIGZvbnQtd2VpZ2h0OiBpbmhlcml0OwogICAgICAgIGZvbnQtc2l6ZTogaW5oZXJpdDsKICAgICAgICBsZXR0ZXItc3BhY2luZzogaW5oZXJpdDsKICAgICAgICB3b3JkLXNwYWNpbmc6IGluaGVyaXQ7CiAgICAgICAgbGluZS1oZWlnaHQ6IGluaGVyaXQ7CiAgICAgICAgdGV4dC1zaGFkb3c6IGluaGVyaXQ7CiAgICAgICAgY29sb3I6IGluaGVyaXQ7CiAgICAgICAgY3Vyc29yOiBpbmhlcml0OwogICAgICB9CgogICAgICBpbnB1dDpkaXNhYmxlZCB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWlucHV0LWRpc2FibGVkOwogICAgICB9CgogICAgICBpbnB1dDo6LXdlYmtpdC1vdXRlci1zcGluLWJ1dHRvbiwKICAgICAgaW5wdXQ6Oi13ZWJraXQtaW5uZXItc3Bpbi1idXR0b24gewogICAgICAgIEBhcHBseSAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1pbnB1dC13ZWJraXQtc3Bpbm5lcjsKICAgICAgfQoKICAgICAgaW5wdXQ6Oi13ZWJraXQtY2xlYXItYnV0dG9uIHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1jb250YWluZXItaW5wdXQtd2Via2l0LWNsZWFyOwogICAgICB9CgogICAgICBpbnB1dDo6LXdlYmtpdC1jYWxlbmRhci1waWNrZXItaW5kaWNhdG9yIHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1pbnB1dC1jb250YWluZXItaW5wdXQtd2Via2l0LWNhbGVuZGFyLXBpY2tlci1pbmRpY2F0b3I7CiAgICAgIH0KCiAgICAgIGlucHV0Ojotd2Via2l0LWlucHV0LXBsYWNlaG9sZGVyIHsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWNvbG9yLCB2YXIoLS1zZWNvbmRhcnktdGV4dC1jb2xvcikpOwogICAgICB9CgogICAgICBpbnB1dDotbW96LXBsYWNlaG9sZGVyIHsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWNvbG9yLCB2YXIoLS1zZWNvbmRhcnktdGV4dC1jb2xvcikpOwogICAgICB9CgogICAgICBpbnB1dDo6LW1vei1wbGFjZWhvbGRlciB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1jb2xvciwgdmFyKC0tc2Vjb25kYXJ5LXRleHQtY29sb3IpKTsKICAgICAgfQoKICAgICAgaW5wdXQ6Oi1tcy1jbGVhciB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtY29udGFpbmVyLW1zLWNsZWFyOwogICAgICB9CgogICAgICBpbnB1dDo6LW1zLXJldmVhbCB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaW5wdXQtY29udGFpbmVyLW1zLXJldmVhbDsKICAgICAgfQoKICAgICAgaW5wdXQ6LW1zLWlucHV0LXBsYWNlaG9sZGVyIHsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWNvbG9yLCB2YXIoLS1zZWNvbmRhcnktdGV4dC1jb2xvcikpOwogICAgICB9CgogICAgICBsYWJlbCB7CiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPHBhcGVyLWlucHV0LWNvbnRhaW5lciBpZD0iY29udGFpbmVyIiBuby1sYWJlbC1mbG9hdD0iW1tub0xhYmVsRmxvYXRdXSIgYWx3YXlzLWZsb2F0LWxhYmVsPSJbW19jb21wdXRlQWx3YXlzRmxvYXRMYWJlbChhbHdheXNGbG9hdExhYmVsLHBsYWNlaG9sZGVyKV1dIiBhdXRvLXZhbGlkYXRlJD0iW1thdXRvVmFsaWRhdGVdXSIgZGlzYWJsZWQkPSJbW2Rpc2FibGVkXV0iIGludmFsaWQ9IltbaW52YWxpZF1dIj4KCiAgICAgIDxzbG90IG5hbWU9InByZWZpeCIgc2xvdD0icHJlZml4Ij48L3Nsb3Q+CgogICAgICA8bGFiZWwgaGlkZGVuJD0iW1shbGFiZWxdXSIgYXJpYS1oaWRkZW49InRydWUiIGZvciQ9IltbX2lucHV0SWRdXSIgc2xvdD0ibGFiZWwiPltbbGFiZWxdXTwvbGFiZWw+CgogICAgICA8IS0tIE5lZWQgdG8gYmluZCBtYXhsZW5ndGggc28gdGhhdCB0aGUgcGFwZXItaW5wdXQtY2hhci1jb3VudGVyIHdvcmtzIGNvcnJlY3RseSAtLT4KICAgICAgPGlyb24taW5wdXQgYmluZC12YWx1ZT0ie3t2YWx1ZX19IiBzbG90PSJpbnB1dCIgY2xhc3M9ImlucHV0LWVsZW1lbnQiIGlkJD0iW1tfaW5wdXRJZF1dIiBtYXhsZW5ndGgkPSJbW21heGxlbmd0aF1dIiBhbGxvd2VkLXBhdHRlcm49IltbYWxsb3dlZFBhdHRlcm5dXSIgaW52YWxpZD0ie3tpbnZhbGlkfX0iIHZhbGlkYXRvcj0iW1t2YWxpZGF0b3JdXSI+CiAgICAgICAgPGlucHV0IGFyaWEtbGFiZWxsZWRieSQ9IltbX2FyaWFMYWJlbGxlZEJ5XV0iIGFyaWEtZGVzY3JpYmVkYnkkPSJbW19hcmlhRGVzY3JpYmVkQnldXSIgZGlzYWJsZWQkPSJbW2Rpc2FibGVkXV0iIHRpdGxlJD0iW1t0aXRsZV1dIiB0eXBlJD0iW1t0eXBlXV0iIHBhdHRlcm4kPSJbW3BhdHRlcm5dXSIgcmVxdWlyZWQkPSJbW3JlcXVpcmVkXV0iIGF1dG9jb21wbGV0ZSQ9IltbYXV0b2NvbXBsZXRlXV0iIGF1dG9mb2N1cyQ9IltbYXV0b2ZvY3VzXV0iIGlucHV0bW9kZSQ9IltbaW5wdXRtb2RlXV0iIG1pbmxlbmd0aCQ9IltbbWlubGVuZ3RoXV0iIG1heGxlbmd0aCQ9IltbbWF4bGVuZ3RoXV0iIG1pbiQ9IltbbWluXV0iIG1heCQ9IltbbWF4XV0iIHN0ZXAkPSJbW3N0ZXBdXSIgbmFtZSQ9IltbbmFtZV1dIiBwbGFjZWhvbGRlciQ9IltbcGxhY2Vob2xkZXJdXSIgcmVhZG9ubHkkPSJbW3JlYWRvbmx5XV0iIGxpc3QkPSJbW2xpc3RdXSIgc2l6ZSQ9Iltbc2l6ZV1dIiBhdXRvY2FwaXRhbGl6ZSQ9IltbYXV0b2NhcGl0YWxpemVdXSIgYXV0b2NvcnJlY3QkPSJbW2F1dG9jb3JyZWN0XV0iIG9uLWNoYW5nZT0iX29uQ2hhbmdlIiB0YWJpbmRleCQ9IltbdGFiSW5kZXhdXSIgYXV0b3NhdmUkPSJbW2F1dG9zYXZlXV0iIHJlc3VsdHMkPSJbW3Jlc3VsdHNdXSIgYWNjZXB0JD0iW1thY2NlcHRdXSIgbXVsdGlwbGUkPSJbW211bHRpcGxlXV0iIHJvbGUkPSJbW2lucHV0Um9sZV1dIiBhcmlhLWhhc3BvcHVwJD0iW1tpbnB1dEFyaWFIYXNwb3B1cF1dIj4KICAgICAgPC9pcm9uLWlucHV0PgoKICAgICAgPHNsb3QgbmFtZT0ic3VmZml4IiBzbG90PSJzdWZmaXgiPjwvc2xvdD4KCiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tlcnJvck1lc3NhZ2VdXSI+CiAgICAgICAgPHBhcGVyLWlucHV0LWVycm9yIGFyaWEtbGl2ZT0iYXNzZXJ0aXZlIiBzbG90PSJhZGQtb24iPltbZXJyb3JNZXNzYWdlXV08L3BhcGVyLWlucHV0LWVycm9yPgogICAgICA8L3RlbXBsYXRlPgoKICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW2NoYXJDb3VudGVyXV0iPgogICAgICAgIDxwYXBlci1pbnB1dC1jaGFyLWNvdW50ZXIgc2xvdD0iYWRkLW9uIj48L3BhcGVyLWlucHV0LWNoYXItY291bnRlcj4KICAgICAgPC90ZW1wbGF0ZT4KCiAgICA8L3BhcGVyLWlucHV0LWNvbnRhaW5lcj4KICBgLGJlaGF2aW9yczpbQzksRWhdLHByb3BlcnRpZXM6e3ZhbHVlOnt0eXBlOlN0cmluZ30saW5wdXRSb2xlOnt0eXBlOlN0cmluZyx2YWx1ZTp2b2lkIDB9LGlucHV0QXJpYUhhc3BvcHVwOnt0eXBlOlN0cmluZyx2YWx1ZTp2b2lkIDB9fSxnZXQgX2ZvY3VzYWJsZUVsZW1lbnQoKXtyZXR1cm4gdGhpcy5pbnB1dEVsZW1lbnQuX2lucHV0RWxlbWVudH0sbGlzdGVuZXJzOnsiaXJvbi1pbnB1dC1yZWFkeSI6Il9vbklyb25JbnB1dFJlYWR5In0sX29uSXJvbklucHV0UmVhZHk6ZnVuY3Rpb24oKXt0aGlzLiQubmF0aXZlSW5wdXR8fCh0aGlzLiQubmF0aXZlSW5wdXQ9dGhpcy4kJCgiaW5wdXQiKSksdGhpcy5pbnB1dEVsZW1lbnQmJnRoaXMuX3R5cGVzVGhhdEhhdmVUZXh0LmluZGV4T2YodGhpcy4kLm5hdGl2ZUlucHV0LnR5cGUpIT09LTEmJih0aGlzLmFsd2F5c0Zsb2F0TGFiZWw9ITApLHRoaXMuaW5wdXRFbGVtZW50LmJpbmRWYWx1ZSYmdGhpcy4kLmNvbnRhaW5lci5faGFuZGxlVmFsdWVBbmRBdXRvVmFsaWRhdGUodGhpcy5pbnB1dEVsZW1lbnQpfX0pO1l0KHtfdGVtcGxhdGU6UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIHBvc2l0aW9uOiBmaXhlZDsKICAgICAgfQoKICAgICAgI2NvbnRlbnRXcmFwcGVyIDo6c2xvdHRlZCgqKSB7CiAgICAgICAgb3ZlcmZsb3c6IGF1dG87CiAgICAgIH0KCiAgICAgICNjb250ZW50V3JhcHBlci5hbmltYXRpbmcgOjpzbG90dGVkKCopIHsKICAgICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxkaXYgaWQ9ImNvbnRlbnRXcmFwcGVyIj4KICAgICAgPHNsb3QgaWQ9ImNvbnRlbnQiIG5hbWU9ImRyb3Bkb3duLWNvbnRlbnQiPjwvc2xvdD4KICAgIDwvZGl2PgpgLGlzOiJpcm9uLWRyb3Bkb3duIixiZWhhdmlvcnM6W0RpLE9vLEt4LHg5XSxwcm9wZXJ0aWVzOntob3Jpem9udGFsQWxpZ246e3R5cGU6U3RyaW5nLHZhbHVlOiJsZWZ0IixyZWZsZWN0VG9BdHRyaWJ1dGU6ITB9LHZlcnRpY2FsQWxpZ246e3R5cGU6U3RyaW5nLHZhbHVlOiJ0b3AiLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0sb3BlbkFuaW1hdGlvbkNvbmZpZzp7dHlwZTpPYmplY3R9LGNsb3NlQW5pbWF0aW9uQ29uZmlnOnt0eXBlOk9iamVjdH0sZm9jdXNUYXJnZXQ6e3R5cGU6T2JqZWN0fSxub0FuaW1hdGlvbnM6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sYWxsb3dPdXRzaWRlU2Nyb2xsOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITEsb2JzZXJ2ZXI6Il9hbGxvd091dHNpZGVTY3JvbGxDaGFuZ2VkIn19LGxpc3RlbmVyczp7Im5lb24tYW5pbWF0aW9uLWZpbmlzaCI6Il9vbk5lb25BbmltYXRpb25GaW5pc2gifSxvYnNlcnZlcnM6WyJfdXBkYXRlT3ZlcmxheVBvc2l0aW9uKHBvc2l0aW9uVGFyZ2V0LCB2ZXJ0aWNhbEFsaWduLCBob3Jpem9udGFsQWxpZ24sIHZlcnRpY2FsT2Zmc2V0LCBob3Jpem9udGFsT2Zmc2V0KSJdLGdldCBjb250YWluZWRFbGVtZW50KCl7Zm9yKHZhciBlPXp0KHRoaXMuJC5jb250ZW50KS5nZXREaXN0cmlidXRlZE5vZGVzKCksdD0wLHI9ZS5sZW5ndGg7dDxyO3QrKylpZihlW3RdLm5vZGVUeXBlPT09Tm9kZS5FTEVNRU5UX05PREUpcmV0dXJuIGVbdF19LHJlYWR5OmZ1bmN0aW9uKCl7dGhpcy5zY3JvbGxBY3Rpb258fCh0aGlzLnNjcm9sbEFjdGlvbj10aGlzLmFsbG93T3V0c2lkZVNjcm9sbD8icmVmaXQiOiJsb2NrIiksdGhpcy5fcmVhZGllZD0hMH0sYXR0YWNoZWQ6ZnVuY3Rpb24oKXsoIXRoaXMuc2l6aW5nVGFyZ2V0fHx0aGlzLnNpemluZ1RhcmdldD09PXRoaXMpJiYodGhpcy5zaXppbmdUYXJnZXQ9dGhpcy5jb250YWluZWRFbGVtZW50fHx0aGlzKX0sZGV0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLmNhbmNlbEFuaW1hdGlvbigpfSxfb3BlbmVkQ2hhbmdlZDpmdW5jdGlvbigpe3RoaXMub3BlbmVkJiZ0aGlzLmRpc2FibGVkP3RoaXMuY2FuY2VsKCk6KHRoaXMuY2FuY2VsQW5pbWF0aW9uKCksdGhpcy5fdXBkYXRlQW5pbWF0aW9uQ29uZmlnKCksUG0uX29wZW5lZENoYW5nZWQuYXBwbHkodGhpcyxhcmd1bWVudHMpKX0sX3JlbmRlck9wZW5lZDpmdW5jdGlvbigpeyF0aGlzLm5vQW5pbWF0aW9ucyYmdGhpcy5hbmltYXRpb25Db25maWcub3Blbj8odGhpcy4kLmNvbnRlbnRXcmFwcGVyLmNsYXNzTGlzdC5hZGQoImFuaW1hdGluZyIpLHRoaXMucGxheUFuaW1hdGlvbigib3BlbiIpKTpQbS5fcmVuZGVyT3BlbmVkLmFwcGx5KHRoaXMsYXJndW1lbnRzKX0sX3JlbmRlckNsb3NlZDpmdW5jdGlvbigpeyF0aGlzLm5vQW5pbWF0aW9ucyYmdGhpcy5hbmltYXRpb25Db25maWcuY2xvc2U/KHRoaXMuJC5jb250ZW50V3JhcHBlci5jbGFzc0xpc3QuYWRkKCJhbmltYXRpbmciKSx0aGlzLnBsYXlBbmltYXRpb24oImNsb3NlIikpOlBtLl9yZW5kZXJDbG9zZWQuYXBwbHkodGhpcyxhcmd1bWVudHMpfSxfb25OZW9uQW5pbWF0aW9uRmluaXNoOmZ1bmN0aW9uKCl7dGhpcy4kLmNvbnRlbnRXcmFwcGVyLmNsYXNzTGlzdC5yZW1vdmUoImFuaW1hdGluZyIpLHRoaXMub3BlbmVkP3RoaXMuX2ZpbmlzaFJlbmRlck9wZW5lZCgpOnRoaXMuX2ZpbmlzaFJlbmRlckNsb3NlZCgpfSxfdXBkYXRlQW5pbWF0aW9uQ29uZmlnOmZ1bmN0aW9uKCl7Zm9yKHZhciBlPXRoaXMuY29udGFpbmVkRWxlbWVudCx0PVtdLmNvbmNhdCh0aGlzLm9wZW5BbmltYXRpb25Db25maWd8fFtdKS5jb25jYXQodGhpcy5jbG9zZUFuaW1hdGlvbkNvbmZpZ3x8W10pLHI9MDtyPHQubGVuZ3RoO3IrKyl0W3JdLm5vZGU9ZTt0aGlzLmFuaW1hdGlvbkNvbmZpZz17b3Blbjp0aGlzLm9wZW5BbmltYXRpb25Db25maWcsY2xvc2U6dGhpcy5jbG9zZUFuaW1hdGlvbkNvbmZpZ319LF91cGRhdGVPdmVybGF5UG9zaXRpb246ZnVuY3Rpb24oKXt0aGlzLmlzQXR0YWNoZWQmJnRoaXMubm90aWZ5UmVzaXplKCl9LF9hbGxvd091dHNpZGVTY3JvbGxDaGFuZ2VkOmZ1bmN0aW9uKGUpeyF0aGlzLl9yZWFkaWVkfHwoZT8oIXRoaXMuc2Nyb2xsQWN0aW9ufHx0aGlzLnNjcm9sbEFjdGlvbj09PSJsb2NrIikmJih0aGlzLnNjcm9sbEFjdGlvbj0icmVmaXQiKTp0aGlzLnNjcm9sbEFjdGlvbj0ibG9jayIpfSxfYXBwbHlGb2N1czpmdW5jdGlvbigpe3ZhciBlPXRoaXMuZm9jdXNUYXJnZXR8fHRoaXMuY29udGFpbmVkRWxlbWVudDtlJiZ0aGlzLm9wZW5lZCYmIXRoaXMubm9BdXRvRm9jdXM/ZS5mb2N1cygpOlBtLl9hcHBseUZvY3VzLmFwcGx5KHRoaXMsYXJndW1lbnRzKX19KTt2YXIgeXA9e3Byb3BlcnRpZXM6e2FuaW1hdGlvblRpbWluZzp7dHlwZTpPYmplY3QsdmFsdWU6ZnVuY3Rpb24oKXtyZXR1cm57ZHVyYXRpb246NTAwLGVhc2luZzoiY3ViaWMtYmV6aWVyKDAuNCwgMCwgMC4yLCAxKSIsZmlsbDoiYm90aCJ9fX19LGlzTmVvbkFuaW1hdGlvbjohMCxjcmVhdGVkOmZ1bmN0aW9uKCl7ZG9jdW1lbnQuYm9keS5hbmltYXRlfHxjb25zb2xlLndhcm4oIk5vIHdlYiBhbmltYXRpb25zIGRldGVjdGVkLiBUaGlzIGVsZW1lbnQgd2lsbCBub3QgZnVuY3Rpb24gd2l0aG91dCBhIHdlYiBhbmltYXRpb25zIHBvbHlmaWxsLiIpfSx0aW1pbmdGcm9tQ29uZmlnOmZ1bmN0aW9uKGUpe2lmKGUudGltaW5nKWZvcih2YXIgdCBpbiBlLnRpbWluZyl0aGlzLmFuaW1hdGlvblRpbWluZ1t0XT1lLnRpbWluZ1t0XTtyZXR1cm4gdGhpcy5hbmltYXRpb25UaW1pbmd9LHNldFByZWZpeGVkUHJvcGVydHk6ZnVuY3Rpb24oZSx0LHIpe2Zvcih2YXIgbj17dHJhbnNmb3JtOlsid2Via2l0VHJhbnNmb3JtIl0sdHJhbnNmb3JtT3JpZ2luOlsibW96VHJhbnNmb3JtT3JpZ2luIiwid2Via2l0VHJhbnNmb3JtT3JpZ2luIl19LGk9blt0XSxvLGE9MDtvPWlbYV07YSsrKWUuc3R5bGVbb109cjtlLnN0eWxlW3RdPXJ9LGNvbXBsZXRlOmZ1bmN0aW9uKGUpe319O1l0KHtpczoiZmFkZS1pbi1hbmltYXRpb24iLGJlaGF2aW9yczpbeXBdLGNvbmZpZ3VyZTpmdW5jdGlvbihlKXt2YXIgdD1lLm5vZGU7cmV0dXJuIHRoaXMuX2VmZmVjdD1uZXcgS2V5ZnJhbWVFZmZlY3QodCxbe29wYWNpdHk6IjAifSx7b3BhY2l0eToiMSJ9XSx0aGlzLnRpbWluZ0Zyb21Db25maWcoZSkpLHRoaXMuX2VmZmVjdH19KTtZdCh7aXM6ImZhZGUtb3V0LWFuaW1hdGlvbiIsYmVoYXZpb3JzOlt5cF0sY29uZmlndXJlOmZ1bmN0aW9uKGUpe3ZhciB0PWUubm9kZTtyZXR1cm4gdGhpcy5fZWZmZWN0PW5ldyBLZXlmcmFtZUVmZmVjdCh0LFt7b3BhY2l0eToiMSJ9LHtvcGFjaXR5OiIwIn1dLHRoaXMudGltaW5nRnJvbUNvbmZpZyhlKSksdGhpcy5fZWZmZWN0fX0pO1l0KHtpczoicGFwZXItbWVudS1ncm93LWhlaWdodC1hbmltYXRpb24iLF90ZW1wbGF0ZTpudWxsLGJlaGF2aW9yczpbeXBdLGNvbmZpZ3VyZTpmdW5jdGlvbihlKXt2YXIgdD1lLm5vZGUscj10LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLG49ci5oZWlnaHQ7cmV0dXJuIHRoaXMuX2VmZmVjdD1uZXcgS2V5ZnJhbWVFZmZlY3QodCxbe2hlaWdodDpuLzIrInB4In0se2hlaWdodDpuKyJweCJ9XSx0aGlzLnRpbWluZ0Zyb21Db25maWcoZSkpLHRoaXMuX2VmZmVjdH19KTtZdCh7aXM6InBhcGVyLW1lbnUtZ3Jvdy13aWR0aC1hbmltYXRpb24iLF90ZW1wbGF0ZTpudWxsLGJlaGF2aW9yczpbeXBdLGNvbmZpZ3VyZTpmdW5jdGlvbihlKXt2YXIgdD1lLm5vZGUscj10LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLG49ci53aWR0aDtyZXR1cm4gdGhpcy5fZWZmZWN0PW5ldyBLZXlmcmFtZUVmZmVjdCh0LFt7d2lkdGg6bi8yKyJweCJ9LHt3aWR0aDpuKyJweCJ9XSx0aGlzLnRpbWluZ0Zyb21Db25maWcoZSkpLHRoaXMuX2VmZmVjdH19KTtZdCh7aXM6InBhcGVyLW1lbnUtc2hyaW5rLXdpZHRoLWFuaW1hdGlvbiIsX3RlbXBsYXRlOm51bGwsYmVoYXZpb3JzOlt5cF0sY29uZmlndXJlOmZ1bmN0aW9uKGUpe3ZhciB0PWUubm9kZSxyPXQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksbj1yLndpZHRoO3JldHVybiB0aGlzLl9lZmZlY3Q9bmV3IEtleWZyYW1lRWZmZWN0KHQsW3t3aWR0aDpuKyJweCJ9LHt3aWR0aDpuLW4vMjArInB4In1dLHRoaXMudGltaW5nRnJvbUNvbmZpZyhlKSksdGhpcy5fZWZmZWN0fX0pO1l0KHtpczoicGFwZXItbWVudS1zaHJpbmstaGVpZ2h0LWFuaW1hdGlvbiIsX3RlbXBsYXRlOm51bGwsYmVoYXZpb3JzOlt5cF0sY29uZmlndXJlOmZ1bmN0aW9uKGUpe3ZhciB0PWUubm9kZSxyPXQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksbj1yLmhlaWdodDtyZXR1cm4gdGhpcy5zZXRQcmVmaXhlZFByb3BlcnR5KHQsInRyYW5zZm9ybU9yaWdpbiIsIjAgMCIpLHRoaXMuX2VmZmVjdD1uZXcgS2V5ZnJhbWVFZmZlY3QodCxbe2hlaWdodDpuKyJweCIsdHJhbnNmb3JtOiJ0cmFuc2xhdGVZKDApIn0se2hlaWdodDpuLzIrInB4Iix0cmFuc2Zvcm06InRyYW5zbGF0ZVkoLTIwcHgpIn1dLHRoaXMudGltaW5nRnJvbUNvbmZpZyhlKSksdGhpcy5fZWZmZWN0fX0pO3ZhciBwRT17QU5JTUFUSU9OX0NVQklDX0JFWklFUjoiY3ViaWMtYmV6aWVyKC4zLC45NSwuNSwxKSIsTUFYX0FOSU1BVElPTl9USU1FX01TOjQwMH0sQmJlPVl0KHtfdGVtcGxhdGU6UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgcGFkZGluZzogOHB4OwogICAgICAgIG91dGxpbmU6IG5vbmU7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLW1lbnUtYnV0dG9uOwogICAgICB9CgogICAgICA6aG9zdChbZGlzYWJsZWRdKSB7CiAgICAgICAgY3Vyc29yOiBhdXRvOwogICAgICAgIGNvbG9yOiB2YXIoLS1kaXNhYmxlZC10ZXh0LWNvbG9yKTsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWVudS1idXR0b24tZGlzYWJsZWQ7CiAgICAgIH0KCiAgICAgIGlyb24tZHJvcGRvd24gewogICAgICAgIEBhcHBseSAtLXBhcGVyLW1lbnUtYnV0dG9uLWRyb3Bkb3duOwogICAgICB9CgogICAgICAuZHJvcGRvd24tY29udGVudCB7CiAgICAgICAgQGFwcGx5IC0tc2hhZG93LWVsZXZhdGlvbi0yZHA7CgogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICBib3JkZXItcmFkaXVzOiAycHg7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItbWVudS1idXR0b24tZHJvcGRvd24tYmFja2dyb3VuZCwgdmFyKC0tcHJpbWFyeS1iYWNrZ3JvdW5kLWNvbG9yKSk7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLW1lbnUtYnV0dG9uLWNvbnRlbnQ7CiAgICAgIH0KCiAgICAgIDpob3N0KFt2ZXJ0aWNhbC1hbGlnbj0idG9wIl0pIC5kcm9wZG93bi1jb250ZW50IHsKICAgICAgICBtYXJnaW4tYm90dG9tOiAyMHB4OwogICAgICAgIG1hcmdpbi10b3A6IC0xMHB4OwogICAgICAgIHRvcDogMTBweDsKICAgICAgfQoKICAgICAgOmhvc3QoW3ZlcnRpY2FsLWFsaWduPSJib3R0b20iXSkgLmRyb3Bkb3duLWNvbnRlbnQgewogICAgICAgIGJvdHRvbTogMTBweDsKICAgICAgICBtYXJnaW4tYm90dG9tOiAtMTBweDsKICAgICAgICBtYXJnaW4tdG9wOiAyMHB4OwogICAgICB9CgogICAgICAjdHJpZ2dlciB7CiAgICAgICAgY3Vyc29yOiBwb2ludGVyOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxkaXYgaWQ9InRyaWdnZXIiIG9uLXRhcD0idG9nZ2xlIj4KICAgICAgPHNsb3QgbmFtZT0iZHJvcGRvd24tdHJpZ2dlciI+PC9zbG90PgogICAgPC9kaXY+CgogICAgPGlyb24tZHJvcGRvd24gaWQ9ImRyb3Bkb3duIiBvcGVuZWQ9Int7b3BlbmVkfX0iIGhvcml6b250YWwtYWxpZ249IltbaG9yaXpvbnRhbEFsaWduXV0iIHZlcnRpY2FsLWFsaWduPSJbW3ZlcnRpY2FsQWxpZ25dXSIgZHluYW1pYy1hbGlnbj0iW1tkeW5hbWljQWxpZ25dXSIgaG9yaXpvbnRhbC1vZmZzZXQ9IltbaG9yaXpvbnRhbE9mZnNldF1dIiB2ZXJ0aWNhbC1vZmZzZXQ9IltbdmVydGljYWxPZmZzZXRdXSIgbm8tb3ZlcmxhcD0iW1tub092ZXJsYXBdXSIgb3Blbi1hbmltYXRpb24tY29uZmlnPSJbW29wZW5BbmltYXRpb25Db25maWddXSIgY2xvc2UtYW5pbWF0aW9uLWNvbmZpZz0iW1tjbG9zZUFuaW1hdGlvbkNvbmZpZ11dIiBuby1hbmltYXRpb25zPSJbW25vQW5pbWF0aW9uc11dIiBmb2N1cy10YXJnZXQ9IltbX2Ryb3Bkb3duQ29udGVudF1dIiBhbGxvdy1vdXRzaWRlLXNjcm9sbD0iW1thbGxvd091dHNpZGVTY3JvbGxdXSIgcmVzdG9yZS1mb2N1cy1vbi1jbG9zZT0iW1tyZXN0b3JlRm9jdXNPbkNsb3NlXV0iIG9uLWlyb24tb3ZlcmxheS1jYW5jZWxlZD0iX19vbklyb25PdmVybGF5Q2FuY2VsZWQiIGV4cGFuZC1zaXppbmctdGFyZ2V0LWZvci1zY3JvbGxiYXJzPSJbW2V4cGFuZFNpemluZ1RhcmdldEZvclNjcm9sbGJhcnNdXSI+CiAgICAgIDxkaXYgc2xvdD0iZHJvcGRvd24tY29udGVudCIgY2xhc3M9ImRyb3Bkb3duLWNvbnRlbnQiPgogICAgICAgIDxzbG90IGlkPSJjb250ZW50IiBuYW1lPSJkcm9wZG93bi1jb250ZW50Ij48L3Nsb3Q+CiAgICAgIDwvZGl2PgogICAgPC9pcm9uLWRyb3Bkb3duPgpgLGlzOiJwYXBlci1tZW51LWJ1dHRvbiIsYmVoYXZpb3JzOltPbyxEaV0scHJvcGVydGllczp7b3BlbmVkOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITEsbm90aWZ5OiEwLG9ic2VydmVyOiJfb3BlbmVkQ2hhbmdlZCJ9LGhvcml6b250YWxBbGlnbjp7dHlwZTpTdHJpbmcsdmFsdWU6ImxlZnQiLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0sdmVydGljYWxBbGlnbjp7dHlwZTpTdHJpbmcsdmFsdWU6InRvcCIscmVmbGVjdFRvQXR0cmlidXRlOiEwfSxkeW5hbWljQWxpZ246e3R5cGU6Qm9vbGVhbn0saG9yaXpvbnRhbE9mZnNldDp7dHlwZTpOdW1iZXIsdmFsdWU6MCxub3RpZnk6ITB9LHZlcnRpY2FsT2Zmc2V0Ont0eXBlOk51bWJlcix2YWx1ZTowLG5vdGlmeTohMH0sbm9PdmVybGFwOnt0eXBlOkJvb2xlYW59LG5vQW5pbWF0aW9uczp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxpZ25vcmVTZWxlY3Q6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sY2xvc2VPbkFjdGl2YXRlOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LG9wZW5BbmltYXRpb25Db25maWc6e3R5cGU6T2JqZWN0LHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuW3tuYW1lOiJmYWRlLWluLWFuaW1hdGlvbiIsdGltaW5nOntkZWxheToxMDAsZHVyYXRpb246MjAwfX0se25hbWU6InBhcGVyLW1lbnUtZ3Jvdy13aWR0aC1hbmltYXRpb24iLHRpbWluZzp7ZGVsYXk6MTAwLGR1cmF0aW9uOjE1MCxlYXNpbmc6cEUuQU5JTUFUSU9OX0NVQklDX0JFWklFUn19LHtuYW1lOiJwYXBlci1tZW51LWdyb3ctaGVpZ2h0LWFuaW1hdGlvbiIsdGltaW5nOntkZWxheToxMDAsZHVyYXRpb246Mjc1LGVhc2luZzpwRS5BTklNQVRJT05fQ1VCSUNfQkVaSUVSfX1dfX0sY2xvc2VBbmltYXRpb25Db25maWc6e3R5cGU6T2JqZWN0LHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuW3tuYW1lOiJmYWRlLW91dC1hbmltYXRpb24iLHRpbWluZzp7ZHVyYXRpb246MTUwfX0se25hbWU6InBhcGVyLW1lbnUtc2hyaW5rLXdpZHRoLWFuaW1hdGlvbiIsdGltaW5nOntkZWxheToxMDAsZHVyYXRpb246NTAsZWFzaW5nOnBFLkFOSU1BVElPTl9DVUJJQ19CRVpJRVJ9fSx7bmFtZToicGFwZXItbWVudS1zaHJpbmstaGVpZ2h0LWFuaW1hdGlvbiIsdGltaW5nOntkdXJhdGlvbjoyMDAsZWFzaW5nOiJlYXNlLWluIn19XX19LGFsbG93T3V0c2lkZVNjcm9sbDp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxyZXN0b3JlRm9jdXNPbkNsb3NlOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITB9LGV4cGFuZFNpemluZ1RhcmdldEZvclNjcm9sbGJhcnM6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sX2Ryb3Bkb3duQ29udGVudDp7dHlwZTpPYmplY3R9fSxob3N0QXR0cmlidXRlczp7cm9sZToiZ3JvdXAiLCJhcmlhLWhhc3BvcHVwIjoidHJ1ZSJ9LGxpc3RlbmVyczp7Imlyb24tYWN0aXZhdGUiOiJfb25Jcm9uQWN0aXZhdGUiLCJpcm9uLXNlbGVjdCI6Il9vbklyb25TZWxlY3QifSxnZXQgY29udGVudEVsZW1lbnQoKXtmb3IodmFyIGU9enQodGhpcy4kLmNvbnRlbnQpLmdldERpc3RyaWJ1dGVkTm9kZXMoKSx0PTAscj1lLmxlbmd0aDt0PHI7dCsrKWlmKGVbdF0ubm9kZVR5cGU9PT1Ob2RlLkVMRU1FTlRfTk9ERSlyZXR1cm4gZVt0XX0sdG9nZ2xlOmZ1bmN0aW9uKCl7dGhpcy5vcGVuZWQ/dGhpcy5jbG9zZSgpOnRoaXMub3BlbigpfSxvcGVuOmZ1bmN0aW9uKCl7dGhpcy5kaXNhYmxlZHx8dGhpcy4kLmRyb3Bkb3duLm9wZW4oKX0sY2xvc2U6ZnVuY3Rpb24oKXt0aGlzLiQuZHJvcGRvd24uY2xvc2UoKX0sX29uSXJvblNlbGVjdDpmdW5jdGlvbihlKXt0aGlzLmlnbm9yZVNlbGVjdHx8dGhpcy5jbG9zZSgpfSxfb25Jcm9uQWN0aXZhdGU6ZnVuY3Rpb24oZSl7dGhpcy5jbG9zZU9uQWN0aXZhdGUmJnRoaXMuY2xvc2UoKX0sX29wZW5lZENoYW5nZWQ6ZnVuY3Rpb24oZSx0KXtlPyh0aGlzLl9kcm9wZG93bkNvbnRlbnQ9dGhpcy5jb250ZW50RWxlbWVudCx0aGlzLmZpcmUoInBhcGVyLWRyb3Bkb3duLW9wZW4iKSk6dCE9bnVsbCYmdGhpcy5maXJlKCJwYXBlci1kcm9wZG93bi1jbG9zZSIpfSxfZGlzYWJsZWRDaGFuZ2VkOmZ1bmN0aW9uKGUpe0RpLl9kaXNhYmxlZENoYW5nZWQuYXBwbHkodGhpcyxhcmd1bWVudHMpLGUmJnRoaXMub3BlbmVkJiZ0aGlzLmNsb3NlKCl9LF9fb25Jcm9uT3ZlcmxheUNhbmNlbGVkOmZ1bmN0aW9uKGUpe3ZhciB0PWUuZGV0YWlsLHI9dGhpcy4kLnRyaWdnZXIsbj16dCh0KS5wYXRoO24uaW5kZXhPZihyKT4tMSYmZS5wcmV2ZW50RGVmYXVsdCgpfX0pO09iamVjdC5rZXlzKHBFKS5mb3JFYWNoKGZ1bmN0aW9uKGUpe0JiZVtlXT1wRVtlXX0pO3ZhciB4Vz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZW1wbGF0ZSIpO3hXLnNldEF0dHJpYnV0ZSgic3R5bGUiLCJkaXNwbGF5OiBub25lOyIpO3hXLmlubmVySFRNTD1gPGlyb24taWNvbnNldC1zdmcgbmFtZT0icGFwZXItZHJvcGRvd24tbWVudSIgc2l6ZT0iMjQiPgo8c3ZnPjxkZWZzPgo8ZyBpZD0iYXJyb3ctZHJvcC1kb3duIj48cGF0aCBkPSJNNyAxMGw1IDUgNS01eiI+PC9wYXRoPjwvZz4KPC9kZWZzPjwvc3ZnPgo8L2lyb24taWNvbnNldC1zdmc+YDtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHhXLmNvbnRlbnQpO3ZhciBiVz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZW1wbGF0ZSIpO2JXLnNldEF0dHJpYnV0ZSgic3R5bGUiLCJkaXNwbGF5OiBub25lOyIpO2JXLmlubmVySFRNTD1gPGRvbS1tb2R1bGUgaWQ9InBhcGVyLWRyb3Bkb3duLW1lbnUtc2hhcmVkLXN0eWxlcyI+CiAgPHRlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICB0ZXh0LWFsaWduOiBsZWZ0OwoKICAgICAgICAvKiBOT1RFKGNkYXRhKTogQm90aCB2YWx1ZXMgYXJlIG5lZWRlZCwgc2luY2Ugc29tZSBwaG9uZXMgcmVxdWlyZSB0aGUKICAgICAgICAgKiB2YWx1ZSB0byBiZSBcYHRyYW5zcGFyZW50XGAuCiAgICAgICAgICovCiAgICAgICAgLXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yOiByZ2JhKDAsMCwwLDApOwogICAgICAgIC13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjogdHJhbnNwYXJlbnQ7CgogICAgICAgIC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWlucHV0OiB7CiAgICAgICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgICAgICAgd2hpdGUtc3BhY2U6IG5vd3JhcDsKICAgICAgICAgIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzOwogICAgICAgICAgbWF4LXdpZHRoOiAxMDAlOwogICAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsKICAgICAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgICAgICB9OwoKICAgICAgICBAYXBwbHkgLS1wYXBlci1kcm9wZG93bi1tZW51OwogICAgICB9CgogICAgICAvKiBwYXBlci1kcm9wZG93bi1tZW51IGFuZCBwYXBlci1kcm9wZG93bi1tZW51LWxpZ2h0IGJvdGggZGVsZWdhdGUgZm9jdXMKICAgICAgICogdG8gb3RoZXIgaW50ZXJuYWwgZWxlbWVudHMgd2hpY2ggbWFuYWdlIGZvY3VzIHN0eWxpbmcuICovCiAgICAgIDpob3N0KDpmb2N1cykgewogICAgICAgIG91dGxpbmU6IG5vbmU7CiAgICAgIH0KCiAgICAgIDpob3N0KDpkaXIocnRsKSkgewogICAgICAgIHRleHQtYWxpZ246IHJpZ2h0OwoKICAgICAgICBAYXBwbHkoLS1wYXBlci1kcm9wZG93bi1tZW51KTsKICAgICAgfQoKICAgICAgOmhvc3QoW2Rpc2FibGVkXSkgewogICAgICAgIEBhcHBseSAtLXBhcGVyLWRyb3Bkb3duLW1lbnUtZGlzYWJsZWQ7CiAgICAgIH0KCiAgICAgIDpob3N0KFtub2lua10pIHBhcGVyLXJpcHBsZSB7CiAgICAgICAgZGlzcGxheTogbm9uZTsKICAgICAgfQoKICAgICAgOmhvc3QoW25vLWxhYmVsLWZsb2F0XSkgcGFwZXItcmlwcGxlIHsKICAgICAgICB0b3A6IDhweDsKICAgICAgfQoKICAgICAgcGFwZXItcmlwcGxlIHsKICAgICAgICB0b3A6IDEycHg7CiAgICAgICAgbGVmdDogMHB4OwogICAgICAgIGJvdHRvbTogOHB4OwogICAgICAgIHJpZ2h0OiAwcHg7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLWRyb3Bkb3duLW1lbnUtcmlwcGxlOwogICAgICB9CgogICAgICBwYXBlci1tZW51LWJ1dHRvbiB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgcGFkZGluZzogMDsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItZHJvcGRvd24tbWVudS1idXR0b247CiAgICAgIH0KCiAgICAgIHBhcGVyLWlucHV0IHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1kcm9wZG93bi1tZW51LWlucHV0OwogICAgICB9CgogICAgICBpcm9uLWljb24gewogICAgICAgIGNvbG9yOiB2YXIoLS1kaXNhYmxlZC10ZXh0LWNvbG9yKTsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItZHJvcGRvd24tbWVudS1pY29uOwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvdGVtcGxhdGU+CjwvZG9tLW1vZHVsZT5gO2RvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoYlcuY29udGVudCk7dmFyIEhiZT1HdChIVE1MRWxlbWVudCk7WXQoe190ZW1wbGF0ZTpRYAogICAgPHN0eWxlIGluY2x1ZGU9InBhcGVyLWRyb3Bkb3duLW1lbnUtc2hhcmVkLXN0eWxlcyI+PC9zdHlsZT4KCiAgICA8cGFwZXItbWVudS1idXR0b24gaWQ9Im1lbnVCdXR0b24iIHZlcnRpY2FsLWFsaWduPSJbW3ZlcnRpY2FsQWxpZ25dXSIgaG9yaXpvbnRhbC1hbGlnbj0iW1tob3Jpem9udGFsQWxpZ25dXSIgZHluYW1pYy1hbGlnbj0iW1tkeW5hbWljQWxpZ25dXSIgdmVydGljYWwtb2Zmc2V0PSJbW19jb21wdXRlTWVudVZlcnRpY2FsT2Zmc2V0KG5vTGFiZWxGbG9hdCwgdmVydGljYWxPZmZzZXQpXV0iIGRpc2FibGVkPSJbW2Rpc2FibGVkXV0iIG5vLWFuaW1hdGlvbnM9Iltbbm9BbmltYXRpb25zXV0iIG9uLWlyb24tc2VsZWN0PSJfb25Jcm9uU2VsZWN0IiBvbi1pcm9uLWRlc2VsZWN0PSJfb25Jcm9uRGVzZWxlY3QiIG9wZW5lZD0ie3tvcGVuZWR9fSIgY2xvc2Utb24tYWN0aXZhdGUgYWxsb3ctb3V0c2lkZS1zY3JvbGw9IltbYWxsb3dPdXRzaWRlU2Nyb2xsXV0iIHJlc3RvcmUtZm9jdXMtb24tY2xvc2U9IltbcmVzdG9yZUZvY3VzT25DbG9zZV1dIiBleHBhbmQtc2l6aW5nLXRhcmdldC1mb3Itc2Nyb2xsYmFycz0iW1tleHBhbmRTaXppbmdUYXJnZXRGb3JTY3JvbGxiYXJzXV0iPgogICAgICA8IS0tIHN1cHBvcnQgaHlicmlkIG1vZGU6IHVzZXIgbWlnaHQgYmUgdXNpbmcgcGFwZXItbWVudS1idXR0b24gMS54IHdoaWNoIGRpc3RyaWJ1dGVzIHZpYSA8Y29udGVudD4gLS0+CiAgICAgIDxkaXYgY2xhc3M9ImRyb3Bkb3duLXRyaWdnZXIiIHNsb3Q9ImRyb3Bkb3duLXRyaWdnZXIiPgogICAgICAgIDxwYXBlci1yaXBwbGU+PC9wYXBlci1yaXBwbGU+CiAgICAgICAgPCEtLSBwYXBlci1pbnB1dCBoYXMgdHlwZT0idGV4dCIgZm9yIGExMXksIGRvIG5vdCByZW1vdmUgLS0+CiAgICAgICAgPHBhcGVyLWlucHV0IGlkPSJpbnB1dCIgdHlwZT0idGV4dCIgaW52YWxpZD0iW1tpbnZhbGlkXV0iIHJlYWRvbmx5IGRpc2FibGVkPSJbW2Rpc2FibGVkXV0iIHZhbHVlPSJbW3ZhbHVlXV0iIHBsYWNlaG9sZGVyPSJbW3BsYWNlaG9sZGVyXV0iIGVycm9yLW1lc3NhZ2U9IltbZXJyb3JNZXNzYWdlXV0iIGFsd2F5cy1mbG9hdC1sYWJlbD0iW1thbHdheXNGbG9hdExhYmVsXV0iIG5vLWxhYmVsLWZsb2F0PSJbW25vTGFiZWxGbG9hdF1dIiBsYWJlbD0iW1tsYWJlbF1dIiBpbnB1dC1yb2xlPSJidXR0b24iIGlucHV0LWFyaWEtaGFzcG9wdXA9Imxpc3Rib3giIGF1dG9jb21wbGV0ZT0ib2ZmIj4KICAgICAgICAgIDwhLS0gc3VwcG9ydCBoeWJyaWQgbW9kZTogdXNlciBtaWdodCBiZSB1c2luZyBwYXBlci1pbnB1dCAxLnggd2hpY2ggZGlzdHJpYnV0ZXMgdmlhIDxjb250ZW50PiAtLT4KICAgICAgICAgIDxpcm9uLWljb24gaWNvbj0icGFwZXItZHJvcGRvd24tbWVudTphcnJvdy1kcm9wLWRvd24iIHN1ZmZpeCBzbG90PSJzdWZmaXgiPjwvaXJvbi1pY29uPgogICAgICAgIDwvcGFwZXItaW5wdXQ+CiAgICAgIDwvZGl2PgogICAgICA8c2xvdCBpZD0iY29udGVudCIgbmFtZT0iZHJvcGRvd24tY29udGVudCIgc2xvdD0iZHJvcGRvd24tY29udGVudCI+PC9zbG90PgogICAgPC9wYXBlci1tZW51LWJ1dHRvbj4KYCxpczoicGFwZXItZHJvcGRvd24tbWVudSIsYmVoYXZpb3JzOltTaCxEaSxFaCxUaF0scHJvcGVydGllczp7c2VsZWN0ZWRJdGVtTGFiZWw6e3R5cGU6U3RyaW5nLG5vdGlmeTohMCxyZWFkT25seTohMH0sc2VsZWN0ZWRJdGVtOnt0eXBlOk9iamVjdCxub3RpZnk6ITAscmVhZE9ubHk6ITB9LHZhbHVlOnt0eXBlOlN0cmluZyxub3RpZnk6ITB9LGxhYmVsOnt0eXBlOlN0cmluZ30scGxhY2Vob2xkZXI6e3R5cGU6U3RyaW5nfSxlcnJvck1lc3NhZ2U6e3R5cGU6U3RyaW5nfSxvcGVuZWQ6e3R5cGU6Qm9vbGVhbixub3RpZnk6ITAsdmFsdWU6ITEsb2JzZXJ2ZXI6Il9vcGVuZWRDaGFuZ2VkIn0sYWxsb3dPdXRzaWRlU2Nyb2xsOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LG5vTGFiZWxGbG9hdDp7dHlwZTpCb29sZWFuLHZhbHVlOiExLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0sYWx3YXlzRmxvYXRMYWJlbDp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxub0FuaW1hdGlvbnM6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0saG9yaXpvbnRhbEFsaWduOnt0eXBlOlN0cmluZyx2YWx1ZToicmlnaHQifSx2ZXJ0aWNhbEFsaWduOnt0eXBlOlN0cmluZyx2YWx1ZToidG9wIn0sdmVydGljYWxPZmZzZXQ6TnVtYmVyLGR5bmFtaWNBbGlnbjp7dHlwZTpCb29sZWFufSxyZXN0b3JlRm9jdXNPbkNsb3NlOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITB9LGV4cGFuZFNpemluZ1RhcmdldEZvclNjcm9sbGJhcnM6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX19LGxpc3RlbmVyczp7dGFwOiJfb25UYXAifSxrZXlCaW5kaW5nczp7InVwIGRvd24iOiJvcGVuIixlc2M6ImNsb3NlIn0sb2JzZXJ2ZXJzOlsiX3NlbGVjdGVkSXRlbUNoYW5nZWQoc2VsZWN0ZWRJdGVtKSJdLF9hdHRhY2hEb20oZSl7bGV0IHQ9dWUodGhpcyk7cmV0dXJuIHQuYXR0YWNoU2hhZG93KHttb2RlOiJvcGVuIixkZWxlZ2F0ZXNGb2N1czohMCxzaGFkeVVwZ3JhZGVGcmFnbWVudDplfSksdC5zaGFkb3dSb290LmFwcGVuZENoaWxkKGUpLEhiZS5wcm90b3R5cGUuX2F0dGFjaERvbS5jYWxsKHRoaXMsZSl9LGZvY3VzKCl7dGhpcy4kLmlucHV0Ll9mb2N1c2FibGVFbGVtZW50LmZvY3VzKCl9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5jb250ZW50RWxlbWVudDtlJiZlLnNlbGVjdGVkSXRlbSYmdGhpcy5fc2V0U2VsZWN0ZWRJdGVtKGUuc2VsZWN0ZWRJdGVtKX0sZ2V0IGNvbnRlbnRFbGVtZW50KCl7Zm9yKHZhciBlPXp0KHRoaXMuJC5jb250ZW50KS5nZXREaXN0cmlidXRlZE5vZGVzKCksdD0wLHI9ZS5sZW5ndGg7dDxyO3QrKylpZihlW3RdLm5vZGVUeXBlPT09Tm9kZS5FTEVNRU5UX05PREUpcmV0dXJuIGVbdF19LG9wZW46ZnVuY3Rpb24oKXt0aGlzLiQubWVudUJ1dHRvbi5vcGVuKCl9LGNsb3NlOmZ1bmN0aW9uKCl7dGhpcy4kLm1lbnVCdXR0b24uY2xvc2UoKX0sX29uSXJvblNlbGVjdDpmdW5jdGlvbihlKXt0aGlzLl9zZXRTZWxlY3RlZEl0ZW0oZS5kZXRhaWwuaXRlbSl9LF9vbklyb25EZXNlbGVjdDpmdW5jdGlvbihlKXt0aGlzLl9zZXRTZWxlY3RlZEl0ZW0obnVsbCl9LF9vblRhcDpmdW5jdGlvbihlKXtpZ3QoZSk9PT10aGlzJiZ0aGlzLm9wZW4oKX0sX3NlbGVjdGVkSXRlbUNoYW5nZWQ6ZnVuY3Rpb24oZSl7dmFyIHQ9IiI7ZT90PWUubGFiZWx8fGUuZ2V0QXR0cmlidXRlKCJsYWJlbCIpfHxlLnRleHRDb250ZW50LnRyaW0oKTp0PSIiLHRoaXMudmFsdWU9dCx0aGlzLl9zZXRTZWxlY3RlZEl0ZW1MYWJlbCh0KX0sX2NvbXB1dGVNZW51VmVydGljYWxPZmZzZXQ6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gdHx8KGU/LTQ6OCl9LF9nZXRWYWxpZGl0eTpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5kaXNhYmxlZHx8IXRoaXMucmVxdWlyZWR8fHRoaXMucmVxdWlyZWQmJiEhdGhpcy52YWx1ZX0sX29wZW5lZENoYW5nZWQ6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLm9wZW5lZD8idHJ1ZSI6ImZhbHNlIix0PXRoaXMuY29udGVudEVsZW1lbnQ7dCYmdC5zZXRBdHRyaWJ1dGUoImFyaWEtZXhwYW5kZWQiLGUpfX0pO3ZhciB3Vz0xLE0wdD0yLEE5PXtvdXRlclNjcm9sbDp7c2Nyb2xsOiEwfSxzaGFkb3dNb2RlOntzdGFuZGFyZDpNMHQsd2F0ZXJmYWxsOndXLCJ3YXRlcmZhbGwtdGFsbCI6d1d9LHRhbGxNb2RlOnsid2F0ZXJmYWxsLXRhbGwiOiEwfX07WXQoe190ZW1wbGF0ZTpRYAogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LXZlcnRpY2FsOwogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaGVhZGVyLXBhbmVsOwogICAgICB9CgogICAgICAjbWFpbkNvbnRhaW5lciB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZsZXg7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIG92ZXJmbG93LXk6IGF1dG87CiAgICAgICAgb3ZlcmZsb3cteDogaGlkZGVuOwogICAgICAgIC13ZWJraXQtb3ZlcmZsb3ctc2Nyb2xsaW5nOiB0b3VjaDsKICAgICAgfQoKICAgICAgI21haW5QYW5lbCB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LXZlcnRpY2FsOwogICAgICAgIEBhcHBseSAtLWxheW91dC1mbGV4OwogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICBtaW4taGVpZ2h0OiAwOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWhlYWRlci1wYW5lbC1ib2R5OwogICAgICB9CgogICAgICAjbWFpbkNvbnRhaW5lciB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaGVhZGVyLXBhbmVsLWNvbnRhaW5lcjsKICAgICAgfQoKICAgICAgLyoKICAgICAgICogbW9kZTogc2Nyb2xsCiAgICAgICAqLwogICAgICA6aG9zdChbbW9kZT1zY3JvbGxdKSAjbWFpbkNvbnRhaW5lciB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaGVhZGVyLXBhbmVsLXNjcm9sbC1jb250YWluZXI7CiAgICAgICAgb3ZlcmZsb3c6IHZpc2libGU7CiAgICAgIH0KCiAgICAgIDpob3N0KFttb2RlPXNjcm9sbF0pIHsKICAgICAgICBvdmVyZmxvdy15OiBhdXRvOwogICAgICAgIG92ZXJmbG93LXg6IGhpZGRlbjsKICAgICAgICAtd2Via2l0LW92ZXJmbG93LXNjcm9sbGluZzogdG91Y2g7CiAgICAgIH0KCiAgICAgIC8qCiAgICAgICAqIG1vZGU6IGNvdmVyCiAgICAgICAqLwogICAgICA6aG9zdChbbW9kZT1jb3Zlcl0pICNtYWluQ29udGFpbmVyIHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1oZWFkZXItcGFuZWwtY292ZXItY29udGFpbmVyOwogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICB0b3A6IDA7CiAgICAgICAgcmlnaHQ6IDA7CiAgICAgICAgYm90dG9tOiAwOwogICAgICAgIGxlZnQ6IDA7CiAgICAgIH0KCiAgICAgIDpob3N0KFttb2RlPWNvdmVyXSkgI21haW5QYW5lbCB7CiAgICAgICAgcG9zaXRpb246IHN0YXRpYzsKICAgICAgfQoKICAgICAgLyoKICAgICAgICogbW9kZTogc3RhbmRhcmQKICAgICAgICovCiAgICAgIDpob3N0KFttb2RlPXN0YW5kYXJkXSkgI21haW5Db250YWluZXIgewogICAgICAgIEBhcHBseSAtLXBhcGVyLWhlYWRlci1wYW5lbC1zdGFuZGFyZC1jb250YWluZXI7CiAgICAgIH0KCiAgICAgIC8qCiAgICAgICAqIG1vZGU6IHNlYW1lZAogICAgICAgKi8KICAgICAgOmhvc3QoW21vZGU9c2VhbWVkXSkgI21haW5Db250YWluZXIgewogICAgICAgIEBhcHBseSAtLXBhcGVyLWhlYWRlci1wYW5lbC1zZWFtZWQtY29udGFpbmVyOwogICAgICB9CgoKICAgICAgLyoKICAgICAgICogbW9kZTogd2F0ZXJmYWxsCiAgICAgICAqLwogICAgICA6aG9zdChbbW9kZT13YXRlcmZhbGxdKSAjbWFpbkNvbnRhaW5lciB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaGVhZGVyLXBhbmVsLXdhdGVyZmFsbC1jb250YWluZXI7CiAgICAgIH0KCiAgICAgIC8qCiAgICAgICAqIG1vZGU6IHdhdGVyZmFsbC10YWxsCiAgICAgICAqLwogICAgICA6aG9zdChbbW9kZT13YXRlcmZhbGwtdGFsbF0pICNtYWluQ29udGFpbmVyIHsKICAgICAgICBAYXBwbHkgLS1wYXBlci1oZWFkZXItcGFuZWwtd2F0ZXJmYWxsLXRhbGwtY29udGFpbmVyOwogICAgICB9CgogICAgICAjZHJvcFNoYWRvdyB7CiAgICAgICAgdHJhbnNpdGlvbjogb3BhY2l0eSAwLjVzOwogICAgICAgIGhlaWdodDogNnB4OwogICAgICAgIGJveC1zaGFkb3c6IGluc2V0IDBweCA1cHggNnB4IC0zcHggcmdiYSgwLCAwLCAwLCAwLjQpOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWhlYWRlci1wYW5lbC1zaGFkb3c7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIHRvcDogMDsKICAgICAgICBsZWZ0OiAwOwogICAgICAgIHJpZ2h0OiAwOwogICAgICAgIG9wYWNpdHk6IDA7CiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgIH0KCiAgICAgICNkcm9wU2hhZG93Lmhhcy1zaGFkb3cgewogICAgICAgIG9wYWNpdHk6IDE7CiAgICAgIH0KCiAgICAgICNtYWluQ29udGFpbmVyID4gOjpzbG90dGVkKC5maXQpIHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtZml0OwogICAgICB9CgogICAgPC9zdHlsZT4KCiAgICA8c2xvdCBpZD0iaGVhZGVyU2xvdCIgbmFtZT0iaGVhZGVyIj48L3Nsb3Q+CgogICAgPGRpdiBpZD0ibWFpblBhbmVsIj4KICAgICAgPGRpdiBpZD0ibWFpbkNvbnRhaW5lciIgY2xhc3NcJD0iW1tfY29tcHV0ZU1haW5Db250YWluZXJDbGFzcyhtb2RlKV1dIj4KICAgICAgICA8c2xvdD48L3Nsb3Q+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGlkPSJkcm9wU2hhZG93Ij48L2Rpdj4KICAgIDwvZGl2PgpgLGlzOiJwYXBlci1oZWFkZXItcGFuZWwiLHByb3BlcnRpZXM6e21vZGU6e3R5cGU6U3RyaW5nLHZhbHVlOiJzdGFuZGFyZCIsb2JzZXJ2ZXI6Il9tb2RlQ2hhbmdlZCIscmVmbGVjdFRvQXR0cmlidXRlOiEwfSxzaGFkb3c6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sdGFsbENsYXNzOnt0eXBlOlN0cmluZyx2YWx1ZToidGFsbCJ9LGF0VG9wOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITAsbm90aWZ5OiEwLHJlYWRPbmx5OiEwLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH19LG9ic2VydmVyczpbIl9jb21wdXRlRHJvcFNoYWRvd0hpZGRlbihhdFRvcCwgbW9kZSwgc2hhZG93KSJdLGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5fYWRkTGlzdGVuZXIoKSx0aGlzLl9rZWVwU2Nyb2xsaW5nU3RhdGUoKX0sZGV0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLl9yZW1vdmVMaXN0ZW5lcigpfSxyZWFkeTpmdW5jdGlvbigpe3RoaXMuc2Nyb2xsSGFuZGxlcj10aGlzLl9zY3JvbGwuYmluZCh0aGlzKSxjb25zb2xlLndhcm4odGhpcy5pcywiaXMgZGVwcmVjYXRlZC4gUGxlYXNlIHVzZSBhcHAtbGF5b3V0IGluc3RlYWQhIil9LGdldCBoZWFkZXIoKXtyZXR1cm4genQodGhpcy4kLmhlYWRlclNsb3QpLmdldERpc3RyaWJ1dGVkTm9kZXMoKVswXX0sZ2V0IHNjcm9sbGVyKCl7cmV0dXJuIHRoaXMuX2dldFNjcm9sbGVyRm9yTW9kZSh0aGlzLm1vZGUpfSxnZXQgdmlzaWJsZVNoYWRvdygpe3JldHVybiB0aGlzLiQuZHJvcFNoYWRvdy5jbGFzc0xpc3QuY29udGFpbnMoImhhcy1zaGFkb3ciKX0sX2NvbXB1dGVEcm9wU2hhZG93SGlkZGVuOmZ1bmN0aW9uKGUsdCxyKXt2YXIgbj1BOS5zaGFkb3dNb2RlW3RdO3RoaXMuc2hhZG93P3RoaXMudG9nZ2xlQ2xhc3MoImhhcy1zaGFkb3ciLCEwLHRoaXMuJC5kcm9wU2hhZG93KTpuPT09TTB0P3RoaXMudG9nZ2xlQ2xhc3MoImhhcy1zaGFkb3ciLCEwLHRoaXMuJC5kcm9wU2hhZG93KTpuPT09d1cmJiFlP3RoaXMudG9nZ2xlQ2xhc3MoImhhcy1zaGFkb3ciLCEwLHRoaXMuJC5kcm9wU2hhZG93KTp0aGlzLnRvZ2dsZUNsYXNzKCJoYXMtc2hhZG93IiwhMSx0aGlzLiQuZHJvcFNoYWRvdyl9LF9jb21wdXRlTWFpbkNvbnRhaW5lckNsYXNzOmZ1bmN0aW9uKGUpe3ZhciB0PXt9O3JldHVybiB0LmZsZXg9ZSE9PSJjb3ZlciIsT2JqZWN0LmtleXModCkuZmlsdGVyKGZ1bmN0aW9uKHIpe3JldHVybiB0W3JdfSkuam9pbigiICIpfSxfYWRkTGlzdGVuZXI6ZnVuY3Rpb24oKXt0aGlzLnNjcm9sbGVyLmFkZEV2ZW50TGlzdGVuZXIoInNjcm9sbCIsdGhpcy5zY3JvbGxIYW5kbGVyKX0sX3JlbW92ZUxpc3RlbmVyOmZ1bmN0aW9uKCl7dGhpcy5zY3JvbGxlci5yZW1vdmVFdmVudExpc3RlbmVyKCJzY3JvbGwiLHRoaXMuc2Nyb2xsSGFuZGxlcil9LF9tb2RlQ2hhbmdlZDpmdW5jdGlvbihlLHQpe3ZhciByPUE5LG49dGhpcy5oZWFkZXIsaT0yMDA7biYmKHIudGFsbE1vZGVbdF0mJiFyLnRhbGxNb2RlW2VdPyhuLmNsYXNzTGlzdC5yZW1vdmUodGhpcy50YWxsQ2xhc3MpLHRoaXMuYXN5bmMoZnVuY3Rpb24oKXtuLmNsYXNzTGlzdC5yZW1vdmUoImFuaW1hdGUiKX0saSkpOnRoaXMudG9nZ2xlQ2xhc3MoImFuaW1hdGUiLHIudGFsbE1vZGVbZV0sbikpLHRoaXMuX2tlZXBTY3JvbGxpbmdTdGF0ZSgpfSxfa2VlcFNjcm9sbGluZ1N0YXRlOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5zY3JvbGxlcix0PXRoaXMuaGVhZGVyO3RoaXMuX3NldEF0VG9wKGUuc2Nyb2xsVG9wPT09MCksdCYmdGhpcy50YWxsQ2xhc3MmJkE5LnRhbGxNb2RlW3RoaXMubW9kZV0mJnRoaXMudG9nZ2xlQ2xhc3ModGhpcy50YWxsQ2xhc3MsdGhpcy5hdFRvcHx8dC5jbGFzc0xpc3QuY29udGFpbnModGhpcy50YWxsQ2xhc3MpJiZlLnNjcm9sbEhlaWdodDx0aGlzLm9mZnNldEhlaWdodCx0KX0sX3Njcm9sbDpmdW5jdGlvbigpe3RoaXMuX2tlZXBTY3JvbGxpbmdTdGF0ZSgpLHRoaXMuZmlyZSgiY29udGVudC1zY3JvbGwiLHt0YXJnZXQ6dGhpcy5zY3JvbGxlcn0se2J1YmJsZXM6ITF9KX0sX2dldFNjcm9sbGVyRm9yTW9kZTpmdW5jdGlvbihlKXtyZXR1cm4gQTkub3V0ZXJTY3JvbGxbZV0/dGhpczp0aGlzLiQubWFpbkNvbnRhaW5lcn19KTtZdCh7aXM6InBhcGVyLWljb24tYnV0dG9uIixfdGVtcGxhdGU6UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgcGFkZGluZzogOHB4OwogICAgICAgIG91dGxpbmU6IG5vbmU7CiAgICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICAtbW96LXVzZXItc2VsZWN0OiBub25lOwogICAgICAgIC1tcy11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICB1c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICAgICAgei1pbmRleDogMDsKICAgICAgICBsaW5lLWhlaWdodDogMTsKCiAgICAgICAgd2lkdGg6IDQwcHg7CiAgICAgICAgaGVpZ2h0OiA0MHB4OwoKICAgICAgICAvKgogICAgICAgICAgTk9URTogQm90aCB2YWx1ZXMgYXJlIG5lZWRlZCwgc2luY2Ugc29tZSBwaG9uZXMgcmVxdWlyZSB0aGUgdmFsdWUgdG8KICAgICAgICAgIGJlIFxgdHJhbnNwYXJlbnRcYC4KICAgICAgICAqLwogICAgICAgIC13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjogcmdiYSgwLCAwLCAwLCAwKTsKICAgICAgICAtd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6IHRyYW5zcGFyZW50OwoKICAgICAgICAvKiBCZWNhdXNlIG9mIHBvbHltZXIvMjU1OCwgdGhpcyBzdHlsZSBoYXMgbG93ZXIgc3BlY2lmaWNpdHkgdGhhbiAqICovCiAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveCAhaW1wb3J0YW50OwoKICAgICAgICBAYXBwbHkgLS1wYXBlci1pY29uLWJ1dHRvbjsKICAgICAgfQoKICAgICAgOmhvc3QgI2luayB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLWljb24tYnV0dG9uLWluay1jb2xvciwgdmFyKC0tcHJpbWFyeS10ZXh0LWNvbG9yKSk7CiAgICAgICAgb3BhY2l0eTogMC42OwogICAgICB9CgogICAgICA6aG9zdChbZGlzYWJsZWRdKSB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLWljb24tYnV0dG9uLWRpc2FibGVkLXRleHQsIHZhcigtLWRpc2FibGVkLXRleHQtY29sb3IpKTsKICAgICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgICBjdXJzb3I6IGF1dG87CgogICAgICAgIEBhcHBseSAtLXBhcGVyLWljb24tYnV0dG9uLWRpc2FibGVkOwogICAgICB9CgogICAgICA6aG9zdChbaGlkZGVuXSkgewogICAgICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICAgICAgfQoKICAgICAgOmhvc3QoOmhvdmVyKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaWNvbi1idXR0b24taG92ZXI7CiAgICAgIH0KCiAgICAgIGlyb24taWNvbiB7CiAgICAgICAgLS1pcm9uLWljb24td2lkdGg6IDEwMCU7CiAgICAgICAgLS1pcm9uLWljb24taGVpZ2h0OiAxMDAlOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxpcm9uLWljb24gaWQ9Imljb24iIHNyYz0iW1tzcmNdXSIgaWNvbj0iW1tpY29uXV0iCiAgICAgICAgICAgICAgIGFsdCQ9IltbYWx0XV0iPjwvaXJvbi1pY29uPgogIGAsaG9zdEF0dHJpYnV0ZXM6e3JvbGU6ImJ1dHRvbiIsdGFiaW5kZXg6IjAifSxiZWhhdmlvcnM6W2p4XSxyZWdpc3RlcmVkOmZ1bmN0aW9uKCl7dGhpcy5fdGVtcGxhdGUuc2V0QXR0cmlidXRlKCJzdHJpcC13aGl0ZXNwYWNlIiwiIil9LHByb3BlcnRpZXM6e3NyYzp7dHlwZTpTdHJpbmd9LGljb246e3R5cGU6U3RyaW5nfSxhbHQ6e3R5cGU6U3RyaW5nLG9ic2VydmVyOiJfYWx0Q2hhbmdlZCJ9fSxfYWx0Q2hhbmdlZDpmdW5jdGlvbihlLHQpe3ZhciByPXRoaXMuZ2V0QXR0cmlidXRlKCJhcmlhLWxhYmVsIik7KCFyfHx0PT1yKSYmdGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWwiLGUpfX0pO1l0KHtfdGVtcGxhdGU6UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgd2lkdGg6IDQwMHB4OwogICAgICAgIGJvcmRlcjogMXB4IHNvbGlkOwogICAgICAgIHBhZGRpbmc6IDJweDsKICAgICAgICAtbW96LWFwcGVhcmFuY2U6IHRleHRhcmVhOwogICAgICAgIC13ZWJraXQtYXBwZWFyYW5jZTogdGV4dGFyZWE7CiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgfQoKICAgICAgLm1pcnJvci10ZXh0IHsKICAgICAgICB2aXNpYmlsaXR5OiBoaWRkZW47CiAgICAgICAgd29yZC13cmFwOiBicmVhay13b3JkOwogICAgICAgIEBhcHBseSAtLWlyb24tYXV0b2dyb3ctdGV4dGFyZWE7CiAgICAgIH0KCiAgICAgIC5maXQgewogICAgICAgIEBhcHBseSAtLWxheW91dC1maXQ7CiAgICAgIH0KCiAgICAgIHRleHRhcmVhIHsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgb3V0bGluZTogbm9uZTsKICAgICAgICBib3JkZXI6IG5vbmU7CiAgICAgICAgcmVzaXplOiBub25lOwogICAgICAgIGJhY2tncm91bmQ6IGluaGVyaXQ7CiAgICAgICAgY29sb3I6IGluaGVyaXQ7CiAgICAgICAgLyogc2VlIGNvbW1lbnRzIGluIHRlbXBsYXRlICovCiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICAgIGZvbnQtc2l6ZTogaW5oZXJpdDsKICAgICAgICBmb250LWZhbWlseTogaW5oZXJpdDsKICAgICAgICBsaW5lLWhlaWdodDogaW5oZXJpdDsKICAgICAgICB0ZXh0LWFsaWduOiBpbmhlcml0OwogICAgICAgIEBhcHBseSAtLWlyb24tYXV0b2dyb3ctdGV4dGFyZWE7CiAgICAgIH0KCiAgICAgIHRleHRhcmVhOjotd2Via2l0LWlucHV0LXBsYWNlaG9sZGVyIHsKICAgICAgICBAYXBwbHkgLS1pcm9uLWF1dG9ncm93LXRleHRhcmVhLXBsYWNlaG9sZGVyOwogICAgICB9CgogICAgICB0ZXh0YXJlYTotbW96LXBsYWNlaG9sZGVyIHsKICAgICAgICBAYXBwbHkgLS1pcm9uLWF1dG9ncm93LXRleHRhcmVhLXBsYWNlaG9sZGVyOwogICAgICB9CgogICAgICB0ZXh0YXJlYTo6LW1vei1wbGFjZWhvbGRlciB7CiAgICAgICAgQGFwcGx5IC0taXJvbi1hdXRvZ3Jvdy10ZXh0YXJlYS1wbGFjZWhvbGRlcjsKICAgICAgfQoKICAgICAgdGV4dGFyZWE6LW1zLWlucHV0LXBsYWNlaG9sZGVyIHsKICAgICAgICBAYXBwbHkgLS1pcm9uLWF1dG9ncm93LXRleHRhcmVhLXBsYWNlaG9sZGVyOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDwhLS0gdGhlIG1pcnJvciBzaXplcyB0aGUgaW5wdXQvdGV4dGFyZWEgc28gaXQgZ3Jvd3Mgd2l0aCB0eXBpbmcgLS0+CiAgICA8IS0tIHVzZSAmIzE2MDsgaW5zdGVhZCAmbmJzcDsgb2YgdG8gYWxsb3cgdGhpcyBlbGVtZW50IHRvIGJlIHVzZWQgaW4gWEhUTUwgLS0+CiAgICA8ZGl2IGlkPSJtaXJyb3IiIGNsYXNzPSJtaXJyb3ItdGV4dCIgYXJpYS1oaWRkZW49InRydWUiPiZuYnNwOzwvZGl2PgoKICAgIDwhLS0gc2l6ZSB0aGUgaW5wdXQvdGV4dGFyZWEgd2l0aCBhIGRpdiwgYmVjYXVzZSB0aGUgdGV4dGFyZWEgaGFzIGludHJpbnNpYyBzaXplIGluIGZmIC0tPgogICAgPGRpdiBjbGFzcz0idGV4dGFyZWEtY29udGFpbmVyIGZpdCI+CiAgICAgIDx0ZXh0YXJlYSBpZD0idGV4dGFyZWEiIG5hbWUkPSJbW25hbWVdXSIgYXJpYS1sYWJlbCQ9IltbbGFiZWxdXSIgYXV0b2NvbXBsZXRlJD0iW1thdXRvY29tcGxldGVdXSIgYXV0b2ZvY3VzJD0iW1thdXRvZm9jdXNdXSIgYXV0b2NhcGl0YWxpemUkPSJbW2F1dG9jYXBpdGFsaXplXV0iIGlucHV0bW9kZSQ9IltbaW5wdXRtb2RlXV0iIHBsYWNlaG9sZGVyJD0iW1twbGFjZWhvbGRlcl1dIiByZWFkb25seSQ9IltbcmVhZG9ubHldXSIgcmVxdWlyZWQkPSJbW3JlcXVpcmVkXV0iIGRpc2FibGVkJD0iW1tkaXNhYmxlZF1dIiByb3dzJD0iW1tyb3dzXV0iIG1pbmxlbmd0aCQ9IltbbWlubGVuZ3RoXV0iIG1heGxlbmd0aCQ9IltbbWF4bGVuZ3RoXV0iPjwvdGV4dGFyZWE+CiAgICA8L2Rpdj4KYCxpczoiaXJvbi1hdXRvZ3Jvdy10ZXh0YXJlYSIsYmVoYXZpb3JzOltUaCxEaV0scHJvcGVydGllczp7dmFsdWU6e29ic2VydmVyOiJfdmFsdWVDaGFuZ2VkIix0eXBlOlN0cmluZyxub3RpZnk6ITB9LGJpbmRWYWx1ZTp7b2JzZXJ2ZXI6Il9iaW5kVmFsdWVDaGFuZ2VkIix0eXBlOlN0cmluZyxub3RpZnk6ITB9LHJvd3M6e3R5cGU6TnVtYmVyLHZhbHVlOjEsb2JzZXJ2ZXI6Il91cGRhdGVDYWNoZWQifSxtYXhSb3dzOnt0eXBlOk51bWJlcix2YWx1ZTowLG9ic2VydmVyOiJfdXBkYXRlQ2FjaGVkIn0sYXV0b2NvbXBsZXRlOnt0eXBlOlN0cmluZyx2YWx1ZToib2ZmIn0sYXV0b2ZvY3VzOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LGF1dG9jYXBpdGFsaXplOnt0eXBlOlN0cmluZyx2YWx1ZToibm9uZSJ9LGlucHV0bW9kZTp7dHlwZTpTdHJpbmd9LHBsYWNlaG9sZGVyOnt0eXBlOlN0cmluZ30scmVhZG9ubHk6e3R5cGU6U3RyaW5nfSxyZXF1aXJlZDp7dHlwZTpCb29sZWFufSxtaW5sZW5ndGg6e3R5cGU6TnVtYmVyfSxtYXhsZW5ndGg6e3R5cGU6TnVtYmVyfSxsYWJlbDp7dHlwZTpTdHJpbmd9fSxsaXN0ZW5lcnM6e2lucHV0OiJfb25JbnB1dCJ9LGdldCB0ZXh0YXJlYSgpe3JldHVybiB0aGlzLiQudGV4dGFyZWF9LGdldCBzZWxlY3Rpb25TdGFydCgpe3JldHVybiB0aGlzLiQudGV4dGFyZWEuc2VsZWN0aW9uU3RhcnR9LGdldCBzZWxlY3Rpb25FbmQoKXtyZXR1cm4gdGhpcy4kLnRleHRhcmVhLnNlbGVjdGlvbkVuZH0sc2V0IHNlbGVjdGlvblN0YXJ0KGUpe3RoaXMuJC50ZXh0YXJlYS5zZWxlY3Rpb25TdGFydD1lfSxzZXQgc2VsZWN0aW9uRW5kKGUpe3RoaXMuJC50ZXh0YXJlYS5zZWxlY3Rpb25FbmQ9ZX0sYXR0YWNoZWQ6ZnVuY3Rpb24oKXt2YXIgZT1uYXZpZ2F0b3IudXNlckFnZW50Lm1hdGNoKC9pUCg/OltvYV1kfGhvbmUpLykmJiFuYXZpZ2F0b3IudXNlckFnZW50Lm1hdGNoKC9PUyAxWzM0NTY3ODldLyk7ZSYmKHRoaXMuJC50ZXh0YXJlYS5zdHlsZS5tYXJnaW5MZWZ0PSItM3B4Iil9LHZhbGlkYXRlOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy4kLnRleHRhcmVhLnZhbGlkaXR5LnZhbGlkO3JldHVybiBlJiYodGhpcy5yZXF1aXJlZCYmdGhpcy52YWx1ZT09PSIiP2U9ITE6dGhpcy5oYXNWYWxpZGF0b3IoKSYmKGU9VGgudmFsaWRhdGUuY2FsbCh0aGlzLHRoaXMudmFsdWUpKSksdGhpcy5pbnZhbGlkPSFlLHRoaXMuZmlyZSgiaXJvbi1pbnB1dC12YWxpZGF0ZSIpLGV9LF9iaW5kVmFsdWVDaGFuZ2VkOmZ1bmN0aW9uKGUpe3RoaXMudmFsdWU9ZX0sX3ZhbHVlQ2hhbmdlZDpmdW5jdGlvbihlKXt2YXIgdD10aGlzLnRleHRhcmVhOyF0fHwodC52YWx1ZSE9PWUmJih0LnZhbHVlPWV8fGU9PT0wP2U6IiIpLHRoaXMuYmluZFZhbHVlPWUsdGhpcy4kLm1pcnJvci5pbm5lckhUTUw9dGhpcy5fdmFsdWVGb3JNaXJyb3IoKSx0aGlzLmZpcmUoImJpbmQtdmFsdWUtY2hhbmdlZCIse3ZhbHVlOnRoaXMuYmluZFZhbHVlfSkpfSxfb25JbnB1dDpmdW5jdGlvbihlKXt2YXIgdD16dChlKS5wYXRoO3RoaXMudmFsdWU9dD90WzBdLnZhbHVlOmUudGFyZ2V0LnZhbHVlfSxfY29uc3RyYWluOmZ1bmN0aW9uKGUpe3ZhciB0O2ZvcihlPWV8fFsiIl0sdGhpcy5tYXhSb3dzPjAmJmUubGVuZ3RoPnRoaXMubWF4Um93cz90PWUuc2xpY2UoMCx0aGlzLm1heFJvd3MpOnQ9ZS5zbGljZSgwKTt0aGlzLnJvd3M+MCYmdC5sZW5ndGg8dGhpcy5yb3dzOyl0LnB1c2goIiIpO3JldHVybiB0LmpvaW4oIjxici8+IikrIiYjMTYwOyJ9LF92YWx1ZUZvck1pcnJvcjpmdW5jdGlvbigpe3ZhciBlPXRoaXMudGV4dGFyZWE7aWYoISFlKXJldHVybiB0aGlzLnRva2Vucz1lJiZlLnZhbHVlP2UudmFsdWUucmVwbGFjZSgvJi9nbSwiJmFtcDsiKS5yZXBsYWNlKC8iL2dtLCImcXVvdDsiKS5yZXBsYWNlKC8nL2dtLCImIzM5OyIpLnJlcGxhY2UoLzwvZ20sIiZsdDsiKS5yZXBsYWNlKC8+L2dtLCImZ3Q7Iikuc3BsaXQoYApgKTpbIiJdLHRoaXMuX2NvbnN0cmFpbih0aGlzLnRva2Vucyl9LF91cGRhdGVDYWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLiQubWlycm9yLmlubmVySFRNTD10aGlzLl9jb25zdHJhaW4odGhpcy50b2tlbnMpfX0pO1l0KHtfdGVtcGxhdGU6UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICB9CgogICAgICA6aG9zdChbaGlkZGVuXSkgewogICAgICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICAgICAgfQoKICAgICAgbGFiZWwgewogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxwYXBlci1pbnB1dC1jb250YWluZXIgbm8tbGFiZWwtZmxvYXQkPSJbW25vTGFiZWxGbG9hdF1dIiBhbHdheXMtZmxvYXQtbGFiZWw9IltbX2NvbXB1dGVBbHdheXNGbG9hdExhYmVsKGFsd2F5c0Zsb2F0TGFiZWwscGxhY2Vob2xkZXIpXV0iIGF1dG8tdmFsaWRhdGUkPSJbW2F1dG9WYWxpZGF0ZV1dIiBkaXNhYmxlZCQ9IltbZGlzYWJsZWRdXSIgaW52YWxpZD0iW1tpbnZhbGlkXV0iPgoKICAgICAgPGxhYmVsIGhpZGRlbiQ9IltbIWxhYmVsXV0iIGFyaWEtaGlkZGVuPSJ0cnVlIiBmb3IkPSJbW19pbnB1dElkXV0iIHNsb3Q9ImxhYmVsIj5bW2xhYmVsXV08L2xhYmVsPgoKICAgICAgPGlyb24tYXV0b2dyb3ctdGV4dGFyZWEgY2xhc3M9InBhcGVyLWlucHV0LWlucHV0IiBzbG90PSJpbnB1dCIgaWQkPSJbW19pbnB1dElkXV0iIGFyaWEtbGFiZWxsZWRieSQ9IltbX2FyaWFMYWJlbGxlZEJ5XV0iIGFyaWEtZGVzY3JpYmVkYnkkPSJbW19hcmlhRGVzY3JpYmVkQnldXSIgYmluZC12YWx1ZT0ie3t2YWx1ZX19IiBpbnZhbGlkPSJ7e2ludmFsaWR9fSIgdmFsaWRhdG9yJD0iW1t2YWxpZGF0b3JdXSIgZGlzYWJsZWQkPSJbW2Rpc2FibGVkXV0iIGF1dG9jb21wbGV0ZSQ9IltbYXV0b2NvbXBsZXRlXV0iIGF1dG9mb2N1cyQ9IltbYXV0b2ZvY3VzXV0iIGlucHV0bW9kZSQ9IltbaW5wdXRtb2RlXV0iIG5hbWUkPSJbW25hbWVdXSIgcGxhY2Vob2xkZXIkPSJbW3BsYWNlaG9sZGVyXV0iIHJlYWRvbmx5JD0iW1tyZWFkb25seV1dIiByZXF1aXJlZCQ9IltbcmVxdWlyZWRdXSIgbWlubGVuZ3RoJD0iW1ttaW5sZW5ndGhdXSIgbWF4bGVuZ3RoJD0iW1ttYXhsZW5ndGhdXSIgYXV0b2NhcGl0YWxpemUkPSJbW2F1dG9jYXBpdGFsaXplXV0iIHJvd3MkPSJbW3Jvd3NdXSIgbWF4LXJvd3MkPSJbW21heFJvd3NdXSIgb24tY2hhbmdlPSJfb25DaGFuZ2UiPjwvaXJvbi1hdXRvZ3Jvdy10ZXh0YXJlYT4KCiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tlcnJvck1lc3NhZ2VdXSI+CiAgICAgICAgPHBhcGVyLWlucHV0LWVycm9yIGFyaWEtbGl2ZT0iYXNzZXJ0aXZlIiBzbG90PSJhZGQtb24iPltbZXJyb3JNZXNzYWdlXV08L3BhcGVyLWlucHV0LWVycm9yPgogICAgICA8L3RlbXBsYXRlPgoKICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW2NoYXJDb3VudGVyXV0iPgogICAgICAgIDxwYXBlci1pbnB1dC1jaGFyLWNvdW50ZXIgc2xvdD0iYWRkLW9uIj48L3BhcGVyLWlucHV0LWNoYXItY291bnRlcj4KICAgICAgPC90ZW1wbGF0ZT4KCiAgICA8L3BhcGVyLWlucHV0LWNvbnRhaW5lcj4KYCxpczoicGFwZXItdGV4dGFyZWEiLGJlaGF2aW9yczpbQzksRWhdLHByb3BlcnRpZXM6e19hcmlhTGFiZWxsZWRCeTp7b2JzZXJ2ZXI6Il9hcmlhTGFiZWxsZWRCeUNoYW5nZWQiLHR5cGU6U3RyaW5nfSxfYXJpYURlc2NyaWJlZEJ5OntvYnNlcnZlcjoiX2FyaWFEZXNjcmliZWRCeUNoYW5nZWQiLHR5cGU6U3RyaW5nfSx2YWx1ZTp7dHlwZTpTdHJpbmd9LHJvd3M6e3R5cGU6TnVtYmVyLHZhbHVlOjF9LG1heFJvd3M6e3R5cGU6TnVtYmVyLHZhbHVlOjB9fSxnZXQgc2VsZWN0aW9uU3RhcnQoKXtyZXR1cm4gdGhpcy4kLmlucHV0LnRleHRhcmVhLnNlbGVjdGlvblN0YXJ0fSxzZXQgc2VsZWN0aW9uU3RhcnQoZSl7dGhpcy4kLmlucHV0LnRleHRhcmVhLnNlbGVjdGlvblN0YXJ0PWV9LGdldCBzZWxlY3Rpb25FbmQoKXtyZXR1cm4gdGhpcy4kLmlucHV0LnRleHRhcmVhLnNlbGVjdGlvbkVuZH0sc2V0IHNlbGVjdGlvbkVuZChlKXt0aGlzLiQuaW5wdXQudGV4dGFyZWEuc2VsZWN0aW9uRW5kPWV9LF9hcmlhTGFiZWxsZWRCeUNoYW5nZWQ6ZnVuY3Rpb24oZSl7dGhpcy5fZm9jdXNhYmxlRWxlbWVudC5zZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWxsZWRieSIsZSl9LF9hcmlhRGVzY3JpYmVkQnlDaGFuZ2VkOmZ1bmN0aW9uKGUpe3RoaXMuX2ZvY3VzYWJsZUVsZW1lbnQuc2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IixlKX0sZ2V0IF9mb2N1c2FibGVFbGVtZW50KCl7cmV0dXJuIHRoaXMuaW5wdXRFbGVtZW50LnRleHRhcmVhfX0pO3ZhciBTVz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZW1wbGF0ZSIpO1NXLnNldEF0dHJpYnV0ZSgic3R5bGUiLCJkaXNwbGF5OiBub25lOyIpO1NXLmlubmVySFRNTD1gPGRvbS1tb2R1bGUgaWQ9InBhcGVyLWl0ZW0tc2hhcmVkLXN0eWxlcyI+CiAgPHRlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICA6aG9zdCwgLnBhcGVyLWl0ZW0gewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICBtaW4taGVpZ2h0OiB2YXIoLS1wYXBlci1pdGVtLW1pbi1oZWlnaHQsIDQ4cHgpOwogICAgICAgIHBhZGRpbmc6IDBweCAxNnB4OwogICAgICB9CgogICAgICAucGFwZXItaXRlbSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1zdWJoZWFkOwogICAgICAgIGJvcmRlcjpub25lOwogICAgICAgIG91dGxpbmU6IG5vbmU7CiAgICAgICAgYmFja2dyb3VuZDogd2hpdGU7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgdGV4dC1hbGlnbjogbGVmdDsKICAgICAgfQoKICAgICAgOmhvc3QoW2hpZGRlbl0pLCAucGFwZXItaXRlbVtoaWRkZW5dIHsKICAgICAgICBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7CiAgICAgIH0KCiAgICAgIDpob3N0KC5pcm9uLXNlbGVjdGVkKSwgLnBhcGVyLWl0ZW0uaXJvbi1zZWxlY3RlZCB7CiAgICAgICAgZm9udC13ZWlnaHQ6IHZhcigtLXBhcGVyLWl0ZW0tc2VsZWN0ZWQtd2VpZ2h0LCBib2xkKTsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItaXRlbS1zZWxlY3RlZDsKICAgICAgfQoKICAgICAgOmhvc3QoW2Rpc2FibGVkXSksIC5wYXBlci1pdGVtW2Rpc2FibGVkXSB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLWl0ZW0tZGlzYWJsZWQtY29sb3IsIHZhcigtLWRpc2FibGVkLXRleHQtY29sb3IpKTsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItaXRlbS1kaXNhYmxlZDsKICAgICAgfQoKICAgICAgOmhvc3QoOmZvY3VzKSwgLnBhcGVyLWl0ZW06Zm9jdXMgewogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICBvdXRsaW5lOiAwOwoKICAgICAgICBAYXBwbHkgLS1wYXBlci1pdGVtLWZvY3VzZWQ7CiAgICAgIH0KCiAgICAgIDpob3N0KDpmb2N1cyk6YmVmb3JlLCAucGFwZXItaXRlbTpmb2N1czpiZWZvcmUgewogICAgICAgIEBhcHBseSAtLWxheW91dC1maXQ7CgogICAgICAgIGJhY2tncm91bmQ6IGN1cnJlbnRDb2xvcjsKICAgICAgICBjb250ZW50OiAnJzsKICAgICAgICBvcGFjaXR5OiB2YXIoLS1kYXJrLWRpdmlkZXItb3BhY2l0eSk7CiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLWl0ZW0tZm9jdXNlZC1iZWZvcmU7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC90ZW1wbGF0ZT4KPC9kb20tbW9kdWxlPmA7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChTVy5jb250ZW50KTt2YXIgVmJlPXtob3N0QXR0cmlidXRlczp7cm9sZToib3B0aW9uIix0YWJpbmRleDoiMCJ9fSxQOT1bU2gsRGksVmJlXTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGUgaW5jbHVkZT0icGFwZXItaXRlbS1zaGFyZWQtc3R5bGVzIj4KICAgICAgOmhvc3QgewogICAgICAgIEBhcHBseSAtLWxheW91dC1ob3Jpem9udGFsOwogICAgICAgIEBhcHBseSAtLWxheW91dC1jZW50ZXI7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1zdWJoZWFkOwoKICAgICAgICBAYXBwbHkgLS1wYXBlci1pdGVtOwogICAgICB9CiAgICA8L3N0eWxlPgogICAgPHNsb3Q+PC9zbG90PgpgLGlzOiJwYXBlci1pdGVtIixiZWhhdmlvcnM6W1A5XX0pO1l0KHtfdGVtcGxhdGU6UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIG92ZXJmbG93OiBoaWRkZW47IC8qIG5lZWRlZCBmb3IgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXMgdG8gd29yayBvbiBmZiAqLwogICAgICAgIEBhcHBseSAtLWxheW91dC12ZXJ0aWNhbDsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtY2VudGVyLWp1c3RpZmllZDsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtZmxleDsKICAgICAgfQoKICAgICAgOmhvc3QoW3R3by1saW5lXSkgewogICAgICAgIG1pbi1oZWlnaHQ6IHZhcigtLXBhcGVyLWl0ZW0tYm9keS10d28tbGluZS1taW4taGVpZ2h0LCA3MnB4KTsKICAgICAgfQoKICAgICAgOmhvc3QoW3RocmVlLWxpbmVdKSB7CiAgICAgICAgbWluLWhlaWdodDogdmFyKC0tcGFwZXItaXRlbS1ib2R5LXRocmVlLWxpbmUtbWluLWhlaWdodCwgODhweCk7CiAgICAgIH0KCiAgICAgIDpob3N0ID4gOjpzbG90dGVkKCopIHsKICAgICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgICAgIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzOwogICAgICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7CiAgICAgIH0KCiAgICAgIDpob3N0ID4gOjpzbG90dGVkKFtzZWNvbmRhcnldKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1ib2R5MTsKCiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLWl0ZW0tYm9keS1zZWNvbmRhcnktY29sb3IsIHZhcigtLXNlY29uZGFyeS10ZXh0LWNvbG9yKSk7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLWl0ZW0tYm9keS1zZWNvbmRhcnk7CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPHNsb3Q+PC9zbG90PgpgLGlzOiJwYXBlci1pdGVtLWJvZHkifSk7WXQoe190ZW1wbGF0ZTpRYAogICAgPHN0eWxlIGluY2x1ZGU9InBhcGVyLWl0ZW0tc2hhcmVkLXN0eWxlcyI+PC9zdHlsZT4KICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIEBhcHBseSAtLWxheW91dC1ob3Jpem9udGFsOwogICAgICAgIEBhcHBseSAtLWxheW91dC1jZW50ZXI7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1zdWJoZWFkOwoKICAgICAgICBAYXBwbHkgLS1wYXBlci1pdGVtOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWljb24taXRlbTsKICAgICAgfQoKICAgICAgLmNvbnRlbnQtaWNvbiB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWhvcml6b250YWw7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWNlbnRlcjsKCiAgICAgICAgd2lkdGg6IHZhcigtLXBhcGVyLWl0ZW0taWNvbi13aWR0aCwgNTZweCk7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItaXRlbS1pY29uOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxkaXYgaWQ9ImNvbnRlbnRJY29uIiBjbGFzcz0iY29udGVudC1pY29uIj4KICAgICAgPHNsb3QgbmFtZT0iaXRlbS1pY29uIj48L3Nsb3Q+CiAgICA8L2Rpdj4KICAgIDxzbG90Pjwvc2xvdD4KYCxpczoicGFwZXItaWNvbi1pdGVtIixiZWhhdmlvcnM6W1A5XX0pO3ZhciBNVz17cHJvcGVydGllczp7bXVsdGk6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxvYnNlcnZlcjoibXVsdGlDaGFuZ2VkIn0sc2VsZWN0ZWRWYWx1ZXM6e3R5cGU6QXJyYXksbm90aWZ5OiEwLHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuW119fSxzZWxlY3RlZEl0ZW1zOnt0eXBlOkFycmF5LHJlYWRPbmx5OiEwLG5vdGlmeTohMCx2YWx1ZTpmdW5jdGlvbigpe3JldHVybltdfX19LG9ic2VydmVyczpbIl91cGRhdGVTZWxlY3RlZChzZWxlY3RlZFZhbHVlcy5zcGxpY2VzKSJdLHNlbGVjdDpmdW5jdGlvbihlKXt0aGlzLm11bHRpP3RoaXMuX3RvZ2dsZVNlbGVjdGVkKGUpOnRoaXMuc2VsZWN0ZWQ9ZX0sbXVsdGlDaGFuZ2VkOmZ1bmN0aW9uKGUpe3RoaXMuX3NlbGVjdGlvbi5tdWx0aT1lLHRoaXMuX3VwZGF0ZVNlbGVjdGVkKCl9LGdldCBfc2hvdWxkVXBkYXRlU2VsZWN0aW9uKCl7cmV0dXJuIHRoaXMuc2VsZWN0ZWQhPW51bGx8fHRoaXMuc2VsZWN0ZWRWYWx1ZXMhPW51bGwmJnRoaXMuc2VsZWN0ZWRWYWx1ZXMubGVuZ3RofSxfdXBkYXRlQXR0ckZvclNlbGVjdGVkOmZ1bmN0aW9uKCl7dGhpcy5tdWx0aT90aGlzLnNlbGVjdGVkSXRlbXMmJnRoaXMuc2VsZWN0ZWRJdGVtcy5sZW5ndGg+MCYmKHRoaXMuc2VsZWN0ZWRWYWx1ZXM9dGhpcy5zZWxlY3RlZEl0ZW1zLm1hcChmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5faW5kZXhUb1ZhbHVlKHRoaXMuaW5kZXhPZihlKSl9LHRoaXMpLmZpbHRlcihmdW5jdGlvbihlKXtyZXR1cm4gZSE9bnVsbH0sdGhpcykpOndoLl91cGRhdGVBdHRyRm9yU2VsZWN0ZWQuYXBwbHkodGhpcyl9LF91cGRhdGVTZWxlY3RlZDpmdW5jdGlvbigpe3RoaXMubXVsdGk/dGhpcy5fc2VsZWN0TXVsdGkodGhpcy5zZWxlY3RlZFZhbHVlcyk6dGhpcy5fc2VsZWN0U2VsZWN0ZWQodGhpcy5zZWxlY3RlZCl9LF9zZWxlY3RNdWx0aTpmdW5jdGlvbihlKXtlPWV8fFtdO3ZhciB0PSh0aGlzLl92YWx1ZXNUb0l0ZW1zKGUpfHxbXSkuZmlsdGVyKGZ1bmN0aW9uKGkpe3JldHVybiBpIT1udWxsfSk7dGhpcy5fc2VsZWN0aW9uLmNsZWFyKHQpO2Zvcih2YXIgcj0wO3I8dC5sZW5ndGg7cisrKXRoaXMuX3NlbGVjdGlvbi5zZXRJdGVtU2VsZWN0ZWQodFtyXSwhMCk7aWYodGhpcy5mYWxsYmFja1NlbGVjdGlvbiYmIXRoaXMuX3NlbGVjdGlvbi5nZXQoKS5sZW5ndGgpe3ZhciBuPXRoaXMuX3ZhbHVlVG9JdGVtKHRoaXMuZmFsbGJhY2tTZWxlY3Rpb24pO24mJnRoaXMuc2VsZWN0KHRoaXMuZmFsbGJhY2tTZWxlY3Rpb24pfX0sX3NlbGVjdGlvbkNoYW5nZTpmdW5jdGlvbigpe3ZhciBlPXRoaXMuX3NlbGVjdGlvbi5nZXQoKTt0aGlzLm11bHRpPyh0aGlzLl9zZXRTZWxlY3RlZEl0ZW1zKGUpLHRoaXMuX3NldFNlbGVjdGVkSXRlbShlLmxlbmd0aD9lWzBdOm51bGwpKTplIT1udWxsPyh0aGlzLl9zZXRTZWxlY3RlZEl0ZW1zKFtlXSksdGhpcy5fc2V0U2VsZWN0ZWRJdGVtKGUpKToodGhpcy5fc2V0U2VsZWN0ZWRJdGVtcyhbXSksdGhpcy5fc2V0U2VsZWN0ZWRJdGVtKG51bGwpKX0sX3RvZ2dsZVNlbGVjdGVkOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuc2VsZWN0ZWRWYWx1ZXMuaW5kZXhPZihlKSxyPXQ8MDtyP3RoaXMucHVzaCgic2VsZWN0ZWRWYWx1ZXMiLGUpOnRoaXMuc3BsaWNlKCJzZWxlY3RlZFZhbHVlcyIsdCwxKX0sX3ZhbHVlc1RvSXRlbXM6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9PW51bGw/bnVsbDplLm1hcChmdW5jdGlvbih0KXtyZXR1cm4gdGhpcy5fdmFsdWVUb0l0ZW0odCl9LHRoaXMpfX0sRTB0PVt3aCxNV107dmFyIF9fPXtwcm9wZXJ0aWVzOntmb2N1c2VkSXRlbTp7b2JzZXJ2ZXI6Il9mb2N1c2VkSXRlbUNoYW5nZWQiLHJlYWRPbmx5OiEwLHR5cGU6T2JqZWN0fSxhdHRyRm9ySXRlbVRpdGxlOnt0eXBlOlN0cmluZ30sZGlzYWJsZWQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxvYnNlcnZlcjoiX2Rpc2FibGVkQ2hhbmdlZCJ9fSxfTU9ESUZJRVJfS0VZUzpbIkFsdCIsIkFsdEdyYXBoIiwiQ2Fwc0xvY2siLCJDb250cm9sIiwiRm4iLCJGbkxvY2siLCJIeXBlciIsIk1ldGEiLCJOdW1Mb2NrIiwiT1MiLCJTY3JvbGxMb2NrIiwiU2hpZnQiLCJTdXBlciIsIlN5bWJvbCIsIlN5bWJvbExvY2siXSxfU0VBUkNIX1JFU0VUX1RJTUVPVVRfTVM6MWUzLF9wcmV2aW91c1RhYkluZGV4OjAsaG9zdEF0dHJpYnV0ZXM6e3JvbGU6Im1lbnUifSxvYnNlcnZlcnM6WyJfdXBkYXRlTXVsdGlzZWxlY3RhYmxlKG11bHRpKSJdLGxpc3RlbmVyczp7Zm9jdXM6Il9vbkZvY3VzIixrZXlkb3duOiJfb25LZXlkb3duIiwiaXJvbi1pdGVtcy1jaGFuZ2VkIjoiX29uSXJvbkl0ZW1zQ2hhbmdlZCJ9LGtleUJpbmRpbmdzOnt1cDoiX29uVXBLZXkiLGRvd246Il9vbkRvd25LZXkiLGVzYzoiX29uRXNjS2V5Iiwic2hpZnQrdGFiOmtleWRvd24iOiJfb25TaGlmdFRhYkRvd24ifSxhdHRhY2hlZDpmdW5jdGlvbigpe3RoaXMuX3Jlc2V0VGFiaW5kaWNlcygpfSxzZWxlY3Q6ZnVuY3Rpb24oZSl7dGhpcy5fZGVmYXVsdEZvY3VzQXN5bmMmJih0aGlzLmNhbmNlbEFzeW5jKHRoaXMuX2RlZmF1bHRGb2N1c0FzeW5jKSx0aGlzLl9kZWZhdWx0Rm9jdXNBc3luYz1udWxsKTt2YXIgdD10aGlzLl92YWx1ZVRvSXRlbShlKTt0JiZ0Lmhhc0F0dHJpYnV0ZSgiZGlzYWJsZWQiKXx8KHRoaXMuX3NldEZvY3VzZWRJdGVtKHQpLE1XLnNlbGVjdC5hcHBseSh0aGlzLGFyZ3VtZW50cykpfSxfcmVzZXRUYWJpbmRpY2VzOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5tdWx0aT90aGlzLnNlbGVjdGVkSXRlbXMmJnRoaXMuc2VsZWN0ZWRJdGVtc1swXTp0aGlzLnNlbGVjdGVkSXRlbTt0aGlzLml0ZW1zLmZvckVhY2goZnVuY3Rpb24odCl7dC5zZXRBdHRyaWJ1dGUoInRhYmluZGV4Iix0PT09ZT8iMCI6Ii0xIiksdC5zZXRBdHRyaWJ1dGUoImFyaWEtc2VsZWN0ZWQiLHRoaXMuX3NlbGVjdGlvbi5pc1NlbGVjdGVkKHQpKX0sdGhpcyl9LF91cGRhdGVNdWx0aXNlbGVjdGFibGU6ZnVuY3Rpb24oZSl7ZT90aGlzLnNldEF0dHJpYnV0ZSgiYXJpYS1tdWx0aXNlbGVjdGFibGUiLCJ0cnVlIik6dGhpcy5yZW1vdmVBdHRyaWJ1dGUoImFyaWEtbXVsdGlzZWxlY3RhYmxlIil9LF9mb2N1c1dpdGhLZXlib2FyZEV2ZW50OmZ1bmN0aW9uKGUpe2lmKHRoaXMuX01PRElGSUVSX0tFWVMuaW5kZXhPZihlLmtleSk9PT0tMSl7dGhpcy5jYW5jZWxEZWJvdW5jZXIoIl9jbGVhclNlYXJjaFRleHQiKTt2YXIgdD10aGlzLl9zZWFyY2hUZXh0fHwiIixyPWUua2V5JiZlLmtleS5sZW5ndGg9PTE/ZS5rZXk6U3RyaW5nLmZyb21DaGFyQ29kZShlLmtleUNvZGUpO3QrPXIudG9Mb2NhbGVMb3dlckNhc2UoKTtmb3IodmFyIG49dC5sZW5ndGgsaT0wLG87bz10aGlzLml0ZW1zW2ldO2krKylpZighby5oYXNBdHRyaWJ1dGUoImRpc2FibGVkIikpe3ZhciBhPXRoaXMuYXR0ckZvckl0ZW1UaXRsZXx8InRleHRDb250ZW50IixzPShvW2FdfHxvLmdldEF0dHJpYnV0ZShhKXx8IiIpLnRyaW0oKTtpZighKHMubGVuZ3RoPG4pJiZzLnNsaWNlKDAsbikudG9Mb2NhbGVMb3dlckNhc2UoKT09dCl7dGhpcy5fc2V0Rm9jdXNlZEl0ZW0obyk7YnJlYWt9fXRoaXMuX3NlYXJjaFRleHQ9dCx0aGlzLmRlYm91bmNlKCJfY2xlYXJTZWFyY2hUZXh0Iix0aGlzLl9jbGVhclNlYXJjaFRleHQsdGhpcy5fU0VBUkNIX1JFU0VUX1RJTUVPVVRfTVMpfX0sX2NsZWFyU2VhcmNoVGV4dDpmdW5jdGlvbigpe3RoaXMuX3NlYXJjaFRleHQ9IiJ9LF9mb2N1c1ByZXZpb3VzOmZ1bmN0aW9uKCl7Zm9yKHZhciBlPXRoaXMuaXRlbXMubGVuZ3RoLHQ9TnVtYmVyKHRoaXMuaW5kZXhPZih0aGlzLmZvY3VzZWRJdGVtKSkscj0xO3I8ZSsxO3IrKyl7dmFyIG49dGhpcy5pdGVtc1sodC1yK2UpJWVdO2lmKCFuLmhhc0F0dHJpYnV0ZSgiZGlzYWJsZWQiKSl7dmFyIGk9enQobikuZ2V0T3duZXJSb290KCl8fGRvY3VtZW50O2lmKHRoaXMuX3NldEZvY3VzZWRJdGVtKG4pLHp0KGkpLmFjdGl2ZUVsZW1lbnQ9PW4pcmV0dXJufX19LF9mb2N1c05leHQ6ZnVuY3Rpb24oKXtmb3IodmFyIGU9dGhpcy5pdGVtcy5sZW5ndGgsdD1OdW1iZXIodGhpcy5pbmRleE9mKHRoaXMuZm9jdXNlZEl0ZW0pKSxyPTE7cjxlKzE7cisrKXt2YXIgbj10aGlzLml0ZW1zWyh0K3IpJWVdO2lmKCFuLmhhc0F0dHJpYnV0ZSgiZGlzYWJsZWQiKSl7dmFyIGk9enQobikuZ2V0T3duZXJSb290KCl8fGRvY3VtZW50O2lmKHRoaXMuX3NldEZvY3VzZWRJdGVtKG4pLHp0KGkpLmFjdGl2ZUVsZW1lbnQ9PW4pcmV0dXJufX19LF9hcHBseVNlbGVjdGlvbjpmdW5jdGlvbihlLHQpe3Q/ZS5zZXRBdHRyaWJ1dGUoImFyaWEtc2VsZWN0ZWQiLCJ0cnVlIik6ZS5zZXRBdHRyaWJ1dGUoImFyaWEtc2VsZWN0ZWQiLCJmYWxzZSIpLHdoLl9hcHBseVNlbGVjdGlvbi5hcHBseSh0aGlzLGFyZ3VtZW50cyl9LF9mb2N1c2VkSXRlbUNoYW5nZWQ6ZnVuY3Rpb24oZSx0KXt0JiZ0LnNldEF0dHJpYnV0ZSgidGFiaW5kZXgiLCItMSIpLGUmJiFlLmhhc0F0dHJpYnV0ZSgiZGlzYWJsZWQiKSYmIXRoaXMuZGlzYWJsZWQmJihlLnNldEF0dHJpYnV0ZSgidGFiaW5kZXgiLCIwIiksZS5mb2N1cygpKX0sX29uSXJvbkl0ZW1zQ2hhbmdlZDpmdW5jdGlvbihlKXtlLmRldGFpbC5hZGRlZE5vZGVzLmxlbmd0aCYmdGhpcy5fcmVzZXRUYWJpbmRpY2VzKCl9LF9vblNoaWZ0VGFiRG93bjpmdW5jdGlvbihlKXt2YXIgdD10aGlzLmdldEF0dHJpYnV0ZSgidGFiaW5kZXgiKTtfXy5fc2hpZnRUYWJQcmVzc2VkPSEwLHRoaXMuX3NldEZvY3VzZWRJdGVtKG51bGwpLHRoaXMuc2V0QXR0cmlidXRlKCJ0YWJpbmRleCIsIi0xIiksdGhpcy5hc3luYyhmdW5jdGlvbigpe3RoaXMuc2V0QXR0cmlidXRlKCJ0YWJpbmRleCIsdCksX18uX3NoaWZ0VGFiUHJlc3NlZD0hMX0sMSl9LF9vbkZvY3VzOmZ1bmN0aW9uKGUpe2lmKCFfXy5fc2hpZnRUYWJQcmVzc2VkKXt2YXIgdD16dChlKS5yb290VGFyZ2V0O3QhPT10aGlzJiZ0eXBlb2YgdC50YWJJbmRleCE9InVuZGVmaW5lZCImJiF0aGlzLmlzTGlnaHREZXNjZW5kYW50KHQpfHwodGhpcy5fZGVmYXVsdEZvY3VzQXN5bmM9dGhpcy5hc3luYyhmdW5jdGlvbigpe3ZhciByPXRoaXMubXVsdGk/dGhpcy5zZWxlY3RlZEl0ZW1zJiZ0aGlzLnNlbGVjdGVkSXRlbXNbMF06dGhpcy5zZWxlY3RlZEl0ZW07dGhpcy5fc2V0Rm9jdXNlZEl0ZW0obnVsbCkscj90aGlzLl9zZXRGb2N1c2VkSXRlbShyKTp0aGlzLml0ZW1zWzBdJiZ0aGlzLl9mb2N1c05leHQoKX0pKX19LF9vblVwS2V5OmZ1bmN0aW9uKGUpe3RoaXMuX2ZvY3VzUHJldmlvdXMoKSxlLmRldGFpbC5rZXlib2FyZEV2ZW50LnByZXZlbnREZWZhdWx0KCl9LF9vbkRvd25LZXk6ZnVuY3Rpb24oZSl7dGhpcy5fZm9jdXNOZXh0KCksZS5kZXRhaWwua2V5Ym9hcmRFdmVudC5wcmV2ZW50RGVmYXVsdCgpfSxfb25Fc2NLZXk6ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy5mb2N1c2VkSXRlbTt0JiZ0LmJsdXIoKX0sX29uS2V5ZG93bjpmdW5jdGlvbihlKXt0aGlzLmtleWJvYXJkRXZlbnRNYXRjaGVzS2V5cyhlLCJ1cCBkb3duIGVzYyIpfHx0aGlzLl9mb2N1c1dpdGhLZXlib2FyZEV2ZW50KGUpLGUuc3RvcFByb3BhZ2F0aW9uKCl9LF9hY3RpdmF0ZUhhbmRsZXI6ZnVuY3Rpb24oZSl7d2guX2FjdGl2YXRlSGFuZGxlci5jYWxsKHRoaXMsZSksZS5zdG9wUHJvcGFnYXRpb24oKX0sX2Rpc2FibGVkQ2hhbmdlZDpmdW5jdGlvbihlKXtlPyh0aGlzLl9wcmV2aW91c1RhYkluZGV4PXRoaXMuaGFzQXR0cmlidXRlKCJ0YWJpbmRleCIpP3RoaXMudGFiSW5kZXg6MCx0aGlzLnJlbW92ZUF0dHJpYnV0ZSgidGFiaW5kZXgiKSk6dGhpcy5oYXNBdHRyaWJ1dGUoInRhYmluZGV4Iil8fHRoaXMuc2V0QXR0cmlidXRlKCJ0YWJpbmRleCIsdGhpcy5fcHJldmlvdXNUYWJJbmRleCl9fTtfXy5fc2hpZnRUYWJQcmVzc2VkPSExO3ZhciBJOT1bRTB0LE9vLF9fXTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBwYWRkaW5nOiA4cHggMDsKCiAgICAgICAgYmFja2dyb3VuZDogdmFyKC0tcGFwZXItbGlzdGJveC1iYWNrZ3JvdW5kLWNvbG9yLCB2YXIoLS1wcmltYXJ5LWJhY2tncm91bmQtY29sb3IpKTsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItbGlzdGJveC1jb2xvciwgdmFyKC0tcHJpbWFyeS10ZXh0LWNvbG9yKSk7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLWxpc3Rib3g7CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPHNsb3Q+PC9zbG90PgpgLGlzOiJwYXBlci1saXN0Ym94IixiZWhhdmlvcnM6W0k5XSxob3N0QXR0cmlidXRlczp7cm9sZToibGlzdGJveCJ9fSk7dmFyIFQwdD1RYAo8ZG9tLW1vZHVsZSBpZD0icGFwZXItbWF0ZXJpYWwtc2hhcmVkLXN0eWxlcyI+CiAgPHRlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICB9CgogICAgICA6aG9zdChbZWxldmF0aW9uPSIxIl0pIHsKICAgICAgICBAYXBwbHkgLS1zaGFkb3ctZWxldmF0aW9uLTJkcDsKICAgICAgfQoKICAgICAgOmhvc3QoW2VsZXZhdGlvbj0iMiJdKSB7CiAgICAgICAgQGFwcGx5IC0tc2hhZG93LWVsZXZhdGlvbi00ZHA7CiAgICAgIH0KCiAgICAgIDpob3N0KFtlbGV2YXRpb249IjMiXSkgewogICAgICAgIEBhcHBseSAtLXNoYWRvdy1lbGV2YXRpb24tNmRwOwogICAgICB9CgogICAgICA6aG9zdChbZWxldmF0aW9uPSI0Il0pIHsKICAgICAgICBAYXBwbHkgLS1zaGFkb3ctZWxldmF0aW9uLThkcDsKICAgICAgfQoKICAgICAgOmhvc3QoW2VsZXZhdGlvbj0iNSJdKSB7CiAgICAgICAgQGFwcGx5IC0tc2hhZG93LWVsZXZhdGlvbi0xNmRwOwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvdGVtcGxhdGU+CjwvZG9tLW1vZHVsZT4KYDtUMHQuc2V0QXR0cmlidXRlKCJzdHlsZSIsImRpc3BsYXk6IG5vbmU7Iik7ZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChUMHQuY29udGVudCk7WXQoe190ZW1wbGF0ZTpRYAogICAgPHN0eWxlIGluY2x1ZGU9InBhcGVyLW1hdGVyaWFsLXNoYXJlZC1zdHlsZXMiPjwvc3R5bGU+CiAgICA8c3R5bGU+CiAgICAgIDpob3N0KFthbmltYXRlZF0pIHsKICAgICAgICBAYXBwbHkgLS1zaGFkb3ctdHJhbnNpdGlvbjsKICAgICAgfQogICAgICA6aG9zdCB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItbWF0ZXJpYWw7CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPHNsb3Q+PC9zbG90PgpgLGlzOiJwYXBlci1tYXRlcmlhbCIscHJvcGVydGllczp7ZWxldmF0aW9uOnt0eXBlOk51bWJlcixyZWZsZWN0VG9BdHRyaWJ1dGU6ITAsdmFsdWU6MX0sYW5pbWF0ZWQ6e3R5cGU6Qm9vbGVhbixyZWZsZWN0VG9BdHRyaWJ1dGU6ITAsdmFsdWU6ITF9fX0pO3ZhciBMOT17cHJvcGVydGllczp7dmFsdWU6e3R5cGU6TnVtYmVyLHZhbHVlOjAsbm90aWZ5OiEwLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0sbWluOnt0eXBlOk51bWJlcix2YWx1ZTowLG5vdGlmeTohMH0sbWF4Ont0eXBlOk51bWJlcix2YWx1ZToxMDAsbm90aWZ5OiEwfSxzdGVwOnt0eXBlOk51bWJlcix2YWx1ZToxLG5vdGlmeTohMH0scmF0aW86e3R5cGU6TnVtYmVyLHZhbHVlOjAscmVhZE9ubHk6ITAsbm90aWZ5OiEwfX0sb2JzZXJ2ZXJzOlsiX3VwZGF0ZSh2YWx1ZSwgbWluLCBtYXgsIHN0ZXApIl0sX2NhbGNSYXRpbzpmdW5jdGlvbihlKXtyZXR1cm4odGhpcy5fY2xhbXBWYWx1ZShlKS10aGlzLm1pbikvKHRoaXMubWF4LXRoaXMubWluKX0sX2NsYW1wVmFsdWU6ZnVuY3Rpb24oZSl7cmV0dXJuIE1hdGgubWluKHRoaXMubWF4LE1hdGgubWF4KHRoaXMubWluLHRoaXMuX2NhbGNTdGVwKGUpKSl9LF9jYWxjU3RlcDpmdW5jdGlvbihlKXtpZihlPXBhcnNlRmxvYXQoZSksIXRoaXMuc3RlcClyZXR1cm4gZTt2YXIgdD1NYXRoLnJvdW5kKChlLXRoaXMubWluKS90aGlzLnN0ZXApO3JldHVybiB0aGlzLnN0ZXA8MT90LygxL3RoaXMuc3RlcCkrdGhpcy5taW46dCp0aGlzLnN0ZXArdGhpcy5taW59LF92YWxpZGF0ZVZhbHVlOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5fY2xhbXBWYWx1ZSh0aGlzLnZhbHVlKTtyZXR1cm4gdGhpcy52YWx1ZT10aGlzLm9sZFZhbHVlPWlzTmFOKGUpP3RoaXMub2xkVmFsdWU6ZSx0aGlzLnZhbHVlIT09ZX0sX3VwZGF0ZTpmdW5jdGlvbigpe3RoaXMuX3ZhbGlkYXRlVmFsdWUoKSx0aGlzLl9zZXRSYXRpbyh0aGlzLl9jYWxjUmF0aW8odGhpcy52YWx1ZSkqMTAwKX19O1l0KHtfdGVtcGxhdGU6UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgIHdpZHRoOiAyMDBweDsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgfQoKICAgICAgOmhvc3QoW2hpZGRlbl0pLCBbaGlkZGVuXSB7CiAgICAgICAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OwogICAgICB9CgogICAgICAjcHJvZ3Jlc3NDb250YWluZXIgewogICAgICAgIEBhcHBseSAtLXBhcGVyLXByb2dyZXNzLWNvbnRhaW5lcjsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgIH0KCiAgICAgICNwcm9ncmVzc0NvbnRhaW5lciwKICAgICAgLyogdGhlIHN0cmlwZSBmb3IgdGhlIGluZGV0ZXJtaW5hdGUgYW5pbWF0aW9uKi8KICAgICAgLmluZGV0ZXJtaW5hdGU6OmFmdGVyIHsKICAgICAgICBoZWlnaHQ6IHZhcigtLXBhcGVyLXByb2dyZXNzLWhlaWdodCwgNHB4KTsKICAgICAgfQoKICAgICAgI3ByaW1hcnlQcm9ncmVzcywKICAgICAgI3NlY29uZGFyeVByb2dyZXNzLAogICAgICAuaW5kZXRlcm1pbmF0ZTo6YWZ0ZXIgewogICAgICAgIEBhcHBseSAtLWxheW91dC1maXQ7CiAgICAgIH0KCiAgICAgICNwcm9ncmVzc0NvbnRhaW5lciwKICAgICAgLmluZGV0ZXJtaW5hdGU6OmFmdGVyIHsKICAgICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1wYXBlci1wcm9ncmVzcy1jb250YWluZXItY29sb3IsIHZhcigtLWdvb2dsZS1ncmV5LTMwMCkpOwogICAgICB9CgogICAgICA6aG9zdCgudHJhbnNpdGluZykgI3ByaW1hcnlQcm9ncmVzcywKICAgICAgOmhvc3QoLnRyYW5zaXRpbmcpICNzZWNvbmRhcnlQcm9ncmVzcyB7CiAgICAgICAgLXdlYmtpdC10cmFuc2l0aW9uLXByb3BlcnR5OiAtd2Via2l0LXRyYW5zZm9ybTsKICAgICAgICB0cmFuc2l0aW9uLXByb3BlcnR5OiB0cmFuc2Zvcm07CgogICAgICAgIC8qIER1cmF0aW9uICovCiAgICAgICAgLXdlYmtpdC10cmFuc2l0aW9uLWR1cmF0aW9uOiB2YXIoLS1wYXBlci1wcm9ncmVzcy10cmFuc2l0aW9uLWR1cmF0aW9uLCAwLjA4cyk7CiAgICAgICAgdHJhbnNpdGlvbi1kdXJhdGlvbjogdmFyKC0tcGFwZXItcHJvZ3Jlc3MtdHJhbnNpdGlvbi1kdXJhdGlvbiwgMC4wOHMpOwoKICAgICAgICAvKiBUaW1pbmcgZnVuY3Rpb24gKi8KICAgICAgICAtd2Via2l0LXRyYW5zaXRpb24tdGltaW5nLWZ1bmN0aW9uOiB2YXIoLS1wYXBlci1wcm9ncmVzcy10cmFuc2l0aW9uLXRpbWluZy1mdW5jdGlvbiwgZWFzZSk7CiAgICAgICAgdHJhbnNpdGlvbi10aW1pbmctZnVuY3Rpb246IHZhcigtLXBhcGVyLXByb2dyZXNzLXRyYW5zaXRpb24tdGltaW5nLWZ1bmN0aW9uLCBlYXNlKTsKCiAgICAgICAgLyogRGVsYXkgKi8KICAgICAgICAtd2Via2l0LXRyYW5zaXRpb24tZGVsYXk6IHZhcigtLXBhcGVyLXByb2dyZXNzLXRyYW5zaXRpb24tZGVsYXksIDBzKTsKICAgICAgICB0cmFuc2l0aW9uLWRlbGF5OiB2YXIoLS1wYXBlci1wcm9ncmVzcy10cmFuc2l0aW9uLWRlbGF5LCAwcyk7CiAgICAgIH0KCiAgICAgICNwcmltYXJ5UHJvZ3Jlc3MsCiAgICAgICNzZWNvbmRhcnlQcm9ncmVzcyB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZpdDsKICAgICAgICAtd2Via2l0LXRyYW5zZm9ybS1vcmlnaW46IGxlZnQgY2VudGVyOwogICAgICAgIHRyYW5zZm9ybS1vcmlnaW46IGxlZnQgY2VudGVyOwogICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiBzY2FsZVgoMCk7CiAgICAgICAgdHJhbnNmb3JtOiBzY2FsZVgoMCk7CiAgICAgICAgd2lsbC1jaGFuZ2U6IHRyYW5zZm9ybTsKICAgICAgfQoKICAgICAgI3ByaW1hcnlQcm9ncmVzcyB7CiAgICAgICAgYmFja2dyb3VuZDogdmFyKC0tcGFwZXItcHJvZ3Jlc3MtYWN0aXZlLWNvbG9yLCB2YXIoLS1nb29nbGUtZ3JlZW4tNTAwKSk7CiAgICAgIH0KCiAgICAgICNzZWNvbmRhcnlQcm9ncmVzcyB7CiAgICAgICAgYmFja2dyb3VuZDogdmFyKC0tcGFwZXItcHJvZ3Jlc3Mtc2Vjb25kYXJ5LWNvbG9yLCB2YXIoLS1nb29nbGUtZ3JlZW4tMTAwKSk7CiAgICAgIH0KCiAgICAgIDpob3N0KFtkaXNhYmxlZF0pICNwcmltYXJ5UHJvZ3Jlc3MgewogICAgICAgIGJhY2tncm91bmQ6IHZhcigtLXBhcGVyLXByb2dyZXNzLWRpc2FibGVkLWFjdGl2ZS1jb2xvciwgdmFyKC0tZ29vZ2xlLWdyZXktNTAwKSk7CiAgICAgIH0KCiAgICAgIDpob3N0KFtkaXNhYmxlZF0pICNzZWNvbmRhcnlQcm9ncmVzcyB7CiAgICAgICAgYmFja2dyb3VuZDogdmFyKC0tcGFwZXItcHJvZ3Jlc3MtZGlzYWJsZWQtc2Vjb25kYXJ5LWNvbG9yLCB2YXIoLS1nb29nbGUtZ3JleS0zMDApKTsKICAgICAgfQoKICAgICAgOmhvc3QoOm5vdChbZGlzYWJsZWRdKSkgI3ByaW1hcnlQcm9ncmVzcy5pbmRldGVybWluYXRlIHsKICAgICAgICAtd2Via2l0LXRyYW5zZm9ybS1vcmlnaW46IHJpZ2h0IGNlbnRlcjsKICAgICAgICB0cmFuc2Zvcm0tb3JpZ2luOiByaWdodCBjZW50ZXI7CiAgICAgICAgLXdlYmtpdC1hbmltYXRpb246IGluZGV0ZXJtaW5hdGUtYmFyIHZhcigtLXBhcGVyLXByb2dyZXNzLWluZGV0ZXJtaW5hdGUtY3ljbGUtZHVyYXRpb24sIDJzKSBsaW5lYXIgaW5maW5pdGU7CiAgICAgICAgYW5pbWF0aW9uOiBpbmRldGVybWluYXRlLWJhciB2YXIoLS1wYXBlci1wcm9ncmVzcy1pbmRldGVybWluYXRlLWN5Y2xlLWR1cmF0aW9uLCAycykgbGluZWFyIGluZmluaXRlOwogICAgICB9CgogICAgICA6aG9zdCg6bm90KFtkaXNhYmxlZF0pKSAjcHJpbWFyeVByb2dyZXNzLmluZGV0ZXJtaW5hdGU6OmFmdGVyIHsKICAgICAgICBjb250ZW50OiAiIjsKICAgICAgICAtd2Via2l0LXRyYW5zZm9ybS1vcmlnaW46IGNlbnRlciBjZW50ZXI7CiAgICAgICAgdHJhbnNmb3JtLW9yaWdpbjogY2VudGVyIGNlbnRlcjsKCiAgICAgICAgLXdlYmtpdC1hbmltYXRpb246IGluZGV0ZXJtaW5hdGUtc3BsaXR0ZXIgdmFyKC0tcGFwZXItcHJvZ3Jlc3MtaW5kZXRlcm1pbmF0ZS1jeWNsZS1kdXJhdGlvbiwgMnMpIGxpbmVhciBpbmZpbml0ZTsKICAgICAgICBhbmltYXRpb246IGluZGV0ZXJtaW5hdGUtc3BsaXR0ZXIgdmFyKC0tcGFwZXItcHJvZ3Jlc3MtaW5kZXRlcm1pbmF0ZS1jeWNsZS1kdXJhdGlvbiwgMnMpIGxpbmVhciBpbmZpbml0ZTsKICAgICAgfQoKICAgICAgQC13ZWJraXQta2V5ZnJhbWVzIGluZGV0ZXJtaW5hdGUtYmFyIHsKICAgICAgICAwJSB7CiAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGVYKDEpIHRyYW5zbGF0ZVgoLTEwMCUpOwogICAgICAgIH0KICAgICAgICA1MCUgewogICAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlWCgxKSB0cmFuc2xhdGVYKDAlKTsKICAgICAgICB9CiAgICAgICAgNzUlIHsKICAgICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiBzY2FsZVgoMSkgdHJhbnNsYXRlWCgwJSk7CiAgICAgICAgICAtd2Via2l0LWFuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246IGN1YmljLWJlemllciguMjgsLjYyLC4zNywuOTEpOwogICAgICAgIH0KICAgICAgICAxMDAlIHsKICAgICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiBzY2FsZVgoMCkgdHJhbnNsYXRlWCgwJSk7CiAgICAgICAgfQogICAgICB9CgogICAgICBALXdlYmtpdC1rZXlmcmFtZXMgaW5kZXRlcm1pbmF0ZS1zcGxpdHRlciB7CiAgICAgICAgMCUgewogICAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlWCguNzUpIHRyYW5zbGF0ZVgoLTEyNSUpOwogICAgICAgIH0KICAgICAgICAzMCUgewogICAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlWCguNzUpIHRyYW5zbGF0ZVgoLTEyNSUpOwogICAgICAgICAgLXdlYmtpdC1hbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoLjQyLDAsLjYsLjgpOwogICAgICAgIH0KICAgICAgICA5MCUgewogICAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlWCguNzUpIHRyYW5zbGF0ZVgoMTI1JSk7CiAgICAgICAgfQogICAgICAgIDEwMCUgewogICAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlWCguNzUpIHRyYW5zbGF0ZVgoMTI1JSk7CiAgICAgICAgfQogICAgICB9CgogICAgICBAa2V5ZnJhbWVzIGluZGV0ZXJtaW5hdGUtYmFyIHsKICAgICAgICAwJSB7CiAgICAgICAgICB0cmFuc2Zvcm06IHNjYWxlWCgxKSB0cmFuc2xhdGVYKC0xMDAlKTsKICAgICAgICB9CiAgICAgICAgNTAlIHsKICAgICAgICAgIHRyYW5zZm9ybTogc2NhbGVYKDEpIHRyYW5zbGF0ZVgoMCUpOwogICAgICAgIH0KICAgICAgICA3NSUgewogICAgICAgICAgdHJhbnNmb3JtOiBzY2FsZVgoMSkgdHJhbnNsYXRlWCgwJSk7CiAgICAgICAgICBhbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoLjI4LC42MiwuMzcsLjkxKTsKICAgICAgICB9CiAgICAgICAgMTAwJSB7CiAgICAgICAgICB0cmFuc2Zvcm06IHNjYWxlWCgwKSB0cmFuc2xhdGVYKDAlKTsKICAgICAgICB9CiAgICAgIH0KCiAgICAgIEBrZXlmcmFtZXMgaW5kZXRlcm1pbmF0ZS1zcGxpdHRlciB7CiAgICAgICAgMCUgewogICAgICAgICAgdHJhbnNmb3JtOiBzY2FsZVgoLjc1KSB0cmFuc2xhdGVYKC0xMjUlKTsKICAgICAgICB9CiAgICAgICAgMzAlIHsKICAgICAgICAgIHRyYW5zZm9ybTogc2NhbGVYKC43NSkgdHJhbnNsYXRlWCgtMTI1JSk7CiAgICAgICAgICBhbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoLjQyLDAsLjYsLjgpOwogICAgICAgIH0KICAgICAgICA5MCUgewogICAgICAgICAgdHJhbnNmb3JtOiBzY2FsZVgoLjc1KSB0cmFuc2xhdGVYKDEyNSUpOwogICAgICAgIH0KICAgICAgICAxMDAlIHsKICAgICAgICAgIHRyYW5zZm9ybTogc2NhbGVYKC43NSkgdHJhbnNsYXRlWCgxMjUlKTsKICAgICAgICB9CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPGRpdiBpZD0icHJvZ3Jlc3NDb250YWluZXIiPgogICAgICA8ZGl2IGlkPSJzZWNvbmRhcnlQcm9ncmVzcyIgaGlkZGVuXCQ9IltbX2hpZGVTZWNvbmRhcnlQcm9ncmVzcyhzZWNvbmRhcnlSYXRpbyldXSI+PC9kaXY+CiAgICAgIDxkaXYgaWQ9InByaW1hcnlQcm9ncmVzcyI+PC9kaXY+CiAgICA8L2Rpdj4KYCxpczoicGFwZXItcHJvZ3Jlc3MiLGJlaGF2aW9yczpbTDldLHByb3BlcnRpZXM6e3NlY29uZGFyeVByb2dyZXNzOnt0eXBlOk51bWJlcix2YWx1ZTowfSxzZWNvbmRhcnlSYXRpbzp7dHlwZTpOdW1iZXIsdmFsdWU6MCxyZWFkT25seTohMH0saW5kZXRlcm1pbmF0ZTp7dHlwZTpCb29sZWFuLHZhbHVlOiExLG9ic2VydmVyOiJfdG9nZ2xlSW5kZXRlcm1pbmF0ZSJ9LGRpc2FibGVkOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITEscmVmbGVjdFRvQXR0cmlidXRlOiEwLG9ic2VydmVyOiJfZGlzYWJsZWRDaGFuZ2VkIn19LG9ic2VydmVyczpbIl9wcm9ncmVzc0NoYW5nZWQoc2Vjb25kYXJ5UHJvZ3Jlc3MsIHZhbHVlLCBtaW4sIG1heCwgaW5kZXRlcm1pbmF0ZSkiXSxob3N0QXR0cmlidXRlczp7cm9sZToicHJvZ3Jlc3NiYXIifSxfdG9nZ2xlSW5kZXRlcm1pbmF0ZTpmdW5jdGlvbihlKXt0aGlzLnRvZ2dsZUNsYXNzKCJpbmRldGVybWluYXRlIixlLHRoaXMuJC5wcmltYXJ5UHJvZ3Jlc3MpfSxfdHJhbnNmb3JtUHJvZ3Jlc3M6ZnVuY3Rpb24oZSx0KXt2YXIgcj0ic2NhbGVYKCIrdC8xMDArIikiO2Uuc3R5bGUudHJhbnNmb3JtPWUuc3R5bGUud2Via2l0VHJhbnNmb3JtPXJ9LF9tYWluUmF0aW9DaGFuZ2VkOmZ1bmN0aW9uKGUpe3RoaXMuX3RyYW5zZm9ybVByb2dyZXNzKHRoaXMuJC5wcmltYXJ5UHJvZ3Jlc3MsZSl9LF9wcm9ncmVzc0NoYW5nZWQ6ZnVuY3Rpb24oZSx0LHIsbixpKXtlPXRoaXMuX2NsYW1wVmFsdWUoZSksdD10aGlzLl9jbGFtcFZhbHVlKHQpO3ZhciBvPXRoaXMuX2NhbGNSYXRpbyhlKSoxMDAsYT10aGlzLl9jYWxjUmF0aW8odCkqMTAwO3RoaXMuX3NldFNlY29uZGFyeVJhdGlvKG8pLHRoaXMuX3RyYW5zZm9ybVByb2dyZXNzKHRoaXMuJC5zZWNvbmRhcnlQcm9ncmVzcyxvKSx0aGlzLl90cmFuc2Zvcm1Qcm9ncmVzcyh0aGlzLiQucHJpbWFyeVByb2dyZXNzLGEpLHRoaXMuc2Vjb25kYXJ5UHJvZ3Jlc3M9ZSxpP3RoaXMucmVtb3ZlQXR0cmlidXRlKCJhcmlhLXZhbHVlbm93Iik6dGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtdmFsdWVub3ciLHQpLHRoaXMuc2V0QXR0cmlidXRlKCJhcmlhLXZhbHVlbWluIixyKSx0aGlzLnNldEF0dHJpYnV0ZSgiYXJpYS12YWx1ZW1heCIsbil9LF9kaXNhYmxlZENoYW5nZWQ6ZnVuY3Rpb24oZSl7dGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtZGlzYWJsZWQiLGU/InRydWUiOiJmYWxzZSIpfSxfaGlkZVNlY29uZGFyeVByb2dyZXNzOmZ1bmN0aW9uKGUpe3JldHVybiBlPT09MH19KTt2YXIgQzB0PVFgCjxzdHlsZT4KICA6aG9zdCB7CiAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICBsaW5lLWhlaWdodDogMDsKICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7CiAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwogICAgLS1jYWxjdWxhdGVkLXBhcGVyLXJhZGlvLWJ1dHRvbi1zaXplOiB2YXIoLS1wYXBlci1yYWRpby1idXR0b24tc2l6ZSwgMTZweCk7CiAgICAvKiAtMXB4IGlzIGEgc2VudGluZWwgZm9yIHRoZSBkZWZhdWx0IGFuZCBpcyByZXBsYWNlIGluIFxgYXR0YWNoZWRcYC4gKi8KICAgIC0tY2FsY3VsYXRlZC1wYXBlci1yYWRpby1idXR0b24taW5rLXNpemU6IHZhcigtLXBhcGVyLXJhZGlvLWJ1dHRvbi1pbmstc2l6ZSwgLTFweCk7CiAgfQoKICA6aG9zdCg6Zm9jdXMpIHsKICAgIG91dGxpbmU6IG5vbmU7CiAgfQoKICAjcmFkaW9Db250YWluZXIgewogICAgQGFwcGx5IC0tbGF5b3V0LWlubGluZTsKICAgIEBhcHBseSAtLWxheW91dC1jZW50ZXItY2VudGVyOwogICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgd2lkdGg6IHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItcmFkaW8tYnV0dG9uLXNpemUpOwogICAgaGVpZ2h0OiB2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXJhZGlvLWJ1dHRvbi1zaXplKTsKICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7CgogICAgQGFwcGx5IC0tcGFwZXItcmFkaW8tYnV0dG9uLXJhZGlvLWNvbnRhaW5lcjsKICB9CgogICNpbmsgewogICAgcG9zaXRpb246IGFic29sdXRlOwogICAgdG9wOiA1MCU7CiAgICBsZWZ0OiA1MCU7CiAgICByaWdodDogYXV0bzsKICAgIHdpZHRoOiB2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXJhZGlvLWJ1dHRvbi1pbmstc2l6ZSk7CiAgICBoZWlnaHQ6IHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItcmFkaW8tYnV0dG9uLWluay1zaXplKTsKICAgIGNvbG9yOiB2YXIoLS1wYXBlci1yYWRpby1idXR0b24tdW5jaGVja2VkLWluay1jb2xvciwgdmFyKC0tcHJpbWFyeS10ZXh0LWNvbG9yKSk7CiAgICBvcGFjaXR5OiAwLjY7CiAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgIC13ZWJraXQtdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSwgLTUwJSk7CiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKTsKICB9CgogICNpbmtbY2hlY2tlZF0gewogICAgY29sb3I6IHZhcigtLXBhcGVyLXJhZGlvLWJ1dHRvbi1jaGVja2VkLWluay1jb2xvciwgdmFyKC0tcHJpbWFyeS1jb2xvcikpOwogIH0KCiAgI29mZlJhZGlvLCAjb25SYWRpbyB7CiAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgdG9wOiAwOwogICAgbGVmdDogMDsKICAgIHdpZHRoOiAxMDAlOwogICAgaGVpZ2h0OiAxMDAlOwogICAgYm9yZGVyLXJhZGl1czogNTAlOwogIH0KCiAgI29mZlJhZGlvIHsKICAgIGJvcmRlcjogMnB4IHNvbGlkIHZhcigtLXBhcGVyLXJhZGlvLWJ1dHRvbi11bmNoZWNrZWQtY29sb3IsIHZhcigtLXByaW1hcnktdGV4dC1jb2xvcikpOwogICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItcmFkaW8tYnV0dG9uLXVuY2hlY2tlZC1iYWNrZ3JvdW5kLWNvbG9yLCB0cmFuc3BhcmVudCk7CiAgICB0cmFuc2l0aW9uOiBib3JkZXItY29sb3IgMC4yOHM7CiAgfQoKICAjb25SYWRpbyB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1wYXBlci1yYWRpby1idXR0b24tY2hlY2tlZC1jb2xvciwgdmFyKC0tcHJpbWFyeS1jb2xvcikpOwogICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlKDApOwogICAgdHJhbnNmb3JtOiBzY2FsZSgwKTsKICAgIHRyYW5zaXRpb246IC13ZWJraXQtdHJhbnNmb3JtIGVhc2UgMC4yOHM7CiAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gZWFzZSAwLjI4czsKICAgIHdpbGwtY2hhbmdlOiB0cmFuc2Zvcm07CiAgfQoKICA6aG9zdChbY2hlY2tlZF0pICNvZmZSYWRpbyB7CiAgICBib3JkZXItY29sb3I6IHZhcigtLXBhcGVyLXJhZGlvLWJ1dHRvbi1jaGVja2VkLWNvbG9yLCB2YXIoLS1wcmltYXJ5LWNvbG9yKSk7CiAgfQoKICA6aG9zdChbY2hlY2tlZF0pICNvblJhZGlvIHsKICAgIC13ZWJraXQtdHJhbnNmb3JtOiBzY2FsZSgwLjUpOwogICAgdHJhbnNmb3JtOiBzY2FsZSgwLjUpOwogIH0KCiAgI3JhZGlvTGFiZWwgewogICAgbGluZS1oZWlnaHQ6IG5vcm1hbDsKICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7CiAgICBtYXJnaW4tbGVmdDogdmFyKC0tcGFwZXItcmFkaW8tYnV0dG9uLWxhYmVsLXNwYWNpbmcsIDEwcHgpOwogICAgd2hpdGUtc3BhY2U6IG5vcm1hbDsKICAgIGNvbG9yOiB2YXIoLS1wYXBlci1yYWRpby1idXR0b24tbGFiZWwtY29sb3IsIHZhcigtLXByaW1hcnktdGV4dC1jb2xvcikpOwoKICAgIEBhcHBseSAtLXBhcGVyLXJhZGlvLWJ1dHRvbi1sYWJlbDsKICB9CgogIDpob3N0KFtjaGVja2VkXSkgI3JhZGlvTGFiZWwgewogICAgQGFwcGx5IC0tcGFwZXItcmFkaW8tYnV0dG9uLWxhYmVsLWNoZWNrZWQ7CiAgfQoKICAjcmFkaW9MYWJlbDpkaXIocnRsKSB7CiAgICBtYXJnaW4tbGVmdDogMDsKICAgIG1hcmdpbi1yaWdodDogdmFyKC0tcGFwZXItcmFkaW8tYnV0dG9uLWxhYmVsLXNwYWNpbmcsIDEwcHgpOwogIH0KCiAgI3JhZGlvTGFiZWxbaGlkZGVuXSB7CiAgICBkaXNwbGF5OiBub25lOwogIH0KCiAgLyogZGlzYWJsZWQgc3RhdGUgKi8KCiAgOmhvc3QoW2Rpc2FibGVkXSkgI29mZlJhZGlvIHsKICAgIGJvcmRlci1jb2xvcjogdmFyKC0tcGFwZXItcmFkaW8tYnV0dG9uLXVuY2hlY2tlZC1jb2xvciwgdmFyKC0tcHJpbWFyeS10ZXh0LWNvbG9yKSk7CiAgICBvcGFjaXR5OiAwLjU7CiAgfQoKICA6aG9zdChbZGlzYWJsZWRdW2NoZWNrZWRdKSAjb25SYWRpbyB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1wYXBlci1yYWRpby1idXR0b24tdW5jaGVja2VkLWNvbG9yLCB2YXIoLS1wcmltYXJ5LXRleHQtY29sb3IpKTsKICAgIG9wYWNpdHk6IDAuNTsKICB9CgogIDpob3N0KFtkaXNhYmxlZF0pICNyYWRpb0xhYmVsIHsKICAgIC8qIHNsaWdodGx5IGRhcmtlciB0aGFuIHRoZSBidXR0b24sIHNvIHRoYXQgaXQncyByZWFkYWJsZSAqLwogICAgb3BhY2l0eTogMC42NTsKICB9Cjwvc3R5bGU+Cgo8ZGl2IGlkPSJyYWRpb0NvbnRhaW5lciI+CiAgPGRpdiBpZD0ib2ZmUmFkaW8iPjwvZGl2PgogIDxkaXYgaWQ9Im9uUmFkaW8iPjwvZGl2Pgo8L2Rpdj4KCjxkaXYgaWQ9InJhZGlvTGFiZWwiPjxzbG90Pjwvc2xvdD48L2Rpdj5gO0MwdC5zZXRBdHRyaWJ1dGUoInN0cmlwLXdoaXRlc3BhY2UiLCIiKTtZdCh7X3RlbXBsYXRlOkMwdCxpczoicGFwZXItcmFkaW8tYnV0dG9uIixiZWhhdmlvcnM6W1h4XSxob3N0QXR0cmlidXRlczp7cm9sZToicmFkaW8iLCJhcmlhLWNoZWNrZWQiOiExLHRhYmluZGV4OjB9LHByb3BlcnRpZXM6e2FyaWFBY3RpdmVBdHRyaWJ1dGU6e3R5cGU6U3RyaW5nLHZhbHVlOiJhcmlhLWNoZWNrZWQifX0scmVhZHk6ZnVuY3Rpb24oKXt0aGlzLl9yaXBwbGVDb250YWluZXI9dGhpcy4kLnJhZGlvQ29udGFpbmVyfSxhdHRhY2hlZDpmdW5jdGlvbigpe1RtKHRoaXMsZnVuY3Rpb24oKXt2YXIgZT10aGlzLmdldENvbXB1dGVkU3R5bGVWYWx1ZSgiLS1jYWxjdWxhdGVkLXBhcGVyLXJhZGlvLWJ1dHRvbi1pbmstc2l6ZSIpLnRyaW0oKTtpZihlPT09Ii0xcHgiKXt2YXIgdD1wYXJzZUZsb2F0KHRoaXMuZ2V0Q29tcHV0ZWRTdHlsZVZhbHVlKCItLWNhbGN1bGF0ZWQtcGFwZXItcmFkaW8tYnV0dG9uLXNpemUiKS50cmltKCkpLHI9TWF0aC5mbG9vcigzKnQpO3IlMiE9PXQlMiYmcisrLHRoaXMudXBkYXRlU3R5bGVzKHsiLS1wYXBlci1yYWRpby1idXR0b24taW5rLXNpemUiOnIrInB4In0pfX0pfX0pO3ZhciBrOT17aG9zdEF0dHJpYnV0ZXM6e3JvbGU6Im1lbnViYXIifSxrZXlCaW5kaW5nczp7bGVmdDoiX29uTGVmdEtleSIscmlnaHQ6Il9vblJpZ2h0S2V5In0sX29uVXBLZXk6ZnVuY3Rpb24oZSl7dGhpcy5mb2N1c2VkSXRlbS5jbGljaygpLGUuZGV0YWlsLmtleWJvYXJkRXZlbnQucHJldmVudERlZmF1bHQoKX0sX29uRG93bktleTpmdW5jdGlvbihlKXt0aGlzLmZvY3VzZWRJdGVtLmNsaWNrKCksZS5kZXRhaWwua2V5Ym9hcmRFdmVudC5wcmV2ZW50RGVmYXVsdCgpfSxnZXQgX2lzUlRMKCl7cmV0dXJuIHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHRoaXMpLmRpcmVjdGlvbj09PSJydGwifSxfb25MZWZ0S2V5OmZ1bmN0aW9uKGUpe3RoaXMuX2lzUlRMP3RoaXMuX2ZvY3VzTmV4dCgpOnRoaXMuX2ZvY3VzUHJldmlvdXMoKSxlLmRldGFpbC5rZXlib2FyZEV2ZW50LnByZXZlbnREZWZhdWx0KCl9LF9vblJpZ2h0S2V5OmZ1bmN0aW9uKGUpe3RoaXMuX2lzUlRMP3RoaXMuX2ZvY3VzUHJldmlvdXMoKTp0aGlzLl9mb2N1c05leHQoKSxlLmRldGFpbC5rZXlib2FyZEV2ZW50LnByZXZlbnREZWZhdWx0KCl9LF9vbktleWRvd246ZnVuY3Rpb24oZSl7dGhpcy5rZXlib2FyZEV2ZW50TWF0Y2hlc0tleXMoZSwidXAgZG93biBsZWZ0IHJpZ2h0IGVzYyIpfHx0aGlzLl9mb2N1c1dpdGhLZXlib2FyZEV2ZW50KGUpfX0sUjk9W0k5LGs5XTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgIH0KCiAgICAgIDpob3N0IDo6c2xvdHRlZCgqKSB7CiAgICAgICAgcGFkZGluZzogdmFyKC0tcGFwZXItcmFkaW8tZ3JvdXAtaXRlbS1wYWRkaW5nLCAxMnB4KTsKICAgICAgfQogICAgPC9zdHlsZT4KCiAgICA8c2xvdD48L3Nsb3Q+CmAsaXM6InBhcGVyLXJhZGlvLWdyb3VwIixiZWhhdmlvcnM6W1I5XSxob3N0QXR0cmlidXRlczp7cm9sZToicmFkaW9ncm91cCJ9LHByb3BlcnRpZXM6e2F0dHJGb3JTZWxlY3RlZDp7dHlwZTpTdHJpbmcsdmFsdWU6Im5hbWUifSxzZWxlY3RlZEF0dHJpYnV0ZTp7dHlwZTpTdHJpbmcsdmFsdWU6ImNoZWNrZWQifSxzZWxlY3RhYmxlOnt0eXBlOlN0cmluZyx2YWx1ZToicGFwZXItcmFkaW8tYnV0dG9uIn0sYWxsb3dFbXB0eVNlbGVjdGlvbjp7dHlwZTpCb29sZWFuLHZhbHVlOiExfX0sc2VsZWN0OmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX3ZhbHVlVG9JdGVtKGUpO2lmKCEodCYmdC5oYXNBdHRyaWJ1dGUoImRpc2FibGVkIikpKXtpZih0aGlzLnNlbGVjdGVkKXt2YXIgcj10aGlzLl92YWx1ZVRvSXRlbSh0aGlzLnNlbGVjdGVkKTtpZih0aGlzLnNlbGVjdGVkPT1lKWlmKHRoaXMuYWxsb3dFbXB0eVNlbGVjdGlvbillPSIiO2Vsc2V7ciYmKHIuY2hlY2tlZD0hMCk7cmV0dXJufXImJihyLmNoZWNrZWQ9ITEpfXdoLnNlbGVjdC5hcHBseSh0aGlzLFtlXSksdGhpcy5maXJlKCJwYXBlci1yYWRpby1ncm91cC1jaGFuZ2VkIil9fSxfYWN0aXZhdGVGb2N1c2VkSXRlbTpmdW5jdGlvbigpe3RoaXMuX2l0ZW1BY3RpdmF0ZSh0aGlzLl92YWx1ZUZvckl0ZW0odGhpcy5mb2N1c2VkSXRlbSksdGhpcy5mb2N1c2VkSXRlbSl9LF9vblVwS2V5OmZ1bmN0aW9uKGUpe3RoaXMuX2ZvY3VzUHJldmlvdXMoKSxlLnByZXZlbnREZWZhdWx0KCksdGhpcy5fYWN0aXZhdGVGb2N1c2VkSXRlbSgpfSxfb25Eb3duS2V5OmZ1bmN0aW9uKGUpe3RoaXMuX2ZvY3VzTmV4dCgpLGUucHJldmVudERlZmF1bHQoKSx0aGlzLl9hY3RpdmF0ZUZvY3VzZWRJdGVtKCl9LF9vbkxlZnRLZXk6ZnVuY3Rpb24oZSl7azkuX29uTGVmdEtleS5hcHBseSh0aGlzLGFyZ3VtZW50cyksdGhpcy5fYWN0aXZhdGVGb2N1c2VkSXRlbSgpfSxfb25SaWdodEtleTpmdW5jdGlvbihlKXtrOS5fb25SaWdodEtleS5hcHBseSh0aGlzLGFyZ3VtZW50cyksdGhpcy5fYWN0aXZhdGVGb2N1c2VkSXRlbSgpfX0pO3ZhciBBMHQ9UWAKICA8c3R5bGU+CiAgICA6aG9zdCB7CiAgICAgIEBhcHBseSAtLWxheW91dDsKICAgICAgQGFwcGx5IC0tbGF5b3V0LWp1c3RpZmllZDsKICAgICAgQGFwcGx5IC0tbGF5b3V0LWNlbnRlcjsKICAgICAgd2lkdGg6IDIwMHB4OwogICAgICBjdXJzb3I6IGRlZmF1bHQ7CiAgICAgIC13ZWJraXQtdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgIC1tb3otdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgIC1tcy11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgIC13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjogcmdiYSgwLCAwLCAwLCAwKTsKICAgICAgLS1wYXBlci1wcm9ncmVzcy1hY3RpdmUtY29sb3I6IHZhcigtLXBhcGVyLXNsaWRlci1hY3RpdmUtY29sb3IsIHZhcigtLWdvb2dsZS1ibHVlLTcwMCkpOwogICAgICAtLXBhcGVyLXByb2dyZXNzLXNlY29uZGFyeS1jb2xvcjogdmFyKC0tcGFwZXItc2xpZGVyLXNlY29uZGFyeS1jb2xvciwgdmFyKC0tZ29vZ2xlLWJsdWUtMzAwKSk7CiAgICAgIC0tcGFwZXItcHJvZ3Jlc3MtZGlzYWJsZWQtYWN0aXZlLWNvbG9yOiB2YXIoLS1wYXBlci1zbGlkZXItZGlzYWJsZWQtYWN0aXZlLWNvbG9yLCB2YXIoLS1wYXBlci1ncmV5LTQwMCkpOwogICAgICAtLXBhcGVyLXByb2dyZXNzLWRpc2FibGVkLXNlY29uZGFyeS1jb2xvcjogdmFyKC0tcGFwZXItc2xpZGVyLWRpc2FibGVkLXNlY29uZGFyeS1jb2xvciwgdmFyKC0tcGFwZXItZ3JleS00MDApKTsKICAgICAgLS1jYWxjdWxhdGVkLXBhcGVyLXNsaWRlci1oZWlnaHQ6IHZhcigtLXBhcGVyLXNsaWRlci1oZWlnaHQsIDJweCk7CiAgICB9CgogICAgLyogZm9jdXMgc2hvd3MgdGhlIHJpcHBsZSAqLwogICAgOmhvc3QoOmZvY3VzKSB7CiAgICAgIG91dGxpbmU6IG5vbmU7CiAgICB9CgogICAgLyoqCiAgICAgICogTk9URShrZWFudWxlZSk6IFRob3VnaCA6aG9zdC1jb250ZXh0IGlzIG5vdCB1bml2ZXJzYWxseSBzdXBwb3J0ZWQsIHNvbWUgcGFnZXMKICAgICAgKiBzdGlsbCByZWx5IG9uIHBhcGVyLXNsaWRlciBiZWluZyBmbGlwcGVkIHdoZW4gZGlyPSJydGwiIGlzIHNldCBvbiBib2R5LiBGb3IgZnVsbAogICAgICAqIGNvbXBhdGliaWxpdHksIGRpcj0icnRsIiBtdXN0IGJlIGV4cGxpY2l0bHkgc2V0IG9uIHBhcGVyLXNsaWRlci4KICAgICAgKi8KICAgIDpkaXIocnRsKSAjc2xpZGVyQ29udGFpbmVyIHsKICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlWCgtMSk7CiAgICAgIHRyYW5zZm9ybTogc2NhbGVYKC0xKTsKICAgIH0KCiAgICAvKioKICAgICAgKiBOT1RFKGtlYW51bGVlKTogVGhpcyBpcyBzZXBhcmF0ZSBmcm9tIHRoZSBydWxlIGFib3ZlIGJlY2F1c2UgOmhvc3QtY29udGV4dCBtYXkKICAgICAgKiBub3QgYmUgcmVjb2duaXplZC4KICAgICAgKi8KICAgIDpob3N0KFtkaXI9InJ0bCJdKSAjc2xpZGVyQ29udGFpbmVyIHsKICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlWCgtMSk7CiAgICAgIHRyYW5zZm9ybTogc2NhbGVYKC0xKTsKICAgIH0KCiAgICAvKioKICAgICAgKiBOT1RFKGtlYW51bGVlKTogTmVlZGVkIHRvIG92ZXJyaWRlIHRoZSA6aG9zdC1jb250ZXh0IHJ1bGUgKHdoZXJlIHN1cHBvcnRlZCkKICAgICAgKiB0byBzdXBwb3J0IExUUiBzbGlkZXJzIGluIFJUTCBwYWdlcy4KICAgICAgKi8KICAgIDpob3N0KFtkaXI9Imx0ciJdKSAjc2xpZGVyQ29udGFpbmVyIHsKICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlWCgxKTsKICAgICAgdHJhbnNmb3JtOiBzY2FsZVgoMSk7CiAgICB9CgogICAgI3NsaWRlckNvbnRhaW5lciB7CiAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIGhlaWdodDogY2FsYygzMHB4ICsgdmFyKC0tY2FsY3VsYXRlZC1wYXBlci1zbGlkZXItaGVpZ2h0KSk7CiAgICAgIG1hcmdpbi1sZWZ0OiBjYWxjKDE1cHggKyB2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXNsaWRlci1oZWlnaHQpLzIpOwogICAgICBtYXJnaW4tcmlnaHQ6IGNhbGMoMTVweCArIHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItc2xpZGVyLWhlaWdodCkvMik7CiAgICB9CgogICAgI3NsaWRlckNvbnRhaW5lcjpmb2N1cyB7CiAgICAgIG91dGxpbmU6IDA7CiAgICB9CgogICAgI3NsaWRlckNvbnRhaW5lci5lZGl0YWJsZSB7CiAgICAgIG1hcmdpbi10b3A6IDEycHg7CiAgICAgIG1hcmdpbi1ib3R0b206IDEycHg7CiAgICB9CgogICAgLmJhci1jb250YWluZXIgewogICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgIHRvcDogMDsKICAgICAgYm90dG9tOiAwOwogICAgICBsZWZ0OiAwOwogICAgICByaWdodDogMDsKICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgIH0KCiAgICAucmluZyA+IC5iYXItY29udGFpbmVyIHsKICAgICAgbGVmdDogY2FsYyg1cHggKyB2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXNsaWRlci1oZWlnaHQpLzIpOwogICAgICB0cmFuc2l0aW9uOiBsZWZ0IDAuMThzIGVhc2U7CiAgICB9CgogICAgLnJpbmcuZXhwYW5kLmRyYWdnaW5nID4gLmJhci1jb250YWluZXIgewogICAgICB0cmFuc2l0aW9uOiBub25lOwogICAgfQoKICAgIC5yaW5nLmV4cGFuZDpub3QoLnBpbikgPiAuYmFyLWNvbnRhaW5lciB7CiAgICAgIGxlZnQ6IGNhbGMoOHB4ICsgdmFyKC0tY2FsY3VsYXRlZC1wYXBlci1zbGlkZXItaGVpZ2h0KS8yKTsKICAgIH0KCiAgICAjc2xpZGVyQmFyIHsKICAgICAgcGFkZGluZzogMTVweCAwOwogICAgICB3aWR0aDogMTAwJTsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItc2xpZGVyLWJhci1jb2xvciwgdHJhbnNwYXJlbnQpOwogICAgICAtLXBhcGVyLXByb2dyZXNzLWNvbnRhaW5lci1jb2xvcjogdmFyKC0tcGFwZXItc2xpZGVyLWNvbnRhaW5lci1jb2xvciwgdmFyKC0tcGFwZXItZ3JleS00MDApKTsKICAgICAgLS1wYXBlci1wcm9ncmVzcy1oZWlnaHQ6IHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItc2xpZGVyLWhlaWdodCk7CiAgICB9CgogICAgLnNsaWRlci1tYXJrZXJzIHsKICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAvKiBzbGlkZXIta25vYiBpcyAzMHB4ICsgdGhlIHNsaWRlci1oZWlnaHQgc28gdGhhdCB0aGUgbWFya2VycyBzaG91bGQgc3RhcnQgYXQgYSBvZmZzZXQgb2YgMTVweCovCiAgICAgIHRvcDogMTVweDsKICAgICAgaGVpZ2h0OiB2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXNsaWRlci1oZWlnaHQpOwogICAgICBsZWZ0OiAwOwogICAgICByaWdodDogLTFweDsKICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsKICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgIEBhcHBseSAtLWxheW91dC1ob3Jpem9udGFsOwogICAgfQoKICAgIC5zbGlkZXItbWFya2VyIHsKICAgICAgQGFwcGx5IC0tbGF5b3V0LWZsZXg7CiAgICB9CiAgICAuc2xpZGVyLW1hcmtlcnM6OmFmdGVyLAogICAgLnNsaWRlci1tYXJrZXI6OmFmdGVyIHsKICAgICAgY29udGVudDogIiI7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICBtYXJnaW4tbGVmdDogLTFweDsKICAgICAgd2lkdGg6IDJweDsKICAgICAgaGVpZ2h0OiB2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXNsaWRlci1oZWlnaHQpOwogICAgICBib3JkZXItcmFkaXVzOiA1MCU7CiAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLXBhcGVyLXNsaWRlci1tYXJrZXJzLWNvbG9yLCAjMDAwKTsKICAgIH0KCiAgICAuc2xpZGVyLWtub2IgewogICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgIGxlZnQ6IDA7CiAgICAgIHRvcDogMDsKICAgICAgbWFyZ2luLWxlZnQ6IGNhbGMoLTE1cHggLSB2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXNsaWRlci1oZWlnaHQpLzIpOwogICAgICB3aWR0aDogY2FsYygzMHB4ICsgdmFyKC0tY2FsY3VsYXRlZC1wYXBlci1zbGlkZXItaGVpZ2h0KSk7CiAgICAgIGhlaWdodDogY2FsYygzMHB4ICsgdmFyKC0tY2FsY3VsYXRlZC1wYXBlci1zbGlkZXItaGVpZ2h0KSk7CiAgICB9CgogICAgLnRyYW5zaXRpbmcgPiAuc2xpZGVyLWtub2IgewogICAgICB0cmFuc2l0aW9uOiBsZWZ0IDAuMDhzIGVhc2U7CiAgICB9CgogICAgLnNsaWRlci1rbm9iOmZvY3VzIHsKICAgICAgb3V0bGluZTogbm9uZTsKICAgIH0KCiAgICAuc2xpZGVyLWtub2IuZHJhZ2dpbmcgewogICAgICB0cmFuc2l0aW9uOiBub25lOwogICAgfQoKICAgIC5zbmFwcyA+IC5zbGlkZXIta25vYi5kcmFnZ2luZyB7CiAgICAgIHRyYW5zaXRpb246IC13ZWJraXQtdHJhbnNmb3JtIDAuMDhzIGVhc2U7CiAgICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAwLjA4cyBlYXNlOwogICAgfQoKICAgIC5zbGlkZXIta25vYi1pbm5lciB7CiAgICAgIG1hcmdpbjogMTBweDsKICAgICAgd2lkdGg6IGNhbGMoMTAwJSAtIDIwcHgpOwogICAgICBoZWlnaHQ6IGNhbGMoMTAwJSAtIDIwcHgpOwogICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1wYXBlci1zbGlkZXIta25vYi1jb2xvciwgdmFyKC0tZ29vZ2xlLWJsdWUtNzAwKSk7CiAgICAgIGJvcmRlcjogMnB4IHNvbGlkIHZhcigtLXBhcGVyLXNsaWRlci1rbm9iLWNvbG9yLCB2YXIoLS1nb29nbGUtYmx1ZS03MDApKTsKICAgICAgYm9yZGVyLXJhZGl1czogNTAlOwoKICAgICAgLW1vei1ib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwoKICAgICAgdHJhbnNpdGlvbi1wcm9wZXJ0eTogLXdlYmtpdC10cmFuc2Zvcm0sIGJhY2tncm91bmQtY29sb3IsIGJvcmRlcjsKICAgICAgdHJhbnNpdGlvbi1wcm9wZXJ0eTogdHJhbnNmb3JtLCBiYWNrZ3JvdW5kLWNvbG9yLCBib3JkZXI7CiAgICAgIHRyYW5zaXRpb24tZHVyYXRpb246IDAuMThzOwogICAgICB0cmFuc2l0aW9uLXRpbWluZy1mdW5jdGlvbjogZWFzZTsKICAgIH0KCiAgICAuZXhwYW5kOm5vdCgucGluKSA+IC5zbGlkZXIta25vYiA+IC5zbGlkZXIta25vYi1pbm5lciB7CiAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiBzY2FsZSgxLjUpOwogICAgICB0cmFuc2Zvcm06IHNjYWxlKDEuNSk7CiAgICB9CgogICAgLnJpbmcgPiAuc2xpZGVyLWtub2IgPiAuc2xpZGVyLWtub2ItaW5uZXIgewogICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1wYXBlci1zbGlkZXIta25vYi1zdGFydC1jb2xvciwgdHJhbnNwYXJlbnQpOwogICAgICBib3JkZXI6IDJweCBzb2xpZCB2YXIoLS1wYXBlci1zbGlkZXIta25vYi1zdGFydC1ib3JkZXItY29sb3IsIHZhcigtLXBhcGVyLWdyZXktNDAwKSk7CiAgICB9CgogICAgLnNsaWRlci1rbm9iLWlubmVyOjpiZWZvcmUgewogICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1wYXBlci1zbGlkZXItcGluLWNvbG9yLCB2YXIoLS1nb29nbGUtYmx1ZS03MDApKTsKICAgIH0KCiAgICAucGluID4gLnNsaWRlci1rbm9iID4gLnNsaWRlci1rbm9iLWlubmVyOjpiZWZvcmUgewogICAgICBjb250ZW50OiAiIjsKICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICB0b3A6IDA7CiAgICAgIGxlZnQ6IDUwJTsKICAgICAgbWFyZ2luLWxlZnQ6IC0xM3B4OwogICAgICB3aWR0aDogMjZweDsKICAgICAgaGVpZ2h0OiAyNnB4OwogICAgICBib3JkZXItcmFkaXVzOiA1MCUgNTAlIDUwJSAwOwoKICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHJvdGF0ZSgtNDVkZWcpIHNjYWxlKDApIHRyYW5zbGF0ZSgwKTsKICAgICAgdHJhbnNmb3JtOiByb3RhdGUoLTQ1ZGVnKSBzY2FsZSgwKSB0cmFuc2xhdGUoMCk7CiAgICB9CgogICAgLnNsaWRlci1rbm9iLWlubmVyOjpiZWZvcmUsCiAgICAuc2xpZGVyLWtub2ItaW5uZXI6OmFmdGVyIHsKICAgICAgdHJhbnNpdGlvbjogLXdlYmtpdC10cmFuc2Zvcm0gLjE4cyBlYXNlLCBiYWNrZ3JvdW5kLWNvbG9yIC4xOHMgZWFzZTsKICAgICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIC4xOHMgZWFzZSwgYmFja2dyb3VuZC1jb2xvciAuMThzIGVhc2U7CiAgICB9CgogICAgLnBpbi5yaW5nID4gLnNsaWRlci1rbm9iID4gLnNsaWRlci1rbm9iLWlubmVyOjpiZWZvcmUgewogICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1wYXBlci1zbGlkZXItcGluLXN0YXJ0LWNvbG9yLCB2YXIoLS1wYXBlci1ncmV5LTQwMCkpOwogICAgfQoKICAgIC5waW4uZXhwYW5kID4gLnNsaWRlci1rbm9iID4gLnNsaWRlci1rbm9iLWlubmVyOjpiZWZvcmUgewogICAgICAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKC00NWRlZykgc2NhbGUoMSkgdHJhbnNsYXRlKDE3cHgsIC0xN3B4KTsKICAgICAgdHJhbnNmb3JtOiByb3RhdGUoLTQ1ZGVnKSBzY2FsZSgxKSB0cmFuc2xhdGUoMTdweCwgLTE3cHgpOwogICAgfQoKICAgIC5waW4gPiAuc2xpZGVyLWtub2IgPiAuc2xpZGVyLWtub2ItaW5uZXI6OmFmdGVyIHsKICAgICAgY29udGVudDogYXR0cih2YWx1ZSk7CiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgdG9wOiAwOwogICAgICBsZWZ0OiA1MCU7CiAgICAgIG1hcmdpbi1sZWZ0OiAtMTZweDsKICAgICAgd2lkdGg6IDMycHg7CiAgICAgIGhlaWdodDogMjZweDsKICAgICAgdGV4dC1hbGlnbjogY2VudGVyOwogICAgICBjb2xvcjogdmFyKC0tcGFwZXItc2xpZGVyLWZvbnQtY29sb3IsICNmZmYpOwogICAgICBmb250LXNpemU6IDEwcHg7CgogICAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGUoMCkgdHJhbnNsYXRlKDApOwogICAgICB0cmFuc2Zvcm06IHNjYWxlKDApIHRyYW5zbGF0ZSgwKTsKICAgIH0KCiAgICAucGluLmV4cGFuZCA+IC5zbGlkZXIta25vYiA+IC5zbGlkZXIta25vYi1pbm5lcjo6YWZ0ZXIgewogICAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGUoMSkgdHJhbnNsYXRlKDAsIC0xN3B4KTsKICAgICAgdHJhbnNmb3JtOiBzY2FsZSgxKSB0cmFuc2xhdGUoMCwgLTE3cHgpOwogICAgfQoKICAgIC8qIHBhcGVyLWlucHV0ICovCiAgICAuc2xpZGVyLWlucHV0IHsKICAgICAgd2lkdGg6IDUwcHg7CiAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgIC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWlucHV0OiB7CiAgICAgICAgdGV4dC1hbGlnbjogY2VudGVyOwogICAgICAgIEBhcHBseSAtLXBhcGVyLXNsaWRlci1pbnB1dC1jb250YWluZXItaW5wdXQ7CiAgICAgIH07CiAgICAgIEBhcHBseSAtLXBhcGVyLXNsaWRlci1pbnB1dDsKICAgIH0KCiAgICAvKiBkaXNhYmxlZCBzdGF0ZSAqLwogICAgI3NsaWRlckNvbnRhaW5lci5kaXNhYmxlZCB7CiAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgfQoKICAgIC5kaXNhYmxlZCA+IC5zbGlkZXIta25vYiA+IC5zbGlkZXIta25vYi1pbm5lciB7CiAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLXBhcGVyLXNsaWRlci1kaXNhYmxlZC1rbm9iLWNvbG9yLCB2YXIoLS1wYXBlci1ncmV5LTQwMCkpOwogICAgICBib3JkZXI6IDJweCBzb2xpZCB2YXIoLS1wYXBlci1zbGlkZXItZGlzYWJsZWQta25vYi1jb2xvciwgdmFyKC0tcGFwZXItZ3JleS00MDApKTsKICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlM2QoMC43NSwgMC43NSwgMSk7CiAgICAgIHRyYW5zZm9ybTogc2NhbGUzZCgwLjc1LCAwLjc1LCAxKTsKICAgIH0KCiAgICAuZGlzYWJsZWQucmluZyA+IC5zbGlkZXIta25vYiA+IC5zbGlkZXIta25vYi1pbm5lciB7CiAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLXBhcGVyLXNsaWRlci1rbm9iLXN0YXJ0LWNvbG9yLCB0cmFuc3BhcmVudCk7CiAgICAgIGJvcmRlcjogMnB4IHNvbGlkIHZhcigtLXBhcGVyLXNsaWRlci1rbm9iLXN0YXJ0LWJvcmRlci1jb2xvciwgdmFyKC0tcGFwZXItZ3JleS00MDApKTsKICAgIH0KCiAgICBwYXBlci1yaXBwbGUgewogICAgICBjb2xvcjogdmFyKC0tcGFwZXItc2xpZGVyLWtub2ItY29sb3IsIHZhcigtLWdvb2dsZS1ibHVlLTcwMCkpOwogICAgfQogIDwvc3R5bGU+CgogIDxkaXYgaWQ9InNsaWRlckNvbnRhaW5lciIgY2xhc3NcJD0iW1tfZ2V0Q2xhc3NOYW1lcyhkaXNhYmxlZCwgcGluLCBzbmFwcywgaW1tZWRpYXRlVmFsdWUsIG1pbiwgZXhwYW5kLCBkcmFnZ2luZywgdHJhbnNpdGluZywgZWRpdGFibGUpXV0iPgogICAgPGRpdiBjbGFzcz0iYmFyLWNvbnRhaW5lciI+CiAgICAgIDxwYXBlci1wcm9ncmVzcyBkaXNhYmxlZFwkPSJbW2Rpc2FibGVkXV0iIGlkPSJzbGlkZXJCYXIiIGFyaWEtaGlkZGVuPSJ0cnVlIiBtaW49IltbbWluXV0iIG1heD0iW1ttYXhdXSIgc3RlcD0iW1tzdGVwXV0iIHZhbHVlPSJbW2ltbWVkaWF0ZVZhbHVlXV0iIHNlY29uZGFyeS1wcm9ncmVzcz0iW1tzZWNvbmRhcnlQcm9ncmVzc11dIiBvbi1kb3duPSJfYmFyZG93biIgb24tdXA9Il9yZXNldEtub2IiIG9uLXRyYWNrPSJfYmFydHJhY2siIG9uLXRhcD0iX2JhcmNsaWNrIj4KICAgICAgPC9wYXBlci1wcm9ncmVzcz4KICAgIDwvZGl2PgoKICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tzbmFwc11dIj4KICAgICAgPGRpdiBjbGFzcz0ic2xpZGVyLW1hcmtlcnMiPgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9IltbbWFya2Vyc11dIj4KICAgICAgICAgIDxkaXYgY2xhc3M9InNsaWRlci1tYXJrZXIiPjwvZGl2PgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDwvZGl2PgogICAgPC90ZW1wbGF0ZT4KCiAgICA8ZGl2IGlkPSJzbGlkZXJLbm9iIiBjbGFzcz0ic2xpZGVyLWtub2IiIG9uLWRvd249Il9rbm9iZG93biIgb24tdXA9Il9yZXNldEtub2IiIG9uLXRyYWNrPSJfb25UcmFjayIgb24tdHJhbnNpdGlvbmVuZD0iX2tub2JUcmFuc2l0aW9uRW5kIj4KICAgICAgICA8ZGl2IGNsYXNzPSJzbGlkZXIta25vYi1pbm5lciIgdmFsdWVcJD0iW1tpbW1lZGlhdGVWYWx1ZV1dIj48L2Rpdj4KICAgIDwvZGl2PgogIDwvZGl2PgoKICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbZWRpdGFibGVdXSI+CiAgICA8cGFwZXItaW5wdXQgaWQ9ImlucHV0IiB0eXBlPSJudW1iZXIiIHN0ZXA9Iltbc3RlcF1dIiBtaW49IltbbWluXV0iIG1heD0iW1ttYXhdXSIgY2xhc3M9InNsaWRlci1pbnB1dCIgZGlzYWJsZWRcJD0iW1tkaXNhYmxlZF1dIiB2YWx1ZT0iW1tpbW1lZGlhdGVWYWx1ZV1dIiBvbi1jaGFuZ2U9Il9jaGFuZ2VWYWx1ZSIgb24ta2V5ZG93bj0iX2lucHV0S2V5RG93biIgbm8tbGFiZWwtZmxvYXQ+CiAgICA8L3BhcGVyLWlucHV0PgogIDwvdGVtcGxhdGU+CmA7QTB0LnNldEF0dHJpYnV0ZSgic3RyaXAtd2hpdGVzcGFjZSIsIiIpO1l0KHtfdGVtcGxhdGU6QTB0LGlzOiJwYXBlci1zbGlkZXIiLGJlaGF2aW9yczpbT28sRWgsangsTDldLHByb3BlcnRpZXM6e3ZhbHVlOnt0eXBlOk51bWJlcix2YWx1ZTowfSxzbmFwczp7dHlwZTpCb29sZWFuLHZhbHVlOiExLG5vdGlmeTohMH0scGluOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITEsbm90aWZ5OiEwfSxzZWNvbmRhcnlQcm9ncmVzczp7dHlwZTpOdW1iZXIsdmFsdWU6MCxub3RpZnk6ITAsb2JzZXJ2ZXI6Il9zZWNvbmRhcnlQcm9ncmVzc0NoYW5nZWQifSxlZGl0YWJsZTp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxpbW1lZGlhdGVWYWx1ZTp7dHlwZTpOdW1iZXIsdmFsdWU6MCxyZWFkT25seTohMCxub3RpZnk6ITB9LG1heE1hcmtlcnM6e3R5cGU6TnVtYmVyLHZhbHVlOjAsbm90aWZ5OiEwfSxleHBhbmQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxyZWFkT25seTohMH0saWdub3JlQmFyVG91Y2g6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sZHJhZ2dpbmc6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxyZWFkT25seTohMCxub3RpZnk6ITB9LHRyYW5zaXRpbmc6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxyZWFkT25seTohMH0sbWFya2Vyczp7dHlwZTpBcnJheSxyZWFkT25seTohMCx2YWx1ZTpmdW5jdGlvbigpe3JldHVybltdfX19LG9ic2VydmVyczpbIl91cGRhdGVLbm9iKHZhbHVlLCBtaW4sIG1heCwgc25hcHMsIHN0ZXApIiwiX3ZhbHVlQ2hhbmdlZCh2YWx1ZSkiLCJfaW1tZWRpYXRlVmFsdWVDaGFuZ2VkKGltbWVkaWF0ZVZhbHVlKSIsIl91cGRhdGVNYXJrZXJzKG1heE1hcmtlcnMsIG1pbiwgbWF4LCBzbmFwcykiXSxob3N0QXR0cmlidXRlczp7cm9sZToic2xpZGVyIix0YWJpbmRleDowfSxrZXlCaW5kaW5nczp7bGVmdDoiX2xlZnRLZXkiLHJpZ2h0OiJfcmlnaHRLZXkiLCJkb3duIHBhZ2Vkb3duIGhvbWUiOiJfZGVjcmVtZW50S2V5IiwidXAgcGFnZXVwIGVuZCI6Il9pbmNyZW1lbnRLZXkifSxyZWFkeTpmdW5jdGlvbigpe3RoaXMuaWdub3JlQmFyVG91Y2gmJmRfKHRoaXMuJC5zbGlkZXJCYXIsImF1dG8iKX0saW5jcmVtZW50OmZ1bmN0aW9uKCl7dGhpcy52YWx1ZT10aGlzLl9jbGFtcFZhbHVlKHRoaXMudmFsdWUrdGhpcy5zdGVwKX0sZGVjcmVtZW50OmZ1bmN0aW9uKCl7dGhpcy52YWx1ZT10aGlzLl9jbGFtcFZhbHVlKHRoaXMudmFsdWUtdGhpcy5zdGVwKX0sX3VwZGF0ZUtub2I6ZnVuY3Rpb24oZSx0LHIsbixpKXt0aGlzLnNldEF0dHJpYnV0ZSgiYXJpYS12YWx1ZW1pbiIsdCksdGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtdmFsdWVtYXgiLHIpLHRoaXMuc2V0QXR0cmlidXRlKCJhcmlhLXZhbHVlbm93IixlKSx0aGlzLl9wb3NpdGlvbktub2IodGhpcy5fY2FsY1JhdGlvKGUpKjEwMCl9LF92YWx1ZUNoYW5nZWQ6ZnVuY3Rpb24oKXt0aGlzLmZpcmUoInZhbHVlLWNoYW5nZSIse2NvbXBvc2VkOiEwfSl9LF9pbW1lZGlhdGVWYWx1ZUNoYW5nZWQ6ZnVuY3Rpb24oKXt0aGlzLmRyYWdnaW5nP3RoaXMuZmlyZSgiaW1tZWRpYXRlLXZhbHVlLWNoYW5nZSIse2NvbXBvc2VkOiEwfSk6dGhpcy52YWx1ZT10aGlzLmltbWVkaWF0ZVZhbHVlfSxfc2Vjb25kYXJ5UHJvZ3Jlc3NDaGFuZ2VkOmZ1bmN0aW9uKCl7dGhpcy5zZWNvbmRhcnlQcm9ncmVzcz10aGlzLl9jbGFtcFZhbHVlKHRoaXMuc2Vjb25kYXJ5UHJvZ3Jlc3MpfSxfZXhwYW5kS25vYjpmdW5jdGlvbigpe3RoaXMuX3NldEV4cGFuZCghMCl9LF9yZXNldEtub2I6ZnVuY3Rpb24oKXt0aGlzLmNhbmNlbERlYm91bmNlcigiZXhwYW5kS25vYiIpLHRoaXMuX3NldEV4cGFuZCghMSl9LF9wb3NpdGlvbktub2I6ZnVuY3Rpb24oZSl7dGhpcy5fc2V0SW1tZWRpYXRlVmFsdWUodGhpcy5fY2FsY1N0ZXAodGhpcy5fY2FsY0tub2JQb3NpdGlvbihlKSkpLHRoaXMuX3NldFJhdGlvKHRoaXMuX2NhbGNSYXRpbyh0aGlzLmltbWVkaWF0ZVZhbHVlKSoxMDApLHRoaXMuJC5zbGlkZXJLbm9iLnN0eWxlLmxlZnQ9dGhpcy5yYXRpbysiJSIsdGhpcy5kcmFnZ2luZyYmKHRoaXMuX2tub2JzdGFydHg9dGhpcy5yYXRpbyp0aGlzLl93LzEwMCx0aGlzLnRyYW5zbGF0ZTNkKDAsMCwwLHRoaXMuJC5zbGlkZXJLbm9iKSl9LF9jYWxjS25vYlBvc2l0aW9uOmZ1bmN0aW9uKGUpe3JldHVybih0aGlzLm1heC10aGlzLm1pbikqZS8xMDArdGhpcy5taW59LF9vblRyYWNrOmZ1bmN0aW9uKGUpe3N3aXRjaChlLnN0b3BQcm9wYWdhdGlvbigpLGUuZGV0YWlsLnN0YXRlKXtjYXNlInN0YXJ0Ijp0aGlzLl90cmFja1N0YXJ0KGUpO2JyZWFrO2Nhc2UidHJhY2siOnRoaXMuX3RyYWNrWChlKTticmVhaztjYXNlImVuZCI6dGhpcy5fdHJhY2tFbmQoKTticmVha319LF90cmFja1N0YXJ0OmZ1bmN0aW9uKGUpe3RoaXMuX3NldFRyYW5zaXRpbmcoITEpLHRoaXMuX3c9dGhpcy4kLnNsaWRlckJhci5vZmZzZXRXaWR0aCx0aGlzLl94PXRoaXMucmF0aW8qdGhpcy5fdy8xMDAsdGhpcy5fc3RhcnR4PXRoaXMuX3gsdGhpcy5fa25vYnN0YXJ0eD10aGlzLl9zdGFydHgsdGhpcy5fbWlueD0tdGhpcy5fc3RhcnR4LHRoaXMuX21heHg9dGhpcy5fdy10aGlzLl9zdGFydHgsdGhpcy4kLnNsaWRlcktub2IuY2xhc3NMaXN0LmFkZCgiZHJhZ2dpbmciKSx0aGlzLl9zZXREcmFnZ2luZyghMCl9LF90cmFja1g6ZnVuY3Rpb24oZSl7dGhpcy5kcmFnZ2luZ3x8dGhpcy5fdHJhY2tTdGFydChlKTt2YXIgdD10aGlzLl9pc1JUTD8tMToxLHI9TWF0aC5taW4odGhpcy5fbWF4eCxNYXRoLm1heCh0aGlzLl9taW54LGUuZGV0YWlsLmR4KnQpKTt0aGlzLl94PXRoaXMuX3N0YXJ0eCtyO3ZhciBuPXRoaXMuX2NhbGNTdGVwKHRoaXMuX2NhbGNLbm9iUG9zaXRpb24odGhpcy5feC90aGlzLl93KjEwMCkpO3RoaXMuX3NldEltbWVkaWF0ZVZhbHVlKG4pO3ZhciBpPXRoaXMuX2NhbGNSYXRpbyh0aGlzLmltbWVkaWF0ZVZhbHVlKSp0aGlzLl93LXRoaXMuX2tub2JzdGFydHg7dGhpcy50cmFuc2xhdGUzZChpKyJweCIsMCwwLHRoaXMuJC5zbGlkZXJLbm9iKX0sX3RyYWNrRW5kOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy4kLnNsaWRlcktub2Iuc3R5bGU7dGhpcy4kLnNsaWRlcktub2IuY2xhc3NMaXN0LnJlbW92ZSgiZHJhZ2dpbmciKSx0aGlzLl9zZXREcmFnZ2luZyghMSksdGhpcy5fcmVzZXRLbm9iKCksdGhpcy52YWx1ZT10aGlzLmltbWVkaWF0ZVZhbHVlLGUudHJhbnNmb3JtPWUud2Via2l0VHJhbnNmb3JtPSIiLHRoaXMuZmlyZSgiY2hhbmdlIix7Y29tcG9zZWQ6ITB9KX0sX2tub2Jkb3duOmZ1bmN0aW9uKGUpe3RoaXMuX2V4cGFuZEtub2IoKSxlLnByZXZlbnREZWZhdWx0KCksdGhpcy5mb2N1cygpfSxfYmFydHJhY2s6ZnVuY3Rpb24oZSl7dGhpcy5fYWxsb3dCYXJFdmVudChlKSYmdGhpcy5fb25UcmFjayhlKX0sX2JhcmNsaWNrOmZ1bmN0aW9uKGUpe3RoaXMuX3c9dGhpcy4kLnNsaWRlckJhci5vZmZzZXRXaWR0aDt2YXIgdD10aGlzLiQuc2xpZGVyQmFyLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLHI9KGUuZGV0YWlsLngtdC5sZWZ0KS90aGlzLl93KjEwMDt0aGlzLl9pc1JUTCYmKHI9MTAwLXIpO3ZhciBuPXRoaXMucmF0aW87dGhpcy5fc2V0VHJhbnNpdGluZyghMCksdGhpcy5fcG9zaXRpb25Lbm9iKHIpLG49PT10aGlzLnJhdGlvJiZ0aGlzLl9zZXRUcmFuc2l0aW5nKCExKSx0aGlzLmFzeW5jKGZ1bmN0aW9uKCl7dGhpcy5maXJlKCJjaGFuZ2UiLHtjb21wb3NlZDohMH0pfSksZS5wcmV2ZW50RGVmYXVsdCgpLHRoaXMuZm9jdXMoKX0sX2JhcmRvd246ZnVuY3Rpb24oZSl7dGhpcy5fYWxsb3dCYXJFdmVudChlKSYmKHRoaXMuZGVib3VuY2UoImV4cGFuZEtub2IiLHRoaXMuX2V4cGFuZEtub2IsNjApLHRoaXMuX2JhcmNsaWNrKGUpKX0sX2tub2JUcmFuc2l0aW9uRW5kOmZ1bmN0aW9uKGUpe2UudGFyZ2V0PT09dGhpcy4kLnNsaWRlcktub2ImJnRoaXMuX3NldFRyYW5zaXRpbmcoITEpfSxfdXBkYXRlTWFya2VyczpmdW5jdGlvbihlLHQscixuKXtufHx0aGlzLl9zZXRNYXJrZXJzKFtdKTt2YXIgaT1NYXRoLnJvdW5kKChyLXQpL3RoaXMuc3RlcCk7aT5lJiYoaT1lKSwoaTwwfHwhaXNGaW5pdGUoaSkpJiYoaT0wKSx0aGlzLl9zZXRNYXJrZXJzKG5ldyBBcnJheShpKSl9LF9tZXJnZUNsYXNzZXM6ZnVuY3Rpb24oZSl7cmV0dXJuIE9iamVjdC5rZXlzKGUpLmZpbHRlcihmdW5jdGlvbih0KXtyZXR1cm4gZVt0XX0pLmpvaW4oIiAiKX0sX2dldENsYXNzTmFtZXM6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fbWVyZ2VDbGFzc2VzKHtkaXNhYmxlZDp0aGlzLmRpc2FibGVkLHBpbjp0aGlzLnBpbixzbmFwczp0aGlzLnNuYXBzLHJpbmc6dGhpcy5pbW1lZGlhdGVWYWx1ZTw9dGhpcy5taW4sZXhwYW5kOnRoaXMuZXhwYW5kLGRyYWdnaW5nOnRoaXMuZHJhZ2dpbmcsdHJhbnNpdGluZzp0aGlzLnRyYW5zaXRpbmcsZWRpdGFibGU6dGhpcy5lZGl0YWJsZX0pfSxfYWxsb3dCYXJFdmVudDpmdW5jdGlvbihlKXtyZXR1cm4hdGhpcy5pZ25vcmVCYXJUb3VjaHx8ZS5kZXRhaWwuc291cmNlRXZlbnQgaW5zdGFuY2VvZiBNb3VzZUV2ZW50fSxnZXQgX2lzUlRMKCl7cmV0dXJuIHRoaXMuX19pc1JUTD09PXZvaWQgMCYmKHRoaXMuX19pc1JUTD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzKS5kaXJlY3Rpb249PT0icnRsIiksdGhpcy5fX2lzUlRMfSxfbGVmdEtleTpmdW5jdGlvbihlKXt0aGlzLl9pc1JUTD90aGlzLl9pbmNyZW1lbnRLZXkoZSk6dGhpcy5fZGVjcmVtZW50S2V5KGUpfSxfcmlnaHRLZXk6ZnVuY3Rpb24oZSl7dGhpcy5faXNSVEw/dGhpcy5fZGVjcmVtZW50S2V5KGUpOnRoaXMuX2luY3JlbWVudEtleShlKX0sX2luY3JlbWVudEtleTpmdW5jdGlvbihlKXt0aGlzLmRpc2FibGVkfHwoZS5kZXRhaWwua2V5PT09ImVuZCI/dGhpcy52YWx1ZT10aGlzLm1heDp0aGlzLmluY3JlbWVudCgpLHRoaXMuZmlyZSgiY2hhbmdlIiksZS5wcmV2ZW50RGVmYXVsdCgpKX0sX2RlY3JlbWVudEtleTpmdW5jdGlvbihlKXt0aGlzLmRpc2FibGVkfHwoZS5kZXRhaWwua2V5PT09ImhvbWUiP3RoaXMudmFsdWU9dGhpcy5taW46dGhpcy5kZWNyZW1lbnQoKSx0aGlzLmZpcmUoImNoYW5nZSIpLGUucHJldmVudERlZmF1bHQoKSl9LF9jaGFuZ2VWYWx1ZTpmdW5jdGlvbihlKXt0aGlzLnZhbHVlPWUudGFyZ2V0LnZhbHVlLHRoaXMuZmlyZSgiY2hhbmdlIix7Y29tcG9zZWQ6ITB9KX0sX2lucHV0S2V5RG93bjpmdW5jdGlvbihlKXtlLnN0b3BQcm9wYWdhdGlvbigpfSxfY3JlYXRlUmlwcGxlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3JpcHBsZUNvbnRhaW5lcj10aGlzLiQuc2xpZGVyS25vYixmRS5fY3JlYXRlUmlwcGxlLmNhbGwodGhpcyl9LF9mb2N1c2VkQ2hhbmdlZDpmdW5jdGlvbihlKXtlJiZ0aGlzLmVuc3VyZVJpcHBsZSgpLHRoaXMuaGFzUmlwcGxlKCkmJihlP3RoaXMuX3JpcHBsZS5zdHlsZS5kaXNwbGF5PSIiOnRoaXMuX3JpcHBsZS5zdHlsZS5kaXNwbGF5PSJub25lIix0aGlzLl9yaXBwbGUuaG9sZERvd249ZSl9fSk7dmFyIEVXPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInRlbXBsYXRlIik7RVcuc2V0QXR0cmlidXRlKCJzdHlsZSIsImRpc3BsYXk6IG5vbmU7Iik7RVcuaW5uZXJIVE1MPWA8ZG9tLW1vZHVsZSBpZD0icGFwZXItc3Bpbm5lci1zdHlsZXMiPgogIDx0ZW1wbGF0ZT4KICAgIDxzdHlsZT4KICAgICAgLyoKICAgICAgLyoqKioqKioqKioqKioqKioqKioqKioqKioqLwogICAgICAvKiBTVFlMRVMgRk9SIFRIRSBTUElOTkVSICovCiAgICAgIC8qKioqKioqKioqKioqKioqKioqKioqKioqKi8KCiAgICAgIC8qCiAgICAgICAqIENvbnN0YW50czoKICAgICAgICogICAgICBBUkNTSVpFICAgICA9IDI3MCBkZWdyZWVzIChhbW91bnQgb2YgY2lyY2xlIHRoZSBhcmMgdGFrZXMgdXApCiAgICAgICAqICAgICAgQVJDVElNRSAgICAgPSAxMzMzbXMgKHRpbWUgaXQgdGFrZXMgdG8gZXhwYW5kIGFuZCBjb250cmFjdCBhcmMpCiAgICAgICAqICAgICAgQVJDU1RBUlRST1QgPSAyMTYgZGVncmVlcyAoaG93IG11Y2ggdGhlIHN0YXJ0IGxvY2F0aW9uIG9mIHRoZSBhcmMKICAgICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3VsZCByb3RhdGUgZWFjaCB0aW1lLCAyMTYgZ2l2ZXMgdXMgYQogICAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgNSBwb2ludGVkIHN0YXIgc2hhcGUgKGl0J3MgMzYwLzUgKiAzKS4KICAgICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZvciBhIDcgcG9pbnRlZCBzdGFyLCB3ZSBtaWdodCBkbwogICAgICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMzYwLzcgKiAzID0gMTU0LjI4NikKICAgICAgICogICAgICBTSFJJTktfVElNRSA9IDQwMG1zCiAgICAgICAqLwoKICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgd2lkdGg6IDI4cHg7CiAgICAgICAgaGVpZ2h0OiAyOHB4OwoKICAgICAgICAvKiAzNjAgKiBBUkNUSU1FIC8gKEFSQ1NUQVJUUk9UICsgKDM2MC1BUkNTSVpFKSkgKi8KICAgICAgICAtLXBhcGVyLXNwaW5uZXItY29udGFpbmVyLXJvdGF0aW9uLWR1cmF0aW9uOiAxNTY4bXM7CgogICAgICAgIC8qIEFSQ1RJTUUgKi8KICAgICAgICAtLXBhcGVyLXNwaW5uZXItZXhwYW5kLWNvbnRyYWN0LWR1cmF0aW9uOiAxMzMzbXM7CgogICAgICAgIC8qIDQgKiBBUkNUSU1FICovCiAgICAgICAgLS1wYXBlci1zcGlubmVyLWZ1bGwtY3ljbGUtZHVyYXRpb246IDUzMzJtczsKCiAgICAgICAgLyogU0hSSU5LX1RJTUUgKi8KICAgICAgICAtLXBhcGVyLXNwaW5uZXItY29vbGRvd24tZHVyYXRpb246IDQwMG1zOwogICAgICB9CgogICAgICAjc3Bpbm5lckNvbnRhaW5lciB7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwoKICAgICAgICAvKiBUaGUgc3Bpbm5lciBkb2VzIG5vdCBoYXZlIGFueSBjb250ZW50cyB0aGF0IHdvdWxkIGhhdmUgdG8gYmUKICAgICAgICAgKiBmbGlwcGVkIGlmIHRoZSBkaXJlY3Rpb24gY2hhbmdlcy4gQWx3YXlzIHVzZSBsdHIgc28gdGhhdCB0aGUKICAgICAgICAgKiBzdHlsZSB3b3JrcyBvdXQgY29ycmVjdGx5IGluIGJvdGggY2FzZXMuICovCiAgICAgICAgZGlyZWN0aW9uOiBsdHI7CiAgICAgIH0KCiAgICAgICNzcGlubmVyQ29udGFpbmVyLmFjdGl2ZSB7CiAgICAgICAgLXdlYmtpdC1hbmltYXRpb246IGNvbnRhaW5lci1yb3RhdGUgdmFyKC0tcGFwZXItc3Bpbm5lci1jb250YWluZXItcm90YXRpb24tZHVyYXRpb24pIGxpbmVhciBpbmZpbml0ZTsKICAgICAgICBhbmltYXRpb246IGNvbnRhaW5lci1yb3RhdGUgdmFyKC0tcGFwZXItc3Bpbm5lci1jb250YWluZXItcm90YXRpb24tZHVyYXRpb24pIGxpbmVhciBpbmZpbml0ZTsKICAgICAgfQoKICAgICAgQC13ZWJraXQta2V5ZnJhbWVzIGNvbnRhaW5lci1yb3RhdGUgewogICAgICAgIHRvIHsgLXdlYmtpdC10cmFuc2Zvcm06IHJvdGF0ZSgzNjBkZWcpIH0KICAgICAgfQoKICAgICAgQGtleWZyYW1lcyBjb250YWluZXItcm90YXRlIHsKICAgICAgICB0byB7IHRyYW5zZm9ybTogcm90YXRlKDM2MGRlZykgfQogICAgICB9CgogICAgICAuc3Bpbm5lci1sYXllciB7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgICBvcGFjaXR5OiAwOwogICAgICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLXNwaW5uZXItY29sb3IsIHZhcigtLWdvb2dsZS1ibHVlLTUwMCkpOwogICAgICB9CgogICAgICAubGF5ZXItMSB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLXNwaW5uZXItbGF5ZXItMS1jb2xvciwgdmFyKC0tZ29vZ2xlLWJsdWUtNTAwKSk7CiAgICAgIH0KCiAgICAgIC5sYXllci0yIHsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItc3Bpbm5lci1sYXllci0yLWNvbG9yLCB2YXIoLS1nb29nbGUtcmVkLTUwMCkpOwogICAgICB9CgogICAgICAubGF5ZXItMyB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLXNwaW5uZXItbGF5ZXItMy1jb2xvciwgdmFyKC0tZ29vZ2xlLXllbGxvdy01MDApKTsKICAgICAgfQoKICAgICAgLmxheWVyLTQgewogICAgICAgIGNvbG9yOiB2YXIoLS1wYXBlci1zcGlubmVyLWxheWVyLTQtY29sb3IsIHZhcigtLWdvb2dsZS1ncmVlbi01MDApKTsKICAgICAgfQoKICAgICAgLyoqCiAgICAgICAqIElNUE9SVEFOVCBOT1RFIEFCT1VUIENTUyBBTklNQVRJT04gUFJPUEVSVElFUyAoa2VhbnVsZWUpOgogICAgICAgKgogICAgICAgKiBpT1MgU2FmYXJpICh0ZXN0ZWQgb24gaU9TIDguMSkgZG9lcyBub3QgaGFuZGxlIGFuaW1hdGlvbi1kZWxheSB2ZXJ5IHdlbGwgLSBpdCBkb2Vzbid0CiAgICAgICAqIGd1YXJhbnRlZSB0aGF0IHRoZSBhbmltYXRpb24gd2lsbCBzdGFydCBfZXhhY3RseV8gYWZ0ZXIgdGhhdCB2YWx1ZS4gU28gd2UgYXZvaWQgdXNpbmcKICAgICAgICogYW5pbWF0aW9uLWRlbGF5IGFuZCBpbnN0ZWFkIHNldCBjdXN0b20ga2V5ZnJhbWVzIGZvciBlYWNoIGNvbG9yIChhcyBsYXllci0ydW5kYW50IGFzIGl0CiAgICAgICAqIHNlZW1zKS4KICAgICAgICovCiAgICAgIC5hY3RpdmUgLnNwaW5uZXItbGF5ZXIgewogICAgICAgIC13ZWJraXQtYW5pbWF0aW9uLW5hbWU6IGZpbGwtdW5maWxsLXJvdGF0ZTsKICAgICAgICAtd2Via2l0LWFuaW1hdGlvbi1kdXJhdGlvbjogdmFyKC0tcGFwZXItc3Bpbm5lci1mdWxsLWN5Y2xlLWR1cmF0aW9uKTsKICAgICAgICAtd2Via2l0LWFuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246IGN1YmljLWJlemllcigwLjQsIDAuMCwgMC4yLCAxKTsKICAgICAgICAtd2Via2l0LWFuaW1hdGlvbi1pdGVyYXRpb24tY291bnQ6IGluZmluaXRlOwogICAgICAgIGFuaW1hdGlvbi1uYW1lOiBmaWxsLXVuZmlsbC1yb3RhdGU7CiAgICAgICAgYW5pbWF0aW9uLWR1cmF0aW9uOiB2YXIoLS1wYXBlci1zcGlubmVyLWZ1bGwtY3ljbGUtZHVyYXRpb24pOwogICAgICAgIGFuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246IGN1YmljLWJlemllcigwLjQsIDAuMCwgMC4yLCAxKTsKICAgICAgICBhbmltYXRpb24taXRlcmF0aW9uLWNvdW50OiBpbmZpbml0ZTsKICAgICAgICBvcGFjaXR5OiAxOwogICAgICB9CgogICAgICAuYWN0aXZlIC5zcGlubmVyLWxheWVyLmxheWVyLTEgewogICAgICAgIC13ZWJraXQtYW5pbWF0aW9uLW5hbWU6IGZpbGwtdW5maWxsLXJvdGF0ZSwgbGF5ZXItMS1mYWRlLWluLW91dDsKICAgICAgICBhbmltYXRpb24tbmFtZTogZmlsbC11bmZpbGwtcm90YXRlLCBsYXllci0xLWZhZGUtaW4tb3V0OwogICAgICB9CgogICAgICAuYWN0aXZlIC5zcGlubmVyLWxheWVyLmxheWVyLTIgewogICAgICAgIC13ZWJraXQtYW5pbWF0aW9uLW5hbWU6IGZpbGwtdW5maWxsLXJvdGF0ZSwgbGF5ZXItMi1mYWRlLWluLW91dDsKICAgICAgICBhbmltYXRpb24tbmFtZTogZmlsbC11bmZpbGwtcm90YXRlLCBsYXllci0yLWZhZGUtaW4tb3V0OwogICAgICB9CgogICAgICAuYWN0aXZlIC5zcGlubmVyLWxheWVyLmxheWVyLTMgewogICAgICAgIC13ZWJraXQtYW5pbWF0aW9uLW5hbWU6IGZpbGwtdW5maWxsLXJvdGF0ZSwgbGF5ZXItMy1mYWRlLWluLW91dDsKICAgICAgICBhbmltYXRpb24tbmFtZTogZmlsbC11bmZpbGwtcm90YXRlLCBsYXllci0zLWZhZGUtaW4tb3V0OwogICAgICB9CgogICAgICAuYWN0aXZlIC5zcGlubmVyLWxheWVyLmxheWVyLTQgewogICAgICAgIC13ZWJraXQtYW5pbWF0aW9uLW5hbWU6IGZpbGwtdW5maWxsLXJvdGF0ZSwgbGF5ZXItNC1mYWRlLWluLW91dDsKICAgICAgICBhbmltYXRpb24tbmFtZTogZmlsbC11bmZpbGwtcm90YXRlLCBsYXllci00LWZhZGUtaW4tb3V0OwogICAgICB9CgogICAgICBALXdlYmtpdC1rZXlmcmFtZXMgZmlsbC11bmZpbGwtcm90YXRlIHsKICAgICAgICAxMi41JSB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMTM1ZGVnKSB9IC8qIDAuNSAqIEFSQ1NJWkUgKi8KICAgICAgICAyNSUgICB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMjcwZGVnKSB9IC8qIDEgICAqIEFSQ1NJWkUgKi8KICAgICAgICAzNy41JSB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoNDA1ZGVnKSB9IC8qIDEuNSAqIEFSQ1NJWkUgKi8KICAgICAgICA1MCUgICB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoNTQwZGVnKSB9IC8qIDIgICAqIEFSQ1NJWkUgKi8KICAgICAgICA2Mi41JSB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoNjc1ZGVnKSB9IC8qIDIuNSAqIEFSQ1NJWkUgKi8KICAgICAgICA3NSUgICB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoODEwZGVnKSB9IC8qIDMgICAqIEFSQ1NJWkUgKi8KICAgICAgICA4Ny41JSB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoOTQ1ZGVnKSB9IC8qIDMuNSAqIEFSQ1NJWkUgKi8KICAgICAgICB0byAgICB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMTA4MGRlZykgfSAvKiA0ICAgKiBBUkNTSVpFICovCiAgICAgIH0KCiAgICAgIEBrZXlmcmFtZXMgZmlsbC11bmZpbGwtcm90YXRlIHsKICAgICAgICAxMi41JSB7IHRyYW5zZm9ybTogcm90YXRlKDEzNWRlZykgfSAvKiAwLjUgKiBBUkNTSVpFICovCiAgICAgICAgMjUlICAgeyB0cmFuc2Zvcm06IHJvdGF0ZSgyNzBkZWcpIH0gLyogMSAgICogQVJDU0laRSAqLwogICAgICAgIDM3LjUlIHsgdHJhbnNmb3JtOiByb3RhdGUoNDA1ZGVnKSB9IC8qIDEuNSAqIEFSQ1NJWkUgKi8KICAgICAgICA1MCUgICB7IHRyYW5zZm9ybTogcm90YXRlKDU0MGRlZykgfSAvKiAyICAgKiBBUkNTSVpFICovCiAgICAgICAgNjIuNSUgeyB0cmFuc2Zvcm06IHJvdGF0ZSg2NzVkZWcpIH0gLyogMi41ICogQVJDU0laRSAqLwogICAgICAgIDc1JSAgIHsgdHJhbnNmb3JtOiByb3RhdGUoODEwZGVnKSB9IC8qIDMgICAqIEFSQ1NJWkUgKi8KICAgICAgICA4Ny41JSB7IHRyYW5zZm9ybTogcm90YXRlKDk0NWRlZykgfSAvKiAzLjUgKiBBUkNTSVpFICovCiAgICAgICAgdG8gICAgeyB0cmFuc2Zvcm06IHJvdGF0ZSgxMDgwZGVnKSB9IC8qIDQgICAqIEFSQ1NJWkUgKi8KICAgICAgfQoKICAgICAgQC13ZWJraXQta2V5ZnJhbWVzIGxheWVyLTEtZmFkZS1pbi1vdXQgewogICAgICAgIDAlIHsgb3BhY2l0eTogMSB9CiAgICAgICAgMjUlIHsgb3BhY2l0eTogMSB9CiAgICAgICAgMjYlIHsgb3BhY2l0eTogMCB9CiAgICAgICAgODklIHsgb3BhY2l0eTogMCB9CiAgICAgICAgOTAlIHsgb3BhY2l0eTogMSB9CiAgICAgICAgdG8geyBvcGFjaXR5OiAxIH0KICAgICAgfQoKICAgICAgQGtleWZyYW1lcyBsYXllci0xLWZhZGUtaW4tb3V0IHsKICAgICAgICAwJSB7IG9wYWNpdHk6IDEgfQogICAgICAgIDI1JSB7IG9wYWNpdHk6IDEgfQogICAgICAgIDI2JSB7IG9wYWNpdHk6IDAgfQogICAgICAgIDg5JSB7IG9wYWNpdHk6IDAgfQogICAgICAgIDkwJSB7IG9wYWNpdHk6IDEgfQogICAgICAgIHRvIHsgb3BhY2l0eTogMSB9CiAgICAgIH0KCiAgICAgIEAtd2Via2l0LWtleWZyYW1lcyBsYXllci0yLWZhZGUtaW4tb3V0IHsKICAgICAgICAwJSB7IG9wYWNpdHk6IDAgfQogICAgICAgIDE1JSB7IG9wYWNpdHk6IDAgfQogICAgICAgIDI1JSB7IG9wYWNpdHk6IDEgfQogICAgICAgIDUwJSB7IG9wYWNpdHk6IDEgfQogICAgICAgIDUxJSB7IG9wYWNpdHk6IDAgfQogICAgICAgIHRvIHsgb3BhY2l0eTogMCB9CiAgICAgIH0KCiAgICAgIEBrZXlmcmFtZXMgbGF5ZXItMi1mYWRlLWluLW91dCB7CiAgICAgICAgMCUgeyBvcGFjaXR5OiAwIH0KICAgICAgICAxNSUgeyBvcGFjaXR5OiAwIH0KICAgICAgICAyNSUgeyBvcGFjaXR5OiAxIH0KICAgICAgICA1MCUgeyBvcGFjaXR5OiAxIH0KICAgICAgICA1MSUgeyBvcGFjaXR5OiAwIH0KICAgICAgICB0byB7IG9wYWNpdHk6IDAgfQogICAgICB9CgogICAgICBALXdlYmtpdC1rZXlmcmFtZXMgbGF5ZXItMy1mYWRlLWluLW91dCB7CiAgICAgICAgMCUgeyBvcGFjaXR5OiAwIH0KICAgICAgICA0MCUgeyBvcGFjaXR5OiAwIH0KICAgICAgICA1MCUgeyBvcGFjaXR5OiAxIH0KICAgICAgICA3NSUgeyBvcGFjaXR5OiAxIH0KICAgICAgICA3NiUgeyBvcGFjaXR5OiAwIH0KICAgICAgICB0byB7IG9wYWNpdHk6IDAgfQogICAgICB9CgogICAgICBAa2V5ZnJhbWVzIGxheWVyLTMtZmFkZS1pbi1vdXQgewogICAgICAgIDAlIHsgb3BhY2l0eTogMCB9CiAgICAgICAgNDAlIHsgb3BhY2l0eTogMCB9CiAgICAgICAgNTAlIHsgb3BhY2l0eTogMSB9CiAgICAgICAgNzUlIHsgb3BhY2l0eTogMSB9CiAgICAgICAgNzYlIHsgb3BhY2l0eTogMCB9CiAgICAgICAgdG8geyBvcGFjaXR5OiAwIH0KICAgICAgfQoKICAgICAgQC13ZWJraXQta2V5ZnJhbWVzIGxheWVyLTQtZmFkZS1pbi1vdXQgewogICAgICAgIDAlIHsgb3BhY2l0eTogMCB9CiAgICAgICAgNjUlIHsgb3BhY2l0eTogMCB9CiAgICAgICAgNzUlIHsgb3BhY2l0eTogMSB9CiAgICAgICAgOTAlIHsgb3BhY2l0eTogMSB9CiAgICAgICAgdG8geyBvcGFjaXR5OiAwIH0KICAgICAgfQoKICAgICAgQGtleWZyYW1lcyBsYXllci00LWZhZGUtaW4tb3V0IHsKICAgICAgICAwJSB7IG9wYWNpdHk6IDAgfQogICAgICAgIDY1JSB7IG9wYWNpdHk6IDAgfQogICAgICAgIDc1JSB7IG9wYWNpdHk6IDEgfQogICAgICAgIDkwJSB7IG9wYWNpdHk6IDEgfQogICAgICAgIHRvIHsgb3BhY2l0eTogMCB9CiAgICAgIH0KCiAgICAgIC5jaXJjbGUtY2xpcHBlciB7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICB3aWR0aDogNTAlOwogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgICB9CgogICAgICAvKioKICAgICAgICogUGF0Y2ggdGhlIGdhcCB0aGF0IGFwcGVhciBiZXR3ZWVuIHRoZSB0d28gYWRqYWNlbnQgZGl2LmNpcmNsZS1jbGlwcGVyIHdoaWxlIHRoZQogICAgICAgKiBzcGlubmVyIGlzIHJvdGF0aW5nIChhcHBlYXJzIG9uIENocm9tZSA1MCwgU2FmYXJpIDkuMS4xLCBhbmQgRWRnZSkuCiAgICAgICAqLwogICAgICAuc3Bpbm5lci1sYXllcjo6YWZ0ZXIgewogICAgICAgIGNvbnRlbnQ6ICcnOwogICAgICAgIGxlZnQ6IDQ1JTsKICAgICAgICB3aWR0aDogMTAlOwogICAgICAgIGJvcmRlci10b3Atc3R5bGU6IHNvbGlkOwogICAgICB9CgogICAgICAuc3Bpbm5lci1sYXllcjo6YWZ0ZXIsCiAgICAgIC5jaXJjbGUtY2xpcHBlciAuY2lyY2xlIHsKICAgICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICB0b3A6IDA7CiAgICAgICAgYm9yZGVyLXdpZHRoOiB2YXIoLS1wYXBlci1zcGlubmVyLXN0cm9rZS13aWR0aCwgM3B4KTsKICAgICAgICBib3JkZXItcmFkaXVzOiA1MCU7CiAgICAgIH0KCiAgICAgIC5jaXJjbGUtY2xpcHBlciAuY2lyY2xlIHsKICAgICAgICBib3R0b206IDA7CiAgICAgICAgd2lkdGg6IDIwMCU7CiAgICAgICAgYm9yZGVyLXN0eWxlOiBzb2xpZDsKICAgICAgICBib3JkZXItYm90dG9tLWNvbG9yOiB0cmFuc3BhcmVudCAhaW1wb3J0YW50OwogICAgICB9CgogICAgICAuY2lyY2xlLWNsaXBwZXIubGVmdCAuY2lyY2xlIHsKICAgICAgICBsZWZ0OiAwOwogICAgICAgIGJvcmRlci1yaWdodC1jb2xvcjogdHJhbnNwYXJlbnQgIWltcG9ydGFudDsKICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKDEyOWRlZyk7CiAgICAgICAgdHJhbnNmb3JtOiByb3RhdGUoMTI5ZGVnKTsKICAgICAgfQoKICAgICAgLmNpcmNsZS1jbGlwcGVyLnJpZ2h0IC5jaXJjbGUgewogICAgICAgIGxlZnQ6IC0xMDAlOwogICAgICAgIGJvcmRlci1sZWZ0LWNvbG9yOiB0cmFuc3BhcmVudCAhaW1wb3J0YW50OwogICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoLTEyOWRlZyk7CiAgICAgICAgdHJhbnNmb3JtOiByb3RhdGUoLTEyOWRlZyk7CiAgICAgIH0KCiAgICAgIC5hY3RpdmUgLmdhcC1wYXRjaDo6YWZ0ZXIsCiAgICAgIC5hY3RpdmUgLmNpcmNsZS1jbGlwcGVyIC5jaXJjbGUgewogICAgICAgIC13ZWJraXQtYW5pbWF0aW9uLWR1cmF0aW9uOiB2YXIoLS1wYXBlci1zcGlubmVyLWV4cGFuZC1jb250cmFjdC1kdXJhdGlvbik7CiAgICAgICAgLXdlYmtpdC1hbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoMC40LCAwLjAsIDAuMiwgMSk7CiAgICAgICAgLXdlYmtpdC1hbmltYXRpb24taXRlcmF0aW9uLWNvdW50OiBpbmZpbml0ZTsKICAgICAgICBhbmltYXRpb24tZHVyYXRpb246IHZhcigtLXBhcGVyLXNwaW5uZXItZXhwYW5kLWNvbnRyYWN0LWR1cmF0aW9uKTsKICAgICAgICBhbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoMC40LCAwLjAsIDAuMiwgMSk7CiAgICAgICAgYW5pbWF0aW9uLWl0ZXJhdGlvbi1jb3VudDogaW5maW5pdGU7CiAgICAgIH0KCiAgICAgIC5hY3RpdmUgLmNpcmNsZS1jbGlwcGVyLmxlZnQgLmNpcmNsZSB7CiAgICAgICAgLXdlYmtpdC1hbmltYXRpb24tbmFtZTogbGVmdC1zcGluOwogICAgICAgIGFuaW1hdGlvbi1uYW1lOiBsZWZ0LXNwaW47CiAgICAgIH0KCiAgICAgIC5hY3RpdmUgLmNpcmNsZS1jbGlwcGVyLnJpZ2h0IC5jaXJjbGUgewogICAgICAgIC13ZWJraXQtYW5pbWF0aW9uLW5hbWU6IHJpZ2h0LXNwaW47CiAgICAgICAgYW5pbWF0aW9uLW5hbWU6IHJpZ2h0LXNwaW47CiAgICAgIH0KCiAgICAgIEAtd2Via2l0LWtleWZyYW1lcyBsZWZ0LXNwaW4gewogICAgICAgIDAlIHsgLXdlYmtpdC10cmFuc2Zvcm06IHJvdGF0ZSgxMzBkZWcpIH0KICAgICAgICA1MCUgeyAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKC01ZGVnKSB9CiAgICAgICAgdG8geyAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKDEzMGRlZykgfQogICAgICB9CgogICAgICBAa2V5ZnJhbWVzIGxlZnQtc3BpbiB7CiAgICAgICAgMCUgeyB0cmFuc2Zvcm06IHJvdGF0ZSgxMzBkZWcpIH0KICAgICAgICA1MCUgeyB0cmFuc2Zvcm06IHJvdGF0ZSgtNWRlZykgfQogICAgICAgIHRvIHsgdHJhbnNmb3JtOiByb3RhdGUoMTMwZGVnKSB9CiAgICAgIH0KCiAgICAgIEAtd2Via2l0LWtleWZyYW1lcyByaWdodC1zcGluIHsKICAgICAgICAwJSB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoLTEzMGRlZykgfQogICAgICAgIDUwJSB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoNWRlZykgfQogICAgICAgIHRvIHsgLXdlYmtpdC10cmFuc2Zvcm06IHJvdGF0ZSgtMTMwZGVnKSB9CiAgICAgIH0KCiAgICAgIEBrZXlmcmFtZXMgcmlnaHQtc3BpbiB7CiAgICAgICAgMCUgeyB0cmFuc2Zvcm06IHJvdGF0ZSgtMTMwZGVnKSB9CiAgICAgICAgNTAlIHsgdHJhbnNmb3JtOiByb3RhdGUoNWRlZykgfQogICAgICAgIHRvIHsgdHJhbnNmb3JtOiByb3RhdGUoLTEzMGRlZykgfQogICAgICB9CgogICAgICAjc3Bpbm5lckNvbnRhaW5lci5jb29sZG93biB7CiAgICAgICAgLXdlYmtpdC1hbmltYXRpb246IGNvbnRhaW5lci1yb3RhdGUgdmFyKC0tcGFwZXItc3Bpbm5lci1jb250YWluZXItcm90YXRpb24tZHVyYXRpb24pIGxpbmVhciBpbmZpbml0ZSwgZmFkZS1vdXQgdmFyKC0tcGFwZXItc3Bpbm5lci1jb29sZG93bi1kdXJhdGlvbikgY3ViaWMtYmV6aWVyKDAuNCwgMC4wLCAwLjIsIDEpOwogICAgICAgIGFuaW1hdGlvbjogY29udGFpbmVyLXJvdGF0ZSB2YXIoLS1wYXBlci1zcGlubmVyLWNvbnRhaW5lci1yb3RhdGlvbi1kdXJhdGlvbikgbGluZWFyIGluZmluaXRlLCBmYWRlLW91dCB2YXIoLS1wYXBlci1zcGlubmVyLWNvb2xkb3duLWR1cmF0aW9uKSBjdWJpYy1iZXppZXIoMC40LCAwLjAsIDAuMiwgMSk7CiAgICAgIH0KCiAgICAgIEAtd2Via2l0LWtleWZyYW1lcyBmYWRlLW91dCB7CiAgICAgICAgMCUgeyBvcGFjaXR5OiAxIH0KICAgICAgICB0byB7IG9wYWNpdHk6IDAgfQogICAgICB9CgogICAgICBAa2V5ZnJhbWVzIGZhZGUtb3V0IHsKICAgICAgICAwJSB7IG9wYWNpdHk6IDEgfQogICAgICAgIHRvIHsgb3BhY2l0eTogMCB9CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC90ZW1wbGF0ZT4KPC9kb20tbW9kdWxlPmA7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChFVy5jb250ZW50KTt2YXIgTjk9e3Byb3BlcnRpZXM6e2FjdGl2ZTp7dHlwZTpCb29sZWFuLHZhbHVlOiExLHJlZmxlY3RUb0F0dHJpYnV0ZTohMCxvYnNlcnZlcjoiX19hY3RpdmVDaGFuZ2VkIn0sYWx0Ont0eXBlOlN0cmluZyx2YWx1ZToibG9hZGluZyIsb2JzZXJ2ZXI6Il9fYWx0Q2hhbmdlZCJ9LF9fY29vbGluZ0Rvd246e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX19LF9fY29tcHV0ZUNvbnRhaW5lckNsYXNzZXM6ZnVuY3Rpb24oZSx0KXtyZXR1cm5bZXx8dD8iYWN0aXZlIjoiIix0PyJjb29sZG93biI6IiJdLmpvaW4oIiAiKX0sX19hY3RpdmVDaGFuZ2VkOmZ1bmN0aW9uKGUsdCl7dGhpcy5fX3NldEFyaWFIaWRkZW4oIWUpLHRoaXMuX19jb29saW5nRG93bj0hZSYmdH0sX19hbHRDaGFuZ2VkOmZ1bmN0aW9uKGUpe2U9PT0ibG9hZGluZyI/dGhpcy5hbHQ9dGhpcy5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWwiKXx8ZToodGhpcy5fX3NldEFyaWFIaWRkZW4oZT09PSIiKSx0aGlzLnNldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIsZSkpfSxfX3NldEFyaWFIaWRkZW46ZnVuY3Rpb24oZSl7dmFyIHQ9ImFyaWEtaGlkZGVuIjtlP3RoaXMuc2V0QXR0cmlidXRlKHQsInRydWUiKTp0aGlzLnJlbW92ZUF0dHJpYnV0ZSh0KX0sX19yZXNldDpmdW5jdGlvbigpe3RoaXMuYWN0aXZlPSExLHRoaXMuX19jb29saW5nRG93bj0hMX19O3ZhciBQMHQ9UWAKICA8c3R5bGUgaW5jbHVkZT0icGFwZXItc3Bpbm5lci1zdHlsZXMiPjwvc3R5bGU+CgogIDxkaXYgaWQ9InNwaW5uZXJDb250YWluZXIiIGNsYXNzLW5hbWU9IltbX19jb21wdXRlQ29udGFpbmVyQ2xhc3NlcyhhY3RpdmUsIF9fY29vbGluZ0Rvd24pXV0iIG9uLWFuaW1hdGlvbmVuZD0iX19yZXNldCIgb24td2Via2l0LWFuaW1hdGlvbi1lbmQ9Il9fcmVzZXQiPgogICAgPGRpdiBjbGFzcz0ic3Bpbm5lci1sYXllciBsYXllci0xIj4KICAgICAgPGRpdiBjbGFzcz0iY2lyY2xlLWNsaXBwZXIgbGVmdCI+CiAgICAgICAgPGRpdiBjbGFzcz0iY2lyY2xlIj48L2Rpdj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImNpcmNsZS1jbGlwcGVyIHJpZ2h0Ij4KICAgICAgICA8ZGl2IGNsYXNzPSJjaXJjbGUiPjwvZGl2PgogICAgICA8L2Rpdj4KICAgIDwvZGl2PgoKICAgIDxkaXYgY2xhc3M9InNwaW5uZXItbGF5ZXIgbGF5ZXItMiI+CiAgICAgIDxkaXYgY2xhc3M9ImNpcmNsZS1jbGlwcGVyIGxlZnQiPgogICAgICAgIDxkaXYgY2xhc3M9ImNpcmNsZSI+PC9kaXY+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJjaXJjbGUtY2xpcHBlciByaWdodCI+CiAgICAgICAgPGRpdiBjbGFzcz0iY2lyY2xlIj48L2Rpdj4KICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KCiAgICA8ZGl2IGNsYXNzPSJzcGlubmVyLWxheWVyIGxheWVyLTMiPgogICAgICA8ZGl2IGNsYXNzPSJjaXJjbGUtY2xpcHBlciBsZWZ0Ij4KICAgICAgICA8ZGl2IGNsYXNzPSJjaXJjbGUiPjwvZGl2PgogICAgICA8L2Rpdj4KICAgICAgPGRpdiBjbGFzcz0iY2lyY2xlLWNsaXBwZXIgcmlnaHQiPgogICAgICAgIDxkaXYgY2xhc3M9ImNpcmNsZSI+PC9kaXY+CiAgICAgIDwvZGl2PgogICAgPC9kaXY+CgogICAgPGRpdiBjbGFzcz0ic3Bpbm5lci1sYXllciBsYXllci00Ij4KICAgICAgPGRpdiBjbGFzcz0iY2lyY2xlLWNsaXBwZXIgbGVmdCI+CiAgICAgICAgPGRpdiBjbGFzcz0iY2lyY2xlIj48L2Rpdj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImNpcmNsZS1jbGlwcGVyIHJpZ2h0Ij4KICAgICAgICA8ZGl2IGNsYXNzPSJjaXJjbGUiPjwvZGl2PgogICAgICA8L2Rpdj4KICAgIDwvZGl2PgogIDwvZGl2PgpgO1AwdC5zZXRBdHRyaWJ1dGUoInN0cmlwLXdoaXRlc3BhY2UiLCIiKTtZdCh7X3RlbXBsYXRlOlAwdCxpczoicGFwZXItc3Bpbm5lciIsYmVoYXZpb3JzOltOOV19KTt2YXIgSTB0PVFgCiAgPHN0eWxlIGluY2x1ZGU9InBhcGVyLXNwaW5uZXItc3R5bGVzIj48L3N0eWxlPgoKICA8ZGl2IGlkPSJzcGlubmVyQ29udGFpbmVyIiBjbGFzcy1uYW1lPSJbW19fY29tcHV0ZUNvbnRhaW5lckNsYXNzZXMoYWN0aXZlLCBfX2Nvb2xpbmdEb3duKV1dIiBvbi1hbmltYXRpb25lbmQ9Il9fcmVzZXQiIG9uLXdlYmtpdC1hbmltYXRpb24tZW5kPSJfX3Jlc2V0Ij4KICAgIDxkaXYgY2xhc3M9InNwaW5uZXItbGF5ZXIiPgogICAgICA8ZGl2IGNsYXNzPSJjaXJjbGUtY2xpcHBlciBsZWZ0Ij4KICAgICAgICA8ZGl2IGNsYXNzPSJjaXJjbGUiPjwvZGl2PgogICAgICA8L2Rpdj4KICAgICAgPGRpdiBjbGFzcz0iY2lyY2xlLWNsaXBwZXIgcmlnaHQiPgogICAgICAgIDxkaXYgY2xhc3M9ImNpcmNsZSI+PC9kaXY+CiAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgPC9kaXY+CmA7STB0LnNldEF0dHJpYnV0ZSgic3RyaXAtd2hpdGVzcGFjZSIsIiIpO1l0KHtfdGVtcGxhdGU6STB0LGlzOiJwYXBlci1zcGlubmVyLWxpdGUiLGJlaGF2aW9yczpbTjldfSk7dmFyIFViZT1RYDxpcm9uLWljb25zZXQtc3ZnIG5hbWU9InBhcGVyLXRhYnMiIHNpemU9IjI0Ij4KPHN2Zz48ZGVmcz4KPGcgaWQ9ImNoZXZyb24tbGVmdCI+PHBhdGggZD0iTTE1LjQxIDcuNDFMMTQgNmwtNiA2IDYgNiAxLjQxLTEuNDFMMTAuODMgMTJ6Ij48L3BhdGg+PC9nPgo8ZyBpZD0iY2hldnJvbi1yaWdodCI+PHBhdGggZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02eiI+PC9wYXRoPjwvZz4KPC9kZWZzPjwvc3ZnPgo8L2lyb24taWNvbnNldC1zdmc+YDtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKFViZS5jb250ZW50KTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtaW5saW5lOwogICAgICAgIEBhcHBseSAtLWxheW91dC1jZW50ZXI7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWNlbnRlci1qdXN0aWZpZWQ7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZsZXgtYXV0bzsKCiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIHBhZGRpbmc6IDAgMTJweDsKICAgICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgICAgICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlOwoKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwogICAgICAgIEBhcHBseSAtLXBhcGVyLXRhYjsKICAgICAgfQoKICAgICAgOmhvc3QoOmZvY3VzKSB7CiAgICAgICAgb3V0bGluZTogbm9uZTsKICAgICAgfQoKICAgICAgOmhvc3QoW2xpbmtdKSB7CiAgICAgICAgcGFkZGluZzogMDsKICAgICAgfQoKICAgICAgLnRhYi1jb250ZW50IHsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVaKDApOwogICAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHRyYW5zbGF0ZVooMCk7CiAgICAgICAgdHJhbnNpdGlvbjogb3BhY2l0eSAwLjFzIGN1YmljLWJlemllcigwLjQsIDAuMCwgMSwgMSk7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWhvcml6b250YWw7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWNlbnRlci1jZW50ZXI7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZsZXgtYXV0bzsKICAgICAgICBAYXBwbHkgLS1wYXBlci10YWItY29udGVudDsKICAgICAgfQoKICAgICAgOmhvc3QoOm5vdCguaXJvbi1zZWxlY3RlZCkpID4gLnRhYi1jb250ZW50IHsKICAgICAgICBvcGFjaXR5OiAwLjg7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLXRhYi1jb250ZW50LXVuc2VsZWN0ZWQ7CiAgICAgIH0KCiAgICAgIDpob3N0KDpmb2N1cykgLnRhYi1jb250ZW50IHsKICAgICAgICBvcGFjaXR5OiAxOwogICAgICAgIGZvbnQtd2VpZ2h0OiA3MDA7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLXRhYi1jb250ZW50LWZvY3VzZWQ7CiAgICAgIH0KCiAgICAgIHBhcGVyLXJpcHBsZSB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLXRhYi1pbmssIHZhcigtLXBhcGVyLXllbGxvdy1hMTAwKSk7CiAgICAgIH0KCiAgICAgIC50YWItY29udGVudCA+IDo6c2xvdHRlZChhKSB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZsZXgtYXV0bzsKCiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxkaXYgY2xhc3M9InRhYi1jb250ZW50Ij4KICAgICAgPHNsb3Q+PC9zbG90PgogICAgPC9kaXY+CmAsaXM6InBhcGVyLXRhYiIsYmVoYXZpb3JzOltEaSxTaCxzdV0scHJvcGVydGllczp7bGluazp7dHlwZTpCb29sZWFuLHZhbHVlOiExLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH19LGhvc3RBdHRyaWJ1dGVzOntyb2xlOiJ0YWIifSxsaXN0ZW5lcnM6e2Rvd246Il91cGRhdGVOb2luayIsdGFwOiJfb25UYXAifSxhdHRhY2hlZDpmdW5jdGlvbigpe3RoaXMuX3VwZGF0ZU5vaW5rKCl9LGdldCBfcGFyZW50Tm9pbmsoKXt2YXIgZT16dCh0aGlzKS5wYXJlbnROb2RlO3JldHVybiEhZSYmISFlLm5vaW5rfSxfdXBkYXRlTm9pbms6ZnVuY3Rpb24oKXt0aGlzLm5vaW5rPSEhdGhpcy5ub2lua3x8ISF0aGlzLl9wYXJlbnROb2lua30sX29uVGFwOmZ1bmN0aW9uKGUpe2lmKHRoaXMubGluayl7dmFyIHQ9dGhpcy5xdWVyeUVmZmVjdGl2ZUNoaWxkcmVuKCJhIik7aWYoIXR8fGUudGFyZ2V0PT09dClyZXR1cm47dC5jbGljaygpfX19KTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQ7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWNlbnRlcjsKCiAgICAgICAgaGVpZ2h0OiA0OHB4OwogICAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgICBmb250LXdlaWdodDogNTAwOwogICAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgICAgLW1vei11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICAtbXMtdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICB1c2VyLXNlbGVjdDogbm9uZTsKCiAgICAgICAgLyogTk9URTogQm90aCB2YWx1ZXMgYXJlIG5lZWRlZCwgc2luY2Ugc29tZSBwaG9uZXMgcmVxdWlyZSB0aGUgdmFsdWUgdG8gYmUgXGB0cmFuc3BhcmVudFxgLiAqLwogICAgICAgIC13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjogcmdiYSgwLCAwLCAwLCAwKTsKICAgICAgICAtd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6IHRyYW5zcGFyZW50OwoKICAgICAgICBAYXBwbHkgLS1wYXBlci10YWJzOwogICAgICB9CgogICAgICA6aG9zdCg6ZGlyKHJ0bCkpIHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtaG9yaXpvbnRhbC1yZXZlcnNlOwogICAgICB9CgogICAgICAjdGFic0NvbnRhaW5lciB7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZsZXgtYXV0bzsKICAgICAgICBAYXBwbHkgLS1wYXBlci10YWJzLWNvbnRhaW5lcjsKICAgICAgfQoKICAgICAgI3RhYnNDb250ZW50IHsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgLW1vei1mbGV4LWJhc2lzOiBhdXRvOwogICAgICAgIC1tcy1mbGV4LWJhc2lzOiBhdXRvOwogICAgICAgIGZsZXgtYmFzaXM6IGF1dG87CiAgICAgICAgQGFwcGx5IC0tcGFwZXItdGFicy1jb250ZW50OwogICAgICB9CgogICAgICAjdGFic0NvbnRlbnQuc2Nyb2xsYWJsZSB7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7CiAgICAgIH0KCiAgICAgICN0YWJzQ29udGVudDpub3QoLnNjcm9sbGFibGUpLAogICAgICAjdGFic0NvbnRlbnQuc2Nyb2xsYWJsZS5maXQtY29udGFpbmVyIHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtaG9yaXpvbnRhbDsKICAgICAgfQoKICAgICAgI3RhYnNDb250ZW50LnNjcm9sbGFibGUuZml0LWNvbnRhaW5lciB7CiAgICAgICAgbWluLXdpZHRoOiAxMDAlOwogICAgICB9CgogICAgICAjdGFic0NvbnRlbnQuc2Nyb2xsYWJsZS5maXQtY29udGFpbmVyID4gOjpzbG90dGVkKCopIHsKICAgICAgICAvKiBJRSAtIHByZXZlbnQgdGFicyBmcm9tIGNvbXByZXNzaW5nIHdoZW4gdGhleSBzaG91bGQgc2Nyb2xsLiAqLwogICAgICAgIC1tcy1mbGV4OiAxIDAgYXV0bzsKICAgICAgICAtd2Via2l0LWZsZXg6IDEgMCBhdXRvOwogICAgICAgIGZsZXg6IDEgMCBhdXRvOwogICAgICB9CgogICAgICAuaGlkZGVuIHsKICAgICAgICBkaXNwbGF5OiBub25lOwogICAgICB9CgogICAgICAubm90LXZpc2libGUgewogICAgICAgIG9wYWNpdHk6IDA7CiAgICAgICAgY3Vyc29yOiBkZWZhdWx0OwogICAgICB9CgogICAgICBwYXBlci1pY29uLWJ1dHRvbiB7CiAgICAgICAgd2lkdGg6IDQ4cHg7CiAgICAgICAgaGVpZ2h0OiA0OHB4OwogICAgICAgIHBhZGRpbmc6IDEycHg7CiAgICAgICAgbWFyZ2luOiAwIDRweDsKICAgICAgfQoKICAgICAgI3NlbGVjdGlvbkJhciB7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIGhlaWdodDogMDsKICAgICAgICBib3R0b206IDA7CiAgICAgICAgbGVmdDogMDsKICAgICAgICByaWdodDogMDsKICAgICAgICBib3JkZXItYm90dG9tOiAycHggc29saWQgdmFyKC0tcGFwZXItdGFicy1zZWxlY3Rpb24tYmFyLWNvbG9yLCB2YXIoLS1wYXBlci15ZWxsb3ctYTEwMCkpOwogICAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlKDApOwogICAgICAgIHRyYW5zZm9ybTogc2NhbGUoMCk7CiAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybS1vcmlnaW46IGxlZnQgY2VudGVyOwogICAgICAgIHRyYW5zZm9ybS1vcmlnaW46IGxlZnQgY2VudGVyOwogICAgICAgICAgdHJhbnNpdGlvbjogLXdlYmtpdC10cmFuc2Zvcm07CiAgICAgICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtOwoKICAgICAgICBAYXBwbHkgLS1wYXBlci10YWJzLXNlbGVjdGlvbi1iYXI7CiAgICAgIH0KCiAgICAgICNzZWxlY3Rpb25CYXIuYWxpZ24tYm90dG9tIHsKICAgICAgICB0b3A6IDA7CiAgICAgICAgYm90dG9tOiBhdXRvOwogICAgICB9CgogICAgICAjc2VsZWN0aW9uQmFyLmV4cGFuZCB7CiAgICAgICAgdHJhbnNpdGlvbi1kdXJhdGlvbjogMC4xNXM7CiAgICAgICAgdHJhbnNpdGlvbi10aW1pbmctZnVuY3Rpb246IGN1YmljLWJlemllcigwLjQsIDAuMCwgMSwgMSk7CiAgICAgIH0KCiAgICAgICNzZWxlY3Rpb25CYXIuY29udHJhY3QgewogICAgICAgIHRyYW5zaXRpb24tZHVyYXRpb246IDAuMThzOwogICAgICAgIHRyYW5zaXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoMC4wLCAwLjAsIDAuMiwgMSk7CiAgICAgIH0KCiAgICAgICN0YWJzQ29udGVudCA+IDo6c2xvdHRlZCg6bm90KCNzZWxlY3Rpb25CYXIpKSB7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxwYXBlci1pY29uLWJ1dHRvbiBpY29uPSJwYXBlci10YWJzOmNoZXZyb24tbGVmdCIgY2xhc3MkPSJbW19jb21wdXRlU2Nyb2xsQnV0dG9uQ2xhc3MoX2xlZnRIaWRkZW4sIHNjcm9sbGFibGUsIGhpZGVTY3JvbGxCdXR0b25zKV1dIiBvbi11cD0iX29uU2Nyb2xsQnV0dG9uVXAiIG9uLWRvd249Il9vbkxlZnRTY3JvbGxCdXR0b25Eb3duIiB0YWJpbmRleD0iLTEiPjwvcGFwZXItaWNvbi1idXR0b24+CgogICAgPGRpdiBpZD0idGFic0NvbnRhaW5lciIgb24tdHJhY2s9Il9zY3JvbGwiIG9uLWRvd249Il9kb3duIj4KICAgICAgPGRpdiBpZD0idGFic0NvbnRlbnQiIGNsYXNzJD0iW1tfY29tcHV0ZVRhYnNDb250ZW50Q2xhc3Moc2Nyb2xsYWJsZSwgZml0Q29udGFpbmVyKV1dIj4KICAgICAgICA8ZGl2IGlkPSJzZWxlY3Rpb25CYXIiIGNsYXNzJD0iW1tfY29tcHV0ZVNlbGVjdGlvbkJhckNsYXNzKG5vQmFyLCBhbGlnbkJvdHRvbSldXSIgb24tdHJhbnNpdGlvbmVuZD0iX29uQmFyVHJhbnNpdGlvbkVuZCI+PC9kaXY+CiAgICAgICAgPHNsb3Q+PC9zbG90PgogICAgICA8L2Rpdj4KICAgIDwvZGl2PgoKICAgIDxwYXBlci1pY29uLWJ1dHRvbiBpY29uPSJwYXBlci10YWJzOmNoZXZyb24tcmlnaHQiIGNsYXNzJD0iW1tfY29tcHV0ZVNjcm9sbEJ1dHRvbkNsYXNzKF9yaWdodEhpZGRlbiwgc2Nyb2xsYWJsZSwgaGlkZVNjcm9sbEJ1dHRvbnMpXV0iIG9uLXVwPSJfb25TY3JvbGxCdXR0b25VcCIgb24tZG93bj0iX29uUmlnaHRTY3JvbGxCdXR0b25Eb3duIiB0YWJpbmRleD0iLTEiPjwvcGFwZXItaWNvbi1idXR0b24+CmAsaXM6InBhcGVyLXRhYnMiLGJlaGF2aW9yczpbSnMsUjldLHByb3BlcnRpZXM6e25vaW5rOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITEsb2JzZXJ2ZXI6Il9ub2lua0NoYW5nZWQifSxub0Jhcjp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxub1NsaWRlOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LHNjcm9sbGFibGU6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sZml0Q29udGFpbmVyOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LGRpc2FibGVEcmFnOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LGhpZGVTY3JvbGxCdXR0b25zOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LGFsaWduQm90dG9tOnt0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LHNlbGVjdGFibGU6e3R5cGU6U3RyaW5nLHZhbHVlOiJwYXBlci10YWIifSxhdXRvc2VsZWN0Ont0eXBlOkJvb2xlYW4sdmFsdWU6ITF9LGF1dG9zZWxlY3REZWxheTp7dHlwZTpOdW1iZXIsdmFsdWU6MH0sX3N0ZXA6e3R5cGU6TnVtYmVyLHZhbHVlOjEwfSxfaG9sZERlbGF5Ont0eXBlOk51bWJlcix2YWx1ZToxfSxfbGVmdEhpZGRlbjp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxfcmlnaHRIaWRkZW46e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sX3ByZXZpb3VzVGFiOnt0eXBlOk9iamVjdH19LGhvc3RBdHRyaWJ1dGVzOntyb2xlOiJ0YWJsaXN0In0sbGlzdGVuZXJzOnsiaXJvbi1yZXNpemUiOiJfb25UYWJTaXppbmdDaGFuZ2VkIiwiaXJvbi1pdGVtcy1jaGFuZ2VkIjoiX29uVGFiU2l6aW5nQ2hhbmdlZCIsImlyb24tc2VsZWN0IjoiX29uSXJvblNlbGVjdCIsImlyb24tZGVzZWxlY3QiOiJfb25Jcm9uRGVzZWxlY3QifSxrZXlCaW5kaW5nczp7ImxlZnQ6a2V5dXAgcmlnaHQ6a2V5dXAiOiJfb25BcnJvd0tleXVwIn0sY3JlYXRlZDpmdW5jdGlvbigpe3RoaXMuX2hvbGRKb2I9bnVsbCx0aGlzLl9wZW5kaW5nQWN0aXZhdGlvbkl0ZW09dm9pZCAwLHRoaXMuX3BlbmRpbmdBY3RpdmF0aW9uVGltZW91dD12b2lkIDAsdGhpcy5fYmluZERlbGF5ZWRBY3RpdmF0aW9uSGFuZGxlcj10aGlzLl9kZWxheWVkQWN0aXZhdGlvbkhhbmRsZXIuYmluZCh0aGlzKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImJsdXIiLHRoaXMuX29uQmx1ckNhcHR1cmUuYmluZCh0aGlzKSwhMCl9LHJlYWR5OmZ1bmN0aW9uKCl7dGhpcy5zZXRTY3JvbGxEaXJlY3Rpb24oInkiLHRoaXMuJC50YWJzQ29udGFpbmVyKX0sZGV0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLl9jYW5jZWxQZW5kaW5nQWN0aXZhdGlvbigpfSxfbm9pbmtDaGFuZ2VkOmZ1bmN0aW9uKGUpe3ZhciB0PXp0KHRoaXMpLnF1ZXJ5U2VsZWN0b3JBbGwoInBhcGVyLXRhYiIpO3QuZm9yRWFjaChlP3RoaXMuX3NldE5vaW5rQXR0cmlidXRlOnRoaXMuX3JlbW92ZU5vaW5rQXR0cmlidXRlKX0sX3NldE5vaW5rQXR0cmlidXRlOmZ1bmN0aW9uKGUpe2Uuc2V0QXR0cmlidXRlKCJub2luayIsIiIpfSxfcmVtb3ZlTm9pbmtBdHRyaWJ1dGU6ZnVuY3Rpb24oZSl7ZS5yZW1vdmVBdHRyaWJ1dGUoIm5vaW5rIil9LF9jb21wdXRlU2Nyb2xsQnV0dG9uQ2xhc3M6ZnVuY3Rpb24oZSx0LHIpe3JldHVybiF0fHxyPyJoaWRkZW4iOmU/Im5vdC12aXNpYmxlIjoiIn0sX2NvbXB1dGVUYWJzQ29udGVudENsYXNzOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIGU/InNjcm9sbGFibGUiKyh0PyIgZml0LWNvbnRhaW5lciI6IiIpOiIgZml0LWNvbnRhaW5lciJ9LF9jb21wdXRlU2VsZWN0aW9uQmFyQ2xhc3M6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gZT8iaGlkZGVuIjp0PyJhbGlnbi1ib3R0b20iOiIifSxfb25UYWJTaXppbmdDaGFuZ2VkOmZ1bmN0aW9uKCl7dGhpcy5kZWJvdW5jZSgiX29uVGFiU2l6aW5nQ2hhbmdlZCIsZnVuY3Rpb24oKXt0aGlzLl9zY3JvbGwoKSx0aGlzLl90YWJDaGFuZ2VkKHRoaXMuc2VsZWN0ZWRJdGVtKX0sMTApfSxfb25Jcm9uU2VsZWN0OmZ1bmN0aW9uKGUpe3RoaXMuX3RhYkNoYW5nZWQoZS5kZXRhaWwuaXRlbSx0aGlzLl9wcmV2aW91c1RhYiksdGhpcy5fcHJldmlvdXNUYWI9ZS5kZXRhaWwuaXRlbSx0aGlzLmNhbmNlbERlYm91bmNlcigidGFiLWNoYW5nZWQiKX0sX29uSXJvbkRlc2VsZWN0OmZ1bmN0aW9uKGUpe3RoaXMuZGVib3VuY2UoInRhYi1jaGFuZ2VkIixmdW5jdGlvbigpe3RoaXMuX3RhYkNoYW5nZWQobnVsbCx0aGlzLl9wcmV2aW91c1RhYiksdGhpcy5fcHJldmlvdXNUYWI9bnVsbH0sMSl9LF9hY3RpdmF0ZUhhbmRsZXI6ZnVuY3Rpb24oKXt0aGlzLl9jYW5jZWxQZW5kaW5nQWN0aXZhdGlvbigpLF9fLl9hY3RpdmF0ZUhhbmRsZXIuYXBwbHkodGhpcyxhcmd1bWVudHMpfSxfc2NoZWR1bGVBY3RpdmF0aW9uOmZ1bmN0aW9uKGUsdCl7dGhpcy5fcGVuZGluZ0FjdGl2YXRpb25JdGVtPWUsdGhpcy5fcGVuZGluZ0FjdGl2YXRpb25UaW1lb3V0PXRoaXMuYXN5bmModGhpcy5fYmluZERlbGF5ZWRBY3RpdmF0aW9uSGFuZGxlcix0KX0sX2RlbGF5ZWRBY3RpdmF0aW9uSGFuZGxlcjpmdW5jdGlvbigpe3ZhciBlPXRoaXMuX3BlbmRpbmdBY3RpdmF0aW9uSXRlbTt0aGlzLl9wZW5kaW5nQWN0aXZhdGlvbkl0ZW09dm9pZCAwLHRoaXMuX3BlbmRpbmdBY3RpdmF0aW9uVGltZW91dD12b2lkIDAsZS5maXJlKHRoaXMuYWN0aXZhdGVFdmVudCxudWxsLHtidWJibGVzOiEwLGNhbmNlbGFibGU6ITB9KX0sX2NhbmNlbFBlbmRpbmdBY3RpdmF0aW9uOmZ1bmN0aW9uKCl7dGhpcy5fcGVuZGluZ0FjdGl2YXRpb25UaW1lb3V0IT09dm9pZCAwJiYodGhpcy5jYW5jZWxBc3luYyh0aGlzLl9wZW5kaW5nQWN0aXZhdGlvblRpbWVvdXQpLHRoaXMuX3BlbmRpbmdBY3RpdmF0aW9uSXRlbT12b2lkIDAsdGhpcy5fcGVuZGluZ0FjdGl2YXRpb25UaW1lb3V0PXZvaWQgMCl9LF9vbkFycm93S2V5dXA6ZnVuY3Rpb24oZSl7dGhpcy5hdXRvc2VsZWN0JiZ0aGlzLl9zY2hlZHVsZUFjdGl2YXRpb24odGhpcy5mb2N1c2VkSXRlbSx0aGlzLmF1dG9zZWxlY3REZWxheSl9LF9vbkJsdXJDYXB0dXJlOmZ1bmN0aW9uKGUpe2UudGFyZ2V0PT09dGhpcy5fcGVuZGluZ0FjdGl2YXRpb25JdGVtJiZ0aGlzLl9jYW5jZWxQZW5kaW5nQWN0aXZhdGlvbigpfSxnZXQgX3RhYkNvbnRhaW5lclNjcm9sbFNpemUoKXtyZXR1cm4gTWF0aC5tYXgoMCx0aGlzLiQudGFic0NvbnRhaW5lci5zY3JvbGxXaWR0aC10aGlzLiQudGFic0NvbnRhaW5lci5vZmZzZXRXaWR0aCl9LF9zY3JvbGw6ZnVuY3Rpb24oZSx0KXtpZighIXRoaXMuc2Nyb2xsYWJsZSl7dmFyIHI9dCYmLXQuZGR4fHwwO3RoaXMuX2FmZmVjdFNjcm9sbChyKX19LF9kb3duOmZ1bmN0aW9uKGUpe3RoaXMuYXN5bmMoZnVuY3Rpb24oKXt0aGlzLl9kZWZhdWx0Rm9jdXNBc3luYyYmKHRoaXMuY2FuY2VsQXN5bmModGhpcy5fZGVmYXVsdEZvY3VzQXN5bmMpLHRoaXMuX2RlZmF1bHRGb2N1c0FzeW5jPW51bGwpfSwxKX0sX2FmZmVjdFNjcm9sbDpmdW5jdGlvbihlKXt0aGlzLiQudGFic0NvbnRhaW5lci5zY3JvbGxMZWZ0Kz1lO3ZhciB0PXRoaXMuJC50YWJzQ29udGFpbmVyLnNjcm9sbExlZnQ7dGhpcy5fbGVmdEhpZGRlbj10PT09MCx0aGlzLl9yaWdodEhpZGRlbj10PT09dGhpcy5fdGFiQ29udGFpbmVyU2Nyb2xsU2l6ZX0sX29uTGVmdFNjcm9sbEJ1dHRvbkRvd246ZnVuY3Rpb24oKXt0aGlzLl9zY3JvbGxUb0xlZnQoKSx0aGlzLl9ob2xkSm9iPXNldEludGVydmFsKHRoaXMuX3Njcm9sbFRvTGVmdC5iaW5kKHRoaXMpLHRoaXMuX2hvbGREZWxheSl9LF9vblJpZ2h0U2Nyb2xsQnV0dG9uRG93bjpmdW5jdGlvbigpe3RoaXMuX3Njcm9sbFRvUmlnaHQoKSx0aGlzLl9ob2xkSm9iPXNldEludGVydmFsKHRoaXMuX3Njcm9sbFRvUmlnaHQuYmluZCh0aGlzKSx0aGlzLl9ob2xkRGVsYXkpfSxfb25TY3JvbGxCdXR0b25VcDpmdW5jdGlvbigpe2NsZWFySW50ZXJ2YWwodGhpcy5faG9sZEpvYiksdGhpcy5faG9sZEpvYj1udWxsfSxfc2Nyb2xsVG9MZWZ0OmZ1bmN0aW9uKCl7dGhpcy5fYWZmZWN0U2Nyb2xsKC10aGlzLl9zdGVwKX0sX3Njcm9sbFRvUmlnaHQ6ZnVuY3Rpb24oKXt0aGlzLl9hZmZlY3RTY3JvbGwodGhpcy5fc3RlcCl9LF90YWJDaGFuZ2VkOmZ1bmN0aW9uKGUsdCl7aWYoIWUpe3RoaXMuJC5zZWxlY3Rpb25CYXIuY2xhc3NMaXN0LnJlbW92ZSgiZXhwYW5kIiksdGhpcy4kLnNlbGVjdGlvbkJhci5jbGFzc0xpc3QucmVtb3ZlKCJjb250cmFjdCIpLHRoaXMuX3Bvc2l0aW9uQmFyKDAsMCk7cmV0dXJufXZhciByPXRoaXMuJC50YWJzQ29udGVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxuPXIud2lkdGgsaT1lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLG89aS5sZWZ0LXIubGVmdDtpZih0aGlzLl9wb3M9e3dpZHRoOnRoaXMuX2NhbGNQZXJjZW50KGkud2lkdGgsbiksbGVmdDp0aGlzLl9jYWxjUGVyY2VudChvLG4pfSx0aGlzLm5vU2xpZGV8fHQ9PW51bGwpe3RoaXMuJC5zZWxlY3Rpb25CYXIuY2xhc3NMaXN0LnJlbW92ZSgiZXhwYW5kIiksdGhpcy4kLnNlbGVjdGlvbkJhci5jbGFzc0xpc3QucmVtb3ZlKCJjb250cmFjdCIpLHRoaXMuX3Bvc2l0aW9uQmFyKHRoaXMuX3Bvcy53aWR0aCx0aGlzLl9wb3MubGVmdCk7cmV0dXJufXZhciBhPXQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkscz10aGlzLml0ZW1zLmluZGV4T2YodCksbD10aGlzLml0ZW1zLmluZGV4T2YoZSksYz01O3RoaXMuJC5zZWxlY3Rpb25CYXIuY2xhc3NMaXN0LmFkZCgiZXhwYW5kIik7dmFyIHU9czxsLGg9dGhpcy5faXNSVEw7aCYmKHU9IXUpLHU/dGhpcy5fcG9zaXRpb25CYXIodGhpcy5fY2FsY1BlcmNlbnQoaS5sZWZ0K2kud2lkdGgtYS5sZWZ0LG4pLWMsdGhpcy5fbGVmdCk6dGhpcy5fcG9zaXRpb25CYXIodGhpcy5fY2FsY1BlcmNlbnQoYS5sZWZ0K2Eud2lkdGgtaS5sZWZ0LG4pLWMsdGhpcy5fY2FsY1BlcmNlbnQobyxuKStjKSx0aGlzLnNjcm9sbGFibGUmJnRoaXMuX3Njcm9sbFRvU2VsZWN0ZWRJZk5lZWRlZChpLndpZHRoLG8pfSxfc2Nyb2xsVG9TZWxlY3RlZElmTmVlZGVkOmZ1bmN0aW9uKGUsdCl7dmFyIHI9dC10aGlzLiQudGFic0NvbnRhaW5lci5zY3JvbGxMZWZ0O3I8MD90aGlzLiQudGFic0NvbnRhaW5lci5zY3JvbGxMZWZ0Kz1yOihyKz1lLXRoaXMuJC50YWJzQ29udGFpbmVyLm9mZnNldFdpZHRoLHI+MCYmKHRoaXMuJC50YWJzQ29udGFpbmVyLnNjcm9sbExlZnQrPXIpKX0sX2NhbGNQZXJjZW50OmZ1bmN0aW9uKGUsdCl7cmV0dXJuIDEwMCplL3R9LF9wb3NpdGlvbkJhcjpmdW5jdGlvbihlLHQpe2U9ZXx8MCx0PXR8fDAsdGhpcy5fd2lkdGg9ZSx0aGlzLl9sZWZ0PXQsdGhpcy50cmFuc2Zvcm0oInRyYW5zbGF0ZVgoIit0KyIlKSBzY2FsZVgoIitlLzEwMCsiKSIsdGhpcy4kLnNlbGVjdGlvbkJhcil9LF9vbkJhclRyYW5zaXRpb25FbmQ6ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy4kLnNlbGVjdGlvbkJhci5jbGFzc0xpc3Q7dC5jb250YWlucygiZXhwYW5kIik/KHQucmVtb3ZlKCJleHBhbmQiKSx0LmFkZCgiY29udHJhY3QiKSx0aGlzLl9wb3NpdGlvbkJhcih0aGlzLl9wb3Mud2lkdGgsdGhpcy5fcG9zLmxlZnQpKTp0LmNvbnRhaW5zKCJjb250cmFjdCIpJiZ0LnJlbW92ZSgiY29udHJhY3QiKX19KTt2YXIgSng9bnVsbDtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBwb3NpdGlvbjogZml4ZWQ7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItdG9hc3QtYmFja2dyb3VuZC1jb2xvciwgIzMyMzIzMik7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLXRvYXN0LWNvbG9yLCAjZjFmMWYxKTsKICAgICAgICBtaW4taGVpZ2h0OiA0OHB4OwogICAgICAgIG1pbi13aWR0aDogMjg4cHg7CiAgICAgICAgcGFkZGluZzogMTZweCAyNHB4OwogICAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgICAgYm94LXNoYWRvdzogMCAycHggNXB4IDAgcmdiYSgwLCAwLCAwLCAwLjI2KTsKICAgICAgICBib3JkZXItcmFkaXVzOiAycHg7CiAgICAgICAgbWFyZ2luOiAxMnB4OwogICAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgICBjdXJzb3I6IGRlZmF1bHQ7CiAgICAgICAgLXdlYmtpdC10cmFuc2l0aW9uOiAtd2Via2l0LXRyYW5zZm9ybSAwLjNzLCBvcGFjaXR5IDAuM3M7CiAgICAgICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDAuM3MsIG9wYWNpdHkgMC4zczsKICAgICAgICBvcGFjaXR5OiAwOwogICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDEwMHB4KTsKICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMTAwcHgpOwogICAgICAgIEBhcHBseSAtLXBhcGVyLWZvbnQtY29tbW9uLWJhc2U7CiAgICAgIH0KCiAgICAgIDpob3N0KC5jYXBzdWxlKSB7CiAgICAgICAgYm9yZGVyLXJhZGl1czogMjRweDsKICAgICAgfQoKICAgICAgOmhvc3QoLmZpdC1ib3R0b20pIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBtaW4td2lkdGg6IDA7CiAgICAgICAgYm9yZGVyLXJhZGl1czogMDsKICAgICAgICBtYXJnaW46IDA7CiAgICAgIH0KCiAgICAgIDpob3N0KC5wYXBlci10b2FzdC1vcGVuKSB7CiAgICAgICAgb3BhY2l0eTogMTsKICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogdHJhbnNsYXRlWSgwcHgpOwogICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgwcHgpOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxzcGFuIGlkPSJsYWJlbCI+e3t0ZXh0fX08L3NwYW4+CiAgICA8c2xvdD48L3Nsb3Q+CmAsaXM6InBhcGVyLXRvYXN0IixiZWhhdmlvcnM6W0t4XSxwcm9wZXJ0aWVzOntmaXRJbnRvOnt0eXBlOk9iamVjdCx2YWx1ZTp3aW5kb3csb2JzZXJ2ZXI6Il9vbkZpdEludG9DaGFuZ2VkIn0saG9yaXpvbnRhbEFsaWduOnt0eXBlOlN0cmluZyx2YWx1ZToibGVmdCJ9LHZlcnRpY2FsQWxpZ246e3R5cGU6U3RyaW5nLHZhbHVlOiJib3R0b20ifSxkdXJhdGlvbjp7dHlwZTpOdW1iZXIsdmFsdWU6M2UzfSx0ZXh0Ont0eXBlOlN0cmluZyx2YWx1ZToiIn0sbm9DYW5jZWxPbk91dHNpZGVDbGljazp7dHlwZTpCb29sZWFuLHZhbHVlOiEwfSxub0F1dG9Gb2N1czp7dHlwZTpCb29sZWFuLHZhbHVlOiEwfX0sbGlzdGVuZXJzOnt0cmFuc2l0aW9uZW5kOiJfX29uVHJhbnNpdGlvbkVuZCJ9LGdldCB2aXNpYmxlKCl7cmV0dXJuIERhLl93YXJuKCJgdmlzaWJsZWAgaXMgZGVwcmVjYXRlZCwgdXNlIGBvcGVuZWRgIGluc3RlYWQiKSx0aGlzLm9wZW5lZH0sZ2V0IF9jYW5BdXRvQ2xvc2UoKXtyZXR1cm4gdGhpcy5kdXJhdGlvbj4wJiZ0aGlzLmR1cmF0aW9uIT09MS8wfSxjcmVhdGVkOmZ1bmN0aW9uKCl7dGhpcy5fYXV0b0Nsb3NlPW51bGwsZWMucmVxdWVzdEF2YWlsYWJpbGl0eSgpfSxzaG93OmZ1bmN0aW9uKGUpe3R5cGVvZiBlPT0ic3RyaW5nIiYmKGU9e3RleHQ6ZX0pO2Zvcih2YXIgdCBpbiBlKXQuaW5kZXhPZigiXyIpPT09MD9EYS5fd2FybignVGhlIHByb3BlcnR5ICInK3QrJyIgaXMgcHJpdmF0ZSBhbmQgd2FzIG5vdCBzZXQuJyk6dCBpbiB0aGlzP3RoaXNbdF09ZVt0XTpEYS5fd2FybignVGhlIHByb3BlcnR5ICInK3QrJyIgaXMgbm90IHZhbGlkLicpO3RoaXMub3BlbigpfSxoaWRlOmZ1bmN0aW9uKCl7dGhpcy5jbG9zZSgpfSxfX29uVHJhbnNpdGlvbkVuZDpmdW5jdGlvbihlKXtlJiZlLnRhcmdldD09PXRoaXMmJmUucHJvcGVydHlOYW1lPT09Im9wYWNpdHkiJiYodGhpcy5vcGVuZWQ/dGhpcy5fZmluaXNoUmVuZGVyT3BlbmVkKCk6dGhpcy5fZmluaXNoUmVuZGVyQ2xvc2VkKCkpfSxfb3BlbmVkQ2hhbmdlZDpmdW5jdGlvbigpe3RoaXMuX2F1dG9DbG9zZSE9PW51bGwmJih0aGlzLmNhbmNlbEFzeW5jKHRoaXMuX2F1dG9DbG9zZSksdGhpcy5fYXV0b0Nsb3NlPW51bGwpLHRoaXMub3BlbmVkPyhKeCYmSnghPT10aGlzJiZKeC5jbG9zZSgpLEp4PXRoaXMsdGhpcy5maXJlKCJpcm9uLWFubm91bmNlIix7dGV4dDp0aGlzLnRleHR9KSx0aGlzLl9jYW5BdXRvQ2xvc2UmJih0aGlzLl9hdXRvQ2xvc2U9dGhpcy5hc3luYyh0aGlzLmNsb3NlLHRoaXMuZHVyYXRpb24pKSk6Sng9PT10aGlzJiYoSng9bnVsbCksUG0uX29wZW5lZENoYW5nZWQuYXBwbHkodGhpcyxhcmd1bWVudHMpfSxfcmVuZGVyT3BlbmVkOmZ1bmN0aW9uKCl7dGhpcy5jbGFzc0xpc3QuYWRkKCJwYXBlci10b2FzdC1vcGVuIil9LF9yZW5kZXJDbG9zZWQ6ZnVuY3Rpb24oKXt0aGlzLmNsYXNzTGlzdC5yZW1vdmUoInBhcGVyLXRvYXN0LW9wZW4iKX0sX29uRml0SW50b0NoYW5nZWQ6ZnVuY3Rpb24oZSl7dGhpcy5wb3NpdGlvblRhcmdldD1lfX0pO3ZhciBMMHQ9UWAKCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWhvcml6b250YWw7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWNlbnRlcjsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwogICAgICB9CgogICAgICA6aG9zdChbZGlzYWJsZWRdKSB7CiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgIH0KCiAgICAgIDpob3N0KDpmb2N1cykgewogICAgICAgIG91dGxpbmU6bm9uZTsKICAgICAgfQoKICAgICAgLnRvZ2dsZS1iYXIgewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgYm9yZGVyLXJhZGl1czogOHB4OwogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICAgIG9wYWNpdHk6IDAuNDsKICAgICAgICB0cmFuc2l0aW9uOiBiYWNrZ3JvdW5kLWNvbG9yIGxpbmVhciAuMDhzOwogICAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLXBhcGVyLXRvZ2dsZS1idXR0b24tdW5jaGVja2VkLWJhci1jb2xvciwgIzAwMDAwMCk7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLXRvZ2dsZS1idXR0b24tdW5jaGVja2VkLWJhcjsKICAgICAgfQoKICAgICAgLnRvZ2dsZS1idXR0b24gewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICB0b3A6IC0zcHg7CiAgICAgICAgbGVmdDogMDsKICAgICAgICBoZWlnaHQ6IDIwcHg7CiAgICAgICAgd2lkdGg6IDIwcHg7CiAgICAgICAgYm9yZGVyLXJhZGl1czogNTAlOwogICAgICAgIGJveC1zaGFkb3c6IDAgMXB4IDVweCAwIHJnYmEoMCwgMCwgMCwgMC42KTsKICAgICAgICB0cmFuc2l0aW9uOiAtd2Via2l0LXRyYW5zZm9ybSBsaW5lYXIgLjA4cywgYmFja2dyb3VuZC1jb2xvciBsaW5lYXIgLjA4czsKICAgICAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gbGluZWFyIC4wOHMsIGJhY2tncm91bmQtY29sb3IgbGluZWFyIC4wOHM7CiAgICAgICAgd2lsbC1jaGFuZ2U6IHRyYW5zZm9ybTsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1wYXBlci10b2dnbGUtYnV0dG9uLXVuY2hlY2tlZC1idXR0b24tY29sb3IsIHZhcigtLXBhcGVyLWdyZXktNTApKTsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItdG9nZ2xlLWJ1dHRvbi11bmNoZWNrZWQtYnV0dG9uOwogICAgICB9CgogICAgICAudG9nZ2xlLWJ1dHRvbi5kcmFnZ2luZyB7CiAgICAgICAgLXdlYmtpdC10cmFuc2l0aW9uOiBub25lOwogICAgICAgIHRyYW5zaXRpb246IG5vbmU7CiAgICAgIH0KCiAgICAgIDpob3N0KFtjaGVja2VkXTpub3QoW2Rpc2FibGVkXSkpIC50b2dnbGUtYmFyIHsKICAgICAgICBvcGFjaXR5OiAwLjU7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItdG9nZ2xlLWJ1dHRvbi1jaGVja2VkLWJhci1jb2xvciwgdmFyKC0tcHJpbWFyeS1jb2xvcikpOwoKICAgICAgICBAYXBwbHkgLS1wYXBlci10b2dnbGUtYnV0dG9uLWNoZWNrZWQtYmFyOwogICAgICB9CgogICAgICA6aG9zdChbZGlzYWJsZWRdKSAudG9nZ2xlLWJhciB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogIzAwMDsKICAgICAgICBvcGFjaXR5OiAwLjEyOwogICAgICB9CgogICAgICA6aG9zdChbY2hlY2tlZF0pIC50b2dnbGUtYnV0dG9uIHsKICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogdHJhbnNsYXRlKDE2cHgsIDApOwogICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKDE2cHgsIDApOwogICAgICB9CgogICAgICA6aG9zdChbY2hlY2tlZF06bm90KFtkaXNhYmxlZF0pKSAudG9nZ2xlLWJ1dHRvbiB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItdG9nZ2xlLWJ1dHRvbi1jaGVja2VkLWJ1dHRvbi1jb2xvciwgdmFyKC0tcHJpbWFyeS1jb2xvcikpOwoKICAgICAgICBAYXBwbHkgLS1wYXBlci10b2dnbGUtYnV0dG9uLWNoZWNrZWQtYnV0dG9uOwogICAgICB9CgogICAgICA6aG9zdChbZGlzYWJsZWRdKSAudG9nZ2xlLWJ1dHRvbiB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2JkYmRiZDsKICAgICAgICBvcGFjaXR5OiAxOwogICAgICB9CgogICAgICAudG9nZ2xlLWluayB7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIHRvcDogLTE0cHg7CiAgICAgICAgbGVmdDogLTE0cHg7CiAgICAgICAgcmlnaHQ6IGF1dG87CiAgICAgICAgYm90dG9tOiBhdXRvOwogICAgICAgIHdpZHRoOiA0OHB4OwogICAgICAgIGhlaWdodDogNDhweDsKICAgICAgICBvcGFjaXR5OiAwLjU7CiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLXRvZ2dsZS1idXR0b24tdW5jaGVja2VkLWluay1jb2xvciwgdmFyKC0tcHJpbWFyeS10ZXh0LWNvbG9yKSk7CgogICAgICAgIEBhcHBseSAtLXBhcGVyLXRvZ2dsZS1idXR0b24tdW5jaGVja2VkLWluazsKICAgICAgfQoKICAgICAgOmhvc3QoW2NoZWNrZWRdKSAudG9nZ2xlLWluayB7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLXRvZ2dsZS1idXR0b24tY2hlY2tlZC1pbmstY29sb3IsIHZhcigtLXByaW1hcnktY29sb3IpKTsKCiAgICAgICAgQGFwcGx5IC0tcGFwZXItdG9nZ2xlLWJ1dHRvbi1jaGVja2VkLWluazsKICAgICAgfQoKICAgICAgLnRvZ2dsZS1jb250YWluZXIgewogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgd2lkdGg6IDM2cHg7CiAgICAgICAgaGVpZ2h0OiAxNHB4OwogICAgICAgIC8qIFRoZSB0b2dnbGUgYnV0dG9uIGhhcyBhbiBhYnNvbHV0ZSBwb3NpdGlvbiBvZiAtM3B4OyBUaGUgZXh0cmEgMXB4CiAgICAgICAgLyogYWNjb3VudHMgZm9yIHRoZSB0b2dnbGUgYnV0dG9uIHNoYWRvdyBib3guICovCiAgICAgICAgbWFyZ2luOiA0cHggMXB4OwogICAgICB9CgogICAgICAudG9nZ2xlLWxhYmVsIHsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7CiAgICAgICAgcGFkZGluZy1sZWZ0OiB2YXIoLS1wYXBlci10b2dnbGUtYnV0dG9uLWxhYmVsLXNwYWNpbmcsIDhweCk7CiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLXRvZ2dsZS1idXR0b24tbGFiZWwtY29sb3IsIHZhcigtLXByaW1hcnktdGV4dC1jb2xvcikpOwogICAgICB9CgogICAgICAvKiBpbnZhbGlkIHN0YXRlICovCiAgICAgIDpob3N0KFtpbnZhbGlkXSkgLnRvZ2dsZS1iYXIgewogICAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLXBhcGVyLXRvZ2dsZS1idXR0b24taW52YWxpZC1iYXItY29sb3IsIHZhcigtLWVycm9yLWNvbG9yKSk7CiAgICAgIH0KCiAgICAgIDpob3N0KFtpbnZhbGlkXSkgLnRvZ2dsZS1idXR0b24gewogICAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLXBhcGVyLXRvZ2dsZS1idXR0b24taW52YWxpZC1idXR0b24tY29sb3IsIHZhcigtLWVycm9yLWNvbG9yKSk7CiAgICAgIH0KCiAgICAgIDpob3N0KFtpbnZhbGlkXSkgLnRvZ2dsZS1pbmsgewogICAgICAgIGNvbG9yOiB2YXIoLS1wYXBlci10b2dnbGUtYnV0dG9uLWludmFsaWQtaW5rLWNvbG9yLCB2YXIoLS1lcnJvci1jb2xvcikpOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxkaXYgY2xhc3M9InRvZ2dsZS1jb250YWluZXIiPgogICAgICA8ZGl2IGlkPSJ0b2dnbGVCYXIiIGNsYXNzPSJ0b2dnbGUtYmFyIj48L2Rpdj4KICAgICAgPGRpdiBpZD0idG9nZ2xlQnV0dG9uIiBjbGFzcz0idG9nZ2xlLWJ1dHRvbiI+PC9kaXY+CiAgICA8L2Rpdj4KCiAgICA8ZGl2IGNsYXNzPSJ0b2dnbGUtbGFiZWwiPjxzbG90Pjwvc2xvdD48L2Rpdj4KCiAgYDtMMHQuc2V0QXR0cmlidXRlKCJzdHJpcC13aGl0ZXNwYWNlIiwiIik7WXQoe190ZW1wbGF0ZTpMMHQsaXM6InBhcGVyLXRvZ2dsZS1idXR0b24iLGJlaGF2aW9yczpbWHhdLGhvc3RBdHRyaWJ1dGVzOntyb2xlOiJidXR0b24iLCJhcmlhLXByZXNzZWQiOiJmYWxzZSIsdGFiaW5kZXg6MH0scHJvcGVydGllczp7fSxsaXN0ZW5lcnM6e3RyYWNrOiJfb250cmFjayJ9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7VG0odGhpcyxmdW5jdGlvbigpe2RfKHRoaXMsInBhbi15Iil9KX0sX29udHJhY2s6ZnVuY3Rpb24oZSl7dmFyIHQ9ZS5kZXRhaWw7dC5zdGF0ZT09PSJzdGFydCI/dGhpcy5fdHJhY2tTdGFydCh0KTp0LnN0YXRlPT09InRyYWNrIj90aGlzLl90cmFja01vdmUodCk6dC5zdGF0ZT09PSJlbmQiJiZ0aGlzLl90cmFja0VuZCh0KX0sX3RyYWNrU3RhcnQ6ZnVuY3Rpb24oZSl7dGhpcy5fd2lkdGg9dGhpcy4kLnRvZ2dsZUJhci5vZmZzZXRXaWR0aC8yLHRoaXMuX3RyYWNrQ2hlY2tlZD10aGlzLmNoZWNrZWQsdGhpcy4kLnRvZ2dsZUJ1dHRvbi5jbGFzc0xpc3QuYWRkKCJkcmFnZ2luZyIpfSxfdHJhY2tNb3ZlOmZ1bmN0aW9uKGUpe3ZhciB0PWUuZHg7dGhpcy5feD1NYXRoLm1pbih0aGlzLl93aWR0aCxNYXRoLm1heCgwLHRoaXMuX3RyYWNrQ2hlY2tlZD90aGlzLl93aWR0aCt0OnQpKSx0aGlzLnRyYW5zbGF0ZTNkKHRoaXMuX3grInB4IiwwLDAsdGhpcy4kLnRvZ2dsZUJ1dHRvbiksdGhpcy5fdXNlckFjdGl2YXRlKHRoaXMuX3g+dGhpcy5fd2lkdGgvMil9LF90cmFja0VuZDpmdW5jdGlvbihlKXt0aGlzLiQudG9nZ2xlQnV0dG9uLmNsYXNzTGlzdC5yZW1vdmUoImRyYWdnaW5nIiksdGhpcy50cmFuc2Zvcm0oIiIsdGhpcy4kLnRvZ2dsZUJ1dHRvbil9LF9jcmVhdGVSaXBwbGU6ZnVuY3Rpb24oKXt0aGlzLl9yaXBwbGVDb250YWluZXI9dGhpcy4kLnRvZ2dsZUJ1dHRvbjt2YXIgZT1zdS5fY3JlYXRlUmlwcGxlKCk7cmV0dXJuIGUuaWQ9ImluayIsZS5zZXRBdHRyaWJ1dGUoInJlY2VudGVycyIsIiIpLGUuY2xhc3NMaXN0LmFkZCgiY2lyY2xlIiwidG9nZ2xlLWluayIpLGV9fSk7WXQoe190ZW1wbGF0ZTpRYAogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgLS1jYWxjdWxhdGVkLXBhcGVyLXRvb2xiYXItaGVpZ2h0OiB2YXIoLS1wYXBlci10b29sYmFyLWhlaWdodCwgNjRweCk7CiAgICAgICAgLS1jYWxjdWxhdGVkLXBhcGVyLXRvb2xiYXItc20taGVpZ2h0OiB2YXIoLS1wYXBlci10b29sYmFyLXNtLWhlaWdodCwgNTZweCk7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgICAgLW1vei1ib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICAgIGhlaWdodDogdmFyKC0tY2FsY3VsYXRlZC1wYXBlci10b29sYmFyLWhlaWdodCk7CiAgICAgICAgYmFja2dyb3VuZDogdmFyKC0tcGFwZXItdG9vbGJhci1iYWNrZ3JvdW5kLCB2YXIoLS1wcmltYXJ5LWNvbG9yKSk7CiAgICAgICAgY29sb3I6IHZhcigtLXBhcGVyLXRvb2xiYXItY29sb3IsIHZhcigtLWRhcmstdGhlbWUtdGV4dC1jb2xvcikpOwogICAgICAgIEBhcHBseSAtLXBhcGVyLXRvb2xiYXI7CiAgICAgIH0KCiAgICAgIDpob3N0KC5hbmltYXRlKSB7CiAgICAgICAgdHJhbnNpdGlvbjogdmFyKC0tcGFwZXItdG9vbGJhci10cmFuc2l0aW9uLCBoZWlnaHQgMC4xOHMgZWFzZS1pbik7CiAgICAgIH0KCiAgICAgIDpob3N0KC5tZWRpdW0tdGFsbCkgewogICAgICAgIGhlaWdodDogY2FsYyh2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXRvb2xiYXItaGVpZ2h0KSAqIDIpOwogICAgICAgIEBhcHBseSAtLXBhcGVyLXRvb2xiYXItbWVkaXVtOwogICAgICB9CgogICAgICA6aG9zdCgudGFsbCkgewogICAgICAgIGhlaWdodDogY2FsYyh2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXRvb2xiYXItaGVpZ2h0KSAqIDMpOwogICAgICAgIEBhcHBseSAtLXBhcGVyLXRvb2xiYXItdGFsbDsKICAgICAgfQoKICAgICAgLnRvb2xiYXItdG9vbHMgewogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICBoZWlnaHQ6IHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItdG9vbGJhci1oZWlnaHQpOwogICAgICAgIHBhZGRpbmc6IDAgMTZweDsKICAgICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtaG9yaXpvbnRhbDsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtY2VudGVyOwogICAgICAgIEBhcHBseSAtLXBhcGVyLXRvb2xiYXItY29udGVudDsKICAgICAgfQoKICAgICAgLyoKICAgICAgICogVE9ETzogV2hlcmUgc2hvdWxkIG1lZGlhIHF1ZXJ5IGJyZWFrcG9pbnRzIGxpdmUgc28gdGhleSBjYW4gYmUgc2hhcmVkIGJldHdlZW4gZWxlbWVudHM/CiAgICAgICAqLwoKICAgICAgQG1lZGlhIChtYXgtd2lkdGg6IDYwMHB4KSB7CiAgICAgICAgOmhvc3QgewogICAgICAgICAgaGVpZ2h0OiB2YXIoLS1jYWxjdWxhdGVkLXBhcGVyLXRvb2xiYXItc20taGVpZ2h0KTsKICAgICAgICB9CgogICAgICAgIDpob3N0KC5tZWRpdW0tdGFsbCkgewogICAgICAgICAgaGVpZ2h0OiBjYWxjKHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItdG9vbGJhci1zbS1oZWlnaHQpICogMik7CiAgICAgICAgfQoKICAgICAgICA6aG9zdCgudGFsbCkgewogICAgICAgICAgaGVpZ2h0OiBjYWxjKHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItdG9vbGJhci1zbS1oZWlnaHQpICogMyk7CiAgICAgICAgfQoKICAgICAgICAudG9vbGJhci10b29scyB7CiAgICAgICAgICBoZWlnaHQ6IHZhcigtLWNhbGN1bGF0ZWQtcGFwZXItdG9vbGJhci1zbS1oZWlnaHQpOwogICAgICAgIH0KICAgICAgfQoKICAgICAgI3RvcEJhciB7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICB9CgogICAgICAvKiBtaWRkbGUgYmFyICovCiAgICAgICNtaWRkbGVCYXIgewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICB0b3A6IDA7CiAgICAgICAgcmlnaHQ6IDA7CiAgICAgICAgbGVmdDogMDsKICAgICAgfQoKICAgICAgOmhvc3QoLnRhbGwpICNtaWRkbGVCYXIsCiAgICAgIDpob3N0KC5tZWRpdW0tdGFsbCkgI21pZGRsZUJhciB7CiAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMTAwJSk7CiAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDEwMCUpOwogICAgICB9CgogICAgICAvKiBib3R0b20gYmFyICovCiAgICAgICNib3R0b21CYXIgewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICByaWdodDogMDsKICAgICAgICBib3R0b206IDA7CiAgICAgICAgbGVmdDogMDsKICAgICAgfQoKICAgICAgLyoKICAgICAgICogbWFrZSBlbGVtZW50cyAoZS5nLiBidXR0b25zKSByZXNwb25kIHRvIG1vdXNlL3RvdWNoIGV2ZW50cwogICAgICAgKgogICAgICAgKiBcYC50b29sYmFyLXRvb2xzXGAgZGlzYWJsZXMgdG91Y2ggZXZlbnRzIHNvIG11bHRpcGxlIHRvb2xiYXJzIGNhbiBzdGFjayBhbmQgbm90CiAgICAgICAqIGFic29yYiBldmVudHMuIEFsbCBjaGlsZHJlbiBtdXN0IGhhdmUgcG9pbnRlciBldmVudHMgcmUtZW5hYmxlZCB0byB3b3JrIGFzCiAgICAgICAqIGV4cGVjdGVkLgogICAgICAgKi8KICAgICAgLnRvb2xiYXItdG9vbHMgPiA6OnNsb3R0ZWQoKjpub3QoW2Rpc2FibGVkXSkpIHsKICAgICAgICBwb2ludGVyLWV2ZW50czogYXV0bzsKICAgICAgfQoKICAgICAgLnRvb2xiYXItdG9vbHMgPiA6OnNsb3R0ZWQoLnRpdGxlKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItZm9udC1jb21tb24tYmFzZTsKICAgICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgICAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7CiAgICAgICAgZm9udC1zaXplOiAyMHB4OwogICAgICAgIGZvbnQtd2VpZ2h0OiA0MDA7CiAgICAgICAgbGluZS1oZWlnaHQ6IDE7CiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZsZXg7CiAgICAgIH0KCiAgICAgIC50b29sYmFyLXRvb2xzID4gOjpzbG90dGVkKC50aXRsZSkgewogICAgICAgIG1hcmdpbi1sZWZ0OiA1NnB4OwogICAgICB9CgogICAgICAudG9vbGJhci10b29scyA+IDo6c2xvdHRlZChwYXBlci1pY29uLWJ1dHRvbiArIC50aXRsZSkgewogICAgICAgIG1hcmdpbi1sZWZ0OiAwOwogICAgICB9CgogICAgICAvKioKICAgICAgICogVGhlIC0tcGFwZXItdG9vbGJhci10aXRsZSBtaXhpbiBpcyBhcHBsaWVkIGhlcmUgaW5zdGVhZCBvZiBhYm92ZSB0bwogICAgICAgKiBmaXggdGhlIGlzc3VlIHdpdGggbWFyZ2luLWxlZnQgYmVpbmcgaWdub3JlZCBkdWUgdG8gY3NzIG9yZGVyaW5nLgogICAgICAgKi8KICAgICAgLnRvb2xiYXItdG9vbHMgPiA6OnNsb3R0ZWQoLnRpdGxlKSB7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItdG9vbGJhci10aXRsZTsKICAgICAgfQoKICAgICAgLnRvb2xiYXItdG9vbHMgPiA6OnNsb3R0ZWQocGFwZXItaWNvbi1idXR0b25baWNvbj1tZW51XSkgewogICAgICAgIG1hcmdpbi1yaWdodDogMjRweDsKICAgICAgfQoKICAgICAgLnRvb2xiYXItdG9vbHMgPiA6OnNsb3R0ZWQoLmZpdCkgewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICB0b3A6IGF1dG87CiAgICAgICAgcmlnaHQ6IDA7CiAgICAgICAgYm90dG9tOiAwOwogICAgICAgIGxlZnQ6IDA7CiAgICAgICAgd2lkdGg6IGF1dG87CiAgICAgICAgbWFyZ2luOiAwOwogICAgICB9CgogICAgICAvKiBUT0RPKG5vbXMpOiBVbnRpbCB3ZSBoYXZlIGEgYmV0dGVyIHNvbHV0aW9uIGZvciBjbGFzc2VzIHRoYXQgZG9uJ3QgdXNlCiAgICAgICAqIC9kZWVwLyBjcmVhdGUgb3VyIG93bi4KICAgICAgICovCiAgICAgIC5zdGFydC1qdXN0aWZpZWQgewogICAgICAgIEBhcHBseSAtLWxheW91dC1zdGFydC1qdXN0aWZpZWQ7CiAgICAgIH0KCiAgICAgIC5jZW50ZXItanVzdGlmaWVkIHsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtY2VudGVyLWp1c3RpZmllZDsKICAgICAgfQoKICAgICAgLmVuZC1qdXN0aWZpZWQgewogICAgICAgIEBhcHBseSAtLWxheW91dC1lbmQtanVzdGlmaWVkOwogICAgICB9CgogICAgICAuYXJvdW5kLWp1c3RpZmllZCB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWFyb3VuZC1qdXN0aWZpZWQ7CiAgICAgIH0KCiAgICAgIC5qdXN0aWZpZWQgewogICAgICAgIEBhcHBseSAtLWxheW91dC1qdXN0aWZpZWQ7CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPGRpdiBpZD0idG9wQmFyIiBjbGFzc1wkPSJ0b29sYmFyLXRvb2xzIFtbX2NvbXB1dGVCYXJFeHRyYUNsYXNzZXMoanVzdGlmeSldXSI+CiAgICAgIDxzbG90IG5hbWU9InRvcCI+PC9zbG90PgogICAgPC9kaXY+CgogICAgPGRpdiBpZD0ibWlkZGxlQmFyIiBjbGFzc1wkPSJ0b29sYmFyLXRvb2xzIFtbX2NvbXB1dGVCYXJFeHRyYUNsYXNzZXMobWlkZGxlSnVzdGlmeSldXSI+CiAgICAgIDxzbG90IG5hbWU9Im1pZGRsZSI+PC9zbG90PgogICAgPC9kaXY+CgogICAgPGRpdiBpZD0iYm90dG9tQmFyIiBjbGFzc1wkPSJ0b29sYmFyLXRvb2xzIFtbX2NvbXB1dGVCYXJFeHRyYUNsYXNzZXMoYm90dG9tSnVzdGlmeSldXSI+CiAgICAgIDxzbG90IG5hbWU9ImJvdHRvbSI+PC9zbG90PgogICAgPC9kaXY+CmAsaXM6InBhcGVyLXRvb2xiYXIiLGhvc3RBdHRyaWJ1dGVzOntyb2xlOiJ0b29sYmFyIn0scHJvcGVydGllczp7Ym90dG9tSnVzdGlmeTp7dHlwZTpTdHJpbmcsdmFsdWU6IiJ9LGp1c3RpZnk6e3R5cGU6U3RyaW5nLHZhbHVlOiIifSxtaWRkbGVKdXN0aWZ5Ont0eXBlOlN0cmluZyx2YWx1ZToiIn19LHJlYWR5OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKHRoaXMuaXMsImlzIGRlcHJlY2F0ZWQuIFBsZWFzZSB1c2UgYXBwLWxheW91dCBpbnN0ZWFkISIpfSxhdHRhY2hlZDpmdW5jdGlvbigpe3RoaXMuX29ic2VydmVyPXRoaXMuX29ic2VydmUodGhpcyksdGhpcy5fdXBkYXRlQXJpYUxhYmVsbGVkQnkoKX0sZGV0YWNoZWQ6ZnVuY3Rpb24oKXt0aGlzLl9vYnNlcnZlciYmdGhpcy5fb2JzZXJ2ZXIuZGlzY29ubmVjdCgpfSxfb2JzZXJ2ZTpmdW5jdGlvbihlKXt2YXIgdD1uZXcgTXV0YXRpb25PYnNlcnZlcihmdW5jdGlvbigpe3RoaXMuX3VwZGF0ZUFyaWFMYWJlbGxlZEJ5KCl9LmJpbmQodGhpcykpO3JldHVybiB0Lm9ic2VydmUoZSx7Y2hpbGRMaXN0OiEwLHN1YnRyZWU6ITB9KSx0fSxfdXBkYXRlQXJpYUxhYmVsbGVkQnk6ZnVuY3Rpb24oKXt1aSgpO2Zvcih2YXIgZT1bXSx0PUFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKHp0KHRoaXMucm9vdCkucXVlcnlTZWxlY3RvckFsbCgic2xvdCIpKS5jb25jYXQoQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoenQodGhpcy5yb290KS5xdWVyeVNlbGVjdG9yQWxsKCJjb250ZW50IikpKSxyLG49MDtyPXRbbl07bisrKWZvcih2YXIgaT16dChyKS5nZXREaXN0cmlidXRlZE5vZGVzKCksbyxhPTA7bz1pW2FdO2ErKylpZihvLmNsYXNzTGlzdCYmby5jbGFzc0xpc3QuY29udGFpbnMoInRpdGxlIikpaWYoby5pZCllLnB1c2goby5pZCk7ZWxzZXt2YXIgcz0icGFwZXItdG9vbGJhci1sYWJlbC0iK01hdGguZmxvb3IoTWF0aC5yYW5kb20oKSoxZTQpO28uaWQ9cyxlLnB1c2gocyl9ZS5sZW5ndGg+MCYmdGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWxsZWRieSIsZS5qb2luKCIgIikpfSxfY29tcHV0ZUJhckV4dHJhQ2xhc3NlczpmdW5jdGlvbihlKXtyZXR1cm4gZT9lKyhlPT09Imp1c3RpZmllZCI/IiI6Ii1qdXN0aWZpZWQiKToiIn19KTtZdCh7X3RlbXBsYXRlOlFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgb3V0bGluZTogbm9uZTsKICAgICAgICB6LWluZGV4OiAxMDAyOwogICAgICAgIC1tb3otdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgICAgLW1zLXVzZXItc2VsZWN0OiBub25lOwogICAgICAgIC13ZWJraXQtdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgICAgdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgICAgY3Vyc29yOiBkZWZhdWx0OwogICAgICB9CgogICAgICAjdG9vbHRpcCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgb3V0bGluZTogbm9uZTsKICAgICAgICBAYXBwbHkgLS1wYXBlci1mb250LWNvbW1vbi1iYXNlOwogICAgICAgIGZvbnQtc2l6ZTogMTBweDsKICAgICAgICBsaW5lLWhlaWdodDogMTsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1wYXBlci10b29sdGlwLWJhY2tncm91bmQsICM2MTYxNjEpOwogICAgICAgIGNvbG9yOiB2YXIoLS1wYXBlci10b29sdGlwLXRleHQtY29sb3IsIHdoaXRlKTsKICAgICAgICBwYWRkaW5nOiA4cHg7CiAgICAgICAgYm9yZGVyLXJhZGl1czogMnB4OwogICAgICAgIEBhcHBseSAtLXBhcGVyLXRvb2x0aXA7CiAgICAgIH0KCiAgICAgIEBrZXlmcmFtZXMga2V5RnJhbWVTY2FsZVVwIHsKICAgICAgICAwJSB7CiAgICAgICAgICB0cmFuc2Zvcm06IHNjYWxlKDAuMCk7CiAgICAgICAgfQogICAgICAgIDEwMCUgewogICAgICAgICAgdHJhbnNmb3JtOiBzY2FsZSgxLjApOwogICAgICAgIH0KICAgICAgfQoKICAgICAgQGtleWZyYW1lcyBrZXlGcmFtZVNjYWxlRG93biB7CiAgICAgICAgMCUgewogICAgICAgICAgdHJhbnNmb3JtOiBzY2FsZSgxLjApOwogICAgICAgIH0KICAgICAgICAxMDAlIHsKICAgICAgICAgIHRyYW5zZm9ybTogc2NhbGUoMC4wKTsKICAgICAgICB9CiAgICAgIH0KCiAgICAgIEBrZXlmcmFtZXMga2V5RnJhbWVGYWRlSW5PcGFjaXR5IHsKICAgICAgICAwJSB7CiAgICAgICAgICBvcGFjaXR5OiAwOwogICAgICAgIH0KICAgICAgICAxMDAlIHsKICAgICAgICAgIG9wYWNpdHk6IHZhcigtLXBhcGVyLXRvb2x0aXAtb3BhY2l0eSwgMC45KTsKICAgICAgICB9CiAgICAgIH0KCiAgICAgIEBrZXlmcmFtZXMga2V5RnJhbWVGYWRlT3V0T3BhY2l0eSB7CiAgICAgICAgMCUgewogICAgICAgICAgb3BhY2l0eTogdmFyKC0tcGFwZXItdG9vbHRpcC1vcGFjaXR5LCAwLjkpOwogICAgICAgIH0KICAgICAgICAxMDAlIHsKICAgICAgICAgIG9wYWNpdHk6IDA7CiAgICAgICAgfQogICAgICB9CgogICAgICBAa2V5ZnJhbWVzIGtleUZyYW1lU2xpZGVEb3duSW4gewogICAgICAgIDAlIHsKICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtMjAwMHB4KTsKICAgICAgICAgIG9wYWNpdHk6IDA7CiAgICAgICAgfQogICAgICAgIDEwJSB7CiAgICAgICAgICBvcGFjaXR5OiAwLjI7CiAgICAgICAgfQogICAgICAgIDEwMCUgewogICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDApOwogICAgICAgICAgb3BhY2l0eTogdmFyKC0tcGFwZXItdG9vbHRpcC1vcGFjaXR5LCAwLjkpOwogICAgICAgIH0KICAgICAgfQoKICAgICAgQGtleWZyYW1lcyBrZXlGcmFtZVNsaWRlRG93bk91dCB7CiAgICAgICAgMCUgewogICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDApOwogICAgICAgICAgb3BhY2l0eTogdmFyKC0tcGFwZXItdG9vbHRpcC1vcGFjaXR5LCAwLjkpOwogICAgICAgIH0KICAgICAgICAxMCUgewogICAgICAgICAgb3BhY2l0eTogMC4yOwogICAgICAgIH0KICAgICAgICAxMDAlIHsKICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtMjAwMHB4KTsKICAgICAgICAgIG9wYWNpdHk6IDA7CiAgICAgICAgfQogICAgICB9CgogICAgICAuZmFkZS1pbi1hbmltYXRpb24gewogICAgICAgIG9wYWNpdHk6IDA7CiAgICAgICAgYW5pbWF0aW9uLWRlbGF5OiB2YXIoLS1wYXBlci10b29sdGlwLWRlbGF5LWluLCA1MDBtcyk7CiAgICAgICAgYW5pbWF0aW9uLW5hbWU6IGtleUZyYW1lRmFkZUluT3BhY2l0eTsKICAgICAgICBhbmltYXRpb24taXRlcmF0aW9uLWNvdW50OiAxOwogICAgICAgIGFuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246IGVhc2UtaW47CiAgICAgICAgYW5pbWF0aW9uLWR1cmF0aW9uOiB2YXIoLS1wYXBlci10b29sdGlwLWR1cmF0aW9uLWluLCA1MDBtcyk7CiAgICAgICAgYW5pbWF0aW9uLWZpbGwtbW9kZTogZm9yd2FyZHM7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItdG9vbHRpcC1hbmltYXRpb247CiAgICAgIH0KCiAgICAgIC5mYWRlLW91dC1hbmltYXRpb24gewogICAgICAgIG9wYWNpdHk6IHZhcigtLXBhcGVyLXRvb2x0aXAtb3BhY2l0eSwgMC45KTsKICAgICAgICBhbmltYXRpb24tZGVsYXk6IHZhcigtLXBhcGVyLXRvb2x0aXAtZGVsYXktb3V0LCAwbXMpOwogICAgICAgIGFuaW1hdGlvbi1uYW1lOiBrZXlGcmFtZUZhZGVPdXRPcGFjaXR5OwogICAgICAgIGFuaW1hdGlvbi1pdGVyYXRpb24tY291bnQ6IDE7CiAgICAgICAgYW5pbWF0aW9uLXRpbWluZy1mdW5jdGlvbjogZWFzZS1pbjsKICAgICAgICBhbmltYXRpb24tZHVyYXRpb246IHZhcigtLXBhcGVyLXRvb2x0aXAtZHVyYXRpb24tb3V0LCA1MDBtcyk7CiAgICAgICAgYW5pbWF0aW9uLWZpbGwtbW9kZTogZm9yd2FyZHM7CiAgICAgICAgQGFwcGx5IC0tcGFwZXItdG9vbHRpcC1hbmltYXRpb247CiAgICAgIH0KCiAgICAgIC5zY2FsZS11cC1hbmltYXRpb24gewogICAgICAgIHRyYW5zZm9ybTogc2NhbGUoMCk7CiAgICAgICAgb3BhY2l0eTogdmFyKC0tcGFwZXItdG9vbHRpcC1vcGFjaXR5LCAwLjkpOwogICAgICAgIGFuaW1hdGlvbi1kZWxheTogdmFyKC0tcGFwZXItdG9vbHRpcC1kZWxheS1pbiwgNTAwbXMpOwogICAgICAgIGFuaW1hdGlvbi1uYW1lOiBrZXlGcmFtZVNjYWxlVXA7CiAgICAgICAgYW5pbWF0aW9uLWl0ZXJhdGlvbi1jb3VudDogMTsKICAgICAgICBhbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBlYXNlLWluOwogICAgICAgIGFuaW1hdGlvbi1kdXJhdGlvbjogdmFyKC0tcGFwZXItdG9vbHRpcC1kdXJhdGlvbi1pbiwgNTAwbXMpOwogICAgICAgIGFuaW1hdGlvbi1maWxsLW1vZGU6IGZvcndhcmRzOwogICAgICAgIEBhcHBseSAtLXBhcGVyLXRvb2x0aXAtYW5pbWF0aW9uOwogICAgICB9CgogICAgICAuc2NhbGUtZG93bi1hbmltYXRpb24gewogICAgICAgIHRyYW5zZm9ybTogc2NhbGUoMSk7CiAgICAgICAgb3BhY2l0eTogdmFyKC0tcGFwZXItdG9vbHRpcC1vcGFjaXR5LCAwLjkpOwogICAgICAgIGFuaW1hdGlvbi1kZWxheTogdmFyKC0tcGFwZXItdG9vbHRpcC1kZWxheS1vdXQsIDUwMG1zKTsKICAgICAgICBhbmltYXRpb24tbmFtZToga2V5RnJhbWVTY2FsZURvd247CiAgICAgICAgYW5pbWF0aW9uLWl0ZXJhdGlvbi1jb3VudDogMTsKICAgICAgICBhbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBlYXNlLWluOwogICAgICAgIGFuaW1hdGlvbi1kdXJhdGlvbjogdmFyKC0tcGFwZXItdG9vbHRpcC1kdXJhdGlvbi1vdXQsIDUwMG1zKTsKICAgICAgICBhbmltYXRpb24tZmlsbC1tb2RlOiBmb3J3YXJkczsKICAgICAgICBAYXBwbHkgLS1wYXBlci10b29sdGlwLWFuaW1hdGlvbjsKICAgICAgfQoKICAgICAgLnNsaWRlLWRvd24tYW5pbWF0aW9uIHsKICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTIwMDBweCk7CiAgICAgICAgb3BhY2l0eTogMDsKICAgICAgICBhbmltYXRpb24tZGVsYXk6IHZhcigtLXBhcGVyLXRvb2x0aXAtZGVsYXktb3V0LCA1MDBtcyk7CiAgICAgICAgYW5pbWF0aW9uLW5hbWU6IGtleUZyYW1lU2xpZGVEb3duSW47CiAgICAgICAgYW5pbWF0aW9uLWl0ZXJhdGlvbi1jb3VudDogMTsKICAgICAgICBhbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoMC4wLCAwLjAsIDAuMiwgMSk7CiAgICAgICAgYW5pbWF0aW9uLWR1cmF0aW9uOiB2YXIoLS1wYXBlci10b29sdGlwLWR1cmF0aW9uLW91dCwgNTAwbXMpOwogICAgICAgIGFuaW1hdGlvbi1maWxsLW1vZGU6IGZvcndhcmRzOwogICAgICAgIEBhcHBseSAtLXBhcGVyLXRvb2x0aXAtYW5pbWF0aW9uOwogICAgICB9CgogICAgICAuc2xpZGUtZG93bi1hbmltYXRpb24tb3V0IHsKICAgICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMCk7CiAgICAgICAgb3BhY2l0eTogdmFyKC0tcGFwZXItdG9vbHRpcC1vcGFjaXR5LCAwLjkpOwogICAgICAgIGFuaW1hdGlvbi1kZWxheTogdmFyKC0tcGFwZXItdG9vbHRpcC1kZWxheS1vdXQsIDUwMG1zKTsKICAgICAgICBhbmltYXRpb24tbmFtZToga2V5RnJhbWVTbGlkZURvd25PdXQ7CiAgICAgICAgYW5pbWF0aW9uLWl0ZXJhdGlvbi1jb3VudDogMTsKICAgICAgICBhbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOiBjdWJpYy1iZXppZXIoMC40LCAwLjAsIDEsIDEpOwogICAgICAgIGFuaW1hdGlvbi1kdXJhdGlvbjogdmFyKC0tcGFwZXItdG9vbHRpcC1kdXJhdGlvbi1vdXQsIDUwMG1zKTsKICAgICAgICBhbmltYXRpb24tZmlsbC1tb2RlOiBmb3J3YXJkczsKICAgICAgICBAYXBwbHkgLS1wYXBlci10b29sdGlwLWFuaW1hdGlvbjsKICAgICAgfQoKICAgICAgLmNhbmNlbC1hbmltYXRpb24gewogICAgICAgIGFuaW1hdGlvbi1kZWxheTogLTMwcyAhaW1wb3J0YW50OwogICAgICB9CgogICAgICAvKiBUaGFua3MgSUUgMTAuICovCgogICAgICAuaGlkZGVuIHsKICAgICAgICBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPGRpdiBpZD0idG9vbHRpcCIgY2xhc3M9ImhpZGRlbiI+CiAgICAgIDxzbG90Pjwvc2xvdD4KICAgIDwvZGl2PgpgLGlzOiJwYXBlci10b29sdGlwIixob3N0QXR0cmlidXRlczp7cm9sZToidG9vbHRpcCIsdGFiaW5kZXg6LTF9LHByb3BlcnRpZXM6e2Zvcjp7dHlwZTpTdHJpbmcsb2JzZXJ2ZXI6Il9maW5kVGFyZ2V0In0sbWFudWFsTW9kZTp7dHlwZTpCb29sZWFuLHZhbHVlOiExLG9ic2VydmVyOiJfbWFudWFsTW9kZUNoYW5nZWQifSxwb3NpdGlvbjp7dHlwZTpTdHJpbmcsdmFsdWU6ImJvdHRvbSJ9LGZpdFRvVmlzaWJsZUJvdW5kczp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxvZmZzZXQ6e3R5cGU6TnVtYmVyLHZhbHVlOjE0fSxtYXJnaW5Ub3A6e3R5cGU6TnVtYmVyLHZhbHVlOjE0fSxhbmltYXRpb25EZWxheTp7dHlwZTpOdW1iZXIsdmFsdWU6NTAwLG9ic2VydmVyOiJfZGVsYXlDaGFuZ2UifSxhbmltYXRpb25FbnRyeTp7dHlwZTpTdHJpbmcsdmFsdWU6IiJ9LGFuaW1hdGlvbkV4aXQ6e3R5cGU6U3RyaW5nLHZhbHVlOiIifSxhbmltYXRpb25Db25maWc6e3R5cGU6T2JqZWN0LHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJue2VudHJ5Olt7bmFtZToiZmFkZS1pbi1hbmltYXRpb24iLG5vZGU6dGhpcyx0aW1pbmc6e2RlbGF5OjB9fV0sZXhpdDpbe25hbWU6ImZhZGUtb3V0LWFuaW1hdGlvbiIsbm9kZTp0aGlzfV19fX0sX3Nob3dpbmc6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX19LGxpc3RlbmVyczp7d2Via2l0QW5pbWF0aW9uRW5kOiJfb25BbmltYXRpb25FbmQifSxnZXQgdGFyZ2V0KCl7dmFyIGU9enQodGhpcykucGFyZW50Tm9kZSx0PXp0KHRoaXMpLmdldE93bmVyUm9vdCgpLHI7cmV0dXJuIHRoaXMuZm9yP3I9enQodCkucXVlcnlTZWxlY3RvcigiIyIrdGhpcy5mb3IpOnI9ZS5ub2RlVHlwZT09Tm9kZS5ET0NVTUVOVF9GUkFHTUVOVF9OT0RFP3QuaG9zdDplLHJ9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5fZmluZFRhcmdldCgpfSxkZXRhY2hlZDpmdW5jdGlvbigpe3RoaXMubWFudWFsTW9kZXx8dGhpcy5fcmVtb3ZlTGlzdGVuZXJzKCl9LHBsYXlBbmltYXRpb246ZnVuY3Rpb24oZSl7ZT09PSJlbnRyeSI/dGhpcy5zaG93KCk6ZT09PSJleGl0IiYmdGhpcy5oaWRlKCl9LGNhbmNlbEFuaW1hdGlvbjpmdW5jdGlvbigpe3RoaXMuJC50b29sdGlwLmNsYXNzTGlzdC5hZGQoImNhbmNlbC1hbmltYXRpb24iKX0sc2hvdzpmdW5jdGlvbigpe2lmKCF0aGlzLl9zaG93aW5nKXtpZih6dCh0aGlzKS50ZXh0Q29udGVudC50cmltKCk9PT0iIil7Zm9yKHZhciBlPSEwLHQ9enQodGhpcykuZ2V0RWZmZWN0aXZlQ2hpbGROb2RlcygpLHI9MDtyPHQubGVuZ3RoO3IrKylpZih0W3JdLnRleHRDb250ZW50LnRyaW0oKSE9PSIiKXtlPSExO2JyZWFrfWlmKGUpcmV0dXJufXRoaXMuX3Nob3dpbmc9ITAsdGhpcy4kLnRvb2x0aXAuY2xhc3NMaXN0LnJlbW92ZSgiaGlkZGVuIiksdGhpcy4kLnRvb2x0aXAuY2xhc3NMaXN0LnJlbW92ZSgiY2FuY2VsLWFuaW1hdGlvbiIpLHRoaXMuJC50b29sdGlwLmNsYXNzTGlzdC5yZW1vdmUodGhpcy5fZ2V0QW5pbWF0aW9uVHlwZSgiZXhpdCIpKSx0aGlzLnVwZGF0ZVBvc2l0aW9uKCksdGhpcy5fYW5pbWF0aW9uUGxheWluZz0hMCx0aGlzLiQudG9vbHRpcC5jbGFzc0xpc3QuYWRkKHRoaXMuX2dldEFuaW1hdGlvblR5cGUoImVudHJ5IikpfX0saGlkZTpmdW5jdGlvbigpe2lmKCEhdGhpcy5fc2hvd2luZyl7aWYodGhpcy5fYW5pbWF0aW9uUGxheWluZyl7dGhpcy5fc2hvd2luZz0hMSx0aGlzLl9jYW5jZWxBbmltYXRpb24oKTtyZXR1cm59ZWxzZSB0aGlzLl9vbkFuaW1hdGlvbkZpbmlzaCgpO3RoaXMuX3Nob3dpbmc9ITEsdGhpcy5fYW5pbWF0aW9uUGxheWluZz0hMH19LHVwZGF0ZVBvc2l0aW9uOmZ1bmN0aW9uKCl7aWYoISghdGhpcy5fdGFyZ2V0fHwhdGhpcy5vZmZzZXRQYXJlbnQpKXt2YXIgZT10aGlzLm9mZnNldDt0aGlzLm1hcmdpblRvcCE9MTQmJnRoaXMub2Zmc2V0PT0xNCYmKGU9dGhpcy5tYXJnaW5Ub3ApO3ZhciB0PXRoaXMub2Zmc2V0UGFyZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLHI9dGhpcy5fdGFyZ2V0LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLG49dGhpcy5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxpPShyLndpZHRoLW4ud2lkdGgpLzIsbz0oci5oZWlnaHQtbi5oZWlnaHQpLzIsYT1yLmxlZnQtdC5sZWZ0LHM9ci50b3AtdC50b3AsbCxjO3N3aXRjaCh0aGlzLnBvc2l0aW9uKXtjYXNlInRvcCI6bD1hK2ksYz1zLW4uaGVpZ2h0LWU7YnJlYWs7Y2FzZSJib3R0b20iOmw9YStpLGM9cytyLmhlaWdodCtlO2JyZWFrO2Nhc2UibGVmdCI6bD1hLW4ud2lkdGgtZSxjPXMrbzticmVhaztjYXNlInJpZ2h0IjpsPWErci53aWR0aCtlLGM9cytvO2JyZWFrfXRoaXMuZml0VG9WaXNpYmxlQm91bmRzPyh0LmxlZnQrbCtuLndpZHRoPndpbmRvdy5pbm5lcldpZHRoPyh0aGlzLnN0eWxlLnJpZ2h0PSIwcHgiLHRoaXMuc3R5bGUubGVmdD0iYXV0byIpOih0aGlzLnN0eWxlLmxlZnQ9TWF0aC5tYXgoMCxsKSsicHgiLHRoaXMuc3R5bGUucmlnaHQ9ImF1dG8iKSx0LnRvcCtjK24uaGVpZ2h0PndpbmRvdy5pbm5lckhlaWdodD8odGhpcy5zdHlsZS5ib3R0b209dC5oZWlnaHQtcytlKyJweCIsdGhpcy5zdHlsZS50b3A9ImF1dG8iKToodGhpcy5zdHlsZS50b3A9TWF0aC5tYXgoLXQudG9wLGMpKyJweCIsdGhpcy5zdHlsZS5ib3R0b209ImF1dG8iKSk6KHRoaXMuc3R5bGUubGVmdD1sKyJweCIsdGhpcy5zdHlsZS50b3A9YysicHgiKX19LF9hZGRMaXN0ZW5lcnM6ZnVuY3Rpb24oKXt0aGlzLl90YXJnZXQmJih0aGlzLmxpc3Rlbih0aGlzLl90YXJnZXQsIm1vdXNlZW50ZXIiLCJzaG93IiksdGhpcy5saXN0ZW4odGhpcy5fdGFyZ2V0LCJmb2N1cyIsInNob3ciKSx0aGlzLmxpc3Rlbih0aGlzLl90YXJnZXQsIm1vdXNlbGVhdmUiLCJoaWRlIiksdGhpcy5saXN0ZW4odGhpcy5fdGFyZ2V0LCJibHVyIiwiaGlkZSIpLHRoaXMubGlzdGVuKHRoaXMuX3RhcmdldCwidGFwIiwiaGlkZSIpKSx0aGlzLmxpc3Rlbih0aGlzLiQudG9vbHRpcCwiYW5pbWF0aW9uZW5kIiwiX29uQW5pbWF0aW9uRW5kIiksdGhpcy5saXN0ZW4odGhpcywibW91c2VlbnRlciIsImhpZGUiKX0sX2ZpbmRUYXJnZXQ6ZnVuY3Rpb24oKXt0aGlzLm1hbnVhbE1vZGV8fHRoaXMuX3JlbW92ZUxpc3RlbmVycygpLHRoaXMuX3RhcmdldD10aGlzLnRhcmdldCx0aGlzLm1hbnVhbE1vZGV8fHRoaXMuX2FkZExpc3RlbmVycygpfSxfZGVsYXlDaGFuZ2U6ZnVuY3Rpb24oZSl7ZSE9PTUwMCYmdGhpcy51cGRhdGVTdHlsZXMoeyItLXBhcGVyLXRvb2x0aXAtZGVsYXktaW4iOmUrIm1zIn0pfSxfbWFudWFsTW9kZUNoYW5nZWQ6ZnVuY3Rpb24oKXt0aGlzLm1hbnVhbE1vZGU/dGhpcy5fcmVtb3ZlTGlzdGVuZXJzKCk6dGhpcy5fYWRkTGlzdGVuZXJzKCl9LF9jYW5jZWxBbmltYXRpb246ZnVuY3Rpb24oKXt0aGlzLiQudG9vbHRpcC5jbGFzc0xpc3QucmVtb3ZlKHRoaXMuX2dldEFuaW1hdGlvblR5cGUoImVudHJ5IikpLHRoaXMuJC50b29sdGlwLmNsYXNzTGlzdC5yZW1vdmUodGhpcy5fZ2V0QW5pbWF0aW9uVHlwZSgiZXhpdCIpKSx0aGlzLiQudG9vbHRpcC5jbGFzc0xpc3QucmVtb3ZlKCJjYW5jZWwtYW5pbWF0aW9uIiksdGhpcy4kLnRvb2x0aXAuY2xhc3NMaXN0LmFkZCgiaGlkZGVuIil9LF9vbkFuaW1hdGlvbkZpbmlzaDpmdW5jdGlvbigpe3RoaXMuX3Nob3dpbmcmJih0aGlzLiQudG9vbHRpcC5jbGFzc0xpc3QucmVtb3ZlKHRoaXMuX2dldEFuaW1hdGlvblR5cGUoImVudHJ5IikpLHRoaXMuJC50b29sdGlwLmNsYXNzTGlzdC5yZW1vdmUoImNhbmNlbC1hbmltYXRpb24iKSx0aGlzLiQudG9vbHRpcC5jbGFzc0xpc3QuYWRkKHRoaXMuX2dldEFuaW1hdGlvblR5cGUoImV4aXQiKSkpfSxfb25BbmltYXRpb25FbmQ6ZnVuY3Rpb24oKXt0aGlzLl9hbmltYXRpb25QbGF5aW5nPSExLHRoaXMuX3Nob3dpbmd8fCh0aGlzLiQudG9vbHRpcC5jbGFzc0xpc3QucmVtb3ZlKHRoaXMuX2dldEFuaW1hdGlvblR5cGUoImV4aXQiKSksdGhpcy4kLnRvb2x0aXAuY2xhc3NMaXN0LmFkZCgiaGlkZGVuIikpfSxfZ2V0QW5pbWF0aW9uVHlwZTpmdW5jdGlvbihlKXtpZihlPT09ImVudHJ5IiYmdGhpcy5hbmltYXRpb25FbnRyeSE9PSIiKXJldHVybiB0aGlzLmFuaW1hdGlvbkVudHJ5O2lmKGU9PT0iZXhpdCImJnRoaXMuYW5pbWF0aW9uRXhpdCE9PSIiKXJldHVybiB0aGlzLmFuaW1hdGlvbkV4aXQ7aWYodGhpcy5hbmltYXRpb25Db25maWdbZV0mJnR5cGVvZiB0aGlzLmFuaW1hdGlvbkNvbmZpZ1tlXVswXS5uYW1lPT0ic3RyaW5nIil7aWYodGhpcy5hbmltYXRpb25Db25maWdbZV1bMF0udGltaW5nJiZ0aGlzLmFuaW1hdGlvbkNvbmZpZ1tlXVswXS50aW1pbmcuZGVsYXkmJnRoaXMuYW5pbWF0aW9uQ29uZmlnW2VdWzBdLnRpbWluZy5kZWxheSE9PTApe3ZhciB0PXRoaXMuYW5pbWF0aW9uQ29uZmlnW2VdWzBdLnRpbWluZy5kZWxheTtlPT09ImVudHJ5Ij90aGlzLnVwZGF0ZVN0eWxlcyh7Ii0tcGFwZXItdG9vbHRpcC1kZWxheS1pbiI6dCsibXMifSk6ZT09PSJleGl0IiYmdGhpcy51cGRhdGVTdHlsZXMoeyItLXBhcGVyLXRvb2x0aXAtZGVsYXktb3V0Ijp0KyJtcyJ9KX1yZXR1cm4gdGhpcy5hbmltYXRpb25Db25maWdbZV1bMF0ubmFtZX19LF9yZW1vdmVMaXN0ZW5lcnM6ZnVuY3Rpb24oKXt0aGlzLl90YXJnZXQmJih0aGlzLnVubGlzdGVuKHRoaXMuX3RhcmdldCwibW91c2VlbnRlciIsInNob3ciKSx0aGlzLnVubGlzdGVuKHRoaXMuX3RhcmdldCwiZm9jdXMiLCJzaG93IiksdGhpcy51bmxpc3Rlbih0aGlzLl90YXJnZXQsIm1vdXNlbGVhdmUiLCJoaWRlIiksdGhpcy51bmxpc3Rlbih0aGlzLl90YXJnZXQsImJsdXIiLCJoaWRlIiksdGhpcy51bmxpc3Rlbih0aGlzLl90YXJnZXQsInRhcCIsImhpZGUiKSksdGhpcy51bmxpc3Rlbih0aGlzLiQudG9vbHRpcCwiYW5pbWF0aW9uZW5kIiwiX29uQW5pbWF0aW9uRW5kIiksdGhpcy51bmxpc3Rlbih0aGlzLCJtb3VzZWVudGVyIiwiaGlkZSIpfX0pO3ZhciByYj1FZShPZSgpLDEpO3ZhciBkRT1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLmxpc3RlbmVyPXR9fSxUVz1uZXcgU2V0LEQ5PW5ldyBTZXQ7d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoImhhc2hjaGFuZ2UiLCgpPT57VFcuZm9yRWFjaChlPT5lLmxpc3RlbmVyKCkpfSk7d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoInN0b3JhZ2UiLCgpPT57RDkuZm9yRWFjaChlPT5lLmxpc3RlbmVyKCkpfSk7ZnVuY3Rpb24gbUUoZSl7bGV0IHQ9bmV3IGRFKGUpO3JldHVybiBUVy5hZGQodCksdH1mdW5jdGlvbiBDVyhlKXtsZXQgdD1uZXcgZEUoZSk7cmV0dXJuIEQ5LmFkZCh0KSx0fWZ1bmN0aW9uIEFXKCl7RDkuZm9yRWFjaChlPT5lLmxpc3RlbmVyKCkpfWZ1bmN0aW9uIFBXKGUpe1RXLmRlbGV0ZShlKX1mdW5jdGlvbiBJVyhlKXtEOS5kZWxldGUoZSl9dmFyIFJXPXt9O0tzKFJXLHtnZXRGYWtlSGFzaDooKT0+a1csc2V0RmFrZUhhc2g6KCk9PkxXLHNldFVzZUhhc2g6KCk9PnFiZSx1c2VIYXNoOigpPT5POX0pO3ZhciBrMHQ9ITE7ZnVuY3Rpb24gcWJlKGUpe2swdD1lfWZ1bmN0aW9uIE85KCl7cmV0dXJuIGswdH12YXIgUjB0PSIiO2Z1bmN0aW9uIExXKGUpe1IwdD1lfWZ1bmN0aW9uIGtXKCl7cmV0dXJuIFIwdH12YXIgUXg9Il9fdGFiX18iLE5XPXt9O2Z1bmN0aW9uIE4wdCgpe3JldHVybiBOV31mdW5jdGlvbiBEMHQoZSl7Tlc9ZX1tRSgoKT0+e05XPWViKHRiKCkpfSk7ZnVuY3Rpb24gdGIoKXtyZXR1cm4gTzkoKT93aW5kb3cubG9jYXRpb24uaGFzaC5zbGljZSgxKTprVygpfWZ1bmN0aW9uIGViKGUpe2xldCB0PXt9O3JldHVybiBlLnNwbGl0KCImIikuZm9yRWFjaChuPT57bGV0IGk9bi5zcGxpdCgiPSIpO2kubGVuZ3RoPT09MT90W1F4XT1pWzBdOmkubGVuZ3RoPT09MiYmKHRbZGVjb2RlVVJJQ29tcG9uZW50KGlbMF0pXT1kZWNvZGVVUklDb21wb25lbnQoaVsxXSkpfSksdH1mdW5jdGlvbiB6OShlLHQ9ITEpe2lmKE85KCkpaWYodCl7bGV0IHI9bmV3IFVSTCh3aW5kb3cubG9jYXRpb24uaHJlZik7ci5oYXNoPWUsd2luZG93Lmhpc3RvcnkucmVwbGFjZVN0YXRlKHdpbmRvdy5oaXN0b3J5LnN0YXRlLCIiLHIudG9TdHJpbmcoKSl9ZWxzZSB3aW5kb3cubG9jYXRpb24uaGFzaD1lO2Vsc2UgTFcoZSl9ZnVuY3Rpb24gRjkoZSl7bGV0IHQ9IiI7ZVtReF0hPT12b2lkIDAmJih0Kz1lW1F4XSk7bGV0IHI9T2JqZWN0LmtleXMoZSkubWFwKG49PltuLGVbbl1dKS5maWx0ZXIobj0+blswXSE9PVF4KS5tYXAobj0+ZW5jb2RlVVJJQ29tcG9uZW50KG5bMF0pKyI9IitlbmNvZGVVUklDb21wb25lbnQoblsxXSkpLmpvaW4oIiYiKTtyZXR1cm4gci5sZW5ndGg+MD90KyImIityOnR9ZnVuY3Rpb24gTzB0KGUsdD0hMSl7bGV0IHI9ZWIodGIoKSk7ZGVsZXRlIHJbZV0sejkoRjkociksdCl9dmFyIEYwdD0iZGlzYW1iaWd1YXRvciIse2dldDpHYmUsc2V0OldiZSxnZXRJbml0aWFsaXplcjp5XyxnZXRPYnNlcnZlcjp2XyxkaXNwb3NlQmluZGluZzpZYmV9PXlFKGU9PmUsZT0+ZSkse2dldDpqYmUsc2V0OlhiZSxnZXRJbml0aWFsaXplcjp2cCxnZXRPYnNlcnZlcjp4cCxkaXNwb3NlQmluZGluZzokYmV9PXlFKGU9PmU9PT0idHJ1ZSI/ITA6ZT09PSJmYWxzZSI/ITE6dm9pZCAwLGU9PmUudG9TdHJpbmcoKSkse2dldDpEVyxzZXQ6T1csZ2V0SW5pdGlhbGl6ZXI6Z0UsZ2V0T2JzZXJ2ZXI6X0UsZGlzcG9zZUJpbmRpbmc6S2JlfT15RShlPT4rZSxlPT5lLnRvU3RyaW5nKCkpLHtnZXQ6WmJlLHNldDpKYmUsZ2V0SW5pdGlhbGl6ZXI6elcsZ2V0T2JzZXJ2ZXI6RlcsZGlzcG9zZUJpbmRpbmc6UWJlfT15RShlPT5KU09OLnBhcnNlKGF0b2IoZSkpLGU9PmJ0b2EoSlNPTi5zdHJpbmdpZnkoZSkpKTtmdW5jdGlvbiB5RShlLHQpe2xldCByPVtdLG49W107ZnVuY3Rpb24gaShjLHU9e30pe2xldHtkZWZhdWx0VmFsdWU6aCx1c2VMb2NhbFN0b3JhZ2U6Zj0hMX09dSxwPWY/d2luZG93LmxvY2FsU3RvcmFnZS5nZXRJdGVtKGMpOmViKHRiKCkpW2NdO3JldHVybiBwPT1udWxsP3JiLmNsb25lRGVlcChoKTplKHApfWZ1bmN0aW9uIG8oYyx1LGg9e30pe2xldHtkZWZhdWx0VmFsdWU6Zix1c2VMb2NhbFN0b3JhZ2U6cD0hMSx1c2VMb2NhdGlvblJlcGxhY2U6ZD0hMX09aCxnPXQodSk7aWYocCl3aW5kb3cubG9jYWxTdG9yYWdlLnNldEl0ZW0oYyxnKSxBVygpO2Vsc2UgaWYoIXJiLmlzRXF1YWwodSxpKGMse3VzZUxvY2FsU3RvcmFnZTpwfSkpKWlmKHJiLmlzRXF1YWwodSxmKSlPMHQoYyxkKTtlbHNle2xldCBfPWViKHRiKCkpO19bY109Zyx6OShGOShfKSxkKX19ZnVuY3Rpb24gYShjLHUpe2xldCBoPUtsKHtkZWZhdWx0VmFsdWU6dS5kZWZhdWx0VmFsdWUscG9seW1lclByb3BlcnR5OmMsdXNlTG9jYWxTdG9yYWdlOiExfSx1KTtyZXR1cm4gZnVuY3Rpb24oKXtsZXQgZj16MHQodGhpcyxjKSxwPSgpPT57bGV0IF89aShmLGgpLHk9dGhpc1toLnBvbHltZXJQcm9wZXJ0eV07cmIuaXNFcXVhbChfLHkpfHwodGhpc1toLnBvbHltZXJQcm9wZXJ0eV09Xyl9LGc9KGgudXNlTG9jYWxTdG9yYWdlP0NXOm1FKSgoKT0+cCgpKTtyZXR1cm4gaC51c2VMb2NhbFN0b3JhZ2U/bi5wdXNoKGcpOnIucHVzaChnKSxwKCksdGhpc1toLnBvbHltZXJQcm9wZXJ0eV19fWZ1bmN0aW9uIHMoKXtyLmZvckVhY2goYz0+UFcoYykpLG4uZm9yRWFjaChjPT5JVyhjKSl9ZnVuY3Rpb24gbChjLHUpe2xldCBoPUtsKHtkZWZhdWx0VmFsdWU6dS5kZWZhdWx0VmFsdWUscG9seW1lclByb3BlcnR5OmMsdXNlTG9jYWxTdG9yYWdlOiExfSx1KTtyZXR1cm4gZnVuY3Rpb24oKXtsZXQgZj16MHQodGhpcyxjKSxwPXRoaXNbaC5wb2x5bWVyUHJvcGVydHldO28oZixwLGgpfX1yZXR1cm57Z2V0Omksc2V0Om8sZ2V0SW5pdGlhbGl6ZXI6YSxnZXRPYnNlcnZlcjpsLGRpc3Bvc2VCaW5kaW5nOnN9fWZ1bmN0aW9uIHQyZSgpe2xldCBlPW5ldyBTZXQoWyJleGFtcGxlc1BhdGgiLCJoaWRlTW9kZWxQYW5lMiIsIm1vZGVsTmFtZTEiLCJtb2RlbE5hbWUyIiwiaW5mZXJlbmNlQWRkcmVzczEiLCJpbmZlcmVuY2VBZGRyZXNzMiIsIm1vZGVsVHlwZSIsIm1vZGVsVmVyc2lvbjEiLCJtb2RlbFZlcnNpb24yIiwibW9kZWxTaWduYXR1cmUxIiwibW9kZWxTaWduYXR1cmUyIiwibWF4RXhhbXBsZXMiLCJsYWJlbFZvY2FiUGF0aCIsIm11bHRpQ2xhc3MiLCJzZXF1ZW5jZUV4YW1wbGVzIiwibWF4Q2xhc3Nlc1RvRGlzcGxheSIsInNhbXBsaW5nT2RkcyIsInVzZVByZWRpY3RBcGkiLCJwcmVkaWN0SW5wdXRUZW5zb3IiLCJwcmVkaWN0T3V0cHV0VGVuc29yIl0pLHQ9ZWIodGIoKSk7aWYodFtReF09PT0id2hhdGlmIil7Zm9yKGxldCByIG9mIGUpaWYociBpbiB0KXtsZXQgbj10W3JdO3RbYHAud2hhdGlmLiR7cn1gXT1ufX16OShGOSh0KSksRDB0KHQpfWZ1bmN0aW9uIHowdChlLHQpe2xldCByPWVbRjB0XTtyZXR1cm4ocj09bnVsbD9bdF06W3IsdF0pLmpvaW4oIi4iKX12YXIgdkU9Y2xhc3MgZXh0ZW5kcyBtdHtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fdGFnRmlsdGVyPXlfKCJ0YWdGaWx0ZXIiLHtkZWZhdWx0VmFsdWU6IiIsdXNlTG9jYWxTdG9yYWdlOiExLHBvbHltZXJQcm9wZXJ0eToiX3RhZ0ZpbHRlciJ9KS5jYWxsKHRoaXMpLHRoaXMuX3RhZ0ZpbHRlck9ic2VydmVyPXZfKCJ0YWdGaWx0ZXIiLHtkZWZhdWx0VmFsdWU6IiIsdXNlTG9jYWxTdG9yYWdlOiExLHBvbHltZXJQcm9wZXJ0eToiX3RhZ0ZpbHRlciJ9KX1fY29tcHV0ZVRhZ0ZpbHRlcigpe3JldHVybiB0aGlzLl90YWdGaWx0ZXJ9fTt2RS50ZW1wbGF0ZT1RYAogICAgPHBhcGVyLWlucHV0CiAgICAgIG5vLWxhYmVsLWZsb2F0PSIiCiAgICAgIGxhYmVsPSJGaWx0ZXIgdGFncyAocmVndWxhciBleHByZXNzaW9ucyBzdXBwb3J0ZWQpIgogICAgICB2YWx1ZT0ie3tfdGFnRmlsdGVyfX0iCiAgICAgIGNsYXNzPSJzZWFyY2gtaW5wdXQiCiAgICA+CiAgICAgIDxpcm9uLWljb24gcHJlZml4PSIiIGljb249InNlYXJjaCIgc2xvdD0icHJlZml4Ij48L2lyb24taWNvbj4KICAgIDwvcGFwZXItaW5wdXQ+CiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBtYXJnaW46IDEwcHggNXB4IDEwcHggMTBweDsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6U3RyaW5nLG5vdGlmeTohMCxjb21wdXRlZDoiX2NvbXB1dGVUYWdGaWx0ZXIoX3RhZ0ZpbHRlcikifSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLHZFLnByb3RvdHlwZSwidGFnRmlsdGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG9ic2VydmVyOiJfdGFnRmlsdGVyT2JzZXJ2ZXIifSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLHZFLnByb3RvdHlwZSwiX3RhZ0ZpbHRlciIsdm9pZCAwKTt2RT1FKFt5dCgidGYtdGFnLWZpbHRlcmVyIildLHZFKTtmdW5jdGlvbiBfcyhlKXtsZXR7bW9kdWxlTmFtZTp0LHN0eWxlQ29udGVudDpyfT1lLG49ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZG9tLW1vZHVsZSIpLGk9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGVtcGxhdGUiKSxvPVtdO2Uuc3R5bGVEZXBlbmRlbmNpZXMmJmUuc3R5bGVEZXBlbmRlbmNpZXMuZm9yRWFjaChzPT57bGV0IGw9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3R5bGUiKTtsLnNldEF0dHJpYnV0ZSgiaW5jbHVkZSIscyksby5wdXNoKGwpfSk7bGV0IGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3R5bGUiKTtPYmplY3QuYXNzaWduKGEse3RleHRDb250ZW50OnJ9KSxvLmZvckVhY2gocz0+e2kuY29udGVudC5hcHBlbmRDaGlsZChzKX0pLGkuY29udGVudC5hcHBlbmRDaGlsZChhKSxuLmFwcGVuZENoaWxkKGkpLG4ucmVnaXN0ZXIodCl9X3Moe21vZHVsZU5hbWU6ImRhc2hib2FyZC1zdHlsZSIsc3R5bGVEZXBlbmRlbmNpZXM6WyJpcm9uLWZsZXgiXSxzdHlsZUNvbnRlbnQ6YAogICAgICA6aG9zdCB7CiAgICAgICAgLS1zaWRlYmFyLXZlcnRpY2FsLXBhZGRpbmc6IDE1cHg7CiAgICAgICAgLS1zaWRlYmFyLWxlZnQtcGFkZGluZzogMzBweDsKICAgICAgfQoKICAgICAgW3Nsb3Q9J3NpZGViYXInXSB7CiAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICAgIG1hcmdpbi1yaWdodDogMTBweDsKICAgICAgICBvdmVyZmxvdy14OiBoaWRkZW47CiAgICAgICAgcGFkZGluZzogNXB4IDA7CiAgICAgICAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7CiAgICAgIH0KCiAgICAgIC5zZXR0aW5ncyB7CiAgICAgICAgbWluLWhlaWdodDogNTBweDsKICAgICAgICBvdmVyZmxvdy14OiBoaWRkZW47CiAgICAgICAgb3ZlcmZsb3cteTogYXV0bzsKICAgICAgICB3aWxsLWNoYW5nZTogdHJhbnNmb3JtOwogICAgICB9CgogICAgICAucnVucy1zZWxlY3RvciB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgICAgbWluLWhlaWdodDogMjAwcHg7CiAgICAgIH0KCiAgICAgIHRmLXJ1bnMtc2VsZWN0b3IgewogICAgICAgIGZsZXgtZ3JvdzogMTsKICAgICAgICBmbGV4LXNocmluazogMTsKICAgICAgICBsZWZ0OiB2YXIoLS1zaWRlYmFyLWxlZnQtcGFkZGluZyk7CiAgICAgICAgbWF4LWhlaWdodDogY2FsYygxMDAlIC0gdmFyKC0tc2lkZWJhci12ZXJ0aWNhbC1wYWRkaW5nKSAqIDIpOwogICAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIHJpZ2h0OiAwOwogICAgICB9CgogICAgICAuc2VhcmNoLWlucHV0IHsKICAgICAgICBtYXJnaW46IDEwcHggNXB4IDAgMTBweDsKICAgICAgfQoKICAgICAgLnNpZGViYXItc2VjdGlvbiB7CiAgICAgICAgYm9yZGVyLXRvcDogc29saWQgMXB4IHZhcigtLXRiLXVpLWJvcmRlcik7CiAgICAgICAgbWFyZ2luLXJpZ2h0OiAxMHB4OwogICAgICAgIHBhZGRpbmc6IHZhcigtLXNpZGViYXItdmVydGljYWwtcGFkZGluZykgMAogICAgICAgICAgdmFyKC0tc2lkZWJhci12ZXJ0aWNhbC1wYWRkaW5nKSB2YXIoLS1zaWRlYmFyLWxlZnQtcGFkZGluZyk7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgIH0KCiAgICAgIC5zaWRlYmFyLXNlY3Rpb246Zmlyc3Qtb2YtdHlwZSB7CiAgICAgICAgYm9yZGVyOiBub25lOwogICAgICB9CgogICAgICAuc2lkZWJhci1zZWN0aW9uIHBhcGVyLWJ1dHRvbiB7CiAgICAgICAgbWFyZ2luOiA1cHg7CiAgICAgIH0KCiAgICAgIC5zaWRlYmFyLXNlY3Rpb24gcGFwZXItYnV0dG9uOmZpcnN0LW9mLXR5cGUgewogICAgICAgIG1hcmdpbi1sZWZ0OiAwICFpbXBvcnRhbnQ7CiAgICAgIH0KCiAgICAgIC5zaWRlYmFyLXNlY3Rpb24gcGFwZXItYnV0dG9uOmxhc3Qtb2YtdHlwZSB7CiAgICAgICAgbWFyZ2luLXJpZ2h0OiAwICFpbXBvcnRhbnQ7CiAgICAgIH0KCiAgICAgIC5zaWRlYmFyLXNlY3Rpb24gPiA6Zmlyc3QtY2hpbGQgewogICAgICAgIG1hcmdpbi10b3A6IDA7CiAgICAgICAgcGFkZGluZy10b3A6IDA7CiAgICAgIH0KCiAgICAgIC5zaWRlYmFyLXNlY3Rpb24gPiA6bGFzdC1jaGlsZCB7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogMDsKICAgICAgICBwYWRkaW5nLWJvdHRvbTogMDsKICAgICAgfQoKICAgICAgLnNpZGViYXItc2VjdGlvbiBoMyB7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLXNlY29uZGFyeS10ZXh0LWNvbG9yKTsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBmb250LXNpemU6IDE0cHg7CiAgICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDsKICAgICAgICBtYXJnaW46IDEwcHggMCA1cHg7CiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgIH0KCiAgICAgIHBhcGVyLWNoZWNrYm94IHsKICAgICAgICAtLXBhcGVyLWNoZWNrYm94LWNoZWNrZWQtY29sb3I6IHZhcigtLXRiLXVpLWRhcmstYWNjZW50KTsKICAgICAgICAtLXBhcGVyLWNoZWNrYm94LXVuY2hlY2tlZC1jb2xvcjogdmFyKC0tdGItdWktZGFyay1hY2NlbnQpOwogICAgICAgIGZvbnQtc2l6ZTogMTVweDsKICAgICAgICBtYXJnaW4tdG9wOiA1cHg7CiAgICAgIH0KCiAgICAgIGEgewogICAgICAgIGNvbG9yOiB2YXIoLS10Yi1saW5rKTsKICAgICAgfQoKICAgICAgYTp2aXNpdGVkIHsKICAgICAgICBjb2xvcjogdmFyKC0tdGItbGluay12aXNpdGVkKTsKICAgICAgfQogIGB9KTtmdW5jdGlvbiBfbyhlKXtyZXR1cm4gY2xhc3MgZXh0ZW5kcyBle2Nvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLl9tYXliZVNldERhcmtNb2RlKCksdGhpcy5vYnNlcnZlcj1uZXcgTXV0YXRpb25PYnNlcnZlcihyPT57ci5zb21lKGk9PmkuYXR0cmlidXRlTmFtZT09PSJjbGFzcyIpJiZ0aGlzLl9tYXliZVNldERhcmtNb2RlKCl9KSx0aGlzLm9ic2VydmVyLm9ic2VydmUoZG9jdW1lbnQuYm9keSx7YXR0cmlidXRlczohMH0pfWRpc2Nvbm5lY3RlZENhbGxiYWNrKCl7dmFyIHI7c3VwZXIuZGlzY29ubmVjdGVkQ2FsbGJhY2soKSwocj10aGlzLm9ic2VydmVyKT09bnVsbHx8ci5kaXNjb25uZWN0KCl9X21heWJlU2V0RGFya01vZGUoKXt0aGlzLmNsYXNzTGlzdC50b2dnbGUoImRhcmstbW9kZSIsZG9jdW1lbnQuYm9keS5jbGFzc0xpc3QuY29udGFpbnMoImRhcmstbW9kZSIpKX19fV9zKHttb2R1bGVOYW1lOiJzY3JvbGxiYXItc3R5bGUiLHN0eWxlQ29udGVudDpgCiAgICAuc2Nyb2xsYmFyOjotd2Via2l0LXNjcm9sbGJhci10cmFjayB7CiAgICAgIHZpc2liaWxpdHk6IGhpZGRlbjsKICAgIH0KCiAgICAuc2Nyb2xsYmFyOjotd2Via2l0LXNjcm9sbGJhciB7CiAgICAgIHdpZHRoOiAxMHB4OwogICAgfQoKICAgIC5zY3JvbGxiYXI6Oi13ZWJraXQtc2Nyb2xsYmFyLXRodW1iIHsKICAgICAgYm9yZGVyLXJhZGl1czogMTBweDsKICAgICAgLXdlYmtpdC1ib3gtc2hhZG93OiBpbnNldCAwIDAgMnB4IHJnYmEoMCwgMCwgMCwgMC4zKTsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcGFwZXItZ3JleS01MDApOwogICAgICBjb2xvcjogdmFyKC0tcGFwZXItZ3JleS05MDApOwogICAgfQogICAgLnNjcm9sbGJhciB7CiAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICB9CiAgYH0pO3ZhciBIVz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzdHlsZSIpO0hXLnNldEF0dHJpYnV0ZSgiaXMiLCJjdXN0b20tc3R5bGUiKTtIVy50ZXh0Q29udGVudD1gCiAgOnJvb3QgewogICAgLS10Yi1vcmFuZ2Utd2VhazogI2ZmYTcyNjsKICAgIC0tdGItb3JhbmdlLXN0cm9uZzogI2Y1N2MwMDsKICAgIC0tdGItb3JhbmdlLWRhcms6ICNkYzczMjA7CiAgICAtLXRiLWdyZXktZGFya2VyOiAjZTJlMmUyOwogICAgLS10Yi1ncmV5LWxpZ2h0ZXI6ICNmM2YzZjM7CiAgICAtLXRiLXVpLWRhcmstYWNjZW50OiAjNzU3NTc1OwogICAgLS10Yi11aS1saWdodC1hY2NlbnQ6ICNlMGUwZTA7CiAgICAtLXRiLXVpLWJvcmRlcjogdmFyKC0tcGFwZXItZ3JleS0zMDApOwogICAgLS10Yi1ncmFwaC1mYWRlZDogI2UwZDRiMzsKICAgIC0tdGItc2Vjb25kYXJ5LXRleHQtY29sb3I6IHZhcigtLXBhcGVyLWdyZXktODAwKTsKICAgIC0tdGItcmFpc2VkLWJ1dHRvbi1zaGFkb3ctY29sb3I6IHJnYmEoMCwgMCwgMCwgMC4yKTsKICAgIC0tcHJpbWFyeS1iYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOwogICAgLS1zZWNvbmRhcnktYmFja2dyb3VuZC1jb2xvcjogI2U5ZTllOTsKICAgIC0tdGItbGF5b3V0LWJhY2tncm91bmQtY29sb3I6ICNmNWY1ZjU7CiAgICAtLXRiLWxpbms6ICMxOTc2ZDI7IC8qIG1hdGVyaWFsIGJsdWUgNzAwLiAqLwogICAgLS10Yi1saW5rLXZpc2l0ZWQ6ICM3YjFmYTI7IC8qIG1hdGVyaWFsIHB1cnBsZSA3MDAuICovCiAgfQoKICA6cm9vdCAuZGFyay1tb2RlIHsKICAgIC0tdGItdWktYm9yZGVyOiB2YXIoLS1wYXBlci1ncmV5LTcwMCk7CiAgICAtLXRiLXVpLWRhcmstYWNjZW50OiB2YXIoLS1wYXBlci1ncmV5LTQwMCk7CiAgICAtLXRiLXVpLWxpZ2h0LWFjY2VudDogdmFyKC0tcGFwZXItZ3JleS02MDApOwogICAgLS10Yi1zZWNvbmRhcnktdGV4dC1jb2xvcjogdmFyKC0tcGFwZXItZ3JleS00MDApOwogICAgLS10Yi1yYWlzZWQtYnV0dG9uLXNoYWRvdy1jb2xvcjogcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpOwogICAgLS1wcmltYXJ5LXRleHQtY29sb3I6ICNmZmY7CiAgICAtLXNlY29uZGFyeS10ZXh0LWNvbG9yOiB2YXIoLS1wYXBlci1ncmV5LTQwMCk7CiAgICAtLXByaW1hcnktYmFja2dyb3VuZC1jb2xvcjogIzMwMzAzMDsgIC8qIG1hdGVyaWFsIGdyZXkgQTQwMC4gKi8KICAgIC0tc2Vjb25kYXJ5LWJhY2tncm91bmQtY29sb3I6ICMzYTNhM2E7CiAgICAtLXRiLWxheW91dC1iYWNrZ3JvdW5kLWNvbG9yOiAjM2EzYTNhOwogICAgLS10Yi1saW5rOiAjNDJhNWY1OyAvKiBtYXRlcmlhbCBibHVlIDQwMC4gKi8KICAgIC0tdGItbGluay12aXNpdGVkOiAjYmE2OGM4OyAvKiBtYXRlcmlhbCBwdXJwbGUgMzAwLiAqLwogICAgLyogT3ZlcnJpZGVzIHBhcGVyLW1hdGVyaWFsICovCiAgICAtLXNoYWRvdy1lbGV2YXRpb24tMmRwXy1fYm94LXNoYWRvdzogMCAycHggMnB4IDAgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjE0KSwKICAgICAgMCAxcHggNXB4IDAgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjEyKSwKICAgICAgMCAzcHggMXB4IC0ycHggcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjIpOwogIH0KYDtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKEhXKTt2YXIgVlc9Y2xhc3MgZXh0ZW5kcyBfbyhtdCl7fTtWVy50ZW1wbGF0ZT1RYAogICAgPGRpdiBpZD0ic2lkZWJhciI+CiAgICAgIDxzbG90IG5hbWU9InNpZGViYXIiPjwvc2xvdD4KICAgIDwvZGl2PgoKICAgIDxkaXYgaWQ9ImNlbnRlciI+CiAgICAgIDxzbG90IG5hbWU9ImNlbnRlciIgY2xhc3M9InNjb2xsYmFyIj48L3Nsb3Q+CiAgICA8L2Rpdj4KICAgIDxzdHlsZSBpbmNsdWRlPSJzY3JvbGxiYXItc3R5bGUiPjwvc3R5bGU+CiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjVmNWY1OwogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IHJvdzsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgIH0KCiAgICAgIDpob3N0KC5kYXJrLW1vZGUpIHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1zZWNvbmRhcnktYmFja2dyb3VuZC1jb2xvcik7CiAgICAgIH0KCiAgICAgICNzaWRlYmFyIHsKICAgICAgICBmbGV4OiAwIDAgdmFyKC0tdGYtZGFzaGJvYXJkLWxheW91dC1zaWRlYmFyLWJhc2lzLCAyNSUpOwogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgICBtYXgtd2lkdGg6IHZhcigtLXRmLWRhc2hib2FyZC1sYXlvdXQtc2lkZWJhci1tYXgtd2lkdGgsIDM1MHB4KTsKICAgICAgICBtaW4td2lkdGg6IHZhcigtLXRmLWRhc2hib2FyZC1sYXlvdXQtc2lkZWJhci1taW4td2lkdGgsIDI3MHB4KTsKICAgICAgICBvdmVyZmxvdy15OiBhdXRvOwogICAgICAgIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzOwogICAgICB9CgogICAgICAjY2VudGVyIHsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgICAgZmxleC1zaHJpbms6IDE7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgIH0KCiAgICAgIDo6c2xvdHRlZChbc2xvdD0nY2VudGVyJ10pIHsKICAgICAgICBjb250YWluOiBzdHJpY3Q7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICAgIG92ZXJmbG93LXg6IGhpZGRlbjsKICAgICAgICBvdmVyZmxvdy15OiBhdXRvOwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICAgIHdpbGwtY2hhbmdlOiB0cmFuc2Zvcm07CiAgICAgIH0KCiAgICAgIC50Zi1ncmFwaC1kYXNoYm9hcmQgI2NlbnRlciB7CiAgICAgICAgYmFja2dyb3VuZDogI2ZmZjsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO1ZXPUUoW3l0KCJ0Zi1kYXNoYm9hcmQtbGF5b3V0IildLFZXKTt2YXIgQjB0PSJURi5UZW5zb3JCb2FyZC5QYWdpbmF0ZWRWaWV3LmxpbWl0IixlMmU9MTIsQWg9bnVsbCxVVz1uZXcgU2V0O2Z1bmN0aW9uIHFXKGUpe1VXLmFkZChlKX1mdW5jdGlvbiBHVyhlKXtVVy5kZWxldGUoZSl9ZnVuY3Rpb24gV1coKXtyZXR1cm4gQWg9PW51bGwmJihBaD1EVyhCMHQse3VzZUxvY2FsU3RvcmFnZTohMH0pLChBaD09bnVsbHx8IWlzRmluaXRlKEFoKXx8QWg8PTApJiYoQWg9ZTJlKSksQWh9ZnVuY3Rpb24gcjJlKGUpe2lmKGUhPT1NYXRoLmZsb29yKGUpKXRocm93IG5ldyBFcnJvcihgbGltaXQgbXVzdCBiZSBhbiBpbnRlZ2VyLCBidXQgZ290OiAke2V9YCk7aWYoZTw9MCl0aHJvdyBuZXcgRXJyb3IoYGxpbWl0IG11c3QgYmUgcG9zaXRpdmUsIGJ1dCBnb3Q6ICR7ZX1gKTtlIT09QWgmJihBaD1lLE9XKEIwdCxBaCx7dXNlTG9jYWxTdG9yYWdlOiEwfSksVVcuZm9yRWFjaCh0PT57dCgpfSkpfXZhciBuYj1jbGFzcyBleHRlbmRzIG10e3VwZGF0ZUFycmF5UHJvcCh0LHIsbil7bGV0IGk9dGhpcy5nZXQodCksbz1yO2lmKCFBcnJheS5pc0FycmF5KG8pKXRocm93IFJhbmdlRXJyb3IoYEV4cGVjdGVkIG5ldyB2YWx1ZSB0byAnJHt0fScgdG8gYmUgYW4gYXJyYXkuYCk7QXJyYXkuaXNBcnJheShpKXx8KGk9W10sdGhpcy5zZXQodCxpKSk7bGV0IGE9bmV3IFNldChvLm1hcCgoYyx1KT0+bihjLHUpKSkscz0wLGw9MDtmb3IoO3M8aS5sZW5ndGgmJmw8by5sZW5ndGg7KXtpZihhLmhhcyhuKGlbc10scykpKW4oaVtzXSxzKT09bihvW2xdLGwpP3RoaXMuc2V0KGAke3R9LiR7c31gLG9bbF0pOnRoaXMuc3BsaWNlKHQscywwLG9bbF0pO2Vsc2V7dGhpcy5zcGxpY2UodCxzLDEpO2NvbnRpbnVlfWwrKyxzKyt9czxpLmxlbmd0aCYmdGhpcy5zcGxpY2UodCxzKSxsPG8ubGVuZ3RoJiZ0aGlzLnB1c2godCwuLi5vLnNsaWNlKGwpKX19O3ZhciBPaT1jbGFzcyBleHRlbmRzIG5ie2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmFzPSJpdGVtIix0aGlzLl9jb250ZW50QWN0aXZlPSEwLHRoaXMuX2RvbUJvb3RzdHJhcHBlZD0hMSx0aGlzLl9jdG9yPW51bGwsdGhpcy5fcmVuZGVyZWRJdGVtcz1bXSx0aGlzLl9yZW5kZXJlZFRlbXBsYXRlSW5zdD1uZXcgTWFwLHRoaXMuX2xydUNhY2hlZEl0ZW1zPW5ldyBNYXAsdGhpcy5fY2FjaGVTaXplPTEwLHRoaXMuX2dldEl0ZW1LZXk9dD0+SlNPTi5zdHJpbmdpZnkodCksdGhpcy5faXNDb25uZWN0ZWQ9ITF9Y29ubmVjdGVkQ2FsbGJhY2soKXtzdXBlci5jb25uZWN0ZWRDYWxsYmFjaygpLHRoaXMuX2lzQ29ubmVjdGVkPSEwfXNldENhY2hlU2l6ZSh0KXt0aGlzLl9jYWNoZVNpemU9dH1zZXRHZXRJdGVtS2V5KHQpe3RoaXMuX2dldEl0ZW1LZXk9dH11cGRhdGVEb20odCl7dGhpcy51cGRhdGVBcnJheVByb3AoIl9yZW5kZXJlZEl0ZW1zIix0LHRoaXMuX2dldEl0ZW1LZXkpfV9lbnN1cmVUZW1wbGF0aXplZCgpe2lmKCF0aGlzLmlzQ29ubmVjdGVkKXJldHVybiExO2lmKCF0aGlzLl9jdG9yKXtsZXQgdD10aGlzLnF1ZXJ5U2VsZWN0b3IoInRlbXBsYXRlIik7dGhpcy5fY3Rvcj10Yyh0LHRoaXMse3BhcmVudE1vZGVsOiEwLGluc3RhbmNlUHJvcHM6e1t0aGlzLmFzXTohMCxhY3RpdmU6dGhpcy5fY29udGVudEFjdGl2ZX0sZm9yd2FyZEhvc3RQcm9wOmZ1bmN0aW9uKHIsbil7dGhpcy5fcmVuZGVyZWRUZW1wbGF0ZUluc3QuZm9yRWFjaChpPT57aS5mb3J3YXJkSG9zdFByb3AocixuKX0pfX0pfXJldHVybiEwfV9ib290c3RyYXBEb20oKXtpZighdGhpcy5fZW5zdXJlVGVtcGxhdGl6ZWQoKXx8dGhpcy5fZG9tQm9vdHN0cmFwcGVkKXJldHVybjtuZXcgTXV0YXRpb25PYnNlcnZlcihyPT57Zm9yKGxldCBuIG9mIHIpaWYobi50eXBlPT09ImNoaWxkTGlzdCIpZm9yKGxldCBpIG9mIEFycmF5LmZyb20obi5hZGRlZE5vZGVzKSlpIGluc3RhbmNlb2YgRWxlbWVudCYmaS5zZXRBdHRyaWJ1dGUoInNsb3QiLCJpdGVtcyIpfSkub2JzZXJ2ZSh0aGlzLHtjaGlsZExpc3Q6ITB9KSxBcnJheS5mcm9tKHRoaXMuY2hpbGRyZW4pLmZvckVhY2gocj0+e3RoaXMucmVtb3ZlQ2hpbGQocil9KSx0aGlzLl9scnVDYWNoZWRJdGVtcy5jbGVhcigpLHRoaXMuX3JlbmRlcmVkSXRlbXMuZm9yRWFjaCgocixuKT0+dGhpcy5faW5zZXJ0SXRlbShyLG4pKSx0aGlzLl9kb21Cb290c3RyYXBwZWQ9ITB9X3VwZGF0ZUFjdGl2ZSgpeyF0aGlzLl9kb21Cb290c3RyYXBwZWR8fEFycmF5LmZyb20odGhpcy5fcmVuZGVyZWRUZW1wbGF0ZUluc3QudmFsdWVzKCkpLmZvckVhY2godD0+e3Qubm90aWZ5UGF0aCgiYWN0aXZlIix0aGlzLl9jb250ZW50QWN0aXZlKX0pfV91cGRhdGVEb20odCl7aWYoISF0aGlzLl9kb21Cb290c3RyYXBwZWQmJiEodC5wYXRoPT0iX3JlbmRlcmVkSXRlbXMifHx0LnBhdGg9PSJfcmVuZGVyZWRJdGVtcy5sZW5ndGgiKSlpZih0LnBhdGg9PT0iX3JlbmRlcmVkSXRlbXMuc3BsaWNlcyIpdC52YWx1ZS5pbmRleFNwbGljZXMuZm9yRWFjaChuPT57bGV0e2luZGV4OmksYWRkZWRDb3VudDpvLG9iamVjdDphLHJlbW92ZWQ6c309bjtzLmZvckVhY2gobD0+e3RoaXMuX3JlbW92ZUl0ZW0obCx0aGlzLmNoaWxkcmVuW2ldKX0pLGEuc2xpY2UoaSxpK28pLmZvckVhY2goKGwsYyk9PnRoaXMuX2luc2VydEl0ZW0obCxpK2MpKSx0aGlzLl90cmltQ2FjaGUoKX0pO2Vsc2V7bGV0IHI9dGhpcy5fZ2V0SXRlbUtleSh0LnZhbHVlKTt0aGlzLl9yZW5kZXJlZFRlbXBsYXRlSW5zdC5oYXMocik/dGhpcy5fcmVuZGVyZWRUZW1wbGF0ZUluc3QuZ2V0KHIpLm5vdGlmeVBhdGgodGhpcy5hcyx0LnZhbHVlKTpjb25zb2xlLndhcm4oYEV4cGVjdGVkICcke3J9JyB0byBleGlzdCBpbiB0aGUgRE9NIGJ1dCBjb3VsZCBub3QgZmluZCBvbmUuYCl9fV9pbnNlcnRJdGVtKHQscil7aWYoIXRoaXMuX2Vuc3VyZVRlbXBsYXRpemVkKCkpdGhyb3cgbmV3IEVycm9yKCJFeHBlY3RlZCB0ZW1wbGF0aXplZCBiZWZvcmUgaW5zZXJ0aW5nIGFuIGl0ZW0iKTtsZXQgbixpPXRoaXMuX2dldEl0ZW1LZXkodCk7aWYodGhpcy5fbHJ1Q2FjaGVkSXRlbXMuaGFzKGkpKW49dGhpcy5fbHJ1Q2FjaGVkSXRlbXMuZ2V0KGkpLHRoaXMuX2xydUNhY2hlZEl0ZW1zLmRlbGV0ZShpKSx0aGlzLl9yZW5kZXJlZFRlbXBsYXRlSW5zdC5nZXQoaSkubm90aWZ5UGF0aCgiYWN0aXZlIix0aGlzLl9jb250ZW50QWN0aXZlKTtlbHNle2xldCBvPXtbdGhpcy5hc106dCxhY3RpdmU6dGhpcy5fY29udGVudEFjdGl2ZX0sYT1uZXcgdGhpcy5fY3RvcihvKTtuPWEucm9vdCx0aGlzLl9yZW5kZXJlZFRlbXBsYXRlSW5zdC5zZXQoaSxhKX10aGlzLmNoaWxkcmVuW3JdP3RoaXMuaW5zZXJ0QmVmb3JlKG4sdGhpcy5jaGlsZHJlbltyXSk6KChuLm5vZGVUeXBlPT1Ob2RlLkRPQ1VNRU5UX0ZSQUdNRU5UX05PREU/QXJyYXkuZnJvbShuLmNoaWxkcmVuKTpbbl0pLmZvckVhY2goYT0+YS5zZXRBdHRyaWJ1dGUoInNsb3QiLCJpdGVtcyIpKSx0aGlzLmFwcGVuZENoaWxkKG4pKX1fcmVtb3ZlSXRlbSh0LHIpe3IucGFyZW50Tm9kZSYmci5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKHIpO2xldCBuPXRoaXMuX2dldEl0ZW1LZXkodCk7dGhpcy5fbHJ1Q2FjaGVkSXRlbXMuc2V0KG4sciksdGhpcy5fcmVuZGVyZWRUZW1wbGF0ZUluc3QuZ2V0KG4pLm5vdGlmeVBhdGgoImFjdGl2ZSIsITEpfV90cmltQ2FjaGUoKXtmb3IoO3RoaXMuX2xydUNhY2hlZEl0ZW1zLnNpemU+dGhpcy5fY2FjaGVTaXplOyl7bGV0W3RdPXRoaXMuX2xydUNhY2hlZEl0ZW1zLmtleXMoKTt0aGlzLl9scnVDYWNoZWRJdGVtcy5kZWxldGUodCksdGhpcy5fcmVuZGVyZWRUZW1wbGF0ZUluc3QuZGVsZXRlKHQpfX19O0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLE9pLnByb3RvdHlwZSwiYXMiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLE9pLnByb3RvdHlwZSwiaXRlbXMiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxPaS5wcm90b3R5cGUsIl9jb250ZW50QWN0aXZlIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxPaS5wcm90b3R5cGUsIl9kb21Cb290c3RyYXBwZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sT2kucHJvdG90eXBlLCJfY3RvciIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sT2kucHJvdG90eXBlLCJfcmVuZGVyZWRJdGVtcyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxPaS5wcm90b3R5cGUsIl9yZW5kZXJlZFRlbXBsYXRlSW5zdCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxPaS5wcm90b3R5cGUsIl9scnVDYWNoZWRJdGVtcyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxPaS5wcm90b3R5cGUsIl9jYWNoZVNpemUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sT2kucHJvdG90eXBlLCJfZ2V0SXRlbUtleSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sT2kucHJvdG90eXBlLCJfaXNDb25uZWN0ZWQiLHZvaWQgMCk7RShbQnQoIl9pc0Nvbm5lY3RlZCIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sT2kucHJvdG90eXBlLCJfYm9vdHN0cmFwRG9tIixudWxsKTtFKFtCdCgiX2NvbnRlbnRBY3RpdmUiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLE9pLnByb3RvdHlwZSwiX3VwZGF0ZUFjdGl2ZSIsbnVsbCk7RShbQnQoIl9yZW5kZXJlZEl0ZW1zLioiLCJfZG9tQm9vdHN0cmFwcGVkIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW09iamVjdF0pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxPaS5wcm90b3R5cGUsIl91cGRhdGVEb20iLG51bGwpO0UoW0J0KCJfY2FjaGVTaXplIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxPaS5wcm90b3R5cGUsIl90cmltQ2FjaGUiLG51bGwpO3ZhciBobj1jbGFzcyBleHRlbmRzIE9pe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmRpc2FibGVQYWdpbmF0aW9uPSExLHRoaXMuZ2V0Q2F0ZWdvcnlJdGVtS2V5PXQ9PkpTT04uc3RyaW5naWZ5KHQpLHRoaXMuX2xpbWl0PTEyLHRoaXMuX2FjdGl2ZUluZGV4PTAsdGhpcy5fcGFnZUlucHV0UmF3VmFsdWU9IiIsdGhpcy5fcGFnZUlucHV0Rm9jdXNlZD0hMX1fY29tcHV0ZUNvdW50KCl7cmV0dXJuIHRoaXMuY2F0ZWdvcnkuaXRlbXMubGVuZ3RofWdldCBfaGFzTXVsdGlwbGUoKXtyZXR1cm4gdGhpcy5fY291bnQ+MX1fdG9nZ2xlUGFuZSgpe3RoaXMub3BlbmVkPSF0aGlzLm9wZW5lZH1fY2hhbmdlQ29udGVudEFjdGl2ZSh0KXt0aGlzLl9jb250ZW50QWN0aXZlPXR9X29uUGFuZVJlbmRlcmVkQ2hhbmdlZCh0LHIpe3QmJnQhPT1yJiZ0aGlzLiQuaWZSZW5kZXJlZC5yZW5kZXIoKX1fY29tcHV0ZVBhbmVSZW5kZXJlZCh0KXtyZXR1cm4hKHQubWV0YWRhdGEudHlwZT09PU5hLlNFQVJDSF9SRVNVTFRTJiZ0Lm5hbWU9PT0iIil9Z2V0IF9pdGVtc1JlbmRlcmVkKCl7cmV0dXJuIHRoaXMuX3BhbmVSZW5kZXJlZCYmdGhpcy5vcGVuZWR9X2NvbXB1dGVJc1NlYXJjaFJlc3VsdHModCl7cmV0dXJuIHQ9PT1OYS5TRUFSQ0hfUkVTVUxUU31fY29tcHV0ZUlzSW52YWxpZFNlYXJjaFJlc3VsdHModCl7cmV0dXJuIHQudHlwZT09PU5hLlNFQVJDSF9SRVNVTFRTJiYhdC52YWxpZFJlZ2V4fV9jb21wdXRlSXNVbml2ZXJzYWxTZWFyY2hRdWVyeSh0KXtyZXR1cm4gdC50eXBlPT09TmEuU0VBUkNIX1JFU1VMVFMmJnQudW5pdmVyc2FsUmVnZXh9X2lzQ29tcG9zaXRlU2VhcmNoKCl7bGV0e3R5cGU6dCxjb21wb3NpdGVTZWFyY2g6cn09dGhpcy5jYXRlZ29yeS5tZXRhZGF0YTtyZXR1cm4gciYmdD09PU5hLlNFQVJDSF9SRVNVTFRTfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLm9wZW5lZD10aGlzLmluaXRpYWxPcGVuZWQ9PW51bGw/ITA6dGhpcy5pbml0aWFsT3BlbmVkLHRoaXMuX2xpbWl0TGlzdGVuZXI9KCk9Pnt0aGlzLnNldCgiX2xpbWl0IixXVygpKX0scVcodGhpcy5fbGltaXRMaXN0ZW5lciksdGhpcy5fbGltaXRMaXN0ZW5lcigpfWRldGFjaGVkKCl7R1codGhpcy5fbGltaXRMaXN0ZW5lcil9X3VwZGF0ZVJlbmRlcmVkSXRlbXMoKXt2YXIgdD10aGlzLl9pdGVtc1JlbmRlcmVkLHI9dGhpcy5fbGltaXQsbj10aGlzLl9hY3RpdmVJbmRleCxpPXRoaXMuZGlzYWJsZVBhZ2luYXRpb247aWYoIXQpcmV0dXJuO2xldCBvPU1hdGguZmxvb3Iobi9yKSxhPXRoaXMuY2F0ZWdvcnkuaXRlbXN8fFtdLHM9aT9hOmEuc2xpY2UobypyLChvKzEpKnIpO3RoaXMudXBkYXRlRG9tKHMpfV9saW1pdENoYW5nZWQodCl7dGhpcy5zZXRDYWNoZVNpemUodCoyKX1fZ2V0Q2F0ZWdvcnlJdGVtS2V5Q2hhbmdlZCgpe3RoaXMuc2V0R2V0SXRlbUtleSh0aGlzLmdldENhdGVnb3J5SXRlbUtleSl9Z2V0IF9jdXJyZW50UGFnZSgpe3ZhciB0PXRoaXMuX2xpbWl0LHI9dGhpcy5fYWN0aXZlSW5kZXg7cmV0dXJuIE1hdGguZmxvb3Ioci90KSsxfV9jb21wdXRlUGFnZUNvdW50KHQscil7cmV0dXJuIHRoaXMuY2F0ZWdvcnk/TWF0aC5jZWlsKHRoaXMuY2F0ZWdvcnkuaXRlbXMubGVuZ3RoL3IpOjB9Z2V0IF9tdWx0aXBsZVBhZ2VzRXhpc3QoKXt2YXIgdD10aGlzLl9wYWdlQ291bnQscj10aGlzLmRpc2FibGVQYWdpbmF0aW9uO3JldHVybiFyJiZ0PjF9Z2V0IF9oYXNQcmV2aW91c1BhZ2UoKXt2YXIgdD10aGlzLl9jdXJyZW50UGFnZTtyZXR1cm4gdD4xfWdldCBfaGFzTmV4dFBhZ2UoKXt2YXIgdD10aGlzLl9jdXJyZW50UGFnZSxyPXRoaXMuX3BhZ2VDb3VudDtyZXR1cm4gdDxyfV9jb21wdXRlSW5wdXRXaWR0aCh0KXtyZXR1cm5gY2FsYygke3QudG9TdHJpbmcoKS5sZW5ndGh9ZW0gKyAyMHB4KWB9X3NldEFjdGl2ZUluZGV4KHQpe2xldCByPSh0aGlzLmNhdGVnb3J5Lml0ZW1zfHxbXSkubGVuZ3RoLTE7dD5yJiYodD1yKSx0PDAmJih0PTApLHRoaXMuc2V0KCJfYWN0aXZlSW5kZXgiLHQpfV9jbGFtcEFjdGl2ZUluZGV4KCl7dGhpcy5fc2V0QWN0aXZlSW5kZXgodGhpcy5fYWN0aXZlSW5kZXgpfV9wZXJmb3JtUHJldmlvdXNQYWdlKCl7dGhpcy5fc2V0QWN0aXZlSW5kZXgodGhpcy5fYWN0aXZlSW5kZXgtdGhpcy5fbGltaXQpfV9wZXJmb3JtTmV4dFBhZ2UoKXt0aGlzLl9zZXRBY3RpdmVJbmRleCh0aGlzLl9hY3RpdmVJbmRleCt0aGlzLl9saW1pdCl9X2NvbXB1dGVQYWdlSW5wdXRWYWx1ZSh0LHIsbil7cmV0dXJuIHQ/cjpuLnRvU3RyaW5nKCl9X2hhbmRsZVBhZ2VJbnB1dEV2ZW50KHQpe3RoaXMuc2V0KCJfcGFnZUlucHV0UmF3VmFsdWUiLHQudGFyZ2V0LnZhbHVlKTtsZXQgcj1OdW1iZXIodC50YXJnZXQudmFsdWV8fE5hTik7aWYoaXNOYU4ocikpcmV0dXJuO2xldCBuPU1hdGgubWF4KDEsTWF0aC5taW4ocix0aGlzLl9wYWdlQ291bnQpKS0xO3RoaXMuX3NldEFjdGl2ZUluZGV4KHRoaXMuX2xpbWl0Km4pfV9oYW5kbGVQYWdlQ2hhbmdlRXZlbnQoKXt0aGlzLnNldCgiX3BhZ2VJbnB1dFJhd1ZhbHVlIix0aGlzLl9jdXJyZW50UGFnZS50b1N0cmluZygpKX1faGFuZGxlUGFnZUZvY3VzRXZlbnQoKXt0aGlzLnNldCgiX3BhZ2VJbnB1dFJhd1ZhbHVlIix0aGlzLl9wYWdlSW5wdXRWYWx1ZSksdGhpcy5zZXQoIl9wYWdlSW5wdXRGb2N1c2VkIiwhMCl9X2hhbmRsZVBhZ2VCbHVyRXZlbnQoKXt0aGlzLnNldCgiX3BhZ2VJbnB1dEZvY3VzZWQiLCExKX1fdXBkYXRlUGFnZUlucHV0VmFsdWUodCl7dmFyIG47bGV0IHI9KG49dGhpcy5zaGFkb3dSb290KT09bnVsbD92b2lkIDA6bi5xdWVyeVNlbGVjdG9yKCIjcGFnZS1pbnB1dCBpbnB1dCIpO3ImJihyLnZhbHVlPXQpfV91cGRhdGVJbnB1dFdpZHRoKCl7dGhpcy51cGRhdGVTdHlsZXMoeyItLXRmLWNhdGVnb3J5LXBhZ2luYXRlZC12aWV3LXBhZ2UtaW5wdXQtd2lkdGgiOnRoaXMuX2lucHV0V2lkdGh9KX19O2huLnRlbXBsYXRlPVFgCiAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX3BhbmVSZW5kZXJlZF1dIiBpZD0iaWZSZW5kZXJlZCI+CiAgICAgIDxidXR0b24gY2xhc3M9ImhlYWRpbmciIG9uLXRhcD0iX3RvZ2dsZVBhbmUiIG9wZW4tYnV0dG9uJD0iW1tvcGVuZWRdXSI+CiAgICAgICAgPHNwYW4gY2xhc3M9Im5hbWUiPgogICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19pc1NlYXJjaFJlc3VsdHNdXSI+CiAgICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfaXNDb21wb3NpdGVTZWFyY2goY2F0ZWdvcnkpXV0iPgogICAgICAgICAgICAgIDxzcGFuPlRhZ3MgbWF0Y2hpbmcgbXVsdGlwbGUgZXhwZXJpbWVudHM8L3NwYW4+CiAgICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19pc0ludmFsaWRTZWFyY2hSZXN1bHRzXV0iPgogICAgICAgICAgICAgICAgPHNwYW4KICAgICAgICAgICAgICAgICAgPiZuYnNwOzxzdHJvbmc+KG1hbGZvcm1lZCByZWd1bGFyIGV4cHJlc3Npb24pPC9zdHJvbmc+PC9zcGFuCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbWyFfaXNDb21wb3NpdGVTZWFyY2goY2F0ZWdvcnkpXV0iPgogICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsaWdodCI+VGFncyBtYXRjaGluZyAvPC9zcGFuPgogICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJjYXRlZ29yeS1uYW1lIiB0aXRsZSQ9IltbY2F0ZWdvcnkubmFtZV1dIgogICAgICAgICAgICAgICAgPltbY2F0ZWdvcnkubmFtZV1dPC9zcGFuCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsaWdodCI+Lzwvc3Bhbj4KICAgICAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2lzVW5pdmVyc2FsU2VhcmNoUXVlcnldXSI+CiAgICAgICAgICAgICAgICA8c3Bhbj4gKGFsbCB0YWdzKTwvc3Bhbj4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfaXNJbnZhbGlkU2VhcmNoUmVzdWx0c11dIj4KICAgICAgICAgICAgICAgIDxzcGFuPiA8c3Ryb25nPihtYWxmb3JtZWQgcmVndWxhciBleHByZXNzaW9uKTwvc3Ryb25nPjwvc3Bhbj4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1shX2lzU2VhcmNoUmVzdWx0c11dIj4KICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImNhdGVnb3J5LW5hbWUiIHRpdGxlJD0iW1tjYXRlZ29yeS5uYW1lXV0iCiAgICAgICAgICAgICAgPltbY2F0ZWdvcnkubmFtZV1dPC9zcGFuCiAgICAgICAgICAgID4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPC9zcGFuPgogICAgICAgIDxzcGFuIGNsYXNzPSJjb3VudCI+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2hhc011bHRpcGxlXV0iPgogICAgICAgICAgICA8c3Bhbj5bW19jb3VudF1dPC9zcGFuPgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDxpcm9uLWljb24gaWNvbj0iZXhwYW5kLW1vcmUiIGNsYXNzPSJleHBhbmQtYXJyb3ciPjwvaXJvbi1pY29uPgogICAgICAgIDwvc3Bhbj4KICAgICAgPC9idXR0b24+CiAgICAgIDwhLS0gVE9ETyhzdGVwaGFud2xlZSk6IGludmVzdGlnYXRlIGZ1cnRoZXIuIEZvciBzb21lIHJlYXNvbiwKICAgICAgICB0cmFuc2l0aW9uZW5kIHRoYXQgdGhlIGlyb24tY29sbGFwc2UgcmVsaWVzIG9uIHNvbWV0aW1lcyBkb2VzIG5vdAogICAgICAgIHRyaWdnZXIgd2hlbiByZW5kZXJpbmcgYSBjaGFydCB3aXRoIGEgc3Bpbm5lci4gQSB0b3kgZXhhbXBsZSBjYW5ub3QKICAgICAgICByZXByb2R1Y2UgdGhpcyBidWcuIC0tPgogICAgICA8aXJvbi1jb2xsYXBzZSBvcGVuZWQ9Iltbb3BlbmVkXV0iIG5vLWFuaW1hdGlvbj0iIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZW50Ij4KICAgICAgICAgIDxzcGFuIGlkPSJ0b3Atb2YtY29udGFpbmVyIj48L3NwYW4+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX211bHRpcGxlUGFnZXNFeGlzdF1dIj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0iYmlnLXBhZ2UtYnV0dG9ucyIgc3R5bGU9Im1hcmdpbi1ib3R0b206IDEwcHg7Ij4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uCiAgICAgICAgICAgICAgICBvbi10YXA9Il9wZXJmb3JtUHJldmlvdXNQYWdlIgogICAgICAgICAgICAgICAgZGlzYWJsZWQkPSJbWyFfaGFzUHJldmlvdXNQYWdlXV0iCiAgICAgICAgICAgICAgICA+UHJldmlvdXMgcGFnZTwvcGFwZXItYnV0dG9uCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIDxwYXBlci1idXR0b24KICAgICAgICAgICAgICAgIG9uLXRhcD0iX3BlcmZvcm1OZXh0UGFnZSIKICAgICAgICAgICAgICAgIGRpc2FibGVkJD0iW1shX2hhc05leHRQYWdlXV0iCiAgICAgICAgICAgICAgICA+TmV4dCBwYWdlPC9wYXBlci1idXR0b24KICAgICAgICAgICAgICA+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KCiAgICAgICAgICA8ZGl2IGlkPSJpdGVtcyI+CiAgICAgICAgICAgIDxzbG90IG5hbWU9Iml0ZW1zIj48L3Nsb3Q+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfbXVsdGlwbGVQYWdlc0V4aXN0XV0iPgogICAgICAgICAgICA8ZGl2IGlkPSJjb250cm9scy1jb250YWluZXIiPgogICAgICAgICAgICAgIDxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgcGFkZGluZzogMCA1cHgiPgogICAgICAgICAgICAgICAgUGFnZQogICAgICAgICAgICAgICAgPHBhcGVyLWlucHV0CiAgICAgICAgICAgICAgICAgIGlkPSJwYWdlLWlucHV0IgogICAgICAgICAgICAgICAgICB0eXBlPSJudW1iZXIiCiAgICAgICAgICAgICAgICAgIG5vLWxhYmVsLWZsb2F0PSIiCiAgICAgICAgICAgICAgICAgIG1pbj0iMSIKICAgICAgICAgICAgICAgICAgbWF4PSJbW19wYWdlQ291bnRdXSIKICAgICAgICAgICAgICAgICAgdmFsdWU9IltbX3BhZ2VJbnB1dFZhbHVlXV0iCiAgICAgICAgICAgICAgICAgIG9uLWlucHV0PSJfaGFuZGxlUGFnZUlucHV0RXZlbnQiCiAgICAgICAgICAgICAgICAgIG9uLWNoYW5nZT0iX2hhbmRsZVBhZ2VDaGFuZ2VFdmVudCIKICAgICAgICAgICAgICAgICAgb24tZm9jdXM9Il9oYW5kbGVQYWdlRm9jdXNFdmVudCIKICAgICAgICAgICAgICAgICAgb24tYmx1cj0iX2hhbmRsZVBhZ2VCbHVyRXZlbnQiCiAgICAgICAgICAgICAgICA+PC9wYXBlci1pbnB1dD4KICAgICAgICAgICAgICAgIG9mIFtbX3BhZ2VDb3VudF1dCiAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgPGRpdiBjbGFzcz0iYmlnLXBhZ2UtYnV0dG9ucyIgc3R5bGU9Im1hcmdpbi10b3A6IDEwcHg7Ij4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uCiAgICAgICAgICAgICAgICBvbi10YXA9Il9wZXJmb3JtUHJldmlvdXNQYWdlIgogICAgICAgICAgICAgICAgZGlzYWJsZWQkPSJbWyFfaGFzUHJldmlvdXNQYWdlXV0iCiAgICAgICAgICAgICAgICA+UHJldmlvdXMgcGFnZTwvcGFwZXItYnV0dG9uCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIDxwYXBlci1idXR0b24KICAgICAgICAgICAgICAgIG9uLXRhcD0iX3BlcmZvcm1OZXh0UGFnZSIKICAgICAgICAgICAgICAgIGRpc2FibGVkJD0iW1shX2hhc05leHRQYWdlXV0iCiAgICAgICAgICAgICAgICA+TmV4dCBwYWdlPC9wYXBlci1idXR0b24KICAgICAgICAgICAgICA+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L2Rpdj4KICAgICAgPC9pcm9uLWNvbGxhcHNlPgogICAgPC90ZW1wbGF0ZT4KICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgIG1hcmdpbjogMCA1cHggMXB4IDEwcHg7CiAgICAgIH0KCiAgICAgIDpob3N0KDpmaXJzdC1vZi10eXBlKSB7CiAgICAgICAgbWFyZ2luLXRvcDogMTBweDsKICAgICAgfQoKICAgICAgOmhvc3QoOmxhc3Qtb2YtdHlwZSkgewogICAgICAgIG1hcmdpbi1ib3R0b206IDIwcHg7CiAgICAgIH0KCiAgICAgIC5oZWFkaW5nIHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1wcmltYXJ5LWJhY2tncm91bmQtY29sb3IpOwogICAgICAgIGJvcmRlcjogbm9uZTsKICAgICAgICBjb2xvcjogaW5oZXJpdDsKICAgICAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgZm9udC1zaXplOiAxNXB4OwogICAgICAgIGxpbmUtaGVpZ2h0OiAxOwogICAgICAgIGJveC1zaGFkb3c6IDAgMXB4IDVweCB2YXIoLS10Yi1yYWlzZWQtYnV0dG9uLXNoYWRvdy1jb2xvcik7CiAgICAgICAgcGFkZGluZzogMTBweCAxNXB4OwogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgIH0KCiAgICAgIC5oZWFkaW5nOjotbW96LWZvY3VzLWlubmVyIHsKICAgICAgICBwYWRkaW5nOiAxMHB4IDE1cHg7CiAgICAgIH0KCiAgICAgIFtvcGVuLWJ1dHRvbl0gewogICAgICAgIGJvcmRlci1ib3R0b20tbGVmdC1yYWRpdXM6IDAgIWltcG9ydGFudDsKICAgICAgICBib3JkZXItYm90dG9tLXJpZ2h0LXJhZGl1czogMCAhaW1wb3J0YW50OwogICAgICB9CgogICAgICBbb3Blbi1idXR0b25dIC5leHBhbmQtYXJyb3cgewogICAgICAgIHRyYW5zZm9ybTogcm90YXRlWigxODBkZWcpOwogICAgICB9CgogICAgICAubmFtZSB7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7CiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgfQoKICAgICAgLmxpZ2h0IHsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItZ3JleS01MDApOwogICAgICB9CgogICAgICAuY2F0ZWdvcnktbmFtZSB7CiAgICAgICAgd2hpdGUtc3BhY2U6IHByZTsKICAgICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgICAgIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzOwogICAgICAgIHBhZGRpbmc6IDJweCAwOwogICAgICB9CgogICAgICAuY291bnQgewogICAgICAgIG1hcmdpbjogMCA1cHg7CiAgICAgICAgZm9udC1zaXplOiAxMnB4OwogICAgICAgIGNvbG9yOiB2YXIoLS1wYXBlci1ncmV5LTUwMCk7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICAgIGZsZXg6IG5vbmU7CiAgICAgIH0KCiAgICAgIC5oZWFkaW5nOjotbW96LWZvY3VzLWlubmVyIHsKICAgICAgICBwYWRkaW5nOiAxMHB4IDE1cHg7CiAgICAgIH0KCiAgICAgIC5jb250ZW50IHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tcHJpbWFyeS1iYWNrZ3JvdW5kLWNvbG9yKTsKICAgICAgICBib3JkZXItYm90dG9tLWxlZnQtcmFkaXVzOiAycHg7CiAgICAgICAgYm9yZGVyLWJvdHRvbS1yaWdodC1yYWRpdXM6IDJweDsKICAgICAgICBib3JkZXItdG9wOiBub25lOwogICAgICAgIGJvcmRlcjogMXB4IHNvbGlkICNkZWRlZGU7CiAgICAgICAgcGFkZGluZzogMTVweDsKICAgICAgfQoKICAgICAgLmxpZ2h0IHsKICAgICAgICBjb2xvcjogdmFyKC0tcGFwZXItZ3JleS01MDApOwogICAgICB9CgogICAgICAjY29udHJvbHMtY29udGFpbmVyIHsKICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiByb3c7CiAgICAgICAgZmxleC1ncm93OiAwOwogICAgICAgIGZsZXgtc2hyaW5rOiAwOwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICB9CgogICAgICAjY29udHJvbHMtY29udGFpbmVyIHBhcGVyLWJ1dHRvbiB7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICB9CgogICAgICAuYmlnLXBhZ2UtYnV0dG9ucyB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgfQoKICAgICAgLmJpZy1wYWdlLWJ1dHRvbnMgcGFwZXItYnV0dG9uIHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS10Yi11aS1saWdodC1hY2NlbnQpOwogICAgICAgIGNvbG9yOiB2YXIoLS10Yi11aS1kYXJrLWFjY2VudCk7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICAgIGZsZXgtYmFzaXM6IDA7CiAgICAgICAgZmxleC1ncm93OiAxOwogICAgICAgIGZsZXgtc2hyaW5rOiAxOwogICAgICAgIGZvbnQtc2l6ZTogMTNweDsKICAgICAgfQoKICAgICAgLmJpZy1wYWdlLWJ1dHRvbnMgcGFwZXItYnV0dG9uW2Rpc2FibGVkXSB7CiAgICAgICAgYmFja2dyb3VuZDogbm9uZTsKICAgICAgfQoKICAgICAgc2xvdCB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogcm93OwogICAgICAgIGZsZXgtd3JhcDogd3JhcDsKICAgICAgfQoKICAgICAgOjpzbG90dGVkKFtzbG90PSdpdGVtcyddKSB7CiAgICAgICAgLyogVG9vbHRpcCBmb3IgZGVzY3JpcHRpb25zIGFuZCBvdGhlcnMgYnJlYWsgd2l0aCBtb3JlIHN0cmljdCBvbmVzLiAqLwogICAgICAgIGNvbnRhaW46IHN0eWxlOwogICAgICB9CgogICAgICAjcGFnZS1pbnB1dCB7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICAgIHdpZHRoOiB2YXIoLS10Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldy1wYWdlLWlucHV0LXdpZHRoLCAxMDAlKTsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGhuLnByb3RvdHlwZSwiY2F0ZWdvcnkiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxobi5wcm90b3R5cGUsImluaXRpYWxPcGVuZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0saG4ucHJvdG90eXBlLCJvcGVuZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxobi5wcm90b3R5cGUsImRpc2FibGVQYWdpbmF0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyLGNvbXB1dGVkOiJfY29tcHV0ZUNvdW50KGNhdGVnb3J5Lml0ZW1zLiopIn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxobi5wcm90b3R5cGUsIl9jb3VudCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4sY29tcHV0ZWQ6Il9jb21wdXRlUGFuZVJlbmRlcmVkKGNhdGVnb3J5KSIsb2JzZXJ2ZXI6Il9vblBhbmVSZW5kZXJlZENoYW5nZWQifSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxobi5wcm90b3R5cGUsIl9wYW5lUmVuZGVyZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLGNvbXB1dGVkOiJfY29tcHV0ZUlzU2VhcmNoUmVzdWx0cyhjYXRlZ29yeS5tZXRhZGF0YS50eXBlKSJ9KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGhuLnByb3RvdHlwZSwiX2lzU2VhcmNoUmVzdWx0cyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4sY29tcHV0ZWQ6Il9jb21wdXRlSXNJbnZhbGlkU2VhcmNoUmVzdWx0cyhjYXRlZ29yeS5tZXRhZGF0YSkifSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxobi5wcm90b3R5cGUsIl9pc0ludmFsaWRTZWFyY2hSZXN1bHRzIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbixjb21wdXRlZDoiX2NvbXB1dGVJc1VuaXZlcnNhbFNlYXJjaFF1ZXJ5KGNhdGVnb3J5Lm1ldGFkYXRhKSJ9KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGhuLnByb3RvdHlwZSwiX2lzVW5pdmVyc2FsU2VhcmNoUXVlcnkiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3Qsb2JzZXJ2ZXI6Il9nZXRDYXRlZ29yeUl0ZW1LZXlDaGFuZ2VkIn0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxobi5wcm90b3R5cGUsImdldENhdGVnb3J5SXRlbUtleSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcixvYnNlcnZlcjoiX2xpbWl0Q2hhbmdlZCJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0saG4ucHJvdG90eXBlLCJfbGltaXQiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0saG4ucHJvdG90eXBlLCJfYWN0aXZlSW5kZXgiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXIsY29tcHV0ZWQ6Il9jb21wdXRlUGFnZUNvdW50KGNhdGVnb3J5Lml0ZW1zLiosIF9saW1pdCkifSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLGhuLnByb3RvdHlwZSwiX3BhZ2VDb3VudCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZyxjb21wdXRlZDoiX2NvbXB1dGVJbnB1dFdpZHRoKF9wYWdlQ291bnQpIixvYnNlcnZlcjoiX3VwZGF0ZUlucHV0V2lkdGgifSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLGhuLnByb3RvdHlwZSwiX2lucHV0V2lkdGgiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmcsY29tcHV0ZWQ6Il9jb21wdXRlUGFnZUlucHV0VmFsdWUoX3BhZ2VJbnB1dEZvY3VzZWQsIF9wYWdlSW5wdXRSYXdWYWx1ZSwgX2N1cnJlbnRQYWdlKSIsb2JzZXJ2ZXI6Il91cGRhdGVQYWdlSW5wdXRWYWx1ZSJ9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0saG4ucHJvdG90eXBlLCJfcGFnZUlucHV0VmFsdWUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0saG4ucHJvdG90eXBlLCJfcGFnZUlucHV0UmF3VmFsdWUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxobi5wcm90b3R5cGUsIl9wYWdlSW5wdXRGb2N1c2VkIix2b2lkIDApO0UoW1J0KCJfY291bnQiKSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0saG4ucHJvdG90eXBlLCJfaGFzTXVsdGlwbGUiLG51bGwpO0UoW0J0KCJvcGVuZWQiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbQm9vbGVhbl0pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxobi5wcm90b3R5cGUsIl9jaGFuZ2VDb250ZW50QWN0aXZlIixudWxsKTtFKFtSdCgib3BlbmVkIiwiX3BhbmVSZW5kZXJlZCIpLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxobi5wcm90b3R5cGUsIl9pdGVtc1JlbmRlcmVkIixudWxsKTtFKFtCdCgiX2l0ZW1zUmVuZGVyZWQiLCJjYXRlZ29yeS5pdGVtcy4qIiwiX2xpbWl0IiwiX2FjdGl2ZUluZGV4IiwiX3BhZ2VDb3VudCIsImRpc2FibGVQYWdpbmF0aW9uIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxobi5wcm90b3R5cGUsIl91cGRhdGVSZW5kZXJlZEl0ZW1zIixudWxsKTtFKFtSdCgiX2xpbWl0IiwiX2FjdGl2ZUluZGV4IiksdygiZGVzaWduOnR5cGUiLE51bWJlciksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0saG4ucHJvdG90eXBlLCJfY3VycmVudFBhZ2UiLG51bGwpO0UoW1J0KCJfcGFnZUNvdW50IiwiZGlzYWJsZVBhZ2luYXRpb24iKSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0saG4ucHJvdG90eXBlLCJfbXVsdGlwbGVQYWdlc0V4aXN0IixudWxsKTtFKFtSdCgiX2N1cnJlbnRQYWdlIiksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLGhuLnByb3RvdHlwZSwiX2hhc1ByZXZpb3VzUGFnZSIsbnVsbCk7RShbUnQoIl9jdXJyZW50UGFnZSIsIl9wYWdlQ291bnQiKSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0saG4ucHJvdG90eXBlLCJfaGFzTmV4dFBhZ2UiLG51bGwpO0UoW0J0KCJjYXRlZ29yeS5pdGVtcy4qIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxobi5wcm90b3R5cGUsIl9jbGFtcEFjdGl2ZUluZGV4IixudWxsKTtobj1FKFt5dCgidGYtY2F0ZWdvcnktcGFnaW5hdGVkLXZpZXciKV0saG4pO3ZhciBIMHQ9RWUoT2UoKSwxKTt2YXIgQjk9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5saXN0ZW5lcj10fX0sYnA9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnJlcXVlc3RNYW5hZ2VyPW5ldyBBZSgxKSx0aGlzLl9saXN0ZW5lcnM9bmV3IFNldCx0aGlzLmluaXRpYWxpemVkPSExfXJlZnJlc2goKXtyZXR1cm4gdGhpcy5sb2FkKCkudGhlbigoKT0+e3RoaXMuaW5pdGlhbGl6ZWQ9ITB9KX1hZGRMaXN0ZW5lcih0KXtsZXQgcj1uZXcgQjkodCk7cmV0dXJuIHRoaXMuX2xpc3RlbmVycy5hZGQocikscn1yZW1vdmVMaXN0ZW5lckJ5S2V5KHQpe3RoaXMuX2xpc3RlbmVycy5kZWxldGUodCl9ZW1pdENoYW5nZSgpe3RoaXMuX2xpc3RlbmVycy5mb3JFYWNoKHQ9Pnt0cnl7dC5saXN0ZW5lcigpfWNhdGNoKHIpe319KX19O3ZhciBIOT1jbGFzcyBleHRlbmRzIGJwe2xvYWQoKXtsZXQgdD12ZSgpLmVudmlyb25tZW50KCk7cmV0dXJuIHRoaXMucmVxdWVzdE1hbmFnZXIucmVxdWVzdCh0KS50aGVuKHI9PntsZXQgbj17ZGF0YUxvY2F0aW9uOnIuZGF0YV9sb2NhdGlvbix3aW5kb3dUaXRsZTpyLndpbmRvd190aXRsZX07ci5leHBlcmltZW50X25hbWUhPT12b2lkIDAmJihuLmV4cGVyaW1lbnROYW1lPXIuZXhwZXJpbWVudF9uYW1lKSxyLmV4cGVyaW1lbnRfZGVzY3JpcHRpb24hPT12b2lkIDAmJihuLmV4cGVyaW1lbnREZXNjcmlwdGlvbj1yLmV4cGVyaW1lbnRfZGVzY3JpcHRpb24pLHIuY3JlYXRpb25fdGltZSE9PXZvaWQgMCYmKG4uY3JlYXRpb25UaW1lPXIuY3JlYXRpb25fdGltZSksIUgwdC5pc0VxdWFsKHRoaXMuZW52aXJvbm1lbnQsbikmJih0aGlzLmVudmlyb25tZW50PW4sdGhpcy5lbWl0Q2hhbmdlKCkpfSl9Z2V0RGF0YUxvY2F0aW9uKCl7cmV0dXJuIHRoaXMuZW52aXJvbm1lbnQ/dGhpcy5lbnZpcm9ubWVudC5kYXRhTG9jYXRpb246IiJ9Z2V0V2luZG93VGl0bGUoKXtyZXR1cm4gdGhpcy5lbnZpcm9ubWVudD90aGlzLmVudmlyb25tZW50LndpbmRvd1RpdGxlOiIifWdldEV4cGVyaW1lbnROYW1lKCl7cmV0dXJuIHRoaXMuZW52aXJvbm1lbnQ/dGhpcy5lbnZpcm9ubWVudC5leHBlcmltZW50TmFtZToiIn1nZXRFeHBlcmltZW50RGVzY3JpcHRpb24oKXtyZXR1cm4gdGhpcy5lbnZpcm9ubWVudD90aGlzLmVudmlyb25tZW50LmV4cGVyaW1lbnREZXNjcmlwdGlvbjoiIn1nZXRDcmVhdGlvblRpbWUoKXtyZXR1cm4gdGhpcy5lbnZpcm9ubWVudD90aGlzLmVudmlyb25tZW50LmNyZWF0aW9uVGltZTpudWxsfX0saWI9bmV3IEg5O3ZhciBWMHQ9RWUoT2UoKSwxKTt2YXIgVjk9Y2xhc3MgZXh0ZW5kcyBicHtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fcnVucz1bXX1sb2FkKCl7bGV0IHQ9dmUoKS5ydW5zKCk7cmV0dXJuIHRoaXMucmVxdWVzdE1hbmFnZXIucmVxdWVzdCh0KS50aGVuKHI9PntWMHQuaXNFcXVhbCh0aGlzLl9ydW5zLHIpfHwodGhpcy5fcnVucz1yLHRoaXMuZW1pdENoYW5nZSgpKX0pfWdldFJ1bnMoKXtyZXR1cm4gdGhpcy5fcnVucy5zbGljZSgpfX0sd3A9bmV3IFY5O3ZhciBWcj17fTtLcyhWcix7Rm9ybWF0U3BlY2lmaWVyOigpPT5xRSxhY3RpdmU6KCk9Pk8xdCxhcmM6KCk9Pk5TdCxhcmVhOigpPT5POCxhcmVhUmFkaWFsOigpPT5hJCxhc2NlbmRpbmc6KCk9Pm9hLGF1dG9UeXBlOigpPT5vaixheGlzQm90dG9tOigpPT5LOSxheGlzTGVmdDooKT0+bGIsYXhpc1JpZ2h0OigpPT51X3QsYXhpc1RvcDooKT0+Y190LGJpc2VjdDooKT0+eXMsYmlzZWN0TGVmdDooKT0+RzB0LGJpc2VjdFJpZ2h0OigpPT5ZVyxiaXNlY3RvcjooKT0+b2IsYmxvYjooKT0+SXZ0LGJydXNoOigpPT5xTCxicnVzaFNlbGVjdGlvbjooKT0+VkwsYnJ1c2hYOigpPT5VMXQsYnJ1c2hZOigpPT5VTCxidWZmZXI6KCk9Pkx2dCxjaG9yZDooKT0+RzF0LGNsaWVudFBvaW50OigpPT5EbSxjbHVzdGVyOigpPT5IYnQsY29sb3I6KCk9PnJjLGNvbnRvdXJEZW5zaXR5OigpPT5odnQsY29udG91cnM6KCk9PktMLGNyZWF0ZTooKT0+cnl0LGNyZWF0b3I6KCk9PlJtLGNyb3NzOigpPT5VOSxjc3Y6KCk9PlJ2dCxjc3ZGb3JtYXQ6KCk9Pmd2dCxjc3ZGb3JtYXRCb2R5OigpPT5fdnQsY3N2Rm9ybWF0Um93OigpPT52dnQsY3N2Rm9ybWF0Um93czooKT0+eXZ0LGNzdkZvcm1hdFZhbHVlOigpPT54dnQsY3N2UGFyc2U6KCk9PkNiLGNzdlBhcnNlUm93czooKT0+bXZ0LGN1YmVoZWxpeDooKT0+bGEsY3VydmVCYXNpczooKT0+RzgsY3VydmVCYXNpc0Nsb3NlZDooKT0+WFN0LGN1cnZlQmFzaXNPcGVuOigpPT5LU3QsY3VydmVCdW5kbGU6KCk9PkpTdCxjdXJ2ZUNhcmRpbmFsOigpPT5RU3QsY3VydmVDYXJkaW5hbENsb3NlZDooKT0+dDN0LGN1cnZlQ2FyZGluYWxPcGVuOigpPT5lM3QsY3VydmVDYXRtdWxsUm9tOigpPT5uM3QsY3VydmVDYXRtdWxsUm9tQ2xvc2VkOigpPT5vM3QsY3VydmVDYXRtdWxsUm9tT3BlbjooKT0+czN0LGN1cnZlTGluZWFyOigpPT5ZaCxjdXJ2ZUxpbmVhckNsb3NlZDooKT0+YzN0LGN1cnZlTW9ub3RvbmVYOigpPT5tM3QsY3VydmVNb25vdG9uZVk6KCk9PmczdCxjdXJ2ZU5hdHVyYWw6KCk9PnYzdCxjdXJ2ZVN0ZXA6KCk9PngzdCxjdXJ2ZVN0ZXBBZnRlcjooKT0+dzN0LGN1cnZlU3RlcEJlZm9yZTooKT0+YjN0LGN1c3RvbUV2ZW50OigpPT5NcCxkZXNjZW5kaW5nOigpPT5ZMHQsZGV2aWF0aW9uOigpPT5HOSxkaXNwYXRjaDooKT0+dnMsZHJhZzooKT0+cGIsZHJhZ0Rpc2FibGU6KCk9PnptLGRyYWdFbmFibGU6KCk9PkZtLGRzdjooKT0+YWosZHN2Rm9ybWF0OigpPT5XbSxlYXNlQmFjazooKT0+REwsZWFzZUJhY2tJbjooKT0+elksZWFzZUJhY2tJbk91dDooKT0+REwsZWFzZUJhY2tPdXQ6KCk9PkZZLGVhc2VCb3VuY2U6KCk9PlBfLGVhc2VCb3VuY2VJbjooKT0+TlksZWFzZUJvdW5jZUluT3V0OigpPT5EWSxlYXNlQm91bmNlT3V0OigpPT5QXyxlYXNlQ2lyY2xlOigpPT5STCxlYXNlQ2lyY2xlSW46KCk9PkxZLGVhc2VDaXJjbGVJbk91dDooKT0+UkwsZWFzZUNpcmNsZU91dDooKT0+a1ksZWFzZUN1YmljOigpPT54cyxlYXNlQ3ViaWNJbjooKT0+YlksZWFzZUN1YmljSW5PdXQ6KCk9PnhzLGVhc2VDdWJpY091dDooKT0+d1ksZWFzZUVsYXN0aWM6KCk9Pk9MLGVhc2VFbGFzdGljSW46KCk9PlZZLGVhc2VFbGFzdGljSW5PdXQ6KCk9PlVZLGVhc2VFbGFzdGljT3V0OigpPT5PTCxlYXNlRXhwOigpPT5rTCxlYXNlRXhwSW46KCk9PlBZLGVhc2VFeHBJbk91dDooKT0+a0wsZWFzZUV4cE91dDooKT0+SVksZWFzZUxpbmVhcjooKT0+eVksZWFzZVBvbHk6KCk9PklMLGVhc2VQb2x5SW46KCk9Pk1ZLGVhc2VQb2x5SW5PdXQ6KCk9PklMLGVhc2VQb2x5T3V0OigpPT5FWSxlYXNlUXVhZDooKT0+UEwsZWFzZVF1YWRJbjooKT0+dlksZWFzZVF1YWRJbk91dDooKT0+UEwsZWFzZVF1YWRPdXQ6KCk9PnhZLGVhc2VTaW46KCk9PkxMLGVhc2VTaW5JbjooKT0+VFksZWFzZVNpbkluT3V0OigpPT5MTCxlYXNlU2luT3V0OigpPT5DWSxlbnRyaWVzOigpPT5vdnQsZXZlbnQ6KCk9PnF0LGV4dGVudDooKT0+YWEsZm9yY2VDZW50ZXI6KCk9Pkh2dCxmb3JjZUNvbGxpZGU6KCk9Pm94dCxmb3JjZUxpbms6KCk9PnN4dCxmb3JjZU1hbnlCb2R5OigpPT5oeHQsZm9yY2VSYWRpYWw6KCk9PmZ4dCxmb3JjZVNpbXVsYXRpb246KCk9PnV4dCxmb3JjZVg6KCk9PnB4dCxmb3JjZVk6KCk9PmR4dCxmb3JtYXQ6KCk9PnhuLGZvcm1hdERlZmF1bHRMb2NhbGU6KCk9PnJrLGZvcm1hdExvY2FsZTooKT0+dGssZm9ybWF0UHJlZml4OigpPT5HRSxmb3JtYXRTcGVjaWZpZXI6KCk9PkxwLGdlb0FsYmVyczooKT0+VmssZ2VvQWxiZXJzVXNhOigpPT5UYnQsZ2VvQXJlYTooKT0+UHh0LGdlb0F6aW11dGhhbEVxdWFsQXJlYTooKT0+Q2J0LGdlb0F6aW11dGhhbEVxdWFsQXJlYVJhdzooKT0+cWssZ2VvQXppbXV0aGFsRXF1aWRpc3RhbnQ6KCk9PkFidCxnZW9BemltdXRoYWxFcXVpZGlzdGFudFJhdzooKT0+R2ssZ2VvQm91bmRzOigpPT56eHQsZ2VvQ2VudHJvaWQ6KCk9PnF4dCxnZW9DaXJjbGU6KCk9Plh4dCxnZW9DbGlwQW50aW1lcmlkaWFuOigpPT50NSxnZW9DbGlwQ2lyY2xlOigpPT5DayxnZW9DbGlwRXh0ZW50OigpPT5aeHQsZ2VvQ2xpcFJlY3RhbmdsZTooKT0+RHAsZ2VvQ29uaWNDb25mb3JtYWw6KCk9PklidCxnZW9Db25pY0NvbmZvcm1hbFJhdzooKT0+WGosZ2VvQ29uaWNFcXVhbEFyZWE6KCk9PldfLGdlb0NvbmljRXF1YWxBcmVhUmF3OigpPT5ZaixnZW9Db25pY0VxdWlkaXN0YW50OigpPT5rYnQsZ2VvQ29uaWNFcXVpZGlzdGFudFJhdzooKT0+JGosZ2VvQ29udGFpbnM6KCk9PmlidCxnZW9EaXN0YW5jZTooKT0+TmIsZ2VvRXF1YWxFYXJ0aDooKT0+UmJ0LGdlb0VxdWFsRWFydGhSYXc6KCk9PmprLGdlb0VxdWlyZWN0YW5ndWxhcjooKT0+TGJ0LGdlb0VxdWlyZWN0YW5ndWxhclJhdzooKT0+al8sZ2VvR25vbW9uaWM6KCk9Pk5idCxnZW9Hbm9tb25pY1JhdzooKT0+WGssZ2VvR3JhdGljdWxlOigpPT5SayxnZW9HcmF0aWN1bGUxMDooKT0+c2J0LGdlb0lkZW50aXR5OigpPT5EYnQsZ2VvSW50ZXJwb2xhdGU6KCk9PmxidCxnZW9MZW5ndGg6KCk9PkxrLGdlb01lcmNhdG9yOigpPT5QYnQsZ2VvTWVyY2F0b3JSYXc6KCk9PllfLGdlb05hdHVyYWxFYXJ0aDE6KCk9Pk9idCxnZW9OYXR1cmFsRWFydGgxUmF3OigpPT4kayxnZW9PcnRob2dyYXBoaWM6KCk9PnpidCxnZW9PcnRob2dyYXBoaWNSYXc6KCk9PktrLGdlb1BhdGg6KCk9PmJidCxnZW9Qcm9qZWN0aW9uOigpPT5NaSxnZW9Qcm9qZWN0aW9uTXV0YXRvcjooKT0+czUsZ2VvUm90YXRpb246KCk9PmJrLGdlb1N0ZXJlb2dyYXBoaWM6KCk9PkZidCxnZW9TdGVyZW9ncmFwaGljUmF3OigpPT5aayxnZW9TdHJlYW06KCk9PnZvLGdlb1RyYW5zZm9ybTooKT0+d2J0LGdlb1RyYW5zdmVyc2VNZXJjYXRvcjooKT0+QmJ0LGdlb1RyYW5zdmVyc2VNZXJjYXRvclJhdzooKT0+SmssZ3JheTooKT0+eXl0LGhjbDooKT0+Z2IsaGllcmFyY2h5OigpPT5mNSxoaXN0b2dyYW06KCk9PlowdCxoc2w6KCk9PlZtLGh0bWw6KCk9PkZ2dCxpbWFnZTooKT0+RHZ0LGludGVycG9sYXRlOigpPT5uYyxpbnRlcnBvbGF0ZUFycmF5OigpPT5JeXQsaW50ZXJwb2xhdGVCYXNpczooKT0+c0wsaW50ZXJwb2xhdGVCYXNpc0Nsb3NlZDooKT0+bEwsaW50ZXJwb2xhdGVCbHVlczooKT0+bVN0LGludGVycG9sYXRlQnJCRzooKT0+WHd0LGludGVycG9sYXRlQnVHbjooKT0+blN0LGludGVycG9sYXRlQnVQdTooKT0+aVN0LGludGVycG9sYXRlQ2l2aWRpczooKT0+YlN0LGludGVycG9sYXRlQ29vbDooKT0+TVN0LGludGVycG9sYXRlQ3ViZWhlbGl4OigpPT5qeXQsaW50ZXJwb2xhdGVDdWJlaGVsaXhEZWZhdWx0OigpPT53U3QsaW50ZXJwb2xhdGVDdWJlaGVsaXhMb25nOigpPT5FXyxpbnRlcnBvbGF0ZURhdGU6KCk9PmhMLGludGVycG9sYXRlRGlzY3JldGU6KCk9Pkx5dCxpbnRlcnBvbGF0ZUduQnU6KCk9Pm9TdCxpbnRlcnBvbGF0ZUdyZWVuczooKT0+Z1N0LGludGVycG9sYXRlR3JleXM6KCk9Pl9TdCxpbnRlcnBvbGF0ZUhjbDooKT0+R3l0LGludGVycG9sYXRlSGNsTG9uZzooKT0+V3l0LGludGVycG9sYXRlSHNsOigpPT5WeXQsaW50ZXJwb2xhdGVIc2xMb25nOigpPT5VeXQsaW50ZXJwb2xhdGVIdWU6KCk9Pmt5dCxpbnRlcnBvbGF0ZUluZmVybm86KCk9PklTdCxpbnRlcnBvbGF0ZUxhYjooKT0+TV8saW50ZXJwb2xhdGVNYWdtYTooKT0+UFN0LGludGVycG9sYXRlTnVtYmVyOigpPT56aSxpbnRlcnBvbGF0ZU51bWJlckFycmF5OigpPT55YixpbnRlcnBvbGF0ZU9iamVjdDooKT0+ZkwsaW50ZXJwb2xhdGVPclJkOigpPT5hU3QsaW50ZXJwb2xhdGVPcmFuZ2VzOigpPT54U3QsaW50ZXJwb2xhdGVQUkduOigpPT4kd3QsaW50ZXJwb2xhdGVQaVlHOigpPT5Ld3QsaW50ZXJwb2xhdGVQbGFzbWE6KCk9PkxTdCxpbnRlcnBvbGF0ZVB1QnU6KCk9PmxTdCxpbnRlcnBvbGF0ZVB1QnVHbjooKT0+c1N0LGludGVycG9sYXRlUHVPcjooKT0+Wnd0LGludGVycG9sYXRlUHVSZDooKT0+Y1N0LGludGVycG9sYXRlUHVycGxlczooKT0+eVN0LGludGVycG9sYXRlUmFpbmJvdzooKT0+RVN0LGludGVycG9sYXRlUmRCdTooKT0+Snd0LGludGVycG9sYXRlUmRHeTooKT0+UXd0LGludGVycG9sYXRlUmRQdTooKT0+dVN0LGludGVycG9sYXRlUmRZbEJ1OigpPT50U3QsaW50ZXJwb2xhdGVSZFlsR246KCk9PmVTdCxpbnRlcnBvbGF0ZVJlZHM6KCk9PnZTdCxpbnRlcnBvbGF0ZVJnYjooKT0+cW0saW50ZXJwb2xhdGVSZ2JCYXNpczooKT0+Y0wsaW50ZXJwb2xhdGVSZ2JCYXNpc0Nsb3NlZDooKT0+UHl0LGludGVycG9sYXRlUm91bmQ6KCk9PnBMLGludGVycG9sYXRlU2luZWJvdzooKT0+VFN0LGludGVycG9sYXRlU3BlY3RyYWw6KCk9PnJTdCxpbnRlcnBvbGF0ZVN0cmluZzooKT0+dmIsaW50ZXJwb2xhdGVUcmFuc2Zvcm1Dc3M6KCk9PmdMLGludGVycG9sYXRlVHJhbnNmb3JtU3ZnOigpPT5fTCxpbnRlcnBvbGF0ZVR1cmJvOigpPT5DU3QsaW50ZXJwb2xhdGVWaXJpZGlzOigpPT5BU3QsaW50ZXJwb2xhdGVXYXJtOigpPT5TU3QsaW50ZXJwb2xhdGVZbEduOigpPT5mU3QsaW50ZXJwb2xhdGVZbEduQnU6KCk9PmhTdCxpbnRlcnBvbGF0ZVlsT3JCcjooKT0+cFN0LGludGVycG9sYXRlWWxPclJkOigpPT5kU3QsaW50ZXJwb2xhdGVab29tOigpPT55TCxpbnRlcnJ1cHQ6KCk9Pmh1LGludGVydmFsOigpPT5KeXQsaXNvRm9ybWF0OigpPT5Sd3QsaXNvUGFyc2U6KCk9Pk53dCxqc29uOigpPT5PdnQsa2V5czooKT0+WEwsbGFiOigpPT53XyxsY2g6KCk9Pnh5dCxsaW5lOigpPT52dSxsaW5lUmFkaWFsOigpPT5vJCxsaW5rSG9yaXpvbnRhbDooKT0+SFN0LGxpbmtSYWRpYWw6KCk9PlVTdCxsaW5rVmVydGljYWw6KCk9PlZTdCxsb2NhbDooKT0+dEwsbWFwOigpPT5KaSxtYXRjaGVyOigpPT51YixtYXg6KCk9Pmx1LG1lYW46KCk9PnRfdCxtZWRpYW46KCk9PmVfdCxtZXJnZTooKT0+SW0sbWluOigpPT5MbSxtb3VzZTooKT0+em8sbmFtZXNwYWNlOigpPT5QaCxuYW1lc3BhY2VzOigpPT53RSxuZXN0OigpPT5aMXQsbm93OigpPT5BcCxwYWNrOigpPT5jMnQscGFja0VuY2xvc2U6KCk9PnQ4LHBhY2tTaWJsaW5nczooKT0+bzJ0LHBhaXJzOigpPT5XMHQscGFydGl0aW9uOigpPT51MnQscGF0aDooKT0+YnMscGVybXV0ZTooKT0+cl90LHBpZTooKT0+RlN0LHBpZWNld2lzZTooKT0+bVkscG9pbnRSYWRpYWw6KCk9Pmx5LHBvbHlnb25BcmVhOigpPT52MnQscG9seWdvbkNlbnRyb2lkOigpPT54MnQscG9seWdvbkNvbnRhaW5zOigpPT5NMnQscG9seWdvbkh1bGw6KCk9PlMydCxwb2x5Z29uTGVuZ3RoOigpPT5FMnQscHJlY2lzaW9uRml4ZWQ6KCk9Pm5rLHByZWNpc2lvblByZWZpeDooKT0+aWsscHJlY2lzaW9uUm91bmQ6KCk9Pm9rLHF1YWR0cmVlOigpPT56aCxxdWFudGlsZTooKT0+c2EscXVhbnRpemU6KCk9Plh5dCxyYWRpYWxBcmVhOigpPT5hJCxyYWRpYWxMaW5lOigpPT5vJCxyYW5kb21CYXRlczooKT0+QTJ0LHJhbmRvbUV4cG9uZW50aWFsOigpPT5QMnQscmFuZG9tSXJ3aW5IYWxsOigpPT5hOCxyYW5kb21Mb2dOb3JtYWw6KCk9PkMydCxyYW5kb21Ob3JtYWw6KCk9Pm84LHJhbmRvbVVuaWZvcm06KCk9PlQydCxyYW5nZTooKT0+SXIscmdiOigpPT5jdSxyaWJib246KCk9PmoxdCxzY2FsZUJhbmQ6KCk9PlFtLHNjYWxlRGl2ZXJnaW5nOigpPT5QOCxzY2FsZURpdmVyZ2luZ0xvZzooKT0+QVgsc2NhbGVEaXZlcmdpbmdQb3c6KCk9Pkk4LHNjYWxlRGl2ZXJnaW5nU3FydDooKT0+Rnd0LHNjYWxlRGl2ZXJnaW5nU3ltbG9nOigpPT5QWCxzY2FsZUlkZW50aXR5OigpPT5jOCxzY2FsZUltcGxpY2l0OigpPT5zOCxzY2FsZUxpbmVhcjooKT0+em4sc2NhbGVMb2c6KCk9PmNjLHNjYWxlT3JkaW5hbDooKT0+Z3Usc2NhbGVQb2ludDooKT0+dGcsc2NhbGVQb3c6KCk9PktfLHNjYWxlUXVhbnRpbGU6KCk9PmVnLHNjYWxlUXVhbnRpemU6KCk9PnFiLHNjYWxlU2VxdWVudGlhbDooKT0+RTgsc2NhbGVTZXF1ZW50aWFsTG9nOigpPT5UWCxzY2FsZVNlcXVlbnRpYWxQb3c6KCk9PlQ4LHNjYWxlU2VxdWVudGlhbFF1YW50aWxlOigpPT5DOCxzY2FsZVNlcXVlbnRpYWxTcXJ0OigpPT56d3Qsc2NhbGVTZXF1ZW50aWFsU3ltbG9nOigpPT5DWCxzY2FsZVNxcnQ6KCk9PlYydCxzY2FsZVN5bWxvZzooKT0+aDgsc2NhbGVUaHJlc2hvbGQ6KCk9PmY4LHNjYWxlVGltZTooKT0+WWIsc2NhbGVVdGM6KCk9Pk93dCxzY2FuOigpPT5uX3Qsc2NoZW1lQWNjZW50OigpPT5Cd3Qsc2NoZW1lQmx1ZXM6KCk9PlpYLHNjaGVtZUJyQkc6KCk9PklYLHNjaGVtZUJ1R246KCk9PkJYLHNjaGVtZUJ1UHU6KCk9PkhYLHNjaGVtZUNhdGVnb3J5MTA6KCk9PmpiLHNjaGVtZURhcmsyOigpPT5Id3Qsc2NoZW1lR25CdTooKT0+Vlgsc2NoZW1lR3JlZW5zOigpPT5KWCxzY2hlbWVHcmV5czooKT0+UVgsc2NoZW1lT3JSZDooKT0+VVgsc2NoZW1lT3JhbmdlczooKT0+ciQsc2NoZW1lUFJHbjooKT0+TFgsc2NoZW1lUGFpcmVkOigpPT5Wd3Qsc2NoZW1lUGFzdGVsMTooKT0+VXd0LHNjaGVtZVBhc3RlbDI6KCk9PnF3dCxzY2hlbWVQaVlHOigpPT5rWCxzY2hlbWVQdUJ1OigpPT5HWCxzY2hlbWVQdUJ1R246KCk9PnFYLHNjaGVtZVB1T3I6KCk9PlJYLHNjaGVtZVB1UmQ6KCk9PldYLHNjaGVtZVB1cnBsZXM6KCk9PnQkLHNjaGVtZVJkQnU6KCk9Pk5YLHNjaGVtZVJkR3k6KCk9PkRYLHNjaGVtZVJkUHU6KCk9PllYLHNjaGVtZVJkWWxCdTooKT0+T1gsc2NoZW1lUmRZbEduOigpPT56WCxzY2hlbWVSZWRzOigpPT5lJCxzY2hlbWVTZXQxOigpPT5Hd3Qsc2NoZW1lU2V0MjooKT0+V3d0LHNjaGVtZVNldDM6KCk9Pll3dCxzY2hlbWVTcGVjdHJhbDooKT0+Rlgsc2NoZW1lVGFibGVhdTEwOigpPT5qd3Qsc2NoZW1lWWxHbjooKT0+WFgsc2NoZW1lWWxHbkJ1OigpPT5qWCxzY2hlbWVZbE9yQnI6KCk9PiRYLHNjaGVtZVlsT3JSZDooKT0+S1gsc2VsZWN0OigpPT5IdCxzZWxlY3RBbGw6KCk9PkVwLHNlbGVjdGlvbjooKT0+SWgsc2VsZWN0b3I6KCk9Pk5tLHNlbGVjdG9yQWxsOigpPT5jYixzZXQ6KCk9PnR2dCxzaHVmZmxlOigpPT5pX3Qsc3RhY2s6KCk9PlMzdCxzdGFja09mZnNldERpdmVyZ2luZzooKT0+RTN0LHN0YWNrT2Zmc2V0RXhwYW5kOigpPT5NM3Qsc3RhY2tPZmZzZXROb25lOigpPT54dSxzdGFja09mZnNldFNpbGhvdWV0dGU6KCk9PlQzdCxzdGFja09mZnNldFdpZ2dsZTooKT0+QzN0LHN0YWNrT3JkZXJBcHBlYXJhbmNlOigpPT5LOCxzdGFja09yZGVyQXNjZW5kaW5nOigpPT5aOCxzdGFja09yZGVyRGVzY2VuZGluZzooKT0+QTN0LHN0YWNrT3JkZXJJbnNpZGVPdXQ6KCk9PlAzdCxzdGFja09yZGVyTm9uZTooKT0+YnUsc3RhY2tPcmRlclJldmVyc2U6KCk9PkkzdCxzdHJhdGlmeTooKT0+cDJ0LHN0eWxlOigpPT5TcCxzdW06KCk9Pm9fdCxzdmc6KCk9PkJ2dCxzeW1ib2w6KCk9PllTdCxzeW1ib2xDaXJjbGU6KCk9Pkw1LHN5bWJvbENyb3NzOigpPT5GOCxzeW1ib2xEaWFtb25kOigpPT5COCxzeW1ib2xTcXVhcmU6KCk9PlY4LHN5bWJvbFN0YXI6KCk9Pkg4LHN5bWJvbFRyaWFuZ2xlOigpPT5VOCxzeW1ib2xXeWU6KCk9PnE4LHN5bWJvbHM6KCk9PldTdCx0ZXh0OigpPT5EXyx0aHJlc2hvbGRGcmVlZG1hbkRpYWNvbmlzOigpPT5KMHQsdGhyZXNob2xkU2NvdHQ6KCk9PlEwdCx0aHJlc2hvbGRTdHVyZ2VzOigpPT5zYix0aWNrRm9ybWF0OigpPT5sOCx0aWNrSW5jcmVtZW50OigpPT54Xyx0aWNrU3RlcDooKT0+dGwsdGlja3M6KCk9PmFiLHRpbWVEYXk6KCk9PnR5LHRpbWVEYXlzOigpPT5YMnQsdGltZUZvcm1hdDooKT0+UzUsdGltZUZvcm1hdERlZmF1bHRMb2NhbGU6KCk9Pnc4LHRpbWVGb3JtYXRMb2NhbGU6KCk9Pnc1LHRpbWVGcmlkYXk6KCk9PmhYLHRpbWVGcmlkYXlzOigpPT5RMnQsdGltZUhvdXI6KCk9Pl84LHRpbWVIb3VyczooKT0+WTJ0LHRpbWVJbnRlcnZhbDooKT0+YnIsdGltZU1pbGxpc2Vjb25kOigpPT5aXyx0aW1lTWlsbGlzZWNvbmRzOigpPT5zWCx0aW1lTWludXRlOigpPT5nOCx0aW1lTWludXRlczooKT0+RzJ0LHRpbWVNb25kYXk6KCk9PnJ5LHRpbWVNb25kYXlzOigpPT4kMnQsdGltZU1vbnRoOigpPT55OCx0aW1lTW9udGhzOigpPT5yd3QsdGltZVBhcnNlOigpPT5TWCx0aW1lU2F0dXJkYXk6KCk9PmZYLHRpbWVTYXR1cmRheXM6KCk9PnR3dCx0aW1lU2Vjb25kOigpPT5RXyx0aW1lU2Vjb25kczooKT0+bFgsdGltZVN1bmRheTooKT0+cmcsdGltZVN1bmRheXM6KCk9PnBYLHRpbWVUaHVyc2RheTooKT0+enAsdGltZVRodXJzZGF5czooKT0+SjJ0LHRpbWVUdWVzZGF5OigpPT5jWCx0aW1lVHVlc2RheXM6KCk9PksydCx0aW1lV2VkbmVzZGF5OigpPT51WCx0aW1lV2VkbmVzZGF5czooKT0+WjJ0LHRpbWVXZWVrOigpPT5yZyx0aW1lV2Vla3M6KCk9PnBYLHRpbWVZZWFyOigpPT5HaCx0aW1lWWVhcnM6KCk9Pm53dCx0aW1lb3V0OigpPT5PRSx0aW1lcjooKT0+QV8sdGltZXJGbHVzaDooKT0+X1ksdG91Y2g6KCk9PlRwLHRvdWNoZXM6KCk9Pm55dCx0cmFuc2l0aW9uOigpPT5BTCx0cmFuc3Bvc2U6KCk9Plc5LHRyZWU6KCk9PmQydCx0cmVlbWFwOigpPT5tMnQsdHJlZW1hcEJpbmFyeTooKT0+ZzJ0LHRyZWVtYXBEaWNlOigpPT5VaCx0cmVlbWFwUmVzcXVhcmlmeTooKT0+eTJ0LHRyZWVtYXBTbGljZTooKT0+Sm0sdHJlZW1hcFNsaWNlRGljZTooKT0+XzJ0LHRyZWVtYXBTcXVhcmlmeTooKT0+aTgsdHN2OigpPT5OdnQsdHN2Rm9ybWF0OigpPT5TdnQsdHN2Rm9ybWF0Qm9keTooKT0+TXZ0LHRzdkZvcm1hdFJvdzooKT0+VHZ0LHRzdkZvcm1hdFJvd3M6KCk9PkV2dCx0c3ZGb3JtYXRWYWx1ZTooKT0+Q3Z0LHRzdlBhcnNlOigpPT5BYix0c3ZQYXJzZVJvd3M6KCk9Pnd2dCx1dGNEYXk6KCk9Pm55LHV0Y0RheXM6KCk9PmN3dCx1dGNGb3JtYXQ6KCk9PmF5LHV0Y0ZyaWRheTooKT0+X1gsdXRjRnJpZGF5czooKT0+ZHd0LHV0Y0hvdXI6KCk9Png4LHV0Y0hvdXJzOigpPT5zd3QsdXRjTWlsbGlzZWNvbmQ6KCk9PlpfLHV0Y01pbGxpc2Vjb25kczooKT0+c1gsdXRjTWludXRlOigpPT52OCx1dGNNaW51dGVzOigpPT5vd3QsdXRjTW9uZGF5OigpPT5veSx1dGNNb25kYXlzOigpPT51d3QsdXRjTW9udGg6KCk9PmI4LHV0Y01vbnRoczooKT0+X3d0LHV0Y1BhcnNlOigpPT5NNSx1dGNTYXR1cmRheTooKT0+eVgsdXRjU2F0dXJkYXlzOigpPT5td3QsdXRjU2Vjb25kOigpPT5RXyx1dGNTZWNvbmRzOigpPT5sWCx1dGNTdW5kYXk6KCk9Pm5nLHV0Y1N1bmRheXM6KCk9PnZYLHV0Y1RodXJzZGF5OigpPT5GcCx1dGNUaHVyc2RheXM6KCk9PnB3dCx1dGNUdWVzZGF5OigpPT5tWCx1dGNUdWVzZGF5czooKT0+aHd0LHV0Y1dlZG5lc2RheTooKT0+Z1gsdXRjV2VkbmVzZGF5czooKT0+Znd0LHV0Y1dlZWs6KCk9Pm5nLHV0Y1dlZWtzOigpPT52WCx1dGNZZWFyOigpPT5XaCx1dGNZZWFyczooKT0+eXd0LHZhbHVlczooKT0+bnZ0LHZhcmlhbmNlOigpPT5xOSx2ZXJzaW9uOigpPT5VMHQsdm9yb25vaTooKT0+VzN0LHdpbmRvdzooKT0+aGIseG1sOigpPT56dnQsemlwOigpPT5hX3Qsem9vbTooKT0+dFIsem9vbUlkZW50aXR5OigpPT5YaCx6b29tVHJhbnNmb3JtOigpPT5pMn0pO3ZhciBVMHQ9IjUuNy4wIjtmdW5jdGlvbiBvYShlLHQpe3JldHVybiBlPHQ/LTE6ZT50PzE6ZT49dD8wOk5hTn1mdW5jdGlvbiBvYihlKXtyZXR1cm4gZS5sZW5ndGg9PT0xJiYoZT1uMmUoZSkpLHtsZWZ0OmZ1bmN0aW9uKHQscixuLGkpe2ZvcihuPT1udWxsJiYobj0wKSxpPT1udWxsJiYoaT10Lmxlbmd0aCk7bjxpOyl7dmFyIG89bitpPj4+MTtlKHRbb10scik8MD9uPW8rMTppPW99cmV0dXJuIG59LHJpZ2h0OmZ1bmN0aW9uKHQscixuLGkpe2ZvcihuPT1udWxsJiYobj0wKSxpPT1udWxsJiYoaT10Lmxlbmd0aCk7bjxpOyl7dmFyIG89bitpPj4+MTtlKHRbb10scik+MD9pPW86bj1vKzF9cmV0dXJuIG59fX1mdW5jdGlvbiBuMmUoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7cmV0dXJuIG9hKGUodCkscil9fXZhciBxMHQ9b2Iob2EpLFlXPXEwdC5yaWdodCxHMHQ9cTB0LmxlZnQseXM9WVc7ZnVuY3Rpb24gVzB0KGUsdCl7dD09bnVsbCYmKHQ9alcpO2Zvcih2YXIgcj0wLG49ZS5sZW5ndGgtMSxpPWVbMF0sbz1uZXcgQXJyYXkobjwwPzA6bik7cjxuOylvW3JdPXQoaSxpPWVbKytyXSk7cmV0dXJuIG99ZnVuY3Rpb24galcoZSx0KXtyZXR1cm5bZSx0XX1mdW5jdGlvbiBVOShlLHQscil7dmFyIG49ZS5sZW5ndGgsaT10Lmxlbmd0aCxvPW5ldyBBcnJheShuKmkpLGEscyxsLGM7Zm9yKHI9PW51bGwmJihyPWpXKSxhPWw9MDthPG47KythKWZvcihjPWVbYV0scz0wO3M8aTsrK3MsKytsKW9bbF09cihjLHRbc10pO3JldHVybiBvfWZ1bmN0aW9uIFkwdChlLHQpe3JldHVybiB0PGU/LTE6dD5lPzE6dD49ZT8wOk5hTn1mdW5jdGlvbiBRcyhlKXtyZXR1cm4gZT09PW51bGw/TmFOOitlfWZ1bmN0aW9uIHE5KGUsdCl7dmFyIHI9ZS5sZW5ndGgsbj0wLGk9LTEsbz0wLGEscyxsPTA7aWYodD09bnVsbClmb3IoOysraTxyOylpc05hTihhPVFzKGVbaV0pKXx8KHM9YS1vLG8rPXMvKytuLGwrPXMqKGEtbykpO2Vsc2UgZm9yKDsrK2k8cjspaXNOYU4oYT1Rcyh0KGVbaV0saSxlKSkpfHwocz1hLW8sbys9cy8rK24sbCs9cyooYS1vKSk7aWYobj4xKXJldHVybiBsLyhuLTEpfWZ1bmN0aW9uIEc5KGUsdCl7dmFyIHI9cTkoZSx0KTtyZXR1cm4gciYmTWF0aC5zcXJ0KHIpfWZ1bmN0aW9uIGFhKGUsdCl7dmFyIHI9ZS5sZW5ndGgsbj0tMSxpLG8sYTtpZih0PT1udWxsKXtmb3IoOysrbjxyOylpZigoaT1lW25dKSE9bnVsbCYmaT49aSlmb3Iobz1hPWk7KytuPHI7KShpPWVbbl0pIT1udWxsJiYobz5pJiYobz1pKSxhPGkmJihhPWkpKX1lbHNlIGZvcig7KytuPHI7KWlmKChpPXQoZVtuXSxuLGUpKSE9bnVsbCYmaT49aSlmb3Iobz1hPWk7KytuPHI7KShpPXQoZVtuXSxuLGUpKSE9bnVsbCYmKG8+aSYmKG89aSksYTxpJiYoYT1pKSk7cmV0dXJuW28sYV19dmFyIGowdD1BcnJheS5wcm90b3R5cGUsWDB0PWowdC5zbGljZSwkMHQ9ajB0Lm1hcDtmdW5jdGlvbiB4RShlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19ZnVuY3Rpb24gSzB0KGUpe3JldHVybiBlfWZ1bmN0aW9uIElyKGUsdCxyKXtlPStlLHQ9K3Qscj0oaT1hcmd1bWVudHMubGVuZ3RoKTwyPyh0PWUsZT0wLDEpOmk8Mz8xOityO2Zvcih2YXIgbj0tMSxpPU1hdGgubWF4KDAsTWF0aC5jZWlsKCh0LWUpL3IpKXwwLG89bmV3IEFycmF5KGkpOysrbjxpOylvW25dPWUrbipyO3JldHVybiBvfXZhciBYVz1NYXRoLnNxcnQoNTApLCRXPU1hdGguc3FydCgxMCksS1c9TWF0aC5zcXJ0KDIpO2Z1bmN0aW9uIGFiKGUsdCxyKXt2YXIgbixpPS0xLG8sYSxzO2lmKHQ9K3QsZT0rZSxyPStyLGU9PT10JiZyPjApcmV0dXJuW2VdO2lmKChuPXQ8ZSkmJihvPWUsZT10LHQ9byksKHM9eF8oZSx0LHIpKT09PTB8fCFpc0Zpbml0ZShzKSlyZXR1cm5bXTtpZihzPjApZm9yKGU9TWF0aC5jZWlsKGUvcyksdD1NYXRoLmZsb29yKHQvcyksYT1uZXcgQXJyYXkobz1NYXRoLmNlaWwodC1lKzEpKTsrK2k8bzspYVtpXT0oZStpKSpzO2Vsc2UgZm9yKGU9TWF0aC5mbG9vcihlKnMpLHQ9TWF0aC5jZWlsKHQqcyksYT1uZXcgQXJyYXkobz1NYXRoLmNlaWwoZS10KzEpKTsrK2k8bzspYVtpXT0oZS1pKS9zO3JldHVybiBuJiZhLnJldmVyc2UoKSxhfWZ1bmN0aW9uIHhfKGUsdCxyKXt2YXIgbj0odC1lKS9NYXRoLm1heCgwLHIpLGk9TWF0aC5mbG9vcihNYXRoLmxvZyhuKS9NYXRoLkxOMTApLG89bi9NYXRoLnBvdygxMCxpKTtyZXR1cm4gaT49MD8obz49WFc/MTA6bz49JFc/NTpvPj1LVz8yOjEpKk1hdGgucG93KDEwLGkpOi1NYXRoLnBvdygxMCwtaSkvKG8+PVhXPzEwOm8+PSRXPzU6bz49S1c/MjoxKX1mdW5jdGlvbiB0bChlLHQscil7dmFyIG49TWF0aC5hYnModC1lKS9NYXRoLm1heCgwLHIpLGk9TWF0aC5wb3coMTAsTWF0aC5mbG9vcihNYXRoLmxvZyhuKS9NYXRoLkxOMTApKSxvPW4vaTtyZXR1cm4gbz49WFc/aSo9MTA6bz49JFc/aSo9NTpvPj1LVyYmKGkqPTIpLHQ8ZT8taTppfWZ1bmN0aW9uIHNiKGUpe3JldHVybiBNYXRoLmNlaWwoTWF0aC5sb2coZS5sZW5ndGgpL01hdGguTE4yKSsxfWZ1bmN0aW9uIFowdCgpe3ZhciBlPUswdCx0PWFhLHI9c2I7ZnVuY3Rpb24gbihpKXt2YXIgbyxhPWkubGVuZ3RoLHMsbD1uZXcgQXJyYXkoYSk7Zm9yKG89MDtvPGE7KytvKWxbb109ZShpW29dLG8saSk7dmFyIGM9dChsKSx1PWNbMF0saD1jWzFdLGY9cihsLHUsaCk7QXJyYXkuaXNBcnJheShmKXx8KGY9dGwodSxoLGYpLGY9SXIoTWF0aC5jZWlsKHUvZikqZixoLGYpKTtmb3IodmFyIHA9Zi5sZW5ndGg7ZlswXTw9dTspZi5zaGlmdCgpLC0tcDtmb3IoO2ZbcC0xXT5oOylmLnBvcCgpLC0tcDt2YXIgZD1uZXcgQXJyYXkocCsxKSxnO2ZvcihvPTA7bzw9cDsrK28pZz1kW29dPVtdLGcueDA9bz4wP2Zbby0xXTp1LGcueDE9bzxwP2Zbb106aDtmb3Iobz0wO288YTsrK28pcz1sW29dLHU8PXMmJnM8PWgmJmRbeXMoZixzLDAscCldLnB1c2goaVtvXSk7cmV0dXJuIGR9cmV0dXJuIG4udmFsdWU9ZnVuY3Rpb24oaSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIGk9PSJmdW5jdGlvbiI/aTp4RShpKSxuKTplfSxuLmRvbWFpbj1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD10eXBlb2YgaT09ImZ1bmN0aW9uIj9pOnhFKFtpWzBdLGlbMV1dKSxuKTp0fSxuLnRocmVzaG9sZHM9ZnVuY3Rpb24oaSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9dHlwZW9mIGk9PSJmdW5jdGlvbiI/aTpBcnJheS5pc0FycmF5KGkpP3hFKFgwdC5jYWxsKGkpKTp4RShpKSxuKTpyfSxufWZ1bmN0aW9uIHNhKGUsdCxyKXtpZihyPT1udWxsJiYocj1RcyksISEobj1lLmxlbmd0aCkpe2lmKCh0PSt0KTw9MHx8bjwyKXJldHVybityKGVbMF0sMCxlKTtpZih0Pj0xKXJldHVybityKGVbbi0xXSxuLTEsZSk7dmFyIG4saT0obi0xKSp0LG89TWF0aC5mbG9vcihpKSxhPStyKGVbb10sbyxlKSxzPStyKGVbbysxXSxvKzEsZSk7cmV0dXJuIGErKHMtYSkqKGktbyl9fWZ1bmN0aW9uIEowdChlLHQscil7cmV0dXJuIGU9JDB0LmNhbGwoZSxRcykuc29ydChvYSksTWF0aC5jZWlsKChyLXQpLygyKihzYShlLC43NSktc2EoZSwuMjUpKSpNYXRoLnBvdyhlLmxlbmd0aCwtMS8zKSkpfWZ1bmN0aW9uIFEwdChlLHQscil7cmV0dXJuIE1hdGguY2VpbCgoci10KS8oMy41Kkc5KGUpKk1hdGgucG93KGUubGVuZ3RoLC0xLzMpKSl9ZnVuY3Rpb24gbHUoZSx0KXt2YXIgcj1lLmxlbmd0aCxuPS0xLGksbztpZih0PT1udWxsKXtmb3IoOysrbjxyOylpZigoaT1lW25dKSE9bnVsbCYmaT49aSlmb3Iobz1pOysrbjxyOykoaT1lW25dKSE9bnVsbCYmaT5vJiYobz1pKX1lbHNlIGZvcig7KytuPHI7KWlmKChpPXQoZVtuXSxuLGUpKSE9bnVsbCYmaT49aSlmb3Iobz1pOysrbjxyOykoaT10KGVbbl0sbixlKSkhPW51bGwmJmk+byYmKG89aSk7cmV0dXJuIG99ZnVuY3Rpb24gdF90KGUsdCl7dmFyIHI9ZS5sZW5ndGgsbj1yLGk9LTEsbyxhPTA7aWYodD09bnVsbClmb3IoOysraTxyOylpc05hTihvPVFzKGVbaV0pKT8tLW46YSs9bztlbHNlIGZvcig7KytpPHI7KWlzTmFOKG89UXModChlW2ldLGksZSkpKT8tLW46YSs9bztpZihuKXJldHVybiBhL259ZnVuY3Rpb24gZV90KGUsdCl7dmFyIHI9ZS5sZW5ndGgsbj0tMSxpLG89W107aWYodD09bnVsbClmb3IoOysrbjxyOylpc05hTihpPVFzKGVbbl0pKXx8by5wdXNoKGkpO2Vsc2UgZm9yKDsrK248cjspaXNOYU4oaT1Rcyh0KGVbbl0sbixlKSkpfHxvLnB1c2goaSk7cmV0dXJuIHNhKG8uc29ydChvYSksLjUpfWZ1bmN0aW9uIEltKGUpe2Zvcih2YXIgdD1lLmxlbmd0aCxyLG49LTEsaT0wLG8sYTsrK248dDspaSs9ZVtuXS5sZW5ndGg7Zm9yKG89bmV3IEFycmF5KGkpOy0tdD49MDspZm9yKGE9ZVt0XSxyPWEubGVuZ3RoOy0tcj49MDspb1stLWldPWFbcl07cmV0dXJuIG99ZnVuY3Rpb24gTG0oZSx0KXt2YXIgcj1lLmxlbmd0aCxuPS0xLGksbztpZih0PT1udWxsKXtmb3IoOysrbjxyOylpZigoaT1lW25dKSE9bnVsbCYmaT49aSlmb3Iobz1pOysrbjxyOykoaT1lW25dKSE9bnVsbCYmbz5pJiYobz1pKX1lbHNlIGZvcig7KytuPHI7KWlmKChpPXQoZVtuXSxuLGUpKSE9bnVsbCYmaT49aSlmb3Iobz1pOysrbjxyOykoaT10KGVbbl0sbixlKSkhPW51bGwmJm8+aSYmKG89aSk7cmV0dXJuIG99ZnVuY3Rpb24gcl90KGUsdCl7Zm9yKHZhciByPXQubGVuZ3RoLG49bmV3IEFycmF5KHIpO3ItLTspbltyXT1lW3Rbcl1dO3JldHVybiBufWZ1bmN0aW9uIG5fdChlLHQpe2lmKCEhKHI9ZS5sZW5ndGgpKXt2YXIgcixuPTAsaT0wLG8sYT1lW2ldO2Zvcih0PT1udWxsJiYodD1vYSk7KytuPHI7KSh0KG89ZVtuXSxhKTwwfHx0KGEsYSkhPT0wKSYmKGE9byxpPW4pO2lmKHQoYSxhKT09PTApcmV0dXJuIGl9fWZ1bmN0aW9uIGlfdChlLHQscil7Zm9yKHZhciBuPShyPT1udWxsP2UubGVuZ3RoOnIpLSh0PXQ9PW51bGw/MDordCksaSxvO247KW89TWF0aC5yYW5kb20oKSpuLS18MCxpPWVbbit0XSxlW24rdF09ZVtvK3RdLGVbbyt0XT1pO3JldHVybiBlfWZ1bmN0aW9uIG9fdChlLHQpe3ZhciByPWUubGVuZ3RoLG49LTEsaSxvPTA7aWYodD09bnVsbClmb3IoOysrbjxyOykoaT0rZVtuXSkmJihvKz1pKTtlbHNlIGZvcig7KytuPHI7KShpPSt0KGVbbl0sbixlKSkmJihvKz1pKTtyZXR1cm4gb31mdW5jdGlvbiBXOShlKXtpZighKG89ZS5sZW5ndGgpKXJldHVybltdO2Zvcih2YXIgdD0tMSxyPUxtKGUsaTJlKSxuPW5ldyBBcnJheShyKTsrK3Q8cjspZm9yKHZhciBpPS0xLG8sYT1uW3RdPW5ldyBBcnJheShvKTsrK2k8bzspYVtpXT1lW2ldW3RdO3JldHVybiBufWZ1bmN0aW9uIGkyZShlKXtyZXR1cm4gZS5sZW5ndGh9ZnVuY3Rpb24gYV90KCl7cmV0dXJuIFc5KGFyZ3VtZW50cyl9dmFyIFk5PUFycmF5LnByb3RvdHlwZS5zbGljZTtmdW5jdGlvbiBzX3QoZSl7cmV0dXJuIGV9dmFyIGo5PTEsWDk9MixaVz0zLGJFPTQsbF90PTFlLTY7ZnVuY3Rpb24gbzJlKGUpe3JldHVybiJ0cmFuc2xhdGUoIisoZSsuNSkrIiwwKSJ9ZnVuY3Rpb24gYTJlKGUpe3JldHVybiJ0cmFuc2xhdGUoMCwiKyhlKy41KSsiKSJ9ZnVuY3Rpb24gczJlKGUpe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4rZSh0KX19ZnVuY3Rpb24gbDJlKGUpe3ZhciB0PU1hdGgubWF4KDAsZS5iYW5kd2lkdGgoKS0xKS8yO3JldHVybiBlLnJvdW5kKCkmJih0PU1hdGgucm91bmQodCkpLGZ1bmN0aW9uKHIpe3JldHVybitlKHIpK3R9fWZ1bmN0aW9uIGMyZSgpe3JldHVybiF0aGlzLl9fYXhpc31mdW5jdGlvbiAkOShlLHQpe3ZhciByPVtdLG49bnVsbCxpPW51bGwsbz02LGE9NixzPTMsbD1lPT09ajl8fGU9PT1iRT8tMToxLGM9ZT09PWJFfHxlPT09WDk/IngiOiJ5Iix1PWU9PT1qOXx8ZT09PVpXP28yZTphMmU7ZnVuY3Rpb24gaChmKXt2YXIgcD1uPT1udWxsP3QudGlja3M/dC50aWNrcy5hcHBseSh0LHIpOnQuZG9tYWluKCk6bixkPWk9PW51bGw/dC50aWNrRm9ybWF0P3QudGlja0Zvcm1hdC5hcHBseSh0LHIpOnNfdDppLGc9TWF0aC5tYXgobywwKStzLF89dC5yYW5nZSgpLHk9K19bMF0rLjUseD0rX1tfLmxlbmd0aC0xXSsuNSxiPSh0LmJhbmR3aWR0aD9sMmU6czJlKSh0LmNvcHkoKSksUz1mLnNlbGVjdGlvbj9mLnNlbGVjdGlvbigpOmYsQz1TLnNlbGVjdEFsbCgiLmRvbWFpbiIpLmRhdGEoW251bGxdKSxQPVMuc2VsZWN0QWxsKCIudGljayIpLmRhdGEocCx0KS5vcmRlcigpLGs9UC5leGl0KCksTz1QLmVudGVyKCkuYXBwZW5kKCJnIikuYXR0cigiY2xhc3MiLCJ0aWNrIiksRD1QLnNlbGVjdCgibGluZSIpLEI9UC5zZWxlY3QoInRleHQiKTtDPUMubWVyZ2UoQy5lbnRlcigpLmluc2VydCgicGF0aCIsIi50aWNrIikuYXR0cigiY2xhc3MiLCJkb21haW4iKS5hdHRyKCJzdHJva2UiLCJjdXJyZW50Q29sb3IiKSksUD1QLm1lcmdlKE8pLEQ9RC5tZXJnZShPLmFwcGVuZCgibGluZSIpLmF0dHIoInN0cm9rZSIsImN1cnJlbnRDb2xvciIpLmF0dHIoYysiMiIsbCpvKSksQj1CLm1lcmdlKE8uYXBwZW5kKCJ0ZXh0IikuYXR0cigiZmlsbCIsImN1cnJlbnRDb2xvciIpLmF0dHIoYyxsKmcpLmF0dHIoImR5IixlPT09ajk/IjBlbSI6ZT09PVpXPyIwLjcxZW0iOiIwLjMyZW0iKSksZiE9PVMmJihDPUMudHJhbnNpdGlvbihmKSxQPVAudHJhbnNpdGlvbihmKSxEPUQudHJhbnNpdGlvbihmKSxCPUIudHJhbnNpdGlvbihmKSxrPWsudHJhbnNpdGlvbihmKS5hdHRyKCJvcGFjaXR5IixsX3QpLmF0dHIoInRyYW5zZm9ybSIsZnVuY3Rpb24oSSl7cmV0dXJuIGlzRmluaXRlKEk9YihJKSk/dShJKTp0aGlzLmdldEF0dHJpYnV0ZSgidHJhbnNmb3JtIil9KSxPLmF0dHIoIm9wYWNpdHkiLGxfdCkuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihJKXt2YXIgTD10aGlzLnBhcmVudE5vZGUuX19heGlzO3JldHVybiB1KEwmJmlzRmluaXRlKEw9TChJKSk/TDpiKEkpKX0pKSxrLnJlbW92ZSgpLEMuYXR0cigiZCIsZT09PWJFfHxlPT1YOT9hPyJNIitsKmErIiwiK3krIkgwLjVWIit4KyJIIitsKmE6Ik0wLjUsIit5KyJWIit4OmE/Ik0iK3krIiwiK2wqYSsiVjAuNUgiK3grIlYiK2wqYToiTSIreSsiLDAuNUgiK3gpLFAuYXR0cigib3BhY2l0eSIsMSkuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihJKXtyZXR1cm4gdShiKEkpKX0pLEQuYXR0cihjKyIyIixsKm8pLEIuYXR0cihjLGwqZykudGV4dChkKSxTLmZpbHRlcihjMmUpLmF0dHIoImZpbGwiLCJub25lIikuYXR0cigiZm9udC1zaXplIiwxMCkuYXR0cigiZm9udC1mYW1pbHkiLCJzYW5zLXNlcmlmIikuYXR0cigidGV4dC1hbmNob3IiLGU9PT1YOT8ic3RhcnQiOmU9PT1iRT8iZW5kIjoibWlkZGxlIiksUy5lYWNoKGZ1bmN0aW9uKCl7dGhpcy5fX2F4aXM9Yn0pfXJldHVybiBoLnNjYWxlPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PWYsaCk6dH0saC50aWNrcz1mdW5jdGlvbigpe3JldHVybiByPVk5LmNhbGwoYXJndW1lbnRzKSxofSxoLnRpY2tBcmd1bWVudHM9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9Zj09bnVsbD9bXTpZOS5jYWxsKGYpLGgpOnIuc2xpY2UoKX0saC50aWNrVmFsdWVzPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPWY9PW51bGw/bnVsbDpZOS5jYWxsKGYpLGgpOm4mJm4uc2xpY2UoKX0saC50aWNrRm9ybWF0PWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPWYsaCk6aX0saC50aWNrU2l6ZT1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz1hPStmLGgpOm99LGgudGlja1NpemVJbm5lcj1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz0rZixoKTpvfSxoLnRpY2tTaXplT3V0ZXI9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGE9K2YsaCk6YX0saC50aWNrUGFkZGluZz1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocz0rZixoKTpzfSxofWZ1bmN0aW9uIGNfdChlKXtyZXR1cm4gJDkoajksZSl9ZnVuY3Rpb24gdV90KGUpe3JldHVybiAkOShYOSxlKX1mdW5jdGlvbiBLOShlKXtyZXR1cm4gJDkoWlcsZSl9ZnVuY3Rpb24gbGIoZSl7cmV0dXJuICQ5KGJFLGUpfWttKCk7a20oKTt2YXIgSjk9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiLHdFPXtzdmc6Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIix4aHRtbDpKOSx4bGluazoiaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIseG1sOiJodHRwOi8vd3d3LnczLm9yZy9YTUwvMTk5OC9uYW1lc3BhY2UiLHhtbG5zOiJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3htbG5zLyJ9O2Z1bmN0aW9uIFBoKGUpe3ZhciB0PWUrPSIiLHI9dC5pbmRleE9mKCI6Iik7cmV0dXJuIHI+PTAmJih0PWUuc2xpY2UoMCxyKSkhPT0ieG1sbnMiJiYoZT1lLnNsaWNlKHIrMSkpLHdFLmhhc093blByb3BlcnR5KHQpP3tzcGFjZTp3RVt0XSxsb2NhbDplfTplfWZ1bmN0aW9uIHAyZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD10aGlzLm93bmVyRG9jdW1lbnQscj10aGlzLm5hbWVzcGFjZVVSSTtyZXR1cm4gcj09PUo5JiZ0LmRvY3VtZW50RWxlbWVudC5uYW1lc3BhY2VVUkk9PT1KOT90LmNyZWF0ZUVsZW1lbnQoZSk6dC5jcmVhdGVFbGVtZW50TlMocixlKX19ZnVuY3Rpb24gZDJlKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiB0aGlzLm93bmVyRG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKGUuc3BhY2UsZS5sb2NhbCl9fWZ1bmN0aW9uIFJtKGUpe3ZhciB0PVBoKGUpO3JldHVybih0LmxvY2FsP2QyZTpwMmUpKHQpfWZ1bmN0aW9uIG0yZSgpe31mdW5jdGlvbiBObShlKXtyZXR1cm4gZT09bnVsbD9tMmU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5xdWVyeVNlbGVjdG9yKGUpfX1mdW5jdGlvbiBkX3QoZSl7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPU5tKGUpKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9dC5sZW5ndGgsbj1uZXcgQXJyYXkociksaT0wO2k8cjsrK2kpZm9yKHZhciBvPXRbaV0sYT1vLmxlbmd0aCxzPW5baV09bmV3IEFycmF5KGEpLGwsYyx1PTA7dTxhOysrdSkobD1vW3VdKSYmKGM9ZS5jYWxsKGwsbC5fX2RhdGFfXyx1LG8pKSYmKCJfX2RhdGFfXyJpbiBsJiYoYy5fX2RhdGFfXz1sLl9fZGF0YV9fKSxzW3VdPWMpO3JldHVybiBuZXcgRG4obix0aGlzLl9wYXJlbnRzKX1mdW5jdGlvbiBnMmUoKXtyZXR1cm5bXX1mdW5jdGlvbiBjYihlKXtyZXR1cm4gZT09bnVsbD9nMmU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5xdWVyeVNlbGVjdG9yQWxsKGUpfX1mdW5jdGlvbiBtX3QoZSl7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPWNiKGUpKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9dC5sZW5ndGgsbj1bXSxpPVtdLG89MDtvPHI7KytvKWZvcih2YXIgYT10W29dLHM9YS5sZW5ndGgsbCxjPTA7YzxzOysrYykobD1hW2NdKSYmKG4ucHVzaChlLmNhbGwobCxsLl9fZGF0YV9fLGMsYSkpLGkucHVzaChsKSk7cmV0dXJuIG5ldyBEbihuLGkpfWZ1bmN0aW9uIHViKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiB0aGlzLm1hdGNoZXMoZSl9fWZ1bmN0aW9uIGdfdChlKXt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9dWIoZSkpO2Zvcih2YXIgdD10aGlzLl9ncm91cHMscj10Lmxlbmd0aCxuPW5ldyBBcnJheShyKSxpPTA7aTxyOysraSlmb3IodmFyIG89dFtpXSxhPW8ubGVuZ3RoLHM9bltpXT1bXSxsLGM9MDtjPGE7KytjKShsPW9bY10pJiZlLmNhbGwobCxsLl9fZGF0YV9fLGMsbykmJnMucHVzaChsKTtyZXR1cm4gbmV3IERuKG4sdGhpcy5fcGFyZW50cyl9ZnVuY3Rpb24gUTkoZSl7cmV0dXJuIG5ldyBBcnJheShlLmxlbmd0aCl9ZnVuY3Rpb24gX190KCl7cmV0dXJuIG5ldyBEbih0aGlzLl9lbnRlcnx8dGhpcy5fZ3JvdXBzLm1hcChROSksdGhpcy5fcGFyZW50cyl9ZnVuY3Rpb24gU0UoZSx0KXt0aGlzLm93bmVyRG9jdW1lbnQ9ZS5vd25lckRvY3VtZW50LHRoaXMubmFtZXNwYWNlVVJJPWUubmFtZXNwYWNlVVJJLHRoaXMuX25leHQ9bnVsbCx0aGlzLl9wYXJlbnQ9ZSx0aGlzLl9fZGF0YV9fPXR9U0UucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpTRSxhcHBlbmRDaGlsZDpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fcGFyZW50Lmluc2VydEJlZm9yZShlLHRoaXMuX25leHQpfSxpbnNlcnRCZWZvcmU6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gdGhpcy5fcGFyZW50Lmluc2VydEJlZm9yZShlLHQpfSxxdWVyeVNlbGVjdG9yOmZ1bmN0aW9uKGUpe3JldHVybiB0aGlzLl9wYXJlbnQucXVlcnlTZWxlY3RvcihlKX0scXVlcnlTZWxlY3RvckFsbDpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fcGFyZW50LnF1ZXJ5U2VsZWN0b3JBbGwoZSl9fTtmdW5jdGlvbiB5X3QoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fXZhciB2X3Q9IiQiO2Z1bmN0aW9uIF8yZShlLHQscixuLGksbyl7Zm9yKHZhciBhPTAscyxsPXQubGVuZ3RoLGM9by5sZW5ndGg7YTxjOysrYSkocz10W2FdKT8ocy5fX2RhdGFfXz1vW2FdLG5bYV09cyk6clthXT1uZXcgU0UoZSxvW2FdKTtmb3IoO2E8bDsrK2EpKHM9dFthXSkmJihpW2FdPXMpfWZ1bmN0aW9uIHkyZShlLHQscixuLGksbyxhKXt2YXIgcyxsLGM9e30sdT10Lmxlbmd0aCxoPW8ubGVuZ3RoLGY9bmV3IEFycmF5KHUpLHA7Zm9yKHM9MDtzPHU7KytzKShsPXRbc10pJiYoZltzXT1wPXZfdCthLmNhbGwobCxsLl9fZGF0YV9fLHMsdCkscCBpbiBjP2lbc109bDpjW3BdPWwpO2ZvcihzPTA7czxoOysrcylwPXZfdCthLmNhbGwoZSxvW3NdLHMsbyksKGw9Y1twXSk/KG5bc109bCxsLl9fZGF0YV9fPW9bc10sY1twXT1udWxsKTpyW3NdPW5ldyBTRShlLG9bc10pO2ZvcihzPTA7czx1OysrcykobD10W3NdKSYmY1tmW3NdXT09PWwmJihpW3NdPWwpfWZ1bmN0aW9uIHhfdChlLHQpe2lmKCFlKXJldHVybiBwPW5ldyBBcnJheSh0aGlzLnNpemUoKSksYz0tMSx0aGlzLmVhY2goZnVuY3Rpb24oUCl7cFsrK2NdPVB9KSxwO3ZhciByPXQ/eTJlOl8yZSxuPXRoaXMuX3BhcmVudHMsaT10aGlzLl9ncm91cHM7dHlwZW9mIGUhPSJmdW5jdGlvbiImJihlPXlfdChlKSk7Zm9yKHZhciBvPWkubGVuZ3RoLGE9bmV3IEFycmF5KG8pLHM9bmV3IEFycmF5KG8pLGw9bmV3IEFycmF5KG8pLGM9MDtjPG87KytjKXt2YXIgdT1uW2NdLGg9aVtjXSxmPWgubGVuZ3RoLHA9ZS5jYWxsKHUsdSYmdS5fX2RhdGFfXyxjLG4pLGQ9cC5sZW5ndGgsZz1zW2NdPW5ldyBBcnJheShkKSxfPWFbY109bmV3IEFycmF5KGQpLHk9bFtjXT1uZXcgQXJyYXkoZik7cih1LGgsZyxfLHkscCx0KTtmb3IodmFyIHg9MCxiPTAsUyxDO3g8ZDsrK3gpaWYoUz1nW3hdKXtmb3IoeD49YiYmKGI9eCsxKTshKEM9X1tiXSkmJisrYjxkOyk7Uy5fbmV4dD1DfHxudWxsfX1yZXR1cm4gYT1uZXcgRG4oYSxuKSxhLl9lbnRlcj1zLGEuX2V4aXQ9bCxhfWZ1bmN0aW9uIGJfdCgpe3JldHVybiBuZXcgRG4odGhpcy5fZXhpdHx8dGhpcy5fZ3JvdXBzLm1hcChROSksdGhpcy5fcGFyZW50cyl9ZnVuY3Rpb24gd190KGUsdCxyKXt2YXIgbj10aGlzLmVudGVyKCksaT10aGlzLG89dGhpcy5leGl0KCk7cmV0dXJuIG49dHlwZW9mIGU9PSJmdW5jdGlvbiI/ZShuKTpuLmFwcGVuZChlKyIiKSx0IT1udWxsJiYoaT10KGkpKSxyPT1udWxsP28ucmVtb3ZlKCk6cihvKSxuJiZpP24ubWVyZ2UoaSkub3JkZXIoKTppfWZ1bmN0aW9uIFNfdChlKXtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9ZS5fZ3JvdXBzLG49dC5sZW5ndGgsaT1yLmxlbmd0aCxvPU1hdGgubWluKG4saSksYT1uZXcgQXJyYXkobikscz0wO3M8bzsrK3MpZm9yKHZhciBsPXRbc10sYz1yW3NdLHU9bC5sZW5ndGgsaD1hW3NdPW5ldyBBcnJheSh1KSxmLHA9MDtwPHU7KytwKShmPWxbcF18fGNbcF0pJiYoaFtwXT1mKTtmb3IoO3M8bjsrK3MpYVtzXT10W3NdO3JldHVybiBuZXcgRG4oYSx0aGlzLl9wYXJlbnRzKX1mdW5jdGlvbiBNX3QoKXtmb3IodmFyIGU9dGhpcy5fZ3JvdXBzLHQ9LTEscj1lLmxlbmd0aDsrK3Q8cjspZm9yKHZhciBuPWVbdF0saT1uLmxlbmd0aC0xLG89bltpXSxhOy0taT49MDspKGE9bltpXSkmJihvJiZhLmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKG8pXjQmJm8ucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUoYSxvKSxvPWEpO3JldHVybiB0aGlzfWZ1bmN0aW9uIEVfdChlKXtlfHwoZT12MmUpO2Z1bmN0aW9uIHQoaCxmKXtyZXR1cm4gaCYmZj9lKGguX19kYXRhX18sZi5fX2RhdGFfXyk6IWgtIWZ9Zm9yKHZhciByPXRoaXMuX2dyb3VwcyxuPXIubGVuZ3RoLGk9bmV3IEFycmF5KG4pLG89MDtvPG47KytvKXtmb3IodmFyIGE9cltvXSxzPWEubGVuZ3RoLGw9aVtvXT1uZXcgQXJyYXkocyksYyx1PTA7dTxzOysrdSkoYz1hW3VdKSYmKGxbdV09Yyk7bC5zb3J0KHQpfXJldHVybiBuZXcgRG4oaSx0aGlzLl9wYXJlbnRzKS5vcmRlcigpfWZ1bmN0aW9uIHYyZShlLHQpe3JldHVybiBlPHQ/LTE6ZT50PzE6ZT49dD8wOk5hTn1mdW5jdGlvbiBUX3QoKXt2YXIgZT1hcmd1bWVudHNbMF07cmV0dXJuIGFyZ3VtZW50c1swXT10aGlzLGUuYXBwbHkobnVsbCxhcmd1bWVudHMpLHRoaXN9ZnVuY3Rpb24gQ190KCl7dmFyIGU9bmV3IEFycmF5KHRoaXMuc2l6ZSgpKSx0PS0xO3JldHVybiB0aGlzLmVhY2goZnVuY3Rpb24oKXtlWysrdF09dGhpc30pLGV9ZnVuY3Rpb24gQV90KCl7Zm9yKHZhciBlPXRoaXMuX2dyb3Vwcyx0PTAscj1lLmxlbmd0aDt0PHI7Kyt0KWZvcih2YXIgbj1lW3RdLGk9MCxvPW4ubGVuZ3RoO2k8bzsrK2kpe3ZhciBhPW5baV07aWYoYSlyZXR1cm4gYX1yZXR1cm4gbnVsbH1mdW5jdGlvbiBQX3QoKXt2YXIgZT0wO3JldHVybiB0aGlzLmVhY2goZnVuY3Rpb24oKXsrK2V9KSxlfWZ1bmN0aW9uIElfdCgpe3JldHVybiF0aGlzLm5vZGUoKX1mdW5jdGlvbiBMX3QoZSl7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPTAsbj10Lmxlbmd0aDtyPG47KytyKWZvcih2YXIgaT10W3JdLG89MCxhPWkubGVuZ3RoLHM7bzxhOysrbykocz1pW29dKSYmZS5jYWxsKHMscy5fX2RhdGFfXyxvLGkpO3JldHVybiB0aGlzfWZ1bmN0aW9uIHgyZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnJlbW92ZUF0dHJpYnV0ZShlKX19ZnVuY3Rpb24gYjJlKGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKX19ZnVuY3Rpb24gdzJlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zZXRBdHRyaWJ1dGUoZSx0KX19ZnVuY3Rpb24gUzJlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zZXRBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwsdCl9fWZ1bmN0aW9uIE0yZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3ZhciByPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3I9PW51bGw/dGhpcy5yZW1vdmVBdHRyaWJ1dGUoZSk6dGhpcy5zZXRBdHRyaWJ1dGUoZSxyKX19ZnVuY3Rpb24gRTJlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHI9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cj09bnVsbD90aGlzLnJlbW92ZUF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCk6dGhpcy5zZXRBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwscil9fWZ1bmN0aW9uIGtfdChlLHQpe3ZhciByPVBoKGUpO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7dmFyIG49dGhpcy5ub2RlKCk7cmV0dXJuIHIubG9jYWw/bi5nZXRBdHRyaWJ1dGVOUyhyLnNwYWNlLHIubG9jYWwpOm4uZ2V0QXR0cmlidXRlKHIpfXJldHVybiB0aGlzLmVhY2goKHQ9PW51bGw/ci5sb2NhbD9iMmU6eDJlOnR5cGVvZiB0PT0iZnVuY3Rpb24iP3IubG9jYWw/RTJlOk0yZTpyLmxvY2FsP1MyZTp3MmUpKHIsdCkpfWZ1bmN0aW9uIGhiKGUpe3JldHVybiBlLm93bmVyRG9jdW1lbnQmJmUub3duZXJEb2N1bWVudC5kZWZhdWx0Vmlld3x8ZS5kb2N1bWVudCYmZXx8ZS5kZWZhdWx0Vmlld31mdW5jdGlvbiBUMmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShlKX19ZnVuY3Rpb24gQzJlKGUsdCxyKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnN0eWxlLnNldFByb3BlcnR5KGUsdCxyKX19ZnVuY3Rpb24gQTJlKGUsdCxyKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbj10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtuPT1udWxsP3RoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkoZSk6dGhpcy5zdHlsZS5zZXRQcm9wZXJ0eShlLG4scil9fWZ1bmN0aW9uIFJfdChlLHQscil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg+MT90aGlzLmVhY2goKHQ9PW51bGw/VDJlOnR5cGVvZiB0PT0iZnVuY3Rpb24iP0EyZTpDMmUpKGUsdCxyPT1udWxsPyIiOnIpKTpTcCh0aGlzLm5vZGUoKSxlKX1mdW5jdGlvbiBTcChlLHQpe3JldHVybiBlLnN0eWxlLmdldFByb3BlcnR5VmFsdWUodCl8fGhiKGUpLmdldENvbXB1dGVkU3R5bGUoZSxudWxsKS5nZXRQcm9wZXJ0eVZhbHVlKHQpfWZ1bmN0aW9uIFAyZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtkZWxldGUgdGhpc1tlXX19ZnVuY3Rpb24gSTJlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpc1tlXT10fX1mdW5jdGlvbiBMMmUoZSx0KXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgcj10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyPT1udWxsP2RlbGV0ZSB0aGlzW2VdOnRoaXNbZV09cn19ZnVuY3Rpb24gTl90KGUsdCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg+MT90aGlzLmVhY2goKHQ9PW51bGw/UDJlOnR5cGVvZiB0PT0iZnVuY3Rpb24iP0wyZTpJMmUpKGUsdCkpOnRoaXMubm9kZSgpW2VdfWZ1bmN0aW9uIERfdChlKXtyZXR1cm4gZS50cmltKCkuc3BsaXQoL158XHMrLyl9ZnVuY3Rpb24gSlcoZSl7cmV0dXJuIGUuY2xhc3NMaXN0fHxuZXcgT190KGUpfWZ1bmN0aW9uIE9fdChlKXt0aGlzLl9ub2RlPWUsdGhpcy5fbmFtZXM9RF90KGUuZ2V0QXR0cmlidXRlKCJjbGFzcyIpfHwiIil9T190LnByb3RvdHlwZT17YWRkOmZ1bmN0aW9uKGUpe3ZhciB0PXRoaXMuX25hbWVzLmluZGV4T2YoZSk7dDwwJiYodGhpcy5fbmFtZXMucHVzaChlKSx0aGlzLl9ub2RlLnNldEF0dHJpYnV0ZSgiY2xhc3MiLHRoaXMuX25hbWVzLmpvaW4oIiAiKSkpfSxyZW1vdmU6ZnVuY3Rpb24oZSl7dmFyIHQ9dGhpcy5fbmFtZXMuaW5kZXhPZihlKTt0Pj0wJiYodGhpcy5fbmFtZXMuc3BsaWNlKHQsMSksdGhpcy5fbm9kZS5zZXRBdHRyaWJ1dGUoImNsYXNzIix0aGlzLl9uYW1lcy5qb2luKCIgIikpKX0sY29udGFpbnM6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX25hbWVzLmluZGV4T2YoZSk+PTB9fTtmdW5jdGlvbiB6X3QoZSx0KXtmb3IodmFyIHI9SlcoZSksbj0tMSxpPXQubGVuZ3RoOysrbjxpOylyLmFkZCh0W25dKX1mdW5jdGlvbiBGX3QoZSx0KXtmb3IodmFyIHI9SlcoZSksbj0tMSxpPXQubGVuZ3RoOysrbjxpOylyLnJlbW92ZSh0W25dKX1mdW5jdGlvbiBrMmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7el90KHRoaXMsZSl9fWZ1bmN0aW9uIFIyZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtGX3QodGhpcyxlKX19ZnVuY3Rpb24gTjJlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7KHQuYXBwbHkodGhpcyxhcmd1bWVudHMpP3pfdDpGX3QpKHRoaXMsZSl9fWZ1bmN0aW9uIEJfdChlLHQpe3ZhciByPURfdChlKyIiKTtpZihhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcih2YXIgbj1KVyh0aGlzLm5vZGUoKSksaT0tMSxvPXIubGVuZ3RoOysraTxvOylpZighbi5jb250YWlucyhyW2ldKSlyZXR1cm4hMTtyZXR1cm4hMH1yZXR1cm4gdGhpcy5lYWNoKCh0eXBlb2YgdD09ImZ1bmN0aW9uIj9OMmU6dD9rMmU6UjJlKShyLHQpKX1mdW5jdGlvbiBEMmUoKXt0aGlzLnRleHRDb250ZW50PSIifWZ1bmN0aW9uIE8yZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnRleHRDb250ZW50PWV9fWZ1bmN0aW9uIHoyZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD1lLmFwcGx5KHRoaXMsYXJndW1lbnRzKTt0aGlzLnRleHRDb250ZW50PXQ9PW51bGw/IiI6dH19ZnVuY3Rpb24gSF90KGUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaChlPT1udWxsP0QyZToodHlwZW9mIGU9PSJmdW5jdGlvbiI/ejJlOk8yZSkoZSkpOnRoaXMubm9kZSgpLnRleHRDb250ZW50fWZ1bmN0aW9uIEYyZSgpe3RoaXMuaW5uZXJIVE1MPSIifWZ1bmN0aW9uIEIyZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLmlubmVySFRNTD1lfX1mdW5jdGlvbiBIMmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyk7dGhpcy5pbm5lckhUTUw9dD09bnVsbD8iIjp0fX1mdW5jdGlvbiBWX3QoZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5lYWNoKGU9PW51bGw/RjJlOih0eXBlb2YgZT09ImZ1bmN0aW9uIj9IMmU6QjJlKShlKSk6dGhpcy5ub2RlKCkuaW5uZXJIVE1MfWZ1bmN0aW9uIFYyZSgpe3RoaXMubmV4dFNpYmxpbmcmJnRoaXMucGFyZW50Tm9kZS5hcHBlbmRDaGlsZCh0aGlzKX1mdW5jdGlvbiBVX3QoKXtyZXR1cm4gdGhpcy5lYWNoKFYyZSl9ZnVuY3Rpb24gVTJlKCl7dGhpcy5wcmV2aW91c1NpYmxpbmcmJnRoaXMucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUodGhpcyx0aGlzLnBhcmVudE5vZGUuZmlyc3RDaGlsZCl9ZnVuY3Rpb24gcV90KCl7cmV0dXJuIHRoaXMuZWFjaChVMmUpfWZ1bmN0aW9uIEdfdChlKXt2YXIgdD10eXBlb2YgZT09ImZ1bmN0aW9uIj9lOlJtKGUpO3JldHVybiB0aGlzLnNlbGVjdChmdW5jdGlvbigpe3JldHVybiB0aGlzLmFwcGVuZENoaWxkKHQuYXBwbHkodGhpcyxhcmd1bWVudHMpKX0pfWZ1bmN0aW9uIHEyZSgpe3JldHVybiBudWxsfWZ1bmN0aW9uIFdfdChlLHQpe3ZhciByPXR5cGVvZiBlPT0iZnVuY3Rpb24iP2U6Um0oZSksbj10PT1udWxsP3EyZTp0eXBlb2YgdD09ImZ1bmN0aW9uIj90Ok5tKHQpO3JldHVybiB0aGlzLnNlbGVjdChmdW5jdGlvbigpe3JldHVybiB0aGlzLmluc2VydEJlZm9yZShyLmFwcGx5KHRoaXMsYXJndW1lbnRzKSxuLmFwcGx5KHRoaXMsYXJndW1lbnRzKXx8bnVsbCl9KX1mdW5jdGlvbiBHMmUoKXt2YXIgZT10aGlzLnBhcmVudE5vZGU7ZSYmZS5yZW1vdmVDaGlsZCh0aGlzKX1mdW5jdGlvbiBZX3QoKXtyZXR1cm4gdGhpcy5lYWNoKEcyZSl9ZnVuY3Rpb24gVzJlKCl7dmFyIGU9dGhpcy5jbG9uZU5vZGUoITEpLHQ9dGhpcy5wYXJlbnROb2RlO3JldHVybiB0P3QuaW5zZXJ0QmVmb3JlKGUsdGhpcy5uZXh0U2libGluZyk6ZX1mdW5jdGlvbiBZMmUoKXt2YXIgZT10aGlzLmNsb25lTm9kZSghMCksdD10aGlzLnBhcmVudE5vZGU7cmV0dXJuIHQ/dC5pbnNlcnRCZWZvcmUoZSx0aGlzLm5leHRTaWJsaW5nKTplfWZ1bmN0aW9uIGpfdChlKXtyZXR1cm4gdGhpcy5zZWxlY3QoZT9ZMmU6VzJlKX1mdW5jdGlvbiBYX3QoZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5wcm9wZXJ0eSgiX19kYXRhX18iLGUpOnRoaXMubm9kZSgpLl9fZGF0YV9ffXZhciBLX3Q9e30scXQ9bnVsbDt0eXBlb2YgZG9jdW1lbnQhPSJ1bmRlZmluZWQiJiYoJF90PWRvY3VtZW50LmRvY3VtZW50RWxlbWVudCwib25tb3VzZWVudGVyImluICRfdHx8KEtfdD17bW91c2VlbnRlcjoibW91c2VvdmVyIixtb3VzZWxlYXZlOiJtb3VzZW91dCJ9KSk7dmFyICRfdDtmdW5jdGlvbiBqMmUoZSx0LHIpe3JldHVybiBlPVpfdChlLHQsciksZnVuY3Rpb24obil7dmFyIGk9bi5yZWxhdGVkVGFyZ2V0OyghaXx8aSE9PXRoaXMmJiEoaS5jb21wYXJlRG9jdW1lbnRQb3NpdGlvbih0aGlzKSY4KSkmJmUuY2FsbCh0aGlzLG4pfX1mdW5jdGlvbiBaX3QoZSx0LHIpe3JldHVybiBmdW5jdGlvbihuKXt2YXIgaT1xdDtxdD1uO3RyeXtlLmNhbGwodGhpcyx0aGlzLl9fZGF0YV9fLHQscil9ZmluYWxseXtxdD1pfX19ZnVuY3Rpb24gWDJlKGUpe3JldHVybiBlLnRyaW0oKS5zcGxpdCgvXnxccysvKS5tYXAoZnVuY3Rpb24odCl7dmFyIHI9IiIsbj10LmluZGV4T2YoIi4iKTtyZXR1cm4gbj49MCYmKHI9dC5zbGljZShuKzEpLHQ9dC5zbGljZSgwLG4pKSx7dHlwZTp0LG5hbWU6cn19KX1mdW5jdGlvbiAkMmUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5fX29uO2lmKCEhdCl7Zm9yKHZhciByPTAsbj0tMSxpPXQubGVuZ3RoLG87cjxpOysrcilvPXRbcl0sKCFlLnR5cGV8fG8udHlwZT09PWUudHlwZSkmJm8ubmFtZT09PWUubmFtZT90aGlzLnJlbW92ZUV2ZW50TGlzdGVuZXIoby50eXBlLG8ubGlzdGVuZXIsby5jYXB0dXJlKTp0Wysrbl09bzsrK24/dC5sZW5ndGg9bjpkZWxldGUgdGhpcy5fX29ufX19ZnVuY3Rpb24gSzJlKGUsdCxyKXt2YXIgbj1LX3QuaGFzT3duUHJvcGVydHkoZS50eXBlKT9qMmU6Wl90O3JldHVybiBmdW5jdGlvbihpLG8sYSl7dmFyIHM9dGhpcy5fX29uLGwsYz1uKHQsbyxhKTtpZihzKXtmb3IodmFyIHU9MCxoPXMubGVuZ3RoO3U8aDsrK3UpaWYoKGw9c1t1XSkudHlwZT09PWUudHlwZSYmbC5uYW1lPT09ZS5uYW1lKXt0aGlzLnJlbW92ZUV2ZW50TGlzdGVuZXIobC50eXBlLGwubGlzdGVuZXIsbC5jYXB0dXJlKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIobC50eXBlLGwubGlzdGVuZXI9YyxsLmNhcHR1cmU9ciksbC52YWx1ZT10O3JldHVybn19dGhpcy5hZGRFdmVudExpc3RlbmVyKGUudHlwZSxjLHIpLGw9e3R5cGU6ZS50eXBlLG5hbWU6ZS5uYW1lLHZhbHVlOnQsbGlzdGVuZXI6YyxjYXB0dXJlOnJ9LHM/cy5wdXNoKGwpOnRoaXMuX19vbj1bbF19fWZ1bmN0aW9uIEpfdChlLHQscil7dmFyIG49WDJlKGUrIiIpLGksbz1uLmxlbmd0aCxhO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7dmFyIHM9dGhpcy5ub2RlKCkuX19vbjtpZihzKXtmb3IodmFyIGw9MCxjPXMubGVuZ3RoLHU7bDxjOysrbClmb3IoaT0wLHU9c1tsXTtpPG87KytpKWlmKChhPW5baV0pLnR5cGU9PT11LnR5cGUmJmEubmFtZT09PXUubmFtZSlyZXR1cm4gdS52YWx1ZX1yZXR1cm59Zm9yKHM9dD9LMmU6JDJlLHI9PW51bGwmJihyPSExKSxpPTA7aTxvOysraSl0aGlzLmVhY2gocyhuW2ldLHQscikpO3JldHVybiB0aGlzfWZ1bmN0aW9uIE1wKGUsdCxyLG4pe3ZhciBpPXF0O2Uuc291cmNlRXZlbnQ9cXQscXQ9ZTt0cnl7cmV0dXJuIHQuYXBwbHkocixuKX1maW5hbGx5e3F0PWl9fWZ1bmN0aW9uIFFfdChlLHQscil7dmFyIG49aGIoZSksaT1uLkN1c3RvbUV2ZW50O3R5cGVvZiBpPT0iZnVuY3Rpb24iP2k9bmV3IGkodCxyKTooaT1uLmRvY3VtZW50LmNyZWF0ZUV2ZW50KCJFdmVudCIpLHI/KGkuaW5pdEV2ZW50KHQsci5idWJibGVzLHIuY2FuY2VsYWJsZSksaS5kZXRhaWw9ci5kZXRhaWwpOmkuaW5pdEV2ZW50KHQsITEsITEpKSxlLmRpc3BhdGNoRXZlbnQoaSl9ZnVuY3Rpb24gWjJlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIFFfdCh0aGlzLGUsdCl9fWZ1bmN0aW9uIEoyZShlLHQpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBRX3QodGhpcyxlLHQuYXBwbHkodGhpcyxhcmd1bWVudHMpKX19ZnVuY3Rpb24gdHl0KGUsdCl7cmV0dXJuIHRoaXMuZWFjaCgodHlwZW9mIHQ9PSJmdW5jdGlvbiI/SjJlOloyZSkoZSx0KSl9dmFyIE1FPVtudWxsXTtmdW5jdGlvbiBEbihlLHQpe3RoaXMuX2dyb3Vwcz1lLHRoaXMuX3BhcmVudHM9dH1mdW5jdGlvbiBleXQoKXtyZXR1cm4gbmV3IERuKFtbZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50XV0sTUUpfURuLnByb3RvdHlwZT1leXQucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpEbixzZWxlY3Q6ZF90LHNlbGVjdEFsbDptX3QsZmlsdGVyOmdfdCxkYXRhOnhfdCxlbnRlcjpfX3QsZXhpdDpiX3Qsam9pbjp3X3QsbWVyZ2U6U190LG9yZGVyOk1fdCxzb3J0OkVfdCxjYWxsOlRfdCxub2RlczpDX3Qsbm9kZTpBX3Qsc2l6ZTpQX3QsZW1wdHk6SV90LGVhY2g6TF90LGF0dHI6a190LHN0eWxlOlJfdCxwcm9wZXJ0eTpOX3QsY2xhc3NlZDpCX3QsdGV4dDpIX3QsaHRtbDpWX3QscmFpc2U6VV90LGxvd2VyOnFfdCxhcHBlbmQ6R190LGluc2VydDpXX3QscmVtb3ZlOllfdCxjbG9uZTpqX3QsZGF0dW06WF90LG9uOkpfdCxkaXNwYXRjaDp0eXR9O3ZhciBJaD1leXQ7ZnVuY3Rpb24gSHQoZSl7cmV0dXJuIHR5cGVvZiBlPT0ic3RyaW5nIj9uZXcgRG4oW1tkb2N1bWVudC5xdWVyeVNlbGVjdG9yKGUpXV0sW2RvY3VtZW50LmRvY3VtZW50RWxlbWVudF0pOm5ldyBEbihbW2VdXSxNRSl9ZnVuY3Rpb24gcnl0KGUpe3JldHVybiBIdChSbShlKS5jYWxsKGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCkpfXZhciBRMmU9MDtmdW5jdGlvbiB0TCgpe3JldHVybiBuZXcgUVd9ZnVuY3Rpb24gUVcoKXt0aGlzLl89IkAiKygrK1EyZSkudG9TdHJpbmcoMzYpfVFXLnByb3RvdHlwZT10TC5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOlFXLGdldDpmdW5jdGlvbihlKXtmb3IodmFyIHQ9dGhpcy5fOyEodCBpbiBlKTspaWYoIShlPWUucGFyZW50Tm9kZSkpcmV0dXJuO3JldHVybiBlW3RdfSxzZXQ6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gZVt0aGlzLl9dPXR9LHJlbW92ZTpmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy5fIGluIGUmJmRlbGV0ZSBlW3RoaXMuX119LHRvU3RyaW5nOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX319O2Z1bmN0aW9uIGZiKCl7Zm9yKHZhciBlPXF0LHQ7dD1lLnNvdXJjZUV2ZW50OyllPXQ7cmV0dXJuIGV9ZnVuY3Rpb24gRG0oZSx0KXt2YXIgcj1lLm93bmVyU1ZHRWxlbWVudHx8ZTtpZihyLmNyZWF0ZVNWR1BvaW50KXt2YXIgbj1yLmNyZWF0ZVNWR1BvaW50KCk7cmV0dXJuIG4ueD10LmNsaWVudFgsbi55PXQuY2xpZW50WSxuPW4ubWF0cml4VHJhbnNmb3JtKGUuZ2V0U2NyZWVuQ1RNKCkuaW52ZXJzZSgpKSxbbi54LG4ueV19dmFyIGk9ZS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtyZXR1cm5bdC5jbGllbnRYLWkubGVmdC1lLmNsaWVudExlZnQsdC5jbGllbnRZLWkudG9wLWUuY2xpZW50VG9wXX1mdW5jdGlvbiB6byhlKXt2YXIgdD1mYigpO3JldHVybiB0LmNoYW5nZWRUb3VjaGVzJiYodD10LmNoYW5nZWRUb3VjaGVzWzBdKSxEbShlLHQpfWZ1bmN0aW9uIEVwKGUpe3JldHVybiB0eXBlb2YgZT09InN0cmluZyI/bmV3IERuKFtkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKGUpXSxbZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50XSk6bmV3IERuKFtlPT1udWxsP1tdOmVdLE1FKX1mdW5jdGlvbiBUcChlLHQscil7YXJndW1lbnRzLmxlbmd0aDwzJiYocj10LHQ9ZmIoKS5jaGFuZ2VkVG91Y2hlcyk7Zm9yKHZhciBuPTAsaT10P3QubGVuZ3RoOjAsbztuPGk7KytuKWlmKChvPXRbbl0pLmlkZW50aWZpZXI9PT1yKXJldHVybiBEbShlLG8pO3JldHVybiBudWxsfWZ1bmN0aW9uIG55dChlLHQpe3Q9PW51bGwmJih0PWZiKCkudG91Y2hlcyk7Zm9yKHZhciByPTAsbj10P3QubGVuZ3RoOjAsaT1uZXcgQXJyYXkobik7cjxuOysrcilpW3JdPURtKGUsdFtyXSk7cmV0dXJuIGl9ZnVuY3Rpb24gZUwoKXtxdC5zdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24oKX1mdW5jdGlvbiBPbSgpe3F0LnByZXZlbnREZWZhdWx0KCkscXQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCl9ZnVuY3Rpb24gem0oZSl7dmFyIHQ9ZS5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQscj1IdChlKS5vbigiZHJhZ3N0YXJ0LmRyYWciLE9tLCEwKTsib25zZWxlY3RzdGFydCJpbiB0P3Iub24oInNlbGVjdHN0YXJ0LmRyYWciLE9tLCEwKToodC5fX25vc2VsZWN0PXQuc3R5bGUuTW96VXNlclNlbGVjdCx0LnN0eWxlLk1velVzZXJTZWxlY3Q9Im5vbmUiKX1mdW5jdGlvbiBGbShlLHQpe3ZhciByPWUuZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LG49SHQoZSkub24oImRyYWdzdGFydC5kcmFnIixudWxsKTt0JiYobi5vbigiY2xpY2suZHJhZyIsT20sITApLHNldFRpbWVvdXQoZnVuY3Rpb24oKXtuLm9uKCJjbGljay5kcmFnIixudWxsKX0sMCkpLCJvbnNlbGVjdHN0YXJ0ImluIHI/bi5vbigic2VsZWN0c3RhcnQuZHJhZyIsbnVsbCk6KHIuc3R5bGUuTW96VXNlclNlbGVjdD1yLl9fbm9zZWxlY3QsZGVsZXRlIHIuX19ub3NlbGVjdCl9ZnVuY3Rpb24gRUUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fWZ1bmN0aW9uIFRFKGUsdCxyLG4saSxvLGEscyxsLGMpe3RoaXMudGFyZ2V0PWUsdGhpcy50eXBlPXQsdGhpcy5zdWJqZWN0PXIsdGhpcy5pZGVudGlmaWVyPW4sdGhpcy5hY3RpdmU9aSx0aGlzLng9byx0aGlzLnk9YSx0aGlzLmR4PXMsdGhpcy5keT1sLHRoaXMuXz1jfVRFLnByb3RvdHlwZS5vbj1mdW5jdGlvbigpe3ZhciBlPXRoaXMuXy5vbi5hcHBseSh0aGlzLl8sYXJndW1lbnRzKTtyZXR1cm4gZT09PXRoaXMuXz90aGlzOmV9O2Z1bmN0aW9uIHR3ZSgpe3JldHVybiFxdC5jdHJsS2V5JiYhcXQuYnV0dG9ufWZ1bmN0aW9uIGV3ZSgpe3JldHVybiB0aGlzLnBhcmVudE5vZGV9ZnVuY3Rpb24gcndlKGUpe3JldHVybiBlPT1udWxsP3t4OnF0LngseTpxdC55fTplfWZ1bmN0aW9uIG53ZSgpe3JldHVybiBuYXZpZ2F0b3IubWF4VG91Y2hQb2ludHN8fCJvbnRvdWNoc3RhcnQiaW4gdGhpc31mdW5jdGlvbiBwYigpe3ZhciBlPXR3ZSx0PWV3ZSxyPXJ3ZSxuPW53ZSxpPXt9LG89dnMoInN0YXJ0IiwiZHJhZyIsImVuZCIpLGE9MCxzLGwsYyx1LGg9MDtmdW5jdGlvbiBmKFMpe1Mub24oIm1vdXNlZG93bi5kcmFnIixwKS5maWx0ZXIobikub24oInRvdWNoc3RhcnQuZHJhZyIsXykub24oInRvdWNobW92ZS5kcmFnIix5KS5vbigidG91Y2hlbmQuZHJhZyB0b3VjaGNhbmNlbC5kcmFnIix4KS5zdHlsZSgidG91Y2gtYWN0aW9uIiwibm9uZSIpLnN0eWxlKCItd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3IiLCJyZ2JhKDAsMCwwLDApIil9ZnVuY3Rpb24gcCgpe2lmKCEodXx8IWUuYXBwbHkodGhpcyxhcmd1bWVudHMpKSl7dmFyIFM9YigibW91c2UiLHQuYXBwbHkodGhpcyxhcmd1bWVudHMpLHpvLHRoaXMsYXJndW1lbnRzKTshU3x8KEh0KHF0LnZpZXcpLm9uKCJtb3VzZW1vdmUuZHJhZyIsZCwhMCkub24oIm1vdXNldXAuZHJhZyIsZywhMCksem0ocXQudmlldyksZUwoKSxjPSExLHM9cXQuY2xpZW50WCxsPXF0LmNsaWVudFksUygic3RhcnQiKSl9fWZ1bmN0aW9uIGQoKXtpZihPbSgpLCFjKXt2YXIgUz1xdC5jbGllbnRYLXMsQz1xdC5jbGllbnRZLWw7Yz1TKlMrQypDPmh9aS5tb3VzZSgiZHJhZyIpfWZ1bmN0aW9uIGcoKXtIdChxdC52aWV3KS5vbigibW91c2Vtb3ZlLmRyYWcgbW91c2V1cC5kcmFnIixudWxsKSxGbShxdC52aWV3LGMpLE9tKCksaS5tb3VzZSgiZW5kIil9ZnVuY3Rpb24gXygpe2lmKCEhZS5hcHBseSh0aGlzLGFyZ3VtZW50cykpe3ZhciBTPXF0LmNoYW5nZWRUb3VjaGVzLEM9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyksUD1TLmxlbmd0aCxrLE87Zm9yKGs9MDtrPFA7KytrKShPPWIoU1trXS5pZGVudGlmaWVyLEMsVHAsdGhpcyxhcmd1bWVudHMpKSYmKGVMKCksTygic3RhcnQiKSl9fWZ1bmN0aW9uIHkoKXt2YXIgUz1xdC5jaGFuZ2VkVG91Y2hlcyxDPVMubGVuZ3RoLFAsaztmb3IoUD0wO1A8QzsrK1ApKGs9aVtTW1BdLmlkZW50aWZpZXJdKSYmKE9tKCksaygiZHJhZyIpKX1mdW5jdGlvbiB4KCl7dmFyIFM9cXQuY2hhbmdlZFRvdWNoZXMsQz1TLmxlbmd0aCxQLGs7Zm9yKHUmJmNsZWFyVGltZW91dCh1KSx1PXNldFRpbWVvdXQoZnVuY3Rpb24oKXt1PW51bGx9LDUwMCksUD0wO1A8QzsrK1ApKGs9aVtTW1BdLmlkZW50aWZpZXJdKSYmKGVMKCksaygiZW5kIikpfWZ1bmN0aW9uIGIoUyxDLFAsayxPKXt2YXIgRD1QKEMsUyksQixJLEwsUj1vLmNvcHkoKTtpZighIU1wKG5ldyBURShmLCJiZWZvcmVzdGFydCIsQixTLGEsRFswXSxEWzFdLDAsMCxSKSxmdW5jdGlvbigpe3JldHVybihxdC5zdWJqZWN0PUI9ci5hcHBseShrLE8pKT09bnVsbD8hMTooST1CLngtRFswXXx8MCxMPUIueS1EWzFdfHwwLCEwKX0pKXJldHVybiBmdW5jdGlvbiBGKHope3ZhciBVPUQsVztzd2l0Y2goeil7Y2FzZSJzdGFydCI6aVtTXT1GLFc9YSsrO2JyZWFrO2Nhc2UiZW5kIjpkZWxldGUgaVtTXSwtLWE7Y2FzZSJkcmFnIjpEPVAoQyxTKSxXPWE7YnJlYWt9TXAobmV3IFRFKGYseixCLFMsVyxEWzBdK0ksRFsxXStMLERbMF0tVVswXSxEWzFdLVVbMV0sUiksUi5hcHBseSxSLFt6LGssT10pfX1yZXR1cm4gZi5maWx0ZXI9ZnVuY3Rpb24oUyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIFM9PSJmdW5jdGlvbiI/UzpFRSghIVMpLGYpOmV9LGYuY29udGFpbmVyPWZ1bmN0aW9uKFMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBTPT0iZnVuY3Rpb24iP1M6RUUoUyksZik6dH0sZi5zdWJqZWN0PWZ1bmN0aW9uKFMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPXR5cGVvZiBTPT0iZnVuY3Rpb24iP1M6RUUoUyksZik6cn0sZi50b3VjaGFibGU9ZnVuY3Rpb24oUyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49dHlwZW9mIFM9PSJmdW5jdGlvbiI/UzpFRSghIVMpLGYpOm59LGYub249ZnVuY3Rpb24oKXt2YXIgUz1vLm9uLmFwcGx5KG8sYXJndW1lbnRzKTtyZXR1cm4gUz09PW8/ZjpTfSxmLmNsaWNrRGlzdGFuY2U9ZnVuY3Rpb24oUyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGg9KFM9K1MpKlMsZik6TWF0aC5zcXJ0KGgpfSxmfWZ1bmN0aW9uIENwKGUsdCxyKXtlLnByb3RvdHlwZT10LnByb3RvdHlwZT1yLHIuY29uc3RydWN0b3I9ZX1mdW5jdGlvbiBCbShlLHQpe3ZhciByPU9iamVjdC5jcmVhdGUoZS5wcm90b3R5cGUpO2Zvcih2YXIgbiBpbiB0KXJbbl09dFtuXTtyZXR1cm4gcn1mdW5jdGlvbiBSaCgpe312YXIgSG09LjcsYl89MS9IbSxkYj0iXFxzKihbKy1dP1xcZCspXFxzKiIsQ0U9IlxccyooWystXT9cXGQqXFwuP1xcZCsoPzpbZUVdWystXT9cXGQrKT8pXFxzKiIsa2g9IlxccyooWystXT9cXGQqXFwuP1xcZCsoPzpbZUVdWystXT9cXGQrKT8pJVxccyoiLGl3ZT0vXiMoWzAtOWEtZl17Myw4fSkkLyxvd2U9bmV3IFJlZ0V4cCgiXnJnYlxcKCIrW2RiLGRiLGRiXSsiXFwpJCIpLGF3ZT1uZXcgUmVnRXhwKCJecmdiXFwoIitba2gsa2gsa2hdKyJcXCkkIiksc3dlPW5ldyBSZWdFeHAoIl5yZ2JhXFwoIitbZGIsZGIsZGIsQ0VdKyJcXCkkIiksbHdlPW5ldyBSZWdFeHAoIl5yZ2JhXFwoIitba2gsa2gsa2gsQ0VdKyJcXCkkIiksY3dlPW5ldyBSZWdFeHAoIl5oc2xcXCgiK1tDRSxraCxraF0rIlxcKSQiKSx1d2U9bmV3IFJlZ0V4cCgiXmhzbGFcXCgiK1tDRSxraCxraCxDRV0rIlxcKSQiKSxpeXQ9e2FsaWNlYmx1ZToxNTc5MjM4MyxhbnRpcXVld2hpdGU6MTY0NDQzNzUsYXF1YTo2NTUzNSxhcXVhbWFyaW5lOjgzODg1NjQsYXp1cmU6MTU3OTQxNzUsYmVpZ2U6MTYxMTkyNjAsYmlzcXVlOjE2NzcwMjQ0LGJsYWNrOjAsYmxhbmNoZWRhbG1vbmQ6MTY3NzIwNDUsYmx1ZToyNTUsYmx1ZXZpb2xldDo5MDU1MjAyLGJyb3duOjEwODI0MjM0LGJ1cmx5d29vZDoxNDU5NjIzMSxjYWRldGJsdWU6NjI2NjUyOCxjaGFydHJldXNlOjgzODgzNTIsY2hvY29sYXRlOjEzNzg5NDcwLGNvcmFsOjE2NzQ0MjcyLGNvcm5mbG93ZXJibHVlOjY1OTE5ODEsY29ybnNpbGs6MTY3NzUzODgsY3JpbXNvbjoxNDQyMzEwMCxjeWFuOjY1NTM1LGRhcmtibHVlOjEzOSxkYXJrY3lhbjozNTcyMyxkYXJrZ29sZGVucm9kOjEyMDkyOTM5LGRhcmtncmF5OjExMTE5MDE3LGRhcmtncmVlbjoyNTYwMCxkYXJrZ3JleToxMTExOTAxNyxkYXJra2hha2k6MTI0MzMyNTksZGFya21hZ2VudGE6OTEwOTY0MyxkYXJrb2xpdmVncmVlbjo1NTk3OTk5LGRhcmtvcmFuZ2U6MTY3NDc1MjAsZGFya29yY2hpZDoxMDA0MDAxMixkYXJrcmVkOjkxMDk1MDQsZGFya3NhbG1vbjoxNTMwODQxMCxkYXJrc2VhZ3JlZW46OTQxOTkxOSxkYXJrc2xhdGVibHVlOjQ3MzQzNDcsZGFya3NsYXRlZ3JheTozMTAwNDk1LGRhcmtzbGF0ZWdyZXk6MzEwMDQ5NSxkYXJrdHVycXVvaXNlOjUyOTQ1LGRhcmt2aW9sZXQ6OTY5OTUzOSxkZWVwcGluazoxNjcxNjk0NyxkZWVwc2t5Ymx1ZTo0OTE1MSxkaW1ncmF5OjY5MDgyNjUsZGltZ3JleTo2OTA4MjY1LGRvZGdlcmJsdWU6MjAwMzE5OSxmaXJlYnJpY2s6MTE2NzQxNDYsZmxvcmFsd2hpdGU6MTY3NzU5MjAsZm9yZXN0Z3JlZW46MjI2Mzg0MixmdWNoc2lhOjE2NzExOTM1LGdhaW5zYm9ybzoxNDQ3NDQ2MCxnaG9zdHdoaXRlOjE2MzE2NjcxLGdvbGQ6MTY3NjY3MjAsZ29sZGVucm9kOjE0MzI5MTIwLGdyYXk6ODQyMTUwNCxncmVlbjozMjc2OCxncmVlbnllbGxvdzoxMTQwMzA1NSxncmV5Ojg0MjE1MDQsaG9uZXlkZXc6MTU3OTQxNjAsaG90cGluazoxNjczODc0MCxpbmRpYW5yZWQ6MTM0NTg1MjQsaW5kaWdvOjQ5MTUzMzAsaXZvcnk6MTY3NzcyMDAsa2hha2k6MTU3ODc2NjAsbGF2ZW5kZXI6MTUxMzI0MTAsbGF2ZW5kZXJibHVzaDoxNjc3MzM2NSxsYXduZ3JlZW46ODE5MDk3NixsZW1vbmNoaWZmb246MTY3NzU4ODUsbGlnaHRibHVlOjExMzkzMjU0LGxpZ2h0Y29yYWw6MTU3NjE1MzYsbGlnaHRjeWFuOjE0NzQ1NTk5LGxpZ2h0Z29sZGVucm9keWVsbG93OjE2NDQ4MjEwLGxpZ2h0Z3JheToxMzg4MjMyMyxsaWdodGdyZWVuOjk0OTgyNTYsbGlnaHRncmV5OjEzODgyMzIzLGxpZ2h0cGluazoxNjc1ODQ2NSxsaWdodHNhbG1vbjoxNjc1Mjc2MixsaWdodHNlYWdyZWVuOjIxNDI4OTAsbGlnaHRza3libHVlOjg5MDAzNDYsbGlnaHRzbGF0ZWdyYXk6NzgzMzc1MyxsaWdodHNsYXRlZ3JleTo3ODMzNzUzLGxpZ2h0c3RlZWxibHVlOjExNTg0NzM0LGxpZ2h0eWVsbG93OjE2Nzc3MTg0LGxpbWU6NjUyODAsbGltZWdyZWVuOjMzMjkzMzAsbGluZW46MTY0NDU2NzAsbWFnZW50YToxNjcxMTkzNSxtYXJvb246ODM4ODYwOCxtZWRpdW1hcXVhbWFyaW5lOjY3MzczMjIsbWVkaXVtYmx1ZToyMDUsbWVkaXVtb3JjaGlkOjEyMjExNjY3LG1lZGl1bXB1cnBsZTo5NjYyNjgzLG1lZGl1bXNlYWdyZWVuOjM5NzgwOTcsbWVkaXVtc2xhdGVibHVlOjgwODc3OTAsbWVkaXVtc3ByaW5nZ3JlZW46NjQxNTQsbWVkaXVtdHVycXVvaXNlOjQ3NzIzMDAsbWVkaXVtdmlvbGV0cmVkOjEzMDQ3MTczLG1pZG5pZ2h0Ymx1ZToxNjQ0OTEyLG1pbnRjcmVhbToxNjEyMTg1MCxtaXN0eXJvc2U6MTY3NzAyNzMsbW9jY2FzaW46MTY3NzAyMjksbmF2YWpvd2hpdGU6MTY3Njg2ODUsbmF2eToxMjgsb2xkbGFjZToxNjY0MzU1OCxvbGl2ZTo4NDIxMzc2LG9saXZlZHJhYjo3MDQ4NzM5LG9yYW5nZToxNjc1MzkyMCxvcmFuZ2VyZWQ6MTY3MjkzNDQsb3JjaGlkOjE0MzE1NzM0LHBhbGVnb2xkZW5yb2Q6MTU2NTcxMzAscGFsZWdyZWVuOjEwMDI1ODgwLHBhbGV0dXJxdW9pc2U6MTE1Mjk5NjYscGFsZXZpb2xldHJlZDoxNDM4MTIwMyxwYXBheWF3aGlwOjE2NzczMDc3LHBlYWNocHVmZjoxNjc2NzY3MyxwZXJ1OjEzNDY4OTkxLHBpbms6MTY3NjEwMzUscGx1bToxNDUyNDYzNyxwb3dkZXJibHVlOjExNTkxOTEwLHB1cnBsZTo4Mzg4NzM2LHJlYmVjY2FwdXJwbGU6NjY5Nzg4MSxyZWQ6MTY3MTE2ODAscm9zeWJyb3duOjEyMzU3NTE5LHJveWFsYmx1ZTo0Mjg2OTQ1LHNhZGRsZWJyb3duOjkxMjcxODcsc2FsbW9uOjE2NDE2ODgyLHNhbmR5YnJvd246MTYwMzI4NjQsc2VhZ3JlZW46MzA1MDMyNyxzZWFzaGVsbDoxNjc3NDYzOCxzaWVubmE6MTA1MDY3OTcsc2lsdmVyOjEyNjMyMjU2LHNreWJsdWU6ODkwMDMzMSxzbGF0ZWJsdWU6Njk3MDA2MSxzbGF0ZWdyYXk6NzM3Mjk0NCxzbGF0ZWdyZXk6NzM3Mjk0NCxzbm93OjE2Nzc1OTMwLHNwcmluZ2dyZWVuOjY1NDA3LHN0ZWVsYmx1ZTo0NjIwOTgwLHRhbjoxMzgwODc4MCx0ZWFsOjMyODk2LHRoaXN0bGU6MTQyMDQ4ODgsdG9tYXRvOjE2NzM3MDk1LHR1cnF1b2lzZTo0MjUxODU2LHZpb2xldDoxNTYzMTA4Nix3aGVhdDoxNjExMzMzMSx3aGl0ZToxNjc3NzIxNSx3aGl0ZXNtb2tlOjE2MTE5Mjg1LHllbGxvdzoxNjc3Njk2MCx5ZWxsb3dncmVlbjoxMDE0NTA3NH07Q3AoUmgscmMse2NvcHk6ZnVuY3Rpb24oZSl7cmV0dXJuIE9iamVjdC5hc3NpZ24obmV3IHRoaXMuY29uc3RydWN0b3IsdGhpcyxlKX0sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5yZ2IoKS5kaXNwbGF5YWJsZSgpfSxoZXg6b3l0LGZvcm1hdEhleDpveXQsZm9ybWF0SHNsOmh3ZSxmb3JtYXRSZ2I6YXl0LHRvU3RyaW5nOmF5dH0pO2Z1bmN0aW9uIG95dCgpe3JldHVybiB0aGlzLnJnYigpLmZvcm1hdEhleCgpfWZ1bmN0aW9uIGh3ZSgpe3JldHVybiBoeXQodGhpcykuZm9ybWF0SHNsKCl9ZnVuY3Rpb24gYXl0KCl7cmV0dXJuIHRoaXMucmdiKCkuZm9ybWF0UmdiKCl9ZnVuY3Rpb24gcmMoZSl7dmFyIHQscjtyZXR1cm4gZT0oZSsiIikudHJpbSgpLnRvTG93ZXJDYXNlKCksKHQ9aXdlLmV4ZWMoZSkpPyhyPXRbMV0ubGVuZ3RoLHQ9cGFyc2VJbnQodFsxXSwxNikscj09PTY/c3l0KHQpOnI9PT0zP25ldyBLaSh0Pj44JjE1fHQ+PjQmMjQwLHQ+PjQmMTV8dCYyNDAsKHQmMTUpPDw0fHQmMTUsMSk6cj09PTg/ckwodD4+MjQmMjU1LHQ+PjE2JjI1NSx0Pj44JjI1NSwodCYyNTUpLzI1NSk6cj09PTQ/ckwodD4+MTImMTV8dD4+OCYyNDAsdD4+OCYxNXx0Pj40JjI0MCx0Pj40JjE1fHQmMjQwLCgodCYxNSk8PDR8dCYxNSkvMjU1KTpudWxsKToodD1vd2UuZXhlYyhlKSk/bmV3IEtpKHRbMV0sdFsyXSx0WzNdLDEpOih0PWF3ZS5leGVjKGUpKT9uZXcgS2kodFsxXSoyNTUvMTAwLHRbMl0qMjU1LzEwMCx0WzNdKjI1NS8xMDAsMSk6KHQ9c3dlLmV4ZWMoZSkpP3JMKHRbMV0sdFsyXSx0WzNdLHRbNF0pOih0PWx3ZS5leGVjKGUpKT9yTCh0WzFdKjI1NS8xMDAsdFsyXSoyNTUvMTAwLHRbM10qMjU1LzEwMCx0WzRdKToodD1jd2UuZXhlYyhlKSk/dXl0KHRbMV0sdFsyXS8xMDAsdFszXS8xMDAsMSk6KHQ9dXdlLmV4ZWMoZSkpP3V5dCh0WzFdLHRbMl0vMTAwLHRbM10vMTAwLHRbNF0pOml5dC5oYXNPd25Qcm9wZXJ0eShlKT9zeXQoaXl0W2VdKTplPT09InRyYW5zcGFyZW50Ij9uZXcgS2koTmFOLE5hTixOYU4sMCk6bnVsbH1mdW5jdGlvbiBzeXQoZSl7cmV0dXJuIG5ldyBLaShlPj4xNiYyNTUsZT4+OCYyNTUsZSYyNTUsMSl9ZnVuY3Rpb24gckwoZSx0LHIsbil7cmV0dXJuIG48PTAmJihlPXQ9cj1OYU4pLG5ldyBLaShlLHQscixuKX1mdW5jdGlvbiBBRShlKXtyZXR1cm4gZSBpbnN0YW5jZW9mIFJofHwoZT1yYyhlKSksZT8oZT1lLnJnYigpLG5ldyBLaShlLnIsZS5nLGUuYixlLm9wYWNpdHkpKTpuZXcgS2l9ZnVuY3Rpb24gY3UoZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP0FFKGUpOm5ldyBLaShlLHQscixuPT1udWxsPzE6bil9ZnVuY3Rpb24gS2koZSx0LHIsbil7dGhpcy5yPStlLHRoaXMuZz0rdCx0aGlzLmI9K3IsdGhpcy5vcGFjaXR5PStufUNwKEtpLGN1LEJtKFJoLHticmlnaHRlcjpmdW5jdGlvbihlKXtyZXR1cm4gZT1lPT1udWxsP2JfOk1hdGgucG93KGJfLGUpLG5ldyBLaSh0aGlzLnIqZSx0aGlzLmcqZSx0aGlzLmIqZSx0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9IbTpNYXRoLnBvdyhIbSxlKSxuZXcgS2kodGhpcy5yKmUsdGhpcy5nKmUsdGhpcy5iKmUsdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXN9LGRpc3BsYXlhYmxlOmZ1bmN0aW9uKCl7cmV0dXJuLS41PD10aGlzLnImJnRoaXMucjwyNTUuNSYmLS41PD10aGlzLmcmJnRoaXMuZzwyNTUuNSYmLS41PD10aGlzLmImJnRoaXMuYjwyNTUuNSYmMDw9dGhpcy5vcGFjaXR5JiZ0aGlzLm9wYWNpdHk8PTF9LGhleDpseXQsZm9ybWF0SGV4Omx5dCxmb3JtYXRSZ2I6Y3l0LHRvU3RyaW5nOmN5dH0pKTtmdW5jdGlvbiBseXQoKXtyZXR1cm4iIyIrdFkodGhpcy5yKSt0WSh0aGlzLmcpK3RZKHRoaXMuYil9ZnVuY3Rpb24gY3l0KCl7dmFyIGU9dGhpcy5vcGFjaXR5O3JldHVybiBlPWlzTmFOKGUpPzE6TWF0aC5tYXgoMCxNYXRoLm1pbigxLGUpKSwoZT09PTE/InJnYigiOiJyZ2JhKCIpK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQodGhpcy5yKXx8MCkpKyIsICIrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCh0aGlzLmcpfHwwKSkrIiwgIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKHRoaXMuYil8fDApKSsoZT09PTE/IikiOiIsICIrZSsiKSIpfWZ1bmN0aW9uIHRZKGUpe3JldHVybiBlPU1hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQoZSl8fDApKSwoZTwxNj8iMCI6IiIpK2UudG9TdHJpbmcoMTYpfWZ1bmN0aW9uIHV5dChlLHQscixuKXtyZXR1cm4gbjw9MD9lPXQ9cj1OYU46cjw9MHx8cj49MT9lPXQ9TmFOOnQ8PTAmJihlPU5hTiksbmV3IExoKGUsdCxyLG4pfWZ1bmN0aW9uIGh5dChlKXtpZihlIGluc3RhbmNlb2YgTGgpcmV0dXJuIG5ldyBMaChlLmgsZS5zLGUubCxlLm9wYWNpdHkpO2lmKGUgaW5zdGFuY2VvZiBSaHx8KGU9cmMoZSkpLCFlKXJldHVybiBuZXcgTGg7aWYoZSBpbnN0YW5jZW9mIExoKXJldHVybiBlO2U9ZS5yZ2IoKTt2YXIgdD1lLnIvMjU1LHI9ZS5nLzI1NSxuPWUuYi8yNTUsaT1NYXRoLm1pbih0LHIsbiksbz1NYXRoLm1heCh0LHIsbiksYT1OYU4scz1vLWksbD0obytpKS8yO3JldHVybiBzPyh0PT09bz9hPShyLW4pL3MrKHI8bikqNjpyPT09bz9hPShuLXQpL3MrMjphPSh0LXIpL3MrNCxzLz1sPC41P28raToyLW8taSxhKj02MCk6cz1sPjAmJmw8MT8wOmEsbmV3IExoKGEscyxsLGUub3BhY2l0eSl9ZnVuY3Rpb24gVm0oZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP2h5dChlKTpuZXcgTGgoZSx0LHIsbj09bnVsbD8xOm4pfWZ1bmN0aW9uIExoKGUsdCxyLG4pe3RoaXMuaD0rZSx0aGlzLnM9K3QsdGhpcy5sPStyLHRoaXMub3BhY2l0eT0rbn1DcChMaCxWbSxCbShSaCx7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9iXzpNYXRoLnBvdyhiXyxlKSxuZXcgTGgodGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9IbTpNYXRoLnBvdyhIbSxlKSxuZXcgTGgodGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLmglMzYwKyh0aGlzLmg8MCkqMzYwLHQ9aXNOYU4oZSl8fGlzTmFOKHRoaXMucyk/MDp0aGlzLnMscj10aGlzLmwsbj1yKyhyPC41P3I6MS1yKSp0LGk9MipyLW47cmV0dXJuIG5ldyBLaShlWShlPj0yNDA/ZS0yNDA6ZSsxMjAsaSxuKSxlWShlLGksbiksZVkoZTwxMjA/ZSsyNDA6ZS0xMjAsaSxuKSx0aGlzLm9wYWNpdHkpfSxkaXNwbGF5YWJsZTpmdW5jdGlvbigpe3JldHVybigwPD10aGlzLnMmJnRoaXMuczw9MXx8aXNOYU4odGhpcy5zKSkmJjA8PXRoaXMubCYmdGhpcy5sPD0xJiYwPD10aGlzLm9wYWNpdHkmJnRoaXMub3BhY2l0eTw9MX0sZm9ybWF0SHNsOmZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5vcGFjaXR5O3JldHVybiBlPWlzTmFOKGUpPzE6TWF0aC5tYXgoMCxNYXRoLm1pbigxLGUpKSwoZT09PTE/ImhzbCgiOiJoc2xhKCIpKyh0aGlzLmh8fDApKyIsICIrKHRoaXMuc3x8MCkqMTAwKyIlLCAiKyh0aGlzLmx8fDApKjEwMCsiJSIrKGU9PT0xPyIpIjoiLCAiK2UrIikiKX19KSk7ZnVuY3Rpb24gZVkoZSx0LHIpe3JldHVybihlPDYwP3QrKHItdCkqZS82MDplPDE4MD9yOmU8MjQwP3QrKHItdCkqKDI0MC1lKS82MDp0KSoyNTV9dmFyIG5MPU1hdGguUEkvMTgwLGlMPTE4MC9NYXRoLlBJO3ZhciBvTD0xOCxmeXQ9Ljk2NDIyLHB5dD0xLGR5dD0uODI1MjEsbXl0PTQvMjksbWI9Ni8yOSxneXQ9MyptYiptYixmd2U9bWIqbWIqbWI7ZnVuY3Rpb24gX3l0KGUpe2lmKGUgaW5zdGFuY2VvZiB1dSlyZXR1cm4gbmV3IHV1KGUubCxlLmEsZS5iLGUub3BhY2l0eSk7aWYoZSBpbnN0YW5jZW9mIE5oKXJldHVybiBieXQoZSk7ZSBpbnN0YW5jZW9mIEtpfHwoZT1BRShlKSk7dmFyIHQ9b1koZS5yKSxyPW9ZKGUuZyksbj1vWShlLmIpLGk9clkoKC4yMjI1MDQ1KnQrLjcxNjg3ODYqcisuMDYwNjE2OSpuKS9weXQpLG8sYTtyZXR1cm4gdD09PXImJnI9PT1uP289YT1pOihvPXJZKCguNDM2MDc0Nyp0Ky4zODUwNjQ5KnIrLjE0MzA4MDQqbikvZnl0KSxhPXJZKCguMDEzOTMyMip0Ky4wOTcxMDQ1KnIrLjcxNDE3MzMqbikvZHl0KSksbmV3IHV1KDExNippLTE2LDUwMCooby1pKSwyMDAqKGktYSksZS5vcGFjaXR5KX1mdW5jdGlvbiB5eXQoZSx0KXtyZXR1cm4gbmV3IHV1KGUsMCwwLHQ9PW51bGw/MTp0KX1mdW5jdGlvbiB3XyhlLHQscixuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTE/X3l0KGUpOm5ldyB1dShlLHQscixuPT1udWxsPzE6bil9ZnVuY3Rpb24gdXUoZSx0LHIsbil7dGhpcy5sPStlLHRoaXMuYT0rdCx0aGlzLmI9K3IsdGhpcy5vcGFjaXR5PStufUNwKHV1LHdfLEJtKFJoLHticmlnaHRlcjpmdW5jdGlvbihlKXtyZXR1cm4gbmV3IHV1KHRoaXMubCtvTCooZT09bnVsbD8xOmUpLHRoaXMuYSx0aGlzLmIsdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKGUpe3JldHVybiBuZXcgdXUodGhpcy5sLW9MKihlPT1udWxsPzE6ZSksdGhpcy5hLHRoaXMuYix0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXt2YXIgZT0odGhpcy5sKzE2KS8xMTYsdD1pc05hTih0aGlzLmEpP2U6ZSt0aGlzLmEvNTAwLHI9aXNOYU4odGhpcy5iKT9lOmUtdGhpcy5iLzIwMDtyZXR1cm4gdD1meXQqblkodCksZT1weXQqblkoZSkscj1keXQqblkociksbmV3IEtpKGlZKDMuMTMzODU2MSp0LTEuNjE2ODY2NyplLS40OTA2MTQ2KnIpLGlZKC0uOTc4NzY4NCp0KzEuOTE2MTQxNSplKy4wMzM0NTQqciksaVkoLjA3MTk0NTMqdC0uMjI4OTkxNCplKzEuNDA1MjQyNypyKSx0aGlzLm9wYWNpdHkpfX0pKTtmdW5jdGlvbiByWShlKXtyZXR1cm4gZT5md2U/TWF0aC5wb3coZSwxLzMpOmUvZ3l0K215dH1mdW5jdGlvbiBuWShlKXtyZXR1cm4gZT5tYj9lKmUqZTpneXQqKGUtbXl0KX1mdW5jdGlvbiBpWShlKXtyZXR1cm4gMjU1KihlPD0uMDAzMTMwOD8xMi45MiplOjEuMDU1Kk1hdGgucG93KGUsMS8yLjQpLS4wNTUpfWZ1bmN0aW9uIG9ZKGUpe3JldHVybihlLz0yNTUpPD0uMDQwNDU/ZS8xMi45MjpNYXRoLnBvdygoZSsuMDU1KS8xLjA1NSwyLjQpfWZ1bmN0aW9uIHZ5dChlKXtpZihlIGluc3RhbmNlb2YgTmgpcmV0dXJuIG5ldyBOaChlLmgsZS5jLGUubCxlLm9wYWNpdHkpO2lmKGUgaW5zdGFuY2VvZiB1dXx8KGU9X3l0KGUpKSxlLmE9PT0wJiZlLmI9PT0wKXJldHVybiBuZXcgTmgoTmFOLDA8ZS5sJiZlLmw8MTAwPzA6TmFOLGUubCxlLm9wYWNpdHkpO3ZhciB0PU1hdGguYXRhbjIoZS5iLGUuYSkqaUw7cmV0dXJuIG5ldyBOaCh0PDA/dCszNjA6dCxNYXRoLnNxcnQoZS5hKmUuYStlLmIqZS5iKSxlLmwsZS5vcGFjaXR5KX1mdW5jdGlvbiB4eXQoZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP3Z5dChlKTpuZXcgTmgocix0LGUsbj09bnVsbD8xOm4pfWZ1bmN0aW9uIGdiKGUsdCxyLG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPT09MT92eXQoZSk6bmV3IE5oKGUsdCxyLG49PW51bGw/MTpuKX1mdW5jdGlvbiBOaChlLHQscixuKXt0aGlzLmg9K2UsdGhpcy5jPSt0LHRoaXMubD0rcix0aGlzLm9wYWNpdHk9K259ZnVuY3Rpb24gYnl0KGUpe2lmKGlzTmFOKGUuaCkpcmV0dXJuIG5ldyB1dShlLmwsMCwwLGUub3BhY2l0eSk7dmFyIHQ9ZS5oKm5MO3JldHVybiBuZXcgdXUoZS5sLE1hdGguY29zKHQpKmUuYyxNYXRoLnNpbih0KSplLmMsZS5vcGFjaXR5KX1DcChOaCxnYixCbShSaCx7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyBOaCh0aGlzLmgsdGhpcy5jLHRoaXMubCtvTCooZT09bnVsbD8xOmUpLHRoaXMub3BhY2l0eSl9LGRhcmtlcjpmdW5jdGlvbihlKXtyZXR1cm4gbmV3IE5oKHRoaXMuaCx0aGlzLmMsdGhpcy5sLW9MKihlPT1udWxsPzE6ZSksdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7cmV0dXJuIGJ5dCh0aGlzKS5yZ2IoKX19KSk7dmFyIEV5dD0tLjE0ODYxLGFZPTEuNzgyNzcsc1k9LS4yOTIyNyxhTD0tLjkwNjQ5LFBFPTEuOTcyOTQsd3l0PVBFKmFMLFN5dD1QRSphWSxNeXQ9YVkqc1ktYUwqRXl0O2Z1bmN0aW9uIHB3ZShlKXtpZihlIGluc3RhbmNlb2YgU18pcmV0dXJuIG5ldyBTXyhlLmgsZS5zLGUubCxlLm9wYWNpdHkpO2UgaW5zdGFuY2VvZiBLaXx8KGU9QUUoZSkpO3ZhciB0PWUuci8yNTUscj1lLmcvMjU1LG49ZS5iLzI1NSxpPShNeXQqbit3eXQqdC1TeXQqcikvKE15dCt3eXQtU3l0KSxvPW4taSxhPShQRSooci1pKS1zWSpvKS9hTCxzPU1hdGguc3FydChhKmErbypvKS8oUEUqaSooMS1pKSksbD1zP01hdGguYXRhbjIoYSxvKSppTC0xMjA6TmFOO3JldHVybiBuZXcgU18obDwwP2wrMzYwOmwscyxpLGUub3BhY2l0eSl9ZnVuY3Rpb24gbGEoZSx0LHIsbil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg9PT0xP3B3ZShlKTpuZXcgU18oZSx0LHIsbj09bnVsbD8xOm4pfWZ1bmN0aW9uIFNfKGUsdCxyLG4pe3RoaXMuaD0rZSx0aGlzLnM9K3QsdGhpcy5sPStyLHRoaXMub3BhY2l0eT0rbn1DcChTXyxsYSxCbShSaCx7YnJpZ2h0ZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9iXzpNYXRoLnBvdyhiXyxlKSxuZXcgU18odGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZT09bnVsbD9IbTpNYXRoLnBvdyhIbSxlKSxuZXcgU18odGhpcy5oLHRoaXMucyx0aGlzLmwqZSx0aGlzLm9wYWNpdHkpfSxyZ2I6ZnVuY3Rpb24oKXt2YXIgZT1pc05hTih0aGlzLmgpPzA6KHRoaXMuaCsxMjApKm5MLHQ9K3RoaXMubCxyPWlzTmFOKHRoaXMucyk/MDp0aGlzLnMqdCooMS10KSxuPU1hdGguY29zKGUpLGk9TWF0aC5zaW4oZSk7cmV0dXJuIG5ldyBLaSgyNTUqKHQrciooRXl0Km4rYVkqaSkpLDI1NSoodCtyKihzWSpuK2FMKmkpKSwyNTUqKHQrciooUEUqbikpLHRoaXMub3BhY2l0eSl9fSkpO2Z1bmN0aW9uIGxZKGUsdCxyLG4saSl7dmFyIG89ZSplLGE9byplO3JldHVybigoMS0zKmUrMypvLWEpKnQrKDQtNipvKzMqYSkqcisoMSszKmUrMypvLTMqYSkqbithKmkpLzZ9ZnVuY3Rpb24gc0woZSl7dmFyIHQ9ZS5sZW5ndGgtMTtyZXR1cm4gZnVuY3Rpb24ocil7dmFyIG49cjw9MD9yPTA6cj49MT8ocj0xLHQtMSk6TWF0aC5mbG9vcihyKnQpLGk9ZVtuXSxvPWVbbisxXSxhPW4+MD9lW24tMV06MippLW8scz1uPHQtMT9lW24rMl06MipvLWk7cmV0dXJuIGxZKChyLW4vdCkqdCxhLGksbyxzKX19ZnVuY3Rpb24gbEwoZSl7dmFyIHQ9ZS5sZW5ndGg7cmV0dXJuIGZ1bmN0aW9uKHIpe3ZhciBuPU1hdGguZmxvb3IoKChyJT0xKTwwPysrcjpyKSp0KSxpPWVbKG4rdC0xKSV0XSxvPWVbbiV0XSxhPWVbKG4rMSkldF0scz1lWyhuKzIpJXRdO3JldHVybiBsWSgoci1uL3QpKnQsaSxvLGEscyl9fWZ1bmN0aW9uIF9iKGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX1mdW5jdGlvbiBUeXQoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocil7cmV0dXJuIGUrcip0fX1mdW5jdGlvbiBkd2UoZSx0LHIpe3JldHVybiBlPU1hdGgucG93KGUsciksdD1NYXRoLnBvdyh0LHIpLWUscj0xL3IsZnVuY3Rpb24obil7cmV0dXJuIE1hdGgucG93KGUrbip0LHIpfX1mdW5jdGlvbiBVbShlLHQpe3ZhciByPXQtZTtyZXR1cm4gcj9UeXQoZSxyPjE4MHx8cjwtMTgwP3ItMzYwKk1hdGgucm91bmQoci8zNjApOnIpOl9iKGlzTmFOKGUpP3Q6ZSl9ZnVuY3Rpb24gQ3l0KGUpe3JldHVybihlPStlKT09MT9abjpmdW5jdGlvbih0LHIpe3JldHVybiByLXQ/ZHdlKHQscixlKTpfYihpc05hTih0KT9yOnQpfX1mdW5jdGlvbiBabihlLHQpe3ZhciByPXQtZTtyZXR1cm4gcj9UeXQoZSxyKTpfYihpc05hTihlKT90OmUpfXZhciBxbT1mdW5jdGlvbiBlKHQpe3ZhciByPUN5dCh0KTtmdW5jdGlvbiBuKGksbyl7dmFyIGE9cigoaT1jdShpKSkuciwobz1jdShvKSkucikscz1yKGkuZyxvLmcpLGw9cihpLmIsby5iKSxjPVpuKGkub3BhY2l0eSxvLm9wYWNpdHkpO3JldHVybiBmdW5jdGlvbih1KXtyZXR1cm4gaS5yPWEodSksaS5nPXModSksaS5iPWwodSksaS5vcGFjaXR5PWModSksaSsiIn19cmV0dXJuIG4uZ2FtbWE9ZSxufSgxKTtmdW5jdGlvbiBBeXQoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3ZhciByPXQubGVuZ3RoLG49bmV3IEFycmF5KHIpLGk9bmV3IEFycmF5KHIpLG89bmV3IEFycmF5KHIpLGEscztmb3IoYT0wO2E8cjsrK2Epcz1jdSh0W2FdKSxuW2FdPXMucnx8MCxpW2FdPXMuZ3x8MCxvW2FdPXMuYnx8MDtyZXR1cm4gbj1lKG4pLGk9ZShpKSxvPWUobykscy5vcGFjaXR5PTEsZnVuY3Rpb24obCl7cmV0dXJuIHMucj1uKGwpLHMuZz1pKGwpLHMuYj1vKGwpLHMrIiJ9fX12YXIgY0w9QXl0KHNMKSxQeXQ9QXl0KGxMKTtmdW5jdGlvbiB5YihlLHQpe3R8fCh0PVtdKTt2YXIgcj1lP01hdGgubWluKHQubGVuZ3RoLGUubGVuZ3RoKTowLG49dC5zbGljZSgpLGk7cmV0dXJuIGZ1bmN0aW9uKG8pe2ZvcihpPTA7aTxyOysraSluW2ldPWVbaV0qKDEtbykrdFtpXSpvO3JldHVybiBufX1mdW5jdGlvbiB1TChlKXtyZXR1cm4gQXJyYXlCdWZmZXIuaXNWaWV3KGUpJiYhKGUgaW5zdGFuY2VvZiBEYXRhVmlldyl9ZnVuY3Rpb24gSXl0KGUsdCl7cmV0dXJuKHVMKHQpP3liOmNZKShlLHQpfWZ1bmN0aW9uIGNZKGUsdCl7dmFyIHI9dD90Lmxlbmd0aDowLG49ZT9NYXRoLm1pbihyLGUubGVuZ3RoKTowLGk9bmV3IEFycmF5KG4pLG89bmV3IEFycmF5KHIpLGE7Zm9yKGE9MDthPG47KythKWlbYV09bmMoZVthXSx0W2FdKTtmb3IoO2E8cjsrK2Epb1thXT10W2FdO3JldHVybiBmdW5jdGlvbihzKXtmb3IoYT0wO2E8bjsrK2Epb1thXT1pW2FdKHMpO3JldHVybiBvfX1mdW5jdGlvbiBoTChlLHQpe3ZhciByPW5ldyBEYXRlO3JldHVybiBlPStlLHQ9K3QsZnVuY3Rpb24obil7cmV0dXJuIHIuc2V0VGltZShlKigxLW4pK3Qqbikscn19ZnVuY3Rpb24gemkoZSx0KXtyZXR1cm4gZT0rZSx0PSt0LGZ1bmN0aW9uKHIpe3JldHVybiBlKigxLXIpK3Qqcn19ZnVuY3Rpb24gZkwoZSx0KXt2YXIgcj17fSxuPXt9LGk7KGU9PT1udWxsfHx0eXBlb2YgZSE9Im9iamVjdCIpJiYoZT17fSksKHQ9PT1udWxsfHx0eXBlb2YgdCE9Im9iamVjdCIpJiYodD17fSk7Zm9yKGkgaW4gdClpIGluIGU/cltpXT1uYyhlW2ldLHRbaV0pOm5baV09dFtpXTtyZXR1cm4gZnVuY3Rpb24obyl7Zm9yKGkgaW4gciluW2ldPXJbaV0obyk7cmV0dXJuIG59fXZhciBoWT0vWy0rXT8oPzpcZCtcLj9cZCp8XC4/XGQrKSg/OltlRV1bLStdP1xkKyk/L2csdVk9bmV3IFJlZ0V4cChoWS5zb3VyY2UsImciKTtmdW5jdGlvbiBtd2UoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fWZ1bmN0aW9uIGd3ZShlKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuIGUodCkrIiJ9fWZ1bmN0aW9uIHZiKGUsdCl7dmFyIHI9aFkubGFzdEluZGV4PXVZLmxhc3RJbmRleD0wLG4saSxvLGE9LTEscz1bXSxsPVtdO2ZvcihlPWUrIiIsdD10KyIiOyhuPWhZLmV4ZWMoZSkpJiYoaT11WS5leGVjKHQpKTspKG89aS5pbmRleCk+ciYmKG89dC5zbGljZShyLG8pLHNbYV0/c1thXSs9bzpzWysrYV09byksKG49blswXSk9PT0oaT1pWzBdKT9zW2FdP3NbYV0rPWk6c1srK2FdPWk6KHNbKythXT1udWxsLGwucHVzaCh7aTphLHg6emkobixpKX0pKSxyPXVZLmxhc3RJbmRleDtyZXR1cm4gcjx0Lmxlbmd0aCYmKG89dC5zbGljZShyKSxzW2FdP3NbYV0rPW86c1srK2FdPW8pLHMubGVuZ3RoPDI/bFswXT9nd2UobFswXS54KTptd2UodCk6KHQ9bC5sZW5ndGgsZnVuY3Rpb24oYyl7Zm9yKHZhciB1PTAsaDt1PHQ7Kyt1KXNbKGg9bFt1XSkuaV09aC54KGMpO3JldHVybiBzLmpvaW4oIiIpfSl9ZnVuY3Rpb24gbmMoZSx0KXt2YXIgcj10eXBlb2YgdCxuO3JldHVybiB0PT1udWxsfHxyPT09ImJvb2xlYW4iP19iKHQpOihyPT09Im51bWJlciI/emk6cj09PSJzdHJpbmciPyhuPXJjKHQpKT8odD1uLHFtKTp2Yjp0IGluc3RhbmNlb2YgcmM/cW06dCBpbnN0YW5jZW9mIERhdGU/aEw6dUwodCk/eWI6QXJyYXkuaXNBcnJheSh0KT9jWTp0eXBlb2YgdC52YWx1ZU9mIT0iZnVuY3Rpb24iJiZ0eXBlb2YgdC50b1N0cmluZyE9ImZ1bmN0aW9uInx8aXNOYU4odCk/Zkw6emkpKGUsdCl9ZnVuY3Rpb24gTHl0KGUpe3ZhciB0PWUubGVuZ3RoO3JldHVybiBmdW5jdGlvbihyKXtyZXR1cm4gZVtNYXRoLm1heCgwLE1hdGgubWluKHQtMSxNYXRoLmZsb29yKHIqdCkpKV19fWZ1bmN0aW9uIGt5dChlLHQpe3ZhciByPVVtKCtlLCt0KTtyZXR1cm4gZnVuY3Rpb24obil7dmFyIGk9cihuKTtyZXR1cm4gaS0zNjAqTWF0aC5mbG9vcihpLzM2MCl9fWZ1bmN0aW9uIHBMKGUsdCl7cmV0dXJuIGU9K2UsdD0rdCxmdW5jdGlvbihyKXtyZXR1cm4gTWF0aC5yb3VuZChlKigxLXIpK3Qqcil9fXZhciBSeXQ9MTgwL01hdGguUEksZEw9e3RyYW5zbGF0ZVg6MCx0cmFuc2xhdGVZOjAscm90YXRlOjAsc2tld1g6MCxzY2FsZVg6MSxzY2FsZVk6MX07ZnVuY3Rpb24gZlkoZSx0LHIsbixpLG8pe3ZhciBhLHMsbDtyZXR1cm4oYT1NYXRoLnNxcnQoZSplK3QqdCkpJiYoZS89YSx0Lz1hKSwobD1lKnIrdCpuKSYmKHItPWUqbCxuLT10KmwpLChzPU1hdGguc3FydChyKnIrbipuKSkmJihyLz1zLG4vPXMsbC89cyksZSpuPHQqciYmKGU9LWUsdD0tdCxsPS1sLGE9LWEpLHt0cmFuc2xhdGVYOmksdHJhbnNsYXRlWTpvLHJvdGF0ZTpNYXRoLmF0YW4yKHQsZSkqUnl0LHNrZXdYOk1hdGguYXRhbihsKSpSeXQsc2NhbGVYOmEsc2NhbGVZOnN9fXZhciBJRSxwWSxOeXQsbUw7ZnVuY3Rpb24gRHl0KGUpe3JldHVybiBlPT09Im5vbmUiP2RMOihJRXx8KElFPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkRJViIpLHBZPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudCxOeXQ9ZG9jdW1lbnQuZGVmYXVsdFZpZXcpLElFLnN0eWxlLnRyYW5zZm9ybT1lLGU9Tnl0LmdldENvbXB1dGVkU3R5bGUocFkuYXBwZW5kQ2hpbGQoSUUpLG51bGwpLmdldFByb3BlcnR5VmFsdWUoInRyYW5zZm9ybSIpLHBZLnJlbW92ZUNoaWxkKElFKSxlPWUuc2xpY2UoNywtMSkuc3BsaXQoIiwiKSxmWSgrZVswXSwrZVsxXSwrZVsyXSwrZVszXSwrZVs0XSwrZVs1XSkpfWZ1bmN0aW9uIE95dChlKXtyZXR1cm4gZT09bnVsbD9kTDoobUx8fChtTD1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwiZyIpKSxtTC5zZXRBdHRyaWJ1dGUoInRyYW5zZm9ybSIsZSksKGU9bUwudHJhbnNmb3JtLmJhc2VWYWwuY29uc29saWRhdGUoKSk/KGU9ZS5tYXRyaXgsZlkoZS5hLGUuYixlLmMsZS5kLGUuZSxlLmYpKTpkTCl9ZnVuY3Rpb24genl0KGUsdCxyLG4pe2Z1bmN0aW9uIGkoYyl7cmV0dXJuIGMubGVuZ3RoP2MucG9wKCkrIiAiOiIifWZ1bmN0aW9uIG8oYyx1LGgsZixwLGQpe2lmKGMhPT1ofHx1IT09Zil7dmFyIGc9cC5wdXNoKCJ0cmFuc2xhdGUoIixudWxsLHQsbnVsbCxyKTtkLnB1c2goe2k6Zy00LHg6emkoYyxoKX0se2k6Zy0yLHg6emkodSxmKX0pfWVsc2UoaHx8ZikmJnAucHVzaCgidHJhbnNsYXRlKCIraCt0K2Yrcil9ZnVuY3Rpb24gYShjLHUsaCxmKXtjIT09dT8oYy11PjE4MD91Kz0zNjA6dS1jPjE4MCYmKGMrPTM2MCksZi5wdXNoKHtpOmgucHVzaChpKGgpKyJyb3RhdGUoIixudWxsLG4pLTIseDp6aShjLHUpfSkpOnUmJmgucHVzaChpKGgpKyJyb3RhdGUoIit1K24pfWZ1bmN0aW9uIHMoYyx1LGgsZil7YyE9PXU/Zi5wdXNoKHtpOmgucHVzaChpKGgpKyJza2V3WCgiLG51bGwsbiktMix4OnppKGMsdSl9KTp1JiZoLnB1c2goaShoKSsic2tld1goIit1K24pfWZ1bmN0aW9uIGwoYyx1LGgsZixwLGQpe2lmKGMhPT1ofHx1IT09Zil7dmFyIGc9cC5wdXNoKGkocCkrInNjYWxlKCIsbnVsbCwiLCIsbnVsbCwiKSIpO2QucHVzaCh7aTpnLTQseDp6aShjLGgpfSx7aTpnLTIseDp6aSh1LGYpfSl9ZWxzZShoIT09MXx8ZiE9PTEpJiZwLnB1c2goaShwKSsic2NhbGUoIitoKyIsIitmKyIpIil9cmV0dXJuIGZ1bmN0aW9uKGMsdSl7dmFyIGg9W10sZj1bXTtyZXR1cm4gYz1lKGMpLHU9ZSh1KSxvKGMudHJhbnNsYXRlWCxjLnRyYW5zbGF0ZVksdS50cmFuc2xhdGVYLHUudHJhbnNsYXRlWSxoLGYpLGEoYy5yb3RhdGUsdS5yb3RhdGUsaCxmKSxzKGMuc2tld1gsdS5za2V3WCxoLGYpLGwoYy5zY2FsZVgsYy5zY2FsZVksdS5zY2FsZVgsdS5zY2FsZVksaCxmKSxjPXU9bnVsbCxmdW5jdGlvbihwKXtmb3IodmFyIGQ9LTEsZz1mLmxlbmd0aCxfOysrZDxnOyloWyhfPWZbZF0pLmldPV8ueChwKTtyZXR1cm4gaC5qb2luKCIiKX19fXZhciBnTD16eXQoRHl0LCJweCwgIiwicHgpIiwiZGVnKSIpLF9MPXp5dChPeXQsIiwgIiwiKSIsIikiKTt2YXIgTEU9TWF0aC5TUVJUMixkWT0yLEZ5dD00LF93ZT0xZS0xMjtmdW5jdGlvbiBCeXQoZSl7cmV0dXJuKChlPU1hdGguZXhwKGUpKSsxL2UpLzJ9ZnVuY3Rpb24geXdlKGUpe3JldHVybigoZT1NYXRoLmV4cChlKSktMS9lKS8yfWZ1bmN0aW9uIHZ3ZShlKXtyZXR1cm4oKGU9TWF0aC5leHAoMiplKSktMSkvKGUrMSl9ZnVuY3Rpb24geUwoZSx0KXt2YXIgcj1lWzBdLG49ZVsxXSxpPWVbMl0sbz10WzBdLGE9dFsxXSxzPXRbMl0sbD1vLXIsYz1hLW4sdT1sKmwrYypjLGgsZjtpZih1PF93ZSlmPU1hdGgubG9nKHMvaSkvTEUsaD1mdW5jdGlvbih4KXtyZXR1cm5bcit4Kmwsbit4KmMsaSpNYXRoLmV4cChMRSp4KmYpXX07ZWxzZXt2YXIgcD1NYXRoLnNxcnQodSksZD0ocypzLWkqaStGeXQqdSkvKDIqaSpkWSpwKSxnPShzKnMtaSppLUZ5dCp1KS8oMipzKmRZKnApLF89TWF0aC5sb2coTWF0aC5zcXJ0KGQqZCsxKS1kKSx5PU1hdGgubG9nKE1hdGguc3FydChnKmcrMSktZyk7Zj0oeS1fKS9MRSxoPWZ1bmN0aW9uKHgpe3ZhciBiPXgqZixTPUJ5dChfKSxDPWkvKGRZKnApKihTKnZ3ZShMRSpiK18pLXl3ZShfKSk7cmV0dXJuW3IrQypsLG4rQypjLGkqUy9CeXQoTEUqYitfKV19fXJldHVybiBoLmR1cmF0aW9uPWYqMWUzLGh9ZnVuY3Rpb24gSHl0KGUpe3JldHVybiBmdW5jdGlvbih0LHIpe3ZhciBuPWUoKHQ9Vm0odCkpLmgsKHI9Vm0ocikpLmgpLGk9Wm4odC5zLHIucyksbz1abih0Lmwsci5sKSxhPVpuKHQub3BhY2l0eSxyLm9wYWNpdHkpO3JldHVybiBmdW5jdGlvbihzKXtyZXR1cm4gdC5oPW4ocyksdC5zPWkocyksdC5sPW8ocyksdC5vcGFjaXR5PWEocyksdCsiIn19fXZhciBWeXQ9SHl0KFVtKSxVeXQ9SHl0KFpuKTtmdW5jdGlvbiBNXyhlLHQpe3ZhciByPVpuKChlPXdfKGUpKS5sLCh0PXdfKHQpKS5sKSxuPVpuKGUuYSx0LmEpLGk9Wm4oZS5iLHQuYiksbz1abihlLm9wYWNpdHksdC5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24oYSl7cmV0dXJuIGUubD1yKGEpLGUuYT1uKGEpLGUuYj1pKGEpLGUub3BhY2l0eT1vKGEpLGUrIiJ9fWZ1bmN0aW9uIHF5dChlKXtyZXR1cm4gZnVuY3Rpb24odCxyKXt2YXIgbj1lKCh0PWdiKHQpKS5oLChyPWdiKHIpKS5oKSxpPVpuKHQuYyxyLmMpLG89Wm4odC5sLHIubCksYT1abih0Lm9wYWNpdHksci5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24ocyl7cmV0dXJuIHQuaD1uKHMpLHQuYz1pKHMpLHQubD1vKHMpLHQub3BhY2l0eT1hKHMpLHQrIiJ9fX12YXIgR3l0PXF5dChVbSksV3l0PXF5dChabik7ZnVuY3Rpb24gWXl0KGUpe3JldHVybiBmdW5jdGlvbiB0KHIpe3I9K3I7ZnVuY3Rpb24gbihpLG8pe3ZhciBhPWUoKGk9bGEoaSkpLmgsKG89bGEobykpLmgpLHM9Wm4oaS5zLG8ucyksbD1abihpLmwsby5sKSxjPVpuKGkub3BhY2l0eSxvLm9wYWNpdHkpO3JldHVybiBmdW5jdGlvbih1KXtyZXR1cm4gaS5oPWEodSksaS5zPXModSksaS5sPWwoTWF0aC5wb3codSxyKSksaS5vcGFjaXR5PWModSksaSsiIn19cmV0dXJuIG4uZ2FtbWE9dCxufSgxKX12YXIganl0PVl5dChVbSksRV89WXl0KFpuKTtmdW5jdGlvbiBtWShlLHQpe2Zvcih2YXIgcj0wLG49dC5sZW5ndGgtMSxpPXRbMF0sbz1uZXcgQXJyYXkobjwwPzA6bik7cjxuOylvW3JdPWUoaSxpPXRbKytyXSk7cmV0dXJuIGZ1bmN0aW9uKGEpe3ZhciBzPU1hdGgubWF4KDAsTWF0aC5taW4obi0xLE1hdGguZmxvb3IoYSo9bikpKTtyZXR1cm4gb1tzXShhLXMpfX1mdW5jdGlvbiBYeXQoZSx0KXtmb3IodmFyIHI9bmV3IEFycmF5KHQpLG49MDtuPHQ7KytuKXJbbl09ZShuLyh0LTEpKTtyZXR1cm4gcn1rbSgpO3ZhciB4Yj0wLFJFPTAsa0U9MCxLeXQ9MWUzLHZMLE5FLHhMPTAsVF89MCxiTD0wLERFPXR5cGVvZiBwZXJmb3JtYW5jZT09Im9iamVjdCImJnBlcmZvcm1hbmNlLm5vdz9wZXJmb3JtYW5jZTpEYXRlLFp5dD10eXBlb2Ygd2luZG93PT0ib2JqZWN0IiYmd2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZT93aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lLmJpbmQod2luZG93KTpmdW5jdGlvbihlKXtzZXRUaW1lb3V0KGUsMTcpfTtmdW5jdGlvbiBBcCgpe3JldHVybiBUX3x8KFp5dCh4d2UpLFRfPURFLm5vdygpK2JMKX1mdW5jdGlvbiB4d2UoKXtUXz0wfWZ1bmN0aW9uIENfKCl7dGhpcy5fY2FsbD10aGlzLl90aW1lPXRoaXMuX25leHQ9bnVsbH1DXy5wcm90b3R5cGU9QV8ucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpDXyxyZXN0YXJ0OmZ1bmN0aW9uKGUsdCxyKXtpZih0eXBlb2YgZSE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgVHlwZUVycm9yKCJjYWxsYmFjayBpcyBub3QgYSBmdW5jdGlvbiIpO3I9KHI9PW51bGw/QXAoKTorcikrKHQ9PW51bGw/MDordCksIXRoaXMuX25leHQmJk5FIT09dGhpcyYmKE5FP05FLl9uZXh0PXRoaXM6dkw9dGhpcyxORT10aGlzKSx0aGlzLl9jYWxsPWUsdGhpcy5fdGltZT1yLGdZKCl9LHN0b3A6ZnVuY3Rpb24oKXt0aGlzLl9jYWxsJiYodGhpcy5fY2FsbD1udWxsLHRoaXMuX3RpbWU9MS8wLGdZKCkpfX07ZnVuY3Rpb24gQV8oZSx0LHIpe3ZhciBuPW5ldyBDXztyZXR1cm4gbi5yZXN0YXJ0KGUsdCxyKSxufWZ1bmN0aW9uIF9ZKCl7QXAoKSwrK3hiO2Zvcih2YXIgZT12TCx0O2U7KSh0PVRfLWUuX3RpbWUpPj0wJiZlLl9jYWxsLmNhbGwobnVsbCx0KSxlPWUuX25leHQ7LS14Yn1mdW5jdGlvbiAkeXQoKXtUXz0oeEw9REUubm93KCkpK2JMLHhiPVJFPTA7dHJ5e19ZKCl9ZmluYWxseXt4Yj0wLHd3ZSgpLFRfPTB9fWZ1bmN0aW9uIGJ3ZSgpe3ZhciBlPURFLm5vdygpLHQ9ZS14TDt0Pkt5dCYmKGJMLT10LHhMPWUpfWZ1bmN0aW9uIHd3ZSgpe2Zvcih2YXIgZSx0PXZMLHIsbj0xLzA7dDspdC5fY2FsbD8obj50Ll90aW1lJiYobj10Ll90aW1lKSxlPXQsdD10Ll9uZXh0KToocj10Ll9uZXh0LHQuX25leHQ9bnVsbCx0PWU/ZS5fbmV4dD1yOnZMPXIpO05FPWUsZ1kobil9ZnVuY3Rpb24gZ1koZSl7aWYoIXhiKXtSRSYmKFJFPWNsZWFyVGltZW91dChSRSkpO3ZhciB0PWUtVF87dD4yND8oZTwxLzAmJihSRT1zZXRUaW1lb3V0KCR5dCxlLURFLm5vdygpLWJMKSksa0UmJihrRT1jbGVhckludGVydmFsKGtFKSkpOihrRXx8KHhMPURFLm5vdygpLGtFPXNldEludGVydmFsKGJ3ZSxLeXQpKSx4Yj0xLFp5dCgkeXQpKX19ZnVuY3Rpb24gT0UoZSx0LHIpe3ZhciBuPW5ldyBDXztyZXR1cm4gdD10PT1udWxsPzA6K3Qsbi5yZXN0YXJ0KGZ1bmN0aW9uKGkpe24uc3RvcCgpLGUoaSt0KX0sdCxyKSxufWZ1bmN0aW9uIEp5dChlLHQscil7dmFyIG49bmV3IENfLGk9dDtyZXR1cm4gdD09bnVsbD8obi5yZXN0YXJ0KGUsdCxyKSxuKToodD0rdCxyPXI9PW51bGw/QXAoKTorcixuLnJlc3RhcnQoZnVuY3Rpb24gbyhhKXthKz1pLG4ucmVzdGFydChvLGkrPXQsciksZShhKX0sdCxyKSxuKX12YXIgU3dlPXZzKCJzdGFydCIsImVuZCIsImNhbmNlbCIsImludGVycnVwdCIpLE13ZT1bXSx0MXQ9MCxTTD0xLE1MPTIsd0w9MyxReXQ9NCxFTD01LHpFPTY7ZnVuY3Rpb24gR20oZSx0LHIsbixpLG8pe3ZhciBhPWUuX190cmFuc2l0aW9uO2lmKCFhKWUuX190cmFuc2l0aW9uPXt9O2Vsc2UgaWYociBpbiBhKXJldHVybjtFd2UoZSxyLHtuYW1lOnQsaW5kZXg6bixncm91cDppLG9uOlN3ZSx0d2VlbjpNd2UsdGltZTpvLnRpbWUsZGVsYXk6by5kZWxheSxkdXJhdGlvbjpvLmR1cmF0aW9uLGVhc2U6by5lYXNlLHRpbWVyOm51bGwsc3RhdGU6dDF0fSl9ZnVuY3Rpb24gRkUoZSx0KXt2YXIgcj1aaShlLHQpO2lmKHIuc3RhdGU+dDF0KXRocm93IG5ldyBFcnJvcigidG9vIGxhdGU7IGFscmVhZHkgc2NoZWR1bGVkIik7cmV0dXJuIHJ9ZnVuY3Rpb24gT2EoZSx0KXt2YXIgcj1aaShlLHQpO2lmKHIuc3RhdGU+d0wpdGhyb3cgbmV3IEVycm9yKCJ0b28gbGF0ZTsgYWxyZWFkeSBydW5uaW5nIik7cmV0dXJuIHJ9ZnVuY3Rpb24gWmkoZSx0KXt2YXIgcj1lLl9fdHJhbnNpdGlvbjtpZighcnx8IShyPXJbdF0pKXRocm93IG5ldyBFcnJvcigidHJhbnNpdGlvbiBub3QgZm91bmQiKTtyZXR1cm4gcn1mdW5jdGlvbiBFd2UoZSx0LHIpe3ZhciBuPWUuX190cmFuc2l0aW9uLGk7blt0XT1yLHIudGltZXI9QV8obywwLHIudGltZSk7ZnVuY3Rpb24gbyhjKXtyLnN0YXRlPVNMLHIudGltZXIucmVzdGFydChhLHIuZGVsYXksci50aW1lKSxyLmRlbGF5PD1jJiZhKGMtci5kZWxheSl9ZnVuY3Rpb24gYShjKXt2YXIgdSxoLGYscDtpZihyLnN0YXRlIT09U0wpcmV0dXJuIGwoKTtmb3IodSBpbiBuKWlmKHA9blt1XSxwLm5hbWU9PT1yLm5hbWUpe2lmKHAuc3RhdGU9PT13TClyZXR1cm4gT0UoYSk7cC5zdGF0ZT09PVF5dD8ocC5zdGF0ZT16RSxwLnRpbWVyLnN0b3AoKSxwLm9uLmNhbGwoImludGVycnVwdCIsZSxlLl9fZGF0YV9fLHAuaW5kZXgscC5ncm91cCksZGVsZXRlIG5bdV0pOit1PHQmJihwLnN0YXRlPXpFLHAudGltZXIuc3RvcCgpLHAub24uY2FsbCgiY2FuY2VsIixlLGUuX19kYXRhX18scC5pbmRleCxwLmdyb3VwKSxkZWxldGUgblt1XSl9aWYoT0UoZnVuY3Rpb24oKXtyLnN0YXRlPT09d0wmJihyLnN0YXRlPVF5dCxyLnRpbWVyLnJlc3RhcnQocyxyLmRlbGF5LHIudGltZSkscyhjKSl9KSxyLnN0YXRlPU1MLHIub24uY2FsbCgic3RhcnQiLGUsZS5fX2RhdGFfXyxyLmluZGV4LHIuZ3JvdXApLHIuc3RhdGU9PT1NTCl7Zm9yKHIuc3RhdGU9d0wsaT1uZXcgQXJyYXkoZj1yLnR3ZWVuLmxlbmd0aCksdT0wLGg9LTE7dTxmOysrdSkocD1yLnR3ZWVuW3VdLnZhbHVlLmNhbGwoZSxlLl9fZGF0YV9fLHIuaW5kZXgsci5ncm91cCkpJiYoaVsrK2hdPXApO2kubGVuZ3RoPWgrMX19ZnVuY3Rpb24gcyhjKXtmb3IodmFyIHU9YzxyLmR1cmF0aW9uP3IuZWFzZS5jYWxsKG51bGwsYy9yLmR1cmF0aW9uKTooci50aW1lci5yZXN0YXJ0KGwpLHIuc3RhdGU9RUwsMSksaD0tMSxmPWkubGVuZ3RoOysraDxmOylpW2hdLmNhbGwoZSx1KTtyLnN0YXRlPT09RUwmJihyLm9uLmNhbGwoImVuZCIsZSxlLl9fZGF0YV9fLHIuaW5kZXgsci5ncm91cCksbCgpKX1mdW5jdGlvbiBsKCl7ci5zdGF0ZT16RSxyLnRpbWVyLnN0b3AoKSxkZWxldGUgblt0XTtmb3IodmFyIGMgaW4gbilyZXR1cm47ZGVsZXRlIGUuX190cmFuc2l0aW9ufX1mdW5jdGlvbiBodShlLHQpe3ZhciByPWUuX190cmFuc2l0aW9uLG4saSxvPSEwLGE7aWYoISFyKXt0PXQ9PW51bGw/bnVsbDp0KyIiO2ZvcihhIGluIHIpe2lmKChuPXJbYV0pLm5hbWUhPT10KXtvPSExO2NvbnRpbnVlfWk9bi5zdGF0ZT5NTCYmbi5zdGF0ZTxFTCxuLnN0YXRlPXpFLG4udGltZXIuc3RvcCgpLG4ub24uY2FsbChpPyJpbnRlcnJ1cHQiOiJjYW5jZWwiLGUsZS5fX2RhdGFfXyxuLmluZGV4LG4uZ3JvdXApLGRlbGV0ZSByW2FdfW8mJmRlbGV0ZSBlLl9fdHJhbnNpdGlvbn19ZnVuY3Rpb24gZTF0KGUpe3JldHVybiB0aGlzLmVhY2goZnVuY3Rpb24oKXtodSh0aGlzLGUpfSl9ZnVuY3Rpb24gVHdlKGUsdCl7dmFyIHIsbjtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgaT1PYSh0aGlzLGUpLG89aS50d2VlbjtpZihvIT09cil7bj1yPW87Zm9yKHZhciBhPTAscz1uLmxlbmd0aDthPHM7KythKWlmKG5bYV0ubmFtZT09PXQpe249bi5zbGljZSgpLG4uc3BsaWNlKGEsMSk7YnJlYWt9fWkudHdlZW49bn19ZnVuY3Rpb24gQ3dlKGUsdCxyKXt2YXIgbixpO2lmKHR5cGVvZiByIT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbz1PYSh0aGlzLGUpLGE9by50d2VlbjtpZihhIT09bil7aT0obj1hKS5zbGljZSgpO2Zvcih2YXIgcz17bmFtZTp0LHZhbHVlOnJ9LGw9MCxjPWkubGVuZ3RoO2w8YzsrK2wpaWYoaVtsXS5uYW1lPT09dCl7aVtsXT1zO2JyZWFrfWw9PT1jJiZpLnB1c2gocyl9by50d2Vlbj1pfX1mdW5jdGlvbiByMXQoZSx0KXt2YXIgcj10aGlzLl9pZDtpZihlKz0iIixhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcih2YXIgbj1aaSh0aGlzLm5vZGUoKSxyKS50d2VlbixpPTAsbz1uLmxlbmd0aCxhO2k8bzsrK2kpaWYoKGE9bltpXSkubmFtZT09PWUpcmV0dXJuIGEudmFsdWU7cmV0dXJuIG51bGx9cmV0dXJuIHRoaXMuZWFjaCgodD09bnVsbD9Ud2U6Q3dlKShyLGUsdCkpfWZ1bmN0aW9uIGJiKGUsdCxyKXt2YXIgbj1lLl9pZDtyZXR1cm4gZS5lYWNoKGZ1bmN0aW9uKCl7dmFyIGk9T2EodGhpcyxuKTsoaS52YWx1ZXx8KGkudmFsdWU9e30pKVt0XT1yLmFwcGx5KHRoaXMsYXJndW1lbnRzKX0pLGZ1bmN0aW9uKGkpe3JldHVybiBaaShpLG4pLnZhbHVlW3RdfX1mdW5jdGlvbiBUTChlLHQpe3ZhciByO3JldHVybih0eXBlb2YgdD09Im51bWJlciI/emk6dCBpbnN0YW5jZW9mIHJjP3FtOihyPXJjKHQpKT8odD1yLHFtKTp2YikoZSx0KX1mdW5jdGlvbiBBd2UoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGUoZSl9fWZ1bmN0aW9uIFB3ZShlKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnJlbW92ZUF0dHJpYnV0ZU5TKGUuc3BhY2UsZS5sb2NhbCl9fWZ1bmN0aW9uIEl3ZShlLHQscil7dmFyIG4saT1yKyIiLG87cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGE9dGhpcy5nZXRBdHRyaWJ1dGUoZSk7cmV0dXJuIGE9PT1pP251bGw6YT09PW4/bzpvPXQobj1hLHIpfX1mdW5jdGlvbiBMd2UoZSx0LHIpe3ZhciBuLGk9cisiIixvO3JldHVybiBmdW5jdGlvbigpe3ZhciBhPXRoaXMuZ2V0QXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKTtyZXR1cm4gYT09PWk/bnVsbDphPT09bj9vOm89dChuPWEscil9fWZ1bmN0aW9uIGt3ZShlLHQscil7dmFyIG4saSxvO3JldHVybiBmdW5jdGlvbigpe3ZhciBhLHM9cih0aGlzKSxsO3JldHVybiBzPT1udWxsP3ZvaWQgdGhpcy5yZW1vdmVBdHRyaWJ1dGUoZSk6KGE9dGhpcy5nZXRBdHRyaWJ1dGUoZSksbD1zKyIiLGE9PT1sP251bGw6YT09PW4mJmw9PT1pP286KGk9bCxvPXQobj1hLHMpKSl9fWZ1bmN0aW9uIFJ3ZShlLHQscil7dmFyIG4saSxvO3JldHVybiBmdW5jdGlvbigpe3ZhciBhLHM9cih0aGlzKSxsO3JldHVybiBzPT1udWxsP3ZvaWQgdGhpcy5yZW1vdmVBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpOihhPXRoaXMuZ2V0QXR0cmlidXRlTlMoZS5zcGFjZSxlLmxvY2FsKSxsPXMrIiIsYT09PWw/bnVsbDphPT09biYmbD09PWk/bzooaT1sLG89dChuPWEscykpKX19ZnVuY3Rpb24gbjF0KGUsdCl7dmFyIHI9UGgoZSksbj1yPT09InRyYW5zZm9ybSI/X0w6VEw7cmV0dXJuIHRoaXMuYXR0clR3ZWVuKGUsdHlwZW9mIHQ9PSJmdW5jdGlvbiI/KHIubG9jYWw/UndlOmt3ZSkocixuLGJiKHRoaXMsImF0dHIuIitlLHQpKTp0PT1udWxsPyhyLmxvY2FsP1B3ZTpBd2UpKHIpOihyLmxvY2FsP0x3ZTpJd2UpKHIsbix0KSl9ZnVuY3Rpb24gTndlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKHIpe3RoaXMuc2V0QXR0cmlidXRlKGUsdC5jYWxsKHRoaXMscikpfX1mdW5jdGlvbiBEd2UoZSx0KXtyZXR1cm4gZnVuY3Rpb24ocil7dGhpcy5zZXRBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwsdC5jYWxsKHRoaXMscikpfX1mdW5jdGlvbiBPd2UoZSx0KXt2YXIgcixuO2Z1bmN0aW9uIGkoKXt2YXIgbz10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyZXR1cm4gbyE9PW4mJihyPShuPW8pJiZEd2UoZSxvKSkscn1yZXR1cm4gaS5fdmFsdWU9dCxpfWZ1bmN0aW9uIHp3ZShlLHQpe3ZhciByLG47ZnVuY3Rpb24gaSgpe3ZhciBvPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiBvIT09biYmKHI9KG49bykmJk53ZShlLG8pKSxyfXJldHVybiBpLl92YWx1ZT10LGl9ZnVuY3Rpb24gaTF0KGUsdCl7dmFyIHI9ImF0dHIuIitlO2lmKGFyZ3VtZW50cy5sZW5ndGg8MilyZXR1cm4ocj10aGlzLnR3ZWVuKHIpKSYmci5fdmFsdWU7aWYodD09bnVsbClyZXR1cm4gdGhpcy50d2VlbihyLG51bGwpO2lmKHR5cGVvZiB0IT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjt2YXIgbj1QaChlKTtyZXR1cm4gdGhpcy50d2VlbihyLChuLmxvY2FsP093ZTp6d2UpKG4sdCkpfWZ1bmN0aW9uIEZ3ZShlLHQpe3JldHVybiBmdW5jdGlvbigpe0ZFKHRoaXMsZSkuZGVsYXk9K3QuYXBwbHkodGhpcyxhcmd1bWVudHMpfX1mdW5jdGlvbiBCd2UoZSx0KXtyZXR1cm4gdD0rdCxmdW5jdGlvbigpe0ZFKHRoaXMsZSkuZGVsYXk9dH19ZnVuY3Rpb24gbzF0KGUpe3ZhciB0PXRoaXMuX2lkO3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3RoaXMuZWFjaCgodHlwZW9mIGU9PSJmdW5jdGlvbiI/RndlOkJ3ZSkodCxlKSk6WmkodGhpcy5ub2RlKCksdCkuZGVsYXl9ZnVuY3Rpb24gSHdlKGUsdCl7cmV0dXJuIGZ1bmN0aW9uKCl7T2EodGhpcyxlKS5kdXJhdGlvbj0rdC5hcHBseSh0aGlzLGFyZ3VtZW50cyl9fWZ1bmN0aW9uIFZ3ZShlLHQpe3JldHVybiB0PSt0LGZ1bmN0aW9uKCl7T2EodGhpcyxlKS5kdXJhdGlvbj10fX1mdW5jdGlvbiBhMXQoZSl7dmFyIHQ9dGhpcy5faWQ7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5lYWNoKCh0eXBlb2YgZT09ImZ1bmN0aW9uIj9Id2U6VndlKSh0LGUpKTpaaSh0aGlzLm5vZGUoKSx0KS5kdXJhdGlvbn1mdW5jdGlvbiBVd2UoZSx0KXtpZih0eXBlb2YgdCE9ImZ1bmN0aW9uIil0aHJvdyBuZXcgRXJyb3I7cmV0dXJuIGZ1bmN0aW9uKCl7T2EodGhpcyxlKS5lYXNlPXR9fWZ1bmN0aW9uIHMxdChlKXt2YXIgdD10aGlzLl9pZDtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2goVXdlKHQsZSkpOlppKHRoaXMubm9kZSgpLHQpLmVhc2V9ZnVuY3Rpb24gbDF0KGUpe3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT11YihlKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxyPXQubGVuZ3RoLG49bmV3IEFycmF5KHIpLGk9MDtpPHI7KytpKWZvcih2YXIgbz10W2ldLGE9by5sZW5ndGgscz1uW2ldPVtdLGwsYz0wO2M8YTsrK2MpKGw9b1tjXSkmJmUuY2FsbChsLGwuX19kYXRhX18sYyxvKSYmcy5wdXNoKGwpO3JldHVybiBuZXcgRm8obix0aGlzLl9wYXJlbnRzLHRoaXMuX25hbWUsdGhpcy5faWQpfWZ1bmN0aW9uIGMxdChlKXtpZihlLl9pZCE9PXRoaXMuX2lkKXRocm93IG5ldyBFcnJvcjtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLHI9ZS5fZ3JvdXBzLG49dC5sZW5ndGgsaT1yLmxlbmd0aCxvPU1hdGgubWluKG4saSksYT1uZXcgQXJyYXkobikscz0wO3M8bzsrK3MpZm9yKHZhciBsPXRbc10sYz1yW3NdLHU9bC5sZW5ndGgsaD1hW3NdPW5ldyBBcnJheSh1KSxmLHA9MDtwPHU7KytwKShmPWxbcF18fGNbcF0pJiYoaFtwXT1mKTtmb3IoO3M8bjsrK3MpYVtzXT10W3NdO3JldHVybiBuZXcgRm8oYSx0aGlzLl9wYXJlbnRzLHRoaXMuX25hbWUsdGhpcy5faWQpfWZ1bmN0aW9uIHF3ZShlKXtyZXR1cm4oZSsiIikudHJpbSgpLnNwbGl0KC9efFxzKy8pLmV2ZXJ5KGZ1bmN0aW9uKHQpe3ZhciByPXQuaW5kZXhPZigiLiIpO3JldHVybiByPj0wJiYodD10LnNsaWNlKDAscikpLCF0fHx0PT09InN0YXJ0In0pfWZ1bmN0aW9uIEd3ZShlLHQscil7dmFyIG4saSxvPXF3ZSh0KT9GRTpPYTtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgYT1vKHRoaXMsZSkscz1hLm9uO3MhPT1uJiYoaT0obj1zKS5jb3B5KCkpLm9uKHQsciksYS5vbj1pfX1mdW5jdGlvbiB1MXQoZSx0KXt2YXIgcj10aGlzLl9pZDtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aDwyP1ppKHRoaXMubm9kZSgpLHIpLm9uLm9uKGUpOnRoaXMuZWFjaChHd2UocixlLHQpKX1mdW5jdGlvbiBXd2UoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5wYXJlbnROb2RlO2Zvcih2YXIgciBpbiB0aGlzLl9fdHJhbnNpdGlvbilpZigrciE9PWUpcmV0dXJuO3QmJnQucmVtb3ZlQ2hpbGQodGhpcyl9fWZ1bmN0aW9uIGgxdCgpe3JldHVybiB0aGlzLm9uKCJlbmQucmVtb3ZlIixXd2UodGhpcy5faWQpKX1mdW5jdGlvbiBmMXQoZSl7dmFyIHQ9dGhpcy5fbmFtZSxyPXRoaXMuX2lkO3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1ObShlKSk7Zm9yKHZhciBuPXRoaXMuX2dyb3VwcyxpPW4ubGVuZ3RoLG89bmV3IEFycmF5KGkpLGE9MDthPGk7KythKWZvcih2YXIgcz1uW2FdLGw9cy5sZW5ndGgsYz1vW2FdPW5ldyBBcnJheShsKSx1LGgsZj0wO2Y8bDsrK2YpKHU9c1tmXSkmJihoPWUuY2FsbCh1LHUuX19kYXRhX18sZixzKSkmJigiX19kYXRhX18iaW4gdSYmKGguX19kYXRhX189dS5fX2RhdGFfXyksY1tmXT1oLEdtKGNbZl0sdCxyLGYsYyxaaSh1LHIpKSk7cmV0dXJuIG5ldyBGbyhvLHRoaXMuX3BhcmVudHMsdCxyKX1mdW5jdGlvbiBwMXQoZSl7dmFyIHQ9dGhpcy5fbmFtZSxyPXRoaXMuX2lkO3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1jYihlKSk7Zm9yKHZhciBuPXRoaXMuX2dyb3VwcyxpPW4ubGVuZ3RoLG89W10sYT1bXSxzPTA7czxpOysrcylmb3IodmFyIGw9bltzXSxjPWwubGVuZ3RoLHUsaD0wO2g8YzsrK2gpaWYodT1sW2hdKXtmb3IodmFyIGY9ZS5jYWxsKHUsdS5fX2RhdGFfXyxoLGwpLHAsZD1aaSh1LHIpLGc9MCxfPWYubGVuZ3RoO2c8XzsrK2cpKHA9ZltnXSkmJkdtKHAsdCxyLGcsZixkKTtvLnB1c2goZiksYS5wdXNoKHUpfXJldHVybiBuZXcgRm8obyxhLHQscil9dmFyIFl3ZT1JaC5wcm90b3R5cGUuY29uc3RydWN0b3I7ZnVuY3Rpb24gZDF0KCl7cmV0dXJuIG5ldyBZd2UodGhpcy5fZ3JvdXBzLHRoaXMuX3BhcmVudHMpfWZ1bmN0aW9uIGp3ZShlLHQpe3ZhciByLG4saTtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbz1TcCh0aGlzLGUpLGE9KHRoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkoZSksU3AodGhpcyxlKSk7cmV0dXJuIG89PT1hP251bGw6bz09PXImJmE9PT1uP2k6aT10KHI9byxuPWEpfX1mdW5jdGlvbiBtMXQoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShlKX19ZnVuY3Rpb24gWHdlKGUsdCxyKXt2YXIgbixpPXIrIiIsbztyZXR1cm4gZnVuY3Rpb24oKXt2YXIgYT1TcCh0aGlzLGUpO3JldHVybiBhPT09aT9udWxsOmE9PT1uP286bz10KG49YSxyKX19ZnVuY3Rpb24gJHdlKGUsdCxyKXt2YXIgbixpLG87cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGE9U3AodGhpcyxlKSxzPXIodGhpcyksbD1zKyIiO3JldHVybiBzPT1udWxsJiYobD1zPSh0aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KGUpLFNwKHRoaXMsZSkpKSxhPT09bD9udWxsOmE9PT1uJiZsPT09aT9vOihpPWwsbz10KG49YSxzKSl9fWZ1bmN0aW9uIEt3ZShlLHQpe3ZhciByLG4saSxvPSJzdHlsZS4iK3QsYT0iZW5kLiIrbyxzO3JldHVybiBmdW5jdGlvbigpe3ZhciBsPU9hKHRoaXMsZSksYz1sLm9uLHU9bC52YWx1ZVtvXT09bnVsbD9zfHwocz1tMXQodCkpOnZvaWQgMDsoYyE9PXJ8fGkhPT11KSYmKG49KHI9YykuY29weSgpKS5vbihhLGk9dSksbC5vbj1ufX1mdW5jdGlvbiBnMXQoZSx0LHIpe3ZhciBuPShlKz0iIik9PSJ0cmFuc2Zvcm0iP2dMOlRMO3JldHVybiB0PT1udWxsP3RoaXMuc3R5bGVUd2VlbihlLGp3ZShlLG4pKS5vbigiZW5kLnN0eWxlLiIrZSxtMXQoZSkpOnR5cGVvZiB0PT0iZnVuY3Rpb24iP3RoaXMuc3R5bGVUd2VlbihlLCR3ZShlLG4sYmIodGhpcywic3R5bGUuIitlLHQpKSkuZWFjaChLd2UodGhpcy5faWQsZSkpOnRoaXMuc3R5bGVUd2VlbihlLFh3ZShlLG4sdCkscikub24oImVuZC5zdHlsZS4iK2UsbnVsbCl9ZnVuY3Rpb24gWndlKGUsdCxyKXtyZXR1cm4gZnVuY3Rpb24obil7dGhpcy5zdHlsZS5zZXRQcm9wZXJ0eShlLHQuY2FsbCh0aGlzLG4pLHIpfX1mdW5jdGlvbiBKd2UoZSx0LHIpe3ZhciBuLGk7ZnVuY3Rpb24gbygpe3ZhciBhPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiBhIT09aSYmKG49KGk9YSkmJlp3ZShlLGEscikpLG59cmV0dXJuIG8uX3ZhbHVlPXQsb31mdW5jdGlvbiBfMXQoZSx0LHIpe3ZhciBuPSJzdHlsZS4iKyhlKz0iIik7aWYoYXJndW1lbnRzLmxlbmd0aDwyKXJldHVybihuPXRoaXMudHdlZW4obikpJiZuLl92YWx1ZTtpZih0PT1udWxsKXJldHVybiB0aGlzLnR3ZWVuKG4sbnVsbCk7aWYodHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3JldHVybiB0aGlzLnR3ZWVuKG4sSndlKGUsdCxyPT1udWxsPyIiOnIpKX1mdW5jdGlvbiBRd2UoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy50ZXh0Q29udGVudD1lfX1mdW5jdGlvbiB0U2UoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9ZSh0aGlzKTt0aGlzLnRleHRDb250ZW50PXQ9PW51bGw/IiI6dH19ZnVuY3Rpb24geTF0KGUpe3JldHVybiB0aGlzLnR3ZWVuKCJ0ZXh0Iix0eXBlb2YgZT09ImZ1bmN0aW9uIj90U2UoYmIodGhpcywidGV4dCIsZSkpOlF3ZShlPT1udWxsPyIiOmUrIiIpKX1mdW5jdGlvbiBlU2UoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3RoaXMudGV4dENvbnRlbnQ9ZS5jYWxsKHRoaXMsdCl9fWZ1bmN0aW9uIHJTZShlKXt2YXIgdCxyO2Z1bmN0aW9uIG4oKXt2YXIgaT1lLmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyZXR1cm4gaSE9PXImJih0PShyPWkpJiZlU2UoaSkpLHR9cmV0dXJuIG4uX3ZhbHVlPWUsbn1mdW5jdGlvbiB2MXQoZSl7dmFyIHQ9InRleHQiO2lmKGFyZ3VtZW50cy5sZW5ndGg8MSlyZXR1cm4odD10aGlzLnR3ZWVuKHQpKSYmdC5fdmFsdWU7aWYoZT09bnVsbClyZXR1cm4gdGhpcy50d2Vlbih0LG51bGwpO2lmKHR5cGVvZiBlIT0iZnVuY3Rpb24iKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gdGhpcy50d2Vlbih0LHJTZShlKSl9ZnVuY3Rpb24geDF0KCl7Zm9yKHZhciBlPXRoaXMuX25hbWUsdD10aGlzLl9pZCxyPUNMKCksbj10aGlzLl9ncm91cHMsaT1uLmxlbmd0aCxvPTA7bzxpOysrbylmb3IodmFyIGE9bltvXSxzPWEubGVuZ3RoLGwsYz0wO2M8czsrK2MpaWYobD1hW2NdKXt2YXIgdT1aaShsLHQpO0dtKGwsZSxyLGMsYSx7dGltZTp1LnRpbWUrdS5kZWxheSt1LmR1cmF0aW9uLGRlbGF5OjAsZHVyYXRpb246dS5kdXJhdGlvbixlYXNlOnUuZWFzZX0pfXJldHVybiBuZXcgRm8obix0aGlzLl9wYXJlbnRzLGUscil9ZnVuY3Rpb24gYjF0KCl7dmFyIGUsdCxyPXRoaXMsbj1yLl9pZCxpPXIuc2l6ZSgpO3JldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbihvLGEpe3ZhciBzPXt2YWx1ZTphfSxsPXt2YWx1ZTpmdW5jdGlvbigpey0taT09PTAmJm8oKX19O3IuZWFjaChmdW5jdGlvbigpe3ZhciBjPU9hKHRoaXMsbiksdT1jLm9uO3UhPT1lJiYodD0oZT11KS5jb3B5KCksdC5fLmNhbmNlbC5wdXNoKHMpLHQuXy5pbnRlcnJ1cHQucHVzaChzKSx0Ll8uZW5kLnB1c2gobCkpLGMub249dH0pfSl9dmFyIG5TZT0wO2Z1bmN0aW9uIEZvKGUsdCxyLG4pe3RoaXMuX2dyb3Vwcz1lLHRoaXMuX3BhcmVudHM9dCx0aGlzLl9uYW1lPXIsdGhpcy5faWQ9bn1mdW5jdGlvbiBBTChlKXtyZXR1cm4gSWgoKS50cmFuc2l0aW9uKGUpfWZ1bmN0aW9uIENMKCl7cmV0dXJuKytuU2V9dmFyIHdiPUloLnByb3RvdHlwZTtGby5wcm90b3R5cGU9QUwucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpGbyxzZWxlY3Q6ZjF0LHNlbGVjdEFsbDpwMXQsZmlsdGVyOmwxdCxtZXJnZTpjMXQsc2VsZWN0aW9uOmQxdCx0cmFuc2l0aW9uOngxdCxjYWxsOndiLmNhbGwsbm9kZXM6d2Iubm9kZXMsbm9kZTp3Yi5ub2RlLHNpemU6d2Iuc2l6ZSxlbXB0eTp3Yi5lbXB0eSxlYWNoOndiLmVhY2gsb246dTF0LGF0dHI6bjF0LGF0dHJUd2VlbjppMXQsc3R5bGU6ZzF0LHN0eWxlVHdlZW46XzF0LHRleHQ6eTF0LHRleHRUd2Vlbjp2MXQscmVtb3ZlOmgxdCx0d2VlbjpyMXQsZGVsYXk6bzF0LGR1cmF0aW9uOmExdCxlYXNlOnMxdCxlbmQ6YjF0fTtJXygpO3ZhciBxWT17dGltZTpudWxsLGRlbGF5OjAsZHVyYXRpb246MjUwLGVhc2U6eHN9O2Z1bmN0aW9uIGZTZShlLHQpe2Zvcih2YXIgcjshKHI9ZS5fX3RyYW5zaXRpb24pfHwhKHI9clt0XSk7KWlmKCEoZT1lLnBhcmVudE5vZGUpKXJldHVybiBxWS50aW1lPUFwKCkscVk7cmV0dXJuIHJ9ZnVuY3Rpb24gRDF0KGUpe3ZhciB0LHI7ZSBpbnN0YW5jZW9mIEZvPyh0PWUuX2lkLGU9ZS5fbmFtZSk6KHQ9Q0woKSwocj1xWSkudGltZT1BcCgpLGU9ZT09bnVsbD9udWxsOmUrIiIpO2Zvcih2YXIgbj10aGlzLl9ncm91cHMsaT1uLmxlbmd0aCxvPTA7bzxpOysrbylmb3IodmFyIGE9bltvXSxzPWEubGVuZ3RoLGwsYz0wO2M8czsrK2MpKGw9YVtjXSkmJkdtKGwsZSx0LGMsYSxyfHxmU2UobCx0KSk7cmV0dXJuIG5ldyBGbyhuLHRoaXMuX3BhcmVudHMsZSx0KX1JaC5wcm90b3R5cGUuaW50ZXJydXB0PWUxdDtJaC5wcm90b3R5cGUudHJhbnNpdGlvbj1EMXQ7dmFyIHBTZT1bbnVsbF07ZnVuY3Rpb24gTzF0KGUsdCl7dmFyIHI9ZS5fX3RyYW5zaXRpb24sbixpO2lmKHIpe3Q9dD09bnVsbD9udWxsOnQrIiI7Zm9yKGkgaW4gcilpZigobj1yW2ldKS5zdGF0ZT5TTCYmbi5uYW1lPT09dClyZXR1cm4gbmV3IEZvKFtbZV1dLHBTZSx0LCtpKX1yZXR1cm4gbnVsbH1mdW5jdGlvbiB6TChlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19ZnVuY3Rpb24gejF0KGUsdCxyKXt0aGlzLnRhcmdldD1lLHRoaXMudHlwZT10LHRoaXMuc2VsZWN0aW9uPXJ9ZnVuY3Rpb24gR1koKXtxdC5zdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24oKX1mdW5jdGlvbiBGTCgpe3F0LnByZXZlbnREZWZhdWx0KCkscXQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCl9dmFyIEYxdD17bmFtZToiZHJhZyJ9LFdZPXtuYW1lOiJzcGFjZSJ9LE1iPXtuYW1lOiJoYW5kbGUifSxFYj17bmFtZToiY2VudGVyIn07ZnVuY3Rpb24gQjF0KGUpe3JldHVyblsrZVswXSwrZVsxXV19ZnVuY3Rpb24galkoZSl7cmV0dXJuW0IxdChlWzBdKSxCMXQoZVsxXSldfWZ1bmN0aW9uIGRTZShlKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuIFRwKHQscXQudG91Y2hlcyxlKX19dmFyIEJMPXtuYW1lOiJ4IixoYW5kbGVzOlsidyIsImUiXS5tYXAoQkUpLGlucHV0OmZ1bmN0aW9uKGUsdCl7cmV0dXJuIGU9PW51bGw/bnVsbDpbWytlWzBdLHRbMF1bMV1dLFsrZVsxXSx0WzFdWzFdXV19LG91dHB1dDpmdW5jdGlvbihlKXtyZXR1cm4gZSYmW2VbMF1bMF0sZVsxXVswXV19fSxITD17bmFtZToieSIsaGFuZGxlczpbIm4iLCJzIl0ubWFwKEJFKSxpbnB1dDpmdW5jdGlvbihlLHQpe3JldHVybiBlPT1udWxsP251bGw6W1t0WzBdWzBdLCtlWzBdXSxbdFsxXVswXSwrZVsxXV1dfSxvdXRwdXQ6ZnVuY3Rpb24oZSl7cmV0dXJuIGUmJltlWzBdWzFdLGVbMV1bMV1dfX0sbVNlPXtuYW1lOiJ4eSIsaGFuZGxlczpbIm4iLCJ3IiwiZSIsInMiLCJudyIsIm5lIiwic3ciLCJzZSJdLm1hcChCRSksaW5wdXQ6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9PW51bGw/bnVsbDpqWShlKX0sb3V0cHV0OmZ1bmN0aW9uKGUpe3JldHVybiBlfX0sUHA9e292ZXJsYXk6ImNyb3NzaGFpciIsc2VsZWN0aW9uOiJtb3ZlIixuOiJucy1yZXNpemUiLGU6ImV3LXJlc2l6ZSIsczoibnMtcmVzaXplIix3OiJldy1yZXNpemUiLG53OiJud3NlLXJlc2l6ZSIsbmU6Im5lc3ctcmVzaXplIixzZToibndzZS1yZXNpemUiLHN3OiJuZXN3LXJlc2l6ZSJ9LEgxdD17ZToidyIsdzoiZSIsbnc6Im5lIixuZToibnciLHNlOiJzdyIsc3c6InNlIn0sVjF0PXtuOiJzIixzOiJuIixudzoic3ciLG5lOiJzZSIsc2U6Im5lIixzdzoibncifSxnU2U9e292ZXJsYXk6MSxzZWxlY3Rpb246MSxuOm51bGwsZToxLHM6bnVsbCx3Oi0xLG53Oi0xLG5lOjEsc2U6MSxzdzotMX0sX1NlPXtvdmVybGF5OjEsc2VsZWN0aW9uOjEsbjotMSxlOm51bGwsczoxLHc6bnVsbCxudzotMSxuZTotMSxzZToxLHN3OjF9O2Z1bmN0aW9uIEJFKGUpe3JldHVybnt0eXBlOmV9fWZ1bmN0aW9uIHlTZSgpe3JldHVybiFxdC5jdHJsS2V5JiYhcXQuYnV0dG9ufWZ1bmN0aW9uIHZTZSgpe3ZhciBlPXRoaXMub3duZXJTVkdFbGVtZW50fHx0aGlzO3JldHVybiBlLmhhc0F0dHJpYnV0ZSgidmlld0JveCIpPyhlPWUudmlld0JveC5iYXNlVmFsLFtbZS54LGUueV0sW2UueCtlLndpZHRoLGUueStlLmhlaWdodF1dKTpbWzAsMF0sW2Uud2lkdGguYmFzZVZhbC52YWx1ZSxlLmhlaWdodC5iYXNlVmFsLnZhbHVlXV19ZnVuY3Rpb24geFNlKCl7cmV0dXJuIG5hdmlnYXRvci5tYXhUb3VjaFBvaW50c3x8Im9udG91Y2hzdGFydCJpbiB0aGlzfWZ1bmN0aW9uIFlZKGUpe2Zvcig7IWUuX19icnVzaDspaWYoIShlPWUucGFyZW50Tm9kZSkpcmV0dXJuO3JldHVybiBlLl9fYnJ1c2h9ZnVuY3Rpb24gYlNlKGUpe3JldHVybiBlWzBdWzBdPT09ZVsxXVswXXx8ZVswXVsxXT09PWVbMV1bMV19ZnVuY3Rpb24gVkwoZSl7dmFyIHQ9ZS5fX2JydXNoO3JldHVybiB0P3QuZGltLm91dHB1dCh0LnNlbGVjdGlvbik6bnVsbH1mdW5jdGlvbiBVMXQoKXtyZXR1cm4gWFkoQkwpfWZ1bmN0aW9uIFVMKCl7cmV0dXJuIFhZKEhMKX1mdW5jdGlvbiBxTCgpe3JldHVybiBYWShtU2UpfWZ1bmN0aW9uIFhZKGUpe3ZhciB0PXZTZSxyPXlTZSxuPXhTZSxpPSEwLG89dnMoInN0YXJ0IiwiYnJ1c2giLCJlbmQiKSxhPTYscztmdW5jdGlvbiBsKF8pe3ZhciB5PV8ucHJvcGVydHkoIl9fYnJ1c2giLGcpLnNlbGVjdEFsbCgiLm92ZXJsYXkiKS5kYXRhKFtCRSgib3ZlcmxheSIpXSk7eS5lbnRlcigpLmFwcGVuZCgicmVjdCIpLmF0dHIoImNsYXNzIiwib3ZlcmxheSIpLmF0dHIoInBvaW50ZXItZXZlbnRzIiwiYWxsIikuYXR0cigiY3Vyc29yIixQcC5vdmVybGF5KS5tZXJnZSh5KS5lYWNoKGZ1bmN0aW9uKCl7dmFyIGI9WVkodGhpcykuZXh0ZW50O0h0KHRoaXMpLmF0dHIoIngiLGJbMF1bMF0pLmF0dHIoInkiLGJbMF1bMV0pLmF0dHIoIndpZHRoIixiWzFdWzBdLWJbMF1bMF0pLmF0dHIoImhlaWdodCIsYlsxXVsxXS1iWzBdWzFdKX0pLF8uc2VsZWN0QWxsKCIuc2VsZWN0aW9uIikuZGF0YShbQkUoInNlbGVjdGlvbiIpXSkuZW50ZXIoKS5hcHBlbmQoInJlY3QiKS5hdHRyKCJjbGFzcyIsInNlbGVjdGlvbiIpLmF0dHIoImN1cnNvciIsUHAuc2VsZWN0aW9uKS5hdHRyKCJmaWxsIiwiIzc3NyIpLmF0dHIoImZpbGwtb3BhY2l0eSIsLjMpLmF0dHIoInN0cm9rZSIsIiNmZmYiKS5hdHRyKCJzaGFwZS1yZW5kZXJpbmciLCJjcmlzcEVkZ2VzIik7dmFyIHg9Xy5zZWxlY3RBbGwoIi5oYW5kbGUiKS5kYXRhKGUuaGFuZGxlcyxmdW5jdGlvbihiKXtyZXR1cm4gYi50eXBlfSk7eC5leGl0KCkucmVtb3ZlKCkseC5lbnRlcigpLmFwcGVuZCgicmVjdCIpLmF0dHIoImNsYXNzIixmdW5jdGlvbihiKXtyZXR1cm4iaGFuZGxlIGhhbmRsZS0tIitiLnR5cGV9KS5hdHRyKCJjdXJzb3IiLGZ1bmN0aW9uKGIpe3JldHVybiBQcFtiLnR5cGVdfSksXy5lYWNoKGMpLmF0dHIoImZpbGwiLCJub25lIikuYXR0cigicG9pbnRlci1ldmVudHMiLCJhbGwiKS5vbigibW91c2Vkb3duLmJydXNoIixmKS5maWx0ZXIobikub24oInRvdWNoc3RhcnQuYnJ1c2giLGYpLm9uKCJ0b3VjaG1vdmUuYnJ1c2giLHApLm9uKCJ0b3VjaGVuZC5icnVzaCB0b3VjaGNhbmNlbC5icnVzaCIsZCkuc3R5bGUoInRvdWNoLWFjdGlvbiIsIm5vbmUiKS5zdHlsZSgiLXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yIiwicmdiYSgwLDAsMCwwKSIpfWwubW92ZT1mdW5jdGlvbihfLHkpe18uc2VsZWN0aW9uP18ub24oInN0YXJ0LmJydXNoIixmdW5jdGlvbigpe3UodGhpcyxhcmd1bWVudHMpLmJlZm9yZXN0YXJ0KCkuc3RhcnQoKX0pLm9uKCJpbnRlcnJ1cHQuYnJ1c2ggZW5kLmJydXNoIixmdW5jdGlvbigpe3UodGhpcyxhcmd1bWVudHMpLmVuZCgpfSkudHdlZW4oImJydXNoIixmdW5jdGlvbigpe3ZhciB4PXRoaXMsYj14Ll9fYnJ1c2gsUz11KHgsYXJndW1lbnRzKSxDPWIuc2VsZWN0aW9uLFA9ZS5pbnB1dCh0eXBlb2YgeT09ImZ1bmN0aW9uIj95LmFwcGx5KHRoaXMsYXJndW1lbnRzKTp5LGIuZXh0ZW50KSxrPW5jKEMsUCk7ZnVuY3Rpb24gTyhEKXtiLnNlbGVjdGlvbj1EPT09MSYmUD09PW51bGw/bnVsbDprKEQpLGMuY2FsbCh4KSxTLmJydXNoKCl9cmV0dXJuIEMhPT1udWxsJiZQIT09bnVsbD9POk8oMSl9KTpfLmVhY2goZnVuY3Rpb24oKXt2YXIgeD10aGlzLGI9YXJndW1lbnRzLFM9eC5fX2JydXNoLEM9ZS5pbnB1dCh0eXBlb2YgeT09ImZ1bmN0aW9uIj95LmFwcGx5KHgsYik6eSxTLmV4dGVudCksUD11KHgsYikuYmVmb3Jlc3RhcnQoKTtodSh4KSxTLnNlbGVjdGlvbj1DPT09bnVsbD9udWxsOkMsYy5jYWxsKHgpLFAuc3RhcnQoKS5icnVzaCgpLmVuZCgpfSl9LGwuY2xlYXI9ZnVuY3Rpb24oXyl7bC5tb3ZlKF8sbnVsbCl9O2Z1bmN0aW9uIGMoKXt2YXIgXz1IdCh0aGlzKSx5PVlZKHRoaXMpLnNlbGVjdGlvbjt5PyhfLnNlbGVjdEFsbCgiLnNlbGVjdGlvbiIpLnN0eWxlKCJkaXNwbGF5IixudWxsKS5hdHRyKCJ4Iix5WzBdWzBdKS5hdHRyKCJ5Iix5WzBdWzFdKS5hdHRyKCJ3aWR0aCIseVsxXVswXS15WzBdWzBdKS5hdHRyKCJoZWlnaHQiLHlbMV1bMV0teVswXVsxXSksXy5zZWxlY3RBbGwoIi5oYW5kbGUiKS5zdHlsZSgiZGlzcGxheSIsbnVsbCkuYXR0cigieCIsZnVuY3Rpb24oeCl7cmV0dXJuIHgudHlwZVt4LnR5cGUubGVuZ3RoLTFdPT09ImUiP3lbMV1bMF0tYS8yOnlbMF1bMF0tYS8yfSkuYXR0cigieSIsZnVuY3Rpb24oeCl7cmV0dXJuIHgudHlwZVswXT09PSJzIj95WzFdWzFdLWEvMjp5WzBdWzFdLWEvMn0pLmF0dHIoIndpZHRoIixmdW5jdGlvbih4KXtyZXR1cm4geC50eXBlPT09Im4ifHx4LnR5cGU9PT0icyI/eVsxXVswXS15WzBdWzBdK2E6YX0pLmF0dHIoImhlaWdodCIsZnVuY3Rpb24oeCl7cmV0dXJuIHgudHlwZT09PSJlInx8eC50eXBlPT09InciP3lbMV1bMV0teVswXVsxXSthOmF9KSk6Xy5zZWxlY3RBbGwoIi5zZWxlY3Rpb24sLmhhbmRsZSIpLnN0eWxlKCJkaXNwbGF5Iiwibm9uZSIpLmF0dHIoIngiLG51bGwpLmF0dHIoInkiLG51bGwpLmF0dHIoIndpZHRoIixudWxsKS5hdHRyKCJoZWlnaHQiLG51bGwpfWZ1bmN0aW9uIHUoXyx5LHgpe3ZhciBiPV8uX19icnVzaC5lbWl0dGVyO3JldHVybiBiJiYoIXh8fCFiLmNsZWFuKT9iOm5ldyBoKF8seSx4KX1mdW5jdGlvbiBoKF8seSx4KXt0aGlzLnRoYXQ9Xyx0aGlzLmFyZ3M9eSx0aGlzLnN0YXRlPV8uX19icnVzaCx0aGlzLmFjdGl2ZT0wLHRoaXMuY2xlYW49eH1oLnByb3RvdHlwZT17YmVmb3Jlc3RhcnQ6ZnVuY3Rpb24oKXtyZXR1cm4rK3RoaXMuYWN0aXZlPT09MSYmKHRoaXMuc3RhdGUuZW1pdHRlcj10aGlzLHRoaXMuc3RhcnRpbmc9ITApLHRoaXN9LHN0YXJ0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc3RhcnRpbmc/KHRoaXMuc3RhcnRpbmc9ITEsdGhpcy5lbWl0KCJzdGFydCIpKTp0aGlzLmVtaXQoImJydXNoIiksdGhpc30sYnJ1c2g6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5lbWl0KCJicnVzaCIpLHRoaXN9LGVuZDpmdW5jdGlvbigpe3JldHVybi0tdGhpcy5hY3RpdmU9PT0wJiYoZGVsZXRlIHRoaXMuc3RhdGUuZW1pdHRlcix0aGlzLmVtaXQoImVuZCIpKSx0aGlzfSxlbWl0OmZ1bmN0aW9uKF8pe01wKG5ldyB6MXQobCxfLGUub3V0cHV0KHRoaXMuc3RhdGUuc2VsZWN0aW9uKSksby5hcHBseSxvLFtfLHRoaXMudGhhdCx0aGlzLmFyZ3NdKX19O2Z1bmN0aW9uIGYoKXtpZihzJiYhcXQudG91Y2hlc3x8IXIuYXBwbHkodGhpcyxhcmd1bWVudHMpKXJldHVybjt2YXIgXz10aGlzLHk9cXQudGFyZ2V0Ll9fZGF0YV9fLnR5cGUseD0oaSYmcXQubWV0YUtleT95PSJvdmVybGF5Ijp5KT09PSJzZWxlY3Rpb24iP0YxdDppJiZxdC5hbHRLZXk/RWI6TWIsYj1lPT09SEw/bnVsbDpnU2VbeV0sUz1lPT09Qkw/bnVsbDpfU2VbeV0sQz1ZWShfKSxQPUMuZXh0ZW50LGs9Qy5zZWxlY3Rpb24sTz1QWzBdWzBdLEQsQixJPVBbMF1bMV0sTCxSLEY9UFsxXVswXSx6LFUsVz1QWzFdWzFdLFoscnQsb3Q9MCxzdD0wLFN0LGJ0PWImJlMmJmkmJnF0LnNoaWZ0S2V5LE10LGx0LEt0PXF0LnRvdWNoZXM/ZFNlKHF0LmNoYW5nZWRUb3VjaGVzWzBdLmlkZW50aWZpZXIpOnpvLF90PUt0KF8pLGN0PV90LFg9dShfLGFyZ3VtZW50cywhMCkuYmVmb3Jlc3RhcnQoKTt5PT09Im92ZXJsYXkiPyhrJiYoU3Q9ITApLEMuc2VsZWN0aW9uPWs9W1tEPWU9PT1ITD9POl90WzBdLEw9ZT09PUJMP0k6X3RbMV1dLFt6PWU9PT1ITD9GOkQsWj1lPT09Qkw/VzpMXV0pOihEPWtbMF1bMF0sTD1rWzBdWzFdLHo9a1sxXVswXSxaPWtbMV1bMV0pLEI9RCxSPUwsVT16LHJ0PVo7dmFyIGV0PUh0KF8pLmF0dHIoInBvaW50ZXItZXZlbnRzIiwibm9uZSIpLGR0PWV0LnNlbGVjdEFsbCgiLm92ZXJsYXkiKS5hdHRyKCJjdXJzb3IiLFBwW3ldKTtpZihxdC50b3VjaGVzKVgubW92ZWQ9cHQsWC5lbmRlZD13dDtlbHNle3ZhciBxPUh0KHF0LnZpZXcpLm9uKCJtb3VzZW1vdmUuYnJ1c2giLHB0LCEwKS5vbigibW91c2V1cC5icnVzaCIsd3QsITApO2kmJnEub24oImtleWRvd24uYnJ1c2giLGt0LCEwKS5vbigia2V5dXAuYnJ1c2giLGllLCEwKSx6bShxdC52aWV3KX1HWSgpLGh1KF8pLGMuY2FsbChfKSxYLnN0YXJ0KCk7ZnVuY3Rpb24gcHQoKXt2YXIgZWU9S3QoXyk7YnQmJiFNdCYmIWx0JiYoTWF0aC5hYnMoZWVbMF0tY3RbMF0pPk1hdGguYWJzKGVlWzFdLWN0WzFdKT9sdD0hMDpNdD0hMCksY3Q9ZWUsU3Q9ITAsRkwoKSxodCgpfWZ1bmN0aW9uIGh0KCl7dmFyIGVlO3N3aXRjaChvdD1jdFswXS1fdFswXSxzdD1jdFsxXS1fdFsxXSx4KXtjYXNlIFdZOmNhc2UgRjF0OntiJiYob3Q9TWF0aC5tYXgoTy1ELE1hdGgubWluKEYteixvdCkpLEI9RCtvdCxVPXorb3QpLFMmJihzdD1NYXRoLm1heChJLUwsTWF0aC5taW4oVy1aLHN0KSksUj1MK3N0LHJ0PVorc3QpO2JyZWFrfWNhc2UgTWI6e2I8MD8ob3Q9TWF0aC5tYXgoTy1ELE1hdGgubWluKEYtRCxvdCkpLEI9RCtvdCxVPXopOmI+MCYmKG90PU1hdGgubWF4KE8teixNYXRoLm1pbihGLXosb3QpKSxCPUQsVT16K290KSxTPDA/KHN0PU1hdGgubWF4KEktTCxNYXRoLm1pbihXLUwsc3QpKSxSPUwrc3QscnQ9Wik6Uz4wJiYoc3Q9TWF0aC5tYXgoSS1aLE1hdGgubWluKFctWixzdCkpLFI9TCxydD1aK3N0KTticmVha31jYXNlIEViOntiJiYoQj1NYXRoLm1heChPLE1hdGgubWluKEYsRC1vdCpiKSksVT1NYXRoLm1heChPLE1hdGgubWluKEYseitvdCpiKSkpLFMmJihSPU1hdGgubWF4KEksTWF0aC5taW4oVyxMLXN0KlMpKSxydD1NYXRoLm1heChJLE1hdGgubWluKFcsWitzdCpTKSkpO2JyZWFrfX1VPEImJihiKj0tMSxlZT1ELEQ9eix6PWVlLGVlPUIsQj1VLFU9ZWUseSBpbiBIMXQmJmR0LmF0dHIoImN1cnNvciIsUHBbeT1IMXRbeV1dKSkscnQ8UiYmKFMqPS0xLGVlPUwsTD1aLFo9ZWUsZWU9UixSPXJ0LHJ0PWVlLHkgaW4gVjF0JiZkdC5hdHRyKCJjdXJzb3IiLFBwW3k9VjF0W3ldXSkpLEMuc2VsZWN0aW9uJiYoaz1DLnNlbGVjdGlvbiksTXQmJihCPWtbMF1bMF0sVT1rWzFdWzBdKSxsdCYmKFI9a1swXVsxXSxydD1rWzFdWzFdKSwoa1swXVswXSE9PUJ8fGtbMF1bMV0hPT1SfHxrWzFdWzBdIT09VXx8a1sxXVsxXSE9PXJ0KSYmKEMuc2VsZWN0aW9uPVtbQixSXSxbVSxydF1dLGMuY2FsbChfKSxYLmJydXNoKCkpfWZ1bmN0aW9uIHd0KCl7aWYoR1koKSxxdC50b3VjaGVzKXtpZihxdC50b3VjaGVzLmxlbmd0aClyZXR1cm47cyYmY2xlYXJUaW1lb3V0KHMpLHM9c2V0VGltZW91dChmdW5jdGlvbigpe3M9bnVsbH0sNTAwKX1lbHNlIEZtKHF0LnZpZXcsU3QpLHEub24oImtleWRvd24uYnJ1c2gga2V5dXAuYnJ1c2ggbW91c2Vtb3ZlLmJydXNoIG1vdXNldXAuYnJ1c2giLG51bGwpO2V0LmF0dHIoInBvaW50ZXItZXZlbnRzIiwiYWxsIiksZHQuYXR0cigiY3Vyc29yIixQcC5vdmVybGF5KSxDLnNlbGVjdGlvbiYmKGs9Qy5zZWxlY3Rpb24pLGJTZShrKSYmKEMuc2VsZWN0aW9uPW51bGwsYy5jYWxsKF8pKSxYLmVuZCgpfWZ1bmN0aW9uIGt0KCl7c3dpdGNoKHF0LmtleUNvZGUpe2Nhc2UgMTY6e2J0PWImJlM7YnJlYWt9Y2FzZSAxODp7eD09PU1iJiYoYiYmKHo9VS1vdCpiLEQ9QitvdCpiKSxTJiYoWj1ydC1zdCpTLEw9UitzdCpTKSx4PUViLGh0KCkpO2JyZWFrfWNhc2UgMzI6eyh4PT09TWJ8fHg9PT1FYikmJihiPDA/ej1VLW90OmI+MCYmKEQ9Qi1vdCksUzwwP1o9cnQtc3Q6Uz4wJiYoTD1SLXN0KSx4PVdZLGR0LmF0dHIoImN1cnNvciIsUHAuc2VsZWN0aW9uKSxodCgpKTticmVha31kZWZhdWx0OnJldHVybn1GTCgpfWZ1bmN0aW9uIGllKCl7c3dpdGNoKHF0LmtleUNvZGUpe2Nhc2UgMTY6e2J0JiYoTXQ9bHQ9YnQ9ITEsaHQoKSk7YnJlYWt9Y2FzZSAxODp7eD09PUViJiYoYjwwP3o9VTpiPjAmJihEPUIpLFM8MD9aPXJ0OlM+MCYmKEw9UikseD1NYixodCgpKTticmVha31jYXNlIDMyOnt4PT09V1kmJihxdC5hbHRLZXk/KGImJih6PVUtb3QqYixEPUIrb3QqYiksUyYmKFo9cnQtc3QqUyxMPVIrc3QqUykseD1FYik6KGI8MD96PVU6Yj4wJiYoRD1CKSxTPDA/Wj1ydDpTPjAmJihMPVIpLHg9TWIpLGR0LmF0dHIoImN1cnNvciIsUHBbeV0pLGh0KCkpO2JyZWFrfWRlZmF1bHQ6cmV0dXJufUZMKCl9fWZ1bmN0aW9uIHAoKXt1KHRoaXMsYXJndW1lbnRzKS5tb3ZlZCgpfWZ1bmN0aW9uIGQoKXt1KHRoaXMsYXJndW1lbnRzKS5lbmRlZCgpfWZ1bmN0aW9uIGcoKXt2YXIgXz10aGlzLl9fYnJ1c2h8fHtzZWxlY3Rpb246bnVsbH07cmV0dXJuIF8uZXh0ZW50PWpZKHQuYXBwbHkodGhpcyxhcmd1bWVudHMpKSxfLmRpbT1lLF99cmV0dXJuIGwuZXh0ZW50PWZ1bmN0aW9uKF8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBfPT0iZnVuY3Rpb24iP186ekwoalkoXykpLGwpOnR9LGwuZmlsdGVyPWZ1bmN0aW9uKF8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPXR5cGVvZiBfPT0iZnVuY3Rpb24iP186ekwoISFfKSxsKTpyfSxsLnRvdWNoYWJsZT1mdW5jdGlvbihfKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj10eXBlb2YgXz09ImZ1bmN0aW9uIj9fOnpMKCEhXyksbCk6bn0sbC5oYW5kbGVTaXplPWZ1bmN0aW9uKF8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhhPStfLGwpOmF9LGwua2V5TW9kaWZpZXJzPWZ1bmN0aW9uKF8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPSEhXyxsKTppfSxsLm9uPWZ1bmN0aW9uKCl7dmFyIF89by5vbi5hcHBseShvLGFyZ3VtZW50cyk7cmV0dXJuIF89PT1vP2w6X30sbH12YXIgJFk9TWF0aC5jb3MsS1k9TWF0aC5zaW4scTF0PU1hdGguUEksSEU9cTF0LzIsWlk9cTF0KjIsSlk9TWF0aC5tYXg7ZnVuY3Rpb24gd1NlKGUpe3JldHVybiBmdW5jdGlvbih0LHIpe3JldHVybiBlKHQuc291cmNlLnZhbHVlK3QudGFyZ2V0LnZhbHVlLHIuc291cmNlLnZhbHVlK3IudGFyZ2V0LnZhbHVlKX19ZnVuY3Rpb24gRzF0KCl7dmFyIGU9MCx0PW51bGwscj1udWxsLG49bnVsbDtmdW5jdGlvbiBpKG8pe3ZhciBhPW8ubGVuZ3RoLHM9W10sbD1JcihhKSxjPVtdLHU9W10saD11Lmdyb3Vwcz1uZXcgQXJyYXkoYSksZj1uZXcgQXJyYXkoYSphKSxwLGQsZyxfLHkseDtmb3IocD0wLHk9LTE7Kyt5PGE7KXtmb3IoZD0wLHg9LTE7Kyt4PGE7KWQrPW9beV1beF07cy5wdXNoKGQpLGMucHVzaChJcihhKSkscCs9ZH1mb3IodCYmbC5zb3J0KGZ1bmN0aW9uKEIsSSl7cmV0dXJuIHQoc1tCXSxzW0ldKX0pLHImJmMuZm9yRWFjaChmdW5jdGlvbihCLEkpe0Iuc29ydChmdW5jdGlvbihMLFIpe3JldHVybiByKG9bSV1bTF0sb1tJXVtSXSl9KX0pLHA9SlkoMCxaWS1lKmEpL3AsXz1wP2U6WlkvYSxkPTAseT0tMTsrK3k8YTspe2ZvcihnPWQseD0tMTsrK3g8YTspe3ZhciBiPWxbeV0sUz1jW2JdW3hdLEM9b1tiXVtTXSxQPWQsaz1kKz1DKnA7ZltTKmErYl09e2luZGV4OmIsc3ViaW5kZXg6UyxzdGFydEFuZ2xlOlAsZW5kQW5nbGU6ayx2YWx1ZTpDfX1oW2JdPXtpbmRleDpiLHN0YXJ0QW5nbGU6ZyxlbmRBbmdsZTpkLHZhbHVlOnNbYl19LGQrPV99Zm9yKHk9LTE7Kyt5PGE7KWZvcih4PXktMTsrK3g8YTspe3ZhciBPPWZbeCphK3ldLEQ9Zlt5KmEreF07KE8udmFsdWV8fEQudmFsdWUpJiZ1LnB1c2goTy52YWx1ZTxELnZhbHVlP3tzb3VyY2U6RCx0YXJnZXQ6T306e3NvdXJjZTpPLHRhcmdldDpEfSl9cmV0dXJuIG4/dS5zb3J0KG4pOnV9cmV0dXJuIGkucGFkQW5nbGU9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9SlkoMCxvKSxpKTplfSxpLnNvcnRHcm91cHM9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9byxpKTp0fSxpLnNvcnRTdWJncm91cHM9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9byxpKTpyfSxpLnNvcnRDaG9yZHM9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG89PW51bGw/bj1udWxsOihuPXdTZShvKSkuXz1vLGkpOm4mJm4uX30saX12YXIgVzF0PUFycmF5LnByb3RvdHlwZS5zbGljZTtmdW5jdGlvbiBHTChlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIFFZPU1hdGguUEksdGo9MipRWSxMXz0xZS02LFNTZT10ai1MXztmdW5jdGlvbiBlaigpe3RoaXMuX3gwPXRoaXMuX3kwPXRoaXMuX3gxPXRoaXMuX3kxPW51bGwsdGhpcy5fPSIifWZ1bmN0aW9uIFkxdCgpe3JldHVybiBuZXcgZWp9ZWoucHJvdG90eXBlPVkxdC5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOmVqLG1vdmVUbzpmdW5jdGlvbihlLHQpe3RoaXMuXys9Ik0iKyh0aGlzLl94MD10aGlzLl94MT0rZSkrIiwiKyh0aGlzLl95MD10aGlzLl95MT0rdCl9LGNsb3NlUGF0aDpmdW5jdGlvbigpe3RoaXMuX3gxIT09bnVsbCYmKHRoaXMuX3gxPXRoaXMuX3gwLHRoaXMuX3kxPXRoaXMuX3kwLHRoaXMuXys9IloiKX0sbGluZVRvOmZ1bmN0aW9uKGUsdCl7dGhpcy5fKz0iTCIrKHRoaXMuX3gxPStlKSsiLCIrKHRoaXMuX3kxPSt0KX0scXVhZHJhdGljQ3VydmVUbzpmdW5jdGlvbihlLHQscixuKXt0aGlzLl8rPSJRIisgK2UrIiwiKyArdCsiLCIrKHRoaXMuX3gxPStyKSsiLCIrKHRoaXMuX3kxPStuKX0sYmV6aWVyQ3VydmVUbzpmdW5jdGlvbihlLHQscixuLGksbyl7dGhpcy5fKz0iQyIrICtlKyIsIisgK3QrIiwiKyArcisiLCIrICtuKyIsIisodGhpcy5feDE9K2kpKyIsIisodGhpcy5feTE9K28pfSxhcmNUbzpmdW5jdGlvbihlLHQscixuLGkpe2U9K2UsdD0rdCxyPStyLG49K24saT0raTt2YXIgbz10aGlzLl94MSxhPXRoaXMuX3kxLHM9ci1lLGw9bi10LGM9by1lLHU9YS10LGg9YypjK3UqdTtpZihpPDApdGhyb3cgbmV3IEVycm9yKCJuZWdhdGl2ZSByYWRpdXM6ICIraSk7aWYodGhpcy5feDE9PT1udWxsKXRoaXMuXys9Ik0iKyh0aGlzLl94MT1lKSsiLCIrKHRoaXMuX3kxPXQpO2Vsc2UgaWYoaD5MXylpZighKE1hdGguYWJzKHUqcy1sKmMpPkxfKXx8IWkpdGhpcy5fKz0iTCIrKHRoaXMuX3gxPWUpKyIsIisodGhpcy5feTE9dCk7ZWxzZXt2YXIgZj1yLW8scD1uLWEsZD1zKnMrbCpsLGc9ZipmK3AqcCxfPU1hdGguc3FydChkKSx5PU1hdGguc3FydChoKSx4PWkqTWF0aC50YW4oKFFZLU1hdGguYWNvcygoZCtoLWcpLygyKl8qeSkpKS8yKSxiPXgveSxTPXgvXztNYXRoLmFicyhiLTEpPkxfJiYodGhpcy5fKz0iTCIrKGUrYipjKSsiLCIrKHQrYip1KSksdGhpcy5fKz0iQSIraSsiLCIraSsiLDAsMCwiKyArKHUqZj5jKnApKyIsIisodGhpcy5feDE9ZStTKnMpKyIsIisodGhpcy5feTE9dCtTKmwpfX0sYXJjOmZ1bmN0aW9uKGUsdCxyLG4saSxvKXtlPStlLHQ9K3Qscj0rcixvPSEhbzt2YXIgYT1yKk1hdGguY29zKG4pLHM9cipNYXRoLnNpbihuKSxsPWUrYSxjPXQrcyx1PTFebyxoPW8/bi1pOmktbjtpZihyPDApdGhyb3cgbmV3IEVycm9yKCJuZWdhdGl2ZSByYWRpdXM6ICIrcik7dGhpcy5feDE9PT1udWxsP3RoaXMuXys9Ik0iK2wrIiwiK2M6KE1hdGguYWJzKHRoaXMuX3gxLWwpPkxffHxNYXRoLmFicyh0aGlzLl95MS1jKT5MXykmJih0aGlzLl8rPSJMIitsKyIsIitjKSxyJiYoaDwwJiYoaD1oJXRqK3RqKSxoPlNTZT90aGlzLl8rPSJBIityKyIsIityKyIsMCwxLCIrdSsiLCIrKGUtYSkrIiwiKyh0LXMpKyJBIityKyIsIityKyIsMCwxLCIrdSsiLCIrKHRoaXMuX3gxPWwpKyIsIisodGhpcy5feTE9Yyk6aD5MXyYmKHRoaXMuXys9IkEiK3IrIiwiK3IrIiwwLCIrICsoaD49UVkpKyIsIit1KyIsIisodGhpcy5feDE9ZStyKk1hdGguY29zKGkpKSsiLCIrKHRoaXMuX3kxPXQrcipNYXRoLnNpbihpKSkpKX0scmVjdDpmdW5jdGlvbihlLHQscixuKXt0aGlzLl8rPSJNIisodGhpcy5feDA9dGhpcy5feDE9K2UpKyIsIisodGhpcy5feTA9dGhpcy5feTE9K3QpKyJoIisgK3IrInYiKyArbisiaCIrLXIrIloifSx0b1N0cmluZzpmdW5jdGlvbigpe3JldHVybiB0aGlzLl99fTt2YXIgYnM9WTF0O2Z1bmN0aW9uIE1TZShlKXtyZXR1cm4gZS5zb3VyY2V9ZnVuY3Rpb24gRVNlKGUpe3JldHVybiBlLnRhcmdldH1mdW5jdGlvbiBUU2UoZSl7cmV0dXJuIGUucmFkaXVzfWZ1bmN0aW9uIENTZShlKXtyZXR1cm4gZS5zdGFydEFuZ2xlfWZ1bmN0aW9uIEFTZShlKXtyZXR1cm4gZS5lbmRBbmdsZX1mdW5jdGlvbiBqMXQoKXt2YXIgZT1NU2UsdD1FU2Uscj1UU2Usbj1DU2UsaT1BU2Usbz1udWxsO2Z1bmN0aW9uIGEoKXt2YXIgcyxsPVcxdC5jYWxsKGFyZ3VtZW50cyksYz1lLmFwcGx5KHRoaXMsbCksdT10LmFwcGx5KHRoaXMsbCksaD0rci5hcHBseSh0aGlzLChsWzBdPWMsbCkpLGY9bi5hcHBseSh0aGlzLGwpLUhFLHA9aS5hcHBseSh0aGlzLGwpLUhFLGQ9aCokWShmKSxnPWgqS1koZiksXz0rci5hcHBseSh0aGlzLChsWzBdPXUsbCkpLHk9bi5hcHBseSh0aGlzLGwpLUhFLHg9aS5hcHBseSh0aGlzLGwpLUhFO2lmKG98fChvPXM9YnMoKSksby5tb3ZlVG8oZCxnKSxvLmFyYygwLDAsaCxmLHApLChmIT09eXx8cCE9PXgpJiYoby5xdWFkcmF0aWNDdXJ2ZVRvKDAsMCxfKiRZKHkpLF8qS1koeSkpLG8uYXJjKDAsMCxfLHkseCkpLG8ucXVhZHJhdGljQ3VydmVUbygwLDAsZCxnKSxvLmNsb3NlUGF0aCgpLHMpcmV0dXJuIG89bnVsbCxzKyIifHxudWxsfXJldHVybiBhLnJhZGl1cz1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOkdMKCtzKSxhKTpyfSxhLnN0YXJ0QW5nbGU9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpHTCgrcyksYSk6bn0sYS5lbmRBbmdsZT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOkdMKCtzKSxhKTppfSxhLnNvdXJjZT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT1zLGEpOmV9LGEudGFyZ2V0PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXMsYSk6dH0sYS5jb250ZXh0PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPXM9PW51bGw/bnVsbDpzLGEpOm99LGF9VGIoKTt2YXIgTFNlPUFycmF5LnByb3RvdHlwZSwkTD1MU2Uuc2xpY2U7ZnVuY3Rpb24gc3Z0KGUsdCl7cmV0dXJuIGUtdH1mdW5jdGlvbiBsdnQoZSl7Zm9yKHZhciB0PTAscj1lLmxlbmd0aCxuPWVbci0xXVsxXSplWzBdWzBdLWVbci0xXVswXSplWzBdWzFdOysrdDxyOyluKz1lW3QtMV1bMV0qZVt0XVswXS1lW3QtMV1bMF0qZVt0XVsxXTtyZXR1cm4gbn1mdW5jdGlvbiBPaChlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19ZnVuY3Rpb24gY3Z0KGUsdCl7Zm9yKHZhciByPS0xLG49dC5sZW5ndGgsaTsrK3I8bjspaWYoaT1rU2UoZSx0W3JdKSlyZXR1cm4gaTtyZXR1cm4gMH1mdW5jdGlvbiBrU2UoZSx0KXtmb3IodmFyIHI9dFswXSxuPXRbMV0saT0tMSxvPTAsYT1lLmxlbmd0aCxzPWEtMTtvPGE7cz1vKyspe3ZhciBsPWVbb10sYz1sWzBdLHU9bFsxXSxoPWVbc10sZj1oWzBdLHA9aFsxXTtpZihSU2UobCxoLHQpKXJldHVybiAwO3U+biE9cD5uJiZyPChmLWMpKihuLXUpLyhwLXUpK2MmJihpPS1pKX1yZXR1cm4gaX1mdW5jdGlvbiBSU2UoZSx0LHIpe3ZhciBuO3JldHVybiBOU2UoZSx0LHIpJiZEU2UoZVtuPSsoZVswXT09PXRbMF0pXSxyW25dLHRbbl0pfWZ1bmN0aW9uIE5TZShlLHQscil7cmV0dXJuKHRbMF0tZVswXSkqKHJbMV0tZVsxXSk9PT0oclswXS1lWzBdKSoodFsxXS1lWzFdKX1mdW5jdGlvbiBEU2UoZSx0LHIpe3JldHVybiBlPD10JiZ0PD1yfHxyPD10JiZ0PD1lfWZ1bmN0aW9uIHV2dCgpe312YXIgSXA9W1tdLFtbWzEsMS41XSxbLjUsMV1dXSxbW1sxLjUsMV0sWzEsMS41XV1dLFtbWzEuNSwxXSxbLjUsMV1dXSxbW1sxLC41XSxbMS41LDFdXV0sW1tbMSwxLjVdLFsuNSwxXV0sW1sxLC41XSxbMS41LDFdXV0sW1tbMSwuNV0sWzEsMS41XV1dLFtbWzEsLjVdLFsuNSwxXV1dLFtbWy41LDFdLFsxLC41XV1dLFtbWzEsMS41XSxbMSwuNV1dXSxbW1suNSwxXSxbMSwuNV1dLFtbMS41LDFdLFsxLDEuNV1dXSxbW1sxLjUsMV0sWzEsLjVdXV0sW1tbLjUsMV0sWzEuNSwxXV1dLFtbWzEsMS41XSxbMS41LDFdXV0sW1tbLjUsMV0sWzEsMS41XV1dLFtdXTtmdW5jdGlvbiBLTCgpe3ZhciBlPTEsdD0xLHI9c2Isbj1sO2Z1bmN0aW9uIGkoYyl7dmFyIHU9cihjKTtpZihBcnJheS5pc0FycmF5KHUpKXU9dS5zbGljZSgpLnNvcnQoc3Z0KTtlbHNle3ZhciBoPWFhKGMpLGY9aFswXSxwPWhbMV07dT10bChmLHAsdSksdT1JcihNYXRoLmZsb29yKGYvdSkqdSxNYXRoLmZsb29yKHAvdSkqdSx1KX1yZXR1cm4gdS5tYXAoZnVuY3Rpb24oZCl7cmV0dXJuIG8oYyxkKX0pfWZ1bmN0aW9uIG8oYyx1KXt2YXIgaD1bXSxmPVtdO3JldHVybiBhKGMsdSxmdW5jdGlvbihwKXtuKHAsYyx1KSxsdnQocCk+MD9oLnB1c2goW3BdKTpmLnB1c2gocCl9KSxmLmZvckVhY2goZnVuY3Rpb24ocCl7Zm9yKHZhciBkPTAsZz1oLmxlbmd0aCxfO2Q8ZzsrK2QpaWYoY3Z0KChfPWhbZF0pWzBdLHApIT09LTEpe18ucHVzaChwKTtyZXR1cm59fSkse3R5cGU6Ik11bHRpUG9seWdvbiIsdmFsdWU6dSxjb29yZGluYXRlczpofX1mdW5jdGlvbiBhKGMsdSxoKXt2YXIgZj1uZXcgQXJyYXkscD1uZXcgQXJyYXksZCxnLF8seSx4LGI7Zm9yKGQ9Zz0tMSx5PWNbMF0+PXUsSXBbeTw8MV0uZm9yRWFjaChTKTsrK2Q8ZS0xOylfPXkseT1jW2QrMV0+PXUsSXBbX3x5PDwxXS5mb3JFYWNoKFMpO2ZvcihJcFt5PDwwXS5mb3JFYWNoKFMpOysrZzx0LTE7KXtmb3IoZD0tMSx5PWNbZyplK2VdPj11LHg9Y1tnKmVdPj11LElwW3k8PDF8eDw8Ml0uZm9yRWFjaChTKTsrK2Q8ZS0xOylfPXkseT1jW2cqZStlK2QrMV0+PXUsYj14LHg9Y1tnKmUrZCsxXT49dSxJcFtffHk8PDF8eDw8MnxiPDwzXS5mb3JFYWNoKFMpO0lwW3l8eDw8M10uZm9yRWFjaChTKX1mb3IoZD0tMSx4PWNbZyplXT49dSxJcFt4PDwyXS5mb3JFYWNoKFMpOysrZDxlLTE7KWI9eCx4PWNbZyplK2QrMV0+PXUsSXBbeDw8MnxiPDwzXS5mb3JFYWNoKFMpO0lwW3g8PDNdLmZvckVhY2goUyk7ZnVuY3Rpb24gUyhDKXt2YXIgUD1bQ1swXVswXStkLENbMF1bMV0rZ10saz1bQ1sxXVswXStkLENbMV1bMV0rZ10sTz1zKFApLEQ9cyhrKSxCLEk7KEI9cFtPXSk/KEk9ZltEXSk/KGRlbGV0ZSBwW0IuZW5kXSxkZWxldGUgZltJLnN0YXJ0XSxCPT09ST8oQi5yaW5nLnB1c2goayksaChCLnJpbmcpKTpmW0Iuc3RhcnRdPXBbSS5lbmRdPXtzdGFydDpCLnN0YXJ0LGVuZDpJLmVuZCxyaW5nOkIucmluZy5jb25jYXQoSS5yaW5nKX0pOihkZWxldGUgcFtCLmVuZF0sQi5yaW5nLnB1c2goaykscFtCLmVuZD1EXT1CKTooQj1mW0RdKT8oST1wW09dKT8oZGVsZXRlIGZbQi5zdGFydF0sZGVsZXRlIHBbSS5lbmRdLEI9PT1JPyhCLnJpbmcucHVzaChrKSxoKEIucmluZykpOmZbSS5zdGFydF09cFtCLmVuZF09e3N0YXJ0Okkuc3RhcnQsZW5kOkIuZW5kLHJpbmc6SS5yaW5nLmNvbmNhdChCLnJpbmcpfSk6KGRlbGV0ZSBmW0Iuc3RhcnRdLEIucmluZy51bnNoaWZ0KFApLGZbQi5zdGFydD1PXT1CKTpmW09dPXBbRF09e3N0YXJ0Ok8sZW5kOkQscmluZzpbUCxrXX19fWZ1bmN0aW9uIHMoYyl7cmV0dXJuIGNbMF0qMitjWzFdKihlKzEpKjR9ZnVuY3Rpb24gbChjLHUsaCl7Yy5mb3JFYWNoKGZ1bmN0aW9uKGYpe3ZhciBwPWZbMF0sZD1mWzFdLGc9cHwwLF89ZHwwLHkseD11W18qZStnXTtwPjAmJnA8ZSYmZz09PXAmJih5PXVbXyplK2ctMV0sZlswXT1wKyhoLXkpLyh4LXkpLS41KSxkPjAmJmQ8dCYmXz09PWQmJih5PXVbKF8tMSkqZStnXSxmWzFdPWQrKGgteSkvKHgteSktLjUpfSl9cmV0dXJuIGkuY29udG91cj1vLGkuc2l6ZT1mdW5jdGlvbihjKXtpZighYXJndW1lbnRzLmxlbmd0aClyZXR1cm5bZSx0XTt2YXIgdT1NYXRoLmNlaWwoY1swXSksaD1NYXRoLmNlaWwoY1sxXSk7aWYoISh1PjApfHwhKGg+MCkpdGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIHNpemUiKTtyZXR1cm4gZT11LHQ9aCxpfSxpLnRocmVzaG9sZHM9ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9dHlwZW9mIGM9PSJmdW5jdGlvbiI/YzpBcnJheS5pc0FycmF5KGMpP09oKCRMLmNhbGwoYykpOk9oKGMpLGkpOnJ9LGkuc21vb3RoPWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPWM/bDp1dnQsaSk6bj09PWx9LGl9ZnVuY3Rpb24gWkwoZSx0LHIpe2Zvcih2YXIgbj1lLndpZHRoLGk9ZS5oZWlnaHQsbz0ocjw8MSkrMSxhPTA7YTxpOysrYSlmb3IodmFyIHM9MCxsPTA7czxuK3I7KytzKXM8biYmKGwrPWUuZGF0YVtzK2Eqbl0pLHM+PXImJihzPj1vJiYobC09ZS5kYXRhW3MtbythKm5dKSx0LmRhdGFbcy1yK2Eqbl09bC9NYXRoLm1pbihzKzEsbi0xK28tcyxvKSl9ZnVuY3Rpb24gSkwoZSx0LHIpe2Zvcih2YXIgbj1lLndpZHRoLGk9ZS5oZWlnaHQsbz0ocjw8MSkrMSxhPTA7YTxuOysrYSlmb3IodmFyIHM9MCxsPTA7czxpK3I7KytzKXM8aSYmKGwrPWUuZGF0YVthK3Mqbl0pLHM+PXImJihzPj1vJiYobC09ZS5kYXRhW2ErKHMtbykqbl0pLHQuZGF0YVthKyhzLXIpKm5dPWwvTWF0aC5taW4ocysxLGktMStvLXMsbykpfWZ1bmN0aW9uIE9TZShlKXtyZXR1cm4gZVswXX1mdW5jdGlvbiB6U2UoZSl7cmV0dXJuIGVbMV19ZnVuY3Rpb24gRlNlKCl7cmV0dXJuIDF9ZnVuY3Rpb24gaHZ0KCl7dmFyIGU9T1NlLHQ9elNlLHI9RlNlLG49OTYwLGk9NTAwLG89MjAsYT0yLHM9byozLGw9bitzKjI+PmEsYz1pK3MqMj4+YSx1PU9oKDIwKTtmdW5jdGlvbiBoKHkpe3ZhciB4PW5ldyBGbG9hdDMyQXJyYXkobCpjKSxiPW5ldyBGbG9hdDMyQXJyYXkobCpjKTt5LmZvckVhY2goZnVuY3Rpb24oUCxrLE8pe3ZhciBEPStlKFAsayxPKStzPj5hLEI9K3QoUCxrLE8pK3M+PmEsST0rcihQLGssTyk7RD49MCYmRDxsJiZCPj0wJiZCPGMmJih4W0QrQipsXSs9SSl9KSxaTCh7d2lkdGg6bCxoZWlnaHQ6YyxkYXRhOnh9LHt3aWR0aDpsLGhlaWdodDpjLGRhdGE6Yn0sbz4+YSksSkwoe3dpZHRoOmwsaGVpZ2h0OmMsZGF0YTpifSx7d2lkdGg6bCxoZWlnaHQ6YyxkYXRhOnh9LG8+PmEpLFpMKHt3aWR0aDpsLGhlaWdodDpjLGRhdGE6eH0se3dpZHRoOmwsaGVpZ2h0OmMsZGF0YTpifSxvPj5hKSxKTCh7d2lkdGg6bCxoZWlnaHQ6YyxkYXRhOmJ9LHt3aWR0aDpsLGhlaWdodDpjLGRhdGE6eH0sbz4+YSksWkwoe3dpZHRoOmwsaGVpZ2h0OmMsZGF0YTp4fSx7d2lkdGg6bCxoZWlnaHQ6YyxkYXRhOmJ9LG8+PmEpLEpMKHt3aWR0aDpsLGhlaWdodDpjLGRhdGE6Yn0se3dpZHRoOmwsaGVpZ2h0OmMsZGF0YTp4fSxvPj5hKTt2YXIgUz11KHgpO2lmKCFBcnJheS5pc0FycmF5KFMpKXt2YXIgQz1sdSh4KTtTPXRsKDAsQyxTKSxTPUlyKDAsTWF0aC5mbG9vcihDL1MpKlMsUyksUy5zaGlmdCgpfXJldHVybiBLTCgpLnRocmVzaG9sZHMoUykuc2l6ZShbbCxjXSkoeCkubWFwKGYpfWZ1bmN0aW9uIGYoeSl7cmV0dXJuIHkudmFsdWUqPU1hdGgucG93KDIsLTIqYSkseS5jb29yZGluYXRlcy5mb3JFYWNoKHApLHl9ZnVuY3Rpb24gcCh5KXt5LmZvckVhY2goZCl9ZnVuY3Rpb24gZCh5KXt5LmZvckVhY2goZyl9ZnVuY3Rpb24gZyh5KXt5WzBdPXlbMF0qTWF0aC5wb3coMixhKS1zLHlbMV09eVsxXSpNYXRoLnBvdygyLGEpLXN9ZnVuY3Rpb24gXygpe3JldHVybiBzPW8qMyxsPW4rcyoyPj5hLGM9aStzKjI+PmEsaH1yZXR1cm4gaC54PWZ1bmN0aW9uKHkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiB5PT0iZnVuY3Rpb24iP3k6T2goK3kpLGgpOmV9LGgueT1mdW5jdGlvbih5KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD10eXBlb2YgeT09ImZ1bmN0aW9uIj95Ok9oKCt5KSxoKTp0fSxoLndlaWdodD1mdW5jdGlvbih5KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2YgeT09ImZ1bmN0aW9uIj95Ok9oKCt5KSxoKTpyfSxoLnNpemU9ZnVuY3Rpb24oeSl7aWYoIWFyZ3VtZW50cy5sZW5ndGgpcmV0dXJuW24saV07dmFyIHg9TWF0aC5jZWlsKHlbMF0pLGI9TWF0aC5jZWlsKHlbMV0pO2lmKCEoeD49MCkmJiEoeD49MCkpdGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIHNpemUiKTtyZXR1cm4gbj14LGk9YixfKCl9LGguY2VsbFNpemU9ZnVuY3Rpb24oeSl7aWYoIWFyZ3VtZW50cy5sZW5ndGgpcmV0dXJuIDE8PGE7aWYoISgoeT0reSk+PTEpKXRocm93IG5ldyBFcnJvcigiaW52YWxpZCBjZWxsIHNpemUiKTtyZXR1cm4gYT1NYXRoLmZsb29yKE1hdGgubG9nKHkpL01hdGguTE4yKSxfKCl9LGgudGhyZXNob2xkcz1mdW5jdGlvbih5KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odT10eXBlb2YgeT09ImZ1bmN0aW9uIj95OkFycmF5LmlzQXJyYXkoeSk/T2goJEwuY2FsbCh5KSk6T2goeSksaCk6dX0saC5iYW5kd2lkdGg9ZnVuY3Rpb24oeSl7aWYoIWFyZ3VtZW50cy5sZW5ndGgpcmV0dXJuIE1hdGguc3FydChvKihvKzEpKTtpZighKCh5PSt5KT49MCkpdGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIGJhbmR3aWR0aCIpO3JldHVybiBvPU1hdGgucm91bmQoKE1hdGguc3FydCg0KnkqeSsxKS0xKS8yKSxfKCl9LGh9a20oKTtVRSgpO0lfKCk7ZnVuY3Rpb24gcVNlKGUpe2lmKCFlLm9rKXRocm93IG5ldyBFcnJvcihlLnN0YXR1cysiICIrZS5zdGF0dXNUZXh0KTtyZXR1cm4gZS5ibG9iKCl9ZnVuY3Rpb24gSXZ0KGUsdCl7cmV0dXJuIGZldGNoKGUsdCkudGhlbihxU2UpfWZ1bmN0aW9uIEdTZShlKXtpZighZS5vayl0aHJvdyBuZXcgRXJyb3IoZS5zdGF0dXMrIiAiK2Uuc3RhdHVzVGV4dCk7cmV0dXJuIGUuYXJyYXlCdWZmZXIoKX1mdW5jdGlvbiBMdnQoZSx0KXtyZXR1cm4gZmV0Y2goZSx0KS50aGVuKEdTZSl9VUUoKTtmdW5jdGlvbiBXU2UoZSl7aWYoIWUub2spdGhyb3cgbmV3IEVycm9yKGUuc3RhdHVzKyIgIitlLnN0YXR1c1RleHQpO3JldHVybiBlLnRleHQoKX1mdW5jdGlvbiBEXyhlLHQpe3JldHVybiBmZXRjaChlLHQpLnRoZW4oV1NlKX1mdW5jdGlvbiBrdnQoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscixuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD09PTImJnR5cGVvZiByPT0iZnVuY3Rpb24iJiYobj1yLHI9dm9pZCAwKSxEXyh0LHIpLnRoZW4oZnVuY3Rpb24oaSl7cmV0dXJuIGUoaSxuKX0pfX1mdW5jdGlvbiBhaihlLHQscixuKXthcmd1bWVudHMubGVuZ3RoPT09MyYmdHlwZW9mIHI9PSJmdW5jdGlvbiImJihuPXIscj12b2lkIDApO3ZhciBpPVdtKGUpO3JldHVybiBEXyh0LHIpLnRoZW4oZnVuY3Rpb24obyl7cmV0dXJuIGkucGFyc2UobyxuKX0pfXZhciBSdnQ9a3Z0KENiKSxOdnQ9a3Z0KEFiKTtmdW5jdGlvbiBEdnQoZSx0KXtyZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24ocixuKXt2YXIgaT1uZXcgSW1hZ2U7Zm9yKHZhciBvIGluIHQpaVtvXT10W29dO2kub25lcnJvcj1uLGkub25sb2FkPWZ1bmN0aW9uKCl7cihpKX0saS5zcmM9ZX0pfWZ1bmN0aW9uIFlTZShlKXtpZighZS5vayl0aHJvdyBuZXcgRXJyb3IoZS5zdGF0dXMrIiAiK2Uuc3RhdHVzVGV4dCk7aWYoIShlLnN0YXR1cz09PTIwNHx8ZS5zdGF0dXM9PT0yMDUpKXJldHVybiBlLmpzb24oKX1mdW5jdGlvbiBPdnQoZSx0KXtyZXR1cm4gZmV0Y2goZSx0KS50aGVuKFlTZSl9ZnVuY3Rpb24gc2ooZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7cmV0dXJuIERfKHQscikudGhlbihmdW5jdGlvbihuKXtyZXR1cm4gbmV3IERPTVBhcnNlcigpLnBhcnNlRnJvbVN0cmluZyhuLGUpfSl9fXZhciB6dnQ9c2ooImFwcGxpY2F0aW9uL3htbCIpLEZ2dD1zaigidGV4dC9odG1sIiksQnZ0PXNqKCJpbWFnZS9zdmcreG1sIik7ZnVuY3Rpb24gSHZ0KGUsdCl7dmFyIHI7ZT09bnVsbCYmKGU9MCksdD09bnVsbCYmKHQ9MCk7ZnVuY3Rpb24gbigpe3ZhciBpLG89ci5sZW5ndGgsYSxzPTAsbD0wO2ZvcihpPTA7aTxvOysraSlhPXJbaV0scys9YS54LGwrPWEueTtmb3Iocz1zL28tZSxsPWwvby10LGk9MDtpPG87KytpKWE9cltpXSxhLngtPXMsYS55LT1sfXJldHVybiBuLmluaXRpYWxpemU9ZnVuY3Rpb24oaSl7cj1pfSxuLng9ZnVuY3Rpb24oaSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9K2ksbik6ZX0sbi55PWZ1bmN0aW9uKGkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PStpLG4pOnR9LG59ZnVuY3Rpb24gT24oZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fWZ1bmN0aW9uIGZ1KCl7cmV0dXJuKE1hdGgucmFuZG9tKCktLjUpKjFlLTZ9ZnVuY3Rpb24gVnZ0KGUpe3ZhciB0PSt0aGlzLl94LmNhbGwobnVsbCxlKSxyPSt0aGlzLl95LmNhbGwobnVsbCxlKTtyZXR1cm4gVXZ0KHRoaXMuY292ZXIodCxyKSx0LHIsZSl9ZnVuY3Rpb24gVXZ0KGUsdCxyLG4pe2lmKGlzTmFOKHQpfHxpc05hTihyKSlyZXR1cm4gZTt2YXIgaSxvPWUuX3Jvb3QsYT17ZGF0YTpufSxzPWUuX3gwLGw9ZS5feTAsYz1lLl94MSx1PWUuX3kxLGgsZixwLGQsZyxfLHkseDtpZighbylyZXR1cm4gZS5fcm9vdD1hLGU7Zm9yKDtvLmxlbmd0aDspaWYoKGc9dD49KGg9KHMrYykvMikpP3M9aDpjPWgsKF89cj49KGY9KGwrdSkvMikpP2w9Zjp1PWYsaT1vLCEobz1vW3k9Xzw8MXxnXSkpcmV0dXJuIGlbeV09YSxlO2lmKHA9K2UuX3guY2FsbChudWxsLG8uZGF0YSksZD0rZS5feS5jYWxsKG51bGwsby5kYXRhKSx0PT09cCYmcj09PWQpcmV0dXJuIGEubmV4dD1vLGk/aVt5XT1hOmUuX3Jvb3Q9YSxlO2RvIGk9aT9pW3ldPW5ldyBBcnJheSg0KTplLl9yb290PW5ldyBBcnJheSg0KSwoZz10Pj0oaD0ocytjKS8yKSk/cz1oOmM9aCwoXz1yPj0oZj0obCt1KS8yKSk/bD1mOnU9Zjt3aGlsZSgoeT1fPDwxfGcpPT09KHg9KGQ+PWYpPDwxfHA+PWgpKTtyZXR1cm4gaVt4XT1vLGlbeV09YSxlfWZ1bmN0aW9uIHF2dChlKXt2YXIgdCxyLG49ZS5sZW5ndGgsaSxvLGE9bmV3IEFycmF5KG4pLHM9bmV3IEFycmF5KG4pLGw9MS8wLGM9MS8wLHU9LTEvMCxoPS0xLzA7Zm9yKHI9MDtyPG47KytyKWlzTmFOKGk9K3RoaXMuX3guY2FsbChudWxsLHQ9ZVtyXSkpfHxpc05hTihvPSt0aGlzLl95LmNhbGwobnVsbCx0KSl8fChhW3JdPWksc1tyXT1vLGk8bCYmKGw9aSksaT51JiYodT1pKSxvPGMmJihjPW8pLG8+aCYmKGg9bykpO2lmKGw+dXx8Yz5oKXJldHVybiB0aGlzO2Zvcih0aGlzLmNvdmVyKGwsYykuY292ZXIodSxoKSxyPTA7cjxuOysrcilVdnQodGhpcyxhW3JdLHNbcl0sZVtyXSk7cmV0dXJuIHRoaXN9ZnVuY3Rpb24gR3Z0KGUsdCl7aWYoaXNOYU4oZT0rZSl8fGlzTmFOKHQ9K3QpKXJldHVybiB0aGlzO3ZhciByPXRoaXMuX3gwLG49dGhpcy5feTAsaT10aGlzLl94MSxvPXRoaXMuX3kxO2lmKGlzTmFOKHIpKWk9KHI9TWF0aC5mbG9vcihlKSkrMSxvPShuPU1hdGguZmxvb3IodCkpKzE7ZWxzZXtmb3IodmFyIGE9aS1yLHM9dGhpcy5fcm9vdCxsLGM7cj5lfHxlPj1pfHxuPnR8fHQ+PW87KXN3aXRjaChjPSh0PG4pPDwxfGU8cixsPW5ldyBBcnJheSg0KSxsW2NdPXMscz1sLGEqPTIsYyl7Y2FzZSAwOmk9cithLG89bithO2JyZWFrO2Nhc2UgMTpyPWktYSxvPW4rYTticmVhaztjYXNlIDI6aT1yK2Esbj1vLWE7YnJlYWs7Y2FzZSAzOnI9aS1hLG49by1hO2JyZWFrfXRoaXMuX3Jvb3QmJnRoaXMuX3Jvb3QubGVuZ3RoJiYodGhpcy5fcm9vdD1zKX1yZXR1cm4gdGhpcy5feDA9cix0aGlzLl95MD1uLHRoaXMuX3gxPWksdGhpcy5feTE9byx0aGlzfWZ1bmN0aW9uIFd2dCgpe3ZhciBlPVtdO3JldHVybiB0aGlzLnZpc2l0KGZ1bmN0aW9uKHQpe2lmKCF0Lmxlbmd0aClkbyBlLnB1c2godC5kYXRhKTt3aGlsZSh0PXQubmV4dCl9KSxlfWZ1bmN0aW9uIFl2dChlKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmNvdmVyKCtlWzBdWzBdLCtlWzBdWzFdKS5jb3ZlcigrZVsxXVswXSwrZVsxXVsxXSk6aXNOYU4odGhpcy5feDApP3ZvaWQgMDpbW3RoaXMuX3gwLHRoaXMuX3kwXSxbdGhpcy5feDEsdGhpcy5feTFdXX1mdW5jdGlvbiB5byhlLHQscixuLGkpe3RoaXMubm9kZT1lLHRoaXMueDA9dCx0aGlzLnkwPXIsdGhpcy54MT1uLHRoaXMueTE9aX1mdW5jdGlvbiBqdnQoZSx0LHIpe3ZhciBuLGk9dGhpcy5feDAsbz10aGlzLl95MCxhLHMsbCxjLHU9dGhpcy5feDEsaD10aGlzLl95MSxmPVtdLHA9dGhpcy5fcm9vdCxkLGc7Zm9yKHAmJmYucHVzaChuZXcgeW8ocCxpLG8sdSxoKSkscj09bnVsbD9yPTEvMDooaT1lLXIsbz10LXIsdT1lK3IsaD10K3Iscio9cik7ZD1mLnBvcCgpOylpZighKCEocD1kLm5vZGUpfHwoYT1kLngwKT51fHwocz1kLnkwKT5ofHwobD1kLngxKTxpfHwoYz1kLnkxKTxvKSlpZihwLmxlbmd0aCl7dmFyIF89KGErbCkvMix5PShzK2MpLzI7Zi5wdXNoKG5ldyB5byhwWzNdLF8seSxsLGMpLG5ldyB5byhwWzJdLGEseSxfLGMpLG5ldyB5byhwWzFdLF8scyxsLHkpLG5ldyB5byhwWzBdLGEscyxfLHkpKSwoZz0odD49eSk8PDF8ZT49XykmJihkPWZbZi5sZW5ndGgtMV0sZltmLmxlbmd0aC0xXT1mW2YubGVuZ3RoLTEtZ10sZltmLmxlbmd0aC0xLWddPWQpfWVsc2V7dmFyIHg9ZS0rdGhpcy5feC5jYWxsKG51bGwscC5kYXRhKSxiPXQtK3RoaXMuX3kuY2FsbChudWxsLHAuZGF0YSksUz14KngrYipiO2lmKFM8cil7dmFyIEM9TWF0aC5zcXJ0KHI9Uyk7aT1lLUMsbz10LUMsdT1lK0MsaD10K0Msbj1wLmRhdGF9fXJldHVybiBufWZ1bmN0aW9uIFh2dChlKXtpZihpc05hTih1PSt0aGlzLl94LmNhbGwobnVsbCxlKSl8fGlzTmFOKGg9K3RoaXMuX3kuY2FsbChudWxsLGUpKSlyZXR1cm4gdGhpczt2YXIgdCxyPXRoaXMuX3Jvb3QsbixpLG8sYT10aGlzLl94MCxzPXRoaXMuX3kwLGw9dGhpcy5feDEsYz10aGlzLl95MSx1LGgsZixwLGQsZyxfLHk7aWYoIXIpcmV0dXJuIHRoaXM7aWYoci5sZW5ndGgpZm9yKDs7KXtpZigoZD11Pj0oZj0oYStsKS8yKSk/YT1mOmw9ZiwoZz1oPj0ocD0ocytjKS8yKSk/cz1wOmM9cCx0PXIsIShyPXJbXz1nPDwxfGRdKSlyZXR1cm4gdGhpcztpZighci5sZW5ndGgpYnJlYWs7KHRbXysxJjNdfHx0W18rMiYzXXx8dFtfKzMmM10pJiYobj10LHk9Xyl9Zm9yKDtyLmRhdGEhPT1lOylpZihpPXIsIShyPXIubmV4dCkpcmV0dXJuIHRoaXM7cmV0dXJuKG89ci5uZXh0KSYmZGVsZXRlIHIubmV4dCxpPyhvP2kubmV4dD1vOmRlbGV0ZSBpLm5leHQsdGhpcyk6dD8obz90W19dPW86ZGVsZXRlIHRbX10sKHI9dFswXXx8dFsxXXx8dFsyXXx8dFszXSkmJnI9PT0odFszXXx8dFsyXXx8dFsxXXx8dFswXSkmJiFyLmxlbmd0aCYmKG4/blt5XT1yOnRoaXMuX3Jvb3Q9ciksdGhpcyk6KHRoaXMuX3Jvb3Q9byx0aGlzKX1mdW5jdGlvbiAkdnQoZSl7Zm9yKHZhciB0PTAscj1lLmxlbmd0aDt0PHI7Kyt0KXRoaXMucmVtb3ZlKGVbdF0pO3JldHVybiB0aGlzfWZ1bmN0aW9uIEt2dCgpe3JldHVybiB0aGlzLl9yb290fWZ1bmN0aW9uIFp2dCgpe3ZhciBlPTA7cmV0dXJuIHRoaXMudmlzaXQoZnVuY3Rpb24odCl7aWYoIXQubGVuZ3RoKWRvKytlO3doaWxlKHQ9dC5uZXh0KX0pLGV9ZnVuY3Rpb24gSnZ0KGUpe3ZhciB0PVtdLHIsbj10aGlzLl9yb290LGksbyxhLHMsbDtmb3IobiYmdC5wdXNoKG5ldyB5byhuLHRoaXMuX3gwLHRoaXMuX3kwLHRoaXMuX3gxLHRoaXMuX3kxKSk7cj10LnBvcCgpOylpZighZShuPXIubm9kZSxvPXIueDAsYT1yLnkwLHM9ci54MSxsPXIueTEpJiZuLmxlbmd0aCl7dmFyIGM9KG8rcykvMix1PShhK2wpLzI7KGk9blszXSkmJnQucHVzaChuZXcgeW8oaSxjLHUscyxsKSksKGk9blsyXSkmJnQucHVzaChuZXcgeW8oaSxvLHUsYyxsKSksKGk9blsxXSkmJnQucHVzaChuZXcgeW8oaSxjLGEscyx1KSksKGk9blswXSkmJnQucHVzaChuZXcgeW8oaSxvLGEsYyx1KSl9cmV0dXJuIHRoaXN9ZnVuY3Rpb24gUXZ0KGUpe3ZhciB0PVtdLHI9W10sbjtmb3IodGhpcy5fcm9vdCYmdC5wdXNoKG5ldyB5byh0aGlzLl9yb290LHRoaXMuX3gwLHRoaXMuX3kwLHRoaXMuX3gxLHRoaXMuX3kxKSk7bj10LnBvcCgpOyl7dmFyIGk9bi5ub2RlO2lmKGkubGVuZ3RoKXt2YXIgbyxhPW4ueDAscz1uLnkwLGw9bi54MSxjPW4ueTEsdT0oYStsKS8yLGg9KHMrYykvMjsobz1pWzBdKSYmdC5wdXNoKG5ldyB5byhvLGEscyx1LGgpKSwobz1pWzFdKSYmdC5wdXNoKG5ldyB5byhvLHUscyxsLGgpKSwobz1pWzJdKSYmdC5wdXNoKG5ldyB5byhvLGEsaCx1LGMpKSwobz1pWzNdKSYmdC5wdXNoKG5ldyB5byhvLHUsaCxsLGMpKX1yLnB1c2gobil9Zm9yKDtuPXIucG9wKCk7KWUobi5ub2RlLG4ueDAsbi55MCxuLngxLG4ueTEpO3JldHVybiB0aGlzfWZ1bmN0aW9uIHR4dChlKXtyZXR1cm4gZVswXX1mdW5jdGlvbiBleHQoZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHRoaXMuX3g9ZSx0aGlzKTp0aGlzLl94fWZ1bmN0aW9uIHJ4dChlKXtyZXR1cm4gZVsxXX1mdW5jdGlvbiBueHQoZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHRoaXMuX3k9ZSx0aGlzKTp0aGlzLl95fWZ1bmN0aW9uIHpoKGUsdCxyKXt2YXIgbj1uZXcgbGoodD09bnVsbD90eHQ6dCxyPT1udWxsP3J4dDpyLE5hTixOYU4sTmFOLE5hTik7cmV0dXJuIGU9PW51bGw/bjpuLmFkZEFsbChlKX1mdW5jdGlvbiBsaihlLHQscixuLGksbyl7dGhpcy5feD1lLHRoaXMuX3k9dCx0aGlzLl94MD1yLHRoaXMuX3kwPW4sdGhpcy5feDE9aSx0aGlzLl95MT1vLHRoaXMuX3Jvb3Q9dm9pZCAwfWZ1bmN0aW9uIGl4dChlKXtmb3IodmFyIHQ9e2RhdGE6ZS5kYXRhfSxyPXQ7ZT1lLm5leHQ7KXI9ci5uZXh0PXtkYXRhOmUuZGF0YX07cmV0dXJuIHR9dmFyIHphPXpoLnByb3RvdHlwZT1sai5wcm90b3R5cGU7emEuY29weT1mdW5jdGlvbigpe3ZhciBlPW5ldyBsaih0aGlzLl94LHRoaXMuX3ksdGhpcy5feDAsdGhpcy5feTAsdGhpcy5feDEsdGhpcy5feTEpLHQ9dGhpcy5fcm9vdCxyLG47aWYoIXQpcmV0dXJuIGU7aWYoIXQubGVuZ3RoKXJldHVybiBlLl9yb290PWl4dCh0KSxlO2ZvcihyPVt7c291cmNlOnQsdGFyZ2V0OmUuX3Jvb3Q9bmV3IEFycmF5KDQpfV07dD1yLnBvcCgpOylmb3IodmFyIGk9MDtpPDQ7KytpKShuPXQuc291cmNlW2ldKSYmKG4ubGVuZ3RoP3IucHVzaCh7c291cmNlOm4sdGFyZ2V0OnQudGFyZ2V0W2ldPW5ldyBBcnJheSg0KX0pOnQudGFyZ2V0W2ldPWl4dChuKSk7cmV0dXJuIGV9O3phLmFkZD1WdnQ7emEuYWRkQWxsPXF2dDt6YS5jb3Zlcj1HdnQ7emEuZGF0YT1XdnQ7emEuZXh0ZW50PVl2dDt6YS5maW5kPWp2dDt6YS5yZW1vdmU9WHZ0O3phLnJlbW92ZUFsbD0kdnQ7emEucm9vdD1LdnQ7emEuc2l6ZT1adnQ7emEudmlzaXQ9SnZ0O3phLnZpc2l0QWZ0ZXI9UXZ0O3phLng9ZXh0O3phLnk9bnh0O2Z1bmN0aW9uIGpTZShlKXtyZXR1cm4gZS54K2Uudnh9ZnVuY3Rpb24gWFNlKGUpe3JldHVybiBlLnkrZS52eX1mdW5jdGlvbiBveHQoZSl7dmFyIHQscixuPTEsaT0xO3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1PbihlPT1udWxsPzE6K2UpKTtmdW5jdGlvbiBvKCl7Zm9yKHZhciBsLGM9dC5sZW5ndGgsdSxoLGYscCxkLGcsXz0wO188aTsrK18pZm9yKHU9emgodCxqU2UsWFNlKS52aXNpdEFmdGVyKGEpLGw9MDtsPGM7KytsKWg9dFtsXSxkPXJbaC5pbmRleF0sZz1kKmQsZj1oLngraC52eCxwPWgueStoLnZ5LHUudmlzaXQoeSk7ZnVuY3Rpb24geSh4LGIsUyxDLFApe3ZhciBrPXguZGF0YSxPPXgucixEPWQrTztpZihrKXtpZihrLmluZGV4PmguaW5kZXgpe3ZhciBCPWYtay54LWsudngsST1wLWsueS1rLnZ5LEw9QipCK0kqSTtMPEQqRCYmKEI9PT0wJiYoQj1mdSgpLEwrPUIqQiksST09PTAmJihJPWZ1KCksTCs9SSpJKSxMPShELShMPU1hdGguc3FydChMKSkpL0wqbixoLnZ4Kz0oQio9TCkqKEQ9KE8qPU8pLyhnK08pKSxoLnZ5Kz0oSSo9TCkqRCxrLnZ4LT1CKihEPTEtRCksay52eS09SSpEKX1yZXR1cm59cmV0dXJuIGI+ZitEfHxDPGYtRHx8Uz5wK0R8fFA8cC1EfX1mdW5jdGlvbiBhKGwpe2lmKGwuZGF0YSlyZXR1cm4gbC5yPXJbbC5kYXRhLmluZGV4XTtmb3IodmFyIGM9bC5yPTA7Yzw0OysrYylsW2NdJiZsW2NdLnI+bC5yJiYobC5yPWxbY10ucil9ZnVuY3Rpb24gcygpe2lmKCEhdCl7dmFyIGwsYz10Lmxlbmd0aCx1O2ZvcihyPW5ldyBBcnJheShjKSxsPTA7bDxjOysrbCl1PXRbbF0sclt1LmluZGV4XT0rZSh1LGwsdCl9fXJldHVybiBvLmluaXRpYWxpemU9ZnVuY3Rpb24obCl7dD1sLHMoKX0sby5pdGVyYXRpb25zPWZ1bmN0aW9uKGwpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPStsLG8pOml9LG8uc3RyZW5ndGg9ZnVuY3Rpb24obCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49K2wsbyk6bn0sby5yYWRpdXM9ZnVuY3Rpb24obCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIGw9PSJmdW5jdGlvbiI/bDpPbigrbCkscygpLG8pOmV9LG99VGIoKTtmdW5jdGlvbiAkU2UoZSl7cmV0dXJuIGUuaW5kZXh9ZnVuY3Rpb24gYXh0KGUsdCl7dmFyIHI9ZS5nZXQodCk7aWYoIXIpdGhyb3cgbmV3IEVycm9yKCJtaXNzaW5nOiAiK3QpO3JldHVybiByfWZ1bmN0aW9uIHN4dChlKXt2YXIgdD0kU2Uscj11LG4saT1PbigzMCksbyxhLHMsbCxjPTE7ZT09bnVsbCYmKGU9W10pO2Z1bmN0aW9uIHUoZyl7cmV0dXJuIDEvTWF0aC5taW4oc1tnLnNvdXJjZS5pbmRleF0sc1tnLnRhcmdldC5pbmRleF0pfWZ1bmN0aW9uIGgoZyl7Zm9yKHZhciBfPTAseT1lLmxlbmd0aDtfPGM7KytfKWZvcih2YXIgeD0wLGIsUyxDLFAsayxPLEQ7eDx5OysreCliPWVbeF0sUz1iLnNvdXJjZSxDPWIudGFyZ2V0LFA9Qy54K0MudngtUy54LVMudnh8fGZ1KCksaz1DLnkrQy52eS1TLnktUy52eXx8ZnUoKSxPPU1hdGguc3FydChQKlArayprKSxPPShPLW9beF0pL08qZypuW3hdLFAqPU8sayo9TyxDLnZ4LT1QKihEPWxbeF0pLEMudnktPWsqRCxTLnZ4Kz1QKihEPTEtRCksUy52eSs9aypEfWZ1bmN0aW9uIGYoKXtpZighIWEpe3ZhciBnLF89YS5sZW5ndGgseT1lLmxlbmd0aCx4PUppKGEsdCksYjtmb3IoZz0wLHM9bmV3IEFycmF5KF8pO2c8eTsrK2cpYj1lW2ddLGIuaW5kZXg9Zyx0eXBlb2YgYi5zb3VyY2UhPSJvYmplY3QiJiYoYi5zb3VyY2U9YXh0KHgsYi5zb3VyY2UpKSx0eXBlb2YgYi50YXJnZXQhPSJvYmplY3QiJiYoYi50YXJnZXQ9YXh0KHgsYi50YXJnZXQpKSxzW2Iuc291cmNlLmluZGV4XT0oc1tiLnNvdXJjZS5pbmRleF18fDApKzEsc1tiLnRhcmdldC5pbmRleF09KHNbYi50YXJnZXQuaW5kZXhdfHwwKSsxO2ZvcihnPTAsbD1uZXcgQXJyYXkoeSk7Zzx5OysrZyliPWVbZ10sbFtnXT1zW2Iuc291cmNlLmluZGV4XS8oc1tiLnNvdXJjZS5pbmRleF0rc1tiLnRhcmdldC5pbmRleF0pO249bmV3IEFycmF5KHkpLHAoKSxvPW5ldyBBcnJheSh5KSxkKCl9fWZ1bmN0aW9uIHAoKXtpZighIWEpZm9yKHZhciBnPTAsXz1lLmxlbmd0aDtnPF87KytnKW5bZ109K3IoZVtnXSxnLGUpfWZ1bmN0aW9uIGQoKXtpZighIWEpZm9yKHZhciBnPTAsXz1lLmxlbmd0aDtnPF87KytnKW9bZ109K2koZVtnXSxnLGUpfXJldHVybiBoLmluaXRpYWxpemU9ZnVuY3Rpb24oZyl7YT1nLGYoKX0saC5saW5rcz1mdW5jdGlvbihnKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT1nLGYoKSxoKTplfSxoLmlkPWZ1bmN0aW9uKGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PWcsaCk6dH0saC5pdGVyYXRpb25zPWZ1bmN0aW9uKGcpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhjPStnLGgpOmN9LGguc3RyZW5ndGg9ZnVuY3Rpb24oZyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9dHlwZW9mIGc9PSJmdW5jdGlvbiI/ZzpPbigrZykscCgpLGgpOnJ9LGguZGlzdGFuY2U9ZnVuY3Rpb24oZyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9dHlwZW9mIGc9PSJmdW5jdGlvbiI/ZzpPbigrZyksZCgpLGgpOml9LGh9a20oKTtUYigpO2Z1bmN0aW9uIGx4dChlKXtyZXR1cm4gZS54fWZ1bmN0aW9uIGN4dChlKXtyZXR1cm4gZS55fXZhciBLU2U9MTAsWlNlPU1hdGguUEkqKDMtTWF0aC5zcXJ0KDUpKTtmdW5jdGlvbiB1eHQoZSl7dmFyIHQscj0xLG49LjAwMSxpPTEtTWF0aC5wb3cobiwxLzMwMCksbz0wLGE9LjYscz1KaSgpLGw9QV8odSksYz12cygidGljayIsImVuZCIpO2U9PW51bGwmJihlPVtdKTtmdW5jdGlvbiB1KCl7aCgpLGMuY2FsbCgidGljayIsdCkscjxuJiYobC5zdG9wKCksYy5jYWxsKCJlbmQiLHQpKX1mdW5jdGlvbiBoKGQpe3ZhciBnLF89ZS5sZW5ndGgseTtkPT09dm9pZCAwJiYoZD0xKTtmb3IodmFyIHg9MDt4PGQ7Kyt4KWZvcihyKz0oby1yKSppLHMuZWFjaChmdW5jdGlvbihiKXtiKHIpfSksZz0wO2c8XzsrK2cpeT1lW2ddLHkuZng9PW51bGw/eS54Kz15LnZ4Kj1hOih5Lng9eS5meCx5LnZ4PTApLHkuZnk9PW51bGw/eS55Kz15LnZ5Kj1hOih5Lnk9eS5meSx5LnZ5PTApO3JldHVybiB0fWZ1bmN0aW9uIGYoKXtmb3IodmFyIGQ9MCxnPWUubGVuZ3RoLF87ZDxnOysrZCl7aWYoXz1lW2RdLF8uaW5kZXg9ZCxfLmZ4IT1udWxsJiYoXy54PV8uZngpLF8uZnkhPW51bGwmJihfLnk9Xy5meSksaXNOYU4oXy54KXx8aXNOYU4oXy55KSl7dmFyIHk9S1NlKk1hdGguc3FydChkKSx4PWQqWlNlO18ueD15Kk1hdGguY29zKHgpLF8ueT15Kk1hdGguc2luKHgpfShpc05hTihfLnZ4KXx8aXNOYU4oXy52eSkpJiYoXy52eD1fLnZ5PTApfX1mdW5jdGlvbiBwKGQpe3JldHVybiBkLmluaXRpYWxpemUmJmQuaW5pdGlhbGl6ZShlKSxkfXJldHVybiBmKCksdD17dGljazpoLHJlc3RhcnQ6ZnVuY3Rpb24oKXtyZXR1cm4gbC5yZXN0YXJ0KHUpLHR9LHN0b3A6ZnVuY3Rpb24oKXtyZXR1cm4gbC5zdG9wKCksdH0sbm9kZXM6ZnVuY3Rpb24oZCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9ZCxmKCkscy5lYWNoKHApLHQpOmV9LGFscGhhOmZ1bmN0aW9uKGQpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPStkLHQpOnJ9LGFscGhhTWluOmZ1bmN0aW9uKGQpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPStkLHQpOm59LGFscGhhRGVjYXk6ZnVuY3Rpb24oZCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9K2QsdCk6K2l9LGFscGhhVGFyZ2V0OmZ1bmN0aW9uKGQpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPStkLHQpOm99LHZlbG9jaXR5RGVjYXk6ZnVuY3Rpb24oZCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGE9MS1kLHQpOjEtYX0sZm9yY2U6ZnVuY3Rpb24oZCxnKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD4xPyhnPT1udWxsP3MucmVtb3ZlKGQpOnMuc2V0KGQscChnKSksdCk6cy5nZXQoZCl9LGZpbmQ6ZnVuY3Rpb24oZCxnLF8pe3ZhciB5PTAseD1lLmxlbmd0aCxiLFMsQyxQLGs7Zm9yKF89PW51bGw/Xz0xLzA6Xyo9Xyx5PTA7eTx4OysreSlQPWVbeV0sYj1kLVAueCxTPWctUC55LEM9YipiK1MqUyxDPF8mJihrPVAsXz1DKTtyZXR1cm4ga30sb246ZnVuY3Rpb24oZCxnKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD4xPyhjLm9uKGQsZyksdCk6Yy5vbihkKX19fWZ1bmN0aW9uIGh4dCgpe3ZhciBlLHQscixuPU9uKC0zMCksaSxvPTEsYT0xLzAscz0uODE7ZnVuY3Rpb24gbChmKXt2YXIgcCxkPWUubGVuZ3RoLGc9emgoZSxseHQsY3h0KS52aXNpdEFmdGVyKHUpO2ZvcihyPWYscD0wO3A8ZDsrK3ApdD1lW3BdLGcudmlzaXQoaCl9ZnVuY3Rpb24gYygpe2lmKCEhZSl7dmFyIGYscD1lLmxlbmd0aCxkO2ZvcihpPW5ldyBBcnJheShwKSxmPTA7ZjxwOysrZilkPWVbZl0saVtkLmluZGV4XT0rbihkLGYsZSl9fWZ1bmN0aW9uIHUoZil7dmFyIHA9MCxkLGcsXz0wLHkseCxiO2lmKGYubGVuZ3RoKXtmb3IoeT14PWI9MDtiPDQ7KytiKShkPWZbYl0pJiYoZz1NYXRoLmFicyhkLnZhbHVlKSkmJihwKz1kLnZhbHVlLF8rPWcseSs9ZypkLngseCs9ZypkLnkpO2YueD15L18sZi55PXgvX31lbHNle2Q9ZixkLng9ZC5kYXRhLngsZC55PWQuZGF0YS55O2RvIHArPWlbZC5kYXRhLmluZGV4XTt3aGlsZShkPWQubmV4dCl9Zi52YWx1ZT1wfWZ1bmN0aW9uIGgoZixwLGQsZyl7aWYoIWYudmFsdWUpcmV0dXJuITA7dmFyIF89Zi54LXQueCx5PWYueS10LnkseD1nLXAsYj1fKl8reSp5O2lmKHgqeC9zPGIpcmV0dXJuIGI8YSYmKF89PT0wJiYoXz1mdSgpLGIrPV8qXykseT09PTAmJih5PWZ1KCksYis9eSp5KSxiPG8mJihiPU1hdGguc3FydChvKmIpKSx0LnZ4Kz1fKmYudmFsdWUqci9iLHQudnkrPXkqZi52YWx1ZSpyL2IpLCEwO2lmKGYubGVuZ3RofHxiPj1hKXJldHVybjsoZi5kYXRhIT09dHx8Zi5uZXh0KSYmKF89PT0wJiYoXz1mdSgpLGIrPV8qXykseT09PTAmJih5PWZ1KCksYis9eSp5KSxiPG8mJihiPU1hdGguc3FydChvKmIpKSk7ZG8gZi5kYXRhIT09dCYmKHg9aVtmLmRhdGEuaW5kZXhdKnIvYix0LnZ4Kz1fKngsdC52eSs9eSp4KTt3aGlsZShmPWYubmV4dCl9cmV0dXJuIGwuaW5pdGlhbGl6ZT1mdW5jdGlvbihmKXtlPWYsYygpfSxsLnN0cmVuZ3RoPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPXR5cGVvZiBmPT0iZnVuY3Rpb24iP2Y6T24oK2YpLGMoKSxsKTpufSxsLmRpc3RhbmNlTWluPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPWYqZixsKTpNYXRoLnNxcnQobyl9LGwuZGlzdGFuY2VNYXg9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGE9ZipmLGwpOk1hdGguc3FydChhKX0sbC50aGV0YT1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocz1mKmYsbCk6TWF0aC5zcXJ0KHMpfSxsfWZ1bmN0aW9uIGZ4dChlLHQscil7dmFyIG4saT1PbiguMSksbyxhO3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1PbigrZSkpLHQ9PW51bGwmJih0PTApLHI9PW51bGwmJihyPTApO2Z1bmN0aW9uIHMoYyl7Zm9yKHZhciB1PTAsaD1uLmxlbmd0aDt1PGg7Kyt1KXt2YXIgZj1uW3VdLHA9Zi54LXR8fDFlLTYsZD1mLnktcnx8MWUtNixnPU1hdGguc3FydChwKnArZCpkKSxfPShhW3VdLWcpKm9bdV0qYy9nO2YudngrPXAqXyxmLnZ5Kz1kKl99fWZ1bmN0aW9uIGwoKXtpZighIW4pe3ZhciBjLHU9bi5sZW5ndGg7Zm9yKG89bmV3IEFycmF5KHUpLGE9bmV3IEFycmF5KHUpLGM9MDtjPHU7KytjKWFbY109K2UobltjXSxjLG4pLG9bY109aXNOYU4oYVtjXSk/MDoraShuW2NdLGMsbil9fXJldHVybiBzLmluaXRpYWxpemU9ZnVuY3Rpb24oYyl7bj1jLGwoKX0scy5zdHJlbmd0aD1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT10eXBlb2YgYz09ImZ1bmN0aW9uIj9jOk9uKCtjKSxsKCkscyk6aX0scy5yYWRpdXM9ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIGM9PSJmdW5jdGlvbiI/YzpPbigrYyksbCgpLHMpOmV9LHMueD1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD0rYyxzKTp0fSxzLnk9ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9K2Mscyk6cn0sc31mdW5jdGlvbiBweHQoZSl7dmFyIHQ9T24oLjEpLHIsbixpO3R5cGVvZiBlIT0iZnVuY3Rpb24iJiYoZT1PbihlPT1udWxsPzA6K2UpKTtmdW5jdGlvbiBvKHMpe2Zvcih2YXIgbD0wLGM9ci5sZW5ndGgsdTtsPGM7KytsKXU9cltsXSx1LnZ4Kz0oaVtsXS11LngpKm5bbF0qc31mdW5jdGlvbiBhKCl7aWYoISFyKXt2YXIgcyxsPXIubGVuZ3RoO2ZvcihuPW5ldyBBcnJheShsKSxpPW5ldyBBcnJheShsKSxzPTA7czxsOysrcyluW3NdPWlzTmFOKGlbc109K2UocltzXSxzLHIpKT8wOit0KHJbc10scyxyKX19cmV0dXJuIG8uaW5pdGlhbGl6ZT1mdW5jdGlvbihzKXtyPXMsYSgpfSxvLnN0cmVuZ3RoPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBzPT0iZnVuY3Rpb24iP3M6T24oK3MpLGEoKSxvKTp0fSxvLng9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpPbigrcyksYSgpLG8pOmV9LG99ZnVuY3Rpb24gZHh0KGUpe3ZhciB0PU9uKC4xKSxyLG4saTt0eXBlb2YgZSE9ImZ1bmN0aW9uIiYmKGU9T24oZT09bnVsbD8wOitlKSk7ZnVuY3Rpb24gbyhzKXtmb3IodmFyIGw9MCxjPXIubGVuZ3RoLHU7bDxjOysrbCl1PXJbbF0sdS52eSs9KGlbbF0tdS55KSpuW2xdKnN9ZnVuY3Rpb24gYSgpe2lmKCEhcil7dmFyIHMsbD1yLmxlbmd0aDtmb3Iobj1uZXcgQXJyYXkobCksaT1uZXcgQXJyYXkobCkscz0wO3M8bDsrK3MpbltzXT1pc05hTihpW3NdPStlKHJbc10scyxyKSk/MDordChyW3NdLHMscil9fXJldHVybiBvLmluaXRpYWxpemU9ZnVuY3Rpb24ocyl7cj1zLGEoKX0sby5zdHJlbmd0aD1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOk9uKCtzKSxhKCksbyk6dH0sby55PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiBzPT0iZnVuY3Rpb24iP3M6T24oK3MpLGEoKSxvKTplfSxvfWZ1bmN0aW9uIG14dChlKXtyZXR1cm4gTWF0aC5hYnMoZT1NYXRoLnJvdW5kKGUpKT49MWUyMT9lLnRvTG9jYWxlU3RyaW5nKCJlbiIpLnJlcGxhY2UoLywvZywiIik6ZS50b1N0cmluZygxMCl9ZnVuY3Rpb24gT18oZSx0KXtpZigocj0oZT10P2UudG9FeHBvbmVudGlhbCh0LTEpOmUudG9FeHBvbmVudGlhbCgpKS5pbmRleE9mKCJlIikpPDApcmV0dXJuIG51bGw7dmFyIHIsbj1lLnNsaWNlKDAscik7cmV0dXJuW24ubGVuZ3RoPjE/blswXStuLnNsaWNlKDIpOm4sK2Uuc2xpY2UocisxKV19ZnVuY3Rpb24gRmgoZSl7cmV0dXJuIGU9T18oTWF0aC5hYnMoZSkpLGU/ZVsxXTpOYU59ZnVuY3Rpb24gZ3h0KGUsdCl7cmV0dXJuIGZ1bmN0aW9uKHIsbil7Zm9yKHZhciBpPXIubGVuZ3RoLG89W10sYT0wLHM9ZVswXSxsPTA7aT4wJiZzPjAmJihsK3MrMT5uJiYocz1NYXRoLm1heCgxLG4tbCkpLG8ucHVzaChyLnN1YnN0cmluZyhpLT1zLGkrcykpLCEoKGwrPXMrMSk+bikpOylzPWVbYT0oYSsxKSVlLmxlbmd0aF07cmV0dXJuIG8ucmV2ZXJzZSgpLmpvaW4odCl9fWZ1bmN0aW9uIF94dChlKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuIHQucmVwbGFjZSgvWzAtOV0vZyxmdW5jdGlvbihyKXtyZXR1cm4gZVsrcl19KX19dmFyIEpTZT0vXig/OiguKT8oWzw+PV5dKSk/KFsrXC0oIF0pPyhbJCNdKT8oMCk/KFxkKyk/KCwpPyhcLlxkKyk/KH4pPyhbYS16JV0pPyQvaTtmdW5jdGlvbiBMcChlKXtpZighKHQ9SlNlLmV4ZWMoZSkpKXRocm93IG5ldyBFcnJvcigiaW52YWxpZCBmb3JtYXQ6ICIrZSk7dmFyIHQ7cmV0dXJuIG5ldyBxRSh7ZmlsbDp0WzFdLGFsaWduOnRbMl0sc2lnbjp0WzNdLHN5bWJvbDp0WzRdLHplcm86dFs1XSx3aWR0aDp0WzZdLGNvbW1hOnRbN10scHJlY2lzaW9uOnRbOF0mJnRbOF0uc2xpY2UoMSksdHJpbTp0WzldLHR5cGU6dFsxMF19KX1McC5wcm90b3R5cGU9cUUucHJvdG90eXBlO2Z1bmN0aW9uIHFFKGUpe3RoaXMuZmlsbD1lLmZpbGw9PT12b2lkIDA/IiAiOmUuZmlsbCsiIix0aGlzLmFsaWduPWUuYWxpZ249PT12b2lkIDA/Ij4iOmUuYWxpZ24rIiIsdGhpcy5zaWduPWUuc2lnbj09PXZvaWQgMD8iLSI6ZS5zaWduKyIiLHRoaXMuc3ltYm9sPWUuc3ltYm9sPT09dm9pZCAwPyIiOmUuc3ltYm9sKyIiLHRoaXMuemVybz0hIWUuemVybyx0aGlzLndpZHRoPWUud2lkdGg9PT12b2lkIDA/dm9pZCAwOitlLndpZHRoLHRoaXMuY29tbWE9ISFlLmNvbW1hLHRoaXMucHJlY2lzaW9uPWUucHJlY2lzaW9uPT09dm9pZCAwP3ZvaWQgMDorZS5wcmVjaXNpb24sdGhpcy50cmltPSEhZS50cmltLHRoaXMudHlwZT1lLnR5cGU9PT12b2lkIDA/IiI6ZS50eXBlKyIifXFFLnByb3RvdHlwZS50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiB0aGlzLmZpbGwrdGhpcy5hbGlnbit0aGlzLnNpZ24rdGhpcy5zeW1ib2wrKHRoaXMuemVybz8iMCI6IiIpKyh0aGlzLndpZHRoPT09dm9pZCAwPyIiOk1hdGgubWF4KDEsdGhpcy53aWR0aHwwKSkrKHRoaXMuY29tbWE/IiwiOiIiKSsodGhpcy5wcmVjaXNpb249PT12b2lkIDA/IiI6Ii4iK01hdGgubWF4KDAsdGhpcy5wcmVjaXNpb258MCkpKyh0aGlzLnRyaW0/In4iOiIiKSt0aGlzLnR5cGV9O2Z1bmN0aW9uIHl4dChlKXt0OmZvcih2YXIgdD1lLmxlbmd0aCxyPTEsbj0tMSxpO3I8dDsrK3Ipc3dpdGNoKGVbcl0pe2Nhc2UiLiI6bj1pPXI7YnJlYWs7Y2FzZSIwIjpuPT09MCYmKG49ciksaT1yO2JyZWFrO2RlZmF1bHQ6aWYoIStlW3JdKWJyZWFrIHQ7bj4wJiYobj0wKTticmVha31yZXR1cm4gbj4wP2Uuc2xpY2UoMCxuKStlLnNsaWNlKGkrMSk6ZX12YXIgY2o7ZnVuY3Rpb24gdnh0KGUsdCl7dmFyIHI9T18oZSx0KTtpZighcilyZXR1cm4gZSsiIjt2YXIgbj1yWzBdLGk9clsxXSxvPWktKGNqPU1hdGgubWF4KC04LE1hdGgubWluKDgsTWF0aC5mbG9vcihpLzMpKSkqMykrMSxhPW4ubGVuZ3RoO3JldHVybiBvPT09YT9uOm8+YT9uK25ldyBBcnJheShvLWErMSkuam9pbigiMCIpOm8+MD9uLnNsaWNlKDAsbykrIi4iK24uc2xpY2Uobyk6IjAuIituZXcgQXJyYXkoMS1vKS5qb2luKCIwIikrT18oZSxNYXRoLm1heCgwLHQrby0xKSlbMF19ZnVuY3Rpb24gdWooZSx0KXt2YXIgcj1PXyhlLHQpO2lmKCFyKXJldHVybiBlKyIiO3ZhciBuPXJbMF0saT1yWzFdO3JldHVybiBpPDA/IjAuIituZXcgQXJyYXkoLWkpLmpvaW4oIjAiKStuOm4ubGVuZ3RoPmkrMT9uLnNsaWNlKDAsaSsxKSsiLiIrbi5zbGljZShpKzEpOm4rbmV3IEFycmF5KGktbi5sZW5ndGgrMikuam9pbigiMCIpfXZhciBoaj17IiUiOmZ1bmN0aW9uKGUsdCl7cmV0dXJuKGUqMTAwKS50b0ZpeGVkKHQpfSxiOmZ1bmN0aW9uKGUpe3JldHVybiBNYXRoLnJvdW5kKGUpLnRvU3RyaW5nKDIpfSxjOmZ1bmN0aW9uKGUpe3JldHVybiBlKyIifSxkOm14dCxlOmZ1bmN0aW9uKGUsdCl7cmV0dXJuIGUudG9FeHBvbmVudGlhbCh0KX0sZjpmdW5jdGlvbihlLHQpe3JldHVybiBlLnRvRml4ZWQodCl9LGc6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gZS50b1ByZWNpc2lvbih0KX0sbzpmdW5jdGlvbihlKXtyZXR1cm4gTWF0aC5yb3VuZChlKS50b1N0cmluZyg4KX0scDpmdW5jdGlvbihlLHQpe3JldHVybiB1aihlKjEwMCx0KX0scjp1aixzOnZ4dCxYOmZ1bmN0aW9uKGUpe3JldHVybiBNYXRoLnJvdW5kKGUpLnRvU3RyaW5nKDE2KS50b1VwcGVyQ2FzZSgpfSx4OmZ1bmN0aW9uKGUpe3JldHVybiBNYXRoLnJvdW5kKGUpLnRvU3RyaW5nKDE2KX19O2Z1bmN0aW9uIGZqKGUpe3JldHVybiBlfXZhciB4eHQ9QXJyYXkucHJvdG90eXBlLm1hcCxieHQ9WyJ5IiwieiIsImEiLCJmIiwicCIsIm4iLCJceEI1IiwibSIsIiIsImsiLCJNIiwiRyIsIlQiLCJQIiwiRSIsIloiLCJZIl07ZnVuY3Rpb24gdGsoZSl7dmFyIHQ9ZS5ncm91cGluZz09PXZvaWQgMHx8ZS50aG91c2FuZHM9PT12b2lkIDA/Zmo6Z3h0KHh4dC5jYWxsKGUuZ3JvdXBpbmcsTnVtYmVyKSxlLnRob3VzYW5kcysiIikscj1lLmN1cnJlbmN5PT09dm9pZCAwPyIiOmUuY3VycmVuY3lbMF0rIiIsbj1lLmN1cnJlbmN5PT09dm9pZCAwPyIiOmUuY3VycmVuY3lbMV0rIiIsaT1lLmRlY2ltYWw9PT12b2lkIDA/Ii4iOmUuZGVjaW1hbCsiIixvPWUubnVtZXJhbHM9PT12b2lkIDA/Zmo6X3h0KHh4dC5jYWxsKGUubnVtZXJhbHMsU3RyaW5nKSksYT1lLnBlcmNlbnQ9PT12b2lkIDA/IiUiOmUucGVyY2VudCsiIixzPWUubWludXM9PT12b2lkIDA/Ii0iOmUubWludXMrIiIsbD1lLm5hbj09PXZvaWQgMD8iTmFOIjplLm5hbisiIjtmdW5jdGlvbiBjKGgpe2g9THAoaCk7dmFyIGY9aC5maWxsLHA9aC5hbGlnbixkPWguc2lnbixnPWguc3ltYm9sLF89aC56ZXJvLHk9aC53aWR0aCx4PWguY29tbWEsYj1oLnByZWNpc2lvbixTPWgudHJpbSxDPWgudHlwZTtDPT09Im4iPyh4PSEwLEM9ImciKTpoaltDXXx8KGI9PT12b2lkIDAmJihiPTEyKSxTPSEwLEM9ImciKSwoX3x8Zj09PSIwIiYmcD09PSI9IikmJihfPSEwLGY9IjAiLHA9Ij0iKTt2YXIgUD1nPT09IiQiP3I6Zz09PSIjIiYmL1tib3hYXS8udGVzdChDKT8iMCIrQy50b0xvd2VyQ2FzZSgpOiIiLGs9Zz09PSIkIj9uOi9bJXBdLy50ZXN0KEMpP2E6IiIsTz1oaltDXSxEPS9bZGVmZ3BycyVdLy50ZXN0KEMpO2I9Yj09PXZvaWQgMD82Oi9bZ3Byc10vLnRlc3QoQyk/TWF0aC5tYXgoMSxNYXRoLm1pbigyMSxiKSk6TWF0aC5tYXgoMCxNYXRoLm1pbigyMCxiKSk7ZnVuY3Rpb24gQihJKXt2YXIgTD1QLFI9ayxGLHosVTtpZihDPT09ImMiKVI9TyhJKStSLEk9IiI7ZWxzZXtJPStJO3ZhciBXPUk8MHx8MS9JPDA7aWYoST1pc05hTihJKT9sOk8oTWF0aC5hYnMoSSksYiksUyYmKEk9eXh0KEkpKSxXJiYrST09MCYmZCE9PSIrIiYmKFc9ITEpLEw9KFc/ZD09PSIoIj9kOnM6ZD09PSItInx8ZD09PSIoIj8iIjpkKStMLFI9KEM9PT0icyI/Ynh0WzgrY2ovM106IiIpK1IrKFcmJmQ9PT0iKCI/IikiOiIiKSxEKXtmb3IoRj0tMSx6PUkubGVuZ3RoOysrRjx6OylpZihVPUkuY2hhckNvZGVBdChGKSw0OD5VfHxVPjU3KXtSPShVPT09NDY/aStJLnNsaWNlKEYrMSk6SS5zbGljZShGKSkrUixJPUkuc2xpY2UoMCxGKTticmVha319fXgmJiFfJiYoST10KEksMS8wKSk7dmFyIFo9TC5sZW5ndGgrSS5sZW5ndGgrUi5sZW5ndGgscnQ9Wjx5P25ldyBBcnJheSh5LVorMSkuam9pbihmKToiIjtzd2l0Y2goeCYmXyYmKEk9dChydCtJLHJ0Lmxlbmd0aD95LVIubGVuZ3RoOjEvMCkscnQ9IiIpLHApe2Nhc2UiPCI6ST1MK0krUitydDticmVhaztjYXNlIj0iOkk9TCtydCtJK1I7YnJlYWs7Y2FzZSJeIjpJPXJ0LnNsaWNlKDAsWj1ydC5sZW5ndGg+PjEpK0wrSStSK3J0LnNsaWNlKFopO2JyZWFrO2RlZmF1bHQ6ST1ydCtMK0krUjticmVha31yZXR1cm4gbyhJKX1yZXR1cm4gQi50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiBoKyIifSxCfWZ1bmN0aW9uIHUoaCxmKXt2YXIgcD1jKChoPUxwKGgpLGgudHlwZT0iZiIsaCkpLGQ9TWF0aC5tYXgoLTgsTWF0aC5taW4oOCxNYXRoLmZsb29yKEZoKGYpLzMpKSkqMyxnPU1hdGgucG93KDEwLC1kKSxfPWJ4dFs4K2QvM107cmV0dXJuIGZ1bmN0aW9uKHkpe3JldHVybiBwKGcqeSkrX319cmV0dXJue2Zvcm1hdDpjLGZvcm1hdFByZWZpeDp1fX12YXIgZWsseG4sR0U7cmsoe2RlY2ltYWw6Ii4iLHRob3VzYW5kczoiLCIsZ3JvdXBpbmc6WzNdLGN1cnJlbmN5OlsiJCIsIiJdLG1pbnVzOiItIn0pO2Z1bmN0aW9uIHJrKGUpe3JldHVybiBlaz10ayhlKSx4bj1lay5mb3JtYXQsR0U9ZWsuZm9ybWF0UHJlZml4LGVrfWZ1bmN0aW9uIG5rKGUpe3JldHVybiBNYXRoLm1heCgwLC1GaChNYXRoLmFicyhlKSkpfWZ1bmN0aW9uIGlrKGUsdCl7cmV0dXJuIE1hdGgubWF4KDAsTWF0aC5tYXgoLTgsTWF0aC5taW4oOCxNYXRoLmZsb29yKEZoKHQpLzMpKSkqMy1GaChNYXRoLmFicyhlKSkpfWZ1bmN0aW9uIG9rKGUsdCl7cmV0dXJuIGU9TWF0aC5hYnMoZSksdD1NYXRoLmFicyh0KS1lLE1hdGgubWF4KDAsRmgodCktRmgoZSkpKzF9ZnVuY3Rpb24gU3MoKXtyZXR1cm4gbmV3IHNrfWZ1bmN0aW9uIHNrKCl7dGhpcy5yZXNldCgpfXNrLnByb3RvdHlwZT17Y29uc3RydWN0b3I6c2sscmVzZXQ6ZnVuY3Rpb24oKXt0aGlzLnM9dGhpcy50PTB9LGFkZDpmdW5jdGlvbihlKXt3eHQoYWssZSx0aGlzLnQpLHd4dCh0aGlzLGFrLnMsdGhpcy5zKSx0aGlzLnM/dGhpcy50Kz1hay50OnRoaXMucz1hay50fSx2YWx1ZU9mOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc319O3ZhciBhaz1uZXcgc2s7ZnVuY3Rpb24gd3h0KGUsdCxyKXt2YXIgbj1lLnM9dCtyLGk9bi10LG89bi1pO2UudD10LW8rKHItaSl9dmFyIGxlPTFlLTYsel89MWUtMTIsQmU9TWF0aC5QSSxtbj1CZS8yLFBiPUJlLzQsU2k9QmUqMix2cj0xODAvQmUsX2U9QmUvMTgwLFJlPU1hdGguYWJzLGljPU1hdGguYXRhbixibj1NYXRoLmF0YW4yLFp0PU1hdGguY29zLFdFPU1hdGguY2VpbCxsaz1NYXRoLmV4cDt2YXIgRl89TWF0aC5sb2csY2s9TWF0aC5wb3csWHQ9TWF0aC5zaW4sY2E9TWF0aC5zaWdufHxmdW5jdGlvbihlKXtyZXR1cm4gZT4wPzE6ZTwwPy0xOjB9LHhyPU1hdGguc3FydCxJYj1NYXRoLnRhbjtmdW5jdGlvbiB1ayhlKXtyZXR1cm4gZT4xPzA6ZTwtMT9CZTpNYXRoLmFjb3MoZSl9ZnVuY3Rpb24gd24oZSl7cmV0dXJuIGU+MT9tbjplPC0xPy1tbjpNYXRoLmFzaW4oZSl9ZnVuY3Rpb24gcGooZSl7cmV0dXJuKGU9WHQoZS8yKSkqZX1mdW5jdGlvbiBGcigpe31mdW5jdGlvbiBoayhlLHQpe2UmJk14dC5oYXNPd25Qcm9wZXJ0eShlLnR5cGUpJiZNeHRbZS50eXBlXShlLHQpfXZhciBTeHQ9e0ZlYXR1cmU6ZnVuY3Rpb24oZSx0KXtoayhlLmdlb21ldHJ5LHQpfSxGZWF0dXJlQ29sbGVjdGlvbjpmdW5jdGlvbihlLHQpe2Zvcih2YXIgcj1lLmZlYXR1cmVzLG49LTEsaT1yLmxlbmd0aDsrK248aTspaGsocltuXS5nZW9tZXRyeSx0KX19LE14dD17U3BoZXJlOmZ1bmN0aW9uKGUsdCl7dC5zcGhlcmUoKX0sUG9pbnQ6ZnVuY3Rpb24oZSx0KXtlPWUuY29vcmRpbmF0ZXMsdC5wb2ludChlWzBdLGVbMV0sZVsyXSl9LE11bHRpUG9pbnQ6ZnVuY3Rpb24oZSx0KXtmb3IodmFyIHI9ZS5jb29yZGluYXRlcyxuPS0xLGk9ci5sZW5ndGg7KytuPGk7KWU9cltuXSx0LnBvaW50KGVbMF0sZVsxXSxlWzJdKX0sTGluZVN0cmluZzpmdW5jdGlvbihlLHQpe2RqKGUuY29vcmRpbmF0ZXMsdCwwKX0sTXVsdGlMaW5lU3RyaW5nOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuY29vcmRpbmF0ZXMsbj0tMSxpPXIubGVuZ3RoOysrbjxpOylkaihyW25dLHQsMCl9LFBvbHlnb246ZnVuY3Rpb24oZSx0KXtFeHQoZS5jb29yZGluYXRlcyx0KX0sTXVsdGlQb2x5Z29uOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuY29vcmRpbmF0ZXMsbj0tMSxpPXIubGVuZ3RoOysrbjxpOylFeHQocltuXSx0KX0sR2VvbWV0cnlDb2xsZWN0aW9uOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuZ2VvbWV0cmllcyxuPS0xLGk9ci5sZW5ndGg7KytuPGk7KWhrKHJbbl0sdCl9fTtmdW5jdGlvbiBkaihlLHQscil7dmFyIG49LTEsaT1lLmxlbmd0aC1yLG87Zm9yKHQubGluZVN0YXJ0KCk7KytuPGk7KW89ZVtuXSx0LnBvaW50KG9bMF0sb1sxXSxvWzJdKTt0LmxpbmVFbmQoKX1mdW5jdGlvbiBFeHQoZSx0KXt2YXIgcj0tMSxuPWUubGVuZ3RoO2Zvcih0LnBvbHlnb25TdGFydCgpOysrcjxuOylkaihlW3JdLHQsMSk7dC5wb2x5Z29uRW5kKCl9ZnVuY3Rpb24gdm8oZSx0KXtlJiZTeHQuaGFzT3duUHJvcGVydHkoZS50eXBlKT9TeHRbZS50eXBlXShlLHQpOmhrKGUsdCl9dmFyIFlFPVNzKCksZms9U3MoKSxUeHQsQ3h0LG1qLGdqLF9qLHB1PXtwb2ludDpGcixsaW5lU3RhcnQ6RnIsbGluZUVuZDpGcixwb2x5Z29uU3RhcnQ6ZnVuY3Rpb24oKXtZRS5yZXNldCgpLHB1LmxpbmVTdGFydD1RU2UscHUubGluZUVuZD10M2V9LHBvbHlnb25FbmQ6ZnVuY3Rpb24oKXt2YXIgZT0rWUU7ZmsuYWRkKGU8MD9TaStlOmUpLHRoaXMubGluZVN0YXJ0PXRoaXMubGluZUVuZD10aGlzLnBvaW50PUZyfSxzcGhlcmU6ZnVuY3Rpb24oKXtmay5hZGQoU2kpfX07ZnVuY3Rpb24gUVNlKCl7cHUucG9pbnQ9ZTNlfWZ1bmN0aW9uIHQzZSgpe0F4dChUeHQsQ3h0KX1mdW5jdGlvbiBlM2UoZSx0KXtwdS5wb2ludD1BeHQsVHh0PWUsQ3h0PXQsZSo9X2UsdCo9X2UsbWo9ZSxnaj1adCh0PXQvMitQYiksX2o9WHQodCl9ZnVuY3Rpb24gQXh0KGUsdCl7ZSo9X2UsdCo9X2UsdD10LzIrUGI7dmFyIHI9ZS1taixuPXI+PTA/MTotMSxpPW4qcixvPVp0KHQpLGE9WHQodCkscz1faiphLGw9Z2oqbytzKlp0KGkpLGM9cypuKlh0KGkpO1lFLmFkZChibihjLGwpKSxtaj1lLGdqPW8sX2o9YX1mdW5jdGlvbiBQeHQoZSl7cmV0dXJuIGZrLnJlc2V0KCksdm8oZSxwdSksZmsqMn1mdW5jdGlvbiBCXyhlKXtyZXR1cm5bYm4oZVsxXSxlWzBdKSx3bihlWzJdKV19ZnVuY3Rpb24gb2MoZSl7dmFyIHQ9ZVswXSxyPWVbMV0sbj1adChyKTtyZXR1cm5bbipadCh0KSxuKlh0KHQpLFh0KHIpXX1mdW5jdGlvbiBqRShlLHQpe3JldHVybiBlWzBdKnRbMF0rZVsxXSp0WzFdK2VbMl0qdFsyXX1mdW5jdGlvbiBrcChlLHQpe3JldHVybltlWzFdKnRbMl0tZVsyXSp0WzFdLGVbMl0qdFswXS1lWzBdKnRbMl0sZVswXSp0WzFdLWVbMV0qdFswXV19ZnVuY3Rpb24gcGsoZSx0KXtlWzBdKz10WzBdLGVbMV0rPXRbMV0sZVsyXSs9dFsyXX1mdW5jdGlvbiBYRShlLHQpe3JldHVybltlWzBdKnQsZVsxXSp0LGVbMl0qdF19ZnVuY3Rpb24gSF8oZSl7dmFyIHQ9eHIoZVswXSplWzBdK2VbMV0qZVsxXStlWzJdKmVbMl0pO2VbMF0vPXQsZVsxXS89dCxlWzJdLz10fXZhciBuaSxNcyxoaSxubCxWXyxSeHQsTnh0LExiLCRFPVNzKCksWW0sTnAsUnA9e3BvaW50OnlqLGxpbmVTdGFydDpJeHQsbGluZUVuZDpMeHQscG9seWdvblN0YXJ0OmZ1bmN0aW9uKCl7UnAucG9pbnQ9T3h0LFJwLmxpbmVTdGFydD1yM2UsUnAubGluZUVuZD1uM2UsJEUucmVzZXQoKSxwdS5wb2x5Z29uU3RhcnQoKX0scG9seWdvbkVuZDpmdW5jdGlvbigpe3B1LnBvbHlnb25FbmQoKSxScC5wb2ludD15aixScC5saW5lU3RhcnQ9SXh0LFJwLmxpbmVFbmQ9THh0LFlFPDA/KG5pPS0oaGk9MTgwKSxNcz0tKG5sPTkwKSk6JEU+bGU/bmw9OTA6JEU8LWxlJiYoTXM9LTkwKSxOcFswXT1uaSxOcFsxXT1oaX0sc3BoZXJlOmZ1bmN0aW9uKCl7bmk9LShoaT0xODApLE1zPS0obmw9OTApfX07ZnVuY3Rpb24geWooZSx0KXtZbS5wdXNoKE5wPVtuaT1lLGhpPWVdKSx0PE1zJiYoTXM9dCksdD5ubCYmKG5sPXQpfWZ1bmN0aW9uIER4dChlLHQpe3ZhciByPW9jKFtlKl9lLHQqX2VdKTtpZihMYil7dmFyIG49a3AoTGIsciksaT1bblsxXSwtblswXSwwXSxvPWtwKGksbik7SF8obyksbz1CXyhvKTt2YXIgYT1lLVZfLHM9YT4wPzE6LTEsbD1vWzBdKnZyKnMsYyx1PVJlKGEpPjE4MDt1XihzKlZfPGwmJmw8cyplKT8oYz1vWzFdKnZyLGM+bmwmJihubD1jKSk6KGw9KGwrMzYwKSUzNjAtMTgwLHVeKHMqVl88bCYmbDxzKmUpPyhjPS1vWzFdKnZyLGM8TXMmJihNcz1jKSk6KHQ8TXMmJihNcz10KSx0Pm5sJiYobmw9dCkpKSx1P2U8Vl8/cmwobmksZSk+cmwobmksaGkpJiYoaGk9ZSk6cmwoZSxoaSk+cmwobmksaGkpJiYobmk9ZSk6aGk+PW5pPyhlPG5pJiYobmk9ZSksZT5oaSYmKGhpPWUpKTplPlZfP3JsKG5pLGUpPnJsKG5pLGhpKSYmKGhpPWUpOnJsKGUsaGkpPnJsKG5pLGhpKSYmKG5pPWUpfWVsc2UgWW0ucHVzaChOcD1bbmk9ZSxoaT1lXSk7dDxNcyYmKE1zPXQpLHQ+bmwmJihubD10KSxMYj1yLFZfPWV9ZnVuY3Rpb24gSXh0KCl7UnAucG9pbnQ9RHh0fWZ1bmN0aW9uIEx4dCgpe05wWzBdPW5pLE5wWzFdPWhpLFJwLnBvaW50PXlqLExiPW51bGx9ZnVuY3Rpb24gT3h0KGUsdCl7aWYoTGIpe3ZhciByPWUtVl87JEUuYWRkKFJlKHIpPjE4MD9yKyhyPjA/MzYwOi0zNjApOnIpfWVsc2UgUnh0PWUsTnh0PXQ7cHUucG9pbnQoZSx0KSxEeHQoZSx0KX1mdW5jdGlvbiByM2UoKXtwdS5saW5lU3RhcnQoKX1mdW5jdGlvbiBuM2UoKXtPeHQoUnh0LE54dCkscHUubGluZUVuZCgpLFJlKCRFKT5sZSYmKG5pPS0oaGk9MTgwKSksTnBbMF09bmksTnBbMV09aGksTGI9bnVsbH1mdW5jdGlvbiBybChlLHQpe3JldHVybih0LT1lKTwwP3QrMzYwOnR9ZnVuY3Rpb24gaTNlKGUsdCl7cmV0dXJuIGVbMF0tdFswXX1mdW5jdGlvbiBreHQoZSx0KXtyZXR1cm4gZVswXTw9ZVsxXT9lWzBdPD10JiZ0PD1lWzFdOnQ8ZVswXXx8ZVsxXTx0fWZ1bmN0aW9uIHp4dChlKXt2YXIgdCxyLG4saSxvLGEscztpZihubD1oaT0tKG5pPU1zPTEvMCksWW09W10sdm8oZSxScCkscj1ZbS5sZW5ndGgpe2ZvcihZbS5zb3J0KGkzZSksdD0xLG49WW1bMF0sbz1bbl07dDxyOysrdClpPVltW3RdLGt4dChuLGlbMF0pfHxreHQobixpWzFdKT8ocmwoblswXSxpWzFdKT5ybChuWzBdLG5bMV0pJiYoblsxXT1pWzFdKSxybChpWzBdLG5bMV0pPnJsKG5bMF0sblsxXSkmJihuWzBdPWlbMF0pKTpvLnB1c2gobj1pKTtmb3IoYT0tMS8wLHI9by5sZW5ndGgtMSx0PTAsbj1vW3JdO3Q8PXI7bj1pLCsrdClpPW9bdF0sKHM9cmwoblsxXSxpWzBdKSk+YSYmKGE9cyxuaT1pWzBdLGhpPW5bMV0pfXJldHVybiBZbT1OcD1udWxsLG5pPT09MS8wfHxNcz09PTEvMD9bW05hTixOYU5dLFtOYU4sTmFOXV06W1tuaSxNc10sW2hpLG5sXV19dmFyIEtFLGRrLG1rLGdrLF9rLHlrLHZrLHhrLHZqLHhqLGJqLEh4dCxWeHQsRmEsQmEsSGEsZHU9e3NwaGVyZTpGcixwb2ludDp3aixsaW5lU3RhcnQ6Rnh0LGxpbmVFbmQ6Qnh0LHBvbHlnb25TdGFydDpmdW5jdGlvbigpe2R1LmxpbmVTdGFydD1zM2UsZHUubGluZUVuZD1sM2V9LHBvbHlnb25FbmQ6ZnVuY3Rpb24oKXtkdS5saW5lU3RhcnQ9Rnh0LGR1LmxpbmVFbmQ9Qnh0fX07ZnVuY3Rpb24gd2ooZSx0KXtlKj1fZSx0Kj1fZTt2YXIgcj1adCh0KTtaRShyKlp0KGUpLHIqWHQoZSksWHQodCkpfWZ1bmN0aW9uIFpFKGUsdCxyKXsrK0tFLG1rKz0oZS1taykvS0UsZ2srPSh0LWdrKS9LRSxfays9KHItX2spL0tFfWZ1bmN0aW9uIEZ4dCgpe2R1LnBvaW50PW8zZX1mdW5jdGlvbiBvM2UoZSx0KXtlKj1fZSx0Kj1fZTt2YXIgcj1adCh0KTtGYT1yKlp0KGUpLEJhPXIqWHQoZSksSGE9WHQodCksZHUucG9pbnQ9YTNlLFpFKEZhLEJhLEhhKX1mdW5jdGlvbiBhM2UoZSx0KXtlKj1fZSx0Kj1fZTt2YXIgcj1adCh0KSxuPXIqWnQoZSksaT1yKlh0KGUpLG89WHQodCksYT1ibih4cigoYT1CYSpvLUhhKmkpKmErKGE9SGEqbi1GYSpvKSphKyhhPUZhKmktQmEqbikqYSksRmEqbitCYSppK0hhKm8pO2RrKz1hLHlrKz1hKihGYSsoRmE9bikpLHZrKz1hKihCYSsoQmE9aSkpLHhrKz1hKihIYSsoSGE9bykpLFpFKEZhLEJhLEhhKX1mdW5jdGlvbiBCeHQoKXtkdS5wb2ludD13an1mdW5jdGlvbiBzM2UoKXtkdS5wb2ludD1jM2V9ZnVuY3Rpb24gbDNlKCl7VXh0KEh4dCxWeHQpLGR1LnBvaW50PXdqfWZ1bmN0aW9uIGMzZShlLHQpe0h4dD1lLFZ4dD10LGUqPV9lLHQqPV9lLGR1LnBvaW50PVV4dDt2YXIgcj1adCh0KTtGYT1yKlp0KGUpLEJhPXIqWHQoZSksSGE9WHQodCksWkUoRmEsQmEsSGEpfWZ1bmN0aW9uIFV4dChlLHQpe2UqPV9lLHQqPV9lO3ZhciByPVp0KHQpLG49cipadChlKSxpPXIqWHQoZSksbz1YdCh0KSxhPUJhKm8tSGEqaSxzPUhhKm4tRmEqbyxsPUZhKmktQmEqbixjPXhyKGEqYStzKnMrbCpsKSx1PXduKGMpLGg9YyYmLXUvYzt2ais9aCphLHhqKz1oKnMsYmorPWgqbCxkays9dSx5ays9dSooRmErKEZhPW4pKSx2ays9dSooQmErKEJhPWkpKSx4ays9dSooSGErKEhhPW8pKSxaRShGYSxCYSxIYSl9ZnVuY3Rpb24gcXh0KGUpe0tFPWRrPW1rPWdrPV9rPXlrPXZrPXhrPXZqPXhqPWJqPTAsdm8oZSxkdSk7dmFyIHQ9dmoscj14aixuPWJqLGk9dCp0K3IqcituKm47cmV0dXJuIGk8el8mJih0PXlrLHI9dmssbj14ayxkazxsZSYmKHQ9bWsscj1nayxuPV9rKSxpPXQqdCtyKnIrbipuLGk8el8pP1tOYU4sTmFOXTpbYm4ocix0KSp2cix3bihuL3hyKGkpKSp2cl19ZnVuY3Rpb24gVV8oZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fWZ1bmN0aW9uIEpFKGUsdCl7ZnVuY3Rpb24gcihuLGkpe3JldHVybiBuPWUobixpKSx0KG5bMF0sblsxXSl9cmV0dXJuIGUuaW52ZXJ0JiZ0LmludmVydCYmKHIuaW52ZXJ0PWZ1bmN0aW9uKG4saSl7cmV0dXJuIG49dC5pbnZlcnQobixpKSxuJiZlLmludmVydChuWzBdLG5bMV0pfSkscn1mdW5jdGlvbiBTaihlLHQpe3JldHVybltSZShlKT5CZT9lK01hdGgucm91bmQoLWUvU2kpKlNpOmUsdF19U2ouaW52ZXJ0PVNqO2Z1bmN0aW9uIFFFKGUsdCxyKXtyZXR1cm4oZSU9U2kpP3R8fHI/SkUoV3h0KGUpLFl4dCh0LHIpKTpXeHQoZSk6dHx8cj9ZeHQodCxyKTpTan1mdW5jdGlvbiBHeHQoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7cmV0dXJuIHQrPWUsW3Q+QmU/dC1TaTp0PC1CZT90K1NpOnQscl19fWZ1bmN0aW9uIFd4dChlKXt2YXIgdD1HeHQoZSk7cmV0dXJuIHQuaW52ZXJ0PUd4dCgtZSksdH1mdW5jdGlvbiBZeHQoZSx0KXt2YXIgcj1adChlKSxuPVh0KGUpLGk9WnQodCksbz1YdCh0KTtmdW5jdGlvbiBhKHMsbCl7dmFyIGM9WnQobCksdT1adChzKSpjLGg9WHQocykqYyxmPVh0KGwpLHA9ZipyK3UqbjtyZXR1cm5bYm4oaCppLXAqbyx1KnItZipuKSx3bihwKmkraCpvKV19cmV0dXJuIGEuaW52ZXJ0PWZ1bmN0aW9uKHMsbCl7dmFyIGM9WnQobCksdT1adChzKSpjLGg9WHQocykqYyxmPVh0KGwpLHA9ZippLWgqbztyZXR1cm5bYm4oaCppK2Yqbyx1KnIrcCpuKSx3bihwKnItdSpuKV19LGF9ZnVuY3Rpb24gYmsoZSl7ZT1RRShlWzBdKl9lLGVbMV0qX2UsZS5sZW5ndGg+Mj9lWzJdKl9lOjApO2Z1bmN0aW9uIHQocil7cmV0dXJuIHI9ZShyWzBdKl9lLHJbMV0qX2UpLHJbMF0qPXZyLHJbMV0qPXZyLHJ9cmV0dXJuIHQuaW52ZXJ0PWZ1bmN0aW9uKHIpe3JldHVybiByPWUuaW52ZXJ0KHJbMF0qX2UsclsxXSpfZSksclswXSo9dnIsclsxXSo9dnIscn0sdH1mdW5jdGlvbiBNaihlLHQscixuLGksbyl7aWYoISFyKXt2YXIgYT1adCh0KSxzPVh0KHQpLGw9bipyO2k9PW51bGw/KGk9dCtuKlNpLG89dC1sLzIpOihpPWp4dChhLGkpLG89anh0KGEsbyksKG4+MD9pPG86aT5vKSYmKGkrPW4qU2kpKTtmb3IodmFyIGMsdT1pO24+MD91Pm86dTxvO3UtPWwpYz1CXyhbYSwtcypadCh1KSwtcypYdCh1KV0pLGUucG9pbnQoY1swXSxjWzFdKX19ZnVuY3Rpb24ganh0KGUsdCl7dD1vYyh0KSx0WzBdLT1lLEhfKHQpO3ZhciByPXVrKC10WzFdKTtyZXR1cm4oKC10WzJdPDA/LXI6cikrU2ktbGUpJVNpfWZ1bmN0aW9uIFh4dCgpe3ZhciBlPVVfKFswLDBdKSx0PVVfKDkwKSxyPVVfKDYpLG4saSxvPXtwb2ludDphfTtmdW5jdGlvbiBhKGwsYyl7bi5wdXNoKGw9aShsLGMpKSxsWzBdKj12cixsWzFdKj12cn1mdW5jdGlvbiBzKCl7dmFyIGw9ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyksYz10LmFwcGx5KHRoaXMsYXJndW1lbnRzKSpfZSx1PXIuYXBwbHkodGhpcyxhcmd1bWVudHMpKl9lO3JldHVybiBuPVtdLGk9UUUoLWxbMF0qX2UsLWxbMV0qX2UsMCkuaW52ZXJ0LE1qKG8sYyx1LDEpLGw9e3R5cGU6IlBvbHlnb24iLGNvb3JkaW5hdGVzOltuXX0sbj1pPW51bGwsbH1yZXR1cm4gcy5jZW50ZXI9ZnVuY3Rpb24obCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIGw9PSJmdW5jdGlvbiI/bDpVXyhbK2xbMF0sK2xbMV1dKSxzKTplfSxzLnJhZGl1cz1mdW5jdGlvbihsKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD10eXBlb2YgbD09ImZ1bmN0aW9uIj9sOlVfKCtsKSxzKTp0fSxzLnByZWNpc2lvbj1mdW5jdGlvbihsKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2YgbD09ImZ1bmN0aW9uIj9sOlVfKCtsKSxzKTpyfSxzfWZ1bmN0aW9uIHdrKCl7dmFyIGU9W10sdDtyZXR1cm57cG9pbnQ6ZnVuY3Rpb24ocixuLGkpe3QucHVzaChbcixuLGldKX0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7ZS5wdXNoKHQ9W10pfSxsaW5lRW5kOkZyLHJlam9pbjpmdW5jdGlvbigpe2UubGVuZ3RoPjEmJmUucHVzaChlLnBvcCgpLmNvbmNhdChlLnNoaWZ0KCkpKX0scmVzdWx0OmZ1bmN0aW9uKCl7dmFyIHI9ZTtyZXR1cm4gZT1bXSx0PW51bGwscn19fWZ1bmN0aW9uIGtiKGUsdCl7cmV0dXJuIFJlKGVbMF0tdFswXSk8bGUmJlJlKGVbMV0tdFsxXSk8bGV9ZnVuY3Rpb24gU2soZSx0LHIsbil7dGhpcy54PWUsdGhpcy56PXQsdGhpcy5vPXIsdGhpcy5lPW4sdGhpcy52PSExLHRoaXMubj10aGlzLnA9bnVsbH1mdW5jdGlvbiBNayhlLHQscixuLGkpe3ZhciBvPVtdLGE9W10scyxsO2lmKGUuZm9yRWFjaChmdW5jdGlvbihkKXtpZighKChnPWQubGVuZ3RoLTEpPD0wKSl7dmFyIGcsXz1kWzBdLHk9ZFtnXSx4O2lmKGtiKF8seSkpe2lmKCFfWzJdJiYheVsyXSl7Zm9yKGkubGluZVN0YXJ0KCkscz0wO3M8ZzsrK3MpaS5wb2ludCgoXz1kW3NdKVswXSxfWzFdKTtpLmxpbmVFbmQoKTtyZXR1cm59eVswXSs9MipsZX1vLnB1c2goeD1uZXcgU2soXyxkLG51bGwsITApKSxhLnB1c2goeC5vPW5ldyBTayhfLG51bGwseCwhMSkpLG8ucHVzaCh4PW5ldyBTayh5LGQsbnVsbCwhMSkpLGEucHVzaCh4Lm89bmV3IFNrKHksbnVsbCx4LCEwKSl9fSksISFvLmxlbmd0aCl7Zm9yKGEuc29ydCh0KSwkeHQobyksJHh0KGEpLHM9MCxsPWEubGVuZ3RoO3M8bDsrK3MpYVtzXS5lPXI9IXI7Zm9yKHZhciBjPW9bMF0sdSxoOzspe2Zvcih2YXIgZj1jLHA9ITA7Zi52OylpZigoZj1mLm4pPT09YylyZXR1cm47dT1mLnosaS5saW5lU3RhcnQoKTtkb3tpZihmLnY9Zi5vLnY9ITAsZi5lKXtpZihwKWZvcihzPTAsbD11Lmxlbmd0aDtzPGw7KytzKWkucG9pbnQoKGg9dVtzXSlbMF0saFsxXSk7ZWxzZSBuKGYueCxmLm4ueCwxLGkpO2Y9Zi5ufWVsc2V7aWYocClmb3IodT1mLnAueixzPXUubGVuZ3RoLTE7cz49MDstLXMpaS5wb2ludCgoaD11W3NdKVswXSxoWzFdKTtlbHNlIG4oZi54LGYucC54LC0xLGkpO2Y9Zi5wfWY9Zi5vLHU9Zi56LHA9IXB9d2hpbGUoIWYudik7aS5saW5lRW5kKCl9fX1mdW5jdGlvbiAkeHQoZSl7aWYoISEodD1lLmxlbmd0aCkpe2Zvcih2YXIgdCxyPTAsbj1lWzBdLGk7KytyPHQ7KW4ubj1pPWVbcl0saS5wPW4sbj1pO24ubj1pPWVbMF0saS5wPW59fXZhciBFaj1TcygpO2Z1bmN0aW9uIFRqKGUpe3JldHVybiBSZShlWzBdKTw9QmU/ZVswXTpjYShlWzBdKSooKFJlKGVbMF0pK0JlKSVTaS1CZSl9ZnVuY3Rpb24gRWsoZSx0KXt2YXIgcj1Uaih0KSxuPXRbMV0saT1YdChuKSxvPVtYdChyKSwtWnQociksMF0sYT0wLHM9MDtFai5yZXNldCgpLGk9PT0xP249bW4rbGU6aT09PS0xJiYobj0tbW4tbGUpO2Zvcih2YXIgbD0wLGM9ZS5sZW5ndGg7bDxjOysrbClpZighIShoPSh1PWVbbF0pLmxlbmd0aCkpZm9yKHZhciB1LGgsZj11W2gtMV0scD1UaihmKSxkPWZbMV0vMitQYixnPVh0KGQpLF89WnQoZCkseT0wO3k8aDsrK3kscD1iLGc9QyxfPVAsZj14KXt2YXIgeD11W3ldLGI9VGooeCksUz14WzFdLzIrUGIsQz1YdChTKSxQPVp0KFMpLGs9Yi1wLE89az49MD8xOi0xLEQ9TyprLEI9RD5CZSxJPWcqQztpZihFai5hZGQoYm4oSSpPKlh0KEQpLF8qUCtJKlp0KEQpKSksYSs9Qj9rK08qU2k6ayxCXnA+PXJeYj49cil7dmFyIEw9a3Aob2MoZiksb2MoeCkpO0hfKEwpO3ZhciBSPWtwKG8sTCk7SF8oUik7dmFyIEY9KEJeaz49MD8tMToxKSp3bihSWzJdKTsobj5GfHxuPT09RiYmKExbMF18fExbMV0pKSYmKHMrPUJeaz49MD8xOi0xKX19cmV0dXJuKGE8LWxlfHxhPGxlJiZFajwtbGUpXnMmMX1mdW5jdGlvbiBUayhlLHQscixuKXtyZXR1cm4gZnVuY3Rpb24oaSl7dmFyIG89dChpKSxhPXdrKCkscz10KGEpLGw9ITEsYyx1LGgsZj17cG9pbnQ6cCxsaW5lU3RhcnQ6ZyxsaW5lRW5kOl8scG9seWdvblN0YXJ0OmZ1bmN0aW9uKCl7Zi5wb2ludD15LGYubGluZVN0YXJ0PXgsZi5saW5lRW5kPWIsdT1bXSxjPVtdfSxwb2x5Z29uRW5kOmZ1bmN0aW9uKCl7Zi5wb2ludD1wLGYubGluZVN0YXJ0PWcsZi5saW5lRW5kPV8sdT1JbSh1KTt2YXIgUz1FayhjLG4pO3UubGVuZ3RoPyhsfHwoaS5wb2x5Z29uU3RhcnQoKSxsPSEwKSxNayh1LGgzZSxTLHIsaSkpOlMmJihsfHwoaS5wb2x5Z29uU3RhcnQoKSxsPSEwKSxpLmxpbmVTdGFydCgpLHIobnVsbCxudWxsLDEsaSksaS5saW5lRW5kKCkpLGwmJihpLnBvbHlnb25FbmQoKSxsPSExKSx1PWM9bnVsbH0sc3BoZXJlOmZ1bmN0aW9uKCl7aS5wb2x5Z29uU3RhcnQoKSxpLmxpbmVTdGFydCgpLHIobnVsbCxudWxsLDEsaSksaS5saW5lRW5kKCksaS5wb2x5Z29uRW5kKCl9fTtmdW5jdGlvbiBwKFMsQyl7ZShTLEMpJiZpLnBvaW50KFMsQyl9ZnVuY3Rpb24gZChTLEMpe28ucG9pbnQoUyxDKX1mdW5jdGlvbiBnKCl7Zi5wb2ludD1kLG8ubGluZVN0YXJ0KCl9ZnVuY3Rpb24gXygpe2YucG9pbnQ9cCxvLmxpbmVFbmQoKX1mdW5jdGlvbiB5KFMsQyl7aC5wdXNoKFtTLENdKSxzLnBvaW50KFMsQyl9ZnVuY3Rpb24geCgpe3MubGluZVN0YXJ0KCksaD1bXX1mdW5jdGlvbiBiKCl7eShoWzBdWzBdLGhbMF1bMV0pLHMubGluZUVuZCgpO3ZhciBTPXMuY2xlYW4oKSxDPWEucmVzdWx0KCksUCxrPUMubGVuZ3RoLE8sRCxCO2lmKGgucG9wKCksYy5wdXNoKGgpLGg9bnVsbCwhIWspe2lmKFMmMSl7aWYoRD1DWzBdLChPPUQubGVuZ3RoLTEpPjApe2ZvcihsfHwoaS5wb2x5Z29uU3RhcnQoKSxsPSEwKSxpLmxpbmVTdGFydCgpLFA9MDtQPE87KytQKWkucG9pbnQoKEI9RFtQXSlbMF0sQlsxXSk7aS5saW5lRW5kKCl9cmV0dXJufWs+MSYmUyYyJiZDLnB1c2goQy5wb3AoKS5jb25jYXQoQy5zaGlmdCgpKSksdS5wdXNoKEMuZmlsdGVyKHUzZSkpfX1yZXR1cm4gZn19ZnVuY3Rpb24gdTNlKGUpe3JldHVybiBlLmxlbmd0aD4xfWZ1bmN0aW9uIGgzZShlLHQpe3JldHVybigoZT1lLngpWzBdPDA/ZVsxXS1tbi1sZTptbi1lWzFdKS0oKHQ9dC54KVswXTwwP3RbMV0tbW4tbGU6bW4tdFsxXSl9dmFyIHQ1PVRrKGZ1bmN0aW9uKCl7cmV0dXJuITB9LGYzZSxkM2UsWy1CZSwtbW5dKTtmdW5jdGlvbiBmM2UoZSl7dmFyIHQ9TmFOLHI9TmFOLG49TmFOLGk7cmV0dXJue2xpbmVTdGFydDpmdW5jdGlvbigpe2UubGluZVN0YXJ0KCksaT0xfSxwb2ludDpmdW5jdGlvbihvLGEpe3ZhciBzPW8+MD9CZTotQmUsbD1SZShvLXQpO1JlKGwtQmUpPGxlPyhlLnBvaW50KHQscj0ocithKS8yPjA/bW46LW1uKSxlLnBvaW50KG4sciksZS5saW5lRW5kKCksZS5saW5lU3RhcnQoKSxlLnBvaW50KHMsciksZS5wb2ludChvLHIpLGk9MCk6biE9PXMmJmw+PUJlJiYoUmUodC1uKTxsZSYmKHQtPW4qbGUpLFJlKG8tcyk8bGUmJihvLT1zKmxlKSxyPXAzZSh0LHIsbyxhKSxlLnBvaW50KG4sciksZS5saW5lRW5kKCksZS5saW5lU3RhcnQoKSxlLnBvaW50KHMsciksaT0wKSxlLnBvaW50KHQ9byxyPWEpLG49c30sbGluZUVuZDpmdW5jdGlvbigpe2UubGluZUVuZCgpLHQ9cj1OYU59LGNsZWFuOmZ1bmN0aW9uKCl7cmV0dXJuIDItaX19fWZ1bmN0aW9uIHAzZShlLHQscixuKXt2YXIgaSxvLGE9WHQoZS1yKTtyZXR1cm4gUmUoYSk+bGU/aWMoKFh0KHQpKihvPVp0KG4pKSpYdChyKS1YdChuKSooaT1adCh0KSkqWHQoZSkpLyhpKm8qYSkpOih0K24pLzJ9ZnVuY3Rpb24gZDNlKGUsdCxyLG4pe3ZhciBpO2lmKGU9PW51bGwpaT1yKm1uLG4ucG9pbnQoLUJlLGkpLG4ucG9pbnQoMCxpKSxuLnBvaW50KEJlLGkpLG4ucG9pbnQoQmUsMCksbi5wb2ludChCZSwtaSksbi5wb2ludCgwLC1pKSxuLnBvaW50KC1CZSwtaSksbi5wb2ludCgtQmUsMCksbi5wb2ludCgtQmUsaSk7ZWxzZSBpZihSZShlWzBdLXRbMF0pPmxlKXt2YXIgbz1lWzBdPHRbMF0/QmU6LUJlO2k9cipvLzIsbi5wb2ludCgtbyxpKSxuLnBvaW50KDAsaSksbi5wb2ludChvLGkpfWVsc2Ugbi5wb2ludCh0WzBdLHRbMV0pfWZ1bmN0aW9uIENrKGUpe3ZhciB0PVp0KGUpLHI9NipfZSxuPXQ+MCxpPVJlKHQpPmxlO2Z1bmN0aW9uIG8odSxoLGYscCl7TWoocCxlLHIsZix1LGgpfWZ1bmN0aW9uIGEodSxoKXtyZXR1cm4gWnQodSkqWnQoaCk+dH1mdW5jdGlvbiBzKHUpe3ZhciBoLGYscCxkLGc7cmV0dXJue2xpbmVTdGFydDpmdW5jdGlvbigpe2Q9cD0hMSxnPTF9LHBvaW50OmZ1bmN0aW9uKF8seSl7dmFyIHg9W18seV0sYixTPWEoXyx5KSxDPW4/Uz8wOmMoXyx5KTpTP2MoXysoXzwwP0JlOi1CZSkseSk6MDtpZighaCYmKGQ9cD1TKSYmdS5saW5lU3RhcnQoKSxTIT09cCYmKGI9bChoLHgpLCghYnx8a2IoaCxiKXx8a2IoeCxiKSkmJih4WzJdPTEpKSxTIT09cClnPTAsUz8odS5saW5lU3RhcnQoKSxiPWwoeCxoKSx1LnBvaW50KGJbMF0sYlsxXSkpOihiPWwoaCx4KSx1LnBvaW50KGJbMF0sYlsxXSwyKSx1LmxpbmVFbmQoKSksaD1iO2Vsc2UgaWYoaSYmaCYmbl5TKXt2YXIgUDshKEMmZikmJihQPWwoeCxoLCEwKSkmJihnPTAsbj8odS5saW5lU3RhcnQoKSx1LnBvaW50KFBbMF1bMF0sUFswXVsxXSksdS5wb2ludChQWzFdWzBdLFBbMV1bMV0pLHUubGluZUVuZCgpKToodS5wb2ludChQWzFdWzBdLFBbMV1bMV0pLHUubGluZUVuZCgpLHUubGluZVN0YXJ0KCksdS5wb2ludChQWzBdWzBdLFBbMF1bMV0sMykpKX1TJiYoIWh8fCFrYihoLHgpKSYmdS5wb2ludCh4WzBdLHhbMV0pLGg9eCxwPVMsZj1DfSxsaW5lRW5kOmZ1bmN0aW9uKCl7cCYmdS5saW5lRW5kKCksaD1udWxsfSxjbGVhbjpmdW5jdGlvbigpe3JldHVybiBnfChkJiZwKTw8MX19fWZ1bmN0aW9uIGwodSxoLGYpe3ZhciBwPW9jKHUpLGQ9b2MoaCksZz1bMSwwLDBdLF89a3AocCxkKSx5PWpFKF8sXykseD1fWzBdLGI9eS14Kng7aWYoIWIpcmV0dXJuIWYmJnU7dmFyIFM9dCp5L2IsQz0tdCp4L2IsUD1rcChnLF8pLGs9WEUoZyxTKSxPPVhFKF8sQyk7cGsoayxPKTt2YXIgRD1QLEI9akUoayxEKSxJPWpFKEQsRCksTD1CKkItSSooakUoayxrKS0xKTtpZighKEw8MCkpe3ZhciBSPXhyKEwpLEY9WEUoRCwoLUItUikvSSk7aWYocGsoRixrKSxGPUJfKEYpLCFmKXJldHVybiBGO3ZhciB6PXVbMF0sVT1oWzBdLFc9dVsxXSxaPWhbMV0scnQ7VTx6JiYocnQ9eix6PVUsVT1ydCk7dmFyIG90PVUteixzdD1SZShvdC1CZSk8bGUsU3Q9c3R8fG90PGxlO2lmKCFzdCYmWjxXJiYocnQ9VyxXPVosWj1ydCksU3Q/c3Q/VytaPjBeRlsxXTwoUmUoRlswXS16KTxsZT9XOlopOlc8PUZbMV0mJkZbMV08PVo6b3Q+QmVeKHo8PUZbMF0mJkZbMF08PVUpKXt2YXIgYnQ9WEUoRCwoLUIrUikvSSk7cmV0dXJuIHBrKGJ0LGspLFtGLEJfKGJ0KV19fX1mdW5jdGlvbiBjKHUsaCl7dmFyIGY9bj9lOkJlLWUscD0wO3JldHVybiB1PC1mP3B8PTE6dT5mJiYocHw9MiksaDwtZj9wfD00Omg+ZiYmKHB8PTgpLHB9cmV0dXJuIFRrKGEscyxvLG4/WzAsLWVdOlstQmUsZS1CZV0pfWZ1bmN0aW9uIEt4dChlLHQscixuLGksbyl7dmFyIGE9ZVswXSxzPWVbMV0sbD10WzBdLGM9dFsxXSx1PTAsaD0xLGY9bC1hLHA9Yy1zLGQ7aWYoZD1yLWEsISghZiYmZD4wKSl7aWYoZC89ZixmPDApe2lmKGQ8dSlyZXR1cm47ZDxoJiYoaD1kKX1lbHNlIGlmKGY+MCl7aWYoZD5oKXJldHVybjtkPnUmJih1PWQpfWlmKGQ9aS1hLCEoIWYmJmQ8MCkpe2lmKGQvPWYsZjwwKXtpZihkPmgpcmV0dXJuO2Q+dSYmKHU9ZCl9ZWxzZSBpZihmPjApe2lmKGQ8dSlyZXR1cm47ZDxoJiYoaD1kKX1pZihkPW4tcywhKCFwJiZkPjApKXtpZihkLz1wLHA8MCl7aWYoZDx1KXJldHVybjtkPGgmJihoPWQpfWVsc2UgaWYocD4wKXtpZihkPmgpcmV0dXJuO2Q+dSYmKHU9ZCl9aWYoZD1vLXMsISghcCYmZDwwKSl7aWYoZC89cCxwPDApe2lmKGQ+aClyZXR1cm47ZD51JiYodT1kKX1lbHNlIGlmKHA+MCl7aWYoZDx1KXJldHVybjtkPGgmJihoPWQpfXJldHVybiB1PjAmJihlWzBdPWErdSpmLGVbMV09cyt1KnApLGg8MSYmKHRbMF09YStoKmYsdFsxXT1zK2gqcCksITB9fX19fXZhciBlNT0xZTksQWs9LWU1O2Z1bmN0aW9uIERwKGUsdCxyLG4pe2Z1bmN0aW9uIGkoYyx1KXtyZXR1cm4gZTw9YyYmYzw9ciYmdDw9dSYmdTw9bn1mdW5jdGlvbiBvKGMsdSxoLGYpe3ZhciBwPTAsZD0wO2lmKGM9PW51bGx8fChwPWEoYyxoKSkhPT0oZD1hKHUsaCkpfHxsKGMsdSk8MF5oPjApZG8gZi5wb2ludChwPT09MHx8cD09PTM/ZTpyLHA+MT9uOnQpO3doaWxlKChwPShwK2grNCklNCkhPT1kKTtlbHNlIGYucG9pbnQodVswXSx1WzFdKX1mdW5jdGlvbiBhKGMsdSl7cmV0dXJuIFJlKGNbMF0tZSk8bGU/dT4wPzA6MzpSZShjWzBdLXIpPGxlP3U+MD8yOjE6UmUoY1sxXS10KTxsZT91PjA/MTowOnU+MD8zOjJ9ZnVuY3Rpb24gcyhjLHUpe3JldHVybiBsKGMueCx1LngpfWZ1bmN0aW9uIGwoYyx1KXt2YXIgaD1hKGMsMSksZj1hKHUsMSk7cmV0dXJuIGghPT1mP2gtZjpoPT09MD91WzFdLWNbMV06aD09PTE/Y1swXS11WzBdOmg9PT0yP2NbMV0tdVsxXTp1WzBdLWNbMF19cmV0dXJuIGZ1bmN0aW9uKGMpe3ZhciB1PWMsaD13aygpLGYscCxkLGcsXyx5LHgsYixTLEMsUCxrPXtwb2ludDpPLGxpbmVTdGFydDpMLGxpbmVFbmQ6Uixwb2x5Z29uU3RhcnQ6Qixwb2x5Z29uRW5kOkl9O2Z1bmN0aW9uIE8oeixVKXtpKHosVSkmJnUucG9pbnQoeixVKX1mdW5jdGlvbiBEKCl7Zm9yKHZhciB6PTAsVT0wLFc9cC5sZW5ndGg7VTxXOysrVSlmb3IodmFyIFo9cFtVXSxydD0xLG90PVoubGVuZ3RoLHN0PVpbMF0sU3QsYnQsTXQ9c3RbMF0sbHQ9c3RbMV07cnQ8b3Q7KytydClTdD1NdCxidD1sdCxzdD1aW3J0XSxNdD1zdFswXSxsdD1zdFsxXSxidDw9bj9sdD5uJiYoTXQtU3QpKihuLWJ0KT4obHQtYnQpKihlLVN0KSYmKyt6Omx0PD1uJiYoTXQtU3QpKihuLWJ0KTwobHQtYnQpKihlLVN0KSYmLS16O3JldHVybiB6fWZ1bmN0aW9uIEIoKXt1PWgsZj1bXSxwPVtdLFA9ITB9ZnVuY3Rpb24gSSgpe3ZhciB6PUQoKSxVPVAmJnosVz0oZj1JbShmKSkubGVuZ3RoOyhVfHxXKSYmKGMucG9seWdvblN0YXJ0KCksVSYmKGMubGluZVN0YXJ0KCksbyhudWxsLG51bGwsMSxjKSxjLmxpbmVFbmQoKSksVyYmTWsoZixzLHosbyxjKSxjLnBvbHlnb25FbmQoKSksdT1jLGY9cD1kPW51bGx9ZnVuY3Rpb24gTCgpe2sucG9pbnQ9RixwJiZwLnB1c2goZD1bXSksQz0hMCxTPSExLHg9Yj1OYU59ZnVuY3Rpb24gUigpe2YmJihGKGcsXykseSYmUyYmaC5yZWpvaW4oKSxmLnB1c2goaC5yZXN1bHQoKSkpLGsucG9pbnQ9TyxTJiZ1LmxpbmVFbmQoKX1mdW5jdGlvbiBGKHosVSl7dmFyIFc9aSh6LFUpO2lmKHAmJmQucHVzaChbeixVXSksQylnPXosXz1VLHk9VyxDPSExLFcmJih1LmxpbmVTdGFydCgpLHUucG9pbnQoeixVKSk7ZWxzZSBpZihXJiZTKXUucG9pbnQoeixVKTtlbHNle3ZhciBaPVt4PU1hdGgubWF4KEFrLE1hdGgubWluKGU1LHgpKSxiPU1hdGgubWF4KEFrLE1hdGgubWluKGU1LGIpKV0scnQ9W3o9TWF0aC5tYXgoQWssTWF0aC5taW4oZTUseikpLFU9TWF0aC5tYXgoQWssTWF0aC5taW4oZTUsVSkpXTtLeHQoWixydCxlLHQscixuKT8oU3x8KHUubGluZVN0YXJ0KCksdS5wb2ludChaWzBdLFpbMV0pKSx1LnBvaW50KHJ0WzBdLHJ0WzFdKSxXfHx1LmxpbmVFbmQoKSxQPSExKTpXJiYodS5saW5lU3RhcnQoKSx1LnBvaW50KHosVSksUD0hMSl9eD16LGI9VSxTPVd9cmV0dXJuIGt9fWZ1bmN0aW9uIFp4dCgpe3ZhciBlPTAsdD0wLHI9OTYwLG49NTAwLGksbyxhO3JldHVybiBhPXtzdHJlYW06ZnVuY3Rpb24ocyl7cmV0dXJuIGkmJm89PT1zP2k6aT1EcChlLHQscixuKShvPXMpfSxleHRlbnQ6ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9K3NbMF1bMF0sdD0rc1swXVsxXSxyPStzWzFdWzBdLG49K3NbMV1bMV0saT1vPW51bGwsYSk6W1tlLHRdLFtyLG5dXX19fXZhciBDaj1TcygpLEFqLFBrLElrLFJiPXtzcGhlcmU6RnIscG9pbnQ6RnIsbGluZVN0YXJ0Om0zZSxsaW5lRW5kOkZyLHBvbHlnb25TdGFydDpGcixwb2x5Z29uRW5kOkZyfTtmdW5jdGlvbiBtM2UoKXtSYi5wb2ludD1fM2UsUmIubGluZUVuZD1nM2V9ZnVuY3Rpb24gZzNlKCl7UmIucG9pbnQ9UmIubGluZUVuZD1Gcn1mdW5jdGlvbiBfM2UoZSx0KXtlKj1fZSx0Kj1fZSxBaj1lLFBrPVh0KHQpLElrPVp0KHQpLFJiLnBvaW50PXkzZX1mdW5jdGlvbiB5M2UoZSx0KXtlKj1fZSx0Kj1fZTt2YXIgcj1YdCh0KSxuPVp0KHQpLGk9UmUoZS1Baiksbz1adChpKSxhPVh0KGkpLHM9biphLGw9SWsqci1QaypuKm8sYz1QaypyK0lrKm4qbztDai5hZGQoYm4oeHIocypzK2wqbCksYykpLEFqPWUsUGs9cixJaz1ufWZ1bmN0aW9uIExrKGUpe3JldHVybiBDai5yZXNldCgpLHZvKGUsUmIpLCtDan12YXIgUGo9W251bGwsbnVsbF0sdjNlPXt0eXBlOiJMaW5lU3RyaW5nIixjb29yZGluYXRlczpQan07ZnVuY3Rpb24gTmIoZSx0KXtyZXR1cm4gUGpbMF09ZSxQalsxXT10LExrKHYzZSl9dmFyIEp4dD17RmVhdHVyZTpmdW5jdGlvbihlLHQpe3JldHVybiBrayhlLmdlb21ldHJ5LHQpfSxGZWF0dXJlQ29sbGVjdGlvbjpmdW5jdGlvbihlLHQpe2Zvcih2YXIgcj1lLmZlYXR1cmVzLG49LTEsaT1yLmxlbmd0aDsrK248aTspaWYoa2socltuXS5nZW9tZXRyeSx0KSlyZXR1cm4hMDtyZXR1cm4hMX19LFF4dD17U3BoZXJlOmZ1bmN0aW9uKCl7cmV0dXJuITB9LFBvaW50OmZ1bmN0aW9uKGUsdCl7cmV0dXJuIHRidChlLmNvb3JkaW5hdGVzLHQpfSxNdWx0aVBvaW50OmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuY29vcmRpbmF0ZXMsbj0tMSxpPXIubGVuZ3RoOysrbjxpOylpZih0YnQocltuXSx0KSlyZXR1cm4hMDtyZXR1cm4hMX0sTGluZVN0cmluZzpmdW5jdGlvbihlLHQpe3JldHVybiBlYnQoZS5jb29yZGluYXRlcyx0KX0sTXVsdGlMaW5lU3RyaW5nOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuY29vcmRpbmF0ZXMsbj0tMSxpPXIubGVuZ3RoOysrbjxpOylpZihlYnQocltuXSx0KSlyZXR1cm4hMDtyZXR1cm4hMX0sUG9seWdvbjpmdW5jdGlvbihlLHQpe3JldHVybiByYnQoZS5jb29yZGluYXRlcyx0KX0sTXVsdGlQb2x5Z29uOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuY29vcmRpbmF0ZXMsbj0tMSxpPXIubGVuZ3RoOysrbjxpOylpZihyYnQocltuXSx0KSlyZXR1cm4hMDtyZXR1cm4hMX0sR2VvbWV0cnlDb2xsZWN0aW9uOmZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPWUuZ2VvbWV0cmllcyxuPS0xLGk9ci5sZW5ndGg7KytuPGk7KWlmKGtrKHJbbl0sdCkpcmV0dXJuITA7cmV0dXJuITF9fTtmdW5jdGlvbiBrayhlLHQpe3JldHVybiBlJiZReHQuaGFzT3duUHJvcGVydHkoZS50eXBlKT9ReHRbZS50eXBlXShlLHQpOiExfWZ1bmN0aW9uIHRidChlLHQpe3JldHVybiBOYihlLHQpPT09MH1mdW5jdGlvbiBlYnQoZSx0KXtmb3IodmFyIHIsbixpLG89MCxhPWUubGVuZ3RoO288YTtvKyspe2lmKG49TmIoZVtvXSx0KSxuPT09MHx8bz4wJiYoaT1OYihlW29dLGVbby0xXSksaT4wJiZyPD1pJiZuPD1pJiYocituLWkpKigxLU1hdGgucG93KChyLW4pL2ksMikpPHpfKmkpKXJldHVybiEwO3I9bn1yZXR1cm4hMX1mdW5jdGlvbiByYnQoZSx0KXtyZXR1cm4hIUVrKGUubWFwKHgzZSksbmJ0KHQpKX1mdW5jdGlvbiB4M2UoZSl7cmV0dXJuIGU9ZS5tYXAobmJ0KSxlLnBvcCgpLGV9ZnVuY3Rpb24gbmJ0KGUpe3JldHVybltlWzBdKl9lLGVbMV0qX2VdfWZ1bmN0aW9uIGlidChlLHQpe3JldHVybihlJiZKeHQuaGFzT3duUHJvcGVydHkoZS50eXBlKT9KeHRbZS50eXBlXTpraykoZSx0KX1mdW5jdGlvbiBvYnQoZSx0LHIpe3ZhciBuPUlyKGUsdC1sZSxyKS5jb25jYXQodCk7cmV0dXJuIGZ1bmN0aW9uKGkpe3JldHVybiBuLm1hcChmdW5jdGlvbihvKXtyZXR1cm5baSxvXX0pfX1mdW5jdGlvbiBhYnQoZSx0LHIpe3ZhciBuPUlyKGUsdC1sZSxyKS5jb25jYXQodCk7cmV0dXJuIGZ1bmN0aW9uKGkpe3JldHVybiBuLm1hcChmdW5jdGlvbihvKXtyZXR1cm5bbyxpXX0pfX1mdW5jdGlvbiBSaygpe3ZhciBlLHQscixuLGksbyxhLHMsbD0xMCxjPWwsdT05MCxoPTM2MCxmLHAsZCxnLF89Mi41O2Z1bmN0aW9uIHkoKXtyZXR1cm57dHlwZToiTXVsdGlMaW5lU3RyaW5nIixjb29yZGluYXRlczp4KCl9fWZ1bmN0aW9uIHgoKXtyZXR1cm4gSXIoV0Uobi91KSp1LHIsdSkubWFwKGQpLmNvbmNhdChJcihXRShzL2gpKmgsYSxoKS5tYXAoZykpLmNvbmNhdChJcihXRSh0L2wpKmwsZSxsKS5maWx0ZXIoZnVuY3Rpb24oYil7cmV0dXJuIFJlKGIldSk+bGV9KS5tYXAoZikpLmNvbmNhdChJcihXRShvL2MpKmMsaSxjKS5maWx0ZXIoZnVuY3Rpb24oYil7cmV0dXJuIFJlKGIlaCk+bGV9KS5tYXAocCkpfXJldHVybiB5LmxpbmVzPWZ1bmN0aW9uKCl7cmV0dXJuIHgoKS5tYXAoZnVuY3Rpb24oYil7cmV0dXJue3R5cGU6IkxpbmVTdHJpbmciLGNvb3JkaW5hdGVzOmJ9fSl9LHkub3V0bGluZT1mdW5jdGlvbigpe3JldHVybnt0eXBlOiJQb2x5Z29uIixjb29yZGluYXRlczpbZChuKS5jb25jYXQoZyhhKS5zbGljZSgxKSxkKHIpLnJldmVyc2UoKS5zbGljZSgxKSxnKHMpLnJldmVyc2UoKS5zbGljZSgxKSldfX0seS5leHRlbnQ9ZnVuY3Rpb24oYil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/eS5leHRlbnRNYWpvcihiKS5leHRlbnRNaW5vcihiKTp5LmV4dGVudE1pbm9yKCl9LHkuZXh0ZW50TWFqb3I9ZnVuY3Rpb24oYil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49K2JbMF1bMF0scj0rYlsxXVswXSxzPStiWzBdWzFdLGE9K2JbMV1bMV0sbj5yJiYoYj1uLG49cixyPWIpLHM+YSYmKGI9cyxzPWEsYT1iKSx5LnByZWNpc2lvbihfKSk6W1tuLHNdLFtyLGFdXX0seS5leHRlbnRNaW5vcj1mdW5jdGlvbihiKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD0rYlswXVswXSxlPStiWzFdWzBdLG89K2JbMF1bMV0saT0rYlsxXVsxXSx0PmUmJihiPXQsdD1lLGU9Yiksbz5pJiYoYj1vLG89aSxpPWIpLHkucHJlY2lzaW9uKF8pKTpbW3Qsb10sW2UsaV1dfSx5LnN0ZXA9ZnVuY3Rpb24oYil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/eS5zdGVwTWFqb3IoYikuc3RlcE1pbm9yKGIpOnkuc3RlcE1pbm9yKCl9LHkuc3RlcE1ham9yPWZ1bmN0aW9uKGIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh1PStiWzBdLGg9K2JbMV0seSk6W3UsaF19LHkuc3RlcE1pbm9yPWZ1bmN0aW9uKGIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhsPStiWzBdLGM9K2JbMV0seSk6W2wsY119LHkucHJlY2lzaW9uPWZ1bmN0aW9uKGIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhfPStiLGY9b2J0KG8saSw5MCkscD1hYnQodCxlLF8pLGQ9b2J0KHMsYSw5MCksZz1hYnQobixyLF8pLHkpOl99LHkuZXh0ZW50TWFqb3IoW1stMTgwLC05MCtsZV0sWzE4MCw5MC1sZV1dKS5leHRlbnRNaW5vcihbWy0xODAsLTgwLWxlXSxbMTgwLDgwK2xlXV0pfWZ1bmN0aW9uIHNidCgpe3JldHVybiBSaygpKCl9ZnVuY3Rpb24gbGJ0KGUsdCl7dmFyIHI9ZVswXSpfZSxuPWVbMV0qX2UsaT10WzBdKl9lLG89dFsxXSpfZSxhPVp0KG4pLHM9WHQobiksbD1adChvKSxjPVh0KG8pLHU9YSpadChyKSxoPWEqWHQociksZj1sKlp0KGkpLHA9bCpYdChpKSxkPTIqd24oeHIocGooby1uKSthKmwqcGooaS1yKSkpLGc9WHQoZCksXz1kP2Z1bmN0aW9uKHkpe3ZhciB4PVh0KHkqPWQpL2csYj1YdChkLXkpL2csUz1iKnUreCpmLEM9YipoK3gqcCxQPWIqcyt4KmM7cmV0dXJuW2JuKEMsUykqdnIsYm4oUCx4cihTKlMrQypDKSkqdnJdfTpmdW5jdGlvbigpe3JldHVybltyKnZyLG4qdnJdfTtyZXR1cm4gXy5kaXN0YW5jZT1kLF99ZnVuY3Rpb24gam0oZSl7cmV0dXJuIGV9dmFyIElqPVNzKCksTGo9U3MoKSxjYnQsdWJ0LGtqLFJqLFhtPXtwb2ludDpGcixsaW5lU3RhcnQ6RnIsbGluZUVuZDpGcixwb2x5Z29uU3RhcnQ6ZnVuY3Rpb24oKXtYbS5saW5lU3RhcnQ9YjNlLFhtLmxpbmVFbmQ9UzNlfSxwb2x5Z29uRW5kOmZ1bmN0aW9uKCl7WG0ubGluZVN0YXJ0PVhtLmxpbmVFbmQ9WG0ucG9pbnQ9RnIsSWouYWRkKFJlKExqKSksTGoucmVzZXQoKX0scmVzdWx0OmZ1bmN0aW9uKCl7dmFyIGU9SWovMjtyZXR1cm4gSWoucmVzZXQoKSxlfX07ZnVuY3Rpb24gYjNlKCl7WG0ucG9pbnQ9dzNlfWZ1bmN0aW9uIHczZShlLHQpe1htLnBvaW50PWhidCxjYnQ9a2o9ZSx1YnQ9Umo9dH1mdW5jdGlvbiBoYnQoZSx0KXtMai5hZGQoUmoqZS1raip0KSxraj1lLFJqPXR9ZnVuY3Rpb24gUzNlKCl7aGJ0KGNidCx1YnQpfXZhciBOaj1YbTt2YXIgRGI9MS8wLE5rPURiLHI1PS1EYixEaz1yNSxNM2U9e3BvaW50OkUzZSxsaW5lU3RhcnQ6RnIsbGluZUVuZDpGcixwb2x5Z29uU3RhcnQ6RnIscG9seWdvbkVuZDpGcixyZXN1bHQ6ZnVuY3Rpb24oKXt2YXIgZT1bW0RiLE5rXSxbcjUsRGtdXTtyZXR1cm4gcjU9RGs9LShOaz1EYj0xLzApLGV9fTtmdW5jdGlvbiBFM2UoZSx0KXtlPERiJiYoRGI9ZSksZT5yNSYmKHI1PWUpLHQ8TmsmJihOaz10KSx0PkRrJiYoRGs9dCl9dmFyIE9iPU0zZTt2YXIgRGo9MCxPaj0wLG41PTAsT2s9MCx6az0wLHpiPTAsemo9MCxGaj0wLGk1PTAsZGJ0LG1idCxCaCxIaCxtdT17cG9pbnQ6cV8sbGluZVN0YXJ0OmZidCxsaW5lRW5kOnBidCxwb2x5Z29uU3RhcnQ6ZnVuY3Rpb24oKXttdS5saW5lU3RhcnQ9QTNlLG11LmxpbmVFbmQ9UDNlfSxwb2x5Z29uRW5kOmZ1bmN0aW9uKCl7bXUucG9pbnQ9cV8sbXUubGluZVN0YXJ0PWZidCxtdS5saW5lRW5kPXBidH0scmVzdWx0OmZ1bmN0aW9uKCl7dmFyIGU9aTU/W3pqL2k1LEZqL2k1XTp6Yj9bT2svemIsemsvemJdOm41P1tEai9uNSxPai9uNV06W05hTixOYU5dO3JldHVybiBEaj1Paj1uNT1Paz16az16Yj16aj1Gaj1pNT0wLGV9fTtmdW5jdGlvbiBxXyhlLHQpe0RqKz1lLE9qKz10LCsrbjV9ZnVuY3Rpb24gZmJ0KCl7bXUucG9pbnQ9VDNlfWZ1bmN0aW9uIFQzZShlLHQpe211LnBvaW50PUMzZSxxXyhCaD1lLEhoPXQpfWZ1bmN0aW9uIEMzZShlLHQpe3ZhciByPWUtQmgsbj10LUhoLGk9eHIocipyK24qbik7T2srPWkqKEJoK2UpLzIsemsrPWkqKEhoK3QpLzIsemIrPWkscV8oQmg9ZSxIaD10KX1mdW5jdGlvbiBwYnQoKXttdS5wb2ludD1xX31mdW5jdGlvbiBBM2UoKXttdS5wb2ludD1JM2V9ZnVuY3Rpb24gUDNlKCl7Z2J0KGRidCxtYnQpfWZ1bmN0aW9uIEkzZShlLHQpe211LnBvaW50PWdidCxxXyhkYnQ9Qmg9ZSxtYnQ9SGg9dCl9ZnVuY3Rpb24gZ2J0KGUsdCl7dmFyIHI9ZS1CaCxuPXQtSGgsaT14cihyKnIrbipuKTtPays9aSooQmgrZSkvMix6ays9aSooSGgrdCkvMix6Yis9aSxpPUhoKmUtQmgqdCx6ais9aSooQmgrZSksRmorPWkqKEhoK3QpLGk1Kz1pKjMscV8oQmg9ZSxIaD10KX12YXIgQmo9bXU7ZnVuY3Rpb24gRmsoZSl7dGhpcy5fY29udGV4dD1lfUZrLnByb3RvdHlwZT17X3JhZGl1czo0LjUscG9pbnRSYWRpdXM6ZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMuX3JhZGl1cz1lLHRoaXN9LHBvbHlnb25TdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0scG9seWdvbkVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT09PTAmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCksdGhpcy5fcG9pbnQ9TmFOfSxwb2ludDpmdW5jdGlvbihlLHQpe3N3aXRjaCh0aGlzLl9wb2ludCl7Y2FzZSAwOnt0aGlzLl9jb250ZXh0Lm1vdmVUbyhlLHQpLHRoaXMuX3BvaW50PTE7YnJlYWt9Y2FzZSAxOnt0aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpO2JyZWFrfWRlZmF1bHQ6e3RoaXMuX2NvbnRleHQubW92ZVRvKGUrdGhpcy5fcmFkaXVzLHQpLHRoaXMuX2NvbnRleHQuYXJjKGUsdCx0aGlzLl9yYWRpdXMsMCxTaSk7YnJlYWt9fX0scmVzdWx0OkZyfTt2YXIgVmo9U3MoKSxIaixfYnQseWJ0LG81LGE1LEJrPXtwb2ludDpGcixsaW5lU3RhcnQ6ZnVuY3Rpb24oKXtCay5wb2ludD1MM2V9LGxpbmVFbmQ6ZnVuY3Rpb24oKXtIaiYmdmJ0KF9idCx5YnQpLEJrLnBvaW50PUZyfSxwb2x5Z29uU3RhcnQ6ZnVuY3Rpb24oKXtIaj0hMH0scG9seWdvbkVuZDpmdW5jdGlvbigpe0hqPW51bGx9LHJlc3VsdDpmdW5jdGlvbigpe3ZhciBlPStWajtyZXR1cm4gVmoucmVzZXQoKSxlfX07ZnVuY3Rpb24gTDNlKGUsdCl7QmsucG9pbnQ9dmJ0LF9idD1vNT1lLHlidD1hNT10fWZ1bmN0aW9uIHZidChlLHQpe281LT1lLGE1LT10LFZqLmFkZCh4cihvNSpvNSthNSphNSkpLG81PWUsYTU9dH12YXIgVWo9Qms7ZnVuY3Rpb24gSGsoKXt0aGlzLl9zdHJpbmc9W119SGsucHJvdG90eXBlPXtfcmFkaXVzOjQuNSxfY2lyY2xlOnhidCg0LjUpLHBvaW50UmFkaXVzOmZ1bmN0aW9uKGUpe3JldHVybihlPStlKSE9PXRoaXMuX3JhZGl1cyYmKHRoaXMuX3JhZGl1cz1lLHRoaXMuX2NpcmNsZT1udWxsKSx0aGlzfSxwb2x5Z29uU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPTB9LHBvbHlnb25FbmQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPU5hTn0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9PT0wJiZ0aGlzLl9zdHJpbmcucHVzaCgiWiIpLHRoaXMuX3BvaW50PU5hTn0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMDp7dGhpcy5fc3RyaW5nLnB1c2goIk0iLGUsIiwiLHQpLHRoaXMuX3BvaW50PTE7YnJlYWt9Y2FzZSAxOnt0aGlzLl9zdHJpbmcucHVzaCgiTCIsZSwiLCIsdCk7YnJlYWt9ZGVmYXVsdDp7dGhpcy5fY2lyY2xlPT1udWxsJiYodGhpcy5fY2lyY2xlPXhidCh0aGlzLl9yYWRpdXMpKSx0aGlzLl9zdHJpbmcucHVzaCgiTSIsZSwiLCIsdCx0aGlzLl9jaXJjbGUpO2JyZWFrfX19LHJlc3VsdDpmdW5jdGlvbigpe2lmKHRoaXMuX3N0cmluZy5sZW5ndGgpe3ZhciBlPXRoaXMuX3N0cmluZy5qb2luKCIiKTtyZXR1cm4gdGhpcy5fc3RyaW5nPVtdLGV9ZWxzZSByZXR1cm4gbnVsbH19O2Z1bmN0aW9uIHhidChlKXtyZXR1cm4ibTAsIitlKyJhIitlKyIsIitlKyIgMCAxLDEgMCwiKy0yKmUrImEiK2UrIiwiK2UrIiAwIDEsMSAwLCIrMiplKyJ6In1mdW5jdGlvbiBiYnQoZSx0KXt2YXIgcj00LjUsbixpO2Z1bmN0aW9uIG8oYSl7cmV0dXJuIGEmJih0eXBlb2Ygcj09ImZ1bmN0aW9uIiYmaS5wb2ludFJhZGl1cygrci5hcHBseSh0aGlzLGFyZ3VtZW50cykpLHZvKGEsbihpKSkpLGkucmVzdWx0KCl9cmV0dXJuIG8uYXJlYT1mdW5jdGlvbihhKXtyZXR1cm4gdm8oYSxuKE5qKSksTmoucmVzdWx0KCl9LG8ubWVhc3VyZT1mdW5jdGlvbihhKXtyZXR1cm4gdm8oYSxuKFVqKSksVWoucmVzdWx0KCl9LG8uYm91bmRzPWZ1bmN0aW9uKGEpe3JldHVybiB2byhhLG4oT2IpKSxPYi5yZXN1bHQoKX0sby5jZW50cm9pZD1mdW5jdGlvbihhKXtyZXR1cm4gdm8oYSxuKEJqKSksQmoucmVzdWx0KCl9LG8ucHJvamVjdGlvbj1mdW5jdGlvbihhKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj1hPT1udWxsPyhlPW51bGwsam0pOihlPWEpLnN0cmVhbSxvKTplfSxvLmNvbnRleHQ9ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9YT09bnVsbD8odD1udWxsLG5ldyBIayk6bmV3IEZrKHQ9YSksdHlwZW9mIHIhPSJmdW5jdGlvbiImJmkucG9pbnRSYWRpdXMociksbyk6dH0sby5wb2ludFJhZGl1cz1mdW5jdGlvbihhKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2YgYT09ImZ1bmN0aW9uIj9hOihpLnBvaW50UmFkaXVzKCthKSwrYSksbyk6cn0sby5wcm9qZWN0aW9uKGUpLmNvbnRleHQodCl9ZnVuY3Rpb24gd2J0KGUpe3JldHVybntzdHJlYW06JG0oZSl9fWZ1bmN0aW9uICRtKGUpe3JldHVybiBmdW5jdGlvbih0KXt2YXIgcj1uZXcgcWo7Zm9yKHZhciBuIGluIGUpcltuXT1lW25dO3JldHVybiByLnN0cmVhbT10LHJ9fWZ1bmN0aW9uIHFqKCl7fXFqLnByb3RvdHlwZT17Y29uc3RydWN0b3I6cWoscG9pbnQ6ZnVuY3Rpb24oZSx0KXt0aGlzLnN0cmVhbS5wb2ludChlLHQpfSxzcGhlcmU6ZnVuY3Rpb24oKXt0aGlzLnN0cmVhbS5zcGhlcmUoKX0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5zdHJlYW0ubGluZVN0YXJ0KCl9LGxpbmVFbmQ6ZnVuY3Rpb24oKXt0aGlzLnN0cmVhbS5saW5lRW5kKCl9LHBvbHlnb25TdGFydDpmdW5jdGlvbigpe3RoaXMuc3RyZWFtLnBvbHlnb25TdGFydCgpfSxwb2x5Z29uRW5kOmZ1bmN0aW9uKCl7dGhpcy5zdHJlYW0ucG9seWdvbkVuZCgpfX07ZnVuY3Rpb24gR2ooZSx0LHIpe3ZhciBuPWUuY2xpcEV4dGVudCYmZS5jbGlwRXh0ZW50KCk7cmV0dXJuIGUuc2NhbGUoMTUwKS50cmFuc2xhdGUoWzAsMF0pLG4hPW51bGwmJmUuY2xpcEV4dGVudChudWxsKSx2byhyLGUuc3RyZWFtKE9iKSksdChPYi5yZXN1bHQoKSksbiE9bnVsbCYmZS5jbGlwRXh0ZW50KG4pLGV9ZnVuY3Rpb24gR18oZSx0LHIpe3JldHVybiBHaihlLGZ1bmN0aW9uKG4pe3ZhciBpPXRbMV1bMF0tdFswXVswXSxvPXRbMV1bMV0tdFswXVsxXSxhPU1hdGgubWluKGkvKG5bMV1bMF0tblswXVswXSksby8oblsxXVsxXS1uWzBdWzFdKSkscz0rdFswXVswXSsoaS1hKihuWzFdWzBdK25bMF1bMF0pKS8yLGw9K3RbMF1bMV0rKG8tYSooblsxXVsxXStuWzBdWzFdKSkvMjtlLnNjYWxlKDE1MCphKS50cmFuc2xhdGUoW3MsbF0pfSxyKX1mdW5jdGlvbiBGYihlLHQscil7cmV0dXJuIEdfKGUsW1swLDBdLHRdLHIpfWZ1bmN0aW9uIEJiKGUsdCxyKXtyZXR1cm4gR2ooZSxmdW5jdGlvbihuKXt2YXIgaT0rdCxvPWkvKG5bMV1bMF0tblswXVswXSksYT0oaS1vKihuWzFdWzBdK25bMF1bMF0pKS8yLHM9LW8qblswXVsxXTtlLnNjYWxlKDE1MCpvKS50cmFuc2xhdGUoW2Esc10pfSxyKX1mdW5jdGlvbiBIYihlLHQscil7cmV0dXJuIEdqKGUsZnVuY3Rpb24obil7dmFyIGk9K3Qsbz1pLyhuWzFdWzFdLW5bMF1bMV0pLGE9LW8qblswXVswXSxzPShpLW8qKG5bMV1bMV0rblswXVsxXSkpLzI7ZS5zY2FsZSgxNTAqbykudHJhbnNsYXRlKFthLHNdKX0scil9dmFyIFNidD0xNixrM2U9WnQoMzAqX2UpO2Z1bmN0aW9uIFdqKGUsdCl7cmV0dXJuK3Q/TjNlKGUsdCk6UjNlKGUpfWZ1bmN0aW9uIFIzZShlKXtyZXR1cm4gJG0oe3BvaW50OmZ1bmN0aW9uKHQscil7dD1lKHQsciksdGhpcy5zdHJlYW0ucG9pbnQodFswXSx0WzFdKX19KX1mdW5jdGlvbiBOM2UoZSx0KXtmdW5jdGlvbiByKG4saSxvLGEscyxsLGMsdSxoLGYscCxkLGcsXyl7dmFyIHk9Yy1uLHg9dS1pLGI9eSp5K3gqeDtpZihiPjQqdCYmZy0tKXt2YXIgUz1hK2YsQz1zK3AsUD1sK2Qsaz14cihTKlMrQypDK1AqUCksTz13bihQLz1rKSxEPVJlKFJlKFApLTEpPGxlfHxSZShvLWgpPGxlPyhvK2gpLzI6Ym4oQyxTKSxCPWUoRCxPKSxJPUJbMF0sTD1CWzFdLFI9SS1uLEY9TC1pLHo9eCpSLXkqRjsoeip6L2I+dHx8UmUoKHkqUit4KkYpL2ItLjUpPi4zfHxhKmYrcypwK2wqZDxrM2UpJiYocihuLGksbyxhLHMsbCxJLEwsRCxTLz1rLEMvPWssUCxnLF8pLF8ucG9pbnQoSSxMKSxyKEksTCxELFMsQyxQLGMsdSxoLGYscCxkLGcsXykpfX1yZXR1cm4gZnVuY3Rpb24obil7dmFyIGksbyxhLHMsbCxjLHUsaCxmLHAsZCxnLF89e3BvaW50OnksbGluZVN0YXJ0OngsbGluZUVuZDpTLHBvbHlnb25TdGFydDpmdW5jdGlvbigpe24ucG9seWdvblN0YXJ0KCksXy5saW5lU3RhcnQ9Q30scG9seWdvbkVuZDpmdW5jdGlvbigpe24ucG9seWdvbkVuZCgpLF8ubGluZVN0YXJ0PXh9fTtmdW5jdGlvbiB5KE8sRCl7Tz1lKE8sRCksbi5wb2ludChPWzBdLE9bMV0pfWZ1bmN0aW9uIHgoKXtoPU5hTixfLnBvaW50PWIsbi5saW5lU3RhcnQoKX1mdW5jdGlvbiBiKE8sRCl7dmFyIEI9b2MoW08sRF0pLEk9ZShPLEQpO3IoaCxmLHUscCxkLGcsaD1JWzBdLGY9SVsxXSx1PU8scD1CWzBdLGQ9QlsxXSxnPUJbMl0sU2J0LG4pLG4ucG9pbnQoaCxmKX1mdW5jdGlvbiBTKCl7Xy5wb2ludD15LG4ubGluZUVuZCgpfWZ1bmN0aW9uIEMoKXt4KCksXy5wb2ludD1QLF8ubGluZUVuZD1rfWZ1bmN0aW9uIFAoTyxEKXtiKGk9TyxEKSxvPWgsYT1mLHM9cCxsPWQsYz1nLF8ucG9pbnQ9Yn1mdW5jdGlvbiBrKCl7cihoLGYsdSxwLGQsZyxvLGEsaSxzLGwsYyxTYnQsbiksXy5saW5lRW5kPVMsUygpfXJldHVybiBffX12YXIgRDNlPSRtKHtwb2ludDpmdW5jdGlvbihlLHQpe3RoaXMuc3RyZWFtLnBvaW50KGUqX2UsdCpfZSl9fSk7ZnVuY3Rpb24gTzNlKGUpe3JldHVybiAkbSh7cG9pbnQ6ZnVuY3Rpb24odCxyKXt2YXIgbj1lKHQscik7cmV0dXJuIHRoaXMuc3RyZWFtLnBvaW50KG5bMF0sblsxXSl9fSl9ZnVuY3Rpb24gejNlKGUsdCxyLG4saSl7ZnVuY3Rpb24gbyhhLHMpe3JldHVybiBhKj1uLHMqPWksW3QrZSphLHItZSpzXX1yZXR1cm4gby5pbnZlcnQ9ZnVuY3Rpb24oYSxzKXtyZXR1cm5bKGEtdCkvZSpuLChyLXMpL2UqaV19LG99ZnVuY3Rpb24gTWJ0KGUsdCxyLG4saSxvKXt2YXIgYT1adChvKSxzPVh0KG8pLGw9YSplLGM9cyplLHU9YS9lLGg9cy9lLGY9KHMqci1hKnQpL2UscD0ocyp0K2EqcikvZTtmdW5jdGlvbiBkKGcsXyl7cmV0dXJuIGcqPW4sXyo9aSxbbCpnLWMqXyt0LHItYypnLWwqX119cmV0dXJuIGQuaW52ZXJ0PWZ1bmN0aW9uKGcsXyl7cmV0dXJuW24qKHUqZy1oKl8rZiksaSoocC1oKmctdSpfKV19LGR9ZnVuY3Rpb24gTWkoZSl7cmV0dXJuIHM1KGZ1bmN0aW9uKCl7cmV0dXJuIGV9KSgpfWZ1bmN0aW9uIHM1KGUpe3ZhciB0LHI9MTUwLG49NDgwLGk9MjUwLG89MCxhPTAscz0wLGw9MCxjPTAsdSxoPTAsZj0xLHA9MSxkPW51bGwsZz10NSxfPW51bGwseSx4LGIsUz1qbSxDPS41LFAsayxPLEQsQjtmdW5jdGlvbiBJKHope3JldHVybiBPKHpbMF0qX2UselsxXSpfZSl9ZnVuY3Rpb24gTCh6KXtyZXR1cm4gej1PLmludmVydCh6WzBdLHpbMV0pLHomJlt6WzBdKnZyLHpbMV0qdnJdfUkuc3RyZWFtPWZ1bmN0aW9uKHope3JldHVybiBEJiZCPT09ej9EOkQ9RDNlKE8zZSh1KShnKFAoUyhCPXopKSkpKX0sSS5wcmVjbGlwPWZ1bmN0aW9uKHope3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhnPXosZD12b2lkIDAsRigpKTpnfSxJLnBvc3RjbGlwPWZ1bmN0aW9uKHope3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhTPXosXz15PXg9Yj1udWxsLEYoKSk6U30sSS5jbGlwQW5nbGU9ZnVuY3Rpb24oeil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGc9K3o/Q2soZD16Kl9lKTooZD1udWxsLHQ1KSxGKCkpOmQqdnJ9LEkuY2xpcEV4dGVudD1mdW5jdGlvbih6KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oUz16PT1udWxsPyhfPXk9eD1iPW51bGwsam0pOkRwKF89K3pbMF1bMF0seT0relswXVsxXSx4PSt6WzFdWzBdLGI9K3pbMV1bMV0pLEYoKSk6Xz09bnVsbD9udWxsOltbXyx5XSxbeCxiXV19LEkuc2NhbGU9ZnVuY3Rpb24oeil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9K3osUigpKTpyfSxJLnRyYW5zbGF0ZT1mdW5jdGlvbih6KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj0relswXSxpPSt6WzFdLFIoKSk6W24saV19LEkuY2VudGVyPWZ1bmN0aW9uKHope3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPXpbMF0lMzYwKl9lLGE9elsxXSUzNjAqX2UsUigpKTpbbyp2cixhKnZyXX0sSS5yb3RhdGU9ZnVuY3Rpb24oeil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHM9elswXSUzNjAqX2UsbD16WzFdJTM2MCpfZSxjPXoubGVuZ3RoPjI/elsyXSUzNjAqX2U6MCxSKCkpOltzKnZyLGwqdnIsYyp2cl19LEkuYW5nbGU9ZnVuY3Rpb24oeil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGg9eiUzNjAqX2UsUigpKTpoKnZyfSxJLnJlZmxlY3RYPWZ1bmN0aW9uKHope3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhmPXo/LTE6MSxSKCkpOmY8MH0sSS5yZWZsZWN0WT1mdW5jdGlvbih6KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocD16Py0xOjEsUigpKTpwPDB9LEkucHJlY2lzaW9uPWZ1bmN0aW9uKHope3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhQPVdqKGssQz16KnopLEYoKSk6eHIoQyl9LEkuZml0RXh0ZW50PWZ1bmN0aW9uKHosVSl7cmV0dXJuIEdfKEkseixVKX0sSS5maXRTaXplPWZ1bmN0aW9uKHosVSl7cmV0dXJuIEZiKEkseixVKX0sSS5maXRXaWR0aD1mdW5jdGlvbih6LFUpe3JldHVybiBCYihJLHosVSl9LEkuZml0SGVpZ2h0PWZ1bmN0aW9uKHosVSl7cmV0dXJuIEhiKEkseixVKX07ZnVuY3Rpb24gUigpe3ZhciB6PU1idChyLDAsMCxmLHAsaCkuYXBwbHkobnVsbCx0KG8sYSkpLFU9KGg/TWJ0OnozZSkocixuLXpbMF0saS16WzFdLGYscCxoKTtyZXR1cm4gdT1RRShzLGwsYyksaz1KRSh0LFUpLE89SkUodSxrKSxQPVdqKGssQyksRigpfWZ1bmN0aW9uIEYoKXtyZXR1cm4gRD1CPW51bGwsSX1yZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gdD1lLmFwcGx5KHRoaXMsYXJndW1lbnRzKSxJLmludmVydD10LmludmVydCYmTCxSKCl9fWZ1bmN0aW9uIFZiKGUpe3ZhciB0PTAscj1CZS8zLG49czUoZSksaT1uKHQscik7cmV0dXJuIGkucGFyYWxsZWxzPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP24odD1vWzBdKl9lLHI9b1sxXSpfZSk6W3QqdnIscip2cl19LGl9ZnVuY3Rpb24gRWJ0KGUpe3ZhciB0PVp0KGUpO2Z1bmN0aW9uIHIobixpKXtyZXR1cm5bbip0LFh0KGkpL3RdfXJldHVybiByLmludmVydD1mdW5jdGlvbihuLGkpe3JldHVybltuL3Qsd24oaSp0KV19LHJ9ZnVuY3Rpb24gWWooZSx0KXt2YXIgcj1YdChlKSxuPShyK1h0KHQpKS8yO2lmKFJlKG4pPGxlKXJldHVybiBFYnQoZSk7dmFyIGk9MStyKigyKm4tciksbz14cihpKS9uO2Z1bmN0aW9uIGEocyxsKXt2YXIgYz14cihpLTIqbipYdChsKSkvbjtyZXR1cm5bYypYdChzKj1uKSxvLWMqWnQocyldfXJldHVybiBhLmludmVydD1mdW5jdGlvbihzLGwpe3ZhciBjPW8tbCx1PWJuKHMsUmUoYykpKmNhKGMpO3JldHVybiBjKm48MCYmKHUtPUJlKmNhKHMpKmNhKGMpKSxbdS9uLHduKChpLShzKnMrYypjKSpuKm4pLygyKm4pKV19LGF9ZnVuY3Rpb24gV18oKXtyZXR1cm4gVmIoWWopLnNjYWxlKDE1NS40MjQpLmNlbnRlcihbMCwzMy42NDQyXSl9ZnVuY3Rpb24gVmsoKXtyZXR1cm4gV18oKS5wYXJhbGxlbHMoWzI5LjUsNDUuNV0pLnNjYWxlKDEwNzApLnRyYW5zbGF0ZShbNDgwLDI1MF0pLnJvdGF0ZShbOTYsMF0pLmNlbnRlcihbLS42LDM4LjddKX1mdW5jdGlvbiBGM2UoZSl7dmFyIHQ9ZS5sZW5ndGg7cmV0dXJue3BvaW50OmZ1bmN0aW9uKHIsbil7Zm9yKHZhciBpPS0xOysraTx0OyllW2ldLnBvaW50KHIsbil9LHNwaGVyZTpmdW5jdGlvbigpe2Zvcih2YXIgcj0tMTsrK3I8dDspZVtyXS5zcGhlcmUoKX0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7Zm9yKHZhciByPS0xOysrcjx0OyllW3JdLmxpbmVTdGFydCgpfSxsaW5lRW5kOmZ1bmN0aW9uKCl7Zm9yKHZhciByPS0xOysrcjx0OyllW3JdLmxpbmVFbmQoKX0scG9seWdvblN0YXJ0OmZ1bmN0aW9uKCl7Zm9yKHZhciByPS0xOysrcjx0OyllW3JdLnBvbHlnb25TdGFydCgpfSxwb2x5Z29uRW5kOmZ1bmN0aW9uKCl7Zm9yKHZhciByPS0xOysrcjx0OyllW3JdLnBvbHlnb25FbmQoKX19fWZ1bmN0aW9uIFRidCgpe3ZhciBlLHQscj1WaygpLG4saT1XXygpLnJvdGF0ZShbMTU0LDBdKS5jZW50ZXIoWy0yLDU4LjVdKS5wYXJhbGxlbHMoWzU1LDY1XSksbyxhPVdfKCkucm90YXRlKFsxNTcsMF0pLmNlbnRlcihbLTMsMTkuOV0pLnBhcmFsbGVscyhbOCwxOF0pLHMsbCxjPXtwb2ludDpmdW5jdGlvbihmLHApe2w9W2YscF19fTtmdW5jdGlvbiB1KGYpe3ZhciBwPWZbMF0sZD1mWzFdO3JldHVybiBsPW51bGwsbi5wb2ludChwLGQpLGx8fChvLnBvaW50KHAsZCksbCl8fChzLnBvaW50KHAsZCksbCl9dS5pbnZlcnQ9ZnVuY3Rpb24oZil7dmFyIHA9ci5zY2FsZSgpLGQ9ci50cmFuc2xhdGUoKSxnPShmWzBdLWRbMF0pL3AsXz0oZlsxXS1kWzFdKS9wO3JldHVybihfPj0uMTImJl88LjIzNCYmZz49LS40MjUmJmc8LS4yMTQ/aTpfPj0uMTY2JiZfPC4yMzQmJmc+PS0uMjE0JiZnPC0uMTE1P2E6cikuaW52ZXJ0KGYpfSx1LnN0cmVhbT1mdW5jdGlvbihmKXtyZXR1cm4gZSYmdD09PWY/ZTplPUYzZShbci5zdHJlYW0odD1mKSxpLnN0cmVhbShmKSxhLnN0cmVhbShmKV0pfSx1LnByZWNpc2lvbj1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oci5wcmVjaXNpb24oZiksaS5wcmVjaXNpb24oZiksYS5wcmVjaXNpb24oZiksaCgpKTpyLnByZWNpc2lvbigpfSx1LnNjYWxlPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyLnNjYWxlKGYpLGkuc2NhbGUoZiouMzUpLGEuc2NhbGUoZiksdS50cmFuc2xhdGUoci50cmFuc2xhdGUoKSkpOnIuc2NhbGUoKX0sdS50cmFuc2xhdGU9ZnVuY3Rpb24oZil7aWYoIWFyZ3VtZW50cy5sZW5ndGgpcmV0dXJuIHIudHJhbnNsYXRlKCk7dmFyIHA9ci5zY2FsZSgpLGQ9K2ZbMF0sZz0rZlsxXTtyZXR1cm4gbj1yLnRyYW5zbGF0ZShmKS5jbGlwRXh0ZW50KFtbZC0uNDU1KnAsZy0uMjM4KnBdLFtkKy40NTUqcCxnKy4yMzgqcF1dKS5zdHJlYW0oYyksbz1pLnRyYW5zbGF0ZShbZC0uMzA3KnAsZysuMjAxKnBdKS5jbGlwRXh0ZW50KFtbZC0uNDI1KnArbGUsZysuMTIqcCtsZV0sW2QtLjIxNCpwLWxlLGcrLjIzNCpwLWxlXV0pLnN0cmVhbShjKSxzPWEudHJhbnNsYXRlKFtkLS4yMDUqcCxnKy4yMTIqcF0pLmNsaXBFeHRlbnQoW1tkLS4yMTQqcCtsZSxnKy4xNjYqcCtsZV0sW2QtLjExNSpwLWxlLGcrLjIzNCpwLWxlXV0pLnN0cmVhbShjKSxoKCl9LHUuZml0RXh0ZW50PWZ1bmN0aW9uKGYscCl7cmV0dXJuIEdfKHUsZixwKX0sdS5maXRTaXplPWZ1bmN0aW9uKGYscCl7cmV0dXJuIEZiKHUsZixwKX0sdS5maXRXaWR0aD1mdW5jdGlvbihmLHApe3JldHVybiBCYih1LGYscCl9LHUuZml0SGVpZ2h0PWZ1bmN0aW9uKGYscCl7cmV0dXJuIEhiKHUsZixwKX07ZnVuY3Rpb24gaCgpe3JldHVybiBlPXQ9bnVsbCx1fXJldHVybiB1LnNjYWxlKDEwNzApfWZ1bmN0aW9uIFVrKGUpe3JldHVybiBmdW5jdGlvbih0LHIpe3ZhciBuPVp0KHQpLGk9WnQociksbz1lKG4qaSk7cmV0dXJuW28qaSpYdCh0KSxvKlh0KHIpXX19ZnVuY3Rpb24gVmgoZSl7cmV0dXJuIGZ1bmN0aW9uKHQscil7dmFyIG49eHIodCp0K3IqciksaT1lKG4pLG89WHQoaSksYT1adChpKTtyZXR1cm5bYm4odCpvLG4qYSksd24obiYmcipvL24pXX19dmFyIHFrPVVrKGZ1bmN0aW9uKGUpe3JldHVybiB4cigyLygxK2UpKX0pO3FrLmludmVydD1WaChmdW5jdGlvbihlKXtyZXR1cm4gMip3bihlLzIpfSk7ZnVuY3Rpb24gQ2J0KCl7cmV0dXJuIE1pKHFrKS5zY2FsZSgxMjQuNzUpLmNsaXBBbmdsZSgxODAtLjAwMSl9dmFyIEdrPVVrKGZ1bmN0aW9uKGUpe3JldHVybihlPXVrKGUpKSYmZS9YdChlKX0pO0drLmludmVydD1WaChmdW5jdGlvbihlKXtyZXR1cm4gZX0pO2Z1bmN0aW9uIEFidCgpe3JldHVybiBNaShHaykuc2NhbGUoNzkuNDE4OCkuY2xpcEFuZ2xlKDE4MC0uMDAxKX1mdW5jdGlvbiBZXyhlLHQpe3JldHVybltlLEZfKEliKChtbit0KS8yKSldfVlfLmludmVydD1mdW5jdGlvbihlLHQpe3JldHVybltlLDIqaWMobGsodCkpLW1uXX07ZnVuY3Rpb24gUGJ0KCl7cmV0dXJuIGpqKFlfKS5zY2FsZSg5NjEvU2kpfWZ1bmN0aW9uIGpqKGUpe3ZhciB0PU1pKGUpLHI9dC5jZW50ZXIsbj10LnNjYWxlLGk9dC50cmFuc2xhdGUsbz10LmNsaXBFeHRlbnQsYT1udWxsLHMsbCxjO3Quc2NhbGU9ZnVuY3Rpb24oaCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG4oaCksdSgpKTpuKCl9LHQudHJhbnNsYXRlPWZ1bmN0aW9uKGgpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpKGgpLHUoKSk6aSgpfSx0LmNlbnRlcj1mdW5jdGlvbihoKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocihoKSx1KCkpOnIoKX0sdC5jbGlwRXh0ZW50PWZ1bmN0aW9uKGgpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhoPT1udWxsP2E9cz1sPWM9bnVsbDooYT0raFswXVswXSxzPStoWzBdWzFdLGw9K2hbMV1bMF0sYz0raFsxXVsxXSksdSgpKTphPT1udWxsP251bGw6W1thLHNdLFtsLGNdXX07ZnVuY3Rpb24gdSgpe3ZhciBoPUJlKm4oKSxmPXQoYmsodC5yb3RhdGUoKSkuaW52ZXJ0KFswLDBdKSk7cmV0dXJuIG8oYT09bnVsbD9bW2ZbMF0taCxmWzFdLWhdLFtmWzBdK2gsZlsxXStoXV06ZT09PVlfP1tbTWF0aC5tYXgoZlswXS1oLGEpLHNdLFtNYXRoLm1pbihmWzBdK2gsbCksY11dOltbYSxNYXRoLm1heChmWzFdLWgscyldLFtsLE1hdGgubWluKGZbMV0raCxjKV1dKX1yZXR1cm4gdSgpfWZ1bmN0aW9uIFdrKGUpe3JldHVybiBJYigobW4rZSkvMil9ZnVuY3Rpb24gWGooZSx0KXt2YXIgcj1adChlKSxuPWU9PT10P1h0KGUpOkZfKHIvWnQodCkpL0ZfKFdrKHQpL1drKGUpKSxpPXIqY2soV2soZSksbikvbjtpZighbilyZXR1cm4gWV87ZnVuY3Rpb24gbyhhLHMpe2k+MD9zPC1tbitsZSYmKHM9LW1uK2xlKTpzPm1uLWxlJiYocz1tbi1sZSk7dmFyIGw9aS9jayhXayhzKSxuKTtyZXR1cm5bbCpYdChuKmEpLGktbCpadChuKmEpXX1yZXR1cm4gby5pbnZlcnQ9ZnVuY3Rpb24oYSxzKXt2YXIgbD1pLXMsYz1jYShuKSp4cihhKmErbCpsKSx1PWJuKGEsUmUobCkpKmNhKGwpO3JldHVybiBsKm48MCYmKHUtPUJlKmNhKGEpKmNhKGwpKSxbdS9uLDIqaWMoY2soaS9jLDEvbikpLW1uXX0sb31mdW5jdGlvbiBJYnQoKXtyZXR1cm4gVmIoWGopLnNjYWxlKDEwOS41KS5wYXJhbGxlbHMoWzMwLDMwXSl9ZnVuY3Rpb24gal8oZSx0KXtyZXR1cm5bZSx0XX1qXy5pbnZlcnQ9al87ZnVuY3Rpb24gTGJ0KCl7cmV0dXJuIE1pKGpfKS5zY2FsZSgxNTIuNjMpfWZ1bmN0aW9uICRqKGUsdCl7dmFyIHI9WnQoZSksbj1lPT09dD9YdChlKTooci1adCh0KSkvKHQtZSksaT1yL24rZTtpZihSZShuKTxsZSlyZXR1cm4gal87ZnVuY3Rpb24gbyhhLHMpe3ZhciBsPWktcyxjPW4qYTtyZXR1cm5bbCpYdChjKSxpLWwqWnQoYyldfXJldHVybiBvLmludmVydD1mdW5jdGlvbihhLHMpe3ZhciBsPWktcyxjPWJuKGEsUmUobCkpKmNhKGwpO3JldHVybiBsKm48MCYmKGMtPUJlKmNhKGEpKmNhKGwpKSxbYy9uLGktY2EobikqeHIoYSphK2wqbCldfSxvfWZ1bmN0aW9uIGtidCgpe3JldHVybiBWYigkaikuc2NhbGUoMTMxLjE1NCkuY2VudGVyKFswLDEzLjkzODldKX12YXIgbDU9MS4zNDAyNjQsYzU9LS4wODExMDYsdTU9ODkzZS02LGg1PS4wMDM3OTYsWWs9eHIoMykvMixCM2U9MTI7ZnVuY3Rpb24gamsoZSx0KXt2YXIgcj13bihZaypYdCh0KSksbj1yKnIsaT1uKm4qbjtyZXR1cm5bZSpadChyKS8oWWsqKGw1KzMqYzUqbitpKig3KnU1KzkqaDUqbikpKSxyKihsNStjNSpuK2kqKHU1K2g1Km4pKV19amsuaW52ZXJ0PWZ1bmN0aW9uKGUsdCl7Zm9yKHZhciByPXQsbj1yKnIsaT1uKm4qbixvPTAsYSxzLGw7bzxCM2UmJihzPXIqKGw1K2M1Km4raSoodTUraDUqbikpLXQsbD1sNSszKmM1Km4raSooNyp1NSs5Kmg1Km4pLHItPWE9cy9sLG49cipyLGk9bipuKm4sIShSZShhKTx6XykpOysrbyk7cmV0dXJuW1lrKmUqKGw1KzMqYzUqbitpKig3KnU1KzkqaDUqbikpL1p0KHIpLHduKFh0KHIpL1lrKV19O2Z1bmN0aW9uIFJidCgpe3JldHVybiBNaShqaykuc2NhbGUoMTc3LjE1OCl9ZnVuY3Rpb24gWGsoZSx0KXt2YXIgcj1adCh0KSxuPVp0KGUpKnI7cmV0dXJuW3IqWHQoZSkvbixYdCh0KS9uXX1Yay5pbnZlcnQ9VmgoaWMpO2Z1bmN0aW9uIE5idCgpe3JldHVybiBNaShYaykuc2NhbGUoMTQ0LjA0OSkuY2xpcEFuZ2xlKDYwKX1mdW5jdGlvbiBEYnQoKXt2YXIgZT0xLHQ9MCxyPTAsbj0xLGk9MSxvPTAsYSxzLGw9bnVsbCxjLHUsaCxmPTEscD0xLGQ9JG0oe3BvaW50OmZ1bmN0aW9uKFMsQyl7dmFyIFA9YihbUyxDXSk7dGhpcy5zdHJlYW0ucG9pbnQoUFswXSxQWzFdKX19KSxnPWptLF8seTtmdW5jdGlvbiB4KCl7cmV0dXJuIGY9ZSpuLHA9ZSppLF89eT1udWxsLGJ9ZnVuY3Rpb24gYihTKXt2YXIgQz1TWzBdKmYsUD1TWzFdKnA7aWYobyl7dmFyIGs9UCphLUMqcztDPUMqYStQKnMsUD1rfXJldHVybltDK3QsUCtyXX1yZXR1cm4gYi5pbnZlcnQ9ZnVuY3Rpb24oUyl7dmFyIEM9U1swXS10LFA9U1sxXS1yO2lmKG8pe3ZhciBrPVAqYStDKnM7Qz1DKmEtUCpzLFA9a31yZXR1cm5bQy9mLFAvcF19LGIuc3RyZWFtPWZ1bmN0aW9uKFMpe3JldHVybiBfJiZ5PT09Uz9fOl89ZChnKHk9UykpfSxiLnBvc3RjbGlwPWZ1bmN0aW9uKFMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhnPVMsbD1jPXU9aD1udWxsLHgoKSk6Z30sYi5jbGlwRXh0ZW50PWZ1bmN0aW9uKFMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhnPVM9PW51bGw/KGw9Yz11PWg9bnVsbCxqbSk6RHAobD0rU1swXVswXSxjPStTWzBdWzFdLHU9K1NbMV1bMF0saD0rU1sxXVsxXSkseCgpKTpsPT1udWxsP251bGw6W1tsLGNdLFt1LGhdXX0sYi5zY2FsZT1mdW5jdGlvbihTKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT0rUyx4KCkpOmV9LGIudHJhbnNsYXRlPWZ1bmN0aW9uKFMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PStTWzBdLHI9K1NbMV0seCgpKTpbdCxyXX0sYi5hbmdsZT1mdW5jdGlvbihTKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz1TJTM2MCpfZSxzPVh0KG8pLGE9WnQobykseCgpKTpvKnZyfSxiLnJlZmxlY3RYPWZ1bmN0aW9uKFMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPVM/LTE6MSx4KCkpOm48MH0sYi5yZWZsZWN0WT1mdW5jdGlvbihTKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT1TPy0xOjEseCgpKTppPDB9LGIuZml0RXh0ZW50PWZ1bmN0aW9uKFMsQyl7cmV0dXJuIEdfKGIsUyxDKX0sYi5maXRTaXplPWZ1bmN0aW9uKFMsQyl7cmV0dXJuIEZiKGIsUyxDKX0sYi5maXRXaWR0aD1mdW5jdGlvbihTLEMpe3JldHVybiBCYihiLFMsQyl9LGIuZml0SGVpZ2h0PWZ1bmN0aW9uKFMsQyl7cmV0dXJuIEhiKGIsUyxDKX0sYn1mdW5jdGlvbiAkayhlLHQpe3ZhciByPXQqdCxuPXIqcjtyZXR1cm5bZSooLjg3MDctLjEzMTk3OSpyK24qKC0uMDEzNzkxK24qKC4wMDM5NzEqci0uMDAxNTI5Km4pKSksdCooMS4wMDcyMjYrciooLjAxNTA4NStuKigtLjA0NDQ3NSsuMDI4ODc0KnItLjAwNTkxNipuKSkpXX0kay5pbnZlcnQ9ZnVuY3Rpb24oZSx0KXt2YXIgcj10LG49MjUsaTtkb3t2YXIgbz1yKnIsYT1vKm87ci09aT0ociooMS4wMDcyMjYrbyooLjAxNTA4NSthKigtLjA0NDQ3NSsuMDI4ODc0Km8tLjAwNTkxNiphKSkpLXQpLygxLjAwNzIyNitvKiguMDE1MDg1KjMrYSooLS4wNDQ0NzUqNysuMDI4ODc0Kjkqby0uMDA1OTE2KjExKmEpKSl9d2hpbGUoUmUoaSk+bGUmJi0tbj4wKTtyZXR1cm5bZS8oLjg3MDcrKG89cipyKSooLS4xMzE5NzkrbyooLS4wMTM3OTErbypvKm8qKC4wMDM5NzEtLjAwMTUyOSpvKSkpKSxyXX07ZnVuY3Rpb24gT2J0KCl7cmV0dXJuIE1pKCRrKS5zY2FsZSgxNzUuMjk1KX1mdW5jdGlvbiBLayhlLHQpe3JldHVybltadCh0KSpYdChlKSxYdCh0KV19S2suaW52ZXJ0PVZoKHduKTtmdW5jdGlvbiB6YnQoKXtyZXR1cm4gTWkoS2spLnNjYWxlKDI0OS41KS5jbGlwQW5nbGUoOTArbGUpfWZ1bmN0aW9uIFprKGUsdCl7dmFyIHI9WnQodCksbj0xK1p0KGUpKnI7cmV0dXJuW3IqWHQoZSkvbixYdCh0KS9uXX1aay5pbnZlcnQ9VmgoZnVuY3Rpb24oZSl7cmV0dXJuIDIqaWMoZSl9KTtmdW5jdGlvbiBGYnQoKXtyZXR1cm4gTWkoWmspLnNjYWxlKDI1MCkuY2xpcEFuZ2xlKDE0Mil9ZnVuY3Rpb24gSmsoZSx0KXtyZXR1cm5bRl8oSWIoKG1uK3QpLzIpKSwtZV19SmsuaW52ZXJ0PWZ1bmN0aW9uKGUsdCl7cmV0dXJuWy10LDIqaWMobGsoZSkpLW1uXX07ZnVuY3Rpb24gQmJ0KCl7dmFyIGU9amooSmspLHQ9ZS5jZW50ZXIscj1lLnJvdGF0ZTtyZXR1cm4gZS5jZW50ZXI9ZnVuY3Rpb24obil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dChbLW5bMV0sblswXV0pOihuPXQoKSxbblsxXSwtblswXV0pfSxlLnJvdGF0ZT1mdW5jdGlvbihuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD9yKFtuWzBdLG5bMV0sbi5sZW5ndGg+Mj9uWzJdKzkwOjkwXSk6KG49cigpLFtuWzBdLG5bMV0sblsyXS05MF0pfSxyKFswLDAsOTBdKS5zY2FsZSgxNTkuMTU1KX1mdW5jdGlvbiBIM2UoZSx0KXtyZXR1cm4gZS5wYXJlbnQ9PT10LnBhcmVudD8xOjJ9ZnVuY3Rpb24gVjNlKGUpe3JldHVybiBlLnJlZHVjZShVM2UsMCkvZS5sZW5ndGh9ZnVuY3Rpb24gVTNlKGUsdCl7cmV0dXJuIGUrdC54fWZ1bmN0aW9uIHEzZShlKXtyZXR1cm4gMStlLnJlZHVjZShHM2UsMCl9ZnVuY3Rpb24gRzNlKGUsdCl7cmV0dXJuIE1hdGgubWF4KGUsdC55KX1mdW5jdGlvbiBXM2UoZSl7Zm9yKHZhciB0O3Q9ZS5jaGlsZHJlbjspZT10WzBdO3JldHVybiBlfWZ1bmN0aW9uIFkzZShlKXtmb3IodmFyIHQ7dD1lLmNoaWxkcmVuOyllPXRbdC5sZW5ndGgtMV07cmV0dXJuIGV9ZnVuY3Rpb24gSGJ0KCl7dmFyIGU9SDNlLHQ9MSxyPTEsbj0hMTtmdW5jdGlvbiBpKG8pe3ZhciBhLHM9MDtvLmVhY2hBZnRlcihmdW5jdGlvbihmKXt2YXIgcD1mLmNoaWxkcmVuO3A/KGYueD1WM2UocCksZi55PXEzZShwKSk6KGYueD1hP3MrPWUoZixhKTowLGYueT0wLGE9Zil9KTt2YXIgbD1XM2UobyksYz1ZM2UobyksdT1sLngtZShsLGMpLzIsaD1jLngrZShjLGwpLzI7cmV0dXJuIG8uZWFjaEFmdGVyKG4/ZnVuY3Rpb24oZil7Zi54PShmLngtby54KSp0LGYueT0oby55LWYueSkqcn06ZnVuY3Rpb24oZil7Zi54PShmLngtdSkvKGgtdSkqdCxmLnk9KDEtKG8ueT9mLnkvby55OjEpKSpyfSl9cmV0dXJuIGkuc2VwYXJhdGlvbj1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT1vLGkpOmV9LGkuc2l6ZT1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj0hMSx0PStvWzBdLHI9K29bMV0saSk6bj9udWxsOlt0LHJdfSxpLm5vZGVTaXplPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPSEwLHQ9K29bMF0scj0rb1sxXSxpKTpuP1t0LHJdOm51bGx9LGl9ZnVuY3Rpb24gajNlKGUpe3ZhciB0PTAscj1lLmNoaWxkcmVuLG49ciYmci5sZW5ndGg7aWYoIW4pdD0xO2Vsc2UgZm9yKDstLW4+PTA7KXQrPXJbbl0udmFsdWU7ZS52YWx1ZT10fWZ1bmN0aW9uIFZidCgpe3JldHVybiB0aGlzLmVhY2hBZnRlcihqM2UpfWZ1bmN0aW9uIFVidChlKXt2YXIgdD10aGlzLHIsbj1bdF0saSxvLGE7ZG8gZm9yKHI9bi5yZXZlcnNlKCksbj1bXTt0PXIucG9wKCk7KWlmKGUodCksaT10LmNoaWxkcmVuLGkpZm9yKG89MCxhPWkubGVuZ3RoO288YTsrK28pbi5wdXNoKGlbb10pO3doaWxlKG4ubGVuZ3RoKTtyZXR1cm4gdGhpc31mdW5jdGlvbiBxYnQoZSl7Zm9yKHZhciB0PXRoaXMscj1bdF0sbixpO3Q9ci5wb3AoKTspaWYoZSh0KSxuPXQuY2hpbGRyZW4sbilmb3IoaT1uLmxlbmd0aC0xO2k+PTA7LS1pKXIucHVzaChuW2ldKTtyZXR1cm4gdGhpc31mdW5jdGlvbiBHYnQoZSl7Zm9yKHZhciB0PXRoaXMscj1bdF0sbj1bXSxpLG8sYTt0PXIucG9wKCk7KWlmKG4ucHVzaCh0KSxpPXQuY2hpbGRyZW4saSlmb3Iobz0wLGE9aS5sZW5ndGg7bzxhOysrbylyLnB1c2goaVtvXSk7Zm9yKDt0PW4ucG9wKCk7KWUodCk7cmV0dXJuIHRoaXN9ZnVuY3Rpb24gV2J0KGUpe3JldHVybiB0aGlzLmVhY2hBZnRlcihmdW5jdGlvbih0KXtmb3IodmFyIHI9K2UodC5kYXRhKXx8MCxuPXQuY2hpbGRyZW4saT1uJiZuLmxlbmd0aDstLWk+PTA7KXIrPW5baV0udmFsdWU7dC52YWx1ZT1yfSl9ZnVuY3Rpb24gWWJ0KGUpe3JldHVybiB0aGlzLmVhY2hCZWZvcmUoZnVuY3Rpb24odCl7dC5jaGlsZHJlbiYmdC5jaGlsZHJlbi5zb3J0KGUpfSl9ZnVuY3Rpb24gamJ0KGUpe2Zvcih2YXIgdD10aGlzLHI9WDNlKHQsZSksbj1bdF07dCE9PXI7KXQ9dC5wYXJlbnQsbi5wdXNoKHQpO2Zvcih2YXIgaT1uLmxlbmd0aDtlIT09cjspbi5zcGxpY2UoaSwwLGUpLGU9ZS5wYXJlbnQ7cmV0dXJuIG59ZnVuY3Rpb24gWDNlKGUsdCl7aWYoZT09PXQpcmV0dXJuIGU7dmFyIHI9ZS5hbmNlc3RvcnMoKSxuPXQuYW5jZXN0b3JzKCksaT1udWxsO2ZvcihlPXIucG9wKCksdD1uLnBvcCgpO2U9PT10OylpPWUsZT1yLnBvcCgpLHQ9bi5wb3AoKTtyZXR1cm4gaX1mdW5jdGlvbiBYYnQoKXtmb3IodmFyIGU9dGhpcyx0PVtlXTtlPWUucGFyZW50Oyl0LnB1c2goZSk7cmV0dXJuIHR9ZnVuY3Rpb24gJGJ0KCl7dmFyIGU9W107cmV0dXJuIHRoaXMuZWFjaChmdW5jdGlvbih0KXtlLnB1c2godCl9KSxlfWZ1bmN0aW9uIEtidCgpe3ZhciBlPVtdO3JldHVybiB0aGlzLmVhY2hCZWZvcmUoZnVuY3Rpb24odCl7dC5jaGlsZHJlbnx8ZS5wdXNoKHQpfSksZX1mdW5jdGlvbiBaYnQoKXt2YXIgZT10aGlzLHQ9W107cmV0dXJuIGUuZWFjaChmdW5jdGlvbihyKXtyIT09ZSYmdC5wdXNoKHtzb3VyY2U6ci5wYXJlbnQsdGFyZ2V0OnJ9KX0pLHR9ZnVuY3Rpb24gZjUoZSx0KXt2YXIgcj1uZXcgS20oZSksbj0rZS52YWx1ZSYmKHIudmFsdWU9ZS52YWx1ZSksaSxvPVtyXSxhLHMsbCxjO2Zvcih0PT1udWxsJiYodD1LM2UpO2k9by5wb3AoKTspaWYobiYmKGkudmFsdWU9K2kuZGF0YS52YWx1ZSksKHM9dChpLmRhdGEpKSYmKGM9cy5sZW5ndGgpKWZvcihpLmNoaWxkcmVuPW5ldyBBcnJheShjKSxsPWMtMTtsPj0wOy0tbClvLnB1c2goYT1pLmNoaWxkcmVuW2xdPW5ldyBLbShzW2xdKSksYS5wYXJlbnQ9aSxhLmRlcHRoPWkuZGVwdGgrMTtyZXR1cm4gci5lYWNoQmVmb3JlKEtqKX1mdW5jdGlvbiAkM2UoKXtyZXR1cm4gZjUodGhpcykuZWFjaEJlZm9yZShaM2UpfWZ1bmN0aW9uIEszZShlKXtyZXR1cm4gZS5jaGlsZHJlbn1mdW5jdGlvbiBaM2UoZSl7ZS5kYXRhPWUuZGF0YS5kYXRhfWZ1bmN0aW9uIEtqKGUpe3ZhciB0PTA7ZG8gZS5oZWlnaHQ9dDt3aGlsZSgoZT1lLnBhcmVudCkmJmUuaGVpZ2h0PCsrdCl9ZnVuY3Rpb24gS20oZSl7dGhpcy5kYXRhPWUsdGhpcy5kZXB0aD10aGlzLmhlaWdodD0wLHRoaXMucGFyZW50PW51bGx9S20ucHJvdG90eXBlPWY1LnByb3RvdHlwZT17Y29uc3RydWN0b3I6S20sY291bnQ6VmJ0LGVhY2g6VWJ0LGVhY2hBZnRlcjpHYnQsZWFjaEJlZm9yZTpxYnQsc3VtOldidCxzb3J0OllidCxwYXRoOmpidCxhbmNlc3RvcnM6WGJ0LGRlc2NlbmRhbnRzOiRidCxsZWF2ZXM6S2J0LGxpbmtzOlpidCxjb3B5OiQzZX07dmFyIEpidD1BcnJheS5wcm90b3R5cGUuc2xpY2U7ZnVuY3Rpb24gUWJ0KGUpe2Zvcih2YXIgdD1lLmxlbmd0aCxyLG47dDspbj1NYXRoLnJhbmRvbSgpKnQtLXwwLHI9ZVt0XSxlW3RdPWVbbl0sZVtuXT1yO3JldHVybiBlfWZ1bmN0aW9uIHQ4KGUpe2Zvcih2YXIgdD0wLHI9KGU9UWJ0KEpidC5jYWxsKGUpKSkubGVuZ3RoLG49W10saSxvO3Q8cjspaT1lW3RdLG8mJnQydChvLGkpPysrdDoobz1RM2Uobj1KM2UobixpKSksdD0wKTtyZXR1cm4gb31mdW5jdGlvbiBKM2UoZSx0KXt2YXIgcixuO2lmKFpqKHQsZSkpcmV0dXJuW3RdO2ZvcihyPTA7cjxlLmxlbmd0aDsrK3IpaWYoUWsodCxlW3JdKSYmWmoocDUoZVtyXSx0KSxlKSlyZXR1cm5bZVtyXSx0XTtmb3Iocj0wO3I8ZS5sZW5ndGgtMTsrK3IpZm9yKG49cisxO248ZS5sZW5ndGg7KytuKWlmKFFrKHA1KGVbcl0sZVtuXSksdCkmJlFrKHA1KGVbcl0sdCksZVtuXSkmJlFrKHA1KGVbbl0sdCksZVtyXSkmJlpqKGUydChlW3JdLGVbbl0sdCksZSkpcmV0dXJuW2Vbcl0sZVtuXSx0XTt0aHJvdyBuZXcgRXJyb3J9ZnVuY3Rpb24gUWsoZSx0KXt2YXIgcj1lLnItdC5yLG49dC54LWUueCxpPXQueS1lLnk7cmV0dXJuIHI8MHx8cipyPG4qbitpKml9ZnVuY3Rpb24gdDJ0KGUsdCl7dmFyIHI9ZS5yLXQucisxZS02LG49dC54LWUueCxpPXQueS1lLnk7cmV0dXJuIHI+MCYmcipyPm4qbitpKml9ZnVuY3Rpb24gWmooZSx0KXtmb3IodmFyIHI9MDtyPHQubGVuZ3RoOysrcilpZighdDJ0KGUsdFtyXSkpcmV0dXJuITE7cmV0dXJuITB9ZnVuY3Rpb24gUTNlKGUpe3N3aXRjaChlLmxlbmd0aCl7Y2FzZSAxOnJldHVybiB0TWUoZVswXSk7Y2FzZSAyOnJldHVybiBwNShlWzBdLGVbMV0pO2Nhc2UgMzpyZXR1cm4gZTJ0KGVbMF0sZVsxXSxlWzJdKX19ZnVuY3Rpb24gdE1lKGUpe3JldHVybnt4OmUueCx5OmUueSxyOmUucn19ZnVuY3Rpb24gcDUoZSx0KXt2YXIgcj1lLngsbj1lLnksaT1lLnIsbz10LngsYT10Lnkscz10LnIsbD1vLXIsYz1hLW4sdT1zLWksaD1NYXRoLnNxcnQobCpsK2MqYyk7cmV0dXJue3g6KHIrbytsL2gqdSkvMix5OihuK2ErYy9oKnUpLzIscjooaCtpK3MpLzJ9fWZ1bmN0aW9uIGUydChlLHQscil7dmFyIG49ZS54LGk9ZS55LG89ZS5yLGE9dC54LHM9dC55LGw9dC5yLGM9ci54LHU9ci55LGg9ci5yLGY9bi1hLHA9bi1jLGQ9aS1zLGc9aS11LF89bC1vLHk9aC1vLHg9bipuK2kqaS1vKm8sYj14LWEqYS1zKnMrbCpsLFM9eC1jKmMtdSp1K2gqaCxDPXAqZC1mKmcsUD0oZCpTLWcqYikvKEMqMiktbixrPShnKl8tZCp5KS9DLE89KHAqYi1mKlMpLyhDKjIpLWksRD0oZip5LXAqXykvQyxCPWsqaytEKkQtMSxJPTIqKG8rUCprK08qRCksTD1QKlArTypPLW8qbyxSPS0oQj8oSStNYXRoLnNxcnQoSSpJLTQqQipMKSkvKDIqQik6TC9JKTtyZXR1cm57eDpuK1AraypSLHk6aStPK0QqUixyOlJ9fWZ1bmN0aW9uIHIydChlLHQscil7dmFyIG49ZS54LXQueCxpLG8sYT1lLnktdC55LHMsbCxjPW4qbithKmE7Yz8obz10LnIrci5yLG8qPW8sbD1lLnIrci5yLGwqPWwsbz5sPyhpPShjK2wtbykvKDIqYykscz1NYXRoLnNxcnQoTWF0aC5tYXgoMCxsL2MtaSppKSksci54PWUueC1pKm4tcyphLHIueT1lLnktaSphK3Mqbik6KGk9KGMrby1sKS8oMipjKSxzPU1hdGguc3FydChNYXRoLm1heCgwLG8vYy1pKmkpKSxyLng9dC54K2kqbi1zKmEsci55PXQueStpKmErcypuKSk6KHIueD10Lngrci5yLHIueT10LnkpfWZ1bmN0aW9uIG4ydChlLHQpe3ZhciByPWUucit0LnItMWUtNixuPXQueC1lLngsaT10LnktZS55O3JldHVybiByPjAmJnIqcj5uKm4raSppfWZ1bmN0aW9uIGkydChlKXt2YXIgdD1lLl8scj1lLm5leHQuXyxuPXQucityLnIsaT0odC54KnIucityLngqdC5yKS9uLG89KHQueSpyLnIrci55KnQucikvbjtyZXR1cm4gaSppK28qb31mdW5jdGlvbiBlOChlKXt0aGlzLl89ZSx0aGlzLm5leHQ9bnVsbCx0aGlzLnByZXZpb3VzPW51bGx9ZnVuY3Rpb24gSmooZSl7aWYoIShpPWUubGVuZ3RoKSlyZXR1cm4gMDt2YXIgdCxyLG4saSxvLGEscyxsLGMsdSxoO2lmKHQ9ZVswXSx0Lng9MCx0Lnk9MCwhKGk+MSkpcmV0dXJuIHQucjtpZihyPWVbMV0sdC54PS1yLnIsci54PXQucixyLnk9MCwhKGk+MikpcmV0dXJuIHQucityLnI7cjJ0KHIsdCxuPWVbMl0pLHQ9bmV3IGU4KHQpLHI9bmV3IGU4KHIpLG49bmV3IGU4KG4pLHQubmV4dD1uLnByZXZpb3VzPXIsci5uZXh0PXQucHJldmlvdXM9bixuLm5leHQ9ci5wcmV2aW91cz10O3Q6Zm9yKHM9MztzPGk7KytzKXtyMnQodC5fLHIuXyxuPWVbc10pLG49bmV3IGU4KG4pLGw9ci5uZXh0LGM9dC5wcmV2aW91cyx1PXIuXy5yLGg9dC5fLnI7ZG8gaWYodTw9aCl7aWYobjJ0KGwuXyxuLl8pKXtyPWwsdC5uZXh0PXIsci5wcmV2aW91cz10LC0tcztjb250aW51ZSB0fXUrPWwuXy5yLGw9bC5uZXh0fWVsc2V7aWYobjJ0KGMuXyxuLl8pKXt0PWMsdC5uZXh0PXIsci5wcmV2aW91cz10LC0tcztjb250aW51ZSB0fWgrPWMuXy5yLGM9Yy5wcmV2aW91c313aGlsZShsIT09Yy5uZXh0KTtmb3Iobi5wcmV2aW91cz10LG4ubmV4dD1yLHQubmV4dD1yLnByZXZpb3VzPXI9bixvPWkydCh0KTsobj1uLm5leHQpIT09cjspKGE9aTJ0KG4pKTxvJiYodD1uLG89YSk7cj10Lm5leHR9Zm9yKHQ9W3IuX10sbj1yOyhuPW4ubmV4dCkhPT1yOyl0LnB1c2gobi5fKTtmb3Iobj10OCh0KSxzPTA7czxpOysrcyl0PWVbc10sdC54LT1uLngsdC55LT1uLnk7cmV0dXJuIG4ucn1mdW5jdGlvbiBvMnQoZSl7cmV0dXJuIEpqKGUpLGV9ZnVuY3Rpb24gYTJ0KGUpe3JldHVybiBlPT1udWxsP251bGw6VWIoZSl9ZnVuY3Rpb24gVWIoZSl7aWYodHlwZW9mIGUhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yO3JldHVybiBlfWZ1bmN0aW9uIE9wKCl7cmV0dXJuIDB9ZnVuY3Rpb24gWm0oZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fWZ1bmN0aW9uIGVNZShlKXtyZXR1cm4gTWF0aC5zcXJ0KGUudmFsdWUpfWZ1bmN0aW9uIGMydCgpe3ZhciBlPW51bGwsdD0xLHI9MSxuPU9wO2Z1bmN0aW9uIGkobyl7cmV0dXJuIG8ueD10LzIsby55PXIvMixlP28uZWFjaEJlZm9yZShzMnQoZSkpLmVhY2hBZnRlcihRaihuLC41KSkuZWFjaEJlZm9yZShsMnQoMSkpOm8uZWFjaEJlZm9yZShzMnQoZU1lKSkuZWFjaEFmdGVyKFFqKE9wLDEpKS5lYWNoQWZ0ZXIoUWoobixvLnIvTWF0aC5taW4odCxyKSkpLmVhY2hCZWZvcmUobDJ0KE1hdGgubWluKHQscikvKDIqby5yKSkpLG99cmV0dXJuIGkucmFkaXVzPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPWEydChvKSxpKTplfSxpLnNpemU9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9K29bMF0scj0rb1sxXSxpKTpbdCxyXX0saS5wYWRkaW5nPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPXR5cGVvZiBvPT0iZnVuY3Rpb24iP286Wm0oK28pLGkpOm59LGl9ZnVuY3Rpb24gczJ0KGUpe3JldHVybiBmdW5jdGlvbih0KXt0LmNoaWxkcmVufHwodC5yPU1hdGgubWF4KDAsK2UodCl8fDApKX19ZnVuY3Rpb24gUWooZSx0KXtyZXR1cm4gZnVuY3Rpb24ocil7aWYobj1yLmNoaWxkcmVuKXt2YXIgbixpLG89bi5sZW5ndGgsYT1lKHIpKnR8fDAscztpZihhKWZvcihpPTA7aTxvOysraSluW2ldLnIrPWE7aWYocz1KaihuKSxhKWZvcihpPTA7aTxvOysraSluW2ldLnItPWE7ci5yPXMrYX19fWZ1bmN0aW9uIGwydChlKXtyZXR1cm4gZnVuY3Rpb24odCl7dmFyIHI9dC5wYXJlbnQ7dC5yKj1lLHImJih0Lng9ci54K2UqdC54LHQueT1yLnkrZSp0LnkpfX1mdW5jdGlvbiByOChlKXtlLngwPU1hdGgucm91bmQoZS54MCksZS55MD1NYXRoLnJvdW5kKGUueTApLGUueDE9TWF0aC5yb3VuZChlLngxKSxlLnkxPU1hdGgucm91bmQoZS55MSl9ZnVuY3Rpb24gVWgoZSx0LHIsbixpKXtmb3IodmFyIG89ZS5jaGlsZHJlbixhLHM9LTEsbD1vLmxlbmd0aCxjPWUudmFsdWUmJihuLXQpL2UudmFsdWU7KytzPGw7KWE9b1tzXSxhLnkwPXIsYS55MT1pLGEueDA9dCxhLngxPXQrPWEudmFsdWUqY31mdW5jdGlvbiB1MnQoKXt2YXIgZT0xLHQ9MSxyPTAsbj0hMTtmdW5jdGlvbiBpKGEpe3ZhciBzPWEuaGVpZ2h0KzE7cmV0dXJuIGEueDA9YS55MD1yLGEueDE9ZSxhLnkxPXQvcyxhLmVhY2hCZWZvcmUobyh0LHMpKSxuJiZhLmVhY2hCZWZvcmUocjgpLGF9ZnVuY3Rpb24gbyhhLHMpe3JldHVybiBmdW5jdGlvbihsKXtsLmNoaWxkcmVuJiZVaChsLGwueDAsYSoobC5kZXB0aCsxKS9zLGwueDEsYSoobC5kZXB0aCsyKS9zKTt2YXIgYz1sLngwLHU9bC55MCxoPWwueDEtcixmPWwueTEtcjtoPGMmJihjPWg9KGMraCkvMiksZjx1JiYodT1mPSh1K2YpLzIpLGwueDA9YyxsLnkwPXUsbC54MT1oLGwueTE9Zn19cmV0dXJuIGkucm91bmQ9ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49ISFhLGkpOm59LGkuc2l6ZT1mdW5jdGlvbihhKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT0rYVswXSx0PSthWzFdLGkpOltlLHRdfSxpLnBhZGRpbmc9ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9K2EsaSk6cn0saX12YXIgaDJ0PSIkIixyTWU9e2RlcHRoOi0xfSxmMnQ9e307ZnVuY3Rpb24gbk1lKGUpe3JldHVybiBlLmlkfWZ1bmN0aW9uIGlNZShlKXtyZXR1cm4gZS5wYXJlbnRJZH1mdW5jdGlvbiBwMnQoKXt2YXIgZT1uTWUsdD1pTWU7ZnVuY3Rpb24gcihuKXt2YXIgaSxvLGE9bi5sZW5ndGgscyxsLGMsdT1uZXcgQXJyYXkoYSksaCxmLHA9e307Zm9yKG89MDtvPGE7KytvKWk9bltvXSxjPXVbb109bmV3IEttKGkpLChoPWUoaSxvLG4pKSE9bnVsbCYmKGgrPSIiKSYmKGY9aDJ0KyhjLmlkPWgpLHBbZl09ZiBpbiBwP2YydDpjKTtmb3Iobz0wO288YTsrK28paWYoYz11W29dLGg9dChuW29dLG8sbiksaD09bnVsbHx8IShoKz0iIikpe2lmKHMpdGhyb3cgbmV3IEVycm9yKCJtdWx0aXBsZSByb290cyIpO3M9Y31lbHNle2lmKGw9cFtoMnQraF0sIWwpdGhyb3cgbmV3IEVycm9yKCJtaXNzaW5nOiAiK2gpO2lmKGw9PT1mMnQpdGhyb3cgbmV3IEVycm9yKCJhbWJpZ3VvdXM6ICIraCk7bC5jaGlsZHJlbj9sLmNoaWxkcmVuLnB1c2goYyk6bC5jaGlsZHJlbj1bY10sYy5wYXJlbnQ9bH1pZighcyl0aHJvdyBuZXcgRXJyb3IoIm5vIHJvb3QiKTtpZihzLnBhcmVudD1yTWUscy5lYWNoQmVmb3JlKGZ1bmN0aW9uKGQpe2QuZGVwdGg9ZC5wYXJlbnQuZGVwdGgrMSwtLWF9KS5lYWNoQmVmb3JlKEtqKSxzLnBhcmVudD1udWxsLGE+MCl0aHJvdyBuZXcgRXJyb3IoImN5Y2xlIik7cmV0dXJuIHN9cmV0dXJuIHIuaWQ9ZnVuY3Rpb24obil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9VWIobikscik6ZX0sci5wYXJlbnRJZD1mdW5jdGlvbihuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD1VYihuKSxyKTp0fSxyfWZ1bmN0aW9uIG9NZShlLHQpe3JldHVybiBlLnBhcmVudD09PXQucGFyZW50PzE6Mn1mdW5jdGlvbiB0WChlKXt2YXIgdD1lLmNoaWxkcmVuO3JldHVybiB0P3RbMF06ZS50fWZ1bmN0aW9uIGVYKGUpe3ZhciB0PWUuY2hpbGRyZW47cmV0dXJuIHQ/dFt0Lmxlbmd0aC0xXTplLnR9ZnVuY3Rpb24gYU1lKGUsdCxyKXt2YXIgbj1yLyh0LmktZS5pKTt0LmMtPW4sdC5zKz1yLGUuYys9bix0LnorPXIsdC5tKz1yfWZ1bmN0aW9uIHNNZShlKXtmb3IodmFyIHQ9MCxyPTAsbj1lLmNoaWxkcmVuLGk9bi5sZW5ndGgsbzstLWk+PTA7KW89bltpXSxvLnorPXQsby5tKz10LHQrPW8ucysocis9by5jKX1mdW5jdGlvbiBsTWUoZSx0LHIpe3JldHVybiBlLmEucGFyZW50PT09dC5wYXJlbnQ/ZS5hOnJ9ZnVuY3Rpb24gbjgoZSx0KXt0aGlzLl89ZSx0aGlzLnBhcmVudD1udWxsLHRoaXMuY2hpbGRyZW49bnVsbCx0aGlzLkE9bnVsbCx0aGlzLmE9dGhpcyx0aGlzLno9MCx0aGlzLm09MCx0aGlzLmM9MCx0aGlzLnM9MCx0aGlzLnQ9bnVsbCx0aGlzLmk9dH1uOC5wcm90b3R5cGU9T2JqZWN0LmNyZWF0ZShLbS5wcm90b3R5cGUpO2Z1bmN0aW9uIGNNZShlKXtmb3IodmFyIHQ9bmV3IG44KGUsMCkscixuPVt0XSxpLG8sYSxzO3I9bi5wb3AoKTspaWYobz1yLl8uY2hpbGRyZW4pZm9yKHIuY2hpbGRyZW49bmV3IEFycmF5KHM9by5sZW5ndGgpLGE9cy0xO2E+PTA7LS1hKW4ucHVzaChpPXIuY2hpbGRyZW5bYV09bmV3IG44KG9bYV0sYSkpLGkucGFyZW50PXI7cmV0dXJuKHQucGFyZW50PW5ldyBuOChudWxsLDApKS5jaGlsZHJlbj1bdF0sdH1mdW5jdGlvbiBkMnQoKXt2YXIgZT1vTWUsdD0xLHI9MSxuPW51bGw7ZnVuY3Rpb24gaShjKXt2YXIgdT1jTWUoYyk7aWYodS5lYWNoQWZ0ZXIobyksdS5wYXJlbnQubT0tdS56LHUuZWFjaEJlZm9yZShhKSxuKWMuZWFjaEJlZm9yZShsKTtlbHNle3ZhciBoPWMsZj1jLHA9YztjLmVhY2hCZWZvcmUoZnVuY3Rpb24oeCl7eC54PGgueCYmKGg9eCkseC54PmYueCYmKGY9eCkseC5kZXB0aD5wLmRlcHRoJiYocD14KX0pO3ZhciBkPWg9PT1mPzE6ZShoLGYpLzIsZz1kLWgueCxfPXQvKGYueCtkK2cpLHk9ci8ocC5kZXB0aHx8MSk7Yy5lYWNoQmVmb3JlKGZ1bmN0aW9uKHgpe3gueD0oeC54K2cpKl8seC55PXguZGVwdGgqeX0pfXJldHVybiBjfWZ1bmN0aW9uIG8oYyl7dmFyIHU9Yy5jaGlsZHJlbixoPWMucGFyZW50LmNoaWxkcmVuLGY9Yy5pP2hbYy5pLTFdOm51bGw7aWYodSl7c01lKGMpO3ZhciBwPSh1WzBdLnordVt1Lmxlbmd0aC0xXS56KS8yO2Y/KGMuej1mLnorZShjLl8sZi5fKSxjLm09Yy56LXApOmMuej1wfWVsc2UgZiYmKGMuej1mLnorZShjLl8sZi5fKSk7Yy5wYXJlbnQuQT1zKGMsZixjLnBhcmVudC5BfHxoWzBdKX1mdW5jdGlvbiBhKGMpe2MuXy54PWMueitjLnBhcmVudC5tLGMubSs9Yy5wYXJlbnQubX1mdW5jdGlvbiBzKGMsdSxoKXtpZih1KXtmb3IodmFyIGY9YyxwPWMsZD11LGc9Zi5wYXJlbnQuY2hpbGRyZW5bMF0sXz1mLm0seT1wLm0seD1kLm0sYj1nLm0sUztkPWVYKGQpLGY9dFgoZiksZCYmZjspZz10WChnKSxwPWVYKHApLHAuYT1jLFM9ZC56K3gtZi56LV8rZShkLl8sZi5fKSxTPjAmJihhTWUobE1lKGQsYyxoKSxjLFMpLF8rPVMseSs9UykseCs9ZC5tLF8rPWYubSxiKz1nLm0seSs9cC5tO2QmJiFlWChwKSYmKHAudD1kLHAubSs9eC15KSxmJiYhdFgoZykmJihnLnQ9ZixnLm0rPV8tYixoPWMpfXJldHVybiBofWZ1bmN0aW9uIGwoYyl7Yy54Kj10LGMueT1jLmRlcHRoKnJ9cmV0dXJuIGkuc2VwYXJhdGlvbj1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT1jLGkpOmV9LGkuc2l6ZT1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj0hMSx0PStjWzBdLHI9K2NbMV0saSk6bj9udWxsOlt0LHJdfSxpLm5vZGVTaXplPWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPSEwLHQ9K2NbMF0scj0rY1sxXSxpKTpuP1t0LHJdOm51bGx9LGl9ZnVuY3Rpb24gSm0oZSx0LHIsbixpKXtmb3IodmFyIG89ZS5jaGlsZHJlbixhLHM9LTEsbD1vLmxlbmd0aCxjPWUudmFsdWUmJihpLXIpL2UudmFsdWU7KytzPGw7KWE9b1tzXSxhLngwPXQsYS54MT1uLGEueTA9cixhLnkxPXIrPWEudmFsdWUqY312YXIgclg9KDErTWF0aC5zcXJ0KDUpKS8yO2Z1bmN0aW9uIG5YKGUsdCxyLG4saSxvKXtmb3IodmFyIGE9W10scz10LmNoaWxkcmVuLGwsYyx1PTAsaD0wLGY9cy5sZW5ndGgscCxkLGc9dC52YWx1ZSxfLHkseCxiLFMsQyxQO3U8Zjspe3A9aS1yLGQ9by1uO2RvIF89c1toKytdLnZhbHVlO3doaWxlKCFfJiZoPGYpO2Zvcih5PXg9XyxDPU1hdGgubWF4KGQvcCxwL2QpLyhnKmUpLFA9XypfKkMsUz1NYXRoLm1heCh4L1AsUC95KTtoPGY7KytoKXtpZihfKz1jPXNbaF0udmFsdWUsYzx5JiYoeT1jKSxjPngmJih4PWMpLFA9XypfKkMsYj1NYXRoLm1heCh4L1AsUC95KSxiPlMpe18tPWM7YnJlYWt9Uz1ifWEucHVzaChsPXt2YWx1ZTpfLGRpY2U6cDxkLGNoaWxkcmVuOnMuc2xpY2UodSxoKX0pLGwuZGljZT9VaChsLHIsbixpLGc/bis9ZCpfL2c6byk6Sm0obCxyLG4sZz9yKz1wKl8vZzppLG8pLGctPV8sdT1ofXJldHVybiBhfXZhciBpOD1mdW5jdGlvbiBlKHQpe2Z1bmN0aW9uIHIobixpLG8sYSxzKXtuWCh0LG4saSxvLGEscyl9cmV0dXJuIHIucmF0aW89ZnVuY3Rpb24obil7cmV0dXJuIGUoKG49K24pPjE/bjoxKX0scn0oclgpO2Z1bmN0aW9uIG0ydCgpe3ZhciBlPWk4LHQ9ITEscj0xLG49MSxpPVswXSxvPU9wLGE9T3Ascz1PcCxsPU9wLGM9T3A7ZnVuY3Rpb24gdShmKXtyZXR1cm4gZi54MD1mLnkwPTAsZi54MT1yLGYueTE9bixmLmVhY2hCZWZvcmUoaCksaT1bMF0sdCYmZi5lYWNoQmVmb3JlKHI4KSxmfWZ1bmN0aW9uIGgoZil7dmFyIHA9aVtmLmRlcHRoXSxkPWYueDArcCxnPWYueTArcCxfPWYueDEtcCx5PWYueTEtcDtfPGQmJihkPV89KGQrXykvMikseTxnJiYoZz15PShnK3kpLzIpLGYueDA9ZCxmLnkwPWcsZi54MT1fLGYueTE9eSxmLmNoaWxkcmVuJiYocD1pW2YuZGVwdGgrMV09byhmKS8yLGQrPWMoZiktcCxnKz1hKGYpLXAsXy09cyhmKS1wLHktPWwoZiktcCxfPGQmJihkPV89KGQrXykvMikseTxnJiYoZz15PShnK3kpLzIpLGUoZixkLGcsXyx5KSl9cmV0dXJuIHUucm91bmQ9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9ISFmLHUpOnR9LHUuc2l6ZT1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj0rZlswXSxuPStmWzFdLHUpOltyLG5dfSx1LnRpbGU9ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9VWIoZiksdSk6ZX0sdS5wYWRkaW5nPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3UucGFkZGluZ0lubmVyKGYpLnBhZGRpbmdPdXRlcihmKTp1LnBhZGRpbmdJbm5lcigpfSx1LnBhZGRpbmdJbm5lcj1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz10eXBlb2YgZj09ImZ1bmN0aW9uIj9mOlptKCtmKSx1KTpvfSx1LnBhZGRpbmdPdXRlcj1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD91LnBhZGRpbmdUb3AoZikucGFkZGluZ1JpZ2h0KGYpLnBhZGRpbmdCb3R0b20oZikucGFkZGluZ0xlZnQoZik6dS5wYWRkaW5nVG9wKCl9LHUucGFkZGluZ1RvcD1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYT10eXBlb2YgZj09ImZ1bmN0aW9uIj9mOlptKCtmKSx1KTphfSx1LnBhZGRpbmdSaWdodD1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocz10eXBlb2YgZj09ImZ1bmN0aW9uIj9mOlptKCtmKSx1KTpzfSx1LnBhZGRpbmdCb3R0b209ZnVuY3Rpb24oZil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGw9dHlwZW9mIGY9PSJmdW5jdGlvbiI/ZjpabSgrZiksdSk6bH0sdS5wYWRkaW5nTGVmdD1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYz10eXBlb2YgZj09ImZ1bmN0aW9uIj9mOlptKCtmKSx1KTpjfSx1fWZ1bmN0aW9uIGcydChlLHQscixuLGkpe3ZhciBvPWUuY2hpbGRyZW4sYSxzPW8ubGVuZ3RoLGwsYz1uZXcgQXJyYXkocysxKTtmb3IoY1swXT1sPWE9MDthPHM7KythKWNbYSsxXT1sKz1vW2FdLnZhbHVlO3UoMCxzLGUudmFsdWUsdCxyLG4saSk7ZnVuY3Rpb24gdShoLGYscCxkLGcsXyx5KXtpZihoPj1mLTEpe3ZhciB4PW9baF07eC54MD1kLHgueTA9Zyx4LngxPV8seC55MT15O3JldHVybn1mb3IodmFyIGI9Y1toXSxTPXAvMitiLEM9aCsxLFA9Zi0xO0M8UDspe3ZhciBrPUMrUD4+PjE7Y1trXTxTP0M9aysxOlA9a31TLWNbQy0xXTxjW0NdLVMmJmgrMTxDJiYtLUM7dmFyIE89Y1tDXS1iLEQ9cC1PO2lmKF8tZD55LWcpe3ZhciBCPShkKkQrXypPKS9wO3UoaCxDLE8sZCxnLEIseSksdShDLGYsRCxCLGcsXyx5KX1lbHNle3ZhciBJPShnKkQreSpPKS9wO3UoaCxDLE8sZCxnLF8sSSksdShDLGYsRCxkLEksXyx5KX19fWZ1bmN0aW9uIF8ydChlLHQscixuLGkpeyhlLmRlcHRoJjE/Sm06VWgpKGUsdCxyLG4saSl9dmFyIHkydD1mdW5jdGlvbiBlKHQpe2Z1bmN0aW9uIHIobixpLG8sYSxzKXtpZigobD1uLl9zcXVhcmlmeSkmJmwucmF0aW89PT10KWZvcih2YXIgbCxjLHUsaCxmPS0xLHAsZD1sLmxlbmd0aCxnPW4udmFsdWU7KytmPGQ7KXtmb3IoYz1sW2ZdLHU9Yy5jaGlsZHJlbixoPWMudmFsdWU9MCxwPXUubGVuZ3RoO2g8cDsrK2gpYy52YWx1ZSs9dVtoXS52YWx1ZTtjLmRpY2U/VWgoYyxpLG8sYSxvKz0ocy1vKSpjLnZhbHVlL2cpOkptKGMsaSxvLGkrPShhLWkpKmMudmFsdWUvZyxzKSxnLT1jLnZhbHVlfWVsc2Ugbi5fc3F1YXJpZnk9bD1uWCh0LG4saSxvLGEscyksbC5yYXRpbz10fXJldHVybiByLnJhdGlvPWZ1bmN0aW9uKG4pe3JldHVybiBlKChuPStuKT4xP246MSl9LHJ9KHJYKTtmdW5jdGlvbiB2MnQoZSl7Zm9yKHZhciB0PS0xLHI9ZS5sZW5ndGgsbixpPWVbci0xXSxvPTA7Kyt0PHI7KW49aSxpPWVbdF0sbys9blsxXSppWzBdLW5bMF0qaVsxXTtyZXR1cm4gby8yfWZ1bmN0aW9uIHgydChlKXtmb3IodmFyIHQ9LTEscj1lLmxlbmd0aCxuPTAsaT0wLG8sYT1lW3ItMV0scyxsPTA7Kyt0PHI7KW89YSxhPWVbdF0sbCs9cz1vWzBdKmFbMV0tYVswXSpvWzFdLG4rPShvWzBdK2FbMF0pKnMsaSs9KG9bMV0rYVsxXSkqcztyZXR1cm4gbCo9Myxbbi9sLGkvbF19ZnVuY3Rpb24gYjJ0KGUsdCxyKXtyZXR1cm4odFswXS1lWzBdKSooclsxXS1lWzFdKS0odFsxXS1lWzFdKSooclswXS1lWzBdKX1mdW5jdGlvbiB1TWUoZSx0KXtyZXR1cm4gZVswXS10WzBdfHxlWzFdLXRbMV19ZnVuY3Rpb24gdzJ0KGUpe2Zvcih2YXIgdD1lLmxlbmd0aCxyPVswLDFdLG49MixpPTI7aTx0OysraSl7Zm9yKDtuPjEmJmIydChlW3Jbbi0yXV0sZVtyW24tMV1dLGVbaV0pPD0wOyktLW47cltuKytdPWl9cmV0dXJuIHIuc2xpY2UoMCxuKX1mdW5jdGlvbiBTMnQoZSl7aWYoKHI9ZS5sZW5ndGgpPDMpcmV0dXJuIG51bGw7dmFyIHQscixuPW5ldyBBcnJheShyKSxpPW5ldyBBcnJheShyKTtmb3IodD0wO3Q8cjsrK3Qpblt0XT1bK2VbdF1bMF0sK2VbdF1bMV0sdF07Zm9yKG4uc29ydCh1TWUpLHQ9MDt0PHI7Kyt0KWlbdF09W25bdF1bMF0sLW5bdF1bMV1dO3ZhciBvPXcydChuKSxhPXcydChpKSxzPWFbMF09PT1vWzBdLGw9YVthLmxlbmd0aC0xXT09PW9bby5sZW5ndGgtMV0sYz1bXTtmb3IodD1vLmxlbmd0aC0xO3Q+PTA7LS10KWMucHVzaChlW25bb1t0XV1bMl1dKTtmb3IodD0rczt0PGEubGVuZ3RoLWw7Kyt0KWMucHVzaChlW25bYVt0XV1bMl1dKTtyZXR1cm4gY31mdW5jdGlvbiBNMnQoZSx0KXtmb3IodmFyIHI9ZS5sZW5ndGgsbj1lW3ItMV0saT10WzBdLG89dFsxXSxhPW5bMF0scz1uWzFdLGwsYyx1PSExLGg9MDtoPHI7KytoKW49ZVtoXSxsPW5bMF0sYz1uWzFdLGM+byE9cz5vJiZpPChhLWwpKihvLWMpLyhzLWMpK2wmJih1PSF1KSxhPWwscz1jO3JldHVybiB1fWZ1bmN0aW9uIEUydChlKXtmb3IodmFyIHQ9LTEscj1lLmxlbmd0aCxuPWVbci0xXSxpLG8sYT1uWzBdLHM9blsxXSxsPTA7Kyt0PHI7KWk9YSxvPXMsbj1lW3RdLGE9blswXSxzPW5bMV0saS09YSxvLT1zLGwrPU1hdGguc3FydChpKmkrbypvKTtyZXR1cm4gbH1mdW5jdGlvbiBhYygpe3JldHVybiBNYXRoLnJhbmRvbSgpfXZhciBUMnQ9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4saSl7cmV0dXJuIG49bj09bnVsbD8wOituLGk9aT09bnVsbD8xOitpLGFyZ3VtZW50cy5sZW5ndGg9PT0xPyhpPW4sbj0wKTppLT1uLGZ1bmN0aW9uKCl7cmV0dXJuIHQoKSppK259fXJldHVybiByLnNvdXJjZT1lLHJ9KGFjKTt2YXIgbzg9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4saSl7dmFyIG8sYTtyZXR1cm4gbj1uPT1udWxsPzA6K24saT1pPT1udWxsPzE6K2ksZnVuY3Rpb24oKXt2YXIgcztpZihvIT1udWxsKXM9byxvPW51bGw7ZWxzZSBkbyBvPXQoKSoyLTEscz10KCkqMi0xLGE9bypvK3Mqczt3aGlsZSghYXx8YT4xKTtyZXR1cm4gbitpKnMqTWF0aC5zcXJ0KC0yKk1hdGgubG9nKGEpL2EpfX1yZXR1cm4gci5zb3VyY2U9ZSxyfShhYyk7dmFyIEMydD1mdW5jdGlvbiBlKHQpe2Z1bmN0aW9uIHIoKXt2YXIgbj1vOC5zb3VyY2UodCkuYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiBmdW5jdGlvbigpe3JldHVybiBNYXRoLmV4cChuKCkpfX1yZXR1cm4gci5zb3VyY2U9ZSxyfShhYyk7dmFyIGE4PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuKXtyZXR1cm4gZnVuY3Rpb24oKXtmb3IodmFyIGk9MCxvPTA7bzxuOysrbylpKz10KCk7cmV0dXJuIGl9fXJldHVybiByLnNvdXJjZT1lLHJ9KGFjKTt2YXIgQTJ0PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuKXt2YXIgaT1hOC5zb3VyY2UodCkobik7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGkoKS9ufX1yZXR1cm4gci5zb3VyY2U9ZSxyfShhYyk7dmFyIFAydD1mdW5jdGlvbiBlKHQpe2Z1bmN0aW9uIHIobil7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuLU1hdGgubG9nKDEtdCgpKS9ufX1yZXR1cm4gci5zb3VyY2U9ZSxyfShhYyk7ZnVuY3Rpb24gZmkoZSx0KXtzd2l0Y2goYXJndW1lbnRzLmxlbmd0aCl7Y2FzZSAwOmJyZWFrO2Nhc2UgMTp0aGlzLnJhbmdlKGUpO2JyZWFrO2RlZmF1bHQ6dGhpcy5yYW5nZSh0KS5kb21haW4oZSk7YnJlYWt9cmV0dXJuIHRoaXN9ZnVuY3Rpb24gc2MoZSx0KXtzd2l0Y2goYXJndW1lbnRzLmxlbmd0aCl7Y2FzZSAwOmJyZWFrO2Nhc2UgMTp0aGlzLmludGVycG9sYXRvcihlKTticmVhaztkZWZhdWx0OnRoaXMuaW50ZXJwb2xhdG9yKHQpLmRvbWFpbihlKTticmVha31yZXR1cm4gdGhpc31UYigpO3ZhciBJMnQ9QXJyYXkucHJvdG90eXBlLFhfPUkydC5tYXAsbGM9STJ0LnNsaWNlO3ZhciBzOD17bmFtZToiaW1wbGljaXQifTtmdW5jdGlvbiBndSgpe3ZhciBlPUppKCksdD1bXSxyPVtdLG49czg7ZnVuY3Rpb24gaShvKXt2YXIgYT1vKyIiLHM9ZS5nZXQoYSk7aWYoIXMpe2lmKG4hPT1zOClyZXR1cm4gbjtlLnNldChhLHM9dC5wdXNoKG8pKX1yZXR1cm4gclsocy0xKSVyLmxlbmd0aF19cmV0dXJuIGkuZG9tYWluPWZ1bmN0aW9uKG8pe2lmKCFhcmd1bWVudHMubGVuZ3RoKXJldHVybiB0LnNsaWNlKCk7dD1bXSxlPUppKCk7Zm9yKHZhciBhPS0xLHM9by5sZW5ndGgsbCxjOysrYTxzOyllLmhhcyhjPShsPW9bYV0pKyIiKXx8ZS5zZXQoYyx0LnB1c2gobCkpO3JldHVybiBpfSxpLnJhbmdlPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPWxjLmNhbGwobyksaSk6ci5zbGljZSgpfSxpLnVua25vd249ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49byxpKTpufSxpLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gZ3UodCxyKS51bmtub3duKG4pfSxmaS5hcHBseShpLGFyZ3VtZW50cyksaX1mdW5jdGlvbiBRbSgpe3ZhciBlPWd1KCkudW5rbm93bih2b2lkIDApLHQ9ZS5kb21haW4scj1lLnJhbmdlLG49WzAsMV0saSxvLGE9ITEscz0wLGw9MCxjPS41O2RlbGV0ZSBlLnVua25vd247ZnVuY3Rpb24gdSgpe3ZhciBoPXQoKS5sZW5ndGgsZj1uWzFdPG5bMF0scD1uW2YtMF0sZD1uWzEtZl07aT0oZC1wKS9NYXRoLm1heCgxLGgtcytsKjIpLGEmJihpPU1hdGguZmxvb3IoaSkpLHArPShkLXAtaSooaC1zKSkqYyxvPWkqKDEtcyksYSYmKHA9TWF0aC5yb3VuZChwKSxvPU1hdGgucm91bmQobykpO3ZhciBnPUlyKGgpLm1hcChmdW5jdGlvbihfKXtyZXR1cm4gcCtpKl99KTtyZXR1cm4gcihmP2cucmV2ZXJzZSgpOmcpfXJldHVybiBlLmRvbWFpbj1mdW5jdGlvbihoKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odChoKSx1KCkpOnQoKX0sZS5yYW5nZT1mdW5jdGlvbihoKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj1bK2hbMF0sK2hbMV1dLHUoKSk6bi5zbGljZSgpfSxlLnJhbmdlUm91bmQ9ZnVuY3Rpb24oaCl7cmV0dXJuIG49WytoWzBdLCtoWzFdXSxhPSEwLHUoKX0sZS5iYW5kd2lkdGg9ZnVuY3Rpb24oKXtyZXR1cm4gb30sZS5zdGVwPWZ1bmN0aW9uKCl7cmV0dXJuIGl9LGUucm91bmQ9ZnVuY3Rpb24oaCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGE9ISFoLHUoKSk6YX0sZS5wYWRkaW5nPWZ1bmN0aW9uKGgpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhzPU1hdGgubWluKDEsbD0raCksdSgpKTpzfSxlLnBhZGRpbmdJbm5lcj1mdW5jdGlvbihoKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocz1NYXRoLm1pbigxLGgpLHUoKSk6c30sZS5wYWRkaW5nT3V0ZXI9ZnVuY3Rpb24oaCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGw9K2gsdSgpKTpsfSxlLmFsaWduPWZ1bmN0aW9uKGgpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhjPU1hdGgubWF4KDAsTWF0aC5taW4oMSxoKSksdSgpKTpjfSxlLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gUW0odCgpLG4pLnJvdW5kKGEpLnBhZGRpbmdJbm5lcihzKS5wYWRkaW5nT3V0ZXIobCkuYWxpZ24oYyl9LGZpLmFwcGx5KHUoKSxhcmd1bWVudHMpfWZ1bmN0aW9uIEwydChlKXt2YXIgdD1lLmNvcHk7cmV0dXJuIGUucGFkZGluZz1lLnBhZGRpbmdPdXRlcixkZWxldGUgZS5wYWRkaW5nSW5uZXIsZGVsZXRlIGUucGFkZGluZ091dGVyLGUuY29weT1mdW5jdGlvbigpe3JldHVybiBMMnQodCgpKX0sZX1mdW5jdGlvbiB0Zygpe3JldHVybiBMMnQoUW0uYXBwbHkobnVsbCxhcmd1bWVudHMpLnBhZGRpbmdJbm5lcigxKSl9ZnVuY3Rpb24gazJ0KGUpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlfX1mdW5jdGlvbiBkNShlKXtyZXR1cm4rZX12YXIgUjJ0PVswLDFdO2Z1bmN0aW9uIGlpKGUpe3JldHVybiBlfWZ1bmN0aW9uIGlYKGUsdCl7cmV0dXJuKHQtPWU9K2UpP2Z1bmN0aW9uKHIpe3JldHVybihyLWUpL3R9OmsydChpc05hTih0KT9OYU46LjUpfWZ1bmN0aW9uIE4ydChlKXt2YXIgdD1lWzBdLHI9ZVtlLmxlbmd0aC0xXSxuO3JldHVybiB0PnImJihuPXQsdD1yLHI9biksZnVuY3Rpb24oaSl7cmV0dXJuIE1hdGgubWF4KHQsTWF0aC5taW4ocixpKSl9fWZ1bmN0aW9uIGhNZShlLHQscil7dmFyIG49ZVswXSxpPWVbMV0sbz10WzBdLGE9dFsxXTtyZXR1cm4gaTxuPyhuPWlYKGksbiksbz1yKGEsbykpOihuPWlYKG4saSksbz1yKG8sYSkpLGZ1bmN0aW9uKHMpe3JldHVybiBvKG4ocykpfX1mdW5jdGlvbiBmTWUoZSx0LHIpe3ZhciBuPU1hdGgubWluKGUubGVuZ3RoLHQubGVuZ3RoKS0xLGk9bmV3IEFycmF5KG4pLG89bmV3IEFycmF5KG4pLGE9LTE7Zm9yKGVbbl08ZVswXSYmKGU9ZS5zbGljZSgpLnJldmVyc2UoKSx0PXQuc2xpY2UoKS5yZXZlcnNlKCkpOysrYTxuOylpW2FdPWlYKGVbYV0sZVthKzFdKSxvW2FdPXIodFthXSx0W2ErMV0pO3JldHVybiBmdW5jdGlvbihzKXt2YXIgbD15cyhlLHMsMSxuKS0xO3JldHVybiBvW2xdKGlbbF0ocykpfX1mdW5jdGlvbiBxaChlLHQpe3JldHVybiB0LmRvbWFpbihlLmRvbWFpbigpKS5yYW5nZShlLnJhbmdlKCkpLmludGVycG9sYXRlKGUuaW50ZXJwb2xhdGUoKSkuY2xhbXAoZS5jbGFtcCgpKS51bmtub3duKGUudW5rbm93bigpKX1mdW5jdGlvbiAkXygpe3ZhciBlPVIydCx0PVIydCxyPW5jLG4saSxvLGE9aWkscyxsLGM7ZnVuY3Rpb24gdSgpe3JldHVybiBzPU1hdGgubWluKGUubGVuZ3RoLHQubGVuZ3RoKT4yP2ZNZTpoTWUsbD1jPW51bGwsaH1mdW5jdGlvbiBoKGYpe3JldHVybiBpc05hTihmPStmKT9vOihsfHwobD1zKGUubWFwKG4pLHQscikpKShuKGEoZikpKX1yZXR1cm4gaC5pbnZlcnQ9ZnVuY3Rpb24oZil7cmV0dXJuIGEoaSgoY3x8KGM9cyh0LGUubWFwKG4pLHppKSkpKGYpKSl9LGguZG9tYWluPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPVhfLmNhbGwoZixkNSksYT09PWlpfHwoYT1OMnQoZSkpLHUoKSk6ZS5zbGljZSgpfSxoLnJhbmdlPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PWxjLmNhbGwoZiksdSgpKTp0LnNsaWNlKCl9LGgucmFuZ2VSb3VuZD1mdW5jdGlvbihmKXtyZXR1cm4gdD1sYy5jYWxsKGYpLHI9cEwsdSgpfSxoLmNsYW1wPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhhPWY/TjJ0KGUpOmlpLGgpOmEhPT1paX0saC5pbnRlcnBvbGF0ZT1mdW5jdGlvbihmKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj1mLHUoKSk6cn0saC51bmtub3duPWZ1bmN0aW9uKGYpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPWYsaCk6b30sZnVuY3Rpb24oZixwKXtyZXR1cm4gbj1mLGk9cCx1KCl9fWZ1bmN0aW9uIG01KGUsdCl7cmV0dXJuICRfKCkoZSx0KX1mdW5jdGlvbiBsOChlLHQscixuKXt2YXIgaT10bChlLHQsciksbztzd2l0Y2gobj1McChuPT1udWxsPyIsZiI6biksbi50eXBlKXtjYXNlInMiOnt2YXIgYT1NYXRoLm1heChNYXRoLmFicyhlKSxNYXRoLmFicyh0KSk7cmV0dXJuIG4ucHJlY2lzaW9uPT1udWxsJiYhaXNOYU4obz1payhpLGEpKSYmKG4ucHJlY2lzaW9uPW8pLEdFKG4sYSl9Y2FzZSIiOmNhc2UiZSI6Y2FzZSJnIjpjYXNlInAiOmNhc2UiciI6e24ucHJlY2lzaW9uPT1udWxsJiYhaXNOYU4obz1vayhpLE1hdGgubWF4KE1hdGguYWJzKGUpLE1hdGguYWJzKHQpKSkpJiYobi5wcmVjaXNpb249by0obi50eXBlPT09ImUiKSk7YnJlYWt9Y2FzZSJmIjpjYXNlIiUiOntuLnByZWNpc2lvbj09bnVsbCYmIWlzTmFOKG89bmsoaSkpJiYobi5wcmVjaXNpb249by0obi50eXBlPT09IiUiKSoyKTticmVha319cmV0dXJuIHhuKG4pfWZ1bmN0aW9uIGlsKGUpe3ZhciB0PWUuZG9tYWluO3JldHVybiBlLnRpY2tzPWZ1bmN0aW9uKHIpe3ZhciBuPXQoKTtyZXR1cm4gYWIoblswXSxuW24ubGVuZ3RoLTFdLHI9PW51bGw/MTA6cil9LGUudGlja0Zvcm1hdD1mdW5jdGlvbihyLG4pe3ZhciBpPXQoKTtyZXR1cm4gbDgoaVswXSxpW2kubGVuZ3RoLTFdLHI9PW51bGw/MTA6cixuKX0sZS5uaWNlPWZ1bmN0aW9uKHIpe3I9PW51bGwmJihyPTEwKTt2YXIgbj10KCksaT0wLG89bi5sZW5ndGgtMSxhPW5baV0scz1uW29dLGw7cmV0dXJuIHM8YSYmKGw9YSxhPXMscz1sLGw9aSxpPW8sbz1sKSxsPXhfKGEscyxyKSxsPjA/KGE9TWF0aC5mbG9vcihhL2wpKmwscz1NYXRoLmNlaWwocy9sKSpsLGw9eF8oYSxzLHIpKTpsPDAmJihhPU1hdGguY2VpbChhKmwpL2wscz1NYXRoLmZsb29yKHMqbCkvbCxsPXhfKGEscyxyKSksbD4wPyhuW2ldPU1hdGguZmxvb3IoYS9sKSpsLG5bb109TWF0aC5jZWlsKHMvbCkqbCx0KG4pKTpsPDAmJihuW2ldPU1hdGguY2VpbChhKmwpL2wsbltvXT1NYXRoLmZsb29yKHMqbCkvbCx0KG4pKSxlfSxlfWZ1bmN0aW9uIHpuKCl7dmFyIGU9bTUoaWksaWkpO3JldHVybiBlLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gcWgoZSx6bigpKX0sZmkuYXBwbHkoZSxhcmd1bWVudHMpLGlsKGUpfWZ1bmN0aW9uIGM4KGUpe3ZhciB0O2Z1bmN0aW9uIHIobil7cmV0dXJuIGlzTmFOKG49K24pP3Q6bn1yZXR1cm4gci5pbnZlcnQ9cixyLmRvbWFpbj1yLnJhbmdlPWZ1bmN0aW9uKG4pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPVhfLmNhbGwobixkNSkscik6ZS5zbGljZSgpfSxyLnVua25vd249ZnVuY3Rpb24obil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9bixyKTp0fSxyLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gYzgoZSkudW5rbm93bih0KX0sZT1hcmd1bWVudHMubGVuZ3RoP1hfLmNhbGwoZSxkNSk6WzAsMV0saWwocil9ZnVuY3Rpb24gdTgoZSx0KXtlPWUuc2xpY2UoKTt2YXIgcj0wLG49ZS5sZW5ndGgtMSxpPWVbcl0sbz1lW25dLGE7cmV0dXJuIG88aSYmKGE9cixyPW4sbj1hLGE9aSxpPW8sbz1hKSxlW3JdPXQuZmxvb3IoaSksZVtuXT10LmNlaWwobyksZX1mdW5jdGlvbiBEMnQoZSl7cmV0dXJuIE1hdGgubG9nKGUpfWZ1bmN0aW9uIE8ydChlKXtyZXR1cm4gTWF0aC5leHAoZSl9ZnVuY3Rpb24gcE1lKGUpe3JldHVybi1NYXRoLmxvZygtZSl9ZnVuY3Rpb24gZE1lKGUpe3JldHVybi1NYXRoLmV4cCgtZSl9ZnVuY3Rpb24gbU1lKGUpe3JldHVybiBpc0Zpbml0ZShlKT8rKCIxZSIrZSk6ZTwwPzA6ZX1mdW5jdGlvbiBnTWUoZSl7cmV0dXJuIGU9PT0xMD9tTWU6ZT09PU1hdGguRT9NYXRoLmV4cDpmdW5jdGlvbih0KXtyZXR1cm4gTWF0aC5wb3coZSx0KX19ZnVuY3Rpb24gX01lKGUpe3JldHVybiBlPT09TWF0aC5FP01hdGgubG9nOmU9PT0xMCYmTWF0aC5sb2cxMHx8ZT09PTImJk1hdGgubG9nMnx8KGU9TWF0aC5sb2coZSksZnVuY3Rpb24odCl7cmV0dXJuIE1hdGgubG9nKHQpL2V9KX1mdW5jdGlvbiB6MnQoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3JldHVybi1lKC10KX19ZnVuY3Rpb24gZzUoZSl7dmFyIHQ9ZShEMnQsTzJ0KSxyPXQuZG9tYWluLG49MTAsaSxvO2Z1bmN0aW9uIGEoKXtyZXR1cm4gaT1fTWUobiksbz1nTWUobikscigpWzBdPDA/KGk9ejJ0KGkpLG89ejJ0KG8pLGUocE1lLGRNZSkpOmUoRDJ0LE8ydCksdH1yZXR1cm4gdC5iYXNlPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPStzLGEoKSk6bn0sdC5kb21haW49ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHIocyksYSgpKTpyKCl9LHQudGlja3M9ZnVuY3Rpb24ocyl7dmFyIGw9cigpLGM9bFswXSx1PWxbbC5sZW5ndGgtMV0saDsoaD11PGMpJiYoZj1jLGM9dSx1PWYpO3ZhciBmPWkoYykscD1pKHUpLGQsZyxfLHk9cz09bnVsbD8xMDorcyx4PVtdO2lmKCEobiUxKSYmcC1mPHkpe2lmKGY9TWF0aC5yb3VuZChmKS0xLHA9TWF0aC5yb3VuZChwKSsxLGM+MCl7Zm9yKDtmPHA7KytmKWZvcihnPTEsZD1vKGYpO2c8bjsrK2cpaWYoXz1kKmcsIShfPGMpKXtpZihfPnUpYnJlYWs7eC5wdXNoKF8pfX1lbHNlIGZvcig7ZjxwOysrZilmb3IoZz1uLTEsZD1vKGYpO2c+PTE7LS1nKWlmKF89ZCpnLCEoXzxjKSl7aWYoXz51KWJyZWFrO3gucHVzaChfKX19ZWxzZSB4PWFiKGYscCxNYXRoLm1pbihwLWYseSkpLm1hcChvKTtyZXR1cm4gaD94LnJldmVyc2UoKTp4fSx0LnRpY2tGb3JtYXQ9ZnVuY3Rpb24ocyxsKXtpZihsPT1udWxsJiYobD1uPT09MTA/Ii4wZSI6IiwiKSx0eXBlb2YgbCE9ImZ1bmN0aW9uIiYmKGw9eG4obCkpLHM9PT0xLzApcmV0dXJuIGw7cz09bnVsbCYmKHM9MTApO3ZhciBjPU1hdGgubWF4KDEsbipzL3QudGlja3MoKS5sZW5ndGgpO3JldHVybiBmdW5jdGlvbih1KXt2YXIgaD11L28oTWF0aC5yb3VuZChpKHUpKSk7cmV0dXJuIGgqbjxuLS41JiYoaCo9biksaDw9Yz9sKHUpOiIifX0sdC5uaWNlPWZ1bmN0aW9uKCl7cmV0dXJuIHIodTgocigpLHtmbG9vcjpmdW5jdGlvbihzKXtyZXR1cm4gbyhNYXRoLmZsb29yKGkocykpKX0sY2VpbDpmdW5jdGlvbihzKXtyZXR1cm4gbyhNYXRoLmNlaWwoaShzKSkpfX0pKX0sdH1mdW5jdGlvbiBjYygpe3ZhciBlPWc1KCRfKCkpLmRvbWFpbihbMSwxMF0pO3JldHVybiBlLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gcWgoZSxjYygpKS5iYXNlKGUuYmFzZSgpKX0sZmkuYXBwbHkoZSxhcmd1bWVudHMpLGV9ZnVuY3Rpb24gRjJ0KGUpe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4gTWF0aC5zaWduKHQpKk1hdGgubG9nMXAoTWF0aC5hYnModC9lKSl9fWZ1bmN0aW9uIEIydChlKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuIE1hdGguc2lnbih0KSpNYXRoLmV4cG0xKE1hdGguYWJzKHQpKSplfX1mdW5jdGlvbiBfNShlKXt2YXIgdD0xLHI9ZShGMnQodCksQjJ0KHQpKTtyZXR1cm4gci5jb25zdGFudD1mdW5jdGlvbihuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD9lKEYydCh0PStuKSxCMnQodCkpOnR9LGlsKHIpfWZ1bmN0aW9uIGg4KCl7dmFyIGU9XzUoJF8oKSk7cmV0dXJuIGUuY29weT1mdW5jdGlvbigpe3JldHVybiBxaChlLGg4KCkpLmNvbnN0YW50KGUuY29uc3RhbnQoKSl9LGZpLmFwcGx5KGUsYXJndW1lbnRzKX1mdW5jdGlvbiBIMnQoZSl7cmV0dXJuIGZ1bmN0aW9uKHQpe3JldHVybiB0PDA/LU1hdGgucG93KC10LGUpOk1hdGgucG93KHQsZSl9fWZ1bmN0aW9uIHlNZShlKXtyZXR1cm4gZTwwPy1NYXRoLnNxcnQoLWUpOk1hdGguc3FydChlKX1mdW5jdGlvbiB2TWUoZSl7cmV0dXJuIGU8MD8tZSplOmUqZX1mdW5jdGlvbiB5NShlKXt2YXIgdD1lKGlpLGlpKSxyPTE7ZnVuY3Rpb24gbigpe3JldHVybiByPT09MT9lKGlpLGlpKTpyPT09LjU/ZSh5TWUsdk1lKTplKEgydChyKSxIMnQoMS9yKSl9cmV0dXJuIHQuZXhwb25lbnQ9ZnVuY3Rpb24oaSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9K2ksbigpKTpyfSxpbCh0KX1mdW5jdGlvbiBLXygpe3ZhciBlPXk1KCRfKCkpO3JldHVybiBlLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gcWgoZSxLXygpKS5leHBvbmVudChlLmV4cG9uZW50KCkpfSxmaS5hcHBseShlLGFyZ3VtZW50cyksZX1mdW5jdGlvbiBWMnQoKXtyZXR1cm4gS18uYXBwbHkobnVsbCxhcmd1bWVudHMpLmV4cG9uZW50KC41KX1mdW5jdGlvbiBlZygpe3ZhciBlPVtdLHQ9W10scj1bXSxuO2Z1bmN0aW9uIGkoKXt2YXIgYT0wLHM9TWF0aC5tYXgoMSx0Lmxlbmd0aCk7Zm9yKHI9bmV3IEFycmF5KHMtMSk7KythPHM7KXJbYS0xXT1zYShlLGEvcyk7cmV0dXJuIG99ZnVuY3Rpb24gbyhhKXtyZXR1cm4gaXNOYU4oYT0rYSk/bjp0W3lzKHIsYSldfXJldHVybiBvLmludmVydEV4dGVudD1mdW5jdGlvbihhKXt2YXIgcz10LmluZGV4T2YoYSk7cmV0dXJuIHM8MD9bTmFOLE5hTl06W3M+MD9yW3MtMV06ZVswXSxzPHIubGVuZ3RoP3Jbc106ZVtlLmxlbmd0aC0xXV19LG8uZG9tYWluPWZ1bmN0aW9uKGEpe2lmKCFhcmd1bWVudHMubGVuZ3RoKXJldHVybiBlLnNsaWNlKCk7ZT1bXTtmb3IodmFyIHM9MCxsPWEubGVuZ3RoLGM7czxsOysrcyljPWFbc10sYyE9bnVsbCYmIWlzTmFOKGM9K2MpJiZlLnB1c2goYyk7cmV0dXJuIGUuc29ydChvYSksaSgpfSxvLnJhbmdlPWZ1bmN0aW9uKGEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PWxjLmNhbGwoYSksaSgpKTp0LnNsaWNlKCl9LG8udW5rbm93bj1mdW5jdGlvbihhKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj1hLG8pOm59LG8ucXVhbnRpbGVzPWZ1bmN0aW9uKCl7cmV0dXJuIHIuc2xpY2UoKX0sby5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIGVnKCkuZG9tYWluKGUpLnJhbmdlKHQpLnVua25vd24obil9LGZpLmFwcGx5KG8sYXJndW1lbnRzKX1mdW5jdGlvbiBxYigpe3ZhciBlPTAsdD0xLHI9MSxuPVsuNV0saT1bMCwxXSxvO2Z1bmN0aW9uIGEobCl7cmV0dXJuIGw8PWw/aVt5cyhuLGwsMCxyKV06b31mdW5jdGlvbiBzKCl7dmFyIGw9LTE7Zm9yKG49bmV3IEFycmF5KHIpOysrbDxyOyluW2xdPSgobCsxKSp0LShsLXIpKmUpLyhyKzEpO3JldHVybiBhfXJldHVybiBhLmRvbWFpbj1mdW5jdGlvbihsKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT0rbFswXSx0PStsWzFdLHMoKSk6W2UsdF19LGEucmFuZ2U9ZnVuY3Rpb24obCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9KGk9bGMuY2FsbChsKSkubGVuZ3RoLTEscygpKTppLnNsaWNlKCl9LGEuaW52ZXJ0RXh0ZW50PWZ1bmN0aW9uKGwpe3ZhciBjPWkuaW5kZXhPZihsKTtyZXR1cm4gYzwwP1tOYU4sTmFOXTpjPDE/W2UsblswXV06Yz49cj9bbltyLTFdLHRdOltuW2MtMV0sbltjXV19LGEudW5rbm93bj1mdW5jdGlvbihsKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aCYmKG89bCksYX0sYS50aHJlc2hvbGRzPWZ1bmN0aW9uKCl7cmV0dXJuIG4uc2xpY2UoKX0sYS5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIHFiKCkuZG9tYWluKFtlLHRdKS5yYW5nZShpKS51bmtub3duKG8pfSxmaS5hcHBseShpbChhKSxhcmd1bWVudHMpfWZ1bmN0aW9uIGY4KCl7dmFyIGU9Wy41XSx0PVswLDFdLHIsbj0xO2Z1bmN0aW9uIGkobyl7cmV0dXJuIG88PW8/dFt5cyhlLG8sMCxuKV06cn1yZXR1cm4gaS5kb21haW49ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9bGMuY2FsbChvKSxuPU1hdGgubWluKGUubGVuZ3RoLHQubGVuZ3RoLTEpLGkpOmUuc2xpY2UoKX0saS5yYW5nZT1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD1sYy5jYWxsKG8pLG49TWF0aC5taW4oZS5sZW5ndGgsdC5sZW5ndGgtMSksaSk6dC5zbGljZSgpfSxpLmludmVydEV4dGVudD1mdW5jdGlvbihvKXt2YXIgYT10LmluZGV4T2Yobyk7cmV0dXJuW2VbYS0xXSxlW2FdXX0saS51bmtub3duPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPW8saSk6cn0saS5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIGY4KCkuZG9tYWluKGUpLnJhbmdlKHQpLnVua25vd24ocil9LGZpLmFwcGx5KGksYXJndW1lbnRzKX12YXIgb1g9bmV3IERhdGUsYVg9bmV3IERhdGU7ZnVuY3Rpb24gYnIoZSx0LHIsbil7ZnVuY3Rpb24gaShvKXtyZXR1cm4gZShvPWFyZ3VtZW50cy5sZW5ndGg9PT0wP25ldyBEYXRlOm5ldyBEYXRlKCtvKSksb31yZXR1cm4gaS5mbG9vcj1mdW5jdGlvbihvKXtyZXR1cm4gZShvPW5ldyBEYXRlKCtvKSksb30saS5jZWlsPWZ1bmN0aW9uKG8pe3JldHVybiBlKG89bmV3IERhdGUoby0xKSksdChvLDEpLGUobyksb30saS5yb3VuZD1mdW5jdGlvbihvKXt2YXIgYT1pKG8pLHM9aS5jZWlsKG8pO3JldHVybiBvLWE8cy1vP2E6c30saS5vZmZzZXQ9ZnVuY3Rpb24obyxhKXtyZXR1cm4gdChvPW5ldyBEYXRlKCtvKSxhPT1udWxsPzE6TWF0aC5mbG9vcihhKSksb30saS5yYW5nZT1mdW5jdGlvbihvLGEscyl7dmFyIGw9W10sYztpZihvPWkuY2VpbChvKSxzPXM9PW51bGw/MTpNYXRoLmZsb29yKHMpLCEobzxhKXx8IShzPjApKXJldHVybiBsO2RvIGwucHVzaChjPW5ldyBEYXRlKCtvKSksdChvLHMpLGUobyk7d2hpbGUoYzxvJiZvPGEpO3JldHVybiBsfSxpLmZpbHRlcj1mdW5jdGlvbihvKXtyZXR1cm4gYnIoZnVuY3Rpb24oYSl7aWYoYT49YSlmb3IoO2UoYSksIW8oYSk7KWEuc2V0VGltZShhLTEpfSxmdW5jdGlvbihhLHMpe2lmKGE+PWEpaWYoczwwKWZvcig7KytzPD0wOylmb3IoO3QoYSwtMSksIW8oYSk7KTtlbHNlIGZvcig7LS1zPj0wOylmb3IoO3QoYSwxKSwhbyhhKTspO30pfSxyJiYoaS5jb3VudD1mdW5jdGlvbihvLGEpe3JldHVybiBvWC5zZXRUaW1lKCtvKSxhWC5zZXRUaW1lKCthKSxlKG9YKSxlKGFYKSxNYXRoLmZsb29yKHIob1gsYVgpKX0saS5ldmVyeT1mdW5jdGlvbihvKXtyZXR1cm4gbz1NYXRoLmZsb29yKG8pLCFpc0Zpbml0ZShvKXx8IShvPjApP251bGw6bz4xP2kuZmlsdGVyKG4/ZnVuY3Rpb24oYSl7cmV0dXJuIG4oYSklbz09PTB9OmZ1bmN0aW9uKGEpe3JldHVybiBpLmNvdW50KDAsYSklbz09PTB9KTppfSksaX12YXIgcDg9YnIoZnVuY3Rpb24oKXt9LGZ1bmN0aW9uKGUsdCl7ZS5zZXRUaW1lKCtlK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybiB0LWV9KTtwOC5ldmVyeT1mdW5jdGlvbihlKXtyZXR1cm4gZT1NYXRoLmZsb29yKGUpLCFpc0Zpbml0ZShlKXx8IShlPjApP251bGw6ZT4xP2JyKGZ1bmN0aW9uKHQpe3Quc2V0VGltZShNYXRoLmZsb29yKHQvZSkqZSl9LGZ1bmN0aW9uKHQscil7dC5zZXRUaW1lKCt0K3IqZSl9LGZ1bmN0aW9uKHQscil7cmV0dXJuKHItdCkvZX0pOnA4fTt2YXIgWl89cDgsc1g9cDgucmFuZ2U7dmFyIEpfPTFlMyx1Yz02ZTQsR2I9MzZlNSxkOD04NjRlNSxtOD02MDQ4ZTU7dmFyIFUydD1icihmdW5jdGlvbihlKXtlLnNldFRpbWUoZS1lLmdldE1pbGxpc2Vjb25kcygpKX0sZnVuY3Rpb24oZSx0KXtlLnNldFRpbWUoK2UrdCpKXyl9LGZ1bmN0aW9uKGUsdCl7cmV0dXJuKHQtZSkvSl99LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldFVUQ1NlY29uZHMoKX0pLFFfPVUydCxsWD1VMnQucmFuZ2U7dmFyIHEydD1icihmdW5jdGlvbihlKXtlLnNldFRpbWUoZS1lLmdldE1pbGxpc2Vjb25kcygpLWUuZ2V0U2Vjb25kcygpKkpfKX0sZnVuY3Rpb24oZSx0KXtlLnNldFRpbWUoK2UrdCp1Yyl9LGZ1bmN0aW9uKGUsdCl7cmV0dXJuKHQtZSkvdWN9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldE1pbnV0ZXMoKX0pLGc4PXEydCxHMnQ9cTJ0LnJhbmdlO3ZhciBXMnQ9YnIoZnVuY3Rpb24oZSl7ZS5zZXRUaW1lKGUtZS5nZXRNaWxsaXNlY29uZHMoKS1lLmdldFNlY29uZHMoKSpKXy1lLmdldE1pbnV0ZXMoKSp1Yyl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXRUaW1lKCtlK3QqR2IpfSxmdW5jdGlvbihlLHQpe3JldHVybih0LWUpL0difSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRIb3VycygpfSksXzg9VzJ0LFkydD1XMnQucmFuZ2U7dmFyIGoydD1icihmdW5jdGlvbihlKXtlLnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0RGF0ZShlLmdldERhdGUoKSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4odC1lLSh0LmdldFRpbWV6b25lT2Zmc2V0KCktZS5nZXRUaW1lem9uZU9mZnNldCgpKSp1YykvZDh9LGZ1bmN0aW9uKGUpe3JldHVybiBlLmdldERhdGUoKS0xfSksdHk9ajJ0LFgydD1qMnQucmFuZ2U7ZnVuY3Rpb24gZXkoZSl7cmV0dXJuIGJyKGZ1bmN0aW9uKHQpe3Quc2V0RGF0ZSh0LmdldERhdGUoKS0odC5nZXREYXkoKSs3LWUpJTcpLHQuc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKHQscil7dC5zZXREYXRlKHQuZ2V0RGF0ZSgpK3IqNyl9LGZ1bmN0aW9uKHQscil7cmV0dXJuKHItdC0oci5nZXRUaW1lem9uZU9mZnNldCgpLXQuZ2V0VGltZXpvbmVPZmZzZXQoKSkqdWMpL204fSl9dmFyIHJnPWV5KDApLHJ5PWV5KDEpLGNYPWV5KDIpLHVYPWV5KDMpLHpwPWV5KDQpLGhYPWV5KDUpLGZYPWV5KDYpLHBYPXJnLnJhbmdlLCQydD1yeS5yYW5nZSxLMnQ9Y1gucmFuZ2UsWjJ0PXVYLnJhbmdlLEoydD16cC5yYW5nZSxRMnQ9aFgucmFuZ2UsdHd0PWZYLnJhbmdlO3ZhciBld3Q9YnIoZnVuY3Rpb24oZSl7ZS5zZXREYXRlKDEpLGUuc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXRNb250aChlLmdldE1vbnRoKCkrdCl9LGZ1bmN0aW9uKGUsdCl7cmV0dXJuIHQuZ2V0TW9udGgoKS1lLmdldE1vbnRoKCkrKHQuZ2V0RnVsbFllYXIoKS1lLmdldEZ1bGxZZWFyKCkpKjEyfSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRNb250aCgpfSkseTg9ZXd0LHJ3dD1ld3QucmFuZ2U7dmFyIGRYPWJyKGZ1bmN0aW9uKGUpe2Uuc2V0TW9udGgoMCwxKSxlLnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0RnVsbFllYXIoZS5nZXRGdWxsWWVhcigpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybiB0LmdldEZ1bGxZZWFyKCktZS5nZXRGdWxsWWVhcigpfSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRGdWxsWWVhcigpfSk7ZFguZXZlcnk9ZnVuY3Rpb24oZSl7cmV0dXJuIWlzRmluaXRlKGU9TWF0aC5mbG9vcihlKSl8fCEoZT4wKT9udWxsOmJyKGZ1bmN0aW9uKHQpe3Quc2V0RnVsbFllYXIoTWF0aC5mbG9vcih0LmdldEZ1bGxZZWFyKCkvZSkqZSksdC5zZXRNb250aCgwLDEpLHQuc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKHQscil7dC5zZXRGdWxsWWVhcih0LmdldEZ1bGxZZWFyKCkrciplKX0pfTt2YXIgR2g9ZFgsbnd0PWRYLnJhbmdlO3ZhciBpd3Q9YnIoZnVuY3Rpb24oZSl7ZS5zZXRVVENTZWNvbmRzKDAsMCl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXRUaW1lKCtlK3QqdWMpfSxmdW5jdGlvbihlLHQpe3JldHVybih0LWUpL3VjfSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRVVENNaW51dGVzKCl9KSx2OD1pd3Qsb3d0PWl3dC5yYW5nZTt2YXIgYXd0PWJyKGZ1bmN0aW9uKGUpe2Uuc2V0VVRDTWludXRlcygwLDAsMCl9LGZ1bmN0aW9uKGUsdCl7ZS5zZXRUaW1lKCtlK3QqR2IpfSxmdW5jdGlvbihlLHQpe3JldHVybih0LWUpL0difSxmdW5jdGlvbihlKXtyZXR1cm4gZS5nZXRVVENIb3VycygpfSkseDg9YXd0LHN3dD1hd3QucmFuZ2U7dmFyIGx3dD1icihmdW5jdGlvbihlKXtlLnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0VVRDRGF0ZShlLmdldFVUQ0RhdGUoKSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4odC1lKS9kOH0sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0VVRDRGF0ZSgpLTF9KSxueT1sd3QsY3d0PWx3dC5yYW5nZTtmdW5jdGlvbiBpeShlKXtyZXR1cm4gYnIoZnVuY3Rpb24odCl7dC5zZXRVVENEYXRlKHQuZ2V0VVRDRGF0ZSgpLSh0LmdldFVUQ0RheSgpKzctZSklNyksdC5zZXRVVENIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24odCxyKXt0LnNldFVUQ0RhdGUodC5nZXRVVENEYXRlKCkrcio3KX0sZnVuY3Rpb24odCxyKXtyZXR1cm4oci10KS9tOH0pfXZhciBuZz1peSgwKSxveT1peSgxKSxtWD1peSgyKSxnWD1peSgzKSxGcD1peSg0KSxfWD1peSg1KSx5WD1peSg2KSx2WD1uZy5yYW5nZSx1d3Q9b3kucmFuZ2UsaHd0PW1YLnJhbmdlLGZ3dD1nWC5yYW5nZSxwd3Q9RnAucmFuZ2UsZHd0PV9YLnJhbmdlLG13dD15WC5yYW5nZTt2YXIgZ3d0PWJyKGZ1bmN0aW9uKGUpe2Uuc2V0VVRDRGF0ZSgxKSxlLnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihlLHQpe2Uuc2V0VVRDTW9udGgoZS5nZXRVVENNb250aCgpK3QpfSxmdW5jdGlvbihlLHQpe3JldHVybiB0LmdldFVUQ01vbnRoKCktZS5nZXRVVENNb250aCgpKyh0LmdldFVUQ0Z1bGxZZWFyKCktZS5nZXRVVENGdWxsWWVhcigpKSoxMn0sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0VVRDTW9udGgoKX0pLGI4PWd3dCxfd3Q9Z3d0LnJhbmdlO3ZhciB4WD1icihmdW5jdGlvbihlKXtlLnNldFVUQ01vbnRoKDAsMSksZS5zZXRVVENIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24oZSx0KXtlLnNldFVUQ0Z1bGxZZWFyKGUuZ2V0VVRDRnVsbFllYXIoKSt0KX0sZnVuY3Rpb24oZSx0KXtyZXR1cm4gdC5nZXRVVENGdWxsWWVhcigpLWUuZ2V0VVRDRnVsbFllYXIoKX0sZnVuY3Rpb24oZSl7cmV0dXJuIGUuZ2V0VVRDRnVsbFllYXIoKX0pO3hYLmV2ZXJ5PWZ1bmN0aW9uKGUpe3JldHVybiFpc0Zpbml0ZShlPU1hdGguZmxvb3IoZSkpfHwhKGU+MCk/bnVsbDpicihmdW5jdGlvbih0KXt0LnNldFVUQ0Z1bGxZZWFyKE1hdGguZmxvb3IodC5nZXRVVENGdWxsWWVhcigpL2UpKmUpLHQuc2V0VVRDTW9udGgoMCwxKSx0LnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbih0LHIpe3Quc2V0VVRDRnVsbFllYXIodC5nZXRVVENGdWxsWWVhcigpK3IqZSl9KX07dmFyIFdoPXhYLHl3dD14WC5yYW5nZTtmdW5jdGlvbiBiWChlKXtpZigwPD1lLnkmJmUueTwxMDApe3ZhciB0PW5ldyBEYXRlKC0xLGUubSxlLmQsZS5ILGUuTSxlLlMsZS5MKTtyZXR1cm4gdC5zZXRGdWxsWWVhcihlLnkpLHR9cmV0dXJuIG5ldyBEYXRlKGUueSxlLm0sZS5kLGUuSCxlLk0sZS5TLGUuTCl9ZnVuY3Rpb24gd1goZSl7aWYoMDw9ZS55JiZlLnk8MTAwKXt2YXIgdD1uZXcgRGF0ZShEYXRlLlVUQygtMSxlLm0sZS5kLGUuSCxlLk0sZS5TLGUuTCkpO3JldHVybiB0LnNldFVUQ0Z1bGxZZWFyKGUueSksdH1yZXR1cm4gbmV3IERhdGUoRGF0ZS5VVEMoZS55LGUubSxlLmQsZS5ILGUuTSxlLlMsZS5MKSl9ZnVuY3Rpb24gdjUoZSx0LHIpe3JldHVybnt5OmUsbTp0LGQ6cixIOjAsTTowLFM6MCxMOjB9fWZ1bmN0aW9uIHc1KGUpe3ZhciB0PWUuZGF0ZVRpbWUscj1lLmRhdGUsbj1lLnRpbWUsaT1lLnBlcmlvZHMsbz1lLmRheXMsYT1lLnNob3J0RGF5cyxzPWUubW9udGhzLGw9ZS5zaG9ydE1vbnRocyxjPXg1KGkpLHU9YjUoaSksaD14NShvKSxmPWI1KG8pLHA9eDUoYSksZD1iNShhKSxnPXg1KHMpLF89YjUocykseT14NShsKSx4PWI1KGwpLGI9e2E6VyxBOlosYjpydCxCOm90LGM6bnVsbCxkOk13dCxlOk13dCxmOlVNZSxnOkpNZSxHOnRFZSxIOkJNZSxJOkhNZSxqOlZNZSxMOlB3dCxtOnFNZSxNOkdNZSxwOnN0LHE6U3QsUTpDd3QsczpBd3QsUzpXTWUsdTpZTWUsVTpqTWUsVjpYTWUsdzokTWUsVzpLTWUseDpudWxsLFg6bnVsbCx5OlpNZSxZOlFNZSxaOmVFZSwiJSI6VHd0fSxTPXthOmJ0LEE6TXQsYjpsdCxCOkt0LGM6bnVsbCxkOkV3dCxlOkV3dCxmOm9FZSxnOm1FZSxHOl9FZSxIOnJFZSxJOm5FZSxqOmlFZSxMOkx3dCxtOmFFZSxNOnNFZSxwOl90LHE6Y3QsUTpDd3QsczpBd3QsUzpsRWUsdTpjRWUsVTp1RWUsVjpoRWUsdzpmRWUsVzpwRWUseDpudWxsLFg6bnVsbCx5OmRFZSxZOmdFZSxaOnlFZSwiJSI6VHd0fSxDPXthOkIsQTpJLGI6TCxCOlIsYzpGLGQ6d3d0LGU6d3d0LGY6RE1lLGc6Ynd0LEc6eHd0LEg6U3d0LEk6U3d0LGo6TE1lLEw6Tk1lLG06SU1lLE06a01lLHA6RCxxOlBNZSxROnpNZSxzOkZNZSxTOlJNZSx1Ok1NZSxVOkVNZSxWOlRNZSx3OlNNZSxXOkNNZSx4OnosWDpVLHk6Ynd0LFk6eHd0LFo6QU1lLCIlIjpPTWV9O2IueD1QKHIsYiksYi5YPVAobixiKSxiLmM9UCh0LGIpLFMueD1QKHIsUyksUy5YPVAobixTKSxTLmM9UCh0LFMpO2Z1bmN0aW9uIFAoWCxldCl7cmV0dXJuIGZ1bmN0aW9uKGR0KXt2YXIgcT1bXSxwdD0tMSxodD0wLHd0PVgubGVuZ3RoLGt0LGllLGVlO2ZvcihkdCBpbnN0YW5jZW9mIERhdGV8fChkdD1uZXcgRGF0ZSgrZHQpKTsrK3B0PHd0OylYLmNoYXJDb2RlQXQocHQpPT09MzcmJihxLnB1c2goWC5zbGljZShodCxwdCkpLChpZT12d3Rba3Q9WC5jaGFyQXQoKytwdCldKSE9bnVsbD9rdD1YLmNoYXJBdCgrK3B0KTppZT1rdD09PSJlIj8iICI6IjAiLChlZT1ldFtrdF0pJiYoa3Q9ZWUoZHQsaWUpKSxxLnB1c2goa3QpLGh0PXB0KzEpO3JldHVybiBxLnB1c2goWC5zbGljZShodCxwdCkpLHEuam9pbigiIil9fWZ1bmN0aW9uIGsoWCxldCl7cmV0dXJuIGZ1bmN0aW9uKGR0KXt2YXIgcT12NSgxOTAwLHZvaWQgMCwxKSxwdD1PKHEsWCxkdCs9IiIsMCksaHQsd3Q7aWYocHQhPWR0Lmxlbmd0aClyZXR1cm4gbnVsbDtpZigiUSJpbiBxKXJldHVybiBuZXcgRGF0ZShxLlEpO2lmKCJzImluIHEpcmV0dXJuIG5ldyBEYXRlKHEucyoxZTMrKCJMImluIHE/cS5MOjApKTtpZihldCYmISgiWiJpbiBxKSYmKHEuWj0wKSwicCJpbiBxJiYocS5IPXEuSCUxMitxLnAqMTIpLHEubT09PXZvaWQgMCYmKHEubT0icSJpbiBxP3EucTowKSwiViJpbiBxKXtpZihxLlY8MXx8cS5WPjUzKXJldHVybiBudWxsOyJ3ImluIHF8fChxLnc9MSksIloiaW4gcT8oaHQ9d1godjUocS55LDAsMSkpLHd0PWh0LmdldFVUQ0RheSgpLGh0PXd0PjR8fHd0PT09MD9veS5jZWlsKGh0KTpveShodCksaHQ9bnkub2Zmc2V0KGh0LChxLlYtMSkqNykscS55PWh0LmdldFVUQ0Z1bGxZZWFyKCkscS5tPWh0LmdldFVUQ01vbnRoKCkscS5kPWh0LmdldFVUQ0RhdGUoKSsocS53KzYpJTcpOihodD1iWCh2NShxLnksMCwxKSksd3Q9aHQuZ2V0RGF5KCksaHQ9d3Q+NHx8d3Q9PT0wP3J5LmNlaWwoaHQpOnJ5KGh0KSxodD10eS5vZmZzZXQoaHQsKHEuVi0xKSo3KSxxLnk9aHQuZ2V0RnVsbFllYXIoKSxxLm09aHQuZ2V0TW9udGgoKSxxLmQ9aHQuZ2V0RGF0ZSgpKyhxLncrNiklNyl9ZWxzZSgiVyJpbiBxfHwiVSJpbiBxKSYmKCJ3ImluIHF8fChxLnc9InUiaW4gcT9xLnUlNzoiVyJpbiBxPzE6MCksd3Q9IloiaW4gcT93WCh2NShxLnksMCwxKSkuZ2V0VVRDRGF5KCk6YlgodjUocS55LDAsMSkpLmdldERheSgpLHEubT0wLHEuZD0iVyJpbiBxPyhxLncrNiklNytxLlcqNy0od3QrNSklNzpxLncrcS5VKjctKHd0KzYpJTcpO3JldHVybiJaImluIHE/KHEuSCs9cS5aLzEwMHwwLHEuTSs9cS5aJTEwMCx3WChxKSk6YlgocSl9fWZ1bmN0aW9uIE8oWCxldCxkdCxxKXtmb3IodmFyIHB0PTAsaHQ9ZXQubGVuZ3RoLHd0PWR0Lmxlbmd0aCxrdCxpZTtwdDxodDspe2lmKHE+PXd0KXJldHVybi0xO2lmKGt0PWV0LmNoYXJDb2RlQXQocHQrKyksa3Q9PT0zNyl7aWYoa3Q9ZXQuY2hhckF0KHB0KyspLGllPUNba3QgaW4gdnd0P2V0LmNoYXJBdChwdCsrKTprdF0sIWllfHwocT1pZShYLGR0LHEpKTwwKXJldHVybi0xfWVsc2UgaWYoa3QhPWR0LmNoYXJDb2RlQXQocSsrKSlyZXR1cm4tMX1yZXR1cm4gcX1mdW5jdGlvbiBEKFgsZXQsZHQpe3ZhciBxPWMuZXhlYyhldC5zbGljZShkdCkpO3JldHVybiBxPyhYLnA9dVtxWzBdLnRvTG93ZXJDYXNlKCldLGR0K3FbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBCKFgsZXQsZHQpe3ZhciBxPXAuZXhlYyhldC5zbGljZShkdCkpO3JldHVybiBxPyhYLnc9ZFtxWzBdLnRvTG93ZXJDYXNlKCldLGR0K3FbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBJKFgsZXQsZHQpe3ZhciBxPWguZXhlYyhldC5zbGljZShkdCkpO3JldHVybiBxPyhYLnc9ZltxWzBdLnRvTG93ZXJDYXNlKCldLGR0K3FbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBMKFgsZXQsZHQpe3ZhciBxPXkuZXhlYyhldC5zbGljZShkdCkpO3JldHVybiBxPyhYLm09eFtxWzBdLnRvTG93ZXJDYXNlKCldLGR0K3FbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBSKFgsZXQsZHQpe3ZhciBxPWcuZXhlYyhldC5zbGljZShkdCkpO3JldHVybiBxPyhYLm09X1txWzBdLnRvTG93ZXJDYXNlKCldLGR0K3FbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBGKFgsZXQsZHQpe3JldHVybiBPKFgsdCxldCxkdCl9ZnVuY3Rpb24geihYLGV0LGR0KXtyZXR1cm4gTyhYLHIsZXQsZHQpfWZ1bmN0aW9uIFUoWCxldCxkdCl7cmV0dXJuIE8oWCxuLGV0LGR0KX1mdW5jdGlvbiBXKFgpe3JldHVybiBhW1guZ2V0RGF5KCldfWZ1bmN0aW9uIFooWCl7cmV0dXJuIG9bWC5nZXREYXkoKV19ZnVuY3Rpb24gcnQoWCl7cmV0dXJuIGxbWC5nZXRNb250aCgpXX1mdW5jdGlvbiBvdChYKXtyZXR1cm4gc1tYLmdldE1vbnRoKCldfWZ1bmN0aW9uIHN0KFgpe3JldHVybiBpWysoWC5nZXRIb3VycygpPj0xMildfWZ1bmN0aW9uIFN0KFgpe3JldHVybiAxK35+KFguZ2V0TW9udGgoKS8zKX1mdW5jdGlvbiBidChYKXtyZXR1cm4gYVtYLmdldFVUQ0RheSgpXX1mdW5jdGlvbiBNdChYKXtyZXR1cm4gb1tYLmdldFVUQ0RheSgpXX1mdW5jdGlvbiBsdChYKXtyZXR1cm4gbFtYLmdldFVUQ01vbnRoKCldfWZ1bmN0aW9uIEt0KFgpe3JldHVybiBzW1guZ2V0VVRDTW9udGgoKV19ZnVuY3Rpb24gX3QoWCl7cmV0dXJuIGlbKyhYLmdldFVUQ0hvdXJzKCk+PTEyKV19ZnVuY3Rpb24gY3QoWCl7cmV0dXJuIDErfn4oWC5nZXRVVENNb250aCgpLzMpfXJldHVybntmb3JtYXQ6ZnVuY3Rpb24oWCl7dmFyIGV0PVAoWCs9IiIsYik7cmV0dXJuIGV0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIFh9LGV0fSxwYXJzZTpmdW5jdGlvbihYKXt2YXIgZXQ9ayhYKz0iIiwhMSk7cmV0dXJuIGV0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIFh9LGV0fSx1dGNGb3JtYXQ6ZnVuY3Rpb24oWCl7dmFyIGV0PVAoWCs9IiIsUyk7cmV0dXJuIGV0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIFh9LGV0fSx1dGNQYXJzZTpmdW5jdGlvbihYKXt2YXIgZXQ9ayhYKz0iIiwhMCk7cmV0dXJuIGV0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIFh9LGV0fX19dmFyIHZ3dD17Ii0iOiIiLF86IiAiLDA6IjAifSx4bz0vXlxzKlxkKy8seE1lPS9eJS8sYk1lPS9bXFxeJCorP3xbXF0oKS57fV0vZztmdW5jdGlvbiBCcihlLHQscil7dmFyIG49ZTwwPyItIjoiIixpPShuPy1lOmUpKyIiLG89aS5sZW5ndGg7cmV0dXJuIG4rKG88cj9uZXcgQXJyYXkoci1vKzEpLmpvaW4odCkraTppKX1mdW5jdGlvbiB3TWUoZSl7cmV0dXJuIGUucmVwbGFjZShiTWUsIlxcJCYiKX1mdW5jdGlvbiB4NShlKXtyZXR1cm4gbmV3IFJlZ0V4cCgiXig/OiIrZS5tYXAod01lKS5qb2luKCJ8IikrIikiLCJpIil9ZnVuY3Rpb24gYjUoZSl7Zm9yKHZhciB0PXt9LHI9LTEsbj1lLmxlbmd0aDsrK3I8bjspdFtlW3JdLnRvTG93ZXJDYXNlKCldPXI7cmV0dXJuIHR9ZnVuY3Rpb24gU01lKGUsdCxyKXt2YXIgbj14by5leGVjKHQuc2xpY2UocixyKzEpKTtyZXR1cm4gbj8oZS53PStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIE1NZShlLHQscil7dmFyIG49eG8uZXhlYyh0LnNsaWNlKHIscisxKSk7cmV0dXJuIG4/KGUudT0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBFTWUoZSx0LHIpe3ZhciBuPXhvLmV4ZWModC5zbGljZShyLHIrMikpO3JldHVybiBuPyhlLlU9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gVE1lKGUsdCxyKXt2YXIgbj14by5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS5WPStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIENNZShlLHQscil7dmFyIG49eG8uZXhlYyh0LnNsaWNlKHIscisyKSk7cmV0dXJuIG4/KGUuVz0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiB4d3QoZSx0LHIpe3ZhciBuPXhvLmV4ZWModC5zbGljZShyLHIrNCkpO3JldHVybiBuPyhlLnk9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gYnd0KGUsdCxyKXt2YXIgbj14by5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS55PStuWzBdKygrblswXT42OD8xOTAwOjJlMykscituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gQU1lKGUsdCxyKXt2YXIgbj0vXihaKXwoWystXVxkXGQpKD86Oj8oXGRcZCkpPy8uZXhlYyh0LnNsaWNlKHIscis2KSk7cmV0dXJuIG4/KGUuWj1uWzFdPzA6LShuWzJdKyhuWzNdfHwiMDAiKSkscituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gUE1lKGUsdCxyKXt2YXIgbj14by5leGVjKHQuc2xpY2UocixyKzEpKTtyZXR1cm4gbj8oZS5xPW5bMF0qMy0zLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIElNZShlLHQscil7dmFyIG49eG8uZXhlYyh0LnNsaWNlKHIscisyKSk7cmV0dXJuIG4/KGUubT1uWzBdLTEscituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gd3d0KGUsdCxyKXt2YXIgbj14by5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS5kPStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIExNZShlLHQscil7dmFyIG49eG8uZXhlYyh0LnNsaWNlKHIsciszKSk7cmV0dXJuIG4/KGUubT0wLGUuZD0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBTd3QoZSx0LHIpe3ZhciBuPXhvLmV4ZWModC5zbGljZShyLHIrMikpO3JldHVybiBuPyhlLkg9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24ga01lKGUsdCxyKXt2YXIgbj14by5leGVjKHQuc2xpY2UocixyKzIpKTtyZXR1cm4gbj8oZS5NPStuWzBdLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIFJNZShlLHQscil7dmFyIG49eG8uZXhlYyh0LnNsaWNlKHIscisyKSk7cmV0dXJuIG4/KGUuUz0rblswXSxyK25bMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBOTWUoZSx0LHIpe3ZhciBuPXhvLmV4ZWModC5zbGljZShyLHIrMykpO3JldHVybiBuPyhlLkw9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gRE1lKGUsdCxyKXt2YXIgbj14by5leGVjKHQuc2xpY2UocixyKzYpKTtyZXR1cm4gbj8oZS5MPU1hdGguZmxvb3IoblswXS8xZTMpLHIrblswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIE9NZShlLHQscil7dmFyIG49eE1lLmV4ZWModC5zbGljZShyLHIrMSkpO3JldHVybiBuP3IrblswXS5sZW5ndGg6LTF9ZnVuY3Rpb24gek1lKGUsdCxyKXt2YXIgbj14by5leGVjKHQuc2xpY2UocikpO3JldHVybiBuPyhlLlE9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gRk1lKGUsdCxyKXt2YXIgbj14by5leGVjKHQuc2xpY2UocikpO3JldHVybiBuPyhlLnM9K25bMF0scituWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gTXd0KGUsdCl7cmV0dXJuIEJyKGUuZ2V0RGF0ZSgpLHQsMil9ZnVuY3Rpb24gQk1lKGUsdCl7cmV0dXJuIEJyKGUuZ2V0SG91cnMoKSx0LDIpfWZ1bmN0aW9uIEhNZShlLHQpe3JldHVybiBCcihlLmdldEhvdXJzKCklMTJ8fDEyLHQsMil9ZnVuY3Rpb24gVk1lKGUsdCl7cmV0dXJuIEJyKDErdHkuY291bnQoR2goZSksZSksdCwzKX1mdW5jdGlvbiBQd3QoZSx0KXtyZXR1cm4gQnIoZS5nZXRNaWxsaXNlY29uZHMoKSx0LDMpfWZ1bmN0aW9uIFVNZShlLHQpe3JldHVybiBQd3QoZSx0KSsiMDAwIn1mdW5jdGlvbiBxTWUoZSx0KXtyZXR1cm4gQnIoZS5nZXRNb250aCgpKzEsdCwyKX1mdW5jdGlvbiBHTWUoZSx0KXtyZXR1cm4gQnIoZS5nZXRNaW51dGVzKCksdCwyKX1mdW5jdGlvbiBXTWUoZSx0KXtyZXR1cm4gQnIoZS5nZXRTZWNvbmRzKCksdCwyKX1mdW5jdGlvbiBZTWUoZSl7dmFyIHQ9ZS5nZXREYXkoKTtyZXR1cm4gdD09PTA/Nzp0fWZ1bmN0aW9uIGpNZShlLHQpe3JldHVybiBCcihyZy5jb3VudChHaChlKS0xLGUpLHQsMil9ZnVuY3Rpb24gSXd0KGUpe3ZhciB0PWUuZ2V0RGF5KCk7cmV0dXJuIHQ+PTR8fHQ9PT0wP3pwKGUpOnpwLmNlaWwoZSl9ZnVuY3Rpb24gWE1lKGUsdCl7cmV0dXJuIGU9SXd0KGUpLEJyKHpwLmNvdW50KEdoKGUpLGUpKyhHaChlKS5nZXREYXkoKT09PTQpLHQsMil9ZnVuY3Rpb24gJE1lKGUpe3JldHVybiBlLmdldERheSgpfWZ1bmN0aW9uIEtNZShlLHQpe3JldHVybiBCcihyeS5jb3VudChHaChlKS0xLGUpLHQsMil9ZnVuY3Rpb24gWk1lKGUsdCl7cmV0dXJuIEJyKGUuZ2V0RnVsbFllYXIoKSUxMDAsdCwyKX1mdW5jdGlvbiBKTWUoZSx0KXtyZXR1cm4gZT1Jd3QoZSksQnIoZS5nZXRGdWxsWWVhcigpJTEwMCx0LDIpfWZ1bmN0aW9uIFFNZShlLHQpe3JldHVybiBCcihlLmdldEZ1bGxZZWFyKCklMWU0LHQsNCl9ZnVuY3Rpb24gdEVlKGUsdCl7dmFyIHI9ZS5nZXREYXkoKTtyZXR1cm4gZT1yPj00fHxyPT09MD96cChlKTp6cC5jZWlsKGUpLEJyKGUuZ2V0RnVsbFllYXIoKSUxZTQsdCw0KX1mdW5jdGlvbiBlRWUoZSl7dmFyIHQ9ZS5nZXRUaW1lem9uZU9mZnNldCgpO3JldHVybih0PjA/Ii0iOih0Kj0tMSwiKyIpKStCcih0LzYwfDAsIjAiLDIpK0JyKHQlNjAsIjAiLDIpfWZ1bmN0aW9uIEV3dChlLHQpe3JldHVybiBCcihlLmdldFVUQ0RhdGUoKSx0LDIpfWZ1bmN0aW9uIHJFZShlLHQpe3JldHVybiBCcihlLmdldFVUQ0hvdXJzKCksdCwyKX1mdW5jdGlvbiBuRWUoZSx0KXtyZXR1cm4gQnIoZS5nZXRVVENIb3VycygpJTEyfHwxMix0LDIpfWZ1bmN0aW9uIGlFZShlLHQpe3JldHVybiBCcigxK255LmNvdW50KFdoKGUpLGUpLHQsMyl9ZnVuY3Rpb24gTHd0KGUsdCl7cmV0dXJuIEJyKGUuZ2V0VVRDTWlsbGlzZWNvbmRzKCksdCwzKX1mdW5jdGlvbiBvRWUoZSx0KXtyZXR1cm4gTHd0KGUsdCkrIjAwMCJ9ZnVuY3Rpb24gYUVlKGUsdCl7cmV0dXJuIEJyKGUuZ2V0VVRDTW9udGgoKSsxLHQsMil9ZnVuY3Rpb24gc0VlKGUsdCl7cmV0dXJuIEJyKGUuZ2V0VVRDTWludXRlcygpLHQsMil9ZnVuY3Rpb24gbEVlKGUsdCl7cmV0dXJuIEJyKGUuZ2V0VVRDU2Vjb25kcygpLHQsMil9ZnVuY3Rpb24gY0VlKGUpe3ZhciB0PWUuZ2V0VVRDRGF5KCk7cmV0dXJuIHQ9PT0wPzc6dH1mdW5jdGlvbiB1RWUoZSx0KXtyZXR1cm4gQnIobmcuY291bnQoV2goZSktMSxlKSx0LDIpfWZ1bmN0aW9uIGt3dChlKXt2YXIgdD1lLmdldFVUQ0RheSgpO3JldHVybiB0Pj00fHx0PT09MD9GcChlKTpGcC5jZWlsKGUpfWZ1bmN0aW9uIGhFZShlLHQpe3JldHVybiBlPWt3dChlKSxCcihGcC5jb3VudChXaChlKSxlKSsoV2goZSkuZ2V0VVRDRGF5KCk9PT00KSx0LDIpfWZ1bmN0aW9uIGZFZShlKXtyZXR1cm4gZS5nZXRVVENEYXkoKX1mdW5jdGlvbiBwRWUoZSx0KXtyZXR1cm4gQnIob3kuY291bnQoV2goZSktMSxlKSx0LDIpfWZ1bmN0aW9uIGRFZShlLHQpe3JldHVybiBCcihlLmdldFVUQ0Z1bGxZZWFyKCklMTAwLHQsMil9ZnVuY3Rpb24gbUVlKGUsdCl7cmV0dXJuIGU9a3d0KGUpLEJyKGUuZ2V0VVRDRnVsbFllYXIoKSUxMDAsdCwyKX1mdW5jdGlvbiBnRWUoZSx0KXtyZXR1cm4gQnIoZS5nZXRVVENGdWxsWWVhcigpJTFlNCx0LDQpfWZ1bmN0aW9uIF9FZShlLHQpe3ZhciByPWUuZ2V0VVRDRGF5KCk7cmV0dXJuIGU9cj49NHx8cj09PTA/RnAoZSk6RnAuY2VpbChlKSxCcihlLmdldFVUQ0Z1bGxZZWFyKCklMWU0LHQsNCl9ZnVuY3Rpb24geUVlKCl7cmV0dXJuIiswMDAwIn1mdW5jdGlvbiBUd3QoKXtyZXR1cm4iJSJ9ZnVuY3Rpb24gQ3d0KGUpe3JldHVybitlfWZ1bmN0aW9uIEF3dChlKXtyZXR1cm4gTWF0aC5mbG9vcigrZS8xZTMpfXZhciBXYixTNSxTWCxheSxNNTt3OCh7ZGF0ZVRpbWU6IiV4LCAlWCIsZGF0ZToiJS1tLyUtZC8lWSIsdGltZToiJS1JOiVNOiVTICVwIixwZXJpb2RzOlsiQU0iLCJQTSJdLGRheXM6WyJTdW5kYXkiLCJNb25kYXkiLCJUdWVzZGF5IiwiV2VkbmVzZGF5IiwiVGh1cnNkYXkiLCJGcmlkYXkiLCJTYXR1cmRheSJdLHNob3J0RGF5czpbIlN1biIsIk1vbiIsIlR1ZSIsIldlZCIsIlRodSIsIkZyaSIsIlNhdCJdLG1vbnRoczpbIkphbnVhcnkiLCJGZWJydWFyeSIsIk1hcmNoIiwiQXByaWwiLCJNYXkiLCJKdW5lIiwiSnVseSIsIkF1Z3VzdCIsIlNlcHRlbWJlciIsIk9jdG9iZXIiLCJOb3ZlbWJlciIsIkRlY2VtYmVyIl0sc2hvcnRNb250aHM6WyJKYW4iLCJGZWIiLCJNYXIiLCJBcHIiLCJNYXkiLCJKdW4iLCJKdWwiLCJBdWciLCJTZXAiLCJPY3QiLCJOb3YiLCJEZWMiXX0pO2Z1bmN0aW9uIHc4KGUpe3JldHVybiBXYj13NShlKSxTNT1XYi5mb3JtYXQsU1g9V2IucGFyc2UsYXk9V2IudXRjRm9ybWF0LE01PVdiLnV0Y1BhcnNlLFdifXZhciBNWD0iJVktJW0tJWRUJUg6JU06JVMuJUxaIjtmdW5jdGlvbiB2RWUoZSl7cmV0dXJuIGUudG9JU09TdHJpbmcoKX12YXIgeEVlPURhdGUucHJvdG90eXBlLnRvSVNPU3RyaW5nP3ZFZTpheShNWCksUnd0PXhFZTtmdW5jdGlvbiBiRWUoZSl7dmFyIHQ9bmV3IERhdGUoZSk7cmV0dXJuIGlzTmFOKHQpP251bGw6dH12YXIgd0VlPStuZXcgRGF0ZSgiMjAwMC0wMS0wMVQwMDowMDowMC4wMDBaIik/YkVlOk01KE1YKSxOd3Q9d0VlO3ZhciBFNT0xZTMsVDU9RTUqNjAsQzU9VDUqNjAsQTU9QzUqMjQsU0VlPUE1KjcsRHd0PUE1KjMwLEVYPUE1KjM2NTtmdW5jdGlvbiBNRWUoZSl7cmV0dXJuIG5ldyBEYXRlKGUpfWZ1bmN0aW9uIEVFZShlKXtyZXR1cm4gZSBpbnN0YW5jZW9mIERhdGU/K2U6K25ldyBEYXRlKCtlKX1mdW5jdGlvbiBTOChlLHQscixuLGksbyxhLHMsbCl7dmFyIGM9bTUoaWksaWkpLHU9Yy5pbnZlcnQsaD1jLmRvbWFpbixmPWwoIi4lTCIpLHA9bCgiOiVTIiksZD1sKCIlSTolTSIpLGc9bCgiJUkgJXAiKSxfPWwoIiVhICVkIikseT1sKCIlYiAlZCIpLHg9bCgiJUIiKSxiPWwoIiVZIiksUz1bW2EsMSxFNV0sW2EsNSw1KkU1XSxbYSwxNSwxNSpFNV0sW2EsMzAsMzAqRTVdLFtvLDEsVDVdLFtvLDUsNSpUNV0sW28sMTUsMTUqVDVdLFtvLDMwLDMwKlQ1XSxbaSwxLEM1XSxbaSwzLDMqQzVdLFtpLDYsNipDNV0sW2ksMTIsMTIqQzVdLFtuLDEsQTVdLFtuLDIsMipBNV0sW3IsMSxTRWVdLFt0LDEsRHd0XSxbdCwzLDMqRHd0XSxbZSwxLEVYXV07ZnVuY3Rpb24gQyhrKXtyZXR1cm4oYShrKTxrP2Y6byhrKTxrP3A6aShrKTxrP2Q6bihrKTxrP2c6dChrKTxrP3Ioayk8az9fOnk6ZShrKTxrP3g6Yikoayl9ZnVuY3Rpb24gUChrLE8sRCxCKXtpZihrPT1udWxsJiYoaz0xMCksdHlwZW9mIGs9PSJudW1iZXIiKXt2YXIgST1NYXRoLmFicyhELU8pL2ssTD1vYihmdW5jdGlvbihSKXtyZXR1cm4gUlsyXX0pLnJpZ2h0KFMsSSk7TD09PVMubGVuZ3RoPyhCPXRsKE8vRVgsRC9FWCxrKSxrPWUpOkw/KEw9U1tJL1NbTC0xXVsyXTxTW0xdWzJdL0k/TC0xOkxdLEI9TFsxXSxrPUxbMF0pOihCPU1hdGgubWF4KHRsKE8sRCxrKSwxKSxrPXMpfXJldHVybiBCPT1udWxsP2s6ay5ldmVyeShCKX1yZXR1cm4gYy5pbnZlcnQ9ZnVuY3Rpb24oayl7cmV0dXJuIG5ldyBEYXRlKHUoaykpfSxjLmRvbWFpbj1mdW5jdGlvbihrKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD9oKFhfLmNhbGwoayxFRWUpKTpoKCkubWFwKE1FZSl9LGMudGlja3M9ZnVuY3Rpb24oayxPKXt2YXIgRD1oKCksQj1EWzBdLEk9RFtELmxlbmd0aC0xXSxMPUk8QixSO3JldHVybiBMJiYoUj1CLEI9SSxJPVIpLFI9UChrLEIsSSxPKSxSPVI/Ui5yYW5nZShCLEkrMSk6W10sTD9SLnJldmVyc2UoKTpSfSxjLnRpY2tGb3JtYXQ9ZnVuY3Rpb24oayxPKXtyZXR1cm4gTz09bnVsbD9DOmwoTyl9LGMubmljZT1mdW5jdGlvbihrLE8pe3ZhciBEPWgoKTtyZXR1cm4oaz1QKGssRFswXSxEW0QubGVuZ3RoLTFdLE8pKT9oKHU4KEQsaykpOmN9LGMuY29weT1mdW5jdGlvbigpe3JldHVybiBxaChjLFM4KGUsdCxyLG4saSxvLGEscyxsKSl9LGN9ZnVuY3Rpb24gWWIoKXtyZXR1cm4gZmkuYXBwbHkoUzgoR2gseTgscmcsdHksXzgsZzgsUV8sWl8sUzUpLmRvbWFpbihbbmV3IERhdGUoMmUzLDAsMSksbmV3IERhdGUoMmUzLDAsMildKSxhcmd1bWVudHMpfWZ1bmN0aW9uIE93dCgpe3JldHVybiBmaS5hcHBseShTOChXaCxiOCxuZyxueSx4OCx2OCxRXyxaXyxheSkuZG9tYWluKFtEYXRlLlVUQygyZTMsMCwxKSxEYXRlLlVUQygyZTMsMCwyKV0pLGFyZ3VtZW50cyl9ZnVuY3Rpb24gTTgoKXt2YXIgZT0wLHQ9MSxyLG4saSxvLGE9aWkscz0hMSxsO2Z1bmN0aW9uIGModSl7cmV0dXJuIGlzTmFOKHU9K3UpP2w6YShpPT09MD8uNToodT0obyh1KS1yKSppLHM/TWF0aC5tYXgoMCxNYXRoLm1pbigxLHUpKTp1KSl9cmV0dXJuIGMuZG9tYWluPWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPW8oZT0rdVswXSksbj1vKHQ9K3VbMV0pLGk9cj09PW4/MDoxLyhuLXIpLGMpOltlLHRdfSxjLmNsYW1wPWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhzPSEhdSxjKTpzfSxjLmludGVycG9sYXRvcj1mdW5jdGlvbih1KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYT11LGMpOmF9LGMudW5rbm93bj1mdW5jdGlvbih1KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obD11LGMpOmx9LGZ1bmN0aW9uKHUpe3JldHVybiBvPXUscj11KGUpLG49dSh0KSxpPXI9PT1uPzA6MS8obi1yKSxjfX1mdW5jdGlvbiBCcChlLHQpe3JldHVybiB0LmRvbWFpbihlLmRvbWFpbigpKS5pbnRlcnBvbGF0b3IoZS5pbnRlcnBvbGF0b3IoKSkuY2xhbXAoZS5jbGFtcCgpKS51bmtub3duKGUudW5rbm93bigpKX1mdW5jdGlvbiBFOCgpe3ZhciBlPWlsKE04KCkoaWkpKTtyZXR1cm4gZS5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIEJwKGUsRTgoKSl9LHNjLmFwcGx5KGUsYXJndW1lbnRzKX1mdW5jdGlvbiBUWCgpe3ZhciBlPWc1KE04KCkpLmRvbWFpbihbMSwxMF0pO3JldHVybiBlLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gQnAoZSxUWCgpKS5iYXNlKGUuYmFzZSgpKX0sc2MuYXBwbHkoZSxhcmd1bWVudHMpfWZ1bmN0aW9uIENYKCl7dmFyIGU9XzUoTTgoKSk7cmV0dXJuIGUuY29weT1mdW5jdGlvbigpe3JldHVybiBCcChlLENYKCkpLmNvbnN0YW50KGUuY29uc3RhbnQoKSl9LHNjLmFwcGx5KGUsYXJndW1lbnRzKX1mdW5jdGlvbiBUOCgpe3ZhciBlPXk1KE04KCkpO3JldHVybiBlLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gQnAoZSxUOCgpKS5leHBvbmVudChlLmV4cG9uZW50KCkpfSxzYy5hcHBseShlLGFyZ3VtZW50cyl9ZnVuY3Rpb24gend0KCl7cmV0dXJuIFQ4LmFwcGx5KG51bGwsYXJndW1lbnRzKS5leHBvbmVudCguNSl9ZnVuY3Rpb24gQzgoKXt2YXIgZT1bXSx0PWlpO2Z1bmN0aW9uIHIobil7aWYoIWlzTmFOKG49K24pKXJldHVybiB0KCh5cyhlLG4pLTEpLyhlLmxlbmd0aC0xKSl9cmV0dXJuIHIuZG9tYWluPWZ1bmN0aW9uKG4pe2lmKCFhcmd1bWVudHMubGVuZ3RoKXJldHVybiBlLnNsaWNlKCk7ZT1bXTtmb3IodmFyIGk9MCxvPW4ubGVuZ3RoLGE7aTxvOysraSlhPW5baV0sYSE9bnVsbCYmIWlzTmFOKGE9K2EpJiZlLnB1c2goYSk7cmV0dXJuIGUuc29ydChvYSkscn0sci5pbnRlcnBvbGF0b3I9ZnVuY3Rpb24obil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9bixyKTp0fSxyLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gQzgodCkuZG9tYWluKGUpfSxzYy5hcHBseShyLGFyZ3VtZW50cyl9ZnVuY3Rpb24gQTgoKXt2YXIgZT0wLHQ9LjUscj0xLG4saSxvLGEscyxsPWlpLGMsdT0hMSxoO2Z1bmN0aW9uIGYocCl7cmV0dXJuIGlzTmFOKHA9K3ApP2g6KHA9LjUrKChwPStjKHApKS1pKSoocDxpP2E6cyksbCh1P01hdGgubWF4KDAsTWF0aC5taW4oMSxwKSk6cCkpfXJldHVybiBmLmRvbWFpbj1mdW5jdGlvbihwKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj1jKGU9K3BbMF0pLGk9Yyh0PStwWzFdKSxvPWMocj0rcFsyXSksYT1uPT09aT8wOi41LyhpLW4pLHM9aT09PW8/MDouNS8oby1pKSxmKTpbZSx0LHJdfSxmLmNsYW1wPWZ1bmN0aW9uKHApe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh1PSEhcCxmKTp1fSxmLmludGVycG9sYXRvcj1mdW5jdGlvbihwKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obD1wLGYpOmx9LGYudW5rbm93bj1mdW5jdGlvbihwKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaD1wLGYpOmh9LGZ1bmN0aW9uKHApe3JldHVybiBjPXAsbj1wKGUpLGk9cCh0KSxvPXAociksYT1uPT09aT8wOi41LyhpLW4pLHM9aT09PW8/MDouNS8oby1pKSxmfX1mdW5jdGlvbiBQOCgpe3ZhciBlPWlsKEE4KCkoaWkpKTtyZXR1cm4gZS5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIEJwKGUsUDgoKSl9LHNjLmFwcGx5KGUsYXJndW1lbnRzKX1mdW5jdGlvbiBBWCgpe3ZhciBlPWc1KEE4KCkpLmRvbWFpbihbLjEsMSwxMF0pO3JldHVybiBlLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gQnAoZSxBWCgpKS5iYXNlKGUuYmFzZSgpKX0sc2MuYXBwbHkoZSxhcmd1bWVudHMpfWZ1bmN0aW9uIFBYKCl7dmFyIGU9XzUoQTgoKSk7cmV0dXJuIGUuY29weT1mdW5jdGlvbigpe3JldHVybiBCcChlLFBYKCkpLmNvbnN0YW50KGUuY29uc3RhbnQoKSl9LHNjLmFwcGx5KGUsYXJndW1lbnRzKX1mdW5jdGlvbiBJOCgpe3ZhciBlPXk1KEE4KCkpO3JldHVybiBlLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gQnAoZSxJOCgpKS5leHBvbmVudChlLmV4cG9uZW50KCkpfSxzYy5hcHBseShlLGFyZ3VtZW50cyl9ZnVuY3Rpb24gRnd0KCl7cmV0dXJuIEk4LmFwcGx5KG51bGwsYXJndW1lbnRzKS5leHBvbmVudCguNSl9ZnVuY3Rpb24gdGUoZSl7Zm9yKHZhciB0PWUubGVuZ3RoLzZ8MCxyPW5ldyBBcnJheSh0KSxuPTA7bjx0OylyW25dPSIjIitlLnNsaWNlKG4qNiwrK24qNik7cmV0dXJuIHJ9dmFyIGpiPXRlKCIxZjc3YjRmZjdmMGUyY2EwMmNkNjI3Mjg5NDY3YmQ4YzU2NGJlMzc3YzI3ZjdmN2ZiY2JkMjIxN2JlY2YiKTt2YXIgQnd0PXRlKCI3ZmM5N2ZiZWFlZDRmZGMwODZmZmZmOTkzODZjYjBmMDAyN2ZiZjViMTc2NjY2NjYiKTt2YXIgSHd0PXRlKCIxYjllNzdkOTVmMDI3NTcwYjNlNzI5OGE2NmE2MWVlNmFiMDJhNjc2MWQ2NjY2NjYiKTt2YXIgVnd0PXRlKCJhNmNlZTMxZjc4YjRiMmRmOGEzM2EwMmNmYjlhOTllMzFhMWNmZGJmNmZmZjdmMDBjYWIyZDY2YTNkOWFmZmZmOTliMTU5MjgiKTt2YXIgVXd0PXRlKCJmYmI0YWViM2NkZTNjY2ViYzVkZWNiZTRmZWQ5YTZmZmZmY2NlNWQ4YmRmZGRhZWNmMmYyZjIiKTt2YXIgcXd0PXRlKCJiM2UyY2RmZGNkYWNjYmQ1ZThmNGNhZTRlNmY1YzlmZmYyYWVmMWUyY2NjY2NjY2MiKTt2YXIgR3d0PXRlKCJlNDFhMWMzNzdlYjg0ZGFmNGE5ODRlYTNmZjdmMDBmZmZmMzNhNjU2MjhmNzgxYmY5OTk5OTkiKTt2YXIgV3d0PXRlKCI2NmMyYTVmYzhkNjI4ZGEwY2JlNzhhYzNhNmQ4NTRmZmQ5MmZlNWM0OTRiM2IzYjMiKTt2YXIgWXd0PXRlKCI4ZGQzYzdmZmZmYjNiZWJhZGFmYjgwNzI4MGIxZDNmZGI0NjJiM2RlNjlmY2NkZTVkOWQ5ZDliYzgwYmRjY2ViYzVmZmVkNmYiKTt2YXIgand0PXRlKCI0ZTc5YTdmMjhlMmNlMTU3NTk3NmI3YjI1OWExNGZlZGM5NDlhZjdhYTFmZjlkYTc5Yzc1NWZiYWIwYWIiKTtmdW5jdGlvbiBJZShlKXtyZXR1cm4gY0woZVtlLmxlbmd0aC0xXSl9dmFyIElYPW5ldyBBcnJheSgzKS5jb25jYXQoImQ4YjM2NWY1ZjVmNTVhYjRhYyIsImE2NjExYWRmYzI3ZDgwY2RjMTAxODU3MSIsImE2NjExYWRmYzI3ZGY1ZjVmNTgwY2RjMTAxODU3MSIsIjhjNTEwYWQ4YjM2NWY2ZThjM2M3ZWFlNTVhYjRhYzAxNjY1ZSIsIjhjNTEwYWQ4YjM2NWY2ZThjM2Y1ZjVmNWM3ZWFlNTVhYjRhYzAxNjY1ZSIsIjhjNTEwYWJmODEyZGRmYzI3ZGY2ZThjM2M3ZWFlNTgwY2RjMTM1OTc4ZjAxNjY1ZSIsIjhjNTEwYWJmODEyZGRmYzI3ZGY2ZThjM2Y1ZjVmNWM3ZWFlNTgwY2RjMTM1OTc4ZjAxNjY1ZSIsIjU0MzAwNThjNTEwYWJmODEyZGRmYzI3ZGY2ZThjM2M3ZWFlNTgwY2RjMTM1OTc4ZjAxNjY1ZTAwM2MzMCIsIjU0MzAwNThjNTEwYWJmODEyZGRmYzI3ZGY2ZThjM2Y1ZjVmNWM3ZWFlNTgwY2RjMTM1OTc4ZjAxNjY1ZTAwM2MzMCIpLm1hcCh0ZSksWHd0PUllKElYKTt2YXIgTFg9bmV3IEFycmF5KDMpLmNvbmNhdCgiYWY4ZGMzZjdmN2Y3N2ZiZjdiIiwiN2IzMjk0YzJhNWNmYTZkYmEwMDA4ODM3IiwiN2IzMjk0YzJhNWNmZjdmN2Y3YTZkYmEwMDA4ODM3IiwiNzYyYTgzYWY4ZGMzZTdkNGU4ZDlmMGQzN2ZiZjdiMWI3ODM3IiwiNzYyYTgzYWY4ZGMzZTdkNGU4ZjdmN2Y3ZDlmMGQzN2ZiZjdiMWI3ODM3IiwiNzYyYTgzOTk3MGFiYzJhNWNmZTdkNGU4ZDlmMGQzYTZkYmEwNWFhZTYxMWI3ODM3IiwiNzYyYTgzOTk3MGFiYzJhNWNmZTdkNGU4ZjdmN2Y3ZDlmMGQzYTZkYmEwNWFhZTYxMWI3ODM3IiwiNDAwMDRiNzYyYTgzOTk3MGFiYzJhNWNmZTdkNGU4ZDlmMGQzYTZkYmEwNWFhZTYxMWI3ODM3MDA0NDFiIiwiNDAwMDRiNzYyYTgzOTk3MGFiYzJhNWNmZTdkNGU4ZjdmN2Y3ZDlmMGQzYTZkYmEwNWFhZTYxMWI3ODM3MDA0NDFiIikubWFwKHRlKSwkd3Q9SWUoTFgpO3ZhciBrWD1uZXcgQXJyYXkoMykuY29uY2F0KCJlOWEzYzlmN2Y3ZjdhMWQ3NmEiLCJkMDFjOGJmMWI2ZGFiOGUxODY0ZGFjMjYiLCJkMDFjOGJmMWI2ZGFmN2Y3ZjdiOGUxODY0ZGFjMjYiLCJjNTFiN2RlOWEzYzlmZGUwZWZlNmY1ZDBhMWQ3NmE0ZDkyMjEiLCJjNTFiN2RlOWEzYzlmZGUwZWZmN2Y3ZjdlNmY1ZDBhMWQ3NmE0ZDkyMjEiLCJjNTFiN2RkZTc3YWVmMWI2ZGFmZGUwZWZlNmY1ZDBiOGUxODY3ZmJjNDE0ZDkyMjEiLCJjNTFiN2RkZTc3YWVmMWI2ZGFmZGUwZWZmN2Y3ZjdlNmY1ZDBiOGUxODY3ZmJjNDE0ZDkyMjEiLCI4ZTAxNTJjNTFiN2RkZTc3YWVmMWI2ZGFmZGUwZWZlNmY1ZDBiOGUxODY3ZmJjNDE0ZDkyMjEyNzY0MTkiLCI4ZTAxNTJjNTFiN2RkZTc3YWVmMWI2ZGFmZGUwZWZmN2Y3ZjdlNmY1ZDBiOGUxODY3ZmJjNDE0ZDkyMjEyNzY0MTkiKS5tYXAodGUpLEt3dD1JZShrWCk7dmFyIFJYPW5ldyBBcnJheSgzKS5jb25jYXQoIjk5OGVjM2Y3ZjdmN2YxYTM0MCIsIjVlM2M5OWIyYWJkMmZkYjg2M2U2NjEwMSIsIjVlM2M5OWIyYWJkMmY3ZjdmN2ZkYjg2M2U2NjEwMSIsIjU0Mjc4ODk5OGVjM2Q4ZGFlYmZlZTBiNmYxYTM0MGIzNTgwNiIsIjU0Mjc4ODk5OGVjM2Q4ZGFlYmY3ZjdmN2ZlZTBiNmYxYTM0MGIzNTgwNiIsIjU0Mjc4ODgwNzNhY2IyYWJkMmQ4ZGFlYmZlZTBiNmZkYjg2M2UwODIxNGIzNTgwNiIsIjU0Mjc4ODgwNzNhY2IyYWJkMmQ4ZGFlYmY3ZjdmN2ZlZTBiNmZkYjg2M2UwODIxNGIzNTgwNiIsIjJkMDA0YjU0Mjc4ODgwNzNhY2IyYWJkMmQ4ZGFlYmZlZTBiNmZkYjg2M2UwODIxNGIzNTgwNjdmM2IwOCIsIjJkMDA0YjU0Mjc4ODgwNzNhY2IyYWJkMmQ4ZGFlYmY3ZjdmN2ZlZTBiNmZkYjg2M2UwODIxNGIzNTgwNjdmM2IwOCIpLm1hcCh0ZSksWnd0PUllKFJYKTt2YXIgTlg9bmV3IEFycmF5KDMpLmNvbmNhdCgiZWY4YTYyZjdmN2Y3NjdhOWNmIiwiY2EwMDIwZjRhNTgyOTJjNWRlMDU3MWIwIiwiY2EwMDIwZjRhNTgyZjdmN2Y3OTJjNWRlMDU3MWIwIiwiYjIxODJiZWY4YTYyZmRkYmM3ZDFlNWYwNjdhOWNmMjE2NmFjIiwiYjIxODJiZWY4YTYyZmRkYmM3ZjdmN2Y3ZDFlNWYwNjdhOWNmMjE2NmFjIiwiYjIxODJiZDY2MDRkZjRhNTgyZmRkYmM3ZDFlNWYwOTJjNWRlNDM5M2MzMjE2NmFjIiwiYjIxODJiZDY2MDRkZjRhNTgyZmRkYmM3ZjdmN2Y3ZDFlNWYwOTJjNWRlNDM5M2MzMjE2NmFjIiwiNjcwMDFmYjIxODJiZDY2MDRkZjRhNTgyZmRkYmM3ZDFlNWYwOTJjNWRlNDM5M2MzMjE2NmFjMDUzMDYxIiwiNjcwMDFmYjIxODJiZDY2MDRkZjRhNTgyZmRkYmM3ZjdmN2Y3ZDFlNWYwOTJjNWRlNDM5M2MzMjE2NmFjMDUzMDYxIikubWFwKHRlKSxKd3Q9SWUoTlgpO3ZhciBEWD1uZXcgQXJyYXkoMykuY29uY2F0KCJlZjhhNjJmZmZmZmY5OTk5OTkiLCJjYTAwMjBmNGE1ODJiYWJhYmE0MDQwNDAiLCJjYTAwMjBmNGE1ODJmZmZmZmZiYWJhYmE0MDQwNDAiLCJiMjE4MmJlZjhhNjJmZGRiYzdlMGUwZTA5OTk5OTk0ZDRkNGQiLCJiMjE4MmJlZjhhNjJmZGRiYzdmZmZmZmZlMGUwZTA5OTk5OTk0ZDRkNGQiLCJiMjE4MmJkNjYwNGRmNGE1ODJmZGRiYzdlMGUwZTBiYWJhYmE4Nzg3ODc0ZDRkNGQiLCJiMjE4MmJkNjYwNGRmNGE1ODJmZGRiYzdmZmZmZmZlMGUwZTBiYWJhYmE4Nzg3ODc0ZDRkNGQiLCI2NzAwMWZiMjE4MmJkNjYwNGRmNGE1ODJmZGRiYzdlMGUwZTBiYWJhYmE4Nzg3ODc0ZDRkNGQxYTFhMWEiLCI2NzAwMWZiMjE4MmJkNjYwNGRmNGE1ODJmZGRiYzdmZmZmZmZlMGUwZTBiYWJhYmE4Nzg3ODc0ZDRkNGQxYTFhMWEiKS5tYXAodGUpLFF3dD1JZShEWCk7dmFyIE9YPW5ldyBBcnJheSgzKS5jb25jYXQoImZjOGQ1OWZmZmZiZjkxYmZkYiIsImQ3MTkxY2ZkYWU2MWFiZDllOTJjN2JiNiIsImQ3MTkxY2ZkYWU2MWZmZmZiZmFiZDllOTJjN2JiNiIsImQ3MzAyN2ZjOGQ1OWZlZTA5MGUwZjNmODkxYmZkYjQ1NzViNCIsImQ3MzAyN2ZjOGQ1OWZlZTA5MGZmZmZiZmUwZjNmODkxYmZkYjQ1NzViNCIsImQ3MzAyN2Y0NmQ0M2ZkYWU2MWZlZTA5MGUwZjNmOGFiZDllOTc0YWRkMTQ1NzViNCIsImQ3MzAyN2Y0NmQ0M2ZkYWU2MWZlZTA5MGZmZmZiZmUwZjNmOGFiZDllOTc0YWRkMTQ1NzViNCIsImE1MDAyNmQ3MzAyN2Y0NmQ0M2ZkYWU2MWZlZTA5MGUwZjNmOGFiZDllOTc0YWRkMTQ1NzViNDMxMzY5NSIsImE1MDAyNmQ3MzAyN2Y0NmQ0M2ZkYWU2MWZlZTA5MGZmZmZiZmUwZjNmOGFiZDllOTc0YWRkMTQ1NzViNDMxMzY5NSIpLm1hcCh0ZSksdFN0PUllKE9YKTt2YXIgelg9bmV3IEFycmF5KDMpLmNvbmNhdCgiZmM4ZDU5ZmZmZmJmOTFjZjYwIiwiZDcxOTFjZmRhZTYxYTZkOTZhMWE5NjQxIiwiZDcxOTFjZmRhZTYxZmZmZmJmYTZkOTZhMWE5NjQxIiwiZDczMDI3ZmM4ZDU5ZmVlMDhiZDllZjhiOTFjZjYwMWE5ODUwIiwiZDczMDI3ZmM4ZDU5ZmVlMDhiZmZmZmJmZDllZjhiOTFjZjYwMWE5ODUwIiwiZDczMDI3ZjQ2ZDQzZmRhZTYxZmVlMDhiZDllZjhiYTZkOTZhNjZiZDYzMWE5ODUwIiwiZDczMDI3ZjQ2ZDQzZmRhZTYxZmVlMDhiZmZmZmJmZDllZjhiYTZkOTZhNjZiZDYzMWE5ODUwIiwiYTUwMDI2ZDczMDI3ZjQ2ZDQzZmRhZTYxZmVlMDhiZDllZjhiYTZkOTZhNjZiZDYzMWE5ODUwMDA2ODM3IiwiYTUwMDI2ZDczMDI3ZjQ2ZDQzZmRhZTYxZmVlMDhiZmZmZmJmZDllZjhiYTZkOTZhNjZiZDYzMWE5ODUwMDA2ODM3IikubWFwKHRlKSxlU3Q9SWUoelgpO3ZhciBGWD1uZXcgQXJyYXkoMykuY29uY2F0KCJmYzhkNTlmZmZmYmY5OWQ1OTQiLCJkNzE5MWNmZGFlNjFhYmRkYTQyYjgzYmEiLCJkNzE5MWNmZGFlNjFmZmZmYmZhYmRkYTQyYjgzYmEiLCJkNTNlNGZmYzhkNTlmZWUwOGJlNmY1OTg5OWQ1OTQzMjg4YmQiLCJkNTNlNGZmYzhkNTlmZWUwOGJmZmZmYmZlNmY1OTg5OWQ1OTQzMjg4YmQiLCJkNTNlNGZmNDZkNDNmZGFlNjFmZWUwOGJlNmY1OThhYmRkYTQ2NmMyYTUzMjg4YmQiLCJkNTNlNGZmNDZkNDNmZGFlNjFmZWUwOGJmZmZmYmZlNmY1OThhYmRkYTQ2NmMyYTUzMjg4YmQiLCI5ZTAxNDJkNTNlNGZmNDZkNDNmZGFlNjFmZWUwOGJlNmY1OThhYmRkYTQ2NmMyYTUzMjg4YmQ1ZTRmYTIiLCI5ZTAxNDJkNTNlNGZmNDZkNDNmZGFlNjFmZWUwOGJmZmZmYmZlNmY1OThhYmRkYTQ2NmMyYTUzMjg4YmQ1ZTRmYTIiKS5tYXAodGUpLHJTdD1JZShGWCk7dmFyIEJYPW5ldyBBcnJheSgzKS5jb25jYXQoImU1ZjVmOTk5ZDhjOTJjYTI1ZiIsImVkZjhmYmIyZTJlMjY2YzJhNDIzOGI0NSIsImVkZjhmYmIyZTJlMjY2YzJhNDJjYTI1ZjAwNmQyYyIsImVkZjhmYmNjZWNlNjk5ZDhjOTY2YzJhNDJjYTI1ZjAwNmQyYyIsImVkZjhmYmNjZWNlNjk5ZDhjOTY2YzJhNDQxYWU3NjIzOGI0NTAwNTgyNCIsImY3ZmNmZGU1ZjVmOWNjZWNlNjk5ZDhjOTY2YzJhNDQxYWU3NjIzOGI0NTAwNTgyNCIsImY3ZmNmZGU1ZjVmOWNjZWNlNjk5ZDhjOTY2YzJhNDQxYWU3NjIzOGI0NTAwNmQyYzAwNDQxYiIpLm1hcCh0ZSksblN0PUllKEJYKTt2YXIgSFg9bmV3IEFycmF5KDMpLmNvbmNhdCgiZTBlY2Y0OWViY2RhODg1NmE3IiwiZWRmOGZiYjNjZGUzOGM5NmM2ODg0MTlkIiwiZWRmOGZiYjNjZGUzOGM5NmM2ODg1NmE3ODEwZjdjIiwiZWRmOGZiYmZkM2U2OWViY2RhOGM5NmM2ODg1NmE3ODEwZjdjIiwiZWRmOGZiYmZkM2U2OWViY2RhOGM5NmM2OGM2YmIxODg0MTlkNmUwMTZiIiwiZjdmY2ZkZTBlY2Y0YmZkM2U2OWViY2RhOGM5NmM2OGM2YmIxODg0MTlkNmUwMTZiIiwiZjdmY2ZkZTBlY2Y0YmZkM2U2OWViY2RhOGM5NmM2OGM2YmIxODg0MTlkODEwZjdjNGQwMDRiIikubWFwKHRlKSxpU3Q9SWUoSFgpO3ZhciBWWD1uZXcgQXJyYXkoMykuY29uY2F0KCJlMGYzZGJhOGRkYjU0M2EyY2EiLCJmMGY5ZThiYWU0YmM3YmNjYzQyYjhjYmUiLCJmMGY5ZThiYWU0YmM3YmNjYzQ0M2EyY2EwODY4YWMiLCJmMGY5ZThjY2ViYzVhOGRkYjU3YmNjYzQ0M2EyY2EwODY4YWMiLCJmMGY5ZThjY2ViYzVhOGRkYjU3YmNjYzQ0ZWIzZDMyYjhjYmUwODU4OWUiLCJmN2ZjZjBlMGYzZGJjY2ViYzVhOGRkYjU3YmNjYzQ0ZWIzZDMyYjhjYmUwODU4OWUiLCJmN2ZjZjBlMGYzZGJjY2ViYzVhOGRkYjU3YmNjYzQ0ZWIzZDMyYjhjYmUwODY4YWMwODQwODEiKS5tYXAodGUpLG9TdD1JZShWWCk7dmFyIFVYPW5ldyBBcnJheSgzKS5jb25jYXQoImZlZThjOGZkYmI4NGUzNGEzMyIsImZlZjBkOWZkY2M4YWZjOGQ1OWQ3MzAxZiIsImZlZjBkOWZkY2M4YWZjOGQ1OWUzNGEzM2IzMDAwMCIsImZlZjBkOWZkZDQ5ZWZkYmI4NGZjOGQ1OWUzNGEzM2IzMDAwMCIsImZlZjBkOWZkZDQ5ZWZkYmI4NGZjOGQ1OWVmNjU0OGQ3MzAxZjk5MDAwMCIsImZmZjdlY2ZlZThjOGZkZDQ5ZWZkYmI4NGZjOGQ1OWVmNjU0OGQ3MzAxZjk5MDAwMCIsImZmZjdlY2ZlZThjOGZkZDQ5ZWZkYmI4NGZjOGQ1OWVmNjU0OGQ3MzAxZmIzMDAwMDdmMDAwMCIpLm1hcCh0ZSksYVN0PUllKFVYKTt2YXIgcVg9bmV3IEFycmF5KDMpLmNvbmNhdCgiZWNlMmYwYTZiZGRiMWM5MDk5IiwiZjZlZmY3YmRjOWUxNjdhOWNmMDI4MThhIiwiZjZlZmY3YmRjOWUxNjdhOWNmMWM5MDk5MDE2YzU5IiwiZjZlZmY3ZDBkMWU2YTZiZGRiNjdhOWNmMWM5MDk5MDE2YzU5IiwiZjZlZmY3ZDBkMWU2YTZiZGRiNjdhOWNmMzY5MGMwMDI4MThhMDE2NDUwIiwiZmZmN2ZiZWNlMmYwZDBkMWU2YTZiZGRiNjdhOWNmMzY5MGMwMDI4MThhMDE2NDUwIiwiZmZmN2ZiZWNlMmYwZDBkMWU2YTZiZGRiNjdhOWNmMzY5MGMwMDI4MThhMDE2YzU5MDE0NjM2IikubWFwKHRlKSxzU3Q9SWUocVgpO3ZhciBHWD1uZXcgQXJyYXkoMykuY29uY2F0KCJlY2U3ZjJhNmJkZGIyYjhjYmUiLCJmMWVlZjZiZGM5ZTE3NGE5Y2YwNTcwYjAiLCJmMWVlZjZiZGM5ZTE3NGE5Y2YyYjhjYmUwNDVhOGQiLCJmMWVlZjZkMGQxZTZhNmJkZGI3NGE5Y2YyYjhjYmUwNDVhOGQiLCJmMWVlZjZkMGQxZTZhNmJkZGI3NGE5Y2YzNjkwYzAwNTcwYjAwMzRlN2IiLCJmZmY3ZmJlY2U3ZjJkMGQxZTZhNmJkZGI3NGE5Y2YzNjkwYzAwNTcwYjAwMzRlN2IiLCJmZmY3ZmJlY2U3ZjJkMGQxZTZhNmJkZGI3NGE5Y2YzNjkwYzAwNTcwYjAwNDVhOGQwMjM4NTgiKS5tYXAodGUpLGxTdD1JZShHWCk7dmFyIFdYPW5ldyBBcnJheSgzKS5jb25jYXQoImU3ZTFlZmM5OTRjN2RkMWM3NyIsImYxZWVmNmQ3YjVkOGRmNjViMGNlMTI1NiIsImYxZWVmNmQ3YjVkOGRmNjViMGRkMWM3Nzk4MDA0MyIsImYxZWVmNmQ0YjlkYWM5OTRjN2RmNjViMGRkMWM3Nzk4MDA0MyIsImYxZWVmNmQ0YjlkYWM5OTRjN2RmNjViMGU3Mjk4YWNlMTI1NjkxMDAzZiIsImY3ZjRmOWU3ZTFlZmQ0YjlkYWM5OTRjN2RmNjViMGU3Mjk4YWNlMTI1NjkxMDAzZiIsImY3ZjRmOWU3ZTFlZmQ0YjlkYWM5OTRjN2RmNjViMGU3Mjk4YWNlMTI1Njk4MDA0MzY3MDAxZiIpLm1hcCh0ZSksY1N0PUllKFdYKTt2YXIgWVg9bmV3IEFycmF5KDMpLmNvbmNhdCgiZmRlMGRkZmE5ZmI1YzUxYjhhIiwiZmVlYmUyZmJiNGI5Zjc2OGExYWUwMTdlIiwiZmVlYmUyZmJiNGI5Zjc2OGExYzUxYjhhN2EwMTc3IiwiZmVlYmUyZmNjNWMwZmE5ZmI1Zjc2OGExYzUxYjhhN2EwMTc3IiwiZmVlYmUyZmNjNWMwZmE5ZmI1Zjc2OGExZGQzNDk3YWUwMTdlN2EwMTc3IiwiZmZmN2YzZmRlMGRkZmNjNWMwZmE5ZmI1Zjc2OGExZGQzNDk3YWUwMTdlN2EwMTc3IiwiZmZmN2YzZmRlMGRkZmNjNWMwZmE5ZmI1Zjc2OGExZGQzNDk3YWUwMTdlN2EwMTc3NDkwMDZhIikubWFwKHRlKSx1U3Q9SWUoWVgpO3ZhciBqWD1uZXcgQXJyYXkoMykuY29uY2F0KCJlZGY4YjE3ZmNkYmIyYzdmYjgiLCJmZmZmY2NhMWRhYjQ0MWI2YzQyMjVlYTgiLCJmZmZmY2NhMWRhYjQ0MWI2YzQyYzdmYjgyNTM0OTQiLCJmZmZmY2NjN2U5YjQ3ZmNkYmI0MWI2YzQyYzdmYjgyNTM0OTQiLCJmZmZmY2NjN2U5YjQ3ZmNkYmI0MWI2YzQxZDkxYzAyMjVlYTgwYzJjODQiLCJmZmZmZDllZGY4YjFjN2U5YjQ3ZmNkYmI0MWI2YzQxZDkxYzAyMjVlYTgwYzJjODQiLCJmZmZmZDllZGY4YjFjN2U5YjQ3ZmNkYmI0MWI2YzQxZDkxYzAyMjVlYTgyNTM0OTQwODFkNTgiKS5tYXAodGUpLGhTdD1JZShqWCk7dmFyIFhYPW5ldyBBcnJheSgzKS5jb25jYXQoImY3ZmNiOWFkZGQ4ZTMxYTM1NCIsImZmZmZjY2MyZTY5OTc4YzY3OTIzODQ0MyIsImZmZmZjY2MyZTY5OTc4YzY3OTMxYTM1NDAwNjgzNyIsImZmZmZjY2Q5ZjBhM2FkZGQ4ZTc4YzY3OTMxYTM1NDAwNjgzNyIsImZmZmZjY2Q5ZjBhM2FkZGQ4ZTc4YzY3OTQxYWI1ZDIzODQ0MzAwNWEzMiIsImZmZmZlNWY3ZmNiOWQ5ZjBhM2FkZGQ4ZTc4YzY3OTQxYWI1ZDIzODQ0MzAwNWEzMiIsImZmZmZlNWY3ZmNiOWQ5ZjBhM2FkZGQ4ZTc4YzY3OTQxYWI1ZDIzODQ0MzAwNjgzNzAwNDUyOSIpLm1hcCh0ZSksZlN0PUllKFhYKTt2YXIgJFg9bmV3IEFycmF5KDMpLmNvbmNhdCgiZmZmN2JjZmVjNDRmZDk1ZjBlIiwiZmZmZmQ0ZmVkOThlZmU5OTI5Y2M0YzAyIiwiZmZmZmQ0ZmVkOThlZmU5OTI5ZDk1ZjBlOTkzNDA0IiwiZmZmZmQ0ZmVlMzkxZmVjNDRmZmU5OTI5ZDk1ZjBlOTkzNDA0IiwiZmZmZmQ0ZmVlMzkxZmVjNDRmZmU5OTI5ZWM3MDE0Y2M0YzAyOGMyZDA0IiwiZmZmZmU1ZmZmN2JjZmVlMzkxZmVjNDRmZmU5OTI5ZWM3MDE0Y2M0YzAyOGMyZDA0IiwiZmZmZmU1ZmZmN2JjZmVlMzkxZmVjNDRmZmU5OTI5ZWM3MDE0Y2M0YzAyOTkzNDA0NjYyNTA2IikubWFwKHRlKSxwU3Q9SWUoJFgpO3ZhciBLWD1uZXcgQXJyYXkoMykuY29uY2F0KCJmZmVkYTBmZWIyNGNmMDNiMjAiLCJmZmZmYjJmZWNjNWNmZDhkM2NlMzFhMWMiLCJmZmZmYjJmZWNjNWNmZDhkM2NmMDNiMjBiZDAwMjYiLCJmZmZmYjJmZWQ5NzZmZWIyNGNmZDhkM2NmMDNiMjBiZDAwMjYiLCJmZmZmYjJmZWQ5NzZmZWIyNGNmZDhkM2NmYzRlMmFlMzFhMWNiMTAwMjYiLCJmZmZmY2NmZmVkYTBmZWQ5NzZmZWIyNGNmZDhkM2NmYzRlMmFlMzFhMWNiMTAwMjYiLCJmZmZmY2NmZmVkYTBmZWQ5NzZmZWIyNGNmZDhkM2NmYzRlMmFlMzFhMWNiZDAwMjY4MDAwMjYiKS5tYXAodGUpLGRTdD1JZShLWCk7dmFyIFpYPW5ldyBBcnJheSgzKS5jb25jYXQoImRlZWJmNzllY2FlMTMxODJiZCIsImVmZjNmZmJkZDdlNzZiYWVkNjIxNzFiNSIsImVmZjNmZmJkZDdlNzZiYWVkNjMxODJiZDA4NTE5YyIsImVmZjNmZmM2ZGJlZjllY2FlMTZiYWVkNjMxODJiZDA4NTE5YyIsImVmZjNmZmM2ZGJlZjllY2FlMTZiYWVkNjQyOTJjNjIxNzFiNTA4NDU5NCIsImY3ZmJmZmRlZWJmN2M2ZGJlZjllY2FlMTZiYWVkNjQyOTJjNjIxNzFiNTA4NDU5NCIsImY3ZmJmZmRlZWJmN2M2ZGJlZjllY2FlMTZiYWVkNjQyOTJjNjIxNzFiNTA4NTE5YzA4MzA2YiIpLm1hcCh0ZSksbVN0PUllKFpYKTt2YXIgSlg9bmV3IEFycmF5KDMpLmNvbmNhdCgiZTVmNWUwYTFkOTliMzFhMzU0IiwiZWRmOGU5YmFlNGIzNzRjNDc2MjM4YjQ1IiwiZWRmOGU5YmFlNGIzNzRjNDc2MzFhMzU0MDA2ZDJjIiwiZWRmOGU5YzdlOWMwYTFkOTliNzRjNDc2MzFhMzU0MDA2ZDJjIiwiZWRmOGU5YzdlOWMwYTFkOTliNzRjNDc2NDFhYjVkMjM4YjQ1MDA1YTMyIiwiZjdmY2Y1ZTVmNWUwYzdlOWMwYTFkOTliNzRjNDc2NDFhYjVkMjM4YjQ1MDA1YTMyIiwiZjdmY2Y1ZTVmNWUwYzdlOWMwYTFkOTliNzRjNDc2NDFhYjVkMjM4YjQ1MDA2ZDJjMDA0NDFiIikubWFwKHRlKSxnU3Q9SWUoSlgpO3ZhciBRWD1uZXcgQXJyYXkoMykuY29uY2F0KCJmMGYwZjBiZGJkYmQ2MzYzNjMiLCJmN2Y3ZjdjY2NjY2M5Njk2OTY1MjUyNTIiLCJmN2Y3ZjdjY2NjY2M5Njk2OTY2MzYzNjMyNTI1MjUiLCJmN2Y3ZjdkOWQ5ZDliZGJkYmQ5Njk2OTY2MzYzNjMyNTI1MjUiLCJmN2Y3ZjdkOWQ5ZDliZGJkYmQ5Njk2OTY3MzczNzM1MjUyNTIyNTI1MjUiLCJmZmZmZmZmMGYwZjBkOWQ5ZDliZGJkYmQ5Njk2OTY3MzczNzM1MjUyNTIyNTI1MjUiLCJmZmZmZmZmMGYwZjBkOWQ5ZDliZGJkYmQ5Njk2OTY3MzczNzM1MjUyNTIyNTI1MjUwMDAwMDAiKS5tYXAodGUpLF9TdD1JZShRWCk7dmFyIHQkPW5ldyBBcnJheSgzKS5jb25jYXQoImVmZWRmNWJjYmRkYzc1NmJiMSIsImYyZjBmN2NiYzllMjllOWFjODZhNTFhMyIsImYyZjBmN2NiYzllMjllOWFjODc1NmJiMTU0Mjc4ZiIsImYyZjBmN2RhZGFlYmJjYmRkYzllOWFjODc1NmJiMTU0Mjc4ZiIsImYyZjBmN2RhZGFlYmJjYmRkYzllOWFjODgwN2RiYTZhNTFhMzRhMTQ4NiIsImZjZmJmZGVmZWRmNWRhZGFlYmJjYmRkYzllOWFjODgwN2RiYTZhNTFhMzRhMTQ4NiIsImZjZmJmZGVmZWRmNWRhZGFlYmJjYmRkYzllOWFjODgwN2RiYTZhNTFhMzU0Mjc4ZjNmMDA3ZCIpLm1hcCh0ZSkseVN0PUllKHQkKTt2YXIgZSQ9bmV3IEFycmF5KDMpLmNvbmNhdCgiZmVlMGQyZmM5MjcyZGUyZDI2IiwiZmVlNWQ5ZmNhZTkxZmI2YTRhY2IxODFkIiwiZmVlNWQ5ZmNhZTkxZmI2YTRhZGUyZDI2YTUwZjE1IiwiZmVlNWQ5ZmNiYmExZmM5MjcyZmI2YTRhZGUyZDI2YTUwZjE1IiwiZmVlNWQ5ZmNiYmExZmM5MjcyZmI2YTRhZWYzYjJjY2IxODFkOTkwMDBkIiwiZmZmNWYwZmVlMGQyZmNiYmExZmM5MjcyZmI2YTRhZWYzYjJjY2IxODFkOTkwMDBkIiwiZmZmNWYwZmVlMGQyZmNiYmExZmM5MjcyZmI2YTRhZWYzYjJjY2IxODFkYTUwZjE1NjcwMDBkIikubWFwKHRlKSx2U3Q9SWUoZSQpO3ZhciByJD1uZXcgQXJyYXkoMykuY29uY2F0KCJmZWU2Y2VmZGFlNmJlNjU1MGQiLCJmZWVkZGVmZGJlODVmZDhkM2NkOTQ3MDEiLCJmZWVkZGVmZGJlODVmZDhkM2NlNjU1MGRhNjM2MDMiLCJmZWVkZGVmZGQwYTJmZGFlNmJmZDhkM2NlNjU1MGRhNjM2MDMiLCJmZWVkZGVmZGQwYTJmZGFlNmJmZDhkM2NmMTY5MTNkOTQ4MDE4YzJkMDQiLCJmZmY1ZWJmZWU2Y2VmZGQwYTJmZGFlNmJmZDhkM2NmMTY5MTNkOTQ4MDE4YzJkMDQiLCJmZmY1ZWJmZWU2Y2VmZGQwYTJmZGFlNmJmZDhkM2NmMTY5MTNkOTQ4MDFhNjM2MDM3ZjI3MDQiKS5tYXAodGUpLHhTdD1JZShyJCk7ZnVuY3Rpb24gYlN0KGUpe3JldHVybiBlPU1hdGgubWF4KDAsTWF0aC5taW4oMSxlKSksInJnYigiK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQoLTQuNTQtZSooMzUuMzQtZSooMjM4MS43My1lKig2NDAyLjctZSooNzAyNC43Mi1lKjI3MTAuNTcpKSkpKSkpKyIsICIrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCgzMi40OStlKigxNzAuNzMrZSooNTIuODItZSooMTMxLjQ2LWUqKDE3Ni41OC1lKjY3LjM3KSkpKSkpKSsiLCAiK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQoODEuMjQrZSooNDQyLjM2LWUqKDI0ODIuNDMtZSooNjE2Ny4yNC1lKig2NjE0Ljk0LWUqMjQ3NS42NykpKSkpKSkrIikifXZhciB3U3Q9RV8obGEoMzAwLC41LDApLGxhKC0yNDAsLjUsMSkpO3ZhciBTU3Q9RV8obGEoLTEwMCwuNzUsLjM1KSxsYSg4MCwxLjUsLjgpKSxNU3Q9RV8obGEoMjYwLC43NSwuMzUpLGxhKDgwLDEuNSwuOCkpLEw4PWxhKCk7ZnVuY3Rpb24gRVN0KGUpeyhlPDB8fGU+MSkmJihlLT1NYXRoLmZsb29yKGUpKTt2YXIgdD1NYXRoLmFicyhlLS41KTtyZXR1cm4gTDguaD0zNjAqZS0xMDAsTDgucz0xLjUtMS41KnQsTDgubD0uOC0uOSp0LEw4KyIifXZhciBrOD1jdSgpLFRFZT1NYXRoLlBJLzMsQ0VlPU1hdGguUEkqMi8zO2Z1bmN0aW9uIFRTdChlKXt2YXIgdDtyZXR1cm4gZT0oLjUtZSkqTWF0aC5QSSxrOC5yPTI1NSoodD1NYXRoLnNpbihlKSkqdCxrOC5nPTI1NSoodD1NYXRoLnNpbihlK1RFZSkpKnQsazguYj0yNTUqKHQ9TWF0aC5zaW4oZStDRWUpKSp0LGs4KyIifWZ1bmN0aW9uIENTdChlKXtyZXR1cm4gZT1NYXRoLm1heCgwLE1hdGgubWluKDEsZSkpLCJyZ2IoIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKDM0LjYxK2UqKDExNzIuMzMtZSooMTA3OTMuNTYtZSooMzMzMDAuMTItZSooMzgzOTQuNDktZSoxNDgyNS4wNSkpKSkpKSkrIiwgIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKDIzLjMxK2UqKDU1Ny4zMytlKigxMjI1LjMzLWUqKDM1NzQuOTYtZSooMTA3My43NytlKjcwNy41NikpKSkpKSkrIiwgIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKDI3LjIrZSooMzIxMS4xLWUqKDE1MzI3Ljk3LWUqKDI3ODE0LWUqKDIyNTY5LjE4LWUqNjgzOC42NikpKSkpKSkrIikifWZ1bmN0aW9uIFI4KGUpe3ZhciB0PWUubGVuZ3RoO3JldHVybiBmdW5jdGlvbihyKXtyZXR1cm4gZVtNYXRoLm1heCgwLE1hdGgubWluKHQtMSxNYXRoLmZsb29yKHIqdCkpKV19fXZhciBBU3Q9UjgodGUoIjQ0MDE1NDQ0MDI1NjQ1MDQ1NzQ1MDU1OTQ2MDc1YTQ2MDg1YzQ2MGE1ZDQ2MGI1ZTQ3MGQ2MDQ3MGU2MTQ3MTA2MzQ3MTE2NDQ3MTM2NTQ4MTQ2NzQ4MTY2ODQ4MTc2OTQ4MTg2YTQ4MWE2YzQ4MWI2ZDQ4MWM2ZTQ4MWQ2ZjQ4MWY3MDQ4MjA3MTQ4MjE3MzQ4MjM3NDQ4MjQ3NTQ4MjU3NjQ4MjY3NzQ4Mjg3ODQ4Mjk3OTQ3MmE3YTQ3MmM3YTQ3MmQ3YjQ3MmU3YzQ3MmY3ZDQ2MzA3ZTQ2MzI3ZTQ2MzM3ZjQ2MzQ4MDQ1MzU4MTQ1Mzc4MTQ1Mzg4MjQ0Mzk4MzQ0M2E4MzQ0M2I4NDQzM2Q4NDQzM2U4NTQyM2Y4NTQyNDA4NjQyNDE4NjQxNDI4NzQxNDQ4NzQwNDU4ODQwNDY4ODNmNDc4ODNmNDg4OTNlNDk4OTNlNGE4OTNlNGM4YTNkNGQ4YTNkNGU4YTNjNGY4YTNjNTA4YjNiNTE4YjNiNTI4YjNhNTM4YjNhNTQ4YzM5NTU4YzM5NTY4YzM4NTg4YzM4NTk4YzM3NWE4YzM3NWI4ZDM2NWM4ZDM2NWQ4ZDM1NWU4ZDM1NWY4ZDM0NjA4ZDM0NjE4ZDMzNjI4ZDMzNjM4ZDMyNjQ4ZTMyNjU4ZTMxNjY4ZTMxNjc4ZTMxNjg4ZTMwNjk4ZTMwNmE4ZTJmNmI4ZTJmNmM4ZTJlNmQ4ZTJlNmU4ZTJlNmY4ZTJkNzA4ZTJkNzE4ZTJjNzE4ZTJjNzI4ZTJjNzM4ZTJiNzQ4ZTJiNzU4ZTJhNzY4ZTJhNzc4ZTJhNzg4ZTI5Nzk4ZTI5N2E4ZTI5N2I4ZTI4N2M4ZTI4N2Q4ZTI3N2U4ZTI3N2Y4ZTI3ODA4ZTI2ODE4ZTI2ODI4ZTI2ODI4ZTI1ODM4ZTI1ODQ4ZTI1ODU4ZTI0ODY4ZTI0ODc4ZTIzODg4ZTIzODk4ZTIzOGE4ZDIyOGI4ZDIyOGM4ZDIyOGQ4ZDIxOGU4ZDIxOGY4ZDIxOTA4ZDIxOTE4YzIwOTI4YzIwOTI4YzIwOTM4YzFmOTQ4YzFmOTU4YjFmOTY4YjFmOTc4YjFmOTg4YjFmOTk4YTFmOWE4YTFlOWI4YTFlOWM4OTFlOWQ4OTFmOWU4OTFmOWY4ODFmYTA4ODFmYTE4ODFmYTE4NzFmYTI4NzIwYTM4NjIwYTQ4NjIxYTU4NTIxYTY4NTIyYTc4NTIyYTg4NDIzYTk4MzI0YWE4MzI1YWI4MjI1YWM4MjI2YWQ4MTI3YWQ4MTI4YWU4MDI5YWY3ZjJhYjA3ZjJjYjE3ZTJkYjI3ZDJlYjM3YzJmYjQ3YzMxYjU3YjMyYjY3YTM0YjY3OTM1Yjc3OTM3Yjg3ODM4Yjk3NzNhYmE3NjNiYmI3NTNkYmM3NDNmYmM3MzQwYmQ3MjQyYmU3MTQ0YmY3MDQ2YzA2ZjQ4YzE2ZTRhYzE2ZDRjYzI2YzRlYzM2YjUwYzQ2YTUyYzU2OTU0YzU2ODU2YzY2NzU4Yzc2NTVhYzg2NDVjYzg2MzVlYzk2MjYwY2E2MDYzY2I1ZjY1Y2I1ZTY3Y2M1YzY5Y2Q1YjZjY2Q1YTZlY2U1ODcwY2Y1NzczZDA1Njc1ZDA1NDc3ZDE1MzdhZDE1MTdjZDI1MDdmZDM0ZTgxZDM0ZDg0ZDQ0Yjg2ZDU0OTg5ZDU0ODhiZDY0NjhlZDY0NTkwZDc0MzkzZDc0MTk1ZDg0MDk4ZDgzZTliZDkzYzlkZDkzYmEwZGEzOWEyZGEzN2E1ZGIzNmE4ZGIzNGFhZGMzMmFkZGMzMGIwZGQyZmIyZGQyZGI1ZGUyYmI4ZGUyOWJhZGUyOGJkZGYyNmMwZGYyNWMyZGYyM2M1ZTAyMWM4ZTAyMGNhZTExZmNkZTExZGQwZTExY2QyZTIxYmQ1ZTIxYWQ4ZTIxOWRhZTMxOWRkZTMxOGRmZTMxOGUyZTQxOGU1ZTQxOWU3ZTQxOWVhZTUxYWVjZTUxYmVmZTUxY2YxZTUxZGY0ZTYxZWY2ZTYyMGY4ZTYyMWZiZTcyM2ZkZTcyNSIpKSxQU3Q9UjgodGUoIjAwMDAwNDAxMDAwNTAxMDEwNjAxMDEwODAyMDEwOTAyMDIwYjAyMDIwZDAzMDMwZjAzMDMxMjA0MDQxNDA1MDQxNjA2MDUxODA2MDUxYTA3MDYxYzA4MDcxZTA5MDcyMDBhMDgyMjBiMDkyNDBjMDkyNjBkMGEyOTBlMGIyYjEwMGIyZDExMGMyZjEyMGQzMTEzMGQzNDE0MGUzNjE1MGUzODE2MGYzYjE4MGYzZDE5MTAzZjFhMTA0MjFjMTA0NDFkMTE0NzFlMTE0OTIwMTE0YjIxMTE0ZTIyMTE1MDI0MTI1MzI1MTI1NTI3MTI1ODI5MTE1YTJhMTE1YzJjMTE1ZjJkMTE2MTJmMTE2MzMxMTE2NTMzMTA2NzM0MTA2OTM2MTA2YjM4MTA2YzM5MGY2ZTNiMGY3MDNkMGY3MTNmMGY3MjQwMGY3NDQyMGY3NTQ0MGY3NjQ1MTA3NzQ3MTA3ODQ5MTA3ODRhMTA3OTRjMTE3YTRlMTE3YjRmMTI3YjUxMTI3YzUyMTM3YzU0MTM3ZDU2MTQ3ZDU3MTU3ZTU5MTU3ZTVhMTY3ZTVjMTY3ZjVkMTc3ZjVmMTg3ZjYwMTg4MDYyMTk4MDY0MWE4MDY1MWE4MDY3MWI4MDY4MWM4MTZhMWM4MTZiMWQ4MTZkMWQ4MTZlMWU4MTcwMWY4MTcyMWY4MTczMjA4MTc1MjE4MTc2MjE4MTc4MjI4MTc5MjI4MjdiMjM4MjdjMjM4MjdlMjQ4MjgwMjU4MjgxMjU4MTgzMjY4MTg0MjY4MTg2Mjc4MTg4Mjc4MTg5Mjg4MThiMjk4MThjMjk4MThlMmE4MTkwMmE4MTkxMmI4MTkzMmI4MDk0MmM4MDk2MmM4MDk4MmQ4MDk5MmQ4MDliMmU3ZjljMmU3ZjllMmY3ZmEwMmY3ZmExMzA3ZWEzMzA3ZWE1MzE3ZWE2MzE3ZGE4MzI3ZGFhMzM3ZGFiMzM3Y2FkMzQ3Y2FlMzQ3YmIwMzU3YmIyMzU3YmIzMzY3YWI1MzY3YWI3Mzc3OWI4Mzc3OWJhMzg3OGJjMzk3OGJkMzk3N2JmM2E3N2MwM2E3NmMyM2I3NWM0M2M3NWM1M2M3NGM3M2Q3M2M4M2U3M2NhM2U3MmNjM2Y3MWNkNDA3MWNmNDA3MGQwNDE2ZmQyNDI2ZmQzNDM2ZWQ1NDQ2ZGQ2NDU2Y2Q4NDU2Y2Q5NDY2YmRiNDc2YWRjNDg2OWRlNDk2OGRmNGE2OGUwNGM2N2UyNGQ2NmUzNGU2NWU0NGY2NGU1NTA2NGU3NTI2M2U4NTM2MmU5NTQ2MmVhNTY2MWViNTc2MGVjNTg2MGVkNWE1ZmVlNWI1ZWVmNWQ1ZWYwNWY1ZWYxNjA1ZGYyNjI1ZGYyNjQ1Y2YzNjU1Y2Y0Njc1Y2Y0Njk1Y2Y1NmI1Y2Y2NmM1Y2Y2NmU1Y2Y3NzA1Y2Y3NzI1Y2Y4NzQ1Y2Y4NzY1Y2Y5Nzg1ZGY5Nzk1ZGY5N2I1ZGZhN2Q1ZWZhN2Y1ZWZhODE1ZmZiODM1ZmZiODU2MGZiODc2MWZjODk2MWZjOGE2MmZjOGM2M2ZjOGU2NGZjOTA2NWZkOTI2NmZkOTQ2N2ZkOTY2OGZkOTg2OWZkOWE2YWZkOWI2YmZlOWQ2Y2ZlOWY2ZGZlYTE2ZWZlYTM2ZmZlYTU3MWZlYTc3MmZlYTk3M2ZlYWE3NGZlYWM3NmZlYWU3N2ZlYjA3OGZlYjI3YWZlYjQ3YmZlYjY3Y2ZlYjc3ZWZlYjk3ZmZlYmI4MWZlYmQ4MmZlYmY4NGZlYzE4NWZlYzI4N2ZlYzQ4OGZlYzY4YWZlYzg4Y2ZlY2E4ZGZlY2M4ZmZlY2Q5MGZlY2Y5MmZlZDE5NGZlZDM5NWZlZDU5N2ZlZDc5OWZlZDg5YWZkZGE5Y2ZkZGM5ZWZkZGVhMGZkZTBhMWZkZTJhM2ZkZTNhNWZkZTVhN2ZkZTdhOWZkZTlhYWZkZWJhY2ZjZWNhZWZjZWViMGZjZjBiMmZjZjJiNGZjZjRiNmZjZjZiOGZjZjdiOWZjZjliYmZjZmJiZGZjZmRiZiIpKSxJU3Q9UjgodGUoIjAwMDAwNDAxMDAwNTAxMDEwNjAxMDEwODAyMDEwYTAyMDIwYzAyMDIwZTAzMDIxMDA0MDMxMjA0MDMxNDA1MDQxNzA2MDQxOTA3MDUxYjA4MDUxZDA5MDYxZjBhMDcyMjBiMDcyNDBjMDgyNjBkMDgyOTBlMDkyYjEwMDkyZDExMGEzMDEyMGEzMjE0MGIzNDE1MGIzNzE2MGIzOTE4MGMzYzE5MGMzZTFiMGM0MTFjMGM0MzFlMGM0NTFmMGM0ODIxMGM0YTIzMGM0YzI0MGM0ZjI2MGM1MTI4MGI1MzI5MGI1NTJiMGI1NzJkMGI1OTJmMGE1YjMxMGE1YzMyMGE1ZTM0MGE1ZjM2MDk2MTM4MDk2MjM5MDk2MzNiMDk2NDNkMDk2NTNlMDk2NjQwMGE2NzQyMGE2ODQ0MGE2ODQ1MGE2OTQ3MGI2YTQ5MGI2YTRhMGM2YjRjMGM2YjRkMGQ2YzRmMGQ2YzUxMGU2YzUyMGU2ZDU0MGY2ZDU1MGY2ZDU3MTA2ZTU5MTA2ZTVhMTE2ZTVjMTI2ZTVkMTI2ZTVmMTM2ZTYxMTM2ZTYyMTQ2ZTY0MTU2ZTY1MTU2ZTY3MTY2ZTY5MTY2ZTZhMTc2ZTZjMTg2ZTZkMTg2ZTZmMTk2ZTcxMTk2ZTcyMWE2ZTc0MWE2ZTc1MWI2ZTc3MWM2ZDc4MWM2ZDdhMWQ2ZDdjMWQ2ZDdkMWU2ZDdmMWU2YzgwMWY2YzgyMjA2Yzg0MjA2Yjg1MjE2Yjg3MjE2Yjg4MjI2YThhMjI2YThjMjM2OThkMjM2OThmMjQ2OTkwMjU2ODkyMjU2ODkzMjY2Nzk1MjY2Nzk3Mjc2Njk4Mjc2NjlhMjg2NTliMjk2NDlkMjk2NDlmMmE2M2EwMmE2M2EyMmI2MmEzMmM2MWE1MmM2MGE2MmQ2MGE4MmU1ZmE5MmU1ZWFiMmY1ZWFkMzA1ZGFlMzA1Y2IwMzE1YmIxMzI1YWIzMzI1YWI0MzM1OWI2MzQ1OGI3MzU1N2I5MzU1NmJhMzY1NWJjMzc1NGJkMzg1M2JmMzk1MmMwM2E1MWMxM2E1MGMzM2I0ZmM0M2M0ZWM2M2Q0ZGM3M2U0Y2M4M2Y0YmNhNDA0YWNiNDE0OWNjNDI0OGNlNDM0N2NmNDQ0NmQwNDU0NWQyNDY0NGQzNDc0M2Q0NDg0MmQ1NGE0MWQ3NGIzZmQ4NGMzZWQ5NGQzZGRhNGUzY2RiNTAzYmRkNTEzYWRlNTIzOGRmNTMzN2UwNTUzNmUxNTYzNWUyNTczNGUzNTkzM2U0NWEzMWU1NWMzMGU2NWQyZmU3NWUyZWU4NjAyZGU5NjEyYmVhNjMyYWViNjQyOWViNjYyOGVjNjcyNmVkNjkyNWVlNmEyNGVmNmMyM2VmNmUyMWYwNmYyMGYxNzExZmYxNzMxZGYyNzQxY2YzNzYxYmYzNzgxOWY0NzkxOGY1N2IxN2Y1N2QxNWY2N2UxNGY2ODAxM2Y3ODIxMmY3ODQxMGY4ODUwZmY4ODcwZWY4ODkwY2Y5OGIwYmY5OGMwYWY5OGUwOWZhOTAwOGZhOTIwN2ZhOTQwN2ZiOTYwNmZiOTcwNmZiOTkwNmZiOWIwNmZiOWQwN2ZjOWYwN2ZjYTEwOGZjYTMwOWZjYTUwYWZjYTYwY2ZjYTgwZGZjYWEwZmZjYWMxMWZjYWUxMmZjYjAxNGZjYjIxNmZjYjQxOGZiYjYxYWZiYjgxZGZiYmExZmZiYmMyMWZiYmUyM2ZhYzAyNmZhYzIyOGZhYzQyYWZhYzYyZGY5YzcyZmY5YzkzMmY5Y2IzNWY4Y2QzN2Y4Y2YzYWY3ZDEzZGY3ZDM0MGY2ZDU0M2Y2ZDc0NmY1ZDk0OWY1ZGI0Y2Y0ZGQ0ZmY0ZGY1M2Y0ZTE1NmYzZTM1YWYzZTU1ZGYyZTY2MWYyZTg2NWYyZWE2OWYxZWM2ZGYxZWQ3MWYxZWY3NWYxZjE3OWYyZjI3ZGYyZjQ4MmYzZjU4NmYzZjY4YWY0Zjg4ZWY1Zjk5MmY2ZmE5NmY4ZmI5YWY5ZmM5ZGZhZmRhMWZjZmZhNCIpKSxMU3Q9UjgodGUoIjBkMDg4NzEwMDc4ODEzMDc4OTE2MDc4YTE5MDY4YzFiMDY4ZDFkMDY4ZTIwMDY4ZjIyMDY5MDI0MDY5MTI2MDU5MTI4MDU5MjJhMDU5MzJjMDU5NDJlMDU5NTJmMDU5NjMxMDU5NzMzMDU5NzM1MDQ5ODM3MDQ5OTM4MDQ5YTNhMDQ5YTNjMDQ5YjNlMDQ5YzNmMDQ5YzQxMDQ5ZDQzMDM5ZTQ0MDM5ZTQ2MDM5ZjQ4MDM5ZjQ5MDNhMDRiMDNhMTRjMDJhMTRlMDJhMjUwMDJhMjUxMDJhMzUzMDJhMzU1MDJhNDU2MDFhNDU4MDFhNDU5MDFhNTViMDFhNTVjMDFhNjVlMDFhNjYwMDFhNjYxMDBhNzYzMDBhNzY0MDBhNzY2MDBhNzY3MDBhODY5MDBhODZhMDBhODZjMDBhODZlMDBhODZmMDBhODcxMDBhODcyMDFhODc0MDFhODc1MDFhODc3MDFhODc4MDFhODdhMDJhODdiMDJhODdkMDNhODdlMDNhODgwMDRhODgxMDRhNzgzMDVhNzg0MDVhNzg2MDZhNjg3MDdhNjg4MDhhNjhhMDlhNThiMGFhNThkMGJhNThlMGNhNDhmMGRhNDkxMGVhMzkyMGZhMzk0MTBhMjk1MTFhMTk2MTNhMTk4MTRhMDk5MTU5ZjlhMTY5ZjljMTc5ZTlkMTg5ZDllMTk5ZGEwMWE5Y2ExMWI5YmEyMWQ5YWEzMWU5YWE1MWY5OWE2MjA5OGE3MjE5N2E4MjI5NmFhMjM5NWFiMjQ5NGFjMjY5NGFkMjc5M2FlMjg5MmIwMjk5MWIxMmE5MGIyMmI4ZmIzMmM4ZWI0MmU4ZGI1MmY4Y2I2MzA4YmI3MzE4YWI4MzI4OWJhMzM4OGJiMzQ4OGJjMzU4N2JkMzc4NmJlMzg4NWJmMzk4NGMwM2E4M2MxM2I4MmMyM2M4MWMzM2Q4MGM0M2U3ZmM1NDA3ZWM2NDE3ZGM3NDI3Y2M4NDM3YmM5NDQ3YWNhNDU3YWNiNDY3OWNjNDc3OGNjNDk3N2NkNGE3NmNlNGI3NWNmNGM3NGQwNGQ3M2QxNGU3MmQyNGY3MWQzNTE3MWQ0NTI3MGQ1NTM2ZmQ1NTQ2ZWQ2NTU2ZGQ3NTY2Y2Q4NTc2YmQ5NTg2YWRhNWE2YWRhNWI2OWRiNWM2OGRjNWQ2N2RkNWU2NmRlNWY2NWRlNjE2NGRmNjI2M2UwNjM2M2UxNjQ2MmUyNjU2MWUyNjY2MGUzNjg1ZmU0Njk1ZWU1NmE1ZGU1NmI1ZGU2NmM1Y2U3NmU1YmU3NmY1YWU4NzA1OWU5NzE1OGU5NzI1N2VhNzQ1N2ViNzU1NmViNzY1NWVjNzc1NGVkNzk1M2VkN2E1MmVlN2I1MWVmN2M1MWVmN2U1MGYwN2Y0ZmYwODA0ZWYxODE0ZGYxODM0Y2YyODQ0YmYzODU0YmYzODc0YWY0ODg0OWY0ODk0OGY1OGI0N2Y1OGM0NmY2OGQ0NWY2OGY0NGY3OTA0NGY3OTE0M2Y3OTM0MmY4OTQ0MWY4OTU0MGY5OTczZmY5OTgzZWY5OWEzZWZhOWIzZGZhOWMzY2ZhOWUzYmZiOWYzYWZiYTEzOWZiYTIzOGZjYTMzOGZjYTUzN2ZjYTYzNmZjYTgzNWZjYTkzNGZkYWIzM2ZkYWMzM2ZkYWUzMmZkYWYzMWZkYjEzMGZkYjIyZmZkYjQyZmZkYjUyZWZlYjcyZGZlYjgyY2ZlYmEyY2ZlYmIyYmZlYmQyYWZlYmUyYWZlYzAyOWZkYzIyOWZkYzMyOGZkYzUyN2ZkYzYyN2ZkYzgyN2ZkY2EyNmZkY2IyNmZjY2QyNWZjY2UyNWZjZDAyNWZjZDIyNWZiZDMyNGZiZDUyNGZiZDcyNGZhZDgyNGZhZGEyNGY5ZGMyNGY5ZGQyNWY4ZGYyNWY4ZTEyNWY3ZTIyNWY3ZTQyNWY2ZTYyNmY2ZTgyNmY1ZTkyNmY1ZWIyN2Y0ZWQyN2YzZWUyN2YzZjAyN2YyZjIyN2YxZjQyNmYxZjUyNWYwZjcyNGYwZjkyMSIpKTtmdW5jdGlvbiBxZShlKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gZX19dmFyIG4kPU1hdGguYWJzLEJvPU1hdGguYXRhbjIsaWc9TWF0aC5jb3Msa1N0PU1hdGgubWF4LE44PU1hdGgubWluLF91PU1hdGguc2luLHN5PU1hdGguc3FydCxibz0xZS0xMix5dT1NYXRoLlBJLFA1PXl1LzIsaGM9Mip5dTtmdW5jdGlvbiBSU3QoZSl7cmV0dXJuIGU+MT8wOmU8LTE/eXU6TWF0aC5hY29zKGUpfWZ1bmN0aW9uIGkkKGUpe3JldHVybiBlPj0xP1A1OmU8PS0xPy1QNTpNYXRoLmFzaW4oZSl9ZnVuY3Rpb24gQUVlKGUpe3JldHVybiBlLmlubmVyUmFkaXVzfWZ1bmN0aW9uIFBFZShlKXtyZXR1cm4gZS5vdXRlclJhZGl1c31mdW5jdGlvbiBJRWUoZSl7cmV0dXJuIGUuc3RhcnRBbmdsZX1mdW5jdGlvbiBMRWUoZSl7cmV0dXJuIGUuZW5kQW5nbGV9ZnVuY3Rpb24ga0VlKGUpe3JldHVybiBlJiZlLnBhZEFuZ2xlfWZ1bmN0aW9uIFJFZShlLHQscixuLGksbyxhLHMpe3ZhciBsPXItZSxjPW4tdCx1PWEtaSxoPXMtbyxmPWgqbC11KmM7aWYoIShmKmY8Ym8pKXJldHVybiBmPSh1Kih0LW8pLWgqKGUtaSkpL2YsW2UrZipsLHQrZipjXX1mdW5jdGlvbiBEOChlLHQscixuLGksbyxhKXt2YXIgcz1lLXIsbD10LW4sYz0oYT9vOi1vKS9zeShzKnMrbCpsKSx1PWMqbCxoPS1jKnMsZj1lK3UscD10K2gsZD1yK3UsZz1uK2gsXz0oZitkKS8yLHk9KHArZykvMix4PWQtZixiPWctcCxTPXgqeCtiKmIsQz1pLW8sUD1mKmctZCpwLGs9KGI8MD8tMToxKSpzeShrU3QoMCxDKkMqUy1QKlApKSxPPShQKmIteCprKS9TLEQ9KC1QKngtYiprKS9TLEI9KFAqYit4KmspL1MsST0oLVAqeCtiKmspL1MsTD1PLV8sUj1ELXksRj1CLV8sej1JLXk7cmV0dXJuIEwqTCtSKlI+RipGK3oqeiYmKE89QixEPUkpLHtjeDpPLGN5OkQseDAxOi11LHkwMTotaCx4MTE6TyooaS9DLTEpLHkxMTpEKihpL0MtMSl9fWZ1bmN0aW9uIE5TdCgpe3ZhciBlPUFFZSx0PVBFZSxyPXFlKDApLG49bnVsbCxpPUlFZSxvPUxFZSxhPWtFZSxzPW51bGw7ZnVuY3Rpb24gbCgpe3ZhciBjLHUsaD0rZS5hcHBseSh0aGlzLGFyZ3VtZW50cyksZj0rdC5hcHBseSh0aGlzLGFyZ3VtZW50cykscD1pLmFwcGx5KHRoaXMsYXJndW1lbnRzKS1QNSxkPW8uYXBwbHkodGhpcyxhcmd1bWVudHMpLVA1LGc9biQoZC1wKSxfPWQ+cDtpZihzfHwocz1jPWJzKCkpLGY8aCYmKHU9ZixmPWgsaD11KSwhKGY+Ym8pKXMubW92ZVRvKDAsMCk7ZWxzZSBpZihnPmhjLWJvKXMubW92ZVRvKGYqaWcocCksZipfdShwKSkscy5hcmMoMCwwLGYscCxkLCFfKSxoPmJvJiYocy5tb3ZlVG8oaCppZyhkKSxoKl91KGQpKSxzLmFyYygwLDAsaCxkLHAsXykpO2Vsc2V7dmFyIHk9cCx4PWQsYj1wLFM9ZCxDPWcsUD1nLGs9YS5hcHBseSh0aGlzLGFyZ3VtZW50cykvMixPPWs+Ym8mJihuPytuLmFwcGx5KHRoaXMsYXJndW1lbnRzKTpzeShoKmgrZipmKSksRD1OOChuJChmLWgpLzIsK3IuYXBwbHkodGhpcyxhcmd1bWVudHMpKSxCPUQsST1ELEwsUjtpZihPPmJvKXt2YXIgRj1pJChPL2gqX3UoaykpLHo9aSQoTy9mKl91KGspKTsoQy09RioyKT5ibz8oRio9Xz8xOi0xLGIrPUYsUy09Rik6KEM9MCxiPVM9KHArZCkvMiksKFAtPXoqMik+Ym8/KHoqPV8/MTotMSx5Kz16LHgtPXopOihQPTAseT14PShwK2QpLzIpfXZhciBVPWYqaWcoeSksVz1mKl91KHkpLFo9aCppZyhTKSxydD1oKl91KFMpO2lmKEQ+Ym8pe3ZhciBvdD1mKmlnKHgpLHN0PWYqX3UoeCksU3Q9aCppZyhiKSxidD1oKl91KGIpLE10O2lmKGc8eXUmJihNdD1SRWUoVSxXLFN0LGJ0LG90LHN0LFoscnQpKSl7dmFyIGx0PVUtTXRbMF0sS3Q9Vy1NdFsxXSxfdD1vdC1NdFswXSxjdD1zdC1NdFsxXSxYPTEvX3UoUlN0KChsdCpfdCtLdCpjdCkvKHN5KGx0Kmx0K0t0Kkt0KSpzeShfdCpfdCtjdCpjdCkpKS8yKSxldD1zeShNdFswXSpNdFswXStNdFsxXSpNdFsxXSk7Qj1OOChELChoLWV0KS8oWC0xKSksST1OOChELChmLWV0KS8oWCsxKSl9fVA+Ym8/ST5ibz8oTD1EOChTdCxidCxVLFcsZixJLF8pLFI9RDgob3Qsc3QsWixydCxmLEksXykscy5tb3ZlVG8oTC5jeCtMLngwMSxMLmN5K0wueTAxKSxJPEQ/cy5hcmMoTC5jeCxMLmN5LEksQm8oTC55MDEsTC54MDEpLEJvKFIueTAxLFIueDAxKSwhXyk6KHMuYXJjKEwuY3gsTC5jeSxJLEJvKEwueTAxLEwueDAxKSxCbyhMLnkxMSxMLngxMSksIV8pLHMuYXJjKDAsMCxmLEJvKEwuY3krTC55MTEsTC5jeCtMLngxMSksQm8oUi5jeStSLnkxMSxSLmN4K1IueDExKSwhXykscy5hcmMoUi5jeCxSLmN5LEksQm8oUi55MTEsUi54MTEpLEJvKFIueTAxLFIueDAxKSwhXykpKToocy5tb3ZlVG8oVSxXKSxzLmFyYygwLDAsZix5LHgsIV8pKTpzLm1vdmVUbyhVLFcpLCEoaD5ibyl8fCEoQz5ibyk/cy5saW5lVG8oWixydCk6Qj5ibz8oTD1EOChaLHJ0LG90LHN0LGgsLUIsXyksUj1EOChVLFcsU3QsYnQsaCwtQixfKSxzLmxpbmVUbyhMLmN4K0wueDAxLEwuY3krTC55MDEpLEI8RD9zLmFyYyhMLmN4LEwuY3ksQixCbyhMLnkwMSxMLngwMSksQm8oUi55MDEsUi54MDEpLCFfKToocy5hcmMoTC5jeCxMLmN5LEIsQm8oTC55MDEsTC54MDEpLEJvKEwueTExLEwueDExKSwhXykscy5hcmMoMCwwLGgsQm8oTC5jeStMLnkxMSxMLmN4K0wueDExKSxCbyhSLmN5K1IueTExLFIuY3grUi54MTEpLF8pLHMuYXJjKFIuY3gsUi5jeSxCLEJvKFIueTExLFIueDExKSxCbyhSLnkwMSxSLngwMSksIV8pKSk6cy5hcmMoMCwwLGgsUyxiLF8pfWlmKHMuY2xvc2VQYXRoKCksYylyZXR1cm4gcz1udWxsLGMrIiJ8fG51bGx9cmV0dXJuIGwuY2VudHJvaWQ9ZnVuY3Rpb24oKXt2YXIgYz0oK2UuYXBwbHkodGhpcyxhcmd1bWVudHMpKyArdC5hcHBseSh0aGlzLGFyZ3VtZW50cykpLzIsdT0oK2kuYXBwbHkodGhpcyxhcmd1bWVudHMpKyArby5hcHBseSh0aGlzLGFyZ3VtZW50cykpLzIteXUvMjtyZXR1cm5baWcodSkqYyxfdSh1KSpjXX0sbC5pbm5lclJhZGl1cz1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2YgYz09ImZ1bmN0aW9uIj9jOnFlKCtjKSxsKTplfSxsLm91dGVyUmFkaXVzPWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBjPT0iZnVuY3Rpb24iP2M6cWUoK2MpLGwpOnR9LGwuY29ybmVyUmFkaXVzPWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPXR5cGVvZiBjPT0iZnVuY3Rpb24iP2M6cWUoK2MpLGwpOnJ9LGwucGFkUmFkaXVzPWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPWM9PW51bGw/bnVsbDp0eXBlb2YgYz09ImZ1bmN0aW9uIj9jOnFlKCtjKSxsKTpufSxsLnN0YXJ0QW5nbGU9ZnVuY3Rpb24oYyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9dHlwZW9mIGM9PSJmdW5jdGlvbiI/YzpxZSgrYyksbCk6aX0sbC5lbmRBbmdsZT1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz10eXBlb2YgYz09ImZ1bmN0aW9uIj9jOnFlKCtjKSxsKTpvfSxsLnBhZEFuZ2xlPWZ1bmN0aW9uKGMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhhPXR5cGVvZiBjPT0iZnVuY3Rpb24iP2M6cWUoK2MpLGwpOmF9LGwuY29udGV4dD1mdW5jdGlvbihjKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocz1jPT1udWxsP251bGw6YyxsKTpzfSxsfWZ1bmN0aW9uIERTdChlKXt0aGlzLl9jb250ZXh0PWV9RFN0LnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXsodGhpcy5fbGluZXx8dGhpcy5fbGluZSE9PTAmJnRoaXMuX3BvaW50PT09MSkmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCksdGhpcy5fbGluZT0xLXRoaXMuX2xpbmV9LHBvaW50OmZ1bmN0aW9uKGUsdCl7c3dpdGNoKGU9K2UsdD0rdCx0aGlzLl9wb2ludCl7Y2FzZSAwOnRoaXMuX3BvaW50PTEsdGhpcy5fbGluZT90aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpOnRoaXMuX2NvbnRleHQubW92ZVRvKGUsdCk7YnJlYWs7Y2FzZSAxOnRoaXMuX3BvaW50PTI7ZGVmYXVsdDp0aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpO2JyZWFrfX19O2Z1bmN0aW9uIFloKGUpe3JldHVybiBuZXcgRFN0KGUpfWZ1bmN0aW9uIFhiKGUpe3JldHVybiBlWzBdfWZ1bmN0aW9uICRiKGUpe3JldHVybiBlWzFdfWZ1bmN0aW9uIHZ1KCl7dmFyIGU9WGIsdD0kYixyPXFlKCEwKSxuPW51bGwsaT1ZaCxvPW51bGw7ZnVuY3Rpb24gYShzKXt2YXIgbCxjPXMubGVuZ3RoLHUsaD0hMSxmO2ZvcihuPT1udWxsJiYobz1pKGY9YnMoKSkpLGw9MDtsPD1jOysrbCkhKGw8YyYmcih1PXNbbF0sbCxzKSk9PT1oJiYoKGg9IWgpP28ubGluZVN0YXJ0KCk6by5saW5lRW5kKCkpLGgmJm8ucG9pbnQoK2UodSxsLHMpLCt0KHUsbCxzKSk7aWYoZilyZXR1cm4gbz1udWxsLGYrIiJ8fG51bGx9cmV0dXJuIGEueD1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOnFlKCtzKSxhKTplfSxhLnk9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpxZSgrcyksYSk6dH0sYS5kZWZpbmVkPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPXR5cGVvZiBzPT0iZnVuY3Rpb24iP3M6cWUoISFzKSxhKTpyfSxhLmN1cnZlPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPXMsbiE9bnVsbCYmKG89aShuKSksYSk6aX0sYS5jb250ZXh0PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhzPT1udWxsP249bz1udWxsOm89aShuPXMpLGEpOm59LGF9ZnVuY3Rpb24gTzgoKXt2YXIgZT1YYix0PW51bGwscj1xZSgwKSxuPSRiLGk9cWUoITApLG89bnVsbCxhPVloLHM9bnVsbDtmdW5jdGlvbiBsKHUpe3ZhciBoLGYscCxkPXUubGVuZ3RoLGcsXz0hMSx5LHg9bmV3IEFycmF5KGQpLGI9bmV3IEFycmF5KGQpO2ZvcihvPT1udWxsJiYocz1hKHk9YnMoKSkpLGg9MDtoPD1kOysraCl7aWYoIShoPGQmJmkoZz11W2hdLGgsdSkpPT09XylpZihfPSFfKWY9aCxzLmFyZWFTdGFydCgpLHMubGluZVN0YXJ0KCk7ZWxzZXtmb3Iocy5saW5lRW5kKCkscy5saW5lU3RhcnQoKSxwPWgtMTtwPj1mOy0tcClzLnBvaW50KHhbcF0sYltwXSk7cy5saW5lRW5kKCkscy5hcmVhRW5kKCl9XyYmKHhbaF09K2UoZyxoLHUpLGJbaF09K3IoZyxoLHUpLHMucG9pbnQodD8rdChnLGgsdSk6eFtoXSxuPytuKGcsaCx1KTpiW2hdKSl9aWYoeSlyZXR1cm4gcz1udWxsLHkrIiJ8fG51bGx9ZnVuY3Rpb24gYygpe3JldHVybiB2dSgpLmRlZmluZWQoaSkuY3VydmUoYSkuY29udGV4dChvKX1yZXR1cm4gbC54PWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiB1PT0iZnVuY3Rpb24iP3U6cWUoK3UpLHQ9bnVsbCxsKTplfSxsLngwPWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiB1PT0iZnVuY3Rpb24iP3U6cWUoK3UpLGwpOmV9LGwueDE9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9dT09bnVsbD9udWxsOnR5cGVvZiB1PT0iZnVuY3Rpb24iP3U6cWUoK3UpLGwpOnR9LGwueT1mdW5jdGlvbih1KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2YgdT09ImZ1bmN0aW9uIj91OnFlKCt1KSxuPW51bGwsbCk6cn0sbC55MD1mdW5jdGlvbih1KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj10eXBlb2YgdT09ImZ1bmN0aW9uIj91OnFlKCt1KSxsKTpyfSxsLnkxPWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPXU9PW51bGw/bnVsbDp0eXBlb2YgdT09ImZ1bmN0aW9uIj91OnFlKCt1KSxsKTpufSxsLmxpbmVYMD1sLmxpbmVZMD1mdW5jdGlvbigpe3JldHVybiBjKCkueChlKS55KHIpfSxsLmxpbmVZMT1mdW5jdGlvbigpe3JldHVybiBjKCkueChlKS55KG4pfSxsLmxpbmVYMT1mdW5jdGlvbigpe3JldHVybiBjKCkueCh0KS55KHIpfSxsLmRlZmluZWQ9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9dHlwZW9mIHU9PSJmdW5jdGlvbiI/dTpxZSghIXUpLGwpOml9LGwuY3VydmU9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGE9dSxvIT1udWxsJiYocz1hKG8pKSxsKTphfSxsLmNvbnRleHQ9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHU9PW51bGw/bz1zPW51bGw6cz1hKG89dSksbCk6b30sbH1mdW5jdGlvbiBPU3QoZSx0KXtyZXR1cm4gdDxlPy0xOnQ+ZT8xOnQ+PWU/MDpOYU59ZnVuY3Rpb24gelN0KGUpe3JldHVybiBlfWZ1bmN0aW9uIEZTdCgpe3ZhciBlPXpTdCx0PU9TdCxyPW51bGwsbj1xZSgwKSxpPXFlKGhjKSxvPXFlKDApO2Z1bmN0aW9uIGEocyl7dmFyIGwsYz1zLmxlbmd0aCx1LGgsZj0wLHA9bmV3IEFycmF5KGMpLGQ9bmV3IEFycmF5KGMpLGc9K24uYXBwbHkodGhpcyxhcmd1bWVudHMpLF89TWF0aC5taW4oaGMsTWF0aC5tYXgoLWhjLGkuYXBwbHkodGhpcyxhcmd1bWVudHMpLWcpKSx5LHg9TWF0aC5taW4oTWF0aC5hYnMoXykvYyxvLmFwcGx5KHRoaXMsYXJndW1lbnRzKSksYj14KihfPDA/LTE6MSksUztmb3IobD0wO2w8YzsrK2wpKFM9ZFtwW2xdPWxdPStlKHNbbF0sbCxzKSk+MCYmKGYrPVMpO2Zvcih0IT1udWxsP3Auc29ydChmdW5jdGlvbihDLFApe3JldHVybiB0KGRbQ10sZFtQXSl9KTpyIT1udWxsJiZwLnNvcnQoZnVuY3Rpb24oQyxQKXtyZXR1cm4gcihzW0NdLHNbUF0pfSksbD0wLGg9Zj8oXy1jKmIpL2Y6MDtsPGM7KytsLGc9eSl1PXBbbF0sUz1kW3VdLHk9ZysoUz4wP1MqaDowKStiLGRbdV09e2RhdGE6c1t1XSxpbmRleDpsLHZhbHVlOlMsc3RhcnRBbmdsZTpnLGVuZEFuZ2xlOnkscGFkQW5nbGU6eH07cmV0dXJuIGR9cmV0dXJuIGEudmFsdWU9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpxZSgrcyksYSk6ZX0sYS5zb3J0VmFsdWVzPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXMscj1udWxsLGEpOnR9LGEuc29ydD1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj1zLHQ9bnVsbCxhKTpyfSxhLnN0YXJ0QW5nbGU9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpxZSgrcyksYSk6bn0sYS5lbmRBbmdsZT1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oaT10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOnFlKCtzKSxhKTppfSxhLnBhZEFuZ2xlPWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPXR5cGVvZiBzPT0iZnVuY3Rpb24iP3M6cWUoK3MpLGEpOm99LGF9dmFyIHo4PUtiKFloKTtmdW5jdGlvbiBCU3QoZSl7dGhpcy5fY3VydmU9ZX1CU3QucHJvdG90eXBlPXthcmVhU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9jdXJ2ZS5hcmVhU3RhcnQoKX0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2N1cnZlLmFyZWFFbmQoKX0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fY3VydmUubGluZVN0YXJ0KCl9LGxpbmVFbmQ6ZnVuY3Rpb24oKXt0aGlzLl9jdXJ2ZS5saW5lRW5kKCl9LHBvaW50OmZ1bmN0aW9uKGUsdCl7dGhpcy5fY3VydmUucG9pbnQodCpNYXRoLnNpbihlKSx0Ki1NYXRoLmNvcyhlKSl9fTtmdW5jdGlvbiBLYihlKXtmdW5jdGlvbiB0KHIpe3JldHVybiBuZXcgQlN0KGUocikpfXJldHVybiB0Ll9jdXJ2ZT1lLHR9ZnVuY3Rpb24gWmIoZSl7dmFyIHQ9ZS5jdXJ2ZTtyZXR1cm4gZS5hbmdsZT1lLngsZGVsZXRlIGUueCxlLnJhZGl1cz1lLnksZGVsZXRlIGUueSxlLmN1cnZlPWZ1bmN0aW9uKHIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoP3QoS2IocikpOnQoKS5fY3VydmV9LGV9ZnVuY3Rpb24gbyQoKXtyZXR1cm4gWmIodnUoKS5jdXJ2ZSh6OCkpfWZ1bmN0aW9uIGEkKCl7dmFyIGU9TzgoKS5jdXJ2ZSh6OCksdD1lLmN1cnZlLHI9ZS5saW5lWDAsbj1lLmxpbmVYMSxpPWUubGluZVkwLG89ZS5saW5lWTE7cmV0dXJuIGUuYW5nbGU9ZS54LGRlbGV0ZSBlLngsZS5zdGFydEFuZ2xlPWUueDAsZGVsZXRlIGUueDAsZS5lbmRBbmdsZT1lLngxLGRlbGV0ZSBlLngxLGUucmFkaXVzPWUueSxkZWxldGUgZS55LGUuaW5uZXJSYWRpdXM9ZS55MCxkZWxldGUgZS55MCxlLm91dGVyUmFkaXVzPWUueTEsZGVsZXRlIGUueTEsZS5saW5lU3RhcnRBbmdsZT1mdW5jdGlvbigpe3JldHVybiBaYihyKCkpfSxkZWxldGUgZS5saW5lWDAsZS5saW5lRW5kQW5nbGU9ZnVuY3Rpb24oKXtyZXR1cm4gWmIobigpKX0sZGVsZXRlIGUubGluZVgxLGUubGluZUlubmVyUmFkaXVzPWZ1bmN0aW9uKCl7cmV0dXJuIFpiKGkoKSl9LGRlbGV0ZSBlLmxpbmVZMCxlLmxpbmVPdXRlclJhZGl1cz1mdW5jdGlvbigpe3JldHVybiBaYihvKCkpfSxkZWxldGUgZS5saW5lWTEsZS5jdXJ2ZT1mdW5jdGlvbihhKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90KEtiKGEpKTp0KCkuX2N1cnZlfSxlfWZ1bmN0aW9uIGx5KGUsdCl7cmV0dXJuWyh0PSt0KSpNYXRoLmNvcyhlLT1NYXRoLlBJLzIpLHQqTWF0aC5zaW4oZSldfXZhciBJNT1BcnJheS5wcm90b3R5cGUuc2xpY2U7ZnVuY3Rpb24gTkVlKGUpe3JldHVybiBlLnNvdXJjZX1mdW5jdGlvbiBERWUoZSl7cmV0dXJuIGUudGFyZ2V0fWZ1bmN0aW9uIHMkKGUpe3ZhciB0PU5FZSxyPURFZSxuPVhiLGk9JGIsbz1udWxsO2Z1bmN0aW9uIGEoKXt2YXIgcyxsPUk1LmNhbGwoYXJndW1lbnRzKSxjPXQuYXBwbHkodGhpcyxsKSx1PXIuYXBwbHkodGhpcyxsKTtpZihvfHwobz1zPWJzKCkpLGUobywrbi5hcHBseSh0aGlzLChsWzBdPWMsbCkpLCtpLmFwcGx5KHRoaXMsbCksK24uYXBwbHkodGhpcywobFswXT11LGwpKSwraS5hcHBseSh0aGlzLGwpKSxzKXJldHVybiBvPW51bGwscysiInx8bnVsbH1yZXR1cm4gYS5zb3VyY2U9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9cyxhKTp0fSxhLnRhcmdldD1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj1zLGEpOnJ9LGEueD1mdW5jdGlvbihzKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj10eXBlb2Ygcz09ImZ1bmN0aW9uIj9zOnFlKCtzKSxhKTpufSxhLnk9ZnVuY3Rpb24ocyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9dHlwZW9mIHM9PSJmdW5jdGlvbiI/czpxZSgrcyksYSk6aX0sYS5jb250ZXh0PWZ1bmN0aW9uKHMpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPXM9PW51bGw/bnVsbDpzLGEpOm99LGF9ZnVuY3Rpb24gT0VlKGUsdCxyLG4saSl7ZS5tb3ZlVG8odCxyKSxlLmJlemllckN1cnZlVG8odD0odCtuKS8yLHIsdCxpLG4saSl9ZnVuY3Rpb24gekVlKGUsdCxyLG4saSl7ZS5tb3ZlVG8odCxyKSxlLmJlemllckN1cnZlVG8odCxyPShyK2kpLzIsbixyLG4saSl9ZnVuY3Rpb24gRkVlKGUsdCxyLG4saSl7dmFyIG89bHkodCxyKSxhPWx5KHQscj0ocitpKS8yKSxzPWx5KG4sciksbD1seShuLGkpO2UubW92ZVRvKG9bMF0sb1sxXSksZS5iZXppZXJDdXJ2ZVRvKGFbMF0sYVsxXSxzWzBdLHNbMV0sbFswXSxsWzFdKX1mdW5jdGlvbiBIU3QoKXtyZXR1cm4gcyQoT0VlKX1mdW5jdGlvbiBWU3QoKXtyZXR1cm4gcyQoekVlKX1mdW5jdGlvbiBVU3QoKXt2YXIgZT1zJChGRWUpO3JldHVybiBlLmFuZ2xlPWUueCxkZWxldGUgZS54LGUucmFkaXVzPWUueSxkZWxldGUgZS55LGV9dmFyIEw1PXtkcmF3OmZ1bmN0aW9uKGUsdCl7dmFyIHI9TWF0aC5zcXJ0KHQveXUpO2UubW92ZVRvKHIsMCksZS5hcmMoMCwwLHIsMCxoYyl9fTt2YXIgRjg9e2RyYXc6ZnVuY3Rpb24oZSx0KXt2YXIgcj1NYXRoLnNxcnQodC81KS8yO2UubW92ZVRvKC0zKnIsLXIpLGUubGluZVRvKC1yLC1yKSxlLmxpbmVUbygtciwtMypyKSxlLmxpbmVUbyhyLC0zKnIpLGUubGluZVRvKHIsLXIpLGUubGluZVRvKDMqciwtciksZS5saW5lVG8oMypyLHIpLGUubGluZVRvKHIsciksZS5saW5lVG8ociwzKnIpLGUubGluZVRvKC1yLDMqciksZS5saW5lVG8oLXIsciksZS5saW5lVG8oLTMqcixyKSxlLmNsb3NlUGF0aCgpfX07dmFyIHFTdD1NYXRoLnNxcnQoLjMzMzMzMzMzMzMzMzMzMzMpLEJFZT1xU3QqMixCOD17ZHJhdzpmdW5jdGlvbihlLHQpe3ZhciByPU1hdGguc3FydCh0L0JFZSksbj1yKnFTdDtlLm1vdmVUbygwLC1yKSxlLmxpbmVUbyhuLDApLGUubGluZVRvKDAsciksZS5saW5lVG8oLW4sMCksZS5jbG9zZVBhdGgoKX19O3ZhciBIRWU9Ljg5MDgxMzA5MTUyOTI4NTIsR1N0PU1hdGguc2luKHl1LzEwKS9NYXRoLnNpbig3Knl1LzEwKSxWRWU9TWF0aC5zaW4oaGMvMTApKkdTdCxVRWU9LU1hdGguY29zKGhjLzEwKSpHU3QsSDg9e2RyYXc6ZnVuY3Rpb24oZSx0KXt2YXIgcj1NYXRoLnNxcnQodCpIRWUpLG49VkVlKnIsaT1VRWUqcjtlLm1vdmVUbygwLC1yKSxlLmxpbmVUbyhuLGkpO2Zvcih2YXIgbz0xO288NTsrK28pe3ZhciBhPWhjKm8vNSxzPU1hdGguY29zKGEpLGw9TWF0aC5zaW4oYSk7ZS5saW5lVG8obCpyLC1zKnIpLGUubGluZVRvKHMqbi1sKmksbCpuK3MqaSl9ZS5jbG9zZVBhdGgoKX19O3ZhciBWOD17ZHJhdzpmdW5jdGlvbihlLHQpe3ZhciByPU1hdGguc3FydCh0KSxuPS1yLzI7ZS5yZWN0KG4sbixyLHIpfX07dmFyIGwkPU1hdGguc3FydCgzKSxVOD17ZHJhdzpmdW5jdGlvbihlLHQpe3ZhciByPS1NYXRoLnNxcnQodC8obCQqMykpO2UubW92ZVRvKDAscioyKSxlLmxpbmVUbygtbCQqciwtciksZS5saW5lVG8obCQqciwtciksZS5jbG9zZVBhdGgoKX19O3ZhciBmYz0tLjUscGM9TWF0aC5zcXJ0KDMpLzIsYyQ9MS9NYXRoLnNxcnQoMTIpLHFFZT0oYyQvMisxKSozLHE4PXtkcmF3OmZ1bmN0aW9uKGUsdCl7dmFyIHI9TWF0aC5zcXJ0KHQvcUVlKSxuPXIvMixpPXIqYyQsbz1uLGE9cipjJCtyLHM9LW8sbD1hO2UubW92ZVRvKG4saSksZS5saW5lVG8obyxhKSxlLmxpbmVUbyhzLGwpLGUubGluZVRvKGZjKm4tcGMqaSxwYypuK2ZjKmkpLGUubGluZVRvKGZjKm8tcGMqYSxwYypvK2ZjKmEpLGUubGluZVRvKGZjKnMtcGMqbCxwYypzK2ZjKmwpLGUubGluZVRvKGZjKm4rcGMqaSxmYyppLXBjKm4pLGUubGluZVRvKGZjKm8rcGMqYSxmYyphLXBjKm8pLGUubGluZVRvKGZjKnMrcGMqbCxmYypsLXBjKnMpLGUuY2xvc2VQYXRoKCl9fTt2YXIgV1N0PVtMNSxGOCxCOCxWOCxIOCxVOCxxOF07ZnVuY3Rpb24gWVN0KCl7dmFyIGU9cWUoTDUpLHQ9cWUoNjQpLHI9bnVsbDtmdW5jdGlvbiBuKCl7dmFyIGk7aWYocnx8KHI9aT1icygpKSxlLmFwcGx5KHRoaXMsYXJndW1lbnRzKS5kcmF3KHIsK3QuYXBwbHkodGhpcyxhcmd1bWVudHMpKSxpKXJldHVybiByPW51bGwsaSsiInx8bnVsbH1yZXR1cm4gbi50eXBlPWZ1bmN0aW9uKGkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiBpPT0iZnVuY3Rpb24iP2k6cWUoaSksbik6ZX0sbi5zaXplPWZ1bmN0aW9uKGkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXR5cGVvZiBpPT0iZnVuY3Rpb24iP2k6cWUoK2kpLG4pOnR9LG4uY29udGV4dD1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj1pPT1udWxsP251bGw6aSxuKTpyfSxufWZ1bmN0aW9uIGRjKCl7fWZ1bmN0aW9uIEpiKGUsdCxyKXtlLl9jb250ZXh0LmJlemllckN1cnZlVG8oKDIqZS5feDArZS5feDEpLzMsKDIqZS5feTArZS5feTEpLzMsKGUuX3gwKzIqZS5feDEpLzMsKGUuX3kwKzIqZS5feTEpLzMsKGUuX3gwKzQqZS5feDErdCkvNiwoZS5feTArNCplLl95MStyKS82KX1mdW5jdGlvbiBrNShlKXt0aGlzLl9jb250ZXh0PWV9azUucHJvdG90eXBlPXthcmVhU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPTB9LGFyZWFFbmQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPU5hTn0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5feDA9dGhpcy5feDE9dGhpcy5feTA9dGhpcy5feTE9TmFOLHRoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMzpKYih0aGlzLHRoaXMuX3gxLHRoaXMuX3kxKTtjYXNlIDI6dGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feDEsdGhpcy5feTEpO2JyZWFrfSh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0xKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2goZT0rZSx0PSt0LHRoaXMuX3BvaW50KXtjYXNlIDA6dGhpcy5fcG9pbnQ9MSx0aGlzLl9saW5lP3RoaXMuX2NvbnRleHQubGluZVRvKGUsdCk6dGhpcy5fY29udGV4dC5tb3ZlVG8oZSx0KTticmVhaztjYXNlIDE6dGhpcy5fcG9pbnQ9MjticmVhaztjYXNlIDI6dGhpcy5fcG9pbnQ9Myx0aGlzLl9jb250ZXh0LmxpbmVUbygoNSp0aGlzLl94MCt0aGlzLl94MSkvNiwoNSp0aGlzLl95MCt0aGlzLl95MSkvNik7ZGVmYXVsdDpKYih0aGlzLGUsdCk7YnJlYWt9dGhpcy5feDA9dGhpcy5feDEsdGhpcy5feDE9ZSx0aGlzLl95MD10aGlzLl95MSx0aGlzLl95MT10fX07ZnVuY3Rpb24gRzgoZSl7cmV0dXJuIG5ldyBrNShlKX1mdW5jdGlvbiBqU3QoZSl7dGhpcy5fY29udGV4dD1lfWpTdC5wcm90b3R5cGU9e2FyZWFTdGFydDpkYyxhcmVhRW5kOmRjLGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3gwPXRoaXMuX3gxPXRoaXMuX3gyPXRoaXMuX3gzPXRoaXMuX3g0PXRoaXMuX3kwPXRoaXMuX3kxPXRoaXMuX3kyPXRoaXMuX3kzPXRoaXMuX3k0PU5hTix0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7c3dpdGNoKHRoaXMuX3BvaW50KXtjYXNlIDE6e3RoaXMuX2NvbnRleHQubW92ZVRvKHRoaXMuX3gyLHRoaXMuX3kyKSx0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpO2JyZWFrfWNhc2UgMjp7dGhpcy5fY29udGV4dC5tb3ZlVG8oKHRoaXMuX3gyKzIqdGhpcy5feDMpLzMsKHRoaXMuX3kyKzIqdGhpcy5feTMpLzMpLHRoaXMuX2NvbnRleHQubGluZVRvKCh0aGlzLl94MysyKnRoaXMuX3gyKS8zLCh0aGlzLl95MysyKnRoaXMuX3kyKS8zKSx0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpO2JyZWFrfWNhc2UgMzp7dGhpcy5wb2ludCh0aGlzLl94Mix0aGlzLl95MiksdGhpcy5wb2ludCh0aGlzLl94Myx0aGlzLl95MyksdGhpcy5wb2ludCh0aGlzLl94NCx0aGlzLl95NCk7YnJlYWt9fX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2goZT0rZSx0PSt0LHRoaXMuX3BvaW50KXtjYXNlIDA6dGhpcy5fcG9pbnQ9MSx0aGlzLl94Mj1lLHRoaXMuX3kyPXQ7YnJlYWs7Y2FzZSAxOnRoaXMuX3BvaW50PTIsdGhpcy5feDM9ZSx0aGlzLl95Mz10O2JyZWFrO2Nhc2UgMjp0aGlzLl9wb2ludD0zLHRoaXMuX3g0PWUsdGhpcy5feTQ9dCx0aGlzLl9jb250ZXh0Lm1vdmVUbygodGhpcy5feDArNCp0aGlzLl94MStlKS82LCh0aGlzLl95MCs0KnRoaXMuX3kxK3QpLzYpO2JyZWFrO2RlZmF1bHQ6SmIodGhpcyxlLHQpO2JyZWFrfXRoaXMuX3gwPXRoaXMuX3gxLHRoaXMuX3gxPWUsdGhpcy5feTA9dGhpcy5feTEsdGhpcy5feTE9dH19O2Z1bmN0aW9uIFhTdChlKXtyZXR1cm4gbmV3IGpTdChlKX1mdW5jdGlvbiAkU3QoZSl7dGhpcy5fY29udGV4dD1lfSRTdC5wcm90b3R5cGU9e2FyZWFTdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94MD10aGlzLl94MT10aGlzLl95MD10aGlzLl95MT1OYU4sdGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpeyh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0zKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2goZT0rZSx0PSt0LHRoaXMuX3BvaW50KXtjYXNlIDA6dGhpcy5fcG9pbnQ9MTticmVhaztjYXNlIDE6dGhpcy5fcG9pbnQ9MjticmVhaztjYXNlIDI6dGhpcy5fcG9pbnQ9Mzt2YXIgcj0odGhpcy5feDArNCp0aGlzLl94MStlKS82LG49KHRoaXMuX3kwKzQqdGhpcy5feTErdCkvNjt0aGlzLl9saW5lP3RoaXMuX2NvbnRleHQubGluZVRvKHIsbik6dGhpcy5fY29udGV4dC5tb3ZlVG8ocixuKTticmVhaztjYXNlIDM6dGhpcy5fcG9pbnQ9NDtkZWZhdWx0OkpiKHRoaXMsZSx0KTticmVha310aGlzLl94MD10aGlzLl94MSx0aGlzLl94MT1lLHRoaXMuX3kwPXRoaXMuX3kxLHRoaXMuX3kxPXR9fTtmdW5jdGlvbiBLU3QoZSl7cmV0dXJuIG5ldyAkU3QoZSl9ZnVuY3Rpb24gWlN0KGUsdCl7dGhpcy5fYmFzaXM9bmV3IGs1KGUpLHRoaXMuX2JldGE9dH1aU3QucHJvdG90eXBlPXtsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94PVtdLHRoaXMuX3k9W10sdGhpcy5fYmFzaXMubGluZVN0YXJ0KCl9LGxpbmVFbmQ6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLl94LHQ9dGhpcy5feSxyPWUubGVuZ3RoLTE7aWYocj4wKWZvcih2YXIgbj1lWzBdLGk9dFswXSxvPWVbcl0tbixhPXRbcl0taSxzPS0xLGw7KytzPD1yOylsPXMvcix0aGlzLl9iYXNpcy5wb2ludCh0aGlzLl9iZXRhKmVbc10rKDEtdGhpcy5fYmV0YSkqKG4rbCpvKSx0aGlzLl9iZXRhKnRbc10rKDEtdGhpcy5fYmV0YSkqKGkrbCphKSk7dGhpcy5feD10aGlzLl95PW51bGwsdGhpcy5fYmFzaXMubGluZUVuZCgpfSxwb2ludDpmdW5jdGlvbihlLHQpe3RoaXMuX3gucHVzaCgrZSksdGhpcy5feS5wdXNoKCt0KX19O3ZhciBKU3Q9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4pe3JldHVybiB0PT09MT9uZXcgazUobik6bmV3IFpTdChuLHQpfXJldHVybiByLmJldGE9ZnVuY3Rpb24obil7cmV0dXJuIGUoK24pfSxyfSguODUpO2Z1bmN0aW9uIFFiKGUsdCxyKXtlLl9jb250ZXh0LmJlemllckN1cnZlVG8oZS5feDErZS5fayooZS5feDItZS5feDApLGUuX3kxK2UuX2sqKGUuX3kyLWUuX3kwKSxlLl94MitlLl9rKihlLl94MS10KSxlLl95MitlLl9rKihlLl95MS1yKSxlLl94MixlLl95Mil9ZnVuY3Rpb24gVzgoZSx0KXt0aGlzLl9jb250ZXh0PWUsdGhpcy5faz0oMS10KS82fVc4LnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3gwPXRoaXMuX3gxPXRoaXMuX3gyPXRoaXMuX3kwPXRoaXMuX3kxPXRoaXMuX3kyPU5hTix0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7c3dpdGNoKHRoaXMuX3BvaW50KXtjYXNlIDI6dGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feDIsdGhpcy5feTIpO2JyZWFrO2Nhc2UgMzpRYih0aGlzLHRoaXMuX3gxLHRoaXMuX3kxKTticmVha30odGhpcy5fbGluZXx8dGhpcy5fbGluZSE9PTAmJnRoaXMuX3BvaW50PT09MSkmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCksdGhpcy5fbGluZT0xLXRoaXMuX2xpbmV9LHBvaW50OmZ1bmN0aW9uKGUsdCl7c3dpdGNoKGU9K2UsdD0rdCx0aGlzLl9wb2ludCl7Y2FzZSAwOnRoaXMuX3BvaW50PTEsdGhpcy5fbGluZT90aGlzLl9jb250ZXh0LmxpbmVUbyhlLHQpOnRoaXMuX2NvbnRleHQubW92ZVRvKGUsdCk7YnJlYWs7Y2FzZSAxOnRoaXMuX3BvaW50PTIsdGhpcy5feDE9ZSx0aGlzLl95MT10O2JyZWFrO2Nhc2UgMjp0aGlzLl9wb2ludD0zO2RlZmF1bHQ6UWIodGhpcyxlLHQpO2JyZWFrfXRoaXMuX3gwPXRoaXMuX3gxLHRoaXMuX3gxPXRoaXMuX3gyLHRoaXMuX3gyPWUsdGhpcy5feTA9dGhpcy5feTEsdGhpcy5feTE9dGhpcy5feTIsdGhpcy5feTI9dH19O3ZhciBRU3Q9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4pe3JldHVybiBuZXcgVzgobix0KX1yZXR1cm4gci50ZW5zaW9uPWZ1bmN0aW9uKG4pe3JldHVybiBlKCtuKX0scn0oMCk7ZnVuY3Rpb24gWTgoZSx0KXt0aGlzLl9jb250ZXh0PWUsdGhpcy5faz0oMS10KS82fVk4LnByb3RvdHlwZT17YXJlYVN0YXJ0OmRjLGFyZWFFbmQ6ZGMsbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5feDA9dGhpcy5feDE9dGhpcy5feDI9dGhpcy5feDM9dGhpcy5feDQ9dGhpcy5feDU9dGhpcy5feTA9dGhpcy5feTE9dGhpcy5feTI9dGhpcy5feTM9dGhpcy5feTQ9dGhpcy5feTU9TmFOLHRoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMTp7dGhpcy5fY29udGV4dC5tb3ZlVG8odGhpcy5feDMsdGhpcy5feTMpLHRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCk7YnJlYWt9Y2FzZSAyOnt0aGlzLl9jb250ZXh0LmxpbmVUbyh0aGlzLl94Myx0aGlzLl95MyksdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKTticmVha31jYXNlIDM6e3RoaXMucG9pbnQodGhpcy5feDMsdGhpcy5feTMpLHRoaXMucG9pbnQodGhpcy5feDQsdGhpcy5feTQpLHRoaXMucG9pbnQodGhpcy5feDUsdGhpcy5feTUpO2JyZWFrfX19LHBvaW50OmZ1bmN0aW9uKGUsdCl7c3dpdGNoKGU9K2UsdD0rdCx0aGlzLl9wb2ludCl7Y2FzZSAwOnRoaXMuX3BvaW50PTEsdGhpcy5feDM9ZSx0aGlzLl95Mz10O2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yLHRoaXMuX2NvbnRleHQubW92ZVRvKHRoaXMuX3g0PWUsdGhpcy5feTQ9dCk7YnJlYWs7Y2FzZSAyOnRoaXMuX3BvaW50PTMsdGhpcy5feDU9ZSx0aGlzLl95NT10O2JyZWFrO2RlZmF1bHQ6UWIodGhpcyxlLHQpO2JyZWFrfXRoaXMuX3gwPXRoaXMuX3gxLHRoaXMuX3gxPXRoaXMuX3gyLHRoaXMuX3gyPWUsdGhpcy5feTA9dGhpcy5feTEsdGhpcy5feTE9dGhpcy5feTIsdGhpcy5feTI9dH19O3ZhciB0M3Q9ZnVuY3Rpb24gZSh0KXtmdW5jdGlvbiByKG4pe3JldHVybiBuZXcgWTgobix0KX1yZXR1cm4gci50ZW5zaW9uPWZ1bmN0aW9uKG4pe3JldHVybiBlKCtuKX0scn0oMCk7ZnVuY3Rpb24gajgoZSx0KXt0aGlzLl9jb250ZXh0PWUsdGhpcy5faz0oMS10KS82fWo4LnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3gwPXRoaXMuX3gxPXRoaXMuX3gyPXRoaXMuX3kwPXRoaXMuX3kxPXRoaXMuX3kyPU5hTix0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7KHRoaXMuX2xpbmV8fHRoaXMuX2xpbmUhPT0wJiZ0aGlzLl9wb2ludD09PTMpJiZ0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpLHRoaXMuX2xpbmU9MS10aGlzLl9saW5lfSxwb2ludDpmdW5jdGlvbihlLHQpe3N3aXRjaChlPStlLHQ9K3QsdGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xO2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yO2JyZWFrO2Nhc2UgMjp0aGlzLl9wb2ludD0zLHRoaXMuX2xpbmU/dGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feDIsdGhpcy5feTIpOnRoaXMuX2NvbnRleHQubW92ZVRvKHRoaXMuX3gyLHRoaXMuX3kyKTticmVhaztjYXNlIDM6dGhpcy5fcG9pbnQ9NDtkZWZhdWx0OlFiKHRoaXMsZSx0KTticmVha310aGlzLl94MD10aGlzLl94MSx0aGlzLl94MT10aGlzLl94Mix0aGlzLl94Mj1lLHRoaXMuX3kwPXRoaXMuX3kxLHRoaXMuX3kxPXRoaXMuX3kyLHRoaXMuX3kyPXR9fTt2YXIgZTN0PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuKXtyZXR1cm4gbmV3IGo4KG4sdCl9cmV0dXJuIHIudGVuc2lvbj1mdW5jdGlvbihuKXtyZXR1cm4gZSgrbil9LHJ9KDApO2Z1bmN0aW9uIFI1KGUsdCxyKXt2YXIgbj1lLl94MSxpPWUuX3kxLG89ZS5feDIsYT1lLl95MjtpZihlLl9sMDFfYT5ibyl7dmFyIHM9MiplLl9sMDFfMmErMyplLl9sMDFfYSplLl9sMTJfYStlLl9sMTJfMmEsbD0zKmUuX2wwMV9hKihlLl9sMDFfYStlLl9sMTJfYSk7bj0obipzLWUuX3gwKmUuX2wxMl8yYStlLl94MiplLl9sMDFfMmEpL2wsaT0oaSpzLWUuX3kwKmUuX2wxMl8yYStlLl95MiplLl9sMDFfMmEpL2x9aWYoZS5fbDIzX2E+Ym8pe3ZhciBjPTIqZS5fbDIzXzJhKzMqZS5fbDIzX2EqZS5fbDEyX2ErZS5fbDEyXzJhLHU9MyplLl9sMjNfYSooZS5fbDIzX2ErZS5fbDEyX2EpO289KG8qYytlLl94MSplLl9sMjNfMmEtdCplLl9sMTJfMmEpL3UsYT0oYSpjK2UuX3kxKmUuX2wyM18yYS1yKmUuX2wxMl8yYSkvdX1lLl9jb250ZXh0LmJlemllckN1cnZlVG8obixpLG8sYSxlLl94MixlLl95Mil9ZnVuY3Rpb24gcjN0KGUsdCl7dGhpcy5fY29udGV4dD1lLHRoaXMuX2FscGhhPXR9cjN0LnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3gwPXRoaXMuX3gxPXRoaXMuX3gyPXRoaXMuX3kwPXRoaXMuX3kxPXRoaXMuX3kyPU5hTix0aGlzLl9sMDFfYT10aGlzLl9sMTJfYT10aGlzLl9sMjNfYT10aGlzLl9sMDFfMmE9dGhpcy5fbDEyXzJhPXRoaXMuX2wyM18yYT10aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7c3dpdGNoKHRoaXMuX3BvaW50KXtjYXNlIDI6dGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feDIsdGhpcy5feTIpO2JyZWFrO2Nhc2UgMzp0aGlzLnBvaW50KHRoaXMuX3gyLHRoaXMuX3kyKTticmVha30odGhpcy5fbGluZXx8dGhpcy5fbGluZSE9PTAmJnRoaXMuX3BvaW50PT09MSkmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCksdGhpcy5fbGluZT0xLXRoaXMuX2xpbmV9LHBvaW50OmZ1bmN0aW9uKGUsdCl7aWYoZT0rZSx0PSt0LHRoaXMuX3BvaW50KXt2YXIgcj10aGlzLl94Mi1lLG49dGhpcy5feTItdDt0aGlzLl9sMjNfYT1NYXRoLnNxcnQodGhpcy5fbDIzXzJhPU1hdGgucG93KHIqcituKm4sdGhpcy5fYWxwaGEpKX1zd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xLHRoaXMuX2xpbmU/dGhpcy5fY29udGV4dC5saW5lVG8oZSx0KTp0aGlzLl9jb250ZXh0Lm1vdmVUbyhlLHQpO2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yO2JyZWFrO2Nhc2UgMjp0aGlzLl9wb2ludD0zO2RlZmF1bHQ6UjUodGhpcyxlLHQpO2JyZWFrfXRoaXMuX2wwMV9hPXRoaXMuX2wxMl9hLHRoaXMuX2wxMl9hPXRoaXMuX2wyM19hLHRoaXMuX2wwMV8yYT10aGlzLl9sMTJfMmEsdGhpcy5fbDEyXzJhPXRoaXMuX2wyM18yYSx0aGlzLl94MD10aGlzLl94MSx0aGlzLl94MT10aGlzLl94Mix0aGlzLl94Mj1lLHRoaXMuX3kwPXRoaXMuX3kxLHRoaXMuX3kxPXRoaXMuX3kyLHRoaXMuX3kyPXR9fTt2YXIgbjN0PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuKXtyZXR1cm4gdD9uZXcgcjN0KG4sdCk6bmV3IFc4KG4sMCl9cmV0dXJuIHIuYWxwaGE9ZnVuY3Rpb24obil7cmV0dXJuIGUoK24pfSxyfSguNSk7ZnVuY3Rpb24gaTN0KGUsdCl7dGhpcy5fY29udGV4dD1lLHRoaXMuX2FscGhhPXR9aTN0LnByb3RvdHlwZT17YXJlYVN0YXJ0OmRjLGFyZWFFbmQ6ZGMsbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5feDA9dGhpcy5feDE9dGhpcy5feDI9dGhpcy5feDM9dGhpcy5feDQ9dGhpcy5feDU9dGhpcy5feTA9dGhpcy5feTE9dGhpcy5feTI9dGhpcy5feTM9dGhpcy5feTQ9dGhpcy5feTU9TmFOLHRoaXMuX2wwMV9hPXRoaXMuX2wxMl9hPXRoaXMuX2wyM19hPXRoaXMuX2wwMV8yYT10aGlzLl9sMTJfMmE9dGhpcy5fbDIzXzJhPXRoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMTp7dGhpcy5fY29udGV4dC5tb3ZlVG8odGhpcy5feDMsdGhpcy5feTMpLHRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCk7YnJlYWt9Y2FzZSAyOnt0aGlzLl9jb250ZXh0LmxpbmVUbyh0aGlzLl94Myx0aGlzLl95MyksdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKTticmVha31jYXNlIDM6e3RoaXMucG9pbnQodGhpcy5feDMsdGhpcy5feTMpLHRoaXMucG9pbnQodGhpcy5feDQsdGhpcy5feTQpLHRoaXMucG9pbnQodGhpcy5feDUsdGhpcy5feTUpO2JyZWFrfX19LHBvaW50OmZ1bmN0aW9uKGUsdCl7aWYoZT0rZSx0PSt0LHRoaXMuX3BvaW50KXt2YXIgcj10aGlzLl94Mi1lLG49dGhpcy5feTItdDt0aGlzLl9sMjNfYT1NYXRoLnNxcnQodGhpcy5fbDIzXzJhPU1hdGgucG93KHIqcituKm4sdGhpcy5fYWxwaGEpKX1zd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xLHRoaXMuX3gzPWUsdGhpcy5feTM9dDticmVhaztjYXNlIDE6dGhpcy5fcG9pbnQ9Mix0aGlzLl9jb250ZXh0Lm1vdmVUbyh0aGlzLl94ND1lLHRoaXMuX3k0PXQpO2JyZWFrO2Nhc2UgMjp0aGlzLl9wb2ludD0zLHRoaXMuX3g1PWUsdGhpcy5feTU9dDticmVhaztkZWZhdWx0OlI1KHRoaXMsZSx0KTticmVha310aGlzLl9sMDFfYT10aGlzLl9sMTJfYSx0aGlzLl9sMTJfYT10aGlzLl9sMjNfYSx0aGlzLl9sMDFfMmE9dGhpcy5fbDEyXzJhLHRoaXMuX2wxMl8yYT10aGlzLl9sMjNfMmEsdGhpcy5feDA9dGhpcy5feDEsdGhpcy5feDE9dGhpcy5feDIsdGhpcy5feDI9ZSx0aGlzLl95MD10aGlzLl95MSx0aGlzLl95MT10aGlzLl95Mix0aGlzLl95Mj10fX07dmFyIG8zdD1mdW5jdGlvbiBlKHQpe2Z1bmN0aW9uIHIobil7cmV0dXJuIHQ/bmV3IGkzdChuLHQpOm5ldyBZOChuLDApfXJldHVybiByLmFscGhhPWZ1bmN0aW9uKG4pe3JldHVybiBlKCtuKX0scn0oLjUpO2Z1bmN0aW9uIGEzdChlLHQpe3RoaXMuX2NvbnRleHQ9ZSx0aGlzLl9hbHBoYT10fWEzdC5wcm90b3R5cGU9e2FyZWFTdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94MD10aGlzLl94MT10aGlzLl94Mj10aGlzLl95MD10aGlzLl95MT10aGlzLl95Mj1OYU4sdGhpcy5fbDAxX2E9dGhpcy5fbDEyX2E9dGhpcy5fbDIzX2E9dGhpcy5fbDAxXzJhPXRoaXMuX2wxMl8yYT10aGlzLl9sMjNfMmE9dGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpeyh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0zKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtpZihlPStlLHQ9K3QsdGhpcy5fcG9pbnQpe3ZhciByPXRoaXMuX3gyLWUsbj10aGlzLl95Mi10O3RoaXMuX2wyM19hPU1hdGguc3FydCh0aGlzLl9sMjNfMmE9TWF0aC5wb3cocipyK24qbix0aGlzLl9hbHBoYSkpfXN3aXRjaCh0aGlzLl9wb2ludCl7Y2FzZSAwOnRoaXMuX3BvaW50PTE7YnJlYWs7Y2FzZSAxOnRoaXMuX3BvaW50PTI7YnJlYWs7Y2FzZSAyOnRoaXMuX3BvaW50PTMsdGhpcy5fbGluZT90aGlzLl9jb250ZXh0LmxpbmVUbyh0aGlzLl94Mix0aGlzLl95Mik6dGhpcy5fY29udGV4dC5tb3ZlVG8odGhpcy5feDIsdGhpcy5feTIpO2JyZWFrO2Nhc2UgMzp0aGlzLl9wb2ludD00O2RlZmF1bHQ6UjUodGhpcyxlLHQpO2JyZWFrfXRoaXMuX2wwMV9hPXRoaXMuX2wxMl9hLHRoaXMuX2wxMl9hPXRoaXMuX2wyM19hLHRoaXMuX2wwMV8yYT10aGlzLl9sMTJfMmEsdGhpcy5fbDEyXzJhPXRoaXMuX2wyM18yYSx0aGlzLl94MD10aGlzLl94MSx0aGlzLl94MT10aGlzLl94Mix0aGlzLl94Mj1lLHRoaXMuX3kwPXRoaXMuX3kxLHRoaXMuX3kxPXRoaXMuX3kyLHRoaXMuX3kyPXR9fTt2YXIgczN0PWZ1bmN0aW9uIGUodCl7ZnVuY3Rpb24gcihuKXtyZXR1cm4gdD9uZXcgYTN0KG4sdCk6bmV3IGo4KG4sMCl9cmV0dXJuIHIuYWxwaGE9ZnVuY3Rpb24obil7cmV0dXJuIGUoK24pfSxyfSguNSk7ZnVuY3Rpb24gbDN0KGUpe3RoaXMuX2NvbnRleHQ9ZX1sM3QucHJvdG90eXBlPXthcmVhU3RhcnQ6ZGMsYXJlYUVuZDpkYyxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7dGhpcy5fcG9pbnQmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCl9LHBvaW50OmZ1bmN0aW9uKGUsdCl7ZT0rZSx0PSt0LHRoaXMuX3BvaW50P3RoaXMuX2NvbnRleHQubGluZVRvKGUsdCk6KHRoaXMuX3BvaW50PTEsdGhpcy5fY29udGV4dC5tb3ZlVG8oZSx0KSl9fTtmdW5jdGlvbiBjM3QoZSl7cmV0dXJuIG5ldyBsM3QoZSl9ZnVuY3Rpb24gdTN0KGUpe3JldHVybiBlPDA/LTE6MX1mdW5jdGlvbiBoM3QoZSx0LHIpe3ZhciBuPWUuX3gxLWUuX3gwLGk9dC1lLl94MSxvPShlLl95MS1lLl95MCkvKG58fGk8MCYmLTApLGE9KHItZS5feTEpLyhpfHxuPDAmJi0wKSxzPShvKmkrYSpuKS8obitpKTtyZXR1cm4odTN0KG8pK3UzdChhKSkqTWF0aC5taW4oTWF0aC5hYnMobyksTWF0aC5hYnMoYSksLjUqTWF0aC5hYnMocykpfHwwfWZ1bmN0aW9uIGYzdChlLHQpe3ZhciByPWUuX3gxLWUuX3gwO3JldHVybiByPygzKihlLl95MS1lLl95MCkvci10KS8yOnR9ZnVuY3Rpb24gdSQoZSx0LHIpe3ZhciBuPWUuX3gwLGk9ZS5feTAsbz1lLl94MSxhPWUuX3kxLHM9KG8tbikvMztlLl9jb250ZXh0LmJlemllckN1cnZlVG8obitzLGkrcyp0LG8tcyxhLXMqcixvLGEpfWZ1bmN0aW9uIFg4KGUpe3RoaXMuX2NvbnRleHQ9ZX1YOC5wcm90b3R5cGU9e2FyZWFTdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94MD10aGlzLl94MT10aGlzLl95MD10aGlzLl95MT10aGlzLl90MD1OYU4sdGhpcy5fcG9pbnQ9MH0sbGluZUVuZDpmdW5jdGlvbigpe3N3aXRjaCh0aGlzLl9wb2ludCl7Y2FzZSAyOnRoaXMuX2NvbnRleHQubGluZVRvKHRoaXMuX3gxLHRoaXMuX3kxKTticmVhaztjYXNlIDM6dSQodGhpcyx0aGlzLl90MCxmM3QodGhpcyx0aGlzLl90MCkpO2JyZWFrfSh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmdGhpcy5fcG9pbnQ9PT0xKSYmdGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKSx0aGlzLl9saW5lPTEtdGhpcy5fbGluZX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXt2YXIgcj1OYU47aWYoZT0rZSx0PSt0LCEoZT09PXRoaXMuX3gxJiZ0PT09dGhpcy5feTEpKXtzd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xLHRoaXMuX2xpbmU/dGhpcy5fY29udGV4dC5saW5lVG8oZSx0KTp0aGlzLl9jb250ZXh0Lm1vdmVUbyhlLHQpO2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yO2JyZWFrO2Nhc2UgMjp0aGlzLl9wb2ludD0zLHUkKHRoaXMsZjN0KHRoaXMscj1oM3QodGhpcyxlLHQpKSxyKTticmVhaztkZWZhdWx0OnUkKHRoaXMsdGhpcy5fdDAscj1oM3QodGhpcyxlLHQpKTticmVha310aGlzLl94MD10aGlzLl94MSx0aGlzLl94MT1lLHRoaXMuX3kwPXRoaXMuX3kxLHRoaXMuX3kxPXQsdGhpcy5fdDA9cn19fTtmdW5jdGlvbiBwM3QoZSl7dGhpcy5fY29udGV4dD1uZXcgZDN0KGUpfShwM3QucHJvdG90eXBlPU9iamVjdC5jcmVhdGUoWDgucHJvdG90eXBlKSkucG9pbnQ9ZnVuY3Rpb24oZSx0KXtYOC5wcm90b3R5cGUucG9pbnQuY2FsbCh0aGlzLHQsZSl9O2Z1bmN0aW9uIGQzdChlKXt0aGlzLl9jb250ZXh0PWV9ZDN0LnByb3RvdHlwZT17bW92ZVRvOmZ1bmN0aW9uKGUsdCl7dGhpcy5fY29udGV4dC5tb3ZlVG8odCxlKX0sY2xvc2VQYXRoOmZ1bmN0aW9uKCl7dGhpcy5fY29udGV4dC5jbG9zZVBhdGgoKX0sbGluZVRvOmZ1bmN0aW9uKGUsdCl7dGhpcy5fY29udGV4dC5saW5lVG8odCxlKX0sYmV6aWVyQ3VydmVUbzpmdW5jdGlvbihlLHQscixuLGksbyl7dGhpcy5fY29udGV4dC5iZXppZXJDdXJ2ZVRvKHQsZSxuLHIsbyxpKX19O2Z1bmN0aW9uIG0zdChlKXtyZXR1cm4gbmV3IFg4KGUpfWZ1bmN0aW9uIGczdChlKXtyZXR1cm4gbmV3IHAzdChlKX1mdW5jdGlvbiB5M3QoZSl7dGhpcy5fY29udGV4dD1lfXkzdC5wcm90b3R5cGU9e2FyZWFTdGFydDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9MH0sYXJlYUVuZDpmdW5jdGlvbigpe3RoaXMuX2xpbmU9TmFOfSxsaW5lU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl94PVtdLHRoaXMuX3k9W119LGxpbmVFbmQ6ZnVuY3Rpb24oKXt2YXIgZT10aGlzLl94LHQ9dGhpcy5feSxyPWUubGVuZ3RoO2lmKHIpaWYodGhpcy5fbGluZT90aGlzLl9jb250ZXh0LmxpbmVUbyhlWzBdLHRbMF0pOnRoaXMuX2NvbnRleHQubW92ZVRvKGVbMF0sdFswXSkscj09PTIpdGhpcy5fY29udGV4dC5saW5lVG8oZVsxXSx0WzFdKTtlbHNlIGZvcih2YXIgbj1fM3QoZSksaT1fM3QodCksbz0wLGE9MTthPHI7KytvLCsrYSl0aGlzLl9jb250ZXh0LmJlemllckN1cnZlVG8oblswXVtvXSxpWzBdW29dLG5bMV1bb10saVsxXVtvXSxlW2FdLHRbYV0pOyh0aGlzLl9saW5lfHx0aGlzLl9saW5lIT09MCYmcj09PTEpJiZ0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpLHRoaXMuX2xpbmU9MS10aGlzLl9saW5lLHRoaXMuX3g9dGhpcy5feT1udWxsfSxwb2ludDpmdW5jdGlvbihlLHQpe3RoaXMuX3gucHVzaCgrZSksdGhpcy5feS5wdXNoKCt0KX19O2Z1bmN0aW9uIF8zdChlKXt2YXIgdCxyPWUubGVuZ3RoLTEsbixpPW5ldyBBcnJheShyKSxvPW5ldyBBcnJheShyKSxhPW5ldyBBcnJheShyKTtmb3IoaVswXT0wLG9bMF09MixhWzBdPWVbMF0rMiplWzFdLHQ9MTt0PHItMTsrK3QpaVt0XT0xLG9bdF09NCxhW3RdPTQqZVt0XSsyKmVbdCsxXTtmb3IoaVtyLTFdPTIsb1tyLTFdPTcsYVtyLTFdPTgqZVtyLTFdK2Vbcl0sdD0xO3Q8cjsrK3Qpbj1pW3RdL29bdC0xXSxvW3RdLT1uLGFbdF0tPW4qYVt0LTFdO2ZvcihpW3ItMV09YVtyLTFdL29bci0xXSx0PXItMjt0Pj0wOy0tdClpW3RdPShhW3RdLWlbdCsxXSkvb1t0XTtmb3Iob1tyLTFdPShlW3JdK2lbci0xXSkvMix0PTA7dDxyLTE7Kyt0KW9bdF09MiplW3QrMV0taVt0KzFdO3JldHVybltpLG9dfWZ1bmN0aW9uIHYzdChlKXtyZXR1cm4gbmV3IHkzdChlKX1mdW5jdGlvbiAkOChlLHQpe3RoaXMuX2NvbnRleHQ9ZSx0aGlzLl90PXR9JDgucHJvdG90eXBlPXthcmVhU3RhcnQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPTB9LGFyZWFFbmQ6ZnVuY3Rpb24oKXt0aGlzLl9saW5lPU5hTn0sbGluZVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5feD10aGlzLl95PU5hTix0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7MDx0aGlzLl90JiZ0aGlzLl90PDEmJnRoaXMuX3BvaW50PT09MiYmdGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feCx0aGlzLl95KSwodGhpcy5fbGluZXx8dGhpcy5fbGluZSE9PTAmJnRoaXMuX3BvaW50PT09MSkmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCksdGhpcy5fbGluZT49MCYmKHRoaXMuX3Q9MS10aGlzLl90LHRoaXMuX2xpbmU9MS10aGlzLl9saW5lKX0scG9pbnQ6ZnVuY3Rpb24oZSx0KXtzd2l0Y2goZT0rZSx0PSt0LHRoaXMuX3BvaW50KXtjYXNlIDA6dGhpcy5fcG9pbnQ9MSx0aGlzLl9saW5lP3RoaXMuX2NvbnRleHQubGluZVRvKGUsdCk6dGhpcy5fY29udGV4dC5tb3ZlVG8oZSx0KTticmVhaztjYXNlIDE6dGhpcy5fcG9pbnQ9MjtkZWZhdWx0OntpZih0aGlzLl90PD0wKXRoaXMuX2NvbnRleHQubGluZVRvKHRoaXMuX3gsdCksdGhpcy5fY29udGV4dC5saW5lVG8oZSx0KTtlbHNle3ZhciByPXRoaXMuX3gqKDEtdGhpcy5fdCkrZSp0aGlzLl90O3RoaXMuX2NvbnRleHQubGluZVRvKHIsdGhpcy5feSksdGhpcy5fY29udGV4dC5saW5lVG8ocix0KX1icmVha319dGhpcy5feD1lLHRoaXMuX3k9dH19O2Z1bmN0aW9uIHgzdChlKXtyZXR1cm4gbmV3ICQ4KGUsLjUpfWZ1bmN0aW9uIGIzdChlKXtyZXR1cm4gbmV3ICQ4KGUsMCl9ZnVuY3Rpb24gdzN0KGUpe3JldHVybiBuZXcgJDgoZSwxKX1mdW5jdGlvbiB4dShlLHQpe2lmKChhPWUubGVuZ3RoKT4xKWZvcih2YXIgcj0xLG4saSxvPWVbdFswXV0sYSxzPW8ubGVuZ3RoO3I8YTsrK3IpZm9yKGk9byxvPWVbdFtyXV0sbj0wO248czsrK24pb1tuXVsxXSs9b1tuXVswXT1pc05hTihpW25dWzFdKT9pW25dWzBdOmlbbl1bMV19ZnVuY3Rpb24gYnUoZSl7Zm9yKHZhciB0PWUubGVuZ3RoLHI9bmV3IEFycmF5KHQpOy0tdD49MDspclt0XT10O3JldHVybiByfWZ1bmN0aW9uIEdFZShlLHQpe3JldHVybiBlW3RdfWZ1bmN0aW9uIFMzdCgpe3ZhciBlPXFlKFtdKSx0PWJ1LHI9eHUsbj1HRWU7ZnVuY3Rpb24gaShvKXt2YXIgYT1lLmFwcGx5KHRoaXMsYXJndW1lbnRzKSxzLGw9by5sZW5ndGgsYz1hLmxlbmd0aCx1PW5ldyBBcnJheShjKSxoO2ZvcihzPTA7czxjOysrcyl7Zm9yKHZhciBmPWFbc10scD11W3NdPW5ldyBBcnJheShsKSxkPTAsZztkPGw7KytkKXBbZF09Zz1bMCwrbihvW2RdLGYsZCxvKV0sZy5kYXRhPW9bZF07cC5rZXk9Zn1mb3Iocz0wLGg9dCh1KTtzPGM7KytzKXVbaFtzXV0uaW5kZXg9cztyZXR1cm4gcih1LGgpLHV9cmV0dXJuIGkua2V5cz1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10eXBlb2Ygbz09ImZ1bmN0aW9uIj9vOnFlKEk1LmNhbGwobykpLGkpOmV9LGkudmFsdWU9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49dHlwZW9mIG89PSJmdW5jdGlvbiI/bzpxZSgrbyksaSk6bn0saS5vcmRlcj1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD1vPT1udWxsP2J1OnR5cGVvZiBvPT0iZnVuY3Rpb24iP286cWUoSTUuY2FsbChvKSksaSk6dH0saS5vZmZzZXQ9ZnVuY3Rpb24obyl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9bz09bnVsbD94dTpvLGkpOnJ9LGl9ZnVuY3Rpb24gTTN0KGUsdCl7aWYoKG49ZS5sZW5ndGgpPjApe2Zvcih2YXIgcixuLGk9MCxvPWVbMF0ubGVuZ3RoLGE7aTxvOysraSl7Zm9yKGE9cj0wO3I8bjsrK3IpYSs9ZVtyXVtpXVsxXXx8MDtpZihhKWZvcihyPTA7cjxuOysrcillW3JdW2ldWzFdLz1hfXh1KGUsdCl9fWZ1bmN0aW9uIEUzdChlLHQpe2lmKChsPWUubGVuZ3RoKT4wKWZvcih2YXIgcixuPTAsaSxvLGEscyxsLGM9ZVt0WzBdXS5sZW5ndGg7bjxjOysrbilmb3IoYT1zPTAscj0wO3I8bDsrK3IpKG89KGk9ZVt0W3JdXVtuXSlbMV0taVswXSk+MD8oaVswXT1hLGlbMV09YSs9byk6bzwwPyhpWzFdPXMsaVswXT1zKz1vKTooaVswXT0wLGlbMV09byl9ZnVuY3Rpb24gVDN0KGUsdCl7aWYoKGk9ZS5sZW5ndGgpPjApe2Zvcih2YXIgcj0wLG49ZVt0WzBdXSxpLG89bi5sZW5ndGg7cjxvOysrcil7Zm9yKHZhciBhPTAscz0wO2E8aTsrK2Epcys9ZVthXVtyXVsxXXx8MDtuW3JdWzFdKz1uW3JdWzBdPS1zLzJ9eHUoZSx0KX19ZnVuY3Rpb24gQzN0KGUsdCl7aWYoISghKChhPWUubGVuZ3RoKT4wKXx8ISgobz0oaT1lW3RbMF1dKS5sZW5ndGgpPjApKSl7Zm9yKHZhciByPTAsbj0xLGksbyxhO248bzsrK24pe2Zvcih2YXIgcz0wLGw9MCxjPTA7czxhOysrcyl7Zm9yKHZhciB1PWVbdFtzXV0saD11W25dWzFdfHwwLGY9dVtuLTFdWzFdfHwwLHA9KGgtZikvMixkPTA7ZDxzOysrZCl7dmFyIGc9ZVt0W2RdXSxfPWdbbl1bMV18fDAseT1nW24tMV1bMV18fDA7cCs9Xy15fWwrPWgsYys9cCpofWlbbi0xXVsxXSs9aVtuLTFdWzBdPXIsbCYmKHItPWMvbCl9aVtuLTFdWzFdKz1pW24tMV1bMF09cix4dShlLHQpfX1mdW5jdGlvbiBLOChlKXt2YXIgdD1lLm1hcChXRWUpO3JldHVybiBidShlKS5zb3J0KGZ1bmN0aW9uKHIsbil7cmV0dXJuIHRbcl0tdFtuXX0pfWZ1bmN0aW9uIFdFZShlKXtmb3IodmFyIHQ9LTEscj0wLG49ZS5sZW5ndGgsaSxvPS0xLzA7Kyt0PG47KShpPStlW3RdWzFdKT5vJiYobz1pLHI9dCk7cmV0dXJuIHJ9ZnVuY3Rpb24gWjgoZSl7dmFyIHQ9ZS5tYXAoaCQpO3JldHVybiBidShlKS5zb3J0KGZ1bmN0aW9uKHIsbil7cmV0dXJuIHRbcl0tdFtuXX0pfWZ1bmN0aW9uIGgkKGUpe2Zvcih2YXIgdD0wLHI9LTEsbj1lLmxlbmd0aCxpOysrcjxuOykoaT0rZVtyXVsxXSkmJih0Kz1pKTtyZXR1cm4gdH1mdW5jdGlvbiBBM3QoZSl7cmV0dXJuIFo4KGUpLnJldmVyc2UoKX1mdW5jdGlvbiBQM3QoZSl7dmFyIHQ9ZS5sZW5ndGgscixuLGk9ZS5tYXAoaCQpLG89SzgoZSksYT0wLHM9MCxsPVtdLGM9W107Zm9yKHI9MDtyPHQ7KytyKW49b1tyXSxhPHM/KGErPWlbbl0sbC5wdXNoKG4pKToocys9aVtuXSxjLnB1c2gobikpO3JldHVybiBjLnJldmVyc2UoKS5jb25jYXQobCl9ZnVuY3Rpb24gSTN0KGUpe3JldHVybiBidShlKS5yZXZlcnNlKCl9ZnVuY3Rpb24gZiQoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fWZ1bmN0aW9uIEwzdChlKXtyZXR1cm4gZVswXX1mdW5jdGlvbiBrM3QoZSl7cmV0dXJuIGVbMV19ZnVuY3Rpb24gcCQoKXt0aGlzLl89bnVsbH1mdW5jdGlvbiB0MihlKXtlLlU9ZS5DPWUuTD1lLlI9ZS5QPWUuTj1udWxsfXAkLnByb3RvdHlwZT17Y29uc3RydWN0b3I6cCQsaW5zZXJ0OmZ1bmN0aW9uKGUsdCl7dmFyIHIsbixpO2lmKGUpe2lmKHQuUD1lLHQuTj1lLk4sZS5OJiYoZS5OLlA9dCksZS5OPXQsZS5SKXtmb3IoZT1lLlI7ZS5MOyllPWUuTDtlLkw9dH1lbHNlIGUuUj10O3I9ZX1lbHNlIHRoaXMuXz8oZT1SM3QodGhpcy5fKSx0LlA9bnVsbCx0Lk49ZSxlLlA9ZS5MPXQscj1lKToodC5QPXQuTj1udWxsLHRoaXMuXz10LHI9bnVsbCk7Zm9yKHQuTD10LlI9bnVsbCx0LlU9cix0LkM9ITAsZT10O3ImJnIuQzspbj1yLlUscj09PW4uTD8oaT1uLlIsaSYmaS5DPyhyLkM9aS5DPSExLG4uQz0hMCxlPW4pOihlPT09ci5SJiYoTjUodGhpcyxyKSxlPXIscj1lLlUpLHIuQz0hMSxuLkM9ITAsRDUodGhpcyxuKSkpOihpPW4uTCxpJiZpLkM/KHIuQz1pLkM9ITEsbi5DPSEwLGU9bik6KGU9PT1yLkwmJihENSh0aGlzLHIpLGU9cixyPWUuVSksci5DPSExLG4uQz0hMCxONSh0aGlzLG4pKSkscj1lLlU7dGhpcy5fLkM9ITF9LHJlbW92ZTpmdW5jdGlvbihlKXtlLk4mJihlLk4uUD1lLlApLGUuUCYmKGUuUC5OPWUuTiksZS5OPWUuUD1udWxsO3ZhciB0PWUuVSxyLG49ZS5MLGk9ZS5SLG8sYTtpZihuP2k/bz1SM3QoaSk6bz1uOm89aSx0P3QuTD09PWU/dC5MPW86dC5SPW86dGhpcy5fPW8sbiYmaT8oYT1vLkMsby5DPWUuQyxvLkw9bixuLlU9byxvIT09aT8odD1vLlUsby5VPWUuVSxlPW8uUix0Lkw9ZSxvLlI9aSxpLlU9byk6KG8uVT10LHQ9byxlPW8uUikpOihhPWUuQyxlPW8pLGUmJihlLlU9dCksIWEpe2lmKGUmJmUuQyl7ZS5DPSExO3JldHVybn1kb3tpZihlPT09dGhpcy5fKWJyZWFrO2lmKGU9PT10Lkwpe2lmKHI9dC5SLHIuQyYmKHIuQz0hMSx0LkM9ITAsTjUodGhpcyx0KSxyPXQuUiksci5MJiZyLkwuQ3x8ci5SJiZyLlIuQyl7KCFyLlJ8fCFyLlIuQykmJihyLkwuQz0hMSxyLkM9ITAsRDUodGhpcyxyKSxyPXQuUiksci5DPXQuQyx0LkM9ci5SLkM9ITEsTjUodGhpcyx0KSxlPXRoaXMuXzticmVha319ZWxzZSBpZihyPXQuTCxyLkMmJihyLkM9ITEsdC5DPSEwLEQ1KHRoaXMsdCkscj10LkwpLHIuTCYmci5MLkN8fHIuUiYmci5SLkMpeyghci5MfHwhci5MLkMpJiYoci5SLkM9ITEsci5DPSEwLE41KHRoaXMscikscj10LkwpLHIuQz10LkMsdC5DPXIuTC5DPSExLEQ1KHRoaXMsdCksZT10aGlzLl87YnJlYWt9ci5DPSEwLGU9dCx0PXQuVX13aGlsZSghZS5DKTtlJiYoZS5DPSExKX19fTtmdW5jdGlvbiBONShlLHQpe3ZhciByPXQsbj10LlIsaT1yLlU7aT9pLkw9PT1yP2kuTD1uOmkuUj1uOmUuXz1uLG4uVT1pLHIuVT1uLHIuUj1uLkwsci5SJiYoci5SLlU9ciksbi5MPXJ9ZnVuY3Rpb24gRDUoZSx0KXt2YXIgcj10LG49dC5MLGk9ci5VO2k/aS5MPT09cj9pLkw9bjppLlI9bjplLl89bixuLlU9aSxyLlU9bixyLkw9bi5SLHIuTCYmKHIuTC5VPXIpLG4uUj1yfWZ1bmN0aW9uIFIzdChlKXtmb3IoO2UuTDspZT1lLkw7cmV0dXJuIGV9dmFyIGQkPXAkO2Z1bmN0aW9uIGUyKGUsdCxyLG4pe3ZhciBpPVtudWxsLG51bGxdLG89d28ucHVzaChpKS0xO3JldHVybiBpLmxlZnQ9ZSxpLnJpZ2h0PXQsciYmTzUoaSxlLHQsciksbiYmTzUoaSx0LGUsbiksVmFbZS5pbmRleF0uaGFsZmVkZ2VzLnB1c2gobyksVmFbdC5pbmRleF0uaGFsZmVkZ2VzLnB1c2gobyksaX1mdW5jdGlvbiByMihlLHQscil7dmFyIG49W3Qscl07cmV0dXJuIG4ubGVmdD1lLG59ZnVuY3Rpb24gTzUoZSx0LHIsbil7IWVbMF0mJiFlWzFdPyhlWzBdPW4sZS5sZWZ0PXQsZS5yaWdodD1yKTplLmxlZnQ9PT1yP2VbMV09bjplWzBdPW59ZnVuY3Rpb24gWUVlKGUsdCxyLG4saSl7dmFyIG89ZVswXSxhPWVbMV0scz1vWzBdLGw9b1sxXSxjPWFbMF0sdT1hWzFdLGg9MCxmPTEscD1jLXMsZD11LWwsZztpZihnPXQtcywhKCFwJiZnPjApKXtpZihnLz1wLHA8MCl7aWYoZzxoKXJldHVybjtnPGYmJihmPWcpfWVsc2UgaWYocD4wKXtpZihnPmYpcmV0dXJuO2c+aCYmKGg9Zyl9aWYoZz1uLXMsISghcCYmZzwwKSl7aWYoZy89cCxwPDApe2lmKGc+ZilyZXR1cm47Zz5oJiYoaD1nKX1lbHNlIGlmKHA+MCl7aWYoZzxoKXJldHVybjtnPGYmJihmPWcpfWlmKGc9ci1sLCEoIWQmJmc+MCkpe2lmKGcvPWQsZDwwKXtpZihnPGgpcmV0dXJuO2c8ZiYmKGY9Zyl9ZWxzZSBpZihkPjApe2lmKGc+ZilyZXR1cm47Zz5oJiYoaD1nKX1pZihnPWktbCwhKCFkJiZnPDApKXtpZihnLz1kLGQ8MCl7aWYoZz5mKXJldHVybjtnPmgmJihoPWcpfWVsc2UgaWYoZD4wKXtpZihnPGgpcmV0dXJuO2c8ZiYmKGY9Zyl9cmV0dXJuIShoPjApJiYhKGY8MSl8fChoPjAmJihlWzBdPVtzK2gqcCxsK2gqZF0pLGY8MSYmKGVbMV09W3MrZipwLGwrZipkXSkpLCEwfX19fX1mdW5jdGlvbiBqRWUoZSx0LHIsbixpKXt2YXIgbz1lWzFdO2lmKG8pcmV0dXJuITA7dmFyIGE9ZVswXSxzPWUubGVmdCxsPWUucmlnaHQsYz1zWzBdLHU9c1sxXSxoPWxbMF0sZj1sWzFdLHA9KGMraCkvMixkPSh1K2YpLzIsZyxfO2lmKGY9PT11KXtpZihwPHR8fHA+PW4pcmV0dXJuO2lmKGM+aCl7aWYoIWEpYT1bcCxyXTtlbHNlIGlmKGFbMV0+PWkpcmV0dXJuO289W3AsaV19ZWxzZXtpZighYSlhPVtwLGldO2Vsc2UgaWYoYVsxXTxyKXJldHVybjtvPVtwLHJdfX1lbHNlIGlmKGc9KGMtaCkvKGYtdSksXz1kLWcqcCxnPC0xfHxnPjEpaWYoYz5oKXtpZighYSlhPVsoci1fKS9nLHJdO2Vsc2UgaWYoYVsxXT49aSlyZXR1cm47bz1bKGktXykvZyxpXX1lbHNle2lmKCFhKWE9WyhpLV8pL2csaV07ZWxzZSBpZihhWzFdPHIpcmV0dXJuO289WyhyLV8pL2cscl19ZWxzZSBpZih1PGYpe2lmKCFhKWE9W3QsZyp0K19dO2Vsc2UgaWYoYVswXT49bilyZXR1cm47bz1bbixnKm4rX119ZWxzZXtpZighYSlhPVtuLGcqbitfXTtlbHNlIGlmKGFbMF08dClyZXR1cm47bz1bdCxnKnQrX119cmV0dXJuIGVbMF09YSxlWzFdPW8sITB9ZnVuY3Rpb24gTjN0KGUsdCxyLG4pe2Zvcih2YXIgaT13by5sZW5ndGgsbztpLS07KSghakVlKG89d29baV0sZSx0LHIsbil8fCFZRWUobyxlLHQscixuKXx8IShNYXRoLmFicyhvWzBdWzBdLW9bMV1bMF0pPkhyfHxNYXRoLmFicyhvWzBdWzFdLW9bMV1bMV0pPkhyKSkmJmRlbGV0ZSB3b1tpXX1mdW5jdGlvbiBEM3QoZSl7cmV0dXJuIFZhW2UuaW5kZXhdPXtzaXRlOmUsaGFsZmVkZ2VzOltdfX1mdW5jdGlvbiBYRWUoZSx0KXt2YXIgcj1lLnNpdGUsbj10LmxlZnQsaT10LnJpZ2h0O3JldHVybiByPT09aSYmKGk9bixuPXIpLGk/TWF0aC5hdGFuMihpWzFdLW5bMV0saVswXS1uWzBdKToocj09PW4/KG49dFsxXSxpPXRbMF0pOihuPXRbMF0saT10WzFdKSxNYXRoLmF0YW4yKG5bMF0taVswXSxpWzFdLW5bMV0pKX1mdW5jdGlvbiBtJChlLHQpe3JldHVybiB0WysodC5sZWZ0IT09ZS5zaXRlKV19ZnVuY3Rpb24gJEVlKGUsdCl7cmV0dXJuIHRbKyh0LmxlZnQ9PT1lLnNpdGUpXX1mdW5jdGlvbiBPM3QoKXtmb3IodmFyIGU9MCx0PVZhLmxlbmd0aCxyLG4saSxvO2U8dDsrK2UpaWYoKHI9VmFbZV0pJiYobz0obj1yLmhhbGZlZGdlcykubGVuZ3RoKSl7dmFyIGE9bmV3IEFycmF5KG8pLHM9bmV3IEFycmF5KG8pO2ZvcihpPTA7aTxvOysraSlhW2ldPWksc1tpXT1YRWUocix3b1tuW2ldXSk7Zm9yKGEuc29ydChmdW5jdGlvbihsLGMpe3JldHVybiBzW2NdLXNbbF19KSxpPTA7aTxvOysraSlzW2ldPW5bYVtpXV07Zm9yKGk9MDtpPG87KytpKW5baV09c1tpXX19ZnVuY3Rpb24gejN0KGUsdCxyLG4pe3ZhciBpPVZhLmxlbmd0aCxvLGEscyxsLGMsdSxoLGYscCxkLGcsXyx5PSEwO2ZvcihvPTA7bzxpOysrbylpZihhPVZhW29dKXtmb3Iocz1hLnNpdGUsYz1hLmhhbGZlZGdlcyxsPWMubGVuZ3RoO2wtLTspd29bY1tsXV18fGMuc3BsaWNlKGwsMSk7Zm9yKGw9MCx1PWMubGVuZ3RoO2w8dTspZD0kRWUoYSx3b1tjW2xdXSksZz1kWzBdLF89ZFsxXSxoPW0kKGEsd29bY1srK2wldV1dKSxmPWhbMF0scD1oWzFdLChNYXRoLmFicyhnLWYpPkhyfHxNYXRoLmFicyhfLXApPkhyKSYmKGMuc3BsaWNlKGwsMCx3by5wdXNoKHIyKHMsZCxNYXRoLmFicyhnLWUpPEhyJiZuLV8+SHI/W2UsTWF0aC5hYnMoZi1lKTxIcj9wOm5dOk1hdGguYWJzKF8tbik8SHImJnItZz5Icj9bTWF0aC5hYnMocC1uKTxIcj9mOnIsbl06TWF0aC5hYnMoZy1yKTxIciYmXy10PkhyP1tyLE1hdGguYWJzKGYtcik8SHI/cDp0XTpNYXRoLmFicyhfLXQpPEhyJiZnLWU+SHI/W01hdGguYWJzKHAtdCk8SHI/ZjplLHRdOm51bGwpKS0xKSwrK3UpO3UmJih5PSExKX1pZih5KXt2YXIgeCxiLFMsQz0xLzA7Zm9yKG89MCx5PW51bGw7bzxpOysrbykoYT1WYVtvXSkmJihzPWEuc2l0ZSx4PXNbMF0tZSxiPXNbMV0tdCxTPXgqeCtiKmIsUzxDJiYoQz1TLHk9YSkpO2lmKHkpe3ZhciBQPVtlLHRdLGs9W2Usbl0sTz1bcixuXSxEPVtyLHRdO3kuaGFsZmVkZ2VzLnB1c2god28ucHVzaChyMihzPXkuc2l0ZSxQLGspKS0xLHdvLnB1c2gocjIocyxrLE8pKS0xLHdvLnB1c2gocjIocyxPLEQpKS0xLHdvLnB1c2gocjIocyxELFApKS0xKX19Zm9yKG89MDtvPGk7KytvKShhPVZhW29dKSYmKGEuaGFsZmVkZ2VzLmxlbmd0aHx8ZGVsZXRlIFZhW29dKX12YXIgRjN0PVtdLEo4O2Z1bmN0aW9uIEtFZSgpe3QyKHRoaXMpLHRoaXMueD10aGlzLnk9dGhpcy5hcmM9dGhpcy5zaXRlPXRoaXMuY3k9bnVsbH1mdW5jdGlvbiBjeShlKXt2YXIgdD1lLlAscj1lLk47aWYoISghdHx8IXIpKXt2YXIgbj10LnNpdGUsaT1lLnNpdGUsbz1yLnNpdGU7aWYobiE9PW8pe3ZhciBhPWlbMF0scz1pWzFdLGw9blswXS1hLGM9blsxXS1zLHU9b1swXS1hLGg9b1sxXS1zLGY9MioobCpoLWMqdSk7aWYoIShmPj0tQjN0KSl7dmFyIHA9bCpsK2MqYyxkPXUqdStoKmgsZz0oaCpwLWMqZCkvZixfPShsKmQtdSpwKS9mLHk9RjN0LnBvcCgpfHxuZXcgS0VlO3kuYXJjPWUseS5zaXRlPWkseS54PWcrYSx5Lnk9KHkuY3k9XytzKStNYXRoLnNxcnQoZypnK18qXyksZS5jaXJjbGU9eTtmb3IodmFyIHg9bnVsbCxiPW4yLl87YjspaWYoeS55PGIueXx8eS55PT09Yi55JiZ5Lng8PWIueClpZihiLkwpYj1iLkw7ZWxzZXt4PWIuUDticmVha31lbHNlIGlmKGIuUiliPWIuUjtlbHNle3g9YjticmVha31uMi5pbnNlcnQoeCx5KSx4fHwoSjg9eSl9fX19ZnVuY3Rpb24gdXkoZSl7dmFyIHQ9ZS5jaXJjbGU7dCYmKHQuUHx8KEo4PXQuTiksbjIucmVtb3ZlKHQpLEYzdC5wdXNoKHQpLHQyKHQpLGUuY2lyY2xlPW51bGwpfXZhciBWM3Q9W107ZnVuY3Rpb24gWkVlKCl7dDIodGhpcyksdGhpcy5lZGdlPXRoaXMuc2l0ZT10aGlzLmNpcmNsZT1udWxsfWZ1bmN0aW9uIEgzdChlKXt2YXIgdD1WM3QucG9wKCl8fG5ldyBaRWU7cmV0dXJuIHQuc2l0ZT1lLHR9ZnVuY3Rpb24gZyQoZSl7dXkoZSksaHkucmVtb3ZlKGUpLFYzdC5wdXNoKGUpLHQyKGUpfWZ1bmN0aW9uIFUzdChlKXt2YXIgdD1lLmNpcmNsZSxyPXQueCxuPXQuY3ksaT1bcixuXSxvPWUuUCxhPWUuTixzPVtlXTtnJChlKTtmb3IodmFyIGw9bztsLmNpcmNsZSYmTWF0aC5hYnMoci1sLmNpcmNsZS54KTxIciYmTWF0aC5hYnMobi1sLmNpcmNsZS5jeSk8SHI7KW89bC5QLHMudW5zaGlmdChsKSxnJChsKSxsPW87cy51bnNoaWZ0KGwpLHV5KGwpO2Zvcih2YXIgYz1hO2MuY2lyY2xlJiZNYXRoLmFicyhyLWMuY2lyY2xlLngpPEhyJiZNYXRoLmFicyhuLWMuY2lyY2xlLmN5KTxIcjspYT1jLk4scy5wdXNoKGMpLGckKGMpLGM9YTtzLnB1c2goYyksdXkoYyk7dmFyIHU9cy5sZW5ndGgsaDtmb3IoaD0xO2g8dTsrK2gpYz1zW2hdLGw9c1toLTFdLE81KGMuZWRnZSxsLnNpdGUsYy5zaXRlLGkpO2w9c1swXSxjPXNbdS0xXSxjLmVkZ2U9ZTIobC5zaXRlLGMuc2l0ZSxudWxsLGkpLGN5KGwpLGN5KGMpfWZ1bmN0aW9uIHEzdChlKXtmb3IodmFyIHQ9ZVswXSxyPWVbMV0sbixpLG8sYSxzPWh5Ll87czspaWYobz1HM3QocyxyKS10LG8+SHIpcz1zLkw7ZWxzZSBpZihhPXQtSkVlKHMsciksYT5Icil7aWYoIXMuUil7bj1zO2JyZWFrfXM9cy5SfWVsc2V7bz4tSHI/KG49cy5QLGk9cyk6YT4tSHI/KG49cyxpPXMuTik6bj1pPXM7YnJlYWt9RDN0KGUpO3ZhciBsPUgzdChlKTtpZihoeS5pbnNlcnQobixsKSwhKCFuJiYhaSkpe2lmKG49PT1pKXt1eShuKSxpPUgzdChuLnNpdGUpLGh5Lmluc2VydChsLGkpLGwuZWRnZT1pLmVkZ2U9ZTIobi5zaXRlLGwuc2l0ZSksY3kobiksY3koaSk7cmV0dXJufWlmKCFpKXtsLmVkZ2U9ZTIobi5zaXRlLGwuc2l0ZSk7cmV0dXJufXV5KG4pLHV5KGkpO3ZhciBjPW4uc2l0ZSx1PWNbMF0saD1jWzFdLGY9ZVswXS11LHA9ZVsxXS1oLGQ9aS5zaXRlLGc9ZFswXS11LF89ZFsxXS1oLHk9MiooZipfLXAqZykseD1mKmYrcCpwLGI9ZypnK18qXyxTPVsoXyp4LXAqYikveSt1LChmKmItZyp4KS95K2hdO081KGkuZWRnZSxjLGQsUyksbC5lZGdlPWUyKGMsZSxudWxsLFMpLGkuZWRnZT1lMihlLGQsbnVsbCxTKSxjeShuKSxjeShpKX19ZnVuY3Rpb24gRzN0KGUsdCl7dmFyIHI9ZS5zaXRlLG49clswXSxpPXJbMV0sbz1pLXQ7aWYoIW8pcmV0dXJuIG47dmFyIGE9ZS5QO2lmKCFhKXJldHVybi0xLzA7cj1hLnNpdGU7dmFyIHM9clswXSxsPXJbMV0sYz1sLXQ7aWYoIWMpcmV0dXJuIHM7dmFyIHU9cy1uLGg9MS9vLTEvYyxmPXUvYztyZXR1cm4gaD8oLWYrTWF0aC5zcXJ0KGYqZi0yKmgqKHUqdS8oLTIqYyktbCtjLzIraS1vLzIpKSkvaCtuOihuK3MpLzJ9ZnVuY3Rpb24gSkVlKGUsdCl7dmFyIHI9ZS5OO2lmKHIpcmV0dXJuIEczdChyLHQpO3ZhciBuPWUuc2l0ZTtyZXR1cm4gblsxXT09PXQ/blswXToxLzB9dmFyIEhyPTFlLTYsQjN0PTFlLTEyLGh5LFZhLG4yLHdvO2Z1bmN0aW9uIFFFZShlLHQscil7cmV0dXJuKGVbMF0tclswXSkqKHRbMV0tZVsxXSktKGVbMF0tdFswXSkqKHJbMV0tZVsxXSl9ZnVuY3Rpb24gdDVlKGUsdCl7cmV0dXJuIHRbMV0tZVsxXXx8dFswXS1lWzBdfWZ1bmN0aW9uIHo1KGUsdCl7dmFyIHI9ZS5zb3J0KHQ1ZSkucG9wKCksbixpLG87Zm9yKHdvPVtdLFZhPW5ldyBBcnJheShlLmxlbmd0aCksaHk9bmV3IGQkLG4yPW5ldyBkJDs7KWlmKG89SjgsciYmKCFvfHxyWzFdPG8ueXx8clsxXT09PW8ueSYmclswXTxvLngpKShyWzBdIT09bnx8clsxXSE9PWkpJiYocTN0KHIpLG49clswXSxpPXJbMV0pLHI9ZS5wb3AoKTtlbHNlIGlmKG8pVTN0KG8uYXJjKTtlbHNlIGJyZWFrO2lmKE8zdCgpLHQpe3ZhciBhPSt0WzBdWzBdLHM9K3RbMF1bMV0sbD0rdFsxXVswXSxjPSt0WzFdWzFdO04zdChhLHMsbCxjKSx6M3QoYSxzLGwsYyl9dGhpcy5lZGdlcz13byx0aGlzLmNlbGxzPVZhLGh5PW4yPXdvPVZhPW51bGx9ejUucHJvdG90eXBlPXtjb25zdHJ1Y3Rvcjp6NSxwb2x5Z29uczpmdW5jdGlvbigpe3ZhciBlPXRoaXMuZWRnZXM7cmV0dXJuIHRoaXMuY2VsbHMubWFwKGZ1bmN0aW9uKHQpe3ZhciByPXQuaGFsZmVkZ2VzLm1hcChmdW5jdGlvbihuKXtyZXR1cm4gbSQodCxlW25dKX0pO3JldHVybiByLmRhdGE9dC5zaXRlLmRhdGEscn0pfSx0cmlhbmdsZXM6ZnVuY3Rpb24oKXt2YXIgZT1bXSx0PXRoaXMuZWRnZXM7cmV0dXJuIHRoaXMuY2VsbHMuZm9yRWFjaChmdW5jdGlvbihyLG4pe2lmKCEhKHM9KG89ci5oYWxmZWRnZXMpLmxlbmd0aCkpZm9yKHZhciBpPXIuc2l0ZSxvLGE9LTEscyxsLGM9dFtvW3MtMV1dLHU9Yy5sZWZ0PT09aT9jLnJpZ2h0OmMubGVmdDsrK2E8czspbD11LGM9dFtvW2FdXSx1PWMubGVmdD09PWk/Yy5yaWdodDpjLmxlZnQsbCYmdSYmbjxsLmluZGV4JiZuPHUuaW5kZXgmJlFFZShpLGwsdSk8MCYmZS5wdXNoKFtpLmRhdGEsbC5kYXRhLHUuZGF0YV0pfSksZX0sbGlua3M6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5lZGdlcy5maWx0ZXIoZnVuY3Rpb24oZSl7cmV0dXJuIGUucmlnaHR9KS5tYXAoZnVuY3Rpb24oZSl7cmV0dXJue3NvdXJjZTplLmxlZnQuZGF0YSx0YXJnZXQ6ZS5yaWdodC5kYXRhfX0pfSxmaW5kOmZ1bmN0aW9uKGUsdCxyKXtmb3IodmFyIG49dGhpcyxpLG89bi5fZm91bmR8fDAsYT1uLmNlbGxzLmxlbmd0aCxzOyEocz1uLmNlbGxzW29dKTspaWYoKytvPj1hKXJldHVybiBudWxsO3ZhciBsPWUtcy5zaXRlWzBdLGM9dC1zLnNpdGVbMV0sdT1sKmwrYypjO2RvIHM9bi5jZWxsc1tpPW9dLG89bnVsbCxzLmhhbGZlZGdlcy5mb3JFYWNoKGZ1bmN0aW9uKGgpe3ZhciBmPW4uZWRnZXNbaF0scD1mLmxlZnQ7aWYoISgocD09PXMuc2l0ZXx8IXApJiYhKHA9Zi5yaWdodCkpKXt2YXIgZD1lLXBbMF0sZz10LXBbMV0sXz1kKmQrZypnO188dSYmKHU9XyxvPXAuaW5kZXgpfX0pO3doaWxlKG8hPT1udWxsKTtyZXR1cm4gbi5fZm91bmQ9aSxyPT1udWxsfHx1PD1yKnI/cy5zaXRlOm51bGx9fTtmdW5jdGlvbiBXM3QoKXt2YXIgZT1MM3QsdD1rM3Qscj1udWxsO2Z1bmN0aW9uIG4oaSl7cmV0dXJuIG5ldyB6NShpLm1hcChmdW5jdGlvbihvLGEpe3ZhciBzPVtNYXRoLnJvdW5kKGUobyxhLGkpL0hyKSpIcixNYXRoLnJvdW5kKHQobyxhLGkpL0hyKSpIcl07cmV0dXJuIHMuaW5kZXg9YSxzLmRhdGE9byxzfSkscil9cmV0dXJuIG4ucG9seWdvbnM9ZnVuY3Rpb24oaSl7cmV0dXJuIG4oaSkucG9seWdvbnMoKX0sbi5saW5rcz1mdW5jdGlvbihpKXtyZXR1cm4gbihpKS5saW5rcygpfSxuLnRyaWFuZ2xlcz1mdW5jdGlvbihpKXtyZXR1cm4gbihpKS50cmlhbmdsZXMoKX0sbi54PWZ1bmN0aW9uKGkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPXR5cGVvZiBpPT0iZnVuY3Rpb24iP2k6ZiQoK2kpLG4pOmV9LG4ueT1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD10eXBlb2YgaT09ImZ1bmN0aW9uIj9pOmYkKCtpKSxuKTp0fSxuLmV4dGVudD1mdW5jdGlvbihpKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj1pPT1udWxsP251bGw6W1sraVswXVswXSwraVswXVsxXV0sWytpWzFdWzBdLCtpWzFdWzFdXV0sbik6ciYmW1tyWzBdWzBdLHJbMF1bMV1dLFtyWzFdWzBdLHJbMV1bMV1dXX0sbi5zaXplPWZ1bmN0aW9uKGkpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPWk9PW51bGw/bnVsbDpbWzAsMF0sWytpWzBdLCtpWzFdXV0sbik6ciYmW3JbMV1bMF0tclswXVswXSxyWzFdWzFdLXJbMF1bMV1dfSxufWttKCk7ZnVuY3Rpb24gRjUoZSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGV9fWZ1bmN0aW9uIF8kKGUsdCxyKXt0aGlzLnRhcmdldD1lLHRoaXMudHlwZT10LHRoaXMudHJhbnNmb3JtPXJ9ZnVuY3Rpb24gamgoZSx0LHIpe3RoaXMuaz1lLHRoaXMueD10LHRoaXMueT1yfWpoLnByb3RvdHlwZT17Y29uc3RydWN0b3I6amgsc2NhbGU6ZnVuY3Rpb24oZSl7cmV0dXJuIGU9PT0xP3RoaXM6bmV3IGpoKHRoaXMuayplLHRoaXMueCx0aGlzLnkpfSx0cmFuc2xhdGU6ZnVuY3Rpb24oZSx0KXtyZXR1cm4gZT09PTAmdD09PTA/dGhpczpuZXcgamgodGhpcy5rLHRoaXMueCt0aGlzLmsqZSx0aGlzLnkrdGhpcy5rKnQpfSxhcHBseTpmdW5jdGlvbihlKXtyZXR1cm5bZVswXSp0aGlzLmsrdGhpcy54LGVbMV0qdGhpcy5rK3RoaXMueV19LGFwcGx5WDpmdW5jdGlvbihlKXtyZXR1cm4gZSp0aGlzLmsrdGhpcy54fSxhcHBseVk6ZnVuY3Rpb24oZSl7cmV0dXJuIGUqdGhpcy5rK3RoaXMueX0saW52ZXJ0OmZ1bmN0aW9uKGUpe3JldHVyblsoZVswXS10aGlzLngpL3RoaXMuaywoZVsxXS10aGlzLnkpL3RoaXMua119LGludmVydFg6ZnVuY3Rpb24oZSl7cmV0dXJuKGUtdGhpcy54KS90aGlzLmt9LGludmVydFk6ZnVuY3Rpb24oZSl7cmV0dXJuKGUtdGhpcy55KS90aGlzLmt9LHJlc2NhbGVYOmZ1bmN0aW9uKGUpe3JldHVybiBlLmNvcHkoKS5kb21haW4oZS5yYW5nZSgpLm1hcCh0aGlzLmludmVydFgsdGhpcykubWFwKGUuaW52ZXJ0LGUpKX0scmVzY2FsZVk6ZnVuY3Rpb24oZSl7cmV0dXJuIGUuY29weSgpLmRvbWFpbihlLnJhbmdlKCkubWFwKHRoaXMuaW52ZXJ0WSx0aGlzKS5tYXAoZS5pbnZlcnQsZSkpfSx0b1N0cmluZzpmdW5jdGlvbigpe3JldHVybiJ0cmFuc2xhdGUoIit0aGlzLngrIiwiK3RoaXMueSsiKSBzY2FsZSgiK3RoaXMuaysiKSJ9fTt2YXIgWGg9bmV3IGpoKDEsMCwwKTtpMi5wcm90b3R5cGU9amgucHJvdG90eXBlO2Z1bmN0aW9uIGkyKGUpe2Zvcig7IWUuX196b29tOylpZighKGU9ZS5wYXJlbnROb2RlKSlyZXR1cm4gWGg7cmV0dXJuIGUuX196b29tfWZ1bmN0aW9uIFE4KCl7cXQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCl9ZnVuY3Rpb24gbzIoKXtxdC5wcmV2ZW50RGVmYXVsdCgpLHF0LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpfWZ1bmN0aW9uIGU1ZSgpe3JldHVybiFxdC5jdHJsS2V5JiYhcXQuYnV0dG9ufWZ1bmN0aW9uIHI1ZSgpe3ZhciBlPXRoaXM7cmV0dXJuIGUgaW5zdGFuY2VvZiBTVkdFbGVtZW50PyhlPWUub3duZXJTVkdFbGVtZW50fHxlLGUuaGFzQXR0cmlidXRlKCJ2aWV3Qm94Iik/KGU9ZS52aWV3Qm94LmJhc2VWYWwsW1tlLngsZS55XSxbZS54K2Uud2lkdGgsZS55K2UuaGVpZ2h0XV0pOltbMCwwXSxbZS53aWR0aC5iYXNlVmFsLnZhbHVlLGUuaGVpZ2h0LmJhc2VWYWwudmFsdWVdXSk6W1swLDBdLFtlLmNsaWVudFdpZHRoLGUuY2xpZW50SGVpZ2h0XV19ZnVuY3Rpb24gWTN0KCl7cmV0dXJuIHRoaXMuX196b29tfHxYaH1mdW5jdGlvbiBuNWUoKXtyZXR1cm4tcXQuZGVsdGFZKihxdC5kZWx0YU1vZGU9PT0xPy4wNTpxdC5kZWx0YU1vZGU/MTouMDAyKX1mdW5jdGlvbiBpNWUoKXtyZXR1cm4gbmF2aWdhdG9yLm1heFRvdWNoUG9pbnRzfHwib250b3VjaHN0YXJ0ImluIHRoaXN9ZnVuY3Rpb24gbzVlKGUsdCxyKXt2YXIgbj1lLmludmVydFgodFswXVswXSktclswXVswXSxpPWUuaW52ZXJ0WCh0WzFdWzBdKS1yWzFdWzBdLG89ZS5pbnZlcnRZKHRbMF1bMV0pLXJbMF1bMV0sYT1lLmludmVydFkodFsxXVsxXSktclsxXVsxXTtyZXR1cm4gZS50cmFuc2xhdGUoaT5uPyhuK2kpLzI6TWF0aC5taW4oMCxuKXx8TWF0aC5tYXgoMCxpKSxhPm8/KG8rYSkvMjpNYXRoLm1pbigwLG8pfHxNYXRoLm1heCgwLGEpKX1mdW5jdGlvbiB0Uigpe3ZhciBlPWU1ZSx0PXI1ZSxyPW81ZSxuPW41ZSxpPWk1ZSxvPVswLDEvMF0sYT1bWy0xLzAsLTEvMF0sWzEvMCwxLzBdXSxzPTI1MCxsPXlMLGM9dnMoInN0YXJ0Iiwiem9vbSIsImVuZCIpLHUsaCxmPTUwMCxwPTE1MCxkPTA7ZnVuY3Rpb24gZyhMKXtMLnByb3BlcnR5KCJfX3pvb20iLFkzdCkub24oIndoZWVsLnpvb20iLFApLm9uKCJtb3VzZWRvd24uem9vbSIsaykub24oImRibGNsaWNrLnpvb20iLE8pLmZpbHRlcihpKS5vbigidG91Y2hzdGFydC56b29tIixEKS5vbigidG91Y2htb3ZlLnpvb20iLEIpLm9uKCJ0b3VjaGVuZC56b29tIHRvdWNoY2FuY2VsLnpvb20iLEkpLnN0eWxlKCJ0b3VjaC1hY3Rpb24iLCJub25lIikuc3R5bGUoIi13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvciIsInJnYmEoMCwwLDAsMCkiKX1nLnRyYW5zZm9ybT1mdW5jdGlvbihMLFIsRil7dmFyIHo9TC5zZWxlY3Rpb24/TC5zZWxlY3Rpb24oKTpMO3oucHJvcGVydHkoIl9fem9vbSIsWTN0KSxMIT09ej9iKEwsUixGKTp6LmludGVycnVwdCgpLmVhY2goZnVuY3Rpb24oKXtTKHRoaXMsYXJndW1lbnRzKS5zdGFydCgpLnpvb20obnVsbCx0eXBlb2YgUj09ImZ1bmN0aW9uIj9SLmFwcGx5KHRoaXMsYXJndW1lbnRzKTpSKS5lbmQoKX0pfSxnLnNjYWxlQnk9ZnVuY3Rpb24oTCxSLEYpe2cuc2NhbGVUbyhMLGZ1bmN0aW9uKCl7dmFyIHo9dGhpcy5fX3pvb20uayxVPXR5cGVvZiBSPT0iZnVuY3Rpb24iP1IuYXBwbHkodGhpcyxhcmd1bWVudHMpOlI7cmV0dXJuIHoqVX0sRil9LGcuc2NhbGVUbz1mdW5jdGlvbihMLFIsRil7Zy50cmFuc2Zvcm0oTCxmdW5jdGlvbigpe3ZhciB6PXQuYXBwbHkodGhpcyxhcmd1bWVudHMpLFU9dGhpcy5fX3pvb20sVz1GPT1udWxsP3goeik6dHlwZW9mIEY9PSJmdW5jdGlvbiI/Ri5hcHBseSh0aGlzLGFyZ3VtZW50cyk6RixaPVUuaW52ZXJ0KFcpLHJ0PXR5cGVvZiBSPT0iZnVuY3Rpb24iP1IuYXBwbHkodGhpcyxhcmd1bWVudHMpOlI7cmV0dXJuIHIoeShfKFUscnQpLFcsWikseixhKX0sRil9LGcudHJhbnNsYXRlQnk9ZnVuY3Rpb24oTCxSLEYpe2cudHJhbnNmb3JtKEwsZnVuY3Rpb24oKXtyZXR1cm4gcih0aGlzLl9fem9vbS50cmFuc2xhdGUodHlwZW9mIFI9PSJmdW5jdGlvbiI/Ui5hcHBseSh0aGlzLGFyZ3VtZW50cyk6Uix0eXBlb2YgRj09ImZ1bmN0aW9uIj9GLmFwcGx5KHRoaXMsYXJndW1lbnRzKTpGKSx0LmFwcGx5KHRoaXMsYXJndW1lbnRzKSxhKX0pfSxnLnRyYW5zbGF0ZVRvPWZ1bmN0aW9uKEwsUixGLHope2cudHJhbnNmb3JtKEwsZnVuY3Rpb24oKXt2YXIgVT10LmFwcGx5KHRoaXMsYXJndW1lbnRzKSxXPXRoaXMuX196b29tLFo9ej09bnVsbD94KFUpOnR5cGVvZiB6PT0iZnVuY3Rpb24iP3ouYXBwbHkodGhpcyxhcmd1bWVudHMpOno7cmV0dXJuIHIoWGgudHJhbnNsYXRlKFpbMF0sWlsxXSkuc2NhbGUoVy5rKS50cmFuc2xhdGUodHlwZW9mIFI9PSJmdW5jdGlvbiI/LVIuYXBwbHkodGhpcyxhcmd1bWVudHMpOi1SLHR5cGVvZiBGPT0iZnVuY3Rpb24iPy1GLmFwcGx5KHRoaXMsYXJndW1lbnRzKTotRiksVSxhKX0seil9O2Z1bmN0aW9uIF8oTCxSKXtyZXR1cm4gUj1NYXRoLm1heChvWzBdLE1hdGgubWluKG9bMV0sUikpLFI9PT1MLms/TDpuZXcgamgoUixMLngsTC55KX1mdW5jdGlvbiB5KEwsUixGKXt2YXIgej1SWzBdLUZbMF0qTC5rLFU9UlsxXS1GWzFdKkwuaztyZXR1cm4gej09PUwueCYmVT09PUwueT9MOm5ldyBqaChMLmsseixVKX1mdW5jdGlvbiB4KEwpe3JldHVyblsoK0xbMF1bMF0rICtMWzFdWzBdKS8yLCgrTFswXVsxXSsgK0xbMV1bMV0pLzJdfWZ1bmN0aW9uIGIoTCxSLEYpe0wub24oInN0YXJ0Lnpvb20iLGZ1bmN0aW9uKCl7Uyh0aGlzLGFyZ3VtZW50cykuc3RhcnQoKX0pLm9uKCJpbnRlcnJ1cHQuem9vbSBlbmQuem9vbSIsZnVuY3Rpb24oKXtTKHRoaXMsYXJndW1lbnRzKS5lbmQoKX0pLnR3ZWVuKCJ6b29tIixmdW5jdGlvbigpe3ZhciB6PXRoaXMsVT1hcmd1bWVudHMsVz1TKHosVSksWj10LmFwcGx5KHosVSkscnQ9Rj09bnVsbD94KFopOnR5cGVvZiBGPT0iZnVuY3Rpb24iP0YuYXBwbHkoeixVKTpGLG90PU1hdGgubWF4KFpbMV1bMF0tWlswXVswXSxaWzFdWzFdLVpbMF1bMV0pLHN0PXouX196b29tLFN0PXR5cGVvZiBSPT0iZnVuY3Rpb24iP1IuYXBwbHkoeixVKTpSLGJ0PWwoc3QuaW52ZXJ0KHJ0KS5jb25jYXQob3Qvc3QuayksU3QuaW52ZXJ0KHJ0KS5jb25jYXQob3QvU3QuaykpO3JldHVybiBmdW5jdGlvbihNdCl7aWYoTXQ9PT0xKU10PVN0O2Vsc2V7dmFyIGx0PWJ0KE10KSxLdD1vdC9sdFsyXTtNdD1uZXcgamgoS3QscnRbMF0tbHRbMF0qS3QscnRbMV0tbHRbMV0qS3QpfVcuem9vbShudWxsLE10KX19KX1mdW5jdGlvbiBTKEwsUixGKXtyZXR1cm4hRiYmTC5fX3pvb21pbmd8fG5ldyBDKEwsUil9ZnVuY3Rpb24gQyhMLFIpe3RoaXMudGhhdD1MLHRoaXMuYXJncz1SLHRoaXMuYWN0aXZlPTAsdGhpcy5leHRlbnQ9dC5hcHBseShMLFIpLHRoaXMudGFwcz0wfUMucHJvdG90eXBlPXtzdGFydDpmdW5jdGlvbigpe3JldHVybisrdGhpcy5hY3RpdmU9PT0xJiYodGhpcy50aGF0Ll9fem9vbWluZz10aGlzLHRoaXMuZW1pdCgic3RhcnQiKSksdGhpc30sem9vbTpmdW5jdGlvbihMLFIpe3JldHVybiB0aGlzLm1vdXNlJiZMIT09Im1vdXNlIiYmKHRoaXMubW91c2VbMV09Ui5pbnZlcnQodGhpcy5tb3VzZVswXSkpLHRoaXMudG91Y2gwJiZMIT09InRvdWNoIiYmKHRoaXMudG91Y2gwWzFdPVIuaW52ZXJ0KHRoaXMudG91Y2gwWzBdKSksdGhpcy50b3VjaDEmJkwhPT0idG91Y2giJiYodGhpcy50b3VjaDFbMV09Ui5pbnZlcnQodGhpcy50b3VjaDFbMF0pKSx0aGlzLnRoYXQuX196b29tPVIsdGhpcy5lbWl0KCJ6b29tIiksdGhpc30sZW5kOmZ1bmN0aW9uKCl7cmV0dXJuLS10aGlzLmFjdGl2ZT09PTAmJihkZWxldGUgdGhpcy50aGF0Ll9fem9vbWluZyx0aGlzLmVtaXQoImVuZCIpKSx0aGlzfSxlbWl0OmZ1bmN0aW9uKEwpe01wKG5ldyBfJChnLEwsdGhpcy50aGF0Ll9fem9vbSksYy5hcHBseSxjLFtMLHRoaXMudGhhdCx0aGlzLmFyZ3NdKX19O2Z1bmN0aW9uIFAoKXtpZighZS5hcHBseSh0aGlzLGFyZ3VtZW50cykpcmV0dXJuO3ZhciBMPVModGhpcyxhcmd1bWVudHMpLFI9dGhpcy5fX3pvb20sRj1NYXRoLm1heChvWzBdLE1hdGgubWluKG9bMV0sUi5rKk1hdGgucG93KDIsbi5hcHBseSh0aGlzLGFyZ3VtZW50cykpKSksej16byh0aGlzKTtpZihMLndoZWVsKShMLm1vdXNlWzBdWzBdIT09elswXXx8TC5tb3VzZVswXVsxXSE9PXpbMV0pJiYoTC5tb3VzZVsxXT1SLmludmVydChMLm1vdXNlWzBdPXopKSxjbGVhclRpbWVvdXQoTC53aGVlbCk7ZWxzZXtpZihSLms9PT1GKXJldHVybjtMLm1vdXNlPVt6LFIuaW52ZXJ0KHopXSxodSh0aGlzKSxMLnN0YXJ0KCl9bzIoKSxMLndoZWVsPXNldFRpbWVvdXQoVSxwKSxMLnpvb20oIm1vdXNlIixyKHkoXyhSLEYpLEwubW91c2VbMF0sTC5tb3VzZVsxXSksTC5leHRlbnQsYSkpO2Z1bmN0aW9uIFUoKXtMLndoZWVsPW51bGwsTC5lbmQoKX19ZnVuY3Rpb24gaygpe2lmKGh8fCFlLmFwcGx5KHRoaXMsYXJndW1lbnRzKSlyZXR1cm47dmFyIEw9Uyh0aGlzLGFyZ3VtZW50cywhMCksUj1IdChxdC52aWV3KS5vbigibW91c2Vtb3ZlLnpvb20iLFcsITApLm9uKCJtb3VzZXVwLnpvb20iLFosITApLEY9em8odGhpcyksej1xdC5jbGllbnRYLFU9cXQuY2xpZW50WTt6bShxdC52aWV3KSxROCgpLEwubW91c2U9W0YsdGhpcy5fX3pvb20uaW52ZXJ0KEYpXSxodSh0aGlzKSxMLnN0YXJ0KCk7ZnVuY3Rpb24gVygpe2lmKG8yKCksIUwubW92ZWQpe3ZhciBydD1xdC5jbGllbnRYLXosb3Q9cXQuY2xpZW50WS1VO0wubW92ZWQ9cnQqcnQrb3Qqb3Q+ZH1MLnpvb20oIm1vdXNlIixyKHkoTC50aGF0Ll9fem9vbSxMLm1vdXNlWzBdPXpvKEwudGhhdCksTC5tb3VzZVsxXSksTC5leHRlbnQsYSkpfWZ1bmN0aW9uIFooKXtSLm9uKCJtb3VzZW1vdmUuem9vbSBtb3VzZXVwLnpvb20iLG51bGwpLEZtKHF0LnZpZXcsTC5tb3ZlZCksbzIoKSxMLmVuZCgpfX1mdW5jdGlvbiBPKCl7aWYoISFlLmFwcGx5KHRoaXMsYXJndW1lbnRzKSl7dmFyIEw9dGhpcy5fX3pvb20sUj16byh0aGlzKSxGPUwuaW52ZXJ0KFIpLHo9TC5rKihxdC5zaGlmdEtleT8uNToyKSxVPXIoeShfKEwseiksUixGKSx0LmFwcGx5KHRoaXMsYXJndW1lbnRzKSxhKTtvMigpLHM+MD9IdCh0aGlzKS50cmFuc2l0aW9uKCkuZHVyYXRpb24ocykuY2FsbChiLFUsUik6SHQodGhpcykuY2FsbChnLnRyYW5zZm9ybSxVKX19ZnVuY3Rpb24gRCgpe2lmKCEhZS5hcHBseSh0aGlzLGFyZ3VtZW50cykpe3ZhciBMPXF0LnRvdWNoZXMsUj1MLmxlbmd0aCxGPVModGhpcyxhcmd1bWVudHMscXQuY2hhbmdlZFRvdWNoZXMubGVuZ3RoPT09UikseixVLFcsWjtmb3IoUTgoKSxVPTA7VTxSOysrVSlXPUxbVV0sWj1UcCh0aGlzLEwsVy5pZGVudGlmaWVyKSxaPVtaLHRoaXMuX196b29tLmludmVydChaKSxXLmlkZW50aWZpZXJdLEYudG91Y2gwPyFGLnRvdWNoMSYmRi50b3VjaDBbMl0hPT1aWzJdJiYoRi50b3VjaDE9WixGLnRhcHM9MCk6KEYudG91Y2gwPVosej0hMCxGLnRhcHM9MSshIXUpO3UmJih1PWNsZWFyVGltZW91dCh1KSkseiYmKEYudGFwczwyJiYodT1zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7dT1udWxsfSxmKSksaHUodGhpcyksRi5zdGFydCgpKX19ZnVuY3Rpb24gQigpe2lmKCEhdGhpcy5fX3pvb21pbmcpe3ZhciBMPVModGhpcyxhcmd1bWVudHMpLFI9cXQuY2hhbmdlZFRvdWNoZXMsRj1SLmxlbmd0aCx6LFUsVyxaO2ZvcihvMigpLHUmJih1PWNsZWFyVGltZW91dCh1KSksTC50YXBzPTAsej0wO3o8RjsrK3opVT1SW3pdLFc9VHAodGhpcyxSLFUuaWRlbnRpZmllciksTC50b3VjaDAmJkwudG91Y2gwWzJdPT09VS5pZGVudGlmaWVyP0wudG91Y2gwWzBdPVc6TC50b3VjaDEmJkwudG91Y2gxWzJdPT09VS5pZGVudGlmaWVyJiYoTC50b3VjaDFbMF09Vyk7aWYoVT1MLnRoYXQuX196b29tLEwudG91Y2gxKXt2YXIgcnQ9TC50b3VjaDBbMF0sb3Q9TC50b3VjaDBbMV0sc3Q9TC50b3VjaDFbMF0sU3Q9TC50b3VjaDFbMV0sYnQ9KGJ0PXN0WzBdLXJ0WzBdKSpidCsoYnQ9c3RbMV0tcnRbMV0pKmJ0LE10PShNdD1TdFswXS1vdFswXSkqTXQrKE10PVN0WzFdLW90WzFdKSpNdDtVPV8oVSxNYXRoLnNxcnQoYnQvTXQpKSxXPVsocnRbMF0rc3RbMF0pLzIsKHJ0WzFdK3N0WzFdKS8yXSxaPVsob3RbMF0rU3RbMF0pLzIsKG90WzFdK1N0WzFdKS8yXX1lbHNlIGlmKEwudG91Y2gwKVc9TC50b3VjaDBbMF0sWj1MLnRvdWNoMFsxXTtlbHNlIHJldHVybjtMLnpvb20oInRvdWNoIixyKHkoVSxXLFopLEwuZXh0ZW50LGEpKX19ZnVuY3Rpb24gSSgpe2lmKCEhdGhpcy5fX3pvb21pbmcpe3ZhciBMPVModGhpcyxhcmd1bWVudHMpLFI9cXQuY2hhbmdlZFRvdWNoZXMsRj1SLmxlbmd0aCx6LFU7Zm9yKFE4KCksaCYmY2xlYXJUaW1lb3V0KGgpLGg9c2V0VGltZW91dChmdW5jdGlvbigpe2g9bnVsbH0sZiksej0wO3o8RjsrK3opVT1SW3pdLEwudG91Y2gwJiZMLnRvdWNoMFsyXT09PVUuaWRlbnRpZmllcj9kZWxldGUgTC50b3VjaDA6TC50b3VjaDEmJkwudG91Y2gxWzJdPT09VS5pZGVudGlmaWVyJiZkZWxldGUgTC50b3VjaDE7aWYoTC50b3VjaDEmJiFMLnRvdWNoMCYmKEwudG91Y2gwPUwudG91Y2gxLGRlbGV0ZSBMLnRvdWNoMSksTC50b3VjaDApTC50b3VjaDBbMV09dGhpcy5fX3pvb20uaW52ZXJ0KEwudG91Y2gwWzBdKTtlbHNlIGlmKEwuZW5kKCksTC50YXBzPT09Mil7dmFyIFc9SHQodGhpcykub24oImRibGNsaWNrLnpvb20iKTtXJiZXLmFwcGx5KHRoaXMsYXJndW1lbnRzKX19fXJldHVybiBnLndoZWVsRGVsdGE9ZnVuY3Rpb24oTCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG49dHlwZW9mIEw9PSJmdW5jdGlvbiI/TDpGNSgrTCksZyk6bn0sZy5maWx0ZXI9ZnVuY3Rpb24oTCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9dHlwZW9mIEw9PSJmdW5jdGlvbiI/TDpGNSghIUwpLGcpOmV9LGcudG91Y2hhYmxlPWZ1bmN0aW9uKEwpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPXR5cGVvZiBMPT0iZnVuY3Rpb24iP0w6RjUoISFMKSxnKTppfSxnLmV4dGVudD1mdW5jdGlvbihMKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD10eXBlb2YgTD09ImZ1bmN0aW9uIj9MOkY1KFtbK0xbMF1bMF0sK0xbMF1bMV1dLFsrTFsxXVswXSwrTFsxXVsxXV1dKSxnKTp0fSxnLnNjYWxlRXh0ZW50PWZ1bmN0aW9uKEwpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvWzBdPStMWzBdLG9bMV09K0xbMV0sZyk6W29bMF0sb1sxXV19LGcudHJhbnNsYXRlRXh0ZW50PWZ1bmN0aW9uKEwpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhhWzBdWzBdPStMWzBdWzBdLGFbMV1bMF09K0xbMV1bMF0sYVswXVsxXT0rTFswXVsxXSxhWzFdWzFdPStMWzFdWzFdLGcpOltbYVswXVswXSxhWzBdWzFdXSxbYVsxXVswXSxhWzFdWzFdXV19LGcuY29uc3RyYWluPWZ1bmN0aW9uKEwpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPUwsZyk6cn0sZy5kdXJhdGlvbj1mdW5jdGlvbihMKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocz0rTCxnKTpzfSxnLmludGVycG9sYXRlPWZ1bmN0aW9uKEwpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhsPUwsZyk6bH0sZy5vbj1mdW5jdGlvbigpe3ZhciBMPWMub24uYXBwbHkoYyxhcmd1bWVudHMpO3JldHVybiBMPT09Yz9nOkx9LGcuY2xpY2tEaXN0YW5jZT1mdW5jdGlvbihMKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZD0oTD0rTCkqTCxnKTpNYXRoLnNxcnQoZCl9LGd9dmFyIGozdD1FZShPZSgpLDEpO3ZhciBlUj1jbGFzcyBleHRlbmRzIGJwe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl9leHBlcmltZW50cz1bXX1sb2FkKCl7bGV0IHQ9dmUoKS5leHBlcmltZW50cygpO3JldHVybiB0aGlzLnJlcXVlc3RNYW5hZ2VyLnJlcXVlc3QodCkudGhlbihyPT57ajN0LmlzRXF1YWwodGhpcy5fZXhwZXJpbWVudHMscil8fCh0aGlzLl9leHBlcmltZW50cz1yLHRoaXMuZW1pdENoYW5nZSgpKX0pfWdldEV4cGVyaW1lbnRzKCl7cmV0dXJuIHRoaXMuX2V4cGVyaW1lbnRzLnNsaWNlKCl9fSxyUj1uZXcgZVI7dmFyIGE1ZT17Z29vZ2xlU3RhbmRhcmQ6WyIjZGI0NDM3IiwiI2ZmNzA0MyIsIiNmNGI0MDAiLCIjMGY5ZDU4IiwiIzAwNzk2YiIsIiMwMGFjYzEiLCIjNDI4NWY0IiwiIzVjNmJjMCIsIiNhYjQ3YmMiXSxnb29nbGVDb29sOlsiIzllOWQyNCIsIiMwZjlkNTgiLCIjMDA3OTZiIiwiIzAwYWNjMSIsIiM0Mjg1ZjQiLCIjNWM2YmMwIiwiIzYwN2Q4YiJdLGdvb2dsZVdhcm06WyIjNzk1NTQ4IiwiI2FiNDdiYyIsIiNmMDYyOTIiLCIjYzIxODViIiwiI2RiNDQzNyIsIiNmZjcwNDMiLCIjZjRiNDAwIl0sZ29vZ2xlQ29sb3JCbGluZEFzc2lzdDpbIiNmZjcwNDMiLCIjMDBBQ0MxIiwiI0FCNDdCQyIsIiMyQTU2QzYiLCIjMGI4MDQzIiwiI0Y3Q0I0RCIsIiNjMGNhMzMiLCIjNWUzNWIxIiwiI0E1MjcxNCJdLHRlbnNvcmJvYXJkQ29sb3JCbGluZEFzc2lzdDpbIiNmZjcwNDMiLCIjMDA3N2JiIiwiI2NjMzMxMSIsIiMzM2JiZWUiLCIjZWUzMzc3IiwiIzAwOTk4OCIsIiNiYmJiYmIiXSxjb2xvckJsaW5kQXNzaXN0MTpbIiM0NDc3YWEiLCIjNDRhYWFhIiwiI2FhYWE0NCIsIiNhYTc3NDQiLCIjYWE0NDU1IiwiI2FhNDQ4OCJdLGNvbG9yQmxpbmRBc3Npc3QyOlsiIzg4Y2NlZSIsIiM0NGFhOTkiLCIjMTE3NzMzIiwiIzk5OTkzMyIsIiNkZGNjNzciLCIjY2M2Njc3IiwiIzg4MjI1NSIsIiNhYTQ0OTkiXSxjb2xvckJsaW5kQXNzaXN0MzpbIiMzMzIyODgiLCIjNjY5OWNjIiwiIzg4Y2NlZSIsIiM0NGFhOTkiLCIjMTE3NzMzIiwiIzk5OTkzMyIsIiNkZGNjNzciLCIjY2M2Njc3IiwiI2FhNDQ2NiIsIiM4ODIyNTUiLCIjNjYxMTAwIiwiI2FhNDQ5OSJdLGNvbG9yQmxpbmRBc3Npc3Q0OlsiIzQ0NzdhYSIsIiM2NmNjZWUiLCIjMjI4ODMzIiwiI2NjYmI0NCIsIiNlZTY2NzciLCIjYWEzMzc3IiwiI2JiYmJiYiJdLGNvbG9yQmxpbmRBc3Npc3Q1OlsiI0ZGNkRCNiIsIiM5MjAwMDAiLCIjOTI0OTAwIiwiI0RCRDEwMCIsIiMyNEZGMjQiLCIjMDA2RERCIiwiIzQ5MDA5MiJdLG1sZGFzaDpbIiNFNDdFQUQiLCIjRjQ2NDBEIiwiI0ZBQTMwMCIsIiNGNUU2MzYiLCIjMDBBMDc3IiwiIzAwNzdCOCIsIiMwMEI3RUQiXX0sblI9YTVlLnRlbnNvcmJvYXJkQ29sb3JCbGluZEFzc2lzdDt2YXIgeSQ9Y2xhc3N7Y29uc3RydWN0b3IodD1uUil7dGhpcy5wYWxldHRlPXQsdGhpcy5pZGVudGlmaWVycz1KaSgpfXNldERvbWFpbih0KXtyZXR1cm4gdGhpcy5pZGVudGlmaWVycz1KaSgpLHQuZm9yRWFjaCgocixuKT0+e3RoaXMuaWRlbnRpZmllcnMuc2V0KHIsdGhpcy5wYWxldHRlW24ldGhpcy5wYWxldHRlLmxlbmd0aF0pfSksdGhpc31nZXRDb2xvcih0KXtpZighdGhpcy5pZGVudGlmaWVycy5oYXModCkpdGhyb3cgbmV3IEVycm9yKGBTdHJpbmcgJHt0fSB3YXMgbm90IGluIHRoZSBkb21haW4uYCk7cmV0dXJuIHRoaXMuaWRlbnRpZmllcnMuZ2V0KHQpfX07ZnVuY3Rpb24gWDN0KGUsdCl7bGV0IHI9bmV3IHkkO2Z1bmN0aW9uIG4oKXtyLnNldERvbWFpbih0KCkpfXJldHVybiBlLmFkZExpc3RlbmVyKG4pLG4oKSxpPT5yLmdldENvbG9yKGkpfXZhciBmbj1YM3Qod3AsKCk9PndwLmdldFJ1bnMoKSksbFFyPVgzdChyUiwoKT0+clIuZ2V0RXhwZXJpbWVudHMoKS5tYXAoKHtuYW1lOmV9KT0+ZSkpO3ZhciBvZz1FZShPZSgpLDEpO19zKHttb2R1bGVOYW1lOiJydW4tY29sb3Itc3R5bGUiLHN0eWxlQ29udGVudDpgCiAgICBbY29sb3ItY2xhc3M9J2xpZ2h0LWJsdWUnXSBwYXBlci1jaGVja2JveCB7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1jb2xvcjogdmFyKC0tcGFwZXItbGlnaHQtYmx1ZS01MDApOwogICAgICAtLXBhcGVyLWNoZWNrYm94LWNoZWNrZWQtaW5rLWNvbG9yOiB2YXIoLS1wYXBlci1saWdodC1ibHVlLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWNvbG9yOiB2YXIoLS1wYXBlci1saWdodC1ibHVlLTkwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWluay1jb2xvcjogdmFyKC0tcGFwZXItbGlnaHQtYmx1ZS05MDApOwogICAgfQogICAgW2NvbG9yLWNsYXNzPSdyZWQnXSBwYXBlci1jaGVja2JveCB7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1jb2xvcjogdmFyKC0tcGFwZXItcmVkLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1pbmstY29sb3I6IHZhcigtLXBhcGVyLXJlZC01MDApOwogICAgICAtLXBhcGVyLWNoZWNrYm94LXVuY2hlY2tlZC1jb2xvcjogdmFyKC0tcGFwZXItcmVkLTkwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWluay1jb2xvcjogdmFyKC0tcGFwZXItcmVkLTkwMCk7CiAgICB9CiAgICBbY29sb3ItY2xhc3M9J2dyZWVuJ10gcGFwZXItY2hlY2tib3ggewogICAgICAtLXBhcGVyLWNoZWNrYm94LWNoZWNrZWQtY29sb3I6IHZhcigtLXBhcGVyLWdyZWVuLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1pbmstY29sb3I6IHZhcigtLXBhcGVyLWdyZWVuLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWNvbG9yOiB2YXIoLS1wYXBlci1ncmVlbi05MDApOwogICAgICAtLXBhcGVyLWNoZWNrYm94LXVuY2hlY2tlZC1pbmstY29sb3I6IHZhcigtLXBhcGVyLWdyZWVuLTkwMCk7CiAgICB9CiAgICBbY29sb3ItY2xhc3M9J3B1cnBsZSddIHBhcGVyLWNoZWNrYm94IHsKICAgICAgLS1wYXBlci1jaGVja2JveC1jaGVja2VkLWNvbG9yOiB2YXIoLS1wYXBlci1wdXJwbGUtNTAwKTsKICAgICAgLS1wYXBlci1jaGVja2JveC1jaGVja2VkLWluay1jb2xvcjogdmFyKC0tcGFwZXItcHVycGxlLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWNvbG9yOiB2YXIoLS1wYXBlci1wdXJwbGUtOTAwKTsKICAgICAgLS1wYXBlci1jaGVja2JveC11bmNoZWNrZWQtaW5rLWNvbG9yOiB2YXIoLS1wYXBlci1wdXJwbGUtOTAwKTsKICAgIH0KICAgIFtjb2xvci1jbGFzcz0ndGVhbCddIHBhcGVyLWNoZWNrYm94IHsKICAgICAgLS1wYXBlci1jaGVja2JveC1jaGVja2VkLWNvbG9yOiB2YXIoLS1wYXBlci10ZWFsLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1pbmstY29sb3I6IHZhcigtLXBhcGVyLXRlYWwtNTAwKTsKICAgICAgLS1wYXBlci1jaGVja2JveC11bmNoZWNrZWQtY29sb3I6IHZhcigtLXBhcGVyLXRlYWwtOTAwKTsKICAgICAgLS1wYXBlci1jaGVja2JveC11bmNoZWNrZWQtaW5rLWNvbG9yOiB2YXIoLS1wYXBlci10ZWFsLTkwMCk7CiAgICB9CiAgICBbY29sb3ItY2xhc3M9J3BpbmsnXSBwYXBlci1jaGVja2JveCB7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1jb2xvcjogdmFyKC0tcGFwZXItcGluay01MDApOwogICAgICAtLXBhcGVyLWNoZWNrYm94LWNoZWNrZWQtaW5rLWNvbG9yOiB2YXIoLS1wYXBlci1waW5rLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWNvbG9yOiB2YXIoLS1wYXBlci1waW5rLTkwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWluay1jb2xvcjogdmFyKC0tcGFwZXItcGluay05MDApOwogICAgfQogICAgW2NvbG9yLWNsYXNzPSdvcmFuZ2UnXSBwYXBlci1jaGVja2JveCB7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1jb2xvcjogdmFyKC0tcGFwZXItb3JhbmdlLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1pbmstY29sb3I6IHZhcigtLXBhcGVyLW9yYW5nZS01MDApOwogICAgICAtLXBhcGVyLWNoZWNrYm94LXVuY2hlY2tlZC1jb2xvcjogdmFyKC0tcGFwZXItb3JhbmdlLTkwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWluay1jb2xvcjogdmFyKC0tcGFwZXItb3JhbmdlLTkwMCk7CiAgICB9CiAgICBbY29sb3ItY2xhc3M9J2Jyb3duJ10gcGFwZXItY2hlY2tib3ggewogICAgICAtLXBhcGVyLWNoZWNrYm94LWNoZWNrZWQtY29sb3I6IHZhcigtLXBhcGVyLWJyb3duLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1pbmstY29sb3I6IHZhcigtLXBhcGVyLWJyb3duLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWNvbG9yOiB2YXIoLS1wYXBlci1icm93bi05MDApOwogICAgICAtLXBhcGVyLWNoZWNrYm94LXVuY2hlY2tlZC1pbmstY29sb3I6IHZhcigtLXBhcGVyLWJyb3duLTkwMCk7CiAgICB9CiAgICBbY29sb3ItY2xhc3M9J2luZGlnbyddIHBhcGVyLWNoZWNrYm94IHsKICAgICAgLS1wYXBlci1jaGVja2JveC1jaGVja2VkLWNvbG9yOiB2YXIoLS1wYXBlci1pbmRpZ28tNTAwKTsKICAgICAgLS1wYXBlci1jaGVja2JveC1jaGVja2VkLWluay1jb2xvcjogdmFyKC0tcGFwZXItaW5kaWdvLTUwMCk7CiAgICAgIC0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWNvbG9yOiB2YXIoLS1wYXBlci1pbmRpZ28tOTAwKTsKICAgICAgLS1wYXBlci1jaGVja2JveC11bmNoZWNrZWQtaW5rLWNvbG9yOiB2YXIoLS1wYXBlci1pbmRpZ28tOTAwKTsKICAgIH0KICBgfSk7dmFyIG9sPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLm5hbWVzPVtdLHRoaXMuY29sb3Jpbmc9e2dldENvbG9yOigpPT4iIn0sdGhpcy5yZWdleD0iIix0aGlzLnNlbGVjdGlvblN0YXRlPXt9LHRoaXMubWF4TmFtZXNUb0VuYWJsZUJ5RGVmYXVsdD00MCx0aGlzLl9kZWJvdW5jZWRSZWdleENoYW5nZT10aGlzLl9kZWJvdW5jZWRSZWdleENoYW5nZUltcGwoKX1fZGVib3VuY2VkUmVnZXhDaGFuZ2VJbXBsKCl7dmFyIHQ9b2cuZGVib3VuY2Uocj0+e3RoaXMucmVnZXg9cn0sMTUwLHtsZWFkaW5nOiExfSk7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHI9dGhpcy4kJCgiI25hbWVzLXJlZ2V4IikudmFsdWU7cj09IiI/dGhpcy5hc3luYygoKT0+e3RoaXMucmVnZXg9cn0sMzApOnQocil9fWdldCBfcmVnZXgoKXt2YXIgdD10aGlzLnJlZ2V4O3RyeXtyZXR1cm4gbmV3IFJlZ0V4cCh0KX1jYXRjaChyKXtyZXR1cm4gbnVsbH19X3NldElzb2xhdG9ySWNvbigpe3ZhciBpO3ZhciB0PXRoaXMuc2VsZWN0aW9uU3RhdGUscj1vZy5maWx0ZXIob2cudmFsdWVzKHQpKS5sZW5ndGgsbj1BcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbCgoaT10aGlzLnJvb3QpPT1udWxsP3ZvaWQgMDppLnF1ZXJ5U2VsZWN0b3JBbGwoIi5pc29sYXRvciIpKTtuLmZvckVhY2goZnVuY3Rpb24obyl7cj09PTEmJnRbby5uYW1lXT9vLmljb249InJhZGlvLWJ1dHRvbi1jaGVja2VkIjpvLmljb249InJhZGlvLWJ1dHRvbi11bmNoZWNrZWQifSl9Y29tcHV0ZU5hbWVzTWF0Y2hpbmdSZWdleCh0LHIpe2xldCBuPXRoaXMuX3JlZ2V4O3JldHVybiBuP3RoaXMubmFtZXMuZmlsdGVyKGk9Pm4udGVzdChpKSk6dGhpcy5uYW1lc31jb21wdXRlT3V0U2VsZWN0ZWQodCxyKXt2YXIgbj10aGlzLnNlbGVjdGlvblN0YXRlLGk9dGhpcy5tYXhOYW1lc1RvRW5hYmxlQnlEZWZhdWx0LG89dGhpcy5uYW1lc01hdGNoaW5nUmVnZXgubGVuZ3RoPD1pO3JldHVybiB0aGlzLm5hbWVzTWF0Y2hpbmdSZWdleC5maWx0ZXIoYT0+blthXT09bnVsbD9vOm5bYV0pfXN5bmNocm9uaXplQ29sb3JzKHQpe3ZhciBpLG8sYSxzO3RoaXMuX3NldElzb2xhdG9ySWNvbigpLCgobz0oaT10aGlzLnJvb3QpPT1udWxsP3ZvaWQgMDppLnF1ZXJ5U2VsZWN0b3JBbGwoInBhcGVyLWNoZWNrYm94IikpIT1udWxsP286W10pLmZvckVhY2gobD0+e2xldCBjPXRoaXMuY29sb3JpbmcuZ2V0Q29sb3IobC5uYW1lKTtsLnVwZGF0ZVN0eWxlcyh7Ii0tcGFwZXItY2hlY2tib3gtY2hlY2tlZC1jb2xvciI6YywiLS1wYXBlci1jaGVja2JveC1jaGVja2VkLWluay1jb2xvciI6YywiLS1wYXBlci1jaGVja2JveC11bmNoZWNrZWQtY29sb3IiOmMsIi0tcGFwZXItY2hlY2tib3gtdW5jaGVja2VkLWluay1jb2xvciI6Y30pfSksKChzPShhPXRoaXMucm9vdCk9PW51bGw/dm9pZCAwOmEucXVlcnlTZWxlY3RvckFsbCgiLmlzb2xhdG9yIikpIT1udWxsP3M6W10pLmZvckVhY2gobD0+e2xldCBjPXRoaXMuY29sb3JpbmcuZ2V0Q29sb3IobC5uYW1lKTtsLnN0eWxlLmNvbG9yPWN9KSx3aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT57dGhpcy51cGRhdGVTdHlsZXMoKX0pfV9pc29sYXRlTmFtZSh0KXt2YXIgcj10LnRhcmdldC5uYW1lLG49e307dGhpcy5uYW1lcy5mb3JFYWNoKGZ1bmN0aW9uKGkpe25baV09aT09cn0pLHRoaXMuc2VsZWN0aW9uU3RhdGU9bn1fY2hlY2tib3hDaGFuZ2UodCl7dmFyIHI9dC50YXJnZXQ7bGV0IG49b2cuY2xvbmUodGhpcy5zZWxlY3Rpb25TdGF0ZSk7bltyLm5hbWVdPXIuY2hlY2tlZCx0aGlzLnNlbGVjdGlvblN0YXRlPW59X2lzQ2hlY2tlZCh0LHIpe3JldHVybiB0aGlzLm91dFNlbGVjdGVkLmluZGV4T2YodCkhPS0xfXRvZ2dsZUFsbCgpe2xldCB0PXRoaXMubmFtZXNNYXRjaGluZ1JlZ2V4LnNvbWUobj0+dGhpcy5vdXRTZWxlY3RlZC5pbmNsdWRlcyhuKSkscj17fTt0aGlzLm5hbWVzLmZvckVhY2gobj0+e3Jbbl09IXR9KSx0aGlzLnNlbGVjdGlvblN0YXRlPXJ9fTtvbC50ZW1wbGF0ZT1RYAogICAgPHN0eWxlIGluY2x1ZGU9InNjcm9sbGJhci1zdHlsZSI+PC9zdHlsZT4KICAgIDxzdHlsZSBpbmNsdWRlPSJydW4tY29sb3Itc3R5bGUiPjwvc3R5bGU+CgogICAgPHBhcGVyLWlucHV0CiAgICAgIGlkPSJuYW1lcy1yZWdleCIKICAgICAgbm8tbGFiZWwtZmxvYXQ9IiIKICAgICAgbGFiZWw9IldyaXRlIGEgcmVnZXggdG8gZmlsdGVyIHJ1bnMiCiAgICAgIHZhbHVlPSJbW3JlZ2V4XV0iCiAgICAgIG9uLWJpbmQtdmFsdWUtY2hhbmdlZD0iX2RlYm91bmNlZFJlZ2V4Q2hhbmdlIgogICAgPjwvcGFwZXItaW5wdXQ+CiAgICA8ZGl2IGlkPSJvdXRlci1jb250YWluZXIiIGNsYXNzPSJzY3JvbGxiYXIiPgogICAgICA8dGVtcGxhdGUKICAgICAgICBpcz0iZG9tLXJlcGVhdCIKICAgICAgICBpdGVtcz0iW1tuYW1lc01hdGNoaW5nUmVnZXhdXSIKICAgICAgICBvbi1kb20tY2hhbmdlPSJzeW5jaHJvbml6ZUNvbG9ycyIKICAgICAgPgogICAgICAgIDxkaXYgY2xhc3M9Im5hbWUtcm93Ij4KICAgICAgICAgIDxkaXYKICAgICAgICAgICAgY2xhc3M9Imljb24tY29udGFpbmVyIGNoZWNrYm94LWNvbnRhaW5lciB2ZXJ0aWNhbC1hbGlnbi1jb250YWluZXIiCiAgICAgICAgICA+CiAgICAgICAgICAgIDxwYXBlci1jaGVja2JveAogICAgICAgICAgICAgIGNsYXNzPSJjaGVja2JveCB2ZXJ0aWNhbC1hbGlnbi1jZW50ZXIiCiAgICAgICAgICAgICAgaWQkPSJjaGVja2JveC1bW2l0ZW1dXSIKICAgICAgICAgICAgICBuYW1lPSJbW2l0ZW1dXSIKICAgICAgICAgICAgICBjaGVja2VkJD0iW1tfaXNDaGVja2VkKGl0ZW0sIHNlbGVjdGlvblN0YXRlLiopXV0iCiAgICAgICAgICAgICAgb24tY2hhbmdlPSJfY2hlY2tib3hDaGFuZ2UiCiAgICAgICAgICAgID48L3BhcGVyLWNoZWNrYm94PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8ZGl2CiAgICAgICAgICAgIGNsYXNzPSJpY29uLWNvbnRhaW5lciBpc29sYXRvci1jb250YWluZXIgdmVydGljYWwtYWxpZ24tY29udGFpbmVyIgogICAgICAgICAgPgogICAgICAgICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICAgICAgICBpY29uPSJyYWRpby1idXR0b24tdW5jaGVja2VkIgogICAgICAgICAgICAgIGNsYXNzPSJpc29sYXRvciB2ZXJ0aWNhbC1hbGlnbi1jZW50ZXIiCiAgICAgICAgICAgICAgb24tdGFwPSJfaXNvbGF0ZU5hbWUiCiAgICAgICAgICAgICAgbmFtZT0iW1tpdGVtXV0iCiAgICAgICAgICAgID48L3BhcGVyLWljb24tYnV0dG9uPgogICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJpdGVtLWxhYmVsLWNvbnRhaW5lciI+CiAgICAgICAgICAgIDxzcGFuPltbaXRlbV1dPC9zcGFuPgogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvdGVtcGxhdGU+CiAgICA8L2Rpdj4KICAgIDxzdHlsZT4KICAgICAgcGFwZXItaW5wdXQgewogICAgICAgIC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWZvY3VzLWNvbG9yOiB2YXIoLS10Yi1vcmFuZ2Utc3Ryb25nKTsKICAgICAgICAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1pbnB1dDogewogICAgICAgICAgZm9udC1zaXplOiAxNHB4OwogICAgICAgIH0KICAgICAgICAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1sYWJlbDogewogICAgICAgICAgZm9udC1zaXplOiAxNHB4OwogICAgICAgIH0KICAgICAgfQogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgICB9CiAgICAgICNvdXRlci1jb250YWluZXIgewogICAgICAgIGNvbnRhaW46IGNvbnRlbnQ7CiAgICAgICAgZmxleC1ncm93OiAxOwogICAgICAgIGZsZXgtc2hyaW5rOiAxOwogICAgICAgIG92ZXJmbG93LXg6IGhpZGRlbjsKICAgICAgICBvdmVyZmxvdy15OiBhdXRvOwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICAgIHdpbGwtY2hhbmdlOiB0cmFuc2Zvcm07CiAgICAgICAgd29yZC13cmFwOiBicmVhay13b3JkOwogICAgICB9CiAgICAgIC5uYW1lLXJvdyB7CiAgICAgICAgY29udGFpbjogY29udGVudDsKICAgICAgICBwYWRkaW5nLXRvcDogNXB4OwogICAgICAgIHBhZGRpbmctYm90dG9tOiA1cHg7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogcm93OwogICAgICAgIGZvbnQtc2l6ZTogMTNweDsKICAgICAgICB3b3JkLWJyZWFrOiBicmVhay1hbGw7IC8qIG1ha2VzIHdyYXBwaW5nIG9mIGh5cGVycGFyYW0gc3RyaW5ncyBiZXR0ZXIgKi8KICAgICAgfQogICAgICAuaWNvbi1jb250YWluZXIgewogICAgICAgIGZsZXgtZ3JvdzogMDsKICAgICAgICBmbGV4LXNocmluazogMDsKICAgICAgICBwYWRkaW5nLWxlZnQ6IDJweDsKICAgICAgfQogICAgICAuY2hlY2tib3ggewogICAgICAgIHBhZGRpbmctbGVmdDogMnB4OwogICAgICAgIHdpZHRoOiAxOHB4OwogICAgICAgIGhlaWdodDogMThweDsKICAgICAgfQogICAgICAuaXNvbGF0b3IgewogICAgICAgIHdpZHRoOiAxOHB4OwogICAgICAgIGhlaWdodDogMThweDsKICAgICAgICBwYWRkaW5nOiAwcHg7CiAgICAgIH0KICAgICAgLmlzb2xhdG9yLWNvbnRhaW5lciB7CiAgICAgICAgcGFkZGluZy1sZWZ0OiA2cHg7CiAgICAgICAgcGFkZGluZy1yaWdodDogM3B4OwogICAgICB9CiAgICAgIC5jaGVja2JveC1jb250YWluZXIgewogICAgICAgIHBhZGRpbmctbGVmdDogMnB4OwogICAgICB9CiAgICAgIC5pdGVtLWxhYmVsLWNvbnRhaW5lciB7CiAgICAgICAgcGFkZGluZy1sZWZ0OiA1cHg7CiAgICAgICAgZmxleC1ncm93OiAxOwogICAgICAgIGZsZXgtc2hyaW5rOiAxOwogICAgICAgIHdpZHRoOiAwcHg7IC8qIGhhY2sgdG8gZ2V0IHRoZSBmbGV4LWdyb3cgdG8gd29yayBwcm9wZXJseSAqLwogICAgICB9CiAgICAgIC50b29sdGlwLXZhbHVlLWNvbnRhaW5lciB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgICBmbGV4LWdyb3c6IDA7CiAgICAgICAgZmxleC1zaHJpbms6IDA7CiAgICAgICAgdGV4dC1hbGlnbjogcmlnaHQ7CiAgICAgICAgcGFkZGluZy1sZWZ0OiAycHg7CiAgICAgIH0KICAgICAgLnZlcnRpY2FsLWFsaWduLWNvbnRhaW5lciB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgfQogICAgICAudmVydGljYWwtYWxpZ24tY29udGFpbmVyIC52ZXJ0aWNhbC1hbGlnbi1jZW50ZXIgewogICAgICAgIGFsaWduLXNlbGY6IGNlbnRlcjsKICAgICAgfQogICAgICAudmVydGljYWwtYWxpZ24tY29udGFpbmVyIC52ZXJ0aWNhbC1hbGlnbi10b3AgewogICAgICAgIGFsaWduLXNlbGY6IHN0YXJ0OwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLG9sLnByb3RvdHlwZSwibmFtZXMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sb2wucHJvdG90eXBlLCJjb2xvcmluZyIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZyxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sb2wucHJvdG90eXBlLCJyZWdleCIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5LGNvbXB1dGVkOiJjb21wdXRlTmFtZXNNYXRjaGluZ1JlZ2V4KG5hbWVzLiosIF9yZWdleCkifSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sb2wucHJvdG90eXBlLCJuYW1lc01hdGNoaW5nUmVnZXgiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3Qsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLG9sLnByb3RvdHlwZSwic2VsZWN0aW9uU3RhdGUiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheSxub3RpZnk6ITAsY29tcHV0ZWQ6ImNvbXB1dGVPdXRTZWxlY3RlZChuYW1lc01hdGNoaW5nUmVnZXguKiwgc2VsZWN0aW9uU3RhdGUuKikifSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sb2wucHJvdG90eXBlLCJvdXRTZWxlY3RlZCIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxvbC5wcm90b3R5cGUsIm1heE5hbWVzVG9FbmFibGVCeURlZmF1bHQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sb2wucHJvdG90eXBlLCJfZGVib3VuY2VkUmVnZXhDaGFuZ2UiLHZvaWQgMCk7RShbUnQoInJlZ2V4IiksdygiZGVzaWduOnR5cGUiLE9iamVjdCksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sb2wucHJvdG90eXBlLCJfcmVnZXgiLG51bGwpO0UoW0J0KCJzZWxlY3Rpb25TdGF0ZSIsIm5hbWVzIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxvbC5wcm90b3R5cGUsIl9zZXRJc29sYXRvckljb24iLG51bGwpO29sPUUoW3l0KCJ0Zi1tdWx0aS1jaGVja2JveCIpXSxvbCk7dmFyIGEyPWNsYXNzIGV4dGVuZHMgbXR7Z2V0IF9wYXJ0cygpe3ZhciB0PXRoaXMudmFsdWUscj10aGlzLmRlbGltaXRlclBhdHRlcm47bGV0IG49W107Zm9yKDs7KXtsZXQgaT1uZXcgUmVnRXhwKHIsImciKTtpZihpLnRlc3QodCksaS5sYXN0SW5kZXg9PT0wKXtuLnB1c2godCk7YnJlYWt9ZWxzZSBuLnB1c2godC5zbGljZSgwLGkubGFzdEluZGV4KSksdD10LnNsaWNlKGkubGFzdEluZGV4KX1yZXR1cm4gbn19O2EyLnRlbXBsYXRlPVFgCiAgICA8IS0tCiAgICAgIFRoaXMgdWdseSBmb3JtYXR0aW5nIGlzIHJlcXVpcmVkIHRvIHByZXZlbnQgc3BhY2VzIGZyb20gc2xpcHBpbmcKICAgICAgaW50byB0aGUgSFRNTC4KICAgIC0tPgogICAgPHRlbXBsYXRlIGlzPSJkb20tcmVwZWF0IiBpdGVtcz0iW1tfcGFydHNdXSIgYXM9InBhcnQiCiAgICAgID5bW3BhcnRdXTx3YnIKICAgIC8+PC90ZW1wbGF0ZT4KICBgO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLGEyLnByb3RvdHlwZSwidmFsdWUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sYTIucHJvdG90eXBlLCJkZWxpbWl0ZXJQYXR0ZXJuIix2b2lkIDApO0UoW1J0KCJ2YWx1ZSIsImRlbGltaXRlclBhdHRlcm4iKSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLGEyLnByb3RvdHlwZSwiX3BhcnRzIixudWxsKTthMj1FKFt5dCgidGYtd2JyLXN0cmluZyIpXSxhMik7dmFyIG1jPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLnJ1blNlbGVjdGlvblN0YXRlPXpXKCJydW5TZWxlY3Rpb25TdGF0ZSIse2RlZmF1bHRWYWx1ZTp7fX0pLmNhbGwodGhpcyksdGhpcy5yZWdleElucHV0PXlfKCJyZWdleElucHV0Iix7ZGVmYXVsdFZhbHVlOiIifSkuY2FsbCh0aGlzKSx0aGlzLl9kYXRhTG9jYXRpb25DbGlwTGVuZ3RoPTI1MCx0aGlzLl9kYXRhTG9jYXRpb25EZWxpbWl0ZXJQYXR0ZXJuPSJbLz1fLC1dIix0aGlzLmNvbG9yaW5nPXtnZXRDb2xvcjpmbn0sdGhpcy5fc3RvcmVSdW5TZWxlY3Rpb25TdGF0ZT1GVygicnVuU2VsZWN0aW9uU3RhdGUiLHtkZWZhdWx0VmFsdWU6e319KSx0aGlzLl9yZWdleE9ic2VydmVyPXZfKCJyZWdleElucHV0Iix7ZGVmYXVsdFZhbHVlOiIifSl9YXR0YWNoZWQoKXt0aGlzLl9ydW5TdG9yZUxpc3RlbmVyPXdwLmFkZExpc3RlbmVyKCgpPT57dGhpcy5zZXQoInJ1bnMiLHdwLmdldFJ1bnMoKSl9KSx0aGlzLnNldCgicnVucyIsd3AuZ2V0UnVucygpKSx0aGlzLl9lbnZTdG9yZUxpc3RlbmVyPWliLmFkZExpc3RlbmVyKCgpPT57dGhpcy5zZXQoImRhdGFMb2NhdGlvbiIsaWIuZ2V0RGF0YUxvY2F0aW9uKCkpfSksdGhpcy5zZXQoImRhdGFMb2NhdGlvbiIsaWIuZ2V0RGF0YUxvY2F0aW9uKCkpfWRldGFjaGVkKCl7d3AucmVtb3ZlTGlzdGVuZXJCeUtleSh0aGlzLl9ydW5TdG9yZUxpc3RlbmVyKSxpYi5yZW1vdmVMaXN0ZW5lckJ5S2V5KHRoaXMuX2VudlN0b3JlTGlzdGVuZXIpfV90b2dnbGVBbGwoKXt0aGlzLiQubXVsdGlDaGVja2JveC50b2dnbGVBbGwoKX1nZXQgX2NsaXBwZWREYXRhTG9jYXRpb24oKXt2YXIgdD10aGlzLmRhdGFMb2NhdGlvbixyPXRoaXMuX2RhdGFMb2NhdGlvbkNsaXBMZW5ndGg7aWYodCE9PXZvaWQgMClyZXR1cm4gdC5sZW5ndGg+cj90LnN1YnN0cmluZygwLHIpOnR9X29wZW5EYXRhTG9jYXRpb25EaWFsb2codCl7dC5wcmV2ZW50RGVmYXVsdCgpLHRoaXMuJCQoIiNkYXRhLWxvY2F0aW9uLWRpYWxvZyIpLm9wZW4oKX1fc2hvdWxkU2hvd0V4cGFuZERhdGFMb2NhdGlvbkJ1dHRvbih0LHIpe3JldHVybiB0JiZ0Lmxlbmd0aD5yfX07bWMudGVtcGxhdGU9UWAKICAgIDxwYXBlci1kaWFsb2cgd2l0aC1iYWNrZHJvcD0iIiBpZD0iZGF0YS1sb2NhdGlvbi1kaWFsb2ciPgogICAgICA8aDI+RGF0YSBMb2NhdGlvbjwvaDI+CiAgICAgIDx0Zi13YnItc3RyaW5nCiAgICAgICAgdmFsdWU9IltbZGF0YUxvY2F0aW9uXV0iCiAgICAgICAgZGVsaW1pdGVyLXBhdHRlcm49IltbX2RhdGFMb2NhdGlvbkRlbGltaXRlclBhdHRlcm5dXSIKICAgICAgPgogICAgICA8L3RmLXdici1zdHJpbmcKICAgID48L3BhcGVyLWRpYWxvZz4KICAgIDxkaXYgaWQ9InRvcC10ZXh0Ij4KICAgICAgPGgzIGlkPSJ0b29sdGlwLWhlbHAiIGNsYXNzPSJ0b29sdGlwLWNvbnRhaW5lciI+UnVuczwvaDM+CiAgICA8L2Rpdj4KICAgIDx0Zi1tdWx0aS1jaGVja2JveAogICAgICBpZD0ibXVsdGlDaGVja2JveCIKICAgICAgbmFtZXM9IltbcnVuc11dIgogICAgICBzZWxlY3Rpb24tc3RhdGU9Int7cnVuU2VsZWN0aW9uU3RhdGV9fSIKICAgICAgb3V0LXNlbGVjdGVkPSJ7e3NlbGVjdGVkUnVuc319IgogICAgICByZWdleD0ie3tyZWdleElucHV0fX0iCiAgICAgIGNvbG9yaW5nPSJbW2NvbG9yaW5nXV0iCiAgICA+PC90Zi1tdWx0aS1jaGVja2JveD4KICAgIDxwYXBlci1idXR0b24gY2xhc3M9IngtYnV0dG9uIiBpZD0idG9nZ2xlLWFsbCIgb24tdGFwPSJfdG9nZ2xlQWxsIj4KICAgICAgVG9nZ2xlIEFsbCBSdW5zCiAgICA8L3BhcGVyLWJ1dHRvbj4KICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tkYXRhTG9jYXRpb25dXSI+CiAgICAgIDxkaXYgaWQ9ImRhdGEtbG9jYXRpb24iPgogICAgICAgIDx0Zi13YnItc3RyaW5nCiAgICAgICAgICB2YWx1ZT0iW1tfY2xpcHBlZERhdGFMb2NhdGlvbl1dIgogICAgICAgICAgZGVsaW1pdGVyLXBhdHRlcm49IltbX2RhdGFMb2NhdGlvbkRlbGltaXRlclBhdHRlcm5dXSIKICAgICAgICA+PC90Zi13YnItc3RyaW5nCiAgICAgICAgPjwhLS0KICAgICAgICAgIFdlIHVzZSBIVE1MIGNvbW1lbnRzIHRvIHJlbW92ZSBzcGFjZXMgYmVmb3JlIHRoZSBlbGxpcHNpcy4KICAgICAgICAtLT48dGVtcGxhdGUKICAgICAgICAgIGlzPSJkb20taWYiCiAgICAgICAgICBpZj0iW1tfc2hvdWxkU2hvd0V4cGFuZERhdGFMb2NhdGlvbkJ1dHRvbihkYXRhTG9jYXRpb24sIF9kYXRhTG9jYXRpb25DbGlwTGVuZ3RoKV1dIgogICAgICAgICAgPjwhLS0KICAgICAgICAgIC0tPjxhIGhyZWY9IiIgb24tY2xpY2s9Il9vcGVuRGF0YUxvY2F0aW9uRGlhbG9nIj7igKY8L2E+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPC9kaXY+CiAgICA8L3RlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgcGFkZGluZy1ib3R0b206IDEwcHg7CiAgICAgIH0KICAgICAgI3RvcC10ZXh0IHsKICAgICAgICBjb2xvcjogdmFyKC0tdGItc2Vjb25kYXJ5LXRleHQtY29sb3IpOwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICAgIGZsZXgtZ3JvdzogMDsKICAgICAgICBmbGV4LXNocmluazogMDsKICAgICAgICBwYWRkaW5nLXJpZ2h0OiAxNnB4OwogICAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgIH0KICAgICAgdGYtd2JyLXN0cmluZyB7CiAgICAgICAgb3ZlcmZsb3ctd3JhcDogYnJlYWstd29yZDsKICAgICAgfQogICAgICB0Zi1tdWx0aS1jaGVja2JveCB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgICAgZmxleC1zaHJpbms6IDE7CiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgfQogICAgICAueC1idXR0b24gewogICAgICAgIGZvbnQtc2l6ZTogMTNweDsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS10Yi11aS1saWdodC1hY2NlbnQpOwogICAgICAgIGNvbG9yOiB2YXIoLS10Yi11aS1kYXJrLWFjY2VudCk7CiAgICAgIH0KICAgICAgI3Rvb2x0aXAtaGVscCB7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLXNlY29uZGFyeS10ZXh0LWNvbG9yKTsKICAgICAgICBtYXJnaW46IDA7CiAgICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDsKICAgICAgICBmb250LXNpemU6IDE0cHg7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogNXB4OwogICAgICB9CiAgICAgIHBhcGVyLWJ1dHRvbiB7CiAgICAgICAgbWFyZ2luLWxlZnQ6IDA7CiAgICAgIH0KICAgICAgI2RhdGEtbG9jYXRpb24gewogICAgICAgIGNvbG9yOiB2YXIoLS10Yi11aS1kYXJrLWFjY2VudCk7CiAgICAgICAgZm9udC1zaXplOiAxM3B4OwogICAgICAgIG1hcmdpbjogNXB4IDAgMCAwOwogICAgICAgIG1heC13aWR0aDogMjg4cHg7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOk9iamVjdCxvYnNlcnZlcjoiX3N0b3JlUnVuU2VsZWN0aW9uU3RhdGUifSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLG1jLnByb3RvdHlwZSwicnVuU2VsZWN0aW9uU3RhdGUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmcsb2JzZXJ2ZXI6Il9yZWdleE9ic2VydmVyIn0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxtYy5wcm90b3R5cGUsInJlZ2V4SW5wdXQiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheSxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxtYy5wcm90b3R5cGUsInNlbGVjdGVkUnVucyIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sbWMucHJvdG90eXBlLCJydW5zIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxtYy5wcm90b3R5cGUsImRhdGFMb2NhdGlvbiIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxtYy5wcm90b3R5cGUsIl9kYXRhTG9jYXRpb25DbGlwTGVuZ3RoIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLG1jLnByb3RvdHlwZSwiX2RhdGFMb2NhdGlvbkRlbGltaXRlclBhdHRlcm4iLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sbWMucHJvdG90eXBlLCJjb2xvcmluZyIsdm9pZCAwKTtFKFtSdCgiZGF0YUxvY2F0aW9uIiwiX2RhdGFMb2NhdGlvbkNsaXBMZW5ndGgiKSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxtYy5wcm90b3R5cGUsIl9jbGlwcGVkRGF0YUxvY2F0aW9uIixudWxsKTttYz1FKFt5dCgidGYtcnVucy1zZWxlY3RvciIpXSxtYyk7dmFyIGFuPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5jYW5jZWxsYXRpb25Db3VudD0wfWNhbmNlbGxhYmxlKHQpe2xldCByPXRoaXMuY2FuY2VsbGF0aW9uQ291bnQ7cmV0dXJuIG49PntsZXQgaT10aGlzLmNhbmNlbGxhdGlvbkNvdW50IT09cjtyZXR1cm4gdCh7dmFsdWU6bixjYW5jZWxsZWQ6aX0pfX1jYW5jZWxBbGwoKXt0aGlzLmNhbmNlbGxhdGlvbkNvdW50Kyt9fTt2YXIgQjU9Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuaHRtbD0iIn1nZXQgc2FuaXRpemVkSHRtbCgpe3JldHVybiB0aGlzLmh0bWx9YXR0YWNoZWQoKXt3aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT57dGhpcy5zY29wZVN1YnRyZWUodGhpcy4kLm1hcmtkb3duLCEwKX0pfX07QjUudGVtcGxhdGU9UWAKICAgIDxkaXYgaWQ9Im1hcmtkb3duIiBpbm5lci1oLXQtbS1sPSJbW3Nhbml0aXplZEh0bWxdXSI+PC9kaXY+CiAgICA8c3R5bGU+CiAgICAgIC8qCiAgICAgICAqIFJlZHVjZSB0b3Btb3N0IGFuZCBib3R0b21tb3N0IG1hcmdpbnMgZnJvbSAxNnB4IHRvIDAuM2VtIChyZW5kZXJzCiAgICAgICAqIGF0IGFib3V0IDQuOHB4KSB0byBrZWVwIHRoZSBsYXlvdXQgY29tcGFjdC4gVGhpcyBpbXByb3ZlcyB0aGUKICAgICAgICogYXBwZWFyYW5jZSB3aGVuIHRoZXJlIGlzIG9ubHkgb25lIGxpbmUgb2YgdGV4dDsgc3RhbmRhcmQgTWFya2Rvd24KICAgICAgICogcmVuZGVyZXJzIHdpbGwgc3RpbGwgaW5jbHVkZSBhIFxgPHA+XGAgZWxlbWVudC4KICAgICAgICoKICAgICAgICogQnkgdGFyZ2V0aW5nIG9ubHkgdGhlIHRvcC1sZXZlbCwgZXh0cmVtYWwgZWxlbWVudHMsIHdlIHByZXNlcnZlIGFueQogICAgICAgKiBhY3R1YWwgcGFyYWdyYXBoIGJyZWFrcyBhbmQgb25seSBjaGFuZ2UgdGhlIHBhZGRpbmcgYWdhaW5zdCB0aGUKICAgICAgICogY29tcG9uZW50IGVkZ2VzLgogICAgICAgKi8KICAgICAgI21hcmtkb3duID4gcDpmaXJzdC1jaGlsZCB7CiAgICAgICAgbWFyZ2luLXRvcDogMC4zZW07CiAgICAgIH0KICAgICAgI21hcmtkb3duID4gcDpsYXN0LWNoaWxkIHsKICAgICAgICBtYXJnaW4tYm90dG9tOiAwLjNlbTsKICAgICAgfQoKICAgICAgLyogUGxlYXNhbnQgc3R5bGVzIGZvciBNYXJrZG93biB0YWJsZXMuICovCiAgICAgICNtYXJrZG93biB0YWJsZSB7CiAgICAgICAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsKICAgICAgfQogICAgICAjbWFya2Rvd24gdGFibGUgdGggewogICAgICAgIGZvbnQtd2VpZ2h0OiA2MDA7CiAgICAgIH0KICAgICAgI21hcmtkb3duIHRhYmxlIHRoLAogICAgICAjbWFya2Rvd24gdGFibGUgdGQgewogICAgICAgIHBhZGRpbmc6IDZweCAxM3B4OwogICAgICAgIGJvcmRlcjogMXB4IHNvbGlkIHZhcigtLXRiLXVpLWJvcmRlciwgI2RmZTJlNSk7CiAgICAgIH0KICAgICAgI21hcmtkb3duIHRhYmxlIHRyIHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiBpbmhlcml0OwogICAgICAgIGJvcmRlci10b3A6IDFweCBzb2xpZCB2YXIoLS10Yi11aS1ib3JkZXIsICNjNmNiZDEpOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sQjUucHJvdG90eXBlLCJodG1sIix2b2lkIDApO0UoW1J0KCJodG1sIiksdygiZGVzaWduOnR5cGUiLE9iamVjdCksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sQjUucHJvdG90eXBlLCJzYW5pdGl6ZWRIdG1sIixudWxsKTtCNT1FKFt5dCgidGYtbWFya2Rvd24tdmlldyIpXSxCNSk7X3Moe21vZHVsZU5hbWU6InRmLWNhcmQtaGVhZGluZy1zdHlsZSIsc3R5bGVDb250ZW50OmAKICAgIGZpZ2NhcHRpb24gewogICAgICB3aWR0aDogMTAwJTsKICAgIH0KCiAgICAvKiogSG9yaXpvbnRhbCBsaW5lIG9mIGxhYmVscy4gKi8KICAgIC5oZWFkaW5nLXJvdyB7CiAgICAgIG1hcmdpbi10b3A6IC00cHg7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGZsZXgtZGlyZWN0aW9uOiByb3c7CiAgICAgIGZsZXgtd3JhcDogd3JhcDsKICAgIH0KCiAgICAvKiogUGllY2Ugb2YgdGV4dCBpbiB0aGUgZmlndXJlIGNhcHRpb24uICovCiAgICAuaGVhZGluZy1sYWJlbCB7CiAgICAgIGZsZXgtZ3JvdzogMTsKICAgICAgbWFyZ2luLXRvcDogNHB4OwogICAgICBtYXgtd2lkdGg6IDEwMCU7CiAgICAgIHdvcmQtd3JhcDogYnJlYWstd29yZDsKICAgIH0KCiAgICAvKiogTWFrZXMgbGFiZWwgc2hvdyBvbiB0aGUgcmlnaHQuICovCiAgICAuaGVhZGluZy1yaWdodCB7CiAgICAgIGZsZXgtZ3JvdzogMDsKICAgIH0KICBgfSk7ZnVuY3Rpb24gczIoZSl7cmV0dXJuIGU/ZS50b1N0cmluZygpLnJlcGxhY2UoL0dNVC1cZCsgXCgoW14pXSspXCkvLCIkMSIpOiIifWZ1bmN0aW9uICQzdChlKXtsZXQgdD1sNWUoZSk7cmV0dXJuIHQ/TWF0aC5yb3VuZCgodFswXSoyOTkrdFsxXSo1ODcrdFsyXSoxMTQpLzFlMyk+MTI1PyJpbmhlcml0IjoiI2VlZSI6ImluaGVyaXQifWZ1bmN0aW9uIGw1ZShlKXtpZighZSlyZXR1cm4gbnVsbDtsZXQgdD1lLm1hdGNoKC9eIyhbMC05YS1mXXsxLDJ9KShbMC05YS1mXXsxLDJ9KShbMC05YS1mXXsxLDJ9KSQvKTtpZighdClyZXR1cm4gbnVsbDtpZihlLmxlbmd0aD09NClmb3IodmFyIHI9MTtyPD0zO3IrKyl0W3JdPXRbcl0rdFtyXTtyZXR1cm5bcGFyc2VJbnQodFsxXSwxNikscGFyc2VJbnQodFsyXSwxNikscGFyc2VJbnQodFszXSwxNildfXZhciBnYz1jbGFzcyBleHRlbmRzIG10e2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmRpc3BsYXlOYW1lPW51bGwsdGhpcy50YWc9bnVsbCx0aGlzLnJ1bj1udWxsLHRoaXMuZGVzY3JpcHRpb249bnVsbCx0aGlzLmNvbG9yPW51bGx9X3VwZGF0ZUhlYWRpbmdTdHlsZSgpe3RoaXMudXBkYXRlU3R5bGVzKHsiLS10Zi1jYXJkLWhlYWRpbmctYmFja2dyb3VuZC1jb2xvciI6dGhpcy5fcnVuQmFja2dyb3VuZCwiLS10Zi1jYXJkLWhlYWRpbmctY29sb3IiOnRoaXMuX3J1bkNvbG9yfSl9X2NvbXB1dGVSdW5CYWNrZ3JvdW5kKHQpe3JldHVybiB0fHwibm9uZSJ9X2NvbXB1dGVSdW5Db2xvcih0KXtyZXR1cm4gJDN0KHQpfWdldCBfbmFtZUxhYmVsKCl7dmFyIHQ9dGhpcy5kaXNwbGF5TmFtZSxyPXRoaXMudGFnO3JldHVybiB0fHxyfHwiIn1nZXQgX3RhZ0xhYmVsKCl7dmFyIHQ9dGhpcy5kaXNwbGF5TmFtZSxyPXRoaXMudGFnO3JldHVybiByJiZyIT09dD9yOiIifV90b2dnbGVEZXNjcmlwdGlvbkRpYWxvZyh0KXtsZXQgcj10aGlzLiQuZGVzY3JpcHRpb25EaWFsb2c7ci5wb3NpdGlvblRhcmdldD10LnRhcmdldCxyLnRvZ2dsZSgpfX07Z2MudGVtcGxhdGU9UWAKICAgIDxkaXYgY2xhc3M9ImNvbnRhaW5lciI+CiAgICAgIDxmaWdjYXB0aW9uIGNsYXNzPSJjb250ZW50Ij4KICAgICAgICA8ZGl2IGNsYXNzPSJoZWFkaW5nLXJvdyI+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX25hbWVMYWJlbF1dIj4KICAgICAgICAgICAgPGRpdiBpdGVtcHJvcD0ibmFtZSIgY2xhc3M9ImhlYWRpbmctbGFiZWwgbmFtZSI+W1tfbmFtZUxhYmVsXV08L2Rpdj4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbcnVuXV0iPgogICAgICAgICAgICA8IS0tIEV4dHJhIHdyYXBwaW5nIHNwYW4gbmVlZGVkIHRvIGF2b2lkIGZsZXhib3ggYmxvY2tpZmljYXRpb24uIC0tPgogICAgICAgICAgICA8IS0tIChzZWUgZmxleGJveCBzcGVjLCBzZWN0aW9uIDQgIkZsZXggSXRlbXMiKSAtLT4KICAgICAgICAgICAgPHNwYW4+CiAgICAgICAgICAgICAgPHNwYW4KICAgICAgICAgICAgICAgIGl0ZW1wcm9wPSJydW4iCiAgICAgICAgICAgICAgICBpZD0iaGVhZGluZy1ydW4iCiAgICAgICAgICAgICAgICBjbGFzcz0iaGVhZGluZy1sYWJlbCBoZWFkaW5nLXJpZ2h0IHJ1biIKICAgICAgICAgICAgICAgID5bW3J1bl1dPC9zcGFuCiAgICAgICAgICAgICAgPgogICAgICAgICAgICA8L3NwYW4+CiAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDwvZGl2PgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfdGFnTGFiZWxdXSI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJoZWFkaW5nLXJvdyI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImhlYWRpbmctbGFiZWwiPgogICAgICAgICAgICAgIHRhZzogPHNwYW4gaXRlbXByb3A9InRhZyI+W1tfdGFnTGFiZWxdXTwvc3Bhbj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDxzbG90Pjwvc2xvdD4KICAgICAgPC9maWdjYXB0aW9uPgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbZGVzY3JpcHRpb25dXSI+CiAgICAgICAgPHBhcGVyLWljb24tYnV0dG9uCiAgICAgICAgICBpY29uPSJpbmZvIgogICAgICAgICAgb24tdGFwPSJfdG9nZ2xlRGVzY3JpcHRpb25EaWFsb2ciCiAgICAgICAgICB0aXRsZT0iU2hvdyBzdW1tYXJ5IGRlc2NyaXB0aW9uIgogICAgICAgID48L3BhcGVyLWljb24tYnV0dG9uPgogICAgICA8L3RlbXBsYXRlPgogICAgICA8cGFwZXItZGlhbG9nCiAgICAgICAgaWQ9ImRlc2NyaXB0aW9uRGlhbG9nIgogICAgICAgIG5vLW92ZXJsYXA9IiIKICAgICAgICBob3Jpem9udGFsLWFsaWduPSJhdXRvIgogICAgICAgIHZlcnRpY2FsLWFsaWduPSJhdXRvIgogICAgICA+CiAgICAgICAgPHBhcGVyLWRpYWxvZy1zY3JvbGxhYmxlPgogICAgICAgICAgPHRmLW1hcmtkb3duLXZpZXcgaHRtbD0iW1tkZXNjcmlwdGlvbl1dIj48L3RmLW1hcmtkb3duLXZpZXc+CiAgICAgICAgPC9wYXBlci1kaWFsb2ctc2Nyb2xsYWJsZT4KICAgICAgPC9wYXBlci1kaWFsb2c+CiAgICA8L2Rpdj4KICAgIDxzdHlsZSBpbmNsdWRlPSJ0Zi1jYXJkLWhlYWRpbmctc3R5bGUiPgogICAgICAuY29udGFpbmVyIHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICB9CiAgICAgIC5jb250ZW50IHsKICAgICAgICBmb250LXNpemU6IDEycHg7CiAgICAgICAgZmxleC1ncm93OiAxOwogICAgICB9CiAgICAgIC5uYW1lIHsKICAgICAgICBmb250LXNpemU6IDE0cHg7CiAgICAgIH0KICAgICAgLnJ1biB7CiAgICAgICAgZm9udC1zaXplOiAxMXB4OwogICAgICAgIHdpZHRoOiBhdXRvOwogICAgICAgIGJvcmRlci1yYWRpdXM6IDNweDsKICAgICAgICBmb250LXdlaWdodDogYm9sZDsKICAgICAgICBwYWRkaW5nOiAxcHggNHB4IDJweDsKICAgICAgfQogICAgICBwYXBlci1pY29uLWJ1dHRvbiB7CiAgICAgICAgZmxleC1ncm93OiAwOwogICAgICB9CiAgICAgIHBhcGVyLWRpYWxvZy1zY3JvbGxhYmxlIHsKICAgICAgICBtYXgtd2lkdGg6IDY0MHB4OwogICAgICB9CiAgICAgICNoZWFkaW5nLXJ1biB7CiAgICAgICAgYmFja2dyb3VuZDogdmFyKC0tdGYtY2FyZC1oZWFkaW5nLWJhY2tncm91bmQtY29sb3IpOwogICAgICAgIGNvbG9yOiB2YXIoLS10Zi1jYXJkLWhlYWRpbmctY29sb3IpOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sZ2MucHJvdG90eXBlLCJkaXNwbGF5TmFtZSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxnYy5wcm90b3R5cGUsInRhZyIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxnYy5wcm90b3R5cGUsInJ1biIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxnYy5wcm90b3R5cGUsImRlc2NyaXB0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGdjLnByb3RvdHlwZSwiY29sb3IiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmcsY29tcHV0ZWQ6Il9jb21wdXRlUnVuQmFja2dyb3VuZChjb2xvcikiLHJlYWRPbmx5OiEwLG9ic2VydmVyOiJfdXBkYXRlSGVhZGluZ1N0eWxlIn0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxnYy5wcm90b3R5cGUsIl9ydW5CYWNrZ3JvdW5kIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLGNvbXB1dGVkOiJfY29tcHV0ZVJ1bkNvbG9yKGNvbG9yKSIscmVhZE9ubHk6ITAsb2JzZXJ2ZXI6Il91cGRhdGVIZWFkaW5nU3R5bGUifSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLGdjLnByb3RvdHlwZSwiX3J1bkNvbG9yIix2b2lkIDApO0UoW1J0KCJkaXNwbGF5TmFtZSIsInRhZyIpLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLGdjLnByb3RvdHlwZSwiX25hbWVMYWJlbCIsbnVsbCk7RShbUnQoImRpc3BsYXlOYW1lIiwidGFnIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sZ2MucHJvdG90eXBlLCJfdGFnTGFiZWwiLG51bGwpO2djPUUoW3l0KCJ0Zi1jYXJkLWhlYWRpbmciKV0sZ2MpO3ZhciBGaT1jbGFzcyBleHRlbmRzIEd0KG10KXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fbWV0YWRhdGFDYW5jZWxsZXI9bmV3IGFuLHRoaXMuX3N0ZXBzPVtdLHRoaXMuX2F0dGFjaGVkPSExfWdldCBfcnVuQ29sb3IoKXt2YXIgdD10aGlzLnJ1bjtyZXR1cm4gZm4odCl9Z2V0IF9oYXNBdExlYXN0T25lU3RlcCgpe3ZhciB0PXRoaXMuX3N0ZXBzO3JldHVybiEhdCYmdC5sZW5ndGg+MH1nZXQgX2hhc011bHRpcGxlU3RlcHMoKXt2YXIgdD10aGlzLl9zdGVwcztyZXR1cm4hIXQmJnQubGVuZ3RoPjF9Z2V0IF9tYXhTdGVwSW5kZXgoKXt2YXIgdD10aGlzLl9zdGVwcztyZXR1cm4gdC5sZW5ndGgtMX1nZXQgX2N1cnJlbnREYXR1bSgpe3ZhciB0PXRoaXMuX3N0ZXBzLHI9dGhpcy5fc3RlcEluZGV4O3JldHVybiB0W3JdfWdldCBfc2FtcGxlVGV4dCgpe3ZhciB0PXRoaXMuc2FtcGxlO3JldHVybmAke3QrMX1gfWdldCBfaGFzTXVsdGlwbGVTYW1wbGVzKCl7dmFyIHQ9dGhpcy50b3RhbFNhbXBsZXM7cmV0dXJuIHQ+MX1hdHRhY2hlZCgpe3RoaXMuX2F0dGFjaGVkPSEwLHRoaXMucmVsb2FkKCl9X3JlbG9hZE9uUnVuVGFnQ2hhbmdlKCl7dGhpcy5yZWxvYWQoKX1yZWxvYWQoKXtpZighdGhpcy5fYXR0YWNoZWQpcmV0dXJuO3RoaXMuX21ldGFkYXRhQ2FuY2VsbGVyLmNhbmNlbEFsbCgpO2xldCByPXZlKCkucGx1Z2luUm91dGUoImF1ZGlvIiwiL2F1ZGlvIixuZXcgVVJMU2VhcmNoUGFyYW1zKHt0YWc6dGhpcy50YWcscnVuOnRoaXMucnVuLHNhbXBsZTpTdHJpbmcodGhpcy5zYW1wbGUpfSkpLG49dGhpcy5fbWV0YWRhdGFDYW5jZWxsZXIuY2FuY2VsbGFibGUoaT0+e2lmKGkuY2FuY2VsbGVkKXJldHVybjtsZXQgYT1pLnZhbHVlLm1hcCh0aGlzLl9jcmVhdGVTdGVwRGF0dW0uYmluZCh0aGlzKSk7dGhpcy5zZXQoIl9zdGVwcyIsYSksdGhpcy5zZXQoIl9zdGVwSW5kZXgiLGEubGVuZ3RoLTEpfSk7dGhpcy5yZXF1ZXN0TWFuYWdlci5yZXF1ZXN0KHIpLnRoZW4obil9X2NyZWF0ZVN0ZXBEYXR1bSh0KXtsZXQgcj1uZXcgVVJMU2VhcmNoUGFyYW1zKHQucXVlcnkpO3IuYXBwZW5kKCJ0cyIsU3RyaW5nKHQud2FsbF90aW1lKSk7bGV0IG49dmUoKS5wbHVnaW5Sb3V0ZSgiYXVkaW8iLCIvaW5kaXZpZHVhbEF1ZGlvIixyKTtyZXR1cm57d2FsbF90aW1lOnMyKG5ldyBEYXRlKHQud2FsbF90aW1lKjFlMykpLHN0ZXA6dC5zdGVwLGxhYmVsOnQubGFiZWwsY29udGVudFR5cGU6dC5jb250ZW50VHlwZSx1cmw6bn19fTtGaS50ZW1wbGF0ZT1RYAogICAgPHRmLWNhcmQtaGVhZGluZwogICAgICB0YWc9IltbdGFnXV0iCiAgICAgIHJ1bj0iW1tydW5dXSIKICAgICAgZGlzcGxheS1uYW1lPSJbW3RhZ01ldGFkYXRhLmRpc3BsYXlOYW1lXV0iCiAgICAgIGRlc2NyaXB0aW9uPSJbW3RhZ01ldGFkYXRhLmRlc2NyaXB0aW9uXV0iCiAgICAgIGNvbG9yPSJbW19ydW5Db2xvcl1dIgogICAgPgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2hhc011bHRpcGxlU2FtcGxlc11dIj4KICAgICAgICA8ZGl2IGNsYXNzPSJoZWFkaW5nLXJvdyI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJoZWFkaW5nLWxhYmVsIj4KICAgICAgICAgICAgc2FtcGxlOiBbW19zYW1wbGVUZXh0XV0gb2YgW1t0b3RhbFNhbXBsZXNdXQogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfaGFzQXRMZWFzdE9uZVN0ZXBdXSI+CiAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1yb3ciPgogICAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1sYWJlbCI+CiAgICAgICAgICAgIHN0ZXAgPHN0cm9uZz5bW19jdXJyZW50RGF0dW0uc3RlcF1dPC9zdHJvbmc+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfY3VycmVudERhdHVtLndhbGxfdGltZV1dIj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1sYWJlbCBoZWFkaW5nLXJpZ2h0Ij4KICAgICAgICAgICAgICBbW19jdXJyZW50RGF0dW0ud2FsbF90aW1lXV0KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDwvZGl2PgogICAgICA8L3RlbXBsYXRlPgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2hhc011bHRpcGxlU3RlcHNdXSI+CiAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1yb3ciPgogICAgICAgICAgPHBhcGVyLXNsaWRlcgogICAgICAgICAgICBpZD0ic3RlcHMiCiAgICAgICAgICAgIGltbWVkaWF0ZS12YWx1ZT0ie3tfc3RlcEluZGV4fX0iCiAgICAgICAgICAgIG1heD0iW1tfbWF4U3RlcEluZGV4XV0iCiAgICAgICAgICAgIG1heC1tYXJrZXJzPSJbW19tYXhTdGVwSW5kZXhdXSIKICAgICAgICAgICAgc25hcHM9IiIKICAgICAgICAgICAgc3RlcD0iMSIKICAgICAgICAgICAgdmFsdWU9Int7X3N0ZXBJbmRleH19IgogICAgICAgICAgPjwvcGFwZXItc2xpZGVyPgogICAgICAgIDwvZGl2PgogICAgICA8L3RlbXBsYXRlPgogICAgPC90Zi1jYXJkLWhlYWRpbmc+CiAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2hhc0F0TGVhc3RPbmVTdGVwXV0iPgogICAgICA8YXVkaW8KICAgICAgICBjb250cm9scz0iIgogICAgICAgIHNyYyQ9IltbX2N1cnJlbnREYXR1bS51cmxdXSIKICAgICAgICB0eXBlJD0iW1tfY3VycmVudERhdHVtLmNvbnRlbnRUeXBlXV0iCiAgICAgID48L2F1ZGlvPgogICAgICA8dGYtbWFya2Rvd24tdmlldyBodG1sPSJbW19jdXJyZW50RGF0dW0ubGFiZWxdXSI+PC90Zi1tYXJrZG93bi12aWV3PgogICAgPC90ZW1wbGF0ZT4KICAgIDxkaXYgaWQ9Im1haW4tYXVkaW8tY29udGFpbmVyIj48L2Rpdj4KCiAgICA8c3R5bGUgaW5jbHVkZT0idGYtY2FyZC1oZWFkaW5nLXN0eWxlIj4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgIHdpZHRoOiAzNTBweDsKICAgICAgICBoZWlnaHQ6IGF1dG87CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIC0tc3RlcC1zbGlkZXIta25vYi1jb2xvcjogIzQyNDI0MjsKICAgICAgICBtYXJnaW4tcmlnaHQ6IDE1cHg7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTVweDsKICAgICAgfQoKICAgICAgI3N0ZXBzIHsKICAgICAgICBoZWlnaHQ6IDE1cHg7CiAgICAgICAgbWFyZ2luOiAwIDAgMCAtMTVweDsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICAgIHBhZGRpbmc6IDAgNXB4OyAvKiBzbyB0aGUgc2xpZGVyIGtub2IgZG9lc24ndCBidXR0IG91dCAqLwogICAgICAgIG1hcmdpbi10b3A6IDVweDsKICAgICAgICAtLXBhcGVyLXNsaWRlci1hY3RpdmUtY29sb3I6IHZhcigtLXN0ZXAtc2xpZGVyLWtub2ItY29sb3IpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLWtub2ItY29sb3I6IHZhcigtLXN0ZXAtc2xpZGVyLWtub2ItY29sb3IpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLXBpbi1jb2xvcjogdmFyKC0tc3RlcC1zbGlkZXIta25vYi1jb2xvcik7CiAgICAgICAgLS1wYXBlci1zbGlkZXIta25vYi1zdGFydC1jb2xvcjogdmFyKC0tc3RlcC1zbGlkZXIta25vYi1jb2xvcik7CiAgICAgICAgLS1wYXBlci1zbGlkZXIta25vYi1zdGFydC1ib3JkZXItY29sb3I6IHZhcigtLXN0ZXAtc2xpZGVyLWtub2ItY29sb3IpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLXBpbi1zdGFydC1jb2xvcjogdmFyKC0tc3RlcC1zbGlkZXIta25vYi1jb2xvcik7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxGaS5wcm90b3R5cGUsInJ1biIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxGaS5wcm90b3R5cGUsInRhZyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxGaS5wcm90b3R5cGUsInNhbXBsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxGaS5wcm90b3R5cGUsInRvdGFsU2FtcGxlcyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxGaS5wcm90b3R5cGUsInRhZ01ldGFkYXRhIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEFlKV0sRmkucHJvdG90eXBlLCJyZXF1ZXN0TWFuYWdlciIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixhbildLEZpLnByb3RvdHlwZSwiX21ldGFkYXRhQ2FuY2VsbGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxGaS5wcm90b3R5cGUsIl9zdGVwcyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxGaS5wcm90b3R5cGUsIl9zdGVwSW5kZXgiLHZvaWQgMCk7RShbUnQoInJ1biIpLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLEZpLnByb3RvdHlwZSwiX3J1bkNvbG9yIixudWxsKTtFKFtSdCgiX3N0ZXBzIiksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLEZpLnByb3RvdHlwZSwiX2hhc0F0TGVhc3RPbmVTdGVwIixudWxsKTtFKFtSdCgiX3N0ZXBzIiksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLEZpLnByb3RvdHlwZSwiX2hhc011bHRpcGxlU3RlcHMiLG51bGwpO0UoW1J0KCJfc3RlcHMiKSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxGaS5wcm90b3R5cGUsIl9tYXhTdGVwSW5kZXgiLG51bGwpO0UoW1J0KCJfc3RlcHMiLCJfc3RlcEluZGV4IiksdygiZGVzaWduOnR5cGUiLE9iamVjdCksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sRmkucHJvdG90eXBlLCJfY3VycmVudERhdHVtIixudWxsKTtFKFtSdCgic2FtcGxlIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sRmkucHJvdG90eXBlLCJfc2FtcGxlVGV4dCIsbnVsbCk7RShbUnQoInRvdGFsU2FtcGxlcyIpLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxGaS5wcm90b3R5cGUsIl9oYXNNdWx0aXBsZVNhbXBsZXMiLG51bGwpO0UoW0J0KCJydW4iLCJ0YWciKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLEZpLnByb3RvdHlwZSwiX3JlbG9hZE9uUnVuVGFnQ2hhbmdlIixudWxsKTtGaT1FKFt5dCgidGYtYXVkaW8tbG9hZGVyIildLEZpKTt2YXIgJGg9Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMucmVsb2FkT25SZWFkeT0hMCx0aGlzLl90YWdGaWx0ZXI9IiIsdGhpcy5fcmVxdWVzdE1hbmFnZXI9bmV3IEFlfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLnJlbG9hZE9uUmVhZHkmJnRoaXMucmVsb2FkKCl9cmVsb2FkKCl7dGhpcy5fZmV0Y2hUYWdzKCkudGhlbigoKT0+e3RoaXMuX3JlbG9hZEF1ZGlvKCl9KX1fZmV0Y2hUYWdzKCl7bGV0IHQ9dmUoKS5wbHVnaW5Sb3V0ZSgiYXVkaW8iLCIvdGFncyIpO3JldHVybiB0aGlzLl9yZXF1ZXN0TWFuYWdlci5yZXF1ZXN0KHQpLnRoZW4ocj0+e2lmKGZ5LmlzRXF1YWwocix0aGlzLl9ydW5Ub1RhZ0luZm8pKXJldHVybjtsZXQgbj1meS5tYXBWYWx1ZXMocixvPT5PYmplY3Qua2V5cyhvKSksaT0kaShuKTt0aGlzLnNldCgiX2RhdGFOb3RGb3VuZCIsaS5sZW5ndGg9PT0wKSx0aGlzLnNldCgiX3J1blRvVGFnSW5mbyIscil9KX1fcmVsb2FkQXVkaW8oKXt2YXIgdDsodD10aGlzLnJvb3QpPT1udWxsfHx0LnF1ZXJ5U2VsZWN0b3JBbGwoInRmLWF1ZGlvLWxvYWRlciIpLmZvckVhY2gocj0+e3IucmVsb2FkKCl9KX1fc2hvdWxkT3Blbih0KXtyZXR1cm4gdDw9Mn1nZXQgX2NhdGVnb3JpZXMoKXt2YXIgdD10aGlzLl9ydW5Ub1RhZ0luZm8scj10aGlzLl9zZWxlY3RlZFJ1bnMsbj10aGlzLl90YWdGaWx0ZXI7bGV0IGk9ZnkubWFwVmFsdWVzKHQsbD0+T2JqZWN0LmtleXMobCkpLG89UWwoaSxyLG4pO2Z1bmN0aW9uIGEobCl7bGV0IGM9dFtsLnJ1bl1bbC50YWddLnNhbXBsZXM7cmV0dXJuIGZ5LnJhbmdlKGMpLm1hcCh1PT5PYmplY3QuYXNzaWduKHt9LGwse3NhbXBsZTp1LHRvdGFsU2FtcGxlczpjfSkpfXJldHVybiBvLm1hcChsPT5PYmplY3QuYXNzaWduKHt9LGwse2l0ZW1zOltdLmNvbmNhdC5hcHBseShbXSxsLml0ZW1zLm1hcChhKSl9KSl9X3RhZ01ldGFkYXRhKHQscixuKXtyZXR1cm4gdFtyXVtuXX19OyRoLnRlbXBsYXRlPVFgCiAgICA8dGYtZGFzaGJvYXJkLWxheW91dD4KICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhciIgc2xvdD0ic2lkZWJhciI+CiAgICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhci1zZWN0aW9uIHJ1bnMtc2VsZWN0b3IiPgogICAgICAgICAgPHRmLXJ1bnMtc2VsZWN0b3IKICAgICAgICAgICAgaWQ9InJ1bnMtc2VsZWN0b3IiCiAgICAgICAgICAgIHNlbGVjdGVkLXJ1bnM9Int7X3NlbGVjdGVkUnVuc319IgogICAgICAgICAgPjwvdGYtcnVucy1zZWxlY3Rvcj4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImNlbnRlciIgc2xvdD0iY2VudGVyIj4KICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2RhdGFOb3RGb3VuZF1dIj4KICAgICAgICAgIDxkaXYgY2xhc3M9Im5vLWRhdGEtd2FybmluZyI+CiAgICAgICAgICAgIDxoMz5ObyBhdWRpbyBkYXRhIHdhcyBmb3VuZC48L2gzPgogICAgICAgICAgICA8cD5Qcm9iYWJsZSBjYXVzZXM6PC9wPgogICAgICAgICAgICA8dWw+CiAgICAgICAgICAgICAgPGxpPllvdSBoYXZlbuKAmXQgd3JpdHRlbiBhbnkgYXVkaW8gZGF0YSB0byB5b3VyIGV2ZW50IGZpbGVzLjwvbGk+CiAgICAgICAgICAgICAgPGxpPlRlbnNvckJvYXJkIGNhbuKAmXQgZmluZCB5b3VyIGV2ZW50IGZpbGVzLjwvbGk+CiAgICAgICAgICAgIDwvdWw+CgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICBJZiB5b3XigJlyZSBuZXcgdG8gdXNpbmcgVGVuc29yQm9hcmQsIGFuZCB3YW50IHRvIGZpbmQgb3V0IGhvdyB0bwogICAgICAgICAgICAgIGFkZCBkYXRhIGFuZCBzZXQgdXAgeW91ciBldmVudCBmaWxlcywgY2hlY2sgb3V0IHRoZQogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQiCiAgICAgICAgICAgICAgICA+UkVBRE1FPC9hCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIGFuZCBwZXJoYXBzIHRoZQogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmPSJodHRwczovL3d3dy50ZW5zb3JmbG93Lm9yZy9nZXRfc3RhcnRlZC9zdW1tYXJpZXNfYW5kX3RlbnNvcmJvYXJkIgogICAgICAgICAgICAgICAgPlRlbnNvckJvYXJkIHR1dG9yaWFsPC9hCiAgICAgICAgICAgICAgPi4KICAgICAgICAgICAgPC9wPgoKICAgICAgICAgICAgPHA+CiAgICAgICAgICAgICAgSWYgeW91IHRoaW5rIFRlbnNvckJvYXJkIGlzIGNvbmZpZ3VyZWQgcHJvcGVybHksIHBsZWFzZSBzZWUKICAgICAgICAgICAgICA8YQogICAgICAgICAgICAgICAgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL3RlbnNvcmZsb3cvdGVuc29yYm9hcmQvYmxvYi9tYXN0ZXIvUkVBRE1FLm1kI215LXRlbnNvcmJvYXJkLWlzbnQtc2hvd2luZy1hbnktZGF0YS13aGF0cy13cm9uZyIKICAgICAgICAgICAgICAgID50aGUgc2VjdGlvbiBvZiB0aGUgUkVBRE1FIGRldm90ZWQgdG8gbWlzc2luZyBkYXRhIHByb2JsZW1zPC9hCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIGFuZCBjb25zaWRlciBmaWxpbmcgYW4gaXNzdWUgb24gR2l0SHViLgogICAgICAgICAgICA8L3A+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1shX2RhdGFOb3RGb3VuZF1dIj4KICAgICAgICAgIDx0Zi10YWctZmlsdGVyZXIgdGFnLWZpbHRlcj0ie3tfdGFnRmlsdGVyfX0iPjwvdGYtdGFnLWZpbHRlcmVyPgogICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20tcmVwZWF0IiBpdGVtcz0iW1tfY2F0ZWdvcmllc11dIiBhcz0iY2F0ZWdvcnkiPgogICAgICAgICAgICA8dGYtY2F0ZWdvcnktcGFnaW5hdGVkLXZpZXcKICAgICAgICAgICAgICBjYXRlZ29yeT0iW1tjYXRlZ29yeV1dIgogICAgICAgICAgICAgIGluaXRpYWwtb3BlbmVkPSJbW19zaG91bGRPcGVuKGluZGV4KV1dIgogICAgICAgICAgICA+CiAgICAgICAgICAgICAgPHRlbXBsYXRlPgogICAgICAgICAgICAgICAgPHRmLWF1ZGlvLWxvYWRlcgogICAgICAgICAgICAgICAgICBhY3RpdmU9IltbYWN0aXZlXV0iCiAgICAgICAgICAgICAgICAgIHJ1bj0iW1tpdGVtLnJ1bl1dIgogICAgICAgICAgICAgICAgICB0YWc9IltbaXRlbS50YWddXSIKICAgICAgICAgICAgICAgICAgc2FtcGxlPSJbW2l0ZW0uc2FtcGxlXV0iCiAgICAgICAgICAgICAgICAgIHRvdGFsLXNhbXBsZXM9IltbaXRlbS50b3RhbFNhbXBsZXNdXSIKICAgICAgICAgICAgICAgICAgdGFnLW1ldGFkYXRhPSJbW190YWdNZXRhZGF0YShfcnVuVG9UYWdJbmZvLCBpdGVtLnJ1biwgaXRlbS50YWcpXV0iCiAgICAgICAgICAgICAgICAgIHJlcXVlc3QtbWFuYWdlcj0iW1tfcmVxdWVzdE1hbmFnZXJdXSIKICAgICAgICAgICAgICAgID48L3RmLWF1ZGlvLWxvYWRlcj4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8L3RmLWNhdGVnb3J5LXBhZ2luYXRlZC12aWV3PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICA8L2Rpdj4KICAgIDwvdGYtZGFzaGJvYXJkLWxheW91dD4KICAgIDxzdHlsZSBpbmNsdWRlPSJkYXNoYm9hcmQtc3R5bGUiPjwvc3R5bGU+CiAgICA8c3R5bGU+CiAgICAgIC5uby1kYXRhLXdhcm5pbmcgewogICAgICAgIG1heC13aWR0aDogNTQwcHg7CiAgICAgICAgbWFyZ2luOiA4MHB4IGF1dG8gMCBhdXRvOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSwkaC5wcm90b3R5cGUsInJlbG9hZE9uUmVhZHkiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLCRoLnByb3RvdHlwZSwiX3NlbGVjdGVkUnVucyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSwkaC5wcm90b3R5cGUsIl9ydW5Ub1RhZ0luZm8iLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSwkaC5wcm90b3R5cGUsIl9kYXRhTm90Rm91bmQiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sJGgucHJvdG90eXBlLCJfdGFnRmlsdGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEFlKV0sJGgucHJvdG90eXBlLCJfcmVxdWVzdE1hbmFnZXIiLHZvaWQgMCk7RShbUnQoIl9ydW5Ub1RhZ0luZm8iLCJfc2VsZWN0ZWRSdW5zIiwiX3RhZ0ZpbHRlciIpLHcoImRlc2lnbjp0eXBlIixBcnJheSksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sJGgucHJvdG90eXBlLCJfY2F0ZWdvcmllcyIsbnVsbCk7JGg9RShbeXQoInRmLWF1ZGlvLWRhc2hib2FyZCIpXSwkaCk7dmFyIEg1PWNsYXNzIGV4dGVuZHMgR3QobXQpe2F0dGFjaGVkKCl7dGhpcy5hc3luYyhmdW5jdGlvbigpe3RoaXMuZ2V0RWZmZWN0aXZlQ2hpbGRyZW4oKS5mb3JFYWNoKGZ1bmN0aW9uKHQpe3RoaXMubGlzdGVuKHQsInRhcCIsIl9zZWxlY3RUYXJnZXQiKX0uYmluZCh0aGlzKSl9KX1fc2VsZWN0VGFyZ2V0KHQpe3RoaXMuc2VsZWN0ZWRJZD10LmN1cnJlbnRUYXJnZXQuaWR9X3NlbGVjdGVkSWRDaGFuZ2VkKCl7dmFyIHQ9dGhpcy5xdWVyeUVmZmVjdGl2ZUNoaWxkcmVuKCIjIit0aGlzLnNlbGVjdGVkSWQpOyF0fHwodGhpcy5nZXRFZmZlY3RpdmVDaGlsZHJlbigpLmZvckVhY2goZnVuY3Rpb24ocil7ci5jbGFzc0xpc3QucmVtb3ZlKCJzZWxlY3RlZCIpfSksdC5jbGFzc0xpc3QuYWRkKCJzZWxlY3RlZCIpKX19O0g1LnRlbXBsYXRlPVFgCiAgICA8ZGl2IGlkPSJ3cmFwIj4KICAgICAgPGgzPltbbmFtZV1dPC9oMz4KICAgICAgPGRpdiBjbGFzcz0iY29udGVudC13cmFwcGVyIj48c2xvdD48L3Nsb3Q+PC9kaXY+CiAgICA8L2Rpdj4KICAgIDxzdHlsZT4KICAgICAgLmNvbnRlbnQtd3JhcHBlciA6OnNsb3R0ZWQoKikgewogICAgICAgIGJhY2tncm91bmQ6IG5vbmU7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLXVpLWRhcmstYWNjZW50KTsKICAgICAgICBmb250LXNpemU6IDEzcHg7CiAgICAgICAgbWFyZ2luLXRvcDogMTBweDsKICAgICAgfQoKICAgICAgLmNvbnRlbnQtd3JhcHBlciA6OnNsb3R0ZWQoKikgewogICAgICAgIGJhY2tncm91bmQ6IG5vbmU7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLXVpLWRhcmstYWNjZW50KTsKICAgICAgICBmb250LXNpemU6IDEzcHg7CiAgICAgICAgbWFyZ2luLXRvcDogMTBweDsKICAgICAgfQoKICAgICAgLmNvbnRlbnQtd3JhcHBlciA6OnNsb3R0ZWQoLnNlbGVjdGVkKSB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tdGItdWktZGFyay1hY2NlbnQpOwogICAgICAgIGNvbG9yOiB3aGl0ZSAhaW1wb3J0YW50OwogICAgICB9CgogICAgICBoMyB7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLXNlY29uZGFyeS10ZXh0LWNvbG9yKTsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBmb250LXNpemU6IDE0cHg7CiAgICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDsKICAgICAgICBtYXJnaW46IDAgMCA1cHg7CiAgICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxINS5wcm90b3R5cGUsIm5hbWUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmcsbm90aWZ5OiEwLG9ic2VydmVyOiJfc2VsZWN0ZWRJZENoYW5nZWQifSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLEg1LnByb3RvdHlwZSwic2VsZWN0ZWRJZCIsdm9pZCAwKTtINT1FKFt5dCgidGYtb3B0aW9uLXNlbGVjdG9yIildLEg1KTtmdW5jdGlvbiBpUihlLHQpe2xldCByLG49e307T2JqZWN0LmtleXMoZSkuZm9yRWFjaChhPT57bGV0IHM9ZVthXTtyPT09dm9pZCAwJiYocj1zLmRpc3BsYXlOYW1lKSxyIT09cy5kaXNwbGF5TmFtZSYmKHI9bnVsbCksbltzLmRlc2NyaXB0aW9uXT09PXZvaWQgMCYmKG5bcy5kZXNjcmlwdGlvbl09W10pLG5bcy5kZXNjcmlwdGlvbl0ucHVzaChhKX0pO2xldCBpPXIhPW51bGw/cjp0LG89KCgpPT57bGV0IGE9T2JqZWN0LmtleXMobik7cmV0dXJuIGEubGVuZ3RoPT09MD8iIjphLmxlbmd0aD09PTE/YVswXTpgPHA+PHN0cm9uZz5NdWx0aXBsZSBkZXNjcmlwdGlvbnM6PC9zdHJvbmc+PC9wPjx1bD4ke2EubWFwKGM9PntsZXQgdT1uW2NdLm1hcChwPT5gPGNvZGU+JHtwLnJlcGxhY2UoLzwvZywiJmx0OyIpLnJlcGxhY2UoLz4vZywiJmd0OyIpLnJlcGxhY2UoLyYvZywiJmFtcDsiKX08L2NvZGU+YCksaD11Lmxlbmd0aD4yP3Uuc2xpY2UoMCx1Lmxlbmd0aC0xKS5qb2luKCIsICIpKyIsIGFuZCAiK3VbdS5sZW5ndGgtMV06dS5qb2luKCIgYW5kICIpO3JldHVybmA8bGk+PHA+Rm9yICR7YzVlKHUubGVuZ3RoLCJydW4iLCJydW5zIil9ICR7aH06PC9wPiR7Y308L2xpPmB9KS5qb2luKCIiKX08L3VsPmB9KSgpO3JldHVybntkaXNwbGF5TmFtZTppLGRlc2NyaXB0aW9uOm99fWZ1bmN0aW9uIGM1ZShlLHQscil7cmV0dXJuIGU9PT0xP3Q6cn12YXIgWjN0PUVlKE9lKCksMSk7dmFyIEhwPWNsYXNzIGV4dGVuZHMgbXR7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMud2VpZ2h0PS42LHRoaXMuX3VwZGF0ZVdlaWdodD1aM3QuZGVib3VuY2UoZnVuY3Rpb24odCl7dGhpcy53ZWlnaHQ9dH0sMjUwKX1faW1tZWRpYXRlV2VpZ2h0TnVtYmVyRm9yUGFwZXJTbGlkZXJDaGFuZ2VkKCl7dGhpcy5faW5wdXRXZWlnaHRTdHJpbmdGb3JQYXBlcklucHV0PXRoaXMuX2ltbWVkaWF0ZVdlaWdodE51bWJlckZvclBhcGVyU2xpZGVyLnRvU3RyaW5nKCksdGhpcy5fdXBkYXRlV2VpZ2h0LmNhbGwodGhpcyx0aGlzLl9pbW1lZGlhdGVXZWlnaHROdW1iZXJGb3JQYXBlclNsaWRlcil9X2lucHV0V2VpZ2h0U3RyaW5nRm9yUGFwZXJJbnB1dENoYW5nZWQoKXsrdGhpcy5faW5wdXRXZWlnaHRTdHJpbmdGb3JQYXBlcklucHV0PDA/dGhpcy5faW5wdXRXZWlnaHRTdHJpbmdGb3JQYXBlcklucHV0PSIwIjordGhpcy5faW5wdXRXZWlnaHRTdHJpbmdGb3JQYXBlcklucHV0PjEmJih0aGlzLl9pbnB1dFdlaWdodFN0cmluZ0ZvclBhcGVySW5wdXQ9IjEiKTt2YXIgdD0rdGhpcy5faW5wdXRXZWlnaHRTdHJpbmdGb3JQYXBlcklucHV0O2lzTmFOKHQpfHx0aGlzLl91cGRhdGVXZWlnaHQuY2FsbCh0aGlzLHQpfX07SHAudGVtcGxhdGU9UWAKICAgIDxoMyBjbGFzcz0idGl0bGUiPlNtb290aGluZzwvaDM+CiAgICA8ZGl2IGNsYXNzPSJzbW9vdGhpbmctYmxvY2siPgogICAgICA8cGFwZXItc2xpZGVyCiAgICAgICAgaWQ9InNsaWRlciIKICAgICAgICBpbW1lZGlhdGUtdmFsdWU9Int7X2ltbWVkaWF0ZVdlaWdodE51bWJlckZvclBhcGVyU2xpZGVyfX0iCiAgICAgICAgbWF4PSJbW21heF1dIgogICAgICAgIG1pbj0iW1ttaW5dXSIKICAgICAgICBwaW4KICAgICAgICBzdGVwPSJbW3N0ZXBdXSIKICAgICAgICB0eXBlPSJudW1iZXIiCiAgICAgICAgdmFsdWU9Int7d2VpZ2h0fX0iCiAgICAgID48L3BhcGVyLXNsaWRlcj4KICAgICAgPHBhcGVyLWlucHV0CiAgICAgICAgaWQ9ImlucHV0IgogICAgICAgIGxhYmVsPSJ3ZWlnaHQiCiAgICAgICAgbm8tbGFiZWwtZmxvYXQKICAgICAgICB2YWx1ZT0ie3tfaW5wdXRXZWlnaHRTdHJpbmdGb3JQYXBlcklucHV0fX0iCiAgICAgICAgdHlwZT0ibnVtYmVyIgogICAgICAgIHN0ZXA9Iltbc3RlcF1dIgogICAgICAgIG1pbj0iW1ttaW5dXSIKICAgICAgICBtYXg9IltbbWF4XV0iCiAgICAgID48L3BhcGVyLWlucHV0PgogICAgPC9kaXY+CiAgICA8c3R5bGU+CiAgICAgIC50aXRsZSB7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLXNlY29uZGFyeS10ZXh0LWNvbG9yKTsKICAgICAgICBtYXJnaW46IDA7CiAgICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDsKICAgICAgICBmb250LXNpemU6IDE0cHg7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogNXB4OwogICAgICB9CgogICAgICAuc21vb3RoaW5nLWJsb2NrIHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICB9CgogICAgICBwYXBlci1zbGlkZXIgewogICAgICAgIC0tcGFwZXItc2xpZGVyLWFjdGl2ZS1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1zbGlkZXIta25vYi1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1zbGlkZXIta25vYi1zdGFydC1ib3JkZXItY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLWtub2Itc3RhcnQtY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLW1hcmtlcnMtY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLXBpbi1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1zbGlkZXItcGluLXN0YXJ0LWNvbG9yOiB2YXIoLS10Yi1vcmFuZ2Utc3Ryb25nKTsKICAgICAgICBmbGV4LWdyb3c6IDI7CiAgICAgIH0KCiAgICAgIHBhcGVyLWlucHV0IHsKICAgICAgICAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1mb2N1cy1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1pbnB1dC1jb250YWluZXItaW5wdXQ6IHsKICAgICAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgICB9CiAgICAgICAgLS1wYXBlci1pbnB1dC1jb250YWluZXItbGFiZWw6IHsKICAgICAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgICB9CiAgICAgICAgd2lkdGg6IDYwcHg7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxIcC5wcm90b3R5cGUsInN0ZXAiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sSHAucHJvdG90eXBlLCJtYXgiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sSHAucHJvdG90eXBlLCJtaW4iLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXIsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLEhwLnByb3RvdHlwZSwid2VpZ2h0Iix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyLG5vdGlmeTohMCxvYnNlcnZlcjoiX2ltbWVkaWF0ZVdlaWdodE51bWJlckZvclBhcGVyU2xpZGVyQ2hhbmdlZCJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sSHAucHJvdG90eXBlLCJfaW1tZWRpYXRlV2VpZ2h0TnVtYmVyRm9yUGFwZXJTbGlkZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmcsbm90aWZ5OiEwLG9ic2VydmVyOiJfaW5wdXRXZWlnaHRTdHJpbmdGb3JQYXBlcklucHV0Q2hhbmdlZCJ9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sSHAucHJvdG90eXBlLCJfaW5wdXRXZWlnaHRTdHJpbmdGb3JQYXBlcklucHV0Iix2b2lkIDApO0hwPUUoW3l0KCJ0Zi1zbW9vdGhpbmctaW5wdXQiKV0sSHApO3ZhciBZbz1FZShPZSgpLDEpO2Z1bmN0aW9uIENuKGUsdCl7bGV0IHI9T2JqZWN0LmtleXModCkuc29ydCgpLmZpbHRlcihhPT50W2FdIT09dm9pZCAwKTtpZighci5sZW5ndGgpcmV0dXJuIGU7bGV0IG49ZS5pbmRleE9mKCI/IikhPT0tMT8iJiI6Ij8iLG89QXJyYXkoKS5jb25jYXQoLi4uci5tYXAoYT0+e2xldCBzPXRbYV07cmV0dXJuKEFycmF5LmlzQXJyYXkocyk/czpbc10pLm1hcChjPT5gJHthfT0ke3U1ZShjKX1gKX0pKS5qb2luKCImIik7cmV0dXJuIGUrbitvfWZ1bmN0aW9uIHU1ZShlKXtyZXR1cm4gZW5jb2RlVVJJQ29tcG9uZW50KGUpLnJlcGxhY2UoL1woL2csIiUyOCIpLnJlcGxhY2UoL1wpL2csIiUyOSIpfXZhciBHS3Q9RWUoT2UoKSwxKSxIV249RWUod2woKSwxKTt2YXIgSUt0PUVlKE9lKCksMSk7dmFyIHY0OyhmdW5jdGlvbihlKXtlW2UuTE9BRElORz0wXT0iTE9BRElORyIsZVtlLkxPQURFRD0xXT0iTE9BREVEIn0pKHY0fHwodjQ9e30pKTtmdW5jdGlvbiBrUyhlKXtyZXR1cm4gY2xhc3MgZXh0ZW5kcyBle2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmxvYWRLZXk9IiIsdGhpcy5kYXRhVG9Mb2FkPVtdLHRoaXMuZ2V0RGF0YUxvYWROYW1lPXI9PlN0cmluZyhyKSx0aGlzLmRhdGFMb2FkaW5nPSExLHRoaXMuZGF0YUxvYWRlZEF0TGVhc3RPbmNlPSExLHRoaXMuX2lzQ29ubmVjdGVkPSExLHRoaXMuX2RhdGFMb2FkU3RhdGU9bmV3IE1hcCx0aGlzLl9jYW5jZWxsZXI9bmV3IGFuLHRoaXMuX2xvYWREYXRhQXN5bmM9bnVsbCx0aGlzLl9sb2FkRGF0YT1JS3QudGhyb3R0bGUodGhpcy5fbG9hZERhdGFJbXBsLDEwMCx7bGVhZGluZzohMCx0cmFpbGluZzohMH0pfWNvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLl9pc0Nvbm5lY3RlZD0hMH1kaXNjb25uZWN0ZWRDYWxsYmFjaygpe3N1cGVyLmRpc2Nvbm5lY3RlZENhbGxiYWNrKCksdGhpcy5faXNDb25uZWN0ZWQ9ITF9c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue2FjdGl2ZTp7dHlwZTpCb29sZWFuLG9ic2VydmVyOiJfbG9hZERhdGFJZkFjdGl2ZSJ9LF9pc0Nvbm5lY3RlZDp7dHlwZTpCb29sZWFufSxsb2FkS2V5Ont0eXBlOlN0cmluZ30sZGF0YVRvTG9hZDp7dHlwZTpBcnJheX0sZ2V0RGF0YUxvYWROYW1lOnt0eXBlOk9iamVjdH0sbG9hZERhdGFDYWxsYmFjazp7dHlwZTpPYmplY3R9LHJlcXVlc3REYXRhOnt0eXBlOk9iamVjdH19fXN0YXRpYyBnZXQgb2JzZXJ2ZXJzKCl7cmV0dXJuWyJfZGF0YVRvTG9hZENoYW5nZWQoX2lzQ29ubmVjdGVkLCBkYXRhVG9Mb2FkLiopIl19b25Mb2FkRmluaXNoKCl7fXJlbG9hZCgpe3RoaXMuX2RhdGFMb2FkU3RhdGUuY2xlYXIoKSx0aGlzLl9sb2FkRGF0YSgpfXJlc2V0KCl7dGhpcy5fbG9hZERhdGFBc3luYyE9bnVsbCYmKGNsZWFyVGltZW91dCh0aGlzLl9sb2FkRGF0YUFzeW5jKSx0aGlzLl9sb2FkRGF0YUFzeW5jPW51bGwpLHRoaXMuX2NhbmNlbGxlciYmdGhpcy5fY2FuY2VsbGVyLmNhbmNlbEFsbCgpLHRoaXMuX2RhdGFMb2FkU3RhdGUmJnRoaXMuX2RhdGFMb2FkU3RhdGUuY2xlYXIoKSx0aGlzLl9pc0Nvbm5lY3RlZCYmdGhpcy5fbG9hZERhdGEoKX1fZGF0YVRvTG9hZENoYW5nZWQoKXt0aGlzLl9pc0Nvbm5lY3RlZCYmdGhpcy5fbG9hZERhdGEoKX1kZXRhY2hlZCgpe3RoaXMuX2xvYWREYXRhQXN5bmMhPW51bGwmJihjbGVhclRpbWVvdXQodGhpcy5fbG9hZERhdGFBc3luYyksdGhpcy5fbG9hZERhdGFBc3luYz1udWxsKX1fbG9hZERhdGFJZkFjdGl2ZSgpe3RoaXMuYWN0aXZlJiZ0aGlzLl9sb2FkRGF0YSgpfV9sb2FkRGF0YUltcGwoKXshdGhpcy5hY3RpdmV8fCh0aGlzLl9sb2FkRGF0YUFzeW5jIT09bnVsbCYmY2xlYXJUaW1lb3V0KHRoaXMuX2xvYWREYXRhQXN5bmMpLHRoaXMuX2xvYWREYXRhQXN5bmM9c2V0VGltZW91dCh0aGlzLl9jYW5jZWxsZXIuY2FuY2VsbGFibGUocj0+e2lmKHIuY2FuY2VsbGVkKXJldHVybjt0aGlzLmRhdGFMb2FkaW5nPSEwO2xldCBuPXRoaXMuZGF0YVRvTG9hZC5maWx0ZXIoYT0+e2xldCBzPXRoaXMuZ2V0RGF0YUxvYWROYW1lKGEpO3JldHVybiF0aGlzLl9kYXRhTG9hZFN0YXRlLmhhcyhzKX0pO2ZvcihsZXQgYSBvZiBuKXtsZXQgcz10aGlzLmdldERhdGFMb2FkTmFtZShhKTt0aGlzLl9kYXRhTG9hZFN0YXRlLnNldChzLHY0LkxPQURJTkcpfWxldCBpPXRoaXMuX2NhbmNlbGxlci5jYW5jZWxsYWJsZShhPT57aWYoYS5jYW5jZWxsZWQpcmV0dXJuO2xldHtpdGVtOnMsZGF0YTpsfT1hLnZhbHVlLGM9dGhpcy5nZXREYXRhTG9hZE5hbWUocyk7dGhpcy5fZGF0YUxvYWRTdGF0ZS5zZXQoYyx2NC5MT0FERUQpLHRoaXMubG9hZERhdGFDYWxsYmFjayh0aGlzLHMsbCl9KSxvPXRoaXMuX2NhbmNlbGxlci5jYW5jZWxsYWJsZShhPT57aWYoIWEuY2FuY2VsbGVkKXtsZXQgbD1hLnZhbHVlLGM9bmV3IFNldChuLm1hcChoPT50aGlzLmdldERhdGFMb2FkTmFtZShoKSkpO3RoaXMuZGF0YVRvTG9hZC5zb21lKGg9PmMuaGFzKHRoaXMuZ2V0RGF0YUxvYWROYW1lKGgpKSkmJnRoaXMub25Mb2FkRmluaXNoKCksdGhpcy5fbG9hZERhdGFBc3luYz1udWxsLHRoaXMuZGF0YUxvYWRlZEF0TGVhc3RPbmNlPSEwfUFycmF5LmZyb20odGhpcy5fZGF0YUxvYWRTdGF0ZS52YWx1ZXMoKSkuaW5jbHVkZXModjQuTE9BRElORyl8fCh0aGlzLmRhdGFMb2FkaW5nPSExKX0pO3RoaXMucmVxdWVzdERhdGEobixpLCgpPT5vKHZvaWQgMCkpfSkpKX19fXZhciBncWU9RWUoT2UoKSwxKSxXbz1FZSh3bCgpLDEpLFJTPVt7Y2hhcmFjdGVyOiJcdTI1RkMiLG1ldGhvZDpXby5TeW1ib2xGYWN0b3JpZXMuc3F1YXJlfSx7Y2hhcmFjdGVyOiJcdTI1QzYiLG1ldGhvZDpXby5TeW1ib2xGYWN0b3JpZXMuZGlhbW9uZH0se2NoYXJhY3RlcjoiXHUyNUIyIixtZXRob2Q6V28uU3ltYm9sRmFjdG9yaWVzLnRyaWFuZ2xlfSx7Y2hhcmFjdGVyOiJcdTI2MDUiLG1ldGhvZDpXby5TeW1ib2xGYWN0b3JpZXMuc3Rhcn0se2NoYXJhY3RlcjoiXHUyNzFBIixtZXRob2Q6V28uU3ltYm9sRmFjdG9yaWVzLmNyb3NzfV0sRWQ7KGZ1bmN0aW9uKGUpe2UuU1RFUD0ic3RlcCIsZS5SRUxBVElWRT0icmVsYXRpdmUiLGUuV0FMTF9USU1FPSJ3YWxsX3RpbWUifSkoRWR8fChFZD17fSkpO3ZhciBlMD00LF9xZT00LGRCPTMsbUI9MjAsZ0I9NCxMS3Q9NjtmdW5jdGlvbiBXdShlKXtyZXR1cm4gdD0+e2xldCByPU1hdGguYWJzKHQpO3I8MWUtMTUmJihyPTApO2xldCBuO3JldHVybiByPj0xZTQ/bj14bigiLiIrZSsifmUiKTpyPjAmJnI8LjAxP249eG4oIi4iK2UrIn5lIik6bj14bigiLiIrZSsifmciKSxuKHQpfX12YXIgeDQ9eG4oYC4ke19xZX1+c2ApO2Z1bmN0aW9uICRhdCgpe2xldCBlPW5ldyBXby5TY2FsZXMuTGluZWFyO2UudGlja0dlbmVyYXRvcihXby5TY2FsZXMuVGlja0dlbmVyYXRvcnMuaW50ZWdlclRpY2tHZW5lcmF0b3IoKSk7bGV0IHQ9bmV3IFdvLkF4ZXMuTnVtZXJpYyhlLCJib3R0b20iKTtyZXR1cm4gdC5mb3JtYXR0ZXIoeDQpLHtzY2FsZTplLGF4aXM6dCxhY2Nlc3NvcjpyPT5yLnN0ZXB9fXZhciBfQj1Xby5Gb3JtYXR0ZXJzLnRpbWUoIiVhICViICVlLCAlSDolTTolUyIpO2Z1bmN0aW9uIHlxZSgpe2xldCBlPW5ldyBXby5TY2FsZXMuVGltZTtyZXR1cm57c2NhbGU6ZSxheGlzOm5ldyBXby5BeGVzLlRpbWUoZSwiYm90dG9tIiksYWNjZXNzb3I6dD0+dC53YWxsX3RpbWV9fXZhciByMD0oZSx0LHIpPT57aWYoZS5yZWxhdGl2ZSE9bnVsbClyZXR1cm4gZS5yZWxhdGl2ZTtsZXQgbj1yLmRhdGEoKSxpPW4ubGVuZ3RoPjA/K25bMF0ud2FsbF90aW1lOjA7cmV0dXJuKCtlLndhbGxfdGltZS1pKS8oNjAqNjAqMWUzKX0seUI9ZT0+e2xldCB0PSIiLHI9TWF0aC5mbG9vcihlLzI0KTtlLT1yKjI0LHImJih0Kz1yKyJkICIpO2xldCBuPU1hdGguZmxvb3IoZSk7ZS09bixlKj02MCwobnx8cikmJih0Kz1uKyJoICIpO2xldCBpPU1hdGguZmxvb3IoZSk7ZS09aSxlKj02MCwoaXx8bnx8cikmJih0Kz1pKyJtICIpO2xldCBvPU1hdGguZmxvb3IoZSk7cmV0dXJuIHQrbysicyJ9O2Z1bmN0aW9uIHZxZSgpe2xldCBlPW5ldyBXby5TY2FsZXMuTGluZWFyO3JldHVybntzY2FsZTplLGF4aXM6bmV3IFdvLkF4ZXMuTnVtZXJpYyhlLCJib3R0b20iKSxhY2Nlc3NvcjpyMH19ZnVuY3Rpb24gdkIoZSl7c3dpdGNoKGUpe2Nhc2UgRWQuU1RFUDpyZXR1cm4gJGF0KCk7Y2FzZSBFZC5XQUxMX1RJTUU6cmV0dXJuIHlxZSgpO2Nhc2UgRWQuUkVMQVRJVkU6cmV0dXJuIHZxZSgpO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKCJpbnZhbGlkIHhUeXBlOiAiK2UpfX12YXIgenM9RWUoT2UoKSwxKSxNbj1FZSh3bCgpLDEpO3ZhciB2YT1FZSh3bCgpLDEpO2Z1bmN0aW9uIHhxZShlKXtsZXQgdD1bXSxyPWU7Zm9yKDtyJiZyIGluc3RhbmNlb2YgSFRNTEVsZW1lbnQ7KWlmKHQucHVzaChyKSxyLmFzc2lnbmVkU2xvdClyPXIuYXNzaWduZWRTbG90O2Vsc2UgaWYoci5wYXJlbnRFbGVtZW50KXI9ci5wYXJlbnRFbGVtZW50O2Vsc2V7bGV0IG49ci5wYXJlbnROb2RlO24gaW5zdGFuY2VvZiBEb2N1bWVudEZyYWdtZW50P3I9bi5ob3N0OnI9biE9PXI/bjpudWxsfXJldHVybiB0fXZhciBicWU9WzEsMCwwLDEsMCwwXTtmdW5jdGlvbiB3cWUoZSl7bGV0IHQ9eHFlKGUpLHI9YnFlLG49bnVsbDtmb3IobGV0IGkgb2YgdCl7bGV0IG89dmEuVXRpbHMuRE9NLmdldEVsZW1lbnRUcmFuc2Zvcm0oaSk7aWYobyE9bnVsbCl7bGV0IGw9aS5jbGllbnRXaWR0aC8yLGM9aS5jbGllbnRIZWlnaHQvMjtyPXZhLlV0aWxzLk1hdGgubXVsdGlwbHlUcmFuc2xhdGUocixbbCxjXSkscj12YS5VdGlscy5NYXRoLm11bHRpcGx5TWF0cml4KHIsdmEuVXRpbHMuTWF0aC5pbnZlcnRNYXRyaXgobykpLHI9dmEuVXRpbHMuTWF0aC5tdWx0aXBseVRyYW5zbGF0ZShyLFstbCwtY10pfWxldCBhPWkuc2Nyb2xsTGVmdCxzPWkuc2Nyb2xsVG9wOyhuPT09bnVsbHx8aT09PW4pJiYoYS09aS5vZmZzZXRMZWZ0K2kuY2xpZW50TGVmdCxzLT1pLm9mZnNldFRvcCtpLmNsaWVudFRvcCxuPWkub2Zmc2V0UGFyZW50KSxyPXZhLlV0aWxzLk1hdGgubXVsdGlwbHlUcmFuc2xhdGUocixbYSxzXSl9cmV0dXJuIHJ9dmFyIGJCPWNsYXNzIGV4dGVuZHMgdmEuVXRpbHMuVHJhbnNsYXRvcntjb21wdXRlUG9zaXRpb24odCxyKXtsZXQgbj17eDp0LHk6cn0saT13cWUodGhpcy5fcm9vdEVsZW1lbnQpO3JldHVybiBpPT1udWxsP246dmEuVXRpbHMuTWF0aC5hcHBseVRyYW5zZm9ybShpLG4pfX0sVTE9Y2xhc3MgZXh0ZW5kcyB2YS5EaXNwYXRjaGVycy5Nb3VzZXtjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KSx0aGlzLl9ldmVudFRhcmdldD10LnJvb3QoKS5yb290RWxlbWVudCgpLm5vZGUoKSx0aGlzLl90cmFuc2xhdG9yPW5ldyBiQih0LnJvb3QoKS5yb290RWxlbWVudCgpLm5vZGUoKSl9c3RhdGljIGdldERpc3BhdGNoZXIodCl7bGV0IHI9dC5yb290KCkucm9vdEVsZW1lbnQoKSxuPXJbVTEuX0RJU1BBVENIRVJfS0VZXTtyZXR1cm4gbnx8KG49bmV3IFUxKHQpLHJbVTEuX0RJU1BBVENIRVJfS0VZXT1uKSxufX0scTE9Y2xhc3MgZXh0ZW5kcyB2YS5EaXNwYXRjaGVycy5Ub3VjaHtjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KSx0aGlzLl9ldmVudFRhcmdldD10LnJvb3QoKS5yb290RWxlbWVudCgpLm5vZGUoKSx0aGlzLl90cmFuc2xhdG9yPW5ldyBiQih0LnJvb3QoKS5yb290RWxlbWVudCgpLm5vZGUoKSl9c3RhdGljIGdldERpc3BhdGNoZXIodCl7bGV0IHI9dC5yb290KCkucm9vdEVsZW1lbnQoKSxuPXJbcTEuX0RJU1BBVENIRVJfS0VZXTtyZXR1cm4gbnx8KG49bmV3IHExKHQpLHJbcTEuX0RJU1BBVENIRVJfS0VZXT1uKSxufX07dmEuSW50ZXJhY3Rpb24ucHJvdG90eXBlLl9pc0luc2lkZUNvbXBvbmVudD1mdW5jdGlvbihlKXtyZXR1cm4gMDw9ZS54JiYwPD1lLnkmJmUueDx0aGlzLl9jb21wb25lbnRBdHRhY2hlZFRvLndpZHRoKCkmJmUueTx0aGlzLl9jb21wb25lbnRBdHRhY2hlZFRvLmhlaWdodCgpfTt2YXIgd0I9Y2xhc3MgZXh0ZW5kcyB2YS5JbnRlcmFjdGlvbnMuUG9pbnRlcntfYW5jaG9yKHQpe2xldCByPXRoaXM7ci5faXNBbmNob3JlZD0hMCxyLl9tb3VzZURpc3BhdGNoZXI9VTEuZ2V0RGlzcGF0Y2hlcihyLl9jb21wb25lbnRBdHRhY2hlZFRvKSxyLl9tb3VzZURpc3BhdGNoZXIub25Nb3VzZU1vdmUoci5fbW91c2VNb3ZlQ2FsbGJhY2spLHIuX3RvdWNoRGlzcGF0Y2hlcj1xMS5nZXREaXNwYXRjaGVyKHIuX2NvbXBvbmVudEF0dGFjaGVkVG8pLHIuX3RvdWNoRGlzcGF0Y2hlci5vblRvdWNoU3RhcnQoci5fdG91Y2hTdGFydENhbGxiYWNrKX19O3ZhciBrS3Q9RWUoT2UoKSwxKTt2YXIgRzE7KGZ1bmN0aW9uKGUpe2UuQVVUTz0iYXV0byIsZS5CT1RUT009ImJvdHRvbSIsZS5SSUdIVD0icmlnaHQifSkoRzF8fChHMT17fSkpO3ZhciBTcWU9e2JveFNoYWRvdzoiMCAxcHggNHB4IHJnYmEoMCwgMCwgMCwgLjMpIixvcGFjaXR5OjAscG9zaXRpb246ImZpeGVkIix3aWxsQ2hhbmdlOiJ0cmFuc2Zvcm0iLHpJbmRleDo1fSxiND1jbGFzcyBleHRlbmRzIEd0KG10KXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5wb3NpdGlvbj1HMS5BVVRPLHRoaXMubWluRGlzdEZyb21FZGdlPTE1LHRoaXMuX3N0eWxlQ2FjaGU9bnVsbCx0aGlzLl9yYWY9bnVsbCx0aGlzLl90dW5uZWw9bnVsbH1yZWFkeSgpe3RoaXMuX3N0eWxlQ2FjaGU9bnVsbCx0aGlzLl9yYWY9bnVsbCx0aGlzLl90dW5uZWw9bnVsbH1hdHRhY2hlZCgpe3RoaXMuX3R1bm5lbD10aGlzLl9jcmVhdGVUdW5uZWwoKSx0aGlzLl9oaWRlT25CbHVyPSgpPT57ZG9jdW1lbnQuaGlkZGVuJiZ0aGlzLmhpZGUoKX0sd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoInZpc2liaWxpdHljaGFuZ2UiLHRoaXMuX2hpZGVPbkJsdXIpfWRldGFjaGVkKCl7dGhpcy5oaWRlKCksdGhpcy5fcmVtb3ZlVHVubmVsKHRoaXMuX3R1bm5lbCksdGhpcy5fdHVubmVsPW51bGwsd2luZG93LnJlbW92ZUV2ZW50TGlzdGVuZXIoInZpc2liaWxpdHljaGFuZ2UiLHRoaXMuX2hpZGVPbkJsdXIpfWNvbnRlbnQoKXtyZXR1cm4gdGhpcy5fdHVubmVsLnNoYWRvd1Jvb3R9aGlkZSgpe3RoaXMuX3JhZiE9PW51bGwmJndpbmRvdy5jYW5jZWxBbmltYXRpb25GcmFtZSh0aGlzLl9yYWYpLHRoaXMuX3N0eWxlQ2FjaGU9bnVsbCx0aGlzLl90dW5uZWwuc3R5bGUub3BhY2l0eT0wfXVwZGF0ZUFuZFBvc2l0aW9uKHQpe3RoaXMuX3JhZiE9PW51bGwmJndpbmRvdy5jYW5jZWxBbmltYXRpb25GcmFtZSh0aGlzLl9yYWYpLHRoaXMuX3JhZj13aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT57IXRoaXMuaXNBdHRhY2hlZHx8dGhpcy5fcmVwb3NpdGlvbkltcGwodCl9KX1fcmVwb3NpdGlvbkltcGwodCl7bGV0IHI9dGhpcy5fdHVubmVsLG49dC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxpPXIuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksbz13aW5kb3cuaW5uZXJIZWlnaHQsYT1kb2N1bWVudC5ib2R5LmNsaWVudFdpZHRoLHM9bi50b3AsbD1zK24uaGVpZ2h0LGM9aS5oZWlnaHQrbUIsdT1udWxsLGg9TWF0aC5tYXgodGhpcy5taW5EaXN0RnJvbUVkZ2Usbi5sZWZ0KSxmPW51bGwscD1zO3RoaXMucG9zaXRpb249PUcxLlJJR0hUP2g9bi5yaWdodDoocD1sK21CLGE8aCtpLndpZHRoK3RoaXMubWluRGlzdEZyb21FZGdlJiYoaD1udWxsLGY9dGhpcy5taW5EaXN0RnJvbUVkZ2UpKSx0aGlzLnBvc2l0aW9uPT1HMS5BVVRPJiZuLnRvcC1jPjAmJm88bi50b3Arbi5oZWlnaHQrYyYmKHA9bnVsbCx1PW8tcyttQik7bGV0IGQ9e2NvbnRhaW46ImNvbnRlbnQiLG9wYWNpdHk6MSxsZWZ0Omg/YCR7aH1weGA6bnVsbCxyaWdodDpmP2Ake2Z9cHhgOm51bGwsdG9wOnA/YCR7cH1weGA6bnVsbCxib3R0b206dT9gJHt1fXB4YDpudWxsfTtrS3QuaXNFcXVhbCh0aGlzLl9zdHlsZUNhY2hlLGQpfHwoT2JqZWN0LmFzc2lnbihyLnN0eWxlLGQpLHRoaXMuX3N0eWxlQ2FjaGU9ZCl9X2NyZWF0ZVR1bm5lbCgpe2lmKCF0aGlzLmNvbnRlbnRDb21wb25lbnROYW1lKXRocm93IG5ldyBSYW5nZUVycm9yKCJSZXF1aXJlIGBjb250ZW50Q29tcG9uZW50TmFtZWAgdG8gYmUgYSBuYW1lIG9mIGEgUG9seW1lciBjb21wb25lbnQiKTtsZXQgdD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KHRoaXMuY29udGVudENvbXBvbmVudE5hbWUpO3JldHVybiBPYmplY3QuYXNzaWduKHQuc3R5bGUsU3FlKSxkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHQpLHR9X3JlbW92ZVR1bm5lbCh0KXtkb2N1bWVudC5ib2R5LnJlbW92ZUNoaWxkKHQpfX07RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sYjQucHJvdG90eXBlLCJjb250ZW50Q29tcG9uZW50TmFtZSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxiNC5wcm90b3R5cGUsInBvc2l0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLGI0LnByb3RvdHlwZSwibWluRGlzdEZyb21FZGdlIix2b2lkIDApO2I0PUUoW3l0KCJ2ei1jaGFydC10b29sdGlwIildLGI0KTt2YXIgTlM9RWUod2woKSwxKTt2YXIgREt0PTFlNCxPS3Q9LjAwMSx6S3Q9eG4oIi4yfmUiKSxNcWU9eG4oIi40fnIiKSxSS3Q9eG4oIix+Iik7ZnVuY3Rpb24gTkt0KGUpe2lmKGU9PT0wKXJldHVybiIwIjtsZXQgdD1NYXRoLmFicyhlKTtyZXR1cm4gdD49REt0fHx0PE9LdD96S3QoZSk6TXFlKGUpfXZhciBaYXQ9e2Zvcm1hdFRpY2s6Tkt0LGZvcm1hdFNob3J0Ok5LdCxmb3JtYXRSZWFkYWJsZShlKXtsZXQgdD1NYXRoLmFicyhlKTtyZXR1cm4gdD49REt0fHx0PE9LdD96S3QoZSk6Ukt0KGUpfSxmb3JtYXRMb25nOlJLdH0sakduPW5ldyBJbnRsLk51bWJlckZvcm1hdCh2b2lkIDAse21heGltdW1GcmFjdGlvbkRpZ2l0czozfSk7dmFyIFhHbj14bigiMC4zfnMiKSwkR249eG4oIiwuM35mIik7dmFyIEVxZT0xZTMsVHFlPTYwKkVxZSxDcWU9NjAqVHFlLEFxZT0yNCpDcWUsS0duPTM2NSpBcWUsWkduPXhuKCIuNH4iKTt2YXIgUHFlPVliKCkudGlja0Zvcm1hdCgpLEthdCxGS3Q9e2Zvcm1hdFRpY2soZSl7cmV0dXJuIFBxZShuZXcgRGF0ZShlKSl9LGZvcm1hdFNob3J0KGUpe3JldHVybiBuZXcgRGF0ZShlKS50b0xvY2FsZVN0cmluZyhLYXQse3llYXI6Im51bWVyaWMiLG1vbnRoOiJzaG9ydCIsZGF5OiJudW1lcmljIixob3VyOiJudW1lcmljIixtaW51dGU6Im51bWVyaWMiLHNlY29uZDoibnVtZXJpYyJ9KX0sZm9ybWF0UmVhZGFibGUoZSl7cmV0dXJuIG5ldyBEYXRlKGUpLnRvTG9jYWxlU3RyaW5nKEthdCx7eWVhcjoibnVtZXJpYyIsbW9udGg6InNob3J0IixkYXk6Im51bWVyaWMiLGhvdXI6Im51bWVyaWMiLG1pbnV0ZToibnVtZXJpYyIsc2Vjb25kOiJudW1lcmljIix0aW1lWm9uZU5hbWU6InNob3J0In0pfSxmb3JtYXRMb25nKGUpe3JldHVybiBuZXcgRGF0ZShlKS50b0xvY2FsZVN0cmluZyhLYXQse3llYXI6Im51bWVyaWMiLG1vbnRoOiJsb25nIixkYXk6Im51bWVyaWMiLGhvdXI6Im51bWVyaWMiLG1pbnV0ZToibnVtZXJpYyIsc2Vjb25kOiJudW1lcmljIix0aW1lWm9uZU5hbWU6InNob3J0IixmcmFjdGlvbmFsU2Vjb25kRGlnaXRzOjN9KX19O3ZhciBUZDsoZnVuY3Rpb24oZSl7ZVtlLkxJTkVBUj0wXT0iTElORUFSIixlW2UuTE9HMTA9MV09IkxPRzEwIixlW2UuVElNRT0yXT0iVElNRSJ9KShUZHx8KFRkPXt9KSk7ZnVuY3Rpb24gQkt0KGUpe3N3aXRjaChlKXtjYXNlIFRkLkxJTkVBUjpyZXR1cm4gbmV3IEphdDtjYXNlIFRkLkxPRzEwOnJldHVybiBuZXcgUWF0O2Nhc2UgVGQuVElNRTpyZXR1cm4gbmV3IHRzdDtkZWZhdWx0OmxldCB0PWU7dGhyb3cgbmV3IFJhbmdlRXJyb3IoYFNjYWxlVHlwZSAke3R9IG5vdCBzdXBwb3J0ZWQuYCl9fXZhciBJcWU9LjA1LEphdD1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuZGVmYXVsdEZvcm1hdHRlcj1aYXR9dHJhbnNmb3JtKHQscixuKXtsZXRbaSxvXT10LGE9by1pLFtzLGxdPXIsYz1sLXM7cmV0dXJuIGE9PT0wP3M6Yy9hKihuLWkpK3N9Zm9yd2FyZCh0LHIsbil7cmV0dXJuIHRoaXMudHJhbnNmb3JtKHQscixuKX1yZXZlcnNlKHQscixuKXtyZXR1cm4gdGhpcy50cmFuc2Zvcm0ocix0LG4pfW5pY2VEb21haW4odCl7bGV0W3Isbl09dDtpZihuPHIpdGhyb3cgbmV3IEVycm9yKCJVbmV4cGVjdGVkIGlucHV0OiBtaW4gaXMgbGFyZ2VyIHRoYW4gbWF4Iik7aWYobj09PXIpcmV0dXJuIHI9PT0wP1stMSwxXTpyPDA/WzIqciwwXTpbMCwyKnJdO2xldCBpPXpuKCksbz0obi1yK051bWJlci5FUFNJTE9OKSpJcWUsW2Esc109aS5kb21haW4oW3ItbyxuK29dKS5uaWNlKCkuZG9tYWluKCk7cmV0dXJuW2Esc119dGlja3ModCxyKXtyZXR1cm4gem4oKS5kb21haW4odCkudGlja3Mocil9aXNTYWZlTnVtYmVyKHQpe3JldHVybiBOdW1iZXIuaXNGaW5pdGUodCl9fSxRYXQ9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmRlZmF1bHRGb3JtYXR0ZXI9WmF0fXRyYW5zZm9ybSh0KXtyZXR1cm4gTWF0aC5sb2cxMCh0PjA/dDpOdW1iZXIuTUlOX1ZBTFVFKX11bnRyYW5zZm9ybSh0KXtyZXR1cm4gTWF0aC5leHAodC9NYXRoLkxPRzEwRSl9Zm9yd2FyZCh0LHIsbil7aWYobjw9MClyZXR1cm4gclswXTtsZXRbaSxvXT10LFthLHNdPXIsbD10aGlzLnRyYW5zZm9ybShpKSx1PXRoaXMudHJhbnNmb3JtKG8pLWwsaD1zLWE7cmV0dXJuIG49dGhpcy50cmFuc2Zvcm0obiksaC8odStOdW1iZXIuRVBTSUxPTikqKG4tbCkrYX1yZXZlcnNlKHQscixuKXtsZXRbaSxvXT10LFthLHNdPXIsbD10aGlzLnRyYW5zZm9ybShpKSx1PXRoaXMudHJhbnNmb3JtKG8pLWwsaD1zLWEsZj11LyhoK051bWJlci5FUFNJTE9OKSoobi1hKStsO3JldHVybiB0aGlzLnVudHJhbnNmb3JtKGYpfW5pY2VEb21haW4odCl7bGV0W3Isbl09dDtpZihyPm4pdGhyb3cgbmV3IEVycm9yKCJVbmV4cGVjdGVkIGlucHV0OiBtaW4gaXMgbGFyZ2VyIHRoYW4gbWF4Iik7bGV0IGk9TWF0aC5tYXgocixOdW1iZXIuTUlOX1ZBTFVFKSxvPU1hdGgubWF4KG4sTnVtYmVyLk1JTl9WQUxVRSk7cmV0dXJuIG48PTA/W051bWJlci5NSU5fVkFMVUUsMV06W01hdGgubWF4KE51bWJlci5NSU5fVkFMVUUsaSouNSksbyoyXX10aWNrcyh0LHIpe2xldCBuPXRbMF08PTA/TnVtYmVyLk1JTl9WQUxVRTp0WzBdLGk9dFsxXTw9MD9OdW1iZXIuTUlOX1ZBTFVFOnRbMV0sbz1jYygpLmRvbWFpbihbbixpXSkudGlja3Mocik7cmV0dXJuIG8ubGVuZ3RoP286dH1pc1NhZmVOdW1iZXIodCl7cmV0dXJuIE51bWJlci5pc0Zpbml0ZSh0KSYmdD4wfX0sdHN0PWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5zY2FsZT1ZYigpLHRoaXMuZGVmYXVsdEZvcm1hdHRlcj1GS3R9Zm9yd2FyZCh0LHIsbil7cmV0dXJuIHRoaXMuc2NhbGUuZG9tYWluKHQpLnJhbmdlKHIpKG4pfXJldmVyc2UodCxyLG4pe3JldHVybiB0aGlzLnNjYWxlLmRvbWFpbih0KS5yYW5nZShyKS5pbnZlcnQobikuZ2V0VGltZSgpfW5pY2VEb21haW4odCl7bGV0W3Isbl09dGhpcy5zY2FsZS5kb21haW4odCkubmljZSgpLmRvbWFpbigpO3JldHVybltyLmdldFRpbWUoKSxuLmdldFRpbWUoKV19dGlja3ModCxyKXtyZXR1cm4gdGhpcy5zY2FsZS5kb21haW4odCkudGlja3MocikubWFwKG49Pm4uZ2V0VGltZSgpKX1pc1NhZmVOdW1iZXIodCl7cmV0dXJuIE51bWJlci5pc0Zpbml0ZSh0KX19O3ZhciBTQj1jbGFzcyBleHRlbmRzIE5TLlNjYWxlcy5MaW5lYXJ7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX2lnbm9yZU91dGxpZXI9ITEsdGhpcy5wYWRQcm9wb3J0aW9uKC4yKX1zZXRWYWx1ZVByb3ZpZGVyRm9yRG9tYWluKHQpe3JldHVybiB0aGlzLl92YWx1ZVByb3ZpZGVyRm9yRG9tYWluPXQsdGhpc31fbmljZURvbWFpbih0LHIpe2xldFtuLGldPXQ7cmV0dXJuIEJLdChUZC5MSU5FQVIpLm5pY2VEb21haW4oW24saV0pfV9nZXRVbmJvdW5kZWRFeHRlbnQodCl7bGV0IHI9dGhpcy5fZ2V0QWxsSW5jbHVkZWRWYWx1ZXModCksbj10aGlzLl9kZWZhdWx0RXh0ZW50KCk7aWYoci5sZW5ndGghPT0wKXtsZXQgaT1bTlMuVXRpbHMuTWF0aC5taW4ocixuWzBdKSxOUy5VdGlscy5NYXRoLm1heChyLG5bMV0pXTtuPXRoaXMuX25pY2VEb21haW4oaSl9cmV0dXJuIG59X2dldEFsbEluY2x1ZGVkVmFsdWVzKHQ9ITEpe2xldCByPXRoaXMuX3ZhbHVlUHJvdmlkZXJGb3JEb21haW4/dGhpcy5fdmFsdWVQcm92aWRlckZvckRvbWFpbigpOltdO3JldHVybiB0aGlzLmV4dGVudE9mVmFsdWVzKHIpfWV4dGVudE9mVmFsdWVzKHQpe2xldCByPXQuZmlsdGVyKG89Pk5TLlV0aWxzLk1hdGguaXNWYWxpZE51bWJlcihvKSksbj1yO2lmKHRoaXMuaWdub3JlT3V0bGllcigpKXtsZXQgbz1yLnNvcnQoKGwsYyk9PmwtYyksYT1zYShvLC4wNSkscz1zYShvLC45NSk7bj1yLmZpbHRlcihsPT5sPj1hJiZsPD1zKX1sZXQgaT1hYShuKTtyZXR1cm4gaVswXT09bnVsbHx8aVsxXT09bnVsbD9bXTppfWlnbm9yZU91dGxpZXIodCl7cmV0dXJuIHR5cGVvZiB0PT0iYm9vbGVhbiI/KHRoaXMuX2lnbm9yZU91dGxpZXI9dCx0aGlzKTp0aGlzLl9pZ25vcmVPdXRsaWVyfX07dmFyIFRCPUVlKHdsKCksMSk7dmFyIEhLdD1FZSh3bCgpLDEpLE1CPWNsYXNzIGV4dGVuZHMgSEt0LlF1YW50aXRhdGl2ZVNjYWxle2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl9pZ25vcmVPdXRsaWVyPSExfXNldFZhbHVlUHJvdmlkZXJGb3JEb21haW4odCl7cmV0dXJuIHRoaXMuX3ZhbHVlUHJvdmlkZXJGb3JEb21haW49dCx0aGlzfWlnbm9yZU91dGxpZXIodCl7cmV0dXJuIHR5cGVvZiB0PT0iYm9vbGVhbiI/KHRoaXMuX2lnbm9yZU91dGxpZXI9dCx0aGlzKTp0aGlzLl9pZ25vcmVPdXRsaWVyfV9nZXRBbGxJbmNsdWRlZFZhbHVlcyh0PSExKXtsZXQgcj10aGlzLl92YWx1ZVByb3ZpZGVyRm9yRG9tYWluP3RoaXMuX3ZhbHVlUHJvdmlkZXJGb3JEb21haW4oKTpbXTtyZXR1cm4gdGhpcy5leHRlbnRPZlZhbHVlcyhyKX19O3ZhciBFQj1NYXRoLnBvdygyLC0xMDc0KTtmdW5jdGlvbiB3NChlKXtyZXR1cm4gTWF0aC5sb2cxMChlKX1mdW5jdGlvbiBlc3QoZSl7cmV0dXJuIE1hdGgucG93KDEwLGUpfXZhciBDQj1jbGFzcyBleHRlbmRzIE1Ce2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLl9kM0xvZ1NjYWxlPWNjKCksdGhpcy5wYWRQcm9wb3J0aW9uKC4yKX1zY2FsZSh0KXtyZXR1cm4gdDw9MD9OYU46dGhpcy5fZDNMb2dTY2FsZSh0KX1pbnZlcnQodCl7cmV0dXJuIHRoaXMuX2QzTG9nU2NhbGUuaW52ZXJ0KHQpfXNjYWxlVHJhbnNmb3JtYXRpb24odCl7cmV0dXJuIHRoaXMuc2NhbGUodCl9aW52ZXJ0ZWRUcmFuc2Zvcm1hdGlvbih0KXtyZXR1cm4gdGhpcy5pbnZlcnQodCl9Z2V0VHJhbnNmb3JtYXRpb25Eb21haW4oKXtyZXR1cm4gdGhpcy5kb21haW4oKX1zZXRUcmFuc2Zvcm1hdGlvbkRvbWFpbih0KXt0aGlzLmRvbWFpbih0KX1nZXRUcmFuc2Zvcm1hdGlvbkV4dGVudCgpe3JldHVybiB0aGlzLl9nZXRVbmJvdW5kZWRFeHRlbnQoITApfV9nZXREb21haW4oKXtyZXR1cm4gdGhpcy5fdW50cmFuc2Zvcm1lZERvbWFpbn1fc2V0RG9tYWluKHQpe3RoaXMuX3VudHJhbnNmb3JtZWREb21haW49dDtsZXRbcixuXT10O3N1cGVyLl9zZXREb21haW4oW01hdGgubWF4KEVCLHIpLG5dKX1fbmljZURvbWFpbih0LHIpe2xldFtuLGldPXQsbz1NYXRoLm1heCh3NChFQiksdzQobikpLGE9dzQoaSkscz1hLW8sbD1zP3MqdGhpcy5wYWRQcm9wb3J0aW9uKCk6MTtyZXR1cm5bZXN0KE1hdGgubWF4KHc0KEVCKSxvLWwpKSxlc3QoYStsKV19X2dldFVuYm91bmRlZEV4dGVudCh0KXtsZXQgcj10aGlzLl9nZXRBbGxJbmNsdWRlZFZhbHVlcyh0KSxuPXRoaXMuX2RlZmF1bHRFeHRlbnQoKTtpZihyLmxlbmd0aCE9PTApe2xldCBpPVtUQi5VdGlscy5NYXRoLm1pbihyLG5bMF0pLFRCLlV0aWxzLk1hdGgubWF4KHIsblsxXSldO249dGhpcy5fbmljZURvbWFpbihpKX1yZXR1cm4gbn1fZ2V0QWxsSW5jbHVkZWRWYWx1ZXModD0hMSl7cmV0dXJuIHN1cGVyLl9nZXRBbGxJbmNsdWRlZFZhbHVlcygpLm1hcChuPT5uPjA/bjpFQil9X2RlZmF1bHRFeHRlbnQoKXtyZXR1cm5bMSwxMF19X2JhY2tpbmdTY2FsZURvbWFpbih0KXtyZXR1cm4gdD09bnVsbD90aGlzLl9kM0xvZ1NjYWxlLmRvbWFpbigpOih0aGlzLl9kM0xvZ1NjYWxlLmRvbWFpbih0KSx0aGlzKX1fZ2V0UmFuZ2UoKXtyZXR1cm4gdGhpcy5fZDNMb2dTY2FsZS5yYW5nZSgpfV9zZXRSYW5nZSh0KXt0aGlzLl9kM0xvZ1NjYWxlLnJhbmdlKHQpfWRlZmF1bHRUaWNrcygpe3JldHVybiB0aGlzLl9kM0xvZ1NjYWxlLnRpY2tzKDEpfXRpY2tzKCl7cmV0dXJuIHRoaXMuX2QzTG9nU2NhbGUudGlja3MoKX1leHRlbnRPZlZhbHVlcyh0KXtsZXQgcj10LmZpbHRlcihvPT5UQi5VdGlscy5NYXRoLmlzVmFsaWROdW1iZXIobykmJm8+MCksbj1yO2lmKHRoaXMuaWdub3JlT3V0bGllcigpKXtsZXQgYT1yLm1hcCh3NCkuc29ydCgoYyx1KT0+Yy11KSxzPXNhKGEsLjA1KSxsPXNhKGEsLjk1KTtuPWEuZmlsdGVyKGM9PmM+PXMmJmM8PWwpLm1hcChlc3QpfWxldCBpPWFhKG4pO3JldHVybiBpWzBdPT1udWxsfHxpWzFdPT1udWxsP1tdOml9fTt2YXIgQ2Q9RWUod2woKSwxKTt2YXIgbjA9RWUod2woKSwxKSxBQj1jbGFzcyBleHRlbmRzIG4wLkNvbXBvbmVudHMuU2VsZWN0aW9uQm94TGF5ZXJ7Y29uc3RydWN0b3IodCxyLG4pe3N1cGVyKCksdGhpcy5lYXNlRm49eHMsdGhpcy5fYW5pbWF0aW9uVGltZT03NTAsdGhpcy54U2NhbGUodCksdGhpcy55U2NhbGUociksdGhpcy5fZHJhZ0ludGVyYWN0aW9uPW5ldyBuMC5JbnRlcmFjdGlvbnMuRHJhZyx0aGlzLl9kb3VibGVDbGlja0ludGVyYWN0aW9uPW5ldyBuMC5JbnRlcmFjdGlvbnMuQ2xpY2ssdGhpcy5zZXR1cENhbGxiYWNrcygpLHRoaXMudW56b29tTWV0aG9kPW4sdGhpcy5vbkRldGFjaCgoKT0+e3RoaXMuX2RvdWJsZUNsaWNrSW50ZXJhY3Rpb24uZGV0YWNoRnJvbSh0aGlzKSx0aGlzLl9kcmFnSW50ZXJhY3Rpb24uZGV0YWNoRnJvbSh0aGlzKX0pLHRoaXMub25BbmNob3IoKCk9Pnt0aGlzLl9kb3VibGVDbGlja0ludGVyYWN0aW9uLmF0dGFjaFRvKHRoaXMpLHRoaXMuX2RyYWdJbnRlcmFjdGlvbi5hdHRhY2hUbyh0aGlzKX0pfWludGVyYWN0aW9uU3RhcnQodCl7dGhpcy5vblN0YXJ0PXR9aW50ZXJhY3Rpb25FbmQodCl7dGhpcy5vbkVuZD10fWRyYWdJbnRlcmFjdGlvbigpe3JldHVybiB0aGlzLl9kcmFnSW50ZXJhY3Rpb259c2V0dXBDYWxsYmFja3MoKXtsZXQgdD0hMTt0aGlzLl9kcmFnSW50ZXJhY3Rpb24ub25EcmFnU3RhcnQocj0+e3RoaXMuYm91bmRzKHt0b3BMZWZ0OnIsYm90dG9tUmlnaHQ6cn0pLHRoaXMub25TdGFydCgpfSksdGhpcy5fZHJhZ0ludGVyYWN0aW9uLm9uRHJhZygocixuKT0+e3RoaXMuYm91bmRzKHt0b3BMZWZ0OnIsYm90dG9tUmlnaHQ6bn0pLHRoaXMuYm94VmlzaWJsZSghMCksdD0hMH0pLHRoaXMuX2RyYWdJbnRlcmFjdGlvbi5vbkRyYWdFbmQoKHIsbik9Pnt0aGlzLmJveFZpc2libGUoITEpLHRoaXMuYm91bmRzKHt0b3BMZWZ0OnIsYm90dG9tUmlnaHQ6bn0pLHQ/dGhpcy56b29tKCk6dGhpcy5vbkVuZCgpLHQ9ITF9KSx0aGlzLl9kb3VibGVDbGlja0ludGVyYWN0aW9uLm9uRG91YmxlQ2xpY2sodGhpcy51bnpvb20uYmluZCh0aGlzKSl9YW5pbWF0aW9uVGltZSh0KXtpZih0PT1udWxsKXJldHVybiB0aGlzLl9hbmltYXRpb25UaW1lO2lmKHQ8MCl0aHJvdyBuZXcgRXJyb3IoImFuaW1hdGlvblRpbWUgY2Fubm90IGJlIG5lZ2F0aXZlIik7cmV0dXJuIHRoaXMuX2FuaW1hdGlvblRpbWU9dCx0aGlzfWVhc2UodCl7aWYodHlwZW9mIHQhPSJmdW5jdGlvbiIpdGhyb3cgbmV3IEVycm9yKCJlYXNlIGZ1bmN0aW9uIG11c3QgYmUgYSBmdW5jdGlvbiIpO3JldHVybih0KDApIT09MHx8dCgxKSE9PTEpJiZuMC5VdGlscy5XaW5kb3cud2FybigiRWFzaW5nIGZ1bmN0aW9uIGRvZXMgbm90IG1haW50YWluIGludmFyaWFudCBmKDApPT0wICYmIGYoMSk9PTEuIEJhZCBiZWhhdmlvciBtYXkgcmVzdWx0LiIpLHRoaXMuZWFzZUZuPXQsdGhpc316b29tKCl7bGV0IHQ9dGhpcy54RXh0ZW50KClbMF0udmFsdWVPZigpLHI9dGhpcy54RXh0ZW50KClbMV0udmFsdWVPZigpLG49dGhpcy55RXh0ZW50KClbMV0udmFsdWVPZigpLGk9dGhpcy55RXh0ZW50KClbMF0udmFsdWVPZigpO3Q9PT1yfHxuPT09aXx8dGhpcy5pbnRlcnBvbGF0ZVpvb20odCxyLG4saSl9dW56b29tKCl7bGV0IHQ9dGhpcy54U2NhbGUoKTt0Ll9kb21haW5NaW49bnVsbCx0Ll9kb21haW5NYXg9bnVsbDtsZXQgcj10Ll9nZXRFeHRlbnQoKTt0aGlzLnhTY2FsZSgpLmRvbWFpbihyKSx0aGlzLnVuem9vbU1ldGhvZCgpfWlzWm9vbWluZyh0KXt0aGlzLl9kcmFnSW50ZXJhY3Rpb24uZW5hYmxlZCghdCksdGhpcy5fZG91YmxlQ2xpY2tJbnRlcmFjdGlvbi5lbmFibGVkKCF0KX1pbnRlcnBvbGF0ZVpvb20odCxyLG4saSl7bGV0IG89dGhpcy54U2NhbGUoKS5kb21haW4oKVswXS52YWx1ZU9mKCksYT10aGlzLnhTY2FsZSgpLmRvbWFpbigpWzFdLnZhbHVlT2YoKSxzPXRoaXMueVNjYWxlKCkuZG9tYWluKClbMF0udmFsdWVPZigpLGw9dGhpcy55U2NhbGUoKS5kb21haW4oKVsxXS52YWx1ZU9mKCksYz10aGlzLmVhc2VGbix1PShwLGQsZyk9PnppKHAsZCkoYyhnKSk7dGhpcy5pc1pvb21pbmcoITApO2xldCBoPURhdGUubm93KCksZj0oKT0+e2xldCBkPURhdGUubm93KCktaCxnPXRoaXMuX2FuaW1hdGlvblRpbWU9PT0wPzE6TWF0aC5taW4oMSxkL3RoaXMuX2FuaW1hdGlvblRpbWUpLF89dShvLHQsZykseT11KGEscixnKSx4PXUocyxuLGcpLGI9dShsLGksZyk7dGhpcy54U2NhbGUoKS5kb21haW4oW18seV0pLHRoaXMueVNjYWxlKCkuZG9tYWluKFt4LGJdKSxnPDE/bjAuVXRpbHMuRE9NLnJlcXVlc3RBbmltYXRpb25GcmFtZVBvbHlmaWxsKGYpOih0aGlzLm9uRW5kKCksdGhpcy5pc1pvb21pbmcoITEpKX07ZigpfX07dmFyIHhhOyhmdW5jdGlvbihlKXtlW2UuTk9ORT0wXT0iTk9ORSIsZVtlLkRSQUdfWk9PTUlORz0xXT0iRFJBR19aT09NSU5HIixlW2UuUEFOTklORz0yXT0iUEFOTklORyJ9KSh4YXx8KHhhPXt9KSk7dmFyIHpmPWNsYXNzIGV4dGVuZHMgQ2QuQ29tcG9uZW50cy5Hcm91cHtjb25zdHJ1Y3Rvcih0LHIsbil7c3VwZXIoKSx0aGlzLnN0YXRlPXhhLk5PTkUsdGhpcy5wYW5TdGFydENhbGxiYWNrPW5ldyBDZC5VdGlscy5DYWxsYmFja1NldCx0aGlzLnBhbkVuZENhbGxiYWNrPW5ldyBDZC5VdGlscy5DYWxsYmFja1NldCx0aGlzLnBhblpvb209bmV3IENkLkludGVyYWN0aW9ucy5QYW5ab29tKHQsciksdGhpcy5wYW5ab29tLmRyYWdJbnRlcmFjdGlvbigpLm1vdXNlRmlsdGVyKG89PnpmLmlzUGFuS2V5KG8pJiZvLmJ1dHRvbj09PTApLHRoaXMucGFuWm9vbS53aGVlbEZpbHRlcih0aGlzLmNhblNjcm9sbFpvb20pLHRoaXMuZHJhZ1pvb21MYXllcj1uZXcgQUIodCxyLG4pLHRoaXMuZHJhZ1pvb21MYXllci5kcmFnSW50ZXJhY3Rpb24oKS5tb3VzZUZpbHRlcihvPT4hemYuaXNQYW5LZXkobykmJm8uYnV0dG9uPT09MCksdGhpcy5hcHBlbmQodGhpcy5kcmFnWm9vbUxheWVyKTtsZXQgaT10aGlzLm9uV2hlZWwuYmluZCh0aGlzKTt0aGlzLm9uQW5jaG9yKCgpPT57dGhpcy5fbW91c2VEaXNwYXRjaGVyPUNkLkRpc3BhdGNoZXJzLk1vdXNlLmdldERpc3BhdGNoZXIodGhpcyksdGhpcy5fbW91c2VEaXNwYXRjaGVyLm9uV2hlZWwoaSksdGhpcy5wYW5ab29tLmF0dGFjaFRvKHRoaXMpfSksdGhpcy5vbkRldGFjaCgoKT0+e3RoaXMucGFuWm9vbS5kZXRhY2hGcm9tKHRoaXMpLHRoaXMuX21vdXNlRGlzcGF0Y2hlciYmKHRoaXMuX21vdXNlRGlzcGF0Y2hlci5vZmZXaGVlbChpKSx0aGlzLl9tb3VzZURpc3BhdGNoZXI9bnVsbCl9KSx0aGlzLnBhblpvb20uZHJhZ0ludGVyYWN0aW9uKCkub25EcmFnU3RhcnQoKCk9Pnt0aGlzLnN0YXRlPT14YS5OT05FJiZ0aGlzLnNldFN0YXRlKHhhLlBBTk5JTkcpfSksdGhpcy5wYW5ab29tLmRyYWdJbnRlcmFjdGlvbigpLm9uRHJhZ0VuZCgoKT0+e3RoaXMuc3RhdGU9PXhhLlBBTk5JTkcmJnRoaXMuc2V0U3RhdGUoeGEuTk9ORSl9KSx0aGlzLmRyYWdab29tTGF5ZXIuZHJhZ0ludGVyYWN0aW9uKCkub25EcmFnU3RhcnQoKCk9Pnt0aGlzLnN0YXRlPT14YS5OT05FJiZ0aGlzLnNldFN0YXRlKHhhLkRSQUdfWk9PTUlORyl9KSx0aGlzLmRyYWdab29tTGF5ZXIuZHJhZ0ludGVyYWN0aW9uKCkub25EcmFnRW5kKCgpPT57dGhpcy5zdGF0ZT09eGEuRFJBR19aT09NSU5HJiZ0aGlzLnNldFN0YXRlKHhhLk5PTkUpfSl9b25XaGVlbCh0LHIpe2lmKHRoaXMuY2FuU2Nyb2xsWm9vbShyKSlyZXR1cm47bGV0IG49dGhpcy5lbGVtZW50KCk7aWYoIW4uc2VsZWN0KCIuaGVscCIpLmVtcHR5KCkpcmV0dXJuO2xldCBpPW4uYXBwZW5kKCJkaXYiKS5jbGFzc2VkKCJoZWxwIiwhMCk7aS5hcHBlbmQoInNwYW4iKS50ZXh0KCJBbHQgKyBTY3JvbGwgdG8gWm9vbSIpLGkub24oImFuaW1hdGlvbmVuZCIsKCk9PnZvaWQgaS5yZW1vdmUoKSl9c3RhdGljIGlzUGFuS2V5KHQpe3JldHVybiBCb29sZWFuKHQuYWx0S2V5KXx8Qm9vbGVhbih0LnNoaWZ0S2V5KX1jYW5TY3JvbGxab29tKHQpe3JldHVybiB0LmFsdEtleX1zZXRTdGF0ZSh0KXtpZih0aGlzLnN0YXRlPT10KXJldHVybjtsZXQgcj10aGlzLnN0YXRlO3RoaXMuc3RhdGU9dCx0aGlzLnJvb3QoKS5yZW1vdmVDbGFzcyh0aGlzLnN0YXRlQ2xhc3NOYW1lKHIpKSx0aGlzLnJvb3QoKS5hZGRDbGFzcyh0aGlzLnN0YXRlQ2xhc3NOYW1lKHQpKSxyPT14YS5QQU5OSU5HJiZ0aGlzLnBhbkVuZENhbGxiYWNrLmNhbGxDYWxsYmFja3MoKSx0PT14YS5QQU5OSU5HJiZ0aGlzLnBhblN0YXJ0Q2FsbGJhY2suY2FsbENhbGxiYWNrcygpfXN0YXRlQ2xhc3NOYW1lKHQpe3N3aXRjaCh0KXtjYXNlIHhhLlBBTk5JTkc6cmV0dXJuInBhbm5pbmciO2Nhc2UgeGEuRFJBR19aT09NSU5HOnJldHVybiJkcmFnLXpvb21pbmciO2Nhc2UgeGEuTk9ORTpkZWZhdWx0OnJldHVybiIifX1vblBhblN0YXJ0KHQpe3RoaXMucGFuU3RhcnRDYWxsYmFjay5hZGQodCl9b25QYW5FbmQodCl7dGhpcy5wYW5FbmRDYWxsYmFjay5hZGQodCl9b25TY3JvbGxab29tKHQpe3RoaXMucGFuWm9vbS5vblpvb21FbmQodCl9b25EcmFnWm9vbVN0YXJ0KHQpe3RoaXMuZHJhZ1pvb21MYXllci5pbnRlcmFjdGlvblN0YXJ0KHQpfW9uRHJhZ1pvb21FbmQodCl7dGhpcy5kcmFnWm9vbUxheWVyLmludGVyYWN0aW9uRW5kKHQpfX07dmFyIFBCOyhmdW5jdGlvbihlKXtlW2UuVEVYVD0wXT0iVEVYVCIsZVtlLkRPTT0xXT0iRE9NIn0pKFBCfHwoUEI9e30pKTt2YXIgRmY7KGZ1bmN0aW9uKGUpe2UuTE9HPSJsb2ciLGUuTElORUFSPSJsaW5lYXIifSkoRmZ8fChGZj17fSkpO3ZhciBWS3Q9MjAsRFM9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4saSxvLGEscyxsLGMsdSxoKXt0aGlzLmRpcnR5RGF0YXNldHM9bmV3IFNldCx0aGlzLnNlcmllc05hbWVzPVtdLHRoaXMubmFtZTJkYXRhc2V0cz17fSx0aGlzLmNvbG9yU2NhbGU9aSx0aGlzLnRvb2x0aXA9byx0aGlzLmRhdGFzZXRzPVtdLHRoaXMuX2lnbm9yZVlPdXRsaWVycz0hMSx0aGlzLmxhc3RQb2ludHNEYXRhc2V0PW5ldyBNbi5EYXRhc2V0LHRoaXMubmFuRGF0YXNldD1uZXcgTW4uRGF0YXNldCx0aGlzLnlWYWx1ZUFjY2Vzc29yPXIsdGhpcy5zeW1ib2xGdW5jdGlvbj11LHRoaXMuX2RlZmF1bHRYUmFuZ2U9bCx0aGlzLl9kZWZhdWx0WVJhbmdlPWMsdGhpcy50b29sdGlwQ29sdW1ucz1hLHRoaXMuYnVpbGRDaGFydCh0LHIsbixzLGgpfWJ1aWxkQ2hhcnQodCxyLG4saSxvKXt0aGlzLmRlc3Ryb3koKTtsZXQgYT10KCk7dGhpcy54QWNjZXNzb3I9YS5hY2Nlc3Nvcix0aGlzLnhTY2FsZT1hLnNjYWxlLHRoaXMueEF4aXM9YS5heGlzLHRoaXMueEF4aXMubWFyZ2luKDEpLnRpY2tMYWJlbFBhZGRpbmcoMyksbyYmdGhpcy54QXhpcy5mb3JtYXR0ZXIobyksdGhpcy55U2NhbGU9RFMuZ2V0WVNjYWxlRnJvbVR5cGUobiksdGhpcy55U2NhbGUuc2V0VmFsdWVQcm92aWRlckZvckRvbWFpbigoKT0+dGhpcy5nZXRWYWx1ZXNGb3JZQXhpc0RvbWFpbkNvbXB1dGUoKSksdGhpcy55QXhpcz1uZXcgTW4uQXhlcy5OdW1lcmljKHRoaXMueVNjYWxlLCJsZWZ0Iik7bGV0IHM9V3UoZEIpO3RoaXMueUF4aXMubWFyZ2luKDApLnRpY2tMYWJlbFBhZGRpbmcoNSkuZm9ybWF0dGVyKHMpLHRoaXMueUF4aXMudXNlc1RleHRXaWR0aEFwcHJveGltYXRpb24oITApLHRoaXMuZmlsbEFyZWE9aTtsZXQgbD1uZXcgemYodGhpcy54U2NhbGUsdGhpcy55U2NhbGUsKCk9PnRoaXMucmVzZXREb21haW4oKSk7dGhpcy50b29sdGlwSW50ZXJhY3Rpb249dGhpcy5jcmVhdGVUb29sdGlwSW50ZXJhY3Rpb24obCksdGhpcy50b29sdGlwUG9pbnRzQ29tcG9uZW50PW5ldyBNbi5Db21wb25lbnQ7bGV0IGM9dGhpcy5idWlsZFBsb3QodGhpcy54U2NhbGUsdGhpcy55U2NhbGUsaSk7dGhpcy5ncmlkbGluZXM9bmV3IE1uLkNvbXBvbmVudHMuR3JpZGxpbmVzKHRoaXMueFNjYWxlLHRoaXMueVNjYWxlKTtsZXQgdT1udWxsO24hPT1GZi5MT0cmJih1PW5ldyBNbi5Db21wb25lbnRzLkd1aWRlTGluZUxheWVyKCJob3Jpem9udGFsIiksdS5zY2FsZSh0aGlzLnlTY2FsZSkudmFsdWUoMCkpO2xldCBoPW5ldyBNbi5Db21wb25lbnRzLkd1aWRlTGluZUxheWVyKCJ2ZXJ0aWNhbCIpO2guc2NhbGUodGhpcy54U2NhbGUpLnZhbHVlKDApLHRoaXMuY2VudGVyPW5ldyBNbi5Db21wb25lbnRzLkdyb3VwKFt0aGlzLmdyaWRsaW5lcyx1LGgsYyx0aGlzLnRvb2x0aXBQb2ludHNDb21wb25lbnQsbF0pLHRoaXMuY2VudGVyLmFkZENsYXNzKCJtYWluIiksdGhpcy5vdXRlcj1uZXcgTW4uQ29tcG9uZW50cy5UYWJsZShbW3RoaXMueUF4aXMsdGhpcy5jZW50ZXJdLFtudWxsLHRoaXMueEF4aXNdXSl9YnVpbGRQbG90KHQscixuKXtuJiYodGhpcy5tYXJnaW5BcmVhUGxvdD1uZXcgTW4uUGxvdHMuQXJlYSx0aGlzLm1hcmdpbkFyZWFQbG90LngodGhpcy54QWNjZXNzb3IsdCksdGhpcy5tYXJnaW5BcmVhUGxvdC55KG4uaGlnaGVyQWNjZXNzb3IsciksdGhpcy5tYXJnaW5BcmVhUGxvdC55MChuLmxvd2VyQWNjZXNzb3IpLHRoaXMubWFyZ2luQXJlYVBsb3QuYXR0cigiZmlsbCIsKGMsdSxoKT0+dGhpcy5jb2xvclNjYWxlLnNjYWxlKGgubWV0YWRhdGEoKS5uYW1lKSksdGhpcy5tYXJnaW5BcmVhUGxvdC5hdHRyKCJmaWxsLW9wYWNpdHkiLC4zKSx0aGlzLm1hcmdpbkFyZWFQbG90LmF0dHIoInN0cm9rZS13aWR0aCIsMCkpLHRoaXMuc21vb3RoZWRBY2Nlc3Nvcj1jPT5jLnNtb290aGVkO2xldCBpPW5ldyBNbi5QbG90cy5MaW5lO2kueCh0aGlzLnhBY2Nlc3Nvcix0KSxpLnkodGhpcy55VmFsdWVBY2Nlc3NvcixyKSxpLmF0dHIoInN0cm9rZSIsKGMsdSxoKT0+dGhpcy5jb2xvclNjYWxlLnNjYWxlKGgubWV0YWRhdGEoKS5uYW1lKSksdGhpcy5saW5lUGxvdD1pLHRoaXMuc2V0dXBUb29sdGlwcyhpKTtsZXQgbz1uZXcgTW4uUGxvdHMuTGluZTtpZihvLngodGhpcy54QWNjZXNzb3IsdCksby55KHRoaXMuc21vb3RoZWRBY2Nlc3NvcixyKSxvLmF0dHIoInN0cm9rZSIsKGMsdSxoKT0+dGhpcy5jb2xvclNjYWxlLnNjYWxlKGgubWV0YWRhdGEoKS5uYW1lKSksdGhpcy5zbW9vdGhMaW5lUGxvdD1vLHRoaXMuc3ltYm9sRnVuY3Rpb24pe2xldCBjPW5ldyBNbi5QbG90cy5TY2F0dGVyO2MueCh0aGlzLnhBY2Nlc3Nvcix0KSxjLnkodGhpcy55VmFsdWVBY2Nlc3NvcixyKSxjLmF0dHIoImZpbGwiLCh1LGgsZik9PnRoaXMuY29sb3JTY2FsZS5zY2FsZShmLm1ldGFkYXRhKCkubmFtZSkpLGMuYXR0cigib3BhY2l0eSIsMSksYy5zaXplKGdCKjIpLGMuc3ltYm9sKCh1LGgsZik9PnRoaXMuc3ltYm9sRnVuY3Rpb24oZi5tZXRhZGF0YSgpLm5hbWUpKSx0aGlzLm1hcmtlcnNTY2F0dGVyUGxvdD1jfWxldCBhPW5ldyBNbi5QbG90cy5TY2F0dGVyO2EueCh0aGlzLnhBY2Nlc3Nvcix0KSxhLnkodGhpcy55VmFsdWVBY2Nlc3NvcixyKSxhLmF0dHIoImZpbGwiLGM9PnRoaXMuY29sb3JTY2FsZS5zY2FsZShjLm5hbWUpKSxhLmF0dHIoIm9wYWNpdHkiLDEpLGEuc2l6ZShnQioyKSxhLmRhdGFzZXRzKFt0aGlzLmxhc3RQb2ludHNEYXRhc2V0XSksdGhpcy5zY2F0dGVyUGxvdD1hO2xldCBzPW5ldyBNbi5QbG90cy5TY2F0dGVyO3MueCh0aGlzLnhBY2Nlc3Nvcix0KSxzLnkoYz0+Yy5kaXNwbGF5WSxyKSxzLmF0dHIoImZpbGwiLGM9PnRoaXMuY29sb3JTY2FsZS5zY2FsZShjLm5hbWUpKSxzLmF0dHIoIm9wYWNpdHkiLDEpLHMuc2l6ZShMS3QqMikscy5kYXRhc2V0cyhbdGhpcy5uYW5EYXRhc2V0XSkscy5zeW1ib2woTW4uU3ltYm9sRmFjdG9yaWVzLnRyaWFuZ2xlKSx0aGlzLm5hbkRpc3BsYXk9cztsZXQgbD1bcyxhLG8saV07cmV0dXJuIHRoaXMubWFyZ2luQXJlYVBsb3QmJmwucHVzaCh0aGlzLm1hcmdpbkFyZWFQbG90KSx0aGlzLm1hcmtlcnNTY2F0dGVyUGxvdCYmbC5wdXNoKHRoaXMubWFya2Vyc1NjYXR0ZXJQbG90KSxuZXcgTW4uQ29tcG9uZW50cy5Hcm91cChsKX1pZ25vcmVZT3V0bGllcnModCl7dCE9PXRoaXMuX2lnbm9yZVlPdXRsaWVycyYmKHRoaXMuX2lnbm9yZVlPdXRsaWVycz10LHRoaXMudXBkYXRlU3BlY2lhbERhdGFzZXRzKCksdGhpcy55U2NhbGUuaWdub3JlT3V0bGllcih0KSx0aGlzLnJlc2V0WURvbWFpbigpKX1nZXRWYWx1ZXNGb3JZQXhpc0RvbWFpbkNvbXB1dGUoKXtsZXQgdD10aGlzLmdldEFjY2Vzc29yc0ZvckNvbXB1dGluZ1lSYW5nZSgpLHI9bj0+dC5tYXAoaT0+bi5kYXRhKCkubWFwKG89PmkobywtMSxuKSkpO3JldHVybiB6cy5mbGF0dGVuRGVlcCh0aGlzLmRhdGFzZXRzLm1hcChyKSkuZmlsdGVyKGlzRmluaXRlKX11cGRhdGVTcGVjaWFsRGF0YXNldHMoKXtsZXQgdD10aGlzLmdldFlBeGlzQWNjZXNzb3IoKSxyPXRoaXMuZGF0YXNldHMubWFwKG89PntsZXQgYT1udWxsLHM9by5kYXRhKCkuZmlsdGVyKGw9PiFpc05hTih0KGwsLTEsbykpKTtpZihzLmxlbmd0aD4wKXtsZXQgbD1zLmxlbmd0aC0xO2E9c1tsXSxhLm5hbWU9by5tZXRhZGF0YSgpLm5hbWUsYS5yZWxhdGl2ZT1yMChhLC0xLG8pfXJldHVybiBhfSkuZmlsdGVyKG89Pm8hPW51bGwpO3RoaXMubGFzdFBvaW50c0RhdGFzZXQuZGF0YShyKSx0aGlzLm1hcmtlcnNTY2F0dGVyUGxvdCYmdGhpcy5tYXJrZXJzU2NhdHRlclBsb3QuZGF0YXNldHModGhpcy5kYXRhc2V0cy5tYXAodGhpcy5jcmVhdGVTYW1wbGVkRGF0YXNldEZvck1hcmtlcnMpKTtsZXQgbj1vPT57bGV0IGE9bnVsbCxzPW8uZGF0YSgpLGw9MDtmb3IoO2w8cy5sZW5ndGgmJmE9PW51bGw7KWlzTmFOKHQoc1tsXSwtMSxvKSl8fChhPXQoc1tsXSwtMSxvKSksbCsrO2E9PW51bGwmJihhPTApO2xldCBjPVtdO2ZvcihsPTA7bDxzLmxlbmd0aDtsKyspaXNOYU4odChzW2xdLC0xLG8pKT8oc1tsXS5uYW1lPW8ubWV0YWRhdGEoKS5uYW1lLHNbbF0uZGlzcGxheVk9YSxzW2xdLnJlbGF0aXZlPXIwKHNbbF0sLTEsbyksYy5wdXNoKHNbbF0pKTphPXQoc1tsXSwtMSxvKTtyZXR1cm4gY30saT16cy5mbGF0dGVuKHRoaXMuZGF0YXNldHMubWFwKG4pKTt0aGlzLm5hbkRhdGFzZXQuZGF0YShpKX1yZXNldERvbWFpbigpe3RoaXMucmVzZXRYRG9tYWluKCksdGhpcy5yZXNldFlEb21haW4oKX1yZXNldFhEb21haW4oKXtsZXQgdDtpZih0aGlzLl9kZWZhdWx0WFJhbmdlIT1udWxsKXQ9dGhpcy5fZGVmYXVsdFhSYW5nZTtlbHNle2xldCByPXRoaXMueFNjYWxlO3IuX2RvbWFpbk1pbj1udWxsLHIuX2RvbWFpbk1heD1udWxsLHQ9ci5fZ2V0RXh0ZW50KCl9dGhpcy54U2NhbGUuZG9tYWluKHQpfXJlc2V0WURvbWFpbigpe3RoaXMuX2RlZmF1bHRZUmFuZ2UhPW51bGw/dGhpcy55U2NhbGUuZG9tYWluKHRoaXMuX2RlZmF1bHRZUmFuZ2UpOih0aGlzLnlTY2FsZS5hdXRvRG9tYWluKCksdGhpcy55U2NhbGUuZG9tYWluKHRoaXMueVNjYWxlLmRvbWFpbigpKSl9Z2V0QWNjZXNzb3JzRm9yQ29tcHV0aW5nWVJhbmdlKCl7bGV0IHQ9W3RoaXMuZ2V0WUF4aXNBY2Nlc3NvcigpXTtyZXR1cm4gdGhpcy5maWxsQXJlYSYmdC5wdXNoKHRoaXMuZmlsbEFyZWEubG93ZXJBY2Nlc3Nvcix0aGlzLmZpbGxBcmVhLmhpZ2hlckFjY2Vzc29yKSx0fWdldFlBeGlzQWNjZXNzb3IoKXtyZXR1cm4gdGhpcy5zbW9vdGhpbmdFbmFibGVkP3RoaXMuc21vb3RoZWRBY2Nlc3Nvcjp0aGlzLnlWYWx1ZUFjY2Vzc29yfWNyZWF0ZVRvb2x0aXBJbnRlcmFjdGlvbih0KXtsZXQgcj1uZXcgd0Isbj0oKT0+e3IuZW5hYmxlZCghMSksdGhpcy5oaWRlVG9vbHRpcHMoKX0saT0oKT0+ci5lbmFibGVkKCEwKTtyZXR1cm4gdC5vblBhblN0YXJ0KG4pLHQub25EcmFnWm9vbVN0YXJ0KG4pLHQub25QYW5FbmQoaSksdC5vbkRyYWdab29tRW5kKGkpLHQub25TY3JvbGxab29tKCgpPT50aGlzLnVwZGF0ZVRvb2x0aXBDb250ZW50KHRoaXMuX2xhc3RNb3VzZVBvc2l0aW9uKSksci5vblBvaW50ZXJNb3ZlKG89Pnt0aGlzLl9sYXN0TW91c2VQb3NpdGlvbj1vLHRoaXMudXBkYXRlVG9vbHRpcENvbnRlbnQobyl9KSxyLm9uUG9pbnRlckV4aXQoKCk9PnRoaXMuaGlkZVRvb2x0aXBzKCkpLHJ9dXBkYXRlVG9vbHRpcENvbnRlbnQodCl7IXRoaXMubGluZVBsb3R8fCh3aW5kb3cuY2FuY2VsQW5pbWF0aW9uRnJhbWUodGhpcy5fdG9vbHRpcFVwZGF0ZUFuaW1hdGlvbkZyYW1lKSx0aGlzLl90b29sdGlwVXBkYXRlQW5pbWF0aW9uRnJhbWU9d2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZSgoKT0+e2xldCByPXt4OnQueCx5OnQueX0sbj10aGlzLmdyaWRsaW5lcy5jb250ZW50KCkubm9kZSgpLmdldEJCb3goKSxpPXRoaXMubGluZVBsb3QuZGF0YXNldHMoKS5tYXAobD0+dGhpcy5maW5kQ2xvc2VzdFBvaW50KHIsbCkpLmZpbHRlcihsPT5Cb29sZWFuKGwpKSxvPU1uLlV0aWxzLkRPTS5pbnRlcnNlY3RzQkJveCxhPWkuZmlsdGVyKGw9Pm8obC54LGwueSxuKXx8aXNOYU4odGhpcy55VmFsdWVBY2Nlc3NvcihsLmRhdHVtLDAsbC5kYXRhc2V0KSkpLHM9YS5maWx0ZXIobD0+IWlzTmFOKHRoaXMueVZhbHVlQWNjZXNzb3IobC5kYXR1bSwwLGwuZGF0YXNldCkpKTtpZihpLmxlbmd0aCE9PTApe3RoaXMuc2NhdHRlclBsb3QuYXR0cigiZGlzcGxheSIsIm5vbmUiKTtsZXQgbD10aGlzLnRvb2x0aXBQb2ludHNDb21wb25lbnQuY29udGVudCgpLnNlbGVjdEFsbCgiLnBvaW50IikuZGF0YShzLGM9PmMuZGF0YXNldC5tZXRhZGF0YSgpLm5hbWUpO2wuZW50ZXIoKS5hcHBlbmQoImNpcmNsZSIpLmNsYXNzZWQoInBvaW50IiwhMCksbC5hdHRyKCJyIixnQikuYXR0cigiY3giLGM9PmMueCkuYXR0cigiY3kiLGM9PmMueSkuc3R5bGUoInN0cm9rZSIsIm5vbmUiKS5hdHRyKCJmaWxsIixjPT50aGlzLmNvbG9yU2NhbGUuc2NhbGUoYy5kYXRhc2V0Lm1ldGFkYXRhKCkubmFtZSkpLGwuZXhpdCgpLnJlbW92ZSgpLHRoaXMuZHJhd1Rvb2x0aXBzKGEscix0aGlzLnRvb2x0aXBDb2x1bW5zKX1lbHNlIHRoaXMuaGlkZVRvb2x0aXBzKCl9KSl9aGlkZVRvb2x0aXBzKCl7d2luZG93LmNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuX3Rvb2x0aXBVcGRhdGVBbmltYXRpb25GcmFtZSksdGhpcy50b29sdGlwLmhpZGUoKSx0aGlzLnNjYXR0ZXJQbG90LmF0dHIoImRpc3BsYXkiLCJibG9jayIpLHRoaXMudG9vbHRpcFBvaW50c0NvbXBvbmVudC5jb250ZW50KCkuc2VsZWN0QWxsKCIucG9pbnQiKS5yZW1vdmUoKX1zZXR1cFRvb2x0aXBzKHQpe3Qub25EZXRhY2goKCk9Pnt0aGlzLnRvb2x0aXBJbnRlcmFjdGlvbi5kZXRhY2hGcm9tKHQpLHRoaXMudG9vbHRpcEludGVyYWN0aW9uLmVuYWJsZWQoITEpfSksdC5vbkFuY2hvcigoKT0+e3RoaXMudG9vbHRpcEludGVyYWN0aW9uLmF0dGFjaFRvKHQpLHRoaXMudG9vbHRpcEludGVyYWN0aW9uLmVuYWJsZWQoITApfSl9ZHJhd1Rvb2x0aXBzKHQscixuKXtpZighdC5sZW5ndGgpe3RoaXMudG9vbHRpcC5oaWRlKCk7cmV0dXJufWxldHtjb2xvclNjYWxlOml9PXRoaXM7bj1be3RpdGxlOiIiLHN0YXRpYzohMSxldmFsVHlwZTpQQi5ET00sZXZhbHVhdGUoZCl7cmV0dXJuIEh0KHRoaXMpLnNlbGVjdCgic3BhbiIpLnN0eWxlKCJiYWNrZ3JvdW5kLWNvbG9yIiwoKT0+aS5zY2FsZShkLmRhdGFzZXQubWV0YWRhdGEoKS5uYW1lKSksIiJ9LGVudGVyKGQpe0h0KHRoaXMpLmFwcGVuZCgic3BhbiIpLmNsYXNzZWQoInN3YXRjaCIsITApLnN0eWxlKCJiYWNrZ3JvdW5kLWNvbG9yIiwoKT0+aS5zY2FsZShkLmRhdGFzZXQubWV0YWRhdGEoKS5uYW1lKSl9fSwuLi5uXTtsZXQgYT1XdShlMCkscz1kPT5NYXRoLnBvdyhkLngtci54LDIpK01hdGgucG93KGQueS1yLnksMiksbD16cy5taW4odC5tYXAocykpLGM9dGhpcy5zbW9vdGhpbmdFbmFibGVkP3RoaXMuc21vb3RoZWRBY2Nlc3Nvcjp0aGlzLnlWYWx1ZUFjY2Vzc29yO3RoaXMudG9vbHRpcFNvcnRpbmdNZXRob2Q9PT0iYXNjZW5kaW5nIj90PXpzLnNvcnRCeSh0LGQ9PmMoZC5kYXR1bSwtMSxkLmRhdGFzZXQpKTp0aGlzLnRvb2x0aXBTb3J0aW5nTWV0aG9kPT09ImRlc2NlbmRpbmciP3Q9enMuc29ydEJ5KHQsZD0+YyhkLmRhdHVtLC0xLGQuZGF0YXNldCkpLnJldmVyc2UoKTp0aGlzLnRvb2x0aXBTb3J0aW5nTWV0aG9kPT09Im5lYXJlc3QiP3Q9enMuc29ydEJ5KHQscyk6dD10LnNsaWNlKDApLnJldmVyc2UoKTtsZXQgdT10aGlzLGg9SHQodGhpcy50b29sdGlwLmNvbnRlbnQoKSkuc2VsZWN0KCJ0YWJsZSIpLGY9aC5zZWxlY3QoInRoZWFkIikuc2VsZWN0QWxsKCJ0aCIpLmRhdGEobiwoZCxnLF8pPT5kLnRpdGxlKTtmLmVudGVyKCkuYXBwZW5kKCJ0aCIpLnRleHQoZD0+ZC50aXRsZSkubm9kZXMoKSxmLmV4aXQoKS5yZW1vdmUoKTtsZXQgcD1oLnNlbGVjdCgidGJvZHkiKS5zZWxlY3RBbGwoInRyIikuZGF0YSh0LChkLGcsXyk9PmQuZGF0YXNldC5tZXRhZGF0YSgpLm5hbWUpO3AuY2xhc3NlZCgiZGlzdGFudCIsZD0+e2xldCBnPWQuZGF0YXNldC5kYXRhKClbMF0sXz16cy5sYXN0KGQuZGF0YXNldC5kYXRhKCkpLHk9dGhpcy54U2NhbGUuc2NhbGUodGhpcy54QWNjZXNzb3IoZywwLGQuZGF0YXNldCkpLHg9dGhpcy54U2NhbGUuc2NhbGUodGhpcy54QWNjZXNzb3IoXywwLGQuZGF0YXNldCkpLGI9dGhpcy5zbW9vdGhpbmdFbmFibGVkP2QuZGF0dW0uc21vb3RoZWQ6dGhpcy55VmFsdWVBY2Nlc3NvcihkLmRhdHVtLDAsZC5kYXRhc2V0KTtyZXR1cm4gci54PHl8fHIueD54fHxpc05hTihiKX0pLmNsYXNzZWQoImNsb3Nlc3QiLGQ9PnMoZCk9PT1sKS5lYWNoKGZ1bmN0aW9uKGQpe3UuZHJhd1Rvb2x0aXBSb3codGhpcyxuLGQpfSkub3JkZXIoKSxwLmV4aXQoKS5yZW1vdmUoKSxwLmVudGVyKCkuYXBwZW5kKCJ0ciIpLmVhY2goZnVuY3Rpb24oZCl7dS5kcmF3VG9vbHRpcFJvdyh0aGlzLG4sZCl9KS5ub2RlcygpLHRoaXMudG9vbHRpcC51cGRhdGVBbmRQb3NpdGlvbih0aGlzLnRhcmdldFNWRy5ub2RlKCkpfWRyYXdUb29sdGlwUm93KHQscixuKXtsZXQgaT10aGlzLG89SHQodCkuc2VsZWN0QWxsKCJ0ZCIpLmRhdGEocik7by5lYWNoKGZ1bmN0aW9uKGEpe2Euc3RhdGljfHxpLmRyYXdUb29sdGlwQ29sdW1uLmNhbGwoaSx0aGlzLGEsbil9KSxvLmV4aXQoKS5yZW1vdmUoKSxvLmVudGVyKCkuYXBwZW5kKCJ0ZCIpLmVhY2goZnVuY3Rpb24oYSl7ImVudGVyImluIGEmJmEuZW50ZXIuY2FsbCh0aGlzLG4pLGkuZHJhd1Rvb2x0aXBDb2x1bW4uY2FsbChpLHRoaXMsYSxuKX0pfWRyYXdUb29sdGlwQ29sdW1uKHQscixuKXtsZXR7c21vb3RoaW5nRW5hYmxlZDppfT10aGlzOyJldmFsVHlwZSJpbiByJiZyLmV2YWxUeXBlPT1QQi5ET00/ci5ldmFsdWF0ZS5jYWxsKHQsbix7c21vb3RoaW5nRW5hYmxlZDppfSk6SHQodCkudGV4dChyLmV2YWx1YXRlLmNhbGwodCxuLHtzbW9vdGhpbmdFbmFibGVkOml9KSl9ZmluZENsb3Nlc3RQb2ludCh0LHIpe2xldCBuPXIuZGF0YSgpLm1hcCgocyxsKT0+dGhpcy54U2NhbGUuc2NhbGUodGhpcy54QWNjZXNzb3IocyxsLHIpKSksaT16cy5zb3J0ZWRJbmRleChuLHQueCk7aWYobi5sZW5ndGg9PTApcmV0dXJuIG51bGw7aWYoaT09PW4ubGVuZ3RoKWk9aS0xO2Vsc2UgaWYoaSE9PTApe2xldCBzPU1hdGguYWJzKG5baS0xXS10LngpLGw9TWF0aC5hYnMobltpXS10LngpO2k9czxsP2ktMTppfWxldCBvPXIuZGF0YSgpW2ldLGE9dGhpcy5zbW9vdGhpbmdFbmFibGVkP3RoaXMuc21vb3RoZWRBY2Nlc3NvcihvLGkscik6dGhpcy55VmFsdWVBY2Nlc3NvcihvLGkscik7cmV0dXJue3g6bltpXSx5OnRoaXMueVNjYWxlLnNjYWxlKGEpLGRhdHVtOm8sZGF0YXNldDpyfX1yZXNtb290aERhdGFzZXQodCl7bGV0IHI9dC5kYXRhKCksbj10aGlzLnNtb290aGluZ1dlaWdodCxpPXIubGVuZ3RoPjA/MDpOYU4sbz0wLGE9ci5tYXAoKGwsYyk9PnRoaXMueVZhbHVlQWNjZXNzb3IobCxjLHQpKSxzPWEuZXZlcnkobD0+bD09YVswXSk7ci5mb3JFYWNoKChsLGMpPT57bGV0IHU9YVtjXTtpZihzfHwhTnVtYmVyLmlzRmluaXRlKHUpKWwuc21vb3RoZWQ9dTtlbHNle2k9aSpuKygxLW4pKnUsbysrO2xldCBoPTE7biE9PTEmJihoPTEtTWF0aC5wb3cobixvKSksbC5zbW9vdGhlZD1pL2h9fSl9Z2V0RGF0YXNldCh0KXtyZXR1cm4gdGhpcy5uYW1lMmRhdGFzZXRzW3RdPT09dm9pZCAwJiYodGhpcy5uYW1lMmRhdGFzZXRzW3RdPW5ldyBNbi5EYXRhc2V0KFtdLHtuYW1lOnQsbWV0YTpudWxsfSkpLHRoaXMubmFtZTJkYXRhc2V0c1t0XX1zdGF0aWMgZ2V0WVNjYWxlRnJvbVR5cGUodCl7aWYodD09PUZmLkxPRylyZXR1cm4gbmV3IENCO2lmKHQ9PT1GZi5MSU5FQVIpcmV0dXJuIG5ldyBTQjt0aHJvdyBuZXcgRXJyb3IoIlVucmVjb2duaXplZCB5U2NhbGUgdHlwZSAiK3QpfXNldFZpc2libGVTZXJpZXModCl7dGhpcy5kaXNhYmxlQ2hhbmdlcygpLHQ9dC5zb3J0KCksdC5yZXZlcnNlKCksdGhpcy5zZXJpZXNOYW1lcz10fWRpc2FibGVDaGFuZ2VzKCl7dGhpcy5kaXJ0eURhdGFzZXRzLnNpemV8fCh0aGlzLmxpbmVQbG90LmRhdGFzZXRzKFtdKSx0aGlzLnNtb290aExpbmVQbG90JiZ0aGlzLnNtb290aExpbmVQbG90LmRhdGFzZXRzKFtdKSx0aGlzLm1hcmdpbkFyZWFQbG90JiZ0aGlzLm1hcmdpbkFyZWFQbG90LmRhdGFzZXRzKFtdKSl9Y29tbWl0Q2hhbmdlcygpe3RoaXMuZGF0YXNldHM9dGhpcy5zZXJpZXNOYW1lcy5tYXAodD0+dGhpcy5nZXREYXRhc2V0KHQpKSxbLi4udGhpcy5kaXJ0eURhdGFzZXRzXS5mb3JFYWNoKHQ9Pnt0aGlzLnNtb290aGluZ0VuYWJsZWQmJnRoaXMucmVzbW9vdGhEYXRhc2V0KHRoaXMuZ2V0RGF0YXNldCh0KSl9KSx0aGlzLnVwZGF0ZVNwZWNpYWxEYXRhc2V0cygpLHRoaXMubGluZVBsb3QuZGF0YXNldHModGhpcy5kYXRhc2V0cyksdGhpcy5zbW9vdGhpbmdFbmFibGVkJiZ0aGlzLnNtb290aExpbmVQbG90LmRhdGFzZXRzKHRoaXMuZGF0YXNldHMpLHRoaXMubWFyZ2luQXJlYVBsb3QmJnRoaXMubWFyZ2luQXJlYVBsb3QuZGF0YXNldHModGhpcy5kYXRhc2V0cyksdGhpcy5tZWFzdXJlQkJveEFuZE1heWJlSW52YWxpZGF0ZUxheW91dEluUmFmKCksdGhpcy5kaXJ0eURhdGFzZXRzLmNsZWFyKCl9Y3JlYXRlU2FtcGxlZERhdGFzZXRGb3JNYXJrZXJzKHQpe2xldCByPXQuZGF0YSgpO2lmKHIubGVuZ3RoPD1WS3QpcmV0dXJuIHQ7bGV0IG49TWF0aC5jZWlsKHIubGVuZ3RoL1ZLdCksaT1uZXcgQXJyYXkoTWF0aC5mbG9vcihyLmxlbmd0aC9uKSk7Zm9yKGxldCBvPTAsYT0wO288aS5sZW5ndGg7bysrLGErPW4paVtvXT1yW2FdO3JldHVybiBuZXcgTW4uRGF0YXNldChpLHQubWV0YWRhdGEoKSl9c2V0U2VyaWVzRGF0YSh0LHIpe3RoaXMuZGlzYWJsZUNoYW5nZXMoKSx0aGlzLmdldERhdGFzZXQodCkuZGF0YShyKSx0aGlzLmRpcnR5RGF0YXNldHMuYWRkKHQpfXNldFNlcmllc01ldGFkYXRhKHQscil7dGhpcy5kaXNhYmxlQ2hhbmdlcygpLHRoaXMuZ2V0RGF0YXNldCh0KS5tZXRhZGF0YShNeChLbCh7fSx0aGlzLmdldERhdGFzZXQodCkubWV0YWRhdGEoKSkse21ldGE6cn0pKSx0aGlzLmRpcnR5RGF0YXNldHMuYWRkKHQpfXNtb290aGluZ1VwZGF0ZSh0KXt0aGlzLnNtb290aGluZ1dlaWdodD10LHRoaXMuZGF0YXNldHMuZm9yRWFjaChyPT50aGlzLnJlc21vb3RoRGF0YXNldChyKSksdGhpcy5zbW9vdGhpbmdFbmFibGVkfHwodGhpcy5saW5lUGxvdC5hZGRDbGFzcygiZ2hvc3QiKSx0aGlzLnNjYXR0ZXJQbG90LnkodGhpcy5zbW9vdGhlZEFjY2Vzc29yLHRoaXMueVNjYWxlKSx0aGlzLnNtb290aGluZ0VuYWJsZWQ9ITAsdGhpcy5zbW9vdGhMaW5lUGxvdC5kYXRhc2V0cyh0aGlzLmRhdGFzZXRzKSksdGhpcy5tYXJrZXJzU2NhdHRlclBsb3QmJnRoaXMubWFya2Vyc1NjYXR0ZXJQbG90LnkodGhpcy5nZXRZQXhpc0FjY2Vzc29yKCksdGhpcy55U2NhbGUpLHRoaXMudXBkYXRlU3BlY2lhbERhdGFzZXRzKCl9c21vb3RoaW5nRGlzYWJsZSgpe3RoaXMuc21vb3RoaW5nRW5hYmxlZCYmKHRoaXMubGluZVBsb3QucmVtb3ZlQ2xhc3MoImdob3N0IiksdGhpcy5zY2F0dGVyUGxvdC55KHRoaXMueVZhbHVlQWNjZXNzb3IsdGhpcy55U2NhbGUpLHRoaXMuc21vb3RoTGluZVBsb3QuZGF0YXNldHMoW10pLHRoaXMuc21vb3RoaW5nRW5hYmxlZD0hMSx0aGlzLnVwZGF0ZVNwZWNpYWxEYXRhc2V0cygpKSx0aGlzLm1hcmtlcnNTY2F0dGVyUGxvdCYmdGhpcy5tYXJrZXJzU2NhdHRlclBsb3QueSh0aGlzLmdldFlBeGlzQWNjZXNzb3IoKSx0aGlzLnlTY2FsZSl9c2V0Q29sb3JTY2FsZSh0KXt0aGlzLmNvbG9yU2NhbGU9dH1zZXRUb29sdGlwQ29sdW1ucyh0KXt0aGlzLnRvb2x0aXBDb2x1bW5zPXR9c2V0VG9vbHRpcFNvcnRpbmdNZXRob2QodCl7dGhpcy50b29sdGlwU29ydGluZ01ldGhvZD10fXJlbmRlclRvKHQpe3RoaXMudGFyZ2V0U1ZHPXQsdGhpcy5vdXRlci5yZW5kZXJUbyh0KSx0aGlzLl9kZWZhdWx0WFJhbmdlIT1udWxsJiZ0aGlzLnJlc2V0WERvbWFpbigpLHRoaXMuX2RlZmF1bHRZUmFuZ2UhPW51bGwmJnRoaXMucmVzZXRZRG9tYWluKCksdGhpcy5tZWFzdXJlQkJveEFuZE1heWJlSW52YWxpZGF0ZUxheW91dEluUmFmKCl9cmVkcmF3KCl7d2luZG93LmNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuX3JlZHJhd1JhZiksdGhpcy5fcmVkcmF3UmFmPXdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCk9Pnt0aGlzLm1lYXN1cmVCQm94QW5kTWF5YmVJbnZhbGlkYXRlTGF5b3V0KCksdGhpcy5vdXRlci5yZWRyYXcoKX0pfW1lYXN1cmVCQm94QW5kTWF5YmVJbnZhbGlkYXRlTGF5b3V0SW5SYWYoKXt3aW5kb3cuY2FuY2VsQW5pbWF0aW9uRnJhbWUodGhpcy5faW52YWxpZGF0ZUxheW91dFJhZiksdGhpcy5faW52YWxpZGF0ZUxheW91dFJhZj13aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT57dGhpcy5tZWFzdXJlQkJveEFuZE1heWJlSW52YWxpZGF0ZUxheW91dCgpfSl9bWVhc3VyZUJCb3hBbmRNYXliZUludmFsaWRhdGVMYXlvdXQoKXtpZih0aGlzLl9sYXN0RHJhd0JCb3gpe2xldHt3aWR0aDp0fT10aGlzLl9sYXN0RHJhd0JCb3gse3dpZHRoOnJ9PXRoaXMudGFyZ2V0U1ZHLm5vZGUoKS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTt0PT0wJiZ0PHImJnRoaXMub3V0ZXIuaW52YWxpZGF0ZUNhY2hlKCl9dGhpcy5fbGFzdERyYXdCQm94PXRoaXMudGFyZ2V0U1ZHLm5vZGUoKS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKX1kZXN0cm95KCl7d2luZG93LmNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuX3JlZHJhd1JhZiksd2luZG93LmNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuX2ludmFsaWRhdGVMYXlvdXRSYWYpLHRoaXMub3V0ZXImJnRoaXMub3V0ZXIuZGVzdHJveSgpfW9uQW5jaG9yKHQpe3RoaXMub3V0ZXImJnRoaXMub3V0ZXIub25BbmNob3IodCl9aXNEYXRhRml0VG9Eb21haW4oKXtyZXR1cm4gdCh0aGlzLnhBeGlzLmdldFNjYWxlKCkpJiZ0KHRoaXMueUF4aXMuZ2V0U2NhbGUoKSk7ZnVuY3Rpb24gdChyKXtsZXQgbj1yLmdldFRyYW5zZm9ybWF0aW9uRG9tYWluKCksaT1yLmdldFRyYW5zZm9ybWF0aW9uRXh0ZW50KCk7cmV0dXJuIGlbMF09PT1uWzBdJiZpWzFdPT09blsxXX19fTt2YXIgcUt0PUVlKE9lKCksMSksaXN0PUVlKHdsKCksMSk7X3Moe21vZHVsZU5hbWU6InBsb3R0YWJsZS1zdHlsZSIsc3R5bGVDb250ZW50OmAKICAgIAoucGxvdHRhYmxlLWNvbG9ycy0wIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjNTI3OWM3OyAvKiBJTkRJR08gKi8KfQoKLnBsb3R0YWJsZS1jb2xvcnMtMSB7CiAgYmFja2dyb3VuZC1jb2xvcjogI2ZkMzczZTsgLyogQ09SQUxfUkVEICovCn0KCi5wbG90dGFibGUtY29sb3JzLTIgewogIGJhY2tncm91bmQtY29sb3I6ICM2M2MyNjE7IC8qIEZFUk4gKi8KfQoKLnBsb3R0YWJsZS1jb2xvcnMtMyB7CiAgYmFja2dyb3VuZC1jb2xvcjogI2ZhZDQxOTsgLyogQlJJR0hUX1NVTiAqLwp9CgoucGxvdHRhYmxlLWNvbG9ycy00IHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjMmMyYjZmOyAvKiBKQUNBUlRBICovCn0KCi5wbG90dGFibGUtY29sb3JzLTUgewogIGJhY2tncm91bmQtY29sb3I6ICNmZjc5Mzk7IC8qIEJVUk5JTkdfT1JBTkdFICovCn0KCi5wbG90dGFibGUtY29sb3JzLTYgewogIGJhY2tncm91bmQtY29sb3I6ICNkYjJlNjU7IC8qIENFUklTRV9SRUQgKi8KfQoKLnBsb3R0YWJsZS1jb2xvcnMtNyB7CiAgYmFja2dyb3VuZC1jb2xvcjogIzk5Y2U1MDsgLyogQ09OSUZFUiAqLwp9CgoucGxvdHRhYmxlLWNvbG9ycy04IHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjOTYyNTY1OyAvKiBST1lBTF9IRUFUSCAqLwp9CgoucGxvdHRhYmxlLWNvbG9ycy05IHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDZjY2NjOyAvKiBST0JJTlNfRUdHX0JMVUUgKi8KfQoKLyoqCiAqIFVzZXItc3VwcGxpZWQgcmVuZGVyVG8gZWxlbWVudC4KICovCi5wbG90dGFibGUgewogIGRpc3BsYXk6IGJsb2NrOyAvKiBtdXN0IGJlIGJsb2NrIGVsZW1lbnRzIGZvciB3aWR0aC9oZWlnaHQgY2FsY3VsYXRpb25zIHRvIHdvcmsgaW4gRmlyZWZveC4gKi8KICBwb2ludGVyLWV2ZW50czogdmlzaWJsZUZpbGw7CiAgcG9zaXRpb246IHJlbGF0aXZlOwogIC8qKgogICAqIFByZSAzLjAsIHVzZXJzIGNvdWxkIHNldCB0aGUgZGltZW5zaW9uIG9mIHRoZSByb290IGVsZW1lbnQgaW4gdHdvIHdheXM6IGVpdGhlciB1c2luZyBDU1MKICAgKiAoaW5saW5lIG9yIHRocm91Z2ggYSBzdHlsZXNoZWV0KSwgb3IgdXNpbmcgdGhlIFNWRyB3aWR0aC9oZWlnaHQgYXR0cmlidXRlcy4gQnkgZGVmYXVsdCwgd2UKICAgKiBzZXQgdGhlIFNWRyB3aWR0aC9oZWlnaHQgYXR0cmlidXRlcyB0byAxMDAlLgogICAqCiAgICogUG9zdCAzLjAgdGhlIHJvb3QgZWxlbWVudCBpcyBhbHdheXMgYSBub3JtYWwgZGl2IGFuZCB0aGUgb25seSB3YXkgdG8gc2V0IHRoZSBkaW1lbnNpb25zIGlzCiAgICogdG8gdXNlIENTUy4gVG8gcmVwbGljYXRlIHRoZSAiMTAwJS1ieS1kZWZhdWx0IiBiZWhhdmlvciwgd2UgYXBwbHkgd2lkdGgvaGVpZ2h0IDEwMCUuCiAgICovCiAgd2lkdGg6IDEwMCU7CiAgaGVpZ2h0OiAxMDAlOwp9CgovKioKICogVGhlIF9lbGVtZW50IHRoYXQgcm9vdHMgZWFjaCBDb21wb25lbnQncyBET00uCiAqLwoucGxvdHRhYmxlIC5jb21wb25lbnQgewogIC8qIEFsbG93IGNvbXBvbmVudHMgdG8gYmUgcG9zaXRpb25lZCB3aXRoIGV4cGxpY2l0IGxlZnQvdG9wL3dpZHRoL2hlaWdodCBzdHlsZXMgKi8KICBwb3NpdGlvbjogYWJzb2x1dGU7Cn0KCi5wbG90dGFibGUgLmJhY2tncm91bmQtY29udGFpbmVyLAoucGxvdHRhYmxlIC5jb250ZW50LAoucGxvdHRhYmxlIC5mb3JlZ3JvdW5kLWNvbnRhaW5lciB7CiAgcG9zaXRpb246IGFic29sdXRlOwogIHdpZHRoOiAxMDAlOwogIGhlaWdodDogMTAwJTsKfQoKLyoqCiAqIERvbid0IGFsbG93IHN2ZyBlbGVtZW50cyBhYm92ZSB0aGUgY29udGVudCB0byBzdGVhbCBldmVudHMKICovCi5wbG90dGFibGUgLmZvcmVncm91bmQtY29udGFpbmVyIHsKICBwb2ludGVyLWV2ZW50czogbm9uZTsKfQoKLnBsb3R0YWJsZSAuY29tcG9uZW50LW92ZXJmbG93LWhpZGRlbiB7CiAgb3ZlcmZsb3c6IGhpZGRlbjsKfQoKLnBsb3R0YWJsZSAuY29tcG9uZW50LW92ZXJmbG93LXZpc2libGUgewogIG92ZXJmbG93OiB2aXNpYmxlOwp9CgoucGxvdHRhYmxlIC5wbG90LWNhbnZhcy1jb250YWluZXIgewogIHdpZHRoOiAxMDAlOwogIGhlaWdodDogMTAwJTsKICBvdmVyZmxvdzogaGlkZGVuOwp9CgoucGxvdHRhYmxlIC5wbG90LWNhbnZhcyB7CiAgd2lkdGg6IDEwMCU7CiAgaGVpZ2h0OiAxMDAlOwogIC8qKgogICAqIFBsYXkgd2VsbCB3aXRoIGRlZmVycmVkIHJlbmRlcmluZy4KICAgKi8KICB0cmFuc2Zvcm0tb3JpZ2luOiAwcHggMHB4IDBweDsKfQoKLnBsb3R0YWJsZSB0ZXh0IHsKICB0ZXh0LXJlbmRlcmluZzogZ2VvbWV0cmljUHJlY2lzaW9uOwp9CgoucGxvdHRhYmxlIC5sYWJlbCB0ZXh0IHsKICBmaWxsOiAjMzIzMTNGOwp9CgoucGxvdHRhYmxlIC5iYXItbGFiZWwtdGV4dC1hcmVhIHRleHQsCi5wbG90dGFibGUgLnNjYXR0ZXItbGFiZWwtdGV4dC1hcmVhIHRleHQgewogIGZvbnQtc2l6ZTogMTJweDsKfQoKLnBsb3R0YWJsZSAubGFiZWwtYXJlYSB0ZXh0IHsKICBmaWxsOiAjMzIzMTNGOwogIGZvbnQtc2l6ZTogMTRweDsKfQoKLnBsb3R0YWJsZSAubGlnaHQtbGFiZWwgdGV4dCB7CiAgZmlsbDogd2hpdGU7Cn0KCi5wbG90dGFibGUgLmRhcmstbGFiZWwgdGV4dCB7CiAgZmlsbDogIzMyMzEzRjsKfQoKLnBsb3R0YWJsZSAub2ZmLWJhci1sYWJlbCB0ZXh0IHsKICBmaWxsOiAjMzIzMTNGOwp9CgoucGxvdHRhYmxlIC5zdGFja2VkLWJhci1sYWJlbCB0ZXh0IHsKICBmaWxsOiAjMzIzMTNGOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKfQoKLnBsb3R0YWJsZSAuc3RhY2tlZC1iYXItcGxvdCAub2ZmLWJhci1sYWJlbCB7CiAgLyogSEFDS0hBQ0sgIzI3OTU6IGNvcnJlY3Qgb2ZmLWJhciBsYWJlbCBsb2dpYyB0byBiZSBpbXBsZW1lbnRlZCBvbiBTdGFja2VkQmFyICovCiAgdmlzaWJpbGl0eTogaGlkZGVuICFpbXBvcnRhbnQ7Cn0KCi5wbG90dGFibGUgLmF4aXMtbGFiZWwgdGV4dCB7CiAgZm9udC1zaXplOiAxMHB4OwogIGZvbnQtd2VpZ2h0OiBib2xkOwogIGxldHRlci1zcGFjaW5nOiAxcHg7CiAgbGluZS1oZWlnaHQ6IG5vcm1hbDsKICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlOwp9CgoucGxvdHRhYmxlIC50aXRsZS1sYWJlbCB0ZXh0IHsKICBmb250LXNpemU6IDIwcHg7CiAgZm9udC13ZWlnaHQ6IGJvbGQ7Cn0KCi5wbG90dGFibGUgLmF4aXMgbGluZS5iYXNlbGluZSB7CiAgc3Ryb2tlOiAjQ0NDOwogIHN0cm9rZS13aWR0aDogMXB4Owp9CgoucGxvdHRhYmxlIC5heGlzIGxpbmUudGljay1tYXJrIHsKICBzdHJva2U6ICNDQ0M7CiAgc3Ryb2tlLXdpZHRoOiAxcHg7Cn0KCi5wbG90dGFibGUgLmF4aXMgdGV4dCB7CiAgZmlsbDogIzMyMzEzRjsKICBmb250LXNpemU6IDEycHg7CiAgZm9udC13ZWlnaHQ6IDIwMDsKICBsaW5lLWhlaWdodDogbm9ybWFsOwp9CgoucGxvdHRhYmxlIC5heGlzIC5hbm5vdGF0aW9uLWNpcmNsZSB7CiAgZmlsbDogd2hpdGU7CiAgc3Ryb2tlLXdpZHRoOiAxcHg7CiAgc3Ryb2tlOiAjQ0NDOwp9CgoucGxvdHRhYmxlIC5heGlzIC5hbm5vdGF0aW9uLWxpbmUgewogIHN0cm9rZTogI0NDQzsKICBzdHJva2Utd2lkdGg6IDFweDsKfQoKLnBsb3R0YWJsZSAuYXhpcyAuYW5ub3RhdGlvbi1yZWN0IHsKICBzdHJva2U6ICNDQ0M7CiAgc3Ryb2tlLXdpZHRoOiAxcHg7CiAgZmlsbDogd2hpdGU7Cn0KCi5wbG90dGFibGUgLmJhci1wbG90IC5iYXNlbGluZSB7CiAgc3Ryb2tlOiAjOTk5Owp9CgoucGxvdHRhYmxlIC5ncmlkbGluZXMgbGluZSB7CiAgc3Ryb2tlOiAjM0MzQzNDOyAvKiBoYWNraGFjazogZ3JpZGxpbmVzIHNob3VsZCBiZSBzb2xpZDsgc2VlICM4MjAgKi8KICBvcGFjaXR5OiAwLjI1OwogIHN0cm9rZS13aWR0aDogMXB4Owp9CgoucGxvdHRhYmxlIC5zZWxlY3Rpb24tYm94LWxheWVyIC5zZWxlY3Rpb24tYXJlYSB7CiAgZmlsbDogYmxhY2s7CiAgZmlsbC1vcGFjaXR5OiAwLjAzOwogIHN0cm9rZTogI0NDQzsKfQovKiBEcmFnQm94TGF5ZXIgKi8KLnBsb3R0YWJsZSAuZHJhZy1ib3gtbGF5ZXIueC1yZXNpemFibGUgLmRyYWctZWRnZS1sciB7CiAgY3Vyc29yOiBldy1yZXNpemU7Cn0KLnBsb3R0YWJsZSAuZHJhZy1ib3gtbGF5ZXIueS1yZXNpemFibGUgLmRyYWctZWRnZS10YiB7CiAgY3Vyc29yOiBucy1yZXNpemU7Cn0KCi5wbG90dGFibGUgLmRyYWctYm94LWxheWVyLngtcmVzaXphYmxlLnktcmVzaXphYmxlIC5kcmFnLWNvcm5lci10bCB7CiAgY3Vyc29yOiBud3NlLXJlc2l6ZTsKfQoucGxvdHRhYmxlIC5kcmFnLWJveC1sYXllci54LXJlc2l6YWJsZS55LXJlc2l6YWJsZSAuZHJhZy1jb3JuZXItdHIgewogIGN1cnNvcjogbmVzdy1yZXNpemU7Cn0KLnBsb3R0YWJsZSAuZHJhZy1ib3gtbGF5ZXIueC1yZXNpemFibGUueS1yZXNpemFibGUgLmRyYWctY29ybmVyLWJsIHsKICBjdXJzb3I6IG5lc3ctcmVzaXplOwp9Ci5wbG90dGFibGUgLmRyYWctYm94LWxheWVyLngtcmVzaXphYmxlLnktcmVzaXphYmxlIC5kcmFnLWNvcm5lci1iciB7CiAgY3Vyc29yOiBud3NlLXJlc2l6ZTsKfQoKLnBsb3R0YWJsZSAuZHJhZy1ib3gtbGF5ZXIubW92YWJsZSAuc2VsZWN0aW9uLWFyZWEgewogIGN1cnNvcjogbW92ZTsgLyogSUUgZmFsbGJhY2sgKi8KICBjdXJzb3I6IC1tb3otZ3JhYjsKICBjdXJzb3I6IC13ZWJraXQtZ3JhYjsKICBjdXJzb3I6IGdyYWI7Cn0KCi5wbG90dGFibGUgLmRyYWctYm94LWxheWVyLm1vdmFibGUgLnNlbGVjdGlvbi1hcmVhOmFjdGl2ZSB7CiAgY3Vyc29yOiAtbW96LWdyYWJiaW5nOwogIGN1cnNvcjogLXdlYmtpdC1ncmFiYmluZzsKICBjdXJzb3I6IGdyYWJiaW5nOwp9Ci8qIC9EcmFnQm94TGF5ZXIgKi8KCi5wbG90dGFibGUgLmd1aWRlLWxpbmUtbGF5ZXIgbGluZS5ndWlkZS1saW5lIHsKICBzdHJva2U6ICNDQ0M7CiAgc3Ryb2tlLXdpZHRoOiAxcHg7Cn0KCi5wbG90dGFibGUgLmRyYWctbGluZS1sYXllci5lbmFibGVkLnZlcnRpY2FsIGxpbmUuZHJhZy1lZGdlIHsKICBjdXJzb3I6IGV3LXJlc2l6ZTsKfQoKLnBsb3R0YWJsZSAuZHJhZy1saW5lLWxheWVyLmVuYWJsZWQuaG9yaXpvbnRhbCBsaW5lLmRyYWctZWRnZSB7CiAgY3Vyc29yOiBucy1yZXNpemU7Cn0KCi5wbG90dGFibGUgLmxlZ2VuZCB0ZXh0IHsKICBmaWxsOiAjMzIzMTNGOwogIGZvbnQtc2l6ZTogMTJweDsKICBmb250LXdlaWdodDogYm9sZDsKICBsaW5lLWhlaWdodDogbm9ybWFsOwp9CgoucGxvdHRhYmxlIC5pbnRlcnBvbGF0ZWQtY29sb3ItbGVnZW5kIHJlY3Quc3dhdGNoLWJvdW5kaW5nLWJveCB7CiAgZmlsbDogbm9uZTsKICBzdHJva2U6ICNDQ0M7CiAgc3Ryb2tlLXdpZHRoOiAxcHg7CiAgcG9pbnRlci1ldmVudHM6IG5vbmU7Cn0KCi5wbG90dGFibGUgLndhdGVyZmFsbC1wbG90IGxpbmUuY29ubmVjdG9yIHsKICBzdHJva2U6ICNDQ0M7CiAgc3Ryb2tlLXdpZHRoOiAxcHg7Cn0KCi5wbG90dGFibGUgLnBpZS1wbG90IC5hcmMub3V0bGluZSB7CiAgc3Ryb2tlLWxpbmVqb2luOiByb3VuZDsKfQoKYH0pO3ZhciBpMDsoZnVuY3Rpb24oZSl7ZS5HUk9VUD0iRyIsZS5ESVY9IkRJViIsZS5TVkc9IlNWRyIsZS5URVhUPSJURVhUIn0pKGkwfHwoaTA9e30pKTt2YXIgcnN0PWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMudW5pcXVlSWQ9MCx0aGlzLnJvb3Q9dH1leHBvcnRBc1N0cmluZygpe2xldCB0PXRoaXMuY29udmVydCh0aGlzLnJvb3QpO2lmKCF0KXJldHVybiIiO2xldCByPXRoaXMuY3JlYXRlUm9vdFN2ZygpO3JldHVybiByLmFwcGVuZENoaWxkKHQpLHIub3V0ZXJIVE1MfWNyZWF0ZVVuaXF1ZUlkKHQpe3JldHVybmAke3R9XyR7dGhpcy51bmlxdWVJZCsrfWB9Z2V0U2l6ZSgpe3JldHVybiB0aGlzLnJvb3QuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCl9Y3JlYXRlUm9vdFN2Zygpe2xldCB0PWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInN2ZyIpLHI9dGhpcy5nZXRTaXplKCk7cmV0dXJuIHQuc2V0QXR0cmlidXRlTlMoInN2ZyIsInZpZXdCb3giLGAwIDAgJHtyLndpZHRofSAke3IuaGVpZ2h0fWApLHQuc2V0QXR0cmlidXRlKCJ4bWxucyIsImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiksdH1jcmVhdGVDb252ZXJ0ZWROb2RlKHQpe2xldCByPXQubm9kZU5hbWUudG9VcHBlckNhc2UoKTtpZih0Lm5vZGVUeXBlPT1Ob2RlLkVMRU1FTlRfTk9ERSYmKHI9PWkwLkRJVnx8cj09aTAuU1ZHKSl7bGV0IG49ZG9jdW1lbnQuY3JlYXRlRWxlbWVudChpMC5HUk9VUCksaT13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0KSxvPXBhcnNlSW50KGkubGVmdCwxMCksYT1wYXJzZUludChpLnRvcCwxMCk7aWYob3x8YSl7bGV0IHM9dGhpcy5jcmVhdGVVbmlxdWVJZCgiY2xpcCIpO24uc2V0QXR0cmlidXRlKCJ0cmFuc2Zvcm0iLGB0cmFuc2xhdGUoJHtvfSwgJHthfSlgKSxuLnNldEF0dHJpYnV0ZSgiY2xpcC1wYXRoIixgdXJsKCMke3N9KWApO2xldCBsPXBhcnNlSW50KGkud2lkdGgsMTApLGM9cGFyc2VJbnQoaS5oZWlnaHQsMTApLHU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgicmVjdCIpO3Uuc2V0QXR0cmlidXRlKCJ3aWR0aCIsU3RyaW5nKGwpKSx1LnNldEF0dHJpYnV0ZSgiaGVpZ2h0IixTdHJpbmcoYykpO2xldCBoPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUygic3ZnIiwiY2xpcFBhdGgiKTtoLmlkPXMsaC5hcHBlbmRDaGlsZCh1KSxuLmFwcGVuZENoaWxkKGgpfXJldHVybiBufWVsc2UgcmV0dXJuIHQuY2xvbmVOb2RlKCl9Y29udmVydCh0KXtsZXQgcj10aGlzLmNyZWF0ZUNvbnZlcnRlZE5vZGUodCk7cmV0dXJuIEFycmF5LmZyb20odC5jaGlsZE5vZGVzKS5tYXAoaT0+dGhpcy5jb252ZXJ0KGkpKS5maWx0ZXIoQm9vbGVhbikuZm9yRWFjaChpPT57ci5hcHBlbmRDaGlsZChpKX0pLHIubm9kZU5hbWUudG9VcHBlckNhc2UoKT09aTAuR1JPVVAmJiFyLmhhc0NoaWxkTm9kZXMoKXx8dGhpcy5zaG91bGRPbWl0Tm9kZSh0KT9udWxsOnRoaXMuc3RyaXBDbGFzcyh0aGlzLnRyYW5zZmVyU3R5bGUodCxyKSl9c3RyaXBDbGFzcyh0KXtyZXR1cm4gdC5ub2RlVHlwZT09Tm9kZS5FTEVNRU5UX05PREUmJnQucmVtb3ZlQXR0cmlidXRlKCJjbGFzcyIpLHR9dHJhbnNmZXJTdHlsZSh0LHIpe2lmKHIubm9kZVR5cGUhPU5vZGUuRUxFTUVOVF9OT0RFKXJldHVybiByO2xldCBuPXIsaT1yLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCksbz13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0KTtyZXR1cm4gaT09aTAuVEVYVCYmT2JqZWN0LmFzc2lnbihuLnN0eWxlLHtmb250RmFtaWx5Om8uZm9udEZhbWlseSxmb250U2l6ZTpvLmZvbnRTaXplLGZvbnRXZWlnaHQ6by5mb250V2VpZ2h0fSksaSE9aTAuR1JPVVAmJihuLnNldEF0dHJpYnV0ZSgiZmlsbCIsby5maWxsKSxuLnNldEF0dHJpYnV0ZSgic3Ryb2tlIixvLnN0cm9rZSksbi5zZXRBdHRyaWJ1dGUoInN0cm9rZS13aWR0aCIsby5zdHJva2VXaWR0aCkpLG8ub3BhY2l0eSE9IjEiJiZuLnNldEF0dHJpYnV0ZSgib3BhY2l0eSIsby5vcGFjaXR5KSxyfXNob3VsZE9taXROb2RlKHQpe3JldHVybiExfX0sSUI9Y2xhc3MgZXh0ZW5kcyByc3R7c2hvdWxkT21pdE5vZGUodCl7cmV0dXJuIHQubm9kZVR5cGU9PU5vZGUuRUxFTUVOVF9OT0RFP3QuY2xhc3NMaXN0LmNvbnRhaW5zKCJzY2F0dGVyLXBsb3QiKTohMX19O19zKHttb2R1bGVOYW1lOiJ2ei1wYW4tem9vbS1zdHlsZSIsc3R5bGVDb250ZW50OmAKICAgIC5oZWxwIHsKICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgYW5pbWF0aW9uLWRlbGF5OiAxczsKICAgICAgYW5pbWF0aW9uLWR1cmF0aW9uOiAxczsKICAgICAgYW5pbWF0aW9uLW5hbWU6IGZhZGUtb3V0OwogICAgICBiYWNrZ3JvdW5kOiByZ2JhKDMwLCAzMCwgMzAsIDAuNik7CiAgICAgIGJvdHRvbTogMDsKICAgICAgY29sb3I6ICNmZmY7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICBsZWZ0OiAwOwogICAgICBvcGFjaXR5OiAxOwogICAgICBwYWRkaW5nOiAyMHB4OwogICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICByaWdodDogMDsKICAgICAgdG9wOiAwOwogICAgfQoKICAgIC5oZWxwID4gc3BhbiB7CiAgICAgIHdoaXRlLXNwYWNlOiBub3JtYWw7CiAgICB9CgogICAgQGtleWZyYW1lcyBmYWRlLW91dCB7CiAgICAgIDAlIHsKICAgICAgICBvcGFjaXR5OiAxOwogICAgICB9CgogICAgICAxMDAlIHsKICAgICAgICBvcGFjaXR5OiAwOwogICAgICB9CiAgICB9CiAgYH0pO3ZhciBMcWU9V3UoZTApLFVLdD1lPT5pc05hTihlKT8iTmFOIjpMcWUoZSksb3N0PVt7dGl0bGU6Ik5hbWUiLGV2YWx1YXRlOmU9PmUuZGF0YXNldC5tZXRhZGF0YSgpLm5hbWV9LHt0aXRsZToiU21vb3RoZWQiLGV2YWx1YXRlKGUsdCl7bGV0e3Ntb290aGluZ0VuYWJsZWQ6cn09dDtyZXR1cm4gVUt0KHI/ZS5kYXR1bS5zbW9vdGhlZDplLmRhdHVtLnNjYWxhcil9fSx7dGl0bGU6IlZhbHVlIixldmFsdWF0ZTplPT5VS3QoZS5kYXR1bS5zY2FsYXIpfSx7dGl0bGU6IlN0ZXAiLGV2YWx1YXRlOmU9Png0KGUuZGF0dW0uc3RlcCl9LHt0aXRsZToiVGltZSIsZXZhbHVhdGU6ZT0+X0IoZS5kYXR1bS53YWxsX3RpbWUpfSx7dGl0bGU6IlJlbGF0aXZlIixldmFsdWF0ZTplPT55QihyMChlLmRhdHVtLC0xLGUuZGF0YXNldCkpfV0sWnI9Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuY29sb3JTY2FsZT1uZXcgaXN0LlNjYWxlcy5Db2xvcigpLnJhbmdlKGpiLnNsaWNlKDApKSx0aGlzLnNtb290aGluZ0VuYWJsZWQ9ITEsdGhpcy5zbW9vdGhpbmdXZWlnaHQ9LjYsdGhpcy54VHlwZT1udWxsLHRoaXMueENvbXBvbmVudHNDcmVhdGlvbk1ldGhvZD1udWxsLHRoaXMueVZhbHVlQWNjZXNzb3I9dD0+dC5zY2FsYXIsdGhpcy50b29sdGlwQ29sdW1ucz1vc3QsdGhpcy55U2NhbGVUeXBlPUZmLkxJTkVBUix0aGlzLmlnbm9yZVlPdXRsaWVycz0hMSx0aGlzLnRvb2x0aXBTb3J0aW5nTWV0aG9kPSJkZWZhdWx0Iix0aGlzLnRvb2x0aXBQb3NpdGlvbj1HMS5CT1RUT00sdGhpcy5fdmlzaWJsZVNlcmllc0NhY2hlPVtdLHRoaXMuX3Nlcmllc0RhdGFDYWNoZT17fSx0aGlzLl9zZXJpZXNNZXRhZGF0YUNhY2hlPXt9LHRoaXMuX21ha2VDaGFydEFzeW5jQ2FsbGJhY2tJZD1udWxsfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLnNjb3BlU3VidHJlZSh0aGlzLiQuY2hhcnRkaXYsITApfWF0dGFjaGVkKCl7bGV0IHQ9e2NhcHR1cmU6ITAscGFzc2l2ZTohMH07dGhpcy5fbGlzdGVuKHRoaXMsIm1vdXNlZG93biIsdGhpcy5fb25Nb3VzZURvd24uYmluZCh0aGlzKSx0KSx0aGlzLl9saXN0ZW4odGhpcywibW91c2V1cCIsdGhpcy5fb25Nb3VzZVVwLmJpbmQodGhpcyksdCksdGhpcy5fbGlzdGVuKHdpbmRvdywia2V5ZG93biIsdGhpcy5fb25LZXlEb3duLmJpbmQodGhpcyksdCksdGhpcy5fbGlzdGVuKHdpbmRvdywia2V5dXAiLHRoaXMuX29uS2V5VXAuYmluZCh0aGlzKSx0KX1kZXRhY2hlZCgpe3RoaXMuX21ha2VDaGFydEFzeW5jQ2FsbGJhY2tJZCE9PW51bGwmJih0aGlzLmNhbmNlbEFzeW5jKHRoaXMuX21ha2VDaGFydEFzeW5jQ2FsbGJhY2tJZCksdGhpcy5fbWFrZUNoYXJ0QXN5bmNDYWxsYmFja0lkPW51bGwpLHRoaXMuX2NoYXJ0JiYodGhpcy5fY2hhcnQuZGVzdHJveSgpLHRoaXMuX2NoYXJ0PXZvaWQgMCksdGhpcy5fbGlzdGVuZXJzJiYodGhpcy5fbGlzdGVuZXJzLmZvckVhY2goKHtub2RlOnQsZXZlbnROYW1lOnIsZnVuYzpuLG9wdGlvbjppfSk9Pnt0LnJlbW92ZUV2ZW50TGlzdGVuZXIocixuLGkpfSksdGhpcy5fbGlzdGVuZXJzLmNsZWFyKCkpfV9saXN0ZW4odCxyLG4saT17fSl7dGhpcy5fbGlzdGVuZXJzfHwodGhpcy5fbGlzdGVuZXJzPW5ldyBTZXQpLHRoaXMuX2xpc3RlbmVycy5hZGQoe25vZGU6dCxldmVudE5hbWU6cixmdW5jOm4sb3B0aW9uOml9KSx0LmFkZEV2ZW50TGlzdGVuZXIocixuLGkpfV9vbktleURvd24odCl7dGhpcy50b2dnbGVDbGFzcygicGFua2V5Iix6Zi5pc1BhbktleSh0KSl9X29uS2V5VXAodCl7dGhpcy50b2dnbGVDbGFzcygicGFua2V5Iix6Zi5pc1BhbktleSh0KSl9X29uTW91c2VEb3duKHQpe3RoaXMudG9nZ2xlQ2xhc3MoIm1vdXNlZG93biIsITApfV9vbk1vdXNlVXAodCl7dGhpcy50b2dnbGVDbGFzcygibW91c2Vkb3duIiwhMSl9aXNEYXRhRml0VG9Eb21haW4oKXtyZXR1cm4gdGhpcy5fY2hhcnQ/dGhpcy5fY2hhcnQuaXNEYXRhRml0VG9Eb21haW4oKTohMH1zZXRWaXNpYmxlU2VyaWVzKHQpe3FLdC5pc0VxdWFsKHRoaXMuX3Zpc2libGVTZXJpZXNDYWNoZSx0KXx8KHRoaXMuX3Zpc2libGVTZXJpZXNDYWNoZT10KX1zZXRTZXJpZXNEYXRhKHQscil7dGhpcy5fc2VyaWVzRGF0YUNhY2hlW3RdPXIsdGhpcy5fY2hhcnQmJnRoaXMuX2NoYXJ0LnNldFNlcmllc0RhdGEodCxyKX1zZXRTZXJpZXNNZXRhZGF0YSh0LHIpe3RoaXMuX3Nlcmllc01ldGFkYXRhQ2FjaGVbdF09cix0aGlzLl9jaGFydCYmdGhpcy5fY2hhcnQuc2V0U2VyaWVzTWV0YWRhdGEodCxyKX1jb21taXRDaGFuZ2VzKCl7IXRoaXMuX2NoYXJ0fHx0aGlzLl9jaGFydC5jb21taXRDaGFuZ2VzKCl9cmVzZXREb21haW4oKXt0aGlzLl9jaGFydCYmdGhpcy5fY2hhcnQucmVzZXREb21haW4oKX1yZWRyYXcoKXt0aGlzLl9jaGFydCYmdGhpcy5fY2hhcnQucmVkcmF3KCl9X21ha2VDaGFydCgpe3RoaXMuX21ha2VDaGFydEFzeW5jQ2FsbGJhY2tJZCE9PW51bGwmJih0aGlzLmNhbmNlbEFzeW5jKHRoaXMuX21ha2VDaGFydEFzeW5jQ2FsbGJhY2tJZCksdGhpcy5fbWFrZUNoYXJ0QXN5bmNDYWxsYmFja0lkPW51bGwpLHRoaXMuX21ha2VDaGFydEFzeW5jQ2FsbGJhY2tJZD10aGlzLmFzeW5jKGZ1bmN0aW9uKCl7dGhpcy5fbWFrZUNoYXJ0QXN5bmNDYWxsYmFja0lkPW51bGw7bGV0IHQ9dGhpcy54Q29tcG9uZW50c0NyZWF0aW9uTWV0aG9kO2lmKCF0aGlzLnhUeXBlJiYhdD90PSRhdDp0aGlzLnhUeXBlJiYodD0oKT0+dkIodGhpcy54VHlwZSkpLCEoIXR8fCF0aGlzLnlWYWx1ZUFjY2Vzc29yfHwhdGhpcy50b29sdGlwQ29sdW1ucykpe3ZhciByPW5ldyBEUyh0LHRoaXMueVZhbHVlQWNjZXNzb3IsdGhpcy55U2NhbGVUeXBlLHRoaXMuY29sb3JTY2FsZSx0aGlzLiQudG9vbHRpcCx0aGlzLnRvb2x0aXBDb2x1bW5zLHRoaXMuZmlsbEFyZWEsdGhpcy5kZWZhdWx0WFJhbmdlLHRoaXMuZGVmYXVsdFlSYW5nZSx0aGlzLnN5bWJvbEZ1bmN0aW9uLHRoaXMueEF4aXNGb3JtYXR0ZXIpLG49SHQodGhpcy4kLmNoYXJ0ZGl2KTtyLnJlbmRlclRvKG4pLHRoaXMuX2NoYXJ0JiZ0aGlzLl9jaGFydC5kZXN0cm95KCksdGhpcy5fY2hhcnQ9cix0aGlzLl9jaGFydC5vbkFuY2hvcigoKT0+dGhpcy5maXJlKCJjaGFydC1hdHRhY2hlZCIpKX19LDM1MCl9X3JlbG9hZEZyb21DYWNoZSgpeyF0aGlzLl9jaGFydHx8KHRoaXMuX3Zpc2libGVTZXJpZXNDYWNoZS5mb3JFYWNoKHQ9Pnt0aGlzLl9jaGFydC5zZXRTZXJpZXNEYXRhKHQsdGhpcy5fc2VyaWVzRGF0YUNhY2hlW3RdfHxbXSl9KSx0aGlzLl92aXNpYmxlU2VyaWVzQ2FjaGUuZmlsdGVyKHQ9PnRoaXMuX3Nlcmllc01ldGFkYXRhQ2FjaGVbdF0pLmZvckVhY2godD0+e3RoaXMuX2NoYXJ0LnNldFNlcmllc01ldGFkYXRhKHQsdGhpcy5fc2VyaWVzTWV0YWRhdGFDYWNoZVt0XSl9KSx0aGlzLl9jaGFydC5zZXRWaXNpYmxlU2VyaWVzKHRoaXMuX3Zpc2libGVTZXJpZXNDYWNoZSksdGhpcy5fY2hhcnQuY29tbWl0Q2hhbmdlcygpKX1fc21vb3RoaW5nQ2hhbmdlZCgpeyF0aGlzLl9jaGFydHx8KHRoaXMuc21vb3RoaW5nRW5hYmxlZD90aGlzLl9jaGFydC5zbW9vdGhpbmdVcGRhdGUodGhpcy5zbW9vdGhpbmdXZWlnaHQpOnRoaXMuX2NoYXJ0LnNtb290aGluZ0Rpc2FibGUoKSl9X291dGxpZXJzQ2hhbmdlZCgpeyF0aGlzLl9jaGFydHx8dGhpcy5fY2hhcnQuaWdub3JlWU91dGxpZXJzKHRoaXMuaWdub3JlWU91dGxpZXJzKX1fY29sb3JTY2FsZUNoYW5nZWQoKXshdGhpcy5fY2hhcnR8fCh0aGlzLl9jaGFydC5zZXRDb2xvclNjYWxlKHRoaXMuY29sb3JTY2FsZSksdGhpcy5fY2hhcnQucmVkcmF3KCkpfV90b29sdGlwQ29sdW1uc0NoYW5nZWQoKXshdGhpcy5fY2hhcnR8fHRoaXMuX2NoYXJ0LnNldFRvb2x0aXBDb2x1bW5zKHRoaXMudG9vbHRpcENvbHVtbnMpfV90b29sdGlwU29ydGluZ01ldGhvZENoYW5nZWQoKXshdGhpcy5fY2hhcnR8fHRoaXMuX2NoYXJ0LnNldFRvb2x0aXBTb3J0aW5nTWV0aG9kKHRoaXMudG9vbHRpcFNvcnRpbmdNZXRob2QpfWdldEV4cG9ydGVyKCl7cmV0dXJuIG5ldyBJQih0aGlzLiQuY2hhcnRkaXYpfX07WnIudGVtcGxhdGU9UWAKICAgIDxkaXYgaWQ9ImNoYXJ0ZGl2Ij48L2Rpdj4KICAgIDx2ei1jaGFydC10b29sdGlwCiAgICAgIGlkPSJ0b29sdGlwIgogICAgICBwb3NpdGlvbj0iW1t0b29sdGlwUG9zaXRpb25dXSIKICAgICAgY29udGVudC1jb21wb25lbnQtbmFtZT0idnotbGluZS1jaGFydC10b29sdGlwIgogICAgPjwvdnotY2hhcnQtdG9vbHRpcD4KICAgIDxzdHlsZSBpbmNsdWRlPSJwbG90dGFibGUtc3R5bGUiPjwvc3R5bGU+CiAgICA8c3R5bGUgaW5jbHVkZT0idnotcGFuLXpvb20tc3R5bGUiPjwvc3R5bGU+CiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICAtbW96LXVzZXItc2VsZWN0OiBub25lOwogICAgICAgIC13ZWJraXQtdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgIGZsZXgtZ3JvdzogMTsKICAgICAgICBmbGV4LXNocmluazogMTsKICAgICAgICBvdXRsaW5lOiBub25lOwogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgICB9CiAgICAgIGRpdiB7CiAgICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICAtbW96LXVzZXItc2VsZWN0OiBub25lOwogICAgICAgIGZsZXgtZ3JvdzogMTsKICAgICAgICBmbGV4LXNocmluazogMTsKICAgICAgfQoKICAgICAgI2NoYXJ0ZGl2IC5tYWluIHsKICAgICAgICBjb250YWluOiBzdHJpY3Q7CiAgICAgICAgY3Vyc29yOiBjcm9zc2hhaXI7CiAgICAgIH0KCiAgICAgIDpob3N0KC5wYW5rZXkpICNjaGFydGRpdiA6bm90KC5kcmFnLXpvb21pbmcpIC5tYWluIHsKICAgICAgICBjdXJzb3I6IC13ZWJraXQtZ3JhYjsKICAgICAgICBjdXJzb3I6IGdyYWI7CiAgICAgIH0KCiAgICAgIDpob3N0KC5tb3VzZWRvd24pICNjaGFydGRpdiAucGFubmluZyAubWFpbiB7CiAgICAgICAgY3Vyc29yOiAtd2Via2l0LWdyYWJiaW5nOwogICAgICAgIGN1cnNvcjogZ3JhYmJpbmc7CiAgICAgIH0KCiAgICAgICNjaGFydGRpdiB7CiAgICAgICAgY29udGFpbjogc3RyaWN0OwogICAgICB9CgogICAgICAjY2hhcnRkaXYgbGluZS5ndWlkZS1saW5lIHsKICAgICAgICBzdHJva2U6ICM5OTk7CiAgICAgICAgc3Ryb2tlLXdpZHRoOiAxLjVweDsKICAgICAgfQogICAgICAjY2hhcnRkaXY6aG92ZXIgLm1haW4gewogICAgICAgIHdpbGwtY2hhbmdlOiB0cmFuc2Zvcm07CiAgICAgIH0KCiAgICAgIC5naG9zdCB7CiAgICAgICAgb3BhY2l0eTogMC4yOwogICAgICAgIHN0cm9rZS13aWR0aDogMXB4OwogICAgICB9CgogICAgICAucGxvdHRhYmxlIC5heGlzIHRleHQgewogICAgICAgIGZpbGw6IGN1cnJlbnRDb2xvcjsKICAgICAgfQoKICAgICAgLnBsb3R0YWJsZSAuZ3JpZGxpbmVzIGxpbmUgewogICAgICAgIHN0cm9rZTogdmFyKC0tdGItc2Vjb25kYXJ5LXRleHQtY29sb3IpOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsaXN0LlNjYWxlcy5Db2xvcildLFpyLnByb3RvdHlwZSwiY29sb3JTY2FsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbildLFpyLnByb3RvdHlwZSwic3ltYm9sRnVuY3Rpb24iLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sWnIucHJvdG90eXBlLCJzbW9vdGhpbmdFbmFibGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLFpyLnByb3RvdHlwZSwic21vb3RoaW5nV2VpZ2h0Iix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFpyLnByb3RvdHlwZSwieFR5cGUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sWnIucHJvdG90eXBlLCJ4Q29tcG9uZW50c0NyZWF0aW9uTWV0aG9kIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKV0sWnIucHJvdG90eXBlLCJ4QXhpc0Zvcm1hdHRlciIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbildLFpyLnByb3RvdHlwZSwieVZhbHVlQWNjZXNzb3IiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLFpyLnByb3RvdHlwZSwidG9vbHRpcENvbHVtbnMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sWnIucHJvdG90eXBlLCJmaWxsQXJlYSIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sWnIucHJvdG90eXBlLCJkZWZhdWx0WFJhbmdlIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxaci5wcm90b3R5cGUsImRlZmF1bHRZUmFuZ2UiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sWnIucHJvdG90eXBlLCJ5U2NhbGVUeXBlIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sWnIucHJvdG90eXBlLCJpZ25vcmVZT3V0bGllcnMiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sWnIucHJvdG90eXBlLCJ0b29sdGlwU29ydGluZ01ldGhvZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxaci5wcm90b3R5cGUsInRvb2x0aXBQb3NpdGlvbiIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxaci5wcm90b3R5cGUsIl9jaGFydCIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sWnIucHJvdG90eXBlLCJfdmlzaWJsZVNlcmllc0NhY2hlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFpyLnByb3RvdHlwZSwiX3Nlcmllc0RhdGFDYWNoZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxaci5wcm90b3R5cGUsIl9zZXJpZXNNZXRhZGF0YUNhY2hlIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFpyLnByb3RvdHlwZSwiX21ha2VDaGFydEFzeW5jQ2FsbGJhY2tJZCIsdm9pZCAwKTtFKFtCdCgieENvbXBvbmVudHNDcmVhdGlvbk1ldGhvZCIsInhUeXBlIiwieVZhbHVlQWNjZXNzb3IiLCJ5U2NhbGVUeXBlIiwiaXNBdHRhY2hlZCIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sWnIucHJvdG90eXBlLCJfbWFrZUNoYXJ0IixudWxsKTtFKFtCdCgiX2NoYXJ0IiwiX3Zpc2libGVTZXJpZXNDYWNoZSIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sWnIucHJvdG90eXBlLCJfcmVsb2FkRnJvbUNhY2hlIixudWxsKTtFKFtCdCgic21vb3RoaW5nRW5hYmxlZCIsInNtb290aGluZ1dlaWdodCIsIl9jaGFydCIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sWnIucHJvdG90eXBlLCJfc21vb3RoaW5nQ2hhbmdlZCIsbnVsbCk7RShbQnQoImlnbm9yZVlPdXRsaWVycyIsIl9jaGFydCIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sWnIucHJvdG90eXBlLCJfb3V0bGllcnNDaGFuZ2VkIixudWxsKTtFKFtCdCgiY29sb3JTY2FsZSIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sWnIucHJvdG90eXBlLCJfY29sb3JTY2FsZUNoYW5nZWQiLG51bGwpO0UoW0J0KCJ0b29sdGlwQ29sdW1ucyIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sWnIucHJvdG90eXBlLCJfdG9vbHRpcENvbHVtbnNDaGFuZ2VkIixudWxsKTtFKFtCdCgidG9vbHRpcFNvcnRpbmdNZXRob2QiLCJfY2hhcnQiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLFpyLnByb3RvdHlwZSwiX3Rvb2x0aXBTb3J0aW5nTWV0aG9kQ2hhbmdlZCIsbnVsbCk7WnI9RShbeXQoInZ6LWxpbmUtY2hhcnQyIildLFpyKTt2YXIgbnN0PWNsYXNzIGV4dGVuZHMgbXR7fTtuc3QudGVtcGxhdGU9UWAKICAgIDxkaXYgY2xhc3M9ImNvbnRlbnQiPgogICAgICA8dGFibGU+CiAgICAgICAgPHRoZWFkPjwvdGhlYWQ+CiAgICAgICAgPHRib2R5PjwvdGJvZHk+CiAgICAgIDwvdGFibGU+CiAgICA8L2Rpdj4KICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICB9CgogICAgICAuY29udGVudCB7CiAgICAgICAgYmFja2dyb3VuZDogcmdiYSgwLCAwLCAwLCAwLjgpOwogICAgICAgIGJvcmRlci1yYWRpdXM6IDRweDsKICAgICAgICBjb2xvcjogI2ZmZjsKICAgICAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICB9CgogICAgICB0YWJsZSB7CiAgICAgICAgZm9udC1zaXplOiAxM3B4OwogICAgICAgIGxpbmUtaGVpZ2h0OiAxLjRlbTsKICAgICAgICBtYXJnaW4tdG9wOiAxMHB4OwogICAgICAgIHBhZGRpbmc6IDhweDsKICAgICAgfQoKICAgICAgdGhlYWQgewogICAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgfQoKICAgICAgdGJvZHkgewogICAgICAgIGZvbnQtc2l6ZTogMTNweDsKICAgICAgICBsaW5lLWhlaWdodDogMjFweDsKICAgICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgICB9CgogICAgICB0ZCB7CiAgICAgICAgcGFkZGluZzogMCA1cHg7CiAgICAgIH0KCiAgICAgIC5zd2F0Y2ggewogICAgICAgIGJvcmRlci1yYWRpdXM6IDUwJTsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBoZWlnaHQ6IDE4cHg7CiAgICAgICAgd2lkdGg6IDE4cHg7CiAgICAgIH0KCiAgICAgIC5jbG9zZXN0IC5zd2F0Y2ggewogICAgICAgIGJveC1zaGFkb3c6IGluc2V0IDAgMCAwIDJweCAjZmZmOwogICAgICB9CgogICAgICB0aCB7CiAgICAgICAgcGFkZGluZzogMCA1cHg7CiAgICAgICAgdGV4dC1hbGlnbjogbGVmdDsKICAgICAgfQoKICAgICAgLmRpc3RhbnQgdGQ6bm90KC5zd2F0Y2gpIHsKICAgICAgICBvcGFjaXR5OiAwLjg7CiAgICAgIH0KCiAgICAgIC5naG9zdCB7CiAgICAgICAgb3BhY2l0eTogMC4yOwogICAgICAgIHN0cm9rZS13aWR0aDogMXB4OwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7bnN0PUUoW3l0KCJ2ei1saW5lLWNoYXJ0LXRvb2x0aXAiKV0sbnN0KTt2YXIgYXN0PVtdLGtxZT0wLFJxZT1HS3QudGhyb3R0bGUoZnVuY3Rpb24gZSgpe2lmKGFzdC5sZW5ndGg9PTApcmV0dXJuO2xldCB0PWFzdC5zaGlmdCgpO3QmJnQuYWN0aXZlJiYodC5yZWRyYXcoKSx0Ll9tYXliZVJlbmRlcmVkSW5CYWRTdGF0ZT0hMSksd2luZG93LmNhbmNlbEFuaW1hdGlvbkZyYW1lKGtxZSksd2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZShlKX0sMTAwKSxWbj1jbGFzcyBleHRlbmRzIGtTKEd0KG10KSl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX3JlZHJhd1JhZj1udWxsLHRoaXMuYWN0aXZlPSExLHRoaXMubG9nU2NhbGVBY3RpdmU9ITEsdGhpcy5jb2xvclNjYWxlPXtzY2FsZTpmbn0sdGhpcy5fcmVzZXREb21haW5Pbk5leHRMb2FkPSEwLHRoaXMuX21heWJlUmVuZGVyZWRJbkJhZFN0YXRlPSExfW9uTG9hZEZpbmlzaCgpe3RoaXMuY29tbWl0Q2hhbmdlcygpLHRoaXMuZGF0YVRvTG9hZC5sZW5ndGg+MCYmdGhpcy5fcmVzZXREb21haW5Pbk5leHRMb2FkJiYodGhpcy5fcmVzZXREb21haW5Pbk5leHRMb2FkPSExLHRoaXMuZ2V0Q2hhcnQoKS5yZXNldERvbWFpbigpKSx0aGlzLnJlZHJhdygpfWRpc2Nvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuZGlzY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLl9yZWRyYXdSYWYhPT1udWxsJiZjYW5jZWxBbmltYXRpb25GcmFtZSh0aGlzLl9yZWRyYXdSYWYpfWV4cG9ydEFzU3ZnU3RyaW5nKCl7cmV0dXJuIHRoaXMuZ2V0Q2hhcnQoKS5nZXRFeHBvcnRlcigpLmV4cG9ydEFzU3RyaW5nKCl9Z2V0Q2hhcnQoKXtyZXR1cm4gdGhpcy4kLmNoYXJ0fXJlc2V0RG9tYWluKCl7dGhpcy5nZXRDaGFydCgpLnJlc2V0RG9tYWluKCl9c2V0U2VyaWVzRGF0YSh0LHIpe3RoaXMuZ2V0Q2hhcnQoKS5zZXRTZXJpZXNEYXRhKHQscil9c2V0U2VyaWVzTWV0YWRhdGEodCxyKXt0aGlzLmdldENoYXJ0KCkuc2V0U2VyaWVzTWV0YWRhdGEodCxyKX1jb21taXRDaGFuZ2VzKCl7dGhpcy5nZXRDaGFydCgpLmNvbW1pdENoYW5nZXMoKX1yZWRyYXcoKXt0aGlzLl9yZWRyYXdSYWYhPT1udWxsJiZjYW5jZWxBbmltYXRpb25GcmFtZSh0aGlzLl9yZWRyYXdSYWYpLHRoaXMuX3JlZHJhd1JhZj13aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT57dGhpcy5hY3RpdmU/dGhpcy5nZXRDaGFydCgpLnJlZHJhdygpOnRoaXMuX21heWJlUmVuZGVyZWRJbkJhZFN0YXRlPSEwfSl9X2xvYWRLZXlDaGFuZ2VkKCl7dGhpcy5yZXNldCgpLHRoaXMuX3Jlc2V0RG9tYWluT25OZXh0TG9hZD0hMH1fZGF0YVNlcmllc0NoYW5nZWQoKXt0aGlzLmdldENoYXJ0KCkuc2V0VmlzaWJsZVNlcmllcyh0aGlzLmRhdGFTZXJpZXMpfV9sb2dTY2FsZUNoYW5nZWQodCl7bGV0IHI9dGhpcy5nZXRDaGFydCgpO3IueVNjYWxlVHlwZT10P0ZmLkxPRzpGZi5MSU5FQVIsdGhpcy5yZWRyYXcoKX1fZml4QmFkU3RhdGVXaGVuQWN0aXZlKCl7dGhpcy5hY3RpdmUmJnRoaXMuX21heWJlUmVuZGVyZWRJbkJhZFN0YXRlJiYoYXN0LnB1c2godGhpcyksUnFlKCkpfV9vbkNoYXJ0QXR0YWNoZWQoKXt0aGlzLmFjdGl2ZXx8KHRoaXMuX21heWJlUmVuZGVyZWRJbkJhZFN0YXRlPSEwKX19O1ZuLnRlbXBsYXRlPVFgCiAgICA8ZGl2IGlkPSJjaGFydC1hbmQtc3Bpbm5lci1jb250YWluZXIiPgogICAgICA8dnotbGluZS1jaGFydDIKICAgICAgICBpZD0iY2hhcnQiCiAgICAgICAgZGF0YS1sb2FkaW5nJD0iW1tkYXRhTG9hZGluZ11dIgogICAgICAgIGRhdGEtbG9hZGVkLW9uY2UkPSJbW2RhdGFMb2FkZWRBdExlYXN0T25jZV1dIgogICAgICAgIGNvbG9yLXNjYWxlPSJbW2NvbG9yU2NhbGVdXSIKICAgICAgICBkZWZhdWx0LXgtcmFuZ2U9IltbZGVmYXVsdFhSYW5nZV1dIgogICAgICAgIGRlZmF1bHQteS1yYW5nZT0iW1tkZWZhdWx0WVJhbmdlXV0iCiAgICAgICAgZmlsbC1hcmVhPSJbW2ZpbGxBcmVhXV0iCiAgICAgICAgaWdub3JlLXktb3V0bGllcnM9IltbaWdub3JlWU91dGxpZXJzXV0iCiAgICAgICAgb24tY2hhcnQtYXR0YWNoZWQ9Il9vbkNoYXJ0QXR0YWNoZWQiCiAgICAgICAgc21vb3RoaW5nLWVuYWJsZWQ9Iltbc21vb3RoaW5nRW5hYmxlZF1dIgogICAgICAgIHNtb290aGluZy13ZWlnaHQ9Iltbc21vb3RoaW5nV2VpZ2h0XV0iCiAgICAgICAgc3ltYm9sLWZ1bmN0aW9uPSJbW3N5bWJvbEZ1bmN0aW9uXV0iCiAgICAgICAgdG9vbHRpcC1jb2x1bW5zPSJbW3Rvb2x0aXBDb2x1bW5zXV0iCiAgICAgICAgdG9vbHRpcC1wb3NpdGlvbj0iW1t0b29sdGlwUG9zaXRpb25dXSIKICAgICAgICB0b29sdGlwLXNvcnRpbmctbWV0aG9kPSJbW3Rvb2x0aXBTb3J0aW5nTWV0aG9kXV0iCiAgICAgICAgeC1jb21wb25lbnRzLWNyZWF0aW9uLW1ldGhvZD0iW1t4Q29tcG9uZW50c0NyZWF0aW9uTWV0aG9kXV0iCiAgICAgICAgeC10eXBlPSJbW3hUeXBlXV0iCiAgICAgICAgeS12YWx1ZS1hY2Nlc3Nvcj0iW1t5VmFsdWVBY2Nlc3Nvcl1dIgogICAgICA+PC92ei1saW5lLWNoYXJ0Mj4KICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW2RhdGFMb2FkaW5nXV0iPgogICAgICAgIDxkaXYgaWQ9ImxvYWRpbmctc3Bpbm5lci1jb250YWluZXIiPgogICAgICAgICAgPHBhcGVyLXNwaW5uZXItbGl0ZSBhY3RpdmU9IiI+PC9wYXBlci1zcGlubmVyLWxpdGU+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvdGVtcGxhdGU+CiAgICA8L2Rpdj4KICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgIH0KCiAgICAgIDpob3N0KFtfbWF5YmUtcmVuZGVyZWQtaW4tYmFkLXN0YXRlXSkgdnotbGluZS1jaGFydCB7CiAgICAgICAgdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICB9CgogICAgICAjY2hhcnQtYW5kLXNwaW5uZXItY29udGFpbmVyIHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZsZXgtZ3JvdzogMTsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgIH0KCiAgICAgICNsb2FkaW5nLXNwaW5uZXItY29udGFpbmVyIHsKICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICAgIGJvdHRvbTogMDsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7CiAgICAgICAgbGVmdDogMDsKICAgICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgcmlnaHQ6IDA7CiAgICAgICAgdG9wOiAwOwogICAgICB9CgogICAgICB2ei1saW5lLWNoYXJ0MiB7CiAgICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICAtbW96LXVzZXItc2VsZWN0OiBub25lOwogICAgICB9CgogICAgICB2ei1saW5lLWNoYXJ0MltkYXRhLWxvYWRpbmddIHsKICAgICAgICBvcGFjaXR5OiAwLjM7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9maXhCYWRTdGF0ZVdoZW5BY3RpdmUifSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxWbi5wcm90b3R5cGUsImFjdGl2ZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sVm4ucHJvdG90eXBlLCJkYXRhU2VyaWVzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEFlKV0sVm4ucHJvdG90eXBlLCJyZXF1ZXN0TWFuYWdlciIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9sb2dTY2FsZUNoYW5nZWQifSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxWbi5wcm90b3R5cGUsImxvZ1NjYWxlQWN0aXZlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFZuLnByb3RvdHlwZSwieENvbXBvbmVudHNDcmVhdGlvbk1ldGhvZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxWbi5wcm90b3R5cGUsInhUeXBlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKV0sVm4ucHJvdG90eXBlLCJ5VmFsdWVBY2Nlc3NvciIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxWbi5wcm90b3R5cGUsImZpbGxBcmVhIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sVm4ucHJvdG90eXBlLCJzbW9vdGhpbmdFbmFibGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLFZuLnByb3RvdHlwZSwic21vb3RoaW5nV2VpZ2h0Iix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxWbi5wcm90b3R5cGUsInRvb2x0aXBDb2x1bW5zIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFZuLnByb3RvdHlwZSwidG9vbHRpcFNvcnRpbmdNZXRob2QiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sVm4ucHJvdG90eXBlLCJ0b29sdGlwUG9zaXRpb24iLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxWbi5wcm90b3R5cGUsImlnbm9yZVlPdXRsaWVycyIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sVm4ucHJvdG90eXBlLCJkZWZhdWx0WFJhbmdlIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxWbi5wcm90b3R5cGUsImRlZmF1bHRZUmFuZ2UiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pXSxWbi5wcm90b3R5cGUsInN5bWJvbEZ1bmN0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFZuLnByb3RvdHlwZSwiY29sb3JTY2FsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLFZuLnByb3RvdHlwZSwiX3Jlc2V0RG9tYWluT25OZXh0TG9hZCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4scmVmbGVjdFRvQXR0cmlidXRlOiEwfSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxWbi5wcm90b3R5cGUsIl9tYXliZVJlbmRlcmVkSW5CYWRTdGF0ZSIsdm9pZCAwKTtFKFtCdCgibG9hZEtleSIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sVm4ucHJvdG90eXBlLCJfbG9hZEtleUNoYW5nZWQiLG51bGwpO0UoW0J0KCJkYXRhU2VyaWVzLioiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLFZuLnByb3RvdHlwZSwiX2RhdGFTZXJpZXNDaGFuZ2VkIixudWxsKTtWbj1FKFt5dCgidGYtbGluZS1jaGFydC1kYXRhLWxvYWRlciIpXSxWbik7X3Moe21vZHVsZU5hbWU6InRmLWN1c3RvbS1zY2FsYXItY2FyZC1zdHlsZSIsc3R5bGVDb250ZW50OmAKICAgIDpob3N0IHsKICAgICAgbWFyZ2luOiA1cHggMTBweDsKICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICB3aWR0aDogMzMwcHg7CiAgICAgIHZlcnRpY2FsLWFsaWduOiB0ZXh0LXRvcDsKICAgIH0KCiAgICA6aG9zdChbX2V4cGFuZGVkXSkgewogICAgICB3aWR0aDogMTAwJTsKICAgIH0KCiAgICA6aG9zdChbX2V4cGFuZGVkXSkgI3RmLWxpbmUtY2hhcnQtZGF0YS1sb2FkZXItY29udGFpbmVyIHsKICAgICAgaGVpZ2h0OiA0MDBweDsKICAgIH0KCiAgICBoMSB7CiAgICAgIGZvbnQtc2l6ZTogMTlweDsKICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDsKICAgIH0KCiAgICAjdGYtbGluZS1jaGFydC1kYXRhLWxvYWRlci1jb250YWluZXIgewogICAgICBoZWlnaHQ6IDIwMHB4OwogICAgICB3aWR0aDogMTAwJTsKICAgIH0KCiAgICAjYnV0dG9ucyB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGZsZXgtZGlyZWN0aW9uOiByb3c7CiAgICB9CgogICAgcGFwZXItaWNvbi1idXR0b24gewogICAgICBjb2xvcjogIzIxOTZmMzsKICAgICAgYm9yZGVyLXJhZGl1czogMTAwJTsKICAgICAgd2lkdGg6IDMycHg7CiAgICAgIGhlaWdodDogMzJweDsKICAgICAgcGFkZGluZzogNHB4OwogICAgfQoKICAgIHBhcGVyLWljb24tYnV0dG9uW3NlbGVjdGVkXSB7CiAgICAgIGJhY2tncm91bmQ6IHZhcigtLXRiLXVpLWxpZ2h0LWFjY2VudCk7CiAgICB9CgogICAgLmRvd25sb2FkLWxpbmtzIHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAgaGVpZ2h0OiAzMnB4OwogICAgfQoKICAgIC5kb3dubG9hZC1saW5rcyBhIHsKICAgICAgZm9udC1zaXplOiAxMHB4OwogICAgICBhbGlnbi1zZWxmOiBjZW50ZXI7CiAgICAgIG1hcmdpbjogMnB4OwogICAgfQoKICAgIC5kb3dubG9hZC1saW5rcyBwYXBlci1kcm9wZG93bi1tZW51IHsKICAgICAgd2lkdGg6IDEwMHB4OwogICAgICAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1sYWJlbDogewogICAgICAgIGZvbnQtc2l6ZTogMTBweDsKICAgICAgfQogICAgICAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1pbnB1dDogewogICAgICAgIGZvbnQtc2l6ZTogMTBweDsKICAgICAgfQogICAgfQogIGB9KTt2YXIgT1M9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4saSxvKXt0aGlzLnJ1bj10LHRoaXMudGFnPXIsdGhpcy5uYW1lPW4sdGhpcy5zY2FsYXJEYXRhPWksdGhpcy5zeW1ib2w9b31nZXROYW1lKCl7cmV0dXJuIHRoaXMubmFtZX1zZXREYXRhKHQpe3RoaXMuc2NhbGFyRGF0YT10fWdldERhdGEoKXtyZXR1cm4gdGhpcy5zY2FsYXJEYXRhfWdldFJ1bigpe3JldHVybiB0aGlzLnJ1bn1nZXRUYWcoKXtyZXR1cm4gdGhpcy50YWd9Z2V0U3ltYm9sKCl7cmV0dXJuIHRoaXMuc3ltYm9sfX07ZnVuY3Rpb24gTEIoZSx0KXtyZXR1cm5gJHt0fSAoJHtlfSlgfXZhciBXMT1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLnJ1bkJhc2VkQ29sb3JTY2FsZT10fXNjYWxlKHQpe3JldHVybiB0aGlzLnJ1bkJhc2VkQ29sb3JTY2FsZS5zY2FsZSh0aGlzLnBhcnNlUnVuTmFtZSh0KSl9cGFyc2VSdW5OYW1lKHQpe2xldCByPXQubWF0Y2goL1woKC4qKVwpJC8pO3JldHVybiByP3JbMV06IiJ9fTt2YXIgSnI9Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuYWN0aXZlPSEwLHRoaXMuX2NvbG9yU2NhbGU9bmV3IFcxKHtzY2FsZTpmbn0pLHRoaXMuX25hbWVUb0RhdGFTZXJpZXM9e30sdGhpcy5fZXhwYW5kZWQ9ITEsdGhpcy5fcmVxdWVzdERhdGE9KHQscixuKT0+e2xldCBvPXZlKCkucGx1Z2luUm91dGUoImN1c3RvbV9zY2FsYXJzIiwiL3NjYWxhcnMiKTtQcm9taXNlLmFsbCh0Lm1hcChhPT57bGV0IHM9YSxsPXRoaXMuX3RhZ0ZpbHRlcixjPUNuKG8se3RhZzpsLHJ1bjpzfSk7cmV0dXJuIHRoaXMucmVxdWVzdE1hbmFnZXIucmVxdWVzdChjKS50aGVuKHU9PnZvaWQgcih7aXRlbTphLGRhdGE6dX0pKX0pKS5maW5hbGx5KCgpPT52b2lkIG4oKSl9LHRoaXMuX3J1blRvTmV4dEF2YWlsYWJsZVN5bWJvbEluZGV4PXt9LHRoaXMuX21hdGNoZXNMaXN0T3BlbmVkPSExLHRoaXMuX2ZpbGxBcmVhPXtsb3dlckFjY2Vzc29yOnQ9PnQubG93ZXIsaGlnaGVyQWNjZXNzb3I6dD0+dC51cHBlcn0sdGhpcy5fdG9vbHRpcENvbHVtbnM9KCgpPT57bGV0IHQ9V3UoZTApLHI9bj0+aXNOYU4obik/Ik5hTiI6dChuKTtyZXR1cm5be3RpdGxlOiJOYW1lIixldmFsdWF0ZTpuPT5uLmRhdGFzZXQubWV0YWRhdGEoKS5uYW1lfSx7dGl0bGU6IlZhbHVlIixldmFsdWF0ZTpuPT5yKG4uZGF0dW0uc2NhbGFyKX0se3RpdGxlOiJMb3dlciBNYXJnaW4iLGV2YWx1YXRlOm49PnIobi5kYXR1bS5sb3dlcil9LHt0aXRsZToiVXBwZXIgTWFyZ2luIixldmFsdWF0ZTpuPT5yKG4uZGF0dW0udXBwZXIpfSx7dGl0bGU6IlN0ZXAiLGV2YWx1YXRlOm49Png0KG4uZGF0dW0uc3RlcCl9LHt0aXRsZToiVGltZSIsZXZhbHVhdGU6bj0+X0Iobi5kYXR1bS53YWxsX3RpbWUpfSx7dGl0bGU6IlJlbGF0aXZlIixldmFsdWF0ZTpuPT55QihyMChuLmRhdHVtLC0xLG4uZGF0YXNldCkpfV19KSgpLHRoaXMuX21pc3NpbmdUYWdzPVtdLHRoaXMuX21pc3NpbmdUYWdzQ29sbGFwc2libGVPcGVuZWQ9ITF9cmVsb2FkKCl7dGhpcy4kLmxvYWRlci5yZWxvYWQoKX1yZWRyYXcoKXt0aGlzLiQubG9hZGVyLnJlZHJhdygpfV90b2dnbGVFeHBhbmRlZCh0KXt0aGlzLnNldCgiX2V4cGFuZGVkIiwhdGhpcy5fZXhwYW5kZWQpLHRoaXMucmVkcmF3KCl9X3RvZ2dsZUxvZ1NjYWxlKCl7dGhpcy5zZXQoIl9sb2dTY2FsZUFjdGl2ZSIsIXRoaXMuX2xvZ1NjYWxlQWN0aXZlKX1fcmVzZXREb21haW4oKXtsZXQgdD10aGlzLiQubG9hZGVyO3QmJnQucmVzZXREb21haW4oKX1fY3N2VXJsKHQscil7aWYoIXIpcmV0dXJuIiI7bGV0IG49dGhpcy5fZG93bmxvYWREYXRhVXJsKHQscik7cmV0dXJuIENuKG4se2Zvcm1hdDoiY3N2In0pfV9qc29uVXJsKHQscil7aWYoIXIpcmV0dXJuIiI7bGV0IG49dGhpcy5fZG93bmxvYWREYXRhVXJsKHQscik7cmV0dXJuIENuKG4se2Zvcm1hdDoianNvbiJ9KX1fZG93bmxvYWREYXRhVXJsKHQscil7bGV0IG49dFtyXSxpPXt0YWc6bi5nZXRUYWcoKSxydW46bi5nZXRSdW4oKX07cmV0dXJuIENuKHZlKCkucGx1Z2luUm91dGUoImN1c3RvbV9zY2FsYXJzIiwiL2Rvd25sb2FkX2RhdGEiKSxpKX1fY3JlYXRlUHJvY2Vzc0RhdGFGdW5jdGlvbih0KXtyZXR1cm4ocixuLGkpPT57aWYoIWkucmVnZXhfdmFsaWQpe3RoaXMuc2V0KCJfdGFnRmlsdGVySW52YWxpZCIsITApO3JldHVybn1sZXQgbz1Zby5jbG9uZSh0aGlzLl9uYW1lVG9EYXRhU2VyaWVzKSxhPVtdO1lvLmZvckVhY2godCxsPT57bGV0IGM9ITEsdT1pLnRhZ190b19ldmVudHNbbC52YWx1ZV0saD1pLnRhZ190b19ldmVudHNbbC5sb3dlcl0sZj1pLnRhZ190b19ldmVudHNbbC51cHBlcl07aWYoWW8uaXNVbmRlZmluZWQodSkmJihhLnB1c2gobC52YWx1ZSksYz0hMCksWW8uaXNVbmRlZmluZWQoaCkmJihhLnB1c2gobC5sb3dlciksYz0hMCksWW8uaXNVbmRlZmluZWQoZikmJihhLnB1c2gobC51cHBlciksYz0hMCksYylyZXR1cm47bGV0IHA9Yj0+YlsxXSxkPXRoaXMuX2ZpbmRTdGVwTWlzbWF0Y2gobCx1Lm1hcChwKSxoLm1hcChwKSxmLm1hcChwKSk7aWYoZCl7dGhpcy5zZXQoIl9zdGVwc01pc21hdGNoIixkKTtyZXR1cm59bGV0IGc9Yj0+YlsyXSxfPXUubWFwKChiLFMpPT4oe3dhbGxfdGltZTpuZXcgRGF0ZShiWzBdKjFlMyksc3RlcDpwKGIpLHNjYWxhcjpnKGIpLGxvd2VyOmcoaFtTXSksdXBwZXI6ZyhmW1NdKX0pKSx5PUxCKG4sbC52YWx1ZSkseD1vW3ldO2lmKHgpeC5zZXREYXRhKF8pO2Vsc2V7bGV0IGI9dGhpcy5fY3JlYXRlTmV3RGF0YVNlcmllcyhuLGwudmFsdWUseSxfKTtvW3ldPWJ9fSksdGhpcy5zZXQoIl9uYW1lVG9EYXRhU2VyaWVzIixvKTtsZXQgcz1Zby5maW5kSW5kZXgodGhpcy5fbWlzc2luZ1RhZ3MsbD0+bC5ydW49PT1uKTtpZihhLmxlbmd0aCYmYS5sZW5ndGghPTMpe2xldCBsPXtydW46bix0YWdzOmF9O3M+PTA/dGhpcy5zcGxpY2UoIl9taXNzaW5nVGFncyIscywxLGwpOnRoaXMucHVzaCgiX21pc3NpbmdUYWdzIixsKX1lbHNlIHM+PTAmJnRoaXMuc3BsaWNlKCJfbWlzc2luZ1RhZ3MiLHMsMSl9fV9maW5kU3RlcE1pc21hdGNoKHQscixuLGkpe3JldHVybiBZby5pc0VxdWFsKG4scikmJllvLmlzRXF1YWwoaSxyKT9udWxsOntzZXJpZXNPYmplY3Q6dCx2YWx1ZVN0ZXBzOnIsbG93ZXJTdGVwczpuLHVwcGVyU3RlcHM6aX19X2NyZWF0ZU5ld0RhdGFTZXJpZXModCxyLG4saSl7dGhpcy5fcnVuVG9OZXh0QXZhaWxhYmxlU3ltYm9sSW5kZXhbdF18PTA7bGV0IG89UlNbdGhpcy5fcnVuVG9OZXh0QXZhaWxhYmxlU3ltYm9sSW5kZXhbdF1dLGE9bmV3IE9TKHQscixuLGksbykscz1SUy5sZW5ndGg7cmV0dXJuIHRoaXMuX3J1blRvTmV4dEF2YWlsYWJsZVN5bWJvbEluZGV4W3RdPSh0aGlzLl9ydW5Ub05leHRBdmFpbGFibGVTeW1ib2xJbmRleFt0XSsxKSVzLGF9X3VwZGF0ZUNoYXJ0KCl7dmFyIHQ9dGhpcy5fbmFtZVRvRGF0YVNlcmllcztZby5mb3JPd24odCxyPT57dGhpcy4kLmxvYWRlci5zZXRTZXJpZXNEYXRhKHIuZ2V0TmFtZSgpLHIuZ2V0RGF0YSgpKX0pLHRoaXMuJC5sb2FkZXIuY29tbWl0Q2hhbmdlcygpfWdldCBfc2VyaWVzTmFtZXMoKXtsZXQgdD1uZXcgU2V0KHRoaXMucnVucyk7cmV0dXJuIE9iamVjdC5lbnRyaWVzKHRoaXMuX25hbWVUb0RhdGFTZXJpZXMpLmZpbHRlcigoW3Isbl0pPT50LmhhcyhuLnJ1bikpLm1hcCgoW3JdKT0+cil9X2RldGVybWluZUNvbG9yKHQscil7cmV0dXJuIHQuc2NhbGUocil9X3JlZnJlc2hEYXRhU2VyaWVzKCl7dmFyIHQ9dGhpcy5fdGFnRmlsdGVyO3RoaXMuc2V0KCJfbmFtZVRvRGF0YVNlcmllcyIse30pfV9jcmVhdGVTeW1ib2xGdW5jdGlvbigpe3JldHVybiB0PT50aGlzLl9uYW1lVG9EYXRhU2VyaWVzW3RdLmdldFN5bWJvbCgpLm1ldGhvZCgpfV9kZXRlcm1pbmVTeW1ib2wodCxyKXtyZXR1cm4gdFtyXS5nZXRTeW1ib2woKS5jaGFyYWN0ZXJ9Z2V0IF90YWdGaWx0ZXIoKXt2YXIgdD10aGlzLm1hcmdpbkNoYXJ0U2VyaWVzO3JldHVybiBZby5mbGF0dGVuKHQubWFwKGk9PltpLnZhbHVlLGkubG93ZXIsaS51cHBlcl0pKS5tYXAoaT0+IigiK3RoaXMuX2VzY2FwZVJlZ2V4Q2hhcmFjdGVycyhpKSsiKSIpLmpvaW4oInwiKX1fZXNjYXBlUmVnZXhDaGFyYWN0ZXJzKHQpe3JldHVybiB0LnJlcGxhY2UoL1suKis/XiR7fSgpfFtcXVxcXS9nLCJcXCQmIil9X2dldFRvZ2dsZUNvbGxhcHNpYmxlSWNvbih0KXtyZXR1cm4gdD8iZXhwYW5kLWxlc3MiOiJleHBhbmQtbW9yZSJ9X3RvZ2dsZU1hdGNoZXNPcGVuKCl7dGhpcy5zZXQoIl9tYXRjaGVzTGlzdE9wZW5lZCIsIXRoaXMuX21hdGNoZXNMaXN0T3BlbmVkKX1nZXQgX3RpdGxlRGlzcGxheVN0cmluZygpe3ZhciB0PXRoaXMudGl0bGU7cmV0dXJuIHR8fCJ1bnRpdGxlZCJ9X3NlcGFyYXRlV2l0aENvbW1hcyh0KXtyZXR1cm4gdC5qb2luKCIsICIpfV90b2dnbGVNaXNzaW5nVGFnc0NvbGxhcHNpYmxlT3Blbigpe3RoaXMuc2V0KCJfbWlzc2luZ1RhZ3NDb2xsYXBzaWJsZU9wZW5lZCIsIXRoaXMuX21pc3NpbmdUYWdzQ29sbGFwc2libGVPcGVuZWQpfV9tYXRjaExpc3RFbnRyeUNvbG9yVXBkYXRlZCgpe3ZhciByO2xldCB0PXRoaXMuJCQoIiNtYXRjaC1saXN0LXJlcGVhdCIpOyF0fHwocj10aGlzLnJvb3QpPT1udWxsfHxyLnF1ZXJ5U2VsZWN0b3JBbGwoIi5tYXRjaC1saXN0LWVudHJ5IikuZm9yRWFjaChuPT57bGV0IGk9dC5pdGVtRm9yRWxlbWVudChuKTtuLnN0eWxlLmNvbG9yPXRoaXMuX2RldGVybWluZUNvbG9yKHRoaXMuX2NvbG9yU2NhbGUsaSl9KX19O0pyLnRlbXBsYXRlPVFgCiAgICA8dGYtY2FyZC1oZWFkaW5nIGRpc3BsYXktbmFtZT0iW1tfdGl0bGVEaXNwbGF5U3RyaW5nXV0iPjwvdGYtY2FyZC1oZWFkaW5nPgogICAgPGRpdiBpZD0idGYtbGluZS1jaGFydC1kYXRhLWxvYWRlci1jb250YWluZXIiPgogICAgICA8dGYtbGluZS1jaGFydC1kYXRhLWxvYWRlcgogICAgICAgIGlkPSJsb2FkZXIiCiAgICAgICAgYWN0aXZlPSJbW2FjdGl2ZV1dIgogICAgICAgIGNvbG9yLXNjYWxlPSJbW19jb2xvclNjYWxlXV0iCiAgICAgICAgZGF0YS1zZXJpZXM9IltbX3Nlcmllc05hbWVzXV0iCiAgICAgICAgZmlsbC1hcmVhPSJbW19maWxsQXJlYV1dIgogICAgICAgIGlnbm9yZS15LW91dGxpZXJzPSJbW2lnbm9yZVlPdXRsaWVyc11dIgogICAgICAgIGxvYWQta2V5PSJbW190YWdGaWx0ZXJdXSIKICAgICAgICBkYXRhLXRvLWxvYWQ9IltbcnVuc11dIgogICAgICAgIHJlcXVlc3QtZGF0YT0iW1tfcmVxdWVzdERhdGFdXSIKICAgICAgICBsb2ctc2NhbGUtYWN0aXZlPSJbW19sb2dTY2FsZUFjdGl2ZV1dIgogICAgICAgIGxvYWQtZGF0YS1jYWxsYmFjaz0iW1tfY3JlYXRlUHJvY2Vzc0RhdGFGdW5jdGlvbihtYXJnaW5DaGFydFNlcmllcyldXSIKICAgICAgICByZXF1ZXN0LW1hbmFnZXI9IltbcmVxdWVzdE1hbmFnZXJdXSIKICAgICAgICBzeW1ib2wtZnVuY3Rpb249IltbX2NyZWF0ZVN5bWJvbEZ1bmN0aW9uKCldXSIKICAgICAgICB0b29sdGlwLWNvbHVtbnM9IltbX3Rvb2x0aXBDb2x1bW5zXV0iCiAgICAgICAgdG9vbHRpcC1zb3J0aW5nLW1ldGhvZD0iW1t0b29sdGlwU29ydGluZ01ldGhvZF1dIgogICAgICAgIHgtdHlwZT0iW1t4VHlwZV1dIgogICAgICA+CiAgICAgIDwvdGYtbGluZS1jaGFydC1kYXRhLWxvYWRlcj4KICAgIDwvZGl2PgogICAgPGRpdiBpZD0iYnV0dG9ucyI+CiAgICAgIDxwYXBlci1pY29uLWJ1dHRvbgogICAgICAgIHNlbGVjdGVkJD0iW1tfZXhwYW5kZWRdXSIKICAgICAgICBpY29uPSJmdWxsc2NyZWVuIgogICAgICAgIG9uLXRhcD0iX3RvZ2dsZUV4cGFuZGVkIgogICAgICA+PC9wYXBlci1pY29uLWJ1dHRvbj4KICAgICAgPHBhcGVyLWljb24tYnV0dG9uCiAgICAgICAgc2VsZWN0ZWQkPSJbW19sb2dTY2FsZUFjdGl2ZV1dIgogICAgICAgIGljb249ImxpbmUtd2VpZ2h0IgogICAgICAgIG9uLXRhcD0iX3RvZ2dsZUxvZ1NjYWxlIgogICAgICAgIHRpdGxlPSJUb2dnbGUgeS1heGlzIGxvZyBzY2FsZSIKICAgICAgPjwvcGFwZXItaWNvbi1idXR0b24+CiAgICAgIDxwYXBlci1pY29uLWJ1dHRvbgogICAgICAgIGljb249InNldHRpbmdzLW92ZXJzY2FuIgogICAgICAgIG9uLXRhcD0iX3Jlc2V0RG9tYWluIgogICAgICAgIHRpdGxlPSJGaXQgZG9tYWluIHRvIGRhdGEiCiAgICAgID48L3BhcGVyLWljb24tYnV0dG9uPgogICAgICA8c3BhbiBzdHlsZT0iZmxleC1ncm93OiAxIj48L3NwYW4+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tzaG93RG93bmxvYWRMaW5rc11dIj4KICAgICAgICA8ZGl2IGNsYXNzPSJkb3dubG9hZC1saW5rcyI+CiAgICAgICAgICA8cGFwZXItZHJvcGRvd24tbWVudQogICAgICAgICAgICBuby1sYWJlbC1mbG9hdD0idHJ1ZSIKICAgICAgICAgICAgbGFiZWw9InNlcmllcyB0byBkb3dubG9hZCIKICAgICAgICAgICAgc2VsZWN0ZWQtaXRlbS1sYWJlbD0ie3tfZGF0YVNlcmllc05hbWVUb0Rvd25sb2FkfX0iCiAgICAgICAgICA+CiAgICAgICAgICAgIDxwYXBlci1saXN0Ym94IGNsYXNzPSJkcm9wZG93bi1jb250ZW50IiBzbG90PSJkcm9wZG93bi1jb250ZW50Ij4KICAgICAgICAgICAgICA8dGVtcGxhdGUKICAgICAgICAgICAgICAgIGlzPSJkb20tcmVwZWF0IgogICAgICAgICAgICAgICAgaXRlbXM9IltbX3Nlcmllc05hbWVzXV0iCiAgICAgICAgICAgICAgICBhcz0iZGF0YVNlcmllc05hbWUiCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgPHBhcGVyLWl0ZW0gbm8tbGFiZWwtZmxvYXQ9InRydWUiCiAgICAgICAgICAgICAgICAgID5bW2RhdGFTZXJpZXNOYW1lXV08L3BhcGVyLWl0ZW0KICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8L3BhcGVyLWxpc3Rib3g+CiAgICAgICAgICA8L3BhcGVyLWRyb3Bkb3duLW1lbnU+CiAgICAgICAgICA8YQogICAgICAgICAgICBkb3dubG9hZD0iW1tfZGF0YVNlcmllc05hbWVUb0Rvd25sb2FkXV0uY3N2IgogICAgICAgICAgICBocmVmPSJbW19jc3ZVcmwoX25hbWVUb0RhdGFTZXJpZXMsIF9kYXRhU2VyaWVzTmFtZVRvRG93bmxvYWQpXV0iCiAgICAgICAgICAgID5DU1Y8L2EKICAgICAgICAgID4KICAgICAgICAgIDxhCiAgICAgICAgICAgIGRvd25sb2FkPSJbW19kYXRhU2VyaWVzTmFtZVRvRG93bmxvYWRdXS5qc29uIgogICAgICAgICAgICBocmVmPSJbW19qc29uVXJsKF9uYW1lVG9EYXRhU2VyaWVzLCBfZGF0YVNlcmllc05hbWVUb0Rvd25sb2FkKV1dIgogICAgICAgICAgICA+SlNPTjwvYQogICAgICAgICAgPgogICAgICAgIDwvZGl2PgogICAgICA8L3RlbXBsYXRlPgogICAgPC9kaXY+CgogICAgPCEtLSBoZXJlIC0tPgogICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19taXNzaW5nVGFncy5sZW5ndGhdXSI+CiAgICAgIDxkaXYgY2xhc3M9ImNvbGxhcHNpYmxlLWxpc3QtdGl0bGUiPgogICAgICAgIDxwYXBlci1pY29uLWJ1dHRvbgogICAgICAgICAgaWNvbj0iW1tfZ2V0VG9nZ2xlQ29sbGFwc2libGVJY29uKF9taXNzaW5nVGFnc0NvbGxhcHNpYmxlT3BlbmVkKV1dIgogICAgICAgICAgb24tY2xpY2s9Il90b2dnbGVNaXNzaW5nVGFnc0NvbGxhcHNpYmxlT3BlbiIKICAgICAgICAgIGNsYXNzPSJ0b2dnbGUtY29sbGFwc2libGUtYnV0dG9uIgogICAgICAgID4KICAgICAgICA8L3BhcGVyLWljb24tYnV0dG9uPgogICAgICAgIDxzcGFuIGNsYXNzPSJjb2xsYXBzaWJsZS10aXRsZS10ZXh0Ij4KICAgICAgICAgIDxpcm9uLWljb24gaWNvbj0iaWNvbnM6ZXJyb3IiPjwvaXJvbi1pY29uPiBNaXNzaW5nIFRhZ3MKICAgICAgICA8L3NwYW4+CiAgICAgIDwvZGl2PgogICAgICA8aXJvbi1jb2xsYXBzZSBvcGVuZWQ9IltbX21pc3NpbmdUYWdzQ29sbGFwc2libGVPcGVuZWRdXSI+CiAgICAgICAgPGRpdiBjbGFzcz0iZXJyb3ItY29udGVudCI+CiAgICAgICAgICA8aXJvbi1pY29uIGNsYXNzPSJlcnJvci1pY29uIiBpY29uPSJpY29uczplcnJvciI+PC9pcm9uLWljb24+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19taXNzaW5nVGFnc11dIiBhcz0ibWlzc2luZ0VudHJ5Ij4KICAgICAgICAgICAgPGRpdiBjbGFzcz0ibWlzc2luZy10YWdzLWZvci1ydW4tY29udGFpbmVyIj4KICAgICAgICAgICAgICBSdW4gIltbbWlzc2luZ0VudHJ5LnJ1bl1dIiBsYWNrcyBkYXRhIGZvciB0YWdzCiAgICAgICAgICAgICAgPHVsPgogICAgICAgICAgICAgICAgPHRlbXBsYXRlCiAgICAgICAgICAgICAgICAgIGlzPSJkb20tcmVwZWF0IgogICAgICAgICAgICAgICAgICBpdGVtcz0iW1ttaXNzaW5nRW50cnkudGFnc11dIgogICAgICAgICAgICAgICAgICBhcz0idGFnIgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICA8bGk+W1t0YWddXTwvbGk+CiAgICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICAgIDwvdWw+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L2Rpdj4KICAgICAgPC9pcm9uLWNvbGxhcHNlPgogICAgPC90ZW1wbGF0ZT4KCiAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX3RhZ0ZpbHRlckludmFsaWRdXSI+CiAgICAgIDxkaXYgY2xhc3M9ImVycm9yLWNvbnRlbnQiPgogICAgICAgIDxpcm9uLWljb24gY2xhc3M9ImVycm9yLWljb24iIGljb249Imljb25zOmVycm9yIj48L2lyb24taWNvbj4KICAgICAgICBUaGlzIHJlZ3VsYXIgZXhwcmVzaW9uIGlzIGludmFsaWQ6PGJyIC8+CiAgICAgICAgPHNwYW4gY2xhc3M9ImludmFsaWQtcmVnZXgiPltbX3RhZ0ZpbHRlcl1dPC9zcGFuPgogICAgICA8L2Rpdj4KICAgIDwvdGVtcGxhdGU+CgogICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19zdGVwc01pc21hdGNoXV0iPgogICAgICA8ZGl2IGNsYXNzPSJlcnJvci1jb250ZW50Ij4KICAgICAgICA8aXJvbi1pY29uIGNsYXNzPSJlcnJvci1pY29uIiBpY29uPSJpY29uczplcnJvciI+PC9pcm9uLWljb24+CiAgICAgICAgVGhlIHN0ZXBzIGZvciB2YWx1ZSwgbG93ZXIsIGFuZCB1cHBlciB0YWdzIGRvIG5vdCBtYXRjaDoKICAgICAgICA8dWw+CiAgICAgICAgICA8bGk+CiAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJ0YWctbmFtZSI+W1tfc3RlcHNNaXNtYXRjaC5zZXJpZXNPYmplY3QudmFsdWVdXTwvc3Bhbj46CiAgICAgICAgICAgIFtbX3NlcGFyYXRlV2l0aENvbW1hcyhfc3RlcHNNaXNtYXRjaC52YWx1ZVN0ZXBzKV1dCiAgICAgICAgICA8L2xpPgogICAgICAgICAgPGxpPgogICAgICAgICAgICA8c3BhbiBjbGFzcz0idGFnLW5hbWUiPltbX3N0ZXBzTWlzbWF0Y2guc2VyaWVzT2JqZWN0Lmxvd2VyXV08L3NwYW4+OgogICAgICAgICAgICBbW19zZXBhcmF0ZVdpdGhDb21tYXMoX3N0ZXBzTWlzbWF0Y2gubG93ZXJTdGVwcyldXQogICAgICAgICAgPC9saT4KICAgICAgICAgIDxsaT4KICAgICAgICAgICAgPHNwYW4gY2xhc3M9InRhZy1uYW1lIj5bW19zdGVwc01pc21hdGNoLnNlcmllc09iamVjdC51cHBlcl1dPC9zcGFuPjoKICAgICAgICAgICAgW1tfc2VwYXJhdGVXaXRoQ29tbWFzKF9zdGVwc01pc21hdGNoLnVwcGVyU3RlcHMpXV0KICAgICAgICAgIDwvbGk+CiAgICAgICAgPC91bD4KICAgICAgPC9kaXY+CiAgICA8L3RlbXBsYXRlPgoKICAgIDxkaXYgaWQ9Im1hdGNoZXMtY29udGFpbmVyIj4KICAgICAgPGRpdiBjbGFzcz0iY29sbGFwc2libGUtbGlzdC10aXRsZSI+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19zZXJpZXNOYW1lcy5sZW5ndGhdXSI+CiAgICAgICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICAgICAgaWNvbj0iW1tfZ2V0VG9nZ2xlQ29sbGFwc2libGVJY29uKF9tYXRjaGVzTGlzdE9wZW5lZCldXSIKICAgICAgICAgICAgb24tY2xpY2s9Il90b2dnbGVNYXRjaGVzT3BlbiIKICAgICAgICAgICAgY2xhc3M9InRvZ2dsZS1tYXRjaGVzLWJ1dHRvbiIKICAgICAgICAgID4KICAgICAgICAgIDwvcGFwZXItaWNvbi1idXR0b24+CiAgICAgICAgPC90ZW1wbGF0ZT4KCiAgICAgICAgPHNwYW4gY2xhc3M9ImNvbGxhcHNpYmxlLXRpdGxlLXRleHQiPgogICAgICAgICAgTWF0Y2hlcyAoW1tfc2VyaWVzTmFtZXMubGVuZ3RoXV0pCiAgICAgICAgPC9zcGFuPgogICAgICA8L2Rpdj4KICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19zZXJpZXNOYW1lcy5sZW5ndGhdXSI+CiAgICAgICAgPGlyb24tY29sbGFwc2Ugb3BlbmVkPSJbW19tYXRjaGVzTGlzdE9wZW5lZF1dIj4KICAgICAgICAgIDxkaXYgaWQ9Im1hdGNoZXMtbGlzdCI+CiAgICAgICAgICAgIDx0ZW1wbGF0ZQogICAgICAgICAgICAgIGlzPSJkb20tcmVwZWF0IgogICAgICAgICAgICAgIGl0ZW1zPSJbW19zZXJpZXNOYW1lc11dIgogICAgICAgICAgICAgIGFzPSJzZXJpZXNOYW1lIgogICAgICAgICAgICAgIGlkPSJtYXRjaC1saXN0LXJlcGVhdCIKICAgICAgICAgICAgICBvbi1kb20tY2hhbmdlPSJfbWF0Y2hMaXN0RW50cnlDb2xvclVwZGF0ZWQiCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtYXRjaC1saXN0LWVudHJ5Ij4KICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJtYXRjaC1lbnRyeS1zeW1ib2wiPgogICAgICAgICAgICAgICAgICBbW19kZXRlcm1pbmVTeW1ib2woX25hbWVUb0RhdGFTZXJpZXMsIHNlcmllc05hbWUpXV0KICAgICAgICAgICAgICAgIDwvc3Bhbj4KICAgICAgICAgICAgICAgIFtbc2VyaWVzTmFtZV1dCiAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2lyb24tY29sbGFwc2U+CiAgICAgIDwvdGVtcGxhdGU+CiAgICA8L2Rpdj4KCiAgICA8c3R5bGUgaW5jbHVkZT0idGYtY3VzdG9tLXNjYWxhci1jYXJkLXN0eWxlIj48L3N0eWxlPgogICAgPHN0eWxlPgogICAgICAuZXJyb3ItY29udGVudCB7CiAgICAgICAgYmFja2dyb3VuZDogI2YwMDsKICAgICAgICBib3JkZXItcmFkaXVzOiA1cHg7CiAgICAgICAgY29sb3I6ICNmZmY7CiAgICAgICAgbWFyZ2luOiAxMHB4IDAgMCAwOwogICAgICAgIHBhZGRpbmc6IDEwcHg7CiAgICAgIH0KCiAgICAgIC5lcnJvci1pY29uIHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBmaWxsOiAjZmZmOwogICAgICAgIG1hcmdpbjogMCBhdXRvIDVweCBhdXRvOwogICAgICB9CgogICAgICAuaW52YWxpZC1yZWdleCB7CiAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICAgIH0KCiAgICAgIC5lcnJvci1jb250ZW50IHVsIHsKICAgICAgICBtYXJnaW46IDFweCAwIDAgMDsKICAgICAgICBwYWRkaW5nOiAwIDAgMCAxOXB4OwogICAgICB9CgogICAgICAudGFnLW5hbWUgewogICAgICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgICB9CgogICAgICAuY29sbGFwc2libGUtbGlzdC10aXRsZSB7CiAgICAgICAgbWFyZ2luOiAxMHB4IDAgNXB4IDA7CiAgICAgIH0KCiAgICAgIC5jb2xsYXBzaWJsZS10aXRsZS10ZXh0IHsKICAgICAgICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlOwogICAgICB9CgogICAgICAjbWF0Y2hlcy1saXN0IHsKICAgICAgICBtYXgtaGVpZ2h0OiAyMDBweDsKICAgICAgICBvdmVyZmxvdy15OiBhdXRvOwogICAgICB9CgogICAgICAubWF0Y2gtbGlzdC1lbnRyeSB7CiAgICAgICAgbWFyZ2luOiAwIDAgNXB4IDA7CiAgICAgIH0KCiAgICAgIC5tYXRjaC1lbnRyeS1zeW1ib2wgewogICAgICAgIGZvbnQtZmFtaWx5OiBhcmlhbCwgc2Fucy1zZXJpZjsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgICAgd2lkdGg6IDEwcHg7CiAgICAgIH0KCiAgICAgIC5taXNzaW5nLXRhZ3MtZm9yLXJ1bi1jb250YWluZXIgewogICAgICAgIG1hcmdpbjogOHB4IDAgMCAwOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLEpyLnByb3RvdHlwZSwicnVucyIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxKci5wcm90b3R5cGUsInhUeXBlIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSnIucHJvdG90eXBlLCJhY3RpdmUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sSnIucHJvdG90eXBlLCJ0aXRsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sSnIucHJvdG90eXBlLCJtYXJnaW5DaGFydFNlcmllcyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLEpyLnByb3RvdHlwZSwiaWdub3JlWU91dGxpZXJzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEFlKV0sSnIucHJvdG90eXBlLCJyZXF1ZXN0TWFuYWdlciIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLEpyLnByb3RvdHlwZSwic2hvd0Rvd25sb2FkTGlua3MiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sSnIucHJvdG90eXBlLCJ0YWdNZXRhZGF0YSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxKci5wcm90b3R5cGUsInRvb2x0aXBTb3J0aW5nTWV0aG9kIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEpyLnByb3RvdHlwZSwiX2NvbG9yU2NhbGUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxKci5wcm90b3R5cGUsIl90YWdGaWx0ZXJJbnZhbGlkIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEpyLnByb3RvdHlwZSwiX25hbWVUb0RhdGFTZXJpZXMiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSnIucHJvdG90eXBlLCJfZXhwYW5kZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxKci5wcm90b3R5cGUsIl9sb2dTY2FsZUFjdGl2ZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbildLEpyLnByb3RvdHlwZSwiX3JlcXVlc3REYXRhIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEpyLnByb3RvdHlwZSwiX3J1blRvTmV4dEF2YWlsYWJsZVN5bWJvbEluZGV4Iix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSnIucHJvdG90eXBlLCJfbWF0Y2hlc0xpc3RPcGVuZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sSnIucHJvdG90eXBlLCJfZmlsbEFyZWEiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLEpyLnByb3RvdHlwZSwiX3Rvb2x0aXBDb2x1bW5zIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxKci5wcm90b3R5cGUsIl9taXNzaW5nVGFncyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLEpyLnByb3RvdHlwZSwiX21pc3NpbmdUYWdzQ29sbGFwc2libGVPcGVuZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sSnIucHJvdG90eXBlLCJfc3RlcHNNaXNtYXRjaCIsdm9pZCAwKTtFKFtCdCgiX25hbWVUb0RhdGFTZXJpZXMiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLEpyLnByb3RvdHlwZSwiX3VwZGF0ZUNoYXJ0IixudWxsKTtFKFtSdCgiX25hbWVUb0RhdGFTZXJpZXMiLCJydW5zIiksdygiZGVzaWduOnR5cGUiLE9iamVjdCksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sSnIucHJvdG90eXBlLCJfc2VyaWVzTmFtZXMiLG51bGwpO0UoW0J0KCJfdGFnRmlsdGVyIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxKci5wcm90b3R5cGUsIl9yZWZyZXNoRGF0YVNlcmllcyIsbnVsbCk7RShbUnQoIm1hcmdpbkNoYXJ0U2VyaWVzIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sSnIucHJvdG90eXBlLCJfdGFnRmlsdGVyIixudWxsKTtFKFtSdCgidGl0bGUiKSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxKci5wcm90b3R5cGUsIl90aXRsZURpc3BsYXlTdHJpbmciLG51bGwpO0pyPUUoW3l0KCJ0Zi1jdXN0b20tc2NhbGFyLW1hcmdpbi1jaGFydC1jYXJkIildLEpyKTt2YXIgbzA9RWUoT2UoKSwxKTt2YXIga0I9e307S3Moa0Ise0Jhc2VTdG9yZTooKT0+YnAsQ2FuY2VsbGVyOigpPT5hbixFbnZpcm9ubWVudFN0b3JlOigpPT5IOSxFeHBlcmltZW50c1N0b3JlOigpPT5lUixIdHRwTWV0aG9kVHlwZTooKT0+QW0sSW52YWxpZFJlcXVlc3RPcHRpb25zRXJyb3I6KCk9PlZ4LExpc3RlbktleTooKT0+QjksUmVxdWVzdENhbmNlbGxhdGlvbkVycm9yOigpPT5sOSxSZXF1ZXN0TWFuYWdlcjooKT0+QWUsUmVxdWVzdE5ldHdvcmtFcnJvcjooKT0+Y0UsUmVxdWVzdE9wdGlvbnM6KCk9PlV4LFJ1bnNTdG9yZTooKT0+VjksVFlQRVM6KCk9Pkx4ZSxhZGRQYXJhbXM6KCk9PkNuLGNyZWF0ZVJvdXRlcjooKT0+UGd0LGNyZWF0ZVNlYXJjaFBhcmFtOigpPT5lVyxlbnZpcm9ubWVudFN0b3JlOigpPT5pYixleHBlcmltZW50c1N0b3JlOigpPT5yUixmaWx0ZXJUYWdzOigpPT5SeGUsZ2V0Um91dGVyOigpPT52ZSxnZXRSdW5zTmFtZWQ6KCk9Pmt4ZSxnZXRUYWdzOigpPT4kaSxydW5zU3RvcmU6KCk9PndwLHNldFJvdXRlcjooKT0+SHhlfSk7dmFyIEVuPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmFjdGl2ZT0hMCx0aGlzLl9jb2xvclNjYWxlPW5ldyBXMSh7c2NhbGU6Zm59KSx0aGlzLl9uYW1lVG9EYXRhU2VyaWVzPXt9LHRoaXMuX2V4cGFuZGVkPSExLHRoaXMuX3JlcXVlc3REYXRhPSh0LHIsbik9PntsZXQgbz12ZSgpLnBsdWdpblJvdXRlKCJjdXN0b21fc2NhbGFycyIsIi9zY2FsYXJzIik7UHJvbWlzZS5hbGwodC5tYXAoYT0+e2xldCBzPWEsbD10aGlzLl90YWdGaWx0ZXIsYz1DbihvLHt0YWc6bCxydW46c30pO3JldHVybiB0aGlzLnJlcXVlc3RNYW5hZ2VyLnJlcXVlc3QoYykudGhlbih1PT52b2lkIHIoe2l0ZW06YSxkYXRhOnV9KSl9KSkuZmluYWxseSgoKT0+dm9pZCBuKCkpfSx0aGlzLl9ydW5Ub05leHRBdmFpbGFibGVTeW1ib2xJbmRleD17fSx0aGlzLl9tYXRjaGVzTGlzdE9wZW5lZD0hMX1yZWxvYWQoKXt0aGlzLiQubG9hZGVyLnJlbG9hZCgpfXJlZHJhdygpe3RoaXMuJC5sb2FkZXIucmVkcmF3KCl9X3RvZ2dsZUV4cGFuZGVkKHQpe3RoaXMuc2V0KCJfZXhwYW5kZWQiLCF0aGlzLl9leHBhbmRlZCksdGhpcy5yZWRyYXcoKX1fdG9nZ2xlTG9nU2NhbGUoKXt0aGlzLnNldCgiX2xvZ1NjYWxlQWN0aXZlIiwhdGhpcy5fbG9nU2NhbGVBY3RpdmUpfV9yZXNldERvbWFpbigpe2xldCB0PXRoaXMuJC5sb2FkZXI7dCYmdC5yZXNldERvbWFpbigpfV9jc3ZVcmwodCxyKXtpZighcilyZXR1cm4iIjtsZXQgbj10aGlzLl9kb3dubG9hZERhdGFVcmwodCxyKTtyZXR1cm4gQ24obix7Zm9ybWF0OiJjc3YifSl9X2pzb25VcmwodCxyKXtpZighcilyZXR1cm4iIjtsZXQgbj10aGlzLl9kb3dubG9hZERhdGFVcmwodCxyKTtyZXR1cm4gQ24obix7Zm9ybWF0OiJqc29uIn0pfV9kb3dubG9hZERhdGFVcmwodCxyKXtsZXQgbj10W3JdLGk9e3RhZzpuLmdldFRhZygpLHJ1bjpuLmdldFJ1bigpfTtyZXR1cm4gQ24odmUoKS5wbHVnaW5Sb3V0ZSgiY3VzdG9tX3NjYWxhcnMiLCIvZG93bmxvYWRfZGF0YSIpLGkpfV9jcmVhdGVQcm9jZXNzRGF0YUZ1bmN0aW9uKCl7cmV0dXJuKHQscixuKT0+e2lmKG4ucmVnZXhfdmFsaWQpe2xldCBpPW8wLmNsb25lKHRoaXMuX25hbWVUb0RhdGFTZXJpZXMpO28wLmZvck93bihuLnRhZ190b19ldmVudHMsKG8sYSk9PntsZXQgcz1vLm1hcCh1PT4oe3dhbGxfdGltZTpuZXcgRGF0ZSh1WzBdKjFlMyksc3RlcDp1WzFdLHNjYWxhcjp1WzJdfSkpLGw9TEIocixhKSxjPWlbbF07aWYoYyljLnNldERhdGEocyk7ZWxzZXtvMC5pc1VuZGVmaW5lZCh0aGlzLl9ydW5Ub05leHRBdmFpbGFibGVTeW1ib2xJbmRleFtyXSkmJih0aGlzLl9ydW5Ub05leHRBdmFpbGFibGVTeW1ib2xJbmRleFtyXT0wKTtsZXQgdT1SU1t0aGlzLl9ydW5Ub05leHRBdmFpbGFibGVTeW1ib2xJbmRleFtyXV0saD1uZXcgT1MocixhLGwscyx1KTtpW2xdPWg7bGV0IGY9UlMubGVuZ3RoO3RoaXMuX3J1blRvTmV4dEF2YWlsYWJsZVN5bWJvbEluZGV4W3JdPSh0aGlzLl9ydW5Ub05leHRBdmFpbGFibGVTeW1ib2xJbmRleFtyXSsxKSVmfX0pLHRoaXMuc2V0KCJfbmFtZVRvRGF0YVNlcmllcyIsaSl9fX1fdXBkYXRlQ2hhcnQoKXt2YXIgdD10aGlzLl9uYW1lVG9EYXRhU2VyaWVzO09iamVjdC5lbnRyaWVzKHQpLmZvckVhY2goKFtyLG5dKT0+e3RoaXMuJC5sb2FkZXIuc2V0U2VyaWVzRGF0YShyLG4uZ2V0RGF0YSgpKX0pLHRoaXMuJC5sb2FkZXIuY29tbWl0Q2hhbmdlcygpfV9jb21wdXRlU2VsZWN0ZWRSdW5zU2V0KHQpe2xldCByPXt9O3JldHVybiBvMC5mb3JFYWNoKHQsbj0+e3Jbbl09MX0pLHJ9Z2V0IF9zZXJpZXNOYW1lcygpe2xldCB0PW5ldyBTZXQodGhpcy5ydW5zKTtyZXR1cm4gT2JqZWN0LmVudHJpZXModGhpcy5fbmFtZVRvRGF0YVNlcmllcykuZmlsdGVyKChbcixuXSk9PnQuaGFzKG4ucnVuKSkubWFwKChbcl0pPT5yKX1fZGV0ZXJtaW5lQ29sb3IodCxyKXtyZXR1cm4gdC5zY2FsZShyKX1fcmVmcmVzaERhdGFTZXJpZXMoKXt2YXIgdD10aGlzLl90YWdGaWx0ZXI7dGhpcy5zZXQoIl9uYW1lVG9EYXRhU2VyaWVzIix7fSl9X2NyZWF0ZVN5bWJvbEZ1bmN0aW9uKCl7cmV0dXJuIHQ9PnRoaXMuX25hbWVUb0RhdGFTZXJpZXNbdF0uZ2V0U3ltYm9sKCkubWV0aG9kKCl9X2RldGVybWluZVN5bWJvbCh0LHIpe3JldHVybiB0W3JdLmdldFN5bWJvbCgpLmNoYXJhY3Rlcn1nZXQgX3RhZ0ZpbHRlcigpe3ZhciB0PXRoaXMudGFnUmVnZXhlcztyZXR1cm4gdC5sZW5ndGg9PT0xP3RbMF06dC5tYXAocj0+IigiK3IrIikiKS5qb2luKCJ8Iil9X2dldFRvZ2dsZU1hdGNoZXNJY29uKHQpe3JldHVybiB0PyJleHBhbmQtbGVzcyI6ImV4cGFuZC1tb3JlIn1fdG9nZ2xlTWF0Y2hlc09wZW4oKXt0aGlzLnNldCgiX21hdGNoZXNMaXN0T3BlbmVkIiwhdGhpcy5fbWF0Y2hlc0xpc3RPcGVuZWQpfWdldCBfdGl0bGVEaXNwbGF5U3RyaW5nKCl7dmFyIHQ9dGhpcy50aXRsZTtyZXR1cm4gdHx8InVudGl0bGVkIn1fbWF0Y2hMaXN0RW50cnlDb2xvclVwZGF0ZWQodCl7dmFyIG47bGV0IHI9dGhpcy4kJCgiI21hdGNoLWxpc3QtcmVwZWF0Iik7IXJ8fChuPXRoaXMucm9vdCk9PW51bGx8fG4ucXVlcnlTZWxlY3RvckFsbCgiLm1hdGNoLWxpc3QtZW50cnkiKS5mb3JFYWNoKGk9PntsZXQgbz1yLml0ZW1Gb3JFbGVtZW50KGkpO2kuc3R5bGUuY29sb3I9dGhpcy5fZGV0ZXJtaW5lQ29sb3IodGhpcy5fY29sb3JTY2FsZSxvKX0pfX07RW4udGVtcGxhdGU9UWAKICAgIDx0Zi1jYXJkLWhlYWRpbmcgZGlzcGxheS1uYW1lPSJbW190aXRsZURpc3BsYXlTdHJpbmddXSI+PC90Zi1jYXJkLWhlYWRpbmc+CiAgICA8ZGl2IGlkPSJ0Zi1saW5lLWNoYXJ0LWRhdGEtbG9hZGVyLWNvbnRhaW5lciI+CiAgICAgIDx0Zi1saW5lLWNoYXJ0LWRhdGEtbG9hZGVyCiAgICAgICAgaWQ9ImxvYWRlciIKICAgICAgICBhY3RpdmU9IltbYWN0aXZlXV0iCiAgICAgICAgY29sb3Itc2NhbGU9IltbX2NvbG9yU2NhbGVdXSIKICAgICAgICBkYXRhLXNlcmllcz0iW1tfc2VyaWVzTmFtZXNdXSIKICAgICAgICBpZ25vcmUteS1vdXRsaWVycz0iW1tpZ25vcmVZT3V0bGllcnNdXSIKICAgICAgICBsb2FkLWtleT0iW1tfdGFnRmlsdGVyXV0iCiAgICAgICAgZGF0YS10by1sb2FkPSJbW3J1bnNdXSIKICAgICAgICByZXF1ZXN0LWRhdGE9IltbX3JlcXVlc3REYXRhXV0iCiAgICAgICAgbG9nLXNjYWxlLWFjdGl2ZT0iW1tfbG9nU2NhbGVBY3RpdmVdXSIKICAgICAgICBsb2FkLWRhdGEtY2FsbGJhY2s9IltbX2NyZWF0ZVByb2Nlc3NEYXRhRnVuY3Rpb24oKV1dIgogICAgICAgIHJlcXVlc3QtbWFuYWdlcj0iW1tyZXF1ZXN0TWFuYWdlcl1dIgogICAgICAgIHNtb290aGluZy1lbmFibGVkPSJbW3Ntb290aGluZ0VuYWJsZWRdXSIKICAgICAgICBzbW9vdGhpbmctd2VpZ2h0PSJbW3Ntb290aGluZ1dlaWdodF1dIgogICAgICAgIHN5bWJvbC1mdW5jdGlvbj0iW1tfY3JlYXRlU3ltYm9sRnVuY3Rpb24oKV1dIgogICAgICAgIHRvb2x0aXAtc29ydGluZy1tZXRob2Q9IltbdG9vbHRpcFNvcnRpbmdNZXRob2RdXSIKICAgICAgICB4LXR5cGU9IltbeFR5cGVdXSIKICAgICAgPgogICAgICA8L3RmLWxpbmUtY2hhcnQtZGF0YS1sb2FkZXI+CiAgICA8L2Rpdj4KICAgIDxkaXYgaWQ9ImJ1dHRvbnMiPgogICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICBzZWxlY3RlZCQ9IltbX2V4cGFuZGVkXV0iCiAgICAgICAgaWNvbj0iZnVsbHNjcmVlbiIKICAgICAgICBvbi10YXA9Il90b2dnbGVFeHBhbmRlZCIKICAgICAgPjwvcGFwZXItaWNvbi1idXR0b24+CiAgICAgIDxwYXBlci1pY29uLWJ1dHRvbgogICAgICAgIHNlbGVjdGVkJD0iW1tfbG9nU2NhbGVBY3RpdmVdXSIKICAgICAgICBpY29uPSJsaW5lLXdlaWdodCIKICAgICAgICBvbi10YXA9Il90b2dnbGVMb2dTY2FsZSIKICAgICAgICB0aXRsZT0iVG9nZ2xlIHktYXhpcyBsb2cgc2NhbGUiCiAgICAgID48L3BhcGVyLWljb24tYnV0dG9uPgogICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICBpY29uPSJzZXR0aW5ncy1vdmVyc2NhbiIKICAgICAgICBvbi10YXA9Il9yZXNldERvbWFpbiIKICAgICAgICB0aXRsZT0iRml0IGRvbWFpbiB0byBkYXRhIgogICAgICA+PC9wYXBlci1pY29uLWJ1dHRvbj4KICAgICAgPHNwYW4gc3R5bGU9ImZsZXgtZ3JvdzogMSI+PC9zcGFuPgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9Iltbc2hvd0Rvd25sb2FkTGlua3NdXSI+CiAgICAgICAgPGRpdiBjbGFzcz0iZG93bmxvYWQtbGlua3MiPgogICAgICAgICAgPHBhcGVyLWRyb3Bkb3duLW1lbnUKICAgICAgICAgICAgbm8tbGFiZWwtZmxvYXQ9InRydWUiCiAgICAgICAgICAgIGxhYmVsPSJzZXJpZXMgdG8gZG93bmxvYWQiCiAgICAgICAgICAgIHNlbGVjdGVkLWl0ZW0tbGFiZWw9Int7X2RhdGFTZXJpZXNOYW1lVG9Eb3dubG9hZH19IgogICAgICAgICAgPgogICAgICAgICAgICA8cGFwZXItbGlzdGJveCBjbGFzcz0iZHJvcGRvd24tY29udGVudCIgc2xvdD0iZHJvcGRvd24tY29udGVudCI+CiAgICAgICAgICAgICAgPHRlbXBsYXRlCiAgICAgICAgICAgICAgICBpcz0iZG9tLXJlcGVhdCIKICAgICAgICAgICAgICAgIGl0ZW1zPSJbW19zZXJpZXNOYW1lc11dIgogICAgICAgICAgICAgICAgYXM9ImRhdGFTZXJpZXNOYW1lIgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDxwYXBlci1pdGVtIG5vLWxhYmVsLWZsb2F0PSJ0cnVlIgogICAgICAgICAgICAgICAgICA+W1tkYXRhU2VyaWVzTmFtZV1dPC9wYXBlci1pdGVtCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgPC9wYXBlci1saXN0Ym94PgogICAgICAgICAgPC9wYXBlci1kcm9wZG93bi1tZW51PgogICAgICAgICAgPGEKICAgICAgICAgICAgZG93bmxvYWQ9IltbX2RhdGFTZXJpZXNOYW1lVG9Eb3dubG9hZF1dLmNzdiIKICAgICAgICAgICAgaHJlZj0iW1tfY3N2VXJsKF9uYW1lVG9EYXRhU2VyaWVzLCBfZGF0YVNlcmllc05hbWVUb0Rvd25sb2FkKV1dIgogICAgICAgICAgICA+Q1NWPC9hCiAgICAgICAgICA+CiAgICAgICAgICA8YQogICAgICAgICAgICBkb3dubG9hZD0iW1tfZGF0YVNlcmllc05hbWVUb0Rvd25sb2FkXV0uanNvbiIKICAgICAgICAgICAgaHJlZj0iW1tfanNvblVybChfbmFtZVRvRGF0YVNlcmllcywgX2RhdGFTZXJpZXNOYW1lVG9Eb3dubG9hZCldXSIKICAgICAgICAgICAgPkpTT048L2EKICAgICAgICAgID4KICAgICAgICA8L2Rpdj4KICAgICAgPC90ZW1wbGF0ZT4KICAgIDwvZGl2PgogICAgPGRpdiBpZD0ibWF0Y2hlcy1jb250YWluZXIiPgogICAgICA8ZGl2IGlkPSJtYXRjaGVzLWxpc3QtdGl0bGUiPgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfc2VyaWVzTmFtZXMubGVuZ3RoXV0iPgogICAgICAgICAgPHBhcGVyLWljb24tYnV0dG9uCiAgICAgICAgICAgIGljb249IltbX2dldFRvZ2dsZU1hdGNoZXNJY29uKF9tYXRjaGVzTGlzdE9wZW5lZCldXSIKICAgICAgICAgICAgb24tY2xpY2s9Il90b2dnbGVNYXRjaGVzT3BlbiIKICAgICAgICAgICAgY2xhc3M9InRvZ2dsZS1tYXRjaGVzLWJ1dHRvbiIKICAgICAgICAgID4KICAgICAgICAgIDwvcGFwZXItaWNvbi1idXR0b24+CiAgICAgICAgPC90ZW1wbGF0ZT4KCiAgICAgICAgPHNwYW4gY2xhc3M9Im1hdGNoZXMtdGV4dCI+IE1hdGNoZXMgKFtbX3Nlcmllc05hbWVzLmxlbmd0aF1dKSA8L3NwYW4+CiAgICAgIDwvZGl2PgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX3Nlcmllc05hbWVzLmxlbmd0aF1dIj4KICAgICAgICA8aXJvbi1jb2xsYXBzZSBvcGVuZWQ9IltbX21hdGNoZXNMaXN0T3BlbmVkXV0iPgogICAgICAgICAgPGRpdiBpZD0ibWF0Y2hlcy1saXN0Ij4KICAgICAgICAgICAgPHRlbXBsYXRlCiAgICAgICAgICAgICAgaXM9ImRvbS1yZXBlYXQiCiAgICAgICAgICAgICAgaXRlbXM9IltbX3Nlcmllc05hbWVzXV0iCiAgICAgICAgICAgICAgYXM9InNlcmllc05hbWUiCiAgICAgICAgICAgICAgaWQ9Im1hdGNoLWxpc3QtcmVwZWF0IgogICAgICAgICAgICAgIG9uLWRvbS1jaGFuZ2U9Il9tYXRjaExpc3RFbnRyeUNvbG9yVXBkYXRlZCIKICAgICAgICAgICAgPgogICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im1hdGNoLWxpc3QtZW50cnkiPgogICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9Im1hdGNoLWVudHJ5LXN5bWJvbCI+CiAgICAgICAgICAgICAgICAgIFtbX2RldGVybWluZVN5bWJvbChfbmFtZVRvRGF0YVNlcmllcywgc2VyaWVzTmFtZSldXQogICAgICAgICAgICAgICAgPC9zcGFuPgogICAgICAgICAgICAgICAgW1tzZXJpZXNOYW1lXV0KICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvaXJvbi1jb2xsYXBzZT4KICAgICAgPC90ZW1wbGF0ZT4KICAgIDwvZGl2PgoKICAgIDxzdHlsZSBpbmNsdWRlPSJ0Zi1jdXN0b20tc2NhbGFyLWNhcmQtc3R5bGUiPjwvc3R5bGU+CiAgICA8c3R5bGU+CiAgICAgICNtYXRjaGVzLWxpc3QtdGl0bGUgewogICAgICAgIG1hcmdpbjogMTBweCAwIDVweCAwOwogICAgICB9CgogICAgICAjbWF0Y2hlcy1saXN0IHsKICAgICAgICBtYXgtaGVpZ2h0OiAyMDBweDsKICAgICAgICBvdmVyZmxvdy15OiBhdXRvOwogICAgICB9CgogICAgICAubWF0Y2gtbGlzdC1lbnRyeSB7CiAgICAgICAgbWFyZ2luOiAwIDAgNXB4IDA7CiAgICAgIH0KCiAgICAgIC5tYXRjaC1lbnRyeS1zeW1ib2wgewogICAgICAgIGZvbnQtZmFtaWx5OiBhcmlhbCwgc2Fucy1zZXJpZjsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgICAgd2lkdGg6IDEwcHg7CiAgICAgIH0KCiAgICAgIC5tYXRjaGVzLXRleHQgewogICAgICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sRW4ucHJvdG90eXBlLCJydW5zIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLEVuLnByb3RvdHlwZSwieFR5cGUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxFbi5wcm90b3R5cGUsImFjdGl2ZSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxFbi5wcm90b3R5cGUsInRpdGxlIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxFbi5wcm90b3R5cGUsInRhZ1JlZ2V4ZXMiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxFbi5wcm90b3R5cGUsImlnbm9yZVlPdXRsaWVycyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixBZSldLEVuLnByb3RvdHlwZSwicmVxdWVzdE1hbmFnZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxFbi5wcm90b3R5cGUsInNob3dEb3dubG9hZExpbmtzIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sRW4ucHJvdG90eXBlLCJzbW9vdGhpbmdFbmFibGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLEVuLnByb3RvdHlwZSwic21vb3RoaW5nV2VpZ2h0Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEVuLnByb3RvdHlwZSwidGFnTWV0YWRhdGEiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sRW4ucHJvdG90eXBlLCJ0b29sdGlwU29ydGluZ01ldGhvZCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixXMSldLEVuLnByb3RvdHlwZSwiX2NvbG9yU2NhbGUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sRW4ucHJvdG90eXBlLCJfbmFtZVRvRGF0YVNlcmllcyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4scmVmbGVjdFRvQXR0cmlidXRlOiEwfSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxFbi5wcm90b3R5cGUsIl9leHBhbmRlZCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLEVuLnByb3RvdHlwZSwiX2xvZ1NjYWxlQWN0aXZlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKV0sRW4ucHJvdG90eXBlLCJfcmVxdWVzdERhdGEiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sRW4ucHJvdG90eXBlLCJfcnVuVG9OZXh0QXZhaWxhYmxlU3ltYm9sSW5kZXgiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxFbi5wcm90b3R5cGUsIl9tYXRjaGVzTGlzdE9wZW5lZCIsdm9pZCAwKTtFKFtCdCgiX25hbWVUb0RhdGFTZXJpZXMiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLEVuLnByb3RvdHlwZSwiX3VwZGF0ZUNoYXJ0IixudWxsKTtFKFtSdCgiX25hbWVUb0RhdGFTZXJpZXMiLCJydW5zIiksdygiZGVzaWduOnR5cGUiLE9iamVjdCksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sRW4ucHJvdG90eXBlLCJfc2VyaWVzTmFtZXMiLG51bGwpO0UoW0J0KCJfdGFnRmlsdGVyIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxFbi5wcm90b3R5cGUsIl9yZWZyZXNoRGF0YVNlcmllcyIsbnVsbCk7RShbUnQoInRhZ1JlZ2V4ZXMiKSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxFbi5wcm90b3R5cGUsIl90YWdGaWx0ZXIiLG51bGwpO0UoW1J0KCJ0aXRsZSIpLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLEVuLnByb3RvdHlwZSwiX3RpdGxlRGlzcGxheVN0cmluZyIsbnVsbCk7RW49RShbeXQoInRmLWN1c3RvbS1zY2FsYXItbXVsdGktbGluZS1jaGFydC1jYXJkIildLEVuKTt2YXIgam89Y2xhc3MgZXh0ZW5kcyBtdHtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fcmVxdWVzdE1hbmFnZXI9bmV3IEFlKDUwKSx0aGlzLl9jYW5jZWxsZXI9bmV3IGFuLHRoaXMuX3Nob3dEb3dubG9hZExpbmtzPXZwKCJfc2hvd0Rvd25sb2FkTGlua3MiLHtkZWZhdWx0VmFsdWU6ITEsdXNlTG9jYWxTdG9yYWdlOiEwfSkuY2FsbCh0aGlzKSx0aGlzLl9zbW9vdGhpbmdXZWlnaHQ9Z0UoIl9zbW9vdGhpbmdXZWlnaHQiLHtkZWZhdWx0VmFsdWU6LjZ9KS5jYWxsKHRoaXMpLHRoaXMuX2lnbm9yZVlPdXRsaWVycz12cCgiX2lnbm9yZVlPdXRsaWVycyIse2RlZmF1bHRWYWx1ZTohMCx1c2VMb2NhbFN0b3JhZ2U6ITB9KS5jYWxsKHRoaXMpLHRoaXMuX3hUeXBlPSJzdGVwIix0aGlzLl9hY3RpdmU9ITAsdGhpcy5yZWxvYWRPblJlYWR5PSEwLHRoaXMuX3Nob3dEb3dubG9hZExpbmtzT2JzZXJ2ZXI9eHAoIl9zaG93RG93bmxvYWRMaW5rcyIse2RlZmF1bHRWYWx1ZTohMSx1c2VMb2NhbFN0b3JhZ2U6ITB9KSx0aGlzLl9zbW9vdGhpbmdXZWlnaHRPYnNlcnZlcj1fRSgiX3Ntb290aGluZ1dlaWdodCIse2RlZmF1bHRWYWx1ZTouNn0pLHRoaXMuX2lnbm9yZVlPdXRsaWVyc09ic2VydmVyPXhwKCJfaWdub3JlWU91dGxpZXJzIix7ZGVmYXVsdFZhbHVlOiEwLHVzZUxvY2FsU3RvcmFnZTohMH0pfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLnJlbG9hZE9uUmVhZHkmJnRoaXMucmVsb2FkKCl9cmVsb2FkKCl7bGV0IHQ9dmUoKS5wbHVnaW5zTGlzdGluZygpLHI9dGhpcy5fY2FuY2VsbGVyLmNhbmNlbGxhYmxlKG49PntuLmNhbmNlbGxlZHx8KHRoaXMuc2V0KCJfZGF0YU5vdEZvdW5kIiwhbi52YWx1ZS5jdXN0b21fc2NhbGFycyksIXRoaXMuX2RhdGFOb3RGb3VuZCYmdGhpcy5fcmV0cmlldmVMYXlvdXRBbmREYXRhKCkpfSk7dGhpcy5fcmVxdWVzdE1hbmFnZXIucmVxdWVzdCh0KS50aGVuKHIpfV9yZWxvYWRDaGFydHMoKXt2YXIgcjtsZXQgdD0ocj10aGlzLnJvb3QpPT1udWxsP3ZvaWQgMDpyLnF1ZXJ5U2VsZWN0b3JBbGwoInRmLWN1c3RvbS1zY2FsYXItbWFyZ2luLWNoYXJ0LWNhcmQsIHRmLWN1c3RvbS1zY2FsYXItbXVsdGktbGluZS1jaGFydC1jYXJkIik7dD09bnVsbHx8dC5mb3JFYWNoKG49PntuLnJlbG9hZCgpfSl9X3JldHJpZXZlTGF5b3V0QW5kRGF0YSgpe2xldCB0PXZlKCkucGx1Z2luUm91dGUoImN1c3RvbV9zY2FsYXJzIiwiL2xheW91dCIpLHI9dGhpcy5fY2FuY2VsbGVyLmNhbmNlbGxhYmxlKG49PntuLmNhbmNlbGxlZHx8KHRoaXMuc2V0KCJfbGF5b3V0IixuLnZhbHVlKSx0aGlzLl9kYXRhTm90Rm91bmR8fHRoaXMuX3JlbG9hZENoYXJ0cygpKX0pO3RoaXMuX3JlcXVlc3RNYW5hZ2VyLnJlcXVlc3QodCkudGhlbihyKX1nZXQgX3Ntb290aGluZ0VuYWJsZWQoKXt2YXIgdD10aGlzLl9zbW9vdGhpbmdXZWlnaHQ7cmV0dXJuIHQ+MH1nZXQgX2NhdGVnb3JpZXMoKXt2YXIgdD10aGlzLl9sYXlvdXQ7aWYoIXQuY2F0ZWdvcnkpcmV0dXJuW107bGV0IHI9ITE7cmV0dXJuIHRoaXMuX29wZW5lZENhdGVnb3JpZXN8fChyPSEwLHRoaXMuX29wZW5lZENhdGVnb3JpZXM9e30pLHQuY2F0ZWdvcnkubWFwKGk9PihyJiYhaS5jbG9zZWQmJih0aGlzLl9vcGVuZWRDYXRlZ29yaWVzW2kudGl0bGVdPSEwKSx7bmFtZTppLnRpdGxlLGl0ZW1zOmkuY2hhcnQsbWV0YWRhdGE6e3R5cGU6TmEuUFJFRklYX0dST1VQLG9wZW5lZDohIXRoaXMuX29wZW5lZENhdGVnb3JpZXNbaS50aXRsZV19fSkpfV9jYXRlZ29yeU9wZW5lZFRvZ2dsZWQodCl7bGV0IHI9dC50YXJnZXQ7ci5vcGVuZWQ/dGhpcy5fb3BlbmVkQ2F0ZWdvcmllc1tyLmNhdGVnb3J5Lm5hbWVdPSEwOmRlbGV0ZSB0aGlzLl9vcGVuZWRDYXRlZ29yaWVzW3IuY2F0ZWdvcnkubmFtZV19fTtqby50ZW1wbGF0ZT1RYAogICAgPHRmLWRhc2hib2FyZC1sYXlvdXQ+CiAgICAgIDxkaXYgY2xhc3M9InNpZGViYXIiIHNsb3Q9InNpZGViYXIiPgogICAgICAgIDxkaXYgY2xhc3M9InNldHRpbmdzIj4KICAgICAgICAgIDxkaXYgY2xhc3M9InNpZGViYXItc2VjdGlvbiI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxpbmUtaXRlbSI+CiAgICAgICAgICAgICAgPHBhcGVyLWNoZWNrYm94IGNoZWNrZWQ9Int7X3Nob3dEb3dubG9hZExpbmtzfX0iCiAgICAgICAgICAgICAgICA+U2hvdyBkYXRhIGRvd25sb2FkIGxpbmtzPC9wYXBlci1jaGVja2JveAogICAgICAgICAgICAgID4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxpbmUtaXRlbSI+CiAgICAgICAgICAgICAgPHBhcGVyLWNoZWNrYm94IGNoZWNrZWQ9Int7X2lnbm9yZVlPdXRsaWVyc319IgogICAgICAgICAgICAgICAgPklnbm9yZSBvdXRsaWVycyBpbiBjaGFydCBzY2FsaW5nPC9wYXBlci1jaGVja2JveAogICAgICAgICAgICAgID4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgaWQ9InRvb2x0aXAtc29ydGluZyI+CiAgICAgICAgICAgICAgPGRpdiBpZD0idG9vbHRpcC1zb3J0aW5nLWxhYmVsIj5Ub29sdGlwIHNvcnRpbmcgbWV0aG9kOjwvZGl2PgogICAgICAgICAgICAgIDxwYXBlci1kcm9wZG93bi1tZW51CiAgICAgICAgICAgICAgICBuby1sYWJlbC1mbG9hdD0iIgogICAgICAgICAgICAgICAgc2VsZWN0ZWQtaXRlbS1sYWJlbD0ie3tfdG9vbHRpcFNvcnRpbmdNZXRob2R9fSIKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8cGFwZXItbGlzdGJveAogICAgICAgICAgICAgICAgICBjbGFzcz0iZHJvcGRvd24tY29udGVudCIKICAgICAgICAgICAgICAgICAgc2VsZWN0ZWQ9IjAiCiAgICAgICAgICAgICAgICAgIHNsb3Q9ImRyb3Bkb3duLWNvbnRlbnQiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDxwYXBlci1pdGVtPmRlZmF1bHQ8L3BhcGVyLWl0ZW0+CiAgICAgICAgICAgICAgICAgIDxwYXBlci1pdGVtPmRlc2NlbmRpbmc8L3BhcGVyLWl0ZW0+CiAgICAgICAgICAgICAgICAgIDxwYXBlci1pdGVtPmFzY2VuZGluZzwvcGFwZXItaXRlbT4KICAgICAgICAgICAgICAgICAgPHBhcGVyLWl0ZW0+bmVhcmVzdDwvcGFwZXItaXRlbT4KICAgICAgICAgICAgICAgIDwvcGFwZXItbGlzdGJveD4KICAgICAgICAgICAgICA8L3BhcGVyLWRyb3Bkb3duLW1lbnU+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyLXNlY3Rpb24iPgogICAgICAgICAgICA8dGYtc21vb3RoaW5nLWlucHV0CiAgICAgICAgICAgICAgd2VpZ2h0PSJ7e19zbW9vdGhpbmdXZWlnaHR9fSIKICAgICAgICAgICAgICBzdGVwPSIwLjAwMSIKICAgICAgICAgICAgICBtaW49IjAiCiAgICAgICAgICAgICAgbWF4PSIxIgogICAgICAgICAgICA+PC90Zi1zbW9vdGhpbmctaW5wdXQ+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDxkaXYgY2xhc3M9InNpZGViYXItc2VjdGlvbiI+CiAgICAgICAgICAgIDx0Zi1vcHRpb24tc2VsZWN0b3IKICAgICAgICAgICAgICBpZD0ieC10eXBlLXNlbGVjdG9yIgogICAgICAgICAgICAgIG5hbWU9Ikhvcml6b250YWwgQXhpcyIKICAgICAgICAgICAgICBzZWxlY3RlZC1pZD0ie3tfeFR5cGV9fSIKICAgICAgICAgICAgPgogICAgICAgICAgICAgIDxwYXBlci1idXR0b24gaWQ9InN0ZXAiPnN0ZXA8L3BhcGVyLWJ1dHRvbgogICAgICAgICAgICAgID48IS0tCiAgICAgICAgICAgIC0tPjxwYXBlci1idXR0b24gaWQ9InJlbGF0aXZlIj5yZWxhdGl2ZTwvcGFwZXItYnV0dG9uCiAgICAgICAgICAgICAgPjwhLS0KICAgICAgICAgICAgLS0+PHBhcGVyLWJ1dHRvbiBpZD0id2FsbF90aW1lIj53YWxsPC9wYXBlci1idXR0b24+CiAgICAgICAgICAgIDwvdGYtb3B0aW9uLXNlbGVjdG9yPgogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhci1zZWN0aW9uIHJ1bnMtc2VsZWN0b3IiPgogICAgICAgICAgPHRmLXJ1bnMtc2VsZWN0b3Igc2VsZWN0ZWQtcnVucz0ie3tfc2VsZWN0ZWRSdW5zfX0iPgogICAgICAgICAgPC90Zi1ydW5zLXNlbGVjdG9yPgogICAgICAgIDwvZGl2PgogICAgICA8L2Rpdj4KICAgICAgPGRpdiBjbGFzcz0iY2VudGVyIiBzbG90PSJjZW50ZXIiIGlkPSJjYXRlZ29yaWVzLWNvbnRhaW5lciI+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19kYXRhTm90Rm91bmRdXSI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJuby1kYXRhLXdhcm5pbmciPgogICAgICAgICAgICA8aDM+VGhlIGN1c3RvbSBzY2FsYXJzIGRhc2hib2FyZCBpcyBpbmFjdGl2ZS48L2gzPgogICAgICAgICAgICA8cD5Qcm9iYWJsZSBjYXVzZXM6PC9wPgogICAgICAgICAgICA8b2w+CiAgICAgICAgICAgICAgPGxpPllvdSBoYXZlbid0IGxhaWQgb3V0IHRoZSBkYXNoYm9hcmQuPC9saT4KICAgICAgICAgICAgICA8bGk+WW91IGhhdmVu4oCZdCB3cml0dGVuIGFueSBzY2FsYXIgZGF0YSB0byB5b3VyIGV2ZW50IGZpbGVzLjwvbGk+CiAgICAgICAgICAgIDwvb2w+CgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICBUbyBsYXkgb3V0IHRoZSBkYXNoYm9hcmQsIHBhc3MgYSA8Y29kZT5MYXlvdXQ8L2NvZGU+IHByb3RvYnVmZmVyCiAgICAgICAgICAgICAgdG8gdGhlIDxjb2RlPnNldF9sYXlvdXQ8L2NvZGU+IG1ldGhvZC4gRm9yIGV4YW1wbGUsCiAgICAgICAgICAgIDwvcD4KICAgICAgICAgICAgPHByZT4KZnJvbSB0ZW5zb3Jib2FyZCBpbXBvcnQgc3VtbWFyeQpmcm9tIHRlbnNvcmJvYXJkLnBsdWdpbnMuY3VzdG9tX3NjYWxhciBpbXBvcnQgbGF5b3V0X3BiMgouLi4KIyBUaGlzIGFjdGlvbiBkb2VzIG5vdCBoYXZlIHRvIGJlIHBlcmZvcm1lZCBhdCBldmVyeSBzdGVwLCBzbyB0aGUgYWN0aW9uIGlzIG5vdAojIHRha2VuIGNhcmUgb2YgYnkgYW4gb3AgaW4gdGhlIGdyYXBoLiBXZSBvbmx5IG5lZWQgdG8gc3BlY2lmeSB0aGUgbGF5b3V0IG9uY2UKIyAoaW5zdGVhZCBvZiBwZXIgc3RlcCkuCmxheW91dF9zdW1tYXJ5ID0gc3VtbWFyeV9saWIuY3VzdG9tX3NjYWxhcl9wYihsYXlvdXRfcGIyLkxheW91dCgKICBjYXRlZ29yeT1bCiAgICBsYXlvdXRfcGIyLkNhdGVnb3J5KAogICAgICB0aXRsZT0nbG9zc2VzJywKICAgICAgY2hhcnQ9WwogICAgICAgICAgbGF5b3V0X3BiMi5DaGFydCgKICAgICAgICAgICAgICB0aXRsZT0nbG9zc2VzJywKICAgICAgICAgICAgICBtdWx0aWxpbmU9bGF5b3V0X3BiMi5NdWx0aWxpbmVDaGFydENvbnRlbnQoCiAgICAgICAgICAgICAgICB0YWc9W3InbG9zcy4qJ10sCiAgICAgICAgICAgICAgKSksCiAgICAgICAgICBsYXlvdXRfcGIyLkNoYXJ0KAogICAgICAgICAgICAgIHRpdGxlPSdiYXonLAogICAgICAgICAgICAgIG1hcmdpbj1sYXlvdXRfcGIyLk1hcmdpbkNoYXJ0Q29udGVudCgKICAgICAgICAgICAgICAgIHNlcmllcz1bCiAgICAgICAgICAgICAgICAgIGxheW91dF9wYjIuTWFyZ2luQ2hhcnRDb250ZW50LlNlcmllcygKICAgICAgICAgICAgICAgICAgICB2YWx1ZT0nbG9zcy9iYXovc2NhbGFyX3N1bW1hcnknLAogICAgICAgICAgICAgICAgICAgIGxvd2VyPSdiYXpfbG93ZXIvYmF6L3NjYWxhcl9zdW1tYXJ5JywKICAgICAgICAgICAgICAgICAgICB1cHBlcj0nYmF6X3VwcGVyL2Jhei9zY2FsYXJfc3VtbWFyeScpLAogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICApKSwKICAgICAgXSksCiAgICBsYXlvdXRfcGIyLkNhdGVnb3J5KAogICAgICB0aXRsZT0ndHJpZyBmdW5jdGlvbnMnLAogICAgICBjaGFydD1bCiAgICAgICAgICBsYXlvdXRfcGIyLkNoYXJ0KAogICAgICAgICAgICAgIHRpdGxlPSd3YXZlIHRyaWcgZnVuY3Rpb25zJywKICAgICAgICAgICAgICBtdWx0aWxpbmU9bGF5b3V0X3BiMi5NdWx0aWxpbmVDaGFydENvbnRlbnQoCiAgICAgICAgICAgICAgICB0YWc9W3IndHJpZ0Z1bmN0aW9ucy9jb3NpbmUnLCByJ3RyaWdGdW5jdGlvbnMvc2luZSddLAogICAgICAgICAgICAgICkpLAogICAgICAgICAgIyBUaGUgcmFuZ2Ugb2YgdGFuZ2VudCBpcyBkaWZmZXJlbnQuIExldCdzIGdpdmUgaXQgaXRzIG93biBjaGFydC4KICAgICAgICAgIGxheW91dF9wYjIuQ2hhcnQoCiAgICAgICAgICAgICAgdGl0bGU9J3RhbicsCiAgICAgICAgICAgICAgbXVsdGlsaW5lPWxheW91dF9wYjIuTXVsdGlsaW5lQ2hhcnRDb250ZW50KAogICAgICAgICAgICAgICAgdGFnPVtyJ3RyaWdGdW5jdGlvbnMvdGFuZ2VudCddLAogICAgICAgICAgICAgICkpLAogICAgICBdLAogICAgICAjIFRoaXMgY2F0ZWdvcnkgd2UgY2FyZSBsZXNzIGFib3V0LiBMZXQncyBtYWtlIGl0IGluaXRpYWxseSBjbG9zZWQuCiAgICAgIGNsb3NlZD1UcnVlKSwKICBdKSkKd3JpdGVyLmFkZF9zdW1tYXJ5KGxheW91dF9zdW1tYXJ5KQo8L3ByZQogICAgICAgICAgICA+CiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgIElmIHlvdeKAmXJlIG5ldyB0byB1c2luZyBUZW5zb3JCb2FyZCwgYW5kIHdhbnQgdG8gZmluZCBvdXQgaG93IHRvCiAgICAgICAgICAgICAgYWRkIGRhdGEgYW5kIHNldCB1cCB5b3VyIGV2ZW50IGZpbGVzLCBjaGVjayBvdXQgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCIKICAgICAgICAgICAgICAgID5SRUFETUU8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIHBlcmhhcHMgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiCiAgICAgICAgICAgICAgICA+VGVuc29yQm9hcmQgdHV0b3JpYWw8L2EKICAgICAgICAgICAgICA+LgogICAgICAgICAgICA8L3A+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1shX2RhdGFOb3RGb3VuZF1dIj4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9IltbX2NhdGVnb3JpZXNdXSIgYXM9ImNhdGVnb3J5Ij4KICAgICAgICAgICAgPHRmLWNhdGVnb3J5LXBhZ2luYXRlZC12aWV3CiAgICAgICAgICAgICAgYXM9ImNoYXJ0IgogICAgICAgICAgICAgIGNhdGVnb3J5PSJbW2NhdGVnb3J5XV0iCiAgICAgICAgICAgICAgZGlzYWJsZS1wYWdpbmF0aW9uCiAgICAgICAgICAgICAgaW5pdGlhbC1vcGVuZWQ9IltbY2F0ZWdvcnkubWV0YWRhdGEub3BlbmVkXV0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbY2hhcnQubXVsdGlsaW5lXV0iPgogICAgICAgICAgICAgICAgICA8dGYtY3VzdG9tLXNjYWxhci1tdWx0aS1saW5lLWNoYXJ0LWNhcmQKICAgICAgICAgICAgICAgICAgICBhY3RpdmU9IltbYWN0aXZlXV0iCiAgICAgICAgICAgICAgICAgICAgcmVxdWVzdC1tYW5hZ2VyPSJbW19yZXF1ZXN0TWFuYWdlcl1dIgogICAgICAgICAgICAgICAgICAgIHJ1bnM9IltbX3NlbGVjdGVkUnVuc11dIgogICAgICAgICAgICAgICAgICAgIHRpdGxlPSJbW2NoYXJ0LnRpdGxlXV0iCiAgICAgICAgICAgICAgICAgICAgeC10eXBlPSJbW194VHlwZV1dIgogICAgICAgICAgICAgICAgICAgIHNtb290aGluZy1lbmFibGVkPSJbW19zbW9vdGhpbmdFbmFibGVkXV0iCiAgICAgICAgICAgICAgICAgICAgc21vb3RoaW5nLXdlaWdodD0iW1tfc21vb3RoaW5nV2VpZ2h0XV0iCiAgICAgICAgICAgICAgICAgICAgdG9vbHRpcC1zb3J0aW5nLW1ldGhvZD0iW1t0b29sdGlwU29ydGluZ01ldGhvZF1dIgogICAgICAgICAgICAgICAgICAgIGlnbm9yZS15LW91dGxpZXJzPSJbW19pZ25vcmVZT3V0bGllcnNdXSIKICAgICAgICAgICAgICAgICAgICBzaG93LWRvd25sb2FkLWxpbmtzPSJbW19zaG93RG93bmxvYWRMaW5rc11dIgogICAgICAgICAgICAgICAgICAgIHRhZy1yZWdleGVzPSJbW2NoYXJ0Lm11bHRpbGluZS50YWddXSIKICAgICAgICAgICAgICAgICAgPjwvdGYtY3VzdG9tLXNjYWxhci1tdWx0aS1saW5lLWNoYXJ0LWNhcmQ+CiAgICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW2NoYXJ0Lm1hcmdpbl1dIj4KICAgICAgICAgICAgICAgICAgPHRmLWN1c3RvbS1zY2FsYXItbWFyZ2luLWNoYXJ0LWNhcmQKICAgICAgICAgICAgICAgICAgICBhY3RpdmU9IltbYWN0aXZlXV0iCiAgICAgICAgICAgICAgICAgICAgcmVxdWVzdC1tYW5hZ2VyPSJbW19yZXF1ZXN0TWFuYWdlcl1dIgogICAgICAgICAgICAgICAgICAgIHJ1bnM9IltbX3NlbGVjdGVkUnVuc11dIgogICAgICAgICAgICAgICAgICAgIHRpdGxlPSJbW2NoYXJ0LnRpdGxlXV0iCiAgICAgICAgICAgICAgICAgICAgeC10eXBlPSJbW194VHlwZV1dIgogICAgICAgICAgICAgICAgICAgIHRvb2x0aXAtc29ydGluZy1tZXRob2Q9IltbdG9vbHRpcFNvcnRpbmdNZXRob2RdXSIKICAgICAgICAgICAgICAgICAgICBpZ25vcmUteS1vdXRsaWVycz0iW1tfaWdub3JlWU91dGxpZXJzXV0iCiAgICAgICAgICAgICAgICAgICAgc2hvdy1kb3dubG9hZC1saW5rcz0iW1tfc2hvd0Rvd25sb2FkTGlua3NdXSIKICAgICAgICAgICAgICAgICAgICBtYXJnaW4tY2hhcnQtc2VyaWVzPSJbW2NoYXJ0Lm1hcmdpbi5zZXJpZXNdXSIKICAgICAgICAgICAgICAgICAgPjwvdGYtY3VzdG9tLXNjYWxhci1tYXJnaW4tY2hhcnQtY2FyZD4KICAgICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgPC90Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldz4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPC9kaXY+CiAgICA8L3RmLWRhc2hib2FyZC1sYXlvdXQ+CgogICAgPHN0eWxlIGluY2x1ZGU9ImRhc2hib2FyZC1zdHlsZSI+PC9zdHlsZT4KICAgIDxzdHlsZT4KICAgICAgI3Rvb2x0aXAtc29ydGluZyB7CiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgICBtYXJnaW4tdG9wOiAxNXB4OwogICAgICB9CiAgICAgICN0b29sdGlwLXNvcnRpbmcgcGFwZXItZHJvcGRvd24tbWVudSB7CiAgICAgICAgbWFyZ2luLWxlZnQ6IDEwcHg7CiAgICAgICAgLS1wYXBlci1pbnB1dC1jb250YWluZXItZm9jdXMtY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIHdpZHRoOiAxMDVweDsKICAgICAgfQogICAgICAubGluZS1pdGVtIHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBwYWRkaW5nLXRvcDogNXB4OwogICAgICB9CiAgICAgIC5uby1kYXRhLXdhcm5pbmcgewogICAgICAgIG1heC13aWR0aDogNTQwcHg7CiAgICAgICAgbWFyZ2luOiA4MHB4IGF1dG8gMCBhdXRvOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsQWUpXSxqby5wcm90b3R5cGUsIl9yZXF1ZXN0TWFuYWdlciIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixhbildLGpvLnByb3RvdHlwZSwiX2NhbmNlbGxlciIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sam8ucHJvdG90eXBlLCJfc2VsZWN0ZWRSdW5zIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbixub3RpZnk6ITAsb2JzZXJ2ZXI6Il9zaG93RG93bmxvYWRMaW5rc09ic2VydmVyIn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sam8ucHJvdG90eXBlLCJfc2hvd0Rvd25sb2FkTGlua3MiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXIsbm90aWZ5OiEwLG9ic2VydmVyOiJfc21vb3RoaW5nV2VpZ2h0T2JzZXJ2ZXIifSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLGpvLnByb3RvdHlwZSwiX3Ntb290aGluZ1dlaWdodCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9pZ25vcmVZT3V0bGllcnNPYnNlcnZlciJ9KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGpvLnByb3RvdHlwZSwiX2lnbm9yZVlPdXRsaWVycyIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxqby5wcm90b3R5cGUsIl94VHlwZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxqby5wcm90b3R5cGUsIl9sYXlvdXQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxqby5wcm90b3R5cGUsIl9kYXRhTm90Rm91bmQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sam8ucHJvdG90eXBlLCJfb3BlbmVkQ2F0ZWdvcmllcyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGpvLnByb3RvdHlwZSwiX2FjdGl2ZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGpvLnByb3RvdHlwZSwicmVsb2FkT25SZWFkeSIsdm9pZCAwKTtFKFtSdCgiX3Ntb290aGluZ1dlaWdodCIpLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxqby5wcm90b3R5cGUsIl9zbW9vdGhpbmdFbmFibGVkIixudWxsKTtFKFtSdCgiX2xheW91dCIpLHcoImRlc2lnbjp0eXBlIixBcnJheSksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sam8ucHJvdG90eXBlLCJfY2F0ZWdvcmllcyIsbnVsbCk7am89RShbeXQoInRmLWN1c3RvbS1zY2FsYXItZGFzaGJvYXJkIildLGpvKTt2YXIgTkI9RWUoT2UoKSwxKTt2YXIgc3N0PUVlKE9lKCksMSksYmE9RWUod2woKSwxKTt2YXIgUkI9Y2xhc3N7Y29uc3RydWN0b3IodCxyKXt0aGlzLnJ1bjJkYXRhc2V0cz17fSx0aGlzLmNvbG9yU2NhbGU9cix0aGlzLmJ1aWxkQ2hhcnQodCl9Z2V0RGF0YXNldCh0KXtyZXR1cm4gdGhpcy5ydW4yZGF0YXNldHNbdF09PT12b2lkIDAmJih0aGlzLnJ1bjJkYXRhc2V0c1t0XT1uZXcgYmEuRGF0YXNldChbXSx7cnVuOnR9KSksdGhpcy5ydW4yZGF0YXNldHNbdF19YnVpbGRDaGFydCh0KXt0aGlzLm91dGVyJiZ0aGlzLm91dGVyLmRlc3Ryb3koKTtsZXQgcj12Qih0KTt0aGlzLnhBY2Nlc3Nvcj1yLmFjY2Vzc29yLHRoaXMueFNjYWxlPXIuc2NhbGUsdGhpcy54QXhpcz1yLmF4aXMsdGhpcy54QXhpcy5tYXJnaW4oMCksdGhpcy54QXhpcy50aWNrTGFiZWxQYWRkaW5nKDMpLHRoaXMueVNjYWxlPW5ldyBiYS5TY2FsZXMuTGluZWFyLHRoaXMueUF4aXM9bmV3IGJhLkF4ZXMuTnVtZXJpYyh0aGlzLnlTY2FsZSwibGVmdCIpO2xldCBuPVd1KGRCKTt0aGlzLnlBeGlzLm1hcmdpbigwKS50aWNrTGFiZWxQYWRkaW5nKDUpLmZvcm1hdHRlcihuKSx0aGlzLnlBeGlzLnVzZXNUZXh0V2lkdGhBcHByb3hpbWF0aW9uKCEwKTtsZXQgaT10aGlzLmJ1aWxkUGxvdCh0aGlzLnhBY2Nlc3Nvcix0aGlzLnhTY2FsZSx0aGlzLnlTY2FsZSk7dGhpcy5ncmlkbGluZXM9bmV3IGJhLkNvbXBvbmVudHMuR3JpZGxpbmVzKHRoaXMueFNjYWxlLHRoaXMueVNjYWxlKSx0aGlzLmNlbnRlcj1uZXcgYmEuQ29tcG9uZW50cy5Hcm91cChbdGhpcy5ncmlkbGluZXMsaV0pLHRoaXMub3V0ZXI9bmV3IGJhLkNvbXBvbmVudHMuVGFibGUoW1t0aGlzLnlBeGlzLHRoaXMuY2VudGVyXSxbbnVsbCx0aGlzLnhBeGlzXV0pfWJ1aWxkUGxvdCh0LHIsbil7bGV0IGk9WzAsMjI4LDE1ODcsMzA4NSw1ZTMsNjkxNSw4NDEzLDk3NzIsMWU0XSxvPXNzdC5yYW5nZShpLmxlbmd0aC0xKS5tYXAoaD0+KGlbaCsxXS1pW2hdKS8yNTAwKSxhPWkubWFwKChoLGYpPT5wPT5wW2ZdWzFdKSxzPTQsbD1hW3NdLGM9c3N0LnJhbmdlKGEubGVuZ3RoLTEpLm1hcChoPT57bGV0IGY9bmV3IGJhLlBsb3RzLkFyZWE7Zi54KHQscik7bGV0IHA9aD5zP2FbaF06YVtoKzFdLGQ9aD5zP2FbaCsxXTphW2hdO3JldHVybiBmLnkoZCxuKSxmLnkwKHApLGYuYXR0cigiZmlsbCIsKGcsXyx5KT0+dGhpcy5jb2xvclNjYWxlLnNjYWxlKHkubWV0YWRhdGEoKS5ydW4pKSxmLmF0dHIoInN0cm9rZSIsKGcsXyx5KT0+dGhpcy5jb2xvclNjYWxlLnNjYWxlKHkubWV0YWRhdGEoKS5ydW4pKSxmLmF0dHIoInN0cm9rZS13ZWlnaHQiLChnLF8seSk9PiIwLjVweCIpLGYuYXR0cigic3Ryb2tlLW9wYWNpdHkiLCgpPT5vW2hdKSxmLmF0dHIoImZpbGwtb3BhY2l0eSIsKCk9Pm9baF0pLGZ9KSx1PW5ldyBiYS5QbG90cy5MaW5lO3JldHVybiB1LngodCxyKSx1LnkobCxuKSx1LmF0dHIoInN0cm9rZSIsKGgsZixwKT0+dGhpcy5jb2xvclNjYWxlLnNjYWxlKHAucnVuKSksdGhpcy5wbG90cz1jLG5ldyBiYS5Db21wb25lbnRzLkdyb3VwKGMpfXNldFZpc2libGVTZXJpZXModCl7dGhpcy5ydW5zPXQ7bGV0IHI9dC5tYXAobj0+dGhpcy5nZXREYXRhc2V0KG4pKTt0aGlzLnBsb3RzLmZvckVhY2gobj0+bi5kYXRhc2V0cyhyKSl9c2V0U2VyaWVzRGF0YSh0LHIpe3RoaXMuZ2V0RGF0YXNldCh0KS5kYXRhKHIpfXJlbmRlclRvKHQpe3RoaXMudGFyZ2V0U1ZHPXQsdGhpcy5vdXRlci5yZW5kZXJUbyh0KX1yZWRyYXcoKXt0aGlzLm91dGVyLnJlZHJhdygpfWRlc3Ryb3koKXt0aGlzLm91dGVyLmRlc3Ryb3koKX19LEJjPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmNvbG9yU2NhbGU9bmV3IGJhLlNjYWxlcy5Db2xvcigpLnJhbmdlKGpiLnNsaWNlKCkpLHRoaXMueFR5cGU9InN0ZXAiLHRoaXMuX3Zpc2libGVTZXJpZXNDYWNoZT1bXSx0aGlzLl9zZXJpZXNEYXRhQ2FjaGU9e30sdGhpcy5fbWFrZUNoYXJ0QXN5bmNDYWxsYmFja0lkPW51bGx9c2V0VmlzaWJsZVNlcmllcyh0KXt0aGlzLl92aXNpYmxlU2VyaWVzQ2FjaGU9dCx0aGlzLl9jaGFydCYmKHRoaXMuX2NoYXJ0LnNldFZpc2libGVTZXJpZXModCksdGhpcy5yZWRyYXcoKSl9c2V0U2VyaWVzRGF0YSh0LHIpe3RoaXMuX3Nlcmllc0RhdGFDYWNoZVt0XT1yLHRoaXMuX2NoYXJ0JiZ0aGlzLl9jaGFydC5zZXRTZXJpZXNEYXRhKHQscil9cmVkcmF3KCl7dGhpcy5fY2hhcnQucmVkcmF3KCl9X21ha2VDaGFydCgpe3ZhciB0PXRoaXMueFR5cGUscj10aGlzLmNvbG9yU2NhbGUsbj10aGlzLl9hdHRhY2hlZDt0aGlzLl9tYWtlQ2hhcnRBc3luY0NhbGxiYWNrSWQhPT1udWxsJiZ0aGlzLmNhbmNlbEFzeW5jKHRoaXMuX21ha2VDaGFydEFzeW5jQ2FsbGJhY2tJZCksdGhpcy5fbWFrZUNoYXJ0QXN5bmNDYWxsYmFja0lkPXRoaXMuYXN5bmMoZnVuY3Rpb24oKXtpZih0aGlzLl9tYWtlQ2hhcnRBc3luY0NhbGxiYWNrSWQ9bnVsbCwhIW4pe3RoaXMuX2NoYXJ0JiZ0aGlzLl9jaGFydC5kZXN0cm95KCk7dmFyIGk9bmV3IFJCKHQsciksbz1IdCh0aGlzLiQuY2hhcnRkaXYpO2kucmVuZGVyVG8obyksdGhpcy5fY2hhcnQ9aX19LDM1MCl9X3JlbG9hZEZyb21DYWNoZSgpe3RoaXMuX2NoYXJ0JiYodGhpcy5fY2hhcnQuc2V0VmlzaWJsZVNlcmllcyh0aGlzLl92aXNpYmxlU2VyaWVzQ2FjaGUpLHRoaXMuX3Zpc2libGVTZXJpZXNDYWNoZS5mb3JFYWNoKGZ1bmN0aW9uKHQpe3RoaXMuX2NoYXJ0LnNldFNlcmllc0RhdGEodCx0aGlzLl9zZXJpZXNEYXRhQ2FjaGVbdF18fFtdKX0uYmluZCh0aGlzKSkpfWF0dGFjaGVkKCl7dGhpcy5fYXR0YWNoZWQ9ITB9ZGV0YWNoZWQoKXt0aGlzLl9hdHRhY2hlZD0hMX19O0JjLnRlbXBsYXRlPVFgCiAgICA8c3R5bGUgaW5jbHVkZT0icGxvdHRhYmxlLXN0eWxlIj48L3N0eWxlPgogICAgPGRpdiBpZD0iY2hhcnRkaXYiPjwvZGl2PgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICAtbW96LXVzZXItc2VsZWN0OiBub25lOwogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgICAgZmxleC1zaHJpbms6IDE7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICB9CiAgICAgICNjaGFydGRpdiB7CiAgICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICAtbW96LXVzZXItc2VsZWN0OiBub25lOwogICAgICAgIGZsZXgtZ3JvdzogMTsKICAgICAgICBmbGV4LXNocmluazogMTsKICAgICAgfQogICAgICAucGxvdHRhYmxlIC5heGlzIHRleHQgewogICAgICAgIGZpbGw6IGN1cnJlbnRDb2xvcjsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLGJhLlNjYWxlcy5Db2xvcildLEJjLnByb3RvdHlwZSwiY29sb3JTY2FsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxCYy5wcm90b3R5cGUsInhUeXBlIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sQmMucHJvdG90eXBlLCJfYXR0YWNoZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsUkIpXSxCYy5wcm90b3R5cGUsIl9jaGFydCIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sQmMucHJvdG90eXBlLCJfdmlzaWJsZVNlcmllc0NhY2hlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEJjLnByb3RvdHlwZSwiX3Nlcmllc0RhdGFDYWNoZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxCYy5wcm90b3R5cGUsIl9tYWtlQ2hhcnRBc3luY0NhbGxiYWNrSWQiLHZvaWQgMCk7RShbQnQoInhUeXBlIiwiY29sb3JTY2FsZSIsIl9hdHRhY2hlZCIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sQmMucHJvdG90eXBlLCJfbWFrZUNoYXJ0IixudWxsKTtFKFtCdCgiX2NoYXJ0IiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxCYy5wcm90b3R5cGUsIl9yZWxvYWRGcm9tQ2FjaGUiLG51bGwpO0JjPUUoW3l0KCJ2ei1kaXN0cmlidXRpb24tY2hhcnQiKV0sQmMpO3ZhciB3YT1jbGFzcyBleHRlbmRzIGtTKEd0KG10KSl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuZ2V0RGF0YUxvYWROYW1lPSh7cnVuOnR9KT0+dCx0aGlzLnJlcXVlc3REYXRhPSh0LHIsbik9PntsZXQgbz12ZSgpLnBsdWdpblJvdXRlKCJkaXN0cmlidXRpb25zIiwiL2Rpc3RyaWJ1dGlvbnMiKTtQcm9taXNlLmFsbCh0Lm1hcChhPT57bGV0IHM9Q24obyx7dGFnOmEudGFnLHJ1bjphLnJ1bn0pO3JldHVybiB0aGlzLnJlcXVlc3RNYW5hZ2VyLnJlcXVlc3QocykudGhlbihsPT52b2lkIHIoe2l0ZW06YSxkYXRhOmx9KSl9KSkuZmluYWxseSgoKT0+dm9pZCBuKCkpfSx0aGlzLmxvYWREYXRhQ2FsbGJhY2s9KHQscixuKT0+e2xldCBpPW4ubWFwKGE9PntsZXRbcyxsLGNdPWE7cmV0dXJuIGMud2FsbF90aW1lPW5ldyBEYXRlKHMqMWUzKSxjLnN0ZXA9bCxjfSksbz10aGlzLmdldERhdGFMb2FkTmFtZShyKTt0aGlzLiQuY2hhcnQuc2V0U2VyaWVzRGF0YShvLGkpLHRoaXMuJC5jaGFydC5zZXRWaXNpYmxlU2VyaWVzKFtvXSl9LHRoaXMuX2NvbG9yU2NhbGU9e3NjYWxlOmZufSx0aGlzLl9leHBhbmRlZD0hMSx0aGlzLl9jYW5jZWxsZXI9bmV3IGFufV9yZWxvYWRPblJ1blRhZ0NoYW5nZSgpe3RoaXMucmVsb2FkKCl9X3VwZGF0ZURhdGFUb0xvYWQoKXt2YXIgdD10aGlzLnJ1bixyPXRoaXMudGFnO3RoaXMuZGF0YVRvTG9hZD1be3J1bjp0LHRhZzpyfV19Z2V0IF9ydW5Db2xvcigpe3ZhciB0PXRoaXMucnVuO3JldHVybiB0aGlzLl9jb2xvclNjYWxlLnNjYWxlKHQpfXJlZHJhdygpe3RoaXMuJC5jaGFydC5yZWRyYXcoKX1fdG9nZ2xlRXhwYW5kZWQodCl7dGhpcy5zZXQoIl9leHBhbmRlZCIsIXRoaXMuX2V4cGFuZGVkKSx0aGlzLnJlZHJhdygpfX07d2EudGVtcGxhdGU9UWAKICAgIDx0Zi1jYXJkLWhlYWRpbmcKICAgICAgdGFnPSJbW3RhZ11dIgogICAgICBydW49IltbcnVuXV0iCiAgICAgIGRpc3BsYXktbmFtZT0iW1t0YWdNZXRhZGF0YS5kaXNwbGF5TmFtZV1dIgogICAgICBkZXNjcmlwdGlvbj0iW1t0YWdNZXRhZGF0YS5kZXNjcmlwdGlvbl1dIgogICAgICBjb2xvcj0iW1tfcnVuQ29sb3JdXSIKICAgID48L3RmLWNhcmQtaGVhZGluZz4KICAgIDwhLS0KICAgICAgVGhlIG1haW4gZGlzdHJpYnV0aW9uIHRoYXQgd2UgcmVuZGVyLiBEYXRhIGlzIHNldCBkaXJlY3RseSB3aXRoCiAgICAgIFxgc2V0U2VyaWVzRGF0YVxgLCBub3Qgd2l0aCBhIGJvdW5kIHByb3BlcnR5LgogICAgLS0+CiAgICA8dnotZGlzdHJpYnV0aW9uLWNoYXJ0CiAgICAgIGlkPSJjaGFydCIKICAgICAgeC10eXBlPSJbW3hUeXBlXV0iCiAgICAgIGNvbG9yLXNjYWxlPSJbW19jb2xvclNjYWxlXV0iCiAgICA+PC92ei1kaXN0cmlidXRpb24tY2hhcnQ+CiAgICA8ZGl2IHN0eWxlPSJkaXNwbGF5OiBmbGV4OyBmbGV4LWRpcmVjdGlvbjogcm93OyI+CiAgICAgIDxwYXBlci1pY29uLWJ1dHRvbgogICAgICAgIHNlbGVjdGVkJD0iW1tfZXhwYW5kZWRdXSIKICAgICAgICBpY29uPSJmdWxsc2NyZWVuIgogICAgICAgIG9uLXRhcD0iX3RvZ2dsZUV4cGFuZGVkIgogICAgICA+PC9wYXBlci1pY29uLWJ1dHRvbj4KICAgIDwvZGl2PgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgIHdpZHRoOiAzMzBweDsKICAgICAgICBoZWlnaHQ6IDIzNXB4OwogICAgICAgIG1hcmdpbi1yaWdodDogMTBweDsKICAgICAgICBtYXJnaW4tYm90dG9tOiAxNXB4OwogICAgICB9CiAgICAgIDpob3N0KFtfZXhwYW5kZWRdKSB7CiAgICAgICAgd2lkdGg6IDcwMHB4OwogICAgICAgIGhlaWdodDogNTAwcHg7CiAgICAgIH0KCiAgICAgIHZ6LWhpc3RvZ3JhbS10aW1lc2VyaWVzIHsKICAgICAgICAtbW96LXVzZXItc2VsZWN0OiBub25lOwogICAgICAgIC13ZWJraXQtdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgIH0KCiAgICAgIHBhcGVyLWljb24tYnV0dG9uIHsKICAgICAgICBjb2xvcjogIzIxOTZmMzsKICAgICAgICBib3JkZXItcmFkaXVzOiAxMDAlOwogICAgICAgIHdpZHRoOiAzMnB4OwogICAgICAgIGhlaWdodDogMzJweDsKICAgICAgICBwYWRkaW5nOiA0cHg7CiAgICAgIH0KICAgICAgcGFwZXItaWNvbi1idXR0b25bc2VsZWN0ZWRdIHsKICAgICAgICBiYWNrZ3JvdW5kOiB2YXIoLS10Yi11aS1saWdodC1hY2NlbnQpOwogICAgICB9CgogICAgICB0Zi1jYXJkLWhlYWRpbmcgewogICAgICAgIG1hcmdpbi1ib3R0b206IDEwcHg7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSx3YS5wcm90b3R5cGUsInJ1biIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSx3YS5wcm90b3R5cGUsInRhZyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSx3YS5wcm90b3R5cGUsInRhZ01ldGFkYXRhIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLHdhLnByb3RvdHlwZSwieFR5cGUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sd2EucHJvdG90eXBlLCJnZXREYXRhTG9hZE5hbWUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sd2EucHJvdG90eXBlLCJsb2FkRGF0YUNhbGxiYWNrIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHdhLnByb3RvdHlwZSwiX2NvbG9yU2NhbGUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sd2EucHJvdG90eXBlLCJfZXhwYW5kZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsQWUpXSx3YS5wcm90b3R5cGUsInJlcXVlc3RNYW5hZ2VyIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLGFuKV0sd2EucHJvdG90eXBlLCJfY2FuY2VsbGVyIix2b2lkIDApO0UoW0J0KCJydW4iLCJ0YWciKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLHdhLnByb3RvdHlwZSwiX3JlbG9hZE9uUnVuVGFnQ2hhbmdlIixudWxsKTtFKFtCdCgicnVuIiwidGFnIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSx3YS5wcm90b3R5cGUsIl91cGRhdGVEYXRhVG9Mb2FkIixudWxsKTtFKFtSdCgicnVuIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sd2EucHJvdG90eXBlLCJfcnVuQ29sb3IiLG51bGwpO3dhPUUoW3l0KCJ0Zi1kaXN0cmlidXRpb24tbG9hZGVyIildLHdhKTt2YXIgU2w9Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMucmVsb2FkT25SZWFkeT0hMCx0aGlzLl94VHlwZT0ic3RlcCIsdGhpcy5fcmVxdWVzdE1hbmFnZXI9bmV3IEFlfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLnJlbG9hZE9uUmVhZHkmJnRoaXMucmVsb2FkKCl9cmVsb2FkKCl7dGhpcy5fZmV0Y2hUYWdzKCkudGhlbigoKT0+e3RoaXMuX3JlbG9hZERpc3RyaWJ1dGlvbnMoKX0pfV9mZXRjaFRhZ3MoKXtsZXQgdD12ZSgpLnBsdWdpblJvdXRlKCJkaXN0cmlidXRpb25zIiwiL3RhZ3MiKTtyZXR1cm4gdGhpcy5fcmVxdWVzdE1hbmFnZXIucmVxdWVzdCh0KS50aGVuKHI9PntpZihOQi5pc0VxdWFsKHIsdGhpcy5fcnVuVG9UYWdJbmZvKSlyZXR1cm47bGV0IG49TkIubWFwVmFsdWVzKHIsbz0+T2JqZWN0LmtleXMobykpLGk9JGkobik7dGhpcy5zZXQoIl9kYXRhTm90Rm91bmQiLGkubGVuZ3RoPT09MCksdGhpcy5zZXQoIl9ydW5Ub1RhZyIsbiksdGhpcy5zZXQoIl9ydW5Ub1RhZ0luZm8iLHIpLHRoaXMuYXN5bmMoKCk9Pnt0aGlzLnNldCgiX2NhdGVnb3JpZXNEb21SZWFkeSIsITApfSl9KX1fcmVsb2FkRGlzdHJpYnV0aW9ucygpe3ZhciB0Oyh0PXRoaXMucm9vdCk9PW51bGx8fHQucXVlcnlTZWxlY3RvckFsbCgidGYtZGlzdHJpYnV0aW9uLWxvYWRlciIpLmZvckVhY2gocj0+e3IucmVsb2FkKCl9KX1fc2hvdWxkT3Blbih0KXtyZXR1cm4gdDw9Mn1nZXQgX2NhdGVnb3JpZXMoKXt2YXIgdD10aGlzLl9ydW5Ub1RhZyxyPXRoaXMuX3NlbGVjdGVkUnVucyxuPXRoaXMuX3RhZ0ZpbHRlcixpPXRoaXMuX2NhdGVnb3JpZXNEb21SZWFkeTtyZXR1cm4gUWwodCxyLG4pfV90YWdNZXRhZGF0YSh0LHIsbil7cmV0dXJuIHRbcl1bbl19fTtTbC50ZW1wbGF0ZT1RYAogICAgPHRmLWRhc2hib2FyZC1sYXlvdXQ+CiAgICAgIDxkaXYgY2xhc3M9InNpZGViYXIiIHNsb3Q9InNpZGViYXIiPgogICAgICAgIDxkaXYgY2xhc3M9InNldHRpbmdzIj4KICAgICAgICAgIDxkaXYgY2xhc3M9InNpZGViYXItc2VjdGlvbiI+CiAgICAgICAgICAgIDx0Zi1vcHRpb24tc2VsZWN0b3IKICAgICAgICAgICAgICBpZD0ieFR5cGVTZWxlY3RvciIKICAgICAgICAgICAgICBuYW1lPSJIb3Jpem9udGFsIGF4aXMiCiAgICAgICAgICAgICAgc2VsZWN0ZWQtaWQ9Int7X3hUeXBlfX0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uIGlkPSJzdGVwIj5zdGVwPC9wYXBlci1idXR0b24+CiAgICAgICAgICAgICAgPHBhcGVyLWJ1dHRvbiBpZD0icmVsYXRpdmUiPnJlbGF0aXZlPC9wYXBlci1idXR0b24+CiAgICAgICAgICAgICAgPHBhcGVyLWJ1dHRvbiBpZD0id2FsbF90aW1lIj53YWxsPC9wYXBlci1idXR0b24+CiAgICAgICAgICAgIDwvdGYtb3B0aW9uLXNlbGVjdG9yPgogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhci1zZWN0aW9uIHJ1bnMtc2VsZWN0b3IiPgogICAgICAgICAgPHRmLXJ1bnMtc2VsZWN0b3Igc2VsZWN0ZWQtcnVucz0ie3tfc2VsZWN0ZWRSdW5zfX0iPgogICAgICAgICAgPC90Zi1ydW5zLXNlbGVjdG9yPgogICAgICAgIDwvZGl2PgogICAgICA8L2Rpdj4KCiAgICAgIDxkaXYgY2xhc3M9ImNlbnRlciIgc2xvdD0iY2VudGVyIj4KICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2RhdGFOb3RGb3VuZF1dIj4KICAgICAgICAgIDxkaXYgY2xhc3M9Im5vLWRhdGEtd2FybmluZyI+CiAgICAgICAgICAgIDxoMz5ObyBkaXN0cmlidXRpb24gZGF0YSB3YXMgZm91bmQuPC9oMz4KICAgICAgICAgICAgPHA+UHJvYmFibGUgY2F1c2VzOjwvcD4KICAgICAgICAgICAgPHVsPgogICAgICAgICAgICAgIDxsaT4KICAgICAgICAgICAgICAgIFlvdSBoYXZlbuKAmXQgd3JpdHRlbiBhbnkgaGlzdG9ncmFtIGRhdGEgdG8geW91ciBldmVudCBmaWxlcy4KICAgICAgICAgICAgICAgIChIaXN0b2dyYW1zIGFuZCBkaXN0cmlidXRpb25zIGJvdGggdXNlIHRoZSBoaXN0b2dyYW0gc3VtbWFyeQogICAgICAgICAgICAgICAgb3BlcmF0aW9uLikKICAgICAgICAgICAgICA8L2xpPgoKICAgICAgICAgICAgICA8bGk+VGVuc29yQm9hcmQgY2Fu4oCZdCBmaW5kIHlvdXIgZXZlbnQgZmlsZXMuPC9saT4KICAgICAgICAgICAgPC91bD4KCiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgIElmIHlvdeKAmXJlIG5ldyB0byB1c2luZyBUZW5zb3JCb2FyZCwgYW5kIHdhbnQgdG8gZmluZCBvdXQgaG93IHRvCiAgICAgICAgICAgICAgYWRkIGRhdGEgYW5kIHNldCB1cCB5b3VyIGV2ZW50IGZpbGVzLCBjaGVjayBvdXQgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCIKICAgICAgICAgICAgICAgID5SRUFETUU8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIHBlcmhhcHMgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiCiAgICAgICAgICAgICAgICA+VGVuc29yQm9hcmQgdHV0b3JpYWw8L2EKICAgICAgICAgICAgICA+LgogICAgICAgICAgICA8L3A+CgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICBJZiB5b3UgdGhpbmsgVGVuc29yQm9hcmQgaXMgY29uZmlndXJlZCBwcm9wZXJseSwgcGxlYXNlIHNlZQogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQjbXktdGVuc29yYm9hcmQtaXNudC1zaG93aW5nLWFueS1kYXRhLXdoYXRzLXdyb25nIgogICAgICAgICAgICAgICAgPnRoZSBzZWN0aW9uIG9mIHRoZSBSRUFETUUgZGV2b3RlZCB0byBtaXNzaW5nIGRhdGEgcHJvYmxlbXM8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIGNvbnNpZGVyIGZpbGluZyBhbiBpc3N1ZSBvbiBHaXRIdWIuCiAgICAgICAgICAgIDwvcD4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbWyFfZGF0YU5vdEZvdW5kXV0iPgogICAgICAgICAgPHRmLXRhZy1maWx0ZXJlciB0YWctZmlsdGVyPSJ7e190YWdGaWx0ZXJ9fSI+PC90Zi10YWctZmlsdGVyZXI+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19jYXRlZ29yaWVzXV0iIGFzPSJjYXRlZ29yeSI+CiAgICAgICAgICAgIDx0Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldwogICAgICAgICAgICAgIGNhdGVnb3J5PSJbW2NhdGVnb3J5XV0iCiAgICAgICAgICAgICAgaW5pdGlhbC1vcGVuZWQ9IltbX3Nob3VsZE9wZW4oaW5kZXgpXV0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8dGYtZGlzdHJpYnV0aW9uLWxvYWRlcgogICAgICAgICAgICAgICAgICBhY3RpdmU9IltbYWN0aXZlXV0iCiAgICAgICAgICAgICAgICAgIHJ1bj0iW1tpdGVtLnJ1bl1dIgogICAgICAgICAgICAgICAgICB0YWc9IltbaXRlbS50YWddXSIKICAgICAgICAgICAgICAgICAgdGFnLW1ldGFkYXRhPSJbW190YWdNZXRhZGF0YShfcnVuVG9UYWdJbmZvLCBpdGVtLnJ1biwgaXRlbS50YWcpXV0iCiAgICAgICAgICAgICAgICAgIHgtdHlwZT0iW1tfeFR5cGVdXSIKICAgICAgICAgICAgICAgICAgcmVxdWVzdC1tYW5hZ2VyPSJbW19yZXF1ZXN0TWFuYWdlcl1dIgogICAgICAgICAgICAgICAgPjwvdGYtZGlzdHJpYnV0aW9uLWxvYWRlcj4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8L3RmLWNhdGVnb3J5LXBhZ2luYXRlZC12aWV3PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICA8L2Rpdj4KICAgIDwvdGYtZGFzaGJvYXJkLWxheW91dD4KCiAgICA8c3R5bGUgaW5jbHVkZT0iZGFzaGJvYXJkLXN0eWxlIj48L3N0eWxlPgogICAgPHN0eWxlPgogICAgICAubm8tZGF0YS13YXJuaW5nIHsKICAgICAgICBtYXgtd2lkdGg6IDU0MHB4OwogICAgICAgIG1hcmdpbjogODBweCBhdXRvIDAgYXV0bzsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sU2wucHJvdG90eXBlLCJyZWxvYWRPblJlYWR5Iix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLFNsLnByb3RvdHlwZSwiX3hUeXBlIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxTbC5wcm90b3R5cGUsIl9zZWxlY3RlZFJ1bnMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sU2wucHJvdG90eXBlLCJfcnVuVG9UYWciLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sU2wucHJvdG90eXBlLCJfcnVuVG9UYWdJbmZvIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sU2wucHJvdG90eXBlLCJfZGF0YU5vdEZvdW5kIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLFNsLnByb3RvdHlwZSwiX3RhZ0ZpbHRlciIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLFNsLnByb3RvdHlwZSwiX2NhdGVnb3JpZXNEb21SZWFkeSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixBZSldLFNsLnByb3RvdHlwZSwiX3JlcXVlc3RNYW5hZ2VyIix2b2lkIDApO0UoW1J0KCJfcnVuVG9UYWciLCJfc2VsZWN0ZWRSdW5zIiwiX3RhZ0ZpbHRlciIsIl9jYXRlZ29yaWVzRG9tUmVhZHkiKSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLFNsLnByb3RvdHlwZSwiX2NhdGVnb3JpZXMiLG51bGwpO1NsPUUoW3l0KCJ0Zi1kaXN0cmlidXRpb24tZGFzaGJvYXJkIildLFNsKTt2YXIgREI9e307S3MoREIse0RJU0FNQklHVUFUT1I6KCk9PkYwdCxMaXN0ZW5LZXk6KCk9PmRFLGFkZEhhc2hMaXN0ZW5lcjooKT0+bUUsYWRkU3RvcmFnZUxpc3RlbmVyOigpPT5DVyxkaXNwb3NlQm9vbGVhbkJpbmRpbmc6KCk9PiRiZSxkaXNwb3NlTnVtYmVyQmluZGluZzooKT0+S2JlLGRpc3Bvc2VPYmplY3RCaW5kaW5nOigpPT5RYmUsZGlzcG9zZVN0cmluZ0JpbmRpbmc6KCk9PlliZSxmaXJlU3RvcmFnZUNoYW5nZWQ6KCk9PkFXLGdldEJvb2xlYW46KCk9PmpiZSxnZXRCb29sZWFuSW5pdGlhbGl6ZXI6KCk9PnZwLGdldEJvb2xlYW5PYnNlcnZlcjooKT0+eHAsZ2V0TnVtYmVyOigpPT5EVyxnZXROdW1iZXJJbml0aWFsaXplcjooKT0+Z0UsZ2V0TnVtYmVyT2JzZXJ2ZXI6KCk9Pl9FLGdldE9iamVjdDooKT0+WmJlLGdldE9iamVjdEluaXRpYWxpemVyOigpPT56VyxnZXRPYmplY3RPYnNlcnZlcjooKT0+RlcsZ2V0U3RyaW5nOigpPT5HYmUsZ2V0U3RyaW5nSW5pdGlhbGl6ZXI6KCk9PnlfLGdldFN0cmluZ09ic2VydmVyOigpPT52XyxnZXRVcmxIYXNoRGljdDooKT0+TjB0LG1ha2VCaW5kaW5nczooKT0+eUUsbWlncmF0ZUxlZ2FjeVVSTFNjaGVtZTooKT0+dDJlLHJlbW92ZUhhc2hMaXN0ZW5lckJ5S2V5OigpPT5QVyxyZW1vdmVTdG9yYWdlTGlzdGVuZXJCeUtleTooKT0+SVcsc2V0Qm9vbGVhbjooKT0+WGJlLHNldE51bWJlcjooKT0+T1csc2V0T2JqZWN0OigpPT5KYmUsc2V0U3RyaW5nOigpPT5XYmV9KTt2YXIgb2N0PUVlKE9lKCksMSk7dmFyIFdLdD0iR3JhcGggZGFzaGJvYXJkIGFjdGlvbnMiLFlLdD0iR3JhcGggZGFzaGJvYXJkIHRpbWluZ3MiLGxzdDsoZnVuY3Rpb24oZSl7ZS5GRVRDSF9QQlRYVF9CWVRFUz0iRkVUQ0hfUEJUWFRfQllURVMiLGUuRkVUQ0hfUEJUWFRfQllURVNfRlJPTV9GSUxFU1lTVEVNPSJGRVRDSF9QQlRYVF9CWVRFU19GUk9NX0ZJTEVTWVNURU0iLGUuRkVUQ0hfUEJUWFRfQllURVNfRlJPTV9TRVJWRVI9IkZFVENIX1BCVFhUX0JZVEVTX0ZST01fU0VSVkVSIixlLlBBUlNFX1BCVFhUX0lOVE9fT0JKRUNUPSJQQVJTRV9QQlRYVF9JTlRPX09CSkVDVCIsZS5GRVRDSF9NRVRBREFUQV9QQlRYVF9CWVRFUz0iRkVUQ0hfTUVUQURBVEFfUEJUWFRfQllURVMiLGUuUEFSU0VfTUVUQURBVEFfUEJUWFRfSU5UT19PQkpFQ1Q9IlBBUlNFX01FVEFEQVRBX1BCVFhUX0lOVE9fT0JKRUNUIixlLk5PUk1BTElaSU5HX05BTUVTPSJOT1JNQUxJWklOR19OQU1FUyIsZS5CVUlMRF9TTElNX0dSQVBIPSJCVUlMRF9TTElNX0dSQVBIIixlLkhJRVJBUkNIWV9BRERfTk9ERVM9IkhJRVJBUkNIWV9BRERfTk9ERVMiLGUuSElFUkFSQ0hZX0RFVEVDVF9TRVJJRVM9IkhJRVJBUkNIWV9ERVRFQ1RfU0VSSUVTIixlLkhJRVJBUkNIWV9BRERfRURHRVM9IkhJRVJBUkNIWV9BRERfRURHRVMiLGUuSElFUkFSQ0hZX0ZJTkRfU0lNSUxBUl9TVUJHUkFQSFM9IkhJRVJBUkNIWV9GSU5EX1NJTUlMQVJfU1VCR1JBUEhTIixlLlJFTkRFUl9CVUlMRF9ISUVSQVJDSFk9IlJFTkRFUl9CVUlMRF9ISUVSQVJDSFkiLGUuUkVOREVSX1NDRU5FX0xBWU9VVD0iUkVOREVSX1NDRU5FX0xBWU9VVCIsZS5SRU5ERVJfU0NFTkVfQlVJTERfU0NFTkU9IlJFTkRFUl9TQ0VORV9CVUlMRF9TQ0VORSIsZS5HUkFQSF9MT0FEX1NVQ0NFRURFRD0iR1JBUEhfTE9BRF9TVUNDRUVERUQiLGUuR1JBUEhfTE9BRF9GQUlMRUQ9IkdSQVBIX0xPQURfRkFJTEVEIn0pKGxzdHx8KGxzdD17fSkpO3ZhciBjc3Q7KGZ1bmN0aW9uKGUpe2UuTk9ERV9FWFBBTlNJT05fVE9HR0xFRD0iTk9ERV9FWFBBTlNJT05fVE9HR0xFRCIsZS5OT0RFX1NFQVJDSF9SRVNVTFRfRk9DVVNFRD0iTk9ERV9TRUFSQ0hfUkVTVUxUX0ZPQ1VTRUQiLGUuTk9ERV9BVVhJTElBUllfRVhUUkFDVElPTl9DSEFOR0VEPSJOT0RFX0FVWElMSUFSWV9FWFRSQUNUSU9OX0NIQU5HRUQiLGUuR1JBUEhfVFlQRV9DSEFOR0VEPSJHUkFQSF9UWVBFX0NIQU5HRUQiLGUuVFJBQ0VfSU5QVVRfTU9ERV9UT0dHTEVEPSJUUkFDRV9JTlBVVF9NT0RFX1RPR0dMRUQiLGUuTk9ERV9DT0xPUl9NT0RFX0NIQU5HRUQ9Ik5PREVfQ09MT1JfTU9ERV9DSEFOR0VEIixlLlVQTE9BREVEX0dSQVBIX0ZST01fRklMRVNZU1RFTT0iVVBMT0FERURfR1JBUEhfRlJPTV9GSUxFU1lTVEVNIn0pKGNzdHx8KGNzdD17fSkpO3ZhciBqcj1LbChLbCh7fSxsc3QpLGNzdCk7dmFyIEpzZT1FZSh6bHQoKSwxKSxwbj1FZShPZSgpLDEpO3ZhciBGczsoZnVuY3Rpb24oZSl7ZS5PUF9HUkFQSD0ib3BfZ3JhcGgiLGUuQ09OQ0VQVFVBTF9HUkFQSD0iY29uY2VwdHVhbF9ncmFwaCIsZS5QUk9GSUxFPSJwcm9maWxlIn0pKEZzfHwoRnM9e30pKTt2YXIgeWU9e05vZGU6e0NPTlRBSU5FUjoibm9kZXMiLEdST1VQOiJub2RlIixTSEFQRToibm9kZXNoYXBlIixDT0xPUl9UQVJHRVQ6Im5vZGVjb2xvcnRhcmdldCIsTEFCRUw6Im5vZGVsYWJlbCIsQlVUVE9OX0NPTlRBSU5FUjoiYnV0dG9uY29udGFpbmVyIixCVVRUT05fQ0lSQ0xFOiJidXR0b25jaXJjbGUiLEVYUEFORF9CVVRUT046ImV4cGFuZGJ1dHRvbiIsQ09MTEFQU0VfQlVUVE9OOiJjb2xsYXBzZWJ1dHRvbiJ9LEVkZ2U6e0NPTlRBSU5FUjoiZWRnZXMiLEdST1VQOiJlZGdlIixMSU5FOiJlZGdlbGluZSIsUkVGRVJFTkNFX0VER0U6InJlZmVyZW5jZWVkZ2UiLFJFRl9MSU5FOiJyZWZsaW5lIixTRUxFQ1RBQkxFOiJzZWxlY3RhYmxlZWRnZSIsU0VMRUNURUQ6InNlbGVjdGVkZWRnZSIsU1RSVUNUVVJBTDoic3RydWN0dXJhbCJ9LEFubm90YXRpb246e09VVEJPWDoib3V0LWFubm90YXRpb25zIixJTkJPWDoiaW4tYW5ub3RhdGlvbnMiLEdST1VQOiJhbm5vdGF0aW9uIixOT0RFOiJhbm5vdGF0aW9uLW5vZGUiLEVER0U6ImFubm90YXRpb24tZWRnZSIsQ09OVFJPTF9FREdFOiJhbm5vdGF0aW9uLWNvbnRyb2wtZWRnZSIsTEFCRUw6ImFubm90YXRpb24tbGFiZWwiLEVMTElQU0lTOiJhbm5vdGF0aW9uLWVsbGlwc2lzIn0sU2NlbmU6e0dST1VQOiJzY2VuZSIsQ09SRToiY29yZSIsRlVOQ1RJT05fTElCUkFSWToiZnVuY3Rpb24tbGlicmFyeSIsSU5FWFRSQUNUOiJpbi1leHRyYWN0IixPVVRFWFRSQUNUOiJvdXQtZXh0cmFjdCJ9LFN1YnNjZW5lOntHUk9VUDoic3Vic2NlbmUifSxPUE5PREU6Im9wIixNRVRBTk9ERToibWV0YSIsU0VSSUVTTk9ERToic2VyaWVzIixCUklER0VOT0RFOiJicmlkZ2UiLEVMTElQU0lTTk9ERToiZWxsaXBzaXMifSxaND17RWRnZTp7TEFCRUw6My41fSxBbm5vdGF0aW9uOntMQUJFTDo1fSxOb2RlOntFWFBBTkRFRF9MQUJFTDo5LFNFUklFU19MQUJFTDo4LE9QX0xBQkVMOjYsSEVBTFRIX1BJTExfU1RBVF9MQUJFTDo0fX0sanU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIjtmdW5jdGlvbiBtMChlLHQscil7bGV0IG49ZS5ub2RlKCkuY2hpbGROb2Rlcztmb3IobGV0IGk9MDtpPG4ubGVuZ3RoO2krKyl7bGV0IG89bltpXTtpZihvLnRhZ05hbWU9PT10KXtpZihyIGluc3RhbmNlb2YgQXJyYXkpe2xldCBhPSEwO2ZvcihsZXQgcz0wO3M8ci5sZW5ndGg7cysrKWE9YSYmby5jbGFzc0xpc3QuY29udGFpbnMocltzXSk7aWYoYSlyZXR1cm4gSHQobyl9ZWxzZSBpZighcnx8by5jbGFzc0xpc3QuY29udGFpbnMocikpcmV0dXJuIEh0KG8pfX1yZXR1cm4gSHQobnVsbCl9ZnVuY3Rpb24gUG4oZSx0LHIsbil7bGV0IGk9bTAoZSx0LHIpO2lmKCFpLmVtcHR5KCkpcmV0dXJuIGk7bGV0IG89ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIsdCk7aWYociBpbnN0YW5jZW9mIEFycmF5KWZvcihsZXQgYT0wO2E8ci5sZW5ndGg7YSsrKW8uY2xhc3NMaXN0LmFkZChyW2FdKTtlbHNlIG8uY2xhc3NMaXN0LmFkZChyKTtyZXR1cm4gbj9lLm5vZGUoKS5pbnNlcnRCZWZvcmUobyxuKTplLm5vZGUoKS5hcHBlbmRDaGlsZChvKSxIdChvKS5kYXR1bShlLmRhdHVtKCkpfXZhciBLUz1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLnRvdGFsQnl0ZXM9MCx0aGlzLm91dHB1dFNpemU9dH1hZGRFeGVjdXRpb25UaW1lKHQscil7dGhpcy5zdGFydFRpbWUhPW51bGw/dGhpcy5zdGFydFRpbWU9TWF0aC5taW4odGhpcy5zdGFydFRpbWUsdCk6dGhpcy5zdGFydFRpbWU9dCx0aGlzLmVuZFRpbWUhPW51bGw/dGhpcy5lbmRUaW1lPU1hdGgubWF4KHRoaXMuZW5kVGltZSxyKTp0aGlzLmVuZFRpbWU9cn1hZGRCeXRlc0FsbG9jYXRpb24odCl7dGhpcy50b3RhbEJ5dGVzIT1udWxsP3RoaXMudG90YWxCeXRlcz1NYXRoLm1heCh0aGlzLnRvdGFsQnl0ZXMsdCk6dGhpcy50b3RhbEJ5dGVzPXR9Y29tYmluZSh0KXt0LnRvdGFsQnl0ZXMhPW51bGwmJih0aGlzLnRvdGFsQnl0ZXMrPXQudG90YWxCeXRlcyksdC5nZXRUb3RhbE1pY3JvcygpIT1udWxsJiZ0aGlzLmFkZEV4ZWN1dGlvblRpbWUodC5zdGFydFRpbWUsdC5lbmRUaW1lKX1nZXRUb3RhbE1pY3Jvcygpe3JldHVybiB0aGlzLnN0YXJ0VGltZT09bnVsbHx8dGhpcy5lbmRUaW1lPT1udWxsP251bGw6dGhpcy5lbmRUaW1lLXRoaXMuc3RhcnRUaW1lfX0sSjQ9Ljc1LFE0PTEyLCRscj0uMyxLbHI9WzEsNWU2XSxxc2U9S18oKS5leHBvbmVudCgkbHIpLmRvbWFpbihLbHIpLnJhbmdlKFtKNCxRNF0pLmNsYW1wKCEwKTt2YXIgWlM9RWUoT2UoKSwxKTt2YXIgV3NlPTIwO2Z1bmN0aW9uIFpscihlKXtyZXR1cm4gZS5oYXNPd25Qcm9wZXJ0eSgidGltaW5nSWQiKX1mdW5jdGlvbiBQbyhlKXtabHIoZSk/KFlLdCxlLnRpbWluZ0lkLGUuZXZlbnRWYWx1ZSx2b2lkIDApOihXS3QsZS5hY3Rpb25JZCxlLmV2ZW50TGFiZWwsdm9pZCAwKX1mdW5jdGlvbiBSZChlLHQscil7bGV0IG49RGF0ZS5ub3coKSxpPXQoKSxvPURhdGUubm93KCktbjtyZXR1cm4gY29uc29sZS5sb2coZSwiOiIsbywibXMiKSxyJiZQbyh7dGltaW5nSWQ6cixldmVudFZhbHVlOm99KSxpfWZ1bmN0aW9uIHJQKGUpe3JldHVybntzZXRNZXNzYWdlOmZ1bmN0aW9uKHQpe2Uuc2V0KCJwcm9ncmVzcyIse3ZhbHVlOmUucHJvZ3Jlc3MudmFsdWUsbXNnOnR9KX0sdXBkYXRlUHJvZ3Jlc3M6ZnVuY3Rpb24odCl7ZS5zZXQoInByb2dyZXNzIix7dmFsdWU6ZS5wcm9ncmVzcy52YWx1ZSt0LG1zZzplLnByb2dyZXNzLm1zZ30pfSxyZXBvcnRFcnJvcjpmdW5jdGlvbih0LHIpe2NvbnNvbGUuZXJyb3Ioci5zdGFjayksZS5zZXQoInByb2dyZXNzIix7dmFsdWU6ZS5wcm9ncmVzcy52YWx1ZSxtc2c6dCxlcnJvcjohMH0pfX19ZnVuY3Rpb24gSlMoZSx0LHIpe3JldHVybntzZXRNZXNzYWdlOmZ1bmN0aW9uKG4pe2Uuc2V0TWVzc2FnZShyKyI6ICIrbil9LHVwZGF0ZVByb2dyZXNzOmZ1bmN0aW9uKG4pe2UudXBkYXRlUHJvZ3Jlc3Mobip0LzEwMCl9LHJlcG9ydEVycm9yOmZ1bmN0aW9uKG4saSl7ZS5yZXBvcnRFcnJvcihyKyI6ICIrbixpKX19fWZ1bmN0aW9uIFlzZShlLHQscixuLGkpe24uc2V0TWVzc2FnZShlKTt0cnl7bGV0IG89UmQoZSxyLGkpO3JldHVybiBuLnVwZGF0ZVByb2dyZXNzKHQpLG99Y2F0Y2gobyl7cmV0dXJuIG4ucmVwb3J0RXJyb3IoIkZhaWxlZCAiK2UsbyksbnVsbH19ZnVuY3Rpb24gZXYoZSx0LHIsbixpKXtyZXR1cm4gbmV3IFByb21pc2UoKG8sYSk9PntuLnNldE1lc3NhZ2UoZSksc2V0VGltZW91dChmdW5jdGlvbigpe3RyeXtsZXQgcz1SZChlLHIsaSk7bi51cGRhdGVQcm9ncmVzcyh0KSxvKHMpfWNhdGNoKHMpe24ucmVwb3J0RXJyb3IoIkZhaWxlZCAiK2Uscyl9fSxXc2UpfSl9ZnVuY3Rpb24gZEgoZSx0LHIsbixpKXtyZXR1cm4gbmV3IFByb21pc2UoKG8sYSk9PntsZXQgcz1mdW5jdGlvbihsKXtuLnJlcG9ydEVycm9yKCJGYWlsZWQgIitlLGwpLGEobCl9O24uc2V0TWVzc2FnZShlKSxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7dHJ5e2xldCBsPURhdGUubm93KCk7cigpLnRoZW4oZnVuY3Rpb24oYyl7bGV0IHU9RGF0ZS5ub3coKS1sO2NvbnNvbGUubG9nKGUsIjoiLHUsIm1zIiksbi51cGRhdGVQcm9ncmVzcyh0KSxQbyh7dGltaW5nSWQ6aSxldmVudFZhbHVlOnV9KSxvKGMpfSkuY2F0Y2gocyl9Y2F0Y2gobCl7cyhsKX19LFdzZSl9KX1mdW5jdGlvbiBqc2UoZSl7cmV0dXJuIGUucmVwbGFjZSgvKFs6LlxbXF0sL1xcXChcKV0pL2csIlxcJDEiKX12YXIgblA9W3tzeW1ib2w6IkIifSx7c3ltYm9sOiJLQiIsbnVtVW5pdHM6MTAyNH0se3N5bWJvbDoiTUIiLG51bVVuaXRzOjEwMjR9LHtzeW1ib2w6IkdCIixudW1Vbml0czoxMDI0fSx7c3ltYm9sOiJUQiIsbnVtVW5pdHM6MTAyNH0se3N5bWJvbDoiUEIiLG51bVVuaXRzOjEwMjR9XSxpUD1be3N5bWJvbDoiXHhCNXMifSx7c3ltYm9sOiJtcyIsbnVtVW5pdHM6MWUzfSx7c3ltYm9sOiJzIixudW1Vbml0czoxZTN9LHtzeW1ib2w6Im1pbiIsbnVtVW5pdHM6NjB9LHtzeW1ib2w6ImhyIixudW1Vbml0czo2MH0se3N5bWJvbDoiZGF5cyIsbnVtVW5pdHM6MjR9XTtmdW5jdGlvbiBOZChlLHQscj0wKXtyZXR1cm4gcisxPHQubGVuZ3RoJiZlPj10W3IrMV0ubnVtVW5pdHM/TmQoZS90W3IrMV0ubnVtVW5pdHMsdCxyKzEpOk51bWJlcihlLnRvUHJlY2lzaW9uKDMpKSsiICIrdFtyXS5zeW1ib2x9ZnVuY3Rpb24gbUgoZSl7cmV0dXJuISEoZSYmKGUudG90YWxCeXRlcz4wfHxlLmdldFRvdGFsTWljcm9zKCk+MHx8ZS5vdXRwdXRTaXplKSl9ZnVuY3Rpb24gRmx0KGUpe2lmKGUubGVuZ3RoPDIpcmV0dXJuIGU7bGV0IHQ9MCxyPTAsbj1aUy5taW4oWlMubWFwKGUsaT0+aS5sZW5ndGgpKTtmb3IoOzspe3QrKztsZXQgaT1aUy5tYXAoZSxhPT5hLnN1YnN0cmluZygwLHQpKTtpZihpLmV2ZXJ5KChhLHMpPT5zPT09MD8hMDphPT09aVtzLTFdKSl7aWYodD49bilyZXR1cm4gZTtyPXR9ZWxzZSBicmVha31yZXR1cm4gWlMubWFwKGUsaT0+aS5zdWJzdHJpbmcocikpfWZ1bmN0aW9uIFhzZShlKXt2YXIgdD0rbmV3IERhdGUtK25ldyBEYXRlKGUvMWUzKTtyZXR1cm4gdDwzZTQ/Imp1c3Qgbm93Ijp0PDZlND9NYXRoLmZsb29yKHQvMWUzKSsiIHNlY29uZHMgYWdvIjp0PDEyZTQ/ImEgbWludXRlIGFnbyI6dDwzNmU1P01hdGguZmxvb3IodC82ZTQpKyIgbWludXRlcyBhZ28iOk1hdGguZmxvb3IodC8zNmU1KT09MT8iYW4gaG91ciBhZ28iOnQ8ODY0ZTU/TWF0aC5mbG9vcih0LzM2ZTUpKyIgaG91cnMgYWdvIjp0PDE3MjhlNT8ieWVzdGVyZGF5IjpNYXRoLmZsb29yKHQvODY0ZTUpKyIgZGF5cyBhZ28ifXZhciBKbHI9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiY2FudmFzIiksZVA9SmxyLmdldENvbnRleHQoIjJkIik7ZnVuY3Rpb24gR3NlKGUsdCl7cmV0dXJuIGVQJiYoZVAuZm9udD1gJHt0fXB4IFJvYm90bywgc2Fucy1zZXJpZmApLGVQPT1udWxsP3ZvaWQgMDplUC5tZWFzdXJlVGV4dChlKS53aWR0aH1mdW5jdGlvbiAkc2UoZSx0LHIpe2lmKCFlKXJldHVybiIiO2lmKEdzZShlLHQpPD1yKXJldHVybiBlO2xldCBuPTAsaT1lLmxlbmd0aDtmb3IoO248aTspe2xldCBvPW4rTWF0aC5yb3VuZCgoaS1uKS8yKSxhPWUuc2xpY2UoMCxvKSsiXHUyMDI2IjtHc2UoYSx0KTw9cj9uPW86aT1vLTF9cmV0dXJuIG49PT0wP2VbMF06ZS5zbGljZSgwLG4pKyJcdTIwMjYifXZhciBwSD1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuZXZlbnRUeXBlVG9MaXN0ZW5lcnM9bmV3IE1hcH1nZXRMaXN0ZW5lcnModCl7cmV0dXJuIHRoaXMuZXZlbnRUeXBlVG9MaXN0ZW5lcnMuaGFzKHQpfHx0aGlzLmV2ZW50VHlwZVRvTGlzdGVuZXJzLnNldCh0LFtdKSx0aGlzLmV2ZW50VHlwZVRvTGlzdGVuZXJzLmdldCh0KX1hZGRMaXN0ZW5lcih0LHIpe3ZhciBuOyhuPXRoaXMuZ2V0TGlzdGVuZXJzKHQpKT09bnVsbHx8bi5wdXNoKHIpfXJlbW92ZUxpc3RlbmVyKHQscil7dmFyIGk7bGV0IG49KGk9dGhpcy5nZXRMaXN0ZW5lcnModCkpPT1udWxsP3ZvaWQgMDppLmZpbHRlcihvPT5vIT09cik7dGhpcy5ldmVudFR5cGVUb0xpc3RlbmVycy5zZXQodCxuKX1kaXNwYXRjaEV2ZW50KHQscil7Zm9yKGxldCBuIG9mIHRoaXMuZ2V0TGlzdGVuZXJzKHQpKW4ocil9fTt2YXIgQWw9Ii8iLHFjPSJfX3Jvb3RfXyIsU2E9Il9fZnVuY3Rpb25fbGlicmFyeV9fIixRc2U9Il90b29fbGFyZ2VfYXR0cnMiO3ZhciB0bGU9Ii0tIixnMDsoZnVuY3Rpb24oZSl7ZVtlLkZVTEw9MF09IkZVTEwiLGVbZS5FTUJFRERFRD0xXT0iRU1CRURERUQiLGVbZS5NRVRBPTJdPSJNRVRBIixlW2UuU0VSSUVTPTNdPSJTRVJJRVMiLGVbZS5DT1JFPTRdPSJDT1JFIixlW2UuU0hBRE9XPTVdPSJTSEFET1ciLGVbZS5CUklER0U9Nl09IkJSSURHRSIsZVtlLkVER0U9N109IkVER0UifSkoZzB8fChnMD17fSkpO3ZhciBqdDsoZnVuY3Rpb24oZSl7ZVtlLk1FVEE9MF09Ik1FVEEiLGVbZS5PUD0xXT0iT1AiLGVbZS5TRVJJRVM9Ml09IlNFUklFUyIsZVtlLkJSSURHRT0zXT0iQlJJREdFIixlW2UuRUxMSVBTSVM9NF09IkVMTElQU0lTIn0pKGp0fHwoanQ9e30pKTt2YXIgdXI7KGZ1bmN0aW9uKGUpe2VbZS5JTkNMVURFPTBdPSJJTkNMVURFIixlW2UuRVhDTFVERT0xXT0iRVhDTFVERSIsZVtlLlVOU1BFQ0lGSUVEPTJdPSJVTlNQRUNJRklFRCJ9KSh1cnx8KHVyPXt9KSk7dmFyIGlzOyhmdW5jdGlvbihlKXtlW2UuR1JPVVA9MF09IkdST1VQIixlW2UuVU5HUk9VUD0xXT0iVU5HUk9VUCJ9KShpc3x8KGlzPXt9KSk7dmFyIFFscj0iX291dHB1dF9zaGFwZXMiLHRjcj0iX1hsYUNsdXN0ZXIiLFh1PWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5ub2Rlcz17fSx0aGlzLmVkZ2VzPVtdfX0sZ0g9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy50eXBlPWp0LkVMTElQU0lTLHRoaXMuaXNHcm91cE5vZGU9ITEsdGhpcy5jYXJkaW5hbGl0eT0xLHRoaXMucGFyZW50Tm9kZT1udWxsLHRoaXMuc3RhdHM9bnVsbCx0aGlzLnNldE51bU1vcmVOb2Rlcyh0KSx0aGlzLmluY2x1ZGU9dXIuVU5TUEVDSUZJRUR9c2V0TnVtTW9yZU5vZGVzKHQpe3RoaXMubnVtTW9yZU5vZGVzPXQsdGhpcy5uYW1lPSIuLi4gIit0KyIgbW9yZSJ9fSxfMD1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLm9wPXQub3AsdGhpcy5uYW1lPXQubmFtZSx0aGlzLmRldmljZT10LmRldmljZSx0aGlzLmF0dHI9dC5hdHRyLHRoaXMuaW5wdXRzPWljcih0LmlucHV0KSx0aGlzLm91dHB1dFNoYXBlcz1lY3IodC5hdHRyKSx0aGlzLnhsYUNsdXN0ZXI9cmNyKHQuYXR0ciksdGhpcy5jb21wYXRpYmxlPSExLHRoaXMudHlwZT1qdC5PUCx0aGlzLmlzR3JvdXBOb2RlPSExLHRoaXMuY2FyZGluYWxpdHk9MSx0aGlzLmluRW1iZWRkaW5ncz1bXSx0aGlzLm91dEVtYmVkZGluZ3M9W10sdGhpcy5wYXJlbnROb2RlPW51bGwsdGhpcy5pbmNsdWRlPXVyLlVOU1BFQ0lGSUVELHRoaXMub3duaW5nU2VyaWVzPW51bGx9fTtmdW5jdGlvbiBzUChlLHQ9e30pe3JldHVybiBuZXcgb1AoZSx0KX1mdW5jdGlvbiBlbGUoZSx0LHIpe3BuLmVhY2goZS5ub2RlcyxuPT57bi5zdGF0cz1udWxsfSkscG4uZWFjaCh0LmRldl9zdGF0cyxuPT57ciYmIXJbbi5kZXZpY2VdfHxwbi5lYWNoKG4ubm9kZV9zdGF0cyxpPT57bGV0IG89aS5ub2RlX25hbWUgaW4gZS5ub2Rlcz9pLm5vZGVfbmFtZTphUChpLm5vZGVfbmFtZSk7aWYoIShvIGluIGUubm9kZXMpKXJldHVybjtsZXQgYT0wO2kubWVtb3J5JiZwbi5lYWNoKGkubWVtb3J5LGw9PntsLnRvdGFsX2J5dGVzJiYobC50b3RhbF9ieXRlcz4wP2ErPU51bWJlcihsLnRvdGFsX2J5dGVzKTpjb25zb2xlLmxvZygiaWdub3JpbmcgbmVnYXRpdmUgbWVtb3J5IGFsbG9jYXRpb24gZm9yICIrbykpfSk7bGV0IHM9bnVsbDtpLm91dHB1dCYmKHM9cG4ubWFwKGkub3V0cHV0LGw9PnBuLm1hcChsLnRlbnNvcl9kZXNjcmlwdGlvbi5zaGFwZS5kaW0sYz0+TnVtYmVyKGMuc2l6ZSkpKSksZS5ub2Rlc1tvXS5kZXZpY2U9bi5kZXZpY2UsZS5ub2Rlc1tvXS5zdGF0cz09bnVsbCYmKGUubm9kZXNbb10uc3RhdHM9bmV3IEtTKHMpKSxlLm5vZGVzW29dLnN0YXRzLmFkZEJ5dGVzQWxsb2NhdGlvbihhKSxpLmFsbF9lbmRfcmVsX21pY3JvcyYmKGkuYWxsX2VuZF9yZWxfbWljcm9zPjA/ZS5ub2Rlc1tvXS5zdGF0cy5hZGRFeGVjdXRpb25UaW1lKGkuYWxsX3N0YXJ0X21pY3JvcyxpLmFsbF9zdGFydF9taWNyb3MraS5hbGxfZW5kX3JlbF9taWNyb3MpOmNvbnNvbGUubG9nKCJpZ25vcmluZyBuZWdhdGl2ZSBydW50aW1lIGZvciAiK28pKX0pfSl9dmFyIG9QPWNsYXNze2NvbnN0cnVjdG9yKHQscj17fSl7dGhpcy5uYW1lPXQsdGhpcy50eXBlPWp0Lk1FVEEsdGhpcy5kZXB0aD0xLHRoaXMuaXNHcm91cE5vZGU9ITAsdGhpcy5jYXJkaW5hbGl0eT0wLHRoaXMubWV0YWdyYXBoPWUzKHQsZzAuTUVUQSxyKSx0aGlzLmJyaWRnZWdyYXBoPW51bGwsdGhpcy5vcEhpc3RvZ3JhbT17fSx0aGlzLmRldmljZUhpc3RvZ3JhbT17fSx0aGlzLnhsYUNsdXN0ZXJIaXN0b2dyYW09e30sdGhpcy5jb21wYXRpYmlsaXR5SGlzdG9ncmFtPXtjb21wYXRpYmxlOjAsaW5jb21wYXRpYmxlOjB9LHRoaXMudGVtcGxhdGVJZD1udWxsLHRoaXMucGFyZW50Tm9kZT1udWxsLHRoaXMuaGFzTm9uQ29udHJvbEVkZ2VzPSExLHRoaXMuaW5jbHVkZT11ci5VTlNQRUNJRklFRCx0aGlzLmFzc29jaWF0ZWRGdW5jdGlvbj0iIn1nZXRGaXJzdENoaWxkKCl7cmV0dXJuIHRoaXMubWV0YWdyYXBoLm5vZGUodGhpcy5tZXRhZ3JhcGgubm9kZXMoKVswXSl9Z2V0Um9vdE9wKCl7bGV0IHQ9dGhpcy5uYW1lLnNwbGl0KCIvIikscj10aGlzLm5hbWUrIi8oIit0W3QubGVuZ3RoLTFdKyIpIjtyZXR1cm4gdGhpcy5tZXRhZ3JhcGgubm9kZShyKX1sZWF2ZXMoKXtsZXQgdD1bXSxyPVt0aGlzXSxuO2Zvcig7ci5sZW5ndGg7KXtsZXQgaT1yLnNoaWZ0KCk7aSE9bnVsbCYmaS5pc0dyb3VwTm9kZT8obj1pLm1ldGFncmFwaCxwbi5lYWNoKG4ubm9kZXMoKSxvPT5yLnB1c2gobi5ub2RlKG8pKSkpOnQucHVzaChpPT1udWxsP3ZvaWQgMDppLm5hbWUpfXJldHVybiB0fX07ZnVuY3Rpb24gVmx0KGUsdCl7cmV0dXJuIG5ldyBxZihlLHQpfXZhciBxZj1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIpe3RoaXMudj10LHRoaXMudz1yLHRoaXMuYmFzZUVkZ2VMaXN0PVtdLHRoaXMuaW5ib3VuZD1udWxsLHRoaXMubnVtUmVndWxhckVkZ2VzPTAsdGhpcy5udW1Db250cm9sRWRnZXM9MCx0aGlzLm51bVJlZkVkZ2VzPTAsdGhpcy50b3RhbFNpemU9MH1hZGRCYXNlRWRnZSh0LHIpe3RoaXMuYmFzZUVkZ2VMaXN0LnB1c2godCksdC5pc0NvbnRyb2xEZXBlbmRlbmN5P3RoaXMubnVtQ29udHJvbEVkZ2VzKz0xOnRoaXMubnVtUmVndWxhckVkZ2VzKz0xLHQuaXNSZWZlcmVuY2VFZGdlJiYodGhpcy5udW1SZWZFZGdlcys9MSksdGhpcy50b3RhbFNpemUrPXFmLmNvbXB1dGVTaXplT2ZFZGdlKHQsciksci5tYXhNZXRhRWRnZVNpemU9TWF0aC5tYXgoci5tYXhNZXRhRWRnZVNpemUsdGhpcy50b3RhbFNpemUpfXN0YXRpYyBjb21wdXRlU2l6ZU9mRWRnZSh0LHIpe2xldCBuPXIubm9kZSh0LnYpO2lmKCFuLm91dHB1dFNoYXBlcylyZXR1cm4gMTtyLmhhc1NoYXBlSW5mbz0hMDtsZXQgaT1PYmplY3Qua2V5cyhuLm91dHB1dFNoYXBlcykubWFwKG89Pm4ub3V0cHV0U2hhcGVzW29dKS5tYXAobz0+bz09bnVsbD8xOm8ucmVkdWNlKChhLHMpPT4ocz09PS0xJiYocz0xKSxhKnMpLDEpKTtyZXR1cm4gcG4uc3VtKGkpfX07ZnVuY3Rpb24gUVMoZSx0LHIsbixpLG8pe3JldHVybiBuZXcgSGx0KGUsdCxyLG4saSxvKX1mdW5jdGlvbiB0MyhlLHQscixuLGkpe2xldCBvPXR5cGVvZiBuIT0idW5kZWZpbmVkIiYmdHlwZW9mIGkhPSJ1bmRlZmluZWQiPyJbIituKyItIitpKyJdIjoiIyIsYT1lK28rdDtyZXR1cm4ocj9yKyIvIjoiIikrYX12YXIgSGx0PWNsYXNze2NvbnN0cnVjdG9yKHQscixuLGksbyxhKXt0aGlzLm5hbWU9b3x8dDModCxyLG4pLHRoaXMudHlwZT1qdC5TRVJJRVMsdGhpcy5oYXNMb29wPSExLHRoaXMucHJlZml4PXQsdGhpcy5zdWZmaXg9cix0aGlzLmNsdXN0ZXJJZD1pLHRoaXMuaWRzPVtdLHRoaXMucGFyZW50PW4sdGhpcy5pc0dyb3VwTm9kZT0hMCx0aGlzLmNhcmRpbmFsaXR5PTAsdGhpcy5tZXRhZ3JhcGg9ZTMobyxnMC5TRVJJRVMsYSksdGhpcy5icmlkZ2VncmFwaD1udWxsLHRoaXMucGFyZW50Tm9kZT1udWxsLHRoaXMuZGV2aWNlSGlzdG9ncmFtPXt9LHRoaXMueGxhQ2x1c3Rlckhpc3RvZ3JhbT17fSx0aGlzLmNvbXBhdGliaWxpdHlIaXN0b2dyYW09e2NvbXBhdGlibGU6MCxpbmNvbXBhdGlibGU6MH0sdGhpcy5oYXNOb25Db250cm9sRWRnZXM9ITEsdGhpcy5pbmNsdWRlPXVyLlVOU1BFQ0lGSUVEfX07ZnVuY3Rpb24gZWNyKGUpe2xldCB0PW51bGw7aWYoIWUpcmV0dXJuIG51bGw7Zm9yKGxldCByPTA7cjxlLmxlbmd0aDtyKyspe2xldHtrZXk6bix2YWx1ZTppfT1lW3JdO2lmKG49PT1RbHIpe2lmKCFpLmxpc3R8fCFpLmxpc3Quc2hhcGUpcmV0dXJuIG51bGw7bGV0IG89aS5saXN0LnNoYXBlLm1hcChhPT5hLnVua25vd25fcmFuaz9udWxsOmEuZGltPT1udWxsfHxhLmRpbS5sZW5ndGg9PT0xJiZhLmRpbVswXS5zaXplPT1udWxsP1tdOmEuZGltLm1hcChzPT5zLnNpemUpKTtyZXR1cm4gZS5zcGxpY2UociwxKSxvfX1yZXR1cm4gbnVsbH1mdW5jdGlvbiByY3IoZSl7aWYoIWUpcmV0dXJuIG51bGw7Zm9yKGxldCB0PTA7dDxlLmxlbmd0aDt0KyspaWYoZVt0XS5rZXk9PT10Y3IpcmV0dXJuIGVbdF0udmFsdWUuc3x8bnVsbDtyZXR1cm4gbnVsbH12YXIgbmNyPS9eKFteOl0rKTooKFx3Kzp8KVxkKykkLztmdW5jdGlvbiBpY3IoZSl7bGV0IHQ9W10scj1udWxsO2ZvcihsZXQgbiBvZiBlfHxbXSl7bGV0IGk9bi5zdGFydHNXaXRoKCJeIik7aSYmKG49bi5zdWJzdHJpbmcoMSkpO2xldCBvPW4sYT0iMCIscz1uLmluY2x1ZGVzKCI6IikmJm4ubWF0Y2gobmNyKTtzJiYobz1zWzFdLGE9c1syXSksciE9PW8mJihyPW8sdC5wdXNoKHtuYW1lOm8sb3V0cHV0VGVuc29yS2V5OmEsaXNDb250cm9sRGVwZW5kZW5jeTppfSkpfXJldHVybiB0fWZ1bmN0aW9uIEJsdChlLHQscixuLGksbyl7aWYodD09PXIubmFtZSlyZXR1cm47bGV0IGE9aS5yZWZFZGdlc1tyLm9wKyIgIitvXT09PSEwO2UuZWRnZXMucHVzaCh7djp0LHc6ci5uYW1lLG91dHB1dFRlbnNvcktleTpuLm91dHB1dFRlbnNvcktleSxpc0NvbnRyb2xEZXBlbmRlbmN5Om4uaXNDb250cm9sRGVwZW5kZW5jeSxpc1JlZmVyZW5jZUVkZ2U6YX0pfXZhciBybGU9e2VuYWJsZUVtYmVkZGluZzohMCxpbkVtYmVkZGluZ1R5cGVzOlsiQ29uc3QiXSxvdXRFbWJlZGRpbmdUeXBlczpbIl5bYS16QS1aXStTdW1tYXJ5JCJdLHJlZkVkZ2VzOnsiQXNzaWduIDAiOiEwLCJBc3NpZ25BZGQgMCI6ITAsIkFzc2lnblN1YiAwIjohMCwiYXNzaWduIDAiOiEwLCJhc3NpZ25fYWRkIDAiOiEwLCJhc3NpZ25fc3ViIDAiOiEwLCJjb3VudF91cF90byAwIjohMCwiU2NhdHRlckFkZCAwIjohMCwiU2NhdHRlclN1YiAwIjohMCwiU2NhdHRlclVwZGF0ZSAwIjohMCwic2NhdHRlcl9hZGQgMCI6ITAsInNjYXR0ZXJfc3ViIDAiOiEwLCJzY2F0dGVyX3VwZGF0ZSAwIjohMH19O2Z1bmN0aW9uIG5sZShlLHQscil7bGV0IG49e30saT17fSxvPXt9LGE9S3NlKHQuaW5FbWJlZGRpbmdUeXBlcykscz1Lc2UodC5vdXRFbWJlZGRpbmdUeXBlcyksbD1bXSxjPWUubm9kZSx1PW5ldyBBcnJheShjLmxlbmd0aCk7cmV0dXJuIGV2KCJOb3JtYWxpemluZyBuYW1lcyIsMzAsKCk9PntsZXQgaD1uZXcgQXJyYXkoYy5sZW5ndGgpLGY9MCxwPWc9PntsZXQgXz1uZXcgXzAoZyk7cmV0dXJuIGEoXyk/KGwucHVzaChfLm5hbWUpLG5bXy5uYW1lXT1fLF8pOnMoXyk/KGwucHVzaChfLm5hbWUpLGlbXy5uYW1lXT1fLHBuLmVhY2goXy5pbnB1dHMseT0+e2xldCB4PXkubmFtZTtvW3hdPW9beF18fFtdLG9beF0ucHVzaChfKX0pLF8pOihoW2ZdPV8sdVtmXT1fLm5hbWUsZisrLF8pfTtwbi5lYWNoKGMscCk7bGV0IGQ9Zz0+e2xldCBfPVNhK2cuc2lnbmF0dXJlLm5hbWU7aWYocCh7bmFtZTpfLGlucHV0OltdLGRldmljZToiIixvcDoiIixhdHRyOltdfSksZy5zaWduYXR1cmUuaW5wdXRfYXJnKXtsZXQgYj0wLFM9Qz0+e2xldCBQPXAoe25hbWU6XytBbCtDLm5hbWUsaW5wdXQ6W10sZGV2aWNlOiIiLG9wOiJpbnB1dF9hcmciLGF0dHI6W3trZXk6IlQiLHZhbHVlOnt0eXBlOkMudHlwZX19XX0pO1AuZnVuY3Rpb25JbnB1dEluZGV4PWIsYisrfTtnLnNpZ25hdHVyZS5pbnB1dF9hcmcubmFtZT9TKGcuc2lnbmF0dXJlLmlucHV0X2FyZyk6cG4uZWFjaChnLnNpZ25hdHVyZS5pbnB1dF9hcmcsUyl9bGV0IHk9MCx4PXt9O2lmKGcuc2lnbmF0dXJlLm91dHB1dF9hcmcpe2xldCBiPVM9Pnt4W18rQWwrUy5uYW1lXT15LHkrK307Zy5zaWduYXR1cmUub3V0cHV0X2FyZy5uYW1lP2IoZy5zaWduYXR1cmUub3V0cHV0X2FyZyk6cG4uZWFjaChnLnNpZ25hdHVyZS5vdXRwdXRfYXJnLGIpfXBuLmVhY2goZy5ub2RlX2RlZixiPT57Yi5uYW1lPV8rIi8iK2IubmFtZSx0eXBlb2YgYi5pbnB1dD09InN0cmluZyImJihiLmlucHV0PVtiLmlucHV0XSk7bGV0IFM9cChiKTtwbi5pc051bWJlcih4W2IubmFtZV0pJiYoUy5mdW5jdGlvbk91dHB1dEluZGV4PXhbYi5uYW1lXSkscG4uZWFjaChTLmlucHV0cyxDPT57Qy5uYW1lPV8rQWwrQy5uYW1lfSl9KX07cmV0dXJuIGUubGlicmFyeSYmZS5saWJyYXJ5LmZ1bmN0aW9uJiZwbi5lYWNoKGUubGlicmFyeS5mdW5jdGlvbixkKSxoLnNwbGljZShmKSx1LnNwbGljZShmKSxofSxyLGpyLk5PUk1BTElaSU5HX05BTUVTKS50aGVuKGg9PmV2KCJCdWlsZGluZyB0aGUgZGF0YSBzdHJ1Y3R1cmUiLDcwLCgpPT57bGV0IGY9b2NyKHUsbCkscD1uZXcgWHU7cmV0dXJuIHBuLmVhY2goaCxkPT57bGV0IGc9ZltkLm5hbWVdfHxkLm5hbWU7cC5ub2Rlc1tnXT1kLGQubmFtZSBpbiBvJiYoZC5vdXRFbWJlZGRpbmdzPW9bZC5uYW1lXSxwbi5lYWNoKGQub3V0RW1iZWRkaW5ncyxfPT57Xy5uYW1lPWZbXy5uYW1lXXx8Xy5uYW1lfSkpLGQubmFtZT1nfSkscG4uZWFjaChoLGQ9Pntwbi5lYWNoKGQuaW5wdXRzLChnLF8pPT57bGV0IHk9Zy5uYW1lO2lmKHkgaW4gbil7bGV0IHg9blt5XTtkLmluRW1iZWRkaW5ncy5wdXNoKHgpO2ZvcihsZXQgYiBvZiB4LmlucHV0cylCbHQocCxmW2IubmFtZV18fGIubmFtZSxkLGIsdCxfKX1lbHNlIGlmKHkgaW4gaSl7bGV0IHg9aVt5XTtmb3IobGV0IGIgb2YgeC5pbnB1dHMpQmx0KHAsZltiLm5hbWVdfHxiLm5hbWUsZCxnLHQsXyl9ZWxzZSBCbHQocCxmW3ldfHx5LGQsZyx0LF8pfSl9KSxwbi5lYWNoKG4sKGQsZyk9PntkLm5hbWU9ZltkLm5hbWVdfHxkLm5hbWV9KSxwfSxyLGpyLkJVSUxEX1NMSU1fR1JBUEgpKX1mdW5jdGlvbiBlMyhlLHQscj17fSl7bGV0IG49bmV3IEpzZS5ncmFwaGxpYi5HcmFwaChyKTtyZXR1cm4gbi5zZXRHcmFwaCh7bmFtZTplLHJhbmtkaXI6ci5yYW5rZGlyfHwiQlQiLHR5cGU6dH0pLG59ZnVuY3Rpb24gS3NlKGUpe3JldHVybiBmdW5jdGlvbih0KXtmb3IobGV0IHI9MDtyPGUubGVuZ3RoO3IrKyl7bGV0IG49bmV3IFJlZ0V4cChlW3JdKTtpZih0eXBlb2YgdC5vcD09InN0cmluZyImJnQub3AubWF0Y2gobikpcmV0dXJuITB9cmV0dXJuITF9fWZ1bmN0aW9uIGFQKGUpe2xldCB0PWUuc3BsaXQoQWwpO3JldHVybiBlK0FsKyIoIit0W3QubGVuZ3RoLTFdKyIpIn1mdW5jdGlvbiBvY3IoZSx0KXtsZXQgcj17fSxuPXt9O2Uuc29ydCgpO2ZvcihsZXQgaT0wO2k8ZS5sZW5ndGgtMTsrK2kpe2xldCBvPWVbaV07cG4uZWFjaChsUChvKS5zbGljZSgwLC0xKSxhPT57blthXT0hMH0pO2ZvcihsZXQgYT1pKzE7YTxlLmxlbmd0aDsrK2Epe2xldCBzPWVbYV07aWYocG4uc3RhcnRzV2l0aChzLG8pKXtpZihzLmxlbmd0aD5vLmxlbmd0aCYmcy5jaGFyQXQoby5sZW5ndGgpPT09QWwpe3Jbb109YVAobyk7YnJlYWt9fWVsc2UgYnJlYWt9fXJldHVybiBwbi5lYWNoKHQsaT0+e2kgaW4gbiYmKHJbaV09YVAoaSkpfSkscn1mdW5jdGlvbiBac2UoZSl7bGV0IHQ9ZS5ub2RlcygpLm1hcChmdW5jdGlvbihyKXt2YXIgbjtyZXR1cm4obj1lLm5laWdoYm9ycyhyKSk9PW51bGw/dm9pZCAwOm4ubGVuZ3RofSk7cmV0dXJuIHQuc29ydCgpLHR9ZnVuY3Rpb24gaWxlKGUsdCl7bGV0IHI9WnNlKGUpLG49WnNlKHQpO2ZvcihsZXQgaT0wO2k8ci5sZW5ndGg7aSsrKWlmKHJbaV0hPT1uW2ldKXJldHVybiExO3JldHVybiEwfWZ1bmN0aW9uIGxQKGUsdCl7bGV0IHI9W10sbj1lLmluZGV4T2YoQWwpO2Zvcig7bj49MDspci5wdXNoKGUuc3Vic3RyaW5nKDAsbikpLG49ZS5pbmRleE9mKEFsLG4rMSk7aWYodCl7bGV0IGk9dFtlXTtpJiZyLnB1c2goaSl9cmV0dXJuIHIucHVzaChlKSxyfWZ1bmN0aW9uIF9IKGUpe3JldHVybiBlPT09dXIuRVhDTFVERT8iQWRkIHRvIG1haW4gZ3JhcGgiOiJSZW1vdmUgZnJvbSBtYWluIGdyYXBoIn1mdW5jdGlvbiBvbGUoZSl7cmV0dXJuIGU9PT1pcy5HUk9VUD8iVW5ncm91cCB0aGlzIHNlcmllcyBvZiBub2RlcyI6Ikdyb3VwIHRoaXMgc2VyaWVzIG9mIG5vZGVzIn12YXIgSGU9RWUoT2UoKSwxKTt2YXIgcnY9RWUoT2UoKSwxKTtmdW5jdGlvbiBhbGUoZSx0KXtsZXQgcj1zY3IoZSksbj1sY3Iocix0KTtyZXR1cm4gT2JqZWN0LmtleXMobikuc29ydChpPT5uW2ldLmxldmVsKS5yZWR1Y2UoKGksbyk9PihpW29dPW5bb10saSkse30pfWZ1bmN0aW9uIGFjcihlKXtsZXQgdD1ydi5tYXAoe2RlcHRoOmUuZGVwdGgsInxWfCI6ZS5tZXRhZ3JhcGgubm9kZXMoKS5sZW5ndGgsInxFfCI6ZS5tZXRhZ3JhcGguZWRnZXMoKS5sZW5ndGh9LGZ1bmN0aW9uKG4saSl7cmV0dXJuIGkrIj0iK259KS5qb2luKCIgIikscj1ydi5tYXAoZS5vcEhpc3RvZ3JhbSxmdW5jdGlvbihuLGkpe3JldHVybiBpKyI9IitufSkuam9pbigiLCIpO3JldHVybiB0KyIgW29wc10gIityfWZ1bmN0aW9uIHNjcihlKXtsZXQgdD1lLmdldE5vZGVNYXAoKSxyPU9iamVjdC5rZXlzKHQpLnJlZHVjZSgobixpKT0+e2xldCBvPXRbaV07aWYoby50eXBlIT09anQuTUVUQSlyZXR1cm4gbjtsZXQgYT1pLnNwbGl0KCIvIikubGVuZ3RoLTEscz1hY3IobyksbD1uW3NdfHx7bm9kZXM6W10sbGV2ZWw6YX07cmV0dXJuIG5bc109bCxsLm5vZGVzLnB1c2gobyksbC5sZXZlbD5hJiYobC5sZXZlbD1hKSxufSx7fSk7cmV0dXJuIE9iamVjdC5rZXlzKHIpLm1hcChuPT5bbixyW25dXSkuZmlsdGVyKChbbixpXSk9PntsZXR7bm9kZXM6b309aTtpZihvLmxlbmd0aD4xKXJldHVybiEwO2xldCBhPW9bMF07cmV0dXJuIGEudHlwZT09PWp0Lk1FVEEmJmEuYXNzb2NpYXRlZEZ1bmN0aW9ufSkuc29ydCgoW24saV0pPT5pLm5vZGVzWzBdLmRlcHRoKX1mdW5jdGlvbiBsY3IoZSx0KXtyZXR1cm4gcnYucmVkdWNlKGUsZnVuY3Rpb24obixpKXtsZXQgbz1pWzBdLGE9aVsxXS5ub2RlcyxzPVtdO3JldHVybiBhLmZvckVhY2goZnVuY3Rpb24obCl7Zm9yKGxldCBjPTA7YzxzLmxlbmd0aDtjKyspaWYoIXR8fGNjcihzW2NdLm1ldGFub2RlLm1ldGFncmFwaCxsLm1ldGFncmFwaCkpe2wudGVtcGxhdGVJZD1zW2NdLm1ldGFub2RlLnRlbXBsYXRlSWQsc1tjXS5tZW1iZXJzLnB1c2gobC5uYW1lKTtyZXR1cm59bC50ZW1wbGF0ZUlkPW8rIlsiK3MubGVuZ3RoKyJdIixzLnB1c2goe21ldGFub2RlOmwsbWVtYmVyczpbbC5uYW1lXX0pfSkscy5mb3JFYWNoKGZ1bmN0aW9uKGwpe25bbC5tZXRhbm9kZS50ZW1wbGF0ZUlkXT17bGV2ZWw6aVsxXS5sZXZlbCxub2RlczpsLm1lbWJlcnN9fSksbn0se30pfWZ1bmN0aW9uIHlIKGUsdCxyKXtyZXR1cm4gcnYuc29ydEJ5KGUsW249PnQubm9kZShuKS5vcCxuPT50Lm5vZGUobikudGVtcGxhdGVJZCxuPT57dmFyIGk7cmV0dXJuKGk9dC5uZWlnaGJvcnMobikpPT1udWxsP3ZvaWQgMDppLmxlbmd0aH0sbj0+e3ZhciBpO3JldHVybihpPXQucHJlZGVjZXNzb3JzKG4pKT09bnVsbD92b2lkIDA6aS5sZW5ndGh9LG49Pnt2YXIgaTtyZXR1cm4oaT10LnN1Y2Nlc3NvcnMobikpPT1udWxsP3ZvaWQgMDppLmxlbmd0aH0sbj0+bi5zdWJzdHIoci5sZW5ndGgpXSl9ZnVuY3Rpb24gY2NyKGUsdCl7aWYoIWlsZShlLHQpKXJldHVybiExO2xldCByPWUuZ3JhcGgoKS5uYW1lLG49dC5ncmFwaCgpLm5hbWUsaT17fSxvPXt9LGE9W107ZnVuY3Rpb24gcyh1LGgpe2xldCBmPXUuc3Vic3RyKHIubGVuZ3RoKSxwPWguc3Vic3RyKG4ubGVuZ3RoKTtyZXR1cm4gaVtmXV5vW3BdPyhjb25zb2xlLndhcm4oImRpZmZlcmVudCB2aXNpdCBwYXR0ZXJuIiwiWyIrcisiXSIsZiwiWyIrbisiXSIscCksITApOihpW2ZdfHwoaVtmXT1vW3BdPSEwLGEucHVzaCh7bjE6dSxuMjpofSkpLCExKX1sZXQgbD1lLnNvdXJjZXMoKSxjPXQuc291cmNlcygpO2lmKGwubGVuZ3RoIT09Yy5sZW5ndGgpcmV0dXJuIGNvbnNvbGUubG9nKCJkaWZmZXJlbnQgc291cmNlIGxlbmd0aCIpLCExO2w9eUgobCxlLHIpLGM9eUgoYyx0LG4pO2ZvcihsZXQgdT0wO3U8bC5sZW5ndGg7dSsrKWlmKHMobFt1XSxjW3VdKSlyZXR1cm4hMTtmb3IoO2EubGVuZ3RoPjA7KXtsZXQgdT1hLnBvcCgpO2lmKCF1Y3IoZS5ub2RlKHU9PW51bGw/dm9pZCAwOnUubjEpLHQubm9kZSh1PT1udWxsP3ZvaWQgMDp1Lm4yKSkpcmV0dXJuITE7bGV0IGY9ZS5zdWNjZXNzb3JzKHU9PW51bGw/dm9pZCAwOnUubjEpLHA9dC5zdWNjZXNzb3JzKHU9PW51bGw/dm9pZCAwOnUubjIpO2lmKChmPT1udWxsP3ZvaWQgMDpmLmxlbmd0aCkhPT0ocD09bnVsbD92b2lkIDA6cC5sZW5ndGgpKXJldHVybiBjb25zb2xlLmxvZygiIyBvZiBzdWNjZXNzb3JzIG1pc21hdGNoIixmLHApLCExO2Y9eUgoZixlLHIpLHA9eUgocCx0LG4pO2ZvcihsZXQgZD0wO2Q8KGY9PW51bGw/dm9pZCAwOmYubGVuZ3RoKTtkKyspaWYocyhmPT1udWxsP3ZvaWQgMDpmW2RdLHA9PW51bGw/dm9pZCAwOnBbZF0pKXJldHVybiExfXJldHVybiEwfWZ1bmN0aW9uIHVjcihlLHQpe2lmKGUudHlwZT09PWp0Lk1FVEEpe2xldCByPWUsbj10O3JldHVybiEhci50ZW1wbGF0ZUlkJiYhIW4udGVtcGxhdGVJZCYmci50ZW1wbGF0ZUlkPT09bi50ZW1wbGF0ZUlkfWVsc2V7aWYoZS50eXBlPT09anQuT1AmJnQudHlwZT09PWp0Lk9QKXJldHVybiBlLm9wPT09dC5vcDtpZihlLnR5cGU9PT1qdC5TRVJJRVMmJnQudHlwZT09PWp0LlNFUklFUyl7bGV0IHI9ZSxuPXQsaT1yLm1ldGFncmFwaC5ub2RlQ291bnQoKTtyZXR1cm4gaT09PW4ubWV0YWdyYXBoLm5vZGVDb3VudCgpJiYoaT09PTB8fHIubWV0YWdyYXBoLm5vZGUoci5tZXRhZ3JhcGgubm9kZXMoKVswXSkub3A9PT1uLm1ldGFncmFwaC5ub2RlKG4ubWV0YWdyYXBoLm5vZGVzKClbMF0pLm9wKX19cmV0dXJuITF9dmFyIERkOyhmdW5jdGlvbihlKXtlW2UuVEVNUExBVEVTX1VQREFURUQ9MF09IlRFTVBMQVRFU19VUERBVEVEIn0pKERkfHwoRGQ9e30pKTt2YXIgb3M9Y2xhc3MgZXh0ZW5kcyBwSHtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMuaGFzU2hhcGVJbmZvPSExLHRoaXMubWF4TWV0YUVkZ2VTaXplPTEsdGhpcy5ncmFwaE9wdGlvbnM9e30sdGhpcy50ZW1wbGF0ZXM9bnVsbCx0aGlzLmdyYXBoT3B0aW9ucy5jb21wb3VuZD0hMCx0aGlzLmdyYXBoT3B0aW9ucy5yYW5rZGlyPXQucmFua0RpcmVjdGlvbix0aGlzLnJvb3Q9c1AocWMsdGhpcy5ncmFwaE9wdGlvbnMpLHRoaXMubGlicmFyeUZ1bmN0aW9ucz17fSx0aGlzLnNlcmllc0dyb3VwTWFwPW5ldyBNYXAodC5zZXJpZXNNYXApLHRoaXMuZGV2aWNlcz1udWxsLHRoaXMueGxhQ2x1c3RlcnM9bnVsbCx0aGlzLnZlcmlmeVRlbXBsYXRlPXQudmVyaWZ5VGVtcGxhdGUsdGhpcy5pbmRleD17fSx0aGlzLmluZGV4W3FjXT10aGlzLnJvb3QsdGhpcy5vcmRlcmluZ3M9e319Z2V0U2VyaWVzR3JvdXBUeXBlKHQpe3ZhciByO3JldHVybihyPXRoaXMuc2VyaWVzR3JvdXBNYXAuZ2V0KHQpKSE9bnVsbD9yOmlzLkdST1VQfXNldFNlcmllc0dyb3VwVHlwZSh0LHIpe3JldHVybiB0aGlzLnNlcmllc0dyb3VwTWFwLnNldCh0LHIpfWJ1aWxkU2VyaWVzR3JvdXBNYXBUb2dnbGVkKHQpe2xldCByPXRoaXMuZ2V0U2VyaWVzR3JvdXBUeXBlKHQpPT09aXMuR1JPVVA/aXMuVU5HUk9VUDppcy5HUk9VUDtyZXR1cm4gbmV3IE1hcChbLi4udGhpcy5zZXJpZXNHcm91cE1hcCxbdCxyXV0pfWdldE5vZGVNYXAoKXtyZXR1cm4gdGhpcy5pbmRleH1ub2RlKHQpe3JldHVybiB0aGlzLmluZGV4W3RdfXNldE5vZGUodCxyKXt0aGlzLmluZGV4W3RdPXJ9Z2V0QnJpZGdlZ3JhcGgodCl7bGV0IHI9dGhpcy5pbmRleFt0XTtpZighcil0aHJvdyBFcnJvcigiQ291bGQgbm90IGZpbmQgbm9kZSBpbiBoaWVyYXJjaHk6ICIrdCk7aWYoISgibWV0YWdyYXBoImluIHIpKXJldHVybiBudWxsO2xldCBuPXI7aWYobi5icmlkZ2VncmFwaClyZXR1cm4gbi5icmlkZ2VncmFwaDtsZXQgaT1uLmJyaWRnZWdyYXBoPWUzKCJCUklER0VHUkFQSCIsZzAuQlJJREdFLHRoaXMuZ3JhcGhPcHRpb25zKTtpZighci5wYXJlbnROb2RlfHwhKCJtZXRhZ3JhcGgiaW4gci5wYXJlbnROb2RlKSlyZXR1cm4gaTtsZXQgbz1yLnBhcmVudE5vZGUsYT1vLm1ldGFncmFwaCxzPXRoaXMuZ2V0QnJpZGdlZ3JhcGgoby5uYW1lKTtyZXR1cm4gSGUuZWFjaChbYSxzXSxsPT57bC5lZGdlcygpLmZpbHRlcihjPT5jLnY9PT10fHxjLnc9PT10KS5mb3JFYWNoKGM9PntsZXQgdT1jLnc9PT10LGg9bC5lZGdlKGMpO0hlLmVhY2goaC5iYXNlRWRnZUxpc3QsZj0+e2xldFtwLGRdPXU/W2YudyxjLnZdOltmLnYsYy53XSxnPXRoaXMuZ2V0Q2hpbGROYW1lKHQscCksXz17djp1P2Q6Zyx3OnU/ZzpkfSx5PWkuZWRnZShfKTt5fHwoeT1WbHQoXy52LF8udykseS5pbmJvdW5kPXUsaS5zZXRFZGdlKF8udixfLncseSkpLHkuYWRkQmFzZUVkZ2UoZix0aGlzKX0pfSl9KSxpfWdldENoaWxkTmFtZSh0LHIpe2xldCBuPXRoaXMuaW5kZXhbcl07Zm9yKDtuOyl7aWYobi5wYXJlbnROb2RlJiZuLnBhcmVudE5vZGUubmFtZT09PXQpcmV0dXJuIG4ubmFtZTtuPW4ucGFyZW50Tm9kZX10aHJvdyBFcnJvcigiQ291bGQgbm90IGZpbmQgaW1tZWRpYXRlIGNoaWxkIGZvciBkZXNjZW5kYW50OiAiK3IpfWdldFByZWRlY2Vzc29ycyh0KXtsZXQgcj10aGlzLmluZGV4W3RdO2lmKCFyKXRocm93IEVycm9yKCJDb3VsZCBub3QgZmluZCBub2RlIHdpdGggbmFtZTogIit0KTtsZXQgbj10aGlzLmdldE9uZVdheUVkZ2VzKHIsITApO3JldHVybiByLmlzR3JvdXBOb2RlfHxIZS5lYWNoKHIuaW5FbWJlZGRpbmdzLGk9PntIZS5lYWNoKHIuaW5wdXRzLG89PntpZihvLm5hbWU9PT1pLm5hbWUpe2xldCBhPW5ldyBxZihpLm5hbWUsdCk7YS5hZGRCYXNlRWRnZSh7aXNDb250cm9sRGVwZW5kZW5jeTpvLmlzQ29udHJvbERlcGVuZGVuY3ksb3V0cHV0VGVuc29yS2V5Om8ub3V0cHV0VGVuc29yS2V5LGlzUmVmZXJlbmNlRWRnZTohMSx2OmkubmFtZSx3OnR9LHRoaXMpLG4ucmVndWxhci5wdXNoKGEpfX0pfSksbn1nZXRTdWNjZXNzb3JzKHQpe2xldCByPXRoaXMuaW5kZXhbdF07aWYoIXIpdGhyb3cgRXJyb3IoIkNvdWxkIG5vdCBmaW5kIG5vZGUgd2l0aCBuYW1lOiAiK3QpO2xldCBuPXRoaXMuZ2V0T25lV2F5RWRnZXMociwhMSk7cmV0dXJuIHIuaXNHcm91cE5vZGV8fEhlLmVhY2goci5vdXRFbWJlZGRpbmdzLGk9PntIZS5lYWNoKGkuaW5wdXRzLG89PntpZihvLm5hbWU9PT10KXtsZXQgYT1uZXcgcWYodCxpLm5hbWUpO2EuYWRkQmFzZUVkZ2Uoe2lzQ29udHJvbERlcGVuZGVuY3k6by5pc0NvbnRyb2xEZXBlbmRlbmN5LG91dHB1dFRlbnNvcktleTpvLm91dHB1dFRlbnNvcktleSxpc1JlZmVyZW5jZUVkZ2U6ITEsdjp0LHc6aS5uYW1lfSx0aGlzKSxuLnJlZ3VsYXIucHVzaChhKX19KX0pLG59Z2V0T25lV2F5RWRnZXModCxyKXtsZXQgbj17Y29udHJvbDpbXSxyZWd1bGFyOltdfTtpZighdC5wYXJlbnROb2RlfHwhdC5wYXJlbnROb2RlLmlzR3JvdXBOb2RlKXJldHVybiBuO2xldCBpPXQucGFyZW50Tm9kZSxvPWkubWV0YWdyYXBoLGE9dGhpcy5nZXRCcmlkZ2VncmFwaChpLm5hbWUpO3JldHVybiBzbGUobyx0LHIsbiksc2xlKGEsdCxyLG4pLG59Z2V0VG9wb2xvZ2ljYWxPcmRlcmluZyh0KXtsZXQgcj10aGlzLmluZGV4W3RdO2lmKCFyKXRocm93IEVycm9yKCJDb3VsZCBub3QgZmluZCBub2RlIHdpdGggbmFtZTogIit0KTtpZighci5pc0dyb3VwTm9kZSlyZXR1cm4gbnVsbDtpZih0IGluIHRoaXMub3JkZXJpbmdzKXJldHVybiB0aGlzLm9yZGVyaW5nc1t0XTtsZXQgbj17fSxpPXt9LG89ci5tZXRhZ3JhcGg7SGUuZWFjaChvLmVkZ2VzKCksYz0+eyFvLmVkZ2UoYykubnVtUmVndWxhckVkZ2VzfHwoYy52IGluIG58fChuW2Mudl09W10pLG5bYy52XS5wdXNoKGMudyksaVtjLnddPSEwKX0pO2xldCBhPUhlLmRpZmZlcmVuY2UoSGUua2V5cyhuKSxIZS5rZXlzKGkpKSxzPXRoaXMub3JkZXJpbmdzW3RdPXt9LGw9MDtmb3IoO2EubGVuZ3RoOyl7bGV0IGM9YS5zaGlmdCgpO3NbY109bCsrLEhlLmVhY2gobltjXSx1PT5hLnB1c2godSkpLGRlbGV0ZSBuW2NdfXJldHVybiBzfWdldFRlbXBsYXRlSW5kZXgoKXtpZighdGhpcy50ZW1wbGF0ZXMpcmV0dXJuIG51bGw7bGV0IHQ9WEwodGhpcy50ZW1wbGF0ZXMpO2lmKCF0Lmxlbmd0aClyZXR1cm4gbnVsbDtsZXQgcj1ndSgpLmRvbWFpbih0KS5yYW5nZShJcigwLHQubGVuZ3RoKSk7cmV0dXJuIG49PnIobil9dXBkYXRlVGVtcGxhdGVzKCl7UmQoIkZpbmRpbmcgc2ltaWxhciBzdWJncmFwaHMiLCgpPT57dGhpcy50ZW1wbGF0ZXM9YWxlKHRoaXMsdGhpcy52ZXJpZnlUZW1wbGF0ZSksdGhpcy5kaXNwYXRjaEV2ZW50KERkLlRFTVBMQVRFU19VUERBVEVEKX0sanIuSElFUkFSQ0hZX0ZJTkRfU0lNSUxBUl9TVUJHUkFQSFMpfX07ZnVuY3Rpb24gc2xlKGUsdCxyLG4pe2xldCBpPXI/ZS5pbkVkZ2VzKHQubmFtZSk6ZS5vdXRFZGdlcyh0Lm5hbWUpO0hlLmVhY2goaSxvPT57bGV0IGE9ZS5lZGdlKG8pOyhhLm51bVJlZ3VsYXJFZGdlcz9uLnJlZ3VsYXI6bi5jb250cm9sKS5wdXNoKGEpfSl9dmFyIHIzPXt2ZXJpZnlUZW1wbGF0ZTohMCxzZXJpZXNOb2RlTWluU2l6ZTo1LHNlcmllc01hcDpuZXcgTWFwLHJhbmtEaXJlY3Rpb246IkJUIix1c2VHZW5lcmFsaXplZFNlcmllc1BhdHRlcm5zOiExfTtmdW5jdGlvbiB4SChlLHQscil7bGV0IG49bmV3IG9zKHQpLGk9e307cmV0dXJuIGV2KCJBZGRpbmcgbm9kZXMiLDMwLCgpPT57bGV0IG89e30sYT17fTtIZS5lYWNoKGUubm9kZXMsKHMsbCk9PntzLmRldmljZSYmKG9bcy5kZXZpY2VdPSEwKSxzLnhsYUNsdXN0ZXImJihhW3MueGxhQ2x1c3Rlcl09ITApfSksbi5kZXZpY2VzPUhlLmtleXMobyksbi54bGFDbHVzdGVycz1IZS5rZXlzKGEpLGZjcihuLGUpfSxyLGpyLkhJRVJBUkNIWV9BRERfTk9ERVMpLnRoZW4oKCk9PmV2KCJEZXRlY3Qgc2VyaWVzIiwzMCwoKT0+e3Quc2VyaWVzTm9kZU1pblNpemU+MCYmdWxlKG4ucm9vdCxuLGksdC5zZXJpZXNOb2RlTWluU2l6ZSx0LnNlcmllc01hcCx0LnVzZUdlbmVyYWxpemVkU2VyaWVzUGF0dGVybnMpfSxyLGpyLkhJRVJBUkNIWV9ERVRFQ1RfU0VSSUVTKSkudGhlbigoKT0+ZXYoIkFkZGluZyBlZGdlcyIsNDAsKCk9PntwY3IobixlLGkpfSxyLGpyLkhJRVJBUkNIWV9BRERfRURHRVMpKS50aGVuKCgpPT5uKX1mdW5jdGlvbiBsbGUoZSx0KXtsZXQgcj17fSxuPXt9O0hlLmVhY2goZS5yb290LmxlYXZlcygpLGk9PntsZXQgbz1lLm5vZGUoaSk7by5kZXZpY2UhPW51bGwmJihyW28uZGV2aWNlXT0hMCksby54bGFDbHVzdGVyIT1udWxsJiYobltvLnhsYUNsdXN0ZXJdPSEwKX0pLGUuZGV2aWNlcz1IZS5rZXlzKHIpLGUueGxhQ2x1c3RlcnM9SGUua2V5cyhuKSxIZS5lYWNoKGUuZ2V0Tm9kZU1hcCgpLChpLG8pPT57aS5pc0dyb3VwTm9kZSYmKGkuc3RhdHM9bmV3IEtTKG51bGwpLGkuZGV2aWNlSGlzdG9ncmFtPXt9KX0pLEhlLmVhY2goZS5yb290LmxlYXZlcygpLGk9PntsZXQgbz1lLm5vZGUoaSksYT1vO2Zvcig7YS5wYXJlbnROb2RlIT1udWxsOyl7aWYoby5kZXZpY2UhPW51bGwpe2xldCBzPWEucGFyZW50Tm9kZS5kZXZpY2VIaXN0b2dyYW07c1tvLmRldmljZV09KHNbby5kZXZpY2VdfHwwKSsxfWlmKG8ueGxhQ2x1c3RlciE9bnVsbCl7bGV0IHM9YS5wYXJlbnROb2RlLnhsYUNsdXN0ZXJIaXN0b2dyYW07c1tvLnhsYUNsdXN0ZXJdPShzW28ueGxhQ2x1c3Rlcl18fDApKzF9by5zdGF0cyE9bnVsbCYmYS5wYXJlbnROb2RlLnN0YXRzLmNvbWJpbmUoby5zdGF0cyksYT1hLnBhcmVudE5vZGV9fSl9ZnVuY3Rpb24gY2xlKGUpe2xldCB0PVtdLHI9e307cmV0dXJuIEhlLmVhY2goZS5yb290LmxlYXZlcygpLG49PntsZXQgaT1lLm5vZGUobik7aWYoaS50eXBlPT1qdC5PUCl7bGV0IG89aTtpZighby5jb21wYXRpYmxlKWlmKG8ub3duaW5nU2VyaWVzKXtpZihlLmdldFNlcmllc0dyb3VwVHlwZShvLm93bmluZ1Nlcmllcyk9PT1pcy5VTkdST1VQKXQucHVzaChvKTtlbHNlIGlmKCFyW28ub3duaW5nU2VyaWVzXSl7bGV0IGE9ZS5ub2RlKG8ub3duaW5nU2VyaWVzKTthJiYocltvLm93bmluZ1Nlcmllc109YSx0LnB1c2goYSkpfX1lbHNlIHQucHVzaChvKTtIZS5lYWNoKG8uaW5FbWJlZGRpbmdzLGE9PnthLmNvbXBhdGlibGV8fHQucHVzaChhKX0pLEhlLmVhY2goby5vdXRFbWJlZGRpbmdzLGE9PnthLmNvbXBhdGlibGV8fHQucHVzaChhKX0pfX0pLHR9ZnVuY3Rpb24gZmNyKGUsdCl7bGV0IHI9e307SGUuZWFjaCh0Lm5vZGVzLChuLGkpPT57bGV0IG89bFAobi5uYW1lKSxhPWUucm9vdDthLmRlcHRoPU1hdGgubWF4KG8ubGVuZ3RoLGEuZGVwdGgpLHJbbi5vcF18fChyW24ub3BdPVtdKSxyW24ub3BdLnB1c2gobik7Zm9yKGxldCBzPTA7czxvLmxlbmd0aCYmKGEuZGVwdGg9TWF0aC5tYXgoYS5kZXB0aCxvLmxlbmd0aC1zKSxhLmNhcmRpbmFsaXR5Kz1uLmNhcmRpbmFsaXR5LGEub3BIaXN0b2dyYW1bbi5vcF09KGEub3BIaXN0b2dyYW1bbi5vcF18fDApKzEsbi5kZXZpY2UhPW51bGwmJihhLmRldmljZUhpc3RvZ3JhbVtuLmRldmljZV09KGEuZGV2aWNlSGlzdG9ncmFtW24uZGV2aWNlXXx8MCkrMSksbi54bGFDbHVzdGVyIT1udWxsJiYoYS54bGFDbHVzdGVySGlzdG9ncmFtW24ueGxhQ2x1c3Rlcl09KGEueGxhQ2x1c3Rlckhpc3RvZ3JhbVtuLnhsYUNsdXN0ZXJdfHwwKSsxKSxuLmNvbXBhdGlibGU/YS5jb21wYXRpYmlsaXR5SGlzdG9ncmFtLmNvbXBhdGlibGU9KGEuY29tcGF0aWJpbGl0eUhpc3RvZ3JhbS5jb21wYXRpYmxlfHwwKSsxOmEuY29tcGF0aWJpbGl0eUhpc3RvZ3JhbS5pbmNvbXBhdGlibGU9KGEuY29tcGF0aWJpbGl0eUhpc3RvZ3JhbS5pbmNvbXBhdGlibGV8fDApKzEsSGUuZWFjaChuLmluRW1iZWRkaW5ncyx1PT57dS5jb21wYXRpYmxlP2EuY29tcGF0aWJpbGl0eUhpc3RvZ3JhbS5jb21wYXRpYmxlPShhLmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uY29tcGF0aWJsZXx8MCkrMTphLmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uaW5jb21wYXRpYmxlPShhLmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uaW5jb21wYXRpYmxlfHwwKSsxfSksSGUuZWFjaChuLm91dEVtYmVkZGluZ3MsdT0+e3UuY29tcGF0aWJsZT9hLmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uY29tcGF0aWJsZT0oYS5jb21wYXRpYmlsaXR5SGlzdG9ncmFtLmNvbXBhdGlibGV8fDApKzE6YS5jb21wYXRpYmlsaXR5SGlzdG9ncmFtLmluY29tcGF0aWJsZT0oYS5jb21wYXRpYmlsaXR5SGlzdG9ncmFtLmluY29tcGF0aWJsZXx8MCkrMX0pLHMhPT1vLmxlbmd0aC0xKTtzKyspe2xldCBsPW9bc10sYz1lLm5vZGUobCk7aWYoIWMmJihjPXNQKGwsZS5ncmFwaE9wdGlvbnMpLGMucGFyZW50Tm9kZT1hLGUuc2V0Tm9kZShsLGMpLGEubWV0YWdyYXBoLnNldE5vZGUobCxjKSxsLmluZGV4T2YoU2EpPT09MCYmYS5uYW1lPT09cWMpKXtsZXQgdT1sLnN1YnN0cmluZyhTYS5sZW5ndGgpO3JbdV18fChyW3VdPVtdKSxlLmxpYnJhcnlGdW5jdGlvbnNbdV09e25vZGU6Yyx1c2FnZXM6clt1XX0sYy5hc3NvY2lhdGVkRnVuY3Rpb249dX1hPWN9ZS5zZXROb2RlKG4ubmFtZSxuKSxuLnBhcmVudE5vZGU9YSxhLm1ldGFncmFwaC5zZXROb2RlKG4ubmFtZSxuKSxIZS5lYWNoKG4uaW5FbWJlZGRpbmdzLGZ1bmN0aW9uKHMpe2Uuc2V0Tm9kZShzLm5hbWUscykscy5wYXJlbnROb2RlPW59KSxIZS5lYWNoKG4ub3V0RW1iZWRkaW5ncyxmdW5jdGlvbihzKXtlLnNldE5vZGUocy5uYW1lLHMpLHMucGFyZW50Tm9kZT1ufSl9KX1mdW5jdGlvbiBwY3IoZSx0LHIpe2xldCBuPWUuZ2V0Tm9kZU1hcCgpLGk9W10sbz1bXSxhPShzLGwpPT57bGV0IGM9MDtmb3IoO3M7KWxbYysrXT1zLm5hbWUscz1zLnBhcmVudE5vZGU7cmV0dXJuIGMtMX07SGUuZWFjaCh0LmVkZ2VzLHM9PntsZXQgbD1hKHQubm9kZXNbcy52XSxpKSxjPWEodC5ub2Rlc1tzLnddLG8pO2lmKGw9PT0tMXx8Yz09PS0xKXJldHVybjtmb3IoO2lbbF09PT1vW2NdOylpZihsLS0sYy0tLGw8MHx8YzwwKXRocm93IEVycm9yKCJObyBkaWZmZXJlbmNlIGZvdW5kIGJldHdlZW4gYW5jZXN0b3IgcGF0aHMuIik7bGV0IHU9bltpW2wrMV1dLGg9aVtsXSxmPW9bY10scD11Lm1ldGFncmFwaC5lZGdlKGgsZik7cHx8KHA9Vmx0KGgsZiksdS5tZXRhZ3JhcGguc2V0RWRnZShoLGYscCkpLCF1Lmhhc05vbkNvbnRyb2xFZGdlcyYmIXMuaXNDb250cm9sRGVwZW5kZW5jeSYmKHUuaGFzTm9uQ29udHJvbEVkZ2VzPSEwKSxwLmFkZEJhc2VFZGdlKHMsZSl9KX1mdW5jdGlvbiB1bGUoZSx0LHIsbixpLG8pe2xldCBhPWUubWV0YWdyYXBoO0hlLmVhY2goYS5ub2RlcygpLHU9PntsZXQgaD1hLm5vZGUodSk7aC50eXBlPT09anQuTUVUQSYmdWxlKGgsdCxyLG4saSxvKX0pO2xldCBzPWRjcihhKSxjPShvP2djcjptY3IpKHMsYSx0LmdyYXBoT3B0aW9ucyk7SGUuZWFjaChjLGZ1bmN0aW9uKHUsaCl7bGV0IGY9dS5tZXRhZ3JhcGgubm9kZXMoKTtIZS5lYWNoKGYscD0+e2xldCBkPWEubm9kZShwKTtkLm93bmluZ1Nlcmllc3x8KGQub3duaW5nU2VyaWVzPWgpfSksZi5sZW5ndGg8biYmdC5nZXRTZXJpZXNHcm91cFR5cGUodS5uYW1lKT09PWlzLkdST1VQJiZ0LnNldFNlcmllc0dyb3VwVHlwZSh1Lm5hbWUsaXMuVU5HUk9VUCksdC5nZXRTZXJpZXNHcm91cFR5cGUodS5uYW1lKSE9PWlzLlVOR1JPVVAmJih0LnNldE5vZGUoaCx1KSxhLnNldE5vZGUoaCx1KSxIZS5lYWNoKGYscD0+e2xldCBkPWEubm9kZShwKTt1Lm1ldGFncmFwaC5zZXROb2RlKHAsZCksdS5wYXJlbnROb2RlPWQucGFyZW50Tm9kZSx1LmNhcmRpbmFsaXR5KyssZC5kZXZpY2UhPW51bGwmJih1LmRldmljZUhpc3RvZ3JhbVtkLmRldmljZV09KHUuZGV2aWNlSGlzdG9ncmFtW2QuZGV2aWNlXXx8MCkrMSksZC54bGFDbHVzdGVyIT1udWxsJiYodS54bGFDbHVzdGVySGlzdG9ncmFtW2QueGxhQ2x1c3Rlcl09KHUueGxhQ2x1c3Rlckhpc3RvZ3JhbVtkLnhsYUNsdXN0ZXJdfHwwKSsxKSxkLmNvbXBhdGlibGU/dS5jb21wYXRpYmlsaXR5SGlzdG9ncmFtLmNvbXBhdGlibGU9KHUuY29tcGF0aWJpbGl0eUhpc3RvZ3JhbS5jb21wYXRpYmxlfHwwKSsxOnUuY29tcGF0aWJpbGl0eUhpc3RvZ3JhbS5pbmNvbXBhdGlibGU9KHUuY29tcGF0aWJpbGl0eUhpc3RvZ3JhbS5pbmNvbXBhdGlibGV8fDApKzEsSGUuZWFjaChkLmluRW1iZWRkaW5ncyxnPT57Zy5jb21wYXRpYmxlP3UuY29tcGF0aWJpbGl0eUhpc3RvZ3JhbS5jb21wYXRpYmxlPSh1LmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uY29tcGF0aWJsZXx8MCkrMTp1LmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uaW5jb21wYXRpYmxlPSh1LmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uaW5jb21wYXRpYmxlfHwwKSsxfSksSGUuZWFjaChkLm91dEVtYmVkZGluZ3MsZz0+e2cuY29tcGF0aWJsZT91LmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uY29tcGF0aWJsZT0odS5jb21wYXRpYmlsaXR5SGlzdG9ncmFtLmNvbXBhdGlibGV8fDApKzE6dS5jb21wYXRpYmlsaXR5SGlzdG9ncmFtLmluY29tcGF0aWJsZT0odS5jb21wYXRpYmlsaXR5SGlzdG9ncmFtLmluY29tcGF0aWJsZXx8MCkrMX0pLGQucGFyZW50Tm9kZT11LHJbcF09aCxhLnJlbW92ZU5vZGUocCl9KSl9KX1mdW5jdGlvbiBkY3IoZSl7bGV0IHQ9e307cmV0dXJuIEhlLnJlZHVjZShlLm5vZGVzKCksKHIsbik9PntsZXQgaT1lLm5vZGUobik7aWYoaS50eXBlPT09anQuTUVUQSlyZXR1cm4gcjtsZXQgbz1pLm9wO3JldHVybiBvJiYocltvXT1yW29dfHxbXSxyW29dLnB1c2goaS5uYW1lKSkscn0sdCl9ZnVuY3Rpb24gbWNyKGUsdCxyKXtsZXQgbj17fTtyZXR1cm4gSGUuZWFjaChlLGZ1bmN0aW9uKGksbyl7aWYoaS5sZW5ndGg8PTEpcmV0dXJuO2xldCBhPXt9O0hlLmVhY2goaSxmdW5jdGlvbihzKXtsZXQgbD1zLmNoYXJBdChzLmxlbmd0aC0xKT09PSIqIixjPXMuc3BsaXQoIi8iKSx1PWNbYy5sZW5ndGgtMV0saD1jLnNsaWNlKDAsYy5sZW5ndGgtMSkuam9pbigiLyIpLGY9dS5tYXRjaCgvXihcRCopKFxkKykkLykscCxkLGc9IiI7Zj8ocD1mWzFdLGQ9ZlsyXSk6KHA9bD91LnN1YnN0cigwLHUubGVuZ3RoLTEpOnUsZD0wLGc9bD8iKiI6IiIpO2xldCBfPXQzKHAsZyxoKTthW19dPWFbX118fFtdO2xldCB5PVFTKHAsZyxoLCtkLHMscik7YVtfXS5wdXNoKHkpfSksSGUuZWFjaChhLGZ1bmN0aW9uKHMsbCl7aWYocy5sZW5ndGg8MilyZXR1cm47cy5zb3J0KGZ1bmN0aW9uKHUsaCl7cmV0dXJuK3UuY2x1c3RlcklkLStoLmNsdXN0ZXJJZH0pO2xldCBjPVtzWzBdXTtmb3IobGV0IHU9MTt1PHMubGVuZ3RoO3UrKyl7bGV0IGg9c1t1XTtpZihoLmNsdXN0ZXJJZD09PWNbYy5sZW5ndGgtMV0uY2x1c3RlcklkKzEpe2MucHVzaChoKTtjb250aW51ZX12SChjLG4sK28sdCxyKSxjPVtoXX12SChjLG4sK28sdCxyKX0pfSksbn1mdW5jdGlvbiBnY3IoZSx0LHIpe2xldCBuPXt9O3JldHVybiBIZS5lYWNoKGUsZnVuY3Rpb24oaSxvKXtpZihpLmxlbmd0aDw9MSlyZXR1cm47bGV0IGE9e30scz17fTtIZS5lYWNoKGksZnVuY3Rpb24oYyl7bGV0IHU9Yy5jaGFyQXQoYy5sZW5ndGgtMSk9PT0iKiIsaD1jLnNwbGl0KCIvIiksZj1oW2gubGVuZ3RoLTFdLHA9aC5zbGljZSgwLGgubGVuZ3RoLTEpLmpvaW4oIi8iKSxkPS8oXGQrKS9nLGc9W10sXyx5LHgsYixTLEM9MDtmb3IoO189ZC5leGVjKGYpOykrK0MseT1mLnNsaWNlKDAsXy5pbmRleCkseD1fWzBdLGI9Zi5zbGljZShfLmluZGV4K19bMF0ubGVuZ3RoKSxTPXQzKHksYixwKSxhW1NdPWFbU10sYVtTXXx8KGFbU109UVMoeSxiLHAsK3gsYyxyKSksYVtTXS5pZHMucHVzaCh4KSxzW2NdPXNbY118fFtdLHNbY10ucHVzaChbUyx4XSk7QzwxJiYoeT11P2Yuc3Vic3RyKDAsZi5sZW5ndGgtMSk6Zix4PTAsYj11PyIqIjoiIixTPXQzKHksYixwKSxhW1NdPWFbU10sYVtTXXx8KGFbU109UVMoeSxiLHAsK3gsYyxyKSksYVtTXS5pZHMucHVzaCh4KSxzW2NdPXNbY118fFtdLHNbY10ucHVzaChbUyx4XSkpfSk7dmFyIGw9e307SGUuZWFjaChzLGZ1bmN0aW9uKGMsdSl7Yy5zb3J0KGZ1bmN0aW9uKHkseCl7cmV0dXJuIGFbeFswXV0uaWRzLmxlbmd0aC1hW3lbMF1dLmlkcy5sZW5ndGh9KTt2YXIgaD1jWzBdWzBdLGY9Y1swXVsxXTtsW2hdPWxbaF18fFtdO2xldCBwPXUuc3BsaXQoIi8iKSxkPXBbcC5sZW5ndGgtMV0sZz1wLnNsaWNlKDAscC5sZW5ndGgtMSkuam9pbigiLyIpO3ZhciBfPVFTKGFbaF0ucHJlZml4LGFbaF0uc3VmZml4LGcsK2YsdSxyKTtsW2hdLnB1c2goXyl9KSxIZS5lYWNoKGwsZnVuY3Rpb24oYyx1KXtpZihjLmxlbmd0aDwyKXJldHVybjtjLnNvcnQoZnVuY3Rpb24oZixwKXtyZXR1cm4rZi5jbHVzdGVySWQtK3AuY2x1c3RlcklkfSk7bGV0IGg9W2NbMF1dO2ZvcihsZXQgZj0xO2Y8Yy5sZW5ndGg7ZisrKXtsZXQgcD1jW2ZdO2lmKHAuY2x1c3RlcklkPT09aFtoLmxlbmd0aC0xXS5jbHVzdGVySWQrMSl7aC5wdXNoKHApO2NvbnRpbnVlfXZIKGgsbiwrbyx0LHIpLGg9W3BdfXZIKGgsbiwrbyx0LHIpfSl9KSxufWZ1bmN0aW9uIHZIKGUsdCxyLG4saSl7aWYoZS5sZW5ndGg+MSl7bGV0IG89dDMoZVswXS5wcmVmaXgsZVswXS5zdWZmaXgsZVswXS5wYXJlbnQsZVswXS5jbHVzdGVySWQsZVtlLmxlbmd0aC0xXS5jbHVzdGVySWQpLGE9UVMoZVswXS5wcmVmaXgsZVswXS5zdWZmaXgsZVswXS5wYXJlbnQscixvLGkpO0hlLmVhY2goZSxmdW5jdGlvbihzKXthLmlkcy5wdXNoKHMuY2x1c3RlcklkKSxhLm1ldGFncmFwaC5zZXROb2RlKHMubmFtZSxuLm5vZGUocy5uYW1lKSl9KSx0W29dPWF9fXZhciBtZT1FZShPZSgpLDEpO3ZhciB5MD17REVGQVVMVF9GSUxMOiIjZmZmZmZmIixERUZBVUxUX1NUUk9LRToiI2IyYjJiMiIsQ09NUEFUSUJMRToiIzBmOWQ1OCIsSU5DT01QQVRJQkxFOiIjZGI0NDM3In0sS3U9e0RFRkFVTFRfRklMTDoiI2Q5ZDlkOSIsREVGQVVMVF9TVFJPS0U6IiNhNmE2YTYiLFNBVFVSQVRJT046LjYsTElHSFRORVNTOi44NSxFWFBBTkRFRF9DT0xPUjoiI2YwZjBmMCIsSFVFUzpbMjIwLDEwMCwxODAsNDAsMjAsMzQwLDI2MCwzMDAsMTQwLDYwXSxTVFJVQ1RVUkVfUEFMRVRURShlLHQpe2xldCByPUt1LkhVRVMsbj1yLmxlbmd0aCxpPXJbZSVuXSxvPU1hdGguc2luKGkqTWF0aC5QSS8zNjApLGE9dD8zMDo5MC02MCpvLHM9dD85NTo4MDtyZXR1cm4gVm0oaSwuMDEqYSwuMDEqcykudG9TdHJpbmcoKX0sREVWSUNFX1BBTEVUVEUoZSl7cmV0dXJuIEt1LlNUUlVDVFVSRV9QQUxFVFRFKGUpfSxYTEFfQ0xVU1RFUl9QQUxFVFRFKGUpe3JldHVybiBLdS5TVFJVQ1RVUkVfUEFMRVRURShlKX0sVU5LTk9XTjoiI2VlZSIsR1JBRElFTlRfT1VUTElORToiIzg4OCJ9LFVsdD17REVGQVVMVF9GSUxMOiJ3aGl0ZSIsREVGQVVMVF9TVFJPS0U6IiNiMmIyYjIifSxYbz17bWluTm9kZUNvdW50Rm9yRXh0cmFjdGlvbjoxNSxtaW5EZWdyZWVGb3JFeHRyYWN0aW9uOjUsbWF4Q29udHJvbERlZ3JlZTo0LG1heEJyaWRnZVBhdGhEZWdyZWU6NCxvdXRFeHRyYWN0VHlwZXM6WyJOb09wIl0saW5FeHRyYWN0VHlwZXM6W10sZGV0YWNoQWxsRWRnZXNGb3JIaWdoRGVncmVlOiEwLGV4dHJhY3RJc29sYXRlZE5vZGVzV2l0aEFubm90YXRpb25zT25PbmVTaWRlOiEwLGVuYWJsZUJyaWRnZWdyYXBoOiEwLG1pbk1heENvbG9yczpbIiNmZmY1ZjAiLCIjZmI2YTRhIl0sbWF4QW5ub3RhdGlvbnM6NX0sX2NyPW5ldyBSZWdFeHAoIl4oPzoiK1NhKyIpPyhcXHcrKV9bYS16MC05XXs4fSg/Ol9cXGQrKT8kIiksbG89Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4pe3RoaXMuaGllcmFyY2h5PXQsdGhpcy5kaXNwbGF5aW5nU3RhdHM9cix0aGlzLmF1dG9FeHRyYWN0Tm9kZXM9bix0aGlzLmluZGV4PXt9LHRoaXMucmVuZGVyZWRPcE5hbWVzPVtdLHRoaXMuY29tcHV0ZVNjYWxlcygpLHRoaXMuaGFzU3ViaGllcmFyY2h5PXt9LHRoaXMucm9vdD1uZXcgd0godC5yb290LHQuZ3JhcGhPcHRpb25zKSx0aGlzLmluZGV4W3Qucm9vdC5uYW1lXT10aGlzLnJvb3QsdGhpcy5yZW5kZXJlZE9wTmFtZXMucHVzaCh0LnJvb3QubmFtZSksdGhpcy5idWlsZFN1YmhpZXJhcmNoeSh0LnJvb3QubmFtZSksdGhpcy5yb290LmV4cGFuZGVkPSEwLHRoaXMudHJhY2VJbnB1dHM9ITF9Y29tcHV0ZVNjYWxlcygpe3RoaXMuZGV2aWNlQ29sb3JNYXA9Z3UoKS5kb21haW4odGhpcy5oaWVyYXJjaHkuZGV2aWNlcykucmFuZ2UobWUubWFwKElyKHRoaXMuaGllcmFyY2h5LmRldmljZXMubGVuZ3RoKSxLdS5ERVZJQ0VfUEFMRVRURSkpLHRoaXMueGxhQ2x1c3RlckNvbG9yTWFwPWd1KCkuZG9tYWluKHRoaXMuaGllcmFyY2h5LnhsYUNsdXN0ZXJzKS5yYW5nZShtZS5tYXAoSXIodGhpcy5oaWVyYXJjaHkueGxhQ2x1c3RlcnMubGVuZ3RoKSxLdS5YTEFfQ0xVU1RFUl9QQUxFVFRFKSk7bGV0IHQ9dGhpcy5oaWVyYXJjaHkucm9vdC5tZXRhZ3JhcGgscj1sdSh0Lm5vZGVzKCksKGksbyk9PntsZXQgYT10Lm5vZGUoaSk7aWYoYS5zdGF0cyE9bnVsbClyZXR1cm4gYS5zdGF0cy50b3RhbEJ5dGVzfSk7dGhpcy5tZW1vcnlVc2FnZVNjYWxlPXpuKCkuZG9tYWluKFswLHJdKS5yYW5nZShYby5taW5NYXhDb2xvcnMpO2xldCBuPWx1KHQubm9kZXMoKSwoaSxvKT0+e2xldCBhPXQubm9kZShpKTtpZihhLnN0YXRzIT1udWxsKXJldHVybiBhLnN0YXRzLmdldFRvdGFsTWljcm9zKCl9KTt0aGlzLmNvbXB1dGVUaW1lU2NhbGU9em4oKS5kb21haW4oWzAsbl0pLnJhbmdlKFhvLm1pbk1heENvbG9ycyksdGhpcy5lZGdlV2lkdGhTaXplZEJhc2VkU2NhbGU9dGhpcy5oaWVyYXJjaHkuaGFzU2hhcGVJbmZvP3FzZTp6bigpLmRvbWFpbihbMSx0aGlzLmhpZXJhcmNoeS5tYXhNZXRhRWRnZVNpemVdKS5yYW5nZShbSjQsUTRdKX1nZXRSZW5kZXJOb2RlQnlOYW1lKHQpe3JldHVybiB0aGlzLmluZGV4W3RdfWdldE5vZGVCeU5hbWUodCl7cmV0dXJuIHRoaXMuaGllcmFyY2h5Lm5vZGUodCl9Y29sb3JIaXN0b2dyYW0odCxyKXtpZihPYmplY3Qua2V5cyh0KS5sZW5ndGg+MCl7bGV0IG49bWUuc3VtKE9iamVjdC5rZXlzKHQpLm1hcChpPT50W2ldKSk7cmV0dXJuIE9iamVjdC5rZXlzKHQpLm1hcChpPT4oe2NvbG9yOnIoaSkscHJvcG9ydGlvbjp0W2ldL259KSl9cmV0dXJuIG51bGx9Z2V0T3JDcmVhdGVSZW5kZXJOb2RlQnlOYW1lKHQpe2lmKCF0KXJldHVybiBudWxsO2lmKHQgaW4gdGhpcy5pbmRleClyZXR1cm4gdGhpcy5pbmRleFt0XTtsZXQgcj10aGlzLmhpZXJhcmNoeS5ub2RlKHQpO2lmKCFyKXJldHVybiBudWxsO2xldCBuPXIuaXNHcm91cE5vZGU/bmV3IHdIKHIsdGhpcy5oaWVyYXJjaHkuZ3JhcGhPcHRpb25zKTpuZXcgR2Yocik7dGhpcy5pbmRleFt0XT1uLHRoaXMucmVuZGVyZWRPcE5hbWVzLnB1c2godCksci5zdGF0cyYmKG4ubWVtb3J5Q29sb3I9dGhpcy5tZW1vcnlVc2FnZVNjYWxlKHIuc3RhdHMudG90YWxCeXRlcyksbi5jb21wdXRlVGltZUNvbG9yPXRoaXMuY29tcHV0ZVRpbWVTY2FsZShyLnN0YXRzLmdldFRvdGFsTWljcm9zKCkpKSxuLmlzRmFkZWRPdXQ9dGhpcy5kaXNwbGF5aW5nU3RhdHMmJiFtSChyLnN0YXRzKTt2YXIgaT1udWxsLG89bnVsbCxhPW51bGw7aWYoci5pc0dyb3VwTm9kZSl7aT1yLmRldmljZUhpc3RvZ3JhbSxvPXIueGxhQ2x1c3Rlckhpc3RvZ3JhbTtsZXQgcz1yLmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uY29tcGF0aWJsZSxsPXIuY29tcGF0aWJpbGl0eUhpc3RvZ3JhbS5pbmNvbXBhdGlibGU7KHMhPTB8fGwhPTApJiYoYT1zLyhzK2wpKX1lbHNle2xldCBzPW4ubm9kZS5kZXZpY2U7cyYmKGk9e1tzXToxfSk7bGV0IGw9bi5ub2RlLnhsYUNsdXN0ZXI7bCYmKG89e1tsXToxfSksbi5ub2RlLnR5cGU9PT1qdC5PUCYmKGE9bi5ub2RlLmNvbXBhdGlibGU/MTowKX1yZXR1cm4gaSYmKG4uZGV2aWNlQ29sb3JzPXRoaXMuY29sb3JIaXN0b2dyYW0oaSx0aGlzLmRldmljZUNvbG9yTWFwKSksbyYmKG4ueGxhQ2x1c3RlckNvbG9ycz10aGlzLmNvbG9ySGlzdG9ncmFtKG8sdGhpcy54bGFDbHVzdGVyQ29sb3JNYXApKSxhIT1udWxsJiYobi5jb21wYXRpYmlsaXR5Q29sb3JzPVt7Y29sb3I6eTAuQ09NUEFUSUJMRSxwcm9wb3J0aW9uOmF9LHtjb2xvcjp5MC5JTkNPTVBBVElCTEUscHJvcG9ydGlvbjoxLWF9XSksdGhpcy5pbmRleFt0XX1nZXROZWFyZXN0VmlzaWJsZUFuY2VzdG9yKHQpe2xldCByPWxQKHQpLG49MCxpPW51bGwsbz10O2Zvcig7bjxyLmxlbmd0aCYmKG89cltuXSxpPXRoaXMuZ2V0UmVuZGVyTm9kZUJ5TmFtZShvKSwhIWkuZXhwYW5kZWQpO24rKyk7aWYobj09ci5sZW5ndGgtMil7bGV0IGE9cltuKzFdO2lmKGkhPW51bGwmJmkuaW5Bbm5vdGF0aW9ucy5ub2RlTmFtZXNbYV18fGkhPW51bGwmJmkub3V0QW5ub3RhdGlvbnMubm9kZU5hbWVzW2FdKXJldHVybiBhfXJldHVybiBvfXNldERlcHRoKHQpe3BsZSh0aGlzLnJvb3QsK3QpfWlzTm9kZUF1eGlsaWFyeSh0KXtsZXQgcj10aGlzLmdldFJlbmRlck5vZGVCeU5hbWUodC5ub2RlLnBhcmVudE5vZGUubmFtZSksbj1tZS5maW5kKHIuaXNvbGF0ZWRJbkV4dHJhY3QsaT0+aS5ub2RlLm5hbWU9PT10Lm5vZGUubmFtZSk7cmV0dXJuIG4/ITA6KG49bWUuZmluZChyLmlzb2xhdGVkT3V0RXh0cmFjdCxpPT5pLm5vZGUubmFtZT09PXQubm9kZS5uYW1lKSwhIW4pfWdldE5hbWVzT2ZSZW5kZXJlZE9wcygpe3JldHVybiB0aGlzLnJlbmRlcmVkT3BOYW1lc31jbG9uZUFuZEFkZEZ1bmN0aW9uT3BOb2RlKHQscixuLGkpe2xldCBvPW4ubmFtZS5yZXBsYWNlKHIsaSksYT10Lm1ldGFncmFwaC5ub2RlKG8pO2lmKGEpcmV0dXJuIGE7YT1uZXcgXzAoe25hbWU6byxpbnB1dDpbXSxkZXZpY2U6bi5kZXZpY2Usb3A6bi5vcCxhdHRyOm1lLmNsb25lRGVlcChuLmF0dHIpfSksYS5jYXJkaW5hbGl0eT1uLmNhcmRpbmFsaXR5LGEuaW5jbHVkZT1uLmluY2x1ZGUsYS5vdXRwdXRTaGFwZXM9bWUuY2xvbmVEZWVwKG4ub3V0cHV0U2hhcGVzKSxhLnhsYUNsdXN0ZXI9bi54bGFDbHVzdGVyLGEuZnVuY3Rpb25JbnB1dEluZGV4PW4uZnVuY3Rpb25JbnB1dEluZGV4LGEuZnVuY3Rpb25PdXRwdXRJbmRleD1uLmZ1bmN0aW9uT3V0cHV0SW5kZXgsYS5pbnB1dHM9bi5pbnB1dHMubWFwKGw9PntsZXQgYz1tZS5jbG9uZShsKTtyZXR1cm4gYy5uYW1lPWwubmFtZS5yZXBsYWNlKHIsaSksY30pLGEucGFyZW50Tm9kZT10LHQubWV0YWdyYXBoLnNldE5vZGUoYS5uYW1lLGEpLHRoaXMuaGllcmFyY2h5LnNldE5vZGUoYS5uYW1lLGEpO2xldCBzPWw9PnRoaXMuY2xvbmVBbmRBZGRGdW5jdGlvbk9wTm9kZSh0LHIsbCxpKTtyZXR1cm4gYS5pbkVtYmVkZGluZ3M9bi5pbkVtYmVkZGluZ3MubWFwKHMpLGEub3V0RW1iZWRkaW5ncz1uLm91dEVtYmVkZGluZ3MubWFwKHMpLGF9Y2xvbmVGdW5jdGlvbkxpYnJhcnlNZXRhbm9kZSh0LHIsbixpLG8pe2xldCBhPXt9LHM9dGhpcy5jbG9uZUZ1bmN0aW9uTGlicmFyeU1ldGFub2RlSGVscGVyKHQscixuLGksbyxhKTtyZXR1cm4gbWUuaXNFbXB0eShhKXx8dGhpcy5wYXRjaEVkZ2VzRnJvbUZ1bmN0aW9uT3V0cHV0cyhyLGEpLHN9Y2xvbmVGdW5jdGlvbkxpYnJhcnlNZXRhbm9kZUhlbHBlcih0LHIsbixpLG8sYSl7bGV0IHM9c1Aobi5uYW1lLnJlcGxhY2UoaSxvKSk7cmV0dXJuIHMuZGVwdGg9bi5kZXB0aCxzLmNhcmRpbmFsaXR5PW4uY2FyZGluYWxpdHkscy50ZW1wbGF0ZUlkPW4udGVtcGxhdGVJZCxzLm9wSGlzdG9ncmFtPW1lLmNsb25lKG4ub3BIaXN0b2dyYW0pLHMuZGV2aWNlSGlzdG9ncmFtPW1lLmNsb25lKG4uZGV2aWNlSGlzdG9ncmFtKSxzLnhsYUNsdXN0ZXJIaXN0b2dyYW09bWUuY2xvbmUobi54bGFDbHVzdGVySGlzdG9ncmFtKSxzLmhhc05vbkNvbnRyb2xFZGdlcz1uLmhhc05vbkNvbnRyb2xFZGdlcyxzLmluY2x1ZGU9bi5pbmNsdWRlLHMubm9kZUF0dHJpYnV0ZXM9bWUuY2xvbmUobi5ub2RlQXR0cmlidXRlcykscy5hc3NvY2lhdGVkRnVuY3Rpb249bi5hc3NvY2lhdGVkRnVuY3Rpb24sbWUuZWFjaChuLm1ldGFncmFwaC5ub2RlcygpLGw9PntsZXQgYz1uLm1ldGFncmFwaC5ub2RlKGwpO3N3aXRjaChjLnR5cGUpe2Nhc2UganQuTUVUQTpsZXQgdT10aGlzLmNsb25lRnVuY3Rpb25MaWJyYXJ5TWV0YW5vZGVIZWxwZXIodCxyLGMsaSxvLGEpO3UucGFyZW50Tm9kZT1zLHMubWV0YWdyYXBoLnNldE5vZGUodS5uYW1lLHUpLHRoaXMuaGllcmFyY2h5LnNldE5vZGUodS5uYW1lLHUpO2JyZWFrO2Nhc2UganQuT1A6bGV0IGg9dGhpcy5jbG9uZUFuZEFkZEZ1bmN0aW9uT3BOb2RlKHMsaSxjLG8pO21lLmlzTnVtYmVyKGguZnVuY3Rpb25JbnB1dEluZGV4KSYmdGhpcy5wYXRjaEVkZ2VzSW50b0Z1bmN0aW9uSW5wdXRzKHIsaCksbWUuaXNOdW1iZXIoaC5mdW5jdGlvbk91dHB1dEluZGV4KSYmKGFbaC5mdW5jdGlvbk91dHB1dEluZGV4XT1oKTticmVhaztkZWZhdWx0OmNvbnNvbGUud2FybihjLm5hbWUrIiBpcyBvZGRseSBuZWl0aGVyIGEgbWV0YW5vZGUgbm9yIGFuIG9wbm9kZS4iKX19KSx0aGlzLmNsb25lTGlicmFyeU1ldGFub2RlRWRnZXMobixzLGksbyksc31jbG9uZUxpYnJhcnlNZXRhbm9kZUVkZ2VzKHQscixuLGkpe21lLmVhY2godC5tZXRhZ3JhcGguZWRnZXMoKSxvPT57bGV0IGE9dC5tZXRhZ3JhcGguZWRnZShvKSxzPWEudi5yZXBsYWNlKG4saSksbD1hLncucmVwbGFjZShuLGkpLGM9bmV3IHFmKHMsbCk7Yy5pbmJvdW5kPWEuaW5ib3VuZCxjLm51bVJlZ3VsYXJFZGdlcz1hLm51bVJlZ3VsYXJFZGdlcyxjLm51bUNvbnRyb2xFZGdlcz1hLm51bUNvbnRyb2xFZGdlcyxjLm51bVJlZkVkZ2VzPWEubnVtUmVmRWRnZXMsYy50b3RhbFNpemU9YS50b3RhbFNpemUsYS5iYXNlRWRnZUxpc3QmJihjLmJhc2VFZGdlTGlzdD1hLmJhc2VFZGdlTGlzdC5tYXAodT0+e2xldCBoPW1lLmNsb25lKHUpO3JldHVybiBoLnY9dS52LnJlcGxhY2UobixpKSxoLnc9dS53LnJlcGxhY2UobixpKSxofSkpLHIubWV0YWdyYXBoLm5vZGUobCk/ci5tZXRhZ3JhcGguc2V0RWRnZShzLGwsYyk6ci5tZXRhZ3JhcGguc2V0RWRnZShsLHMsYyl9KX1wYXRjaEVkZ2VzSW50b0Z1bmN0aW9uSW5wdXRzKHQscil7bGV0IG49TWF0aC5taW4oci5mdW5jdGlvbklucHV0SW5kZXgsdC5pbnB1dHMubGVuZ3RoLTEpLGk9bWUuY2xvbmUodC5pbnB1dHNbbl0pO2Zvcig7aS5pc0NvbnRyb2xEZXBlbmRlbmN5OyluKyssaT10LmlucHV0c1tuXTtyLmlucHV0cy5wdXNoKGkpO2xldCBvPXRoaXMuaGllcmFyY2h5LmdldFByZWRlY2Vzc29ycyh0Lm5hbWUpLGEscz0wO21lLmVhY2goby5yZWd1bGFyLGw9PntpZihzKz1sLm51bVJlZ3VsYXJFZGdlcyxzPm4pcmV0dXJuIGE9bCwhMX0pLG1lLmVhY2goYS5iYXNlRWRnZUxpc3QsbD0+e2wudz09PXQubmFtZSYmKGwudz1yLm5hbWUpLGwudj09PXQubmFtZSYmKGwudj1yLm5hbWUpfSl9cGF0Y2hFZGdlc0Zyb21GdW5jdGlvbk91dHB1dHModCxyKXtsZXQgbj10aGlzLmhpZXJhcmNoeS5nZXRTdWNjZXNzb3JzKHQubmFtZSk7bWUuZWFjaChuLnJlZ3VsYXIsaT0+e21lLmVhY2goaS5iYXNlRWRnZUxpc3Qsbz0+e2xldCBhPXRoaXMuaGllcmFyY2h5Lm5vZGUoby53KTttZS5lYWNoKGEuaW5wdXRzLHM9PntpZihzLm5hbWU9PT10Lm5hbWUpe2xldCBsPXJbcy5vdXRwdXRUZW5zb3JLZXldO3MubmFtZT1sLm5hbWUscy5vdXRwdXRUZW5zb3JLZXk9by5vdXRwdXRUZW5zb3JLZXl9fSl9KSxtZS5lYWNoKGkuYmFzZUVkZ2VMaXN0LG89PntvLnY9cltvLm91dHB1dFRlbnNvcktleV0ubmFtZSxvLm91dHB1dFRlbnNvcktleT0iMCJ9KX0pfWJ1aWxkU3ViaGllcmFyY2h5KHQpe2lmKHQgaW4gdGhpcy5oYXNTdWJoaWVyYXJjaHkpcmV0dXJuO3RoaXMuaGFzU3ViaGllcmFyY2h5W3RdPSEwO2xldCByPXRoaXMuaW5kZXhbdF07aWYoci5ub2RlLnR5cGUhPT1qdC5NRVRBJiZyLm5vZGUudHlwZSE9PWp0LlNFUklFUylyZXR1cm47bGV0IG49cixpPW4ubm9kZS5tZXRhZ3JhcGgsbz1uLmNvcmVHcmFwaCxhPVtdLHM9W107bWUuaXNFbXB0eSh0aGlzLmhpZXJhcmNoeS5saWJyYXJ5RnVuY3Rpb25zKXx8KG1lLmVhY2goaS5ub2RlcygpLGQ9PntsZXQgZz1pLm5vZGUoZCksXz10aGlzLmhpZXJhcmNoeS5saWJyYXJ5RnVuY3Rpb25zW2cub3BdO2lmKCFffHxkLmluZGV4T2YoU2EpPT09MClyZXR1cm47bGV0IHk9dGhpcy5jbG9uZUZ1bmN0aW9uTGlicmFyeU1ldGFub2RlKGksZyxfLm5vZGUsXy5ub2RlLm5hbWUsZy5uYW1lKTthLnB1c2goZykscy5wdXNoKHkpfSksbWUuZWFjaChzLChkLGcpPT57bGV0IF89YVtnXTtkLnBhcmVudE5vZGU9Xy5wYXJlbnROb2RlLGkuc2V0Tm9kZShfLm5hbWUsZCksdGhpcy5oaWVyYXJjaHkuc2V0Tm9kZShfLm5hbWUsZCl9KSksbWUuZWFjaChpLm5vZGVzKCksZD0+e2xldCBnPXRoaXMuZ2V0T3JDcmVhdGVSZW5kZXJOb2RlQnlOYW1lKGQpLF89Zy5ub2RlO28uc2V0Tm9kZShkLGcpLF8uaXNHcm91cE5vZGV8fChtZS5lYWNoKF8uaW5FbWJlZGRpbmdzLHk9PntsZXQgeD1uZXcgT2QobnVsbCksYj1uZXcgR2YoeSk7aGxlKGcseSxiLHgsX2kuQ09OU1RBTlQpLHRoaXMuaW5kZXhbeS5uYW1lXT1ifSksbWUuZWFjaChfLm91dEVtYmVkZGluZ3MseT0+e2xldCB4PW5ldyBPZChudWxsKSxiPW5ldyBHZih5KTtmbGUoZyx5LGIseCxfaS5TVU1NQVJZKSx0aGlzLmluZGV4W3kubmFtZV09Yn0pKX0pLG1lLmVhY2goaS5lZGdlcygpLGQ9PntsZXQgZz1pLmVkZ2UoZCksXz1uZXcgT2QoZyk7Xy5pc0ZhZGVkT3V0PXRoaXMuaW5kZXhbZC52XS5pc0ZhZGVkT3V0fHx0aGlzLmluZGV4W2Qud10uaXNGYWRlZE91dCxvLnNldEVkZ2UoZC52LGQudyxfKX0pLG4ubm9kZS50eXBlPT09anQuTUVUQSYmTWNyKG4sdGhpcy5hdXRvRXh0cmFjdE5vZGVzKSxtZS5pc0VtcHR5KHRoaXMuaGllcmFyY2h5LmxpYnJhcnlGdW5jdGlvbnMpfHx0aGlzLmJ1aWxkU3ViaGllcmFyY2hpZXNGb3JOZWVkZWRGdW5jdGlvbnMoaSksdD09PXFjJiZtZS5mb3JPd24odGhpcy5oaWVyYXJjaHkubGlicmFyeUZ1bmN0aW9ucywoZCxnKT0+e2xldCBfPWQubm9kZSx5PXRoaXMuZ2V0T3JDcmVhdGVSZW5kZXJOb2RlQnlOYW1lKF8ubmFtZSk7bi5saWJyYXJ5RnVuY3Rpb25zRXh0cmFjdC5wdXNoKHkpLHkubm9kZS5pbmNsdWRlPXVyLkVYQ0xVREUsby5yZW1vdmVOb2RlKF8ubmFtZSl9KTtsZXQgbD1uLm5vZGUucGFyZW50Tm9kZTtpZighbClyZXR1cm47bGV0IGM9dGhpcy5pbmRleFtsLm5hbWVdLHU9KGQsLi4uZyk9PmcuY29uY2F0KFtkPyJJTiI6Ik9VVCJdKS5qb2luKCJ+fiIpLGg9dGhpcy5oaWVyYXJjaHkuZ2V0QnJpZGdlZ3JhcGgodCksZj17aW46e30sb3V0Ont9LGNvbnRyb2w6e319O21lLmVhY2goaC5lZGdlcygpLGQ9PntsZXQgZz0hIWkubm9kZShkLncpLF89Zz9kLnY6ZC53O2guZWRnZShkKS5udW1SZWd1bGFyRWRnZXM/Zz9mLm91dFtfXT0oZi5vdXRbX118fDApKzE6Zi5pbltfXT0oZi5pbltfXXx8MCkrMTpmLmNvbnRyb2xbX109KGYuY29udHJvbFtfXXx8MCkrMX0pO2xldCBwPXRoaXMuaGllcmFyY2h5LmdldE5vZGVNYXAoKTttZS5lYWNoKGguZWRnZXMoKSxkPT57bGV0IGc9aC5lZGdlKGQpLF89ISFpLm5vZGUoZC53KSxbeSx4XT1fP1tkLncsZC52XTpbZC52LGQud10sYj10aGlzLmluZGV4W3ldLFM9dGhpcy5pbmRleFt4XSxDPVM/Uy5ub2RlOnBbeF0sUD0hZy5udW1SZWd1bGFyRWRnZXMmJmYuY29udHJvbFt4XT5Yby5tYXhDb250cm9sRGVncmVlLFssa109Xz9bci5pbkFubm90YXRpb25zLGIuaW5Bbm5vdGF0aW9uc106W3Iub3V0QW5ub3RhdGlvbnMsYi5vdXRBbm5vdGF0aW9uc10sRD0oXz9mLm91dDpmLmluKVt4XT5Yby5tYXhCcmlkZ2VQYXRoRGVncmVlLEI9bnVsbCxJPSExO2lmKFhvLmVuYWJsZUJyaWRnZWdyYXBoJiYhRCYmIVAmJmIuaXNJbkNvcmUoKSl7bGV0IFc9Wj0+e2xldCBydD1fP3t2Olosdzp0fTp7djp0LHc6Wn07cmV0dXJuIGMuY29yZUdyYXBoLmVkZ2UocnQpfTtCPVcoeCksQnx8KEI9Vyh1KF8seCxsLm5hbWUpKSksST0hIUJ9bGV0IEw9ITE7aWYoQiYmIWcubnVtUmVndWxhckVkZ2VzKXtsZXQgVz1CLFo9Yy5ub2RlO2Zvcig7Vy5hZGpvaW5pbmdNZXRhZWRnZTspVz1XLmFkam9pbmluZ01ldGFlZGdlLFo9Wi5wYXJlbnROb2RlO2xldCBydD10aGlzLmhpZXJhcmNoeS5nZXRUb3BvbG9naWNhbE9yZGVyaW5nKFoubmFtZSksb3Q9Vy5tZXRhZWRnZTtMPXJ0W290LnZdPnJ0W290LnddfWlmKEk9SSYmIUwsIUkpe2sucHVzaChuZXcgaTMoQyxTLG5ldyBPZChnKSxfaS5TSE9SVENVVCxfKSk7cmV0dXJufWxldCBSPXUoXyx0KSxGPXUoXyx4LHQpLHo9by5ub2RlKEYpO2lmKCF6KXtsZXQgVz1vLm5vZGUoUik7aWYoIVcpe2xldCBydD17bmFtZTpSLHR5cGU6anQuQlJJREdFLGlzR3JvdXBOb2RlOiExLGNhcmRpbmFsaXR5OjAscGFyZW50Tm9kZTpudWxsLHN0YXRzOm51bGwsaW5jbHVkZTp1ci5VTlNQRUNJRklFRCxpbmJvdW5kOl8sbm9kZUF0dHJpYnV0ZXM6e319O1c9bmV3IEdmKHJ0KSx0aGlzLmluZGV4W1JdPVcsby5zZXROb2RlKFIsVyl9bGV0IFo9e25hbWU6Rix0eXBlOmp0LkJSSURHRSxpc0dyb3VwTm9kZTohMSxjYXJkaW5hbGl0eToxLHBhcmVudE5vZGU6bnVsbCxzdGF0czpudWxsLGluY2x1ZGU6dXIuVU5TUEVDSUZJRUQsaW5ib3VuZDpfLG5vZGVBdHRyaWJ1dGVzOnt9fTt6PW5ldyBHZihaKSx0aGlzLmluZGV4W0ZdPXosby5zZXROb2RlKEYseiksby5zZXRQYXJlbnQoRixSKSxXLm5vZGUuY2FyZGluYWxpdHkrK31sZXQgVT1uZXcgT2QoZyk7VS5hZGpvaW5pbmdNZXRhZWRnZT1CLF8/by5zZXRFZGdlKEYseSxVKTpvLnNldEVkZ2UoeSxGLFUpfSksbWUuZWFjaChbITAsITFdLGQ9PntsZXQgZz11KGQsdCksXz1vLm5vZGUoZyk7IV98fG1lLmVhY2goby5ub2RlcygpLHk9Pnt2YXIgayxPO2lmKG8ubm9kZSh5KS5ub2RlLnR5cGU9PT1qdC5CUklER0V8fCEoZD8hKChrPW8ucHJlZGVjZXNzb3JzKHkpKSE9bnVsbCYmay5sZW5ndGgpOiEoKE89by5zdWNjZXNzb3JzKHkpKSE9bnVsbCYmTy5sZW5ndGgpKSlyZXR1cm47bGV0IFM9dShkLHQsIlNUUlVDVFVSQUxfVEFSR0VUIiksQz1vLm5vZGUoUyk7aWYoIUMpe2xldCBEPXtuYW1lOlMsdHlwZTpqdC5CUklER0UsaXNHcm91cE5vZGU6ITEsY2FyZGluYWxpdHk6MSxwYXJlbnROb2RlOm51bGwsc3RhdHM6bnVsbCxpbmNsdWRlOnVyLlVOU1BFQ0lGSUVELGluYm91bmQ6ZCxub2RlQXR0cmlidXRlczp7fX07Qz1uZXcgR2YoRCksQy5zdHJ1Y3R1cmFsPSEwLHRoaXMuaW5kZXhbU109QyxvLnNldE5vZGUoUyxDKSxfLm5vZGUuY2FyZGluYWxpdHkrKyxvLnNldFBhcmVudChTLGcpfWxldCBQPW5ldyBPZChudWxsKTtQLnN0cnVjdHVyYWw9ITAsUC53ZWlnaHQtLSxkP28uc2V0RWRnZShTLHksUCk6by5zZXRFZGdlKHksUyxQKX0pfSl9YnVpbGRTdWJoaWVyYXJjaGllc0Zvck5lZWRlZEZ1bmN0aW9ucyh0KXttZS5lYWNoKHQuZWRnZXMoKSxyPT57bGV0IG49dC5lZGdlKHIpLGk9bmV3IE9kKG4pO21lLmZvckVhY2goaS5tZXRhZWRnZS5iYXNlRWRnZUxpc3Qsbz0+e2xldCBhPW8udi5zcGxpdChBbCk7Zm9yKGxldCBzPWEubGVuZ3RoO3M+PTA7cy0tKXtsZXQgbD1hLnNsaWNlKDAscyksYz10aGlzLmhpZXJhcmNoeS5ub2RlKGwuam9pbihBbCkpO2lmKGMpe2lmKGMudHlwZT09PWp0Lk9QJiZ0aGlzLmhpZXJhcmNoeS5saWJyYXJ5RnVuY3Rpb25zW2Mub3BdKWZvcihsZXQgdT0xO3U8bC5sZW5ndGg7dSsrKXtsZXQgaD1sLnNsaWNlKDAsdSkuam9pbihBbCk7IWh8fHRoaXMuYnVpbGRTdWJoaWVyYXJjaHkoaCl9YnJlYWt9fX0pfSl9fSxpMz1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIsbixpLG8pe3RoaXMubm9kZT10LHRoaXMucmVuZGVyTm9kZUluZm89cix0aGlzLnJlbmRlck1ldGFlZGdlSW5mbz1uLHRoaXMuYW5ub3RhdGlvblR5cGU9aSx0aGlzLmR4PTAsdGhpcy5keT0wLHRoaXMud2lkdGg9MCx0aGlzLmhlaWdodD0wLG4mJm4ubWV0YWVkZ2UmJih0aGlzLnY9bi5tZXRhZWRnZS52LHRoaXMudz1uLm1ldGFlZGdlLncpLHRoaXMuaXNJbj1vLHRoaXMucG9pbnRzPVtdfX0sX2k7KGZ1bmN0aW9uKGUpe2VbZS5TSE9SVENVVD0wXT0iU0hPUlRDVVQiLGVbZS5DT05TVEFOVD0xXT0iQ09OU1RBTlQiLGVbZS5TVU1NQVJZPTJdPSJTVU1NQVJZIixlW2UuRUxMSVBTSVM9M109IkVMTElQU0lTIn0pKF9pfHwoX2k9e30pKTt2YXIgYkg9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmxpc3Q9W10sdGhpcy5ub2RlTmFtZXM9e319cHVzaCh0KXtpZih0Lm5vZGUubmFtZSBpbiB0aGlzLm5vZGVOYW1lcylyZXR1cm47aWYodGhpcy5ub2RlTmFtZXNbdC5ub2RlLm5hbWVdPSEwLHRoaXMubGlzdC5sZW5ndGg8WG8ubWF4QW5ub3RhdGlvbnMpe3RoaXMubGlzdC5wdXNoKHQpO3JldHVybn1sZXQgcj10aGlzLmxpc3RbdGhpcy5saXN0Lmxlbmd0aC0xXTtpZihyLmFubm90YXRpb25UeXBlPT09X2kuRUxMSVBTSVMpe2xldCBpPXIubm9kZTtpLnNldE51bU1vcmVOb2RlcygrK2kubnVtTW9yZU5vZGVzKTtyZXR1cm59bGV0IG49bmV3IGdIKDEpO3RoaXMubGlzdC5wdXNoKG5ldyBpMyhuLG5ldyBHZihuKSxudWxsLF9pLkVMTElQU0lTLHQuaXNJbikpfX0sR2Y9Y2xhc3N7Y29uc3RydWN0b3IodCl7aWYodGhpcy5ub2RlPXQsdGhpcy5leHBhbmRlZD0hMSx0aGlzLmluQW5ub3RhdGlvbnM9bmV3IGJILHRoaXMub3V0QW5ub3RhdGlvbnM9bmV3IGJILHRoaXMueD0wLHRoaXMueT0wLHRoaXMud2lkdGg9MCx0aGlzLmhlaWdodD0wLHRoaXMuaW5ib3hXaWR0aD0wLHRoaXMub3V0Ym94V2lkdGg9MCx0aGlzLmV4Y2x1ZGVkPSExLHRoaXMuc3RydWN0dXJhbD0hMSx0aGlzLmxhYmVsT2Zmc2V0PTAsdGhpcy5yYWRpdXM9MCx0aGlzLmxhYmVsSGVpZ2h0PTAsdGhpcy5wYWRkaW5nVG9wPTAsdGhpcy5wYWRkaW5nTGVmdD0wLHRoaXMucGFkZGluZ1JpZ2h0PTAsdGhpcy5wYWRkaW5nQm90dG9tPTAsdGhpcy5pc0luRXh0cmFjdD0hMSx0aGlzLmlzT3V0RXh0cmFjdD0hMSx0aGlzLmNvcmVCb3g9e3dpZHRoOjAsaGVpZ2h0OjB9LHRoaXMuaXNGYWRlZE91dD0hMSx0aGlzLmRpc3BsYXlOYW1lPXQubmFtZS5zdWJzdHJpbmcodC5uYW1lLmxhc3RJbmRleE9mKEFsKSsxKSx0LnR5cGU9PT1qdC5NRVRBJiZ0LmFzc29jaWF0ZWRGdW5jdGlvbil7bGV0IHI9dGhpcy5kaXNwbGF5TmFtZS5tYXRjaChfY3IpO3I/dGhpcy5kaXNwbGF5TmFtZT1yWzFdOm1lLnN0YXJ0c1dpdGgodGhpcy5kaXNwbGF5TmFtZSxTYSkmJih0aGlzLmRpc3BsYXlOYW1lPXRoaXMuZGlzcGxheU5hbWUuc3Vic3RyaW5nKFNhLmxlbmd0aCkpfX1pc0luQ29yZSgpe3JldHVybiF0aGlzLmlzSW5FeHRyYWN0JiYhdGhpcy5pc091dEV4dHJhY3QmJiF0aGlzLmlzTGlicmFyeUZ1bmN0aW9ufX0sT2Q9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5tZXRhZWRnZT10LHRoaXMuYWRqb2luaW5nTWV0YWVkZ2U9bnVsbCx0aGlzLnN0cnVjdHVyYWw9ITEsdGhpcy53ZWlnaHQ9MSx0aGlzLmlzRmFkZWRPdXQ9ITF9fTtmdW5jdGlvbiBobGUoZSx0LHIsbixpKXtsZXQgbz1uZXcgaTModCxyLG4saSwhMCk7ZS5pbkFubm90YXRpb25zLnB1c2gobyl9ZnVuY3Rpb24gZmxlKGUsdCxyLG4saSl7bGV0IG89bmV3IGkzKHQscixuLGksITEpO2Uub3V0QW5ub3RhdGlvbnMucHVzaChvKX1mdW5jdGlvbiB5Y3IoZSx0KXttZS5lYWNoKGUubm9kZXMoKSxyPT57bGV0IG49ZS5ub2RlKHIpO2lmKG4uZXhwYW5kZWQ9dD4xLHQ+MClzd2l0Y2gobi5ub2RlLnR5cGUpe2Nhc2UganQuTUVUQTpjYXNlIGp0LlNFUklFUzpwbGUobix0LTEpO2JyZWFrfX0pfXZhciB3SD1jbGFzcyBleHRlbmRzIEdme2NvbnN0cnVjdG9yKHQscil7c3VwZXIodCk7bGV0IGk9dC5tZXRhZ3JhcGguZ3JhcGgoKTt0aGlzLmNvcmVHcmFwaD1lMyhpLm5hbWUsZzAuQ09SRSxyKSx0aGlzLmluRXh0cmFjdEJveD17d2lkdGg6MCxoZWlnaHQ6MH0sdGhpcy5vdXRFeHRyYWN0Qm94PXt3aWR0aDowLGhlaWdodDowfSx0aGlzLmxpYnJhcnlGdW5jdGlvbnNCb3g9e3dpZHRoOjAsaGVpZ2h0OjB9LHRoaXMuaXNvbGF0ZWRJbkV4dHJhY3Q9W10sdGhpcy5pc29sYXRlZE91dEV4dHJhY3Q9W10sdGhpcy5saWJyYXJ5RnVuY3Rpb25zRXh0cmFjdD1bXX19O2Z1bmN0aW9uIHBsZShlLHQpe2UuY29yZUdyYXBoJiZ5Y3IoZS5jb3JlR3JhcGgsdCl9ZnVuY3Rpb24gY1AoZSx0LHIpe2xldCBuPWUubm9kZSh0KSxpPWUubm9kZShyKSxvPWUuZWRnZSh0LHIpOyhuLm5vZGUuaW5jbHVkZT09PXVyLklOQ0xVREV8fGkubm9kZS5pbmNsdWRlPT09dXIuSU5DTFVERSkmJm4ubm9kZS5pbmNsdWRlIT09dXIuRVhDTFVERSYmaS5ub2RlLmluY2x1ZGUhPT11ci5FWENMVURFfHwoZmxlKG4saS5ub2RlLGksbyxfaS5TSE9SVENVVCksaGxlKGksbi5ub2RlLG4sbyxfaS5TSE9SVENVVCksZS5yZW1vdmVFZGdlKHQscikpfWZ1bmN0aW9uIHFsdChlLHQscil7dmFyIG87bGV0IG49ZS5jb3JlR3JhcGgsaT1uLm5vZGUodCk7aS5pc091dEV4dHJhY3Q9ITAsbWUuZWFjaChuLnByZWRlY2Vzc29ycyh0KSwoYSxzKT0+e2NQKG4sYSx0KX0pLChYby5kZXRhY2hBbGxFZGdlc0ZvckhpZ2hEZWdyZWV8fHIpJiZtZS5lYWNoKG4uc3VjY2Vzc29ycyh0KSwoYSxzKT0+e2NQKG4sdCxhKX0pLCgobz1uLm5laWdoYm9ycyh0KSk9PW51bGw/dm9pZCAwOm8ubGVuZ3RoKT09PTAmJihpLm5vZGUuaW5jbHVkZT11ci5FWENMVURFLGUuaXNvbGF0ZWRPdXRFeHRyYWN0LnB1c2goaSksbi5yZW1vdmVOb2RlKHQpKX1mdW5jdGlvbiBHbHQoZSx0LHIpe3ZhciBvO2xldCBuPWUuY29yZUdyYXBoLGk9bi5ub2RlKHQpO2kuaXNJbkV4dHJhY3Q9ITAsbWUuZWFjaChuLnN1Y2Nlc3NvcnModCksKGEscyk9PntjUChuLHQsYSl9KSwoWG8uZGV0YWNoQWxsRWRnZXNGb3JIaWdoRGVncmVlfHxyKSYmbWUuZWFjaChuLnByZWRlY2Vzc29ycyh0KSwoYSxzKT0+e2NQKG4sYSx0KX0pLCgobz1uLm5laWdoYm9ycyh0KSk9PW51bGw/dm9pZCAwOm8ubGVuZ3RoKT09PTAmJihpLm5vZGUuaW5jbHVkZT11ci5FWENMVURFLGUuaXNvbGF0ZWRJbkV4dHJhY3QucHVzaChpKSxuLnJlbW92ZU5vZGUodCkpfWZ1bmN0aW9uIGRsZShlLHQpe2lmKGUudHlwZT09PWp0Lk9QKXtmb3IobGV0IHI9MDtyPHQubGVuZ3RoO3IrKylpZihlLm9wPT09dFtyXSlyZXR1cm4hMH1lbHNlIGlmKGUudHlwZT09PWp0Lk1FVEEpe2xldCByPWUuZ2V0Um9vdE9wKCk7aWYocil7Zm9yKGxldCBuPTA7bjx0Lmxlbmd0aDtuKyspaWYoci5vcD09PXRbbl0pcmV0dXJuITB9fXJldHVybiExfWZ1bmN0aW9uIHZjcihlKXtsZXQgdD1lLmNvcmVHcmFwaDttZS5lYWNoKHQubm9kZXMoKSxyPT57dmFyIGksbzt0Lm5vZGUocikubm9kZS5pbmNsdWRlPT09dXIuRVhDTFVERSYmIXIuc3RhcnRzV2l0aChTYSkmJigoKGk9ZS5jb3JlR3JhcGgub3V0RWRnZXMocikpPT1udWxsP3ZvaWQgMDppLmxlbmd0aCk+KChvPWUuY29yZUdyYXBoLmluRWRnZXMocikpPT1udWxsP3ZvaWQgMDpvLmxlbmd0aCk/cWx0KGUsciwhMCk6R2x0KGUsciwhMCkpfSl9ZnVuY3Rpb24geGNyKGUpe2xldCB0PWUuY29yZUdyYXBoO21lLmVhY2godC5ub2RlcygpLHI9PntsZXQgbj10Lm5vZGUocik7bi5ub2RlLmluY2x1ZGU9PT11ci5VTlNQRUNJRklFRCYmZGxlKG4ubm9kZSxYby5vdXRFeHRyYWN0VHlwZXMpJiZxbHQoZSxyKX0pfWZ1bmN0aW9uIGJjcihlKXtsZXQgdD1lLmNvcmVHcmFwaDttZS5lYWNoKHQubm9kZXMoKSxyPT57bGV0IG49dC5ub2RlKHIpO24ubm9kZS5pbmNsdWRlPT09dXIuVU5TUEVDSUZJRUQmJmRsZShuLm5vZGUsWG8uaW5FeHRyYWN0VHlwZXMpJiZHbHQoZSxyKX0pfWZ1bmN0aW9uIHdjcihlKXtsZXQgdD1lLmNvcmVHcmFwaCxyPXt9LG49e30saT0wO2lmKG1lLmVhY2godC5ub2RlcygpLF89Pnt2YXIgYixTLEMsUDtpZih0Lm5vZGUoXykubm9kZS5pbmNsdWRlIT09dXIuVU5TUEVDSUZJRUQpcmV0dXJuO2xldCB5PW1lLnJlZHVjZSh0LnByZWRlY2Vzc29ycyhfKSwoayxPKT0+e2xldCBEPXQuZWRnZShPLF8pLm1ldGFlZGdlO3JldHVybiBrKyhELm51bVJlZ3VsYXJFZGdlcz8xOjApfSwwKTt5PT09MCYmKChiPXQucHJlZGVjZXNzb3JzKF8pKT09bnVsbD92b2lkIDA6Yi5sZW5ndGgpPjAmJih5PShTPXQucHJlZGVjZXNzb3JzKF8pKT09bnVsbD92b2lkIDA6Uy5sZW5ndGgpO2xldCB4PW1lLnJlZHVjZSh0LnN1Y2Nlc3NvcnMoXyksKGssTyk9PntsZXQgRD10LmVkZ2UoXyxPKS5tZXRhZWRnZTtyZXR1cm4gaysoRC5udW1SZWd1bGFyRWRnZXM/MTowKX0sMCk7eD09PTAmJigoQz10LnN1Y2Nlc3NvcnMoXykpPT1udWxsP3ZvaWQgMDpDLmxlbmd0aCk+MCYmKHg9KFA9dC5zdWNjZXNzb3JzKF8pKT09bnVsbD92b2lkIDA6UC5sZW5ndGgpLHJbX109eSxuW19dPXgsaSsrfSksaTxYby5taW5Ob2RlQ291bnRGb3JFeHRyYWN0aW9uKXJldHVybjtsZXQgbz1Yby5taW5EZWdyZWVGb3JFeHRyYWN0aW9uLTEsYT1NYXRoLnJvdW5kKGkqLjc1KSxzPU1hdGgucm91bmQoaSouMjUpLGw9T2JqZWN0LmtleXMocikuc29ydCgoXyx5KT0+cltfXS1yW3ldKSxjPXJbbFthXV0sdT1yW2xbc11dLGg9YytjLXU7aD1NYXRoLm1heChoLG8pO2ZvcihsZXQgXz1pLTE7cltsW19dXT5oO18tLSlHbHQoZSxsW19dKTtsZXQgZj1PYmplY3Qua2V5cyhuKS5zb3J0KChfLHkpPT5uW19dLW5beV0pLHA9bltmW2FdXSxkPW5bZltzXV0sZz1wKyhwLWQpKjQ7Zz1NYXRoLm1heChnLG8pO2ZvcihsZXQgXz1pLTE7bltmW19dXT5nO18tLSl7bGV0IHk9dC5ub2RlKGZbX10pOyF5fHx5LmlzSW5FeHRyYWN0fHxxbHQoZSxmW19dKX19ZnVuY3Rpb24gU2NyKGUpe2xldCB0PWUuY29yZUdyYXBoLHI9e307bWUuZWFjaCh0LmVkZ2VzKCksbj0+e3QuZWRnZShuKS5tZXRhZWRnZS5udW1SZWd1bGFyRWRnZXN8fCgocltuLnZdPXJbbi52XXx8W10pLnB1c2gobiksKHJbbi53XT1yW24ud118fFtdKS5wdXNoKG4pKX0pLG1lLmVhY2gociwobixpKT0+e24ubGVuZ3RoPlhvLm1heENvbnRyb2xEZWdyZWUmJm1lLmVhY2gobixvPT5jUCh0LG8udixvLncpKX0pfWZ1bmN0aW9uIE1jcihlLHQpe3ZjcihlKSxYby5vdXRFeHRyYWN0VHlwZXMubGVuZ3RoJiZ4Y3IoZSksWG8uaW5FeHRyYWN0VHlwZXMubGVuZ3RoJiZiY3IoZSksdCYmd2NyKGUpLFhvLm1heENvbnRyb2xEZWdyZWUmJlNjcihlKTtsZXQgcj1lLmNvcmVHcmFwaDttZS5lYWNoKHIubm9kZXMoKSxuPT57dmFyIGE7bGV0IGk9ci5ub2RlKG4pLG89KGE9ci5uZWlnaGJvcnMobikpPT1udWxsP3ZvaWQgMDphLmxlbmd0aDtpZihpLm5vZGUuaW5jbHVkZT09PXVyLlVOU1BFQ0lGSUVEJiZvPT09MCl7bGV0IHM9aS5vdXRBbm5vdGF0aW9ucy5saXN0Lmxlbmd0aD4wLGw9aS5pbkFubm90YXRpb25zLmxpc3QubGVuZ3RoPjA7aS5pc0luRXh0cmFjdD8oZS5pc29sYXRlZEluRXh0cmFjdC5wdXNoKGkpLGkubm9kZS5pbmNsdWRlPXVyLkVYQ0xVREUsci5yZW1vdmVOb2RlKG4pKTppLmlzT3V0RXh0cmFjdD8oZS5pc29sYXRlZE91dEV4dHJhY3QucHVzaChpKSxpLm5vZGUuaW5jbHVkZT11ci5FWENMVURFLHIucmVtb3ZlTm9kZShuKSk6WG8uZXh0cmFjdElzb2xhdGVkTm9kZXNXaXRoQW5ub3RhdGlvbnNPbk9uZVNpZGUmJihzJiYhbD8oaS5pc0luRXh0cmFjdD0hMCxlLmlzb2xhdGVkSW5FeHRyYWN0LnB1c2goaSksaS5ub2RlLmluY2x1ZGU9dXIuRVhDTFVERSxyLnJlbW92ZU5vZGUobikpOmwmJiFzJiYoaS5pc091dEV4dHJhY3Q9ITAsZS5pc29sYXRlZE91dEV4dHJhY3QucHVzaChpKSxpLm5vZGUuaW5jbHVkZT11ci5FWENMVURFLHIucmVtb3ZlTm9kZShuKSkpfX0pfWZ1bmN0aW9uIG1sZShlLHQscil7bGV0IG49ci5zcGxpdCgiLyIpLGk9bltuLmxlbmd0aC0xXS5tYXRjaCgvKC4qKTpcdysvKTsoaT09bnVsbD92b2lkIDA6aS5sZW5ndGgpPT09MiYmKG5bbi5sZW5ndGgtMV09aT09bnVsbD92b2lkIDA6aVsxXSk7bGV0IG89blswXSxhPXQuZ2V0UmVuZGVyTm9kZUJ5TmFtZShvKTtmb3IobGV0IHM9MTtzPG4ubGVuZ3RoJiZhLm5vZGUudHlwZSE9PWp0Lk9QO3MrKyl0LmJ1aWxkU3ViaGllcmFyY2h5KG8pLGEuZXhwYW5kZWQ9ITAsZS5zZXROb2RlRXhwYW5kZWQoYSksbys9Ii8iK25bc10sYT10LmdldFJlbmRlck5vZGVCeU5hbWUobyk7cmV0dXJuIGEubm9kZS5uYW1lfXZhciB2bGU9RWUoemx0KCksMSksWmU9RWUoT2UoKSwxKTt2YXIgVHI9e2FuaW1hdGlvbjp7ZHVyYXRpb246MjUwfSxncmFwaDp7bWV0YTp7bm9kZVNlcDo1LHJhbmtTZXA6MjUsZWRnZVNlcDo1fSxzZXJpZXM6e25vZGVTZXA6NSxyYW5rU2VwOjI1LGVkZ2VTZXA6NX0scGFkZGluZzp7cGFkZGluZ1RvcDo0MCxwYWRkaW5nTGVmdDoyMH19LHN1YnNjZW5lOnttZXRhOntwYWRkaW5nVG9wOjEwLHBhZGRpbmdCb3R0b206MTAscGFkZGluZ0xlZnQ6MTAscGFkZGluZ1JpZ2h0OjEwLGxhYmVsSGVpZ2h0OjIwLGV4dHJhY3RYT2Zmc2V0OjE1LGV4dHJhY3RZT2Zmc2V0OjIwfSxzZXJpZXM6e3BhZGRpbmdUb3A6MTAscGFkZGluZ0JvdHRvbToxMCxwYWRkaW5nTGVmdDoxMCxwYWRkaW5nUmlnaHQ6MTAsbGFiZWxIZWlnaHQ6MTB9fSxub2RlU2l6ZTp7bWV0YTp7cmFkaXVzOjUsd2lkdGg6NjAsbWF4TGFiZWxXaWR0aDo1MixoZWlnaHQ6em4oKS5kb21haW4oWzEsMjAwXSkucmFuZ2UoWzE1LDYwXSkuY2xhbXAoITApLGV4cGFuZEJ1dHRvblJhZGl1czozfSxvcDp7d2lkdGg6MTUsaGVpZ2h0OjYscmFkaXVzOjMsbGFiZWxPZmZzZXQ6LTgsbWF4TGFiZWxXaWR0aDozMH0sc2VyaWVzOntleHBhbmRlZDp7cmFkaXVzOjEwLGxhYmVsT2Zmc2V0OjB9LHZlcnRpY2FsOnt3aWR0aDoxNixoZWlnaHQ6MTMsbGFiZWxPZmZzZXQ6LTEzfSxob3Jpem9udGFsOnt3aWR0aDoyNCxoZWlnaHQ6OCxyYWRpdXM6MTAsbGFiZWxPZmZzZXQ6LTEwfX0sYnJpZGdlOnt3aWR0aDoyMCxoZWlnaHQ6MjAscmFkaXVzOjIsbGFiZWxPZmZzZXQ6MH19LHNob3J0Y3V0U2l6ZTp7b3A6e3dpZHRoOjEwLGhlaWdodDo0fSxtZXRhOnt3aWR0aDoxMixoZWlnaHQ6NCxyYWRpdXM6MX0sc2VyaWVzOnt3aWR0aDoxNCxoZWlnaHQ6NH19LGFubm90YXRpb25zOntpbmJveFdpZHRoOjUwLG91dGJveFdpZHRoOjUwLHhPZmZzZXQ6MTAseU9mZnNldDozLGxhYmVsT2Zmc2V0OjIsbWF4TGFiZWxXaWR0aDo0MH0sY29uc3RhbnQ6e3NpemU6e3dpZHRoOjQsaGVpZ2h0OjR9fSxzZXJpZXM6e21heFN0YWNrQ291bnQ6MyxwYXJhbGxlbFN0YWNrT2Zmc2V0UmF0aW86LjIsdG93ZXJTdGFja09mZnNldFJhdGlvOi41fSxtaW5pbWFwOntzaXplOjE1MH19LG8zPTE0MDtmdW5jdGlvbiBTSChlKXtlLm5vZGUuaXNHcm91cE5vZGUmJlRjcihlKSxlLm5vZGUudHlwZT09PWp0Lk1FVEE/Q2NyKGUpOmUubm9kZS50eXBlPT09anQuU0VSSUVTJiZBY3IoZSl9ZnVuY3Rpb24gRWNyKGUpe2UuaW5ib3hXaWR0aD1lLmluQW5ub3RhdGlvbnMubGlzdC5sZW5ndGg+MD9Uci5hbm5vdGF0aW9ucy5pbmJveFdpZHRoOjAsZS5vdXRib3hXaWR0aD1lLm91dEFubm90YXRpb25zLmxpc3QubGVuZ3RoPjA/VHIuYW5ub3RhdGlvbnMub3V0Ym94V2lkdGg6MCxlLmNvcmVCb3gud2lkdGg9ZS53aWR0aCxlLmNvcmVCb3guaGVpZ2h0PWUuaGVpZ2h0O2xldCB0PWUuZGlzcGxheU5hbWUubGVuZ3RoLHI9MztlLndpZHRoPU1hdGgubWF4KGUuY29yZUJveC53aWR0aCtlLmluYm94V2lkdGgrZS5vdXRib3hXaWR0aCx0KnIpfWZ1bmN0aW9uIFRjcihlKXtsZXQgdD1lLmNvcmVHcmFwaC5ub2RlcygpLm1hcChyPT5lLmNvcmVHcmFwaC5ub2RlKHIpKS5jb25jYXQoZS5pc29sYXRlZEluRXh0cmFjdCxlLmlzb2xhdGVkT3V0RXh0cmFjdCxlLmxpYnJhcnlGdW5jdGlvbnNFeHRyYWN0KTtaZS5lYWNoKHQscj0+e3N3aXRjaChyLm5vZGUudHlwZSl7Y2FzZSBqdC5PUDpaZS5leHRlbmQocixUci5ub2RlU2l6ZS5vcCk7YnJlYWs7Y2FzZSBqdC5CUklER0U6WmUuZXh0ZW5kKHIsVHIubm9kZVNpemUuYnJpZGdlKTticmVhaztjYXNlIGp0Lk1FVEE6ci5leHBhbmRlZD9TSChyKTooWmUuZXh0ZW5kKHIsVHIubm9kZVNpemUubWV0YSksci5oZWlnaHQ9VHIubm9kZVNpemUubWV0YS5oZWlnaHQoci5ub2RlLmNhcmRpbmFsaXR5KSk7YnJlYWs7Y2FzZSBqdC5TRVJJRVM6aWYoci5leHBhbmRlZClaZS5leHRlbmQocixUci5ub2RlU2l6ZS5zZXJpZXMuZXhwYW5kZWQpLFNIKHIpO2Vsc2V7bGV0IGk9ci5ub2RlLmhhc05vbkNvbnRyb2xFZGdlcz9Uci5ub2RlU2l6ZS5zZXJpZXMudmVydGljYWw6VHIubm9kZVNpemUuc2VyaWVzLmhvcml6b250YWw7WmUuZXh0ZW5kKHIsaSl9YnJlYWs7ZGVmYXVsdDp0aHJvdyBFcnJvcigiVW5yZWNvZ25pemVkIG5vZGUgdHlwZTogIityLm5vZGUudHlwZSl9ci5leHBhbmRlZHx8RWNyKHIpLFBjcihyKX0pfWZ1bmN0aW9uIHhsZShlLHQpe1plLmV4dGVuZChlLmdyYXBoKCkse25vZGVzZXA6dC5ub2RlU2VwLHJhbmtzZXA6dC5yYW5rU2VwLGVkZ2VzZXA6dC5lZGdlU2VwfSk7bGV0IHI9W10sbj1bXTtpZihaZS5lYWNoKGUubm9kZXMoKSxsPT57ZS5ub2RlKGwpLm5vZGUudHlwZT09PWp0LkJSSURHRT9yLnB1c2gobCk6bi5wdXNoKGwpfSksIW4ubGVuZ3RoKXJldHVybnt3aWR0aDowLGhlaWdodDowfTt2bGUubGF5b3V0KGUpO2xldCBpPTEvMCxvPTEvMCxhPS0xLzAscz0tMS8wO3JldHVybiBaZS5lYWNoKG4sbD0+e2xldCBjPWUubm9kZShsKSx1PS41KmMud2lkdGgsaD1jLngtdSxmPWMueCt1O2k9aDxpP2g6aSxhPWY+YT9mOmE7bGV0IHA9LjUqYy5oZWlnaHQsZD1jLnktcCxnPWMueStwO289ZDxvP2Q6byxzPWc+cz9nOnN9KSxaZS5lYWNoKGUuZWRnZXMoKSxsPT57bGV0IGM9ZS5lZGdlKGwpO2lmKGMuc3RydWN0dXJhbClyZXR1cm47bGV0IHU9ZS5ub2RlKGMubWV0YWVkZ2UudiksaD1lLm5vZGUoYy5tZXRhZWRnZS53KTtpZihjLnBvaW50cy5sZW5ndGg9PT0zJiZJY3IoYy5wb2ludHMpKXtpZih1IT1udWxsKXtsZXQgZD11LmV4cGFuZGVkP3UueDp2MCh1KTtjLnBvaW50c1swXS54PWR9aWYoaCE9bnVsbCl7bGV0IGQ9aC5leHBhbmRlZD9oLng6djAoaCk7Yy5wb2ludHNbMl0ueD1kfWMucG9pbnRzPVtjLnBvaW50c1swXSxjLnBvaW50c1sxXV19bGV0IGY9Yy5wb2ludHNbYy5wb2ludHMubGVuZ3RoLTJdO2ghPW51bGwmJihjLnBvaW50c1tjLnBvaW50cy5sZW5ndGgtMV09eWxlKGYsaCkpO2xldCBwPWMucG9pbnRzWzFdO3UhPW51bGwmJihjLnBvaW50c1swXT15bGUocCx1KSksWmUuZWFjaChjLnBvaW50cyxkPT57aT1kLng8aT9kLng6aSxhPWQueD5hP2QueDphLG89ZC55PG8/ZC55Om8scz1kLnk+cz9kLnk6c30pfSksWmUuZWFjaChlLm5vZGVzKCksbD0+e2xldCBjPWUubm9kZShsKTtjLngtPWksYy55LT1vfSksWmUuZWFjaChlLmVkZ2VzKCksbD0+e1plLmVhY2goZS5lZGdlKGwpLnBvaW50cyxjPT57Yy54LT1pLGMueS09b30pfSkse3dpZHRoOmEtaSxoZWlnaHQ6cy1vfX1mdW5jdGlvbiBDY3IoZSl7bGV0IHQ9VHIuc3Vic2NlbmUubWV0YTtaZS5leHRlbmQoZSx0KSxaZS5leHRlbmQoZS5jb3JlQm94LHhsZShlLmNvcmVHcmFwaCxUci5ncmFwaC5tZXRhKSk7bGV0IHI9ZS5pc29sYXRlZEluRXh0cmFjdC5sZW5ndGg/WmUubWF4QnkoZS5pc29sYXRlZEluRXh0cmFjdCxjPT5jLndpZHRoKS53aWR0aDpudWxsO2UuaW5FeHRyYWN0Qm94LndpZHRoPXIhPW51bGw/cjowLGUuaW5FeHRyYWN0Qm94LmhlaWdodD1aZS5yZWR1Y2UoZS5pc29sYXRlZEluRXh0cmFjdCwoYyx1LGgpPT57bGV0IGY9aD4wP3QuZXh0cmFjdFlPZmZzZXQ6MDtyZXR1cm4gdS54PTAsdS55PWMrZit1LmhlaWdodC8yLGMrZit1LmhlaWdodH0sMCk7bGV0IG49ZS5pc29sYXRlZE91dEV4dHJhY3QubGVuZ3RoP1plLm1heEJ5KGUuaXNvbGF0ZWRPdXRFeHRyYWN0LGM9PmMud2lkdGgpLndpZHRoOm51bGw7ZS5vdXRFeHRyYWN0Qm94LndpZHRoPW4hPW51bGw/bjowLGUub3V0RXh0cmFjdEJveC5oZWlnaHQ9WmUucmVkdWNlKGUuaXNvbGF0ZWRPdXRFeHRyYWN0LChjLHUsaCk9PntsZXQgZj1oPjA/dC5leHRyYWN0WU9mZnNldDowO3JldHVybiB1Lng9MCx1Lnk9YytmK3UuaGVpZ2h0LzIsYytmK3UuaGVpZ2h0fSwwKTtsZXQgaT1lLmxpYnJhcnlGdW5jdGlvbnNFeHRyYWN0Lmxlbmd0aD9aZS5tYXhCeShlLmxpYnJhcnlGdW5jdGlvbnNFeHRyYWN0LGM9PmMud2lkdGgpLndpZHRoOm51bGw7ZS5saWJyYXJ5RnVuY3Rpb25zQm94LndpZHRoPWkhPW51bGw/aTowLGUubGlicmFyeUZ1bmN0aW9uc0JveC5oZWlnaHQ9WmUucmVkdWNlKGUubGlicmFyeUZ1bmN0aW9uc0V4dHJhY3QsKGMsdSxoKT0+e2xldCBmPWg+MD90LmV4dHJhY3RZT2Zmc2V0OjA7cmV0dXJuIHUueD0wLHUueT1jK2YrdS5oZWlnaHQvMixjK2YrdS5oZWlnaHR9LDApO2xldCBvPTA7ZS5pc29sYXRlZEluRXh0cmFjdC5sZW5ndGg+MCYmbysrLGUuaXNvbGF0ZWRPdXRFeHRyYWN0Lmxlbmd0aD4wJiZvKyssZS5saWJyYXJ5RnVuY3Rpb25zRXh0cmFjdC5sZW5ndGg+MCYmbysrLGUuY29yZUdyYXBoLm5vZGVDb3VudCgpPjAmJm8rKztsZXQgYT1Uci5zdWJzY2VuZS5tZXRhLmV4dHJhY3RYT2Zmc2V0LHM9bzw9MT8wOm8qYSxsPU1hdGgubWF4KG8zLGUuaW5FeHRyYWN0Qm94LndpZHRoK2Uub3V0RXh0cmFjdEJveC53aWR0aCk7ZS5jb3JlQm94LndpZHRoKz1sK3MrZS5saWJyYXJ5RnVuY3Rpb25zQm94LndpZHRoK3MsZS5jb3JlQm94LmhlaWdodD10LmxhYmVsSGVpZ2h0K01hdGgubWF4KGUuaW5FeHRyYWN0Qm94LmhlaWdodCxlLmNvcmVCb3guaGVpZ2h0LGUubGlicmFyeUZ1bmN0aW9uc0JveC5oZWlnaHQsZS5vdXRFeHRyYWN0Qm94LmhlaWdodCksZS53aWR0aD1lLmNvcmVCb3gud2lkdGgrdC5wYWRkaW5nTGVmdCt0LnBhZGRpbmdSaWdodCxlLmhlaWdodD1lLnBhZGRpbmdUb3ArZS5jb3JlQm94LmhlaWdodCtlLnBhZGRpbmdCb3R0b219ZnVuY3Rpb24gQWNyKGUpe2xldCB0PWUuY29yZUdyYXBoLHI9VHIuc3Vic2NlbmUuc2VyaWVzO1plLmV4dGVuZChlLHIpLFplLmV4dGVuZChlLmNvcmVCb3gseGxlKGUuY29yZUdyYXBoLFRyLmdyYXBoLnNlcmllcykpLFplLmVhY2godC5ub2RlcygpLG49Pnt0Lm5vZGUobikuZXhjbHVkZWQ9ITF9KSxlLndpZHRoPWUuY29yZUJveC53aWR0aCtyLnBhZGRpbmdMZWZ0K3IucGFkZGluZ1JpZ2h0LGUuaGVpZ2h0PWUuY29yZUJveC5oZWlnaHQrci5wYWRkaW5nVG9wK3IucGFkZGluZ0JvdHRvbX1mdW5jdGlvbiBQY3IoZSl7aWYoZS5leHBhbmRlZClyZXR1cm47bGV0IHQ9ZS5pbkFubm90YXRpb25zLmxpc3Qscj1lLm91dEFubm90YXRpb25zLmxpc3Q7WmUuZWFjaCh0LHU9PmdsZSh1KSksWmUuZWFjaChyLHU9PmdsZSh1KSk7bGV0IG49VHIuYW5ub3RhdGlvbnMsaT1aZS5yZWR1Y2UodCwodSxoLGYpPT57bGV0IHA9Zj4wP24ueU9mZnNldDowO3JldHVybiBoLmR4PS0oZS5jb3JlQm94LndpZHRoK2gud2lkdGgpLzItbi54T2Zmc2V0LGguZHk9dStwK2guaGVpZ2h0LzIsdStwK2guaGVpZ2h0fSwwKTtaZS5lYWNoKHQsdT0+e3UuZHktPWkvMix1LmxhYmVsT2Zmc2V0PW4ubGFiZWxPZmZzZXR9KTtsZXQgbz1aZS5yZWR1Y2UociwodSxoLGYpPT57bGV0IHA9Zj4wP24ueU9mZnNldDowO3JldHVybiBoLmR4PShlLmNvcmVCb3gud2lkdGgraC53aWR0aCkvMituLnhPZmZzZXQsaC5keT11K3AraC5oZWlnaHQvMix1K3AraC5oZWlnaHR9LDApO1plLmVhY2gocix1PT57dS5keS09by8yLHUubGFiZWxPZmZzZXQ9bi5sYWJlbE9mZnNldH0pO2xldCBhPU1hdGgubWluKGUuaGVpZ2h0LzItZS5yYWRpdXMsaS8yKTthPWE8MD8wOmE7bGV0IHM9em4oKS5kb21haW4oWzAsdC5sZW5ndGgtMV0pLnJhbmdlKFstYSxhXSk7WmUuZWFjaCh0LCh1LGgpPT57dS5wb2ludHM9W3tkeDp1LmR4K3Uud2lkdGgvMixkeTp1LmR5fSx7ZHg6LWUuY29yZUJveC53aWR0aC8yLGR5OnQubGVuZ3RoPjE/cyhoKTowfV19KTtsZXQgbD1NYXRoLm1pbihlLmhlaWdodC8yLWUucmFkaXVzLG8vMik7bD1sPDA/MDpsO2xldCBjPXpuKCkuZG9tYWluKFswLHIubGVuZ3RoLTFdKS5yYW5nZShbLWwsbF0pO1plLmVhY2gociwodSxoKT0+e3UucG9pbnRzPVt7ZHg6ZS5jb3JlQm94LndpZHRoLzIsZHk6ci5sZW5ndGg+MT9jKGgpOjB9LHtkeDp1LmR4LXUud2lkdGgvMixkeTp1LmR5fV19KSxlLmhlaWdodD1NYXRoLm1heChlLmhlaWdodCxpLG8pfWZ1bmN0aW9uIGdsZShlKXtzd2l0Y2goZS5hbm5vdGF0aW9uVHlwZSl7Y2FzZSBfaS5DT05TVEFOVDpaZS5leHRlbmQoZSxUci5jb25zdGFudC5zaXplKTticmVhaztjYXNlIF9pLlNIT1JUQ1VUOmlmKGUubm9kZS50eXBlPT09anQuT1ApWmUuZXh0ZW5kKGUsVHIuc2hvcnRjdXRTaXplLm9wKTtlbHNlIGlmKGUubm9kZS50eXBlPT09anQuTUVUQSlaZS5leHRlbmQoZSxUci5zaG9ydGN1dFNpemUubWV0YSk7ZWxzZSBpZihlLm5vZGUudHlwZT09PWp0LlNFUklFUylaZS5leHRlbmQoZSxUci5zaG9ydGN1dFNpemUuc2VyaWVzKTtlbHNlIHRocm93IEVycm9yKCJJbnZhbGlkIG5vZGUgdHlwZTogIitlLm5vZGUudHlwZSk7YnJlYWs7Y2FzZSBfaS5TVU1NQVJZOlplLmV4dGVuZChlLFRyLmNvbnN0YW50LnNpemUpO2JyZWFrfX1mdW5jdGlvbiB2MChlKXtpZihlLmV4cGFuZGVkKXJldHVybiBlLng7bGV0IHQ9ZS5pbkFubm90YXRpb25zLmxpc3QubGVuZ3RoP2UuaW5ib3hXaWR0aDowO3JldHVybiBlLngtZS53aWR0aC8yK3QrZS5jb3JlQm94LndpZHRoLzJ9ZnVuY3Rpb24gX2xlKGUsdCl7bGV0IHI9dC54LWUueCxuPXQueS1lLnk7cmV0dXJuIDE4MCpNYXRoLmF0YW4obi9yKS9NYXRoLlBJfWZ1bmN0aW9uIEljcihlKXtsZXQgdD1fbGUoZVswXSxlWzFdKTtmb3IobGV0IHI9MTtyPGUubGVuZ3RoLTE7cisrKXtsZXQgbj1fbGUoZVtyXSxlW3IrMV0pO2lmKE1hdGguYWJzKG4tdCk+MSlyZXR1cm4hMTt0PW59cmV0dXJuITB9ZnVuY3Rpb24geWxlKGUsdCl7bGV0IHI9dC5leHBhbmRlZD90Lng6djAodCksbj10LnksaT1lLngtcixvPWUueS1uLGE9dC5leHBhbmRlZD90LndpZHRoOnQuY29yZUJveC53aWR0aCxzPXQuZXhwYW5kZWQ/dC5oZWlnaHQ6dC5jb3JlQm94LmhlaWdodCxsLGM7cmV0dXJuIE1hdGguYWJzKG8pKmEvMj5NYXRoLmFicyhpKSpzLzI/KG88MCYmKHM9LXMpLGw9bz09PTA/MDpzLzIqaS9vLGM9cy8yKTooaTwwJiYoYT0tYSksbD1hLzIsYz1pPT09MD8wOmEvMipvL2kpLHt4OnIrbCx5Om4rY319dmFyIFBsPW0wLEhpPXllLExjcj0zMjAsa2NyPTE1MCxoUD1be2JhY2tncm91bmRfY29sb3I6IiNDQzJGMkMiLGxhYmVsOiJOYU4ifSx7YmFja2dyb3VuZF9jb2xvcjoiI0ZGOEQwMCIsbGFiZWw6Ii1cdTIyMUUifSx7YmFja2dyb3VuZF9jb2xvcjoiI0VBRUFFQSIsbGFiZWw6Ii0ifSx7YmFja2dyb3VuZF9jb2xvcjoiI0E1QTVBNSIsbGFiZWw6IjAifSx7YmFja2dyb3VuZF9jb2xvcjoiIzI2MjYyNiIsbGFiZWw6IisifSx7YmFja2dyb3VuZF9jb2xvcjoiIzAwM0VENCIsbGFiZWw6IitcdTIyMUUifV07ZnVuY3Rpb24gd2xlKGUsdCxyLG4pe2xldCBpPWUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksbz1udWxsO3RyeXtpZihvPXQuZ2V0QkJveCgpLChvPT1udWxsP3ZvaWQgMDpvLndpZHRoKT09PTApcmV0dXJufWNhdGNoKGMpe3JldHVybn1sZXQgYT0uOSpNYXRoLm1pbihpLndpZHRoLyhvPT1udWxsP3ZvaWQgMDpvLndpZHRoKSxpLmhlaWdodC8obz09bnVsbD92b2lkIDA6by5oZWlnaHQpLDIpLHM9VHIuZ3JhcGgsbD1YaC5zY2FsZShhKS50cmFuc2xhdGUocy5wYWRkaW5nLnBhZGRpbmdMZWZ0LHMucGFkZGluZy5wYWRkaW5nVG9wKTtIdChlKS50cmFuc2l0aW9uKCkuZHVyYXRpb24oNTAwKS5jYWxsKHIudHJhbnNmb3JtLGwpLm9uKCJlbmQuZml0dGVkIiwoKT0+e3Iub24oImVuZC5maXR0ZWQiLG51bGwpLG4oKX0pfWZ1bmN0aW9uIFNsZShlLHQscixuKXtsZXQgaT1IdCh0KS5zZWxlY3QoYFtkYXRhLW5hbWU9IiR7ZX0iXWApLm5vZGUoKTtpZighaSlyZXR1cm4gY29uc29sZS53YXJuKGBwYW5Ub05vZGUoKSBmYWlsZWQgZm9yIG5vZGUgbmFtZSAiJHtlfSJgKSwhMTtsZXQgbz1pLmdldEJCb3goKSxhPWkuZ2V0U2NyZWVuQ1RNKCkscz10LmNyZWF0ZVNWR1BvaW50KCksbD10LmNyZWF0ZVNWR1BvaW50KCk7cy54PW8ueCxzLnk9by55LGwueD1vLngrby53aWR0aCxsLnk9by55K28uaGVpZ2h0LHM9cy5tYXRyaXhUcmFuc2Zvcm0oYSksbD1sLm1hdHJpeFRyYW5zZm9ybShhKTtsZXQgYz0ocCxkLGcsXyk9PiEocD5nJiZkPF8pLHU9dC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxoPXUubGVmdCt1LndpZHRoLUxjcixmPXUudG9wK3UuaGVpZ2h0LWtjcjtpZihjKHMueCxsLngsdS5sZWZ0LGgpfHxjKHMueSxsLnksdS50b3AsZikpe2xldCBwPShzLngrbC54KS8yLGQ9KHMueStsLnkpLzIsZz11LmxlZnQrdS53aWR0aC8yLXAsXz11LnRvcCt1LmhlaWdodC8yLWQseT1pMih0KTtyZXR1cm4gSHQodCkudHJhbnNpdGlvbigpLmR1cmF0aW9uKDUwMCkuY2FsbChuLnRyYW5zbGF0ZUJ5LGcveS5rLF8veS5rKSwhMH1yZXR1cm4hMX1mdW5jdGlvbiBNbGUoZSx0KXtsZXQgcj10Lm5vZGUudHlwZT09PWp0LlNFUklFUz8wOlRyLnN1YnNjZW5lLm1ldGEubGFiZWxIZWlnaHQ7YTMoUGwoZSwiZyIsSGkuU2NlbmUuQ09SRSksMCxyKTtsZXQgbj10Lmlzb2xhdGVkSW5FeHRyYWN0Lmxlbmd0aD4wLGk9dC5pc29sYXRlZE91dEV4dHJhY3QubGVuZ3RoPjAsbz10LmxpYnJhcnlGdW5jdGlvbnNFeHRyYWN0Lmxlbmd0aD4wLGE9VHIuc3Vic2NlbmUubWV0YS5leHRyYWN0WE9mZnNldCxzPTA7aWYobiYmKHMrPXQub3V0RXh0cmFjdEJveC53aWR0aCksaSYmKHMrPXQub3V0RXh0cmFjdEJveC53aWR0aCksbil7bGV0IGw9dC5jb3JlQm94LndpZHRoO3M8bzM/bD1sLW8zK3QuaW5FeHRyYWN0Qm94LndpZHRoLzI6bD1sLXQuaW5FeHRyYWN0Qm94LndpZHRoLzItdC5vdXRFeHRyYWN0Qm94LndpZHRoLShpP2E6MCksbD1sLXQubGlicmFyeUZ1bmN0aW9uc0JveC53aWR0aC0obz9hOjApLGEzKFBsKGUsImciLEhpLlNjZW5lLklORVhUUkFDVCksbCxyKX1pZihpKXtsZXQgbD10LmNvcmVCb3gud2lkdGg7czxvMz9sPWwtbzMrdC5vdXRFeHRyYWN0Qm94LndpZHRoLzI6bC09dC5vdXRFeHRyYWN0Qm94LndpZHRoLzIsbD1sLXQubGlicmFyeUZ1bmN0aW9uc0JveC53aWR0aC0obz9hOjApLGEzKFBsKGUsImciLEhpLlNjZW5lLk9VVEVYVFJBQ1QpLGwscil9aWYobyl7bGV0IGw9dC5jb3JlQm94LndpZHRoLXQubGlicmFyeUZ1bmN0aW9uc0JveC53aWR0aC8yO2EzKFBsKGUsImciLEhpLlNjZW5lLkZVTkNUSU9OX0xJQlJBUlkpLGwscil9fWZ1bmN0aW9uIEVsZShlLHQpe0h0KGUpLm9uKCJjbGljayIsKCk9Pnt0LmZpcmUoImdyYXBoLXNlbGVjdCIpfSl9ZnVuY3Rpb24gYTMoZSx0LHIpe2UuYXR0cigidHJhbnNmb3JtIikhPW51bGwmJihlPWUudHJhbnNpdGlvbigicG9zaXRpb24iKSksZS5hdHRyKCJ0cmFuc2Zvcm0iLCJ0cmFuc2xhdGUoIit0KyIsIityKyIpIil9ZnVuY3Rpb24gemQoZSx0LHIsbixpKXtlLnRyYW5zaXRpb24oKS5hdHRyKCJ4Iix0LW4vMikuYXR0cigieSIsci1pLzIpLmF0dHIoIndpZHRoIixuKS5hdHRyKCJoZWlnaHQiLGkpfWZ1bmN0aW9uIFRsZShlLHQscixuLGkpe2xldCBvPWkvMixhPW4vMixzPVtbdCxyLW9dLFt0K2EscitvXSxbdC1hLHIrb11dO2UudHJhbnNpdGlvbigpLmF0dHIoInBvaW50cyIscy5tYXAobD0+bC5qb2luKCIsIikpLmpvaW4oIiAiKSl9ZnVuY3Rpb24gQ2xlKGUsdCl7bGV0IHI9djAodCksbj10LmV4cGFuZGVkP3Qud2lkdGg6dC5jb3JlQm94LndpZHRoLGk9dC5leHBhbmRlZD90LmhlaWdodDp0LmNvcmVCb3guaGVpZ2h0LG89cituLzItNixhPXQueS1pLzIrNjt0Lm5vZGUudHlwZT09PWp0LlNFUklFUyYmIXQuZXhwYW5kZWQmJihvKz0xMCxhLT0yKTtsZXQgcz0idHJhbnNsYXRlKCIrbysiLCIrYSsiKSI7ZS5zZWxlY3RBbGwoInBhdGgiKS50cmFuc2l0aW9uKCkuYXR0cigidHJhbnNmb3JtIixzKSxlLnNlbGVjdCgiY2lyY2xlIikudHJhbnNpdGlvbigpLmF0dHIoe2N4Om8sY3k6YSxyOlRyLm5vZGVTaXplLm1ldGEuZXhwYW5kQnV0dG9uUmFkaXVzfSl9ZnVuY3Rpb24gTUgoZSx0LHIsbixpKXtlLnRyYW5zaXRpb24oKS5hdHRyKCJjeCIsdCkuYXR0cigiY3kiLHIpLmF0dHIoInJ4IixuLzIpLmF0dHIoInJ5IixpLzIpfWZ1bmN0aW9uIGJsZShlLHQpe3JldHVybiB0P2UudG9GaXhlZCgwKTpNYXRoLmFicyhlKT49MT9lLnRvRml4ZWQoMSk6ZS50b0V4cG9uZW50aWFsKDEpfWZ1bmN0aW9uIFJjcihlLHQscixuKXtsZXQgaT0iRGV2aWNlOiAiK2UuZGV2aWNlX25hbWUrYApgO2krPSJkdHlwZTogIitlLmR0eXBlK2AKYDtsZXQgbz0iKHNjYWxhcikiO2Uuc2hhcGUubGVuZ3RoPjAmJihvPSIoIitlLnNoYXBlLmpvaW4oIiwiKSsiKSIpLGkrPWAKc2hhcGU6IGArbytgCgpgLGkrPSIjKGVsZW1lbnRzKTogIit0K2AKYDtsZXQgYT1bXTtmb3IobGV0IHM9MDtzPHIubGVuZ3RoO3MrKylyW3NdPjAmJmEucHVzaCgiIygiK2hQW3NdLmxhYmVsKyIpOiAiK3Jbc10pO3JldHVybiBpKz1hLmpvaW4oIiwgIikrYAoKYCxuLm1heD49bi5taW4mJihpKz0ibWluOiAiK24ubWluKyIsIG1heDogIituLm1heCtgCmAsaSs9Im1lYW46ICIrbi5tZWFuKyIsIHN0ZGRldjogIituLnN0ZGRldiksaX1mdW5jdGlvbiBOY3IoZSx0LHIsbixpPTYwLG89MTAsYT0wLHMpe2lmKEh0KGUucGFyZW50Tm9kZSkuc2VsZWN0QWxsKCIuaGVhbHRoLXBpbGwiKS5yZW1vdmUoKSwhdClyZXR1cm47bGV0IGw9dC52YWx1ZSxjPWwuc2xpY2UoMiw4KSx1PWNbMF0saD1jWzFdLGY9Y1s1XSxwPWxbMV0sZD17bWluOmxbOF0sbWF4OmxbOV0sbWVhbjpsWzEwXSxzdGRkZXY6TWF0aC5zcXJ0KGxbMTFdKX07aT09bnVsbCYmKGk9NjApLG89PW51bGwmJihvPTEwKSxhPT1udWxsJiYoYT0wKSxyIT1udWxsJiZyLm5vZGUudHlwZT09PWp0Lk9QJiYoaS89MixvLz0yKTtsZXQgZz1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoanUsImciKTtnLmNsYXNzTGlzdC5hZGQoImhlYWx0aC1waWxsIik7bGV0IF89ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKGp1LCJkZWZzIik7Zy5hcHBlbmRDaGlsZChfKTtsZXQgeT1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoanUsImxpbmVhckdyYWRpZW50IikseD0iaGVhbHRoLXBpbGwtZ3JhZGllbnQtIituO3kuc2V0QXR0cmlidXRlKCJpZCIseCk7bGV0IGI9MCxTPSIwJSI7Zm9yKGxldCBEPTA7RDxjLmxlbmd0aDtEKyspe2lmKCFjW0RdKWNvbnRpbnVlO2IrPWNbRF07bGV0IEI9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKGp1LCJzdG9wIik7Qi5zZXRBdHRyaWJ1dGUoIm9mZnNldCIsUyksQi5zZXRBdHRyaWJ1dGUoInN0b3AtY29sb3IiLGhQW0RdLmJhY2tncm91bmRfY29sb3IpLHkuYXBwZW5kQ2hpbGQoQik7bGV0IEk9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKGp1LCJzdG9wIiksTD1iKjEwMC9wKyIlIjtJLnNldEF0dHJpYnV0ZSgib2Zmc2V0IixMKSxJLnNldEF0dHJpYnV0ZSgic3RvcC1jb2xvciIsaFBbRF0uYmFja2dyb3VuZF9jb2xvcikseS5hcHBlbmRDaGlsZChJKSxTPUx9Xy5hcHBlbmRDaGlsZCh5KTtsZXQgQz1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoanUsInJlY3QiKTtDLnNldEF0dHJpYnV0ZSgiZmlsbCIsInVybCgjIit4KyIpIiksQy5zZXRBdHRyaWJ1dGUoIndpZHRoIixTdHJpbmcoaSkpLEMuc2V0QXR0cmlidXRlKCJoZWlnaHQiLFN0cmluZyhvKSksQy5zZXRBdHRyaWJ1dGUoInkiLFN0cmluZyhhKSksZy5hcHBlbmRDaGlsZChDKTtsZXQgUD1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoanUsInRpdGxlIik7UC50ZXh0Q29udGVudD1SY3IodCxwLGMsZCksZy5hcHBlbmRDaGlsZChQKTtsZXQgaz0hMTtpZihyIT1udWxsKXtsZXQgRD1yLngtaS8yLEI9ci55LW8tci5oZWlnaHQvMi0yO2lmKHIubGFiZWxPZmZzZXQ8MCYmKEIrPXIubGFiZWxPZmZzZXQpLGcuc2V0QXR0cmlidXRlKCJ0cmFuc2Zvcm0iLCJ0cmFuc2xhdGUoIitEKyIsICIrQisiKSIpLGNbMl18fGNbM118fGNbNF0pe2xldCBMPXIubm9kZS5hdHRyO2lmKEwmJkwubGVuZ3RoKXtmb3IobGV0IFI9MDtSPEwubGVuZ3RoO1IrKylpZihMW1JdLmtleT09PSJUIil7bGV0IEY9TFtSXS52YWx1ZS50eXBlO2s9RiYmL15EVF8oQk9PTHxJTlR8VUlOVCkvLnRlc3QoRik7YnJlYWt9fX19bGV0IE89ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKGp1LCJ0ZXh0Iik7aWYoTnVtYmVyLmlzRmluaXRlKGQubWluKSYmTnVtYmVyLmlzRmluaXRlKGQubWF4KSl7bGV0IEQ9YmxlKGQubWluLGspLEI9YmxlKGQubWF4LGspO2lmKHA+MT9PLnRleHRDb250ZW50PUQrIiB+ICIrQjpPLnRleHRDb250ZW50PUQsdT4wfHxoPjB8fGY+MCl7Ty50ZXh0Q29udGVudCs9IiAoIjtsZXQgST1bXTt1PjAmJkkucHVzaChgTmFOXHhENyR7dX1gKSxoPjAmJkkucHVzaChgLVx1MjIxRVx4RDcke2h9YCksZj4wJiZJLnB1c2goYCtcdTIyMUVceEQ3JHtmfWApLE8udGV4dENvbnRlbnQrPUkuam9pbigiOyAiKSsiKSJ9fWVsc2UgTy50ZXh0Q29udGVudD0iKE5vIGZpbml0ZSBlbGVtZW50cykiO08uY2xhc3NMaXN0LmFkZCgiaGVhbHRoLXBpbGwtc3RhdHMiKSxzPT1udWxsJiYocz1pLzIpLE8uc2V0QXR0cmlidXRlKCJ4IixTdHJpbmcocykpLE8uc2V0QXR0cmlidXRlKCJ5IixTdHJpbmcoYS0yKSksZy5hcHBlbmRDaGlsZChPKSx6dChlLnBhcmVudE5vZGUpLmFwcGVuZENoaWxkKGcpfWZ1bmN0aW9uIEFsZShlLHQscil7aWYoIXQpcmV0dXJuO2xldCBuPTE7SHQoZSkuc2VsZWN0QWxsKCJnLm5vZGVzaGFwZSIpLmVhY2goZnVuY3Rpb24obyl7bGV0IGE9dFtvLm5vZGUubmFtZV0scz1hP2Fbcl06bnVsbDtOY3IodGhpcyxzLG8sbisrKX0pfXZhciBHbjsoZnVuY3Rpb24oZSl7ZS5OT05FPSJub25lIixlLkNPTVBVVEVfVElNRT0iY29tcHV0ZV90aW1lIixlLkRFVklDRT0iZGV2aWNlIixlLk1FTU9SWT0ibWVtb3J5IixlLk9QX0NPTVBBVElCSUxJVFk9Im9wX2NvbXBhdGliaWxpdHkiLGUuU1RSVUNUVVJFPSJzdHJ1Y3R1cmUiLGUuWExBX0NMVVNURVI9InhsYV9jbHVzdGVyIn0pKEdufHwoR249e30pKTt2YXIgZFA9RWUoT2UoKSwxKTt2YXIgVmk9RWUoT2UoKSwxKTtmdW5jdGlvbiBEY3IoZSl7bGV0IHQ9MCxyPTAsbj1lO2Zvcig7biYmbi5vZmZzZXRMZWZ0Pj0wJiZuLm9mZnNldFRvcD49MDspdCs9bi5vZmZzZXRMZWZ0LW4uc2Nyb2xsTGVmdCxyKz1uLm9mZnNldFRvcC1uLnNjcm9sbFRvcCxuPW4ub2Zmc2V0UGFyZW50O3JldHVybntsZWZ0OnQsdG9wOnJ9fWZ1bmN0aW9uIFlsdChlLHQpe2xldCByPWUuZ2V0Q29udGV4dE1lbnUoKSxuPUh0KGUuZ2V0Q29udGV4dE1lbnUoKSk7cmV0dXJuIGZ1bmN0aW9uKGksbyl7bGV0IGE9cXQscz1EY3IoZSk7bi5zdHlsZSgiZGlzcGxheSIsImJsb2NrIikuc3R5bGUoImxlZnQiLGEuY2xpZW50WC1zLmxlZnQrMSsicHgiKS5zdHlsZSgidG9wIixhLmNsaWVudFktcy50b3ArMSsicHgiKSxhLnByZXZlbnREZWZhdWx0KCksYS5zdG9wUHJvcGFnYXRpb24oKTtmdW5jdGlvbiBsKHUpe3UmJnUuY29tcG9zZWRQYXRoKCkuaW5jbHVkZXMocil8fChuLnN0eWxlKCJkaXNwbGF5Iiwibm9uZSIpLGRvY3VtZW50LmJvZHkucmVtb3ZlRXZlbnRMaXN0ZW5lcigibW91c2Vkb3duIixsLHtjYXB0dXJlOiEwfSkpfWRvY3VtZW50LmJvZHkuYWRkRXZlbnRMaXN0ZW5lcigibW91c2Vkb3duIixsLHtjYXB0dXJlOiEwfSksbi50ZXh0KCIiKSxuLmFwcGVuZCgidWwiKS5zZWxlY3RBbGwoImxpIikuZGF0YSh0KS5lbnRlcigpLmFwcGVuZCgibGkiKS5vbigiY2xpY2siLCh1LGgpPT57dS5hY3Rpb24odGhpcyxpLG8pLGwoKX0pLnRleHQoZnVuY3Rpb24odSl7cmV0dXJuIHUudGl0bGUoaSl9KX19dmFyIENIPUVlKE9lKCksMSk7dmFyIHpjcj0iXHhENyIsUGxlPXFiKCkuZG9tYWluKFtKNCxRNF0pLnJhbmdlKFsic21hbGwiLCJtZWRpdW0iLCJsYXJnZSIsInhsYXJnZSJdKSxGY3I9Mi41O2Z1bmN0aW9uIGZQKGUpe3JldHVybiBlLnYrdGxlK2Uud31mdW5jdGlvbiBrbGUoZSx0LHIpe2xldCBuPXIsaT1bXTtpPUNILnJlZHVjZSh0LmVkZ2VzKCksKHMsbCk9PntsZXQgYz10LmVkZ2UobCk7cmV0dXJuIHMucHVzaCh7djpsLnYsdzpsLncsbGFiZWw6Y30pLHN9LGkpO2xldCBhPVBuKGUsImciLHllLkVkZ2UuQ09OVEFJTkVSKS5zZWxlY3RBbGwoZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5jaGlsZE5vZGVzfSkuZGF0YShpLGZQKTtyZXR1cm4gYS5lbnRlcigpLmFwcGVuZCgiZyIpLmF0dHIoImNsYXNzIix5ZS5FZGdlLkdST1VQKS5hdHRyKCJkYXRhLWVkZ2UiLGZQKS5lYWNoKGZ1bmN0aW9uKHMpe2xldCBsPUh0KHRoaXMpO3MubGFiZWwuZWRnZUdyb3VwPWwsbi5fZWRnZUdyb3VwSW5kZXhbZlAocyldPWwsbi5oYW5kbGVFZGdlU2VsZWN0ZWQmJmwub24oImNsaWNrIixjPT57cXQuc3RvcFByb3BhZ2F0aW9uKCksbi5maXJlKCJlZGdlLXNlbGVjdCIse2VkZ2VEYXRhOmMsZWRnZUdyb3VwOmx9KX0pLCRsdChsLHMsbil9KS5tZXJnZShhKS5lYWNoKGZ1bmN0aW9uKCl7SGNyKHIsdGhpcyl9KS5lYWNoKGZ1bmN0aW9uKHMpe1ZjcihIdCh0aGlzKSxzLG4pfSksYS5leGl0KCkuZWFjaChzPT57ZGVsZXRlIG4uX2VkZ2VHcm91cEluZGV4W2ZQKHMpXX0pLnJlbW92ZSgpLGF9ZnVuY3Rpb24gamx0KGUsdCl7bGV0IHI9dC5nZXROb2RlQnlOYW1lKGUudik7aWYoci5vdXRwdXRTaGFwZXM9PW51bGx8fENILmlzRW1wdHkoci5vdXRwdXRTaGFwZXMpKXJldHVybiBudWxsO2xldCBuPXIub3V0cHV0U2hhcGVzW2Uub3V0cHV0VGVuc29yS2V5XTtyZXR1cm4gbj09bnVsbD9udWxsOm4ubGVuZ3RoPT09MD8ic2NhbGFyIjpuLm1hcChpPT5pPT09LTE/Ij8iOmkpLmpvaW4oemNyKX1mdW5jdGlvbiBYbHQoZSx0KXtyZXR1cm4gdC5lZGdlTGFiZWxGdW5jdGlvbj90LmVkZ2VMYWJlbEZ1bmN0aW9uKGUsdCk6ZS5iYXNlRWRnZUxpc3QubGVuZ3RoPjE/ZS5iYXNlRWRnZUxpc3QubGVuZ3RoKyIgdGVuc29ycyI6amx0KGUuYmFzZUVkZ2VMaXN0WzBdLHQpfWZ1bmN0aW9uIElsZShlLHQscil7bGV0IG49ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKGp1LCJwYXRoIik7Zm9yKGxldCBpPTE7aTxlLmxlbmd0aDtpKyspaWYobi5zZXRBdHRyaWJ1dGUoImQiLHIoZS5zbGljZSgwLGkpKSksbi5nZXRUb3RhbExlbmd0aCgpPnQpcmV0dXJuIGktMTtyZXR1cm4gZS5sZW5ndGgtMX1mdW5jdGlvbiBMbGUoZSx0LHIpe2xldCBuPXZ1KCkueCh1PT51LngpLnkodT0+dS55KSxpPUh0KGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUygiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLCJwYXRoIikpLmF0dHIoImQiLG4oZSkpLG89K3QuYXR0cigibWFya2VyV2lkdGgiKSxhPXQuYXR0cigidmlld0JveCIpLnNwbGl0KCIgIikubWFwKE51bWJlcikscz1hWzJdLWFbMF0sbD0rdC5hdHRyKCJyZWZYIiksYz1pLm5vZGUoKTtpZihyKXtsZXQgdT0xLWwvcyxoPW8qdSxmPWMuZ2V0UG9pbnRBdExlbmd0aChoKSxwPUlsZShlLGgsbik7cmV0dXJuIGVbcC0xXT17eDpmLngseTpmLnl9LGUuc2xpY2UocC0xKX1lbHNle2xldCB1PTEtbC9zLGg9Yy5nZXRUb3RhbExlbmd0aCgpLW8qdSxmPWMuZ2V0UG9pbnRBdExlbmd0aChoKSxwPUlsZShlLGgsbik7cmV0dXJuIGVbcF09e3g6Zi54LHk6Zi55fSxlLnNsaWNlKDAscCsxKX19ZnVuY3Rpb24gJGx0KGUsdCxyLG4pe249bnx8eWUuRWRnZS5MSU5FLHQubGFiZWwmJnQubGFiZWwuc3RydWN0dXJhbCYmKG4rPSIgIit5ZS5FZGdlLlNUUlVDVFVSQUwpLHQubGFiZWwmJnQubGFiZWwubWV0YWVkZ2UmJnQubGFiZWwubWV0YWVkZ2UubnVtUmVmRWRnZXMmJihuKz0iICIreWUuRWRnZS5SRUZFUkVOQ0VfRURHRSksci5oYW5kbGVFZGdlU2VsZWN0ZWQmJihuKz0iICIreWUuRWRnZS5TRUxFQ1RBQkxFKTtsZXQgaT0icGF0aF8iK2ZQKHQpLG87aWYoci5yZW5kZXJIaWVyYXJjaHkuZWRnZVdpZHRoRnVuY3Rpb24pbz1yLnJlbmRlckhpZXJhcmNoeS5lZGdlV2lkdGhGdW5jdGlvbih0LG4pO2Vsc2V7bGV0IGM9MTt0LmxhYmVsIT1udWxsJiZ0LmxhYmVsLm1ldGFlZGdlIT1udWxsJiYoYz10LmxhYmVsLm1ldGFlZGdlLnRvdGFsU2l6ZSksbz1yLnJlbmRlckhpZXJhcmNoeS5lZGdlV2lkdGhTaXplZEJhc2VkU2NhbGUoYyl9bGV0IGE9ZS5hcHBlbmQoInBhdGgiKS5hdHRyKCJpZCIsaSkuYXR0cigiY2xhc3MiLG4pLnN0eWxlKCJzdHJva2Utd2lkdGgiLG8rInB4Iik7aWYodC5sYWJlbCYmdC5sYWJlbC5tZXRhZWRnZSlpZih0LmxhYmVsLm1ldGFlZGdlLm51bVJlZkVkZ2VzKXtsZXQgYz1gcmVmZXJlbmNlLWFycm93aGVhZC0ke1BsZShvKX1gO2Euc3R5bGUoIm1hcmtlci1zdGFydCIsYHVybCgjJHtjfSlgKSx0LmxhYmVsLnN0YXJ0TWFya2VySWQ9Y31lbHNle2xldCBjPWBkYXRhZmxvdy1hcnJvd2hlYWQtJHtQbGUobyl9YDthLnN0eWxlKCJtYXJrZXItZW5kIixgdXJsKCMke2N9KWApLHQubGFiZWwuZW5kTWFya2VySWQ9Y31pZih0LmxhYmVsPT1udWxsfHx0LmxhYmVsLm1ldGFlZGdlPT1udWxsKXJldHVybjtsZXQgcz1YbHQodC5sYWJlbC5tZXRhZWRnZSxyLnJlbmRlckhpZXJhcmNoeSk7aWYocz09bnVsbClyZXR1cm47bGV0IGw9bz5GY3I/ImNlbnRyYWwiOiJ0ZXh0LWFmdGVyLWVkZ2UiO2UuYXBwZW5kKCJ0ZXh0IikuYXBwZW5kKCJ0ZXh0UGF0aCIpLmF0dHIoInhsaW5rOmhyZWYiLCIjIitpKS5hdHRyKCJzdGFydE9mZnNldCIsIjUwJSIpLmF0dHIoInRleHQtYW5jaG9yIiwibWlkZGxlIikuYXR0cigiZG9taW5hbnQtYmFzZWxpbmUiLCJjZW50cmFsIikudGV4dChzKX12YXIgVEg9dnUoKS5jdXJ2ZShHOCkueChlPT5lLngpLnkoZT0+ZS55KTtmdW5jdGlvbiBCY3IoZSx0LHIsbixpKXtsZXQgbz1yLmxhYmVsLGE9by5hZGpvaW5pbmdNZXRhZWRnZSxzPW8ucG9pbnRzLHtzaGFkb3dSb290Omx9PWU7aWYoci5sYWJlbC5zdGFydE1hcmtlcklkJiYocz1MbGUocyxIdChsPT1udWxsP3ZvaWQgMDpsLnF1ZXJ5U2VsZWN0b3IoIiMiK3IubGFiZWwuc3RhcnRNYXJrZXJJZCkpLCEwKSksci5sYWJlbC5lbmRNYXJrZXJJZCYmKHM9TGxlKHMsSHQobD09bnVsbD92b2lkIDA6bC5xdWVyeVNlbGVjdG9yKCIjIityLmxhYmVsLmVuZE1hcmtlcklkKSksITEpKSwhYSlyZXR1cm4gbmMoaSxUSChzKSk7bGV0IGM9YS5lZGdlR3JvdXAubm9kZSgpLmZpcnN0Q2hpbGQsdT1vLm1ldGFlZGdlLmluYm91bmQ7cmV0dXJuIGZ1bmN0aW9uKGgpe3ZhciBnO2xldCBmPWMuZ2V0UG9pbnRBdExlbmd0aCh1P2MuZ2V0VG90YWxMZW5ndGgoKTowKS5tYXRyaXhUcmFuc2Zvcm0oYy5nZXRDVE0oKSkubWF0cml4VHJhbnNmb3JtKChnPXQuZ2V0Q1RNKCkpPT1udWxsP3ZvaWQgMDpnLmludmVyc2UoKSkscD11PzA6cy5sZW5ndGgtMTtyZXR1cm4gc1twXS54PWYueCxzW3BdLnk9Zi55LFRIKHMpfX1mdW5jdGlvbiBIY3IoZSx0KXtIdCh0KS5zZWxlY3QoInBhdGguIit5ZS5FZGdlLkxJTkUpLnRyYW5zaXRpb24oKS5hdHRyVHdlZW4oImQiLGZ1bmN0aW9uKHIsbixpKXtyZXR1cm4gQmNyKGUsdGhpcyxyLG4saSl9KX1mdW5jdGlvbiBWY3IoZSx0LHIpe2UuY2xhc3NlZCgiZmFkZWQiLHQubGFiZWwuaXNGYWRlZE91dCk7bGV0IG49dC5sYWJlbC5tZXRhZWRnZTtlLnNlbGVjdCgicGF0aC4iK3llLkVkZ2UuTElORSkuY2xhc3NlZCgiY29udHJvbC1kZXAiLG4mJiFuLm51bVJlZ3VsYXJFZGdlcyl9ZnVuY3Rpb24gQUgoZSx0LHIpe2xldCBpPVBuKGUsImciLHllLk5vZGUuQ09OVEFJTkVSKS5zZWxlY3RBbGwoZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5jaGlsZE5vZGVzfSkuZGF0YSh0LG89Pm8ubm9kZS5uYW1lKyI6IitvLm5vZGUudHlwZSk7cmV0dXJuIGkuZW50ZXIoKS5hcHBlbmQoImciKS5hdHRyKCJkYXRhLW5hbWUiLG89Pm8ubm9kZS5uYW1lKS5lYWNoKGZ1bmN0aW9uKG8pe2xldCBhPUh0KHRoaXMpO3IuYWRkTm9kZUdyb3VwKG8ubm9kZS5uYW1lLGEpfSkubWVyZ2UoaSkuYXR0cigiY2xhc3MiLG89PnllLk5vZGUuR1JPVVArIiAiK0hsZShvKSkuZWFjaChmdW5jdGlvbihvKXtsZXQgYT1IdCh0aGlzKSxzPVBuKGEsImciLHllLkFubm90YXRpb24uSU5CT1gpO09sZShzLG8uaW5Bbm5vdGF0aW9ucyxvLHIpO2xldCBsPVBuKGEsImciLHllLkFubm90YXRpb24uT1VUQk9YKTtPbGUobCxvLm91dEFubm90YXRpb25zLG8scik7bGV0IGM9QmxlKGEsbyx5ZS5Ob2RlLlNIQVBFKTtvLm5vZGUuaXNHcm91cE5vZGUmJnFjcihjLG8sciksRGxlKGMsbyxyKSxVY3IoYSxvLHIpO2xldCB1PVdjcihhLG8scik7RGxlKHUsbyxyLG8ubm9kZS50eXBlPT09anQuTUVUQSksczMoYSxvLHIpLGpjcihhLG8pfSksaS5leGl0KCkuZWFjaChmdW5jdGlvbihvKXtyLnJlbW92ZU5vZGVHcm91cChvLm5vZGUubmFtZSk7bGV0IGE9SHQodGhpcyk7by5pbkFubm90YXRpb25zLmxpc3QubGVuZ3RoPjAmJmEuc2VsZWN0KCIuIit5ZS5Bbm5vdGF0aW9uLklOQk9YKS5zZWxlY3RBbGwoIi4iK3llLkFubm90YXRpb24uR1JPVVApLmVhY2gocz0+e3IucmVtb3ZlQW5ub3RhdGlvbkdyb3VwKHMsbyl9KSxvLm91dEFubm90YXRpb25zLmxpc3QubGVuZ3RoPjAmJmEuc2VsZWN0KCIuIit5ZS5Bbm5vdGF0aW9uLk9VVEJPWCkuc2VsZWN0QWxsKCIuIit5ZS5Bbm5vdGF0aW9uLkdST1VQKS5lYWNoKHM9PntyLnJlbW92ZUFubm90YXRpb25Hcm91cChzLG8pfSl9KS5yZW1vdmUoKSxpfWZ1bmN0aW9uIFVjcihlLHQscil7aWYodC5ub2RlLmlzR3JvdXBOb2RlKXtpZih0LmV4cGFuZGVkKXJldHVybiByY3QoZSx0LHIseWUuU3Vic2NlbmUuR1JPVVApO1BsKGUsImciLHllLlN1YnNjZW5lLkdST1VQKS5yZW1vdmUoKX1yZXR1cm4gbnVsbH1mdW5jdGlvbiBObGUoZSx0KXtsZXQgcj10LngtdC53aWR0aC8yK3QucGFkZGluZ0xlZnQsbj10LnktdC5oZWlnaHQvMit0LnBhZGRpbmdUb3AsaT1QbChlLCJnIix5ZS5TdWJzY2VuZS5HUk9VUCk7YTMoaSxyLG4pfWZ1bmN0aW9uIHFjcihlLHQscil7bGV0IG49UG4oZSwiZyIseWUuTm9kZS5CVVRUT05fQ09OVEFJTkVSKTtQbihuLCJjaXJjbGUiLHllLk5vZGUuQlVUVE9OX0NJUkNMRSksUG4obiwicGF0aCIseWUuTm9kZS5FWFBBTkRfQlVUVE9OKS5hdHRyKCJkIiwiTTAsLTIuMiBWMi4yIE0tMi4yLDAgSDIuMiIpLFBuKG4sInBhdGgiLHllLk5vZGUuQ09MTEFQU0VfQlVUVE9OKS5hdHRyKCJkIiwiTS0yLjIsMCBIMi4yIiksbi5vbigiY2xpY2siLGk9PntxdC5zdG9wUHJvcGFnYXRpb24oKSxyLmZpcmUoIm5vZGUtdG9nZ2xlLWV4cGFuZCIse25hbWU6aS5ub2RlLm5hbWV9KX0pLENsZShuLHQpfWZ1bmN0aW9uIERsZShlLHQscixuKXtpZihuKXtlLmF0dHIoInBvaW50ZXItZXZlbnRzIiwibm9uZSIpO3JldHVybn1sZXQgaT1ZbHQocix6bGUodC5ub2RlLHIpKTtlLm9uKCJkYmxjbGljayIsbz0+e3IuZmlyZSgibm9kZS10b2dnbGUtZXhwYW5kIix7bmFtZTpvLm5vZGUubmFtZX0pfSkub24oIm1vdXNlb3ZlciIsbz0+e3IuaXNOb2RlRXhwYW5kZWQobyl8fHIuZmlyZSgibm9kZS1oaWdobGlnaHQiLHtuYW1lOm8ubm9kZS5uYW1lfSl9KS5vbigibW91c2VvdXQiLG89PntyLmlzTm9kZUV4cGFuZGVkKG8pfHxyLmZpcmUoIm5vZGUtdW5oaWdobGlnaHQiLHtuYW1lOm8ubm9kZS5uYW1lfSl9KS5vbigiY2xpY2siLG89PntxdC5zdG9wUHJvcGFnYXRpb24oKSxyLmZpcmUoIm5vZGUtc2VsZWN0Iix7bmFtZTpvLm5vZGUubmFtZX0pfSkub24oImNvbnRleHRtZW51IiwobyxhKT0+e3IuZmlyZSgibm9kZS1zZWxlY3QiLHtuYW1lOm8ubm9kZS5uYW1lfSksaS5jYWxsKG8sYSl9KX1mdW5jdGlvbiB6bGUoZSx0KXtsZXQgcj1be3RpdGxlOm49Pl9IKGUuaW5jbHVkZSksYWN0aW9uOihuLGksbyk9Pnt0LmZpcmUoIm5vZGUtdG9nZ2xlLWV4dHJhY3QiLHtuYW1lOmUubmFtZX0pfX1dO3JldHVybiB0Lm5vZGVDb250ZXh0TWVudUl0ZW1zJiYocj1yLmNvbmNhdCh0Lm5vZGVDb250ZXh0TWVudUl0ZW1zKSksUWx0KGUpJiZyLnB1c2goe3RpdGxlOm49PnRjdChlKSxhY3Rpb246KG4saSxvKT0+e3QuZmlyZSgibm9kZS10b2dnbGUtc2VyaWVzZ3JvdXAiLHtuYW1lOlBIKGUpfSl9fSkscn1mdW5jdGlvbiBRbHQoZSl7cmV0dXJuIFBIKGUpIT09bnVsbH1mdW5jdGlvbiBQSChlKXtyZXR1cm4gZT9lLnR5cGU9PT1qdC5TRVJJRVM/ZS5uYW1lOmUudHlwZT09PWp0Lk9QP2Uub3duaW5nU2VyaWVzOm51bGw6bnVsbH1mdW5jdGlvbiBHY3IoZSl7bGV0IHQ9bnVsbDtpZihlKWUudHlwZT09PWp0LlNFUklFUz90PWU6ZS5wYXJlbnROb2RlJiZlLnBhcmVudE5vZGUudHlwZT09PWp0LlNFUklFUyYmKHQ9ZS5wYXJlbnROb2RlKTtlbHNlIHJldHVybiBudWxsO3JldHVybiB0fWZ1bmN0aW9uIHRjdChlKXtyZXR1cm4gb2xlKEdjcihlKSE9PW51bGw/aXMuR1JPVVA6aXMuVU5HUk9VUCl9ZnVuY3Rpb24gV2NyKGUsdCxyKXt2YXIgYztsZXQgbj10LmRpc3BsYXlOYW1lLGk9dC5ub2RlLnR5cGU9PT1qdC5NRVRBJiYhdC5leHBhbmRlZCxvPVBuKGUsInRleHQiLHllLk5vZGUuTEFCRUwpLGE9by5ub2RlKCk7KGM9YS5wYXJlbnROb2RlKT09bnVsbHx8Yy5hcHBlbmRDaGlsZChhKSxvLmF0dHIoImR5IiwiLjM1ZW0iKS5hdHRyKCJ0ZXh0LWFuY2hvciIsIm1pZGRsZSIpO2xldCBzPTg7c3dpdGNoKHQubm9kZS50eXBlKXtjYXNlIGp0Lk1FVEE6cz10LmV4cGFuZGVkP1o0Lk5vZGUuRVhQQU5ERURfTEFCRUw6WjQuTm9kZS5TRVJJRVNfTEFCRUw7YnJlYWs7Y2FzZSBqdC5PUDpzPVo0Lk5vZGUuT1BfTEFCRUw7YnJlYWt9aWYoaSl7bi5sZW5ndGg+ci5tYXhNZXRhbm9kZUxhYmVsTGVuZ3RoJiYobj1uLnN1YnN0cigwLHIubWF4TWV0YW5vZGVMYWJlbExlbmd0aC0yKSsiXHUyMDI2Iik7bGV0IHU9WWNyKHIpO28uYXR0cigiZm9udC1zaXplIix1KG4ubGVuZ3RoKSsicHgiKSxzPXUobi5sZW5ndGgpfWxldCBsPW8udGV4dChuKTtyZXR1cm4gRmxlKGwsdC5ub2RlLnR5cGUscyx0KSxvfWZ1bmN0aW9uIEZsZShlLHQscixuKXtsZXQgaT1lLm5vZGUoKSxvPWkudGV4dENvbnRlbnQsYT1udWxsO3N3aXRjaCh0KXtjYXNlIGp0Lk1FVEE6biYmIW4uZXhwYW5kZWQmJihhPVRyLm5vZGVTaXplLm1ldGEubWF4TGFiZWxXaWR0aCk7YnJlYWs7Y2FzZSBqdC5PUDphPVRyLm5vZGVTaXplLm9wLm1heExhYmVsV2lkdGg7YnJlYWs7Y2FzZS0xOmE9VHIuYW5ub3RhdGlvbnMubWF4TGFiZWxXaWR0aDticmVhaztkZWZhdWx0OmJyZWFrfWlmKGEhPT1udWxsKXJldHVybiBpLnRleHRDb250ZW50PSRzZShpLnRleHRDb250ZW50LHIsYSksZS5hcHBlbmQoInRpdGxlIikudGV4dChvKX12YXIgS2x0PW51bGw7ZnVuY3Rpb24gWWNyKGUpe3JldHVybiBLbHR8fChLbHQ9em4oKS5kb21haW4oW2UubWF4TWV0YW5vZGVMYWJlbExlbmd0aExhcmdlRm9udCxlLm1heE1ldGFub2RlTGFiZWxMZW5ndGhdKS5yYW5nZShbZS5tYXhNZXRhbm9kZUxhYmVsTGVuZ3RoRm9udFNpemUsZS5taW5NZXRhbm9kZUxhYmVsTGVuZ3RoRm9udFNpemVdKS5jbGFtcCghMCkpLEtsdH1mdW5jdGlvbiBwUChlLHQscixuKXtQbChlLCJ0ZXh0Iix5ZS5Ob2RlLkxBQkVMKS50cmFuc2l0aW9uKCkuYXR0cigieCIsdCkuYXR0cigieSIscituKX1mdW5jdGlvbiBCbGUoZSx0LHIpe2xldCBuPVBuKGUsImciLHIpO3N3aXRjaCh0Lm5vZGUudHlwZSl7Y2FzZSBqdC5PUDpsZXQgaT10Lm5vZGU7aWYoVmkuaXNOdW1iZXIoaS5mdW5jdGlvbklucHV0SW5kZXgpfHxWaS5pc051bWJlcihpLmZ1bmN0aW9uT3V0cHV0SW5kZXgpKXtQbihuLCJwb2x5Z29uIix5ZS5Ob2RlLkNPTE9SX1RBUkdFVCk7YnJlYWt9UG4obiwiZWxsaXBzZSIseWUuTm9kZS5DT0xPUl9UQVJHRVQpO2JyZWFrO2Nhc2UganQuU0VSSUVTOmxldCBvPSJhbm5vdGF0aW9uIixhPXQ7YS5jb3JlR3JhcGgmJihvPWEubm9kZS5oYXNOb25Db250cm9sRWRnZXM/InZlcnRpY2FsIjoiaG9yaXpvbnRhbCIpO2xldCBzPVt5ZS5Ob2RlLkNPTE9SX1RBUkdFVF07YS5pc0ZhZGVkT3V0JiZzLnB1c2goImZhZGVkLWVsbGlwc2UiKSxQbihuLCJ1c2UiLHMpLmF0dHIoInhsaW5rOmhyZWYiLCIjb3Atc2VyaWVzLSIrbysiLXN0YW1wIiksUG4obiwicmVjdCIseWUuTm9kZS5DT0xPUl9UQVJHRVQpLmF0dHIoInJ4Iix0LnJhZGl1cykuYXR0cigicnkiLHQucmFkaXVzKTticmVhaztjYXNlIGp0LkJSSURHRTpQbihuLCJyZWN0Iix5ZS5Ob2RlLkNPTE9SX1RBUkdFVCkuYXR0cigicngiLHQucmFkaXVzKS5hdHRyKCJyeSIsdC5yYWRpdXMpO2JyZWFrO2Nhc2UganQuTUVUQTpQbihuLCJyZWN0Iix5ZS5Ob2RlLkNPTE9SX1RBUkdFVCkuYXR0cigicngiLHQucmFkaXVzKS5hdHRyKCJyeSIsdC5yYWRpdXMpO2JyZWFrO2RlZmF1bHQ6dGhyb3cgRXJyb3IoIlVucmVjb2duaXplZCBub2RlIHR5cGU6ICIrdC5ub2RlLnR5cGUpfXJldHVybiBufWZ1bmN0aW9uIEhsZShlKXtzd2l0Y2goZS5ub2RlLnR5cGUpe2Nhc2UganQuT1A6cmV0dXJuIHllLk9QTk9ERTtjYXNlIGp0Lk1FVEE6cmV0dXJuIHllLk1FVEFOT0RFO2Nhc2UganQuU0VSSUVTOnJldHVybiB5ZS5TRVJJRVNOT0RFO2Nhc2UganQuQlJJREdFOnJldHVybiB5ZS5CUklER0VOT0RFO2Nhc2UganQuRUxMSVBTSVM6cmV0dXJuIHllLkVMTElQU0lTTk9ERX10aHJvdyBFcnJvcigiVW5yZWNvZ25pemVkIG5vZGUgdHlwZTogIitlLm5vZGUudHlwZSl9ZnVuY3Rpb24gamNyKGUsdCl7bGV0IHI9UGwoZSwiZyIseWUuTm9kZS5TSEFQRSksbj12MCh0KTtzd2l0Y2godC5ub2RlLnR5cGUpe2Nhc2UganQuT1A6e2xldCBpPXQubm9kZTtpZihWaS5pc051bWJlcihpLmZ1bmN0aW9uSW5wdXRJbmRleCl8fFZpLmlzTnVtYmVyKGkuZnVuY3Rpb25PdXRwdXRJbmRleCkpe2xldCBvPVBsKHIsInBvbHlnb24iKTtUbGUobyx0LngsdC55LHQuY29yZUJveC53aWR0aCx0LmNvcmVCb3guaGVpZ2h0KX1lbHNle2xldCBvPVBsKHIsImVsbGlwc2UiKTtNSChvLG4sdC55LHQuY29yZUJveC53aWR0aCx0LmNvcmVCb3guaGVpZ2h0KX1wUChlLG4sdC55LHQubGFiZWxPZmZzZXQpO2JyZWFrfWNhc2UganQuTUVUQTp7bGV0IGk9ci5zZWxlY3RBbGwoInJlY3QiKTt0LmV4cGFuZGVkPyh6ZChpLHQueCx0LnksdC53aWR0aCx0LmhlaWdodCksTmxlKGUsdCkscFAoZSxuLHQueSwtdC5oZWlnaHQvMit0LmxhYmVsSGVpZ2h0LzIpKTooemQoaSxuLHQueSx0LmNvcmVCb3gud2lkdGgsdC5jb3JlQm94LmhlaWdodCkscFAoZSxuLHQueSwwKSk7YnJlYWt9Y2FzZSBqdC5TRVJJRVM6e2xldCBpPVBsKHIsInVzZSIpO3QuZXhwYW5kZWQ/KHpkKGksdC54LHQueSx0LndpZHRoLHQuaGVpZ2h0KSxObGUoZSx0KSxwUChlLG4sdC55LC10LmhlaWdodC8yK3QubGFiZWxIZWlnaHQvMikpOih6ZChpLG4sdC55LHQuY29yZUJveC53aWR0aCx0LmNvcmVCb3guaGVpZ2h0KSxwUChlLG4sdC55LHQubGFiZWxPZmZzZXQpKTticmVha31jYXNlIGp0LkJSSURHRTp7bGV0IGk9UGwociwicmVjdCIpO3pkKGksdC54LHQueSx0LndpZHRoLHQuaGVpZ2h0KTticmVha31kZWZhdWx0OnRocm93IEVycm9yKCJVbnJlY29nbml6ZWQgbm9kZSB0eXBlOiAiK3Qubm9kZS50eXBlKX19ZnVuY3Rpb24gWmx0KGUsdCxyKXtsZXQgbj1qc2UoZSk7aWYoIXIpcmV0dXJuYHVybCgjJHtufSlgO2xldCBpPUh0KHIpLG89aS5zZWxlY3QoImRlZnMjX2dyYXBoLWdyYWRpZW50cyIpO28uZW1wdHkoKSYmKG89aS5hcHBlbmQoImRlZnMiKS5hdHRyKCJpZCIsIl9ncmFwaC1ncmFkaWVudHMiKSk7bGV0IGE9by5zZWxlY3QoImxpbmVhckdyYWRpZW50IyIrbik7aWYoYS5lbXB0eSgpKXthPW8uYXBwZW5kKCJsaW5lYXJHcmFkaWVudCIpLmF0dHIoImlkIixlKSxhLnNlbGVjdEFsbCgiKiIpLnJlbW92ZSgpO2xldCBzPTA7VmkuZWFjaCh0LGw9PntsZXQgYz1sLmNvbG9yO2EuYXBwZW5kKCJzdG9wIikuYXR0cigib2Zmc2V0IixzKS5hdHRyKCJzdG9wLWNvbG9yIixjKSxhLmFwcGVuZCgic3RvcCIpLmF0dHIoIm9mZnNldCIscytsLnByb3BvcnRpb24pLmF0dHIoInN0b3AtY29sb3IiLGMpLHMrPWwucHJvcG9ydGlvbn0pfXJldHVybmB1cmwoIyR7bn0pYH1mdW5jdGlvbiBJSChlKXtIdChlKS5zZWxlY3QoImRlZnMjX2dyYXBoLWdyYWRpZW50cyIpLnJlbW92ZSgpfWZ1bmN0aW9uIExIKGUsdCxyLG4saSl7bGV0IG89S3U7c3dpdGNoKGU9ZXx8KCgpPT4wKSx0KXtjYXNlIEduLk5PTkU6Y2FzZSBHbi5TVFJVQ1RVUkU6aWYoci5ub2RlLnR5cGU9PT1qdC5NRVRBKXtsZXQgYT1yLm5vZGUudGVtcGxhdGVJZDtyZXR1cm4gdD09PUduLlNUUlVDVFVSRSYmYSE9PW51bGw/by5TVFJVQ1RVUkVfUEFMRVRURShlKGEpLG4pOm8uVU5LTk9XTn1lbHNlIHJldHVybiByLm5vZGUudHlwZT09PWp0LlNFUklFUz9uP28uRVhQQU5ERURfQ09MT1I6IndoaXRlIjpyLm5vZGUudHlwZT09PWp0LkJSSURHRT9yLnN0cnVjdHVyYWw/IiNmMGUiOnIubm9kZS5pbmJvdW5kPyIjMGVmIjoiI2ZlMCI6VmkuaXNOdW1iZXIoci5ub2RlLmZ1bmN0aW9uSW5wdXRJbmRleCk/IiM3OTU1NDgiOlZpLmlzTnVtYmVyKHIubm9kZS5mdW5jdGlvbk91dHB1dEluZGV4KT8iIzAwOTY4OCI6IndoaXRlIjtjYXNlIEduLkRFVklDRTpyZXR1cm4gci5kZXZpY2VDb2xvcnM9PW51bGw/by5VTktOT1dOOm4/by5FWFBBTkRFRF9DT0xPUjpabHQoImRldmljZS0iK3Iubm9kZS5uYW1lLHIuZGV2aWNlQ29sb3JzLGkpO2Nhc2UgR24uWExBX0NMVVNURVI6cmV0dXJuIHIueGxhQ2x1c3RlckNvbG9ycz09bnVsbD9vLlVOS05PV046bj9vLkVYUEFOREVEX0NPTE9SOlpsdCgieGxhLSIrci5ub2RlLm5hbWUsci54bGFDbHVzdGVyQ29sb3JzLGkpO2Nhc2UgR24uQ09NUFVURV9USU1FOnJldHVybiBuP28uRVhQQU5ERURfQ09MT1I6ci5jb21wdXRlVGltZUNvbG9yfHxvLlVOS05PV047Y2FzZSBHbi5NRU1PUlk6cmV0dXJuIG4/by5FWFBBTkRFRF9DT0xPUjpyLm1lbW9yeUNvbG9yfHxvLlVOS05PV047Y2FzZSBHbi5PUF9DT01QQVRJQklMSVRZOnJldHVybiByLmNvbXBhdGliaWxpdHlDb2xvcnM9PW51bGw/by5VTktOT1dOOm4/by5FWFBBTkRFRF9DT0xPUjpabHQoIm9wLWNvbXBhdC0iK3Iubm9kZS5uYW1lLHIuY29tcGF0aWJpbGl0eUNvbG9ycyxpKTtkZWZhdWx0OnRocm93IG5ldyBFcnJvcigiVW5rbm93biBjYXNlIHRvIGNvbG9yIG5vZGVzIGJ5Iil9fWZ1bmN0aW9uIHMzKGUsdCxyLG4pe249bnx8eWUuTm9kZS5TSEFQRTtsZXQgaT1yLmlzTm9kZUhpZ2hsaWdodGVkKHQubm9kZS5uYW1lKSxvPXIuaXNOb2RlU2VsZWN0ZWQodC5ub2RlLm5hbWUpLGE9dC5pc0luRXh0cmFjdHx8dC5pc091dEV4dHJhY3R8fHQuaXNMaWJyYXJ5RnVuY3Rpb24scz10LmV4cGFuZGVkJiZuIT09eWUuQW5ub3RhdGlvbi5OT0RFLGw9dC5pc0ZhZGVkT3V0O2UuY2xhc3NlZCgiaGlnaGxpZ2h0ZWQiLGkpLGUuY2xhc3NlZCgic2VsZWN0ZWQiLG8pLGUuY2xhc3NlZCgiZXh0cmFjdCIsYSksZS5jbGFzc2VkKCJleHBhbmRlZCIscyksZS5jbGFzc2VkKCJmYWRlZCIsbCk7bGV0IGM9ZS5zZWxlY3QoIi4iK24rIiAuIit5ZS5Ob2RlLkNPTE9SX1RBUkdFVCksdT1MSChyLnRlbXBsYXRlSW5kZXgsci5jb2xvckJ5LHQscyxyLmdldEdyYXBoU3ZnUm9vdCgpKTtjLnN0eWxlKCJmaWxsIix1KSxjLnN0eWxlKCJzdHJva2UiLG8/bnVsbDplY3QodSkpfWZ1bmN0aW9uIGVjdChlKXtyZXR1cm4gZS5zdWJzdHJpbmcoMCwzKT09PSJ1cmwiP0t1LkdSQURJRU5UX09VVExJTkU6Y3UoZSkuZGFya2VyKCkudG9TdHJpbmcoKX1mdW5jdGlvbiBWbGUoZSx0LHIsbil7bGV0IGk9SHQoZSk7aWYoaS5zZWxlY3RBbGwoIi5pbnB1dC1oaWdobGlnaHQiKS5jbGFzc2VkKCJpbnB1dC1oaWdobGlnaHQiLCExKSxpLnNlbGVjdEFsbCgiLm5vbi1pbnB1dCIpLmNsYXNzZWQoIm5vbi1pbnB1dCIsITEpLGkuc2VsZWN0QWxsKCIuaW5wdXQtcGFyZW50IikuY2xhc3NlZCgiaW5wdXQtcGFyZW50IiwhMSksaS5zZWxlY3RBbGwoIi5pbnB1dC1jaGlsZCIpLmNsYXNzZWQoImlucHV0LWNoaWxkIiwhMSksaS5zZWxlY3RBbGwoIi5pbnB1dC1lZGdlLWhpZ2hsaWdodCIpLmNsYXNzZWQoImlucHV0LWVkZ2UtaGlnaGxpZ2h0IiwhMSksaS5zZWxlY3RBbGwoIi5ub24taW5wdXQtZWRnZS1oaWdobGlnaHQiKS5jbGFzc2VkKCJub24taW5wdXQtZWRnZS1oaWdobGlnaHQiLCExKSxpLnNlbGVjdEFsbCgiLmlucHV0LWhpZ2hsaWdodC1zZWxlY3RlZCIpLmNsYXNzZWQoImlucHV0LWhpZ2hsaWdodC1zZWxlY3RlZCIsITEpLCF0fHwhbnx8IXIpcmV0dXJuO2xldCBvPVVsZShyLHQpLGE9e307VmkuZWFjaChvLGZ1bmN0aW9uKGMpe2E9cWxlKGUsdCxjLGEpfSk7bGV0IHM9T2JqZWN0LmtleXMoYSksbD0kY3IodCxzKTtLY3IoZSxsKSxpLnNlbGVjdEFsbCgiZy5ub2RlOm5vdCguc2VsZWN0ZWQpOm5vdCguaW5wdXQtaGlnaGxpZ2h0KTpub3QoLmlucHV0LXBhcmVudCk6bm90KC5pbnB1dC1jaGlsZHJlbikiKS5jbGFzc2VkKCJub24taW5wdXQiLCEwKS5lYWNoKGZ1bmN0aW9uKGMpe2xldCB1PWMubm9kZS5uYW1lO2kuc2VsZWN0QWxsKGBbZGF0YS1uYW1lPSIke3V9Il1gKS5jbGFzc2VkKCJub24taW5wdXQiLCEwKX0pLGkuc2VsZWN0QWxsKCJnLmVkZ2U6bm90KC5pbnB1dC1lZGdlLWhpZ2hsaWdodCkiKS5jbGFzc2VkKCJub24taW5wdXQtZWRnZS1oaWdobGlnaHQiLCEwKX1mdW5jdGlvbiBVbGUoZSx0KXtsZXQgcj1bXSxuPXQuZ2V0Tm9kZUJ5TmFtZShlKTtpZihuIGluc3RhbmNlb2YgXzApcmV0dXJuW25dLmNvbmNhdChuLmluRW1iZWRkaW5ncyk7bGV0IGk9bi5tZXRhZ3JhcGgubm9kZXMoKTtyZXR1cm4gVmkuZWFjaChpLGZ1bmN0aW9uKG8pe3I9ci5jb25jYXQoVWxlKG8sdCkpfSkscn1mdW5jdGlvbiBxbGUoZSx0LHIsbil7aWYobltyLm5hbWVdKXJldHVybiBuO25bci5uYW1lXT0hMDtsZXQgaT1yLmlucHV0cyxvPUpsdCh0LHIpO0h0KGUpLnNlbGVjdChgLm5vZGVbZGF0YS1uYW1lPSIke28ubmFtZX0iXWApLmNsYXNzZWQoImlucHV0LWhpZ2hsaWdodCIsITApO2xldCBhPXt9O1ZpLmVhY2goaSxmdW5jdGlvbih1KXtsZXQgaD10LmdldE5vZGVCeU5hbWUodS5uYW1lKTtpZihoPT09dm9pZCAwKXJldHVybjtpZihoIGluc3RhbmNlb2Ygb1Ape2xldCBkPWFQKGgubmFtZSk7aD10LmdldE5vZGVCeU5hbWUoZCl9bGV0IGY9Smx0KHQsaCkscD1hW2YubmFtZV07cD9wLm9wTm9kZXMucHVzaChoKTphW2YubmFtZV09e3Zpc2libGVQYXJlbnQ6ZixvcE5vZGVzOltoXX19KTtsZXQgcz17fSxsPVtvXTtzW28ubmFtZV09e3RyYWNlZDohMSxpbmRleDowLGNvbm5lY3Rpb25FbmRwb2ludHM6W119O2xldCBjPW87Zm9yKGxldCB1PTE7Yy5uYW1lIT09cWM7dSsrKWM9Yy5wYXJlbnROb2RlLHNbYy5uYW1lXT17dHJhY2VkOiExLGluZGV4OnUsY29ubmVjdGlvbkVuZHBvaW50czpbXX0sbFt1XT1jO3JldHVybiBWaS5mb3JPd24oYSxmdW5jdGlvbih1LGgpe2xldCBmPXUudmlzaWJsZVBhcmVudDtWaS5lYWNoKHUub3BOb2RlcyxmdW5jdGlvbihwKXtuPXFsZShlLHQscCxuKX0pLGYubmFtZSE9PW8ubmFtZSYmWGNyKGUsZixzLGwpfSksbn1mdW5jdGlvbiBYY3IoZSx0LHIsbil7bGV0IGk9dCxvPXQsYT1bXTtmb3IoOyFyW2kubmFtZV07KW8ubmFtZSE9PWkubmFtZSYmYS5wdXNoKFtvLGldKSxvPWksaT1pLnBhcmVudE5vZGU7bGV0IHM9cltpLm5hbWVdLmluZGV4LGw9bltNYXRoLm1heChzLTEsMCldLm5hbWUsYz1sLHU9by5uYW1lLGg9by5uYW1lLGY9SHQoZSk7Zi5zZWxlY3RBbGwoYFtkYXRhLWVkZ2U9IiR7aH0tLSR7bH0iXWApLmNsYXNzZWQoImlucHV0LWVkZ2UtaGlnaGxpZ2h0IiwhMCksVmkuZWFjaChhLGZ1bmN0aW9uKHApe2xldCBkPXBbMF0sZz1wWzFdLF89YFtkYXRhLWVkZ2U9IiR7ZC5uYW1lfS0tJHtjfX5+JHtnLm5hbWV9fn5PVVQiXWA7Zi5zZWxlY3RBbGwoXykuY2xhc3NlZCgiaW5wdXQtZWRnZS1oaWdobGlnaHQiLCEwKX0pO2ZvcihsZXQgcD0xO3A8cztwKyspe2xldCBkPW5bcC0xXSxnPW5bcF0sXz1gW2RhdGEtZWRnZT0iJHt1fX5+JHtnLm5hbWV9fn5JTi0tJHtkLm5hbWV9Il1gO2Yuc2VsZWN0QWxsKF8pLmNsYXNzZWQoImlucHV0LWVkZ2UtaGlnaGxpZ2h0IiwhMCl9fWZ1bmN0aW9uICRjcihlLHQpe2xldCByPXt9O3JldHVybiBWaS5lYWNoKHQsZnVuY3Rpb24obil7bGV0IGk9ZS5nZXROb2RlQnlOYW1lKG4pLG89Smx0KGUsaSk7cltvLm5hbWVdPW99KSxyfWZ1bmN0aW9uIEtjcihlLHQpe1ZpLmZvck93bih0LGZ1bmN0aW9uKHIpe2xldCBuPXI7Zm9yKDtuLm5hbWUhPT1xYzspe2xldCBpPUh0KGUpLnNlbGVjdChgLm5vZGVbZGF0YS1uYW1lPSIke24ubmFtZX0iXWApO2kubm9kZXMoKS5sZW5ndGgmJiFpLmNsYXNzZWQoImlucHV0LWhpZ2hsaWdodCIpJiYhaS5jbGFzc2VkKCJzZWxlY3RlZCIpJiYhaS5jbGFzc2VkKCJvcCIpJiZpLmNsYXNzZWQoImlucHV0LXBhcmVudCIsITApLG49bi5wYXJlbnROb2RlfX0pfWZ1bmN0aW9uIEpsdChlLHQpe2xldCByPSExLG49dDtmb3IoOyFyOylpZih0PW4sbj10LnBhcmVudE5vZGUsbj09PXZvaWQgMClyPSEwO2Vsc2V7bGV0IGk9ZS5nZXRSZW5kZXJOb2RlQnlOYW1lKG4ubmFtZSk7aSYmKGkuZXhwYW5kZWR8fG4gaW5zdGFuY2VvZiBfMCkmJihyPSEwKX1yZXR1cm4gdH1mdW5jdGlvbiBPbGUoZSx0LHIsbil7bGV0IGk9ZS5zZWxlY3RBbGwoZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5jaGlsZE5vZGVzfSkuZGF0YSh0Lmxpc3Qsbz0+by5ub2RlLm5hbWUpO3JldHVybiBpLmVudGVyKCkuYXBwZW5kKCJnIikuYXR0cigiZGF0YS1uYW1lIixvPT5vLm5vZGUubmFtZSkuZWFjaChmdW5jdGlvbihvKXtsZXQgYT1IdCh0aGlzKTtuLmFkZEFubm90YXRpb25Hcm91cChvLHIsYSk7bGV0IHM9eWUuQW5ub3RhdGlvbi5FREdFLGw9by5yZW5kZXJNZXRhZWRnZUluZm8mJm8ucmVuZGVyTWV0YWVkZ2VJbmZvLm1ldGFlZGdlO2wmJiFsLm51bVJlZ3VsYXJFZGdlcyYmKHMrPSIgIit5ZS5Bbm5vdGF0aW9uLkNPTlRST0xfRURHRSksbCYmbC5udW1SZWZFZGdlcyYmKHMrPSIgIit5ZS5FZGdlLlJFRl9MSU5FKSwkbHQoYSxvLG4scyksby5hbm5vdGF0aW9uVHlwZSE9PV9pLkVMTElQU0lTPyhRY3IoYSxvKSxKY3IoYSxvKSk6R2xlKGEsby5ub2RlLm5hbWUsbyx5ZS5Bbm5vdGF0aW9uLkVMTElQU0lTKX0pLm1lcmdlKGkpLmF0dHIoImNsYXNzIixvPT55ZS5Bbm5vdGF0aW9uLkdST1VQKyIgIitaY3Ioby5hbm5vdGF0aW9uVHlwZSkrIiAiK0hsZShvKSkuZWFjaChmdW5jdGlvbihvKXtsZXQgYT1IdCh0aGlzKTtldXIoYSxyLG8sbiksby5hbm5vdGF0aW9uVHlwZSE9PV9pLkVMTElQU0lTJiZ0dXIoYSxyLG8sbil9KSxpLmV4aXQoKS5lYWNoKGZ1bmN0aW9uKG8pe24ucmVtb3ZlQW5ub3RhdGlvbkdyb3VwKG8scil9KS5yZW1vdmUoKSxpfWZ1bmN0aW9uIFpjcihlKXtyZXR1cm4oX2lbZV18fCIiKS50b0xvd2VyQ2FzZSgpfHxudWxsfWZ1bmN0aW9uIEpjcihlLHQpe2lmKHQuYW5ub3RhdGlvblR5cGU9PT1faS5TVU1NQVJZKVBuKGUsInVzZSIpLmF0dHIoImNsYXNzIiwic3VtbWFyeSIpLmF0dHIoInhsaW5rOmhyZWYiLCIjc3VtbWFyeS1pY29uIikuYXR0cigiY3Vyc29yIiwicG9pbnRlciIpO2Vsc2V7bGV0IHI9QmxlKGUsdCx5ZS5Bbm5vdGF0aW9uLk5PREUpO1BuKHIsInRpdGxlIikudGV4dCh0Lm5vZGUubmFtZSl9fWZ1bmN0aW9uIFFjcihlLHQpe2xldCByPXQubm9kZS5uYW1lLnNwbGl0KCIvIiksbj1yW3IubGVuZ3RoLTFdO3JldHVybiBHbGUoZSxuLHQsbnVsbCl9ZnVuY3Rpb24gR2xlKGUsdCxyLG4pe2xldCBpPXllLkFubm90YXRpb24uTEFCRUw7biYmKGkrPSIgIituKTtsZXQgbz1lLmFwcGVuZCgidGV4dCIpLmF0dHIoImNsYXNzIixpKS5hdHRyKCJkeSIsIi4zNWVtIikuYXR0cigidGV4dC1hbmNob3IiLHIuaXNJbj8iZW5kIjoic3RhcnQiKS50ZXh0KHQpO3JldHVybiBGbGUobywtMSxaNC5Bbm5vdGF0aW9uLkxBQkVMKX1mdW5jdGlvbiB0dXIoZSx0LHIsbil7ZS5vbigibW91c2VvdmVyIixpPT57bi5maXJlKCJhbm5vdGF0aW9uLWhpZ2hsaWdodCIse25hbWU6aS5ub2RlLm5hbWUsaG9zdE5hbWU6dC5ub2RlLm5hbWV9KX0pLm9uKCJtb3VzZW91dCIsaT0+e24uZmlyZSgiYW5ub3RhdGlvbi11bmhpZ2hsaWdodCIse25hbWU6aS5ub2RlLm5hbWUsaG9zdE5hbWU6dC5ub2RlLm5hbWV9KX0pLm9uKCJjbGljayIsaT0+e3F0LnN0b3BQcm9wYWdhdGlvbigpLG4uZmlyZSgiYW5ub3RhdGlvbi1zZWxlY3QiLHtuYW1lOmkubm9kZS5uYW1lLGhvc3ROYW1lOnQubm9kZS5uYW1lfSl9KSxyLmFubm90YXRpb25UeXBlIT09X2kuU1VNTUFSWSYmci5hbm5vdGF0aW9uVHlwZSE9PV9pLkNPTlNUQU5UJiZlLm9uKCJjb250ZXh0bWVudSIsWWx0KG4semxlKHIubm9kZSxuKSkpfWZ1bmN0aW9uIGV1cihlLHQscixuKXtsZXQgaT12MCh0KTtyLnJlbmRlck5vZGVJbmZvJiZyLmFubm90YXRpb25UeXBlIT09X2kuRUxMSVBTSVMmJnMzKGUsci5yZW5kZXJOb2RlSW5mbyxuLHllLkFubm90YXRpb24uTk9ERSksci5hbm5vdGF0aW9uVHlwZT09PV9pLlNVTU1BUlkmJihyLndpZHRoKz0xMCksZS5zZWxlY3QoInRleHQuIit5ZS5Bbm5vdGF0aW9uLkxBQkVMKS50cmFuc2l0aW9uKCkuYXR0cigieCIsaStyLmR4KyhyLmlzSW4/LTE6MSkqKHIud2lkdGgvMityLmxhYmVsT2Zmc2V0KSkuYXR0cigieSIsdC55K3IuZHkpLGUuc2VsZWN0KCJ1c2Uuc3VtbWFyeSIpLnRyYW5zaXRpb24oKS5hdHRyKCJ4IixpK3IuZHgtMykuYXR0cigieSIsdC55K3IuZHktNiksTUgoZS5zZWxlY3QoIi4iK3llLkFubm90YXRpb24uTk9ERSsiIGVsbGlwc2UiKSxpK3IuZHgsdC55K3IuZHksci53aWR0aCxyLmhlaWdodCksemQoZS5zZWxlY3QoIi4iK3llLkFubm90YXRpb24uTk9ERSsiIHJlY3QiKSxpK3IuZHgsdC55K3IuZHksci53aWR0aCxyLmhlaWdodCksemQoZS5zZWxlY3QoIi4iK3llLkFubm90YXRpb24uTk9ERSsiIHVzZSIpLGkrci5keCx0Lnkrci5keSxyLndpZHRoLHIuaGVpZ2h0KSxlLnNlbGVjdCgicGF0aC4iK3llLkFubm90YXRpb24uRURHRSkudHJhbnNpdGlvbigpLmF0dHIoImQiLG89PntsZXQgYT1vLnBvaW50cy5tYXAocz0+KHt4OnMuZHgraSx5OnMuZHkrdC55fSkpO3JldHVybiBUSChhKX0pfWZ1bmN0aW9uIHJjdChlLHQscixuKXtuPW58fHllLlNjZW5lLkdST1VQO2xldCBpPW0wKGUsImciLG4pLmVtcHR5KCksbz1QbihlLCJnIixuKSxhPVBuKG8sImciLHllLlNjZW5lLkNPUkUpLHM9VmkucmVkdWNlKHQuY29yZUdyYXBoLm5vZGVzKCksKGwsYyk9PntsZXQgdT10LmNvcmVHcmFwaC5ub2RlKGMpO3JldHVybiB1LmV4Y2x1ZGVkfHxsLnB1c2godSksbH0sQXJyYXkoKSk7aWYodC5ub2RlLnR5cGU9PT1qdC5TRVJJRVMmJnMucmV2ZXJzZSgpLGtsZShhLHQuY29yZUdyYXBoLHIpLEFIKGEscyxyKSx0Lmlzb2xhdGVkSW5FeHRyYWN0Lmxlbmd0aD4wKXtsZXQgbD1QbihvLCJnIix5ZS5TY2VuZS5JTkVYVFJBQ1QpO0FIKGwsdC5pc29sYXRlZEluRXh0cmFjdCxyKX1lbHNlIG0wKG8sImciLHllLlNjZW5lLklORVhUUkFDVCkucmVtb3ZlKCk7aWYodC5pc29sYXRlZE91dEV4dHJhY3QubGVuZ3RoPjApe2xldCBsPVBuKG8sImciLHllLlNjZW5lLk9VVEVYVFJBQ1QpO0FIKGwsdC5pc29sYXRlZE91dEV4dHJhY3Qscil9ZWxzZSBtMChvLCJnIix5ZS5TY2VuZS5PVVRFWFRSQUNUKS5yZW1vdmUoKTtpZih0LmxpYnJhcnlGdW5jdGlvbnNFeHRyYWN0Lmxlbmd0aD4wKXtsZXQgbD1QbihvLCJnIix5ZS5TY2VuZS5GVU5DVElPTl9MSUJSQVJZKTtBSChsLHQubGlicmFyeUZ1bmN0aW9uc0V4dHJhY3Qscil9ZWxzZSBtMChvLCJnIix5ZS5TY2VuZS5GVU5DVElPTl9MSUJSQVJZKS5yZW1vdmUoKTtyZXR1cm4gTWxlKG8sdCksaSYmby5hdHRyKCJvcGFjaXR5IiwwKS50cmFuc2l0aW9uKCkuYXR0cigib3BhY2l0eSIsMSksb312YXIgcnVyPS44LGtIPWNsYXNze2NvbnN0cnVjdG9yKHQscixuLGksbyxhKXt0aGlzLnN2Zz10LHRoaXMubGFiZWxQYWRkaW5nPWEsdGhpcy56b29tRz1yLHRoaXMubWFpblpvb209bix0aGlzLm1heFdhbmRIPW87bGV0IHM9SHQoaS5zaGFkb3dSb290KSxsPXMuc2VsZWN0KCJzdmciKSxjPWwuc2VsZWN0KCJyZWN0IiksdT1mPT57dGhpcy52aWV3cG9pbnRDb29yZC54PXF0LngsdGhpcy52aWV3cG9pbnRDb29yZC55PXF0LnksdGhpcy51cGRhdGVWaWV3cG9pbnQoKX07dGhpcy52aWV3cG9pbnRDb29yZD17eDowLHk6MH07bGV0IGg9cGIoKS5zdWJqZWN0KE9iamVjdCkub24oImRyYWciLHUpO2MuZGF0dW0odGhpcy52aWV3cG9pbnRDb29yZCkuY2FsbChoKSxsLm9uKCJjbGljayIsKCk9PntpZihxdC5kZWZhdWx0UHJldmVudGVkKXJldHVybjtsZXQgZj1OdW1iZXIoYy5hdHRyKCJ3aWR0aCIpKSxwPU51bWJlcihjLmF0dHIoImhlaWdodCIpKSxkPXpvKGwubm9kZSgpKTt0aGlzLnZpZXdwb2ludENvb3JkLng9ZFswXS1mLzIsdGhpcy52aWV3cG9pbnRDb29yZC55PWRbMV0tcC8yLHRoaXMudXBkYXRlVmlld3BvaW50KCl9KSx0aGlzLnZpZXdwb2ludD1jLm5vZGUoKSx0aGlzLm1pbmltYXBTdmc9bC5ub2RlKCksdGhpcy5taW5pbWFwPWksdGhpcy5jYW52YXM9cy5zZWxlY3QoImNhbnZhcy5maXJzdCIpLm5vZGUoKSx0aGlzLmNhbnZhc0J1ZmZlcj1zLnNlbGVjdCgiY2FudmFzLnNlY29uZCIpLm5vZGUoKSx0aGlzLmRvd25sb2FkQ2FudmFzPXMuc2VsZWN0KCJjYW52YXMuZG93bmxvYWQiKS5ub2RlKCksSHQodGhpcy5kb3dubG9hZENhbnZhcykuc3R5bGUoImRpc3BsYXkiLCJub25lIiksdGhpcy51cGRhdGUoKX11cGRhdGVWaWV3cG9pbnQoKXtIdCh0aGlzLnZpZXdwb2ludCkuYXR0cigieCIsdGhpcy52aWV3cG9pbnRDb29yZC54KS5hdHRyKCJ5Iix0aGlzLnZpZXdwb2ludENvb3JkLnkpO2xldCB0PS10aGlzLnZpZXdwb2ludENvb3JkLngqdGhpcy5zY2FsZU1haW4vdGhpcy5zY2FsZU1pbmltYXAscj0tdGhpcy52aWV3cG9pbnRDb29yZC55KnRoaXMuc2NhbGVNYWluL3RoaXMuc2NhbGVNaW5pbWFwO0h0KHRoaXMuc3ZnKS5jYWxsKHRoaXMubWFpblpvb20udHJhbnNmb3JtLFhoLnRyYW5zbGF0ZSh0LHIpLnNjYWxlKHRoaXMuc2NhbGVNYWluKSl9Z2V0SW1hZ2VCbG9iKCl7cmV0dXJuIG5ldyBQcm9taXNlKHQ9Pnt0aGlzLmRvd25sb2FkQ2FudmFzLnRvQmxvYihyPT57dChyKX0sImltYWdlL3BuZyIpfSl9dXBkYXRlKCl7bGV0IHQ9bnVsbDt0cnl7aWYodD10aGlzLnpvb21HLmdldEJCb3goKSx0LndpZHRoPT09MClyZXR1cm59Y2F0Y2gocCl7cmV0dXJufWxldCByPUh0KHRoaXMuc3ZnKSxuPSIiLGk9dGhpcy5zdmcsYT0oaS5nZXRSb290Tm9kZT9pLmdldFJvb3ROb2RlKCk6dGhpcy5zdmcucGFyZW50Tm9kZSkuc3R5bGVTaGVldHM7Zm9yKGxldCBwPTA7cDxhLmxlbmd0aDtwKyspdHJ5e2xldCBkPWFbcF0uY3NzUnVsZXN8fGFbcF0ucnVsZXM7aWYoZD09bnVsbCljb250aW51ZTtmb3IobGV0IGc9MDtnPGQubGVuZ3RoO2crKyluKz1kW2ddLmNzc1RleHQucmVwbGFjZSgvID90Zi1bXHctXSsgPy9nLCIiKStgCmB9Y2F0Y2goZCl7aWYoZC5uYW1lIT09IlNlY3VyaXR5RXJyb3IiKXRocm93IGR9bGV0IHM9ci5hcHBlbmQoInN0eWxlIik7cy50ZXh0KG4pO2xldCBsPUh0KHRoaXMuem9vbUcpLGM9bC5hdHRyKCJ0cmFuc2Zvcm0iKTtsLmF0dHIoInRyYW5zZm9ybSIsbnVsbCksdC5oZWlnaHQrPXQueSx0LndpZHRoKz10LngsdC5oZWlnaHQrPXRoaXMubGFiZWxQYWRkaW5nKjIsdC53aWR0aCs9dGhpcy5sYWJlbFBhZGRpbmcqMixyLmF0dHIoIndpZHRoIix0LndpZHRoKS5hdHRyKCJoZWlnaHQiLHQuaGVpZ2h0KSx0aGlzLnNjYWxlTWluaW1hcD10aGlzLm1heFdhbmRIL01hdGgubWF4KHQud2lkdGgsdC5oZWlnaHQpLHRoaXMubWluaW1hcFNpemU9e3dpZHRoOnQud2lkdGgqdGhpcy5zY2FsZU1pbmltYXAsaGVpZ2h0OnQuaGVpZ2h0KnRoaXMuc2NhbGVNaW5pbWFwfSxIdCh0aGlzLm1pbmltYXBTdmcpLmF0dHIodGhpcy5taW5pbWFwU2l6ZSksSHQodGhpcy5jYW52YXNCdWZmZXIpLmF0dHIodGhpcy5taW5pbWFwU2l6ZSk7bGV0IHU9SHQodGhpcy5kb3dubG9hZENhbnZhcyk7dS5zdHlsZSgid2lkdGgiLHQud2lkdGgpLHUuc3R5bGUoImhlaWdodCIsdC5oZWlnaHQpLHUuYXR0cigid2lkdGgiLDMqdC53aWR0aCksdS5hdHRyKCJoZWlnaHQiLDMqdC5oZWlnaHQpLHRoaXMudHJhbnNsYXRlIT1udWxsJiZ0aGlzLnpvb20hPW51bGwmJnJlcXVlc3RBbmltYXRpb25GcmFtZSgoKT0+dGhpcy56b29tKCkpO2xldCBoPW5ldyBYTUxTZXJpYWxpemVyKCkuc2VyaWFsaXplVG9TdHJpbmcodGhpcy5zdmcpO3MucmVtb3ZlKCksci5hdHRyKCJ3aWR0aCIsbnVsbCkuYXR0cigiaGVpZ2h0IixudWxsKSxsLmF0dHIoInRyYW5zZm9ybSIsYyk7bGV0IGY9bmV3IEltYWdlO2Yub25sb2FkPSgpPT57bGV0IHA9dGhpcy5jYW52YXNCdWZmZXIuZ2V0Q29udGV4dCgiMmQiKTtwPT1udWxsfHxwLmNsZWFyUmVjdCgwLDAsdGhpcy5jYW52YXNCdWZmZXIud2lkdGgsdGhpcy5jYW52YXNCdWZmZXIuaGVpZ2h0KSxwPT1udWxsfHxwLmRyYXdJbWFnZShmLDAsMCx0aGlzLm1pbmltYXBTaXplLndpZHRoLHRoaXMubWluaW1hcFNpemUuaGVpZ2h0KSxyZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCk9PntIdCh0aGlzLmNhbnZhc0J1ZmZlcikuc3R5bGUoImRpc3BsYXkiLG51bGwpLEh0KHRoaXMuY2FudmFzKS5zdHlsZSgiZGlzcGxheSIsIm5vbmUiKSxbdGhpcy5jYW52YXMsdGhpcy5jYW52YXNCdWZmZXJdPVt0aGlzLmNhbnZhc0J1ZmZlcix0aGlzLmNhbnZhc119KTtsZXQgZD10aGlzLmRvd25sb2FkQ2FudmFzLmdldENvbnRleHQoIjJkIik7ZD09bnVsbHx8ZC5jbGVhclJlY3QoMCwwLHRoaXMuZG93bmxvYWRDYW52YXMud2lkdGgsdGhpcy5kb3dubG9hZENhbnZhcy5oZWlnaHQpLGQ9PW51bGx8fGQuZHJhd0ltYWdlKGYsMCwwLHRoaXMuZG93bmxvYWRDYW52YXMud2lkdGgsdGhpcy5kb3dubG9hZENhbnZhcy5oZWlnaHQpfSxmLm9uZXJyb3I9KCk9PntsZXQgcD1uZXcgQmxvYihbaF0se3R5cGU6ImltYWdlL3N2Zyt4bWw7Y2hhcnNldD11dGYtOCJ9KTtmLnNyYz1VUkwuY3JlYXRlT2JqZWN0VVJMKHApfSxmLnNyYz0iZGF0YTppbWFnZS9zdmcreG1sO2NoYXJzZXQ9dXRmLTgsIitlbmNvZGVVUklDb21wb25lbnQoaCl9em9vbSh0KXtpZih0aGlzLnNjYWxlTWluaW1hcD09bnVsbClyZXR1cm47dCYmKHRoaXMudHJhbnNsYXRlPVt0LngsdC55XSx0aGlzLnNjYWxlTWFpbj10LmspO2xldCByPXRoaXMuc3ZnLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLG49SHQodGhpcy52aWV3cG9pbnQpO3RoaXMudmlld3BvaW50Q29vcmQueD0tdGhpcy50cmFuc2xhdGVbMF0qdGhpcy5zY2FsZU1pbmltYXAvdGhpcy5zY2FsZU1haW4sdGhpcy52aWV3cG9pbnRDb29yZC55PS10aGlzLnRyYW5zbGF0ZVsxXSp0aGlzLnNjYWxlTWluaW1hcC90aGlzLnNjYWxlTWFpbjtsZXQgaT1yLndpZHRoKnRoaXMuc2NhbGVNaW5pbWFwL3RoaXMuc2NhbGVNYWluLG89ci5oZWlnaHQqdGhpcy5zY2FsZU1pbmltYXAvdGhpcy5zY2FsZU1haW47bi5hdHRyKCJ4Iix0aGlzLnZpZXdwb2ludENvb3JkLngpLmF0dHIoInkiLHRoaXMudmlld3BvaW50Q29vcmQueSkuYXR0cigid2lkdGgiLGkpLmF0dHIoImhlaWdodCIsbyk7bGV0IGE9dGhpcy5taW5pbWFwU2l6ZS53aWR0aCxzPXRoaXMubWluaW1hcFNpemUuaGVpZ2h0LGw9dGhpcy52aWV3cG9pbnRDb29yZC54LGM9dGhpcy52aWV3cG9pbnRDb29yZC55LHU9TWF0aC5taW4oTWF0aC5tYXgoMCxsK2kpLGEpLU1hdGgubWluKE1hdGgubWF4KDAsbCksYSksaD1NYXRoLm1pbihNYXRoLm1heCgwLGMrbykscyktTWF0aC5taW4oTWF0aC5tYXgoMCxjKSxzKTt1KmgvKGEqcyk8cnVyP3RoaXMubWluaW1hcC5jbGFzc0xpc3QucmVtb3ZlKCJoaWRkZW4iKTp0aGlzLm1pbmltYXAuY2xhc3NMaXN0LmFkZCgiaGlkZGVuIil9fTt2YXIgaWN0PWNsYXNzIGV4dGVuZHMgbXR7aW5pdCh0LHIsbixpLG8pe3JldHVybiBuZXcga0godCxyLG4sdGhpcyxpLG8pfX07aWN0LnRlbXBsYXRlPVFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB3aGl0ZTsKICAgICAgICB0cmFuc2l0aW9uOiBvcGFjaXR5IDAuM3MgbGluZWFyOwogICAgICAgIHBvaW50ZXItZXZlbnRzOiBhdXRvOwogICAgICB9CgogICAgICA6aG9zdCguaGlkZGVuKSB7CiAgICAgICAgb3BhY2l0eTogMDsKICAgICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgfQoKICAgICAgY2FudmFzIHsKICAgICAgICBib3JkZXI6IDFweCBzb2xpZCAjOTk5OwogICAgICB9CgogICAgICByZWN0IHsKICAgICAgICBmaWxsOiB3aGl0ZTsKICAgICAgICBzdHJva2U6ICMxMTExMTE7CiAgICAgICAgc3Ryb2tlLXdpZHRoOiAxcHg7CiAgICAgICAgZmlsbC1vcGFjaXR5OiAwOwogICAgICAgIGZpbHRlcjogdXJsKCNtaW5pbWFwRHJvcFNoYWRvdyk7CiAgICAgICAgY3Vyc29yOiBtb3ZlOwogICAgICB9CgogICAgICBzdmcgewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxzdmc+CiAgICAgIDxkZWZzPgogICAgICAgIDxmaWx0ZXIKICAgICAgICAgIGlkPSJtaW5pbWFwRHJvcFNoYWRvdyIKICAgICAgICAgIHg9Ii0yMCUiCiAgICAgICAgICB5PSItMjAlIgogICAgICAgICAgd2lkdGg9IjE1MCUiCiAgICAgICAgICBoZWlnaHQ9IjE1MCUiCiAgICAgICAgPgogICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlR3JhcGhpYyIgZHg9IjEiIGR5PSIxIj48L2ZlT2Zmc2V0PgogICAgICAgICAgPGZlQ29sb3JNYXRyaXgKICAgICAgICAgICAgcmVzdWx0PSJtYXRyaXhPdXQiCiAgICAgICAgICAgIGluPSJvZmZPdXQiCiAgICAgICAgICAgIHR5cGU9Im1hdHJpeCIKICAgICAgICAgICAgdmFsdWVzPSIwLjEgMCAwIDAgMCAwIDAuMSAwIDAgMCAwIDAgMC4xIDAgMCAwIDAgMCAwLjUgMCIKICAgICAgICAgID48L2ZlQ29sb3JNYXRyaXg+CiAgICAgICAgICA8ZmVHYXVzc2lhbkJsdXIKICAgICAgICAgICAgcmVzdWx0PSJibHVyT3V0IgogICAgICAgICAgICBpbj0ibWF0cml4T3V0IgogICAgICAgICAgICBzdGREZXZpYXRpb249IjIiCiAgICAgICAgICA+PC9mZUdhdXNzaWFuQmx1cj4KICAgICAgICAgIDxmZUJsZW5kIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImJsdXJPdXQiIG1vZGU9Im5vcm1hbCI+PC9mZUJsZW5kPgogICAgICAgIDwvZmlsdGVyPgogICAgICA8L2RlZnM+CiAgICAgIDxyZWN0PjwvcmVjdD4KICAgIDwvc3ZnPgogICAgPGNhbnZhcyBjbGFzcz0iZmlyc3QiPjwvY2FudmFzPgogICAgPCEtLSBBZGRpdGlvbmFsIGNhbnZhcyB0byB1c2UgYXMgYnVmZmVyIHRvIGF2b2lkIGZsaWNrZXJpbmcgYmV0d2VlbiB1cGRhdGVzIC0tPgogICAgPGNhbnZhcyBjbGFzcz0ic2Vjb25kIj48L2NhbnZhcz4KICAgIDxjYW52YXMgY2xhc3M9ImRvd25sb2FkIj48L2NhbnZhcz4KICBgO2ljdD1FKFt5dCgidGYtZ3JhcGgtbWluaW1hcCIpXSxpY3QpO3ZhciBXbGU9UWAKICA8c3R5bGU+CiAgICA6aG9zdCguZGFyay1tb2RlKSB7CiAgICAgIGZpbHRlcjogaW52ZXJ0KDEpOwogICAgfQoKICAgIDpob3N0IHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAgZm9udC1zaXplOiAyMHB4OwogICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgIHdpZHRoOiAxMDAlOwogICAgfQoKICAgICNzdmcgewogICAgICBmbGV4OiAxOwogICAgICBmb250LWZhbWlseTogUm9ib3RvLCBzYW5zLXNlcmlmOwogICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgIHdpZHRoOiAxMDAlOwogICAgfQoKICAgICNoaWRkZW4gewogICAgICBwb3NpdGlvbjogZml4ZWQ7CiAgICAgIHRvcDogMHB4OwogICAgICB2aXNpYmlsaXR5OiBoaWRkZW47CiAgICB9CgogICAgdGV4dCB7CiAgICAgIHVzZXItc2VsZWN0OiBub25lOwogICAgfQoKICAgIC8qIC0tLSBOb2RlIGFuZCBhbm5vdGF0aW9uLW5vZGUgZm9yIE1ldGFub2RlIC0tLSAqLwoKICAgIC5tZXRhID4gLm5vZGVzaGFwZSA+IHJlY3QsCiAgICAubWV0YSA+IC5hbm5vdGF0aW9uLW5vZGUgPiByZWN0IHsKICAgICAgY3Vyc29yOiBwb2ludGVyOwogICAgICBmaWxsOiBoc2woMCwgMCUsIDcwJSk7CiAgICB9CiAgICAubm9kZS5tZXRhLmhpZ2hsaWdodGVkID4gLm5vZGVzaGFwZSA+IHJlY3QsCiAgICAubm9kZS5tZXRhLmhpZ2hsaWdodGVkID4gLmFubm90YXRpb24tbm9kZSA+IHJlY3QgewogICAgICBzdHJva2Utd2lkdGg6IDI7CiAgICB9CiAgICAuYW5ub3RhdGlvbi5tZXRhLmhpZ2hsaWdodGVkID4gLm5vZGVzaGFwZSA+IHJlY3QsCiAgICAuYW5ub3RhdGlvbi5tZXRhLmhpZ2hsaWdodGVkID4gLmFubm90YXRpb24tbm9kZSA+IHJlY3QgewogICAgICBzdHJva2Utd2lkdGg6IDE7CiAgICB9CiAgICAubWV0YS5zZWxlY3RlZCA+IC5ub2Rlc2hhcGUgPiByZWN0LAogICAgLm1ldGEuc2VsZWN0ZWQgPiAuYW5ub3RhdGlvbi1ub2RlID4gcmVjdCB7CiAgICAgIHN0cm9rZTogcmVkOwogICAgICBzdHJva2Utd2lkdGg6IDI7CiAgICB9CiAgICAubm9kZS5tZXRhLnNlbGVjdGVkLmV4cGFuZGVkID4gLm5vZGVzaGFwZSA+IHJlY3QsCiAgICAubm9kZS5tZXRhLnNlbGVjdGVkLmV4cGFuZGVkID4gLmFubm90YXRpb24tbm9kZSA+IHJlY3QgewogICAgICBzdHJva2U6IHJlZDsKICAgICAgc3Ryb2tlLXdpZHRoOiAzOwogICAgfQogICAgLmFubm90YXRpb24ubWV0YS5zZWxlY3RlZCA+IC5ub2Rlc2hhcGUgPiByZWN0LAogICAgLmFubm90YXRpb24ubWV0YS5zZWxlY3RlZCA+IC5hbm5vdGF0aW9uLW5vZGUgPiByZWN0IHsKICAgICAgc3Ryb2tlOiByZWQ7CiAgICAgIHN0cm9rZS13aWR0aDogMjsKICAgIH0KICAgIC5ub2RlLm1ldGEuc2VsZWN0ZWQuZXhwYW5kZWQuaGlnaGxpZ2h0ZWQgPiAubm9kZXNoYXBlID4gcmVjdCwKICAgIC5ub2RlLm1ldGEuc2VsZWN0ZWQuZXhwYW5kZWQuaGlnaGxpZ2h0ZWQgPiAuYW5ub3RhdGlvbi1ub2RlID4gcmVjdCB7CiAgICAgIHN0cm9rZTogcmVkOwogICAgICBzdHJva2Utd2lkdGg6IDQ7CiAgICB9CgogICAgLmZhZGVkLAogICAgLmZhZGVkIHJlY3QsCiAgICAuZmFkZWQgZWxsaXBzZSwKICAgIC5mYWRlZCBwYXRoLAogICAgLmZhZGVkIHVzZSwKICAgICNyZWN0SGF0Y2ggbGluZSwKICAgICNlbGxpcHNlSGF0Y2ggbGluZSB7CiAgICAgIGNvbG9yOiAjZTBkNGIzICFpbXBvcnRhbnQ7CiAgICAgIGZpbGw6IHdoaXRlOwogICAgICBzdHJva2U6ICNlMGQ0YjMgIWltcG9ydGFudDsKICAgIH0KCiAgICAuZmFkZWQgcGF0aCB7CiAgICAgIHN0cm9rZS13aWR0aDogMXB4ICFpbXBvcnRhbnQ7CiAgICB9CgogICAgLmZhZGVkIHJlY3QgewogICAgICBmaWxsOiB1cmwoI3JlY3RIYXRjaCkgIWltcG9ydGFudDsKICAgIH0KCiAgICAuZmFkZWQgZWxsaXBzZSwKICAgIC5mYWRlZCB1c2UgewogICAgICBmaWxsOiB1cmwoI2VsbGlwc2VIYXRjaCkgIWltcG9ydGFudDsKICAgIH0KCiAgICAuZmFkZWQgdGV4dCB7CiAgICAgIG9wYWNpdHk6IDA7CiAgICB9CgogICAgLyogUnVsZXMgdXNlZCBmb3IgaW5wdXQtdHJhY2luZy4gKi8KICAgIC5pbnB1dC1oaWdobGlnaHQgPiAqID4gcmVjdCwKICAgIC5pbnB1dC1oaWdobGlnaHQgPiAqID4gZWxsaXBzZSwKICAgIC5pbnB1dC1oaWdobGlnaHQgPiAqID4gdXNlIHsKICAgICAgZmlsbDogd2hpdGU7CiAgICAgIHN0cm9rZTogI2ZmOTgwMCAhaW1wb3J0YW50OwogICAgfQoKICAgIC8qICAtIEZhZGVkIG5vbi1pbnB1dCBzdHlsaW5nICovCiAgICAubm9uLWlucHV0ID4gKiA+IHJlY3QsCi5ub24taW5wdXQgPiAqID4gZWxsaXBzZSwKLm5vbi1pbnB1dCA+ICogPiB1c2UsCi8qIEZvciBDb25zdCBub2Rlcy4gKi8KLm5vbi1pbnB1dCA+ICogPiAuY29uc3RhbnQ6bm90KFtjbGFzcyo9ImlucHV0LWhpZ2hsaWdodCJdKSA+CiAgLmFubm90YXRpb24tbm9kZSA+IGVsbGlwc2UsCi8qIEZvciBzdHlsaW5nIG9mIGFubm90YXRpb24gbm9kZXMgb2Ygbm9uLWlucHV0IG5vZGVzLiAqLwoubm9uLWlucHV0ID4gZyA+IC5hbm5vdGF0aW9uID4gLmFubm90YXRpb24tbm9kZSA+IHJlY3QgewogICAgICBzdHJva2U6ICNlMGQ0YjMgIWltcG9ydGFudDsKICAgICAgc3Ryb2tlLXdpZHRoOiBpbmhlcml0OwogICAgICBzdHJva2UtZGFzaGFycmF5OiBpbmhlcml0OwogICAgfQoKICAgIC5ub24taW5wdXQgcGF0aCB7CiAgICAgIHZpc2liaWxpdHk6IGhpZGRlbjsKICAgIH0KCiAgICAubm9uLWlucHV0ID4gLm5vZGVzaGFwZSA+IHJlY3QsCi5ub24taW5wdXQgPiAuYW5ub3RhdGlvbi1ub2RlID4gcmVjdCwKLyogRm9yIHN0eWxpbmcgb2YgYW5ub3RhdGlvbiBub2RlcyBvZiBub24taW5wdXQgbm9kZXMuICovCi5ub24taW5wdXQgPiBnID4gLmFubm90YXRpb24gPiAuYW5ub3RhdGlvbi1ub2RlID4gcmVjdCB7CiAgICAgIGZpbGw6IHVybCgjcmVjdEhhdGNoKSAhaW1wb3J0YW50OwogICAgfQoKICAgIC5ub24taW5wdXQgZWxsaXBzZSwKICAgIC5ub24taW5wdXQgdXNlIHsKICAgICAgZmlsbDogdXJsKCNlbGxpcHNlSGF0Y2gpICFpbXBvcnRhbnQ7CiAgICB9CgogICAgLm5vbi1pbnB1dCA+IHRleHQgewogICAgICBvcGFjaXR5OiAwOwogICAgfQoKICAgIC5ub24taW5wdXQgLmFubm90YXRpb24gPiAuYW5ub3RhdGlvbi1lZGdlIHsKICAgICAgbWFya2VyLWVuZDogdXJsKCNhbm5vdGF0aW9uLWFycm93aGVhZC1mYWRlZCk7CiAgICB9CgogICAgLm5vbi1pbnB1dCAuYW5ub3RhdGlvbiA+IC5hbm5vdGF0aW9uLWVkZ2UucmVmbGluZSB7CiAgICAgIG1hcmtlci1zdGFydDogdXJsKCNyZWYtYW5ub3RhdGlvbi1hcnJvd2hlYWQtZmFkZWQpOwogICAgfQoKICAgIC8qIElucHV0IGVkZ2VzLiAqLwogICAgLmlucHV0LWVkZ2UtaGlnaGxpZ2h0ID4gdGV4dCB7CiAgICAgIGZpbGw6IGJsYWNrICFpbXBvcnRhbnQ7CiAgICB9CiAgICAuaW5wdXQtaGlnaGxpZ2h0ID4gLmluLWFubm90YXRpb25zID4gLmFubm90YXRpb24gPiAuYW5ub3RhdGlvbi1lZGdlLAogICAgLmlucHV0LWhpZ2hsaWdodC1zZWxlY3RlZAogICAgICA+IC5pbi1hbm5vdGF0aW9ucwogICAgICA+IC5hbm5vdGF0aW9uCiAgICAgID4gLmFubm90YXRpb24tZWRnZSB7CiAgICAgIHN0cm9rZTogIzk5OSAhaW1wb3J0YW50OwogICAgfQoKICAgIC8qIE5vbi1pbnB1dCBlZGdlcy4gKi8KICAgIC5ub24taW5wdXQtZWRnZS1oaWdobGlnaHQsCi5ub24taW5wdXQgPiBnID4gLmFubm90YXRpb24gPiBwYXRoLAovKiBBbm5vdGF0aW9uIHN0eWxlcyAobGFiZWwgYW5kIGVkZ2VzIHJlc3BlY3RpdmVseSkuICovCi5ub24taW5wdXQgPiBnID4KLmFubm90YXRpb246bm90KC5pbnB1dC1oaWdobGlnaHQpOm5vdCguaW5wdXQtaGlnaGxpZ2h0LXNlbGVjdGVkKSA+Ci5hbm5vdGF0aW9uLWxhYmVsCi8qLmFubm90YXRpb24tZWRnZSovIHsKICAgICAgdmlzaWJpbGl0eTogaGlkZGVuOwogICAgfQoKICAgIC8qIC0tLSBPcCBOb2RlIC0tLSAqLwoKICAgIC5vcCA+IC5ub2Rlc2hhcGUgPiAubm9kZWNvbG9ydGFyZ2V0LAogICAgLm9wID4gLmFubm90YXRpb24tbm9kZSA+IC5ub2RlY29sb3J0YXJnZXQgewogICAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICAgIGZpbGw6ICNmZmY7CiAgICAgIHN0cm9rZTogI2NjYzsKICAgIH0KCiAgICAub3Auc2VsZWN0ZWQgPiAubm9kZXNoYXBlID4gLm5vZGVjb2xvcnRhcmdldCwKICAgIC5vcC5zZWxlY3RlZCA+IC5hbm5vdGF0aW9uLW5vZGUgPiAubm9kZWNvbG9ydGFyZ2V0IHsKICAgICAgc3Ryb2tlOiByZWQ7CiAgICAgIHN0cm9rZS13aWR0aDogMjsKICAgIH0KCiAgICAub3AuaGlnaGxpZ2h0ZWQgPiAubm9kZXNoYXBlID4gLm5vZGVjb2xvcnRhcmdldCwKICAgIC5vcC5oaWdobGlnaHRlZCA+IC5hbm5vdGF0aW9uLW5vZGUgPiAubm9kZWNvbG9ydGFyZ2V0IHsKICAgICAgc3Ryb2tlLXdpZHRoOiAyOwogICAgfQoKICAgIC8qIC0tLSBTZXJpZXMgTm9kZSAtLS0gKi8KCiAgICAvKiBCeSBkZWZhdWx0LCBkb24ndCBzaG93IHRoZSBzZXJpZXMgYmFja2dyb3VuZCA8cmVjdD4uICovCiAgICAuc2VyaWVzID4gLm5vZGVzaGFwZSA+IHJlY3QgewogICAgICBmaWxsOiBoc2woMCwgMCUsIDcwJSk7CiAgICAgIGZpbGwtb3BhY2l0eTogMDsKICAgICAgc3Ryb2tlLWRhc2hhcnJheTogNSwgNTsKICAgICAgc3Ryb2tlLW9wYWNpdHk6IDA7CiAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgIH0KCiAgICAvKiBPbmNlIGV4cGFuZGVkLCBzaG93IHRoZSBzZXJpZXMgYmFja2dyb3VuZCA8cmVjdD4gYW5kIGhpZGUgdGhlIDx1c2U+LiAqLwogICAgLnNlcmllcy5leHBhbmRlZCA+IC5ub2Rlc2hhcGUgPiByZWN0IHsKICAgICAgZmlsbC1vcGFjaXR5OiAwLjE1OwogICAgICBzdHJva2U6IGhzbCgwLCAwJSwgNzAlKTsKICAgICAgc3Ryb2tlLW9wYWNpdHk6IDE7CiAgICB9CiAgICAuc2VyaWVzLmV4cGFuZGVkID4gLm5vZGVzaGFwZSA+IHVzZSB7CiAgICAgIHZpc2liaWxpdHk6IGhpZGRlbjsKICAgIH0KCiAgICAvKioKICogVE9ETzogU2ltcGxpZnkgdGhpcyBieSBhcHBseWluZyBhIHN0YWJsZSBjbGFzcyBuYW1lIHRvIGFsbCA8Zz4KICogZWxlbWVudHMgdGhhdCBjdXJyZW50bHkgaGF2ZSBlaXRoZXIgdGhlIG5vZGVzaGFwZSBvciBhbm5vdGF0aW9uLW5vZGUgY2xhc3Nlcy4KICovCiAgICAuc2VyaWVzID4gLm5vZGVzaGFwZSA+IHVzZSwKICAgIC5zZXJpZXMgPiAuYW5ub3RhdGlvbi1ub2RlID4gdXNlIHsKICAgICAgc3Ryb2tlOiAjY2NjOwogICAgfQogICAgLnNlcmllcy5oaWdobGlnaHRlZCA+IC5ub2Rlc2hhcGUgPiB1c2UsCiAgICAuc2VyaWVzLmhpZ2hsaWdodGVkID4gLmFubm90YXRpb24tbm9kZSA+IHVzZSB7CiAgICAgIHN0cm9rZS13aWR0aDogMjsKICAgIH0KICAgIC5zZXJpZXMuc2VsZWN0ZWQgPiAubm9kZXNoYXBlID4gdXNlLAogICAgLnNlcmllcy5zZWxlY3RlZCA+IC5hbm5vdGF0aW9uLW5vZGUgPiB1c2UgewogICAgICBzdHJva2U6IHJlZDsKICAgICAgc3Ryb2tlLXdpZHRoOiAyOwogICAgfQoKICAgIC5zZXJpZXMuc2VsZWN0ZWQgPiAubm9kZXNoYXBlID4gcmVjdCB7CiAgICAgIHN0cm9rZTogcmVkOwogICAgICBzdHJva2Utd2lkdGg6IDI7CiAgICB9CgogICAgLmFubm90YXRpb24uc2VyaWVzLnNlbGVjdGVkID4gLmFubm90YXRpb24tbm9kZSA+IHVzZSB7CiAgICAgIHN0cm9rZTogcmVkOwogICAgICBzdHJva2Utd2lkdGg6IDI7CiAgICB9CgogICAgLyogLS0tIEJyaWRnZSBOb2RlIC0tLSAqLwogICAgLmJyaWRnZSA+IC5ub2Rlc2hhcGUgPiByZWN0IHsKICAgICAgc3Ryb2tlOiAjZjBmOwogICAgICBvcGFjaXR5OiAwLjI7CiAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICB9CgogICAgLyogLS0tIFN0cnVjdHVyYWwgRWxlbWVudHMgLS0tICovCiAgICAuZWRnZSA+IHBhdGguZWRnZWxpbmUuc3RydWN0dXJhbCB7CiAgICAgIHN0cm9rZTogI2YwZjsKICAgICAgb3BhY2l0eTogMC4yOwogICAgICBkaXNwbGF5OiBub25lOwogICAgfQoKICAgIC8qIFJlZmVyZW5jZSBFZGdlICovCiAgICAuZWRnZSA+IHBhdGguZWRnZWxpbmUucmVmZXJlbmNlZWRnZSB7CiAgICAgIHN0cm9rZTogI2ZmYjc0ZDsKICAgICAgb3BhY2l0eTogMTsKICAgIH0KCiAgICAvKiAtLS0gU2VyaWVzIE5vZGVzIC0tLSAqLwoKICAgIC8qIEhpZGUgdGhlIHJlY3QgZm9yIGEgc2VyaWVzJyBhbm5vdGF0aW9uLiAqLwogICAgLnNlcmllcyA+IC5hbm5vdGF0aW9uLW5vZGUgPiByZWN0IHsKICAgICAgZGlzcGxheTogbm9uZTsKICAgIH0KCiAgICAvKiAtLS0gTm9kZSBsYWJlbCAtLS0gKi8KCiAgICAubm9kZSB7CiAgICAgIC8qIFByb3ZpZGUgYSBoaW50IHRvIGJyb3dzZXJzIHRvIGF2b2lkIHVzaW5nIHRoZWlyIHN0YXRpYyByYXN0ZXJpemF0aW9uCiAgICAgIGF0IGluaXRpYWwgc2NhbGUsIHdoaWNoIGxvb2tzIHZlcnkgcGl4ZWxhdGVkIG9uIENocm9taXVtIHdoZW4gem9vbWVkIGluLgogICAgICBOb3RlIHRoYXQgd2UgaW50ZW50aW9uYWxseSBkbyAqbm90KiB1c2UgJ3dpbGwtY2hhbmdlOiB0cmFuc2Zvcm0nIGFuZAogICAgICAndHJhbnNsYXRlWigwKSBoZXJlLCB3aGljaCBpbnRyb2R1Y2UgYmx1cnJpbmVzcyBvbiBGaXJlZm94LgogICAgICBTZWUgaHR0cHM6Ly9naXRodWIuY29tL3RlbnNvcmZsb3cvdGVuc29yYm9hcmQvaXNzdWVzLzQ3NDQgKi8KICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVaKDFweCk7CiAgICB9CgogICAgLm5vZGUgPiB0ZXh0Lm5vZGVsYWJlbCB7CiAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgICAgZmlsbDogIzQ0NDsKICAgIH0KCiAgICAubWV0YS5leHBhbmRlZCA+IHRleHQubm9kZWxhYmVsIHsKICAgICAgZm9udC1zaXplOiA5cHg7CiAgICB9CgogICAgLnNlcmllcyA+IHRleHQubm9kZWxhYmVsIHsKICAgICAgZm9udC1zaXplOiA4cHg7CiAgICB9CgogICAgLm9wID4gdGV4dC5ub2RlbGFiZWwgewogICAgICBmb250LXNpemU6IDZweDsKICAgIH0KCiAgICAuYnJpZGdlID4gdGV4dC5ub2RlbGFiZWwgewogICAgICBkaXNwbGF5OiBub25lOwogICAgfQoKICAgIC5ub2RlLm1ldGEuZXhwYW5kZWQgPiB0ZXh0Lm5vZGVsYWJlbCB7CiAgICAgIGN1cnNvcjogbm9ybWFsOwogICAgfQoKICAgIC5hbm5vdGF0aW9uLm1ldGEuaGlnaGxpZ2h0ZWQgPiB0ZXh0LmFubm90YXRpb24tbGFiZWwgewogICAgICBmaWxsOiAjNTBhM2Y3OwogICAgfQoKICAgIC5hbm5vdGF0aW9uLm1ldGEuc2VsZWN0ZWQgPiB0ZXh0LmFubm90YXRpb24tbGFiZWwgewogICAgICBmaWxsOiAjNDI4NWY0OwogICAgfQoKICAgIC8qIC0tLSBBbm5vdGF0aW9uIC0tLSAqLwoKICAgIC8qIG9ubHkgYXBwbGllZCBmb3IgYW5ub3RhdGlvbnMgdGhhdCBhcmUgbm90IHN1bW1hcnkgb3IgY29uc3RhbnQuCiguc3VtbWFyeSwgLmNvbnN0YW50IGdldHMgb3ZlcnJpZGRlbiBiZWxvdykgKi8KICAgIC5hbm5vdGF0aW9uID4gLmFubm90YXRpb24tbm9kZSA+ICogewogICAgICBzdHJva2Utd2lkdGg6IDAuNTsKICAgICAgc3Ryb2tlLWRhc2hhcnJheTogMSwgMTsKICAgIH0KCiAgICAuYW5ub3RhdGlvbi5zdW1tYXJ5ID4gLmFubm90YXRpb24tbm9kZSA+ICosCiAgICAuYW5ub3RhdGlvbi5jb25zdGFudCA+IC5hbm5vdGF0aW9uLW5vZGUgPiAqIHsKICAgICAgc3Ryb2tlLXdpZHRoOiAxOwogICAgICBzdHJva2UtZGFzaGFycmF5OiBub25lOwogICAgfQoKICAgIC5hbm5vdGF0aW9uID4gLmFubm90YXRpb24tZWRnZSB7CiAgICAgIGZpbGw6IG5vbmU7CiAgICAgIHN0cm9rZTogI2FhYTsKICAgICAgc3Ryb2tlLXdpZHRoOiAwLjU7CiAgICAgIG1hcmtlci1lbmQ6IHVybCgjYW5ub3RhdGlvbi1hcnJvd2hlYWQpOwogICAgfQoKICAgIC5mYWRlZCAuYW5ub3RhdGlvbiA+IC5hbm5vdGF0aW9uLWVkZ2UgewogICAgICBtYXJrZXItZW5kOiB1cmwoI2Fubm90YXRpb24tYXJyb3doZWFkLWZhZGVkKTsKICAgIH0KCiAgICAuYW5ub3RhdGlvbiA+IC5hbm5vdGF0aW9uLWVkZ2UucmVmbGluZSB7CiAgICAgIG1hcmtlci1zdGFydDogdXJsKCNyZWYtYW5ub3RhdGlvbi1hcnJvd2hlYWQpOwogICAgfQoKICAgIC5mYWRlZCAuYW5ub3RhdGlvbiA+IC5hbm5vdGF0aW9uLWVkZ2UucmVmbGluZSB7CiAgICAgIG1hcmtlci1zdGFydDogdXJsKCNyZWYtYW5ub3RhdGlvbi1hcnJvd2hlYWQtZmFkZWQpOwogICAgfQoKICAgIC5hbm5vdGF0aW9uID4gLmFubm90YXRpb24tY29udHJvbC1lZGdlIHsKICAgICAgc3Ryb2tlLWRhc2hhcnJheTogMSwgMTsKICAgIH0KCiAgICAjYW5ub3RhdGlvbi1hcnJvd2hlYWQgewogICAgICBmaWxsOiAjYWFhOwogICAgfQoKICAgICNhbm5vdGF0aW9uLWFycm93aGVhZC1mYWRlZCB7CiAgICAgIGZpbGw6ICNlMGQ0YjM7CiAgICB9CgogICAgI3JlZi1hbm5vdGF0aW9uLWFycm93aGVhZCB7CiAgICAgIGZpbGw6ICNhYWE7CiAgICB9CgogICAgI3JlZi1hbm5vdGF0aW9uLWFycm93aGVhZC1mYWRlZCB7CiAgICAgIGZpbGw6ICNlMGQ0YjM7CiAgICB9CgogICAgLmFubm90YXRpb24gPiAuYW5ub3RhdGlvbi1sYWJlbCB7CiAgICAgIGZvbnQtc2l6ZTogNXB4OwogICAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICB9CiAgICAuYW5ub3RhdGlvbiA+IC5hbm5vdGF0aW9uLWxhYmVsLmFubm90YXRpb24tZWxsaXBzaXMgewogICAgICBjdXJzb3I6IGRlZmF1bHQ7CiAgICB9CgogICAgLyogSGlkZSBhbm5vdGF0aW9ucyBvbiBleHBhbmRlZCBtZXRhIG5vZGVzIHNpbmNlIHRoZXkncmUgcmVkdW5kYW50LiAqLwogICAgLmV4cGFuZGVkID4gLmluLWFubm90YXRpb25zLAogICAgLmV4cGFuZGVkID4gLm91dC1hbm5vdGF0aW9ucyB7CiAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICB9CgogICAgLyogLS0tIEFubm90YXRpb246IENvbnN0YW50IC0tLSAqLwoKICAgIC5jb25zdGFudCA+IC5hbm5vdGF0aW9uLW5vZGUgPiBlbGxpcHNlIHsKICAgICAgY3Vyc29yOiBwb2ludGVyOwogICAgICBmaWxsOiB3aGl0ZTsKICAgICAgc3Ryb2tlOiAjODQ4NDg0OwogICAgfQoKICAgIC5jb25zdGFudC5zZWxlY3RlZCA+IC5hbm5vdGF0aW9uLW5vZGUgPiBlbGxpcHNlIHsKICAgICAgZmlsbDogd2hpdGU7CiAgICAgIHN0cm9rZTogcmVkOwogICAgfQoKICAgIC5jb25zdGFudC5oaWdobGlnaHRlZCA+IC5hbm5vdGF0aW9uLW5vZGUgPiBlbGxpcHNlIHsKICAgICAgc3Ryb2tlLXdpZHRoOiAxLjU7CiAgICB9CgogICAgLyogLS0tIEFubm90YXRpb246IFN1bW1hcnkgLS0tICovCgogICAgLnN1bW1hcnkgPiAuYW5ub3RhdGlvbi1ub2RlID4gZWxsaXBzZSB7CiAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgICAgZmlsbDogI2RiNDQzNzsKICAgICAgc3Ryb2tlOiAjZGI0NDM3OwogICAgfQoKICAgIC5zdW1tYXJ5LnNlbGVjdGVkID4gLmFubm90YXRpb24tbm9kZSA+IGVsbGlwc2UgewogICAgICBmaWxsOiAjYTUyNzE0OwogICAgICBzdHJva2U6ICNhNTI3MTQ7CiAgICB9CgogICAgLnN1bW1hcnkuaGlnaGxpZ2h0ZWQgPiAuYW5ub3RhdGlvbi1ub2RlID4gZWxsaXBzZSB7CiAgICAgIHN0cm9rZS13aWR0aDogMS41OwogICAgfQoKICAgIC8qIC0tLSBFZGdlIC0tLSAqLwoKICAgIC5lZGdlID4gcGF0aC5lZGdlbGluZSB7CiAgICAgIGZpbGw6IG5vbmU7CiAgICAgIHN0cm9rZTogI2JiYjsKICAgICAgc3Ryb2tlLWxpbmVjYXA6IHJvdW5kOwogICAgICBzdHJva2Utd2lkdGg6IDAuNzU7CiAgICB9CgogICAgLmVkZ2UgLnNlbGVjdGFibGVlZGdlIHsKICAgICAgY3Vyc29yOiBwb2ludGVyOwogICAgfQoKICAgIC5zZWxlY3RlZGVkZ2UgPiBwYXRoLmVkZ2VsaW5lIHsKICAgICAgY3Vyc29yOiBkZWZhdWx0OwogICAgICBzdHJva2U6ICNmMDA7CiAgICB9CgogICAgLmVkZ2Uuc2VsZWN0ZWRlZGdlIHRleHQgewogICAgICBmaWxsOiAjMDAwOwogICAgfQoKICAgIC8qIExhYmVscyBzaG93aW5nIHRlbnNvciBzaGFwZXMgb24gZWRnZXMgKi8KICAgIC5lZGdlID4gdGV4dCB7CiAgICAgIGZvbnQtc2l6ZTogMy41cHg7CiAgICAgIGZpbGw6ICM2NjY7CiAgICB9CgogICAgLmRhdGFmbG93LWFycm93aGVhZCB7CiAgICAgIGZpbGw6ICNiYmI7CiAgICB9CgogICAgLnJlZmVyZW5jZS1hcnJvd2hlYWQgewogICAgICBmaWxsOiAjZmZiNzRkOwogICAgfQoKICAgIC5zZWxlY3RlZC1hcnJvd2hlYWQgewogICAgICBmaWxsOiAjZjAwOwogICAgfQoKICAgIC5lZGdlIC5jb250cm9sLWRlcCB7CiAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDIsIDI7CiAgICB9CgogICAgLyogLS0tIEdyb3VwIG5vZGUgZXhwYW5kL2NvbGxhcHNlIGJ1dHRvbiAtLS0gKi8KCiAgICAvKiBIaWRlcyBleHBhbmQvY29sbGFwc2UgYnV0dG9ucyB3aGVuIGEgbm9kZSBpc24ndCBleHBhbmRlZCBvciBoaWdobGlnaHRlZC4gVXNpbmcKICAgaW5jcmVkaWJseSBzbWFsbCBvcGFjaXR5IHNvIHRoYXQgdGhlIGJvdW5kaW5nIGJveCBvZiB0aGUgPGc+IHBhcmVudCBzdGlsbCB0YWtlcwogICB0aGlzIGNvbnRhaW5lciBpbnRvIGFjY291bnQgZXZlbiB3aGVuIGl0IGlzbid0IHZpc2libGUgKi8KICAgIC5ub2RlOm5vdCguaGlnaGxpZ2h0ZWQpOm5vdCguZXhwYW5kZWQpID4gLm5vZGVzaGFwZSA+IC5idXR0b25jb250YWluZXIgewogICAgICBvcGFjaXR5OiAwLjAxOwogICAgfQogICAgLm5vZGUuaGlnaGxpZ2h0ZWQgPiAubm9kZXNoYXBlID4gLmJ1dHRvbmNvbnRhaW5lciB7CiAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgIH0KICAgIC5idXR0b25jaXJjbGUgewogICAgICBmaWxsOiAjZTc4MTFkOwogICAgfQogICAgLmJ1dHRvbmNpcmNsZTpob3ZlciB7CiAgICAgIGZpbGw6ICNiOTY3MTc7CiAgICB9CiAgICAuZXhwYW5kYnV0dG9uLAogICAgLmNvbGxhcHNlYnV0dG9uIHsKICAgICAgc3Ryb2tlOiB3aGl0ZTsKICAgIH0KICAgIC8qIERvIG5vdCBsZXQgdGhlIHBhdGggZWxlbWVudHMgaW4gdGhlIGJ1dHRvbiB0YWtlIHBvaW50ZXIgZm9jdXMgKi8KICAgIC5ub2RlID4gLm5vZGVzaGFwZSA+IC5idXR0b25jb250YWluZXIgPiAuZXhwYW5kYnV0dG9uLAogICAgLm5vZGUgPiAubm9kZXNoYXBlID4gLmJ1dHRvbmNvbnRhaW5lciA+IC5jb2xsYXBzZWJ1dHRvbiB7CiAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgfQogICAgLyogT25seSBzaG93IHRoZSBleHBhbmQgYnV0dG9uIHdoZW4gYSBub2RlIGlzIGNvbGxhcHNlZCBhbmQgb25seSBzaG93IHRoZQogICBjb2xsYXBzZSBidXR0b24gd2hlbiBhIG5vZGUgaXMgZXhwYW5kZWQuICovCiAgICAubm9kZS5leHBhbmRlZCA+IC5ub2Rlc2hhcGUgPiAuYnV0dG9uY29udGFpbmVyID4gLmV4cGFuZGJ1dHRvbiB7CiAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICB9CiAgICAubm9kZTpub3QoLmV4cGFuZGVkKSA+IC5ub2Rlc2hhcGUgPiAuYnV0dG9uY29udGFpbmVyID4gLmNvbGxhcHNlYnV0dG9uIHsKICAgICAgZGlzcGxheTogbm9uZTsKICAgIH0KCiAgICAuaGVhbHRoLXBpbGwtc3RhdHMgewogICAgICBmb250LXNpemU6IDRweDsKICAgICAgdGV4dC1hbmNob3I6IG1pZGRsZTsKICAgIH0KCiAgICAuaGVhbHRoLXBpbGwgcmVjdCB7CiAgICAgIGZpbHRlcjogdXJsKCNoZWFsdGgtcGlsbC1zaGFkb3cpOwogICAgICByeDogMzsKICAgICAgcnk6IDM7CiAgICB9CgogICAgLnRpdGxlQ29udGFpbmVyIHsKICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICB0b3A6IDIwcHg7CiAgICB9CgogICAgLnRpdGxlLAogICAgLmF1eFRpdGxlLAogICAgLmZ1bmN0aW9uTGlicmFyeVRpdGxlIHsKICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgfQoKICAgICNtaW5pbWFwIHsKICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICByaWdodDogMjBweDsKICAgICAgYm90dG9tOiAyMHB4OwogICAgfQoKICAgIC5jb250ZXh0LW1lbnUgewogICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICAgIGJhY2tncm91bmQtY29sb3I6ICNlMmUyZTI7CiAgICAgIGJvcmRlci1yYWRpdXM6IDJweDsKICAgICAgZm9udC1zaXplOiAxNHB4OwogICAgICBtaW4td2lkdGg6IDE1MHB4OwogICAgICBib3JkZXI6IDFweCBzb2xpZCAjZDRkNGQ0OwogICAgfQoKICAgIC5jb250ZXh0LW1lbnUgdWwgewogICAgICBsaXN0LXN0eWxlLXR5cGU6IG5vbmU7CiAgICAgIG1hcmdpbjogMDsKICAgICAgcGFkZGluZzogMDsKICAgICAgY3Vyc29yOiBkZWZhdWx0OwogICAgfQoKICAgIC5jb250ZXh0LW1lbnUgdWwgbGkgewogICAgICBwYWRkaW5nOiA0cHggMTZweDsKICAgIH0KCiAgICAuY29udGV4dC1tZW51IHVsIGxpOmhvdmVyIHsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YzOTEzZTsKICAgICAgY29sb3I6IHdoaXRlOwogICAgfQogIDwvc3R5bGU+CiAgPGRpdiBjbGFzcz0idGl0bGVDb250YWluZXIiPgogICAgPGRpdiBpZD0idGl0bGUiIGNsYXNzPSJ0aXRsZSI+TWFpbiBHcmFwaDwvZGl2PgogICAgPGRpdiBpZD0iYXV4VGl0bGUiIGNsYXNzPSJhdXhUaXRsZSI+QXV4aWxpYXJ5IE5vZGVzPC9kaXY+CiAgICA8ZGl2IGlkPSJmdW5jdGlvbkxpYnJhcnlUaXRsZSIgY2xhc3M9ImZ1bmN0aW9uTGlicmFyeVRpdGxlIj5GdW5jdGlvbnM8L2Rpdj4KICA8L2Rpdj4KICA8c3ZnIGlkPSJzdmciPgogICAgPGRlZnM+CiAgICAgIDwhLS0gQXJyb3cgaGVhZHMgZm9yIHJlZmVyZW5jZSBlZGdlIHBhdGhzIG9mIGRpZmZlcmVudCBwcmVkZWZpbmVkIHNpemVzIHBlciBjb2xvci4gLS0+CiAgICAgIDxwYXRoCiAgICAgICAgaWQ9InJlZmVyZW5jZS1hcnJvd2hlYWQtcGF0aCIKICAgICAgICBkPSJNIDAsMCBMIDEwLDUgTCAwLDEwIEMgMyw3IDMsMyAwLDAiCiAgICAgID48L3BhdGg+CiAgICAgIDxtYXJrZXIKICAgICAgICBjbGFzcz0icmVmZXJlbmNlLWFycm93aGVhZCIKICAgICAgICBpZD0icmVmZXJlbmNlLWFycm93aGVhZC1zbWFsbCIKICAgICAgICB2aWV3Qm94PSIwIDAgMTAgMTAiCiAgICAgICAgbWFya2VyV2lkdGg9IjUiCiAgICAgICAgbWFya2VySGVpZ2h0PSI1IgogICAgICAgIHJlZlg9IjIiCiAgICAgICAgcmVmWT0iNSIKICAgICAgICBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSIKICAgICAgICBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgICAgID4KICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNyZWZlcmVuY2UtYXJyb3doZWFkLXBhdGgiPjwvdXNlPgogICAgICA8L21hcmtlcj4KICAgICAgPG1hcmtlcgogICAgICAgIGNsYXNzPSJyZWZlcmVuY2UtYXJyb3doZWFkIgogICAgICAgIGlkPSJyZWZlcmVuY2UtYXJyb3doZWFkLW1lZGl1bSIKICAgICAgICB2aWV3Qm94PSIwIDAgMTAgMTAiCiAgICAgICAgbWFya2VyV2lkdGg9IjEzIgogICAgICAgIG1hcmtlckhlaWdodD0iMTMiCiAgICAgICAgcmVmWD0iMiIKICAgICAgICByZWZZPSI1IgogICAgICAgIG9yaWVudD0iYXV0by1zdGFydC1yZXZlcnNlIgogICAgICAgIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgICAgPgogICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI3JlZmVyZW5jZS1hcnJvd2hlYWQtcGF0aCI+PC91c2U+CiAgICAgIDwvbWFya2VyPgogICAgICA8bWFya2VyCiAgICAgICAgY2xhc3M9InJlZmVyZW5jZS1hcnJvd2hlYWQiCiAgICAgICAgaWQ9InJlZmVyZW5jZS1hcnJvd2hlYWQtbGFyZ2UiCiAgICAgICAgdmlld0JveD0iMCAwIDEwIDEwIgogICAgICAgIG1hcmtlcldpZHRoPSIxNiIKICAgICAgICBtYXJrZXJIZWlnaHQ9IjE2IgogICAgICAgIHJlZlg9IjIiCiAgICAgICAgcmVmWT0iNSIKICAgICAgICBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSIKICAgICAgICBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgICAgID4KICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNyZWZlcmVuY2UtYXJyb3doZWFkLXBhdGgiPjwvdXNlPgogICAgICA8L21hcmtlcj4KICAgICAgPG1hcmtlcgogICAgICAgIGNsYXNzPSJyZWZlcmVuY2UtYXJyb3doZWFkIgogICAgICAgIGlkPSJyZWZlcmVuY2UtYXJyb3doZWFkLXhsYXJnZSIKICAgICAgICB2aWV3Qm94PSIwIDAgMTAgMTAiCiAgICAgICAgbWFya2VyV2lkdGg9IjIwIgogICAgICAgIG1hcmtlckhlaWdodD0iMjAiCiAgICAgICAgcmVmWD0iMiIKICAgICAgICByZWZZPSI1IgogICAgICAgIG9yaWVudD0iYXV0by1zdGFydC1yZXZlcnNlIgogICAgICAgIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgICAgPgogICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI3JlZmVyZW5jZS1hcnJvd2hlYWQtcGF0aCI+PC91c2U+CiAgICAgIDwvbWFya2VyPgoKICAgICAgPCEtLSBBcnJvdyBoZWFkcyBmb3IgZGF0YWZsb3cgZWRnZSBwYXRocyBvZiBkaWZmZXJlbnQgcHJlZGVmaW5lZCBzaXplcyBwZXIgY29sb3IuIC0tPgogICAgICA8cGF0aAogICAgICAgIGlkPSJkYXRhZmxvdy1hcnJvd2hlYWQtcGF0aCIKICAgICAgICBkPSJNIDAsMCBMIDEwLDUgTCAwLDEwIEMgMyw3IDMsMyAwLDAiCiAgICAgID48L3BhdGg+CiAgICAgIDxtYXJrZXIKICAgICAgICBjbGFzcz0iZGF0YWZsb3ctYXJyb3doZWFkIgogICAgICAgIGlkPSJkYXRhZmxvdy1hcnJvd2hlYWQtc21hbGwiCiAgICAgICAgdmlld0JveD0iMCAwIDEwIDEwIgogICAgICAgIG1hcmtlcldpZHRoPSI1IgogICAgICAgIG1hcmtlckhlaWdodD0iNSIKICAgICAgICByZWZYPSIyIgogICAgICAgIHJlZlk9IjUiCiAgICAgICAgb3JpZW50PSJhdXRvLXN0YXJ0LXJldmVyc2UiCiAgICAgICAgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICAgICA+CiAgICAgICAgPHVzZSB4bGluazpocmVmPSIjZGF0YWZsb3ctYXJyb3doZWFkLXBhdGgiPjwvdXNlPgogICAgICA8L21hcmtlcj4KICAgICAgPG1hcmtlcgogICAgICAgIGNsYXNzPSJkYXRhZmxvdy1hcnJvd2hlYWQiCiAgICAgICAgaWQ9ImRhdGFmbG93LWFycm93aGVhZC1tZWRpdW0iCiAgICAgICAgdmlld0JveD0iMCAwIDEwIDEwIgogICAgICAgIG1hcmtlcldpZHRoPSIxMyIKICAgICAgICBtYXJrZXJIZWlnaHQ9IjEzIgogICAgICAgIHJlZlg9IjIiCiAgICAgICAgcmVmWT0iNSIKICAgICAgICBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSIKICAgICAgICBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgICAgID4KICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNkYXRhZmxvdy1hcnJvd2hlYWQtcGF0aCI+PC91c2U+CiAgICAgIDwvbWFya2VyPgogICAgICA8bWFya2VyCiAgICAgICAgY2xhc3M9ImRhdGFmbG93LWFycm93aGVhZCIKICAgICAgICBpZD0iZGF0YWZsb3ctYXJyb3doZWFkLWxhcmdlIgogICAgICAgIHZpZXdCb3g9IjAgMCAxMCAxMCIKICAgICAgICBtYXJrZXJXaWR0aD0iMTYiCiAgICAgICAgbWFya2VySGVpZ2h0PSIxNiIKICAgICAgICByZWZYPSIyIgogICAgICAgIHJlZlk9IjUiCiAgICAgICAgb3JpZW50PSJhdXRvLXN0YXJ0LXJldmVyc2UiCiAgICAgICAgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICAgICA+CiAgICAgICAgPHVzZSB4bGluazpocmVmPSIjZGF0YWZsb3ctYXJyb3doZWFkLXBhdGgiPjwvdXNlPgogICAgICA8L21hcmtlcj4KICAgICAgPG1hcmtlcgogICAgICAgIGNsYXNzPSJkYXRhZmxvdy1hcnJvd2hlYWQiCiAgICAgICAgaWQ9ImRhdGFmbG93LWFycm93aGVhZC14bGFyZ2UiCiAgICAgICAgdmlld0JveD0iMCAwIDEwIDEwIgogICAgICAgIG1hcmtlcldpZHRoPSIyMCIKICAgICAgICBtYXJrZXJIZWlnaHQ9IjIwIgogICAgICAgIHJlZlg9IjIiCiAgICAgICAgcmVmWT0iNSIKICAgICAgICBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSIKICAgICAgICBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgICAgID4KICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNkYXRhZmxvdy1hcnJvd2hlYWQtcGF0aCI+PC91c2U+CiAgICAgIDwvbWFya2VyPgoKICAgICAgPCEtLSBBcnJvdyBoZWFkIGZvciBhbm5vdGF0aW9uIGVkZ2UgcGF0aHMuIC0tPgogICAgICA8bWFya2VyCiAgICAgICAgaWQ9ImFubm90YXRpb24tYXJyb3doZWFkIgogICAgICAgIG1hcmtlcldpZHRoPSI1IgogICAgICAgIG1hcmtlckhlaWdodD0iNSIKICAgICAgICByZWZYPSI1IgogICAgICAgIHJlZlk9IjIuNSIKICAgICAgICBvcmllbnQ9ImF1dG8iCiAgICAgID4KICAgICAgICA8cGF0aCBkPSJNIDAsMCBMIDUsMi41IEwgMCw1IEwgMCwwIj48L3BhdGg+CiAgICAgIDwvbWFya2VyPgogICAgICA8bWFya2VyCiAgICAgICAgaWQ9ImFubm90YXRpb24tYXJyb3doZWFkLWZhZGVkIgogICAgICAgIG1hcmtlcldpZHRoPSI1IgogICAgICAgIG1hcmtlckhlaWdodD0iNSIKICAgICAgICByZWZYPSI1IgogICAgICAgIHJlZlk9IjIuNSIKICAgICAgICBvcmllbnQ9ImF1dG8iCiAgICAgID4KICAgICAgICA8cGF0aCBkPSJNIDAsMCBMIDUsMi41IEwgMCw1IEwgMCwwIj48L3BhdGg+CiAgICAgIDwvbWFya2VyPgogICAgICA8bWFya2VyCiAgICAgICAgaWQ9InJlZi1hbm5vdGF0aW9uLWFycm93aGVhZCIKICAgICAgICBtYXJrZXJXaWR0aD0iNSIKICAgICAgICBtYXJrZXJIZWlnaHQ9IjUiCiAgICAgICAgcmVmWD0iMCIKICAgICAgICByZWZZPSIyLjUiCiAgICAgICAgb3JpZW50PSJhdXRvIgogICAgICA+CiAgICAgICAgPHBhdGggZD0iTSA1LDAgTCAwLDIuNSBMIDUsNSBMIDUsMCI+PC9wYXRoPgogICAgICA8L21hcmtlcj4KICAgICAgPG1hcmtlcgogICAgICAgIGlkPSJyZWYtYW5ub3RhdGlvbi1hcnJvd2hlYWQtZmFkZWQiCiAgICAgICAgbWFya2VyV2lkdGg9IjUiCiAgICAgICAgbWFya2VySGVpZ2h0PSI1IgogICAgICAgIHJlZlg9IjAiCiAgICAgICAgcmVmWT0iMi41IgogICAgICAgIG9yaWVudD0iYXV0byIKICAgICAgPgogICAgICAgIDxwYXRoIGQ9Ik0gNSwwIEwgMCwyLjUgTCA1LDUgTCA1LDAiPjwvcGF0aD4KICAgICAgPC9tYXJrZXI+CiAgICAgIDwhLS0gVGVtcGxhdGUgZm9yIGFuIE9wIG5vZGUgZWxsaXBzZS4gLS0+CiAgICAgIDxlbGxpcHNlCiAgICAgICAgaWQ9Im9wLW5vZGUtc3RhbXAiCiAgICAgICAgcng9IjcuNSIKICAgICAgICByeT0iMyIKICAgICAgICBzdHJva2U9ImluaGVyaXQiCiAgICAgICAgZmlsbD0iaW5oZXJpdCIKICAgICAgPjwvZWxsaXBzZT4KICAgICAgPCEtLSBUZW1wbGF0ZSBmb3IgYW4gT3Agbm9kZSBhbm5vdGF0aW9uIGVsbGlwc2UgKHNtYWxsZXIpLiAtLT4KICAgICAgPGVsbGlwc2UKICAgICAgICBpZD0ib3Atbm9kZS1hbm5vdGF0aW9uLXN0YW1wIgogICAgICAgIHJ4PSI1IgogICAgICAgIHJ5PSIyIgogICAgICAgIHN0cm9rZT0iaW5oZXJpdCIKICAgICAgICBmaWxsPSJpbmhlcml0IgogICAgICA+PC9lbGxpcHNlPgogICAgICA8IS0tIFZlcnRpY2FsbHkgc3RhY2tlZCBzZXJpZXMgb2YgT3Agbm9kZXMgd2hlbiB1bmV4cGFuZGVkLiAtLT4KICAgICAgPGcgaWQ9Im9wLXNlcmllcy12ZXJ0aWNhbC1zdGFtcCI+CiAgICAgICAgPHVzZSB4bGluazpocmVmPSIjb3Atbm9kZS1zdGFtcCIgeD0iOCIgeT0iOSI+PC91c2U+CiAgICAgICAgPHVzZSB4bGluazpocmVmPSIjb3Atbm9kZS1zdGFtcCIgeD0iOCIgeT0iNiI+PC91c2U+CiAgICAgICAgPHVzZSB4bGluazpocmVmPSIjb3Atbm9kZS1zdGFtcCIgeD0iOCIgeT0iMyI+PC91c2U+CiAgICAgIDwvZz4KICAgICAgPCEtLSBIb3Jpem9udGFsbHkgc3RhY2tlZCBzZXJpZXMgb2YgT3Agbm9kZXMgd2hlbiB1bmV4cGFuZGVkLiAtLT4KICAgICAgPGcgaWQ9Im9wLXNlcmllcy1ob3Jpem9udGFsLXN0YW1wIj4KICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNvcC1ub2RlLXN0YW1wIiB4PSIxNiIgeT0iNCI+PC91c2U+CiAgICAgICAgPHVzZSB4bGluazpocmVmPSIjb3Atbm9kZS1zdGFtcCIgeD0iMTIiIHk9IjQiPjwvdXNlPgogICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI29wLW5vZGUtc3RhbXAiIHg9IjgiIHk9IjQiPjwvdXNlPgogICAgICA8L2c+CiAgICAgIDwhLS0gSG9yaXpvbnRhbGx5IHN0YWNrZWQgc2VyaWVzIG9mIE9wIG5vZGVzIGZvciBhbm5vdGF0aW9uLiAtLT4KICAgICAgPGcgaWQ9Im9wLXNlcmllcy1hbm5vdGF0aW9uLXN0YW1wIj4KICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNvcC1ub2RlLWFubm90YXRpb24tc3RhbXAiIHg9IjkiIHk9IjIiPjwvdXNlPgogICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI29wLW5vZGUtYW5ub3RhdGlvbi1zdGFtcCIgeD0iNyIgeT0iMiI+PC91c2U+CiAgICAgICAgPHVzZSB4bGluazpocmVmPSIjb3Atbm9kZS1hbm5vdGF0aW9uLXN0YW1wIiB4PSI1IiB5PSIyIj48L3VzZT4KICAgICAgPC9nPgogICAgICA8c3ZnCiAgICAgICAgaWQ9InN1bW1hcnktaWNvbiIKICAgICAgICBmaWxsPSIjODQ4NDg0IgogICAgICAgIGhlaWdodD0iMTIiCiAgICAgICAgdmlld0JveD0iMCAwIDI0IDI0IgogICAgICAgIHdpZHRoPSIxMiIKICAgICAgPgogICAgICAgIDxwYXRoCiAgICAgICAgICBkPSJNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6TTkgMTdIN3YtN2gydjd6bTQgMGgtMlY3aDJ2MTB6bTQgMGgtMnYtNGgydjR6IgogICAgICAgID48L3BhdGg+CiAgICAgIDwvc3ZnPgoKICAgICAgPCEtLSBIYXRjaCBwYXR0ZXJucyBmb3IgZmFkZWQgb3V0IG5vZGVzLiAtLT4KICAgICAgPHBhdHRlcm4KICAgICAgICBpZD0icmVjdEhhdGNoIgogICAgICAgIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSg0NSAwIDApIgogICAgICAgIHdpZHRoPSI1IgogICAgICAgIGhlaWdodD0iNSIKICAgICAgICBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICAgICA+CiAgICAgICAgPGxpbmUgeDE9IjAiIHkxPSIwIiB4Mj0iMCIgeTI9IjUiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDEiPjwvbGluZT4KICAgICAgPC9wYXR0ZXJuPgogICAgICA8cGF0dGVybgogICAgICAgIGlkPSJlbGxpcHNlSGF0Y2giCiAgICAgICAgcGF0dGVyblRyYW5zZm9ybT0icm90YXRlKDQ1IDAgMCkiCiAgICAgICAgd2lkdGg9IjIiCiAgICAgICAgaGVpZ2h0PSIyIgogICAgICAgIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiCiAgICAgID4KICAgICAgICA8bGluZSB4MT0iMCIgeTE9IjAiIHgyPSIwIiB5Mj0iMiIgc3R5bGU9InN0cm9rZS13aWR0aDogMSI+PC9saW5lPgogICAgICA8L3BhdHRlcm4+CgogICAgICA8IS0tIEEgc2hhZG93IGZvciBoZWFsdGggcGlsbHMuIC0tPgogICAgICA8ZmlsdGVyCiAgICAgICAgaWQ9ImhlYWx0aC1waWxsLXNoYWRvdyIKICAgICAgICB4PSItNDAlIgogICAgICAgIHk9Ii00MCUiCiAgICAgICAgd2lkdGg9IjE4MCUiCiAgICAgICAgaGVpZ2h0PSIxODAlIgogICAgICA+CiAgICAgICAgPGZlR2F1c3NpYW5CbHVyIGluPSJTb3VyY2VBbHBoYSIgc3RkRGV2aWF0aW9uPSIwLjgiPjwvZmVHYXVzc2lhbkJsdXI+CiAgICAgICAgPGZlT2Zmc2V0IGR4PSIwIiBkeT0iMCIgcmVzdWx0PSJvZmZzZXRibHVyIj48L2ZlT2Zmc2V0PgogICAgICAgIDxmZUZsb29kIGZsb29kLWNvbG9yPSIjMDAwMDAwIj48L2ZlRmxvb2Q+CiAgICAgICAgPGZlQ29tcG9zaXRlIGluMj0ib2Zmc2V0Ymx1ciIgb3BlcmF0b3I9ImluIj48L2ZlQ29tcG9zaXRlPgogICAgICAgIDxmZU1lcmdlPgogICAgICAgICAgPGZlTWVyZ2VOb2RlPjwvZmVNZXJnZU5vZGU+CiAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiPjwvZmVNZXJnZU5vZGU+CiAgICAgICAgPC9mZU1lcmdlPgogICAgICA8L2ZpbHRlcj4KICAgIDwvZGVmcz4KICAgIDwhLS0gTWFrZSBhIGxhcmdlIHJlY3RhbmdsZSB0aGF0IGZpbGxzIHRoZSBzdmcgc3BhY2Ugc28gdGhhdAogIHpvb20gZXZlbnRzIGdldCBjYXB0dXJlZCBvbiBzYWZhcmkgLS0+CiAgICA8cmVjdCBmaWxsPSJ3aGl0ZSIgd2lkdGg9IjEwMDAwIiBoZWlnaHQ9IjEwMDAwIj48L3JlY3Q+CiAgICA8ZyBpZD0icm9vdCI+PC9nPgogIDwvc3ZnPgogIDx0Zi1ncmFwaC1taW5pbWFwIGlkPSJtaW5pbWFwIj48L3RmLWdyYXBoLW1pbmltYXA+CiAgPGRpdiBpZD0iY29udGV4dE1lbnUiIGNsYXNzPSJjb250ZXh0LW1lbnUiPjwvZGl2PgpgO3ZhciBMcj1jbGFzcyBleHRlbmRzIEd0KF9vKG10KSl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX3pvb21lZD0hMSx0aGlzLl96b29tU3RhcnRDb29yZHM9bnVsbCx0aGlzLl96b29tVHJhbnNmb3JtPW51bGwsdGhpcy5fbWF4Wm9vbURpc3RhbmNlRm9yQ2xpY2s9MjAsdGhpcy5fbm9kZUdyb3VwSW5kZXg9e30sdGhpcy5fYW5ub3RhdGlvbkdyb3VwSW5kZXg9e30sdGhpcy5fZWRnZUdyb3VwSW5kZXg9e30sdGhpcy5tYXhNZXRhbm9kZUxhYmVsTGVuZ3RoRm9udFNpemU9OSx0aGlzLm1pbk1ldGFub2RlTGFiZWxMZW5ndGhGb250U2l6ZT02LHRoaXMubWF4TWV0YW5vZGVMYWJlbExlbmd0aExhcmdlRm9udD0xMSx0aGlzLm1heE1ldGFub2RlTGFiZWxMZW5ndGg9MTh9Z2V0Tm9kZSh0KXtyZXR1cm4gdGhpcy5yZW5kZXJIaWVyYXJjaHkuZ2V0UmVuZGVyTm9kZUJ5TmFtZSh0KX1pc05vZGVFeHBhbmRlZCh0KXtyZXR1cm4gdC5leHBhbmRlZH1zZXROb2RlRXhwYW5kZWQodCl7dGhpcy5fYnVpbGQodGhpcy5yZW5kZXJIaWVyYXJjaHkpLHRoaXMuX3VwZGF0ZUxhYmVscyghdGhpcy5fem9vbWVkKX1wYW5Ub05vZGUodCl7U2xlKHQsdGhpcy4kLnN2Zyx0aGlzLiQucm9vdCx0aGlzLl96b29tKSYmKHRoaXMuX3pvb21lZD0hMCl9Z2V0R3JhcGhTdmdSb290KCl7cmV0dXJuIHRoaXMuJC5zdmd9Z2V0Q29udGV4dE1lbnUoKXtyZXR1cm4gdGhpcy4kLmNvbnRleHRNZW51fV9yZXNldFN0YXRlKCl7dGhpcy5fbm9kZUdyb3VwSW5kZXg9e30sdGhpcy5fYW5ub3RhdGlvbkdyb3VwSW5kZXg9e30sdGhpcy5fZWRnZUdyb3VwSW5kZXg9e30sdGhpcy5fdXBkYXRlTGFiZWxzKCExKSxIdCh0aGlzLiQuc3ZnKS5zZWxlY3QoIiNyb290Iikuc2VsZWN0QWxsKCIqIikucmVtb3ZlKCksSUgodGhpcy4kLnN2Zyl9X2J1aWxkKHQpe3RoaXMudGVtcGxhdGVJbmRleD10LmhpZXJhcmNoeS5nZXRUZW1wbGF0ZUluZGV4KCksUmQoInRmLWdyYXBoLXNjZW5lIChsYXlvdXQpOiIsZnVuY3Rpb24oKXtTSCh0LnJvb3QpfS5iaW5kKHRoaXMpLGpyLlJFTkRFUl9TQ0VORV9MQVlPVVQpLFJkKCJ0Zi1ncmFwaC1zY2VuZSAoYnVpbGQgc2NlbmUpOiIsZnVuY3Rpb24oKXtyY3QoSHQodGhpcy4kLnJvb3QpLHQucm9vdCx0aGlzKSxFbGUodGhpcy4kLnN2Zyx0aGlzKSx0aGlzLl91cGRhdGVJbnB1dFRyYWNlKCl9LmJpbmQodGhpcyksanIuUkVOREVSX1NDRU5FX0JVSUxEX1NDRU5FKSxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7dGhpcy5fdXBkYXRlSGVhbHRoUGlsbHModGhpcy5ub2RlTmFtZXNUb0hlYWx0aFBpbGxzLHRoaXMuaGVhbHRoUGlsbFN0ZXBJbmRleCksdGhpcy5taW5pbWFwLnVwZGF0ZSgpfS5iaW5kKHRoaXMpLFRyLmFuaW1hdGlvbi5kdXJhdGlvbil9cmVhZHkoKXtzdXBlci5yZWFkeSgpLHRoaXMuX3pvb209dFIoKS5vbigiZW5kIixmdW5jdGlvbigpe2lmKHRoaXMuX3pvb21TdGFydENvb3Jkcyl7dmFyIHQ9TWF0aC5zcXJ0KE1hdGgucG93KHRoaXMuX3pvb21TdGFydENvb3Jkcy54LXRoaXMuX3pvb21UcmFuc2Zvcm0ueCwyKStNYXRoLnBvdyh0aGlzLl96b29tU3RhcnRDb29yZHMueS10aGlzLl96b29tVHJhbnNmb3JtLnksMikpO3Q8dGhpcy5fbWF4Wm9vbURpc3RhbmNlRm9yQ2xpY2s/dGhpcy5fZmlyZUVuYWJsZUNsaWNrKCk6c2V0VGltZW91dCh0aGlzLl9maXJlRW5hYmxlQ2xpY2suYmluZCh0aGlzKSw1MCl9dGhpcy5fem9vbVN0YXJ0Q29vcmRzPW51bGx9LmJpbmQodGhpcykpLm9uKCJ6b29tIixmdW5jdGlvbigpe3RoaXMuX3pvb21UcmFuc2Zvcm09cXQudHJhbnNmb3JtLHRoaXMuX3pvb21TdGFydENvb3Jkc3x8KHRoaXMuX3pvb21TdGFydENvb3Jkcz10aGlzLl96b29tVHJhbnNmb3JtLHRoaXMuZmlyZSgiZGlzYWJsZS1jbGljayIpKSx0aGlzLl96b29tZWQ9ITAsSHQodGhpcy4kLnJvb3QpLmF0dHIoInRyYW5zZm9ybSIscXQudHJhbnNmb3JtKSx0aGlzLm1pbmltYXAuem9vbShxdC50cmFuc2Zvcm0pfS5iaW5kKHRoaXMpKSxIdCh0aGlzLiQuc3ZnKS5jYWxsKHRoaXMuX3pvb20pLm9uKCJkYmxjbGljay56b29tIixudWxsKSxIdCh3aW5kb3cpLm9uKCJyZXNpemUiLGZ1bmN0aW9uKCl7dGhpcy5taW5pbWFwLnpvb20oKX0uYmluZCh0aGlzKSksdGhpcy5taW5pbWFwPXRoaXMuJC5taW5pbWFwLmluaXQodGhpcy4kLnN2Zyx0aGlzLiQucm9vdCx0aGlzLl96b29tLFRyLm1pbmltYXAuc2l6ZSxUci5zdWJzY2VuZS5tZXRhLmxhYmVsSGVpZ2h0KX1hdHRhY2hlZCgpe3RoaXMuc2V0KCJfaXNBdHRhY2hlZCIsITApfWRldGFjaGVkKCl7dGhpcy5zZXQoIl9pc0F0dGFjaGVkIiwhMSl9X3JlbmRlckhpZXJhcmNoeUNoYW5nZWQoKXt2YXIgdD10aGlzLnJlbmRlckhpZXJhcmNoeTt0aGlzLl9oYXNSZW5kZXJIaWVyYXJjaHlCZWVuRml0T25jZT0hMSx0aGlzLl9yZXNldFN0YXRlKCksdGhpcy5fYnVpbGQodCl9X2FuaW1hdGVBbmRGaXQoKXt2YXIgdD10aGlzLl9pc0F0dGFjaGVkO3RoaXMuX2hhc1JlbmRlckhpZXJhcmNoeUJlZW5GaXRPbmNlfHwhdHx8c2V0VGltZW91dCh0aGlzLmZpdC5iaW5kKHRoaXMpLFRyLmFuaW1hdGlvbi5kdXJhdGlvbil9X3VwZGF0ZUxhYmVscyh0KXt2YXIgcj10aGlzLiQkKCIudGl0bGUiKSxuPXIuc3R5bGUsaT10aGlzLiQkKCIuYXV4VGl0bGUiKSxvPWkuc3R5bGUsYT10aGlzLiQkKCIuZnVuY3Rpb25MaWJyYXJ5VGl0bGUiKS5zdHlsZTtsZXQgcz1IdCh0aGlzLiQuc3ZnKTt2YXIgbD1zLnNlbGVjdCgiLiIrSGkuU2NlbmUuR1JPVVArIj4uIitIaS5TY2VuZS5DT1JFKS5ub2RlKCk7aWYodCYmbCYmdGhpcy5wcm9ncmVzcyYmdGhpcy5wcm9ncmVzcy52YWx1ZT09PTEwMCl7dmFyIGM9cy5zZWxlY3QoIi4iK0hpLlNjZW5lLkdST1VQKyI+LiIrSGkuU2NlbmUuSU5FWFRSQUNUKS5ub2RlKCl8fHMuc2VsZWN0KCIuIitIaS5TY2VuZS5HUk9VUCsiPi4iK0hpLlNjZW5lLk9VVEVYVFJBQ1QpLm5vZGUoKSx1PWwuZ2V0Q1RNKCkuZSxoPWM/Yy5nZXRDVE0oKS5lOm51bGw7bi5kaXNwbGF5PSJpbmxpbmUiLG4ubGVmdD11KyJweCIsaCE9PW51bGwmJmghPT11PyhvLmRpc3BsYXk9ImlubGluZSIsaD1NYXRoLm1heCh1K3IuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkud2lkdGgsaCksby5sZWZ0PWgrInB4Iik6by5kaXNwbGF5PSJub25lIjtsZXQgZj1zLnNlbGVjdCgiLiIrSGkuU2NlbmUuR1JPVVArIj4uIitIaS5TY2VuZS5GVU5DVElPTl9MSUJSQVJZKS5ub2RlKCkscD1mP2YuZ2V0Q1RNKCkuZTpudWxsO3AhPT1udWxsJiZwIT09aD8oYS5kaXNwbGF5PSJpbmxpbmUiLHA9TWF0aC5tYXgoaCtpLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLndpZHRoLHApLGEubGVmdD1wKyJweCIpOmEuZGlzcGxheT0ibm9uZSJ9ZWxzZSBuLmRpc3BsYXk9Im5vbmUiLG8uZGlzcGxheT0ibm9uZSIsYS5kaXNwbGF5PSJub25lIn1ub2RlQ29sb3JzQ2hhbmdlZCgpe3RoaXMucmVuZGVySGllcmFyY2h5IT1udWxsJiYodGhpcy50ZW1wbGF0ZUluZGV4PXRoaXMucmVuZGVySGllcmFyY2h5LmhpZXJhcmNoeS5nZXRUZW1wbGF0ZUluZGV4KCksZFAuZWFjaCh0aGlzLl9ub2RlR3JvdXBJbmRleCwodCxyKT0+e3RoaXMuX3VwZGF0ZU5vZGVTdGF0ZShyKX0pLHRoaXMubWluaW1hcC51cGRhdGUoKSl9Zml0KCl7dGhpcy5faGFzUmVuZGVySGllcmFyY2h5QmVlbkZpdE9uY2U9ITAsd2xlKHRoaXMuJC5zdmcsdGhpcy4kLnJvb3QsdGhpcy5fem9vbSxmdW5jdGlvbigpe3RoaXMuX3pvb21lZD0hMX0uYmluZCh0aGlzKSl9Z2V0SW1hZ2VCbG9iKCl7cmV0dXJuIHRoaXMubWluaW1hcC5nZXRJbWFnZUJsb2IoKX1pc05vZGVTZWxlY3RlZCh0KXtyZXR1cm4gdD09PXRoaXMuc2VsZWN0ZWROb2RlfWlzTm9kZUhpZ2hsaWdodGVkKHQpe3JldHVybiB0PT09dGhpcy5oaWdobGlnaHRlZE5vZGV9YWRkQW5ub3RhdGlvbkdyb3VwKHQscixuKXt2YXIgaT10Lm5vZGUubmFtZTt0aGlzLl9hbm5vdGF0aW9uR3JvdXBJbmRleFtpXT10aGlzLl9hbm5vdGF0aW9uR3JvdXBJbmRleFtpXXx8e30sdGhpcy5fYW5ub3RhdGlvbkdyb3VwSW5kZXhbaV1bci5ub2RlLm5hbWVdPW59Z2V0QW5ub3RhdGlvbkdyb3Vwc0luZGV4KHQpe3JldHVybiB0aGlzLl9hbm5vdGF0aW9uR3JvdXBJbmRleFt0XX1yZW1vdmVBbm5vdGF0aW9uR3JvdXAodCxyKXtkZWxldGUgdGhpcy5fYW5ub3RhdGlvbkdyb3VwSW5kZXhbdC5ub2RlLm5hbWVdW3Iubm9kZS5uYW1lXX1hZGROb2RlR3JvdXAodCxyKXt0aGlzLl9ub2RlR3JvdXBJbmRleFt0XT1yfWdldE5vZGVHcm91cCh0KXtyZXR1cm4gdGhpcy5fbm9kZUdyb3VwSW5kZXhbdF19cmVtb3ZlTm9kZUdyb3VwKHQpe2RlbGV0ZSB0aGlzLl9ub2RlR3JvdXBJbmRleFt0XX1hZGRFZGdlR3JvdXAodCxyKXt0aGlzLl9lZGdlR3JvdXBJbmRleFt0XT1yfWdldEVkZ2VHcm91cCh0KXtyZXR1cm4gdGhpcy5fZWRnZUdyb3VwSW5kZXhbdF19X3VwZGF0ZUhlYWx0aFBpbGxzKCl7dmFyIHQ9dGhpcy5ub2RlTmFtZXNUb0hlYWx0aFBpbGxzLHI9dGhpcy5oZWFsdGhQaWxsU3RlcEluZGV4O0FsZSh0aGlzLiQuc3ZnLHQscil9X3VwZGF0ZU5vZGVTdGF0ZSh0KXt2YXIgcj10aGlzLmdldE5vZGUodCksbj10aGlzLmdldE5vZGVHcm91cCh0KTtpZihuJiZzMyhuLHIsdGhpcyksci5ub2RlLnR5cGU9PT1qdC5NRVRBJiZyLm5vZGUuYXNzb2NpYXRlZEZ1bmN0aW9uJiYhci5pc0xpYnJhcnlGdW5jdGlvbil7dmFyIGk9U2Erci5ub2RlLmFzc29jaWF0ZWRGdW5jdGlvbixvPUh0KCIuIitIaS5TY2VuZS5HUk9VUCsiPi4iK0hpLlNjZW5lLkZVTkNUSU9OX0xJQlJBUlkrJyBnW2RhdGEtbmFtZT0iJytpKyciXScpO3MzKG8scix0aGlzKX12YXIgYT10aGlzLmdldEFubm90YXRpb25Hcm91cHNJbmRleCh0KTtkUC5lYWNoKGEsKHMsbCk9PntzMyhzLHIsdGhpcyxIaS5Bbm5vdGF0aW9uLk5PREUpfSl9X3NlbGVjdGVkTm9kZUNoYW5nZWQodCxyKXtpZih0IT09ciYmKHImJnRoaXMuX3VwZGF0ZU5vZGVTdGF0ZShyKSwhIXQpKXt0aGlzLm1pbmltYXAudXBkYXRlKCk7Zm9yKHZhciBuPXRoaXMucmVuZGVySGllcmFyY2h5LmhpZXJhcmNoeS5ub2RlKHQpLGk9W107bi5wYXJlbnROb2RlIT1udWxsJiZuLnBhcmVudE5vZGUubmFtZSE9cWM7KW49bi5wYXJlbnROb2RlLGkucHVzaChuLm5hbWUpO3ZhciBvO2RQLmZvckVhY2hSaWdodChpLGE9Pnt0aGlzLnJlbmRlckhpZXJhcmNoeS5idWlsZFN1YmhpZXJhcmNoeShhKTt2YXIgcz10aGlzLnJlbmRlckhpZXJhcmNoeS5nZXRSZW5kZXJOb2RlQnlOYW1lKGEpO3Mubm9kZS5pc0dyb3VwTm9kZSYmIXMuZXhwYW5kZWQmJihzLmV4cGFuZGVkPSEwLG98fChvPXMpKX0pLG8mJih0aGlzLnNldE5vZGVFeHBhbmRlZChvKSx0aGlzLl96b29tZWQ9ITApLHQmJnRoaXMuX3VwZGF0ZU5vZGVTdGF0ZSh0KSxzZXRUaW1lb3V0KCgpPT57dGhpcy5wYW5Ub05vZGUodCl9LFRyLmFuaW1hdGlvbi5kdXJhdGlvbil9fV9oaWdobGlnaHRlZE5vZGVDaGFuZ2VkKHQscil7dCE9PXImJih0JiZ0aGlzLl91cGRhdGVOb2RlU3RhdGUodCksciYmdGhpcy5fdXBkYXRlTm9kZVN0YXRlKHIpKX1fb25ab29tQ2hhbmdlZCgpe3RoaXMuX3VwZGF0ZUxhYmVscyghdGhpcy5fem9vbWVkKX1fZmlyZUVuYWJsZUNsaWNrKCl7dGhpcy5maXJlKCJlbmFibGUtY2xpY2siKX1fdXBkYXRlSW5wdXRUcmFjZSgpe1ZsZSh0aGlzLmdldEdyYXBoU3ZnUm9vdCgpLHRoaXMucmVuZGVySGllcmFyY2h5LHRoaXMuc2VsZWN0ZWROb2RlLHRoaXMudHJhY2VJbnB1dHMpfX07THIudGVtcGxhdGU9V2xlO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLGxvKV0sTHIucHJvdG90eXBlLCJyZW5kZXJIaWVyYXJjaHkiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sTHIucHJvdG90eXBlLCJuYW1lIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLExyLnByb3RvdHlwZSwiY29sb3JCeSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLExyLnByb3RvdHlwZSwidHJhY2VJbnB1dHMiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxMci5wcm90b3R5cGUsIl9oYXNSZW5kZXJIaWVyYXJjaHlCZWVuRml0T25jZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLExyLnByb3RvdHlwZSwiX2lzQXR0YWNoZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sTHIucHJvdG90eXBlLCJfem9vbSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZyxvYnNlcnZlcjoiX2hpZ2hsaWdodGVkTm9kZUNoYW5nZWQifSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLExyLnByb3RvdHlwZSwiaGlnaGxpZ2h0ZWROb2RlIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG9ic2VydmVyOiJfc2VsZWN0ZWROb2RlQ2hhbmdlZCJ9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sTHIucHJvdG90eXBlLCJzZWxlY3RlZE5vZGUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sTHIucHJvdG90eXBlLCJoYW5kbGVFZGdlU2VsZWN0ZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLG9ic2VydmVyOiJfb25ab29tQ2hhbmdlZCJ9KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLExyLnByb3RvdHlwZSwiX3pvb21lZCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxMci5wcm90b3R5cGUsIl96b29tU3RhcnRDb29yZHMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sTHIucHJvdG90eXBlLCJfem9vbVRyYW5zZm9ybSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxMci5wcm90b3R5cGUsIl9tYXhab29tRGlzdGFuY2VGb3JDbGljayIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbildLExyLnByb3RvdHlwZSwidGVtcGxhdGVJbmRleCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxMci5wcm90b3R5cGUsIl9ub2RlR3JvdXBJbmRleCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxMci5wcm90b3R5cGUsIl9hbm5vdGF0aW9uR3JvdXBJbmRleCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxMci5wcm90b3R5cGUsIl9lZGdlR3JvdXBJbmRleCIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxMci5wcm90b3R5cGUsIm1heE1ldGFub2RlTGFiZWxMZW5ndGhGb250U2l6ZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxMci5wcm90b3R5cGUsIm1pbk1ldGFub2RlTGFiZWxMZW5ndGhGb250U2l6ZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxMci5wcm90b3R5cGUsIm1heE1ldGFub2RlTGFiZWxMZW5ndGhMYXJnZUZvbnQiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sTHIucHJvdG90eXBlLCJtYXhNZXRhbm9kZUxhYmVsTGVuZ3RoIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLExyLnByb3RvdHlwZSwicHJvZ3Jlc3MiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLExyLnByb3RvdHlwZSwibm9kZUNvbnRleHRNZW51SXRlbXMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sTHIucHJvdG90eXBlLCJub2RlTmFtZXNUb0hlYWx0aFBpbGxzIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLExyLnByb3RvdHlwZSwiaGVhbHRoUGlsbFN0ZXBJbmRleCIsdm9pZCAwKTtFKFtCdCgicmVuZGVySGllcmFyY2h5IiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxMci5wcm90b3R5cGUsIl9yZW5kZXJIaWVyYXJjaHlDaGFuZ2VkIixudWxsKTtFKFtCdCgiX2lzQXR0YWNoZWQiLCJyZW5kZXJIaWVyYXJjaHkiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLExyLnByb3RvdHlwZSwiX2FuaW1hdGVBbmRGaXQiLG51bGwpO0UoW0J0KCJjb2xvckJ5IiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxMci5wcm90b3R5cGUsIm5vZGVDb2xvcnNDaGFuZ2VkIixudWxsKTtFKFtCdCgibm9kZU5hbWVzVG9IZWFsdGhQaWxscyIsImhlYWx0aFBpbGxTdGVwSW5kZXgiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLExyLnByb3RvdHlwZSwiX3VwZGF0ZUhlYWx0aFBpbGxzIixudWxsKTtFKFtCdCgidHJhY2VJbnB1dHMiLCJzZWxlY3RlZE5vZGUiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLExyLnByb3RvdHlwZSwiX3VwZGF0ZUlucHV0VHJhY2UiLG51bGwpO0xyPUUoW3l0KCJ0Zi1ncmFwaC1zY2VuZSIpXSxMcik7dmFyIERyPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl9yZW5kZXJEZXB0aD0xLHRoaXMuX2FsbG93R3JhcGhTZWxlY3Q9ITAsdGhpcy5lZGdlV2lkdGhGdW5jdGlvbj0iIix0aGlzLmhhbmRsZU5vZGVTZWxlY3RlZD0iIix0aGlzLmVkZ2VMYWJlbEZ1bmN0aW9uPSIiLHRoaXMuaGFuZGxlRWRnZVNlbGVjdGVkPSIifXBhblRvTm9kZSh0KXt0aGlzLiQkKCJ0Zi1ncmFwaC1zY2VuZSIpLnBhblRvTm9kZSh0KX1fYXV0b0V4dHJhY3ROb2Rlc0NoYW5nZWQoKXt2YXIgdD10aGlzLmdyYXBoSGllcmFyY2h5O2lmKCEhdCl7Zm9yKGxldCByIG9mIE9iamVjdC52YWx1ZXModC5nZXROb2RlTWFwKCkpKXIuaW5jbHVkZT11ci5VTlNQRUNJRklFRDt0aGlzLl9idWlsZFJlbmRlckhpZXJhcmNoeSh0KX19X2J1aWxkTmV3UmVuZGVySGllcmFyY2h5KCl7dmFyIHQ9dGhpcy5ncmFwaEhpZXJhcmNoeTshdHx8dGhpcy5fYnVpbGRSZW5kZXJIaWVyYXJjaHkodCl9X3N0YXRzQ2hhbmdlZCgpe3ZhciB0PXRoaXMuc3RhdHMscj10aGlzLmRldmljZXNGb3JTdGF0czt0aGlzLmdyYXBoSGllcmFyY2h5JiYodCYmciYmKGVsZSh0aGlzLmJhc2ljR3JhcGgsdCxyKSxsbGUodGhpcy5ncmFwaEhpZXJhcmNoeSx0KSksdGhpcy5fYnVpbGRSZW5kZXJIaWVyYXJjaHkodGhpcy5ncmFwaEhpZXJhcmNoeSkpfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImdyYXBoLXNlbGVjdCIsdGhpcy5fZ3JhcGhTZWxlY3RlZC5iaW5kKHRoaXMpKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImRpc2FibGUtY2xpY2siLHRoaXMuX2Rpc2FibGVDbGljay5iaW5kKHRoaXMpKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImVuYWJsZS1jbGljayIsdGhpcy5fZW5hYmxlQ2xpY2suYmluZCh0aGlzKSksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJub2RlLXRvZ2dsZS1leHBhbmQiLHRoaXMuX25vZGVUb2dnbGVFeHBhbmQuYmluZCh0aGlzKSksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJub2RlLXNlbGVjdCIsdGhpcy5fbm9kZVNlbGVjdGVkLmJpbmQodGhpcykpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigibm9kZS1oaWdobGlnaHQiLHRoaXMuX25vZGVIaWdobGlnaHRlZC5iaW5kKHRoaXMpKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoIm5vZGUtdW5oaWdobGlnaHQiLHRoaXMuX25vZGVVbmhpZ2hsaWdodGVkLmJpbmQodGhpcykpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigibm9kZS10b2dnbGUtZXh0cmFjdCIsdGhpcy5fbm9kZVRvZ2dsZUV4dHJhY3QuYmluZCh0aGlzKSksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJub2RlLXRvZ2dsZS1zZXJpZXNncm91cCIsdGhpcy5fbm9kZVRvZ2dsZVNlcmllc0dyb3VwLmJpbmQodGhpcykpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigiZWRnZS1zZWxlY3QiLHRoaXMuX2VkZ2VTZWxlY3RlZC5iaW5kKHRoaXMpKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImFubm90YXRpb24tc2VsZWN0Iix0aGlzLl9ub2RlU2VsZWN0ZWQuYmluZCh0aGlzKSksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJhbm5vdGF0aW9uLWhpZ2hsaWdodCIsdGhpcy5fbm9kZUhpZ2hsaWdodGVkLmJpbmQodGhpcykpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigiYW5ub3RhdGlvbi11bmhpZ2hsaWdodCIsdGhpcy5fbm9kZVVuaGlnaGxpZ2h0ZWQuYmluZCh0aGlzKSl9X2J1aWxkUmVuZGVySGllcmFyY2h5KHQpe2lmKHQucm9vdC50eXBlIT09anQuTUVUQSlyZXR1cm47bGV0IHI9dGhpcyxuPVJkKCJuZXcgdGZfZ3JhcGhfcmVuZGVyLkhpZXJhcmNoeSIsKCk9PntsZXQgaT1uZXcgbG8odCwhIXRoaXMuc3RhdHMsdGhpcy5hdXRvRXh0cmFjdE5vZGVzKTtpLmVkZ2VMYWJlbEZ1bmN0aW9uPXRoaXMuZWRnZUxhYmVsRnVuY3Rpb24saS5lZGdlV2lkdGhGdW5jdGlvbj10aGlzLmVkZ2VXaWR0aEZ1bmN0aW9uO2Z1bmN0aW9uIG8oYSl7cmV0dXJue21pblZhbHVlOmEuZG9tYWluKClbMF0sbWF4VmFsdWU6YS5kb21haW4oKVsxXSxzdGFydENvbG9yOmEucmFuZ2UoKVswXSxlbmRDb2xvcjphLnJhbmdlKClbMV19fXJldHVybiByLl9zZXRDb2xvckJ5UGFyYW1zKHtjb21wdXRlX3RpbWU6byhpLmNvbXB1dGVUaW1lU2NhbGUpLG1lbW9yeTpvKGkubWVtb3J5VXNhZ2VTY2FsZSksZGV2aWNlOm9jdC5tYXAoaS5kZXZpY2VDb2xvck1hcC5kb21haW4oKSxmdW5jdGlvbihhKXtyZXR1cm57ZGV2aWNlOmEsY29sb3I6aS5kZXZpY2VDb2xvck1hcChhKX19KSx4bGFfY2x1c3RlcjpvY3QubWFwKGkueGxhQ2x1c3RlckNvbG9yTWFwLmRvbWFpbigpLGZ1bmN0aW9uKGEpe3JldHVybnt4bGFfY2x1c3RlcjphLGNvbG9yOmkueGxhQ2x1c3RlckNvbG9yTWFwKGEpfX0pfSksaX0sanIuUkVOREVSX0JVSUxEX0hJRVJBUkNIWSk7ci5fc2V0UmVuZGVySGllcmFyY2h5KG4pfV9nZXRWaXNpYmxlKHQpe3JldHVybiB0JiZ0aGlzLnJlbmRlckhpZXJhcmNoeS5nZXROZWFyZXN0VmlzaWJsZUFuY2VzdG9yKHQpfWZpdCgpe3RoaXMuJC5zY2VuZS5maXQoKX1nZXRJbWFnZUJsb2IoKXtyZXR1cm4gdGhpcy4kLnNjZW5lLmdldEltYWdlQmxvYigpfV9ncmFwaENoYW5nZWQoKXshdGhpcy5ncmFwaEhpZXJhcmNoeXx8KHRoaXMuZ3JhcGhIaWVyYXJjaHkuYWRkTGlzdGVuZXIoRGQuVEVNUExBVEVTX1VQREFURUQsKCk9Pnt0aGlzLiQuc2NlbmUubm9kZUNvbG9yc0NoYW5nZWQoKX0pLHRoaXMuZmlyZSgiZ3JhcGgtc2VsZWN0IikpfV9ncmFwaFNlbGVjdGVkKHQpe3RoaXMuX2FsbG93R3JhcGhTZWxlY3QmJih0aGlzLnNldCgic2VsZWN0ZWROb2RlIixudWxsKSx0aGlzLnNldCgic2VsZWN0ZWRFZGdlIixudWxsKSksdGhpcy5fYWxsb3dHcmFwaFNlbGVjdD0hMH1fZGlzYWJsZUNsaWNrKHQpe3RoaXMuX2FsbG93R3JhcGhTZWxlY3Q9ITF9X2VuYWJsZUNsaWNrKHQpe3RoaXMuX2FsbG93R3JhcGhTZWxlY3Q9ITB9X3NlbGVjdGVkTm9kZUNoYW5nZWQoKXt2YXIgdD10aGlzLnNlbGVjdGVkTm9kZTt0aGlzLmhhbmRsZU5vZGVTZWxlY3RlZCYmdGhpcy5oYW5kbGVOb2RlU2VsZWN0ZWQodCl9X3NlbGVjdGVkRWRnZUNoYW5nZWQoKXt2YXIgdD10aGlzLnNlbGVjdGVkRWRnZTt0aGlzLl9kZXNlbGVjdFByZXZpb3VzRWRnZSgpLHQmJih0aGlzLl9sYXN0U2VsZWN0ZWRFZGdlR3JvdXAuY2xhc3NlZChIaS5FZGdlLlNFTEVDVEVELCEwKSx0aGlzLl91cGRhdGVNYXJrZXJPZlNlbGVjdGVkRWRnZSh0KSksdGhpcy5oYW5kbGVFZGdlU2VsZWN0ZWQmJnRoaXMuaGFuZGxlRWRnZVNlbGVjdGVkKHQpfV9ub2RlU2VsZWN0ZWQodCl7dGhpcy5fYWxsb3dHcmFwaFNlbGVjdCYmdGhpcy5zZXQoInNlbGVjdGVkTm9kZSIsdC5kZXRhaWwubmFtZSksdGhpcy5fYWxsb3dHcmFwaFNlbGVjdD0hMH1fZWRnZVNlbGVjdGVkKHQpe3RoaXMuX2FsbG93R3JhcGhTZWxlY3QmJih0aGlzLnNldCgiX2xhc3RTZWxlY3RlZEVkZ2VHcm91cCIsdC5kZXRhaWwuZWRnZUdyb3VwKSx0aGlzLnNldCgic2VsZWN0ZWRFZGdlIix0LmRldGFpbC5lZGdlRGF0YSkpLHRoaXMuX2FsbG93R3JhcGhTZWxlY3Q9ITB9X25vZGVIaWdobGlnaHRlZCh0KXt0aGlzLnNldCgiaGlnaGxpZ2h0ZWROb2RlIix0LmRldGFpbC5uYW1lKX1fbm9kZVVuaGlnaGxpZ2h0ZWQodCl7dGhpcy5zZXQoImhpZ2hsaWdodGVkTm9kZSIsbnVsbCl9X25vZGVUb2dnbGVFeHBhbmQodCl7dGhpcy5fbm9kZVNlbGVjdGVkKHQpO3ZhciByPXQuZGV0YWlsLm5hbWUsbj10aGlzLnJlbmRlckhpZXJhcmNoeS5nZXRSZW5kZXJOb2RlQnlOYW1lKHIpO24ubm9kZS50eXBlIT09anQuT1AmJih0aGlzLnJlbmRlckhpZXJhcmNoeS5idWlsZFN1YmhpZXJhcmNoeShyKSxuLmV4cGFuZGVkPSFuLmV4cGFuZGVkLHRoaXMuYXN5bmMoZnVuY3Rpb24oKXt0aGlzLiQuc2NlbmUuc2V0Tm9kZUV4cGFuZGVkKG4pfSw3NSksUG8oe2FjdGlvbklkOmpyLk5PREVfRVhQQU5TSU9OX1RPR0dMRUQsZXZlbnRMYWJlbDpuLmV4cGFuZGVkPyJleHBhbmRlZCI6ImNvbGxhcHNlZCJ9KSl9X25vZGVUb2dnbGVFeHRyYWN0KHQpe3ZhciByPXQuZGV0YWlsLm5hbWU7dGhpcy5ub2RlVG9nZ2xlRXh0cmFjdChyKX1ub2RlVG9nZ2xlRXh0cmFjdCh0KXtsZXQgcj10aGlzLnJlbmRlckhpZXJhcmNoeS5nZXRSZW5kZXJOb2RlQnlOYW1lKHQpO3Iubm9kZS5pbmNsdWRlPT11ci5JTkNMVURFP3Iubm9kZS5pbmNsdWRlPXVyLkVYQ0xVREU6ci5ub2RlLmluY2x1ZGU9PXVyLkVYQ0xVREU/ci5ub2RlLmluY2x1ZGU9dXIuSU5DTFVERTpyLm5vZGUuaW5jbHVkZT10aGlzLnJlbmRlckhpZXJhcmNoeS5pc05vZGVBdXhpbGlhcnkocik/dXIuSU5DTFVERTp1ci5FWENMVURFLHRoaXMuX2J1aWxkUmVuZGVySGllcmFyY2h5KHRoaXMuZ3JhcGhIaWVyYXJjaHkpLFBvKHthY3Rpb25JZDpqci5OT0RFX0FVWElMSUFSWV9FWFRSQUNUSU9OX0NIQU5HRUQsZXZlbnRMYWJlbDpyLm5vZGUuaW5jbHVkZT09PXVyLklOQ0xVREU/IkF1eGlsaWFyeSB0byBNYWluIjoiTWFpbiB0byBBdXhpbGlhcnkifSl9X25vZGVUb2dnbGVTZXJpZXNHcm91cCh0KXt2YXIgcj10LmRldGFpbC5uYW1lO3RoaXMubm9kZVRvZ2dsZVNlcmllc0dyb3VwKHIpfW5vZGVUb2dnbGVTZXJpZXNHcm91cCh0KXt0aGlzLnNldCgicHJvZ3Jlc3MiLHt2YWx1ZTowLG1zZzoiIn0pO3ZhciByPXJQKHRoaXMpLG49SlMociwxMDAsIk5hbWVzcGFjZSBoaWVyYXJjaHkiKTtsZXQgaT1NeChLbCh7fSx0aGlzLmhpZXJhcmNoeVBhcmFtcykse3Nlcmllc01hcDp0aGlzLmdyYXBoSGllcmFyY2h5LmJ1aWxkU2VyaWVzR3JvdXBNYXBUb2dnbGVkKHQpfSk7eEgodGhpcy5iYXNpY0dyYXBoLGksbikudGhlbihmdW5jdGlvbihvKXt0aGlzLnNldCgiZ3JhcGhIaWVyYXJjaHkiLG8pLHRoaXMuX2J1aWxkUmVuZGVySGllcmFyY2h5KHRoaXMuZ3JhcGhIaWVyYXJjaHkpfS5iaW5kKHRoaXMpKX1fZGVzZWxlY3RQcmV2aW91c0VkZ2UoKXtsZXQgdD0iLiIrSGkuRWRnZS5TRUxFQ1RFRDtIdCh0KS5jbGFzc2VkKEhpLkVkZ2UuU0VMRUNURUQsITEpLmVhY2goKHIsbik9PntpZihyLmxhYmVsKXtsZXQgaT1IdCh0aGlzKS5zZWxlY3RBbGwoInBhdGguZWRnZWxpbmUiKTtyLmxhYmVsLnN0YXJ0TWFya2VySWQmJmkuc3R5bGUoIm1hcmtlci1zdGFydCIsYHVybCgjJHtyLmxhYmVsLnN0YXJ0TWFya2VySWR9KWApLHIubGFiZWwuZW5kTWFya2VySWQmJmkuc3R5bGUoIm1hcmtlci1lbmQiLGB1cmwoIyR7ci5sYWJlbC5lbmRNYXJrZXJJZH0pYCl9fSl9X3VwZGF0ZU1hcmtlck9mU2VsZWN0ZWRFZGdlKHQpe3ZhciByO2lmKHQubGFiZWwpe2xldCBuPXQubGFiZWwuc3RhcnRNYXJrZXJJZHx8dC5sYWJlbC5lbmRNYXJrZXJJZDtpZihuKXtsZXQgaT1uLnJlcGxhY2UoImRhdGFmbG93LSIsInNlbGVjdGVkLSIpLG89dGhpcy4kJCgiIyIraSk7aWYoIW8pe2xldCBzPXRoaXMuJC5zY2VuZS5xdWVyeVNlbGVjdG9yKCIjIituKTtvPXM9PW51bGw/dm9pZCAwOnMuY2xvbmVOb2RlKCEwKSxvLnNldEF0dHJpYnV0ZSgiaWQiLGkpLG8uY2xhc3NMaXN0LmFkZCgic2VsZWN0ZWQtYXJyb3doZWFkIiksKHI9cz09bnVsbD92b2lkIDA6cy5wYXJlbnROb2RlKT09bnVsbHx8ci5hcHBlbmRDaGlsZChvKX1sZXQgYT10LmxhYmVsLnN0YXJ0TWFya2VySWQ/Im1hcmtlci1zdGFydCI6Im1hcmtlci1lbmQiO3RoaXMuX2xhc3RTZWxlY3RlZEVkZ2VHcm91cC5zZWxlY3RBbGwoInBhdGguZWRnZWxpbmUiKS5zdHlsZShhLGB1cmwoIyR7aX0pYCl9fX1ub3QodCl7cmV0dXJuIXR9fTtEci50ZW1wbGF0ZT1RYAogICAgPHN0eWxlPgogICAgICAuY29udGFpbmVyIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgYmFja2dyb3VuZDogd2hpdGU7CiAgICAgICAgYm94LXNoYWRvdzogMCAxcHggNXB4IHJnYmEoMCwgMCwgMCwgMC4yKTsKICAgICAgfQoKICAgICAgLnZlcnRpY2FsIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LXZlcnRpY2FsOwogICAgICB9CgogICAgICAuYXV0byB7CiAgICAgICAgQGFwcGx5IC0tbGF5b3V0LWZsZXgtYXV0bzsKICAgICAgICBAYXBwbHkgLS1sYXlvdXQtdmVydGljYWw7CiAgICAgIH0KCiAgICAgIGgyIHsKICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7CiAgICAgIH0KCiAgICAgIHBhcGVyLWJ1dHRvbiB7CiAgICAgICAgdGV4dC10cmFuc2Zvcm06IG5vbmU7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgogICAgICA8ZGl2IGNsYXNzPSJ2ZXJ0aWNhbCI+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW3RpdGxlXV0iPgogICAgICAgICAgPGgyPltbdGl0bGVdXTwvaDI+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8dGYtZ3JhcGgtc2NlbmUKICAgICAgICAgIGlkPSJzY2VuZSIKICAgICAgICAgIGNsYXNzPSJhdXRvIgogICAgICAgICAgcmVuZGVyLWhpZXJhcmNoeT0iW1tyZW5kZXJIaWVyYXJjaHldXSIKICAgICAgICAgIGhpZ2hsaWdodGVkLW5vZGU9IltbX2dldFZpc2libGUoaGlnaGxpZ2h0ZWROb2RlKV1dIgogICAgICAgICAgc2VsZWN0ZWQtbm9kZT0ie3tzZWxlY3RlZE5vZGV9fSIKICAgICAgICAgIHNlbGVjdGVkLWVkZ2U9Int7c2VsZWN0ZWRFZGdlfX0iCiAgICAgICAgICBjb2xvci1ieT0iW1tjb2xvckJ5XV0iCiAgICAgICAgICBwcm9ncmVzcz0iW1twcm9ncmVzc11dIgogICAgICAgICAgbm9kZS1jb250ZXh0LW1lbnUtaXRlbXM9Iltbbm9kZUNvbnRleHRNZW51SXRlbXNdXSIKICAgICAgICAgIG5vZGUtbmFtZXMtdG8taGVhbHRoLXBpbGxzPSJbW25vZGVOYW1lc1RvSGVhbHRoUGlsbHNdXSIKICAgICAgICAgIGhlYWx0aC1waWxsLXN0ZXAtaW5kZXg9Int7aGVhbHRoUGlsbFN0ZXBJbmRleH19IgogICAgICAgICAgaGFuZGxlLWVkZ2Utc2VsZWN0ZWQ9IltbaGFuZGxlRWRnZVNlbGVjdGVkXV0iCiAgICAgICAgICB0cmFjZS1pbnB1dHM9IltbdHJhY2VJbnB1dHNdXSIKICAgICAgICA+PC90Zi1ncmFwaC1zY2VuZT4KICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0LG5vdGlmeTohMCxvYnNlcnZlcjoiX2dyYXBoQ2hhbmdlZCJ9KSx3KCJkZXNpZ246dHlwZSIsb3MpXSxEci5wcm90b3R5cGUsImdyYXBoSGllcmFyY2h5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLFh1KV0sRHIucHJvdG90eXBlLCJiYXNpY0dyYXBoIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLERyLnByb3RvdHlwZSwic3RhdHMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sRHIucHJvdG90eXBlLCJkZXZpY2VzRm9yU3RhdHMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sRHIucHJvdG90eXBlLCJoaWVyYXJjaHlQYXJhbXMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3Qsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLERyLnByb3RvdHlwZSwicHJvZ3Jlc3MiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sRHIucHJvdG90eXBlLCJ0aXRsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZyxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sRHIucHJvdG90eXBlLCJzZWxlY3RlZE5vZGUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3Qsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLERyLnByb3RvdHlwZSwic2VsZWN0ZWRFZGdlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLERyLnByb3RvdHlwZSwiX2xhc3RTZWxlY3RlZEVkZ2VHcm91cCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZyxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sRHIucHJvdG90eXBlLCJoaWdobGlnaHRlZE5vZGUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sRHIucHJvdG90eXBlLCJjb2xvckJ5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0LG5vdGlmeTohMCxyZWFkT25seTohMH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxEci5wcm90b3R5cGUsImNvbG9yQnlQYXJhbXMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3QscmVhZE9ubHk6ITAsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLGxvKV0sRHIucHJvdG90eXBlLCJyZW5kZXJIaWVyYXJjaHkiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxEci5wcm90b3R5cGUsInRyYWNlSW5wdXRzIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sRHIucHJvdG90eXBlLCJhdXRvRXh0cmFjdE5vZGVzIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxEci5wcm90b3R5cGUsIm5vZGVDb250ZXh0TWVudUl0ZW1zIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLERyLnByb3RvdHlwZSwiX3JlbmRlckRlcHRoIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sRHIucHJvdG90eXBlLCJfYWxsb3dHcmFwaFNlbGVjdCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxEci5wcm90b3R5cGUsIm5vZGVOYW1lc1RvSGVhbHRoUGlsbHMiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sRHIucHJvdG90eXBlLCJoZWFsdGhQaWxsU3RlcEluZGV4Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLERyLnByb3RvdHlwZSwiZWRnZVdpZHRoRnVuY3Rpb24iLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sRHIucHJvdG90eXBlLCJoYW5kbGVOb2RlU2VsZWN0ZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sRHIucHJvdG90eXBlLCJlZGdlTGFiZWxGdW5jdGlvbiIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxEci5wcm90b3R5cGUsImhhbmRsZUVkZ2VTZWxlY3RlZCIsdm9pZCAwKTtFKFtCdCgiYXV0b0V4dHJhY3ROb2RlcyIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sRHIucHJvdG90eXBlLCJfYXV0b0V4dHJhY3ROb2Rlc0NoYW5nZWQiLG51bGwpO0UoW0J0KCJncmFwaEhpZXJhcmNoeSIsImVkZ2VXaWR0aEZ1bmN0aW9uIiwiaGFuZGxlTm9kZVNlbGVjdGVkIiwiZWRnZUxhYmVsRnVuY3Rpb24iLCJoYW5kbGVFZGdlU2VsZWN0ZWQiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLERyLnByb3RvdHlwZSwiX2J1aWxkTmV3UmVuZGVySGllcmFyY2h5IixudWxsKTtFKFtCdCgic3RhdHMiLCJkZXZpY2VzRm9yU3RhdHMiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLERyLnByb3RvdHlwZSwiX3N0YXRzQ2hhbmdlZCIsbnVsbCk7RShbQnQoInNlbGVjdGVkTm9kZSIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sRHIucHJvdG90eXBlLCJfc2VsZWN0ZWROb2RlQ2hhbmdlZCIsbnVsbCk7RShbQnQoInNlbGVjdGVkRWRnZSIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sRHIucHJvdG90eXBlLCJfc2VsZWN0ZWRFZGdlQ2hhbmdlZCIsbnVsbCk7RHI9RShbeXQoInRmLWdyYXBoIildLERyKTt2YXIgY289Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuc3BlY2lmaWNIZWFsdGhQaWxsU3RlcD0wLHRoaXMuaGVhbHRoUGlsbEVudHJpZXM9aFB9cmVhZHkoKXtzdXBlci5yZWFkeSgpO3ZhciB0PWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJtYWluQ29udGFpbmVyIikscj1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKCJ0Zi1kYXNoYm9hcmQtbGF5b3V0IC5zY3JvbGxiYXIiKTt0JiZyJiYodC5zdHlsZS5vdmVyZmxvdz0iaGlkZGVuIixyLnN0eWxlLm92ZXJmbG93PSJoaWRkZW4iKX1faGVhbHRoUGlsbHNBdmFpbGFibGUodCxyKXtyZXR1cm4gdCYmcn1fY29tcHV0ZVRlbnNvckNvdW50U3RyaW5nKHQscil7cmV0dXJuIHQ/dFtyXS50b0ZpeGVkKDApOiIifWdldCBoZWFsdGhQaWxsVmFsdWVzRm9yU2VsZWN0ZWROb2RlKCl7dmFyIHQ9dGhpcy5ub2RlTmFtZXNUb0hlYWx0aFBpbGxzLHI9dGhpcy5oZWFsdGhQaWxsU3RlcEluZGV4LG49dGhpcy5zZWxlY3RlZE5vZGUsaT10aGlzLmFsbFN0ZXBzTW9kZUVuYWJsZWQsbz10aGlzLmFyZUhlYWx0aFBpbGxzTG9hZGluZztpZihvfHwhbilyZXR1cm4gbnVsbDtsZXQgYT10W25dO2lmKCFhKXJldHVybiBudWxsO2xldCBzPWFbaT8wOnJdO3JldHVybiBzP3MudmFsdWUuc2xpY2UoMiw4KTpudWxsfWdldCBfY3VycmVudFN0ZXBEaXNwbGF5VmFsdWUoKXt2YXIgdD10aGlzLm5vZGVOYW1lc1RvSGVhbHRoUGlsbHMscj10aGlzLmhlYWx0aFBpbGxTdGVwSW5kZXgsbj10aGlzLmFsbFN0ZXBzTW9kZUVuYWJsZWQsaT10aGlzLnNwZWNpZmljSGVhbHRoUGlsbFN0ZXAsbz10aGlzLmFyZUhlYWx0aFBpbGxzTG9hZGluZztpZihuKXJldHVybiBpLnRvRml4ZWQoMCk7aWYobylyZXR1cm4gMDtmb3IobGV0IGEgaW4gdClyZXR1cm4gdFthXVtyXS5zdGVwLnRvRml4ZWQoMCk7cmV0dXJuIDB9Z2V0IF9iaWdnZXN0U3RlcEV2ZXJTZWVuKCl7dmFyIHQ9dGhpcy5ub2RlTmFtZXNUb0hlYWx0aFBpbGxzO2ZvcihsZXQgbiBpbiB0KXt2YXIgcj10W25dO3JldHVybiBNYXRoLm1heCh0aGlzLl9iaWdnZXN0U3RlcEV2ZXJTZWVuLHJbci5sZW5ndGgtMV0uc3RlcCl9cmV0dXJuIHRoaXMuX2JpZ2dlc3RTdGVwRXZlclNlZW58fDB9Z2V0IF9tYXhTdGVwSW5kZXgoKXt2YXIgdD10aGlzLm5vZGVOYW1lc1RvSGVhbHRoUGlsbHM7Zm9yKGxldCByIGluIHQpcmV0dXJuIHRbcl0ubGVuZ3RoLTE7cmV0dXJuIDB9X2hhc0RlYnVnZ2VyTnVtZXJpY0FsZXJ0cyh0KXtyZXR1cm4gdCYmdC5sZW5ndGh9X3VwZGF0ZUFsZXJ0c0xpc3QoKXt2YXIgdD10aGlzLmRlYnVnZ2VyTnVtZXJpY0FsZXJ0cyxyPXRoaXMuJCQoIiNudW1lcmljLWFsZXJ0cy1ib2R5Iik7aWYoISFyKXtyLmlubmVyVGV4dD0iIjtmb3IodmFyIG49MDtuPHQubGVuZ3RoO24rKyl7dmFyIGk9dFtuXSxvPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInRyIiksYT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZCIpO2EuaW5uZXJUZXh0PVhzZShpLmZpcnN0X3RpbWVzdGFtcCksYS5jbGFzc0xpc3QuYWRkKCJmaXJzdC1vZmZlbnNlLXRkIiksby5hcHBlbmRDaGlsZChhKTt2YXIgcz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZCIpO3MuY2xhc3NMaXN0LmFkZCgidGVuc29yLWRldmljZS10ZCIpO3ZhciBsPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpO2wuY2xhc3NMaXN0LmFkZCgidGVuc29yLXNlY3Rpb24td2l0aGluLXRhYmxlIiksbC5pbm5lclRleHQ9aS50ZW5zb3JfbmFtZSx0aGlzLl9hZGRPcEV4cGFuc2lvbkxpc3RlbmVyKGwsaS50ZW5zb3JfbmFtZSkscy5hcHBlbmRDaGlsZChsKTt2YXIgYz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTtjLmNsYXNzTGlzdC5hZGQoImRldmljZS1zZWN0aW9uLXdpdGhpbi10YWJsZSIpLGMuaW5uZXJUZXh0PSIoIitpLmRldmljZV9uYW1lKyIpIixzLmFwcGVuZENoaWxkKGMpLG8uYXBwZW5kQ2hpbGQocyk7dmFyIHU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7dS5jbGFzc0xpc3QuYWRkKCJtaW5pLWhlYWx0aC1waWxsIik7dmFyIGg9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGQiKTtpZihoLmNsYXNzTGlzdC5hZGQoIm1pbmktaGVhbHRoLXBpbGwtdGQiKSxoLmFwcGVuZENoaWxkKHUpLG8uYXBwZW5kQ2hpbGQoaCksaS5uZWdfaW5mX2V2ZW50X2NvdW50KXt2YXIgZj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTtmLmNsYXNzTGlzdC5hZGQoIm5lZ2F0aXZlLWluZi1taW5pLWhlYWx0aC1waWxsLXNlY3Rpb24iKSxmLmlubmVyVGV4dD1pLm5lZ19pbmZfZXZlbnRfY291bnQsZi5zZXRBdHRyaWJ1dGUoInRpdGxlIixpLm5lZ19pbmZfZXZlbnRfY291bnQrIiBldmVudHMgd2l0aCAtXHUyMjFFIiksdS5hcHBlbmRDaGlsZChmKX1pZihpLnBvc19pbmZfZXZlbnRfY291bnQpe3ZhciBwPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpO3AuY2xhc3NMaXN0LmFkZCgicG9zaXRpdmUtaW5mLW1pbmktaGVhbHRoLXBpbGwtc2VjdGlvbiIpLHAuaW5uZXJUZXh0PWkucG9zX2luZl9ldmVudF9jb3VudCxwLnNldEF0dHJpYnV0ZSgidGl0bGUiLGkucG9zX2luZl9ldmVudF9jb3VudCsiIGV2ZW50cyB3aXRoICtcdTIyMUUiKSx1LmFwcGVuZENoaWxkKHApfWlmKGkubmFuX2V2ZW50X2NvdW50KXt2YXIgZD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTtkLmNsYXNzTGlzdC5hZGQoIm5hbi1taW5pLWhlYWx0aC1waWxsLXNlY3Rpb24iKSxkLmlubmVyVGV4dD1pLm5hbl9ldmVudF9jb3VudCxkLnNldEF0dHJpYnV0ZSgidGl0bGUiLGkubmFuX2V2ZW50X2NvdW50KyIgZXZlbnRzIHdpdGggTmFOIiksdS5hcHBlbmRDaGlsZChkKX16dChyKS5hcHBlbmRDaGlsZChvKX19fV9hZGRPcEV4cGFuc2lvbkxpc3RlbmVyKHQscil7dC5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsKCk9Pnt2YXIgbj1tbGUoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNjZW5lIiksdGhpcy5yZW5kZXJIaWVyYXJjaHksciksaSxvPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoInRmLWdyYXBoLWluZm8jZ3JhcGgtaW5mbyIpO28mJihpPW8uc2Nyb2xsSGVpZ2h0LW8uc2Nyb2xsVG9wKTt2YXIgYT10aGlzLnNlbGVjdGVkTm9kZTt0aGlzLnNldCgic2VsZWN0ZWROb2RlIixuKTt2YXIgcz0oKT0+e28uc2Nyb2xsVG9wPW8uc2Nyb2xsSGVpZ2h0LWl9O28mJihhP3MoKTp3aW5kb3cuc2V0VGltZW91dChzLDIwKSl9KX19O2NvLnRlbXBsYXRlPVFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBmb250LXNpemU6IDEycHg7CiAgICAgICAgbWFyZ2luOiAwOwogICAgICAgIHBhZGRpbmc6IDA7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgIH0KCiAgICAgIGgyIHsKICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgIHRleHQtYWxpZ246IGNlbnRlcjsKICAgICAgICBtYXJnaW46IDA7CiAgICAgIH0KCiAgICAgIC5oZWFsdGgtcGlsbC1sZWdlbmQgewogICAgICAgIHBhZGRpbmc6IDE1cHg7CiAgICAgIH0KCiAgICAgIC5oZWFsdGgtcGlsbC1sZWdlbmQgaDIgewogICAgICAgIHRleHQtYWxpZ246IGxlZnQ7CiAgICAgIH0KCiAgICAgIC5oZWFsdGgtcGlsbC1lbnRyeSB7CiAgICAgICAgbWFyZ2luOiAxMHB4IDEwcHggMTBweCAwOwogICAgICB9CgogICAgICAuaGVhbHRoLXBpbGwtZW50cnkgLmNvbG9yLXByZXZpZXcgewogICAgICAgIHdpZHRoOiAyNnB4OwogICAgICAgIGhlaWdodDogMjZweDsKICAgICAgICBib3JkZXItcmFkaXVzOiAzcHg7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICAgIG1hcmdpbjogMCAxMHB4IDAgMDsKICAgICAgfQoKICAgICAgLmhlYWx0aC1waWxsLWVudHJ5IC5jb2xvci1sYWJlbCwKICAgICAgLmhlYWx0aC1waWxsLWVudHJ5IC50ZW5zb3ItY291bnQgewogICAgICAgIGNvbG9yOiAjNzc3OwogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICBoZWlnaHQ6IDI2cHg7CiAgICAgICAgZm9udC1zaXplOiAyMnB4OwogICAgICAgIGxpbmUtaGVpZ2h0OiAyNnB4OwogICAgICAgIHZlcnRpY2FsLWFsaWduOiB0b3A7CiAgICAgIH0KCiAgICAgIC5oZWFsdGgtcGlsbC1lbnRyeSAudGVuc29yLWNvdW50IHsKICAgICAgICBmbG9hdDogcmlnaHQ7CiAgICAgIH0KCiAgICAgICNoZWFsdGgtcGlsbC1zdGVwLXNsaWRlciB7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgbWFyZ2luOiAwIDAgMCAtMTVweDsKICAgICAgICAvKiAzMSBjb21lcyBmcm9tIGFkZGluZyBhIHBhZGRpbmcgb2YgMTVweCBmcm9tIGJvdGggc2lkZXMgb2YgdGhlIHBhcGVyLXNsaWRlciwgc3VidHJhY3RpbmcKICAgKiAxcHggc28gdGhhdCB0aGUgc2xpZGVyIHdpZHRoIGFsaWducyB3aXRoIHRoZSBpbWFnZSAodGhlIGxhc3Qgc2xpZGVyIG1hcmtlciB0YWtlcyB1cCAxcHgpLAogICAqIGFuZCBhZGRpbmcgMnB4IHRvIGFjY291bnQgZm9yIGEgYm9yZGVyIG9mIDFweCBvbiBib3RoIHNpZGVzIG9mIHRoZSBpbWFnZS4gMzAgLSAxICsgMi4KICAgKiBBcHBhcmVudGx5LCB0aGUgcGFwZXItc2xpZGVyIGxhY2tzIGEgbWl4aW4gZm9yIHRob3NlIHBhZGRpbmcgdmFsdWVzLiAqLwogICAgICAgIHdpZHRoOiBjYWxjKDEwMCUgKyAzMXB4KTsKICAgICAgfQoKICAgICAgI2hlYWx0aC1waWxscy1sb2FkaW5nLXNwaW5uZXIgewogICAgICAgIHdpZHRoOiAyMHB4OwogICAgICAgIGhlaWdodDogMjBweDsKICAgICAgICB2ZXJ0aWNhbC1hbGlnbjogdG9wOwogICAgICB9CgogICAgICAjaGVhbHRoLXBpbGwtc3RlcC1udW1iZXItaW5wdXQgewogICAgICAgIHRleHQtYWxpZ246IGNlbnRlcjsKICAgICAgICB2ZXJ0aWNhbC1hbGlnbjogdG9wOwogICAgICB9CgogICAgICAjbnVtZXJpYy1hbGVydHMtdGFibGUtY29udGFpbmVyIHsKICAgICAgICBtYXgtaGVpZ2h0OiA0MDBweDsKICAgICAgICBvdmVyZmxvdy14OiBoaWRkZW47CiAgICAgICAgb3ZlcmZsb3cteTogYXV0bzsKICAgICAgfQoKICAgICAgI251bWVyaWMtYWxlcnRzLXRhYmxlIHsKICAgICAgICB0ZXh0LWFsaWduOiBsZWZ0OwogICAgICB9CgogICAgICAjbnVtZXJpYy1hbGVydHMtdGFibGUgdGQgewogICAgICAgIHZlcnRpY2FsLWFsaWduOiB0b3A7CiAgICAgIH0KCiAgICAgICNudW1lcmljLWFsZXJ0cy10YWJsZSAuZmlyc3Qtb2ZmZW5zZS10ZCB7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICB9CgogICAgICAuZmlyc3Qtb2ZmZW5zZS10ZCB7CiAgICAgICAgd2lkdGg6IDgwcHg7CiAgICAgIH0KCiAgICAgIC50ZW5zb3ItZGV2aWNlLXRkIHsKICAgICAgICBtYXgtd2lkdGg6IDE0MHB4OwogICAgICAgIHdvcmQtd3JhcDogYnJlYWstd29yZDsKICAgICAgfQoKICAgICAgLnRlbnNvci1zZWN0aW9uLXdpdGhpbi10YWJsZSB7CiAgICAgICAgY29sb3I6ICMyNjYyMzY7CiAgICAgICAgY3Vyc29yOiBwb2ludGVyOwogICAgICAgIG9wYWNpdHk6IDAuODsKICAgICAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKICAgICAgfQoKICAgICAgLnRlbnNvci1zZWN0aW9uLXdpdGhpbi10YWJsZTpob3ZlciB7CiAgICAgICAgb3BhY2l0eTogMTsKICAgICAgfQoKICAgICAgLmRldmljZS1zZWN0aW9uLXdpdGhpbi10YWJsZSB7CiAgICAgICAgY29sb3I6ICM2NjY7CiAgICAgIH0KCiAgICAgIC5taW5pLWhlYWx0aC1waWxsIHsKICAgICAgICB3aWR0aDogMTMwcHg7CiAgICAgIH0KCiAgICAgIC5taW5pLWhlYWx0aC1waWxsID4gZGl2IHsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgd2lkdGg6IDYwcHg7CiAgICAgICAgYm9yZGVyLXJhZGl1czogM3B4OwogICAgICB9CgogICAgICAjZXZlbnQtY291bnRzLXRoIHsKICAgICAgICBwYWRkaW5nOiAwIDAgMCAxMHB4OwogICAgICB9CgogICAgICAubmVnYXRpdmUtaW5mLW1pbmktaGVhbHRoLXBpbGwtc2VjdGlvbiB7CiAgICAgICAgYmFja2dyb3VuZDogcmdiKDI1NSwgMTQxLCAwKTsKICAgICAgICB3aWR0aDogMjBweDsKICAgICAgfQoKICAgICAgLnBvc2l0aXZlLWluZi1taW5pLWhlYWx0aC1waWxsLXNlY3Rpb24gewogICAgICAgIGJhY2tncm91bmQ6IHJnYigwLCA2MiwgMjEyKTsKICAgICAgICB3aWR0aDogMjBweDsKICAgICAgfQoKICAgICAgLm5hbi1taW5pLWhlYWx0aC1waWxsLXNlY3Rpb24gewogICAgICAgIGJhY2tncm91bmQ6IHJnYigyMDQsIDQ3LCA0NCk7CiAgICAgICAgd2lkdGg6IDIwcHg7CiAgICAgIH0KCiAgICAgIC5uZWdhdGl2ZS1pbmYtbWluaS1oZWFsdGgtcGlsbC1zZWN0aW9uLAogICAgICAucG9zaXRpdmUtaW5mLW1pbmktaGVhbHRoLXBpbGwtc2VjdGlvbiwKICAgICAgLm5hbi1taW5pLWhlYWx0aC1waWxsLXNlY3Rpb24gewogICAgICAgIGNvbG9yOiAjZmZmOwogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgbGluZS1oZWlnaHQ6IDIwcHg7CiAgICAgICAgbWFyZ2luOiAwIDAgMCAxMHB4OwogICAgICAgIHRleHQtYWxpZ246IGNlbnRlcjsKICAgICAgfQoKICAgICAgLm5vLW51bWVyaWMtYWxlcnRzLW5vdGlmaWNhdGlvbiB7CiAgICAgICAgbWFyZ2luOiAwOwogICAgICB9CiAgICA8L3N0eWxlPgogICAgPHBhcGVyLW1hdGVyaWFsIGVsZXZhdGlvbj0iMSIgY2xhc3M9ImNhcmQgaGVhbHRoLXBpbGwtbGVnZW5kIj4KICAgICAgPGRpdiBjbGFzcz0idGl0bGUiPgogICAgICAgIEVuYWJsZSBhbGwgKG5vdCBqdXN0IHNhbXBsZWQpIHN0ZXBzLiBSZXF1aXJlcyBzbG93IGRpc2sgcmVhZC4KICAgICAgPC9kaXY+CiAgICAgIDxwYXBlci10b2dnbGUtYnV0dG9uCiAgICAgICAgaWQ9ImVuYWJsZUFsbFN0ZXBzTW9kZVRvZ2dsZSIKICAgICAgICBjaGVja2VkPSJ7e2FsbFN0ZXBzTW9kZUVuYWJsZWR9fSIKICAgICAgPgogICAgICA8L3BhcGVyLXRvZ2dsZS1idXR0b24+CiAgICAgIDxoMj4KICAgICAgICBTdGVwIG9mIEhlYWx0aCBQaWxsczoKICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbYWxsU3RlcHNNb2RlRW5hYmxlZF1dIj4KICAgICAgICAgIDxpbnB1dAogICAgICAgICAgICB0eXBlPSJudW1iZXIiCiAgICAgICAgICAgIGlkPSJoZWFsdGgtcGlsbC1zdGVwLW51bWJlci1pbnB1dCIKICAgICAgICAgICAgbWluPSIwIgogICAgICAgICAgICBtYXg9IltbX2JpZ2dlc3RTdGVwRXZlclNlZW5dXSIKICAgICAgICAgICAgdmFsdWU9Int7c3BlY2lmaWNIZWFsdGhQaWxsU3RlcDo6aW5wdXR9fSIKICAgICAgICAgIC8+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbIWFsbFN0ZXBzTW9kZUVuYWJsZWRdXSI+CiAgICAgICAgICBbW19jdXJyZW50U3RlcERpc3BsYXlWYWx1ZV1dCiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8cGFwZXItc3Bpbm5lci1saXRlCiAgICAgICAgICBhY3RpdmUKICAgICAgICAgIGhpZGRlbiQ9IltbIWFyZUhlYWx0aFBpbGxzTG9hZGluZ11dIgogICAgICAgICAgaWQ9ImhlYWx0aC1waWxscy1sb2FkaW5nLXNwaW5uZXIiCiAgICAgICAgPjwvcGFwZXItc3Bpbm5lci1saXRlPgogICAgICA8L2gyPgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbYWxsU3RlcHNNb2RlRW5hYmxlZF1dIj4KICAgICAgICA8cGFwZXItc2xpZGVyCiAgICAgICAgICBpZD0iaGVhbHRoLXBpbGwtc3RlcC1zbGlkZXIiCiAgICAgICAgICBpbW1lZGlhdGUtdmFsdWU9Int7c3BlY2lmaWNIZWFsdGhQaWxsU3RlcH19IgogICAgICAgICAgbWF4PSJbW19iaWdnZXN0U3RlcEV2ZXJTZWVuXV0iCiAgICAgICAgICBzbmFwcwogICAgICAgICAgc3RlcD0iMSIKICAgICAgICAgIHZhbHVlPSJ7e3NwZWNpZmljSGVhbHRoUGlsbFN0ZXB9fSIKICAgICAgICA+PC9wYXBlci1zbGlkZXI+CiAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1shYWxsU3RlcHNNb2RlRW5hYmxlZF1dIj4KICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX21heFN0ZXBJbmRleF1dIj4KICAgICAgICAgIDxwYXBlci1zbGlkZXIKICAgICAgICAgICAgaWQ9ImhlYWx0aC1waWxsLXN0ZXAtc2xpZGVyIgogICAgICAgICAgICBpbW1lZGlhdGUtdmFsdWU9Int7aGVhbHRoUGlsbFN0ZXBJbmRleH19IgogICAgICAgICAgICBtYXg9IltbX21heFN0ZXBJbmRleF1dIgogICAgICAgICAgICBzbmFwcwogICAgICAgICAgICBzdGVwPSIxIgogICAgICAgICAgICB2YWx1ZT0ie3toZWFsdGhQaWxsU3RlcEluZGV4fX0iCiAgICAgICAgICA+PC9wYXBlci1zbGlkZXI+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPGgyPgogICAgICAgIEhlYWx0aCBQaWxsCiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW2hlYWx0aFBpbGxWYWx1ZXNGb3JTZWxlY3RlZE5vZGVdXSI+CiAgICAgICAgICBDb3VudHMgZm9yIFNlbGVjdGVkIE5vZGUKICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1shaGVhbHRoUGlsbFZhbHVlc0ZvclNlbGVjdGVkTm9kZV1dIj4KICAgICAgICAgIExlZ2VuZAogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDwvaDI+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9IltbaGVhbHRoUGlsbEVudHJpZXNdXSI+CiAgICAgICAgPGRpdiBjbGFzcz0iaGVhbHRoLXBpbGwtZW50cnkiPgogICAgICAgICAgPGRpdgogICAgICAgICAgICBjbGFzcz0iY29sb3ItcHJldmlldyIKICAgICAgICAgICAgc3R5bGU9ImJhY2tncm91bmQ6W1tpdGVtLmJhY2tncm91bmRfY29sb3JdXSIKICAgICAgICAgID48L2Rpdj4KICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbG9yLWxhYmVsIj5bW2l0ZW0ubGFiZWxdXTwvZGl2PgogICAgICAgICAgPGRpdiBjbGFzcz0idGVuc29yLWNvdW50Ij4KICAgICAgICAgICAgW1tfY29tcHV0ZVRlbnNvckNvdW50U3RyaW5nKGhlYWx0aFBpbGxWYWx1ZXNGb3JTZWxlY3RlZE5vZGUsCiAgICAgICAgICAgIGluZGV4KV1dCiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPGRpdiBoaWRkZW4kPSJbWyFfaGFzRGVidWdnZXJOdW1lcmljQWxlcnRzKGRlYnVnZ2VyTnVtZXJpY0FsZXJ0cyldXSI+CiAgICAgICAgPGgyIGlkPSJudW1lcmljLWFsZXJ0cy1oZWFkZXIiPk51bWVyaWMgQWxlcnRzPC9oMj4KICAgICAgICA8cD5BbGVydHMgYXJlIHNvcnRlZCBmcm9tIHRvcCB0byBib3R0b20gYnkgaW5jcmVhc2luZyB0aW1lc3RhbXAuPC9wPgogICAgICAgIDxkaXYgaWQ9Im51bWVyaWMtYWxlcnRzLXRhYmxlLWNvbnRhaW5lciI+CiAgICAgICAgICA8dGFibGUgaWQ9Im51bWVyaWMtYWxlcnRzLXRhYmxlIj4KICAgICAgICAgICAgPHRoZWFkPgogICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgIDx0aD5GaXJzdCBPZmZlbnNlPC90aD4KICAgICAgICAgICAgICAgIDx0aD5UZW5zb3IgKERldmljZSk8L3RoPgogICAgICAgICAgICAgICAgPHRoIGlkPSJldmVudC1jb3VudHMtdGgiPkV2ZW50IENvdW50czwvdGg+CiAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgPHRib2R5IGlkPSJudW1lcmljLWFsZXJ0cy1ib2R5Ij48L3Rib2R5PgogICAgICAgICAgPC90YWJsZT4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICAgIDx0ZW1wbGF0ZQogICAgICAgIGlzPSJkb20taWYiCiAgICAgICAgaWY9IltbIV9oYXNEZWJ1Z2dlck51bWVyaWNBbGVydHMoZGVidWdnZXJOdW1lcmljQWxlcnRzKV1dIgogICAgICA+CiAgICAgICAgPHAgY2xhc3M9Im5vLW51bWVyaWMtYWxlcnRzLW5vdGlmaWNhdGlvbiI+CiAgICAgICAgICBObyBudW1lcmljIGFsZXJ0cyBzbyBmYXIuIFRoYXQgaXMgbGlrZWx5IGdvb2QuIEFsZXJ0cyBpbmRpY2F0ZSB0aGUKICAgICAgICAgIHByZXNlbmNlIG9mIE5hTiBvciAoKy8tKSBJbmZpbml0eSB2YWx1ZXMsIHdoaWNoIG1heSBiZSBjb25jZXJuaW5nLgogICAgICAgIDwvcD4KICAgICAgPC90ZW1wbGF0ZT4KICAgIDwvcGFwZXItbWF0ZXJpYWw+CiAgYDtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixsbyldLGNvLnByb3RvdHlwZSwicmVuZGVySGllcmFyY2h5Iix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXksbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGNvLnByb3RvdHlwZSwiZGVidWdnZXJOdW1lcmljQWxlcnRzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGNvLnByb3RvdHlwZSwibm9kZU5hbWVzVG9IZWFsdGhQaWxscyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcixub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sY28ucHJvdG90eXBlLCJoZWFsdGhQaWxsU3RlcEluZGV4Iix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxjby5wcm90b3R5cGUsInNwZWNpZmljSGVhbHRoUGlsbFN0ZXAiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmcsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGNvLnByb3RvdHlwZSwic2VsZWN0ZWROb2RlIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxjby5wcm90b3R5cGUsImhpZ2hsaWdodGVkTm9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcixub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sY28ucHJvdG90eXBlLCJzZWxlY3RlZE5vZGVJbmNsdWRlIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxjby5wcm90b3R5cGUsImFyZUhlYWx0aFBpbGxzTG9hZGluZyIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sY28ucHJvdG90eXBlLCJoZWFsdGhQaWxsRW50cmllcyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4sbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGNvLnByb3RvdHlwZSwiYWxsU3RlcHNNb2RlRW5hYmxlZCIsdm9pZCAwKTtFKFtSdCgibm9kZU5hbWVzVG9IZWFsdGhQaWxscyIsImhlYWx0aFBpbGxTdGVwSW5kZXgiLCJzZWxlY3RlZE5vZGUiLCJhbGxTdGVwc01vZGVFbmFibGVkIiwiYXJlSGVhbHRoUGlsbHNMb2FkaW5nIiksdygiZGVzaWduOnR5cGUiLE9iamVjdCksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sY28ucHJvdG90eXBlLCJoZWFsdGhQaWxsVmFsdWVzRm9yU2VsZWN0ZWROb2RlIixudWxsKTtFKFtSdCgibm9kZU5hbWVzVG9IZWFsdGhQaWxscyIsImhlYWx0aFBpbGxTdGVwSW5kZXgiLCJhbGxTdGVwc01vZGVFbmFibGVkIiwic3BlY2lmaWNIZWFsdGhQaWxsU3RlcCIsImFyZUhlYWx0aFBpbGxzTG9hZGluZyIpLHcoImRlc2lnbjp0eXBlIixPYmplY3QpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLGNvLnByb3RvdHlwZSwiX2N1cnJlbnRTdGVwRGlzcGxheVZhbHVlIixudWxsKTtFKFtSdCgibm9kZU5hbWVzVG9IZWFsdGhQaWxscyIpLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLGNvLnByb3RvdHlwZSwiX2JpZ2dlc3RTdGVwRXZlclNlZW4iLG51bGwpO0UoW1J0KCJub2RlTmFtZXNUb0hlYWx0aFBpbGxzIiksdygiZGVzaWduOnR5cGUiLE51bWJlciksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sY28ucHJvdG90eXBlLCJfbWF4U3RlcEluZGV4IixudWxsKTtFKFtCdCgiZGVidWdnZXJOdW1lcmljQWxlcnRzIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxjby5wcm90b3R5cGUsIl91cGRhdGVBbGVydHNMaXN0IixudWxsKTtjbz1FKFt5dCgidGYtZ3JhcGgtZGVidWdnZXItZGF0YS1jYXJkIildLGNvKTt2YXIgYWN0PXt9O0tzKGFjdCx7R3JhcGhJY29uVHlwZTooKT0+bnZ9KTt2YXIgbnY7KGZ1bmN0aW9uKGUpe2UuQ09OU1Q9IkNPTlNUIixlLk1FVEE9Ik1FVEEiLGUuT1A9Ik9QIixlLlNFUklFUz0iU0VSSUVTIixlLlNVTU1BUlk9IlNVTU1BUlkifSkobnZ8fChudj17fSkpO3ZhciBadT1jbGFzcyBleHRlbmRzIEd0KF9vKG10KSl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMudmVydGljYWw9ITEsdGhpcy5maWxsT3ZlcnJpZGU9bnVsbCx0aGlzLnN0cm9rZU92ZXJyaWRlPW51bGwsdGhpcy5oZWlnaHQ9MjAsdGhpcy5mYWRlZD0hMX1nZXRTdmdEZWZpbmFibGVFbGVtZW50KCl7cmV0dXJuIHRoaXMuJC5zdmdEZWZzfWdldCBfZmlsbCgpe3ZhciB0PXRoaXMudHlwZSxyPXRoaXMuZmlsbE92ZXJyaWRlO2lmKHIhPW51bGwpcmV0dXJuIHI7c3dpdGNoKHQpe2Nhc2UgbnYuTUVUQTpyZXR1cm4gS3UuREVGQVVMVF9GSUxMO2Nhc2UgbnYuU0VSSUVTOnJldHVybiBVbHQuREVGQVVMVF9GSUxMO2RlZmF1bHQ6cmV0dXJuIHkwLkRFRkFVTFRfRklMTH19Z2V0IF9zdHJva2UoKXt2YXIgdD10aGlzLnR5cGUscj10aGlzLnN0cm9rZU92ZXJyaWRlO2lmKHIhPW51bGwpcmV0dXJuIHI7c3dpdGNoKHQpe2Nhc2UgbnYuTUVUQTpyZXR1cm4gS3UuREVGQVVMVF9TVFJPS0U7Y2FzZSBudi5TRVJJRVM6cmV0dXJuIFVsdC5ERUZBVUxUX1NUUk9LRTtkZWZhdWx0OnJldHVybiB5MC5ERUZBVUxUX1NUUk9LRX19X2lzVHlwZSh0LHIpe3JldHVybiB0PT09cn1fZmFkZWRDbGFzcyh0LHIpe3JldHVybiB0PyJmYWRlZC0iK3I6IiJ9fTtadS50ZW1wbGF0ZT1RYAogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZm9udC1zaXplOiAwOwogICAgICB9CgogICAgICA6aG9zdCguZGFyay1tb2RlKSBzdmcgewogICAgICAgIGZpbHRlcjogaW52ZXJ0KDEpOwogICAgICB9CgogICAgICAuZmFkZWQtcmVjdCB7CiAgICAgICAgZmlsbDogdXJsKCNyZWN0SGF0Y2gpOwogICAgICB9CgogICAgICAuZmFkZWQtZWxsaXBzZSB7CiAgICAgICAgZmlsbDogdXJsKCNlbGxpcHNlSGF0Y2gpOwogICAgICB9CgogICAgICAuZmFkZWQtcmVjdCwKICAgICAgLmZhZGVkLWVsbGlwc2UsCiAgICAgIC5mYWRlZC1zZXJpZXMgewogICAgICAgIHN0cm9rZTogdmFyKC0tdGItZ3JhcGgtZmFkZWQpICFpbXBvcnRhbnQ7CiAgICAgIH0KICAgICAgI3JlY3RIYXRjaCBsaW5lLAogICAgICAjZWxsaXBzZUhhdGNoIGxpbmUgewogICAgICAgIGNvbG9yOiAjZTBkNGIzICFpbXBvcnRhbnQ7CiAgICAgICAgZmlsbDogd2hpdGU7CiAgICAgICAgc3Ryb2tlOiAjZTBkNGIzICFpbXBvcnRhbnQ7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8IS0tIFNWRyBmb3IgZGVmaW5pdGlvbnMgLS0+CiAgICA8c3ZnIGhlaWdodD0iMCIgd2lkdGg9IjAiIGlkPSJzdmdEZWZzIj4KICAgICAgPGRlZnM+CiAgICAgICAgPCEtLSBIYXRjaCBwYXR0ZXJucyBmb3IgZmFkZWQgb3V0IG5vZGVzLiAtLT4KICAgICAgICA8cGF0dGVybgogICAgICAgICAgaWQ9InJlY3RIYXRjaCIKICAgICAgICAgIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSg0NSAwIDApIgogICAgICAgICAgd2lkdGg9IjUiCiAgICAgICAgICBoZWlnaHQ9IjUiCiAgICAgICAgICBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICAgICAgID4KICAgICAgICAgIDxsaW5lIHgxPSIwIiB5MT0iMCIgeDI9IjAiIHkyPSI1IiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxIj48L2xpbmU+CiAgICAgICAgPC9wYXR0ZXJuPgogICAgICAgIDxwYXR0ZXJuCiAgICAgICAgICBpZD0iZWxsaXBzZUhhdGNoIgogICAgICAgICAgcGF0dGVyblRyYW5zZm9ybT0icm90YXRlKDQ1IDAgMCkiCiAgICAgICAgICB3aWR0aD0iMiIKICAgICAgICAgIGhlaWdodD0iMiIKICAgICAgICAgIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiCiAgICAgICAgPgogICAgICAgICAgPGxpbmUgeDE9IjAiIHkxPSIwIiB4Mj0iMCIgeTI9IjIiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDEiPjwvbGluZT4KICAgICAgICA8L3BhdHRlcm4+CiAgICAgICAgPCEtLSBUZW1wbGF0ZSBmb3IgYW4gT3Agbm9kZSBlbGxpcHNlLiAtLT4KICAgICAgICA8ZWxsaXBzZQogICAgICAgICAgaWQ9Im9wLW5vZGUtc3RhbXAiCiAgICAgICAgICByeD0iNy41IgogICAgICAgICAgcnk9IjMiCiAgICAgICAgICBzdHJva2U9ImluaGVyaXQiCiAgICAgICAgICBmaWxsPSJpbmhlcml0IgogICAgICAgID48L2VsbGlwc2U+CiAgICAgICAgPCEtLSBUZW1wbGF0ZSBmb3IgYW4gT3Agbm9kZSBhbm5vdGF0aW9uIGVsbGlwc2UgKHNtYWxsZXIpLiAtLT4KICAgICAgICA8ZWxsaXBzZQogICAgICAgICAgaWQ9Im9wLW5vZGUtYW5ub3RhdGlvbi1zdGFtcCIKICAgICAgICAgIHJ4PSI1IgogICAgICAgICAgcnk9IjIiCiAgICAgICAgICBzdHJva2U9ImluaGVyaXQiCiAgICAgICAgICBmaWxsPSJpbmhlcml0IgogICAgICAgID48L2VsbGlwc2U+CiAgICAgICAgPCEtLSBWZXJ0aWNhbGx5IHN0YWNrZWQgc2VyaWVzIG9mIE9wIG5vZGVzIHdoZW4gdW5leHBhbmRlZC4gLS0+CiAgICAgICAgPGcgaWQ9Im9wLXNlcmllcy12ZXJ0aWNhbC1zdGFtcCI+CiAgICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNvcC1ub2RlLXN0YW1wIiB4PSI4IiB5PSI5Ij48L3VzZT4KICAgICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI29wLW5vZGUtc3RhbXAiIHg9IjgiIHk9IjYiPjwvdXNlPgogICAgICAgICAgPHVzZSB4bGluazpocmVmPSIjb3Atbm9kZS1zdGFtcCIgeD0iOCIgeT0iMyI+PC91c2U+CiAgICAgICAgPC9nPgogICAgICAgIDxnIGlkPSJvcC1zZXJpZXMtaG9yaXpvbnRhbC1zdGFtcCI+CiAgICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNvcC1ub2RlLXN0YW1wIiB4PSIxNiIgeT0iNCI+PC91c2U+CiAgICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNvcC1ub2RlLXN0YW1wIiB4PSIxMiIgeT0iNCI+PC91c2U+CiAgICAgICAgICA8dXNlIHhsaW5rOmhyZWY9IiNvcC1ub2RlLXN0YW1wIiB4PSI4IiB5PSI0Ij48L3VzZT4KICAgICAgICA8L2c+CiAgICAgICAgPGcKICAgICAgICAgIGlkPSJzdW1tYXJ5LWljb24iCiAgICAgICAgICBmaWxsPSIjODQ4NDg0IgogICAgICAgICAgaGVpZ2h0PSIxMiIKICAgICAgICAgIHZpZXdCb3g9IjAgMCAyNCAyNCIKICAgICAgICAgIHdpZHRoPSIxMiIKICAgICAgICA+CiAgICAgICAgICA8cGF0aAogICAgICAgICAgICBkPSJNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6TTkgMTdIN3YtN2gydjd6bTQgMGgtMlY3aDJ2MTB6bTQgMGgtMnYtNGgydjR6IgogICAgICAgICAgPjwvcGF0aD4KICAgICAgICA8L2c+CiAgICAgIDwvZGVmcz4KICAgIDwvc3ZnPgogICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19pc1R5cGUodHlwZSwgJ0NPTlNUJyldXSI+CiAgICAgIDxzdmcKICAgICAgICBoZWlnaHQkPSJbW2hlaWdodF1dIgogICAgICAgIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWlkIG1lZXQiCiAgICAgICAgdmlld0JveD0iMCAwIDEwIDEwIgogICAgICA+CiAgICAgICAgPGNpcmNsZQogICAgICAgICAgY3g9IjUiCiAgICAgICAgICBjeT0iNSIKICAgICAgICAgIHI9IjMiCiAgICAgICAgICBmaWxsJD0iW1tfZmlsbF1dIgogICAgICAgICAgc3Ryb2tlJD0iW1tfc3Ryb2tlXV0iCiAgICAgICAgPjwvY2lyY2xlPgogICAgICA8L3N2Zz4KICAgIDwvdGVtcGxhdGU+CiAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2lzVHlwZSh0eXBlLCAnU1VNTUFSWScpXV0iPgogICAgICA8c3ZnCiAgICAgICAgd2lkdGgkPSJbW2hlaWdodF1dIgogICAgICAgIGhlaWdodCQ9IltbaGVpZ2h0XV0iCiAgICAgICAgdmlld0JveD0iMCAwIDI0IDI0IgogICAgICAgIGZpbGw9IiM4NDg0ODQiCiAgICAgID4KICAgICAgICA8cGF0aAogICAgICAgICAgZD0iTTE5IDNINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yek05IDE3SDd2LTdoMnY3em00IDBoLTJWN2gydjEwem00IDBoLTJ2LTRoMnY0eiIKICAgICAgICA+PC9wYXRoPgogICAgICA8L3N2Zz4KICAgIDwvdGVtcGxhdGU+CiAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2lzVHlwZSh0eXBlLCAnT1AnKV1dIj4KICAgICAgPHN2ZwogICAgICAgIGhlaWdodCQ9IltbaGVpZ2h0XV0iCiAgICAgICAgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pbllNaWQgbWVldCIKICAgICAgICB2aWV3Qm94PSIwIDAgMTYgOCIKICAgICAgPgogICAgICAgIDx1c2UKICAgICAgICAgIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIgogICAgICAgICAgeGxpbms6aHJlZj0iI29wLW5vZGUtc3RhbXAiCiAgICAgICAgICBmaWxsJD0iW1tfZmlsbF1dIgogICAgICAgICAgc3Ryb2tlJD0iW1tfc3Ryb2tlXV0iCiAgICAgICAgICBjbGFzcyQ9Int7X2ZhZGVkQ2xhc3MoZmFkZWQsICdlbGxpcHNlJyl9fSIKICAgICAgICAgIHg9IjgiCiAgICAgICAgICB5PSI0IgogICAgICAgID48L3VzZT4KICAgICAgPC9zdmc+CiAgICA8L3RlbXBsYXRlPgogICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19pc1R5cGUodHlwZSwgJ01FVEEnKV1dIj4KICAgICAgPHN2ZwogICAgICAgIGhlaWdodCQ9IltbaGVpZ2h0XV0iCiAgICAgICAgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pbllNaWQgbWVldCIKICAgICAgICB2aWV3Qm94PSIwIDAgMzcgMTYiCiAgICAgID4KICAgICAgICA8cmVjdAogICAgICAgICAgeD0iMSIKICAgICAgICAgIHk9IjEiCiAgICAgICAgICBmaWxsJD0iW1tfZmlsbF1dIgogICAgICAgICAgc3Ryb2tlJD0iW1tfc3Ryb2tlXV0iCiAgICAgICAgICBjbGFzcyQ9Int7X2ZhZGVkQ2xhc3MoZmFkZWQsICdyZWN0Jyl9fSIKICAgICAgICAgIHN0cm9rZS13aWR0aD0iMnB4IgogICAgICAgICAgaGVpZ2h0PSIxNCIKICAgICAgICAgIHdpZHRoPSIzNSIKICAgICAgICAgIHJ4PSI1IgogICAgICAgICAgcnk9IjUiCiAgICAgICAgPjwvcmVjdD4KICAgICAgPC9zdmc+CiAgICA8L3RlbXBsYXRlPgogICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19pc1R5cGUodHlwZSwgJ1NFUklFUycpXV0iPgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbdmVydGljYWxdXSI+CiAgICAgICAgPHN2ZwogICAgICAgICAgaGVpZ2h0JD0iW1toZWlnaHRdXSIKICAgICAgICAgIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWlkIG1lZXQiCiAgICAgICAgICB2aWV3Qm94PSIwIDAgMTYgMTUiCiAgICAgICAgPgogICAgICAgICAgPHVzZQogICAgICAgICAgICB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIKICAgICAgICAgICAgeGxpbms6aHJlZj0iI29wLXNlcmllcy12ZXJ0aWNhbC1zdGFtcCIKICAgICAgICAgICAgZmlsbCQ9IltbX2ZpbGxdXSIKICAgICAgICAgICAgc3Ryb2tlJD0iW1tfc3Ryb2tlXV0iCiAgICAgICAgICAgIGNsYXNzJD0ie3tfZmFkZWRDbGFzcyhmYWRlZCwgJ3NlcmllcycpfX0iCiAgICAgICAgICAgIHg9IjAiCiAgICAgICAgICAgIHk9IjIiCiAgICAgICAgICA+PC91c2U+CiAgICAgICAgPC9zdmc+CiAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1shdmVydGljYWxdXSI+CiAgICAgICAgPHN2ZwogICAgICAgICAgaGVpZ2h0JD0iW1toZWlnaHRdXSIKICAgICAgICAgIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWlkIG1lZXQiCiAgICAgICAgICB2aWV3Qm94PSIwIDAgMjQgMTAiCiAgICAgICAgPgogICAgICAgICAgPHVzZQogICAgICAgICAgICB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIKICAgICAgICAgICAgeGxpbms6aHJlZj0iI29wLXNlcmllcy1ob3Jpem9udGFsLXN0YW1wIgogICAgICAgICAgICBmaWxsJD0iW1tfZmlsbF1dIgogICAgICAgICAgICBzdHJva2UkPSJbW19zdHJva2VdXSIKICAgICAgICAgICAgY2xhc3MkPSJ7e19mYWRlZENsYXNzKGZhZGVkLCAnc2VyaWVzJyl9fSIKICAgICAgICAgICAgeD0iMCIKICAgICAgICAgICAgeT0iMSIKICAgICAgICAgID48L3VzZT4KICAgICAgICA8L3N2Zz4KICAgICAgPC90ZW1wbGF0ZT4KICAgIDwvdGVtcGxhdGU+CiAgYDtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxadS5wcm90b3R5cGUsInR5cGUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxadS5wcm90b3R5cGUsInZlcnRpY2FsIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFp1LnByb3RvdHlwZSwiZmlsbE92ZXJyaWRlIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFp1LnByb3RvdHlwZSwic3Ryb2tlT3ZlcnJpZGUiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sWnUucHJvdG90eXBlLCJoZWlnaHQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxadS5wcm90b3R5cGUsImZhZGVkIix2b2lkIDApO0UoW1J0KCJ0eXBlIiwiZmlsbE92ZXJyaWRlIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sWnUucHJvdG90eXBlLCJfZmlsbCIsbnVsbCk7RShbUnQoInR5cGUiLCJzdHJva2VPdmVycmlkZSIpLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLFp1LnByb3RvdHlwZSwiX3N0cm9rZSIsbnVsbCk7WnU9RShbeXQoInRmLWdyYXBoLWljb24iKV0sWnUpO3ZhciBCcz1jbGFzcyBleHRlbmRzIEd0KG10KXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5ub2RlPW51bGwsdGhpcy5yZW5kZXJJbmZvPW51bGwsdGhpcy5jb2xvckJ5PUduLlNUUlVDVFVSRSx0aGlzLnRlbXBsYXRlSW5kZXg9bnVsbCx0aGlzLnR5cGU9bnVsbCx0aGlzLnZlcnRpY2FsPSExLHRoaXMuY29uc3Q9ITEsdGhpcy5zdW1tYXJ5PSExLHRoaXMuZmlsbD1udWxsLHRoaXMuaGVpZ2h0PTIwfV9jb21wdXRlRmlsbE92ZXJyaWRlKHQscixuLGksbyl7cmV0dXJuIHQmJnImJmk/TEgoaSxuLHIsITEpOm99X2dldFN0cm9rZU92ZXJyaWRlKHQpe3JldHVybiB0P2VjdCh0KTpudWxsfV9nZXRUeXBlKHQscixuLGkpe2xldHtHcmFwaEljb25UeXBlOm99PWFjdDtpZih0KXN3aXRjaCh0LnR5cGUpe2Nhc2UganQuT1A6e2xldCBhPXQub3A7cmV0dXJuIHR5cGVvZiBhIT0ic3RyaW5nIj9vLk9QOmE9PT0iQ29uc3QifHxuP28uQ09OU1Q6YS5lbmRzV2l0aCgiU3VtbWFyeSIpfHxyP28uU1VNTUFSWTpvLk9QfWNhc2UganQuTUVUQTpyZXR1cm4gby5NRVRBO2Nhc2UganQuU0VSSUVTOnJldHVybiBvLlNFUklFU31yZXR1cm4gaX1faXNWZXJ0aWNhbCh0LHIpe3JldHVybiB0P3QuaGFzTm9uQ29udHJvbEVkZ2VzOiEhcn1fZ2V0RmFkZWQodCl7cmV0dXJuIHQmJnQuaXNGYWRlZE91dH1fb25GaWxsT3ZlcnJpZGVDaGFuZ2VkKHQscil7bGV0e25vZGU6bixyZW5kZXJJbmZvOmksY29sb3JCeTpvLHRlbXBsYXRlSW5kZXg6YX09dGhpczt0IT09ciYmSUgodGhpcy4kLmljb24uZ2V0U3ZnRGVmaW5hYmxlRWxlbWVudCgpKSxuJiZpJiZhJiZMSChhLG8saSwhMSx0aGlzLiQuaWNvbi5nZXRTdmdEZWZpbmFibGVFbGVtZW50KCkpfX07QnMudGVtcGxhdGU9UWAKICAgIDxzdHlsZT4KICAgICAgdGYtZ3JhcGgtaWNvbiB7CiAgICAgICAgLS10Yi1ncmFwaC1mYWRlZDogdmFyKC0tdGItZ3JhcGgtZmFkZWQpOwogICAgICB9CiAgICA8L3N0eWxlPgogICAgPHRmLWdyYXBoLWljb24KICAgICAgaWQ9Imljb24iCiAgICAgIHR5cGU9IltbX2dldFR5cGUobm9kZSwgc3VtbWFyeSwgY29uc3QsIHR5cGUpXV0iCiAgICAgIGhlaWdodD0iW1toZWlnaHRdXSIKICAgICAgZmlsbC1vdmVycmlkZT0iW1tfZmlsbE92ZXJyaWRlXV0iCiAgICAgIHN0cm9rZS1vdmVycmlkZT0iW1tfZ2V0U3Ryb2tlT3ZlcnJpZGUoX2ZpbGxPdmVycmlkZSldXSIKICAgICAgZmFkZWQ9IltbX2dldEZhZGVkKHJlbmRlckluZm8pXV0iCiAgICAgIHZlcnRpY2FsPSJbW19pc1ZlcnRpY2FsKG5vZGUsIHZlcnRpY2FsKV1dIgogICAgPjwvdGYtZ3JhcGgtaWNvbj4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEJzLnByb3RvdHlwZSwibm9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxCcy5wcm90b3R5cGUsInJlbmRlckluZm8iLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sQnMucHJvdG90eXBlLCJjb2xvckJ5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEJzLnByb3RvdHlwZSwidGVtcGxhdGVJbmRleCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxCcy5wcm90b3R5cGUsInR5cGUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxCcy5wcm90b3R5cGUsInZlcnRpY2FsIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sQnMucHJvdG90eXBlLCJjb25zdCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLEJzLnByb3RvdHlwZSwic3VtbWFyeSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxCcy5wcm90b3R5cGUsImZpbGwiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sQnMucHJvdG90eXBlLCJoZWlnaHQiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmcsY29tcHV0ZWQ6Il9jb21wdXRlRmlsbE92ZXJyaWRlKG5vZGUsIHJlbmRlckluZm8sIGNvbG9yQnksIHRlbXBsYXRlSW5kZXgsIGZpbGwpIixvYnNlcnZlcjoiX29uRmlsbE92ZXJyaWRlQ2hhbmdlZCJ9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sQnMucHJvdG90eXBlLCJfZmlsbE92ZXJyaWRlIix2b2lkIDApO0JzPUUoW3l0KCJ0Zi1ub2RlLWljb24iKV0sQnMpO3ZhciBHYz1jbGFzcyBleHRlbmRzIEd0KG10KXtfaXRlbVR5cGVDaGFuZ2VkKCl7dGhpcy5pdGVtVHlwZSE9PSJzdWJub2RlIj90aGlzLiRbImxpc3QtaXRlbSJdLmNsYXNzTGlzdC5hZGQoImNsaWNrYWJsZSIpOnRoaXMuJFsibGlzdC1pdGVtIl0uY2xhc3NMaXN0LnJlbW92ZSgiY2xpY2thYmxlIil9X25vZGVMaXN0ZW5lcih0KXt0aGlzLmZpcmUoIm5vZGUtbGlzdC1pdGVtLSIrdC50eXBlLHtub2RlTmFtZTp0aGlzLm5hbWUsdHlwZTp0aGlzLml0ZW1UeXBlfSl9X2ZhZGVkQ2xhc3ModCl7cmV0dXJuIHQmJnQuaXNGYWRlZE91dD8iZmFkZWQiOiIifX07R2MudGVtcGxhdGU9UWAKICAgIDxzdHlsZT4KICAgICAgI2xpc3QtaXRlbSB7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgY29sb3I6IHZhcigtLXNlY29uZGFyeS10ZXh0LWNvbG9yKTsKICAgICAgICBmb250LXNpemU6IDExcHQ7CiAgICAgICAgZm9udC13ZWlnaHQ6IDQwMDsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICB9CgogICAgICAjbGlzdC1pdGVtOmhvdmVyIHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1nb29nbGUteWVsbG93LTEwMCk7CiAgICAgIH0KCiAgICAgIC5jbGlja2FibGUgewogICAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgICAgfQoKICAgICAgI2xpc3QtaXRlbSBzcGFuIHsKICAgICAgICBtYXJnaW4tbGVmdDogNDBweDsKICAgICAgfQoKICAgICAgI2xpc3QtaXRlbS5leGNsdWRlZCBzcGFuIHsKICAgICAgICBjb2xvcjogIzk5OTsKICAgICAgfQoKICAgICAgI2xpc3QtaXRlbSBzcGFuLmVkZ2UtbGFiZWwgewogICAgICAgIGZsb2F0OiByaWdodDsKICAgICAgICBmb250LXNpemU6IDEwcHg7CiAgICAgICAgbWFyZ2luLWxlZnQ6IDNweDsKICAgICAgICBtYXJnaW4tcmlnaHQ6IDVweDsKICAgICAgfQoKICAgICAgLm5vZGUtaWNvbiB7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIHRvcDogMXB4OwogICAgICAgIGxlZnQ6IDJweDsKICAgICAgfQoKICAgICAgLmZhZGVkIHNwYW4gewogICAgICAgIGNvbG9yOiB2YXIoLS10Yi1ncmFwaC1mYWRlZCk7CiAgICAgIH0KICAgIDwvc3R5bGU+CgogICAgPGRpdgogICAgICBpZD0ibGlzdC1pdGVtIgogICAgICBvbi1tb3VzZW92ZXI9Il9ub2RlTGlzdGVuZXIiCiAgICAgIG9uLW1vdXNlb3V0PSJfbm9kZUxpc3RlbmVyIgogICAgICBvbi1jbGljaz0iX25vZGVMaXN0ZW5lciIKICAgID4KICAgICAgPGRpdiBjbGFzcyQ9Int7X2ZhZGVkQ2xhc3MoaXRlbVJlbmRlckluZm8pfX0iPgogICAgICAgIDx0Zi1ub2RlLWljb24KICAgICAgICAgIGNsYXNzPSJub2RlLWljb24iCiAgICAgICAgICBoZWlnaHQ9IjEyIgogICAgICAgICAgY29sb3ItYnk9IltbY29sb3JCeV1dIgogICAgICAgICAgY29sb3ItYnktcGFyYW1zPSJbW2NvbG9yQnlQYXJhbXNdXSIKICAgICAgICAgIG5vZGU9IltbaXRlbU5vZGVdXSIKICAgICAgICAgIHJlbmRlci1pbmZvPSJbW2l0ZW1SZW5kZXJJbmZvXV0iCiAgICAgICAgICB0ZW1wbGF0ZS1pbmRleD0iW1t0ZW1wbGF0ZUluZGV4XV0iCiAgICAgICAgPgogICAgICAgIDwvdGYtbm9kZS1pY29uPgogICAgICAgIDxzcGFuIHRpdGxlJD0iW1tuYW1lXV0iPltbbmFtZV1dPC9zcGFuPgogICAgICA8L2Rpdj4KICAgIDwvZGl2PgogIGA7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sR2MucHJvdG90eXBlLCJjYXJkTm9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxHYy5wcm90b3R5cGUsIml0ZW1Ob2RlIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLEdjLnByb3RvdHlwZSwiZWRnZUxhYmVsIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEdjLnByb3RvdHlwZSwiaXRlbVJlbmRlckluZm8iLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sR2MucHJvdG90eXBlLCJuYW1lIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG9ic2VydmVyOiJfaXRlbVR5cGVDaGFuZ2VkIn0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxHYy5wcm90b3R5cGUsIml0ZW1UeXBlIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLEdjLnByb3RvdHlwZSwiY29sb3JCeSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxHYy5wcm90b3R5cGUsImNvbG9yQnlQYXJhbXMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pXSxHYy5wcm90b3R5cGUsInRlbXBsYXRlSW5kZXgiLHZvaWQgMCk7R2M9RShbeXQoInRmLWdyYXBoLW9wLWNvbXBhdC1saXN0LWl0ZW0iKV0sR2MpO3ZhciBhcz1jbGFzcyBleHRlbmRzIEd0KF9vKG10KSl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX2V4cGFuZGVkPSEwLHRoaXMuX29wQ29tcGF0Q29sb3I9eTAuQ09NUEFUSUJMRSx0aGlzLl9vcEluY29tcGF0Q29sb3I9eTAuSU5DT01QQVRJQkxFLHRoaXMuX3RlbXBsYXRlSW5kZXg9bnVsbH1fZ2V0Tm9kZSh0LHIpe3JldHVybiByLm5vZGUodCl9X2dldFJlbmRlckluZm8odCxyKXtyZXR1cm4gdGhpcy5yZW5kZXJIaWVyYXJjaHkuZ2V0T3JDcmVhdGVSZW5kZXJOb2RlQnlOYW1lKHQpfV90b2dnbGVFeHBhbmRlZCgpe3RoaXMuX2V4cGFuZGVkPSF0aGlzLl9leHBhbmRlZH1fZ2V0VG9nZ2xlSWNvbih0KXtyZXR1cm4gdD8iZXhwYW5kLWxlc3MiOiJleHBhbmQtbW9yZSJ9X3Jlc2l6ZUxpc3QodCl7dmFyIHI9ZG9jdW1lbnQucXVlcnlTZWxlY3Rvcih0KTtyJiZyLmZpcmUoImlyb24tcmVzaXplIil9Z2V0IF9pbmNvbXBhdGlibGVPcE5vZGVzKCl7bGV0IHQ9dGhpcy5ncmFwaEhpZXJhcmNoeTtyZXR1cm4hdHx8IXQucm9vdD9bXToodGhpcy5hc3luYyh0aGlzLl9yZXNpemVMaXN0LmJpbmQodGhpcywiI2luY29tcGF0aWJsZU9wc0xpc3QiKSksY2xlKHQpKX1nZXQgX29wQ29tcGF0U2NvcmUoKXt2YXIgdD10aGlzLmdyYXBoSGllcmFyY2h5O2lmKHQmJnQucm9vdCl7dmFyIHI9dC5yb290LG49ci5jb21wYXRpYmlsaXR5SGlzdG9ncmFtLmNvbXBhdGlibGUsaT1yLmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uaW5jb21wYXRpYmxlO2lmKG49PTAmJmk9PTApcmV0dXJuIDA7dmFyIG89bitpO3JldHVybiBNYXRoLmZsb29yKDEwMCpuL28pLzEwMH1yZXR1cm4gMH1nZXQgX29wQ29tcGF0U2NvcmVMYWJlbCgpe3ZhciB0PXRoaXMuX29wQ29tcGF0U2NvcmU7cmV0dXJuIHhuKCIuMCUiKSh0KX1nZXQgX3RvdGFsSW5jb21wYXRPcHMoKXt2YXIgdD10aGlzLmdyYXBoSGllcmFyY2h5O3JldHVybiB0JiZ0LnJvb3Q/dC5yb290LmNvbXBhdGliaWxpdHlIaXN0b2dyYW0uaW5jb21wYXRpYmxlOjB9X2dyYXBoSGllcmFyY2h5Q2hhbmdlZCgpe3RoaXMuX3RlbXBsYXRlSW5kZXg9dGhpcy5ncmFwaEhpZXJhcmNoeS5nZXRUZW1wbGF0ZUluZGV4KCksdGhpcy5ncmFwaEhpZXJhcmNoeS5hZGRMaXN0ZW5lcihEZC5URU1QTEFURVNfVVBEQVRFRCwoKT0+e3RoaXMuX3RlbXBsYXRlSW5kZXg9dGhpcy5ncmFwaEhpZXJhcmNoeS5nZXRUZW1wbGF0ZUluZGV4KCl9KX19O2FzLnRlbXBsYXRlPVFgCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBtYXgtaGVpZ2h0OiA1MDBweDsKICAgICAgfQoKICAgICAgLmluY29tcGF0aWJsZS1vcHMtbGlzdCB7CiAgICAgICAgaGVpZ2h0OiAzNTBweDsKICAgICAgICBtYXgtaGVpZ2h0OiA0MDBweDsKICAgICAgICBvdmVyZmxvdy15OiBzY3JvbGw7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICB9CgogICAgICBpcm9uLWxpc3QgewogICAgICAgIGZsZXg6IDEgMSBhdXRvOwogICAgICB9CgogICAgICBwYXBlci1pdGVtIHsKICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgIGJhY2tncm91bmQ6IHZhcigtLXNlY29uZGFyeS1iYWNrZ3JvdW5kLWNvbG9yKTsKICAgICAgfQoKICAgICAgcGFwZXItaXRlbS1ib2R5W3R3by1saW5lXSB7CiAgICAgICAgbWluLWhlaWdodDogMDsKICAgICAgICBwYWRkaW5nOiA4cHggMTJweCA0cHg7CiAgICAgIH0KCiAgICAgIC5leHBhbmRlZEluZm8gewogICAgICAgIHBhZGRpbmc6IDhweCAxMnB4OwogICAgICAgIGZvbnQtd2VpZ2h0OiA1MDA7CiAgICAgICAgZm9udC1zaXplOiAxMnB0OwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICB9CgogICAgICAubm9kZS1uYW1lIHsKICAgICAgICB3aGl0ZS1zcGFjZTogbm9ybWFsOwogICAgICAgIHdvcmQtd3JhcDogYnJlYWstd29yZDsKICAgICAgICBmb250LXNpemU6IDE0cHQ7CiAgICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgICAgfQoKICAgICAgLnN1YnRpdGxlIHsKICAgICAgICBjb2xvcjogdmFyKC0tc2Vjb25kYXJ5LXRleHQtY29sb3IpOwogICAgICAgIGZvbnQtc2l6ZTogMTJwdDsKICAgICAgfQoKICAgICAgLnRvZ2dsZS1idXR0b24gewogICAgICAgIGZsb2F0OiByaWdodDsKICAgICAgICBtYXgtaGVpZ2h0OiAyMHB4OwogICAgICAgIG1heC13aWR0aDogMjBweDsKICAgICAgICBwYWRkaW5nOiAwOwogICAgICB9CgogICAgICAubm9uLWNvbnRyb2wtbGlzdC1pdGVtIHsKICAgICAgICBwYWRkaW5nLWxlZnQ6IDEwcHg7CiAgICAgIH0KCiAgICAgIGRpdi5vcC1jb21wYXQtZGlzcGxheSB7CiAgICAgICAgbWFyZ2luLXRvcDogMTBweDsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgIH0KCiAgICAgIC8qKgogICAgICAgKiBTYWRseSwgYmVjYXVzZSB0aGUgd2hvbGUgYm9keSBpcyBpbnZlcnRlZCBpbiBjb2xvciwgbGVnZW5kcyBhbHNvIG5lZWQKICAgICAgICogdG8gYmUgaW52ZXJ0ZWQuCiAgICAgICAqKi8KICAgICAgOmhvc3QoLmRhcmstbW9kZSkgZGl2Lm9wLWNvbXBhdC1kaXNwbGF5IHsKICAgICAgICBmaWx0ZXI6IGludmVydCgxKTsKICAgICAgfQoKICAgICAgc3ZnLm9wLWNvbXBhdCB7CiAgICAgICAgd2lkdGg6IDI1MHB4OwogICAgICAgIGhlaWdodDogMjVweDsKICAgICAgICBmbG9hdDogbGVmdDsKICAgICAgfQoKICAgICAgZGl2Lm9wLWNvbXBhdC12YWx1ZSB7CiAgICAgICAgZmxvYXQ6IHJpZ2h0OwogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgICBmb250LXNpemU6IDE0cHg7CiAgICAgICAgY29sb3I6IGJsYWNrOwogICAgICAgIG1hcmdpbi1sZWZ0OiAxMHB4OwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxwYXBlci1pdGVtPgogICAgICA8cGFwZXItaXRlbS1ib2R5IHR3by1saW5lPgogICAgICAgIDxkaXY+CiAgICAgICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICAgICAgaWNvbj0ie3tfZ2V0VG9nZ2xlSWNvbihfZXhwYW5kZWQpfX0iCiAgICAgICAgICAgIG9uLWNsaWNrPSJfdG9nZ2xlRXhwYW5kZWQiCiAgICAgICAgICAgIGNsYXNzPSJ0b2dnbGUtYnV0dG9uIgogICAgICAgICAgPgogICAgICAgICAgPC9wYXBlci1pY29uLWJ1dHRvbj4KICAgICAgICAgIDxkaXYgY2xhc3M9Im5vZGUtbmFtZSIgaWQ9Im5vZGV0aXRsZSI+W1tub2RlVGl0bGVdXTwvZGl2PgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgc2Vjb25kYXJ5PgogICAgICAgICAgPGRpdiBjbGFzcz0ic3VidGl0bGUiPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJvcC1jb21wYXQtZGlzcGxheSI+CiAgICAgICAgICAgICAgPHN2ZwogICAgICAgICAgICAgICAgY2xhc3M9Im9wLWNvbXBhdCIKICAgICAgICAgICAgICAgIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWlkIG1lZXQiCiAgICAgICAgICAgICAgICB2aWV3Qm94PSIwIDAgMjUwIDI1IgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDxkZWZzPgogICAgICAgICAgICAgICAgICA8bGluZWFyR3JhZGllbnQgaWQ9Im9wLWNvbXBhdC1maWxsIj4KICAgICAgICAgICAgICAgICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3IkPSJbW19vcENvbXBhdENvbG9yXV0iPjwvc3RvcD4KICAgICAgICAgICAgICAgICAgICA8c3RvcAogICAgICAgICAgICAgICAgICAgICAgb2Zmc2V0JD0iW1tfb3BDb21wYXRTY29yZV1dIgogICAgICAgICAgICAgICAgICAgICAgc3RvcC1jb2xvciQ9IltbX29wQ29tcGF0Q29sb3JdXSIKICAgICAgICAgICAgICAgICAgICA+PC9zdG9wPgogICAgICAgICAgICAgICAgICAgIDxzdG9wCiAgICAgICAgICAgICAgICAgICAgICBvZmZzZXQkPSJbW19vcENvbXBhdFNjb3JlXV0iCiAgICAgICAgICAgICAgICAgICAgICBzdG9wLWNvbG9yJD0iW1tfb3BJbmNvbXBhdENvbG9yXV0iCiAgICAgICAgICAgICAgICAgICAgPjwvc3RvcD4KICAgICAgICAgICAgICAgICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3IkPSJbW19vcEluY29tcGF0Q29sb3IgXV0iPjwvc3RvcD4KICAgICAgICAgICAgICAgICAgPC9saW5lYXJHcmFkaWVudD4KICAgICAgICAgICAgICAgIDwvZGVmcz4KICAgICAgICAgICAgICAgIDxyZWN0CiAgICAgICAgICAgICAgICAgIGhlaWdodD0iMjUiCiAgICAgICAgICAgICAgICAgIHdpZHRoPSIyNTAiCiAgICAgICAgICAgICAgICAgIHJ4PSI1IgogICAgICAgICAgICAgICAgICByeT0iNSIKICAgICAgICAgICAgICAgICAgc3R5bGU9ImZpbGw6IHVybCgnI29wLWNvbXBhdC1maWxsJyk7IgogICAgICAgICAgICAgICAgPjwvcmVjdD4KICAgICAgICAgICAgICA8L3N2Zz4KICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJvcC1jb21wYXQtdmFsdWUiPltbX29wQ29tcGF0U2NvcmVMYWJlbF1dPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvcGFwZXItaXRlbS1ib2R5PgogICAgPC9wYXBlci1pdGVtPgoKICAgIDxpcm9uLWNvbGxhcHNlIG9wZW5lZD0ie3tfZXhwYW5kZWR9fSI+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0ie3tfZXhwYW5kZWR9fSIgcmVzdGFtcD0idHJ1ZSI+CiAgICAgICAgPGRpdiBjbGFzcz0iZXhwYW5kZWRJbmZvIj4KICAgICAgICAgIEluY29tcGF0aWJsZSBPcGVyYXRpb25zOiAoPHNwYW4+W1tfdG90YWxJbmNvbXBhdE9wc11dPC9zcGFuPikKICAgICAgICAgIDxpcm9uLWxpc3QKICAgICAgICAgICAgY2xhc3M9ImluY29tcGF0aWJsZS1vcHMtbGlzdCIKICAgICAgICAgICAgaWQ9ImluY29tcGF0aWJsZU9wc0xpc3QiCiAgICAgICAgICAgIGl0ZW1zPSJbW19pbmNvbXBhdGlibGVPcE5vZGVzXV0iCiAgICAgICAgICA+CiAgICAgICAgICAgIDx0ZW1wbGF0ZT4KICAgICAgICAgICAgICA8dGYtZ3JhcGgtb3AtY29tcGF0LWxpc3QtaXRlbQogICAgICAgICAgICAgICAgY2xhc3M9Im5vbi1jb250cm9sLWxpc3QtaXRlbSIKICAgICAgICAgICAgICAgIGl0ZW0tbm9kZT0iW1tpdGVtXV0iCiAgICAgICAgICAgICAgICBpdGVtLXJlbmRlci1pbmZvPSJbW19nZXRSZW5kZXJJbmZvKGl0ZW0ubmFtZSwgcmVuZGVySGllcmFyY2h5KV1dIgogICAgICAgICAgICAgICAgbmFtZT0iW1tpdGVtLm5hbWVdXSIKICAgICAgICAgICAgICAgIHRlbXBsYXRlLWluZGV4PSJbW190ZW1wbGF0ZUluZGV4XV0iCiAgICAgICAgICAgICAgICBjb2xvci1ieT0iW1tjb2xvckJ5XV0iCiAgICAgICAgICAgICAgICBpdGVtLXR5cGU9ImluY29tcGF0aWJsZS1vcHMiCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIDwvdGYtZ3JhcGgtb3AtY29tcGF0LWxpc3QtaXRlbT4KICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDwvaXJvbi1saXN0PgogICAgICAgIDwvZGl2PgogICAgICA8L3RlbXBsYXRlPgogICAgPC9pcm9uLWNvbGxhcHNlPgogIGA7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsb3MpXSxhcy5wcm90b3R5cGUsImdyYXBoSGllcmFyY2h5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLGxvKV0sYXMucHJvdG90eXBlLCJyZW5kZXJIaWVyYXJjaHkiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sYXMucHJvdG90eXBlLCJub2RlVGl0bGUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxhcy5wcm90b3R5cGUsIl9leHBhbmRlZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxhcy5wcm90b3R5cGUsIl9vcENvbXBhdENvbG9yIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLGFzLnByb3RvdHlwZSwiX29wSW5jb21wYXRDb2xvciIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxhcy5wcm90b3R5cGUsIl90ZW1wbGF0ZUluZGV4Iix2b2lkIDApO0UoW1J0KCJncmFwaEhpZXJhcmNoeSIpLHcoImRlc2lnbjp0eXBlIixBcnJheSksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sYXMucHJvdG90eXBlLCJfaW5jb21wYXRpYmxlT3BOb2RlcyIsbnVsbCk7RShbUnQoImdyYXBoSGllcmFyY2h5IiksdygiZGVzaWduOnR5cGUiLE51bWJlciksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sYXMucHJvdG90eXBlLCJfb3BDb21wYXRTY29yZSIsbnVsbCk7RShbUnQoIl9vcENvbXBhdFNjb3JlIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sYXMucHJvdG90eXBlLCJfb3BDb21wYXRTY29yZUxhYmVsIixudWxsKTtFKFtSdCgiZ3JhcGhIaWVyYXJjaHkiKSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxhcy5wcm90b3R5cGUsIl90b3RhbEluY29tcGF0T3BzIixudWxsKTtFKFtCdCgiZ3JhcGhIaWVyYXJjaHkiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLGFzLnByb3RvdHlwZSwiX2dyYXBoSGllcmFyY2h5Q2hhbmdlZCIsbnVsbCk7YXM9RShbeXQoInRmLWdyYXBoLW9wLWNvbXBhdC1jYXJkIildLGFzKTt2YXIgbDM9RWUoT2UoKSwxKTt2YXIgV2M9Y2xhc3MgZXh0ZW5kcyBHdChfbyhtdCkpe19pdGVtVHlwZUNoYW5nZWQoKXt0aGlzLml0ZW1UeXBlIT09InN1Ym5vZGUiP3RoaXMuJFsibGlzdC1pdGVtIl0uY2xhc3NMaXN0LmFkZCgiY2xpY2thYmxlIik6dGhpcy4kWyJsaXN0LWl0ZW0iXS5jbGFzc0xpc3QucmVtb3ZlKCJjbGlja2FibGUiKX1fbm9kZUxpc3RlbmVyKHQpe3RoaXMuZmlyZSgibm9kZS1saXN0LWl0ZW0tIit0LnR5cGUse2NhcmROb2RlOnRoaXMuY2FyZE5vZGUubmFtZSxub2RlTmFtZTp0aGlzLm5hbWUsdHlwZTp0aGlzLml0ZW1UeXBlfSl9X2ZhZGVkQ2xhc3ModCl7cmV0dXJuIHQmJnQuaXNGYWRlZE91dD8iZmFkZWQiOiIifX07V2MudGVtcGxhdGU9UWAKICAgIDxzdHlsZT4KICAgICAgI2xpc3QtaXRlbSB7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgY29sb3I6IHZhcigtLXNlY29uZGFyeS10ZXh0LWNvbG9yKTsKICAgICAgICBmb250LXNpemU6IDExcHQ7CiAgICAgICAgZm9udC13ZWlnaHQ6IDQwMDsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICB9CgogICAgICAjbGlzdC1pdGVtOmhvdmVyIHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1nb29nbGUteWVsbG93LTEwMCk7CiAgICAgIH0KCiAgICAgIDpob3N0KC5kYXJrLW1vZGUpICNsaXN0LWl0ZW06aG92ZXIgewogICAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLXBhcGVyLXllbGxvdy05MDApOwogICAgICAgIGNvbG9yOiAjZmZmOwogICAgICB9CgogICAgICAuY2xpY2thYmxlIHsKICAgICAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICAgIH0KCiAgICAgICNsaXN0LWl0ZW0gc3BhbiB7CiAgICAgICAgbWFyZ2luLWxlZnQ6IDQwcHg7CiAgICAgIH0KCiAgICAgICNsaXN0LWl0ZW0uZXhjbHVkZWQgc3BhbiB7CiAgICAgICAgY29sb3I6ICM5OTk7CiAgICAgIH0KCiAgICAgICNsaXN0LWl0ZW0gc3Bhbi5lZGdlLWxhYmVsIHsKICAgICAgICBmbG9hdDogcmlnaHQ7CiAgICAgICAgZm9udC1zaXplOiAxMHB4OwogICAgICAgIG1hcmdpbi1sZWZ0OiAzcHg7CiAgICAgICAgbWFyZ2luLXJpZ2h0OiA1cHg7CiAgICAgIH0KCiAgICAgIC5ub2RlLWljb24gewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICB0b3A6IDFweDsKICAgICAgICBsZWZ0OiAycHg7CiAgICAgIH0KCiAgICAgIC5mYWRlZCBzcGFuIHsKICAgICAgICBjb2xvcjogdmFyKC0tdGItZ3JhcGgtZmFkZWQpOwogICAgICB9CiAgICA8L3N0eWxlPgogICAgPGRpdgogICAgICBpZD0ibGlzdC1pdGVtIgogICAgICBvbi1tb3VzZW92ZXI9Il9ub2RlTGlzdGVuZXIiCiAgICAgIG9uLW1vdXNlb3V0PSJfbm9kZUxpc3RlbmVyIgogICAgICBvbi1jbGljaz0iX25vZGVMaXN0ZW5lciIKICAgID4KICAgICAgPGRpdiBjbGFzcyQ9Int7X2ZhZGVkQ2xhc3MoaXRlbVJlbmRlckluZm8pfX0iPgogICAgICAgIDx0Zi1ub2RlLWljb24KICAgICAgICAgIGNsYXNzPSJub2RlLWljb24iCiAgICAgICAgICBoZWlnaHQ9IjEyIgogICAgICAgICAgY29sb3ItYnk9IltbY29sb3JCeV1dIgogICAgICAgICAgY29sb3ItYnktcGFyYW1zPSJbW2NvbG9yQnlQYXJhbXNdXSIKICAgICAgICAgIG5vZGU9IltbaXRlbU5vZGVdXSIKICAgICAgICAgIHJlbmRlci1pbmZvPSJbW2l0ZW1SZW5kZXJJbmZvXV0iCiAgICAgICAgICB0ZW1wbGF0ZS1pbmRleD0iW1t0ZW1wbGF0ZUluZGV4XV0iCiAgICAgICAgPjwvdGYtbm9kZS1pY29uPgogICAgICAgIDxzcGFuIHRpdGxlJD0iW1tuYW1lXV0iPltbbmFtZV1dPC9zcGFuPgogICAgICAgIDxzcGFuIGNsYXNzPSJlZGdlLWxhYmVsIj5bW2VkZ2VMYWJlbF1dPC9zcGFuPgogICAgICA8L2Rpdj4KICAgIDwvZGl2PgogIGA7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sV2MucHJvdG90eXBlLCJjYXJkTm9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxXYy5wcm90b3R5cGUsIml0ZW1Ob2RlIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLFdjLnByb3RvdHlwZSwiZWRnZUxhYmVsIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFdjLnByb3RvdHlwZSwiaXRlbVJlbmRlckluZm8iLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sV2MucHJvdG90eXBlLCJuYW1lIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG9ic2VydmVyOiJfaXRlbVR5cGVDaGFuZ2VkIn0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxXYy5wcm90b3R5cGUsIml0ZW1UeXBlIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLFdjLnByb3RvdHlwZSwiY29sb3JCeSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxXYy5wcm90b3R5cGUsImNvbG9yQnlQYXJhbXMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sV2MucHJvdG90eXBlLCJ0ZW1wbGF0ZUluZGV4Iix2b2lkIDApO1djPUUoW3l0KCJ0Zi1ub2RlLWxpc3QtaXRlbSIpXSxXYyk7dmFyIGRuPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl9leHBhbmRlZD0hMCx0aGlzLl9vcGVuZWRDb250cm9sUHJlZD0hMSx0aGlzLl9vcGVuZWRDb250cm9sU3VjYz0hMSx0aGlzLl90ZW1wbGF0ZUluZGV4PW51bGx9ZXhwYW5kTm9kZSgpe3RoaXMuZmlyZSgiX25vZGUuZXhwYW5kIix0aGlzLm5vZGUpfV9nZXROb2RlKHQscil7cmV0dXJuIHIubm9kZSh0KX1fZ2V0Tm9kZVN0YXRzKHQscil7dmFyIG49dGhpcy5fZ2V0Tm9kZSh0LHIpO3JldHVybiBuP24uc3RhdHM6bnVsbH1fZ2V0VG90YWxNaWNyb3ModCl7cmV0dXJuIHQ/dC5nZXRUb3RhbE1pY3JvcygpOjB9Z2V0IF9oYXNEaXNwbGF5YWJsZU5vZGVTdGF0cygpe3ZhciB0PXRoaXMuX25vZGVTdGF0cztyZXR1cm4gbUgodCl9Z2V0IF9ub2RlU3RhdHNGb3JtYXR0ZWRCeXRlcygpe3ZhciB0PXRoaXMuX25vZGVTdGF0cztpZighKCF0fHwhdC50b3RhbEJ5dGVzKSlyZXR1cm4gTmQodC50b3RhbEJ5dGVzLG5QKX1nZXQgX25vZGVTdGF0c0Zvcm1hdHRlZENvbXB1dGVUaW1lKCl7dmFyIHQ9dGhpcy5fbm9kZVN0YXRzO2lmKCEoIXR8fCF0LmdldFRvdGFsTWljcm9zKCkpKXJldHVybiBOZCh0LmdldFRvdGFsTWljcm9zKCksaVApfWdldCBfbm9kZVN0YXRzRm9ybWF0dGVkT3V0cHV0U2l6ZXMoKXt2YXIgdD10aGlzLl9ub2RlU3RhdHM7aWYoISghdHx8IXQub3V0cHV0U2l6ZXx8IXQub3V0cHV0U2l6ZS5sZW5ndGgpKXJldHVybiBsMy5tYXAodC5vdXRwdXRTaXplLGZ1bmN0aW9uKHIpe3JldHVybiByLmxlbmd0aD09PTA/InNjYWxhciI6IlsiK3Iuam9pbigiLCAiKSsiXSJ9KX1fZ2V0UmVuZGVySW5mbyh0LHIpe3JldHVybiB0aGlzLnJlbmRlckhpZXJhcmNoeS5nZXRPckNyZWF0ZVJlbmRlck5vZGVCeU5hbWUodCl9Z2V0IF9hdHRyaWJ1dGVzKCl7dmFyIHQ9dGhpcy5fbm9kZTtpZih0aGlzLmFzeW5jKHRoaXMuX3Jlc2l6ZUxpc3QuYmluZCh0aGlzLCIjYXR0cmlidXRlc0xpc3QiKSksIXR8fCF0LmF0dHIpcmV0dXJuW107dmFyIHI9W107cmV0dXJuIGwzLmVhY2godC5hdHRyLGZ1bmN0aW9uKG4pe24ua2V5PT09UXNlP3I9ci5jb25jYXQobi52YWx1ZS5saXN0LnMubWFwKGZ1bmN0aW9uKGkpe3JldHVybntrZXk6aSx2YWx1ZToiVG9vIGxhcmdlIHRvIHNob3cuLi4ifX0pKTpyLnB1c2goe2tleTpuLmtleSx2YWx1ZTpKU09OLnN0cmluZ2lmeShuLnZhbHVlKX0pfSkscn1nZXQgX2RldmljZSgpe3ZhciB0PXRoaXMuX25vZGU7cmV0dXJuIHQ/dC5kZXZpY2U6bnVsbH1nZXQgX3N1Y2Nlc3NvcnMoKXt2YXIgdD10aGlzLl9ub2RlLHI9dGhpcy5ncmFwaEhpZXJhcmNoeTtyZXR1cm4gdGhpcy5fcmVmcmVzaE5vZGVJdGVtTGlzdCgiaW5wdXRzTGlzdCIpLHQ/dGhpcy5fY29udmVydEVkZ2VMaXN0VG9FZGdlSW5mb0xpc3Qoci5nZXRTdWNjZXNzb3JzKHQubmFtZSksITEsdC5pc0dyb3VwTm9kZSk6e3JlZ3VsYXI6W10sY29udHJvbDpbXX19Z2V0IF9wcmVkZWNlc3NvcnMoKXt2YXIgdD10aGlzLl9ub2RlLHI9dGhpcy5ncmFwaEhpZXJhcmNoeTtyZXR1cm4gdGhpcy5fcmVmcmVzaE5vZGVJdGVtTGlzdCgib3V0cHV0c0xpc3QiKSx0P3RoaXMuX2NvbnZlcnRFZGdlTGlzdFRvRWRnZUluZm9MaXN0KHIuZ2V0UHJlZGVjZXNzb3JzKHQubmFtZSksITAsdC5pc0dyb3VwTm9kZSk6e3JlZ3VsYXI6W10sY29udHJvbDpbXX19Z2V0IF9mdW5jdGlvblVzYWdlcygpe3ZhciB0PXRoaXMuX25vZGUscj10aGlzLmdyYXBoSGllcmFyY2h5O2lmKHRoaXMuX3JlZnJlc2hOb2RlSXRlbUxpc3QoImZ1bmN0aW9uVXNhZ2VzTGlzdCIpLCF0fHx0LnR5cGUhPT1qdC5NRVRBKXJldHVybltdO2xldCBuPXIubGlicmFyeUZ1bmN0aW9uc1t0LmFzc29jaWF0ZWRGdW5jdGlvbl07cmV0dXJuIG4/bi51c2FnZXM6W119X3JlZnJlc2hOb2RlSXRlbUxpc3QodCl7dGhpcy5hc3luYyh0aGlzLl9yZXNpemVMaXN0LmJpbmQodGhpcyxgIyR7dH1gKSl9X2NvbnZlcnRFZGdlTGlzdFRvRWRnZUluZm9MaXN0KHQscixuKXt2YXIgaT1hPT5sMy5tYXAoYS5iYXNlRWRnZUxpc3Qscz0+e3ZhciBsPXI/cy52OnMudztyZXR1cm57bmFtZTpsLG5vZGU6dGhpcy5fZ2V0Tm9kZShsLHRoaXMuZ3JhcGhIaWVyYXJjaHkpLGVkZ2VMYWJlbDpqbHQocyx0aGlzLnJlbmRlckhpZXJhcmNoeSkscmVuZGVySW5mbzp0aGlzLl9nZXRSZW5kZXJJbmZvKGwsdGhpcy5yZW5kZXJIaWVyYXJjaHkpfX0pLG89ZnVuY3Rpb24oYSl7dmFyIHM9W107cmV0dXJuIGwzLmVhY2goYSxsPT57dmFyIGM9cj9sLnY6bC53OyFufHxsLmJhc2VFZGdlTGlzdC5sZW5ndGg9PTE/cz1zLmNvbmNhdChpKGwpKTpzLnB1c2goe25hbWU6Yyxub2RlOnRoaXMuX2dldE5vZGUoYyx0aGlzLmdyYXBoSGllcmFyY2h5KSxlZGdlTGFiZWw6WGx0KGwsdGhpcy5yZW5kZXJIaWVyYXJjaHkpLHJlbmRlckluZm86dGhpcy5fZ2V0UmVuZGVySW5mbyhjLHRoaXMucmVuZGVySGllcmFyY2h5KX0pfSksc30uYmluZCh0aGlzKTtyZXR1cm57cmVndWxhcjpvKHQucmVndWxhciksY29udHJvbDpvKHQuY29udHJvbCl9fWdldCBfc3Vibm9kZXMoKXt2YXIgdD10aGlzLl9ub2RlO3JldHVybiB0JiZ0Lm1ldGFncmFwaD90Lm1ldGFncmFwaC5ub2RlcygpOm51bGx9Z2V0IF90b3RhbFByZWRlY2Vzc29ycygpe3ZhciB0PXRoaXMuX3ByZWRlY2Vzc29ycztyZXR1cm4gdC5yZWd1bGFyLmxlbmd0aCt0LmNvbnRyb2wubGVuZ3RofWdldCBfdG90YWxTdWNjZXNzb3JzKCl7dmFyIHQ9dGhpcy5fc3VjY2Vzc29ycztyZXR1cm4gdC5yZWd1bGFyLmxlbmd0aCt0LmNvbnRyb2wubGVuZ3RofV90b2dnbGVDb250cm9sUHJlZCgpe3RoaXMuX29wZW5lZENvbnRyb2xQcmVkPSF0aGlzLl9vcGVuZWRDb250cm9sUHJlZH1fdG9nZ2xlQ29udHJvbFN1Y2MoKXt0aGlzLl9vcGVuZWRDb250cm9sU3VjYz0hdGhpcy5fb3BlbmVkQ29udHJvbFN1Y2N9X3RvZ2dsZUV4cGFuZGVkKCl7dGhpcy5fZXhwYW5kZWQ9IXRoaXMuX2V4cGFuZGVkfV9nZXRUb2dnbGVJY29uKHQpe3JldHVybiB0PyJleHBhbmQtbGVzcyI6ImV4cGFuZC1tb3JlIn1fcmVzZXRTdGF0ZSgpe3RoaXMuX29wZW5lZENvbnRyb2xQcmVkPSExLHRoaXMuX29wZW5lZENvbnRyb2xTdWNjPSExLHRoaXMuc2V0KCJfZ3JvdXBCdXR0b25UZXh0Iix0Y3QodGhpcy5fbm9kZSkpfV9yZXNpemVMaXN0KHQpe3ZhciByPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IodCk7ciYmci5maXJlKCJpcm9uLXJlc2l6ZSIpfV90b2dnbGVJbmNsdWRlKCl7dGhpcy5maXJlKCJub2RlLXRvZ2dsZS1pbmNsdXNpb24iLHtuYW1lOnRoaXMuZ3JhcGhOb2RlTmFtZX0pfV9ub2RlSW5jbHVkZVN0YXRlQ2hhbmdlZCh0LHIpe3RoaXMuc2V0KCJfYXV4QnV0dG9uVGV4dCIsX0godCkpfV90b2dnbGVHcm91cCgpe3ZhciB0PVBIKHRoaXMuX25vZGUpO3RoaXMuZmlyZSgibm9kZS10b2dnbGUtc2VyaWVzZ3JvdXAiLHtuYW1lOnR9KX1faXNMaWJyYXJ5RnVuY3Rpb24odCl7cmV0dXJuIHQmJnQubmFtZS5zdGFydHNXaXRoKFNhKX1faXNJblNlcmllcyh0KXtyZXR1cm4gUWx0KHQpfV9ncmFwaEhpZXJhcmNoeUNoYW5nZWQoKXt0aGlzLl90ZW1wbGF0ZUluZGV4PXRoaXMuZ3JhcGhIaWVyYXJjaHkuZ2V0VGVtcGxhdGVJbmRleCgpLHRoaXMuZ3JhcGhIaWVyYXJjaHkuYWRkTGlzdGVuZXIoRGQuVEVNUExBVEVTX1VQREFURUQsKCk9Pnt0aGlzLl90ZW1wbGF0ZUluZGV4PXRoaXMuZ3JhcGhIaWVyYXJjaHkuZ2V0VGVtcGxhdGVJbmRleCgpfSl9fTtkbi50ZW1wbGF0ZT1RYAogICAgPHN0eWxlPgogICAgICAuc3ViLWxpc3QtZ3JvdXAgewogICAgICAgIGZvbnQtd2VpZ2h0OiA1MDA7CiAgICAgICAgZm9udC1zaXplOiAxMnB0OwogICAgICAgIHBhZGRpbmctYm90dG9tOiA4cHg7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIH0KCiAgICAgIC5zdWItbGlzdCB7CiAgICAgICAgbWF4LWhlaWdodDogMzAwcHg7CiAgICAgICAgb3ZlcmZsb3cteTogc2Nyb2xsOwogICAgICB9CgogICAgICAuYXR0ci1sZWZ0IHsKICAgICAgICBmbG9hdDogbGVmdDsKICAgICAgICB3aWR0aDogMzAlOwogICAgICAgIHdvcmQtd3JhcDogYnJlYWstd29yZDsKICAgICAgICBjb2xvcjogdmFyKC0tc2Vjb25kYXJ5LXRleHQtY29sb3IpOwogICAgICAgIGZvbnQtc2l6ZTogMTFwdDsKICAgICAgICBmb250LXdlaWdodDogNDAwOwogICAgICB9CgogICAgICAuYXR0ci1yaWdodCB7CiAgICAgICAgbWFyZ2luLWxlZnQ6IDMwJTsKICAgICAgICB3b3JkLXdyYXA6IGJyZWFrLXdvcmQ7CiAgICAgICAgY29sb3I6IHZhcigtLXNlY29uZGFyeS10ZXh0LWNvbG9yKTsKICAgICAgICBmb250LXdlaWdodDogNDAwOwogICAgICB9CgogICAgICAuc3ViLWxpc3QtdGFibGUgewogICAgICAgIGRpc3BsYXk6IHRhYmxlOwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICB9CgogICAgICAuc3ViLWxpc3QtdGFibGUtcm93IHsKICAgICAgICBkaXNwbGF5OiB0YWJsZS1yb3c7CiAgICAgIH0KCiAgICAgIC5zdWItbGlzdC10YWJsZS1yb3cgLnN1Yi1saXN0LXRhYmxlLWNlbGw6bGFzdC1jaGlsZCB7CiAgICAgICAgdGV4dC1hbGlnbjogcmlnaHQ7CiAgICAgIH0KCiAgICAgIC5zdWItbGlzdC10YWJsZS1jZWxsIHsKICAgICAgICBjb2xvcjogdmFyKC0tc2Vjb25kYXJ5LXRleHQtY29sb3IpOwogICAgICAgIGRpc3BsYXk6IHRhYmxlLWNlbGw7CiAgICAgICAgZm9udC1zaXplOiAxMXB0OwogICAgICAgIGZvbnQtd2VpZ2h0OiA0MDA7CiAgICAgICAgbWF4LXdpZHRoOiAyMDBweDsKICAgICAgICBwYWRkaW5nOiAwIDRweDsKICAgICAgfQoKICAgICAgcGFwZXItaXRlbSB7CiAgICAgICAgcGFkZGluZzogMDsKICAgICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1wcmltYXJ5LWJhY2tncm91bmQtY29sb3IpOwogICAgICB9CgogICAgICBwYXBlci1pdGVtLWJvZHlbdHdvLWxpbmVdIHsKICAgICAgICBtaW4taGVpZ2h0OiAwOwogICAgICAgIHBhZGRpbmc6IDhweCAxMnB4IDRweDsKICAgICAgfQoKICAgICAgLmV4cGFuZGVkSW5mbyB7CiAgICAgICAgcGFkZGluZzogOHB4IDEycHg7CiAgICAgIH0KCiAgICAgIC5jb250cm9sRGVwcyB7CiAgICAgICAgcGFkZGluZzogMCAwIDAgOHB4OwogICAgICB9CgogICAgICAubm9kZS1uYW1lIHsKICAgICAgICB3aGl0ZS1zcGFjZTogbm9ybWFsOwogICAgICAgIHdvcmQtd3JhcDogYnJlYWstd29yZDsKICAgICAgICBmb250LXNpemU6IDE0cHQ7CiAgICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgICAgfQoKICAgICAgLm5vZGUtaWNvbiB7CiAgICAgICAgZmxvYXQ6IHJpZ2h0OwogICAgICB9CgogICAgICAuc3VidGl0bGUgewogICAgICAgIGNvbG9yOiB2YXIoLS1zZWNvbmRhcnktdGV4dC1jb2xvcik7CiAgICAgICAgZm9udC1zaXplOiAxMnB0OwogICAgICB9CgogICAgICAuY29udHJvbExpbmUgewogICAgICAgIGZvbnQtc2l6ZTogMTFwdDsKICAgICAgICBmb250LXdlaWdodDogNDAwOwogICAgICB9CgogICAgICAudG9nZ2xlLWJ1dHRvbiB7CiAgICAgICAgZmxvYXQ6IHJpZ2h0OwogICAgICAgIG1heC1oZWlnaHQ6IDIwcHg7CiAgICAgICAgbWF4LXdpZHRoOiAyMHB4OwogICAgICAgIHBhZGRpbmc6IDA7CiAgICAgIH0KCiAgICAgIC5jb250cm9sLXRvZ2dsZS1idXR0b24gewogICAgICAgIGZsb2F0OiBsZWZ0OwogICAgICAgIG1heC1oZWlnaHQ6IDIwcHg7CiAgICAgICAgbWF4LXdpZHRoOiAyMHB4OwogICAgICAgIHBhZGRpbmc6IDA7CiAgICAgIH0KCiAgICAgIC50b2dnbGUtaW5jbHVkZS1ncm91cCB7CiAgICAgICAgcGFkZGluZy10b3A6IDRweDsKICAgICAgfQoKICAgICAgLnRvZ2dsZS1pbmNsdWRlIHsKICAgICAgICBtYXJnaW46IDVweCA2cHg7CiAgICAgICAgdGV4dC10cmFuc2Zvcm06IG5vbmU7CiAgICAgICAgcGFkZGluZzogNHB4IDZweDsKICAgICAgICBmb250LXNpemU6IDEwcHQ7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2ZhZmFmYTsKICAgICAgICBjb2xvcjogIzY2NjsKICAgICAgfQoKICAgICAgLnRvZ2dsZS1pbmNsdWRlOmhvdmVyIHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1nb29nbGUteWVsbG93LTEwMCk7CiAgICAgIH0KCiAgICAgIC5ub24tY29udHJvbC1saXN0LWl0ZW0gewogICAgICAgIHBhZGRpbmctbGVmdDogMTBweDsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxwYXBlci1pdGVtPgogICAgICA8cGFwZXItaXRlbS1ib2R5IHR3by1saW5lPgogICAgICAgIDxkaXY+CiAgICAgICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICAgICAgaWNvbj0ie3tfZ2V0VG9nZ2xlSWNvbihfZXhwYW5kZWQpfX0iCiAgICAgICAgICAgIG9uLWNsaWNrPSJfdG9nZ2xlRXhwYW5kZWQiCiAgICAgICAgICAgIGNsYXNzPSJ0b2dnbGUtYnV0dG9uIgogICAgICAgICAgPgogICAgICAgICAgPC9wYXBlci1pY29uLWJ1dHRvbj4KICAgICAgICAgIDxkaXYgY2xhc3M9Im5vZGUtbmFtZSI+CiAgICAgICAgICAgIDx0Zi13YnItc3RyaW5nIHZhbHVlPSJbW19ub2RlLm5hbWVdXSIgZGVsaW1pdGVyLXBhdHRlcm49Ii8iPgogICAgICAgICAgICA8L3RmLXdici1zdHJpbmc+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgICA8ZGl2IHNlY29uZGFyeT4KICAgICAgICAgIDx0Zi1ub2RlLWljb24KICAgICAgICAgICAgY2xhc3M9Im5vZGUtaWNvbiIKICAgICAgICAgICAgbm9kZT0iW1tfbm9kZV1dIgogICAgICAgICAgICByZW5kZXItaW5mbz0iW1tfZ2V0UmVuZGVySW5mbyhncmFwaE5vZGVOYW1lLCByZW5kZXJIaWVyYXJjaHkpXV0iCiAgICAgICAgICAgIGNvbG9yLWJ5PSJbW2NvbG9yQnldXSIKICAgICAgICAgICAgdGVtcGxhdGUtaW5kZXg9IltbX3RlbXBsYXRlSW5kZXhdXSIKICAgICAgICAgID48L3RmLW5vZGUtaWNvbj4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0ie3tfbm9kZS5vcH19Ij4KICAgICAgICAgICAgPGRpdiBjbGFzcz0ic3VidGl0bGUiPgogICAgICAgICAgICAgIE9wZXJhdGlvbjoKICAgICAgICAgICAgICA8c3Bhbj5bW19ub2RlLm9wXV08L3NwYW4+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0ie3tfbm9kZS5tZXRhZ3JhcGh9fSI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9InN1YnRpdGxlIj4KICAgICAgICAgICAgICBTdWJncmFwaDoKICAgICAgICAgICAgICA8c3Bhbj5bW19ub2RlLmNhcmRpbmFsaXR5XV08L3NwYW4+IG5vZGVzCiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L2Rpdj4KICAgICAgPC9wYXBlci1pdGVtLWJvZHk+CiAgICA8L3BhcGVyLWl0ZW0+CiAgICA8aXJvbi1jb2xsYXBzZSBvcGVuZWQ9Int7X2V4cGFuZGVkfX0iPgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9Int7X2V4cGFuZGVkfX0iIHJlc3RhbXA9InRydWUiPgogICAgICAgIDxkaXYgY2xhc3M9ImV4cGFuZGVkSW5mbyI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJzdWItbGlzdC1ncm91cCBhdHRyaWJ1dGVzIj4KICAgICAgICAgICAgQXR0cmlidXRlcyAoPHNwYW4+W1tfYXR0cmlidXRlcy5sZW5ndGhdXTwvc3Bhbj4pCiAgICAgICAgICAgIDxpcm9uLWxpc3QKICAgICAgICAgICAgICBjbGFzcz0ic3ViLWxpc3QiCiAgICAgICAgICAgICAgaWQ9ImF0dHJpYnV0ZXNMaXN0IgogICAgICAgICAgICAgIGl0ZW1zPSJbW19hdHRyaWJ1dGVzXV0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8ZGl2PgogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJhdHRyLWxlZnQiPltbaXRlbS5rZXldXTwvZGl2PgogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJhdHRyLXJpZ2h0Ij5bW2l0ZW0udmFsdWVdXTwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgPC9pcm9uLWxpc3Q+CiAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9Int7X2RldmljZX19Ij4KICAgICAgICAgICAgPGRpdiBjbGFzcz0ic3ViLWxpc3QtZ3JvdXAgZGV2aWNlIj4KICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJhdHRyLWxlZnQiPkRldmljZTwvZGl2PgogICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImF0dHItcmlnaHQiPltbX2RldmljZV1dPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KCiAgICAgICAgICA8ZGl2IGNsYXNzPSJzdWItbGlzdC1ncm91cCBwcmVkZWNlc3NvcnMiPgogICAgICAgICAgICBJbnB1dHMgKDxzcGFuPltbX3RvdGFsUHJlZGVjZXNzb3JzXV08L3NwYW4+KQogICAgICAgICAgICA8aXJvbi1saXN0CiAgICAgICAgICAgICAgY2xhc3M9InN1Yi1saXN0IgogICAgICAgICAgICAgIGlkPSJpbnB1dHNMaXN0IgogICAgICAgICAgICAgIGl0ZW1zPSJbW19wcmVkZWNlc3NvcnMucmVndWxhcl1dIgogICAgICAgICAgICA+CiAgICAgICAgICAgICAgPHRlbXBsYXRlPgogICAgICAgICAgICAgICAgPHRmLW5vZGUtbGlzdC1pdGVtCiAgICAgICAgICAgICAgICAgIGNsYXNzPSJub24tY29udHJvbC1saXN0LWl0ZW0iCiAgICAgICAgICAgICAgICAgIGNhcmQtbm9kZT0iW1tfbm9kZV1dIgogICAgICAgICAgICAgICAgICBpdGVtLW5vZGU9IltbaXRlbS5ub2RlXV0iCiAgICAgICAgICAgICAgICAgIGVkZ2UtbGFiZWw9IltbaXRlbS5lZGdlTGFiZWxdXSIKICAgICAgICAgICAgICAgICAgaXRlbS1yZW5kZXItaW5mbz0iW1tpdGVtLnJlbmRlckluZm9dXSIKICAgICAgICAgICAgICAgICAgbmFtZT0iW1tpdGVtLm5hbWVdXSIKICAgICAgICAgICAgICAgICAgaXRlbS10eXBlPSJwcmVkZWNlc3NvcnMiCiAgICAgICAgICAgICAgICAgIGNvbG9yLWJ5PSJbW2NvbG9yQnldXSIKICAgICAgICAgICAgICAgICAgdGVtcGxhdGUtaW5kZXg9IltbX3RlbXBsYXRlSW5kZXhdXSIKICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDwvdGYtbm9kZS1saXN0LWl0ZW0+CiAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgPC9pcm9uLWxpc3Q+CiAgICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfcHJlZGVjZXNzb3JzLmNvbnRyb2wubGVuZ3RoXV0iPgogICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRyb2xEZXBzIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRyb2xMaW5lIj4KICAgICAgICAgICAgICAgICAgPHBhcGVyLWljb24tYnV0dG9uCiAgICAgICAgICAgICAgICAgICAgaWNvbj0ie3tfZ2V0VG9nZ2xlSWNvbihfb3BlbmVkQ29udHJvbFByZWQpfX0iCiAgICAgICAgICAgICAgICAgICAgb24tY2xpY2s9Il90b2dnbGVDb250cm9sUHJlZCIKICAgICAgICAgICAgICAgICAgICBjbGFzcz0iY29udHJvbC10b2dnbGUtYnV0dG9uIgogICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDwvcGFwZXItaWNvbi1idXR0b24+CiAgICAgICAgICAgICAgICAgIENvbnRyb2wgZGVwZW5kZW5jaWVzCiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxpcm9uLWNvbGxhcHNlIG9wZW5lZD0ie3tfb3BlbmVkQ29udHJvbFByZWR9fSIgbm8tYW5pbWF0aW9uPgogICAgICAgICAgICAgICAgICA8dGVtcGxhdGUKICAgICAgICAgICAgICAgICAgICBpcz0iZG9tLWlmIgogICAgICAgICAgICAgICAgICAgIGlmPSJ7e19vcGVuZWRDb250cm9sUHJlZH19IgogICAgICAgICAgICAgICAgICAgIHJlc3RhbXA9InRydWUiCiAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICA8aXJvbi1saXN0CiAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0ic3ViLWxpc3QiCiAgICAgICAgICAgICAgICAgICAgICBpdGVtcz0iW1tfcHJlZGVjZXNzb3JzLmNvbnRyb2xdXSIKICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0Zi1ub2RlLWxpc3QtaXRlbQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhcmQtbm9kZT0iW1tfbm9kZV1dIgogICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZW0tbm9kZT0iW1tpdGVtLm5vZGVdXSIKICAgICAgICAgICAgICAgICAgICAgICAgICBpdGVtLXJlbmRlci1pbmZvPSJbW2l0ZW0ucmVuZGVySW5mb11dIgogICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWU9IltbaXRlbS5uYW1lXV0iCiAgICAgICAgICAgICAgICAgICAgICAgICAgaXRlbS10eXBlPSJwcmVkZWNlc3NvcnMiCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3ItYnk9IltbY29sb3JCeV1dIgogICAgICAgICAgICAgICAgICAgICAgICAgIHRlbXBsYXRlLWluZGV4PSJbW190ZW1wbGF0ZUluZGV4XV0iCiAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Zi1ub2RlLWxpc3QtaXRlbT4KICAgICAgICAgICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgICAgICAgICAgPC9pcm9uLWxpc3Q+CiAgICAgICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8L2lyb24tY29sbGFwc2U+CiAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICA8ZGl2IGNsYXNzPSJzdWItbGlzdC1ncm91cCBzdWNjZXNzb3JzIj4KICAgICAgICAgICAgT3V0cHV0cyAoPHNwYW4+W1tfdG90YWxTdWNjZXNzb3JzXV08L3NwYW4+KQogICAgICAgICAgICA8aXJvbi1saXN0CiAgICAgICAgICAgICAgY2xhc3M9InN1Yi1saXN0IgogICAgICAgICAgICAgIGlkPSJvdXRwdXRzTGlzdCIKICAgICAgICAgICAgICBpdGVtcz0iW1tfc3VjY2Vzc29ycy5yZWd1bGFyXV0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8dGYtbm9kZS1saXN0LWl0ZW0KICAgICAgICAgICAgICAgICAgY2xhc3M9Im5vbi1jb250cm9sLWxpc3QtaXRlbSIKICAgICAgICAgICAgICAgICAgY2FyZC1ub2RlPSJbW19ub2RlXV0iCiAgICAgICAgICAgICAgICAgIGl0ZW0tbm9kZT0iW1tpdGVtLm5vZGVdXSIKICAgICAgICAgICAgICAgICAgZWRnZS1sYWJlbD0iW1tpdGVtLmVkZ2VMYWJlbF1dIgogICAgICAgICAgICAgICAgICBpdGVtLXJlbmRlci1pbmZvPSJbW2l0ZW0ucmVuZGVySW5mb11dIgogICAgICAgICAgICAgICAgICBuYW1lPSJbW2l0ZW0ubmFtZV1dIgogICAgICAgICAgICAgICAgICBpdGVtLXR5cGU9InN1Y2Nlc3NvciIKICAgICAgICAgICAgICAgICAgY29sb3ItYnk9IltbY29sb3JCeV1dIgogICAgICAgICAgICAgICAgICB0ZW1wbGF0ZS1pbmRleD0iW1tfdGVtcGxhdGVJbmRleF1dIgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgPC90Zi1ub2RlLWxpc3QtaXRlbT4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8L2lyb24tbGlzdD4KICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19zdWNjZXNzb3JzLmNvbnRyb2wubGVuZ3RoXV0iPgogICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRyb2xEZXBzIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRyb2xMaW5lIj4KICAgICAgICAgICAgICAgICAgPHBhcGVyLWljb24tYnV0dG9uCiAgICAgICAgICAgICAgICAgICAgaWNvbj0ie3tfZ2V0VG9nZ2xlSWNvbihfb3BlbmVkQ29udHJvbFN1Y2MpfX0iCiAgICAgICAgICAgICAgICAgICAgb24tY2xpY2s9Il90b2dnbGVDb250cm9sU3VjYyIKICAgICAgICAgICAgICAgICAgICBjbGFzcz0iY29udHJvbC10b2dnbGUtYnV0dG9uIgogICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDwvcGFwZXItaWNvbi1idXR0b24+CiAgICAgICAgICAgICAgICAgIENvbnRyb2wgZGVwZW5kZW5jaWVzCiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxpcm9uLWNvbGxhcHNlIG9wZW5lZD0ie3tfb3BlbmVkQ29udHJvbFN1Y2N9fSIgbm8tYW5pbWF0aW9uPgogICAgICAgICAgICAgICAgICA8dGVtcGxhdGUKICAgICAgICAgICAgICAgICAgICBpcz0iZG9tLWlmIgogICAgICAgICAgICAgICAgICAgIGlmPSJ7e19vcGVuZWRDb250cm9sU3VjY319IgogICAgICAgICAgICAgICAgICAgIHJlc3RhbXA9InRydWUiCiAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICA8aXJvbi1saXN0IGNsYXNzPSJzdWItbGlzdCIgaXRlbXM9IltbX3N1Y2Nlc3NvcnMuY29udHJvbF1dIj4KICAgICAgICAgICAgICAgICAgICAgIDx0ZW1wbGF0ZT4KICAgICAgICAgICAgICAgICAgICAgICAgPHRmLW5vZGUtbGlzdC1pdGVtCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2FyZC1ub2RlPSJbW19ub2RlXV0iCiAgICAgICAgICAgICAgICAgICAgICAgICAgaXRlbS1ub2RlPSJbW2l0ZW0ubm9kZV1dIgogICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZW0tcmVuZGVyLWluZm89IltbaXRlbS5yZW5kZXJJbmZvXV0iCiAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZT0iW1tpdGVtLm5hbWVdXSIKICAgICAgICAgICAgICAgICAgICAgICAgICBpdGVtLXR5cGU9InN1Y2Nlc3NvcnMiCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3ItYnk9IltbY29sb3JCeV1dIgogICAgICAgICAgICAgICAgICAgICAgICAgIHRlbXBsYXRlLWluZGV4PSJbW190ZW1wbGF0ZUluZGV4XV0iCiAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Zi1ub2RlLWxpc3QtaXRlbT4KICAgICAgICAgICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgICAgICAgICAgPC9pcm9uLWxpc3Q+CiAgICAgICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8L2lyb24tY29sbGFwc2U+CiAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0ie3tfaGFzRGlzcGxheWFibGVOb2RlU3RhdHN9fSI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9InN1Yi1saXN0LWdyb3VwIG5vZGUtc3RhdHMiPgogICAgICAgICAgICAgIE5vZGUgU3RhdHMKICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJzdWItbGlzdC10YWJsZSI+CiAgICAgICAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9Int7X25vZGVTdGF0cy50b3RhbEJ5dGVzfX0iPgogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJzdWItbGlzdC10YWJsZS1yb3ciPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InN1Yi1saXN0LXRhYmxlLWNlbGwiPk1lbW9yeTwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InN1Yi1saXN0LXRhYmxlLWNlbGwiPgogICAgICAgICAgICAgICAgICAgICAgW1tfbm9kZVN0YXRzRm9ybWF0dGVkQnl0ZXNdXQogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9Int7X2dldFRvdGFsTWljcm9zKF9ub2RlU3RhdHMpfX0iPgogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJzdWItbGlzdC10YWJsZS1yb3ciPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InN1Yi1saXN0LXRhYmxlLWNlbGwiPkNvbXB1dGUgVGltZTwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InN1Yi1saXN0LXRhYmxlLWNlbGwiPgogICAgICAgICAgICAgICAgICAgICAgW1tfbm9kZVN0YXRzRm9ybWF0dGVkQ29tcHV0ZVRpbWVdXQogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9Int7X25vZGVTdGF0cy5vdXRwdXRTaXplfX0iPgogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJzdWItbGlzdC10YWJsZS1yb3ciPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InN1Yi1saXN0LXRhYmxlLWNlbGwiPlRlbnNvciBPdXRwdXQgU2l6ZXM8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJzdWItbGlzdC10YWJsZS1jZWxsIj4KICAgICAgICAgICAgICAgICAgICAgIDx0ZW1wbGF0ZQogICAgICAgICAgICAgICAgICAgICAgICBpcz0iZG9tLXJlcGVhdCIKICAgICAgICAgICAgICAgICAgICAgICAgaXRlbXM9Int7X25vZGVTdGF0c0Zvcm1hdHRlZE91dHB1dFNpemVzfX0iCiAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgIFtbaXRlbV1dIDxiciAvPgogICAgICAgICAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvdGVtcGxhdGU+CgogICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19mdW5jdGlvblVzYWdlcy5sZW5ndGhdXSI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9InN1Yi1saXN0LWdyb3VwIHByZWRlY2Vzc29ycyI+CiAgICAgICAgICAgICAgVXNhZ2VzIG9mIHRoZSBGdW5jdGlvbiAoPHNwYW4+W1tfZnVuY3Rpb25Vc2FnZXMubGVuZ3RoXV08L3NwYW4+KQogICAgICAgICAgICAgIDxpcm9uLWxpc3QKICAgICAgICAgICAgICAgIGNsYXNzPSJzdWItbGlzdCIKICAgICAgICAgICAgICAgIGlkPSJmdW5jdGlvblVzYWdlc0xpc3QiCiAgICAgICAgICAgICAgICBpdGVtcz0iW1tfZnVuY3Rpb25Vc2FnZXNdXSIKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgICAgICAgIDx0Zi1ub2RlLWxpc3QtaXRlbQogICAgICAgICAgICAgICAgICAgIGNsYXNzPSJub24tY29udHJvbC1saXN0LWl0ZW0iCiAgICAgICAgICAgICAgICAgICAgY2FyZC1ub2RlPSJbW19ub2RlXV0iCiAgICAgICAgICAgICAgICAgICAgaXRlbS1ub2RlPSJbW2l0ZW1dXSIKICAgICAgICAgICAgICAgICAgICBuYW1lPSJbW2l0ZW0ubmFtZV1dIgogICAgICAgICAgICAgICAgICAgIGl0ZW0tdHlwZT0iZnVuY3Rpb25Vc2FnZXMiCiAgICAgICAgICAgICAgICAgICAgY29sb3ItYnk9IltbY29sb3JCeV1dIgogICAgICAgICAgICAgICAgICAgIHRlbXBsYXRlLWluZGV4PSJbW190ZW1wbGF0ZUluZGV4XV0iCiAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgPC90Zi1ub2RlLWxpc3QtaXRlbT4KICAgICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgICAgPC9pcm9uLWxpc3Q+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KCiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbIV9pc0xpYnJhcnlGdW5jdGlvbihfbm9kZSldXSI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9InRvZ2dsZS1pbmNsdWRlLWdyb3VwIj4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uCiAgICAgICAgICAgICAgICByYWlzZWQKICAgICAgICAgICAgICAgIGNsYXNzPSJ0b2dnbGUtaW5jbHVkZSIKICAgICAgICAgICAgICAgIG9uLWNsaWNrPSJfdG9nZ2xlSW5jbHVkZSIKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8c3Bhbj5bW19hdXhCdXR0b25UZXh0XV08L3NwYW4+CiAgICAgICAgICAgICAgPC9wYXBlci1idXR0b24+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KCiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9Int7X2lzSW5TZXJpZXMoX25vZGUpfX0iPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJ0b2dnbGUtaW5jbHVkZS1ncm91cCI+CiAgICAgICAgICAgICAgPHBhcGVyLWJ1dHRvbgogICAgICAgICAgICAgICAgcmFpc2VkCiAgICAgICAgICAgICAgICBjbGFzcz0idG9nZ2xlLWluY2x1ZGUiCiAgICAgICAgICAgICAgICBvbi1jbGljaz0iX3RvZ2dsZUdyb3VwIgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDxzcGFuPltbX2dyb3VwQnV0dG9uVGV4dF1dPC9zcGFuPgogICAgICAgICAgICAgIDwvcGFwZXItYnV0dG9uPgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvdGVtcGxhdGU+CiAgICA8L2lyb24tY29sbGFwc2U+CiAgYDtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxkbi5wcm90b3R5cGUsImdyYXBoTm9kZU5hbWUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsb3MpXSxkbi5wcm90b3R5cGUsImdyYXBoSGllcmFyY2h5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGRuLnByb3RvdHlwZSwicmVuZGVySGllcmFyY2h5Iix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLGRuLnByb3RvdHlwZSwiY29sb3JCeSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxjb21wdXRlZDoiX2dldE5vZGUoZ3JhcGhOb2RlTmFtZSwgZ3JhcGhIaWVyYXJjaHkpIixvYnNlcnZlcjoiX3Jlc2V0U3RhdGUifSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGRuLnByb3RvdHlwZSwiX25vZGUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3QsY29tcHV0ZWQ6Il9nZXROb2RlU3RhdHMoZ3JhcGhOb2RlTmFtZSwgZ3JhcGhIaWVyYXJjaHkpIixvYnNlcnZlcjoiX3Jlc2V0U3RhdGUifSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGRuLnByb3RvdHlwZSwiX25vZGVTdGF0cyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcixvYnNlcnZlcjoiX25vZGVJbmNsdWRlU3RhdGVDaGFuZ2VkIn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxkbi5wcm90b3R5cGUsIm5vZGVJbmNsdWRlIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sZG4ucHJvdG90eXBlLCJfZXhwYW5kZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxkbi5wcm90b3R5cGUsIl9vcGVuZWRDb250cm9sUHJlZCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGRuLnByb3RvdHlwZSwiX29wZW5lZENvbnRyb2xTdWNjIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLGRuLnByb3RvdHlwZSwiX2F1eEJ1dHRvblRleHQiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sZG4ucHJvdG90eXBlLCJfZ3JvdXBCdXR0b25UZXh0Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKV0sZG4ucHJvdG90eXBlLCJfdGVtcGxhdGVJbmRleCIsdm9pZCAwKTtFKFtSdCgiX25vZGVTdGF0cyIpLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl9oYXNEaXNwbGF5YWJsZU5vZGVTdGF0cyIsbnVsbCk7RShbUnQoIl9ub2RlU3RhdHMiKSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl9ub2RlU3RhdHNGb3JtYXR0ZWRCeXRlcyIsbnVsbCk7RShbUnQoIl9ub2RlU3RhdHMiKSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl9ub2RlU3RhdHNGb3JtYXR0ZWRDb21wdXRlVGltZSIsbnVsbCk7RShbUnQoIl9ub2RlU3RhdHMiKSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl9ub2RlU3RhdHNGb3JtYXR0ZWRPdXRwdXRTaXplcyIsbnVsbCk7RShbUnQoIl9ub2RlIiksdygiZGVzaWduOnR5cGUiLEFycmF5KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl9hdHRyaWJ1dGVzIixudWxsKTtFKFtSdCgiX25vZGUiKSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl9kZXZpY2UiLG51bGwpO0UoW1J0KCJfbm9kZSIsImdyYXBoSGllcmFyY2h5IiksdygiZGVzaWduOnR5cGUiLE9iamVjdCksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sZG4ucHJvdG90eXBlLCJfc3VjY2Vzc29ycyIsbnVsbCk7RShbUnQoIl9ub2RlIiwiZ3JhcGhIaWVyYXJjaHkiKSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl9wcmVkZWNlc3NvcnMiLG51bGwpO0UoW1J0KCJfbm9kZSIsImdyYXBoSGllcmFyY2h5IiksdygiZGVzaWduOnR5cGUiLEFycmF5KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl9mdW5jdGlvblVzYWdlcyIsbnVsbCk7RShbUnQoIl9ub2RlIiksdygiZGVzaWduOnR5cGUiLEFycmF5KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl9zdWJub2RlcyIsbnVsbCk7RShbUnQoIl9wcmVkZWNlc3NvcnMiKSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxkbi5wcm90b3R5cGUsIl90b3RhbFByZWRlY2Vzc29ycyIsbnVsbCk7RShbUnQoIl9zdWNjZXNzb3JzIiksdygiZGVzaWduOnR5cGUiLE51bWJlciksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sZG4ucHJvdG90eXBlLCJfdG90YWxTdWNjZXNzb3JzIixudWxsKTtFKFtCdCgiZ3JhcGhIaWVyYXJjaHkiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLGRuLnByb3RvdHlwZSwiX2dyYXBoSGllcmFyY2h5Q2hhbmdlZCIsbnVsbCk7ZG49RShbeXQoInRmLW5vZGUtaW5mbyIpXSxkbik7dmFyIHNzPWNsYXNzIGV4dGVuZHMgR3QobXQpe3JlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoIm5vZGUtbGlzdC1pdGVtLWNsaWNrIix0aGlzLl9ub2RlTGlzdEl0ZW1DbGlja2VkLmJpbmQodGhpcykpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigibm9kZS1saXN0LWl0ZW0tbW91c2VvdmVyIix0aGlzLl9ub2RlTGlzdEl0ZW1Nb3VzZW92ZXIuYmluZCh0aGlzKSksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJub2RlLWxpc3QtaXRlbS1tb3VzZW91dCIsdGhpcy5fbm9kZUxpc3RJdGVtTW91c2VvdXQuYmluZCh0aGlzKSl9X25vZGVMaXN0SXRlbUNsaWNrZWQodCl7dGhpcy5zZWxlY3RlZE5vZGU9dC5kZXRhaWwubm9kZU5hbWV9X25vZGVMaXN0SXRlbU1vdXNlb3Zlcih0KXt0aGlzLmhpZ2hsaWdodGVkTm9kZT10LmRldGFpbC5ub2RlTmFtZX1fbm9kZUxpc3RJdGVtTW91c2VvdXQoKXt0aGlzLmhpZ2hsaWdodGVkTm9kZT1udWxsfV9oZWFsdGhQaWxsc0F2YWlsYWJsZSh0LHIpe3JldHVybiB0JiZyJiZPYmplY3Qua2V5cyhyKS5sZW5ndGg+MH1fZXF1YWxzKHQscil7cmV0dXJuIHQ9PT1yfX07c3MudGVtcGxhdGU9UWAKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGJhY2tncm91bmQ6IHZhcigtLXNlY29uZGFyeS1iYWNrZ3JvdW5kLWNvbG9yKTsKICAgICAgICBmb250LXNpemU6IDEycHg7CiAgICAgICAgbWFyZ2luOiAwOwogICAgICAgIHBhZGRpbmc6IDA7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgbWF4LWhlaWdodDogNjUwcHg7CiAgICAgICAgb3ZlcmZsb3cteDogaGlkZGVuOwogICAgICAgIG92ZXJmbG93LXk6IGF1dG87CiAgICAgIH0KCiAgICAgIGgyIHsKICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgIHRleHQtYWxpZ246IGNlbnRlcjsKICAgICAgICBtYXJnaW46IDA7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9Int7c2VsZWN0ZWROb2RlfX0iPgogICAgICA8cGFwZXItbWF0ZXJpYWwgZWxldmF0aW9uPSIxIiBjbGFzcz0iY2FyZCI+CiAgICAgICAgPHRmLW5vZGUtaW5mbwogICAgICAgICAgZ3JhcGgtaGllcmFyY2h5PSJbW2dyYXBoSGllcmFyY2h5XV0iCiAgICAgICAgICByZW5kZXItaGllcmFyY2h5PSJbW3JlbmRlckhpZXJhcmNoeV1dIgogICAgICAgICAgZmxhdC1ncmFwaD0iW1tncmFwaF1dIgogICAgICAgICAgZ3JhcGgtbm9kZS1uYW1lPSJbW3NlbGVjdGVkTm9kZV1dIgogICAgICAgICAgbm9kZS1pbmNsdWRlPSJbW3NlbGVjdGVkTm9kZUluY2x1ZGVdXSIKICAgICAgICAgIGhpZ2hsaWdodGVkLW5vZGU9Int7aGlnaGxpZ2h0ZWROb2RlfX0iCiAgICAgICAgICBjb2xvci1ieT0iW1tjb2xvckJ5XV0iCiAgICAgICAgPgogICAgICAgIDwvdGYtbm9kZS1pbmZvPgogICAgICA8L3BhcGVyLW1hdGVyaWFsPgogICAgPC90ZW1wbGF0ZT4KICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfZXF1YWxzKGNvbG9yQnksICdvcF9jb21wYXRpYmlsaXR5JyldXSI+CiAgICAgIDx0Zi1ncmFwaC1vcC1jb21wYXQtY2FyZAogICAgICAgIGdyYXBoLWhpZXJhcmNoeT0iW1tncmFwaEhpZXJhcmNoeV1dIgogICAgICAgIHJlbmRlci1oaWVyYXJjaHk9IltbcmVuZGVySGllcmFyY2h5XV0iCiAgICAgICAgY29sb3ItYnk9IltbY29sb3JCeV1dIgogICAgICAgIG5vZGUtdGl0bGU9IltbY29tcGF0Tm9kZVRpdGxlXV0iCiAgICAgID4KICAgICAgPC90Zi1ncmFwaC1vcC1jb21wYXQtY2FyZD4KICAgIDwvdGVtcGxhdGU+CiAgICA8dGVtcGxhdGUKICAgICAgaXM9ImRvbS1pZiIKICAgICAgaWY9IltbX2hlYWx0aFBpbGxzQXZhaWxhYmxlKGRlYnVnZ2VyRGF0YUVuYWJsZWQsIG5vZGVOYW1lc1RvSGVhbHRoUGlsbHMpXV0iCiAgICA+CiAgICAgIDx0Zi1ncmFwaC1kZWJ1Z2dlci1kYXRhLWNhcmQKICAgICAgICByZW5kZXItaGllcmFyY2h5PSJbW3JlbmRlckhpZXJhcmNoeV1dIgogICAgICAgIGRlYnVnZ2VyLW51bWVyaWMtYWxlcnRzPSJbW2RlYnVnZ2VyTnVtZXJpY0FsZXJ0c11dIgogICAgICAgIG5vZGUtbmFtZXMtdG8taGVhbHRoLXBpbGxzPSJbW25vZGVOYW1lc1RvSGVhbHRoUGlsbHNdXSIKICAgICAgICBzZWxlY3RlZC1ub2RlPSJ7e3NlbGVjdGVkTm9kZX19IgogICAgICAgIGhpZ2hsaWdodGVkLW5vZGU9Int7aGlnaGxpZ2h0ZWROb2RlfX0iCiAgICAgICAgYXJlLWhlYWx0aC1waWxscy1sb2FkaW5nPSJbW2FyZUhlYWx0aFBpbGxzTG9hZGluZ11dIgogICAgICAgIGFsbC1zdGVwcy1tb2RlLWVuYWJsZWQ9Int7YWxsU3RlcHNNb2RlRW5hYmxlZH19IgogICAgICAgIHNwZWNpZmljLWhlYWx0aC1waWxsLXN0ZXA9Int7c3BlY2lmaWNIZWFsdGhQaWxsU3RlcH19IgogICAgICAgIGhlYWx0aC1waWxsLXN0ZXAtaW5kZXg9Int7aGVhbHRoUGlsbFN0ZXBJbmRleH19IgogICAgICA+CiAgICAgIDwvdGYtZ3JhcGgtZGVidWdnZXItZGF0YS1jYXJkPgogICAgPC90ZW1wbGF0ZT4KICBgO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLHNzLnByb3RvdHlwZSwidGl0bGUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsb3MpXSxzcy5wcm90b3R5cGUsImdyYXBoSGllcmFyY2h5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLFh1KV0sc3MucHJvdG90eXBlLCJncmFwaCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixsbyldLHNzLnByb3RvdHlwZSwicmVuZGVySGllcmFyY2h5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHNzLnByb3RvdHlwZSwibm9kZU5hbWVzVG9IZWFsdGhQaWxscyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcixub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sc3MucHJvdG90eXBlLCJoZWFsdGhQaWxsU3RlcEluZGV4Iix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLHNzLnByb3RvdHlwZSwiY29sb3JCeSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxzcy5wcm90b3R5cGUsImNvbXBhdE5vZGVUaXRsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZyxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sc3MucHJvdG90eXBlLCJzZWxlY3RlZE5vZGUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmcsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLHNzLnByb3RvdHlwZSwiaGlnaGxpZ2h0ZWROb2RlIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxzcy5wcm90b3R5cGUsInNlbGVjdGVkTm9kZUluY2x1ZGUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxzcy5wcm90b3R5cGUsImRlYnVnZ2VyRGF0YUVuYWJsZWQiLHZvaWQgMCk7c3M9RShbeXQoInRmLWdyYXBoLWluZm8iKV0sc3MpO3ZhciBpdXI9e01BWF9OT0RFX0NPVU5UOjFlNCxNQVhfRURHRV9DT1VOVDoxZTR9LHRuPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmhpZXJhcmNoeVBhcmFtcz1yMyx0aGlzLmFsbFN0ZXBzTW9kZUVuYWJsZWQ9ITEsdGhpcy5zcGVjaWZpY0hlYWx0aFBpbGxTdGVwPTAsdGhpcy5jb21wYXROb2RlVGl0bGU9IlRQVSBDb21wYXRpYmlsaXR5In1maXQoKXt0aGlzLiQuZ3JhcGguZml0KCl9ZG93bmxvYWRBc0ltYWdlKHQpe3JldHVybiBSaSh0aGlzLG51bGwsZnVuY3Rpb24qKCl7bGV0IHI9eWllbGQgdGhpcy4kLmdyYXBoLmdldEltYWdlQmxvYigpLG49ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiYSIpO24uaHJlZj1VUkwuY3JlYXRlT2JqZWN0VVJMKHIpLG4uZG93bmxvYWQ9dCxuLmNsaWNrKCksVVJMLnJldm9rZU9iamVjdFVSTChuLmhyZWYpfSl9X2lzTm90Q29tcGxldGUodCl7cmV0dXJuIHQudmFsdWU8MTAwfV9nZXRDb250YWluZXJDbGFzcyh0KXt2YXIgcj0iY29udGFpbmVyIjtyZXR1cm4gdC5lcnJvciYmKHIrPSIgZXJyb3IiKSx0aGlzLl9pc05vdENvbXBsZXRlKHQpJiYocis9IiBsb2FkaW5nIikscn1fb25Ob2RlSW5jbHVzaW9uVG9nZ2xlZCh0KXt0aGlzLiQuZ3JhcGgubm9kZVRvZ2dsZUV4dHJhY3QodC5kZXRhaWwubmFtZSl9X29uTm9kZVNlcmllc0dyb3VwVG9nZ2xlZCh0KXt0aGlzLiQuZ3JhcGgubm9kZVRvZ2dsZVNlcmllc0dyb3VwKHQuZGV0YWlsLm5hbWUpfV91cGRhdGVOb2RlSW5jbHVkZSgpe2xldCB0PXRoaXMucmVuZGVySGllcmFyY2h5P3RoaXMucmVuZGVySGllcmFyY2h5LmdldE5vZGVCeU5hbWUodGhpcy5zZWxlY3RlZE5vZGUpOm51bGw7dGhpcy5fc2VsZWN0ZWROb2RlSW5jbHVkZT10P3QuaW5jbHVkZTp1ci5VTlNQRUNJRklFRH1fc2xpbUdyYXBoQ2hhbmdlZCgpe2lmKCF0aGlzLmdyYXBoKXJldHVybjtsZXR7TUFYX05PREVfQ09VTlQ6dCxNQVhfRURHRV9DT1VOVDpyfT1pdXI7T2JqZWN0LmtleXModGhpcy5ncmFwaC5ub2RlcykubGVuZ3RoPnQmJnRoaXMuZ3JhcGguZWRnZXMubGVuZ3RoPnImJnRoaXMuY29sb3JCeT09PUduLlNUUlVDVFVSRSYmKHRoaXMuY29sb3JCeT1Hbi5OT05FKX1fZW5zdXJlVGVtcGxhdGVzKCl7IXRoaXMuZ3JhcGhIaWVyYXJjaHl8fHRoaXMuY29sb3JCeSE9PUduLlNUUlVDVFVSRXx8dGhpcy5ncmFwaEhpZXJhcmNoeS5nZXRUZW1wbGF0ZUluZGV4KCl8fHRoaXMuZ3JhcGhIaWVyYXJjaHkudXBkYXRlVGVtcGxhdGVzKCl9fTt0bi50ZW1wbGF0ZT1RYAogICAgPHN0eWxlPgogICAgICA6Omhvc3QgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICB9CgogICAgICAvZGVlcC8gLmNsb3NlIHsKICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgY3Vyc29yOiBwb2ludGVyOwogICAgICAgIGxlZnQ6IDE1cHg7CiAgICAgICAgYm90dG9tOiAxNXB4OwogICAgICB9CgogICAgICAuY29udGFpbmVyIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgb3BhY2l0eTogMTsKICAgICAgfQoKICAgICAgLmNvbnRhaW5lci5sb2FkaW5nIHsKICAgICAgICBjdXJzb3I6IHByb2dyZXNzOwogICAgICAgIG9wYWNpdHk6IDAuMTsKICAgICAgfQoKICAgICAgLmNvbnRhaW5lci5sb2FkaW5nLmVycm9yIHsKICAgICAgICBjdXJzb3I6IGF1dG87CiAgICAgIH0KCiAgICAgICNpbmZvIHsKICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgcmlnaHQ6IDVweDsKICAgICAgICB0b3A6IDVweDsKICAgICAgICBwYWRkaW5nOiAwcHg7CiAgICAgICAgbWF4LXdpZHRoOiAzODBweDsKICAgICAgICBtaW4td2lkdGg6IDMyMHB4OwogICAgICAgIGJhY2tncm91bmQtY29sb3I6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC45KTsKICAgICAgICBAYXBwbHkgLS1zaGFkb3ctZWxldmF0aW9uLTJkcDsKICAgICAgfQoKICAgICAgI21haW4gewogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgfQoKICAgICAgI3Byb2dyZXNzLWJhciB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIHRvcDogNDBweDsKICAgICAgICBsZWZ0OiAwOwogICAgICAgIGZvbnQtc2l6ZTogMTNweDsKICAgICAgfQoKICAgICAgI3Byb2dyZXNzLW1zZyB7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogNXB4OwogICAgICAgIHdoaXRlLXNwYWNlOiBwcmUtd3JhcDsKICAgICAgICB3aWR0aDogNDAwcHg7CiAgICAgIH0KCiAgICAgIHBhcGVyLXByb2dyZXNzIHsKICAgICAgICB3aWR0aDogNDAwcHg7CiAgICAgICAgLS1wYXBlci1wcm9ncmVzcy1oZWlnaHQ6IDZweDsKICAgICAgICAtLXBhcGVyLXByb2dyZXNzLWFjdGl2ZS1jb2xvcjogI2YzOTEzZTsKICAgICAgfQoKICAgICAgLmNvbnRleHQtbWVudSB7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2UyZTJlMjsKICAgICAgICBib3JkZXItcmFkaXVzOiAycHg7CiAgICAgICAgZm9udC1zaXplOiAxNHB4OwogICAgICAgIG1pbi13aWR0aDogMTUwcHg7CiAgICAgICAgYm9yZGVyOiAxcHggc29saWQgI2Q0ZDRkNDsKICAgICAgfQoKICAgICAgL2RlZXAvIC5jb250ZXh0LW1lbnUgdWwgewogICAgICAgIGxpc3Qtc3R5bGUtdHlwZTogbm9uZTsKICAgICAgICBtYXJnaW46IDA7CiAgICAgICAgcGFkZGluZzogMDsKICAgICAgICBjdXJzb3I6IGRlZmF1bHQ7CiAgICAgIH0KCiAgICAgIC9kZWVwLyAuY29udGV4dC1tZW51IHVsIGxpIHsKICAgICAgICBwYWRkaW5nOiA0cHggMTZweDsKICAgICAgfQoKICAgICAgL2RlZXAvIC5jb250ZXh0LW1lbnUgdWwgbGk6aG92ZXIgewogICAgICAgIGJhY2tncm91bmQtY29sb3I6ICNmMzkxM2U7CiAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICB9CiAgICA8L3N0eWxlPgogICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19pc05vdENvbXBsZXRlKHByb2dyZXNzKV1dIj4KICAgICAgPGRpdiBpZD0icHJvZ3Jlc3MtYmFyIj4KICAgICAgICA8ZGl2IGlkPSJwcm9ncmVzcy1tc2ciPltbcHJvZ3Jlc3MubXNnXV08L2Rpdj4KICAgICAgICA8cGFwZXItcHJvZ3Jlc3MgdmFsdWU9IltbcHJvZ3Jlc3MudmFsdWVdXSI+PC9wYXBlci1wcm9ncmVzcz4KICAgICAgPC9kaXY+CiAgICA8L3RlbXBsYXRlPgogICAgPGRpdiBjbGFzcyQ9IltbX2dldENvbnRhaW5lckNsYXNzKHByb2dyZXNzKV1dIj4KICAgICAgPGRpdiBpZD0ibWFpbiI+CiAgICAgICAgPHRmLWdyYXBoCiAgICAgICAgICBpZD0iZ3JhcGgiCiAgICAgICAgICBncmFwaC1oaWVyYXJjaHk9Int7Z3JhcGhIaWVyYXJjaHl9fSIKICAgICAgICAgIGJhc2ljLWdyYXBoPSJbW2dyYXBoXV0iCiAgICAgICAgICBoaWVyYXJjaHktcGFyYW1zPSJbW2hpZXJhcmNoeVBhcmFtc11dIgogICAgICAgICAgcmVuZGVyLWhpZXJhcmNoeT0ie3tyZW5kZXJIaWVyYXJjaHl9fSIKICAgICAgICAgIGRldmljZXMtZm9yLXN0YXRzPSJbW2RldmljZXNGb3JTdGF0c11dIgogICAgICAgICAgc3RhdHM9Iltbc3RhdHNdXSIKICAgICAgICAgIHNlbGVjdGVkLW5vZGU9Int7c2VsZWN0ZWROb2RlfX0iCiAgICAgICAgICBoaWdobGlnaHRlZC1ub2RlPSJ7e19oaWdobGlnaHRlZE5vZGV9fSIKICAgICAgICAgIGNvbG9yLWJ5PSJbW2NvbG9yQnldXSIKICAgICAgICAgIGNvbG9yLWJ5LXBhcmFtcz0ie3tjb2xvckJ5UGFyYW1zfX0iCiAgICAgICAgICBwcm9ncmVzcz0ie3twcm9ncmVzc319IgogICAgICAgICAgZWRnZS1sYWJlbC1mdW5jdGlvbj0iW1tlZGdlTGFiZWxGdW5jdGlvbl1dIgogICAgICAgICAgZWRnZS13aWR0aC1mdW5jdGlvbj0iW1tlZGdlV2lkdGhGdW5jdGlvbl1dIgogICAgICAgICAgbm9kZS1uYW1lcy10by1oZWFsdGgtcGlsbHM9Iltbbm9kZU5hbWVzVG9IZWFsdGhQaWxsc11dIgogICAgICAgICAgaGVhbHRoLXBpbGwtc3RlcC1pbmRleD0iW1toZWFsdGhQaWxsU3RlcEluZGV4XV0iCiAgICAgICAgICBoYW5kbGUtbm9kZS1zZWxlY3RlZD0iW1toYW5kbGVOb2RlU2VsZWN0ZWRdXSIKICAgICAgICAgIGhhbmRsZS1lZGdlLXNlbGVjdGVkPSJbW2hhbmRsZUVkZ2VTZWxlY3RlZF1dIgogICAgICAgICAgdHJhY2UtaW5wdXRzPSJbW3RyYWNlSW5wdXRzXV0iCiAgICAgICAgICBhdXRvLWV4dHJhY3Qtbm9kZXM9IltbYXV0b0V4dHJhY3ROb2Rlc11dIgogICAgICAgID48L3RmLWdyYXBoPgogICAgICA8L2Rpdj4KICAgICAgPGRpdiBpZD0iaW5mbyI+CiAgICAgICAgPHRmLWdyYXBoLWluZm8KICAgICAgICAgIGlkPSJncmFwaC1pbmZvIgogICAgICAgICAgdGl0bGU9InNlbGVjdGVkIgogICAgICAgICAgZ3JhcGgtaGllcmFyY2h5PSJbW2dyYXBoSGllcmFyY2h5XV0iCiAgICAgICAgICByZW5kZXItaGllcmFyY2h5PSJbW3JlbmRlckhpZXJhcmNoeV1dIgogICAgICAgICAgZ3JhcGg9IltbZ3JhcGhdXSIKICAgICAgICAgIHNlbGVjdGVkLW5vZGU9Int7c2VsZWN0ZWROb2RlfX0iCiAgICAgICAgICBzZWxlY3RlZC1ub2RlLWluY2x1ZGU9Int7X3NlbGVjdGVkTm9kZUluY2x1ZGV9fSIKICAgICAgICAgIGhpZ2hsaWdodGVkLW5vZGU9Int7X2hpZ2hsaWdodGVkTm9kZX19IgogICAgICAgICAgY29sb3ItYnk9IltbY29sb3JCeV1dIgogICAgICAgICAgY29sb3ItYnktcGFyYW1zPSJbW2NvbG9yQnlQYXJhbXNdXSIKICAgICAgICAgIGRlYnVnZ2VyLWRhdGEtZW5hYmxlZD0iW1tkZWJ1Z2dlckRhdGFFbmFibGVkXV0iCiAgICAgICAgICBhcmUtaGVhbHRoLXBpbGxzLWxvYWRpbmc9IltbYXJlSGVhbHRoUGlsbHNMb2FkaW5nXV0iCiAgICAgICAgICBkZWJ1Z2dlci1udW1lcmljLWFsZXJ0cz0iW1tkZWJ1Z2dlck51bWVyaWNBbGVydHNdXSIKICAgICAgICAgIG5vZGUtbmFtZXMtdG8taGVhbHRoLXBpbGxzPSJbW25vZGVOYW1lc1RvSGVhbHRoUGlsbHNdXSIKICAgICAgICAgIGFsbC1zdGVwcy1tb2RlLWVuYWJsZWQ9Int7YWxsU3RlcHNNb2RlRW5hYmxlZH19IgogICAgICAgICAgc3BlY2lmaWMtaGVhbHRoLXBpbGwtc3RlcD0ie3tzcGVjaWZpY0hlYWx0aFBpbGxTdGVwfX0iCiAgICAgICAgICBoZWFsdGgtcGlsbC1zdGVwLWluZGV4PSJ7e2hlYWx0aFBpbGxTdGVwSW5kZXh9fSIKICAgICAgICAgIGNvbXBhdC1ub2RlLXRpdGxlPSJbW2NvbXBhdE5vZGVUaXRsZV1dIgogICAgICAgICAgb24tbm9kZS10b2dnbGUtaW5jbHVzaW9uPSJfb25Ob2RlSW5jbHVzaW9uVG9nZ2xlZCIKICAgICAgICAgIG9uLW5vZGUtdG9nZ2xlLXNlcmllc2dyb3VwPSJfb25Ob2RlU2VyaWVzR3JvdXBUb2dnbGVkIgogICAgICAgID48L3RmLWdyYXBoLWluZm8+CiAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgYDtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixvcyldLHRuLnByb3RvdHlwZSwiZ3JhcGhIaWVyYXJjaHkiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsWHUpXSx0bi5wcm90b3R5cGUsImdyYXBoIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHRuLnByb3RvdHlwZSwiaGllcmFyY2h5UGFyYW1zIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHRuLnByb3RvdHlwZSwic3RhdHMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sdG4ucHJvdG90eXBlLCJwcm9ncmVzcyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLHRuLnByb3RvdHlwZSwidHJhY2VJbnB1dHMiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSx0bi5wcm90b3R5cGUsImF1dG9FeHRyYWN0Tm9kZXMiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmcsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLHRuLnByb3RvdHlwZSwiY29sb3JCeSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sdG4ucHJvdG90eXBlLCJjb2xvckJ5UGFyYW1zIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0LG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixsbyldLHRuLnByb3RvdHlwZSwicmVuZGVySGllcmFyY2h5Iix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sdG4ucHJvdG90eXBlLCJkZWJ1Z2dlckRhdGFFbmFibGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sdG4ucHJvdG90eXBlLCJhcmVIZWFsdGhQaWxsc0xvYWRpbmciLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheSxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSx0bi5wcm90b3R5cGUsImRlYnVnZ2VyTnVtZXJpY0FsZXJ0cyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSx0bi5wcm90b3R5cGUsIm5vZGVOYW1lc1RvSGVhbHRoUGlsbHMiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sdG4ucHJvdG90eXBlLCJhbGxTdGVwc01vZGVFbmFibGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSx0bi5wcm90b3R5cGUsInNwZWNpZmljSGVhbHRoUGlsbFN0ZXAiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sdG4ucHJvdG90eXBlLCJoZWFsdGhQaWxsU3RlcEluZGV4Iix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSx0bi5wcm90b3R5cGUsInNlbGVjdGVkTm9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSx0bi5wcm90b3R5cGUsImNvbXBhdE5vZGVUaXRsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSx0bi5wcm90b3R5cGUsImVkZ2VXaWR0aEZ1bmN0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLHRuLnByb3RvdHlwZSwiX3NlbGVjdGVkTm9kZUluY2x1ZGUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sdG4ucHJvdG90eXBlLCJfaGlnaGxpZ2h0ZWROb2RlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHRuLnByb3RvdHlwZSwiaGFuZGxlTm9kZVNlbGVjdGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHRuLnByb3RvdHlwZSwiZWRnZUxhYmVsRnVuY3Rpb24iLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sdG4ucHJvdG90eXBlLCJoYW5kbGVFZGdlU2VsZWN0ZWQiLHZvaWQgMCk7RShbQnQoInNlbGVjdGVkTm9kZSIsInJlbmRlckhpZXJhcmNoeSIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sdG4ucHJvdG90eXBlLCJfdXBkYXRlTm9kZUluY2x1ZGUiLG51bGwpO0UoW0J0KCJncmFwaCIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sdG4ucHJvdG90eXBlLCJfc2xpbUdyYXBoQ2hhbmdlZCIsbnVsbCk7RShbQnQoImNvbG9yQnkiLCJncmFwaEhpZXJhcmNoeSIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sdG4ucHJvdG90eXBlLCJfZW5zdXJlVGVtcGxhdGVzIixudWxsKTt0bj1FKFt5dCgidGYtZ3JhcGgtYm9hcmQiKV0sdG4pO3ZhciBjMz1FZShPZSgpLDEpO3ZhciBKdT1jbGFzc3tpc05vdFRwdU9wKHQpe3JldHVybiB0LnRvTG93ZXJDYXNlKCkuc2VhcmNoKCJjcHU6IikhPS0xfHx0LnRvTG93ZXJDYXNlKCkuc2VhcmNoKCJncHU6IikhPS0xPyEwOnQudG9Mb3dlckNhc2UoKS5zZWFyY2goInRwdSIpPT0tMX1vcFZhbGlkKHQpe3JldHVybiB0Lm5hbWUuc2VhcmNoKFNhKT09MHx8IXQub3B8fHQuZGV2aWNlJiZ0aGlzLmlzTm90VHB1T3AodC5kZXZpY2UpfHx0LmRldmljZSYmdC5kZXZpY2Uuc2VhcmNoKCJUUFVfU1lTVEVNIikhPS0xPyEwOmMzLmluY2x1ZGVzKEp1LldISVRFTElTVCx0Lm9wKX19O0p1LldISVRFTElTVD1bIkFicyIsIkFjb3MiLCJBY29zaCIsIkFkZCIsIkFkZE4iLCJBZGRWMiIsIkFkanVzdENvbnRyYXN0djIiLCJBZGp1c3RIdWUiLCJBZGp1c3RTYXR1cmF0aW9uIiwiQWxsIiwiQWxsVG9BbGwiLCJBbmdsZSIsIkFueSIsIkFwcHJveGltYXRlRXF1YWwiLCJBcmdNYXgiLCJBcmdNaW4iLCJBc2luIiwiQXNpbmgiLCJBc3NlcnQiLCJBc3NpZ25BZGRWYXJpYWJsZU9wIiwiQXNzaWduU3ViVmFyaWFibGVPcCIsIkFzc2lnblZhcmlhYmxlT3AiLCJBdGFuIiwiQXRhbjIiLCJBdGFuaCIsIkF2Z1Bvb2wiLCJBdmdQb29sM0QiLCJBdmdQb29sM0RHcmFkIiwiQXZnUG9vbEdyYWQiLCJCYXRjaE1hdE11bCIsIkJhdGNoTWF0TXVsVjIiLCJCYXRjaFRvU3BhY2UiLCJCYXRjaFRvU3BhY2VORCIsIkJlc3NlbEkwZSIsIkJlc3NlbEkxZSIsIkJldGFpbmMiLCJCaWFzQWRkIiwiQmlhc0FkZEdyYWQiLCJCaWFzQWRkVjEiLCJCaXRjYXN0IiwiQml0d2lzZUFuZCIsIkJpdHdpc2VPciIsIkJpdHdpc2VYb3IiLCJCcm9hZGNhc3RBcmdzIiwiQnJvYWRjYXN0R3JhZGllbnRBcmdzIiwiQnJvYWRjYXN0VG8iLCJCdWNrZXRpemUiLCJDYXNlIiwiQ2FzdCIsIkNlaWwiLCJDaGVja051bWVyaWNzIiwiQ2hvbGVza3kiLCJDbGlwQnlWYWx1ZSIsIkNvbGxlY3RpdmVQZXJtdXRlIiwiQ29sbGVjdGl2ZVJlZHVjZVYyIiwiQ29tcGxleCIsIkNvbXBsZXhBYnMiLCJDb25jYXQiLCJDb25jYXRPZmZzZXQiLCJDb25jYXRWMiIsIkNvbmoiLCJDb25qdWdhdGVUcmFuc3Bvc2UiLCJDb25zdCIsIkNvbnRyb2xUcmlnZ2VyIiwiQ29udjJEIiwiQ29udjJEQmFja3Byb3BGaWx0ZXIiLCJDb252MkRCYWNrcHJvcElucHV0IiwiQ29udjNEIiwiQ29udjNEQmFja3Byb3BGaWx0ZXJWMiIsIkNvbnYzREJhY2twcm9wSW5wdXRWMiIsIkNvcyIsIkNvc2giLCJDcm9zcyIsIkNyb3NzUmVwbGljYVN1bSIsIkN1bXByb2QiLCJDdW1zdW0iLCJEYXRhRm9ybWF0RGltTWFwIiwiRGF0YUZvcm1hdFZlY1Blcm11dGUiLCJEZXB0aFRvU3BhY2UiLCJEZXB0aHdpc2VDb252MmROYXRpdmUiLCJEZXB0aHdpc2VDb252MmROYXRpdmVCYWNrcHJvcEZpbHRlciIsIkRlcHRod2lzZUNvbnYyZE5hdGl2ZUJhY2twcm9wSW5wdXQiLCJEZXF1YW50aXplIiwiRGV2aWNlSW5kZXgiLCJEaWFnIiwiRGlhZ1BhcnQiLCJEaWdhbW1hIiwiRGl2IiwiRGl2Tm9OYW4iLCJEeW5hbWljU3RpdGNoIiwiRWluc3VtIiwiRWx1IiwiRWx1R3JhZCIsIkVtcHR5IiwiRW1wdHlUZW5zb3JMaXN0IiwiRW5zdXJlU2hhcGUiLCJFcXVhbCIsIkVyZiIsIkVyZmMiLCJFcmZpbnYiLCJFeHAiLCJFeHBhbmREaW1zIiwiRXhwbTEiLCJFeHRyYWN0SW1hZ2VQYXRjaGVzIiwiRkZUIiwiRkZUMkQiLCJGRlQzRCIsIkZha2VQYXJhbSIsIkZha2VRdWFudFdpdGhNaW5NYXhBcmdzIiwiRmFrZVF1YW50V2l0aE1pbk1heEFyZ3NHcmFkaWVudCIsIkZha2VRdWFudFdpdGhNaW5NYXhWYXJzIiwiRmFrZVF1YW50V2l0aE1pbk1heFZhcnNHcmFkaWVudCIsIkZpbGwiLCJGbG9vciIsIkZsb29yRGl2IiwiRmxvb3JNb2QiLCJGdXNlZEJhdGNoTm9ybSIsIkZ1c2VkQmF0Y2hOb3JtR3JhZCIsIkZ1c2VkQmF0Y2hOb3JtR3JhZFYyIiwiRnVzZWRCYXRjaE5vcm1HcmFkVjMiLCJGdXNlZEJhdGNoTm9ybVYyIiwiRnVzZWRCYXRjaE5vcm1WMyIsIkdhdGhlciIsIkdhdGhlck5kIiwiR2F0aGVyVjIiLCJHZXRJdGVtIiwiR3JlYXRlciIsIkdyZWF0ZXJFcXVhbCIsIkhTVlRvUkdCIiwiSUZGVCIsIklGRlQyRCIsIklGRlQzRCIsIklSRkZUIiwiSVJGRlQyRCIsIklSRkZUM0QiLCJJZGVudGl0eSIsIklkZW50aXR5TiIsIklmIiwiSWdhbW1hIiwiSWdhbW1hR3JhZEEiLCJJZ2FtbWFjIiwiSW1hZyIsIkluVG9wS1YyIiwiSW5mZWVkRGVxdWV1ZSIsIkluZmVlZERlcXVldWVUdXBsZSIsIklucGxhY2VBZGQiLCJJbnBsYWNlVXBkYXRlIiwiSW52IiwiSW52ZXJ0IiwiSW52ZXJ0UGVybXV0YXRpb24iLCJJc0Zpbml0ZSIsIklzSW5mIiwiSXNOYW4iLCJLdGhPcmRlclN0YXRpc3RpYyIsIkwyTG9zcyIsIkxSTiIsIkxSTkdyYWQiLCJMZWFreVJlbHUiLCJMZWFreVJlbHVHcmFkIiwiTGVmdFNoaWZ0IiwiTGVzcyIsIkxlc3NFcXVhbCIsIkxnYW1tYSIsIkxpblNwYWNlIiwiTGlzdERpZmYiLCJMb2ciLCJMb2cxcCIsIkxvZ1NvZnRtYXgiLCJMb2dpY2FsQW5kIiwiTG9naWNhbE5vdCIsIkxvZ2ljYWxPciIsIkxvd2VyQm91bmQiLCJNYWtlVW5pcXVlIiwiTWF0TXVsIiwiTWF0cml4QmFuZFBhcnQiLCJNYXRyaXhEaWFnIiwiTWF0cml4RGlhZ1BhcnQiLCJNYXRyaXhEaWFnUGFydFYyIiwiTWF0cml4RGlhZ1BhcnRWMyIsIk1hdHJpeERpYWdWMiIsIk1hdHJpeERpYWdWMyIsIk1hdHJpeEludmVyc2UiLCJNYXRyaXhTZXREaWFnIiwiTWF0cml4U2V0RGlhZ1YyIiwiTWF0cml4U2V0RGlhZ1YzIiwiTWF0cml4U29sdmUiLCJNYXRyaXhUcmlhbmd1bGFyU29sdmUiLCJNYXgiLCJNYXhQb29sIiwiTWF4UG9vbDNEIiwiTWF4UG9vbDNER3JhZCIsIk1heFBvb2wzREdyYWRHcmFkIiwiTWF4UG9vbEdyYWQiLCJNYXhQb29sR3JhZEdyYWQiLCJNYXhQb29sR3JhZEdyYWRWMiIsIk1heFBvb2xHcmFkVjIiLCJNYXhQb29sVjIiLCJNYXhpbXVtIiwiTWVhbiIsIk1pbiIsIk1pbmltdW0iLCJNaXJyb3JQYWQiLCJNaXJyb3JQYWRHcmFkIiwiTW9kIiwiTXVsIiwiTXVsTm9OYW4iLCJNdWx0aW5vbWlhbCIsIk5kdHJpIiwiTmVnIiwiTmV4dEFmdGVyIiwiTm9PcCIsIk5vbk1heFN1cHByZXNzaW9uVjQiLCJOb3RFcXVhbCIsIk9uZUhvdCIsIk9uZXNMaWtlIiwiT3V0ZmVlZEVucXVldWUiLCJPdXRmZWVkRW5xdWV1ZVR1cGxlIiwiUGFjayIsIlBhZCIsIlBhZFYyIiwiUGFyYWxsZWxEeW5hbWljU3RpdGNoIiwiUGFyYW1ldGVyaXplZFRydW5jYXRlZE5vcm1hbCIsIlBhcnRpdGlvbmVkQ2FsbCIsIlBsYWNlaG9sZGVyV2l0aERlZmF1bHQiLCJQb2x5Z2FtbWEiLCJQb3B1bGF0aW9uQ291bnQiLCJQb3ciLCJQcmV2ZW50R3JhZGllbnQiLCJQcm9kIiwiUXIiLCJRdWFudGl6ZUFuZERlcXVhbnRpemVWMiIsIlF1YW50aXplQW5kRGVxdWFudGl6ZVYzIiwiUkZGVCIsIlJGRlQyRCIsIlJGRlQzRCIsIlJHQlRvSFNWIiwiUmFuZG9tR2FtbWFHcmFkIiwiUmFuZG9tU2h1ZmZsZSIsIlJhbmRvbVN0YW5kYXJkTm9ybWFsIiwiUmFuZG9tVW5pZm9ybSIsIlJhbmRvbVVuaWZvcm1JbnQiLCJSYW5nZSIsIlJhbmsiLCJSZWFkVmFyaWFibGVPcCIsIlJlYWwiLCJSZWFsRGl2IiwiUmVjaXByb2NhbCIsIlJlY2lwcm9jYWxHcmFkIiwiUmVsdSIsIlJlbHU2IiwiUmVsdTZHcmFkIiwiUmVsdUdyYWQiLCJSZXNoYXBlIiwiUmVzaXplQmlsaW5lYXIiLCJSZXNpemVCaWxpbmVhckdyYWQiLCJSZXNpemVOZWFyZXN0TmVpZ2hib3IiLCJSZXNpemVOZWFyZXN0TmVpZ2hib3JHcmFkIiwiUmVzb3VyY2VBcHBseUFkYU1heCIsIlJlc291cmNlQXBwbHlBZGFkZWx0YSIsIlJlc291cmNlQXBwbHlBZGFncmFkIiwiUmVzb3VyY2VBcHBseUFkYWdyYWREQSIsIlJlc291cmNlQXBwbHlBZGFncmFkVjIiLCJSZXNvdXJjZUFwcGx5QWRhbSIsIlJlc291cmNlQXBwbHlBZGRTaWduIiwiUmVzb3VyY2VBcHBseUNlbnRlcmVkUk1TUHJvcCIsIlJlc291cmNlQXBwbHlGdHJsIiwiUmVzb3VyY2VBcHBseUZ0cmxWMiIsIlJlc291cmNlQXBwbHlHcmFkaWVudERlc2NlbnQiLCJSZXNvdXJjZUFwcGx5S2VyYXNNb21lbnR1bSIsIlJlc291cmNlQXBwbHlNb21lbnR1bSIsIlJlc291cmNlQXBwbHlQb3dlclNpZ24iLCJSZXNvdXJjZUFwcGx5UHJveGltYWxBZGFncmFkIiwiUmVzb3VyY2VBcHBseVByb3hpbWFsR3JhZGllbnREZXNjZW50IiwiUmVzb3VyY2VBcHBseVJNU1Byb3AiLCJSZXNvdXJjZUdhdGhlciIsIlJlc291cmNlU2NhdHRlckFkZCIsIlJlc291cmNlU2NhdHRlckRpdiIsIlJlc291cmNlU2NhdHRlck1heCIsIlJlc291cmNlU2NhdHRlck1pbiIsIlJlc291cmNlU2NhdHRlck11bCIsIlJlc291cmNlU2NhdHRlck5kQWRkIiwiUmVzb3VyY2VTY2F0dGVyTmRTdWIiLCJSZXNvdXJjZVNjYXR0ZXJOZFVwZGF0ZSIsIlJlc291cmNlU2NhdHRlclN1YiIsIlJlc291cmNlU2NhdHRlclVwZGF0ZSIsIlJlc291cmNlU3RyaWRlZFNsaWNlQXNzaWduIiwiUmV2ZXJzZSIsIlJldmVyc2VTZXF1ZW5jZSIsIlJldmVyc2VWMiIsIlJpZ2h0U2hpZnQiLCJSaW50IiwiUm5nUmVhZEFuZFNraXAiLCJSbmdTa2lwIiwiUm9sbCIsIlJvdW5kIiwiUnNxcnQiLCJSc3FydEdyYWQiLCJTY2F0dGVyTmQiLCJTZWxlY3QiLCJTZWxlY3RWMiIsIlNlbGZBZGpvaW50RWlnVjIiLCJTZWx1IiwiU2VsdUdyYWQiLCJTaGFwZSIsIlNoYXBlTiIsIlNpZ21vaWQiLCJTaWdtb2lkR3JhZCIsIlNpZ24iLCJTaW4iLCJTaW5oIiwiU2l6ZSIsIlNsaWNlIiwiU25hcHNob3QiLCJTb2Z0bWF4IiwiU29mdG1heENyb3NzRW50cm9weVdpdGhMb2dpdHMiLCJTb2Z0cGx1cyIsIlNvZnRwbHVzR3JhZCIsIlNvZnRzaWduIiwiU29mdHNpZ25HcmFkIiwiU3BhY2VUb0JhdGNoIiwiU3BhY2VUb0JhdGNoTkQiLCJTcGFjZVRvRGVwdGgiLCJTcGFyc2VNYXRNdWwiLCJTcGFyc2VTb2Z0bWF4Q3Jvc3NFbnRyb3B5V2l0aExvZ2l0cyIsIlNwYXJzZVRvRGVuc2UiLCJTcGxpdCIsIlNwbGl0ViIsIlNxcnQiLCJTcXJ0R3JhZCIsIlNxdWFyZSIsIlNxdWFyZWREaWZmZXJlbmNlIiwiU3F1ZWV6ZSIsIlN0YWNrQ2xvc2VWMiIsIlN0YWNrUG9wVjIiLCJTdGFja1B1c2hWMiIsIlN0YWNrVjIiLCJTdGF0ZWZ1bFBhcnRpdGlvbmVkQ2FsbCIsIlN0YXRlZnVsU3RhbmRhcmROb3JtYWxWMiIsIlN0YXRlZnVsVHJ1bmNhdGVkTm9ybWFsIiwiU3RhdGVmdWxVbmlmb3JtIiwiU3RhdGVmdWxVbmlmb3JtRnVsbEludCIsIlN0YXRlZnVsVW5pZm9ybUludCIsIlN0YXRlbGVzc0Nhc2UiLCJTdGF0ZWxlc3NJZiIsIlN0YXRlbGVzc011bHRpbm9taWFsIiwiU3RhdGVsZXNzUmFuZG9tR2V0QWxnIiwiU3RhdGVsZXNzUmFuZG9tR2V0S2V5Q291bnRlciIsIlN0YXRlbGVzc1JhbmRvbUdldEtleUNvdW50ZXJBbGciLCJTdGF0ZWxlc3NSYW5kb21Ob3JtYWwiLCJTdGF0ZWxlc3NSYW5kb21Ob3JtYWxWMiIsIlN0YXRlbGVzc1JhbmRvbVVuaWZvcm0iLCJTdGF0ZWxlc3NSYW5kb21Vbmlmb3JtRnVsbEludCIsIlN0YXRlbGVzc1JhbmRvbVVuaWZvcm1GdWxsSW50VjIiLCJTdGF0ZWxlc3NSYW5kb21Vbmlmb3JtSW50IiwiU3RhdGVsZXNzUmFuZG9tVW5pZm9ybUludFYyIiwiU3RhdGVsZXNzUmFuZG9tVW5pZm9ybVYyIiwiU3RhdGVsZXNzVHJ1bmNhdGVkTm9ybWFsIiwiU3RhdGVsZXNzVHJ1bmNhdGVkTm9ybWFsVjIiLCJTdGF0ZWxlc3NXaGlsZSIsIlN0b3BHcmFkaWVudCIsIlN0cmlkZWRTbGljZSIsIlN0cmlkZWRTbGljZUdyYWQiLCJTdWIiLCJTdW0iLCJTdmQiLCJTeW1ib2xpY0dyYWRpZW50IiwiVFBVRW1iZWRkaW5nQWN0aXZhdGlvbnMiLCJUYW4iLCJUYW5oIiwiVGFuaEdyYWQiLCJUZW5zb3JBcnJheUNsb3NlVjMiLCJUZW5zb3JBcnJheUNvbmNhdFYzIiwiVGVuc29yQXJyYXlHYXRoZXJWMyIsIlRlbnNvckFycmF5R3JhZFYzIiwiVGVuc29yQXJyYXlSZWFkVjMiLCJUZW5zb3JBcnJheVNjYXR0ZXJWMyIsIlRlbnNvckFycmF5U2l6ZVYzIiwiVGVuc29yQXJyYXlTcGxpdFYzIiwiVGVuc29yQXJyYXlWMyIsIlRlbnNvckFycmF5V3JpdGVWMyIsIlRlbnNvckxpc3RDb25jYXRWMiIsIlRlbnNvckxpc3RFbGVtZW50U2hhcGUiLCJUZW5zb3JMaXN0RnJvbVRlbnNvciIsIlRlbnNvckxpc3RHYXRoZXIiLCJUZW5zb3JMaXN0R2V0SXRlbSIsIlRlbnNvckxpc3RMZW5ndGgiLCJUZW5zb3JMaXN0UG9wQmFjayIsIlRlbnNvckxpc3RQdXNoQmFjayIsIlRlbnNvckxpc3RSZXNlcnZlIiwiVGVuc29yTGlzdFNldEl0ZW0iLCJUZW5zb3JMaXN0U3BsaXQiLCJUZW5zb3JMaXN0U3RhY2siLCJUZW5zb3JTY2F0dGVyQWRkIiwiVGVuc29yU2NhdHRlck1heCIsIlRlbnNvclNjYXR0ZXJNaW4iLCJUZW5zb3JTY2F0dGVyU3ViIiwiVGVuc29yU2NhdHRlclVwZGF0ZSIsIlRlbnNvclN0cmlkZWRTbGljZVVwZGF0ZSIsIlRpbGUiLCJUb3BLVW5pcXVlIiwiVG9wS1YyIiwiVG9wS1dpdGhVbmlxdWUiLCJUcmFuc3Bvc2UiLCJUcmlkaWFnb25hbFNvbHZlIiwiVHJ1bmNhdGVEaXYiLCJUcnVuY2F0ZU1vZCIsIlRydW5jYXRlZE5vcm1hbCIsIlVuaXF1ZSIsIlVucGFjayIsIlVuc29ydGVkU2VnbWVudE1heCIsIlVuc29ydGVkU2VnbWVudE1pbiIsIlVuc29ydGVkU2VnbWVudFByb2QiLCJVbnNvcnRlZFNlZ21lbnRTdW0iLCJVcHBlckJvdW5kIiwiVmFySXNJbml0aWFsaXplZE9wIiwiVmFyaWFibGVTaGFwZSIsIldoZXJlIiwiV2hpbGUiLCJYZGl2eSIsIlhsYUJyb2FkY2FzdEhlbHBlciIsIlhsYUNvbnYiLCJYbGFDb252VjIiLCJYbGFEZXF1YW50aXplIiwiWGxhRG90IiwiWGxhRG90VjIiLCJYbGFEeW5hbWljU2xpY2UiLCJYbGFEeW5hbWljVXBkYXRlU2xpY2UiLCJYbGFFaW5zdW0iLCJYbGFHYXRoZXIiLCJYbGFIb3N0Q29tcHV0ZSIsIlhsYUlmIiwiWGxhS2V5VmFsdWVTb3J0IiwiWGxhUGFkIiwiWGxhUmVjdiIsIlhsYVJlY3ZGcm9tSG9zdCIsIlhsYVJlZHVjZSIsIlhsYVJlZHVjZVdpbmRvdyIsIlhsYVJlcGxpY2FJZCIsIlhsYVNjYXR0ZXIiLCJYbGFTZWxlY3RBbmRTY2F0dGVyIiwiWGxhU2VsZkFkam9pbnRFaWciLCJYbGFTZW5kIiwiWGxhU2VuZFRvSG9zdCIsIlhsYVNldEJvdW5kIiwiWGxhU2V0RHluYW1pY0RpbWVuc2lvblNpemUiLCJYbGFTaGFyZGluZyIsIlhsYVNvcnQiLCJYbGFTcG1kRnVsbFRvU2hhcmRTaGFwZSIsIlhsYVNwbWRTaGFyZFRvRnVsbFNoYXBlIiwiWGxhU3ZkIiwiWGxhVmFyaWFkaWNSZWR1Y2UiLCJYbGFWYXJpYWRpY1NvcnQiLCJYbGFXaGlsZSIsIlhsb2cxcHkiLCJYbG9neSIsIlplcm9zTGlrZSIsIlpldGEiLCJFbnRlciIsIkV4aXQiLCJMb29wQ29uZCIsIk1lcmdlIiwiTmV4dEl0ZXJhdGlvbiIsIlN3aXRjaCIsIl9BcmciLCJfQXJyYXlUb0xpc3QiLCJfRnVzZWRCYXRjaE5vcm1FeCIsIl9MaXN0VG9BcnJheSIsIl9QYXJhbGxlbENvbmNhdFVwZGF0ZSIsIl9SZWN2VFBVRW1iZWRkaW5nQWN0aXZhdGlvbnMiLCJfUmVjdlRQVUVtYmVkZGluZ0RlZHVwbGljYXRpb25EYXRhIiwiX1JldHZhbCIsIl9TZW5kVFBVRW1iZWRkaW5nR3JhZGllbnRzIiwiX1RQVUNvbXBpbGUiLCJfVFBVRXhlY3V0ZSIsIl9VbmFyeU9wc0NvbXBvc2l0aW9uIiwiVFBVQ29tcGlsYXRpb25SZXN1bHQiLCJUUFVSZXBsaWNhdGVkSW5wdXQiLCJUUFVSZXBsaWNhdGVkT3V0cHV0IiwiVFBVUmVwbGljYXRlTWV0YWRhdGEiLCJNZXJnZVYyQ2hlY2twb2ludHMiLCJSZXN0b3JlVjIiLCJTYXZlVjIiLCJBYm9ydCIsIkFzc2VydCIsIkFzc2lnbiIsIlBsYWNlaG9sZGVyIiwiUGxhY2Vob2xkZXJWMiIsIlNoYXJkZWRGaWxlbmFtZSIsIlN0cmluZ0pvaW4iLCJWYXJpYWJsZSIsIlZhcmlhYmxlVjIiLCJWYXJIYW5kbGVPcCIsIkF1ZGlvU3VtbWFyeSIsIkF1ZGlvU3VtbWFyeVYyIiwiRGVidWdOdW1lcmljU3VtbWFyeSIsIkhpc3RvZ3JhbVN1bW1hcnkiLCJJbWFnZVN1bW1hcnkiLCJNZXJnZVN1bW1hcnkiLCJTY2FsYXJTdW1tYXJ5IiwiU3RhdHNBZ2dyZWdhdG9yU3VtbWFyeSJdO2Z1bmN0aW9uIFlsZShlLHQpe2lmKHQ9PT1udWxsKXRocm93IG5ldyBFcnJvcigiQ29tcGF0aWJpbGl0eSBwcm92aWRlciByZXF1aXJlZCwgYnV0IGdvdDogIit0KTtjMy5lYWNoKGUubm9kZXMscj0+e3IuY29tcGF0aWJsZT10Lm9wVmFsaWQociksYzMuZWFjaChyLmluRW1iZWRkaW5ncyxuPT57bi5jb21wYXRpYmxlPXQub3BWYWxpZChuKX0pLGMzLmVhY2goci5vdXRFbWJlZGRpbmdzLG49PntuLmNvbXBhdGlibGU9dC5vcFZhbGlkKG4pfSl9KX12YXIgbVA9RWUoT2UoKSwxKTt2YXIgamxlPUVlKE9lKCksMSk7dmFyIElsPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl9yYXdSZWdleElucHV0PSIiLHRoaXMuX3ByZXZpb3VzUmVnZXhJbnB1dD0iIix0aGlzLl9zZWFyY2hUaW1lb3V0RGVsYXk9MTUwLHRoaXMuX21heFJlZ2V4UmVzdWx0cz00Mn1nZXQgX3JlZ2V4SW5wdXQoKXt2YXIgdD10aGlzLnJlbmRlckhpZXJhcmNoeSxyPXRoaXMuX3Jhd1JlZ2V4SW5wdXQ7cmV0dXJuIHIudHJpbSgpfV9yZWdleElucHV0Q2hhbmdlZCgpe3ZhciB0PXRoaXMuX3JlZ2V4SW5wdXQ7dGhpcy5fcmVxdWVzdFNlYXJjaCgpfV9jbGVhclNlYXJjaFJlc3VsdHMoKXt0aGlzLnNldCgiX3JlZ2V4TWF0Y2hlcyIsW10pfV9yZXF1ZXN0U2VhcmNoKCl7aWYoIXRoaXMuX3NlYXJjaFBlbmRpbmcpe2lmKHRoaXMuX3JlZ2V4SW5wdXQ9PT10aGlzLl9wcmV2aW91c1JlZ2V4SW5wdXQpe3RoaXMuX3NlYXJjaFBlbmRpbmc9ITE7cmV0dXJufXRoaXMuX3NlYXJjaFBlbmRpbmc9ITAsdGhpcy5fZXhlY3V0ZVNlYXJjaCgpLHRoaXMuYXN5bmMoKCk9Pnt0aGlzLl9zZWFyY2hQZW5kaW5nPSExLHRoaXMuX3JlcXVlc3RTZWFyY2goKX0sdGhpcy5fc2VhcmNoVGltZW91dERlbGF5KX19X2V4ZWN1dGVTZWFyY2goKXtpZih0aGlzLl9wcmV2aW91c1JlZ2V4SW5wdXQ9dGhpcy5fcmVnZXhJbnB1dCwhdGhpcy5fcmVnZXhJbnB1dCl7dGhpcy5fY2xlYXJTZWFyY2hSZXN1bHRzKCk7cmV0dXJufXRyeXt2YXIgdD1uZXcgUmVnRXhwKHRoaXMuX3JlZ2V4SW5wdXQpfWNhdGNoKGkpe3RoaXMuX2NsZWFyU2VhcmNoUmVzdWx0cygpO3JldHVybn1sZXQgcj1bXSxuPXRoaXMucmVuZGVySGllcmFyY2h5LmhpZXJhcmNoeS5nZXROb2RlTWFwKCk7amxlLmVhY2gobiwoaSxvKT0+e2lmKHIubGVuZ3RoPj10aGlzLl9tYXhSZWdleFJlc3VsdHMpcmV0dXJuITE7IXQudGVzdChvKXx8ci5wdXNoKG8pfSksdGhpcy5zZXQoIl9yZWdleE1hdGNoZXMiLHIpfV9tYXRjaENsaWNrZWQodCl7bGV0IHI9dC5tb2RlbC5pdGVtO3RoaXMuc2V0KCJzZWxlY3RlZE5vZGUiLHIpLFBvKHthY3Rpb25JZDpqci5OT0RFX1NFQVJDSF9SRVNVTFRfRk9DVVNFRH0pfX07SWwudGVtcGxhdGU9UWAKICAgIDxkaXYgaWQ9InNlYXJjaC1jb250YWluZXIiPgogICAgICA8cGFwZXItaW5wdXQKICAgICAgICBpZD0icnVucy1yZWdleCIKICAgICAgICBsYWJlbD0iU2VhcmNoIG5vZGVzIChyZWdleCkiCiAgICAgICAgdmFsdWU9Int7X3Jhd1JlZ2V4SW5wdXR9fSIKICAgICAgPgogICAgICA8L3BhcGVyLWlucHV0PgogICAgICA8ZGl2IGlkPSJzZWFyY2gtcmVzdWx0cy1hbmNob3IiPgogICAgICAgIDxkaXYgaWQ9InNlYXJjaC1yZXN1bHRzIj4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9IltbX3JlZ2V4TWF0Y2hlc11dIj4KICAgICAgICAgICAgPGRpdiBpZD0ic2VhcmNoLW1hdGNoIiBvbi1jbGljaz0iX21hdGNoQ2xpY2tlZCI+W1tpdGVtXV08L2Rpdj4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgICA8c3R5bGU+CiAgICAgICNzZWFyY2gtY29udGFpbmVyIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBvdmVyZmxvdzogdmlzaWJsZTsKICAgICAgfQoKICAgICAgI3J1bnMtcmVnZXggewogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICB9CgogICAgICAjc2VhcmNoLXJlc3VsdHMtYW5jaG9yIHsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgIH0KCiAgICAgICNzZWFyY2gtcmVzdWx0cyB7CiAgICAgICAgY29sb3I6ICNmZmY7CiAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgIG1heC1oZWlnaHQ6IDIwMHB4OwogICAgICAgIG92ZXJmbG93LXg6IGhpZGRlbjsKICAgICAgICBvdmVyZmxvdy15OiBhdXRvOwogICAgICAgIHRleHQtYWxpZ246IHJpZ2h0OwogICAgICAgIG1heC13aWR0aDogMTAwJTsKICAgICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICB9CgogICAgICAjc2VhcmNoLW1hdGNoIHsKICAgICAgICBiYWNrZ3JvdW5kOiB2YXIoLS10Yi1vcmFuZ2Utc3Ryb25nKTsKICAgICAgICBwYWRkaW5nOiAzcHg7CiAgICAgICAgZmxvYXQ6IHJpZ2h0OwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgICAgZGlyZWN0aW9uOiBydGw7CiAgICAgIH0KCiAgICAgICNzZWFyY2gtbWF0Y2g6aG92ZXIgewogICAgICAgIGJhY2tncm91bmQ6IHZhcigtLXRiLW9yYW5nZS13ZWFrKTsKICAgICAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxJbC5wcm90b3R5cGUsInJlbmRlckhpZXJhcmNoeSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZyxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sSWwucHJvdG90eXBlLCJzZWxlY3RlZE5vZGUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sSWwucHJvdG90eXBlLCJfcmF3UmVnZXhJbnB1dCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxJbC5wcm90b3R5cGUsIl9wcmV2aW91c1JlZ2V4SW5wdXQiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sSWwucHJvdG90eXBlLCJfc2VhcmNoVGltZW91dERlbGF5Iix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSWwucHJvdG90eXBlLCJfc2VhcmNoUGVuZGluZyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxJbC5wcm90b3R5cGUsIl9tYXhSZWdleFJlc3VsdHMiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLElsLnByb3RvdHlwZSwiX3JlZ2V4TWF0Y2hlcyIsdm9pZCAwKTtFKFtSdCgicmVuZGVySGllcmFyY2h5IiwiX3Jhd1JlZ2V4SW5wdXQiKSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxJbC5wcm90b3R5cGUsIl9yZWdleElucHV0IixudWxsKTtFKFtCdCgiX3JlZ2V4SW5wdXQiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLElsLnByb3RvdHlwZSwiX3JlZ2V4SW5wdXRDaGFuZ2VkIixudWxsKTtJbD1FKFt5dCgidGYtZ3JhcGgtbm9kZS1zZWFyY2giKV0sSWwpO3ZhciBjY3Q9L2RldmljZTooW146XSs6WzAtOV0rKSQvLGxjdD1be3JlZ2V4OmNjdH1dLFhsZT1bXSxvdXI9bmV3IFNldChbR24uQ09NUFVURV9USU1FLEduLk1FTU9SWV0pLEluPWNsYXNzIGV4dGVuZHMgR3QoX28obXQpKXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5Db2xvckJ5PUduLHRoaXMuc3RhdHM9bnVsbCx0aGlzLmRldmljZXNGb3JTdGF0cz1udWxsLHRoaXMuY29sb3JCeT1Hbi5TVFJVQ1RVUkUsdGhpcy5kYXRhc2V0cz1bXSx0aGlzLl9zZWxlY3RlZFJ1bkluZGV4PTAsdGhpcy50cmFjZUlucHV0cz0hMSx0aGlzLmF1dG9FeHRyYWN0Tm9kZXM9ITAsdGhpcy5fc2VsZWN0ZWRUYWdJbmRleD0wLHRoaXMuX3NlbGVjdGVkR3JhcGhUeXBlPUZzLk9QX0dSQVBILHRoaXMuc2hvd1Nlc3Npb25SdW5zRHJvcGRvd249ITAsdGhpcy5zaG93VXBsb2FkQnV0dG9uPSEwLHRoaXMuX2xlZ2VuZE9wZW5lZD0hMCx0aGlzLl9kb3dubG9hZEZpbGVuYW1lPSJncmFwaC5wbmcifV9vbkdyYXBoVHlwZUNoYW5nZWRCeVVzZXJHZXN0dXJlKCl7UG8oe2FjdGlvbklkOmpyLkdSQVBIX1RZUEVfQ0hBTkdFRCxldmVudExhYmVsOnRoaXMuX3NlbGVjdGVkR3JhcGhUeXBlfSl9X29uQ29sb3JCeUNoYW5nZWRCeVVzZXJHZXN0dXJlKCl7UG8oe2FjdGlvbklkOmpyLk5PREVfQ09MT1JfTU9ERV9DSEFOR0VELGV2ZW50TGFiZWw6dGhpcy5jb2xvckJ5fSl9X29uVHJhY2VJbnB1dHNDaGFuZ2VkQnlVc2VyR2VzdHVyZSgpe1BvKHthY3Rpb25JZDpqci5UUkFDRV9JTlBVVF9NT0RFX1RPR0dMRUR9KX1feGxhQ2x1c3RlcnNQcm92aWRlZCh0KXtyZXR1cm4gdCYmdC5oaWVyYXJjaHkmJnQuaGllcmFyY2h5LnhsYUNsdXN0ZXJzLmxlbmd0aD4wfV9zdGF0c0NoYW5nZWQodCl7aWYodCE9bnVsbCl7dmFyIHI9e30sbj1tUC5lYWNoKHQuZGV2X3N0YXRzLGZ1bmN0aW9uKGkpe3ZhciBvPW1QLnNvbWUobGN0LGZ1bmN0aW9uKHMpe3JldHVybiBzLnJlZ2V4LnRlc3QoaS5kZXZpY2UpfSksYT1tUC5zb21lKFhsZSxmdW5jdGlvbihzKXtyZXR1cm4gcy5yZWdleC50ZXN0KGkuZGV2aWNlKX0pO28mJiFhJiYocltpLmRldmljZV09ITApfSk7dGhpcy5zZXQoImRldmljZXNGb3JTdGF0cyIscil9fWdldCBfY3VycmVudERldmljZXMoKXt2YXIgdD10aGlzLmRldmljZXNGb3JTdGF0cztsZXQgcj10aGlzLnN0YXRzLG89KHI/ci5kZXZfc3RhdHM6W10pLm1hcChzPT5zLmRldmljZSkuZmlsdGVyKHM9PmxjdC5zb21lKGw9PmwucmVnZXgudGVzdChzKSkpLGE9Rmx0KG8pO2lmKGEubGVuZ3RoPT0xKXtsZXQgcz1hWzBdLm1hdGNoKGNjdCk7cyYmKGFbMF09c1sxXSl9cmV0dXJuIG8ubWFwKChzLGwpPT57bGV0IGM9bnVsbDtyZXR1cm4gWGxlLmZvckVhY2godT0+e3UucmVnZXgudGVzdChzKSYmKGM9dS5tc2cpfSkse2RldmljZTpzLHN1ZmZpeDphW2xdLHVzZWQ6dD09bnVsbD92b2lkIDA6dFtzXSxpZ25vcmVkTXNnOmN9fSl9X2RldmljZUNoZWNrYm94Q2xpY2tlZCh0KXtsZXQgcj10LnRhcmdldCxuPU9iamVjdC5hc3NpZ24oe30sdGhpcy5kZXZpY2VzRm9yU3RhdHMpLGk9ci52YWx1ZTtyLmNoZWNrZWQ/bltpXT0hMDpkZWxldGUgbltpXSx0aGlzLnNldCgiZGV2aWNlc0ZvclN0YXRzIixuKX1fbnVtVGFncyh0LHIpe3JldHVybiB0aGlzLl9nZXRUYWdzKHQscikubGVuZ3RofV9nZXRUYWdzKHQscil7cmV0dXJuIXR8fCF0W3JdP1tdOnRbcl0udGFnc31fZml0KCl7dGhpcy5maXJlKCJmaXQtdGFwIil9X2lzR3JhZGllbnRDb2xvcmluZyh0LHIpe3JldHVybiBvdXIuaGFzKHIpJiZ0IT1udWxsfV9lcXVhbHModCxyKXtyZXR1cm4gdD09PXJ9Z2V0IF9jdXJyZW50RGV2aWNlUGFyYW1zKCl7dmFyIHQ9dGhpcy5jb2xvckJ5UGFyYW1zO2xldCByPXQuZGV2aWNlLmZpbHRlcihvPT5sY3Quc29tZShhPT5hLnJlZ2V4LnRlc3Qoby5kZXZpY2UpKSksbj1GbHQoci5tYXAobz0+by5kZXZpY2UpKTtpZihuLmxlbmd0aD09MSl7dmFyIGk9blswXS5tYXRjaChjY3QpO2kmJihuWzBdPWlbMV0pfXJldHVybiByLm1hcCgobyxhKT0+KHtkZXZpY2U6blthXSxjb2xvcjpvLmNvbG9yfSkpfWdldCBfY3VycmVudFhsYUNsdXN0ZXJQYXJhbXMoKXt2YXIgdD10aGlzLmNvbG9yQnlQYXJhbXM7cmV0dXJuIHQueGxhX2NsdXN0ZXJ9Z2V0IF9jdXJyZW50R3JhZGllbnRQYXJhbXMoKXt2YXIgdD10aGlzLmNvbG9yQnlQYXJhbXMscj10aGlzLmNvbG9yQnk7aWYoIXRoaXMuX2lzR3JhZGllbnRDb2xvcmluZyh0aGlzLnN0YXRzLHIpKXJldHVybiBudWxsO2xldCBuPXRbcl0saT1uLm1pblZhbHVlLG89bi5tYXhWYWx1ZTtyZXR1cm4gcj09PUduLk1FTU9SWT8oaT1OZChpLG5QKSxvPU5kKG8sblApKTpyPT09R24uQ09NUFVURV9USU1FJiYoaT1OZChpLGlQKSxvPU5kKG8saVApKSx7bWluVmFsdWU6aSxtYXhWYWx1ZTpvLHN0YXJ0Q29sb3I6bi5zdGFydENvbG9yLGVuZENvbG9yOm4uZW5kQ29sb3J9fWRvd25sb2FkKCl7dGhpcy5maXJlKCJkb3dubG9hZC1pbWFnZS1yZXF1ZXN0ZWQiLHRoaXMuX2Rvd25sb2FkRmlsZW5hbWUpfV91cGRhdGVGaWxlSW5wdXQodCl7dmFyIGE7bGV0IHI9KGE9dC50YXJnZXQuZmlsZXMpPT1udWxsP3ZvaWQgMDphWzBdO2lmKCFyKXJldHVybjtsZXQgbj1yLm5hbWUsaT1uLmxhc3RJbmRleE9mKCIuIik7aT49MCYmKG49bi5zdWJzdHJpbmcoMCxpKSk7bGV0IG89bi5sYXN0SW5kZXhPZigiLyIpO28+PTAmJihuPW4uc3Vic3RyaW5nKG8rMSkpLHRoaXMuX3NldERvd25sb2FkRmlsZW5hbWUobiksdGhpcy5zZXQoInNlbGVjdGVkRmlsZSIsdCksUG8oe2FjdGlvbklkOmpyLlVQTE9BREVEX0dSQVBIX0ZST01fRklMRVNZU1RFTX0pfV9kYXRhc2V0c0NoYW5nZWQodCxyKXt2YXIgbjtyIT1udWxsJiYodGhpcy5fc2VsZWN0ZWRSdW5JbmRleD0wKSx0aGlzLl9zZXREb3dubG9hZEZpbGVuYW1lKChuPXRoaXMuZGF0YXNldHNbdGhpcy5fc2VsZWN0ZWRSdW5JbmRleF0pPT1udWxsP3ZvaWQgMDpuLm5hbWUpfV9jb21wdXRlU2VsZWN0aW9uKHQscixuLGkpe3JldHVybiF0W3JdfHwhdFtyXS50YWdzW25dP251bGw6e3J1bjp0W3JdLm5hbWUsdGFnOnRbcl0udGFnc1tuXS50YWcsdHlwZTppfX1fc2VsZWN0ZWRSdW5JbmRleENoYW5nZWQodCl7dmFyIHI7IXRoaXMuZGF0YXNldHN8fCh0aGlzLmNvbG9yQnk9R24uU1RSVUNUVVJFLHRoaXMuX3NlbGVjdGVkVGFnSW5kZXg9MCx0aGlzLl9zZWxlY3RlZEdyYXBoVHlwZT10aGlzLl9nZXREZWZhdWx0U2VsZWN0aW9uVHlwZSgpLHRoaXMudHJhY2VJbnB1dHM9ITEsdGhpcy5fc2V0RG93bmxvYWRGaWxlbmFtZSgocj10aGlzLmRhdGFzZXRzW3RdKT09bnVsbD92b2lkIDA6ci5uYW1lKSl9X3NlbGVjdGVkVGFnSW5kZXhDaGFuZ2VkKCl7dGhpcy5fc2VsZWN0ZWRHcmFwaFR5cGU9dGhpcy5fZ2V0RGVmYXVsdFNlbGVjdGlvblR5cGUoKX1fZ2V0RGVmYXVsdFNlbGVjdGlvblR5cGUoKXtsZXR7ZGF0YXNldHM6dCxfc2VsZWN0ZWRSdW5JbmRleDpyLF9zZWxlY3RlZFRhZ0luZGV4Om59PXRoaXM7aWYoIXR8fCF0W3JdfHwhdFtyXS50YWdzW25dfHx0W3JdLnRhZ3Nbbl0ub3BHcmFwaClyZXR1cm4gRnMuT1BfR1JBUEg7bGV0IGk9dFtyXTtyZXR1cm4gaS50YWdzW25dLnByb2ZpbGU/RnMuUFJPRklMRTppLnRhZ3Nbbl0uY29uY2VwdHVhbEdyYXBoP0ZzLkNPTkNFUFRVQUxfR1JBUEg6RnMuT1BfR1JBUEh9X2dldEZpbGUoKXt0aGlzLiQkKCIjZmlsZSIpLmNsaWNrKCl9X3NldERvd25sb2FkRmlsZW5hbWUodCl7dGhpcy5fZG93bmxvYWRGaWxlbmFtZT0odHx8ImdyYXBoIikrIi5wbmcifV9zdGF0c05vdE51bGwodCl7cmV0dXJuIHQhPT1udWxsfV90b2dnbGVMZWdlbmRPcGVuKCl7dGhpcy5zZXQoIl9sZWdlbmRPcGVuZWQiLCF0aGlzLl9sZWdlbmRPcGVuZWQpfV9nZXRUb2dnbGVMZWdlbmRJY29uKHQpe3JldHVybiB0PyJleHBhbmQtbW9yZSI6ImV4cGFuZC1sZXNzIn1fZ2V0U2VsZWN0aW9uT3BHcmFwaERpc2FibGVkKHQscixuKXtyZXR1cm4hdFtyXXx8IXRbcl0udGFnc1tuXXx8IXRbcl0udGFnc1tuXS5vcEdyYXBofV9nZXRTZWxlY3Rpb25Qcm9maWxlRGlzYWJsZWQodCxyLG4pe3JldHVybiF0W3JdfHwhdFtyXS50YWdzW25dfHwhdFtyXS50YWdzW25dLnByb2ZpbGV9X2dldFNlbGVjdGlvbkNvbmNlcHR1YWxHcmFwaERpc2FibGVkKHQscixuKXtyZXR1cm4hdFtyXXx8IXRbcl0udGFnc1tuXXx8IXRbcl0udGFnc1tuXS5jb25jZXB0dWFsR3JhcGh9fTtJbi50ZW1wbGF0ZT1RYAogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgY29sb3I6ICM1NTU7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgIGZvbnQtc2l6ZTogMTJweDsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICAtLXRiLWdyYXBoLWNvbnRyb2xzLXRpdGxlLWNvbG9yOiAjMDAwOwogICAgICAgIC0tdGItZ3JhcGgtY29udHJvbHMtbGVnZW5kLXRleHQtY29sb3I6ICMwMDA7CiAgICAgICAgLS10Yi1ncmFwaC1jb250cm9scy10ZXh0LWNvbG9yOiAjNTU1OwogICAgICAgIC0tdGItZ3JhcGgtY29udHJvbHMtdGl0bGUtZm9udC1zaXplOiAxNHB4OwogICAgICAgIC0tdGItZ3JhcGgtY29udHJvbHMtc3VidGl0bGUtZm9udC1zaXplOiAxNHB4OwogICAgICAgIC0tcGFwZXItaW5wdXQtY29udGFpbmVyLXNoYXJlZC1pbnB1dC1zdHlsZV8tX2ZvbnQtc2l6ZTogMTRweDsKICAgICAgICAtLXBhcGVyLWZvbnQtc3ViaGVhZF8tX2ZvbnQtc2l6ZTogMTRweDsKICAgICAgfQoKICAgICAgOmhvc3QoLmRhcmstbW9kZSkgewogICAgICAgIC0tdGItZ3JhcGgtY29udHJvbHMtdGl0bGUtY29sb3I6ICNmZmY7CiAgICAgICAgLS10Yi1ncmFwaC1jb250cm9scy1sZWdlbmQtdGV4dC1jb2xvcjogI2YzZjNmMzsKICAgICAgICAtLXRiLWdyYXBoLWNvbnRyb2xzLXRleHQtY29sb3I6ICNlZWU7CiAgICAgIH0KCiAgICAgIHBhcGVyLWRyb3Bkb3duLW1lbnUgewogICAgICAgIC0tcGFwZXItZHJvcGRvd24tbWVudS1pbnB1dDogewogICAgICAgICAgcGFkZGluZzogMDsKICAgICAgICAgIGNvbG9yOiBncmF5OwogICAgICAgIH0KICAgICAgICAtLWlyb24taWNvbi13aWR0aDogMTVweDsKICAgICAgICAtLWlyb24taWNvbi1oZWlnaHQ6IDE1cHg7CiAgICAgICAgLS1wcmltYXJ5LXRleHQtY29sb3I6IGdyYXk7CiAgICAgICAgLS1wYXBlci1pdGVtLW1pbi1oZWlnaHQ6IDMwcHg7CiAgICAgIH0KCiAgICAgIHBhcGVyLWJ1dHRvbltyYWlzZWRdLmtleWJvYXJkLWZvY3VzIHsKICAgICAgICBmb250LXdlaWdodDogbm9ybWFsOwogICAgICB9CgogICAgICAucnVuLWRyb3Bkb3duIHsKICAgICAgICAtLXBhcGVyLWlucHV0LWNvbnRhaW5lcjogewogICAgICAgICAgcGFkZGluZzogNXB4IDAgNXB4IDVweDsKICAgICAgICB9CiAgICAgIH0KCiAgICAgIHRhYmxlIHsKICAgICAgICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOwogICAgICAgIGJvcmRlci1zcGFjaW5nOiAwOwogICAgICB9CgogICAgICB0YWJsZSB0ciB7CiAgICAgICAgaGVpZ2h0OiAyMHB4OwogICAgICB9CgogICAgICB0YWJsZSB0ZCB7CiAgICAgICAgcGFkZGluZzogMDsKICAgICAgICBtYXJnaW46IDA7CiAgICAgIH0KCiAgICAgIC5hbGxjb250cm9scyB7CiAgICAgICAgcGFkZGluZzogMCAyMHB4IDIwcHg7CiAgICAgICAgZmxleC1ncm93OiAxOwogICAgICAgIG92ZXJmbG93LXk6IGF1dG87CiAgICAgIH0KCiAgICAgIC5sZWdlbmQtaG9sZGVyIHsKICAgICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1zZWNvbmRhcnktYmFja2dyb3VuZC1jb2xvcik7CiAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsKICAgICAgICBjb2xvcjogdmFyKC0tdGItZ3JhcGgtY29udHJvbHMtdGV4dC1jb2xvcik7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIH0KCiAgICAgIC5sZWdlbmQtdG9vbGJhciB7CiAgICAgICAgYXBwZWFyYW5jZTogbm9uZTsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiBpbmhlcml0OwogICAgICAgIGJvcmRlci10b3A6IDFweCBzb2xpZCAjY2NjOwogICAgICAgIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjY2NjOwogICAgICAgIGJvcmRlci1yaWdodDogbm9uZTsKICAgICAgICBib3JkZXItbGVmdDogbm9uZTsKICAgICAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLWdyYXBoLWNvbnRyb2xzLWxlZ2VuZC10ZXh0LWNvbG9yKTsKICAgICAgICBmb250OiBpbmhlcml0OwogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIH0KCiAgICAgIC5sZWdlbmQtdG9vbGJhciwKICAgICAgLmxlZ2VuZC1jb250ZW50IHsKICAgICAgICBwYWRkaW5nOiA4cHggMjBweDsKICAgICAgfQoKICAgICAgLnRvZ2dsZS1sZWdlbmQtYnV0dG9uIHsKICAgICAgICBtYXgtaGVpZ2h0OiAyMHB4OwogICAgICAgIG1heC13aWR0aDogMjBweDsKICAgICAgICBwYWRkaW5nOiAwOwogICAgICB9CgogICAgICAudG9nZ2xlLWxlZ2VuZC10ZXh0IHsKICAgICAgICBmb250LXNpemU6IHZhcigtLXRiLWdyYXBoLWNvbnRyb2xzLXN1YnRpdGxlLWZvbnQtc2l6ZSk7CiAgICAgIH0KCiAgICAgIHBhcGVyLXJhZGlvLWJ1dHRvbiB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgcGFkZGluZzogNXB4OwogICAgICB9CiAgICAgIHN2Zy5pY29uLAogICAgICB0Zi1ncmFwaC1pY29uIHsKICAgICAgICB3aWR0aDogNjBweDsKICAgICAgICBoZWlnaHQ6IDE4cHg7CiAgICAgIH0KICAgICAgLmRvbWFpblZhbHVlcyB7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTBweDsKICAgICAgICB3aWR0aDogMTY1cHg7CiAgICAgIH0KICAgICAgLmRvbWFpblN0YXJ0IHsKICAgICAgICBmbG9hdDogbGVmdDsKICAgICAgfQogICAgICAuZG9tYWluRW5kIHsKICAgICAgICBmbG9hdDogcmlnaHQ7CiAgICAgIH0KICAgICAgLmNvbG9yQm94IHsKICAgICAgICB3aWR0aDogMjBweDsKICAgICAgfQoKICAgICAgLmltYWdlLWljb24gewogICAgICAgIHdpZHRoOiAyNHB4OwogICAgICAgIGhlaWdodDogMjRweDsKICAgICAgfQoKICAgICAgLmhlbHAtaWNvbiB7CiAgICAgICAgaGVpZ2h0OiAxNXB4OwogICAgICAgIG1hcmdpbjogMDsKICAgICAgICBwYWRkaW5nOiAwOwogICAgICB9CgogICAgICAuZ3JheSB7CiAgICAgICAgY29sb3I6ICM2NjY7CiAgICAgIH0KCiAgICAgIC50aXRsZSB7CiAgICAgICAgZm9udC1zaXplOiB2YXIoLS10Yi1ncmFwaC1jb250cm9scy10aXRsZS1mb250LXNpemUpOwogICAgICAgIG1hcmdpbjogOHB4IDVweCA4cHggMDsKICAgICAgICBjb2xvcjogdmFyKC0tdGItZ3JhcGgtY29udHJvbHMtdGl0bGUtY29sb3IpOwogICAgICB9CiAgICAgIC50aXRsZSBzbWFsbCB7CiAgICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDsKICAgICAgfQogICAgICAuZGV2aWNlTGlzdCwKICAgICAgLnhsYUNsdXN0ZXJMaXN0IHsKICAgICAgICBtYXgtaGVpZ2h0OiAyMDBweDsKICAgICAgICBvdmVyZmxvdy15OiBhdXRvOwogICAgICB9CgogICAgICAjZmlsZSB7CiAgICAgICAgcGFkZGluZzogOHB4IDA7CiAgICAgIH0KCiAgICAgIC5jb2xvci1sZWdlbmQtcm93IHsKICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICAgIGNsZWFyOiBib3RoOwogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgaGVpZ2h0OiAyMHB4OwogICAgICAgIG1hcmdpbi10b3A6IDVweDsKICAgICAgfQoKICAgICAgLmNvbG9yLWxlZ2VuZC1yb3cgLmxhYmVsLAogICAgICAuY29sb3ItbGVnZW5kLXJvdyBzdmcsCiAgICAgIC5jb2xvci1sZWdlbmQtcm93IHRmLWdyYXBoLWljb24gewogICAgICAgIGZsZXg6IDAgMCA0MHB4OwogICAgICAgIG1hcmdpbi1yaWdodDogMjBweDsKICAgICAgfQoKICAgICAgLmRldmljZXMtY2hlY2tib3ggaW5wdXQgewogICAgICAgIHRleHQtYWxpZ246IGxlZnQ7CiAgICAgICAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsKICAgICAgfQoKICAgICAgLmNvbnRyb2wtaG9sZGVyIC5pY29uLWJ1dHRvbiB7CiAgICAgICAgZm9udC1zaXplOiB2YXIoLS10Yi1ncmFwaC1jb250cm9scy1zdWJ0aXRsZS1mb250LXNpemUpOwogICAgICAgIG1hcmdpbjogMCAtNXB4OwogICAgICAgIHBhZGRpbmc6IDVweDsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGp1c3RpZnktY29udGVudDogZmxleC1zdGFydDsKICAgICAgICBjb2xvcjogdmFyKC0tdGItZ3JhcGgtY29udHJvbHMtdGV4dC1jb2xvcik7CiAgICAgIH0KCiAgICAgIC5idXR0b24tdGV4dCB7CiAgICAgICAgcGFkZGluZy1sZWZ0OiAyMHB4OwogICAgICAgIHRleHQtdHJhbnNmb3JtOiBub25lOwogICAgICB9CgogICAgICAudXBsb2FkLWJ1dHRvbiB7CiAgICAgICAgd2lkdGg6IDE2NXB4OwogICAgICAgIGhlaWdodDogMjVweDsKICAgICAgICB0ZXh0LXRyYW5zZm9ybTogbm9uZTsKICAgICAgICBtYXJnaW4tdG9wOiA0cHg7CiAgICAgIH0KCiAgICAgIC5idXR0b24taWNvbiB7CiAgICAgICAgd2lkdGg6IDI2cHg7CiAgICAgICAgaGVpZ2h0OiAyNnB4OwogICAgICAgIGNvbG9yOiB2YXIoLS1wYXBlci1vcmFuZ2UtNTAwKTsKICAgICAgfQoKICAgICAgLmhpZGRlbi1pbnB1dCB7CiAgICAgICAgZGlzcGxheTogbm9uZTsKICAgICAgfQoKICAgICAgLmFsbGNvbnRyb2xzIC5jb250cm9sLWhvbGRlciB7CiAgICAgICAgY2xlYXI6IGJvdGg7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgICAgIH0KCiAgICAgIC5hbGxjb250cm9scyAuY29udHJvbC1ob2xkZXIuY29udHJvbC1vcHRpb25zIHsKICAgICAgICBwYWRkaW5nOiAwIDAgMTVweCAxNXB4OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgIH0KCiAgICAgIC5hbGxjb250cm9scyAuY29udHJvbC1ob2xkZXIgcGFwZXItdG9nZ2xlLWJ1dHRvbiB7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogNXB4OwogICAgICB9CgogICAgICBzcGFuLmNvdW50ZXIgewogICAgICAgIGZvbnQtc2l6ZTogdmFyKC0tdGItZ3JhcGgtY29udHJvbHMtc3VidGl0bGUtZm9udC1zaXplKTsKICAgICAgICBjb2xvcjogZ3JheTsKICAgICAgICBtYXJnaW4tbGVmdDogNHB4OwogICAgICB9CgogICAgICAucnVucy1yb3cgLnRpdGxlLAogICAgICAudGFncy1yb3cgLnRpdGxlIHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGFsaWduLWl0ZW1zOiBiYXNlbGluZTsKICAgICAgfQoKICAgICAgLnJ1bnMtcm93IHBhcGVyLWl0ZW0sCiAgICAgIC50YWdzLXJvdyBwYXBlci1pdGVtIHsKICAgICAgICAtLXBhcGVyLWl0ZW06IHsKICAgICAgICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7CiAgICAgICAgfQogICAgICB9CgogICAgICB0YWJsZS5jb250cm9sLWhvbGRlciB7CiAgICAgICAgYm9yZGVyOiAwOwogICAgICAgIGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7CiAgICAgIH0KCiAgICAgIHRhYmxlLnRmLWdyYXBoLWNvbnRyb2xzIHRkLmlucHV0LWVsZW1lbnQtdGFibGUtZGF0YSB7CiAgICAgICAgcGFkZGluZzogMCAwIDAgMjBweDsKICAgICAgfQoKICAgICAgLnNwYWNlciB7CiAgICAgICAgZmxleC1ncm93OiAxOwogICAgICB9CgogICAgICAuY29sb3ItdGV4dCB7CiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgfQoKICAgICAgLmNvbG9yLXRleHQuZ3JhZGllbnQtY29udGFpbmVyIHsKICAgICAgICBtYXJnaW46IDAgNXB4OwogICAgICB9CgogICAgICAvKiogT3ZlcnJpZGUgaW5saW5lIHN0eWxlcyB0aGF0IHN1cHByZXNzIHBvaW50ZXIgZXZlbnRzIGZvciBkaXNhYmxlZCBidXR0b25zLiBPdGhlcndpc2UsIHRoZSAqLwogICAgICAvKiAgdG9vbHRpcHMgZG8gbm90IGFwcGVhci4gKi8KICAgICAgcGFwZXItcmFkaW8tZ3JvdXAgcGFwZXItcmFkaW8tYnV0dG9uIHsKICAgICAgICBwb2ludGVyLWV2ZW50czogYXV0byAhaW1wb3J0YW50OwogICAgICB9CgogICAgICAubGVnZW5kLWNsYXJpZmllciB7CiAgICAgICAgY29sb3I6ICMyNjYyMzY7CiAgICAgICAgY3Vyc29yOiBoZWxwOwogICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKICAgICAgfQoKICAgICAgLmxlZ2VuZC1jbGFyaWZpZXIgcGFwZXItdG9vbHRpcCB7CiAgICAgICAgd2lkdGg6IDE1MHB4OwogICAgICB9CgogICAgICAvKiogT3RoZXJ3aXNlLCBwb2x5bWVyIFVJIGNvbnRyb2xzIGFwcGVhciBhdG9wIG5vZGUgc2VhcmNoLiAqLwogICAgICB0Zi1ncmFwaC1ub2RlLXNlYXJjaCB7CiAgICAgICAgei1pbmRleDogMTsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgfQoKICAgICAgcGFwZXItZHJvcGRvd24tbWVudSB7CiAgICAgICAgZmxleC1ncm93OiAxOwogICAgICB9CiAgICA8L3N0eWxlPgoKICAgIDxkaXYgY2xhc3M9ImFsbGNvbnRyb2xzIj4KICAgICAgPGRpdiBjbGFzcz0iY29udHJvbC1ob2xkZXIiPgogICAgICAgIDx0Zi1ncmFwaC1ub2RlLXNlYXJjaAogICAgICAgICAgc2VsZWN0ZWQtbm9kZT0ie3tzZWxlY3RlZE5vZGV9fSIKICAgICAgICAgIHJlbmRlci1oaWVyYXJjaHk9IltbcmVuZGVySGllcmFyY2h5XV0iCiAgICAgICAgPjwvdGYtZ3JhcGgtbm9kZS1zZWFyY2g+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJjb250cm9sLWhvbGRlciI+CiAgICAgICAgPHBhcGVyLWJ1dHRvbiBjbGFzcz0iaWNvbi1idXR0b24iIG9uLXRhcD0iX2ZpdCIgYWx0PSJGaXQgdG8gc2NyZWVuIj4KICAgICAgICAgIDxpcm9uLWljb24gaWNvbj0iYXNwZWN0LXJhdGlvIiBjbGFzcz0iYnV0dG9uLWljb24iPjwvaXJvbi1pY29uPgogICAgICAgICAgPHNwYW4gY2xhc3M9ImJ1dHRvbi10ZXh0Ij5GaXQgdG8gc2NyZWVuPC9zcGFuPgogICAgICAgIDwvcGFwZXItYnV0dG9uPgogICAgICA8L2Rpdj4KICAgICAgPGRpdiBjbGFzcz0iY29udHJvbC1ob2xkZXIiPgogICAgICAgIDxwYXBlci1idXR0b24KICAgICAgICAgIGNsYXNzPSJpY29uLWJ1dHRvbiIKICAgICAgICAgIG9uLWNsaWNrPSJkb3dubG9hZCIKICAgICAgICAgIGFsdD0iRG93bmxvYWQgUE5HIgogICAgICAgID4KICAgICAgICAgIDxpcm9uLWljb24gaWNvbj0iZmlsZS1kb3dubG9hZCIgY2xhc3M9ImJ1dHRvbi1pY29uIj48L2lyb24taWNvbj4KICAgICAgICAgIDxzcGFuIGNsYXNzPSJidXR0b24tdGV4dCI+RG93bmxvYWQgUE5HPC9zcGFuPgogICAgICAgIDwvcGFwZXItYnV0dG9uPgogICAgICA8L2Rpdj4KICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW3Nob3dVcGxvYWRCdXR0b25dXSI+CiAgICAgICAgPGRpdiBjbGFzcz0iY29udHJvbC1ob2xkZXIiPgogICAgICAgICAgPHBhcGVyLWJ1dHRvbgogICAgICAgICAgICBjbGFzcz0iaWNvbi1idXR0b24iCiAgICAgICAgICAgIG9uLWNsaWNrPSJfZ2V0RmlsZSIKICAgICAgICAgICAgYWx0PSJVcGxvYWQgZmlsZSIKICAgICAgICAgICAgdGl0bGU9IlVwbG9hZCBhIHBidHh0IGZpbGUgdG8gdmlldyBhIGdyYXBoIGZyb20gdGhlIGxvY2FsIGZpbGVzeXN0ZW0iCiAgICAgICAgICA+CiAgICAgICAgICAgIDxpcm9uLWljb24gaWNvbj0iZmlsZS11cGxvYWQiIGNsYXNzPSJidXR0b24taWNvbiI+PC9pcm9uLWljb24+CiAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJidXR0b24tdGV4dCI+VXBsb2FkIGZpbGU8L3NwYW4+CiAgICAgICAgICA8L3BhcGVyLWJ1dHRvbj4KCiAgICAgICAgICA8ZGl2IGNsYXNzPSJoaWRkZW4taW5wdXQiPgogICAgICAgICAgICA8aW5wdXQKICAgICAgICAgICAgICB0eXBlPSJmaWxlIgogICAgICAgICAgICAgIGlkPSJmaWxlIgogICAgICAgICAgICAgIG5hbWU9ImZpbGUiCiAgICAgICAgICAgICAgb24tY2hhbmdlPSJfdXBkYXRlRmlsZUlucHV0IgogICAgICAgICAgICAgIGFjY2VwdD0iLnBidHh0IgogICAgICAgICAgICAvPgogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDxkaXYgY2xhc3M9ImNvbnRyb2wtaG9sZGVyIHJ1bnMtcm93Ij4KICAgICAgICA8ZGl2IGNsYXNzPSJ0aXRsZSI+CiAgICAgICAgICBSdW4gPHNwYW4gY2xhc3M9ImNvdW50ZXIiPihbW2RhdGFzZXRzLmxlbmd0aF1dKTwvc3Bhbj4KICAgICAgICA8L2Rpdj4KICAgICAgICA8cGFwZXItZHJvcGRvd24tbWVudQogICAgICAgICAgbm8tbGFiZWwtZmxvYXQKICAgICAgICAgIG5vLWFuaW1hdGlvbnMKICAgICAgICAgIG5vaW5rCiAgICAgICAgICBob3Jpem9udGFsLWFsaWduPSJsZWZ0IgogICAgICAgICAgY2xhc3M9InJ1bi1kcm9wZG93biIKICAgICAgICA+CiAgICAgICAgICA8cGFwZXItbGlzdGJveAogICAgICAgICAgICBjbGFzcz0iZHJvcGRvd24tY29udGVudCIKICAgICAgICAgICAgc2VsZWN0ZWQ9Int7X3NlbGVjdGVkUnVuSW5kZXh9fSIKICAgICAgICAgICAgc2xvdD0iZHJvcGRvd24tY29udGVudCIKICAgICAgICAgID4KICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20tcmVwZWF0IiBpdGVtcz0iW1tkYXRhc2V0c11dIj4KICAgICAgICAgICAgICA8cGFwZXItaXRlbT5bW2l0ZW0ubmFtZV1dPC9wYXBlci1pdGVtPgogICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgPC9wYXBlci1saXN0Ym94PgogICAgICAgIDwvcGFwZXItZHJvcGRvd24tbWVudT4KICAgICAgPC9kaXY+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tzaG93U2Vzc2lvblJ1bnNEcm9wZG93bl1dIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250cm9sLWhvbGRlciB0YWdzLXJvdyI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJ0aXRsZSI+CiAgICAgICAgICAgIFRhZwogICAgICAgICAgICA8c3BhbiBjbGFzcz0iY291bnRlciIKICAgICAgICAgICAgICA+KFtbX251bVRhZ3MoZGF0YXNldHMsIF9zZWxlY3RlZFJ1bkluZGV4KV1dKTwvc3BhbgogICAgICAgICAgICA+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDxwYXBlci1kcm9wZG93bi1tZW51CiAgICAgICAgICAgIG5vLWxhYmVsLWZsb2F0CiAgICAgICAgICAgIG5vLWFuaW1hdGlvbnMKICAgICAgICAgICAgaG9yaXpvbnRhbC1hbGlnbj0ibGVmdCIKICAgICAgICAgICAgbm9pbmsKICAgICAgICAgICAgY2xhc3M9InJ1bi1kcm9wZG93biIKICAgICAgICAgID4KICAgICAgICAgICAgPHBhcGVyLWxpc3Rib3gKICAgICAgICAgICAgICBjbGFzcz0iZHJvcGRvd24tY29udGVudCIKICAgICAgICAgICAgICBzZWxlY3RlZD0ie3tfc2VsZWN0ZWRUYWdJbmRleH19IgogICAgICAgICAgICAgIHNsb3Q9ImRyb3Bkb3duLWNvbnRlbnQiCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8dGVtcGxhdGUKICAgICAgICAgICAgICAgIGlzPSJkb20tcmVwZWF0IgogICAgICAgICAgICAgICAgaXRlbXM9IltbX2dldFRhZ3MoZGF0YXNldHMsIF9zZWxlY3RlZFJ1bkluZGV4KV1dIgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDxwYXBlci1pdGVtPltbaXRlbS5kaXNwbGF5TmFtZV1dPC9wYXBlci1pdGVtPgogICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgIDwvcGFwZXItbGlzdGJveD4KICAgICAgICAgIDwvcGFwZXItZHJvcGRvd24tbWVudT4KICAgICAgICA8L2Rpdj4KICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPGRpdiBjbGFzcz0idGl0bGUiPkdyYXBoIHR5cGU8L2Rpdj4KICAgICAgPGRpdiBjbGFzcz0iY29udHJvbC1ob2xkZXIgY29udHJvbC1vcHRpb25zIj4KICAgICAgICA8cGFwZXItcmFkaW8tZ3JvdXAKICAgICAgICAgIHNlbGVjdGVkPSJ7e19zZWxlY3RlZEdyYXBoVHlwZX19IgogICAgICAgICAgb24tcGFwZXItcmFkaW8tZ3JvdXAtY2hhbmdlZD0iX29uR3JhcGhUeXBlQ2hhbmdlZEJ5VXNlckdlc3R1cmUiCiAgICAgICAgPgogICAgICAgICAgPCEtLSBOb3RlIHRoYXQgdGhlIG5hbWUgaGFzIHRvIG1hdGNoIHRoYXQgb2YgdGZfZ3JhcGhfY29tbW9uLlNlbGVjdGlvblR5cGUuIC0tPgogICAgICAgICAgPHBhcGVyLXJhZGlvLWJ1dHRvbgogICAgICAgICAgICBuYW1lPSJvcF9ncmFwaCIKICAgICAgICAgICAgZGlzYWJsZWQ9IltbX2dldFNlbGVjdGlvbk9wR3JhcGhEaXNhYmxlZChkYXRhc2V0cywgX3NlbGVjdGVkUnVuSW5kZXgsIF9zZWxlY3RlZFRhZ0luZGV4KV1dIgogICAgICAgICAgICA+T3AgZ3JhcGg8L3BhcGVyLXJhZGlvLWJ1dHRvbgogICAgICAgICAgPgogICAgICAgICAgPHBhcGVyLXJhZGlvLWJ1dHRvbgogICAgICAgICAgICBuYW1lPSJjb25jZXB0dWFsX2dyYXBoIgogICAgICAgICAgICBkaXNhYmxlZD0iW1tfZ2V0U2VsZWN0aW9uQ29uY2VwdHVhbEdyYXBoRGlzYWJsZWQoZGF0YXNldHMsIF9zZWxlY3RlZFJ1bkluZGV4LCBfc2VsZWN0ZWRUYWdJbmRleCldXSIKICAgICAgICAgICAgPkNvbmNlcHR1YWwgZ3JhcGg8L3BhcGVyLXJhZGlvLWJ1dHRvbgogICAgICAgICAgPgogICAgICAgICAgPHBhcGVyLXJhZGlvLWJ1dHRvbgogICAgICAgICAgICBuYW1lPSJwcm9maWxlIgogICAgICAgICAgICBkaXNhYmxlZD0iW1tfZ2V0U2VsZWN0aW9uUHJvZmlsZURpc2FibGVkKGRhdGFzZXRzLCBfc2VsZWN0ZWRSdW5JbmRleCwgX3NlbGVjdGVkVGFnSW5kZXgpXV0iCiAgICAgICAgICAgID5Qcm9maWxlPC9wYXBlci1yYWRpby1idXR0b24KICAgICAgICAgID4KICAgICAgICA8L3BhcGVyLXJhZGlvLWdyb3VwPgogICAgICA8L2Rpdj4KICAgICAgPGRpdiBjbGFzcz0idGl0bGUiPk5vZGUgb3B0aW9uczwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJjb250cm9sLWhvbGRlciBjb250cm9sLW9wdGlvbnMiPgogICAgICAgIDxwYXBlci10b2dnbGUtYnV0dG9uCiAgICAgICAgICBjaGVja2VkPSJ7e3RyYWNlSW5wdXRzfX0iCiAgICAgICAgICBvbi1jaGFuZ2U9Il9vblRyYWNlSW5wdXRzQ2hhbmdlZEJ5VXNlckdlc3R1cmUiCiAgICAgICAgPgogICAgICAgICAgVHJhY2UgaW5wdXRzCiAgICAgICAgPC9wYXBlci10b2dnbGUtYnV0dG9uPgogICAgICAgIDxwYXBlci10b2dnbGUtYnV0dG9uIGNoZWNrZWQ9Int7YXV0b0V4dHJhY3ROb2Rlc319Ij4KICAgICAgICAgIEF1dG8tZXh0cmFjdCBoaWdoLWRlZ3JlZSBub2RlcwogICAgICAgIDwvcGFwZXItdG9nZ2xlLWJ1dHRvbj4KICAgICAgPC9kaXY+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1toZWFsdGhQaWxsc0ZlYXR1cmVFbmFibGVkXV0iPgogICAgICAgIDxkaXYgY2xhc3M9ImNvbnRyb2wtaG9sZGVyIj4KICAgICAgICAgIDxwYXBlci10b2dnbGUtYnV0dG9uIGNoZWNrZWQ9Int7aGVhbHRoUGlsbHNUb2dnbGVkT259fSIKICAgICAgICAgICAgPlNob3cgaGVhbHRoIHBpbGxzPC9wYXBlci10b2dnbGUtYnV0dG9uCiAgICAgICAgICA+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDxkaXYgY2xhc3M9InRpdGxlIj5Db2xvciBieTwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJjb250cm9sLWhvbGRlciBjb250cm9sLW9wdGlvbnMiPgogICAgICAgIDxwYXBlci1yYWRpby1ncm91cAogICAgICAgICAgc2VsZWN0ZWQ9Int7Y29sb3JCeX19IgogICAgICAgICAgb24tcGFwZXItcmFkaW8tZ3JvdXAtY2hhbmdlZD0iX29uQ29sb3JCeUNoYW5nZWRCeVVzZXJHZXN0dXJlIgogICAgICAgID4KICAgICAgICAgIDxwYXBlci1yYWRpby1idXR0b24gbmFtZT0iW1tDb2xvckJ5Lk5PTkVdXSI+Tm9uZTwvcGFwZXItcmFkaW8tYnV0dG9uPgoKICAgICAgICAgIDxwYXBlci1yYWRpby1idXR0b24gbmFtZT0iW1tDb2xvckJ5LlNUUlVDVFVSRV1dIgogICAgICAgICAgICA+U3RydWN0dXJlPC9wYXBlci1yYWRpby1idXR0b24KICAgICAgICAgID4KCiAgICAgICAgICA8cGFwZXItcmFkaW8tYnV0dG9uIG5hbWU9IltbQ29sb3JCeS5ERVZJQ0VdXSIKICAgICAgICAgICAgPkRldmljZTwvcGFwZXItcmFkaW8tYnV0dG9uCiAgICAgICAgICA+CgogICAgICAgICAgPHBhcGVyLXJhZGlvLWJ1dHRvbgogICAgICAgICAgICBpZD0ieGxhLWNsdXN0ZXItcmFkaW8tYnV0dG9uIgogICAgICAgICAgICBuYW1lPSJbW0NvbG9yQnkuWExBX0NMVVNURVJdXSIKICAgICAgICAgICAgZGlzYWJsZWQ9IltbIV94bGFDbHVzdGVyc1Byb3ZpZGVkKHJlbmRlckhpZXJhcmNoeSldXSIKICAgICAgICAgID4KICAgICAgICAgICAgWExBIGNsdXN0ZXIKICAgICAgICAgIDwvcGFwZXItcmFkaW8tYnV0dG9uPgogICAgICAgICAgPHBhcGVyLXRvb2x0aXAKICAgICAgICAgICAgYW5pbWF0aW9uLWRlbGF5PSIwIgogICAgICAgICAgICBmb3I9InhsYS1jbHVzdGVyLXJhZGlvLWJ1dHRvbiIKICAgICAgICAgICAgcG9zaXRpb249InJpZ2h0IgogICAgICAgICAgICBvZmZzZXQ9IjAiCiAgICAgICAgICA+CiAgICAgICAgICAgIENvbG9yaW5nIGJ5IFhMQSBjbHVzdGVyIGlzIG9ubHkgZW5hYmxlZCBpZiBhdCBsZWFzdCAxIG9wIHNwZWNpZmllcwogICAgICAgICAgICBhbiBYTEEgY2x1c3Rlci4KICAgICAgICAgIDwvcGFwZXItdG9vbHRpcD4KCiAgICAgICAgICA8cGFwZXItcmFkaW8tYnV0dG9uCiAgICAgICAgICAgIGlkPSJjb21wdXRlLXRpbWUtcmFkaW8tYnV0dG9uIgogICAgICAgICAgICBuYW1lPSJbW0NvbG9yQnkuQ09NUFVURV9USU1FXV0iCiAgICAgICAgICAgIGRpc2FibGVkPSJbWyFzdGF0c11dIgogICAgICAgICAgPgogICAgICAgICAgICBDb21wdXRlIHRpbWUKICAgICAgICAgIDwvcGFwZXItcmFkaW8tYnV0dG9uPgogICAgICAgICAgPHBhcGVyLXRvb2x0aXAKICAgICAgICAgICAgYW5pbWF0aW9uLWRlbGF5PSIwIgogICAgICAgICAgICBmb3I9ImNvbXB1dGUtdGltZS1yYWRpby1idXR0b24iCiAgICAgICAgICAgIHBvc2l0aW9uPSJyaWdodCIKICAgICAgICAgICAgb2Zmc2V0PSIwIgogICAgICAgICAgPgogICAgICAgICAgICBDb2xvcmluZyBieSBjb21wdXRlIHRpbWUgaXMgb25seSBlbmFibGVkIGlmIHRoZSBSdW5NZXRhZGF0YSBwcm90byBpcwogICAgICAgICAgICBwYXNzZWQgdG8gdGhlIEZpbGVXcml0ZXIgd2hlbiBhIHNwZWNpZmljIHNlc3Npb24gaXMgcnVuLgogICAgICAgICAgPC9wYXBlci10b29sdGlwPgoKICAgICAgICAgIDxwYXBlci1yYWRpby1idXR0b24KICAgICAgICAgICAgaWQ9Im1lbW9yeS1yYWRpby1idXR0b24iCiAgICAgICAgICAgIG5hbWU9IltbQ29sb3JCeS5NRU1PUlldXSIKICAgICAgICAgICAgZGlzYWJsZWQ9IltbIXN0YXRzXV0iCiAgICAgICAgICA+CiAgICAgICAgICAgIE1lbW9yeQogICAgICAgICAgPC9wYXBlci1yYWRpby1idXR0b24+CiAgICAgICAgICA8cGFwZXItdG9vbHRpcAogICAgICAgICAgICBhbmltYXRpb24tZGVsYXk9IjAiCiAgICAgICAgICAgIGZvcj0ibWVtb3J5LXJhZGlvLWJ1dHRvbiIKICAgICAgICAgICAgcG9zaXRpb249InJpZ2h0IgogICAgICAgICAgICBvZmZzZXQ9IjAiCiAgICAgICAgICA+CiAgICAgICAgICAgIENvbG9yaW5nIGJ5IG1lbW9yeSBpcyBvbmx5IGVuYWJsZWQgaWYgdGhlIFJ1bk1ldGFkYXRhIHByb3RvIGlzCiAgICAgICAgICAgIHBhc3NlZCB0byB0aGUgRmlsZVdyaXRlciB3aGVuIGEgc3BlY2lmaWMgc2Vzc2lvbiBpcyBydW4uCiAgICAgICAgICA8L3BhcGVyLXRvb2x0aXA+CgogICAgICAgICAgPHBhcGVyLXJhZGlvLWJ1dHRvbgogICAgICAgICAgICBpZD0idHB1LWNvbXBhdGliaWxpdHktcmFkaW8tYnV0dG9uIgogICAgICAgICAgICBuYW1lPSJbW0NvbG9yQnkuT1BfQ09NUEFUSUJJTElUWV1dIgogICAgICAgICAgPgogICAgICAgICAgICBUUFUgY29tcGF0aWJpbGl0eQogICAgICAgICAgPC9wYXBlci1yYWRpby1idXR0b24+CiAgICAgICAgICA8cGFwZXItdG9vbHRpcAogICAgICAgICAgICBhbmltYXRpb24tZGVsYXk9IjAiCiAgICAgICAgICAgIGZvcj0idHB1LWNvbXBhdGliaWxpdHktcmFkaW8tYnV0dG9uIgogICAgICAgICAgICBwb3NpdGlvbj0icmlnaHQiCiAgICAgICAgICAgIG9mZnNldD0iMCIKICAgICAgICAgID4KICAgICAgICAgICAgQ29sb3JpbmcgYnkgd2hldGhlciBhbiBvcGVyYXRpb24gaXMgY29tcGF0aWJsZSBmb3IgdGhlIFRQVSBkZXZpY2UuCiAgICAgICAgICA8L3BhcGVyLXRvb2x0aXA+CiAgICAgICAgPC9wYXBlci1yYWRpby1ncm91cD4KICAgICAgICA8c3BhbiBjbGFzcz0ic3BhY2VyIj48L3NwYW4+CiAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgICA8ZGl2IGNsYXNzPSJsZWdlbmQtaG9sZGVyIj4KICAgICAgPGJ1dHRvbiBjbGFzcz0ibGVnZW5kLXRvb2xiYXIiIG9uLWNsaWNrPSJfdG9nZ2xlTGVnZW5kT3BlbiI+CiAgICAgICAgPHNwYW4gY2xhc3M9InRvZ2dsZS1sZWdlbmQtdGV4dCI+TGVnZW5kPC9zcGFuPgogICAgICAgIDxpcm9uLWljb24KICAgICAgICAgIGljb249IltbX2dldFRvZ2dsZUxlZ2VuZEljb24oX2xlZ2VuZE9wZW5lZCldXSIKICAgICAgICAgIGNsYXNzPSJ0b2dnbGUtbGVnZW5kLWJ1dHRvbiIKICAgICAgICA+CiAgICAgICAgPC9pcm9uLWljb24+CiAgICAgIDwvYnV0dG9uPgogICAgICA8aXJvbi1jb2xsYXBzZSBvcGVuZWQ9IltbX2xlZ2VuZE9wZW5lZF1dIiBjbGFzcz0ibGVnZW5kLWNvbnRlbnQiPgogICAgICAgIDwhLS0gQ29sb3ItbW9kZS1zcGVjaWZpYyBsZWdlbmQgaXRlbXMgLS0+CiAgICAgICAgPGRpdj4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfaXNHcmFkaWVudENvbG9yaW5nKHN0YXRzLCBjb2xvckJ5KV1dIj4KICAgICAgICAgICAgPHN2ZyB3aWR0aD0iMTQwIiBoZWlnaHQ9IjIwIiBjbGFzcz0iY29sb3ItdGV4dCBncmFkaWVudC1jb250YWluZXIiPgogICAgICAgICAgICAgIDxkZWZzPgogICAgICAgICAgICAgICAgPGxpbmVhckdyYWRpZW50CiAgICAgICAgICAgICAgICAgIGlkPSJsaW5lYXJHcmFkaWVudCIKICAgICAgICAgICAgICAgICAgeDE9IjAlIgogICAgICAgICAgICAgICAgICB5MT0iMCUiCiAgICAgICAgICAgICAgICAgIHgyPSIxMDAlIgogICAgICAgICAgICAgICAgICB5Mj0iMCUiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDxzdG9wCiAgICAgICAgICAgICAgICAgICAgY2xhc3M9InN0YXJ0IgogICAgICAgICAgICAgICAgICAgIG9mZnNldD0iMCUiCiAgICAgICAgICAgICAgICAgICAgc3RvcC1jb2xvciQ9IltbX2N1cnJlbnRHcmFkaWVudFBhcmFtcy5zdGFydENvbG9yXV0iCiAgICAgICAgICAgICAgICAgID48L3N0b3A+CiAgICAgICAgICAgICAgICAgIDxzdG9wCiAgICAgICAgICAgICAgICAgICAgY2xhc3M9ImVuZCIKICAgICAgICAgICAgICAgICAgICBvZmZzZXQ9IjEwMCUiCiAgICAgICAgICAgICAgICAgICAgc3RvcC1jb2xvciQ9IltbX2N1cnJlbnRHcmFkaWVudFBhcmFtcy5lbmRDb2xvcl1dIgogICAgICAgICAgICAgICAgICA+PC9zdG9wPgogICAgICAgICAgICAgICAgPC9saW5lYXJHcmFkaWVudD4KICAgICAgICAgICAgICA8L2RlZnM+CiAgICAgICAgICAgICAgPHJlY3QKICAgICAgICAgICAgICAgIHg9IjAiCiAgICAgICAgICAgICAgICB5PSIwIgogICAgICAgICAgICAgICAgd2lkdGg9IjEzNSIKICAgICAgICAgICAgICAgIGhlaWdodD0iMjAiCiAgICAgICAgICAgICAgICBmaWxsPSJ1cmwoI2xpbmVhckdyYWRpZW50KSIKICAgICAgICAgICAgICAgIHN0cm9rZT0iYmxhY2siCiAgICAgICAgICAgICAgPjwvcmVjdD4KICAgICAgICAgICAgPC9zdmc+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImRvbWFpblZhbHVlcyBjb2xvci10ZXh0Ij4KICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJkb21haW5TdGFydCI+W1tfY3VycmVudEdyYWRpZW50UGFyYW1zLm1pblZhbHVlXV08L2Rpdj4KICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJkb21haW5FbmQiPltbX2N1cnJlbnRHcmFkaWVudFBhcmFtcy5tYXhWYWx1ZV1dPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8YnIgc3R5bGU9ImNsZWFyOiBib3RoIiAvPgogICAgICAgICAgICA8ZGl2PkRldmljZXMgaW5jbHVkZWQgaW4gc3RhdHM6PC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImRldmljZUxpc3QiPgogICAgICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9IltbX2N1cnJlbnREZXZpY2VzXV0iPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29sb3ItbGVnZW5kLXJvdyBkZXZpY2VzLWNoZWNrYm94Ij4KICAgICAgICAgICAgICAgICAgPHNwYW4KICAgICAgICAgICAgICAgICAgICA+PGlucHV0CiAgICAgICAgICAgICAgICAgICAgICB0eXBlPSJjaGVja2JveCIKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlJD0iW1tpdGVtLmRldmljZV1dIgogICAgICAgICAgICAgICAgICAgICAgY2hlY2tlZCQ9IltbaXRlbS51c2VkXV0iCiAgICAgICAgICAgICAgICAgICAgICBvbi1jbGljaz0iX2RldmljZUNoZWNrYm94Q2xpY2tlZCIKICAgICAgICAgICAgICAgICAgLz48L3NwYW4+CiAgICAgICAgICAgICAgICAgIDxzcGFuPltbaXRlbS5zdWZmaXhdXTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW2l0ZW0uaWdub3JlZE1zZ11dIj4KICAgICAgICAgICAgICAgICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICAgICAgICAgICAgICAgIGljb249ImhlbHAiCiAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0iaGVscC1pY29uIgogICAgICAgICAgICAgICAgICAgID48L3BhcGVyLWljb24tYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDxwYXBlci10b29sdGlwCiAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbj0icmlnaHQiCiAgICAgICAgICAgICAgICAgICAgICBvZmZzZXQ9IjAiCiAgICAgICAgICAgICAgICAgICAgICBhbmltYXRpb24tZGVsYXk9IjAiCiAgICAgICAgICAgICAgICAgICAgICA+W1tpdGVtLmlnbm9yZWRNc2ddXTwvcGFwZXItdG9vbHRpcAogICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfZXF1YWxzKGNvbG9yQnksICdzdHJ1Y3R1cmUnKV1dIj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29sb3ItdGV4dCI+CiAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29sb3ItbGVnZW5kLXJvdyI+CiAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiZWwiPiBjb2xvcnMgPC9zcGFuPgogICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImNvbG9yLWxlZ2VuZC12YWx1ZSI+c2FtZSBzdWJzdHJ1Y3R1cmU8L3NwYW4+CiAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29sb3ItbGVnZW5kLXJvdyI+CiAgICAgICAgICAgICAgICA8dGYtZ3JhcGgtaWNvbgogICAgICAgICAgICAgICAgICB0eXBlPSJNRVRBIgogICAgICAgICAgICAgICAgICBoZWlnaHQ9IjE2IgogICAgICAgICAgICAgICAgICBmaWxsLW92ZXJyaWRlPSIjZWVlIgogICAgICAgICAgICAgICAgICBzdHJva2Utb3ZlcnJpZGU9IiNhNmE2YTYiCiAgICAgICAgICAgICAgICA+PC90Zi1ncmFwaC1pY29uPgogICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImNvbG9yLWxlZ2VuZC12YWx1ZSI+dW5pcXVlIHN1YnN0cnVjdHVyZTwvc3Bhbj4KICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19lcXVhbHMoY29sb3JCeSwgJ2RldmljZScpXV0iPgogICAgICAgICAgICA8ZGl2PgogICAgICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9IltbX2N1cnJlbnREZXZpY2VQYXJhbXNdXSI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb2xvci1sZWdlbmQtcm93Ij4KICAgICAgICAgICAgICAgICAgPHRmLWdyYXBoLWljb24KICAgICAgICAgICAgICAgICAgICB0eXBlPSJNRVRBIgogICAgICAgICAgICAgICAgICAgIGhlaWdodD0iMTYiCiAgICAgICAgICAgICAgICAgICAgZmlsbC1vdmVycmlkZT0iW1tpdGVtLmNvbG9yXV0iCiAgICAgICAgICAgICAgICAgICAgc3Ryb2tlLW92ZXJyaWRlPSIjYTZhNmE2IgogICAgICAgICAgICAgICAgICA+PC90Zi1ncmFwaC1pY29uPgogICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0iY29sb3ItbGVnZW5kLXZhbHVlIj5bW2l0ZW0uZGV2aWNlXV08L3NwYW4+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbG9yLWxlZ2VuZC1yb3ciPgogICAgICAgICAgICAgICAgPHRmLWdyYXBoLWljb24KICAgICAgICAgICAgICAgICAgdHlwZT0iTUVUQSIKICAgICAgICAgICAgICAgICAgaGVpZ2h0PSIxNiIKICAgICAgICAgICAgICAgICAgZmlsbC1vdmVycmlkZT0iI2VlZSIKICAgICAgICAgICAgICAgICAgc3Ryb2tlLW92ZXJyaWRlPSIjYTZhNmE2IgogICAgICAgICAgICAgICAgPjwvdGYtZ3JhcGgtaWNvbj4KICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJjb2xvci1sZWdlbmQtdmFsdWUiPnVua25vd24gZGV2aWNlPC9zcGFuPgogICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2VxdWFscyhjb2xvckJ5LCAneGxhX2NsdXN0ZXInKV1dIj4KICAgICAgICAgICAgPGRpdj4KICAgICAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19jdXJyZW50WGxhQ2x1c3RlclBhcmFtc11dIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbG9yLWxlZ2VuZC1yb3ciPgogICAgICAgICAgICAgICAgICA8c3ZnPgogICAgICAgICAgICAgICAgICAgIDx1c2UKICAgICAgICAgICAgICAgICAgICAgIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIgogICAgICAgICAgICAgICAgICAgICAgeGxpbms6aHJlZj0iI3VuZmlsbGVkLXJlY3QiCiAgICAgICAgICAgICAgICAgICAgICB4PSIwIgogICAgICAgICAgICAgICAgICAgICAgeT0iMCIKICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJmaWxsOltbaXRlbS5jb2xvcl1dIgogICAgICAgICAgICAgICAgICAgID48L3VzZT4KICAgICAgICAgICAgICAgICAgPC9zdmc+CiAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJjb2xvci1sZWdlbmQtdmFsdWUiPltbaXRlbS54bGFfY2x1c3Rlcl1dPC9zcGFuPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb2xvci1sZWdlbmQtcm93Ij4KICAgICAgICAgICAgICAgIDxzdmc+CiAgICAgICAgICAgICAgICAgIDx1c2UKICAgICAgICAgICAgICAgICAgICB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIKICAgICAgICAgICAgICAgICAgICB4bGluazpocmVmPSIjZ3JleS1yZWN0IgogICAgICAgICAgICAgICAgICAgIHg9IjAiCiAgICAgICAgICAgICAgICAgICAgeT0iMCIKICAgICAgICAgICAgICAgICAgPjwvdXNlPgogICAgICAgICAgICAgICAgPC9zdmc+CiAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0iY29sb3ItbGVnZW5kLXZhbHVlIj51bmtub3duIFhMQSBjbHVzdGVyPC9zcGFuPgogICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2VxdWFscyhjb2xvckJ5LCAnb3BfY29tcGF0aWJpbGl0eScpXV0iPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb2xvci10ZXh0Ij4KICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb2xvci1sZWdlbmQtcm93Ij4KICAgICAgICAgICAgICAgIDx0Zi1ncmFwaC1pY29uCiAgICAgICAgICAgICAgICAgIHR5cGU9Ik9QIgogICAgICAgICAgICAgICAgICBoZWlnaHQ9IjE2IgogICAgICAgICAgICAgICAgICBmaWxsLW92ZXJyaWRlPSIjMGY5ZDU4IgogICAgICAgICAgICAgICAgICBzdHJva2Utb3ZlcnJpZGU9IiNjY2MiCiAgICAgICAgICAgICAgICA+PC90Zi1ncmFwaC1pY29uPgogICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImNvbG9yLWxlZ2VuZC12YWx1ZSI+VmFsaWQgT3A8L3NwYW4+CiAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29sb3ItbGVnZW5kLXJvdyI+CiAgICAgICAgICAgICAgICA8dGYtZ3JhcGgtaWNvbgogICAgICAgICAgICAgICAgICB0eXBlPSJPUCIKICAgICAgICAgICAgICAgICAgaGVpZ2h0PSIxNiIKICAgICAgICAgICAgICAgICAgZmlsbC1vdmVycmlkZT0iI2RiNDQzNyIKICAgICAgICAgICAgICAgICAgc3Ryb2tlLW92ZXJyaWRlPSIjY2NjIgogICAgICAgICAgICAgICAgPjwvdGYtZ3JhcGgtaWNvbj4KICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJjb2xvci1sZWdlbmQtdmFsdWUiPkludmFsaWQgT3A8L3NwYW4+CiAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfc3RhdHNOb3ROdWxsKHN0YXRzKV1dIj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29sb3ItbGVnZW5kLXJvdyI+CiAgICAgICAgICAgICAgPHRmLWdyYXBoLWljb24gdHlwZT0iTUVUQSIgaGVpZ2h0PSIxNiIgZmFkZWQ+PC90Zi1ncmFwaC1pY29uPgogICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJjb2xvci1sZWdlbmQtdmFsdWUiPnVudXNlZCBzdWJzdHJ1Y3R1cmU8L3NwYW4+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L2Rpdj4KCiAgICAgICAgPCEtLSBDb21tb24gbGVnZW5kIGl0ZW1zIC0tPgogICAgICAgIDxkaXY+CiAgICAgICAgICA8dGFibGU+CiAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICA8dGQ+PC90ZD4KICAgICAgICAgICAgICAgIDx0ZD4oKiA9IGV4cGFuZGFibGUpPC90ZD4KICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgPHRmLWdyYXBoLWljb24KICAgICAgICAgICAgICAgICAgICB0eXBlPSJNRVRBIgogICAgICAgICAgICAgICAgICAgIGhlaWdodD0iMTYiCiAgICAgICAgICAgICAgICAgICAgZmlsbC1vdmVycmlkZT0iI2Q5ZDlkOSIKICAgICAgICAgICAgICAgICAgICBzdHJva2Utb3ZlcnJpZGU9IiNjY2MiCiAgICAgICAgICAgICAgICAgID48L3RmLWdyYXBoLWljb24+CiAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgICBOYW1lc3BhY2U8c3BhbiBjbGFzcz0iZ3JheSI+Kjwvc3Bhbj4KICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGVnZW5kLWNsYXJpZmllciI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4+Pzwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8cGFwZXItdG9vbHRpcAogICAgICAgICAgICAgICAgICAgICAgYW5pbWF0aW9uLWRlbGF5PSIwIgogICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb249InJpZ2h0IgogICAgICAgICAgICAgICAgICAgICAgb2Zmc2V0PSIwIgogICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgIEVuY2Fwc3VsYXRlcyBhIHNldCBvZiBub2Rlcy4gTmFtZXNwYWNlIGlzIGhpZXJhcmNoaWNhbCBhbmQKICAgICAgICAgICAgICAgICAgICAgIGJhc2VkIG9uIHNjb3BlLgogICAgICAgICAgICAgICAgICAgIDwvcGFwZXItdG9vbHRpcD4KICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgICA8dGYtZ3JhcGgtaWNvbiB0eXBlPSJPUCIgaGVpZ2h0PSIxNiI+PC90Zi1ncmFwaC1pY29uPgogICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgT3BOb2RlCiAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxlZ2VuZC1jbGFyaWZpZXIiPgogICAgICAgICAgICAgICAgICAgIDxzcGFuPj88L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgPHBhcGVyLXRvb2x0aXAKICAgICAgICAgICAgICAgICAgICAgIGFuaW1hdGlvbi1kZWxheT0iMCIKICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uPSJyaWdodCIKICAgICAgICAgICAgICAgICAgICAgIG9mZnNldD0iMCIKICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICBOb2RlIHRoYXQgcGVyZm9ybXMgYW4gb3BlcmF0aW9uLiBUaGVzZSBub2RlcyBjYW5ub3QKICAgICAgICAgICAgICAgICAgICAgIGV4cGFuZC4KICAgICAgICAgICAgICAgICAgICA8L3BhcGVyLXRvb2x0aXA+CiAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgPHRmLWdyYXBoLWljb24gdHlwZT0iU0VSSUVTIiBoZWlnaHQ9IjE2Ij48L3RmLWdyYXBoLWljb24+CiAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgICBVbmNvbm5lY3RlZCBzZXJpZXM8c3BhbiBjbGFzcz0iZ3JheSI+Kjwvc3Bhbj4KICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGVnZW5kLWNsYXJpZmllciI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4+Pzwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8cGFwZXItdG9vbHRpcAogICAgICAgICAgICAgICAgICAgICAgYW5pbWF0aW9uLWRlbGF5PSIwIgogICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb249InJpZ2h0IgogICAgICAgICAgICAgICAgICAgICAgb2Zmc2V0PSIwIgogICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgIFNlcXVlbmNlIG9mIG51bWJlcmVkIG5vZGVzIHRoYXQgYXJlIG5vdCBjb25uZWN0ZWQgdG8gZWFjaAogICAgICAgICAgICAgICAgICAgICAgb3RoZXIuCiAgICAgICAgICAgICAgICAgICAgPC9wYXBlci10b29sdGlwPgogICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgIDx0Zi1ncmFwaC1pY29uCiAgICAgICAgICAgICAgICAgICAgdHlwZT0iU0VSSUVTIgogICAgICAgICAgICAgICAgICAgIGhlaWdodD0iMTYiCiAgICAgICAgICAgICAgICAgICAgdmVydGljYWwKICAgICAgICAgICAgICAgICAgPjwvdGYtZ3JhcGgtaWNvbj4KICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgIENvbm5lY3RlZCBzZXJpZXM8c3BhbiBjbGFzcz0iZ3JheSI+Kjwvc3Bhbj4KICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGVnZW5kLWNsYXJpZmllciI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4+Pzwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8cGFwZXItdG9vbHRpcAogICAgICAgICAgICAgICAgICAgICAgYW5pbWF0aW9uLWRlbGF5PSIwIgogICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb249InJpZ2h0IgogICAgICAgICAgICAgICAgICAgICAgb2Zmc2V0PSIwIgogICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgIFNlcXVlbmNlIG9mIG51bWJlcmVkIG5vZGVzIHRoYXQgYXJlIGNvbm5lY3RlZCB0byBlYWNoCiAgICAgICAgICAgICAgICAgICAgICBvdGhlci4KICAgICAgICAgICAgICAgICAgICA8L3BhcGVyLXRvb2x0aXA+CiAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgPHN2ZyBjbGFzcz0iaWNvbiI+CiAgICAgICAgICAgICAgICAgICAgPGNpcmNsZQogICAgICAgICAgICAgICAgICAgICAgZmlsbD0id2hpdGUiCiAgICAgICAgICAgICAgICAgICAgICBzdHJva2U9IiM4NDg0ODQiCiAgICAgICAgICAgICAgICAgICAgICBjeD0iMTAiCiAgICAgICAgICAgICAgICAgICAgICBjeT0iMTAiCiAgICAgICAgICAgICAgICAgICAgICByPSI1IgogICAgICAgICAgICAgICAgICAgID48L2NpcmNsZT4KICAgICAgICAgICAgICAgICAgPC9zdmc+CiAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgICBDb25zdGFudAogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsZWdlbmQtY2xhcmlmaWVyIj4KICAgICAgICAgICAgICAgICAgICA8c3Bhbj4/PC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDxwYXBlci10b29sdGlwCiAgICAgICAgICAgICAgICAgICAgICBhbmltYXRpb24tZGVsYXk9IjAiCiAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbj0icmlnaHQiCiAgICAgICAgICAgICAgICAgICAgICBvZmZzZXQ9IjAiCiAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgTm9kZSB0aGF0IG91dHB1dHMgYSBjb25zdGFudCB2YWx1ZS4KICAgICAgICAgICAgICAgICAgICA8L3BhcGVyLXRvb2x0aXA+CiAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgPHRmLWdyYXBoLWljb24gdHlwZT0iU1VNTUFSWSIgaGVpZ2h0PSIyMCI+PC90Zi1ncmFwaC1pY29uPgogICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgU3VtbWFyeQogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsZWdlbmQtY2xhcmlmaWVyIj4KICAgICAgICAgICAgICAgICAgICA8c3Bhbj4/PC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDxwYXBlci10b29sdGlwCiAgICAgICAgICAgICAgICAgICAgICBhbmltYXRpb24tZGVsYXk9IjAiCiAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbj0icmlnaHQiCiAgICAgICAgICAgICAgICAgICAgICBvZmZzZXQ9IjAiCiAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgTm9kZSB0aGF0IGNvbGxlY3RzIGRhdGEgZm9yIHZpc3VhbGl6YXRpb24gd2l0aGluCiAgICAgICAgICAgICAgICAgICAgICBUZW5zb3JCb2FyZC4KICAgICAgICAgICAgICAgICAgICA8L3BhcGVyLXRvb2x0aXA+CiAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgPHN2ZwogICAgICAgICAgICAgICAgICAgIGNsYXNzPSJpY29uIgogICAgICAgICAgICAgICAgICAgIGhlaWdodD0iMTVweCIKICAgICAgICAgICAgICAgICAgICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pZCBtZWV0IgogICAgICAgICAgICAgICAgICAgIHZpZXdCb3g9IjAgMCAxNSAxNSIKICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgIDxkZWZzPgogICAgICAgICAgICAgICAgICAgICAgPG1hcmtlcgogICAgICAgICAgICAgICAgICAgICAgICBpZD0iZGF0YWZsb3ctYXJyb3doZWFkLWxlZ2VuZCIKICAgICAgICAgICAgICAgICAgICAgICAgZmlsbD0iI2JiYiIKICAgICAgICAgICAgICAgICAgICAgICAgbWFya2VyV2lkdGg9IjEwIgogICAgICAgICAgICAgICAgICAgICAgICBtYXJrZXJIZWlnaHQ9IjEwIgogICAgICAgICAgICAgICAgICAgICAgICByZWZYPSI5IgogICAgICAgICAgICAgICAgICAgICAgICByZWZZPSI1IgogICAgICAgICAgICAgICAgICAgICAgICBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSIKICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTSAwLDAgTCAxMCw1IEwgMCwxMCBDIDMsNyAzLDMgMCwwIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgICA8L21hcmtlcj4KICAgICAgICAgICAgICAgICAgICA8L2RlZnM+CiAgICAgICAgICAgICAgICAgICAgPHBhdGgKICAgICAgICAgICAgICAgICAgICAgIG1hcmtlci1lbmQ9InVybCgjZGF0YWZsb3ctYXJyb3doZWFkLWxlZ2VuZCkiCiAgICAgICAgICAgICAgICAgICAgICBzdHJva2U9IiNiYmIiCiAgICAgICAgICAgICAgICAgICAgICBkPSJNMiA5IGwgMjkgMCIKICAgICAgICAgICAgICAgICAgICAgIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICAgICAgICAgICAgICAgICAgICA+PC9wYXRoPgogICAgICAgICAgICAgICAgICA8L3N2Zz4KICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgIERhdGFmbG93IGVkZ2UKICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGVnZW5kLWNsYXJpZmllciI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4+Pzwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8cGFwZXItdG9vbHRpcAogICAgICAgICAgICAgICAgICAgICAgYW5pbWF0aW9uLWRlbGF5PSIwIgogICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb249InJpZ2h0IgogICAgICAgICAgICAgICAgICAgICAgb2Zmc2V0PSIwIgogICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgIEVkZ2Ugc2hvd2luZyB0aGUgZGF0YSBmbG93IGJldHdlZW4gb3BlcmF0aW9ucy4gRWRnZXMgZmxvdwogICAgICAgICAgICAgICAgICAgICAgdXB3YXJkcyB1bmxlc3MgYXJyb3doZWFkcyBzcGVjaWZ5IG90aGVyd2lzZS4KICAgICAgICAgICAgICAgICAgICA8L3BhcGVyLXRvb2x0aXA+CiAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgPHN2ZwogICAgICAgICAgICAgICAgICAgIGNsYXNzPSJpY29uIgogICAgICAgICAgICAgICAgICAgIGhlaWdodD0iMTVweCIKICAgICAgICAgICAgICAgICAgICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pZCBtZWV0IgogICAgICAgICAgICAgICAgICAgIHZpZXdCb3g9IjAgMCAxNSAxNSIKICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgIDxwYXRoCiAgICAgICAgICAgICAgICAgICAgICBzdHJva2U9IiNiYmIiCiAgICAgICAgICAgICAgICAgICAgICBkPSJNMiA5IGwgMjkgMCIKICAgICAgICAgICAgICAgICAgICAgIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIKICAgICAgICAgICAgICAgICAgICAgIHN0cm9rZS1kYXNoYXJyYXk9IjIsIDIiCiAgICAgICAgICAgICAgICAgICAgPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgPC9zdmc+CiAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgICBDb250cm9sIGRlcGVuZGVuY3kgZWRnZQogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsZWdlbmQtY2xhcmlmaWVyIj4KICAgICAgICAgICAgICAgICAgICA8c3Bhbj4/PC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDxwYXBlci10b29sdGlwCiAgICAgICAgICAgICAgICAgICAgICBhbmltYXRpb24tZGVsYXk9IjAiCiAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbj0icmlnaHQiCiAgICAgICAgICAgICAgICAgICAgICBvZmZzZXQ9IjAiCiAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgRWRnZSBzaG93aW5nIHRoZSBjb250cm9sIGRlcGVuZGVuY3kgYmV0d2VlbiBvcGVyYXRpb25zLgogICAgICAgICAgICAgICAgICAgIDwvcGFwZXItdG9vbHRpcD4KICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgICA8c3ZnCiAgICAgICAgICAgICAgICAgICAgY2xhc3M9Imljb24iCiAgICAgICAgICAgICAgICAgICAgaGVpZ2h0PSIxNXB4IgogICAgICAgICAgICAgICAgICAgIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWlkIG1lZXQiCiAgICAgICAgICAgICAgICAgICAgdmlld0JveD0iMCAwIDE1IDE1IgogICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgPGRlZnM+CiAgICAgICAgICAgICAgICAgICAgICA8bWFya2VyCiAgICAgICAgICAgICAgICAgICAgICAgIGlkPSJyZWZlcmVuY2UtYXJyb3doZWFkLWxlZ2VuZCIKICAgICAgICAgICAgICAgICAgICAgICAgZmlsbD0iI0ZGQjc0RCIKICAgICAgICAgICAgICAgICAgICAgICAgbWFya2VyV2lkdGg9IjEwIgogICAgICAgICAgICAgICAgICAgICAgICBtYXJrZXJIZWlnaHQ9IjEwIgogICAgICAgICAgICAgICAgICAgICAgICByZWZYPSI5IgogICAgICAgICAgICAgICAgICAgICAgICByZWZZPSI1IgogICAgICAgICAgICAgICAgICAgICAgICBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSIKICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTSAwLDAgTCAxMCw1IEwgMCwxMCBDIDMsNyAzLDMgMCwwIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgICA8L21hcmtlcj4KICAgICAgICAgICAgICAgICAgICA8L2RlZnM+CiAgICAgICAgICAgICAgICAgICAgPHBhdGgKICAgICAgICAgICAgICAgICAgICAgIG1hcmtlci1lbmQ9InVybCgjcmVmZXJlbmNlLWFycm93aGVhZC1sZWdlbmQpIgogICAgICAgICAgICAgICAgICAgICAgc3Ryb2tlPSIjRkZCNzREIgogICAgICAgICAgICAgICAgICAgICAgZD0iTTIgOSBsIDI5IDAiCiAgICAgICAgICAgICAgICAgICAgICBzdHJva2UtbGluZWNhcD0icm91bmQiCiAgICAgICAgICAgICAgICAgICAgPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgPC9zdmc+CiAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgICBSZWZlcmVuY2UgZWRnZQogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsZWdlbmQtY2xhcmlmaWVyIj4KICAgICAgICAgICAgICAgICAgICA8c3Bhbj4/PC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDxwYXBlci10b29sdGlwCiAgICAgICAgICAgICAgICAgICAgICBhbmltYXRpb24tZGVsYXk9IjAiCiAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbj0icmlnaHQiCiAgICAgICAgICAgICAgICAgICAgICBvZmZzZXQ9IjAiCiAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgRWRnZSBzaG93aW5nIHRoYXQgdGhlIG91dGdvaW5nIG9wZXJhdGlvbiBub2RlIGNhbiBtdXRhdGUKICAgICAgICAgICAgICAgICAgICAgIHRoZSBpbmNvbWluZyB0ZW5zb3IuCiAgICAgICAgICAgICAgICAgICAgPC9wYXBlci10b29sdGlwPgogICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvaXJvbi1jb2xsYXBzZT4KICAgIDwvZGl2PgogIGA7RShbQSh7dHlwZTpPYmplY3Qsb2JzZXJ2ZXI6Il9zdGF0c0NoYW5nZWQifSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEluLnByb3RvdHlwZSwic3RhdHMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3Qsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEluLnByb3RvdHlwZSwiZGV2aWNlc0ZvclN0YXRzIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxJbi5wcm90b3R5cGUsImNvbG9yQnkiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3Qsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEluLnByb3RvdHlwZSwiY29sb3JCeVBhcmFtcyIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5LG9ic2VydmVyOiJfZGF0YXNldHNDaGFuZ2VkIn0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxJbi5wcm90b3R5cGUsImRhdGFzZXRzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLGxvKV0sSW4ucHJvdG90eXBlLCJyZW5kZXJIaWVyYXJjaHkiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3Qsbm90aWZ5OiEwLHJlYWRPbmx5OiEwLGNvbXB1dGVkOiJfY29tcHV0ZVNlbGVjdGlvbihkYXRhc2V0cywgX3NlbGVjdGVkUnVuSW5kZXgsIF9zZWxlY3RlZFRhZ0luZGV4LCBfc2VsZWN0ZWRHcmFwaFR5cGUpIn0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxJbi5wcm90b3R5cGUsInNlbGVjdGlvbiIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sSW4ucHJvdG90eXBlLCJzZWxlY3RlZEZpbGUiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXIsb2JzZXJ2ZXI6Il9zZWxlY3RlZFJ1bkluZGV4Q2hhbmdlZCJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sSW4ucHJvdG90eXBlLCJfc2VsZWN0ZWRSdW5JbmRleCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4sbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxJbi5wcm90b3R5cGUsInRyYWNlSW5wdXRzIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbixub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLEluLnByb3RvdHlwZSwiYXV0b0V4dHJhY3ROb2RlcyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcixvYnNlcnZlcjoiX3NlbGVjdGVkVGFnSW5kZXhDaGFuZ2VkIn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxJbi5wcm90b3R5cGUsIl9zZWxlY3RlZFRhZ0luZGV4Iix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLEluLnByb3RvdHlwZSwiX3NlbGVjdGVkR3JhcGhUeXBlIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxJbi5wcm90b3R5cGUsInNlbGVjdGVkTm9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLEluLnByb3RvdHlwZSwic2hvd1Nlc3Npb25SdW5zRHJvcGRvd24iLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxJbi5wcm90b3R5cGUsInNob3dVcGxvYWRCdXR0b24iLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxJbi5wcm90b3R5cGUsImhlYWx0aFBpbGxzRmVhdHVyZUVuYWJsZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSW4ucHJvdG90eXBlLCJoZWFsdGhQaWxsc1RvZ2dsZWRPbiIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLEluLnByb3RvdHlwZSwiX2xlZ2VuZE9wZW5lZCIsdm9pZCAwKTtFKFtSdCgiZGV2aWNlc0ZvclN0YXRzIiksdygiZGVzaWduOnR5cGUiLEFycmF5KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxJbi5wcm90b3R5cGUsIl9jdXJyZW50RGV2aWNlcyIsbnVsbCk7RShbUnQoImNvbG9yQnlQYXJhbXMiKSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLEluLnByb3RvdHlwZSwiX2N1cnJlbnREZXZpY2VQYXJhbXMiLG51bGwpO0UoW1J0KCJjb2xvckJ5UGFyYW1zIiksdygiZGVzaWduOnR5cGUiLEFycmF5KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxJbi5wcm90b3R5cGUsIl9jdXJyZW50WGxhQ2x1c3RlclBhcmFtcyIsbnVsbCk7RShbUnQoImNvbG9yQnlQYXJhbXMiLCJjb2xvckJ5IiksdygiZGVzaWduOnR5cGUiLE9iamVjdCksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sSW4ucHJvdG90eXBlLCJfY3VycmVudEdyYWRpZW50UGFyYW1zIixudWxsKTtJbj1FKFt5dCgidGYtZ3JhcGgtY29udHJvbHMiKV0sSW4pO2Z1bmN0aW9uIGF1cihlKXtpZihlPT09InRydWUiKXJldHVybiEwO2lmKGU9PT0iZmFsc2UiKXJldHVybiExO2lmKGVbMF09PT0nIicpcmV0dXJuIGUuc3Vic3RyaW5nKDEsZS5sZW5ndGgtMSk7bGV0IHI9cGFyc2VGbG9hdChlKTtyZXR1cm4gaXNOYU4ocik/ZTpyfWZ1bmN0aW9uICRsZShlKXtyZXR1cm4gbmV3IFByb21pc2UoKHQscik9PntmZXRjaChlKS50aGVuKG49PntuLm9rP24uYXJyYXlCdWZmZXIoKS50aGVuKHQscik6bi50ZXh0KCkudGhlbihyLHIpfSl9KX1mdW5jdGlvbiBLbGUoZSx0KXtyZXR1cm4gWXNlKCJSZWFkaW5nIG1ldGFkYXRhIHBidHh0Iiw0MCwoKT0+ZT09bnVsbD9Qcm9taXNlLnJlc29sdmUobnVsbCk6JGxlKGUpLHQsanIuRkVUQ0hfTUVUQURBVEFfUEJUWFRfQllURVMpLnRoZW4ocj0+ZEgoIlBhcnNpbmcgbWV0YWRhdGEucGJ0eHQiLDYwLCgpPT5yIT1udWxsP2h1cihyKTpQcm9taXNlLnJlc29sdmUobnVsbCksdCxqci5QQVJTRV9NRVRBREFUQV9QQlRYVF9JTlRPX09CSkVDVCkpfWZ1bmN0aW9uIFpsZShlLHQscil7cmV0dXJuIGRIKCJSZWFkaW5nIGdyYXBoIHBidHh0Iiw0MCwoKT0+UmkodGhpcyxudWxsLGZ1bmN0aW9uKigpe2xldCBuPURhdGUubm93KCk7aWYodCl7bGV0IG89eWllbGQgbmV3IFByb21pc2UoZnVuY3Rpb24oYSxzKXtsZXQgbD1uZXcgRmlsZVJlYWRlcjtsLm9ubG9hZD0oKT0+YShsLnJlc3VsdCksbC5vbmVycm9yPSgpPT5zKGwuZXJyb3IpLGwucmVhZEFzQXJyYXlCdWZmZXIodCl9KTtyZXR1cm4gUG8oe3RpbWluZ0lkOmpyLkZFVENIX1BCVFhUX0JZVEVTX0ZST01fRklMRVNZU1RFTSxldmVudFZhbHVlOkRhdGUubm93KCktbn0pLG99bGV0IGk9eWllbGQgJGxlKGUpO3JldHVybiBQbyh7dGltaW5nSWQ6anIuRkVUQ0hfUEJUWFRfQllURVNfRlJPTV9TRVJWRVIsZXZlbnRWYWx1ZTpEYXRlLm5vdygpLW59KSxpfSkscixqci5GRVRDSF9QQlRYVF9CWVRFUykudGhlbihuPT5kSCgiUGFyc2luZyBncmFwaC5wYnR4dCIsNjAsKCk9PnV1cihuKSxyLGpyLlBBUlNFX1BCVFhUX0lOVE9fT0JKRUNUKSl9ZnVuY3Rpb24gc3VyKGUsdCxyPTFlNixuPWAKYCl7cmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKGksbyl7ZnVuY3Rpb24gYShzLGwsYyl7bGV0IHU9Yz49ZS5ieXRlTGVuZ3RoLGg9bC5zcGxpdChuKTtoWzBdPXMraFswXTtsZXQgZj11PyIiOmgucG9wKCk7Zm9yKGxldCBnIG9mIGgpdHJ5e3QoZyl9Y2F0Y2goXyl7byhfKTtyZXR1cm59aWYodSl7aSghMCk7cmV0dXJufWxldCBwPW5ldyBCbG9iKFtlLnNsaWNlKGMsYytyKV0pLGQ9bmV3IEZpbGVSZWFkZXI7ZC5vbmxvYWQ9ZnVuY3Rpb24oZyl7YShmLGcudGFyZ2V0LnJlc3VsdCxjK3IpfSxkLnJlYWRBc1RleHQocCl9YSgiIiwiIiwwKX0pfXZhciBsdXI9eyJsaWJyYXJ5LmZ1bmN0aW9uIjohMCwibGlicmFyeS5mdW5jdGlvbi5ub2RlX2RlZiI6ITAsImxpYnJhcnkuZnVuY3Rpb24ubm9kZV9kZWYuaW5wdXQiOiEwLCJsaWJyYXJ5LmZ1bmN0aW9uLm5vZGVfZGVmLmF0dHIiOiEwLCJsaWJyYXJ5LmZ1bmN0aW9uLm5vZGVfZGVmLmF0dHIudmFsdWUubGlzdC5iIjohMCwibGlicmFyeS5mdW5jdGlvbi5ub2RlX2RlZi5hdHRyLnZhbHVlLmxpc3QuZiI6ITAsImxpYnJhcnkuZnVuY3Rpb24ubm9kZV9kZWYuYXR0ci52YWx1ZS5saXN0LmZ1bmMiOiEwLCJsaWJyYXJ5LmZ1bmN0aW9uLm5vZGVfZGVmLmF0dHIudmFsdWUubGlzdC5pIjohMCwibGlicmFyeS5mdW5jdGlvbi5ub2RlX2RlZi5hdHRyLnZhbHVlLmxpc3QucyI6ITAsImxpYnJhcnkuZnVuY3Rpb24ubm9kZV9kZWYuYXR0ci52YWx1ZS5saXN0LnNoYXBlIjohMCwibGlicmFyeS5mdW5jdGlvbi5ub2RlX2RlZi5hdHRyLnZhbHVlLmxpc3Quc2hhcGUuZGltIjohMCwibGlicmFyeS5mdW5jdGlvbi5ub2RlX2RlZi5hdHRyLnZhbHVlLmxpc3QudGVuc29yIjohMCwibGlicmFyeS5mdW5jdGlvbi5ub2RlX2RlZi5hdHRyLnZhbHVlLmxpc3QudHlwZSI6ITAsImxpYnJhcnkuZnVuY3Rpb24ubm9kZV9kZWYuYXR0ci52YWx1ZS5zaGFwZS5kaW0iOiEwLCJsaWJyYXJ5LmZ1bmN0aW9uLm5vZGVfZGVmLmF0dHIudmFsdWUudGVuc29yLnN0cmluZ192YWwiOiEwLCJsaWJyYXJ5LmZ1bmN0aW9uLm5vZGVfZGVmLmF0dHIudmFsdWUudGVuc29yLnRlbnNvcl9zaGFwZS5kaW0iOiEwLCJsaWJyYXJ5LmZ1bmN0aW9uLnNpZ25hdHVyZS5pbnB1dF9hcmciOiEwLCJsaWJyYXJ5LmZ1bmN0aW9uLnNpZ25hdHVyZS5vdXRwdXRfYXJnIjohMCwibGlicmFyeS52ZXJzaW9ucyI6ITAsbm9kZTohMCwibm9kZS5pbnB1dCI6ITAsIm5vZGUuYXR0ciI6ITAsIm5vZGUuYXR0ci52YWx1ZS5saXN0LmIiOiEwLCJub2RlLmF0dHIudmFsdWUubGlzdC5mIjohMCwibm9kZS5hdHRyLnZhbHVlLmxpc3QuZnVuYyI6ITAsIm5vZGUuYXR0ci52YWx1ZS5saXN0LmkiOiEwLCJub2RlLmF0dHIudmFsdWUubGlzdC5zIjohMCwibm9kZS5hdHRyLnZhbHVlLmxpc3Quc2hhcGUiOiEwLCJub2RlLmF0dHIudmFsdWUubGlzdC5zaGFwZS5kaW0iOiEwLCJub2RlLmF0dHIudmFsdWUubGlzdC50ZW5zb3IiOiEwLCJub2RlLmF0dHIudmFsdWUubGlzdC50eXBlIjohMCwibm9kZS5hdHRyLnZhbHVlLnNoYXBlLmRpbSI6ITAsIm5vZGUuYXR0ci52YWx1ZS50ZW5zb3Iuc3RyaW5nX3ZhbCI6ITAsIm5vZGUuYXR0ci52YWx1ZS50ZW5zb3IudGVuc29yX3NoYXBlLmRpbSI6ITB9LGN1cj17InN0ZXBfc3RhdHMuZGV2X3N0YXRzIjohMCwic3RlcF9zdGF0cy5kZXZfc3RhdHMubm9kZV9zdGF0cyI6ITAsInN0ZXBfc3RhdHMuZGV2X3N0YXRzLm5vZGVfc3RhdHMub3V0cHV0IjohMCwic3RlcF9zdGF0cy5kZXZfc3RhdHMubm9kZV9zdGF0cy5tZW1vcnkiOiEwLCJzdGVwX3N0YXRzLmRldl9zdGF0cy5ub2RlX3N0YXRzLm91dHB1dC50ZW5zb3JfZGVzY3JpcHRpb24uc2hhcGUuZGltIjohMH07ZnVuY3Rpb24gdXVyKGUpe3JldHVybiBKbGUoZSxsdXIpfWZ1bmN0aW9uIGh1cihlKXtyZXR1cm4gSmxlKGUsY3VyKS50aGVuKHQ9PnQuc3RlcF9zdGF0cyl9ZnVuY3Rpb24gSmxlKGUsdCl7bGV0IHI9e30sbj1bXSxpPVtdLG89cjtmdW5jdGlvbiBhKGwpe2xldCBjPWwuaW5kZXhPZigiOiIpLHU9bC5zdWJzdHJpbmcoMCxjKS50cmltKCksaD1hdXIobC5zdWJzdHJpbmcoYysyKS50cmltKCkpO3JldHVybntuYW1lOnUsdmFsdWU6aH19ZnVuY3Rpb24gcyhsLGMsdSxoKXtsZXQgZj1sW2NdO2Y9PW51bGw/bFtjXT1oLmpvaW4oIi4iKWluIHQ/W3VdOnU6QXJyYXkuaXNBcnJheShmKT9mLnB1c2godSk6bFtjXT1bZix1XX1yZXR1cm4gc3VyKGUsZnVuY3Rpb24obCl7aWYobD1sLnRyaW0oKSwhIWwpc3dpdGNoKGxbbC5sZW5ndGgtMV0pe2Nhc2UieyI6bGV0IGM9bC5zdWJzdHJpbmcoMCxsLmxlbmd0aC0yKS50cmltKCksdT17fTtuLnB1c2gobyksaS5wdXNoKGMpLHMobyxjLHUsaSksbz11O2JyZWFrO2Nhc2UifSI6bz1uLnBvcCgpLGkucG9wKCk7YnJlYWs7ZGVmYXVsdDpsZXQgaD1hKGwpO3MobyxoLm5hbWUsaC52YWx1ZSxpLmNvbmNhdChoLm5hbWUpKTticmVha319KS50aGVuKGZ1bmN0aW9uKCl7cmV0dXJuIHJ9KX1mdW5jdGlvbiB0Y2UoZSx0LHIsbj1uZXcgSnUsaT1yMyl7bGV0IG89SlMoZSwzMCwiRGF0YSIpLGE9SlMoZSwyMCwiR3JhcGgiKSxzPUpTKGUsNTAsIk5hbWVzcGFjZSBoaWVyYXJjaHkiKSxsPURhdGUubm93KCk7cmV0dXJuIFpsZSh0LHIsbykudGhlbihmdW5jdGlvbihjKXtpZighYy5ub2RlKXRocm93IG5ldyBFcnJvcigiVGhlIGdyYXBoIGlzIGVtcHR5LiBUaGlzIGNhbiBoYXBwZW4gd2hlbiBUZW5zb3JGbG93IGNvdWxkIG5vdCB0cmFjZSBhbnkgZ3JhcGguIFBsZWFzZSByZWZlciB0byBodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9pc3N1ZXMvMTk2MSBmb3IgbW9yZSBpbmZvcm1hdGlvbi4iKTtyZXR1cm4gbmxlKGMscmxlLGEpfSwoKT0+e3Rocm93IG5ldyBFcnJvcigiTWFsZm9ybWVkIEdyYXBoRGVmLiBUaGlzIGNhbiBzb21ldGltZXMgYmUgY2F1c2VkIGJ5IGEgYmFkIG5ldHdvcmsgY29ubmVjdGlvbiBvciBkaWZmaWN1bHR5IHJlY29uY2lsaW5nIG11bHRpcGxlIEdyYXBoRGVmczsgZm9yIHRoZSBsYXR0ZXIgY2FzZSwgcGxlYXNlIHJlZmVyIHRvIGh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2lzc3Vlcy8xOTI5LiIpfSkudGhlbihjPT5SaSh0aGlzLG51bGwsZnVuY3Rpb24qKCl7WWxlKGMsbik7bGV0IHU9eWllbGQgeEgoYyxpLHMpO3JldHVybiBQbyh7dGltaW5nSWQ6anIuR1JBUEhfTE9BRF9TVUNDRUVERUQsZXZlbnRWYWx1ZTpEYXRlLm5vdygpLWx9KSx7Z3JhcGg6YyxncmFwaEhpZXJhcmNoeTp1fX0pKS5jYXRjaChjPT57bGV0IHU9YEdyYXBoIHZpc3VhbGl6YXRpb24gZmFpbGVkLgoKJHtjfWA7dGhyb3cgZS5yZXBvcnRFcnJvcih1LGMpLFBvKHt0aW1pbmdJZDpqci5HUkFQSF9MT0FEX0ZBSUxFRCxldmVudFZhbHVlOkRhdGUubm93KCktbH0pLGN9KX12YXIgSHM9Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuY29tcGF0aWJpbGl0eVByb3ZpZGVyPW5ldyBKdSx0aGlzLmhpZXJhcmNoeVBhcmFtcz1yMyx0aGlzLl90ZW1wbGF0ZT1udWxsfV9zZWxlY3Rpb25DaGFuZ2VkKCl7IXRoaXMuc2VsZWN0aW9ufHx0aGlzLmRlYm91bmNlKCJzZWxlY3Rpb25jaGFuZ2UiLCgpPT57dGhpcy5fbG9hZCh0aGlzLnNlbGVjdGlvbil9KX1fbG9hZCh0KXtsZXR7cnVuOnIsdGFnOm4sdHlwZTppfT10O3N3aXRjaChpKXtjYXNlIEZzLk9QX0dSQVBIOmNhc2UgRnMuQ09OQ0VQVFVBTF9HUkFQSDp7KGZ1bmN0aW9uKCl7dGhpcy5fc2V0T3V0U3RhdHMobnVsbCl9KS5iaW5kKHRoaXMpKCk7bGV0IG89bmV3IFVSTFNlYXJjaFBhcmFtcztvLnNldCgicnVuIixyKSxvLnNldCgiY29uY2VwdHVhbCIsU3RyaW5nKGk9PT1Gcy5DT05DRVBUVUFMX0dSQVBIKSksbiYmby5zZXQoInRhZyIsbik7bGV0IGE9dmUoKS5wbHVnaW5Sb3V0ZSgiZ3JhcGhzIiwiL2dyYXBoIixvKTtyZXR1cm4gdGhpcy5fZmV0Y2hBbmRDb25zdHJ1Y3RIaWVyYXJjaGljYWxHcmFwaChhKS50aGVuKCgpPT57dGhpcy5fZ3JhcGhSdW5UYWc9e3J1bjpyLHRhZzpufX0pfWNhc2UgRnMuUFJPRklMRTp7bGV0e3RhZ3M6b309dGhpcy5kYXRhc2V0cy5maW5kKCh7bmFtZTpmfSk9PmY9PT1yKSxzPW8uZmluZChmPT5mLnRhZz09PW4pLm9wR3JhcGg/bjpudWxsO2NvbnNvbGUuYXNzZXJ0KG8uZmluZChmPT5mLnRhZz09PXMpLGBSZXF1aXJlZCB0YWcgKCR7c30pIGlzIG1pc3NpbmcuYCk7bGV0IGM9IXRoaXMuX2dyYXBoUnVuVGFnfHx0aGlzLl9ncmFwaFJ1blRhZy5ydW4hPT1yfHx0aGlzLl9ncmFwaFJ1blRhZy50YWchPT1zP3RoaXMuX2xvYWQoe3J1bjpyLHRhZzpzLHR5cGU6RnMuT1BfR1JBUEh9KTpQcm9taXNlLnJlc29sdmUoKSx1PW5ldyBVUkxTZWFyY2hQYXJhbXM7dS5zZXQoInRhZyIsbiksdS5zZXQoInJ1biIscik7bGV0IGg9dmUoKS5wbHVnaW5Sb3V0ZSgiZ3JhcGhzIiwiL3J1bl9tZXRhZGF0YSIsdSk7cmV0dXJuIGMudGhlbigoKT0+dGhpcy5fcmVhZEFuZFBhcnNlTWV0YWRhdGEoaCkpfWRlZmF1bHQ6cmV0dXJuIFByb21pc2UucmVqZWN0KG5ldyBFcnJvcihgVW5rbm93biBzZWxlY3Rpb24gdHlwZTogJHtpfWApKX19X3JlYWRBbmRQYXJzZU1ldGFkYXRhKHQpe3RoaXMuc2V0KCJwcm9ncmVzcyIse3ZhbHVlOjAsbXNnOiIifSk7dmFyIHI9clAodGhpcyk7S2xlKHQscikudGhlbihmdW5jdGlvbihuKXt0aGlzLl9zZXRPdXRTdGF0cyhuKX0uYmluZCh0aGlzKSl9X2ZldGNoQW5kQ29uc3RydWN0SGllcmFyY2hpY2FsR3JhcGgodCxyKXt0aGlzLnNldCgicHJvZ3Jlc3MiLHt2YWx1ZTowLG1zZzoiIn0pO2xldCBuPXJQKHRoaXMpO3JldHVybiB0Y2Uobix0LHIhPT12b2lkIDA/cjpudWxsLHRoaXMuY29tcGF0aWJpbGl0eVByb3ZpZGVyLHRoaXMuaGllcmFyY2h5UGFyYW1zKS50aGVuKGZ1bmN0aW9uKHtncmFwaDppLGdyYXBoSGllcmFyY2h5Om99KXt0aGlzLl9zZXRPdXRHcmFwaChpKSx0aGlzLl9zZXRPdXRHcmFwaEhpZXJhcmNoeShvKX0uYmluZCh0aGlzKSl9X3NlbGVjdGVkRmlsZUNoYW5nZWQoKXt2YXIgaTt2YXIgdD10aGlzLnNlbGVjdGVkRmlsZTtpZighdClyZXR1cm47bGV0IHI9dC50YXJnZXQsbj0oaT1yLmZpbGVzKT09bnVsbD92b2lkIDA6aVswXTshbnx8KHIudmFsdWU9IiIsdGhpcy5fZmV0Y2hBbmRDb25zdHJ1Y3RIaWVyYXJjaGljYWxHcmFwaChudWxsLG4pKX19O0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxIcy5wcm90b3R5cGUsImRhdGFzZXRzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0LG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxIcy5wcm90b3R5cGUsInByb2dyZXNzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEhzLnByb3RvdHlwZSwic2VsZWN0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEhzLnByb3RvdHlwZSwic2VsZWN0ZWRGaWxlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEhzLnByb3RvdHlwZSwiY29tcGF0aWJpbGl0eVByb3ZpZGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEhzLnByb3RvdHlwZSwiaGllcmFyY2h5UGFyYW1zIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0LHJlYWRPbmx5OiEwLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixvcyldLEhzLnByb3RvdHlwZSwib3V0R3JhcGhIaWVyYXJjaHkiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3QscmVhZE9ubHk6ITAsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLFh1KV0sSHMucHJvdG90eXBlLCJvdXRHcmFwaCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxyZWFkT25seTohMCxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sSHMucHJvdG90eXBlLCJvdXRTdGF0cyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxIcy5wcm90b3R5cGUsIl9ncmFwaFJ1blRhZyIsdm9pZCAwKTtFKFtCdCgic2VsZWN0aW9uIiwiY29tcGF0aWJpbGl0eVByb3ZpZGVyIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxIcy5wcm90b3R5cGUsIl9zZWxlY3Rpb25DaGFuZ2VkIixudWxsKTtFKFtCdCgic2VsZWN0ZWRGaWxlIiwiY29tcGF0aWJpbGl0eVByb3ZpZGVyIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxIcy5wcm90b3R5cGUsIl9zZWxlY3RlZEZpbGVDaGFuZ2VkIixudWxsKTtIcz1FKFt5dCgidGYtZ3JhcGgtZGFzaGJvYXJkLWxvYWRlciIpXSxIcyk7dmFyIGVjZT0icnVuIjt2YXIgT3I9Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX2RhdGFzZXRzPVtdLHRoaXMuX2RhdGFzZXRzRmV0Y2hlZD0hMSx0aGlzLl9zZWxlY3RlZERhdGFzZXQ9MCx0aGlzLl9yZXF1ZXN0TWFuYWdlcj1uZXcgQWUsdGhpcy5fY2FuY2VsbGVyPW5ldyBhbix0aGlzLnNwZWNpZmljSGVhbHRoUGlsbFN0ZXA9MCx0aGlzLmhlYWx0aFBpbGxzVG9nZ2xlZE9uPSExLHRoaXMuX2RlYnVnZ2VyTnVtZXJpY0FsZXJ0cz1bXSx0aGlzLl9ub2RlTmFtZXNUb0hlYWx0aFBpbGxzPXt9LHRoaXMuX2hlYWx0aFBpbGxSZXF1ZXN0SWQ9MSx0aGlzLl9oZWFsdGhQaWxsU3RlcFJlcXVlc3RUaW1lckRlbGF5PTUwMCx0aGlzLnJ1bj15XyhlY2Use2RlZmF1bHRWYWx1ZToiIix1c2VMb2NhbFN0b3JhZ2U6ITF9KS5jYWxsKHRoaXMpLHRoaXMuX3J1bk9ic2VydmVyPXZfKGVjZSx7ZGVmYXVsdFZhbHVlOiIiLHBvbHltZXJQcm9wZXJ0eToicnVuIix1c2VMb2NhbFN0b3JhZ2U6ITF9KX1hdHRhY2hlZCgpe3RoaXMuc2V0KCJfaXNBdHRhY2hlZCIsITApfWRldGFjaGVkKCl7dGhpcy5zZXQoIl9pc0F0dGFjaGVkIiwhMSl9cmVhZHkoKXtzdXBlci5yZWFkeSgpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigibm9kZS10b2dnbGUtZXhwYW5kIix0aGlzLl9oYW5kbGVOb2RlVG9nZ2xlRXhwYW5kLmJpbmQodGhpcykpfXJlbG9hZCgpe3RoaXMuX2RlYnVnZ2VyRGF0YUVuYWJsZWR8fHRoaXMuX3JlcXVlc3RNYW5hZ2VyLnJlcXVlc3QodmUoKS5wbHVnaW5zTGlzdGluZygpKS50aGVuKHRoaXMuX2NhbmNlbGxlci5jYW5jZWxsYWJsZSh0PT57dC5jYW5jZWxsZWR8fHQudmFsdWUuZGVidWdnZXImJnRoaXMuc2V0KCJfZGVidWdnZXJEYXRhRW5hYmxlZCIsITApfSkpLHRoaXMuX21heWJlRmV0Y2hIZWFsdGhQaWxscygpfV9maXQoKXt0aGlzLiQkKCIjZ3JhcGhib2FyZCIpLmZpdCgpfV9vbkRvd25sb2FkSW1hZ2VSZXF1ZXN0ZWQodCl7dGhpcy4kJCgiI2dyYXBoYm9hcmQiKS5kb3dubG9hZEFzSW1hZ2UodC5kZXRhaWwpfV9nZXRHcmFwaERpc3BsYXlDbGFzc05hbWUodCxyKXtyZXR1cm4gdHx8ci5sZW5ndGg/IiI6Im5vLWdyYXBoIn1fZmV0Y2hEYXRhc2V0KCl7cmV0dXJuIHRoaXMuX3JlcXVlc3RNYW5hZ2VyLnJlcXVlc3QodmUoKS5wbHVnaW5Sb3V0ZSgiZ3JhcGhzIiwiL2luZm8iKSl9X2ZldGNoSGVhbHRoUGlsbHModCxyKXtsZXQgbj17bm9kZV9uYW1lczpKU09OLnN0cmluZ2lmeSh0KSxydW46Il9fZGVidWdnZXJfZGF0YV9fIn07ciE9PXZvaWQgMCYmKG4uc3RlcD1yKTtsZXQgaT12ZSgpLnBsdWdpblJvdXRlKCJkZWJ1Z2dlciIsIi9oZWFsdGhfcGlsbHMiKTtyZXR1cm4gdGhpcy5fcmVxdWVzdE1hbmFnZXIucmVxdWVzdChpLG4pfV9mZXRjaERlYnVnZ2VyTnVtZXJpY3NBbGVydHMoKXtyZXR1cm4gdGhpcy5fcmVxdWVzdE1hbmFnZXIucmVxdWVzdCh2ZSgpLnBsdWdpblJvdXRlKCJkZWJ1Z2dlciIsIi9udW1lcmljc19hbGVydF9yZXBvcnQiKSl9X2dyYXBoVXJsKHQscixuKXtyZXR1cm4gdmUoKS5wbHVnaW5Sb3V0ZSgiZ3JhcGhzIiwiL2dyYXBoIixuZXcgVVJMU2VhcmNoUGFyYW1zKHtydW46dCxsaW1pdF9hdHRyX3NpemU6cixsYXJnZV9hdHRyc19rZXk6bn0pKX1fc2hvdWxkUmVxdWVzdEhlYWx0aFBpbGxzKCl7cmV0dXJuIHRoaXMuX2RlYnVnZ2VyRGF0YUVuYWJsZWQmJnRoaXMuaGVhbHRoUGlsbHNUb2dnbGVkT24mJnRoaXMuX3JlbmRlckhpZXJhcmNoeSYmdGhpcy5fZGF0YXNldHNTdGF0ZSh0aGlzLl9kYXRhc2V0c0ZldGNoZWQsdGhpcy5fZGF0YXNldHMsIlBSRVNFTlQiKX1fbWF5YmVJbml0aWFsaXplRGFzaGJvYXJkKCl7dmFyIHQ9dGhpcy5faXNBdHRhY2hlZDt0aGlzLl9pbml0aWFsaXplZHx8IXR8fCh0aGlzLnNldCgiX2NvbXBhdGliaWxpdHlQcm92aWRlciIsbmV3IEp1KSx0aGlzLl9pbml0aWFsaXplZD0hMCx0aGlzLl9mZXRjaERhdGFzZXQoKS50aGVuKHI9PntsZXQgbj1PYmplY3Qua2V5cyhyKTt0aGlzLl9kYXRhc2V0cz1uLnNvcnQoeGgpLm1hcChpPT57bGV0IG89cltpXSxzPU9iamVjdC5rZXlzKG8udGFncykuc29ydCh4aCkubWFwKGM9Pm8udGFnc1tjXSkubWFwKCh7dGFnOmMsY29uY2VwdHVhbF9ncmFwaDp1LG9wX2dyYXBoOmgscHJvZmlsZTpmfSk9Pih7dGFnOmMsZGlzcGxheU5hbWU6Yyxjb25jZXB0dWFsR3JhcGg6dSxvcEdyYXBoOmgscHJvZmlsZTpmfSkpLGw9by5ydW5fZ3JhcGg/W3t0YWc6bnVsbCxkaXNwbGF5TmFtZToiRGVmYXVsdCIsY29uY2VwdHVhbEdyYXBoOiExLG9wR3JhcGg6ITAscHJvZmlsZTohMX0sLi4uc106cztyZXR1cm57bmFtZTppLHRhZ3M6bH19KSx0aGlzLl9kYXRhc2V0c0ZldGNoZWQ9ITB9KSl9X2RldGVybWluZVNlbGVjdGVkRGF0YXNldCgpe3ZhciB0PXRoaXMuX2RhdGFzZXRzRmV0Y2hlZCxyPXRoaXMuX2RhdGFzZXRzLG49dGhpcy5ydW47aWYoIW4pe3RoaXMuc2V0KCJfc2VsZWN0ZWREYXRhc2V0IiwwKTtyZXR1cm59bGV0IGk9ci5maW5kSW5kZXgobz0+by5uYW1lPT09bik7aWYoaT09PS0xKXtpZih0KXtsZXQgbz10aGlzLiQkKCIjZXJyb3ItZGlhbG9nIik7by50ZXh0Q29udGVudD1gTm8gZGF0YXNldCBuYW1lZCAiJHtufSIgY291bGQgYmUgZm91bmQuYCxvLm9wZW4oKX1yZXR1cm59dGhpcy5zZXQoIl9zZWxlY3RlZERhdGFzZXQiLGkpfV91cGRhdGVTZWxlY3RlZERhdGFzZXROYW1lKCl7dmFyIHQ9dGhpcy5fZGF0YXNldHNGZXRjaGVkLHI9dGhpcy5fZGF0YXNldHMsbj10aGlzLl9zZWxlY3RlZERhdGFzZXQ7IXR8fHIubGVuZ3RoPD1ufHx0aGlzLnNldCgicnVuIixyW25dLm5hbWUpfV9yZXF1ZXN0SGVhbHRoUGlsbHMoKXt0aGlzLnNldCgiX2FyZUhlYWx0aFBpbGxzTG9hZGluZyIsITApO3ZhciB0PSsrdGhpcy5faGVhbHRoUGlsbFJlcXVlc3RJZDt0aGlzLl9oZWFsdGhQaWxsU3RlcFJlcXVlc3RUaW1lcklkIT09bnVsbCYmKHdpbmRvdy5jbGVhclRpbWVvdXQodGhpcy5faGVhbHRoUGlsbFN0ZXBSZXF1ZXN0VGltZXJJZCksdGhpcy5faGVhbHRoUGlsbFN0ZXBSZXF1ZXN0VGltZXJJZD1udWxsKSx0aGlzLmFsbFN0ZXBzTW9kZUVuYWJsZWQ/dGhpcy5faGVhbHRoUGlsbFN0ZXBSZXF1ZXN0VGltZXJJZD1zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7dGhpcy5faGVhbHRoUGlsbFN0ZXBSZXF1ZXN0VGltZXJJZD1udWxsLHRoaXMuX2luaXRpYXRlTmV0d29ya1JlcXVlc3RGb3JIZWFsdGhQaWxscyh0KX0uYmluZCh0aGlzKSx0aGlzLl9oZWFsdGhQaWxsU3RlcFJlcXVlc3RUaW1lckRlbGF5KTp0aGlzLl9pbml0aWF0ZU5ldHdvcmtSZXF1ZXN0Rm9ySGVhbHRoUGlsbHModCl9X2luaXRpYXRlTmV0d29ya1JlcXVlc3RGb3JIZWFsdGhQaWxscyh0KXtpZih0aGlzLl9oZWFsdGhQaWxsUmVxdWVzdElkIT09dClyZXR1cm47bGV0IHI9dGhpcy5hbGxTdGVwc01vZGVFbmFibGVkP3RoaXMuc3BlY2lmaWNIZWFsdGhQaWxsU3RlcDp2b2lkIDAsbj10aGlzLl9mZXRjaEhlYWx0aFBpbGxzKHRoaXMuX3JlbmRlckhpZXJhcmNoeS5nZXROYW1lc09mUmVuZGVyZWRPcHMoKSxyKSxpPXRoaXMuX2ZldGNoRGVidWdnZXJOdW1lcmljc0FsZXJ0cygpO1Byb21pc2UuYWxsKFtuLGldKS50aGVuKGZ1bmN0aW9uKG8pe3ZhciBhPW9bMF0scz1vWzFdO2lmKCEhdGhpcy5oZWFsdGhQaWxsc1RvZ2dsZWRPbiYmdD09PXRoaXMuX2hlYWx0aFBpbGxSZXF1ZXN0SWQpe2Zvcih2YXIgbCBpbiBhKXt0aGlzLnNldCgiX2hlYWx0aFBpbGxTdGVwSW5kZXgiLGFbbF0ubGVuZ3RoLTEpO2JyZWFrfXRoaXMuc2V0KCJfZGVidWdnZXJOdW1lcmljQWxlcnRzIixzKSx0aGlzLnNldCgiX25vZGVOYW1lc1RvSGVhbHRoUGlsbHMiLGEpLHRoaXMuc2V0KCJfYXJlSGVhbHRoUGlsbHNMb2FkaW5nIiwhMSksdGhpcy5zZXQoIl9oZWFsdGhQaWxsU3RlcFJlcXVlc3RUaW1lcklkIixudWxsKX19LmJpbmQodGhpcykpfV9kYXRhc2V0c1N0YXRlKHQscixuKXtyZXR1cm4gdD8hcnx8IXIubGVuZ3RoP249PT0iRU1QVFkiOm49PT0iUFJFU0VOVCI6bj09PSJOT1RfTE9BREVEIn1fcmVuZGVySGllcmFyY2h5Q2hhbmdlZCh0KXt0aGlzLnJlbG9hZCgpfV9oYW5kbGVOb2RlVG9nZ2xlRXhwYW5kKCl7dGhpcy5fbWF5YmVGZXRjaEhlYWx0aFBpbGxzKCl9X2hlYWx0aFBpbGxzVG9nZ2xlZE9uQ2hhbmdlZCh0KXt0P3RoaXMucmVsb2FkKCk6dGhpcy5zZXQoIl9ub2RlTmFtZXNUb0hlYWx0aFBpbGxzIix7fSl9X21heWJlRmV0Y2hIZWFsdGhQaWxscygpeyF0aGlzLl9zaG91bGRSZXF1ZXN0SGVhbHRoUGlsbHMoKXx8dGhpcy5fcmVxdWVzdEhlYWx0aFBpbGxzKCl9fTtPci50ZW1wbGF0ZT1RYAogICAgPHBhcGVyLWRpYWxvZyBpZD0iZXJyb3ItZGlhbG9nIiB3aXRoLWJhY2tkcm9wPjwvcGFwZXItZGlhbG9nPgogICAgPHRmLWRhc2hib2FyZC1sYXlvdXQ+CiAgICAgIDx0Zi1ncmFwaC1jb250cm9scwogICAgICAgIGlkPSJjb250cm9scyIKICAgICAgICBjbGFzcz0ic2lkZWJhciIKICAgICAgICBzbG90PSJzaWRlYmFyIgogICAgICAgIGRldmljZXMtZm9yLXN0YXRzPSJ7e19kZXZpY2VzRm9yU3RhdHN9fSIKICAgICAgICBjb2xvci1ieS1wYXJhbXM9IltbX2NvbG9yQnlQYXJhbXNdXSIKICAgICAgICBzdGF0cz0iW1tfc3RhdHNdXSIKICAgICAgICBjb2xvci1ieT0ie3tfY29sb3JCeX19IgogICAgICAgIGRhdGFzZXRzPSJbW19kYXRhc2V0c11dIgogICAgICAgIHJlbmRlci1oaWVyYXJjaHk9IltbX3JlbmRlckhpZXJhcmNoeV1dIgogICAgICAgIHNlbGVjdGlvbj0ie3tfc2VsZWN0aW9ufX0iCiAgICAgICAgc2VsZWN0ZWQtZmlsZT0ie3tfc2VsZWN0ZWRGaWxlfX0iCiAgICAgICAgc2VsZWN0ZWQtbm9kZT0ie3tfc2VsZWN0ZWROb2RlfX0iCiAgICAgICAgaGVhbHRoLXBpbGxzLWZlYXR1cmUtZW5hYmxlZD0iW1tfZGVidWdnZXJEYXRhRW5hYmxlZF1dIgogICAgICAgIGhlYWx0aC1waWxscy10b2dnbGVkLW9uPSJ7e2hlYWx0aFBpbGxzVG9nZ2xlZE9ufX0iCiAgICAgICAgb24tZml0LXRhcD0iX2ZpdCIKICAgICAgICB0cmFjZS1pbnB1dHM9Int7X3RyYWNlSW5wdXRzfX0iCiAgICAgICAgYXV0by1leHRyYWN0LW5vZGVzPSJ7e19hdXRvRXh0cmFjdE5vZGVzfX0iCiAgICAgICAgb24tZG93bmxvYWQtaW1hZ2UtcmVxdWVzdGVkPSJfb25Eb3dubG9hZEltYWdlUmVxdWVzdGVkIgogICAgICA+PC90Zi1ncmFwaC1jb250cm9scz4KICAgICAgPGRpdgogICAgICAgIGNsYXNzJD0iY2VudGVyIFtbX2dldEdyYXBoRGlzcGxheUNsYXNzTmFtZShfc2VsZWN0ZWRGaWxlLCBfZGF0YXNldHMpXV0iCiAgICAgICAgc2xvdD0iY2VudGVyIgogICAgICA+CiAgICAgICAgPHRmLWdyYXBoLWRhc2hib2FyZC1sb2FkZXIKICAgICAgICAgIGlkPSJsb2FkZXIiCiAgICAgICAgICBkYXRhc2V0cz0iW1tfZGF0YXNldHNdXSIKICAgICAgICAgIHNlbGVjdGlvbj0iW1tfc2VsZWN0aW9uXV0iCiAgICAgICAgICBzZWxlY3RlZC1maWxlPSJbW19zZWxlY3RlZEZpbGVdXSIKICAgICAgICAgIG91dC1ncmFwaC1oaWVyYXJjaHk9Int7X2dyYXBoSGllcmFyY2h5fX0iCiAgICAgICAgICBvdXQtZ3JhcGg9Int7X2dyYXBofX0iCiAgICAgICAgICBvdXQtc3RhdHM9Int7X3N0YXRzfX0iCiAgICAgICAgICBwcm9ncmVzcz0ie3tfcHJvZ3Jlc3N9fSIKICAgICAgICAgIGhpZXJhcmNoeS1wYXJhbXM9IltbX2hpZXJhcmNoeVBhcmFtc11dIgogICAgICAgICAgY29tcGF0aWJpbGl0eS1wcm92aWRlcj0iW1tfY29tcGF0aWJpbGl0eVByb3ZpZGVyXV0iCiAgICAgICAgPjwvdGYtZ3JhcGgtZGFzaGJvYXJkLWxvYWRlcj4KICAgICAgICA8ZGl2IGNsYXNzPSJuby1kYXRhLW1lc3NhZ2UiPgogICAgICAgICAgPGgzPk5vIGdyYXBoIGRlZmluaXRpb24gZmlsZXMgd2VyZSBmb3VuZC48L2gzPgogICAgICAgICAgPHA+CiAgICAgICAgICAgIFRvIHN0b3JlIGEgZ3JhcGgsIGNyZWF0ZSBhCiAgICAgICAgICAgIDxjb2RlPnRmLnN1bW1hcnkuRmlsZVdyaXRlcjwvY29kZT4KICAgICAgICAgICAgYW5kIHBhc3MgdGhlIGdyYXBoIGVpdGhlciB2aWEgdGhlIGNvbnN0cnVjdG9yLCBvciBieSBjYWxsaW5nIGl0cwogICAgICAgICAgICA8Y29kZT5hZGRfZ3JhcGgoKTwvY29kZT4gbWV0aG9kLiBZb3UgbWF5IHdhbnQgdG8gY2hlY2sgb3V0IHRoZQogICAgICAgICAgICA8YSBocmVmPSJodHRwczovL3d3dy50ZW5zb3JmbG93Lm9yZy90ZW5zb3Jib2FyZC9ncmFwaHMiCiAgICAgICAgICAgICAgPmV4YW1pbmluZyB0aGUgVGVuc29yRmxvdyBncmFwaCB0dXRvcmlhbDwvYQogICAgICAgICAgICA+LgogICAgICAgICAgPC9wPgoKICAgICAgICAgIDxwPgogICAgICAgICAgICBJZiB5b3XigJlyZSBuZXcgdG8gdXNpbmcgVGVuc29yQm9hcmQsIGFuZCB3YW50IHRvIGZpbmQgb3V0IGhvdyB0byBhZGQKICAgICAgICAgICAgZGF0YSBhbmQgc2V0IHVwIHlvdXIgZXZlbnQgZmlsZXMsIGNoZWNrIG91dCB0aGUKICAgICAgICAgICAgPGEKICAgICAgICAgICAgICBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQiCiAgICAgICAgICAgICAgPlJFQURNRTwvYQogICAgICAgICAgICA+CiAgICAgICAgICAgIGFuZCBwZXJoYXBzIHRoZQogICAgICAgICAgICA8YQogICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiCiAgICAgICAgICAgICAgPlRlbnNvckJvYXJkIHR1dG9yaWFsPC9hCiAgICAgICAgICAgID4uCiAgICAgICAgICA8L3A+CgogICAgICAgICAgPHA+CiAgICAgICAgICAgIElmIHlvdSB0aGluayBUZW5zb3JCb2FyZCBpcyBjb25maWd1cmVkIHByb3Blcmx5LCBwbGVhc2Ugc2VlCiAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL3RlbnNvcmZsb3cvdGVuc29yYm9hcmQvYmxvYi9tYXN0ZXIvUkVBRE1FLm1kI215LXRlbnNvcmJvYXJkLWlzbnQtc2hvd2luZy1hbnktZGF0YS13aGF0cy13cm9uZyIKICAgICAgICAgICAgICA+dGhlIHNlY3Rpb24gb2YgdGhlIFJFQURNRSBkZXZvdGVkIHRvIG1pc3NpbmcgZGF0YSBwcm9ibGVtczwvYQogICAgICAgICAgICA+CiAgICAgICAgICAgIGFuZCBjb25zaWRlciBmaWxpbmcgYW4gaXNzdWUgb24gR2l0SHViLgogICAgICAgICAgPC9wPgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9ImdyYXBoYm9hcmQiPgogICAgICAgICAgPHRmLWdyYXBoLWJvYXJkCiAgICAgICAgICAgIGlkPSJncmFwaGJvYXJkIgogICAgICAgICAgICBkZXZpY2VzLWZvci1zdGF0cz0iW1tfZGV2aWNlc0ZvclN0YXRzXV0iCiAgICAgICAgICAgIGNvbG9yLWJ5PSJ7e19jb2xvckJ5fX0iCiAgICAgICAgICAgIGNvbG9yLWJ5LXBhcmFtcz0ie3tfY29sb3JCeVBhcmFtc319IgogICAgICAgICAgICBncmFwaC1oaWVyYXJjaHk9IltbX2dyYXBoSGllcmFyY2h5XV0iCiAgICAgICAgICAgIGdyYXBoPSJbW19ncmFwaF1dIgogICAgICAgICAgICBoaWVyYXJjaHktcGFyYW1zPSJbW19oaWVyYXJjaHlQYXJhbXNdXSIKICAgICAgICAgICAgcHJvZ3Jlc3M9IltbX3Byb2dyZXNzXV0iCiAgICAgICAgICAgIGRlYnVnZ2VyLWRhdGEtZW5hYmxlZD0iW1tfZGVidWdnZXJEYXRhRW5hYmxlZF1dIgogICAgICAgICAgICBhcmUtaGVhbHRoLXBpbGxzLWxvYWRpbmc9IltbX2FyZUhlYWx0aFBpbGxzTG9hZGluZ11dIgogICAgICAgICAgICBkZWJ1Z2dlci1udW1lcmljLWFsZXJ0cz0iW1tfZGVidWdnZXJOdW1lcmljQWxlcnRzXV0iCiAgICAgICAgICAgIG5vZGUtbmFtZXMtdG8taGVhbHRoLXBpbGxzPSJbW19ub2RlTmFtZXNUb0hlYWx0aFBpbGxzXV0iCiAgICAgICAgICAgIGFsbC1zdGVwcy1tb2RlLWVuYWJsZWQ9Int7YWxsU3RlcHNNb2RlRW5hYmxlZH19IgogICAgICAgICAgICBzcGVjaWZpYy1oZWFsdGgtcGlsbC1zdGVwPSJ7e3NwZWNpZmljSGVhbHRoUGlsbFN0ZXB9fSIKICAgICAgICAgICAgaGVhbHRoLXBpbGwtc3RlcC1pbmRleD0iW1tfaGVhbHRoUGlsbFN0ZXBJbmRleF1dIgogICAgICAgICAgICByZW5kZXItaGllcmFyY2h5PSJ7e19yZW5kZXJIaWVyYXJjaHl9fSIKICAgICAgICAgICAgc2VsZWN0ZWQtbm9kZT0ie3tfc2VsZWN0ZWROb2RlfX0iCiAgICAgICAgICAgIHN0YXRzPSJbW19zdGF0c11dIgogICAgICAgICAgICB0cmFjZS1pbnB1dHM9IltbX3RyYWNlSW5wdXRzXV0iCiAgICAgICAgICAgIGF1dG8tZXh0cmFjdC1ub2Rlcz0iW1tfYXV0b0V4dHJhY3ROb2Rlc11dIgogICAgICAgICAgPjwvdGYtZ3JhcGgtYm9hcmQ+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvZGl2PgogICAgPC90Zi1kYXNoYm9hcmQtbGF5b3V0PgogICAgPHN0eWxlPgogICAgICA6aG9zdCAvZGVlcC8gewogICAgICAgIGZvbnQtZmFtaWx5OiAnUm9ib3RvJywgc2Fucy1zZXJpZjsKICAgICAgfQoKICAgICAgLnNpZGViYXIgewogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICB9CgogICAgICAuY2VudGVyIHsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICB9CgogICAgICBwYXBlci1kaWFsb2cgewogICAgICAgIHBhZGRpbmc6IDIwcHg7CiAgICAgIH0KCiAgICAgIC5uby1kYXRhLW1lc3NhZ2UgewogICAgICAgIG1heC13aWR0aDogNTQwcHg7CiAgICAgICAgbWFyZ2luOiA4MHB4IGF1dG8gMCBhdXRvOwogICAgICB9CgogICAgICAuZ3JhcGhib2FyZCB7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICB9CgogICAgICAubm8tZ3JhcGggLmdyYXBoYm9hcmQgewogICAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICAgIH0KCiAgICAgIC5jZW50ZXI6bm90KC5uby1ncmFwaCkgLm5vLWRhdGEtbWVzc2FnZSB7CiAgICAgICAgZGlzcGxheTogbm9uZTsKICAgICAgfQoKICAgICAgYSB7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLWxpbmspOwogICAgICB9CgogICAgICBhOnZpc2l0ZWQgewogICAgICAgIGNvbG9yOiB2YXIoLS10Yi1saW5rLXZpc2l0ZWQpOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLE9yLnByb3RvdHlwZSwiX2RhdGFzZXRzIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sT3IucHJvdG90eXBlLCJfZGF0YXNldHNGZXRjaGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLE9yLnByb3RvdHlwZSwiX3NlbGVjdGVkRGF0YXNldCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxvYnNlcnZlcjoiX3JlbmRlckhpZXJhcmNoeUNoYW5nZWQifSksdygiZGVzaWduOnR5cGUiLGxvKV0sT3IucHJvdG90eXBlLCJfcmVuZGVySGllcmFyY2h5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEFlKV0sT3IucHJvdG90eXBlLCJfcmVxdWVzdE1hbmFnZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsYW4pXSxPci5wcm90b3R5cGUsIl9jYW5jZWxsZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxPci5wcm90b3R5cGUsIl9kZWJ1Z2dlckRhdGFFbmFibGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sT3IucHJvdG90eXBlLCJhbGxTdGVwc01vZGVFbmFibGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLE9yLnByb3RvdHlwZSwic3BlY2lmaWNIZWFsdGhQaWxsU3RlcCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9oZWFsdGhQaWxsc1RvZ2dsZWRPbkNoYW5nZWQifSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxPci5wcm90b3R5cGUsImhlYWx0aFBpbGxzVG9nZ2xlZE9uIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxPci5wcm90b3R5cGUsInNlbGVjdGVkTm9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLE9yLnByb3RvdHlwZSwiX2lzQXR0YWNoZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxPci5wcm90b3R5cGUsIl9pbml0aWFsaXplZCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLE9yLnByb3RvdHlwZSwiX2FyZUhlYWx0aFBpbGxzTG9hZGluZyIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5LG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLE9yLnByb3RvdHlwZSwiX2RlYnVnZ2VyTnVtZXJpY0FsZXJ0cyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxPci5wcm90b3R5cGUsIl9ub2RlTmFtZXNUb0hlYWx0aFBpbGxzIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLE9yLnByb3RvdHlwZSwiX2hlYWx0aFBpbGxTdGVwSW5kZXgiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sT3IucHJvdG90eXBlLCJfaGVhbHRoUGlsbFJlcXVlc3RJZCIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxPci5wcm90b3R5cGUsIl9oZWFsdGhQaWxsU3RlcFJlcXVlc3RUaW1lcklkIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLE9yLnByb3RvdHlwZSwiX2hlYWx0aFBpbGxTdGVwUmVxdWVzdFRpbWVyRGVsYXkiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLE9yLnByb3RvdHlwZSwicnVucyIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZyxub3RpZnk6ITAsb2JzZXJ2ZXI6Il9ydW5PYnNlcnZlciJ9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sT3IucHJvdG90eXBlLCJydW4iLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sT3IucHJvdG90eXBlLCJfc2VsZWN0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLE9yLnByb3RvdHlwZSwiX2NvbXBhdGliaWxpdHlQcm92aWRlciIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLE9yLnByb3RvdHlwZSwiX3RyYWNlSW5wdXRzIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sT3IucHJvdG90eXBlLCJfYXV0b0V4dHJhY3ROb2RlcyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxPci5wcm90b3R5cGUsIl9zZWxlY3RlZEZpbGUiLHZvaWQgMCk7RShbQnQoIl9pc0F0dGFjaGVkIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxPci5wcm90b3R5cGUsIl9tYXliZUluaXRpYWxpemVEYXNoYm9hcmQiLG51bGwpO0UoW0J0KCJfZGF0YXNldHNGZXRjaGVkIiwiX2RhdGFzZXRzIiwicnVuIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxPci5wcm90b3R5cGUsIl9kZXRlcm1pbmVTZWxlY3RlZERhdGFzZXQiLG51bGwpO0UoW0J0KCJfZGF0YXNldHNGZXRjaGVkIiwiX2RhdGFzZXRzIiwiX3NlbGVjdGVkRGF0YXNldCIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sT3IucHJvdG90eXBlLCJfdXBkYXRlU2VsZWN0ZWREYXRhc2V0TmFtZSIsbnVsbCk7T3I9RShbeXQoInRmLWdyYXBoLWRhc2hib2FyZCIpXSxPcik7dmFyIFJIPUVlKE9lKCksMSk7dmFyIGtyPVZyLE1hPWNsYXNzIGV4dGVuZHMgR3QoX28obXQpKXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5tb2RlPSJvZmZzZXQiLHRoaXMudGltZVByb3BlcnR5PSJzdGVwIix0aGlzLmJpbnM9ImJpbnMiLHRoaXMueD0ieCIsdGhpcy5keD0iZHgiLHRoaXMueT0ieSIsdGhpcy5jb2xvclNjYWxlPWtyLnNjYWxlT3JkaW5hbChrci5zY2hlbWVDYXRlZ29yeTEwKSx0aGlzLm1vZGVUcmFuc2l0aW9uRHVyYXRpb249NTAwLHRoaXMuX25hbWU9bnVsbCx0aGlzLl9kYXRhPW51bGx9cmVhZHkoKXtzdXBlci5yZWFkeSgpLHRoaXMuc2NvcGVTdWJ0cmVlKHRoaXMuJC5zdmcsITApfWF0dGFjaGVkKCl7dGhpcy5fYXR0YWNoZWQ9ITB9ZGV0YWNoZWQoKXt0aGlzLl9hdHRhY2hlZD0hMX1zZXRTZXJpZXNEYXRhKHQscil7dGhpcy5fbmFtZT10LHRoaXMuX2RhdGE9cix0aGlzLnJlZHJhdygpfV9yZWRyYXdPbkNoYW5nZSgpe3RoaXMucmVkcmF3KCl9cmVkcmF3KCl7dGhpcy5fZHJhdygwKX1fbW9kZVJlZHJhdygpe3RoaXMuX2RyYXcodGhpcy5tb2RlVHJhbnNpdGlvbkR1cmF0aW9uKX1fZHJhdyh0KXtpZighdGhpcy5fYXR0YWNoZWR8fCF0aGlzLl9kYXRhKXJldHVybjtpZih0PT09dm9pZCAwKXRocm93IG5ldyBFcnJvcigidnotaGlzdG9ncmFtLXRpbWVzZXJpZXMgX2RyYXcgbmVlZHMgZHVyYXRpb24iKTtpZih0aGlzLl9kYXRhLmxlbmd0aDw9MCl0aHJvdyBuZXcgRXJyb3IoIk5vdCBlbm91Z2ggc3RlcHMgaW4gdGhlIGRhdGEiKTtpZighdGhpcy5fZGF0YVswXS5oYXNPd25Qcm9wZXJ0eSh0aGlzLmJpbnMpKXRocm93IG5ldyBFcnJvcigiTm8gYmlucyBwcm9wZXJ0eSBvZiAnIit0aGlzLmJpbnMrIicgaW4gZGF0YSIpO2lmKHRoaXMuX2RhdGFbMF1bdGhpcy5iaW5zXS5sZW5ndGg8PTApdGhyb3cgbmV3IEVycm9yKCJNdXN0IGhhdmUgYXQgbGVhc3Qgb25lIGJpbiBpbiBiaW5zIGluIGRhdGEiKTtpZighdGhpcy5fZGF0YVswXVt0aGlzLmJpbnNdWzBdLmhhc093blByb3BlcnR5KHRoaXMueCkpdGhyb3cgbmV3IEVycm9yKCJObyB4IHByb3BlcnR5ICciK3RoaXMueCsiJyBvbiBiaW5zIGRhdGEiKTtpZighdGhpcy5fZGF0YVswXVt0aGlzLmJpbnNdWzBdLmhhc093blByb3BlcnR5KHRoaXMuZHgpKXRocm93IG5ldyBFcnJvcigiTm8gZHggcHJvcGVydHkgJyIrdGhpcy5keCsiJyBvbiBiaW5zIGRhdGEiKTtpZighdGhpcy5fZGF0YVswXVt0aGlzLmJpbnNdWzBdLmhhc093blByb3BlcnR5KHRoaXMueSkpdGhyb3cgbmV3IEVycm9yKCJObyB5IHByb3BlcnR5ICciK3RoaXMueSsiJyBvbiBiaW5zIGRhdGEiKTt2YXIgcj10aGlzLnRpbWVQcm9wZXJ0eSxuPXRoaXMueCxpPXRoaXMuYmlucyxvPXRoaXMuZHgsYT10aGlzLnkscz10aGlzLl9kYXRhLGw9dGhpcy5fbmFtZSxjPXRoaXMubW9kZSx1PWtyLmhjbCh0aGlzLmNvbG9yU2NhbGUobCkpLGg9a3Iuc2VsZWN0KHRoaXMuJC50b29sdGlwKSxmPWZ1bmN0aW9uKE50KXtyZXR1cm4gTnRbbl19LHA9ZnVuY3Rpb24oTnQpe3JldHVybiBOdFthXX0sZD1mdW5jdGlvbihOdCl7cmV0dXJuIE50W29dfSxnPWZ1bmN0aW9uKE50KXtyZXR1cm4gTnRbbl0rTnRbb119LF89ZnVuY3Rpb24oTnQpe3JldHVybiBOdFtyXX07cj09PSJyZWxhdGl2ZSImJihfPWZ1bmN0aW9uKE50KXtyZXR1cm4gTnQud2FsbF90aW1lLXNbMF0ud2FsbF90aW1lfSk7dmFyIHk9dGhpcy4kLnN2Zy5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSx4PXkud2lkdGgsYj15LmhlaWdodCxTLEM9e3RvcDo1LHJpZ2h0OjYwLGJvdHRvbToyMCxsZWZ0OjI0fTtjPT09Im9mZnNldCI/KFM9Yi8yLjUsQy50b3A9Uys1KTpTPWItQy50b3AtQy5ib3R0b207dmFyIFA9eC1DLmxlZnQtQy5yaWdodCxrPWItQy50b3AtQy5ib3R0b20sTz1rci5taW4ocyxmKSxEPWtyLm1heChzLGcpLEI9a3IuZm9ybWF0KCIuM24iKSxJPWtyLmZvcm1hdCgiLjBmIik7cj09PSJ3YWxsX3RpbWUiP0k9a3IudGltZUZvcm1hdCgiJW0vJWQgJVgiKTpyPT09InJlbGF0aXZlIiYmKEk9ZnVuY3Rpb24oTnQpe3JldHVybiBrci5mb3JtYXQoIi4xciIpKE50LzM2ZTUpKyJoIn0pO3ZhciBMPXMubWFwKGZ1bmN0aW9uKE50LHplKXtyZXR1cm5ba3IubWluKE50W2ldLGYpLGtyLm1heChOdFtpXSxnKV19KSxSPXMubWFwKGZ1bmN0aW9uKE50KXtyZXR1cm4ga3IuZXh0ZW50KE50W2ldLHApfSksRj01MDAsej1rci5leHRlbnQocyxfKSxVPShyPT09IndhbGxfdGltZSI/a3Iuc2NhbGVUaW1lKCk6a3Iuc2NhbGVMaW5lYXIoKSkuZG9tYWluKHopLnJhbmdlKFswLGM9PT0ib2Zmc2V0Ij9rOjBdKSxXPWtyLnNjYWxlTGluZWFyKCkuZG9tYWluKFswLGtyLm1heChzLGZ1bmN0aW9uKE50LHplKXtyZXR1cm4gUlt6ZV1bMV19KV0pLnJhbmdlKFtTLDBdKSxaPWtyLnNjYWxlTGluZWFyKCkuZG9tYWluKFcuZG9tYWluKCkpLnJhbmdlKFtGLDBdKSxydD1rci5zY2FsZUxpbmVhcigpLmRvbWFpbihba3IubWluKHMsZnVuY3Rpb24oTnQsemUpe3JldHVybiBMW3plXVswXX0pLGtyLm1heChzLGZ1bmN0aW9uKE50LHplKXtyZXR1cm4gTFt6ZV1bMV19KV0pLm5pY2UoKS5yYW5nZShbMCxQXSksb3Q9a3Iuc2NhbGVMaW5lYXIoKS5kb21haW4ocnQuZG9tYWluKCkpLnJhbmdlKFswLEZdKTtsZXQgc3Q9a3Iuc2NhbGVMaW5lYXIoKS5kb21haW4oa3IuZXh0ZW50KHMsXykpLnJhbmdlKFt1LmJyaWdodGVyKCksdS5kYXJrZXIoKV0pLmludGVycG9sYXRlKGtyLmludGVycG9sYXRlSGNsKTt2YXIgU3Q9a3IuYXhpc0JvdHRvbShydCkudGlja3MoTWF0aC5tYXgoMixQLzIwKSksYnQ9a3IuYXhpc1JpZ2h0KFUpLnRpY2tzKE1hdGgubWF4KDIsay8xNSkpLnRpY2tGb3JtYXQoSSksTXQ9a3IuYXhpc1JpZ2h0KFcpLnRpY2tzKE1hdGgubWF4KDIsay8xNSkpLnRpY2tTaXplKFArNSkudGlja0Zvcm1hdChCKSxsdD1mdW5jdGlvbihOdCl7cmV0dXJuIE50W25dK050W29dLzJ9LEt0PWtyLmxpbmUoKS54KGZ1bmN0aW9uKE50KXtyZXR1cm4gb3QobHQoTnQpKX0pLnkoZnVuY3Rpb24oTnQpe3JldHVybiBaKE50W2FdKX0pLF90PWZ1bmN0aW9uKE50KXtyZXR1cm4iTSIrb3QobHQoTnRbMF0pKSsiLCIrWigwKSsiTCIrS3QoTnQpLnNsaWNlKDEpKyJMIitvdChsdChOdFtOdC5sZW5ndGgtMV0pKSsiLCIrWigwKX0sY3Q9dGhpcy4kLnN2ZyxYPWtyLnNlbGVjdChjdCksZXQ9WC50cmFuc2l0aW9uKCkuZHVyYXRpb24odCksZHQ9WC5zZWxlY3QoImciKS5jbGFzc2VkKCJzbWFsbCIsZnVuY3Rpb24oKXtyZXR1cm4gUD4wJiZQPD0xNTB9KS5jbGFzc2VkKCJtZWRpdW0iLGZ1bmN0aW9uKCl7cmV0dXJuIFA+MTUwJiZQPD0zMDB9KS5jbGFzc2VkKCJsYXJnZSIsZnVuY3Rpb24oKXtyZXR1cm4gUD4zMDB9KSxxPWV0LnNlbGVjdCgiZyIpLmF0dHIoInRyYW5zZm9ybSIsInRyYW5zbGF0ZSgiK0MubGVmdCsiLCIrQy50b3ArIikiKSxwdD1rci5iaXNlY3RvcihnKS5sZWZ0LGh0PWR0LnNlbGVjdCgiLnN0YWdlIikub24oIm1vdXNlb3ZlciIsZnVuY3Rpb24oKXtUdC5zdHlsZSgib3BhY2l0eSIsMSksQ3Quc3R5bGUoIm9wYWNpdHkiLDEpLGF0LnN0eWxlKCJvcGFjaXR5IiwxKSxDZS5zdHlsZSgib3BhY2l0eSIsMSksaC5zdHlsZSgib3BhY2l0eSIsMSl9KS5vbigibW91c2VvdXQiLGZ1bmN0aW9uKCl7VHQuc3R5bGUoIm9wYWNpdHkiLDApLEN0LnN0eWxlKCJvcGFjaXR5IiwwKSxhdC5zdHlsZSgib3BhY2l0eSIsMCksQ2Uuc3R5bGUoIm9wYWNpdHkiLDApLFR0LmNsYXNzZWQoImhvdmVyLWNsb3Nlc3QiLCExKSxJdC5jbGFzc2VkKCJvdXRsaW5lLWhvdmVyIiwhMSksaC5zdHlsZSgib3BhY2l0eSIsMCl9KS5vbigibW91c2Vtb3ZlIixQdCksd3Q9aHQuc2VsZWN0KCIuYmFja2dyb3VuZCIpLmF0dHIoInRyYW5zZm9ybSIsInRyYW5zbGF0ZSgiKy1DLmxlZnQrIiwiKy1DLnRvcCsiKSIpLmF0dHIoIndpZHRoIix4KS5hdHRyKCJoZWlnaHQiLGIpLGt0PWh0LnNlbGVjdEFsbCgiLmhpc3RvZ3JhbSIpLmRhdGEocyksaWU9a3QuZXhpdCgpLnJlbW92ZSgpLGVlPWt0LmVudGVyKCkuYXBwZW5kKCJnIikuYXR0cigiY2xhc3MiLCJoaXN0b2dyYW0iKSxMZT1lZS5tZXJnZShrdCkuc29ydChmdW5jdGlvbihOdCx6ZSl7cmV0dXJuIF8oTnQpLV8oemUpfSksYXI9cS5zZWxlY3RBbGwoIi5oaXN0b2dyYW0iKS5hdHRyKCJ0cmFuc2Zvcm0iLGZ1bmN0aW9uKE50KXtyZXR1cm4idHJhbnNsYXRlKDAsICIrKGM9PT0ib2Zmc2V0Ij9VKF8oTnQpKS1TOjApKyIpIn0pLGZyPWVlLmFwcGVuZCgibGluZSIpLmF0dHIoImNsYXNzIiwiYmFzZWxpbmUiKSx0dD1hci5zZWxlY3QoIi5iYXNlbGluZSIpLnN0eWxlKCJzdHJva2Utb3BhY2l0eSIsZnVuY3Rpb24oTnQpe3JldHVybiBjPT09Im9mZnNldCI/LjE6MH0pLmF0dHIoInkxIixTKS5hdHRyKCJ5MiIsUykuYXR0cigieDIiLFApLCQ9ZWUuYXBwZW5kKCJwYXRoIikuYXR0cigiY2xhc3MiLCJvdXRsaW5lIiksSXQ9TGUuc2VsZWN0KCIub3V0bGluZSIpLmF0dHIoInZlY3Rvci1lZmZlY3QiLCJub24tc2NhbGluZy1zdHJva2UiKS5hdHRyKCJkIixmdW5jdGlvbihOdCl7cmV0dXJuIF90KE50W2ldKX0pLnN0eWxlKCJzdHJva2Utd2lkdGgiLDEpLCR0PWFyLnNlbGVjdCgiLm91dGxpbmUiKS5hdHRyKCJ0cmFuc2Zvcm0iLCJzY2FsZSgiK1AvRisiLCAiK1MvRisiKSIpLnN0eWxlKCJzdHJva2UiLGZ1bmN0aW9uKE50KXtyZXR1cm4gYz09PSJvZmZzZXQiPyIiOnN0KF8oTnQpKX0pLnN0eWxlKCJmaWxsLW9wYWNpdHkiLGZ1bmN0aW9uKE50KXtyZXR1cm4gYz09PSJvZmZzZXQiPzE6MH0pLnN0eWxlKCJmaWxsIixmdW5jdGlvbihOdCl7cmV0dXJuIHN0KF8oTnQpKX0pLGhlPWVlLmFwcGVuZCgiZyIpLmF0dHIoImNsYXNzIiwiaG92ZXIiKSxUdD1MZS5zZWxlY3QoIi5ob3ZlciIpLnN0eWxlKCJmaWxsIixmdW5jdGlvbihOdCl7cmV0dXJuIHN0KF8oTnQpKX0pO2hlLmFwcGVuZCgiY2lyY2xlIikuYXR0cigiciIsMiksaGUuYXBwZW5kKCJ0ZXh0Iikuc3R5bGUoImRpc3BsYXkiLCJub25lIikuYXR0cigiZHgiLDQpO3ZhciBiZT1kdC5zZWxlY3QoIi54LWF4aXMtaG92ZXIiKS5zZWxlY3RBbGwoIi5sYWJlbCIpLmRhdGEoWyJ4Il0pLG50PWJlLmVudGVyKCkuYXBwZW5kKCJnIikuYXR0cigiY2xhc3MiLCJsYWJlbCIpLEN0PWJlLm1lcmdlKG50KTtudC5hcHBlbmQoInJlY3QiKS5hdHRyKCJ4IiwtMjApLmF0dHIoInkiLDYpLmF0dHIoIndpZHRoIiw0MCkuYXR0cigiaGVpZ2h0IiwxNCksbnQuYXBwZW5kKCJsaW5lIikuYXR0cigieDEiLDApLmF0dHIoIngyIiwwKS5hdHRyKCJ5MSIsMCkuYXR0cigieTIiLDYpLG50LmFwcGVuZCgidGV4dCIpLmF0dHIoImR5IiwxOCk7dmFyIFd0PWR0LnNlbGVjdCgiLnktYXhpcy1ob3ZlciIpLnNlbGVjdEFsbCgiLmxhYmVsIikuZGF0YShbInkiXSksZmU9V3QuZW50ZXIoKS5hcHBlbmQoImciKS5hdHRyKCJjbGFzcyIsImxhYmVsIiksYXQ9V3QubWVyZ2UoZmUpO2ZlLmFwcGVuZCgicmVjdCIpLmF0dHIoIngiLDgpLmF0dHIoInkiLC02KS5hdHRyKCJ3aWR0aCIsNDApLmF0dHIoImhlaWdodCIsMTQpLGZlLmFwcGVuZCgibGluZSIpLmF0dHIoIngxIiwwKS5hdHRyKCJ4MiIsNikuYXR0cigieTEiLDApLmF0dHIoInkyIiwwKSxmZS5hcHBlbmQoInRleHQiKS5hdHRyKCJkeCIsOCkuYXR0cigiZHkiLDQpO3ZhciBzZT1kdC5zZWxlY3QoIi55LXNsaWNlLWF4aXMtaG92ZXIiKS5zZWxlY3RBbGwoIi5sYWJlbCIpLmRhdGEoWyJ5Il0pLFF0PXNlLmVudGVyKCkuYXBwZW5kKCJnIikuYXR0cigiY2xhc3MiLCJsYWJlbCIpLENlPXNlLm1lcmdlKFF0KTtRdC5hcHBlbmQoInJlY3QiKS5hdHRyKCJ4Iiw4KS5hdHRyKCJ5IiwtNikuYXR0cigid2lkdGgiLDQwKS5hdHRyKCJoZWlnaHQiLDE0KSxRdC5hcHBlbmQoImxpbmUiKS5hdHRyKCJ4MSIsMCkuYXR0cigieDIiLDYpLmF0dHIoInkxIiwwKS5hdHRyKCJ5MiIsMCksUXQuYXBwZW5kKCJ0ZXh0IikuYXR0cigiZHgiLDgpLmF0dHIoImR5Iiw0KSxxLnNlbGVjdCgiLnkuYXhpcy5zbGljZSIpLnN0eWxlKCJvcGFjaXR5IixjPT09Im9mZnNldCI/MDoxKS5hdHRyKCJ0cmFuc2Zvcm0iLCJ0cmFuc2xhdGUoMCwgIisoYz09PSJvZmZzZXQiPy1TOjApKyIpIikuY2FsbChNdCkscS5zZWxlY3QoIi54LmF4aXMiKS5hdHRyKCJ0cmFuc2Zvcm0iLCJ0cmFuc2xhdGUoMCwgIitrKyIpIikuY2FsbChTdCkscS5zZWxlY3QoIi55LmF4aXMiKS5zdHlsZSgib3BhY2l0eSIsYz09PSJvZmZzZXQiPzE6MCkuYXR0cigidHJhbnNmb3JtIiwidHJhbnNsYXRlKCIrUCsiLCAiKyhjPT09Im9mZnNldCI/MDprKSsiKSIpLmNhbGwoYnQpLHEuc2VsZWN0QWxsKCIudGljayB0ZXh0IikuYXR0cigiZmlsbCIsIiNhYWEiKSxxLnNlbGVjdEFsbCgiLmF4aXMgcGF0aC5kb21haW4iKS5hdHRyKCJzdHJva2UiLCJub25lIik7ZnVuY3Rpb24gUHQoKXt2YXIgTnQ9a3IubW91c2UodGhpcyksemU9cnQuaW52ZXJ0KE50WzBdKSx5bj1VLmludmVydChOdFsxXSk7ZnVuY3Rpb24gV2koY24pe3JldHVybiBNYXRoLm1pbihjbltpXS5sZW5ndGgtMSxwdChjbltpXSx6ZSkpfXZhciBBcixQYT0xLzAsaG87VHQuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihjbixjeCl7dmFyIHJwPVdpKGNuKTtobz1jbjt2YXIgSz1ydChjbltpXVtycF1bbl0rY25baV1bcnBdW29dLzIpLGd0PVcoY25baV1bcnBdW2FdKSxFdD1jPT09Im9mZnNldCI/VShfKGNuKSktKFMtZ3QpOmd0LHh0PU1hdGguYWJzKE50WzFdLUV0KTtyZXR1cm4geHQ8UGEmJihQYT14dCxBcj1jbiksInRyYW5zbGF0ZSgiK0srIiwiK2d0KyIpIn0pLFR0LnNlbGVjdCgidGV4dCIpLnRleHQoZnVuY3Rpb24oY24pe3ZhciBjeD1XaShjbik7cmV0dXJuIGNuW2ldW2N4XVthXX0pLFR0LmNsYXNzZWQoImhvdmVyLWNsb3Nlc3QiLGZ1bmN0aW9uKGNuKXtyZXR1cm4gY249PT1Bcn0pLEl0LmNsYXNzZWQoIm91dGxpbmUtaG92ZXIiLGZ1bmN0aW9uKGNuKXtyZXR1cm4gY249PT1Bcn0pO3ZhciBJYT1XaShobyk7Q3QuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihjbil7cmV0dXJuInRyYW5zbGF0ZSgiK3J0KGhvW2ldW0lhXVtuXStob1tpXVtJYV1bb10vMikrIiwgIitrKyIpIn0pLnNlbGVjdCgidGV4dCIpLnRleHQoZnVuY3Rpb24oY24pe3JldHVybiBCKGhvW2ldW0lhXVtuXStob1tpXVtJYV1bb10vMil9KTt2YXIgbHg9YnQudGlja0Zvcm1hdCgpO2F0LmF0dHIoInRyYW5zZm9ybSIsZnVuY3Rpb24oY24pe3JldHVybiJ0cmFuc2xhdGUoIitQKyIsICIrKGM9PT0ib2Zmc2V0Ij9VKF8oQXIpKTowKSsiKSJ9KS5zdHlsZSgiZGlzcGxheSIsYz09PSJvZmZzZXQiPyIiOiJub25lIikuc2VsZWN0KCJ0ZXh0IikudGV4dChmdW5jdGlvbihjbil7cmV0dXJuIGx4KF8oQXIpKX0pO3ZhciBjbT1NdC50aWNrRm9ybWF0KCk7Q2UuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihjbil7cmV0dXJuInRyYW5zbGF0ZSgiK1ArIiwgIisoYz09PSJvZmZzZXQiPzA6VyhBcltpXVtJYV1bYV0pKSsiKSJ9KS5zdHlsZSgiZGlzcGxheSIsYz09PSJvZmZzZXQiPyJub25lIjoiIikuc2VsZWN0KCJ0ZXh0IikudGV4dChmdW5jdGlvbihjbil7cmV0dXJuIGNtKEFyW2ldW0lhXVthXSl9KTt2YXIgSjA9a3IubW91c2UoY3QpO2guc3R5bGUoInRyYW5zZm9ybSIsInRyYW5zbGF0ZSgiKyhKMFswXSsxNSkrInB4LCIrKEowWzFdLTE1KSsicHgpIikuc2VsZWN0KCJzcGFuIikudGV4dChjPT09Im9mZnNldCI/Y20oQXJbaV1bSWFdW2FdKToocj09PSJzdGVwIj8ic3RlcCAiOiIiKStseChfKEFyKSkpfX19O01hLnRlbXBsYXRlPVFgCiAgICA8ZGl2IGlkPSJ0b29sdGlwIj48c3Bhbj48L3NwYW4+PC9kaXY+CiAgICA8c3ZnIGlkPSJzdmciPgogICAgICA8Zz4KICAgICAgICA8ZyBjbGFzcz0iYXhpcyB4Ij48L2c+CiAgICAgICAgPGcgY2xhc3M9ImF4aXMgeSI+PC9nPgogICAgICAgIDxnIGNsYXNzPSJheGlzIHkgc2xpY2UiPjwvZz4KICAgICAgICA8ZyBjbGFzcz0ic3RhZ2UiPgogICAgICAgICAgPHJlY3QgY2xhc3M9ImJhY2tncm91bmQiPjwvcmVjdD4KICAgICAgICA8L2c+CiAgICAgICAgPGcgY2xhc3M9IngtYXhpcy1ob3ZlciI+PC9nPgogICAgICAgIDxnIGNsYXNzPSJ5LWF4aXMtaG92ZXIiPjwvZz4KICAgICAgICA8ZyBjbGFzcz0ieS1zbGljZS1heGlzLWhvdmVyIj48L2c+CiAgICAgIDwvZz4KICAgIDwvc3ZnPgoKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGNvbG9yOiAjYWFhOwogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgICAgZmxleC1zaHJpbms6IDE7CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICAgIC0tdnotaGlzdG9ncmFtLXRpbWVzZXJpZXMtaG92ZXItYmctY29sb3I6ICNmZmY7CiAgICAgICAgLS12ei1oaXN0b2dyYW0tdGltZXNlcmllcy1vdXRsaW5lLWNvbG9yOiAjZmZmOwogICAgICAgIC0tdnotaGlzdG9ncmFtLXRpbWVzZXJpZXMtaG92ZXItb3V0bGluZS1jb2xvcjogIzAwMDsKICAgICAgfQoKICAgICAgOmhvc3QoLmRhcmstbW9kZSkgewogICAgICAgIC0tdnotaGlzdG9ncmFtLXRpbWVzZXJpZXMtaG92ZXItYmctY29sb3I6IHZhcigKICAgICAgICAgIC0tcHJpbWFyeS1iYWNrZ3JvdW5kLWNvbG9yCiAgICAgICAgKTsKICAgICAgICAtLXZ6LWhpc3RvZ3JhbS10aW1lc2VyaWVzLW91dGxpbmUtY29sb3I6IHZhcigtLXBhcGVyLWdyZXktNjAwKTsKICAgICAgICAtLXZ6LWhpc3RvZ3JhbS10aW1lc2VyaWVzLWhvdmVyLW91dGxpbmUtY29sb3I6ICNmZmY7CiAgICAgIH0KCiAgICAgIHN2ZyB7CiAgICAgICAgZm9udC1mYW1pbHk6IHJvYm90bywgc2Fucy1zZXJpZjsKICAgICAgICBvdmVyZmxvdzogdmlzaWJsZTsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgICAgZmxleC1zaHJpbms6IDE7CiAgICAgIH0KCiAgICAgIHRleHQgewogICAgICAgIGZpbGw6IGN1cnJlbnRDb2xvcjsKICAgICAgfQoKICAgICAgI3Rvb2x0aXAgewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBvcGFjaXR5OiAwOwogICAgICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgICAgIGZvbnQtc2l6ZTogMTFweDsKICAgICAgfQoKICAgICAgLmJhY2tncm91bmQgewogICAgICAgIGZpbGwtb3BhY2l0eTogMDsKICAgICAgICBmaWxsOiByZWQ7CiAgICAgIH0KCiAgICAgIC5oaXN0b2dyYW0gewogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICB9CgogICAgICAuaG92ZXIgewogICAgICAgIGZvbnQtc2l6ZTogOXB4OwogICAgICAgIGRvbWluYW50LWJhc2VsaW5lOiBtaWRkbGU7CiAgICAgICAgb3BhY2l0eTogMDsKICAgICAgfQoKICAgICAgLmhvdmVyIGNpcmNsZSB7CiAgICAgICAgc3Ryb2tlOiB3aGl0ZTsKICAgICAgICBzdHJva2Utb3BhY2l0eTogMC41OwogICAgICAgIHN0cm9rZS13aWR0aDogMXB4OwogICAgICB9CgogICAgICAuaG92ZXIgdGV4dCB7CiAgICAgICAgZmlsbDogYmxhY2s7CiAgICAgICAgb3BhY2l0eTogMDsKICAgICAgfQoKICAgICAgLmhvdmVyLmhvdmVyLWNsb3Nlc3QgY2lyY2xlIHsKICAgICAgICBmaWxsOiB2YXIoLS12ei1oaXN0b2dyYW0tdGltZXNlcmllcy1ob3Zlci1vdXRsaW5lLWNvbG9yKSAhaW1wb3J0YW50OwogICAgICB9CgogICAgICAuaG92ZXIuaG92ZXItY2xvc2VzdCB0ZXh0IHsKICAgICAgICBvcGFjaXR5OiAxOwogICAgICB9CgogICAgICAuYmFzZWxpbmUgewogICAgICAgIHN0cm9rZTogYmxhY2s7CiAgICAgICAgc3Ryb2tlLW9wYWNpdHk6IDAuMTsKICAgICAgfQoKICAgICAgLm91dGxpbmUgewogICAgICAgIGZpbGw6IG5vbmU7CiAgICAgICAgc3Ryb2tlOiB2YXIoLS12ei1oaXN0b2dyYW0tdGltZXNlcmllcy1vdXRsaW5lLWNvbG9yKTsKICAgICAgICBzdHJva2Utb3BhY2l0eTogMC41OwogICAgICB9CgogICAgICAub3V0bGluZS5vdXRsaW5lLWhvdmVyIHsKICAgICAgICBzdHJva2U6IHZhcigtLXZ6LWhpc3RvZ3JhbS10aW1lc2VyaWVzLWhvdmVyLW91dGxpbmUtY29sb3IpICFpbXBvcnRhbnQ7CiAgICAgICAgc3Ryb2tlLW9wYWNpdHk6IDE7CiAgICAgIH0KCiAgICAgIC54LWF4aXMtaG92ZXIsCiAgICAgIC55LWF4aXMtaG92ZXIsCiAgICAgIC55LXNsaWNlLWF4aXMtaG92ZXIgewogICAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICB9CgogICAgICAueC1heGlzLWhvdmVyIC5sYWJlbCwKICAgICAgLnktYXhpcy1ob3ZlciAubGFiZWwsCiAgICAgIC55LXNsaWNlLWF4aXMtaG92ZXIgLmxhYmVsIHsKICAgICAgICBvcGFjaXR5OiAwOwogICAgICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgICAgIGZvbnQtc2l6ZTogMTFweDsKICAgICAgICB0ZXh0LWFuY2hvcjogZW5kOwogICAgICB9CgogICAgICAueC1heGlzLWhvdmVyIHRleHQgewogICAgICAgIHRleHQtYW5jaG9yOiBtaWRkbGU7CiAgICAgIH0KCiAgICAgIC55LWF4aXMtaG92ZXIgdGV4dCwKICAgICAgLnktc2xpY2UtYXhpcy1ob3ZlciB0ZXh0IHsKICAgICAgICB0ZXh0LWFuY2hvcjogc3RhcnQ7CiAgICAgIH0KCiAgICAgIC54LWF4aXMtaG92ZXIgbGluZSwKICAgICAgLnktYXhpcy1ob3ZlciBsaW5lLAogICAgICAueS1zbGljZS1heGlzLWhvdmVyIGxpbmUgewogICAgICAgIHN0cm9rZTogY3VycmVudENvbG9yOwogICAgICB9CgogICAgICAueC1heGlzLWhvdmVyIHJlY3QsCiAgICAgIC55LWF4aXMtaG92ZXIgcmVjdCwKICAgICAgLnktc2xpY2UtYXhpcy1ob3ZlciByZWN0IHsKICAgICAgICBmaWxsOiB2YXIoLS12ei1oaXN0b2dyYW0tdGltZXNlcmllcy1ob3Zlci1iZy1jb2xvcik7CiAgICAgIH0KCiAgICAgICN0b29sdGlwLAogICAgICAueC1heGlzLWhvdmVyIHRleHQsCiAgICAgIC55LWF4aXMtaG92ZXIgdGV4dCwKICAgICAgLnktc2xpY2UtYXhpcy1ob3ZlciB0ZXh0IHsKICAgICAgICBjb2xvcjogdmFyKC0tdnotaGlzdG9ncmFtLXRpbWVzZXJpZXMtaG92ZXItb3V0bGluZS1jb2xvcik7CiAgICAgIH0KCiAgICAgIC5heGlzIHsKICAgICAgICBmb250LXNpemU6IDExcHg7CiAgICAgIH0KCiAgICAgIC5heGlzIHBhdGguZG9tYWluIHsKICAgICAgICBmaWxsOiBub25lOwogICAgICB9CgogICAgICAuYXhpcyAudGljayBsaW5lIHsKICAgICAgICBzdHJva2U6ICNkZGQ7CiAgICAgIH0KCiAgICAgIC5heGlzLnNsaWNlIHsKICAgICAgICBvcGFjaXR5OiAwOwogICAgICB9CgogICAgICAuYXhpcy5zbGljZSAudGljayBsaW5lIHsKICAgICAgICBzdHJva2UtZGFzaGFycmF5OiAyOwogICAgICB9CgogICAgICAuc21hbGwgLmF4aXMgdGV4dCB7CiAgICAgICAgZGlzcGxheTogbm9uZTsKICAgICAgfQogICAgICAuc21hbGwgLmF4aXMgLnRpY2s6Zmlyc3Qtb2YtdHlwZSB0ZXh0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgfQogICAgICAuc21hbGwgLmF4aXMgLnRpY2s6bGFzdC1vZi10eXBlIHRleHQgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICB9CiAgICAgIC5tZWRpdW0gLmF4aXMgdGV4dCB7CiAgICAgICAgZGlzcGxheTogbm9uZTsKICAgICAgfQogICAgICAubWVkaXVtIC5heGlzIC50aWNrOm50aC1jaGlsZCgybiArIDEpIHRleHQgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICB9CiAgICAgIC5sYXJnZSAuYXhpcyB0ZXh0IHsKICAgICAgICBkaXNwbGF5OiBub25lOwogICAgICB9CiAgICAgIC5sYXJnZSAuYXhpcyAudGljazpudGgtY2hpbGQoMm4gKyAxKSB0ZXh0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLE1hLnByb3RvdHlwZSwibW9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxNYS5wcm90b3R5cGUsInRpbWVQcm9wZXJ0eSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxNYS5wcm90b3R5cGUsImJpbnMiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sTWEucHJvdG90eXBlLCJ4Iix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLE1hLnByb3RvdHlwZSwiZHgiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sTWEucHJvdG90eXBlLCJ5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLE1hLnByb3RvdHlwZSwiY29sb3JTY2FsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxNYS5wcm90b3R5cGUsIm1vZGVUcmFuc2l0aW9uRHVyYXRpb24iLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxNYS5wcm90b3R5cGUsIl9hdHRhY2hlZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxNYS5wcm90b3R5cGUsIl9uYW1lIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sTWEucHJvdG90eXBlLCJfZGF0YSIsdm9pZCAwKTtFKFtCdCgidGltZVByb3BlcnR5IiwiY29sb3JTY2FsZSIsIl9hdHRhY2hlZCIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sTWEucHJvdG90eXBlLCJfcmVkcmF3T25DaGFuZ2UiLG51bGwpO0UoW0J0KCJtb2RlIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxNYS5wcm90b3R5cGUsIl9tb2RlUmVkcmF3IixudWxsKTtNYT1FKFt5dCgidnotaGlzdG9ncmFtLXRpbWVzZXJpZXMiKV0sTWEpO2Z1bmN0aW9uIHB1cihlKXtsZXRbdCxyLG5dPWU7cmV0dXJue3dhbGxfdGltZTp0LHN0ZXA6cixtaW46TG0obi5tYXAoKFtpLCxdKT0+aSkpLG1heDpsdShuLm1hcCgoWyxpXSk9PmkpKSxidWNrZXRzOm4ubWFwKChbaSxvLGFdKT0+KHtsZWZ0OmkscmlnaHQ6byxjb3VudDphfSkpfX1mdW5jdGlvbiBkdXIoZSx0LHIsbj0zMCl7KHQ9PT12b2lkIDB8fHI9PW51bGwpJiYodD0wLHI9MCkscj09PXQmJihyPXQqMS4xKzEsdD10LzEuMS0xKTtsZXQgaT0oci10KS9uLG89MCxhPVtdO2ZvcihsZXQgcz0wO3M8bjtzKyspe2xldCBsPXQrcyppLGM9bCtpLHU9MDtmb3IoO288ZS5idWNrZXRzLmxlbmd0aDspe2xldCBoPU1hdGgubWluKHIsZS5idWNrZXRzW29dLnJpZ2h0KSxmPU1hdGgubWF4KHQsZS5idWNrZXRzW29dLmxlZnQpO2lmKGgtZj4wKXtsZXQgZD1NYXRoLm1pbihoLGMpLU1hdGgubWF4KGYsbCksZz1kLyhoLWYpKmUuYnVja2V0c1tvXS5jb3VudDt1Kz1kPjA/ZzowfWVsc2V7bGV0IGQ9Yz49cjt1Kz1sPD1mJiYoZD9oPD1jOmg8Yyk/ZS5idWNrZXRzW29dLmNvdW50OjB9aWYoaD5jKWJyZWFrO28rK31hLnB1c2goe3g6bCxkeDppLHk6dX0pfXJldHVybiBhfWZ1bmN0aW9uIHJjZShlKXtsZXQgdD1lLm1hcChwdXIpLHI9TG0odCxpPT5pLm1pbiksbj1sdSh0LGk9PmkubWF4KTtyZXR1cm4gdC5tYXAoaT0+KHt3YWxsX3RpbWU6aS53YWxsX3RpbWUsc3RlcDppLnN0ZXAsYmluczpkdXIoaSxyLG4pfSkpfXZhciBFYT1jbGFzcyBleHRlbmRzIGtTKEd0KG10KSl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuZ2V0RGF0YUxvYWROYW1lPSh7cnVuOnR9KT0+dCx0aGlzLnJlcXVlc3REYXRhPSh0LHIsbik9PntsZXQgbz12ZSgpLnBsdWdpblJvdXRlKCJoaXN0b2dyYW1zIiwiL2hpc3RvZ3JhbXMiKTtQcm9taXNlLmFsbCh0Lm1hcChhPT57bGV0IHM9Q24obyx7dGFnOmEudGFnLHJ1bjphLnJ1bn0pO3JldHVybiB0aGlzLnJlcXVlc3RNYW5hZ2VyLnJlcXVlc3QocykudGhlbihsPT52b2lkIHIoe2l0ZW06YSxkYXRhOmx9KSl9KSkuZmluYWxseSgoKT0+dm9pZCBuKCkpfSx0aGlzLmxvYWREYXRhQ2FsbGJhY2s9KHQscixuKT0+e2xldCBpPXJjZShuKSxvPXRoaXMuZ2V0RGF0YUxvYWROYW1lKHIpO3RoaXMuJC5jaGFydC5zZXRTZXJpZXNEYXRhKG8saSl9LHRoaXMuX2NvbG9yU2NhbGVGdW5jdGlvbj1mbix0aGlzLl9leHBhbmRlZD0hMX1fcmVsb2FkT25SdW5UYWdSZXF1ZXN0TWFuYWdlckNoYW5nZSgpe3RoaXMucmVsb2FkKCl9X3VwZGF0ZURhdGFUb0xvYWQoKXt2YXIgdD10aGlzLnJ1bixyPXRoaXMudGFnO3RoaXMuZGF0YVRvTG9hZD1be3J1bjp0LHRhZzpyfV19Z2V0IF9ydW5Db2xvcigpe3ZhciB0PXRoaXMucnVuO3JldHVybiB0aGlzLl9jb2xvclNjYWxlRnVuY3Rpb24odCl9cmVkcmF3KCl7dGhpcy4kLmNoYXJ0LnJlZHJhdygpfV90b2dnbGVFeHBhbmRlZCh0KXt0aGlzLnNldCgiX2V4cGFuZGVkIiwhdGhpcy5fZXhwYW5kZWQpLHRoaXMucmVkcmF3KCl9fTtFYS50ZW1wbGF0ZT1RYAogICAgPHRmLWNhcmQtaGVhZGluZwogICAgICB0YWc9IltbdGFnXV0iCiAgICAgIHJ1bj0iW1tydW5dXSIKICAgICAgZGlzcGxheS1uYW1lPSJbW3RhZ01ldGFkYXRhLmRpc3BsYXlOYW1lXV0iCiAgICAgIGRlc2NyaXB0aW9uPSJbW3RhZ01ldGFkYXRhLmRlc2NyaXB0aW9uXV0iCiAgICAgIGNvbG9yPSJbW19ydW5Db2xvcl1dIgogICAgPjwvdGYtY2FyZC1oZWFkaW5nPgogICAgPCEtLQogICAgICBUaGUgbWFpbiBoaXN0b2dyYW0gdGhhdCB3ZSByZW5kZXIuIERhdGEgaXMgc2V0IGRpcmVjdGx5IHdpdGgKICAgICAgXGBzZXRTZXJpZXNEYXRhXGAsIG5vdCB3aXRoIGEgYm91bmQgcHJvcGVydHkuCiAgICAtLT4KICAgIDx2ei1oaXN0b2dyYW0tdGltZXNlcmllcwogICAgICBpZD0iY2hhcnQiCiAgICAgIHRpbWUtcHJvcGVydHk9IltbdGltZVByb3BlcnR5XV0iCiAgICAgIG1vZGU9IltbaGlzdG9ncmFtTW9kZV1dIgogICAgICBjb2xvci1zY2FsZT0iW1tfY29sb3JTY2FsZUZ1bmN0aW9uXV0iCiAgICA+PC92ei1oaXN0b2dyYW0tdGltZXNlcmllcz4KICAgIDxkaXYgc3R5bGU9ImRpc3BsYXk6IGZsZXg7IGZsZXgtZGlyZWN0aW9uOiByb3c7Ij4KICAgICAgPHBhcGVyLWljb24tYnV0dG9uCiAgICAgICAgc2VsZWN0ZWQkPSJbW19leHBhbmRlZF1dIgogICAgICAgIGljb249ImZ1bGxzY3JlZW4iCiAgICAgICAgb24tdGFwPSJfdG9nZ2xlRXhwYW5kZWQiCiAgICAgID48L3BhcGVyLWljb24tYnV0dG9uPgogICAgPC9kaXY+CiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgd2lkdGg6IDMzMHB4OwogICAgICAgIGhlaWdodDogMjM1cHg7CiAgICAgICAgbWFyZ2luLXJpZ2h0OiAxMHB4OwogICAgICAgIG1hcmdpbi1ib3R0b206IDE1cHg7CiAgICAgIH0KICAgICAgOmhvc3QoW19leHBhbmRlZF0pIHsKICAgICAgICB3aWR0aDogNzAwcHg7CiAgICAgICAgaGVpZ2h0OiA1MDBweDsKICAgICAgfQoKICAgICAgdnotaGlzdG9ncmFtLXRpbWVzZXJpZXMgewogICAgICAgIC1tb3otdXNlci1zZWxlY3Q6IG5vbmU7CiAgICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICB3aWxsLWNoYW5nZTogdHJhbnNmb3JtOwogICAgICB9CgogICAgICBwYXBlci1pY29uLWJ1dHRvbiB7CiAgICAgICAgY29sb3I6ICMyMTk2ZjM7CiAgICAgICAgYm9yZGVyLXJhZGl1czogMTAwJTsKICAgICAgICB3aWR0aDogMzJweDsKICAgICAgICBoZWlnaHQ6IDMycHg7CiAgICAgICAgcGFkZGluZzogNHB4OwogICAgICB9CgogICAgICBwYXBlci1pY29uLWJ1dHRvbltzZWxlY3RlZF0gewogICAgICAgIGJhY2tncm91bmQ6IHZhcigtLXRiLXVpLWxpZ2h0LWFjY2VudCk7CiAgICAgIH0KCiAgICAgIHRmLWNhcmQtaGVhZGluZyB7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTBweDsKICAgICAgICB3aWR0aDogOTAlOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sRWEucHJvdG90eXBlLCJydW4iLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sRWEucHJvdG90eXBlLCJ0YWciLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sRWEucHJvdG90eXBlLCJnZXREYXRhTG9hZE5hbWUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsQWUpXSxFYS5wcm90b3R5cGUsInJlcXVlc3RNYW5hZ2VyIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEVhLnByb3RvdHlwZSwibG9hZERhdGFDYWxsYmFjayIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxFYS5wcm90b3R5cGUsInRhZ01ldGFkYXRhIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLEVhLnByb3RvdHlwZSwidGltZVByb3BlcnR5Iix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLEVhLnByb3RvdHlwZSwiaGlzdG9ncmFtTW9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbildLEVhLnByb3RvdHlwZSwiX2NvbG9yU2NhbGVGdW5jdGlvbiIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4scmVmbGVjdFRvQXR0cmlidXRlOiEwfSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxFYS5wcm90b3R5cGUsIl9leHBhbmRlZCIsdm9pZCAwKTtFKFtCdCgicnVuIiwidGFnIiwicmVxdWVzdE1hbmFnZXIiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLEVhLnByb3RvdHlwZSwiX3JlbG9hZE9uUnVuVGFnUmVxdWVzdE1hbmFnZXJDaGFuZ2UiLG51bGwpO0UoW0J0KCJydW4iLCJ0YWciKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLEVhLnByb3RvdHlwZSwiX3VwZGF0ZURhdGFUb0xvYWQiLG51bGwpO0UoW1J0KCJydW4iKSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxFYS5wcm90b3R5cGUsIl9ydW5Db2xvciIsbnVsbCk7RWE9RShbeXQoInRmLWhpc3RvZ3JhbS1sb2FkZXIiKV0sRWEpO3ZhciBscz1jbGFzcyBleHRlbmRzIEd0KG10KXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5yZWxvYWRPblJlYWR5PSEwLHRoaXMuX2hpc3RvZ3JhbU1vZGU9Im9mZnNldCIsdGhpcy5fdGltZVByb3BlcnR5PSJzdGVwIix0aGlzLl9yZXN0YW1wPSExLHRoaXMuX3JlcXVlc3RNYW5hZ2VyPW5ldyBBZX1fcmVkcmF3Q2F0ZWdvcnlQYW5lKHQscil7IXJ8fHQudGFyZ2V0LnF1ZXJ5U2VsZWN0b3JBbGwoInRmLWhpc3RvZ3JhbS1sb2FkZXIiKS5mb3JFYWNoKG49Pm4ucmVkcmF3KCkpfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLnJlbG9hZE9uUmVhZHkmJnRoaXMucmVsb2FkKCl9cmVsb2FkKCl7dGhpcy5fZmV0Y2hUYWdzKCkudGhlbigoKT0+e3RoaXMuX3JlbG9hZEhpc3RvZ3JhbXMoKX0pfV9mZXRjaFRhZ3MoKXtsZXQgdD12ZSgpLnBsdWdpblJvdXRlKCJoaXN0b2dyYW1zIiwiL3RhZ3MiKTtyZXR1cm4gdGhpcy5fcmVxdWVzdE1hbmFnZXIucmVxdWVzdCh0KS50aGVuKHI9PntpZihSSC5pc0VxdWFsKHIsdGhpcy5fcnVuVG9UYWdJbmZvKSlyZXR1cm47bGV0IG49UkgubWFwVmFsdWVzKHIsbz0+T2JqZWN0LmtleXMobykpLGk9JGkobik7dGhpcy5zZXQoIl9kYXRhTm90Rm91bmQiLGkubGVuZ3RoPT09MCksdGhpcy5zZXQoIl9ydW5Ub1RhZyIsbiksdGhpcy5zZXQoIl9ydW5Ub1RhZ0luZm8iLHIpLHRoaXMuYXN5bmMoKCk9Pnt0aGlzLnNldCgiX2NhdGVnb3JpZXNEb21SZWFkeSIsITApfSl9KX1fcmVsb2FkSGlzdG9ncmFtcygpe3ZhciB0Oyh0PXRoaXMucm9vdCk9PW51bGx8fHQucXVlcnlTZWxlY3RvckFsbCgidGYtaGlzdG9ncmFtLWxvYWRlciIpLmZvckVhY2gocj0+e3IucmVsb2FkKCl9KX1fc2hvdWxkT3Blbih0KXtyZXR1cm4gdDw9Mn1nZXQgX2NhdGVnb3JpZXMoKXt2YXIgdD10aGlzLl9ydW5Ub1RhZyxyPXRoaXMuX3NlbGVjdGVkUnVucyxuPXRoaXMuX3RhZ0ZpbHRlcixpPXRoaXMuX2NhdGVnb3JpZXNEb21SZWFkeTtyZXR1cm4gUWwodCxyLG4pfV90YWdNZXRhZGF0YSh0LHIsbil7cmV0dXJuIHRbcl1bbl19fTtscy50ZW1wbGF0ZT1RYAogICAgPHRmLWRhc2hib2FyZC1sYXlvdXQ+CiAgICAgIDxkaXYgc2xvdD0ic2lkZWJhciI+CiAgICAgICAgPGRpdiBjbGFzcz0ic2V0dGluZ3MiPgogICAgICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhci1zZWN0aW9uIj4KICAgICAgICAgICAgPHRmLW9wdGlvbi1zZWxlY3RvcgogICAgICAgICAgICAgIGlkPSJoaXN0b2dyYW1Nb2RlU2VsZWN0b3IiCiAgICAgICAgICAgICAgbmFtZT0iSGlzdG9ncmFtIG1vZGUiCiAgICAgICAgICAgICAgc2VsZWN0ZWQtaWQ9Int7X2hpc3RvZ3JhbU1vZGV9fSIKICAgICAgICAgICAgPgogICAgICAgICAgICAgIDxwYXBlci1idXR0b24gaWQ9Im92ZXJsYXkiPm92ZXJsYXk8L3BhcGVyLWJ1dHRvbj4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uIGlkPSJvZmZzZXQiPm9mZnNldDwvcGFwZXItYnV0dG9uPgogICAgICAgICAgICA8L3RmLW9wdGlvbi1zZWxlY3Rvcj4KICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhci1zZWN0aW9uIj4KICAgICAgICAgICAgPHRmLW9wdGlvbi1zZWxlY3RvcgogICAgICAgICAgICAgIGlkPSJ0aW1lUHJvcGVydHlTZWxlY3RvciIKICAgICAgICAgICAgICBuYW1lPSJPZmZzZXQgdGltZSBheGlzIgogICAgICAgICAgICAgIHNlbGVjdGVkLWlkPSJ7e190aW1lUHJvcGVydHl9fSIKICAgICAgICAgICAgPgogICAgICAgICAgICAgIDxwYXBlci1idXR0b24gaWQ9InN0ZXAiPnN0ZXA8L3BhcGVyLWJ1dHRvbj4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uIGlkPSJyZWxhdGl2ZSI+cmVsYXRpdmU8L3BhcGVyLWJ1dHRvbj4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uIGlkPSJ3YWxsX3RpbWUiPndhbGw8L3BhcGVyLWJ1dHRvbj4KICAgICAgICAgICAgPC90Zi1vcHRpb24tc2VsZWN0b3I+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyLXNlY3Rpb24gcnVucy1zZWxlY3RvciI+CiAgICAgICAgICA8dGYtcnVucy1zZWxlY3RvciBzZWxlY3RlZC1ydW5zPSJ7e19zZWxlY3RlZFJ1bnN9fSI+CiAgICAgICAgICA8L3RmLXJ1bnMtc2VsZWN0b3I+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IHNsb3Q9ImNlbnRlciI+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19kYXRhTm90Rm91bmRdXSI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJuby1kYXRhLXdhcm5pbmciPgogICAgICAgICAgICA8aDM+Tm8gaGlzdG9ncmFtIGRhdGEgd2FzIGZvdW5kLjwvaDM+CiAgICAgICAgICAgIDxwPlByb2JhYmxlIGNhdXNlczo8L3A+CiAgICAgICAgICAgIDx1bD4KICAgICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgICBZb3UgaGF2ZW7igJl0IHdyaXR0ZW4gYW55IGhpc3RvZ3JhbSBkYXRhIHRvIHlvdXIgZXZlbnQgZmlsZXMuCiAgICAgICAgICAgICAgPC9saT4KICAgICAgICAgICAgICA8bGk+VGVuc29yQm9hcmQgY2Fu4oCZdCBmaW5kIHlvdXIgZXZlbnQgZmlsZXMuPC9saT4KICAgICAgICAgICAgPC91bD4KCiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgIElmIHlvdeKAmXJlIG5ldyB0byB1c2luZyBUZW5zb3JCb2FyZCwgYW5kIHdhbnQgdG8gZmluZCBvdXQgaG93IHRvCiAgICAgICAgICAgICAgYWRkIGRhdGEgYW5kIHNldCB1cCB5b3VyIGV2ZW50IGZpbGVzLCBjaGVjayBvdXQgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCIKICAgICAgICAgICAgICAgID5SRUFETUU8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIHBlcmhhcHMgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiCiAgICAgICAgICAgICAgICA+VGVuc29yQm9hcmQgdHV0b3JpYWw8L2EKICAgICAgICAgICAgICA+LgogICAgICAgICAgICA8L3A+CgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICBJZiB5b3UgdGhpbmsgVGVuc29yQm9hcmQgaXMgY29uZmlndXJlZCBwcm9wZXJseSwgcGxlYXNlIHNlZQogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQjbXktdGVuc29yYm9hcmQtaXNudC1zaG93aW5nLWFueS1kYXRhLXdoYXRzLXdyb25nIgogICAgICAgICAgICAgICAgPnRoZSBzZWN0aW9uIG9mIHRoZSBSRUFETUUgZGV2b3RlZCB0byBtaXNzaW5nIGRhdGEgcHJvYmxlbXM8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIGNvbnNpZGVyIGZpbGluZyBhbiBpc3N1ZSBvbiBHaXRIdWIuCiAgICAgICAgICAgIDwvcD4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbWyFfZGF0YU5vdEZvdW5kXV0iPgogICAgICAgICAgPHRmLXRhZy1maWx0ZXJlciB0YWctZmlsdGVyPSJ7e190YWdGaWx0ZXJ9fSI+PC90Zi10YWctZmlsdGVyZXI+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19jYXRlZ29yaWVzXV0iIGFzPSJjYXRlZ29yeSI+CiAgICAgICAgICAgIDx0Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldwogICAgICAgICAgICAgIGNhdGVnb3J5PSJbW2NhdGVnb3J5XV0iCiAgICAgICAgICAgICAgaW5pdGlhbC1vcGVuZWQ9IltbX3Nob3VsZE9wZW4oaW5kZXgpXV0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8dGYtaGlzdG9ncmFtLWxvYWRlcgogICAgICAgICAgICAgICAgICBydW49IltbaXRlbS5ydW5dXSIKICAgICAgICAgICAgICAgICAgdGFnPSJbW2l0ZW0udGFnXV0iCiAgICAgICAgICAgICAgICAgIGFjdGl2ZT0iW1thY3RpdmVdXSIKICAgICAgICAgICAgICAgICAgdGFnLW1ldGFkYXRhPSJbW190YWdNZXRhZGF0YShfcnVuVG9UYWdJbmZvLCBpdGVtLnJ1biwgaXRlbS50YWcpXV0iCiAgICAgICAgICAgICAgICAgIHRpbWUtcHJvcGVydHk9IltbX3RpbWVQcm9wZXJ0eV1dIgogICAgICAgICAgICAgICAgICBoaXN0b2dyYW0tbW9kZT0iW1tfaGlzdG9ncmFtTW9kZV1dIgogICAgICAgICAgICAgICAgICByZXF1ZXN0LW1hbmFnZXI9IltbX3JlcXVlc3RNYW5hZ2VyXV0iCiAgICAgICAgICAgICAgICA+PC90Zi1oaXN0b2dyYW0tbG9hZGVyPgogICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgIDwvdGYtY2F0ZWdvcnktcGFnaW5hdGVkLXZpZXc+CiAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDwvZGl2PgogICAgPC90Zi1kYXNoYm9hcmQtbGF5b3V0PgoKICAgIDxzdHlsZSBpbmNsdWRlPSJkYXNoYm9hcmQtc3R5bGUiPjwvc3R5bGU+CiAgICA8c3R5bGU+CiAgICAgIC5uby1kYXRhLXdhcm5pbmcgewogICAgICAgIG1heC13aWR0aDogNTQwcHg7CiAgICAgICAgbWFyZ2luOiA4MHB4IGF1dG8gMCBhdXRvOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxscy5wcm90b3R5cGUsInJlbG9hZE9uUmVhZHkiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sbHMucHJvdG90eXBlLCJfaGlzdG9ncmFtTW9kZSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxscy5wcm90b3R5cGUsIl90aW1lUHJvcGVydHkiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLGxzLnByb3RvdHlwZSwiX3NlbGVjdGVkUnVucyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxscy5wcm90b3R5cGUsIl9ydW5Ub1RhZyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxscy5wcm90b3R5cGUsIl9ydW5Ub1RhZ0luZm8iLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxscy5wcm90b3R5cGUsIl9kYXRhTm90Rm91bmQiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sbHMucHJvdG90eXBlLCJfdGFnRmlsdGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sbHMucHJvdG90eXBlLCJfcmVzdGFtcCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGxzLnByb3RvdHlwZSwiX2NhdGVnb3JpZXNEb21SZWFkeSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixBZSldLGxzLnByb3RvdHlwZSwiX3JlcXVlc3RNYW5hZ2VyIix2b2lkIDApO0UoW1J0KCJfcnVuVG9UYWciLCJfc2VsZWN0ZWRSdW5zIiwiX3RhZ0ZpbHRlciIsIl9jYXRlZ29yaWVzRG9tUmVhZHkiKSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLGxzLnByb3RvdHlwZSwiX2NhdGVnb3JpZXMiLG51bGwpO2xzPUUoW3l0KCJ0Zi1oaXN0b2dyYW0tZGFzaGJvYXJkIildLGxzKTt2YXIgdTM9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG49ITApe3RoaXMuX2FwaVVybD10LHRoaXMuX3JlcXVlc3RNYW5hZ2VyPXIsdGhpcy5fdXNlSHR0cEdldD1ufWdldEV4cGVyaW1lbnQodCl7cmV0dXJuIHRoaXMuX3NlbmRSZXF1ZXN0KCJleHBlcmltZW50Iix0KX1nZXREb3dubG9hZFVybCh0LHIsbil7cmV0dXJuIHRoaXMuX2FwaVVybCsiL2Rvd25sb2FkX2RhdGE/IituZXcgVVJMU2VhcmNoUGFyYW1zKHtmb3JtYXQ6dCxjb2x1bW5zVmlzaWJpbGl0eTpKU09OLnN0cmluZ2lmeShuKSxyZXF1ZXN0OkpTT04uc3RyaW5naWZ5KHIpfSl9bGlzdFNlc3Npb25Hcm91cHModCl7cmV0dXJuIHRoaXMuX3NlbmRSZXF1ZXN0KCJzZXNzaW9uX2dyb3VwcyIsdCl9bGlzdE1ldHJpY0V2YWxzKHQpe3JldHVybiB0aGlzLl9zZW5kUmVxdWVzdCgibWV0cmljX2V2YWxzIix0KX1fc2VuZFJlcXVlc3QodCxyKXtpZih0aGlzLl91c2VIdHRwR2V0KXtsZXQgbz1lbmNvZGVVUklDb21wb25lbnQoSlNPTi5zdHJpbmdpZnkocikpLGE9dGhpcy5fYXBpVXJsKyIvIit0KyI/cmVxdWVzdD0iK287cmV0dXJuIHRoaXMuX3JlcXVlc3RNYW5hZ2VyLnJlcXVlc3QoYSl9bGV0IG49bmV3IFV4O24ud2l0aENyZWRlbnRpYWxzPSEwLG4ubWV0aG9kVHlwZT1BbS5QT1NULG4uY29udGVudFR5cGU9InRleHQvcGxhaW4iLG4uYm9keT1KU09OLnN0cmluZ2lmeShyKTtsZXQgaT10aGlzLl9hcGlVcmwrIi8iK3Q7cmV0dXJuIHRoaXMuX3JlcXVlc3RNYW5hZ2VyLnJlcXVlc3RXaXRoT3B0aW9ucyhpLG4pfX07dmFyIGZjZT1FZShPZSgpLDEpO3ZhciBOSD1jbGFzcyBleHRlbmRzIG10e2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLm9yaWVudGF0aW9uPSJob3Jpem9udGFsIn19O05ILnRlbXBsYXRlPVFgCiAgICA8c2xvdCBuYW1lPSJjb250ZW50Ij48L3Nsb3Q+CgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgIH0KCiAgICAgIDpob3N0IHNsb3QgewogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICB9CgogICAgICA6aG9zdCA6OnNsb3R0ZWQoKikgewogICAgICAgIGZsZXg6IDAgMCBhdXRvOwogICAgICB9CgogICAgICA6aG9zdChbb3JpZW50YXRpb249J2hvcml6b250YWwnXSkgc2xvdCB7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IHJvdzsKICAgICAgICBvdmVyZmxvdy14OiBhdXRvOwogICAgICB9CgogICAgICA6aG9zdChbb3JpZW50YXRpb249J3ZlcnRpY2FsJ10pIHNsb3QgewogICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgb3ZlcmZsb3cteTogYXV0bzsKICAgICAgfQoKICAgICAgOmhvc3QgOjpzbG90dGVkKCo6bm90KDpsYXN0LWNoaWxkKSkgewogICAgICAgIGJvcmRlcjogMCBzb2xpZCB2YXIoLS1kaXZpZGVyLWNvbG9yLCAjY2NjKTsKICAgICAgfQoKICAgICAgOmhvc3QoW29yaWVudGF0aW9uPSd2ZXJ0aWNhbCddKSA6OnNsb3R0ZWQoKjpub3QoOmxhc3QtY2hpbGQpKSB7CiAgICAgICAgYm9yZGVyLWJvdHRvbS13aWR0aDogNXB4OwogICAgICB9CgogICAgICA6aG9zdChbb3JpZW50YXRpb249J2hvcml6b250YWwnXSkgOjpzbG90dGVkKCo6bm90KDpsYXN0LWNoaWxkKSkgewogICAgICAgIGJvcmRlci1yaWdodC13aWR0aDogNXB4OwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpTdHJpbmcscmVmbGVjdFRvQXR0cmlidXRlOiEwfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLE5ILnByb3RvdHlwZSwib3JpZW50YXRpb24iLHZvaWQgMCk7Tkg9RShbeXQoImhwYXJhbXMtc3BsaXQtbGF5b3V0IildLE5IKTt2YXIgY3M9e307S3MoY3Mse2NvbHVtblZhbHVlQnlJbmRleDooKT0+eDAsY29sdW1uVmFsdWVCeVZpc2libGVJbmRleDooKT0+RkgsZXVjbGlkZWFuRGlzdDooKT0+aDMsZmlsdGVyU2V0OigpPT5NdXIsZ2V0QWJzb2x1dGVDb2x1bW5JbmRleDooKT0+T0gsaGFzaE9mU3RyaW5nOigpPT5tY3QsaHBhcmFtTmFtZTooKT0+RmQsaHBhcmFtVmFsdWVCeUluZGV4OigpPT5hY2UsaHBhcmFtVmFsdWVCeU5hbWU6KCk9Pm9jZSxocGFyYW1WYWx1ZUJ5VmlzaWJsZUluZGV4OigpPT51Y2UsaXNOdWxsT3JVbmRlZmluZWQ6KCk9PmJ1cixsMk5vcm1TcXVhcmVkOigpPT5wMyxtZXRyaWNOYW1lOigpPT5RdSxtZXRyaWNWYWx1ZUJ5SW5kZXg6KCk9PnNjZSxtZXRyaWNWYWx1ZUJ5TmFtZTooKT0+ZjMsbWV0cmljVmFsdWVCeVZpc2libGVJbmRleDooKT0+ekgsbnVtQ29sdW1uczooKT0+aGN0LG51bUhQYXJhbXM6KCk9Pm5jZSxudW1NZXRyaWNzOigpPT5pY2UsbnVtVmlzaWJsZUNvbHVtbnM6KCk9Pl91cixudW1WaXNpYmxlSFBhcmFtczooKT0+bGNlLG51bVZpc2libGVNZXRyaWNzOigpPT5jY2UsbnVtZXJpY0NvbHVtbkV4dGVudDooKT0+ZmN0LHBvaW50VG9SZWN0YW5nbGVEaXN0OigpPT5oY2UscHJldHR5UHJpbnQ6KCk9PmIwLHByZXR0eVByaW50SFBhcmFtVmFsdWVCeU5hbWU6KCk9Pnl1cixwcmV0dHlQcmludE1ldHJpY1ZhbHVlQnlOYW1lOigpPT52dXIscXVhZFRyZWVWaXNpdFBvaW50c0luRGlzazooKT0+U3VyLHF1YWRUcmVlVmlzaXRQb2ludHNJblJlY3Q6KCk9Pnd1cixyb3RhdGVTdHI6KCk9Pnh1cixzY2hlbWFDb2x1bW5OYW1lOigpPT51Y3Qsc2NoZW1hVmlzaWJsZUNvbHVtbk5hbWU6KCk9Pmd1cixzZXNzaW9uR3JvdXBXaXRoTmFtZTooKT0+cGN0LHNldEFycmF5T2JzZXJ2YWJseTooKT0+ZGN0LHRyYW5zbGF0ZVN0cjooKT0+X1AsdmlzaWJsZU51bWVyaWNDb2x1bW5FeHRlbnQ6KCk9PmdQfSk7dmFyIERIPUVlKE9lKCksMSk7ZnVuY3Rpb24gRmQoZSl7cmV0dXJuIGUuZGlzcGxheU5hbWUhPT0iIiYmZS5kaXNwbGF5TmFtZSE9PXZvaWQgMD9lLmRpc3BsYXlOYW1lOmUubmFtZX1mdW5jdGlvbiBRdShlKXtpZihlLmRpc3BsYXlOYW1lIT09IiImJmUuZGlzcGxheU5hbWUhPT12b2lkIDApcmV0dXJuIGUuZGlzcGxheU5hbWU7bGV0IHQ9ZS5uYW1lLmdyb3VwLHI9ZS5uYW1lLnRhZztyZXR1cm4gdD09PXZvaWQgMCYmKHQ9IiIpLHI9PT12b2lkIDAmJihyPSIiKSx0PT09IiI/cjp0KyIuIityfWZ1bmN0aW9uIHVjdChlLHQpe2lmKHQ8ZS5ocGFyYW1Db2x1bW5zLmxlbmd0aClyZXR1cm4gRmQoZS5ocGFyYW1Db2x1bW5zW3RdLmhwYXJhbUluZm8pO2xldCByPXQtZS5ocGFyYW1Db2x1bW5zLmxlbmd0aDtyZXR1cm4gUXUoZS5tZXRyaWNDb2x1bW5zW3JdLm1ldHJpY0luZm8pfWZ1bmN0aW9uIG5jZShlKXtyZXR1cm4gZS5ocGFyYW1Db2x1bW5zLmxlbmd0aH1mdW5jdGlvbiBpY2UoZSl7cmV0dXJuIGUubWV0cmljQ29sdW1ucy5sZW5ndGh9ZnVuY3Rpb24gaGN0KGUpe3JldHVybiBuY2UoZSkraWNlKGUpfWZ1bmN0aW9uIG9jZShlLHQpe3JldHVybiBlW3RdfWZ1bmN0aW9uIGYzKGUsdCl7cmV0dXJuIGUuZmluZChyPT5ESC5pc0VxdWFsKHIubmFtZSx0KSl9ZnVuY3Rpb24gYWNlKGUsdCxyKXtyZXR1cm4gdC5ocGFyYW1zW2UuaHBhcmFtQ29sdW1uc1tyXS5ocGFyYW1JbmZvLm5hbWVdfWZ1bmN0aW9uIHNjZShlLHQscil7bGV0IG49ZS5tZXRyaWNDb2x1bW5zW3JdLm1ldHJpY0luZm8ubmFtZSxpPWYzKHQubWV0cmljVmFsdWVzLG4pO3JldHVybiBpPT09dm9pZCAwP3ZvaWQgMDppLnZhbHVlfWZ1bmN0aW9uIHgwKGUsdCxyKXtyZXR1cm4gcjxlLmhwYXJhbUNvbHVtbnMubGVuZ3RoP2FjZShlLHQscik6c2NlKGUsdCxyLWUuaHBhcmFtQ29sdW1ucy5sZW5ndGgpfWZ1bmN0aW9uIGZjdChlLHQscil7cmV0dXJuIGFhKHQsbj0+eDAoZSxuLHIpKX1mdW5jdGlvbiBPSChlLHQscil7bGV0IG47aWYocjx0LmhwYXJhbUluZm9zLmxlbmd0aCluPWUuaHBhcmFtQ29sdW1ucy5maW5kSW5kZXgoaT0+aS5ocGFyYW1JbmZvLm5hbWU9PT10LmhwYXJhbUluZm9zW3JdLm5hbWUpO2Vsc2V7bGV0IGk9ci10LmhwYXJhbUluZm9zLmxlbmd0aCxvPXQubWV0cmljSW5mb3NbaV0ubmFtZTtuPWUuaHBhcmFtQ29sdW1ucy5sZW5ndGgrZS5tZXRyaWNDb2x1bW5zLmZpbmRJbmRleChhPT5hLm1ldHJpY0luZm8ubmFtZT09PW8pfXJldHVybiBjb25zb2xlLmFzc2VydChuIT09LTEpLG59ZnVuY3Rpb24gZ3VyKGUsdCl7aWYodDxlLmhwYXJhbUluZm9zLmxlbmd0aClyZXR1cm4gRmQoZS5ocGFyYW1JbmZvc1t0XSk7bGV0IHI9dC1lLmhwYXJhbUluZm9zLmxlbmd0aDtyZXR1cm4gUXUoZS5tZXRyaWNJbmZvc1tyXSl9ZnVuY3Rpb24gbGNlKGUpe3JldHVybiBlLmhwYXJhbUluZm9zLmxlbmd0aH1mdW5jdGlvbiBjY2UoZSl7cmV0dXJuIGUubWV0cmljSW5mb3MubGVuZ3RofWZ1bmN0aW9uIF91cihlKXtyZXR1cm4gbGNlKGUpK2NjZShlKX1mdW5jdGlvbiBnUChlLHQscil7cmV0dXJuIGFhKHQsbj0+RkgoZSxuLHIpKX1mdW5jdGlvbiB5dXIoZSx0KXtyZXR1cm4gYjAob2NlKGUsdCkpfWZ1bmN0aW9uIHZ1cihlLHQpe3JldHVybiBiMChmMyhlLHQpKX1mdW5jdGlvbiBwY3QoZSx0KXtyZXR1cm4gZS5maW5kKHI9PnIubmFtZT09PXQpfWZ1bmN0aW9uIHVjZShlLHQscil7cmV0dXJuIHQuaHBhcmFtc1tlLmhwYXJhbUluZm9zW3JdLm5hbWVdfWZ1bmN0aW9uIHpIKGUsdCxyKXtsZXQgbj1lLm1ldHJpY0luZm9zW3JdLm5hbWUsaT1mMyh0Lm1ldHJpY1ZhbHVlcyxuKTtyZXR1cm4gaT09PXZvaWQgMD92b2lkIDA6aS52YWx1ZX1mdW5jdGlvbiBGSChlLHQscil7cmV0dXJuIHI8ZS5ocGFyYW1JbmZvcy5sZW5ndGg/dWNlKGUsdCxyKTp6SChlLHQsci1lLmhwYXJhbUluZm9zLmxlbmd0aCl9ZnVuY3Rpb24gYjAoZSl7cmV0dXJuIERILmlzTnVtYmVyKGUpP2UudG9QcmVjaXNpb24oNSk6ZT09PXZvaWQgMD8iIjplLnRvU3RyaW5nKCl9ZnVuY3Rpb24gcDMoZSx0KXtyZXR1cm4gZSplK3QqdH1mdW5jdGlvbiBoMyhlLHQscixuKXtyZXR1cm4gTWF0aC5zcXJ0KHAzKGUtcix0LW4pKX1mdW5jdGlvbiBoY2UoZSx0LHIsbixpLG8pe2lmKGU8ciYmdDxuKXJldHVybiBoMyhlLHQscixuKTtpZihyPD1lJiZlPGkmJnQ8bilyZXR1cm4gbi10O2lmKGk8PWUmJnQ8bilyZXR1cm4gaDMoZSx0LGksbik7aWYoZTxyJiZuPD10JiZ0PG8pcmV0dXJuIHItZTtpZihyPD1lJiZlPGkmJm48PXQmJnQ8bylyZXR1cm4gMDtpZihpPD1lJiZuPD10JiZ0PG8pcmV0dXJuIGUtaTtpZihlPHImJm88PXQpcmV0dXJuIGgzKGUsdCxyLG8pO2lmKHI8PWUmJmU8aSYmbzw9dClyZXR1cm4gdC1vO2lmKGk8PWUmJm88PXQpcmV0dXJuIGgzKGUsdCxpLG8pO3Rocm93IlBvaW50ICh4LHkpIG11c3QgYmUgaW4gb25lIG9mIHRoZSByZWdpb25zIGRlZmluZWQgYWJvdmUuIn1mdW5jdGlvbiBfUChlLHQpe3JldHVybiB0PT09dm9pZCAwPyJ0cmFuc2xhdGUoIitlKyIpIjoidHJhbnNsYXRlKCIrZSsiLCIrdCsiKSJ9ZnVuY3Rpb24geHVyKGUsdCxyKXtsZXQgbj0icm90YXRlKCIrZTtyZXR1cm4gdCE9PXZvaWQgMCYmciE9PXZvaWQgMCYmKG49bisiLCIrdCsiLCIrciksbj1uKyIpIixufWZ1bmN0aW9uIGJ1cihlKXtyZXR1cm4gZT09bnVsbH1mdW5jdGlvbiB3dXIoZSx0LHIsbixpLG8pe2UudmlzaXQoKGEscyxsLGMsdSk9PntpZihhLmxlbmd0aD09PXZvaWQgMCl7ZG97bGV0IGg9ZS54KCkoYS5kYXRhKSxmPWUueSgpKGEuZGF0YSk7dDw9aCYmaDxuJiZyPD1mJiZmPGkmJm8oYS5kYXRhKX13aGlsZShhPWEubmV4dCk7cmV0dXJuITB9cmV0dXJuIHM+PW58fGM8PXR8fGw+PWl8fHU8PXJ9KX1mdW5jdGlvbiBTdXIoZSx0LHIsbixpKXtlLnZpc2l0KChvLGEscyxsLGMpPT57aWYoby5sZW5ndGg9PT12b2lkIDApe2Rve2xldCB1PWUueCgpKG8uZGF0YSksaD1lLnkoKShvLmRhdGEpLGY9aDModCxyLHUsaCk7Zjw9biYmaShvLmRhdGEsZil9d2hpbGUobz1vLm5leHQpO3JldHVybiEwfXJldHVybiBoY2UodCxyLGEscyxsLGMpPm59KX1mdW5jdGlvbiBNdXIoZSx0KXtsZXQgcj1uZXcgU2V0O3JldHVybiBlLmZvckVhY2gobj0+e3QobikmJnIuYWRkKG4pfSkscn1mdW5jdGlvbiBkY3QoZSx0LHIpe2xldCBuPWUuZ2V0KHQsZSk7aWYoIUFycmF5LmlzQXJyYXkobikpe2Uuc2V0KHQscik7cmV0dXJufWUuc3BsaWNlLmFwcGx5KGUsW3QsMCxuLmxlbmd0aF0uY29uY2F0KHIpKX1mdW5jdGlvbiBtY3QoZSl7bGV0IHQ9MDtmb3IobGV0IHI9MDtyPGUubGVuZ3RoOysrcil0PXQqMzErZS5jaGFyQ29kZUF0KHIpJjQyOTQ5NjcyOTU7cmV0dXJuIHQrRUkoMiwzMSl9dmFyIHRpPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmNvbmZpZ3VyYXRpb249e3NjaGVtYTp7aHBhcmFtQ29sdW1uczpBcnJheSgpLG1ldHJpY0NvbHVtbnM6QXJyYXkoKX0sY29sdW1uc1Zpc2liaWxpdHk6QXJyYXkoKSx2aXNpYmxlU2NoZW1hOntocGFyYW1JbmZvczpBcnJheSgpLG1ldHJpY0luZm9zOkFycmF5KCl9fSx0aGlzLnNlc3Npb25Hcm91cHM9W10sdGhpcy5kYXRhTG9hZGVkV2l0aE5vbkVtcHR5SHBhcmFtcz0hMSx0aGlzLmRhdGFMb2FkZWRXaXRoRW1wdHlIcGFyYW1zPSExLHRoaXMuX3N0YXR1c2VzPVt7dmFsdWU6IlNUQVRVU19VTktOT1dOIixkaXNwbGF5TmFtZToiVW5rbm93biIsYWxsb3dlZDohMH0se3ZhbHVlOiJTVEFUVVNfU1VDQ0VTUyIsZGlzcGxheU5hbWU6IlN1Y2Nlc3MiLGFsbG93ZWQ6ITB9LHt2YWx1ZToiU1RBVFVTX0ZBSUxVUkUiLGRpc3BsYXlOYW1lOiJGYWlsdXJlIixhbGxvd2VkOiEwfSx7dmFsdWU6IlNUQVRVU19SVU5OSU5HIixkaXNwbGF5TmFtZToiUnVubmluZyIsYWxsb3dlZDohMH1dLHRoaXMuX2dldEV4cGVyaW1lbnRSZXNvbHZlZD1uZXcgUHJvbWlzZSh0PT57dGhpcy5fcmVzb2x2ZUdldEV4cGVyaW1lbnQ9dH0pLHRoaXMuX2xpc3RTZXNzaW9uR3JvdXBzQ2FuY2VsbGVyPW5ldyBhbix0aGlzLl9wYWdlU2l6ZUlucHV0PXt2YWx1ZToiMTAwIixpbnZhbGlkOiExfSx0aGlzLl9wYWdlTnVtYmVySW5wdXQ9e3ZhbHVlOiIxIixpbnZhbGlkOiExfSx0aGlzLl9wYWdlQ291bnRTdHI9Ij8iLHRoaXMuX2hwYXJhbU5hbWU9RmQsdGhpcy5fbWV0cmljTmFtZT1RdSx0aGlzLl9wcmV0dHlQcmludD1iMH1yZWxvYWQoKXt0aGlzLl9xdWVyeVNlcnZlcigpfV9jc3ZVcmwodCxyKXtyZXR1cm4gdGhpcy5fZG93bmxvYWREYXRhVXJsKHQsciwiY3N2Iil9X2pzb25VcmwodCxyKXtyZXR1cm4gdGhpcy5fZG93bmxvYWREYXRhVXJsKHQsciwianNvbiIpfV9sYXRleFVybCh0LHIpe3JldHVybiB0aGlzLl9kb3dubG9hZERhdGFVcmwodCxyLCJsYXRleCIpfV9kb3dubG9hZERhdGFVcmwodCxyLG4pe2xldCBpPXIuY29sdW1uc1Zpc2liaWxpdHk7cmV0dXJuIHRoaXMuYmFja2VuZC5nZXREb3dubG9hZFVybChuLHQsaSl9X2NvbXB1dGVFeHBlcmltZW50QW5kUmVsYXRlZFByb3BzKCl7bGV0IHQ9Y3M7aWYodC5pc051bGxPclVuZGVmaW5lZCh0aGlzLmJhY2tlbmQpfHx0LmlzTnVsbE9yVW5kZWZpbmVkKHRoaXMuZXhwZXJpbWVudE5hbWUpKXJldHVybjtsZXQgcj17ZXhwZXJpbWVudE5hbWU6dGhpcy5leHBlcmltZW50TmFtZX07dGhpcy5iYWNrZW5kLmdldEV4cGVyaW1lbnQocikudGhlbihuPT57ZmNlLmlzRXF1YWwobix0aGlzLl9leHBlcmltZW50KXx8KHRoaXMuc2V0KCJfZXhwZXJpbWVudCIsbiksdGhpcy5fY29tcHV0ZUhQYXJhbXMoKSx0aGlzLl9jb21wdXRlTWV0cmljcygpLHRoaXMuX3F1ZXJ5U2VydmVyKCksdGhpcy5fcmVzb2x2ZUdldEV4cGVyaW1lbnQoKSl9KS5maW5hbGx5KCgpPT57dGhpcy5fY29tcHV0ZURhdGFGb3VuZCgpfSl9X2NvbXB1dGVEYXRhRm91bmQoKXtsZXQgdD1Cb29sZWFuKHRoaXMuX2V4cGVyaW1lbnQmJnRoaXMuX2V4cGVyaW1lbnQuaHBhcmFtSW5mb3MmJnRoaXMuX2V4cGVyaW1lbnQuaHBhcmFtSW5mb3MubGVuZ3RoPjAmJnRoaXMuX2V4cGVyaW1lbnQubWV0cmljSW5mb3MmJnRoaXMuX2V4cGVyaW1lbnQubWV0cmljSW5mb3MubGVuZ3RoPjApO3RoaXMuc2V0KCJkYXRhTG9hZGVkV2l0aE5vbkVtcHR5SHBhcmFtcyIsdCksdGhpcy5zZXQoImRhdGFMb2FkZWRXaXRoRW1wdHlIcGFyYW1zIiwhdCl9X2NvbXB1dGVIUGFyYW1zKCl7bGV0IHQ9W107dGhpcy5fZXhwZXJpbWVudC5ocGFyYW1JbmZvcy5mb3JFYWNoKChuLGkpPT57bGV0IG89e2luZm86bixkaXNwbGF5ZWQ6aTw1LGZpbHRlcjp7fX07by5pbmZvLmhhc093blByb3BlcnR5KCJkb21haW5EaXNjcmV0ZSIpPyhvLmZpbHRlci5kb21haW5EaXNjcmV0ZT1bXSxvLmluZm8uZG9tYWluRGlzY3JldGUuZm9yRWFjaChhPT57by5maWx0ZXIuZG9tYWluRGlzY3JldGUucHVzaCh7dmFsdWU6YSxjaGVja2VkOiEwfSl9KSk6by5pbmZvLnR5cGU9PT0iREFUQV9UWVBFX0JPT0wiP28uZmlsdGVyLmRvbWFpbkRpc2NyZXRlPVt7dmFsdWU6ITEsY2hlY2tlZDohMH0se3ZhbHVlOiEwLGNoZWNrZWQ6ITB9XTpvLmluZm8udHlwZT09PSJEQVRBX1RZUEVfRkxPQVQ2NCI/by5maWx0ZXIuaW50ZXJ2YWw9e21pbjp7dmFsdWU6IiIsaW52YWxpZDohMX0sbWF4Ont2YWx1ZToiIixpbnZhbGlkOiExfX06by5pbmZvLnR5cGU9PT0iREFUQV9UWVBFX1NUUklORyI/by5maWx0ZXIucmVnZXhwPSIiOmNvbnNvbGUud2FybigidW5rbm93biBocGFyYW0uaW5mby50eXBlOiAlcyIsby5pbmZvLnR5cGUpLHQucHVzaChvKX0pLHRoaXMuc2V0KCJfaHBhcmFtcyIsdCl9X2NvbXB1dGVNZXRyaWNzKCl7bGV0IHQ9W107dGhpcy5fZXhwZXJpbWVudC5tZXRyaWNJbmZvcy5mb3JFYWNoKChuLGkpPT57bGV0IG89e2luZm86bixmaWx0ZXI6e2ludGVydmFsOnttaW46e3ZhbHVlOiIiLGludmFsaWQ6ITF9LG1heDp7dmFsdWU6IiIsaW52YWxpZDohMX19fSxkaXNwbGF5ZWQ6aTw1fTt0LnB1c2gobyl9KSx0aGlzLnNldCgiX21ldHJpY3MiLHQpfV9jb21wdXRlU2NoZW1hKCl7cmV0dXJuIXRoaXMuX2hwYXJhbXN8fCF0aGlzLl9tZXRyaWNzP3tocGFyYW1Db2x1bW5zOltdLG1ldHJpY0NvbHVtbnM6W119OntocGFyYW1Db2x1bW5zOnRoaXMuX2hwYXJhbXMubWFwKHQ9Pih7aHBhcmFtSW5mbzp0LmluZm99KSksbWV0cmljQ29sdW1uczp0aGlzLl9tZXRyaWNzLm1hcCh0PT4oe21ldHJpY0luZm86dC5pbmZvfSkpfX1fdXBkYXRlQ29uZmlndXJhdGlvbigpe3RoaXMuZGVib3VuY2UoIl91cGRhdGVDb25maWd1cmF0aW9uIiwoKT0+e3RoaXMuY29uZmlndXJhdGlvbj17c2NoZW1hOnRoaXMuX2NvbXB1dGVTY2hlbWEoKSxjb2x1bW5zVmlzaWJpbGl0eTp0aGlzLl9jb21wdXRlQ29sdW1uc1Zpc2liaWxpdHkoKSx2aXNpYmxlU2NoZW1hOnRoaXMuX2NvbXB1dGVWaXNpYmxlU2NoZW1hKCl9fSl9X2NvbXB1dGVDb2x1bW5zVmlzaWJpbGl0eSgpe3JldHVybiF0aGlzLl9ocGFyYW1zfHwhdGhpcy5fbWV0cmljcz9bXTp0aGlzLl9ocGFyYW1zLm1hcCh0PT50LmRpc3BsYXllZCkuY29uY2F0KHRoaXMuX21ldHJpY3MubWFwKHQ9PnQuZGlzcGxheWVkKSl9X2NvbXB1dGVWaXNpYmxlU2NoZW1hKCl7aWYoIXRoaXMuX2hwYXJhbXN8fCF0aGlzLl9tZXRyaWNzKXJldHVybntocGFyYW1JbmZvczpbXSxtZXRyaWNJbmZvczpbXX07bGV0IHQ9dGhpcy5faHBhcmFtcy5maWx0ZXIobj0+bi5kaXNwbGF5ZWQpLm1hcChuPT5uLmluZm8pLHI9dGhpcy5fbWV0cmljcy5maWx0ZXIobj0+bi5kaXNwbGF5ZWQpLm1hcChuPT5uLmluZm8pO3JldHVybntocGFyYW1JbmZvczp0LG1ldHJpY0luZm9zOnJ9fV9xdWVyeVNlcnZlcigpe3RoaXMuZGVib3VuY2UoInF1ZXJ5U2VydmVyIiwoKT0+dGhpcy5fcXVlcnlTZXJ2ZXJOb0RlYm91bmNlKCksMTAwKX1fcXVlcnlTZXJ2ZXJOb0RlYm91bmNlKCl7aWYoISghdGhpcy5faHBhcmFtc3x8IXRoaXMuX21ldHJpY3MpKXJldHVybiB0aGlzLl9zZW5kTGlzdFNlc3Npb25Hcm91cHNSZXF1ZXN0KCkudGhlbih0aGlzLl9saXN0U2Vzc2lvbkdyb3Vwc0NhbmNlbGxlci5jYW5jZWxsYWJsZSgoe3ZhbHVlOnQsY2FuY2VsbGVkOnJ9KT0+e2lmKCFyKXtpZih0LnRvdGFsU2l6ZT49MCl7bGV0IG49K3RoaXMuX3BhZ2VTaXplSW5wdXQudmFsdWU7dGhpcy5zZXQoIl9wYWdlQ291bnRTdHIiLFN0cmluZyhNYXRoLmNlaWwodC50b3RhbFNpemUvbikpKSx0aGlzLnNldCgiX3RvdGFsU2Vzc2lvbkdyb3Vwc0NvdW50U3RyIix0LnRvdGFsU2l6ZSl9ZWxzZSB0aGlzLnNldCgiX3BhZ2VDb3VudFN0ciIsIj8iKSx0aGlzLnNldCgiX3RvdGFsU2Vzc2lvbkdyb3Vwc0NvdW50U3RyIiwiVW5rbm93biIpO2RjdCh0aGlzLCJzZXNzaW9uR3JvdXBzIix0LnNlc3Npb25Hcm91cHMpfX0pKX1fc2VuZExpc3RTZXNzaW9uR3JvdXBzUmVxdWVzdCgpe2xldCB0PXRoaXMuX2J1aWxkTGlzdFNlc3Npb25Hcm91cHNSZXF1ZXN0KCk7aWYodCE9PW51bGwpcmV0dXJuIHRoaXMuc2V0KCJfc2Vzc2lvbkdyb3Vwc1JlcXVlc3QiLHQpLHRoaXMuX2xpc3RTZXNzaW9uR3JvdXBzQ2FuY2VsbGVyLmNhbmNlbEFsbCgpLHRoaXMuYmFja2VuZC5saXN0U2Vzc2lvbkdyb3Vwcyh0KX1fYnVpbGRMaXN0U2Vzc2lvbkdyb3Vwc1JlcXVlc3QoKXtsZXQgdD10aGlzLHI9ITA7ZnVuY3Rpb24gbih1KXtsZXQgaD10LmdldCh1KyIubWluLnZhbHVlIik7Y29uc29sZS5hc3NlcnQoaCE9PXZvaWQgMCk7bGV0IGY9aD09PSIiPyItSW5maW5pdHkiOitoO3Quc2V0KHUrIi5taW4uaW52YWxpZCIsaXNOYU4oZikpLHI9ciYmIWlzTmFOKGYpO2xldCBwPXQuZ2V0KHUrIi5tYXgudmFsdWUiKTtjb25zb2xlLmFzc2VydChwIT09dm9pZCAwKTtsZXQgZD1wPT09IiI/IkluZmluaXR5IjorcDtyZXR1cm4gdC5zZXQodSsiLm1heC5pbnZhbGlkIixpc05hTihkKSkscj1yJiYhaXNOYU4oZCksaXNOYU4oZil8fGlzTmFOKGQpP251bGw6e21pblZhbHVlOmYsbWF4VmFsdWU6ZH19ZnVuY3Rpb24gaSh1KXtsZXQgaD10LmdldCh1KyIudmFsdWUiKTtjb25zb2xlLmFzc2VydChoIT09dm9pZCAwKTtsZXQgZj0raCxwPU51bWJlci5pc0ludGVnZXIoZikmJmY+MDtyZXR1cm4gdC5zZXQodSsiLmludmFsaWQiLCFwKSxyPXImJnAscD9mOm51bGx9bGV0IG89dGhpcy5fc3RhdHVzZXMuZmlsdGVyKHU9PnUuYWxsb3dlZCkubWFwKHU9PnUudmFsdWUpLGE9W107aWYodGhpcy5faHBhcmFtcy5mb3JFYWNoKCh1LGgpPT57bGV0IGY9e2hwYXJhbTp1LmluZm8ubmFtZX07dS5maWx0ZXIuZG9tYWluRGlzY3JldGU/KGYuZmlsdGVyRGlzY3JldGU9W10sdS5maWx0ZXIuZG9tYWluRGlzY3JldGUuZm9yRWFjaChwPT57cC5jaGVja2VkJiZmLmZpbHRlckRpc2NyZXRlLnB1c2gocC52YWx1ZSl9KSk6dS5maWx0ZXIuaW50ZXJ2YWw/Zi5maWx0ZXJJbnRlcnZhbD1uKCJfaHBhcmFtcy4iK2grIi5maWx0ZXIuaW50ZXJ2YWwiKTp1LmZpbHRlci5yZWdleHAmJihmLmZpbHRlclJlZ2V4cD11LmZpbHRlci5yZWdleHApLGEucHVzaChmKX0pLHRoaXMuX21ldHJpY3MuZm9yRWFjaCgodSxoKT0+e2xldCBmPXttZXRyaWM6dS5pbmZvLm5hbWUsZmlsdGVySW50ZXJ2YWw6bigiX21ldHJpY3MuIitoKyIuZmlsdGVyLmludGVydmFsIil9O2EucHVzaChmKX0pLHRoaXMuX3NvcnRCeUluZGV4IT09dm9pZCAwJiZ0aGlzLl9zb3J0RGlyZWN0aW9uIT09dm9pZCAwKXtpZighKHRoaXMuX3NvcnRCeUluZGV4IGluIGEpKXJldHVybiBjb25zb2xlLmVycm9yKCJObyBjb2x1bW4gaW4gY29sUGFyYW1zIHdpdGggaW5kZXggc29ydEJ5SW5kZXg6ICVzIix0aGlzLl9zb3J0QnlJbmRleCksbnVsbDthW3RoaXMuX3NvcnRCeUluZGV4XS5vcmRlcj10aGlzLl9zb3J0RGlyZWN0aW9uPT09MD8iT1JERVJfQVNDIjoiT1JERVJfREVTQyJ9bGV0IHM9aSgiX3BhZ2VOdW1iZXJJbnB1dCIpfHwwLGw9aSgiX3BhZ2VTaXplSW5wdXQiKXx8MDtpZighcilyZXR1cm4gbnVsbDtsZXQgYz1sKihzLTEpO3JldHVybntleHBlcmltZW50TmFtZTp0aGlzLmV4cGVyaW1lbnROYW1lLGFsbG93ZWRTdGF0dXNlczpvLGNvbFBhcmFtczphLHN0YXJ0SW5kZXg6YyxzbGljZVNpemU6bH19X21ldHJpY1NvcnRCeUluZGV4KHQpe3JldHVybiB0K3RoaXMuX2hwYXJhbXMubGVuZ3RofX07dGkudGVtcGxhdGU9UWAKICAgIDxocGFyYW1zLXNwbGl0LWxheW91dCBvcmllbnRhdGlvbj0idmVydGljYWwiPgogICAgICA8ZGl2IHNsb3Q9ImNvbnRlbnQiIGNsYXNzPSJzZWN0aW9uIGh5cGVycGFyYW1ldGVycyI+CiAgICAgICAgPGRpdiBjbGFzcz0ic2VjdGlvbi10aXRsZSI+SHlwZXJwYXJhbWV0ZXJzPC9kaXY+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20tcmVwZWF0IiBpdGVtcz0ie3tfaHBhcmFtc319IiBhcz0iaHBhcmFtIj4KICAgICAgICAgIDxkaXYgY2xhc3M9ImhwYXJhbSI+CiAgICAgICAgICAgIDxwYXBlci1jaGVja2JveAogICAgICAgICAgICAgIGNoZWNrZWQ9Int7aHBhcmFtLmRpc3BsYXllZH19IgogICAgICAgICAgICAgIGNsYXNzPSJocGFyYW0tY2hlY2tib3giCiAgICAgICAgICAgID4KICAgICAgICAgICAgICBbW19ocGFyYW1OYW1lKGhwYXJhbS5pbmZvKV1dCiAgICAgICAgICAgIDwvcGFwZXItY2hlY2tib3g+CiAgICAgICAgICAgIDwhLS0gUHJlY2lzZWx5IG9uZSBvZiB0aGUgdGVtcGxhdGVzIGJlbG93IHdpbGwgYmUgc3RhbXBlZC4tLT4KICAgICAgICAgICAgPCEtLSAxLiBBIGxpc3Qgb2YgY2hlY2tib3hlcyAtLT4KICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW2hwYXJhbS5maWx0ZXIuZG9tYWluRGlzY3JldGVdXSI+CiAgICAgICAgICAgICAgPHRlbXBsYXRlCiAgICAgICAgICAgICAgICBpcz0iZG9tLXJlcGVhdCIKICAgICAgICAgICAgICAgIGl0ZW1zPSJbW2hwYXJhbS5maWx0ZXIuZG9tYWluRGlzY3JldGVdXSIKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8cGFwZXItY2hlY2tib3gKICAgICAgICAgICAgICAgICAgY2hlY2tlZD0ie3tpdGVtLmNoZWNrZWR9fSIKICAgICAgICAgICAgICAgICAgY2xhc3M9ImRpc2NyZXRlLXZhbHVlLWNoZWNrYm94IgogICAgICAgICAgICAgICAgICBvbi1jaGFuZ2U9Il9xdWVyeVNlcnZlciIKICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgW1tfcHJldHR5UHJpbnQoaXRlbS52YWx1ZSldXQogICAgICAgICAgICAgICAgPC9wYXBlci1jaGVja2JveD4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8IS0tIDIuIEEgbnVtZXJpYyBpbnRlcnZhbCAtLT4KICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW2hwYXJhbS5maWx0ZXIuaW50ZXJ2YWxdXSI+CiAgICAgICAgICAgICAgPHBhcGVyLWlucHV0CiAgICAgICAgICAgICAgICBsYWJlbD0iTWluIgogICAgICAgICAgICAgICAgdmFsdWU9Int7aHBhcmFtLmZpbHRlci5pbnRlcnZhbC5taW4udmFsdWV9fSIKICAgICAgICAgICAgICAgIGFsbG93ZWRfcGF0dGVybj0iWzAtOS5lXFwtXSIKICAgICAgICAgICAgICAgIG9uLXZhbHVlLWNoYW5nZWQ9Il9xdWVyeVNlcnZlciIKICAgICAgICAgICAgICAgIGVycm9yLW1lc3NhZ2U9IkludmFsaWQgaW5wdXQiCiAgICAgICAgICAgICAgICBpbnZhbGlkPSJbW2hwYXJhbS5maWx0ZXIuaW50ZXJ2YWwubWluLmludmFsaWRdXSIKICAgICAgICAgICAgICAgIHBsYWNlaG9sZGVyPSItaW5maW5pdHkiCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIDwvcGFwZXItaW5wdXQ+CiAgICAgICAgICAgICAgPHBhcGVyLWlucHV0CiAgICAgICAgICAgICAgICBsYWJlbD0iTWF4IgogICAgICAgICAgICAgICAgdmFsdWU9Int7aHBhcmFtLmZpbHRlci5pbnRlcnZhbC5tYXgudmFsdWV9fSIKICAgICAgICAgICAgICAgIGFsbG93ZWRfcGF0dGVybj0iWzAtOS5lXFwtXSIKICAgICAgICAgICAgICAgIG9uLXZhbHVlLWNoYW5nZWQ9Il9xdWVyeVNlcnZlciIKICAgICAgICAgICAgICAgIGVycm9yLW1lc3NhZ2U9IkludmFsaWQgaW5wdXQiCiAgICAgICAgICAgICAgICBpbnZhbGlkPSJbW2hwYXJhbS5maWx0ZXIuaW50ZXJ2YWwubWF4LmludmFsaWRdXSIKICAgICAgICAgICAgICAgIHBsYWNlaG9sZGVyPSIraW5maW5pdHkiCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIDwvcGFwZXItaW5wdXQ+CiAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgIDwhLS0gMy4gQSByZWdleHAgLS0+CiAgICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tocGFyYW0uZmlsdGVyLnJlZ2V4cF1dIj4KICAgICAgICAgICAgICA8cGFwZXItaW5wdXQKICAgICAgICAgICAgICAgIGxhYmVsPSJSZWd1bGFyIGV4cHJlc3Npb24iCiAgICAgICAgICAgICAgICB2YWx1ZT0ie3tocGFyYW0uZmlsdGVyLnJlZ2V4cH19IgogICAgICAgICAgICAgICAgb24tdmFsdWUtY2hhbmdlZD0iX3F1ZXJ5U2VydmVyIgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICA8L3BhcGVyLWlucHV0PgogICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgc2xvdD0iY29udGVudCIgY2xhc3M9InNlY3Rpb24gbWV0cmljcyI+CiAgICAgICAgPGRpdiBjbGFzcz0ic2VjdGlvbi10aXRsZSI+TWV0cmljczwvZGl2PgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9Int7X21ldHJpY3N9fSIgYXM9Im1ldHJpYyI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJtZXRyaWMiPgogICAgICAgICAgICA8IS0tIFRPRE8oZXJleik6IE1ha2UgaXQgZWFzaWVyIHRvIGhhbmRsZSBhIGxhcmdlIG51bWJlciBvZgogICAgICAgICAgICAgICAgICBtZXRyaWNzOgogICAgICAgICAgICAgICAgICAxLiBBZGQgYW4gJ2lzb2xhdG9yJyByYWRpby1idXR0b24gdG8gc2VsZWN0IGp1c3Qgb25lCiAgICAgICAgICAgICAgICAgIG1ldHJpYyBhbmQKICAgICAgICAgICAgICAgICAgaGlkZSBhbGwgdGhlIHJlc3QKICAgICAgICAgICAgICAgICAgMi4gQWRkIGEgJ3RvZ2dsZS1hbGwnIGJ1dHRvbiB0aGF0IHdpbGwgaGlkZS91bmhpZGUKICAgICAgICAgICAgICAgICAgICBhbGwgdGhlCiAgICAgICAgICAgICAgICAgIG1ldHJpY3MuCiAgICAgICAgICAgICAgICAgIFVzZSBzaW1pbGFyIGxvZ2ljL2FwcGVhcmFuY2UgdG8gdGhlIHJ1bi1zZWxlY3RvciBvZgogICAgICAgICAgICAgICAgICBzY2FsYXJzLi0tPgogICAgICAgICAgICA8cGFwZXItY2hlY2tib3gKICAgICAgICAgICAgICBjaGVja2VkPSJ7e21ldHJpYy5kaXNwbGF5ZWR9fSIKICAgICAgICAgICAgICBjbGFzcz0ibWV0cmljLWNoZWNrYm94IgogICAgICAgICAgICA+CiAgICAgICAgICAgICAgW1tfbWV0cmljTmFtZShtZXRyaWMuaW5mbyldXQogICAgICAgICAgICA8L3BhcGVyLWNoZWNrYm94PgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJpbmxpbmUtZWxlbWVudCI+CiAgICAgICAgICAgICAgPHBhcGVyLWlucHV0CiAgICAgICAgICAgICAgICBsYWJlbD0iTWluIgogICAgICAgICAgICAgICAgdmFsdWU9Int7bWV0cmljLmZpbHRlci5pbnRlcnZhbC5taW4udmFsdWV9fSIKICAgICAgICAgICAgICAgIGFsbG93ZWQtcGF0dGVybj0iWzAtOS5lXFwtXSIKICAgICAgICAgICAgICAgIG9uLXZhbHVlLWNoYW5nZWQ9Il9xdWVyeVNlcnZlciIKICAgICAgICAgICAgICAgIGVycm9yLW1lc3NhZ2U9IkludmFsaWQgaW5wdXQiCiAgICAgICAgICAgICAgICBpbnZhbGlkPSJ7e21ldHJpYy5maWx0ZXIuaW50ZXJ2YWwubWluLmludmFsaWR9fSIKICAgICAgICAgICAgICAgIHBsYWNlaG9sZGVyPSItaW5maW5pdHkiCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIDwvcGFwZXItaW5wdXQ+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJpbmxpbmUtZWxlbWVudCI+CiAgICAgICAgICAgICAgPHBhcGVyLWlucHV0CiAgICAgICAgICAgICAgICBsYWJlbD0iTWF4IgogICAgICAgICAgICAgICAgYWxsb3dlZC1wYXR0ZXJuPSJbMC05LmVcXC1dIgogICAgICAgICAgICAgICAgdmFsdWU9Int7bWV0cmljLmZpbHRlci5pbnRlcnZhbC5tYXgudmFsdWV9fSIKICAgICAgICAgICAgICAgIG9uLXZhbHVlLWNoYW5nZWQ9Il9xdWVyeVNlcnZlciIKICAgICAgICAgICAgICAgIGVycm9yLW1lc3NhZ2U9IkludmFsaWQgaW5wdXQiCiAgICAgICAgICAgICAgICBpbnZhbGlkPSJ7e21ldHJpYy5maWx0ZXIuaW50ZXJ2YWwubWF4LmludmFsaWR9fSIKICAgICAgICAgICAgICAgIHBsYWNlaG9sZGVyPSIraW5maW5pdHkiCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIDwvcGFwZXItaW5wdXQ+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgc2xvdD0iY29udGVudCIgY2xhc3M9InNlY3Rpb24gc3RhdHVzIj4KICAgICAgICA8ZGl2IGNsYXNzPSJzZWN0aW9uLXRpdGxlIj5TdGF0dXM8L2Rpdj4KICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19zdGF0dXNlc11dIiBhcz0ic3RhdHVzIj4KICAgICAgICAgIDxwYXBlci1jaGVja2JveCBjaGVja2VkPSJ7e3N0YXR1cy5hbGxvd2VkfX0iIG9uLWNoYW5nZT0iX3F1ZXJ5U2VydmVyIj4KICAgICAgICAgICAgW1tzdGF0dXMuZGlzcGxheU5hbWVdXQogICAgICAgICAgPC9wYXBlci1jaGVja2JveD4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICA8L2Rpdj4KICAgICAgPGRpdiBzbG90PSJjb250ZW50IiBjbGFzcz0ic2VjdGlvbiBzb3J0aW5nIj4KICAgICAgICA8ZGl2IGNsYXNzPSJzZWN0aW9uLXRpdGxlIj5Tb3J0aW5nPC9kaXY+CiAgICAgICAgPHBhcGVyLWRyb3Bkb3duLW1lbnUKICAgICAgICAgIGxhYmVsPSJTb3J0IGJ5IgogICAgICAgICAgb24tc2VsZWN0ZWQtaXRlbS1jaGFuZ2VkPSJfcXVlcnlTZXJ2ZXIiCiAgICAgICAgICBob3Jpem9udGFsLWFsaWduPSJsZWZ0IgogICAgICAgID4KICAgICAgICAgIDxwYXBlci1saXN0Ym94CiAgICAgICAgICAgIGNsYXNzPSJkcm9wZG93bi1jb250ZW50IgogICAgICAgICAgICBzbG90PSJkcm9wZG93bi1jb250ZW50IgogICAgICAgICAgICBzZWxlY3RlZD0ie3tfc29ydEJ5SW5kZXh9fSIKICAgICAgICAgICAgb24tc2VsZWN0ZWQtaXRlbS1jaGFuZ2VkPSJfcXVlcnlTZXJ2ZXIiCiAgICAgICAgICA+CiAgICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9IltbX2hwYXJhbXNdXSIgYXM9ImhwYXJhbSI+CiAgICAgICAgICAgICAgPHBhcGVyLWl0ZW0+IFtbX2hwYXJhbU5hbWUoaHBhcmFtLmluZm8pXV0gPC9wYXBlci1pdGVtPgogICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19tZXRyaWNzXV0iIGFzPSJtZXRyaWMiPgogICAgICAgICAgICAgIDxwYXBlci1pdGVtPiBbW19tZXRyaWNOYW1lKG1ldHJpYy5pbmZvKV1dIDwvcGFwZXItaXRlbT4KICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDwvcGFwZXItbGlzdGJveD4KICAgICAgICA8L3BhcGVyLWRyb3Bkb3duLW1lbnU+CiAgICAgICAgPHBhcGVyLWRyb3Bkb3duLW1lbnUKICAgICAgICAgIGxhYmVsPSJEaXJlY3Rpb24iCiAgICAgICAgICBvbi1zZWxlY3RlZC1pdGVtLWNoYW5nZWQ9Il9xdWVyeVNlcnZlciIKICAgICAgICAgIGhvcml6b250YWwtYWxpZ249ImxlZnQiCiAgICAgICAgPgogICAgICAgICAgPHBhcGVyLWxpc3Rib3gKICAgICAgICAgICAgY2xhc3M9ImRyb3Bkb3duLWNvbnRlbnQiCiAgICAgICAgICAgIHNsb3Q9ImRyb3Bkb3duLWNvbnRlbnQiCiAgICAgICAgICAgIHNlbGVjdGVkPSJ7e19zb3J0RGlyZWN0aW9ufX0iCiAgICAgICAgICA+CiAgICAgICAgICAgIDxwYXBlci1pdGVtPkFzY2VuZGluZzwvcGFwZXItaXRlbT4KICAgICAgICAgICAgPHBhcGVyLWl0ZW0+RGVzY2VuZGluZzwvcGFwZXItaXRlbT4KICAgICAgICAgIDwvcGFwZXItbGlzdGJveD4KICAgICAgICA8L3BhcGVyLWRyb3Bkb3duLW1lbnU+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IHNsb3Q9ImNvbnRlbnQiIGNsYXNzPSJzZWN0aW9uIHBhZ2luZyI+CiAgICAgICAgPGRpdiBjbGFzcz0ic2VjdGlvbi10aXRsZSI+UGFnaW5nPC9kaXY+CiAgICAgICAgPGRpdj4KICAgICAgICAgIE51bWJlciBvZiBtYXRjaGluZyBzZXNzaW9uIGdyb3VwczogW1tfdG90YWxTZXNzaW9uR3JvdXBzQ291bnRTdHJdXQogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9ImlubGluZS1lbGVtZW50IHBhZ2UtbnVtYmVyLWlucHV0Ij4KICAgICAgICAgIDxwYXBlci1pbnB1dAogICAgICAgICAgICBsYWJlbD0iUGFnZSAjIgogICAgICAgICAgICB2YWx1ZT0ie3tfcGFnZU51bWJlcklucHV0LnZhbHVlfX0iCiAgICAgICAgICAgIGFsbG93ZWQtcGF0dGVybj0iWzAtOV0iCiAgICAgICAgICAgIGVycm9yLW1lc3NhZ2U9IkludmFsaWQgaW5wdXQiCiAgICAgICAgICAgIGludmFsaWQ9IltbX3BhZ2VOdW1iZXJJbnB1dC5pbnZhbGlkXV0iCiAgICAgICAgICAgIG9uLXZhbHVlLWNoYW5nZWQ9Il9xdWVyeVNlcnZlciIKICAgICAgICAgID4KICAgICAgICAgICAgPGRpdiBzbG90PSJzdWZmaXgiIGNsYXNzPSJwYWdlLXN1ZmZpeCI+LyBbW19wYWdlQ291bnRTdHJdXTwvZGl2PgogICAgICAgICAgPC9wYXBlci1pbnB1dD4KICAgICAgICA8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSJpbmxpbmUtZWxlbWVudCBwYWdlLXNpemUtaW5wdXQiPgogICAgICAgICAgPHBhcGVyLWlucHV0CiAgICAgICAgICAgIGxhYmVsPSJNYXggIyBvZiBzZXNzaW9uIGdyb3VwcyBwZXIgcGFnZToiCiAgICAgICAgICAgIHZhbHVlPSJ7e19wYWdlU2l6ZUlucHV0LnZhbHVlfX0iCiAgICAgICAgICAgIGFsbG93ZWQtcGF0dGVybj0iWzAtOV0iCiAgICAgICAgICAgIGVycm9yLW1lc3NhZ2U9IkludmFsaWQgaW5wdXQiCiAgICAgICAgICAgIGludmFsaWQ9IltbX3BhZ2VTaXplSW5wdXQuaW52YWxpZF1dIgogICAgICAgICAgICBvbi12YWx1ZS1jaGFuZ2VkPSJfcXVlcnlTZXJ2ZXIiCiAgICAgICAgICA+CiAgICAgICAgICA8L3BhcGVyLWlucHV0PgogICAgICAgIDwvZGl2PgogICAgICA8L2Rpdj4KICAgICAgPGRpdiBzbG90PSJjb250ZW50IiBjbGFzcz0ic2VjdGlvbiBkb3dubG9hZCI+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19zZXNzaW9uR3JvdXBzUmVxdWVzdF1dIj4KICAgICAgICAgIERvd25sb2FkIGRhdGEgYXMKICAgICAgICAgIDxzcGFuPgogICAgICAgICAgICA8YQogICAgICAgICAgICAgIGlkPSJjc3ZMaW5rIgogICAgICAgICAgICAgIGRvd25sb2FkPSJocGFyYW1zX3RhYmxlLmNzdiIKICAgICAgICAgICAgICBocmVmPSJbW19jc3ZVcmwoX3Nlc3Npb25Hcm91cHNSZXF1ZXN0LCBjb25maWd1cmF0aW9uKV1dIgogICAgICAgICAgICAgID5DU1Y8L2EKICAgICAgICAgICAgPgogICAgICAgICAgICA8YQogICAgICAgICAgICAgIGlkPSJqc29uTGluayIKICAgICAgICAgICAgICBkb3dubG9hZD0iaHBhcmFtc190YWJsZS5qc29uIgogICAgICAgICAgICAgIGhyZWY9IltbX2pzb25VcmwoX3Nlc3Npb25Hcm91cHNSZXF1ZXN0LCBjb25maWd1cmF0aW9uKV1dIgogICAgICAgICAgICAgID5KU09OPC9hCiAgICAgICAgICAgID4KICAgICAgICAgICAgPGEKICAgICAgICAgICAgICBpZD0ibGF0ZXhMaW5rIgogICAgICAgICAgICAgIGRvd25sb2FkPSJocGFyYW1zX3RhYmxlLnRleCIKICAgICAgICAgICAgICBocmVmPSJbW19sYXRleFVybChfc2Vzc2lvbkdyb3Vwc1JlcXVlc3QsIGNvbmZpZ3VyYXRpb24pXV0iCiAgICAgICAgICAgICAgPkxhVGVYPC9hCiAgICAgICAgICAgID4KICAgICAgICAgIDwvc3Bhbj4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICA8L2Rpdj4KICAgIDwvaHBhcmFtcy1zcGxpdC1sYXlvdXQ+CiAgICA8c3R5bGU+CiAgICAgIC5zZWN0aW9uIHsKICAgICAgICBwYWRkaW5nOiAxMHB4OwogICAgICB9CiAgICAgIC5zZWN0aW9uLXRpdGxlIHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBmb250LXdlaWdodDogYm9sZDsKICAgICAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKICAgICAgICBtYXJnaW4tYm90dG9tOiA3cHg7CiAgICAgIH0KICAgICAgLmRpc2NyZXRlLXZhbHVlLWNoZWNrYm94LAogICAgICAubWV0cmljLWNoZWNrYm94LAogICAgICAuaHBhcmFtLWNoZWNrYm94IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgfQogICAgICAuZGlzY3JldGUtdmFsdWUtY2hlY2tib3ggewogICAgICAgIG1hcmdpbi1sZWZ0OiAyMHB4OwogICAgICB9CiAgICAgIC5ocGFyYW0sCiAgICAgIC5tZXRyaWMgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICB9CiAgICAgIC5pbmxpbmUtZWxlbWVudCB7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICAgIHdpZHRoOiA0MCU7CiAgICAgICAgbWFyZ2luLWxlZnQ6IDEwcHg7CiAgICAgIH0KICAgICAgLnBhZ2UtbnVtYmVyLWlucHV0IHsKICAgICAgICB3aWR0aDogMjAlOwogICAgICB9CiAgICAgIC5wYWdlLXNpemUtaW5wdXQgewogICAgICAgIHdpZHRoOiA2MCU7CiAgICAgIH0KICAgICAgdmFhZGluLXNwbGl0LWxheW91dCB7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICB9CiAgICAgIHBhcGVyLWxpc3Rib3ggewogICAgICAgIG1heC1oZWlnaHQ6IDE1ZW07CiAgICAgIH0KICAgICAgLnBhZ2Utc3VmZml4IHsKICAgICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sdGkucHJvdG90eXBlLCJleHBlcmltZW50TmFtZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sdGkucHJvdG90eXBlLCJjb25maWd1cmF0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXksbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHRpLnByb3RvdHlwZSwic2Vzc2lvbkdyb3VwcyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4sbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSx0aS5wcm90b3R5cGUsImRhdGFMb2FkZWRXaXRoTm9uRW1wdHlIcGFyYW1zIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbixub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLHRpLnByb3RvdHlwZSwiZGF0YUxvYWRlZFdpdGhFbXB0eUhwYXJhbXMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sdGkucHJvdG90eXBlLCJfZXhwZXJpbWVudCIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sdGkucHJvdG90eXBlLCJfaHBhcmFtcyIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sdGkucHJvdG90eXBlLCJfbWV0cmljcyIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHRpLnByb3RvdHlwZSwiX3N0YXR1c2VzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHRpLnByb3RvdHlwZSwiX2dldEV4cGVyaW1lbnRSZXNvbHZlZCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbildLHRpLnByb3RvdHlwZSwiX3Jlc29sdmVHZXRFeHBlcmltZW50Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHRpLnByb3RvdHlwZSwiX2xpc3RTZXNzaW9uR3JvdXBzQ2FuY2VsbGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLHRpLnByb3RvdHlwZSwiX3NvcnRCeUluZGV4Iix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLHRpLnByb3RvdHlwZSwiX3NvcnREaXJlY3Rpb24iLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sdGkucHJvdG90eXBlLCJfcGFnZVNpemVJbnB1dCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSx0aS5wcm90b3R5cGUsIl9wYWdlTnVtYmVySW5wdXQiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sdGkucHJvdG90eXBlLCJfcGFnZUNvdW50U3RyIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLHRpLnByb3RvdHlwZSwiX3RvdGFsU2Vzc2lvbkdyb3Vwc0NvdW50U3RyIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHRpLnByb3RvdHlwZSwiX3Nlc3Npb25Hcm91cHNSZXF1ZXN0Iix2b2lkIDApO0UoW0J0KCJiYWNrZW5kIiwiZXhwZXJpbWVudE5hbWUiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLHRpLnByb3RvdHlwZSwiX2NvbXB1dGVFeHBlcmltZW50QW5kUmVsYXRlZFByb3BzIixudWxsKTtFKFtCdCgiX2hwYXJhbXMuKiIsIl9tZXRyaWNzLioiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLHRpLnByb3RvdHlwZSwiX3VwZGF0ZUNvbmZpZ3VyYXRpb24iLG51bGwpO3RpPUUoW3l0KCJ0Zi1ocGFyYW1zLXF1ZXJ5LXBhbmUiKV0sdGkpO3ZhciBxY3Q9RWUoT2UoKSwxKTt2YXIgZ2N0PXR5cGVvZiB3aW5kb3chPSJ1bmRlZmluZWQiJiZ3aW5kb3cuY3VzdG9tRWxlbWVudHMhPW51bGwmJndpbmRvdy5jdXN0b21FbGVtZW50cy5wb2x5ZmlsbFdyYXBGbHVzaENhbGxiYWNrIT09dm9pZCAwO3ZhciBpdj0oZSx0LHI9bnVsbCk9Pntmb3IoO3QhPT1yOyl7bGV0IG49dC5uZXh0U2libGluZztlLnJlbW92ZUNoaWxkKHQpLHQ9bn19O3ZhciBZYz1ge3tsaXQtJHtTdHJpbmcoTWF0aC5yYW5kb20oKSkuc2xpY2UoMil9fX1gLF9jdD1gPCEtLSR7WWN9LS0+YCxwY2U9bmV3IFJlZ0V4cChgJHtZY318JHtfY3R9YCksZDM9IiRsaXQkIixvdj1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIpe3RoaXMucGFydHM9W10sdGhpcy5lbGVtZW50PXI7bGV0IG49W10saT1bXSxvPWRvY3VtZW50LmNyZWF0ZVRyZWVXYWxrZXIoci5jb250ZW50LDEzMyxudWxsLCExKSxhPTAscz0tMSxsPTAse3N0cmluZ3M6Yyx2YWx1ZXM6e2xlbmd0aDp1fX09dDtmb3IoO2w8dTspe2xldCBoPW8ubmV4dE5vZGUoKTtpZihoPT09bnVsbCl7by5jdXJyZW50Tm9kZT1pLnBvcCgpO2NvbnRpbnVlfWlmKHMrKyxoLm5vZGVUeXBlPT09MSl7aWYoaC5oYXNBdHRyaWJ1dGVzKCkpe2xldCBmPWguYXR0cmlidXRlcyx7bGVuZ3RoOnB9PWYsZD0wO2ZvcihsZXQgZz0wO2c8cDtnKyspZGNlKGZbZ10ubmFtZSxkMykmJmQrKztmb3IoO2QtLSA+MDspe2xldCBnPWNbbF0sXz1CSC5leGVjKGcpWzJdLHk9Xy50b0xvd2VyQ2FzZSgpK2QzLHg9aC5nZXRBdHRyaWJ1dGUoeSk7aC5yZW1vdmVBdHRyaWJ1dGUoeSk7bGV0IGI9eC5zcGxpdChwY2UpO3RoaXMucGFydHMucHVzaCh7dHlwZToiYXR0cmlidXRlIixpbmRleDpzLG5hbWU6XyxzdHJpbmdzOmJ9KSxsKz1iLmxlbmd0aC0xfX1oLnRhZ05hbWU9PT0iVEVNUExBVEUiJiYoaS5wdXNoKGgpLG8uY3VycmVudE5vZGU9aC5jb250ZW50KX1lbHNlIGlmKGgubm9kZVR5cGU9PT0zKXtsZXQgZj1oLmRhdGE7aWYoZi5pbmRleE9mKFljKT49MCl7bGV0IHA9aC5wYXJlbnROb2RlLGQ9Zi5zcGxpdChwY2UpLGc9ZC5sZW5ndGgtMTtmb3IobGV0IF89MDtfPGc7XysrKXtsZXQgeSx4PWRbX107aWYoeD09PSIiKXk9WWYoKTtlbHNle2xldCBiPUJILmV4ZWMoeCk7YiE9PW51bGwmJmRjZShiWzJdLGQzKSYmKHg9eC5zbGljZSgwLGIuaW5kZXgpK2JbMV0rYlsyXS5zbGljZSgwLC1kMy5sZW5ndGgpK2JbM10pLHk9ZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoeCl9cC5pbnNlcnRCZWZvcmUoeSxoKSx0aGlzLnBhcnRzLnB1c2goe3R5cGU6Im5vZGUiLGluZGV4Oisrc30pfWRbZ109PT0iIj8ocC5pbnNlcnRCZWZvcmUoWWYoKSxoKSxuLnB1c2goaCkpOmguZGF0YT1kW2ddLGwrPWd9fWVsc2UgaWYoaC5ub2RlVHlwZT09PTgpaWYoaC5kYXRhPT09WWMpe2xldCBmPWgucGFyZW50Tm9kZTsoaC5wcmV2aW91c1NpYmxpbmc9PT1udWxsfHxzPT09YSkmJihzKyssZi5pbnNlcnRCZWZvcmUoWWYoKSxoKSksYT1zLHRoaXMucGFydHMucHVzaCh7dHlwZToibm9kZSIsaW5kZXg6c30pLGgubmV4dFNpYmxpbmc9PT1udWxsP2guZGF0YT0iIjoobi5wdXNoKGgpLHMtLSksbCsrfWVsc2V7bGV0IGY9LTE7Zm9yKDsoZj1oLmRhdGEuaW5kZXhPZihZYyxmKzEpKSE9PS0xOyl0aGlzLnBhcnRzLnB1c2goe3R5cGU6Im5vZGUiLGluZGV4Oi0xfSksbCsrfX1mb3IobGV0IGggb2YgbiloLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoaCl9fSxkY2U9KGUsdCk9PntsZXQgcj1lLmxlbmd0aC10Lmxlbmd0aDtyZXR1cm4gcj49MCYmZS5zbGljZShyKT09PXR9LHlQPWU9PmUuaW5kZXghPT0tMSxZZj0oKT0+ZG9jdW1lbnQuY3JlYXRlQ29tbWVudCgiIiksQkg9LyhbIFx4MDlceDBhXHgwY1x4MGRdKShbXlwwLVx4MUZceDdGLVx4OUYgIic+PS9dKykoWyBceDA5XHgwYVx4MGNceDBkXSo9WyBceDA5XHgwYVx4MGNceDBkXSooPzpbXiBceDA5XHgwYVx4MGNceDBkIidgPD49XSp8IlteIl0qfCdbXiddKikpJC87dmFyIHljdD0xMzM7ZnVuY3Rpb24gdmN0KGUsdCl7bGV0e2VsZW1lbnQ6e2NvbnRlbnQ6cn0scGFydHM6bn09ZSxpPWRvY3VtZW50LmNyZWF0ZVRyZWVXYWxrZXIocix5Y3QsbnVsbCwhMSksbz12UChuKSxhPW5bb10scz0tMSxsPTAsYz1bXSx1PW51bGw7Zm9yKDtpLm5leHROb2RlKCk7KXtzKys7bGV0IGg9aS5jdXJyZW50Tm9kZTtmb3IoaC5wcmV2aW91c1NpYmxpbmc9PT11JiYodT1udWxsKSx0LmhhcyhoKSYmKGMucHVzaChoKSx1PT09bnVsbCYmKHU9aCkpLHUhPT1udWxsJiZsKys7YSE9PXZvaWQgMCYmYS5pbmRleD09PXM7KWEuaW5kZXg9dSE9PW51bGw/LTE6YS5pbmRleC1sLG89dlAobixvKSxhPW5bb119Yy5mb3JFYWNoKGg9PmgucGFyZW50Tm9kZS5yZW1vdmVDaGlsZChoKSl9dmFyIEV1cj1lPT57bGV0IHQ9ZS5ub2RlVHlwZT09PTExPzA6MSxyPWRvY3VtZW50LmNyZWF0ZVRyZWVXYWxrZXIoZSx5Y3QsbnVsbCwhMSk7Zm9yKDtyLm5leHROb2RlKCk7KXQrKztyZXR1cm4gdH0sdlA9KGUsdD0tMSk9Pntmb3IobGV0IHI9dCsxO3I8ZS5sZW5ndGg7cisrKXtsZXQgbj1lW3JdO2lmKHlQKG4pKXJldHVybiByfXJldHVybi0xfTtmdW5jdGlvbiBtY2UoZSx0LHI9bnVsbCl7bGV0e2VsZW1lbnQ6e2NvbnRlbnQ6bn0scGFydHM6aX09ZTtpZihyPT1udWxsKXtuLmFwcGVuZENoaWxkKHQpO3JldHVybn1sZXQgbz1kb2N1bWVudC5jcmVhdGVUcmVlV2Fsa2VyKG4seWN0LG51bGwsITEpLGE9dlAoaSkscz0wLGw9LTE7Zm9yKDtvLm5leHROb2RlKCk7KWZvcihsKyssby5jdXJyZW50Tm9kZT09PXImJihzPUV1cih0KSxyLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKHQscikpO2EhPT0tMSYmaVthXS5pbmRleD09PWw7KXtpZihzPjApe2Zvcig7YSE9PS0xOylpW2FdLmluZGV4Kz1zLGE9dlAoaSxhKTtyZXR1cm59YT12UChpLGEpfX12YXIgVHVyPW5ldyBXZWFrTWFwO3ZhciBhdj1lPT50eXBlb2YgZT09ImZ1bmN0aW9uIiYmVHVyLmhhcyhlKTt2YXIgTGw9e30sSEg9e307dmFyIHcwPWNsYXNze2NvbnN0cnVjdG9yKHQscixuKXt0aGlzLl9fcGFydHM9W10sdGhpcy50ZW1wbGF0ZT10LHRoaXMucHJvY2Vzc29yPXIsdGhpcy5vcHRpb25zPW59dXBkYXRlKHQpe2xldCByPTA7Zm9yKGxldCBuIG9mIHRoaXMuX19wYXJ0cyluIT09dm9pZCAwJiZuLnNldFZhbHVlKHRbcl0pLHIrKztmb3IobGV0IG4gb2YgdGhpcy5fX3BhcnRzKW4hPT12b2lkIDAmJm4uY29tbWl0KCl9X2Nsb25lKCl7bGV0IHQ9Z2N0P3RoaXMudGVtcGxhdGUuZWxlbWVudC5jb250ZW50LmNsb25lTm9kZSghMCk6ZG9jdW1lbnQuaW1wb3J0Tm9kZSh0aGlzLnRlbXBsYXRlLmVsZW1lbnQuY29udGVudCwhMCkscj1bXSxuPXRoaXMudGVtcGxhdGUucGFydHMsaT1kb2N1bWVudC5jcmVhdGVUcmVlV2Fsa2VyKHQsMTMzLG51bGwsITEpLG89MCxhPTAscyxsPWkubmV4dE5vZGUoKTtmb3IoO288bi5sZW5ndGg7KXtpZihzPW5bb10sIXlQKHMpKXt0aGlzLl9fcGFydHMucHVzaCh2b2lkIDApLG8rKztjb250aW51ZX1mb3IoO2E8cy5pbmRleDspYSsrLGwubm9kZU5hbWU9PT0iVEVNUExBVEUiJiYoci5wdXNoKGwpLGkuY3VycmVudE5vZGU9bC5jb250ZW50KSwobD1pLm5leHROb2RlKCkpPT09bnVsbCYmKGkuY3VycmVudE5vZGU9ci5wb3AoKSxsPWkubmV4dE5vZGUoKSk7aWYocy50eXBlPT09Im5vZGUiKXtsZXQgYz10aGlzLnByb2Nlc3Nvci5oYW5kbGVUZXh0RXhwcmVzc2lvbih0aGlzLm9wdGlvbnMpO2MuaW5zZXJ0QWZ0ZXJOb2RlKGwucHJldmlvdXNTaWJsaW5nKSx0aGlzLl9fcGFydHMucHVzaChjKX1lbHNlIHRoaXMuX19wYXJ0cy5wdXNoKC4uLnRoaXMucHJvY2Vzc29yLmhhbmRsZUF0dHJpYnV0ZUV4cHJlc3Npb25zKGwscy5uYW1lLHMuc3RyaW5ncyx0aGlzLm9wdGlvbnMpKTtvKyt9cmV0dXJuIGdjdCYmKGRvY3VtZW50LmFkb3B0Tm9kZSh0KSxjdXN0b21FbGVtZW50cy51cGdyYWRlKHQpKSx0fX07dmFyIGdjZT13aW5kb3cudHJ1c3RlZFR5cGVzJiZ0cnVzdGVkVHlwZXMuY3JlYXRlUG9saWN5KCJsaXQtaHRtbCIse2NyZWF0ZUhUTUw6ZT0+ZX0pLEF1cj1gICR7WWN9IGAsUzA9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4saSl7dGhpcy5zdHJpbmdzPXQsdGhpcy52YWx1ZXM9cix0aGlzLnR5cGU9bix0aGlzLnByb2Nlc3Nvcj1pfWdldEhUTUwoKXtsZXQgdD10aGlzLnN0cmluZ3MubGVuZ3RoLTEscj0iIixuPSExO2ZvcihsZXQgaT0wO2k8dDtpKyspe2xldCBvPXRoaXMuc3RyaW5nc1tpXSxhPW8ubGFzdEluZGV4T2YoIjwhLS0iKTtuPShhPi0xfHxuKSYmby5pbmRleE9mKCItLT4iLGErMSk9PT0tMTtsZXQgcz1CSC5leGVjKG8pO3M9PT1udWxsP3IrPW8rKG4/QXVyOl9jdCk6cis9by5zdWJzdHIoMCxzLmluZGV4KStzWzFdK3NbMl0rZDMrc1szXStZY31yZXR1cm4gcis9dGhpcy5zdHJpbmdzW3RdLHJ9Z2V0VGVtcGxhdGVFbGVtZW50KCl7bGV0IHQ9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGVtcGxhdGUiKSxyPXRoaXMuZ2V0SFRNTCgpO3JldHVybiBnY2UhPT12b2lkIDAmJihyPWdjZS5jcmVhdGVIVE1MKHIpKSx0LmlubmVySFRNTD1yLHR9fTt2YXIgcUg9ZT0+ZT09PW51bGx8fCEodHlwZW9mIGU9PSJvYmplY3QifHx0eXBlb2YgZT09ImZ1bmN0aW9uIiksVkg9ZT0+QXJyYXkuaXNBcnJheShlKXx8ISEoZSYmZVtTeW1ib2wuaXRlcmF0b3JdKSxtMz1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIsbil7dGhpcy5kaXJ0eT0hMCx0aGlzLmVsZW1lbnQ9dCx0aGlzLm5hbWU9cix0aGlzLnN0cmluZ3M9bix0aGlzLnBhcnRzPVtdO2ZvcihsZXQgaT0wO2k8bi5sZW5ndGgtMTtpKyspdGhpcy5wYXJ0c1tpXT10aGlzLl9jcmVhdGVQYXJ0KCl9X2NyZWF0ZVBhcnQoKXtyZXR1cm4gbmV3IHhQKHRoaXMpfV9nZXRWYWx1ZSgpe2xldCB0PXRoaXMuc3RyaW5ncyxyPXQubGVuZ3RoLTEsbj10aGlzLnBhcnRzO2lmKHI9PT0xJiZ0WzBdPT09IiImJnRbMV09PT0iIil7bGV0IG89blswXS52YWx1ZTtpZih0eXBlb2Ygbz09InN5bWJvbCIpcmV0dXJuIFN0cmluZyhvKTtpZih0eXBlb2Ygbz09InN0cmluZyJ8fCFWSChvKSlyZXR1cm4gb31sZXQgaT0iIjtmb3IobGV0IG89MDtvPHI7bysrKXtpKz10W29dO2xldCBhPW5bb107aWYoYSE9PXZvaWQgMCl7bGV0IHM9YS52YWx1ZTtpZihxSChzKXx8IVZIKHMpKWkrPXR5cGVvZiBzPT0ic3RyaW5nIj9zOlN0cmluZyhzKTtlbHNlIGZvcihsZXQgbCBvZiBzKWkrPXR5cGVvZiBsPT0ic3RyaW5nIj9sOlN0cmluZyhsKX19cmV0dXJuIGkrPXRbcl0saX1jb21taXQoKXt0aGlzLmRpcnR5JiYodGhpcy5kaXJ0eT0hMSx0aGlzLmVsZW1lbnQuc2V0QXR0cmlidXRlKHRoaXMubmFtZSx0aGlzLl9nZXRWYWx1ZSgpKSl9fSx4UD1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLnZhbHVlPXZvaWQgMCx0aGlzLmNvbW1pdHRlcj10fXNldFZhbHVlKHQpe3QhPT1MbCYmKCFxSCh0KXx8dCE9PXRoaXMudmFsdWUpJiYodGhpcy52YWx1ZT10LGF2KHQpfHwodGhpcy5jb21taXR0ZXIuZGlydHk9ITApKX1jb21taXQoKXtmb3IoO2F2KHRoaXMudmFsdWUpOyl7bGV0IHQ9dGhpcy52YWx1ZTt0aGlzLnZhbHVlPUxsLHQodGhpcyl9dGhpcy52YWx1ZSE9PUxsJiZ0aGlzLmNvbW1pdHRlci5jb21taXQoKX19LEJkPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMudmFsdWU9dm9pZCAwLHRoaXMuX19wZW5kaW5nVmFsdWU9dm9pZCAwLHRoaXMub3B0aW9ucz10fWFwcGVuZEludG8odCl7dGhpcy5zdGFydE5vZGU9dC5hcHBlbmRDaGlsZChZZigpKSx0aGlzLmVuZE5vZGU9dC5hcHBlbmRDaGlsZChZZigpKX1pbnNlcnRBZnRlck5vZGUodCl7dGhpcy5zdGFydE5vZGU9dCx0aGlzLmVuZE5vZGU9dC5uZXh0U2libGluZ31hcHBlbmRJbnRvUGFydCh0KXt0Ll9faW5zZXJ0KHRoaXMuc3RhcnROb2RlPVlmKCkpLHQuX19pbnNlcnQodGhpcy5lbmROb2RlPVlmKCkpfWluc2VydEFmdGVyUGFydCh0KXt0Ll9faW5zZXJ0KHRoaXMuc3RhcnROb2RlPVlmKCkpLHRoaXMuZW5kTm9kZT10LmVuZE5vZGUsdC5lbmROb2RlPXRoaXMuc3RhcnROb2RlfXNldFZhbHVlKHQpe3RoaXMuX19wZW5kaW5nVmFsdWU9dH1jb21taXQoKXtpZih0aGlzLnN0YXJ0Tm9kZS5wYXJlbnROb2RlPT09bnVsbClyZXR1cm47Zm9yKDthdih0aGlzLl9fcGVuZGluZ1ZhbHVlKTspe2xldCByPXRoaXMuX19wZW5kaW5nVmFsdWU7dGhpcy5fX3BlbmRpbmdWYWx1ZT1MbCxyKHRoaXMpfWxldCB0PXRoaXMuX19wZW5kaW5nVmFsdWU7dCE9PUxsJiYocUgodCk/dCE9PXRoaXMudmFsdWUmJnRoaXMuX19jb21taXRUZXh0KHQpOnQgaW5zdGFuY2VvZiBTMD90aGlzLl9fY29tbWl0VGVtcGxhdGVSZXN1bHQodCk6dCBpbnN0YW5jZW9mIE5vZGU/dGhpcy5fX2NvbW1pdE5vZGUodCk6VkgodCk/dGhpcy5fX2NvbW1pdEl0ZXJhYmxlKHQpOnQ9PT1ISD8odGhpcy52YWx1ZT1ISCx0aGlzLmNsZWFyKCkpOnRoaXMuX19jb21taXRUZXh0KHQpKX1fX2luc2VydCh0KXt0aGlzLmVuZE5vZGUucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUodCx0aGlzLmVuZE5vZGUpfV9fY29tbWl0Tm9kZSh0KXt0aGlzLnZhbHVlIT09dCYmKHRoaXMuY2xlYXIoKSx0aGlzLl9faW5zZXJ0KHQpLHRoaXMudmFsdWU9dCl9X19jb21taXRUZXh0KHQpe2xldCByPXRoaXMuc3RhcnROb2RlLm5leHRTaWJsaW5nO3Q9dD09bnVsbD8iIjp0O2xldCBuPXR5cGVvZiB0PT0ic3RyaW5nIj90OlN0cmluZyh0KTtyPT09dGhpcy5lbmROb2RlLnByZXZpb3VzU2libGluZyYmci5ub2RlVHlwZT09PTM/ci5kYXRhPW46dGhpcy5fX2NvbW1pdE5vZGUoZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUobikpLHRoaXMudmFsdWU9dH1fX2NvbW1pdFRlbXBsYXRlUmVzdWx0KHQpe2xldCByPXRoaXMub3B0aW9ucy50ZW1wbGF0ZUZhY3RvcnkodCk7aWYodGhpcy52YWx1ZSBpbnN0YW5jZW9mIHcwJiZ0aGlzLnZhbHVlLnRlbXBsYXRlPT09cil0aGlzLnZhbHVlLnVwZGF0ZSh0LnZhbHVlcyk7ZWxzZXtsZXQgbj1uZXcgdzAocix0LnByb2Nlc3Nvcix0aGlzLm9wdGlvbnMpLGk9bi5fY2xvbmUoKTtuLnVwZGF0ZSh0LnZhbHVlcyksdGhpcy5fX2NvbW1pdE5vZGUoaSksdGhpcy52YWx1ZT1ufX1fX2NvbW1pdEl0ZXJhYmxlKHQpe0FycmF5LmlzQXJyYXkodGhpcy52YWx1ZSl8fCh0aGlzLnZhbHVlPVtdLHRoaXMuY2xlYXIoKSk7bGV0IHI9dGhpcy52YWx1ZSxuPTAsaTtmb3IobGV0IG8gb2YgdClpPXJbbl0saT09PXZvaWQgMCYmKGk9bmV3IEJkKHRoaXMub3B0aW9ucyksci5wdXNoKGkpLG49PT0wP2kuYXBwZW5kSW50b1BhcnQodGhpcyk6aS5pbnNlcnRBZnRlclBhcnQocltuLTFdKSksaS5zZXRWYWx1ZShvKSxpLmNvbW1pdCgpLG4rKztuPHIubGVuZ3RoJiYoci5sZW5ndGg9bix0aGlzLmNsZWFyKGkmJmkuZW5kTm9kZSkpfWNsZWFyKHQ9dGhpcy5zdGFydE5vZGUpe2l2KHRoaXMuc3RhcnROb2RlLnBhcmVudE5vZGUsdC5uZXh0U2libGluZyx0aGlzLmVuZE5vZGUpfX0sYlA9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4pe2lmKHRoaXMudmFsdWU9dm9pZCAwLHRoaXMuX19wZW5kaW5nVmFsdWU9dm9pZCAwLG4ubGVuZ3RoIT09Mnx8blswXSE9PSIifHxuWzFdIT09IiIpdGhyb3cgbmV3IEVycm9yKCJCb29sZWFuIGF0dHJpYnV0ZXMgY2FuIG9ubHkgY29udGFpbiBhIHNpbmdsZSBleHByZXNzaW9uIik7dGhpcy5lbGVtZW50PXQsdGhpcy5uYW1lPXIsdGhpcy5zdHJpbmdzPW59c2V0VmFsdWUodCl7dGhpcy5fX3BlbmRpbmdWYWx1ZT10fWNvbW1pdCgpe2Zvcig7YXYodGhpcy5fX3BlbmRpbmdWYWx1ZSk7KXtsZXQgcj10aGlzLl9fcGVuZGluZ1ZhbHVlO3RoaXMuX19wZW5kaW5nVmFsdWU9TGwscih0aGlzKX1pZih0aGlzLl9fcGVuZGluZ1ZhbHVlPT09TGwpcmV0dXJuO2xldCB0PSEhdGhpcy5fX3BlbmRpbmdWYWx1ZTt0aGlzLnZhbHVlIT09dCYmKHQ/dGhpcy5lbGVtZW50LnNldEF0dHJpYnV0ZSh0aGlzLm5hbWUsIiIpOnRoaXMuZWxlbWVudC5yZW1vdmVBdHRyaWJ1dGUodGhpcy5uYW1lKSx0aGlzLnZhbHVlPXQpLHRoaXMuX19wZW5kaW5nVmFsdWU9TGx9fSx3UD1jbGFzcyBleHRlbmRzIG0ze2NvbnN0cnVjdG9yKHQscixuKXtzdXBlcih0LHIsbiksdGhpcy5zaW5nbGU9bi5sZW5ndGg9PT0yJiZuWzBdPT09IiImJm5bMV09PT0iIn1fY3JlYXRlUGFydCgpe3JldHVybiBuZXcgVUgodGhpcyl9X2dldFZhbHVlKCl7cmV0dXJuIHRoaXMuc2luZ2xlP3RoaXMucGFydHNbMF0udmFsdWU6c3VwZXIuX2dldFZhbHVlKCl9Y29tbWl0KCl7dGhpcy5kaXJ0eSYmKHRoaXMuZGlydHk9ITEsdGhpcy5lbGVtZW50W3RoaXMubmFtZV09dGhpcy5fZ2V0VmFsdWUoKSl9fSxVSD1jbGFzcyBleHRlbmRzIHhQe30sX2NlPSExOygoKT0+e3RyeXtsZXQgZT17Z2V0IGNhcHR1cmUoKXtyZXR1cm4gX2NlPSEwLCExfX07d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoInRlc3QiLGUsZSksd2luZG93LnJlbW92ZUV2ZW50TGlzdGVuZXIoInRlc3QiLGUsZSl9Y2F0Y2goZSl7fX0pKCk7dmFyIFNQPWNsYXNze2NvbnN0cnVjdG9yKHQscixuKXt0aGlzLnZhbHVlPXZvaWQgMCx0aGlzLl9fcGVuZGluZ1ZhbHVlPXZvaWQgMCx0aGlzLmVsZW1lbnQ9dCx0aGlzLmV2ZW50TmFtZT1yLHRoaXMuZXZlbnRDb250ZXh0PW4sdGhpcy5fX2JvdW5kSGFuZGxlRXZlbnQ9aT0+dGhpcy5oYW5kbGVFdmVudChpKX1zZXRWYWx1ZSh0KXt0aGlzLl9fcGVuZGluZ1ZhbHVlPXR9Y29tbWl0KCl7Zm9yKDthdih0aGlzLl9fcGVuZGluZ1ZhbHVlKTspe2xldCBvPXRoaXMuX19wZW5kaW5nVmFsdWU7dGhpcy5fX3BlbmRpbmdWYWx1ZT1MbCxvKHRoaXMpfWlmKHRoaXMuX19wZW5kaW5nVmFsdWU9PT1MbClyZXR1cm47bGV0IHQ9dGhpcy5fX3BlbmRpbmdWYWx1ZSxyPXRoaXMudmFsdWUsbj10PT1udWxsfHxyIT1udWxsJiYodC5jYXB0dXJlIT09ci5jYXB0dXJlfHx0Lm9uY2UhPT1yLm9uY2V8fHQucGFzc2l2ZSE9PXIucGFzc2l2ZSksaT10IT1udWxsJiYocj09bnVsbHx8bik7biYmdGhpcy5lbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIodGhpcy5ldmVudE5hbWUsdGhpcy5fX2JvdW5kSGFuZGxlRXZlbnQsdGhpcy5fX29wdGlvbnMpLGkmJih0aGlzLl9fb3B0aW9ucz1QdXIodCksdGhpcy5lbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIodGhpcy5ldmVudE5hbWUsdGhpcy5fX2JvdW5kSGFuZGxlRXZlbnQsdGhpcy5fX29wdGlvbnMpKSx0aGlzLnZhbHVlPXQsdGhpcy5fX3BlbmRpbmdWYWx1ZT1MbH1oYW5kbGVFdmVudCh0KXt0eXBlb2YgdGhpcy52YWx1ZT09ImZ1bmN0aW9uIj90aGlzLnZhbHVlLmNhbGwodGhpcy5ldmVudENvbnRleHR8fHRoaXMuZWxlbWVudCx0KTp0aGlzLnZhbHVlLmhhbmRsZUV2ZW50KHQpfX0sUHVyPWU9PmUmJihfY2U/e2NhcHR1cmU6ZS5jYXB0dXJlLHBhc3NpdmU6ZS5wYXNzaXZlLG9uY2U6ZS5vbmNlfTplLmNhcHR1cmUpO2Z1bmN0aW9uIHhjdChlKXtsZXQgdD1zdi5nZXQoZS50eXBlKTt0PT09dm9pZCAwJiYodD17c3RyaW5nc0FycmF5Om5ldyBXZWFrTWFwLGtleVN0cmluZzpuZXcgTWFwfSxzdi5zZXQoZS50eXBlLHQpKTtsZXQgcj10LnN0cmluZ3NBcnJheS5nZXQoZS5zdHJpbmdzKTtpZihyIT09dm9pZCAwKXJldHVybiByO2xldCBuPWUuc3RyaW5ncy5qb2luKFljKTtyZXR1cm4gcj10LmtleVN0cmluZy5nZXQobikscj09PXZvaWQgMCYmKHI9bmV3IG92KGUsZS5nZXRUZW1wbGF0ZUVsZW1lbnQoKSksdC5rZXlTdHJpbmcuc2V0KG4scikpLHQuc3RyaW5nc0FycmF5LnNldChlLnN0cmluZ3Mscikscn12YXIgc3Y9bmV3IE1hcDt2YXIgTTA9bmV3IFdlYWtNYXAsYmN0PShlLHQscik9PntsZXQgbj1NMC5nZXQodCk7bj09PXZvaWQgMCYmKGl2KHQsdC5maXJzdENoaWxkKSxNMC5zZXQodCxuPW5ldyBCZChPYmplY3QuYXNzaWduKHt0ZW1wbGF0ZUZhY3Rvcnk6eGN0fSxyKSkpLG4uYXBwZW5kSW50byh0KSksbi5zZXRWYWx1ZShlKSxuLmNvbW1pdCgpfTt2YXIgR0g9Y2xhc3N7aGFuZGxlQXR0cmlidXRlRXhwcmVzc2lvbnModCxyLG4saSl7bGV0IG89clswXTtyZXR1cm4gbz09PSIuIj9uZXcgd1AodCxyLnNsaWNlKDEpLG4pLnBhcnRzOm89PT0iQCI/W25ldyBTUCh0LHIuc2xpY2UoMSksaS5ldmVudENvbnRleHQpXTpvPT09Ij8iP1tuZXcgYlAodCxyLnNsaWNlKDEpLG4pXTpuZXcgbTModCxyLG4pLnBhcnRzfWhhbmRsZVRleHRFeHByZXNzaW9uKHQpe3JldHVybiBuZXcgQmQodCl9fSx5Y2U9bmV3IEdIO3R5cGVvZiB3aW5kb3chPSJ1bmRlZmluZWQiJiYod2luZG93LmxpdEh0bWxWZXJzaW9uc3x8KHdpbmRvdy5saXRIdG1sVmVyc2lvbnM9W10pKS5wdXNoKCIxLjQuMSIpO3ZhciB4Y2U9KGUsdCk9PmAke2V9LS0ke3R9YCxXSD0hMDt0eXBlb2Ygd2luZG93LlNoYWR5Q1NTPT0idW5kZWZpbmVkIj9XSD0hMTp0eXBlb2Ygd2luZG93LlNoYWR5Q1NTLnByZXBhcmVUZW1wbGF0ZURvbT09InVuZGVmaW5lZCImJihjb25zb2xlLndhcm4oIkluY29tcGF0aWJsZSBTaGFkeUNTUyB2ZXJzaW9uIGRldGVjdGVkLiBQbGVhc2UgdXBkYXRlIHRvIGF0IGxlYXN0IEB3ZWJjb21wb25lbnRzL3dlYmNvbXBvbmVudHNqc0AyLjAuMiBhbmQgQHdlYmNvbXBvbmVudHMvc2hhZHljc3NAMS4zLjEuIiksV0g9ITEpO3ZhciBrdXI9ZT0+dD0+e2xldCByPXhjZSh0LnR5cGUsZSksbj1zdi5nZXQocik7bj09PXZvaWQgMCYmKG49e3N0cmluZ3NBcnJheTpuZXcgV2Vha01hcCxrZXlTdHJpbmc6bmV3IE1hcH0sc3Yuc2V0KHIsbikpO2xldCBpPW4uc3RyaW5nc0FycmF5LmdldCh0LnN0cmluZ3MpO2lmKGkhPT12b2lkIDApcmV0dXJuIGk7bGV0IG89dC5zdHJpbmdzLmpvaW4oWWMpO2lmKGk9bi5rZXlTdHJpbmcuZ2V0KG8pLGk9PT12b2lkIDApe2xldCBhPXQuZ2V0VGVtcGxhdGVFbGVtZW50KCk7V0gmJndpbmRvdy5TaGFkeUNTUy5wcmVwYXJlVGVtcGxhdGVEb20oYSxlKSxpPW5ldyBvdih0LGEpLG4ua2V5U3RyaW5nLnNldChvLGkpfXJldHVybiBuLnN0cmluZ3NBcnJheS5zZXQodC5zdHJpbmdzLGkpLGl9LFJ1cj1bImh0bWwiLCJzdmciXSxOdXI9ZT0+e1J1ci5mb3JFYWNoKHQ9PntsZXQgcj1zdi5nZXQoeGNlKHQsZSkpO3IhPT12b2lkIDAmJnIua2V5U3RyaW5nLmZvckVhY2gobj0+e2xldHtlbGVtZW50Ontjb250ZW50Oml9fT1uLG89bmV3IFNldDtBcnJheS5mcm9tKGkucXVlcnlTZWxlY3RvckFsbCgic3R5bGUiKSkuZm9yRWFjaChhPT57by5hZGQoYSl9KSx2Y3QobixvKX0pfSl9LGJjZT1uZXcgU2V0LER1cj0oZSx0LHIpPT57YmNlLmFkZChlKTtsZXQgbj1yP3IuZWxlbWVudDpkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZW1wbGF0ZSIpLGk9dC5xdWVyeVNlbGVjdG9yQWxsKCJzdHlsZSIpLHtsZW5ndGg6b309aTtpZihvPT09MCl7d2luZG93LlNoYWR5Q1NTLnByZXBhcmVUZW1wbGF0ZVN0eWxlcyhuLGUpO3JldHVybn1sZXQgYT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzdHlsZSIpO2ZvcihsZXQgYz0wO2M8bztjKyspe2xldCB1PWlbY107dS5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKHUpLGEudGV4dENvbnRlbnQrPXUudGV4dENvbnRlbnR9TnVyKGUpO2xldCBzPW4uY29udGVudDtyP21jZShyLGEscy5maXJzdENoaWxkKTpzLmluc2VydEJlZm9yZShhLHMuZmlyc3RDaGlsZCksd2luZG93LlNoYWR5Q1NTLnByZXBhcmVUZW1wbGF0ZVN0eWxlcyhuLGUpO2xldCBsPXMucXVlcnlTZWxlY3Rvcigic3R5bGUiKTtpZih3aW5kb3cuU2hhZHlDU1MubmF0aXZlU2hhZG93JiZsIT09bnVsbCl0Lmluc2VydEJlZm9yZShsLmNsb25lTm9kZSghMCksdC5maXJzdENoaWxkKTtlbHNlIGlmKHIpe3MuaW5zZXJ0QmVmb3JlKGEscy5maXJzdENoaWxkKTtsZXQgYz1uZXcgU2V0O2MuYWRkKGEpLHZjdChyLGMpfX0sd2NlPShlLHQscik9PntpZighcnx8dHlwZW9mIHIhPSJvYmplY3QifHwhci5zY29wZU5hbWUpdGhyb3cgbmV3IEVycm9yKCJUaGUgYHNjb3BlTmFtZWAgb3B0aW9uIGlzIHJlcXVpcmVkLiIpO2xldCBuPXIuc2NvcGVOYW1lLGk9TTAuaGFzKHQpLG89V0gmJnQubm9kZVR5cGU9PT0xMSYmISF0Lmhvc3QsYT1vJiYhYmNlLmhhcyhuKSxzPWE/ZG9jdW1lbnQuY3JlYXRlRG9jdW1lbnRGcmFnbWVudCgpOnQ7aWYoYmN0KGUscyxPYmplY3QuYXNzaWduKHt0ZW1wbGF0ZUZhY3Rvcnk6a3VyKG4pfSxyKSksYSl7bGV0IGw9TTAuZ2V0KHMpO00wLmRlbGV0ZShzKTtsZXQgYz1sLnZhbHVlIGluc3RhbmNlb2YgdzA/bC52YWx1ZS50ZW1wbGF0ZTp2b2lkIDA7RHVyKG4scyxjKSxpdih0LHQuZmlyc3RDaGlsZCksdC5hcHBlbmRDaGlsZChzKSxNMC5zZXQodCxsKX0haSYmbyYmd2luZG93LlNoYWR5Q1NTLnN0eWxlRWxlbWVudCh0Lmhvc3QpfTt2YXIgU2NlO3dpbmRvdy5KU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5PShlLHQpPT5lO3ZhciBDY3Q9e3RvQXR0cmlidXRlKGUsdCl7c3dpdGNoKHQpe2Nhc2UgQm9vbGVhbjpyZXR1cm4gZT8iIjpudWxsO2Nhc2UgT2JqZWN0OmNhc2UgQXJyYXk6cmV0dXJuIGU9PW51bGw/ZTpKU09OLnN0cmluZ2lmeShlKX1yZXR1cm4gZX0sZnJvbUF0dHJpYnV0ZShlLHQpe3N3aXRjaCh0KXtjYXNlIEJvb2xlYW46cmV0dXJuIGUhPT1udWxsO2Nhc2UgTnVtYmVyOnJldHVybiBlPT09bnVsbD9udWxsOk51bWJlcihlKTtjYXNlIE9iamVjdDpjYXNlIEFycmF5OnJldHVybiBKU09OLnBhcnNlKGUpfXJldHVybiBlfX0sTWNlPShlLHQpPT50IT09ZSYmKHQ9PT10fHxlPT09ZSksd2N0PXthdHRyaWJ1dGU6ITAsdHlwZTpTdHJpbmcsY29udmVydGVyOkNjdCxyZWZsZWN0OiExLGhhc0NoYW5nZWQ6TWNlfSxTY3Q9MSxNY3Q9MTw8MixFY3Q9MTw8MyxUY3Q9MTw8NCxBY3Q9ImZpbmFsaXplZCIsZzM9Y2xhc3MgZXh0ZW5kcyBIVE1MRWxlbWVudHtjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy5pbml0aWFsaXplKCl9c3RhdGljIGdldCBvYnNlcnZlZEF0dHJpYnV0ZXMoKXt0aGlzLmZpbmFsaXplKCk7bGV0IHQ9W107cmV0dXJuIHRoaXMuX2NsYXNzUHJvcGVydGllcy5mb3JFYWNoKChyLG4pPT57bGV0IGk9dGhpcy5fYXR0cmlidXRlTmFtZUZvclByb3BlcnR5KG4scik7aSE9PXZvaWQgMCYmKHRoaXMuX2F0dHJpYnV0ZVRvUHJvcGVydHlNYXAuc2V0KGksbiksdC5wdXNoKGkpKX0pLHR9c3RhdGljIF9lbnN1cmVDbGFzc1Byb3BlcnRpZXMoKXtpZighdGhpcy5oYXNPd25Qcm9wZXJ0eShKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJfY2xhc3NQcm9wZXJ0aWVzIix0aGlzKSkpe3RoaXMuX2NsYXNzUHJvcGVydGllcz1uZXcgTWFwO2xldCB0PU9iamVjdC5nZXRQcm90b3R5cGVPZih0aGlzKS5fY2xhc3NQcm9wZXJ0aWVzO3QhPT12b2lkIDAmJnQuZm9yRWFjaCgocixuKT0+dGhpcy5fY2xhc3NQcm9wZXJ0aWVzLnNldChuLHIpKX19c3RhdGljIGNyZWF0ZVByb3BlcnR5KHQscj13Y3Qpe2lmKHRoaXMuX2Vuc3VyZUNsYXNzUHJvcGVydGllcygpLHRoaXMuX2NsYXNzUHJvcGVydGllcy5zZXQodCxyKSxyLm5vQWNjZXNzb3J8fHRoaXMucHJvdG90eXBlLmhhc093blByb3BlcnR5KHQpKXJldHVybjtsZXQgbj10eXBlb2YgdD09InN5bWJvbCI/U3ltYm9sKCk6YF9fJHt0fWAsaT10aGlzLmdldFByb3BlcnR5RGVzY3JpcHRvcih0LG4scik7aSE9PXZvaWQgMCYmT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMucHJvdG90eXBlLHQsaSl9c3RhdGljIGdldFByb3BlcnR5RGVzY3JpcHRvcih0LHIsbil7cmV0dXJue2dldCgpe3JldHVybiB0aGlzW3JdfSxzZXQoaSl7bGV0IG89dGhpc1t0XTt0aGlzW3JdPWksdGhpcy5yZXF1ZXN0VXBkYXRlSW50ZXJuYWwodCxvLG4pfSxjb25maWd1cmFibGU6ITAsZW51bWVyYWJsZTohMH19c3RhdGljIGdldFByb3BlcnR5T3B0aW9ucyh0KXtyZXR1cm4gdGhpcy5fY2xhc3NQcm9wZXJ0aWVzJiZ0aGlzLl9jbGFzc1Byb3BlcnRpZXMuZ2V0KHQpfHx3Y3R9c3RhdGljIGZpbmFsaXplKCl7bGV0IHQ9T2JqZWN0LmdldFByb3RvdHlwZU9mKHRoaXMpO2lmKHQuaGFzT3duUHJvcGVydHkoQWN0KXx8dC5maW5hbGl6ZSgpLHRoaXNbQWN0XT0hMCx0aGlzLl9lbnN1cmVDbGFzc1Byb3BlcnRpZXMoKSx0aGlzLl9hdHRyaWJ1dGVUb1Byb3BlcnR5TWFwPW5ldyBNYXAsdGhpcy5oYXNPd25Qcm9wZXJ0eShKU0NvbXBpbGVyX3JlbmFtZVByb3BlcnR5KCJwcm9wZXJ0aWVzIix0aGlzKSkpe2xldCByPXRoaXMucHJvcGVydGllcyxuPVsuLi5PYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyhyKSwuLi50eXBlb2YgT2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scz09ImZ1bmN0aW9uIj9PYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKHIpOltdXTtmb3IobGV0IGkgb2Ygbil0aGlzLmNyZWF0ZVByb3BlcnR5KGkscltpXSl9fXN0YXRpYyBfYXR0cmlidXRlTmFtZUZvclByb3BlcnR5KHQscil7bGV0IG49ci5hdHRyaWJ1dGU7cmV0dXJuIG49PT0hMT92b2lkIDA6dHlwZW9mIG49PSJzdHJpbmciP246dHlwZW9mIHQ9PSJzdHJpbmciP3QudG9Mb3dlckNhc2UoKTp2b2lkIDB9c3RhdGljIF92YWx1ZUhhc0NoYW5nZWQodCxyLG49TWNlKXtyZXR1cm4gbih0LHIpfXN0YXRpYyBfcHJvcGVydHlWYWx1ZUZyb21BdHRyaWJ1dGUodCxyKXtsZXQgbj1yLnR5cGUsaT1yLmNvbnZlcnRlcnx8Q2N0LG89dHlwZW9mIGk9PSJmdW5jdGlvbiI/aTppLmZyb21BdHRyaWJ1dGU7cmV0dXJuIG8/byh0LG4pOnR9c3RhdGljIF9wcm9wZXJ0eVZhbHVlVG9BdHRyaWJ1dGUodCxyKXtpZihyLnJlZmxlY3Q9PT12b2lkIDApcmV0dXJuO2xldCBuPXIudHlwZSxpPXIuY29udmVydGVyO3JldHVybihpJiZpLnRvQXR0cmlidXRlfHxDY3QudG9BdHRyaWJ1dGUpKHQsbil9aW5pdGlhbGl6ZSgpe3RoaXMuX3VwZGF0ZVN0YXRlPTAsdGhpcy5fdXBkYXRlUHJvbWlzZT1uZXcgUHJvbWlzZSh0PT50aGlzLl9lbmFibGVVcGRhdGluZ1Jlc29sdmVyPXQpLHRoaXMuX2NoYW5nZWRQcm9wZXJ0aWVzPW5ldyBNYXAsdGhpcy5fc2F2ZUluc3RhbmNlUHJvcGVydGllcygpLHRoaXMucmVxdWVzdFVwZGF0ZUludGVybmFsKCl9X3NhdmVJbnN0YW5jZVByb3BlcnRpZXMoKXt0aGlzLmNvbnN0cnVjdG9yLl9jbGFzc1Byb3BlcnRpZXMuZm9yRWFjaCgodCxyKT0+e2lmKHRoaXMuaGFzT3duUHJvcGVydHkocikpe2xldCBuPXRoaXNbcl07ZGVsZXRlIHRoaXNbcl0sdGhpcy5faW5zdGFuY2VQcm9wZXJ0aWVzfHwodGhpcy5faW5zdGFuY2VQcm9wZXJ0aWVzPW5ldyBNYXApLHRoaXMuX2luc3RhbmNlUHJvcGVydGllcy5zZXQocixuKX19KX1fYXBwbHlJbnN0YW5jZVByb3BlcnRpZXMoKXt0aGlzLl9pbnN0YW5jZVByb3BlcnRpZXMuZm9yRWFjaCgodCxyKT0+dGhpc1tyXT10KSx0aGlzLl9pbnN0YW5jZVByb3BlcnRpZXM9dm9pZCAwfWNvbm5lY3RlZENhbGxiYWNrKCl7dGhpcy5lbmFibGVVcGRhdGluZygpfWVuYWJsZVVwZGF0aW5nKCl7dGhpcy5fZW5hYmxlVXBkYXRpbmdSZXNvbHZlciE9PXZvaWQgMCYmKHRoaXMuX2VuYWJsZVVwZGF0aW5nUmVzb2x2ZXIoKSx0aGlzLl9lbmFibGVVcGRhdGluZ1Jlc29sdmVyPXZvaWQgMCl9ZGlzY29ubmVjdGVkQ2FsbGJhY2soKXt9YXR0cmlidXRlQ2hhbmdlZENhbGxiYWNrKHQscixuKXtyIT09biYmdGhpcy5fYXR0cmlidXRlVG9Qcm9wZXJ0eSh0LG4pfV9wcm9wZXJ0eVRvQXR0cmlidXRlKHQscixuPXdjdCl7bGV0IGk9dGhpcy5jb25zdHJ1Y3RvcixvPWkuX2F0dHJpYnV0ZU5hbWVGb3JQcm9wZXJ0eSh0LG4pO2lmKG8hPT12b2lkIDApe2xldCBhPWkuX3Byb3BlcnR5VmFsdWVUb0F0dHJpYnV0ZShyLG4pO2lmKGE9PT12b2lkIDApcmV0dXJuO3RoaXMuX3VwZGF0ZVN0YXRlPXRoaXMuX3VwZGF0ZVN0YXRlfEVjdCxhPT1udWxsP3RoaXMucmVtb3ZlQXR0cmlidXRlKG8pOnRoaXMuc2V0QXR0cmlidXRlKG8sYSksdGhpcy5fdXBkYXRlU3RhdGU9dGhpcy5fdXBkYXRlU3RhdGUmfkVjdH19X2F0dHJpYnV0ZVRvUHJvcGVydHkodCxyKXtpZih0aGlzLl91cGRhdGVTdGF0ZSZFY3QpcmV0dXJuO2xldCBuPXRoaXMuY29uc3RydWN0b3IsaT1uLl9hdHRyaWJ1dGVUb1Byb3BlcnR5TWFwLmdldCh0KTtpZihpIT09dm9pZCAwKXtsZXQgbz1uLmdldFByb3BlcnR5T3B0aW9ucyhpKTt0aGlzLl91cGRhdGVTdGF0ZT10aGlzLl91cGRhdGVTdGF0ZXxUY3QsdGhpc1tpXT1uLl9wcm9wZXJ0eVZhbHVlRnJvbUF0dHJpYnV0ZShyLG8pLHRoaXMuX3VwZGF0ZVN0YXRlPXRoaXMuX3VwZGF0ZVN0YXRlJn5UY3R9fXJlcXVlc3RVcGRhdGVJbnRlcm5hbCh0LHIsbil7bGV0IGk9ITA7aWYodCE9PXZvaWQgMCl7bGV0IG89dGhpcy5jb25zdHJ1Y3RvcjtuPW58fG8uZ2V0UHJvcGVydHlPcHRpb25zKHQpLG8uX3ZhbHVlSGFzQ2hhbmdlZCh0aGlzW3RdLHIsbi5oYXNDaGFuZ2VkKT8odGhpcy5fY2hhbmdlZFByb3BlcnRpZXMuaGFzKHQpfHx0aGlzLl9jaGFuZ2VkUHJvcGVydGllcy5zZXQodCxyKSxuLnJlZmxlY3Q9PT0hMCYmISh0aGlzLl91cGRhdGVTdGF0ZSZUY3QpJiYodGhpcy5fcmVmbGVjdGluZ1Byb3BlcnRpZXM9PT12b2lkIDAmJih0aGlzLl9yZWZsZWN0aW5nUHJvcGVydGllcz1uZXcgTWFwKSx0aGlzLl9yZWZsZWN0aW5nUHJvcGVydGllcy5zZXQodCxuKSkpOmk9ITF9IXRoaXMuX2hhc1JlcXVlc3RlZFVwZGF0ZSYmaSYmKHRoaXMuX3VwZGF0ZVByb21pc2U9dGhpcy5fZW5xdWV1ZVVwZGF0ZSgpKX1yZXF1ZXN0VXBkYXRlKHQscil7cmV0dXJuIHRoaXMucmVxdWVzdFVwZGF0ZUludGVybmFsKHQsciksdGhpcy51cGRhdGVDb21wbGV0ZX1fZW5xdWV1ZVVwZGF0ZSgpe3JldHVybiBSaSh0aGlzLG51bGwsZnVuY3Rpb24qKCl7dGhpcy5fdXBkYXRlU3RhdGU9dGhpcy5fdXBkYXRlU3RhdGV8TWN0O3RyeXt5aWVsZCB0aGlzLl91cGRhdGVQcm9taXNlfWNhdGNoKHIpe31sZXQgdD10aGlzLnBlcmZvcm1VcGRhdGUoKTtyZXR1cm4gdCE9bnVsbCYmKHlpZWxkIHQpLCF0aGlzLl9oYXNSZXF1ZXN0ZWRVcGRhdGV9KX1nZXQgX2hhc1JlcXVlc3RlZFVwZGF0ZSgpe3JldHVybiB0aGlzLl91cGRhdGVTdGF0ZSZNY3R9Z2V0IGhhc1VwZGF0ZWQoKXtyZXR1cm4gdGhpcy5fdXBkYXRlU3RhdGUmU2N0fXBlcmZvcm1VcGRhdGUoKXtpZighdGhpcy5faGFzUmVxdWVzdGVkVXBkYXRlKXJldHVybjt0aGlzLl9pbnN0YW5jZVByb3BlcnRpZXMmJnRoaXMuX2FwcGx5SW5zdGFuY2VQcm9wZXJ0aWVzKCk7bGV0IHQ9ITEscj10aGlzLl9jaGFuZ2VkUHJvcGVydGllczt0cnl7dD10aGlzLnNob3VsZFVwZGF0ZShyKSx0P3RoaXMudXBkYXRlKHIpOnRoaXMuX21hcmtVcGRhdGVkKCl9Y2F0Y2gobil7dGhyb3cgdD0hMSx0aGlzLl9tYXJrVXBkYXRlZCgpLG59dCYmKHRoaXMuX3VwZGF0ZVN0YXRlJlNjdHx8KHRoaXMuX3VwZGF0ZVN0YXRlPXRoaXMuX3VwZGF0ZVN0YXRlfFNjdCx0aGlzLmZpcnN0VXBkYXRlZChyKSksdGhpcy51cGRhdGVkKHIpKX1fbWFya1VwZGF0ZWQoKXt0aGlzLl9jaGFuZ2VkUHJvcGVydGllcz1uZXcgTWFwLHRoaXMuX3VwZGF0ZVN0YXRlPXRoaXMuX3VwZGF0ZVN0YXRlJn5NY3R9Z2V0IHVwZGF0ZUNvbXBsZXRlKCl7cmV0dXJuIHRoaXMuX2dldFVwZGF0ZUNvbXBsZXRlKCl9X2dldFVwZGF0ZUNvbXBsZXRlKCl7cmV0dXJuIHRoaXMuZ2V0VXBkYXRlQ29tcGxldGUoKX1nZXRVcGRhdGVDb21wbGV0ZSgpe3JldHVybiB0aGlzLl91cGRhdGVQcm9taXNlfXNob3VsZFVwZGF0ZSh0KXtyZXR1cm4hMH11cGRhdGUodCl7dGhpcy5fcmVmbGVjdGluZ1Byb3BlcnRpZXMhPT12b2lkIDAmJnRoaXMuX3JlZmxlY3RpbmdQcm9wZXJ0aWVzLnNpemU+MCYmKHRoaXMuX3JlZmxlY3RpbmdQcm9wZXJ0aWVzLmZvckVhY2goKHIsbik9PnRoaXMuX3Byb3BlcnR5VG9BdHRyaWJ1dGUobix0aGlzW25dLHIpKSx0aGlzLl9yZWZsZWN0aW5nUHJvcGVydGllcz12b2lkIDApLHRoaXMuX21hcmtVcGRhdGVkKCl9dXBkYXRlZCh0KXt9Zmlyc3RVcGRhdGVkKHQpe319O1NjZT1BY3Q7ZzNbU2NlXT0hMDt2YXIgRWNlPUVsZW1lbnQucHJvdG90eXBlLHlpaT1FY2UubXNNYXRjaGVzU2VsZWN0b3J8fEVjZS53ZWJraXRNYXRjaGVzU2VsZWN0b3I7dmFyIFlIPXdpbmRvdy5TaGFkb3dSb290JiYod2luZG93LlNoYWR5Q1NTPT09dm9pZCAwfHx3aW5kb3cuU2hhZHlDU1MubmF0aXZlU2hhZG93KSYmImFkb3B0ZWRTdHlsZVNoZWV0cyJpbiBEb2N1bWVudC5wcm90b3R5cGUmJiJyZXBsYWNlImluIENTU1N0eWxlU2hlZXQucHJvdG90eXBlLFBjdD1TeW1ib2woKSxsdj1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIpe2lmKHIhPT1QY3QpdGhyb3cgbmV3IEVycm9yKCJDU1NSZXN1bHQgaXMgbm90IGNvbnN0cnVjdGFibGUuIFVzZSBgdW5zYWZlQ1NTYCBvciBgY3NzYCBpbnN0ZWFkLiIpO3RoaXMuY3NzVGV4dD10fWdldCBzdHlsZVNoZWV0KCl7cmV0dXJuIHRoaXMuX3N0eWxlU2hlZXQ9PT12b2lkIDAmJihZSD8odGhpcy5fc3R5bGVTaGVldD1uZXcgQ1NTU3R5bGVTaGVldCx0aGlzLl9zdHlsZVNoZWV0LnJlcGxhY2VTeW5jKHRoaXMuY3NzVGV4dCkpOnRoaXMuX3N0eWxlU2hlZXQ9bnVsbCksdGhpcy5fc3R5bGVTaGVldH10b1N0cmluZygpe3JldHVybiB0aGlzLmNzc1RleHR9fSxJY3Q9ZT0+bmV3IGx2KFN0cmluZyhlKSxQY3QpLE91cj1lPT57aWYoZSBpbnN0YW5jZW9mIGx2KXJldHVybiBlLmNzc1RleHQ7aWYodHlwZW9mIGU9PSJudW1iZXIiKXJldHVybiBlO3Rocm93IG5ldyBFcnJvcihgVmFsdWUgcGFzc2VkIHRvICdjc3MnIGZ1bmN0aW9uIG11c3QgYmUgYSAnY3NzJyBmdW5jdGlvbiByZXN1bHQ6ICR7ZX0uIFVzZSAndW5zYWZlQ1NTJyB0byBwYXNzIG5vbi1saXRlcmFsIHZhbHVlcywgYnV0CiAgICAgICAgICAgIHRha2UgY2FyZSB0byBlbnN1cmUgcGFnZSBzZWN1cml0eS5gKX0sQ2k9KGUsLi4udCk9PntsZXQgcj10LnJlZHVjZSgobixpLG8pPT5uK091cihpKStlW28rMV0sZVswXSk7cmV0dXJuIG5ldyBsdihyLFBjdCl9Oyh3aW5kb3cubGl0RWxlbWVudFZlcnNpb25zfHwod2luZG93LmxpdEVsZW1lbnRWZXJzaW9ucz1bXSkpLnB1c2goIjIuNS4xIik7dmFyIFRjZT17fSxNUD1jbGFzcyBleHRlbmRzIGcze3N0YXRpYyBnZXRTdHlsZXMoKXtyZXR1cm4gdGhpcy5zdHlsZXN9c3RhdGljIF9nZXRVbmlxdWVTdHlsZXMoKXtpZih0aGlzLmhhc093blByb3BlcnR5KEpTQ29tcGlsZXJfcmVuYW1lUHJvcGVydHkoIl9zdHlsZXMiLHRoaXMpKSlyZXR1cm47bGV0IHQ9dGhpcy5nZXRTdHlsZXMoKTtpZihBcnJheS5pc0FycmF5KHQpKXtsZXQgcj0obyxhKT0+by5yZWR1Y2VSaWdodCgocyxsKT0+QXJyYXkuaXNBcnJheShsKT9yKGwscyk6KHMuYWRkKGwpLHMpLGEpLG49cih0LG5ldyBTZXQpLGk9W107bi5mb3JFYWNoKG89PmkudW5zaGlmdChvKSksdGhpcy5fc3R5bGVzPWl9ZWxzZSB0aGlzLl9zdHlsZXM9dD09PXZvaWQgMD9bXTpbdF07dGhpcy5fc3R5bGVzPXRoaXMuX3N0eWxlcy5tYXAocj0+e2lmKHIgaW5zdGFuY2VvZiBDU1NTdHlsZVNoZWV0JiYhWUgpe2xldCBuPUFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKHIuY3NzUnVsZXMpLnJlZHVjZSgoaSxvKT0+aStvLmNzc1RleHQsIiIpO3JldHVybiBJY3Qobil9cmV0dXJuIHJ9KX1pbml0aWFsaXplKCl7c3VwZXIuaW5pdGlhbGl6ZSgpLHRoaXMuY29uc3RydWN0b3IuX2dldFVuaXF1ZVN0eWxlcygpLHRoaXMucmVuZGVyUm9vdD10aGlzLmNyZWF0ZVJlbmRlclJvb3QoKSx3aW5kb3cuU2hhZG93Um9vdCYmdGhpcy5yZW5kZXJSb290IGluc3RhbmNlb2Ygd2luZG93LlNoYWRvd1Jvb3QmJnRoaXMuYWRvcHRTdHlsZXMoKX1jcmVhdGVSZW5kZXJSb290KCl7cmV0dXJuIHRoaXMuYXR0YWNoU2hhZG93KHRoaXMuY29uc3RydWN0b3Iuc2hhZG93Um9vdE9wdGlvbnMpfWFkb3B0U3R5bGVzKCl7bGV0IHQ9dGhpcy5jb25zdHJ1Y3Rvci5fc3R5bGVzO3QubGVuZ3RoIT09MCYmKHdpbmRvdy5TaGFkeUNTUyE9PXZvaWQgMCYmIXdpbmRvdy5TaGFkeUNTUy5uYXRpdmVTaGFkb3c/d2luZG93LlNoYWR5Q1NTLlNjb3BpbmdTaGltLnByZXBhcmVBZG9wdGVkQ3NzVGV4dCh0Lm1hcChyPT5yLmNzc1RleHQpLHRoaXMubG9jYWxOYW1lKTpZSD90aGlzLnJlbmRlclJvb3QuYWRvcHRlZFN0eWxlU2hlZXRzPXQubWFwKHI9PnIgaW5zdGFuY2VvZiBDU1NTdHlsZVNoZWV0P3I6ci5zdHlsZVNoZWV0KTp0aGlzLl9uZWVkc1NoaW1BZG9wdGVkU3R5bGVTaGVldHM9ITApfWNvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLmhhc1VwZGF0ZWQmJndpbmRvdy5TaGFkeUNTUyE9PXZvaWQgMCYmd2luZG93LlNoYWR5Q1NTLnN0eWxlRWxlbWVudCh0aGlzKX11cGRhdGUodCl7bGV0IHI9dGhpcy5yZW5kZXIoKTtzdXBlci51cGRhdGUodCksciE9PVRjZSYmdGhpcy5jb25zdHJ1Y3Rvci5yZW5kZXIocix0aGlzLnJlbmRlclJvb3Qse3Njb3BlTmFtZTp0aGlzLmxvY2FsTmFtZSxldmVudENvbnRleHQ6dGhpc30pLHRoaXMuX25lZWRzU2hpbUFkb3B0ZWRTdHlsZVNoZWV0cyYmKHRoaXMuX25lZWRzU2hpbUFkb3B0ZWRTdHlsZVNoZWV0cz0hMSx0aGlzLmNvbnN0cnVjdG9yLl9zdHlsZXMuZm9yRWFjaChuPT57bGV0IGk9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3R5bGUiKTtpLnRleHRDb250ZW50PW4uY3NzVGV4dCx0aGlzLnJlbmRlclJvb3QuYXBwZW5kQ2hpbGQoaSl9KSl9cmVuZGVyKCl7cmV0dXJuIFRjZX19O01QLmZpbmFsaXplZD0hMDtNUC5yZW5kZXI9d2NlO01QLnNoYWRvd1Jvb3RPcHRpb25zPXttb2RlOiJvcGVuIn07dmFyIENjZT0wLExjdD17fSxqYz0oZSx0LHIpPT57bGV0IG49ciYmci5tb2R1bGVJZHx8YGN1c3RvbS1zdHlsZS1tb2R1bGUtJHtDY2UrK31gO0FycmF5LmlzQXJyYXkodCl8fCh0PXQ/W3RdOltdKSx0LmZvckVhY2goYT0+e2lmKCEoYSBpbnN0YW5jZW9mIGx2KSl0aHJvdyBuZXcgRXJyb3IoIkFuIGl0ZW0gaW4gc3R5bGVzIGlzIG5vdCBvZiB0eXBlIENTU1Jlc3VsdC4gVXNlIGB1bnNhZmVDU1NgIG9yIGBjc3NgLiIpO2lmKCFMY3RbYV0pe2xldCBzPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRvbS1tb2R1bGUiKTtzLmlubmVySFRNTD1gCiAgICAgICAgPHRlbXBsYXRlPgogICAgICAgICAgPHN0eWxlPiR7YS50b1N0cmluZygpfTwvc3R5bGU+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgYDtsZXQgbD1gY3VzdG9tLXN0eWxlLW1vZHVsZS0ke0NjZSsrfWA7cy5yZWdpc3RlcihsKSxMY3RbYV09bH19KTtsZXQgaT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkb20tbW9kdWxlIik7aWYoZSl7bGV0IGE9Y3VzdG9tRWxlbWVudHMuZ2V0KGUpO2EmJk9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChhLCJfX2ZpbmFsaXplZCIpJiZjb25zb2xlLndhcm4oYFRoZSBjdXN0b20gZWxlbWVudCBkZWZpbml0aW9uIGZvciAiJHtlfSIKICAgICAgd2FzIGZpbmFsaXplZCBiZWZvcmUgYSBzdHlsZSBtb2R1bGUgd2FzIHJlZ2lzdGVyZWQuCiAgICAgIE1ha2Ugc3VyZSB0byBhZGQgY29tcG9uZW50IHNwZWNpZmljIHN0eWxlIG1vZHVsZXMgYmVmb3JlCiAgICAgIGltcG9ydGluZyB0aGUgY29ycmVzcG9uZGluZyBjdXN0b20gZWxlbWVudC5gKSxpLnNldEF0dHJpYnV0ZSgidGhlbWUtZm9yIixlKX1sZXQgbz1yJiZyLmluY2x1ZGV8fFtdO2kuaW5uZXJIVE1MPWAKICAgIDx0ZW1wbGF0ZT4KICAgICAgJHtvLm1hcChhPT5gPHN0eWxlIGluY2x1ZGU9JHthfT48L3N0eWxlPmApfQogICAgICAke3QubWFwKGE9PmA8c3R5bGUgaW5jbHVkZT0ke0xjdFthXX0+PC9zdHlsZT5gKX0KICAgIDwvdGVtcGxhdGU+CiAgYCxpLnJlZ2lzdGVyKG4pfTt2YXIga2N0PWNsYXNzIGV4dGVuZHMgSFRNTEVsZW1lbnR7c3RhdGljIGdldCB2ZXJzaW9uKCl7cmV0dXJuIjIwLjAuMiJ9fTtjdXN0b21FbGVtZW50cy5kZWZpbmUoInZhYWRpbi1sdW1vLXN0eWxlcyIsa2N0KTt2YXIgenVyPUNpYAogIDpob3N0IHsKICAgIC8qIEJhc2UgKGJhY2tncm91bmQpICovCiAgICAtLWx1bW8tYmFzZS1jb2xvcjogI2ZmZjsKCiAgICAvKiBUaW50ICovCiAgICAtLWx1bW8tdGludC01cGN0OiBoc2xhKDAsIDAlLCAxMDAlLCAwLjMpOwogICAgLS1sdW1vLXRpbnQtMTBwY3Q6IGhzbGEoMCwgMCUsIDEwMCUsIDAuMzcpOwogICAgLS1sdW1vLXRpbnQtMjBwY3Q6IGhzbGEoMCwgMCUsIDEwMCUsIDAuNDQpOwogICAgLS1sdW1vLXRpbnQtMzBwY3Q6IGhzbGEoMCwgMCUsIDEwMCUsIDAuNSk7CiAgICAtLWx1bW8tdGludC00MHBjdDogaHNsYSgwLCAwJSwgMTAwJSwgMC41Nyk7CiAgICAtLWx1bW8tdGludC01MHBjdDogaHNsYSgwLCAwJSwgMTAwJSwgMC42NCk7CiAgICAtLWx1bW8tdGludC02MHBjdDogaHNsYSgwLCAwJSwgMTAwJSwgMC43KTsKICAgIC0tbHVtby10aW50LTcwcGN0OiBoc2xhKDAsIDAlLCAxMDAlLCAwLjc3KTsKICAgIC0tbHVtby10aW50LTgwcGN0OiBoc2xhKDAsIDAlLCAxMDAlLCAwLjg0KTsKICAgIC0tbHVtby10aW50LTkwcGN0OiBoc2xhKDAsIDAlLCAxMDAlLCAwLjkpOwogICAgLS1sdW1vLXRpbnQ6ICNmZmY7CgogICAgLyogU2hhZGUgKi8KICAgIC0tbHVtby1zaGFkZS01cGN0OiBoc2xhKDIxNCwgNjElLCAyNSUsIDAuMDUpOwogICAgLS1sdW1vLXNoYWRlLTEwcGN0OiBoc2xhKDIxNCwgNTclLCAyNCUsIDAuMSk7CiAgICAtLWx1bW8tc2hhZGUtMjBwY3Q6IGhzbGEoMjE0LCA1MyUsIDIzJSwgMC4xNik7CiAgICAtLWx1bW8tc2hhZGUtMzBwY3Q6IGhzbGEoMjE0LCA1MCUsIDIyJSwgMC4yNik7CiAgICAtLWx1bW8tc2hhZGUtNDBwY3Q6IGhzbGEoMjE0LCA0NyUsIDIxJSwgMC4zOCk7CiAgICAtLWx1bW8tc2hhZGUtNTBwY3Q6IGhzbGEoMjE0LCA0NSUsIDIwJSwgMC41KTsKICAgIC0tbHVtby1zaGFkZS02MHBjdDogaHNsYSgyMTQsIDQzJSwgMTklLCAwLjYxKTsKICAgIC0tbHVtby1zaGFkZS03MHBjdDogaHNsYSgyMTQsIDQyJSwgMTglLCAwLjcyKTsKICAgIC0tbHVtby1zaGFkZS04MHBjdDogaHNsYSgyMTQsIDQxJSwgMTclLCAwLjgzKTsKICAgIC0tbHVtby1zaGFkZS05MHBjdDogaHNsYSgyMTQsIDQwJSwgMTYlLCAwLjk0KTsKICAgIC0tbHVtby1zaGFkZTogaHNsKDIxNCwgMzUlLCAxNSUpOwoKICAgIC8qIENvbnRyYXN0ICovCiAgICAtLWx1bW8tY29udHJhc3QtNXBjdDogdmFyKC0tbHVtby1zaGFkZS01cGN0KTsKICAgIC0tbHVtby1jb250cmFzdC0xMHBjdDogdmFyKC0tbHVtby1zaGFkZS0xMHBjdCk7CiAgICAtLWx1bW8tY29udHJhc3QtMjBwY3Q6IHZhcigtLWx1bW8tc2hhZGUtMjBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTMwcGN0OiB2YXIoLS1sdW1vLXNoYWRlLTMwcGN0KTsKICAgIC0tbHVtby1jb250cmFzdC00MHBjdDogdmFyKC0tbHVtby1zaGFkZS00MHBjdCk7CiAgICAtLWx1bW8tY29udHJhc3QtNTBwY3Q6IHZhcigtLWx1bW8tc2hhZGUtNTBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTYwcGN0OiB2YXIoLS1sdW1vLXNoYWRlLTYwcGN0KTsKICAgIC0tbHVtby1jb250cmFzdC03MHBjdDogdmFyKC0tbHVtby1zaGFkZS03MHBjdCk7CiAgICAtLWx1bW8tY29udHJhc3QtODBwY3Q6IHZhcigtLWx1bW8tc2hhZGUtODBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTkwcGN0OiB2YXIoLS1sdW1vLXNoYWRlLTkwcGN0KTsKICAgIC0tbHVtby1jb250cmFzdDogdmFyKC0tbHVtby1zaGFkZSk7CgogICAgLyogVGV4dCAqLwogICAgLS1sdW1vLWhlYWRlci10ZXh0LWNvbG9yOiB2YXIoLS1sdW1vLWNvbnRyYXN0KTsKICAgIC0tbHVtby1ib2R5LXRleHQtY29sb3I6IHZhcigtLWx1bW8tY29udHJhc3QtOTBwY3QpOwogICAgLS1sdW1vLXNlY29uZGFyeS10ZXh0LWNvbG9yOiB2YXIoLS1sdW1vLWNvbnRyYXN0LTcwcGN0KTsKICAgIC0tbHVtby10ZXJ0aWFyeS10ZXh0LWNvbG9yOiB2YXIoLS1sdW1vLWNvbnRyYXN0LTUwcGN0KTsKICAgIC0tbHVtby1kaXNhYmxlZC10ZXh0LWNvbG9yOiB2YXIoLS1sdW1vLWNvbnRyYXN0LTMwcGN0KTsKCiAgICAvKiBQcmltYXJ5ICovCiAgICAtLWx1bW8tcHJpbWFyeS1jb2xvcjogaHNsKDIxNCwgOTAlLCA1MiUpOwogICAgLS1sdW1vLXByaW1hcnktY29sb3ItNTBwY3Q6IGhzbGEoMjE0LCA5MCUsIDUyJSwgMC41KTsKICAgIC0tbHVtby1wcmltYXJ5LWNvbG9yLTEwcGN0OiBoc2xhKDIxNCwgOTAlLCA1MiUsIDAuMSk7CiAgICAtLWx1bW8tcHJpbWFyeS10ZXh0LWNvbG9yOiB2YXIoLS1sdW1vLXByaW1hcnktY29sb3IpOwogICAgLS1sdW1vLXByaW1hcnktY29udHJhc3QtY29sb3I6ICNmZmY7CgogICAgLyogRXJyb3IgKi8KICAgIC0tbHVtby1lcnJvci1jb2xvcjogaHNsKDMsIDEwMCUsIDYxJSk7CiAgICAtLWx1bW8tZXJyb3ItY29sb3ItNTBwY3Q6IGhzbGEoMywgMTAwJSwgNjAlLCAwLjUpOwogICAgLS1sdW1vLWVycm9yLWNvbG9yLTEwcGN0OiBoc2xhKDMsIDEwMCUsIDYwJSwgMC4xKTsKICAgIC0tbHVtby1lcnJvci10ZXh0LWNvbG9yOiBoc2woMywgOTIlLCA1MyUpOwogICAgLS1sdW1vLWVycm9yLWNvbnRyYXN0LWNvbG9yOiAjZmZmOwoKICAgIC8qIFN1Y2Nlc3MgKi8KICAgIC0tbHVtby1zdWNjZXNzLWNvbG9yOiBoc2woMTQ1LCA4MCUsIDQyJSk7IC8qIGhzbCgxNDQsODIlLDM3JSk7ICovCiAgICAtLWx1bW8tc3VjY2Vzcy1jb2xvci01MHBjdDogaHNsYSgxNDUsIDc2JSwgNDQlLCAwLjU1KTsKICAgIC0tbHVtby1zdWNjZXNzLWNvbG9yLTEwcGN0OiBoc2xhKDE0NSwgNzYlLCA0NCUsIDAuMTIpOwogICAgLS1sdW1vLXN1Y2Nlc3MtdGV4dC1jb2xvcjogaHNsKDE0NSwgMTAwJSwgMzIlKTsKICAgIC0tbHVtby1zdWNjZXNzLWNvbnRyYXN0LWNvbG9yOiAjZmZmOwogIH0KYCxBY2U9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGVtcGxhdGUiKTtBY2UuaW5uZXJIVE1MPWA8c3R5bGU+JHt6dXIudG9TdHJpbmcoKS5yZXBsYWNlKCI6aG9zdCIsImh0bWwiKX08L3N0eWxlPmA7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChBY2UuY29udGVudCk7dmFyIEZ1cj1DaWAKICBbdGhlbWV+PSdkYXJrJ10gewogICAgLyogQmFzZSAoYmFja2dyb3VuZCkgKi8KICAgIC0tbHVtby1iYXNlLWNvbG9yOiBoc2woMjE0LCAzNSUsIDIxJSk7CgogICAgLyogVGludCAqLwogICAgLS1sdW1vLXRpbnQtNXBjdDogaHNsYSgyMTQsIDY1JSwgODUlLCAwLjA2KTsKICAgIC0tbHVtby10aW50LTEwcGN0OiBoc2xhKDIxNCwgNjAlLCA4MCUsIDAuMTQpOwogICAgLS1sdW1vLXRpbnQtMjBwY3Q6IGhzbGEoMjE0LCA2NCUsIDgyJSwgMC4yMyk7CiAgICAtLWx1bW8tdGludC0zMHBjdDogaHNsYSgyMTQsIDY5JSwgODQlLCAwLjMyKTsKICAgIC0tbHVtby10aW50LTQwcGN0OiBoc2xhKDIxNCwgNzMlLCA4NiUsIDAuNDEpOwogICAgLS1sdW1vLXRpbnQtNTBwY3Q6IGhzbGEoMjE0LCA3OCUsIDg4JSwgMC41KTsKICAgIC0tbHVtby10aW50LTYwcGN0OiBoc2xhKDIxNCwgODIlLCA5MCUsIDAuNik7CiAgICAtLWx1bW8tdGludC03MHBjdDogaHNsYSgyMTQsIDg3JSwgOTIlLCAwLjcpOwogICAgLS1sdW1vLXRpbnQtODBwY3Q6IGhzbGEoMjE0LCA5MSUsIDk0JSwgMC44KTsKICAgIC0tbHVtby10aW50LTkwcGN0OiBoc2xhKDIxNCwgOTYlLCA5NiUsIDAuOSk7CiAgICAtLWx1bW8tdGludDogaHNsKDIxNCwgMTAwJSwgOTglKTsKCiAgICAvKiBTaGFkZSAqLwogICAgLS1sdW1vLXNoYWRlLTVwY3Q6IGhzbGEoMjE0LCAwJSwgMCUsIDAuMDcpOwogICAgLS1sdW1vLXNoYWRlLTEwcGN0OiBoc2xhKDIxNCwgNCUsIDIlLCAwLjE1KTsKICAgIC0tbHVtby1zaGFkZS0yMHBjdDogaHNsYSgyMTQsIDglLCA0JSwgMC4yMyk7CiAgICAtLWx1bW8tc2hhZGUtMzBwY3Q6IGhzbGEoMjE0LCAxMiUsIDYlLCAwLjMyKTsKICAgIC0tbHVtby1zaGFkZS00MHBjdDogaHNsYSgyMTQsIDE2JSwgOCUsIDAuNDEpOwogICAgLS1sdW1vLXNoYWRlLTUwcGN0OiBoc2xhKDIxNCwgMjAlLCAxMCUsIDAuNSk7CiAgICAtLWx1bW8tc2hhZGUtNjBwY3Q6IGhzbGEoMjE0LCAyNCUsIDEyJSwgMC42KTsKICAgIC0tbHVtby1zaGFkZS03MHBjdDogaHNsYSgyMTQsIDI4JSwgMTMlLCAwLjcpOwogICAgLS1sdW1vLXNoYWRlLTgwcGN0OiBoc2xhKDIxNCwgMzIlLCAxMyUsIDAuOCk7CiAgICAtLWx1bW8tc2hhZGUtOTBwY3Q6IGhzbGEoMjE0LCAzMyUsIDEzJSwgMC45KTsKICAgIC0tbHVtby1zaGFkZTogaHNsKDIxNCwgMzMlLCAxMyUpOwoKICAgIC8qIENvbnRyYXN0ICovCiAgICAtLWx1bW8tY29udHJhc3QtNXBjdDogdmFyKC0tbHVtby10aW50LTVwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTEwcGN0OiB2YXIoLS1sdW1vLXRpbnQtMTBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTIwcGN0OiB2YXIoLS1sdW1vLXRpbnQtMjBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTMwcGN0OiB2YXIoLS1sdW1vLXRpbnQtMzBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTQwcGN0OiB2YXIoLS1sdW1vLXRpbnQtNDBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTUwcGN0OiB2YXIoLS1sdW1vLXRpbnQtNTBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTYwcGN0OiB2YXIoLS1sdW1vLXRpbnQtNjBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTcwcGN0OiB2YXIoLS1sdW1vLXRpbnQtNzBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTgwcGN0OiB2YXIoLS1sdW1vLXRpbnQtODBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0LTkwcGN0OiB2YXIoLS1sdW1vLXRpbnQtOTBwY3QpOwogICAgLS1sdW1vLWNvbnRyYXN0OiB2YXIoLS1sdW1vLXRpbnQpOwoKICAgIC8qIFRleHQgKi8KICAgIC0tbHVtby1oZWFkZXItdGV4dC1jb2xvcjogdmFyKC0tbHVtby1jb250cmFzdCk7CiAgICAtLWx1bW8tYm9keS10ZXh0LWNvbG9yOiB2YXIoLS1sdW1vLWNvbnRyYXN0LTkwcGN0KTsKICAgIC0tbHVtby1zZWNvbmRhcnktdGV4dC1jb2xvcjogdmFyKC0tbHVtby1jb250cmFzdC03MHBjdCk7CiAgICAtLWx1bW8tdGVydGlhcnktdGV4dC1jb2xvcjogdmFyKC0tbHVtby1jb250cmFzdC01MHBjdCk7CiAgICAtLWx1bW8tZGlzYWJsZWQtdGV4dC1jb2xvcjogdmFyKC0tbHVtby1jb250cmFzdC0zMHBjdCk7CgogICAgLyogUHJpbWFyeSAqLwogICAgLS1sdW1vLXByaW1hcnktY29sb3I6IGhzbCgyMTQsIDg2JSwgNTUlKTsKICAgIC0tbHVtby1wcmltYXJ5LWNvbG9yLTUwcGN0OiBoc2xhKDIxNCwgODYlLCA1NSUsIDAuNSk7CiAgICAtLWx1bW8tcHJpbWFyeS1jb2xvci0xMHBjdDogaHNsYSgyMTQsIDkwJSwgNjMlLCAwLjEpOwogICAgLS1sdW1vLXByaW1hcnktdGV4dC1jb2xvcjogaHNsKDIxNCwgMTAwJSwgNzAlKTsKICAgIC0tbHVtby1wcmltYXJ5LWNvbnRyYXN0LWNvbG9yOiAjZmZmOwoKICAgIC8qIEVycm9yICovCiAgICAtLWx1bW8tZXJyb3ItY29sb3I6IGhzbCgzLCA5MCUsIDYzJSk7CiAgICAtLWx1bW8tZXJyb3ItY29sb3ItNTBwY3Q6IGhzbGEoMywgOTAlLCA2MyUsIDAuNSk7CiAgICAtLWx1bW8tZXJyb3ItY29sb3ItMTBwY3Q6IGhzbGEoMywgOTAlLCA2MyUsIDAuMSk7CiAgICAtLWx1bW8tZXJyb3ItdGV4dC1jb2xvcjogaHNsKDMsIDEwMCUsIDY3JSk7CgogICAgLyogU3VjY2VzcyAqLwogICAgLS1sdW1vLXN1Y2Nlc3MtY29sb3I6IGhzbCgxNDUsIDY1JSwgNDIlKTsKICAgIC0tbHVtby1zdWNjZXNzLWNvbG9yLTUwcGN0OiBoc2xhKDE0NSwgNjUlLCA0MiUsIDAuNSk7CiAgICAtLWx1bW8tc3VjY2Vzcy1jb2xvci0xMHBjdDogaHNsYSgxNDUsIDY1JSwgNDIlLCAwLjEpOwogICAgLS1sdW1vLXN1Y2Nlc3MtdGV4dC1jb2xvcjogaHNsKDE0NSwgODUlLCA0NyUpOwogIH0KCiAgaHRtbCB7CiAgICBjb2xvcjogdmFyKC0tbHVtby1ib2R5LXRleHQtY29sb3IpOwogICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tbHVtby1iYXNlLWNvbG9yKTsKICB9CgogIFt0aGVtZX49J2RhcmsnXSB7CiAgICBjb2xvcjogdmFyKC0tbHVtby1ib2R5LXRleHQtY29sb3IpOwogICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tbHVtby1iYXNlLWNvbG9yKTsKICB9CgogIGgxLAogIGgyLAogIGgzLAogIGg0LAogIGg1LAogIGg2IHsKICAgIGNvbG9yOiB2YXIoLS1sdW1vLWhlYWRlci10ZXh0LWNvbG9yKTsKICB9CgogIGEgewogICAgY29sb3I6IHZhcigtLWx1bW8tcHJpbWFyeS10ZXh0LWNvbG9yKTsKICB9CgogIGJsb2NrcXVvdGUgewogICAgY29sb3I6IHZhcigtLWx1bW8tc2Vjb25kYXJ5LXRleHQtY29sb3IpOwogIH0KCiAgY29kZSwKICBwcmUgewogICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tbHVtby1jb250cmFzdC0xMHBjdCk7CiAgICBib3JkZXItcmFkaXVzOiB2YXIoLS1sdW1vLWJvcmRlci1yYWRpdXMtbSk7CiAgfQpgO2pjKCIiLEZ1cix7bW9kdWxlSWQ6Imx1bW8tY29sb3IifSk7dmFyIEJ1cj1DaWAKICA6aG9zdCB7CiAgICBjb2xvcjogdmFyKC0tbHVtby1ib2R5LXRleHQtY29sb3IpICFpbXBvcnRhbnQ7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1sdW1vLWJhc2UtY29sb3IpICFpbXBvcnRhbnQ7CiAgfQpgO2pjKCIiLEJ1cix7bW9kdWxlSWQ6Imx1bW8tY29sb3ItbGVnYWN5IixpbmNsdWRlOlsibHVtby1jb2xvciJdfSk7dmFyIFBjZT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZW1wbGF0ZSIpO1BjZS5pbm5lckhUTUw9YAogIDxzdHlsZT4KICAgIEBmb250LWZhY2UgewogICAgICBmb250LWZhbWlseTogJ2x1bW8taWNvbnMnOwogICAgICBzcmM6IHVybChkYXRhOmFwcGxpY2F0aW9uL2ZvbnQtd29mZjtjaGFyc2V0PXV0Zi04O2Jhc2U2NCxkMDlHUmdBQkFBQUFBQkVjQUFzQUFBQUFJaXdBQVFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQkhVMVZDQUFBQkNBQUFBRHNBQUFCVUlJc2xlazlUTHpJQUFBRkVBQUFBUXdBQUFGWkFJVXVLWTIxaGNBQUFBWWdBQUFENEFBQURyc0NVOGQ1bmJIbG1BQUFDZ0FBQUMyTUFBQmQ0aDlUbzJXaGxZV1FBQUEza0FBQUFNQUFBQURaYS82U3NhR2hsWVFBQURoUUFBQUFkQUFBQUpBYnBBMzVvYlhSNEFBQU9OQUFBQUJBQUFBQ3NwQkFBQUd4dlkyRUFBQTVFQUFBQVdBQUFBRmg1NUlBc2JXRjRjQUFBRHB3QUFBQWZBQUFBSUFGS0FYQnVZVzFsQUFBT3ZBQUFBVEVBQUFJdVVVSlpDSEJ2YzNRQUFBL3dBQUFCS3dBQUFlbG04U3pWZUp4allHUmdZT0JpTUdDd1kyQnljZk1KWWVETFNTekpZNUJpWUdHQUFKQThNcHN4SnpNOWtZRUR4Z1BLc1lCcERpQm1nNGdDQUNZN0JVZ0FlSnhqWUdTK3lEaUJnWldCZ2FtS2FROERBME1QaEdaOHdHREl5QVFVWldCbFpzQUtBdEpjVXhnY1hqRyswbUlPK3AvRkVNVWN4REFOS013SWtnTUFCbjhNTFFCNG5PM1NXVzZETUFCRjBVdHdDRW5JUE0vemhMSzhMcWhmWFJ5YlNQMTRYVVl0SFY5aEdZUXdRQk5JbzNjVUlQa2hRZU03cmliMWVrcW5YZzk4MVh1QzFxdnk4NGx6b2psZWgzcHV4TDBoUGpHalJVNDczdGVsb0VlZkFVTkdqSmt3WmNhY0JVdFdyTm13WmNlZUEwZE9uTGx3NWNhZEIwOWVsUEdoR2YrajBOVEkvNjVLZlhlclQ2SmhxS25wUkt0Z09wdXFhVHJ0S2pQVWxxSG1odG8yMUk3cEw2aTZobHFZM3E3cUdXcmZVQWVHT2pUVWthR09EWFZpcUZORG5SbnEzRkFYaHJvMDFKV2hyZzExWTZoYlE5MFo2dDVRRDRaNk5OU1RvWjROOVdLb1YwTzlHZXJkVUIrRytqVFVsNkdXUnZrTDI0QmtFWGljdFZoOWJGdlZGYi9ueHZieis3UmYvTjZ6SGNkMmJDZlArV2djMVo5TjBqcE5uRUw2a2JSVlM2SEEyaFFZR2g5VEdSMUNiQ3FhMnJYcldPa1FFL3NITkpnbXRadm9WTlpxRTFCMUROSHh6VFF4Q2VoVVRZaUpUUXlFTnVpMHFTTGV6cjNQZHV5UWZnbVJXT2ZkZTgrOTU1MXo3cm5uL080akxvSi9iUlAwVWFLUU1MRkpqcEJBdnBoTFpDM0RrMG9rN1dCelIyL3VwSnM3Unl3L25mRmJsbi91dU4vYXBDdndyS0xyU3ZVcVJ1ZmJtNXBuMGZzMHc0Z1l4bkdWUDZxSG5PNGJXaURRR1Fnd3RTNmxtM2xCM1FvWDFNMnZ3RW11emlyRjM5eStFczIrREo4ZDFwa3lxQklxb3plM0QxK1p6NERyRm9henhJOGRXd01yRGxaMkRNcVFBUjlBUk9zSlUrMmNtbFRQYXpUY281MkYxeFRhMmEyK0s4dnZxOTJkVkhtdExvUGVRWC9BWlBSWUd0aERZT2VaakJqS29Gc1ZHdWxSM2xXVTk1V2VDSzQ0cUhVN01oV1VHVUtaRFQzb0tVY0cyR1d1aCtFRERmVVlBL2poQWhsMFRPc0pOWVNFdTdtUW1pM1V6Zlh3WktBNEJzVnNITFhRWUdnUlc5NXVFdHBKMVZmbjlYaUxyaVJCbEZFcXhzRGpBMDl5Q05Vb1F4eHdkN0tXU1R0MnkzR1RLaWZscUhSU29XWmMzbTExV2EvZkpkRmdYRDRzU1lmbGVKQktkOEdNejdKOGRabi9jR1JDY0tHRG5BMkdlM2ZLemN2bG5URE50aEdXTFh6WC9XYVh0VUFtUmdlTGxIU3IzMHIwRzlVVFhNYjBBdG13ek9veTczZmtTbEhaa2R1dy9UWXVVOWNBRDRZdXRQb3hUVHNBMzc5N3dWcjRaLzFOQzV6QVJIcjR2dHhKanhJZmlaTWhNa2JXaysxNEJuSlpLd3FHWndEZnN3THl4V0RTZzExckZMSkY3Tm9weGpkMWgxL1FPVCtvZXpnZnUzWXErSGsrZHVmNXgrNDBvMUdUa2FJZ2lrSy9JRW5DNmFZeENVQmFaSlNONFhUWUZqVS9ZTU5JS3FKd2hER09DQ0k4RkRYblhtWGp0R2hHSnlTaHFqQU9uQk9rVzJKRzlTN0dnWWVNV0FVNUp6aG5XbUJPYU9NK0NLRVBvcVNmRkRDMlVucStETGxVZ1VWVUZGTFpHSmc2anRsb2pzZHNhOGtQT2JQdUpkaTVkbkJkQnNMSk1HVFdEYTR0Mkp2dHd1UG85cytZODZzdXYvVzMzUUcxckFhT0FVVit2eDRLNmYyRDA0UFZLbEM3V0xTclp6QWk0NVpWNmxJQzdXb1hxbVJ5dlVxb1Z3cnpVb1ZzSWplVFhXUXYrUkg1R1RsQlhpQi9JbjhsbjBJYkJDQUZPYWpBSnJnWll5T0hXcU9mVWUvYUhqSTEyUjZPUW8xakNndDIxNWwrNGY2WFBiKzBNTm91MFYrNDNuMkY3N3RTZlJiMjRkN3ppdGduS212WUhzNjl6dWdhUHZCd3Y2aW9Ya2IyTGRMNjVBdHc1MXVMa1hsdTFiaE1NUmNYU1BjWW9xS0lSbGgzNGxRUDgvNUpidVVGeWU0dnhENi82TXhGRjExQzB1VkxyOVVsZ3c0NHRTM3BNVmlOTFVFeGJ5Y0ZnTEljdCtRRE1pYlJpbXgxeWRVejhGWFppdU9JREJPTVZYMm5VWmMraHVORTVYVUo4MXVpSm9pYWJ3cWFWRjB1YWNLYmF1L3BsNFIyVlcwWFhsSnJhNmJvVnJZRzY0NlRGNU5Zend5NHZqRU5WckRsY05wWlBsOERINlhYOFhXQ3gwbXZXVlpZNktGTHJ2c1k2Ni96UGljdDVGbnhhTlVSL2p1dlpDTTNUdkQ2MEUyVzF0Wml6YlhUUER1YWJjbTBuYmJ6cFdLcG1BMWF5QlE4Z2llZExVTStBMGtOakJqUWptdVl6N1lyZ0lYWXZtRjYzWkxCd1NYcnBuOVRiOXd3ZGQvVTFIMFBNUUszWGNPOHVsM1dUN1B5UFBkcHkwVGVtS3hOUmNKTmF1aVhKbm5VRHBVcHBRV3M0U25VSXkwRUVTR1lxSllRTEdIeHphR1d3VklhUzZZN21RRk04WmpZRFEzYXhqZjYxU1dqVTMzSndPWkExcHdhRzFMOW16ZjcxYUhSZFgxSkh3NkZwMGFYaE53YnF5ZUdOZzROYmR6R0NCeG96NFpYank0TnU2OVpyNnNEWTZ2TXJMVTVuQTFQOEprYmRXWEo2RVJmTXJ5dk5oMUpmUTkrVDRkSWhHdks5dzNkeGpCQnphdHNRL01sT0hWSURuWXBEejZvZEFYbFEwMXQyUGE1SWFmZDhNTXB4QWVES1AwQzZDamdWTFQ1b3NCNmljVXgwMWxXalh4elQvR3lSRjJ3ZWxFTTVaLzdqRzNWalExU3JObjVJYnl6T0c1ZG9iQjMvUUh4eVp2c1hjb3o4SW9Fd1M3cGxDZyt6eEhRazQyNHE5QmZFcGtFU0piRkhRdXNEQlNXRmt1QmtvUE8wa0xLd1JWWWp4R1hsSFRjVERRTUovSDZUWDlhZmtPN21ucmFUTzFmZVRuWkFYTHU0Y3A3SEFYTW1ORzF5ZUZrOVRnUy9OSGhaUi80UW9CVHIvWkIrNmhDZ3lsMTVOcTFVYk42bkUxL1puUDFVMmNpekNCcHZzOGNKUVpKNExrWXg1Ti95WlBBVVpOUVEwVjRmM0JRbGxXckszWVJ6bDMwZE9UNlJWbjJ1cE51cjZ3b1NhOENxcGRUL2FLbkJNNG8zak51cjlkOXhxdFVUNnZlQkV0OUNhOWF0K0VSekVFaFVrUjhzYTVtUTRhVnZKb1ZlRUE4ekk0ZWk1bVVMWEZHeVU3ei82VEFlWUxWY3B6U1daWThQWVlGNXlyVFY2MHNUMCtYVjE0MXZYKytXZjE2VjJiRmVHVlBaWHhGcGt2eWVLVFdMbHpmVzBtbkt4c1k2WTMyOTQvMDk5OFNDZlgxYmxtNXBiY3ZGR2xxL3IwN01SQU1oWUlEaVc1SkZLV1czdmRyRXBDc1pTSkcrb203WnUvUFNTY1pKaE5rTGJtVzVXc3IxMnBXcVc1ekt0bHdSUzRiRk94VXcxN21Denk2bHNrQ0RsMVdZT0dXRFlyQURyTUE3QkREd2VXV05kNWtvaUpuUjFkeit5dExQMnEwU3FQQjFsbksyY2NCN1JZZTRGU29Qa3MzaUIzdDR0eFRTSGN0YjJzeTFpdmswcHZIdUNObTZ3MWY2d3h2MytPQ2dONzhMcWRRblVWaDdSMG9UQXAwek9mMnJiVzc3MFZ1NUMyZEl5R2RUbkhvOHpTamk3ZHBwajBVU29WQ3orbGhSTVRoNTNUZXE5VmJHZmJqdVNiQW9vU2RYYXlZNFBZSGczNzRDNmY3Z2wxQi9EWHVKNC9RWHhPQmRKRkpzcEZzSTNlZ3BvV1VVQ2psVElGbk5ZTmwrWnlaS21CZVlLR0hrRDFReURsaGFLYkt3S2NJSnFKNFRMSjJPbWRZL0pXWGFlNERkR0J3OEhaN2VYY2dGRjJ6cjJTb2FsRHJ5NWlLcW9hMFB1aGUzaFBRMnMzZWxUWU0rTUkrbjNySzBLZ0w3L0xhM0dlTUx0Nm03dTkxMnZHbnZ0T1JpSWEwcUJtaHFWaStYVzlYTkJtcWI4ZVZnS3pJSGZHSTViTm9HN1gwVUN6ZUlTbXFJY08vblk4Rkg3VThhdlg5ZngvU1QraHgwc2V6UHc5UXk4TXVtM0dXZjJONFV5L3lJWUdWQlhiSkhXSVpwN2RmVGNwdGRNVHI5UW1xN0RhaUsvdWtxQ0w0a3Q0UlVmUzVYUG5NdG1UMjIvbVFGcUY3ZW1TcXRybHU4U1ZFbHhEUkpyWk9Ea3B1d2UwVmZUZmpkRXAxZjdBN3YrZm96TkJYVUovNldUdUsyVHRGbHBGVlpBWjNMY0Z2VWkxWjJwMllUK0VNQWtHSlZTdE96TFRBUGc0SXFXSUFselJTak9Ca2wyenhqM1RLeWNwelQvTW52WDN1YVNNV00rZ1UwcmtYam9oaGVmVlJNYXBzMy9rTE1TS3YyM2xUMjN1eFFya1FqeU9KbGVNRHNkaEFuRDZaR0VsV1o1TWpDWHpDRS9oa1dYK1dGNGtuekdoVk95SzJlUVpla1YzZXlvMHpMOGt1WVdDbkRDdmpqaEFrY1RQT0JEWFZkb2F2M0hWY0ZuUWpMdnRWOVMycDB6QTZKZWdQd01ReHQreUZiM2xsOXpHbHEvNWRSS2IzY0V5UVlvYU5ZcGhhcko3eENCN0FXeHNMWTNqalpYWTBYc1pqMFdqd2M5STZQUC9kS0FCbkNaYXFIcGFaRUFDeGs0WmVMWlNLTmdaQUJsK2xZUVgxc0pRT1NYM242cjQxMGV2Y291ZDVKZUFHVVhWUDlIMXRaT0tlalRxNE9ubzB6MGVycm8xRnJuT3BvaHZhMWQvaFRkdFZzUWRLTjVXOVJsVDNOakQwbnpueUtOVGdLQU1mV05XY3lvZFYwSUdMUElIT0YwbzRKeXF1ZmFLNHo2V0lJenVHaDNkOGM4Y3dRZzhFUitPVnh5cmpkbTh2TnVodHM0TG9PaWhHeElNdVVkZ3p3aVlON3hoaDErb1puSk51VEc3Z1FadnU0WFdaOUdBWlpqR0V1YndlUHFZaHRLRFRIKzlWUWtsMTcvaUd5YnNuSis4K3NLdHlQcmNsbDl0eTY1WnNkc3QvOWlxcEVLaDdNNVZkQnhoM2NzT2ROYzZ0VzNJMXV5TTFQek9YZWdTT3JMRnNGTkkyTzI3TStURjJBcG5OOU1VdjV1ZDZManhJdkVRbkhSenhJdTRJc0E5TUxGa0puMnRjWm9aN09ON2RYZTd1anJjOEhydXNQS2FtbHFYd2Q3N2xRVXVMcGlsYXU0UFVNYXB1ZUJiN2lyVTRSb1VYRVlYdVZ1SUdsUkdtT3ArMmxOa2FSUFZ6aU9xbWxhWnZhcUc0ZEZnU2owanhFSldydjEySVVXbnRtdytyZlFhclJFMEFwaDRvY0k2bmxVbEdxcyt1My8rVC9ldGhXNjJQcEhwMmVIYlpzdG5oL3dPTzk1eURBSGljWTJCa1lHQUE0cG1KNlFIeC9EWmZHYmlaWHdCRkdHcFVOelFpNlArdm1hY3kzUUp5T1JpWVFLSUFOb1VMVlhpY1kyQmtZR0FPK3A4RkpGOHdBQUh6VkFaR0JsU2dEUUJXOWdOdkFBQUFlSnhqWUdCZ1lINHhOREFBendRbWp3QUFBQUFBVGdDYUFPZ0JDZ0VzQVU0QmNBR2FBY1FCN2dJYUFwd0M2QVNhQkx3RTFnVHlCUTRGS2dWNkJkQUYvZ1pFQm1ZR3RnY1lCNUFJR0FoU0NHb0kvZ2xHQ2I0SjJnb0VDandLZ2dxNEN2QUxVQXVXQzd4NG5HTmdaR0JnMEdaTVlSQmxBQUVtSU9ZQ1FnYUcvMkErQXdBWWxBRzhBSGljYlpFOVRzTXdHSWJmOUEvUlNnZ0VZbUh4QWd0cStqTjJaR2ozRHQzVDFHbFRPWEhrdUJXOUF5ZmdFQnlDZ1ROd0NBN0JXL05KbFZCdHlkL2p4KzhYS3dtQWEzd2h3bkZFNkliMU9CcTQ0TzZQbTZRYjRSYjVRYmlOSGg2Rk8vUkQ0UzZlTVJIdTRSYWFUNGhhbHpSM2VCVnU0QXB2d2szNmQrRVcrVU80alh0OENuZm92NFc3V09CSHVJZW42TVhzQ3R2UFUxdldjNzNlbWNTZHhJa1cydFc1TGRVb0hwN2tUSmZhSlY2djFQS2c2djE2N0gybU1tY0xOYldsMThaWVZUbTcxYW1QTjk1WGs4RWdFeCtudG9EQkRnVXMrc2lSc3Bhb01lZjdydWtORXJpemlYTnV3UzdIbW9lOXdnZ3h2K2U1NUl6Sk1xUVRlTllWMDBzY3VOYlk4K1l4clVmR2ZjYU1aYi9DTlBRZTA0YlQwbFRoYkV1VDBzZlloSzZLLzIzQW1mM0x4K0gyNGhjajRHU2NBQUFBZUp4dGp0bHVnekFRUmJrSlVFSkl1dS83dnFSOGxHTlBBY1d4MFlBYjVlL0xrbFI5NkVnZW5TdWZHWTAzOFBxS3ZmOXJoZ0dHOEJFZ3hBNGlqQkJqakFRVFRMR0xQZXpqQUljNHdqRk9jSW96bk9NQ2w3akNOVzV3aXp2YzR3R1BlTUl6WHZDS043empBek44ZW9uUVJXWlNTYVltanZ1ZzZhc2U5OGhGbHRleE1KbW1WTm1WMldCdmROZ1pVYyt1akFXelhXM1VEbnUxdzQzYXNTdEhjOEdwekFYWC9weTBqcVRRWkpUZ2tjeEpMcGFDRjBsRDMyeE50KzQzdEFzbjI5RGZ0MDJ1REtTMmNqR1VOZ3NrMjZxSzJsRnRoWW9VMjdJTlBxbWlEcWc1Z29lMHBxUjVxU29xTWRlay9DVVpGeXdMNDZyRXNpSW1sZXFpcW9NeXQ0YmFYbHUvMUdMZE5GZjV6YmNObWRyMVlVV0NaZTQ3byt6VW1iL0RvU3RidzNjVnNlZjlBTGpqaVBRQSkgZm9ybWF0KCd3b2ZmJyk7CiAgICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7CiAgICAgIGZvbnQtc3R5bGU6IG5vcm1hbDsKICAgIH0KCiAgICBodG1sIHsKICAgICAgLS1sdW1vLWljb25zLWFsaWduLWNlbnRlcjogIlxcZWEwMSI7CiAgICAgIC0tbHVtby1pY29ucy1hbGlnbi1sZWZ0OiAiXFxlYTAyIjsKICAgICAgLS1sdW1vLWljb25zLWFsaWduLXJpZ2h0OiAiXFxlYTAzIjsKICAgICAgLS1sdW1vLWljb25zLWFuZ2xlLWRvd246ICJcXGVhMDQiOwogICAgICAtLWx1bW8taWNvbnMtYW5nbGUtbGVmdDogIlxcZWEwNSI7CiAgICAgIC0tbHVtby1pY29ucy1hbmdsZS1yaWdodDogIlxcZWEwNiI7CiAgICAgIC0tbHVtby1pY29ucy1hbmdsZS11cDogIlxcZWEwNyI7CiAgICAgIC0tbHVtby1pY29ucy1hcnJvdy1kb3duOiAiXFxlYTA4IjsKICAgICAgLS1sdW1vLWljb25zLWFycm93LWxlZnQ6ICJcXGVhMDkiOwogICAgICAtLWx1bW8taWNvbnMtYXJyb3ctcmlnaHQ6ICJcXGVhMGEiOwogICAgICAtLWx1bW8taWNvbnMtYXJyb3ctdXA6ICJcXGVhMGIiOwogICAgICAtLWx1bW8taWNvbnMtYmFyLWNoYXJ0OiAiXFxlYTBjIjsKICAgICAgLS1sdW1vLWljb25zLWJlbGw6ICJcXGVhMGQiOwogICAgICAtLWx1bW8taWNvbnMtY2FsZW5kYXI6ICJcXGVhMGUiOwogICAgICAtLWx1bW8taWNvbnMtY2hlY2ttYXJrOiAiXFxlYTBmIjsKICAgICAgLS1sdW1vLWljb25zLWNoZXZyb24tZG93bjogIlxcZWExMCI7CiAgICAgIC0tbHVtby1pY29ucy1jaGV2cm9uLWxlZnQ6ICJcXGVhMTEiOwogICAgICAtLWx1bW8taWNvbnMtY2hldnJvbi1yaWdodDogIlxcZWExMiI7CiAgICAgIC0tbHVtby1pY29ucy1jaGV2cm9uLXVwOiAiXFxlYTEzIjsKICAgICAgLS1sdW1vLWljb25zLWNsb2NrOiAiXFxlYTE0IjsKICAgICAgLS1sdW1vLWljb25zLWNvZzogIlxcZWExNSI7CiAgICAgIC0tbHVtby1pY29ucy1jcm9zczogIlxcZWExNiI7CiAgICAgIC0tbHVtby1pY29ucy1kb3dubG9hZDogIlxcZWExNyI7CiAgICAgIC0tbHVtby1pY29ucy1kcm9wZG93bjogIlxcZWExOCI7CiAgICAgIC0tbHVtby1pY29ucy1lZGl0OiAiXFxlYTE5IjsKICAgICAgLS1sdW1vLWljb25zLWVycm9yOiAiXFxlYTFhIjsKICAgICAgLS1sdW1vLWljb25zLWV5ZTogIlxcZWExYiI7CiAgICAgIC0tbHVtby1pY29ucy1leWUtZGlzYWJsZWQ6ICJcXGVhMWMiOwogICAgICAtLWx1bW8taWNvbnMtbWVudTogIlxcZWExZCI7CiAgICAgIC0tbHVtby1pY29ucy1taW51czogIlxcZWExZSI7CiAgICAgIC0tbHVtby1pY29ucy1vcmRlcmVkLWxpc3Q6ICJcXGVhMWYiOwogICAgICAtLWx1bW8taWNvbnMtcGhvbmU6ICJcXGVhMjAiOwogICAgICAtLWx1bW8taWNvbnMtcGhvdG86ICJcXGVhMjEiOwogICAgICAtLWx1bW8taWNvbnMtcGxheTogIlxcZWEyMiI7CiAgICAgIC0tbHVtby1pY29ucy1wbHVzOiAiXFxlYTIzIjsKICAgICAgLS1sdW1vLWljb25zLXJlZG86ICJcXGVhMjQiOwogICAgICAtLWx1bW8taWNvbnMtcmVsb2FkOiAiXFxlYTI1IjsKICAgICAgLS1sdW1vLWljb25zLXNlYXJjaDogIlxcZWEyNiI7CiAgICAgIC0tbHVtby1pY29ucy11bmRvOiAiXFxlYTI3IjsKICAgICAgLS1sdW1vLWljb25zLXVub3JkZXJlZC1saXN0OiAiXFxlYTI4IjsKICAgICAgLS1sdW1vLWljb25zLXVwbG9hZDogIlxcZWEyOSI7CiAgICAgIC0tbHVtby1pY29ucy11c2VyOiAiXFxlYTJhIjsKICAgIH0KICA8L3N0eWxlPgpgO2RvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoUGNlLmNvbnRlbnQpO3ZhciBIdXI9Q2lgCiAgOmhvc3QgewogICAgLS1sdW1vLXNpemUteHM6IDEuNjI1cmVtOwogICAgLS1sdW1vLXNpemUtczogMS44NzVyZW07CiAgICAtLWx1bW8tc2l6ZS1tOiAyLjI1cmVtOwogICAgLS1sdW1vLXNpemUtbDogMi43NXJlbTsKICAgIC0tbHVtby1zaXplLXhsOiAzLjVyZW07CgogICAgLyogSWNvbnMgKi8KICAgIC0tbHVtby1pY29uLXNpemUtczogMS4yNWVtOwogICAgLS1sdW1vLWljb24tc2l6ZS1tOiAxLjVlbTsKICAgIC0tbHVtby1pY29uLXNpemUtbDogMi4yNWVtOwogICAgLyogRm9yIGJhY2t3YXJkcyBjb21wYXRpYmlsaXR5ICovCiAgICAtLWx1bW8taWNvbi1zaXplOiB2YXIoLS1sdW1vLWljb24tc2l6ZS1tKTsKICB9CmAsSWNlPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInRlbXBsYXRlIik7SWNlLmlubmVySFRNTD1gPHN0eWxlPiR7SHVyLnRvU3RyaW5nKCkucmVwbGFjZSgiOmhvc3QiLCJodG1sIil9PC9zdHlsZT5gO2RvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoSWNlLmNvbnRlbnQpO3ZhciBWdXI9Q2lgCiAgOmhvc3QgewogICAgLyogU3F1YXJlICovCiAgICAtLWx1bW8tc3BhY2UteHM6IDAuMjVyZW07CiAgICAtLWx1bW8tc3BhY2UtczogMC41cmVtOwogICAgLS1sdW1vLXNwYWNlLW06IDFyZW07CiAgICAtLWx1bW8tc3BhY2UtbDogMS41cmVtOwogICAgLS1sdW1vLXNwYWNlLXhsOiAyLjVyZW07CgogICAgLyogV2lkZSAqLwogICAgLS1sdW1vLXNwYWNlLXdpZGUteHM6IGNhbGModmFyKC0tbHVtby1zcGFjZS14cykgLyAyKSB2YXIoLS1sdW1vLXNwYWNlLXhzKTsKICAgIC0tbHVtby1zcGFjZS13aWRlLXM6IGNhbGModmFyKC0tbHVtby1zcGFjZS1zKSAvIDIpIHZhcigtLWx1bW8tc3BhY2Utcyk7CiAgICAtLWx1bW8tc3BhY2Utd2lkZS1tOiBjYWxjKHZhcigtLWx1bW8tc3BhY2UtbSkgLyAyKSB2YXIoLS1sdW1vLXNwYWNlLW0pOwogICAgLS1sdW1vLXNwYWNlLXdpZGUtbDogY2FsYyh2YXIoLS1sdW1vLXNwYWNlLWwpIC8gMikgdmFyKC0tbHVtby1zcGFjZS1sKTsKICAgIC0tbHVtby1zcGFjZS13aWRlLXhsOiBjYWxjKHZhcigtLWx1bW8tc3BhY2UteGwpIC8gMikgdmFyKC0tbHVtby1zcGFjZS14bCk7CgogICAgLyogVGFsbCAqLwogICAgLS1sdW1vLXNwYWNlLXRhbGwteHM6IHZhcigtLWx1bW8tc3BhY2UteHMpIGNhbGModmFyKC0tbHVtby1zcGFjZS14cykgLyAyKTsKICAgIC0tbHVtby1zcGFjZS10YWxsLXM6IHZhcigtLWx1bW8tc3BhY2UtcykgY2FsYyh2YXIoLS1sdW1vLXNwYWNlLXMpIC8gMik7CiAgICAtLWx1bW8tc3BhY2UtdGFsbC1tOiB2YXIoLS1sdW1vLXNwYWNlLW0pIGNhbGModmFyKC0tbHVtby1zcGFjZS1tKSAvIDIpOwogICAgLS1sdW1vLXNwYWNlLXRhbGwtbDogdmFyKC0tbHVtby1zcGFjZS1sKSBjYWxjKHZhcigtLWx1bW8tc3BhY2UtbCkgLyAyKTsKICAgIC0tbHVtby1zcGFjZS10YWxsLXhsOiB2YXIoLS1sdW1vLXNwYWNlLXhsKSBjYWxjKHZhcigtLWx1bW8tc3BhY2UteGwpIC8gMik7CiAgfQpgLExjZT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZW1wbGF0ZSIpO0xjZS5pbm5lckhUTUw9YDxzdHlsZT4ke1Z1ci50b1N0cmluZygpLnJlcGxhY2UoIjpob3N0IiwiaHRtbCIpfTwvc3R5bGU+YDtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKExjZS5jb250ZW50KTt2YXIgVXVyPUNpYAogIDpob3N0IHsKICAgIC8qIEJvcmRlciByYWRpdXMgKi8KICAgIC0tbHVtby1ib3JkZXItcmFkaXVzLXM6IDAuMjVlbTsgLyogQ2hlY2tib3gsIGJhZGdlLCBkYXRlLXBpY2tlciB5ZWFyIGluZGljYXRvciwgZXRjICovCiAgICAtLWx1bW8tYm9yZGVyLXJhZGl1cy1tOiB2YXIoLS1sdW1vLWJvcmRlci1yYWRpdXMsIDAuMjVlbSk7IC8qIEJ1dHRvbiwgdGV4dCBmaWVsZCwgbWVudSBvdmVybGF5LCBldGMgKi8KICAgIC0tbHVtby1ib3JkZXItcmFkaXVzLWw6IDAuNWVtOyAvKiBEaWFsb2csIG5vdGlmaWNhdGlvbiwgZXRjICovCiAgICAtLWx1bW8tYm9yZGVyLXJhZGl1czogMC4yNWVtOyAvKiBEZXByZWNhdGVkICovCgogICAgLyogU2hhZG93ICovCiAgICAtLWx1bW8tYm94LXNoYWRvdy14czogMCAxcHggNHB4IC0xcHggdmFyKC0tbHVtby1zaGFkZS01MHBjdCk7CiAgICAtLWx1bW8tYm94LXNoYWRvdy1zOiAwIDJweCA0cHggLTFweCB2YXIoLS1sdW1vLXNoYWRlLTIwcGN0KSwgMCAzcHggMTJweCAtMXB4IHZhcigtLWx1bW8tc2hhZGUtMzBwY3QpOwogICAgLS1sdW1vLWJveC1zaGFkb3ctbTogMCAycHggNnB4IC0xcHggdmFyKC0tbHVtby1zaGFkZS0yMHBjdCksIDAgOHB4IDI0cHggLTRweCB2YXIoLS1sdW1vLXNoYWRlLTQwcGN0KTsKICAgIC0tbHVtby1ib3gtc2hhZG93LWw6IDAgM3B4IDE4cHggLTJweCB2YXIoLS1sdW1vLXNoYWRlLTIwcGN0KSwgMCAxMnB4IDQ4cHggLTZweCB2YXIoLS1sdW1vLXNoYWRlLTQwcGN0KTsKICAgIC0tbHVtby1ib3gtc2hhZG93LXhsOiAwIDRweCAyNHB4IC0zcHggdmFyKC0tbHVtby1zaGFkZS0yMHBjdCksIDAgMThweCA2NHB4IC04cHggdmFyKC0tbHVtby1zaGFkZS00MHBjdCk7CgogICAgLyogQ2xpY2thYmxlIGVsZW1lbnQgY3Vyc29yICovCiAgICAtLWx1bW8tY2xpY2thYmxlLWN1cnNvcjogZGVmYXVsdDsKICB9CmAsa2NlPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInRlbXBsYXRlIik7a2NlLmlubmVySFRNTD1gPHN0eWxlPiR7VXVyLnRvU3RyaW5nKCkucmVwbGFjZSgiOmhvc3QiLCJodG1sIil9PC9zdHlsZT5gO2RvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoa2NlLmNvbnRlbnQpO3ZhciBxdXI9Q2lgCiAgOmhvc3QgewogICAgLyogcHJldHRpZXItaWdub3JlICovCiAgICAtLWx1bW8tZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJ1JvYm90bycsICdTZWdvZSBVSScsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWYsICdBcHBsZSBDb2xvciBFbW9qaScsICdTZWdvZSBVSSBFbW9qaScsICdTZWdvZSBVSSBTeW1ib2wnOwoKICAgIC8qIEZvbnQgc2l6ZXMgKi8KICAgIC0tbHVtby1mb250LXNpemUteHhzOiAwLjc1cmVtOwogICAgLS1sdW1vLWZvbnQtc2l6ZS14czogMC44MTI1cmVtOwogICAgLS1sdW1vLWZvbnQtc2l6ZS1zOiAwLjg3NXJlbTsKICAgIC0tbHVtby1mb250LXNpemUtbTogMXJlbTsKICAgIC0tbHVtby1mb250LXNpemUtbDogMS4xMjVyZW07CiAgICAtLWx1bW8tZm9udC1zaXplLXhsOiAxLjM3NXJlbTsKICAgIC0tbHVtby1mb250LXNpemUteHhsOiAxLjc1cmVtOwogICAgLS1sdW1vLWZvbnQtc2l6ZS14eHhsOiAyLjVyZW07CgogICAgLyogTGluZSBoZWlnaHRzICovCiAgICAtLWx1bW8tbGluZS1oZWlnaHQteHM6IDEuMjU7CiAgICAtLWx1bW8tbGluZS1oZWlnaHQtczogMS4zNzU7CiAgICAtLWx1bW8tbGluZS1oZWlnaHQtbTogMS42MjU7CiAgfQpgLFJjZT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZW1wbGF0ZSIpO1JjZS5pbm5lckhUTUw9YDxzdHlsZT4ke3F1ci50b1N0cmluZygpLnJlcGxhY2UoIjpob3N0IiwiaHRtbCIpfTwvc3R5bGU+YDtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKFJjZS5jb250ZW50KTt2YXIgR3VyPUNpYAogIGh0bWwgewogICAgZm9udC1mYW1pbHk6IHZhcigtLWx1bW8tZm9udC1mYW1pbHkpOwogICAgZm9udC1zaXplOiB2YXIoLS1sdW1vLWZvbnQtc2l6ZSwgdmFyKC0tbHVtby1mb250LXNpemUtbSkpOwogICAgbGluZS1oZWlnaHQ6IHZhcigtLWx1bW8tbGluZS1oZWlnaHQtbSk7CiAgICAtd2Via2l0LXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7CiAgICAtd2Via2l0LWZvbnQtc21vb3RoaW5nOiBhbnRpYWxpYXNlZDsKICAgIC1tb3otb3N4LWZvbnQtc21vb3RoaW5nOiBncmF5c2NhbGU7CiAgfQoKICAvKiBDYW7igJl0IGNvbWJpbmUgd2l0aCB0aGUgYWJvdmUgc2VsZWN0b3IgYmVjYXVzZSB0aGF0IGRvZXNu4oCZdCB3b3JrIGluIGJyb3dzZXJzIHdpdGhvdXQgbmF0aXZlIHNoYWRvdyBkb20gKi8KICA6aG9zdCB7CiAgICBmb250LWZhbWlseTogdmFyKC0tbHVtby1mb250LWZhbWlseSk7CiAgICBmb250LXNpemU6IHZhcigtLWx1bW8tZm9udC1zaXplLCB2YXIoLS1sdW1vLWZvbnQtc2l6ZS1tKSk7CiAgICBsaW5lLWhlaWdodDogdmFyKC0tbHVtby1saW5lLWhlaWdodC1tKTsKICAgIC13ZWJraXQtdGV4dC1zaXplLWFkanVzdDogMTAwJTsKICAgIC13ZWJraXQtZm9udC1zbW9vdGhpbmc6IGFudGlhbGlhc2VkOwogICAgLW1vei1vc3gtZm9udC1zbW9vdGhpbmc6IGdyYXlzY2FsZTsKICB9CgogIHNtYWxsLAogIFt0aGVtZX49J2ZvbnQtc2l6ZS1zJ10gewogICAgZm9udC1zaXplOiB2YXIoLS1sdW1vLWZvbnQtc2l6ZS1zKTsKICAgIGxpbmUtaGVpZ2h0OiB2YXIoLS1sdW1vLWxpbmUtaGVpZ2h0LXMpOwogIH0KCiAgW3RoZW1lfj0nZm9udC1zaXplLXhzJ10gewogICAgZm9udC1zaXplOiB2YXIoLS1sdW1vLWZvbnQtc2l6ZS14cyk7CiAgICBsaW5lLWhlaWdodDogdmFyKC0tbHVtby1saW5lLWhlaWdodC14cyk7CiAgfQoKICBoMSwKICBoMiwKICBoMywKICBoNCwKICBoNSwKICBoNiB7CiAgICBmb250LXdlaWdodDogNjAwOwogICAgbGluZS1oZWlnaHQ6IHZhcigtLWx1bW8tbGluZS1oZWlnaHQteHMpOwogICAgbWFyZ2luLXRvcDogMS4yNWVtOwogIH0KCiAgaDEgewogICAgZm9udC1zaXplOiB2YXIoLS1sdW1vLWZvbnQtc2l6ZS14eHhsKTsKICAgIG1hcmdpbi1ib3R0b206IDAuNzVlbTsKICB9CgogIGgyIHsKICAgIGZvbnQtc2l6ZTogdmFyKC0tbHVtby1mb250LXNpemUteHhsKTsKICAgIG1hcmdpbi1ib3R0b206IDAuNWVtOwogIH0KCiAgaDMgewogICAgZm9udC1zaXplOiB2YXIoLS1sdW1vLWZvbnQtc2l6ZS14bCk7CiAgICBtYXJnaW4tYm90dG9tOiAwLjVlbTsKICB9CgogIGg0IHsKICAgIGZvbnQtc2l6ZTogdmFyKC0tbHVtby1mb250LXNpemUtbCk7CiAgICBtYXJnaW4tYm90dG9tOiAwLjVlbTsKICB9CgogIGg1IHsKICAgIGZvbnQtc2l6ZTogdmFyKC0tbHVtby1mb250LXNpemUtbSk7CiAgICBtYXJnaW4tYm90dG9tOiAwLjI1ZW07CiAgfQoKICBoNiB7CiAgICBmb250LXNpemU6IHZhcigtLWx1bW8tZm9udC1zaXplLXhzKTsKICAgIG1hcmdpbi1ib3R0b206IDA7CiAgICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlOwogICAgbGV0dGVyLXNwYWNpbmc6IDAuMDNlbTsKICB9CgogIHAsCiAgYmxvY2txdW90ZSB7CiAgICBtYXJnaW4tdG9wOiAwLjVlbTsKICAgIG1hcmdpbi1ib3R0b206IDAuNzVlbTsKICB9CgogIGEgewogICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwogIH0KCiAgYTpob3ZlciB7CiAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKICB9CgogIGhyIHsKICAgIGRpc3BsYXk6IGJsb2NrOwogICAgYWxpZ24tc2VsZjogc3RyZXRjaDsKICAgIGhlaWdodDogMXB4OwogICAgYm9yZGVyOiAwOwogICAgcGFkZGluZzogMDsKICAgIG1hcmdpbjogdmFyKC0tbHVtby1zcGFjZS1zKSBjYWxjKHZhcigtLWx1bW8tYm9yZGVyLXJhZGl1cy1tKSAvIDIpOwogICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tbHVtby1jb250cmFzdC0xMHBjdCk7CiAgfQoKICBibG9ja3F1b3RlIHsKICAgIGJvcmRlci1sZWZ0OiAycHggc29saWQgdmFyKC0tbHVtby1jb250cmFzdC0zMHBjdCk7CiAgfQoKICBiLAogIHN0cm9uZyB7CiAgICBmb250LXdlaWdodDogNjAwOwogIH0KCiAgLyogUlRMIHNwZWNpZmljIHN0eWxlcyAqLwoKICBibG9ja3F1b3RlW2Rpcj0ncnRsJ10gewogICAgYm9yZGVyLWxlZnQ6IG5vbmU7CiAgICBib3JkZXItcmlnaHQ6IDJweCBzb2xpZCB2YXIoLS1sdW1vLWNvbnRyYXN0LTMwcGN0KTsKICB9CmA7amMoIiIsR3VyLHttb2R1bGVJZDoibHVtby10eXBvZ3JhcGh5In0pO2pjKCJ2YWFkaW4tY2hlY2tib3giLENpYAogICAgOmhvc3QgewogICAgICAtd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6IHRyYW5zcGFyZW50OwogICAgICAtd2Via2l0LXVzZXItc2VsZWN0OiBub25lOwogICAgICAtbW96LXVzZXItc2VsZWN0OiBub25lOwogICAgICB1c2VyLXNlbGVjdDogbm9uZTsKICAgICAgY3Vyc29yOiBkZWZhdWx0OwogICAgICBvdXRsaW5lOiBub25lOwogICAgfQoKICAgIFtwYXJ0PSdsYWJlbCddOm5vdChbZW1wdHldKSB7CiAgICAgIG1hcmdpbjogMC4xODc1ZW0gMC44NzVlbSAwLjE4NzVlbSAwLjM3NWVtOwogICAgfQoKICAgIFtwYXJ0PSdjaGVja2JveCddIHsKICAgICAgd2lkdGg6IGNhbGMoMWVtICsgMnB4KTsKICAgICAgaGVpZ2h0OiBjYWxjKDFlbSArIDJweCk7CiAgICAgIG1hcmdpbjogMC4xODc1ZW07CiAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgYm9yZGVyLXJhZGl1czogdmFyKC0tbHVtby1ib3JkZXItcmFkaXVzLXMpOwogICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1sdW1vLWNvbnRyYXN0LTIwcGN0KTsKICAgICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDAuMnMgY3ViaWMtYmV6aWVyKDAuMTIsIDAuMzIsIDAuNTQsIDIpLCBiYWNrZ3JvdW5kLWNvbG9yIDAuMTVzOwogICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgbGluZS1oZWlnaHQ6IDEuMjsKICAgIH0KCiAgICA6aG9zdChbaW5kZXRlcm1pbmF0ZV0pIFtwYXJ0PSdjaGVja2JveCddLAogICAgOmhvc3QoW2NoZWNrZWRdKSBbcGFydD0nY2hlY2tib3gnXSB7CiAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLWx1bW8tcHJpbWFyeS1jb2xvcik7CiAgICB9CgogICAgLyogTmVlZGVkIHRvIGFsaWduIHRoZSBjaGVja2JveCBuaWNlbHkgb24gdGhlIGJhc2VsaW5lICovCiAgICBbcGFydD0nY2hlY2tib3gnXTo6YmVmb3JlIHsKICAgICAgY29udGVudDogJ1xcMjAwMyc7CiAgICB9CgogICAgLyogQ2hlY2ttYXJrICovCiAgICBbcGFydD0nY2hlY2tib3gnXTo6YWZ0ZXIgewogICAgICBjb250ZW50OiAnJzsKICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICB3aWR0aDogMDsKICAgICAgaGVpZ2h0OiAwOwogICAgICBib3JkZXI6IDAgc29saWQgdmFyKC0tbHVtby1wcmltYXJ5LWNvbnRyYXN0LWNvbG9yKTsKICAgICAgYm9yZGVyLXdpZHRoOiAwLjE4NzVlbSAwIDAgMC4xODc1ZW07CiAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgIHRyYW5zZm9ybS1vcmlnaW46IDAgMDsKICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICB0b3A6IDAuODEyNWVtOwogICAgICBsZWZ0OiAwLjVlbTsKICAgICAgdHJhbnNmb3JtOiBzY2FsZSgwLjU1KSByb3RhdGUoLTEzNWRlZyk7CiAgICAgIG9wYWNpdHk6IDA7CiAgICB9CgogICAgOmhvc3QoW2NoZWNrZWRdKSBbcGFydD0nY2hlY2tib3gnXTo6YWZ0ZXIgewogICAgICBvcGFjaXR5OiAxOwogICAgICB3aWR0aDogMC42MjVlbTsKICAgICAgaGVpZ2h0OiAxLjA2MjVlbTsKICAgIH0KCiAgICAvKiBJbmRldGVybWluYXRlIGNoZWNrbWFyayAqLwogICAgOmhvc3QoW2luZGV0ZXJtaW5hdGVdKSBbcGFydD0nY2hlY2tib3gnXTo6YWZ0ZXIgewogICAgICB0cmFuc2Zvcm06IG5vbmU7CiAgICAgIG9wYWNpdHk6IDE7CiAgICAgIHRvcDogNDUlOwogICAgICBoZWlnaHQ6IDEwJTsKICAgICAgbGVmdDogMjIlOwogICAgICByaWdodDogMjIlOwogICAgICB3aWR0aDogYXV0bzsKICAgICAgYm9yZGVyOiAwOwogICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1sdW1vLXByaW1hcnktY29udHJhc3QtY29sb3IpOwogICAgICB0cmFuc2l0aW9uOiBvcGFjaXR5IDAuMjVzOwogICAgfQoKICAgIC8qIEZvY3VzIHJpbmcgKi8KICAgIDpob3N0KFtmb2N1cy1yaW5nXSkgW3BhcnQ9J2NoZWNrYm94J10gewogICAgICBib3gtc2hhZG93OiAwIDAgMCAzcHggdmFyKC0tbHVtby1wcmltYXJ5LWNvbG9yLTUwcGN0KTsKICAgIH0KCiAgICAvKiBEaXNhYmxlZCAqLwogICAgOmhvc3QoW2Rpc2FibGVkXSkgewogICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgY29sb3I6IHZhcigtLWx1bW8tZGlzYWJsZWQtdGV4dC1jb2xvcik7CiAgICB9CgogICAgOmhvc3QoW2Rpc2FibGVkXSkgW3BhcnQ9J2xhYmVsJ10gOjpzbG90dGVkKCopIHsKICAgICAgY29sb3I6IGluaGVyaXQ7CiAgICB9CgogICAgOmhvc3QoW2Rpc2FibGVkXSkgW3BhcnQ9J2NoZWNrYm94J10gewogICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1sdW1vLWNvbnRyYXN0LTEwcGN0KTsKICAgIH0KCiAgICA6aG9zdChbZGlzYWJsZWRdKSBbcGFydD0nY2hlY2tib3gnXTo6YWZ0ZXIgewogICAgICBib3JkZXItY29sb3I6IHZhcigtLWx1bW8tY29udHJhc3QtMzBwY3QpOwogICAgfQoKICAgIDpob3N0KFtpbmRldGVybWluYXRlXVtkaXNhYmxlZF0pIFtwYXJ0PSdjaGVja2JveCddOjphZnRlciB7CiAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLWx1bW8tY29udHJhc3QtMzBwY3QpOwogICAgfQoKICAgIC8qIFJUTCBzcGVjaWZpYyBzdHlsZXMgKi8KICAgIDpob3N0KFtkaXI9J3J0bCddKSBbcGFydD0nbGFiZWwnXTpub3QoW2VtcHR5XSkgewogICAgICBtYXJnaW46IDAuMTg3NWVtIDAuMzc1ZW0gMC4xODc1ZW0gMC44NzVlbTsKICAgIH0KCiAgICAvKiBUcmFuc2l0aW9uIHRoZSBjaGVja21hcmsgaWYgYWN0aXZhdGVkIHdpdGggdGhlIG1vdXNlIChkaXNhYmxlZCBmb3IgZ3JpZCBzZWxlY3QtYWxsIHRoaXMgd2F5KSAqLwogICAgOmhvc3QoOmhvdmVyKSBbcGFydD0nY2hlY2tib3gnXTo6YWZ0ZXIgewogICAgICB0cmFuc2l0aW9uOiB3aWR0aCAwLjFzLCBoZWlnaHQgMC4yNXM7CiAgICB9CgogICAgLyogVXNlZCBmb3IgYWN0aXZhdGlvbiAiaGFsbyIgKi8KICAgIFtwYXJ0PSdjaGVja2JveCddOjpiZWZvcmUgewogICAgICBjb2xvcjogdHJhbnNwYXJlbnQ7CiAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIGhlaWdodDogMTAwJTsKICAgICAgYm9yZGVyLXJhZGl1czogaW5oZXJpdDsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogaW5oZXJpdDsKICAgICAgdHJhbnNmb3JtOiBzY2FsZSgxLjQpOwogICAgICBvcGFjaXR5OiAwOwogICAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC4xcywgb3BhY2l0eSAwLjhzOwogICAgfQoKICAgIC8qIEhvdmVyICovCiAgICA6aG9zdCg6bm90KFtjaGVja2VkXSk6bm90KFtpbmRldGVybWluYXRlXSk6bm90KFtkaXNhYmxlZF0pOmhvdmVyKSBbcGFydD0nY2hlY2tib3gnXSB7CiAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLWx1bW8tY29udHJhc3QtMzBwY3QpOwogICAgfQoKICAgIC8qIERpc2FibGUgaG92ZXIgZm9yIHRvdWNoIGRldmljZXMgKi8KICAgIEBtZWRpYSAocG9pbnRlcjogY29hcnNlKSB7CiAgICAgIDpob3N0KDpub3QoW2NoZWNrZWRdKTpub3QoW2luZGV0ZXJtaW5hdGVdKTpub3QoW2Rpc2FibGVkXSk6aG92ZXIpIFtwYXJ0PSdjaGVja2JveCddIHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1sdW1vLWNvbnRyYXN0LTIwcGN0KTsKICAgICAgfQogICAgfQoKICAgIC8qIEFjdGl2ZSAqLwogICAgOmhvc3QoW2FjdGl2ZV0pIFtwYXJ0PSdjaGVja2JveCddIHsKICAgICAgdHJhbnNmb3JtOiBzY2FsZSgwLjkpOwogICAgICB0cmFuc2l0aW9uLWR1cmF0aW9uOiAwLjA1czsKICAgIH0KCiAgICA6aG9zdChbYWN0aXZlXVtjaGVja2VkXSkgW3BhcnQ9J2NoZWNrYm94J10gewogICAgICB0cmFuc2Zvcm06IHNjYWxlKDEuMSk7CiAgICB9CgogICAgOmhvc3QoW2FjdGl2ZV06bm90KFtjaGVja2VkXSkpIFtwYXJ0PSdjaGVja2JveCddOjpiZWZvcmUgewogICAgICB0cmFuc2l0aW9uLWR1cmF0aW9uOiAwLjAxcywgMC4wMXM7CiAgICAgIHRyYW5zZm9ybTogc2NhbGUoMCk7CiAgICAgIG9wYWNpdHk6IDAuNDsKICAgIH0KICBgLHttb2R1bGVJZDoibHVtby1jaGVja2JveCJ9KTt2YXIgTmNlPWU9PmNsYXNzIGV4dGVuZHMgZXtzdGF0aWMgZ2V0IHByb3BlcnRpZXMoKXtyZXR1cm57dGhlbWU6e3R5cGU6U3RyaW5nLHJlYWRPbmx5OiEwfX19YXR0cmlidXRlQ2hhbmdlZENhbGxiYWNrKHIsbixpKXtzdXBlci5hdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2socixuLGkpLHI9PT0idGhlbWUiJiZ0aGlzLl9zZXRUaGVtZShpKX19O3ZhciBqSD1lPT5jbGFzcyBleHRlbmRzIE5jZShlKXtzdGF0aWMgZmluYWxpemUoKXtzdXBlci5maW5hbGl6ZSgpO2xldCByPXRoaXMucHJvdG90eXBlLl90ZW1wbGF0ZSxuPU9iamVjdC5nZXRQcm90b3R5cGVPZih0aGlzLnByb3RvdHlwZSkuX3RlbXBsYXRlO24mJkFycmF5LmZyb20obi5jb250ZW50LnF1ZXJ5U2VsZWN0b3JBbGwoInN0eWxlW2luY2x1ZGVdIikpLmZvckVhY2goaT0+e3RoaXMuX2luY2x1ZGVTdHlsZShpLmdldEF0dHJpYnV0ZSgiaW5jbHVkZSIpLHIpfSksdGhpcy5faW5jbHVkZU1hdGNoaW5nVGhlbWVzKHIpfXN0YXRpYyBfaW5jbHVkZU1hdGNoaW5nVGhlbWVzKHIpe2xldCBpPW91LnByb3RvdHlwZS5tb2R1bGVzLG89ITEsYT10aGlzLmlzKyItZGVmYXVsdC10aGVtZSI7T2JqZWN0LmtleXMoaSkuc29ydCgocyxsKT0+e2xldCBjPXMuaW5kZXhPZigidmFhZGluLSIpPT09MCx1PWwuaW5kZXhPZigidmFhZGluLSIpPT09MCxoPVsibHVtby0iLCJtYXRlcmlhbC0iXSxmPWguZmlsdGVyKGQ9PnMuaW5kZXhPZihkKT09PTApLmxlbmd0aD4wLHA9aC5maWx0ZXIoZD0+bC5pbmRleE9mKGQpPT09MCkubGVuZ3RoPjA7cmV0dXJuIGMhPT11P2M/LTE6MTpmIT09cD9mPy0xOjE6MH0pLmZvckVhY2gocz0+e2lmKHMhPT1hKXtsZXQgbD1pW3NdLmdldEF0dHJpYnV0ZSgidGhlbWUtZm9yIik7bCYmbC5zcGxpdCgiICIpLmZvckVhY2goYz0+e25ldyBSZWdFeHAoIl4iK2Muc3BsaXQoIioiKS5qb2luKCIuKiIpKyIkIikudGVzdCh0aGlzLmlzKSYmKG89ITAsdGhpcy5faW5jbHVkZVN0eWxlKHMscikpfSl9fSksIW8mJmlbYV0mJnRoaXMuX2luY2x1ZGVTdHlsZShhLHIpfXN0YXRpYyBfaW5jbHVkZVN0eWxlKHIsbil7aWYobiYmIW4uY29udGVudC5xdWVyeVNlbGVjdG9yKGBzdHlsZVtpbmNsdWRlPSIke3J9Il1gKSl7bGV0IGk9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3R5bGUiKTtpLnNldEF0dHJpYnV0ZSgiaW5jbHVkZSIsciksbi5jb250ZW50LmFwcGVuZENoaWxkKGkpfX19O3ZhciBSY3Q9ITE7d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoImtleWRvd24iLCgpPT57UmN0PSEwfSx7Y2FwdHVyZTohMH0pO3dpbmRvdy5hZGRFdmVudExpc3RlbmVyKCJtb3VzZWRvd24iLCgpPT57UmN0PSExfSx7Y2FwdHVyZTohMH0pO3ZhciBXdXI9ZT0+Y2xhc3MgZXh0ZW5kcyBle3N0YXRpYyBnZXQgcHJvcGVydGllcygpe3JldHVybnt0YWJpbmRleDp7dHlwZTpOdW1iZXIsdmFsdWU6MCxyZWZsZWN0VG9BdHRyaWJ1dGU6ITAsb2JzZXJ2ZXI6Il90YWJpbmRleENoYW5nZWQifX19fSxEY2U9ZT0+Y2xhc3MgZXh0ZW5kcyBXdXIoZSl7c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue2F1dG9mb2N1czp7dHlwZTpCb29sZWFufSxfcHJldmlvdXNUYWJJbmRleDp7dHlwZTpOdW1iZXJ9LGRpc2FibGVkOnt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9kaXNhYmxlZENoYW5nZWQiLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0sX2lzU2hpZnRUYWJiaW5nOnt0eXBlOkJvb2xlYW59fX1yZWFkeSgpe3RoaXMuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXNpbiIscj0+e3IuY29tcG9zZWRQYXRoKClbMF09PT10aGlzP3RoaXMuY29udGFpbnMoci5yZWxhdGVkVGFyZ2V0KXx8dGhpcy5fZm9jdXMoKTpyLmNvbXBvc2VkUGF0aCgpLmluZGV4T2YodGhpcy5mb2N1c0VsZW1lbnQpIT09LTEmJiF0aGlzLmRpc2FibGVkJiZ0aGlzLl9zZXRGb2N1c2VkKCEwKX0pLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXNvdXQiLCgpPT50aGlzLl9zZXRGb2N1c2VkKCExKSksc3VwZXIucmVhZHkoKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImtleWRvd24iLHI9Pnshci5kZWZhdWx0UHJldmVudGVkJiZyLmtleUNvZGU9PT05JiZyLnNoaWZ0S2V5JiYodGhpcy5faXNTaGlmdFRhYmJpbmc9ITAsSFRNTEVsZW1lbnQucHJvdG90eXBlLmZvY3VzLmFwcGx5KHRoaXMpLHRoaXMuX3NldEZvY3VzZWQoITEpLHNldFRpbWVvdXQoKCk9PnRoaXMuX2lzU2hpZnRUYWJiaW5nPSExLDApKX0pLHRoaXMuYXV0b2ZvY3VzJiYhdGhpcy5kaXNhYmxlZCYmd2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZSgoKT0+e3RoaXMuX2ZvY3VzKCksdGhpcy5fc2V0Rm9jdXNlZCghMCksdGhpcy5zZXRBdHRyaWJ1dGUoImZvY3VzLXJpbmciLCIiKX0pfWRpc2Nvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuZGlzY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLmhhc0F0dHJpYnV0ZSgiZm9jdXNlZCIpJiZ0aGlzLl9zZXRGb2N1c2VkKCExKX1fc2V0Rm9jdXNlZChyKXtyP3RoaXMuc2V0QXR0cmlidXRlKCJmb2N1c2VkIiwiIik6dGhpcy5yZW1vdmVBdHRyaWJ1dGUoImZvY3VzZWQiKSxyJiZSY3Q/dGhpcy5zZXRBdHRyaWJ1dGUoImZvY3VzLXJpbmciLCIiKTp0aGlzLnJlbW92ZUF0dHJpYnV0ZSgiZm9jdXMtcmluZyIpfWdldCBmb2N1c0VsZW1lbnQoKXtyZXR1cm4gd2luZG93LmNvbnNvbGUud2FybihgUGxlYXNlIGltcGxlbWVudCB0aGUgJ2ZvY3VzRWxlbWVudCcgcHJvcGVydHkgaW4gPCR7dGhpcy5sb2NhbE5hbWV9PmApLHRoaXN9X2ZvY3VzKCl7IXRoaXMuZm9jdXNFbGVtZW50fHx0aGlzLl9pc1NoaWZ0VGFiYmluZ3x8KHRoaXMuZm9jdXNFbGVtZW50LmZvY3VzKCksdGhpcy5fc2V0Rm9jdXNlZCghMCkpfWZvY3VzKCl7IXRoaXMuZm9jdXNFbGVtZW50fHx0aGlzLmRpc2FibGVkfHwodGhpcy5mb2N1c0VsZW1lbnQuZm9jdXMoKSx0aGlzLl9zZXRGb2N1c2VkKCEwKSl9Ymx1cigpeyF0aGlzLmZvY3VzRWxlbWVudHx8KHRoaXMuZm9jdXNFbGVtZW50LmJsdXIoKSx0aGlzLl9zZXRGb2N1c2VkKCExKSl9X2Rpc2FibGVkQ2hhbmdlZChyKXt0aGlzLmZvY3VzRWxlbWVudC5kaXNhYmxlZD1yLHI/KHRoaXMuYmx1cigpLHRoaXMuX3ByZXZpb3VzVGFiSW5kZXg9dGhpcy50YWJpbmRleCx0aGlzLnRhYmluZGV4PS0xLHRoaXMuc2V0QXR0cmlidXRlKCJhcmlhLWRpc2FibGVkIiwidHJ1ZSIpKToodHlwZW9mIHRoaXMuX3ByZXZpb3VzVGFiSW5kZXghPSJ1bmRlZmluZWQiJiYodGhpcy50YWJpbmRleD10aGlzLl9wcmV2aW91c1RhYkluZGV4KSx0aGlzLnJlbW92ZUF0dHJpYnV0ZSgiYXJpYS1kaXNhYmxlZCIpKX1fdGFiaW5kZXhDaGFuZ2VkKHIpe3IhPT12b2lkIDAmJih0aGlzLmZvY3VzRWxlbWVudC50YWJJbmRleD1yKSx0aGlzLmRpc2FibGVkJiZ0aGlzLnRhYmluZGV4JiYodGhpcy50YWJpbmRleCE9PS0xJiYodGhpcy5fcHJldmlvdXNUYWJJbmRleD10aGlzLnRhYmluZGV4KSx0aGlzLnRhYmluZGV4PXI9dm9pZCAwKX1jbGljaygpe3RoaXMuZGlzYWJsZWR8fHN1cGVyLmNsaWNrKCl9fTt2YXIgWXVyPS9cL1wqXCpccyt2YWFkaW4tZGV2LW1vZGU6c3RhcnQoW1xzXFNdKil2YWFkaW4tZGV2LW1vZGU6ZW5kXHMrXCpcKlwvL2ksWEg9d2luZG93LlZhYWRpbiYmd2luZG93LlZhYWRpbi5GbG93JiZ3aW5kb3cuVmFhZGluLkZsb3cuY2xpZW50cztmdW5jdGlvbiBqdXIoKXtmdW5jdGlvbiBlKCl7cmV0dXJuITB9cmV0dXJuIE9jZShlKX1mdW5jdGlvbiBYdXIoKXt0cnl7cmV0dXJuICR1cigpPyEwOkt1cigpP1hIPyFadXIoKTohanVyKCk6ITF9Y2F0Y2goZSl7cmV0dXJuITF9fWZ1bmN0aW9uICR1cigpe3JldHVybiBsb2NhbFN0b3JhZ2UuZ2V0SXRlbSgidmFhZGluLmRldmVsb3BtZW50bW9kZS5mb3JjZSIpfWZ1bmN0aW9uIEt1cigpe3JldHVyblsibG9jYWxob3N0IiwiMTI3LjAuMC4xIl0uaW5kZXhPZih3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUpPj0wfWZ1bmN0aW9uIFp1cigpe3JldHVybiEhKFhIJiZPYmplY3Qua2V5cyhYSCkubWFwKHQ9PlhIW3RdKS5maWx0ZXIodD0+dC5wcm9kdWN0aW9uTW9kZSkubGVuZ3RoPjApfWZ1bmN0aW9uIE9jZShlLHQpe2lmKHR5cGVvZiBlIT0iZnVuY3Rpb24iKXJldHVybjtsZXQgcj1ZdXIuZXhlYyhlLnRvU3RyaW5nKCkpO2lmKHIpdHJ5e2U9bmV3IEZ1bmN0aW9uKHJbMV0pfWNhdGNoKG4pe2NvbnNvbGUubG9nKCJ2YWFkaW4tZGV2ZWxvcG1lbnQtbW9kZS1kZXRlY3RvcjogdW5jb21tZW50QW5kUnVuKCkgZmFpbGVkIixuKX1yZXR1cm4gZSh0KX13aW5kb3cuVmFhZGluPXdpbmRvdy5WYWFkaW58fHt9O3ZhciBOY3Q9ZnVuY3Rpb24oZSx0KXtpZih3aW5kb3cuVmFhZGluLmRldmVsb3BtZW50TW9kZSlyZXR1cm4gT2NlKGUsdCl9O3dpbmRvdy5WYWFkaW4uZGV2ZWxvcG1lbnRNb2RlPT09dm9pZCAwJiYod2luZG93LlZhYWRpbi5kZXZlbG9wbWVudE1vZGU9WHVyKCkpO2Z1bmN0aW9uIEp1cigpe312YXIgemNlPWZ1bmN0aW9uKCl7aWYodHlwZW9mIE5jdD09ImZ1bmN0aW9uIilyZXR1cm4gTmN0KEp1cil9O3ZhciBfMz1jbGFzc3tzdGF0aWMgZGV0ZWN0U2Nyb2xsVHlwZSgpe2xldCB0PWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpO3QudGV4dENvbnRlbnQ9IkFCQ0QiLHQuZGlyPSJydGwiLHQuc3R5bGUuZm9udFNpemU9IjE0cHgiLHQuc3R5bGUud2lkdGg9IjRweCIsdC5zdHlsZS5oZWlnaHQ9IjFweCIsdC5zdHlsZS5wb3NpdGlvbj0iYWJzb2x1dGUiLHQuc3R5bGUudG9wPSItMTAwMHB4Iix0LnN0eWxlLm92ZXJmbG93PSJzY3JvbGwiLGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQodCk7bGV0IHI9InJldmVyc2UiO3JldHVybiB0LnNjcm9sbExlZnQ+MD9yPSJkZWZhdWx0IjoodC5zY3JvbGxMZWZ0PTIsdC5zY3JvbGxMZWZ0PDImJihyPSJuZWdhdGl2ZSIpKSxkb2N1bWVudC5ib2R5LnJlbW92ZUNoaWxkKHQpLHJ9c3RhdGljIGdldE5vcm1hbGl6ZWRTY3JvbGxMZWZ0KHQscixuKXtsZXR7c2Nyb2xsTGVmdDppfT1uO2lmKHIhPT0icnRsInx8IXQpcmV0dXJuIGk7c3dpdGNoKHQpe2Nhc2UibmVnYXRpdmUiOnJldHVybiBuLnNjcm9sbFdpZHRoLW4uY2xpZW50V2lkdGgraTtjYXNlInJldmVyc2UiOnJldHVybiBuLnNjcm9sbFdpZHRoLW4uY2xpZW50V2lkdGgtaX1yZXR1cm4gaX1zdGF0aWMgc2V0Tm9ybWFsaXplZFNjcm9sbExlZnQodCxyLG4saSl7aWYociE9PSJydGwifHwhdCl7bi5zY3JvbGxMZWZ0PWk7cmV0dXJufXN3aXRjaCh0KXtjYXNlIm5lZ2F0aXZlIjpuLnNjcm9sbExlZnQ9bi5jbGllbnRXaWR0aC1uLnNjcm9sbFdpZHRoK2k7YnJlYWs7Y2FzZSJyZXZlcnNlIjpuLnNjcm9sbExlZnQ9bi5zY3JvbGxXaWR0aC1uLmNsaWVudFdpZHRoLWk7YnJlYWs7ZGVmYXVsdDpuLnNjcm9sbExlZnQ9aTticmVha319fTt2YXIgRTA9W10sUXVyPWZ1bmN0aW9uKCl7bGV0IGU9T2N0KCk7RTAuZm9yRWFjaCh0PT57RGN0KHQsZSl9KX0sJEgsdGhyPW5ldyBNdXRhdGlvbk9ic2VydmVyKFF1cik7dGhyLm9ic2VydmUoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LHthdHRyaWJ1dGVzOiEwLGF0dHJpYnV0ZUZpbHRlcjpbImRpciJdfSk7dmFyIERjdD1mdW5jdGlvbihlLHQscj1lLmdldEF0dHJpYnV0ZSgiZGlyIikpe3Q/ZS5zZXRBdHRyaWJ1dGUoImRpciIsdCk6ciE9bnVsbCYmZS5yZW1vdmVBdHRyaWJ1dGUoImRpciIpfSxPY3Q9ZnVuY3Rpb24oKXtyZXR1cm4gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LmdldEF0dHJpYnV0ZSgiZGlyIil9LEtIPWU9PmNsYXNzIGV4dGVuZHMgZXtzdGF0aWMgZ2V0IHByb3BlcnRpZXMoKXtyZXR1cm57ZGlyOnt0eXBlOlN0cmluZyx2YWx1ZToiIixyZWZsZWN0VG9BdHRyaWJ1dGU6ITB9fX1zdGF0aWMgZmluYWxpemUoKXtzdXBlci5maW5hbGl6ZSgpLCRIfHwoJEg9XzMuZGV0ZWN0U2Nyb2xsVHlwZSgpKX1jb25uZWN0ZWRDYWxsYmFjaygpe3N1cGVyLmNvbm5lY3RlZENhbGxiYWNrKCksdGhpcy5oYXNBdHRyaWJ1dGUoImRpciIpfHwodGhpcy5fX3N1YnNjcmliZSgpLERjdCh0aGlzLE9jdCgpLG51bGwpKX1hdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2socixuLGkpe2lmKHN1cGVyLmF0dHJpYnV0ZUNoYW5nZWRDYWxsYmFjayhyLG4saSksciE9PSJkaXIiKXJldHVybjtsZXQgbz1PY3QoKSxhPWk9PT1vJiZFMC5pbmRleE9mKHRoaXMpPT09LTEscz0haSYmbiYmRTAuaW5kZXhPZih0aGlzKT09PS0xO2F8fHM/KHRoaXMuX19zdWJzY3JpYmUoKSxEY3QodGhpcyxvLGkpKTppIT09byYmbj09PW8mJnRoaXMuX19zdWJzY3JpYmUoITEpfWRpc2Nvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuZGlzY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLl9fc3Vic2NyaWJlKCExKSx0aGlzLnJlbW92ZUF0dHJpYnV0ZSgiZGlyIil9X3ZhbHVlVG9Ob2RlQXR0cmlidXRlKHIsbixpKXtpPT09ImRpciImJm49PT0iIiYmIXIuaGFzQXR0cmlidXRlKCJkaXIiKXx8c3VwZXIuX3ZhbHVlVG9Ob2RlQXR0cmlidXRlKHIsbixpKX1fYXR0cmlidXRlVG9Qcm9wZXJ0eShyLG4saSl7cj09PSJkaXIiJiYhbj90aGlzLmRpcj0iIjpzdXBlci5fYXR0cmlidXRlVG9Qcm9wZXJ0eShyLG4saSl9X19zdWJzY3JpYmUocj0hMCl7cj9FMC5pbmRleE9mKHRoaXMpPT09LTEmJkUwLnB1c2godGhpcyk6RTAuaW5kZXhPZih0aGlzKT4tMSYmRTAuc3BsaWNlKEUwLmluZGV4T2YodGhpcyksMSl9X19nZXROb3JtYWxpemVkU2Nyb2xsTGVmdChyKXtyZXR1cm4gXzMuZ2V0Tm9ybWFsaXplZFNjcm9sbExlZnQoJEgsdGhpcy5nZXRBdHRyaWJ1dGUoImRpciIpfHwibHRyIixyKX1fX3NldE5vcm1hbGl6ZWRTY3JvbGxMZWZ0KHIsbil7cmV0dXJuIF8zLnNldE5vcm1hbGl6ZWRTY3JvbGxMZWZ0KCRILHRoaXMuZ2V0QXR0cmlidXRlKCJkaXIiKXx8Imx0ciIscixuKX19O3dpbmRvdy5WYWFkaW49d2luZG93LlZhYWRpbnx8e307d2luZG93LlZhYWRpbi5yZWdpc3RyYXRpb25zPXdpbmRvdy5WYWFkaW4ucmVnaXN0cmF0aW9uc3x8W107d2luZG93LlZhYWRpbi5kZXZlbG9wbWVudE1vZGVDYWxsYmFjaz13aW5kb3cuVmFhZGluLmRldmVsb3BtZW50TW9kZUNhbGxiYWNrfHx7fTt3aW5kb3cuVmFhZGluLmRldmVsb3BtZW50TW9kZUNhbGxiYWNrWyJ2YWFkaW4tdXNhZ2Utc3RhdGlzdGljcyJdPWZ1bmN0aW9uKCl7emNlKCl9O3ZhciB6Y3QsRmNlPW5ldyBTZXQsWkg9ZT0+Y2xhc3MgZXh0ZW5kcyBLSChlKXtzdGF0aWMgZmluYWxpemUoKXtzdXBlci5maW5hbGl6ZSgpO2xldHtpczpyfT10aGlzO3ImJiFGY2UuaGFzKHIpJiYod2luZG93LlZhYWRpbi5yZWdpc3RyYXRpb25zLnB1c2godGhpcyksRmNlLmFkZChyKSx3aW5kb3cuVmFhZGluLmRldmVsb3BtZW50TW9kZUNhbGxiYWNrJiYoemN0PXNyLmRlYm91bmNlKHpjdCxreCwoKT0+e3dpbmRvdy5WYWFkaW4uZGV2ZWxvcG1lbnRNb2RlQ2FsbGJhY2tbInZhYWRpbi11c2FnZS1zdGF0aXN0aWNzIl0oKX0pLEpsKHpjdCkpKX1jb25zdHJ1Y3Rvcigpe3N1cGVyKCksZG9jdW1lbnQuZG9jdHlwZT09PW51bGwmJmNvbnNvbGUud2FybignVmFhZGluIGNvbXBvbmVudHMgcmVxdWlyZSB0aGUgInN0YW5kYXJkcyBtb2RlIiBkZWNsYXJhdGlvbi4gUGxlYXNlIGFkZCA8IURPQ1RZUEUgaHRtbD4gdG8gdGhlIEhUTUwgZG9jdW1lbnQuJyl9fTt2YXIgSkg9Y2xhc3MgZXh0ZW5kcyBaSChEY2UoakgoeWgobXQpKSkpe3N0YXRpYyBnZXQgdGVtcGxhdGUoKXtyZXR1cm4gUWAKICAgICAgPHN0eWxlPgogICAgICAgIDpob3N0IHsKICAgICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgICAgICB9CgogICAgICAgIDpob3N0KFtoaWRkZW5dKSB7CiAgICAgICAgICBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7CiAgICAgICAgfQoKICAgICAgICBsYWJlbCB7CiAgICAgICAgICBkaXNwbGF5OiBpbmxpbmUtZmxleDsKICAgICAgICAgIGFsaWduLWl0ZW1zOiBiYXNlbGluZTsKICAgICAgICAgIG91dGxpbmU6IG5vbmU7CiAgICAgICAgfQoKICAgICAgICBbcGFydD0nY2hlY2tib3gnXSB7CiAgICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgICAgICBmbGV4OiBub25lOwogICAgICAgIH0KCiAgICAgICAgaW5wdXRbdHlwZT0nY2hlY2tib3gnXSB7CiAgICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgICB0b3A6IDA7CiAgICAgICAgICBsZWZ0OiAwOwogICAgICAgICAgcmlnaHQ6IDA7CiAgICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgICAgIG9wYWNpdHk6IDA7CiAgICAgICAgICBjdXJzb3I6IGluaGVyaXQ7CiAgICAgICAgICBtYXJnaW46IDA7CiAgICAgICAgfQoKICAgICAgICA6aG9zdChbZGlzYWJsZWRdKSB7CiAgICAgICAgICAtd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6IHRyYW5zcGFyZW50OwogICAgICAgIH0KICAgICAgPC9zdHlsZT4KCiAgICAgIDxsYWJlbD4KICAgICAgICA8c3BhbiBwYXJ0PSJjaGVja2JveCI+CiAgICAgICAgICA8aW5wdXQKICAgICAgICAgICAgdHlwZT0iY2hlY2tib3giCiAgICAgICAgICAgIGNoZWNrZWQ9Int7Y2hlY2tlZDo6Y2hhbmdlfX0iCiAgICAgICAgICAgIGRpc2FibGVkJD0iW1tkaXNhYmxlZF1dIgogICAgICAgICAgICBpbmRldGVybWluYXRlPSJ7e2luZGV0ZXJtaW5hdGU6OmNoYW5nZX19IgogICAgICAgICAgICByb2xlPSJwcmVzZW50YXRpb24iCiAgICAgICAgICAgIHRhYmluZGV4PSItMSIKICAgICAgICAgIC8+CiAgICAgICAgPC9zcGFuPgoKICAgICAgICA8c3BhbiBwYXJ0PSJsYWJlbCI+CiAgICAgICAgICA8c2xvdD48L3Nsb3Q+CiAgICAgICAgPC9zcGFuPgogICAgICA8L2xhYmVsPgogICAgYH1zdGF0aWMgZ2V0IGlzKCl7cmV0dXJuInZhYWRpbi1jaGVja2JveCJ9c3RhdGljIGdldCB2ZXJzaW9uKCl7cmV0dXJuIjIwLjAuMiJ9c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue2NoZWNrZWQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxub3RpZnk6ITAsb2JzZXJ2ZXI6Il9jaGVja2VkQ2hhbmdlZCIscmVmbGVjdFRvQXR0cmlidXRlOiEwfSxpbmRldGVybWluYXRlOnt0eXBlOkJvb2xlYW4sbm90aWZ5OiEwLG9ic2VydmVyOiJfaW5kZXRlcm1pbmF0ZUNoYW5nZWQiLHJlZmxlY3RUb0F0dHJpYnV0ZTohMCx2YWx1ZTohMX0sdmFsdWU6e3R5cGU6U3RyaW5nLHZhbHVlOiJvbiJ9LF9uYXRpdmVDaGVja2JveDp7dHlwZTpPYmplY3R9fX1jb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy5uYW1lfWdldCBuYW1lKCl7cmV0dXJuIHRoaXMuY2hlY2tlZD90aGlzLl9zdG9yZWROYW1lOiIifXNldCBuYW1lKHQpe3RoaXMuX3N0b3JlZE5hbWU9dH1yZWFkeSgpe3N1cGVyLnJlYWR5KCksdGhpcy5zZXRBdHRyaWJ1dGUoInJvbGUiLCJjaGVja2JveCIpLHRoaXMuX25hdGl2ZUNoZWNrYm94PXRoaXMuc2hhZG93Um9vdC5xdWVyeVNlbGVjdG9yKCdpbnB1dFt0eXBlPSJjaGVja2JveCJdJyksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsdGhpcy5faGFuZGxlQ2xpY2suYmluZCh0aGlzKSksdGhpcy5fYWRkQWN0aXZlTGlzdGVuZXJzKCk7bGV0IHQ9dGhpcy5nZXRBdHRyaWJ1dGUoIm5hbWUiKTt0JiYodGhpcy5uYW1lPXQpLHRoaXMuc2hhZG93Um9vdC5xdWVyeVNlbGVjdG9yKCdbcGFydH49ImxhYmVsIl0nKS5xdWVyeVNlbGVjdG9yKCJzbG90IikuYWRkRXZlbnRMaXN0ZW5lcigic2xvdGNoYW5nZSIsdGhpcy5fdXBkYXRlTGFiZWxBdHRyaWJ1dGUuYmluZCh0aGlzKSksdGhpcy5fdXBkYXRlTGFiZWxBdHRyaWJ1dGUoKX1fdXBkYXRlTGFiZWxBdHRyaWJ1dGUoKXtsZXQgdD10aGlzLnNoYWRvd1Jvb3QucXVlcnlTZWxlY3RvcignW3BhcnR+PSJsYWJlbCJdJykscj10LmZpcnN0RWxlbWVudENoaWxkLmFzc2lnbmVkTm9kZXMoKTt0aGlzLl9pc0Fzc2lnbmVkTm9kZXNFbXB0eShyKT90LnNldEF0dHJpYnV0ZSgiZW1wdHkiLCIiKTp0LnJlbW92ZUF0dHJpYnV0ZSgiZW1wdHkiKX1faXNBc3NpZ25lZE5vZGVzRW1wdHkodCl7cmV0dXJuIHQubGVuZ3RoPT09MHx8dC5sZW5ndGg9PTEmJnRbMF0ubm9kZVR5cGU9PU5vZGUuVEVYVF9OT0RFJiZ0WzBdLnRleHRDb250ZW50LnRyaW0oKT09PSIifV9jaGVja2VkQ2hhbmdlZCh0KXt0aGlzLmluZGV0ZXJtaW5hdGU/dGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtY2hlY2tlZCIsIm1peGVkIik6dGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtY2hlY2tlZCIsQm9vbGVhbih0KSl9X2luZGV0ZXJtaW5hdGVDaGFuZ2VkKHQpe3Q/dGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtY2hlY2tlZCIsIm1peGVkIik6dGhpcy5zZXRBdHRyaWJ1dGUoImFyaWEtY2hlY2tlZCIsdGhpcy5jaGVja2VkKX1fYWRkQWN0aXZlTGlzdGVuZXJzKCl7dGhpcy5fYWRkRXZlbnRMaXN0ZW5lclRvTm9kZSh0aGlzLCJkb3duIix0PT57dGhpcy5fX2ludGVyYWN0aW9uc0FsbG93ZWQodCkmJnRoaXMuc2V0QXR0cmlidXRlKCJhY3RpdmUiLCIiKX0pLHRoaXMuX2FkZEV2ZW50TGlzdGVuZXJUb05vZGUodGhpcywidXAiLCgpPT50aGlzLnJlbW92ZUF0dHJpYnV0ZSgiYWN0aXZlIikpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigia2V5ZG93biIsdD0+e3RoaXMuX19pbnRlcmFjdGlvbnNBbGxvd2VkKHQpJiZ0LmtleUNvZGU9PT0zMiYmKHQucHJldmVudERlZmF1bHQoKSx0aGlzLnNldEF0dHJpYnV0ZSgiYWN0aXZlIiwiIikpfSksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJrZXl1cCIsdD0+e3RoaXMuX19pbnRlcmFjdGlvbnNBbGxvd2VkKHQpJiZ0LmtleUNvZGU9PT0zMiYmKHQucHJldmVudERlZmF1bHQoKSx0aGlzLl90b2dnbGVDaGVja2VkKCksdGhpcy5yZW1vdmVBdHRyaWJ1dGUoImFjdGl2ZSIpLHRoaXMuaW5kZXRlcm1pbmF0ZSYmKHRoaXMuaW5kZXRlcm1pbmF0ZT0hMSkpfSl9Z2V0IGZvY3VzRWxlbWVudCgpe3JldHVybiB0aGlzLnNoYWRvd1Jvb3QucXVlcnlTZWxlY3RvcigiaW5wdXQiKX1fX2ludGVyYWN0aW9uc0FsbG93ZWQodCl7cmV0dXJuISh0aGlzLmRpc2FibGVkfHx0LnRhcmdldC5sb2NhbE5hbWU9PT0iYSIpfV9oYW5kbGVDbGljayh0KXt0aGlzLl9faW50ZXJhY3Rpb25zQWxsb3dlZCh0KSYmKHRoaXMuaW5kZXRlcm1pbmF0ZT8odGhpcy5pbmRldGVybWluYXRlPSExLHQucHJldmVudERlZmF1bHQoKSx0aGlzLl90b2dnbGVDaGVja2VkKCkpOnQuY29tcG9zZWRQYXRoKClbMF0hPT10aGlzLl9uYXRpdmVDaGVja2JveCYmKHQucHJldmVudERlZmF1bHQoKSx0aGlzLl90b2dnbGVDaGVja2VkKCkpKX1fdG9nZ2xlQ2hlY2tlZCgpe3RoaXMuY2hlY2tlZD0hdGhpcy5jaGVja2VkLHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoImNoYW5nZSIse2NvbXBvc2VkOiExLGJ1YmJsZXM6ITB9KSl9fTtjdXN0b21FbGVtZW50cy5kZWZpbmUoSkguaXMsSkgpO2pjKCJ2YWFkaW4tZ3JpZCIsQ2lgCiAgICA6aG9zdCB7CiAgICAgIGZvbnQtZmFtaWx5OiB2YXIoLS1sdW1vLWZvbnQtZmFtaWx5KTsKICAgICAgZm9udC1zaXplOiB2YXIoLS1sdW1vLWZvbnQtc2l6ZS1tKTsKICAgICAgbGluZS1oZWlnaHQ6IHZhcigtLWx1bW8tbGluZS1oZWlnaHQtcyk7CiAgICAgIGNvbG9yOiB2YXIoLS1sdW1vLWJvZHktdGV4dC1jb2xvcik7CiAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLWx1bW8tYmFzZS1jb2xvcik7CiAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgIC13ZWJraXQtdGV4dC1zaXplLWFkanVzdDogMTAwJTsKICAgICAgLXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yOiB0cmFuc3BhcmVudDsKICAgICAgLXdlYmtpdC1mb250LXNtb290aGluZzogYW50aWFsaWFzZWQ7CiAgICAgIC1tb3otb3N4LWZvbnQtc21vb3RoaW5nOiBncmF5c2NhbGU7CgogICAgICAvKiBGb3IgaW50ZXJuYWwgdXNlIG9ubHkgKi8KICAgICAgLS1fbHVtby1ncmlkLWJvcmRlci1jb2xvcjogdmFyKC0tbHVtby1jb250cmFzdC0yMHBjdCk7CiAgICAgIC0tX2x1bW8tZ3JpZC1zZWNvbmRhcnktYm9yZGVyLWNvbG9yOiB2YXIoLS1sdW1vLWNvbnRyYXN0LTEwcGN0KTsKICAgICAgLS1fbHVtby1ncmlkLWJvcmRlci13aWR0aDogMXB4OwogICAgICAtLV9sdW1vLWdyaWQtc2VsZWN0ZWQtcm93LWNvbG9yOiB2YXIoLS1sdW1vLXByaW1hcnktY29sb3ItMTBwY3QpOwogICAgfQoKICAgIC8qIE5vIChvdXRlcikgYm9yZGVyICovCgogICAgOmhvc3QoOm5vdChbdGhlbWV+PSduby1ib3JkZXInXSkpIHsKICAgICAgYm9yZGVyOiB2YXIoLS1fbHVtby1ncmlkLWJvcmRlci13aWR0aCkgc29saWQgdmFyKC0tX2x1bW8tZ3JpZC1ib3JkZXItY29sb3IpOwogICAgfQoKICAgIC8qIENlbGwgc3R5bGVzICovCgogICAgW3BhcnR+PSdjZWxsJ10gewogICAgICBtaW4taGVpZ2h0OiB2YXIoLS1sdW1vLXNpemUtbSk7CiAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLWx1bW8tYmFzZS1jb2xvcik7CiAgICB9CgogICAgW3BhcnR+PSdjZWxsJ10gOjpzbG90dGVkKHZhYWRpbi1ncmlkLWNlbGwtY29udGVudCkgewogICAgICBjdXJzb3I6IGRlZmF1bHQ7CiAgICAgIHBhZGRpbmc6IHZhcigtLWx1bW8tc3BhY2UteHMpIHZhcigtLWx1bW8tc3BhY2UtbSk7CiAgICB9CgogICAgLyogQXBwbHkgcm93IGJvcmRlcnMgYnkgZGVmYXVsdCBhbmQgaW50cm9kdWNlIHRoZSAibm8tcm93LWJvcmRlcnMiIHZhcmlhbnQgKi8KICAgIDpob3N0KDpub3QoW3RoZW1lfj0nbm8tcm93LWJvcmRlcnMnXSkpIFtwYXJ0fj0nY2VsbCddOm5vdChbcGFydH49J2RldGFpbHMtY2VsbCddKSB7CiAgICAgIGJvcmRlci10b3A6IHZhcigtLV9sdW1vLWdyaWQtYm9yZGVyLXdpZHRoKSBzb2xpZCB2YXIoLS1fbHVtby1ncmlkLXNlY29uZGFyeS1ib3JkZXItY29sb3IpOwogICAgfQoKICAgIC8qIEhpZGUgZmlyc3QgYm9keSByb3cgdG9wIGJvcmRlciAqLwogICAgOmhvc3QoOm5vdChbdGhlbWV+PSduby1yb3ctYm9yZGVycyddKSkgW3BhcnQ9J3JvdyddW2ZpcnN0XSBbcGFydH49J2NlbGwnXTpub3QoW3BhcnR+PSdkZXRhaWxzLWNlbGwnXSkgewogICAgICBib3JkZXItdG9wOiAwOwogICAgICBtaW4taGVpZ2h0OiBjYWxjKHZhcigtLWx1bW8tc2l6ZS1tKSAtIHZhcigtLV9sdW1vLWdyaWQtYm9yZGVyLXdpZHRoKSk7CiAgICB9CgogICAgLyogRm9jdXMtcmluZyAqLwoKICAgIFtwYXJ0fj0nY2VsbCddOmZvY3VzIHsKICAgICAgb3V0bGluZTogbm9uZTsKICAgIH0KCiAgICA6aG9zdChbbmF2aWdhdGluZ10pIFtwYXJ0fj0nY2VsbCddOmZvY3VzOjpiZWZvcmUgewogICAgICBjb250ZW50OiAnJzsKICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICB0b3A6IDA7CiAgICAgIHJpZ2h0OiAwOwogICAgICBib3R0b206IDA7CiAgICAgIGxlZnQ6IDA7CiAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICBib3gtc2hhZG93OiBpbnNldCAwIDAgMCAycHggdmFyKC0tbHVtby1wcmltYXJ5LWNvbG9yLTUwcGN0KTsKICAgIH0KCiAgICAvKiBEcmFnIGFuZCBEcm9wIHN0eWxlcyAqLwogICAgOmhvc3QoW2RyYWdvdmVyXSk6OmFmdGVyIHsKICAgICAgY29udGVudDogJyc7CiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgei1pbmRleDogMTAwOwogICAgICB0b3A6IDA7CiAgICAgIHJpZ2h0OiAwOwogICAgICBib3R0b206IDA7CiAgICAgIGxlZnQ6IDA7CiAgICAgIHBvaW50ZXItZXZlbnRzOiBub25lOwogICAgICBib3gtc2hhZG93OiBpbnNldCAwIDAgMCAycHggdmFyKC0tbHVtby1wcmltYXJ5LWNvbG9yLTUwcGN0KTsKICAgIH0KCiAgICBbcGFydH49J3JvdyddW2RyYWdvdmVyXSB7CiAgICAgIHotaW5kZXg6IDEwMCAhaW1wb3J0YW50OwogICAgfQoKICAgIFtwYXJ0fj0ncm93J11bZHJhZ292ZXJdIFtwYXJ0fj0nY2VsbCddIHsKICAgICAgb3ZlcmZsb3c6IHZpc2libGU7CiAgICB9CgogICAgW3BhcnR+PSdyb3cnXVtkcmFnb3Zlcl0gW3BhcnR+PSdjZWxsJ106OmFmdGVyIHsKICAgICAgY29udGVudDogJyc7CiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgdG9wOiAwOwogICAgICByaWdodDogMDsKICAgICAgYm90dG9tOiAwOwogICAgICBsZWZ0OiAwOwogICAgICBoZWlnaHQ6IGNhbGModmFyKC0tX2x1bW8tZ3JpZC1ib3JkZXItd2lkdGgpICsgMnB4KTsKICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7CiAgICAgIGJhY2tncm91bmQ6IHZhcigtLWx1bW8tcHJpbWFyeS1jb2xvci01MHBjdCk7CiAgICB9CgogICAgOmhvc3QoW3RoZW1lfj0nbm8tcm93LWJvcmRlcnMnXSkgW2RyYWdvdmVyXSBbcGFydH49J2NlbGwnXTo6YWZ0ZXIgewogICAgICBoZWlnaHQ6IDJweDsKICAgIH0KCiAgICBbcGFydH49J3JvdyddW2RyYWdvdmVyPSdiZWxvdyddIFtwYXJ0fj0nY2VsbCddOjphZnRlciB7CiAgICAgIHRvcDogMTAwJTsKICAgICAgYm90dG9tOiBhdXRvOwogICAgICBtYXJnaW4tdG9wOiAtMXB4OwogICAgfQoKICAgIFtwYXJ0fj0ncm93J11bZHJhZ292ZXI9J2Fib3ZlJ10gW3BhcnR+PSdjZWxsJ106OmFmdGVyIHsKICAgICAgdG9wOiBhdXRvOwogICAgICBib3R0b206IDEwMCU7CiAgICAgIG1hcmdpbi1ib3R0b206IC0xcHg7CiAgICB9CgogICAgW3BhcnR+PSdyb3cnXVtkZXRhaWxzLW9wZW5lZF1bZHJhZ292ZXI9J2JlbG93J10gW3BhcnR+PSdjZWxsJ106bm90KFtwYXJ0fj0nZGV0YWlscy1jZWxsJ10pOjphZnRlciwKICAgIFtwYXJ0fj0ncm93J11bZGV0YWlscy1vcGVuZWRdW2RyYWdvdmVyPSdhYm92ZSddIFtwYXJ0fj0nZGV0YWlscy1jZWxsJ106OmFmdGVyIHsKICAgICAgZGlzcGxheTogbm9uZTsKICAgIH0KCiAgICBbcGFydH49J3JvdyddW2RyYWdvdmVyXVtkcmFnb3Zlcj0nb24tdG9wJ10gW3BhcnR+PSdjZWxsJ106OmFmdGVyIHsKICAgICAgaGVpZ2h0OiAxMDAlOwogICAgfQoKICAgIFtwYXJ0fj0ncm93J11bZHJhZ3N0YXJ0XSB7CiAgICAgIC8qIEFkZCBib3R0b20tc3BhY2UgdG8gdGhlIHJvdyBzbyB0aGUgZHJhZyBudW1iZXIgZG9lc24ndCBnZXQgY2xpcHBlZC4gTmVlZGVkIGZvciBJRS9FZGdlICovCiAgICAgIGJvcmRlci1ib3R0b206IDEwMHB4IHNvbGlkIHRyYW5zcGFyZW50OwogICAgICB6LWluZGV4OiAxMDAgIWltcG9ydGFudDsKICAgICAgb3BhY2l0eTogMC45OwogICAgfQoKICAgIFtwYXJ0fj0ncm93J11bZHJhZ3N0YXJ0XSBbcGFydH49J2NlbGwnXSB7CiAgICAgIGJvcmRlcjogbm9uZSAhaW1wb3J0YW50OwogICAgICBib3gtc2hhZG93OiBub25lICFpbXBvcnRhbnQ7CiAgICB9CgogICAgW3BhcnR+PSdyb3cnXVtkcmFnc3RhcnRdIFtwYXJ0fj0nY2VsbCddW2xhc3QtY29sdW1uXSB7CiAgICAgIGJvcmRlci1yYWRpdXM6IDAgdmFyKC0tbHVtby1ib3JkZXItcmFkaXVzLXMpIHZhcigtLWx1bW8tYm9yZGVyLXJhZGl1cy1zKSAwOwogICAgfQoKICAgIFtwYXJ0fj0ncm93J11bZHJhZ3N0YXJ0XSBbcGFydH49J2NlbGwnXVtmaXJzdC1jb2x1bW5dIHsKICAgICAgYm9yZGVyLXJhZGl1czogdmFyKC0tbHVtby1ib3JkZXItcmFkaXVzLXMpIDAgMCB2YXIoLS1sdW1vLWJvcmRlci1yYWRpdXMtcyk7CiAgICB9CgogICAgW2lvc10gW3BhcnR+PSdyb3cnXVtkcmFnc3RhcnRdIFtwYXJ0fj0nY2VsbCddIHsKICAgICAgYmFja2dyb3VuZDogdmFyKC0tbHVtby1wcmltYXJ5LWNvbG9yLTUwcGN0KTsKICAgIH0KCiAgICAjc2Nyb2xsZXI6bm90KFtpb3NdKSBbcGFydH49J3JvdyddW2RyYWdzdGFydF06bm90KFtkcmFnc3RhcnQ9JyddKTo6YWZ0ZXIgewogICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICBsZWZ0OiB2YXIoLS1fZ3JpZC1kcmFnLXN0YXJ0LXgpOwogICAgICB0b3A6IHZhcigtLV9ncmlkLWRyYWctc3RhcnQteSk7CiAgICAgIHotaW5kZXg6IDEwMDsKICAgICAgY29udGVudDogYXR0cihkcmFnc3RhcnQpOwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsKICAgICAgcGFkZGluZzogY2FsYyh2YXIoLS1sdW1vLXNwYWNlLXhzKSAqIDAuOCk7CiAgICAgIGNvbG9yOiB2YXIoLS1sdW1vLWVycm9yLWNvbnRyYXN0LWNvbG9yKTsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tbHVtby1lcnJvci1jb2xvcik7CiAgICAgIGJvcmRlci1yYWRpdXM6IHZhcigtLWx1bW8tYm9yZGVyLXJhZGl1cy1tKTsKICAgICAgZm9udC1mYW1pbHk6IHZhcigtLWx1bW8tZm9udC1mYW1pbHkpOwogICAgICBmb250LXNpemU6IHZhcigtLWx1bW8tZm9udC1zaXplLXh4cyk7CiAgICAgIGxpbmUtaGVpZ2h0OiAxOwogICAgICBmb250LXdlaWdodDogNTAwOwogICAgICB0ZXh0LXRyYW5zZm9ybTogaW5pdGlhbDsKICAgICAgbGV0dGVyLXNwYWNpbmc6IGluaXRpYWw7CiAgICAgIG1pbi13aWR0aDogY2FsYyh2YXIoLS1sdW1vLXNpemUtcykgKiAwLjcpOwogICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7CiAgICB9CgogICAgLyogSGVhZGVycyBhbmQgZm9vdGVycyAqLwoKICAgIFtwYXJ0fj0naGVhZGVyLWNlbGwnXSA6OnNsb3R0ZWQodmFhZGluLWdyaWQtY2VsbC1jb250ZW50KSwKICAgIFtwYXJ0fj0nZm9vdGVyLWNlbGwnXSA6OnNsb3R0ZWQodmFhZGluLWdyaWQtY2VsbC1jb250ZW50KSwKICAgIFtwYXJ0fj0ncmVvcmRlci1naG9zdCddIHsKICAgICAgZm9udC1zaXplOiB2YXIoLS1sdW1vLWZvbnQtc2l6ZS1zKTsKICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgIH0KCiAgICBbcGFydH49J2Zvb3Rlci1jZWxsJ10gOjpzbG90dGVkKHZhYWRpbi1ncmlkLWNlbGwtY29udGVudCkgewogICAgICBmb250LXdlaWdodDogNDAwOwogICAgfQoKICAgIFtwYXJ0PSdyb3cnXTpvbmx5LWNoaWxkIFtwYXJ0fj0naGVhZGVyLWNlbGwnXSB7CiAgICAgIG1pbi1oZWlnaHQ6IHZhcigtLWx1bW8tc2l6ZS14bCk7CiAgICB9CgogICAgLyogSGVhZGVyIGJvcmRlcnMgKi8KCiAgICAvKiBIaWRlIGZpcnN0IGhlYWRlciByb3cgdG9wIGJvcmRlciAqLwogICAgOmhvc3QoOm5vdChbdGhlbWV+PSduby1yb3ctYm9yZGVycyddKSkgW3BhcnQ9J3JvdyddOmZpcnN0LWNoaWxkIFtwYXJ0fj0naGVhZGVyLWNlbGwnXSB7CiAgICAgIGJvcmRlci10b3A6IDA7CiAgICB9CgogICAgW3BhcnQ9J3JvdyddOmxhc3QtY2hpbGQgW3BhcnR+PSdoZWFkZXItY2VsbCddIHsKICAgICAgYm9yZGVyLWJvdHRvbTogdmFyKC0tX2x1bW8tZ3JpZC1ib3JkZXItd2lkdGgpIHNvbGlkIHRyYW5zcGFyZW50OwogICAgfQoKICAgIDpob3N0KDpub3QoW3RoZW1lfj0nbm8tcm93LWJvcmRlcnMnXSkpIFtwYXJ0PSdyb3cnXTpsYXN0LWNoaWxkIFtwYXJ0fj0naGVhZGVyLWNlbGwnXSB7CiAgICAgIGJvcmRlci1ib3R0b20tY29sb3I6IHZhcigtLV9sdW1vLWdyaWQtc2Vjb25kYXJ5LWJvcmRlci1jb2xvcik7CiAgICB9CgogICAgLyogT3ZlcmZsb3cgdXNlcyBhIHN0cm9uZ2VyIGJvcmRlciBjb2xvciAqLwogICAgOmhvc3QoW292ZXJmbG93fj0ndG9wJ10pIFtwYXJ0PSdyb3cnXTpsYXN0LWNoaWxkIFtwYXJ0fj0naGVhZGVyLWNlbGwnXSB7CiAgICAgIGJvcmRlci1ib3R0b20tY29sb3I6IHZhcigtLV9sdW1vLWdyaWQtYm9yZGVyLWNvbG9yKTsKICAgIH0KCiAgICAvKiBGb290ZXIgYm9yZGVycyAqLwoKICAgIFtwYXJ0PSdyb3cnXTpmaXJzdC1jaGlsZCBbcGFydH49J2Zvb3Rlci1jZWxsJ10gewogICAgICBib3JkZXItdG9wOiB2YXIoLS1fbHVtby1ncmlkLWJvcmRlci13aWR0aCkgc29saWQgdHJhbnNwYXJlbnQ7CiAgICB9CgogICAgOmhvc3QoOm5vdChbdGhlbWV+PSduby1yb3ctYm9yZGVycyddKSkgW3BhcnQ9J3JvdyddOmZpcnN0LWNoaWxkIFtwYXJ0fj0nZm9vdGVyLWNlbGwnXSB7CiAgICAgIGJvcmRlci10b3AtY29sb3I6IHZhcigtLV9sdW1vLWdyaWQtc2Vjb25kYXJ5LWJvcmRlci1jb2xvcik7CiAgICB9CgogICAgLyogT3ZlcmZsb3cgdXNlcyBhIHN0cm9uZ2VyIGJvcmRlciBjb2xvciAqLwogICAgOmhvc3QoW292ZXJmbG93fj0nYm90dG9tJ10pIFtwYXJ0PSdyb3cnXTpmaXJzdC1jaGlsZCBbcGFydH49J2Zvb3Rlci1jZWxsJ10gewogICAgICBib3JkZXItdG9wLWNvbG9yOiB2YXIoLS1fbHVtby1ncmlkLWJvcmRlci1jb2xvcik7CiAgICB9CgogICAgLyogQ29sdW1uIHJlb3JkZXJpbmcgKi8KCiAgICA6aG9zdChbcmVvcmRlcmluZ10pIFtwYXJ0fj0nY2VsbCddIHsKICAgICAgYmFja2dyb3VuZDogbGluZWFyLWdyYWRpZW50KHZhcigtLWx1bW8tc2hhZGUtMjBwY3QpLCB2YXIoLS1sdW1vLXNoYWRlLTIwcGN0KSkgdmFyKC0tbHVtby1iYXNlLWNvbG9yKTsKICAgIH0KCiAgICA6aG9zdChbcmVvcmRlcmluZ10pIFtwYXJ0fj0nY2VsbCddW3Jlb3JkZXItc3RhdHVzPSdhbGxvd2VkJ10gewogICAgICBiYWNrZ3JvdW5kOiB2YXIoLS1sdW1vLWJhc2UtY29sb3IpOwogICAgfQoKICAgIDpob3N0KFtyZW9yZGVyaW5nXSkgW3BhcnR+PSdjZWxsJ11bcmVvcmRlci1zdGF0dXM9J2RyYWdnaW5nJ10gewogICAgICBiYWNrZ3JvdW5kOiBsaW5lYXItZ3JhZGllbnQodmFyKC0tbHVtby1jb250cmFzdC01cGN0KSwgdmFyKC0tbHVtby1jb250cmFzdC01cGN0KSkgdmFyKC0tbHVtby1iYXNlLWNvbG9yKTsKICAgIH0KCiAgICBbcGFydH49J3Jlb3JkZXItZ2hvc3QnXSB7CiAgICAgIG9wYWNpdHk6IDAuODU7CiAgICAgIGJveC1zaGFkb3c6IHZhcigtLWx1bW8tYm94LXNoYWRvdy1zKTsKICAgICAgLyogVE9ETyBVc2UgdGhlIHNhbWUgc3R5bGVzIGFzIGZvciB0aGUgY2VsbCBlbGVtZW50IChyZW9yZGVyLWdob3N0IGNvcGllcyBzdHlsZXMgZnJvbSB0aGUgY2VsbCBlbGVtZW50KSAqLwogICAgICBwYWRkaW5nOiB2YXIoLS1sdW1vLXNwYWNlLXMpIHZhcigtLWx1bW8tc3BhY2UtbSkgIWltcG9ydGFudDsKICAgIH0KCiAgICAvKiBDb2x1bW4gcmVzaXppbmcgKi8KCiAgICBbcGFydD0ncmVzaXplLWhhbmRsZSddIHsKICAgICAgd2lkdGg6IDNweDsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tbHVtby1wcmltYXJ5LWNvbG9yLTUwcGN0KTsKICAgICAgb3BhY2l0eTogMDsKICAgICAgdHJhbnNpdGlvbjogb3BhY2l0eSAwLjJzOwogICAgfQoKICAgIDpob3N0KDpub3QoW3Jlb3JkZXJpbmddKSkgKjpub3QoW2NvbHVtbi1yZXNpemluZ10pIFtwYXJ0fj0nY2VsbCddOmhvdmVyIFtwYXJ0PSdyZXNpemUtaGFuZGxlJ10sCiAgICBbcGFydD0ncmVzaXplLWhhbmRsZSddOmFjdGl2ZSB7CiAgICAgIG9wYWNpdHk6IDE7CiAgICAgIHRyYW5zaXRpb24tZGVsYXk6IDAuMTVzOwogICAgfQoKICAgIC8qIENvbHVtbiBib3JkZXJzICovCgogICAgOmhvc3QoW3RoZW1lfj0nY29sdW1uLWJvcmRlcnMnXSkgW3BhcnR+PSdjZWxsJ106bm90KFtsYXN0LWNvbHVtbl0pOm5vdChbcGFydH49J2RldGFpbHMtY2VsbCddKSB7CiAgICAgIGJvcmRlci1yaWdodDogdmFyKC0tX2x1bW8tZ3JpZC1ib3JkZXItd2lkdGgpIHNvbGlkIHZhcigtLV9sdW1vLWdyaWQtc2Vjb25kYXJ5LWJvcmRlci1jb2xvcik7CiAgICB9CgogICAgLyogRnJvemVuIGNvbHVtbnMgKi8KCiAgICBbbGFzdC1mcm96ZW5dIHsKICAgICAgYm9yZGVyLXJpZ2h0OiB2YXIoLS1fbHVtby1ncmlkLWJvcmRlci13aWR0aCkgc29saWQgdHJhbnNwYXJlbnQ7CiAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICB9CgogICAgOmhvc3QoW292ZXJmbG93fj0nbGVmdCddKSBbcGFydH49J2NlbGwnXVtsYXN0LWZyb3plbl06bm90KFtwYXJ0fj0nZGV0YWlscy1jZWxsJ10pIHsKICAgICAgYm9yZGVyLXJpZ2h0LWNvbG9yOiB2YXIoLS1fbHVtby1ncmlkLWJvcmRlci1jb2xvcik7CiAgICB9CgogICAgLyogUm93IHN0cmlwZXMgKi8KCiAgICA6aG9zdChbdGhlbWV+PSdyb3ctc3RyaXBlcyddKSBbcGFydH49J3JvdyddOm5vdChbb2RkXSkgW3BhcnR+PSdib2R5LWNlbGwnXSwKICAgIDpob3N0KFt0aGVtZX49J3Jvdy1zdHJpcGVzJ10pIFtwYXJ0fj0ncm93J106bm90KFtvZGRdKSBbcGFydH49J2RldGFpbHMtY2VsbCddIHsKICAgICAgYmFja2dyb3VuZC1pbWFnZTogbGluZWFyLWdyYWRpZW50KHZhcigtLWx1bW8tY29udHJhc3QtNXBjdCksIHZhcigtLWx1bW8tY29udHJhc3QtNXBjdCkpOwogICAgICBiYWNrZ3JvdW5kLXJlcGVhdDogcmVwZWF0LXg7CiAgICB9CgogICAgLyogU2VsZWN0ZWQgcm93ICovCgogICAgLyogUmFpc2UgdGhlIHNlbGVjdGVkIHJvd3MgYWJvdmUgdW5zZWxlY3RlZCByb3dzIChzbyB0aGF0IGJveC1zaGFkb3cgY2FuIGNvdmVyIHVuc2VsZWN0ZWQgcm93cykgKi8KICAgIDpob3N0KDpub3QoW3Jlb3JkZXJpbmddKSkgW3BhcnR+PSdyb3cnXVtzZWxlY3RlZF0gewogICAgICB6LWluZGV4OiAxOwogICAgfQoKICAgIDpob3N0KDpub3QoW3Jlb3JkZXJpbmddKSkgW3BhcnR+PSdyb3cnXVtzZWxlY3RlZF0gW3BhcnR+PSdib2R5LWNlbGwnXTpub3QoW3BhcnR+PSdkZXRhaWxzLWNlbGwnXSkgewogICAgICBiYWNrZ3JvdW5kLWltYWdlOiBsaW5lYXItZ3JhZGllbnQodmFyKC0tX2x1bW8tZ3JpZC1zZWxlY3RlZC1yb3ctY29sb3IpLCB2YXIoLS1fbHVtby1ncmlkLXNlbGVjdGVkLXJvdy1jb2xvcikpOwogICAgICBiYWNrZ3JvdW5kLXJlcGVhdDogcmVwZWF0OwogICAgfQoKICAgIC8qIENvdmVyIHRoZSBib3JkZXIgb2YgYW4gdW5zZWxlY3RlZCByb3cgKi8KICAgIDpob3N0KDpub3QoW3RoZW1lfj0nbm8tcm93LWJvcmRlcnMnXSkpIFtwYXJ0fj0ncm93J11bc2VsZWN0ZWRdIFtwYXJ0fj0nY2VsbCddOm5vdChbcGFydH49J2RldGFpbHMtY2VsbCddKSB7CiAgICAgIGJveC1zaGFkb3c6IDAgdmFyKC0tX2x1bW8tZ3JpZC1ib3JkZXItd2lkdGgpIDAgMCB2YXIoLS1fbHVtby1ncmlkLXNlbGVjdGVkLXJvdy1jb2xvcik7CiAgICB9CgogICAgLyogQ29tcGFjdCAqLwoKICAgIDpob3N0KFt0aGVtZX49J2NvbXBhY3QnXSkgW3BhcnQ9J3JvdyddOm9ubHktY2hpbGQgW3BhcnR+PSdoZWFkZXItY2VsbCddIHsKICAgICAgbWluLWhlaWdodDogdmFyKC0tbHVtby1zaXplLW0pOwogICAgfQoKICAgIDpob3N0KFt0aGVtZX49J2NvbXBhY3QnXSkgW3BhcnR+PSdjZWxsJ10gewogICAgICBtaW4taGVpZ2h0OiB2YXIoLS1sdW1vLXNpemUtcyk7CiAgICB9CgogICAgOmhvc3QoW3RoZW1lfj0nY29tcGFjdCddKSBbcGFydD0ncm93J11bZmlyc3RdIFtwYXJ0fj0nY2VsbCddOm5vdChbcGFydH49J2RldGFpbHMtY2VsbCddKSB7CiAgICAgIG1pbi1oZWlnaHQ6IGNhbGModmFyKC0tbHVtby1zaXplLXMpIC0gdmFyKC0tX2x1bW8tZ3JpZC1ib3JkZXItd2lkdGgpKTsKICAgIH0KCiAgICA6aG9zdChbdGhlbWV+PSdjb21wYWN0J10pIFtwYXJ0fj0nY2VsbCddIDo6c2xvdHRlZCh2YWFkaW4tZ3JpZC1jZWxsLWNvbnRlbnQpIHsKICAgICAgcGFkZGluZzogdmFyKC0tbHVtby1zcGFjZS14cykgdmFyKC0tbHVtby1zcGFjZS1zKTsKICAgIH0KCiAgICAvKiBXcmFwIGNlbGwgY29udGVudHMgKi8KCiAgICA6aG9zdChbdGhlbWV+PSd3cmFwLWNlbGwtY29udGVudCddKSBbcGFydH49J2NlbGwnXSA6OnNsb3R0ZWQodmFhZGluLWdyaWQtY2VsbC1jb250ZW50KSB7CiAgICAgIHdoaXRlLXNwYWNlOiBub3JtYWw7CiAgICB9CgogICAgLyogUlRMIHNwZWNpZmljIHN0eWxlcyAqLwoKICAgIDpob3N0KFtkaXI9J3J0bCddKSBbcGFydH49J3JvdyddW2RyYWdzdGFydF0gW3BhcnR+PSdjZWxsJ11bbGFzdC1jb2x1bW5dIHsKICAgICAgYm9yZGVyLXJhZGl1czogdmFyKC0tbHVtby1ib3JkZXItcmFkaXVzLXMpIDAgMCB2YXIoLS1sdW1vLWJvcmRlci1yYWRpdXMtcyk7CiAgICB9CgogICAgOmhvc3QoW2Rpcj0ncnRsJ10pIFtwYXJ0fj0ncm93J11bZHJhZ3N0YXJ0XSBbcGFydH49J2NlbGwnXVtmaXJzdC1jb2x1bW5dIHsKICAgICAgYm9yZGVyLXJhZGl1czogMCB2YXIoLS1sdW1vLWJvcmRlci1yYWRpdXMtcykgdmFyKC0tbHVtby1ib3JkZXItcmFkaXVzLXMpIDA7CiAgICB9CgogICAgOmhvc3QoW2Rpcj0ncnRsJ11bdGhlbWV+PSdjb2x1bW4tYm9yZGVycyddKSBbcGFydH49J2NlbGwnXTpub3QoW2xhc3QtY29sdW1uXSk6bm90KFtwYXJ0fj0nZGV0YWlscy1jZWxsJ10pIHsKICAgICAgYm9yZGVyLXJpZ2h0OiBub25lOwogICAgICBib3JkZXItbGVmdDogdmFyKC0tX2x1bW8tZ3JpZC1ib3JkZXItd2lkdGgpIHNvbGlkIHZhcigtLV9sdW1vLWdyaWQtc2Vjb25kYXJ5LWJvcmRlci1jb2xvcik7CiAgICB9CgogICAgOmhvc3QoW2Rpcj0ncnRsJ10pIFtsYXN0LWZyb3plbl0gewogICAgICBib3JkZXItcmlnaHQ6IG5vbmU7CiAgICAgIGJvcmRlci1sZWZ0OiB2YXIoLS1fbHVtby1ncmlkLWJvcmRlci13aWR0aCkgc29saWQgdHJhbnNwYXJlbnQ7CiAgICB9CgogICAgOmhvc3QoW2Rpcj0ncnRsJ11bb3ZlcmZsb3d+PSdyaWdodCddKSBbcGFydH49J2NlbGwnXVtsYXN0LWZyb3plbl06bm90KFtwYXJ0fj0nZGV0YWlscy1jZWxsJ10pIHsKICAgICAgYm9yZGVyLWxlZnQtY29sb3I6IHZhcigtLV9sdW1vLWdyaWQtYm9yZGVyLWNvbG9yKTsKICAgIH0KICBgLHttb2R1bGVJZDoibHVtby1ncmlkIn0pO2pjKCJ2YWFkaW4tY2hlY2tib3giLENpYAogICAgOmhvc3QoLnZhYWRpbi1ncmlkLXNlbGVjdC1hbGwtY2hlY2tib3gpIHsKICAgICAgZm9udC1zaXplOiB2YXIoLS1sdW1vLWZvbnQtc2l6ZS1tKTsKICAgIH0KICBgLHttb2R1bGVJZDoidmFhZGluLWdyaWQtc2VsZWN0LWFsbC1jaGVja2JveC1sdW1vIn0pO3ZhciBCY2U9bmF2aWdhdG9yLnVzZXJBZ2VudC5tYXRjaCgvaVAoPzpob25lfGFkOyg/OiBVOyk/IENQVSkgT1MgKFxkKykvKSxlaHI9QmNlJiZCY2VbMV0+PTgsSGNlPTMsVmNlPXU5KHtiZWhhdmlvcnM6W0pzLHk5XSxfcmF0aW86LjUsX3Njcm9sbGVyUGFkZGluZ1RvcDowLF9zY3JvbGxQb3NpdGlvbjowLF9waHlzaWNhbFNpemU6MCxfcGh5c2ljYWxBdmVyYWdlOjAsX3BoeXNpY2FsQXZlcmFnZUNvdW50OjAsX3BoeXNpY2FsVG9wOjAsX3ZpcnR1YWxDb3VudDowLF9lc3RTY3JvbGxIZWlnaHQ6MCxfc2Nyb2xsSGVpZ2h0OjAsX3ZpZXdwb3J0SGVpZ2h0OjAsX3ZpZXdwb3J0V2lkdGg6MCxfcGh5c2ljYWxJdGVtczpudWxsLF9waHlzaWNhbFNpemVzOm51bGwsX2ZpcnN0VmlzaWJsZUluZGV4VmFsOm51bGwsX2xhc3RWaXNpYmxlSW5kZXhWYWw6bnVsbCxfbWF4UGFnZXM6MixfZm9jdXNlZFZpcnR1YWxJbmRleDotMSxfdGVtcGxhdGVDb3N0OjAsZ2V0IF9waHlzaWNhbEJvdHRvbSgpe3JldHVybiB0aGlzLl9waHlzaWNhbFRvcCt0aGlzLl9waHlzaWNhbFNpemV9LGdldCBfc2Nyb2xsQm90dG9tKCl7cmV0dXJuIHRoaXMuX3Njcm9sbFBvc2l0aW9uK3RoaXMuX3ZpZXdwb3J0SGVpZ2h0fSxnZXQgX3ZpcnR1YWxFbmQoKXtyZXR1cm4gdGhpcy5fdmlydHVhbFN0YXJ0K3RoaXMuX3BoeXNpY2FsQ291bnQtMX0sZ2V0IF9oaWRkZW5Db250ZW50U2l6ZSgpe3JldHVybiB0aGlzLl9waHlzaWNhbFNpemUtdGhpcy5fdmlld3BvcnRIZWlnaHR9LGdldCBfbWF4U2Nyb2xsVG9wKCl7cmV0dXJuIHRoaXMuX2VzdFNjcm9sbEhlaWdodC10aGlzLl92aWV3cG9ydEhlaWdodCt0aGlzLl9zY3JvbGxPZmZzZXR9LGdldCBfbWF4VmlydHVhbFN0YXJ0KCl7cmV0dXJuIE1hdGgubWF4KDAsdGhpcy5fdmlydHVhbENvdW50LXRoaXMuX3BoeXNpY2FsQ291bnQpfSxzZXQgX3ZpcnR1YWxTdGFydChlKXtlPXRoaXMuX2NsYW1wKGUsMCx0aGlzLl9tYXhWaXJ0dWFsU3RhcnQpLHRoaXMuX3ZpcnR1YWxTdGFydFZhbD1lfSxnZXQgX3ZpcnR1YWxTdGFydCgpe3JldHVybiB0aGlzLl92aXJ0dWFsU3RhcnRWYWx8fDB9LHNldCBfcGh5c2ljYWxTdGFydChlKXtlPWUldGhpcy5fcGh5c2ljYWxDb3VudCxlPDAmJihlPXRoaXMuX3BoeXNpY2FsQ291bnQrZSksdGhpcy5fcGh5c2ljYWxTdGFydFZhbD1lfSxnZXQgX3BoeXNpY2FsU3RhcnQoKXtyZXR1cm4gdGhpcy5fcGh5c2ljYWxTdGFydFZhbHx8MH0sZ2V0IF9waHlzaWNhbEVuZCgpe3JldHVybih0aGlzLl9waHlzaWNhbFN0YXJ0K3RoaXMuX3BoeXNpY2FsQ291bnQtMSkldGhpcy5fcGh5c2ljYWxDb3VudH0sc2V0IF9waHlzaWNhbENvdW50KGUpe3RoaXMuX3BoeXNpY2FsQ291bnRWYWw9ZX0sZ2V0IF9waHlzaWNhbENvdW50KCl7cmV0dXJuIHRoaXMuX3BoeXNpY2FsQ291bnRWYWx8fDB9LGdldCBfb3B0UGh5c2ljYWxTaXplKCl7cmV0dXJuIHRoaXMuX3ZpZXdwb3J0SGVpZ2h0PT09MD8xLzA6dGhpcy5fdmlld3BvcnRIZWlnaHQqdGhpcy5fbWF4UGFnZXN9LGdldCBfaXNWaXNpYmxlKCl7cmV0dXJuIEJvb2xlYW4odGhpcy5vZmZzZXRXaWR0aHx8dGhpcy5vZmZzZXRIZWlnaHQpfSxnZXQgZmlyc3RWaXNpYmxlSW5kZXgoKXtsZXQgZT10aGlzLl9maXJzdFZpc2libGVJbmRleFZhbDtpZihlPT1udWxsKXtsZXQgdD10aGlzLl9waHlzaWNhbFRvcCt0aGlzLl9zY3JvbGxPZmZzZXQ7ZT10aGlzLl9pdGVyYXRlSXRlbXMoZnVuY3Rpb24ocixuKXtpZih0Kz10aGlzLl9waHlzaWNhbFNpemVzW3JdLHQ+dGhpcy5fc2Nyb2xsUG9zaXRpb24pcmV0dXJuIG59KXx8MCx0aGlzLl9maXJzdFZpc2libGVJbmRleFZhbD1lfXJldHVybiBlfSxnZXQgbGFzdFZpc2libGVJbmRleCgpe2xldCBlPXRoaXMuX2xhc3RWaXNpYmxlSW5kZXhWYWw7aWYoZT09bnVsbCl7bGV0IHQ9dGhpcy5fcGh5c2ljYWxUb3ArdGhpcy5fc2Nyb2xsT2Zmc2V0O3RoaXMuX2l0ZXJhdGVJdGVtcyhmdW5jdGlvbihyLG4pe3Q8dGhpcy5fc2Nyb2xsQm90dG9tJiYoZT1uKSx0Kz10aGlzLl9waHlzaWNhbFNpemVzW3JdfSksdGhpcy5fbGFzdFZpc2libGVJbmRleFZhbD1lfXJldHVybiBlfSxnZXQgX3Njcm9sbE9mZnNldCgpe3JldHVybiB0aGlzLl9zY3JvbGxlclBhZGRpbmdUb3B9LGF0dGFjaGVkOmZ1bmN0aW9uKCl7dGhpcy5fZGVib3VuY2UoIl9yZW5kZXIiLHRoaXMuX3JlbmRlcixOaSksdGhpcy5saXN0ZW4odGhpcywiaXJvbi1yZXNpemUiLCJfcmVzaXplSGFuZGxlciIpfSxkZXRhY2hlZDpmdW5jdGlvbigpe3RoaXMudW5saXN0ZW4odGhpcywiaXJvbi1yZXNpemUiLCJfcmVzaXplSGFuZGxlciIpfSx1cGRhdGVWaWV3cG9ydEJvdW5kYXJpZXM6ZnVuY3Rpb24oKXtsZXQgZT13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzKTt0aGlzLl9zY3JvbGxlclBhZGRpbmdUb3A9dGhpcy5zY3JvbGxUYXJnZXQ9PT10aGlzPzA6cGFyc2VJbnQoZVsicGFkZGluZy10b3AiXSwxMCksdGhpcy5faXNSVEw9Qm9vbGVhbihlLmRpcmVjdGlvbj09PSJydGwiKSx0aGlzLl92aWV3cG9ydFdpZHRoPXRoaXMuJC5pdGVtcy5vZmZzZXRXaWR0aCx0aGlzLl92aWV3cG9ydEhlaWdodD10aGlzLl9zY3JvbGxUYXJnZXRIZWlnaHR9LF9zY3JvbGxIYW5kbGVyOmZ1bmN0aW9uKCl7bGV0IGU9TWF0aC5tYXgoMCxNYXRoLm1pbih0aGlzLl9tYXhTY3JvbGxUb3AsdGhpcy5fc2Nyb2xsVG9wKSksdD1lLXRoaXMuX3Njcm9sbFBvc2l0aW9uLHI9dD49MDtpZih0aGlzLl9zY3JvbGxQb3NpdGlvbj1lLHRoaXMuX2ZpcnN0VmlzaWJsZUluZGV4VmFsPW51bGwsdGhpcy5fbGFzdFZpc2libGVJbmRleFZhbD1udWxsLE1hdGguYWJzKHQpPnRoaXMuX3BoeXNpY2FsU2l6ZSYmdGhpcy5fcGh5c2ljYWxTaXplPjApe3Q9dC10aGlzLl9zY3JvbGxPZmZzZXQ7bGV0IG49TWF0aC5yb3VuZCh0L3RoaXMuX3BoeXNpY2FsQXZlcmFnZSk7dGhpcy5fdmlydHVhbFN0YXJ0PXRoaXMuX3ZpcnR1YWxTdGFydCtuLHRoaXMuX3BoeXNpY2FsU3RhcnQ9dGhpcy5fcGh5c2ljYWxTdGFydCtuLHRoaXMuX3BoeXNpY2FsVG9wPU1hdGguZmxvb3IodGhpcy5fdmlydHVhbFN0YXJ0KSp0aGlzLl9waHlzaWNhbEF2ZXJhZ2UsdGhpcy5fdXBkYXRlKCl9ZWxzZSBpZih0aGlzLl9waHlzaWNhbENvdW50PjApe2xldHtwaHlzaWNhbFRvcDpuLGluZGV4ZXM6aX09dGhpcy5fZ2V0UmV1c2FibGVzKHIpO3I/KHRoaXMuX3BoeXNpY2FsVG9wPW4sdGhpcy5fdmlydHVhbFN0YXJ0PXRoaXMuX3ZpcnR1YWxTdGFydCtpLmxlbmd0aCx0aGlzLl9waHlzaWNhbFN0YXJ0PXRoaXMuX3BoeXNpY2FsU3RhcnQraS5sZW5ndGgpOih0aGlzLl92aXJ0dWFsU3RhcnQ9dGhpcy5fdmlydHVhbFN0YXJ0LWkubGVuZ3RoLHRoaXMuX3BoeXNpY2FsU3RhcnQ9dGhpcy5fcGh5c2ljYWxTdGFydC1pLmxlbmd0aCksdGhpcy5fdXBkYXRlKGkscj9udWxsOmkpLHRoaXMuX2RlYm91bmNlKCJfaW5jcmVhc2VQb29sSWZOZWVkZWQiLHRoaXMuX2luY3JlYXNlUG9vbElmTmVlZGVkLmJpbmQodGhpcywwKSxjaSl9fSxfZ2V0UmV1c2FibGVzOmZ1bmN0aW9uKGUpe2xldCB0LHIsbixpPVtdLG89dGhpcy5faGlkZGVuQ29udGVudFNpemUqdGhpcy5fcmF0aW8sYT10aGlzLl92aXJ0dWFsU3RhcnQscz10aGlzLl92aXJ0dWFsRW5kLGw9dGhpcy5fcGh5c2ljYWxDb3VudCxjPXRoaXMuX3BoeXNpY2FsVG9wK3RoaXMuX3Njcm9sbE9mZnNldCx1PXRoaXMuX3BoeXNpY2FsQm90dG9tK3RoaXMuX3Njcm9sbE9mZnNldCxoPXRoaXMuX3Njcm9sbFRvcCxmPXRoaXMuX3Njcm9sbEJvdHRvbTtmb3IoZT8odD10aGlzLl9waHlzaWNhbFN0YXJ0LHI9aC1jKToodD10aGlzLl9waHlzaWNhbEVuZCxyPXUtZik7bj10aGlzLl9waHlzaWNhbFNpemVzW3RdLHI9ci1uLCEoaS5sZW5ndGg+PWx8fHI8PW8pOylpZihlKXtpZihzK2kubGVuZ3RoKzE+PXRoaXMuX3ZpcnR1YWxDb3VudHx8YytuPj1oLXRoaXMuX3Njcm9sbE9mZnNldClicmVhaztpLnB1c2godCksYz1jK24sdD0odCsxKSVsfWVsc2V7aWYoYS1pLmxlbmd0aDw9MHx8Yyt0aGlzLl9waHlzaWNhbFNpemUtbjw9ZilicmVhaztpLnB1c2godCksYz1jLW4sdD10PT09MD9sLTE6dC0xfXJldHVybntpbmRleGVzOmkscGh5c2ljYWxUb3A6Yy10aGlzLl9zY3JvbGxPZmZzZXR9fSxfdXBkYXRlOmZ1bmN0aW9uKGUsdCl7aWYoIShlJiZlLmxlbmd0aD09PTB8fHRoaXMuX3BoeXNpY2FsQ291bnQ9PT0wKSl7aWYodGhpcy5fYXNzaWduTW9kZWxzKGUpLHRoaXMuX3VwZGF0ZU1ldHJpY3MoZSksdClmb3IoO3QubGVuZ3RoOyl7bGV0IHI9dC5wb3AoKTt0aGlzLl9waHlzaWNhbFRvcC09dGhpcy5fcGh5c2ljYWxTaXplc1tyXX10aGlzLl9wb3NpdGlvbkl0ZW1zKCksdGhpcy5fdXBkYXRlU2Nyb2xsZXJTaXplKCl9fSxfaXNDbGllbnRGdWxsOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3Njcm9sbEJvdHRvbSE9MCYmdGhpcy5fcGh5c2ljYWxCb3R0b20tMT49dGhpcy5fc2Nyb2xsQm90dG9tJiZ0aGlzLl9waHlzaWNhbFRvcDw9dGhpcy5fc2Nyb2xsUG9zaXRpb259LF9pbmNyZWFzZVBvb2xJZk5lZWRlZDpmdW5jdGlvbihlKXtsZXQgcj10aGlzLl9jbGFtcCh0aGlzLl9waHlzaWNhbENvdW50K2UsSGNlLHRoaXMuX3ZpcnR1YWxDb3VudC10aGlzLl92aXJ0dWFsU3RhcnQpLXRoaXMuX3BoeXNpY2FsQ291bnQsbj1NYXRoLnJvdW5kKHRoaXMuX3BoeXNpY2FsQ291bnQqLjUpO2lmKCEocjwwKSl7aWYocj4wKXtsZXQgaT13aW5kb3cucGVyZm9ybWFuY2Uubm93KCk7W10ucHVzaC5hcHBseSh0aGlzLl9waHlzaWNhbEl0ZW1zLHRoaXMuX2NyZWF0ZVBvb2wocikpO2ZvcihsZXQgbz0wO288cjtvKyspdGhpcy5fcGh5c2ljYWxTaXplcy5wdXNoKDApO3RoaXMuX3BoeXNpY2FsQ291bnQ9dGhpcy5fcGh5c2ljYWxDb3VudCtyLHRoaXMuX3BoeXNpY2FsU3RhcnQ+dGhpcy5fcGh5c2ljYWxFbmQmJnRoaXMuX2lzSW5kZXhSZW5kZXJlZCh0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4KSYmdGhpcy5fZ2V0UGh5c2ljYWxJbmRleCh0aGlzLl9mb2N1c2VkVmlydHVhbEluZGV4KTx0aGlzLl9waHlzaWNhbEVuZCYmKHRoaXMuX3BoeXNpY2FsU3RhcnQ9dGhpcy5fcGh5c2ljYWxTdGFydCtyKSx0aGlzLl91cGRhdGUoKSx0aGlzLl90ZW1wbGF0ZUNvc3Q9KHdpbmRvdy5wZXJmb3JtYW5jZS5ub3coKS1pKS9yLG49TWF0aC5yb3VuZCh0aGlzLl9waHlzaWNhbENvdW50Ki41KX10aGlzLl92aXJ0dWFsRW5kPj10aGlzLl92aXJ0dWFsQ291bnQtMXx8bj09PTB8fCh0aGlzLl9pc0NsaWVudEZ1bGwoKT90aGlzLl9waHlzaWNhbFNpemU8dGhpcy5fb3B0UGh5c2ljYWxTaXplJiZ0aGlzLl9kZWJvdW5jZSgiX2luY3JlYXNlUG9vbElmTmVlZGVkIix0aGlzLl9pbmNyZWFzZVBvb2xJZk5lZWRlZC5iaW5kKHRoaXMsdGhpcy5fY2xhbXAoTWF0aC5yb3VuZCg1MC90aGlzLl90ZW1wbGF0ZUNvc3QpLDEsbikpLGt4KTp0aGlzLl9kZWJvdW5jZSgiX2luY3JlYXNlUG9vbElmTmVlZGVkIix0aGlzLl9pbmNyZWFzZVBvb2xJZk5lZWRlZC5iaW5kKHRoaXMsbiksY2kpKX19LF9yZW5kZXI6ZnVuY3Rpb24oKXtpZighKCF0aGlzLmlzQXR0YWNoZWR8fCF0aGlzLl9pc1Zpc2libGUpKWlmKHRoaXMuX3BoeXNpY2FsQ291bnQhPT0wKXtsZXR7cGh5c2ljYWxUb3A6ZSxpbmRleGVzOnR9PXRoaXMuX2dldFJldXNhYmxlcyghMCk7dGhpcy5fcGh5c2ljYWxUb3A9ZSx0aGlzLl92aXJ0dWFsU3RhcnQ9dGhpcy5fdmlydHVhbFN0YXJ0K3QubGVuZ3RoLHRoaXMuX3BoeXNpY2FsU3RhcnQ9dGhpcy5fcGh5c2ljYWxTdGFydCt0Lmxlbmd0aCx0aGlzLl91cGRhdGUodCksdGhpcy5fdXBkYXRlKCksdGhpcy5faW5jcmVhc2VQb29sSWZOZWVkZWQoMCl9ZWxzZSB0aGlzLl92aXJ0dWFsQ291bnQ+MCYmKHRoaXMudXBkYXRlVmlld3BvcnRCb3VuZGFyaWVzKCksdGhpcy5faW5jcmVhc2VQb29sSWZOZWVkZWQoSGNlKSl9LF9pdGVtc0NoYW5nZWQ6ZnVuY3Rpb24oZSl7ZS5wYXRoPT09Iml0ZW1zIiYmKHRoaXMuX3ZpcnR1YWxTdGFydD0wLHRoaXMuX3BoeXNpY2FsVG9wPTAsdGhpcy5fdmlydHVhbENvdW50PXRoaXMuaXRlbXM/dGhpcy5pdGVtcy5sZW5ndGg6MCx0aGlzLl9waHlzaWNhbEluZGV4Rm9yS2V5PXt9LHRoaXMuX2ZpcnN0VmlzaWJsZUluZGV4VmFsPW51bGwsdGhpcy5fbGFzdFZpc2libGVJbmRleFZhbD1udWxsLHRoaXMuX3BoeXNpY2FsQ291bnQ9dGhpcy5fcGh5c2ljYWxDb3VudHx8MCx0aGlzLl9waHlzaWNhbEl0ZW1zPXRoaXMuX3BoeXNpY2FsSXRlbXN8fFtdLHRoaXMuX3BoeXNpY2FsU2l6ZXM9dGhpcy5fcGh5c2ljYWxTaXplc3x8W10sdGhpcy5fcGh5c2ljYWxTdGFydD0wLHRoaXMuX3Njcm9sbFRvcD50aGlzLl9zY3JvbGxPZmZzZXQmJnRoaXMuX3Jlc2V0U2Nyb2xsUG9zaXRpb24oMCksdGhpcy5fZGVib3VuY2UoIl9yZW5kZXIiLHRoaXMuX3JlbmRlcixOaSkpfSxfaXRlcmF0ZUl0ZW1zOmZ1bmN0aW9uKGUsdCl7bGV0IHIsbixpLG87aWYoYXJndW1lbnRzLmxlbmd0aD09PTImJnQpe2ZvcihvPTA7bzx0Lmxlbmd0aDtvKyspaWYocj10W29dLG49dGhpcy5fY29tcHV0ZVZpZHgociksKGk9ZS5jYWxsKHRoaXMscixuKSkhPW51bGwpcmV0dXJuIGl9ZWxzZXtmb3Iocj10aGlzLl9waHlzaWNhbFN0YXJ0LG49dGhpcy5fdmlydHVhbFN0YXJ0O3I8dGhpcy5fcGh5c2ljYWxDb3VudDtyKyssbisrKWlmKChpPWUuY2FsbCh0aGlzLHIsbikpIT1udWxsKXJldHVybiBpO2ZvcihyPTA7cjx0aGlzLl9waHlzaWNhbFN0YXJ0O3IrKyxuKyspaWYoKGk9ZS5jYWxsKHRoaXMscixuKSkhPW51bGwpcmV0dXJuIGl9fSxfY29tcHV0ZVZpZHg6ZnVuY3Rpb24oZSl7cmV0dXJuIGU+PXRoaXMuX3BoeXNpY2FsU3RhcnQ/dGhpcy5fdmlydHVhbFN0YXJ0KyhlLXRoaXMuX3BoeXNpY2FsU3RhcnQpOnRoaXMuX3ZpcnR1YWxTdGFydCsodGhpcy5fcGh5c2ljYWxDb3VudC10aGlzLl9waHlzaWNhbFN0YXJ0KStlfSxfdXBkYXRlTWV0cmljczpmdW5jdGlvbihlKXtpZighdGhpcy5faXNWaXNpYmxlKXJldHVybjt1aSgpO2xldCB0PTAscj0wLG49dGhpcy5fcGh5c2ljYWxBdmVyYWdlQ291bnQsaT10aGlzLl9waHlzaWNhbEF2ZXJhZ2U7dGhpcy5faXRlcmF0ZUl0ZW1zKGZ1bmN0aW9uKG8pe3IrPXRoaXMuX3BoeXNpY2FsU2l6ZXNbb10sdGhpcy5fcGh5c2ljYWxTaXplc1tvXT10aGlzLl9waHlzaWNhbEl0ZW1zW29dLm9mZnNldEhlaWdodCx0Kz10aGlzLl9waHlzaWNhbFNpemVzW29dLHRoaXMuX3BoeXNpY2FsQXZlcmFnZUNvdW50Kz10aGlzLl9waHlzaWNhbFNpemVzW29dPzE6MH0sZSksdGhpcy5fcGh5c2ljYWxTaXplPXRoaXMuX3BoeXNpY2FsU2l6ZSt0LXIsdGhpcy5fcGh5c2ljYWxBdmVyYWdlQ291bnQhPT1uJiYodGhpcy5fcGh5c2ljYWxBdmVyYWdlPU1hdGgucm91bmQoKGkqbit0KS90aGlzLl9waHlzaWNhbEF2ZXJhZ2VDb3VudCkpfSxfcG9zaXRpb25JdGVtczpmdW5jdGlvbigpe3RoaXMuX2FkanVzdFNjcm9sbFBvc2l0aW9uKCk7bGV0IGU9dGhpcy5fcGh5c2ljYWxUb3A7dGhpcy5faXRlcmF0ZUl0ZW1zKGZ1bmN0aW9uKHQpe3RoaXMudHJhbnNsYXRlM2QoMCxlKyJweCIsMCx0aGlzLl9waHlzaWNhbEl0ZW1zW3RdKSxlKz10aGlzLl9waHlzaWNhbFNpemVzW3RdfSl9LF9hZGp1c3RTY3JvbGxQb3NpdGlvbjpmdW5jdGlvbigpe2xldCBlPXRoaXMuX3ZpcnR1YWxTdGFydD09PTA/dGhpcy5fcGh5c2ljYWxUb3A6TWF0aC5taW4odGhpcy5fc2Nyb2xsUG9zaXRpb24rdGhpcy5fcGh5c2ljYWxUb3AsMCk7aWYoZSE9PTApe3RoaXMuX3BoeXNpY2FsVG9wPXRoaXMuX3BoeXNpY2FsVG9wLWU7bGV0IHQ9dGhpcy5fc2Nyb2xsVG9wOyFlaHImJnQ+MCYmdGhpcy5fcmVzZXRTY3JvbGxQb3NpdGlvbih0LWUpfX0sX3Jlc2V0U2Nyb2xsUG9zaXRpb246ZnVuY3Rpb24oZSl7dGhpcy5zY3JvbGxUYXJnZXQmJmU+PTAmJih0aGlzLl9zY3JvbGxUb3A9ZSx0aGlzLl9zY3JvbGxQb3NpdGlvbj10aGlzLl9zY3JvbGxUb3ApfSxfdXBkYXRlU2Nyb2xsZXJTaXplOmZ1bmN0aW9uKGUpe3RoaXMuX2VzdFNjcm9sbEhlaWdodD10aGlzLl9waHlzaWNhbEJvdHRvbStNYXRoLm1heCh0aGlzLl92aXJ0dWFsQ291bnQtdGhpcy5fcGh5c2ljYWxDb3VudC10aGlzLl92aXJ0dWFsU3RhcnQsMCkqdGhpcy5fcGh5c2ljYWxBdmVyYWdlLGU9ZXx8dGhpcy5fc2Nyb2xsSGVpZ2h0PT09MCxlPWV8fHRoaXMuX3Njcm9sbFBvc2l0aW9uPj10aGlzLl9lc3RTY3JvbGxIZWlnaHQtdGhpcy5fcGh5c2ljYWxTaXplLChlfHxNYXRoLmFicyh0aGlzLl9lc3RTY3JvbGxIZWlnaHQtdGhpcy5fc2Nyb2xsSGVpZ2h0KT49dGhpcy5fdmlld3BvcnRIZWlnaHQpJiYodGhpcy4kLml0ZW1zLnN0eWxlLmhlaWdodD10aGlzLl9lc3RTY3JvbGxIZWlnaHQrInB4Iix0aGlzLl9zY3JvbGxIZWlnaHQ9dGhpcy5fZXN0U2Nyb2xsSGVpZ2h0KX0sc2Nyb2xsVG9JbmRleDpmdW5jdGlvbihlKXtpZih0eXBlb2YgZSE9Im51bWJlciJ8fGU8MHx8ZT50aGlzLml0ZW1zLmxlbmd0aC0xfHwodWkoKSx0aGlzLl9waHlzaWNhbENvdW50PT09MCkpcmV0dXJuO2U9dGhpcy5fY2xhbXAoZSwwLHRoaXMuX3ZpcnR1YWxDb3VudC0xKSwoIXRoaXMuX2lzSW5kZXhSZW5kZXJlZChlKXx8ZT49dGhpcy5fbWF4VmlydHVhbFN0YXJ0KSYmKHRoaXMuX3ZpcnR1YWxTdGFydD1lLTEpLHRoaXMuX2Fzc2lnbk1vZGVscygpLHRoaXMuX3VwZGF0ZU1ldHJpY3MoKSx0aGlzLl9waHlzaWNhbFRvcD1NYXRoLmZsb29yKHRoaXMuX3ZpcnR1YWxTdGFydCkqdGhpcy5fcGh5c2ljYWxBdmVyYWdlO2xldCB0PXRoaXMuX3BoeXNpY2FsU3RhcnQscj10aGlzLl92aXJ0dWFsU3RhcnQsbj0wLGk9dGhpcy5faGlkZGVuQ29udGVudFNpemU7Zm9yKDtyPGUmJm48PWk7KW49bit0aGlzLl9waHlzaWNhbFNpemVzW3RdLHQ9KHQrMSkldGhpcy5fcGh5c2ljYWxDb3VudCxyKys7dGhpcy5fdXBkYXRlU2Nyb2xsZXJTaXplKCEwKSx0aGlzLl9wb3NpdGlvbkl0ZW1zKCksdGhpcy5fcmVzZXRTY3JvbGxQb3NpdGlvbih0aGlzLl9waHlzaWNhbFRvcCt0aGlzLl9zY3JvbGxPZmZzZXQrbiksdGhpcy5faW5jcmVhc2VQb29sSWZOZWVkZWQoMCksdGhpcy5fZmlyc3RWaXNpYmxlSW5kZXhWYWw9bnVsbCx0aGlzLl9sYXN0VmlzaWJsZUluZGV4VmFsPW51bGx9LF9yZXNldEF2ZXJhZ2U6ZnVuY3Rpb24oKXt0aGlzLl9waHlzaWNhbEF2ZXJhZ2U9MCx0aGlzLl9waHlzaWNhbEF2ZXJhZ2VDb3VudD0wfSxfcmVzaXplSGFuZGxlcjpmdW5jdGlvbigpe3RoaXMuX2RlYm91bmNlKCJfcmVuZGVyIixmdW5jdGlvbigpe3RoaXMuX2ZpcnN0VmlzaWJsZUluZGV4VmFsPW51bGwsdGhpcy5fbGFzdFZpc2libGVJbmRleFZhbD1udWxsLHRoaXMudXBkYXRlVmlld3BvcnRCb3VuZGFyaWVzKCksdGhpcy5faXNWaXNpYmxlPyh0aGlzLnRvZ2dsZVNjcm9sbExpc3RlbmVyKCEwKSx0aGlzLl9yZXNldEF2ZXJhZ2UoKSx0aGlzLl9yZW5kZXIoKSk6dGhpcy50b2dnbGVTY3JvbGxMaXN0ZW5lcighMSl9LE5pKX0sX2lzSW5kZXhSZW5kZXJlZDpmdW5jdGlvbihlKXtyZXR1cm4gZT49dGhpcy5fdmlydHVhbFN0YXJ0JiZlPD10aGlzLl92aXJ0dWFsRW5kfSxfZ2V0UGh5c2ljYWxJbmRleDpmdW5jdGlvbihlKXtyZXR1cm4odGhpcy5fcGh5c2ljYWxTdGFydCsoZS10aGlzLl92aXJ0dWFsU3RhcnQpKSV0aGlzLl9waHlzaWNhbENvdW50fSxfY2xhbXA6ZnVuY3Rpb24oZSx0LHIpe3JldHVybiBNYXRoLm1pbihyLE1hdGgubWF4KHQsZSkpfSxfZGVib3VuY2U6ZnVuY3Rpb24oZSx0LHIpe3RoaXMuX2RlYm91bmNlcnM9dGhpcy5fZGVib3VuY2Vyc3x8e30sdGhpcy5fZGVib3VuY2Vyc1tlXT1zci5kZWJvdW5jZSh0aGlzLl9kZWJvdW5jZXJzW2VdLHIsdC5iaW5kKHRoaXMpKSxKbCh0aGlzLl9kZWJvdW5jZXJzW2VdKX19KTt2YXIgUUg9Y2xhc3MgZXh0ZW5kcyBWY2V7c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue3NpemU6e3R5cGU6TnVtYmVyLG5vdGlmeTohMH0sX3ZpZHhPZmZzZXQ6e3R5cGU6TnVtYmVyLHZhbHVlOjB9fX1zdGF0aWMgZ2V0IG9ic2VydmVycygpe3JldHVyblsiX2VmZmVjdGl2ZVNpemVDaGFuZ2VkKF9lZmZlY3RpdmVTaXplKSJdfWNvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLl9zY3JvbGxIYW5kbGVyKCl9X3VwZGF0ZVNjcm9sbGVySXRlbSgpe31fYWZ0ZXJTY3JvbGwoKXt9X2dldFJvd1RhcmdldCgpe31fY3JlYXRlU2Nyb2xsZXJSb3dzKCl7fV9jYW5Qb3B1bGF0ZSgpe31zY3JvbGxUb0luZGV4KHQpe3RoaXMuX3dhcm5Qcml2YXRlQVBJQWNjZXNzKCJzY3JvbGxUb0luZGV4IiksdGhpcy5fc2Nyb2xsaW5nVG9JbmRleD0hMCx0PU1hdGgubWluKE1hdGgubWF4KHQsMCksdGhpcy5fZWZmZWN0aXZlU2l6ZS0xKSx0aGlzLiQudGFibGUuc2Nyb2xsVG9wPXQvdGhpcy5fZWZmZWN0aXZlU2l6ZSoodGhpcy4kLnRhYmxlLnNjcm9sbEhlaWdodC10aGlzLiQudGFibGUub2Zmc2V0SGVpZ2h0KSx0aGlzLl9zY3JvbGxIYW5kbGVyKCksdGhpcy5fYWNjZXNzSXJvbkxpc3RBUEkoKCk9PnRoaXMuX21heFNjcm9sbFRvcCkmJnRoaXMuX3ZpcnR1YWxDb3VudDx0aGlzLl9lZmZlY3RpdmVTaXplJiZ0aGlzLl9hZGp1c3RWaXJ0dWFsSW5kZXhPZmZzZXQoMWU2KSx0aGlzLl9hY2Nlc3NJcm9uTGlzdEFQSSgoKT0+c3VwZXIuc2Nyb2xsVG9JbmRleCh0LXRoaXMuX3ZpZHhPZmZzZXQpKSx0aGlzLl9zY3JvbGxIYW5kbGVyKCk7bGV0IHI9QXJyYXkuZnJvbSh0aGlzLiQuaXRlbXMuY2hpbGRyZW4pLmZpbHRlcihuPT5uLmluZGV4PT09dClbMF07aWYocil7bGV0IG49ci5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AtdGhpcy4kLmhlYWRlci5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS5ib3R0b207TWF0aC5hYnMobik+MSYmKHRoaXMuJC50YWJsZS5zY3JvbGxUb3ArPW4sdGhpcy5fc2Nyb2xsSGFuZGxlcigpKX10aGlzLl9zY3JvbGxpbmdUb0luZGV4PSExfV9lZmZlY3RpdmVTaXplQ2hhbmdlZCh0KXtsZXQgcixuPTA7dGhpcy5faXRlcmF0ZUl0ZW1zKChpLG8pPT57aWYobz09PXRoaXMuX2ZpcnN0VmlzaWJsZUluZGV4KXtsZXQgYT10aGlzLl9waHlzaWNhbEl0ZW1zW2ldO3I9YS5pbmRleCxuPWEuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wfX0pLHRoaXMuaXRlbXMmJnQ8dGhpcy5pdGVtcy5sZW5ndGgmJih0aGlzLl9zY3JvbGxUb3A9MCksQXJyYXkuaXNBcnJheSh0aGlzLml0ZW1zKXx8KHRoaXMuaXRlbXM9e2xlbmd0aDpNYXRoLm1pbih0LDFlNSl9KSx0aGlzLl9hY2Nlc3NJcm9uTGlzdEFQSSgoKT0+c3VwZXIuX2l0ZW1zQ2hhbmdlZCh7cGF0aDoiaXRlbXMifSkpLHRoaXMuX3ZpcnR1YWxDb3VudD1NYXRoLm1pbih0aGlzLml0ZW1zLmxlbmd0aCx0KXx8MCx0aGlzLl9zY3JvbGxUb3A9PT0wJiYodGhpcy5fYWNjZXNzSXJvbkxpc3RBUEkoKCk9PnRoaXMuX3Njcm9sbFRvSW5kZXgoTWF0aC5taW4odC0xLHIpKSksdGhpcy5faXRlcmF0ZUl0ZW1zKGk9PntsZXQgbz10aGlzLl9waHlzaWNhbEl0ZW1zW2ldO2lmKG8uaW5kZXg9PT1yJiYodGhpcy4kLnRhYmxlLnNjcm9sbFRvcCs9TWF0aC5yb3VuZChvLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcC1uKSksby5pbmRleD09PXRoaXMuX2ZvY3VzZWRJdGVtSW5kZXgmJnRoaXMuX2l0ZW1zRm9jdXNhYmxlJiZ0aGlzLiQuaXRlbXMuY29udGFpbnModGhpcy5zaGFkb3dSb290LmFjdGl2ZUVsZW1lbnQpKXtsZXQgYT1BcnJheS5mcm9tKHRoaXMuX2l0ZW1zRm9jdXNhYmxlLnBhcmVudEVsZW1lbnQuY2hpbGRyZW4pLmluZGV4T2YodGhpcy5faXRlbXNGb2N1c2FibGUpO28uY2hpbGRyZW5bYV0uZm9jdXMoKX19KSksdGhpcy5fYXNzaWduTW9kZWxzKCkscmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT50aGlzLl91cGRhdGUoKSksdGhpcy5fX3VwZGF0ZUZvb3RlclBvc2l0aW9uaW5nKCl9X3Bvc2l0aW9uSXRlbXMoKXt0aGlzLl9hZGp1c3RTY3JvbGxQb3NpdGlvbigpO2xldCB0O2lzTmFOKHRoaXMuX3BoeXNpY2FsVG9wKSYmKHQ9ITAsdGhpcy5fcGh5c2ljYWxUb3A9MCk7bGV0IHI9dGhpcy5fcGh5c2ljYWxUb3A7dGhpcy5faXRlcmF0ZUl0ZW1zKG49Pnt0aGlzLl9waHlzaWNhbEl0ZW1zW25dLnN0eWxlLnRyYW5zZm9ybT1gdHJhbnNsYXRlWSgke3J9cHgpYCxyKz10aGlzLl9waHlzaWNhbFNpemVzW25dfSksdCYmdGhpcy5fc2Nyb2xsVG9JbmRleCgwKX1faW5jcmVhc2VQb29sSWZOZWVkZWQodCl7dD09PTAmJnRoaXMuX3Njcm9sbGluZ1RvSW5kZXh8fCF0aGlzLl9jYW5Qb3B1bGF0ZSgpfHwhdGhpcy5fZWZmZWN0aXZlU2l6ZXx8KHRoaXMuX2luaXRpYWxQb29sQ3JlYXRlZD90aGlzLl9vcHRQaHlzaWNhbFNpemUhPT0xLzAmJih0aGlzLl9kZWJvdW5jZUluY3JlYXNlUG9vbD1zci5kZWJvdW5jZSh0aGlzLl9kZWJvdW5jZUluY3JlYXNlUG9vbCxOaSwoKT0+e3RoaXMuX3VwZGF0ZU1ldHJpY3MoKTtsZXQgcj10aGlzLl9vcHRQaHlzaWNhbFNpemUtdGhpcy5fcGh5c2ljYWxTaXplLG49TWF0aC5jZWlsKHIvdGhpcy5fcGh5c2ljYWxBdmVyYWdlKTt0aGlzLl9waHlzaWNhbENvdW50K24+dGhpcy5fZWZmZWN0aXZlU2l6ZSYmKG49TWF0aC5tYXgoMCx0aGlzLl9lZmZlY3RpdmVTaXplLXRoaXMuX3BoeXNpY2FsQ291bnQpKSx0aGlzLl9waHlzaWNhbFNpemUmJm4+MCYmdGhpcy5fb3B0UGh5c2ljYWxTaXplIT09MS8wJiYoc3VwZXIuX2luY3JlYXNlUG9vbElmTmVlZGVkKG4pLHRoaXMuX19yZW9yZGVyQ2hpbGROb2RlcygpKX0pKToodGhpcy5faW5pdGlhbFBvb2xDcmVhdGVkPSEwLHN1cGVyLl9pbmNyZWFzZVBvb2xJZk5lZWRlZCgyNSkpKX1fX3Jlb3JkZXJDaGlsZE5vZGVzKCl7bGV0IHQ9QXJyYXkuZnJvbSh0aGlzLiQuaXRlbXMuY2hpbGROb2Rlcyk7ISF0LnJlZHVjZSgobixpLG8sYSk9PntpZihvPT09MHx8YVtvLTFdLmluZGV4PT09aS5pbmRleC0xKXJldHVybiBufSwhMCl8fHQuc29ydCgobixpKT0+bi5pbmRleC1pLmluZGV4KS5mb3JFYWNoKG49PnRoaXMuJC5pdGVtcy5hcHBlbmRDaGlsZChuKSl9X2NyZWF0ZVBvb2wodCl7bGV0IHI9ZG9jdW1lbnQuY3JlYXRlRG9jdW1lbnRGcmFnbWVudCgpLG49dGhpcy5fY3JlYXRlU2Nyb2xsZXJSb3dzKHQpO24uZm9yRWFjaChvPT5yLmFwcGVuZENoaWxkKG8pKSx0aGlzLl9nZXRSb3dUYXJnZXQoKS5hcHBlbmRDaGlsZChyKTtsZXQgaT10aGlzLnF1ZXJ5U2VsZWN0b3IoIltzbG90XSIpO2lmKGkpe2xldCBvPWkuZ2V0QXR0cmlidXRlKCJzbG90Iik7aS5zZXRBdHRyaWJ1dGUoInNsb3QiLCJmb28tYmFyIiksaS5zZXRBdHRyaWJ1dGUoInNsb3QiLG8pfXJldHVybiBUbSh0aGlzLCgpPT50aGlzLm5vdGlmeVJlc2l6ZSgpKSxufV9hc3NpZ25Nb2RlbHModCl7dGhpcy5faXRlcmF0ZUl0ZW1zKChyLG4pPT57bGV0IGk9dGhpcy5fcGh5c2ljYWxJdGVtc1tyXTt0aGlzLl90b2dnbGVBdHRyaWJ1dGUoImhpZGRlbiIsbj49dGhpcy5fZWZmZWN0aXZlU2l6ZSxpKSx0aGlzLl91cGRhdGVTY3JvbGxlckl0ZW0oaSxuKyh0aGlzLl92aWR4T2Zmc2V0fHwwKSl9LHQpfV9zY3JvbGxIYW5kbGVyKCl7bGV0IHQ9dGhpcy4kLnRhYmxlLnNjcm9sbFRvcC10aGlzLl9zY3JvbGxQb3NpdGlvbjt0aGlzLl9hY2Nlc3NJcm9uTGlzdEFQSShzdXBlci5fc2Nyb2xsSGFuZGxlcik7bGV0IHI9dGhpcy5fdmlkeE9mZnNldDt0aGlzLl9hY2Nlc3NJcm9uTGlzdEFQSSgoKT0+dGhpcy5fbWF4U2Nyb2xsVG9wKSYmdGhpcy5fdmlydHVhbENvdW50PHRoaXMuX2VmZmVjdGl2ZVNpemU/dGhpcy5fYWRqdXN0VmlydHVhbEluZGV4T2Zmc2V0KHQpOnRoaXMuX3ZpZHhPZmZzZXQ9MCx0aGlzLl92aWR4T2Zmc2V0IT09ciYmdGhpcy5fdXBkYXRlKCksdGhpcy5fYWZ0ZXJTY3JvbGwoKX1fYWRqdXN0VmlydHVhbEluZGV4T2Zmc2V0KHQpe2lmKE1hdGguYWJzKHQpPjFlNCl7aWYodGhpcy5fbm9TY2FsZSl7dGhpcy5fbm9TY2FsZT0hMTtyZXR1cm59bGV0IHI9dGhpcy4kLnRhYmxlLnNjcm9sbFRvcC8odGhpcy4kLnRhYmxlLnNjcm9sbEhlaWdodC10aGlzLiQudGFibGUub2Zmc2V0SGVpZ2h0KSxuPXIqdGhpcy5fZWZmZWN0aXZlU2l6ZTt0aGlzLl92aWR4T2Zmc2V0PU1hdGgucm91bmQobi1yKnRoaXMuX3ZpcnR1YWxDb3VudCl9ZWxzZXtsZXQgcj10aGlzLl92aWR4T2Zmc2V0fHwwLG49MWUzLGk9MTAwO3RoaXMuX3Njcm9sbFRvcD09PTA/KHRoaXMuX3ZpZHhPZmZzZXQ9MCxyIT09dGhpcy5fdmlkeE9mZnNldCYmc3VwZXIuc2Nyb2xsVG9JbmRleCgwKSk6dGhpcy5maXJzdFZpc2libGVJbmRleDxuJiZ0aGlzLl92aWR4T2Zmc2V0PjAmJih0aGlzLl92aWR4T2Zmc2V0LT1NYXRoLm1pbih0aGlzLl92aWR4T2Zmc2V0LGkpLHIhPT10aGlzLl92aWR4T2Zmc2V0JiZzdXBlci5zY3JvbGxUb0luZGV4KHRoaXMuZmlyc3RWaXNpYmxlSW5kZXgrKHItdGhpcy5fdmlkeE9mZnNldCkpLHRoaXMuX25vU2NhbGU9ITApO2xldCBvPXRoaXMuX2VmZmVjdGl2ZVNpemUtdGhpcy5fdmlydHVhbENvdW50O3RoaXMuX3Njcm9sbFRvcD49dGhpcy5fbWF4U2Nyb2xsVG9wJiZ0aGlzLl9tYXhTY3JvbGxUb3A+MD8odGhpcy5fdmlkeE9mZnNldD1vLHIhPT10aGlzLl92aWR4T2Zmc2V0JiZzdXBlci5zY3JvbGxUb0luZGV4KHRoaXMuX3ZpcnR1YWxDb3VudCkpOnRoaXMuZmlyc3RWaXNpYmxlSW5kZXg+dGhpcy5fdmlydHVhbENvdW50LW4mJnRoaXMuX3ZpZHhPZmZzZXQ8byYmKHRoaXMuX3ZpZHhPZmZzZXQrPU1hdGgubWluKG8tdGhpcy5fdmlkeE9mZnNldCxpKSxyIT09dGhpcy5fdmlkeE9mZnNldCYmc3VwZXIuc2Nyb2xsVG9JbmRleCh0aGlzLmZpcnN0VmlzaWJsZUluZGV4LSh0aGlzLl92aWR4T2Zmc2V0LXIpKSx0aGlzLl9ub1NjYWxlPSEwKX19X2FjY2Vzc0lyb25MaXN0QVBJKHQpe3RoaXMuX3dhcm5Qcml2YXRlQVBJQWNjZXNzQXN5bmNFbmFibGVkPSExO2xldCByPXQuYXBwbHkodGhpcyk7cmV0dXJuIHRoaXMuX2RlYm91bmNlcldhcm5Qcml2YXRlQVBJQWNjZXNzPXNyLmRlYm91bmNlKHRoaXMuX2RlYm91bmNlcldhcm5Qcml2YXRlQVBJQWNjZXNzLE5pLCgpPT50aGlzLl93YXJuUHJpdmF0ZUFQSUFjY2Vzc0FzeW5jRW5hYmxlZD0hMCkscn1fZGVib3VuY2VSZW5kZXIodCxyKXtzdXBlci5fZGVib3VuY2VSZW5kZXIoKCk9PnRoaXMuX2FjY2Vzc0lyb25MaXN0QVBJKHQpLHIpfV93YXJuUHJpdmF0ZUFQSUFjY2Vzcyh0KXt0aGlzLl93YXJuUHJpdmF0ZUFQSUFjY2Vzc0FzeW5jRW5hYmxlZCYmY29uc29sZS53YXJuKGBBY2Nlc3NpbmcgcHJpdmF0ZSBBUEkgKCR7dH0pIWApfV9yZW5kZXIoKXt0aGlzLl9hY2Nlc3NJcm9uTGlzdEFQSShzdXBlci5fcmVuZGVyKX1faXRlbXNDaGFuZ2VkKCl7fWdldCBfZmlyc3RWaXNpYmxlSW5kZXgoKXtyZXR1cm4gdGhpcy5fYWNjZXNzSXJvbkxpc3RBUEkoKCk9PnN1cGVyLmZpcnN0VmlzaWJsZUluZGV4KX1nZXQgX2xhc3RWaXNpYmxlSW5kZXgoKXtyZXR1cm4gdGhpcy5fYWNjZXNzSXJvbkxpc3RBUEkoKCk9PnN1cGVyLmxhc3RWaXNpYmxlSW5kZXgpfV9zY3JvbGxUb0luZGV4KHQpe3RoaXMuX2FjY2Vzc0lyb25MaXN0QVBJKCgpPT50aGlzLnNjcm9sbFRvSW5kZXgodCkpfWdldCBmaXJzdFZpc2libGVJbmRleCgpe3JldHVybiB0aGlzLl93YXJuUHJpdmF0ZUFQSUFjY2VzcygiZmlyc3RWaXNpYmxlSW5kZXgiKSxzdXBlci5maXJzdFZpc2libGVJbmRleH1zZXQgZmlyc3RWaXNpYmxlSW5kZXgodCl7dGhpcy5fd2FyblByaXZhdGVBUElBY2Nlc3MoImZpcnN0VmlzaWJsZUluZGV4Iiksc3VwZXIuZmlyc3RWaXNpYmxlSW5kZXg9dH1nZXQgbGFzdFZpc2libGVJbmRleCgpe3JldHVybiB0aGlzLl93YXJuUHJpdmF0ZUFQSUFjY2VzcygibGFzdFZpc2libGVJbmRleCIpLHN1cGVyLmxhc3RWaXNpYmxlSW5kZXh9c2V0IGxhc3RWaXNpYmxlSW5kZXgodCl7dGhpcy5fd2FyblByaXZhdGVBUElBY2Nlc3MoImxhc3RWaXNpYmxlSW5kZXgiKSxzdXBlci5sYXN0VmlzaWJsZUluZGV4PXR9dXBkYXRlVmlld3BvcnRCb3VuZGFyaWVzKCl7dGhpcy5fd2FyblByaXZhdGVBUElBY2Nlc3MoInVwZGF0ZVZpZXdwb3J0Qm91bmRhcmllcyIpLHN1cGVyLnVwZGF0ZVZpZXdwb3J0Qm91bmRhcmllcy5hcHBseSh0aGlzLGFyZ3VtZW50cyl9X3Jlc2l6ZUhhbmRsZXIoKXtzdXBlci5fcmVzaXplSGFuZGxlcigpLHVpKCl9fTt2YXIgVWNlPWU9PmNsYXNzIGV4dGVuZHMgZXtzdGF0aWMgZ2V0IG9ic2VydmVycygpe3JldHVyblsiX2ExMXlVcGRhdGVHcmlkU2l6ZShzaXplLCBfY29sdW1uVHJlZSwgX2NvbHVtblRyZWUuKikiXX1fYTExeUdldEhlYWRlclJvd0NvdW50KHIpe3JldHVybiByLmZpbHRlcihuPT5uLnNvbWUoaT0+aS5faGVhZGVyVGVtcGxhdGV8fGkuaGVhZGVyUmVuZGVyZXJ8fGkucGF0aHx8aS5oZWFkZXIpKS5sZW5ndGh9X2ExMXlHZXRGb290ZXJSb3dDb3VudChyKXtyZXR1cm4gci5maWx0ZXIobj0+bi5zb21lKGk9PmkuX2hlYWRlclRlbXBsYXRlfHxpLmhlYWRlclJlbmRlcmVyKSkubGVuZ3RofV9hMTF5VXBkYXRlR3JpZFNpemUocixuKXtpZihyPT09dm9pZCAwfHxuPT09dm9pZCAwKXJldHVybjtsZXQgaT1uW24ubGVuZ3RoLTFdO3RoaXMuJC50YWJsZS5zZXRBdHRyaWJ1dGUoImFyaWEtcm93Y291bnQiLHIrdGhpcy5fYTExeUdldEhlYWRlclJvd0NvdW50KG4pK3RoaXMuX2ExMXlHZXRGb290ZXJSb3dDb3VudChuKSksdGhpcy4kLnRhYmxlLnNldEF0dHJpYnV0ZSgiYXJpYS1jb2xjb3VudCIsaSYmaS5sZW5ndGh8fDApLHRoaXMuX2ExMXlVcGRhdGVIZWFkZXJSb3dzKCksdGhpcy5fYTExeVVwZGF0ZUZvb3RlclJvd3MoKX1fYTExeVVwZGF0ZUhlYWRlclJvd3MoKXtBcnJheS5mcm9tKHRoaXMuJC5oZWFkZXIuY2hpbGRyZW4pLmZvckVhY2goKHIsbik9PnIuc2V0QXR0cmlidXRlKCJhcmlhLXJvd2luZGV4IixuKzEpKX1fYTExeVVwZGF0ZUZvb3RlclJvd3MoKXtBcnJheS5mcm9tKHRoaXMuJC5mb290ZXIuY2hpbGRyZW4pLmZvckVhY2goKHIsbik9PnIuc2V0QXR0cmlidXRlKCJhcmlhLXJvd2luZGV4Iix0aGlzLl9hMTF5R2V0SGVhZGVyUm93Q291bnQodGhpcy5fY29sdW1uVHJlZSkrdGhpcy5zaXplK24rMSkpfV9hMTF5VXBkYXRlUm93Um93aW5kZXgocixuKXtyLnNldEF0dHJpYnV0ZSgiYXJpYS1yb3dpbmRleCIsbit0aGlzLl9hMTF5R2V0SGVhZGVyUm93Q291bnQodGhpcy5fY29sdW1uVHJlZSkrMSl9X2ExMXlVcGRhdGVSb3dTZWxlY3RlZChyLG4pe3Iuc2V0QXR0cmlidXRlKCJhcmlhLXNlbGVjdGVkIixCb29sZWFuKG4pKSxBcnJheS5mcm9tKHIuY2hpbGRyZW4pLmZvckVhY2goaT0+aS5zZXRBdHRyaWJ1dGUoImFyaWEtc2VsZWN0ZWQiLEJvb2xlYW4obikpKX1fYTExeVVwZGF0ZVJvd0xldmVsKHIsbil7ci5zZXRBdHRyaWJ1dGUoImFyaWEtbGV2ZWwiLG4rMSl9X2ExMXlVcGRhdGVSb3dEZXRhaWxzT3BlbmVkKHIsbil7QXJyYXkuZnJvbShyLmNoaWxkcmVuKS5mb3JFYWNoKGk9Pnt0eXBlb2Ygbj09ImJvb2xlYW4iP2kuc2V0QXR0cmlidXRlKCJhcmlhLWV4cGFuZGVkIixuKTppLmhhc0F0dHJpYnV0ZSgiYXJpYS1leHBhbmRlZCIpJiZpLnJlbW92ZUF0dHJpYnV0ZSgiYXJpYS1leHBhbmRlZCIpfSl9X2ExMXlTZXRSb3dEZXRhaWxzQ2VsbChyLG4pe0FycmF5LmZyb20oci5jaGlsZHJlbikuZm9yRWFjaChpPT57aSE9PW4mJmkuc2V0QXR0cmlidXRlKCJhcmlhLWNvbnRyb2xzIixuLmlkKX0pfV9hMTF5VXBkYXRlQ2VsbENvbHNwYW4ocixuKXtyLnNldEF0dHJpYnV0ZSgiYXJpYS1jb2xzcGFuIixOdW1iZXIobikpfV9hMTF5VXBkYXRlU29ydGVycygpe0FycmF5LmZyb20odGhpcy5xdWVyeVNlbGVjdG9yQWxsKCJ2YWFkaW4tZ3JpZC1zb3J0ZXIiKSkuZm9yRWFjaChyPT57bGV0IG49ci5wYXJlbnROb2RlO2Zvcig7biYmbi5sb2NhbE5hbWUhPT0idmFhZGluLWdyaWQtY2VsbC1jb250ZW50Ijspbj1uLnBhcmVudE5vZGU7biYmbi5hc3NpZ25lZFNsb3QmJm4uYXNzaWduZWRTbG90LnBhcmVudE5vZGUuc2V0QXR0cmlidXRlKCJhcmlhLXNvcnQiLHthc2M6ImFzY2VuZGluZyIsZGVzYzoiZGVzY2VuZGluZyJ9W1N0cmluZyhyLmRpcmVjdGlvbildfHwibm9uZSIpfSl9fTt2YXIgcWNlPWU9PmNsYXNzIGV4dGVuZHMgZXtzdGF0aWMgZ2V0IHByb3BlcnRpZXMoKXtyZXR1cm57YWN0aXZlSXRlbTp7dHlwZTpPYmplY3Qsbm90aWZ5OiEwLHZhbHVlOm51bGx9fX1yZWFkeSgpe3N1cGVyLnJlYWR5KCksdGhpcy4kLnNjcm9sbGVyLmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIix0aGlzLl9vbkNsaWNrLmJpbmQodGhpcykpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigiY2VsbC1hY3RpdmF0ZSIsdGhpcy5fYWN0aXZhdGVJdGVtLmJpbmQodGhpcykpfV9hY3RpdmF0ZUl0ZW0ocil7bGV0IG49ci5kZXRhaWwubW9kZWwsaT1uP24uaXRlbTpudWxsO2kmJih0aGlzLmFjdGl2ZUl0ZW09dGhpcy5faXRlbXNFcXVhbCh0aGlzLmFjdGl2ZUl0ZW0saSk/bnVsbDppKX1fb25DbGljayhyKXtpZihyLmRlZmF1bHRQcmV2ZW50ZWQpcmV0dXJuO2xldCBuPXIuY29tcG9zZWRQYXRoKCksaT1uW24uaW5kZXhPZih0aGlzLiQudGFibGUpLTNdO2lmKCFpfHxpLmdldEF0dHJpYnV0ZSgicGFydCIpLmluZGV4T2YoImRldGFpbHMtY2VsbCIpPi0xKXJldHVybjtsZXQgbz1pLl9jb250ZW50LGE9dGhpcy5nZXRSb290Tm9kZSgpLmFjdGl2ZUVsZW1lbnQ7IW8uY29udGFpbnMoYSkmJiF0aGlzLl9pc0ZvY3VzYWJsZShyLnRhcmdldCkmJnRoaXMuZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoImNlbGwtYWN0aXZhdGUiLHtkZXRhaWw6e21vZGVsOnRoaXMuX19nZXRSb3dNb2RlbChpLnBhcmVudEVsZW1lbnQpfX0pKX1faXNGb2N1c2FibGUocil7cmV0dXJuIHJocihyKX19LHJocj1lPT57aWYoIWUucGFyZW50Tm9kZSlyZXR1cm4hMTtsZXQgcj1BcnJheS5mcm9tKGUucGFyZW50Tm9kZS5xdWVyeVNlbGVjdG9yQWxsKCJbdGFiaW5kZXhdLCBidXR0b24sIGlucHV0LCBzZWxlY3QsIHRleHRhcmVhLCBvYmplY3QsIGlmcmFtZSwgbGFiZWwsIGFbaHJlZl0sIGFyZWFbaHJlZl0iKSkuZmlsdGVyKG49Pm4uZ2V0QXR0cmlidXRlKCJwYXJ0IikhPT0iY2VsbCBib2R5LWNlbGwiKS5pbmRleE9mKGUpIT09LTE7cmV0dXJuIWUuZGlzYWJsZWQmJnJ9O3ZhciBHY2U9ZT0+Y2xhc3MgZXh0ZW5kcyBle3N0YXRpYyBnZXQgcHJvcGVydGllcygpe3JldHVybntpdGVtczpBcnJheX19c3RhdGljIGdldCBvYnNlcnZlcnMoKXtyZXR1cm5bIl9pdGVtc0NoYW5nZWQoaXRlbXMsIGl0ZW1zLiosIGlzQXR0YWNoZWQpIl19X2l0ZW1zQ2hhbmdlZChyLG4saSl7aWYoISFpKXtpZighQXJyYXkuaXNBcnJheShyKSl7cj09bnVsbCYmKHRoaXMuc2l6ZT0wKSx0aGlzLmRhdGFQcm92aWRlcj09PXRoaXMuX2FycmF5RGF0YVByb3ZpZGVyJiYodGhpcy5kYXRhUHJvdmlkZXI9dm9pZCAwKTtyZXR1cm59dGhpcy5zaXplPXIubGVuZ3RoLHRoaXMuZGF0YVByb3ZpZGVyPXRoaXMuZGF0YVByb3ZpZGVyfHx0aGlzLl9hcnJheURhdGFQcm92aWRlcix0aGlzLmNsZWFyQ2FjaGUoKSx0aGlzLl9lbnN1cmVGaXJzdFBhZ2VMb2FkZWQoKX19X2FycmF5RGF0YVByb3ZpZGVyKHIsbil7bGV0IGk9KEFycmF5LmlzQXJyYXkodGhpcy5pdGVtcyk/dGhpcy5pdGVtczpbXSkuc2xpY2UoMCk7dGhpcy5fZmlsdGVycyYmdGhpcy5fY2hlY2tQYXRocyh0aGlzLl9maWx0ZXJzLCJmaWx0ZXJpbmciLGkpJiYoaT10aGlzLl9maWx0ZXIoaSkpLHRoaXMuc2l6ZT1pLmxlbmd0aCxyLnNvcnRPcmRlcnMubGVuZ3RoJiZ0aGlzLl9jaGVja1BhdGhzKHRoaXMuX3NvcnRlcnMsInNvcnRpbmciLGkpJiYoaT1pLnNvcnQodGhpcy5fbXVsdGlTb3J0LmJpbmQodGhpcykpKTtsZXQgbz1yLnBhZ2Uqci5wYWdlU2l6ZSxhPW8rci5wYWdlU2l6ZSxzPWkuc2xpY2UobyxhKTtuKHMsaS5sZW5ndGgpfV9jaGVja1BhdGhzKHIsbixpKXtpZighaS5sZW5ndGgpcmV0dXJuITE7bGV0IG89ITA7Zm9yKGxldCBhIGluIHIpe2xldCBzPXJbYV0ucGF0aDtpZighc3x8cy5pbmRleE9mKCIuIik9PT0tMSljb250aW51ZTtsZXQgbD1zLnJlcGxhY2UoL1wuW14uXSokLywiIik7RGEuZ2V0KGwsaVswXSk9PT12b2lkIDAmJihjb25zb2xlLndhcm4oYFBhdGggIiR7c30iIHVzZWQgZm9yICR7bn0gZG9lcyBub3QgZXhpc3QgaW4gYWxsIG9mIHRoZSBpdGVtcywgJHtufSBpcyBkaXNhYmxlZC5gKSxvPSExKX1yZXR1cm4gb31fbXVsdGlTb3J0KHIsbil7cmV0dXJuIHRoaXMuX3NvcnRlcnMubWFwKGk9PmkuZGlyZWN0aW9uPT09ImFzYyI/dGhpcy5fY29tcGFyZShEYS5nZXQoaS5wYXRoLHIpLERhLmdldChpLnBhdGgsbikpOmkuZGlyZWN0aW9uPT09ImRlc2MiP3RoaXMuX2NvbXBhcmUoRGEuZ2V0KGkucGF0aCxuKSxEYS5nZXQoaS5wYXRoLHIpKTowKS5yZWR1Y2UoKGksbyk9Pml8fG8sMCl9X25vcm1hbGl6ZUVtcHR5VmFsdWUocil7cmV0dXJuW3ZvaWQgMCxudWxsXS5pbmRleE9mKHIpPj0wPyIiOmlzTmFOKHIpP3IudG9TdHJpbmcoKTpyfV9jb21wYXJlKHIsbil7cmV0dXJuIHI9dGhpcy5fbm9ybWFsaXplRW1wdHlWYWx1ZShyKSxuPXRoaXMuX25vcm1hbGl6ZUVtcHR5VmFsdWUobikscjxuPy0xOnI+bj8xOjB9X2ZpbHRlcihyKXtyZXR1cm4gci5maWx0ZXIobj0+dGhpcy5fZmlsdGVycy5maWx0ZXIoaT0+e2xldCBvPXRoaXMuX25vcm1hbGl6ZUVtcHR5VmFsdWUoRGEuZ2V0KGkucGF0aCxuKSksYT10aGlzLl9ub3JtYWxpemVFbXB0eVZhbHVlKGkudmFsdWUpLnRvU3RyaW5nKCkudG9Mb3dlckNhc2UoKTtyZXR1cm4gby50b1N0cmluZygpLnRvTG93ZXJDYXNlKCkuaW5kZXhPZihhKT09PS0xfSkubGVuZ3RoPT09MCl9fTt2YXIgV2NlPWU9PmNsYXNzIGV4dGVuZHMgeWgoZSl7cmVhZHkoKXtzdXBlci5yZWFkeSgpO2xldCByPXRoaXMuJC5zY3JvbGxlcjtFbShyLCJ0cmFjayIsdGhpcy5fb25IZWFkZXJUcmFjay5iaW5kKHRoaXMpKSxyLmFkZEV2ZW50TGlzdGVuZXIoInRvdWNobW92ZSIsbj0+ci5oYXNBdHRyaWJ1dGUoImNvbHVtbi1yZXNpemluZyIpJiZuLnByZXZlbnREZWZhdWx0KCkpLHIuYWRkRXZlbnRMaXN0ZW5lcigiY29udGV4dG1lbnUiLG49Pm4udGFyZ2V0LmdldEF0dHJpYnV0ZSgicGFydCIpPT0icmVzaXplLWhhbmRsZSImJm4ucHJldmVudERlZmF1bHQoKSksci5hZGRFdmVudExpc3RlbmVyKCJtb3VzZWRvd24iLG49Pm4udGFyZ2V0LmdldEF0dHJpYnV0ZSgicGFydCIpPT09InJlc2l6ZS1oYW5kbGUiJiZuLnByZXZlbnREZWZhdWx0KCkpfV9vbkhlYWRlclRyYWNrKHIpe2xldCBuPXIudGFyZ2V0O2lmKG4uZ2V0QXR0cmlidXRlKCJwYXJ0Iik9PT0icmVzaXplLWhhbmRsZSIpe2xldCBvPW4ucGFyZW50RWxlbWVudC5fY29sdW1uO2Zvcih0aGlzLl90b2dnbGVBdHRyaWJ1dGUoImNvbHVtbi1yZXNpemluZyIsITAsdGhpcy4kLnNjcm9sbGVyKTtvLmxvY2FsTmFtZT09PSJ2YWFkaW4tZ3JpZC1jb2x1bW4tZ3JvdXAiOylvPUFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKG8uX2NoaWxkQ29sdW1ucywwKS5zb3J0KGZ1bmN0aW9uKGwsYyl7cmV0dXJuIGwuX29yZGVyLWMuX29yZGVyfSkuZmlsdGVyKGZ1bmN0aW9uKGwpe3JldHVybiFsLmhpZGRlbn0pLnBvcCgpO2xldCBhPUFycmF5LmZyb20odGhpcy4kLmhlYWRlci5xdWVyeVNlbGVjdG9yQWxsKCdbcGFydH49InJvdyJdOmxhc3QtY2hpbGQgW3BhcnR+PSJjZWxsIl0nKSkscz1hLmZpbHRlcihsPT5sLl9jb2x1bW49PT1vKVswXTtpZihzLm9mZnNldFdpZHRoKXtsZXQgbD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShzKSxjPTEwK3BhcnNlSW50KGwucGFkZGluZ0xlZnQpK3BhcnNlSW50KGwucGFkZGluZ1JpZ2h0KStwYXJzZUludChsLmJvcmRlckxlZnRXaWR0aCkrcGFyc2VJbnQobC5ib3JkZXJSaWdodFdpZHRoKStwYXJzZUludChsLm1hcmdpbkxlZnQpK3BhcnNlSW50KGwubWFyZ2luUmlnaHQpLHU9cy5vZmZzZXRXaWR0aCsodGhpcy5fX2lzUlRMP3MuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkubGVmdC1yLmRldGFpbC54OnIuZGV0YWlsLngtcy5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS5yaWdodCk7by53aWR0aD1NYXRoLm1heChjLHUpKyJweCIsby5mbGV4R3Jvdz0wfWEuc29ydChmdW5jdGlvbihsLGMpe3JldHVybiBsLl9jb2x1bW4uX29yZGVyLWMuX2NvbHVtbi5fb3JkZXJ9KS5mb3JFYWNoKGZ1bmN0aW9uKGwsYyx1KXtjPHUuaW5kZXhPZihzKSYmKGwuX2NvbHVtbi53aWR0aD1sLm9mZnNldFdpZHRoKyJweCIsbC5fY29sdW1uLmZsZXhHcm93PTApfSksci5kZXRhaWwuc3RhdGU9PT0iZW5kIiYmKHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgiY29sdW1uLXJlc2l6aW5nIiwhMSx0aGlzLiQuc2Nyb2xsZXIpLHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoImNvbHVtbi1yZXNpemUiLHtkZXRhaWw6e3Jlc2l6ZWRDb2x1bW46b319KSkpLHRoaXMuX3Jlc2l6ZUhhbmRsZXIoKX19fTt2YXIgWWNlPWNsYXNzIGpjZXtjb25zdHJ1Y3Rvcih0LHIsbil7dGhpcy5ncmlkPXQsdGhpcy5wYXJlbnRDYWNoZT1yLHRoaXMucGFyZW50SXRlbT1uLHRoaXMuaXRlbUNhY2hlcz17fSx0aGlzLml0ZW1zPXt9LHRoaXMuZWZmZWN0aXZlU2l6ZT0wLHRoaXMuc2l6ZT0wLHRoaXMucGVuZGluZ1JlcXVlc3RzPXt9fWlzTG9hZGluZygpe3JldHVybiBCb29sZWFuKE9iamVjdC5rZXlzKHRoaXMucGVuZGluZ1JlcXVlc3RzKS5sZW5ndGh8fE9iamVjdC5rZXlzKHRoaXMuaXRlbUNhY2hlcykuZmlsdGVyKHQ9PnRoaXMuaXRlbUNhY2hlc1t0XS5pc0xvYWRpbmcoKSlbMF0pfWdldEl0ZW1Gb3JJbmRleCh0KXtsZXR7Y2FjaGU6cixzY2FsZWRJbmRleDpufT10aGlzLmdldENhY2hlQW5kSW5kZXgodCk7cmV0dXJuIHIuaXRlbXNbbl19dXBkYXRlU2l6ZSgpe3RoaXMuZWZmZWN0aXZlU2l6ZT0hdGhpcy5wYXJlbnRJdGVtfHx0aGlzLmdyaWQuX2lzRXhwYW5kZWQodGhpcy5wYXJlbnRJdGVtKT90aGlzLnNpemUrT2JqZWN0LmtleXModGhpcy5pdGVtQ2FjaGVzKS5yZWR1Y2UoKHQscik9PntsZXQgbj10aGlzLml0ZW1DYWNoZXNbcl07cmV0dXJuIG4udXBkYXRlU2l6ZSgpLHQrbi5lZmZlY3RpdmVTaXplfSwwKTowfWVuc3VyZVN1YkNhY2hlRm9yU2NhbGVkSW5kZXgodCl7aWYoIXRoaXMuaXRlbUNhY2hlc1t0XSl7bGV0IHI9bmV3IGpjZSh0aGlzLmdyaWQsdGhpcyx0aGlzLml0ZW1zW3RdKTt0aGlzLml0ZW1DYWNoZXNbdF09cix0aGlzLmdyaWQuX2xvYWRQYWdlKDAscil9fWdldENhY2hlQW5kSW5kZXgodCl7bGV0IHI9dCxuPU9iamVjdC5rZXlzKHRoaXMuaXRlbUNhY2hlcyk7Zm9yKGxldCBpPTA7aTxuLmxlbmd0aDtpKyspe2xldCBvPU51bWJlcihuW2ldKSxhPXRoaXMuaXRlbUNhY2hlc1tvXTtpZihyPD1vKXJldHVybntjYWNoZTp0aGlzLHNjYWxlZEluZGV4OnJ9O2lmKHI8PW8rYS5lZmZlY3RpdmVTaXplKXJldHVybiBhLmdldENhY2hlQW5kSW5kZXgoci1vLTEpO3ItPWEuZWZmZWN0aXZlU2l6ZX1yZXR1cm57Y2FjaGU6dGhpcyxzY2FsZWRJbmRleDpyfX19LFhjZT1lPT5jbGFzcyBleHRlbmRzIGV7c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue3BhZ2VTaXplOnt0eXBlOk51bWJlcix2YWx1ZTo1MCxvYnNlcnZlcjoiX3BhZ2VTaXplQ2hhbmdlZCJ9LGRhdGFQcm92aWRlcjp7dHlwZTpPYmplY3Qsbm90aWZ5OiEwLG9ic2VydmVyOiJfZGF0YVByb3ZpZGVyQ2hhbmdlZCJ9LGxvYWRpbmc6e3R5cGU6Qm9vbGVhbixub3RpZnk6ITAscmVhZE9ubHk6ITAscmVmbGVjdFRvQXR0cmlidXRlOiEwfSxfY2FjaGU6e3R5cGU6T2JqZWN0LHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuIG5ldyBZY2UodGhpcyl9fSxpdGVtSWRQYXRoOnt0eXBlOlN0cmluZyx2YWx1ZTpudWxsfSxleHBhbmRlZEl0ZW1zOnt0eXBlOk9iamVjdCxub3RpZnk6ITAsdmFsdWU6KCk9PltdfX19c3RhdGljIGdldCBvYnNlcnZlcnMoKXtyZXR1cm5bIl9zaXplQ2hhbmdlZChzaXplKSIsIl9pdGVtSWRQYXRoQ2hhbmdlZChpdGVtSWRQYXRoKSIsIl9leHBhbmRlZEl0ZW1zQ2hhbmdlZChleHBhbmRlZEl0ZW1zLiopIl19X3NpemVDaGFuZ2VkKHIpe2xldCBuPXItdGhpcy5fY2FjaGUuc2l6ZTt0aGlzLl9jYWNoZS5zaXplKz1uLHRoaXMuX2NhY2hlLmVmZmVjdGl2ZVNpemUrPW4sdGhpcy5fZWZmZWN0aXZlU2l6ZT10aGlzLl9jYWNoZS5lZmZlY3RpdmVTaXplLHRoaXMuX2luY3JlYXNlUG9vbElmTmVlZGVkKDApLHRoaXMuX2RlYm91bmNlSW5jcmVhc2VQb29sJiZ0aGlzLl9kZWJvdW5jZUluY3JlYXNlUG9vbC5mbHVzaCgpfV9nZXRJdGVtKHIsbil7aWYocj49dGhpcy5fZWZmZWN0aXZlU2l6ZSlyZXR1cm47bi5pbmRleD1yO2xldHtjYWNoZTppLHNjYWxlZEluZGV4Om99PXRoaXMuX2NhY2hlLmdldENhY2hlQW5kSW5kZXgociksYT1pLml0ZW1zW29dO2E/KHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgibG9hZGluZyIsITEsbiksdGhpcy5fdXBkYXRlSXRlbShuLGEpLHRoaXMuX2lzRXhwYW5kZWQoYSkmJmkuZW5zdXJlU3ViQ2FjaGVGb3JTY2FsZWRJbmRleChvKSk6KHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgibG9hZGluZyIsITAsbiksdGhpcy5fbG9hZFBhZ2UodGhpcy5fZ2V0UGFnZUZvckluZGV4KG8pLGkpKX1fZXhwYW5kZWRJbnN0YW5jZUNoYW5nZWRDYWxsYmFjayhyLG4pe3IuaXRlbSE9PXZvaWQgMCYmKG4/dGhpcy5leHBhbmRJdGVtKHIuaXRlbSk6dGhpcy5jb2xsYXBzZUl0ZW0oci5pdGVtKSl9Z2V0SXRlbUlkKHIpe3JldHVybiB0aGlzLml0ZW1JZFBhdGg/dGhpcy5nZXQodGhpcy5pdGVtSWRQYXRoLHIpOnJ9X2lzRXhwYW5kZWQocil7cmV0dXJuIHRoaXMuX19leHBhbmRlZEtleXMuaGFzKHRoaXMuZ2V0SXRlbUlkKHIpKX1fZXhwYW5kZWRJdGVtc0NoYW5nZWQoKXt0aGlzLl9fY2FjaGVFeHBhbmRlZEtleXMoKSx0aGlzLl9jYWNoZS51cGRhdGVTaXplKCksdGhpcy5fZWZmZWN0aXZlU2l6ZT10aGlzLl9jYWNoZS5lZmZlY3RpdmVTaXplLHRoaXMuX2Fzc2lnbk1vZGVscygpfV9pdGVtSWRQYXRoQ2hhbmdlZCgpe3RoaXMuX19jYWNoZUV4cGFuZGVkS2V5cygpfV9fY2FjaGVFeHBhbmRlZEtleXMoKXt0aGlzLmV4cGFuZGVkSXRlbXMmJih0aGlzLl9fZXhwYW5kZWRLZXlzPW5ldyBTZXQsdGhpcy5leHBhbmRlZEl0ZW1zLmZvckVhY2gocj0+e3RoaXMuX19leHBhbmRlZEtleXMuYWRkKHRoaXMuZ2V0SXRlbUlkKHIpKX0pKX1leHBhbmRJdGVtKHIpe3RoaXMuX2lzRXhwYW5kZWQocil8fCh0aGlzLmV4cGFuZGVkSXRlbXM9Wy4uLnRoaXMuZXhwYW5kZWRJdGVtcyxyXSl9Y29sbGFwc2VJdGVtKHIpe3RoaXMuX2lzRXhwYW5kZWQocikmJih0aGlzLmV4cGFuZGVkSXRlbXM9dGhpcy5leHBhbmRlZEl0ZW1zLmZpbHRlcihuPT4hdGhpcy5faXRlbXNFcXVhbChuLHIpKSl9X2dldEluZGV4TGV2ZWwocil7bGV0e2NhY2hlOm59PXRoaXMuX2NhY2hlLmdldENhY2hlQW5kSW5kZXgociksaT0wO2Zvcig7bi5wYXJlbnRDYWNoZTspbj1uLnBhcmVudENhY2hlLGkrKztyZXR1cm4gaX1fY2FuUG9wdWxhdGUoKXtyZXR1cm4gQm9vbGVhbih0aGlzLl9oYXNEYXRhJiZ0aGlzLl9jb2x1bW5UcmVlKX1fbG9hZFBhZ2UocixuKXtpZighbi5wZW5kaW5nUmVxdWVzdHNbcl0mJnRoaXMuZGF0YVByb3ZpZGVyKXt0aGlzLl9zZXRMb2FkaW5nKCEwKSxuLnBlbmRpbmdSZXF1ZXN0c1tyXT0hMDtsZXQgaT17cGFnZTpyLHBhZ2VTaXplOnRoaXMucGFnZVNpemUsc29ydE9yZGVyczp0aGlzLl9tYXBTb3J0ZXJzKCksZmlsdGVyczp0aGlzLl9tYXBGaWx0ZXJzKCkscGFyZW50SXRlbTpuLnBhcmVudEl0ZW19O3RoaXMuX2RlYm91bmNlSW5jcmVhc2VQb29sJiZ0aGlzLl9kZWJvdW5jZUluY3JlYXNlUG9vbC5mbHVzaCgpLHRoaXMuZGF0YVByb3ZpZGVyKGksKG8sYSk9PnthIT09dm9pZCAwP24uc2l6ZT1hOmkucGFyZW50SXRlbSYmKG4uc2l6ZT1vLmxlbmd0aCk7bGV0IHM9QXJyYXkuZnJvbSh0aGlzLiQuaXRlbXMuY2hpbGRyZW4pLm1hcChsPT5sLl9pdGVtKTtvLmZvckVhY2goKGwsYyk9PntsZXQgdT1yKnRoaXMucGFnZVNpemUrYztuLml0ZW1zW3VdPWwsdGhpcy5faXNFeHBhbmRlZChsKSYmcy5pbmRleE9mKGwpPi0xJiZuLmVuc3VyZVN1YkNhY2hlRm9yU2NhbGVkSW5kZXgodSl9KSx0aGlzLl9oYXNEYXRhPSEwLGRlbGV0ZSBuLnBlbmRpbmdSZXF1ZXN0c1tyXSx0aGlzLl9kZWJvdW5jZXJBcHBseUNhY2hlZERhdGE9c3IuZGVib3VuY2UodGhpcy5fZGVib3VuY2VyQXBwbHlDYWNoZWREYXRhLG1vLmFmdGVyKDApLCgpPT57dGhpcy5fc2V0TG9hZGluZyghMSksdGhpcy5fY2FjaGUudXBkYXRlU2l6ZSgpLHRoaXMuX2VmZmVjdGl2ZVNpemU9dGhpcy5fY2FjaGUuZWZmZWN0aXZlU2l6ZSxBcnJheS5mcm9tKHRoaXMuJC5pdGVtcy5jaGlsZHJlbikuZmlsdGVyKGw9PiFsLmhpZGRlbikuZm9yRWFjaChsPT57dGhpcy5fY2FjaGUuZ2V0SXRlbUZvckluZGV4KGwuaW5kZXgpJiZ0aGlzLl9nZXRJdGVtKGwuaW5kZXgsbCl9KSx0aGlzLl9pbmNyZWFzZVBvb2xJZk5lZWRlZCgwKSx0aGlzLl9fc2Nyb2xsVG9QZW5kaW5nSW5kZXgoKX0pLHRoaXMuX2NhY2hlLmlzTG9hZGluZygpfHx0aGlzLl9kZWJvdW5jZXJBcHBseUNhY2hlZERhdGEuZmx1c2goKSx0aGlzLl9faXRlbXNSZWNlaXZlZCgpfSl9fV9nZXRQYWdlRm9ySW5kZXgocil7cmV0dXJuIE1hdGguZmxvb3Ioci90aGlzLnBhZ2VTaXplKX1jbGVhckNhY2hlKCl7dGhpcy5fY2FjaGU9bmV3IFljZSh0aGlzKSxBcnJheS5mcm9tKHRoaXMuJC5pdGVtcy5jaGlsZHJlbikuZm9yRWFjaChyPT57QXJyYXkuZnJvbShyLmNoaWxkcmVuKS5mb3JFYWNoKG49PntuLl9pbnN0YW5jZSYmbi5faW5zdGFuY2UuX3NldFBlbmRpbmdQcm9wZXJ0eSgiaXRlbSIse30sITEpfSl9KSx0aGlzLl9jYWNoZS5zaXplPXRoaXMuc2l6ZXx8MCx0aGlzLl9jYWNoZS51cGRhdGVTaXplKCksdGhpcy5faGFzRGF0YT0hMSx0aGlzLl9hc3NpZ25Nb2RlbHMoKSwoIXRoaXMuX2VmZmVjdGl2ZVNpemV8fCF0aGlzLl9pbml0aWFsUG9vbENyZWF0ZWQpJiZ0aGlzLl9sb2FkUGFnZSgwLHRoaXMuX2NhY2hlKX1fcGFnZVNpemVDaGFuZ2VkKHIsbil7biE9PXZvaWQgMCYmciE9PW4mJnRoaXMuY2xlYXJDYWNoZSgpfV9jaGVja1NpemUoKXt0aGlzLnNpemU9PT12b2lkIDAmJnRoaXMuX2VmZmVjdGl2ZVNpemU9PT0wJiZjb25zb2xlLndhcm4oIlRoZSA8dmFhZGluLWdyaWQ+IG5lZWRzIHRoZSB0b3RhbCBudW1iZXIgb2YgaXRlbXMgaW4gb3JkZXIgdG8gZGlzcGxheSByb3dzLiBTZXQgdGhlIHRvdGFsIG51bWJlciBvZiBpdGVtcyB0byB0aGUgYHNpemVgIHByb3BlcnR5LCBvciBwcm92aWRlIHRoZSB0b3RhbCBudW1iZXIgb2YgaXRlbXMgaW4gdGhlIHNlY29uZCBhcmd1bWVudCBvZiB0aGUgYGRhdGFQcm92aWRlcmBcdTIwMTlzIGBjYWxsYmFja2AgY2FsbC4iKX1fZGF0YVByb3ZpZGVyQ2hhbmdlZChyLG4pe24hPT12b2lkIDAmJnRoaXMuY2xlYXJDYWNoZSgpLHImJnRoaXMuaXRlbXMmJnRoaXMuaXRlbXMubGVuZ3RoJiZ0aGlzLl9zY3JvbGxUb0luZGV4KHRoaXMuX2ZpcnN0VmlzaWJsZUluZGV4KSx0aGlzLl9lbnN1cmVGaXJzdFBhZ2VMb2FkZWQoKSx0aGlzLl9kZWJvdW5jZXJDaGVja1NpemU9c3IuZGVib3VuY2UodGhpcy5fZGVib3VuY2VyQ2hlY2tTaXplLG1vLmFmdGVyKDJlMyksdGhpcy5fY2hlY2tTaXplLmJpbmQodGhpcykpLHRoaXMuX3Njcm9sbEhhbmRsZXIoKX1fZW5zdXJlRmlyc3RQYWdlTG9hZGVkKCl7dGhpcy5faGFzRGF0YXx8dGhpcy5fbG9hZFBhZ2UoMCx0aGlzLl9jYWNoZSl9X2l0ZW1zRXF1YWwocixuKXtyZXR1cm4gdGhpcy5nZXRJdGVtSWQocik9PT10aGlzLmdldEl0ZW1JZChuKX1fZ2V0SXRlbUluZGV4SW5BcnJheShyLG4pe2xldCBpPS0xO3JldHVybiBuLmZvckVhY2goKG8sYSk9Pnt0aGlzLl9pdGVtc0VxdWFsKG8scikmJihpPWEpfSksaX1zY3JvbGxUb0luZGV4KHIpe3N1cGVyLnNjcm9sbFRvSW5kZXgociksIWlzTmFOKHIpJiYodGhpcy5fY2FjaGUuaXNMb2FkaW5nKCl8fCF0aGlzLmNsaWVudEhlaWdodCkmJih0aGlzLl9fcGVuZGluZ1Njcm9sbFRvSW5kZXg9cil9X19zY3JvbGxUb1BlbmRpbmdJbmRleCgpe2lmKHRoaXMuX19wZW5kaW5nU2Nyb2xsVG9JbmRleCYmdGhpcy4kLml0ZW1zLmNoaWxkcmVuLmxlbmd0aCl7bGV0IHI9dGhpcy5fX3BlbmRpbmdTY3JvbGxUb0luZGV4O2RlbGV0ZSB0aGlzLl9fcGVuZGluZ1Njcm9sbFRvSW5kZXgsdGhpcy5fZGVib3VuY2VJbmNyZWFzZVBvb2wmJnRoaXMuX2RlYm91bmNlSW5jcmVhc2VQb29sLmZsdXNoKCksdGhpcy5zY3JvbGxUb0luZGV4KHIpfX19O3ZhciAkY2U9ZT0+Y2xhc3MgZXh0ZW5kcyBle3JlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLl9hZGROb2RlT2JzZXJ2ZXIoKX1faGFzQ29sdW1uR3JvdXBzKHIpe2ZvcihsZXQgbj0wO248ci5sZW5ndGg7bisrKWlmKHJbbl0ubG9jYWxOYW1lPT09InZhYWRpbi1ncmlkLWNvbHVtbi1ncm91cCIpcmV0dXJuITA7cmV0dXJuITF9X2dldENoaWxkQ29sdW1ucyhyKXtyZXR1cm4gdmguZ2V0RmxhdHRlbmVkTm9kZXMocikuZmlsdGVyKHRoaXMuX2lzQ29sdW1uRWxlbWVudCl9X2ZsYXR0ZW5Db2x1bW5Hcm91cHMocil7cmV0dXJuIHIubWFwKG49Pm4ubG9jYWxOYW1lPT09InZhYWRpbi1ncmlkLWNvbHVtbi1ncm91cCI/dGhpcy5fZ2V0Q2hpbGRDb2x1bW5zKG4pOltuXSkucmVkdWNlKChuLGkpPT5uLmNvbmNhdChpKSxbXSl9X2dldENvbHVtblRyZWUoKXtsZXQgcj12aC5nZXRGbGF0dGVuZWROb2Rlcyh0aGlzKS5maWx0ZXIodGhpcy5faXNDb2x1bW5FbGVtZW50KSxuPVtdO2ZvcihsZXQgaT1yO24ucHVzaChpKSwhIXRoaXMuX2hhc0NvbHVtbkdyb3VwcyhpKTspaT10aGlzLl9mbGF0dGVuQ29sdW1uR3JvdXBzKGkpO3JldHVybiBufV91cGRhdGVDb2x1bW5UcmVlKCl7bGV0IHI9dGhpcy5fZ2V0Q29sdW1uVHJlZSgpO3RoaXMuX2FycmF5RXF1YWxzKHIsdGhpcy5fY29sdW1uVHJlZSl8fCh0aGlzLl9jb2x1bW5UcmVlPXIpfV9hZGROb2RlT2JzZXJ2ZXIoKXt0aGlzLl9vYnNlcnZlcj1uZXcgdmgodGhpcyxyPT57bGV0IG49ci5hZGRlZE5vZGVzLmZpbHRlcihvPT5vLmxvY2FsTmFtZT09PSJ0ZW1wbGF0ZSImJm8uY2xhc3NMaXN0LmNvbnRhaW5zKCJyb3ctZGV0YWlscyIpKVswXTtuJiZ0aGlzLl9yb3dEZXRhaWxzVGVtcGxhdGUhPT1uJiYodGhpcy5fcm93RGV0YWlsc1RlbXBsYXRlPW4pO2xldCBpPW89Pm8uZmlsdGVyKHRoaXMuX2lzQ29sdW1uRWxlbWVudCkubGVuZ3RoPjA7aWYoaShyLmFkZGVkTm9kZXMpfHxpKHIucmVtb3ZlZE5vZGVzKSl7bGV0IG89ci5yZW1vdmVkTm9kZXMuZmxhdE1hcChzPT5zLl9hbGxDZWxscyksYT1zPT5vLmZpbHRlcihsPT5sLl9jb250ZW50LmNvbnRhaW5zKHMpKS5sZW5ndGg7dGhpcy5fX3JlbW92ZVNvcnRlcnModGhpcy5fc29ydGVycy5maWx0ZXIoYSkpLHRoaXMuX19yZW1vdmVGaWx0ZXJzKHRoaXMuX2ZpbHRlcnMuZmlsdGVyKGEpKSx0aGlzLl91cGRhdGVDb2x1bW5UcmVlKCl9dGhpcy5fZGVib3VuY2VyQ2hlY2tJbXBvcnRzPXNyLmRlYm91bmNlKHRoaXMuX2RlYm91bmNlckNoZWNrSW1wb3J0cyxtby5hZnRlcigyZTMpLHRoaXMuX2NoZWNrSW1wb3J0cy5iaW5kKHRoaXMpKSx0aGlzLl9lbnN1cmVGaXJzdFBhZ2VMb2FkZWQoKX0pfV9hcnJheUVxdWFscyhyLG4pe2lmKCFyfHwhbnx8ci5sZW5ndGghPW4ubGVuZ3RoKXJldHVybiExO2ZvcihsZXQgaT0wLG89ci5sZW5ndGg7aTxvO2krKylpZihyW2ldaW5zdGFuY2VvZiBBcnJheSYmbltpXWluc3RhbmNlb2YgQXJyYXkpe2lmKCF0aGlzLl9hcnJheUVxdWFscyhyW2ldLG5baV0pKXJldHVybiExfWVsc2UgaWYocltpXSE9bltpXSlyZXR1cm4hMTtyZXR1cm4hMH1fY2hlY2tJbXBvcnRzKCl7WyJ2YWFkaW4tZ3JpZC1jb2x1bW4tZ3JvdXAiLCJ2YWFkaW4tZ3JpZC1maWx0ZXIiLCJ2YWFkaW4tZ3JpZC1maWx0ZXItY29sdW1uIiwidmFhZGluLWdyaWQtdHJlZS10b2dnbGUiLCJ2YWFkaW4tZ3JpZC1zZWxlY3Rpb24tY29sdW1uIiwidmFhZGluLWdyaWQtc29ydC1jb2x1bW4iLCJ2YWFkaW4tZ3JpZC1zb3J0ZXIiXS5mb3JFYWNoKHI9PntsZXQgbj10aGlzLnF1ZXJ5U2VsZWN0b3Iocik7biYmIShuIGluc3RhbmNlb2YgbXQpJiZjb25zb2xlLndhcm4oYE1ha2Ugc3VyZSB5b3UgaGF2ZSBpbXBvcnRlZCB0aGUgcmVxdWlyZWQgbW9kdWxlIGZvciA8JHtyfT4gZWxlbWVudC5gKX0pfV91cGRhdGVGaXJzdEFuZExhc3RDb2x1bW4oKXtBcnJheS5mcm9tKHRoaXMuc2hhZG93Um9vdC5xdWVyeVNlbGVjdG9yQWxsKCJ0ciIpKS5mb3JFYWNoKHI9PnRoaXMuX3VwZGF0ZUZpcnN0QW5kTGFzdENvbHVtbkZvclJvdyhyKSl9X3VwZGF0ZUZpcnN0QW5kTGFzdENvbHVtbkZvclJvdyhyKXtBcnJheS5mcm9tKHIucXVlcnlTZWxlY3RvckFsbCgnW3BhcnR+PSJjZWxsIl06bm90KFtwYXJ0fj0iZGV0YWlscy1jZWxsIl0pJykpLnNvcnQoKG4saSk9Pm4uX2NvbHVtbi5fb3JkZXItaS5fY29sdW1uLl9vcmRlcikuZm9yRWFjaCgobixpLG8pPT57dGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJmaXJzdC1jb2x1bW4iLGk9PT0wLG4pLHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgibGFzdC1jb2x1bW4iLGk9PT1vLmxlbmd0aC0xLG4pfSl9X2lzQ29sdW1uRWxlbWVudChyKXtyZXR1cm4gci5ub2RlVHlwZT09PU5vZGUuRUxFTUVOVF9OT0RFJiYvXGJjb2x1bW5cYi8udGVzdChyLmxvY2FsTmFtZSl9fTt2YXIgS2NlPWU9PmNsYXNzIGV4dGVuZHMgZXtnZXRFdmVudENvbnRleHQocil7bGV0IG49e30saT1yLmNvbXBvc2VkUGF0aCgpLG89aVtpLmluZGV4T2YodGhpcy4kLnRhYmxlKS0zXTtyZXR1cm4gbyYmKG4uc2VjdGlvbj1bImJvZHkiLCJoZWFkZXIiLCJmb290ZXIiLCJkZXRhaWxzIl0uZmlsdGVyKGE9Pm8uZ2V0QXR0cmlidXRlKCJwYXJ0IikuaW5kZXhPZihhKT4tMSlbMF0sby5fY29sdW1uJiYobi5jb2x1bW49by5fY29sdW1uKSwobi5zZWN0aW9uPT09ImJvZHkifHxuLnNlY3Rpb249PT0iZGV0YWlscyIpJiZPYmplY3QuYXNzaWduKG4sdGhpcy5fX2dldFJvd01vZGVsKG8ucGFyZW50RWxlbWVudCkpKSxufX07dmFyIFpjZT1lPT5jbGFzcyBleHRlbmRzIGV7c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue19maWx0ZXJzOnt0eXBlOkFycmF5LHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuW119fX19cmVhZHkoKXtzdXBlci5yZWFkeSgpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigiZmlsdGVyLWNoYW5nZWQiLHRoaXMuX2ZpbHRlckNoYW5nZWQuYmluZCh0aGlzKSl9X2ZpbHRlckNoYW5nZWQocil7ci5zdG9wUHJvcGFnYXRpb24oKSx0aGlzLl9fYWRkRmlsdGVyKHIudGFyZ2V0KSx0aGlzLl9fYXBwbHlGaWx0ZXJzKCl9X19yZW1vdmVGaWx0ZXJzKHIpe3IubGVuZ3RoIT0wJiYodGhpcy5fZmlsdGVycz10aGlzLl9maWx0ZXJzLmZpbHRlcihuPT5yLmluZGV4T2Yobik8MCksdGhpcy5fX2FwcGx5RmlsdGVycygpKX1fX2FkZEZpbHRlcihyKXt0aGlzLl9maWx0ZXJzLmluZGV4T2Yocik9PT0tMSYmdGhpcy5fZmlsdGVycy5wdXNoKHIpfV9fYXBwbHlGaWx0ZXJzKCl7dGhpcy5kYXRhUHJvdmlkZXImJnRoaXMuaXNBdHRhY2hlZCYmdGhpcy5jbGVhckNhY2hlKCl9X21hcEZpbHRlcnMoKXtyZXR1cm4gdGhpcy5fZmlsdGVycy5tYXAocj0+KHtwYXRoOnIucGF0aCx2YWx1ZTpyLnZhbHVlfSkpfX07dmFyIFQwPWNsYXNzIGV4dGVuZHMgbXR7c3RhdGljIGdldCBpcygpe3JldHVybiJ2YWFkaW4tZ3JpZC10ZW1wbGF0aXplciJ9c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue2RhdGFIb3N0Ok9iamVjdCx0ZW1wbGF0ZTpPYmplY3QsX3RlbXBsYXRlSW5zdGFuY2VzOnt0eXBlOkFycmF5LHZhbHVlOmZ1bmN0aW9uKCl7cmV0dXJuW119fSxfcGFyZW50UGF0aFZhbHVlczp7dmFsdWU6ZnVuY3Rpb24oKXtyZXR1cm57fX19LF9ncmlkOk9iamVjdH19c3RhdGljIGdldCBvYnNlcnZlcnMoKXtyZXR1cm5bIl90ZW1wbGF0ZUluc3RhbmNlc0NoYW5nZWQoX3RlbXBsYXRlSW5zdGFuY2VzLiosIF9wYXJlbnRQYXRoVmFsdWVzLiopIl19Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMuX2luc3RhbmNlUHJvcHM9e2RldGFpbHNPcGVuZWQ6ITAsaW5kZXg6ITAsaXRlbTohMCxzZWxlY3RlZDohMCxleHBhbmRlZDohMCxsZXZlbDohMH19Y3JlYXRlSW5zdGFuY2UoKXt0aGlzLl9lbnN1cmVUZW1wbGF0aXplZCgpO2xldCB0PW5ldyB0aGlzLl9UZW1wbGF0ZUNsYXNzKHt9KTtyZXR1cm4gdGhpcy5hZGRJbnN0YW5jZSh0KSx0fWFkZEluc3RhbmNlKHQpe3RoaXMuX3RlbXBsYXRlSW5zdGFuY2VzLmluZGV4T2YodCk9PT0tMSYmKHRoaXMuX3RlbXBsYXRlSW5zdGFuY2VzLnB1c2godCkscmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT50aGlzLm5vdGlmeVBhdGgoIl90ZW1wbGF0ZUluc3RhbmNlcy4qIix0aGlzLl90ZW1wbGF0ZUluc3RhbmNlcykpKX1yZW1vdmVJbnN0YW5jZSh0KXtsZXQgcj10aGlzLl90ZW1wbGF0ZUluc3RhbmNlcy5pbmRleE9mKHQpO3RoaXMuc3BsaWNlKCJfdGVtcGxhdGVJbnN0YW5jZXMiLHIsMSl9X2Vuc3VyZVRlbXBsYXRpemVkKCl7dGhpcy5fVGVtcGxhdGVDbGFzc3x8KHRoaXMuX1RlbXBsYXRlQ2xhc3M9dGModGhpcy50ZW1wbGF0ZSx0aGlzLHtpbnN0YW5jZVByb3BzOnRoaXMuX2luc3RhbmNlUHJvcHMscGFyZW50TW9kZWw6ITAsZm9yd2FyZEhvc3RQcm9wOmZ1bmN0aW9uKHQscil7dGhpcy5fZm9yd2FyZFBhcmVudFByb3AodCxyKSx0aGlzLl90ZW1wbGF0ZUluc3RhbmNlcyYmdGhpcy5fdGVtcGxhdGVJbnN0YW5jZXMuZm9yRWFjaChuPT5uLm5vdGlmeVBhdGgodCxyKSl9LG5vdGlmeUluc3RhbmNlUHJvcDpmdW5jdGlvbih0LHIsbil7aWYocj09PSJpbmRleCJ8fHI9PT0iaXRlbSIpcmV0dXJuO2xldCBpPWBfXyR7cn1fX2A7aWYodFtpXT09PW4pcmV0dXJuO3RbaV09bjtsZXQgbz1BcnJheS5mcm9tKHRoaXMuX2dyaWQuJC5pdGVtcy5jaGlsZHJlbikuZmlsdGVyKGw9PnRoaXMuX2dyaWQuX2l0ZW1zRXF1YWwobC5faXRlbSx0Lml0ZW0pKVswXTtvJiZBcnJheS5mcm9tKG8uY2hpbGRyZW4pLmZvckVhY2gobD0+e2wuX2luc3RhbmNlJiYobC5faW5zdGFuY2VbaV09bixsLl9pbnN0YW5jZS5ub3RpZnlQYXRoKHIsbikpfSk7bGV0IGE9Iml0ZW0uIjtpZihBcnJheS5pc0FycmF5KHRoaXMuX2dyaWQuaXRlbXMpJiZyLmluZGV4T2YoYSk9PT0wKXtsZXQgbD10aGlzLl9ncmlkLml0ZW1zLmluZGV4T2YodC5pdGVtKSxjPXIuc2xpY2UoYS5sZW5ndGgpO3RoaXMuX2dyaWQubm90aWZ5UGF0aChgaXRlbXMuJHtsfS4ke2N9YCxuKX1sZXQgcz1gXyR7cn1JbnN0YW5jZUNoYW5nZWRDYWxsYmFja2A7dGhpcy5fZ3JpZCYmdGhpcy5fZ3JpZFtzXSYmdGhpcy5fZ3JpZFtzXSh0LG4pfX0pKX1fZm9yd2FyZFBhcmVudFByb3AodCxyKXt0aGlzLl9wYXJlbnRQYXRoVmFsdWVzW3RdPXIsdGhpcy5fdGVtcGxhdGVJbnN0YW5jZXMuZm9yRWFjaChuPT5uLm5vdGlmeVBhdGgodCxyKSl9X3RlbXBsYXRlSW5zdGFuY2VzQ2hhbmdlZCh0KXtsZXQgcixuO2lmKHQucGF0aD09PSJfdGVtcGxhdGVJbnN0YW5jZXMiKXI9MCxuPXRoaXMuX3RlbXBsYXRlSW5zdGFuY2VzLmxlbmd0aDtlbHNlIGlmKHQucGF0aD09PSJfdGVtcGxhdGVJbnN0YW5jZXMuc3BsaWNlcyIpcj10LnZhbHVlLmluZGV4LG49dC52YWx1ZS5hZGRlZENvdW50O2Vsc2UgcmV0dXJuO09iamVjdC5rZXlzKHRoaXMuX3BhcmVudFBhdGhWYWx1ZXN8fHt9KS5mb3JFYWNoKGk9Pntmb3IobGV0IG89cjtvPHIrbjtvKyspdGhpcy5fdGVtcGxhdGVJbnN0YW5jZXNbb10uc2V0KGksdGhpcy5fcGFyZW50UGF0aFZhbHVlc1tpXSl9KX19O2N1c3RvbUVsZW1lbnRzLmRlZmluZShUMC5pcyxUMCk7dmFyIEpjZT1lPT5jbGFzcyBleHRlbmRzIGV7c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue2RldGFpbHNPcGVuZWRJdGVtczp7dHlwZTpBcnJheSx2YWx1ZTpmdW5jdGlvbigpe3JldHVybltdfX0sX3Jvd0RldGFpbHNUZW1wbGF0ZTpPYmplY3Qscm93RGV0YWlsc1JlbmRlcmVyOkZ1bmN0aW9uLF9kZXRhaWxzQ2VsbHM6e3R5cGU6QXJyYXl9fX1zdGF0aWMgZ2V0IG9ic2VydmVycygpe3JldHVyblsiX2RldGFpbHNPcGVuZWRJdGVtc0NoYW5nZWQoZGV0YWlsc09wZW5lZEl0ZW1zLiosIF9yb3dEZXRhaWxzVGVtcGxhdGUsIHJvd0RldGFpbHNSZW5kZXJlcikiLCJfcm93RGV0YWlsc1RlbXBsYXRlT3JSZW5kZXJlckNoYW5nZWQoX3Jvd0RldGFpbHNUZW1wbGF0ZSwgcm93RGV0YWlsc1JlbmRlcmVyKSJdfV9yb3dEZXRhaWxzVGVtcGxhdGVPclJlbmRlcmVyQ2hhbmdlZChyLG4pe2lmKHImJm4pdGhyb3cgbmV3IEVycm9yKCJZb3Ugc2hvdWxkIG9ubHkgdXNlIGVpdGhlciBhIHJlbmRlcmVyIG9yIGEgdGVtcGxhdGUgZm9yIHJvdyBkZXRhaWxzIik7aWYocnx8bil7aWYociYmIXIudGVtcGxhdGl6ZXIpe2xldCBpPW5ldyBUMDtpLl9ncmlkPXRoaXMsaS5kYXRhSG9zdD10aGlzLmRhdGFIb3N0LGkudGVtcGxhdGU9cixyLnRlbXBsYXRpemVyPWl9dGhpcy5fY29sdW1uVHJlZSYmQXJyYXkuZnJvbSh0aGlzLiQuaXRlbXMuY2hpbGRyZW4pLmZvckVhY2goaT0+e2kucXVlcnlTZWxlY3RvcigiW3BhcnR+PWRldGFpbHMtY2VsbF0iKXx8KHRoaXMuX3VwZGF0ZVJvdyhpLHRoaXMuX2NvbHVtblRyZWVbdGhpcy5fY29sdW1uVHJlZS5sZW5ndGgtMV0pLHRoaXMuX2ExMXlVcGRhdGVSb3dEZXRhaWxzT3BlbmVkKGksITEpKSxkZWxldGUgaS5xdWVyeVNlbGVjdG9yKCJbcGFydH49ZGV0YWlscy1jZWxsXSIpLl9pbnN0YW5jZX0pLHRoaXMuZGV0YWlsc09wZW5lZEl0ZW1zLmxlbmd0aCYmKEFycmF5LmZyb20odGhpcy4kLml0ZW1zLmNoaWxkcmVuKS5mb3JFYWNoKHRoaXMuX3RvZ2dsZURldGFpbHNDZWxsLHRoaXMpLHRoaXMuX3VwZGF0ZSgpKX19X2RldGFpbHNPcGVuZWRJdGVtc0NoYW5nZWQocil7ci5wYXRoPT09ImRldGFpbHNPcGVuZWRJdGVtcy5sZW5ndGgifHwhci52YWx1ZXx8QXJyYXkuZnJvbSh0aGlzLiQuaXRlbXMuY2hpbGRyZW4pLmZvckVhY2gobj0+e3RoaXMuX3RvZ2dsZURldGFpbHNDZWxsKG4sbi5faXRlbSksdGhpcy5fYTExeVVwZGF0ZVJvd0RldGFpbHNPcGVuZWQobix0aGlzLl9pc0RldGFpbHNPcGVuZWQobi5faXRlbSkpLHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgiZGV0YWlscy1vcGVuZWQiLHRoaXMuX2lzRGV0YWlsc09wZW5lZChuLl9pdGVtKSxuKX0pfV9jb25maWd1cmVEZXRhaWxzQ2VsbChyKXtyLnNldEF0dHJpYnV0ZSgicGFydCIsImNlbGwgZGV0YWlscy1jZWxsIiksdGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJmcm96ZW4iLCEwLHIpfV90b2dnbGVEZXRhaWxzQ2VsbChyLG4pe2xldCBpPXIucXVlcnlTZWxlY3RvcignW3BhcnR+PSJkZXRhaWxzLWNlbGwiXScpO2lmKCFpKXJldHVybjtsZXQgbz0hdGhpcy5faXNEZXRhaWxzT3BlbmVkKG4pLGE9ISFpLmhpZGRlbiE9PW87KCFpLl9pbnN0YW5jZSYmIWkuX3JlbmRlcmVyfHxpLmhpZGRlbiE9PW8pJiYoaS5oaWRkZW49byxvP3Iuc3R5bGUucmVtb3ZlUHJvcGVydHkoInBhZGRpbmctYm90dG9tIik6KHRoaXMucm93RGV0YWlsc1JlbmRlcmVyPyhpLl9yZW5kZXJlcj10aGlzLnJvd0RldGFpbHNSZW5kZXJlcixpLl9yZW5kZXJlci5jYWxsKHRoaXMsaS5fY29udGVudCx0aGlzLHtpbmRleDpyLmluZGV4LGl0ZW06bn0pKTp0aGlzLl9yb3dEZXRhaWxzVGVtcGxhdGUmJiFpLl9pbnN0YW5jZSYmKGkuX2luc3RhbmNlPXRoaXMuX3Jvd0RldGFpbHNUZW1wbGF0ZS50ZW1wbGF0aXplci5jcmVhdGVJbnN0YW5jZSgpLGkuX2NvbnRlbnQuaW5uZXJIVE1MPSIiLGkuX2NvbnRlbnQuYXBwZW5kQ2hpbGQoaS5faW5zdGFuY2Uucm9vdCksdGhpcy5fdXBkYXRlSXRlbShyLG4pKSx1aSgpLHIuc3R5bGUuc2V0UHJvcGVydHkoInBhZGRpbmctYm90dG9tIixgJHtpLm9mZnNldEhlaWdodH1weGApLHJlcXVlc3RBbmltYXRpb25GcmFtZSgoKT0+dGhpcy5ub3RpZnlSZXNpemUoKSkpKSxhJiYodGhpcy5fdXBkYXRlTWV0cmljcygpLHRoaXMuX3Bvc2l0aW9uSXRlbXMoKSl9X3VwZGF0ZURldGFpbHNDZWxsSGVpZ2h0cygpe0FycmF5LmZyb20odGhpcy4kLml0ZW1zLnF1ZXJ5U2VsZWN0b3JBbGwoJ1twYXJ0fj0iZGV0YWlscy1jZWxsIl06bm90KFtoaWRkZW5dKScpKS5mb3JFYWNoKHI9PntyLnBhcmVudEVsZW1lbnQuc3R5bGUuc2V0UHJvcGVydHkoInBhZGRpbmctYm90dG9tIixgJHtyLm9mZnNldEhlaWdodH1weGApfSl9X2lzRGV0YWlsc09wZW5lZChyKXtyZXR1cm4gdGhpcy5kZXRhaWxzT3BlbmVkSXRlbXMmJnRoaXMuX2dldEl0ZW1JbmRleEluQXJyYXkocix0aGlzLmRldGFpbHNPcGVuZWRJdGVtcykhPT0tMX1vcGVuSXRlbURldGFpbHMocil7dGhpcy5faXNEZXRhaWxzT3BlbmVkKHIpfHwodGhpcy5kZXRhaWxzT3BlbmVkSXRlbXM9Wy4uLnRoaXMuZGV0YWlsc09wZW5lZEl0ZW1zLHJdKX1jbG9zZUl0ZW1EZXRhaWxzKHIpe3RoaXMuX2lzRGV0YWlsc09wZW5lZChyKSYmKHRoaXMuZGV0YWlsc09wZW5lZEl0ZW1zPXRoaXMuZGV0YWlsc09wZW5lZEl0ZW1zLmZpbHRlcihuPT4hdGhpcy5faXRlbXNFcXVhbChuLHIpKSl9X2RldGFpbHNPcGVuZWRJbnN0YW5jZUNoYW5nZWRDYWxsYmFjayhyLG4pe24/dGhpcy5vcGVuSXRlbURldGFpbHMoci5pdGVtKTp0aGlzLmNsb3NlSXRlbURldGFpbHMoci5pdGVtKX19O3ZhciBGY3Q9e1NDUk9MTElORzo1MDAsSUdOT1JFX1dIRUVMOjUwMH0sUWNlPWU9PmNsYXNzIGV4dGVuZHMgZXtzdGF0aWMgZ2V0IHByb3BlcnRpZXMoKXtyZXR1cm57X2Zyb3plbkNlbGxzOnt0eXBlOkFycmF5LHZhbHVlOigpPT5bXX0sX3Jvd1dpdGhGb2N1c2VkRWxlbWVudDpFbGVtZW50LF9kZWx0YVlBY2M6e3R5cGU6TnVtYmVyLHZhbHVlOjB9LF91c2VTdGlja3k6e3R5cGU6Qm9vbGVhbix2YWx1ZTp3aW5kb3cuQ1NTJiZ3aW5kb3cuQ1NTLnN1cHBvcnRzJiYod2luZG93LkNTUy5zdXBwb3J0cygicG9zaXRpb24iLCJzdGlja3kiKXx8d2luZG93LkNTUy5zdXBwb3J0cygicG9zaXRpb24iLCItd2Via2l0LXN0aWNreSIpKX19fXN0YXRpYyBnZXQgb2JzZXJ2ZXJzKCl7cmV0dXJuWyJfc2Nyb2xsVmlld3BvcnRIZWlnaHRVcGRhdGVkKF92aWV3cG9ydEhlaWdodCkiXX1zZXQgX3Njcm9sbFRvcChyKXt0aGlzLiQudGFibGUuc2Nyb2xsVG9wPXJ9Z2V0IF9zY3JvbGxUb3AoKXtyZXR1cm4gdGhpcy4kLnRhYmxlLnNjcm9sbFRvcH1jb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy5fc2Nyb2xsTGluZUhlaWdodD10aGlzLl9nZXRTY3JvbGxMaW5lSGVpZ2h0KCl9X2dldFNjcm9sbExpbmVIZWlnaHQoKXtsZXQgcj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTtyLnN0eWxlLmZvbnRTaXplPSJpbml0aWFsIixyLnN0eWxlLmRpc3BsYXk9Im5vbmUiLGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQocik7bGV0IG49d2luZG93LmdldENvbXB1dGVkU3R5bGUocikuZm9udFNpemU7cmV0dXJuIGRvY3VtZW50LmJvZHkucmVtb3ZlQ2hpbGQociksbj93aW5kb3cucGFyc2VJbnQobik6dm9pZCAwfV9zY3JvbGxWaWV3cG9ydEhlaWdodFVwZGF0ZWQocil7dGhpcy5fc2Nyb2xsUGFnZUhlaWdodD1yLXRoaXMuJC5oZWFkZXIuY2xpZW50SGVpZ2h0LXRoaXMuJC5mb290ZXIuY2xpZW50SGVpZ2h0LXRoaXMuX3Njcm9sbExpbmVIZWlnaHR9cmVhZHkoKXtzdXBlci5yZWFkeSgpLHRoaXMuJC5vdXRlcnNjcm9sbGVyPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpLHRoaXMuc2Nyb2xsVGFyZ2V0PXRoaXMuJC50YWJsZSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoIndoZWVsIix0aGlzLl9vbldoZWVsKSx0aGlzLiQuaXRlbXMuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXNpbiIscj0+e2xldCBuPXIuY29tcG9zZWRQYXRoKCkuaW5kZXhPZih0aGlzLiQuaXRlbXMpO3RoaXMuX3Jvd1dpdGhGb2N1c2VkRWxlbWVudD1yLmNvbXBvc2VkUGF0aCgpW24tMV19KSx0aGlzLiQuaXRlbXMuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXNvdXQiLCgpPT50aGlzLl9yb3dXaXRoRm9jdXNlZEVsZW1lbnQ9dm9pZCAwKSx0aGlzLnNjcm9sbFRhcmdldC5hZGRFdmVudExpc3RlbmVyKCJtb3VzZWRvd24iLCgpPT50aGlzLl9fbW91c2VEb3duPSEwKSx0aGlzLnNjcm9sbFRhcmdldC5hZGRFdmVudExpc3RlbmVyKCJtb3VzZXVwIiwoKT0+e3RoaXMuX19tb3VzZURvd249ITEsdGhpcy5fX3BlbmRpbmdSZW9yZGVyJiYodGhpcy5fX3BlbmRpbmdSZW9yZGVyPSExLHNldFRpbWVvdXQoKCk9PnRoaXMuX3Jlb3JkZXJSb3dzKCksRmN0LlNDUk9MTElORykpfSl9c2Nyb2xsVG9JbmRleChyKXt0aGlzLl9hY2Nlc3NJcm9uTGlzdEFQSSgoKT0+c3VwZXIuc2Nyb2xsVG9JbmRleChyKSl9X29uV2hlZWwocil7aWYoci5jdHJsS2V5fHx0aGlzLl9oYXNTY3JvbGxlZEFuY2VzdG9yKHIudGFyZ2V0LHIuZGVsdGFYLHIuZGVsdGFZKSlyZXR1cm47bGV0IG49dGhpcy4kLnRhYmxlLGk9ci5kZWx0YVk7aWYoci5kZWx0YU1vZGU9PT1XaGVlbEV2ZW50LkRPTV9ERUxUQV9MSU5FP2kqPXRoaXMuX3Njcm9sbExpbmVIZWlnaHQ6ci5kZWx0YU1vZGU9PT1XaGVlbEV2ZW50LkRPTV9ERUxUQV9QQUdFJiYoaSo9dGhpcy5fc2Nyb2xsUGFnZUhlaWdodCksdGhpcy5fd2hlZWxBbmltYXRpb25GcmFtZSl7dGhpcy5fZGVsdGFZQWNjKz1pLHIucHJldmVudERlZmF1bHQoKTtyZXR1cm59aSs9dGhpcy5fZGVsdGFZQWNjLHRoaXMuX2RlbHRhWUFjYz0wLHRoaXMuX3doZWVsQW5pbWF0aW9uRnJhbWU9ITAsdGhpcy5fZGVib3VuY2VyV2hlZWxBbmltYXRpb25GcmFtZT1zci5kZWJvdW5jZSh0aGlzLl9kZWJvdW5jZXJXaGVlbEFuaW1hdGlvbkZyYW1lLE5pLCgpPT50aGlzLl93aGVlbEFuaW1hdGlvbkZyYW1lPSExKTtsZXQgbz1NYXRoLmFicyhyLmRlbHRhWCkrTWF0aC5hYnMoaSk7dGhpcy5fY2FuU2Nyb2xsKG4sci5kZWx0YVgsaSk/KHIucHJldmVudERlZmF1bHQoKSxuLnNjcm9sbFRvcCs9aSxuLnNjcm9sbExlZnQrPXIuZGVsdGFYLHRoaXMuX3Njcm9sbEhhbmRsZXIoKSx0aGlzLl9oYXNSZXNpZHVhbE1vbWVudHVtPSEwLHRoaXMuX2lnbm9yZU5ld1doZWVsPSEwLHRoaXMuX2RlYm91bmNlcklnbm9yZU5ld1doZWVsPXNyLmRlYm91bmNlKHRoaXMuX2RlYm91bmNlcklnbm9yZU5ld1doZWVsLG1vLmFmdGVyKEZjdC5JR05PUkVfV0hFRUwpLCgpPT50aGlzLl9pZ25vcmVOZXdXaGVlbD0hMSkpOnRoaXMuX2hhc1Jlc2lkdWFsTW9tZW50dW0mJm88PXRoaXMuX3ByZXZpb3VzTW9tZW50dW18fHRoaXMuX2lnbm9yZU5ld1doZWVsP3IucHJldmVudERlZmF1bHQoKTpvPnRoaXMuX3ByZXZpb3VzTW9tZW50dW0mJih0aGlzLl9oYXNSZXNpZHVhbE1vbWVudHVtPSExKSx0aGlzLl9wcmV2aW91c01vbWVudHVtPW99X2hhc1Njcm9sbGVkQW5jZXN0b3IocixuLGkpe2lmKHIubG9jYWxOYW1lPT09InZhYWRpbi1ncmlkLWNlbGwtY29udGVudCIpcmV0dXJuITE7aWYodGhpcy5fY2FuU2Nyb2xsKHIsbixpKSYmWyJhdXRvIiwic2Nyb2xsIl0uaW5kZXhPZihnZXRDb21wdXRlZFN0eWxlKHIpLm92ZXJmbG93KSE9PS0xKXJldHVybiEwO2lmKHIhPT10aGlzJiZyLnBhcmVudEVsZW1lbnQpcmV0dXJuIHRoaXMuX2hhc1Njcm9sbGVkQW5jZXN0b3Ioci5wYXJlbnRFbGVtZW50LG4saSl9X2NhblNjcm9sbChyLG4saSl7cmV0dXJuIGk+MCYmci5zY3JvbGxUb3A8ci5zY3JvbGxIZWlnaHQtci5vZmZzZXRIZWlnaHR8fGk8MCYmci5zY3JvbGxUb3A+MHx8bj4wJiZyLnNjcm9sbExlZnQ8ci5zY3JvbGxXaWR0aC1yLm9mZnNldFdpZHRofHxuPDAmJnIuc2Nyb2xsTGVmdD4wfV9zY2hlZHVsZVNjcm9sbGluZygpe3RoaXMuX3Njcm9sbGluZ0ZyYW1lfHwodGhpcy5fc2Nyb2xsaW5nRnJhbWU9cmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT50aGlzLl90b2dnbGVBdHRyaWJ1dGUoInNjcm9sbGluZyIsITAsdGhpcy4kLnNjcm9sbGVyKSkpLHRoaXMuX2RlYm91bmNlU2Nyb2xsaW5nPXNyLmRlYm91bmNlKHRoaXMuX2RlYm91bmNlU2Nyb2xsaW5nLG1vLmFmdGVyKEZjdC5TQ1JPTExJTkcpLCgpPT57Y2FuY2VsQW5pbWF0aW9uRnJhbWUodGhpcy5fc2Nyb2xsaW5nRnJhbWUpLGRlbGV0ZSB0aGlzLl9zY3JvbGxpbmdGcmFtZSx0aGlzLl90b2dnbGVBdHRyaWJ1dGUoInNjcm9sbGluZyIsITEsdGhpcy4kLnNjcm9sbGVyKSx0aGlzLl9yZW9yZGVyUm93cygpfSl9X2FmdGVyU2Nyb2xsKCl7dGhpcy5fdHJhbnNsYXRlU3RhdGlvbmFyeUVsZW1lbnRzKCksdGhpcy5oYXNBdHRyaWJ1dGUoInJlb3JkZXJpbmciKXx8dGhpcy5fc2NoZWR1bGVTY3JvbGxpbmcoKSx0aGlzLl91cGRhdGVPdmVyZmxvdygpfV91cGRhdGVPdmVyZmxvdygpe2xldCByPSIiLG49dGhpcy4kLnRhYmxlO24uc2Nyb2xsVG9wPG4uc2Nyb2xsSGVpZ2h0LW4uY2xpZW50SGVpZ2h0JiYocis9IiBib3R0b20iKSxuLnNjcm9sbFRvcD4wJiYocis9IiB0b3AiKSxuLnNjcm9sbExlZnQ8bi5zY3JvbGxXaWR0aC1uLmNsaWVudFdpZHRoJiYocis9IiByaWdodCIpLG4uc2Nyb2xsTGVmdD4wJiYocis9IiBsZWZ0IiksdGhpcy5fZGVib3VuY2VPdmVyZmxvdz1zci5kZWJvdW5jZSh0aGlzLl9kZWJvdW5jZU92ZXJmbG93LE5pLCgpPT57bGV0IGk9ci50cmltKCk7aS5sZW5ndGg+MCYmdGhpcy5nZXRBdHRyaWJ1dGUoIm92ZXJmbG93IikhPT1pP3RoaXMuc2V0QXR0cmlidXRlKCJvdmVyZmxvdyIsaSk6aS5sZW5ndGg9PTAmJnRoaXMuaGFzQXR0cmlidXRlKCJvdmVyZmxvdyIpJiZ0aGlzLnJlbW92ZUF0dHJpYnV0ZSgib3ZlcmZsb3ciKX0pfV9yZW9yZGVyUm93cygpe2lmKHRoaXMuX19tb3VzZURvd24pe3RoaXMuX19wZW5kaW5nUmVvcmRlcj0hMDtyZXR1cm59bGV0IHI9dGhpcy4kLml0ZW1zLG49ci5xdWVyeVNlbGVjdG9yQWxsKCJ0ciIpO2lmKCFuLmxlbmd0aClyZXR1cm47bGV0IGk9dGhpcy5fdmlydHVhbFN0YXJ0K3RoaXMuX3ZpZHhPZmZzZXQsbz10aGlzLl9yb3dXaXRoRm9jdXNlZEVsZW1lbnR8fEFycmF5LmZyb20obikuZmlsdGVyKGw9PiFsLmhpZGRlbilbMF07aWYoIW8pcmV0dXJuO2xldCBhPW8uaW5kZXgtaSxzPUFycmF5LmZyb20obikuaW5kZXhPZihvKS1hO2lmKHM+MClmb3IobGV0IGw9MDtsPHM7bCsrKXIuYXBwZW5kQ2hpbGQobltsXSk7ZWxzZSBpZihzPDApZm9yKGxldCBsPW4ubGVuZ3RoK3M7bDxuLmxlbmd0aDtsKyspci5pbnNlcnRCZWZvcmUobltsXSxuWzBdKTtpZih0aGlzLl9zYWZhcmkpe2xldHt0cmFuc2Zvcm06bH09dGhpcy4kLmhlYWRlci5zdHlsZTt0aGlzLiQuaGVhZGVyLnN0eWxlLnRyYW5zZm9ybT0iIixzZXRUaW1lb3V0KCgpPT50aGlzLiQuaGVhZGVyLnN0eWxlLnRyYW5zZm9ybT1sKX19X2Zyb3plbkNlbGxzQ2hhbmdlZCgpe3RoaXMuX2RlYm91bmNlckNhY2hlRWxlbWVudHM9c3IuZGVib3VuY2UodGhpcy5fZGVib3VuY2VyQ2FjaGVFbGVtZW50cyxjaSwoKT0+e0FycmF5LmZyb20odGhpcy5zaGFkb3dSb290LnF1ZXJ5U2VsZWN0b3JBbGwoJ1twYXJ0fj0iY2VsbCJdJykpLmZvckVhY2goZnVuY3Rpb24ocil7ci5zdHlsZS50cmFuc2Zvcm09IiJ9KSx0aGlzLl9mcm96ZW5DZWxscz1BcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbCh0aGlzLiQudGFibGUucXVlcnlTZWxlY3RvckFsbCgiW2Zyb3plbl0iKSksdGhpcy5fdXBkYXRlU2Nyb2xsZXJNZWFzdXJlbWVudHMoKSx0aGlzLl90cmFuc2xhdGVTdGF0aW9uYXJ5RWxlbWVudHMoKX0pLHRoaXMuX3VwZGF0ZUxhc3RGcm96ZW4oKX1fdXBkYXRlU2Nyb2xsZXJNZWFzdXJlbWVudHMoKXt0aGlzLl9mcm96ZW5DZWxscy5sZW5ndGg+MCYmdGhpcy5fX2lzUlRMJiYodGhpcy5fX3Njcm9sbGVyTWV0cmljcz17c2Nyb2xsV2lkdGg6dGhpcy4kLnRhYmxlLnNjcm9sbFdpZHRoLGNsaWVudFdpZHRoOnRoaXMuJC50YWJsZS5jbGllbnRXaWR0aH0pfV91cGRhdGVMYXN0RnJvemVuKCl7aWYoIXRoaXMuX2NvbHVtblRyZWUpcmV0dXJuO2xldCByPXRoaXMuX2NvbHVtblRyZWVbdGhpcy5fY29sdW1uVHJlZS5sZW5ndGgtMV0uc2xpY2UoMCk7ci5zb3J0KChpLG8pPT5pLl9vcmRlci1vLl9vcmRlcik7bGV0IG49ci5yZWR1Y2UoKGksbyxhKT0+KG8uX2xhc3RGcm96ZW49ITEsby5mcm96ZW4mJiFvLmhpZGRlbj9hOmkpLHZvaWQgMCk7biE9PXZvaWQgMCYmKHJbbl0uX2xhc3RGcm96ZW49ITApfV90cmFuc2xhdGVTdGF0aW9uYXJ5RWxlbWVudHMoKXtsZXQgcj1NYXRoLm1heCgwLHRoaXMuX3Njcm9sbExlZnQpLG49TWF0aC5tYXgoMCx0aGlzLl9zY3JvbGxUb3ApLGk9MCxvPTAsYT0wO2lmKHRoaXMuX3VzZVN0aWNreXx8KGk9cixvPW4sYT10aGlzLiQudGFibGUuY2xpZW50SGVpZ2h0LXRoaXMuJC5mb290ZXIub2Zmc2V0SGVpZ2h0LXRoaXMuJC5mb290ZXIub2Zmc2V0VG9wKSx0aGlzLiQuaGVhZGVyLnN0eWxlLnRyYW5zZm9ybT10aGlzLl9nZXRUcmFuc2xhdGUoLXIraSxvKSx0aGlzLiQuZm9vdGVyLnN0eWxlLnRyYW5zZm9ybT10aGlzLl9nZXRUcmFuc2xhdGUoLXIraSxvK2EpLHRoaXMuJC5pdGVtcy5zdHlsZS50cmFuc2Zvcm09dGhpcy5fZ2V0VHJhbnNsYXRlKC1yK2ksMCksdGhpcy5fZnJvemVuQ2VsbHMubGVuZ3RoPjApe2xldCBzPXRoaXMuX19pc1JUTD90aGlzLl9fZ2V0Tm9ybWFsaXplZFNjcm9sbExlZnQodGhpcy4kLnRhYmxlKSt0aGlzLl9fc2Nyb2xsZXJNZXRyaWNzLmNsaWVudFdpZHRoLXRoaXMuX19zY3JvbGxlck1ldHJpY3Muc2Nyb2xsV2lkdGg6dGhpcy5fc2Nyb2xsTGVmdCxsPXRoaXMuX2dldFRyYW5zbGF0ZShzLDApO2ZvcihsZXQgYz0wO2M8dGhpcy5fZnJvemVuQ2VsbHMubGVuZ3RoO2MrKyl0aGlzLl9mcm96ZW5DZWxsc1tjXS5zdHlsZS50cmFuc2Zvcm09bH19X2dldFRyYW5zbGF0ZShyLG4pe3JldHVybmB0cmFuc2xhdGUoJHtyfXB4LCAke259cHgpYH19O3ZhciB0dWU9ZT0+Y2xhc3MgZXh0ZW5kcyBle3N0YXRpYyBnZXQgcHJvcGVydGllcygpe3JldHVybntzZWxlY3RlZEl0ZW1zOnt0eXBlOk9iamVjdCxub3RpZnk6ITAsdmFsdWU6KCk9PltdfX19c3RhdGljIGdldCBvYnNlcnZlcnMoKXtyZXR1cm5bIl9zZWxlY3RlZEl0ZW1zQ2hhbmdlZChzZWxlY3RlZEl0ZW1zLiopIl19X2lzU2VsZWN0ZWQocil7cmV0dXJuIHRoaXMuc2VsZWN0ZWRJdGVtcyYmdGhpcy5fZ2V0SXRlbUluZGV4SW5BcnJheShyLHRoaXMuc2VsZWN0ZWRJdGVtcyk+LTF9c2VsZWN0SXRlbShyKXt0aGlzLl9pc1NlbGVjdGVkKHIpfHwodGhpcy5zZWxlY3RlZEl0ZW1zPVsuLi50aGlzLnNlbGVjdGVkSXRlbXMscl0pfWRlc2VsZWN0SXRlbShyKXt0aGlzLl9pc1NlbGVjdGVkKHIpJiYodGhpcy5zZWxlY3RlZEl0ZW1zPXRoaXMuc2VsZWN0ZWRJdGVtcy5maWx0ZXIobj0+IXRoaXMuX2l0ZW1zRXF1YWwobixyKSkpfV90b2dnbGVJdGVtKHIpe3RoaXMuX2dldEl0ZW1JbmRleEluQXJyYXkocix0aGlzLnNlbGVjdGVkSXRlbXMpPT09LTE/dGhpcy5zZWxlY3RJdGVtKHIpOnRoaXMuZGVzZWxlY3RJdGVtKHIpfV9zZWxlY3RlZEl0ZW1zQ2hhbmdlZChyKXt0aGlzLiQuaXRlbXMuY2hpbGRyZW4ubGVuZ3RoJiYoci5wYXRoPT09InNlbGVjdGVkSXRlbXMifHxyLnBhdGg9PT0ic2VsZWN0ZWRJdGVtcy5zcGxpY2VzIikmJkFycmF5LmZyb20odGhpcy4kLml0ZW1zLmNoaWxkcmVuKS5mb3JFYWNoKG49Pnt0aGlzLl91cGRhdGVJdGVtKG4sbi5faXRlbSl9KX1fc2VsZWN0ZWRJbnN0YW5jZUNoYW5nZWRDYWxsYmFjayhyLG4pe24/dGhpcy5zZWxlY3RJdGVtKHIuaXRlbSk6dGhpcy5kZXNlbGVjdEl0ZW0oci5pdGVtKX19O3ZhciBldWU9ZT0+Y2xhc3MgZXh0ZW5kcyBle3N0YXRpYyBnZXQgcHJvcGVydGllcygpe3JldHVybnttdWx0aVNvcnQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sX3NvcnRlcnM6e3R5cGU6QXJyYXksdmFsdWU6ZnVuY3Rpb24oKXtyZXR1cm5bXX19LF9wcmV2aW91c1NvcnRlcnM6e3R5cGU6QXJyYXksdmFsdWU6ZnVuY3Rpb24oKXtyZXR1cm5bXX19fX1yZWFkeSgpe3N1cGVyLnJlYWR5KCksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJzb3J0ZXItY2hhbmdlZCIsdGhpcy5fb25Tb3J0ZXJDaGFuZ2VkKX1fb25Tb3J0ZXJDaGFuZ2VkKHIpe2xldCBuPXIudGFyZ2V0O3Iuc3RvcFByb3BhZ2F0aW9uKCksdGhpcy5fX3VwZGF0ZVNvcnRlcihuKSx0aGlzLl9fYXBwbHlTb3J0ZXJzKCl9X19yZW1vdmVTb3J0ZXJzKHIpe3IubGVuZ3RoIT0wJiYodGhpcy5fc29ydGVycz10aGlzLl9zb3J0ZXJzLmZpbHRlcihuPT5yLmluZGV4T2Yobik8MCksdGhpcy5tdWx0aVNvcnQmJnRoaXMuX191cGRhdGVTb3J0T3JkZXJzKCksdGhpcy5fX2FwcGx5U29ydGVycygpKX1fX3VwZGF0ZVNvcnRPcmRlcnMoKXt0aGlzLl9zb3J0ZXJzLmZvckVhY2goKHIsbik9PnIuX29yZGVyPXRoaXMuX3NvcnRlcnMubGVuZ3RoPjE/bjpudWxsLHRoaXMpfV9fdXBkYXRlU29ydGVyKHIpe2lmKCEoIXIuZGlyZWN0aW9uJiZ0aGlzLl9zb3J0ZXJzLmluZGV4T2Yocik9PT0tMSkpe2lmKHIuX29yZGVyPW51bGwsdGhpcy5tdWx0aVNvcnQpdGhpcy5fcmVtb3ZlQXJyYXlJdGVtKHRoaXMuX3NvcnRlcnMsciksci5kaXJlY3Rpb24mJnRoaXMuX3NvcnRlcnMudW5zaGlmdChyKSx0aGlzLl9fdXBkYXRlU29ydE9yZGVycygpO2Vsc2UgaWYoci5kaXJlY3Rpb24pe2xldCBuPXRoaXMuX3NvcnRlcnMuZmlsdGVyKGk9PmkhPXIpO3RoaXMuX3NvcnRlcnM9W3JdLG4uZm9yRWFjaChpPT57aS5fb3JkZXI9bnVsbCxpLmRpcmVjdGlvbj1udWxsfSl9fX1fX2FwcGx5U29ydGVycygpe3RoaXMuZGF0YVByb3ZpZGVyJiZ0aGlzLmlzQXR0YWNoZWQmJkpTT04uc3RyaW5naWZ5KHRoaXMuX3ByZXZpb3VzU29ydGVycykhPT1KU09OLnN0cmluZ2lmeSh0aGlzLl9tYXBTb3J0ZXJzKCkpJiZ0aGlzLmNsZWFyQ2FjaGUoKSx0aGlzLl9hMTF5VXBkYXRlU29ydGVycygpLHRoaXMuX3ByZXZpb3VzU29ydGVycz10aGlzLl9tYXBTb3J0ZXJzKCl9X21hcFNvcnRlcnMoKXtyZXR1cm4gdGhpcy5fc29ydGVycy5tYXAocj0+KHtwYXRoOnIucGF0aCxkaXJlY3Rpb246ci5kaXJlY3Rpb259KSl9X3JlbW92ZUFycmF5SXRlbShyLG4pe2xldCBpPXIuaW5kZXhPZihuKTtpPi0xJiZyLnNwbGljZShpLDEpfX07dmFyIHJ1ZT1lPT5jbGFzcyBleHRlbmRzIGV7c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue2NlbGxDbGFzc05hbWVHZW5lcmF0b3I6RnVuY3Rpb259fXN0YXRpYyBnZXQgb2JzZXJ2ZXJzKCl7cmV0dXJuWyJfX2NlbGxDbGFzc05hbWVHZW5lcmF0b3JDaGFuZ2VkKGNlbGxDbGFzc05hbWVHZW5lcmF0b3IpIl19X19jZWxsQ2xhc3NOYW1lR2VuZXJhdG9yQ2hhbmdlZCgpe3RoaXMuZ2VuZXJhdGVDZWxsQ2xhc3NOYW1lcygpfWdlbmVyYXRlQ2VsbENsYXNzTmFtZXMoKXtBcnJheS5mcm9tKHRoaXMuJC5pdGVtcy5jaGlsZHJlbikuZmlsdGVyKHI9PiFyLmhpZGRlbikuZm9yRWFjaChyPT50aGlzLl9nZW5lcmF0ZUNlbGxDbGFzc05hbWVzKHIsdGhpcy5fX2dldFJvd01vZGVsKHIpKSl9X2dlbmVyYXRlQ2VsbENsYXNzTmFtZXMocixuKXtBcnJheS5mcm9tKHIuY2hpbGRyZW4pLmZvckVhY2goaT0+e2lmKGkuX19nZW5lcmF0ZWRDbGFzc2VzJiZpLl9fZ2VuZXJhdGVkQ2xhc3Nlcy5mb3JFYWNoKG89PmkuY2xhc3NMaXN0LnJlbW92ZShvKSksdGhpcy5jZWxsQ2xhc3NOYW1lR2VuZXJhdG9yKXtsZXQgbz10aGlzLmNlbGxDbGFzc05hbWVHZW5lcmF0b3IoaS5fY29sdW1uLG4pO2kuX19nZW5lcmF0ZWRDbGFzc2VzPW8mJm8uc3BsaXQoIiAiKS5maWx0ZXIoYT0+YS5sZW5ndGg+MCksaS5fX2dlbmVyYXRlZENsYXNzZXMmJmkuX19nZW5lcmF0ZWRDbGFzc2VzLmZvckVhY2goYT0+aS5jbGFzc0xpc3QuYWRkKGEpKX19KX19O3ZhciBFUD17QkVUV0VFTjoiYmV0d2VlbiIsT05fVE9QOiJvbi10b3AiLE9OX1RPUF9PUl9CRVRXRUVOOiJvbi10b3Atb3ItYmV0d2VlbiIsT05fR1JJRDoib24tZ3JpZCJ9LEMwPXtPTl9UT1A6Im9uLXRvcCIsQUJPVkU6ImFib3ZlIixCRUxPVzoiYmVsb3ciLEVNUFRZOiJlbXB0eSJ9LG51ZT1lPT5jbGFzcyBleHRlbmRzIGV7c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue2Ryb3BNb2RlOlN0cmluZyxyb3dzRHJhZ2dhYmxlOkJvb2xlYW4sZHJhZ0ZpbHRlcjpGdW5jdGlvbixkcm9wRmlsdGVyOkZ1bmN0aW9uLF9fZG5kQXV0b1Njcm9sbFRocmVzaG9sZDp7dmFsdWU6NTB9fX1zdGF0aWMgZ2V0IG9ic2VydmVycygpe3JldHVyblsiX2RyYWdEcm9wQWNjZXNzQ2hhbmdlZChyb3dzRHJhZ2dhYmxlLCBkcm9wTW9kZSwgZHJhZ0ZpbHRlciwgZHJvcEZpbHRlcikiXX1yZWFkeSgpe3N1cGVyLnJlYWR5KCksdGhpcy4kLnRhYmxlLmFkZEV2ZW50TGlzdGVuZXIoImRyYWdzdGFydCIsdGhpcy5fb25EcmFnU3RhcnQuYmluZCh0aGlzKSksdGhpcy4kLnRhYmxlLmFkZEV2ZW50TGlzdGVuZXIoImRyYWdlbmQiLHRoaXMuX29uRHJhZ0VuZC5iaW5kKHRoaXMpKSx0aGlzLiQudGFibGUuYWRkRXZlbnRMaXN0ZW5lcigiZHJhZ292ZXIiLHRoaXMuX29uRHJhZ092ZXIuYmluZCh0aGlzKSksdGhpcy4kLnRhYmxlLmFkZEV2ZW50TGlzdGVuZXIoImRyYWdsZWF2ZSIsdGhpcy5fb25EcmFnTGVhdmUuYmluZCh0aGlzKSksdGhpcy4kLnRhYmxlLmFkZEV2ZW50TGlzdGVuZXIoImRyb3AiLHRoaXMuX29uRHJvcC5iaW5kKHRoaXMpKSx0aGlzLiQudGFibGUuYWRkRXZlbnRMaXN0ZW5lcigiZHJhZ2VudGVyIixyPT57dGhpcy5kcm9wTW9kZSYmKHIucHJldmVudERlZmF1bHQoKSxyLnN0b3BQcm9wYWdhdGlvbigpKX0pfV9vbkRyYWdTdGFydChyKXtpZih0aGlzLnJvd3NEcmFnZ2FibGUpe2xldCBuPXIudGFyZ2V0O2lmKG4ubG9jYWxOYW1lPT09InZhYWRpbi1ncmlkLWNlbGwtY29udGVudCImJihuPW4uYXNzaWduZWRTbG90LnBhcmVudE5vZGUucGFyZW50Tm9kZSksbi5wYXJlbnROb2RlIT09dGhpcy4kLml0ZW1zKXJldHVybjtpZihyLnN0b3BQcm9wYWdhdGlvbigpLHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgiZHJhZ2dpbmctcm93cyIsITAsdGhpcyksdGhpcy5fc2FmYXJpKXtsZXQgcz1uLnN0eWxlLnRyYW5zZm9ybTtuLnN0eWxlLnRvcD0vdHJhbnNsYXRlWVwoKC4qKVwpLy5leGVjKHMpWzFdLG4uc3R5bGUudHJhbnNmb3JtPSJub25lIixyZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCk9PntuLnN0eWxlLnRvcD0iIixuLnN0eWxlLnRyYW5zZm9ybT1zfSl9bGV0IGk9bi5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTt0aGlzLl9pb3M/ci5kYXRhVHJhbnNmZXIuc2V0RHJhZ0ltYWdlKG4pOnIuZGF0YVRyYW5zZmVyLnNldERyYWdJbWFnZShuLHIuY2xpZW50WC1pLmxlZnQsci5jbGllbnRZLWkudG9wKTtsZXQgbz1bbl07dGhpcy5faXNTZWxlY3RlZChuLl9pdGVtKSYmKG89dGhpcy5fX2dldFZpZXdwb3J0Um93cygpLmZpbHRlcihzPT50aGlzLl9pc1NlbGVjdGVkKHMuX2l0ZW0pKS5maWx0ZXIocz0+IXRoaXMuZHJhZ0ZpbHRlcnx8dGhpcy5kcmFnRmlsdGVyKHRoaXMuX19nZXRSb3dNb2RlbChzKSkpKSxyLmRhdGFUcmFuc2Zlci5zZXREYXRhKCJ0ZXh0Iix0aGlzLl9fZm9ybWF0RGVmYXVsdFRyYW5zZmVyRGF0YShvKSksbi5zZXRBdHRyaWJ1dGUoImRyYWdzdGFydCIsby5sZW5ndGg+MT9vLmxlbmd0aDoiIiksdGhpcy51cGRhdGVTdHlsZXMoeyItLV9ncmlkLWRyYWctc3RhcnQteCI6YCR7ci5jbGllbnRYLWkubGVmdCsyMH1weGAsIi0tX2dyaWQtZHJhZy1zdGFydC15IjpgJHtyLmNsaWVudFktaS50b3ArMTB9cHhgfSkscmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT57bi5yZW1vdmVBdHRyaWJ1dGUoImRyYWdzdGFydCIpLHRoaXMudXBkYXRlU3R5bGVzKHsiLS1fZ3JpZC1kcmFnLXN0YXJ0LXgiOiIiLCItLV9ncmlkLWRyYWctc3RhcnQteSI6IiJ9KX0pO2xldCBhPW5ldyBDdXN0b21FdmVudCgiZ3JpZC1kcmFnc3RhcnQiLHtkZXRhaWw6e2RyYWdnZWRJdGVtczpvLm1hcChzPT5zLl9pdGVtKSxzZXREcmFnRGF0YToocyxsKT0+ci5kYXRhVHJhbnNmZXIuc2V0RGF0YShzLGwpLHNldERyYWdnZWRJdGVtc0NvdW50OnM9Pm4uc2V0QXR0cmlidXRlKCJkcmFnc3RhcnQiLHMpfX0pO2Eub3JpZ2luYWxFdmVudD1yLHRoaXMuZGlzcGF0Y2hFdmVudChhKX19X29uRHJhZ0VuZChyKXt0aGlzLl90b2dnbGVBdHRyaWJ1dGUoImRyYWdnaW5nLXJvd3MiLCExLHRoaXMpLHIuc3RvcFByb3BhZ2F0aW9uKCk7bGV0IG49bmV3IEN1c3RvbUV2ZW50KCJncmlkLWRyYWdlbmQiKTtuLm9yaWdpbmFsRXZlbnQ9cix0aGlzLmRpc3BhdGNoRXZlbnQobil9X29uRHJhZ0xlYXZlKHIpe3Iuc3RvcFByb3BhZ2F0aW9uKCksdGhpcy5fY2xlYXJEcmFnU3R5bGVzKCl9X29uRHJhZ092ZXIocil7aWYodGhpcy5kcm9wTW9kZSl7aWYodGhpcy5fZHJvcExvY2F0aW9uPXZvaWQgMCx0aGlzLl9kcmFnT3Zlckl0ZW09dm9pZCAwLHRoaXMuX19kbmRBdXRvU2Nyb2xsKHIuY2xpZW50WSkpe3RoaXMuX2NsZWFyRHJhZ1N0eWxlcygpO3JldHVybn1sZXQgbj1yLmNvbXBvc2VkUGF0aCgpLmZpbHRlcihpPT5pLmxvY2FsTmFtZT09PSJ0ciIpWzBdO2lmKCF0aGlzLl9lZmZlY3RpdmVTaXplfHx0aGlzLmRyb3BNb2RlPT09RVAuT05fR1JJRCl0aGlzLl9kcm9wTG9jYXRpb249QzAuRU1QVFk7ZWxzZSBpZighbnx8bi5wYXJlbnROb2RlIT09dGhpcy4kLml0ZW1zKXtpZihuKXJldHVybjtpZih0aGlzLmRyb3BNb2RlPT09RVAuQkVUV0VFTnx8dGhpcy5kcm9wTW9kZT09PUVQLk9OX1RPUF9PUl9CRVRXRUVOKW49QXJyYXkuZnJvbSh0aGlzLiQuaXRlbXMuY2hpbGRyZW4pLmZpbHRlcihpPT4haS5oaWRkZW4pLnBvcCgpLHRoaXMuX2Ryb3BMb2NhdGlvbj1DMC5CRUxPVztlbHNlIHJldHVybn1lbHNle2xldCBpPW4uZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7aWYodGhpcy5fZHJvcExvY2F0aW9uPUMwLk9OX1RPUCx0aGlzLmRyb3BNb2RlPT09RVAuQkVUV0VFTil7bGV0IG89ci5jbGllbnRZLWkudG9wPGkuYm90dG9tLXIuY2xpZW50WTt0aGlzLl9kcm9wTG9jYXRpb249bz9DMC5BQk9WRTpDMC5CRUxPV31lbHNlIHRoaXMuZHJvcE1vZGU9PT1FUC5PTl9UT1BfT1JfQkVUV0VFTiYmKHIuY2xpZW50WS1pLnRvcDxpLmhlaWdodC8zP3RoaXMuX2Ryb3BMb2NhdGlvbj1DMC5BQk9WRTpyLmNsaWVudFktaS50b3A+aS5oZWlnaHQvMyoyJiYodGhpcy5fZHJvcExvY2F0aW9uPUMwLkJFTE9XKSl9aWYobiYmbi5oYXNBdHRyaWJ1dGUoImRyb3AtZGlzYWJsZWQiKSl7dGhpcy5fZHJvcExvY2F0aW9uPXZvaWQgMDtyZXR1cm59ci5zdG9wUHJvcGFnYXRpb24oKSxyLnByZXZlbnREZWZhdWx0KCksdGhpcy5fZHJvcExvY2F0aW9uPT09QzAuRU1QVFk/dGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJkcmFnb3ZlciIsITAsdGhpcyk6bj8odGhpcy5fZHJhZ092ZXJJdGVtPW4uX2l0ZW0sbi5nZXRBdHRyaWJ1dGUoImRyYWdvdmVyIikhPT10aGlzLl9kcm9wTG9jYXRpb24mJm4uc2V0QXR0cmlidXRlKCJkcmFnb3ZlciIsdGhpcy5fZHJvcExvY2F0aW9uKSk6dGhpcy5fY2xlYXJEcmFnU3R5bGVzKCl9fV9fZG5kQXV0b1Njcm9sbChyKXtpZih0aGlzLl9fZG5kQXV0b1Njcm9sbGluZylyZXR1cm4hMDtsZXQgbj10aGlzLiQuaGVhZGVyLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLmJvdHRvbSxpPXRoaXMuJC5mb290ZXIuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wLG89bi1yK3RoaXMuX19kbmRBdXRvU2Nyb2xsVGhyZXNob2xkLGE9ci1pK3RoaXMuX19kbmRBdXRvU2Nyb2xsVGhyZXNob2xkLHM9MDtpZihhPjA/cz1hKjI6bz4wJiYocz0tbyoyKSxzKXtsZXQgbD10aGlzLiQudGFibGUuc2Nyb2xsVG9wO2lmKHRoaXMuJC50YWJsZS5zY3JvbGxUb3ArPXMsbCE9PXRoaXMuJC50YWJsZS5zY3JvbGxUb3ApcmV0dXJuIHRoaXMuX19kbmRBdXRvU2Nyb2xsaW5nPSEwLHNldFRpbWVvdXQoKCk9PnRoaXMuX19kbmRBdXRvU2Nyb2xsaW5nPSExLDIwKSx0aGlzLl9zY3JvbGxIYW5kbGVyKCksITB9fV9fZ2V0Vmlld3BvcnRSb3dzKCl7bGV0IHI9dGhpcy4kLmhlYWRlci5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS5ib3R0b20sbj10aGlzLiQuZm9vdGVyLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcDtyZXR1cm4gQXJyYXkuZnJvbSh0aGlzLiQuaXRlbXMuY2hpbGRyZW4pLmZpbHRlcihpPT57bGV0IG89aS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtyZXR1cm4gby5ib3R0b20+ciYmby50b3A8bn0pfV9jbGVhckRyYWdTdHlsZXMoKXt0aGlzLnJlbW92ZUF0dHJpYnV0ZSgiZHJhZ292ZXIiKSxBcnJheS5mcm9tKHRoaXMuJC5pdGVtcy5jaGlsZHJlbikuZm9yRWFjaChyPT5yLnJlbW92ZUF0dHJpYnV0ZSgiZHJhZ292ZXIiKSl9X29uRHJvcChyKXtpZih0aGlzLmRyb3BNb2RlKXtyLnN0b3BQcm9wYWdhdGlvbigpLHIucHJldmVudERlZmF1bHQoKTtsZXQgbj1yLmRhdGFUcmFuc2Zlci50eXBlcyYmQXJyYXkuZnJvbShyLmRhdGFUcmFuc2Zlci50eXBlcykubWFwKG89Pih7dHlwZTpvLGRhdGE6ci5kYXRhVHJhbnNmZXIuZ2V0RGF0YShvKX0pKTt0aGlzLl9jbGVhckRyYWdTdHlsZXMoKTtsZXQgaT1uZXcgQ3VzdG9tRXZlbnQoImdyaWQtZHJvcCIse2J1YmJsZXM6ci5idWJibGVzLGNhbmNlbGFibGU6ci5jYW5jZWxhYmxlLGRldGFpbDp7ZHJvcFRhcmdldEl0ZW06dGhpcy5fZHJhZ092ZXJJdGVtLGRyb3BMb2NhdGlvbjp0aGlzLl9kcm9wTG9jYXRpb24sZHJhZ0RhdGE6bn19KTtpLm9yaWdpbmFsRXZlbnQ9cix0aGlzLmRpc3BhdGNoRXZlbnQoaSl9fV9fZm9ybWF0RGVmYXVsdFRyYW5zZmVyRGF0YShyKXtyZXR1cm4gci5tYXAobj0+QXJyYXkuZnJvbShuLmNoaWxkcmVuKS5maWx0ZXIoaT0+IWkuaGlkZGVuJiZpLmdldEF0dHJpYnV0ZSgicGFydCIpLmluZGV4T2YoImRldGFpbHMtY2VsbCIpPT09LTEpLnNvcnQoKGksbyk9PmkuX2NvbHVtbi5fb3JkZXI+by5fY29sdW1uLl9vcmRlcj8xOi0xKS5tYXAoaT0+aS5fY29udGVudC50ZXh0Q29udGVudC50cmltKCkpLmZpbHRlcihpPT5pKS5qb2luKCIJIikpLmpvaW4oYApgKX1fZHJhZ0Ryb3BBY2Nlc3NDaGFuZ2VkKCl7dGhpcy5maWx0ZXJEcmFnQW5kRHJvcCgpfWZpbHRlckRyYWdBbmREcm9wKCl7QXJyYXkuZnJvbSh0aGlzLiQuaXRlbXMuY2hpbGRyZW4pLmZpbHRlcihyPT4hci5oaWRkZW4pLmZvckVhY2gocj0+e3RoaXMuX2ZpbHRlckRyYWdBbmREcm9wKHIsdGhpcy5fX2dldFJvd01vZGVsKHIpKX0pfV9maWx0ZXJEcmFnQW5kRHJvcChyLG4pe2xldCBpPSF0aGlzLnJvd3NEcmFnZ2FibGV8fHRoaXMuZHJhZ0ZpbHRlciYmIXRoaXMuZHJhZ0ZpbHRlcihuKSxvPSF0aGlzLmRyb3BNb2RlfHx0aGlzLmRyb3BGaWx0ZXImJiF0aGlzLmRyb3BGaWx0ZXIobik7QXJyYXkuZnJvbShyLmNoaWxkcmVuKS5tYXAocz0+cy5fY29udGVudCkuZm9yRWFjaChzPT57aT9zLnJlbW92ZUF0dHJpYnV0ZSgiZHJhZ2dhYmxlIik6cy5zZXRBdHRyaWJ1dGUoImRyYWdnYWJsZSIsITApfSksdGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJkcmFnLWRpc2FibGVkIixpLHIpLHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgiZHJvcC1kaXNhYmxlZCIsbyxyKX19O3ZhciBpdWU9ZT0+Y2xhc3MgZXh0ZW5kcyBle3N0YXRpYyBnZXQgcHJvcGVydGllcygpe3JldHVybntfaGVhZGVyRm9jdXNhYmxlOnt0eXBlOk9iamVjdCxvYnNlcnZlcjoiX2ZvY3VzYWJsZUNoYW5nZWQifSxfaXRlbXNGb2N1c2FibGU6e3R5cGU6T2JqZWN0LG9ic2VydmVyOiJfZm9jdXNhYmxlQ2hhbmdlZCJ9LF9mb290ZXJGb2N1c2FibGU6e3R5cGU6T2JqZWN0LG9ic2VydmVyOiJfZm9jdXNhYmxlQ2hhbmdlZCJ9LF9uYXZpZ2F0aW5nSXNIaWRkZW46Qm9vbGVhbixfZm9jdXNlZEl0ZW1JbmRleDp7dHlwZTpOdW1iZXIsdmFsdWU6MH0sX2ZvY3VzZWRDb2x1bW5PcmRlcjpOdW1iZXIsaW50ZXJhY3Rpbmc6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMSxyZWZsZWN0VG9BdHRyaWJ1dGU6ITAscmVhZE9ubHk6ITAsb2JzZXJ2ZXI6Il9pbnRlcmFjdGluZ0NoYW5nZWQifX19cmVhZHkoKXtzdXBlci5yZWFkeSgpLCEodGhpcy5faW9zfHx0aGlzLl9hbmRyb2lkKSYmKHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigia2V5ZG93biIsdGhpcy5fb25LZXlEb3duKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImtleXVwIix0aGlzLl9vbktleVVwKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImZvY3VzaW4iLHRoaXMuX29uRm9jdXNJbiksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJmb2N1c291dCIsdGhpcy5fb25Gb2N1c091dCksdGhpcy4kLnRhYmxlLmFkZEV2ZW50TGlzdGVuZXIoImZvY3VzaW4iLHRoaXMuX29uQ2VsbEZvY3VzSW4uYmluZCh0aGlzKSksdGhpcy4kLnRhYmxlLmFkZEV2ZW50TGlzdGVuZXIoImZvY3Vzb3V0Iix0aGlzLl9vbkNlbGxGb2N1c091dC5iaW5kKHRoaXMpKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoIm1vdXNlZG93biIsKCk9Pnt0aGlzLl90b2dnbGVBdHRyaWJ1dGUoIm5hdmlnYXRpbmciLCExLHRoaXMpLHRoaXMuX2lzTW91c2Vkb3duPSEwfSksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJtb3VzZXVwIiwoKT0+dGhpcy5faXNNb3VzZWRvd249ITEpKX1fZm9jdXNhYmxlQ2hhbmdlZChyLG4pe24mJm4uc2V0QXR0cmlidXRlKCJ0YWJpbmRleCIsIi0xIiksciYmdGhpcy5fdXBkYXRlR3JpZFNlY3Rpb25Gb2N1c1RhcmdldChyKX1faW50ZXJhY3RpbmdDaGFuZ2VkKCl7dGhpcy5fdXBkYXRlR3JpZFNlY3Rpb25Gb2N1c1RhcmdldCh0aGlzLl9oZWFkZXJGb2N1c2FibGUpLHRoaXMuX3VwZGF0ZUdyaWRTZWN0aW9uRm9jdXNUYXJnZXQodGhpcy5faXRlbXNGb2N1c2FibGUpLHRoaXMuX3VwZGF0ZUdyaWRTZWN0aW9uRm9jdXNUYXJnZXQodGhpcy5fZm9vdGVyRm9jdXNhYmxlKX1fb25LZXlEb3duKHIpe2xldCBuPXIua2V5LGk7c3dpdGNoKG4pe2Nhc2UiQXJyb3dVcCI6Y2FzZSJBcnJvd0Rvd24iOmNhc2UiQXJyb3dMZWZ0IjpjYXNlIkFycm93UmlnaHQiOmNhc2UiUGFnZVVwIjpjYXNlIlBhZ2VEb3duIjpjYXNlIkhvbWUiOmNhc2UiRW5kIjppPSJOYXZpZ2F0aW9uIjticmVhaztjYXNlIkVudGVyIjpjYXNlIkVzY2FwZSI6Y2FzZSJGMiI6aT0iSW50ZXJhY3Rpb24iO2JyZWFrO2Nhc2UiVGFiIjppPSJUYWIiO2JyZWFrO2Nhc2UiICI6aT0iU3BhY2UiO2JyZWFrfXRoaXMuX2RldGVjdEludGVyYWN0aW5nKHIpLHRoaXMuaW50ZXJhY3RpbmcmJmkhPT0iSW50ZXJhY3Rpb24iJiYoaT12b2lkIDApLGkmJnRoaXNbYF9vbiR7aX1LZXlEb3duYF0ocixuKX1fZW5zdXJlU2Nyb2xsZWRUb0luZGV4KHIpe0FycmF5LmZyb20odGhpcy4kLml0ZW1zLmNoaWxkcmVuKS5maWx0ZXIoaT0+aS5pbmRleD09PXIpWzBdfHx0aGlzLl9zY3JvbGxUb0luZGV4KHIpfV9vbk5hdmlnYXRpb25LZXlEb3duKHIsbil7dGhpcy5fc2Nyb2xsSGFuZGxlcigpLHIucHJldmVudERlZmF1bHQoKTtmdW5jdGlvbiBpKEIpe3JldHVybiBBcnJheS5wcm90b3R5cGUuaW5kZXhPZi5jYWxsKEIucGFyZW50Tm9kZS5jaGlsZHJlbixCKX1sZXQgbz10aGlzLl9sYXN0VmlzaWJsZUluZGV4LXRoaXMuX2ZpcnN0VmlzaWJsZUluZGV4LTEsYT0wLHM9MDtzd2l0Y2gobil7Y2FzZSJBcnJvd1JpZ2h0IjphPXRoaXMuX19pc1JUTD8tMToxO2JyZWFrO2Nhc2UiQXJyb3dMZWZ0IjphPXRoaXMuX19pc1JUTD8xOi0xO2JyZWFrO2Nhc2UiSG9tZSI6YT0tMS8wLHIuY3RybEtleSYmKHM9LTEvMCk7YnJlYWs7Y2FzZSJFbmQiOmE9MS8wLHIuY3RybEtleSYmKHM9MS8wKTticmVhaztjYXNlIkFycm93RG93biI6cz0xO2JyZWFrO2Nhc2UiQXJyb3dVcCI6cz0tMTticmVhaztjYXNlIlBhZ2VEb3duIjpzPW87YnJlYWs7Y2FzZSJQYWdlVXAiOnM9LW87YnJlYWt9bGV0IGw9ci5jb21wb3NlZFBhdGgoKVswXSxjPWkobCksdT10aGlzLl9lbGVtZW50TWF0Y2hlcyhsLCdbcGFydH49ImRldGFpbHMtY2VsbCJdJyksaD1sLnBhcmVudE5vZGUsZj1oLnBhcmVudE5vZGUscD0oZj09PXRoaXMuJC5pdGVtcz90aGlzLl9lZmZlY3RpdmVTaXplOmYuY2hpbGRyZW4ubGVuZ3RoKS0xLGQ9Zj09PXRoaXMuJC5pdGVtcz90aGlzLl9mb2N1c2VkSXRlbUluZGV4IT09dm9pZCAwP3RoaXMuX2ZvY3VzZWRJdGVtSW5kZXg6aC5pbmRleDppKGgpLGc9TWF0aC5tYXgoMCxNYXRoLm1pbihkK3MscCkpLF89ITE7aWYoZj09PXRoaXMuJC5pdGVtcyl7bGV0IEI9aC5faXRlbSxJPXRoaXMuX2NhY2hlLmdldEl0ZW1Gb3JJbmRleChnKTt1P189cz09PTA6Xz1zPT09MSYmdGhpcy5faXNEZXRhaWxzT3BlbmVkKEIpfHxzPT09LTEmJmchPT1kJiZ0aGlzLl9pc0RldGFpbHNPcGVuZWQoSSksXyE9PXUmJihzPT09MSYmX3x8cz09PS0xJiYhXykmJihnPWQpfWlmKGYhPT10aGlzLiQuaXRlbXMpe2lmKGc+ZClmb3IoO2c8cCYmZi5jaGlsZHJlbltnXS5oaWRkZW47KWcrKztlbHNlIGlmKGc8ZClmb3IoO2c+MCYmZi5jaGlsZHJlbltnXS5oaWRkZW47KWctLX10aGlzLl9mb2N1c2VkQ29sdW1uT3JkZXI9PT12b2lkIDAmJih1P3RoaXMuX2ZvY3VzZWRDb2x1bW5PcmRlcj0wOnRoaXMuX2ZvY3VzZWRDb2x1bW5PcmRlcj10aGlzLl9nZXRDb2x1bW5zKGYsZCkuZmlsdGVyKEI9PiFCLmhpZGRlbilbY10uX29yZGVyKTtsZXQgeT10aGlzLl9nZXRDb2x1bW5zKGYsZykuZmlsdGVyKEI9PiFCLmhpZGRlbikseD15Lm1hcChCPT5CLl9vcmRlcikuc29ydCgoQixJKT0+Qi1JKSxiPXgubGVuZ3RoLTEsUz14LmluZGV4T2YoeC5zbGljZSgwKS5zb3J0KChCLEkpPT5NYXRoLmFicyhCLXRoaXMuX2ZvY3VzZWRDb2x1bW5PcmRlciktTWF0aC5hYnMoSS10aGlzLl9mb2N1c2VkQ29sdW1uT3JkZXIpKVswXSksQz1zPT09MCYmdT9TOk1hdGgubWF4KDAsTWF0aC5taW4oUythLGIpKTtDIT09UyYmKHRoaXMuX2ZvY3VzZWRDb2x1bW5PcmRlcj12b2lkIDApLGY9PT10aGlzLiQuaXRlbXMmJnRoaXMuX2Vuc3VyZVNjcm9sbGVkVG9JbmRleChnKSx0aGlzLl90b2dnbGVBdHRyaWJ1dGUoIm5hdmlnYXRpbmciLCEwLHRoaXMpO2xldCBrPXkucmVkdWNlKChCLEksTCk9PihCW0kuX29yZGVyXT1MLEIpLHt9KVt4W0NdXSxPPWY9PT10aGlzLiQuaXRlbXM/QXJyYXkuZnJvbShmLmNoaWxkcmVuKS5maWx0ZXIoQj0+Qi5pbmRleD09PWcpWzBdOmYuY2hpbGRyZW5bZ107aWYoIU8pcmV0dXJuO2xldCBEPV8/QXJyYXkuZnJvbShPLmNoaWxkcmVuKS5maWx0ZXIoQj0+dGhpcy5fZWxlbWVudE1hdGNoZXMoQiwnW3BhcnR+PSJkZXRhaWxzLWNlbGwiXScpKVswXTpPLmNoaWxkcmVuW2tdO2lmKHRoaXMuX3Njcm9sbEhvcml6b250YWxseVRvQ2VsbChEKSxmPT09dGhpcy4kLml0ZW1zJiYodGhpcy5fZm9jdXNlZEl0ZW1JbmRleD1nKSxmPT09dGhpcy4kLml0ZW1zKXtsZXQgQj1ELmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLEk9dGhpcy4kLmZvb3Rlci5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AsTD10aGlzLiQuaGVhZGVyLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLmJvdHRvbTtCLmJvdHRvbT5JPyh0aGlzLiQudGFibGUuc2Nyb2xsVG9wKz1CLmJvdHRvbS1JLHRoaXMuX3Njcm9sbEhhbmRsZXIoKSk6Qi50b3A8TCYmKHRoaXMuJC50YWJsZS5zY3JvbGxUb3AtPUwtQi50b3AsdGhpcy5fc2Nyb2xsSGFuZGxlcigpKX1ELmZvY3VzKCl9X29uSW50ZXJhY3Rpb25LZXlEb3duKHIsbil7bGV0IGk9ci5jb21wb3NlZFBhdGgoKVswXSxvPWkubG9jYWxOYW1lPT09ImlucHV0IiYmIS9eKGJ1dHRvbnxjaGVja2JveHxjb2xvcnxmaWxlfGltYWdlfHJhZGlvfHJhbmdlfHJlc2V0fHN1Ym1pdCkkL2kudGVzdChpLnR5cGUpLGE7c3dpdGNoKG4pe2Nhc2UiRW50ZXIiOmE9dGhpcy5pbnRlcmFjdGluZz8hbzohMDticmVhaztjYXNlIkVzY2FwZSI6YT0hMTticmVhaztjYXNlIkYyIjphPSF0aGlzLmludGVyYWN0aW5nO2JyZWFrfWxldHtjZWxsOnN9PXRoaXMuX2dldEdyaWRFdmVudExvY2F0aW9uKHIpO2lmKHRoaXMuaW50ZXJhY3RpbmchPT1hJiZzIT09bnVsbClpZihhKXtsZXQgbD1zLl9jb250ZW50LnF1ZXJ5U2VsZWN0b3IoIltmb2N1cy10YXJnZXRdIil8fHMuX2NvbnRlbnQuZmlyc3RFbGVtZW50Q2hpbGQ7bCYmKHIucHJldmVudERlZmF1bHQoKSxsLmZvY3VzKCksdGhpcy5fc2V0SW50ZXJhY3RpbmcoITApLHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgibmF2aWdhdGluZyIsITEsdGhpcykpfWVsc2Ugci5wcmV2ZW50RGVmYXVsdCgpLHRoaXMuX2ZvY3VzZWRDb2x1bW5PcmRlcj12b2lkIDAscy5mb2N1cygpLHRoaXMuX3NldEludGVyYWN0aW5nKCExKSx0aGlzLl90b2dnbGVBdHRyaWJ1dGUoIm5hdmlnYXRpbmciLCEwLHRoaXMpfV9wcmVkaWN0Rm9jdXNTdGVwVGFyZ2V0KHIsbil7bGV0IGk9W3RoaXMuJC50YWJsZSx0aGlzLl9oZWFkZXJGb2N1c2FibGUsdGhpcy5faXRlbXNGb2N1c2FibGUsdGhpcy5fZm9vdGVyRm9jdXNhYmxlLHRoaXMuJC5mb2N1c2V4aXRdLG89aS5pbmRleE9mKHIpO2ZvcihvKz1uO28+PTAmJm88PWkubGVuZ3RoLTEmJighaVtvXXx8aVtvXS5wYXJlbnROb2RlLmhpZGRlbik7KW8rPW47cmV0dXJuIGlbb119X29uVGFiS2V5RG93bihyKXtsZXQgbj10aGlzLl9wcmVkaWN0Rm9jdXNTdGVwVGFyZ2V0KHIuY29tcG9zZWRQYXRoKClbMF0sci5zaGlmdEtleT8tMToxKTtpZihuPT09dGhpcy4kLnRhYmxlKXRoaXMuJC50YWJsZS5mb2N1cygpO2Vsc2UgaWYobj09PXRoaXMuJC5mb2N1c2V4aXQpdGhpcy4kLmZvY3VzZXhpdC5mb2N1cygpO2Vsc2UgaWYobj09PXRoaXMuX2l0ZW1zRm9jdXNhYmxlKXtsZXQgaT1uLG89dGhpcy5faXRlbXNGb2N1c2FibGUucGFyZW50Tm9kZTtpZih0aGlzLl9lbnN1cmVTY3JvbGxlZFRvSW5kZXgodGhpcy5fZm9jdXNlZEl0ZW1JbmRleCksby5pbmRleCE9PXRoaXMuX2ZvY3VzZWRJdGVtSW5kZXgpe2xldCBhPUFycmF5LmZyb20oby5jaGlsZHJlbikuaW5kZXhPZih0aGlzLl9pdGVtc0ZvY3VzYWJsZSkscz1BcnJheS5mcm9tKHRoaXMuJC5pdGVtcy5jaGlsZHJlbikuZmlsdGVyKGw9PmwuaW5kZXg9PT10aGlzLl9mb2N1c2VkSXRlbUluZGV4KVswXTtzJiYoaT1zLmNoaWxkcmVuW2FdKX1yLnByZXZlbnREZWZhdWx0KCksaS5mb2N1cygpfWVsc2Ugci5wcmV2ZW50RGVmYXVsdCgpLG4uZm9jdXMoKTt0aGlzLl90b2dnbGVBdHRyaWJ1dGUoIm5hdmlnYXRpbmciLCEwLHRoaXMpfV9vblNwYWNlS2V5RG93bihyKXtyLnByZXZlbnREZWZhdWx0KCk7bGV0IG49ci5jb21wb3NlZFBhdGgoKVswXTsoIW4uX2NvbnRlbnR8fCFuLl9jb250ZW50LmZpcnN0RWxlbWVudENoaWxkKSYmdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBDdXN0b21FdmVudCgiY2VsbC1hY3RpdmF0ZSIse2RldGFpbDp7bW9kZWw6dGhpcy5fX2dldFJvd01vZGVsKG4ucGFyZW50RWxlbWVudCl9fSkpfV9vbktleVVwKHIpe2lmKCEvXiggfFNwYWNlQmFyKSQvLnRlc3Qoci5rZXkpKXJldHVybjtyLnByZXZlbnREZWZhdWx0KCk7bGV0IG49ci5jb21wb3NlZFBhdGgoKVswXTtpZihuLl9jb250ZW50JiZuLl9jb250ZW50LmZpcnN0RWxlbWVudENoaWxkKXtsZXQgaT10aGlzLmhhc0F0dHJpYnV0ZSgibmF2aWdhdGluZyIpO24uX2NvbnRlbnQuZmlyc3RFbGVtZW50Q2hpbGQuY2xpY2soKSx0aGlzLl90b2dnbGVBdHRyaWJ1dGUoIm5hdmlnYXRpbmciLGksdGhpcyl9fV9vbkZvY3VzSW4ocil7dGhpcy5faXNNb3VzZWRvd258fHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgibmF2aWdhdGluZyIsITAsdGhpcyk7bGV0IG49ci5jb21wb3NlZFBhdGgoKVswXTtuPT09dGhpcy4kLnRhYmxlfHxuPT09dGhpcy4kLmZvY3VzZXhpdD8odGhpcy5fcHJlZGljdEZvY3VzU3RlcFRhcmdldChuLG49PT10aGlzLiQudGFibGU/MTotMSkuZm9jdXMoKSx0aGlzLl9zZXRJbnRlcmFjdGluZyghMSkpOnRoaXMuX2RldGVjdEludGVyYWN0aW5nKHIpfV9vbkZvY3VzT3V0KHIpe3RoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgibmF2aWdhdGluZyIsITEsdGhpcyksdGhpcy5fZGV0ZWN0SW50ZXJhY3Rpbmcocil9X29uQ2VsbEZvY3VzSW4ocil7bGV0e3NlY3Rpb246bixjZWxsOml9PXRoaXMuX2dldEdyaWRFdmVudExvY2F0aW9uKHIpO3RoaXMuX2RldGVjdEludGVyYWN0aW5nKHIpLG4mJmkmJih0aGlzLl9hY3RpdmVSb3dHcm91cD1uLHRoaXMuJC5oZWFkZXI9PT1uP3RoaXMuX2hlYWRlckZvY3VzYWJsZT1pOnRoaXMuJC5pdGVtcz09PW4/dGhpcy5faXRlbXNGb2N1c2FibGU9aTp0aGlzLiQuZm9vdGVyPT09biYmKHRoaXMuX2Zvb3RlckZvY3VzYWJsZT1pKSxpLl9jb250ZW50LmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCJjZWxsLWZvY3VzaW4iLHtidWJibGVzOiExfSkpLGkuZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoImNlbGwtZm9jdXMiLHtidWJibGVzOiEwLGNvbXBvc2VkOiEwfSkpKSx0aGlzLl9kZXRlY3RGb2N1c2VkSXRlbUluZGV4KHIpfV9vbkNlbGxGb2N1c091dChyKXtyLmNvbXBvc2VkUGF0aCgpLmluZGV4T2YodGhpcy4kLnRhYmxlKT09PTMmJnIuY29tcG9zZWRQYXRoKClbMF0uX2NvbnRlbnQuZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoImNlbGwtZm9jdXNvdXQiLHtidWJibGVzOiExfSkpfV9kZXRlY3RJbnRlcmFjdGluZyhyKXtsZXQgbj1yLmNvbXBvc2VkUGF0aCgpLnNvbWUoaT0+aS5sb2NhbE5hbWU9PT0idmFhZGluLWdyaWQtY2VsbC1jb250ZW50Iik7dGhpcy5fc2V0SW50ZXJhY3Rpbmcobil9X2RldGVjdEZvY3VzZWRJdGVtSW5kZXgocil7bGV0e3NlY3Rpb246bixyb3c6aX09dGhpcy5fZ2V0R3JpZEV2ZW50TG9jYXRpb24ocik7bj09PXRoaXMuJC5pdGVtcyYmKHRoaXMuX2ZvY3VzZWRJdGVtSW5kZXg9aS5pbmRleCl9X3VwZGF0ZUdyaWRTZWN0aW9uRm9jdXNUYXJnZXQocil7aWYoIXIpcmV0dXJuO2xldCBuPXRoaXMuX2dldEdyaWRTZWN0aW9uRnJvbUZvY3VzVGFyZ2V0KHIpLGk9dGhpcy5pbnRlcmFjdGluZyYmbj09PXRoaXMuX2FjdGl2ZVJvd0dyb3VwO3IudGFiSW5kZXg9aT8tMTowfV9wcmV2ZW50U2Nyb2xsZXJSb3RhdGluZ0NlbGxGb2N1cyhyLG4pe3IuaW5kZXg9PT10aGlzLl9mb2N1c2VkSXRlbUluZGV4JiZ0aGlzLmhhc0F0dHJpYnV0ZSgibmF2aWdhdGluZyIpJiZ0aGlzLl9hY3RpdmVSb3dHcm91cD09PXRoaXMuJC5pdGVtcyYmKHRoaXMuX25hdmlnYXRpbmdJc0hpZGRlbj0hMCx0aGlzLl90b2dnbGVBdHRyaWJ1dGUoIm5hdmlnYXRpbmciLCExLHRoaXMpKSxuPT09dGhpcy5fZm9jdXNlZEl0ZW1JbmRleCYmdGhpcy5fbmF2aWdhdGluZ0lzSGlkZGVuJiYodGhpcy5fbmF2aWdhdGluZ0lzSGlkZGVuPSExLHRoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgibmF2aWdhdGluZyIsITAsdGhpcykpfV9nZXRDb2x1bW5zKHIsbil7bGV0IGk9dGhpcy5fY29sdW1uVHJlZS5sZW5ndGgtMTtyZXR1cm4gcj09PXRoaXMuJC5oZWFkZXI/aT1uOnI9PT10aGlzLiQuZm9vdGVyJiYoaT10aGlzLl9jb2x1bW5UcmVlLmxlbmd0aC0xLW4pLHRoaXMuX2NvbHVtblRyZWVbaV19X3Jlc2V0S2V5Ym9hcmROYXZpZ2F0aW9uKCl7aWYodGhpcy4kLmhlYWRlci5maXJzdEVsZW1lbnRDaGlsZCYmKHRoaXMuX2hlYWRlckZvY3VzYWJsZT1BcnJheS5mcm9tKHRoaXMuJC5oZWFkZXIuZmlyc3RFbGVtZW50Q2hpbGQuY2hpbGRyZW4pLmZpbHRlcihyPT4hci5oaWRkZW4pWzBdKSx0aGlzLiQuaXRlbXMuZmlyc3RFbGVtZW50Q2hpbGQpe2xldCByPXRoaXMuX2l0ZXJhdGVJdGVtcygobixpKT0+e2lmKHRoaXMuX2ZpcnN0VmlzaWJsZUluZGV4PT09aSlyZXR1cm4gdGhpcy4kLml0ZW1zLmNoaWxkcmVuW25dfSk7ciYmKHRoaXMuX2l0ZW1zRm9jdXNhYmxlPUFycmF5LmZyb20oci5jaGlsZHJlbikuZmlsdGVyKG49PiFuLmhpZGRlbilbMF0pfXRoaXMuJC5mb290ZXIuZmlyc3RFbGVtZW50Q2hpbGQmJih0aGlzLl9mb290ZXJGb2N1c2FibGU9QXJyYXkuZnJvbSh0aGlzLiQuZm9vdGVyLmZpcnN0RWxlbWVudENoaWxkLmNoaWxkcmVuKS5maWx0ZXIocj0+IXIuaGlkZGVuKVswXSl9X3Njcm9sbEhvcml6b250YWxseVRvQ2VsbChyKXtpZihyLmhhc0F0dHJpYnV0ZSgiZnJvemVuIil8fHRoaXMuX2VsZW1lbnRNYXRjaGVzKHIsJ1twYXJ0fj0iZGV0YWlscy1jZWxsIl0nKSlyZXR1cm47bGV0IG49ci5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxpPXIucGFyZW50Tm9kZSxvPUFycmF5LmZyb20oaS5jaGlsZHJlbikuaW5kZXhPZihyKSxhPXRoaXMuJC50YWJsZS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxzPWEubGVmdCxsPWEucmlnaHQ7Zm9yKGxldCBjPW8tMTtjPj0wO2MtLSl7bGV0IHU9aS5jaGlsZHJlbltjXTtpZighKHUuaGFzQXR0cmlidXRlKCJoaWRkZW4iKXx8dGhpcy5fZWxlbWVudE1hdGNoZXModSwnW3BhcnR+PSJkZXRhaWxzLWNlbGwiXScpKSYmdS5oYXNBdHRyaWJ1dGUoImZyb3plbiIpKXtzPXUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkucmlnaHQ7YnJlYWt9fWZvcihsZXQgYz1vKzE7YzxpLmNoaWxkcmVuLmxlbmd0aDtjKyspe2xldCB1PWkuY2hpbGRyZW5bY107aWYoISh1Lmhhc0F0dHJpYnV0ZSgiaGlkZGVuIil8fHRoaXMuX2VsZW1lbnRNYXRjaGVzKHUsJ1twYXJ0fj0iZGV0YWlscy1jZWxsIl0nKSkmJnUuaGFzQXR0cmlidXRlKCJmcm96ZW4iKSl7bD11LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLmxlZnQ7YnJlYWt9fW4ubGVmdDxzJiYodGhpcy4kLnRhYmxlLnNjcm9sbExlZnQrPU1hdGgucm91bmQobi5sZWZ0LXMpKSxuLnJpZ2h0PmwmJih0aGlzLiQudGFibGUuc2Nyb2xsTGVmdCs9TWF0aC5yb3VuZChuLnJpZ2h0LWwpKX1fZWxlbWVudE1hdGNoZXMocixuKXtyZXR1cm4gci5tYXRjaGVzP3IubWF0Y2hlcyhuKTpBcnJheS5mcm9tKHIucGFyZW50Tm9kZS5xdWVyeVNlbGVjdG9yQWxsKG4pKS5pbmRleE9mKHIpIT09LTF9X2dldEdyaWRFdmVudExvY2F0aW9uKHIpe2xldCBuPXIuY29tcG9zZWRQYXRoKCksaT1uLmluZGV4T2YodGhpcy4kLnRhYmxlKSxvPWk+PTE/bltpLTFdOm51bGwsYT1pPj0yP25baS0yXTpudWxsLHM9aT49Mz9uW2ktM106bnVsbDtyZXR1cm57c2VjdGlvbjpvLHJvdzphLGNlbGw6c319X2dldEdyaWRTZWN0aW9uRnJvbUZvY3VzVGFyZ2V0KHIpe3JldHVybiByPT09dGhpcy5faGVhZGVyRm9jdXNhYmxlP3RoaXMuJC5oZWFkZXI6cj09PXRoaXMuX2l0ZW1zRm9jdXNhYmxlP3RoaXMuJC5pdGVtczpyPT09dGhpcy5fZm9vdGVyRm9jdXNhYmxlP3RoaXMuJC5mb290ZXI6bnVsbH19O2Z1bmN0aW9uIG91ZShlLHQscil7bGV0IG49MTtlLmZvckVhY2goaT0+e24lMTA9PT0wJiZuKyssaS5fb3JkZXI9cituKnQsbisrfSl9dmFyIGF1ZT1lPT5jbGFzcyBleHRlbmRzIHloKGUpe3N0YXRpYyBnZXQgcHJvcGVydGllcygpe3JldHVybntjb2x1bW5SZW9yZGVyaW5nQWxsb3dlZDp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxfb3JkZXJCYXNlU2NvcGU6e3R5cGU6TnVtYmVyLHZhbHVlOjFlN319fXN0YXRpYyBnZXQgb2JzZXJ2ZXJzKCl7cmV0dXJuWyJfdXBkYXRlT3JkZXJzKF9jb2x1bW5UcmVlLCBfY29sdW1uVHJlZS4qKSJdfXJlYWR5KCl7c3VwZXIucmVhZHkoKSxFbSh0aGlzLCJ0cmFjayIsdGhpcy5fb25UcmFja0V2ZW50KSx0aGlzLl9yZW9yZGVyR2hvc3Q9dGhpcy5zaGFkb3dSb290LnF1ZXJ5U2VsZWN0b3IoJ1twYXJ0PSJyZW9yZGVyLWdob3N0Il0nKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoInRvdWNoc3RhcnQiLHRoaXMuX29uVG91Y2hTdGFydC5iaW5kKHRoaXMpKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoInRvdWNobW92ZSIsdGhpcy5fb25Ub3VjaE1vdmUuYmluZCh0aGlzKSksdGhpcy5hZGRFdmVudExpc3RlbmVyKCJ0b3VjaGVuZCIsdGhpcy5fb25Ub3VjaEVuZC5iaW5kKHRoaXMpKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImNvbnRleHRtZW51Iix0aGlzLl9vbkNvbnRleHRNZW51LmJpbmQodGhpcykpfV9vbkNvbnRleHRNZW51KHIpe3RoaXMuaGFzQXR0cmlidXRlKCJyZW9yZGVyaW5nIikmJnIucHJldmVudERlZmF1bHQoKX1fb25Ub3VjaFN0YXJ0KHIpe3RoaXMuX3N0YXJ0VG91Y2hSZW9yZGVyVGltZW91dD1zZXRUaW1lb3V0KCgpPT57dGhpcy5fb25UcmFja1N0YXJ0KHtkZXRhaWw6e3g6ci50b3VjaGVzWzBdLmNsaWVudFgseTpyLnRvdWNoZXNbMF0uY2xpZW50WX19KX0sMTAwKX1fb25Ub3VjaE1vdmUocil7dGhpcy5fZHJhZ2dlZENvbHVtbiYmci5wcmV2ZW50RGVmYXVsdCgpLGNsZWFyVGltZW91dCh0aGlzLl9zdGFydFRvdWNoUmVvcmRlclRpbWVvdXQpfV9vblRvdWNoRW5kKCl7Y2xlYXJUaW1lb3V0KHRoaXMuX3N0YXJ0VG91Y2hSZW9yZGVyVGltZW91dCksdGhpcy5fb25UcmFja0VuZCgpfV9vblRyYWNrRXZlbnQocil7aWYoci5kZXRhaWwuc3RhdGU9PT0ic3RhcnQiKXtsZXQgbj1yLmNvbXBvc2VkUGF0aCgpLGk9bltuLmluZGV4T2YodGhpcy4kLmhlYWRlciktMl07aWYoIWl8fCFpLl9jb250ZW50fHxpLl9jb250ZW50LmNvbnRhaW5zKHRoaXMuZ2V0Um9vdE5vZGUoKS5hY3RpdmVFbGVtZW50KXx8dGhpcy4kLnNjcm9sbGVyLmhhc0F0dHJpYnV0ZSgiY29sdW1uLXJlc2l6aW5nIikpcmV0dXJuO3RoaXMuX3RvdWNoRGV2aWNlfHx0aGlzLl9vblRyYWNrU3RhcnQocil9ZWxzZSByLmRldGFpbC5zdGF0ZT09PSJ0cmFjayI/dGhpcy5fb25UcmFjayhyKTpyLmRldGFpbC5zdGF0ZT09PSJlbmQiJiZ0aGlzLl9vblRyYWNrRW5kKHIpfV9vblRyYWNrU3RhcnQocil7aWYoIXRoaXMuY29sdW1uUmVvcmRlcmluZ0FsbG93ZWQpcmV0dXJuO2xldCBuPXIuY29tcG9zZWRQYXRoJiZyLmNvbXBvc2VkUGF0aCgpO2lmKG4mJm4uZmlsdGVyKG89Pm8uaGFzQXR0cmlidXRlJiZvLmhhc0F0dHJpYnV0ZSgiZHJhZ2dhYmxlIikpWzBdKXJldHVybjtsZXQgaT10aGlzLl9jZWxsRnJvbVBvaW50KHIuZGV0YWlsLngsci5kZXRhaWwueSk7aWYoISghaXx8aS5nZXRBdHRyaWJ1dGUoInBhcnQiKS5pbmRleE9mKCJoZWFkZXItY2VsbCIpPT09LTEpKXtmb3IodGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJyZW9yZGVyaW5nIiwhMCx0aGlzKSx0aGlzLl9kcmFnZ2VkQ29sdW1uPWkuX2NvbHVtbjt0aGlzLl9kcmFnZ2VkQ29sdW1uLnBhcmVudEVsZW1lbnQuY2hpbGRFbGVtZW50Q291bnQ9PT0xOyl0aGlzLl9kcmFnZ2VkQ29sdW1uPXRoaXMuX2RyYWdnZWRDb2x1bW4ucGFyZW50RWxlbWVudDt0aGlzLl9zZXRTaWJsaW5nc1Jlb3JkZXJTdGF0dXModGhpcy5fZHJhZ2dlZENvbHVtbiwiYWxsb3dlZCIpLHRoaXMuX2RyYWdnZWRDb2x1bW4uX3Jlb3JkZXJTdGF0dXM9ImRyYWdnaW5nIix0aGlzLl91cGRhdGVHaG9zdChpKSx0aGlzLl9yZW9yZGVyR2hvc3Quc3R5bGUudmlzaWJpbGl0eT0idmlzaWJsZSIsdGhpcy5fdXBkYXRlR2hvc3RQb3NpdGlvbihyLmRldGFpbC54LHRoaXMuX3RvdWNoRGV2aWNlP3IuZGV0YWlsLnktNTA6ci5kZXRhaWwueSksdGhpcy5fYXV0b1Njcm9sbGVyKCl9fV9vblRyYWNrKHIpe2lmKCF0aGlzLl9kcmFnZ2VkQ29sdW1uKXJldHVybjtsZXQgbj10aGlzLl9jZWxsRnJvbVBvaW50KHIuZGV0YWlsLngsci5kZXRhaWwueSk7aWYoIW4pcmV0dXJuO2xldCBpPXRoaXMuX2dldFRhcmdldENvbHVtbihuLHRoaXMuX2RyYWdnZWRDb2x1bW4pO3RoaXMuX2lzU3dhcEFsbG93ZWQodGhpcy5fZHJhZ2dlZENvbHVtbixpKSYmdGhpcy5faXNTd2FwcGFibGVCeVBvc2l0aW9uKGksci5kZXRhaWwueCkmJnRoaXMuX3N3YXBDb2x1bW5PcmRlcnModGhpcy5fZHJhZ2dlZENvbHVtbixpKSx0aGlzLl91cGRhdGVHaG9zdFBvc2l0aW9uKHIuZGV0YWlsLngsdGhpcy5fdG91Y2hEZXZpY2U/ci5kZXRhaWwueS01MDpyLmRldGFpbC55KSx0aGlzLl9sYXN0RHJhZ0NsaWVudFg9ci5kZXRhaWwueH1fb25UcmFja0VuZCgpeyF0aGlzLl9kcmFnZ2VkQ29sdW1ufHwodGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJyZW9yZGVyaW5nIiwhMSx0aGlzKSx0aGlzLl9kcmFnZ2VkQ29sdW1uLl9yZW9yZGVyU3RhdHVzPSIiLHRoaXMuX3NldFNpYmxpbmdzUmVvcmRlclN0YXR1cyh0aGlzLl9kcmFnZ2VkQ29sdW1uLCIiKSx0aGlzLl9kcmFnZ2VkQ29sdW1uPW51bGwsdGhpcy5fbGFzdERyYWdDbGllbnRYPW51bGwsdGhpcy5fcmVvcmRlckdob3N0LnN0eWxlLnZpc2liaWxpdHk9ImhpZGRlbiIsdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBDdXN0b21FdmVudCgiY29sdW1uLXJlb3JkZXIiLHtkZXRhaWw6e2NvbHVtbnM6dGhpcy5fZ2V0Q29sdW1uc0luT3JkZXIoKX19KSkpfV9nZXRDb2x1bW5zSW5PcmRlcigpe3JldHVybiB0aGlzLl9jb2x1bW5UcmVlLnNsaWNlKDApLnBvcCgpLmZpbHRlcihyPT4hci5oaWRkZW4pLnNvcnQoKHIsbik9PnIuX29yZGVyLW4uX29yZGVyKX1fY2VsbEZyb21Qb2ludChyLG4pe3I9cnx8MCxuPW58fDAsdGhpcy5fZHJhZ2dlZENvbHVtbnx8dGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJuby1jb250ZW50LXBvaW50ZXItZXZlbnRzIiwhMCx0aGlzLiQuc2Nyb2xsZXIpO2xldCBpPXRoaXMuc2hhZG93Um9vdC5lbGVtZW50RnJvbVBvaW50KHIsbik7aWYodGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJuby1jb250ZW50LXBvaW50ZXItZXZlbnRzIiwhMSx0aGlzLiQuc2Nyb2xsZXIpLGkmJmkuX2NvbHVtbilyZXR1cm4gaX1fdXBkYXRlR2hvc3RQb3NpdGlvbihyLG4pe2xldCBpPXRoaXMuX3Jlb3JkZXJHaG9zdC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxvPXItaS53aWR0aC8yLGE9bi1pLmhlaWdodC8yLHM9cGFyc2VJbnQodGhpcy5fcmVvcmRlckdob3N0Ll9sZWZ0fHwwKSxsPXBhcnNlSW50KHRoaXMuX3Jlb3JkZXJHaG9zdC5fdG9wfHwwKTt0aGlzLl9yZW9yZGVyR2hvc3QuX2xlZnQ9cy0oaS5sZWZ0LW8pLHRoaXMuX3Jlb3JkZXJHaG9zdC5fdG9wPWwtKGkudG9wLWEpLHRoaXMuX3Jlb3JkZXJHaG9zdC5zdHlsZS50cmFuc2Zvcm09YHRyYW5zbGF0ZSgke3RoaXMuX3Jlb3JkZXJHaG9zdC5fbGVmdH1weCwgJHt0aGlzLl9yZW9yZGVyR2hvc3QuX3RvcH1weClgfV91cGRhdGVHaG9zdChyKXtsZXQgbj10aGlzLl9yZW9yZGVyR2hvc3Q7bi50ZXh0Q29udGVudD1yLl9jb250ZW50LmlubmVyVGV4dDtsZXQgaT13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShyKTtyZXR1cm5bImJveFNpemluZyIsImRpc3BsYXkiLCJ3aWR0aCIsImhlaWdodCIsImJhY2tncm91bmQiLCJhbGlnbkl0ZW1zIiwicGFkZGluZyIsImJvcmRlciIsImZsZXgtZGlyZWN0aW9uIiwib3ZlcmZsb3ciXS5mb3JFYWNoKG89Pm4uc3R5bGVbb109aVtvXSksbn1fdXBkYXRlT3JkZXJzKHIsbil7cj09PXZvaWQgMHx8bj09PXZvaWQgMHx8KHJbMF0uZm9yRWFjaChpPT5pLl9vcmRlcj0wKSxvdWUoclswXSx0aGlzLl9vcmRlckJhc2VTY29wZSwwKSl9X3NldFNpYmxpbmdzUmVvcmRlclN0YXR1cyhyLG4pe0FycmF5LmZyb20oci5wYXJlbnROb2RlLmNoaWxkcmVuKS5maWx0ZXIoaT0+L2NvbHVtbi8udGVzdChpLmxvY2FsTmFtZSkmJnRoaXMuX2lzU3dhcEFsbG93ZWQoaSxyKSkuZm9yRWFjaChpPT5pLl9yZW9yZGVyU3RhdHVzPW4pfV9hdXRvU2Nyb2xsZXIoKXtpZih0aGlzLl9sYXN0RHJhZ0NsaWVudFgpe2xldCByPXRoaXMuX2xhc3REcmFnQ2xpZW50WC10aGlzLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnJpZ2h0KzUwLG49dGhpcy5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS5sZWZ0LXRoaXMuX2xhc3REcmFnQ2xpZW50WCs1MDtyPjA/dGhpcy4kLnRhYmxlLnNjcm9sbExlZnQrPXIvMTA6bj4wJiYodGhpcy4kLnRhYmxlLnNjcm9sbExlZnQtPW4vMTApLHRoaXMuX3Njcm9sbEhhbmRsZXIoKX10aGlzLl9kcmFnZ2VkQ29sdW1uJiZ0aGlzLmFzeW5jKHRoaXMuX2F1dG9TY3JvbGxlciwxMCl9X2lzU3dhcEFsbG93ZWQocixuKXtpZihyJiZuKXtsZXQgaT1yIT09bixvPXIucGFyZW50RWxlbWVudD09PW4ucGFyZW50RWxlbWVudCxhPXIuZnJvemVuPT09bi5mcm96ZW47cmV0dXJuIGkmJm8mJmF9fV9pc1N3YXBwYWJsZUJ5UG9zaXRpb24ocixuKXtsZXQgaT1BcnJheS5mcm9tKHRoaXMuJC5oZWFkZXIucXVlcnlTZWxlY3RvckFsbCgndHI6bm90KFtoaWRkZW5dKSBbcGFydH49ImNlbGwiXScpKS5maWx0ZXIocz0+ci5jb250YWlucyhzLl9jb2x1bW4pKVswXSxvPXRoaXMuJC5oZWFkZXIucXVlcnlTZWxlY3RvcigidHI6bm90KFtoaWRkZW5dKSBbcmVvcmRlci1zdGF0dXM9ZHJhZ2dpbmddIikuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksYT1pLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO3JldHVybiBhLmxlZnQ+by5sZWZ0P24+YS5yaWdodC1vLndpZHRoOm48YS5sZWZ0K28ud2lkdGh9X3N3YXBDb2x1bW5PcmRlcnMocixuKXtsZXQgaT1yLl9vcmRlcjtyLl9vcmRlcj1uLl9vcmRlcixuLl9vcmRlcj1pLHRoaXMuX3VwZGF0ZUxhc3RGcm96ZW4oKSx0aGlzLl91cGRhdGVGaXJzdEFuZExhc3RDb2x1bW4oKX1fZ2V0VGFyZ2V0Q29sdW1uKHIsbil7aWYociYmbil7bGV0IGk9ci5fY29sdW1uO2Zvcig7aS5wYXJlbnRFbGVtZW50IT09bi5wYXJlbnRFbGVtZW50JiZpIT09dGhpczspaT1pLnBhcmVudEVsZW1lbnQ7cmV0dXJuIGkucGFyZW50RWxlbWVudD09PW4ucGFyZW50RWxlbWVudD9pOnIuX2NvbHVtbn19fTt2YXIgbmhyPWU9PmNsYXNzIGV4dGVuZHMgZXtzdGF0aWMgZ2V0IHByb3BlcnRpZXMoKXtyZXR1cm57cmVzaXphYmxlOnt0eXBlOkJvb2xlYW4sdmFsdWU6ZnVuY3Rpb24oKXtpZih0aGlzLmxvY2FsTmFtZT09PSJ2YWFkaW4tZ3JpZC1jb2x1bW4tZ3JvdXAiKXJldHVybjtsZXQgcj10aGlzLnBhcmVudE5vZGU7cmV0dXJuIHImJnIubG9jYWxOYW1lPT09InZhYWRpbi1ncmlkLWNvbHVtbi1ncm91cCImJnIucmVzaXphYmxlfHwhMX19LF9oZWFkZXJUZW1wbGF0ZTp7dHlwZTpPYmplY3R9LF9mb290ZXJUZW1wbGF0ZTp7dHlwZTpPYmplY3R9LGZyb3plbjp7dHlwZTpCb29sZWFuLHZhbHVlOiExfSxoaWRkZW46e3R5cGU6Qm9vbGVhbn0saGVhZGVyOnt0eXBlOlN0cmluZ30sdGV4dEFsaWduOnt0eXBlOlN0cmluZ30sX2xhc3RGcm96ZW46e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sX29yZGVyOk51bWJlcixfcmVvcmRlclN0YXR1czpCb29sZWFuLF9lbXB0eUNlbGxzOkFycmF5LF9oZWFkZXJDZWxsOk9iamVjdCxfZm9vdGVyQ2VsbDpPYmplY3QsX2dyaWQ6T2JqZWN0LGhlYWRlclJlbmRlcmVyOkZ1bmN0aW9uLGZvb3RlclJlbmRlcmVyOkZ1bmN0aW9ufX1zdGF0aWMgZ2V0IG9ic2VydmVycygpe3JldHVyblsiX3dpZHRoQ2hhbmdlZCh3aWR0aCwgX2hlYWRlckNlbGwsIF9mb290ZXJDZWxsLCBfY2VsbHMuKikiLCJfZnJvemVuQ2hhbmdlZChmcm96ZW4sIF9oZWFkZXJDZWxsLCBfZm9vdGVyQ2VsbCwgX2NlbGxzLiopIiwiX2ZsZXhHcm93Q2hhbmdlZChmbGV4R3JvdywgX2hlYWRlckNlbGwsIF9mb290ZXJDZWxsLCBfY2VsbHMuKikiLCJfcGF0aE9ySGVhZGVyQ2hhbmdlZChwYXRoLCBoZWFkZXIsIF9oZWFkZXJDZWxsLCBfZm9vdGVyQ2VsbCwgX2NlbGxzLiosIHJlbmRlcmVyLCBoZWFkZXJSZW5kZXJlciwgX2JvZHlUZW1wbGF0ZSwgX2hlYWRlclRlbXBsYXRlKSIsIl90ZXh0QWxpZ25DaGFuZ2VkKHRleHRBbGlnbiwgX2NlbGxzLiosIF9oZWFkZXJDZWxsLCBfZm9vdGVyQ2VsbCkiLCJfb3JkZXJDaGFuZ2VkKF9vcmRlciwgX2hlYWRlckNlbGwsIF9mb290ZXJDZWxsLCBfY2VsbHMuKikiLCJfbGFzdEZyb3plbkNoYW5nZWQoX2xhc3RGcm96ZW4pIiwiX3NldEJvZHlUZW1wbGF0ZU9yUmVuZGVyZXIoX2JvZHlUZW1wbGF0ZSwgcmVuZGVyZXIsIF9jZWxscywgX2NlbGxzLiopIiwiX3NldEhlYWRlclRlbXBsYXRlT3JSZW5kZXJlcihfaGVhZGVyVGVtcGxhdGUsIGhlYWRlclJlbmRlcmVyLCBfaGVhZGVyQ2VsbCkiLCJfc2V0Rm9vdGVyVGVtcGxhdGVPclJlbmRlcmVyKF9mb290ZXJUZW1wbGF0ZSwgZm9vdGVyUmVuZGVyZXIsIF9mb290ZXJDZWxsKSIsIl9yZXNpemFibGVDaGFuZ2VkKHJlc2l6YWJsZSwgX2hlYWRlckNlbGwpIiwiX3Jlb3JkZXJTdGF0dXNDaGFuZ2VkKF9yZW9yZGVyU3RhdHVzLCBfaGVhZGVyQ2VsbCwgX2Zvb3RlckNlbGwsIF9jZWxscy4qKSIsIl9oaWRkZW5DaGFuZ2VkKGhpZGRlbiwgX2hlYWRlckNlbGwsIF9mb290ZXJDZWxsLCBfY2VsbHMuKikiXX1jb25uZWN0ZWRDYWxsYmFjaygpe3N1cGVyLmNvbm5lY3RlZENhbGxiYWNrKCksdGhpcy5fYm9keVRlbXBsYXRlJiYodGhpcy5fYm9keVRlbXBsYXRlLnRlbXBsYXRpemVyLl9ncmlkPXRoaXMuX2dyaWQpLHRoaXMuX2hlYWRlclRlbXBsYXRlJiYodGhpcy5faGVhZGVyVGVtcGxhdGUudGVtcGxhdGl6ZXIuX2dyaWQ9dGhpcy5fZ3JpZCksdGhpcy5fZm9vdGVyVGVtcGxhdGUmJih0aGlzLl9mb290ZXJUZW1wbGF0ZS50ZW1wbGF0aXplci5fZ3JpZD10aGlzLl9ncmlkKSx0aGlzLl90ZW1wbGF0ZU9ic2VydmVyLmZsdXNoKCksdGhpcy5fYm9keVRlbXBsYXRlfHx0aGlzLl90ZW1wbGF0ZU9ic2VydmVyLmNhbGxiYWNrKCkscmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT57dGhpcy5fYWxsQ2VsbHMuZm9yRWFjaChyPT57ci5fY29udGVudC5wYXJlbnROb2RlfHx0aGlzLl9ncmlkJiZ0aGlzLl9ncmlkLmFwcGVuZENoaWxkKHIuX2NvbnRlbnQpfSl9KX1kaXNjb25uZWN0ZWRDYWxsYmFjaygpe3N1cGVyLmRpc2Nvbm5lY3RlZENhbGxiYWNrKCkscmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT57dGhpcy5fZmluZEhvc3RHcmlkKCl8fHRoaXMuX2FsbENlbGxzLmZvckVhY2gocj0+e3IuX2NvbnRlbnQucGFyZW50Tm9kZSYmci5fY29udGVudC5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKHIuX2NvbnRlbnQpfSl9KSx0aGlzLl9ncmlkVmFsdWU9dm9pZCAwfV9maW5kSG9zdEdyaWQoKXtsZXQgcj10aGlzO2Zvcig7ciYmIS9edmFhZGluLipncmlkKC1wcm8pPyQvLnRlc3Qoci5sb2NhbE5hbWUpOylyPXIuYXNzaWduZWRTbG90P3IuYXNzaWduZWRTbG90LnBhcmVudE5vZGU6ci5wYXJlbnROb2RlO3JldHVybiByfHx2b2lkIDB9Z2V0IF9ncmlkKCl7cmV0dXJuIHRoaXMuX2dyaWRWYWx1ZXx8KHRoaXMuX2dyaWRWYWx1ZT10aGlzLl9maW5kSG9zdEdyaWQoKSksdGhpcy5fZ3JpZFZhbHVlfWdldCBfYWxsQ2VsbHMoKXtyZXR1cm5bXS5jb25jYXQodGhpcy5fY2VsbHN8fFtdKS5jb25jYXQodGhpcy5fZW1wdHlDZWxsc3x8W10pLmNvbmNhdCh0aGlzLl9oZWFkZXJDZWxsKS5jb25jYXQodGhpcy5fZm9vdGVyQ2VsbCkuZmlsdGVyKHI9PnIpfWNvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLl90ZW1wbGF0ZU9ic2VydmVyPW5ldyB2aCh0aGlzLCgpPT57dGhpcy5faGVhZGVyVGVtcGxhdGU9dGhpcy5fcHJlcGFyZUhlYWRlclRlbXBsYXRlKCksdGhpcy5fZm9vdGVyVGVtcGxhdGU9dGhpcy5fcHJlcGFyZUZvb3RlclRlbXBsYXRlKCksdGhpcy5fYm9keVRlbXBsYXRlPXRoaXMuX3ByZXBhcmVCb2R5VGVtcGxhdGUoKX0pfV9wcmVwYXJlSGVhZGVyVGVtcGxhdGUoKXtyZXR1cm4gdGhpcy5fcHJlcGFyZVRlbXBsYXRpemVyKHRoaXMuX2ZpbmRUZW1wbGF0ZSghMCl8fG51bGwse30pfV9wcmVwYXJlRm9vdGVyVGVtcGxhdGUoKXtyZXR1cm4gdGhpcy5fcHJlcGFyZVRlbXBsYXRpemVyKHRoaXMuX2ZpbmRUZW1wbGF0ZSghMSwhMCl8fG51bGwse30pfV9wcmVwYXJlQm9keVRlbXBsYXRlKCl7cmV0dXJuIHRoaXMuX3ByZXBhcmVUZW1wbGF0aXplcih0aGlzLl9maW5kVGVtcGxhdGUoKXx8bnVsbCl9X3ByZXBhcmVUZW1wbGF0aXplcihyLG4pe2lmKHImJiFyLnRlbXBsYXRpemVyKXtsZXQgaT1uZXcgVDA7aS5fZ3JpZD10aGlzLl9ncmlkLGkuZGF0YUhvc3Q9dGhpcy5kYXRhSG9zdCxpLl9pbnN0YW5jZVByb3BzPW58fGkuX2luc3RhbmNlUHJvcHMsaS50ZW1wbGF0ZT1yLHIudGVtcGxhdGl6ZXI9aX1yZXR1cm4gcn1fcmVuZGVySGVhZGVyQW5kRm9vdGVyKCl7dGhpcy5oZWFkZXJSZW5kZXJlciYmdGhpcy5faGVhZGVyQ2VsbCYmdGhpcy5fX3J1blJlbmRlcmVyKHRoaXMuaGVhZGVyUmVuZGVyZXIsdGhpcy5faGVhZGVyQ2VsbCksdGhpcy5mb290ZXJSZW5kZXJlciYmdGhpcy5fZm9vdGVyQ2VsbCYmdGhpcy5fX3J1blJlbmRlcmVyKHRoaXMuZm9vdGVyUmVuZGVyZXIsdGhpcy5fZm9vdGVyQ2VsbCl9X19ydW5SZW5kZXJlcihyLG4saSl7bGV0IG89W24uX2NvbnRlbnQsdGhpc107aSYmaS5pdGVtJiZvLnB1c2goaSksci5hcHBseSh0aGlzLG8pfV9fc2V0Q29sdW1uVGVtcGxhdGVPclJlbmRlcmVyKHIsbixpKXtpZighdGhpcy5oaWRkZW4pe2lmKHImJm4pdGhyb3cgbmV3IEVycm9yKCJZb3Ugc2hvdWxkIG9ubHkgdXNlIGVpdGhlciBhIHJlbmRlcmVyIG9yIGEgdGVtcGxhdGUiKTtpLmZvckVhY2gobz0+e2xldCBhPXRoaXMuX2dyaWQuX19nZXRSb3dNb2RlbChvLnBhcmVudEVsZW1lbnQpO2lmKG4pby5fcmVuZGVyZXI9biwoYS5pdGVtfHxuPT09dGhpcy5oZWFkZXJSZW5kZXJlcnx8bj09PXRoaXMuZm9vdGVyUmVuZGVyZXIpJiZ0aGlzLl9fcnVuUmVuZGVyZXIobixvLGEpO2Vsc2UgaWYoby5fdGVtcGxhdGUhPT1yKXtvLl90ZW1wbGF0ZT1yLG8uX2NvbnRlbnQuaW5uZXJIVE1MPSIiLHIudGVtcGxhdGl6ZXIuX2dyaWQ9ci50ZW1wbGF0aXplci5fZ3JpZHx8dGhpcy5fZ3JpZDtsZXQgcz1yLnRlbXBsYXRpemVyLmNyZWF0ZUluc3RhbmNlKCk7by5fY29udGVudC5hcHBlbmRDaGlsZChzLnJvb3QpLG8uX2luc3RhbmNlPXMsYS5pdGVtJiZvLl9pbnN0YW5jZS5zZXRQcm9wZXJ0aWVzKGEpfX0pfX1fc2V0Qm9keVRlbXBsYXRlT3JSZW5kZXJlcihyLG4saSl7KHJ8fG4pJiZpJiZ0aGlzLl9fc2V0Q29sdW1uVGVtcGxhdGVPclJlbmRlcmVyKHIsbixpKX1fc2V0SGVhZGVyVGVtcGxhdGVPclJlbmRlcmVyKHIsbixpKXsocnx8bikmJmkmJnRoaXMuX19zZXRDb2x1bW5UZW1wbGF0ZU9yUmVuZGVyZXIocixuLFtpXSl9X3NldEZvb3RlclRlbXBsYXRlT3JSZW5kZXJlcihyLG4saSl7KHJ8fG4pJiZpJiYodGhpcy5fX3NldENvbHVtblRlbXBsYXRlT3JSZW5kZXJlcihyLG4sW2ldKSx0aGlzLl9ncmlkLl9fdXBkYXRlSGVhZGVyRm9vdGVyUm93VmlzaWJpbGl0eShpLnBhcmVudEVsZW1lbnQpKX1fc2VsZWN0Rmlyc3RUZW1wbGF0ZShyPSExLG49ITEpe3JldHVybiB2aC5nZXRGbGF0dGVuZWROb2Rlcyh0aGlzKS5maWx0ZXIoaT0+aS5sb2NhbE5hbWU9PT0idGVtcGxhdGUiJiZpLmNsYXNzTGlzdC5jb250YWlucygiaGVhZGVyIik9PT1yJiZpLmNsYXNzTGlzdC5jb250YWlucygiZm9vdGVyIik9PT1uKVswXX1fZmluZFRlbXBsYXRlKHIsbil7bGV0IGk9dGhpcy5fc2VsZWN0Rmlyc3RUZW1wbGF0ZShyLG4pO3JldHVybiBpJiZ0aGlzLmRhdGFIb3N0JiYoaS5fcm9vdERhdGFIb3N0PXRoaXMuZGF0YUhvc3QuX3Jvb3REYXRhSG9zdHx8dGhpcy5kYXRhSG9zdCksaX1fZmxleEdyb3dDaGFuZ2VkKHIpe3RoaXMucGFyZW50RWxlbWVudCYmdGhpcy5wYXJlbnRFbGVtZW50Ll9jb2x1bW5Qcm9wQ2hhbmdlZCYmdGhpcy5wYXJlbnRFbGVtZW50Ll9jb2x1bW5Qcm9wQ2hhbmdlZCgiZmxleEdyb3ciKSx0aGlzLl9hbGxDZWxscy5mb3JFYWNoKG49Pm4uc3R5bGUuZmxleEdyb3c9cil9X29yZGVyQ2hhbmdlZChyKXt0aGlzLl9hbGxDZWxscy5mb3JFYWNoKG49Pm4uc3R5bGUub3JkZXI9cil9X3dpZHRoQ2hhbmdlZChyKXt0aGlzLnBhcmVudEVsZW1lbnQmJnRoaXMucGFyZW50RWxlbWVudC5fY29sdW1uUHJvcENoYW5nZWQmJnRoaXMucGFyZW50RWxlbWVudC5fY29sdW1uUHJvcENoYW5nZWQoIndpZHRoIiksdGhpcy5fYWxsQ2VsbHMuZm9yRWFjaChuPT5uLnN0eWxlLndpZHRoPXIpLHRoaXMuX2dyaWQmJnRoaXMuX2dyaWQuX19mb3JjZVJlZmxvdyYmdGhpcy5fZ3JpZC5fX2ZvcmNlUmVmbG93KCl9X2Zyb3plbkNoYW5nZWQocil7dGhpcy5wYXJlbnRFbGVtZW50JiZ0aGlzLnBhcmVudEVsZW1lbnQuX2NvbHVtblByb3BDaGFuZ2VkJiZ0aGlzLnBhcmVudEVsZW1lbnQuX2NvbHVtblByb3BDaGFuZ2VkKCJmcm96ZW4iLHIpLHRoaXMuX2FsbENlbGxzLmZvckVhY2gobj0+dGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJmcm96ZW4iLHIsbikpLHRoaXMuX2dyaWQmJnRoaXMuX2dyaWQuX2Zyb3plbkNlbGxzQ2hhbmdlZCYmdGhpcy5fZ3JpZC5fZnJvemVuQ2VsbHNDaGFuZ2VkKCl9X2xhc3RGcm96ZW5DaGFuZ2VkKHIpe3RoaXMuX2FsbENlbGxzLmZvckVhY2gobj0+dGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJsYXN0LWZyb3plbiIscixuKSksdGhpcy5wYXJlbnRFbGVtZW50JiZ0aGlzLnBhcmVudEVsZW1lbnQuX2NvbHVtblByb3BDaGFuZ2VkJiYodGhpcy5wYXJlbnRFbGVtZW50Ll9sYXN0RnJvemVuPXIpfV9wYXRoT3JIZWFkZXJDaGFuZ2VkKHIsbixpLG8sYSxzLGwsYyx1KXtsZXQgaD1uIT09dm9pZCAwO2lmKCFsJiYhdSYmaCYmaSYmdGhpcy5fX3NldFRleHRDb250ZW50KGkuX2NvbnRlbnQsbiksciYmYS52YWx1ZSl7aWYoIXMmJiFjKXtsZXQgZj0ocCxkLHtpdGVtOmd9KT0+dGhpcy5fX3NldFRleHRDb250ZW50KHAsdGhpcy5nZXQocixnKSk7dGhpcy5fX3NldENvbHVtblRlbXBsYXRlT3JSZW5kZXJlcih2b2lkIDAsZixhLnZhbHVlKX0hbCYmIXUmJiFoJiZpJiZuIT09bnVsbCYmdGhpcy5fX3NldFRleHRDb250ZW50KGkuX2NvbnRlbnQsdGhpcy5fZ2VuZXJhdGVIZWFkZXIocikpfWkmJnRoaXMuX2dyaWQuX191cGRhdGVIZWFkZXJGb290ZXJSb3dWaXNpYmlsaXR5KGkucGFyZW50RWxlbWVudCl9X19zZXRUZXh0Q29udGVudChyLG4pe3IudGV4dENvbnRlbnQhPT1uJiYoci50ZXh0Q29udGVudD1uKX1fZ2VuZXJhdGVIZWFkZXIocil7cmV0dXJuIHIuc3Vic3RyKHIubGFzdEluZGV4T2YoIi4iKSsxKS5yZXBsYWNlKC8oW0EtWl0pL2csIi0kMSIpLnRvTG93ZXJDYXNlKCkucmVwbGFjZSgvLS9nLCIgIikucmVwbGFjZSgvXi4vLG49Pm4udG9VcHBlckNhc2UoKSl9X3RvZ2dsZUF0dHJpYnV0ZShyLG4saSl7aS5oYXNBdHRyaWJ1dGUocik9PT0hbiYmKG4/aS5zZXRBdHRyaWJ1dGUociwiIik6aS5yZW1vdmVBdHRyaWJ1dGUocikpfV9yZW9yZGVyU3RhdHVzQ2hhbmdlZChyKXt0aGlzLl9hbGxDZWxscy5mb3JFYWNoKG49Pm4uc2V0QXR0cmlidXRlKCJyZW9yZGVyLXN0YXR1cyIscikpfV9yZXNpemFibGVDaGFuZ2VkKHIsbil7cj09PXZvaWQgMHx8bj09PXZvaWQgMHx8biYmW25dLmNvbmNhdCh0aGlzLl9lbXB0eUNlbGxzKS5mb3JFYWNoKGk9PntpZihpKXtsZXQgbz1pLnF1ZXJ5U2VsZWN0b3IoJ1twYXJ0fj0icmVzaXplLWhhbmRsZSJdJyk7aWYobyYmaS5yZW1vdmVDaGlsZChvKSxyKXtsZXQgYT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTthLnNldEF0dHJpYnV0ZSgicGFydCIsInJlc2l6ZS1oYW5kbGUiKSxpLmFwcGVuZENoaWxkKGEpfX19KX1fdGV4dEFsaWduQ2hhbmdlZChyKXtpZihyPT09dm9pZCAwKXJldHVybjtpZihbInN0YXJ0IiwiZW5kIiwiY2VudGVyIl0uaW5kZXhPZihyKT09PS0xKXtjb25zb2xlLndhcm4oJ3RleHRBbGlnbiBjYW4gb25seSBiZSBzZXQgYXMgInN0YXJ0IiwgImVuZCIgb3IgImNlbnRlciInKTtyZXR1cm59bGV0IG47Z2V0Q29tcHV0ZWRTdHlsZSh0aGlzLl9ncmlkKS5kaXJlY3Rpb249PT0ibHRyIj9yPT09InN0YXJ0Ij9uPSJsZWZ0IjpyPT09ImVuZCImJihuPSJyaWdodCIpOnI9PT0ic3RhcnQiP249InJpZ2h0IjpyPT09ImVuZCImJihuPSJsZWZ0IiksdGhpcy5fYWxsQ2VsbHMuZm9yRWFjaChpPT57aS5fY29udGVudC5zdHlsZS50ZXh0QWxpZ249cixnZXRDb21wdXRlZFN0eWxlKGkuX2NvbnRlbnQpLnRleHRBbGlnbiE9PXImJihpLl9jb250ZW50LnN0eWxlLnRleHRBbGlnbj1uKX0pfV9oaWRkZW5DaGFuZ2VkKHIpe3RoaXMucGFyZW50RWxlbWVudCYmdGhpcy5wYXJlbnRFbGVtZW50Ll9jb2x1bW5Qcm9wQ2hhbmdlZCYmdGhpcy5wYXJlbnRFbGVtZW50Ll9jb2x1bW5Qcm9wQ2hhbmdlZCgiaGlkZGVuIixyKSwhIXIhPSEhdGhpcy5fcHJldmlvdXNIaWRkZW4mJnRoaXMuX2dyaWQmJihyPT09ITAmJnRoaXMuX2FsbENlbGxzLmZvckVhY2gobj0+e24uX2NvbnRlbnQucGFyZW50Tm9kZSYmbi5fY29udGVudC5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKG4uX2NvbnRlbnQpfSksdGhpcy5fZ3JpZC5fZGVib3VuY2VySGlkZGVuQ2hhbmdlZD1zci5kZWJvdW5jZSh0aGlzLl9ncmlkLl9kZWJvdW5jZXJIaWRkZW5DaGFuZ2VkLE5pLCgpPT57dGhpcy5fZ3JpZCYmdGhpcy5fZ3JpZC5fcmVuZGVyQ29sdW1uVHJlZSYmdGhpcy5fZ3JpZC5fcmVuZGVyQ29sdW1uVHJlZSh0aGlzLl9ncmlkLl9jb2x1bW5UcmVlKX0pLHRoaXMuX2dyaWQuX3VwZGF0ZUxhc3RGcm96ZW4mJnRoaXMuX2dyaWQuX3VwZGF0ZUxhc3RGcm96ZW4oKSx0aGlzLl9ncmlkLm5vdGlmeVJlc2l6ZSYmdGhpcy5fZ3JpZC5ub3RpZnlSZXNpemUoKSx0aGlzLl9ncmlkLl9yZXNldEtleWJvYXJkTmF2aWdhdGlvbiYmdGhpcy5fZ3JpZC5fcmVzZXRLZXlib2FyZE5hdmlnYXRpb24oKSksdGhpcy5fcHJldmlvdXNIaWRkZW49cn19LHRWPWNsYXNzIGV4dGVuZHMgbmhyKEtIKG10KSl7c3RhdGljIGdldCBpcygpe3JldHVybiJ2YWFkaW4tZ3JpZC1jb2x1bW4ifXN0YXRpYyBnZXQgcHJvcGVydGllcygpe3JldHVybnt3aWR0aDp7dHlwZTpTdHJpbmcsdmFsdWU6IjEwMHB4In0sZmxleEdyb3c6e3R5cGU6TnVtYmVyLHZhbHVlOjF9LHJlbmRlcmVyOkZ1bmN0aW9uLHBhdGg6e3R5cGU6U3RyaW5nfSxhdXRvV2lkdGg6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMX0sX2JvZHlUZW1wbGF0ZTp7dHlwZTpPYmplY3R9LF9jZWxsczpBcnJheX19fTtjdXN0b21FbGVtZW50cy5kZWZpbmUodFYuaXMsdFYpO2pjKCJ2YWFkaW4tZ3JpZCIsQ2lgCiAgICBAa2V5ZnJhbWVzIHZhYWRpbi1ncmlkLWFwcGVhciB7CiAgICAgIHRvIHsKICAgICAgICBvcGFjaXR5OiAxOwogICAgICB9CiAgICB9CgogICAgOmhvc3QgewogICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgYW5pbWF0aW9uOiAxbXMgdmFhZGluLWdyaWQtYXBwZWFyOwogICAgICBoZWlnaHQ6IDQwMHB4OwogICAgICBmbGV4OiAxIDEgYXV0bzsKICAgICAgYWxpZ24tc2VsZjogc3RyZXRjaDsKICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgfQoKICAgIDpob3N0KFtoaWRkZW5dKSB7CiAgICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICAgIH0KCiAgICAjc2Nyb2xsZXIgewogICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDApOwogICAgICB3aWR0aDogYXV0bzsKICAgICAgaGVpZ2h0OiBhdXRvOwogICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgIHRvcDogMDsKICAgICAgcmlnaHQ6IDA7CiAgICAgIGJvdHRvbTogMDsKICAgICAgbGVmdDogMDsKICAgIH0KCiAgICA6aG9zdChbaGVpZ2h0LWJ5LXJvd3NdKSB7CiAgICAgIGhlaWdodDogYXV0bzsKICAgICAgYWxpZ24tc2VsZjogZmxleC1zdGFydDsKICAgICAgZmxleC1ncm93OiAwOwogICAgICB3aWR0aDogMTAwJTsKICAgIH0KCiAgICA6aG9zdChbaGVpZ2h0LWJ5LXJvd3NdKSAjc2Nyb2xsZXIgewogICAgICB3aWR0aDogMTAwJTsKICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICB9CgogICAgI3RhYmxlIHsKICAgICAgZGlzcGxheTogZmxleDsKICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIGhlaWdodDogMTAwJTsKICAgICAgb3ZlcmZsb3c6IGF1dG87CiAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgb3V0bGluZTogbm9uZTsKICAgICAgLyogV29ya2Fyb3VuZCBmb3IgYSBEZXNrdG9wIFNhZmFyaSBidWc6IG5ldyBzdGFja2luZyBjb250ZXh0IGhlcmUgcHJldmVudHMgdGhlIHNjcm9sbGJhciBmcm9tIGdldHRpbmcgaGlkZGVuICovCiAgICAgIHotaW5kZXg6IDA7CiAgICB9CgogICAgI2hlYWRlciwKICAgICNmb290ZXIgewogICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgcG9zaXRpb246IC13ZWJraXQtc3RpY2t5OwogICAgICBwb3NpdGlvbjogc3RpY2t5OwogICAgICBsZWZ0OiAwOwogICAgICBvdmVyZmxvdzogdmlzaWJsZTsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIHotaW5kZXg6IDE7CiAgICB9CgogICAgI2hlYWRlciB7CiAgICAgIHRvcDogMDsKICAgIH0KCiAgICB0aCB7CiAgICAgIHRleHQtYWxpZ246IGluaGVyaXQ7CiAgICB9CgogICAgLyogU2FmYXJpIGRvZXNuJ3Qgd29yayB3aXRoICJpbmhlcml0IiAqLwogICAgW3NhZmFyaV0gdGggewogICAgICB0ZXh0LWFsaWduOiBpbml0aWFsOwogICAgfQoKICAgICNmb290ZXIgewogICAgICBib3R0b206IDA7CiAgICB9CgogICAgI2l0ZW1zIHsKICAgICAgZmxleC1ncm93OiAxOwogICAgICBmbGV4LXNocmluazogMDsKICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgIHBvc2l0aW9uOiAtd2Via2l0LXN0aWNreTsKICAgICAgcG9zaXRpb246IHN0aWNreTsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIGxlZnQ6IDA7CiAgICAgIG92ZXJmbG93OiB2aXNpYmxlOwogICAgfQoKICAgIFtwYXJ0fj0ncm93J10gewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICB3aWR0aDogMTAwJTsKICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsKICAgICAgbWFyZ2luOiAwOwogICAgfQoKICAgIFtwYXJ0fj0ncm93J11bbG9hZGluZ10gW3BhcnR+PSdib2R5LWNlbGwnXSA6OnNsb3R0ZWQodmFhZGluLWdyaWQtY2VsbC1jb250ZW50KSB7CiAgICAgIG9wYWNpdHk6IDA7CiAgICB9CgogICAgI2l0ZW1zIFtwYXJ0fj0ncm93J10gewogICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICB9CgogICAgI2l0ZW1zIFtwYXJ0fj0ncm93J106ZW1wdHkgewogICAgICBoZWlnaHQ6IDFlbTsKICAgIH0KCiAgICBbcGFydH49J2NlbGwnXTpub3QoW3BhcnR+PSdkZXRhaWxzLWNlbGwnXSkgewogICAgICBmbGV4LXNocmluazogMDsKICAgICAgZmxleC1ncm93OiAxOwogICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICB3aWR0aDogMTAwJTsKICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBwYWRkaW5nOiAwOwogICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgfQoKICAgIFtwYXJ0fj0nZGV0YWlscy1jZWxsJ10gewogICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgIGJvdHRvbTogMDsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgIHBhZGRpbmc6IDA7CiAgICB9CgogICAgW3BhcnR+PSdjZWxsJ10gOjpzbG90dGVkKHZhYWRpbi1ncmlkLWNlbGwtY29udGVudCkgewogICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzOwogICAgfQoKICAgIFtoaWRkZW5dIHsKICAgICAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OwogICAgfQoKICAgIFtmcm96ZW5dIHsKICAgICAgei1pbmRleDogMjsKICAgICAgd2lsbC1jaGFuZ2U6IHRyYW5zZm9ybTsKICAgIH0KCiAgICBbbm8tc2Nyb2xsYmFyc11bc2FmYXJpXSAjdGFibGUsCiAgICBbbm8tc2Nyb2xsYmFyc11bZmlyZWZveF0gI3RhYmxlIHsKICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgIH0KCiAgICAvKiBSZW9yZGVyaW5nIHN0eWxlcyAqLwogICAgOmhvc3QoW3Jlb3JkZXJpbmddKSBbcGFydH49J2NlbGwnXSA6OnNsb3R0ZWQodmFhZGluLWdyaWQtY2VsbC1jb250ZW50KSwKICAgIDpob3N0KFtyZW9yZGVyaW5nXSkgW3BhcnR+PSdyZXNpemUtaGFuZGxlJ10sCiAgICAjc2Nyb2xsZXJbbm8tY29udGVudC1wb2ludGVyLWV2ZW50c10gW3BhcnR+PSdjZWxsJ10gOjpzbG90dGVkKHZhYWRpbi1ncmlkLWNlbGwtY29udGVudCkgewogICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgIH0KCiAgICBbcGFydH49J3Jlb3JkZXItZ2hvc3QnXSB7CiAgICAgIHZpc2liaWxpdHk6IGhpZGRlbjsKICAgICAgcG9zaXRpb246IGZpeGVkOwogICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgb3BhY2l0eTogMC41OwoKICAgICAgLyogUHJldmVudCBvdmVyZmxvd2luZyB0aGUgZ3JpZCBpbiBGaXJlZm94ICovCiAgICAgIHRvcDogMDsKICAgICAgbGVmdDogMDsKICAgIH0KCiAgICA6aG9zdChbcmVvcmRlcmluZ10pIHsKICAgICAgLW1vei11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgdXNlci1zZWxlY3Q6IG5vbmU7CiAgICB9CgogICAgLyogUmVzaXppbmcgc3R5bGVzICovCiAgICBbcGFydH49J3Jlc2l6ZS1oYW5kbGUnXSB7CiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgdG9wOiAwOwogICAgICByaWdodDogMDsKICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICBjdXJzb3I6IGNvbC1yZXNpemU7CiAgICAgIHotaW5kZXg6IDE7CiAgICB9CgogICAgW3BhcnR+PSdyZXNpemUtaGFuZGxlJ106OmJlZm9yZSB7CiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgY29udGVudDogJyc7CiAgICAgIGhlaWdodDogMTAwJTsKICAgICAgd2lkdGg6IDM1cHg7CiAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgtNTAlKTsKICAgIH0KCiAgICBbbGFzdC1jb2x1bW5dIFtwYXJ0fj0ncmVzaXplLWhhbmRsZSddOjpiZWZvcmUsCiAgICBbbGFzdC1mcm96ZW5dIFtwYXJ0fj0ncmVzaXplLWhhbmRsZSddOjpiZWZvcmUgewogICAgICB3aWR0aDogMThweDsKICAgICAgdHJhbnNmb3JtOiBub25lOwogICAgICByaWdodDogMDsKICAgIH0KCiAgICAjc2Nyb2xsZXJbY29sdW1uLXJlc2l6aW5nXSB7CiAgICAgIC1tcy11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgLW1vei11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgdXNlci1zZWxlY3Q6IG5vbmU7CiAgICB9CgogICAgLyogU2l6ZXIgc3R5bGVzICovCiAgICAjc2l6ZXIgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgIHZpc2liaWxpdHk6IGhpZGRlbjsKICAgIH0KCiAgICAjc2l6ZXIgW3BhcnR+PSdkZXRhaWxzLWNlbGwnXSB7CiAgICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICAgIH0KCiAgICAjc2l6ZXIgW3BhcnR+PSdjZWxsJ11baGlkZGVuXSB7CiAgICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICAgIH0KCiAgICAjc2l6ZXIgW3BhcnR+PSdjZWxsJ10gewogICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgZmxleC1zaHJpbms6IDA7CiAgICAgIGxpbmUtaGVpZ2h0OiAwOwogICAgICBoZWlnaHQ6IDAgIWltcG9ydGFudDsKICAgICAgbWluLWhlaWdodDogMCAhaW1wb3J0YW50OwogICAgICBtYXgtaGVpZ2h0OiAwICFpbXBvcnRhbnQ7CiAgICAgIHBhZGRpbmc6IDAgIWltcG9ydGFudDsKICAgICAgYm9yZGVyOiBub25lICFpbXBvcnRhbnQ7CiAgICB9CgogICAgI3NpemVyIFtwYXJ0fj0nY2VsbCddOjpiZWZvcmUgewogICAgICBjb250ZW50OiAnLSc7CiAgICB9CgogICAgI3NpemVyIFtwYXJ0fj0nY2VsbCddIDo6c2xvdHRlZCh2YWFkaW4tZ3JpZC1jZWxsLWNvbnRlbnQpIHsKICAgICAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OwogICAgfQoKICAgIC8qIFJUTCBzcGVjaWZpYyBzdHlsZXMgKi8KCiAgICA6aG9zdChbZGlyPSdydGwnXSkgI2l0ZW1zLAogICAgOmhvc3QoW2Rpcj0ncnRsJ10pICNoZWFkZXIsCiAgICA6aG9zdChbZGlyPSdydGwnXSkgI2Zvb3RlciB7CiAgICAgIGxlZnQ6IGF1dG87CiAgICB9CgogICAgOmhvc3QoW2Rpcj0ncnRsJ10pIFtwYXJ0fj0ncmVvcmRlci1naG9zdCddIHsKICAgICAgbGVmdDogYXV0bzsKICAgICAgcmlnaHQ6IDA7CiAgICB9CgogICAgOmhvc3QoW2Rpcj0ncnRsJ10pIFtwYXJ0fj0ncmVzaXplLWhhbmRsZSddIHsKICAgICAgbGVmdDogMDsKICAgICAgcmlnaHQ6IGF1dG87CiAgICB9CgogICAgOmhvc3QoW2Rpcj0ncnRsJ10pIFtwYXJ0fj0ncmVzaXplLWhhbmRsZSddOjpiZWZvcmUgewogICAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoNTAlKTsKICAgIH0KCiAgICA6aG9zdChbZGlyPSdydGwnXSkgW2xhc3QtY29sdW1uXSBbcGFydH49J3Jlc2l6ZS1oYW5kbGUnXTo6YmVmb3JlLAogICAgOmhvc3QoW2Rpcj0ncnRsJ10pIFtsYXN0LWZyb3plbl0gW3BhcnR+PSdyZXNpemUtaGFuZGxlJ106OmJlZm9yZSB7CiAgICAgIGxlZnQ6IDA7CiAgICAgIHJpZ2h0OiBhdXRvOwogICAgfQogIGAse21vZHVsZUlkOiJ2YWFkaW4tZ3JpZC1zdHlsZXMifSk7dmFyIGlocj0oKCk9Pnt0cnl7cmV0dXJuIGRvY3VtZW50LmNyZWF0ZUV2ZW50KCJUb3VjaEV2ZW50IiksITB9Y2F0Y2goZSl7cmV0dXJuITF9fSkoKSxlVj1jbGFzcyBleHRlbmRzIFpIKGpIKFhjZShHY2UoJGNlKHFjZShRY2UodHVlKGV1ZShKY2UoaXVlKFVjZShaY2UoYXVlKFdjZShLY2UobnVlKHJ1ZShRSCkpKSkpKSkpKSkpKSkpKSkpKXtzdGF0aWMgZ2V0IHRlbXBsYXRlKCl7cmV0dXJuIFFgCiAgICAgIDxkaXYKICAgICAgICBpZD0ic2Nyb2xsZXIiCiAgICAgICAgc2FmYXJpJD0iW1tfc2FmYXJpXV0iCiAgICAgICAgaW9zJD0iW1tfaW9zXV0iCiAgICAgICAgbG9hZGluZyQ9IltbbG9hZGluZ11dIgogICAgICAgIGNvbHVtbi1yZW9yZGVyaW5nLWFsbG93ZWQkPSJbW2NvbHVtblJlb3JkZXJpbmdBbGxvd2VkXV0iCiAgICAgID4KICAgICAgICA8dGFibGUgaWQ9InRhYmxlIiByb2xlPSJncmlkIiBhcmlhLW11bHRpc2VsZWN0YWJsZT0idHJ1ZSIgdGFiaW5kZXg9IjAiPgogICAgICAgICAgPGNhcHRpb24gaWQ9InNpemVyIiBwYXJ0PSJyb3ciPjwvY2FwdGlvbj4KICAgICAgICAgIDx0aGVhZCBpZD0iaGVhZGVyIiByb2xlPSJyb3dncm91cCI+PC90aGVhZD4KICAgICAgICAgIDx0Ym9keSBpZD0iaXRlbXMiIHJvbGU9InJvd2dyb3VwIj48L3Rib2R5PgogICAgICAgICAgPHRmb290IGlkPSJmb290ZXIiIHJvbGU9InJvd2dyb3VwIj48L3Rmb290PgogICAgICAgIDwvdGFibGU+CgogICAgICAgIDxkaXYgcGFydD0icmVvcmRlci1naG9zdCI+PC9kaXY+CiAgICAgIDwvZGl2PgoKICAgICAgPGRpdiBpZD0iZm9jdXNleGl0IiB0YWJpbmRleD0iMCI+PC9kaXY+CiAgICBgfXN0YXRpYyBnZXQgaXMoKXtyZXR1cm4idmFhZGluLWdyaWQifXN0YXRpYyBnZXQgdmVyc2lvbigpe3JldHVybiIyMC4wLjIifXN0YXRpYyBnZXQgb2JzZXJ2ZXJzKCl7cmV0dXJuWyJfY29sdW1uVHJlZUNoYW5nZWQoX2NvbHVtblRyZWUsIF9jb2x1bW5UcmVlLiopIl19c3RhdGljIGdldCBwcm9wZXJ0aWVzKCl7cmV0dXJue19zYWZhcmk6e3R5cGU6Qm9vbGVhbix2YWx1ZTovXigoPyFjaHJvbWV8YW5kcm9pZCkuKSpzYWZhcmkvaS50ZXN0KG5hdmlnYXRvci51c2VyQWdlbnQpfSxfaW9zOnt0eXBlOkJvb2xlYW4sdmFsdWU6L2lQYWR8aVBob25lfGlQb2QvLnRlc3QobmF2aWdhdG9yLnVzZXJBZ2VudCkmJiF3aW5kb3cuTVNTdHJlYW18fG5hdmlnYXRvci5wbGF0Zm9ybT09PSJNYWNJbnRlbCImJm5hdmlnYXRvci5tYXhUb3VjaFBvaW50cz4xfSxfZmlyZWZveDp7dHlwZTpCb29sZWFuLHZhbHVlOm5hdmlnYXRvci51c2VyQWdlbnQudG9Mb3dlckNhc2UoKS5pbmRleE9mKCJmaXJlZm94Iik+LTF9LF9hbmRyb2lkOnt0eXBlOkJvb2xlYW4sdmFsdWU6L2FuZHJvaWQvaS50ZXN0KG5hdmlnYXRvci51c2VyQWdlbnQpfSxfdG91Y2hEZXZpY2U6e3R5cGU6Qm9vbGVhbix2YWx1ZTppaHJ9LGhlaWdodEJ5Um93czp7dHlwZTpCb29sZWFuLHZhbHVlOiExLHJlZmxlY3RUb0F0dHJpYnV0ZTohMCxvYnNlcnZlcjoiX2hlaWdodEJ5Um93c0NoYW5nZWQifSxfcmVjYWxjdWxhdGVDb2x1bW5XaWR0aE9uY2VMb2FkaW5nRmluaXNoZWQ6e3R5cGU6Qm9vbGVhbix2YWx1ZTohMH19fWNvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLmFkZEV2ZW50TGlzdGVuZXIoImFuaW1hdGlvbmVuZCIsdGhpcy5fb25BbmltYXRpb25FbmQpfWNvbm5lY3RlZENhbGxiYWNrKCl7c3VwZXIuY29ubmVjdGVkQ2FsbGJhY2soKSx0aGlzLnJlY2FsY3VsYXRlQ29sdW1uV2lkdGhzKCl9YXR0cmlidXRlQ2hhbmdlZENhbGxiYWNrKHQscixuKXtzdXBlci5hdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2sodCxyLG4pLHQ9PT0iZGlyIiYmKHRoaXMuX19pc1JUTD1uPT09InJ0bCIsdGhpcy5fdXBkYXRlU2Nyb2xsZXJNZWFzdXJlbWVudHMoKSl9X19oYXNSb3dzV2l0aENsaWVudEhlaWdodCgpe3JldHVybiEhQXJyYXkuZnJvbSh0aGlzLiQuaXRlbXMuY2hpbGRyZW4pLmZpbHRlcih0PT50LmNsaWVudEhlaWdodCkubGVuZ3RofV9faXRlbXNSZWNlaXZlZCgpe3RoaXMuX3JlY2FsY3VsYXRlQ29sdW1uV2lkdGhPbmNlTG9hZGluZ0ZpbmlzaGVkJiYhdGhpcy5fY2FjaGUuaXNMb2FkaW5nKCkmJnRoaXMuX19oYXNSb3dzV2l0aENsaWVudEhlaWdodCgpJiYodGhpcy5fcmVjYWxjdWxhdGVDb2x1bW5XaWR0aE9uY2VMb2FkaW5nRmluaXNoZWQ9ITEsdGhpcy5yZWNhbGN1bGF0ZUNvbHVtbldpZHRocygpKX1fcmVjYWxjdWxhdGVDb2x1bW5XaWR0aHModCl7dC5mb3JFYWNoKHI9PntyLndpZHRoPSJhdXRvIixyLl9vcmlnRmxleEdyb3c9ci5mbGV4R3JvdyxyLmZsZXhHcm93PTB9KSx0LmZvckVhY2gocj0+e3IuX2N1cnJlbnRXaWR0aD0wLHIuX2FsbENlbGxzLmZvckVhY2gobj0+e2xldCBpPW4ub2Zmc2V0V2lkdGgrMTtyLl9jdXJyZW50V2lkdGg9TWF0aC5tYXgoci5fY3VycmVudFdpZHRoLGkpfSl9KSx0LmZvckVhY2gocj0+e3Iud2lkdGg9YCR7ci5fY3VycmVudFdpZHRofXB4YCxyLmZsZXhHcm93PXIuX29yaWdGbGV4R3JvdyxyLl9jdXJyZW50V2lkdGg9dm9pZCAwLHIuX29yaWdGbGV4R3Jvdz12b2lkIDB9KX1yZWNhbGN1bGF0ZUNvbHVtbldpZHRocygpe2lmKCEhdGhpcy5fY29sdW1uVHJlZSlpZih0aGlzLl9jYWNoZS5pc0xvYWRpbmcoKSl0aGlzLl9yZWNhbGN1bGF0ZUNvbHVtbldpZHRoT25jZUxvYWRpbmdGaW5pc2hlZD0hMDtlbHNle2xldCB0PXRoaXMuX2dldENvbHVtbnMoKS5maWx0ZXIocj0+IXIuaGlkZGVuJiZyLmF1dG9XaWR0aCk7dGhpcy5fcmVjYWxjdWxhdGVDb2x1bW5XaWR0aHModCl9fV9jcmVhdGVTY3JvbGxlclJvd3ModCl7bGV0IHI9W107Zm9yKGxldCBuPTA7bjx0O24rKyl7bGV0IGk9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidHIiKTtpLnNldEF0dHJpYnV0ZSgicGFydCIsInJvdyIpLGkuc2V0QXR0cmlidXRlKCJyb2xlIiwicm93IiksdGhpcy5fY29sdW1uVHJlZSYmdGhpcy5fdXBkYXRlUm93KGksdGhpcy5fY29sdW1uVHJlZVt0aGlzLl9jb2x1bW5UcmVlLmxlbmd0aC0xXSwiYm9keSIsITEsITApLHIucHVzaChpKX1yZXR1cm4gdGhpcy5fY29sdW1uVHJlZSYmdGhpcy5fY29sdW1uVHJlZVt0aGlzLl9jb2x1bW5UcmVlLmxlbmd0aC0xXS5mb3JFYWNoKG49Pm4uaXNDb25uZWN0ZWQmJm4ubm90aWZ5UGF0aCYmbi5ub3RpZnlQYXRoKCJfY2VsbHMuKiIsbi5fY2VsbHMpKSxtZ3QodGhpcywoKT0+e3RoaXMuX3VwZGF0ZUZpcnN0QW5kTGFzdENvbHVtbigpLHRoaXMuX3Jlc2V0S2V5Ym9hcmROYXZpZ2F0aW9uKCl9KSxyfV9nZXRSb3dUYXJnZXQoKXtyZXR1cm4gdGhpcy4kLml0ZW1zfV9jcmVhdGVDZWxsKHQpe2xldCByPXRoaXMuX2NvbnRlbnRJbmRleD10aGlzLl9jb250ZW50SW5kZXgrMXx8MCxuPSJ2YWFkaW4tZ3JpZC1jZWxsLWNvbnRlbnQtIityLGk9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidmFhZGluLWdyaWQtY2VsbC1jb250ZW50Iik7aS5zZXRBdHRyaWJ1dGUoInNsb3QiLG4pO2xldCBvPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQodCk7by5pZD1uLnJlcGxhY2UoIi1jb250ZW50LSIsIi0iKSxvLnNldEF0dHJpYnV0ZSgidGFiaW5kZXgiLCItMSIpLG8uc2V0QXR0cmlidXRlKCJyb2xlIix0PT09InRkIj8iZ3JpZGNlbGwiOiJjb2x1bW5oZWFkZXIiKTtsZXQgYT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzbG90Iik7cmV0dXJuIGEuc2V0QXR0cmlidXRlKCJuYW1lIixuKSxvLmFwcGVuZENoaWxkKGEpLG8uX2NvbnRlbnQ9aSxpLmFkZEV2ZW50TGlzdGVuZXIoIm1vdXNlZG93biIsKCk9PntpZih3aW5kb3cuY2hyb21lKXtsZXQgcz0oKT0+e2kuY29udGFpbnModGhpcy5nZXRSb290Tm9kZSgpLmFjdGl2ZUVsZW1lbnQpfHxvLmZvY3VzKCksZG9jdW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcigibW91c2V1cCIscywhMCl9O2RvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoIm1vdXNldXAiLHMsITApfWVsc2Ugc2V0VGltZW91dCgoKT0+e2kuY29udGFpbnModGhpcy5nZXRSb290Tm9kZSgpLmFjdGl2ZUVsZW1lbnQpfHxvLmZvY3VzKCl9KX0pLG99X3VwZGF0ZVJvdyh0LHIsbixpLG8pe249bnx8ImJvZHkiO2xldCBhPWRvY3VtZW50LmNyZWF0ZURvY3VtZW50RnJhZ21lbnQoKTtBcnJheS5mcm9tKHQuY2hpbGRyZW4pLmZvckVhY2gocz0+cy5fdmFjYW50PSEwKSx0LmlubmVySFRNTD0iIix0LmlkIT09InNpemVyIiYmKHQuaGlkZGVuPSEwKSxyLmZpbHRlcihzPT4hcy5oaWRkZW4pLmZvckVhY2goKHMsbCxjKT0+e2xldCB1O2lmKG49PT0iYm9keSIpe2lmKHMuX2NlbGxzPXMuX2NlbGxzfHxbXSx1PXMuX2NlbGxzLmZpbHRlcihoPT5oLl92YWNhbnQpWzBdLHV8fCh1PXRoaXMuX2NyZWF0ZUNlbGwoInRkIikscy5fY2VsbHMucHVzaCh1KSksdS5zZXRBdHRyaWJ1dGUoInBhcnQiLCJjZWxsIGJvZHktY2VsbCIpLHQuYXBwZW5kQ2hpbGQodSksbD09PWMubGVuZ3RoLTEmJih0aGlzLl9yb3dEZXRhaWxzVGVtcGxhdGV8fHRoaXMucm93RGV0YWlsc1JlbmRlcmVyKSl7dGhpcy5fZGV0YWlsc0NlbGxzPXRoaXMuX2RldGFpbHNDZWxsc3x8W107bGV0IGg9dGhpcy5fZGV0YWlsc0NlbGxzLmZpbHRlcihmPT5mLl92YWNhbnQpWzBdfHx0aGlzLl9jcmVhdGVDZWxsKCJ0ZCIpO3RoaXMuX2RldGFpbHNDZWxscy5pbmRleE9mKGgpPT09LTEmJnRoaXMuX2RldGFpbHNDZWxscy5wdXNoKGgpLGguX2NvbnRlbnQucGFyZW50RWxlbWVudHx8YS5hcHBlbmRDaGlsZChoLl9jb250ZW50KSx0aGlzLl9jb25maWd1cmVEZXRhaWxzQ2VsbChoKSx0LmFwcGVuZENoaWxkKGgpLHRoaXMuX2ExMXlTZXRSb3dEZXRhaWxzQ2VsbCh0LGgpLGguX3ZhY2FudD0hMX1zLm5vdGlmeVBhdGgmJiFvJiZzLm5vdGlmeVBhdGgoIl9jZWxscy4qIixzLl9jZWxscyl9ZWxzZXtsZXQgaD1uPT09ImhlYWRlciI/InRoIjoidGQiO2l8fHMubG9jYWxOYW1lPT09InZhYWRpbi1ncmlkLWNvbHVtbi1ncm91cCI/KHU9c1tgXyR7bn1DZWxsYF18fHRoaXMuX2NyZWF0ZUNlbGwoaCksdS5fY29sdW1uPXMsdC5hcHBlbmRDaGlsZCh1KSxzW2BfJHtufUNlbGxgXT11KToocy5fZW1wdHlDZWxscz1zLl9lbXB0eUNlbGxzfHxbXSx1PXMuX2VtcHR5Q2VsbHMuZmlsdGVyKGY9PmYuX3ZhY2FudClbMF18fHRoaXMuX2NyZWF0ZUNlbGwoaCksdS5fY29sdW1uPXMsdC5hcHBlbmRDaGlsZCh1KSxzLl9lbXB0eUNlbGxzLmluZGV4T2YodSk9PT0tMSYmcy5fZW1wdHlDZWxscy5wdXNoKHUpKSx1LnNldEF0dHJpYnV0ZSgicGFydCIsYGNlbGwgJHtufS1jZWxsYCksdGhpcy5fX3VwZGF0ZUhlYWRlckZvb3RlclJvd1Zpc2liaWxpdHkodCl9dS5fY29udGVudC5wYXJlbnRFbGVtZW50fHxhLmFwcGVuZENoaWxkKHUuX2NvbnRlbnQpLHUuX3ZhY2FudD0hMSx1Ll9jb2x1bW49c30pLHRoaXMuYXBwZW5kQ2hpbGQoYSksdGhpcy5fZnJvemVuQ2VsbHNDaGFuZ2VkKCksdGhpcy5fdXBkYXRlRmlyc3RBbmRMYXN0Q29sdW1uRm9yUm93KHQpfV9fdXBkYXRlSGVhZGVyRm9vdGVyUm93VmlzaWJpbGl0eSh0KXtpZighdClyZXR1cm47bGV0IHI9QXJyYXkuZnJvbSh0LmNoaWxkcmVuKS5maWx0ZXIobj0+e2xldCBpPW4uX2NvbHVtbjtpZihpLl9lbXB0eUNlbGxzJiZpLl9lbXB0eUNlbGxzLmluZGV4T2Yobik+LTEpcmV0dXJuITE7aWYodC5wYXJlbnRFbGVtZW50PT09dGhpcy4kLmhlYWRlcil7aWYoaS5oZWFkZXJSZW5kZXJlcnx8aS5faGVhZGVyVGVtcGxhdGUpcmV0dXJuITA7aWYoaS5oZWFkZXI9PT1udWxsKXJldHVybiExO2lmKGkucGF0aHx8aS5oZWFkZXIhPT12b2lkIDApcmV0dXJuITB9ZWxzZSBpZihpLmZvb3RlclJlbmRlcmVyfHxpLl9mb290ZXJUZW1wbGF0ZSlyZXR1cm4hMH0pO3QuaGlkZGVuIT09IXIubGVuZ3RoJiYodC5oaWRkZW49IXIubGVuZ3RoLHRoaXMubm90aWZ5UmVzaXplKCkpfV91cGRhdGVTY3JvbGxlckl0ZW0odCxyKXt0aGlzLl9wcmV2ZW50U2Nyb2xsZXJSb3RhdGluZ0NlbGxGb2N1cyh0LHIpLHRoaXMuX2NvbHVtblRyZWUmJih0aGlzLl90b2dnbGVBdHRyaWJ1dGUoImZpcnN0IixyPT09MCx0KSx0aGlzLl90b2dnbGVBdHRyaWJ1dGUoIm9kZCIsciUyLHQpLHRoaXMuX2ExMXlVcGRhdGVSb3dSb3dpbmRleCh0LHIpLHRoaXMuX2dldEl0ZW0ocix0KSl9X2NvbHVtblRyZWVDaGFuZ2VkKHQpe3RoaXMuX3JlbmRlckNvbHVtblRyZWUodCksdGhpcy5yZWNhbGN1bGF0ZUNvbHVtbldpZHRocygpfV9yZW5kZXJDb2x1bW5UcmVlKHQpe2ZvcihBcnJheS5mcm9tKHRoaXMuJC5pdGVtcy5jaGlsZHJlbikuZm9yRWFjaChyPT50aGlzLl91cGRhdGVSb3cocix0W3QubGVuZ3RoLTFdLG51bGwsITEsITApKTt0aGlzLiQuaGVhZGVyLmNoaWxkcmVuLmxlbmd0aDx0Lmxlbmd0aDspe2xldCByPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInRyIik7ci5zZXRBdHRyaWJ1dGUoInBhcnQiLCJyb3ciKSxyLnNldEF0dHJpYnV0ZSgicm9sZSIsInJvdyIpLHRoaXMuJC5oZWFkZXIuYXBwZW5kQ2hpbGQocik7bGV0IG49ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidHIiKTtuLnNldEF0dHJpYnV0ZSgicGFydCIsInJvdyIpLG4uc2V0QXR0cmlidXRlKCJyb2xlIiwicm93IiksdGhpcy4kLmZvb3Rlci5hcHBlbmRDaGlsZChuKX1mb3IoO3RoaXMuJC5oZWFkZXIuY2hpbGRyZW4ubGVuZ3RoPnQubGVuZ3RoOyl0aGlzLiQuaGVhZGVyLnJlbW92ZUNoaWxkKHRoaXMuJC5oZWFkZXIuZmlyc3RFbGVtZW50Q2hpbGQpLHRoaXMuJC5mb290ZXIucmVtb3ZlQ2hpbGQodGhpcy4kLmZvb3Rlci5maXJzdEVsZW1lbnRDaGlsZCk7QXJyYXkuZnJvbSh0aGlzLiQuaGVhZGVyLmNoaWxkcmVuKS5mb3JFYWNoKChyLG4pPT50aGlzLl91cGRhdGVSb3cocix0W25dLCJoZWFkZXIiLG49PT10Lmxlbmd0aC0xKSksQXJyYXkuZnJvbSh0aGlzLiQuZm9vdGVyLmNoaWxkcmVuKS5mb3JFYWNoKChyLG4pPT50aGlzLl91cGRhdGVSb3cocix0W3QubGVuZ3RoLTEtbl0sImZvb3RlciIsbj09PTApKSx0aGlzLl91cGRhdGVSb3codGhpcy4kLnNpemVyLHRbdC5sZW5ndGgtMV0pLHRoaXMuX3Jlc2l6ZUhhbmRsZXIoKSx0aGlzLl9mcm96ZW5DZWxsc0NoYW5nZWQoKSx0aGlzLl91cGRhdGVGaXJzdEFuZExhc3RDb2x1bW4oKSx0aGlzLl9yZXNldEtleWJvYXJkTmF2aWdhdGlvbigpLHRoaXMuX2ExMXlVcGRhdGVIZWFkZXJSb3dzKCksdGhpcy5fYTExeVVwZGF0ZUZvb3RlclJvd3MoKSx0aGlzLl9fdXBkYXRlRm9vdGVyUG9zaXRpb25pbmcoKX1fX3VwZGF0ZUZvb3RlclBvc2l0aW9uaW5nKCl7dGhpcy5fZmlyZWZveCYmKHRoaXMuJC5pdGVtcy5zdHlsZS5wYWRkaW5nQm90dG9tPTAsdGhpcy5oZWlnaHRCeVJvd3N8fCh0aGlzLiQuaXRlbXMuc3R5bGUucGFkZGluZ0JvdHRvbT1gJHt0aGlzLiQuZm9vdGVyLm9mZnNldEhlaWdodH1weGApKSx0aGlzLl9pb3MmJiF3aW5kb3cuQ1NTLnN1cHBvcnRzKCJwb3NpdGlvbiIsInN0aWNreSIpJiYodGhpcy4kLnRhYmxlLnN0eWxlLmhlaWdodD0iIix0aGlzLiQudGFibGUuc3R5bGUubWluSGVpZ2h0PSIxMDAlIix0aGlzLiQudGFibGUuc3R5bGUubWF4SGVpZ2h0PSIxMDAlIixzZXRUaW1lb3V0KCgpPT50aGlzLiQudGFibGUuc3R5bGUuaGVpZ2h0PWAke3RoaXMuJC5zY3JvbGxlci5vZmZzZXRIZWlnaHR9cHhgKSl9X3VwZGF0ZUl0ZW0odCxyKXt0Ll9pdGVtPXI7bGV0IG49dGhpcy5fX2dldFJvd01vZGVsKHQpO3RoaXMuX3RvZ2dsZUF0dHJpYnV0ZSgic2VsZWN0ZWQiLG4uc2VsZWN0ZWQsdCksdGhpcy5fYTExeVVwZGF0ZVJvd1NlbGVjdGVkKHQsbi5zZWxlY3RlZCksdGhpcy5fYTExeVVwZGF0ZVJvd0xldmVsKHQsbi5sZXZlbCksdGhpcy5fdG9nZ2xlQXR0cmlidXRlKCJleHBhbmRlZCIsbi5leHBhbmRlZCx0KSx0aGlzLl90b2dnbGVBdHRyaWJ1dGUoImRldGFpbHMtb3BlbmVkIix0aGlzLl9pc0RldGFpbHNPcGVuZWQociksdCksKHRoaXMuX3Jvd0RldGFpbHNUZW1wbGF0ZXx8dGhpcy5yb3dEZXRhaWxzUmVuZGVyZXIpJiZ0aGlzLl90b2dnbGVEZXRhaWxzQ2VsbCh0LHIpLHRoaXMuX2dlbmVyYXRlQ2VsbENsYXNzTmFtZXModCxuKSx0aGlzLl9maWx0ZXJEcmFnQW5kRHJvcCh0LG4pLEFycmF5LmZyb20odC5jaGlsZHJlbikuZm9yRWFjaChpPT57aWYoaS5fcmVuZGVyZXIpe2xldCBvPWkuX2NvbHVtbnx8dGhpcztpLl9yZW5kZXJlci5jYWxsKG8saS5fY29udGVudCxvLG4pfWVsc2UgaS5faW5zdGFuY2UmJihpLl9pbnN0YW5jZS5fX2RldGFpbHNPcGVuZWRfXz1uLmRldGFpbHNPcGVuZWQsaS5faW5zdGFuY2UuX19zZWxlY3RlZF9fPW4uc2VsZWN0ZWQsaS5faW5zdGFuY2UuX19sZXZlbF9fPW4ubGV2ZWwsaS5faW5zdGFuY2UuX19leHBhbmRlZF9fPW4uZXhwYW5kZWQsaS5faW5zdGFuY2Uuc2V0UHJvcGVydGllcyhuKSl9KSx0aGlzLl9kZWJvdW5jZXJVcGRhdGVIZWlnaHRzPXNyLmRlYm91bmNlKHRoaXMuX2RlYm91bmNlclVwZGF0ZUhlaWdodHMsbW8uYWZ0ZXIoMSksKCk9Pnt0aGlzLl91cGRhdGVNZXRyaWNzKCksdGhpcy5fcG9zaXRpb25JdGVtcygpLHRoaXMuX3VwZGF0ZVNjcm9sbGVyU2l6ZSgpfSl9X3Jlc2l6ZUhhbmRsZXIoKXt0aGlzLl91cGRhdGVEZXRhaWxzQ2VsbEhlaWdodHMoKSx0aGlzLl9hY2Nlc3NJcm9uTGlzdEFQSShzdXBlci5fcmVzaXplSGFuZGxlciwhMCksdGhpcy5fdXBkYXRlU2Nyb2xsZXJNZWFzdXJlbWVudHMoKSx0aGlzLl9fdXBkYXRlRm9vdGVyUG9zaXRpb25pbmcoKX1fb25BbmltYXRpb25FbmQodCl7dC5hbmltYXRpb25OYW1lLmluZGV4T2YoInZhYWRpbi1ncmlkLWFwcGVhciIpPT09MCYmKHRoaXMuX3JlbmRlcigpLHQuc3RvcFByb3BhZ2F0aW9uKCksdGhpcy5ub3RpZnlSZXNpemUoKSx0aGlzLl9faXRlbXNSZWNlaXZlZCgpLHJlcXVlc3RBbmltYXRpb25GcmFtZSgoKT0+e3RoaXMuX19zY3JvbGxUb1BlbmRpbmdJbmRleCgpLHRoaXMuJC50YWJsZS5zdHlsZS53ZWJraXRPdmVyZmxvd1Njcm9sbGluZz0idG91Y2gifSkpfV90b2dnbGVBdHRyaWJ1dGUodCxyLG4pe24uaGFzQXR0cmlidXRlKHQpPT09IXImJihyP24uc2V0QXR0cmlidXRlKHQsIiIpOm4ucmVtb3ZlQXR0cmlidXRlKHQpKX1fX2dldFJvd01vZGVsKHQpe3JldHVybntpbmRleDp0LmluZGV4LGl0ZW06dC5faXRlbSxsZXZlbDp0aGlzLl9nZXRJbmRleExldmVsKHQuaW5kZXgpLGV4cGFuZGVkOnRoaXMuX2lzRXhwYW5kZWQodC5faXRlbSksc2VsZWN0ZWQ6dGhpcy5faXNTZWxlY3RlZCh0Ll9pdGVtKSxkZXRhaWxzT3BlbmVkOiEhKHRoaXMuX3Jvd0RldGFpbHNUZW1wbGF0ZXx8dGhpcy5yb3dEZXRhaWxzUmVuZGVyZXIpJiZ0aGlzLl9pc0RldGFpbHNPcGVuZWQodC5faXRlbSl9fXJlbmRlcigpe3RoaXMuX2NvbHVtblRyZWUmJih0aGlzLl9jb2x1bW5UcmVlLmZvckVhY2godD0+e3QuZm9yRWFjaChyPT5yLl9yZW5kZXJIZWFkZXJBbmRGb290ZXIoKSl9KSx0aGlzLl91cGRhdGUoKSl9bm90aWZ5UmVzaXplKCl7c3VwZXIubm90aWZ5UmVzaXplKCl9X2hlaWdodEJ5Um93c0NoYW5nZWQodCxyKXsodHx8cikmJnRoaXMubm90aWZ5UmVzaXplKCl9X19mb3JjZVJlZmxvdygpe3RoaXMuX2RlYm91bmNlckZvcmNlUmVmbG93PXNyLmRlYm91bmNlKHRoaXMuX2RlYm91bmNlckZvcmNlUmVmbG93LE5pLCgpPT57dGhpcy4kLnNjcm9sbGVyLnN0eWxlLm92ZXJmbG93PSJoaWRkZW4iLHNldFRpbWVvdXQoKCk9PnRoaXMuJC5zY3JvbGxlci5zdHlsZS5vdmVyZmxvdz0iIil9KX19O2N1c3RvbUVsZW1lbnRzLmRlZmluZShlVi5pcyxlVik7ZnVuY3Rpb24gc3VlKGUsdCl7cmV0dXJuIE5ndChlLHQpfXZhciBjdj1jbGFzcyBleHRlbmRzIG10e2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl9ydW49IiJ9X2NzdlVybCh0LHIsbil7cmV0dXJuIHI/Q24obih0LHIpLHtmb3JtYXQ6ImNzdiJ9KToiIn1fanNvblVybCh0LHIsbil7cmV0dXJuIHI/bih0LHIpOiIifV9jc3ZOYW1lKHQscil7cmV0dXJuIHI/YHJ1bi0ke3J9LXRhZy0ke3R9LmNzdmA6IiJ9X2pzb25OYW1lKHQscil7cmV0dXJuIHI/YHJ1bi0ke3J9LXRhZy0ke3R9Lmpzb25gOiIifX07Y3YudGVtcGxhdGU9UWAKICAgIDxwYXBlci1kcm9wZG93bi1tZW51CiAgICAgIG5vLWxhYmVsLWZsb2F0PSJ0cnVlIgogICAgICBsYWJlbD0icnVuIHRvIGRvd25sb2FkIgogICAgICBzZWxlY3RlZC1pdGVtLWxhYmVsPSJ7e19ydW59fSIKICAgID4KICAgICAgPHBhcGVyLWxpc3Rib3ggc2xvdD0iZHJvcGRvd24tY29udGVudCI+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20tcmVwZWF0IiBpdGVtcz0iW1tydW5zXV0iPgogICAgICAgICAgPHBhcGVyLWl0ZW0gbm8tbGFiZWwtZmxvYXQ9InRydWUiPltbaXRlbV1dPC9wYXBlci1pdGVtPgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDwvcGFwZXItbGlzdGJveD4KICAgIDwvcGFwZXItZHJvcGRvd24tbWVudT4KICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfcnVuXV0iPgogICAgICA8YSBkb3dubG9hZD0iW1tfY3N2TmFtZSh0YWcsIF9ydW4pXV0iIGhyZWY9IltbX2NzdlVybCh0YWcsIF9ydW4sIHVybEZuKV1dIgogICAgICAgID5DU1Y8L2EKICAgICAgPjwhLS0KICAgICAgLS0+PGEKICAgICAgICBkb3dubG9hZD0iW1tfanNvbk5hbWUodGFnLCBfcnVuKV1dIgogICAgICAgIGhyZWY9IltbX2pzb25VcmwodGFnLCBfcnVuLCB1cmxGbildXSIKICAgICAgICA+SlNPTjwvYQogICAgICA+CiAgICA8L3RlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICAgIGhlaWdodDogMzJweDsKICAgICAgfQogICAgICBwYXBlci1kcm9wZG93bi1tZW51IHsKICAgICAgICB3aWR0aDogMTAwcHg7CiAgICAgICAgLS1wYXBlci1pbnB1dC1jb250YWluZXItbGFiZWw6IHsKICAgICAgICAgIGZvbnQtc2l6ZTogMTBweDsKICAgICAgICB9CiAgICAgICAgLS1wYXBlci1pbnB1dC1jb250YWluZXItaW5wdXQ6IHsKICAgICAgICAgIGZvbnQtc2l6ZTogMTBweDsKICAgICAgICB9CiAgICAgIH0KICAgICAgYSB7CiAgICAgICAgZm9udC1zaXplOiAxMHB4OwogICAgICAgIG1hcmdpbjogMCAwLjJlbTsKICAgICAgfQogICAgICBwYXBlci1pbnB1dCB7CiAgICAgICAgZm9udC1zaXplOiAyMnB4OwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sY3YucHJvdG90eXBlLCJfcnVuIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxjdi5wcm90b3R5cGUsInJ1bnMiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sY3YucHJvdG90eXBlLCJ0YWciLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sY3YucHJvdG90eXBlLCJ1cmxGbiIsdm9pZCAwKTtjdj1FKFt5dCgidGYtZG93bmxvYWRlciIpXSxjdik7dmFyIG9ocj02NCxwbGk9bmV3IFVSTFNlYXJjaFBhcmFtcyh3aW5kb3cubG9jYXRpb24uc2VhcmNoKSxXbj1jbGFzcyBleHRlbmRzIG10e2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmNvbG9yU2NhbGU9bnVsbCx0aGlzLl9sb2FkRGF0YUNhbGxiYWNrPSh0LHIsbik9PntpZihuPT1udWxsKXtjb25zb2xlLmVycm9yKCJGYWlsZWQgdG8gbG9hZCBkYXRhIGZvcjoiLHIpO3JldHVybn1sZXQgaT1uLm1hcChhPT4oe3dhbGxfdGltZTpuZXcgRGF0ZShhWzBdKjFlMyksc3RlcDphWzFdLHNjYWxhcjphWzJdfSkpLG89dGhpcy5fZ2V0U2VyaWVzTmFtZUZyb21EYXR1bShyKTt0LnNldFNlcmllc01ldGFkYXRhKG8sciksdC5zZXRTZXJpZXNEYXRhKG8saSl9LHRoaXMuZ2V0RGF0YUxvYWRVcmw9KHt0YWc6dCxydW46cn0pPT52ZSgpLnBsdWdpblJvdXRlKCJzY2FsYXJzIiwiL3NjYWxhcnMiLG5ldyBVUkxTZWFyY2hQYXJhbXMoe3RhZzp0LHJ1bjpyfSkpLHRoaXMuX2Rvd25sb2FkVXJsRm49KHQscik9PnRoaXMuZ2V0RGF0YUxvYWRVcmwoe3RhZzp0LHJ1bjpyfSksdGhpcy5yZXF1ZXN0RGF0YT0odCxyLG4pPT50aGlzLmluQ29sYWI/dGhpcy5fcmVxdWVzdERhdGFHZXQodCxyLG4pOnRoaXMuX3JlcXVlc3REYXRhUG9zdCh0LHIsbiksdGhpcy5fcmVxdWVzdERhdGFHZXQ9KHQscixuKT0+e2xldCBvPXZlKCkucGx1Z2luUm91dGUoInNjYWxhcnMiLCIvc2NhbGFycyIpO1Byb21pc2UuYWxsKHQubWFwKGE9PntsZXQgcz1DbihvLHt0YWc6YS50YWcscnVuOmEucnVufSk7cmV0dXJuIHRoaXMucmVxdWVzdE1hbmFnZXIucmVxdWVzdChzKS50aGVuKGw9PnZvaWQgcih7aXRlbTphLGRhdGE6bH0pKX0pKS5maW5hbGx5KCgpPT52b2lkIG4oKSl9LHRoaXMuX3JlcXVlc3REYXRhUG9zdD0odCxyLG4pPT57dmFyIGM7bGV0IG89dmUoKS5wbHVnaW5Sb3V0ZSgic2NhbGFycyIsIi9zY2FsYXJzX211bHRpcnVuIiksYT1uZXcgTWFwO2ZvcihsZXR7dGFnOnUscnVuOmh9b2YgdCl7bGV0IGY9YS5nZXQodSk7Zj09bnVsbCYmYS5zZXQodSxmPVtdKSxmLnB1c2goaCl9bGV0IHM9KGM9dGhpcy5iYXRjaFNpemUpIT1udWxsP2M6b2hyLGw9W107Zm9yKGxldFt1LGhdb2YgYSlmb3IobGV0IGY9MDtmPGgubGVuZ3RoO2YrPXMpbC5wdXNoKHt0YWc6dSxydW5zOmguc2xpY2UoZixmK3MpfSk7UHJvbWlzZS5hbGwobC5tYXAoKHt0YWc6dSxydW5zOmh9KT0+dGhpcy5yZXF1ZXN0TWFuYWdlci5yZXF1ZXN0KG8se3RhZzp1LHJ1bnM6aH0pLnRoZW4oZj0+e2ZvcihsZXQgcCBvZiBoKXtsZXQgZD17dGFnOnUscnVuOnB9O09iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChmLHApP3Ioe2l0ZW06ZCxkYXRhOmZbcF19KTpyKHtpdGVtOmQsZGF0YTpudWxsfSl9fSkpKS5maW5hbGx5KCgpPT52b2lkIG4oKSl9LHRoaXMuX2dldERhdGFMb2FkTmFtZT10PT50aGlzLl9nZXRTZXJpZXNOYW1lRnJvbURhdHVtKHQpLHRoaXMuX2V4cGFuZGVkPSExLHRoaXMuX3Rvb2x0aXBDb2x1bW5zPSgoKT0+e2xldCB0PW9zdC5zbGljZSgpLHI9dC5maW5kSW5kZXgobj0+bi50aXRsZT09Ik5hbWUiKTtyZXR1cm4gdC5zcGxpY2UociwxLHt0aXRsZToiTmFtZSIsZXZhbHVhdGU6bj0+e2xldCBpPW4uZGF0YXNldC5tZXRhZGF0YSgpLm1ldGE7cmV0dXJuIHRoaXMuX2dldFNlcmllc0Rpc3BsYXlOYW1lRnJvbURhdHVtKGkpfX0pLHR9KSgpfV9nZXRDaGFydERhdGFMb2FkZXIoKXt2YXIgdDtyZXR1cm4odD10aGlzLnNoYWRvd1Jvb3QpPT1udWxsP3ZvaWQgMDp0LnF1ZXJ5U2VsZWN0b3IoInRmLWxpbmUtY2hhcnQtZGF0YS1sb2FkZXIiKX1yZWxvYWQoKXt0aGlzLl9nZXRDaGFydERhdGFMb2FkZXIoKS5yZWxvYWQoKX1yZWRyYXcoKXt0aGlzLl9nZXRDaGFydERhdGFMb2FkZXIoKS5yZWRyYXcoKX1fdG9nZ2xlRXhwYW5kZWQodCl7dGhpcy5zZXQoIl9leHBhbmRlZCIsIXRoaXMuX2V4cGFuZGVkKSx0aGlzLnJlZHJhdygpfV90b2dnbGVMb2dTY2FsZSgpe3RoaXMuc2V0KCJfbG9nU2NhbGVBY3RpdmUiLCF0aGlzLl9sb2dTY2FsZUFjdGl2ZSl9X3Jlc2V0RG9tYWluKCl7bGV0IHQ9dGhpcy5fZ2V0Q2hhcnREYXRhTG9hZGVyKCk7dCYmdC5yZXNldERvbWFpbigpfV91cGRhdGVEb3dubG9hZExpbmsoKXt2YXIgbjtsZXQgdD10aGlzLl9nZXRDaGFydERhdGFMb2FkZXIoKS5leHBvcnRBc1N2Z1N0cmluZygpLHI9KG49dGhpcy5zaGFkb3dSb290KT09bnVsbD92b2lkIDA6bi5xdWVyeVNlbGVjdG9yKCIjc3ZnTGluayIpO3IuaHJlZj1gZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCwke2J0b2EodCl9YH1fcnVuc0Zyb21EYXRhKHQpe3JldHVybiB0Lm1hcChyPT5yLnJ1bil9X2dldERhdGFTZXJpZXMoKXtyZXR1cm4gdGhpcy5kYXRhVG9Mb2FkLm1hcCh0PT50aGlzLl9nZXRTZXJpZXNOYW1lRnJvbURhdHVtKHQpKX1fZ2V0U2VyaWVzTmFtZUZyb21EYXR1bSh7cnVuOnQsZXhwZXJpbWVudDpyPXtuYW1lOiJfZGVmYXVsdCJ9fSl7cmV0dXJuIEpTT04uc3RyaW5naWZ5KFtyLm5hbWUsdF0pfV9nZXRTZXJpZXNEaXNwbGF5TmFtZUZyb21EYXR1bSh0KXtyZXR1cm4gdC5ydW59X2dldENvbG9yU2NhbGUoKXtyZXR1cm4gdGhpcy5jb2xvclNjYWxlIT09bnVsbD90aGlzLmNvbG9yU2NhbGU6e3NjYWxlOnQ9PntsZXRbLHJdPUpTT04ucGFyc2UodCk7cmV0dXJuIGZuKHIpfX19fTtXbi50ZW1wbGF0ZT1RYAogICAgPHRmLWNhcmQtaGVhZGluZwogICAgICB0YWc9IltbdGFnXV0iCiAgICAgIGRpc3BsYXktbmFtZT0iW1t0YWdNZXRhZGF0YS5kaXNwbGF5TmFtZV1dIgogICAgICBkZXNjcmlwdGlvbj0iW1t0YWdNZXRhZGF0YS5kZXNjcmlwdGlvbl1dIgogICAgPjwvdGYtY2FyZC1oZWFkaW5nPgogICAgPGRpdiBpZD0idGYtbGluZS1jaGFydC1kYXRhLWxvYWRlci1jb250YWluZXIiPgogICAgICA8dGYtbGluZS1jaGFydC1kYXRhLWxvYWRlcgogICAgICAgIGFjdGl2ZT0iW1thY3RpdmVdXSIKICAgICAgICBjb2xvci1zY2FsZT0iW1tfZ2V0Q29sb3JTY2FsZShjb2xvclNjYWxlKV1dIgogICAgICAgIGRhdGEtc2VyaWVzPSJbW19nZXREYXRhU2VyaWVzKGRhdGFUb0xvYWQuKildXSIKICAgICAgICBkYXRhLXRvLWxvYWQ9IltbZGF0YVRvTG9hZF1dIgogICAgICAgIGdldC1kYXRhLWxvYWQtbmFtZT0iW1tfZ2V0RGF0YUxvYWROYW1lXV0iCiAgICAgICAgZ2V0LWRhdGEtbG9hZC11cmw9IltbZ2V0RGF0YUxvYWRVcmxdXSIKICAgICAgICByZXF1ZXN0LWRhdGE9IltbcmVxdWVzdERhdGFdXSIKICAgICAgICBpZ25vcmUteS1vdXRsaWVycz0iW1tpZ25vcmVZT3V0bGllcnNdXSIKICAgICAgICBsb2FkLWRhdGEtY2FsbGJhY2s9IltbX2xvYWREYXRhQ2FsbGJhY2tdXSIKICAgICAgICBsb2FkLWtleT0iW1t0YWddXSIKICAgICAgICBsb2ctc2NhbGUtYWN0aXZlPSJbW19sb2dTY2FsZUFjdGl2ZV1dIgogICAgICAgIHJlcXVlc3QtbWFuYWdlcj0iW1tyZXF1ZXN0TWFuYWdlcl1dIgogICAgICAgIHNtb290aGluZy1lbmFibGVkPSJbW3Ntb290aGluZ0VuYWJsZWRdXSIKICAgICAgICBzbW9vdGhpbmctd2VpZ2h0PSJbW3Ntb290aGluZ1dlaWdodF1dIgogICAgICAgIHRhZy1tZXRhZGF0YT0iW1t0YWdNZXRhZGF0YV1dIgogICAgICAgIHRvb2x0aXAtY29sdW1ucz0iW1tfdG9vbHRpcENvbHVtbnNdXSIKICAgICAgICB0b29sdGlwLXBvc2l0aW9uPSJhdXRvIgogICAgICAgIHRvb2x0aXAtc29ydGluZy1tZXRob2Q9IltbdG9vbHRpcFNvcnRpbmdNZXRob2RdXSIKICAgICAgICB4LXR5cGU9IltbeFR5cGVdXSIKICAgICAgPgogICAgICA8L3RmLWxpbmUtY2hhcnQtZGF0YS1sb2FkZXI+CiAgICA8L2Rpdj4KICAgIDxkaXYgaWQ9ImJ1dHRvbnMiPgogICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICBzZWxlY3RlZCQ9IltbX2V4cGFuZGVkXV0iCiAgICAgICAgaWNvbj0iZnVsbHNjcmVlbiIKICAgICAgICBvbi10YXA9Il90b2dnbGVFeHBhbmRlZCIKICAgICAgPjwvcGFwZXItaWNvbi1idXR0b24+CiAgICAgIDxwYXBlci1pY29uLWJ1dHRvbgogICAgICAgIHNlbGVjdGVkJD0iW1tfbG9nU2NhbGVBY3RpdmVdXSIKICAgICAgICBpY29uPSJsaW5lLXdlaWdodCIKICAgICAgICBvbi10YXA9Il90b2dnbGVMb2dTY2FsZSIKICAgICAgICB0aXRsZT0iVG9nZ2xlIHktYXhpcyBsb2cgc2NhbGUiCiAgICAgID48L3BhcGVyLWljb24tYnV0dG9uPgogICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICBpY29uPSJzZXR0aW5ncy1vdmVyc2NhbiIKICAgICAgICBvbi10YXA9Il9yZXNldERvbWFpbiIKICAgICAgICB0aXRsZT0iRml0IGRvbWFpbiB0byBkYXRhIgogICAgICA+PC9wYXBlci1pY29uLWJ1dHRvbj4KICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW3Nob3dEb3dubG9hZExpbmtzXV0iPgogICAgICAgIDxwYXBlci1tZW51LWJ1dHRvbiBvbi1wYXBlci1kcm9wZG93bi1vcGVuPSJfdXBkYXRlRG93bmxvYWRMaW5rIj4KICAgICAgICAgIDxwYXBlci1pY29uLWJ1dHRvbgogICAgICAgICAgICBjbGFzcz0iZHJvcGRvd24tdHJpZ2dlciIKICAgICAgICAgICAgc2xvdD0iZHJvcGRvd24tdHJpZ2dlciIKICAgICAgICAgICAgaWNvbj0iZmlsZS1kb3dubG9hZCIKICAgICAgICAgID48L3BhcGVyLWljb24tYnV0dG9uPgogICAgICAgICAgPHBhcGVyLWxpc3Rib3ggY2xhc3M9ImRyb3Bkb3duLWNvbnRlbnQiIHNsb3Q9ImRyb3Bkb3duLWNvbnRlbnQiPgogICAgICAgICAgICA8cGFwZXItaXRlbT4KICAgICAgICAgICAgICA8YSBpZD0ic3ZnTGluayIgZG93bmxvYWQ9IltbdGFnXV0uc3ZnIj4KICAgICAgICAgICAgICAgIERvd25sb2FkIEN1cnJlbnQgQ2hhcnQgYXMgU1ZHCiAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICA8L3BhcGVyLWl0ZW0+CiAgICAgICAgICA8L3BhcGVyLWxpc3Rib3g+CiAgICAgICAgPC9wYXBlci1tZW51LWJ1dHRvbj4KICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPHNwYW4gc3R5bGU9ImZsZXgtZ3JvdzogMSI+PC9zcGFuPgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9Iltbc2hvd0Rvd25sb2FkTGlua3NdXSI+CiAgICAgICAgPGRpdiBjbGFzcz0iZG93bmxvYWQtbGlua3MiPgogICAgICAgICAgPHRmLWRvd25sb2FkZXIKICAgICAgICAgICAgcnVucz0iW1tfcnVuc0Zyb21EYXRhKGRhdGFUb0xvYWQpXV0iCiAgICAgICAgICAgIHRhZz0iW1t0YWddXSIKICAgICAgICAgICAgdXJsLWZuPSJbW19kb3dubG9hZFVybEZuXV0iCiAgICAgICAgICA+PC90Zi1kb3dubG9hZGVyPgogICAgICAgIDwvZGl2PgogICAgICA8L3RlbXBsYXRlPgogICAgPC9kaXY+CiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBtYXJnaW46IDVweDsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICB3aWR0aDogMzMwcHg7CiAgICAgIH0KCiAgICAgIDpob3N0KFtfZXhwYW5kZWRdKSB7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIH0KCiAgICAgIDpob3N0KFtfZXhwYW5kZWRdKSAjdGYtbGluZS1jaGFydC1kYXRhLWxvYWRlci1jb250YWluZXIgewogICAgICAgIGhlaWdodDogNDAwcHg7CiAgICAgIH0KCiAgICAgICN0Zi1saW5lLWNoYXJ0LWRhdGEtbG9hZGVyLWNvbnRhaW5lciB7CiAgICAgICAgaGVpZ2h0OiAyMDBweDsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgfQoKICAgICAgdGYtY2FyZC1oZWFkaW5nIHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBtYXJnaW4tYm90dG9tOiAxMHB4OwogICAgICB9CgogICAgICAjYnV0dG9ucyB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmbGV4LWRpcmVjdGlvbjogcm93OwogICAgICB9CgogICAgICBwYXBlci1pY29uLWJ1dHRvbiB7CiAgICAgICAgY29sb3I6ICMyMTk2ZjM7CiAgICAgICAgYm9yZGVyLXJhZGl1czogMTAwJTsKICAgICAgICB3aWR0aDogMzJweDsKICAgICAgICBoZWlnaHQ6IDMycHg7CiAgICAgICAgcGFkZGluZzogNHB4OwogICAgICB9CgogICAgICBwYXBlci1pY29uLWJ1dHRvbltzZWxlY3RlZF0gewogICAgICAgIGJhY2tncm91bmQ6IHZhcigtLXRiLXVpLWxpZ2h0LWFjY2VudCk7CiAgICAgIH0KCiAgICAgIC5kb3dubG9hZC1saW5rcyB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBoZWlnaHQ6IDMycHg7CiAgICAgIH0KCiAgICAgIC5kb3dubG9hZC1saW5rcyBhIHsKICAgICAgICBhbGlnbi1zZWxmOiBjZW50ZXI7CiAgICAgICAgZm9udC1zaXplOiAxMHB4OwogICAgICAgIG1hcmdpbjogMnB4OwogICAgICB9CgogICAgICAuZG93bmxvYWQtbGlua3MgcGFwZXItZHJvcGRvd24tbWVudSB7CiAgICAgICAgd2lkdGg6IDEwMHB4OwogICAgICAgIC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWxhYmVsOiB7CiAgICAgICAgICBmb250LXNpemU6IDEwcHg7CiAgICAgICAgfQogICAgICAgIC0tcGFwZXItaW5wdXQtY29udGFpbmVyLWlucHV0OiB7CiAgICAgICAgICBmb250LXNpemU6IDEwcHg7CiAgICAgICAgfQogICAgICB9CgogICAgICBwYXBlci1tZW51LWJ1dHRvbiB7CiAgICAgICAgcGFkZGluZzogMDsKICAgICAgfQogICAgICBwYXBlci1pdGVtIGEgewogICAgICAgIGNvbG9yOiBpbmhlcml0OwogICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTsKICAgICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sV24ucHJvdG90eXBlLCJ0YWciLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLFduLnByb3RvdHlwZSwiZGF0YVRvTG9hZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxXbi5wcm90b3R5cGUsInhUeXBlIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sV24ucHJvdG90eXBlLCJhY3RpdmUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxXbi5wcm90b3R5cGUsImlnbm9yZVlPdXRsaWVycyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixBZSldLFduLnByb3RvdHlwZSwicmVxdWVzdE1hbmFnZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxXbi5wcm90b3R5cGUsInNob3dEb3duTGlua3MiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxXbi5wcm90b3R5cGUsInNtb290aGluZ0VuYWJsZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sV24ucHJvdG90eXBlLCJzbW9vdGhpbmdXZWlnaHQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sV24ucHJvdG90eXBlLCJ0YWdNZXRhZGF0YSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxXbi5wcm90b3R5cGUsImNvbG9yU2NhbGUiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sV24ucHJvdG90eXBlLCJ0b29sdGlwU29ydGluZ01ldGhvZCIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxXbi5wcm90b3R5cGUsImJhdGNoU2l6ZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sV24ucHJvdG90eXBlLCJpbkNvbGFiIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFduLnByb3RvdHlwZSwiX2xvYWREYXRhQ2FsbGJhY2siLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pXSxXbi5wcm90b3R5cGUsImdldERhdGFMb2FkVXJsIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFduLnByb3RvdHlwZSwiX2Rvd25sb2FkVXJsRm4iLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pXSxXbi5wcm90b3R5cGUsInJlcXVlc3REYXRhIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFduLnByb3RvdHlwZSwiX2dldERhdGFMb2FkTmFtZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4scmVmbGVjdFRvQXR0cmlidXRlOiEwfSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxXbi5wcm90b3R5cGUsIl9leHBhbmRlZCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLFduLnByb3RvdHlwZSwiX2xvZ1NjYWxlQWN0aXZlIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxXbi5wcm90b3R5cGUsIl90b29sdGlwQ29sdW1ucyIsdm9pZCAwKTtXbj1FKFt5dCgidGYtc2NhbGFyLWNhcmQiKV0sV24pO3ZhciBWcz1jbGFzcyBleHRlbmRzIHN1ZShbdVddLG10KXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5zZXNzaW9uR3JvdXA9bnVsbCx0aGlzLl94VHlwZT1FZC5TVEVQLHRoaXMuX25vTXVsdGlFeHBlcmltZW50cz0hMSx0aGlzLl9yZXF1ZXN0RGF0YT0odCxyLG4pPT57UHJvbWlzZS5hbGwodC5tYXAoaT0+e2xldCBvPXtleHBlcmltZW50TmFtZTp0aGlzLmV4cGVyaW1lbnROYW1lLHNlc3Npb25OYW1lOmkucnVuLG1ldHJpY05hbWU6aS50YWd9O3JldHVybiB0aGlzLmJhY2tlbmQubGlzdE1ldHJpY0V2YWxzKG8pLnRoZW4oYT0+dm9pZCByKHtpdGVtOmksZGF0YTphfSkpfSkpLmZpbmFsbHkoKCk9PnZvaWQgbigpKX0sdGhpcy5fY29sb3JTY2FsZT17c2NhbGU6dD0+e2xldCByPUpTT04ucGFyc2UodClbMV0sbj10aGlzLl9pbmRleE9mU2Vzc2lvbi5nZXQociksaT1uUjtyZXR1cm4gaVsodGhpcy5fc2Vzc2lvbkdyb3VwTmFtZUhhc2grbiklaS5sZW5ndGhdfX19Y29ubmVjdGVkQ2FsbGJhY2soKXtzdXBlci5jb25uZWN0ZWRDYWxsYmFjaygpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcigiaXJvbi1yZXNpemUiLHRoaXMucmVkcmF3LmJpbmQodGhpcykpfXJlZHJhdygpe3ZhciB0Oyh0PXRoaXMuc2hhZG93Um9vdCk9PW51bGx8fHQucXVlcnlTZWxlY3RvckFsbCgidGYtc2NhbGFyLWNhcmQiKS5mb3JFYWNoKHI9PntyLnJlZHJhdygpfSl9X3Nlc3Npb25Hcm91cENoYW5nZWQoKXt2YXIgdDshdGhpcy5zZXNzaW9uR3JvdXB8fE9iamVjdC5rZXlzKHRoaXMuc2Vzc2lvbkdyb3VwKS5sZW5ndGg9PTA/KHRoaXMuX2luZGV4T2ZTZXNzaW9uPW5ldyBNYXAsdGhpcy5fc2Vzc2lvbkdyb3VwTmFtZUhhc2g9MCk6KHRoaXMuX2luZGV4T2ZTZXNzaW9uPW5ldyBNYXAodGhpcy5zZXNzaW9uR3JvdXAuc2Vzc2lvbnMubWFwKChyLG4pPT5bci5uYW1lLG5dKSksdGhpcy5fc2Vzc2lvbkdyb3VwTmFtZUhhc2g9bWN0KHRoaXMuc2Vzc2lvbkdyb3VwLm5hbWUpKSwodD10aGlzLnNoYWRvd1Jvb3QpPT1udWxsfHx0LnF1ZXJ5U2VsZWN0b3JBbGwoInRmLXNjYWxhci1jYXJkIikuZm9yRWFjaChyPT57bGV0IG49cixpPW4uZ2V0KCJ0YWciKTtuLnNldCgidGFnIiwiIiksbi5zZXQoInRhZyIsaSl9KX1faGF2ZU1ldHJpY3MoKXtyZXR1cm4gdGhpcy52aXNpYmxlU2NoZW1hJiZBcnJheS5pc0FycmF5KHRoaXMudmlzaWJsZVNjaGVtYS5tZXRyaWNJbmZvcykmJnRoaXMudmlzaWJsZVNjaGVtYS5tZXRyaWNJbmZvcy5sZW5ndGg+MH1faGF2ZU1ldHJpY3NBbmRTZXNzaW9uR3JvdXAoKXtyZXR1cm4gdGhpcy5zZXNzaW9uR3JvdXAmJnRoaXMuX2hhdmVNZXRyaWNzKCl9X2NvbXB1dGVTZXJpZXNGb3JTZXNzaW9uR3JvdXBNZXRyaWModCxyKXtyZXR1cm4gdD09PW51bGx8fE9iamVjdC5rZXlzKHQpLmxlbmd0aD09MHx8cj09PW51bGw/W106dC5zZXNzaW9ucy5maWx0ZXIobj0+ZjMobi5tZXRyaWNWYWx1ZXMsci5uYW1lKSE9PXZvaWQgMCkubWFwKG49Pih7dGFnOnIubmFtZSxydW46bi5uYW1lfSkpfV9jb21wdXRlVGFnTWV0YWRhdGEodCl7cmV0dXJue2Rpc3BsYXlOYW1lOlF1KHQpLGRlc2NyaXB0aW9uOnQuZGVzY3JpcHRpb258fCIifX19O1ZzLnRlbXBsYXRlPVFgCiAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbIXNlc3Npb25Hcm91cF1dIj4KICAgICAgPGRpdj4KICAgICAgICA8aDM+Tm8gc2Vzc2lvbiBncm91cCBzZWxlY3RlZDwvaDM+CiAgICAgICAgPHA+UGxlYXNlIHNlbGVjdCBhIHNlc3Npb24gZ3JvdXAgdG8gc2VlIGl0cyBtZXRyaWMtZ3JhcGhzIGhlcmUuPC9wPgogICAgICA8L2Rpdj4KICAgIDwvdGVtcGxhdGU+CiAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbIV9oYXZlTWV0cmljcyh2aXNpYmxlU2NoZW1hLiopXV0iPgogICAgICA8ZGl2PgogICAgICAgIDxoMz5ObyBtZXRyaWNzIGFyZSBlbmFibGVkPC9oMz4KICAgICAgICA8cD5QbGVhc2UgZW5hYmxlIHNvbWUgbWV0cmljcyB0byBzZWUgY29udGVudCBoZXJlLjwvcD4KICAgICAgPC9kaXY+CiAgICA8L3RlbXBsYXRlPgogICAgPGRpdiBjbGFzcz0ibGF5b3V0IGhvcml6b250YWwgd3JhcCBzZXNzaW9uLWdyb3VwLWRldGFpbHMiPgogICAgICA8dGVtcGxhdGUKICAgICAgICBpcz0iZG9tLWlmIgogICAgICAgIGlmPSJbW19oYXZlTWV0cmljc0FuZFNlc3Npb25Hcm91cCh2aXNpYmxlU2NoZW1hLiosIHNlc3Npb25Hcm91cCldXSIKICAgICAgPgogICAgICAgIDx0ZW1wbGF0ZQogICAgICAgICAgaXM9ImRvbS1yZXBlYXQiCiAgICAgICAgICBpdGVtcz0iW1t2aXNpYmxlU2NoZW1hLm1ldHJpY0luZm9zXV0iCiAgICAgICAgICBhcz0ibWV0cmljSW5mbyIKICAgICAgICA+CiAgICAgICAgICA8IS0tIE5vdGUgdGhhdCB3ZSBkbyBub3QgcHJvdmlkZSBhIHJlcXVlc3QtbWFuYWdlciBhdHRyaWJ1dGUgc2luY2UKICAgICAgICAgICAgICAgd2UgcHJvdmlkZSBhIGZ1bmN0aW9uIGluIHJlcXVlc3QtZGF0YSBmb3IgY2FsbGluZyB0aGUgYmFja2VuZAogICAgICAgICAgICAgICB0byBnZXQgdGhlIG1ldHJpY3MgZGF0YS4KICAgICAgICAgICAgLS0+CiAgICAgICAgICA8dGYtc2NhbGFyLWNhcmQKICAgICAgICAgICAgY2xhc3M9InNjYWxhci1jYXJkIgogICAgICAgICAgICBjb2xvci1zY2FsZT0iW1tfY29sb3JTY2FsZV1dIgogICAgICAgICAgICBkYXRhLXRvLWxvYWQ9IltbX2NvbXB1dGVTZXJpZXNGb3JTZXNzaW9uR3JvdXBNZXRyaWMoc2Vzc2lvbkdyb3VwLCBtZXRyaWNJbmZvKV1dIgogICAgICAgICAgICB0YWc9IltbbWV0cmljSW5mby5uYW1lLnRhZ11dIgogICAgICAgICAgICB0YWctbWV0YWRhdGE9IltbX2NvbXB1dGVUYWdNZXRhZGF0YShtZXRyaWNJbmZvKV1dIgogICAgICAgICAgICB4LXR5cGU9IltbX3hUeXBlXV0iCiAgICAgICAgICAgIG11bHRpLWV4cGVyaW1lbnRzPSJbW19ub011bHRpRXhwZXJpbWVudHNdXSIKICAgICAgICAgICAgcmVxdWVzdC1kYXRhPSJbW19yZXF1ZXN0RGF0YV1dIgogICAgICAgICAgICBhY3RpdmUKICAgICAgICAgID4KICAgICAgICAgIDwvdGYtc2NhbGFyLWNhcmQ+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPC90ZW1wbGF0ZT4KICAgIDwvZGl2PgogICAgPCEtLSAiaXJvbi1mbGV4IiBpcyBuZWVkZWQgdG8gdXNlIHRoZSBsYXlvdXQgY2xhc3NlcyBpbiB0aGUgZGl2IGFib3ZlIC0tPgogICAgPHN0eWxlIGluY2x1ZGU9Imlyb24tZmxleCI+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFZzLnByb3RvdHlwZSwiYmFja2VuZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxWcy5wcm90b3R5cGUsImV4cGVyaW1lbnROYW1lIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFZzLnByb3RvdHlwZSwidmlzaWJsZVNjaGVtYSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxWcy5wcm90b3R5cGUsInNlc3Npb25Hcm91cCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxWcy5wcm90b3R5cGUsIl94VHlwZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLFZzLnByb3RvdHlwZSwiX25vTXVsdGlFeHBlcmltZW50cyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxWcy5wcm90b3R5cGUsIl9pbmRleE9mU2Vzc2lvbiIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxWcy5wcm90b3R5cGUsIl9zZXNzaW9uR3JvdXBOYW1lSGFzaCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbildLFZzLnByb3RvdHlwZSwiX3JlcXVlc3REYXRhIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFZzLnByb3RvdHlwZSwiX2NvbG9yU2NhbGUiLHZvaWQgMCk7RShbQnQoInNlc3Npb25Hcm91cC4qIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxWcy5wcm90b3R5cGUsIl9zZXNzaW9uR3JvdXBDaGFuZ2VkIixudWxsKTtWcz1FKFt5dCgidGYtaHBhcmFtcy1zZXNzaW9uLWdyb3VwLWRldGFpbHMiKV0sVnMpO3ZhciBIZD1jbGFzcyBleHRlbmRzIEd0KF9vKG10KSl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX2hwYXJhbU5hbWU9RmQsdGhpcy5fbWV0cmljTmFtZT1RdX1fdmlzaWJsZVNjaGVtYU9yU2Vzc2lvbkdyb3Vwc0NoYW5nZWQoKXtsZXQgdD10aGlzLiQuc2Vzc2lvbkdyb3Vwc1RhYmxlLmdldCgiZGV0YWlsc09wZW5lZEl0ZW1zIik7dGhpcy4kLnNlc3Npb25Hcm91cHNUYWJsZS5zZXQoImRldGFpbHNPcGVuZWRJdGVtcyIsW10pLHVpKCk7bGV0IHI9bmV3IE1hcDt0aGlzLnNlc3Npb25Hcm91cHMuZm9yRWFjaChuPT57ci5zZXQobi5uYW1lLG4pfSksdGhpcy4kLnNlc3Npb25Hcm91cHNUYWJsZS5zZXQoImRldGFpbHNPcGVuZWRJdGVtcyIsdC5tYXAobj0+ci5nZXQobi5uYW1lKSkuZmlsdGVyKEJvb2xlYW4pKX1fc2Vzc2lvbkdyb3VwSFBhcmFtKHQscil7cmV0dXJuIHQ9PW51bGx8fE9iamVjdC5rZXlzKHQpLmxlbmd0aD09MHx8IU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbCh0LmhwYXJhbXMscik/IiI6YjAodC5ocGFyYW1zW3JdKX1fc2Vzc2lvbkdyb3VwTWV0cmljKHQscil7aWYodD09bnVsbHx8T2JqZWN0LmtleXModCkubGVuZ3RoPT0wKXJldHVybiIiO2ZvcihsZXQgbj0wO248dC5tZXRyaWNWYWx1ZXMubGVuZ3RoOysrbil7bGV0IGk9dC5tZXRyaWNWYWx1ZXNbbl07aWYoaS5uYW1lLmdyb3VwPT09ci5ncm91cCYmaS5uYW1lLnRhZz09ci50YWcpcmV0dXJuIGIwKGkudmFsdWUpfXJldHVybiIifV9yb3dOdW1iZXIodCl7cmV0dXJuIHQrMX19O0hkLnRlbXBsYXRlPVFgCiAgICA8dmFhZGluLWdyaWQKICAgICAgY2xhc3M9InNlc3Npb24tZ3JvdXAtdGFibGUiCiAgICAgIGlkPSJzZXNzaW9uR3JvdXBzVGFibGUiCiAgICAgIGNvbHVtbi1yZW9yZGVyaW5nLWFsbG93ZWQ9IiIKICAgICAgaXRlbXM9Iltbc2Vzc2lvbkdyb3Vwc11dIgogICAgPgogICAgICA8dmFhZGluLWdyaWQtY29sdW1uIGZsZXgtZ3Jvdz0iMCIgd2lkdGg9IjEwZW0iIHJlc2l6YWJsZT0iIj4KICAgICAgICA8dGVtcGxhdGUgY2xhc3M9ImhlYWRlciI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJ0YWJsZS1oZWFkZXIgdGFibGUtY2VsbCI+VHJpYWwgSUQ8L2Rpdj4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDx0ZW1wbGF0ZT4KICAgICAgICAgIDxkaXYgY2xhc3M9InRhYmxlLWNlbGwiPltbaXRlbS5uYW1lXV08L2Rpdj4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICA8L3ZhYWRpbi1ncmlkLWNvbHVtbj4KICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW2VuYWJsZVNob3dNZXRyaWNzXV0iPgogICAgICAgIDx2YWFkaW4tZ3JpZC1jb2x1bW4gZmxleC1ncm93PSIwIiBhdXRvV2lkdGg9IiIgcmVzaXphYmxlPSIiPgogICAgICAgICAgPHRlbXBsYXRlIGNsYXNzPSJoZWFkZXIiPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJ0YWJsZS1oZWFkZXIgdGFibGUtY2VsbCI+U2hvdyBNZXRyaWNzPC9kaXY+CiAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgPHRlbXBsYXRlPgogICAgICAgICAgICA8cGFwZXItY2hlY2tib3ggY2xhc3M9InRhYmxlLWNlbGwiIGNoZWNrZWQ9Int7ZGV0YWlsc09wZW5lZH19Ij4KICAgICAgICAgICAgPC9wYXBlci1jaGVja2JveD4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPC92YWFkaW4tZ3JpZC1jb2x1bW4+CiAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDx0ZW1wbGF0ZQogICAgICAgIGlzPSJkb20tcmVwZWF0IgogICAgICAgIGl0ZW1zPSJbW3Zpc2libGVTY2hlbWEuaHBhcmFtSW5mb3NdXSIKICAgICAgICBhcz0iaHBhcmFtSW5mbyIKICAgICAgICBpbmRleC1hcz0iaHBhcmFtSW5kZXgiCiAgICAgID4KICAgICAgICA8dmFhZGluLWdyaWQtY29sdW1uIGZsZXgtZ3Jvdz0iMiIgd2lkdGg9IjEwZW0iIHJlc2l6YWJsZT0iIj4KICAgICAgICAgIDx0ZW1wbGF0ZSBjbGFzcz0iaGVhZGVyIj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0idGFibGUtaGVhZGVyIHRhYmxlLWNlbGwiPgogICAgICAgICAgICAgIFtbX2hwYXJhbU5hbWUoaHBhcmFtSW5mbyldXQogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9InRhYmxlLWNlbGwiPgogICAgICAgICAgICAgIFtbX3Nlc3Npb25Hcm91cEhQYXJhbShpdGVtLCBocGFyYW1JbmZvLm5hbWUpXV0KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDwvdmFhZGluLWdyaWQtY29sdW1uPgogICAgICA8L3RlbXBsYXRlPgogICAgICA8dGVtcGxhdGUKICAgICAgICBpcz0iZG9tLXJlcGVhdCIKICAgICAgICBpdGVtcz0ie3t2aXNpYmxlU2NoZW1hLm1ldHJpY0luZm9zfX0iCiAgICAgICAgYXM9Im1ldHJpY0luZm8iCiAgICAgICAgaW5kZXgtYXM9Im1ldHJpY0luZGV4IgogICAgICA+CiAgICAgICAgPHZhYWRpbi1ncmlkLWNvbHVtbiBmbGV4LWdyb3c9IjIiIHdpZHRoPSIxMGVtIiByZXNpemFibGU9IiI+CiAgICAgICAgICA8dGVtcGxhdGUgY2xhc3M9ImhlYWRlciI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9InRhYmxlLWhlYWRlciB0YWJsZS1jZWxsIj4KICAgICAgICAgICAgICBbW19tZXRyaWNOYW1lKG1ldHJpY0luZm8pXV0KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgPHRlbXBsYXRlPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJ0YWJsZS1jZWxsIj4KICAgICAgICAgICAgICBbW19zZXNzaW9uR3JvdXBNZXRyaWMoaXRlbSwgbWV0cmljSW5mby5uYW1lKV1dCiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L3ZhYWRpbi1ncmlkLWNvbHVtbj4KICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPHRlbXBsYXRlIGNsYXNzPSJyb3ctZGV0YWlscyI+CiAgICAgICAgPHRmLWhwYXJhbXMtc2Vzc2lvbi1ncm91cC1kZXRhaWxzCiAgICAgICAgICBiYWNrZW5kPSJbW2JhY2tlbmRdXSIKICAgICAgICAgIGV4cGVyaW1lbnQtbmFtZT0iW1tleHBlcmltZW50TmFtZV1dIgogICAgICAgICAgc2Vzc2lvbi1ncm91cD0iW1tpdGVtXV0iCiAgICAgICAgICB2aXNpYmxlLXNjaGVtYT0iW1t2aXNpYmxlU2NoZW1hXV0iCiAgICAgICAgICBjbGFzcz0ic2Vzc2lvbi1ncm91cC1kZXRhaWxzIgogICAgICAgID4KICAgICAgICA8L3RmLWhwYXJhbXMtc2Vzc2lvbi1ncm91cC1kZXRhaWxzPgogICAgICA8L3RlbXBsYXRlPgogICAgPC92YWFkaW4tZ3JpZD4KCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBpbmxpbmU7CiAgICAgIH0KCiAgICAgIDpob3N0KC5kYXJrLW1vZGUpIHsKICAgICAgICAtLWx1bW8tYmFzZS1jb2xvcjogIzMwMzAzMDsKICAgICAgICAtLWx1bW8tYm9keS10ZXh0LWNvbG9yOiAjZmZmOwogICAgICB9CgogICAgICA6aG9zdCguZGFyay1tb2RlKSB2YWFkaW4tZ3JpZCB7CiAgICAgICAgLS1fbHVtby1ncmlkLXNlY29uZGFyeS1ib3JkZXItY29sb3I6ICM1MDUwNTA7CiAgICAgIH0KCiAgICAgIC50YWJsZS1jZWxsIHsKICAgICAgICB3aGl0ZS1zcGFjZTogbm93cmFwOwogICAgICAgIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzOwogICAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgIH0KICAgICAgLnRhYmxlLWhlYWRlciB7CiAgICAgICAgLyogbGluZS1icmVhayBvdmVyZmxvd2luZyBjb2x1bW4gaGVhZGVycyAqLwogICAgICAgIHdoaXRlLXNwYWNlOiBub3JtYWw7CiAgICAgICAgb3ZlcmZsb3ctd3JhcDogYnJlYWstd29yZDsKICAgICAgfQogICAgICAuc2Vzc2lvbi1ncm91cC10YWJsZSB7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICB9CiAgICAgIC5zZXNzaW9uLWdyb3VwLWRldGFpbHMgewogICAgICAgIGhlaWdodDogMzYwcHg7CiAgICAgICAgb3ZlcmZsb3cteTogYXV0bzsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEhkLnByb3RvdHlwZSwidmlzaWJsZVNjaGVtYSIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sSGQucHJvdG90eXBlLCJzZXNzaW9uR3JvdXBzIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSGQucHJvdG90eXBlLCJlbmFibGVTaG93TWV0cmljcyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxIZC5wcm90b3R5cGUsImJhY2tlbmQiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sSGQucHJvdG90eXBlLCJleHBlcmltZW50TmFtZSIsdm9pZCAwKTtFKFtCdCgidmlzaWJsZVNjaGVtYS4qIiwic2Vzc2lvbkdyb3Vwcy4qIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxIZC5wcm90b3R5cGUsIl92aXNpYmxlU2NoZW1hT3JTZXNzaW9uR3JvdXBzQ2hhbmdlZCIsbnVsbCk7SGQ9RShbeXQoInRmLWhwYXJhbXMtdGFibGUtdmlldyIpXSxIZCk7dmFyIFRQPWNsYXNzIGV4dGVuZHMgbXR7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuc2Vzc2lvbkdyb3VwPW51bGwsdGhpcy52aXNpYmxlU2NoZW1hPW51bGx9X3Byb3BlcnRpZXNBcmVQb3B1bGF0ZWQodCxyKXtyZXR1cm4gdCE9bnVsbCYmciE9PXZvaWQgMCYmciE9PW51bGx9X3NpbmdsZXRvblNlc3Npb25Hcm91cHModCl7cmV0dXJuIHQ9PW51bGw/W106W3RdfX07VFAudGVtcGxhdGU9UWAKICAgIDwhLS0gSWYgc2Vzc2lvbkdyb3VwIG9yIHZpc2libGVTY2hlbWEgYXJlIG5vdCBwb3B1bGF0ZWQsIGRvIG5vdCBkaXNwbGF5CiAgICAgICAgIGFueXRoaW5nLgogICAgICAtLT4KICAgIDx0ZW1wbGF0ZQogICAgICBpcz0iZG9tLWlmIgogICAgICBpZj0iW1tfcHJvcGVydGllc0FyZVBvcHVsYXRlZCh2aXNpYmxlU2NoZW1hLCBzZXNzaW9uR3JvdXApXV0iCiAgICA+CiAgICAgIDwhLS0gRGlzcGxheSBvbmUgcm93IHdpdGhvdXQgYSAic2hvdy1tZXRyaWNzIiBjb2x1bW4gLS0+CiAgICAgIDx0Zi1ocGFyYW1zLXRhYmxlLXZpZXcKICAgICAgICB2aXNpYmxlLXNjaGVtYT0iW1t2aXNpYmxlU2NoZW1hXV0iCiAgICAgICAgc2Vzc2lvbi1ncm91cHM9IltbX3NpbmdsZXRvblNlc3Npb25Hcm91cHMoc2Vzc2lvbkdyb3VwKV1dIgogICAgICA+CiAgICAgIDwvdGYtaHBhcmFtcy10YWJsZS12aWV3PgogICAgPC90ZW1wbGF0ZT4KICAgIDx0ZW1wbGF0ZQogICAgICBpcz0iZG9tLWlmIgogICAgICBpZj0iW1shX3Byb3BlcnRpZXNBcmVQb3B1bGF0ZWQodmlzaWJsZVNjaGVtYSwgc2Vzc2lvbkdyb3VwKV1dIgogICAgPgogICAgICA8ZGl2PkNsaWNrIG9yIGhvdmVyIG92ZXIgYSBzZXNzaW9uIGdyb3VwIHRvIGRpc3BsYXkgaXRzIHZhbHVlcyBoZXJlLjwvZGl2PgogICAgPC90ZW1wbGF0ZT4KCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFRQLnByb3RvdHlwZSwic2Vzc2lvbkdyb3VwIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFRQLnByb3RvdHlwZSwidmlzaWJsZVNjaGVtYSIsdm9pZCAwKTtUUD1FKFt5dCgidGYtaHBhcmFtcy1zZXNzaW9uLWdyb3VwLXZhbHVlcyIpXSxUUCk7dmFyIENQPUVlKE9lKCksMSk7ZnVuY3Rpb24gbHVlKGUsdCxyLG4pe2lmKHQubGVuZ3RoPDIpcmV0dXJuIGNvbnNvbGUuZXJyb3IoIkxlc3MgdGhhbiB0d28gYXhlcyBpbiBwYXJhbGxlbCBjb29yZGluYXRlcyBwbG90LiIpLG51bGw7bGV0IGk9clswXSxvPXJbMV07aWYoaTw9dFswXXx8aT49dFt0Lmxlbmd0aC0xXSlyZXR1cm4gbnVsbDtsZXQgYT1DUC5zb3J0ZWRJbmRleCh0LGkpO2NvbnNvbGUuYXNzZXJ0KGE+MCksY29uc29sZS5hc3NlcnQoYTx0Lmxlbmd0aCk7bGV0IHM9YS0xO2Z1bmN0aW9uIGwoaCxmLHAsZCl7bGV0IGc9aC1wLF89Zi1kLHk9aS1wLHg9by1kLGI9KGcqeStfKngpLyhnKmcrXypfKTtpZihiPD0wKXJldHVybiBwMyh5LHgpO2lmKGI+PTEpe2xldCBTPWgtaSxDPWYtbztyZXR1cm4gcDMoUyxDKX1yZXR1cm4gcDMoeS1iKmcseC1iKl8pfWxldCBjPW51bGwsdT1udWxsO3JldHVybiBlLmZvckVhY2goaD0+e2xldCBmPWwoaC5jb250cm9sUG9pbnRzW3NdWzBdLGguY29udHJvbFBvaW50c1tzXVsxXSxoLmNvbnRyb2xQb2ludHNbYV1bMF0saC5jb250cm9sUG9pbnRzW2FdWzFdKTtmPm58fChjPT09bnVsbHx8ZjxjKSYmKGM9Zix1PWgpfSksdX1mdW5jdGlvbiBjdWUoZSx0LHIpe3JldHVybiBlLmRvbWFpbigpLmZpbHRlcihuPT57bGV0IGk9ZShuKTtyZXR1cm4gdDw9aSYmaTw9cn0pfWZ1bmN0aW9uIHV1ZShlLHQscil7bGV0IG49ZS5yYW5nZSgpLGk9bi5maWx0ZXIobz0+dDw9byYmbzw9cikubWFwKG89PntsZXQgYT1lLmludmVydEV4dGVudChvKTtyZXR1cm4gbz09PW5bbi5sZW5ndGgtMV0/W2FbMF0sYVsxXSsxXTphfSk7cmV0dXJuIGkubGVuZ3RoPT0wP1swLDBdOmFhKEltKGkpKX1mdW5jdGlvbiBodWUoZSx0LHIpe3JldHVybltlLmludmVydCh0KSxlLmludmVydChyKV0uc29ydCgobixpKT0+bi1pKX1mdW5jdGlvbiBCY3QoZSx0LHIpe2Z1bmN0aW9uIG4oKXtpZihlLmxlbmd0aD09PTApcmV0dXJuWzEsMl07bGV0W2ksb109YWEoZSk7cmV0dXJuIGkhPT1vP1tpLG9dOmk+MD9baSouNSxpKjEuNV06aTwwP1tpKjEuNSxpKi41XTpbLTEsMV19aWYocj09PSJMSU5FQVIiKXJldHVybiB6bigpLmRvbWFpbihuKCkpLnJhbmdlKFt0LDBdKTtpZihyPT09IkxPRyIpe2xldCBpPW4oKTtyZXR1cm4gaVswXTw9MCYmaVsxXT49MD9CY3QoZSx0LCJMSU5FQVIiKTpjYygpLmRvbWFpbihpKS5yYW5nZShbdCwwXSl9ZWxzZSBpZihyPT09IlFVQU5USUxFIil7bGV0IG89SXIoMjApLm1hcChhPT50LWEqdC8xOSk7cmV0dXJuIGUubGVuZ3RoPT09MCYmKGU9WzFdKSxlZygpLmRvbWFpbihDUC51bmlxKGUpKS5yYW5nZShvKX1lbHNle2lmKHI9PT0iTk9OX05VTUVSSUMiKXJldHVybiB0ZygpLmRvbWFpbihDUC51bmlxKGUuc29ydCgpKSkucmFuZ2UoW3QsMF0pLnBhZGRpbmcoLjEpO3Rocm93IFJhbmdlRXJyb3IoIlVua25vd24gc2NhbGU6ICIrcil9fXZhciB1djsoZnVuY3Rpb24oZSl7ZS5MSU5FQVI9IkxJTkVBUiIsZS5MT0c9IkxPRyIsZS5RVUFOVElMRT0iUVVBTlRJTEUiLGUuTk9OX05VTUVSSUM9Ik5PTl9OVU1FUklDIn0pKHV2fHwodXY9e30pKTt2YXIgQVA9Y2xhc3N7aXNQYXNzaW5nKHQpe3JldHVybiEwfX0sclY9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4saSl7dGhpcy5fbG93ZXI9dCx0aGlzLl91cHBlcj1yLHRoaXMuX2xvd2VyT3Blbj1uLHRoaXMuX3VwcGVyT3Blbj1pfWlzUGFzc2luZyh0KXtsZXQgcj10O3JldHVybiB0aGlzLl9iZWZvcmUodGhpcy5fbG93ZXIsciwhdGhpcy5fbG93ZXJPcGVuKSYmdGhpcy5fYmVmb3JlKHIsdGhpcy5fdXBwZXIsIXRoaXMuX3VwcGVyT3Blbil9X2JlZm9yZSh0LHIsbil7cmV0dXJuIG4/dDw9cjp0PHJ9fSxWY3Q9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5fZG9tYWluU2V0PXR9aXNQYXNzaW5nKHQpe3JldHVybiB0aGlzLl9kb21haW5TZXQuZmluZEluZGV4KHI9PnI9PT10KSE9PS0xfX0sVWN0PWNsYXNze2NvbnN0cnVjdG9yKHQscixuLGkpe3RoaXMuX3N2Z1Byb3BzPXQsdGhpcy5fc2NoZW1hPXIsdGhpcy5faW50ZXJhY3Rpb25NYW5hZ2VyPW4sdGhpcy5fY29sSW5kZXg9aSx0aGlzLl9pc0Rpc3BsYXllZD0hMSx0aGlzLl95U2NhbGU9bnVsbCx0aGlzLl9zY2FsZVR5cGU9bnVsbCx0aGlzLnNldEJydXNoU2VsZWN0aW9uKG51bGwpfWNvbEluZGV4KCl7cmV0dXJuIHRoaXMuX2NvbEluZGV4fXlTY2FsZSgpe3JldHVybiB0aGlzLl95U2NhbGV9c2NhbGVUeXBlKCl7cmV0dXJuIHRoaXMuX3NjYWxlVHlwZX1icnVzaFNlbGVjdGlvbigpe3JldHVybiB0aGlzLl9icnVzaFNlbGVjdGlvbn1pc0Rpc3BsYXllZCgpe3JldHVybiB0aGlzLl9pc0Rpc3BsYXllZH1zZXRCcnVzaFNlbGVjdGlvbih0KXt0aGlzLl9icnVzaFNlbGVjdGlvbj10LHRoaXMuX2JydXNoRmlsdGVyPXRoaXMuX2J1aWxkQnJ1c2hGaWx0ZXIodGhpcy5icnVzaFNlbGVjdGlvbigpLHRoaXMuc2NhbGVUeXBlKCksdGhpcy55U2NhbGUoKSl9c2V0RG9tYWluQW5kU2NhbGUodCxyKXt0aGlzLl9zY2FsZVR5cGU9cix0aGlzLl95U2NhbGU9QmN0KHQuc2xpY2UoKSx0aGlzLl9zdmdQcm9wcy5oZWlnaHQsdGhpcy5zY2FsZVR5cGUoKSksdGhpcy5fYnJ1c2hGaWx0ZXI9dGhpcy5fYnVpbGRCcnVzaEZpbHRlcih0aGlzLmJydXNoU2VsZWN0aW9uKCksdGhpcy5zY2FsZVR5cGUoKSx0aGlzLnlTY2FsZSgpKX1icnVzaEZpbHRlcigpe3JldHVybiB0aGlzLl9icnVzaEZpbHRlcn11cGRhdGVET00odCl7bGV0IHI9bGIodGhpcy55U2NhbGUoKSk7dGhpcy5zY2FsZVR5cGUoKT09PXV2LlFVQU5USUxFJiYocj1yLnRpY2tWYWx1ZXModGhpcy55U2NhbGUoKS5xdWFudGlsZXMoKSkudGlja0Zvcm1hdCh4bigiLS42ZyIpKSk7bGV0IG49SHQodCk7bi5zZWxlY3RBbGwoImciKS5yZW1vdmUoKSxuLmFwcGVuZCgiZyIpLmNsYXNzZWQoImF4aXMiLCEwKS5jYWxsKHIpLmFwcGVuZCgidGV4dCIpLmNsYXNzZWQoImF4aXMtdGl0bGUiLCEwKS5zdHlsZSgiY3Vyc29yIiwibW92ZSIpLnN0eWxlKCJ0ZXh0LWFuY2hvciIsIm1pZGRsZSIpLmF0dHIoInkiLC05KS50ZXh0KGE9PnVjdCh0aGlzLl9zY2hlbWEsYSkpLG4uY2FsbChwYigpLm9uKCJzdGFydCIsKCk9Pnt0LnNldEF0dHJpYnV0ZSgiaXMtZHJhZ2dpbmciLCIiKSx0aGlzLl9pbnRlcmFjdGlvbk1hbmFnZXIub25EcmFnU3RhcnQodGhpcy5jb2xJbmRleCgpKX0pLm9uKCJkcmFnIiwoKT0+dGhpcy5faW50ZXJhY3Rpb25NYW5hZ2VyLm9uRHJhZyhxdC54KSkub24oImVuZCIsKCk9Pnt0aGlzLl9pbnRlcmFjdGlvbk1hbmFnZXIub25EcmFnRW5kKCksdC5yZW1vdmVBdHRyaWJ1dGUoImlzLWRyYWdnaW5nIil9KSk7bGV0IGk9VUwoKS5leHRlbnQoW1stOCwwXSxbOCx0aGlzLl9zdmdQcm9wcy5oZWlnaHQrMV1dKS5vbigic3RhcnQiLCgpPT57IUhjdChxdCl8fCh0LnNldEF0dHJpYnV0ZSgiaXMtYnJ1c2hpbmciLCIiKSx0aGlzLl9pbnRlcmFjdGlvbk1hbmFnZXIub25CcnVzaENoYW5nZWQodGhpcy5jb2xJbmRleCgpLHF0LnNlbGVjdGlvbikpfSkub24oImJydXNoIiwoKT0+eyFIY3QocXQpfHx0aGlzLl9pbnRlcmFjdGlvbk1hbmFnZXIub25CcnVzaENoYW5nZWQodGhpcy5jb2xJbmRleCgpLHF0LnNlbGVjdGlvbil9KS5vbigiZW5kIiwoKT0+eyFIY3QocXQpfHwodGhpcy5faW50ZXJhY3Rpb25NYW5hZ2VyLm9uQnJ1c2hDaGFuZ2VkKHRoaXMuY29sSW5kZXgoKSxxdC5zZWxlY3Rpb24pLHQucmVtb3ZlQXR0cmlidXRlKCJpcy1icnVzaGluZyIpKX0pLG89SHQodCkuYXBwZW5kKCJnIikuY2xhc3NlZCgiYnJ1c2giLCEwKTtvLmNhbGwoaSksaS5tb3ZlKG8sdGhpcy5icnVzaFNlbGVjdGlvbigpKX1zZXREaXNwbGF5ZWQodCl7dGhpcy5faXNEaXNwbGF5ZWQ9dH1fYnVpbGRCcnVzaEZpbHRlcih0LHIsbil7aWYodD09PW51bGwpcmV0dXJuIG5ldyBBUDtpZihyPT09bnVsbClyZXR1cm4gY29uc29sZS5lcnJvcigiU2NhbGUgdHlwZSBpcyBudWxsLCBidXQgYnJ1c2hTZWxlY3Rpb24gaXNuJ3Q6ICIsdCksbmV3IEFQO3N3aXRjaChyKXtjYXNlIHV2LkxJTkVBUjpjYXNlIHV2LkxPRzp7bGV0W2ksb109aHVlKG4sdFswXSx0WzFdKTtyZXR1cm4gbmV3IHJWKGksbywhMSwhMSl9Y2FzZSB1di5RVUFOVElMRTp7bGV0W2ksb109dXVlKG4sdFswXSx0WzFdKTtyZXR1cm4gbmV3IHJWKGksbywhMSwhMCl9Y2FzZSB1di5OT05fTlVNRVJJQzpyZXR1cm4gbmV3IFZjdChjdWUobix0WzBdLHRbMV0pKX1yZXR1cm4gY29uc29sZS5lcnJvcigiVW5rbm93biBzY2FsZSB0eXBlOiAiLHIpLG5ldyBBUH19LG5WPWNsYXNze2NvbnN0cnVjdG9yKHQscixuKXt0aGlzLl9zdmdQcm9wcz10LHRoaXMuX3NjaGVtYT1yLHRoaXMuX2F4ZXM9dGhpcy5fY3JlYXRlQXhlcyhuKSx0aGlzLl9zdGF0aW9uYXJ5QXhlc1Bvc2l0aW9ucz10ZygpLnJhbmdlKFsxLHRoaXMuX3N2Z1Byb3BzLndpZHRoLTFdKS5wYWRkaW5nKC41KSx0aGlzLl9kcmFnZ2VkQXhpcz1udWxsLHRoaXMuX3N2Z1Byb3BzLnN2Z0cuc2VsZWN0QWxsKCJnLmF4aXMtcGFyZW50IikucmVtb3ZlKCksdGhpcy5fcGFyZW50c1NlbD10aGlzLl9zdmdQcm9wcy5zdmdHLnNlbGVjdEFsbCgiLmF4aXMtcGFyZW50Iil9dXBkYXRlQXhlcyh0LHIpe2NvbnNvbGUuYXNzZXJ0KCF0aGlzLmlzQXhpc0RyYWdnaW5nKCkpO2xldCBuPW5ldyBTZXQ7dC5jb2x1bW5zLmZvckVhY2gobz0+e2xldCBhPW8uYWJzb2x1dGVJbmRleCxzPXRoaXMuX2F4ZXNbYV07cy5zZXREaXNwbGF5ZWQoITApO2xldCBsPXIubWFwKGM9PngwKHRoaXMuX3NjaGVtYSxjLGEpKTtzLnNldERvbWFpbkFuZFNjYWxlKGwsby5zY2FsZSksbi5hZGQoYSl9KSx0aGlzLl9heGVzLmZvckVhY2gobz0+e24uaGFzKG8uY29sSW5kZXgoKSl8fG8uc2V0RGlzcGxheWVkKCExKX0pLHRoaXMuX3VwZGF0ZVN0YXRpb25hcnlBeGVzUG9zaXRpb25zKG4pLHRoaXMuX3BhcmVudHNTZWw9dGhpcy5fcGFyZW50c1NlbC5kYXRhKEFycmF5LmZyb20obiksbz0+byksdGhpcy5fcGFyZW50c1NlbC5leGl0KCkucmVtb3ZlKCksdGhpcy5fcGFyZW50c1NlbD10aGlzLl9wYXJlbnRzU2VsLmVudGVyKCkuYXBwZW5kKCJnIikuY2xhc3NlZCgiYXhpcy1wYXJlbnQiLCEwKS5tZXJnZSh0aGlzLl9wYXJlbnRzU2VsKTtsZXQgaT10aGlzO3RoaXMuX3BhcmVudHNTZWwuY2FsbChvPT50aGlzLl91cGRhdGVBeGVzUG9zaXRpb25zSW5ET00obykpLmVhY2goZnVuY3Rpb24obyl7aS5fYXhlc1tvXS51cGRhdGVET00odGhpcyl9KX1tYXBWaXNpYmxlQXhlcyh0KXtyZXR1cm4gdGhpcy5fc3RhdGlvbmFyeUF4ZXNQb3NpdGlvbnMuZG9tYWluKCkubWFwKHI9PnQodGhpcy5nZXRBeGlzUG9zaXRpb24ociksdGhpcy5fYXhlc1tyXSkpfWFsbFZpc2libGVBeGVzU2F0aXNmeSh0KXtyZXR1cm4gdGhpcy5fc3RhdGlvbmFyeUF4ZXNQb3NpdGlvbnMuZG9tYWluKCkuZXZlcnkocj0+dCh0aGlzLmdldEF4aXNQb3NpdGlvbihyKSx0aGlzLl9heGVzW3JdKSl9Z2V0QXhpc0ZvckNvbEluZGV4KHQpe3JldHVybiB0aGlzLl9heGVzW3RdfWRyYWdTdGFydCh0KXtjb25zb2xlLmFzc2VydCghdGhpcy5pc0F4aXNEcmFnZ2luZygpKSxjb25zb2xlLmFzc2VydCh0aGlzLl9heGVzW3RdLmlzRGlzcGxheWVkKCkpLHRoaXMuX2RyYWdnZWRBeGlzPXRoaXMuX2F4ZXNbdF0sdGhpcy5fZHJhZ2dlZEF4aXNQb3NpdGlvbj10aGlzLl9zdGF0aW9uYXJ5QXhlc1Bvc2l0aW9ucyh0KX1kcmFnKHQpe3Q9TWF0aC5taW4oTWF0aC5tYXgodCwwKSx0aGlzLl9zdmdQcm9wcy53aWR0aCksdGhpcy5fZHJhZ2dlZEF4aXNQb3NpdGlvbj10O2xldCByPXRoaXMuX3N0YXRpb25hcnlBeGVzUG9zaXRpb25zLmRvbWFpbigpO3Iuc29ydCgobixpKT0+dGhpcy5nZXRBeGlzUG9zaXRpb24obiktdGhpcy5nZXRBeGlzUG9zaXRpb24oaSkpLHRoaXMuX3N0YXRpb25hcnlBeGVzUG9zaXRpb25zLmRvbWFpbihyKSx0aGlzLl91cGRhdGVBeGVzUG9zaXRpb25zSW5ET00odGhpcy5fcGFyZW50c1NlbCl9ZHJhZ0VuZCh0KXtjb25zb2xlLmFzc2VydCh0aGlzLmlzQXhpc0RyYWdnaW5nKCkpLHRoaXMuX2RyYWdnZWRBeGlzUG9zaXRpb249bnVsbCx0aGlzLl9kcmFnZ2VkQXhpcz1udWxsLHRoaXMuX3VwZGF0ZUF4ZXNQb3NpdGlvbnNJbkRPTSh0aGlzLl9wYXJlbnRzU2VsLnRyYW5zaXRpb24oKS5kdXJhdGlvbih0KSl9aXNBeGlzRHJhZ2dpbmcoKXtyZXR1cm4gdGhpcy5fZHJhZ2dlZEF4aXMhPT1udWxsfWdldEF4aXNQb3NpdGlvbih0KXtyZXR1cm4gdGhpcy5fZHJhZ2dlZEF4aXMhPT1udWxsJiZ0aGlzLl9kcmFnZ2VkQXhpcy5jb2xJbmRleCgpPT09dD90aGlzLl9kcmFnZ2VkQXhpc1Bvc2l0aW9uOnRoaXMuX3N0YXRpb25hcnlBeGVzUG9zaXRpb25zKHQpfV91cGRhdGVTdGF0aW9uYXJ5QXhlc1Bvc2l0aW9ucyh0KXtsZXQgcj10aGlzLl9zdGF0aW9uYXJ5QXhlc1Bvc2l0aW9ucy5kb21haW4oKS5maWx0ZXIoaT0+dC5oYXMoaSkpLG49QXJyYXkuZnJvbShuZXcgU2V0KFsuLi5yLC4uLkFycmF5LmZyb20odCldKSk7dGhpcy5fc3RhdGlvbmFyeUF4ZXNQb3NpdGlvbnMuZG9tYWluKG4pfV91cGRhdGVBeGVzUG9zaXRpb25zSW5ET00odCl7dC5hdHRyKCJ0cmFuc2Zvcm0iLHI9Pl9QKHRoaXMuZ2V0QXhpc1Bvc2l0aW9uKHIpKSl9X2NyZWF0ZUF4ZXModCl7cmV0dXJuIElyKGhjdCh0aGlzLl9zY2hlbWEpKS5tYXAocj0+bmV3IFVjdCh0aGlzLl9zdmdQcm9wcyx0aGlzLl9zY2hlbWEsdCxyKSl9fTtmdW5jdGlvbiBIY3QoZSl7cmV0dXJuIGUuc291cmNlRXZlbnQhPT1udWxsfXZhciBqZjsoZnVuY3Rpb24oZSl7ZVtlLkZPUkVHUk9VTkQ9MF09IkZPUkVHUk9VTkQiLGVbZS5CQUNLR1JPVU5EPTFdPSJCQUNLR1JPVU5EIn0pKGpmfHwoamY9e30pKTt2YXIgdGg9Y2xhc3N7Y29uc3RydWN0b3IodCl7dD09PXZvaWQgMCYmKHQ9RXAobnVsbCkpLGNvbnNvbGUuYXNzZXJ0KHQuc2l6ZSgpPD0xKSx0aGlzLl9zZXNzaW9uR3JvdXBTZWw9dH1zZXNzaW9uR3JvdXAoKXtyZXR1cm4gdGhpcy5fc2Vzc2lvbkdyb3VwU2VsLnNpemUoKT09PTE/dGhpcy5fc2Vzc2lvbkdyb3VwU2VsLmRhdHVtKCk6bnVsbH1pc051bGwoKXtyZXR1cm4gdGhpcy5zZXNzaW9uR3JvdXAoKT09PW51bGx9c2VsZWN0aW9uKCl7cmV0dXJuIHRoaXMuX3Nlc3Npb25Hcm91cFNlbH1lcXVhbHNUbyh0KXt2YXIgcixuO3JldHVybiB0aGlzLmlzTnVsbCgpP3QuaXNOdWxsKCk6dC5pc051bGwoKT8hMTooKHI9dC5zZXNzaW9uR3JvdXAoKSk9PW51bGw/dm9pZCAwOnIubmFtZSk9PSgobj10aGlzLnNlc3Npb25Hcm91cCgpKT09bnVsbD92b2lkIDA6bi5uYW1lKX19LGlWPWNsYXNze2NvbnN0cnVjdG9yKHQscixuKXt0aGlzLl9zdmdQcm9wcz10LHRoaXMuX3NjaGVtYT1yLHRoaXMuX2F4ZXNDb2xsZWN0aW9uPW4sdGhpcy5fc2Vzc2lvbkdyb3Vwcz1bXSx0aGlzLl9zdmdQcm9wcy5zdmdHLnNlbGVjdEFsbCgiZy5iYWNrZ3JvdW5kIikucmVtb3ZlKCksdGhpcy5fc3ZnUHJvcHMuc3ZnRy5zZWxlY3RBbGwoImcuZm9yZWdyb3VuZCIpLnJlbW92ZSgpLHRoaXMuX2JnUGF0aHNTZWw9dGhpcy5fc3ZnUHJvcHMuc3ZnRy5hcHBlbmQoImciKS5jbGFzc2VkKCJiYWNrZ3JvdW5kIiwhMCkuc2VsZWN0QWxsKCJwYXRoIiksdGhpcy5fZmdQYXRoc1NlbD10aGlzLl9zdmdQcm9wcy5zdmdHLmFwcGVuZCgiZyIpLmNsYXNzZWQoImZvcmVncm91bmQiLCEwKS5zZWxlY3RBbGwoInBhdGgiKSx0aGlzLl91cGRhdGVWaXNpYmxlRmdQYXRoc1NlbCgpLHRoaXMuX3BlYWtlZFNlc3Npb25Hcm91cEhhbmRsZT1uZXcgdGgsdGhpcy5fc2VsZWN0ZWRTZXNzaW9uR3JvdXBIYW5kbGU9bmV3IHRoLHRoaXMuX2QzbGluZT12dSgpLmN1cnZlKFloKX1nZXRTZXNzaW9uR3JvdXBIYW5kbGUodCl7cmV0dXJuIHQ9PW51bGw/bmV3IHRoOm5ldyB0aCh0aGlzLl9mZ1BhdGhzU2VsLmZpbHRlcihyPT5yLm5hbWU9PT10Lm5hbWUpKX1oaWRlQmFja2dyb3VuZExpbmVzKCl7dGhpcy5fYmdQYXRoc1NlbC5hdHRyKCJ2aXNpYmlsaXR5IiwiaGlkZGVuIil9c2hvd0JhY2tncm91bmRMaW5lcygpe3RoaXMuX2JnUGF0aHNTZWwuYXR0cigidmlzaWJpbGl0eSIsbnVsbCl9cGVha2VkU2Vzc2lvbkdyb3VwSGFuZGxlKCl7cmV0dXJuIHRoaXMuX3BlYWtlZFNlc3Npb25Hcm91cEhhbmRsZX1zZWxlY3RlZFNlc3Npb25Hcm91cEhhbmRsZSgpe3JldHVybiB0aGlzLl9zZWxlY3RlZFNlc3Npb25Hcm91cEhhbmRsZX1yZWNvbXB1dGVDb250cm9sUG9pbnRzKHQscj0wKXsodD09PWpmLkZPUkVHUk9VTkQ/dGhpcy5fZmdQYXRoc1NlbDp0aGlzLl9iZ1BhdGhzU2VsKS50cmFuc2l0aW9uKCkuZHVyYXRpb24ocikuYXR0cigiZCIsaT0+dGhpcy5fcGF0aERBdHRyaWJ1dGUoaSkpLHQ9PT1qZi5GT1JFR1JPVU5EJiZ3aW5kb3cuc2V0VGltZW91dCgoKT0+e2xldCBpPXRoaXM7dGhpcy5fZmdQYXRoc1NlbC5lYWNoKGZ1bmN0aW9uKG8pe2kuX3NldENvbnRyb2xQb2ludHNQcm9wZXJ0eSh0aGlzLG8pfSl9KX1yZWNvbXB1dGVGb3JlZ3JvdW5kTGluZXNWaXNpYmlsaXR5KCl7dGhpcy5fZmdQYXRoc1NlbC5jbGFzc2VkKCJpbnZpc2libGUtcGF0aCIsdD0+IXRoaXMuX2F4ZXNDb2xsZWN0aW9uLmFsbFZpc2libGVBeGVzU2F0aXNmeSgocixuKT0+bi5icnVzaEZpbHRlcigpLmlzUGFzc2luZyh4MCh0aGlzLl9zY2hlbWEsdCxuLmNvbEluZGV4KCkpKSkpLHRoaXMuX3VwZGF0ZVZpc2libGVGZ1BhdGhzU2VsKCl9c2V0Rm9yZWdyb3VuZExpbmVzQ29sb3IodCxyLG4pe2xldCBpPXRoaXMuX2NyZWF0ZUxpbmVDb2xvckZ1bmN0aW9uKHQscixuKTt0aGlzLl9mZ1BhdGhzU2VsLmF0dHIoInN0cm9rZSIsaSl9cmVkcmF3KHQscixuLGkpe2xldCBvPXRoaXMuX3BlYWtlZFNlc3Npb25Hcm91cEhhbmRsZS5zZXNzaW9uR3JvdXAoKSxhPXRoaXMuX3NlbGVjdGVkU2Vzc2lvbkdyb3VwSGFuZGxlLnNlc3Npb25Hcm91cCgpO3RoaXMuX3Nlc3Npb25Hcm91cHM9dCx0aGlzLl9mZ1BhdGhzU2VsPXRoaXMuX3JlY29tcHV0ZVBhdGhTZWxlY3Rpb24odGhpcy5fZmdQYXRoc1NlbCksdGhpcy5fYmdQYXRoc1NlbD10aGlzLl9yZWNvbXB1dGVQYXRoU2VsZWN0aW9uKHRoaXMuX2JnUGF0aHNTZWwpLHRoaXMuX3BlYWtlZFNlc3Npb25Hcm91cEhhbmRsZT10aGlzLmdldFNlc3Npb25Hcm91cEhhbmRsZShvKSx0aGlzLl9zZWxlY3RlZFNlc3Npb25Hcm91cEhhbmRsZT10aGlzLmdldFNlc3Npb25Hcm91cEhhbmRsZShhKSx0aGlzLnJlY29tcHV0ZUNvbnRyb2xQb2ludHMoamYuRk9SRUdST1VORCksdGhpcy5yZWNvbXB1dGVDb250cm9sUG9pbnRzKGpmLkJBQ0tHUk9VTkQpLHRoaXMucmVjb21wdXRlRm9yZWdyb3VuZExpbmVzVmlzaWJpbGl0eSgpLHRoaXMuc2V0Rm9yZWdyb3VuZExpbmVzQ29sb3IocixuLGkpfXVwZGF0ZVBlYWtlZFNlc3Npb25Hcm91cCh0KXt0aGlzLl9wZWFrZWRTZXNzaW9uR3JvdXBIYW5kbGUuc2VsZWN0aW9uKCkuY2xhc3NlZCgicGVha2VkLXBhdGgiLCExKSx0aGlzLl9wZWFrZWRTZXNzaW9uR3JvdXBIYW5kbGU9dCx0aGlzLl9wZWFrZWRTZXNzaW9uR3JvdXBIYW5kbGUuc2VsZWN0aW9uKCkuY2xhc3NlZCgicGVha2VkLXBhdGgiLCEwKX1jbGVhclBlYWtlZFNlc3Npb25Hcm91cCgpe3RoaXMudXBkYXRlUGVha2VkU2Vzc2lvbkdyb3VwKG5ldyB0aCl9dXBkYXRlU2VsZWN0ZWRTZXNzaW9uR3JvdXAodCl7dGhpcy5fc2VsZWN0ZWRTZXNzaW9uR3JvdXBIYW5kbGUuc2VsZWN0aW9uKCkuY2xhc3NlZCgic2VsZWN0ZWQtcGF0aCIsITEpLHRoaXMuX3NlbGVjdGVkU2Vzc2lvbkdyb3VwSGFuZGxlPXQsdGhpcy5fc2VsZWN0ZWRTZXNzaW9uR3JvdXBIYW5kbGUuc2VsZWN0aW9uKCkuY2xhc3NlZCgic2VsZWN0ZWQtcGF0aCIsITApfWZpbmRDbG9zZXN0U2Vzc2lvbkdyb3VwKHQscil7bGV0IG49dGhpcy5fYXhlc0NvbGxlY3Rpb24ubWFwVmlzaWJsZUF4ZXMoKG8sYSk9Pm8pLGk9bHVlKHRoaXMuX3Zpc2libGVGZ1BhdGhzU2VsLm5vZGVzKCksbixbdCxyXSwxMDApO3JldHVybiBpPT09bnVsbD9uZXcgdGg6bmV3IHRoKEh0KGkpKX1fY3JlYXRlTGluZUNvbG9yRnVuY3Rpb24odCxyLG4pe2lmKHQ9PT1udWxsKXJldHVybigpPT4icmVkIjtsZXQgaT16bigpLmRvbWFpbihmY3QodGhpcy5fc2NoZW1hLHRoaXMuX3Nlc3Npb25Hcm91cHMsdCkpLnJhbmdlKFtyLG5dKS5pbnRlcnBvbGF0ZShNXyk7cmV0dXJuIG89PmkoeDAodGhpcy5fc2NoZW1hLG8sdCkpfV9yZWNvbXB1dGVQYXRoU2VsZWN0aW9uKHQpe3JldHVybiB0PXQuZGF0YSh0aGlzLl9zZXNzaW9uR3JvdXBzLHI9PnIubmFtZSksdC5leGl0KCkucmVtb3ZlKCksdC5lbnRlcigpLmFwcGVuZCgicGF0aCIpLm1lcmdlKHQpfV9zZXRDb250cm9sUG9pbnRzUHJvcGVydHkodCxyKXt0LmNvbnRyb2xQb2ludHM9dGhpcy5fY29tcHV0ZUNvbnRyb2xQb2ludHMocil9X2NvbXB1dGVDb250cm9sUG9pbnRzKHQpe3JldHVybiB0aGlzLl9heGVzQ29sbGVjdGlvbi5tYXBWaXNpYmxlQXhlcygocixuKT0+W3Isbi55U2NhbGUoKSh4MCh0aGlzLl9zY2hlbWEsdCxuLmNvbEluZGV4KCkpKV0pfV9wYXRoREF0dHJpYnV0ZSh0KXtyZXR1cm4gdGhpcy5fZDNsaW5lKHRoaXMuX2NvbXB1dGVDb250cm9sUG9pbnRzKHQpKX1fdXBkYXRlVmlzaWJsZUZnUGF0aHNTZWwoKXt0aGlzLl92aXNpYmxlRmdQYXRoc1NlbD10aGlzLl9mZ1BhdGhzU2VsLmZpbHRlcigiOm5vdCguaW52aXNpYmxlLXBhdGgpIil9fTt2YXIgb1Y9Y2xhc3N7Y29uc3RydWN0b3IodCxyKXt0aGlzLnN2Zz1IdCh0KTtsZXQgbj17dG9wOjMwLHJpZ2h0OjEwLGJvdHRvbToxMCxsZWZ0OjEwfSxpPTEwMCxvPTIwMCxhPXIqaStuLmxlZnQrbi5yaWdodCxzPW8rbi50b3Arbi5ib3R0b207dGhpcy5zdmcuYXR0cigidmlld0JveCIsYDAgMCAke2F9ICR7c31gKSx0aGlzLnN2Zy5hdHRyKCJwcmVzZXJ2ZUFzcGVjdFJhdGlvIiwieE1pZFlNaWQiKSx0aGlzLnN2Zy5zdHlsZSgibWluLXdpZHRoIixhKyJweCIpLHRoaXMuc3ZnLnN0eWxlKCJtaW4taGVpZ2h0IixzKyJweCIpLHRoaXMud2lkdGg9YS1uLmxlZnQtbi5yaWdodCx0aGlzLmhlaWdodD1zLW4udG9wLW4uYm90dG9tLHRoaXMuc3ZnRz10aGlzLnN2Zy5hcHBlbmQoImciKS5hdHRyKCJ0cmFuc2Zvcm0iLF9QKG4ubGVmdCxuLnRvcCkpfX0sYVY9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4saSl7dGhpcy5fc3ZnUHJvcHM9dCx0aGlzLl9zY2hlbWE9cix0aGlzLl9wZWFrZWRTZXNzaW9uR3JvdXBDaGFuZ2VkQ0I9bix0aGlzLl9zZWxlY3RlZFNlc3Npb25Hcm91cENoYW5nZWRDQj1pLHRoaXMuX2F4ZXNDb2xsZWN0aW9uPW5ldyBuVih0LHIsdGhpcyksdGhpcy5fbGluZXNDb2xsZWN0aW9uPW5ldyBpVih0LHIsdGhpcy5fYXhlc0NvbGxlY3Rpb24pLHRoaXMuX3N2Z1Byb3BzLnN2Zy5vbigiY2xpY2siLCgpPT50aGlzLm9uQ2xpY2soKSkub24oIm1vdXNlbW92ZSBtb3VzZWVudGVyIiwoKT0+e2xldFtvLGFdPXpvKHRoaXMuX3N2Z1Byb3BzLnN2Z0cubm9kZSgpKTt0aGlzLm9uTW91c2VNb3ZlZChvLGEpfSkub24oIm1vdXNlbGVhdmUiLCgpPT50aGlzLm9uTW91c2VMZWF2ZSgpKX1vbkRyYWdTdGFydCh0KXt0aGlzLl9heGVzQ29sbGVjdGlvbi5kcmFnU3RhcnQodCksdGhpcy5fbGluZXNDb2xsZWN0aW9uLmhpZGVCYWNrZ3JvdW5kTGluZXMoKX1vbkRyYWcodCl7dGhpcy5fYXhlc0NvbGxlY3Rpb24uZHJhZyh0KSx0aGlzLl9saW5lc0NvbGxlY3Rpb24ucmVjb21wdXRlQ29udHJvbFBvaW50cyhqZi5GT1JFR1JPVU5EKX1vbkRyYWdFbmQoKXt0aGlzLl9heGVzQ29sbGVjdGlvbi5kcmFnRW5kKDUwMCksdGhpcy5fbGluZXNDb2xsZWN0aW9uLnJlY29tcHV0ZUNvbnRyb2xQb2ludHMoamYuRk9SRUdST1VORCw1MDApLHdpbmRvdy5zZXRUaW1lb3V0KCgpPT57dGhpcy5fbGluZXNDb2xsZWN0aW9uLnJlY29tcHV0ZUNvbnRyb2xQb2ludHMoamYuQkFDS0dST1VORCksdGhpcy5fbGluZXNDb2xsZWN0aW9uLnNob3dCYWNrZ3JvdW5kTGluZXMoKX0sNTAwKX1vbkJydXNoQ2hhbmdlZCh0LHIpe3RoaXMuX2F4ZXNDb2xsZWN0aW9uLmdldEF4aXNGb3JDb2xJbmRleCh0KS5zZXRCcnVzaFNlbGVjdGlvbihyKSx0aGlzLl9saW5lc0NvbGxlY3Rpb24ucmVjb21wdXRlRm9yZWdyb3VuZExpbmVzVmlzaWJpbGl0eSgpfW9uTW91c2VNb3ZlZCh0LHIpe3RoaXMuX2xpbmVzQ29sbGVjdGlvbi51cGRhdGVQZWFrZWRTZXNzaW9uR3JvdXAodGhpcy5fbGluZXNDb2xsZWN0aW9uLmZpbmRDbG9zZXN0U2Vzc2lvbkdyb3VwKHQscikpLHRoaXMuX3BlYWtlZFNlc3Npb25Hcm91cENoYW5nZWRDQih0aGlzLl9saW5lc0NvbGxlY3Rpb24ucGVha2VkU2Vzc2lvbkdyb3VwSGFuZGxlKCkuc2Vzc2lvbkdyb3VwKCkpfW9uTW91c2VMZWF2ZSgpe3RoaXMuX2xpbmVzQ29sbGVjdGlvbi5wZWFrZWRTZXNzaW9uR3JvdXBIYW5kbGUoKS5pc051bGwoKXx8KHRoaXMuX2xpbmVzQ29sbGVjdGlvbi5jbGVhclBlYWtlZFNlc3Npb25Hcm91cCgpLHRoaXMuX3BlYWtlZFNlc3Npb25Hcm91cENoYW5nZWRDQihudWxsKSl9b25DbGljaygpe3RoaXMuX2xpbmVzQ29sbGVjdGlvbi5wZWFrZWRTZXNzaW9uR3JvdXBIYW5kbGUoKS5zZXNzaW9uR3JvdXAoKT09PXRoaXMuX2xpbmVzQ29sbGVjdGlvbi5zZWxlY3RlZFNlc3Npb25Hcm91cEhhbmRsZSgpLnNlc3Npb25Hcm91cCgpP3RoaXMuX2xpbmVzQ29sbGVjdGlvbi51cGRhdGVTZWxlY3RlZFNlc3Npb25Hcm91cChuZXcgdGgpOnRoaXMuX2xpbmVzQ29sbGVjdGlvbi51cGRhdGVTZWxlY3RlZFNlc3Npb25Hcm91cCh0aGlzLl9saW5lc0NvbGxlY3Rpb24ucGVha2VkU2Vzc2lvbkdyb3VwSGFuZGxlKCkpLHRoaXMuX3NlbGVjdGVkU2Vzc2lvbkdyb3VwQ2hhbmdlZENCKHRoaXMuX2xpbmVzQ29sbGVjdGlvbi5zZWxlY3RlZFNlc3Npb25Hcm91cEhhbmRsZSgpLnNlc3Npb25Hcm91cCgpKX1vbk9wdGlvbnNPclNlc3Npb25Hcm91cHNDaGFuZ2VkKHQscil7dGhpcy5fYXhlc0NvbGxlY3Rpb24udXBkYXRlQXhlcyh0LHIpO2xldCBuPXRoaXMuX2xpbmVzQ29sbGVjdGlvbi5wZWFrZWRTZXNzaW9uR3JvdXBIYW5kbGUoKSxpPXRoaXMuX2xpbmVzQ29sbGVjdGlvbi5zZWxlY3RlZFNlc3Npb25Hcm91cEhhbmRsZSgpO3RoaXMuX2xpbmVzQ29sbGVjdGlvbi5yZWRyYXcocix0LmNvbG9yQnlDb2x1bW5JbmRleCE9PXZvaWQgMD90LmNvbHVtbnNbdC5jb2xvckJ5Q29sdW1uSW5kZXhdLmFic29sdXRlSW5kZXg6bnVsbCx0Lm1pbkNvbG9yLHQubWF4Q29sb3IpLG4uZXF1YWxzVG8odGhpcy5fbGluZXNDb2xsZWN0aW9uLnBlYWtlZFNlc3Npb25Hcm91cEhhbmRsZSgpKXx8dGhpcy5fcGVha2VkU2Vzc2lvbkdyb3VwQ2hhbmdlZENCKHRoaXMuX2xpbmVzQ29sbGVjdGlvbi5wZWFrZWRTZXNzaW9uR3JvdXBIYW5kbGUoKS5zZXNzaW9uR3JvdXAoKSksaS5lcXVhbHNUbyh0aGlzLl9saW5lc0NvbGxlY3Rpb24uc2VsZWN0ZWRTZXNzaW9uR3JvdXBIYW5kbGUoKSl8fHRoaXMuX3NlbGVjdGVkU2Vzc2lvbkdyb3VwQ2hhbmdlZENCKHRoaXMuX2xpbmVzQ29sbGVjdGlvbi5zZWxlY3RlZFNlc3Npb25Hcm91cEhhbmRsZSgpLnNlc3Npb25Hcm91cCgpKX1zY2hlbWEoKXtyZXR1cm4gdGhpcy5fc2NoZW1hfX07dmFyIGVoPWNsYXNzIGV4dGVuZHMgR3QoX28obXQpKXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5zZWxlY3RlZFNlc3Npb25Hcm91cD1udWxsLHRoaXMuY2xvc2VzdFNlc3Npb25Hcm91cD1udWxsLHRoaXMucmVkcmF3Q291bnQ9MH1fb3B0aW9uc09yU2Vzc2lvbkdyb3Vwc0NoYW5nZWQoKXt2YXIgbjtpZighdGhpcy5vcHRpb25zKXJldHVybjtsZXR7Y29uZmlndXJhdGlvbjp0fT0obj10aGlzLl9wcmV2T3B0aW9ucykhPW51bGw/bjp7fSx7Y29uZmlndXJhdGlvbjpyfT10aGlzLm9wdGlvbnM7aWYodGhpcy5faW50ZXJhY3Rpb25NYW5hZ2VyPT09dm9pZCAwfHwhcWN0LmlzRXF1YWwodD09bnVsbD92b2lkIDA6dC5zY2hlbWEsci5zY2hlbWEpfHwhcWN0LmlzRXF1YWwodD09bnVsbD92b2lkIDA6dC5jb2x1bW5zVmlzaWJpbGl0eSxyLmNvbHVtbnNWaXNpYmlsaXR5KSl7SHQodGhpcy4kLnN2Zykuc2VsZWN0QWxsKCIqIikucmVtb3ZlKCk7bGV0IGk9bmV3IG9WKHRoaXMuJC5zdmcsci5jb2x1bW5zVmlzaWJpbGl0eS5maWx0ZXIoQm9vbGVhbikubGVuZ3RoKTt0aGlzLnNjb3BlU3VidHJlZSh0aGlzLiQuc3ZnLCEwKSx0aGlzLl9pbnRlcmFjdGlvbk1hbmFnZXI9bmV3IGFWKGksci5zY2hlbWEsbz0+dGhpcy5jbG9zZXN0U2Vzc2lvbkdyb3VwQ2hhbmdlZChvKSxvPT50aGlzLnNlbGVjdGVkU2Vzc2lvbkdyb3VwQ2hhbmdlZChvKSl9dGhpcy5fY29tcHV0ZVZhbGlkU2Vzc2lvbkdyb3VwcygpLHRoaXMuX2ludGVyYWN0aW9uTWFuYWdlci5vbk9wdGlvbnNPclNlc3Npb25Hcm91cHNDaGFuZ2VkKHRoaXMub3B0aW9ucyx0aGlzLl92YWxpZFNlc3Npb25Hcm91cHMpLHRoaXMucmVkcmF3Q291bnQrKyx0aGlzLl9wcmV2T3B0aW9ucz10aGlzLm9wdGlvbnN9Y2xvc2VzdFNlc3Npb25Hcm91cENoYW5nZWQodCl7dGhpcy5jbG9zZXN0U2Vzc2lvbkdyb3VwPXR9c2VsZWN0ZWRTZXNzaW9uR3JvdXBDaGFuZ2VkKHQpe3RoaXMuc2VsZWN0ZWRTZXNzaW9uR3JvdXA9dH1fY29tcHV0ZVZhbGlkU2Vzc2lvbkdyb3Vwcygpe2xldCB0PWNzO2lmKHRoaXMuc2Vzc2lvbkdyb3Vwcz09PXZvaWQgMCl7dGhpcy5fdmFsaWRTZXNzaW9uR3JvdXBzPXZvaWQgMDtyZXR1cm59bGV0IHI9dGhpcy5vcHRpb25zLmNvbmZpZ3VyYXRpb24uc2NoZW1hO3RoaXMuX3ZhbGlkU2Vzc2lvbkdyb3Vwcz10aGlzLnNlc3Npb25Hcm91cHMuZmlsdGVyKG49Pntmb3IobGV0IGk9MDtpPHQubnVtQ29sdW1ucyhyKTsrK2kpaWYoISF0aGlzLm9wdGlvbnMuY29uZmlndXJhdGlvbi5jb2x1bW5zVmlzaWJpbGl0eVtpXSYmdC5jb2x1bW5WYWx1ZUJ5SW5kZXgocixuLGkpPT09dm9pZCAwKXJldHVybiExO3JldHVybiEwfSl9fTtlaC50ZW1wbGF0ZT1RYAogICAgPGRpdiBpZD0iY29udGFpbmVyIj4KICAgICAgPHN2ZyBpZD0ic3ZnIj48L3N2Zz4KICAgIDwvZGl2PgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgLS10Zi1ocGFyYW1zLXBhcmFsbGVsLWNvb3Jkcy1wbG90LWF4aXMtc2hhZG93OiAwIDFweCAwICNmZmYsCiAgICAgICAgICAxcHggMCAwICNmZmYsIDAgLTFweCAwICNmZmYsIC0xcHggMCAwICNmZmY7CiAgICAgIH0KICAgICAgOmhvc3QoLmRhcmstbW9kZSkgewogICAgICAgIC0tdGYtaHBhcmFtcy1wYXJhbGxlbC1jb29yZHMtcGxvdC1heGlzLXNoYWRvdzogMCAxcHggMCAjMDAwLAogICAgICAgICAgMXB4IDAgMCAjMDAwLCAwIC0xcHggMCAjMDAwLCAtMXB4IDAgMCAjMDAwOwogICAgICB9CiAgICAgIHN2ZyB7CiAgICAgICAgZm9udDogMTBweCBzYW5zLXNlcmlmOwogICAgICB9CgogICAgICAuYmFja2dyb3VuZCBwYXRoIHsKICAgICAgICBmaWxsOiBub25lOwogICAgICAgIHN0cm9rZTogI2RkZDsKICAgICAgICBzaGFwZS1yZW5kZXJpbmc6IGNyaXNwRWRnZXM7CiAgICAgIH0KCiAgICAgIC5mb3JlZ3JvdW5kIHBhdGggewogICAgICAgIGZpbGw6IG5vbmU7CiAgICAgICAgc3Ryb2tlLW9wYWNpdHk6IDAuNzsKICAgICAgICBzdHJva2Utd2lkdGg6IDE7CiAgICAgIH0KCiAgICAgIC8qIFdpbGwgYmUgc2V0IG9uIGZvcmVncm91bmQgcGF0aHMgdGhhdCBhcmUgbm90ICJjb250YWluZWQiIGluIHRoZSBjdXJyZW50CiAgICAgICAgIGF4ZXMgYnJ1c2hlcy4gSWYgbm8gYnJ1c2hlcyBhcmUgc2V0LCBubyBwYXRoIHdpbGwgaGF2ZSB0aGlzIGNsYXNzLiAqLwogICAgICAuZm9yZWdyb3VuZCAuaW52aXNpYmxlLXBhdGggewogICAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICAgIH0KCiAgICAgIC8qIFN0eWxlIGZvciB0aGUgcGF0aCBjbG9zZXN0IHRvIHRoZSBtb3VzZSBwb2ludGVyICh0eXBpY2FsbHkgd2lsbCBiZWNvbWUKICAgICAgdGhlIHNlbGVjdGVkIHBhdGggd2hlbiB0aGUgdXNlciBjbGlja3MpLiAqLwogICAgICAuZm9yZWdyb3VuZCAucGVha2VkLXBhdGggewogICAgICAgIHN0cm9rZS13aWR0aDogMzsKICAgICAgfQoKICAgICAgLyogVGhlIGN1cnJlbnRseSBzZWxlY3RlZCBwYXRoIGNsYXNzLiBXZSB1c2UgIWltcG9ydGFudCB0byBvdmVycmlkZSB0aGUKICAgICAgICAgaW5saW5lIHN0eWxlIHRoYXQgc2V0cyB0aGUgcmVndWxhciBjb2xvciBvZiBhIHBhdGguICovCiAgICAgIC5mb3JlZ3JvdW5kIC5zZWxlY3RlZC1wYXRoIHsKICAgICAgICBzdHJva2Utd2lkdGg6IDMgIWltcG9ydGFudDsKICAgICAgICBzdHJva2U6ICMwZjAgIWltcG9ydGFudDsKICAgICAgfQoKICAgICAgI2NvbnRhaW5lciB7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICB9CgogICAgICBzdmcgewogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgfQoKICAgICAgLmF4aXMgdGV4dCB7CiAgICAgICAgdGV4dC1zaGFkb3c6IHZhcigtLXRmLWhwYXJhbXMtcGFyYWxsZWwtY29vcmRzLXBsb3QtYXhpcy1zaGFkb3cpOwogICAgICAgIGZpbGw6IGN1cnJlbnRDb2xvcjsKICAgICAgICBjdXJzb3I6IG1vdmU7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sZWgucHJvdG90eXBlLCJzZXNzaW9uR3JvdXBzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGVoLnByb3RvdHlwZSwib3B0aW9ucyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sZWgucHJvdG90eXBlLCJzZWxlY3RlZFNlc3Npb25Hcm91cCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sZWgucHJvdG90eXBlLCJjbG9zZXN0U2Vzc2lvbkdyb3VwIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLGVoLnByb3RvdHlwZSwicmVkcmF3Q291bnQiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxlaC5wcm90b3R5cGUsIl92YWxpZFNlc3Npb25Hcm91cHMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sZWgucHJvdG90eXBlLCJfaW50ZXJhY3Rpb25NYW5hZ2VyIix2b2lkIDApO0UoW0J0KCJvcHRpb25zLioiLCJzZXNzaW9uR3JvdXBzLioiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLGVoLnByb3RvdHlwZSwiX29wdGlvbnNPclNlc3Npb25Hcm91cHNDaGFuZ2VkIixudWxsKTtlaD1FKFt5dCgidGYtaHBhcmFtcy1wYXJhbGxlbC1jb29yZHMtcGxvdCIpXSxlaCk7dmFyIEEwPWNsYXNzIGV4dGVuZHMgbXR7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMub3B0aW9ucz1udWxsfV9jb25maWd1cmF0aW9uQ2hhbmdlZCgpe2xldCB0PXRoaXMuY29uZmlndXJhdGlvbi52aXNpYmxlU2NoZW1hLHI9dGhpcy5jb25maWd1cmF0aW9uLnNjaGVtYSxuPShhLHMpPT4oe25hbWU6RmQoYSksaW5kZXg6cyxhYnNvbHV0ZUluZGV4Ok9IKHIsdCxzKSxzY2FsZTp0aGlzLl9pc051bWVyaWNDb2x1bW4ocyk/IkxJTkVBUiI6Ik5PTl9OVU1FUklDIn0pLGk9KGEscyk9PntsZXQgbD1zK3QuaHBhcmFtSW5mb3MubGVuZ3RoO3JldHVybntzY2FsZToiTElORUFSIixuYW1lOlF1KGEpLGluZGV4OmwsYWJzb2x1dGVJbmRleDpPSChyLHQsbCl9fSxvPXtjb2x1bW5zOnQuaHBhcmFtSW5mb3MubWFwKG4pLmNvbmNhdCh0Lm1ldHJpY0luZm9zLm1hcChpKSksbWluQ29sb3I6IiMwMDAwRkYiLG1heENvbG9yOiIjRkYwMDAwIixjb25maWd1cmF0aW9uOnRoaXMuY29uZmlndXJhdGlvbn07dGhpcy5zZXQoIm9wdGlvbnMiLG8pLHVpKCksdGhpcy5zZXQoIm9wdGlvbnMuY29sb3JCeUNvbHVtbkluZGV4Iix0aGlzLl9kZWZhdWx0Q29sb3JCeUNvbHVtbkluZGV4KCkpfV91bnNlbGVjdERpc2FibGVkTG9nU2NhbGVzKCl7dGhpcy5vcHRpb25zIT09bnVsbCYmdGhpcy5vcHRpb25zLmNvbHVtbnMuZm9yRWFjaCh0PT57bGV0IHI9Im9wdGlvbnMuY29sdW1ucy4iK3QuaW5kZXg7IXRoaXMuX2FsbG93TG9nU2NhbGUodCkmJnQuc2NhbGU9PT0iTE9HIiYmdGhpcy5zZXQocisiLnNjYWxlIiwiTElORUFSIil9KX1fYWxsb3dMb2dTY2FsZSh0KXtpZighdGhpcy5faXNOdW1lcmljQ29sdW1uKHQuaW5kZXgpfHwhdGhpcy5zZXNzaW9uR3JvdXBzKXJldHVybiExO2xldFtyLG5dPWdQKHRoaXMuY29uZmlndXJhdGlvbi52aXNpYmxlU2NoZW1hLHRoaXMuc2Vzc2lvbkdyb3Vwcyx0LmluZGV4KTtyZXR1cm4gcj4wfHxuPDB9X2lzTnVtZXJpY0NvbHVtbih0KXtyZXR1cm4gdD49dGhpcy5jb25maWd1cmF0aW9uLnZpc2libGVTY2hlbWEuaHBhcmFtSW5mb3MubGVuZ3RofHx0aGlzLmNvbmZpZ3VyYXRpb24udmlzaWJsZVNjaGVtYS5ocGFyYW1JbmZvc1t0XS50eXBlPT09IkRBVEFfVFlQRV9GTE9BVDY0In1fZGVmYXVsdENvbG9yQnlDb2x1bW5JbmRleCgpe2lmKHRoaXMuY29uZmlndXJhdGlvbi52aXNpYmxlU2NoZW1hLm1ldHJpY0luZm9zLmxlbmd0aD4wKXJldHVybiB0aGlzLmNvbmZpZ3VyYXRpb24udmlzaWJsZVNjaGVtYS5ocGFyYW1JbmZvcy5sZW5ndGg7bGV0IHQ9dGhpcy5jb25maWd1cmF0aW9uLnZpc2libGVTY2hlbWEuaHBhcmFtSW5mb3MuZmluZEluZGV4KHI9PnIudHlwZT09PSJEQVRBX1RZUEVfRkxPQVQ2NCIpO2lmKHQhPT0tMSlyZXR1cm4gdH19O0EwLnRlbXBsYXRlPVFgCiAgICA8ZGl2IGNsYXNzPSJjb250cm9sLXBhbmVsIj4KICAgICAgPCEtLSAnQ29sb3IgYnknIGRyb3AgZG93biBtZW51IC0tPgogICAgICA8cGFwZXItZHJvcGRvd24tbWVudQogICAgICAgIGxhYmVsPSJDb2xvciBieSIKICAgICAgICBpZD0iY29sb3JCeURyb3BEb3duTWVudSIKICAgICAgICBob3Jpem9udGFsLWFsaWduPSJsZWZ0IgogICAgICA+CiAgICAgICAgPHBhcGVyLWxpc3Rib3gKICAgICAgICAgIGNsYXNzPSJkcm9wZG93bi1jb250ZW50IgogICAgICAgICAgc2xvdD0iZHJvcGRvd24tY29udGVudCIKICAgICAgICAgIHNlbGVjdGVkPSJ7e29wdGlvbnMuY29sb3JCeUNvbHVtbkluZGV4fX0iCiAgICAgICAgICBpZD0iY29sb3JCeUxpc3RCb3giCiAgICAgICAgPgogICAgICAgICAgPHRlbXBsYXRlCiAgICAgICAgICAgIGlzPSJkb20tcmVwZWF0IgogICAgICAgICAgICBpdGVtcz0iW1tvcHRpb25zLmNvbHVtbnNdXSIKICAgICAgICAgICAgYXM9ImNvbHVtbiIKICAgICAgICAgICAgaWQ9ImNvbG9yQnlDb2x1bW5UZW1wbGF0ZSIKICAgICAgICAgID4KICAgICAgICAgICAgPHBhcGVyLWl0ZW0gZGlzYWJsZWQ9IltbIV9pc051bWVyaWNDb2x1bW4oY29sdW1uLmluZGV4KV1dIj4KICAgICAgICAgICAgICBbW2NvbHVtbi5uYW1lXV0KICAgICAgICAgICAgPC9wYXBlci1pdGVtPgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L3BhcGVyLWxpc3Rib3g+CiAgICAgIDwvcGFwZXItZHJvcGRvd24tbWVudT4KCiAgICAgIDwhLS0gQ29sdW1ucyBzY2FsZXMgLS0+CiAgICAgIDxkaXYgY2xhc3M9ImNvbHVtbnMtY29udGFpbmVyIj4KICAgICAgICA8IS0tIFNjYWxlIG9wdGlvbnMgZm9yIGVhY2ggbnVtZXJpYyBmZWF0dXJlIC0tPgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9Int7b3B0aW9ucy5jb2x1bW5zfX0iIGFzPSJjb2x1bW4iPgogICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19pc051bWVyaWNDb2x1bW4oY29sdW1uLmluZGV4KV1dIj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29sdW1uIj4KICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb2x1bW4tdGl0bGUiPltbY29sdW1uLm5hbWVdXTwvZGl2PgogICAgICAgICAgICAgIDxkaXY+CiAgICAgICAgICAgICAgICA8cGFwZXItcmFkaW8tZ3JvdXAKICAgICAgICAgICAgICAgICAgY2xhc3M9InNjYWxlLXJhZGlvLWdyb3VwIgogICAgICAgICAgICAgICAgICBzZWxlY3RlZD0ie3tjb2x1bW4uc2NhbGV9fSIKICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgPHBhcGVyLXJhZGlvLWJ1dHRvbiBuYW1lPSJMSU5FQVIiPgogICAgICAgICAgICAgICAgICAgIExpbmVhcgogICAgICAgICAgICAgICAgICA8L3BhcGVyLXJhZGlvLWJ1dHRvbj4KICAgICAgICAgICAgICAgICAgPCEtLSBUaGUgaWQgaGVyZSBpcyB1c2VkIHRvIGFjY2VzcyB0aGlzIGJ1dHRvbiBpbiB1bml0CiAgICAgICAgICAgICAgICAgICAgICAgdGVzdHMuLS0+CiAgICAgICAgICAgICAgICAgIDxwYXBlci1yYWRpby1idXR0b24KICAgICAgICAgICAgICAgICAgICBpZD0ibG9nU2NhbGVCdXR0b25fW1tjb2x1bW4ubmFtZV1dIgogICAgICAgICAgICAgICAgICAgIG5hbWU9IkxPRyIKICAgICAgICAgICAgICAgICAgICBkaXNhYmxlZD0iW1shX2FsbG93TG9nU2NhbGUoY29sdW1uLCBzZXNzaW9uR3JvdXBzLiopXV0iCiAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICBMb2dhcml0aG1pYwogICAgICAgICAgICAgICAgICA8L3BhcGVyLXJhZGlvLWJ1dHRvbj4KICAgICAgICAgICAgICAgICAgPHBhcGVyLXJhZGlvLWJ1dHRvbiBuYW1lPSJRVUFOVElMRSI+CiAgICAgICAgICAgICAgICAgICAgUXVhbnRpbGUKICAgICAgICAgICAgICAgICAgPC9wYXBlci1yYWRpby1idXR0b24+CiAgICAgICAgICAgICAgICA8L3BhcGVyLXJhZGlvLWdyb3VwPgogICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KCiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgfQogICAgICAuY29udHJvbC1wYW5lbCB7CiAgICAgICAgb3ZlcmZsb3c6IGF1dG87CiAgICAgIH0KICAgICAgLmNvbHVtbiB7CiAgICAgICAgZmxleC1ncm93OiAxOwogICAgICAgIGZsZXgtc2hyaW5rOiAxOwogICAgICAgIG1hcmdpbi1yaWdodDogNXB4OwogICAgICAgIGJvcmRlcjogc29saWQgMXB4IGRhcmtncmF5OwogICAgICAgIHBhZGRpbmc6IDNweDsKICAgICAgfQogICAgICAuY29sdW1uLXRpdGxlIHsKICAgICAgICAvKiBGaXQgZXZlcnkgdGl0bGUgaW4gb25lIGxpbmUgc28gdGhlIHJhZGlvIGJveGVzIGFsaWduIHZlcnRpY2FsbHkuICovCiAgICAgICAgd2hpdGUtc3BhY2U6IG5vd3JhcDsKICAgICAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKICAgICAgfQogICAgICAuY29sdW1ucy1jb250YWluZXIgewogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IHJvdzsKICAgICAgfQogICAgICAuc2NhbGUtcmFkaW8tZ3JvdXAgcGFwZXItcmFkaW8tYnV0dG9uIHsKICAgICAgICBwYWRkaW5nOiAycHg7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgIH0KICAgICAgcGFwZXItbGlzdGJveCB7CiAgICAgICAgbWF4LWhlaWdodDogMTVlbTsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLEEwLnByb3RvdHlwZSwiY29uZmlndXJhdGlvbiIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sQTAucHJvdG90eXBlLCJzZXNzaW9uR3JvdXBzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0LG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxBMC5wcm90b3R5cGUsIm9wdGlvbnMiLHZvaWQgMCk7RShbQnQoImNvbmZpZ3VyYXRpb24uKiIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sQTAucHJvdG90eXBlLCJfY29uZmlndXJhdGlvbkNoYW5nZWQiLG51bGwpO0UoW0J0KCJzZXNzaW9uR3JvdXBzLioiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLEEwLnByb3RvdHlwZSwiX3Vuc2VsZWN0RGlzYWJsZWRMb2dTY2FsZXMiLG51bGwpO0EwPUUoW3l0KCJ0Zi1ocGFyYW1zLXNjYWxlLWFuZC1jb2xvci1jb250cm9scyIpXSxBMCk7dmFyIGh2PWNsYXNzIGV4dGVuZHMgbXR7X2Nsb3Nlc3RPclNlbGVjdGVkKHQscil7cmV0dXJuIHQhPT1udWxsP3Q6cn19O2h2LnRlbXBsYXRlPVFgCiAgICA8IS0tIENvbnRyb2xzIGJlaGF2aW9yIG9mIHBhcmFsbGVsIGNvb3JkaW5hdGVzIHBsb3QKICAgICAgICAgb3V0cHV0cyBzZXQgb3B0aW9ucyB0byB0aGUgX29wdGlvbnMgcHJvcGVydHkuCiAgICAgIC0tPgogICAgPGhwYXJhbXMtc3BsaXQtbGF5b3V0IG9yaWVudGF0aW9uPSJ2ZXJ0aWNhbCI+CiAgICAgIDwhLS0gVGhlIHNjYWxlIGFuZCBjb2xvciBjb250cm9scy4gLS0+CiAgICAgIDx0Zi1ocGFyYW1zLXNjYWxlLWFuZC1jb2xvci1jb250cm9scwogICAgICAgIGlkPSJjb250cm9scyIKICAgICAgICBzbG90PSJjb250ZW50IgogICAgICAgIGNsYXNzPSJzZWN0aW9uIgogICAgICAgIGNvbmZpZ3VyYXRpb249IltbY29uZmlndXJhdGlvbl1dIgogICAgICAgIHNlc3Npb24tZ3JvdXBzPSJbW3Nlc3Npb25Hcm91cHNdXSIKICAgICAgICBvcHRpb25zPSJ7e19vcHRpb25zfX0iCiAgICAgID4KICAgICAgPC90Zi1ocGFyYW1zLXNjYWxlLWFuZC1jb2xvci1jb250cm9scz4KICAgICAgPCEtLSBUaGUgYWN0dWFsIHBhcmFsbGVsIGNvb3JkaW5hdGVzIHBsb3QgLS0+CiAgICAgIDx0Zi1ocGFyYW1zLXBhcmFsbGVsLWNvb3Jkcy1wbG90CiAgICAgICAgaWQ9InBsb3QiCiAgICAgICAgc2xvdD0iY29udGVudCIKICAgICAgICBjbGFzcz0ic2VjdGlvbiIKICAgICAgICBzZXNzaW9uLWdyb3Vwcz0iW1tzZXNzaW9uR3JvdXBzXV0iCiAgICAgICAgc2VsZWN0ZWQtc2Vzc2lvbi1ncm91cD0ie3tfc2VsZWN0ZWRHcm91cH19IgogICAgICAgIGNsb3Nlc3Qtc2Vzc2lvbi1ncm91cD0ie3tfY2xvc2VzdEdyb3VwfX0iCiAgICAgICAgb3B0aW9ucz0iW1tfb3B0aW9uc11dIgogICAgICA+CiAgICAgIDwvdGYtaHBhcmFtcy1wYXJhbGxlbC1jb29yZHMtcGxvdD4KICAgICAgPHRmLWhwYXJhbXMtc2Vzc2lvbi1ncm91cC12YWx1ZXMKICAgICAgICBpZD0idmFsdWVzIgogICAgICAgIHNsb3Q9ImNvbnRlbnQiCiAgICAgICAgY2xhc3M9InNlY3Rpb24iCiAgICAgICAgdmlzaWJsZS1zY2hlbWE9IltbY29uZmlndXJhdGlvbi52aXNpYmxlU2NoZW1hXV0iCiAgICAgICAgc2Vzc2lvbi1ncm91cD0iW1tfY2xvc2VzdE9yU2VsZWN0ZWQoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgX2Nsb3Nlc3RHcm91cCwgX3NlbGVjdGVkR3JvdXApXV0iCiAgICAgID4KICAgICAgPC90Zi1ocGFyYW1zLXNlc3Npb24tZ3JvdXAtdmFsdWVzPgogICAgICA8dGYtaHBhcmFtcy1zZXNzaW9uLWdyb3VwLWRldGFpbHMKICAgICAgICBpZD0iZGV0YWlscyIKICAgICAgICBzbG90PSJjb250ZW50IgogICAgICAgIGNsYXNzPSJzZWN0aW9uIgogICAgICAgIGJhY2tlbmQ9IltbYmFja2VuZF1dIgogICAgICAgIGV4cGVyaW1lbnQtbmFtZT0iW1tleHBlcmltZW50TmFtZV1dIgogICAgICAgIHNlc3Npb24tZ3JvdXA9IltbX3NlbGVjdGVkR3JvdXBdXSIKICAgICAgICB2aXNpYmxlLXNjaGVtYT0iW1tjb25maWd1cmF0aW9uLnZpc2libGVTY2hlbWFdXSIKICAgICAgPgogICAgICA8L3RmLWhwYXJhbXMtc2Vzc2lvbi1ncm91cC1kZXRhaWxzPgogICAgPC9ocGFyYW1zLXNwbGl0LWxheW91dD4KCiAgICA8c3R5bGU+CiAgICAgIC5zZWN0aW9uIHsKICAgICAgICBwYWRkaW5nOiAxMHB4OwogICAgICB9CiAgICAgICN2YWx1ZXMgewogICAgICAgIGhlaWdodDogMTE1cHg7CiAgICAgIH0KICAgICAgI2RldGFpbHMgewogICAgICAgIGZsZXgtZ3JvdzogMTsKICAgICAgICBtYXgtaGVpZ2h0OiBmaXQtY29udGVudDsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGh2LnByb3RvdHlwZSwiYmFja2VuZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxodi5wcm90b3R5cGUsImV4cGVyaW1lbnROYW1lIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGh2LnByb3RvdHlwZSwiY29uZmlndXJhdGlvbiIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0saHYucHJvdG90eXBlLCJzZXNzaW9uR3JvdXBzIix2b2lkIDApO2h2PUUoW3l0KCJ0Zi1ocGFyYW1zLXBhcmFsbGVsLWNvb3Jkcy12aWV3IildLGh2KTt2YXIgeTM9RWUoT2UoKSwxKTt2YXIgJG89Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuc2VsZWN0ZWRTZXNzaW9uR3JvdXA9bnVsbCx0aGlzLmNsb3Nlc3RTZXNzaW9uR3JvdXA9bnVsbCx0aGlzLl9jb250YWluZXI9bnVsbCx0aGlzLl9zdmc9bnVsbCx0aGlzLndpZHRoPTAsdGhpcy5oZWlnaHQ9MCx0aGlzLl9icnVzaGVkQ2VsbEluZGV4PW51bGwsdGhpcy5fYnJ1c2hTZWxlY3Rpb249bnVsbH1yZWFkeSgpe3N1cGVyLnJlYWR5KCksdGhpcy5fY29udGFpbmVyPXRoaXMuJC5jb250YWluZXIsdGhpcy5fc3ZnPUh0KHRoaXMuJC5zdmcpLHRoaXMuX3JlZHJhdygpfV9zZXNzaW9uR3JvdXBzQ2hhbmdlZCgpe3RoaXMuc2VsZWN0ZWRTZXNzaW9uR3JvdXAhPT1udWxsJiYodGhpcy5zZWxlY3RlZFNlc3Npb25Hcm91cD1wY3QodGhpcy5zZXNzaW9uR3JvdXBzLHRoaXMuc2VsZWN0ZWRTZXNzaW9uR3JvdXAubmFtZSl8fG51bGwpLHRoaXMuX3JlZHJhdygpfV92aXNpYmxlU2NoZW1hQ2hhbmdlZCgpe3RoaXMuX2JydXNoZWRDZWxsSW5kZXg9bnVsbCx0aGlzLl9icnVzaFNlbGVjdGlvbj1udWxsLHRoaXMuX3JlZHJhdygpfV9yZWRyYXcoKXt0aGlzLmRlYm91bmNlKCJfcmVkcmF3IiwoKT0+e2xldCB0PWNzLHI9MTIwMCxuPS40KnIsaT0xNTAsbz0uNzUqaTt0aGlzLndpZHRoPU1hdGgubWF4KGkqdC5udW1WaXNpYmxlQ29sdW1ucyh0aGlzLnZpc2libGVTY2hlbWEpLHIpLHRoaXMuaGVpZ2h0PU1hdGgubWF4KG8qdC5udW1WaXNpYmxlTWV0cmljcyh0aGlzLnZpc2libGVTY2hlbWEpLG4pLHRoaXMuX2NvbnRhaW5lci5zdHlsZS53aWR0aD10aGlzLndpZHRoKyJweCIsdGhpcy5fY29udGFpbmVyLnN0eWxlLmhlaWdodD10aGlzLmhlaWdodCsicHgiLHRoaXMuX3N2Zy5hdHRyKCJ3aWR0aCIsdGhpcy53aWR0aCkuYXR0cigiaGVpZ2h0Iix0aGlzLmhlaWdodCksdGhpcy5fc3ZnLnNlbGVjdEFsbCgiZyIpLnJlbW92ZSgpLHRoaXMuX2RyYXcoKX0sMTAwKX1fZHJhdygpe2xldCB0PWNzLHI9dGhpcztpZighdGhpcy5zZXNzaW9uR3JvdXBzfHx0aGlzLnNlc3Npb25Hcm91cHMubGVuZ3RoPT0wfHwhdGhpcy52aXNpYmxlU2NoZW1hfHx0aGlzLnZpc2libGVTY2hlbWEubWV0cmljSW5mb3MubGVuZ3RoPT0wKXJldHVybjtsZXQgbj1Jcih0Lm51bVZpc2libGVDb2x1bW5zKHIudmlzaWJsZVNjaGVtYSkpLGk9SXIodC5udW1WaXNpYmxlTWV0cmljcyhyLnZpc2libGVTY2hlbWEpKSxvPTgwLGE9NTAscz01LGw9UW0oKS5kb21haW4obikucmFuZ2UoW28rcyx0aGlzLndpZHRoLTEtc10pLnBhZGRpbmdJbm5lciguMSksYz1RbSgpLmRvbWFpbihpKS5yYW5nZShbdGhpcy5oZWlnaHQtMS1zLWEsc10pLnBhZGRpbmdJbm5lciguMSksdT1sLmJhbmR3aWR0aCgpLGg9Yy5iYW5kd2lkdGgoKSxmPW4ubWFwKGN0PT5yLl9jZWxsU2NhbGUoY3QsWzAsdS0xXSkpLHA9aS5tYXAoY3Q9PnIuX2NlbGxTY2FsZShjdCt0Lm51bVZpc2libGVIUGFyYW1zKHIudmlzaWJsZVNjaGVtYSksW2gtMSwwXSkpLGQ9dGhpcy5fc3ZnLnNlbGVjdEFsbCgiLngtYXhpcyIpLmRhdGEobikuZW50ZXIoKS5hcHBlbmQoImciKS5jbGFzc2VkKCJ4LWF4aXMiLCEwKS5hdHRyKCJ0cmFuc2Zvcm0iLGN0PT50LnRyYW5zbGF0ZVN0cihsKGN0KSwwKSk7ZnVuY3Rpb24gZyhjdCl7cmV0dXJuIngtYXhpcy1jbGlwLXBhdGgtIitjdH1mdW5jdGlvbiBfKGN0KXtyZXR1cm4ieC1sYWJlbC1jbGlwLXBhdGgtIitjdH1kLmFwcGVuZCgiY2xpcFBhdGgiKS5hdHRyKCJpZCIsZykuYXBwZW5kKCJyZWN0IikuYXR0cigieCIsLXMpLmF0dHIoInkiLDApLmF0dHIoIndpZHRoIix1KzIqcykuYXR0cigiaGVpZ2h0IixyLmhlaWdodC1hLzIpLGQuYXBwZW5kKCJjbGlwUGF0aCIpLmF0dHIoImlkIixfKS5hcHBlbmQoInJlY3QiKS5hdHRyKCJ4IiwwKS5hdHRyKCJ5IixyLmhlaWdodC1hLzIpLmF0dHIoIndpZHRoIix1KS5hdHRyKCJoZWlnaHQiLGEvMiksZC5hcHBlbmQoImciKS5hdHRyKCJjbGlwLXBhdGgiLGN0PT4idXJsKCMiK2coY3QpKyIpIikuZWFjaChmdW5jdGlvbihjdCl7SHQodGhpcykuY2FsbChTLEs5KGZbY3RdKS50aWNrU2l6ZShyLmhlaWdodC1hKSx1LDQwLHIub3B0aW9ucy5jb2x1bW5zW2N0XS5zY2FsZSl9KSxkLmFwcGVuZCgiZyIpLmNsYXNzZWQoIngtYXhpcy1sYWJlbCIsITApLmF0dHIoImNsaXAtcGF0aCIsY3Q9PiJ1cmwoIyIrXyhjdCkrIikiKS5hcHBlbmQoInRleHQiKS5hdHRyKCJ0ZXh0LWFuY2hvciIsIm1pZGRsZSIpLmF0dHIoIngiLHUvMikuYXR0cigieSIsci5oZWlnaHQtMS1hLzQpLnRleHQoY3Q9PnQuc2NoZW1hVmlzaWJsZUNvbHVtbk5hbWUoci52aXNpYmxlU2NoZW1hLGN0KSkuYXBwZW5kKCJ0aXRsZSIpLnRleHQoY3Q9PnQuc2NoZW1hVmlzaWJsZUNvbHVtbk5hbWUoci52aXNpYmxlU2NoZW1hLGN0KSk7bGV0IHk9dGhpcy5fc3ZnLnNlbGVjdEFsbCgiLnktYXhpcyIpLmRhdGEoaSkuZW50ZXIoKS5hcHBlbmQoImciKS5jbGFzc2VkKCJ5LWF4aXMiLCEwKS5hdHRyKCJ0cmFuc2Zvcm0iLGN0PT50LnRyYW5zbGF0ZVN0cihyLndpZHRoLTEsYyhjdCkpKTtmdW5jdGlvbiB4KGN0KXtyZXR1cm4ieS1heGlzLWNsaXAtcGF0aC0iK2N0fWZ1bmN0aW9uIGIoY3Qpe3JldHVybiJ5LWxhYmVsLWNsaXAtcGF0aC0iK2N0fXkuYXBwZW5kKCJjbGlwUGF0aCIpLmF0dHIoImlkIix4KS5hcHBlbmQoInJlY3QiKS5hdHRyKCJ4IiwtKHIud2lkdGgtby8yLTEpKS5hdHRyKCJ5IiwtcykuYXR0cigid2lkdGgiLHIud2lkdGgtby8yKS5hdHRyKCJoZWlnaHQiLGgrMipzKSx5LmFwcGVuZCgiY2xpcFBhdGgiKS5hdHRyKCJpZCIsYikuYXBwZW5kKCJyZWN0IikuYXR0cigieCIsLShyLndpZHRoLTEpKS5hdHRyKCJ5IiwwKS5hdHRyKCJ3aWR0aCIsby8yKS5hdHRyKCJoZWlnaHQiLGgpLHkuYXBwZW5kKCJnIikuYXR0cigiY2xpcC1wYXRoIixjdD0+InVybCgjIit4KGN0KSsiKSIpLmVhY2goZnVuY3Rpb24oY3Qpe0h0KHRoaXMpLmNhbGwoUyxsYihwW2N0XSkudGlja1NpemUoci53aWR0aC1vKSxoLDIwLHIub3B0aW9ucy5jb2x1bW5zW2N0K3QubnVtVmlzaWJsZUhQYXJhbXMoci52aXNpYmxlU2NoZW1hKV0uc2NhbGUpfSkseS5hcHBlbmQoImciKS5jbGFzc2VkKCJ5LWF4aXMtbGFiZWwiLCEwKS5hdHRyKCJjbGlwLXBhdGgiLGN0PT4idXJsKCMiK2IoY3QpKyIpIikuYXBwZW5kKCJ0ZXh0IikuYXR0cigidGV4dC1hbmNob3IiLCJtaWRkbGUiKS5hdHRyKCJ4IiwtKHIud2lkdGgtby80LTEpKS5hdHRyKCJ5IixoLzIpLmF0dHIoInRyYW5zZm9ybSIsdC5yb3RhdGVTdHIoOTAsLShyLndpZHRoLW8vNC0xKSxoLzIpKS50ZXh0KGN0PT50Lm1ldHJpY05hbWUoci52aXNpYmxlU2NoZW1hLm1ldHJpY0luZm9zW2N0XSkpLmFwcGVuZCgidGl0bGUiKS50ZXh0KGN0PT50Lm1ldHJpY05hbWUoci52aXNpYmxlU2NoZW1hLm1ldHJpY0luZm9zW2N0XSkpO2Z1bmN0aW9uIFMoY3QsWCxldCxkdCxxKXtsZXQgcHQ9TWF0aC5mbG9vcihldC9kdCksaHQ9WC5zY2FsZSgpO2lmKHE9PT0iUVVBTlRJTEUiKXtsZXQgd3Q9aHQucXVhbnRpbGVzKCksa3Q9TWF0aC5jZWlsKHd0Lmxlbmd0aC9wdCk7d3Q9SXIoMCx3dC5sZW5ndGgsa3QpLm1hcChpZT0+d3RbaWVdKSxYLnRpY2tWYWx1ZXMod3QpLnRpY2tGb3JtYXQoeG4oIi0uMmciKSl9KHE9PT0iTElORUFSInx8cT09PSJMT0ciKSYmWC50aWNrcyhwdCksY3QuY2FsbChYKSxjdC5zZWxlY3RBbGwoIi5kb21haW4iKS5yZW1vdmUoKSxjdC5zZWxlY3RBbGwoIi50aWNrIGxpbmUiKS5hdHRyKCJzdHJva2UiLCIjZGRkIil9bGV0IEM9dGhpcy5fc3ZnLnNlbGVjdEFsbCgiLmNlbGwiKS5kYXRhKFU5KG4saSkpLmVudGVyKCkuYXBwZW5kKCJnIikuY2xhc3NlZCgiY2VsbCIsITApLmF0dHIoInRyYW5zZm9ybSIsKFtjdCxYXSk9PnQudHJhbnNsYXRlU3RyKGwoY3QpLGMoWCkpKSxQPUMuYXBwZW5kKCJnIikuY2xhc3NlZCgiZnJhbWUiLCEwKS5hcHBlbmQoInJlY3QiKS5hdHRyKCJ4IiwtcykuYXR0cigieSIsLXMpLmF0dHIoIndpZHRoIix1KzIqcykuYXR0cigiaGVpZ2h0IixoKzIqcykuYXR0cigic3Ryb2tlIiwiIzAwMCIpLmF0dHIoImZpbGwiLCJub25lIikuYXR0cigic2hhcGUtcmVuZGVyaW5nIiwiY3Jpc3BFZGdlcyIpLGs9bnVsbDtyLm9wdGlvbnMuY29sb3JCeUNvbHVtbkluZGV4IT09dm9pZCAwJiYoaz16bigpLmRvbWFpbih0aGlzLl9jb2xFeHRlbnQodGhpcy5vcHRpb25zLmNvbG9yQnlDb2x1bW5JbmRleCkpLnJhbmdlKFt0aGlzLm9wdGlvbnMubWluQ29sb3IsdGhpcy5vcHRpb25zLm1heENvbG9yXSkuaW50ZXJwb2xhdGUoTV8pKTtsZXQgTz1yLm9wdGlvbnMuY29sb3JCeUNvbHVtbkluZGV4PT09dm9pZCAwPygpPT4icmVkIjooe3Nlc3Npb25Hcm91cDpjdH0pPT5rKHRoaXMuX2NvbFZhbHVlKGN0LHIub3B0aW9ucy5jb2xvckJ5Q29sdW1uSW5kZXgpKTtmdW5jdGlvbiBEKGN0LFgpe3JldHVybiBmW1hdKHIuX2NvbFZhbHVlKGN0LFgpKX1mdW5jdGlvbiBCKGN0LFgpe3JldHVybiBwW1hdKHIuX21ldHJpY1ZhbHVlKGN0LFgpKX1mdW5jdGlvbiBJKGN0LFgpe2xldCBldD1jdC5zZWxlY3RBbGwoIi5kYXRhLW1hcmtlciIpLmRhdGEoKFtwdCxodF0pPT5yLnNlc3Npb25Hcm91cHMuZmlsdGVyKHd0PT5yLl9jb2xWYWx1ZSh3dCxwdCkhPT12b2lkIDAmJnIuX21ldHJpY1ZhbHVlKHd0LGh0KSE9PXZvaWQgMCkubWFwKHd0PT4oe2NvbDpwdCxtZXRyaWM6aHQsc2Vzc2lvbkdyb3VwOnd0LHg6RCh3dCxwdCkseTpCKHd0LGh0KSxzZXNzaW9uR3JvdXBNYXJrZXJzOm51bGx9KSkpLmVudGVyKCkuYXBwZW5kKCJjaXJjbGUiKS5jbGFzc2VkKCJkYXRhLW1hcmtlciIsITApLmF0dHIoImN4Iiwoe3g6cHR9KT0+cHQpLmF0dHIoImN5Iiwoe3k6cHR9KT0+cHQpLmF0dHIoInIiLDIpLmF0dHIoImZpbGwiLFgpLGR0PW5ldyBNYXA7ci5zZXNzaW9uR3JvdXBzLmZvckVhY2gocHQ9PntkdC5zZXQocHQsW10pfSksZXQuZWFjaChmdW5jdGlvbihwdCl7dmFyIGh0OyhodD1kdC5nZXQocHQuc2Vzc2lvbkdyb3VwKSk9PW51bGx8fGh0LnB1c2godGhpcyl9KSxldC5lYWNoKHB0PT57bGV0IGh0PWR0LmdldChwdC5zZXNzaW9uR3JvdXApO3B0LnNlc3Npb25Hcm91cE1hcmtlcnM9bmV3IFNldChodCl9KTtsZXQgcT1uLm1hcChwdD0+aS5tYXAoaHQ9PmV0LmZpbHRlcih3dD0+d3QuY29sPT1wdCYmd3QubWV0cmljPT1odCkpKTtyZXR1cm5bZXQscSxkdF19bGV0W0wsUixGXT1JKEMuYXBwZW5kKCJnIiksTyk7ZnVuY3Rpb24geihjdCxYKXtsZXQgZXQ9W107cmV0dXJuIFJbY3RdW1hdLmVhY2goZnVuY3Rpb24oKXtldC5wdXNoKHRoaXMpfSksemgoKS54KGR0PT5IdChkdCkuZGF0dW0oKS54KS55KGR0PT5IdChkdCkuZGF0dW0oKS55KS5hZGRBbGwoZXQpfWxldCBVPW4ubWFwKGN0PT5pLm1hcChYPT56KGN0LFgpKSksVz1udWxsO2J0KCkmJihXPUMuZmlsdGVyKGN0PT55My5pc0VxdWFsKGN0LHIuX2JydXNoZWRDZWxsSW5kZXgpKSxjb25zb2xlLmFzc2VydChXLnNpemUoKT09MSxXKSk7bGV0IFo9bmV3IFNldChMLm5vZGVzKCkpO3J0KCk7ZnVuY3Rpb24gcnQoKXtsZXQgY3Q9bmV3IFNldChMLm5vZGVzKCkpO010KCl8fChjdD1vdChyLl9icnVzaGVkQ2VsbEluZGV4LHIuX2JydXNoU2VsZWN0aW9uKSksRXAoQXJyYXkuZnJvbSh0LmZpbHRlclNldChjdCxYPT4hWi5oYXMoWCkpKSkuYXR0cigiZmlsbCIsTyksRXAoQXJyYXkuZnJvbSh0LmZpbHRlclNldChaLFg9PiFjdC5oYXMoWCkpKSkuYXR0cigiZmlsbCIsIiNkZGQiKSxaPWN0fWZ1bmN0aW9uIG90KGN0LFgpe2NvbnNvbGUuYXNzZXJ0KGN0IT09bnVsbCksY29uc29sZS5hc3NlcnQoWCE9PW51bGwpO2xldFtldCxkdF09Y3QscT1uZXcgU2V0O3JldHVybiB0LnF1YWRUcmVlVmlzaXRQb2ludHNJblJlY3QoVVtldF1bZHRdLFhbMF1bMF0sWFswXVsxXSxYWzFdWzBdLFhbMV1bMV0scHQ9PntIdChwdCkuZGF0dW0oKS5zZXNzaW9uR3JvdXBNYXJrZXJzLmZvckVhY2god3Q9PntxLmFkZCh3dCl9KX0pLHF9bGV0IHN0PXFMKCkuZXh0ZW50KFtbLXMrMSwtcysxXSxbdS0xK3MtMSxoLTErcy0xXV0pLm9uKCJzdGFydCIsZnVuY3Rpb24oKXtidCgpJiZXLm5vZGUoKSE9dGhpcyYmc3QubW92ZShXLG51bGwpLFN0KHRoaXMpfSkub24oImJydXNoIixmdW5jdGlvbigpe1N0KHRoaXMpfSkub24oImVuZCIsZnVuY3Rpb24oKXtTdCh0aGlzKX0pO2Z1bmN0aW9uIFN0KGN0KXtsZXQgWD1WTChjdCk7IWJ0KCkmJlg9PT1udWxsfHxidCgpJiZjdD09PVcubm9kZSgpJiZ5My5pc0VxdWFsKFgsci5fYnJ1c2hTZWxlY3Rpb24pfHwoci5fYnJ1c2hTZWxlY3Rpb249WCxYIT09bnVsbD8oVz1IdChjdCksci5fYnJ1c2hlZENlbGxJbmRleD1XLmRhdHVtKCkpOihXPW51bGwsci5fYnJ1c2hlZENlbGxJbmRleD1udWxsKSxydCgpKX1mdW5jdGlvbiBidCgpe3JldHVybiByLl9icnVzaGVkQ2VsbEluZGV4IT09bnVsbCYmci5fYnJ1c2hTZWxlY3Rpb24hPT1udWxsfWZ1bmN0aW9uIE10KCl7cmV0dXJuIWJ0KCl8fHIuX2JydXNoU2VsZWN0aW9uWzBdWzBdPT09ci5fYnJ1c2hTZWxlY3Rpb25bMV1bMF18fHIuX2JydXNoU2VsZWN0aW9uWzBdWzFdPT09ci5fYnJ1c2hTZWxlY3Rpb25bMV1bMV19Qy5jYWxsKHN0KSxidCgpJiZzdC5tb3ZlKFcsci5fYnJ1c2hTZWxlY3Rpb24pO2xldCBsdD1udWxsLEt0PW51bGw7dGhpcy5zZWxlY3RlZFNlc3Npb25Hcm91cCE9PW51bGwmJihLdD1FcChGLmdldCh0aGlzLnNlbGVjdGVkU2Vzc2lvbkdyb3VwKSkuY2xhc3NlZCgic2VsZWN0ZWQtbWFya2VyIiwhMCkpLEMub24oImNsaWNrIixmdW5jdGlvbigpe2xldCBjdD1sdD09PUt0P251bGw6bHQ7aWYoY3Q9PT1LdClyZXR1cm47S3QhPT1udWxsJiZLdC5jbGFzc2VkKCJzZWxlY3RlZC1tYXJrZXIiLCExKSxLdD1jdCxLdCE9PW51bGwmJkt0LmNsYXNzZWQoInNlbGVjdGVkLW1hcmtlciIsITApO2xldCBYPUt0PT09bnVsbD9udWxsOkt0LmRhdHVtKCkuc2Vzc2lvbkdyb3VwO3Iuc2VsZWN0ZWRTZXNzaW9uR3JvdXA9WH0pLm9uKCJtb3VzZW1vdmUgbW91c2VlbnRlciIsZnVuY3Rpb24oW2N0LFhdKXtsZXRbZXQsZHRdPXpvKHRoaXMpLHE9X3QoY3QsWCxldCxkdCwyMCk7bHQhPT1xJiYobHQhPT1udWxsJiZsdC5jbGFzc2VkKCJjbG9zZXN0LW1hcmtlciIsITEpLGx0PXEsbHQhPT1udWxsPyhsdC5jbGFzc2VkKCJjbG9zZXN0LW1hcmtlciIsITApLHIuY2xvc2VzdFNlc3Npb25Hcm91cD1sdC5kYXR1bSgpLnNlc3Npb25Hcm91cCk6ci5jbG9zZXN0U2Vzc2lvbkdyb3VwPW51bGwpfSkub24oIm1vdXNlbGVhdmUiLGZ1bmN0aW9uKFtjdCxYXSl7bHQhPT1udWxsJiYobHQuY2xhc3NlZCgiY2xvc2VzdC1tYXJrZXIiLCExKSxsdD1udWxsLHIuY2xvc2VzdFNlc3Npb25Hcm91cD1udWxsKX0pO2Z1bmN0aW9uIF90KGN0LFgsZXQsZHQscSl7bGV0IHB0PTEvMCxodD1udWxsO3JldHVybiB0LnF1YWRUcmVlVmlzaXRQb2ludHNJbkRpc2soVVtjdF1bWF0sZXQsZHQscSwod3Qsa3QpPT57aWYoWi5oYXMod3QpJiZrdDxwdCl7bGV0IGllPUh0KHd0KS5kYXR1bSgpO3B0PWt0LGh0PWllLnNlc3Npb25Hcm91cH19KSxodD09PW51bGw/bnVsbDpFcChGLmdldChodCkpfXRoaXMuX3N2Zy5zZWxlY3RBbGwoIioiKS5jbGFzc2VkKCJ0Zi1ocGFyYW1zLXNjYXR0ZXItcGxvdC1tYXRyaXgtcGxvdCIsITApfV9jZWxsU2NhbGUodCxyKXtsZXQgbj10aGlzLl9jb2xFeHRlbnQodCksaT16bigpLmRvbWFpbihuKS5yYW5nZShyKTtpZih0aGlzLm9wdGlvbnMuY29sdW1uc1t0XS5zY2FsZT09PSJMSU5FQVIiKXJldHVybiBpO2lmKHRoaXMub3B0aW9ucy5jb2x1bW5zW3RdLnNjYWxlPT09IkxPRyIpcmV0dXJuIG5bMF08PTAmJm5bMV0+PTA/aTpjYygpLmRvbWFpbihuKS5yYW5nZShyKTtpZih0aGlzLm9wdGlvbnMuY29sdW1uc1t0XS5zY2FsZT09PSJRVUFOVElMRSIpe2xldCBvPShyWzFdLXJbMF0pLzE5LGE9SXIoMjApLm1hcChzPT5yWzBdK28qcyk7cmV0dXJuIGVnKCkuZG9tYWluKHkzLnVuaXEodGhpcy5zZXNzaW9uR3JvdXBzLm1hcChzPT50aGlzLl9jb2xWYWx1ZShzLHQpKSkpLnJhbmdlKGEpfWVsc2V7aWYodGhpcy5vcHRpb25zLmNvbHVtbnNbdF0uc2NhbGU9PT0iTk9OX05VTUVSSUMiKXJldHVybiB0ZygpLmRvbWFpbih5My51bmlxKHRoaXMuc2Vzc2lvbkdyb3Vwcy5tYXAobz0+dGhpcy5fY29sVmFsdWUobyx0KSkuc29ydCgpKSkucmFuZ2UocikucGFkZGluZyguMSk7dGhyb3ciVW5rbm93biBzY2FsZSBmb3IgY29sdW1uOiAiK3QrIi4gb3B0aW9uczogIit0aGlzLm9wdGlvbnN9fV9jb2xWYWx1ZSh0LHIpe3JldHVybiBGSCh0aGlzLnZpc2libGVTY2hlbWEsdCxyKX1fbWV0cmljVmFsdWUodCxyKXtyZXR1cm4gekgodGhpcy52aXNpYmxlU2NoZW1hLHQscil9X2NvbEV4dGVudCh0KXtyZXR1cm4gZ1AodGhpcy52aXNpYmxlU2NoZW1hLHRoaXMuc2Vzc2lvbkdyb3Vwcyx0KX19OyRvLnRlbXBsYXRlPVFgCiAgICA8ZGl2IGlkPSJjb250YWluZXIiPgogICAgICA8c3ZnIGlkPSJzdmciPjwvc3ZnPgogICAgPC9kaXY+CgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgIH0KICAgICAgc3ZnIHsKICAgICAgICBmb250OiAxMHB4IHNhbnMtc2VyaWY7CiAgICAgIH0KCiAgICAgIHRleHQgewogICAgICAgIGZpbGw6IGN1cnJlbnRDb2xvcjsKICAgICAgfQoKICAgICAgLmZyYW1lIHJlY3QgewogICAgICAgIHN0cm9rZTogY3VycmVudENvbG9yOwogICAgICB9CgogICAgICAvKiBUaGUgY2xvc2VzdCBkYXRhIHBvaW50IG1hcmtlciB0byB0aGUgbW91c2UgcG9pbnRlci4gV2UgdXNlICFpbXBvcnRhbnQKICAgICAgICAgdG8gb3ZlcnJpZGUgdGhlIGlubGluZSBzdHlsZSB0aGF0IHNldHMgdGhlIHJlZ3VsYXIgc3R5bGUgb2YgYSBtYXJrZXIuCiAgICAgICovCiAgICAgIC5jbG9zZXN0LW1hcmtlciB7CiAgICAgICAgcjogNiAhaW1wb3J0YW50OwogICAgICB9CgogICAgICAvKiBUaGUgY3VycmVudGx5IHNlbGVjdGVkIGRhdGEgcG9pbnQgbWFya2VyLiBXZSB1c2UgIWltcG9ydGFudCB0bwogICAgICAgICBvdmVycmlkZSB0aGUgaW5saW5lIHN0eWxlIHRoYXQgc2V0cyB0aGUgcmVndWxhciBzdHlsZSBvZiBhIG1hcmtlci4gKi8KICAgICAgLnNlbGVjdGVkLW1hcmtlciB7CiAgICAgICAgcjogNiAhaW1wb3J0YW50OwogICAgICAgIGZpbGw6ICMwZjAgIWltcG9ydGFudDsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLCRvLnByb3RvdHlwZSwidmlzaWJsZVNjaGVtYSIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sJG8ucHJvdG90eXBlLCJzZXNzaW9uR3JvdXBzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLCRvLnByb3RvdHlwZSwib3B0aW9ucyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sJG8ucHJvdG90eXBlLCJzZWxlY3RlZFNlc3Npb25Hcm91cCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdCxub3RpZnk6ITB9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sJG8ucHJvdG90eXBlLCJjbG9zZXN0U2Vzc2lvbkdyb3VwIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEhUTUxFbGVtZW50KV0sJG8ucHJvdG90eXBlLCJfY29udGFpbmVyIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLCRvLnByb3RvdHlwZSwiX3N2ZyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSwkby5wcm90b3R5cGUsIndpZHRoIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLCRvLnByb3RvdHlwZSwiaGVpZ2h0Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLCRvLnByb3RvdHlwZSwiX2JydXNoZWRDZWxsSW5kZXgiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sJG8ucHJvdG90eXBlLCJfYnJ1c2hTZWxlY3Rpb24iLHZvaWQgMCk7RShbQnQoInNlc3Npb25Hcm91cHMuKiIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sJG8ucHJvdG90eXBlLCJfc2Vzc2lvbkdyb3Vwc0NoYW5nZWQiLG51bGwpO0UoW0J0KCJ2aXNpYmxlU2NoZW1hLioiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLCRvLnByb3RvdHlwZSwiX3Zpc2libGVTY2hlbWFDaGFuZ2VkIixudWxsKTtFKFtCdCgib3B0aW9ucy4qIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSwkby5wcm90b3R5cGUsIl9yZWRyYXciLG51bGwpOyRvPUUoW3l0KCJ0Zi1ocGFyYW1zLXNjYXR0ZXItcGxvdC1tYXRyaXgtcGxvdCIpXSwkbyk7dmFyIGZ2PWNsYXNzIGV4dGVuZHMgbXR7X2Nsb3Nlc3RPclNlbGVjdGVkKHQscil7cmV0dXJuIHQhPT1udWxsP3Q6cn19O2Z2LnRlbXBsYXRlPVFgCiAgICA8aHBhcmFtcy1zcGxpdC1sYXlvdXQgb3JpZW50YXRpb249InZlcnRpY2FsIj4KICAgICAgPCEtLSBDb250cm9scyBiZWhhdmlvciBvZiB0aGUgc2NhdHRlciBwbG90IG1hdHJpeAogICAgICAgICAgICAgb3V0cHV0cyB0aGUgY29uZmlndXJlZCBvcHRpb25zIHRvIHRoZSBfb3B0aW9ucyBwcm9wZXJ0eS4gLS0+CiAgICAgIDx0Zi1ocGFyYW1zLXNjYWxlLWFuZC1jb2xvci1jb250cm9scwogICAgICAgIHNsb3Q9ImNvbnRlbnQiCiAgICAgICAgY2xhc3M9InNlY3Rpb24iCiAgICAgICAgaWQ9ImNvbnRyb2xzIgogICAgICAgIGNvbmZpZ3VyYXRpb249IltbY29uZmlndXJhdGlvbl1dIgogICAgICAgIHNlc3Npb24tZ3JvdXBzPSJbW3Nlc3Npb25Hcm91cHNdXSIKICAgICAgICBvcHRpb25zPSJ7e19vcHRpb25zfX0iCiAgICAgID4KICAgICAgPC90Zi1ocGFyYW1zLXNjYWxlLWFuZC1jb2xvci1jb250cm9scz4KICAgICAgPCEtLSBUaGUgYWN0dWFsIHNjYXR0ZXIgcGxvdCBtYXRyaXggLS0+CiAgICAgIDx0Zi1ocGFyYW1zLXNjYXR0ZXItcGxvdC1tYXRyaXgtcGxvdAogICAgICAgIHNsb3Q9ImNvbnRlbnQiCiAgICAgICAgY2xhc3M9InNlY3Rpb24iCiAgICAgICAgaWQ9InBsb3QiCiAgICAgICAgdmlzaWJsZS1zY2hlbWE9IltbY29uZmlndXJhdGlvbi52aXNpYmxlU2NoZW1hXV0iCiAgICAgICAgc2Vzc2lvbi1ncm91cHM9Iltbc2Vzc2lvbkdyb3Vwc11dIgogICAgICAgIHNlbGVjdGVkLXNlc3Npb24tZ3JvdXA9Int7X3NlbGVjdGVkR3JvdXB9fSIKICAgICAgICBjbG9zZXN0LXNlc3Npb24tZ3JvdXA9Int7X2Nsb3Nlc3RHcm91cH19IgogICAgICAgIG9wdGlvbnM9IltbX29wdGlvbnNdXSIKICAgICAgPgogICAgICA8L3RmLWhwYXJhbXMtc2NhdHRlci1wbG90LW1hdHJpeC1wbG90PgogICAgICA8dGYtaHBhcmFtcy1zZXNzaW9uLWdyb3VwLXZhbHVlcwogICAgICAgIHNsb3Q9ImNvbnRlbnQiCiAgICAgICAgY2xhc3M9InNlY3Rpb24iCiAgICAgICAgaWQ9InZhbHVlcyIKICAgICAgICB2aXNpYmxlLXNjaGVtYT0iW1tjb25maWd1cmF0aW9uLnZpc2libGVTY2hlbWFdXSIKICAgICAgICBzZXNzaW9uLWdyb3VwPSJbW19jbG9zZXN0T3JTZWxlY3RlZCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgX2Nsb3Nlc3RHcm91cCwgX3NlbGVjdGVkR3JvdXApXV0iCiAgICAgID4KICAgICAgPC90Zi1ocGFyYW1zLXNlc3Npb24tZ3JvdXAtdmFsdWVzPgogICAgICA8IS0tIFNob3dzIHNlc3Npb24gZ3JvdXAgZGV0YWlscyBmb3IgdGhlIGNsaWNrZWQgbWFya2VyLiAtLT4KICAgICAgPHRmLWhwYXJhbXMtc2Vzc2lvbi1ncm91cC1kZXRhaWxzCiAgICAgICAgc2xvdD0iY29udGVudCIKICAgICAgICBjbGFzcz0ic2VjdGlvbiIKICAgICAgICBpZD0iZGV0YWlscyIKICAgICAgICBiYWNrZW5kPSJbW2JhY2tlbmRdXSIKICAgICAgICBleHBlcmltZW50LW5hbWU9IltbZXhwZXJpbWVudE5hbWVdXSIKICAgICAgICBzZXNzaW9uLWdyb3VwPSJbW19zZWxlY3RlZEdyb3VwXV0iCiAgICAgICAgdmlzaWJsZS1zY2hlbWE9IltbY29uZmlndXJhdGlvbi52aXNpYmxlU2NoZW1hXV0iCiAgICAgID4KICAgICAgPC90Zi1ocGFyYW1zLXNlc3Npb24tZ3JvdXAtZGV0YWlscz4KICAgIDwvaHBhcmFtcy1zcGxpdC1sYXlvdXQ+CiAgICA8c3R5bGU+CiAgICAgIC5zZWN0aW9uIHsKICAgICAgICBwYWRkaW5nOiAxMHB4OwogICAgICB9CiAgICAgICNjb250cm9scyB7CiAgICAgICAgZmxleC1ncm93OiAwOwogICAgICAgIGZsZXgtc2hyaW5rOiAwOwogICAgICAgIGZsZXgtYmFzaXM6IGF1dG87CiAgICAgICAgaGVpZ2h0OiBhdXRvOwogICAgICAgIG92ZXJmbG93LXk6IGF1dG87CiAgICAgICAgbWF4LWhlaWdodDogZml0LWNvbnRlbnQ7CiAgICAgIH0KICAgICAgI3Bsb3QgewogICAgICAgIGZsZXgtZ3JvdzogMTsKICAgICAgICBmbGV4LXNocmluazogMTsKICAgICAgICBmbGV4LWJhc2lzOiBhdXRvOwogICAgICAgIGhlaWdodDogYXV0bzsKICAgICAgICBvdmVyZmxvdy15OiBhdXRvOwogICAgICAgIG1heC1oZWlnaHQ6IGZpdC1jb250ZW50OwogICAgICB9CiAgICAgICN2YWx1ZXMgewogICAgICAgIGZsZXgtZ3JvdzogMDsKICAgICAgICBmbGV4LXNocmluazogMDsKICAgICAgICBmbGV4LWJhc2lzOiBhdXRvOwogICAgICAgIGhlaWdodDogMTE1cHg7CiAgICAgICAgb3ZlcmZsb3cteTogYXV0bzsKICAgICAgICBtYXgtaGVpZ2h0OiBmaXQtY29udGVudDsKICAgICAgfQogICAgICAjZGV0YWlscyB7CiAgICAgICAgZmxleC1ncm93OiAwOwogICAgICAgIGZsZXgtc2hyaW5rOiAxOwogICAgICAgIGZsZXgtYmFzaXM6IGF1dG87CiAgICAgICAgaGVpZ2h0OiBhdXRvOwogICAgICAgIG92ZXJmbG93LXk6IGF1dG87CiAgICAgICAgbWF4LWhlaWdodDogZml0LWNvbnRlbnQ7CiAgICAgIH0KICAgICAgdmFhZGluLXNwbGl0LWxheW91dCB7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sZnYucHJvdG90eXBlLCJiYWNrZW5kIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLGZ2LnByb3RvdHlwZSwiZXhwZXJpbWVudE5hbWUiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sZnYucHJvdG90eXBlLCJjb25maWd1cmF0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxmdi5wcm90b3R5cGUsInNlc3Npb25Hcm91cHMiLHZvaWQgMCk7ZnY9RShbeXQoInRmLWhwYXJhbXMtc2NhdHRlci1wbG90LW1hdHJpeC12aWV3IildLGZ2KTt2YXIgWGY9Y2xhc3MgZXh0ZW5kcyBtdHtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fc2VsZWN0ZWRUYWI9MH19O1hmLnRlbXBsYXRlPVFgCiAgICA8cGFwZXItaGVhZGVyLXBhbmVsPgogICAgICA8cGFwZXItdG9vbGJhciBzbG90PSJoZWFkZXIiIGNsYXNzPSJ0YWItYmFyIj4KICAgICAgICA8cGFwZXItdGFicyBzZWxlY3RlZD0ie3tfc2VsZWN0ZWRUYWJ9fSIgc2xvdD0idG9wIj4KICAgICAgICAgIDwhLS0gdmlldy1pZCBjYW4gYmUgdXNlZCBieSBpbnRlZ3JhdGlvbiB0ZXN0cyB0byBsb2NhdGUgYSB0YWIuCiAgICAgICAgICAgICAgIEl0IHNob3VsZCBiZSB0aGUgbmFtZSBvZiB0aGUgcm9vdCBlbGVtZW50IGltcGxlbWVudGluZyB0aGUgdmlldwogICAgICAgICAgICAgICB3aXRob3V0IHRoZSAndGYtaHBhcmFtcy0nIHByZWZpeC4gLS0+CiAgICAgICAgICA8cGFwZXItdGFiIHZpZXctaWQ9InRhYmxlLXZpZXciPiBUQUJMRSBWSUVXIDwvcGFwZXItdGFiPgogICAgICAgICAgPHBhcGVyLXRhYiB2aWV3LWlkPSJwYXJhbGxlbC1jb29yZHMtdmlldyI+CiAgICAgICAgICAgIFBBUkFMTEVMIENPT1JESU5BVEVTIFZJRVcKICAgICAgICAgIDwvcGFwZXItdGFiPgogICAgICAgICAgPHBhcGVyLXRhYiB2aWV3LWlkPSJzY2F0dGVyLXBsb3QtbWF0cml4LXZpZXciPgogICAgICAgICAgICBTQ0FUVEVSIFBMT1QgTUFUUklYIFZJRVcKICAgICAgICAgIDwvcGFwZXItdGFiPgogICAgICAgICAgPGRpdiBjbGFzcz0iaGVscC1hbmQtZmVlZGJhY2siPgogICAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbYnVnUmVwb3J0VXJsXV0iPgogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmJD0iW1tidWdSZXBvcnRVcmxdXSIKICAgICAgICAgICAgICAgIHRhcmdldD0iX2JsYW5rIgogICAgICAgICAgICAgICAgcmVsPSJub29wZW5lciBub3JlZmVycmVyIgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDxwYXBlci1idXR0b24KICAgICAgICAgICAgICAgICAgaWQ9ImJ1Zy1yZXBvcnQiCiAgICAgICAgICAgICAgICAgIHJhaXNlZAogICAgICAgICAgICAgICAgICB0aXRsZT0iU2VuZCBhIGJ1ZyByZXBvcnQgb3IgZmVhdHVyZSByZXF1ZXN0IgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICBCdWcgUmVwb3J0IC8gRmVhdHVyZSBSZXF1ZXN0CiAgICAgICAgICAgICAgICA8L3BhcGVyLWJ1dHRvbj4KICAgICAgICAgICAgICA8L2E+CiAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1toZWxwVXJsXV0iPgogICAgICAgICAgICAgIDxhIGhyZWYkPSJbW2hlbHBVcmxdXSIgdGFyZ2V0PSJfYmxhbmsiIHJlbD0ibm9vcGVuZXIgbm9yZWZlcnJlciI+CiAgICAgICAgICAgICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICAgICAgICAgICAgaWNvbj0iaGVscC1vdXRsaW5lIgogICAgICAgICAgICAgICAgICB0aXRsZT0iVmlldyBkb2N1bWVudGF0aW9uIgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgPC9wYXBlci1pY29uLWJ1dHRvbj4KICAgICAgICAgICAgICA8L2E+CiAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L3BhcGVyLXRhYnM+CiAgICAgIDwvcGFwZXItdG9vbGJhcj4KICAgICAgPGlyb24tcGFnZXMgc2VsZWN0ZWQ9IltbX3NlbGVjdGVkVGFiXV0iIGNsYXNzPSJmaXQgdGFiLXZpZXciPgogICAgICAgIDxkaXYgaWQ9IjAiIGNsYXNzPSJ0YWIiPgogICAgICAgICAgPHRmLWhwYXJhbXMtdGFibGUtdmlldwogICAgICAgICAgICBiYWNrZW5kPSJbW2JhY2tlbmRdXSIKICAgICAgICAgICAgZXhwZXJpbWVudC1uYW1lPSJbW2V4cGVyaW1lbnROYW1lXV0iCiAgICAgICAgICAgIHZpc2libGUtc2NoZW1hPSJbW2NvbmZpZ3VyYXRpb24udmlzaWJsZVNjaGVtYV1dIgogICAgICAgICAgICBzZXNzaW9uLWdyb3Vwcz0iW1tzZXNzaW9uR3JvdXBzXV0iCiAgICAgICAgICAgIGVuYWJsZS1zaG93LW1ldHJpY3MKICAgICAgICAgID4KICAgICAgICAgIDwvdGYtaHBhcmFtcy10YWJsZS12aWV3PgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgaWQ9IjEiIGNsYXNzPSJ0YWIiPgogICAgICAgICAgPHRmLWhwYXJhbXMtcGFyYWxsZWwtY29vcmRzLXZpZXcKICAgICAgICAgICAgYmFja2VuZD0iW1tiYWNrZW5kXV0iCiAgICAgICAgICAgIGV4cGVyaW1lbnQtbmFtZT0iW1tleHBlcmltZW50TmFtZV1dIgogICAgICAgICAgICBjb25maWd1cmF0aW9uPSJbW2NvbmZpZ3VyYXRpb25dXSIKICAgICAgICAgICAgc2Vzc2lvbi1ncm91cHM9Iltbc2Vzc2lvbkdyb3Vwc11dIgogICAgICAgICAgPgogICAgICAgICAgPC90Zi1ocGFyYW1zLXBhcmFsbGVsLWNvb3Jkcy12aWV3PgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgaWQ9IjIiIGNsYXNzPSJ0YWIiPgogICAgICAgICAgPHRmLWhwYXJhbXMtc2NhdHRlci1wbG90LW1hdHJpeC12aWV3CiAgICAgICAgICAgIGJhY2tlbmQ9IltbYmFja2VuZF1dIgogICAgICAgICAgICBleHBlcmltZW50LW5hbWU9IltbZXhwZXJpbWVudE5hbWVdXSIKICAgICAgICAgICAgY29uZmlndXJhdGlvbj0iW1tjb25maWd1cmF0aW9uXV0iCiAgICAgICAgICAgIHNlc3Npb24tZ3JvdXBzPSJbW3Nlc3Npb25Hcm91cHNdXSIKICAgICAgICAgID4KICAgICAgICAgIDwvdGYtaHBhcmFtcy1zY2F0dGVyLXBsb3QtbWF0cml4LXZpZXc+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvaXJvbi1wYWdlcz4KICAgIDwvcGFwZXItaGVhZGVyLXBhbmVsPgoKICAgIDxzdHlsZT4KICAgICAgLnRhYi12aWV3IHsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgIH0KICAgICAgLnRhYi1iYXIgewogICAgICAgIG92ZXJmbG93LXk6IGF1dG87CiAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigKICAgICAgICAgIC0tdGItdG9vbGJhci1iYWNrZ3JvdW5kLWNvbG9yLAogICAgICAgICAgdmFyKC0tdGItb3JhbmdlLXN0cm9uZykKICAgICAgICApOwogICAgICB9CiAgICAgIC50YWIgewogICAgICAgIGhlaWdodDogMTAwJTsKICAgICAgfQogICAgICBwYXBlci10YWJzIHsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICAgIC0tcGFwZXItdGFicy1zZWxlY3Rpb24tYmFyLWNvbG9yOiB3aGl0ZTsKICAgICAgICAtLXBhcGVyLXRhYnMtY29udGVudDogewogICAgICAgICAgLXdlYmtpdC1mb250LXNtb290aGluZzogYW50aWFsaWFzZWQ7CiAgICAgICAgfQogICAgICB9CiAgICAgIHRmLWhwYXJhbXMtdGFibGUtdmlldyB7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICB9CiAgICAgIC5oZWxwLWFuZC1mZWVkYmFjayB7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7IC8qIEVuc3VyZSB0aGF0IGljb25zIHN0YXkgYWxpZ25lZCAqLwogICAgICAgIGp1c3RpZnktY29udGVudDogZmxleC1lbmQ7CiAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgICB0ZXh0LWFsaWduOiByaWdodDsKICAgICAgICBjb2xvcjogd2hpdGU7CiAgICAgIH0KICAgICAgI2J1Zy1yZXBvcnQgewogICAgICAgIGJvcmRlcjogc29saWQgYmxhY2s7CiAgICAgICAgYmFja2dyb3VuZDogcmVkOwogICAgICAgIHdoaXRlLXNwYWNlOiBub3JtYWw7CiAgICAgICAgd29yZC1icmVhazogYnJlYWstd29yZHM7CiAgICAgICAgZm9udC1zaXplOiAxMnB4OwogICAgICAgIG1heC13aWR0aDogMTUwcHg7CiAgICAgICAgdGV4dC1hbGlnbjogbGVmdDsKICAgICAgfQogICAgICAuaGVscC1hbmQtZmVlZGJhY2sgYSB7CiAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFhmLnByb3RvdHlwZSwiYmFja2VuZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxYZi5wcm90b3R5cGUsImhlbHBVcmwiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sWGYucHJvdG90eXBlLCJidWdSZXBvcnRVcmwiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sWGYucHJvdG90eXBlLCJleHBlcmltZW50TmFtZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxYZi5wcm90b3R5cGUsImNvbmZpZ3VyYXRpb24iLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLFhmLnByb3RvdHlwZSwic2Vzc2lvbkdyb3VwcyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxYZi5wcm90b3R5cGUsIl9zZWxlY3RlZFRhYiIsdm9pZCAwKTtYZj1FKFt5dCgidGYtaHBhcmFtcy1zZXNzaW9ucy1wYW5lIildLFhmKTt2YXIgcmg9Y2xhc3MgZXh0ZW5kcyBHdChtdCl7cmVsb2FkKCl7dGhpcy4kWyJxdWVyeS1wYW5lIl0ucmVsb2FkKCl9fTtyaC50ZW1wbGF0ZT1RYAogICAgPGhwYXJhbXMtc3BsaXQtbGF5b3V0PgogICAgICA8ZGl2IHNsb3Q9ImNvbnRlbnQiIGNsYXNzPSJzaWRlYmFyIj4KICAgICAgICA8dGYtaHBhcmFtcy1xdWVyeS1wYW5lCiAgICAgICAgICBpZD0icXVlcnktcGFuZSIKICAgICAgICAgIGJhY2tlbmQ9IltbYmFja2VuZF1dIgogICAgICAgICAgZXhwZXJpbWVudC1uYW1lPSJbW2V4cGVyaW1lbnROYW1lXV0iCiAgICAgICAgICBjb25maWd1cmF0aW9uPSJ7e19jb25maWd1cmF0aW9ufX0iCiAgICAgICAgICBzZXNzaW9uLWdyb3Vwcz0ie3tfc2Vzc2lvbkdyb3Vwc319IgogICAgICAgICAgZGF0YS1sb2FkZWQtd2l0aC1ub24tZW1wdHktaHBhcmFtcz0ie3tfZGF0YUxvYWRlZFdpdGhOb25FbXB0eUhwYXJhbXN9fSIKICAgICAgICAgIGRhdGEtbG9hZGVkLXdpdGgtZW1wdHktaHBhcmFtcz0ie3tfZGF0YUxvYWRlZFdpdGhFbXB0eUhwYXJhbXN9fSIKICAgICAgICA+CiAgICAgICAgPC90Zi1ocGFyYW1zLXF1ZXJ5LXBhbmU+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IHNsb3Q9ImNvbnRlbnQiIGNsYXNzPSJjZW50ZXIiPgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfZGF0YUxvYWRlZFdpdGhFbXB0eUhwYXJhbXNdXSI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJuby1kYXRhLXdhcm5pbmciPgogICAgICAgICAgICA8aDM+Tm8gaHBhcmFtcyBkYXRhIHdhcyBmb3VuZC48L2gzPgogICAgICAgICAgICA8cD5Qcm9iYWJsZSBjYXVzZXM6PC9wPgogICAgICAgICAgICA8dWw+CiAgICAgICAgICAgICAgPGxpPllvdSBoYXZlbuKAmXQgd3JpdHRlbiBhbnkgaHBhcmFtcyBkYXRhIHRvIHlvdXIgZXZlbnQgZmlsZXMuPC9saT4KICAgICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgICBFdmVudCBmaWxlcyBhcmUgc3RpbGwgYmVpbmcgbG9hZGVkICh0cnkgcmVsb2FkaW5nIHRoaXMgcGFnZSkuCiAgICAgICAgICAgICAgPC9saT4KICAgICAgICAgICAgICA8bGk+VGVuc29yQm9hcmQgY2Fu4oCZdCBmaW5kIHlvdXIgZXZlbnQgZmlsZXMuPC9saT4KICAgICAgICAgICAgPC91bD4KCiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgIElmIHlvdeKAmXJlIG5ldyB0byB1c2luZyBUZW5zb3JCb2FyZCwgYW5kIHdhbnQgdG8gZmluZCBvdXQgaG93IHRvCiAgICAgICAgICAgICAgYWRkIGRhdGEgYW5kIHNldCB1cCB5b3VyIGV2ZW50IGZpbGVzLCBjaGVjayBvdXQgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCIKICAgICAgICAgICAgICAgID5SRUFETUU8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIHBlcmhhcHMgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiCiAgICAgICAgICAgICAgICA+VGVuc29yQm9hcmQgdHV0b3JpYWw8L2EKICAgICAgICAgICAgICA+LgogICAgICAgICAgICA8L3A+CgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICBJZiB5b3UgdGhpbmsgVGVuc29yQm9hcmQgaXMgY29uZmlndXJlZCBwcm9wZXJseSwgcGxlYXNlIHNlZQogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQjbXktdGVuc29yYm9hcmQtaXNudC1zaG93aW5nLWFueS1kYXRhLXdoYXRzLXdyb25nIgogICAgICAgICAgICAgICAgPnRoZSBzZWN0aW9uIG9mIHRoZSBSRUFETUUgZGV2b3RlZCB0byBtaXNzaW5nIGRhdGEgcHJvYmxlbXM8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIGNvbnNpZGVyIGZpbGluZyBhbiBpc3N1ZSBvbiBHaXRIdWIuCiAgICAgICAgICAgIDwvcD4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvdGVtcGxhdGU+CgogICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfZGF0YUxvYWRlZFdpdGhOb25FbXB0eUhwYXJhbXNdXSI+CiAgICAgICAgICA8dGYtaHBhcmFtcy1zZXNzaW9ucy1wYW5lCiAgICAgICAgICAgIGlkPSJzZXNzaW9ucy1wYW5lIgogICAgICAgICAgICBiYWNrZW5kPSJbW2JhY2tlbmRdXSIKICAgICAgICAgICAgaGVscC11cmw9IltbaGVscFVybF1dIgogICAgICAgICAgICBidWctcmVwb3J0LXVybD0iW1tidWdSZXBvcnRVcmxdXSIKICAgICAgICAgICAgZXhwZXJpbWVudC1uYW1lPSJbW2V4cGVyaW1lbnROYW1lXV0iCiAgICAgICAgICAgIGNvbmZpZ3VyYXRpb249IltbX2NvbmZpZ3VyYXRpb25dXSIKICAgICAgICAgICAgc2Vzc2lvbi1ncm91cHM9IltbX3Nlc3Npb25Hcm91cHNdXSIKICAgICAgICAgID4KICAgICAgICAgIDwvdGYtaHBhcmFtcy1zZXNzaW9ucy1wYW5lPgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDwvZGl2PgogICAgPC9ocGFyYW1zLXNwbGl0LWxheW91dD4KICAgIDxzdHlsZT4KICAgICAgaHBhcmFtcy1zcGxpdC1sYXlvdXQgewogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICB9CgogICAgICAuc2lkZWJhciB7CiAgICAgICAgd2lkdGg6IDIwJTsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgb3ZlcmZsb3c6IGF1dG87CiAgICAgICAgZmxleC1ncm93OiAwOwogICAgICAgIGZsZXgtc2hyaW5rOiAwOwogICAgICAgIG1pbi13aWR0aDogMTAlOwogICAgICB9CgogICAgICAuY2VudGVyIHsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgb3ZlcmZsb3cteTogYXV0bzsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgICAgZmxleC1zaHJpbms6IDE7CiAgICAgICAgd2lkdGg6IDgwJTsKICAgICAgfQoKICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IHJvdzsKICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIH0KCiAgICAgIC5uby1kYXRhLXdhcm5pbmcgewogICAgICAgIG1heC13aWR0aDogNTQwcHg7CiAgICAgICAgbWFyZ2luOiA4MHB4IGF1dG8gMCBhdXRvOwogICAgICB9CgogICAgICBhIHsKICAgICAgICBjb2xvcjogdmFyKC0tdGItbGluayk7CiAgICAgIH0KCiAgICAgIGE6dmlzaXRlZCB7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLWxpbmstdmlzaXRlZCk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIix1MyldLHJoLnByb3RvdHlwZSwiYmFja2VuZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxyaC5wcm90b3R5cGUsImV4cGVyaW1lbnROYW1lIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLHJoLnByb3RvdHlwZSwiaGVscFVybCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxyaC5wcm90b3R5cGUsImJ1Z1JlcG9ydFVybCIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxyaC5wcm90b3R5cGUsIl9jb25maWd1cmF0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxyaC5wcm90b3R5cGUsIl9zZXNzaW9uR3JvdXBzIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0scmgucHJvdG90eXBlLCJfZGF0YUxvYWRlZFdpdGhOb25FbXB0eUhwYXJhbXMiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxyaC5wcm90b3R5cGUsIl9kYXRhTG9hZGVkV2l0aEVtcHR5SHBhcmFtcyIsdm9pZCAwKTtyaD1FKFt5dCgidGYtaHBhcmFtcy1tYWluIildLHJoKTt2YXIgc2hyPW5ldyBVUkxTZWFyY2hQYXJhbXMod2luZG93LmxvY2F0aW9uLnNlYXJjaCkuZ2V0KCJ0ZW5zb3Jib2FyZENvbGFiIik9PT0idHJ1ZSIsbGhyPSJocGFyYW1zIixzVj1jbGFzcyBleHRlbmRzIEd0KG10KXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fYmFja2VuZD1uZXcgdTModmUoKS5wbHVnaW5Sb3V0ZShsaHIsIiIpLG5ldyBBZSxzaHIpfXJlbG9hZCgpe3RoaXMuJFsiaHBhcmFtcy1tYWluIl0ucmVsb2FkKCl9fTtzVi50ZW1wbGF0ZT1RYAogICAgPCEtLSBUZW5zb3JCb2FyZCBkb2VzIG5vdCBzcGVjaWZ5IGFuIGV4cGVyaW1lbnROYW1lLiBDdXJyZW50bHkgaXQgb25seQogICAgICAgICBzdXBwb3J0cyBvbmUgZXhwZXJpbWVudCBwZXIgaW52b2NhdGlvbi4gLS0+CiAgICA8dGYtaHBhcmFtcy1tYWluCiAgICAgIGlkPSJocGFyYW1zLW1haW4iCiAgICAgIGJhY2tlbmQ9IltbX2JhY2tlbmRdXSIKICAgICAgZXhwZXJpbWVudC1uYW1lPSIiCiAgICA+CiAgICA8L3RmLWhwYXJhbXMtbWFpbj4KICBgO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHNWLnByb3RvdHlwZSwiX2JhY2tlbmQiLHZvaWQgMCk7c1Y9RShbeXQoInRmLWhwYXJhbXMtZGFzaGJvYXJkIildLHNWKTt2YXIgcHY9RWUoT2UoKSwxKTt2YXIgX249Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuYWN0dWFsU2l6ZT0hMSx0aGlzLmJyaWdodG5lc3NBZGp1c3RtZW50PS41LHRoaXMuY29udHJhc3RQZXJjZW50YWdlPTAsdGhpcy5fbWV0YWRhdGFDYW5jZWxsZXI9bmV3IGFuLHRoaXMuX2ltYWdlQ2FuY2VsbGVyPW5ldyBhbix0aGlzLl9zdGVwcz1bXSx0aGlzLl9pc0ltYWdlTG9hZGluZz0hMX1nZXQgX3J1bkNvbG9yKCl7dmFyIHQ9dGhpcy5ydW47cmV0dXJuIGZuKHQpfWdldCBfaGFzQXRMZWFzdE9uZVN0ZXAoKXt2YXIgdD10aGlzLl9zdGVwcztyZXR1cm4hIXQmJnQubGVuZ3RoPjB9Z2V0IF9oYXNNdWx0aXBsZVN0ZXBzKCl7dmFyIHQ9dGhpcy5fc3RlcHM7cmV0dXJuISF0JiZ0Lmxlbmd0aD4xfWdldCBfY3VycmVudFN0ZXAoKXt2YXIgdD10aGlzLl9zdGVwcyxyPXRoaXMuX3N0ZXBJbmRleDtyZXR1cm4gdFtyXXx8bnVsbH1nZXQgX3N0ZXBWYWx1ZSgpe3ZhciB0PXRoaXMuX2N1cnJlbnRTdGVwO3JldHVybiB0P3Quc3RlcDowfWdldCBfY3VycmVudFdhbGxUaW1lKCl7dmFyIHQ9dGhpcy5fY3VycmVudFN0ZXA7cmV0dXJuIHQ/czIodC53YWxsX3RpbWUpOiIifWdldCBfbWF4U3RlcEluZGV4KCl7dmFyIHQ9dGhpcy5fc3RlcHM7cmV0dXJuIHQubGVuZ3RoLTF9Z2V0IF9zYW1wbGVUZXh0KCl7dmFyIHQ9dGhpcy5zYW1wbGU7cmV0dXJuYCR7dCsxfWB9Z2V0IF9oYXNNdWx0aXBsZVNhbXBsZXMoKXt2YXIgdD10aGlzLm9mU2FtcGxlcztyZXR1cm4gdD4xfV9nZXRBcmlhRXhwYW5kZWQoKXtyZXR1cm4gdGhpcy5hY3R1YWxTaXplPyJ0cnVlIjoiZmFsc2UifWF0dGFjaGVkKCl7dGhpcy5yZWxvYWQoKX1yZWxvYWQoKXtpZighdGhpcy5pc0F0dGFjaGVkKXJldHVybjt0aGlzLl9tZXRhZGF0YUNhbmNlbGxlci5jYW5jZWxBbGwoKTtsZXQgdD12ZSgpLHI9Q24odC5wbHVnaW5Sb3V0ZSgiaW1hZ2VzIiwiL2ltYWdlcyIpLHt0YWc6dGhpcy50YWcscnVuOnRoaXMucnVuLHNhbXBsZTp0aGlzLnNhbXBsZX0pLG49dGhpcy5fbWV0YWRhdGFDYW5jZWxsZXIuY2FuY2VsbGFibGUoaT0+e2lmKGkuY2FuY2VsbGVkKXJldHVybjtsZXQgYT1pLnZhbHVlLm1hcCh0aGlzLl9jcmVhdGVTdGVwRGF0dW0uYmluZCh0aGlzKSk7dGhpcy5zZXQoIl9zdGVwcyIsYSksdGhpcy5zZXQoIl9zdGVwSW5kZXgiLGEubGVuZ3RoLTEpfSk7dGhpcy5yZXF1ZXN0TWFuYWdlci5yZXF1ZXN0KHIpLnRoZW4obil9X2NyZWF0ZVN0ZXBEYXR1bSh0KXtsZXQgcj12ZSgpLnBsdWdpblJvdXRlKCJpbWFnZXMiLCIvaW5kaXZpZHVhbEltYWdlIik7cmV0dXJuIHI9Q24ocix7dHM6dC53YWxsX3RpbWV9KSxyKz0iJiIrdC5xdWVyeSx7d2FsbF90aW1lOm5ldyBEYXRlKHQud2FsbF90aW1lKjFlMyksc3RlcDp0LnN0ZXAsdXJsOnJ9fV91cGRhdGVJbWFnZVVybCgpe3ZhciB0PXRoaXMuX2N1cnJlbnRTdGVwLHI9dGhpcy5icmlnaHRuZXNzQWRqdXN0bWVudCxuPXRoaXMuY29udHJhc3RQZXJjZW50YWdlO2lmKCF0KXJldHVybjtsZXQgaT1uZXcgSW1hZ2U7dGhpcy5faW1hZ2VDYW5jZWxsZXIuY2FuY2VsQWxsKCksaS5vbmxvYWQ9aS5vbmVycm9yPXRoaXMuX2ltYWdlQ2FuY2VsbGVyLmNhbmNlbGxhYmxlKG89PntpZihvLmNhbmNlbGxlZClyZXR1cm47bGV0IGE9dGhpcy4kJCgiI21haW4taW1hZ2UtY29udGFpbmVyIik7YSYmKGEudGV4dENvbnRlbnQ9IiIsenQoYSkuYXBwZW5kQ2hpbGQoaSkpLHRoaXMuc2V0KCJfaXNJbWFnZUxvYWRpbmciLCExKX0pLmJpbmQodGhpcyksaS5zdHlsZS5maWx0ZXI9YGNvbnRyYXN0KCR7bn0lKSBgLGkuc3R5bGUuZmlsdGVyKz1gYnJpZ2h0bmVzcygke3J9KWAsdGhpcy5zZXQoIl9pc0ltYWdlTG9hZGluZyIsITApLGkuc3JjPXQudXJsfV9oYW5kbGVUYXAodCl7dGhpcy5zZXQoImFjdHVhbFNpemUiLCF0aGlzLmFjdHVhbFNpemUpfV90b0xvY2FsZVN0cmluZyh0KXtyZXR1cm4gdC50b0xvY2FsZVN0cmluZygpfX07X24udGVtcGxhdGU9UWAKICAgIDx0Zi1jYXJkLWhlYWRpbmcKICAgICAgdGFnPSJbW3RhZ11dIgogICAgICBydW49IltbcnVuXV0iCiAgICAgIGRpc3BsYXktbmFtZT0iW1t0YWdNZXRhZGF0YS5kaXNwbGF5TmFtZV1dIgogICAgICBkZXNjcmlwdGlvbj0iW1t0YWdNZXRhZGF0YS5kZXNjcmlwdGlvbl1dIgogICAgICBjb2xvcj0iW1tfcnVuQ29sb3JdXSIKICAgID4KICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19oYXNNdWx0aXBsZVNhbXBsZXNdXSI+CiAgICAgICAgPGRpdj5zYW1wbGU6IFtbX3NhbXBsZVRleHRdXSBvZiBbW29mU2FtcGxlc11dPC9kaXY+CiAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfaGFzQXRMZWFzdE9uZVN0ZXBdXSI+CiAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1yb3ciPgogICAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1sYWJlbCI+CiAgICAgICAgICAgIHN0ZXAKICAgICAgICAgICAgPHNwYW4gc3R5bGU9ImZvbnQtd2VpZ2h0OiBib2xkIgogICAgICAgICAgICAgID5bW190b0xvY2FsZVN0cmluZyhfc3RlcFZhbHVlKV1dPC9zcGFuCiAgICAgICAgICAgID4KICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1sYWJlbCBoZWFkaW5nLXJpZ2h0IGRhdGV0aW1lIj4KICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19jdXJyZW50V2FsbFRpbWVdXSI+CiAgICAgICAgICAgICAgW1tfY3VycmVudFdhbGxUaW1lXV0KICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPGRpdiBjbGFzcz0ibGFiZWwgcmlnaHQiPgogICAgICAgICAgICA8cGFwZXItc3Bpbm5lci1saXRlIGFjdGl2ZSBoaWRkZW4kPSJbWyFfaXNJbWFnZUxvYWRpbmddXSI+CiAgICAgICAgICAgIDwvcGFwZXItc3Bpbm5lci1saXRlPgogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLWlmIiBpZj0iW1tfaGFzTXVsdGlwbGVTdGVwc11dIj4KICAgICAgICA8ZGl2PgogICAgICAgICAgPHBhcGVyLXNsaWRlcgogICAgICAgICAgICBpZD0ic3RlcHMiCiAgICAgICAgICAgIGltbWVkaWF0ZS12YWx1ZT0ie3tfc3RlcEluZGV4fX0iCiAgICAgICAgICAgIG1heD0iW1tfbWF4U3RlcEluZGV4XV0iCiAgICAgICAgICAgIG1heC1tYXJrZXJzPSJbW19tYXhTdGVwSW5kZXhdXSIKICAgICAgICAgICAgc25hcHMKICAgICAgICAgICAgc3RlcD0iMSIKICAgICAgICAgICAgdmFsdWU9Int7X3N0ZXBJbmRleH19IgogICAgICAgICAgPjwvcGFwZXItc2xpZGVyPgogICAgICAgIDwvZGl2PgogICAgICA8L3RlbXBsYXRlPgogICAgPC90Zi1jYXJkLWhlYWRpbmc+CgogICAgPCEtLSBTZW1hbnRpY2FsbHkgYSBidXR0b24gYnV0IDxpbWc+IGluc2lkZSBhIDxidXR0b24+IGRpc2FsbG93cyB1c2VyIHRvIGRvCiAgICBhbiBpbnRlcmVzdGluZyBvcGVyYXRpb24gbGlrZSAiQ29weSBJbWFnZSIgaW4gbm9uLUNocm9taXVtIGJyb3dzZXJzLiAtLT4KICAgIDxhCiAgICAgIGlkPSJtYWluLWltYWdlLWNvbnRhaW5lciIKICAgICAgcm9sZT0iYnV0dG9uIgogICAgICBhcmlhLWxhYmVsPSJUb2dnbGUgYWN0dWFsIHNpemUiCiAgICAgIGFyaWEtZXhwYW5kZWQkPSJbW19nZXRBcmlhRXhwYW5kZWQoYWN0dWFsU2l6ZSldXSIKICAgICAgb24tdGFwPSJfaGFuZGxlVGFwIgogICAgPjwvYT4KCiAgICA8c3R5bGUgaW5jbHVkZT0idGYtY2FyZC1oZWFkaW5nLXN0eWxlIj4KICAgICAgLyoqIE1ha2UgYnV0dG9uIGEgZGl2LiAqLwogICAgICBidXR0b24gewogICAgICAgIHdpZHRoOiAxMDAlOwogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgIGJhY2tncm91bmQ6IG5vbmU7CiAgICAgICAgYm9yZGVyOiAwOwogICAgICAgIHBhZGRpbmc6IDA7CiAgICAgIH0KCiAgICAgIC8qKiBGaXJlZm94OiBHZXQgcmlkIG9mIGRvdHRlZCBsaW5lIGluc2lkZSBidXR0b24uICovCiAgICAgIGJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lciB7CiAgICAgICAgYm9yZGVyOiAwOwogICAgICAgIHBhZGRpbmc6IDA7CiAgICAgIH0KCiAgICAgIC8qKiBGaXJlZm94OiBTaW11bGF0ZSBDaHJvbWUncyBvdXRlciBnbG93IG9uIGJ1dHRvbiB3aGVuIGZvY3VzZWQuICovCiAgICAgIGJ1dHRvbjotbW96LWZvY3VzcmluZyB7CiAgICAgICAgb3V0bGluZTogbm9uZTsKICAgICAgICBib3gtc2hhZG93OiAwcHggMHB4IDFweCAycHggSGlnaGxpZ2h0OwogICAgICB9CgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogYmxvY2s7CiAgICAgICAgd2lkdGg6IDM1MHB4OwogICAgICAgIGhlaWdodDogYXV0bzsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgICAgbWFyZ2luOiAwIDE1cHggNDBweCAwOwogICAgICAgIG92ZXJmbG93LXg6IGF1dG87CiAgICAgIH0KCiAgICAgIC8qKiBXaGVuIGFjdHVhbCBzaXplIHNob3duIGlzIG9uLCB1c2UgdGhlIGFjdHVhbCBpbWFnZSB3aWR0aC4gKi8KICAgICAgOmhvc3QoW2FjdHVhbC1zaXplXSkgewogICAgICAgIG1heC13aWR0aDogMTAwJTsKICAgICAgICB3aWR0aDogYXV0bzsKICAgICAgfQoKICAgICAgOmhvc3QoW2FjdHVhbC1zaXplXSkgI21haW4taW1hZ2UtY29udGFpbmVyIHsKICAgICAgICBtYXgtaGVpZ2h0OiBub25lOwogICAgICAgIHdpZHRoOiBhdXRvOwogICAgICB9CgogICAgICA6aG9zdChbYWN0dWFsLXNpemVdKSAjbWFpbi1pbWFnZS1jb250YWluZXIgaW1nIHsKICAgICAgICB3aWR0aDogYXV0bzsKICAgICAgfQoKICAgICAgcGFwZXItc3Bpbm5lci1saXRlIHsKICAgICAgICB3aWR0aDogMTRweDsKICAgICAgICBoZWlnaHQ6IDE0cHg7CiAgICAgICAgdmVydGljYWwtYWxpZ246IHRleHQtYm90dG9tOwogICAgICAgIC0tcGFwZXItc3Bpbm5lci1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgIH0KCiAgICAgICNzdGVwcyB7CiAgICAgICAgaGVpZ2h0OiAxNXB4OwogICAgICAgIG1hcmdpbjogMCAwIDAgLTE1cHg7CiAgICAgICAgLyoKICAgICAgICAgKiAzMSBjb21lcyBmcm9tIGFkZGluZyBhIHBhZGRpbmcgb2YgMTVweCBmcm9tIGJvdGggc2lkZXMgb2YgdGhlCiAgICAgICAgICogcGFwZXItc2xpZGVyLCBzdWJ0cmFjdGluZyAxcHggc28gdGhhdCB0aGUgc2xpZGVyIHdpZHRoIGFsaWducwogICAgICAgICAqIHdpdGggdGhlIGltYWdlICh0aGUgbGFzdCBzbGlkZXIgbWFya2VyIHRha2VzIHVwIDFweCksIGFuZAogICAgICAgICAqIGFkZGluZyAycHggdG8gYWNjb3VudCBmb3IgYSBib3JkZXIgb2YgMXB4IG9uIGJvdGggc2lkZXMgb2YKICAgICAgICAgKiB0aGUgaW1hZ2UuIDMwIC0gMSArIDIuCiAgICAgICAgICovCiAgICAgICAgd2lkdGg6IGNhbGMoMTAwJSArIDMxcHgpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLWFjdGl2ZS1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1zbGlkZXIta25vYi1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1zbGlkZXIta25vYi1zdGFydC1ib3JkZXItY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLWtub2Itc3RhcnQtY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLW1hcmtlcnMtY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLXBpbi1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1zbGlkZXItcGluLXN0YXJ0LWNvbG9yOiB2YXIoLS10Yi1vcmFuZ2Utc3Ryb25nKTsKICAgICAgfQoKICAgICAgI21haW4taW1hZ2UtY29udGFpbmVyIHsKICAgICAgICBtYXgtaGVpZ2h0OiAxMDI0cHg7CiAgICAgICAgb3ZlcmZsb3c6IGF1dG87CiAgICAgIH0KCiAgICAgICNtYWluLWltYWdlLWNvbnRhaW5lciBpbWcgewogICAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBpbWFnZS1yZW5kZXJpbmc6IC1tb3otY3Jpc3AtZWRnZXM7CiAgICAgICAgaW1hZ2UtcmVuZGVyaW5nOiBwaXhlbGF0ZWQ7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgaGVpZ2h0OiBhdXRvOwogICAgICB9CgogICAgICBwYXBlci1pY29uLWJ1dHRvbiB7CiAgICAgICAgY29sb3I6ICMyMTk2ZjM7CiAgICAgICAgYm9yZGVyLXJhZGl1czogMTAwJTsKICAgICAgICB3aWR0aDogMzJweDsKICAgICAgICBoZWlnaHQ6IDMycHg7CiAgICAgICAgcGFkZGluZzogNHB4OwogICAgICB9CiAgICAgIHBhcGVyLWljb24tYnV0dG9uW3NlbGVjdGVkXSB7CiAgICAgICAgYmFja2dyb3VuZDogdmFyKC0tdGItdWktbGlnaHQtYWNjZW50KTsKICAgICAgfQogICAgICBbaGlkZGVuXSB7CiAgICAgICAgZGlzcGxheTogbm9uZTsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLF9uLnByb3RvdHlwZSwicnVuIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLF9uLnByb3RvdHlwZSwidGFnIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLF9uLnByb3RvdHlwZSwic2FtcGxlIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLF9uLnByb3RvdHlwZSwib2ZTYW1wbGVzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLF9uLnByb3RvdHlwZSwidGFnTWV0YWRhdGEiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sX24ucHJvdG90eXBlLCJhY3R1YWxTaXplIix2b2lkIDApO0UoW0Eoe3R5cGU6TnVtYmVyfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLF9uLnByb3RvdHlwZSwiYnJpZ2h0bmVzc0FkanVzdG1lbnQiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sX24ucHJvdG90eXBlLCJjb250cmFzdFBlcmNlbnRhZ2UiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsQWUpXSxfbi5wcm90b3R5cGUsInJlcXVlc3RNYW5hZ2VyIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLF9uLnByb3RvdHlwZSwiX21ldGFkYXRhQ2FuY2VsbGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLF9uLnByb3RvdHlwZSwiX2ltYWdlQ2FuY2VsbGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXksbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sX24ucHJvdG90eXBlLCJfc3RlcHMiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXIsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLF9uLnByb3RvdHlwZSwiX3N0ZXBJbmRleCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLF9uLnByb3RvdHlwZSwiX2lzSW1hZ2VMb2FkaW5nIix2b2lkIDApO0UoW1J0KCJydW4iKSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxfbi5wcm90b3R5cGUsIl9ydW5Db2xvciIsbnVsbCk7RShbUnQoIl9zdGVwcyIpLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxfbi5wcm90b3R5cGUsIl9oYXNBdExlYXN0T25lU3RlcCIsbnVsbCk7RShbUnQoIl9zdGVwcyIpLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxfbi5wcm90b3R5cGUsIl9oYXNNdWx0aXBsZVN0ZXBzIixudWxsKTtFKFtSdCgiX3N0ZXBzIiwiX3N0ZXBJbmRleCIpLHcoImRlc2lnbjp0eXBlIixPYmplY3QpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLF9uLnByb3RvdHlwZSwiX2N1cnJlbnRTdGVwIixudWxsKTtFKFtSdCgiX2N1cnJlbnRTdGVwIiksdygiZGVzaWduOnR5cGUiLE51bWJlciksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sX24ucHJvdG90eXBlLCJfc3RlcFZhbHVlIixudWxsKTtFKFtSdCgiX2N1cnJlbnRTdGVwIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sX24ucHJvdG90eXBlLCJfY3VycmVudFdhbGxUaW1lIixudWxsKTtFKFtSdCgiX3N0ZXBzIiksdygiZGVzaWduOnR5cGUiLE51bWJlciksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sX24ucHJvdG90eXBlLCJfbWF4U3RlcEluZGV4IixudWxsKTtFKFtSdCgic2FtcGxlIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sX24ucHJvdG90eXBlLCJfc2FtcGxlVGV4dCIsbnVsbCk7RShbUnQoIm9mU2FtcGxlcyIpLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxfbi5wcm90b3R5cGUsIl9oYXNNdWx0aXBsZVNhbXBsZXMiLG51bGwpO0UoW0J0KCJydW4iLCJ0YWciKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLF9uLnByb3RvdHlwZSwicmVsb2FkIixudWxsKTtFKFtCdCgiX2N1cnJlbnRTdGVwIiwiYnJpZ2h0bmVzc0FkanVzdG1lbnQiLCJjb250cmFzdFBlcmNlbnRhZ2UiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLF9uLnByb3RvdHlwZSwiX3VwZGF0ZUltYWdlVXJsIixudWxsKTtfbj1FKFt5dCgidGYtaW1hZ2UtbG9hZGVyIildLF9uKTt2YXIgSW89Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMucmVsb2FkT25SZWFkeT0hMCx0aGlzLl9kZWZhdWx0QnJpZ2h0bmVzc0FkanVzdG1lbnQ9MSx0aGlzLl9kZWZhdWx0Q29udHJhc3RQZXJjZW50YWdlPTEwMCx0aGlzLl9icmlnaHRuZXNzQWRqdXN0bWVudD0xLHRoaXMuX2NvbnRyYXN0UGVyY2VudGFnZT0xMDAsdGhpcy5fcmVxdWVzdE1hbmFnZXI9bmV3IEFlfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLnJlbG9hZE9uUmVhZHkmJnRoaXMucmVsb2FkKCl9cmVsb2FkKCl7dGhpcy5fZmV0Y2hUYWdzKCkudGhlbigoKT0+e3RoaXMuX3JlbG9hZEltYWdlcygpfSl9X2ZldGNoVGFncygpe2xldCB0PXZlKCkucGx1Z2luUm91dGUoImltYWdlcyIsIi90YWdzIik7cmV0dXJuIHRoaXMuX3JlcXVlc3RNYW5hZ2VyLnJlcXVlc3QodCkudGhlbihyPT57aWYocHYuaXNFcXVhbChyLHRoaXMuX3J1blRvVGFnSW5mbykpcmV0dXJuO2xldCBuPXB2Lm1hcFZhbHVlcyhyLG89Pk9iamVjdC5rZXlzKG8pKSxpPSRpKG4pO3RoaXMuc2V0KCJfZGF0YU5vdEZvdW5kIixpLmxlbmd0aD09PTApLHRoaXMuc2V0KCJfcnVuVG9UYWdJbmZvIixyKSx0aGlzLmFzeW5jKCgpPT57dGhpcy5zZXQoIl9jYXRlZ29yaWVzRG9tUmVhZHkiLCEwKX0pfSl9X3JlbG9hZEltYWdlcygpe3ZhciB0Oyh0PXRoaXMucm9vdCk9PW51bGx8fHQucXVlcnlTZWxlY3RvckFsbCgidGYtaW1hZ2UtbG9hZGVyIikuZm9yRWFjaChyPT57ci5yZWxvYWQoKX0pfV9zaG91bGRPcGVuKHQpe3JldHVybiB0PD0yfV9yZXNldEJyaWdodG5lc3MoKXt0aGlzLl9icmlnaHRuZXNzQWRqdXN0bWVudD10aGlzLl9kZWZhdWx0QnJpZ2h0bmVzc0FkanVzdG1lbnR9X3Jlc2V0Q29udHJhc3QoKXt0aGlzLl9jb250cmFzdFBlcmNlbnRhZ2U9dGhpcy5fZGVmYXVsdENvbnRyYXN0UGVyY2VudGFnZX1nZXQgX2JyaWdodG5lc3NJc0RlZmF1bHQoKXt2YXIgdD10aGlzLl9icmlnaHRuZXNzQWRqdXN0bWVudDtyZXR1cm4gdD09PXRoaXMuX2RlZmF1bHRCcmlnaHRuZXNzQWRqdXN0bWVudH1nZXQgX2NvbnRyYXN0SXNEZWZhdWx0KCl7dmFyIHQ9dGhpcy5fY29udHJhc3RQZXJjZW50YWdlO3JldHVybiB0PT09dGhpcy5fZGVmYXVsdENvbnRyYXN0UGVyY2VudGFnZX1nZXQgX2NhdGVnb3JpZXMoKXt2YXIgdD10aGlzLl9ydW5Ub1RhZ0luZm8scj10aGlzLl9zZWxlY3RlZFJ1bnMsbj10aGlzLl90YWdGaWx0ZXIsaT10aGlzLl9jYXRlZ29yaWVzRG9tUmVhZHk7bGV0IG89cHYubWFwVmFsdWVzKHQsYz0+T2JqZWN0LmtleXMoYykpLGE9UWwobyxyLG4pO2Z1bmN0aW9uIHMoYyl7bGV0IHU9dFtjLnJ1bl1bYy50YWddLnNhbXBsZXM7cmV0dXJuIHB2LnJhbmdlKHUpLm1hcChoPT5PYmplY3QuYXNzaWduKHt9LGMse3NhbXBsZTpoLG9mU2FtcGxlczp1fSkpfXJldHVybiBhLm1hcChjPT5PYmplY3QuYXNzaWduKHt9LGMse2l0ZW1zOltdLmNvbmNhdC5hcHBseShbXSxjLml0ZW1zLm1hcChzKSl9KSl9X3RhZ01ldGFkYXRhKHQscixuKXtyZXR1cm4gdFtyXVtuXX19O0lvLnRlbXBsYXRlPVFgCiAgICA8dGYtZGFzaGJvYXJkLWxheW91dD4KICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhciIgc2xvdD0ic2lkZWJhciI+CiAgICAgICAgPGRpdiBjbGFzcz0ic2V0dGluZ3MiPgogICAgICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhci1zZWN0aW9uIj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGluZS1pdGVtIj4KICAgICAgICAgICAgICA8cGFwZXItY2hlY2tib3ggY2hlY2tlZD0ie3tfYWN0dWFsU2l6ZX19IgogICAgICAgICAgICAgICAgPlNob3cgYWN0dWFsIGltYWdlIHNpemU8L3BhcGVyLWNoZWNrYm94CiAgICAgICAgICAgICAgPgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhci1zZWN0aW9uIj4KICAgICAgICAgICAgPGgzIGNsYXNzPSJ0b29sdGlwLWNvbnRhaW5lciI+QnJpZ2h0bmVzcyBhZGp1c3RtZW50PC9oMz4KICAgICAgICAgICAgPGRpdiBjbGFzcz0icmVzZXR0YWJsZS1zbGlkZXItY29udGFpbmVyIj4KICAgICAgICAgICAgICA8cGFwZXItc2xpZGVyCiAgICAgICAgICAgICAgICBtaW49IjAiCiAgICAgICAgICAgICAgICBtYXg9IjIiCiAgICAgICAgICAgICAgICBzbmFwcwogICAgICAgICAgICAgICAgcGluCiAgICAgICAgICAgICAgICBzdGVwPSIwLjAxIgogICAgICAgICAgICAgICAgdmFsdWU9Int7X2JyaWdodG5lc3NBZGp1c3RtZW50fX0iCiAgICAgICAgICAgICAgICBpbW1lZGlhdGUtdmFsdWU9Int7X2JyaWdodG5lc3NBZGp1c3RtZW50fX0iCiAgICAgICAgICAgICAgPjwvcGFwZXItc2xpZGVyPgogICAgICAgICAgICAgIDxwYXBlci1idXR0b24KICAgICAgICAgICAgICAgIGNsYXNzPSJ4LWJ1dHRvbiIKICAgICAgICAgICAgICAgIG9uLXRhcD0iX3Jlc2V0QnJpZ2h0bmVzcyIKICAgICAgICAgICAgICAgIGRpc2FibGVkPSJbW19icmlnaHRuZXNzSXNEZWZhdWx0XV0iCiAgICAgICAgICAgICAgICA+UmVzZXQ8L3BhcGVyLWJ1dHRvbgogICAgICAgICAgICAgID4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDxkaXYgY2xhc3M9InNpZGViYXItc2VjdGlvbiI+CiAgICAgICAgICAgIDxoMyBjbGFzcz0idG9vbHRpcC1jb250YWluZXIiPkNvbnRyYXN0IGFkanVzdG1lbnQ8L2gzPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJyZXNldHRhYmxlLXNsaWRlci1jb250YWluZXIiPgogICAgICAgICAgICAgIDxwYXBlci1zbGlkZXIKICAgICAgICAgICAgICAgIG1pbj0iMCIKICAgICAgICAgICAgICAgIG1heD0iNTAwIgogICAgICAgICAgICAgICAgc25hcHMKICAgICAgICAgICAgICAgIHBpbgogICAgICAgICAgICAgICAgc3RlcD0iMSIKICAgICAgICAgICAgICAgIHZhbHVlPSJ7e19jb250cmFzdFBlcmNlbnRhZ2V9fSIKICAgICAgICAgICAgICAgIGltbWVkaWF0ZS12YWx1ZT0ie3tfY29udHJhc3RQZXJjZW50YWdlfX0iCiAgICAgICAgICAgICAgPjwvcGFwZXItc2xpZGVyPgogICAgICAgICAgICAgIDxwYXBlci1idXR0b24KICAgICAgICAgICAgICAgIGNsYXNzPSJ4LWJ1dHRvbiIKICAgICAgICAgICAgICAgIG9uLXRhcD0iX3Jlc2V0Q29udHJhc3QiCiAgICAgICAgICAgICAgICBkaXNhYmxlZD0iW1tfY29udHJhc3RJc0RlZmF1bHRdXSIKICAgICAgICAgICAgICAgID5SZXNldDwvcGFwZXItYnV0dG9uCiAgICAgICAgICAgICAgPgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9InNpZGViYXItc2VjdGlvbiBydW5zLXNlbGVjdG9yIj4KICAgICAgICAgIDx0Zi1ydW5zLXNlbGVjdG9yCiAgICAgICAgICAgIGlkPSJydW5zLXNlbGVjdG9yIgogICAgICAgICAgICBzZWxlY3RlZC1ydW5zPSJ7e19zZWxlY3RlZFJ1bnN9fSIKICAgICAgICAgID48L3RmLXJ1bnMtc2VsZWN0b3I+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJjZW50ZXIiIHNsb3Q9ImNlbnRlciI+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19kYXRhTm90Rm91bmRdXSI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJuby1kYXRhLXdhcm5pbmciPgogICAgICAgICAgICA8aDM+Tm8gaW1hZ2UgZGF0YSB3YXMgZm91bmQuPC9oMz4KICAgICAgICAgICAgPHA+UHJvYmFibGUgY2F1c2VzOjwvcD4KICAgICAgICAgICAgPHVsPgogICAgICAgICAgICAgIDxsaT5Zb3UgaGF2ZW7igJl0IHdyaXR0ZW4gYW55IGltYWdlIGRhdGEgdG8geW91ciBldmVudCBmaWxlcy48L2xpPgogICAgICAgICAgICAgIDxsaT5UZW5zb3JCb2FyZCBjYW7igJl0IGZpbmQgeW91ciBldmVudCBmaWxlcy48L2xpPgogICAgICAgICAgICA8L3VsPgoKICAgICAgICAgICAgPHA+CiAgICAgICAgICAgICAgSWYgeW914oCZcmUgbmV3IHRvIHVzaW5nIFRlbnNvckJvYXJkLCBhbmQgd2FudCB0byBmaW5kIG91dCBob3cgdG8KICAgICAgICAgICAgICBhZGQgZGF0YSBhbmQgc2V0IHVwIHlvdXIgZXZlbnQgZmlsZXMsIGNoZWNrIG91dCB0aGUKICAgICAgICAgICAgICA8YQogICAgICAgICAgICAgICAgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL3RlbnNvcmZsb3cvdGVuc29yYm9hcmQvYmxvYi9tYXN0ZXIvUkVBRE1FLm1kIgogICAgICAgICAgICAgICAgPlJFQURNRTwvYQogICAgICAgICAgICAgID4KICAgICAgICAgICAgICBhbmQgcGVyaGFwcyB0aGUKICAgICAgICAgICAgICA8YQogICAgICAgICAgICAgICAgaHJlZj0iaHR0cHM6Ly93d3cudGVuc29yZmxvdy5vcmcvZ2V0X3N0YXJ0ZWQvc3VtbWFyaWVzX2FuZF90ZW5zb3Jib2FyZCIKICAgICAgICAgICAgICAgID5UZW5zb3JCb2FyZCB0dXRvcmlhbDwvYQogICAgICAgICAgICAgID4uCiAgICAgICAgICAgIDwvcD4KCiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgIElmIHlvdSB0aGluayBUZW5zb3JCb2FyZCBpcyBjb25maWd1cmVkIHByb3Blcmx5LCBwbGVhc2Ugc2VlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCNteS10ZW5zb3Jib2FyZC1pc250LXNob3dpbmctYW55LWRhdGEtd2hhdHMtd3JvbmciCiAgICAgICAgICAgICAgICA+dGhlIHNlY3Rpb24gb2YgdGhlIFJFQURNRSBkZXZvdGVkIHRvIG1pc3NpbmcgZGF0YSBwcm9ibGVtczwvYQogICAgICAgICAgICAgID4KICAgICAgICAgICAgICBhbmQgY29uc2lkZXIgZmlsaW5nIGFuIGlzc3VlIG9uIEdpdEh1Yi4KICAgICAgICAgICAgPC9wPgogICAgICAgICAgPC9kaXY+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbIV9kYXRhTm90Rm91bmRdXSI+CiAgICAgICAgICA8dGYtdGFnLWZpbHRlcmVyIHRhZy1maWx0ZXI9Int7X3RhZ0ZpbHRlcn19Ij48L3RmLXRhZy1maWx0ZXJlcj4KICAgICAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9IltbX2NhdGVnb3JpZXNdXSIgYXM9ImNhdGVnb3J5Ij4KICAgICAgICAgICAgPHRmLWNhdGVnb3J5LXBhZ2luYXRlZC12aWV3CiAgICAgICAgICAgICAgY2F0ZWdvcnk9IltbY2F0ZWdvcnldXSIKICAgICAgICAgICAgICBpbml0aWFsLW9wZW5lZD0iW1tfc2hvdWxkT3BlbihpbmRleCldXSIKICAgICAgICAgICAgPgogICAgICAgICAgICAgIDx0ZW1wbGF0ZT4KICAgICAgICAgICAgICAgIDx0Zi1pbWFnZS1sb2FkZXIKICAgICAgICAgICAgICAgICAgYWN0aXZlPSJbW2FjdGl2ZV1dIgogICAgICAgICAgICAgICAgICBydW49IltbaXRlbS5ydW5dXSIKICAgICAgICAgICAgICAgICAgdGFnPSJbW2l0ZW0udGFnXV0iCiAgICAgICAgICAgICAgICAgIHNhbXBsZT0iW1tpdGVtLnNhbXBsZV1dIgogICAgICAgICAgICAgICAgICBvZi1zYW1wbGVzPSJbW2l0ZW0ub2ZTYW1wbGVzXV0iCiAgICAgICAgICAgICAgICAgIHRhZy1tZXRhZGF0YT0iW1tfdGFnTWV0YWRhdGEoX3J1blRvVGFnSW5mbywgaXRlbS5ydW4sIGl0ZW0udGFnKV1dIgogICAgICAgICAgICAgICAgICByZXF1ZXN0LW1hbmFnZXI9IltbX3JlcXVlc3RNYW5hZ2VyXV0iCiAgICAgICAgICAgICAgICAgIGFjdHVhbC1zaXplPSJbW19hY3R1YWxTaXplXV0iCiAgICAgICAgICAgICAgICAgIGJyaWdodG5lc3MtYWRqdXN0bWVudD0iW1tfYnJpZ2h0bmVzc0FkanVzdG1lbnRdXSIKICAgICAgICAgICAgICAgICAgY29udHJhc3QtcGVyY2VudGFnZT0iW1tfY29udHJhc3RQZXJjZW50YWdlXV0iCiAgICAgICAgICAgICAgICA+PC90Zi1pbWFnZS1sb2FkZXI+CiAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgPC90Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldz4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPC9kaXY+CiAgICA8L3RmLWRhc2hib2FyZC1sYXlvdXQ+CiAgICA8c3R5bGUgaW5jbHVkZT0iZGFzaGJvYXJkLXN0eWxlIj48L3N0eWxlPgogICAgPHN0eWxlPgogICAgICAucmVzZXR0YWJsZS1zbGlkZXItY29udGFpbmVyIHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICB9CiAgICAgIC5yZXNldHRhYmxlLXNsaWRlci1jb250YWluZXIgcGFwZXItc2xpZGVyIHsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgIH0KICAgICAgLnJlc2V0dGFibGUtc2xpZGVyLWNvbnRhaW5lciBwYXBlci1idXR0b24gewogICAgICAgIGZsZXgtZ3JvdzogMDsKICAgICAgfQogICAgICAucmVzZXR0YWJsZS1zbGlkZXItY29udGFpbmVyIHBhcGVyLWJ1dHRvbltkaXNhYmxlZF0gewogICAgICAgIGJhY2tncm91bmQtY29sb3I6IHVuc2V0OwogICAgICB9CiAgICAgIC54LWJ1dHRvbiB7CiAgICAgICAgZm9udC1zaXplOiAxM3B4OwogICAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLXRiLXVpLWxpZ2h0LWFjY2VudCk7CiAgICAgICAgY29sb3I6IHZhcigtLXRiLXVpLWRhcmstYWNjZW50KTsKICAgICAgfQogICAgICAubm8tZGF0YS13YXJuaW5nIHsKICAgICAgICBtYXgtd2lkdGg6IDU0MHB4OwogICAgICAgIG1hcmdpbjogODBweCBhdXRvIDAgYXV0bzsKICAgICAgfQogICAgICBwYXBlci1zbGlkZXIgewogICAgICAgIC0tcGFwZXItc2xpZGVyLWFjdGl2ZS1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1zbGlkZXIta25vYi1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1zbGlkZXIta25vYi1zdGFydC1ib3JkZXItY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLWtub2Itc3RhcnQtY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLW1hcmtlcnMtY29sb3I6IHZhcigtLXRiLW9yYW5nZS1zdHJvbmcpOwogICAgICAgIC0tcGFwZXItc2xpZGVyLXBpbi1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgLS1wYXBlci1zbGlkZXItcGluLXN0YXJ0LWNvbG9yOiB2YXIoLS10Yi1vcmFuZ2Utc3Ryb25nKTsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSW8ucHJvdG90eXBlLCJyZWxvYWRPblJlYWR5Iix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxJby5wcm90b3R5cGUsIl9zZWxlY3RlZFJ1bnMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sSW8ucHJvdG90eXBlLCJfcnVuVG9UYWdJbmZvIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSW8ucHJvdG90eXBlLCJfZGF0YU5vdEZvdW5kIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSW8ucHJvdG90eXBlLCJfYWN0dWFsU2l6ZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxJby5wcm90b3R5cGUsIl9kZWZhdWx0QnJpZ2h0bmVzc0FkanVzdG1lbnQiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXJ9KSx3KCJkZXNpZ246dHlwZSIsTnVtYmVyKV0sSW8ucHJvdG90eXBlLCJfZGVmYXVsdENvbnRyYXN0UGVyY2VudGFnZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxJby5wcm90b3R5cGUsIl9icmlnaHRuZXNzQWRqdXN0bWVudCIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxJby5wcm90b3R5cGUsIl9jb250cmFzdFBlcmNlbnRhZ2UiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0sSW8ucHJvdG90eXBlLCJfdGFnRmlsdGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sSW8ucHJvdG90eXBlLCJfY2F0ZWdvcmllc0RvbVJlYWR5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLElvLnByb3RvdHlwZSwiX3JlcXVlc3RNYW5hZ2VyIix2b2lkIDApO0UoW1J0KCJfYnJpZ2h0bmVzc0FkanVzdG1lbnQiKSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sSW8ucHJvdG90eXBlLCJfYnJpZ2h0bmVzc0lzRGVmYXVsdCIsbnVsbCk7RShbUnQoIl9jb250cmFzdFBlcmNlbnRhZ2UiKSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sSW8ucHJvdG90eXBlLCJfY29udHJhc3RJc0RlZmF1bHQiLG51bGwpO0UoW1J0KCJfcnVuVG9UYWdJbmZvIiwiX3NlbGVjdGVkUnVucyIsIl90YWdGaWx0ZXIiLCJfY2F0ZWdvcmllc0RvbVJlYWR5IiksdygiZGVzaWduOnR5cGUiLEFycmF5KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxJby5wcm90b3R5cGUsIl9jYXRlZ29yaWVzIixudWxsKTtJbz1FKFt5dCgidGYtaW1hZ2UtZGFzaGJvYXJkIildLElvKTt2YXIgc3g9RWUoT2UoKSwxKTt2YXIgZHY7KGZ1bmN0aW9uKGUpe2VbZS5DQU5DRUxMRUQ9MV09IkNBTkNFTExFRCJ9KShkdnx8KGR2PXt9KSk7dmFyIGxWOyhmdW5jdGlvbihlKXtlW2UuVkVSVEVYPTFdPSJWRVJURVgiLGVbZS5GQUNFPTJdPSJGQUNFIixlW2UuQ09MT1I9M109IkNPTE9SIn0pKGxWfHwobFY9e30pKTt2YXIgR2N0OyhmdW5jdGlvbihlKXtlLlZFUlRFWD0iZmxvYXQzMiIsZS5GQUNFPSJpbnQzMiIsZS5DT0xPUj0idWludDgifSkoR2N0fHwoR2N0PXt9KSk7dmFyIFBQPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuX2NhbmNlbGxlcj1uZXcgYW4sdGhpcy5fcmVxdWVzdE1hbmFnZXI9dH1yZWxvYWQodCxyLG4pe3JldHVybiB0aGlzLl9jYW5jZWxsZXIuY2FuY2VsQWxsKCksdGhpcy5fZmV0Y2hNZXRhZGF0YSh0LHIsbil9X2ZldGNoRGF0YUJ5U3RlcCh0LHIsbixpLG8sYSl7bGV0IHM9dmUoKS5wbHVnaW5Sb3V0ZSgibWVzaCIsIi9kYXRhIixuZXcgVVJMU2VhcmNoUGFyYW1zKHt0YWc6cixydW46dCxjb250ZW50X3R5cGU6bixzYW1wbGU6U3RyaW5nKGkpLHN0ZXA6U3RyaW5nKG8pfSkpLGw9ZnVuY3Rpb24odSl7bGV0IGY9W107Zm9yKGxldCBwPTA7cDx1Lmxlbmd0aC8zO3ArKyl7bGV0IGQ9W107Zm9yKGxldCBnPTA7ZzwzO2crKylkLnB1c2godVtwKjMrZ10pO2YucHVzaChkKX1yZXR1cm4gZn0sYz10aGlzLl9jYW5jZWxsZXIuY2FuY2VsbGFibGUodT0+e2lmKHUuY2FuY2VsbGVkKXJldHVybiBQcm9taXNlLnJlamVjdCh7Y29kZTpkdi5DQU5DRUxMRUQsbWVzc2FnZToiUmVzcG9uc2Ugd2FzIGludmFsaWRhdGVkLiJ9KTtsZXQgaD11LnZhbHVlO3N3aXRjaChuKXtjYXNlIlZFUlRFWCI6YS52ZXJ0aWNlcz1sKG5ldyBGbG9hdDMyQXJyYXkoaCkpO2JyZWFrO2Nhc2UiRkFDRSI6YS5mYWNlcz1sKG5ldyBJbnQzMkFycmF5KGgpKTticmVhaztjYXNlIkNPTE9SIjphLmNvbG9ycz1sKG5ldyBVaW50OEFycmF5KGgpKTticmVha31yZXR1cm4gYX0pO3JldHVybiB0aGlzLl9yZXF1ZXN0TWFuYWdlci5mZXRjaChzLHttZXRob2Q6IkdFVCIsaGVhZGVyczp7cmVzcG9uc2VUeXBlOiJhcnJheWJ1ZmZlciIsY29udGVudFR5cGU6R2N0W25dfX0pLnRoZW4odT0+dS5hcnJheUJ1ZmZlcigpKS50aGVuKGMpfWZldGNoRGF0YSh0LHIsbixpKXtsZXQgbz1bXSxhPW5ldyBNYXA7cmV0dXJuIE9iamVjdC5rZXlzKGxWKS5mb3JFYWNoKHM9PntsZXQgbD0xPDxsVltzXTt0LmNvbXBvbmVudHMmbCYmby5wdXNoKHRoaXMuX2ZldGNoRGF0YUJ5U3RlcChyLG4scyxpLHQuc3RlcCxhKSl9KSxQcm9taXNlLmFsbChvKX1fZmV0Y2hNZXRhZGF0YSh0LHIsbil7dGhpcy5fY2FuY2VsbGVyLmNhbmNlbEFsbCgpO2xldCBpPXZlKCkucGx1Z2luUm91dGUoIm1lc2giLCIvbWVzaGVzIixuZXcgVVJMU2VhcmNoUGFyYW1zKHt0YWc6cixydW46dCxzYW1wbGU6bn0pKSxvPXRoaXMuX2NhbmNlbGxlci5jYW5jZWxsYWJsZShhPT5hLmNhbmNlbGxlZD9Qcm9taXNlLnJlamVjdCh7Y29kZTpkdi5DQU5DRUxMRUQsbWVzc2FnZToiUmVzcG9uc2Ugd2FzIGludmFsaWRhdGVkLiJ9KTphLnZhbHVlKTtyZXR1cm4gdGhpcy5fcmVxdWVzdE1hbmFnZXIuZmV0Y2goaSkudGhlbihhPT5hLmpzb24oKSkudGhlbihvKS50aGVuKHRoaXMuX3Byb2Nlc3NNZXRhZGF0YS5iaW5kKHRoaXMpKX1fcHJvY2Vzc01ldGFkYXRhKHQpe2lmKCF0KXJldHVybjtsZXQgcj1uZXcgTWFwO2ZvcihsZXQgaT0wO2k8dC5sZW5ndGg7aSsrKXtsZXQgbz10W2ldO3IuaGFzKG8uc3RlcCl8fHIuc2V0KG8uc3RlcCxbXSksci5nZXQoby5zdGVwKS5wdXNoKG8pfWxldCBuPVtdO3JldHVybiByLmZvckVhY2goaT0+e2xldCBvPXRoaXMuX2NyZWF0ZVN0ZXBEYXR1bShpWzBdKTtuLnB1c2gobyl9KSxufV9jcmVhdGVTdGVwRGF0dW0odCl7cmV0dXJue3dhbGxfdGltZTpuZXcgRGF0ZSh0LndhbGxfdGltZSoxZTMpLHN0ZXA6dC5zdGVwLGNvbmZpZzp0LmNvbmZpZyxjb250ZW50X3R5cGU6dC5jb250ZW50X3R5cGUsY29tcG9uZW50czp0LmNvbXBvbmVudHN9fX07dmFyIHdNPXt9O0tzKHdNLHtBQ0VTRmlsbWljVG9uZU1hcHBpbmc6KCk9PmxmZSxBZGRFcXVhdGlvbjooKT0+TXYsQWRkT3BlcmF0aW9uOigpPT5pZmUsQWRkaXRpdmVBbmltYXRpb25CbGVuZE1vZGU6KCk9PlJodCxBZGRpdGl2ZUJsZW5kaW5nOigpPT5FdXQsQWxwaGFGb3JtYXQ6KCk9Pm1mZSxBbHdheXNEZXB0aDooKT0+WmhlLEFsd2F5c1N0ZW5jaWxGdW5jOigpPT5MZmUsQW1iaWVudExpZ2h0OigpPT5JNixBbWJpZW50TGlnaHRQcm9iZTooKT0+T1UsQW5pbWF0aW9uQ2xpcDooKT0+UXYsQW5pbWF0aW9uTG9hZGVyOigpPT5haHQsQW5pbWF0aW9uTWl4ZXI6KCk9PkhVLEFuaW1hdGlvbk9iamVjdEdyb3VwOigpPT5CVSxBbmltYXRpb25VdGlsczooKT0+am4sQXJjQ3VydmU6KCk9PnM2LEFycmF5Q2FtZXJhOigpPT5yNixBcnJvd0hlbHBlcjooKT0+RWh0LEF1ZGlvOigpPT5ONixBdWRpb0FuYWx5c2VyOigpPT56VSxBdWRpb0NvbnRleHQ6KCk9PkZodCxBdWRpb0xpc3RlbmVyOigpPT51aHQsQXVkaW9Mb2FkZXI6KCk9Pk5VLEF4ZXNIZWxwZXI6KCk9PnZNLEF4aXNIZWxwZXI6KCk9PlcwcixCYWNrU2lkZTooKT0+SWksQmFzaWNEZXB0aFBhY2tpbmc6KCk9PkFmZSxCYXNpY1NoYWRvd01hcDooKT0+dWhyLEJpbmFyeVRleHR1cmVMb2FkZXI6KCk9PkswcixCb25lOigpPT5zTSxCb29sZWFuS2V5ZnJhbWVUcmFjazooKT0+YW0sQm91bmRpbmdCb3hIZWxwZXI6KCk9PlkwcixCb3gyOigpPT4kMCxCb3gzOigpPT50YSxCb3gzSGVscGVyOigpPT5TaHQsQm94QnVmZmVyR2VvbWV0cnk6KCk9PlFmLEJveEdlb21ldHJ5OigpPT5RZixCb3hIZWxwZXI6KCk9PnlNLEJ1ZmZlckF0dHJpYnV0ZTooKT0+SmUsQnVmZmVyR2VvbWV0cnk6KCk9PlBlLEJ1ZmZlckdlb21ldHJ5TG9hZGVyOigpPT5rVSxCeXRlVHlwZTooKT0+dWZlLENhY2hlOigpPT50eCxDYW1lcmE6KCk9PlJ2LENhbWVyYUhlbHBlcjooKT0+d2h0LENhbnZhc1JlbmRlcmVyOigpPT5KMHIsQ2FudmFzVGV4dHVyZTooKT0+dlUsQ2F0bXVsbFJvbUN1cnZlMzooKT0+bDYsQ2luZW9uVG9uZU1hcHBpbmc6KCk9PnNmZSxDaXJjbGVCdWZmZXJHZW9tZXRyeTooKT0+RnYsQ2lyY2xlR2VvbWV0cnk6KCk9PkZ2LENsYW1wVG9FZGdlV3JhcHBpbmc6KCk9PkpvLENsb2NrOigpPT5tTSxDb2xvcjooKT0+bmUsQ29sb3JLZXlmcmFtZVRyYWNrOigpPT5TNixDb21wcmVzc2VkVGV4dHVyZTooKT0+bzYsQ29tcHJlc3NlZFRleHR1cmVMb2FkZXI6KCk9PnNodCxDb25lQnVmZmVyR2VvbWV0cnk6KCk9PkJ2LENvbmVHZW9tZXRyeTooKT0+QnYsQ3ViZUNhbWVyYTooKT0+SjMsQ3ViZVJlZmxlY3Rpb25NYXBwaW5nOigpPT5ueCxDdWJlUmVmcmFjdGlvbk1hcHBpbmc6KCk9Pml4LEN1YmVUZXh0dXJlOigpPT5IMCxDdWJlVGV4dHVyZUxvYWRlcjooKT0+RVUsQ3ViZVVWUmVmbGVjdGlvbk1hcHBpbmc6KCk9PnhNLEN1YmVVVlJlZnJhY3Rpb25NYXBwaW5nOigpPT5PNixDdWJpY0JlemllckN1cnZlOigpPT5jTSxDdWJpY0JlemllckN1cnZlMzooKT0+YzYsQ3ViaWNJbnRlcnBvbGFudDooKT0+d1UsQ3VsbEZhY2VCYWNrOigpPT5NdXQsQ3VsbEZhY2VGcm9udDooKT0+T2hlLEN1bGxGYWNlRnJvbnRCYWNrOigpPT5jaHIsQ3VsbEZhY2VOb25lOigpPT5EaGUsQ3VydmU6KCk9PmZzLEN1cnZlUGF0aDooKT0+YlUsQ3VzdG9tQmxlbmRpbmc6KCk9PkZoZSxDdXN0b21Ub25lTWFwcGluZzooKT0+Y2ZlLEN5bGluZGVyQnVmZmVyR2VvbWV0cnk6KCk9Pm9tLEN5bGluZGVyR2VvbWV0cnk6KCk9Pm9tLEN5bGluZHJpY2FsOigpPT5naHQsRGF0YVRleHR1cmU6KCk9PkpkLERhdGFUZXh0dXJlMkRBcnJheTooKT0+dE0sRGF0YVRleHR1cmUzRDooKT0+ZTYsRGF0YVRleHR1cmVMb2FkZXI6KCk9PlRVLERhdGFVdGlsczooKT0+Q2h0LERlY3JlbWVudFN0ZW5jaWxPcDooKT0+YmhyLERlY3JlbWVudFdyYXBTdGVuY2lsT3A6KCk9PlNocixEZWZhdWx0TG9hZGluZ01hbmFnZXI6KCk9PlFmZSxEZXB0aEZvcm1hdDooKT0+ejAsRGVwdGhTdGVuY2lsRm9ybWF0OigpPT5rdixEZXB0aFRleHR1cmU6KCk9Pm5NLERpcmVjdGlvbmFsTGlnaHQ6KCk9PlA2LERpcmVjdGlvbmFsTGlnaHRIZWxwZXI6KCk9PmJodCxEaXNjcmV0ZUludGVycG9sYW50OigpPT5TVSxEb2RlY2FoZWRyb25CdWZmZXJHZW9tZXRyeTooKT0+SHYsRG9kZWNhaGVkcm9uR2VvbWV0cnk6KCk9Pkh2LERvdWJsZVNpZGU6KCk9Pkx2LERzdEFscGhhRmFjdG9yOigpPT5XaGUsRHN0Q29sb3JGYWN0b3I6KCk9PmpoZSxEeW5hbWljQnVmZmVyQXR0cmlidXRlOigpPT5EMHIsRHluYW1pY0NvcHlVc2FnZTooKT0+emhyLER5bmFtaWNEcmF3VXNhZ2U6KCk9PlkzLER5bmFtaWNSZWFkVXNhZ2U6KCk9Pk5ocixFZGdlc0dlb21ldHJ5OigpPT5hNixFZGdlc0hlbHBlcjooKT0+ajByLEVsbGlwc2VDdXJ2ZTooKT0+VnYsRXF1YWxEZXB0aDooKT0+UWhlLEVxdWFsU3RlbmNpbEZ1bmM6KCk9PkNocixFcXVpcmVjdGFuZ3VsYXJSZWZsZWN0aW9uTWFwcGluZzooKT0+V1AsRXF1aXJlY3Rhbmd1bGFyUmVmcmFjdGlvbk1hcHBpbmc6KCk9PllQLEV1bGVyOigpPT50bSxFdmVudERpc3BhdGNoZXI6KCk9PlVzLEV4dHJ1ZGVCdWZmZXJHZW9tZXRyeTooKT0+aGgsRXh0cnVkZUdlb21ldHJ5OigpPT5oaCxGYWNlQ29sb3JzOigpPT5NMHIsRmlsZUxvYWRlcjooKT0+SmMsRmxhdFNoYWRpbmc6KCk9PlBodCxGbG9hdDE2QnVmZmVyQXR0cmlidXRlOigpPT5wVSxGbG9hdDMyQXR0cmlidXRlOigpPT5xMHIsRmxvYXQzMkJ1ZmZlckF0dHJpYnV0ZTooKT0+eGUsRmxvYXQ2NEF0dHJpYnV0ZTooKT0+RzByLEZsb2F0NjRCdWZmZXJBdHRyaWJ1dGU6KCk9PmRVLEZsb2F0VHlwZTooKT0+amQsRm9nOigpPT56dixGb2dFeHAyOigpPT5PdixGb250OigpPT5vX3IsRm9udExvYWRlcjooKT0+aV9yLEZyYW1lYnVmZmVyVGV4dHVyZTooKT0+eVUsRnJvbnRTaWRlOigpPT5JdixGcnVzdHVtOigpPT5OdixHTEJ1ZmZlckF0dHJpYnV0ZTooKT0+VVUsR0xTTDE6KCk9PkJocixHTFNMMzooKT0+WnV0LEdyZWF0ZXJEZXB0aDooKT0+ZWZlLEdyZWF0ZXJFcXVhbERlcHRoOigpPT50ZmUsR3JlYXRlckVxdWFsU3RlbmNpbEZ1bmM6KCk9PkxocixHcmVhdGVyU3RlbmNpbEZ1bmM6KCk9PlBocixHcmlkSGVscGVyOigpPT5XVSxHcm91cDooKT0+WGQsSGFsZkZsb2F0VHlwZTooKT0+Q3YsSGVtaXNwaGVyZUxpZ2h0OigpPT5FNixIZW1pc3BoZXJlTGlnaHRIZWxwZXI6KCk9PnZodCxIZW1pc3BoZXJlTGlnaHRQcm9iZTooKT0+RFUsSWNvc2FoZWRyb25CdWZmZXJHZW9tZXRyeTooKT0+R3YsSWNvc2FoZWRyb25HZW9tZXRyeTooKT0+R3YsSW1hZ2VCaXRtYXBMb2FkZXI6KCk9PlJVLEltYWdlTG9hZGVyOigpPT5leCxJbWFnZVV0aWxzOigpPT5LZixJbW1lZGlhdGVSZW5kZXJPYmplY3Q6KCk9PmFfcixJbmNyZW1lbnRTdGVuY2lsT3A6KCk9PnhocixJbmNyZW1lbnRXcmFwU3RlbmNpbE9wOigpPT53aHIsSW5zdGFuY2VkQnVmZmVyQXR0cmlidXRlOigpPT5ybSxJbnN0YW5jZWRCdWZmZXJHZW9tZXRyeTooKT0+UjYsSW5zdGFuY2VkSW50ZXJsZWF2ZWRCdWZmZXI6KCk9PlZVLEluc3RhbmNlZE1lc2g6KCk9Pm42LEludDE2QXR0cmlidXRlOigpPT5CMHIsSW50MTZCdWZmZXJBdHRyaWJ1dGU6KCk9PmhVLEludDMyQXR0cmlidXRlOigpPT5WMHIsSW50MzJCdWZmZXJBdHRyaWJ1dGU6KCk9PmZVLEludDhBdHRyaWJ1dGU6KCk9Pk8wcixJbnQ4QnVmZmVyQXR0cmlidXRlOigpPT5sVSxJbnRUeXBlOigpPT5mZmUsSW50ZXJsZWF2ZWRCdWZmZXI6KCk9PmVtLEludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlOigpPT50cCxJbnRlcnBvbGFudDooKT0+ZmgsSW50ZXJwb2xhdGVEaXNjcmV0ZTooKT0+JFAsSW50ZXJwb2xhdGVMaW5lYXI6KCk9PktQLEludGVycG9sYXRlU21vb3RoOigpPT5lVSxJbnZlcnRTdGVuY2lsT3A6KCk9Pk1ocixKU09OTG9hZGVyOigpPT5RMHIsS2VlcFN0ZW5jaWxPcDooKT0+clUsS2V5ZnJhbWVUcmFjazooKT0+RGwsTE9EOigpPT5nVSxMYXRoZUJ1ZmZlckdlb21ldHJ5OigpPT5XdixMYXRoZUdlb21ldHJ5OigpPT5XdixMYXllcnM6KCk9PlgzLExlbnNGbGFyZTooKT0+ZV9yLExlc3NEZXB0aDooKT0+SmhlLExlc3NFcXVhbERlcHRoOigpPT5uVSxMZXNzRXF1YWxTdGVuY2lsRnVuYzooKT0+QWhyLExlc3NTdGVuY2lsRnVuYzooKT0+VGhyLExpZ2h0OigpPT5PbCxMaWdodFByb2JlOigpPT5yeCxMaW5lOigpPT5jaCxMaW5lMzooKT0+cVUsTGluZUJhc2ljTWF0ZXJpYWw6KCk9PkdpLExpbmVDdXJ2ZTooKT0+VXYsTGluZUN1cnZlMzooKT0+eFUsTGluZURhc2hlZE1hdGVyaWFsOigpPT5iNixMaW5lTG9vcDooKT0+aTYsTGluZVBpZWNlczooKT0+dzByLExpbmVTZWdtZW50czooKT0+QWEsTGluZVN0cmlwOigpPT5iMHIsTGluZWFyRW5jb2Rpbmc6KCk9PlFkLExpbmVhckZpbHRlcjooKT0+b2ksTGluZWFySW50ZXJwb2xhbnQ6KCk9Pnc2LExpbmVhck1pcE1hcExpbmVhckZpbHRlcjooKT0+bWhyLExpbmVhck1pcE1hcE5lYXJlc3RGaWx0ZXI6KCk9PmRocixMaW5lYXJNaXBtYXBMaW5lYXJGaWx0ZXI6KCk9Pm94LExpbmVhck1pcG1hcE5lYXJlc3RGaWx0ZXI6KCk9PmtodCxMaW5lYXJUb25lTWFwcGluZzooKT0+b2ZlLExvYWRlcjooKT0+ZWEsTG9hZGVyVXRpbHM6KCk9PmRNLExvYWRpbmdNYW5hZ2VyOigpPT5NNixMb29wT25jZTooKT0+TWZlLExvb3BQaW5nUG9uZzooKT0+VGZlLExvb3BSZXBlYXQ6KCk9PkVmZSxMdW1pbmFuY2VBbHBoYUZvcm1hdDooKT0+X2ZlLEx1bWluYW5jZUZvcm1hdDooKT0+Z2ZlLE1PVVNFOigpPT5LMCxNYXRlcmlhbDooKT0+cWksTWF0ZXJpYWxMb2FkZXI6KCk9PkxVLE1hdGg6KCk9PlFocixNYXRoVXRpbHM6KCk9PlFocixNYXRyaXgzOigpPT5raSxNYXRyaXg0OigpPT5NZSxNYXhFcXVhdGlvbjooKT0+UHV0LE1lc2g6KCk9PmVpLE1lc2hCYXNpY01hdGVyaWFsOigpPT5zaCxNZXNoRGVwdGhNYXRlcmlhbDooKT0+ZU0sTWVzaERpc3RhbmNlTWF0ZXJpYWw6KCk9PnJNLE1lc2hGYWNlTWF0ZXJpYWw6KCk9PlQwcixNZXNoTGFtYmVydE1hdGVyaWFsOigpPT52NixNZXNoTWF0Y2FwTWF0ZXJpYWw6KCk9Png2LE1lc2hOb3JtYWxNYXRlcmlhbDooKT0+eTYsTWVzaFBob25nTWF0ZXJpYWw6KCk9Pmc2LE1lc2hQaHlzaWNhbE1hdGVyaWFsOigpPT5tNixNZXNoU3RhbmRhcmRNYXRlcmlhbDooKT0+cE0sTWVzaFRvb25NYXRlcmlhbDooKT0+XzYsTWluRXF1YXRpb246KCk9PkF1dCxNaXJyb3JlZFJlcGVhdFdyYXBwaW5nOigpPT5YUCxNaXhPcGVyYXRpb246KCk9Pm5mZSxNdWx0aU1hdGVyaWFsOigpPT5DMHIsTXVsdGlwbHlCbGVuZGluZzooKT0+Q3V0LE11bHRpcGx5T3BlcmF0aW9uOigpPT5ENixOZWFyZXN0RmlsdGVyOigpPT5MaSxOZWFyZXN0TWlwTWFwTGluZWFyRmlsdGVyOigpPT5waHIsTmVhcmVzdE1pcE1hcE5lYXJlc3RGaWx0ZXI6KCk9PmZocixOZWFyZXN0TWlwbWFwTGluZWFyRmlsdGVyOigpPT5vVSxOZWFyZXN0TWlwbWFwTmVhcmVzdEZpbHRlcjooKT0+aVUsTmV2ZXJEZXB0aDooKT0+S2hlLE5ldmVyU3RlbmNpbEZ1bmM6KCk9PkVocixOb0JsZW5kaW5nOigpPT4kZCxOb0NvbG9yczooKT0+UzByLE5vVG9uZU1hcHBpbmc6KCk9PktkLE5vcm1hbEFuaW1hdGlvbkJsZW5kTW9kZTooKT0+WFUsTm9ybWFsQmxlbmRpbmc6KCk9PlYzLE5vdEVxdWFsRGVwdGg6KCk9PnJmZSxOb3RFcXVhbFN0ZW5jaWxGdW5jOigpPT5JaHIsTnVtYmVyS2V5ZnJhbWVUcmFjazooKT0+WnYsT2JqZWN0M0Q6KCk9Pm9yLE9iamVjdExvYWRlcjooKT0+bGh0LE9iamVjdFNwYWNlTm9ybWFsTWFwOigpPT5JZmUsT2N0YWhlZHJvbkJ1ZmZlckdlb21ldHJ5OigpPT5XMCxPY3RhaGVkcm9uR2VvbWV0cnk6KCk9PlcwLE9uZUZhY3RvcjooKT0+VWhlLE9uZU1pbnVzRHN0QWxwaGFGYWN0b3I6KCk9PlloZSxPbmVNaW51c0RzdENvbG9yRmFjdG9yOigpPT5YaGUsT25lTWludXNTcmNBbHBoYUZhY3RvcjooKT0+TGh0LE9uZU1pbnVzU3JjQ29sb3JGYWN0b3I6KCk9PkdoZSxPcnRob2dyYXBoaWNDYW1lcmE6KCk9PkR2LFBDRlNoYWRvd01hcDooKT0+QWh0LFBDRlNvZnRTaGFkb3dNYXA6KCk9PnpoZSxQTVJFTUdlbmVyYXRvcjooKT0+dDYsUGFyYW1ldHJpY0dlb21ldHJ5OigpPT5yX3IsUGFydGljbGU6KCk9PlAwcixQYXJ0aWNsZUJhc2ljTWF0ZXJpYWw6KCk9PmswcixQYXJ0aWNsZVN5c3RlbTooKT0+STByLFBhcnRpY2xlU3lzdGVtTWF0ZXJpYWw6KCk9PlIwcixQYXRoOigpPT5xdixQZXJzcGVjdGl2ZUNhbWVyYTooKT0+VWksUGxhbmU6KCk9PiRjLFBsYW5lQnVmZmVyR2VvbWV0cnk6KCk9PlYwLFBsYW5lR2VvbWV0cnk6KCk9PlYwLFBsYW5lSGVscGVyOigpPT5NaHQsUG9pbnRDbG91ZDooKT0+QTByLFBvaW50Q2xvdWRNYXRlcmlhbDooKT0+TDByLFBvaW50TGlnaHQ6KCk9PkE2LFBvaW50TGlnaHRIZWxwZXI6KCk9PnlodCxQb2ludHM6KCk9PmltLFBvaW50c01hdGVyaWFsOigpPT5ubSxQb2xhckdyaWRIZWxwZXI6KCk9PnhodCxQb2x5aGVkcm9uQnVmZmVyR2VvbWV0cnk6KCk9PnVoLFBvbHloZWRyb25HZW9tZXRyeTooKT0+dWgsUG9zaXRpb25hbEF1ZGlvOigpPT5oaHQsUHJvcGVydHlCaW5kaW5nOigpPT5DcixQcm9wZXJ0eU1peGVyOigpPT5GVSxRdWFkcmF0aWNCZXppZXJDdXJ2ZTooKT0+dU0sUXVhZHJhdGljQmV6aWVyQ3VydmUzOigpPT5oTSxRdWF0ZXJuaW9uOigpPT52aSxRdWF0ZXJuaW9uS2V5ZnJhbWVUcmFjazooKT0+WDAsUXVhdGVybmlvbkxpbmVhckludGVycG9sYW50OigpPT5NVSxSRVZJU0lPTjooKT0+WVUsUkdCQURlcHRoUGFja2luZzooKT0+UGZlLFJHQkFGb3JtYXQ6KCk9PlFvLFJHQkFJbnRlZ2VyRm9ybWF0OigpPT53ZmUsUkdCQV9BU1RDXzEweDEwX0Zvcm1hdDooKT0+anV0LFJHQkFfQVNUQ18xMHg1X0Zvcm1hdDooKT0+R3V0LFJHQkFfQVNUQ18xMHg2X0Zvcm1hdDooKT0+V3V0LFJHQkFfQVNUQ18xMHg4X0Zvcm1hdDooKT0+WXV0LFJHQkFfQVNUQ18xMngxMF9Gb3JtYXQ6KCk9Plh1dCxSR0JBX0FTVENfMTJ4MTJfRm9ybWF0OigpPT4kdXQsUkdCQV9BU1RDXzR4NF9Gb3JtYXQ6KCk9Pk91dCxSR0JBX0FTVENfNXg0X0Zvcm1hdDooKT0+enV0LFJHQkFfQVNUQ181eDVfRm9ybWF0OigpPT5GdXQsUkdCQV9BU1RDXzZ4NV9Gb3JtYXQ6KCk9PkJ1dCxSR0JBX0FTVENfNng2X0Zvcm1hdDooKT0+SHV0LFJHQkFfQVNUQ184eDVfRm9ybWF0OigpPT5WdXQsUkdCQV9BU1RDXzh4Nl9Gb3JtYXQ6KCk9PlV1dCxSR0JBX0FTVENfOHg4X0Zvcm1hdDooKT0+cXV0LFJHQkFfQlBUQ19Gb3JtYXQ6KCk9Pkt1dCxSR0JBX0VUQzJfRUFDX0Zvcm1hdDooKT0+RHV0LFJHQkFfUFZSVENfMkJQUFYxX0Zvcm1hdDooKT0+UnV0LFJHQkFfUFZSVENfNEJQUFYxX0Zvcm1hdDooKT0+a3V0LFJHQkFfUzNUQ19EWFQxX0Zvcm1hdDooKT0+SlYsUkdCQV9TM1RDX0RYVDNfRm9ybWF0OigpPT5RVixSR0JBX1MzVENfRFhUNV9Gb3JtYXQ6KCk9PnRVLFJHQl9FVEMxX0Zvcm1hdDooKT0+U2ZlLFJHQl9FVEMyX0Zvcm1hdDooKT0+TnV0LFJHQl9QVlJUQ18yQlBQVjFfRm9ybWF0OigpPT5MdXQsUkdCX1BWUlRDXzRCUFBWMV9Gb3JtYXQ6KCk9Pkl1dCxSR0JfUzNUQ19EWFQxX0Zvcm1hdDooKT0+WlYsUkdGb3JtYXQ6KCk9PnhmZSxSR0ludGVnZXJGb3JtYXQ6KCk9PmJmZSxSYXdTaGFkZXJNYXRlcmlhbDooKT0+VTAsUmF5OigpPT5KZixSYXljYXN0ZXI6KCk9PmRodCxSZWN0QXJlYUxpZ2h0OigpPT5MNixSZWRGb3JtYXQ6KCk9PnlmZSxSZWRJbnRlZ2VyRm9ybWF0OigpPT52ZmUsUmVpbmhhcmRUb25lTWFwcGluZzooKT0+YWZlLFJlcGVhdFdyYXBwaW5nOigpPT5qUCxSZXBsYWNlU3RlbmNpbE9wOigpPT52aHIsUmV2ZXJzZVN1YnRyYWN0RXF1YXRpb246KCk9PkhoZSxSaW5nQnVmZmVyR2VvbWV0cnk6KCk9Pll2LFJpbmdHZW9tZXRyeTooKT0+WXYsU2NlbmU6KCk9PnEwLFNjZW5lVXRpbHM6KCk9PnRfcixTaGFkZXJDaHVuazooKT0+aHIsU2hhZGVyTGliOigpPT5haCxTaGFkZXJNYXRlcmlhbDooKT0+bGgsU2hhZG93TWF0ZXJpYWw6KCk9PmQ2LFNoYXBlOigpPT5LYyxTaGFwZUJ1ZmZlckdlb21ldHJ5OigpPT5ZMCxTaGFwZUdlb21ldHJ5OigpPT5ZMCxTaGFwZVBhdGg6KCk9PlRodCxTaGFwZVV0aWxzOigpPT5aYyxTaG9ydFR5cGU6KCk9PmhmZSxTa2VsZXRvbjooKT0+bE0sU2tlbGV0b25IZWxwZXI6KCk9PkdVLFNraW5uZWRNZXNoOigpPT5hTSxTbW9vdGhTaGFkaW5nOigpPT5oaHIsU3BoZXJlOigpPT5aZixTcGhlcmVCdWZmZXJHZW9tZXRyeTooKT0+ajAsU3BoZXJlR2VvbWV0cnk6KCk9PmowLFNwaGVyaWNhbDooKT0+X00sU3BoZXJpY2FsSGFybW9uaWNzMzooKT0+azYsU3BsaW5lQ3VydmU6KCk9PmZNLFNwb3RMaWdodDooKT0+QzYsU3BvdExpZ2h0SGVscGVyOigpPT5faHQsU3ByaXRlOigpPT5vTSxTcHJpdGVNYXRlcmlhbDooKT0+aU0sU3JjQWxwaGFGYWN0b3I6KCk9PklodCxTcmNBbHBoYVNhdHVyYXRlRmFjdG9yOigpPT4kaGUsU3JjQ29sb3JGYWN0b3I6KCk9PnFoZSxTdGF0aWNDb3B5VXNhZ2U6KCk9Pk9ocixTdGF0aWNEcmF3VXNhZ2U6KCk9PlczLFN0YXRpY1JlYWRVc2FnZTooKT0+UmhyLFN0ZXJlb0NhbWVyYTooKT0+Y2h0LFN0cmVhbUNvcHlVc2FnZTooKT0+RmhyLFN0cmVhbURyYXdVc2FnZTooKT0+a2hyLFN0cmVhbVJlYWRVc2FnZTooKT0+RGhyLFN0cmluZ0tleWZyYW1lVHJhY2s6KCk9PnNtLFN1YnRyYWN0RXF1YXRpb246KCk9PkJoZSxTdWJ0cmFjdGl2ZUJsZW5kaW5nOigpPT5UdXQsVE9VQ0g6KCk9PlowLFRhbmdlbnRTcGFjZU5vcm1hbE1hcDooKT0+YXgsVGV0cmFoZWRyb25CdWZmZXJHZW9tZXRyeTooKT0+anYsVGV0cmFoZWRyb25HZW9tZXRyeTooKT0+anYsVGV4dEdlb21ldHJ5OigpPT5uX3IsVGV4dHVyZTooKT0+eGksVGV4dHVyZUxvYWRlcjooKT0+Q1UsVG9ydXNCdWZmZXJHZW9tZXRyeTooKT0+WHYsVG9ydXNHZW9tZXRyeTooKT0+WHYsVG9ydXNLbm90QnVmZmVyR2VvbWV0cnk6KCk9PiR2LFRvcnVzS25vdEdlb21ldHJ5OigpPT4kdixUcmlhbmdsZTooKT0+YWksVHJpYW5nbGVGYW5EcmF3TW9kZTooKT0+X2hyLFRyaWFuZ2xlU3RyaXBEcmF3TW9kZTooKT0+Z2hyLFRyaWFuZ2xlc0RyYXdNb2RlOigpPT5DZmUsVHViZUJ1ZmZlckdlb21ldHJ5OigpPT5LdixUdWJlR2VvbWV0cnk6KCk9Pkt2LFVWTWFwcGluZzooKT0+alUsVWludDE2QXR0cmlidXRlOigpPT5IMHIsVWludDE2QnVmZmVyQXR0cmlidXRlOigpPT4kMyxVaW50MzJBdHRyaWJ1dGU6KCk9PlUwcixVaW50MzJCdWZmZXJBdHRyaWJ1dGU6KCk9PkszLFVpbnQ4QXR0cmlidXRlOigpPT56MHIsVWludDhCdWZmZXJBdHRyaWJ1dGU6KCk9PmNVLFVpbnQ4Q2xhbXBlZEF0dHJpYnV0ZTooKT0+RjByLFVpbnQ4Q2xhbXBlZEJ1ZmZlckF0dHJpYnV0ZTooKT0+dVUsVW5pZm9ybTooKT0+Z00sVW5pZm9ybXNMaWI6KCk9PnJlLFVuaWZvcm1zVXRpbHM6KCk9Pk9mZSxVbnNpZ25lZEJ5dGVUeXBlOigpPT5aZCxVbnNpZ25lZEludDI0OFR5cGU6KCk9PkF2LFVuc2lnbmVkSW50VHlwZTooKT0+SFAsVW5zaWduZWRTaG9ydDQ0NDRUeXBlOigpPT5wZmUsVW5zaWduZWRTaG9ydDU1NTFUeXBlOigpPT5kZmUsVW5zaWduZWRTaG9ydFR5cGU6KCk9PkczLFZTTVNoYWRvd01hcDooKT0+RjMsVmVjdG9yMjooKT0+THQsVmVjdG9yMzooKT0+aixWZWN0b3I0OigpPT5lbixWZWN0b3JLZXlmcmFtZVRyYWNrOigpPT5KdixWZXJ0ZXg6KCk9Pk4wcixWZXJ0ZXhDb2xvcnM6KCk9PkUwcixWaWRlb1RleHR1cmU6KCk9Pl9VLFdlYkdMMVJlbmRlcmVyOigpPT5tVSxXZWJHTEN1YmVSZW5kZXJUYXJnZXQ6KCk9PlEzLFdlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzOigpPT5zVSxXZWJHTE11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0OigpPT5qMyxXZWJHTFJlbmRlclRhcmdldDooKT0+dXMsV2ViR0xSZW5kZXJUYXJnZXRDdWJlOigpPT5aMHIsV2ViR0xSZW5kZXJlcjooKT0+cm4sV2ViR0xVdGlsczooKT0+WGZlLFdpcmVmcmFtZUdlb21ldHJ5OigpPT5wNixXaXJlZnJhbWVIZWxwZXI6KCk9PlgwcixXcmFwQXJvdW5kRW5kaW5nOigpPT5aUCxYSFJMb2FkZXI6KCk9PiQwcixaZXJvQ3VydmF0dXJlRW5kaW5nOigpPT5FdixaZXJvRmFjdG9yOigpPT5WaGUsWmVyb1Nsb3BlRW5kaW5nOigpPT5UdixaZXJvU3RlbmNpbE9wOigpPT55aHIsX1NSR0JBRm9ybWF0OigpPT5hVSxzUkdCRW5jb2Rpbmc6KCk9PllufSk7dmFyIFlVPSIxMzciLEswPXtMRUZUOjAsTUlERExFOjEsUklHSFQ6MixST1RBVEU6MCxET0xMWToxLFBBTjoyfSxaMD17Uk9UQVRFOjAsUEFOOjEsRE9MTFlfUEFOOjIsRE9MTFlfUk9UQVRFOjN9LERoZT0wLE11dD0xLE9oZT0yLGNocj0zLHVocj0wLEFodD0xLHpoZT0yLEYzPTMsSXY9MCxJaT0xLEx2PTIsUGh0PTEsaGhyPTIsJGQ9MCxWMz0xLEV1dD0yLFR1dD0zLEN1dD00LEZoZT01LE12PTEwMCxCaGU9MTAxLEhoZT0xMDIsQXV0PTEwMyxQdXQ9MTA0LFZoZT0yMDAsVWhlPTIwMSxxaGU9MjAyLEdoZT0yMDMsSWh0PTIwNCxMaHQ9MjA1LFdoZT0yMDYsWWhlPTIwNyxqaGU9MjA4LFhoZT0yMDksJGhlPTIxMCxLaGU9MCxaaGU9MSxKaGU9MixuVT0zLFFoZT00LHRmZT01LGVmZT02LHJmZT03LEQ2PTAsbmZlPTEsaWZlPTIsS2Q9MCxvZmU9MSxhZmU9MixzZmU9MyxsZmU9NCxjZmU9NSxqVT0zMDAsbng9MzAxLGl4PTMwMixXUD0zMDMsWVA9MzA0LHhNPTMwNixPNj0zMDcsalA9MWUzLEpvPTEwMDEsWFA9MTAwMixMaT0xMDAzLGlVPTEwMDQsZmhyPTEwMDQsb1U9MTAwNSxwaHI9MTAwNSxvaT0xMDA2LGtodD0xMDA3LGRocj0xMDA3LG94PTEwMDgsbWhyPTEwMDgsWmQ9MTAwOSx1ZmU9MTAxMCxoZmU9MTAxMSxHMz0xMDEyLGZmZT0xMDEzLEhQPTEwMTQsamQ9MTAxNSxDdj0xMDE2LHBmZT0xMDE3LGRmZT0xMDE4LEF2PTEwMjAsbWZlPTEwMjEsUW89MTAyMyxnZmU9MTAyNCxfZmU9MTAyNSx6MD0xMDI2LGt2PTEwMjcseWZlPTEwMjgsdmZlPTEwMjkseGZlPTEwMzAsYmZlPTEwMzEsd2ZlPTEwMzMsWlY9MzM3NzYsSlY9MzM3NzcsUVY9MzM3NzgsdFU9MzM3NzksSXV0PTM1ODQwLEx1dD0zNTg0MSxrdXQ9MzU4NDIsUnV0PTM1ODQzLFNmZT0zNjE5NixOdXQ9Mzc0OTIsRHV0PTM3NDk2LE91dD0zNzgwOCx6dXQ9Mzc4MDksRnV0PTM3ODEwLEJ1dD0zNzgxMSxIdXQ9Mzc4MTIsVnV0PTM3ODEzLFV1dD0zNzgxNCxxdXQ9Mzc4MTUsR3V0PTM3ODE2LFd1dD0zNzgxNyxZdXQ9Mzc4MTgsanV0PTM3ODE5LFh1dD0zNzgyMCwkdXQ9Mzc4MjEsS3V0PTM2NDkyLE1mZT0yMjAwLEVmZT0yMjAxLFRmZT0yMjAyLCRQPTIzMDAsS1A9MjMwMSxlVT0yMzAyLEV2PTI0MDAsVHY9MjQwMSxaUD0yNDAyLFhVPTI1MDAsUmh0PTI1MDEsQ2ZlPTAsZ2hyPTEsX2hyPTIsUWQ9M2UzLFluPTMwMDEsQWZlPTMyMDAsUGZlPTMyMDEsYXg9MCxJZmU9MSx5aHI9MCxyVT03NjgwLHZocj03NjgxLHhocj03NjgyLGJocj03NjgzLHdocj0zNDA1NSxTaHI9MzQwNTYsTWhyPTUzODYsRWhyPTUxMixUaHI9NTEzLENocj01MTQsQWhyPTUxNSxQaHI9NTE2LElocj01MTcsTGhyPTUxOCxMZmU9NTE5LFczPTM1MDQ0LFkzPTM1MDQ4LGtocj0zNTA0MCxSaHI9MzUwNDUsTmhyPTM1MDQ5LERocj0zNTA0MSxPaHI9MzUwNDYsemhyPTM1MDUwLEZocj0zNTA0MixCaHI9IjEwMCIsWnV0PSIzMDAgZXMiLGFVPTEwMzUsVXM9Y2xhc3N7YWRkRXZlbnRMaXN0ZW5lcih0LHIpe3RoaXMuX2xpc3RlbmVycz09PXZvaWQgMCYmKHRoaXMuX2xpc3RlbmVycz17fSk7bGV0IG49dGhpcy5fbGlzdGVuZXJzO25bdF09PT12b2lkIDAmJihuW3RdPVtdKSxuW3RdLmluZGV4T2Yocik9PT0tMSYmblt0XS5wdXNoKHIpfWhhc0V2ZW50TGlzdGVuZXIodCxyKXtpZih0aGlzLl9saXN0ZW5lcnM9PT12b2lkIDApcmV0dXJuITE7bGV0IG49dGhpcy5fbGlzdGVuZXJzO3JldHVybiBuW3RdIT09dm9pZCAwJiZuW3RdLmluZGV4T2YocikhPT0tMX1yZW1vdmVFdmVudExpc3RlbmVyKHQscil7aWYodGhpcy5fbGlzdGVuZXJzPT09dm9pZCAwKXJldHVybjtsZXQgaT10aGlzLl9saXN0ZW5lcnNbdF07aWYoaSE9PXZvaWQgMCl7bGV0IG89aS5pbmRleE9mKHIpO28hPT0tMSYmaS5zcGxpY2UobywxKX19ZGlzcGF0Y2hFdmVudCh0KXtpZih0aGlzLl9saXN0ZW5lcnM9PT12b2lkIDApcmV0dXJuO2xldCBuPXRoaXMuX2xpc3RlbmVyc1t0LnR5cGVdO2lmKG4hPT12b2lkIDApe3QudGFyZ2V0PXRoaXM7bGV0IGk9bi5zbGljZSgwKTtmb3IobGV0IG89MCxhPWkubGVuZ3RoO288YTtvKyspaVtvXS5jYWxsKHRoaXMsdCk7dC50YXJnZXQ9bnVsbH19fSxLbz1bXTtmb3IobGV0IGU9MDtlPDI1NjtlKyspS29bZV09KGU8MTY/IjAiOiIiKStlLnRvU3RyaW5nKDE2KTt2YXIgY1Y9MTIzNDU2NyxQdj1NYXRoLlBJLzE4MCxKUD0xODAvTWF0aC5QSTtmdW5jdGlvbiBObCgpe2xldCBlPU1hdGgucmFuZG9tKCkqNDI5NDk2NzI5NXwwLHQ9TWF0aC5yYW5kb20oKSo0Mjk0OTY3Mjk1fDAscj1NYXRoLnJhbmRvbSgpKjQyOTQ5NjcyOTV8MCxuPU1hdGgucmFuZG9tKCkqNDI5NDk2NzI5NXwwO3JldHVybihLb1tlJjI1NV0rS29bZT4+OCYyNTVdK0tvW2U+PjE2JjI1NV0rS29bZT4+MjQmMjU1XSsiLSIrS29bdCYyNTVdK0tvW3Q+PjgmMjU1XSsiLSIrS29bdD4+MTYmMTV8NjRdK0tvW3Q+PjI0JjI1NV0rIi0iK0tvW3ImNjN8MTI4XStLb1tyPj44JjI1NV0rIi0iK0tvW3I+PjE2JjI1NV0rS29bcj4+MjQmMjU1XStLb1tuJjI1NV0rS29bbj4+OCYyNTVdK0tvW24+PjE2JjI1NV0rS29bbj4+MjQmMjU1XSkudG9VcHBlckNhc2UoKX1mdW5jdGlvbiBabyhlLHQscil7cmV0dXJuIE1hdGgubWF4KHQsTWF0aC5taW4ocixlKSl9ZnVuY3Rpb24gTmh0KGUsdCl7cmV0dXJuKGUldCt0KSV0fWZ1bmN0aW9uIEhocihlLHQscixuLGkpe3JldHVybiBuKyhlLXQpKihpLW4pLyhyLXQpfWZ1bmN0aW9uIFZocihlLHQscil7cmV0dXJuIGUhPT10PyhyLWUpLyh0LWUpOjB9ZnVuY3Rpb24gVlAoZSx0LHIpe3JldHVybigxLXIpKmUrcip0fWZ1bmN0aW9uIFVocihlLHQscixuKXtyZXR1cm4gVlAoZSx0LDEtTWF0aC5leHAoLXIqbikpfWZ1bmN0aW9uIHFocihlLHQ9MSl7cmV0dXJuIHQtTWF0aC5hYnMoTmh0KGUsdCoyKS10KX1mdW5jdGlvbiBHaHIoZSx0LHIpe3JldHVybiBlPD10PzA6ZT49cj8xOihlPShlLXQpLyhyLXQpLGUqZSooMy0yKmUpKX1mdW5jdGlvbiBXaHIoZSx0LHIpe3JldHVybiBlPD10PzA6ZT49cj8xOihlPShlLXQpLyhyLXQpLGUqZSplKihlKihlKjYtMTUpKzEwKSl9ZnVuY3Rpb24gWWhyKGUsdCl7cmV0dXJuIGUrTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpKih0LWUrMSkpfWZ1bmN0aW9uIGpocihlLHQpe3JldHVybiBlK01hdGgucmFuZG9tKCkqKHQtZSl9ZnVuY3Rpb24gWGhyKGUpe3JldHVybiBlKiguNS1NYXRoLnJhbmRvbSgpKX1mdW5jdGlvbiAkaHIoZSl7cmV0dXJuIGUhPT12b2lkIDAmJihjVj1lJTIxNDc0ODM2NDcpLGNWPWNWKjE2ODA3JTIxNDc0ODM2NDcsKGNWLTEpLzIxNDc0ODM2NDZ9ZnVuY3Rpb24gS2hyKGUpe3JldHVybiBlKlB2fWZ1bmN0aW9uIFpocihlKXtyZXR1cm4gZSpKUH1mdW5jdGlvbiBKdXQoZSl7cmV0dXJuKGUmZS0xKT09PTAmJmUhPT0wfWZ1bmN0aW9uIGtmZShlKXtyZXR1cm4gTWF0aC5wb3coMixNYXRoLmNlaWwoTWF0aC5sb2coZSkvTWF0aC5MTjIpKX1mdW5jdGlvbiBSZmUoZSl7cmV0dXJuIE1hdGgucG93KDIsTWF0aC5mbG9vcihNYXRoLmxvZyhlKS9NYXRoLkxOMikpfWZ1bmN0aW9uIEpocihlLHQscixuLGkpe2xldCBvPU1hdGguY29zLGE9TWF0aC5zaW4scz1vKHIvMiksbD1hKHIvMiksYz1vKCh0K24pLzIpLHU9YSgodCtuKS8yKSxoPW8oKHQtbikvMiksZj1hKCh0LW4pLzIpLHA9bygobi10KS8yKSxkPWEoKG4tdCkvMik7c3dpdGNoKGkpe2Nhc2UiWFlYIjplLnNldChzKnUsbCpoLGwqZixzKmMpO2JyZWFrO2Nhc2UiWVpZIjplLnNldChsKmYscyp1LGwqaCxzKmMpO2JyZWFrO2Nhc2UiWlhaIjplLnNldChsKmgsbCpmLHMqdSxzKmMpO2JyZWFrO2Nhc2UiWFpYIjplLnNldChzKnUsbCpkLGwqcCxzKmMpO2JyZWFrO2Nhc2UiWVhZIjplLnNldChsKnAscyp1LGwqZCxzKmMpO2JyZWFrO2Nhc2UiWllaIjplLnNldChsKmQsbCpwLHMqdSxzKmMpO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS53YXJuKCJUSFJFRS5NYXRoVXRpbHM6IC5zZXRRdWF0ZXJuaW9uRnJvbVByb3BlckV1bGVyKCkgZW5jb3VudGVyZWQgYW4gdW5rbm93biBvcmRlcjogIitpKX19dmFyIFFocj1PYmplY3QuZnJlZXplKHtfX3Byb3RvX186bnVsbCxERUcyUkFEOlB2LFJBRDJERUc6SlAsZ2VuZXJhdGVVVUlEOk5sLGNsYW1wOlpvLGV1Y2xpZGVhbk1vZHVsbzpOaHQsbWFwTGluZWFyOkhocixpbnZlcnNlTGVycDpWaHIsbGVycDpWUCxkYW1wOlVocixwaW5ncG9uZzpxaHIsc21vb3Roc3RlcDpHaHIsc21vb3RoZXJzdGVwOldocixyYW5kSW50OllocixyYW5kRmxvYXQ6amhyLHJhbmRGbG9hdFNwcmVhZDpYaHIsc2VlZGVkUmFuZG9tOiRocixkZWdUb1JhZDpLaHIscmFkVG9EZWc6WmhyLGlzUG93ZXJPZlR3bzpKdXQsY2VpbFBvd2VyT2ZUd286a2ZlLGZsb29yUG93ZXJPZlR3bzpSZmUsc2V0UXVhdGVybmlvbkZyb21Qcm9wZXJFdWxlcjpKaHJ9KSxMdD1jbGFzc3tjb25zdHJ1Y3Rvcih0PTAscj0wKXt0aGlzLng9dCx0aGlzLnk9cn1nZXQgd2lkdGgoKXtyZXR1cm4gdGhpcy54fXNldCB3aWR0aCh0KXt0aGlzLng9dH1nZXQgaGVpZ2h0KCl7cmV0dXJuIHRoaXMueX1zZXQgaGVpZ2h0KHQpe3RoaXMueT10fXNldCh0LHIpe3JldHVybiB0aGlzLng9dCx0aGlzLnk9cix0aGlzfXNldFNjYWxhcih0KXtyZXR1cm4gdGhpcy54PXQsdGhpcy55PXQsdGhpc31zZXRYKHQpe3JldHVybiB0aGlzLng9dCx0aGlzfXNldFkodCl7cmV0dXJuIHRoaXMueT10LHRoaXN9c2V0Q29tcG9uZW50KHQscil7c3dpdGNoKHQpe2Nhc2UgMDp0aGlzLng9cjticmVhaztjYXNlIDE6dGhpcy55PXI7YnJlYWs7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoImluZGV4IGlzIG91dCBvZiByYW5nZTogIit0KX1yZXR1cm4gdGhpc31nZXRDb21wb25lbnQodCl7c3dpdGNoKHQpe2Nhc2UgMDpyZXR1cm4gdGhpcy54O2Nhc2UgMTpyZXR1cm4gdGhpcy55O2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKCJpbmRleCBpcyBvdXQgb2YgcmFuZ2U6ICIrdCl9fWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKHRoaXMueCx0aGlzLnkpfWNvcHkodCl7cmV0dXJuIHRoaXMueD10LngsdGhpcy55PXQueSx0aGlzfWFkZCh0LHIpe3JldHVybiByIT09dm9pZCAwPyhjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjI6IC5hZGQoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5hZGRWZWN0b3JzKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLmFkZFZlY3RvcnModCxyKSk6KHRoaXMueCs9dC54LHRoaXMueSs9dC55LHRoaXMpfWFkZFNjYWxhcih0KXtyZXR1cm4gdGhpcy54Kz10LHRoaXMueSs9dCx0aGlzfWFkZFZlY3RvcnModCxyKXtyZXR1cm4gdGhpcy54PXQueCtyLngsdGhpcy55PXQueStyLnksdGhpc31hZGRTY2FsZWRWZWN0b3IodCxyKXtyZXR1cm4gdGhpcy54Kz10Lngqcix0aGlzLnkrPXQueSpyLHRoaXN9c3ViKHQscil7cmV0dXJuIHIhPT12b2lkIDA/KGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMjogLnN1YigpIG5vdyBvbmx5IGFjY2VwdHMgb25lIGFyZ3VtZW50LiBVc2UgLnN1YlZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMuc3ViVmVjdG9ycyh0LHIpKToodGhpcy54LT10LngsdGhpcy55LT10LnksdGhpcyl9c3ViU2NhbGFyKHQpe3JldHVybiB0aGlzLngtPXQsdGhpcy55LT10LHRoaXN9c3ViVmVjdG9ycyh0LHIpe3JldHVybiB0aGlzLng9dC54LXIueCx0aGlzLnk9dC55LXIueSx0aGlzfW11bHRpcGx5KHQpe3JldHVybiB0aGlzLngqPXQueCx0aGlzLnkqPXQueSx0aGlzfW11bHRpcGx5U2NhbGFyKHQpe3JldHVybiB0aGlzLngqPXQsdGhpcy55Kj10LHRoaXN9ZGl2aWRlKHQpe3JldHVybiB0aGlzLngvPXQueCx0aGlzLnkvPXQueSx0aGlzfWRpdmlkZVNjYWxhcih0KXtyZXR1cm4gdGhpcy5tdWx0aXBseVNjYWxhcigxL3QpfWFwcGx5TWF0cml4Myh0KXtsZXQgcj10aGlzLngsbj10aGlzLnksaT10LmVsZW1lbnRzO3JldHVybiB0aGlzLng9aVswXSpyK2lbM10qbitpWzZdLHRoaXMueT1pWzFdKnIraVs0XSpuK2lbN10sdGhpc31taW4odCl7cmV0dXJuIHRoaXMueD1NYXRoLm1pbih0aGlzLngsdC54KSx0aGlzLnk9TWF0aC5taW4odGhpcy55LHQueSksdGhpc31tYXgodCl7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0aGlzLngsdC54KSx0aGlzLnk9TWF0aC5tYXgodGhpcy55LHQueSksdGhpc31jbGFtcCh0LHIpe3JldHVybiB0aGlzLng9TWF0aC5tYXgodC54LE1hdGgubWluKHIueCx0aGlzLngpKSx0aGlzLnk9TWF0aC5tYXgodC55LE1hdGgubWluKHIueSx0aGlzLnkpKSx0aGlzfWNsYW1wU2NhbGFyKHQscil7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0LE1hdGgubWluKHIsdGhpcy54KSksdGhpcy55PU1hdGgubWF4KHQsTWF0aC5taW4ocix0aGlzLnkpKSx0aGlzfWNsYW1wTGVuZ3RoKHQscil7bGV0IG49dGhpcy5sZW5ndGgoKTtyZXR1cm4gdGhpcy5kaXZpZGVTY2FsYXIobnx8MSkubXVsdGlwbHlTY2FsYXIoTWF0aC5tYXgodCxNYXRoLm1pbihyLG4pKSl9Zmxvb3IoKXtyZXR1cm4gdGhpcy54PU1hdGguZmxvb3IodGhpcy54KSx0aGlzLnk9TWF0aC5mbG9vcih0aGlzLnkpLHRoaXN9Y2VpbCgpe3JldHVybiB0aGlzLng9TWF0aC5jZWlsKHRoaXMueCksdGhpcy55PU1hdGguY2VpbCh0aGlzLnkpLHRoaXN9cm91bmQoKXtyZXR1cm4gdGhpcy54PU1hdGgucm91bmQodGhpcy54KSx0aGlzLnk9TWF0aC5yb3VuZCh0aGlzLnkpLHRoaXN9cm91bmRUb1plcm8oKXtyZXR1cm4gdGhpcy54PXRoaXMueDwwP01hdGguY2VpbCh0aGlzLngpOk1hdGguZmxvb3IodGhpcy54KSx0aGlzLnk9dGhpcy55PDA/TWF0aC5jZWlsKHRoaXMueSk6TWF0aC5mbG9vcih0aGlzLnkpLHRoaXN9bmVnYXRlKCl7cmV0dXJuIHRoaXMueD0tdGhpcy54LHRoaXMueT0tdGhpcy55LHRoaXN9ZG90KHQpe3JldHVybiB0aGlzLngqdC54K3RoaXMueSp0Lnl9Y3Jvc3ModCl7cmV0dXJuIHRoaXMueCp0LnktdGhpcy55KnQueH1sZW5ndGhTcSgpe3JldHVybiB0aGlzLngqdGhpcy54K3RoaXMueSp0aGlzLnl9bGVuZ3RoKCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLngqdGhpcy54K3RoaXMueSp0aGlzLnkpfW1hbmhhdHRhbkxlbmd0aCgpe3JldHVybiBNYXRoLmFicyh0aGlzLngpK01hdGguYWJzKHRoaXMueSl9bm9ybWFsaXplKCl7cmV0dXJuIHRoaXMuZGl2aWRlU2NhbGFyKHRoaXMubGVuZ3RoKCl8fDEpfWFuZ2xlKCl7cmV0dXJuIE1hdGguYXRhbjIoLXRoaXMueSwtdGhpcy54KStNYXRoLlBJfWRpc3RhbmNlVG8odCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLmRpc3RhbmNlVG9TcXVhcmVkKHQpKX1kaXN0YW5jZVRvU3F1YXJlZCh0KXtsZXQgcj10aGlzLngtdC54LG49dGhpcy55LXQueTtyZXR1cm4gcipyK24qbn1tYW5oYXR0YW5EaXN0YW5jZVRvKHQpe3JldHVybiBNYXRoLmFicyh0aGlzLngtdC54KStNYXRoLmFicyh0aGlzLnktdC55KX1zZXRMZW5ndGgodCl7cmV0dXJuIHRoaXMubm9ybWFsaXplKCkubXVsdGlwbHlTY2FsYXIodCl9bGVycCh0LHIpe3JldHVybiB0aGlzLngrPSh0LngtdGhpcy54KSpyLHRoaXMueSs9KHQueS10aGlzLnkpKnIsdGhpc31sZXJwVmVjdG9ycyh0LHIsbil7cmV0dXJuIHRoaXMueD10LngrKHIueC10LngpKm4sdGhpcy55PXQueSsoci55LXQueSkqbix0aGlzfWVxdWFscyh0KXtyZXR1cm4gdC54PT09dGhpcy54JiZ0Lnk9PT10aGlzLnl9ZnJvbUFycmF5KHQscj0wKXtyZXR1cm4gdGhpcy54PXRbcl0sdGhpcy55PXRbcisxXSx0aGlzfXRvQXJyYXkodD1bXSxyPTApe3JldHVybiB0W3JdPXRoaXMueCx0W3IrMV09dGhpcy55LHR9ZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LHIsbil7cmV0dXJuIG4hPT12b2lkIDAmJmNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMjogb2Zmc2V0IGhhcyBiZWVuIHJlbW92ZWQgZnJvbSAuZnJvbUJ1ZmZlckF0dHJpYnV0ZSgpLiIpLHRoaXMueD10LmdldFgociksdGhpcy55PXQuZ2V0WShyKSx0aGlzfXJvdGF0ZUFyb3VuZCh0LHIpe2xldCBuPU1hdGguY29zKHIpLGk9TWF0aC5zaW4ociksbz10aGlzLngtdC54LGE9dGhpcy55LXQueTtyZXR1cm4gdGhpcy54PW8qbi1hKmkrdC54LHRoaXMueT1vKmkrYSpuK3QueSx0aGlzfXJhbmRvbSgpe3JldHVybiB0aGlzLng9TWF0aC5yYW5kb20oKSx0aGlzLnk9TWF0aC5yYW5kb20oKSx0aGlzfSpbU3ltYm9sLml0ZXJhdG9yXSgpe3lpZWxkIHRoaXMueCx5aWVsZCB0aGlzLnl9fTtMdC5wcm90b3R5cGUuaXNWZWN0b3IyPSEwO3ZhciBraT1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuZWxlbWVudHM9WzEsMCwwLDAsMSwwLDAsMCwxXSxhcmd1bWVudHMubGVuZ3RoPjAmJmNvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDM6IHRoZSBjb25zdHJ1Y3RvciBubyBsb25nZXIgcmVhZHMgYXJndW1lbnRzLiB1c2UgLnNldCgpIGluc3RlYWQuIil9c2V0KHQscixuLGksbyxhLHMsbCxjKXtsZXQgdT10aGlzLmVsZW1lbnRzO3JldHVybiB1WzBdPXQsdVsxXT1pLHVbMl09cyx1WzNdPXIsdVs0XT1vLHVbNV09bCx1WzZdPW4sdVs3XT1hLHVbOF09Yyx0aGlzfWlkZW50aXR5KCl7cmV0dXJuIHRoaXMuc2V0KDEsMCwwLDAsMSwwLDAsMCwxKSx0aGlzfWNvcHkodCl7bGV0IHI9dGhpcy5lbGVtZW50cyxuPXQuZWxlbWVudHM7cmV0dXJuIHJbMF09blswXSxyWzFdPW5bMV0sclsyXT1uWzJdLHJbM109blszXSxyWzRdPW5bNF0scls1XT1uWzVdLHJbNl09bls2XSxyWzddPW5bN10scls4XT1uWzhdLHRoaXN9ZXh0cmFjdEJhc2lzKHQscixuKXtyZXR1cm4gdC5zZXRGcm9tTWF0cml4M0NvbHVtbih0aGlzLDApLHIuc2V0RnJvbU1hdHJpeDNDb2x1bW4odGhpcywxKSxuLnNldEZyb21NYXRyaXgzQ29sdW1uKHRoaXMsMiksdGhpc31zZXRGcm9tTWF0cml4NCh0KXtsZXQgcj10LmVsZW1lbnRzO3JldHVybiB0aGlzLnNldChyWzBdLHJbNF0scls4XSxyWzFdLHJbNV0scls5XSxyWzJdLHJbNl0sclsxMF0pLHRoaXN9bXVsdGlwbHkodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlNYXRyaWNlcyh0aGlzLHQpfXByZW11bHRpcGx5KHQpe3JldHVybiB0aGlzLm11bHRpcGx5TWF0cmljZXModCx0aGlzKX1tdWx0aXBseU1hdHJpY2VzKHQscil7bGV0IG49dC5lbGVtZW50cyxpPXIuZWxlbWVudHMsbz10aGlzLmVsZW1lbnRzLGE9blswXSxzPW5bM10sbD1uWzZdLGM9blsxXSx1PW5bNF0saD1uWzddLGY9blsyXSxwPW5bNV0sZD1uWzhdLGc9aVswXSxfPWlbM10seT1pWzZdLHg9aVsxXSxiPWlbNF0sUz1pWzddLEM9aVsyXSxQPWlbNV0saz1pWzhdO3JldHVybiBvWzBdPWEqZytzKngrbCpDLG9bM109YSpfK3MqYitsKlAsb1s2XT1hKnkrcypTK2wqayxvWzFdPWMqZyt1KngraCpDLG9bNF09YypfK3UqYitoKlAsb1s3XT1jKnkrdSpTK2gqayxvWzJdPWYqZytwKngrZCpDLG9bNV09ZipfK3AqYitkKlAsb1s4XT1mKnkrcCpTK2Qqayx0aGlzfW11bHRpcGx5U2NhbGFyKHQpe2xldCByPXRoaXMuZWxlbWVudHM7cmV0dXJuIHJbMF0qPXQsclszXSo9dCxyWzZdKj10LHJbMV0qPXQscls0XSo9dCxyWzddKj10LHJbMl0qPXQscls1XSo9dCxyWzhdKj10LHRoaXN9ZGV0ZXJtaW5hbnQoKXtsZXQgdD10aGlzLmVsZW1lbnRzLHI9dFswXSxuPXRbMV0saT10WzJdLG89dFszXSxhPXRbNF0scz10WzVdLGw9dFs2XSxjPXRbN10sdT10WzhdO3JldHVybiByKmEqdS1yKnMqYy1uKm8qdStuKnMqbCtpKm8qYy1pKmEqbH1pbnZlcnQoKXtsZXQgdD10aGlzLmVsZW1lbnRzLHI9dFswXSxuPXRbMV0saT10WzJdLG89dFszXSxhPXRbNF0scz10WzVdLGw9dFs2XSxjPXRbN10sdT10WzhdLGg9dSphLXMqYyxmPXMqbC11Km8scD1jKm8tYSpsLGQ9cipoK24qZitpKnA7aWYoZD09PTApcmV0dXJuIHRoaXMuc2V0KDAsMCwwLDAsMCwwLDAsMCwwKTtsZXQgZz0xL2Q7cmV0dXJuIHRbMF09aCpnLHRbMV09KGkqYy11Km4pKmcsdFsyXT0ocypuLWkqYSkqZyx0WzNdPWYqZyx0WzRdPSh1KnItaSpsKSpnLHRbNV09KGkqby1zKnIpKmcsdFs2XT1wKmcsdFs3XT0obipsLWMqcikqZyx0WzhdPShhKnItbipvKSpnLHRoaXN9dHJhbnNwb3NlKCl7bGV0IHQscj10aGlzLmVsZW1lbnRzO3JldHVybiB0PXJbMV0sclsxXT1yWzNdLHJbM109dCx0PXJbMl0sclsyXT1yWzZdLHJbNl09dCx0PXJbNV0scls1XT1yWzddLHJbN109dCx0aGlzfWdldE5vcm1hbE1hdHJpeCh0KXtyZXR1cm4gdGhpcy5zZXRGcm9tTWF0cml4NCh0KS5pbnZlcnQoKS50cmFuc3Bvc2UoKX10cmFuc3Bvc2VJbnRvQXJyYXkodCl7bGV0IHI9dGhpcy5lbGVtZW50cztyZXR1cm4gdFswXT1yWzBdLHRbMV09clszXSx0WzJdPXJbNl0sdFszXT1yWzFdLHRbNF09cls0XSx0WzVdPXJbN10sdFs2XT1yWzJdLHRbN109cls1XSx0WzhdPXJbOF0sdGhpc31zZXRVdlRyYW5zZm9ybSh0LHIsbixpLG8sYSxzKXtsZXQgbD1NYXRoLmNvcyhvKSxjPU1hdGguc2luKG8pO3JldHVybiB0aGlzLnNldChuKmwsbipjLC1uKihsKmErYypzKSthK3QsLWkqYyxpKmwsLWkqKC1jKmErbCpzKStzK3IsMCwwLDEpLHRoaXN9c2NhbGUodCxyKXtsZXQgbj10aGlzLmVsZW1lbnRzO3JldHVybiBuWzBdKj10LG5bM10qPXQsbls2XSo9dCxuWzFdKj1yLG5bNF0qPXIsbls3XSo9cix0aGlzfXJvdGF0ZSh0KXtsZXQgcj1NYXRoLmNvcyh0KSxuPU1hdGguc2luKHQpLGk9dGhpcy5lbGVtZW50cyxvPWlbMF0sYT1pWzNdLHM9aVs2XSxsPWlbMV0sYz1pWzRdLHU9aVs3XTtyZXR1cm4gaVswXT1yKm8rbipsLGlbM109ciphK24qYyxpWzZdPXIqcytuKnUsaVsxXT0tbipvK3IqbCxpWzRdPS1uKmErcipjLGlbN109LW4qcytyKnUsdGhpc310cmFuc2xhdGUodCxyKXtsZXQgbj10aGlzLmVsZW1lbnRzO3JldHVybiBuWzBdKz10Km5bMl0sblszXSs9dCpuWzVdLG5bNl0rPXQqbls4XSxuWzFdKz1yKm5bMl0sbls0XSs9cipuWzVdLG5bN10rPXIqbls4XSx0aGlzfWVxdWFscyh0KXtsZXQgcj10aGlzLmVsZW1lbnRzLG49dC5lbGVtZW50cztmb3IobGV0IGk9MDtpPDk7aSsrKWlmKHJbaV0hPT1uW2ldKXJldHVybiExO3JldHVybiEwfWZyb21BcnJheSh0LHI9MCl7Zm9yKGxldCBuPTA7bjw5O24rKyl0aGlzLmVsZW1lbnRzW25dPXRbbityXTtyZXR1cm4gdGhpc310b0FycmF5KHQ9W10scj0wKXtsZXQgbj10aGlzLmVsZW1lbnRzO3JldHVybiB0W3JdPW5bMF0sdFtyKzFdPW5bMV0sdFtyKzJdPW5bMl0sdFtyKzNdPW5bM10sdFtyKzRdPW5bNF0sdFtyKzVdPW5bNV0sdFtyKzZdPW5bNl0sdFtyKzddPW5bN10sdFtyKzhdPW5bOF0sdH1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmZyb21BcnJheSh0aGlzLmVsZW1lbnRzKX19O2tpLnByb3RvdHlwZS5pc01hdHJpeDM9ITA7ZnVuY3Rpb24gTmZlKGUpe2ZvcihsZXQgdD1lLmxlbmd0aC0xO3Q+PTA7LS10KWlmKGVbdF0+NjU1MzUpcmV0dXJuITA7cmV0dXJuITF9dmFyIHRmcj17SW50OEFycmF5LFVpbnQ4QXJyYXksVWludDhDbGFtcGVkQXJyYXksSW50MTZBcnJheSxVaW50MTZBcnJheSxJbnQzMkFycmF5LFVpbnQzMkFycmF5LEZsb2F0MzJBcnJheSxGbG9hdDY0QXJyYXl9O2Z1bmN0aW9uIEIzKGUsdCl7cmV0dXJuIG5ldyB0ZnJbZV0odCl9ZnVuY3Rpb24gUVAoZSl7cmV0dXJuIGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUygiaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIsZSl9dmFyIERmZT17YWxpY2VibHVlOjE1NzkyMzgzLGFudGlxdWV3aGl0ZToxNjQ0NDM3NSxhcXVhOjY1NTM1LGFxdWFtYXJpbmU6ODM4ODU2NCxhenVyZToxNTc5NDE3NSxiZWlnZToxNjExOTI2MCxiaXNxdWU6MTY3NzAyNDQsYmxhY2s6MCxibGFuY2hlZGFsbW9uZDoxNjc3MjA0NSxibHVlOjI1NSxibHVldmlvbGV0OjkwNTUyMDIsYnJvd246MTA4MjQyMzQsYnVybHl3b29kOjE0NTk2MjMxLGNhZGV0Ymx1ZTo2MjY2NTI4LGNoYXJ0cmV1c2U6ODM4ODM1MixjaG9jb2xhdGU6MTM3ODk0NzAsY29yYWw6MTY3NDQyNzIsY29ybmZsb3dlcmJsdWU6NjU5MTk4MSxjb3Juc2lsazoxNjc3NTM4OCxjcmltc29uOjE0NDIzMTAwLGN5YW46NjU1MzUsZGFya2JsdWU6MTM5LGRhcmtjeWFuOjM1NzIzLGRhcmtnb2xkZW5yb2Q6MTIwOTI5MzksZGFya2dyYXk6MTExMTkwMTcsZGFya2dyZWVuOjI1NjAwLGRhcmtncmV5OjExMTE5MDE3LGRhcmtraGFraToxMjQzMzI1OSxkYXJrbWFnZW50YTo5MTA5NjQzLGRhcmtvbGl2ZWdyZWVuOjU1OTc5OTksZGFya29yYW5nZToxNjc0NzUyMCxkYXJrb3JjaGlkOjEwMDQwMDEyLGRhcmtyZWQ6OTEwOTUwNCxkYXJrc2FsbW9uOjE1MzA4NDEwLGRhcmtzZWFncmVlbjo5NDE5OTE5LGRhcmtzbGF0ZWJsdWU6NDczNDM0NyxkYXJrc2xhdGVncmF5OjMxMDA0OTUsZGFya3NsYXRlZ3JleTozMTAwNDk1LGRhcmt0dXJxdW9pc2U6NTI5NDUsZGFya3Zpb2xldDo5Njk5NTM5LGRlZXBwaW5rOjE2NzE2OTQ3LGRlZXBza3libHVlOjQ5MTUxLGRpbWdyYXk6NjkwODI2NSxkaW1ncmV5OjY5MDgyNjUsZG9kZ2VyYmx1ZToyMDAzMTk5LGZpcmVicmljazoxMTY3NDE0NixmbG9yYWx3aGl0ZToxNjc3NTkyMCxmb3Jlc3RncmVlbjoyMjYzODQyLGZ1Y2hzaWE6MTY3MTE5MzUsZ2FpbnNib3JvOjE0NDc0NDYwLGdob3N0d2hpdGU6MTYzMTY2NzEsZ29sZDoxNjc2NjcyMCxnb2xkZW5yb2Q6MTQzMjkxMjAsZ3JheTo4NDIxNTA0LGdyZWVuOjMyNzY4LGdyZWVueWVsbG93OjExNDAzMDU1LGdyZXk6ODQyMTUwNCxob25leWRldzoxNTc5NDE2MCxob3RwaW5rOjE2NzM4NzQwLGluZGlhbnJlZDoxMzQ1ODUyNCxpbmRpZ286NDkxNTMzMCxpdm9yeToxNjc3NzIwMCxraGFraToxNTc4NzY2MCxsYXZlbmRlcjoxNTEzMjQxMCxsYXZlbmRlcmJsdXNoOjE2NzczMzY1LGxhd25ncmVlbjo4MTkwOTc2LGxlbW9uY2hpZmZvbjoxNjc3NTg4NSxsaWdodGJsdWU6MTEzOTMyNTQsbGlnaHRjb3JhbDoxNTc2MTUzNixsaWdodGN5YW46MTQ3NDU1OTksbGlnaHRnb2xkZW5yb2R5ZWxsb3c6MTY0NDgyMTAsbGlnaHRncmF5OjEzODgyMzIzLGxpZ2h0Z3JlZW46OTQ5ODI1NixsaWdodGdyZXk6MTM4ODIzMjMsbGlnaHRwaW5rOjE2NzU4NDY1LGxpZ2h0c2FsbW9uOjE2NzUyNzYyLGxpZ2h0c2VhZ3JlZW46MjE0Mjg5MCxsaWdodHNreWJsdWU6ODkwMDM0NixsaWdodHNsYXRlZ3JheTo3ODMzNzUzLGxpZ2h0c2xhdGVncmV5Ojc4MzM3NTMsbGlnaHRzdGVlbGJsdWU6MTE1ODQ3MzQsbGlnaHR5ZWxsb3c6MTY3NzcxODQsbGltZTo2NTI4MCxsaW1lZ3JlZW46MzMyOTMzMCxsaW5lbjoxNjQ0NTY3MCxtYWdlbnRhOjE2NzExOTM1LG1hcm9vbjo4Mzg4NjA4LG1lZGl1bWFxdWFtYXJpbmU6NjczNzMyMixtZWRpdW1ibHVlOjIwNSxtZWRpdW1vcmNoaWQ6MTIyMTE2NjcsbWVkaXVtcHVycGxlOjk2NjI2ODMsbWVkaXVtc2VhZ3JlZW46Mzk3ODA5NyxtZWRpdW1zbGF0ZWJsdWU6ODA4Nzc5MCxtZWRpdW1zcHJpbmdncmVlbjo2NDE1NCxtZWRpdW10dXJxdW9pc2U6NDc3MjMwMCxtZWRpdW12aW9sZXRyZWQ6MTMwNDcxNzMsbWlkbmlnaHRibHVlOjE2NDQ5MTIsbWludGNyZWFtOjE2MTIxODUwLG1pc3R5cm9zZToxNjc3MDI3Myxtb2NjYXNpbjoxNjc3MDIyOSxuYXZham93aGl0ZToxNjc2ODY4NSxuYXZ5OjEyOCxvbGRsYWNlOjE2NjQzNTU4LG9saXZlOjg0MjEzNzYsb2xpdmVkcmFiOjcwNDg3Mzksb3JhbmdlOjE2NzUzOTIwLG9yYW5nZXJlZDoxNjcyOTM0NCxvcmNoaWQ6MTQzMTU3MzQscGFsZWdvbGRlbnJvZDoxNTY1NzEzMCxwYWxlZ3JlZW46MTAwMjU4ODAscGFsZXR1cnF1b2lzZToxMTUyOTk2NixwYWxldmlvbGV0cmVkOjE0MzgxMjAzLHBhcGF5YXdoaXA6MTY3NzMwNzcscGVhY2hwdWZmOjE2NzY3NjczLHBlcnU6MTM0Njg5OTEscGluazoxNjc2MTAzNSxwbHVtOjE0NTI0NjM3LHBvd2RlcmJsdWU6MTE1OTE5MTAscHVycGxlOjgzODg3MzYscmViZWNjYXB1cnBsZTo2Njk3ODgxLHJlZDoxNjcxMTY4MCxyb3N5YnJvd246MTIzNTc1MTkscm95YWxibHVlOjQyODY5NDUsc2FkZGxlYnJvd246OTEyNzE4NyxzYWxtb246MTY0MTY4ODIsc2FuZHlicm93bjoxNjAzMjg2NCxzZWFncmVlbjozMDUwMzI3LHNlYXNoZWxsOjE2Nzc0NjM4LHNpZW5uYToxMDUwNjc5NyxzaWx2ZXI6MTI2MzIyNTYsc2t5Ymx1ZTo4OTAwMzMxLHNsYXRlYmx1ZTo2OTcwMDYxLHNsYXRlZ3JheTo3MzcyOTQ0LHNsYXRlZ3JleTo3MzcyOTQ0LHNub3c6MTY3NzU5MzAsc3ByaW5nZ3JlZW46NjU0MDcsc3RlZWxibHVlOjQ2MjA5ODAsdGFuOjEzODA4NzgwLHRlYWw6MzI4OTYsdGhpc3RsZToxNDIwNDg4OCx0b21hdG86MTY3MzcwOTUsdHVycXVvaXNlOjQyNTE4NTYsdmlvbGV0OjE1NjMxMDg2LHdoZWF0OjE2MTEzMzMxLHdoaXRlOjE2Nzc3MjE1LHdoaXRlc21va2U6MTYxMTkyODUseWVsbG93OjE2Nzc2OTYwLHllbGxvd2dyZWVuOjEwMTQ1MDc0fSxuaD17aDowLHM6MCxsOjB9LHVWPXtoOjAsczowLGw6MH07ZnVuY3Rpb24gV2N0KGUsdCxyKXtyZXR1cm4gcjwwJiYocis9MSkscj4xJiYoci09MSkscjwxLzY/ZSsodC1lKSo2KnI6cjwxLzI/dDpyPDIvMz9lKyh0LWUpKjYqKDIvMy1yKTplfWZ1bmN0aW9uIFUzKGUpe3JldHVybiBlPC4wNDA0NT9lKi4wNzczOTkzODA4Ok1hdGgucG93KGUqLjk0Nzg2NzI5ODYrLjA1MjEzMjcwMTQsMi40KX1mdW5jdGlvbiBZY3QoZSl7cmV0dXJuIGU8LjAwMzEzMDg/ZSoxMi45MjoxLjA1NSpNYXRoLnBvdyhlLC40MTY2NiktLjA1NX12YXIgbmU9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4pe3JldHVybiByPT09dm9pZCAwJiZuPT09dm9pZCAwP3RoaXMuc2V0KHQpOnRoaXMuc2V0UkdCKHQscixuKX1zZXQodCl7cmV0dXJuIHQmJnQuaXNDb2xvcj90aGlzLmNvcHkodCk6dHlwZW9mIHQ9PSJudW1iZXIiP3RoaXMuc2V0SGV4KHQpOnR5cGVvZiB0PT0ic3RyaW5nIiYmdGhpcy5zZXRTdHlsZSh0KSx0aGlzfXNldFNjYWxhcih0KXtyZXR1cm4gdGhpcy5yPXQsdGhpcy5nPXQsdGhpcy5iPXQsdGhpc31zZXRIZXgodCl7cmV0dXJuIHQ9TWF0aC5mbG9vcih0KSx0aGlzLnI9KHQ+PjE2JjI1NSkvMjU1LHRoaXMuZz0odD4+OCYyNTUpLzI1NSx0aGlzLmI9KHQmMjU1KS8yNTUsdGhpc31zZXRSR0IodCxyLG4pe3JldHVybiB0aGlzLnI9dCx0aGlzLmc9cix0aGlzLmI9bix0aGlzfXNldEhTTCh0LHIsbil7aWYodD1OaHQodCwxKSxyPVpvKHIsMCwxKSxuPVpvKG4sMCwxKSxyPT09MCl0aGlzLnI9dGhpcy5nPXRoaXMuYj1uO2Vsc2V7bGV0IGk9bjw9LjU/biooMStyKTpuK3ItbipyLG89MipuLWk7dGhpcy5yPVdjdChvLGksdCsxLzMpLHRoaXMuZz1XY3QobyxpLHQpLHRoaXMuYj1XY3QobyxpLHQtMS8zKX1yZXR1cm4gdGhpc31zZXRTdHlsZSh0KXtmdW5jdGlvbiByKGkpe2khPT12b2lkIDAmJnBhcnNlRmxvYXQoaSk8MSYmY29uc29sZS53YXJuKCJUSFJFRS5Db2xvcjogQWxwaGEgY29tcG9uZW50IG9mICIrdCsiIHdpbGwgYmUgaWdub3JlZC4iKX1sZXQgbjtpZihuPS9eKCg/OnJnYnxoc2wpYT8pXCgoW15cKV0qKVwpLy5leGVjKHQpKXtsZXQgaSxvPW5bMV0sYT1uWzJdO3N3aXRjaChvKXtjYXNlInJnYiI6Y2FzZSJyZ2JhIjppZihpPS9eXHMqKFxkKylccyosXHMqKFxkKylccyosXHMqKFxkKylccyooPzosXHMqKFxkKlwuP1xkKylccyopPyQvLmV4ZWMoYSkpcmV0dXJuIHRoaXMucj1NYXRoLm1pbigyNTUscGFyc2VJbnQoaVsxXSwxMCkpLzI1NSx0aGlzLmc9TWF0aC5taW4oMjU1LHBhcnNlSW50KGlbMl0sMTApKS8yNTUsdGhpcy5iPU1hdGgubWluKDI1NSxwYXJzZUludChpWzNdLDEwKSkvMjU1LHIoaVs0XSksdGhpcztpZihpPS9eXHMqKFxkKylcJVxzKixccyooXGQrKVwlXHMqLFxzKihcZCspXCVccyooPzosXHMqKFxkKlwuP1xkKylccyopPyQvLmV4ZWMoYSkpcmV0dXJuIHRoaXMucj1NYXRoLm1pbigxMDAscGFyc2VJbnQoaVsxXSwxMCkpLzEwMCx0aGlzLmc9TWF0aC5taW4oMTAwLHBhcnNlSW50KGlbMl0sMTApKS8xMDAsdGhpcy5iPU1hdGgubWluKDEwMCxwYXJzZUludChpWzNdLDEwKSkvMTAwLHIoaVs0XSksdGhpczticmVhaztjYXNlImhzbCI6Y2FzZSJoc2xhIjppZihpPS9eXHMqKFxkKlwuP1xkKylccyosXHMqKFxkKylcJVxzKixccyooXGQrKVwlXHMqKD86LFxzKihcZCpcLj9cZCspXHMqKT8kLy5leGVjKGEpKXtsZXQgcz1wYXJzZUZsb2F0KGlbMV0pLzM2MCxsPXBhcnNlSW50KGlbMl0sMTApLzEwMCxjPXBhcnNlSW50KGlbM10sMTApLzEwMDtyZXR1cm4gcihpWzRdKSx0aGlzLnNldEhTTChzLGwsYyl9YnJlYWt9fWVsc2UgaWYobj0vXlwjKFtBLUZhLWZcZF0rKSQvLmV4ZWModCkpe2xldCBpPW5bMV0sbz1pLmxlbmd0aDtpZihvPT09MylyZXR1cm4gdGhpcy5yPXBhcnNlSW50KGkuY2hhckF0KDApK2kuY2hhckF0KDApLDE2KS8yNTUsdGhpcy5nPXBhcnNlSW50KGkuY2hhckF0KDEpK2kuY2hhckF0KDEpLDE2KS8yNTUsdGhpcy5iPXBhcnNlSW50KGkuY2hhckF0KDIpK2kuY2hhckF0KDIpLDE2KS8yNTUsdGhpcztpZihvPT09NilyZXR1cm4gdGhpcy5yPXBhcnNlSW50KGkuY2hhckF0KDApK2kuY2hhckF0KDEpLDE2KS8yNTUsdGhpcy5nPXBhcnNlSW50KGkuY2hhckF0KDIpK2kuY2hhckF0KDMpLDE2KS8yNTUsdGhpcy5iPXBhcnNlSW50KGkuY2hhckF0KDQpK2kuY2hhckF0KDUpLDE2KS8yNTUsdGhpc31yZXR1cm4gdCYmdC5sZW5ndGg+MD90aGlzLnNldENvbG9yTmFtZSh0KTp0aGlzfXNldENvbG9yTmFtZSh0KXtsZXQgcj1EZmVbdC50b0xvd2VyQ2FzZSgpXTtyZXR1cm4gciE9PXZvaWQgMD90aGlzLnNldEhleChyKTpjb25zb2xlLndhcm4oIlRIUkVFLkNvbG9yOiBVbmtub3duIGNvbG9yICIrdCksdGhpc31jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3Rvcih0aGlzLnIsdGhpcy5nLHRoaXMuYil9Y29weSh0KXtyZXR1cm4gdGhpcy5yPXQucix0aGlzLmc9dC5nLHRoaXMuYj10LmIsdGhpc31jb3B5U1JHQlRvTGluZWFyKHQpe3JldHVybiB0aGlzLnI9VTModC5yKSx0aGlzLmc9VTModC5nKSx0aGlzLmI9VTModC5iKSx0aGlzfWNvcHlMaW5lYXJUb1NSR0IodCl7cmV0dXJuIHRoaXMucj1ZY3QodC5yKSx0aGlzLmc9WWN0KHQuZyksdGhpcy5iPVljdCh0LmIpLHRoaXN9Y29udmVydFNSR0JUb0xpbmVhcigpe3JldHVybiB0aGlzLmNvcHlTUkdCVG9MaW5lYXIodGhpcyksdGhpc31jb252ZXJ0TGluZWFyVG9TUkdCKCl7cmV0dXJuIHRoaXMuY29weUxpbmVhclRvU1JHQih0aGlzKSx0aGlzfWdldEhleCgpe3JldHVybiB0aGlzLnIqMjU1PDwxNl50aGlzLmcqMjU1PDw4XnRoaXMuYioyNTU8PDB9Z2V0SGV4U3RyaW5nKCl7cmV0dXJuKCIwMDAwMDAiK3RoaXMuZ2V0SGV4KCkudG9TdHJpbmcoMTYpKS5zbGljZSgtNil9Z2V0SFNMKHQpe2xldCByPXRoaXMucixuPXRoaXMuZyxpPXRoaXMuYixvPU1hdGgubWF4KHIsbixpKSxhPU1hdGgubWluKHIsbixpKSxzLGwsYz0oYStvKS8yO2lmKGE9PT1vKXM9MCxsPTA7ZWxzZXtsZXQgdT1vLWE7c3dpdGNoKGw9Yzw9LjU/dS8obythKTp1LygyLW8tYSksbyl7Y2FzZSByOnM9KG4taSkvdSsobjxpPzY6MCk7YnJlYWs7Y2FzZSBuOnM9KGktcikvdSsyO2JyZWFrO2Nhc2UgaTpzPShyLW4pL3UrNDticmVha31zLz02fXJldHVybiB0Lmg9cyx0LnM9bCx0Lmw9Yyx0fWdldFN0eWxlKCl7cmV0dXJuInJnYigiKyh0aGlzLnIqMjU1fDApKyIsIisodGhpcy5nKjI1NXwwKSsiLCIrKHRoaXMuYioyNTV8MCkrIikifW9mZnNldEhTTCh0LHIsbil7cmV0dXJuIHRoaXMuZ2V0SFNMKG5oKSxuaC5oKz10LG5oLnMrPXIsbmgubCs9bix0aGlzLnNldEhTTChuaC5oLG5oLnMsbmgubCksdGhpc31hZGQodCl7cmV0dXJuIHRoaXMucis9dC5yLHRoaXMuZys9dC5nLHRoaXMuYis9dC5iLHRoaXN9YWRkQ29sb3JzKHQscil7cmV0dXJuIHRoaXMucj10LnIrci5yLHRoaXMuZz10Lmcrci5nLHRoaXMuYj10LmIrci5iLHRoaXN9YWRkU2NhbGFyKHQpe3JldHVybiB0aGlzLnIrPXQsdGhpcy5nKz10LHRoaXMuYis9dCx0aGlzfXN1Yih0KXtyZXR1cm4gdGhpcy5yPU1hdGgubWF4KDAsdGhpcy5yLXQuciksdGhpcy5nPU1hdGgubWF4KDAsdGhpcy5nLXQuZyksdGhpcy5iPU1hdGgubWF4KDAsdGhpcy5iLXQuYiksdGhpc31tdWx0aXBseSh0KXtyZXR1cm4gdGhpcy5yKj10LnIsdGhpcy5nKj10LmcsdGhpcy5iKj10LmIsdGhpc31tdWx0aXBseVNjYWxhcih0KXtyZXR1cm4gdGhpcy5yKj10LHRoaXMuZyo9dCx0aGlzLmIqPXQsdGhpc31sZXJwKHQscil7cmV0dXJuIHRoaXMucis9KHQuci10aGlzLnIpKnIsdGhpcy5nKz0odC5nLXRoaXMuZykqcix0aGlzLmIrPSh0LmItdGhpcy5iKSpyLHRoaXN9bGVycENvbG9ycyh0LHIsbil7cmV0dXJuIHRoaXMucj10LnIrKHIuci10LnIpKm4sdGhpcy5nPXQuZysoci5nLXQuZykqbix0aGlzLmI9dC5iKyhyLmItdC5iKSpuLHRoaXN9bGVycEhTTCh0LHIpe3RoaXMuZ2V0SFNMKG5oKSx0LmdldEhTTCh1Vik7bGV0IG49VlAobmguaCx1Vi5oLHIpLGk9VlAobmgucyx1Vi5zLHIpLG89VlAobmgubCx1Vi5sLHIpO3JldHVybiB0aGlzLnNldEhTTChuLGksbyksdGhpc31lcXVhbHModCl7cmV0dXJuIHQucj09PXRoaXMuciYmdC5nPT09dGhpcy5nJiZ0LmI9PT10aGlzLmJ9ZnJvbUFycmF5KHQscj0wKXtyZXR1cm4gdGhpcy5yPXRbcl0sdGhpcy5nPXRbcisxXSx0aGlzLmI9dFtyKzJdLHRoaXN9dG9BcnJheSh0PVtdLHI9MCl7cmV0dXJuIHRbcl09dGhpcy5yLHRbcisxXT10aGlzLmcsdFtyKzJdPXRoaXMuYix0fWZyb21CdWZmZXJBdHRyaWJ1dGUodCxyKXtyZXR1cm4gdGhpcy5yPXQuZ2V0WChyKSx0aGlzLmc9dC5nZXRZKHIpLHRoaXMuYj10LmdldFoociksdC5ub3JtYWxpemVkPT09ITAmJih0aGlzLnIvPTI1NSx0aGlzLmcvPTI1NSx0aGlzLmIvPTI1NSksdGhpc310b0pTT04oKXtyZXR1cm4gdGhpcy5nZXRIZXgoKX19O25lLk5BTUVTPURmZTtuZS5wcm90b3R5cGUuaXNDb2xvcj0hMDtuZS5wcm90b3R5cGUucj0xO25lLnByb3RvdHlwZS5nPTE7bmUucHJvdG90eXBlLmI9MTt2YXIgdjMsS2Y9Y2xhc3N7c3RhdGljIGdldERhdGFVUkwodCl7aWYoL15kYXRhOi9pLnRlc3QodC5zcmMpfHx0eXBlb2YgSFRNTENhbnZhc0VsZW1lbnQ9PSJ1bmRlZmluZWQiKXJldHVybiB0LnNyYztsZXQgcjtpZih0IGluc3RhbmNlb2YgSFRNTENhbnZhc0VsZW1lbnQpcj10O2Vsc2V7djM9PT12b2lkIDAmJih2Mz1RUCgiY2FudmFzIikpLHYzLndpZHRoPXQud2lkdGgsdjMuaGVpZ2h0PXQuaGVpZ2h0O2xldCBuPXYzLmdldENvbnRleHQoIjJkIik7dCBpbnN0YW5jZW9mIEltYWdlRGF0YT9uLnB1dEltYWdlRGF0YSh0LDAsMCk6bi5kcmF3SW1hZ2UodCwwLDAsdC53aWR0aCx0LmhlaWdodCkscj12M31yZXR1cm4gci53aWR0aD4yMDQ4fHxyLmhlaWdodD4yMDQ4Pyhjb25zb2xlLndhcm4oIlRIUkVFLkltYWdlVXRpbHMuZ2V0RGF0YVVSTDogSW1hZ2UgY29udmVydGVkIHRvIGpwZyBmb3IgcGVyZm9ybWFuY2UgcmVhc29ucyIsdCksci50b0RhdGFVUkwoImltYWdlL2pwZWciLC42KSk6ci50b0RhdGFVUkwoImltYWdlL3BuZyIpfXN0YXRpYyBzUkdCVG9MaW5lYXIodCl7aWYodHlwZW9mIEhUTUxJbWFnZUVsZW1lbnQhPSJ1bmRlZmluZWQiJiZ0IGluc3RhbmNlb2YgSFRNTEltYWdlRWxlbWVudHx8dHlwZW9mIEhUTUxDYW52YXNFbGVtZW50IT0idW5kZWZpbmVkIiYmdCBpbnN0YW5jZW9mIEhUTUxDYW52YXNFbGVtZW50fHx0eXBlb2YgSW1hZ2VCaXRtYXAhPSJ1bmRlZmluZWQiJiZ0IGluc3RhbmNlb2YgSW1hZ2VCaXRtYXApe2xldCByPVFQKCJjYW52YXMiKTtyLndpZHRoPXQud2lkdGgsci5oZWlnaHQ9dC5oZWlnaHQ7bGV0IG49ci5nZXRDb250ZXh0KCIyZCIpO24uZHJhd0ltYWdlKHQsMCwwLHQud2lkdGgsdC5oZWlnaHQpO2xldCBpPW4uZ2V0SW1hZ2VEYXRhKDAsMCx0LndpZHRoLHQuaGVpZ2h0KSxvPWkuZGF0YTtmb3IobGV0IGE9MDthPG8ubGVuZ3RoO2ErKylvW2FdPVUzKG9bYV0vMjU1KSoyNTU7cmV0dXJuIG4ucHV0SW1hZ2VEYXRhKGksMCwwKSxyfWVsc2UgaWYodC5kYXRhKXtsZXQgcj10LmRhdGEuc2xpY2UoMCk7Zm9yKGxldCBuPTA7bjxyLmxlbmd0aDtuKyspciBpbnN0YW5jZW9mIFVpbnQ4QXJyYXl8fHIgaW5zdGFuY2VvZiBVaW50OENsYW1wZWRBcnJheT9yW25dPU1hdGguZmxvb3IoVTMocltuXS8yNTUpKjI1NSk6cltuXT1VMyhyW25dKTtyZXR1cm57ZGF0YTpyLHdpZHRoOnQud2lkdGgsaGVpZ2h0OnQuaGVpZ2h0fX1lbHNlIHJldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkltYWdlVXRpbHMuc1JHQlRvTGluZWFyKCk6IFVuc3VwcG9ydGVkIGltYWdlIHR5cGUuIE5vIGNvbG9yIHNwYWNlIGNvbnZlcnNpb24gYXBwbGllZC4iKSx0fX0sZWZyPTAseGk9Y2xhc3MgZXh0ZW5kcyBVc3tjb25zdHJ1Y3Rvcih0PXhpLkRFRkFVTFRfSU1BR0Uscj14aS5ERUZBVUxUX01BUFBJTkcsbj1KbyxpPUpvLG89b2ksYT1veCxzPVFvLGw9WmQsYz0xLHU9UWQpe3N1cGVyKCksT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsImlkIix7dmFsdWU6ZWZyKyt9KSx0aGlzLnV1aWQ9TmwoKSx0aGlzLm5hbWU9IiIsdGhpcy5pbWFnZT10LHRoaXMubWlwbWFwcz1bXSx0aGlzLm1hcHBpbmc9cix0aGlzLndyYXBTPW4sdGhpcy53cmFwVD1pLHRoaXMubWFnRmlsdGVyPW8sdGhpcy5taW5GaWx0ZXI9YSx0aGlzLmFuaXNvdHJvcHk9Yyx0aGlzLmZvcm1hdD1zLHRoaXMuaW50ZXJuYWxGb3JtYXQ9bnVsbCx0aGlzLnR5cGU9bCx0aGlzLm9mZnNldD1uZXcgTHQoMCwwKSx0aGlzLnJlcGVhdD1uZXcgTHQoMSwxKSx0aGlzLmNlbnRlcj1uZXcgTHQoMCwwKSx0aGlzLnJvdGF0aW9uPTAsdGhpcy5tYXRyaXhBdXRvVXBkYXRlPSEwLHRoaXMubWF0cml4PW5ldyBraSx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMCx0aGlzLnByZW11bHRpcGx5QWxwaGE9ITEsdGhpcy5mbGlwWT0hMCx0aGlzLnVucGFja0FsaWdubWVudD00LHRoaXMuZW5jb2Rpbmc9dSx0aGlzLnVzZXJEYXRhPXt9LHRoaXMudmVyc2lvbj0wLHRoaXMub25VcGRhdGU9bnVsbCx0aGlzLmlzUmVuZGVyVGFyZ2V0VGV4dHVyZT0hMSx0aGlzLm5lZWRzUE1SRU1VcGRhdGU9ITF9dXBkYXRlTWF0cml4KCl7dGhpcy5tYXRyaXguc2V0VXZUcmFuc2Zvcm0odGhpcy5vZmZzZXQueCx0aGlzLm9mZnNldC55LHRoaXMucmVwZWF0LngsdGhpcy5yZXBlYXQueSx0aGlzLnJvdGF0aW9uLHRoaXMuY2VudGVyLngsdGhpcy5jZW50ZXIueSl9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfWNvcHkodCl7cmV0dXJuIHRoaXMubmFtZT10Lm5hbWUsdGhpcy5pbWFnZT10LmltYWdlLHRoaXMubWlwbWFwcz10Lm1pcG1hcHMuc2xpY2UoMCksdGhpcy5tYXBwaW5nPXQubWFwcGluZyx0aGlzLndyYXBTPXQud3JhcFMsdGhpcy53cmFwVD10LndyYXBULHRoaXMubWFnRmlsdGVyPXQubWFnRmlsdGVyLHRoaXMubWluRmlsdGVyPXQubWluRmlsdGVyLHRoaXMuYW5pc290cm9weT10LmFuaXNvdHJvcHksdGhpcy5mb3JtYXQ9dC5mb3JtYXQsdGhpcy5pbnRlcm5hbEZvcm1hdD10LmludGVybmFsRm9ybWF0LHRoaXMudHlwZT10LnR5cGUsdGhpcy5vZmZzZXQuY29weSh0Lm9mZnNldCksdGhpcy5yZXBlYXQuY29weSh0LnJlcGVhdCksdGhpcy5jZW50ZXIuY29weSh0LmNlbnRlciksdGhpcy5yb3RhdGlvbj10LnJvdGF0aW9uLHRoaXMubWF0cml4QXV0b1VwZGF0ZT10Lm1hdHJpeEF1dG9VcGRhdGUsdGhpcy5tYXRyaXguY29weSh0Lm1hdHJpeCksdGhpcy5nZW5lcmF0ZU1pcG1hcHM9dC5nZW5lcmF0ZU1pcG1hcHMsdGhpcy5wcmVtdWx0aXBseUFscGhhPXQucHJlbXVsdGlwbHlBbHBoYSx0aGlzLmZsaXBZPXQuZmxpcFksdGhpcy51bnBhY2tBbGlnbm1lbnQ9dC51bnBhY2tBbGlnbm1lbnQsdGhpcy5lbmNvZGluZz10LmVuY29kaW5nLHRoaXMudXNlckRhdGE9SlNPTi5wYXJzZShKU09OLnN0cmluZ2lmeSh0LnVzZXJEYXRhKSksdGhpc310b0pTT04odCl7bGV0IHI9dD09PXZvaWQgMHx8dHlwZW9mIHQ9PSJzdHJpbmciO2lmKCFyJiZ0LnRleHR1cmVzW3RoaXMudXVpZF0hPT12b2lkIDApcmV0dXJuIHQudGV4dHVyZXNbdGhpcy51dWlkXTtsZXQgbj17bWV0YWRhdGE6e3ZlcnNpb246NC41LHR5cGU6IlRleHR1cmUiLGdlbmVyYXRvcjoiVGV4dHVyZS50b0pTT04ifSx1dWlkOnRoaXMudXVpZCxuYW1lOnRoaXMubmFtZSxtYXBwaW5nOnRoaXMubWFwcGluZyxyZXBlYXQ6W3RoaXMucmVwZWF0LngsdGhpcy5yZXBlYXQueV0sb2Zmc2V0Olt0aGlzLm9mZnNldC54LHRoaXMub2Zmc2V0LnldLGNlbnRlcjpbdGhpcy5jZW50ZXIueCx0aGlzLmNlbnRlci55XSxyb3RhdGlvbjp0aGlzLnJvdGF0aW9uLHdyYXA6W3RoaXMud3JhcFMsdGhpcy53cmFwVF0sZm9ybWF0OnRoaXMuZm9ybWF0LHR5cGU6dGhpcy50eXBlLGVuY29kaW5nOnRoaXMuZW5jb2RpbmcsbWluRmlsdGVyOnRoaXMubWluRmlsdGVyLG1hZ0ZpbHRlcjp0aGlzLm1hZ0ZpbHRlcixhbmlzb3Ryb3B5OnRoaXMuYW5pc290cm9weSxmbGlwWTp0aGlzLmZsaXBZLHByZW11bHRpcGx5QWxwaGE6dGhpcy5wcmVtdWx0aXBseUFscGhhLHVucGFja0FsaWdubWVudDp0aGlzLnVucGFja0FsaWdubWVudH07aWYodGhpcy5pbWFnZSE9PXZvaWQgMCl7bGV0IGk9dGhpcy5pbWFnZTtpZihpLnV1aWQ9PT12b2lkIDAmJihpLnV1aWQ9TmwoKSksIXImJnQuaW1hZ2VzW2kudXVpZF09PT12b2lkIDApe2xldCBvO2lmKEFycmF5LmlzQXJyYXkoaSkpe289W107Zm9yKGxldCBhPTAscz1pLmxlbmd0aDthPHM7YSsrKWlbYV0uaXNEYXRhVGV4dHVyZT9vLnB1c2goamN0KGlbYV0uaW1hZ2UpKTpvLnB1c2goamN0KGlbYV0pKX1lbHNlIG89amN0KGkpO3QuaW1hZ2VzW2kudXVpZF09e3V1aWQ6aS51dWlkLHVybDpvfX1uLmltYWdlPWkudXVpZH1yZXR1cm4gSlNPTi5zdHJpbmdpZnkodGhpcy51c2VyRGF0YSkhPT0ie30iJiYobi51c2VyRGF0YT10aGlzLnVzZXJEYXRhKSxyfHwodC50ZXh0dXJlc1t0aGlzLnV1aWRdPW4pLG59ZGlzcG9zZSgpe3RoaXMuZGlzcGF0Y2hFdmVudCh7dHlwZToiZGlzcG9zZSJ9KX10cmFuc2Zvcm1Vdih0KXtpZih0aGlzLm1hcHBpbmchPT1qVSlyZXR1cm4gdDtpZih0LmFwcGx5TWF0cml4Myh0aGlzLm1hdHJpeCksdC54PDB8fHQueD4xKXN3aXRjaCh0aGlzLndyYXBTKXtjYXNlIGpQOnQueD10LngtTWF0aC5mbG9vcih0LngpO2JyZWFrO2Nhc2UgSm86dC54PXQueDwwPzA6MTticmVhaztjYXNlIFhQOk1hdGguYWJzKE1hdGguZmxvb3IodC54KSUyKT09PTE/dC54PU1hdGguY2VpbCh0LngpLXQueDp0Lng9dC54LU1hdGguZmxvb3IodC54KTticmVha31pZih0Lnk8MHx8dC55PjEpc3dpdGNoKHRoaXMud3JhcFQpe2Nhc2UgalA6dC55PXQueS1NYXRoLmZsb29yKHQueSk7YnJlYWs7Y2FzZSBKbzp0Lnk9dC55PDA/MDoxO2JyZWFrO2Nhc2UgWFA6TWF0aC5hYnMoTWF0aC5mbG9vcih0LnkpJTIpPT09MT90Lnk9TWF0aC5jZWlsKHQueSktdC55OnQueT10LnktTWF0aC5mbG9vcih0LnkpO2JyZWFrfXJldHVybiB0aGlzLmZsaXBZJiYodC55PTEtdC55KSx0fXNldCBuZWVkc1VwZGF0ZSh0KXt0PT09ITAmJnRoaXMudmVyc2lvbisrfX07eGkuREVGQVVMVF9JTUFHRT12b2lkIDA7eGkuREVGQVVMVF9NQVBQSU5HPWpVO3hpLnByb3RvdHlwZS5pc1RleHR1cmU9ITA7ZnVuY3Rpb24gamN0KGUpe3JldHVybiB0eXBlb2YgSFRNTEltYWdlRWxlbWVudCE9InVuZGVmaW5lZCImJmUgaW5zdGFuY2VvZiBIVE1MSW1hZ2VFbGVtZW50fHx0eXBlb2YgSFRNTENhbnZhc0VsZW1lbnQhPSJ1bmRlZmluZWQiJiZlIGluc3RhbmNlb2YgSFRNTENhbnZhc0VsZW1lbnR8fHR5cGVvZiBJbWFnZUJpdG1hcCE9InVuZGVmaW5lZCImJmUgaW5zdGFuY2VvZiBJbWFnZUJpdG1hcD9LZi5nZXREYXRhVVJMKGUpOmUuZGF0YT97ZGF0YTpBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChlLmRhdGEpLHdpZHRoOmUud2lkdGgsaGVpZ2h0OmUuaGVpZ2h0LHR5cGU6ZS5kYXRhLmNvbnN0cnVjdG9yLm5hbWV9Oihjb25zb2xlLndhcm4oIlRIUkVFLlRleHR1cmU6IFVuYWJsZSB0byBzZXJpYWxpemUgVGV4dHVyZS4iKSx7fSl9dmFyIGVuPWNsYXNze2NvbnN0cnVjdG9yKHQ9MCxyPTAsbj0wLGk9MSl7dGhpcy54PXQsdGhpcy55PXIsdGhpcy56PW4sdGhpcy53PWl9Z2V0IHdpZHRoKCl7cmV0dXJuIHRoaXMuen1zZXQgd2lkdGgodCl7dGhpcy56PXR9Z2V0IGhlaWdodCgpe3JldHVybiB0aGlzLnd9c2V0IGhlaWdodCh0KXt0aGlzLnc9dH1zZXQodCxyLG4saSl7cmV0dXJuIHRoaXMueD10LHRoaXMueT1yLHRoaXMuej1uLHRoaXMudz1pLHRoaXN9c2V0U2NhbGFyKHQpe3JldHVybiB0aGlzLng9dCx0aGlzLnk9dCx0aGlzLno9dCx0aGlzLnc9dCx0aGlzfXNldFgodCl7cmV0dXJuIHRoaXMueD10LHRoaXN9c2V0WSh0KXtyZXR1cm4gdGhpcy55PXQsdGhpc31zZXRaKHQpe3JldHVybiB0aGlzLno9dCx0aGlzfXNldFcodCl7cmV0dXJuIHRoaXMudz10LHRoaXN9c2V0Q29tcG9uZW50KHQscil7c3dpdGNoKHQpe2Nhc2UgMDp0aGlzLng9cjticmVhaztjYXNlIDE6dGhpcy55PXI7YnJlYWs7Y2FzZSAyOnRoaXMuej1yO2JyZWFrO2Nhc2UgMzp0aGlzLnc9cjticmVhaztkZWZhdWx0OnRocm93IG5ldyBFcnJvcigiaW5kZXggaXMgb3V0IG9mIHJhbmdlOiAiK3QpfXJldHVybiB0aGlzfWdldENvbXBvbmVudCh0KXtzd2l0Y2godCl7Y2FzZSAwOnJldHVybiB0aGlzLng7Y2FzZSAxOnJldHVybiB0aGlzLnk7Y2FzZSAyOnJldHVybiB0aGlzLno7Y2FzZSAzOnJldHVybiB0aGlzLnc7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoImluZGV4IGlzIG91dCBvZiByYW5nZTogIit0KX19Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy54LHRoaXMueSx0aGlzLnosdGhpcy53KX1jb3B5KHQpe3JldHVybiB0aGlzLng9dC54LHRoaXMueT10LnksdGhpcy56PXQueix0aGlzLnc9dC53IT09dm9pZCAwP3QudzoxLHRoaXN9YWRkKHQscil7cmV0dXJuIHIhPT12b2lkIDA/KGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yNDogLmFkZCgpIG5vdyBvbmx5IGFjY2VwdHMgb25lIGFyZ3VtZW50LiBVc2UgLmFkZFZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMuYWRkVmVjdG9ycyh0LHIpKToodGhpcy54Kz10LngsdGhpcy55Kz10LnksdGhpcy56Kz10LnosdGhpcy53Kz10LncsdGhpcyl9YWRkU2NhbGFyKHQpe3JldHVybiB0aGlzLngrPXQsdGhpcy55Kz10LHRoaXMueis9dCx0aGlzLncrPXQsdGhpc31hZGRWZWN0b3JzKHQscil7cmV0dXJuIHRoaXMueD10Lngrci54LHRoaXMueT10Lnkrci55LHRoaXMuej10Lnorci56LHRoaXMudz10Lncrci53LHRoaXN9YWRkU2NhbGVkVmVjdG9yKHQscil7cmV0dXJuIHRoaXMueCs9dC54KnIsdGhpcy55Kz10Lnkqcix0aGlzLnorPXQueipyLHRoaXMudys9dC53KnIsdGhpc31zdWIodCxyKXtyZXR1cm4gciE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3I0OiAuc3ViKCkgbm93IG9ubHkgYWNjZXB0cyBvbmUgYXJndW1lbnQuIFVzZSAuc3ViVmVjdG9ycyggYSwgYiApIGluc3RlYWQuIiksdGhpcy5zdWJWZWN0b3JzKHQscikpOih0aGlzLngtPXQueCx0aGlzLnktPXQueSx0aGlzLnotPXQueix0aGlzLnctPXQudyx0aGlzKX1zdWJTY2FsYXIodCl7cmV0dXJuIHRoaXMueC09dCx0aGlzLnktPXQsdGhpcy56LT10LHRoaXMudy09dCx0aGlzfXN1YlZlY3RvcnModCxyKXtyZXR1cm4gdGhpcy54PXQueC1yLngsdGhpcy55PXQueS1yLnksdGhpcy56PXQuei1yLnosdGhpcy53PXQudy1yLncsdGhpc31tdWx0aXBseSh0KXtyZXR1cm4gdGhpcy54Kj10LngsdGhpcy55Kj10LnksdGhpcy56Kj10LnosdGhpcy53Kj10LncsdGhpc31tdWx0aXBseVNjYWxhcih0KXtyZXR1cm4gdGhpcy54Kj10LHRoaXMueSo9dCx0aGlzLnoqPXQsdGhpcy53Kj10LHRoaXN9YXBwbHlNYXRyaXg0KHQpe2xldCByPXRoaXMueCxuPXRoaXMueSxpPXRoaXMueixvPXRoaXMudyxhPXQuZWxlbWVudHM7cmV0dXJuIHRoaXMueD1hWzBdKnIrYVs0XSpuK2FbOF0qaSthWzEyXSpvLHRoaXMueT1hWzFdKnIrYVs1XSpuK2FbOV0qaSthWzEzXSpvLHRoaXMuej1hWzJdKnIrYVs2XSpuK2FbMTBdKmkrYVsxNF0qbyx0aGlzLnc9YVszXSpyK2FbN10qbithWzExXSppK2FbMTVdKm8sdGhpc31kaXZpZGVTY2FsYXIodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlTY2FsYXIoMS90KX1zZXRBeGlzQW5nbGVGcm9tUXVhdGVybmlvbih0KXt0aGlzLnc9MipNYXRoLmFjb3ModC53KTtsZXQgcj1NYXRoLnNxcnQoMS10LncqdC53KTtyZXR1cm4gcjwxZS00Pyh0aGlzLng9MSx0aGlzLnk9MCx0aGlzLno9MCk6KHRoaXMueD10Lngvcix0aGlzLnk9dC55L3IsdGhpcy56PXQuei9yKSx0aGlzfXNldEF4aXNBbmdsZUZyb21Sb3RhdGlvbk1hdHJpeCh0KXtsZXQgcixuLGksbyxsPXQuZWxlbWVudHMsYz1sWzBdLHU9bFs0XSxoPWxbOF0sZj1sWzFdLHA9bFs1XSxkPWxbOV0sZz1sWzJdLF89bFs2XSx5PWxbMTBdO2lmKE1hdGguYWJzKHUtZik8LjAxJiZNYXRoLmFicyhoLWcpPC4wMSYmTWF0aC5hYnMoZC1fKTwuMDEpe2lmKE1hdGguYWJzKHUrZik8LjEmJk1hdGguYWJzKGgrZyk8LjEmJk1hdGguYWJzKGQrXyk8LjEmJk1hdGguYWJzKGMrcCt5LTMpPC4xKXJldHVybiB0aGlzLnNldCgxLDAsMCwwKSx0aGlzO3I9TWF0aC5QSTtsZXQgYj0oYysxKS8yLFM9KHArMSkvMixDPSh5KzEpLzIsUD0odStmKS80LGs9KGgrZykvNCxPPShkK18pLzQ7cmV0dXJuIGI+UyYmYj5DP2I8LjAxPyhuPTAsaT0uNzA3MTA2NzgxLG89LjcwNzEwNjc4MSk6KG49TWF0aC5zcXJ0KGIpLGk9UC9uLG89ay9uKTpTPkM/UzwuMDE/KG49LjcwNzEwNjc4MSxpPTAsbz0uNzA3MTA2NzgxKTooaT1NYXRoLnNxcnQoUyksbj1QL2ksbz1PL2kpOkM8LjAxPyhuPS43MDcxMDY3ODEsaT0uNzA3MTA2NzgxLG89MCk6KG89TWF0aC5zcXJ0KEMpLG49ay9vLGk9Ty9vKSx0aGlzLnNldChuLGksbyxyKSx0aGlzfWxldCB4PU1hdGguc3FydCgoXy1kKSooXy1kKSsoaC1nKSooaC1nKSsoZi11KSooZi11KSk7cmV0dXJuIE1hdGguYWJzKHgpPC4wMDEmJih4PTEpLHRoaXMueD0oXy1kKS94LHRoaXMueT0oaC1nKS94LHRoaXMuej0oZi11KS94LHRoaXMudz1NYXRoLmFjb3MoKGMrcCt5LTEpLzIpLHRoaXN9bWluKHQpe3JldHVybiB0aGlzLng9TWF0aC5taW4odGhpcy54LHQueCksdGhpcy55PU1hdGgubWluKHRoaXMueSx0LnkpLHRoaXMuej1NYXRoLm1pbih0aGlzLnosdC56KSx0aGlzLnc9TWF0aC5taW4odGhpcy53LHQudyksdGhpc31tYXgodCl7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0aGlzLngsdC54KSx0aGlzLnk9TWF0aC5tYXgodGhpcy55LHQueSksdGhpcy56PU1hdGgubWF4KHRoaXMueix0LnopLHRoaXMudz1NYXRoLm1heCh0aGlzLncsdC53KSx0aGlzfWNsYW1wKHQscil7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0LngsTWF0aC5taW4oci54LHRoaXMueCkpLHRoaXMueT1NYXRoLm1heCh0LnksTWF0aC5taW4oci55LHRoaXMueSkpLHRoaXMuej1NYXRoLm1heCh0LnosTWF0aC5taW4oci56LHRoaXMueikpLHRoaXMudz1NYXRoLm1heCh0LncsTWF0aC5taW4oci53LHRoaXMudykpLHRoaXN9Y2xhbXBTY2FsYXIodCxyKXtyZXR1cm4gdGhpcy54PU1hdGgubWF4KHQsTWF0aC5taW4ocix0aGlzLngpKSx0aGlzLnk9TWF0aC5tYXgodCxNYXRoLm1pbihyLHRoaXMueSkpLHRoaXMuej1NYXRoLm1heCh0LE1hdGgubWluKHIsdGhpcy56KSksdGhpcy53PU1hdGgubWF4KHQsTWF0aC5taW4ocix0aGlzLncpKSx0aGlzfWNsYW1wTGVuZ3RoKHQscil7bGV0IG49dGhpcy5sZW5ndGgoKTtyZXR1cm4gdGhpcy5kaXZpZGVTY2FsYXIobnx8MSkubXVsdGlwbHlTY2FsYXIoTWF0aC5tYXgodCxNYXRoLm1pbihyLG4pKSl9Zmxvb3IoKXtyZXR1cm4gdGhpcy54PU1hdGguZmxvb3IodGhpcy54KSx0aGlzLnk9TWF0aC5mbG9vcih0aGlzLnkpLHRoaXMuej1NYXRoLmZsb29yKHRoaXMueiksdGhpcy53PU1hdGguZmxvb3IodGhpcy53KSx0aGlzfWNlaWwoKXtyZXR1cm4gdGhpcy54PU1hdGguY2VpbCh0aGlzLngpLHRoaXMueT1NYXRoLmNlaWwodGhpcy55KSx0aGlzLno9TWF0aC5jZWlsKHRoaXMueiksdGhpcy53PU1hdGguY2VpbCh0aGlzLncpLHRoaXN9cm91bmQoKXtyZXR1cm4gdGhpcy54PU1hdGgucm91bmQodGhpcy54KSx0aGlzLnk9TWF0aC5yb3VuZCh0aGlzLnkpLHRoaXMuej1NYXRoLnJvdW5kKHRoaXMueiksdGhpcy53PU1hdGgucm91bmQodGhpcy53KSx0aGlzfXJvdW5kVG9aZXJvKCl7cmV0dXJuIHRoaXMueD10aGlzLng8MD9NYXRoLmNlaWwodGhpcy54KTpNYXRoLmZsb29yKHRoaXMueCksdGhpcy55PXRoaXMueTwwP01hdGguY2VpbCh0aGlzLnkpOk1hdGguZmxvb3IodGhpcy55KSx0aGlzLno9dGhpcy56PDA/TWF0aC5jZWlsKHRoaXMueik6TWF0aC5mbG9vcih0aGlzLnopLHRoaXMudz10aGlzLnc8MD9NYXRoLmNlaWwodGhpcy53KTpNYXRoLmZsb29yKHRoaXMudyksdGhpc31uZWdhdGUoKXtyZXR1cm4gdGhpcy54PS10aGlzLngsdGhpcy55PS10aGlzLnksdGhpcy56PS10aGlzLnosdGhpcy53PS10aGlzLncsdGhpc31kb3QodCl7cmV0dXJuIHRoaXMueCp0LngrdGhpcy55KnQueSt0aGlzLnoqdC56K3RoaXMudyp0Lnd9bGVuZ3RoU3EoKXtyZXR1cm4gdGhpcy54KnRoaXMueCt0aGlzLnkqdGhpcy55K3RoaXMueip0aGlzLnordGhpcy53KnRoaXMud31sZW5ndGgoKXtyZXR1cm4gTWF0aC5zcXJ0KHRoaXMueCp0aGlzLngrdGhpcy55KnRoaXMueSt0aGlzLnoqdGhpcy56K3RoaXMudyp0aGlzLncpfW1hbmhhdHRhbkxlbmd0aCgpe3JldHVybiBNYXRoLmFicyh0aGlzLngpK01hdGguYWJzKHRoaXMueSkrTWF0aC5hYnModGhpcy56KStNYXRoLmFicyh0aGlzLncpfW5vcm1hbGl6ZSgpe3JldHVybiB0aGlzLmRpdmlkZVNjYWxhcih0aGlzLmxlbmd0aCgpfHwxKX1zZXRMZW5ndGgodCl7cmV0dXJuIHRoaXMubm9ybWFsaXplKCkubXVsdGlwbHlTY2FsYXIodCl9bGVycCh0LHIpe3JldHVybiB0aGlzLngrPSh0LngtdGhpcy54KSpyLHRoaXMueSs9KHQueS10aGlzLnkpKnIsdGhpcy56Kz0odC56LXRoaXMueikqcix0aGlzLncrPSh0LnctdGhpcy53KSpyLHRoaXN9bGVycFZlY3RvcnModCxyLG4pe3JldHVybiB0aGlzLng9dC54KyhyLngtdC54KSpuLHRoaXMueT10LnkrKHIueS10LnkpKm4sdGhpcy56PXQueisoci56LXQueikqbix0aGlzLnc9dC53KyhyLnctdC53KSpuLHRoaXN9ZXF1YWxzKHQpe3JldHVybiB0Lng9PT10aGlzLngmJnQueT09PXRoaXMueSYmdC56PT09dGhpcy56JiZ0Lnc9PT10aGlzLnd9ZnJvbUFycmF5KHQscj0wKXtyZXR1cm4gdGhpcy54PXRbcl0sdGhpcy55PXRbcisxXSx0aGlzLno9dFtyKzJdLHRoaXMudz10W3IrM10sdGhpc310b0FycmF5KHQ9W10scj0wKXtyZXR1cm4gdFtyXT10aGlzLngsdFtyKzFdPXRoaXMueSx0W3IrMl09dGhpcy56LHRbciszXT10aGlzLncsdH1mcm9tQnVmZmVyQXR0cmlidXRlKHQscixuKXtyZXR1cm4gbiE9PXZvaWQgMCYmY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3I0OiBvZmZzZXQgaGFzIGJlZW4gcmVtb3ZlZCBmcm9tIC5mcm9tQnVmZmVyQXR0cmlidXRlKCkuIiksdGhpcy54PXQuZ2V0WChyKSx0aGlzLnk9dC5nZXRZKHIpLHRoaXMuej10LmdldFoociksdGhpcy53PXQuZ2V0VyhyKSx0aGlzfXJhbmRvbSgpe3JldHVybiB0aGlzLng9TWF0aC5yYW5kb20oKSx0aGlzLnk9TWF0aC5yYW5kb20oKSx0aGlzLno9TWF0aC5yYW5kb20oKSx0aGlzLnc9TWF0aC5yYW5kb20oKSx0aGlzfSpbU3ltYm9sLml0ZXJhdG9yXSgpe3lpZWxkIHRoaXMueCx5aWVsZCB0aGlzLnkseWllbGQgdGhpcy56LHlpZWxkIHRoaXMud319O2VuLnByb3RvdHlwZS5pc1ZlY3RvcjQ9ITA7dmFyIHVzPWNsYXNzIGV4dGVuZHMgVXN7Y29uc3RydWN0b3IodCxyLG49e30pe3N1cGVyKCksdGhpcy53aWR0aD10LHRoaXMuaGVpZ2h0PXIsdGhpcy5kZXB0aD0xLHRoaXMuc2Npc3Nvcj1uZXcgZW4oMCwwLHQsciksdGhpcy5zY2lzc29yVGVzdD0hMSx0aGlzLnZpZXdwb3J0PW5ldyBlbigwLDAsdCxyKSx0aGlzLnRleHR1cmU9bmV3IHhpKHZvaWQgMCxuLm1hcHBpbmcsbi53cmFwUyxuLndyYXBULG4ubWFnRmlsdGVyLG4ubWluRmlsdGVyLG4uZm9ybWF0LG4udHlwZSxuLmFuaXNvdHJvcHksbi5lbmNvZGluZyksdGhpcy50ZXh0dXJlLmlzUmVuZGVyVGFyZ2V0VGV4dHVyZT0hMCx0aGlzLnRleHR1cmUuaW1hZ2U9e3dpZHRoOnQsaGVpZ2h0OnIsZGVwdGg6MX0sdGhpcy50ZXh0dXJlLmdlbmVyYXRlTWlwbWFwcz1uLmdlbmVyYXRlTWlwbWFwcyE9PXZvaWQgMD9uLmdlbmVyYXRlTWlwbWFwczohMSx0aGlzLnRleHR1cmUuaW50ZXJuYWxGb3JtYXQ9bi5pbnRlcm5hbEZvcm1hdCE9PXZvaWQgMD9uLmludGVybmFsRm9ybWF0Om51bGwsdGhpcy50ZXh0dXJlLm1pbkZpbHRlcj1uLm1pbkZpbHRlciE9PXZvaWQgMD9uLm1pbkZpbHRlcjpvaSx0aGlzLmRlcHRoQnVmZmVyPW4uZGVwdGhCdWZmZXIhPT12b2lkIDA/bi5kZXB0aEJ1ZmZlcjohMCx0aGlzLnN0ZW5jaWxCdWZmZXI9bi5zdGVuY2lsQnVmZmVyIT09dm9pZCAwP24uc3RlbmNpbEJ1ZmZlcjohMSx0aGlzLmRlcHRoVGV4dHVyZT1uLmRlcHRoVGV4dHVyZSE9PXZvaWQgMD9uLmRlcHRoVGV4dHVyZTpudWxsfXNldFRleHR1cmUodCl7dC5pbWFnZT17d2lkdGg6dGhpcy53aWR0aCxoZWlnaHQ6dGhpcy5oZWlnaHQsZGVwdGg6dGhpcy5kZXB0aH0sdGhpcy50ZXh0dXJlPXR9c2V0U2l6ZSh0LHIsbj0xKXsodGhpcy53aWR0aCE9PXR8fHRoaXMuaGVpZ2h0IT09cnx8dGhpcy5kZXB0aCE9PW4pJiYodGhpcy53aWR0aD10LHRoaXMuaGVpZ2h0PXIsdGhpcy5kZXB0aD1uLHRoaXMudGV4dHVyZS5pbWFnZS53aWR0aD10LHRoaXMudGV4dHVyZS5pbWFnZS5oZWlnaHQ9cix0aGlzLnRleHR1cmUuaW1hZ2UuZGVwdGg9bix0aGlzLmRpc3Bvc2UoKSksdGhpcy52aWV3cG9ydC5zZXQoMCwwLHQsciksdGhpcy5zY2lzc29yLnNldCgwLDAsdCxyKX1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyl9Y29weSh0KXtyZXR1cm4gdGhpcy53aWR0aD10LndpZHRoLHRoaXMuaGVpZ2h0PXQuaGVpZ2h0LHRoaXMuZGVwdGg9dC5kZXB0aCx0aGlzLnZpZXdwb3J0LmNvcHkodC52aWV3cG9ydCksdGhpcy50ZXh0dXJlPXQudGV4dHVyZS5jbG9uZSgpLHRoaXMudGV4dHVyZS5pbWFnZT1PYmplY3QuYXNzaWduKHt9LHQudGV4dHVyZS5pbWFnZSksdGhpcy5kZXB0aEJ1ZmZlcj10LmRlcHRoQnVmZmVyLHRoaXMuc3RlbmNpbEJ1ZmZlcj10LnN0ZW5jaWxCdWZmZXIsdGhpcy5kZXB0aFRleHR1cmU9dC5kZXB0aFRleHR1cmUsdGhpc31kaXNwb3NlKCl7dGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJkaXNwb3NlIn0pfX07dXMucHJvdG90eXBlLmlzV2ViR0xSZW5kZXJUYXJnZXQ9ITA7dmFyIHNVPWNsYXNzIGV4dGVuZHMgdXN7Y29uc3RydWN0b3IodCxyLG4pe3N1cGVyKHQscik7bGV0IGk9dGhpcy50ZXh0dXJlO3RoaXMudGV4dHVyZT1bXTtmb3IobGV0IG89MDtvPG47bysrKXRoaXMudGV4dHVyZVtvXT1pLmNsb25lKCl9c2V0U2l6ZSh0LHIsbj0xKXtpZih0aGlzLndpZHRoIT09dHx8dGhpcy5oZWlnaHQhPT1yfHx0aGlzLmRlcHRoIT09bil7dGhpcy53aWR0aD10LHRoaXMuaGVpZ2h0PXIsdGhpcy5kZXB0aD1uO2ZvcihsZXQgaT0wLG89dGhpcy50ZXh0dXJlLmxlbmd0aDtpPG87aSsrKXRoaXMudGV4dHVyZVtpXS5pbWFnZS53aWR0aD10LHRoaXMudGV4dHVyZVtpXS5pbWFnZS5oZWlnaHQ9cix0aGlzLnRleHR1cmVbaV0uaW1hZ2UuZGVwdGg9bjt0aGlzLmRpc3Bvc2UoKX1yZXR1cm4gdGhpcy52aWV3cG9ydC5zZXQoMCwwLHQsciksdGhpcy5zY2lzc29yLnNldCgwLDAsdCxyKSx0aGlzfWNvcHkodCl7dGhpcy5kaXNwb3NlKCksdGhpcy53aWR0aD10LndpZHRoLHRoaXMuaGVpZ2h0PXQuaGVpZ2h0LHRoaXMuZGVwdGg9dC5kZXB0aCx0aGlzLnZpZXdwb3J0LnNldCgwLDAsdGhpcy53aWR0aCx0aGlzLmhlaWdodCksdGhpcy5zY2lzc29yLnNldCgwLDAsdGhpcy53aWR0aCx0aGlzLmhlaWdodCksdGhpcy5kZXB0aEJ1ZmZlcj10LmRlcHRoQnVmZmVyLHRoaXMuc3RlbmNpbEJ1ZmZlcj10LnN0ZW5jaWxCdWZmZXIsdGhpcy5kZXB0aFRleHR1cmU9dC5kZXB0aFRleHR1cmUsdGhpcy50ZXh0dXJlLmxlbmd0aD0wO2ZvcihsZXQgcj0wLG49dC50ZXh0dXJlLmxlbmd0aDtyPG47cisrKXRoaXMudGV4dHVyZVtyXT10LnRleHR1cmVbcl0uY2xvbmUoKTtyZXR1cm4gdGhpc319O3NVLnByb3RvdHlwZS5pc1dlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzPSEwO3ZhciBqMz1jbGFzcyBleHRlbmRzIHVze2NvbnN0cnVjdG9yKHQscixuPXt9KXtzdXBlcih0LHIsbiksdGhpcy5zYW1wbGVzPTQsdGhpcy5pZ25vcmVEZXB0aEZvck11bHRpc2FtcGxlQ29weT1uLmlnbm9yZURlcHRoIT09dm9pZCAwP24uaWdub3JlRGVwdGg6ITAsdGhpcy51c2VSZW5kZXJUb1RleHR1cmU9bi51c2VSZW5kZXJUb1RleHR1cmUhPT12b2lkIDA/bi51c2VSZW5kZXJUb1RleHR1cmU6ITEsdGhpcy51c2VSZW5kZXJidWZmZXI9dGhpcy51c2VSZW5kZXJUb1RleHR1cmU9PT0hMX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5LmNhbGwodGhpcyx0KSx0aGlzLnNhbXBsZXM9dC5zYW1wbGVzLHRoaXMudXNlUmVuZGVyVG9UZXh0dXJlPXQudXNlUmVuZGVyVG9UZXh0dXJlLHRoaXMudXNlUmVuZGVyYnVmZmVyPXQudXNlUmVuZGVyYnVmZmVyLHRoaXN9fTtqMy5wcm90b3R5cGUuaXNXZWJHTE11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0PSEwO3ZhciB2aT1jbGFzc3tjb25zdHJ1Y3Rvcih0PTAscj0wLG49MCxpPTEpe3RoaXMuX3g9dCx0aGlzLl95PXIsdGhpcy5fej1uLHRoaXMuX3c9aX1zdGF0aWMgc2xlcnAodCxyLG4saSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUXVhdGVybmlvbjogU3RhdGljIC5zbGVycCgpIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSBxbS5zbGVycFF1YXRlcm5pb25zKCBxYSwgcWIsIHQgKSBpbnN0ZWFkLiIpLG4uc2xlcnBRdWF0ZXJuaW9ucyh0LHIsaSl9c3RhdGljIHNsZXJwRmxhdCh0LHIsbixpLG8sYSxzKXtsZXQgbD1uW2krMF0sYz1uW2krMV0sdT1uW2krMl0saD1uW2krM10sZj1vW2ErMF0scD1vW2ErMV0sZD1vW2ErMl0sZz1vW2ErM107aWYocz09PTApe3RbciswXT1sLHRbcisxXT1jLHRbcisyXT11LHRbciszXT1oO3JldHVybn1pZihzPT09MSl7dFtyKzBdPWYsdFtyKzFdPXAsdFtyKzJdPWQsdFtyKzNdPWc7cmV0dXJufWlmKGghPT1nfHxsIT09Znx8YyE9PXB8fHUhPT1kKXtsZXQgXz0xLXMseT1sKmYrYypwK3UqZCtoKmcseD15Pj0wPzE6LTEsYj0xLXkqeTtpZihiPk51bWJlci5FUFNJTE9OKXtsZXQgQz1NYXRoLnNxcnQoYiksUD1NYXRoLmF0YW4yKEMseSp4KTtfPU1hdGguc2luKF8qUCkvQyxzPU1hdGguc2luKHMqUCkvQ31sZXQgUz1zKng7aWYobD1sKl8rZipTLGM9YypfK3AqUyx1PXUqXytkKlMsaD1oKl8rZypTLF89PT0xLXMpe2xldCBDPTEvTWF0aC5zcXJ0KGwqbCtjKmMrdSp1K2gqaCk7bCo9QyxjKj1DLHUqPUMsaCo9Q319dFtyXT1sLHRbcisxXT1jLHRbcisyXT11LHRbciszXT1ofXN0YXRpYyBtdWx0aXBseVF1YXRlcm5pb25zRmxhdCh0LHIsbixpLG8sYSl7bGV0IHM9bltpXSxsPW5baSsxXSxjPW5baSsyXSx1PW5baSszXSxoPW9bYV0sZj1vW2ErMV0scD1vW2ErMl0sZD1vW2ErM107cmV0dXJuIHRbcl09cypkK3UqaCtsKnAtYypmLHRbcisxXT1sKmQrdSpmK2MqaC1zKnAsdFtyKzJdPWMqZCt1KnArcypmLWwqaCx0W3IrM109dSpkLXMqaC1sKmYtYypwLHR9Z2V0IHgoKXtyZXR1cm4gdGhpcy5feH1zZXQgeCh0KXt0aGlzLl94PXQsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpfWdldCB5KCl7cmV0dXJuIHRoaXMuX3l9c2V0IHkodCl7dGhpcy5feT10LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKX1nZXQgeigpe3JldHVybiB0aGlzLl96fXNldCB6KHQpe3RoaXMuX3o9dCx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCl9Z2V0IHcoKXtyZXR1cm4gdGhpcy5fd31zZXQgdyh0KXt0aGlzLl93PXQsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpfXNldCh0LHIsbixpKXtyZXR1cm4gdGhpcy5feD10LHRoaXMuX3k9cix0aGlzLl96PW4sdGhpcy5fdz1pLHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKHRoaXMuX3gsdGhpcy5feSx0aGlzLl96LHRoaXMuX3cpfWNvcHkodCl7cmV0dXJuIHRoaXMuX3g9dC54LHRoaXMuX3k9dC55LHRoaXMuX3o9dC56LHRoaXMuX3c9dC53LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21FdWxlcih0LHIpe2lmKCEodCYmdC5pc0V1bGVyKSl0aHJvdyBuZXcgRXJyb3IoIlRIUkVFLlF1YXRlcm5pb246IC5zZXRGcm9tRXVsZXIoKSBub3cgZXhwZWN0cyBhbiBFdWxlciByb3RhdGlvbiByYXRoZXIgdGhhbiBhIFZlY3RvcjMgYW5kIG9yZGVyLiIpO2xldCBuPXQuX3gsaT10Ll95LG89dC5feixhPXQuX29yZGVyLHM9TWF0aC5jb3MsbD1NYXRoLnNpbixjPXMobi8yKSx1PXMoaS8yKSxoPXMoby8yKSxmPWwobi8yKSxwPWwoaS8yKSxkPWwoby8yKTtzd2l0Y2goYSl7Y2FzZSJYWVoiOnRoaXMuX3g9Zip1KmgrYypwKmQsdGhpcy5feT1jKnAqaC1mKnUqZCx0aGlzLl96PWMqdSpkK2YqcCpoLHRoaXMuX3c9Yyp1KmgtZipwKmQ7YnJlYWs7Y2FzZSJZWFoiOnRoaXMuX3g9Zip1KmgrYypwKmQsdGhpcy5feT1jKnAqaC1mKnUqZCx0aGlzLl96PWMqdSpkLWYqcCpoLHRoaXMuX3c9Yyp1KmgrZipwKmQ7YnJlYWs7Y2FzZSJaWFkiOnRoaXMuX3g9Zip1KmgtYypwKmQsdGhpcy5feT1jKnAqaCtmKnUqZCx0aGlzLl96PWMqdSpkK2YqcCpoLHRoaXMuX3c9Yyp1KmgtZipwKmQ7YnJlYWs7Y2FzZSJaWVgiOnRoaXMuX3g9Zip1KmgtYypwKmQsdGhpcy5feT1jKnAqaCtmKnUqZCx0aGlzLl96PWMqdSpkLWYqcCpoLHRoaXMuX3c9Yyp1KmgrZipwKmQ7YnJlYWs7Y2FzZSJZWlgiOnRoaXMuX3g9Zip1KmgrYypwKmQsdGhpcy5feT1jKnAqaCtmKnUqZCx0aGlzLl96PWMqdSpkLWYqcCpoLHRoaXMuX3c9Yyp1KmgtZipwKmQ7YnJlYWs7Y2FzZSJYWlkiOnRoaXMuX3g9Zip1KmgtYypwKmQsdGhpcy5feT1jKnAqaC1mKnUqZCx0aGlzLl96PWMqdSpkK2YqcCpoLHRoaXMuX3c9Yyp1KmgrZipwKmQ7YnJlYWs7ZGVmYXVsdDpjb25zb2xlLndhcm4oIlRIUkVFLlF1YXRlcm5pb246IC5zZXRGcm9tRXVsZXIoKSBlbmNvdW50ZXJlZCBhbiB1bmtub3duIG9yZGVyOiAiK2EpfXJldHVybiByIT09ITEmJnRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21BeGlzQW5nbGUodCxyKXtsZXQgbj1yLzIsaT1NYXRoLnNpbihuKTtyZXR1cm4gdGhpcy5feD10LngqaSx0aGlzLl95PXQueSppLHRoaXMuX3o9dC56KmksdGhpcy5fdz1NYXRoLmNvcyhuKSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31zZXRGcm9tUm90YXRpb25NYXRyaXgodCl7bGV0IHI9dC5lbGVtZW50cyxuPXJbMF0saT1yWzRdLG89cls4XSxhPXJbMV0scz1yWzVdLGw9cls5XSxjPXJbMl0sdT1yWzZdLGg9clsxMF0sZj1uK3MraDtpZihmPjApe2xldCBwPS41L01hdGguc3FydChmKzEpO3RoaXMuX3c9LjI1L3AsdGhpcy5feD0odS1sKSpwLHRoaXMuX3k9KG8tYykqcCx0aGlzLl96PShhLWkpKnB9ZWxzZSBpZihuPnMmJm4+aCl7bGV0IHA9MipNYXRoLnNxcnQoMStuLXMtaCk7dGhpcy5fdz0odS1sKS9wLHRoaXMuX3g9LjI1KnAsdGhpcy5feT0oaSthKS9wLHRoaXMuX3o9KG8rYykvcH1lbHNlIGlmKHM+aCl7bGV0IHA9MipNYXRoLnNxcnQoMStzLW4taCk7dGhpcy5fdz0oby1jKS9wLHRoaXMuX3g9KGkrYSkvcCx0aGlzLl95PS4yNSpwLHRoaXMuX3o9KGwrdSkvcH1lbHNle2xldCBwPTIqTWF0aC5zcXJ0KDEraC1uLXMpO3RoaXMuX3c9KGEtaSkvcCx0aGlzLl94PShvK2MpL3AsdGhpcy5feT0obCt1KS9wLHRoaXMuX3o9LjI1KnB9cmV0dXJuIHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21Vbml0VmVjdG9ycyh0LHIpe2xldCBuPXQuZG90KHIpKzE7cmV0dXJuIG48TnVtYmVyLkVQU0lMT04/KG49MCxNYXRoLmFicyh0LngpPk1hdGguYWJzKHQueik/KHRoaXMuX3g9LXQueSx0aGlzLl95PXQueCx0aGlzLl96PTAsdGhpcy5fdz1uKToodGhpcy5feD0wLHRoaXMuX3k9LXQueix0aGlzLl96PXQueSx0aGlzLl93PW4pKToodGhpcy5feD10Lnkqci56LXQueipyLnksdGhpcy5feT10Lnoqci54LXQueCpyLnosdGhpcy5fej10Lngqci55LXQueSpyLngsdGhpcy5fdz1uKSx0aGlzLm5vcm1hbGl6ZSgpfWFuZ2xlVG8odCl7cmV0dXJuIDIqTWF0aC5hY29zKE1hdGguYWJzKFpvKHRoaXMuZG90KHQpLC0xLDEpKSl9cm90YXRlVG93YXJkcyh0LHIpe2xldCBuPXRoaXMuYW5nbGVUbyh0KTtpZihuPT09MClyZXR1cm4gdGhpcztsZXQgaT1NYXRoLm1pbigxLHIvbik7cmV0dXJuIHRoaXMuc2xlcnAodCxpKSx0aGlzfWlkZW50aXR5KCl7cmV0dXJuIHRoaXMuc2V0KDAsMCwwLDEpfWludmVydCgpe3JldHVybiB0aGlzLmNvbmp1Z2F0ZSgpfWNvbmp1Z2F0ZSgpe3JldHVybiB0aGlzLl94Kj0tMSx0aGlzLl95Kj0tMSx0aGlzLl96Kj0tMSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31kb3QodCl7cmV0dXJuIHRoaXMuX3gqdC5feCt0aGlzLl95KnQuX3krdGhpcy5feip0Ll96K3RoaXMuX3cqdC5fd31sZW5ndGhTcSgpe3JldHVybiB0aGlzLl94KnRoaXMuX3grdGhpcy5feSp0aGlzLl95K3RoaXMuX3oqdGhpcy5feit0aGlzLl93KnRoaXMuX3d9bGVuZ3RoKCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLl94KnRoaXMuX3grdGhpcy5feSp0aGlzLl95K3RoaXMuX3oqdGhpcy5feit0aGlzLl93KnRoaXMuX3cpfW5vcm1hbGl6ZSgpe2xldCB0PXRoaXMubGVuZ3RoKCk7cmV0dXJuIHQ9PT0wPyh0aGlzLl94PTAsdGhpcy5feT0wLHRoaXMuX3o9MCx0aGlzLl93PTEpOih0PTEvdCx0aGlzLl94PXRoaXMuX3gqdCx0aGlzLl95PXRoaXMuX3kqdCx0aGlzLl96PXRoaXMuX3oqdCx0aGlzLl93PXRoaXMuX3cqdCksdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9bXVsdGlwbHkodCxyKXtyZXR1cm4gciE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5RdWF0ZXJuaW9uOiAubXVsdGlwbHkoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5tdWx0aXBseVF1YXRlcm5pb25zKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLm11bHRpcGx5UXVhdGVybmlvbnModCxyKSk6dGhpcy5tdWx0aXBseVF1YXRlcm5pb25zKHRoaXMsdCl9cHJlbXVsdGlwbHkodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlRdWF0ZXJuaW9ucyh0LHRoaXMpfW11bHRpcGx5UXVhdGVybmlvbnModCxyKXtsZXQgbj10Ll94LGk9dC5feSxvPXQuX3osYT10Ll93LHM9ci5feCxsPXIuX3ksYz1yLl96LHU9ci5fdztyZXR1cm4gdGhpcy5feD1uKnUrYSpzK2kqYy1vKmwsdGhpcy5feT1pKnUrYSpsK28qcy1uKmMsdGhpcy5fej1vKnUrYSpjK24qbC1pKnMsdGhpcy5fdz1hKnUtbipzLWkqbC1vKmMsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9c2xlcnAodCxyKXtpZihyPT09MClyZXR1cm4gdGhpcztpZihyPT09MSlyZXR1cm4gdGhpcy5jb3B5KHQpO2xldCBuPXRoaXMuX3gsaT10aGlzLl95LG89dGhpcy5feixhPXRoaXMuX3cscz1hKnQuX3crbip0Ll94K2kqdC5feStvKnQuX3o7aWYoczwwPyh0aGlzLl93PS10Ll93LHRoaXMuX3g9LXQuX3gsdGhpcy5feT0tdC5feSx0aGlzLl96PS10Ll96LHM9LXMpOnRoaXMuY29weSh0KSxzPj0xKXJldHVybiB0aGlzLl93PWEsdGhpcy5feD1uLHRoaXMuX3k9aSx0aGlzLl96PW8sdGhpcztsZXQgbD0xLXMqcztpZihsPD1OdW1iZXIuRVBTSUxPTil7bGV0IHA9MS1yO3JldHVybiB0aGlzLl93PXAqYStyKnRoaXMuX3csdGhpcy5feD1wKm4rcip0aGlzLl94LHRoaXMuX3k9cCppK3IqdGhpcy5feSx0aGlzLl96PXAqbytyKnRoaXMuX3osdGhpcy5ub3JtYWxpemUoKSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31sZXQgYz1NYXRoLnNxcnQobCksdT1NYXRoLmF0YW4yKGMscyksaD1NYXRoLnNpbigoMS1yKSp1KS9jLGY9TWF0aC5zaW4ocip1KS9jO3JldHVybiB0aGlzLl93PWEqaCt0aGlzLl93KmYsdGhpcy5feD1uKmgrdGhpcy5feCpmLHRoaXMuX3k9aSpoK3RoaXMuX3kqZix0aGlzLl96PW8qaCt0aGlzLl96KmYsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9c2xlcnBRdWF0ZXJuaW9ucyh0LHIsbil7cmV0dXJuIHRoaXMuY29weSh0KS5zbGVycChyLG4pfXJhbmRvbSgpe2xldCB0PU1hdGgucmFuZG9tKCkscj1NYXRoLnNxcnQoMS10KSxuPU1hdGguc3FydCh0KSxpPTIqTWF0aC5QSSpNYXRoLnJhbmRvbSgpLG89MipNYXRoLlBJKk1hdGgucmFuZG9tKCk7cmV0dXJuIHRoaXMuc2V0KHIqTWF0aC5jb3MoaSksbipNYXRoLnNpbihvKSxuKk1hdGguY29zKG8pLHIqTWF0aC5zaW4oaSkpfWVxdWFscyh0KXtyZXR1cm4gdC5feD09PXRoaXMuX3gmJnQuX3k9PT10aGlzLl95JiZ0Ll96PT09dGhpcy5feiYmdC5fdz09PXRoaXMuX3d9ZnJvbUFycmF5KHQscj0wKXtyZXR1cm4gdGhpcy5feD10W3JdLHRoaXMuX3k9dFtyKzFdLHRoaXMuX3o9dFtyKzJdLHRoaXMuX3c9dFtyKzNdLHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXRvQXJyYXkodD1bXSxyPTApe3JldHVybiB0W3JdPXRoaXMuX3gsdFtyKzFdPXRoaXMuX3ksdFtyKzJdPXRoaXMuX3osdFtyKzNdPXRoaXMuX3csdH1mcm9tQnVmZmVyQXR0cmlidXRlKHQscil7cmV0dXJuIHRoaXMuX3g9dC5nZXRYKHIpLHRoaXMuX3k9dC5nZXRZKHIpLHRoaXMuX3o9dC5nZXRaKHIpLHRoaXMuX3c9dC5nZXRXKHIpLHRoaXN9X29uQ2hhbmdlKHQpe3JldHVybiB0aGlzLl9vbkNoYW5nZUNhbGxiYWNrPXQsdGhpc31fb25DaGFuZ2VDYWxsYmFjaygpe319O3ZpLnByb3RvdHlwZS5pc1F1YXRlcm5pb249ITA7dmFyIGo9Y2xhc3N7Y29uc3RydWN0b3IodD0wLHI9MCxuPTApe3RoaXMueD10LHRoaXMueT1yLHRoaXMuej1ufXNldCh0LHIsbil7cmV0dXJuIG49PT12b2lkIDAmJihuPXRoaXMueiksdGhpcy54PXQsdGhpcy55PXIsdGhpcy56PW4sdGhpc31zZXRTY2FsYXIodCl7cmV0dXJuIHRoaXMueD10LHRoaXMueT10LHRoaXMuej10LHRoaXN9c2V0WCh0KXtyZXR1cm4gdGhpcy54PXQsdGhpc31zZXRZKHQpe3JldHVybiB0aGlzLnk9dCx0aGlzfXNldFoodCl7cmV0dXJuIHRoaXMuej10LHRoaXN9c2V0Q29tcG9uZW50KHQscil7c3dpdGNoKHQpe2Nhc2UgMDp0aGlzLng9cjticmVhaztjYXNlIDE6dGhpcy55PXI7YnJlYWs7Y2FzZSAyOnRoaXMuej1yO2JyZWFrO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKCJpbmRleCBpcyBvdXQgb2YgcmFuZ2U6ICIrdCl9cmV0dXJuIHRoaXN9Z2V0Q29tcG9uZW50KHQpe3N3aXRjaCh0KXtjYXNlIDA6cmV0dXJuIHRoaXMueDtjYXNlIDE6cmV0dXJuIHRoaXMueTtjYXNlIDI6cmV0dXJuIHRoaXMuejtkZWZhdWx0OnRocm93IG5ldyBFcnJvcigiaW5kZXggaXMgb3V0IG9mIHJhbmdlOiAiK3QpfX1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3Rvcih0aGlzLngsdGhpcy55LHRoaXMueil9Y29weSh0KXtyZXR1cm4gdGhpcy54PXQueCx0aGlzLnk9dC55LHRoaXMuej10LnosdGhpc31hZGQodCxyKXtyZXR1cm4gciE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuYWRkKCkgbm93IG9ubHkgYWNjZXB0cyBvbmUgYXJndW1lbnQuIFVzZSAuYWRkVmVjdG9ycyggYSwgYiApIGluc3RlYWQuIiksdGhpcy5hZGRWZWN0b3JzKHQscikpOih0aGlzLngrPXQueCx0aGlzLnkrPXQueSx0aGlzLnorPXQueix0aGlzKX1hZGRTY2FsYXIodCl7cmV0dXJuIHRoaXMueCs9dCx0aGlzLnkrPXQsdGhpcy56Kz10LHRoaXN9YWRkVmVjdG9ycyh0LHIpe3JldHVybiB0aGlzLng9dC54K3IueCx0aGlzLnk9dC55K3IueSx0aGlzLno9dC56K3Iueix0aGlzfWFkZFNjYWxlZFZlY3Rvcih0LHIpe3JldHVybiB0aGlzLngrPXQueCpyLHRoaXMueSs9dC55KnIsdGhpcy56Kz10Lnoqcix0aGlzfXN1Yih0LHIpe3JldHVybiByIT09dm9pZCAwPyhjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5zdWIoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5zdWJWZWN0b3JzKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLnN1YlZlY3RvcnModCxyKSk6KHRoaXMueC09dC54LHRoaXMueS09dC55LHRoaXMuei09dC56LHRoaXMpfXN1YlNjYWxhcih0KXtyZXR1cm4gdGhpcy54LT10LHRoaXMueS09dCx0aGlzLnotPXQsdGhpc31zdWJWZWN0b3JzKHQscil7cmV0dXJuIHRoaXMueD10Lngtci54LHRoaXMueT10Lnktci55LHRoaXMuej10Lnotci56LHRoaXN9bXVsdGlwbHkodCxyKXtyZXR1cm4gciE9PXZvaWQgMD8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAubXVsdGlwbHkoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5tdWx0aXBseVZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMubXVsdGlwbHlWZWN0b3JzKHQscikpOih0aGlzLngqPXQueCx0aGlzLnkqPXQueSx0aGlzLnoqPXQueix0aGlzKX1tdWx0aXBseVNjYWxhcih0KXtyZXR1cm4gdGhpcy54Kj10LHRoaXMueSo9dCx0aGlzLnoqPXQsdGhpc31tdWx0aXBseVZlY3RvcnModCxyKXtyZXR1cm4gdGhpcy54PXQueCpyLngsdGhpcy55PXQueSpyLnksdGhpcy56PXQueipyLnosdGhpc31hcHBseUV1bGVyKHQpe3JldHVybiB0JiZ0LmlzRXVsZXJ8fGNvbnNvbGUuZXJyb3IoIlRIUkVFLlZlY3RvcjM6IC5hcHBseUV1bGVyKCkgbm93IGV4cGVjdHMgYW4gRXVsZXIgcm90YXRpb24gcmF0aGVyIHRoYW4gYSBWZWN0b3IzIGFuZCBvcmRlci4iKSx0aGlzLmFwcGx5UXVhdGVybmlvbihwdWUuc2V0RnJvbUV1bGVyKHQpKX1hcHBseUF4aXNBbmdsZSh0LHIpe3JldHVybiB0aGlzLmFwcGx5UXVhdGVybmlvbihwdWUuc2V0RnJvbUF4aXNBbmdsZSh0LHIpKX1hcHBseU1hdHJpeDModCl7bGV0IHI9dGhpcy54LG49dGhpcy55LGk9dGhpcy56LG89dC5lbGVtZW50cztyZXR1cm4gdGhpcy54PW9bMF0qcitvWzNdKm4rb1s2XSppLHRoaXMueT1vWzFdKnIrb1s0XSpuK29bN10qaSx0aGlzLno9b1syXSpyK29bNV0qbitvWzhdKmksdGhpc31hcHBseU5vcm1hbE1hdHJpeCh0KXtyZXR1cm4gdGhpcy5hcHBseU1hdHJpeDModCkubm9ybWFsaXplKCl9YXBwbHlNYXRyaXg0KHQpe2xldCByPXRoaXMueCxuPXRoaXMueSxpPXRoaXMueixvPXQuZWxlbWVudHMsYT0xLyhvWzNdKnIrb1s3XSpuK29bMTFdKmkrb1sxNV0pO3JldHVybiB0aGlzLng9KG9bMF0qcitvWzRdKm4rb1s4XSppK29bMTJdKSphLHRoaXMueT0ob1sxXSpyK29bNV0qbitvWzldKmkrb1sxM10pKmEsdGhpcy56PShvWzJdKnIrb1s2XSpuK29bMTBdKmkrb1sxNF0pKmEsdGhpc31hcHBseVF1YXRlcm5pb24odCl7bGV0IHI9dGhpcy54LG49dGhpcy55LGk9dGhpcy56LG89dC54LGE9dC55LHM9dC56LGw9dC53LGM9bCpyK2EqaS1zKm4sdT1sKm4rcypyLW8qaSxoPWwqaStvKm4tYSpyLGY9LW8qci1hKm4tcyppO3JldHVybiB0aGlzLng9YypsK2YqLW8rdSotcy1oKi1hLHRoaXMueT11KmwrZiotYStoKi1vLWMqLXMsdGhpcy56PWgqbCtmKi1zK2MqLWEtdSotbyx0aGlzfXByb2plY3QodCl7cmV0dXJuIHRoaXMuYXBwbHlNYXRyaXg0KHQubWF0cml4V29ybGRJbnZlcnNlKS5hcHBseU1hdHJpeDQodC5wcm9qZWN0aW9uTWF0cml4KX11bnByb2plY3QodCl7cmV0dXJuIHRoaXMuYXBwbHlNYXRyaXg0KHQucHJvamVjdGlvbk1hdHJpeEludmVyc2UpLmFwcGx5TWF0cml4NCh0Lm1hdHJpeFdvcmxkKX10cmFuc2Zvcm1EaXJlY3Rpb24odCl7bGV0IHI9dGhpcy54LG49dGhpcy55LGk9dGhpcy56LG89dC5lbGVtZW50cztyZXR1cm4gdGhpcy54PW9bMF0qcitvWzRdKm4rb1s4XSppLHRoaXMueT1vWzFdKnIrb1s1XSpuK29bOV0qaSx0aGlzLno9b1syXSpyK29bNl0qbitvWzEwXSppLHRoaXMubm9ybWFsaXplKCl9ZGl2aWRlKHQpe3JldHVybiB0aGlzLngvPXQueCx0aGlzLnkvPXQueSx0aGlzLnovPXQueix0aGlzfWRpdmlkZVNjYWxhcih0KXtyZXR1cm4gdGhpcy5tdWx0aXBseVNjYWxhcigxL3QpfW1pbih0KXtyZXR1cm4gdGhpcy54PU1hdGgubWluKHRoaXMueCx0LngpLHRoaXMueT1NYXRoLm1pbih0aGlzLnksdC55KSx0aGlzLno9TWF0aC5taW4odGhpcy56LHQueiksdGhpc31tYXgodCl7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0aGlzLngsdC54KSx0aGlzLnk9TWF0aC5tYXgodGhpcy55LHQueSksdGhpcy56PU1hdGgubWF4KHRoaXMueix0LnopLHRoaXN9Y2xhbXAodCxyKXtyZXR1cm4gdGhpcy54PU1hdGgubWF4KHQueCxNYXRoLm1pbihyLngsdGhpcy54KSksdGhpcy55PU1hdGgubWF4KHQueSxNYXRoLm1pbihyLnksdGhpcy55KSksdGhpcy56PU1hdGgubWF4KHQueixNYXRoLm1pbihyLnosdGhpcy56KSksdGhpc31jbGFtcFNjYWxhcih0LHIpe3JldHVybiB0aGlzLng9TWF0aC5tYXgodCxNYXRoLm1pbihyLHRoaXMueCkpLHRoaXMueT1NYXRoLm1heCh0LE1hdGgubWluKHIsdGhpcy55KSksdGhpcy56PU1hdGgubWF4KHQsTWF0aC5taW4ocix0aGlzLnopKSx0aGlzfWNsYW1wTGVuZ3RoKHQscil7bGV0IG49dGhpcy5sZW5ndGgoKTtyZXR1cm4gdGhpcy5kaXZpZGVTY2FsYXIobnx8MSkubXVsdGlwbHlTY2FsYXIoTWF0aC5tYXgodCxNYXRoLm1pbihyLG4pKSl9Zmxvb3IoKXtyZXR1cm4gdGhpcy54PU1hdGguZmxvb3IodGhpcy54KSx0aGlzLnk9TWF0aC5mbG9vcih0aGlzLnkpLHRoaXMuej1NYXRoLmZsb29yKHRoaXMueiksdGhpc31jZWlsKCl7cmV0dXJuIHRoaXMueD1NYXRoLmNlaWwodGhpcy54KSx0aGlzLnk9TWF0aC5jZWlsKHRoaXMueSksdGhpcy56PU1hdGguY2VpbCh0aGlzLnopLHRoaXN9cm91bmQoKXtyZXR1cm4gdGhpcy54PU1hdGgucm91bmQodGhpcy54KSx0aGlzLnk9TWF0aC5yb3VuZCh0aGlzLnkpLHRoaXMuej1NYXRoLnJvdW5kKHRoaXMueiksdGhpc31yb3VuZFRvWmVybygpe3JldHVybiB0aGlzLng9dGhpcy54PDA/TWF0aC5jZWlsKHRoaXMueCk6TWF0aC5mbG9vcih0aGlzLngpLHRoaXMueT10aGlzLnk8MD9NYXRoLmNlaWwodGhpcy55KTpNYXRoLmZsb29yKHRoaXMueSksdGhpcy56PXRoaXMuejwwP01hdGguY2VpbCh0aGlzLnopOk1hdGguZmxvb3IodGhpcy56KSx0aGlzfW5lZ2F0ZSgpe3JldHVybiB0aGlzLng9LXRoaXMueCx0aGlzLnk9LXRoaXMueSx0aGlzLno9LXRoaXMueix0aGlzfWRvdCh0KXtyZXR1cm4gdGhpcy54KnQueCt0aGlzLnkqdC55K3RoaXMueip0Lnp9bGVuZ3RoU3EoKXtyZXR1cm4gdGhpcy54KnRoaXMueCt0aGlzLnkqdGhpcy55K3RoaXMueip0aGlzLnp9bGVuZ3RoKCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLngqdGhpcy54K3RoaXMueSp0aGlzLnkrdGhpcy56KnRoaXMueil9bWFuaGF0dGFuTGVuZ3RoKCl7cmV0dXJuIE1hdGguYWJzKHRoaXMueCkrTWF0aC5hYnModGhpcy55KStNYXRoLmFicyh0aGlzLnopfW5vcm1hbGl6ZSgpe3JldHVybiB0aGlzLmRpdmlkZVNjYWxhcih0aGlzLmxlbmd0aCgpfHwxKX1zZXRMZW5ndGgodCl7cmV0dXJuIHRoaXMubm9ybWFsaXplKCkubXVsdGlwbHlTY2FsYXIodCl9bGVycCh0LHIpe3JldHVybiB0aGlzLngrPSh0LngtdGhpcy54KSpyLHRoaXMueSs9KHQueS10aGlzLnkpKnIsdGhpcy56Kz0odC56LXRoaXMueikqcix0aGlzfWxlcnBWZWN0b3JzKHQscixuKXtyZXR1cm4gdGhpcy54PXQueCsoci54LXQueCkqbix0aGlzLnk9dC55KyhyLnktdC55KSpuLHRoaXMuej10LnorKHIuei10LnopKm4sdGhpc31jcm9zcyh0LHIpe3JldHVybiByIT09dm9pZCAwPyhjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5jcm9zcygpIG5vdyBvbmx5IGFjY2VwdHMgb25lIGFyZ3VtZW50LiBVc2UgLmNyb3NzVmVjdG9ycyggYSwgYiApIGluc3RlYWQuIiksdGhpcy5jcm9zc1ZlY3RvcnModCxyKSk6dGhpcy5jcm9zc1ZlY3RvcnModGhpcyx0KX1jcm9zc1ZlY3RvcnModCxyKXtsZXQgbj10LngsaT10Lnksbz10LnosYT1yLngscz1yLnksbD1yLno7cmV0dXJuIHRoaXMueD1pKmwtbypzLHRoaXMueT1vKmEtbipsLHRoaXMuej1uKnMtaSphLHRoaXN9cHJvamVjdE9uVmVjdG9yKHQpe2xldCByPXQubGVuZ3RoU3EoKTtpZihyPT09MClyZXR1cm4gdGhpcy5zZXQoMCwwLDApO2xldCBuPXQuZG90KHRoaXMpL3I7cmV0dXJuIHRoaXMuY29weSh0KS5tdWx0aXBseVNjYWxhcihuKX1wcm9qZWN0T25QbGFuZSh0KXtyZXR1cm4gWGN0LmNvcHkodGhpcykucHJvamVjdE9uVmVjdG9yKHQpLHRoaXMuc3ViKFhjdCl9cmVmbGVjdCh0KXtyZXR1cm4gdGhpcy5zdWIoWGN0LmNvcHkodCkubXVsdGlwbHlTY2FsYXIoMip0aGlzLmRvdCh0KSkpfWFuZ2xlVG8odCl7bGV0IHI9TWF0aC5zcXJ0KHRoaXMubGVuZ3RoU3EoKSp0Lmxlbmd0aFNxKCkpO2lmKHI9PT0wKXJldHVybiBNYXRoLlBJLzI7bGV0IG49dGhpcy5kb3QodCkvcjtyZXR1cm4gTWF0aC5hY29zKFpvKG4sLTEsMSkpfWRpc3RhbmNlVG8odCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLmRpc3RhbmNlVG9TcXVhcmVkKHQpKX1kaXN0YW5jZVRvU3F1YXJlZCh0KXtsZXQgcj10aGlzLngtdC54LG49dGhpcy55LXQueSxpPXRoaXMuei10Lno7cmV0dXJuIHIqcituKm4raSppfW1hbmhhdHRhbkRpc3RhbmNlVG8odCl7cmV0dXJuIE1hdGguYWJzKHRoaXMueC10LngpK01hdGguYWJzKHRoaXMueS10LnkpK01hdGguYWJzKHRoaXMuei10LnopfXNldEZyb21TcGhlcmljYWwodCl7cmV0dXJuIHRoaXMuc2V0RnJvbVNwaGVyaWNhbENvb3Jkcyh0LnJhZGl1cyx0LnBoaSx0LnRoZXRhKX1zZXRGcm9tU3BoZXJpY2FsQ29vcmRzKHQscixuKXtsZXQgaT1NYXRoLnNpbihyKSp0O3JldHVybiB0aGlzLng9aSpNYXRoLnNpbihuKSx0aGlzLnk9TWF0aC5jb3MocikqdCx0aGlzLno9aSpNYXRoLmNvcyhuKSx0aGlzfXNldEZyb21DeWxpbmRyaWNhbCh0KXtyZXR1cm4gdGhpcy5zZXRGcm9tQ3lsaW5kcmljYWxDb29yZHModC5yYWRpdXMsdC50aGV0YSx0LnkpfXNldEZyb21DeWxpbmRyaWNhbENvb3Jkcyh0LHIsbil7cmV0dXJuIHRoaXMueD10Kk1hdGguc2luKHIpLHRoaXMueT1uLHRoaXMuej10Kk1hdGguY29zKHIpLHRoaXN9c2V0RnJvbU1hdHJpeFBvc2l0aW9uKHQpe2xldCByPXQuZWxlbWVudHM7cmV0dXJuIHRoaXMueD1yWzEyXSx0aGlzLnk9clsxM10sdGhpcy56PXJbMTRdLHRoaXN9c2V0RnJvbU1hdHJpeFNjYWxlKHQpe2xldCByPXRoaXMuc2V0RnJvbU1hdHJpeENvbHVtbih0LDApLmxlbmd0aCgpLG49dGhpcy5zZXRGcm9tTWF0cml4Q29sdW1uKHQsMSkubGVuZ3RoKCksaT10aGlzLnNldEZyb21NYXRyaXhDb2x1bW4odCwyKS5sZW5ndGgoKTtyZXR1cm4gdGhpcy54PXIsdGhpcy55PW4sdGhpcy56PWksdGhpc31zZXRGcm9tTWF0cml4Q29sdW1uKHQscil7cmV0dXJuIHRoaXMuZnJvbUFycmF5KHQuZWxlbWVudHMscio0KX1zZXRGcm9tTWF0cml4M0NvbHVtbih0LHIpe3JldHVybiB0aGlzLmZyb21BcnJheSh0LmVsZW1lbnRzLHIqMyl9ZXF1YWxzKHQpe3JldHVybiB0Lng9PT10aGlzLngmJnQueT09PXRoaXMueSYmdC56PT09dGhpcy56fWZyb21BcnJheSh0LHI9MCl7cmV0dXJuIHRoaXMueD10W3JdLHRoaXMueT10W3IrMV0sdGhpcy56PXRbcisyXSx0aGlzfXRvQXJyYXkodD1bXSxyPTApe3JldHVybiB0W3JdPXRoaXMueCx0W3IrMV09dGhpcy55LHRbcisyXT10aGlzLnosdH1mcm9tQnVmZmVyQXR0cmlidXRlKHQscixuKXtyZXR1cm4gbiE9PXZvaWQgMCYmY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiBvZmZzZXQgaGFzIGJlZW4gcmVtb3ZlZCBmcm9tIC5mcm9tQnVmZmVyQXR0cmlidXRlKCkuIiksdGhpcy54PXQuZ2V0WChyKSx0aGlzLnk9dC5nZXRZKHIpLHRoaXMuej10LmdldFoociksdGhpc31yYW5kb20oKXtyZXR1cm4gdGhpcy54PU1hdGgucmFuZG9tKCksdGhpcy55PU1hdGgucmFuZG9tKCksdGhpcy56PU1hdGgucmFuZG9tKCksdGhpc31yYW5kb21EaXJlY3Rpb24oKXtsZXQgdD0oTWF0aC5yYW5kb20oKS0uNSkqMixyPU1hdGgucmFuZG9tKCkqTWF0aC5QSSoyLG49TWF0aC5zcXJ0KDEtRUkodCwyKSk7cmV0dXJuIHRoaXMueD1uKk1hdGguY29zKHIpLHRoaXMueT1uKk1hdGguc2luKHIpLHRoaXMuej10LHRoaXN9KltTeW1ib2wuaXRlcmF0b3JdKCl7eWllbGQgdGhpcy54LHlpZWxkIHRoaXMueSx5aWVsZCB0aGlzLnp9fTtqLnByb3RvdHlwZS5pc1ZlY3RvcjM9ITA7dmFyIFhjdD1uZXcgaixwdWU9bmV3IHZpLHRhPWNsYXNze2NvbnN0cnVjdG9yKHQ9bmV3IGooMS8wLDEvMCwxLzApLHI9bmV3IGooLTEvMCwtMS8wLC0xLzApKXt0aGlzLm1pbj10LHRoaXMubWF4PXJ9c2V0KHQscil7cmV0dXJuIHRoaXMubWluLmNvcHkodCksdGhpcy5tYXguY29weShyKSx0aGlzfXNldEZyb21BcnJheSh0KXtsZXQgcj0xLzAsbj0xLzAsaT0xLzAsbz0tMS8wLGE9LTEvMCxzPS0xLzA7Zm9yKGxldCBsPTAsYz10Lmxlbmd0aDtsPGM7bCs9Myl7bGV0IHU9dFtsXSxoPXRbbCsxXSxmPXRbbCsyXTt1PHImJihyPXUpLGg8biYmKG49aCksZjxpJiYoaT1mKSx1Pm8mJihvPXUpLGg+YSYmKGE9aCksZj5zJiYocz1mKX1yZXR1cm4gdGhpcy5taW4uc2V0KHIsbixpKSx0aGlzLm1heC5zZXQobyxhLHMpLHRoaXN9c2V0RnJvbUJ1ZmZlckF0dHJpYnV0ZSh0KXtsZXQgcj0xLzAsbj0xLzAsaT0xLzAsbz0tMS8wLGE9LTEvMCxzPS0xLzA7Zm9yKGxldCBsPTAsYz10LmNvdW50O2w8YztsKyspe2xldCB1PXQuZ2V0WChsKSxoPXQuZ2V0WShsKSxmPXQuZ2V0WihsKTt1PHImJihyPXUpLGg8biYmKG49aCksZjxpJiYoaT1mKSx1Pm8mJihvPXUpLGg+YSYmKGE9aCksZj5zJiYocz1mKX1yZXR1cm4gdGhpcy5taW4uc2V0KHIsbixpKSx0aGlzLm1heC5zZXQobyxhLHMpLHRoaXN9c2V0RnJvbVBvaW50cyh0KXt0aGlzLm1ha2VFbXB0eSgpO2ZvcihsZXQgcj0wLG49dC5sZW5ndGg7cjxuO3IrKyl0aGlzLmV4cGFuZEJ5UG9pbnQodFtyXSk7cmV0dXJuIHRoaXN9c2V0RnJvbUNlbnRlckFuZFNpemUodCxyKXtsZXQgbj1tdi5jb3B5KHIpLm11bHRpcGx5U2NhbGFyKC41KTtyZXR1cm4gdGhpcy5taW4uY29weSh0KS5zdWIobiksdGhpcy5tYXguY29weSh0KS5hZGQobiksdGhpc31zZXRGcm9tT2JqZWN0KHQscj0hMSl7cmV0dXJuIHRoaXMubWFrZUVtcHR5KCksdGhpcy5leHBhbmRCeU9iamVjdCh0LHIpfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX1jb3B5KHQpe3JldHVybiB0aGlzLm1pbi5jb3B5KHQubWluKSx0aGlzLm1heC5jb3B5KHQubWF4KSx0aGlzfW1ha2VFbXB0eSgpe3JldHVybiB0aGlzLm1pbi54PXRoaXMubWluLnk9dGhpcy5taW4uej0xLzAsdGhpcy5tYXgueD10aGlzLm1heC55PXRoaXMubWF4Lno9LTEvMCx0aGlzfWlzRW1wdHkoKXtyZXR1cm4gdGhpcy5tYXgueDx0aGlzLm1pbi54fHx0aGlzLm1heC55PHRoaXMubWluLnl8fHRoaXMubWF4Lno8dGhpcy5taW4uen1nZXRDZW50ZXIodCl7cmV0dXJuIHRoaXMuaXNFbXB0eSgpP3Quc2V0KDAsMCwwKTp0LmFkZFZlY3RvcnModGhpcy5taW4sdGhpcy5tYXgpLm11bHRpcGx5U2NhbGFyKC41KX1nZXRTaXplKHQpe3JldHVybiB0aGlzLmlzRW1wdHkoKT90LnNldCgwLDAsMCk6dC5zdWJWZWN0b3JzKHRoaXMubWF4LHRoaXMubWluKX1leHBhbmRCeVBvaW50KHQpe3JldHVybiB0aGlzLm1pbi5taW4odCksdGhpcy5tYXgubWF4KHQpLHRoaXN9ZXhwYW5kQnlWZWN0b3IodCl7cmV0dXJuIHRoaXMubWluLnN1Yih0KSx0aGlzLm1heC5hZGQodCksdGhpc31leHBhbmRCeVNjYWxhcih0KXtyZXR1cm4gdGhpcy5taW4uYWRkU2NhbGFyKC10KSx0aGlzLm1heC5hZGRTY2FsYXIodCksdGhpc31leHBhbmRCeU9iamVjdCh0LHI9ITEpe3QudXBkYXRlV29ybGRNYXRyaXgoITEsITEpO2xldCBuPXQuZ2VvbWV0cnk7aWYobiE9PXZvaWQgMClpZihyJiZuLmF0dHJpYnV0ZXMhPW51bGwmJm4uYXR0cmlidXRlcy5wb3NpdGlvbiE9PXZvaWQgMCl7bGV0IG89bi5hdHRyaWJ1dGVzLnBvc2l0aW9uO2ZvcihsZXQgYT0wLHM9by5jb3VudDthPHM7YSsrKW12LmZyb21CdWZmZXJBdHRyaWJ1dGUobyxhKS5hcHBseU1hdHJpeDQodC5tYXRyaXhXb3JsZCksdGhpcy5leHBhbmRCeVBvaW50KG12KX1lbHNlIG4uYm91bmRpbmdCb3g9PT1udWxsJiZuLmNvbXB1dGVCb3VuZGluZ0JveCgpLCRjdC5jb3B5KG4uYm91bmRpbmdCb3gpLCRjdC5hcHBseU1hdHJpeDQodC5tYXRyaXhXb3JsZCksdGhpcy51bmlvbigkY3QpO2xldCBpPXQuY2hpbGRyZW47Zm9yKGxldCBvPTAsYT1pLmxlbmd0aDtvPGE7bysrKXRoaXMuZXhwYW5kQnlPYmplY3QoaVtvXSxyKTtyZXR1cm4gdGhpc31jb250YWluc1BvaW50KHQpe3JldHVybiEodC54PHRoaXMubWluLnh8fHQueD50aGlzLm1heC54fHx0Lnk8dGhpcy5taW4ueXx8dC55PnRoaXMubWF4Lnl8fHQuejx0aGlzLm1pbi56fHx0Lno+dGhpcy5tYXgueil9Y29udGFpbnNCb3godCl7cmV0dXJuIHRoaXMubWluLng8PXQubWluLngmJnQubWF4Lng8PXRoaXMubWF4LngmJnRoaXMubWluLnk8PXQubWluLnkmJnQubWF4Lnk8PXRoaXMubWF4LnkmJnRoaXMubWluLno8PXQubWluLnomJnQubWF4Lno8PXRoaXMubWF4Lnp9Z2V0UGFyYW1ldGVyKHQscil7cmV0dXJuIHIuc2V0KCh0LngtdGhpcy5taW4ueCkvKHRoaXMubWF4LngtdGhpcy5taW4ueCksKHQueS10aGlzLm1pbi55KS8odGhpcy5tYXgueS10aGlzLm1pbi55KSwodC56LXRoaXMubWluLnopLyh0aGlzLm1heC56LXRoaXMubWluLnopKX1pbnRlcnNlY3RzQm94KHQpe3JldHVybiEodC5tYXgueDx0aGlzLm1pbi54fHx0Lm1pbi54PnRoaXMubWF4Lnh8fHQubWF4Lnk8dGhpcy5taW4ueXx8dC5taW4ueT50aGlzLm1heC55fHx0Lm1heC56PHRoaXMubWluLnp8fHQubWluLno+dGhpcy5tYXgueil9aW50ZXJzZWN0c1NwaGVyZSh0KXtyZXR1cm4gdGhpcy5jbGFtcFBvaW50KHQuY2VudGVyLG12KSxtdi5kaXN0YW5jZVRvU3F1YXJlZCh0LmNlbnRlcik8PXQucmFkaXVzKnQucmFkaXVzfWludGVyc2VjdHNQbGFuZSh0KXtsZXQgcixuO3JldHVybiB0Lm5vcm1hbC54PjA/KHI9dC5ub3JtYWwueCp0aGlzLm1pbi54LG49dC5ub3JtYWwueCp0aGlzLm1heC54KToocj10Lm5vcm1hbC54KnRoaXMubWF4Lngsbj10Lm5vcm1hbC54KnRoaXMubWluLngpLHQubm9ybWFsLnk+MD8ocis9dC5ub3JtYWwueSp0aGlzLm1pbi55LG4rPXQubm9ybWFsLnkqdGhpcy5tYXgueSk6KHIrPXQubm9ybWFsLnkqdGhpcy5tYXgueSxuKz10Lm5vcm1hbC55KnRoaXMubWluLnkpLHQubm9ybWFsLno+MD8ocis9dC5ub3JtYWwueip0aGlzLm1pbi56LG4rPXQubm9ybWFsLnoqdGhpcy5tYXgueik6KHIrPXQubm9ybWFsLnoqdGhpcy5tYXgueixuKz10Lm5vcm1hbC56KnRoaXMubWluLnopLHI8PS10LmNvbnN0YW50JiZuPj0tdC5jb25zdGFudH1pbnRlcnNlY3RzVHJpYW5nbGUodCl7aWYodGhpcy5pc0VtcHR5KCkpcmV0dXJuITE7dGhpcy5nZXRDZW50ZXIoSVApLGhWLnN1YlZlY3RvcnModGhpcy5tYXgsSVApLHgzLnN1YlZlY3RvcnModC5hLElQKSxiMy5zdWJWZWN0b3JzKHQuYixJUCksdzMuc3ViVmVjdG9ycyh0LmMsSVApLFAwLnN1YlZlY3RvcnMoYjMseDMpLEkwLnN1YlZlY3RvcnModzMsYjMpLGd2LnN1YlZlY3RvcnMoeDMsdzMpO2xldCByPVswLC1QMC56LFAwLnksMCwtSTAueixJMC55LDAsLWd2LnosZ3YueSxQMC56LDAsLVAwLngsSTAueiwwLC1JMC54LGd2LnosMCwtZ3YueCwtUDAueSxQMC54LDAsLUkwLnksSTAueCwwLC1ndi55LGd2LngsMF07cmV0dXJuIUtjdChyLHgzLGIzLHczLGhWKXx8KHI9WzEsMCwwLDAsMSwwLDAsMCwxXSwhS2N0KHIseDMsYjMsdzMsaFYpKT8hMTooZlYuY3Jvc3NWZWN0b3JzKFAwLEkwKSxyPVtmVi54LGZWLnksZlYuel0sS2N0KHIseDMsYjMsdzMsaFYpKX1jbGFtcFBvaW50KHQscil7cmV0dXJuIHIuY29weSh0KS5jbGFtcCh0aGlzLm1pbix0aGlzLm1heCl9ZGlzdGFuY2VUb1BvaW50KHQpe3JldHVybiBtdi5jb3B5KHQpLmNsYW1wKHRoaXMubWluLHRoaXMubWF4KS5zdWIodCkubGVuZ3RoKCl9Z2V0Qm91bmRpbmdTcGhlcmUodCl7cmV0dXJuIHRoaXMuZ2V0Q2VudGVyKHQuY2VudGVyKSx0LnJhZGl1cz10aGlzLmdldFNpemUobXYpLmxlbmd0aCgpKi41LHR9aW50ZXJzZWN0KHQpe3JldHVybiB0aGlzLm1pbi5tYXgodC5taW4pLHRoaXMubWF4Lm1pbih0Lm1heCksdGhpcy5pc0VtcHR5KCkmJnRoaXMubWFrZUVtcHR5KCksdGhpc311bmlvbih0KXtyZXR1cm4gdGhpcy5taW4ubWluKHQubWluKSx0aGlzLm1heC5tYXgodC5tYXgpLHRoaXN9YXBwbHlNYXRyaXg0KHQpe3JldHVybiB0aGlzLmlzRW1wdHkoKT90aGlzOihWZFswXS5zZXQodGhpcy5taW4ueCx0aGlzLm1pbi55LHRoaXMubWluLnopLmFwcGx5TWF0cml4NCh0KSxWZFsxXS5zZXQodGhpcy5taW4ueCx0aGlzLm1pbi55LHRoaXMubWF4LnopLmFwcGx5TWF0cml4NCh0KSxWZFsyXS5zZXQodGhpcy5taW4ueCx0aGlzLm1heC55LHRoaXMubWluLnopLmFwcGx5TWF0cml4NCh0KSxWZFszXS5zZXQodGhpcy5taW4ueCx0aGlzLm1heC55LHRoaXMubWF4LnopLmFwcGx5TWF0cml4NCh0KSxWZFs0XS5zZXQodGhpcy5tYXgueCx0aGlzLm1pbi55LHRoaXMubWluLnopLmFwcGx5TWF0cml4NCh0KSxWZFs1XS5zZXQodGhpcy5tYXgueCx0aGlzLm1pbi55LHRoaXMubWF4LnopLmFwcGx5TWF0cml4NCh0KSxWZFs2XS5zZXQodGhpcy5tYXgueCx0aGlzLm1heC55LHRoaXMubWluLnopLmFwcGx5TWF0cml4NCh0KSxWZFs3XS5zZXQodGhpcy5tYXgueCx0aGlzLm1heC55LHRoaXMubWF4LnopLmFwcGx5TWF0cml4NCh0KSx0aGlzLnNldEZyb21Qb2ludHMoVmQpLHRoaXMpfXRyYW5zbGF0ZSh0KXtyZXR1cm4gdGhpcy5taW4uYWRkKHQpLHRoaXMubWF4LmFkZCh0KSx0aGlzfWVxdWFscyh0KXtyZXR1cm4gdC5taW4uZXF1YWxzKHRoaXMubWluKSYmdC5tYXguZXF1YWxzKHRoaXMubWF4KX19O3RhLnByb3RvdHlwZS5pc0JveDM9ITA7dmFyIFZkPVtuZXcgaixuZXcgaixuZXcgaixuZXcgaixuZXcgaixuZXcgaixuZXcgaixuZXcgal0sbXY9bmV3IGosJGN0PW5ldyB0YSx4Mz1uZXcgaixiMz1uZXcgaix3Mz1uZXcgaixQMD1uZXcgaixJMD1uZXcgaixndj1uZXcgaixJUD1uZXcgaixoVj1uZXcgaixmVj1uZXcgaixfdj1uZXcgajtmdW5jdGlvbiBLY3QoZSx0LHIsbixpKXtmb3IobGV0IG89MCxhPWUubGVuZ3RoLTM7bzw9YTtvKz0zKXtfdi5mcm9tQXJyYXkoZSxvKTtsZXQgcz1pLngqTWF0aC5hYnMoX3YueCkraS55Kk1hdGguYWJzKF92LnkpK2kueipNYXRoLmFicyhfdi56KSxsPXQuZG90KF92KSxjPXIuZG90KF92KSx1PW4uZG90KF92KTtpZihNYXRoLm1heCgtTWF0aC5tYXgobCxjLHUpLE1hdGgubWluKGwsYyx1KSk+cylyZXR1cm4hMX1yZXR1cm4hMH12YXIgcmZyPW5ldyB0YSxkdWU9bmV3IGoscFY9bmV3IGosWmN0PW5ldyBqLFpmPWNsYXNze2NvbnN0cnVjdG9yKHQ9bmV3IGoscj0tMSl7dGhpcy5jZW50ZXI9dCx0aGlzLnJhZGl1cz1yfXNldCh0LHIpe3JldHVybiB0aGlzLmNlbnRlci5jb3B5KHQpLHRoaXMucmFkaXVzPXIsdGhpc31zZXRGcm9tUG9pbnRzKHQscil7bGV0IG49dGhpcy5jZW50ZXI7ciE9PXZvaWQgMD9uLmNvcHkocik6cmZyLnNldEZyb21Qb2ludHModCkuZ2V0Q2VudGVyKG4pO2xldCBpPTA7Zm9yKGxldCBvPTAsYT10Lmxlbmd0aDtvPGE7bysrKWk9TWF0aC5tYXgoaSxuLmRpc3RhbmNlVG9TcXVhcmVkKHRbb10pKTtyZXR1cm4gdGhpcy5yYWRpdXM9TWF0aC5zcXJ0KGkpLHRoaXN9Y29weSh0KXtyZXR1cm4gdGhpcy5jZW50ZXIuY29weSh0LmNlbnRlciksdGhpcy5yYWRpdXM9dC5yYWRpdXMsdGhpc31pc0VtcHR5KCl7cmV0dXJuIHRoaXMucmFkaXVzPDB9bWFrZUVtcHR5KCl7cmV0dXJuIHRoaXMuY2VudGVyLnNldCgwLDAsMCksdGhpcy5yYWRpdXM9LTEsdGhpc31jb250YWluc1BvaW50KHQpe3JldHVybiB0LmRpc3RhbmNlVG9TcXVhcmVkKHRoaXMuY2VudGVyKTw9dGhpcy5yYWRpdXMqdGhpcy5yYWRpdXN9ZGlzdGFuY2VUb1BvaW50KHQpe3JldHVybiB0LmRpc3RhbmNlVG8odGhpcy5jZW50ZXIpLXRoaXMucmFkaXVzfWludGVyc2VjdHNTcGhlcmUodCl7bGV0IHI9dGhpcy5yYWRpdXMrdC5yYWRpdXM7cmV0dXJuIHQuY2VudGVyLmRpc3RhbmNlVG9TcXVhcmVkKHRoaXMuY2VudGVyKTw9cipyfWludGVyc2VjdHNCb3godCl7cmV0dXJuIHQuaW50ZXJzZWN0c1NwaGVyZSh0aGlzKX1pbnRlcnNlY3RzUGxhbmUodCl7cmV0dXJuIE1hdGguYWJzKHQuZGlzdGFuY2VUb1BvaW50KHRoaXMuY2VudGVyKSk8PXRoaXMucmFkaXVzfWNsYW1wUG9pbnQodCxyKXtsZXQgbj10aGlzLmNlbnRlci5kaXN0YW5jZVRvU3F1YXJlZCh0KTtyZXR1cm4gci5jb3B5KHQpLG4+dGhpcy5yYWRpdXMqdGhpcy5yYWRpdXMmJihyLnN1Yih0aGlzLmNlbnRlcikubm9ybWFsaXplKCksci5tdWx0aXBseVNjYWxhcih0aGlzLnJhZGl1cykuYWRkKHRoaXMuY2VudGVyKSkscn1nZXRCb3VuZGluZ0JveCh0KXtyZXR1cm4gdGhpcy5pc0VtcHR5KCk/KHQubWFrZUVtcHR5KCksdCk6KHQuc2V0KHRoaXMuY2VudGVyLHRoaXMuY2VudGVyKSx0LmV4cGFuZEJ5U2NhbGFyKHRoaXMucmFkaXVzKSx0KX1hcHBseU1hdHJpeDQodCl7cmV0dXJuIHRoaXMuY2VudGVyLmFwcGx5TWF0cml4NCh0KSx0aGlzLnJhZGl1cz10aGlzLnJhZGl1cyp0LmdldE1heFNjYWxlT25BeGlzKCksdGhpc310cmFuc2xhdGUodCl7cmV0dXJuIHRoaXMuY2VudGVyLmFkZCh0KSx0aGlzfWV4cGFuZEJ5UG9pbnQodCl7WmN0LnN1YlZlY3RvcnModCx0aGlzLmNlbnRlcik7bGV0IHI9WmN0Lmxlbmd0aFNxKCk7aWYocj50aGlzLnJhZGl1cyp0aGlzLnJhZGl1cyl7bGV0IG49TWF0aC5zcXJ0KHIpLGk9KG4tdGhpcy5yYWRpdXMpKi41O3RoaXMuY2VudGVyLmFkZChaY3QubXVsdGlwbHlTY2FsYXIoaS9uKSksdGhpcy5yYWRpdXMrPWl9cmV0dXJuIHRoaXN9dW5pb24odCl7cmV0dXJuIHRoaXMuY2VudGVyLmVxdWFscyh0LmNlbnRlcik9PT0hMD9wVi5zZXQoMCwwLDEpLm11bHRpcGx5U2NhbGFyKHQucmFkaXVzKTpwVi5zdWJWZWN0b3JzKHQuY2VudGVyLHRoaXMuY2VudGVyKS5ub3JtYWxpemUoKS5tdWx0aXBseVNjYWxhcih0LnJhZGl1cyksdGhpcy5leHBhbmRCeVBvaW50KGR1ZS5jb3B5KHQuY2VudGVyKS5hZGQocFYpKSx0aGlzLmV4cGFuZEJ5UG9pbnQoZHVlLmNvcHkodC5jZW50ZXIpLnN1YihwVikpLHRoaXN9ZXF1YWxzKHQpe3JldHVybiB0LmNlbnRlci5lcXVhbHModGhpcy5jZW50ZXIpJiZ0LnJhZGl1cz09PXRoaXMucmFkaXVzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX19LFVkPW5ldyBqLEpjdD1uZXcgaixkVj1uZXcgaixMMD1uZXcgaixRY3Q9bmV3IGosbVY9bmV3IGosdHV0PW5ldyBqLEpmPWNsYXNze2NvbnN0cnVjdG9yKHQ9bmV3IGoscj1uZXcgaigwLDAsLTEpKXt0aGlzLm9yaWdpbj10LHRoaXMuZGlyZWN0aW9uPXJ9c2V0KHQscil7cmV0dXJuIHRoaXMub3JpZ2luLmNvcHkodCksdGhpcy5kaXJlY3Rpb24uY29weShyKSx0aGlzfWNvcHkodCl7cmV0dXJuIHRoaXMub3JpZ2luLmNvcHkodC5vcmlnaW4pLHRoaXMuZGlyZWN0aW9uLmNvcHkodC5kaXJlY3Rpb24pLHRoaXN9YXQodCxyKXtyZXR1cm4gci5jb3B5KHRoaXMuZGlyZWN0aW9uKS5tdWx0aXBseVNjYWxhcih0KS5hZGQodGhpcy5vcmlnaW4pfWxvb2tBdCh0KXtyZXR1cm4gdGhpcy5kaXJlY3Rpb24uY29weSh0KS5zdWIodGhpcy5vcmlnaW4pLm5vcm1hbGl6ZSgpLHRoaXN9cmVjYXN0KHQpe3JldHVybiB0aGlzLm9yaWdpbi5jb3B5KHRoaXMuYXQodCxVZCkpLHRoaXN9Y2xvc2VzdFBvaW50VG9Qb2ludCh0LHIpe3Iuc3ViVmVjdG9ycyh0LHRoaXMub3JpZ2luKTtsZXQgbj1yLmRvdCh0aGlzLmRpcmVjdGlvbik7cmV0dXJuIG48MD9yLmNvcHkodGhpcy5vcmlnaW4pOnIuY29weSh0aGlzLmRpcmVjdGlvbikubXVsdGlwbHlTY2FsYXIobikuYWRkKHRoaXMub3JpZ2luKX1kaXN0YW5jZVRvUG9pbnQodCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLmRpc3RhbmNlU3FUb1BvaW50KHQpKX1kaXN0YW5jZVNxVG9Qb2ludCh0KXtsZXQgcj1VZC5zdWJWZWN0b3JzKHQsdGhpcy5vcmlnaW4pLmRvdCh0aGlzLmRpcmVjdGlvbik7cmV0dXJuIHI8MD90aGlzLm9yaWdpbi5kaXN0YW5jZVRvU3F1YXJlZCh0KTooVWQuY29weSh0aGlzLmRpcmVjdGlvbikubXVsdGlwbHlTY2FsYXIocikuYWRkKHRoaXMub3JpZ2luKSxVZC5kaXN0YW5jZVRvU3F1YXJlZCh0KSl9ZGlzdGFuY2VTcVRvU2VnbWVudCh0LHIsbixpKXtKY3QuY29weSh0KS5hZGQocikubXVsdGlwbHlTY2FsYXIoLjUpLGRWLmNvcHkocikuc3ViKHQpLm5vcm1hbGl6ZSgpLEwwLmNvcHkodGhpcy5vcmlnaW4pLnN1YihKY3QpO2xldCBvPXQuZGlzdGFuY2VUbyhyKSouNSxhPS10aGlzLmRpcmVjdGlvbi5kb3QoZFYpLHM9TDAuZG90KHRoaXMuZGlyZWN0aW9uKSxsPS1MMC5kb3QoZFYpLGM9TDAubGVuZ3RoU3EoKSx1PU1hdGguYWJzKDEtYSphKSxoLGYscCxkO2lmKHU+MClpZihoPWEqbC1zLGY9YSpzLWwsZD1vKnUsaD49MClpZihmPj0tZClpZihmPD1kKXtsZXQgZz0xL3U7aCo9ZyxmKj1nLHA9aCooaCthKmYrMipzKStmKihhKmgrZisyKmwpK2N9ZWxzZSBmPW8saD1NYXRoLm1heCgwLC0oYSpmK3MpKSxwPS1oKmgrZiooZisyKmwpK2M7ZWxzZSBmPS1vLGg9TWF0aC5tYXgoMCwtKGEqZitzKSkscD0taCpoK2YqKGYrMipsKStjO2Vsc2UgZjw9LWQ/KGg9TWF0aC5tYXgoMCwtKC1hKm8rcykpLGY9aD4wPy1vOk1hdGgubWluKE1hdGgubWF4KC1vLC1sKSxvKSxwPS1oKmgrZiooZisyKmwpK2MpOmY8PWQ/KGg9MCxmPU1hdGgubWluKE1hdGgubWF4KC1vLC1sKSxvKSxwPWYqKGYrMipsKStjKTooaD1NYXRoLm1heCgwLC0oYSpvK3MpKSxmPWg+MD9vOk1hdGgubWluKE1hdGgubWF4KC1vLC1sKSxvKSxwPS1oKmgrZiooZisyKmwpK2MpO2Vsc2UgZj1hPjA/LW86byxoPU1hdGgubWF4KDAsLShhKmYrcykpLHA9LWgqaCtmKihmKzIqbCkrYztyZXR1cm4gbiYmbi5jb3B5KHRoaXMuZGlyZWN0aW9uKS5tdWx0aXBseVNjYWxhcihoKS5hZGQodGhpcy5vcmlnaW4pLGkmJmkuY29weShkVikubXVsdGlwbHlTY2FsYXIoZikuYWRkKEpjdCkscH1pbnRlcnNlY3RTcGhlcmUodCxyKXtVZC5zdWJWZWN0b3JzKHQuY2VudGVyLHRoaXMub3JpZ2luKTtsZXQgbj1VZC5kb3QodGhpcy5kaXJlY3Rpb24pLGk9VWQuZG90KFVkKS1uKm4sbz10LnJhZGl1cyp0LnJhZGl1cztpZihpPm8pcmV0dXJuIG51bGw7bGV0IGE9TWF0aC5zcXJ0KG8taSkscz1uLWEsbD1uK2E7cmV0dXJuIHM8MCYmbDwwP251bGw6czwwP3RoaXMuYXQobCxyKTp0aGlzLmF0KHMscil9aW50ZXJzZWN0c1NwaGVyZSh0KXtyZXR1cm4gdGhpcy5kaXN0YW5jZVNxVG9Qb2ludCh0LmNlbnRlcik8PXQucmFkaXVzKnQucmFkaXVzfWRpc3RhbmNlVG9QbGFuZSh0KXtsZXQgcj10Lm5vcm1hbC5kb3QodGhpcy5kaXJlY3Rpb24pO2lmKHI9PT0wKXJldHVybiB0LmRpc3RhbmNlVG9Qb2ludCh0aGlzLm9yaWdpbik9PT0wPzA6bnVsbDtsZXQgbj0tKHRoaXMub3JpZ2luLmRvdCh0Lm5vcm1hbCkrdC5jb25zdGFudCkvcjtyZXR1cm4gbj49MD9uOm51bGx9aW50ZXJzZWN0UGxhbmUodCxyKXtsZXQgbj10aGlzLmRpc3RhbmNlVG9QbGFuZSh0KTtyZXR1cm4gbj09PW51bGw/bnVsbDp0aGlzLmF0KG4scil9aW50ZXJzZWN0c1BsYW5lKHQpe2xldCByPXQuZGlzdGFuY2VUb1BvaW50KHRoaXMub3JpZ2luKTtyZXR1cm4gcj09PTB8fHQubm9ybWFsLmRvdCh0aGlzLmRpcmVjdGlvbikqcjwwfWludGVyc2VjdEJveCh0LHIpe2xldCBuLGksbyxhLHMsbCxjPTEvdGhpcy5kaXJlY3Rpb24ueCx1PTEvdGhpcy5kaXJlY3Rpb24ueSxoPTEvdGhpcy5kaXJlY3Rpb24ueixmPXRoaXMub3JpZ2luO3JldHVybiBjPj0wPyhuPSh0Lm1pbi54LWYueCkqYyxpPSh0Lm1heC54LWYueCkqYyk6KG49KHQubWF4LngtZi54KSpjLGk9KHQubWluLngtZi54KSpjKSx1Pj0wPyhvPSh0Lm1pbi55LWYueSkqdSxhPSh0Lm1heC55LWYueSkqdSk6KG89KHQubWF4LnktZi55KSp1LGE9KHQubWluLnktZi55KSp1KSxuPmF8fG8+aXx8KChvPm58fG4hPT1uKSYmKG49byksKGE8aXx8aSE9PWkpJiYoaT1hKSxoPj0wPyhzPSh0Lm1pbi56LWYueikqaCxsPSh0Lm1heC56LWYueikqaCk6KHM9KHQubWF4LnotZi56KSpoLGw9KHQubWluLnotZi56KSpoKSxuPmx8fHM+aSl8fCgocz5ufHxuIT09bikmJihuPXMpLChsPGl8fGkhPT1pKSYmKGk9bCksaTwwKT9udWxsOnRoaXMuYXQobj49MD9uOmkscil9aW50ZXJzZWN0c0JveCh0KXtyZXR1cm4gdGhpcy5pbnRlcnNlY3RCb3godCxVZCkhPT1udWxsfWludGVyc2VjdFRyaWFuZ2xlKHQscixuLGksbyl7UWN0LnN1YlZlY3RvcnMocix0KSxtVi5zdWJWZWN0b3JzKG4sdCksdHV0LmNyb3NzVmVjdG9ycyhRY3QsbVYpO2xldCBhPXRoaXMuZGlyZWN0aW9uLmRvdCh0dXQpLHM7aWYoYT4wKXtpZihpKXJldHVybiBudWxsO3M9MX1lbHNlIGlmKGE8MClzPS0xLGE9LWE7ZWxzZSByZXR1cm4gbnVsbDtMMC5zdWJWZWN0b3JzKHRoaXMub3JpZ2luLHQpO2xldCBsPXMqdGhpcy5kaXJlY3Rpb24uZG90KG1WLmNyb3NzVmVjdG9ycyhMMCxtVikpO2lmKGw8MClyZXR1cm4gbnVsbDtsZXQgYz1zKnRoaXMuZGlyZWN0aW9uLmRvdChRY3QuY3Jvc3MoTDApKTtpZihjPDB8fGwrYz5hKXJldHVybiBudWxsO2xldCB1PS1zKkwwLmRvdCh0dXQpO3JldHVybiB1PDA/bnVsbDp0aGlzLmF0KHUvYSxvKX1hcHBseU1hdHJpeDQodCl7cmV0dXJuIHRoaXMub3JpZ2luLmFwcGx5TWF0cml4NCh0KSx0aGlzLmRpcmVjdGlvbi50cmFuc2Zvcm1EaXJlY3Rpb24odCksdGhpc31lcXVhbHModCl7cmV0dXJuIHQub3JpZ2luLmVxdWFscyh0aGlzLm9yaWdpbikmJnQuZGlyZWN0aW9uLmVxdWFscyh0aGlzLmRpcmVjdGlvbil9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfX0sTWU9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmVsZW1lbnRzPVsxLDAsMCwwLDAsMSwwLDAsMCwwLDEsMCwwLDAsMCwxXSxhcmd1bWVudHMubGVuZ3RoPjAmJmNvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IHRoZSBjb25zdHJ1Y3RvciBubyBsb25nZXIgcmVhZHMgYXJndW1lbnRzLiB1c2UgLnNldCgpIGluc3RlYWQuIil9c2V0KHQscixuLGksbyxhLHMsbCxjLHUsaCxmLHAsZCxnLF8pe2xldCB5PXRoaXMuZWxlbWVudHM7cmV0dXJuIHlbMF09dCx5WzRdPXIseVs4XT1uLHlbMTJdPWkseVsxXT1vLHlbNV09YSx5WzldPXMseVsxM109bCx5WzJdPWMseVs2XT11LHlbMTBdPWgseVsxNF09Zix5WzNdPXAseVs3XT1kLHlbMTFdPWcseVsxNV09Xyx0aGlzfWlkZW50aXR5KCl7cmV0dXJuIHRoaXMuc2V0KDEsMCwwLDAsMCwxLDAsMCwwLDAsMSwwLDAsMCwwLDEpLHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IE1lKCkuZnJvbUFycmF5KHRoaXMuZWxlbWVudHMpfWNvcHkodCl7bGV0IHI9dGhpcy5lbGVtZW50cyxuPXQuZWxlbWVudHM7cmV0dXJuIHJbMF09blswXSxyWzFdPW5bMV0sclsyXT1uWzJdLHJbM109blszXSxyWzRdPW5bNF0scls1XT1uWzVdLHJbNl09bls2XSxyWzddPW5bN10scls4XT1uWzhdLHJbOV09bls5XSxyWzEwXT1uWzEwXSxyWzExXT1uWzExXSxyWzEyXT1uWzEyXSxyWzEzXT1uWzEzXSxyWzE0XT1uWzE0XSxyWzE1XT1uWzE1XSx0aGlzfWNvcHlQb3NpdGlvbih0KXtsZXQgcj10aGlzLmVsZW1lbnRzLG49dC5lbGVtZW50cztyZXR1cm4gclsxMl09blsxMl0sclsxM109blsxM10sclsxNF09blsxNF0sdGhpc31zZXRGcm9tTWF0cml4Myh0KXtsZXQgcj10LmVsZW1lbnRzO3JldHVybiB0aGlzLnNldChyWzBdLHJbM10scls2XSwwLHJbMV0scls0XSxyWzddLDAsclsyXSxyWzVdLHJbOF0sMCwwLDAsMCwxKSx0aGlzfWV4dHJhY3RCYXNpcyh0LHIsbil7cmV0dXJuIHQuc2V0RnJvbU1hdHJpeENvbHVtbih0aGlzLDApLHIuc2V0RnJvbU1hdHJpeENvbHVtbih0aGlzLDEpLG4uc2V0RnJvbU1hdHJpeENvbHVtbih0aGlzLDIpLHRoaXN9bWFrZUJhc2lzKHQscixuKXtyZXR1cm4gdGhpcy5zZXQodC54LHIueCxuLngsMCx0Lnksci55LG4ueSwwLHQueixyLnosbi56LDAsMCwwLDAsMSksdGhpc31leHRyYWN0Um90YXRpb24odCl7bGV0IHI9dGhpcy5lbGVtZW50cyxuPXQuZWxlbWVudHMsaT0xL1MzLnNldEZyb21NYXRyaXhDb2x1bW4odCwwKS5sZW5ndGgoKSxvPTEvUzMuc2V0RnJvbU1hdHJpeENvbHVtbih0LDEpLmxlbmd0aCgpLGE9MS9TMy5zZXRGcm9tTWF0cml4Q29sdW1uKHQsMikubGVuZ3RoKCk7cmV0dXJuIHJbMF09blswXSppLHJbMV09blsxXSppLHJbMl09blsyXSppLHJbM109MCxyWzRdPW5bNF0qbyxyWzVdPW5bNV0qbyxyWzZdPW5bNl0qbyxyWzddPTAscls4XT1uWzhdKmEscls5XT1uWzldKmEsclsxMF09blsxMF0qYSxyWzExXT0wLHJbMTJdPTAsclsxM109MCxyWzE0XT0wLHJbMTVdPTEsdGhpc31tYWtlUm90YXRpb25Gcm9tRXVsZXIodCl7dCYmdC5pc0V1bGVyfHxjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXg0OiAubWFrZVJvdGF0aW9uRnJvbUV1bGVyKCkgbm93IGV4cGVjdHMgYSBFdWxlciByb3RhdGlvbiByYXRoZXIgdGhhbiBhIFZlY3RvcjMgYW5kIG9yZGVyLiIpO2xldCByPXRoaXMuZWxlbWVudHMsbj10LngsaT10Lnksbz10LnosYT1NYXRoLmNvcyhuKSxzPU1hdGguc2luKG4pLGw9TWF0aC5jb3MoaSksYz1NYXRoLnNpbihpKSx1PU1hdGguY29zKG8pLGg9TWF0aC5zaW4obyk7aWYodC5vcmRlcj09PSJYWVoiKXtsZXQgZj1hKnUscD1hKmgsZD1zKnUsZz1zKmg7clswXT1sKnUscls0XT0tbCpoLHJbOF09YyxyWzFdPXArZCpjLHJbNV09Zi1nKmMscls5XT0tcypsLHJbMl09Zy1mKmMscls2XT1kK3AqYyxyWzEwXT1hKmx9ZWxzZSBpZih0Lm9yZGVyPT09IllYWiIpe2xldCBmPWwqdSxwPWwqaCxkPWMqdSxnPWMqaDtyWzBdPWYrZypzLHJbNF09ZCpzLXAscls4XT1hKmMsclsxXT1hKmgscls1XT1hKnUscls5XT0tcyxyWzJdPXAqcy1kLHJbNl09ZytmKnMsclsxMF09YSpsfWVsc2UgaWYodC5vcmRlcj09PSJaWFkiKXtsZXQgZj1sKnUscD1sKmgsZD1jKnUsZz1jKmg7clswXT1mLWcqcyxyWzRdPS1hKmgscls4XT1kK3AqcyxyWzFdPXArZCpzLHJbNV09YSp1LHJbOV09Zy1mKnMsclsyXT0tYSpjLHJbNl09cyxyWzEwXT1hKmx9ZWxzZSBpZih0Lm9yZGVyPT09IlpZWCIpe2xldCBmPWEqdSxwPWEqaCxkPXMqdSxnPXMqaDtyWzBdPWwqdSxyWzRdPWQqYy1wLHJbOF09ZipjK2csclsxXT1sKmgscls1XT1nKmMrZixyWzldPXAqYy1kLHJbMl09LWMscls2XT1zKmwsclsxMF09YSpsfWVsc2UgaWYodC5vcmRlcj09PSJZWlgiKXtsZXQgZj1hKmwscD1hKmMsZD1zKmwsZz1zKmM7clswXT1sKnUscls0XT1nLWYqaCxyWzhdPWQqaCtwLHJbMV09aCxyWzVdPWEqdSxyWzldPS1zKnUsclsyXT0tYyp1LHJbNl09cCpoK2QsclsxMF09Zi1nKmh9ZWxzZSBpZih0Lm9yZGVyPT09IlhaWSIpe2xldCBmPWEqbCxwPWEqYyxkPXMqbCxnPXMqYztyWzBdPWwqdSxyWzRdPS1oLHJbOF09Yyp1LHJbMV09ZipoK2cscls1XT1hKnUscls5XT1wKmgtZCxyWzJdPWQqaC1wLHJbNl09cyp1LHJbMTBdPWcqaCtmfXJldHVybiByWzNdPTAscls3XT0wLHJbMTFdPTAsclsxMl09MCxyWzEzXT0wLHJbMTRdPTAsclsxNV09MSx0aGlzfW1ha2VSb3RhdGlvbkZyb21RdWF0ZXJuaW9uKHQpe3JldHVybiB0aGlzLmNvbXBvc2UobmZyLHQsaWZyKX1sb29rQXQodCxyLG4pe2xldCBpPXRoaXMuZWxlbWVudHM7cmV0dXJuIGtsLnN1YlZlY3RvcnModCxyKSxrbC5sZW5ndGhTcSgpPT09MCYmKGtsLno9MSksa2wubm9ybWFsaXplKCksazAuY3Jvc3NWZWN0b3JzKG4sa2wpLGswLmxlbmd0aFNxKCk9PT0wJiYoTWF0aC5hYnMobi56KT09PTE/a2wueCs9MWUtNDprbC56Kz0xZS00LGtsLm5vcm1hbGl6ZSgpLGswLmNyb3NzVmVjdG9ycyhuLGtsKSksazAubm9ybWFsaXplKCksZ1YuY3Jvc3NWZWN0b3JzKGtsLGswKSxpWzBdPWswLngsaVs0XT1nVi54LGlbOF09a2wueCxpWzFdPWswLnksaVs1XT1nVi55LGlbOV09a2wueSxpWzJdPWswLnosaVs2XT1nVi56LGlbMTBdPWtsLnosdGhpc31tdWx0aXBseSh0LHIpe3JldHVybiByIT09dm9pZCAwPyhjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5tdWx0aXBseSgpIG5vdyBvbmx5IGFjY2VwdHMgb25lIGFyZ3VtZW50LiBVc2UgLm11bHRpcGx5TWF0cmljZXMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMubXVsdGlwbHlNYXRyaWNlcyh0LHIpKTp0aGlzLm11bHRpcGx5TWF0cmljZXModGhpcyx0KX1wcmVtdWx0aXBseSh0KXtyZXR1cm4gdGhpcy5tdWx0aXBseU1hdHJpY2VzKHQsdGhpcyl9bXVsdGlwbHlNYXRyaWNlcyh0LHIpe2xldCBuPXQuZWxlbWVudHMsaT1yLmVsZW1lbnRzLG89dGhpcy5lbGVtZW50cyxhPW5bMF0scz1uWzRdLGw9bls4XSxjPW5bMTJdLHU9blsxXSxoPW5bNV0sZj1uWzldLHA9blsxM10sZD1uWzJdLGc9bls2XSxfPW5bMTBdLHk9blsxNF0seD1uWzNdLGI9bls3XSxTPW5bMTFdLEM9blsxNV0sUD1pWzBdLGs9aVs0XSxPPWlbOF0sRD1pWzEyXSxCPWlbMV0sST1pWzVdLEw9aVs5XSxSPWlbMTNdLEY9aVsyXSx6PWlbNl0sVT1pWzEwXSxXPWlbMTRdLFo9aVszXSxydD1pWzddLG90PWlbMTFdLHN0PWlbMTVdO3JldHVybiBvWzBdPWEqUCtzKkIrbCpGK2MqWixvWzRdPWEqaytzKkkrbCp6K2MqcnQsb1s4XT1hKk8rcypMK2wqVStjKm90LG9bMTJdPWEqRCtzKlIrbCpXK2Mqc3Qsb1sxXT11KlAraCpCK2YqRitwKlosb1s1XT11KmsraCpJK2YqeitwKnJ0LG9bOV09dSpPK2gqTCtmKlUrcCpvdCxvWzEzXT11KkQraCpSK2YqVytwKnN0LG9bMl09ZCpQK2cqQitfKkYreSpaLG9bNl09ZCprK2cqSStfKnoreSpydCxvWzEwXT1kKk8rZypMK18qVSt5Km90LG9bMTRdPWQqRCtnKlIrXypXK3kqc3Qsb1szXT14KlArYipCK1MqRitDKlosb1s3XT14KmsrYipJK1MqeitDKnJ0LG9bMTFdPXgqTytiKkwrUypVK0Mqb3Qsb1sxNV09eCpEK2IqUitTKlcrQypzdCx0aGlzfW11bHRpcGx5U2NhbGFyKHQpe2xldCByPXRoaXMuZWxlbWVudHM7cmV0dXJuIHJbMF0qPXQscls0XSo9dCxyWzhdKj10LHJbMTJdKj10LHJbMV0qPXQscls1XSo9dCxyWzldKj10LHJbMTNdKj10LHJbMl0qPXQscls2XSo9dCxyWzEwXSo9dCxyWzE0XSo9dCxyWzNdKj10LHJbN10qPXQsclsxMV0qPXQsclsxNV0qPXQsdGhpc31kZXRlcm1pbmFudCgpe2xldCB0PXRoaXMuZWxlbWVudHMscj10WzBdLG49dFs0XSxpPXRbOF0sbz10WzEyXSxhPXRbMV0scz10WzVdLGw9dFs5XSxjPXRbMTNdLHU9dFsyXSxoPXRbNl0sZj10WzEwXSxwPXRbMTRdLGQ9dFszXSxnPXRbN10sXz10WzExXSx5PXRbMTVdO3JldHVybiBkKigrbypsKmgtaSpjKmgtbypzKmYrbipjKmYraSpzKnAtbipsKnApK2cqKCtyKmwqcC1yKmMqZitvKmEqZi1pKmEqcCtpKmMqdS1vKmwqdSkrXyooK3IqYypoLXIqcypwLW8qYSpoK24qYSpwK28qcyp1LW4qYyp1KSt5KigtaSpzKnUtcipsKmgrcipzKmYraSphKmgtbiphKmYrbipsKnUpfXRyYW5zcG9zZSgpe2xldCB0PXRoaXMuZWxlbWVudHMscjtyZXR1cm4gcj10WzFdLHRbMV09dFs0XSx0WzRdPXIscj10WzJdLHRbMl09dFs4XSx0WzhdPXIscj10WzZdLHRbNl09dFs5XSx0WzldPXIscj10WzNdLHRbM109dFsxMl0sdFsxMl09cixyPXRbN10sdFs3XT10WzEzXSx0WzEzXT1yLHI9dFsxMV0sdFsxMV09dFsxNF0sdFsxNF09cix0aGlzfXNldFBvc2l0aW9uKHQscixuKXtsZXQgaT10aGlzLmVsZW1lbnRzO3JldHVybiB0LmlzVmVjdG9yMz8oaVsxMl09dC54LGlbMTNdPXQueSxpWzE0XT10LnopOihpWzEyXT10LGlbMTNdPXIsaVsxNF09biksdGhpc31pbnZlcnQoKXtsZXQgdD10aGlzLmVsZW1lbnRzLHI9dFswXSxuPXRbMV0saT10WzJdLG89dFszXSxhPXRbNF0scz10WzVdLGw9dFs2XSxjPXRbN10sdT10WzhdLGg9dFs5XSxmPXRbMTBdLHA9dFsxMV0sZD10WzEyXSxnPXRbMTNdLF89dFsxNF0seT10WzE1XSx4PWgqXypjLWcqZipjK2cqbCpwLXMqXypwLWgqbCp5K3MqZip5LGI9ZCpmKmMtdSpfKmMtZCpsKnArYSpfKnArdSpsKnktYSpmKnksUz11KmcqYy1kKmgqYytkKnMqcC1hKmcqcC11KnMqeSthKmgqeSxDPWQqaCpsLXUqZypsLWQqcypmK2EqZypmK3UqcypfLWEqaCpfLFA9cip4K24qYitpKlMrbypDO2lmKFA9PT0wKXJldHVybiB0aGlzLnNldCgwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwKTtsZXQgaz0xL1A7cmV0dXJuIHRbMF09eCprLHRbMV09KGcqZipvLWgqXypvLWcqaSpwK24qXypwK2gqaSp5LW4qZip5KSprLHRbMl09KHMqXypvLWcqbCpvK2cqaSpjLW4qXypjLXMqaSp5K24qbCp5KSprLHRbM109KGgqbCpvLXMqZipvLWgqaSpjK24qZipjK3MqaSpwLW4qbCpwKSprLHRbNF09YiprLHRbNV09KHUqXypvLWQqZipvK2QqaSpwLXIqXypwLXUqaSp5K3IqZip5KSprLHRbNl09KGQqbCpvLWEqXypvLWQqaSpjK3IqXypjK2EqaSp5LXIqbCp5KSprLHRbN109KGEqZipvLXUqbCpvK3UqaSpjLXIqZipjLWEqaSpwK3IqbCpwKSprLHRbOF09UyprLHRbOV09KGQqaCpvLXUqZypvLWQqbipwK3IqZypwK3Uqbip5LXIqaCp5KSprLHRbMTBdPShhKmcqby1kKnMqbytkKm4qYy1yKmcqYy1hKm4qeStyKnMqeSkqayx0WzExXT0odSpzKm8tYSpoKm8tdSpuKmMrcipoKmMrYSpuKnAtcipzKnApKmssdFsxMl09QyprLHRbMTNdPSh1KmcqaS1kKmgqaStkKm4qZi1yKmcqZi11Km4qXytyKmgqXykqayx0WzE0XT0oZCpzKmktYSpnKmktZCpuKmwrcipnKmwrYSpuKl8tcipzKl8pKmssdFsxNV09KGEqaCppLXUqcyppK3UqbipsLXIqaCpsLWEqbipmK3IqcypmKSprLHRoaXN9c2NhbGUodCl7bGV0IHI9dGhpcy5lbGVtZW50cyxuPXQueCxpPXQueSxvPXQuejtyZXR1cm4gclswXSo9bixyWzRdKj1pLHJbOF0qPW8sclsxXSo9bixyWzVdKj1pLHJbOV0qPW8sclsyXSo9bixyWzZdKj1pLHJbMTBdKj1vLHJbM10qPW4scls3XSo9aSxyWzExXSo9byx0aGlzfWdldE1heFNjYWxlT25BeGlzKCl7bGV0IHQ9dGhpcy5lbGVtZW50cyxyPXRbMF0qdFswXSt0WzFdKnRbMV0rdFsyXSp0WzJdLG49dFs0XSp0WzRdK3RbNV0qdFs1XSt0WzZdKnRbNl0saT10WzhdKnRbOF0rdFs5XSp0WzldK3RbMTBdKnRbMTBdO3JldHVybiBNYXRoLnNxcnQoTWF0aC5tYXgocixuLGkpKX1tYWtlVHJhbnNsYXRpb24odCxyLG4pe3JldHVybiB0aGlzLnNldCgxLDAsMCx0LDAsMSwwLHIsMCwwLDEsbiwwLDAsMCwxKSx0aGlzfW1ha2VSb3RhdGlvblgodCl7bGV0IHI9TWF0aC5jb3ModCksbj1NYXRoLnNpbih0KTtyZXR1cm4gdGhpcy5zZXQoMSwwLDAsMCwwLHIsLW4sMCwwLG4sciwwLDAsMCwwLDEpLHRoaXN9bWFrZVJvdGF0aW9uWSh0KXtsZXQgcj1NYXRoLmNvcyh0KSxuPU1hdGguc2luKHQpO3JldHVybiB0aGlzLnNldChyLDAsbiwwLDAsMSwwLDAsLW4sMCxyLDAsMCwwLDAsMSksdGhpc31tYWtlUm90YXRpb25aKHQpe2xldCByPU1hdGguY29zKHQpLG49TWF0aC5zaW4odCk7cmV0dXJuIHRoaXMuc2V0KHIsLW4sMCwwLG4sciwwLDAsMCwwLDEsMCwwLDAsMCwxKSx0aGlzfW1ha2VSb3RhdGlvbkF4aXModCxyKXtsZXQgbj1NYXRoLmNvcyhyKSxpPU1hdGguc2luKHIpLG89MS1uLGE9dC54LHM9dC55LGw9dC56LGM9byphLHU9bypzO3JldHVybiB0aGlzLnNldChjKmErbixjKnMtaSpsLGMqbCtpKnMsMCxjKnMraSpsLHUqcytuLHUqbC1pKmEsMCxjKmwtaSpzLHUqbCtpKmEsbypsKmwrbiwwLDAsMCwwLDEpLHRoaXN9bWFrZVNjYWxlKHQscixuKXtyZXR1cm4gdGhpcy5zZXQodCwwLDAsMCwwLHIsMCwwLDAsMCxuLDAsMCwwLDAsMSksdGhpc31tYWtlU2hlYXIodCxyLG4saSxvLGEpe3JldHVybiB0aGlzLnNldCgxLG4sbywwLHQsMSxhLDAscixpLDEsMCwwLDAsMCwxKSx0aGlzfWNvbXBvc2UodCxyLG4pe2xldCBpPXRoaXMuZWxlbWVudHMsbz1yLl94LGE9ci5feSxzPXIuX3osbD1yLl93LGM9bytvLHU9YSthLGg9cytzLGY9bypjLHA9byp1LGQ9bypoLGc9YSp1LF89YSpoLHk9cypoLHg9bCpjLGI9bCp1LFM9bCpoLEM9bi54LFA9bi55LGs9bi56O3JldHVybiBpWzBdPSgxLShnK3kpKSpDLGlbMV09KHArUykqQyxpWzJdPShkLWIpKkMsaVszXT0wLGlbNF09KHAtUykqUCxpWzVdPSgxLShmK3kpKSpQLGlbNl09KF8reCkqUCxpWzddPTAsaVs4XT0oZCtiKSprLGlbOV09KF8teCkqayxpWzEwXT0oMS0oZitnKSkqayxpWzExXT0wLGlbMTJdPXQueCxpWzEzXT10LnksaVsxNF09dC56LGlbMTVdPTEsdGhpc31kZWNvbXBvc2UodCxyLG4pe2xldCBpPXRoaXMuZWxlbWVudHMsbz1TMy5zZXQoaVswXSxpWzFdLGlbMl0pLmxlbmd0aCgpLGE9UzMuc2V0KGlbNF0saVs1XSxpWzZdKS5sZW5ndGgoKSxzPVMzLnNldChpWzhdLGlbOV0saVsxMF0pLmxlbmd0aCgpO3RoaXMuZGV0ZXJtaW5hbnQoKTwwJiYobz0tbyksdC54PWlbMTJdLHQueT1pWzEzXSx0Lno9aVsxNF0saWguY29weSh0aGlzKTtsZXQgYz0xL28sdT0xL2EsaD0xL3M7cmV0dXJuIGloLmVsZW1lbnRzWzBdKj1jLGloLmVsZW1lbnRzWzFdKj1jLGloLmVsZW1lbnRzWzJdKj1jLGloLmVsZW1lbnRzWzRdKj11LGloLmVsZW1lbnRzWzVdKj11LGloLmVsZW1lbnRzWzZdKj11LGloLmVsZW1lbnRzWzhdKj1oLGloLmVsZW1lbnRzWzldKj1oLGloLmVsZW1lbnRzWzEwXSo9aCxyLnNldEZyb21Sb3RhdGlvbk1hdHJpeChpaCksbi54PW8sbi55PWEsbi56PXMsdGhpc31tYWtlUGVyc3BlY3RpdmUodCxyLG4saSxvLGEpe2E9PT12b2lkIDAmJmNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLm1ha2VQZXJzcGVjdGl2ZSgpIGhhcyBiZWVuIHJlZGVmaW5lZCBhbmQgaGFzIGEgbmV3IHNpZ25hdHVyZS4gUGxlYXNlIGNoZWNrIHRoZSBkb2NzLiIpO2xldCBzPXRoaXMuZWxlbWVudHMsbD0yKm8vKHItdCksYz0yKm8vKG4taSksdT0ocit0KS8oci10KSxoPShuK2kpLyhuLWkpLGY9LShhK28pLyhhLW8pLHA9LTIqYSpvLyhhLW8pO3JldHVybiBzWzBdPWwsc1s0XT0wLHNbOF09dSxzWzEyXT0wLHNbMV09MCxzWzVdPWMsc1s5XT1oLHNbMTNdPTAsc1syXT0wLHNbNl09MCxzWzEwXT1mLHNbMTRdPXAsc1szXT0wLHNbN109MCxzWzExXT0tMSxzWzE1XT0wLHRoaXN9bWFrZU9ydGhvZ3JhcGhpYyh0LHIsbixpLG8sYSl7bGV0IHM9dGhpcy5lbGVtZW50cyxsPTEvKHItdCksYz0xLyhuLWkpLHU9MS8oYS1vKSxoPShyK3QpKmwsZj0obitpKSpjLHA9KGErbykqdTtyZXR1cm4gc1swXT0yKmwsc1s0XT0wLHNbOF09MCxzWzEyXT0taCxzWzFdPTAsc1s1XT0yKmMsc1s5XT0wLHNbMTNdPS1mLHNbMl09MCxzWzZdPTAsc1sxMF09LTIqdSxzWzE0XT0tcCxzWzNdPTAsc1s3XT0wLHNbMTFdPTAsc1sxNV09MSx0aGlzfWVxdWFscyh0KXtsZXQgcj10aGlzLmVsZW1lbnRzLG49dC5lbGVtZW50cztmb3IobGV0IGk9MDtpPDE2O2krKylpZihyW2ldIT09bltpXSlyZXR1cm4hMTtyZXR1cm4hMH1mcm9tQXJyYXkodCxyPTApe2ZvcihsZXQgbj0wO248MTY7bisrKXRoaXMuZWxlbWVudHNbbl09dFtuK3JdO3JldHVybiB0aGlzfXRvQXJyYXkodD1bXSxyPTApe2xldCBuPXRoaXMuZWxlbWVudHM7cmV0dXJuIHRbcl09blswXSx0W3IrMV09blsxXSx0W3IrMl09blsyXSx0W3IrM109blszXSx0W3IrNF09bls0XSx0W3IrNV09bls1XSx0W3IrNl09bls2XSx0W3IrN109bls3XSx0W3IrOF09bls4XSx0W3IrOV09bls5XSx0W3IrMTBdPW5bMTBdLHRbcisxMV09blsxMV0sdFtyKzEyXT1uWzEyXSx0W3IrMTNdPW5bMTNdLHRbcisxNF09blsxNF0sdFtyKzE1XT1uWzE1XSx0fX07TWUucHJvdG90eXBlLmlzTWF0cml4ND0hMDt2YXIgUzM9bmV3IGosaWg9bmV3IE1lLG5mcj1uZXcgaigwLDAsMCksaWZyPW5ldyBqKDEsMSwxKSxrMD1uZXcgaixnVj1uZXcgaixrbD1uZXcgaixtdWU9bmV3IE1lLGd1ZT1uZXcgdmksdG09Y2xhc3N7Y29uc3RydWN0b3IodD0wLHI9MCxuPTAsaT10bS5EZWZhdWx0T3JkZXIpe3RoaXMuX3g9dCx0aGlzLl95PXIsdGhpcy5fej1uLHRoaXMuX29yZGVyPWl9Z2V0IHgoKXtyZXR1cm4gdGhpcy5feH1zZXQgeCh0KXt0aGlzLl94PXQsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpfWdldCB5KCl7cmV0dXJuIHRoaXMuX3l9c2V0IHkodCl7dGhpcy5feT10LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKX1nZXQgeigpe3JldHVybiB0aGlzLl96fXNldCB6KHQpe3RoaXMuX3o9dCx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCl9Z2V0IG9yZGVyKCl7cmV0dXJuIHRoaXMuX29yZGVyfXNldCBvcmRlcih0KXt0aGlzLl9vcmRlcj10LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKX1zZXQodCxyLG4saT10aGlzLl9vcmRlcil7cmV0dXJuIHRoaXMuX3g9dCx0aGlzLl95PXIsdGhpcy5fej1uLHRoaXMuX29yZGVyPWksdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5feCx0aGlzLl95LHRoaXMuX3osdGhpcy5fb3JkZXIpfWNvcHkodCl7cmV0dXJuIHRoaXMuX3g9dC5feCx0aGlzLl95PXQuX3ksdGhpcy5fej10Ll96LHRoaXMuX29yZGVyPXQuX29yZGVyLHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21Sb3RhdGlvbk1hdHJpeCh0LHI9dGhpcy5fb3JkZXIsbj0hMCl7bGV0IGk9dC5lbGVtZW50cyxvPWlbMF0sYT1pWzRdLHM9aVs4XSxsPWlbMV0sYz1pWzVdLHU9aVs5XSxoPWlbMl0sZj1pWzZdLHA9aVsxMF07c3dpdGNoKHIpe2Nhc2UiWFlaIjp0aGlzLl95PU1hdGguYXNpbihabyhzLC0xLDEpKSxNYXRoLmFicyhzKTwuOTk5OTk5OT8odGhpcy5feD1NYXRoLmF0YW4yKC11LHApLHRoaXMuX3o9TWF0aC5hdGFuMigtYSxvKSk6KHRoaXMuX3g9TWF0aC5hdGFuMihmLGMpLHRoaXMuX3o9MCk7YnJlYWs7Y2FzZSJZWFoiOnRoaXMuX3g9TWF0aC5hc2luKC1abyh1LC0xLDEpKSxNYXRoLmFicyh1KTwuOTk5OTk5OT8odGhpcy5feT1NYXRoLmF0YW4yKHMscCksdGhpcy5fej1NYXRoLmF0YW4yKGwsYykpOih0aGlzLl95PU1hdGguYXRhbjIoLWgsbyksdGhpcy5fej0wKTticmVhaztjYXNlIlpYWSI6dGhpcy5feD1NYXRoLmFzaW4oWm8oZiwtMSwxKSksTWF0aC5hYnMoZik8Ljk5OTk5OTk/KHRoaXMuX3k9TWF0aC5hdGFuMigtaCxwKSx0aGlzLl96PU1hdGguYXRhbjIoLWEsYykpOih0aGlzLl95PTAsdGhpcy5fej1NYXRoLmF0YW4yKGwsbykpO2JyZWFrO2Nhc2UiWllYIjp0aGlzLl95PU1hdGguYXNpbigtWm8oaCwtMSwxKSksTWF0aC5hYnMoaCk8Ljk5OTk5OTk/KHRoaXMuX3g9TWF0aC5hdGFuMihmLHApLHRoaXMuX3o9TWF0aC5hdGFuMihsLG8pKToodGhpcy5feD0wLHRoaXMuX3o9TWF0aC5hdGFuMigtYSxjKSk7YnJlYWs7Y2FzZSJZWlgiOnRoaXMuX3o9TWF0aC5hc2luKFpvKGwsLTEsMSkpLE1hdGguYWJzKGwpPC45OTk5OTk5Pyh0aGlzLl94PU1hdGguYXRhbjIoLXUsYyksdGhpcy5feT1NYXRoLmF0YW4yKC1oLG8pKToodGhpcy5feD0wLHRoaXMuX3k9TWF0aC5hdGFuMihzLHApKTticmVhaztjYXNlIlhaWSI6dGhpcy5fej1NYXRoLmFzaW4oLVpvKGEsLTEsMSkpLE1hdGguYWJzKGEpPC45OTk5OTk5Pyh0aGlzLl94PU1hdGguYXRhbjIoZixjKSx0aGlzLl95PU1hdGguYXRhbjIocyxvKSk6KHRoaXMuX3g9TWF0aC5hdGFuMigtdSxwKSx0aGlzLl95PTApO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS53YXJuKCJUSFJFRS5FdWxlcjogLnNldEZyb21Sb3RhdGlvbk1hdHJpeCgpIGVuY291bnRlcmVkIGFuIHVua25vd24gb3JkZXI6ICIrcil9cmV0dXJuIHRoaXMuX29yZGVyPXIsbj09PSEwJiZ0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31zZXRGcm9tUXVhdGVybmlvbih0LHIsbil7cmV0dXJuIG11ZS5tYWtlUm90YXRpb25Gcm9tUXVhdGVybmlvbih0KSx0aGlzLnNldEZyb21Sb3RhdGlvbk1hdHJpeChtdWUscixuKX1zZXRGcm9tVmVjdG9yMyh0LHI9dGhpcy5fb3JkZXIpe3JldHVybiB0aGlzLnNldCh0LngsdC55LHQueixyKX1yZW9yZGVyKHQpe3JldHVybiBndWUuc2V0RnJvbUV1bGVyKHRoaXMpLHRoaXMuc2V0RnJvbVF1YXRlcm5pb24oZ3VlLHQpfWVxdWFscyh0KXtyZXR1cm4gdC5feD09PXRoaXMuX3gmJnQuX3k9PT10aGlzLl95JiZ0Ll96PT09dGhpcy5feiYmdC5fb3JkZXI9PT10aGlzLl9vcmRlcn1mcm9tQXJyYXkodCl7cmV0dXJuIHRoaXMuX3g9dFswXSx0aGlzLl95PXRbMV0sdGhpcy5fej10WzJdLHRbM10hPT12b2lkIDAmJih0aGlzLl9vcmRlcj10WzNdKSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc310b0FycmF5KHQ9W10scj0wKXtyZXR1cm4gdFtyXT10aGlzLl94LHRbcisxXT10aGlzLl95LHRbcisyXT10aGlzLl96LHRbciszXT10aGlzLl9vcmRlcix0fXRvVmVjdG9yMyh0KXtyZXR1cm4gdD90LnNldCh0aGlzLl94LHRoaXMuX3ksdGhpcy5feik6bmV3IGoodGhpcy5feCx0aGlzLl95LHRoaXMuX3opfV9vbkNoYW5nZSh0KXtyZXR1cm4gdGhpcy5fb25DaGFuZ2VDYWxsYmFjaz10LHRoaXN9X29uQ2hhbmdlQ2FsbGJhY2soKXt9fTt0bS5wcm90b3R5cGUuaXNFdWxlcj0hMDt0bS5EZWZhdWx0T3JkZXI9IlhZWiI7dG0uUm90YXRpb25PcmRlcnM9WyJYWVoiLCJZWlgiLCJaWFkiLCJYWlkiLCJZWFoiLCJaWVgiXTt2YXIgWDM9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLm1hc2s9MX1zZXQodCl7dGhpcy5tYXNrPSgxPDx0fDApPj4+MH1lbmFibGUodCl7dGhpcy5tYXNrfD0xPDx0fDB9ZW5hYmxlQWxsKCl7dGhpcy5tYXNrPS0xfXRvZ2dsZSh0KXt0aGlzLm1hc2tePTE8PHR8MH1kaXNhYmxlKHQpe3RoaXMubWFzayY9figxPDx0fDApfWRpc2FibGVBbGwoKXt0aGlzLm1hc2s9MH10ZXN0KHQpe3JldHVybih0aGlzLm1hc2smdC5tYXNrKSE9PTB9aXNFbmFibGVkKHQpe3JldHVybih0aGlzLm1hc2smKDE8PHR8MCkpIT09MH19LG9mcj0wLF91ZT1uZXcgaixNMz1uZXcgdmkscWQ9bmV3IE1lLF9WPW5ldyBqLExQPW5ldyBqLGFmcj1uZXcgaixzZnI9bmV3IHZpLHl1ZT1uZXcgaigxLDAsMCksdnVlPW5ldyBqKDAsMSwwKSx4dWU9bmV3IGooMCwwLDEpLGxmcj17dHlwZToiYWRkZWQifSxidWU9e3R5cGU6InJlbW92ZWQifSxvcj1jbGFzcyBleHRlbmRzIFVze2NvbnN0cnVjdG9yKCl7c3VwZXIoKSxPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywiaWQiLHt2YWx1ZTpvZnIrK30pLHRoaXMudXVpZD1ObCgpLHRoaXMubmFtZT0iIix0aGlzLnR5cGU9Ik9iamVjdDNEIix0aGlzLnBhcmVudD1udWxsLHRoaXMuY2hpbGRyZW49W10sdGhpcy51cD1vci5EZWZhdWx0VXAuY2xvbmUoKTtsZXQgdD1uZXcgaixyPW5ldyB0bSxuPW5ldyB2aSxpPW5ldyBqKDEsMSwxKTtmdW5jdGlvbiBvKCl7bi5zZXRGcm9tRXVsZXIociwhMSl9ZnVuY3Rpb24gYSgpe3Iuc2V0RnJvbVF1YXRlcm5pb24obix2b2lkIDAsITEpfXIuX29uQ2hhbmdlKG8pLG4uX29uQ2hhbmdlKGEpLE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMse3Bvc2l0aW9uOntjb25maWd1cmFibGU6ITAsZW51bWVyYWJsZTohMCx2YWx1ZTp0fSxyb3RhdGlvbjp7Y29uZmlndXJhYmxlOiEwLGVudW1lcmFibGU6ITAsdmFsdWU6cn0scXVhdGVybmlvbjp7Y29uZmlndXJhYmxlOiEwLGVudW1lcmFibGU6ITAsdmFsdWU6bn0sc2NhbGU6e2NvbmZpZ3VyYWJsZTohMCxlbnVtZXJhYmxlOiEwLHZhbHVlOml9LG1vZGVsVmlld01hdHJpeDp7dmFsdWU6bmV3IE1lfSxub3JtYWxNYXRyaXg6e3ZhbHVlOm5ldyBraX19KSx0aGlzLm1hdHJpeD1uZXcgTWUsdGhpcy5tYXRyaXhXb3JsZD1uZXcgTWUsdGhpcy5tYXRyaXhBdXRvVXBkYXRlPW9yLkRlZmF1bHRNYXRyaXhBdXRvVXBkYXRlLHRoaXMubWF0cml4V29ybGROZWVkc1VwZGF0ZT0hMSx0aGlzLmxheWVycz1uZXcgWDMsdGhpcy52aXNpYmxlPSEwLHRoaXMuY2FzdFNoYWRvdz0hMSx0aGlzLnJlY2VpdmVTaGFkb3c9ITEsdGhpcy5mcnVzdHVtQ3VsbGVkPSEwLHRoaXMucmVuZGVyT3JkZXI9MCx0aGlzLmFuaW1hdGlvbnM9W10sdGhpcy51c2VyRGF0YT17fX1vbkJlZm9yZVJlbmRlcigpe31vbkFmdGVyUmVuZGVyKCl7fWFwcGx5TWF0cml4NCh0KXt0aGlzLm1hdHJpeEF1dG9VcGRhdGUmJnRoaXMudXBkYXRlTWF0cml4KCksdGhpcy5tYXRyaXgucHJlbXVsdGlwbHkodCksdGhpcy5tYXRyaXguZGVjb21wb3NlKHRoaXMucG9zaXRpb24sdGhpcy5xdWF0ZXJuaW9uLHRoaXMuc2NhbGUpfWFwcGx5UXVhdGVybmlvbih0KXtyZXR1cm4gdGhpcy5xdWF0ZXJuaW9uLnByZW11bHRpcGx5KHQpLHRoaXN9c2V0Um90YXRpb25Gcm9tQXhpc0FuZ2xlKHQscil7dGhpcy5xdWF0ZXJuaW9uLnNldEZyb21BeGlzQW5nbGUodCxyKX1zZXRSb3RhdGlvbkZyb21FdWxlcih0KXt0aGlzLnF1YXRlcm5pb24uc2V0RnJvbUV1bGVyKHQsITApfXNldFJvdGF0aW9uRnJvbU1hdHJpeCh0KXt0aGlzLnF1YXRlcm5pb24uc2V0RnJvbVJvdGF0aW9uTWF0cml4KHQpfXNldFJvdGF0aW9uRnJvbVF1YXRlcm5pb24odCl7dGhpcy5xdWF0ZXJuaW9uLmNvcHkodCl9cm90YXRlT25BeGlzKHQscil7cmV0dXJuIE0zLnNldEZyb21BeGlzQW5nbGUodCxyKSx0aGlzLnF1YXRlcm5pb24ubXVsdGlwbHkoTTMpLHRoaXN9cm90YXRlT25Xb3JsZEF4aXModCxyKXtyZXR1cm4gTTMuc2V0RnJvbUF4aXNBbmdsZSh0LHIpLHRoaXMucXVhdGVybmlvbi5wcmVtdWx0aXBseShNMyksdGhpc31yb3RhdGVYKHQpe3JldHVybiB0aGlzLnJvdGF0ZU9uQXhpcyh5dWUsdCl9cm90YXRlWSh0KXtyZXR1cm4gdGhpcy5yb3RhdGVPbkF4aXModnVlLHQpfXJvdGF0ZVoodCl7cmV0dXJuIHRoaXMucm90YXRlT25BeGlzKHh1ZSx0KX10cmFuc2xhdGVPbkF4aXModCxyKXtyZXR1cm4gX3VlLmNvcHkodCkuYXBwbHlRdWF0ZXJuaW9uKHRoaXMucXVhdGVybmlvbiksdGhpcy5wb3NpdGlvbi5hZGQoX3VlLm11bHRpcGx5U2NhbGFyKHIpKSx0aGlzfXRyYW5zbGF0ZVgodCl7cmV0dXJuIHRoaXMudHJhbnNsYXRlT25BeGlzKHl1ZSx0KX10cmFuc2xhdGVZKHQpe3JldHVybiB0aGlzLnRyYW5zbGF0ZU9uQXhpcyh2dWUsdCl9dHJhbnNsYXRlWih0KXtyZXR1cm4gdGhpcy50cmFuc2xhdGVPbkF4aXMoeHVlLHQpfWxvY2FsVG9Xb3JsZCh0KXtyZXR1cm4gdC5hcHBseU1hdHJpeDQodGhpcy5tYXRyaXhXb3JsZCl9d29ybGRUb0xvY2FsKHQpe3JldHVybiB0LmFwcGx5TWF0cml4NChxZC5jb3B5KHRoaXMubWF0cml4V29ybGQpLmludmVydCgpKX1sb29rQXQodCxyLG4pe3QuaXNWZWN0b3IzP19WLmNvcHkodCk6X1Yuc2V0KHQscixuKTtsZXQgaT10aGlzLnBhcmVudDt0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKSxMUC5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5tYXRyaXhXb3JsZCksdGhpcy5pc0NhbWVyYXx8dGhpcy5pc0xpZ2h0P3FkLmxvb2tBdChMUCxfVix0aGlzLnVwKTpxZC5sb29rQXQoX1YsTFAsdGhpcy51cCksdGhpcy5xdWF0ZXJuaW9uLnNldEZyb21Sb3RhdGlvbk1hdHJpeChxZCksaSYmKHFkLmV4dHJhY3RSb3RhdGlvbihpLm1hdHJpeFdvcmxkKSxNMy5zZXRGcm9tUm90YXRpb25NYXRyaXgocWQpLHRoaXMucXVhdGVybmlvbi5wcmVtdWx0aXBseShNMy5pbnZlcnQoKSkpfWFkZCh0KXtpZihhcmd1bWVudHMubGVuZ3RoPjEpe2ZvcihsZXQgcj0wO3I8YXJndW1lbnRzLmxlbmd0aDtyKyspdGhpcy5hZGQoYXJndW1lbnRzW3JdKTtyZXR1cm4gdGhpc31yZXR1cm4gdD09PXRoaXM/KGNvbnNvbGUuZXJyb3IoIlRIUkVFLk9iamVjdDNELmFkZDogb2JqZWN0IGNhbid0IGJlIGFkZGVkIGFzIGEgY2hpbGQgb2YgaXRzZWxmLiIsdCksdGhpcyk6KHQmJnQuaXNPYmplY3QzRD8odC5wYXJlbnQhPT1udWxsJiZ0LnBhcmVudC5yZW1vdmUodCksdC5wYXJlbnQ9dGhpcyx0aGlzLmNoaWxkcmVuLnB1c2godCksdC5kaXNwYXRjaEV2ZW50KGxmcikpOmNvbnNvbGUuZXJyb3IoIlRIUkVFLk9iamVjdDNELmFkZDogb2JqZWN0IG5vdCBhbiBpbnN0YW5jZSBvZiBUSFJFRS5PYmplY3QzRC4iLHQpLHRoaXMpfXJlbW92ZSh0KXtpZihhcmd1bWVudHMubGVuZ3RoPjEpe2ZvcihsZXQgbj0wO248YXJndW1lbnRzLmxlbmd0aDtuKyspdGhpcy5yZW1vdmUoYXJndW1lbnRzW25dKTtyZXR1cm4gdGhpc31sZXQgcj10aGlzLmNoaWxkcmVuLmluZGV4T2YodCk7cmV0dXJuIHIhPT0tMSYmKHQucGFyZW50PW51bGwsdGhpcy5jaGlsZHJlbi5zcGxpY2UociwxKSx0LmRpc3BhdGNoRXZlbnQoYnVlKSksdGhpc31yZW1vdmVGcm9tUGFyZW50KCl7bGV0IHQ9dGhpcy5wYXJlbnQ7cmV0dXJuIHQhPT1udWxsJiZ0LnJlbW92ZSh0aGlzKSx0aGlzfWNsZWFyKCl7Zm9yKGxldCB0PTA7dDx0aGlzLmNoaWxkcmVuLmxlbmd0aDt0Kyspe2xldCByPXRoaXMuY2hpbGRyZW5bdF07ci5wYXJlbnQ9bnVsbCxyLmRpc3BhdGNoRXZlbnQoYnVlKX1yZXR1cm4gdGhpcy5jaGlsZHJlbi5sZW5ndGg9MCx0aGlzfWF0dGFjaCh0KXtyZXR1cm4gdGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSkscWQuY29weSh0aGlzLm1hdHJpeFdvcmxkKS5pbnZlcnQoKSx0LnBhcmVudCE9PW51bGwmJih0LnBhcmVudC51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSkscWQubXVsdGlwbHkodC5wYXJlbnQubWF0cml4V29ybGQpKSx0LmFwcGx5TWF0cml4NChxZCksdGhpcy5hZGQodCksdC51cGRhdGVXb3JsZE1hdHJpeCghMSwhMCksdGhpc31nZXRPYmplY3RCeUlkKHQpe3JldHVybiB0aGlzLmdldE9iamVjdEJ5UHJvcGVydHkoImlkIix0KX1nZXRPYmplY3RCeU5hbWUodCl7cmV0dXJuIHRoaXMuZ2V0T2JqZWN0QnlQcm9wZXJ0eSgibmFtZSIsdCl9Z2V0T2JqZWN0QnlQcm9wZXJ0eSh0LHIpe2lmKHRoaXNbdF09PT1yKXJldHVybiB0aGlzO2ZvcihsZXQgbj0wLGk9dGhpcy5jaGlsZHJlbi5sZW5ndGg7bjxpO24rKyl7bGV0IGE9dGhpcy5jaGlsZHJlbltuXS5nZXRPYmplY3RCeVByb3BlcnR5KHQscik7aWYoYSE9PXZvaWQgMClyZXR1cm4gYX19Z2V0V29ybGRQb3NpdGlvbih0KXtyZXR1cm4gdGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSksdC5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5tYXRyaXhXb3JsZCl9Z2V0V29ybGRRdWF0ZXJuaW9uKHQpe3JldHVybiB0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKSx0aGlzLm1hdHJpeFdvcmxkLmRlY29tcG9zZShMUCx0LGFmciksdH1nZXRXb3JsZFNjYWxlKHQpe3JldHVybiB0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKSx0aGlzLm1hdHJpeFdvcmxkLmRlY29tcG9zZShMUCxzZnIsdCksdH1nZXRXb3JsZERpcmVjdGlvbih0KXt0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKTtsZXQgcj10aGlzLm1hdHJpeFdvcmxkLmVsZW1lbnRzO3JldHVybiB0LnNldChyWzhdLHJbOV0sclsxMF0pLm5vcm1hbGl6ZSgpfXJheWNhc3QoKXt9dHJhdmVyc2UodCl7dCh0aGlzKTtsZXQgcj10aGlzLmNoaWxkcmVuO2ZvcihsZXQgbj0wLGk9ci5sZW5ndGg7bjxpO24rKylyW25dLnRyYXZlcnNlKHQpfXRyYXZlcnNlVmlzaWJsZSh0KXtpZih0aGlzLnZpc2libGU9PT0hMSlyZXR1cm47dCh0aGlzKTtsZXQgcj10aGlzLmNoaWxkcmVuO2ZvcihsZXQgbj0wLGk9ci5sZW5ndGg7bjxpO24rKylyW25dLnRyYXZlcnNlVmlzaWJsZSh0KX10cmF2ZXJzZUFuY2VzdG9ycyh0KXtsZXQgcj10aGlzLnBhcmVudDtyIT09bnVsbCYmKHQociksci50cmF2ZXJzZUFuY2VzdG9ycyh0KSl9dXBkYXRlTWF0cml4KCl7dGhpcy5tYXRyaXguY29tcG9zZSh0aGlzLnBvc2l0aW9uLHRoaXMucXVhdGVybmlvbix0aGlzLnNjYWxlKSx0aGlzLm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9ITB9dXBkYXRlTWF0cml4V29ybGQodCl7dGhpcy5tYXRyaXhBdXRvVXBkYXRlJiZ0aGlzLnVwZGF0ZU1hdHJpeCgpLCh0aGlzLm1hdHJpeFdvcmxkTmVlZHNVcGRhdGV8fHQpJiYodGhpcy5wYXJlbnQ9PT1udWxsP3RoaXMubWF0cml4V29ybGQuY29weSh0aGlzLm1hdHJpeCk6dGhpcy5tYXRyaXhXb3JsZC5tdWx0aXBseU1hdHJpY2VzKHRoaXMucGFyZW50Lm1hdHJpeFdvcmxkLHRoaXMubWF0cml4KSx0aGlzLm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9ITEsdD0hMCk7bGV0IHI9dGhpcy5jaGlsZHJlbjtmb3IobGV0IG49MCxpPXIubGVuZ3RoO248aTtuKyspcltuXS51cGRhdGVNYXRyaXhXb3JsZCh0KX11cGRhdGVXb3JsZE1hdHJpeCh0LHIpe2xldCBuPXRoaXMucGFyZW50O2lmKHQ9PT0hMCYmbiE9PW51bGwmJm4udXBkYXRlV29ybGRNYXRyaXgoITAsITEpLHRoaXMubWF0cml4QXV0b1VwZGF0ZSYmdGhpcy51cGRhdGVNYXRyaXgoKSx0aGlzLnBhcmVudD09PW51bGw/dGhpcy5tYXRyaXhXb3JsZC5jb3B5KHRoaXMubWF0cml4KTp0aGlzLm1hdHJpeFdvcmxkLm11bHRpcGx5TWF0cmljZXModGhpcy5wYXJlbnQubWF0cml4V29ybGQsdGhpcy5tYXRyaXgpLHI9PT0hMCl7bGV0IGk9dGhpcy5jaGlsZHJlbjtmb3IobGV0IG89MCxhPWkubGVuZ3RoO288YTtvKyspaVtvXS51cGRhdGVXb3JsZE1hdHJpeCghMSwhMCl9fXRvSlNPTih0KXtsZXQgcj10PT09dm9pZCAwfHx0eXBlb2YgdD09InN0cmluZyIsbj17fTtyJiYodD17Z2VvbWV0cmllczp7fSxtYXRlcmlhbHM6e30sdGV4dHVyZXM6e30saW1hZ2VzOnt9LHNoYXBlczp7fSxza2VsZXRvbnM6e30sYW5pbWF0aW9uczp7fX0sbi5tZXRhZGF0YT17dmVyc2lvbjo0LjUsdHlwZToiT2JqZWN0IixnZW5lcmF0b3I6Ik9iamVjdDNELnRvSlNPTiJ9KTtsZXQgaT17fTtpLnV1aWQ9dGhpcy51dWlkLGkudHlwZT10aGlzLnR5cGUsdGhpcy5uYW1lIT09IiImJihpLm5hbWU9dGhpcy5uYW1lKSx0aGlzLmNhc3RTaGFkb3c9PT0hMCYmKGkuY2FzdFNoYWRvdz0hMCksdGhpcy5yZWNlaXZlU2hhZG93PT09ITAmJihpLnJlY2VpdmVTaGFkb3c9ITApLHRoaXMudmlzaWJsZT09PSExJiYoaS52aXNpYmxlPSExKSx0aGlzLmZydXN0dW1DdWxsZWQ9PT0hMSYmKGkuZnJ1c3R1bUN1bGxlZD0hMSksdGhpcy5yZW5kZXJPcmRlciE9PTAmJihpLnJlbmRlck9yZGVyPXRoaXMucmVuZGVyT3JkZXIpLEpTT04uc3RyaW5naWZ5KHRoaXMudXNlckRhdGEpIT09Int9IiYmKGkudXNlckRhdGE9dGhpcy51c2VyRGF0YSksaS5sYXllcnM9dGhpcy5sYXllcnMubWFzayxpLm1hdHJpeD10aGlzLm1hdHJpeC50b0FycmF5KCksdGhpcy5tYXRyaXhBdXRvVXBkYXRlPT09ITEmJihpLm1hdHJpeEF1dG9VcGRhdGU9ITEpLHRoaXMuaXNJbnN0YW5jZWRNZXNoJiYoaS50eXBlPSJJbnN0YW5jZWRNZXNoIixpLmNvdW50PXRoaXMuY291bnQsaS5pbnN0YW5jZU1hdHJpeD10aGlzLmluc3RhbmNlTWF0cml4LnRvSlNPTigpLHRoaXMuaW5zdGFuY2VDb2xvciE9PW51bGwmJihpLmluc3RhbmNlQ29sb3I9dGhpcy5pbnN0YW5jZUNvbG9yLnRvSlNPTigpKSk7ZnVuY3Rpb24gbyhzLGwpe3JldHVybiBzW2wudXVpZF09PT12b2lkIDAmJihzW2wudXVpZF09bC50b0pTT04odCkpLGwudXVpZH1pZih0aGlzLmlzU2NlbmUpdGhpcy5iYWNrZ3JvdW5kJiYodGhpcy5iYWNrZ3JvdW5kLmlzQ29sb3I/aS5iYWNrZ3JvdW5kPXRoaXMuYmFja2dyb3VuZC50b0pTT04oKTp0aGlzLmJhY2tncm91bmQuaXNUZXh0dXJlJiYoaS5iYWNrZ3JvdW5kPXRoaXMuYmFja2dyb3VuZC50b0pTT04odCkudXVpZCkpLHRoaXMuZW52aXJvbm1lbnQmJnRoaXMuZW52aXJvbm1lbnQuaXNUZXh0dXJlJiYoaS5lbnZpcm9ubWVudD10aGlzLmVudmlyb25tZW50LnRvSlNPTih0KS51dWlkKTtlbHNlIGlmKHRoaXMuaXNNZXNofHx0aGlzLmlzTGluZXx8dGhpcy5pc1BvaW50cyl7aS5nZW9tZXRyeT1vKHQuZ2VvbWV0cmllcyx0aGlzLmdlb21ldHJ5KTtsZXQgcz10aGlzLmdlb21ldHJ5LnBhcmFtZXRlcnM7aWYocyE9PXZvaWQgMCYmcy5zaGFwZXMhPT12b2lkIDApe2xldCBsPXMuc2hhcGVzO2lmKEFycmF5LmlzQXJyYXkobCkpZm9yKGxldCBjPTAsdT1sLmxlbmd0aDtjPHU7YysrKXtsZXQgaD1sW2NdO28odC5zaGFwZXMsaCl9ZWxzZSBvKHQuc2hhcGVzLGwpfX1pZih0aGlzLmlzU2tpbm5lZE1lc2gmJihpLmJpbmRNb2RlPXRoaXMuYmluZE1vZGUsaS5iaW5kTWF0cml4PXRoaXMuYmluZE1hdHJpeC50b0FycmF5KCksdGhpcy5za2VsZXRvbiE9PXZvaWQgMCYmKG8odC5za2VsZXRvbnMsdGhpcy5za2VsZXRvbiksaS5za2VsZXRvbj10aGlzLnNrZWxldG9uLnV1aWQpKSx0aGlzLm1hdGVyaWFsIT09dm9pZCAwKWlmKEFycmF5LmlzQXJyYXkodGhpcy5tYXRlcmlhbCkpe2xldCBzPVtdO2ZvcihsZXQgbD0wLGM9dGhpcy5tYXRlcmlhbC5sZW5ndGg7bDxjO2wrKylzLnB1c2gobyh0Lm1hdGVyaWFscyx0aGlzLm1hdGVyaWFsW2xdKSk7aS5tYXRlcmlhbD1zfWVsc2UgaS5tYXRlcmlhbD1vKHQubWF0ZXJpYWxzLHRoaXMubWF0ZXJpYWwpO2lmKHRoaXMuY2hpbGRyZW4ubGVuZ3RoPjApe2kuY2hpbGRyZW49W107Zm9yKGxldCBzPTA7czx0aGlzLmNoaWxkcmVuLmxlbmd0aDtzKyspaS5jaGlsZHJlbi5wdXNoKHRoaXMuY2hpbGRyZW5bc10udG9KU09OKHQpLm9iamVjdCl9aWYodGhpcy5hbmltYXRpb25zLmxlbmd0aD4wKXtpLmFuaW1hdGlvbnM9W107Zm9yKGxldCBzPTA7czx0aGlzLmFuaW1hdGlvbnMubGVuZ3RoO3MrKyl7bGV0IGw9dGhpcy5hbmltYXRpb25zW3NdO2kuYW5pbWF0aW9ucy5wdXNoKG8odC5hbmltYXRpb25zLGwpKX19aWYocil7bGV0IHM9YSh0Lmdlb21ldHJpZXMpLGw9YSh0Lm1hdGVyaWFscyksYz1hKHQudGV4dHVyZXMpLHU9YSh0LmltYWdlcyksaD1hKHQuc2hhcGVzKSxmPWEodC5za2VsZXRvbnMpLHA9YSh0LmFuaW1hdGlvbnMpO3MubGVuZ3RoPjAmJihuLmdlb21ldHJpZXM9cyksbC5sZW5ndGg+MCYmKG4ubWF0ZXJpYWxzPWwpLGMubGVuZ3RoPjAmJihuLnRleHR1cmVzPWMpLHUubGVuZ3RoPjAmJihuLmltYWdlcz11KSxoLmxlbmd0aD4wJiYobi5zaGFwZXM9aCksZi5sZW5ndGg+MCYmKG4uc2tlbGV0b25zPWYpLHAubGVuZ3RoPjAmJihuLmFuaW1hdGlvbnM9cCl9cmV0dXJuIG4ub2JqZWN0PWksbjtmdW5jdGlvbiBhKHMpe2xldCBsPVtdO2ZvcihsZXQgYyBpbiBzKXtsZXQgdT1zW2NdO2RlbGV0ZSB1Lm1ldGFkYXRhLGwucHVzaCh1KX1yZXR1cm4gbH19Y2xvbmUodCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzLHQpfWNvcHkodCxyPSEwKXtpZih0aGlzLm5hbWU9dC5uYW1lLHRoaXMudXAuY29weSh0LnVwKSx0aGlzLnBvc2l0aW9uLmNvcHkodC5wb3NpdGlvbiksdGhpcy5yb3RhdGlvbi5vcmRlcj10LnJvdGF0aW9uLm9yZGVyLHRoaXMucXVhdGVybmlvbi5jb3B5KHQucXVhdGVybmlvbiksdGhpcy5zY2FsZS5jb3B5KHQuc2NhbGUpLHRoaXMubWF0cml4LmNvcHkodC5tYXRyaXgpLHRoaXMubWF0cml4V29ybGQuY29weSh0Lm1hdHJpeFdvcmxkKSx0aGlzLm1hdHJpeEF1dG9VcGRhdGU9dC5tYXRyaXhBdXRvVXBkYXRlLHRoaXMubWF0cml4V29ybGROZWVkc1VwZGF0ZT10Lm1hdHJpeFdvcmxkTmVlZHNVcGRhdGUsdGhpcy5sYXllcnMubWFzaz10LmxheWVycy5tYXNrLHRoaXMudmlzaWJsZT10LnZpc2libGUsdGhpcy5jYXN0U2hhZG93PXQuY2FzdFNoYWRvdyx0aGlzLnJlY2VpdmVTaGFkb3c9dC5yZWNlaXZlU2hhZG93LHRoaXMuZnJ1c3R1bUN1bGxlZD10LmZydXN0dW1DdWxsZWQsdGhpcy5yZW5kZXJPcmRlcj10LnJlbmRlck9yZGVyLHRoaXMudXNlckRhdGE9SlNPTi5wYXJzZShKU09OLnN0cmluZ2lmeSh0LnVzZXJEYXRhKSkscj09PSEwKWZvcihsZXQgbj0wO248dC5jaGlsZHJlbi5sZW5ndGg7bisrKXtsZXQgaT10LmNoaWxkcmVuW25dO3RoaXMuYWRkKGkuY2xvbmUoKSl9cmV0dXJuIHRoaXN9fTtvci5EZWZhdWx0VXA9bmV3IGooMCwxLDApO29yLkRlZmF1bHRNYXRyaXhBdXRvVXBkYXRlPSEwO29yLnByb3RvdHlwZS5pc09iamVjdDNEPSEwO3ZhciBvaD1uZXcgaixHZD1uZXcgaixldXQ9bmV3IGosV2Q9bmV3IGosRTM9bmV3IGosVDM9bmV3IGosd3VlPW5ldyBqLHJ1dD1uZXcgaixudXQ9bmV3IGosaXV0PW5ldyBqLGFpPWNsYXNze2NvbnN0cnVjdG9yKHQ9bmV3IGoscj1uZXcgaixuPW5ldyBqKXt0aGlzLmE9dCx0aGlzLmI9cix0aGlzLmM9bn1zdGF0aWMgZ2V0Tm9ybWFsKHQscixuLGkpe2kuc3ViVmVjdG9ycyhuLHIpLG9oLnN1YlZlY3RvcnModCxyKSxpLmNyb3NzKG9oKTtsZXQgbz1pLmxlbmd0aFNxKCk7cmV0dXJuIG8+MD9pLm11bHRpcGx5U2NhbGFyKDEvTWF0aC5zcXJ0KG8pKTppLnNldCgwLDAsMCl9c3RhdGljIGdldEJhcnljb29yZCh0LHIsbixpLG8pe29oLnN1YlZlY3RvcnMoaSxyKSxHZC5zdWJWZWN0b3JzKG4sciksZXV0LnN1YlZlY3RvcnModCxyKTtsZXQgYT1vaC5kb3Qob2gpLHM9b2guZG90KEdkKSxsPW9oLmRvdChldXQpLGM9R2QuZG90KEdkKSx1PUdkLmRvdChldXQpLGg9YSpjLXMqcztpZihoPT09MClyZXR1cm4gby5zZXQoLTIsLTEsLTEpO2xldCBmPTEvaCxwPShjKmwtcyp1KSpmLGQ9KGEqdS1zKmwpKmY7cmV0dXJuIG8uc2V0KDEtcC1kLGQscCl9c3RhdGljIGNvbnRhaW5zUG9pbnQodCxyLG4saSl7cmV0dXJuIHRoaXMuZ2V0QmFyeWNvb3JkKHQscixuLGksV2QpLFdkLng+PTAmJldkLnk+PTAmJldkLngrV2QueTw9MX1zdGF0aWMgZ2V0VVYodCxyLG4saSxvLGEscyxsKXtyZXR1cm4gdGhpcy5nZXRCYXJ5Y29vcmQodCxyLG4saSxXZCksbC5zZXQoMCwwKSxsLmFkZFNjYWxlZFZlY3RvcihvLFdkLngpLGwuYWRkU2NhbGVkVmVjdG9yKGEsV2QueSksbC5hZGRTY2FsZWRWZWN0b3IocyxXZC56KSxsfXN0YXRpYyBpc0Zyb250RmFjaW5nKHQscixuLGkpe3JldHVybiBvaC5zdWJWZWN0b3JzKG4sciksR2Quc3ViVmVjdG9ycyh0LHIpLG9oLmNyb3NzKEdkKS5kb3QoaSk8MH1zZXQodCxyLG4pe3JldHVybiB0aGlzLmEuY29weSh0KSx0aGlzLmIuY29weShyKSx0aGlzLmMuY29weShuKSx0aGlzfXNldEZyb21Qb2ludHNBbmRJbmRpY2VzKHQscixuLGkpe3JldHVybiB0aGlzLmEuY29weSh0W3JdKSx0aGlzLmIuY29weSh0W25dKSx0aGlzLmMuY29weSh0W2ldKSx0aGlzfXNldEZyb21BdHRyaWJ1dGVBbmRJbmRpY2VzKHQscixuLGkpe3JldHVybiB0aGlzLmEuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LHIpLHRoaXMuYi5mcm9tQnVmZmVyQXR0cmlidXRlKHQsbiksdGhpcy5jLmZyb21CdWZmZXJBdHRyaWJ1dGUodCxpKSx0aGlzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX1jb3B5KHQpe3JldHVybiB0aGlzLmEuY29weSh0LmEpLHRoaXMuYi5jb3B5KHQuYiksdGhpcy5jLmNvcHkodC5jKSx0aGlzfWdldEFyZWEoKXtyZXR1cm4gb2guc3ViVmVjdG9ycyh0aGlzLmMsdGhpcy5iKSxHZC5zdWJWZWN0b3JzKHRoaXMuYSx0aGlzLmIpLG9oLmNyb3NzKEdkKS5sZW5ndGgoKSouNX1nZXRNaWRwb2ludCh0KXtyZXR1cm4gdC5hZGRWZWN0b3JzKHRoaXMuYSx0aGlzLmIpLmFkZCh0aGlzLmMpLm11bHRpcGx5U2NhbGFyKDEvMyl9Z2V0Tm9ybWFsKHQpe3JldHVybiBhaS5nZXROb3JtYWwodGhpcy5hLHRoaXMuYix0aGlzLmMsdCl9Z2V0UGxhbmUodCl7cmV0dXJuIHQuc2V0RnJvbUNvcGxhbmFyUG9pbnRzKHRoaXMuYSx0aGlzLmIsdGhpcy5jKX1nZXRCYXJ5Y29vcmQodCxyKXtyZXR1cm4gYWkuZ2V0QmFyeWNvb3JkKHQsdGhpcy5hLHRoaXMuYix0aGlzLmMscil9Z2V0VVYodCxyLG4saSxvKXtyZXR1cm4gYWkuZ2V0VVYodCx0aGlzLmEsdGhpcy5iLHRoaXMuYyxyLG4saSxvKX1jb250YWluc1BvaW50KHQpe3JldHVybiBhaS5jb250YWluc1BvaW50KHQsdGhpcy5hLHRoaXMuYix0aGlzLmMpfWlzRnJvbnRGYWNpbmcodCl7cmV0dXJuIGFpLmlzRnJvbnRGYWNpbmcodGhpcy5hLHRoaXMuYix0aGlzLmMsdCl9aW50ZXJzZWN0c0JveCh0KXtyZXR1cm4gdC5pbnRlcnNlY3RzVHJpYW5nbGUodGhpcyl9Y2xvc2VzdFBvaW50VG9Qb2ludCh0LHIpe2xldCBuPXRoaXMuYSxpPXRoaXMuYixvPXRoaXMuYyxhLHM7RTMuc3ViVmVjdG9ycyhpLG4pLFQzLnN1YlZlY3RvcnMobyxuKSxydXQuc3ViVmVjdG9ycyh0LG4pO2xldCBsPUUzLmRvdChydXQpLGM9VDMuZG90KHJ1dCk7aWYobDw9MCYmYzw9MClyZXR1cm4gci5jb3B5KG4pO251dC5zdWJWZWN0b3JzKHQsaSk7bGV0IHU9RTMuZG90KG51dCksaD1UMy5kb3QobnV0KTtpZih1Pj0wJiZoPD11KXJldHVybiByLmNvcHkoaSk7bGV0IGY9bCpoLXUqYztpZihmPD0wJiZsPj0wJiZ1PD0wKXJldHVybiBhPWwvKGwtdSksci5jb3B5KG4pLmFkZFNjYWxlZFZlY3RvcihFMyxhKTtpdXQuc3ViVmVjdG9ycyh0LG8pO2xldCBwPUUzLmRvdChpdXQpLGQ9VDMuZG90KGl1dCk7aWYoZD49MCYmcDw9ZClyZXR1cm4gci5jb3B5KG8pO2xldCBnPXAqYy1sKmQ7aWYoZzw9MCYmYz49MCYmZDw9MClyZXR1cm4gcz1jLyhjLWQpLHIuY29weShuKS5hZGRTY2FsZWRWZWN0b3IoVDMscyk7bGV0IF89dSpkLXAqaDtpZihfPD0wJiZoLXU+PTAmJnAtZD49MClyZXR1cm4gd3VlLnN1YlZlY3RvcnMobyxpKSxzPShoLXUpLyhoLXUrKHAtZCkpLHIuY29weShpKS5hZGRTY2FsZWRWZWN0b3Iod3VlLHMpO2xldCB5PTEvKF8rZytmKTtyZXR1cm4gYT1nKnkscz1mKnksci5jb3B5KG4pLmFkZFNjYWxlZFZlY3RvcihFMyxhKS5hZGRTY2FsZWRWZWN0b3IoVDMscyl9ZXF1YWxzKHQpe3JldHVybiB0LmEuZXF1YWxzKHRoaXMuYSkmJnQuYi5lcXVhbHModGhpcy5iKSYmdC5jLmVxdWFscyh0aGlzLmMpfX0sY2ZyPTAscWk9Y2xhc3MgZXh0ZW5kcyBVc3tjb25zdHJ1Y3Rvcigpe3N1cGVyKCksT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsImlkIix7dmFsdWU6Y2ZyKyt9KSx0aGlzLnV1aWQ9TmwoKSx0aGlzLm5hbWU9IiIsdGhpcy50eXBlPSJNYXRlcmlhbCIsdGhpcy5mb2c9ITAsdGhpcy5ibGVuZGluZz1WMyx0aGlzLnNpZGU9SXYsdGhpcy52ZXJ0ZXhDb2xvcnM9ITEsdGhpcy5vcGFjaXR5PTEsdGhpcy50cmFuc3BhcmVudD0hMSx0aGlzLmJsZW5kU3JjPUlodCx0aGlzLmJsZW5kRHN0PUxodCx0aGlzLmJsZW5kRXF1YXRpb249TXYsdGhpcy5ibGVuZFNyY0FscGhhPW51bGwsdGhpcy5ibGVuZERzdEFscGhhPW51bGwsdGhpcy5ibGVuZEVxdWF0aW9uQWxwaGE9bnVsbCx0aGlzLmRlcHRoRnVuYz1uVSx0aGlzLmRlcHRoVGVzdD0hMCx0aGlzLmRlcHRoV3JpdGU9ITAsdGhpcy5zdGVuY2lsV3JpdGVNYXNrPTI1NSx0aGlzLnN0ZW5jaWxGdW5jPUxmZSx0aGlzLnN0ZW5jaWxSZWY9MCx0aGlzLnN0ZW5jaWxGdW5jTWFzaz0yNTUsdGhpcy5zdGVuY2lsRmFpbD1yVSx0aGlzLnN0ZW5jaWxaRmFpbD1yVSx0aGlzLnN0ZW5jaWxaUGFzcz1yVSx0aGlzLnN0ZW5jaWxXcml0ZT0hMSx0aGlzLmNsaXBwaW5nUGxhbmVzPW51bGwsdGhpcy5jbGlwSW50ZXJzZWN0aW9uPSExLHRoaXMuY2xpcFNoYWRvd3M9ITEsdGhpcy5zaGFkb3dTaWRlPW51bGwsdGhpcy5jb2xvcldyaXRlPSEwLHRoaXMuYWxwaGFXcml0ZT0hMCx0aGlzLnByZWNpc2lvbj1udWxsLHRoaXMucG9seWdvbk9mZnNldD0hMSx0aGlzLnBvbHlnb25PZmZzZXRGYWN0b3I9MCx0aGlzLnBvbHlnb25PZmZzZXRVbml0cz0wLHRoaXMuZGl0aGVyaW5nPSExLHRoaXMuYWxwaGFUb0NvdmVyYWdlPSExLHRoaXMucHJlbXVsdGlwbGllZEFscGhhPSExLHRoaXMudmlzaWJsZT0hMCx0aGlzLnRvbmVNYXBwZWQ9ITAsdGhpcy51c2VyRGF0YT17fSx0aGlzLnZlcnNpb249MCx0aGlzLl9hbHBoYVRlc3Q9MH1nZXQgYWxwaGFUZXN0KCl7cmV0dXJuIHRoaXMuX2FscGhhVGVzdH1zZXQgYWxwaGFUZXN0KHQpe3RoaXMuX2FscGhhVGVzdD4wIT10PjAmJnRoaXMudmVyc2lvbisrLHRoaXMuX2FscGhhVGVzdD10fW9uQnVpbGQoKXt9b25CZWZvcmVSZW5kZXIoKXt9b25CZWZvcmVDb21waWxlKCl7fWN1c3RvbVByb2dyYW1DYWNoZUtleSgpe3JldHVybiB0aGlzLm9uQmVmb3JlQ29tcGlsZS50b1N0cmluZygpfXNldFZhbHVlcyh0KXtpZih0IT09dm9pZCAwKWZvcihsZXQgciBpbiB0KXtsZXQgbj10W3JdO2lmKG49PT12b2lkIDApe2NvbnNvbGUud2FybigiVEhSRUUuTWF0ZXJpYWw6ICciK3IrIicgcGFyYW1ldGVyIGlzIHVuZGVmaW5lZC4iKTtjb250aW51ZX1pZihyPT09InNoYWRpbmciKXtjb25zb2xlLndhcm4oIlRIUkVFLiIrdGhpcy50eXBlKyI6IC5zaGFkaW5nIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSB0aGUgYm9vbGVhbiAuZmxhdFNoYWRpbmcgaW5zdGVhZC4iKSx0aGlzLmZsYXRTaGFkaW5nPW49PT1QaHQ7Y29udGludWV9bGV0IGk9dGhpc1tyXTtpZihpPT09dm9pZCAwKXtjb25zb2xlLndhcm4oIlRIUkVFLiIrdGhpcy50eXBlKyI6ICciK3IrIicgaXMgbm90IGEgcHJvcGVydHkgb2YgdGhpcyBtYXRlcmlhbC4iKTtjb250aW51ZX1pJiZpLmlzQ29sb3I/aS5zZXQobik6aSYmaS5pc1ZlY3RvcjMmJm4mJm4uaXNWZWN0b3IzP2kuY29weShuKTp0aGlzW3JdPW59fXRvSlNPTih0KXtsZXQgcj10PT09dm9pZCAwfHx0eXBlb2YgdD09InN0cmluZyI7ciYmKHQ9e3RleHR1cmVzOnt9LGltYWdlczp7fX0pO2xldCBuPXttZXRhZGF0YTp7dmVyc2lvbjo0LjUsdHlwZToiTWF0ZXJpYWwiLGdlbmVyYXRvcjoiTWF0ZXJpYWwudG9KU09OIn19O24udXVpZD10aGlzLnV1aWQsbi50eXBlPXRoaXMudHlwZSx0aGlzLm5hbWUhPT0iIiYmKG4ubmFtZT10aGlzLm5hbWUpLHRoaXMuY29sb3ImJnRoaXMuY29sb3IuaXNDb2xvciYmKG4uY29sb3I9dGhpcy5jb2xvci5nZXRIZXgoKSksdGhpcy5yb3VnaG5lc3MhPT12b2lkIDAmJihuLnJvdWdobmVzcz10aGlzLnJvdWdobmVzcyksdGhpcy5tZXRhbG5lc3MhPT12b2lkIDAmJihuLm1ldGFsbmVzcz10aGlzLm1ldGFsbmVzcyksdGhpcy5zaGVlbiE9PXZvaWQgMCYmKG4uc2hlZW49dGhpcy5zaGVlbiksdGhpcy5zaGVlbkNvbG9yJiZ0aGlzLnNoZWVuQ29sb3IuaXNDb2xvciYmKG4uc2hlZW5Db2xvcj10aGlzLnNoZWVuQ29sb3IuZ2V0SGV4KCkpLHRoaXMuc2hlZW5Sb3VnaG5lc3MhPT12b2lkIDAmJihuLnNoZWVuUm91Z2huZXNzPXRoaXMuc2hlZW5Sb3VnaG5lc3MpLHRoaXMuZW1pc3NpdmUmJnRoaXMuZW1pc3NpdmUuaXNDb2xvciYmKG4uZW1pc3NpdmU9dGhpcy5lbWlzc2l2ZS5nZXRIZXgoKSksdGhpcy5lbWlzc2l2ZUludGVuc2l0eSYmdGhpcy5lbWlzc2l2ZUludGVuc2l0eSE9PTEmJihuLmVtaXNzaXZlSW50ZW5zaXR5PXRoaXMuZW1pc3NpdmVJbnRlbnNpdHkpLHRoaXMuc3BlY3VsYXImJnRoaXMuc3BlY3VsYXIuaXNDb2xvciYmKG4uc3BlY3VsYXI9dGhpcy5zcGVjdWxhci5nZXRIZXgoKSksdGhpcy5zcGVjdWxhckludGVuc2l0eSE9PXZvaWQgMCYmKG4uc3BlY3VsYXJJbnRlbnNpdHk9dGhpcy5zcGVjdWxhckludGVuc2l0eSksdGhpcy5zcGVjdWxhckNvbG9yJiZ0aGlzLnNwZWN1bGFyQ29sb3IuaXNDb2xvciYmKG4uc3BlY3VsYXJDb2xvcj10aGlzLnNwZWN1bGFyQ29sb3IuZ2V0SGV4KCkpLHRoaXMuc2hpbmluZXNzIT09dm9pZCAwJiYobi5zaGluaW5lc3M9dGhpcy5zaGluaW5lc3MpLHRoaXMuY2xlYXJjb2F0IT09dm9pZCAwJiYobi5jbGVhcmNvYXQ9dGhpcy5jbGVhcmNvYXQpLHRoaXMuY2xlYXJjb2F0Um91Z2huZXNzIT09dm9pZCAwJiYobi5jbGVhcmNvYXRSb3VnaG5lc3M9dGhpcy5jbGVhcmNvYXRSb3VnaG5lc3MpLHRoaXMuY2xlYXJjb2F0TWFwJiZ0aGlzLmNsZWFyY29hdE1hcC5pc1RleHR1cmUmJihuLmNsZWFyY29hdE1hcD10aGlzLmNsZWFyY29hdE1hcC50b0pTT04odCkudXVpZCksdGhpcy5jbGVhcmNvYXRSb3VnaG5lc3NNYXAmJnRoaXMuY2xlYXJjb2F0Um91Z2huZXNzTWFwLmlzVGV4dHVyZSYmKG4uY2xlYXJjb2F0Um91Z2huZXNzTWFwPXRoaXMuY2xlYXJjb2F0Um91Z2huZXNzTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLmNsZWFyY29hdE5vcm1hbE1hcCYmdGhpcy5jbGVhcmNvYXROb3JtYWxNYXAuaXNUZXh0dXJlJiYobi5jbGVhcmNvYXROb3JtYWxNYXA9dGhpcy5jbGVhcmNvYXROb3JtYWxNYXAudG9KU09OKHQpLnV1aWQsbi5jbGVhcmNvYXROb3JtYWxTY2FsZT10aGlzLmNsZWFyY29hdE5vcm1hbFNjYWxlLnRvQXJyYXkoKSksdGhpcy5tYXAmJnRoaXMubWFwLmlzVGV4dHVyZSYmKG4ubWFwPXRoaXMubWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLm1hdGNhcCYmdGhpcy5tYXRjYXAuaXNUZXh0dXJlJiYobi5tYXRjYXA9dGhpcy5tYXRjYXAudG9KU09OKHQpLnV1aWQpLHRoaXMuYWxwaGFNYXAmJnRoaXMuYWxwaGFNYXAuaXNUZXh0dXJlJiYobi5hbHBoYU1hcD10aGlzLmFscGhhTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLmxpZ2h0TWFwJiZ0aGlzLmxpZ2h0TWFwLmlzVGV4dHVyZSYmKG4ubGlnaHRNYXA9dGhpcy5saWdodE1hcC50b0pTT04odCkudXVpZCxuLmxpZ2h0TWFwSW50ZW5zaXR5PXRoaXMubGlnaHRNYXBJbnRlbnNpdHkpLHRoaXMuYW9NYXAmJnRoaXMuYW9NYXAuaXNUZXh0dXJlJiYobi5hb01hcD10aGlzLmFvTWFwLnRvSlNPTih0KS51dWlkLG4uYW9NYXBJbnRlbnNpdHk9dGhpcy5hb01hcEludGVuc2l0eSksdGhpcy5idW1wTWFwJiZ0aGlzLmJ1bXBNYXAuaXNUZXh0dXJlJiYobi5idW1wTWFwPXRoaXMuYnVtcE1hcC50b0pTT04odCkudXVpZCxuLmJ1bXBTY2FsZT10aGlzLmJ1bXBTY2FsZSksdGhpcy5ub3JtYWxNYXAmJnRoaXMubm9ybWFsTWFwLmlzVGV4dHVyZSYmKG4ubm9ybWFsTWFwPXRoaXMubm9ybWFsTWFwLnRvSlNPTih0KS51dWlkLG4ubm9ybWFsTWFwVHlwZT10aGlzLm5vcm1hbE1hcFR5cGUsbi5ub3JtYWxTY2FsZT10aGlzLm5vcm1hbFNjYWxlLnRvQXJyYXkoKSksdGhpcy5kaXNwbGFjZW1lbnRNYXAmJnRoaXMuZGlzcGxhY2VtZW50TWFwLmlzVGV4dHVyZSYmKG4uZGlzcGxhY2VtZW50TWFwPXRoaXMuZGlzcGxhY2VtZW50TWFwLnRvSlNPTih0KS51dWlkLG4uZGlzcGxhY2VtZW50U2NhbGU9dGhpcy5kaXNwbGFjZW1lbnRTY2FsZSxuLmRpc3BsYWNlbWVudEJpYXM9dGhpcy5kaXNwbGFjZW1lbnRCaWFzKSx0aGlzLnJvdWdobmVzc01hcCYmdGhpcy5yb3VnaG5lc3NNYXAuaXNUZXh0dXJlJiYobi5yb3VnaG5lc3NNYXA9dGhpcy5yb3VnaG5lc3NNYXAudG9KU09OKHQpLnV1aWQpLHRoaXMubWV0YWxuZXNzTWFwJiZ0aGlzLm1ldGFsbmVzc01hcC5pc1RleHR1cmUmJihuLm1ldGFsbmVzc01hcD10aGlzLm1ldGFsbmVzc01hcC50b0pTT04odCkudXVpZCksdGhpcy5lbWlzc2l2ZU1hcCYmdGhpcy5lbWlzc2l2ZU1hcC5pc1RleHR1cmUmJihuLmVtaXNzaXZlTWFwPXRoaXMuZW1pc3NpdmVNYXAudG9KU09OKHQpLnV1aWQpLHRoaXMuc3BlY3VsYXJNYXAmJnRoaXMuc3BlY3VsYXJNYXAuaXNUZXh0dXJlJiYobi5zcGVjdWxhck1hcD10aGlzLnNwZWN1bGFyTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLnNwZWN1bGFySW50ZW5zaXR5TWFwJiZ0aGlzLnNwZWN1bGFySW50ZW5zaXR5TWFwLmlzVGV4dHVyZSYmKG4uc3BlY3VsYXJJbnRlbnNpdHlNYXA9dGhpcy5zcGVjdWxhckludGVuc2l0eU1hcC50b0pTT04odCkudXVpZCksdGhpcy5zcGVjdWxhckNvbG9yTWFwJiZ0aGlzLnNwZWN1bGFyQ29sb3JNYXAuaXNUZXh0dXJlJiYobi5zcGVjdWxhckNvbG9yTWFwPXRoaXMuc3BlY3VsYXJDb2xvck1hcC50b0pTT04odCkudXVpZCksdGhpcy5lbnZNYXAmJnRoaXMuZW52TWFwLmlzVGV4dHVyZSYmKG4uZW52TWFwPXRoaXMuZW52TWFwLnRvSlNPTih0KS51dWlkLHRoaXMuY29tYmluZSE9PXZvaWQgMCYmKG4uY29tYmluZT10aGlzLmNvbWJpbmUpKSx0aGlzLmVudk1hcEludGVuc2l0eSE9PXZvaWQgMCYmKG4uZW52TWFwSW50ZW5zaXR5PXRoaXMuZW52TWFwSW50ZW5zaXR5KSx0aGlzLnJlZmxlY3Rpdml0eSE9PXZvaWQgMCYmKG4ucmVmbGVjdGl2aXR5PXRoaXMucmVmbGVjdGl2aXR5KSx0aGlzLnJlZnJhY3Rpb25SYXRpbyE9PXZvaWQgMCYmKG4ucmVmcmFjdGlvblJhdGlvPXRoaXMucmVmcmFjdGlvblJhdGlvKSx0aGlzLmdyYWRpZW50TWFwJiZ0aGlzLmdyYWRpZW50TWFwLmlzVGV4dHVyZSYmKG4uZ3JhZGllbnRNYXA9dGhpcy5ncmFkaWVudE1hcC50b0pTT04odCkudXVpZCksdGhpcy50cmFuc21pc3Npb24hPT12b2lkIDAmJihuLnRyYW5zbWlzc2lvbj10aGlzLnRyYW5zbWlzc2lvbiksdGhpcy50cmFuc21pc3Npb25NYXAmJnRoaXMudHJhbnNtaXNzaW9uTWFwLmlzVGV4dHVyZSYmKG4udHJhbnNtaXNzaW9uTWFwPXRoaXMudHJhbnNtaXNzaW9uTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLnRoaWNrbmVzcyE9PXZvaWQgMCYmKG4udGhpY2tuZXNzPXRoaXMudGhpY2tuZXNzKSx0aGlzLnRoaWNrbmVzc01hcCYmdGhpcy50aGlja25lc3NNYXAuaXNUZXh0dXJlJiYobi50aGlja25lc3NNYXA9dGhpcy50aGlja25lc3NNYXAudG9KU09OKHQpLnV1aWQpLHRoaXMuYXR0ZW51YXRpb25EaXN0YW5jZSE9PXZvaWQgMCYmKG4uYXR0ZW51YXRpb25EaXN0YW5jZT10aGlzLmF0dGVudWF0aW9uRGlzdGFuY2UpLHRoaXMuYXR0ZW51YXRpb25Db2xvciE9PXZvaWQgMCYmKG4uYXR0ZW51YXRpb25Db2xvcj10aGlzLmF0dGVudWF0aW9uQ29sb3IuZ2V0SGV4KCkpLHRoaXMuc2l6ZSE9PXZvaWQgMCYmKG4uc2l6ZT10aGlzLnNpemUpLHRoaXMuc2hhZG93U2lkZSE9PW51bGwmJihuLnNoYWRvd1NpZGU9dGhpcy5zaGFkb3dTaWRlKSx0aGlzLnNpemVBdHRlbnVhdGlvbiE9PXZvaWQgMCYmKG4uc2l6ZUF0dGVudWF0aW9uPXRoaXMuc2l6ZUF0dGVudWF0aW9uKSx0aGlzLmJsZW5kaW5nIT09VjMmJihuLmJsZW5kaW5nPXRoaXMuYmxlbmRpbmcpLHRoaXMuc2lkZSE9PUl2JiYobi5zaWRlPXRoaXMuc2lkZSksdGhpcy52ZXJ0ZXhDb2xvcnMmJihuLnZlcnRleENvbG9ycz0hMCksdGhpcy5vcGFjaXR5PDEmJihuLm9wYWNpdHk9dGhpcy5vcGFjaXR5KSx0aGlzLnRyYW5zcGFyZW50PT09ITAmJihuLnRyYW5zcGFyZW50PXRoaXMudHJhbnNwYXJlbnQpLG4uZGVwdGhGdW5jPXRoaXMuZGVwdGhGdW5jLG4uZGVwdGhUZXN0PXRoaXMuZGVwdGhUZXN0LG4uZGVwdGhXcml0ZT10aGlzLmRlcHRoV3JpdGUsbi5jb2xvcldyaXRlPXRoaXMuY29sb3JXcml0ZSxuLmFscGhhV3JpdGU9dGhpcy5hbHBoYVdyaXRlLG4uc3RlbmNpbFdyaXRlPXRoaXMuc3RlbmNpbFdyaXRlLG4uc3RlbmNpbFdyaXRlTWFzaz10aGlzLnN0ZW5jaWxXcml0ZU1hc2ssbi5zdGVuY2lsRnVuYz10aGlzLnN0ZW5jaWxGdW5jLG4uc3RlbmNpbFJlZj10aGlzLnN0ZW5jaWxSZWYsbi5zdGVuY2lsRnVuY01hc2s9dGhpcy5zdGVuY2lsRnVuY01hc2ssbi5zdGVuY2lsRmFpbD10aGlzLnN0ZW5jaWxGYWlsLG4uc3RlbmNpbFpGYWlsPXRoaXMuc3RlbmNpbFpGYWlsLG4uc3RlbmNpbFpQYXNzPXRoaXMuc3RlbmNpbFpQYXNzLHRoaXMucm90YXRpb24mJnRoaXMucm90YXRpb24hPT0wJiYobi5yb3RhdGlvbj10aGlzLnJvdGF0aW9uKSx0aGlzLnBvbHlnb25PZmZzZXQ9PT0hMCYmKG4ucG9seWdvbk9mZnNldD0hMCksdGhpcy5wb2x5Z29uT2Zmc2V0RmFjdG9yIT09MCYmKG4ucG9seWdvbk9mZnNldEZhY3Rvcj10aGlzLnBvbHlnb25PZmZzZXRGYWN0b3IpLHRoaXMucG9seWdvbk9mZnNldFVuaXRzIT09MCYmKG4ucG9seWdvbk9mZnNldFVuaXRzPXRoaXMucG9seWdvbk9mZnNldFVuaXRzKSx0aGlzLmxpbmV3aWR0aCYmdGhpcy5saW5ld2lkdGghPT0xJiYobi5saW5ld2lkdGg9dGhpcy5saW5ld2lkdGgpLHRoaXMuZGFzaFNpemUhPT12b2lkIDAmJihuLmRhc2hTaXplPXRoaXMuZGFzaFNpemUpLHRoaXMuZ2FwU2l6ZSE9PXZvaWQgMCYmKG4uZ2FwU2l6ZT10aGlzLmdhcFNpemUpLHRoaXMuc2NhbGUhPT12b2lkIDAmJihuLnNjYWxlPXRoaXMuc2NhbGUpLHRoaXMuZGl0aGVyaW5nPT09ITAmJihuLmRpdGhlcmluZz0hMCksdGhpcy5hbHBoYVRlc3Q+MCYmKG4uYWxwaGFUZXN0PXRoaXMuYWxwaGFUZXN0KSx0aGlzLmFscGhhVG9Db3ZlcmFnZT09PSEwJiYobi5hbHBoYVRvQ292ZXJhZ2U9dGhpcy5hbHBoYVRvQ292ZXJhZ2UpLHRoaXMucHJlbXVsdGlwbGllZEFscGhhPT09ITAmJihuLnByZW11bHRpcGxpZWRBbHBoYT10aGlzLnByZW11bHRpcGxpZWRBbHBoYSksdGhpcy53aXJlZnJhbWU9PT0hMCYmKG4ud2lyZWZyYW1lPXRoaXMud2lyZWZyYW1lKSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD4xJiYobi53aXJlZnJhbWVMaW5ld2lkdGg9dGhpcy53aXJlZnJhbWVMaW5ld2lkdGgpLHRoaXMud2lyZWZyYW1lTGluZWNhcCE9PSJyb3VuZCImJihuLndpcmVmcmFtZUxpbmVjYXA9dGhpcy53aXJlZnJhbWVMaW5lY2FwKSx0aGlzLndpcmVmcmFtZUxpbmVqb2luIT09InJvdW5kIiYmKG4ud2lyZWZyYW1lTGluZWpvaW49dGhpcy53aXJlZnJhbWVMaW5lam9pbiksdGhpcy5mbGF0U2hhZGluZz09PSEwJiYobi5mbGF0U2hhZGluZz10aGlzLmZsYXRTaGFkaW5nKSx0aGlzLnZpc2libGU9PT0hMSYmKG4udmlzaWJsZT0hMSksdGhpcy50b25lTWFwcGVkPT09ITEmJihuLnRvbmVNYXBwZWQ9ITEpLEpTT04uc3RyaW5naWZ5KHRoaXMudXNlckRhdGEpIT09Int9IiYmKG4udXNlckRhdGE9dGhpcy51c2VyRGF0YSk7ZnVuY3Rpb24gaShvKXtsZXQgYT1bXTtmb3IobGV0IHMgaW4gbyl7bGV0IGw9b1tzXTtkZWxldGUgbC5tZXRhZGF0YSxhLnB1c2gobCl9cmV0dXJuIGF9aWYocil7bGV0IG89aSh0LnRleHR1cmVzKSxhPWkodC5pbWFnZXMpO28ubGVuZ3RoPjAmJihuLnRleHR1cmVzPW8pLGEubGVuZ3RoPjAmJihuLmltYWdlcz1hKX1yZXR1cm4gbn1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyl9Y29weSh0KXt0aGlzLm5hbWU9dC5uYW1lLHRoaXMuZm9nPXQuZm9nLHRoaXMuYmxlbmRpbmc9dC5ibGVuZGluZyx0aGlzLnNpZGU9dC5zaWRlLHRoaXMudmVydGV4Q29sb3JzPXQudmVydGV4Q29sb3JzLHRoaXMub3BhY2l0eT10Lm9wYWNpdHksdGhpcy50cmFuc3BhcmVudD10LnRyYW5zcGFyZW50LHRoaXMuYmxlbmRTcmM9dC5ibGVuZFNyYyx0aGlzLmJsZW5kRHN0PXQuYmxlbmREc3QsdGhpcy5ibGVuZEVxdWF0aW9uPXQuYmxlbmRFcXVhdGlvbix0aGlzLmJsZW5kU3JjQWxwaGE9dC5ibGVuZFNyY0FscGhhLHRoaXMuYmxlbmREc3RBbHBoYT10LmJsZW5kRHN0QWxwaGEsdGhpcy5ibGVuZEVxdWF0aW9uQWxwaGE9dC5ibGVuZEVxdWF0aW9uQWxwaGEsdGhpcy5kZXB0aEZ1bmM9dC5kZXB0aEZ1bmMsdGhpcy5kZXB0aFRlc3Q9dC5kZXB0aFRlc3QsdGhpcy5kZXB0aFdyaXRlPXQuZGVwdGhXcml0ZSx0aGlzLnN0ZW5jaWxXcml0ZU1hc2s9dC5zdGVuY2lsV3JpdGVNYXNrLHRoaXMuc3RlbmNpbEZ1bmM9dC5zdGVuY2lsRnVuYyx0aGlzLnN0ZW5jaWxSZWY9dC5zdGVuY2lsUmVmLHRoaXMuc3RlbmNpbEZ1bmNNYXNrPXQuc3RlbmNpbEZ1bmNNYXNrLHRoaXMuc3RlbmNpbEZhaWw9dC5zdGVuY2lsRmFpbCx0aGlzLnN0ZW5jaWxaRmFpbD10LnN0ZW5jaWxaRmFpbCx0aGlzLnN0ZW5jaWxaUGFzcz10LnN0ZW5jaWxaUGFzcyx0aGlzLnN0ZW5jaWxXcml0ZT10LnN0ZW5jaWxXcml0ZTtsZXQgcj10LmNsaXBwaW5nUGxhbmVzLG49bnVsbDtpZihyIT09bnVsbCl7bGV0IGk9ci5sZW5ndGg7bj1uZXcgQXJyYXkoaSk7Zm9yKGxldCBvPTA7byE9PWk7KytvKW5bb109cltvXS5jbG9uZSgpfXJldHVybiB0aGlzLmNsaXBwaW5nUGxhbmVzPW4sdGhpcy5jbGlwSW50ZXJzZWN0aW9uPXQuY2xpcEludGVyc2VjdGlvbix0aGlzLmNsaXBTaGFkb3dzPXQuY2xpcFNoYWRvd3MsdGhpcy5zaGFkb3dTaWRlPXQuc2hhZG93U2lkZSx0aGlzLmNvbG9yV3JpdGU9dC5jb2xvcldyaXRlLHRoaXMuYWxwaGFXcml0ZT10LmFscGhhV3JpdGUsdGhpcy5wcmVjaXNpb249dC5wcmVjaXNpb24sdGhpcy5wb2x5Z29uT2Zmc2V0PXQucG9seWdvbk9mZnNldCx0aGlzLnBvbHlnb25PZmZzZXRGYWN0b3I9dC5wb2x5Z29uT2Zmc2V0RmFjdG9yLHRoaXMucG9seWdvbk9mZnNldFVuaXRzPXQucG9seWdvbk9mZnNldFVuaXRzLHRoaXMuZGl0aGVyaW5nPXQuZGl0aGVyaW5nLHRoaXMuYWxwaGFUZXN0PXQuYWxwaGFUZXN0LHRoaXMuYWxwaGFUb0NvdmVyYWdlPXQuYWxwaGFUb0NvdmVyYWdlLHRoaXMucHJlbXVsdGlwbGllZEFscGhhPXQucHJlbXVsdGlwbGllZEFscGhhLHRoaXMudmlzaWJsZT10LnZpc2libGUsdGhpcy50b25lTWFwcGVkPXQudG9uZU1hcHBlZCx0aGlzLnVzZXJEYXRhPUpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkodC51c2VyRGF0YSkpLHRoaXN9ZGlzcG9zZSgpe3RoaXMuZGlzcGF0Y2hFdmVudCh7dHlwZToiZGlzcG9zZSJ9KX1zZXQgbmVlZHNVcGRhdGUodCl7dD09PSEwJiZ0aGlzLnZlcnNpb24rK319O3FpLnByb3RvdHlwZS5pc01hdGVyaWFsPSEwO3ZhciBzaD1jbGFzcyBleHRlbmRzIHFpe2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJNZXNoQmFzaWNNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgbmUoMTY3NzcyMTUpLHRoaXMubWFwPW51bGwsdGhpcy5saWdodE1hcD1udWxsLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9MSx0aGlzLmFvTWFwPW51bGwsdGhpcy5hb01hcEludGVuc2l0eT0xLHRoaXMuc3BlY3VsYXJNYXA9bnVsbCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy5lbnZNYXA9bnVsbCx0aGlzLmNvbWJpbmU9RDYsdGhpcy5yZWZsZWN0aXZpdHk9MSx0aGlzLnJlZnJhY3Rpb25SYXRpbz0uOTgsdGhpcy53aXJlZnJhbWU9ITEsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9MSx0aGlzLndpcmVmcmFtZUxpbmVjYXA9InJvdW5kIix0aGlzLndpcmVmcmFtZUxpbmVqb2luPSJyb3VuZCIsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5tYXA9dC5tYXAsdGhpcy5saWdodE1hcD10LmxpZ2h0TWFwLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9dC5saWdodE1hcEludGVuc2l0eSx0aGlzLmFvTWFwPXQuYW9NYXAsdGhpcy5hb01hcEludGVuc2l0eT10LmFvTWFwSW50ZW5zaXR5LHRoaXMuc3BlY3VsYXJNYXA9dC5zcGVjdWxhck1hcCx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5lbnZNYXA9dC5lbnZNYXAsdGhpcy5jb21iaW5lPXQuY29tYmluZSx0aGlzLnJlZmxlY3Rpdml0eT10LnJlZmxlY3Rpdml0eSx0aGlzLnJlZnJhY3Rpb25SYXRpbz10LnJlZnJhY3Rpb25SYXRpbyx0aGlzLndpcmVmcmFtZT10LndpcmVmcmFtZSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD10LndpcmVmcmFtZUxpbmV3aWR0aCx0aGlzLndpcmVmcmFtZUxpbmVjYXA9dC53aXJlZnJhbWVMaW5lY2FwLHRoaXMud2lyZWZyYW1lTGluZWpvaW49dC53aXJlZnJhbWVMaW5lam9pbix0aGlzfX07c2gucHJvdG90eXBlLmlzTWVzaEJhc2ljTWF0ZXJpYWw9ITA7dmFyIExuPW5ldyBqLHlWPW5ldyBMdCxKZT1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIsbil7aWYoQXJyYXkuaXNBcnJheSh0KSl0aHJvdyBuZXcgVHlwZUVycm9yKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGU6IGFycmF5IHNob3VsZCBiZSBhIFR5cGVkIEFycmF5LiIpO3RoaXMubmFtZT0iIix0aGlzLmFycmF5PXQsdGhpcy5pdGVtU2l6ZT1yLHRoaXMuY291bnQ9dCE9PXZvaWQgMD90Lmxlbmd0aC9yOjAsdGhpcy5ub3JtYWxpemVkPW49PT0hMCx0aGlzLnVzYWdlPVczLHRoaXMudXBkYXRlUmFuZ2U9e29mZnNldDowLGNvdW50Oi0xfSx0aGlzLnZlcnNpb249MH1vblVwbG9hZENhbGxiYWNrKCl7fXNldCBuZWVkc1VwZGF0ZSh0KXt0PT09ITAmJnRoaXMudmVyc2lvbisrfXNldFVzYWdlKHQpe3JldHVybiB0aGlzLnVzYWdlPXQsdGhpc31jb3B5KHQpe3JldHVybiB0aGlzLm5hbWU9dC5uYW1lLHRoaXMuYXJyYXk9bmV3IHQuYXJyYXkuY29uc3RydWN0b3IodC5hcnJheSksdGhpcy5pdGVtU2l6ZT10Lml0ZW1TaXplLHRoaXMuY291bnQ9dC5jb3VudCx0aGlzLm5vcm1hbGl6ZWQ9dC5ub3JtYWxpemVkLHRoaXMudXNhZ2U9dC51c2FnZSx0aGlzfWNvcHlBdCh0LHIsbil7dCo9dGhpcy5pdGVtU2l6ZSxuKj1yLml0ZW1TaXplO2ZvcihsZXQgaT0wLG89dGhpcy5pdGVtU2l6ZTtpPG87aSsrKXRoaXMuYXJyYXlbdCtpXT1yLmFycmF5W24raV07cmV0dXJuIHRoaXN9Y29weUFycmF5KHQpe3JldHVybiB0aGlzLmFycmF5LnNldCh0KSx0aGlzfWNvcHlDb2xvcnNBcnJheSh0KXtsZXQgcj10aGlzLmFycmF5LG49MDtmb3IobGV0IGk9MCxvPXQubGVuZ3RoO2k8bztpKyspe2xldCBhPXRbaV07YT09PXZvaWQgMCYmKGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyQXR0cmlidXRlLmNvcHlDb2xvcnNBcnJheSgpOiBjb2xvciBpcyB1bmRlZmluZWQiLGkpLGE9bmV3IG5lKSxyW24rK109YS5yLHJbbisrXT1hLmcscltuKytdPWEuYn1yZXR1cm4gdGhpc31jb3B5VmVjdG9yMnNBcnJheSh0KXtsZXQgcj10aGlzLmFycmF5LG49MDtmb3IobGV0IGk9MCxvPXQubGVuZ3RoO2k8bztpKyspe2xldCBhPXRbaV07YT09PXZvaWQgMCYmKGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyQXR0cmlidXRlLmNvcHlWZWN0b3Iyc0FycmF5KCk6IHZlY3RvciBpcyB1bmRlZmluZWQiLGkpLGE9bmV3IEx0KSxyW24rK109YS54LHJbbisrXT1hLnl9cmV0dXJuIHRoaXN9Y29weVZlY3RvcjNzQXJyYXkodCl7bGV0IHI9dGhpcy5hcnJheSxuPTA7Zm9yKGxldCBpPTAsbz10Lmxlbmd0aDtpPG87aSsrKXtsZXQgYT10W2ldO2E9PT12b2lkIDAmJihjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZS5jb3B5VmVjdG9yM3NBcnJheSgpOiB2ZWN0b3IgaXMgdW5kZWZpbmVkIixpKSxhPW5ldyBqKSxyW24rK109YS54LHJbbisrXT1hLnkscltuKytdPWEuen1yZXR1cm4gdGhpc31jb3B5VmVjdG9yNHNBcnJheSh0KXtsZXQgcj10aGlzLmFycmF5LG49MDtmb3IobGV0IGk9MCxvPXQubGVuZ3RoO2k8bztpKyspe2xldCBhPXRbaV07YT09PXZvaWQgMCYmKGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyQXR0cmlidXRlLmNvcHlWZWN0b3I0c0FycmF5KCk6IHZlY3RvciBpcyB1bmRlZmluZWQiLGkpLGE9bmV3IGVuKSxyW24rK109YS54LHJbbisrXT1hLnkscltuKytdPWEueixyW24rK109YS53fXJldHVybiB0aGlzfWFwcGx5TWF0cml4Myh0KXtpZih0aGlzLml0ZW1TaXplPT09Milmb3IobGV0IHI9MCxuPXRoaXMuY291bnQ7cjxuO3IrKyl5Vi5mcm9tQnVmZmVyQXR0cmlidXRlKHRoaXMscikseVYuYXBwbHlNYXRyaXgzKHQpLHRoaXMuc2V0WFkocix5Vi54LHlWLnkpO2Vsc2UgaWYodGhpcy5pdGVtU2l6ZT09PTMpZm9yKGxldCByPTAsbj10aGlzLmNvdW50O3I8bjtyKyspTG4uZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0aGlzLHIpLExuLmFwcGx5TWF0cml4Myh0KSx0aGlzLnNldFhZWihyLExuLngsTG4ueSxMbi56KTtyZXR1cm4gdGhpc31hcHBseU1hdHJpeDQodCl7Zm9yKGxldCByPTAsbj10aGlzLmNvdW50O3I8bjtyKyspTG4ueD10aGlzLmdldFgociksTG4ueT10aGlzLmdldFkociksTG4uej10aGlzLmdldFoociksTG4uYXBwbHlNYXRyaXg0KHQpLHRoaXMuc2V0WFlaKHIsTG4ueCxMbi55LExuLnopO3JldHVybiB0aGlzfWFwcGx5Tm9ybWFsTWF0cml4KHQpe2ZvcihsZXQgcj0wLG49dGhpcy5jb3VudDtyPG47cisrKUxuLng9dGhpcy5nZXRYKHIpLExuLnk9dGhpcy5nZXRZKHIpLExuLno9dGhpcy5nZXRaKHIpLExuLmFwcGx5Tm9ybWFsTWF0cml4KHQpLHRoaXMuc2V0WFlaKHIsTG4ueCxMbi55LExuLnopO3JldHVybiB0aGlzfXRyYW5zZm9ybURpcmVjdGlvbih0KXtmb3IobGV0IHI9MCxuPXRoaXMuY291bnQ7cjxuO3IrKylMbi54PXRoaXMuZ2V0WChyKSxMbi55PXRoaXMuZ2V0WShyKSxMbi56PXRoaXMuZ2V0WihyKSxMbi50cmFuc2Zvcm1EaXJlY3Rpb24odCksdGhpcy5zZXRYWVoocixMbi54LExuLnksTG4ueik7cmV0dXJuIHRoaXN9c2V0KHQscj0wKXtyZXR1cm4gdGhpcy5hcnJheS5zZXQodCxyKSx0aGlzfWdldFgodCl7cmV0dXJuIHRoaXMuYXJyYXlbdCp0aGlzLml0ZW1TaXplXX1zZXRYKHQscil7cmV0dXJuIHRoaXMuYXJyYXlbdCp0aGlzLml0ZW1TaXplXT1yLHRoaXN9Z2V0WSh0KXtyZXR1cm4gdGhpcy5hcnJheVt0KnRoaXMuaXRlbVNpemUrMV19c2V0WSh0LHIpe3JldHVybiB0aGlzLmFycmF5W3QqdGhpcy5pdGVtU2l6ZSsxXT1yLHRoaXN9Z2V0Wih0KXtyZXR1cm4gdGhpcy5hcnJheVt0KnRoaXMuaXRlbVNpemUrMl19c2V0Wih0LHIpe3JldHVybiB0aGlzLmFycmF5W3QqdGhpcy5pdGVtU2l6ZSsyXT1yLHRoaXN9Z2V0Vyh0KXtyZXR1cm4gdGhpcy5hcnJheVt0KnRoaXMuaXRlbVNpemUrM119c2V0Vyh0LHIpe3JldHVybiB0aGlzLmFycmF5W3QqdGhpcy5pdGVtU2l6ZSszXT1yLHRoaXN9c2V0WFkodCxyLG4pe3JldHVybiB0Kj10aGlzLml0ZW1TaXplLHRoaXMuYXJyYXlbdCswXT1yLHRoaXMuYXJyYXlbdCsxXT1uLHRoaXN9c2V0WFlaKHQscixuLGkpe3JldHVybiB0Kj10aGlzLml0ZW1TaXplLHRoaXMuYXJyYXlbdCswXT1yLHRoaXMuYXJyYXlbdCsxXT1uLHRoaXMuYXJyYXlbdCsyXT1pLHRoaXN9c2V0WFlaVyh0LHIsbixpLG8pe3JldHVybiB0Kj10aGlzLml0ZW1TaXplLHRoaXMuYXJyYXlbdCswXT1yLHRoaXMuYXJyYXlbdCsxXT1uLHRoaXMuYXJyYXlbdCsyXT1pLHRoaXMuYXJyYXlbdCszXT1vLHRoaXN9b25VcGxvYWQodCl7cmV0dXJuIHRoaXMub25VcGxvYWRDYWxsYmFjaz10LHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5hcnJheSx0aGlzLml0ZW1TaXplKS5jb3B5KHRoaXMpfXRvSlNPTigpe2xldCB0PXtpdGVtU2l6ZTp0aGlzLml0ZW1TaXplLHR5cGU6dGhpcy5hcnJheS5jb25zdHJ1Y3Rvci5uYW1lLGFycmF5OkFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKHRoaXMuYXJyYXkpLG5vcm1hbGl6ZWQ6dGhpcy5ub3JtYWxpemVkfTtyZXR1cm4gdGhpcy5uYW1lIT09IiImJih0Lm5hbWU9dGhpcy5uYW1lKSx0aGlzLnVzYWdlIT09VzMmJih0LnVzYWdlPXRoaXMudXNhZ2UpLCh0aGlzLnVwZGF0ZVJhbmdlLm9mZnNldCE9PTB8fHRoaXMudXBkYXRlUmFuZ2UuY291bnQhPT0tMSkmJih0LnVwZGF0ZVJhbmdlPXRoaXMudXBkYXRlUmFuZ2UpLHR9fTtKZS5wcm90b3R5cGUuaXNCdWZmZXJBdHRyaWJ1dGU9ITA7dmFyIGxVPWNsYXNzIGV4dGVuZHMgSmV7Y29uc3RydWN0b3IodCxyLG4pe3N1cGVyKG5ldyBJbnQ4QXJyYXkodCkscixuKX19LGNVPWNsYXNzIGV4dGVuZHMgSmV7Y29uc3RydWN0b3IodCxyLG4pe3N1cGVyKG5ldyBVaW50OEFycmF5KHQpLHIsbil9fSx1VT1jbGFzcyBleHRlbmRzIEple2NvbnN0cnVjdG9yKHQscixuKXtzdXBlcihuZXcgVWludDhDbGFtcGVkQXJyYXkodCkscixuKX19LGhVPWNsYXNzIGV4dGVuZHMgSmV7Y29uc3RydWN0b3IodCxyLG4pe3N1cGVyKG5ldyBJbnQxNkFycmF5KHQpLHIsbil9fSwkMz1jbGFzcyBleHRlbmRzIEple2NvbnN0cnVjdG9yKHQscixuKXtzdXBlcihuZXcgVWludDE2QXJyYXkodCkscixuKX19LGZVPWNsYXNzIGV4dGVuZHMgSmV7Y29uc3RydWN0b3IodCxyLG4pe3N1cGVyKG5ldyBJbnQzMkFycmF5KHQpLHIsbil9fSxLMz1jbGFzcyBleHRlbmRzIEple2NvbnN0cnVjdG9yKHQscixuKXtzdXBlcihuZXcgVWludDMyQXJyYXkodCkscixuKX19LHBVPWNsYXNzIGV4dGVuZHMgSmV7Y29uc3RydWN0b3IodCxyLG4pe3N1cGVyKG5ldyBVaW50MTZBcnJheSh0KSxyLG4pfX07cFUucHJvdG90eXBlLmlzRmxvYXQxNkJ1ZmZlckF0dHJpYnV0ZT0hMDt2YXIgeGU9Y2xhc3MgZXh0ZW5kcyBKZXtjb25zdHJ1Y3Rvcih0LHIsbil7c3VwZXIobmV3IEZsb2F0MzJBcnJheSh0KSxyLG4pfX0sZFU9Y2xhc3MgZXh0ZW5kcyBKZXtjb25zdHJ1Y3Rvcih0LHIsbil7c3VwZXIobmV3IEZsb2F0NjRBcnJheSh0KSxyLG4pfX0sdWZyPTAsWGM9bmV3IE1lLG91dD1uZXcgb3IsQzM9bmV3IGosUmw9bmV3IHRhLGtQPW5ldyB0YSxMbz1uZXcgaixQZT1jbGFzcyBleHRlbmRzIFVze2NvbnN0cnVjdG9yKCl7c3VwZXIoKSxPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywiaWQiLHt2YWx1ZTp1ZnIrK30pLHRoaXMudXVpZD1ObCgpLHRoaXMubmFtZT0iIix0aGlzLnR5cGU9IkJ1ZmZlckdlb21ldHJ5Iix0aGlzLmluZGV4PW51bGwsdGhpcy5hdHRyaWJ1dGVzPXt9LHRoaXMubW9ycGhBdHRyaWJ1dGVzPXt9LHRoaXMubW9ycGhUYXJnZXRzUmVsYXRpdmU9ITEsdGhpcy5ncm91cHM9W10sdGhpcy5ib3VuZGluZ0JveD1udWxsLHRoaXMuYm91bmRpbmdTcGhlcmU9bnVsbCx0aGlzLmRyYXdSYW5nZT17c3RhcnQ6MCxjb3VudDoxLzB9LHRoaXMudXNlckRhdGE9e319Z2V0SW5kZXgoKXtyZXR1cm4gdGhpcy5pbmRleH1zZXRJbmRleCh0KXtyZXR1cm4gQXJyYXkuaXNBcnJheSh0KT90aGlzLmluZGV4PW5ldyhOZmUodCk/SzM6JDMpKHQsMSk6dGhpcy5pbmRleD10LHRoaXN9Z2V0QXR0cmlidXRlKHQpe3JldHVybiB0aGlzLmF0dHJpYnV0ZXNbdF19c2V0QXR0cmlidXRlKHQscil7cmV0dXJuIHRoaXMuYXR0cmlidXRlc1t0XT1yLHRoaXN9ZGVsZXRlQXR0cmlidXRlKHQpe3JldHVybiBkZWxldGUgdGhpcy5hdHRyaWJ1dGVzW3RdLHRoaXN9aGFzQXR0cmlidXRlKHQpe3JldHVybiB0aGlzLmF0dHJpYnV0ZXNbdF0hPT12b2lkIDB9YWRkR3JvdXAodCxyLG49MCl7dGhpcy5ncm91cHMucHVzaCh7c3RhcnQ6dCxjb3VudDpyLG1hdGVyaWFsSW5kZXg6bn0pfWNsZWFyR3JvdXBzKCl7dGhpcy5ncm91cHM9W119c2V0RHJhd1JhbmdlKHQscil7dGhpcy5kcmF3UmFuZ2Uuc3RhcnQ9dCx0aGlzLmRyYXdSYW5nZS5jb3VudD1yfWFwcGx5TWF0cml4NCh0KXtsZXQgcj10aGlzLmF0dHJpYnV0ZXMucG9zaXRpb247ciE9PXZvaWQgMCYmKHIuYXBwbHlNYXRyaXg0KHQpLHIubmVlZHNVcGRhdGU9ITApO2xldCBuPXRoaXMuYXR0cmlidXRlcy5ub3JtYWw7aWYobiE9PXZvaWQgMCl7bGV0IG89bmV3IGtpKCkuZ2V0Tm9ybWFsTWF0cml4KHQpO24uYXBwbHlOb3JtYWxNYXRyaXgobyksbi5uZWVkc1VwZGF0ZT0hMH1sZXQgaT10aGlzLmF0dHJpYnV0ZXMudGFuZ2VudDtyZXR1cm4gaSE9PXZvaWQgMCYmKGkudHJhbnNmb3JtRGlyZWN0aW9uKHQpLGkubmVlZHNVcGRhdGU9ITApLHRoaXMuYm91bmRpbmdCb3ghPT1udWxsJiZ0aGlzLmNvbXB1dGVCb3VuZGluZ0JveCgpLHRoaXMuYm91bmRpbmdTcGhlcmUhPT1udWxsJiZ0aGlzLmNvbXB1dGVCb3VuZGluZ1NwaGVyZSgpLHRoaXN9YXBwbHlRdWF0ZXJuaW9uKHQpe3JldHVybiBYYy5tYWtlUm90YXRpb25Gcm9tUXVhdGVybmlvbih0KSx0aGlzLmFwcGx5TWF0cml4NChYYyksdGhpc31yb3RhdGVYKHQpe3JldHVybiBYYy5tYWtlUm90YXRpb25YKHQpLHRoaXMuYXBwbHlNYXRyaXg0KFhjKSx0aGlzfXJvdGF0ZVkodCl7cmV0dXJuIFhjLm1ha2VSb3RhdGlvblkodCksdGhpcy5hcHBseU1hdHJpeDQoWGMpLHRoaXN9cm90YXRlWih0KXtyZXR1cm4gWGMubWFrZVJvdGF0aW9uWih0KSx0aGlzLmFwcGx5TWF0cml4NChYYyksdGhpc310cmFuc2xhdGUodCxyLG4pe3JldHVybiBYYy5tYWtlVHJhbnNsYXRpb24odCxyLG4pLHRoaXMuYXBwbHlNYXRyaXg0KFhjKSx0aGlzfXNjYWxlKHQscixuKXtyZXR1cm4gWGMubWFrZVNjYWxlKHQscixuKSx0aGlzLmFwcGx5TWF0cml4NChYYyksdGhpc31sb29rQXQodCl7cmV0dXJuIG91dC5sb29rQXQodCksb3V0LnVwZGF0ZU1hdHJpeCgpLHRoaXMuYXBwbHlNYXRyaXg0KG91dC5tYXRyaXgpLHRoaXN9Y2VudGVyKCl7cmV0dXJuIHRoaXMuY29tcHV0ZUJvdW5kaW5nQm94KCksdGhpcy5ib3VuZGluZ0JveC5nZXRDZW50ZXIoQzMpLm5lZ2F0ZSgpLHRoaXMudHJhbnNsYXRlKEMzLngsQzMueSxDMy56KSx0aGlzfXNldEZyb21Qb2ludHModCl7bGV0IHI9W107Zm9yKGxldCBuPTAsaT10Lmxlbmd0aDtuPGk7bisrKXtsZXQgbz10W25dO3IucHVzaChvLngsby55LG8uenx8MCl9cmV0dXJuIHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IHhlKHIsMykpLHRoaXN9Y29tcHV0ZUJvdW5kaW5nQm94KCl7dGhpcy5ib3VuZGluZ0JveD09PW51bGwmJih0aGlzLmJvdW5kaW5nQm94PW5ldyB0YSk7bGV0IHQ9dGhpcy5hdHRyaWJ1dGVzLnBvc2l0aW9uLHI9dGhpcy5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb247aWYodCYmdC5pc0dMQnVmZmVyQXR0cmlidXRlKXtjb25zb2xlLmVycm9yKCdUSFJFRS5CdWZmZXJHZW9tZXRyeS5jb21wdXRlQm91bmRpbmdCb3goKTogR0xCdWZmZXJBdHRyaWJ1dGUgcmVxdWlyZXMgYSBtYW51YWwgYm91bmRpbmcgYm94LiBBbHRlcm5hdGl2ZWx5IHNldCAibWVzaC5mcnVzdHVtQ3VsbGVkIiB0byAiZmFsc2UiLicsdGhpcyksdGhpcy5ib3VuZGluZ0JveC5zZXQobmV3IGooLTEvMCwtMS8wLC0xLzApLG5ldyBqKDEvMCwxLzAsMS8wKSk7cmV0dXJufWlmKHQhPT12b2lkIDApe2lmKHRoaXMuYm91bmRpbmdCb3guc2V0RnJvbUJ1ZmZlckF0dHJpYnV0ZSh0KSxyKWZvcihsZXQgbj0wLGk9ci5sZW5ndGg7bjxpO24rKyl7bGV0IG89cltuXTtSbC5zZXRGcm9tQnVmZmVyQXR0cmlidXRlKG8pLHRoaXMubW9ycGhUYXJnZXRzUmVsYXRpdmU/KExvLmFkZFZlY3RvcnModGhpcy5ib3VuZGluZ0JveC5taW4sUmwubWluKSx0aGlzLmJvdW5kaW5nQm94LmV4cGFuZEJ5UG9pbnQoTG8pLExvLmFkZFZlY3RvcnModGhpcy5ib3VuZGluZ0JveC5tYXgsUmwubWF4KSx0aGlzLmJvdW5kaW5nQm94LmV4cGFuZEJ5UG9pbnQoTG8pKToodGhpcy5ib3VuZGluZ0JveC5leHBhbmRCeVBvaW50KFJsLm1pbiksdGhpcy5ib3VuZGluZ0JveC5leHBhbmRCeVBvaW50KFJsLm1heCkpfX1lbHNlIHRoaXMuYm91bmRpbmdCb3gubWFrZUVtcHR5KCk7KGlzTmFOKHRoaXMuYm91bmRpbmdCb3gubWluLngpfHxpc05hTih0aGlzLmJvdW5kaW5nQm94Lm1pbi55KXx8aXNOYU4odGhpcy5ib3VuZGluZ0JveC5taW4ueikpJiZjb25zb2xlLmVycm9yKCdUSFJFRS5CdWZmZXJHZW9tZXRyeS5jb21wdXRlQm91bmRpbmdCb3goKTogQ29tcHV0ZWQgbWluL21heCBoYXZlIE5hTiB2YWx1ZXMuIFRoZSAicG9zaXRpb24iIGF0dHJpYnV0ZSBpcyBsaWtlbHkgdG8gaGF2ZSBOYU4gdmFsdWVzLicsdGhpcyl9Y29tcHV0ZUJvdW5kaW5nU3BoZXJlKCl7dGhpcy5ib3VuZGluZ1NwaGVyZT09PW51bGwmJih0aGlzLmJvdW5kaW5nU3BoZXJlPW5ldyBaZik7bGV0IHQ9dGhpcy5hdHRyaWJ1dGVzLnBvc2l0aW9uLHI9dGhpcy5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb247aWYodCYmdC5pc0dMQnVmZmVyQXR0cmlidXRlKXtjb25zb2xlLmVycm9yKCdUSFJFRS5CdWZmZXJHZW9tZXRyeS5jb21wdXRlQm91bmRpbmdTcGhlcmUoKTogR0xCdWZmZXJBdHRyaWJ1dGUgcmVxdWlyZXMgYSBtYW51YWwgYm91bmRpbmcgc3BoZXJlLiBBbHRlcm5hdGl2ZWx5IHNldCAibWVzaC5mcnVzdHVtQ3VsbGVkIiB0byAiZmFsc2UiLicsdGhpcyksdGhpcy5ib3VuZGluZ1NwaGVyZS5zZXQobmV3IGosMS8wKTtyZXR1cm59aWYodCl7bGV0IG49dGhpcy5ib3VuZGluZ1NwaGVyZS5jZW50ZXI7aWYoUmwuc2V0RnJvbUJ1ZmZlckF0dHJpYnV0ZSh0KSxyKWZvcihsZXQgbz0wLGE9ci5sZW5ndGg7bzxhO28rKyl7bGV0IHM9cltvXTtrUC5zZXRGcm9tQnVmZmVyQXR0cmlidXRlKHMpLHRoaXMubW9ycGhUYXJnZXRzUmVsYXRpdmU/KExvLmFkZFZlY3RvcnMoUmwubWluLGtQLm1pbiksUmwuZXhwYW5kQnlQb2ludChMbyksTG8uYWRkVmVjdG9ycyhSbC5tYXgsa1AubWF4KSxSbC5leHBhbmRCeVBvaW50KExvKSk6KFJsLmV4cGFuZEJ5UG9pbnQoa1AubWluKSxSbC5leHBhbmRCeVBvaW50KGtQLm1heCkpfVJsLmdldENlbnRlcihuKTtsZXQgaT0wO2ZvcihsZXQgbz0wLGE9dC5jb3VudDtvPGE7bysrKUxvLmZyb21CdWZmZXJBdHRyaWJ1dGUodCxvKSxpPU1hdGgubWF4KGksbi5kaXN0YW5jZVRvU3F1YXJlZChMbykpO2lmKHIpZm9yKGxldCBvPTAsYT1yLmxlbmd0aDtvPGE7bysrKXtsZXQgcz1yW29dLGw9dGhpcy5tb3JwaFRhcmdldHNSZWxhdGl2ZTtmb3IobGV0IGM9MCx1PXMuY291bnQ7Yzx1O2MrKylMby5mcm9tQnVmZmVyQXR0cmlidXRlKHMsYyksbCYmKEMzLmZyb21CdWZmZXJBdHRyaWJ1dGUodCxjKSxMby5hZGQoQzMpKSxpPU1hdGgubWF4KGksbi5kaXN0YW5jZVRvU3F1YXJlZChMbykpfXRoaXMuYm91bmRpbmdTcGhlcmUucmFkaXVzPU1hdGguc3FydChpKSxpc05hTih0aGlzLmJvdW5kaW5nU3BoZXJlLnJhZGl1cykmJmNvbnNvbGUuZXJyb3IoJ1RIUkVFLkJ1ZmZlckdlb21ldHJ5LmNvbXB1dGVCb3VuZGluZ1NwaGVyZSgpOiBDb21wdXRlZCByYWRpdXMgaXMgTmFOLiBUaGUgInBvc2l0aW9uIiBhdHRyaWJ1dGUgaXMgbGlrZWx5IHRvIGhhdmUgTmFOIHZhbHVlcy4nLHRoaXMpfX1jb21wdXRlVGFuZ2VudHMoKXtsZXQgdD10aGlzLmluZGV4LHI9dGhpcy5hdHRyaWJ1dGVzO2lmKHQ9PT1udWxsfHxyLnBvc2l0aW9uPT09dm9pZCAwfHxyLm5vcm1hbD09PXZvaWQgMHx8ci51dj09PXZvaWQgMCl7Y29uc29sZS5lcnJvcigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5jb21wdXRlVGFuZ2VudHMoKSBmYWlsZWQuIE1pc3NpbmcgcmVxdWlyZWQgYXR0cmlidXRlcyAoaW5kZXgsIHBvc2l0aW9uLCBub3JtYWwgb3IgdXYpIik7cmV0dXJufWxldCBuPXQuYXJyYXksaT1yLnBvc2l0aW9uLmFycmF5LG89ci5ub3JtYWwuYXJyYXksYT1yLnV2LmFycmF5LHM9aS5sZW5ndGgvMztyLnRhbmdlbnQ9PT12b2lkIDAmJnRoaXMuc2V0QXR0cmlidXRlKCJ0YW5nZW50IixuZXcgSmUobmV3IEZsb2F0MzJBcnJheSg0KnMpLDQpKTtsZXQgbD1yLnRhbmdlbnQuYXJyYXksYz1bXSx1PVtdO2ZvcihsZXQgQj0wO0I8cztCKyspY1tCXT1uZXcgaix1W0JdPW5ldyBqO2xldCBoPW5ldyBqLGY9bmV3IGoscD1uZXcgaixkPW5ldyBMdCxnPW5ldyBMdCxfPW5ldyBMdCx5PW5ldyBqLHg9bmV3IGo7ZnVuY3Rpb24gYihCLEksTCl7aC5mcm9tQXJyYXkoaSxCKjMpLGYuZnJvbUFycmF5KGksSSozKSxwLmZyb21BcnJheShpLEwqMyksZC5mcm9tQXJyYXkoYSxCKjIpLGcuZnJvbUFycmF5KGEsSSoyKSxfLmZyb21BcnJheShhLEwqMiksZi5zdWIoaCkscC5zdWIoaCksZy5zdWIoZCksXy5zdWIoZCk7bGV0IFI9MS8oZy54Kl8ueS1fLngqZy55KTshaXNGaW5pdGUoUil8fCh5LmNvcHkoZikubXVsdGlwbHlTY2FsYXIoXy55KS5hZGRTY2FsZWRWZWN0b3IocCwtZy55KS5tdWx0aXBseVNjYWxhcihSKSx4LmNvcHkocCkubXVsdGlwbHlTY2FsYXIoZy54KS5hZGRTY2FsZWRWZWN0b3IoZiwtXy54KS5tdWx0aXBseVNjYWxhcihSKSxjW0JdLmFkZCh5KSxjW0ldLmFkZCh5KSxjW0xdLmFkZCh5KSx1W0JdLmFkZCh4KSx1W0ldLmFkZCh4KSx1W0xdLmFkZCh4KSl9bGV0IFM9dGhpcy5ncm91cHM7Uy5sZW5ndGg9PT0wJiYoUz1be3N0YXJ0OjAsY291bnQ6bi5sZW5ndGh9XSk7Zm9yKGxldCBCPTAsST1TLmxlbmd0aDtCPEk7KytCKXtsZXQgTD1TW0JdLFI9TC5zdGFydCxGPUwuY291bnQ7Zm9yKGxldCB6PVIsVT1SK0Y7ejxVO3orPTMpYihuW3orMF0sblt6KzFdLG5beisyXSl9bGV0IEM9bmV3IGosUD1uZXcgaixrPW5ldyBqLE89bmV3IGo7ZnVuY3Rpb24gRChCKXtrLmZyb21BcnJheShvLEIqMyksTy5jb3B5KGspO2xldCBJPWNbQl07Qy5jb3B5KEkpLEMuc3ViKGsubXVsdGlwbHlTY2FsYXIoay5kb3QoSSkpKS5ub3JtYWxpemUoKSxQLmNyb3NzVmVjdG9ycyhPLEkpO2xldCBSPVAuZG90KHVbQl0pPDA/LTE6MTtsW0IqNF09Qy54LGxbQio0KzFdPUMueSxsW0IqNCsyXT1DLnosbFtCKjQrM109Un1mb3IobGV0IEI9MCxJPVMubGVuZ3RoO0I8STsrK0Ipe2xldCBMPVNbQl0sUj1MLnN0YXJ0LEY9TC5jb3VudDtmb3IobGV0IHo9UixVPVIrRjt6PFU7eis9MylEKG5beiswXSksRChuW3orMV0pLEQoblt6KzJdKX19Y29tcHV0ZVZlcnRleE5vcm1hbHMoKXtsZXQgdD10aGlzLmluZGV4LHI9dGhpcy5nZXRBdHRyaWJ1dGUoInBvc2l0aW9uIik7aWYociE9PXZvaWQgMCl7bGV0IG49dGhpcy5nZXRBdHRyaWJ1dGUoIm5vcm1hbCIpO2lmKG49PT12b2lkIDApbj1uZXcgSmUobmV3IEZsb2F0MzJBcnJheShyLmNvdW50KjMpLDMpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG4pO2Vsc2UgZm9yKGxldCBmPTAscD1uLmNvdW50O2Y8cDtmKyspbi5zZXRYWVooZiwwLDAsMCk7bGV0IGk9bmV3IGosbz1uZXcgaixhPW5ldyBqLHM9bmV3IGosbD1uZXcgaixjPW5ldyBqLHU9bmV3IGosaD1uZXcgajtpZih0KWZvcihsZXQgZj0wLHA9dC5jb3VudDtmPHA7Zis9Myl7bGV0IGQ9dC5nZXRYKGYrMCksZz10LmdldFgoZisxKSxfPXQuZ2V0WChmKzIpO2kuZnJvbUJ1ZmZlckF0dHJpYnV0ZShyLGQpLG8uZnJvbUJ1ZmZlckF0dHJpYnV0ZShyLGcpLGEuZnJvbUJ1ZmZlckF0dHJpYnV0ZShyLF8pLHUuc3ViVmVjdG9ycyhhLG8pLGguc3ViVmVjdG9ycyhpLG8pLHUuY3Jvc3MoaCkscy5mcm9tQnVmZmVyQXR0cmlidXRlKG4sZCksbC5mcm9tQnVmZmVyQXR0cmlidXRlKG4sZyksYy5mcm9tQnVmZmVyQXR0cmlidXRlKG4sXykscy5hZGQodSksbC5hZGQodSksYy5hZGQodSksbi5zZXRYWVooZCxzLngscy55LHMueiksbi5zZXRYWVooZyxsLngsbC55LGwueiksbi5zZXRYWVooXyxjLngsYy55LGMueil9ZWxzZSBmb3IobGV0IGY9MCxwPXIuY291bnQ7ZjxwO2YrPTMpaS5mcm9tQnVmZmVyQXR0cmlidXRlKHIsZiswKSxvLmZyb21CdWZmZXJBdHRyaWJ1dGUocixmKzEpLGEuZnJvbUJ1ZmZlckF0dHJpYnV0ZShyLGYrMiksdS5zdWJWZWN0b3JzKGEsbyksaC5zdWJWZWN0b3JzKGksbyksdS5jcm9zcyhoKSxuLnNldFhZWihmKzAsdS54LHUueSx1LnopLG4uc2V0WFlaKGYrMSx1LngsdS55LHUueiksbi5zZXRYWVooZisyLHUueCx1LnksdS56KTt0aGlzLm5vcm1hbGl6ZU5vcm1hbHMoKSxuLm5lZWRzVXBkYXRlPSEwfX1tZXJnZSh0LHIpe2lmKCEodCYmdC5pc0J1ZmZlckdlb21ldHJ5KSl7Y29uc29sZS5lcnJvcigiVEhSRUUuQnVmZmVyR2VvbWV0cnkubWVyZ2UoKTogZ2VvbWV0cnkgbm90IGFuIGluc3RhbmNlIG9mIFRIUkVFLkJ1ZmZlckdlb21ldHJ5LiIsdCk7cmV0dXJufXI9PT12b2lkIDAmJihyPTAsY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeS5tZXJnZSgpOiBPdmVyd3JpdGluZyBvcmlnaW5hbCBnZW9tZXRyeSwgc3RhcnRpbmcgYXQgb2Zmc2V0PTAuIFVzZSBCdWZmZXJHZW9tZXRyeVV0aWxzLm1lcmdlQnVmZmVyR2VvbWV0cmllcygpIGZvciBsb3NzbGVzcyBtZXJnZS4iKSk7bGV0IG49dGhpcy5hdHRyaWJ1dGVzO2ZvcihsZXQgaSBpbiBuKXtpZih0LmF0dHJpYnV0ZXNbaV09PT12b2lkIDApY29udGludWU7bGV0IGE9bltpXS5hcnJheSxzPXQuYXR0cmlidXRlc1tpXSxsPXMuYXJyYXksYz1zLml0ZW1TaXplKnIsdT1NYXRoLm1pbihsLmxlbmd0aCxhLmxlbmd0aC1jKTtmb3IobGV0IGg9MCxmPWM7aDx1O2grKyxmKyspYVtmXT1sW2hdfXJldHVybiB0aGlzfW5vcm1hbGl6ZU5vcm1hbHMoKXtsZXQgdD10aGlzLmF0dHJpYnV0ZXMubm9ybWFsO2ZvcihsZXQgcj0wLG49dC5jb3VudDtyPG47cisrKUxvLmZyb21CdWZmZXJBdHRyaWJ1dGUodCxyKSxMby5ub3JtYWxpemUoKSx0LnNldFhZWihyLExvLngsTG8ueSxMby56KX10b05vbkluZGV4ZWQoKXtmdW5jdGlvbiB0KHMsbCl7bGV0IGM9cy5hcnJheSx1PXMuaXRlbVNpemUsaD1zLm5vcm1hbGl6ZWQsZj1uZXcgYy5jb25zdHJ1Y3RvcihsLmxlbmd0aCp1KSxwPTAsZD0wO2ZvcihsZXQgZz0wLF89bC5sZW5ndGg7ZzxfO2crKyl7cy5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlP3A9bFtnXSpzLmRhdGEuc3RyaWRlK3Mub2Zmc2V0OnA9bFtnXSp1O2ZvcihsZXQgeT0wO3k8dTt5KyspZltkKytdPWNbcCsrXX1yZXR1cm4gbmV3IEplKGYsdSxoKX1pZih0aGlzLmluZGV4PT09bnVsbClyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeS50b05vbkluZGV4ZWQoKTogQnVmZmVyR2VvbWV0cnkgaXMgYWxyZWFkeSBub24taW5kZXhlZC4iKSx0aGlzO2xldCByPW5ldyBQZSxuPXRoaXMuaW5kZXguYXJyYXksaT10aGlzLmF0dHJpYnV0ZXM7Zm9yKGxldCBzIGluIGkpe2xldCBsPWlbc10sYz10KGwsbik7ci5zZXRBdHRyaWJ1dGUocyxjKX1sZXQgbz10aGlzLm1vcnBoQXR0cmlidXRlcztmb3IobGV0IHMgaW4gbyl7bGV0IGw9W10sYz1vW3NdO2ZvcihsZXQgdT0wLGg9Yy5sZW5ndGg7dTxoO3UrKyl7bGV0IGY9Y1t1XSxwPXQoZixuKTtsLnB1c2gocCl9ci5tb3JwaEF0dHJpYnV0ZXNbc109bH1yLm1vcnBoVGFyZ2V0c1JlbGF0aXZlPXRoaXMubW9ycGhUYXJnZXRzUmVsYXRpdmU7bGV0IGE9dGhpcy5ncm91cHM7Zm9yKGxldCBzPTAsbD1hLmxlbmd0aDtzPGw7cysrKXtsZXQgYz1hW3NdO3IuYWRkR3JvdXAoYy5zdGFydCxjLmNvdW50LGMubWF0ZXJpYWxJbmRleCl9cmV0dXJuIHJ9dG9KU09OKCl7bGV0IHQ9e21ldGFkYXRhOnt2ZXJzaW9uOjQuNSx0eXBlOiJCdWZmZXJHZW9tZXRyeSIsZ2VuZXJhdG9yOiJCdWZmZXJHZW9tZXRyeS50b0pTT04ifX07aWYodC51dWlkPXRoaXMudXVpZCx0LnR5cGU9dGhpcy50eXBlLHRoaXMubmFtZSE9PSIiJiYodC5uYW1lPXRoaXMubmFtZSksT2JqZWN0LmtleXModGhpcy51c2VyRGF0YSkubGVuZ3RoPjAmJih0LnVzZXJEYXRhPXRoaXMudXNlckRhdGEpLHRoaXMucGFyYW1ldGVycyE9PXZvaWQgMCl7bGV0IGw9dGhpcy5wYXJhbWV0ZXJzO2ZvcihsZXQgYyBpbiBsKWxbY10hPT12b2lkIDAmJih0W2NdPWxbY10pO3JldHVybiB0fXQuZGF0YT17YXR0cmlidXRlczp7fX07bGV0IHI9dGhpcy5pbmRleDtyIT09bnVsbCYmKHQuZGF0YS5pbmRleD17dHlwZTpyLmFycmF5LmNvbnN0cnVjdG9yLm5hbWUsYXJyYXk6QXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoci5hcnJheSl9KTtsZXQgbj10aGlzLmF0dHJpYnV0ZXM7Zm9yKGxldCBsIGluIG4pe2xldCBjPW5bbF07dC5kYXRhLmF0dHJpYnV0ZXNbbF09Yy50b0pTT04odC5kYXRhKX1sZXQgaT17fSxvPSExO2ZvcihsZXQgbCBpbiB0aGlzLm1vcnBoQXR0cmlidXRlcyl7bGV0IGM9dGhpcy5tb3JwaEF0dHJpYnV0ZXNbbF0sdT1bXTtmb3IobGV0IGg9MCxmPWMubGVuZ3RoO2g8ZjtoKyspe2xldCBwPWNbaF07dS5wdXNoKHAudG9KU09OKHQuZGF0YSkpfXUubGVuZ3RoPjAmJihpW2xdPXUsbz0hMCl9byYmKHQuZGF0YS5tb3JwaEF0dHJpYnV0ZXM9aSx0LmRhdGEubW9ycGhUYXJnZXRzUmVsYXRpdmU9dGhpcy5tb3JwaFRhcmdldHNSZWxhdGl2ZSk7bGV0IGE9dGhpcy5ncm91cHM7YS5sZW5ndGg+MCYmKHQuZGF0YS5ncm91cHM9SlNPTi5wYXJzZShKU09OLnN0cmluZ2lmeShhKSkpO2xldCBzPXRoaXMuYm91bmRpbmdTcGhlcmU7cmV0dXJuIHMhPT1udWxsJiYodC5kYXRhLmJvdW5kaW5nU3BoZXJlPXtjZW50ZXI6cy5jZW50ZXIudG9BcnJheSgpLHJhZGl1czpzLnJhZGl1c30pLHR9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfWNvcHkodCl7dGhpcy5pbmRleD1udWxsLHRoaXMuYXR0cmlidXRlcz17fSx0aGlzLm1vcnBoQXR0cmlidXRlcz17fSx0aGlzLmdyb3Vwcz1bXSx0aGlzLmJvdW5kaW5nQm94PW51bGwsdGhpcy5ib3VuZGluZ1NwaGVyZT1udWxsO2xldCByPXt9O3RoaXMubmFtZT10Lm5hbWU7bGV0IG49dC5pbmRleDtuIT09bnVsbCYmdGhpcy5zZXRJbmRleChuLmNsb25lKHIpKTtsZXQgaT10LmF0dHJpYnV0ZXM7Zm9yKGxldCBjIGluIGkpe2xldCB1PWlbY107dGhpcy5zZXRBdHRyaWJ1dGUoYyx1LmNsb25lKHIpKX1sZXQgbz10Lm1vcnBoQXR0cmlidXRlcztmb3IobGV0IGMgaW4gbyl7bGV0IHU9W10saD1vW2NdO2ZvcihsZXQgZj0wLHA9aC5sZW5ndGg7ZjxwO2YrKyl1LnB1c2goaFtmXS5jbG9uZShyKSk7dGhpcy5tb3JwaEF0dHJpYnV0ZXNbY109dX10aGlzLm1vcnBoVGFyZ2V0c1JlbGF0aXZlPXQubW9ycGhUYXJnZXRzUmVsYXRpdmU7bGV0IGE9dC5ncm91cHM7Zm9yKGxldCBjPTAsdT1hLmxlbmd0aDtjPHU7YysrKXtsZXQgaD1hW2NdO3RoaXMuYWRkR3JvdXAoaC5zdGFydCxoLmNvdW50LGgubWF0ZXJpYWxJbmRleCl9bGV0IHM9dC5ib3VuZGluZ0JveDtzIT09bnVsbCYmKHRoaXMuYm91bmRpbmdCb3g9cy5jbG9uZSgpKTtsZXQgbD10LmJvdW5kaW5nU3BoZXJlO3JldHVybiBsIT09bnVsbCYmKHRoaXMuYm91bmRpbmdTcGhlcmU9bC5jbG9uZSgpKSx0aGlzLmRyYXdSYW5nZS5zdGFydD10LmRyYXdSYW5nZS5zdGFydCx0aGlzLmRyYXdSYW5nZS5jb3VudD10LmRyYXdSYW5nZS5jb3VudCx0aGlzLnVzZXJEYXRhPXQudXNlckRhdGEsdC5wYXJhbWV0ZXJzIT09dm9pZCAwJiYodGhpcy5wYXJhbWV0ZXJzPU9iamVjdC5hc3NpZ24oe30sdC5wYXJhbWV0ZXJzKSksdGhpc31kaXNwb3NlKCl7dGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJkaXNwb3NlIn0pfX07UGUucHJvdG90eXBlLmlzQnVmZmVyR2VvbWV0cnk9ITA7dmFyIFN1ZT1uZXcgTWUsQTM9bmV3IEpmLGF1dD1uZXcgWmYsUjA9bmV3IGosTjA9bmV3IGosRDA9bmV3IGosc3V0PW5ldyBqLGx1dD1uZXcgaixjdXQ9bmV3IGosdlY9bmV3IGoseFY9bmV3IGosYlY9bmV3IGosd1Y9bmV3IEx0LFNWPW5ldyBMdCxNVj1uZXcgTHQsdXV0PW5ldyBqLEVWPW5ldyBqLGVpPWNsYXNzIGV4dGVuZHMgb3J7Y29uc3RydWN0b3IodD1uZXcgUGUscj1uZXcgc2gpe3N1cGVyKCksdGhpcy50eXBlPSJNZXNoIix0aGlzLmdlb21ldHJ5PXQsdGhpcy5tYXRlcmlhbD1yLHRoaXMudXBkYXRlTW9ycGhUYXJnZXRzKCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0Lm1vcnBoVGFyZ2V0SW5mbHVlbmNlcyE9PXZvaWQgMCYmKHRoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzPXQubW9ycGhUYXJnZXRJbmZsdWVuY2VzLnNsaWNlKCkpLHQubW9ycGhUYXJnZXREaWN0aW9uYXJ5IT09dm9pZCAwJiYodGhpcy5tb3JwaFRhcmdldERpY3Rpb25hcnk9T2JqZWN0LmFzc2lnbih7fSx0Lm1vcnBoVGFyZ2V0RGljdGlvbmFyeSkpLHRoaXMubWF0ZXJpYWw9dC5tYXRlcmlhbCx0aGlzLmdlb21ldHJ5PXQuZ2VvbWV0cnksdGhpc311cGRhdGVNb3JwaFRhcmdldHMoKXtsZXQgdD10aGlzLmdlb21ldHJ5O2lmKHQuaXNCdWZmZXJHZW9tZXRyeSl7bGV0IHI9dC5tb3JwaEF0dHJpYnV0ZXMsbj1PYmplY3Qua2V5cyhyKTtpZihuLmxlbmd0aD4wKXtsZXQgaT1yW25bMF1dO2lmKGkhPT12b2lkIDApe3RoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzPVtdLHRoaXMubW9ycGhUYXJnZXREaWN0aW9uYXJ5PXt9O2ZvcihsZXQgbz0wLGE9aS5sZW5ndGg7bzxhO28rKyl7bGV0IHM9aVtvXS5uYW1lfHxTdHJpbmcobyk7dGhpcy5tb3JwaFRhcmdldEluZmx1ZW5jZXMucHVzaCgwKSx0aGlzLm1vcnBoVGFyZ2V0RGljdGlvbmFyeVtzXT1vfX19fWVsc2V7bGV0IHI9dC5tb3JwaFRhcmdldHM7ciE9PXZvaWQgMCYmci5sZW5ndGg+MCYmY29uc29sZS5lcnJvcigiVEhSRUUuTWVzaC51cGRhdGVNb3JwaFRhcmdldHMoKSBubyBsb25nZXIgc3VwcG9ydHMgVEhSRUUuR2VvbWV0cnkuIFVzZSBUSFJFRS5CdWZmZXJHZW9tZXRyeSBpbnN0ZWFkLiIpfX1yYXljYXN0KHQscil7bGV0IG49dGhpcy5nZW9tZXRyeSxpPXRoaXMubWF0ZXJpYWwsbz10aGlzLm1hdHJpeFdvcmxkO2lmKGk9PT12b2lkIDB8fChuLmJvdW5kaW5nU3BoZXJlPT09bnVsbCYmbi5jb21wdXRlQm91bmRpbmdTcGhlcmUoKSxhdXQuY29weShuLmJvdW5kaW5nU3BoZXJlKSxhdXQuYXBwbHlNYXRyaXg0KG8pLHQucmF5LmludGVyc2VjdHNTcGhlcmUoYXV0KT09PSExKXx8KFN1ZS5jb3B5KG8pLmludmVydCgpLEEzLmNvcHkodC5yYXkpLmFwcGx5TWF0cml4NChTdWUpLG4uYm91bmRpbmdCb3ghPT1udWxsJiZBMy5pbnRlcnNlY3RzQm94KG4uYm91bmRpbmdCb3gpPT09ITEpKXJldHVybjtsZXQgYTtpZihuLmlzQnVmZmVyR2VvbWV0cnkpe2xldCBzPW4uaW5kZXgsbD1uLmF0dHJpYnV0ZXMucG9zaXRpb24sYz1uLm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbix1PW4ubW9ycGhUYXJnZXRzUmVsYXRpdmUsaD1uLmF0dHJpYnV0ZXMudXYsZj1uLmF0dHJpYnV0ZXMudXYyLHA9bi5ncm91cHMsZD1uLmRyYXdSYW5nZTtpZihzIT09bnVsbClpZihBcnJheS5pc0FycmF5KGkpKWZvcihsZXQgZz0wLF89cC5sZW5ndGg7ZzxfO2crKyl7bGV0IHk9cFtnXSx4PWlbeS5tYXRlcmlhbEluZGV4XSxiPU1hdGgubWF4KHkuc3RhcnQsZC5zdGFydCksUz1NYXRoLm1pbihzLmNvdW50LE1hdGgubWluKHkuc3RhcnQreS5jb3VudCxkLnN0YXJ0K2QuY291bnQpKTtmb3IobGV0IEM9YixQPVM7QzxQO0MrPTMpe2xldCBrPXMuZ2V0WChDKSxPPXMuZ2V0WChDKzEpLEQ9cy5nZXRYKEMrMik7YT1UVih0aGlzLHgsdCxBMyxsLGMsdSxoLGYsayxPLEQpLGEmJihhLmZhY2VJbmRleD1NYXRoLmZsb29yKEMvMyksYS5mYWNlLm1hdGVyaWFsSW5kZXg9eS5tYXRlcmlhbEluZGV4LHIucHVzaChhKSl9fWVsc2V7bGV0IGc9TWF0aC5tYXgoMCxkLnN0YXJ0KSxfPU1hdGgubWluKHMuY291bnQsZC5zdGFydCtkLmNvdW50KTtmb3IobGV0IHk9Zyx4PV87eTx4O3krPTMpe2xldCBiPXMuZ2V0WCh5KSxTPXMuZ2V0WCh5KzEpLEM9cy5nZXRYKHkrMik7YT1UVih0aGlzLGksdCxBMyxsLGMsdSxoLGYsYixTLEMpLGEmJihhLmZhY2VJbmRleD1NYXRoLmZsb29yKHkvMyksci5wdXNoKGEpKX19ZWxzZSBpZihsIT09dm9pZCAwKWlmKEFycmF5LmlzQXJyYXkoaSkpZm9yKGxldCBnPTAsXz1wLmxlbmd0aDtnPF87ZysrKXtsZXQgeT1wW2ddLHg9aVt5Lm1hdGVyaWFsSW5kZXhdLGI9TWF0aC5tYXgoeS5zdGFydCxkLnN0YXJ0KSxTPU1hdGgubWluKGwuY291bnQsTWF0aC5taW4oeS5zdGFydCt5LmNvdW50LGQuc3RhcnQrZC5jb3VudCkpO2ZvcihsZXQgQz1iLFA9UztDPFA7Qys9Myl7bGV0IGs9QyxPPUMrMSxEPUMrMjthPVRWKHRoaXMseCx0LEEzLGwsYyx1LGgsZixrLE8sRCksYSYmKGEuZmFjZUluZGV4PU1hdGguZmxvb3IoQy8zKSxhLmZhY2UubWF0ZXJpYWxJbmRleD15Lm1hdGVyaWFsSW5kZXgsci5wdXNoKGEpKX19ZWxzZXtsZXQgZz1NYXRoLm1heCgwLGQuc3RhcnQpLF89TWF0aC5taW4obC5jb3VudCxkLnN0YXJ0K2QuY291bnQpO2ZvcihsZXQgeT1nLHg9Xzt5PHg7eSs9Myl7bGV0IGI9eSxTPXkrMSxDPXkrMjthPVRWKHRoaXMsaSx0LEEzLGwsYyx1LGgsZixiLFMsQyksYSYmKGEuZmFjZUluZGV4PU1hdGguZmxvb3IoeS8zKSxyLnB1c2goYSkpfX19ZWxzZSBuLmlzR2VvbWV0cnkmJmNvbnNvbGUuZXJyb3IoIlRIUkVFLk1lc2gucmF5Y2FzdCgpIG5vIGxvbmdlciBzdXBwb3J0cyBUSFJFRS5HZW9tZXRyeS4gVXNlIFRIUkVFLkJ1ZmZlckdlb21ldHJ5IGluc3RlYWQuIil9fTtlaS5wcm90b3R5cGUuaXNNZXNoPSEwO2Z1bmN0aW9uIGhmcihlLHQscixuLGksbyxhLHMpe2xldCBsO2lmKHQuc2lkZT09PUlpP2w9bi5pbnRlcnNlY3RUcmlhbmdsZShhLG8saSwhMCxzKTpsPW4uaW50ZXJzZWN0VHJpYW5nbGUoaSxvLGEsdC5zaWRlIT09THYscyksbD09PW51bGwpcmV0dXJuIG51bGw7RVYuY29weShzKSxFVi5hcHBseU1hdHJpeDQoZS5tYXRyaXhXb3JsZCk7bGV0IGM9ci5yYXkub3JpZ2luLmRpc3RhbmNlVG8oRVYpO3JldHVybiBjPHIubmVhcnx8Yz5yLmZhcj9udWxsOntkaXN0YW5jZTpjLHBvaW50OkVWLmNsb25lKCksb2JqZWN0OmV9fWZ1bmN0aW9uIFRWKGUsdCxyLG4saSxvLGEscyxsLGMsdSxoKXtSMC5mcm9tQnVmZmVyQXR0cmlidXRlKGksYyksTjAuZnJvbUJ1ZmZlckF0dHJpYnV0ZShpLHUpLEQwLmZyb21CdWZmZXJBdHRyaWJ1dGUoaSxoKTtsZXQgZj1lLm1vcnBoVGFyZ2V0SW5mbHVlbmNlcztpZihvJiZmKXt2Vi5zZXQoMCwwLDApLHhWLnNldCgwLDAsMCksYlYuc2V0KDAsMCwwKTtmb3IobGV0IGQ9MCxnPW8ubGVuZ3RoO2Q8ZztkKyspe2xldCBfPWZbZF0seT1vW2RdO18hPT0wJiYoc3V0LmZyb21CdWZmZXJBdHRyaWJ1dGUoeSxjKSxsdXQuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh5LHUpLGN1dC5mcm9tQnVmZmVyQXR0cmlidXRlKHksaCksYT8odlYuYWRkU2NhbGVkVmVjdG9yKHN1dCxfKSx4Vi5hZGRTY2FsZWRWZWN0b3IobHV0LF8pLGJWLmFkZFNjYWxlZFZlY3RvcihjdXQsXykpOih2Vi5hZGRTY2FsZWRWZWN0b3Ioc3V0LnN1YihSMCksXykseFYuYWRkU2NhbGVkVmVjdG9yKGx1dC5zdWIoTjApLF8pLGJWLmFkZFNjYWxlZFZlY3RvcihjdXQuc3ViKEQwKSxfKSkpfVIwLmFkZCh2ViksTjAuYWRkKHhWKSxEMC5hZGQoYlYpfWUuaXNTa2lubmVkTWVzaCYmKGUuYm9uZVRyYW5zZm9ybShjLFIwKSxlLmJvbmVUcmFuc2Zvcm0odSxOMCksZS5ib25lVHJhbnNmb3JtKGgsRDApKTtsZXQgcD1oZnIoZSx0LHIsbixSMCxOMCxEMCx1dXQpO2lmKHApe3MmJih3Vi5mcm9tQnVmZmVyQXR0cmlidXRlKHMsYyksU1YuZnJvbUJ1ZmZlckF0dHJpYnV0ZShzLHUpLE1WLmZyb21CdWZmZXJBdHRyaWJ1dGUocyxoKSxwLnV2PWFpLmdldFVWKHV1dCxSMCxOMCxEMCx3VixTVixNVixuZXcgTHQpKSxsJiYod1YuZnJvbUJ1ZmZlckF0dHJpYnV0ZShsLGMpLFNWLmZyb21CdWZmZXJBdHRyaWJ1dGUobCx1KSxNVi5mcm9tQnVmZmVyQXR0cmlidXRlKGwsaCkscC51djI9YWkuZ2V0VVYodXV0LFIwLE4wLEQwLHdWLFNWLE1WLG5ldyBMdCkpO2xldCBkPXthOmMsYjp1LGM6aCxub3JtYWw6bmV3IGosbWF0ZXJpYWxJbmRleDowfTthaS5nZXROb3JtYWwoUjAsTjAsRDAsZC5ub3JtYWwpLHAuZmFjZT1kfXJldHVybiBwfXZhciBRZj1jbGFzcyBleHRlbmRzIFBle2NvbnN0cnVjdG9yKHQ9MSxyPTEsbj0xLGk9MSxvPTEsYT0xKXtzdXBlcigpLHRoaXMudHlwZT0iQm94R2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17d2lkdGg6dCxoZWlnaHQ6cixkZXB0aDpuLHdpZHRoU2VnbWVudHM6aSxoZWlnaHRTZWdtZW50czpvLGRlcHRoU2VnbWVudHM6YX07bGV0IHM9dGhpcztpPU1hdGguZmxvb3IoaSksbz1NYXRoLmZsb29yKG8pLGE9TWF0aC5mbG9vcihhKTtsZXQgbD1bXSxjPVtdLHU9W10saD1bXSxmPTAscD0wO2QoInoiLCJ5IiwieCIsLTEsLTEsbixyLHQsYSxvLDApLGQoInoiLCJ5IiwieCIsMSwtMSxuLHIsLXQsYSxvLDEpLGQoIngiLCJ6IiwieSIsMSwxLHQsbixyLGksYSwyKSxkKCJ4IiwieiIsInkiLDEsLTEsdCxuLC1yLGksYSwzKSxkKCJ4IiwieSIsInoiLDEsLTEsdCxyLG4saSxvLDQpLGQoIngiLCJ5IiwieiIsLTEsLTEsdCxyLC1uLGksbyw1KSx0aGlzLnNldEluZGV4KGwpLHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IHhlKGMsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG5ldyB4ZSh1LDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyB4ZShoLDIpKTtmdW5jdGlvbiBkKGcsXyx5LHgsYixTLEMsUCxrLE8sRCl7bGV0IEI9Uy9rLEk9Qy9PLEw9Uy8yLFI9Qy8yLEY9UC8yLHo9aysxLFU9TysxLFc9MCxaPTAscnQ9bmV3IGo7Zm9yKGxldCBvdD0wO290PFU7b3QrKyl7bGV0IHN0PW90KkktUjtmb3IobGV0IFN0PTA7U3Q8ejtTdCsrKXtsZXQgYnQ9U3QqQi1MO3J0W2ddPWJ0KngscnRbX109c3QqYixydFt5XT1GLGMucHVzaChydC54LHJ0LnkscnQueikscnRbZ109MCxydFtfXT0wLHJ0W3ldPVA+MD8xOi0xLHUucHVzaChydC54LHJ0LnkscnQueiksaC5wdXNoKFN0L2spLGgucHVzaCgxLW90L08pLFcrPTF9fWZvcihsZXQgb3Q9MDtvdDxPO290KyspZm9yKGxldCBzdD0wO3N0PGs7c3QrKyl7bGV0IFN0PWYrc3QreipvdCxidD1mK3N0K3oqKG90KzEpLE10PWYrKHN0KzEpK3oqKG90KzEpLGx0PWYrKHN0KzEpK3oqb3Q7bC5wdXNoKFN0LGJ0LGx0KSxsLnB1c2goYnQsTXQsbHQpLForPTZ9cy5hZGRHcm91cChwLFosRCkscCs9WixmKz1XfX1zdGF0aWMgZnJvbUpTT04odCl7cmV0dXJuIG5ldyBRZih0LndpZHRoLHQuaGVpZ2h0LHQuZGVwdGgsdC53aWR0aFNlZ21lbnRzLHQuaGVpZ2h0U2VnbWVudHMsdC5kZXB0aFNlZ21lbnRzKX19O2Z1bmN0aW9uIFozKGUpe2xldCB0PXt9O2ZvcihsZXQgciBpbiBlKXt0W3JdPXt9O2ZvcihsZXQgbiBpbiBlW3JdKXtsZXQgaT1lW3JdW25dO2kmJihpLmlzQ29sb3J8fGkuaXNNYXRyaXgzfHxpLmlzTWF0cml4NHx8aS5pc1ZlY3RvcjJ8fGkuaXNWZWN0b3IzfHxpLmlzVmVjdG9yNHx8aS5pc1RleHR1cmV8fGkuaXNRdWF0ZXJuaW9uKT90W3JdW25dPWkuY2xvbmUoKTpBcnJheS5pc0FycmF5KGkpP3Rbcl1bbl09aS5zbGljZSgpOnRbcl1bbl09aX19cmV0dXJuIHR9ZnVuY3Rpb24gVGEoZSl7bGV0IHQ9e307Zm9yKGxldCByPTA7cjxlLmxlbmd0aDtyKyspe2xldCBuPVozKGVbcl0pO2ZvcihsZXQgaSBpbiBuKXRbaV09bltpXX1yZXR1cm4gdH12YXIgT2ZlPXtjbG9uZTpaMyxtZXJnZTpUYX0sZmZyPWB2b2lkIG1haW4oKSB7CglnbF9Qb3NpdGlvbiA9IHByb2plY3Rpb25NYXRyaXggKiBtb2RlbFZpZXdNYXRyaXggKiB2ZWM0KCBwb3NpdGlvbiwgMS4wICk7Cn1gLHBmcj1gdm9pZCBtYWluKCkgewoJZ2xfRnJhZ0NvbG9yID0gdmVjNCggMS4wLCAwLjAsIDAuMCwgMS4wICk7Cn1gLGxoPWNsYXNzIGV4dGVuZHMgcWl7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9IlNoYWRlck1hdGVyaWFsIix0aGlzLmRlZmluZXM9e30sdGhpcy51bmlmb3Jtcz17fSx0aGlzLnZlcnRleFNoYWRlcj1mZnIsdGhpcy5mcmFnbWVudFNoYWRlcj1wZnIsdGhpcy5saW5ld2lkdGg9MSx0aGlzLndpcmVmcmFtZT0hMSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD0xLHRoaXMuZm9nPSExLHRoaXMubGlnaHRzPSExLHRoaXMuY2xpcHBpbmc9ITEsdGhpcy5leHRlbnNpb25zPXtkZXJpdmF0aXZlczohMSxmcmFnRGVwdGg6ITEsZHJhd0J1ZmZlcnM6ITEsc2hhZGVyVGV4dHVyZUxPRDohMX0sdGhpcy5kZWZhdWx0QXR0cmlidXRlVmFsdWVzPXtjb2xvcjpbMSwxLDFdLHV2OlswLDBdLHV2MjpbMCwwXX0sdGhpcy5pbmRleDBBdHRyaWJ1dGVOYW1lPXZvaWQgMCx0aGlzLnVuaWZvcm1zTmVlZFVwZGF0ZT0hMSx0aGlzLmdsc2xWZXJzaW9uPW51bGwsdCE9PXZvaWQgMCYmKHQuYXR0cmlidXRlcyE9PXZvaWQgMCYmY29uc29sZS5lcnJvcigiVEhSRUUuU2hhZGVyTWF0ZXJpYWw6IGF0dHJpYnV0ZXMgc2hvdWxkIG5vdyBiZSBkZWZpbmVkIGluIFRIUkVFLkJ1ZmZlckdlb21ldHJ5IGluc3RlYWQuIiksdGhpcy5zZXRWYWx1ZXModCkpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5mcmFnbWVudFNoYWRlcj10LmZyYWdtZW50U2hhZGVyLHRoaXMudmVydGV4U2hhZGVyPXQudmVydGV4U2hhZGVyLHRoaXMudW5pZm9ybXM9WjModC51bmlmb3JtcyksdGhpcy5kZWZpbmVzPU9iamVjdC5hc3NpZ24oe30sdC5kZWZpbmVzKSx0aGlzLndpcmVmcmFtZT10LndpcmVmcmFtZSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD10LndpcmVmcmFtZUxpbmV3aWR0aCx0aGlzLmxpZ2h0cz10LmxpZ2h0cyx0aGlzLmNsaXBwaW5nPXQuY2xpcHBpbmcsdGhpcy5leHRlbnNpb25zPU9iamVjdC5hc3NpZ24oe30sdC5leHRlbnNpb25zKSx0aGlzLmdsc2xWZXJzaW9uPXQuZ2xzbFZlcnNpb24sdGhpc310b0pTT04odCl7bGV0IHI9c3VwZXIudG9KU09OKHQpO3IuZ2xzbFZlcnNpb249dGhpcy5nbHNsVmVyc2lvbixyLnVuaWZvcm1zPXt9O2ZvcihsZXQgaSBpbiB0aGlzLnVuaWZvcm1zKXtsZXQgYT10aGlzLnVuaWZvcm1zW2ldLnZhbHVlO2EmJmEuaXNUZXh0dXJlP3IudW5pZm9ybXNbaV09e3R5cGU6InQiLHZhbHVlOmEudG9KU09OKHQpLnV1aWR9OmEmJmEuaXNDb2xvcj9yLnVuaWZvcm1zW2ldPXt0eXBlOiJjIix2YWx1ZTphLmdldEhleCgpfTphJiZhLmlzVmVjdG9yMj9yLnVuaWZvcm1zW2ldPXt0eXBlOiJ2MiIsdmFsdWU6YS50b0FycmF5KCl9OmEmJmEuaXNWZWN0b3IzP3IudW5pZm9ybXNbaV09e3R5cGU6InYzIix2YWx1ZTphLnRvQXJyYXkoKX06YSYmYS5pc1ZlY3RvcjQ/ci51bmlmb3Jtc1tpXT17dHlwZToidjQiLHZhbHVlOmEudG9BcnJheSgpfTphJiZhLmlzTWF0cml4Mz9yLnVuaWZvcm1zW2ldPXt0eXBlOiJtMyIsdmFsdWU6YS50b0FycmF5KCl9OmEmJmEuaXNNYXRyaXg0P3IudW5pZm9ybXNbaV09e3R5cGU6Im00Iix2YWx1ZTphLnRvQXJyYXkoKX06ci51bmlmb3Jtc1tpXT17dmFsdWU6YX19T2JqZWN0LmtleXModGhpcy5kZWZpbmVzKS5sZW5ndGg+MCYmKHIuZGVmaW5lcz10aGlzLmRlZmluZXMpLHIudmVydGV4U2hhZGVyPXRoaXMudmVydGV4U2hhZGVyLHIuZnJhZ21lbnRTaGFkZXI9dGhpcy5mcmFnbWVudFNoYWRlcjtsZXQgbj17fTtmb3IobGV0IGkgaW4gdGhpcy5leHRlbnNpb25zKXRoaXMuZXh0ZW5zaW9uc1tpXT09PSEwJiYobltpXT0hMCk7cmV0dXJuIE9iamVjdC5rZXlzKG4pLmxlbmd0aD4wJiYoci5leHRlbnNpb25zPW4pLHJ9fTtsaC5wcm90b3R5cGUuaXNTaGFkZXJNYXRlcmlhbD0hMDt2YXIgUnY9Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy50eXBlPSJDYW1lcmEiLHRoaXMubWF0cml4V29ybGRJbnZlcnNlPW5ldyBNZSx0aGlzLnByb2plY3Rpb25NYXRyaXg9bmV3IE1lLHRoaXMucHJvamVjdGlvbk1hdHJpeEludmVyc2U9bmV3IE1lfWNvcHkodCxyKXtyZXR1cm4gc3VwZXIuY29weSh0LHIpLHRoaXMubWF0cml4V29ybGRJbnZlcnNlLmNvcHkodC5tYXRyaXhXb3JsZEludmVyc2UpLHRoaXMucHJvamVjdGlvbk1hdHJpeC5jb3B5KHQucHJvamVjdGlvbk1hdHJpeCksdGhpcy5wcm9qZWN0aW9uTWF0cml4SW52ZXJzZS5jb3B5KHQucHJvamVjdGlvbk1hdHJpeEludmVyc2UpLHRoaXN9Z2V0V29ybGREaXJlY3Rpb24odCl7dGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSk7bGV0IHI9dGhpcy5tYXRyaXhXb3JsZC5lbGVtZW50cztyZXR1cm4gdC5zZXQoLXJbOF0sLXJbOV0sLXJbMTBdKS5ub3JtYWxpemUoKX11cGRhdGVNYXRyaXhXb3JsZCh0KXtzdXBlci51cGRhdGVNYXRyaXhXb3JsZCh0KSx0aGlzLm1hdHJpeFdvcmxkSW52ZXJzZS5jb3B5KHRoaXMubWF0cml4V29ybGQpLmludmVydCgpfXVwZGF0ZVdvcmxkTWF0cml4KHQscil7c3VwZXIudXBkYXRlV29ybGRNYXRyaXgodCxyKSx0aGlzLm1hdHJpeFdvcmxkSW52ZXJzZS5jb3B5KHRoaXMubWF0cml4V29ybGQpLmludmVydCgpfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX19O1J2LnByb3RvdHlwZS5pc0NhbWVyYT0hMDt2YXIgVWk9Y2xhc3MgZXh0ZW5kcyBSdntjb25zdHJ1Y3Rvcih0PTUwLHI9MSxuPS4xLGk9MmUzKXtzdXBlcigpLHRoaXMudHlwZT0iUGVyc3BlY3RpdmVDYW1lcmEiLHRoaXMuZm92PXQsdGhpcy56b29tPTEsdGhpcy5uZWFyPW4sdGhpcy5mYXI9aSx0aGlzLmZvY3VzPTEwLHRoaXMuYXNwZWN0PXIsdGhpcy52aWV3PW51bGwsdGhpcy5maWxtR2F1Z2U9MzUsdGhpcy5maWxtT2Zmc2V0PTAsdGhpcy51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl9Y29weSh0LHIpe3JldHVybiBzdXBlci5jb3B5KHQsciksdGhpcy5mb3Y9dC5mb3YsdGhpcy56b29tPXQuem9vbSx0aGlzLm5lYXI9dC5uZWFyLHRoaXMuZmFyPXQuZmFyLHRoaXMuZm9jdXM9dC5mb2N1cyx0aGlzLmFzcGVjdD10LmFzcGVjdCx0aGlzLnZpZXc9dC52aWV3PT09bnVsbD9udWxsOk9iamVjdC5hc3NpZ24oe30sdC52aWV3KSx0aGlzLmZpbG1HYXVnZT10LmZpbG1HYXVnZSx0aGlzLmZpbG1PZmZzZXQ9dC5maWxtT2Zmc2V0LHRoaXN9c2V0Rm9jYWxMZW5ndGgodCl7bGV0IHI9LjUqdGhpcy5nZXRGaWxtSGVpZ2h0KCkvdDt0aGlzLmZvdj1KUCoyKk1hdGguYXRhbihyKSx0aGlzLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX1nZXRGb2NhbExlbmd0aCgpe2xldCB0PU1hdGgudGFuKFB2Ki41KnRoaXMuZm92KTtyZXR1cm4gLjUqdGhpcy5nZXRGaWxtSGVpZ2h0KCkvdH1nZXRFZmZlY3RpdmVGT1YoKXtyZXR1cm4gSlAqMipNYXRoLmF0YW4oTWF0aC50YW4oUHYqLjUqdGhpcy5mb3YpL3RoaXMuem9vbSl9Z2V0RmlsbVdpZHRoKCl7cmV0dXJuIHRoaXMuZmlsbUdhdWdlKk1hdGgubWluKHRoaXMuYXNwZWN0LDEpfWdldEZpbG1IZWlnaHQoKXtyZXR1cm4gdGhpcy5maWxtR2F1Z2UvTWF0aC5tYXgodGhpcy5hc3BlY3QsMSl9c2V0Vmlld09mZnNldCh0LHIsbixpLG8sYSl7dGhpcy5hc3BlY3Q9dC9yLHRoaXMudmlldz09PW51bGwmJih0aGlzLnZpZXc9e2VuYWJsZWQ6ITAsZnVsbFdpZHRoOjEsZnVsbEhlaWdodDoxLG9mZnNldFg6MCxvZmZzZXRZOjAsd2lkdGg6MSxoZWlnaHQ6MX0pLHRoaXMudmlldy5lbmFibGVkPSEwLHRoaXMudmlldy5mdWxsV2lkdGg9dCx0aGlzLnZpZXcuZnVsbEhlaWdodD1yLHRoaXMudmlldy5vZmZzZXRYPW4sdGhpcy52aWV3Lm9mZnNldFk9aSx0aGlzLnZpZXcud2lkdGg9byx0aGlzLnZpZXcuaGVpZ2h0PWEsdGhpcy51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl9Y2xlYXJWaWV3T2Zmc2V0KCl7dGhpcy52aWV3IT09bnVsbCYmKHRoaXMudmlldy5lbmFibGVkPSExKSx0aGlzLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX11cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl7bGV0IHQ9dGhpcy5uZWFyLHI9dCpNYXRoLnRhbihQdiouNSp0aGlzLmZvdikvdGhpcy56b29tLG49MipyLGk9dGhpcy5hc3BlY3QqbixvPS0uNSppLGE9dGhpcy52aWV3O2lmKHRoaXMudmlldyE9PW51bGwmJnRoaXMudmlldy5lbmFibGVkKXtsZXQgbD1hLmZ1bGxXaWR0aCxjPWEuZnVsbEhlaWdodDtvKz1hLm9mZnNldFgqaS9sLHItPWEub2Zmc2V0WSpuL2MsaSo9YS53aWR0aC9sLG4qPWEuaGVpZ2h0L2N9bGV0IHM9dGhpcy5maWxtT2Zmc2V0O3MhPT0wJiYobys9dCpzL3RoaXMuZ2V0RmlsbVdpZHRoKCkpLHRoaXMucHJvamVjdGlvbk1hdHJpeC5tYWtlUGVyc3BlY3RpdmUobyxvK2kscixyLW4sdCx0aGlzLmZhciksdGhpcy5wcm9qZWN0aW9uTWF0cml4SW52ZXJzZS5jb3B5KHRoaXMucHJvamVjdGlvbk1hdHJpeCkuaW52ZXJ0KCl9dG9KU09OKHQpe2xldCByPXN1cGVyLnRvSlNPTih0KTtyZXR1cm4gci5vYmplY3QuZm92PXRoaXMuZm92LHIub2JqZWN0Lnpvb209dGhpcy56b29tLHIub2JqZWN0Lm5lYXI9dGhpcy5uZWFyLHIub2JqZWN0LmZhcj10aGlzLmZhcixyLm9iamVjdC5mb2N1cz10aGlzLmZvY3VzLHIub2JqZWN0LmFzcGVjdD10aGlzLmFzcGVjdCx0aGlzLnZpZXchPT1udWxsJiYoci5vYmplY3Qudmlldz1PYmplY3QuYXNzaWduKHt9LHRoaXMudmlldykpLHIub2JqZWN0LmZpbG1HYXVnZT10aGlzLmZpbG1HYXVnZSxyLm9iamVjdC5maWxtT2Zmc2V0PXRoaXMuZmlsbU9mZnNldCxyfX07VWkucHJvdG90eXBlLmlzUGVyc3BlY3RpdmVDYW1lcmE9ITA7dmFyIFAzPTkwLEkzPTEsSjM9Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcih0LHIsbil7aWYoc3VwZXIoKSx0aGlzLnR5cGU9IkN1YmVDYW1lcmEiLG4uaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQhPT0hMCl7Y29uc29sZS5lcnJvcigiVEhSRUUuQ3ViZUNhbWVyYTogVGhlIGNvbnN0cnVjdG9yIG5vdyBleHBlY3RzIGFuIGluc3RhbmNlIG9mIFdlYkdMQ3ViZVJlbmRlclRhcmdldCBhcyB0aGlyZCBwYXJhbWV0ZXIuIik7cmV0dXJufXRoaXMucmVuZGVyVGFyZ2V0PW47bGV0IGk9bmV3IFVpKFAzLEkzLHQscik7aS5sYXllcnM9dGhpcy5sYXllcnMsaS51cC5zZXQoMCwtMSwwKSxpLmxvb2tBdChuZXcgaigxLDAsMCkpLHRoaXMuYWRkKGkpO2xldCBvPW5ldyBVaShQMyxJMyx0LHIpO28ubGF5ZXJzPXRoaXMubGF5ZXJzLG8udXAuc2V0KDAsLTEsMCksby5sb29rQXQobmV3IGooLTEsMCwwKSksdGhpcy5hZGQobyk7bGV0IGE9bmV3IFVpKFAzLEkzLHQscik7YS5sYXllcnM9dGhpcy5sYXllcnMsYS51cC5zZXQoMCwwLDEpLGEubG9va0F0KG5ldyBqKDAsMSwwKSksdGhpcy5hZGQoYSk7bGV0IHM9bmV3IFVpKFAzLEkzLHQscik7cy5sYXllcnM9dGhpcy5sYXllcnMscy51cC5zZXQoMCwwLC0xKSxzLmxvb2tBdChuZXcgaigwLC0xLDApKSx0aGlzLmFkZChzKTtsZXQgbD1uZXcgVWkoUDMsSTMsdCxyKTtsLmxheWVycz10aGlzLmxheWVycyxsLnVwLnNldCgwLC0xLDApLGwubG9va0F0KG5ldyBqKDAsMCwxKSksdGhpcy5hZGQobCk7bGV0IGM9bmV3IFVpKFAzLEkzLHQscik7Yy5sYXllcnM9dGhpcy5sYXllcnMsYy51cC5zZXQoMCwtMSwwKSxjLmxvb2tBdChuZXcgaigwLDAsLTEpKSx0aGlzLmFkZChjKX11cGRhdGUodCxyKXt0aGlzLnBhcmVudD09PW51bGwmJnRoaXMudXBkYXRlTWF0cml4V29ybGQoKTtsZXQgbj10aGlzLnJlbmRlclRhcmdldCxbaSxvLGEscyxsLGNdPXRoaXMuY2hpbGRyZW4sdT10LnhyLmVuYWJsZWQsaD10LmdldFJlbmRlclRhcmdldCgpO3QueHIuZW5hYmxlZD0hMTtsZXQgZj1uLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzO24udGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM9ITEsdC5zZXRSZW5kZXJUYXJnZXQobiwwKSx0LnJlbmRlcihyLGkpLHQuc2V0UmVuZGVyVGFyZ2V0KG4sMSksdC5yZW5kZXIocixvKSx0LnNldFJlbmRlclRhcmdldChuLDIpLHQucmVuZGVyKHIsYSksdC5zZXRSZW5kZXJUYXJnZXQobiwzKSx0LnJlbmRlcihyLHMpLHQuc2V0UmVuZGVyVGFyZ2V0KG4sNCksdC5yZW5kZXIocixsKSxuLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzPWYsdC5zZXRSZW5kZXJUYXJnZXQobiw1KSx0LnJlbmRlcihyLGMpLHQuc2V0UmVuZGVyVGFyZ2V0KGgpLHQueHIuZW5hYmxlZD11LG4udGV4dHVyZS5uZWVkc1BNUkVNVXBkYXRlPSEwfX0sSDA9Y2xhc3MgZXh0ZW5kcyB4aXtjb25zdHJ1Y3Rvcih0LHIsbixpLG8sYSxzLGwsYyx1KXt0PXQhPT12b2lkIDA/dDpbXSxyPXIhPT12b2lkIDA/cjpueCxzdXBlcih0LHIsbixpLG8sYSxzLGwsYyx1KSx0aGlzLmZsaXBZPSExfWdldCBpbWFnZXMoKXtyZXR1cm4gdGhpcy5pbWFnZX1zZXQgaW1hZ2VzKHQpe3RoaXMuaW1hZ2U9dH19O0gwLnByb3RvdHlwZS5pc0N1YmVUZXh0dXJlPSEwO3ZhciBRMz1jbGFzcyBleHRlbmRzIHVze2NvbnN0cnVjdG9yKHQscixuKXtOdW1iZXIuaXNJbnRlZ2VyKHIpJiYoY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTEN1YmVSZW5kZXJUYXJnZXQ6IGNvbnN0cnVjdG9yIHNpZ25hdHVyZSBpcyBub3cgV2ViR0xDdWJlUmVuZGVyVGFyZ2V0KCBzaXplLCBvcHRpb25zICkiKSxyPW4pLHN1cGVyKHQsdCxyKSxyPXJ8fHt9LHRoaXMudGV4dHVyZT1uZXcgSDAodm9pZCAwLHIubWFwcGluZyxyLndyYXBTLHIud3JhcFQsci5tYWdGaWx0ZXIsci5taW5GaWx0ZXIsci5mb3JtYXQsci50eXBlLHIuYW5pc290cm9weSxyLmVuY29kaW5nKSx0aGlzLnRleHR1cmUuaXNSZW5kZXJUYXJnZXRUZXh0dXJlPSEwLHRoaXMudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM9ci5nZW5lcmF0ZU1pcG1hcHMhPT12b2lkIDA/ci5nZW5lcmF0ZU1pcG1hcHM6ITEsdGhpcy50ZXh0dXJlLm1pbkZpbHRlcj1yLm1pbkZpbHRlciE9PXZvaWQgMD9yLm1pbkZpbHRlcjpvaX1mcm9tRXF1aXJlY3Rhbmd1bGFyVGV4dHVyZSh0LHIpe3RoaXMudGV4dHVyZS50eXBlPXIudHlwZSx0aGlzLnRleHR1cmUuZm9ybWF0PVFvLHRoaXMudGV4dHVyZS5lbmNvZGluZz1yLmVuY29kaW5nLHRoaXMudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM9ci5nZW5lcmF0ZU1pcG1hcHMsdGhpcy50ZXh0dXJlLm1pbkZpbHRlcj1yLm1pbkZpbHRlcix0aGlzLnRleHR1cmUubWFnRmlsdGVyPXIubWFnRmlsdGVyO2xldCBuPXt1bmlmb3Jtczp7dEVxdWlyZWN0Ont2YWx1ZTpudWxsfX0sdmVydGV4U2hhZGVyOmAKCgkJCQl2YXJ5aW5nIHZlYzMgdldvcmxkRGlyZWN0aW9uOwoKCQkJCXZlYzMgdHJhbnNmb3JtRGlyZWN0aW9uKCBpbiB2ZWMzIGRpciwgaW4gbWF0NCBtYXRyaXggKSB7CgoJCQkJCXJldHVybiBub3JtYWxpemUoICggbWF0cml4ICogdmVjNCggZGlyLCAwLjAgKSApLnh5eiApOwoKCQkJCX0KCgkJCQl2b2lkIG1haW4oKSB7CgoJCQkJCXZXb3JsZERpcmVjdGlvbiA9IHRyYW5zZm9ybURpcmVjdGlvbiggcG9zaXRpb24sIG1vZGVsTWF0cml4ICk7CgoJCQkJCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkJCQkJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoKCQkJCX0KCQkJYCxmcmFnbWVudFNoYWRlcjpgCgoJCQkJdW5pZm9ybSBzYW1wbGVyMkQgdEVxdWlyZWN0OwoKCQkJCXZhcnlpbmcgdmVjMyB2V29ybGREaXJlY3Rpb247CgoJCQkJI2luY2x1ZGUgPGNvbW1vbj4KCgkJCQl2b2lkIG1haW4oKSB7CgoJCQkJCXZlYzMgZGlyZWN0aW9uID0gbm9ybWFsaXplKCB2V29ybGREaXJlY3Rpb24gKTsKCgkJCQkJdmVjMiBzYW1wbGVVViA9IGVxdWlyZWN0VXYoIGRpcmVjdGlvbiApOwoKCQkJCQlnbF9GcmFnQ29sb3IgPSB0ZXh0dXJlMkQoIHRFcXVpcmVjdCwgc2FtcGxlVVYgKTsKCgkJCQl9CgkJCWB9LGk9bmV3IFFmKDUsNSw1KSxvPW5ldyBsaCh7bmFtZToiQ3ViZW1hcEZyb21FcXVpcmVjdCIsdW5pZm9ybXM6WjMobi51bmlmb3JtcyksdmVydGV4U2hhZGVyOm4udmVydGV4U2hhZGVyLGZyYWdtZW50U2hhZGVyOm4uZnJhZ21lbnRTaGFkZXIsc2lkZTpJaSxibGVuZGluZzokZH0pO28udW5pZm9ybXMudEVxdWlyZWN0LnZhbHVlPXI7bGV0IGE9bmV3IGVpKGksbykscz1yLm1pbkZpbHRlcjtyZXR1cm4gci5taW5GaWx0ZXI9PT1veCYmKHIubWluRmlsdGVyPW9pKSxuZXcgSjMoMSwxMCx0aGlzKS51cGRhdGUodCxhKSxyLm1pbkZpbHRlcj1zLGEuZ2VvbWV0cnkuZGlzcG9zZSgpLGEubWF0ZXJpYWwuZGlzcG9zZSgpLHRoaXN9Y2xlYXIodCxyLG4saSl7bGV0IG89dC5nZXRSZW5kZXJUYXJnZXQoKTtmb3IobGV0IGE9MDthPDY7YSsrKXQuc2V0UmVuZGVyVGFyZ2V0KHRoaXMsYSksdC5jbGVhcihyLG4saSk7dC5zZXRSZW5kZXJUYXJnZXQobyl9fTtRMy5wcm90b3R5cGUuaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQ9ITA7dmFyIGh1dD1uZXcgaixkZnI9bmV3IGosbWZyPW5ldyBraSwkYz1jbGFzc3tjb25zdHJ1Y3Rvcih0PW5ldyBqKDEsMCwwKSxyPTApe3RoaXMubm9ybWFsPXQsdGhpcy5jb25zdGFudD1yfXNldCh0LHIpe3JldHVybiB0aGlzLm5vcm1hbC5jb3B5KHQpLHRoaXMuY29uc3RhbnQ9cix0aGlzfXNldENvbXBvbmVudHModCxyLG4saSl7cmV0dXJuIHRoaXMubm9ybWFsLnNldCh0LHIsbiksdGhpcy5jb25zdGFudD1pLHRoaXN9c2V0RnJvbU5vcm1hbEFuZENvcGxhbmFyUG9pbnQodCxyKXtyZXR1cm4gdGhpcy5ub3JtYWwuY29weSh0KSx0aGlzLmNvbnN0YW50PS1yLmRvdCh0aGlzLm5vcm1hbCksdGhpc31zZXRGcm9tQ29wbGFuYXJQb2ludHModCxyLG4pe2xldCBpPWh1dC5zdWJWZWN0b3JzKG4scikuY3Jvc3MoZGZyLnN1YlZlY3RvcnModCxyKSkubm9ybWFsaXplKCk7cmV0dXJuIHRoaXMuc2V0RnJvbU5vcm1hbEFuZENvcGxhbmFyUG9pbnQoaSx0KSx0aGlzfWNvcHkodCl7cmV0dXJuIHRoaXMubm9ybWFsLmNvcHkodC5ub3JtYWwpLHRoaXMuY29uc3RhbnQ9dC5jb25zdGFudCx0aGlzfW5vcm1hbGl6ZSgpe2xldCB0PTEvdGhpcy5ub3JtYWwubGVuZ3RoKCk7cmV0dXJuIHRoaXMubm9ybWFsLm11bHRpcGx5U2NhbGFyKHQpLHRoaXMuY29uc3RhbnQqPXQsdGhpc31uZWdhdGUoKXtyZXR1cm4gdGhpcy5jb25zdGFudCo9LTEsdGhpcy5ub3JtYWwubmVnYXRlKCksdGhpc31kaXN0YW5jZVRvUG9pbnQodCl7cmV0dXJuIHRoaXMubm9ybWFsLmRvdCh0KSt0aGlzLmNvbnN0YW50fWRpc3RhbmNlVG9TcGhlcmUodCl7cmV0dXJuIHRoaXMuZGlzdGFuY2VUb1BvaW50KHQuY2VudGVyKS10LnJhZGl1c31wcm9qZWN0UG9pbnQodCxyKXtyZXR1cm4gci5jb3B5KHRoaXMubm9ybWFsKS5tdWx0aXBseVNjYWxhcigtdGhpcy5kaXN0YW5jZVRvUG9pbnQodCkpLmFkZCh0KX1pbnRlcnNlY3RMaW5lKHQscil7bGV0IG49dC5kZWx0YShodXQpLGk9dGhpcy5ub3JtYWwuZG90KG4pO2lmKGk9PT0wKXJldHVybiB0aGlzLmRpc3RhbmNlVG9Qb2ludCh0LnN0YXJ0KT09PTA/ci5jb3B5KHQuc3RhcnQpOm51bGw7bGV0IG89LSh0LnN0YXJ0LmRvdCh0aGlzLm5vcm1hbCkrdGhpcy5jb25zdGFudCkvaTtyZXR1cm4gbzwwfHxvPjE/bnVsbDpyLmNvcHkobikubXVsdGlwbHlTY2FsYXIobykuYWRkKHQuc3RhcnQpfWludGVyc2VjdHNMaW5lKHQpe2xldCByPXRoaXMuZGlzdGFuY2VUb1BvaW50KHQuc3RhcnQpLG49dGhpcy5kaXN0YW5jZVRvUG9pbnQodC5lbmQpO3JldHVybiByPDAmJm4+MHx8bjwwJiZyPjB9aW50ZXJzZWN0c0JveCh0KXtyZXR1cm4gdC5pbnRlcnNlY3RzUGxhbmUodGhpcyl9aW50ZXJzZWN0c1NwaGVyZSh0KXtyZXR1cm4gdC5pbnRlcnNlY3RzUGxhbmUodGhpcyl9Y29wbGFuYXJQb2ludCh0KXtyZXR1cm4gdC5jb3B5KHRoaXMubm9ybWFsKS5tdWx0aXBseVNjYWxhcigtdGhpcy5jb25zdGFudCl9YXBwbHlNYXRyaXg0KHQscil7bGV0IG49cnx8bWZyLmdldE5vcm1hbE1hdHJpeCh0KSxpPXRoaXMuY29wbGFuYXJQb2ludChodXQpLmFwcGx5TWF0cml4NCh0KSxvPXRoaXMubm9ybWFsLmFwcGx5TWF0cml4MyhuKS5ub3JtYWxpemUoKTtyZXR1cm4gdGhpcy5jb25zdGFudD0taS5kb3QobyksdGhpc310cmFuc2xhdGUodCl7cmV0dXJuIHRoaXMuY29uc3RhbnQtPXQuZG90KHRoaXMubm9ybWFsKSx0aGlzfWVxdWFscyh0KXtyZXR1cm4gdC5ub3JtYWwuZXF1YWxzKHRoaXMubm9ybWFsKSYmdC5jb25zdGFudD09PXRoaXMuY29uc3RhbnR9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfX07JGMucHJvdG90eXBlLmlzUGxhbmU9ITA7dmFyIEwzPW5ldyBaZixDVj1uZXcgaixOdj1jbGFzc3tjb25zdHJ1Y3Rvcih0PW5ldyAkYyxyPW5ldyAkYyxuPW5ldyAkYyxpPW5ldyAkYyxvPW5ldyAkYyxhPW5ldyAkYyl7dGhpcy5wbGFuZXM9W3QscixuLGksbyxhXX1zZXQodCxyLG4saSxvLGEpe2xldCBzPXRoaXMucGxhbmVzO3JldHVybiBzWzBdLmNvcHkodCksc1sxXS5jb3B5KHIpLHNbMl0uY29weShuKSxzWzNdLmNvcHkoaSksc1s0XS5jb3B5KG8pLHNbNV0uY29weShhKSx0aGlzfWNvcHkodCl7bGV0IHI9dGhpcy5wbGFuZXM7Zm9yKGxldCBuPTA7bjw2O24rKylyW25dLmNvcHkodC5wbGFuZXNbbl0pO3JldHVybiB0aGlzfXNldEZyb21Qcm9qZWN0aW9uTWF0cml4KHQpe2xldCByPXRoaXMucGxhbmVzLG49dC5lbGVtZW50cyxpPW5bMF0sbz1uWzFdLGE9blsyXSxzPW5bM10sbD1uWzRdLGM9bls1XSx1PW5bNl0saD1uWzddLGY9bls4XSxwPW5bOV0sZD1uWzEwXSxnPW5bMTFdLF89blsxMl0seT1uWzEzXSx4PW5bMTRdLGI9blsxNV07cmV0dXJuIHJbMF0uc2V0Q29tcG9uZW50cyhzLWksaC1sLGctZixiLV8pLm5vcm1hbGl6ZSgpLHJbMV0uc2V0Q29tcG9uZW50cyhzK2ksaCtsLGcrZixiK18pLm5vcm1hbGl6ZSgpLHJbMl0uc2V0Q29tcG9uZW50cyhzK28saCtjLGcrcCxiK3kpLm5vcm1hbGl6ZSgpLHJbM10uc2V0Q29tcG9uZW50cyhzLW8saC1jLGctcCxiLXkpLm5vcm1hbGl6ZSgpLHJbNF0uc2V0Q29tcG9uZW50cyhzLWEsaC11LGctZCxiLXgpLm5vcm1hbGl6ZSgpLHJbNV0uc2V0Q29tcG9uZW50cyhzK2EsaCt1LGcrZCxiK3gpLm5vcm1hbGl6ZSgpLHRoaXN9aW50ZXJzZWN0c09iamVjdCh0KXtsZXQgcj10Lmdlb21ldHJ5O3JldHVybiByLmJvdW5kaW5nU3BoZXJlPT09bnVsbCYmci5jb21wdXRlQm91bmRpbmdTcGhlcmUoKSxMMy5jb3B5KHIuYm91bmRpbmdTcGhlcmUpLmFwcGx5TWF0cml4NCh0Lm1hdHJpeFdvcmxkKSx0aGlzLmludGVyc2VjdHNTcGhlcmUoTDMpfWludGVyc2VjdHNTcHJpdGUodCl7cmV0dXJuIEwzLmNlbnRlci5zZXQoMCwwLDApLEwzLnJhZGl1cz0uNzA3MTA2NzgxMTg2NTQ3NixMMy5hcHBseU1hdHJpeDQodC5tYXRyaXhXb3JsZCksdGhpcy5pbnRlcnNlY3RzU3BoZXJlKEwzKX1pbnRlcnNlY3RzU3BoZXJlKHQpe2xldCByPXRoaXMucGxhbmVzLG49dC5jZW50ZXIsaT0tdC5yYWRpdXM7Zm9yKGxldCBvPTA7bzw2O28rKylpZihyW29dLmRpc3RhbmNlVG9Qb2ludChuKTxpKXJldHVybiExO3JldHVybiEwfWludGVyc2VjdHNCb3godCl7bGV0IHI9dGhpcy5wbGFuZXM7Zm9yKGxldCBuPTA7bjw2O24rKyl7bGV0IGk9cltuXTtpZihDVi54PWkubm9ybWFsLng+MD90Lm1heC54OnQubWluLngsQ1YueT1pLm5vcm1hbC55PjA/dC5tYXgueTp0Lm1pbi55LENWLno9aS5ub3JtYWwuej4wP3QubWF4Lno6dC5taW4ueixpLmRpc3RhbmNlVG9Qb2ludChDVik8MClyZXR1cm4hMX1yZXR1cm4hMH1jb250YWluc1BvaW50KHQpe2xldCByPXRoaXMucGxhbmVzO2ZvcihsZXQgbj0wO248NjtuKyspaWYocltuXS5kaXN0YW5jZVRvUG9pbnQodCk8MClyZXR1cm4hMTtyZXR1cm4hMH1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyl9fTtmdW5jdGlvbiB6ZmUoKXtsZXQgZT1udWxsLHQ9ITEscj1udWxsLG49bnVsbDtmdW5jdGlvbiBpKG8sYSl7cihvLGEpLG49ZS5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUoaSl9cmV0dXJue3N0YXJ0OmZ1bmN0aW9uKCl7dCE9PSEwJiZyIT09bnVsbCYmKG49ZS5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUoaSksdD0hMCl9LHN0b3A6ZnVuY3Rpb24oKXtlLmNhbmNlbEFuaW1hdGlvbkZyYW1lKG4pLHQ9ITF9LHNldEFuaW1hdGlvbkxvb3A6ZnVuY3Rpb24obyl7cj1vfSxzZXRDb250ZXh0OmZ1bmN0aW9uKG8pe2U9b319fWZ1bmN0aW9uIGdmcihlLHQpe2xldCByPXQuaXNXZWJHTDIsbj1uZXcgV2Vha01hcDtmdW5jdGlvbiBpKGMsdSl7bGV0IGg9Yy5hcnJheSxmPWMudXNhZ2UscD1lLmNyZWF0ZUJ1ZmZlcigpO2UuYmluZEJ1ZmZlcih1LHApLGUuYnVmZmVyRGF0YSh1LGgsZiksYy5vblVwbG9hZENhbGxiYWNrKCk7bGV0IGQ9NTEyNjtyZXR1cm4gaCBpbnN0YW5jZW9mIEZsb2F0MzJBcnJheT9kPTUxMjY6aCBpbnN0YW5jZW9mIEZsb2F0NjRBcnJheT9jb25zb2xlLndhcm4oIlRIUkVFLldlYkdMQXR0cmlidXRlczogVW5zdXBwb3J0ZWQgZGF0YSBidWZmZXIgZm9ybWF0OiBGbG9hdDY0QXJyYXkuIik6aCBpbnN0YW5jZW9mIFVpbnQxNkFycmF5P2MuaXNGbG9hdDE2QnVmZmVyQXR0cmlidXRlP3I/ZD01MTMxOmNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xBdHRyaWJ1dGVzOiBVc2FnZSBvZiBGbG9hdDE2QnVmZmVyQXR0cmlidXRlIHJlcXVpcmVzIFdlYkdMMi4iKTpkPTUxMjM6aCBpbnN0YW5jZW9mIEludDE2QXJyYXk/ZD01MTIyOmggaW5zdGFuY2VvZiBVaW50MzJBcnJheT9kPTUxMjU6aCBpbnN0YW5jZW9mIEludDMyQXJyYXk/ZD01MTI0OmggaW5zdGFuY2VvZiBJbnQ4QXJyYXk/ZD01MTIwOihoIGluc3RhbmNlb2YgVWludDhBcnJheXx8aCBpbnN0YW5jZW9mIFVpbnQ4Q2xhbXBlZEFycmF5KSYmKGQ9NTEyMSkse2J1ZmZlcjpwLHR5cGU6ZCxieXRlc1BlckVsZW1lbnQ6aC5CWVRFU19QRVJfRUxFTUVOVCx2ZXJzaW9uOmMudmVyc2lvbn19ZnVuY3Rpb24gbyhjLHUsaCl7bGV0IGY9dS5hcnJheSxwPXUudXBkYXRlUmFuZ2U7ZS5iaW5kQnVmZmVyKGgsYykscC5jb3VudD09PS0xP2UuYnVmZmVyU3ViRGF0YShoLDAsZik6KHI/ZS5idWZmZXJTdWJEYXRhKGgscC5vZmZzZXQqZi5CWVRFU19QRVJfRUxFTUVOVCxmLHAub2Zmc2V0LHAuY291bnQpOmUuYnVmZmVyU3ViRGF0YShoLHAub2Zmc2V0KmYuQllURVNfUEVSX0VMRU1FTlQsZi5zdWJhcnJheShwLm9mZnNldCxwLm9mZnNldCtwLmNvdW50KSkscC5jb3VudD0tMSl9ZnVuY3Rpb24gYShjKXtyZXR1cm4gYy5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlJiYoYz1jLmRhdGEpLG4uZ2V0KGMpfWZ1bmN0aW9uIHMoYyl7Yy5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlJiYoYz1jLmRhdGEpO2xldCB1PW4uZ2V0KGMpO3UmJihlLmRlbGV0ZUJ1ZmZlcih1LmJ1ZmZlciksbi5kZWxldGUoYykpfWZ1bmN0aW9uIGwoYyx1KXtpZihjLmlzR0xCdWZmZXJBdHRyaWJ1dGUpe2xldCBmPW4uZ2V0KGMpOyghZnx8Zi52ZXJzaW9uPGMudmVyc2lvbikmJm4uc2V0KGMse2J1ZmZlcjpjLmJ1ZmZlcix0eXBlOmMudHlwZSxieXRlc1BlckVsZW1lbnQ6Yy5lbGVtZW50U2l6ZSx2ZXJzaW9uOmMudmVyc2lvbn0pO3JldHVybn1jLmlzSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGUmJihjPWMuZGF0YSk7bGV0IGg9bi5nZXQoYyk7aD09PXZvaWQgMD9uLnNldChjLGkoYyx1KSk6aC52ZXJzaW9uPGMudmVyc2lvbiYmKG8oaC5idWZmZXIsYyx1KSxoLnZlcnNpb249Yy52ZXJzaW9uKX1yZXR1cm57Z2V0OmEscmVtb3ZlOnMsdXBkYXRlOmx9fXZhciBWMD1jbGFzcyBleHRlbmRzIFBle2NvbnN0cnVjdG9yKHQ9MSxyPTEsbj0xLGk9MSl7c3VwZXIoKSx0aGlzLnR5cGU9IlBsYW5lR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17d2lkdGg6dCxoZWlnaHQ6cix3aWR0aFNlZ21lbnRzOm4saGVpZ2h0U2VnbWVudHM6aX07bGV0IG89dC8yLGE9ci8yLHM9TWF0aC5mbG9vcihuKSxsPU1hdGguZmxvb3IoaSksYz1zKzEsdT1sKzEsaD10L3MsZj1yL2wscD1bXSxkPVtdLGc9W10sXz1bXTtmb3IobGV0IHk9MDt5PHU7eSsrKXtsZXQgeD15KmYtYTtmb3IobGV0IGI9MDtiPGM7YisrKXtsZXQgUz1iKmgtbztkLnB1c2goUywteCwwKSxnLnB1c2goMCwwLDEpLF8ucHVzaChiL3MpLF8ucHVzaCgxLXkvbCl9fWZvcihsZXQgeT0wO3k8bDt5KyspZm9yKGxldCB4PTA7eDxzO3grKyl7bGV0IGI9eCtjKnksUz14K2MqKHkrMSksQz14KzErYyooeSsxKSxQPXgrMStjKnk7cC5wdXNoKGIsUyxQKSxwLnB1c2goUyxDLFApfXRoaXMuc2V0SW5kZXgocCksdGhpcy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUoZCwzKSksdGhpcy5zZXRBdHRyaWJ1dGUoIm5vcm1hbCIsbmV3IHhlKGcsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJ1diIsbmV3IHhlKF8sMikpfXN0YXRpYyBmcm9tSlNPTih0KXtyZXR1cm4gbmV3IFYwKHQud2lkdGgsdC5oZWlnaHQsdC53aWR0aFNlZ21lbnRzLHQuaGVpZ2h0U2VnbWVudHMpfX0sX2ZyPWAjaWZkZWYgVVNFX0FMUEhBTUFQCglkaWZmdXNlQ29sb3IuYSAqPSB0ZXh0dXJlMkQoIGFscGhhTWFwLCB2VXYgKS5nOwojZW5kaWZgLHlmcj1gI2lmZGVmIFVTRV9BTFBIQU1BUAoJdW5pZm9ybSBzYW1wbGVyMkQgYWxwaGFNYXA7CiNlbmRpZmAsdmZyPWAjaWZkZWYgVVNFX0FMUEhBVEVTVAoJaWYgKCBkaWZmdXNlQ29sb3IuYSA8IGFscGhhVGVzdCApIGRpc2NhcmQ7CiNlbmRpZmAseGZyPWAjaWZkZWYgVVNFX0FMUEhBVEVTVAoJdW5pZm9ybSBmbG9hdCBhbHBoYVRlc3Q7CiNlbmRpZmAsYmZyPWAjaWZkZWYgVVNFX0FPTUFQCglmbG9hdCBhbWJpZW50T2NjbHVzaW9uID0gKCB0ZXh0dXJlMkQoIGFvTWFwLCB2VXYyICkuciAtIDEuMCApICogYW9NYXBJbnRlbnNpdHkgKyAxLjA7CglyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKj0gYW1iaWVudE9jY2x1c2lvbjsKCSNpZiBkZWZpbmVkKCBVU0VfRU5WTUFQICkgJiYgZGVmaW5lZCggU1RBTkRBUkQgKQoJCWZsb2F0IGRvdE5WID0gc2F0dXJhdGUoIGRvdCggZ2VvbWV0cnkubm9ybWFsLCBnZW9tZXRyeS52aWV3RGlyICkgKTsKCQlyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdFNwZWN1bGFyICo9IGNvbXB1dGVTcGVjdWxhck9jY2x1c2lvbiggZG90TlYsIGFtYmllbnRPY2NsdXNpb24sIG1hdGVyaWFsLnJvdWdobmVzcyApOwoJI2VuZGlmCiNlbmRpZmAsd2ZyPWAjaWZkZWYgVVNFX0FPTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBhb01hcDsKCXVuaWZvcm0gZmxvYXQgYW9NYXBJbnRlbnNpdHk7CiNlbmRpZmAsU2ZyPSJ2ZWMzIHRyYW5zZm9ybWVkID0gdmVjMyggcG9zaXRpb24gKTsiLE1mcj1gdmVjMyBvYmplY3ROb3JtYWwgPSB2ZWMzKCBub3JtYWwgKTsKI2lmZGVmIFVTRV9UQU5HRU5UCgl2ZWMzIG9iamVjdFRhbmdlbnQgPSB2ZWMzKCB0YW5nZW50Lnh5eiApOwojZW5kaWZgLEVmcj1gdmVjMyBCUkRGX0xhbWJlcnQoIGNvbnN0IGluIHZlYzMgZGlmZnVzZUNvbG9yICkgewoJcmV0dXJuIFJFQ0lQUk9DQUxfUEkgKiBkaWZmdXNlQ29sb3I7Cn0KdmVjMyBGX1NjaGxpY2soIGNvbnN0IGluIHZlYzMgZjAsIGNvbnN0IGluIGZsb2F0IGY5MCwgY29uc3QgaW4gZmxvYXQgZG90VkggKSB7CglmbG9hdCBmcmVzbmVsID0gZXhwMiggKCAtIDUuNTU0NzMgKiBkb3RWSCAtIDYuOTgzMTYgKSAqIGRvdFZIICk7CglyZXR1cm4gZjAgKiAoIDEuMCAtIGZyZXNuZWwgKSArICggZjkwICogZnJlc25lbCApOwp9CmZsb2F0IFZfR0dYX1NtaXRoQ29ycmVsYXRlZCggY29uc3QgaW4gZmxvYXQgYWxwaGEsIGNvbnN0IGluIGZsb2F0IGRvdE5MLCBjb25zdCBpbiBmbG9hdCBkb3ROViApIHsKCWZsb2F0IGEyID0gcG93MiggYWxwaGEgKTsKCWZsb2F0IGd2ID0gZG90TkwgKiBzcXJ0KCBhMiArICggMS4wIC0gYTIgKSAqIHBvdzIoIGRvdE5WICkgKTsKCWZsb2F0IGdsID0gZG90TlYgKiBzcXJ0KCBhMiArICggMS4wIC0gYTIgKSAqIHBvdzIoIGRvdE5MICkgKTsKCXJldHVybiAwLjUgLyBtYXgoIGd2ICsgZ2wsIEVQU0lMT04gKTsKfQpmbG9hdCBEX0dHWCggY29uc3QgaW4gZmxvYXQgYWxwaGEsIGNvbnN0IGluIGZsb2F0IGRvdE5IICkgewoJZmxvYXQgYTIgPSBwb3cyKCBhbHBoYSApOwoJZmxvYXQgZGVub20gPSBwb3cyKCBkb3ROSCApICogKCBhMiAtIDEuMCApICsgMS4wOwoJcmV0dXJuIFJFQ0lQUk9DQUxfUEkgKiBhMiAvIHBvdzIoIGRlbm9tICk7Cn0KdmVjMyBCUkRGX0dHWCggY29uc3QgaW4gdmVjMyBsaWdodERpciwgY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgY29uc3QgaW4gdmVjMyBmMCwgY29uc3QgaW4gZmxvYXQgZjkwLCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MgKSB7CglmbG9hdCBhbHBoYSA9IHBvdzIoIHJvdWdobmVzcyApOwoJdmVjMyBoYWxmRGlyID0gbm9ybWFsaXplKCBsaWdodERpciArIHZpZXdEaXIgKTsKCWZsb2F0IGRvdE5MID0gc2F0dXJhdGUoIGRvdCggbm9ybWFsLCBsaWdodERpciApICk7CglmbG9hdCBkb3ROViA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgdmlld0RpciApICk7CglmbG9hdCBkb3ROSCA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgaGFsZkRpciApICk7CglmbG9hdCBkb3RWSCA9IHNhdHVyYXRlKCBkb3QoIHZpZXdEaXIsIGhhbGZEaXIgKSApOwoJdmVjMyBGID0gRl9TY2hsaWNrKCBmMCwgZjkwLCBkb3RWSCApOwoJZmxvYXQgViA9IFZfR0dYX1NtaXRoQ29ycmVsYXRlZCggYWxwaGEsIGRvdE5MLCBkb3ROViApOwoJZmxvYXQgRCA9IERfR0dYKCBhbHBoYSwgZG90TkggKTsKCXJldHVybiBGICogKCBWICogRCApOwp9CnZlYzIgTFRDX1V2KCBjb25zdCBpbiB2ZWMzIE4sIGNvbnN0IGluIHZlYzMgViwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzICkgewoJY29uc3QgZmxvYXQgTFVUX1NJWkUgPSA2NC4wOwoJY29uc3QgZmxvYXQgTFVUX1NDQUxFID0gKCBMVVRfU0laRSAtIDEuMCApIC8gTFVUX1NJWkU7Cgljb25zdCBmbG9hdCBMVVRfQklBUyA9IDAuNSAvIExVVF9TSVpFOwoJZmxvYXQgZG90TlYgPSBzYXR1cmF0ZSggZG90KCBOLCBWICkgKTsKCXZlYzIgdXYgPSB2ZWMyKCByb3VnaG5lc3MsIHNxcnQoIDEuMCAtIGRvdE5WICkgKTsKCXV2ID0gdXYgKiBMVVRfU0NBTEUgKyBMVVRfQklBUzsKCXJldHVybiB1djsKfQpmbG9hdCBMVENfQ2xpcHBlZFNwaGVyZUZvcm1GYWN0b3IoIGNvbnN0IGluIHZlYzMgZiApIHsKCWZsb2F0IGwgPSBsZW5ndGgoIGYgKTsKCXJldHVybiBtYXgoICggbCAqIGwgKyBmLnogKSAvICggbCArIDEuMCApLCAwLjAgKTsKfQp2ZWMzIExUQ19FZGdlVmVjdG9yRm9ybUZhY3RvciggY29uc3QgaW4gdmVjMyB2MSwgY29uc3QgaW4gdmVjMyB2MiApIHsKCWZsb2F0IHggPSBkb3QoIHYxLCB2MiApOwoJZmxvYXQgeSA9IGFicyggeCApOwoJZmxvYXQgYSA9IDAuODU0Mzk4NSArICggMC40OTY1MTU1ICsgMC4wMTQ1MjA2ICogeSApICogeTsKCWZsb2F0IGIgPSAzLjQxNzU5NDAgKyAoIDQuMTYxNjcyNCArIHkgKSAqIHk7CglmbG9hdCB2ID0gYSAvIGI7CglmbG9hdCB0aGV0YV9zaW50aGV0YSA9ICggeCA+IDAuMCApID8gdiA6IDAuNSAqIGludmVyc2VzcXJ0KCBtYXgoIDEuMCAtIHggKiB4LCAxZS03ICkgKSAtIHY7CglyZXR1cm4gY3Jvc3MoIHYxLCB2MiApICogdGhldGFfc2ludGhldGE7Cn0KdmVjMyBMVENfRXZhbHVhdGUoIGNvbnN0IGluIHZlYzMgTiwgY29uc3QgaW4gdmVjMyBWLCBjb25zdCBpbiB2ZWMzIFAsIGNvbnN0IGluIG1hdDMgbUludiwgY29uc3QgaW4gdmVjMyByZWN0Q29vcmRzWyA0IF0gKSB7Cgl2ZWMzIHYxID0gcmVjdENvb3Jkc1sgMSBdIC0gcmVjdENvb3Jkc1sgMCBdOwoJdmVjMyB2MiA9IHJlY3RDb29yZHNbIDMgXSAtIHJlY3RDb29yZHNbIDAgXTsKCXZlYzMgbGlnaHROb3JtYWwgPSBjcm9zcyggdjEsIHYyICk7CglpZiggZG90KCBsaWdodE5vcm1hbCwgUCAtIHJlY3RDb29yZHNbIDAgXSApIDwgMC4wICkgcmV0dXJuIHZlYzMoIDAuMCApOwoJdmVjMyBUMSwgVDI7CglUMSA9IG5vcm1hbGl6ZSggViAtIE4gKiBkb3QoIFYsIE4gKSApOwoJVDIgPSAtIGNyb3NzKCBOLCBUMSApOwoJbWF0MyBtYXQgPSBtSW52ICogdHJhbnNwb3NlTWF0MyggbWF0MyggVDEsIFQyLCBOICkgKTsKCXZlYzMgY29vcmRzWyA0IF07Cgljb29yZHNbIDAgXSA9IG1hdCAqICggcmVjdENvb3Jkc1sgMCBdIC0gUCApOwoJY29vcmRzWyAxIF0gPSBtYXQgKiAoIHJlY3RDb29yZHNbIDEgXSAtIFAgKTsKCWNvb3Jkc1sgMiBdID0gbWF0ICogKCByZWN0Q29vcmRzWyAyIF0gLSBQICk7Cgljb29yZHNbIDMgXSA9IG1hdCAqICggcmVjdENvb3Jkc1sgMyBdIC0gUCApOwoJY29vcmRzWyAwIF0gPSBub3JtYWxpemUoIGNvb3Jkc1sgMCBdICk7Cgljb29yZHNbIDEgXSA9IG5vcm1hbGl6ZSggY29vcmRzWyAxIF0gKTsKCWNvb3Jkc1sgMiBdID0gbm9ybWFsaXplKCBjb29yZHNbIDIgXSApOwoJY29vcmRzWyAzIF0gPSBub3JtYWxpemUoIGNvb3Jkc1sgMyBdICk7Cgl2ZWMzIHZlY3RvckZvcm1GYWN0b3IgPSB2ZWMzKCAwLjAgKTsKCXZlY3RvckZvcm1GYWN0b3IgKz0gTFRDX0VkZ2VWZWN0b3JGb3JtRmFjdG9yKCBjb29yZHNbIDAgXSwgY29vcmRzWyAxIF0gKTsKCXZlY3RvckZvcm1GYWN0b3IgKz0gTFRDX0VkZ2VWZWN0b3JGb3JtRmFjdG9yKCBjb29yZHNbIDEgXSwgY29vcmRzWyAyIF0gKTsKCXZlY3RvckZvcm1GYWN0b3IgKz0gTFRDX0VkZ2VWZWN0b3JGb3JtRmFjdG9yKCBjb29yZHNbIDIgXSwgY29vcmRzWyAzIF0gKTsKCXZlY3RvckZvcm1GYWN0b3IgKz0gTFRDX0VkZ2VWZWN0b3JGb3JtRmFjdG9yKCBjb29yZHNbIDMgXSwgY29vcmRzWyAwIF0gKTsKCWZsb2F0IHJlc3VsdCA9IExUQ19DbGlwcGVkU3BoZXJlRm9ybUZhY3RvciggdmVjdG9yRm9ybUZhY3RvciApOwoJcmV0dXJuIHZlYzMoIHJlc3VsdCApOwp9CmZsb2F0IEdfQmxpbm5QaG9uZ19JbXBsaWNpdCggKSB7CglyZXR1cm4gMC4yNTsKfQpmbG9hdCBEX0JsaW5uUGhvbmcoIGNvbnN0IGluIGZsb2F0IHNoaW5pbmVzcywgY29uc3QgaW4gZmxvYXQgZG90TkggKSB7CglyZXR1cm4gUkVDSVBST0NBTF9QSSAqICggc2hpbmluZXNzICogMC41ICsgMS4wICkgKiBwb3coIGRvdE5ILCBzaGluaW5lc3MgKTsKfQp2ZWMzIEJSREZfQmxpbm5QaG9uZyggY29uc3QgaW4gdmVjMyBsaWdodERpciwgY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgY29uc3QgaW4gdmVjMyBzcGVjdWxhckNvbG9yLCBjb25zdCBpbiBmbG9hdCBzaGluaW5lc3MgKSB7Cgl2ZWMzIGhhbGZEaXIgPSBub3JtYWxpemUoIGxpZ2h0RGlyICsgdmlld0RpciApOwoJZmxvYXQgZG90TkggPSBzYXR1cmF0ZSggZG90KCBub3JtYWwsIGhhbGZEaXIgKSApOwoJZmxvYXQgZG90VkggPSBzYXR1cmF0ZSggZG90KCB2aWV3RGlyLCBoYWxmRGlyICkgKTsKCXZlYzMgRiA9IEZfU2NobGljayggc3BlY3VsYXJDb2xvciwgMS4wLCBkb3RWSCApOwoJZmxvYXQgRyA9IEdfQmxpbm5QaG9uZ19JbXBsaWNpdCggKTsKCWZsb2F0IEQgPSBEX0JsaW5uUGhvbmcoIHNoaW5pbmVzcywgZG90TkggKTsKCXJldHVybiBGICogKCBHICogRCApOwp9CiNpZiBkZWZpbmVkKCBVU0VfU0hFRU4gKQpmbG9hdCBEX0NoYXJsaWUoIGZsb2F0IHJvdWdobmVzcywgZmxvYXQgZG90TkggKSB7CglmbG9hdCBhbHBoYSA9IHBvdzIoIHJvdWdobmVzcyApOwoJZmxvYXQgaW52QWxwaGEgPSAxLjAgLyBhbHBoYTsKCWZsb2F0IGNvczJoID0gZG90TkggKiBkb3ROSDsKCWZsb2F0IHNpbjJoID0gbWF4KCAxLjAgLSBjb3MyaCwgMC4wMDc4MTI1ICk7CglyZXR1cm4gKCAyLjAgKyBpbnZBbHBoYSApICogcG93KCBzaW4yaCwgaW52QWxwaGEgKiAwLjUgKSAvICggMi4wICogUEkgKTsKfQpmbG9hdCBWX05ldWJlbHQoIGZsb2F0IGRvdE5WLCBmbG9hdCBkb3ROTCApIHsKCXJldHVybiBzYXR1cmF0ZSggMS4wIC8gKCA0LjAgKiAoIGRvdE5MICsgZG90TlYgLSBkb3ROTCAqIGRvdE5WICkgKSApOwp9CnZlYzMgQlJERl9TaGVlbiggY29uc3QgaW4gdmVjMyBsaWdodERpciwgY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgdmVjMyBzaGVlbkNvbG9yLCBjb25zdCBpbiBmbG9hdCBzaGVlblJvdWdobmVzcyApIHsKCXZlYzMgaGFsZkRpciA9IG5vcm1hbGl6ZSggbGlnaHREaXIgKyB2aWV3RGlyICk7CglmbG9hdCBkb3ROTCA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgbGlnaHREaXIgKSApOwoJZmxvYXQgZG90TlYgPSBzYXR1cmF0ZSggZG90KCBub3JtYWwsIHZpZXdEaXIgKSApOwoJZmxvYXQgZG90TkggPSBzYXR1cmF0ZSggZG90KCBub3JtYWwsIGhhbGZEaXIgKSApOwoJZmxvYXQgRCA9IERfQ2hhcmxpZSggc2hlZW5Sb3VnaG5lc3MsIGRvdE5IICk7CglmbG9hdCBWID0gVl9OZXViZWx0KCBkb3ROViwgZG90TkwgKTsKCXJldHVybiBzaGVlbkNvbG9yICogKCBEICogViApOwp9CiNlbmRpZmAsVGZyPWAjaWZkZWYgVVNFX0JVTVBNQVAKCXVuaWZvcm0gc2FtcGxlcjJEIGJ1bXBNYXA7Cgl1bmlmb3JtIGZsb2F0IGJ1bXBTY2FsZTsKCXZlYzIgZEhkeHlfZndkKCkgewoJCXZlYzIgZFNUZHggPSBkRmR4KCB2VXYgKTsKCQl2ZWMyIGRTVGR5ID0gZEZkeSggdlV2ICk7CgkJZmxvYXQgSGxsID0gYnVtcFNjYWxlICogdGV4dHVyZTJEKCBidW1wTWFwLCB2VXYgKS54OwoJCWZsb2F0IGRCeCA9IGJ1bXBTY2FsZSAqIHRleHR1cmUyRCggYnVtcE1hcCwgdlV2ICsgZFNUZHggKS54IC0gSGxsOwoJCWZsb2F0IGRCeSA9IGJ1bXBTY2FsZSAqIHRleHR1cmUyRCggYnVtcE1hcCwgdlV2ICsgZFNUZHkgKS54IC0gSGxsOwoJCXJldHVybiB2ZWMyKCBkQngsIGRCeSApOwoJfQoJdmVjMyBwZXJ0dXJiTm9ybWFsQXJiKCB2ZWMzIHN1cmZfcG9zLCB2ZWMzIHN1cmZfbm9ybSwgdmVjMiBkSGR4eSwgZmxvYXQgZmFjZURpcmVjdGlvbiApIHsKCQl2ZWMzIHZTaWdtYVggPSB2ZWMzKCBkRmR4KCBzdXJmX3Bvcy54ICksIGRGZHgoIHN1cmZfcG9zLnkgKSwgZEZkeCggc3VyZl9wb3MueiApICk7CgkJdmVjMyB2U2lnbWFZID0gdmVjMyggZEZkeSggc3VyZl9wb3MueCApLCBkRmR5KCBzdXJmX3Bvcy55ICksIGRGZHkoIHN1cmZfcG9zLnogKSApOwoJCXZlYzMgdk4gPSBzdXJmX25vcm07CgkJdmVjMyBSMSA9IGNyb3NzKCB2U2lnbWFZLCB2TiApOwoJCXZlYzMgUjIgPSBjcm9zcyggdk4sIHZTaWdtYVggKTsKCQlmbG9hdCBmRGV0ID0gZG90KCB2U2lnbWFYLCBSMSApICogZmFjZURpcmVjdGlvbjsKCQl2ZWMzIHZHcmFkID0gc2lnbiggZkRldCApICogKCBkSGR4eS54ICogUjEgKyBkSGR4eS55ICogUjIgKTsKCQlyZXR1cm4gbm9ybWFsaXplKCBhYnMoIGZEZXQgKSAqIHN1cmZfbm9ybSAtIHZHcmFkICk7Cgl9CiNlbmRpZmAsQ2ZyPWAjaWYgTlVNX0NMSVBQSU5HX1BMQU5FUyA+IDAKCXZlYzQgcGxhbmU7CgkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0Cglmb3IgKCBpbnQgaSA9IDA7IGkgPCBVTklPTl9DTElQUElOR19QTEFORVM7IGkgKysgKSB7CgkJcGxhbmUgPSBjbGlwcGluZ1BsYW5lc1sgaSBdOwoJCWlmICggZG90KCB2Q2xpcFBvc2l0aW9uLCBwbGFuZS54eXogKSA+IHBsYW5lLncgKSBkaXNjYXJkOwoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCSNpZiBVTklPTl9DTElQUElOR19QTEFORVMgPCBOVU1fQ0xJUFBJTkdfUExBTkVTCgkJYm9vbCBjbGlwcGVkID0gdHJ1ZTsKCQkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0CgkJZm9yICggaW50IGkgPSBVTklPTl9DTElQUElOR19QTEFORVM7IGkgPCBOVU1fQ0xJUFBJTkdfUExBTkVTOyBpICsrICkgewoJCQlwbGFuZSA9IGNsaXBwaW5nUGxhbmVzWyBpIF07CgkJCWNsaXBwZWQgPSAoIGRvdCggdkNsaXBQb3NpdGlvbiwgcGxhbmUueHl6ICkgPiBwbGFuZS53ICkgJiYgY2xpcHBlZDsKCQl9CgkJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCQlpZiAoIGNsaXBwZWQgKSBkaXNjYXJkOwoJI2VuZGlmCiNlbmRpZmAsQWZyPWAjaWYgTlVNX0NMSVBQSU5HX1BMQU5FUyA+IDAKCXZhcnlpbmcgdmVjMyB2Q2xpcFBvc2l0aW9uOwoJdW5pZm9ybSB2ZWM0IGNsaXBwaW5nUGxhbmVzWyBOVU1fQ0xJUFBJTkdfUExBTkVTIF07CiNlbmRpZmAsUGZyPWAjaWYgTlVNX0NMSVBQSU5HX1BMQU5FUyA+IDAKCXZhcnlpbmcgdmVjMyB2Q2xpcFBvc2l0aW9uOwojZW5kaWZgLElmcj1gI2lmIE5VTV9DTElQUElOR19QTEFORVMgPiAwCgl2Q2xpcFBvc2l0aW9uID0gLSBtdlBvc2l0aW9uLnh5ejsKI2VuZGlmYCxMZnI9YCNpZiBkZWZpbmVkKCBVU0VfQ09MT1JfQUxQSEEgKQoJZGlmZnVzZUNvbG9yICo9IHZDb2xvcjsKI2VsaWYgZGVmaW5lZCggVVNFX0NPTE9SICkKCWRpZmZ1c2VDb2xvci5yZ2IgKj0gdkNvbG9yOwojZW5kaWZgLGtmcj1gI2lmIGRlZmluZWQoIFVTRV9DT0xPUl9BTFBIQSApCgl2YXJ5aW5nIHZlYzQgdkNvbG9yOwojZWxpZiBkZWZpbmVkKCBVU0VfQ09MT1IgKQoJdmFyeWluZyB2ZWMzIHZDb2xvcjsKI2VuZGlmYCxSZnI9YCNpZiBkZWZpbmVkKCBVU0VfQ09MT1JfQUxQSEEgKQoJdmFyeWluZyB2ZWM0IHZDb2xvcjsKI2VsaWYgZGVmaW5lZCggVVNFX0NPTE9SICkgfHwgZGVmaW5lZCggVVNFX0lOU1RBTkNJTkdfQ09MT1IgKQoJdmFyeWluZyB2ZWMzIHZDb2xvcjsKI2VuZGlmYCxOZnI9YCNpZiBkZWZpbmVkKCBVU0VfQ09MT1JfQUxQSEEgKQoJdkNvbG9yID0gdmVjNCggMS4wICk7CiNlbGlmIGRlZmluZWQoIFVTRV9DT0xPUiApIHx8IGRlZmluZWQoIFVTRV9JTlNUQU5DSU5HX0NPTE9SICkKCXZDb2xvciA9IHZlYzMoIDEuMCApOwojZW5kaWYKI2lmZGVmIFVTRV9DT0xPUgoJdkNvbG9yICo9IGNvbG9yOwojZW5kaWYKI2lmZGVmIFVTRV9JTlNUQU5DSU5HX0NPTE9SCgl2Q29sb3IueHl6ICo9IGluc3RhbmNlQ29sb3IueHl6OwojZW5kaWZgLERmcj1gI2RlZmluZSBQSSAzLjE0MTU5MjY1MzU4OTc5MwojZGVmaW5lIFBJMiA2LjI4MzE4NTMwNzE3OTU4NgojZGVmaW5lIFBJX0hBTEYgMS41NzA3OTYzMjY3OTQ4OTY2CiNkZWZpbmUgUkVDSVBST0NBTF9QSSAwLjMxODMwOTg4NjE4Mzc5MDcKI2RlZmluZSBSRUNJUFJPQ0FMX1BJMiAwLjE1OTE1NDk0MzA5MTg5NTM1CiNkZWZpbmUgRVBTSUxPTiAxZS02CiNpZm5kZWYgc2F0dXJhdGUKI2RlZmluZSBzYXR1cmF0ZSggYSApIGNsYW1wKCBhLCAwLjAsIDEuMCApCiNlbmRpZgojZGVmaW5lIHdoaXRlQ29tcGxlbWVudCggYSApICggMS4wIC0gc2F0dXJhdGUoIGEgKSApCmZsb2F0IHBvdzIoIGNvbnN0IGluIGZsb2F0IHggKSB7IHJldHVybiB4Kng7IH0KZmxvYXQgcG93MyggY29uc3QgaW4gZmxvYXQgeCApIHsgcmV0dXJuIHgqeCp4OyB9CmZsb2F0IHBvdzQoIGNvbnN0IGluIGZsb2F0IHggKSB7IGZsb2F0IHgyID0geCp4OyByZXR1cm4geDIqeDI7IH0KZmxvYXQgbWF4MyggY29uc3QgaW4gdmVjMyB2ICkgeyByZXR1cm4gbWF4KCBtYXgoIHYueCwgdi55ICksIHYueiApOyB9CmZsb2F0IGF2ZXJhZ2UoIGNvbnN0IGluIHZlYzMgY29sb3IgKSB7IHJldHVybiBkb3QoIGNvbG9yLCB2ZWMzKCAwLjMzMzMgKSApOyB9CmhpZ2hwIGZsb2F0IHJhbmQoIGNvbnN0IGluIHZlYzIgdXYgKSB7Cgljb25zdCBoaWdocCBmbG9hdCBhID0gMTIuOTg5OCwgYiA9IDc4LjIzMywgYyA9IDQzNzU4LjU0NTM7CgloaWdocCBmbG9hdCBkdCA9IGRvdCggdXYueHksIHZlYzIoIGEsYiApICksIHNuID0gbW9kKCBkdCwgUEkgKTsKCXJldHVybiBmcmFjdCggc2luKCBzbiApICogYyApOwp9CiNpZmRlZiBISUdIX1BSRUNJU0lPTgoJZmxvYXQgcHJlY2lzaW9uU2FmZUxlbmd0aCggdmVjMyB2ICkgeyByZXR1cm4gbGVuZ3RoKCB2ICk7IH0KI2Vsc2UKCWZsb2F0IHByZWNpc2lvblNhZmVMZW5ndGgoIHZlYzMgdiApIHsKCQlmbG9hdCBtYXhDb21wb25lbnQgPSBtYXgzKCBhYnMoIHYgKSApOwoJCXJldHVybiBsZW5ndGgoIHYgLyBtYXhDb21wb25lbnQgKSAqIG1heENvbXBvbmVudDsKCX0KI2VuZGlmCnN0cnVjdCBJbmNpZGVudExpZ2h0IHsKCXZlYzMgY29sb3I7Cgl2ZWMzIGRpcmVjdGlvbjsKCWJvb2wgdmlzaWJsZTsKfTsKc3RydWN0IFJlZmxlY3RlZExpZ2h0IHsKCXZlYzMgZGlyZWN0RGlmZnVzZTsKCXZlYzMgZGlyZWN0U3BlY3VsYXI7Cgl2ZWMzIGluZGlyZWN0RGlmZnVzZTsKCXZlYzMgaW5kaXJlY3RTcGVjdWxhcjsKfTsKc3RydWN0IEdlb21ldHJpY0NvbnRleHQgewoJdmVjMyBwb3NpdGlvbjsKCXZlYzMgbm9ybWFsOwoJdmVjMyB2aWV3RGlyOwojaWZkZWYgVVNFX0NMRUFSQ09BVAoJdmVjMyBjbGVhcmNvYXROb3JtYWw7CiNlbmRpZgp9Owp2ZWMzIHRyYW5zZm9ybURpcmVjdGlvbiggaW4gdmVjMyBkaXIsIGluIG1hdDQgbWF0cml4ICkgewoJcmV0dXJuIG5vcm1hbGl6ZSggKCBtYXRyaXggKiB2ZWM0KCBkaXIsIDAuMCApICkueHl6ICk7Cn0KdmVjMyBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCBpbiB2ZWMzIGRpciwgaW4gbWF0NCBtYXRyaXggKSB7CglyZXR1cm4gbm9ybWFsaXplKCAoIHZlYzQoIGRpciwgMC4wICkgKiBtYXRyaXggKS54eXogKTsKfQptYXQzIHRyYW5zcG9zZU1hdDMoIGNvbnN0IGluIG1hdDMgbSApIHsKCW1hdDMgdG1wOwoJdG1wWyAwIF0gPSB2ZWMzKCBtWyAwIF0ueCwgbVsgMSBdLngsIG1bIDIgXS54ICk7Cgl0bXBbIDEgXSA9IHZlYzMoIG1bIDAgXS55LCBtWyAxIF0ueSwgbVsgMiBdLnkgKTsKCXRtcFsgMiBdID0gdmVjMyggbVsgMCBdLnosIG1bIDEgXS56LCBtWyAyIF0ueiApOwoJcmV0dXJuIHRtcDsKfQpmbG9hdCBsaW5lYXJUb1JlbGF0aXZlTHVtaW5hbmNlKCBjb25zdCBpbiB2ZWMzIGNvbG9yICkgewoJdmVjMyB3ZWlnaHRzID0gdmVjMyggMC4yMTI2LCAwLjcxNTIsIDAuMDcyMiApOwoJcmV0dXJuIGRvdCggd2VpZ2h0cywgY29sb3IucmdiICk7Cn0KYm9vbCBpc1BlcnNwZWN0aXZlTWF0cml4KCBtYXQ0IG0gKSB7CglyZXR1cm4gbVsgMiBdWyAzIF0gPT0gLSAxLjA7Cn0KdmVjMiBlcXVpcmVjdFV2KCBpbiB2ZWMzIGRpciApIHsKCWZsb2F0IHUgPSBhdGFuKCBkaXIueiwgZGlyLnggKSAqIFJFQ0lQUk9DQUxfUEkyICsgMC41OwoJZmxvYXQgdiA9IGFzaW4oIGNsYW1wKCBkaXIueSwgLSAxLjAsIDEuMCApICkgKiBSRUNJUFJPQ0FMX1BJICsgMC41OwoJcmV0dXJuIHZlYzIoIHUsIHYgKTsKfWAsT2ZyPWAjaWZkZWYgRU5WTUFQX1RZUEVfQ1VCRV9VVgoJI2RlZmluZSBjdWJlVVZfbWF4TWlwTGV2ZWwgOC4wCgkjZGVmaW5lIGN1YmVVVl9taW5NaXBMZXZlbCA0LjAKCSNkZWZpbmUgY3ViZVVWX21heFRpbGVTaXplIDI1Ni4wCgkjZGVmaW5lIGN1YmVVVl9taW5UaWxlU2l6ZSAxNi4wCglmbG9hdCBnZXRGYWNlKCB2ZWMzIGRpcmVjdGlvbiApIHsKCQl2ZWMzIGFic0RpcmVjdGlvbiA9IGFicyggZGlyZWN0aW9uICk7CgkJZmxvYXQgZmFjZSA9IC0gMS4wOwoJCWlmICggYWJzRGlyZWN0aW9uLnggPiBhYnNEaXJlY3Rpb24ueiApIHsKCQkJaWYgKCBhYnNEaXJlY3Rpb24ueCA+IGFic0RpcmVjdGlvbi55ICkKCQkJCWZhY2UgPSBkaXJlY3Rpb24ueCA+IDAuMCA/IDAuMCA6IDMuMDsKCQkJZWxzZQoJCQkJZmFjZSA9IGRpcmVjdGlvbi55ID4gMC4wID8gMS4wIDogNC4wOwoJCX0gZWxzZSB7CgkJCWlmICggYWJzRGlyZWN0aW9uLnogPiBhYnNEaXJlY3Rpb24ueSApCgkJCQlmYWNlID0gZGlyZWN0aW9uLnogPiAwLjAgPyAyLjAgOiA1LjA7CgkJCWVsc2UKCQkJCWZhY2UgPSBkaXJlY3Rpb24ueSA+IDAuMCA/IDEuMCA6IDQuMDsKCQl9CgkJcmV0dXJuIGZhY2U7Cgl9Cgl2ZWMyIGdldFVWKCB2ZWMzIGRpcmVjdGlvbiwgZmxvYXQgZmFjZSApIHsKCQl2ZWMyIHV2OwoJCWlmICggZmFjZSA9PSAwLjAgKSB7CgkJCXV2ID0gdmVjMiggZGlyZWN0aW9uLnosIGRpcmVjdGlvbi55ICkgLyBhYnMoIGRpcmVjdGlvbi54ICk7CgkJfSBlbHNlIGlmICggZmFjZSA9PSAxLjAgKSB7CgkJCXV2ID0gdmVjMiggLSBkaXJlY3Rpb24ueCwgLSBkaXJlY3Rpb24ueiApIC8gYWJzKCBkaXJlY3Rpb24ueSApOwoJCX0gZWxzZSBpZiAoIGZhY2UgPT0gMi4wICkgewoJCQl1diA9IHZlYzIoIC0gZGlyZWN0aW9uLngsIGRpcmVjdGlvbi55ICkgLyBhYnMoIGRpcmVjdGlvbi56ICk7CgkJfSBlbHNlIGlmICggZmFjZSA9PSAzLjAgKSB7CgkJCXV2ID0gdmVjMiggLSBkaXJlY3Rpb24ueiwgZGlyZWN0aW9uLnkgKSAvIGFicyggZGlyZWN0aW9uLnggKTsKCQl9IGVsc2UgaWYgKCBmYWNlID09IDQuMCApIHsKCQkJdXYgPSB2ZWMyKCAtIGRpcmVjdGlvbi54LCBkaXJlY3Rpb24ueiApIC8gYWJzKCBkaXJlY3Rpb24ueSApOwoJCX0gZWxzZSB7CgkJCXV2ID0gdmVjMiggZGlyZWN0aW9uLngsIGRpcmVjdGlvbi55ICkgLyBhYnMoIGRpcmVjdGlvbi56ICk7CgkJfQoJCXJldHVybiAwLjUgKiAoIHV2ICsgMS4wICk7Cgl9Cgl2ZWMzIGJpbGluZWFyQ3ViZVVWKCBzYW1wbGVyMkQgZW52TWFwLCB2ZWMzIGRpcmVjdGlvbiwgZmxvYXQgbWlwSW50ICkgewoJCWZsb2F0IGZhY2UgPSBnZXRGYWNlKCBkaXJlY3Rpb24gKTsKCQlmbG9hdCBmaWx0ZXJJbnQgPSBtYXgoIGN1YmVVVl9taW5NaXBMZXZlbCAtIG1pcEludCwgMC4wICk7CgkJbWlwSW50ID0gbWF4KCBtaXBJbnQsIGN1YmVVVl9taW5NaXBMZXZlbCApOwoJCWZsb2F0IGZhY2VTaXplID0gZXhwMiggbWlwSW50ICk7CgkJZmxvYXQgdGV4ZWxTaXplID0gMS4wIC8gKCAzLjAgKiBjdWJlVVZfbWF4VGlsZVNpemUgKTsKCQl2ZWMyIHV2ID0gZ2V0VVYoIGRpcmVjdGlvbiwgZmFjZSApICogKCBmYWNlU2l6ZSAtIDEuMCApICsgMC41OwoJCWlmICggZmFjZSA+IDIuMCApIHsKCQkJdXYueSArPSBmYWNlU2l6ZTsKCQkJZmFjZSAtPSAzLjA7CgkJfQoJCXV2LnggKz0gZmFjZSAqIGZhY2VTaXplOwoJCWlmICggbWlwSW50IDwgY3ViZVVWX21heE1pcExldmVsICkgewoJCQl1di55ICs9IDIuMCAqIGN1YmVVVl9tYXhUaWxlU2l6ZTsKCQl9CgkJdXYueSArPSBmaWx0ZXJJbnQgKiAyLjAgKiBjdWJlVVZfbWluVGlsZVNpemU7CgkJdXYueCArPSAzLjAgKiBtYXgoIDAuMCwgY3ViZVVWX21heFRpbGVTaXplIC0gMi4wICogZmFjZVNpemUgKTsKCQl1diAqPSB0ZXhlbFNpemU7CgkJcmV0dXJuIHRleHR1cmUyRCggZW52TWFwLCB1diApLnJnYjsKCX0KCSNkZWZpbmUgcjAgMS4wCgkjZGVmaW5lIHYwIDAuMzM5CgkjZGVmaW5lIG0wIC0gMi4wCgkjZGVmaW5lIHIxIDAuOAoJI2RlZmluZSB2MSAwLjI3NgoJI2RlZmluZSBtMSAtIDEuMAoJI2RlZmluZSByNCAwLjQKCSNkZWZpbmUgdjQgMC4wNDYKCSNkZWZpbmUgbTQgMi4wCgkjZGVmaW5lIHI1IDAuMzA1CgkjZGVmaW5lIHY1IDAuMDE2CgkjZGVmaW5lIG01IDMuMAoJI2RlZmluZSByNiAwLjIxCgkjZGVmaW5lIHY2IDAuMDAzOAoJI2RlZmluZSBtNiA0LjAKCWZsb2F0IHJvdWdobmVzc1RvTWlwKCBmbG9hdCByb3VnaG5lc3MgKSB7CgkJZmxvYXQgbWlwID0gMC4wOwoJCWlmICggcm91Z2huZXNzID49IHIxICkgewoJCQltaXAgPSAoIHIwIC0gcm91Z2huZXNzICkgKiAoIG0xIC0gbTAgKSAvICggcjAgLSByMSApICsgbTA7CgkJfSBlbHNlIGlmICggcm91Z2huZXNzID49IHI0ICkgewoJCQltaXAgPSAoIHIxIC0gcm91Z2huZXNzICkgKiAoIG00IC0gbTEgKSAvICggcjEgLSByNCApICsgbTE7CgkJfSBlbHNlIGlmICggcm91Z2huZXNzID49IHI1ICkgewoJCQltaXAgPSAoIHI0IC0gcm91Z2huZXNzICkgKiAoIG01IC0gbTQgKSAvICggcjQgLSByNSApICsgbTQ7CgkJfSBlbHNlIGlmICggcm91Z2huZXNzID49IHI2ICkgewoJCQltaXAgPSAoIHI1IC0gcm91Z2huZXNzICkgKiAoIG02IC0gbTUgKSAvICggcjUgLSByNiApICsgbTU7CgkJfSBlbHNlIHsKCQkJbWlwID0gLSAyLjAgKiBsb2cyKCAxLjE2ICogcm91Z2huZXNzICk7CQl9CgkJcmV0dXJuIG1pcDsKCX0KCXZlYzQgdGV4dHVyZUN1YmVVViggc2FtcGxlcjJEIGVudk1hcCwgdmVjMyBzYW1wbGVEaXIsIGZsb2F0IHJvdWdobmVzcyApIHsKCQlmbG9hdCBtaXAgPSBjbGFtcCggcm91Z2huZXNzVG9NaXAoIHJvdWdobmVzcyApLCBtMCwgY3ViZVVWX21heE1pcExldmVsICk7CgkJZmxvYXQgbWlwRiA9IGZyYWN0KCBtaXAgKTsKCQlmbG9hdCBtaXBJbnQgPSBmbG9vciggbWlwICk7CgkJdmVjMyBjb2xvcjAgPSBiaWxpbmVhckN1YmVVViggZW52TWFwLCBzYW1wbGVEaXIsIG1pcEludCApOwoJCWlmICggbWlwRiA9PSAwLjAgKSB7CgkJCXJldHVybiB2ZWM0KCBjb2xvcjAsIDEuMCApOwoJCX0gZWxzZSB7CgkJCXZlYzMgY29sb3IxID0gYmlsaW5lYXJDdWJlVVYoIGVudk1hcCwgc2FtcGxlRGlyLCBtaXBJbnQgKyAxLjAgKTsKCQkJcmV0dXJuIHZlYzQoIG1peCggY29sb3IwLCBjb2xvcjEsIG1pcEYgKSwgMS4wICk7CgkJfQoJfQojZW5kaWZgLHpmcj1gdmVjMyB0cmFuc2Zvcm1lZE5vcm1hbCA9IG9iamVjdE5vcm1hbDsKI2lmZGVmIFVTRV9JTlNUQU5DSU5HCgltYXQzIG0gPSBtYXQzKCBpbnN0YW5jZU1hdHJpeCApOwoJdHJhbnNmb3JtZWROb3JtYWwgLz0gdmVjMyggZG90KCBtWyAwIF0sIG1bIDAgXSApLCBkb3QoIG1bIDEgXSwgbVsgMSBdICksIGRvdCggbVsgMiBdLCBtWyAyIF0gKSApOwoJdHJhbnNmb3JtZWROb3JtYWwgPSBtICogdHJhbnNmb3JtZWROb3JtYWw7CiNlbmRpZgp0cmFuc2Zvcm1lZE5vcm1hbCA9IG5vcm1hbE1hdHJpeCAqIHRyYW5zZm9ybWVkTm9ybWFsOwojaWZkZWYgRkxJUF9TSURFRAoJdHJhbnNmb3JtZWROb3JtYWwgPSAtIHRyYW5zZm9ybWVkTm9ybWFsOwojZW5kaWYKI2lmZGVmIFVTRV9UQU5HRU5UCgl2ZWMzIHRyYW5zZm9ybWVkVGFuZ2VudCA9ICggbW9kZWxWaWV3TWF0cml4ICogdmVjNCggb2JqZWN0VGFuZ2VudCwgMC4wICkgKS54eXo7CgkjaWZkZWYgRkxJUF9TSURFRAoJCXRyYW5zZm9ybWVkVGFuZ2VudCA9IC0gdHJhbnNmb3JtZWRUYW5nZW50OwoJI2VuZGlmCiNlbmRpZmAsRmZyPWAjaWZkZWYgVVNFX0RJU1BMQUNFTUVOVE1BUAoJdW5pZm9ybSBzYW1wbGVyMkQgZGlzcGxhY2VtZW50TWFwOwoJdW5pZm9ybSBmbG9hdCBkaXNwbGFjZW1lbnRTY2FsZTsKCXVuaWZvcm0gZmxvYXQgZGlzcGxhY2VtZW50QmlhczsKI2VuZGlmYCxCZnI9YCNpZmRlZiBVU0VfRElTUExBQ0VNRU5UTUFQCgl0cmFuc2Zvcm1lZCArPSBub3JtYWxpemUoIG9iamVjdE5vcm1hbCApICogKCB0ZXh0dXJlMkQoIGRpc3BsYWNlbWVudE1hcCwgdlV2ICkueCAqIGRpc3BsYWNlbWVudFNjYWxlICsgZGlzcGxhY2VtZW50QmlhcyApOwojZW5kaWZgLEhmcj1gI2lmZGVmIFVTRV9FTUlTU0lWRU1BUAoJdmVjNCBlbWlzc2l2ZUNvbG9yID0gdGV4dHVyZTJEKCBlbWlzc2l2ZU1hcCwgdlV2ICk7Cgl0b3RhbEVtaXNzaXZlUmFkaWFuY2UgKj0gZW1pc3NpdmVDb2xvci5yZ2I7CiNlbmRpZmAsVmZyPWAjaWZkZWYgVVNFX0VNSVNTSVZFTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBlbWlzc2l2ZU1hcDsKI2VuZGlmYCxVZnI9ImdsX0ZyYWdDb2xvciA9IGxpbmVhclRvT3V0cHV0VGV4ZWwoIGdsX0ZyYWdDb2xvciApOyIscWZyPWB2ZWM0IExpbmVhclRvTGluZWFyKCBpbiB2ZWM0IHZhbHVlICkgewoJcmV0dXJuIHZhbHVlOwp9CnZlYzQgTGluZWFyVG9zUkdCKCBpbiB2ZWM0IHZhbHVlICkgewoJcmV0dXJuIHZlYzQoIG1peCggcG93KCB2YWx1ZS5yZ2IsIHZlYzMoIDAuNDE2NjYgKSApICogMS4wNTUgLSB2ZWMzKCAwLjA1NSApLCB2YWx1ZS5yZ2IgKiAxMi45MiwgdmVjMyggbGVzc1RoYW5FcXVhbCggdmFsdWUucmdiLCB2ZWMzKCAwLjAwMzEzMDggKSApICkgKSwgdmFsdWUuYSApOwp9YCxHZnI9YCNpZmRlZiBVU0VfRU5WTUFQCgkjaWZkZWYgRU5WX1dPUkxEUE9TCgkJdmVjMyBjYW1lcmFUb0ZyYWc7CgkJaWYgKCBpc09ydGhvZ3JhcGhpYyApIHsKCQkJY2FtZXJhVG9GcmFnID0gbm9ybWFsaXplKCB2ZWMzKCAtIHZpZXdNYXRyaXhbIDAgXVsgMiBdLCAtIHZpZXdNYXRyaXhbIDEgXVsgMiBdLCAtIHZpZXdNYXRyaXhbIDIgXVsgMiBdICkgKTsKCQl9IGVsc2UgewoJCQljYW1lcmFUb0ZyYWcgPSBub3JtYWxpemUoIHZXb3JsZFBvc2l0aW9uIC0gY2FtZXJhUG9zaXRpb24gKTsKCQl9CgkJdmVjMyB3b3JsZE5vcm1hbCA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIG5vcm1hbCwgdmlld01hdHJpeCApOwoJCSNpZmRlZiBFTlZNQVBfTU9ERV9SRUZMRUNUSU9OCgkJCXZlYzMgcmVmbGVjdFZlYyA9IHJlZmxlY3QoIGNhbWVyYVRvRnJhZywgd29ybGROb3JtYWwgKTsKCQkjZWxzZQoJCQl2ZWMzIHJlZmxlY3RWZWMgPSByZWZyYWN0KCBjYW1lcmFUb0ZyYWcsIHdvcmxkTm9ybWFsLCByZWZyYWN0aW9uUmF0aW8gKTsKCQkjZW5kaWYKCSNlbHNlCgkJdmVjMyByZWZsZWN0VmVjID0gdlJlZmxlY3Q7CgkjZW5kaWYKCSNpZmRlZiBFTlZNQVBfVFlQRV9DVUJFCgkJdmVjNCBlbnZDb2xvciA9IHRleHR1cmVDdWJlKCBlbnZNYXAsIHZlYzMoIGZsaXBFbnZNYXAgKiByZWZsZWN0VmVjLngsIHJlZmxlY3RWZWMueXogKSApOwoJI2VsaWYgZGVmaW5lZCggRU5WTUFQX1RZUEVfQ1VCRV9VViApCgkJdmVjNCBlbnZDb2xvciA9IHRleHR1cmVDdWJlVVYoIGVudk1hcCwgcmVmbGVjdFZlYywgMC4wICk7CgkjZWxzZQoJCXZlYzQgZW52Q29sb3IgPSB2ZWM0KCAwLjAgKTsKCSNlbmRpZgoJI2lmZGVmIEVOVk1BUF9CTEVORElOR19NVUxUSVBMWQoJCW91dGdvaW5nTGlnaHQgPSBtaXgoIG91dGdvaW5nTGlnaHQsIG91dGdvaW5nTGlnaHQgKiBlbnZDb2xvci54eXosIHNwZWN1bGFyU3RyZW5ndGggKiByZWZsZWN0aXZpdHkgKTsKCSNlbGlmIGRlZmluZWQoIEVOVk1BUF9CTEVORElOR19NSVggKQoJCW91dGdvaW5nTGlnaHQgPSBtaXgoIG91dGdvaW5nTGlnaHQsIGVudkNvbG9yLnh5eiwgc3BlY3VsYXJTdHJlbmd0aCAqIHJlZmxlY3Rpdml0eSApOwoJI2VsaWYgZGVmaW5lZCggRU5WTUFQX0JMRU5ESU5HX0FERCApCgkJb3V0Z29pbmdMaWdodCArPSBlbnZDb2xvci54eXogKiBzcGVjdWxhclN0cmVuZ3RoICogcmVmbGVjdGl2aXR5OwoJI2VuZGlmCiNlbmRpZmAsV2ZyPWAjaWZkZWYgVVNFX0VOVk1BUAoJdW5pZm9ybSBmbG9hdCBlbnZNYXBJbnRlbnNpdHk7Cgl1bmlmb3JtIGZsb2F0IGZsaXBFbnZNYXA7CgkjaWZkZWYgRU5WTUFQX1RZUEVfQ1VCRQoJCXVuaWZvcm0gc2FtcGxlckN1YmUgZW52TWFwOwoJI2Vsc2UKCQl1bmlmb3JtIHNhbXBsZXIyRCBlbnZNYXA7CgkjZW5kaWYKCQojZW5kaWZgLFlmcj1gI2lmZGVmIFVTRV9FTlZNQVAKCXVuaWZvcm0gZmxvYXQgcmVmbGVjdGl2aXR5OwoJI2lmIGRlZmluZWQoIFVTRV9CVU1QTUFQICkgfHwgZGVmaW5lZCggVVNFX05PUk1BTE1BUCApIHx8IGRlZmluZWQoIFBIT05HICkKCQkjZGVmaW5lIEVOVl9XT1JMRFBPUwoJI2VuZGlmCgkjaWZkZWYgRU5WX1dPUkxEUE9TCgkJdmFyeWluZyB2ZWMzIHZXb3JsZFBvc2l0aW9uOwoJCXVuaWZvcm0gZmxvYXQgcmVmcmFjdGlvblJhdGlvOwoJI2Vsc2UKCQl2YXJ5aW5nIHZlYzMgdlJlZmxlY3Q7CgkjZW5kaWYKI2VuZGlmYCxqZnI9YCNpZmRlZiBVU0VfRU5WTUFQCgkjaWYgZGVmaW5lZCggVVNFX0JVTVBNQVAgKSB8fCBkZWZpbmVkKCBVU0VfTk9STUFMTUFQICkgfHxkZWZpbmVkKCBQSE9ORyApCgkJI2RlZmluZSBFTlZfV09STERQT1MKCSNlbmRpZgoJI2lmZGVmIEVOVl9XT1JMRFBPUwoJCQoJCXZhcnlpbmcgdmVjMyB2V29ybGRQb3NpdGlvbjsKCSNlbHNlCgkJdmFyeWluZyB2ZWMzIHZSZWZsZWN0OwoJCXVuaWZvcm0gZmxvYXQgcmVmcmFjdGlvblJhdGlvOwoJI2VuZGlmCiNlbmRpZmAsWGZyPWAjaWZkZWYgVVNFX0VOVk1BUAoJI2lmZGVmIEVOVl9XT1JMRFBPUwoJCXZXb3JsZFBvc2l0aW9uID0gd29ybGRQb3NpdGlvbi54eXo7CgkjZWxzZQoJCXZlYzMgY2FtZXJhVG9WZXJ0ZXg7CgkJaWYgKCBpc09ydGhvZ3JhcGhpYyApIHsKCQkJY2FtZXJhVG9WZXJ0ZXggPSBub3JtYWxpemUoIHZlYzMoIC0gdmlld01hdHJpeFsgMCBdWyAyIF0sIC0gdmlld01hdHJpeFsgMSBdWyAyIF0sIC0gdmlld01hdHJpeFsgMiBdWyAyIF0gKSApOwoJCX0gZWxzZSB7CgkJCWNhbWVyYVRvVmVydGV4ID0gbm9ybWFsaXplKCB3b3JsZFBvc2l0aW9uLnh5eiAtIGNhbWVyYVBvc2l0aW9uICk7CgkJfQoJCXZlYzMgd29ybGROb3JtYWwgPSBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCB0cmFuc2Zvcm1lZE5vcm1hbCwgdmlld01hdHJpeCApOwoJCSNpZmRlZiBFTlZNQVBfTU9ERV9SRUZMRUNUSU9OCgkJCXZSZWZsZWN0ID0gcmVmbGVjdCggY2FtZXJhVG9WZXJ0ZXgsIHdvcmxkTm9ybWFsICk7CgkJI2Vsc2UKCQkJdlJlZmxlY3QgPSByZWZyYWN0KCBjYW1lcmFUb1ZlcnRleCwgd29ybGROb3JtYWwsIHJlZnJhY3Rpb25SYXRpbyApOwoJCSNlbmRpZgoJI2VuZGlmCiNlbmRpZmAsJGZyPWAjaWZkZWYgVVNFX0ZPRwoJdkZvZ0RlcHRoID0gLSBtdlBvc2l0aW9uLno7CiNlbmRpZmAsS2ZyPWAjaWZkZWYgVVNFX0ZPRwoJdmFyeWluZyBmbG9hdCB2Rm9nRGVwdGg7CiNlbmRpZmAsWmZyPWAjaWZkZWYgVVNFX0ZPRwoJI2lmZGVmIEZPR19FWFAyCgkJZmxvYXQgZm9nRmFjdG9yID0gMS4wIC0gZXhwKCAtIGZvZ0RlbnNpdHkgKiBmb2dEZW5zaXR5ICogdkZvZ0RlcHRoICogdkZvZ0RlcHRoICk7CgkjZWxzZQoJCWZsb2F0IGZvZ0ZhY3RvciA9IHNtb290aHN0ZXAoIGZvZ05lYXIsIGZvZ0ZhciwgdkZvZ0RlcHRoICk7CgkjZW5kaWYKCWdsX0ZyYWdDb2xvci5yZ2IgPSBtaXgoIGdsX0ZyYWdDb2xvci5yZ2IsIGZvZ0NvbG9yLCBmb2dGYWN0b3IgKTsKI2VuZGlmYCxKZnI9YCNpZmRlZiBVU0VfRk9HCgl1bmlmb3JtIHZlYzMgZm9nQ29sb3I7Cgl2YXJ5aW5nIGZsb2F0IHZGb2dEZXB0aDsKCSNpZmRlZiBGT0dfRVhQMgoJCXVuaWZvcm0gZmxvYXQgZm9nRGVuc2l0eTsKCSNlbHNlCgkJdW5pZm9ybSBmbG9hdCBmb2dOZWFyOwoJCXVuaWZvcm0gZmxvYXQgZm9nRmFyOwoJI2VuZGlmCiNlbmRpZmAsUWZyPWAjaWZkZWYgVVNFX0dSQURJRU5UTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBncmFkaWVudE1hcDsKI2VuZGlmCnZlYzMgZ2V0R3JhZGllbnRJcnJhZGlhbmNlKCB2ZWMzIG5vcm1hbCwgdmVjMyBsaWdodERpcmVjdGlvbiApIHsKCWZsb2F0IGRvdE5MID0gZG90KCBub3JtYWwsIGxpZ2h0RGlyZWN0aW9uICk7Cgl2ZWMyIGNvb3JkID0gdmVjMiggZG90TkwgKiAwLjUgKyAwLjUsIDAuMCApOwoJI2lmZGVmIFVTRV9HUkFESUVOVE1BUAoJCXJldHVybiB2ZWMzKCB0ZXh0dXJlMkQoIGdyYWRpZW50TWFwLCBjb29yZCApLnIgKTsKCSNlbHNlCgkJcmV0dXJuICggY29vcmQueCA8IDAuNyApID8gdmVjMyggMC43ICkgOiB2ZWMzKCAxLjAgKTsKCSNlbmRpZgp9YCx0cHI9YCNpZmRlZiBVU0VfTElHSFRNQVAKCXZlYzQgbGlnaHRNYXBUZXhlbCA9IHRleHR1cmUyRCggbGlnaHRNYXAsIHZVdjIgKTsKCXZlYzMgbGlnaHRNYXBJcnJhZGlhbmNlID0gbGlnaHRNYXBUZXhlbC5yZ2IgKiBsaWdodE1hcEludGVuc2l0eTsKCSNpZm5kZWYgUEhZU0lDQUxMWV9DT1JSRUNUX0xJR0hUUwoJCWxpZ2h0TWFwSXJyYWRpYW5jZSAqPSBQSTsKCSNlbmRpZgoJcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICs9IGxpZ2h0TWFwSXJyYWRpYW5jZTsKI2VuZGlmYCxlcHI9YCNpZmRlZiBVU0VfTElHSFRNQVAKCXVuaWZvcm0gc2FtcGxlcjJEIGxpZ2h0TWFwOwoJdW5pZm9ybSBmbG9hdCBsaWdodE1hcEludGVuc2l0eTsKI2VuZGlmYCxycHI9YHZlYzMgZGlmZnVzZSA9IHZlYzMoIDEuMCApOwpHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5OwpnZW9tZXRyeS5wb3NpdGlvbiA9IG12UG9zaXRpb24ueHl6OwpnZW9tZXRyeS5ub3JtYWwgPSBub3JtYWxpemUoIHRyYW5zZm9ybWVkTm9ybWFsICk7Cmdlb21ldHJ5LnZpZXdEaXIgPSAoIGlzT3J0aG9ncmFwaGljICkgPyB2ZWMzKCAwLCAwLCAxICkgOiBub3JtYWxpemUoIC1tdlBvc2l0aW9uLnh5eiApOwpHZW9tZXRyaWNDb250ZXh0IGJhY2tHZW9tZXRyeTsKYmFja0dlb21ldHJ5LnBvc2l0aW9uID0gZ2VvbWV0cnkucG9zaXRpb247CmJhY2tHZW9tZXRyeS5ub3JtYWwgPSAtZ2VvbWV0cnkubm9ybWFsOwpiYWNrR2VvbWV0cnkudmlld0RpciA9IGdlb21ldHJ5LnZpZXdEaXI7CnZMaWdodEZyb250ID0gdmVjMyggMC4wICk7CnZJbmRpcmVjdEZyb250ID0gdmVjMyggMC4wICk7CiNpZmRlZiBET1VCTEVfU0lERUQKCXZMaWdodEJhY2sgPSB2ZWMzKCAwLjAgKTsKCXZJbmRpcmVjdEJhY2sgPSB2ZWMzKCAwLjAgKTsKI2VuZGlmCkluY2lkZW50TGlnaHQgZGlyZWN0TGlnaHQ7CmZsb2F0IGRvdE5MOwp2ZWMzIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTsKdkluZGlyZWN0RnJvbnQgKz0gZ2V0QW1iaWVudExpZ2h0SXJyYWRpYW5jZSggYW1iaWVudExpZ2h0Q29sb3IgKTsKdkluZGlyZWN0RnJvbnQgKz0gZ2V0TGlnaHRQcm9iZUlycmFkaWFuY2UoIGxpZ2h0UHJvYmUsIGdlb21ldHJ5Lm5vcm1hbCApOwojaWZkZWYgRE9VQkxFX1NJREVECgl2SW5kaXJlY3RCYWNrICs9IGdldEFtYmllbnRMaWdodElycmFkaWFuY2UoIGFtYmllbnRMaWdodENvbG9yICk7Cgl2SW5kaXJlY3RCYWNrICs9IGdldExpZ2h0UHJvYmVJcnJhZGlhbmNlKCBsaWdodFByb2JlLCBiYWNrR2VvbWV0cnkubm9ybWFsICk7CiNlbmRpZgojaWYgTlVNX1BPSU5UX0xJR0hUUyA+IDAKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9QT0lOVF9MSUdIVFM7IGkgKysgKSB7CgkJZ2V0UG9pbnRMaWdodEluZm8oIHBvaW50TGlnaHRzWyBpIF0sIGdlb21ldHJ5LCBkaXJlY3RMaWdodCApOwoJCWRvdE5MID0gZG90KCBnZW9tZXRyeS5ub3JtYWwsIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiApOwoJCWRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZSA9IGRpcmVjdExpZ2h0LmNvbG9yOwoJCXZMaWdodEZyb250ICs9IHNhdHVyYXRlKCBkb3ROTCApICogZGlyZWN0TGlnaHRDb2xvcl9EaWZmdXNlOwoJCSNpZmRlZiBET1VCTEVfU0lERUQKCQkJdkxpZ2h0QmFjayArPSBzYXR1cmF0ZSggLSBkb3ROTCApICogZGlyZWN0TGlnaHRDb2xvcl9EaWZmdXNlOwoJCSNlbmRpZgoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKI2VuZGlmCiNpZiBOVU1fU1BPVF9MSUdIVFMgPiAwCgkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0Cglmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fU1BPVF9MSUdIVFM7IGkgKysgKSB7CgkJZ2V0U3BvdExpZ2h0SW5mbyggc3BvdExpZ2h0c1sgaSBdLCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTsKCQlkb3ROTCA9IGRvdCggZ2VvbWV0cnkubm9ybWFsLCBkaXJlY3RMaWdodC5kaXJlY3Rpb24gKTsKCQlkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2UgPSBkaXJlY3RMaWdodC5jb2xvcjsKCQl2TGlnaHRGcm9udCArPSBzYXR1cmF0ZSggZG90TkwgKSAqIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTsKCQkjaWZkZWYgRE9VQkxFX1NJREVECgkJCXZMaWdodEJhY2sgKz0gc2F0dXJhdGUoIC0gZG90TkwgKSAqIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTsKCQkjZW5kaWYKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCiNlbmRpZgojaWYgTlVNX0RJUl9MSUdIVFMgPiAwCgkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0Cglmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fRElSX0xJR0hUUzsgaSArKyApIHsKCQlnZXREaXJlY3Rpb25hbExpZ2h0SW5mbyggZGlyZWN0aW9uYWxMaWdodHNbIGkgXSwgZ2VvbWV0cnksIGRpcmVjdExpZ2h0ICk7CgkJZG90TkwgPSBkb3QoIGdlb21ldHJ5Lm5vcm1hbCwgZGlyZWN0TGlnaHQuZGlyZWN0aW9uICk7CgkJZGlyZWN0TGlnaHRDb2xvcl9EaWZmdXNlID0gZGlyZWN0TGlnaHQuY29sb3I7CgkJdkxpZ2h0RnJvbnQgKz0gc2F0dXJhdGUoIGRvdE5MICkgKiBkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2U7CgkJI2lmZGVmIERPVUJMRV9TSURFRAoJCQl2TGlnaHRCYWNrICs9IHNhdHVyYXRlKCAtIGRvdE5MICkgKiBkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2U7CgkJI2VuZGlmCgl9CgkjcHJhZ21hIHVucm9sbF9sb29wX2VuZAojZW5kaWYKI2lmIE5VTV9IRU1JX0xJR0hUUyA+IDAKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9IRU1JX0xJR0hUUzsgaSArKyApIHsKCQl2SW5kaXJlY3RGcm9udCArPSBnZXRIZW1pc3BoZXJlTGlnaHRJcnJhZGlhbmNlKCBoZW1pc3BoZXJlTGlnaHRzWyBpIF0sIGdlb21ldHJ5Lm5vcm1hbCApOwoJCSNpZmRlZiBET1VCTEVfU0lERUQKCQkJdkluZGlyZWN0QmFjayArPSBnZXRIZW1pc3BoZXJlTGlnaHRJcnJhZGlhbmNlKCBoZW1pc3BoZXJlTGlnaHRzWyBpIF0sIGJhY2tHZW9tZXRyeS5ub3JtYWwgKTsKCQkjZW5kaWYKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCiNlbmRpZmAsbnByPWB1bmlmb3JtIGJvb2wgcmVjZWl2ZVNoYWRvdzsKdW5pZm9ybSB2ZWMzIGFtYmllbnRMaWdodENvbG9yOwp1bmlmb3JtIHZlYzMgbGlnaHRQcm9iZVsgOSBdOwp2ZWMzIHNoR2V0SXJyYWRpYW5jZUF0KCBpbiB2ZWMzIG5vcm1hbCwgaW4gdmVjMyBzaENvZWZmaWNpZW50c1sgOSBdICkgewoJZmxvYXQgeCA9IG5vcm1hbC54LCB5ID0gbm9ybWFsLnksIHogPSBub3JtYWwuejsKCXZlYzMgcmVzdWx0ID0gc2hDb2VmZmljaWVudHNbIDAgXSAqIDAuODg2MjI3OwoJcmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyAxIF0gKiAyLjAgKiAwLjUxMTY2NCAqIHk7CglyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDIgXSAqIDIuMCAqIDAuNTExNjY0ICogejsKCXJlc3VsdCArPSBzaENvZWZmaWNpZW50c1sgMyBdICogMi4wICogMC41MTE2NjQgKiB4OwoJcmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyA0IF0gKiAyLjAgKiAwLjQyOTA0MyAqIHggKiB5OwoJcmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyA1IF0gKiAyLjAgKiAwLjQyOTA0MyAqIHkgKiB6OwoJcmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyA2IF0gKiAoIDAuNzQzMTI1ICogeiAqIHogLSAwLjI0NzcwOCApOwoJcmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyA3IF0gKiAyLjAgKiAwLjQyOTA0MyAqIHggKiB6OwoJcmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyA4IF0gKiAwLjQyOTA0MyAqICggeCAqIHggLSB5ICogeSApOwoJcmV0dXJuIHJlc3VsdDsKfQp2ZWMzIGdldExpZ2h0UHJvYmVJcnJhZGlhbmNlKCBjb25zdCBpbiB2ZWMzIGxpZ2h0UHJvYmVbIDkgXSwgY29uc3QgaW4gdmVjMyBub3JtYWwgKSB7Cgl2ZWMzIHdvcmxkTm9ybWFsID0gaW52ZXJzZVRyYW5zZm9ybURpcmVjdGlvbiggbm9ybWFsLCB2aWV3TWF0cml4ICk7Cgl2ZWMzIGlycmFkaWFuY2UgPSBzaEdldElycmFkaWFuY2VBdCggd29ybGROb3JtYWwsIGxpZ2h0UHJvYmUgKTsKCXJldHVybiBpcnJhZGlhbmNlOwp9CnZlYzMgZ2V0QW1iaWVudExpZ2h0SXJyYWRpYW5jZSggY29uc3QgaW4gdmVjMyBhbWJpZW50TGlnaHRDb2xvciApIHsKCXZlYzMgaXJyYWRpYW5jZSA9IGFtYmllbnRMaWdodENvbG9yOwoJcmV0dXJuIGlycmFkaWFuY2U7Cn0KZmxvYXQgZ2V0RGlzdGFuY2VBdHRlbnVhdGlvbiggY29uc3QgaW4gZmxvYXQgbGlnaHREaXN0YW5jZSwgY29uc3QgaW4gZmxvYXQgY3V0b2ZmRGlzdGFuY2UsIGNvbnN0IGluIGZsb2F0IGRlY2F5RXhwb25lbnQgKSB7CgkjaWYgZGVmaW5lZCAoIFBIWVNJQ0FMTFlfQ09SUkVDVF9MSUdIVFMgKQoJCWZsb2F0IGRpc3RhbmNlRmFsbG9mZiA9IDEuMCAvIG1heCggcG93KCBsaWdodERpc3RhbmNlLCBkZWNheUV4cG9uZW50ICksIDAuMDEgKTsKCQlpZiAoIGN1dG9mZkRpc3RhbmNlID4gMC4wICkgewoJCQlkaXN0YW5jZUZhbGxvZmYgKj0gcG93Miggc2F0dXJhdGUoIDEuMCAtIHBvdzQoIGxpZ2h0RGlzdGFuY2UgLyBjdXRvZmZEaXN0YW5jZSApICkgKTsKCQl9CgkJcmV0dXJuIGRpc3RhbmNlRmFsbG9mZjsKCSNlbHNlCgkJaWYgKCBjdXRvZmZEaXN0YW5jZSA+IDAuMCAmJiBkZWNheUV4cG9uZW50ID4gMC4wICkgewoJCQlyZXR1cm4gcG93KCBzYXR1cmF0ZSggLSBsaWdodERpc3RhbmNlIC8gY3V0b2ZmRGlzdGFuY2UgKyAxLjAgKSwgZGVjYXlFeHBvbmVudCApOwoJCX0KCQlyZXR1cm4gMS4wOwoJI2VuZGlmCn0KZmxvYXQgZ2V0U3BvdEF0dGVudWF0aW9uKCBjb25zdCBpbiBmbG9hdCBjb25lQ29zaW5lLCBjb25zdCBpbiBmbG9hdCBwZW51bWJyYUNvc2luZSwgY29uc3QgaW4gZmxvYXQgYW5nbGVDb3NpbmUgKSB7CglyZXR1cm4gc21vb3Roc3RlcCggY29uZUNvc2luZSwgcGVudW1icmFDb3NpbmUsIGFuZ2xlQ29zaW5lICk7Cn0KI2lmIE5VTV9ESVJfTElHSFRTID4gMAoJc3RydWN0IERpcmVjdGlvbmFsTGlnaHQgewoJCXZlYzMgZGlyZWN0aW9uOwoJCXZlYzMgY29sb3I7Cgl9OwoJdW5pZm9ybSBEaXJlY3Rpb25hbExpZ2h0IGRpcmVjdGlvbmFsTGlnaHRzWyBOVU1fRElSX0xJR0hUUyBdOwoJdm9pZCBnZXREaXJlY3Rpb25hbExpZ2h0SW5mbyggY29uc3QgaW4gRGlyZWN0aW9uYWxMaWdodCBkaXJlY3Rpb25hbExpZ2h0LCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBvdXQgSW5jaWRlbnRMaWdodCBsaWdodCApIHsKCQlsaWdodC5jb2xvciA9IGRpcmVjdGlvbmFsTGlnaHQuY29sb3I7CgkJbGlnaHQuZGlyZWN0aW9uID0gZGlyZWN0aW9uYWxMaWdodC5kaXJlY3Rpb247CgkJbGlnaHQudmlzaWJsZSA9IHRydWU7Cgl9CiNlbmRpZgojaWYgTlVNX1BPSU5UX0xJR0hUUyA+IDAKCXN0cnVjdCBQb2ludExpZ2h0IHsKCQl2ZWMzIHBvc2l0aW9uOwoJCXZlYzMgY29sb3I7CgkJZmxvYXQgZGlzdGFuY2U7CgkJZmxvYXQgZGVjYXk7Cgl9OwoJdW5pZm9ybSBQb2ludExpZ2h0IHBvaW50TGlnaHRzWyBOVU1fUE9JTlRfTElHSFRTIF07Cgl2b2lkIGdldFBvaW50TGlnaHRJbmZvKCBjb25zdCBpbiBQb2ludExpZ2h0IHBvaW50TGlnaHQsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIG91dCBJbmNpZGVudExpZ2h0IGxpZ2h0ICkgewoJCXZlYzMgbFZlY3RvciA9IHBvaW50TGlnaHQucG9zaXRpb24gLSBnZW9tZXRyeS5wb3NpdGlvbjsKCQlsaWdodC5kaXJlY3Rpb24gPSBub3JtYWxpemUoIGxWZWN0b3IgKTsKCQlmbG9hdCBsaWdodERpc3RhbmNlID0gbGVuZ3RoKCBsVmVjdG9yICk7CgkJbGlnaHQuY29sb3IgPSBwb2ludExpZ2h0LmNvbG9yOwoJCWxpZ2h0LmNvbG9yICo9IGdldERpc3RhbmNlQXR0ZW51YXRpb24oIGxpZ2h0RGlzdGFuY2UsIHBvaW50TGlnaHQuZGlzdGFuY2UsIHBvaW50TGlnaHQuZGVjYXkgKTsKCQlsaWdodC52aXNpYmxlID0gKCBsaWdodC5jb2xvciAhPSB2ZWMzKCAwLjAgKSApOwoJfQojZW5kaWYKI2lmIE5VTV9TUE9UX0xJR0hUUyA+IDAKCXN0cnVjdCBTcG90TGlnaHQgewoJCXZlYzMgcG9zaXRpb247CgkJdmVjMyBkaXJlY3Rpb247CgkJdmVjMyBjb2xvcjsKCQlmbG9hdCBkaXN0YW5jZTsKCQlmbG9hdCBkZWNheTsKCQlmbG9hdCBjb25lQ29zOwoJCWZsb2F0IHBlbnVtYnJhQ29zOwoJfTsKCXVuaWZvcm0gU3BvdExpZ2h0IHNwb3RMaWdodHNbIE5VTV9TUE9UX0xJR0hUUyBdOwoJdm9pZCBnZXRTcG90TGlnaHRJbmZvKCBjb25zdCBpbiBTcG90TGlnaHQgc3BvdExpZ2h0LCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBvdXQgSW5jaWRlbnRMaWdodCBsaWdodCApIHsKCQl2ZWMzIGxWZWN0b3IgPSBzcG90TGlnaHQucG9zaXRpb24gLSBnZW9tZXRyeS5wb3NpdGlvbjsKCQlsaWdodC5kaXJlY3Rpb24gPSBub3JtYWxpemUoIGxWZWN0b3IgKTsKCQlmbG9hdCBhbmdsZUNvcyA9IGRvdCggbGlnaHQuZGlyZWN0aW9uLCBzcG90TGlnaHQuZGlyZWN0aW9uICk7CgkJZmxvYXQgc3BvdEF0dGVudWF0aW9uID0gZ2V0U3BvdEF0dGVudWF0aW9uKCBzcG90TGlnaHQuY29uZUNvcywgc3BvdExpZ2h0LnBlbnVtYnJhQ29zLCBhbmdsZUNvcyApOwoJCWlmICggc3BvdEF0dGVudWF0aW9uID4gMC4wICkgewoJCQlmbG9hdCBsaWdodERpc3RhbmNlID0gbGVuZ3RoKCBsVmVjdG9yICk7CgkJCWxpZ2h0LmNvbG9yID0gc3BvdExpZ2h0LmNvbG9yICogc3BvdEF0dGVudWF0aW9uOwoJCQlsaWdodC5jb2xvciAqPSBnZXREaXN0YW5jZUF0dGVudWF0aW9uKCBsaWdodERpc3RhbmNlLCBzcG90TGlnaHQuZGlzdGFuY2UsIHNwb3RMaWdodC5kZWNheSApOwoJCQlsaWdodC52aXNpYmxlID0gKCBsaWdodC5jb2xvciAhPSB2ZWMzKCAwLjAgKSApOwoJCX0gZWxzZSB7CgkJCWxpZ2h0LmNvbG9yID0gdmVjMyggMC4wICk7CgkJCWxpZ2h0LnZpc2libGUgPSBmYWxzZTsKCQl9Cgl9CiNlbmRpZgojaWYgTlVNX1JFQ1RfQVJFQV9MSUdIVFMgPiAwCglzdHJ1Y3QgUmVjdEFyZWFMaWdodCB7CgkJdmVjMyBjb2xvcjsKCQl2ZWMzIHBvc2l0aW9uOwoJCXZlYzMgaGFsZldpZHRoOwoJCXZlYzMgaGFsZkhlaWdodDsKCX07Cgl1bmlmb3JtIHNhbXBsZXIyRCBsdGNfMTsJdW5pZm9ybSBzYW1wbGVyMkQgbHRjXzI7Cgl1bmlmb3JtIFJlY3RBcmVhTGlnaHQgcmVjdEFyZWFMaWdodHNbIE5VTV9SRUNUX0FSRUFfTElHSFRTIF07CiNlbmRpZgojaWYgTlVNX0hFTUlfTElHSFRTID4gMAoJc3RydWN0IEhlbWlzcGhlcmVMaWdodCB7CgkJdmVjMyBkaXJlY3Rpb247CgkJdmVjMyBza3lDb2xvcjsKCQl2ZWMzIGdyb3VuZENvbG9yOwoJfTsKCXVuaWZvcm0gSGVtaXNwaGVyZUxpZ2h0IGhlbWlzcGhlcmVMaWdodHNbIE5VTV9IRU1JX0xJR0hUUyBdOwoJdmVjMyBnZXRIZW1pc3BoZXJlTGlnaHRJcnJhZGlhbmNlKCBjb25zdCBpbiBIZW1pc3BoZXJlTGlnaHQgaGVtaUxpZ2h0LCBjb25zdCBpbiB2ZWMzIG5vcm1hbCApIHsKCQlmbG9hdCBkb3ROTCA9IGRvdCggbm9ybWFsLCBoZW1pTGlnaHQuZGlyZWN0aW9uICk7CgkJZmxvYXQgaGVtaURpZmZ1c2VXZWlnaHQgPSAwLjUgKiBkb3ROTCArIDAuNTsKCQl2ZWMzIGlycmFkaWFuY2UgPSBtaXgoIGhlbWlMaWdodC5ncm91bmRDb2xvciwgaGVtaUxpZ2h0LnNreUNvbG9yLCBoZW1pRGlmZnVzZVdlaWdodCApOwoJCXJldHVybiBpcnJhZGlhbmNlOwoJfQojZW5kaWZgLGlwcj1gI2lmIGRlZmluZWQoIFVTRV9FTlZNQVAgKQoJI2lmZGVmIEVOVk1BUF9NT0RFX1JFRlJBQ1RJT04KCQl1bmlmb3JtIGZsb2F0IHJlZnJhY3Rpb25SYXRpbzsKCSNlbmRpZgoJdmVjMyBnZXRJQkxJcnJhZGlhbmNlKCBjb25zdCBpbiB2ZWMzIG5vcm1hbCApIHsKCQkjaWYgZGVmaW5lZCggRU5WTUFQX1RZUEVfQ1VCRV9VViApCgkJCXZlYzMgd29ybGROb3JtYWwgPSBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCBub3JtYWwsIHZpZXdNYXRyaXggKTsKCQkJdmVjNCBlbnZNYXBDb2xvciA9IHRleHR1cmVDdWJlVVYoIGVudk1hcCwgd29ybGROb3JtYWwsIDEuMCApOwoJCQlyZXR1cm4gUEkgKiBlbnZNYXBDb2xvci5yZ2IgKiBlbnZNYXBJbnRlbnNpdHk7CgkJI2Vsc2UKCQkJcmV0dXJuIHZlYzMoIDAuMCApOwoJCSNlbmRpZgoJfQoJdmVjMyBnZXRJQkxSYWRpYW5jZSggY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzICkgewoJCSNpZiBkZWZpbmVkKCBFTlZNQVBfVFlQRV9DVUJFX1VWICkKCQkJdmVjMyByZWZsZWN0VmVjOwoJCQkjaWZkZWYgRU5WTUFQX01PREVfUkVGTEVDVElPTgoJCQkJcmVmbGVjdFZlYyA9IHJlZmxlY3QoIC0gdmlld0Rpciwgbm9ybWFsICk7CgkJCQlyZWZsZWN0VmVjID0gbm9ybWFsaXplKCBtaXgoIHJlZmxlY3RWZWMsIG5vcm1hbCwgcm91Z2huZXNzICogcm91Z2huZXNzKSApOwoJCQkjZWxzZQoJCQkJcmVmbGVjdFZlYyA9IHJlZnJhY3QoIC0gdmlld0Rpciwgbm9ybWFsLCByZWZyYWN0aW9uUmF0aW8gKTsKCQkJI2VuZGlmCgkJCXJlZmxlY3RWZWMgPSBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCByZWZsZWN0VmVjLCB2aWV3TWF0cml4ICk7CgkJCXZlYzQgZW52TWFwQ29sb3IgPSB0ZXh0dXJlQ3ViZVVWKCBlbnZNYXAsIHJlZmxlY3RWZWMsIHJvdWdobmVzcyApOwoJCQlyZXR1cm4gZW52TWFwQ29sb3IucmdiICogZW52TWFwSW50ZW5zaXR5OwoJCSNlbHNlCgkJCXJldHVybiB2ZWMzKCAwLjAgKTsKCQkjZW5kaWYKCX0KI2VuZGlmYCxvcHI9YFRvb25NYXRlcmlhbCBtYXRlcmlhbDsKbWF0ZXJpYWwuZGlmZnVzZUNvbG9yID0gZGlmZnVzZUNvbG9yLnJnYjtgLGFwcj1gdmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247CnN0cnVjdCBUb29uTWF0ZXJpYWwgewoJdmVjMyBkaWZmdXNlQ29sb3I7Cn07CnZvaWQgUkVfRGlyZWN0X1Rvb24oIGNvbnN0IGluIEluY2lkZW50TGlnaHQgZGlyZWN0TGlnaHQsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIGNvbnN0IGluIFRvb25NYXRlcmlhbCBtYXRlcmlhbCwgaW5vdXQgUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgKSB7Cgl2ZWMzIGlycmFkaWFuY2UgPSBnZXRHcmFkaWVudElycmFkaWFuY2UoIGdlb21ldHJ5Lm5vcm1hbCwgZGlyZWN0TGlnaHQuZGlyZWN0aW9uICkgKiBkaXJlY3RMaWdodC5jb2xvcjsKCXJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKz0gaXJyYWRpYW5jZSAqIEJSREZfTGFtYmVydCggbWF0ZXJpYWwuZGlmZnVzZUNvbG9yICk7Cn0Kdm9pZCBSRV9JbmRpcmVjdERpZmZ1c2VfVG9vbiggY29uc3QgaW4gdmVjMyBpcnJhZGlhbmNlLCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBjb25zdCBpbiBUb29uTWF0ZXJpYWwgbWF0ZXJpYWwsIGlub3V0IFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ICkgewoJcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICs9IGlycmFkaWFuY2UgKiBCUkRGX0xhbWJlcnQoIG1hdGVyaWFsLmRpZmZ1c2VDb2xvciApOwp9CiNkZWZpbmUgUkVfRGlyZWN0CQkJCVJFX0RpcmVjdF9Ub29uCiNkZWZpbmUgUkVfSW5kaXJlY3REaWZmdXNlCQlSRV9JbmRpcmVjdERpZmZ1c2VfVG9vbgojZGVmaW5lIE1hdGVyaWFsX0xpZ2h0UHJvYmVMT0QoIG1hdGVyaWFsICkJKDApYCxzcHI9YEJsaW5uUGhvbmdNYXRlcmlhbCBtYXRlcmlhbDsKbWF0ZXJpYWwuZGlmZnVzZUNvbG9yID0gZGlmZnVzZUNvbG9yLnJnYjsKbWF0ZXJpYWwuc3BlY3VsYXJDb2xvciA9IHNwZWN1bGFyOwptYXRlcmlhbC5zcGVjdWxhclNoaW5pbmVzcyA9IHNoaW5pbmVzczsKbWF0ZXJpYWwuc3BlY3VsYXJTdHJlbmd0aCA9IHNwZWN1bGFyU3RyZW5ndGg7YCxscHI9YHZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uOwpzdHJ1Y3QgQmxpbm5QaG9uZ01hdGVyaWFsIHsKCXZlYzMgZGlmZnVzZUNvbG9yOwoJdmVjMyBzcGVjdWxhckNvbG9yOwoJZmxvYXQgc3BlY3VsYXJTaGluaW5lc3M7CglmbG9hdCBzcGVjdWxhclN0cmVuZ3RoOwp9Owp2b2lkIFJFX0RpcmVjdF9CbGlublBob25nKCBjb25zdCBpbiBJbmNpZGVudExpZ2h0IGRpcmVjdExpZ2h0LCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBjb25zdCBpbiBCbGlublBob25nTWF0ZXJpYWwgbWF0ZXJpYWwsIGlub3V0IFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ICkgewoJZmxvYXQgZG90TkwgPSBzYXR1cmF0ZSggZG90KCBnZW9tZXRyeS5ub3JtYWwsIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiApICk7Cgl2ZWMzIGlycmFkaWFuY2UgPSBkb3ROTCAqIGRpcmVjdExpZ2h0LmNvbG9yOwoJcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArPSBpcnJhZGlhbmNlICogQlJERl9MYW1iZXJ0KCBtYXRlcmlhbC5kaWZmdXNlQ29sb3IgKTsKCXJlZmxlY3RlZExpZ2h0LmRpcmVjdFNwZWN1bGFyICs9IGlycmFkaWFuY2UgKiBCUkRGX0JsaW5uUGhvbmcoIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiwgZ2VvbWV0cnkudmlld0RpciwgZ2VvbWV0cnkubm9ybWFsLCBtYXRlcmlhbC5zcGVjdWxhckNvbG9yLCBtYXRlcmlhbC5zcGVjdWxhclNoaW5pbmVzcyApICogbWF0ZXJpYWwuc3BlY3VsYXJTdHJlbmd0aDsKfQp2b2lkIFJFX0luZGlyZWN0RGlmZnVzZV9CbGlublBob25nKCBjb25zdCBpbiB2ZWMzIGlycmFkaWFuY2UsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIGNvbnN0IGluIEJsaW5uUGhvbmdNYXRlcmlhbCBtYXRlcmlhbCwgaW5vdXQgUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgKSB7CglyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKz0gaXJyYWRpYW5jZSAqIEJSREZfTGFtYmVydCggbWF0ZXJpYWwuZGlmZnVzZUNvbG9yICk7Cn0KI2RlZmluZSBSRV9EaXJlY3QJCQkJUkVfRGlyZWN0X0JsaW5uUGhvbmcKI2RlZmluZSBSRV9JbmRpcmVjdERpZmZ1c2UJCVJFX0luZGlyZWN0RGlmZnVzZV9CbGlublBob25nCiNkZWZpbmUgTWF0ZXJpYWxfTGlnaHRQcm9iZUxPRCggbWF0ZXJpYWwgKQkoMClgLGNwcj1gUGh5c2ljYWxNYXRlcmlhbCBtYXRlcmlhbDsKbWF0ZXJpYWwuZGlmZnVzZUNvbG9yID0gZGlmZnVzZUNvbG9yLnJnYiAqICggMS4wIC0gbWV0YWxuZXNzRmFjdG9yICk7CnZlYzMgZHh5ID0gbWF4KCBhYnMoIGRGZHgoIGdlb21ldHJ5Tm9ybWFsICkgKSwgYWJzKCBkRmR5KCBnZW9tZXRyeU5vcm1hbCApICkgKTsKZmxvYXQgZ2VvbWV0cnlSb3VnaG5lc3MgPSBtYXgoIG1heCggZHh5LngsIGR4eS55ICksIGR4eS56ICk7Cm1hdGVyaWFsLnJvdWdobmVzcyA9IG1heCggcm91Z2huZXNzRmFjdG9yLCAwLjA1MjUgKTttYXRlcmlhbC5yb3VnaG5lc3MgKz0gZ2VvbWV0cnlSb3VnaG5lc3M7Cm1hdGVyaWFsLnJvdWdobmVzcyA9IG1pbiggbWF0ZXJpYWwucm91Z2huZXNzLCAxLjAgKTsKI2lmZGVmIElPUgoJI2lmZGVmIFNQRUNVTEFSCgkJZmxvYXQgc3BlY3VsYXJJbnRlbnNpdHlGYWN0b3IgPSBzcGVjdWxhckludGVuc2l0eTsKCQl2ZWMzIHNwZWN1bGFyQ29sb3JGYWN0b3IgPSBzcGVjdWxhckNvbG9yOwoJCSNpZmRlZiBVU0VfU1BFQ1VMQVJJTlRFTlNJVFlNQVAKCQkJc3BlY3VsYXJJbnRlbnNpdHlGYWN0b3IgKj0gdGV4dHVyZTJEKCBzcGVjdWxhckludGVuc2l0eU1hcCwgdlV2ICkuYTsKCQkjZW5kaWYKCQkjaWZkZWYgVVNFX1NQRUNVTEFSQ09MT1JNQVAKCQkJc3BlY3VsYXJDb2xvckZhY3RvciAqPSB0ZXh0dXJlMkQoIHNwZWN1bGFyQ29sb3JNYXAsIHZVdiApLnJnYjsKCQkjZW5kaWYKCQltYXRlcmlhbC5zcGVjdWxhckY5MCA9IG1peCggc3BlY3VsYXJJbnRlbnNpdHlGYWN0b3IsIDEuMCwgbWV0YWxuZXNzRmFjdG9yICk7CgkjZWxzZQoJCWZsb2F0IHNwZWN1bGFySW50ZW5zaXR5RmFjdG9yID0gMS4wOwoJCXZlYzMgc3BlY3VsYXJDb2xvckZhY3RvciA9IHZlYzMoIDEuMCApOwoJCW1hdGVyaWFsLnNwZWN1bGFyRjkwID0gMS4wOwoJI2VuZGlmCgltYXRlcmlhbC5zcGVjdWxhckNvbG9yID0gbWl4KCBtaW4oIHBvdzIoICggaW9yIC0gMS4wICkgLyAoIGlvciArIDEuMCApICkgKiBzcGVjdWxhckNvbG9yRmFjdG9yLCB2ZWMzKCAxLjAgKSApICogc3BlY3VsYXJJbnRlbnNpdHlGYWN0b3IsIGRpZmZ1c2VDb2xvci5yZ2IsIG1ldGFsbmVzc0ZhY3RvciApOwojZWxzZQoJbWF0ZXJpYWwuc3BlY3VsYXJDb2xvciA9IG1peCggdmVjMyggMC4wNCApLCBkaWZmdXNlQ29sb3IucmdiLCBtZXRhbG5lc3NGYWN0b3IgKTsKCW1hdGVyaWFsLnNwZWN1bGFyRjkwID0gMS4wOwojZW5kaWYKI2lmZGVmIFVTRV9DTEVBUkNPQVQKCW1hdGVyaWFsLmNsZWFyY29hdCA9IGNsZWFyY29hdDsKCW1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcyA9IGNsZWFyY29hdFJvdWdobmVzczsKCW1hdGVyaWFsLmNsZWFyY29hdEYwID0gdmVjMyggMC4wNCApOwoJbWF0ZXJpYWwuY2xlYXJjb2F0RjkwID0gMS4wOwoJI2lmZGVmIFVTRV9DTEVBUkNPQVRNQVAKCQltYXRlcmlhbC5jbGVhcmNvYXQgKj0gdGV4dHVyZTJEKCBjbGVhcmNvYXRNYXAsIHZVdiApLng7CgkjZW5kaWYKCSNpZmRlZiBVU0VfQ0xFQVJDT0FUX1JPVUdITkVTU01BUAoJCW1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcyAqPSB0ZXh0dXJlMkQoIGNsZWFyY29hdFJvdWdobmVzc01hcCwgdlV2ICkueTsKCSNlbmRpZgoJbWF0ZXJpYWwuY2xlYXJjb2F0ID0gc2F0dXJhdGUoIG1hdGVyaWFsLmNsZWFyY29hdCApOwltYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgPSBtYXgoIG1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcywgMC4wNTI1ICk7CgltYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgKz0gZ2VvbWV0cnlSb3VnaG5lc3M7CgltYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgPSBtaW4oIG1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcywgMS4wICk7CiNlbmRpZgojaWZkZWYgVVNFX1NIRUVOCgltYXRlcmlhbC5zaGVlbkNvbG9yID0gc2hlZW5Db2xvcjsKCSNpZmRlZiBVU0VfU0hFRU5DT0xPUk1BUAoJCW1hdGVyaWFsLnNoZWVuQ29sb3IgKj0gdGV4dHVyZTJEKCBzaGVlbkNvbG9yTWFwLCB2VXYgKS5yZ2I7CgkjZW5kaWYKCW1hdGVyaWFsLnNoZWVuUm91Z2huZXNzID0gY2xhbXAoIHNoZWVuUm91Z2huZXNzLCAwLjA3LCAxLjAgKTsKCSNpZmRlZiBVU0VfU0hFRU5ST1VHSE5FU1NNQVAKCQltYXRlcmlhbC5zaGVlblJvdWdobmVzcyAqPSB0ZXh0dXJlMkQoIHNoZWVuUm91Z2huZXNzTWFwLCB2VXYgKS5hOwoJI2VuZGlmCiNlbmRpZmAsdXByPWBzdHJ1Y3QgUGh5c2ljYWxNYXRlcmlhbCB7Cgl2ZWMzIGRpZmZ1c2VDb2xvcjsKCWZsb2F0IHJvdWdobmVzczsKCXZlYzMgc3BlY3VsYXJDb2xvcjsKCWZsb2F0IHNwZWN1bGFyRjkwOwoJI2lmZGVmIFVTRV9DTEVBUkNPQVQKCQlmbG9hdCBjbGVhcmNvYXQ7CgkJZmxvYXQgY2xlYXJjb2F0Um91Z2huZXNzOwoJCXZlYzMgY2xlYXJjb2F0RjA7CgkJZmxvYXQgY2xlYXJjb2F0RjkwOwoJI2VuZGlmCgkjaWZkZWYgVVNFX1NIRUVOCgkJdmVjMyBzaGVlbkNvbG9yOwoJCWZsb2F0IHNoZWVuUm91Z2huZXNzOwoJI2VuZGlmCn07CnZlYzMgY2xlYXJjb2F0U3BlY3VsYXIgPSB2ZWMzKCAwLjAgKTsKdmVjMyBzaGVlblNwZWN1bGFyID0gdmVjMyggMC4wICk7CmZsb2F0IElCTFNoZWVuQlJERiggY29uc3QgaW4gdmVjMyBub3JtYWwsIGNvbnN0IGluIHZlYzMgdmlld0RpciwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzKSB7CglmbG9hdCBkb3ROViA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgdmlld0RpciApICk7CglmbG9hdCByMiA9IHJvdWdobmVzcyAqIHJvdWdobmVzczsKCWZsb2F0IGEgPSByb3VnaG5lc3MgPCAwLjI1ID8gLTMzOS4yICogcjIgKyAxNjEuNCAqIHJvdWdobmVzcyAtIDI1LjkgOiAtOC40OCAqIHIyICsgMTQuMyAqIHJvdWdobmVzcyAtIDkuOTU7CglmbG9hdCBiID0gcm91Z2huZXNzIDwgMC4yNSA/IDQ0LjAgKiByMiAtIDIzLjcgKiByb3VnaG5lc3MgKyAzLjI2IDogMS45NyAqIHIyIC0gMy4yNyAqIHJvdWdobmVzcyArIDAuNzI7CglmbG9hdCBERyA9IGV4cCggYSAqIGRvdE5WICsgYiApICsgKCByb3VnaG5lc3MgPCAwLjI1ID8gMC4wIDogMC4xICogKCByb3VnaG5lc3MgLSAwLjI1ICkgKTsKCXJldHVybiBzYXR1cmF0ZSggREcgKiBSRUNJUFJPQ0FMX1BJICk7Cn0KdmVjMiBERkdBcHByb3goIGNvbnN0IGluIHZlYzMgbm9ybWFsLCBjb25zdCBpbiB2ZWMzIHZpZXdEaXIsIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcyApIHsKCWZsb2F0IGRvdE5WID0gc2F0dXJhdGUoIGRvdCggbm9ybWFsLCB2aWV3RGlyICkgKTsKCWNvbnN0IHZlYzQgYzAgPSB2ZWM0KCAtIDEsIC0gMC4wMjc1LCAtIDAuNTcyLCAwLjAyMiApOwoJY29uc3QgdmVjNCBjMSA9IHZlYzQoIDEsIDAuMDQyNSwgMS4wNCwgLSAwLjA0ICk7Cgl2ZWM0IHIgPSByb3VnaG5lc3MgKiBjMCArIGMxOwoJZmxvYXQgYTAwNCA9IG1pbiggci54ICogci54LCBleHAyKCAtIDkuMjggKiBkb3ROViApICkgKiByLnggKyByLnk7Cgl2ZWMyIGZhYiA9IHZlYzIoIC0gMS4wNCwgMS4wNCApICogYTAwNCArIHIuenc7CglyZXR1cm4gZmFiOwp9CnZlYzMgRW52aXJvbm1lbnRCUkRGKCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiB2ZWMzIHNwZWN1bGFyQ29sb3IsIGNvbnN0IGluIGZsb2F0IHNwZWN1bGFyRjkwLCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MgKSB7Cgl2ZWMyIGZhYiA9IERGR0FwcHJveCggbm9ybWFsLCB2aWV3RGlyLCByb3VnaG5lc3MgKTsKCXJldHVybiBzcGVjdWxhckNvbG9yICogZmFiLnggKyBzcGVjdWxhckY5MCAqIGZhYi55Owp9CnZvaWQgY29tcHV0ZU11bHRpc2NhdHRlcmluZyggY29uc3QgaW4gdmVjMyBub3JtYWwsIGNvbnN0IGluIHZlYzMgdmlld0RpciwgY29uc3QgaW4gdmVjMyBzcGVjdWxhckNvbG9yLCBjb25zdCBpbiBmbG9hdCBzcGVjdWxhckY5MCwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzLCBpbm91dCB2ZWMzIHNpbmdsZVNjYXR0ZXIsIGlub3V0IHZlYzMgbXVsdGlTY2F0dGVyICkgewoJdmVjMiBmYWIgPSBERkdBcHByb3goIG5vcm1hbCwgdmlld0Rpciwgcm91Z2huZXNzICk7Cgl2ZWMzIEZzc0VzcyA9IHNwZWN1bGFyQ29sb3IgKiBmYWIueCArIHNwZWN1bGFyRjkwICogZmFiLnk7CglmbG9hdCBFc3MgPSBmYWIueCArIGZhYi55OwoJZmxvYXQgRW1zID0gMS4wIC0gRXNzOwoJdmVjMyBGYXZnID0gc3BlY3VsYXJDb2xvciArICggMS4wIC0gc3BlY3VsYXJDb2xvciApICogMC4wNDc2MTk7CXZlYzMgRm1zID0gRnNzRXNzICogRmF2ZyAvICggMS4wIC0gRW1zICogRmF2ZyApOwoJc2luZ2xlU2NhdHRlciArPSBGc3NFc3M7CgltdWx0aVNjYXR0ZXIgKz0gRm1zICogRW1zOwp9CiNpZiBOVU1fUkVDVF9BUkVBX0xJR0hUUyA+IDAKCXZvaWQgUkVfRGlyZWN0X1JlY3RBcmVhX1BoeXNpY2FsKCBjb25zdCBpbiBSZWN0QXJlYUxpZ2h0IHJlY3RBcmVhTGlnaHQsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIGNvbnN0IGluIFBoeXNpY2FsTWF0ZXJpYWwgbWF0ZXJpYWwsIGlub3V0IFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ICkgewoJCXZlYzMgbm9ybWFsID0gZ2VvbWV0cnkubm9ybWFsOwoJCXZlYzMgdmlld0RpciA9IGdlb21ldHJ5LnZpZXdEaXI7CgkJdmVjMyBwb3NpdGlvbiA9IGdlb21ldHJ5LnBvc2l0aW9uOwoJCXZlYzMgbGlnaHRQb3MgPSByZWN0QXJlYUxpZ2h0LnBvc2l0aW9uOwoJCXZlYzMgaGFsZldpZHRoID0gcmVjdEFyZWFMaWdodC5oYWxmV2lkdGg7CgkJdmVjMyBoYWxmSGVpZ2h0ID0gcmVjdEFyZWFMaWdodC5oYWxmSGVpZ2h0OwoJCXZlYzMgbGlnaHRDb2xvciA9IHJlY3RBcmVhTGlnaHQuY29sb3I7CgkJZmxvYXQgcm91Z2huZXNzID0gbWF0ZXJpYWwucm91Z2huZXNzOwoJCXZlYzMgcmVjdENvb3Jkc1sgNCBdOwoJCXJlY3RDb29yZHNbIDAgXSA9IGxpZ2h0UG9zICsgaGFsZldpZHRoIC0gaGFsZkhlaWdodDsJCXJlY3RDb29yZHNbIDEgXSA9IGxpZ2h0UG9zIC0gaGFsZldpZHRoIC0gaGFsZkhlaWdodDsKCQlyZWN0Q29vcmRzWyAyIF0gPSBsaWdodFBvcyAtIGhhbGZXaWR0aCArIGhhbGZIZWlnaHQ7CgkJcmVjdENvb3Jkc1sgMyBdID0gbGlnaHRQb3MgKyBoYWxmV2lkdGggKyBoYWxmSGVpZ2h0OwoJCXZlYzIgdXYgPSBMVENfVXYoIG5vcm1hbCwgdmlld0Rpciwgcm91Z2huZXNzICk7CgkJdmVjNCB0MSA9IHRleHR1cmUyRCggbHRjXzEsIHV2ICk7CgkJdmVjNCB0MiA9IHRleHR1cmUyRCggbHRjXzIsIHV2ICk7CgkJbWF0MyBtSW52ID0gbWF0MygKCQkJdmVjMyggdDEueCwgMCwgdDEueSApLAoJCQl2ZWMzKCAgICAwLCAxLCAgICAwICksCgkJCXZlYzMoIHQxLnosIDAsIHQxLncgKQoJCSk7CgkJdmVjMyBmcmVzbmVsID0gKCBtYXRlcmlhbC5zcGVjdWxhckNvbG9yICogdDIueCArICggdmVjMyggMS4wICkgLSBtYXRlcmlhbC5zcGVjdWxhckNvbG9yICkgKiB0Mi55ICk7CgkJcmVmbGVjdGVkTGlnaHQuZGlyZWN0U3BlY3VsYXIgKz0gbGlnaHRDb2xvciAqIGZyZXNuZWwgKiBMVENfRXZhbHVhdGUoIG5vcm1hbCwgdmlld0RpciwgcG9zaXRpb24sIG1JbnYsIHJlY3RDb29yZHMgKTsKCQlyZWZsZWN0ZWRMaWdodC5kaXJlY3REaWZmdXNlICs9IGxpZ2h0Q29sb3IgKiBtYXRlcmlhbC5kaWZmdXNlQ29sb3IgKiBMVENfRXZhbHVhdGUoIG5vcm1hbCwgdmlld0RpciwgcG9zaXRpb24sIG1hdDMoIDEuMCApLCByZWN0Q29vcmRzICk7Cgl9CiNlbmRpZgp2b2lkIFJFX0RpcmVjdF9QaHlzaWNhbCggY29uc3QgaW4gSW5jaWRlbnRMaWdodCBkaXJlY3RMaWdodCwgY29uc3QgaW4gR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeSwgY29uc3QgaW4gUGh5c2ljYWxNYXRlcmlhbCBtYXRlcmlhbCwgaW5vdXQgUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgKSB7CglmbG9hdCBkb3ROTCA9IHNhdHVyYXRlKCBkb3QoIGdlb21ldHJ5Lm5vcm1hbCwgZGlyZWN0TGlnaHQuZGlyZWN0aW9uICkgKTsKCXZlYzMgaXJyYWRpYW5jZSA9IGRvdE5MICogZGlyZWN0TGlnaHQuY29sb3I7CgkjaWZkZWYgVVNFX0NMRUFSQ09BVAoJCWZsb2F0IGRvdE5MY2MgPSBzYXR1cmF0ZSggZG90KCBnZW9tZXRyeS5jbGVhcmNvYXROb3JtYWwsIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiApICk7CgkJdmVjMyBjY0lycmFkaWFuY2UgPSBkb3ROTGNjICogZGlyZWN0TGlnaHQuY29sb3I7CgkJY2xlYXJjb2F0U3BlY3VsYXIgKz0gY2NJcnJhZGlhbmNlICogQlJERl9HR1goIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiwgZ2VvbWV0cnkudmlld0RpciwgZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsLCBtYXRlcmlhbC5jbGVhcmNvYXRGMCwgbWF0ZXJpYWwuY2xlYXJjb2F0RjkwLCBtYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgKTsKCSNlbmRpZgoJI2lmZGVmIFVTRV9TSEVFTgoJCXNoZWVuU3BlY3VsYXIgKz0gaXJyYWRpYW5jZSAqIEJSREZfU2hlZW4oIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiwgZ2VvbWV0cnkudmlld0RpciwgZ2VvbWV0cnkubm9ybWFsLCBtYXRlcmlhbC5zaGVlbkNvbG9yLCBtYXRlcmlhbC5zaGVlblJvdWdobmVzcyApOwoJI2VuZGlmCglyZWZsZWN0ZWRMaWdodC5kaXJlY3RTcGVjdWxhciArPSBpcnJhZGlhbmNlICogQlJERl9HR1goIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiwgZ2VvbWV0cnkudmlld0RpciwgZ2VvbWV0cnkubm9ybWFsLCBtYXRlcmlhbC5zcGVjdWxhckNvbG9yLCBtYXRlcmlhbC5zcGVjdWxhckY5MCwgbWF0ZXJpYWwucm91Z2huZXNzICk7CglyZWZsZWN0ZWRMaWdodC5kaXJlY3REaWZmdXNlICs9IGlycmFkaWFuY2UgKiBCUkRGX0xhbWJlcnQoIG1hdGVyaWFsLmRpZmZ1c2VDb2xvciApOwp9CnZvaWQgUkVfSW5kaXJlY3REaWZmdXNlX1BoeXNpY2FsKCBjb25zdCBpbiB2ZWMzIGlycmFkaWFuY2UsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIGNvbnN0IGluIFBoeXNpY2FsTWF0ZXJpYWwgbWF0ZXJpYWwsIGlub3V0IFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ICkgewoJcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICs9IGlycmFkaWFuY2UgKiBCUkRGX0xhbWJlcnQoIG1hdGVyaWFsLmRpZmZ1c2VDb2xvciApOwp9CnZvaWQgUkVfSW5kaXJlY3RTcGVjdWxhcl9QaHlzaWNhbCggY29uc3QgaW4gdmVjMyByYWRpYW5jZSwgY29uc3QgaW4gdmVjMyBpcnJhZGlhbmNlLCBjb25zdCBpbiB2ZWMzIGNsZWFyY29hdFJhZGlhbmNlLCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBjb25zdCBpbiBQaHlzaWNhbE1hdGVyaWFsIG1hdGVyaWFsLCBpbm91dCBSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCkgewoJI2lmZGVmIFVTRV9DTEVBUkNPQVQKCQljbGVhcmNvYXRTcGVjdWxhciArPSBjbGVhcmNvYXRSYWRpYW5jZSAqIEVudmlyb25tZW50QlJERiggZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsLCBnZW9tZXRyeS52aWV3RGlyLCBtYXRlcmlhbC5jbGVhcmNvYXRGMCwgbWF0ZXJpYWwuY2xlYXJjb2F0RjkwLCBtYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgKTsKCSNlbmRpZgoJI2lmZGVmIFVTRV9TSEVFTgoJCXNoZWVuU3BlY3VsYXIgKz0gaXJyYWRpYW5jZSAqIG1hdGVyaWFsLnNoZWVuQ29sb3IgKiBJQkxTaGVlbkJSREYoIGdlb21ldHJ5Lm5vcm1hbCwgZ2VvbWV0cnkudmlld0RpciwgbWF0ZXJpYWwuc2hlZW5Sb3VnaG5lc3MgKTsKCSNlbmRpZgoJdmVjMyBzaW5nbGVTY2F0dGVyaW5nID0gdmVjMyggMC4wICk7Cgl2ZWMzIG11bHRpU2NhdHRlcmluZyA9IHZlYzMoIDAuMCApOwoJdmVjMyBjb3NpbmVXZWlnaHRlZElycmFkaWFuY2UgPSBpcnJhZGlhbmNlICogUkVDSVBST0NBTF9QSTsKCWNvbXB1dGVNdWx0aXNjYXR0ZXJpbmcoIGdlb21ldHJ5Lm5vcm1hbCwgZ2VvbWV0cnkudmlld0RpciwgbWF0ZXJpYWwuc3BlY3VsYXJDb2xvciwgbWF0ZXJpYWwuc3BlY3VsYXJGOTAsIG1hdGVyaWFsLnJvdWdobmVzcywgc2luZ2xlU2NhdHRlcmluZywgbXVsdGlTY2F0dGVyaW5nICk7Cgl2ZWMzIGRpZmZ1c2UgPSBtYXRlcmlhbC5kaWZmdXNlQ29sb3IgKiAoIDEuMCAtICggc2luZ2xlU2NhdHRlcmluZyArIG11bHRpU2NhdHRlcmluZyApICk7CglyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdFNwZWN1bGFyICs9IHJhZGlhbmNlICogc2luZ2xlU2NhdHRlcmluZzsKCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0U3BlY3VsYXIgKz0gbXVsdGlTY2F0dGVyaW5nICogY29zaW5lV2VpZ2h0ZWRJcnJhZGlhbmNlOwoJcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICs9IGRpZmZ1c2UgKiBjb3NpbmVXZWlnaHRlZElycmFkaWFuY2U7Cn0KI2RlZmluZSBSRV9EaXJlY3QJCQkJUkVfRGlyZWN0X1BoeXNpY2FsCiNkZWZpbmUgUkVfRGlyZWN0X1JlY3RBcmVhCQlSRV9EaXJlY3RfUmVjdEFyZWFfUGh5c2ljYWwKI2RlZmluZSBSRV9JbmRpcmVjdERpZmZ1c2UJCVJFX0luZGlyZWN0RGlmZnVzZV9QaHlzaWNhbAojZGVmaW5lIFJFX0luZGlyZWN0U3BlY3VsYXIJCVJFX0luZGlyZWN0U3BlY3VsYXJfUGh5c2ljYWwKZmxvYXQgY29tcHV0ZVNwZWN1bGFyT2NjbHVzaW9uKCBjb25zdCBpbiBmbG9hdCBkb3ROViwgY29uc3QgaW4gZmxvYXQgYW1iaWVudE9jY2x1c2lvbiwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzICkgewoJcmV0dXJuIHNhdHVyYXRlKCBwb3coIGRvdE5WICsgYW1iaWVudE9jY2x1c2lvbiwgZXhwMiggLSAxNi4wICogcm91Z2huZXNzIC0gMS4wICkgKSAtIDEuMCArIGFtYmllbnRPY2NsdXNpb24gKTsKfWAsaHByPWAKR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeTsKZ2VvbWV0cnkucG9zaXRpb24gPSAtIHZWaWV3UG9zaXRpb247Cmdlb21ldHJ5Lm5vcm1hbCA9IG5vcm1hbDsKZ2VvbWV0cnkudmlld0RpciA9ICggaXNPcnRob2dyYXBoaWMgKSA/IHZlYzMoIDAsIDAsIDEgKSA6IG5vcm1hbGl6ZSggdlZpZXdQb3NpdGlvbiApOwojaWZkZWYgVVNFX0NMRUFSQ09BVAoJZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsID0gY2xlYXJjb2F0Tm9ybWFsOwojZW5kaWYKSW5jaWRlbnRMaWdodCBkaXJlY3RMaWdodDsKI2lmICggTlVNX1BPSU5UX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3QgKQoJUG9pbnRMaWdodCBwb2ludExpZ2h0OwoJI2lmIGRlZmluZWQoIFVTRV9TSEFET1dNQVAgKSAmJiBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyA+IDAKCVBvaW50TGlnaHRTaGFkb3cgcG9pbnRMaWdodFNoYWRvdzsKCSNlbmRpZgoJI3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydAoJZm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1BPSU5UX0xJR0hUUzsgaSArKyApIHsKCQlwb2ludExpZ2h0ID0gcG9pbnRMaWdodHNbIGkgXTsKCQlnZXRQb2ludExpZ2h0SW5mbyggcG9pbnRMaWdodCwgZ2VvbWV0cnksIGRpcmVjdExpZ2h0ICk7CgkJI2lmIGRlZmluZWQoIFVTRV9TSEFET1dNQVAgKSAmJiAoIFVOUk9MTEVEX0xPT1BfSU5ERVggPCBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyApCgkJcG9pbnRMaWdodFNoYWRvdyA9IHBvaW50TGlnaHRTaGFkb3dzWyBpIF07CgkJZGlyZWN0TGlnaHQuY29sb3IgKj0gYWxsKCBidmVjMiggZGlyZWN0TGlnaHQudmlzaWJsZSwgcmVjZWl2ZVNoYWRvdyApICkgPyBnZXRQb2ludFNoYWRvdyggcG9pbnRTaGFkb3dNYXBbIGkgXSwgcG9pbnRMaWdodFNoYWRvdy5zaGFkb3dNYXBTaXplLCBwb2ludExpZ2h0U2hhZG93LnNoYWRvd0JpYXMsIHBvaW50TGlnaHRTaGFkb3cuc2hhZG93UmFkaXVzLCB2UG9pbnRTaGFkb3dDb29yZFsgaSBdLCBwb2ludExpZ2h0U2hhZG93LnNoYWRvd0NhbWVyYU5lYXIsIHBvaW50TGlnaHRTaGFkb3cuc2hhZG93Q2FtZXJhRmFyICkgOiAxLjA7CgkJI2VuZGlmCgkJUkVfRGlyZWN0KCBkaXJlY3RMaWdodCwgZ2VvbWV0cnksIG1hdGVyaWFsLCByZWZsZWN0ZWRMaWdodCApOwoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKI2VuZGlmCiNpZiAoIE5VTV9TUE9UX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3QgKQoJU3BvdExpZ2h0IHNwb3RMaWdodDsKCSNpZiBkZWZpbmVkKCBVU0VfU0hBRE9XTUFQICkgJiYgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyA+IDAKCVNwb3RMaWdodFNoYWRvdyBzcG90TGlnaHRTaGFkb3c7CgkjZW5kaWYKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9TUE9UX0xJR0hUUzsgaSArKyApIHsKCQlzcG90TGlnaHQgPSBzcG90TGlnaHRzWyBpIF07CgkJZ2V0U3BvdExpZ2h0SW5mbyggc3BvdExpZ2h0LCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTsKCQkjaWYgZGVmaW5lZCggVVNFX1NIQURPV01BUCApICYmICggVU5ST0xMRURfTE9PUF9JTkRFWCA8IE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgKQoJCXNwb3RMaWdodFNoYWRvdyA9IHNwb3RMaWdodFNoYWRvd3NbIGkgXTsKCQlkaXJlY3RMaWdodC5jb2xvciAqPSBhbGwoIGJ2ZWMyKCBkaXJlY3RMaWdodC52aXNpYmxlLCByZWNlaXZlU2hhZG93ICkgKSA/IGdldFNoYWRvdyggc3BvdFNoYWRvd01hcFsgaSBdLCBzcG90TGlnaHRTaGFkb3cuc2hhZG93TWFwU2l6ZSwgc3BvdExpZ2h0U2hhZG93LnNoYWRvd0JpYXMsIHNwb3RMaWdodFNoYWRvdy5zaGFkb3dSYWRpdXMsIHZTcG90U2hhZG93Q29vcmRbIGkgXSApIDogMS4wOwoJCSNlbmRpZgoJCVJFX0RpcmVjdCggZGlyZWN0TGlnaHQsIGdlb21ldHJ5LCBtYXRlcmlhbCwgcmVmbGVjdGVkTGlnaHQgKTsKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCiNlbmRpZgojaWYgKCBOVU1fRElSX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3QgKQoJRGlyZWN0aW9uYWxMaWdodCBkaXJlY3Rpb25hbExpZ2h0OwoJI2lmIGRlZmluZWQoIFVTRV9TSEFET1dNQVAgKSAmJiBOVU1fRElSX0xJR0hUX1NIQURPV1MgPiAwCglEaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3c7CgkjZW5kaWYKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9ESVJfTElHSFRTOyBpICsrICkgewoJCWRpcmVjdGlvbmFsTGlnaHQgPSBkaXJlY3Rpb25hbExpZ2h0c1sgaSBdOwoJCWdldERpcmVjdGlvbmFsTGlnaHRJbmZvKCBkaXJlY3Rpb25hbExpZ2h0LCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTsKCQkjaWYgZGVmaW5lZCggVVNFX1NIQURPV01BUCApICYmICggVU5ST0xMRURfTE9PUF9JTkRFWCA8IE5VTV9ESVJfTElHSFRfU0hBRE9XUyApCgkJZGlyZWN0aW9uYWxMaWdodFNoYWRvdyA9IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzWyBpIF07CgkJZGlyZWN0TGlnaHQuY29sb3IgKj0gYWxsKCBidmVjMiggZGlyZWN0TGlnaHQudmlzaWJsZSwgcmVjZWl2ZVNoYWRvdyApICkgPyBnZXRTaGFkb3coIGRpcmVjdGlvbmFsU2hhZG93TWFwWyBpIF0sIGRpcmVjdGlvbmFsTGlnaHRTaGFkb3cuc2hhZG93TWFwU2l6ZSwgZGlyZWN0aW9uYWxMaWdodFNoYWRvdy5zaGFkb3dCaWFzLCBkaXJlY3Rpb25hbExpZ2h0U2hhZG93LnNoYWRvd1JhZGl1cywgdkRpcmVjdGlvbmFsU2hhZG93Q29vcmRbIGkgXSApIDogMS4wOwoJCSNlbmRpZgoJCVJFX0RpcmVjdCggZGlyZWN0TGlnaHQsIGdlb21ldHJ5LCBtYXRlcmlhbCwgcmVmbGVjdGVkTGlnaHQgKTsKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCiNlbmRpZgojaWYgKCBOVU1fUkVDVF9BUkVBX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3RfUmVjdEFyZWEgKQoJUmVjdEFyZWFMaWdodCByZWN0QXJlYUxpZ2h0OwoJI3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydAoJZm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1JFQ1RfQVJFQV9MSUdIVFM7IGkgKysgKSB7CgkJcmVjdEFyZWFMaWdodCA9IHJlY3RBcmVhTGlnaHRzWyBpIF07CgkJUkVfRGlyZWN0X1JlY3RBcmVhKCByZWN0QXJlYUxpZ2h0LCBnZW9tZXRyeSwgbWF0ZXJpYWwsIHJlZmxlY3RlZExpZ2h0ICk7Cgl9CgkjcHJhZ21hIHVucm9sbF9sb29wX2VuZAojZW5kaWYKI2lmIGRlZmluZWQoIFJFX0luZGlyZWN0RGlmZnVzZSApCgl2ZWMzIGlibElycmFkaWFuY2UgPSB2ZWMzKCAwLjAgKTsKCXZlYzMgaXJyYWRpYW5jZSA9IGdldEFtYmllbnRMaWdodElycmFkaWFuY2UoIGFtYmllbnRMaWdodENvbG9yICk7CglpcnJhZGlhbmNlICs9IGdldExpZ2h0UHJvYmVJcnJhZGlhbmNlKCBsaWdodFByb2JlLCBnZW9tZXRyeS5ub3JtYWwgKTsKCSNpZiAoIE5VTV9IRU1JX0xJR0hUUyA+IDAgKQoJCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCQlmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fSEVNSV9MSUdIVFM7IGkgKysgKSB7CgkJCWlycmFkaWFuY2UgKz0gZ2V0SGVtaXNwaGVyZUxpZ2h0SXJyYWRpYW5jZSggaGVtaXNwaGVyZUxpZ2h0c1sgaSBdLCBnZW9tZXRyeS5ub3JtYWwgKTsKCQl9CgkJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCSNlbmRpZgojZW5kaWYKI2lmIGRlZmluZWQoIFJFX0luZGlyZWN0U3BlY3VsYXIgKQoJdmVjMyByYWRpYW5jZSA9IHZlYzMoIDAuMCApOwoJdmVjMyBjbGVhcmNvYXRSYWRpYW5jZSA9IHZlYzMoIDAuMCApOwojZW5kaWZgLGZwcj1gI2lmIGRlZmluZWQoIFJFX0luZGlyZWN0RGlmZnVzZSApCgkjaWZkZWYgVVNFX0xJR0hUTUFQCgkJdmVjNCBsaWdodE1hcFRleGVsID0gdGV4dHVyZTJEKCBsaWdodE1hcCwgdlV2MiApOwoJCXZlYzMgbGlnaHRNYXBJcnJhZGlhbmNlID0gbGlnaHRNYXBUZXhlbC5yZ2IgKiBsaWdodE1hcEludGVuc2l0eTsKCQkjaWZuZGVmIFBIWVNJQ0FMTFlfQ09SUkVDVF9MSUdIVFMKCQkJbGlnaHRNYXBJcnJhZGlhbmNlICo9IFBJOwoJCSNlbmRpZgoJCWlycmFkaWFuY2UgKz0gbGlnaHRNYXBJcnJhZGlhbmNlOwoJI2VuZGlmCgkjaWYgZGVmaW5lZCggVVNFX0VOVk1BUCApICYmIGRlZmluZWQoIFNUQU5EQVJEICkgJiYgZGVmaW5lZCggRU5WTUFQX1RZUEVfQ1VCRV9VViApCgkJaWJsSXJyYWRpYW5jZSArPSBnZXRJQkxJcnJhZGlhbmNlKCBnZW9tZXRyeS5ub3JtYWwgKTsKCSNlbmRpZgojZW5kaWYKI2lmIGRlZmluZWQoIFVTRV9FTlZNQVAgKSAmJiBkZWZpbmVkKCBSRV9JbmRpcmVjdFNwZWN1bGFyICkKCXJhZGlhbmNlICs9IGdldElCTFJhZGlhbmNlKCBnZW9tZXRyeS52aWV3RGlyLCBnZW9tZXRyeS5ub3JtYWwsIG1hdGVyaWFsLnJvdWdobmVzcyApOwoJI2lmZGVmIFVTRV9DTEVBUkNPQVQKCQljbGVhcmNvYXRSYWRpYW5jZSArPSBnZXRJQkxSYWRpYW5jZSggZ2VvbWV0cnkudmlld0RpciwgZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsLCBtYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgKTsKCSNlbmRpZgojZW5kaWZgLHBwcj1gI2lmIGRlZmluZWQoIFJFX0luZGlyZWN0RGlmZnVzZSApCglSRV9JbmRpcmVjdERpZmZ1c2UoIGlycmFkaWFuY2UsIGdlb21ldHJ5LCBtYXRlcmlhbCwgcmVmbGVjdGVkTGlnaHQgKTsKI2VuZGlmCiNpZiBkZWZpbmVkKCBSRV9JbmRpcmVjdFNwZWN1bGFyICkKCVJFX0luZGlyZWN0U3BlY3VsYXIoIHJhZGlhbmNlLCBpYmxJcnJhZGlhbmNlLCBjbGVhcmNvYXRSYWRpYW5jZSwgZ2VvbWV0cnksIG1hdGVyaWFsLCByZWZsZWN0ZWRMaWdodCApOwojZW5kaWZgLGRwcj1gI2lmIGRlZmluZWQoIFVTRV9MT0dERVBUSEJVRiApICYmIGRlZmluZWQoIFVTRV9MT0dERVBUSEJVRl9FWFQgKQoJZ2xfRnJhZ0RlcHRoRVhUID0gdklzUGVyc3BlY3RpdmUgPT0gMC4wID8gZ2xfRnJhZ0Nvb3JkLnogOiBsb2cyKCB2RnJhZ0RlcHRoICkgKiBsb2dEZXB0aEJ1ZkZDICogMC41OwojZW5kaWZgLG1wcj1gI2lmIGRlZmluZWQoIFVTRV9MT0dERVBUSEJVRiApICYmIGRlZmluZWQoIFVTRV9MT0dERVBUSEJVRl9FWFQgKQoJdW5pZm9ybSBmbG9hdCBsb2dEZXB0aEJ1ZkZDOwoJdmFyeWluZyBmbG9hdCB2RnJhZ0RlcHRoOwoJdmFyeWluZyBmbG9hdCB2SXNQZXJzcGVjdGl2ZTsKI2VuZGlmYCxncHI9YCNpZmRlZiBVU0VfTE9HREVQVEhCVUYKCSNpZmRlZiBVU0VfTE9HREVQVEhCVUZfRVhUCgkJdmFyeWluZyBmbG9hdCB2RnJhZ0RlcHRoOwoJCXZhcnlpbmcgZmxvYXQgdklzUGVyc3BlY3RpdmU7CgkjZWxzZQoJCXVuaWZvcm0gZmxvYXQgbG9nRGVwdGhCdWZGQzsKCSNlbmRpZgojZW5kaWZgLF9wcj1gI2lmZGVmIFVTRV9MT0dERVBUSEJVRgoJI2lmZGVmIFVTRV9MT0dERVBUSEJVRl9FWFQKCQl2RnJhZ0RlcHRoID0gMS4wICsgZ2xfUG9zaXRpb24udzsKCQl2SXNQZXJzcGVjdGl2ZSA9IGZsb2F0KCBpc1BlcnNwZWN0aXZlTWF0cml4KCBwcm9qZWN0aW9uTWF0cml4ICkgKTsKCSNlbHNlCgkJaWYgKCBpc1BlcnNwZWN0aXZlTWF0cml4KCBwcm9qZWN0aW9uTWF0cml4ICkgKSB7CgkJCWdsX1Bvc2l0aW9uLnogPSBsb2cyKCBtYXgoIEVQU0lMT04sIGdsX1Bvc2l0aW9uLncgKyAxLjAgKSApICogbG9nRGVwdGhCdWZGQyAtIDEuMDsKCQkJZ2xfUG9zaXRpb24ueiAqPSBnbF9Qb3NpdGlvbi53OwoJCX0KCSNlbmRpZgojZW5kaWZgLHlwcj1gI2lmZGVmIFVTRV9NQVAKCXZlYzQgc2FtcGxlZERpZmZ1c2VDb2xvciA9IHRleHR1cmUyRCggbWFwLCB2VXYgKTsKCSNpZmRlZiBERUNPREVfVklERU9fVEVYVFVSRQoJCXNhbXBsZWREaWZmdXNlQ29sb3IgPSB2ZWM0KCBtaXgoIHBvdyggc2FtcGxlZERpZmZ1c2VDb2xvci5yZ2IgKiAwLjk0Nzg2NzI5ODYgKyB2ZWMzKCAwLjA1MjEzMjcwMTQgKSwgdmVjMyggMi40ICkgKSwgc2FtcGxlZERpZmZ1c2VDb2xvci5yZ2IgKiAwLjA3NzM5OTM4MDgsIHZlYzMoIGxlc3NUaGFuRXF1YWwoIHNhbXBsZWREaWZmdXNlQ29sb3IucmdiLCB2ZWMzKCAwLjA0MDQ1ICkgKSApICksIHNhbXBsZWREaWZmdXNlQ29sb3IudyApOwoJI2VuZGlmCglkaWZmdXNlQ29sb3IgKj0gc2FtcGxlZERpZmZ1c2VDb2xvcjsKI2VuZGlmYCx2cHI9YCNpZmRlZiBVU0VfTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBtYXA7CiNlbmRpZmAseHByPWAjaWYgZGVmaW5lZCggVVNFX01BUCApIHx8IGRlZmluZWQoIFVTRV9BTFBIQU1BUCApCgl2ZWMyIHV2ID0gKCB1dlRyYW5zZm9ybSAqIHZlYzMoIGdsX1BvaW50Q29vcmQueCwgMS4wIC0gZ2xfUG9pbnRDb29yZC55LCAxICkgKS54eTsKI2VuZGlmCiNpZmRlZiBVU0VfTUFQCglkaWZmdXNlQ29sb3IgKj0gdGV4dHVyZTJEKCBtYXAsIHV2ICk7CiNlbmRpZgojaWZkZWYgVVNFX0FMUEhBTUFQCglkaWZmdXNlQ29sb3IuYSAqPSB0ZXh0dXJlMkQoIGFscGhhTWFwLCB1diApLmc7CiNlbmRpZmAsYnByPWAjaWYgZGVmaW5lZCggVVNFX01BUCApIHx8IGRlZmluZWQoIFVTRV9BTFBIQU1BUCApCgl1bmlmb3JtIG1hdDMgdXZUcmFuc2Zvcm07CiNlbmRpZgojaWZkZWYgVVNFX01BUAoJdW5pZm9ybSBzYW1wbGVyMkQgbWFwOwojZW5kaWYKI2lmZGVmIFVTRV9BTFBIQU1BUAoJdW5pZm9ybSBzYW1wbGVyMkQgYWxwaGFNYXA7CiNlbmRpZmAsd3ByPWBmbG9hdCBtZXRhbG5lc3NGYWN0b3IgPSBtZXRhbG5lc3M7CiNpZmRlZiBVU0VfTUVUQUxORVNTTUFQCgl2ZWM0IHRleGVsTWV0YWxuZXNzID0gdGV4dHVyZTJEKCBtZXRhbG5lc3NNYXAsIHZVdiApOwoJbWV0YWxuZXNzRmFjdG9yICo9IHRleGVsTWV0YWxuZXNzLmI7CiNlbmRpZmAsU3ByPWAjaWZkZWYgVVNFX01FVEFMTkVTU01BUAoJdW5pZm9ybSBzYW1wbGVyMkQgbWV0YWxuZXNzTWFwOwojZW5kaWZgLE1wcj1gI2lmZGVmIFVTRV9NT1JQSE5PUk1BTFMKCW9iamVjdE5vcm1hbCAqPSBtb3JwaFRhcmdldEJhc2VJbmZsdWVuY2U7CgkjaWZkZWYgTU9SUEhUQVJHRVRTX1RFWFRVUkUKCQlmb3IgKCBpbnQgaSA9IDA7IGkgPCBNT1JQSFRBUkdFVFNfQ09VTlQ7IGkgKysgKSB7CgkJCWlmICggbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyBpIF0gIT0gMC4wICkgb2JqZWN0Tm9ybWFsICs9IGdldE1vcnBoKCBnbF9WZXJ0ZXhJRCwgaSwgMSwgMiApICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyBpIF07CgkJfQoJI2Vsc2UKCQlvYmplY3ROb3JtYWwgKz0gbW9ycGhOb3JtYWwwICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAwIF07CgkJb2JqZWN0Tm9ybWFsICs9IG1vcnBoTm9ybWFsMSAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgMSBdOwoJCW9iamVjdE5vcm1hbCArPSBtb3JwaE5vcm1hbDIgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDIgXTsKCQlvYmplY3ROb3JtYWwgKz0gbW9ycGhOb3JtYWwzICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAzIF07CgkjZW5kaWYKI2VuZGlmYCxFcHI9YCNpZmRlZiBVU0VfTU9SUEhUQVJHRVRTCgl1bmlmb3JtIGZsb2F0IG1vcnBoVGFyZ2V0QmFzZUluZmx1ZW5jZTsKCSNpZmRlZiBNT1JQSFRBUkdFVFNfVEVYVFVSRQoJCXVuaWZvcm0gZmxvYXQgbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyBNT1JQSFRBUkdFVFNfQ09VTlQgXTsKCQl1bmlmb3JtIHNhbXBsZXIyREFycmF5IG1vcnBoVGFyZ2V0c1RleHR1cmU7CgkJdW5pZm9ybSB2ZWMyIG1vcnBoVGFyZ2V0c1RleHR1cmVTaXplOwoJCXZlYzMgZ2V0TW9ycGgoIGNvbnN0IGluIGludCB2ZXJ0ZXhJbmRleCwgY29uc3QgaW4gaW50IG1vcnBoVGFyZ2V0SW5kZXgsIGNvbnN0IGluIGludCBvZmZzZXQsIGNvbnN0IGluIGludCBzdHJpZGUgKSB7CgkJCWZsb2F0IHRleGVsSW5kZXggPSBmbG9hdCggdmVydGV4SW5kZXggKiBzdHJpZGUgKyBvZmZzZXQgKTsKCQkJZmxvYXQgeSA9IGZsb29yKCB0ZXhlbEluZGV4IC8gbW9ycGhUYXJnZXRzVGV4dHVyZVNpemUueCApOwoJCQlmbG9hdCB4ID0gdGV4ZWxJbmRleCAtIHkgKiBtb3JwaFRhcmdldHNUZXh0dXJlU2l6ZS54OwoJCQl2ZWMzIG1vcnBoVVYgPSB2ZWMzKCAoIHggKyAwLjUgKSAvIG1vcnBoVGFyZ2V0c1RleHR1cmVTaXplLngsIHkgLyBtb3JwaFRhcmdldHNUZXh0dXJlU2l6ZS55LCBtb3JwaFRhcmdldEluZGV4ICk7CgkJCXJldHVybiB0ZXh0dXJlKCBtb3JwaFRhcmdldHNUZXh0dXJlLCBtb3JwaFVWICkueHl6OwoJCX0KCSNlbHNlCgkJI2lmbmRlZiBVU0VfTU9SUEhOT1JNQUxTCgkJCXVuaWZvcm0gZmxvYXQgbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyA4IF07CgkJI2Vsc2UKCQkJdW5pZm9ybSBmbG9hdCBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDQgXTsKCQkjZW5kaWYKCSNlbmRpZgojZW5kaWZgLFRwcj1gI2lmZGVmIFVTRV9NT1JQSFRBUkdFVFMKCXRyYW5zZm9ybWVkICo9IG1vcnBoVGFyZ2V0QmFzZUluZmx1ZW5jZTsKCSNpZmRlZiBNT1JQSFRBUkdFVFNfVEVYVFVSRQoJCWZvciAoIGludCBpID0gMDsgaSA8IE1PUlBIVEFSR0VUU19DT1VOVDsgaSArKyApIHsKCQkJI2lmbmRlZiBVU0VfTU9SUEhOT1JNQUxTCgkJCQlpZiAoIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgaSBdICE9IDAuMCApIHRyYW5zZm9ybWVkICs9IGdldE1vcnBoKCBnbF9WZXJ0ZXhJRCwgaSwgMCwgMSApICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyBpIF07CgkJCSNlbHNlCgkJCQlpZiAoIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgaSBdICE9IDAuMCApIHRyYW5zZm9ybWVkICs9IGdldE1vcnBoKCBnbF9WZXJ0ZXhJRCwgaSwgMCwgMiApICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyBpIF07CgkJCSNlbmRpZgoJCX0KCSNlbHNlCgkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQwICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAwIF07CgkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQxICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAxIF07CgkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQyICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAyIF07CgkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQzICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAzIF07CgkJI2lmbmRlZiBVU0VfTU9SUEhOT1JNQUxTCgkJCXRyYW5zZm9ybWVkICs9IG1vcnBoVGFyZ2V0NCAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgNCBdOwoJCQl0cmFuc2Zvcm1lZCArPSBtb3JwaFRhcmdldDUgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDUgXTsKCQkJdHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQ2ICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyA2IF07CgkJCXRyYW5zZm9ybWVkICs9IG1vcnBoVGFyZ2V0NyAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgNyBdOwoJCSNlbmRpZgoJI2VuZGlmCiNlbmRpZmAsQ3ByPWBmbG9hdCBmYWNlRGlyZWN0aW9uID0gZ2xfRnJvbnRGYWNpbmcgPyAxLjAgOiAtIDEuMDsKI2lmZGVmIEZMQVRfU0hBREVECgl2ZWMzIGZkeCA9IHZlYzMoIGRGZHgoIHZWaWV3UG9zaXRpb24ueCApLCBkRmR4KCB2Vmlld1Bvc2l0aW9uLnkgKSwgZEZkeCggdlZpZXdQb3NpdGlvbi56ICkgKTsKCXZlYzMgZmR5ID0gdmVjMyggZEZkeSggdlZpZXdQb3NpdGlvbi54ICksIGRGZHkoIHZWaWV3UG9zaXRpb24ueSApLCBkRmR5KCB2Vmlld1Bvc2l0aW9uLnogKSApOwoJdmVjMyBub3JtYWwgPSBub3JtYWxpemUoIGNyb3NzKCBmZHgsIGZkeSApICk7CiNlbHNlCgl2ZWMzIG5vcm1hbCA9IG5vcm1hbGl6ZSggdk5vcm1hbCApOwoJI2lmZGVmIERPVUJMRV9TSURFRAoJCW5vcm1hbCA9IG5vcm1hbCAqIGZhY2VEaXJlY3Rpb247CgkjZW5kaWYKCSNpZmRlZiBVU0VfVEFOR0VOVAoJCXZlYzMgdGFuZ2VudCA9IG5vcm1hbGl6ZSggdlRhbmdlbnQgKTsKCQl2ZWMzIGJpdGFuZ2VudCA9IG5vcm1hbGl6ZSggdkJpdGFuZ2VudCApOwoJCSNpZmRlZiBET1VCTEVfU0lERUQKCQkJdGFuZ2VudCA9IHRhbmdlbnQgKiBmYWNlRGlyZWN0aW9uOwoJCQliaXRhbmdlbnQgPSBiaXRhbmdlbnQgKiBmYWNlRGlyZWN0aW9uOwoJCSNlbmRpZgoJCSNpZiBkZWZpbmVkKCBUQU5HRU5UU1BBQ0VfTk9STUFMTUFQICkgfHwgZGVmaW5lZCggVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVAgKQoJCQltYXQzIHZUQk4gPSBtYXQzKCB0YW5nZW50LCBiaXRhbmdlbnQsIG5vcm1hbCApOwoJCSNlbmRpZgoJI2VuZGlmCiNlbmRpZgp2ZWMzIGdlb21ldHJ5Tm9ybWFsID0gbm9ybWFsO2AsQXByPWAjaWZkZWYgT0JKRUNUU1BBQ0VfTk9STUFMTUFQCglub3JtYWwgPSB0ZXh0dXJlMkQoIG5vcm1hbE1hcCwgdlV2ICkueHl6ICogMi4wIC0gMS4wOwoJI2lmZGVmIEZMSVBfU0lERUQKCQlub3JtYWwgPSAtIG5vcm1hbDsKCSNlbmRpZgoJI2lmZGVmIERPVUJMRV9TSURFRAoJCW5vcm1hbCA9IG5vcm1hbCAqIGZhY2VEaXJlY3Rpb247CgkjZW5kaWYKCW5vcm1hbCA9IG5vcm1hbGl6ZSggbm9ybWFsTWF0cml4ICogbm9ybWFsICk7CiNlbGlmIGRlZmluZWQoIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAgKQoJdmVjMyBtYXBOID0gdGV4dHVyZTJEKCBub3JtYWxNYXAsIHZVdiApLnh5eiAqIDIuMCAtIDEuMDsKCW1hcE4ueHkgKj0gbm9ybWFsU2NhbGU7CgkjaWZkZWYgVVNFX1RBTkdFTlQKCQlub3JtYWwgPSBub3JtYWxpemUoIHZUQk4gKiBtYXBOICk7CgkjZWxzZQoJCW5vcm1hbCA9IHBlcnR1cmJOb3JtYWwyQXJiKCAtIHZWaWV3UG9zaXRpb24sIG5vcm1hbCwgbWFwTiwgZmFjZURpcmVjdGlvbiApOwoJI2VuZGlmCiNlbGlmIGRlZmluZWQoIFVTRV9CVU1QTUFQICkKCW5vcm1hbCA9IHBlcnR1cmJOb3JtYWxBcmIoIC0gdlZpZXdQb3NpdGlvbiwgbm9ybWFsLCBkSGR4eV9md2QoKSwgZmFjZURpcmVjdGlvbiApOwojZW5kaWZgLFBwcj1gI2lmbmRlZiBGTEFUX1NIQURFRAoJdmFyeWluZyB2ZWMzIHZOb3JtYWw7CgkjaWZkZWYgVVNFX1RBTkdFTlQKCQl2YXJ5aW5nIHZlYzMgdlRhbmdlbnQ7CgkJdmFyeWluZyB2ZWMzIHZCaXRhbmdlbnQ7CgkjZW5kaWYKI2VuZGlmYCxJcHI9YCNpZm5kZWYgRkxBVF9TSEFERUQKCXZhcnlpbmcgdmVjMyB2Tm9ybWFsOwoJI2lmZGVmIFVTRV9UQU5HRU5UCgkJdmFyeWluZyB2ZWMzIHZUYW5nZW50OwoJCXZhcnlpbmcgdmVjMyB2Qml0YW5nZW50OwoJI2VuZGlmCiNlbmRpZmAsTHByPWAjaWZuZGVmIEZMQVRfU0hBREVECgl2Tm9ybWFsID0gbm9ybWFsaXplKCB0cmFuc2Zvcm1lZE5vcm1hbCApOwoJI2lmZGVmIFVTRV9UQU5HRU5UCgkJdlRhbmdlbnQgPSBub3JtYWxpemUoIHRyYW5zZm9ybWVkVGFuZ2VudCApOwoJCXZCaXRhbmdlbnQgPSBub3JtYWxpemUoIGNyb3NzKCB2Tm9ybWFsLCB2VGFuZ2VudCApICogdGFuZ2VudC53ICk7CgkjZW5kaWYKI2VuZGlmYCxrcHI9YCNpZmRlZiBVU0VfTk9STUFMTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBub3JtYWxNYXA7Cgl1bmlmb3JtIHZlYzIgbm9ybWFsU2NhbGU7CiNlbmRpZgojaWZkZWYgT0JKRUNUU1BBQ0VfTk9STUFMTUFQCgl1bmlmb3JtIG1hdDMgbm9ybWFsTWF0cml4OwojZW5kaWYKI2lmICEgZGVmaW5lZCAoIFVTRV9UQU5HRU5UICkgJiYgKCBkZWZpbmVkICggVEFOR0VOVFNQQUNFX05PUk1BTE1BUCApIHx8IGRlZmluZWQgKCBVU0VfQ0xFQVJDT0FUX05PUk1BTE1BUCApICkKCXZlYzMgcGVydHVyYk5vcm1hbDJBcmIoIHZlYzMgZXllX3BvcywgdmVjMyBzdXJmX25vcm0sIHZlYzMgbWFwTiwgZmxvYXQgZmFjZURpcmVjdGlvbiApIHsKCQl2ZWMzIHEwID0gdmVjMyggZEZkeCggZXllX3Bvcy54ICksIGRGZHgoIGV5ZV9wb3MueSApLCBkRmR4KCBleWVfcG9zLnogKSApOwoJCXZlYzMgcTEgPSB2ZWMzKCBkRmR5KCBleWVfcG9zLnggKSwgZEZkeSggZXllX3Bvcy55ICksIGRGZHkoIGV5ZV9wb3MueiApICk7CgkJdmVjMiBzdDAgPSBkRmR4KCB2VXYuc3QgKTsKCQl2ZWMyIHN0MSA9IGRGZHkoIHZVdi5zdCApOwoJCXZlYzMgTiA9IHN1cmZfbm9ybTsKCQl2ZWMzIHExcGVycCA9IGNyb3NzKCBxMSwgTiApOwoJCXZlYzMgcTBwZXJwID0gY3Jvc3MoIE4sIHEwICk7CgkJdmVjMyBUID0gcTFwZXJwICogc3QwLnggKyBxMHBlcnAgKiBzdDEueDsKCQl2ZWMzIEIgPSBxMXBlcnAgKiBzdDAueSArIHEwcGVycCAqIHN0MS55OwoJCWZsb2F0IGRldCA9IG1heCggZG90KCBULCBUICksIGRvdCggQiwgQiApICk7CgkJZmxvYXQgc2NhbGUgPSAoIGRldCA9PSAwLjAgKSA/IDAuMCA6IGZhY2VEaXJlY3Rpb24gKiBpbnZlcnNlc3FydCggZGV0ICk7CgkJcmV0dXJuIG5vcm1hbGl6ZSggVCAqICggbWFwTi54ICogc2NhbGUgKSArIEIgKiAoIG1hcE4ueSAqIHNjYWxlICkgKyBOICogbWFwTi56ICk7Cgl9CiNlbmRpZmAsUnByPWAjaWZkZWYgVVNFX0NMRUFSQ09BVAoJdmVjMyBjbGVhcmNvYXROb3JtYWwgPSBnZW9tZXRyeU5vcm1hbDsKI2VuZGlmYCxOcHI9YCNpZmRlZiBVU0VfQ0xFQVJDT0FUX05PUk1BTE1BUAoJdmVjMyBjbGVhcmNvYXRNYXBOID0gdGV4dHVyZTJEKCBjbGVhcmNvYXROb3JtYWxNYXAsIHZVdiApLnh5eiAqIDIuMCAtIDEuMDsKCWNsZWFyY29hdE1hcE4ueHkgKj0gY2xlYXJjb2F0Tm9ybWFsU2NhbGU7CgkjaWZkZWYgVVNFX1RBTkdFTlQKCQljbGVhcmNvYXROb3JtYWwgPSBub3JtYWxpemUoIHZUQk4gKiBjbGVhcmNvYXRNYXBOICk7CgkjZWxzZQoJCWNsZWFyY29hdE5vcm1hbCA9IHBlcnR1cmJOb3JtYWwyQXJiKCAtIHZWaWV3UG9zaXRpb24sIGNsZWFyY29hdE5vcm1hbCwgY2xlYXJjb2F0TWFwTiwgZmFjZURpcmVjdGlvbiApOwoJI2VuZGlmCiNlbmRpZmAsRHByPWAjaWZkZWYgVVNFX0NMRUFSQ09BVE1BUAoJdW5pZm9ybSBzYW1wbGVyMkQgY2xlYXJjb2F0TWFwOwojZW5kaWYKI2lmZGVmIFVTRV9DTEVBUkNPQVRfUk9VR0hORVNTTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCBjbGVhcmNvYXRSb3VnaG5lc3NNYXA7CiNlbmRpZgojaWZkZWYgVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVAKCXVuaWZvcm0gc2FtcGxlcjJEIGNsZWFyY29hdE5vcm1hbE1hcDsKCXVuaWZvcm0gdmVjMiBjbGVhcmNvYXROb3JtYWxTY2FsZTsKI2VuZGlmYCxPcHI9YCNpZmRlZiBPUEFRVUUKZGlmZnVzZUNvbG9yLmEgPSAxLjA7CiNlbmRpZgojaWZkZWYgVVNFX1RSQU5TTUlTU0lPTgpkaWZmdXNlQ29sb3IuYSAqPSB0cmFuc21pc3Npb25BbHBoYSArIDAuMTsKI2VuZGlmCmdsX0ZyYWdDb2xvciA9IHZlYzQoIG91dGdvaW5nTGlnaHQsIGRpZmZ1c2VDb2xvci5hICk7YCx6cHI9YHZlYzMgcGFja05vcm1hbFRvUkdCKCBjb25zdCBpbiB2ZWMzIG5vcm1hbCApIHsKCXJldHVybiBub3JtYWxpemUoIG5vcm1hbCApICogMC41ICsgMC41Owp9CnZlYzMgdW5wYWNrUkdCVG9Ob3JtYWwoIGNvbnN0IGluIHZlYzMgcmdiICkgewoJcmV0dXJuIDIuMCAqIHJnYi54eXogLSAxLjA7Cn0KY29uc3QgZmxvYXQgUGFja1Vwc2NhbGUgPSAyNTYuIC8gMjU1Ljtjb25zdCBmbG9hdCBVbnBhY2tEb3duc2NhbGUgPSAyNTUuIC8gMjU2LjsKY29uc3QgdmVjMyBQYWNrRmFjdG9ycyA9IHZlYzMoIDI1Ni4gKiAyNTYuICogMjU2LiwgMjU2LiAqIDI1Ni4sIDI1Ni4gKTsKY29uc3QgdmVjNCBVbnBhY2tGYWN0b3JzID0gVW5wYWNrRG93bnNjYWxlIC8gdmVjNCggUGFja0ZhY3RvcnMsIDEuICk7CmNvbnN0IGZsb2F0IFNoaWZ0UmlnaHQ4ID0gMS4gLyAyNTYuOwp2ZWM0IHBhY2tEZXB0aFRvUkdCQSggY29uc3QgaW4gZmxvYXQgdiApIHsKCXZlYzQgciA9IHZlYzQoIGZyYWN0KCB2ICogUGFja0ZhY3RvcnMgKSwgdiApOwoJci55encgLT0gci54eXogKiBTaGlmdFJpZ2h0ODsJcmV0dXJuIHIgKiBQYWNrVXBzY2FsZTsKfQpmbG9hdCB1bnBhY2tSR0JBVG9EZXB0aCggY29uc3QgaW4gdmVjNCB2ICkgewoJcmV0dXJuIGRvdCggdiwgVW5wYWNrRmFjdG9ycyApOwp9CnZlYzQgcGFjazJIYWxmVG9SR0JBKCB2ZWMyIHYgKSB7Cgl2ZWM0IHIgPSB2ZWM0KCB2LngsIGZyYWN0KCB2LnggKiAyNTUuMCApLCB2LnksIGZyYWN0KCB2LnkgKiAyNTUuMCApICk7CglyZXR1cm4gdmVjNCggci54IC0gci55IC8gMjU1LjAsIHIueSwgci56IC0gci53IC8gMjU1LjAsIHIudyApOwp9CnZlYzIgdW5wYWNrUkdCQVRvMkhhbGYoIHZlYzQgdiApIHsKCXJldHVybiB2ZWMyKCB2LnggKyAoIHYueSAvIDI1NS4wICksIHYueiArICggdi53IC8gMjU1LjAgKSApOwp9CmZsb2F0IHZpZXdaVG9PcnRob2dyYXBoaWNEZXB0aCggY29uc3QgaW4gZmxvYXQgdmlld1osIGNvbnN0IGluIGZsb2F0IG5lYXIsIGNvbnN0IGluIGZsb2F0IGZhciApIHsKCXJldHVybiAoIHZpZXdaICsgbmVhciApIC8gKCBuZWFyIC0gZmFyICk7Cn0KZmxvYXQgb3J0aG9ncmFwaGljRGVwdGhUb1ZpZXdaKCBjb25zdCBpbiBmbG9hdCBsaW5lYXJDbGlwWiwgY29uc3QgaW4gZmxvYXQgbmVhciwgY29uc3QgaW4gZmxvYXQgZmFyICkgewoJcmV0dXJuIGxpbmVhckNsaXBaICogKCBuZWFyIC0gZmFyICkgLSBuZWFyOwp9CmZsb2F0IHZpZXdaVG9QZXJzcGVjdGl2ZURlcHRoKCBjb25zdCBpbiBmbG9hdCB2aWV3WiwgY29uc3QgaW4gZmxvYXQgbmVhciwgY29uc3QgaW4gZmxvYXQgZmFyICkgewoJcmV0dXJuICggKCBuZWFyICsgdmlld1ogKSAqIGZhciApIC8gKCAoIGZhciAtIG5lYXIgKSAqIHZpZXdaICk7Cn0KZmxvYXQgcGVyc3BlY3RpdmVEZXB0aFRvVmlld1ooIGNvbnN0IGluIGZsb2F0IGludkNsaXBaLCBjb25zdCBpbiBmbG9hdCBuZWFyLCBjb25zdCBpbiBmbG9hdCBmYXIgKSB7CglyZXR1cm4gKCBuZWFyICogZmFyICkgLyAoICggZmFyIC0gbmVhciApICogaW52Q2xpcFogLSBmYXIgKTsKfWAsRnByPWAjaWZkZWYgUFJFTVVMVElQTElFRF9BTFBIQQoJZ2xfRnJhZ0NvbG9yLnJnYiAqPSBnbF9GcmFnQ29sb3IuYTsKI2VuZGlmYCxCcHI9YHZlYzQgbXZQb3NpdGlvbiA9IHZlYzQoIHRyYW5zZm9ybWVkLCAxLjAgKTsKI2lmZGVmIFVTRV9JTlNUQU5DSU5HCgltdlBvc2l0aW9uID0gaW5zdGFuY2VNYXRyaXggKiBtdlBvc2l0aW9uOwojZW5kaWYKbXZQb3NpdGlvbiA9IG1vZGVsVmlld01hdHJpeCAqIG12UG9zaXRpb247CmdsX1Bvc2l0aW9uID0gcHJvamVjdGlvbk1hdHJpeCAqIG12UG9zaXRpb247YCxIcHI9YCNpZmRlZiBESVRIRVJJTkcKCWdsX0ZyYWdDb2xvci5yZ2IgPSBkaXRoZXJpbmcoIGdsX0ZyYWdDb2xvci5yZ2IgKTsKI2VuZGlmYCxWcHI9YCNpZmRlZiBESVRIRVJJTkcKCXZlYzMgZGl0aGVyaW5nKCB2ZWMzIGNvbG9yICkgewoJCWZsb2F0IGdyaWRfcG9zaXRpb24gPSByYW5kKCBnbF9GcmFnQ29vcmQueHkgKTsKCQl2ZWMzIGRpdGhlcl9zaGlmdF9SR0IgPSB2ZWMzKCAwLjI1IC8gMjU1LjAsIC0wLjI1IC8gMjU1LjAsIDAuMjUgLyAyNTUuMCApOwoJCWRpdGhlcl9zaGlmdF9SR0IgPSBtaXgoIDIuMCAqIGRpdGhlcl9zaGlmdF9SR0IsIC0yLjAgKiBkaXRoZXJfc2hpZnRfUkdCLCBncmlkX3Bvc2l0aW9uICk7CgkJcmV0dXJuIGNvbG9yICsgZGl0aGVyX3NoaWZ0X1JHQjsKCX0KI2VuZGlmYCxVcHI9YGZsb2F0IHJvdWdobmVzc0ZhY3RvciA9IHJvdWdobmVzczsKI2lmZGVmIFVTRV9ST1VHSE5FU1NNQVAKCXZlYzQgdGV4ZWxSb3VnaG5lc3MgPSB0ZXh0dXJlMkQoIHJvdWdobmVzc01hcCwgdlV2ICk7Cglyb3VnaG5lc3NGYWN0b3IgKj0gdGV4ZWxSb3VnaG5lc3MuZzsKI2VuZGlmYCxxcHI9YCNpZmRlZiBVU0VfUk9VR0hORVNTTUFQCgl1bmlmb3JtIHNhbXBsZXIyRCByb3VnaG5lc3NNYXA7CiNlbmRpZmAsR3ByPWAjaWZkZWYgVVNFX1NIQURPV01BUAoJI2lmIE5VTV9ESVJfTElHSFRfU0hBRE9XUyA+IDAKCQl1bmlmb3JtIHNhbXBsZXIyRCBkaXJlY3Rpb25hbFNoYWRvd01hcFsgTlVNX0RJUl9MSUdIVF9TSEFET1dTIF07CgkJdmFyeWluZyB2ZWM0IHZEaXJlY3Rpb25hbFNoYWRvd0Nvb3JkWyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTsKCQlzdHJ1Y3QgRGlyZWN0aW9uYWxMaWdodFNoYWRvdyB7CgkJCWZsb2F0IHNoYWRvd0JpYXM7CgkJCWZsb2F0IHNoYWRvd05vcm1hbEJpYXM7CgkJCWZsb2F0IHNoYWRvd1JhZGl1czsKCQkJdmVjMiBzaGFkb3dNYXBTaXplOwoJCX07CgkJdW5pZm9ybSBEaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzWyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTsKCSNlbmRpZgoJI2lmIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwCgkJdW5pZm9ybSBzYW1wbGVyMkQgc3BvdFNoYWRvd01hcFsgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyBdOwoJCXZhcnlpbmcgdmVjNCB2U3BvdFNoYWRvd0Nvb3JkWyBOVU1fU1BPVF9MSUdIVF9TSEFET1dTIF07CgkJc3RydWN0IFNwb3RMaWdodFNoYWRvdyB7CgkJCWZsb2F0IHNoYWRvd0JpYXM7CgkJCWZsb2F0IHNoYWRvd05vcm1hbEJpYXM7CgkJCWZsb2F0IHNoYWRvd1JhZGl1czsKCQkJdmVjMiBzaGFkb3dNYXBTaXplOwoJCX07CgkJdW5pZm9ybSBTcG90TGlnaHRTaGFkb3cgc3BvdExpZ2h0U2hhZG93c1sgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyBdOwoJI2VuZGlmCgkjaWYgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgPiAwCgkJdW5pZm9ybSBzYW1wbGVyMkQgcG9pbnRTaGFkb3dNYXBbIE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTIF07CgkJdmFyeWluZyB2ZWM0IHZQb2ludFNoYWRvd0Nvb3JkWyBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyBdOwoJCXN0cnVjdCBQb2ludExpZ2h0U2hhZG93IHsKCQkJZmxvYXQgc2hhZG93QmlhczsKCQkJZmxvYXQgc2hhZG93Tm9ybWFsQmlhczsKCQkJZmxvYXQgc2hhZG93UmFkaXVzOwoJCQl2ZWMyIHNoYWRvd01hcFNpemU7CgkJCWZsb2F0IHNoYWRvd0NhbWVyYU5lYXI7CgkJCWZsb2F0IHNoYWRvd0NhbWVyYUZhcjsKCQl9OwoJCXVuaWZvcm0gUG9pbnRMaWdodFNoYWRvdyBwb2ludExpZ2h0U2hhZG93c1sgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgXTsKCSNlbmRpZgoJZmxvYXQgdGV4dHVyZTJEQ29tcGFyZSggc2FtcGxlcjJEIGRlcHRocywgdmVjMiB1diwgZmxvYXQgY29tcGFyZSApIHsKCQlyZXR1cm4gc3RlcCggY29tcGFyZSwgdW5wYWNrUkdCQVRvRGVwdGgoIHRleHR1cmUyRCggZGVwdGhzLCB1diApICkgKTsKCX0KCXZlYzIgdGV4dHVyZTJERGlzdHJpYnV0aW9uKCBzYW1wbGVyMkQgc2hhZG93LCB2ZWMyIHV2ICkgewoJCXJldHVybiB1bnBhY2tSR0JBVG8ySGFsZiggdGV4dHVyZTJEKCBzaGFkb3csIHV2ICkgKTsKCX0KCWZsb2F0IFZTTVNoYWRvdyAoc2FtcGxlcjJEIHNoYWRvdywgdmVjMiB1diwgZmxvYXQgY29tcGFyZSApewoJCWZsb2F0IG9jY2x1c2lvbiA9IDEuMDsKCQl2ZWMyIGRpc3RyaWJ1dGlvbiA9IHRleHR1cmUyRERpc3RyaWJ1dGlvbiggc2hhZG93LCB1diApOwoJCWZsb2F0IGhhcmRfc2hhZG93ID0gc3RlcCggY29tcGFyZSAsIGRpc3RyaWJ1dGlvbi54ICk7CgkJaWYgKGhhcmRfc2hhZG93ICE9IDEuMCApIHsKCQkJZmxvYXQgZGlzdGFuY2UgPSBjb21wYXJlIC0gZGlzdHJpYnV0aW9uLnggOwoJCQlmbG9hdCB2YXJpYW5jZSA9IG1heCggMC4wMDAwMCwgZGlzdHJpYnV0aW9uLnkgKiBkaXN0cmlidXRpb24ueSApOwoJCQlmbG9hdCBzb2Z0bmVzc19wcm9iYWJpbGl0eSA9IHZhcmlhbmNlIC8gKHZhcmlhbmNlICsgZGlzdGFuY2UgKiBkaXN0YW5jZSApOwkJCXNvZnRuZXNzX3Byb2JhYmlsaXR5ID0gY2xhbXAoICggc29mdG5lc3NfcHJvYmFiaWxpdHkgLSAwLjMgKSAvICggMC45NSAtIDAuMyApLCAwLjAsIDEuMCApOwkJCW9jY2x1c2lvbiA9IGNsYW1wKCBtYXgoIGhhcmRfc2hhZG93LCBzb2Z0bmVzc19wcm9iYWJpbGl0eSApLCAwLjAsIDEuMCApOwoJCX0KCQlyZXR1cm4gb2NjbHVzaW9uOwoJfQoJZmxvYXQgZ2V0U2hhZG93KCBzYW1wbGVyMkQgc2hhZG93TWFwLCB2ZWMyIHNoYWRvd01hcFNpemUsIGZsb2F0IHNoYWRvd0JpYXMsIGZsb2F0IHNoYWRvd1JhZGl1cywgdmVjNCBzaGFkb3dDb29yZCApIHsKCQlmbG9hdCBzaGFkb3cgPSAxLjA7CgkJc2hhZG93Q29vcmQueHl6IC89IHNoYWRvd0Nvb3JkLnc7CgkJc2hhZG93Q29vcmQueiArPSBzaGFkb3dCaWFzOwoJCWJ2ZWM0IGluRnJ1c3R1bVZlYyA9IGJ2ZWM0ICggc2hhZG93Q29vcmQueCA+PSAwLjAsIHNoYWRvd0Nvb3JkLnggPD0gMS4wLCBzaGFkb3dDb29yZC55ID49IDAuMCwgc2hhZG93Q29vcmQueSA8PSAxLjAgKTsKCQlib29sIGluRnJ1c3R1bSA9IGFsbCggaW5GcnVzdHVtVmVjICk7CgkJYnZlYzIgZnJ1c3R1bVRlc3RWZWMgPSBidmVjMiggaW5GcnVzdHVtLCBzaGFkb3dDb29yZC56IDw9IDEuMCApOwoJCWJvb2wgZnJ1c3R1bVRlc3QgPSBhbGwoIGZydXN0dW1UZXN0VmVjICk7CgkJaWYgKCBmcnVzdHVtVGVzdCApIHsKCQkjaWYgZGVmaW5lZCggU0hBRE9XTUFQX1RZUEVfUENGICkKCQkJdmVjMiB0ZXhlbFNpemUgPSB2ZWMyKCAxLjAgKSAvIHNoYWRvd01hcFNpemU7CgkJCWZsb2F0IGR4MCA9IC0gdGV4ZWxTaXplLnggKiBzaGFkb3dSYWRpdXM7CgkJCWZsb2F0IGR5MCA9IC0gdGV4ZWxTaXplLnkgKiBzaGFkb3dSYWRpdXM7CgkJCWZsb2F0IGR4MSA9ICsgdGV4ZWxTaXplLnggKiBzaGFkb3dSYWRpdXM7CgkJCWZsb2F0IGR5MSA9ICsgdGV4ZWxTaXplLnkgKiBzaGFkb3dSYWRpdXM7CgkJCWZsb2F0IGR4MiA9IGR4MCAvIDIuMDsKCQkJZmxvYXQgZHkyID0gZHkwIC8gMi4wOwoJCQlmbG9hdCBkeDMgPSBkeDEgLyAyLjA7CgkJCWZsb2F0IGR5MyA9IGR5MSAvIDIuMDsKCQkJc2hhZG93ID0gKAoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MCwgZHkwICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTAgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDEsIGR5MCApLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MiwgZHkyICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTIgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDMsIGR5MiApLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MCwgMC4wICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggZHgyLCAwLjAgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggZHgzLCAwLjAgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDEsIDAuMCApLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MiwgZHkzICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTMgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDMsIGR5MyApLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MCwgZHkxICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTEgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDEsIGR5MSApLCBzaGFkb3dDb29yZC56ICkKCQkJKSAqICggMS4wIC8gMTcuMCApOwoJCSNlbGlmIGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1BDRl9TT0ZUICkKCQkJdmVjMiB0ZXhlbFNpemUgPSB2ZWMyKCAxLjAgKSAvIHNoYWRvd01hcFNpemU7CgkJCWZsb2F0IGR4ID0gdGV4ZWxTaXplLng7CgkJCWZsb2F0IGR5ID0gdGV4ZWxTaXplLnk7CgkJCXZlYzIgdXYgPSBzaGFkb3dDb29yZC54eTsKCQkJdmVjMiBmID0gZnJhY3QoIHV2ICogc2hhZG93TWFwU2l6ZSArIDAuNSApOwoJCQl1diAtPSBmICogdGV4ZWxTaXplOwoJCQlzaGFkb3cgPSAoCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2LCBzaGFkb3dDb29yZC56ICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIGR4LCAwLjAgKSwgc2hhZG93Q29vcmQueiApICsKCQkJCXRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAwLjAsIGR5ICksIHNoYWRvd0Nvb3JkLnogKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdGV4ZWxTaXplLCBzaGFkb3dDb29yZC56ICkgKwoJCQkJbWl4KCB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggLWR4LCAwLjAgKSwgc2hhZG93Q29vcmQueiApLCAKCQkJCQkgdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIDIuMCAqIGR4LCAwLjAgKSwgc2hhZG93Q29vcmQueiApLAoJCQkJCSBmLnggKSArCgkJCQltaXgoIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAtZHgsIGR5ICksIHNoYWRvd0Nvb3JkLnogKSwgCgkJCQkJIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAyLjAgKiBkeCwgZHkgKSwgc2hhZG93Q29vcmQueiApLAoJCQkJCSBmLnggKSArCgkJCQltaXgoIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAwLjAsIC1keSApLCBzaGFkb3dDb29yZC56ICksIAoJCQkJCSB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggMC4wLCAyLjAgKiBkeSApLCBzaGFkb3dDb29yZC56ICksCgkJCQkJIGYueSApICsKCQkJCW1peCggdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIGR4LCAtZHkgKSwgc2hhZG93Q29vcmQueiApLCAKCQkJCQkgdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIGR4LCAyLjAgKiBkeSApLCBzaGFkb3dDb29yZC56ICksCgkJCQkJIGYueSApICsKCQkJCW1peCggbWl4KCB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggLWR4LCAtZHkgKSwgc2hhZG93Q29vcmQueiApLCAKCQkJCQkJICB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggMi4wICogZHgsIC1keSApLCBzaGFkb3dDb29yZC56ICksCgkJCQkJCSAgZi54ICksCgkJCQkJIG1peCggdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIC1keCwgMi4wICogZHkgKSwgc2hhZG93Q29vcmQueiApLCAKCQkJCQkJICB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggMi4wICogZHgsIDIuMCAqIGR5ICksIHNoYWRvd0Nvb3JkLnogKSwKCQkJCQkJICBmLnggKSwKCQkJCQkgZi55ICkKCQkJKSAqICggMS4wIC8gOS4wICk7CgkJI2VsaWYgZGVmaW5lZCggU0hBRE9XTUFQX1RZUEVfVlNNICkKCQkJc2hhZG93ID0gVlNNU2hhZG93KCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5LCBzaGFkb3dDb29yZC56ICk7CgkJI2Vsc2UKCQkJc2hhZG93ID0gdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSwgc2hhZG93Q29vcmQueiApOwoJCSNlbmRpZgoJCX0KCQlyZXR1cm4gc2hhZG93OwoJfQoJdmVjMiBjdWJlVG9VViggdmVjMyB2LCBmbG9hdCB0ZXhlbFNpemVZICkgewoJCXZlYzMgYWJzViA9IGFicyggdiApOwoJCWZsb2F0IHNjYWxlVG9DdWJlID0gMS4wIC8gbWF4KCBhYnNWLngsIG1heCggYWJzVi55LCBhYnNWLnogKSApOwoJCWFic1YgKj0gc2NhbGVUb0N1YmU7CgkJdiAqPSBzY2FsZVRvQ3ViZSAqICggMS4wIC0gMi4wICogdGV4ZWxTaXplWSApOwoJCXZlYzIgcGxhbmFyID0gdi54eTsKCQlmbG9hdCBhbG1vc3RBVGV4ZWwgPSAxLjUgKiB0ZXhlbFNpemVZOwoJCWZsb2F0IGFsbW9zdE9uZSA9IDEuMCAtIGFsbW9zdEFUZXhlbDsKCQlpZiAoIGFic1YueiA+PSBhbG1vc3RPbmUgKSB7CgkJCWlmICggdi56ID4gMC4wICkKCQkJCXBsYW5hci54ID0gNC4wIC0gdi54OwoJCX0gZWxzZSBpZiAoIGFic1YueCA+PSBhbG1vc3RPbmUgKSB7CgkJCWZsb2F0IHNpZ25YID0gc2lnbiggdi54ICk7CgkJCXBsYW5hci54ID0gdi56ICogc2lnblggKyAyLjAgKiBzaWduWDsKCQl9IGVsc2UgaWYgKCBhYnNWLnkgPj0gYWxtb3N0T25lICkgewoJCQlmbG9hdCBzaWduWSA9IHNpZ24oIHYueSApOwoJCQlwbGFuYXIueCA9IHYueCArIDIuMCAqIHNpZ25ZICsgMi4wOwoJCQlwbGFuYXIueSA9IHYueiAqIHNpZ25ZIC0gMi4wOwoJCX0KCQlyZXR1cm4gdmVjMiggMC4xMjUsIDAuMjUgKSAqIHBsYW5hciArIHZlYzIoIDAuMzc1LCAwLjc1ICk7Cgl9CglmbG9hdCBnZXRQb2ludFNoYWRvdyggc2FtcGxlcjJEIHNoYWRvd01hcCwgdmVjMiBzaGFkb3dNYXBTaXplLCBmbG9hdCBzaGFkb3dCaWFzLCBmbG9hdCBzaGFkb3dSYWRpdXMsIHZlYzQgc2hhZG93Q29vcmQsIGZsb2F0IHNoYWRvd0NhbWVyYU5lYXIsIGZsb2F0IHNoYWRvd0NhbWVyYUZhciApIHsKCQl2ZWMyIHRleGVsU2l6ZSA9IHZlYzIoIDEuMCApIC8gKCBzaGFkb3dNYXBTaXplICogdmVjMiggNC4wLCAyLjAgKSApOwoJCXZlYzMgbGlnaHRUb1Bvc2l0aW9uID0gc2hhZG93Q29vcmQueHl6OwoJCWZsb2F0IGRwID0gKCBsZW5ndGgoIGxpZ2h0VG9Qb3NpdGlvbiApIC0gc2hhZG93Q2FtZXJhTmVhciApIC8gKCBzaGFkb3dDYW1lcmFGYXIgLSBzaGFkb3dDYW1lcmFOZWFyICk7CQlkcCArPSBzaGFkb3dCaWFzOwoJCXZlYzMgYmQzRCA9IG5vcm1hbGl6ZSggbGlnaHRUb1Bvc2l0aW9uICk7CgkJI2lmIGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1BDRiApIHx8IGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1BDRl9TT0ZUICkgfHwgZGVmaW5lZCggU0hBRE9XTUFQX1RZUEVfVlNNICkKCQkJdmVjMiBvZmZzZXQgPSB2ZWMyKCAtIDEsIDEgKSAqIHNoYWRvd1JhZGl1cyAqIHRleGVsU2l6ZS55OwoJCQlyZXR1cm4gKAoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC54eXksIHRleGVsU2l6ZS55ICksIGRwICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC55eXksIHRleGVsU2l6ZS55ICksIGRwICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC54eXgsIHRleGVsU2l6ZS55ICksIGRwICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC55eXgsIHRleGVsU2l6ZS55ICksIGRwICkgKwoJCQkJdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCwgdGV4ZWxTaXplLnkgKSwgZHAgKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnh4eSwgdGV4ZWxTaXplLnkgKSwgZHAgKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnl4eSwgdGV4ZWxTaXplLnkgKSwgZHAgKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnh4eCwgdGV4ZWxTaXplLnkgKSwgZHAgKSArCgkJCQl0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnl4eCwgdGV4ZWxTaXplLnkgKSwgZHAgKQoJCQkpICogKCAxLjAgLyA5LjAgKTsKCQkjZWxzZQoJCQlyZXR1cm4gdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCwgdGV4ZWxTaXplLnkgKSwgZHAgKTsKCQkjZW5kaWYKCX0KI2VuZGlmYCxXcHI9YCNpZmRlZiBVU0VfU0hBRE9XTUFQCgkjaWYgTlVNX0RJUl9MSUdIVF9TSEFET1dTID4gMAoJCXVuaWZvcm0gbWF0NCBkaXJlY3Rpb25hbFNoYWRvd01hdHJpeFsgTlVNX0RJUl9MSUdIVF9TSEFET1dTIF07CgkJdmFyeWluZyB2ZWM0IHZEaXJlY3Rpb25hbFNoYWRvd0Nvb3JkWyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTsKCQlzdHJ1Y3QgRGlyZWN0aW9uYWxMaWdodFNoYWRvdyB7CgkJCWZsb2F0IHNoYWRvd0JpYXM7CgkJCWZsb2F0IHNoYWRvd05vcm1hbEJpYXM7CgkJCWZsb2F0IHNoYWRvd1JhZGl1czsKCQkJdmVjMiBzaGFkb3dNYXBTaXplOwoJCX07CgkJdW5pZm9ybSBEaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzWyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTsKCSNlbmRpZgoJI2lmIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwCgkJdW5pZm9ybSBtYXQ0IHNwb3RTaGFkb3dNYXRyaXhbIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgXTsKCQl2YXJ5aW5nIHZlYzQgdlNwb3RTaGFkb3dDb29yZFsgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyBdOwoJCXN0cnVjdCBTcG90TGlnaHRTaGFkb3cgewoJCQlmbG9hdCBzaGFkb3dCaWFzOwoJCQlmbG9hdCBzaGFkb3dOb3JtYWxCaWFzOwoJCQlmbG9hdCBzaGFkb3dSYWRpdXM7CgkJCXZlYzIgc2hhZG93TWFwU2l6ZTsKCQl9OwoJCXVuaWZvcm0gU3BvdExpZ2h0U2hhZG93IHNwb3RMaWdodFNoYWRvd3NbIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgXTsKCSNlbmRpZgoJI2lmIE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTID4gMAoJCXVuaWZvcm0gbWF0NCBwb2ludFNoYWRvd01hdHJpeFsgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgXTsKCQl2YXJ5aW5nIHZlYzQgdlBvaW50U2hhZG93Q29vcmRbIE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTIF07CgkJc3RydWN0IFBvaW50TGlnaHRTaGFkb3cgewoJCQlmbG9hdCBzaGFkb3dCaWFzOwoJCQlmbG9hdCBzaGFkb3dOb3JtYWxCaWFzOwoJCQlmbG9hdCBzaGFkb3dSYWRpdXM7CgkJCXZlYzIgc2hhZG93TWFwU2l6ZTsKCQkJZmxvYXQgc2hhZG93Q2FtZXJhTmVhcjsKCQkJZmxvYXQgc2hhZG93Q2FtZXJhRmFyOwoJCX07CgkJdW5pZm9ybSBQb2ludExpZ2h0U2hhZG93IHBvaW50TGlnaHRTaGFkb3dzWyBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyBdOwoJI2VuZGlmCiNlbmRpZmAsWXByPWAjaWZkZWYgVVNFX1NIQURPV01BUAoJI2lmIE5VTV9ESVJfTElHSFRfU0hBRE9XUyA+IDAgfHwgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyA+IDAgfHwgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgPiAwCgkJdmVjMyBzaGFkb3dXb3JsZE5vcm1hbCA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIHRyYW5zZm9ybWVkTm9ybWFsLCB2aWV3TWF0cml4ICk7CgkJdmVjNCBzaGFkb3dXb3JsZFBvc2l0aW9uOwoJI2VuZGlmCgkjaWYgTlVNX0RJUl9MSUdIVF9TSEFET1dTID4gMAoJI3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydAoJZm9yICggaW50IGkgPSAwOyBpIDwgTlVNX0RJUl9MSUdIVF9TSEFET1dTOyBpICsrICkgewoJCXNoYWRvd1dvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uICsgdmVjNCggc2hhZG93V29ybGROb3JtYWwgKiBkaXJlY3Rpb25hbExpZ2h0U2hhZG93c1sgaSBdLnNoYWRvd05vcm1hbEJpYXMsIDAgKTsKCQl2RGlyZWN0aW9uYWxTaGFkb3dDb29yZFsgaSBdID0gZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXhbIGkgXSAqIHNoYWRvd1dvcmxkUG9zaXRpb247Cgl9CgkjcHJhZ21hIHVucm9sbF9sb29wX2VuZAoJI2VuZGlmCgkjaWYgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyA+IDAKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9TUE9UX0xJR0hUX1NIQURPV1M7IGkgKysgKSB7CgkJc2hhZG93V29ybGRQb3NpdGlvbiA9IHdvcmxkUG9zaXRpb24gKyB2ZWM0KCBzaGFkb3dXb3JsZE5vcm1hbCAqIHNwb3RMaWdodFNoYWRvd3NbIGkgXS5zaGFkb3dOb3JtYWxCaWFzLCAwICk7CgkJdlNwb3RTaGFkb3dDb29yZFsgaSBdID0gc3BvdFNoYWRvd01hdHJpeFsgaSBdICogc2hhZG93V29ybGRQb3NpdGlvbjsKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCgkjZW5kaWYKCSNpZiBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyA+IDAKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTOyBpICsrICkgewoJCXNoYWRvd1dvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uICsgdmVjNCggc2hhZG93V29ybGROb3JtYWwgKiBwb2ludExpZ2h0U2hhZG93c1sgaSBdLnNoYWRvd05vcm1hbEJpYXMsIDAgKTsKCQl2UG9pbnRTaGFkb3dDb29yZFsgaSBdID0gcG9pbnRTaGFkb3dNYXRyaXhbIGkgXSAqIHNoYWRvd1dvcmxkUG9zaXRpb247Cgl9CgkjcHJhZ21hIHVucm9sbF9sb29wX2VuZAoJI2VuZGlmCiNlbmRpZmAsanByPWBmbG9hdCBnZXRTaGFkb3dNYXNrKCkgewoJZmxvYXQgc2hhZG93ID0gMS4wOwoJI2lmZGVmIFVTRV9TSEFET1dNQVAKCSNpZiBOVU1fRElSX0xJR0hUX1NIQURPV1MgPiAwCglEaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHQ7CgkjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0Cglmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fRElSX0xJR0hUX1NIQURPV1M7IGkgKysgKSB7CgkJZGlyZWN0aW9uYWxMaWdodCA9IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzWyBpIF07CgkJc2hhZG93ICo9IHJlY2VpdmVTaGFkb3cgPyBnZXRTaGFkb3coIGRpcmVjdGlvbmFsU2hhZG93TWFwWyBpIF0sIGRpcmVjdGlvbmFsTGlnaHQuc2hhZG93TWFwU2l6ZSwgZGlyZWN0aW9uYWxMaWdodC5zaGFkb3dCaWFzLCBkaXJlY3Rpb25hbExpZ2h0LnNoYWRvd1JhZGl1cywgdkRpcmVjdGlvbmFsU2hhZG93Q29vcmRbIGkgXSApIDogMS4wOwoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCSNlbmRpZgoJI2lmIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwCglTcG90TGlnaHRTaGFkb3cgc3BvdExpZ2h0OwoJI3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydAoJZm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1NQT1RfTElHSFRfU0hBRE9XUzsgaSArKyApIHsKCQlzcG90TGlnaHQgPSBzcG90TGlnaHRTaGFkb3dzWyBpIF07CgkJc2hhZG93ICo9IHJlY2VpdmVTaGFkb3cgPyBnZXRTaGFkb3coIHNwb3RTaGFkb3dNYXBbIGkgXSwgc3BvdExpZ2h0LnNoYWRvd01hcFNpemUsIHNwb3RMaWdodC5zaGFkb3dCaWFzLCBzcG90TGlnaHQuc2hhZG93UmFkaXVzLCB2U3BvdFNoYWRvd0Nvb3JkWyBpIF0gKSA6IDEuMDsKCX0KCSNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kCgkjZW5kaWYKCSNpZiBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyA+IDAKCVBvaW50TGlnaHRTaGFkb3cgcG9pbnRMaWdodDsKCSNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQKCWZvciAoIGludCBpID0gMDsgaSA8IE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTOyBpICsrICkgewoJCXBvaW50TGlnaHQgPSBwb2ludExpZ2h0U2hhZG93c1sgaSBdOwoJCXNoYWRvdyAqPSByZWNlaXZlU2hhZG93ID8gZ2V0UG9pbnRTaGFkb3coIHBvaW50U2hhZG93TWFwWyBpIF0sIHBvaW50TGlnaHQuc2hhZG93TWFwU2l6ZSwgcG9pbnRMaWdodC5zaGFkb3dCaWFzLCBwb2ludExpZ2h0LnNoYWRvd1JhZGl1cywgdlBvaW50U2hhZG93Q29vcmRbIGkgXSwgcG9pbnRMaWdodC5zaGFkb3dDYW1lcmFOZWFyLCBwb2ludExpZ2h0LnNoYWRvd0NhbWVyYUZhciApIDogMS4wOwoJfQoJI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQKCSNlbmRpZgoJI2VuZGlmCglyZXR1cm4gc2hhZG93Owp9YCxYcHI9YCNpZmRlZiBVU0VfU0tJTk5JTkcKCW1hdDQgYm9uZU1hdFggPSBnZXRCb25lTWF0cml4KCBza2luSW5kZXgueCApOwoJbWF0NCBib25lTWF0WSA9IGdldEJvbmVNYXRyaXgoIHNraW5JbmRleC55ICk7CgltYXQ0IGJvbmVNYXRaID0gZ2V0Qm9uZU1hdHJpeCggc2tpbkluZGV4LnogKTsKCW1hdDQgYm9uZU1hdFcgPSBnZXRCb25lTWF0cml4KCBza2luSW5kZXgudyApOwojZW5kaWZgLCRwcj1gI2lmZGVmIFVTRV9TS0lOTklORwoJdW5pZm9ybSBtYXQ0IGJpbmRNYXRyaXg7Cgl1bmlmb3JtIG1hdDQgYmluZE1hdHJpeEludmVyc2U7CgkjaWZkZWYgQk9ORV9URVhUVVJFCgkJdW5pZm9ybSBoaWdocCBzYW1wbGVyMkQgYm9uZVRleHR1cmU7CgkJdW5pZm9ybSBpbnQgYm9uZVRleHR1cmVTaXplOwoJCW1hdDQgZ2V0Qm9uZU1hdHJpeCggY29uc3QgaW4gZmxvYXQgaSApIHsKCQkJZmxvYXQgaiA9IGkgKiA0LjA7CgkJCWZsb2F0IHggPSBtb2QoIGosIGZsb2F0KCBib25lVGV4dHVyZVNpemUgKSApOwoJCQlmbG9hdCB5ID0gZmxvb3IoIGogLyBmbG9hdCggYm9uZVRleHR1cmVTaXplICkgKTsKCQkJZmxvYXQgZHggPSAxLjAgLyBmbG9hdCggYm9uZVRleHR1cmVTaXplICk7CgkJCWZsb2F0IGR5ID0gMS4wIC8gZmxvYXQoIGJvbmVUZXh0dXJlU2l6ZSApOwoJCQl5ID0gZHkgKiAoIHkgKyAwLjUgKTsKCQkJdmVjNCB2MSA9IHRleHR1cmUyRCggYm9uZVRleHR1cmUsIHZlYzIoIGR4ICogKCB4ICsgMC41ICksIHkgKSApOwoJCQl2ZWM0IHYyID0gdGV4dHVyZTJEKCBib25lVGV4dHVyZSwgdmVjMiggZHggKiAoIHggKyAxLjUgKSwgeSApICk7CgkJCXZlYzQgdjMgPSB0ZXh0dXJlMkQoIGJvbmVUZXh0dXJlLCB2ZWMyKCBkeCAqICggeCArIDIuNSApLCB5ICkgKTsKCQkJdmVjNCB2NCA9IHRleHR1cmUyRCggYm9uZVRleHR1cmUsIHZlYzIoIGR4ICogKCB4ICsgMy41ICksIHkgKSApOwoJCQltYXQ0IGJvbmUgPSBtYXQ0KCB2MSwgdjIsIHYzLCB2NCApOwoJCQlyZXR1cm4gYm9uZTsKCQl9CgkjZWxzZQoJCXVuaWZvcm0gbWF0NCBib25lTWF0cmljZXNbIE1BWF9CT05FUyBdOwoJCW1hdDQgZ2V0Qm9uZU1hdHJpeCggY29uc3QgaW4gZmxvYXQgaSApIHsKCQkJbWF0NCBib25lID0gYm9uZU1hdHJpY2VzWyBpbnQoaSkgXTsKCQkJcmV0dXJuIGJvbmU7CgkJfQoJI2VuZGlmCiNlbmRpZmAsS3ByPWAjaWZkZWYgVVNFX1NLSU5OSU5HCgl2ZWM0IHNraW5WZXJ0ZXggPSBiaW5kTWF0cml4ICogdmVjNCggdHJhbnNmb3JtZWQsIDEuMCApOwoJdmVjNCBza2lubmVkID0gdmVjNCggMC4wICk7Cglza2lubmVkICs9IGJvbmVNYXRYICogc2tpblZlcnRleCAqIHNraW5XZWlnaHQueDsKCXNraW5uZWQgKz0gYm9uZU1hdFkgKiBza2luVmVydGV4ICogc2tpbldlaWdodC55OwoJc2tpbm5lZCArPSBib25lTWF0WiAqIHNraW5WZXJ0ZXggKiBza2luV2VpZ2h0Lno7Cglza2lubmVkICs9IGJvbmVNYXRXICogc2tpblZlcnRleCAqIHNraW5XZWlnaHQudzsKCXRyYW5zZm9ybWVkID0gKCBiaW5kTWF0cml4SW52ZXJzZSAqIHNraW5uZWQgKS54eXo7CiNlbmRpZmAsWnByPWAjaWZkZWYgVVNFX1NLSU5OSU5HCgltYXQ0IHNraW5NYXRyaXggPSBtYXQ0KCAwLjAgKTsKCXNraW5NYXRyaXggKz0gc2tpbldlaWdodC54ICogYm9uZU1hdFg7Cglza2luTWF0cml4ICs9IHNraW5XZWlnaHQueSAqIGJvbmVNYXRZOwoJc2tpbk1hdHJpeCArPSBza2luV2VpZ2h0LnogKiBib25lTWF0WjsKCXNraW5NYXRyaXggKz0gc2tpbldlaWdodC53ICogYm9uZU1hdFc7Cglza2luTWF0cml4ID0gYmluZE1hdHJpeEludmVyc2UgKiBza2luTWF0cml4ICogYmluZE1hdHJpeDsKCW9iamVjdE5vcm1hbCA9IHZlYzQoIHNraW5NYXRyaXggKiB2ZWM0KCBvYmplY3ROb3JtYWwsIDAuMCApICkueHl6OwoJI2lmZGVmIFVTRV9UQU5HRU5UCgkJb2JqZWN0VGFuZ2VudCA9IHZlYzQoIHNraW5NYXRyaXggKiB2ZWM0KCBvYmplY3RUYW5nZW50LCAwLjAgKSApLnh5ejsKCSNlbmRpZgojZW5kaWZgLEpwcj1gZmxvYXQgc3BlY3VsYXJTdHJlbmd0aDsKI2lmZGVmIFVTRV9TUEVDVUxBUk1BUAoJdmVjNCB0ZXhlbFNwZWN1bGFyID0gdGV4dHVyZTJEKCBzcGVjdWxhck1hcCwgdlV2ICk7CglzcGVjdWxhclN0cmVuZ3RoID0gdGV4ZWxTcGVjdWxhci5yOwojZWxzZQoJc3BlY3VsYXJTdHJlbmd0aCA9IDEuMDsKI2VuZGlmYCxRcHI9YCNpZmRlZiBVU0VfU1BFQ1VMQVJNQVAKCXVuaWZvcm0gc2FtcGxlcjJEIHNwZWN1bGFyTWFwOwojZW5kaWZgLHRkcj1gI2lmIGRlZmluZWQoIFRPTkVfTUFQUElORyApCglnbF9GcmFnQ29sb3IucmdiID0gdG9uZU1hcHBpbmcoIGdsX0ZyYWdDb2xvci5yZ2IgKTsKI2VuZGlmYCxlZHI9YCNpZm5kZWYgc2F0dXJhdGUKI2RlZmluZSBzYXR1cmF0ZSggYSApIGNsYW1wKCBhLCAwLjAsIDEuMCApCiNlbmRpZgp1bmlmb3JtIGZsb2F0IHRvbmVNYXBwaW5nRXhwb3N1cmU7CnZlYzMgTGluZWFyVG9uZU1hcHBpbmcoIHZlYzMgY29sb3IgKSB7CglyZXR1cm4gdG9uZU1hcHBpbmdFeHBvc3VyZSAqIGNvbG9yOwp9CnZlYzMgUmVpbmhhcmRUb25lTWFwcGluZyggdmVjMyBjb2xvciApIHsKCWNvbG9yICo9IHRvbmVNYXBwaW5nRXhwb3N1cmU7CglyZXR1cm4gc2F0dXJhdGUoIGNvbG9yIC8gKCB2ZWMzKCAxLjAgKSArIGNvbG9yICkgKTsKfQp2ZWMzIE9wdGltaXplZENpbmVvblRvbmVNYXBwaW5nKCB2ZWMzIGNvbG9yICkgewoJY29sb3IgKj0gdG9uZU1hcHBpbmdFeHBvc3VyZTsKCWNvbG9yID0gbWF4KCB2ZWMzKCAwLjAgKSwgY29sb3IgLSAwLjAwNCApOwoJcmV0dXJuIHBvdyggKCBjb2xvciAqICggNi4yICogY29sb3IgKyAwLjUgKSApIC8gKCBjb2xvciAqICggNi4yICogY29sb3IgKyAxLjcgKSArIDAuMDYgKSwgdmVjMyggMi4yICkgKTsKfQp2ZWMzIFJSVEFuZE9EVEZpdCggdmVjMyB2ICkgewoJdmVjMyBhID0gdiAqICggdiArIDAuMDI0NTc4NiApIC0gMC4wMDAwOTA1Mzc7Cgl2ZWMzIGIgPSB2ICogKCAwLjk4MzcyOSAqIHYgKyAwLjQzMjk1MTAgKSArIDAuMjM4MDgxOwoJcmV0dXJuIGEgLyBiOwp9CnZlYzMgQUNFU0ZpbG1pY1RvbmVNYXBwaW5nKCB2ZWMzIGNvbG9yICkgewoJY29uc3QgbWF0MyBBQ0VTSW5wdXRNYXQgPSBtYXQzKAoJCXZlYzMoIDAuNTk3MTksIDAuMDc2MDAsIDAuMDI4NDAgKSwJCXZlYzMoIDAuMzU0NTgsIDAuOTA4MzQsIDAuMTMzODMgKSwKCQl2ZWMzKCAwLjA0ODIzLCAwLjAxNTY2LCAwLjgzNzc3ICkKCSk7Cgljb25zdCBtYXQzIEFDRVNPdXRwdXRNYXQgPSBtYXQzKAoJCXZlYzMoICAxLjYwNDc1LCAtMC4xMDIwOCwgLTAuMDAzMjcgKSwJCXZlYzMoIC0wLjUzMTA4LCAgMS4xMDgxMywgLTAuMDcyNzYgKSwKCQl2ZWMzKCAtMC4wNzM2NywgLTAuMDA2MDUsICAxLjA3NjAyICkKCSk7Cgljb2xvciAqPSB0b25lTWFwcGluZ0V4cG9zdXJlIC8gMC42OwoJY29sb3IgPSBBQ0VTSW5wdXRNYXQgKiBjb2xvcjsKCWNvbG9yID0gUlJUQW5kT0RURml0KCBjb2xvciApOwoJY29sb3IgPSBBQ0VTT3V0cHV0TWF0ICogY29sb3I7CglyZXR1cm4gc2F0dXJhdGUoIGNvbG9yICk7Cn0KdmVjMyBDdXN0b21Ub25lTWFwcGluZyggdmVjMyBjb2xvciApIHsgcmV0dXJuIGNvbG9yOyB9YCxyZHI9YCNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OCglmbG9hdCB0cmFuc21pc3Npb25BbHBoYSA9IDEuMDsKCWZsb2F0IHRyYW5zbWlzc2lvbkZhY3RvciA9IHRyYW5zbWlzc2lvbjsKCWZsb2F0IHRoaWNrbmVzc0ZhY3RvciA9IHRoaWNrbmVzczsKCSNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OTUFQCgkJdHJhbnNtaXNzaW9uRmFjdG9yICo9IHRleHR1cmUyRCggdHJhbnNtaXNzaW9uTWFwLCB2VXYgKS5yOwoJI2VuZGlmCgkjaWZkZWYgVVNFX1RISUNLTkVTU01BUAoJCXRoaWNrbmVzc0ZhY3RvciAqPSB0ZXh0dXJlMkQoIHRoaWNrbmVzc01hcCwgdlV2ICkuZzsKCSNlbmRpZgoJdmVjMyBwb3MgPSB2V29ybGRQb3NpdGlvbjsKCXZlYzMgdiA9IG5vcm1hbGl6ZSggY2FtZXJhUG9zaXRpb24gLSBwb3MgKTsKCXZlYzMgbiA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIG5vcm1hbCwgdmlld01hdHJpeCApOwoJdmVjNCB0cmFuc21pc3Npb24gPSBnZXRJQkxWb2x1bWVSZWZyYWN0aW9uKAoJCW4sIHYsIHJvdWdobmVzc0ZhY3RvciwgbWF0ZXJpYWwuZGlmZnVzZUNvbG9yLCBtYXRlcmlhbC5zcGVjdWxhckNvbG9yLCBtYXRlcmlhbC5zcGVjdWxhckY5MCwKCQlwb3MsIG1vZGVsTWF0cml4LCB2aWV3TWF0cml4LCBwcm9qZWN0aW9uTWF0cml4LCBpb3IsIHRoaWNrbmVzc0ZhY3RvciwKCQlhdHRlbnVhdGlvbkNvbG9yLCBhdHRlbnVhdGlvbkRpc3RhbmNlICk7Cgl0b3RhbERpZmZ1c2UgPSBtaXgoIHRvdGFsRGlmZnVzZSwgdHJhbnNtaXNzaW9uLnJnYiwgdHJhbnNtaXNzaW9uRmFjdG9yICk7Cgl0cmFuc21pc3Npb25BbHBoYSA9IG1peCggdHJhbnNtaXNzaW9uQWxwaGEsIHRyYW5zbWlzc2lvbi5hLCB0cmFuc21pc3Npb25GYWN0b3IgKTsKI2VuZGlmYCxuZHI9YCNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OCgl1bmlmb3JtIGZsb2F0IHRyYW5zbWlzc2lvbjsKCXVuaWZvcm0gZmxvYXQgdGhpY2tuZXNzOwoJdW5pZm9ybSBmbG9hdCBhdHRlbnVhdGlvbkRpc3RhbmNlOwoJdW5pZm9ybSB2ZWMzIGF0dGVudWF0aW9uQ29sb3I7CgkjaWZkZWYgVVNFX1RSQU5TTUlTU0lPTk1BUAoJCXVuaWZvcm0gc2FtcGxlcjJEIHRyYW5zbWlzc2lvbk1hcDsKCSNlbmRpZgoJI2lmZGVmIFVTRV9USElDS05FU1NNQVAKCQl1bmlmb3JtIHNhbXBsZXIyRCB0aGlja25lc3NNYXA7CgkjZW5kaWYKCXVuaWZvcm0gdmVjMiB0cmFuc21pc3Npb25TYW1wbGVyU2l6ZTsKCXVuaWZvcm0gc2FtcGxlcjJEIHRyYW5zbWlzc2lvblNhbXBsZXJNYXA7Cgl1bmlmb3JtIG1hdDQgbW9kZWxNYXRyaXg7Cgl1bmlmb3JtIG1hdDQgcHJvamVjdGlvbk1hdHJpeDsKCXZhcnlpbmcgdmVjMyB2V29ybGRQb3NpdGlvbjsKCXZlYzMgZ2V0Vm9sdW1lVHJhbnNtaXNzaW9uUmF5KCBjb25zdCBpbiB2ZWMzIG4sIGNvbnN0IGluIHZlYzMgdiwgY29uc3QgaW4gZmxvYXQgdGhpY2tuZXNzLCBjb25zdCBpbiBmbG9hdCBpb3IsIGNvbnN0IGluIG1hdDQgbW9kZWxNYXRyaXggKSB7CgkJdmVjMyByZWZyYWN0aW9uVmVjdG9yID0gcmVmcmFjdCggLSB2LCBub3JtYWxpemUoIG4gKSwgMS4wIC8gaW9yICk7CgkJdmVjMyBtb2RlbFNjYWxlOwoJCW1vZGVsU2NhbGUueCA9IGxlbmd0aCggdmVjMyggbW9kZWxNYXRyaXhbIDAgXS54eXogKSApOwoJCW1vZGVsU2NhbGUueSA9IGxlbmd0aCggdmVjMyggbW9kZWxNYXRyaXhbIDEgXS54eXogKSApOwoJCW1vZGVsU2NhbGUueiA9IGxlbmd0aCggdmVjMyggbW9kZWxNYXRyaXhbIDIgXS54eXogKSApOwoJCXJldHVybiBub3JtYWxpemUoIHJlZnJhY3Rpb25WZWN0b3IgKSAqIHRoaWNrbmVzcyAqIG1vZGVsU2NhbGU7Cgl9CglmbG9hdCBhcHBseUlvclRvUm91Z2huZXNzKCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MsIGNvbnN0IGluIGZsb2F0IGlvciApIHsKCQlyZXR1cm4gcm91Z2huZXNzICogY2xhbXAoIGlvciAqIDIuMCAtIDIuMCwgMC4wLCAxLjAgKTsKCX0KCXZlYzQgZ2V0VHJhbnNtaXNzaW9uU2FtcGxlKCBjb25zdCBpbiB2ZWMyIGZyYWdDb29yZCwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzLCBjb25zdCBpbiBmbG9hdCBpb3IgKSB7CgkJZmxvYXQgZnJhbWVidWZmZXJMb2QgPSBsb2cyKCB0cmFuc21pc3Npb25TYW1wbGVyU2l6ZS54ICkgKiBhcHBseUlvclRvUm91Z2huZXNzKCByb3VnaG5lc3MsIGlvciApOwoJCSNpZmRlZiBURVhUVVJFX0xPRF9FWFQKCQkJcmV0dXJuIHRleHR1cmUyRExvZEVYVCggdHJhbnNtaXNzaW9uU2FtcGxlck1hcCwgZnJhZ0Nvb3JkLnh5LCBmcmFtZWJ1ZmZlckxvZCApOwoJCSNlbHNlCgkJCXJldHVybiB0ZXh0dXJlMkQoIHRyYW5zbWlzc2lvblNhbXBsZXJNYXAsIGZyYWdDb29yZC54eSwgZnJhbWVidWZmZXJMb2QgKTsKCQkjZW5kaWYKCX0KCXZlYzMgYXBwbHlWb2x1bWVBdHRlbnVhdGlvbiggY29uc3QgaW4gdmVjMyByYWRpYW5jZSwgY29uc3QgaW4gZmxvYXQgdHJhbnNtaXNzaW9uRGlzdGFuY2UsIGNvbnN0IGluIHZlYzMgYXR0ZW51YXRpb25Db2xvciwgY29uc3QgaW4gZmxvYXQgYXR0ZW51YXRpb25EaXN0YW5jZSApIHsKCQlpZiAoIGF0dGVudWF0aW9uRGlzdGFuY2UgPT0gMC4wICkgewoJCQlyZXR1cm4gcmFkaWFuY2U7CgkJfSBlbHNlIHsKCQkJdmVjMyBhdHRlbnVhdGlvbkNvZWZmaWNpZW50ID0gLWxvZyggYXR0ZW51YXRpb25Db2xvciApIC8gYXR0ZW51YXRpb25EaXN0YW5jZTsKCQkJdmVjMyB0cmFuc21pdHRhbmNlID0gZXhwKCAtIGF0dGVudWF0aW9uQ29lZmZpY2llbnQgKiB0cmFuc21pc3Npb25EaXN0YW5jZSApOwkJCXJldHVybiB0cmFuc21pdHRhbmNlICogcmFkaWFuY2U7CgkJfQoJfQoJdmVjNCBnZXRJQkxWb2x1bWVSZWZyYWN0aW9uKCBjb25zdCBpbiB2ZWMzIG4sIGNvbnN0IGluIHZlYzMgdiwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzLCBjb25zdCBpbiB2ZWMzIGRpZmZ1c2VDb2xvciwKCQljb25zdCBpbiB2ZWMzIHNwZWN1bGFyQ29sb3IsIGNvbnN0IGluIGZsb2F0IHNwZWN1bGFyRjkwLCBjb25zdCBpbiB2ZWMzIHBvc2l0aW9uLCBjb25zdCBpbiBtYXQ0IG1vZGVsTWF0cml4LAoJCWNvbnN0IGluIG1hdDQgdmlld01hdHJpeCwgY29uc3QgaW4gbWF0NCBwcm9qTWF0cml4LCBjb25zdCBpbiBmbG9hdCBpb3IsIGNvbnN0IGluIGZsb2F0IHRoaWNrbmVzcywKCQljb25zdCBpbiB2ZWMzIGF0dGVudWF0aW9uQ29sb3IsIGNvbnN0IGluIGZsb2F0IGF0dGVudWF0aW9uRGlzdGFuY2UgKSB7CgkJdmVjMyB0cmFuc21pc3Npb25SYXkgPSBnZXRWb2x1bWVUcmFuc21pc3Npb25SYXkoIG4sIHYsIHRoaWNrbmVzcywgaW9yLCBtb2RlbE1hdHJpeCApOwoJCXZlYzMgcmVmcmFjdGVkUmF5RXhpdCA9IHBvc2l0aW9uICsgdHJhbnNtaXNzaW9uUmF5OwoJCXZlYzQgbmRjUG9zID0gcHJvak1hdHJpeCAqIHZpZXdNYXRyaXggKiB2ZWM0KCByZWZyYWN0ZWRSYXlFeGl0LCAxLjAgKTsKCQl2ZWMyIHJlZnJhY3Rpb25Db29yZHMgPSBuZGNQb3MueHkgLyBuZGNQb3MudzsKCQlyZWZyYWN0aW9uQ29vcmRzICs9IDEuMDsKCQlyZWZyYWN0aW9uQ29vcmRzIC89IDIuMDsKCQl2ZWM0IHRyYW5zbWl0dGVkTGlnaHQgPSBnZXRUcmFuc21pc3Npb25TYW1wbGUoIHJlZnJhY3Rpb25Db29yZHMsIHJvdWdobmVzcywgaW9yICk7CgkJdmVjMyBhdHRlbnVhdGVkQ29sb3IgPSBhcHBseVZvbHVtZUF0dGVudWF0aW9uKCB0cmFuc21pdHRlZExpZ2h0LnJnYiwgbGVuZ3RoKCB0cmFuc21pc3Npb25SYXkgKSwgYXR0ZW51YXRpb25Db2xvciwgYXR0ZW51YXRpb25EaXN0YW5jZSApOwoJCXZlYzMgRiA9IEVudmlyb25tZW50QlJERiggbiwgdiwgc3BlY3VsYXJDb2xvciwgc3BlY3VsYXJGOTAsIHJvdWdobmVzcyApOwoJCXJldHVybiB2ZWM0KCAoIDEuMCAtIEYgKSAqIGF0dGVudWF0ZWRDb2xvciAqIGRpZmZ1c2VDb2xvciwgdHJhbnNtaXR0ZWRMaWdodC5hICk7Cgl9CiNlbmRpZmAsaWRyPWAjaWYgKCBkZWZpbmVkKCBVU0VfVVYgKSAmJiAhIGRlZmluZWQoIFVWU19WRVJURVhfT05MWSApICkKCXZhcnlpbmcgdmVjMiB2VXY7CiNlbmRpZmAsb2RyPWAjaWZkZWYgVVNFX1VWCgkjaWZkZWYgVVZTX1ZFUlRFWF9PTkxZCgkJdmVjMiB2VXY7CgkjZWxzZQoJCXZhcnlpbmcgdmVjMiB2VXY7CgkjZW5kaWYKCXVuaWZvcm0gbWF0MyB1dlRyYW5zZm9ybTsKI2VuZGlmYCxhZHI9YCNpZmRlZiBVU0VfVVYKCXZVdiA9ICggdXZUcmFuc2Zvcm0gKiB2ZWMzKCB1diwgMSApICkueHk7CiNlbmRpZmAsc2RyPWAjaWYgZGVmaW5lZCggVVNFX0xJR0hUTUFQICkgfHwgZGVmaW5lZCggVVNFX0FPTUFQICkKCXZhcnlpbmcgdmVjMiB2VXYyOwojZW5kaWZgLGxkcj1gI2lmIGRlZmluZWQoIFVTRV9MSUdIVE1BUCApIHx8IGRlZmluZWQoIFVTRV9BT01BUCApCglhdHRyaWJ1dGUgdmVjMiB1djI7Cgl2YXJ5aW5nIHZlYzIgdlV2MjsKCXVuaWZvcm0gbWF0MyB1djJUcmFuc2Zvcm07CiNlbmRpZmAsY2RyPWAjaWYgZGVmaW5lZCggVVNFX0xJR0hUTUFQICkgfHwgZGVmaW5lZCggVVNFX0FPTUFQICkKCXZVdjIgPSAoIHV2MlRyYW5zZm9ybSAqIHZlYzMoIHV2MiwgMSApICkueHk7CiNlbmRpZmAsdWRyPWAjaWYgZGVmaW5lZCggVVNFX0VOVk1BUCApIHx8IGRlZmluZWQoIERJU1RBTkNFICkgfHwgZGVmaW5lZCAoIFVTRV9TSEFET1dNQVAgKSB8fCBkZWZpbmVkICggVVNFX1RSQU5TTUlTU0lPTiApCgl2ZWM0IHdvcmxkUG9zaXRpb24gPSB2ZWM0KCB0cmFuc2Zvcm1lZCwgMS4wICk7CgkjaWZkZWYgVVNFX0lOU1RBTkNJTkcKCQl3b3JsZFBvc2l0aW9uID0gaW5zdGFuY2VNYXRyaXggKiB3b3JsZFBvc2l0aW9uOwoJI2VuZGlmCgl3b3JsZFBvc2l0aW9uID0gbW9kZWxNYXRyaXggKiB3b3JsZFBvc2l0aW9uOwojZW5kaWZgLGhkcj1gdmFyeWluZyB2ZWMyIHZVdjsKdW5pZm9ybSBtYXQzIHV2VHJhbnNmb3JtOwp2b2lkIG1haW4oKSB7Cgl2VXYgPSAoIHV2VHJhbnNmb3JtICogdmVjMyggdXYsIDEgKSApLnh5OwoJZ2xfUG9zaXRpb24gPSB2ZWM0KCBwb3NpdGlvbi54eSwgMS4wLCAxLjAgKTsKfWAsZmRyPWB1bmlmb3JtIHNhbXBsZXIyRCB0MkQ7CnZhcnlpbmcgdmVjMiB2VXY7CnZvaWQgbWFpbigpIHsKCWdsX0ZyYWdDb2xvciA9IHRleHR1cmUyRCggdDJELCB2VXYgKTsKCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+Cn1gLHBkcj1gdmFyeWluZyB2ZWMzIHZXb3JsZERpcmVjdGlvbjsKI2luY2x1ZGUgPGNvbW1vbj4Kdm9pZCBtYWluKCkgewoJdldvcmxkRGlyZWN0aW9uID0gdHJhbnNmb3JtRGlyZWN0aW9uKCBwb3NpdGlvbiwgbW9kZWxNYXRyaXggKTsKCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CglnbF9Qb3NpdGlvbi56ID0gZ2xfUG9zaXRpb24udzsKfWAsZGRyPWAjaW5jbHVkZSA8ZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50Pgp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CnZhcnlpbmcgdmVjMyB2V29ybGREaXJlY3Rpb247CiNpbmNsdWRlIDxjdWJlX3V2X3JlZmxlY3Rpb25fZnJhZ21lbnQ+CnZvaWQgbWFpbigpIHsKCXZlYzMgdlJlZmxlY3QgPSB2V29ybGREaXJlY3Rpb247CgkjaW5jbHVkZSA8ZW52bWFwX2ZyYWdtZW50PgoJZ2xfRnJhZ0NvbG9yID0gZW52Q29sb3I7CglnbF9GcmFnQ29sb3IuYSAqPSBvcGFjaXR5OwoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KfWAsbWRyPWAjaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNraW5uaW5nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+CnZhcnlpbmcgdmVjMiB2SGlnaFByZWNpc2lvblpXOwp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8dXZfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5iYXNlX3ZlcnRleD4KCSNpZmRlZiBVU0VfRElTUExBQ0VNRU5UTUFQCgkJI2luY2x1ZGUgPGJlZ2lubm9ybWFsX3ZlcnRleD4KCQkjaW5jbHVkZSA8bW9ycGhub3JtYWxfdmVydGV4PgoJCSNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD4KCSNlbmRpZgoJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PgoJI2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PgoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+Cgl2SGlnaFByZWNpc2lvblpXID0gZ2xfUG9zaXRpb24uenc7Cn1gLGdkcj1gI2lmIERFUFRIX1BBQ0tJTkcgPT0gMzIwMAoJdW5pZm9ybSBmbG9hdCBvcGFjaXR5OwojZW5kaWYKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHBhY2tpbmc+CiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYW1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGF0ZXN0X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+CnZhcnlpbmcgdmVjMiB2SGlnaFByZWNpc2lvblpXOwp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJdmVjNCBkaWZmdXNlQ29sb3IgPSB2ZWM0KCAxLjAgKTsKCSNpZiBERVBUSF9QQUNLSU5HID09IDMyMDAKCQlkaWZmdXNlQ29sb3IuYSA9IG9wYWNpdHk7CgkjZW5kaWYKCSNpbmNsdWRlIDxtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGFtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PgoJZmxvYXQgZnJhZ0Nvb3JkWiA9IDAuNSAqIHZIaWdoUHJlY2lzaW9uWldbMF0gLyB2SGlnaFByZWNpc2lvblpXWzFdICsgMC41OwoJI2lmIERFUFRIX1BBQ0tJTkcgPT0gMzIwMAoJCWdsX0ZyYWdDb2xvciA9IHZlYzQoIHZlYzMoIDEuMCAtIGZyYWdDb29yZFogKSwgb3BhY2l0eSApOwoJI2VsaWYgREVQVEhfUEFDS0lORyA9PSAzMjAxCgkJZ2xfRnJhZ0NvbG9yID0gcGFja0RlcHRoVG9SR0JBKCBmcmFnQ29vcmRaICk7CgkjZW5kaWYKfWAsX2RyPWAjZGVmaW5lIERJU1RBTkNFCnZhcnlpbmcgdmVjMyB2V29ybGRQb3NpdGlvbjsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPHV2X3ZlcnRleD4KCSNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+CgkjaWZkZWYgVVNFX0RJU1BMQUNFTUVOVE1BUAoJCSNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+CgkJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCQkjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+CgkjZW5kaWYKCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD4KCSNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJI2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD4KCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PgoJdldvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uLnh5ejsKfWAseWRyPWAjZGVmaW5lIERJU1RBTkNFCnVuaWZvcm0gdmVjMyByZWZlcmVuY2VQb3NpdGlvbjsKdW5pZm9ybSBmbG9hdCBuZWFyRGlzdGFuY2U7CnVuaWZvcm0gZmxvYXQgZmFyRGlzdGFuY2U7CnZhcnlpbmcgdmVjMyB2V29ybGRQb3NpdGlvbjsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHBhY2tpbmc+CiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYW1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGF0ZXN0X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc19mcmFnbWVudD4Kdm9pZCBtYWluICgpIHsKCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+Cgl2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIDEuMCApOwoJI2luY2x1ZGUgPG1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYXRlc3RfZnJhZ21lbnQ+CglmbG9hdCBkaXN0ID0gbGVuZ3RoKCB2V29ybGRQb3NpdGlvbiAtIHJlZmVyZW5jZVBvc2l0aW9uICk7CglkaXN0ID0gKCBkaXN0IC0gbmVhckRpc3RhbmNlICkgLyAoIGZhckRpc3RhbmNlIC0gbmVhckRpc3RhbmNlICk7CglkaXN0ID0gc2F0dXJhdGUoIGRpc3QgKTsKCWdsX0ZyYWdDb2xvciA9IHBhY2tEZXB0aFRvUkdCQSggZGlzdCApOwp9YCx2ZHI9YHZhcnlpbmcgdmVjMyB2V29ybGREaXJlY3Rpb247CiNpbmNsdWRlIDxjb21tb24+CnZvaWQgbWFpbigpIHsKCXZXb3JsZERpcmVjdGlvbiA9IHRyYW5zZm9ybURpcmVjdGlvbiggcG9zaXRpb24sIG1vZGVsTWF0cml4ICk7CgkjaW5jbHVkZSA8YmVnaW5fdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4Pgp9YCx4ZHI9YHVuaWZvcm0gc2FtcGxlcjJEIHRFcXVpcmVjdDsKdmFyeWluZyB2ZWMzIHZXb3JsZERpcmVjdGlvbjsKI2luY2x1ZGUgPGNvbW1vbj4Kdm9pZCBtYWluKCkgewoJdmVjMyBkaXJlY3Rpb24gPSBub3JtYWxpemUoIHZXb3JsZERpcmVjdGlvbiApOwoJdmVjMiBzYW1wbGVVViA9IGVxdWlyZWN0VXYoIGRpcmVjdGlvbiApOwoJZ2xfRnJhZ0NvbG9yID0gdGV4dHVyZTJEKCB0RXF1aXJlY3QsIHNhbXBsZVVWICk7CgkjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZW5jb2RpbmdzX2ZyYWdtZW50Pgp9YCxiZHI9YHVuaWZvcm0gZmxvYXQgc2NhbGU7CmF0dHJpYnV0ZSBmbG9hdCBsaW5lRGlzdGFuY2U7CnZhcnlpbmcgZmxvYXQgdkxpbmVEaXN0YW5jZTsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJdkxpbmVEaXN0YW5jZSA9IHNjYWxlICogbGluZURpc3RhbmNlOwoJI2luY2x1ZGUgPGNvbG9yX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD4KCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PgoJI2luY2x1ZGUgPGZvZ192ZXJ0ZXg+Cn1gLHdkcj1gdW5pZm9ybSB2ZWMzIGRpZmZ1c2U7CnVuaWZvcm0gZmxvYXQgb3BhY2l0eTsKdW5pZm9ybSBmbG9hdCBkYXNoU2l6ZTsKdW5pZm9ybSBmbG9hdCB0b3RhbFNpemU7CnZhcnlpbmcgZmxvYXQgdkxpbmVEaXN0YW5jZTsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc19mcmFnbWVudD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD4KCWlmICggbW9kKCB2TGluZURpc3RhbmNlLCB0b3RhbFNpemUgKSA+IGRhc2hTaXplICkgewoJCWRpc2NhcmQ7Cgl9Cgl2ZWMzIG91dGdvaW5nTGlnaHQgPSB2ZWMzKCAwLjAgKTsKCXZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggZGlmZnVzZSwgb3BhY2l0eSApOwoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGNvbG9yX2ZyYWdtZW50PgoJb3V0Z29pbmdMaWdodCA9IGRpZmZ1c2VDb2xvci5yZ2I7CgkjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KCSNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8cHJlbXVsdGlwbGllZF9hbHBoYV9mcmFnbWVudD4KfWAsU2RyPWAjaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDx1djJfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxlbnZtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2tpbm5pbmdfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPHV2X3ZlcnRleD4KCSNpbmNsdWRlIDx1djJfdmVydGV4PgoJI2luY2x1ZGUgPGNvbG9yX3ZlcnRleD4KCSNpZiBkZWZpbmVkICggVVNFX0VOVk1BUCApIHx8IGRlZmluZWQgKCBVU0VfU0tJTk5JTkcgKQoJCSNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+CgkJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCQkjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PgoJCSNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD4KCQkjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+CgkjZW5kaWYKCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD4KCSNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD4KCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl92ZXJ0ZXg+CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD4KCSNpbmNsdWRlIDx3b3JsZHBvc192ZXJ0ZXg+CgkjaW5jbHVkZSA8ZW52bWFwX3ZlcnRleD4KCSNpbmNsdWRlIDxmb2dfdmVydGV4Pgp9YCxNZHI9YHVuaWZvcm0gdmVjMyBkaWZmdXNlOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CiNpZm5kZWYgRkxBVF9TSEFERUQKCXZhcnlpbmcgdmVjMyB2Tm9ybWFsOwojZW5kaWYKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPGRpdGhlcmluZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y29sb3JfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDx1djJfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YW9tYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxpZ2h0bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxlbnZtYXBfY29tbW9uX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxlbnZtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGN1YmVfdXZfcmVmbGVjdGlvbl9mcmFnbWVudD4KI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8c3BlY3VsYXJtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc19mcmFnbWVudD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD4KCXZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggZGlmZnVzZSwgb3BhY2l0eSApOwoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxjb2xvcl9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYXRlc3RfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8c3BlY3VsYXJtYXBfZnJhZ21lbnQ+CglSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCA9IFJlZmxlY3RlZExpZ2h0KCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSApOwoJI2lmZGVmIFVTRV9MSUdIVE1BUAoJCXZlYzQgbGlnaHRNYXBUZXhlbD0gdGV4dHVyZTJEKCBsaWdodE1hcCwgdlV2MiApOwoJCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSBsaWdodE1hcFRleGVsLnJnYiAqIGxpZ2h0TWFwSW50ZW5zaXR5OwoJI2Vsc2UKCQlyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKz0gdmVjMyggMS4wICk7CgkjZW5kaWYKCSNpbmNsdWRlIDxhb21hcF9mcmFnbWVudD4KCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSAqPSBkaWZmdXNlQ29sb3IucmdiOwoJdmVjMyBvdXRnb2luZ0xpZ2h0ID0gcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlOwoJI2luY2x1ZGUgPGVudm1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxvdXRwdXRfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZW5jb2RpbmdzX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGZvZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxwcmVtdWx0aXBsaWVkX2FscGhhX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGRpdGhlcmluZ19mcmFnbWVudD4KfWAsRWRyPWAjZGVmaW5lIExBTUJFUlQKdmFyeWluZyB2ZWMzIHZMaWdodEZyb250Owp2YXJ5aW5nIHZlYzMgdkluZGlyZWN0RnJvbnQ7CiNpZmRlZiBET1VCTEVfU0lERUQKCXZhcnlpbmcgdmVjMyB2TGlnaHRCYWNrOwoJdmFyeWluZyB2ZWMzIHZJbmRpcmVjdEJhY2s7CiNlbmRpZgojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDx1djJfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxlbnZtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxic2Rmcz4KI2luY2x1ZGUgPGxpZ2h0c19wYXJzX2JlZ2luPgojaW5jbHVkZSA8Y29sb3JfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxmb2dfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNraW5uaW5nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDx1dl92ZXJ0ZXg+CgkjaW5jbHVkZSA8dXYyX3ZlcnRleD4KCSNpbmNsdWRlIDxjb2xvcl92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5fdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD4KCSNpbmNsdWRlIDxza2lubmluZ192ZXJ0ZXg+CgkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PgoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+CgkjaW5jbHVkZSA8d29ybGRwb3NfdmVydGV4PgoJI2luY2x1ZGUgPGVudm1hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bGlnaHRzX2xhbWJlcnRfdmVydGV4PgoJI2luY2x1ZGUgPHNoYWRvd21hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8Zm9nX3ZlcnRleD4KfWAsVGRyPWB1bmlmb3JtIHZlYzMgZGlmZnVzZTsKdW5pZm9ybSB2ZWMzIGVtaXNzaXZlOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CnZhcnlpbmcgdmVjMyB2TGlnaHRGcm9udDsKdmFyeWluZyB2ZWMzIHZJbmRpcmVjdEZyb250OwojaWZkZWYgRE9VQkxFX1NJREVECgl2YXJ5aW5nIHZlYzMgdkxpZ2h0QmFjazsKCXZhcnlpbmcgdmVjMyB2SW5kaXJlY3RCYWNrOwojZW5kaWYKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHBhY2tpbmc+CiNpbmNsdWRlIDxkaXRoZXJpbmdfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8dXYyX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFvbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsaWdodG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW1pc3NpdmVtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGVudm1hcF9jb21tb25fcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGVudm1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y3ViZV91dl9yZWZsZWN0aW9uX2ZyYWdtZW50PgojaW5jbHVkZSA8YnNkZnM+CiNpbmNsdWRlIDxsaWdodHNfcGFyc19iZWdpbj4KI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxzaGFkb3dtYXNrX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxzcGVjdWxhcm1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJdmVjNCBkaWZmdXNlQ29sb3IgPSB2ZWM0KCBkaWZmdXNlLCBvcGFjaXR5ICk7CglSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCA9IFJlZmxlY3RlZExpZ2h0KCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSApOwoJdmVjMyB0b3RhbEVtaXNzaXZlUmFkaWFuY2UgPSBlbWlzc2l2ZTsKCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD4KCSNpbmNsdWRlIDxtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGFtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPHNwZWN1bGFybWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVtaXNzaXZlbWFwX2ZyYWdtZW50PgoJI2lmZGVmIERPVUJMRV9TSURFRAoJCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSAoIGdsX0Zyb250RmFjaW5nICkgPyB2SW5kaXJlY3RGcm9udCA6IHZJbmRpcmVjdEJhY2s7CgkjZWxzZQoJCXJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSB2SW5kaXJlY3RGcm9udDsKCSNlbmRpZgoJI2luY2x1ZGUgPGxpZ2h0bWFwX2ZyYWdtZW50PgoJcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICo9IEJSREZfTGFtYmVydCggZGlmZnVzZUNvbG9yLnJnYiApOwoJI2lmZGVmIERPVUJMRV9TSURFRAoJCXJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgPSAoIGdsX0Zyb250RmFjaW5nICkgPyB2TGlnaHRGcm9udCA6IHZMaWdodEJhY2s7CgkjZWxzZQoJCXJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgPSB2TGlnaHRGcm9udDsKCSNlbmRpZgoJcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSAqPSBCUkRGX0xhbWJlcnQoIGRpZmZ1c2VDb2xvci5yZ2IgKSAqIGdldFNoYWRvd01hc2soKTsKCSNpbmNsdWRlIDxhb21hcF9mcmFnbWVudD4KCXZlYzMgb3V0Z29pbmdMaWdodCA9IHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKyByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKyB0b3RhbEVtaXNzaXZlUmFkaWFuY2U7CgkjaW5jbHVkZSA8ZW52bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD4KCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50Pgp9YCxDZHI9YCNkZWZpbmUgTUFUQ0FQCnZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uOwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG5vcm1hbF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2tpbm5pbmdfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPHV2X3ZlcnRleD4KCSNpbmNsdWRlIDxjb2xvcl92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD4KCSNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD4KCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PgoJI2luY2x1ZGUgPGZvZ192ZXJ0ZXg+Cgl2Vmlld1Bvc2l0aW9uID0gLSBtdlBvc2l0aW9uLnh5ejsKfWAsQWRyPWAjZGVmaW5lIE1BVENBUAp1bmlmb3JtIHZlYzMgZGlmZnVzZTsKdW5pZm9ybSBmbG9hdCBvcGFjaXR5Owp1bmlmb3JtIHNhbXBsZXIyRCBtYXRjYXA7CnZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uOwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8ZGl0aGVyaW5nX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8dXZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Zm9nX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxub3JtYWxfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGJ1bXBtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG5vcm1hbG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJdmVjNCBkaWZmdXNlQ29sb3IgPSB2ZWM0KCBkaWZmdXNlLCBvcGFjaXR5ICk7CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGNvbG9yX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD4KCSNpbmNsdWRlIDxub3JtYWxfZnJhZ21lbnRfYmVnaW4+CgkjaW5jbHVkZSA8bm9ybWFsX2ZyYWdtZW50X21hcHM+Cgl2ZWMzIHZpZXdEaXIgPSBub3JtYWxpemUoIHZWaWV3UG9zaXRpb24gKTsKCXZlYzMgeCA9IG5vcm1hbGl6ZSggdmVjMyggdmlld0Rpci56LCAwLjAsIC0gdmlld0Rpci54ICkgKTsKCXZlYzMgeSA9IGNyb3NzKCB2aWV3RGlyLCB4ICk7Cgl2ZWMyIHV2ID0gdmVjMiggZG90KCB4LCBub3JtYWwgKSwgZG90KCB5LCBub3JtYWwgKSApICogMC40OTUgKyAwLjU7CgkjaWZkZWYgVVNFX01BVENBUAoJCXZlYzQgbWF0Y2FwQ29sb3IgPSB0ZXh0dXJlMkQoIG1hdGNhcCwgdXYgKTsKCSNlbHNlCgkJdmVjNCBtYXRjYXBDb2xvciA9IHZlYzQoIHZlYzMoIG1peCggMC4yLCAwLjgsIHV2LnkgKSApLCAxLjAgKTsKCSNlbmRpZgoJdmVjMyBvdXRnb2luZ0xpZ2h0ID0gZGlmZnVzZUNvbG9yLnJnYiAqIG1hdGNhcENvbG9yLnJnYjsKCSNpbmNsdWRlIDxvdXRwdXRfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZW5jb2RpbmdzX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGZvZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxwcmVtdWx0aXBsaWVkX2FscGhhX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGRpdGhlcmluZ19mcmFnbWVudD4KfWAsUGRyPWAjZGVmaW5lIE5PUk1BTAojaWYgZGVmaW5lZCggRkxBVF9TSEFERUQgKSB8fCBkZWZpbmVkKCBVU0VfQlVNUE1BUCApIHx8IGRlZmluZWQoIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAgKQoJdmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247CiNlbmRpZgojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxub3JtYWxfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNraW5uaW5nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDx1dl92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD4KCSNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD4KCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PgojaWYgZGVmaW5lZCggRkxBVF9TSEFERUQgKSB8fCBkZWZpbmVkKCBVU0VfQlVNUE1BUCApIHx8IGRlZmluZWQoIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAgKQoJdlZpZXdQb3NpdGlvbiA9IC0gbXZQb3NpdGlvbi54eXo7CiNlbmRpZgp9YCxJZHI9YCNkZWZpbmUgTk9STUFMCnVuaWZvcm0gZmxvYXQgb3BhY2l0eTsKI2lmIGRlZmluZWQoIEZMQVRfU0hBREVEICkgfHwgZGVmaW5lZCggVVNFX0JVTVBNQVAgKSB8fCBkZWZpbmVkKCBUQU5HRU5UU1BBQ0VfTk9STUFMTUFQICkKCXZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uOwojZW5kaWYKI2luY2x1ZGUgPHBhY2tpbmc+CiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bm9ybWFsX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxidW1wbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxub3JtYWxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc19mcmFnbWVudD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD4KCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD4KCSNpbmNsdWRlIDxub3JtYWxfZnJhZ21lbnRfYmVnaW4+CgkjaW5jbHVkZSA8bm9ybWFsX2ZyYWdtZW50X21hcHM+CglnbF9GcmFnQ29sb3IgPSB2ZWM0KCBwYWNrTm9ybWFsVG9SR0IoIG5vcm1hbCApLCBvcGFjaXR5ICk7Cn1gLExkcj1gI2RlZmluZSBQSE9ORwp2YXJ5aW5nIHZlYzMgdlZpZXdQb3NpdGlvbjsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8dXYyX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8ZW52bWFwX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Y29sb3JfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxmb2dfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxub3JtYWxfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNraW5uaW5nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDx1dl92ZXJ0ZXg+CgkjaW5jbHVkZSA8dXYyX3ZlcnRleD4KCSNpbmNsdWRlIDxjb2xvcl92ZXJ0ZXg+CgkjaW5jbHVkZSA8YmVnaW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD4KCSNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD4KCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PgoJdlZpZXdQb3NpdGlvbiA9IC0gbXZQb3NpdGlvbi54eXo7CgkjaW5jbHVkZSA8d29ybGRwb3NfdmVydGV4PgoJI2luY2x1ZGUgPGVudm1hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2hhZG93bWFwX3ZlcnRleD4KCSNpbmNsdWRlIDxmb2dfdmVydGV4Pgp9YCxrZHI9YCNkZWZpbmUgUEhPTkcKdW5pZm9ybSB2ZWMzIGRpZmZ1c2U7CnVuaWZvcm0gdmVjMyBlbWlzc2l2ZTsKdW5pZm9ybSB2ZWMzIHNwZWN1bGFyOwp1bmlmb3JtIGZsb2F0IHNoaW5pbmVzczsKdW5pZm9ybSBmbG9hdCBvcGFjaXR5OwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8cGFja2luZz4KI2luY2x1ZGUgPGRpdGhlcmluZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y29sb3JfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDx1djJfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YW9tYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxpZ2h0bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxlbWlzc2l2ZW1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8ZW52bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjdWJlX3V2X3JlZmxlY3Rpb25fZnJhZ21lbnQ+CiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGJzZGZzPgojaW5jbHVkZSA8bGlnaHRzX3BhcnNfYmVnaW4+CiNpbmNsdWRlIDxub3JtYWxfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxpZ2h0c19waG9uZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxidW1wbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxub3JtYWxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHNwZWN1bGFybWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+Cgl2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTsKCVJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ID0gUmVmbGVjdGVkTGlnaHQoIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApICk7Cgl2ZWMzIHRvdGFsRW1pc3NpdmVSYWRpYW5jZSA9IGVtaXNzaXZlOwoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxjb2xvcl9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYXRlc3RfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8c3BlY3VsYXJtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bm9ybWFsX2ZyYWdtZW50X2JlZ2luPgoJI2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9tYXBzPgoJI2luY2x1ZGUgPGVtaXNzaXZlbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGxpZ2h0c19waG9uZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxsaWdodHNfZnJhZ21lbnRfYmVnaW4+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X21hcHM+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X2VuZD4KCSNpbmNsdWRlIDxhb21hcF9mcmFnbWVudD4KCXZlYzMgb3V0Z29pbmdMaWdodCA9IHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKyByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKyByZWZsZWN0ZWRMaWdodC5kaXJlY3RTcGVjdWxhciArIHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0U3BlY3VsYXIgKyB0b3RhbEVtaXNzaXZlUmFkaWFuY2U7CgkjaW5jbHVkZSA8ZW52bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD4KCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50Pgp9YCxSZHI9YCNkZWZpbmUgU1RBTkRBUkQKdmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247CiNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OCgl2YXJ5aW5nIHZlYzMgdldvcmxkUG9zaXRpb247CiNlbmRpZgojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDx1djJfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG5vcm1hbF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2tpbm5pbmdfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxzaGFkb3dtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPHV2X3ZlcnRleD4KCSNpbmNsdWRlIDx1djJfdmVydGV4PgoJI2luY2x1ZGUgPGNvbG9yX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGhub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5iYXNlX3ZlcnRleD4KCSNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxkZWZhdWx0bm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PgoJI2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PgoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+Cgl2Vmlld1Bvc2l0aW9uID0gLSBtdlBvc2l0aW9uLnh5ejsKCSNpbmNsdWRlIDx3b3JsZHBvc192ZXJ0ZXg+CgkjaW5jbHVkZSA8c2hhZG93bWFwX3ZlcnRleD4KCSNpbmNsdWRlIDxmb2dfdmVydGV4PgojaWZkZWYgVVNFX1RSQU5TTUlTU0lPTgoJdldvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uLnh5ejsKI2VuZGlmCn1gLE5kcj1gI2RlZmluZSBTVEFOREFSRAojaWZkZWYgUEhZU0lDQUwKCSNkZWZpbmUgSU9SCgkjZGVmaW5lIFNQRUNVTEFSCiNlbmRpZgp1bmlmb3JtIHZlYzMgZGlmZnVzZTsKdW5pZm9ybSB2ZWMzIGVtaXNzaXZlOwp1bmlmb3JtIGZsb2F0IHJvdWdobmVzczsKdW5pZm9ybSBmbG9hdCBtZXRhbG5lc3M7CnVuaWZvcm0gZmxvYXQgb3BhY2l0eTsKI2lmZGVmIElPUgoJdW5pZm9ybSBmbG9hdCBpb3I7CiNlbmRpZgojaWZkZWYgU1BFQ1VMQVIKCXVuaWZvcm0gZmxvYXQgc3BlY3VsYXJJbnRlbnNpdHk7Cgl1bmlmb3JtIHZlYzMgc3BlY3VsYXJDb2xvcjsKCSNpZmRlZiBVU0VfU1BFQ1VMQVJJTlRFTlNJVFlNQVAKCQl1bmlmb3JtIHNhbXBsZXIyRCBzcGVjdWxhckludGVuc2l0eU1hcDsKCSNlbmRpZgoJI2lmZGVmIFVTRV9TUEVDVUxBUkNPTE9STUFQCgkJdW5pZm9ybSBzYW1wbGVyMkQgc3BlY3VsYXJDb2xvck1hcDsKCSNlbmRpZgojZW5kaWYKI2lmZGVmIFVTRV9DTEVBUkNPQVQKCXVuaWZvcm0gZmxvYXQgY2xlYXJjb2F0OwoJdW5pZm9ybSBmbG9hdCBjbGVhcmNvYXRSb3VnaG5lc3M7CiNlbmRpZgojaWZkZWYgVVNFX1NIRUVOCgl1bmlmb3JtIHZlYzMgc2hlZW5Db2xvcjsKCXVuaWZvcm0gZmxvYXQgc2hlZW5Sb3VnaG5lc3M7CgkjaWZkZWYgVVNFX1NIRUVOQ09MT1JNQVAKCQl1bmlmb3JtIHNhbXBsZXIyRCBzaGVlbkNvbG9yTWFwOwoJI2VuZGlmCgkjaWZkZWYgVVNFX1NIRUVOUk9VR0hORVNTTUFQCgkJdW5pZm9ybSBzYW1wbGVyMkQgc2hlZW5Sb3VnaG5lc3NNYXA7CgkjZW5kaWYKI2VuZGlmCnZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uOwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8cGFja2luZz4KI2luY2x1ZGUgPGRpdGhlcmluZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y29sb3JfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDx1djJfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YW9tYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxpZ2h0bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxlbWlzc2l2ZW1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YnNkZnM+CiNpbmNsdWRlIDxjdWJlX3V2X3JlZmxlY3Rpb25fZnJhZ21lbnQ+CiNpbmNsdWRlIDxlbnZtYXBfY29tbW9uX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxlbnZtYXBfcGh5c2ljYWxfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bGlnaHRzX3BhcnNfYmVnaW4+CiNpbmNsdWRlIDxub3JtYWxfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxpZ2h0c19waHlzaWNhbF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8dHJhbnNtaXNzaW9uX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxzaGFkb3dtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGJ1bXBtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG5vcm1hbG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y2xlYXJjb2F0X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxyb3VnaG5lc3NtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG1ldGFsbmVzc21hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJdmVjNCBkaWZmdXNlQ29sb3IgPSB2ZWM0KCBkaWZmdXNlLCBvcGFjaXR5ICk7CglSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCA9IFJlZmxlY3RlZExpZ2h0KCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSApOwoJdmVjMyB0b3RhbEVtaXNzaXZlUmFkaWFuY2UgPSBlbWlzc2l2ZTsKCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD4KCSNpbmNsdWRlIDxtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGFtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPHJvdWdobmVzc21hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxtZXRhbG5lc3NtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bm9ybWFsX2ZyYWdtZW50X2JlZ2luPgoJI2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9tYXBzPgoJI2luY2x1ZGUgPGNsZWFyY29hdF9ub3JtYWxfZnJhZ21lbnRfYmVnaW4+CgkjaW5jbHVkZSA8Y2xlYXJjb2F0X25vcm1hbF9mcmFnbWVudF9tYXBzPgoJI2luY2x1ZGUgPGVtaXNzaXZlbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGxpZ2h0c19waHlzaWNhbF9mcmFnbWVudD4KCSNpbmNsdWRlIDxsaWdodHNfZnJhZ21lbnRfYmVnaW4+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X21hcHM+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X2VuZD4KCSNpbmNsdWRlIDxhb21hcF9mcmFnbWVudD4KCXZlYzMgdG90YWxEaWZmdXNlID0gcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArIHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZTsKCXZlYzMgdG90YWxTcGVjdWxhciA9IHJlZmxlY3RlZExpZ2h0LmRpcmVjdFNwZWN1bGFyICsgcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3RTcGVjdWxhcjsKCSNpbmNsdWRlIDx0cmFuc21pc3Npb25fZnJhZ21lbnQ+Cgl2ZWMzIG91dGdvaW5nTGlnaHQgPSB0b3RhbERpZmZ1c2UgKyB0b3RhbFNwZWN1bGFyICsgdG90YWxFbWlzc2l2ZVJhZGlhbmNlOwoJI2lmZGVmIFVTRV9TSEVFTgoJCWZsb2F0IHNoZWVuRW5lcmd5Q29tcCA9IDEuMCAtIDAuMTU3ICogbWF4MyggbWF0ZXJpYWwuc2hlZW5Db2xvciApOwoJCW91dGdvaW5nTGlnaHQgPSBvdXRnb2luZ0xpZ2h0ICogc2hlZW5FbmVyZ3lDb21wICsgc2hlZW5TcGVjdWxhcjsKCSNlbmRpZgoJI2lmZGVmIFVTRV9DTEVBUkNPQVQKCQlmbG9hdCBkb3ROVmNjID0gc2F0dXJhdGUoIGRvdCggZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsLCBnZW9tZXRyeS52aWV3RGlyICkgKTsKCQl2ZWMzIEZjYyA9IEZfU2NobGljayggbWF0ZXJpYWwuY2xlYXJjb2F0RjAsIG1hdGVyaWFsLmNsZWFyY29hdEY5MCwgZG90TlZjYyApOwoJCW91dGdvaW5nTGlnaHQgPSBvdXRnb2luZ0xpZ2h0ICogKCAxLjAgLSBtYXRlcmlhbC5jbGVhcmNvYXQgKiBGY2MgKSArIGNsZWFyY29hdFNwZWN1bGFyICogbWF0ZXJpYWwuY2xlYXJjb2F0OwoJI2VuZGlmCgkjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KCSNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8cHJlbXVsdGlwbGllZF9hbHBoYV9mcmFnbWVudD4KCSNpbmNsdWRlIDxkaXRoZXJpbmdfZnJhZ21lbnQ+Cn1gLERkcj1gI2RlZmluZSBUT09OCnZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uOwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDx1djJfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG5vcm1hbF9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8c2tpbm5pbmdfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxzaGFkb3dtYXBfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPHV2X3ZlcnRleD4KCSNpbmNsdWRlIDx1djJfdmVydGV4PgoJI2luY2x1ZGUgPGNvbG9yX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGhub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5iYXNlX3ZlcnRleD4KCSNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxkZWZhdWx0bm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PgoJI2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF92ZXJ0ZXg+CgkjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PgoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+Cgl2Vmlld1Bvc2l0aW9uID0gLSBtdlBvc2l0aW9uLnh5ejsKCSNpbmNsdWRlIDx3b3JsZHBvc192ZXJ0ZXg+CgkjaW5jbHVkZSA8c2hhZG93bWFwX3ZlcnRleD4KCSNpbmNsdWRlIDxmb2dfdmVydGV4Pgp9YCxPZHI9YCNkZWZpbmUgVE9PTgp1bmlmb3JtIHZlYzMgZGlmZnVzZTsKdW5pZm9ybSB2ZWMzIGVtaXNzaXZlOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDxwYWNraW5nPgojaW5jbHVkZSA8ZGl0aGVyaW5nX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8dXZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPHV2Ml9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYW1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGF0ZXN0X3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhb21hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bGlnaHRtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGVtaXNzaXZlbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxncmFkaWVudG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Zm9nX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxic2Rmcz4KI2luY2x1ZGUgPGxpZ2h0c19wYXJzX2JlZ2luPgojaW5jbHVkZSA8bm9ybWFsX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsaWdodHNfdG9vbl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxidW1wbWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxub3JtYWxtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc19mcmFnbWVudD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD4KCXZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggZGlmZnVzZSwgb3BhY2l0eSApOwoJUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgPSBSZWZsZWN0ZWRMaWdodCggdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICkgKTsKCXZlYzMgdG90YWxFbWlzc2l2ZVJhZGlhbmNlID0gZW1pc3NpdmU7CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGNvbG9yX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD4KCSNpbmNsdWRlIDxub3JtYWxfZnJhZ21lbnRfYmVnaW4+CgkjaW5jbHVkZSA8bm9ybWFsX2ZyYWdtZW50X21hcHM+CgkjaW5jbHVkZSA8ZW1pc3NpdmVtYXBfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bGlnaHRzX3Rvb25fZnJhZ21lbnQ+CgkjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X2JlZ2luPgoJI2luY2x1ZGUgPGxpZ2h0c19mcmFnbWVudF9tYXBzPgoJI2luY2x1ZGUgPGxpZ2h0c19mcmFnbWVudF9lbmQ+CgkjaW5jbHVkZSA8YW9tYXBfZnJhZ21lbnQ+Cgl2ZWMzIG91dGdvaW5nTGlnaHQgPSByZWZsZWN0ZWRMaWdodC5kaXJlY3REaWZmdXNlICsgcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICsgdG90YWxFbWlzc2l2ZVJhZGlhbmNlOwoJI2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD4KCSNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD4KCSNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50Pgp9YCx6ZHI9YHVuaWZvcm0gZmxvYXQgc2l6ZTsKdW5pZm9ybSBmbG9hdCBzY2FsZTsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGNvbG9yX3ZlcnRleD4KCSNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+CgkjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJZ2xfUG9pbnRTaXplID0gc2l6ZTsKCSNpZmRlZiBVU0VfU0laRUFUVEVOVUFUSU9OCgkJYm9vbCBpc1BlcnNwZWN0aXZlID0gaXNQZXJzcGVjdGl2ZU1hdHJpeCggcHJvamVjdGlvbk1hdHJpeCApOwoJCWlmICggaXNQZXJzcGVjdGl2ZSApIGdsX1BvaW50U2l6ZSAqPSAoIHNjYWxlIC8gLSBtdlBvc2l0aW9uLnogKTsKCSNlbmRpZgoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD4KCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PgoJI2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD4KCSNpbmNsdWRlIDxmb2dfdmVydGV4Pgp9YCxGZHI9YHVuaWZvcm0gdmVjMyBkaWZmdXNlOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bWFwX3BhcnRpY2xlX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Pgp2b2lkIG1haW4oKSB7CgkjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PgoJdmVjMyBvdXRnb2luZ0xpZ2h0ID0gdmVjMyggMC4wICk7Cgl2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTsKCSNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD4KCSNpbmNsdWRlIDxtYXBfcGFydGljbGVfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PgoJb3V0Z29pbmdMaWdodCA9IGRpZmZ1c2VDb2xvci5yZ2I7CgkjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PgoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KCSNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8cHJlbXVsdGlwbGllZF9hbHBoYV9mcmFnbWVudD4KfWAsQmRyPWAjaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD4KI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX3ZlcnRleD4Kdm9pZCBtYWluKCkgewoJI2luY2x1ZGUgPGJlZ2lubm9ybWFsX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaG5vcm1hbF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PgoJI2luY2x1ZGUgPHNraW5ub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGRlZmF1bHRub3JtYWxfdmVydGV4PgoJI2luY2x1ZGUgPGJlZ2luX3ZlcnRleD4KCSNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+CgkjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PgoJI2luY2x1ZGUgPHByb2plY3RfdmVydGV4PgoJI2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD4KCSNpbmNsdWRlIDxzaGFkb3dtYXBfdmVydGV4PgoJI2luY2x1ZGUgPGZvZ192ZXJ0ZXg+Cn1gLEhkcj1gdW5pZm9ybSB2ZWMzIGNvbG9yOwp1bmlmb3JtIGZsb2F0IG9wYWNpdHk7CiNpbmNsdWRlIDxjb21tb24+CiNpbmNsdWRlIDxwYWNraW5nPgojaW5jbHVkZSA8Zm9nX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxic2Rmcz4KI2luY2x1ZGUgPGxpZ2h0c19wYXJzX2JlZ2luPgojaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxzaGFkb3dtYXNrX3BhcnNfZnJhZ21lbnQ+CnZvaWQgbWFpbigpIHsKCWdsX0ZyYWdDb2xvciA9IHZlYzQoIGNvbG9yLCBvcGFjaXR5ICogKCAxLjAgLSBnZXRTaGFkb3dNYXNrKCkgKSApOwoJI2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD4KCSNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+Cn1gLFZkcj1gdW5pZm9ybSBmbG9hdCByb3RhdGlvbjsKdW5pZm9ybSB2ZWMyIGNlbnRlcjsKI2luY2x1ZGUgPGNvbW1vbj4KI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PgojaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PgojaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+CiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDx1dl92ZXJ0ZXg+Cgl2ZWM0IG12UG9zaXRpb24gPSBtb2RlbFZpZXdNYXRyaXggKiB2ZWM0KCAwLjAsIDAuMCwgMC4wLCAxLjAgKTsKCXZlYzIgc2NhbGU7CglzY2FsZS54ID0gbGVuZ3RoKCB2ZWMzKCBtb2RlbE1hdHJpeFsgMCBdLngsIG1vZGVsTWF0cml4WyAwIF0ueSwgbW9kZWxNYXRyaXhbIDAgXS56ICkgKTsKCXNjYWxlLnkgPSBsZW5ndGgoIHZlYzMoIG1vZGVsTWF0cml4WyAxIF0ueCwgbW9kZWxNYXRyaXhbIDEgXS55LCBtb2RlbE1hdHJpeFsgMSBdLnogKSApOwoJI2lmbmRlZiBVU0VfU0laRUFUVEVOVUFUSU9OCgkJYm9vbCBpc1BlcnNwZWN0aXZlID0gaXNQZXJzcGVjdGl2ZU1hdHJpeCggcHJvamVjdGlvbk1hdHJpeCApOwoJCWlmICggaXNQZXJzcGVjdGl2ZSApIHNjYWxlICo9IC0gbXZQb3NpdGlvbi56OwoJI2VuZGlmCgl2ZWMyIGFsaWduZWRQb3NpdGlvbiA9ICggcG9zaXRpb24ueHkgLSAoIGNlbnRlciAtIHZlYzIoIDAuNSApICkgKSAqIHNjYWxlOwoJdmVjMiByb3RhdGVkUG9zaXRpb247Cglyb3RhdGVkUG9zaXRpb24ueCA9IGNvcyggcm90YXRpb24gKSAqIGFsaWduZWRQb3NpdGlvbi54IC0gc2luKCByb3RhdGlvbiApICogYWxpZ25lZFBvc2l0aW9uLnk7Cglyb3RhdGVkUG9zaXRpb24ueSA9IHNpbiggcm90YXRpb24gKSAqIGFsaWduZWRQb3NpdGlvbi54ICsgY29zKCByb3RhdGlvbiApICogYWxpZ25lZFBvc2l0aW9uLnk7CgltdlBvc2l0aW9uLnh5ICs9IHJvdGF0ZWRQb3NpdGlvbjsKCWdsX1Bvc2l0aW9uID0gcHJvamVjdGlvbk1hdHJpeCAqIG12UG9zaXRpb247CgkjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PgoJI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+CgkjaW5jbHVkZSA8Zm9nX3ZlcnRleD4KfWAsVWRyPWB1bmlmb3JtIHZlYzMgZGlmZnVzZTsKdW5pZm9ybSBmbG9hdCBvcGFjaXR5OwojaW5jbHVkZSA8Y29tbW9uPgojaW5jbHVkZSA8dXZfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD4KI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Zm9nX3BhcnNfZnJhZ21lbnQ+CiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50PgojaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+CnZvaWQgbWFpbigpIHsKCSNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+Cgl2ZWMzIG91dGdvaW5nTGlnaHQgPSB2ZWMzKCAwLjAgKTsKCXZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggZGlmZnVzZSwgb3BhY2l0eSApOwoJI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PgoJI2luY2x1ZGUgPG1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD4KCSNpbmNsdWRlIDxhbHBoYXRlc3RfZnJhZ21lbnQ+CglvdXRnb2luZ0xpZ2h0ID0gZGlmZnVzZUNvbG9yLnJnYjsKCSNpbmNsdWRlIDxvdXRwdXRfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+CgkjaW5jbHVkZSA8ZW5jb2RpbmdzX2ZyYWdtZW50PgoJI2luY2x1ZGUgPGZvZ19mcmFnbWVudD4KfWAsaHI9e2FscGhhbWFwX2ZyYWdtZW50Ol9mcixhbHBoYW1hcF9wYXJzX2ZyYWdtZW50OnlmcixhbHBoYXRlc3RfZnJhZ21lbnQ6dmZyLGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50Onhmcixhb21hcF9mcmFnbWVudDpiZnIsYW9tYXBfcGFyc19mcmFnbWVudDp3ZnIsYmVnaW5fdmVydGV4OlNmcixiZWdpbm5vcm1hbF92ZXJ0ZXg6TWZyLGJzZGZzOkVmcixidW1wbWFwX3BhcnNfZnJhZ21lbnQ6VGZyLGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudDpDZnIsY2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ6QWZyLGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleDpQZnIsY2xpcHBpbmdfcGxhbmVzX3ZlcnRleDpJZnIsY29sb3JfZnJhZ21lbnQ6TGZyLGNvbG9yX3BhcnNfZnJhZ21lbnQ6a2ZyLGNvbG9yX3BhcnNfdmVydGV4OlJmcixjb2xvcl92ZXJ0ZXg6TmZyLGNvbW1vbjpEZnIsY3ViZV91dl9yZWZsZWN0aW9uX2ZyYWdtZW50Ok9mcixkZWZhdWx0bm9ybWFsX3ZlcnRleDp6ZnIsZGlzcGxhY2VtZW50bWFwX3BhcnNfdmVydGV4OkZmcixkaXNwbGFjZW1lbnRtYXBfdmVydGV4OkJmcixlbWlzc2l2ZW1hcF9mcmFnbWVudDpIZnIsZW1pc3NpdmVtYXBfcGFyc19mcmFnbWVudDpWZnIsZW5jb2RpbmdzX2ZyYWdtZW50OlVmcixlbmNvZGluZ3NfcGFyc19mcmFnbWVudDpxZnIsZW52bWFwX2ZyYWdtZW50OkdmcixlbnZtYXBfY29tbW9uX3BhcnNfZnJhZ21lbnQ6V2ZyLGVudm1hcF9wYXJzX2ZyYWdtZW50OllmcixlbnZtYXBfcGFyc192ZXJ0ZXg6amZyLGVudm1hcF9waHlzaWNhbF9wYXJzX2ZyYWdtZW50OmlwcixlbnZtYXBfdmVydGV4Olhmcixmb2dfdmVydGV4OiRmcixmb2dfcGFyc192ZXJ0ZXg6S2ZyLGZvZ19mcmFnbWVudDpaZnIsZm9nX3BhcnNfZnJhZ21lbnQ6SmZyLGdyYWRpZW50bWFwX3BhcnNfZnJhZ21lbnQ6UWZyLGxpZ2h0bWFwX2ZyYWdtZW50OnRwcixsaWdodG1hcF9wYXJzX2ZyYWdtZW50OmVwcixsaWdodHNfbGFtYmVydF92ZXJ0ZXg6cnByLGxpZ2h0c19wYXJzX2JlZ2luOm5wcixsaWdodHNfdG9vbl9mcmFnbWVudDpvcHIsbGlnaHRzX3Rvb25fcGFyc19mcmFnbWVudDphcHIsbGlnaHRzX3Bob25nX2ZyYWdtZW50OnNwcixsaWdodHNfcGhvbmdfcGFyc19mcmFnbWVudDpscHIsbGlnaHRzX3BoeXNpY2FsX2ZyYWdtZW50OmNwcixsaWdodHNfcGh5c2ljYWxfcGFyc19mcmFnbWVudDp1cHIsbGlnaHRzX2ZyYWdtZW50X2JlZ2luOmhwcixsaWdodHNfZnJhZ21lbnRfbWFwczpmcHIsbGlnaHRzX2ZyYWdtZW50X2VuZDpwcHIsbG9nZGVwdGhidWZfZnJhZ21lbnQ6ZHByLGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ6bXByLGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4Omdwcixsb2dkZXB0aGJ1Zl92ZXJ0ZXg6X3ByLG1hcF9mcmFnbWVudDp5cHIsbWFwX3BhcnNfZnJhZ21lbnQ6dnByLG1hcF9wYXJ0aWNsZV9mcmFnbWVudDp4cHIsbWFwX3BhcnRpY2xlX3BhcnNfZnJhZ21lbnQ6YnByLG1ldGFsbmVzc21hcF9mcmFnbWVudDp3cHIsbWV0YWxuZXNzbWFwX3BhcnNfZnJhZ21lbnQ6U3ByLG1vcnBobm9ybWFsX3ZlcnRleDpNcHIsbW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg6RXByLG1vcnBodGFyZ2V0X3ZlcnRleDpUcHIsbm9ybWFsX2ZyYWdtZW50X2JlZ2luOkNwcixub3JtYWxfZnJhZ21lbnRfbWFwczpBcHIsbm9ybWFsX3BhcnNfZnJhZ21lbnQ6UHByLG5vcm1hbF9wYXJzX3ZlcnRleDpJcHIsbm9ybWFsX3ZlcnRleDpMcHIsbm9ybWFsbWFwX3BhcnNfZnJhZ21lbnQ6a3ByLGNsZWFyY29hdF9ub3JtYWxfZnJhZ21lbnRfYmVnaW46UnByLGNsZWFyY29hdF9ub3JtYWxfZnJhZ21lbnRfbWFwczpOcHIsY2xlYXJjb2F0X3BhcnNfZnJhZ21lbnQ6RHByLG91dHB1dF9mcmFnbWVudDpPcHIscGFja2luZzp6cHIscHJlbXVsdGlwbGllZF9hbHBoYV9mcmFnbWVudDpGcHIscHJvamVjdF92ZXJ0ZXg6QnByLGRpdGhlcmluZ19mcmFnbWVudDpIcHIsZGl0aGVyaW5nX3BhcnNfZnJhZ21lbnQ6VnByLHJvdWdobmVzc21hcF9mcmFnbWVudDpVcHIscm91Z2huZXNzbWFwX3BhcnNfZnJhZ21lbnQ6cXByLHNoYWRvd21hcF9wYXJzX2ZyYWdtZW50OkdwcixzaGFkb3dtYXBfcGFyc192ZXJ0ZXg6V3ByLHNoYWRvd21hcF92ZXJ0ZXg6WXByLHNoYWRvd21hc2tfcGFyc19mcmFnbWVudDpqcHIsc2tpbmJhc2VfdmVydGV4Olhwcixza2lubmluZ19wYXJzX3ZlcnRleDokcHIsc2tpbm5pbmdfdmVydGV4Oktwcixza2lubm9ybWFsX3ZlcnRleDpacHIsc3BlY3VsYXJtYXBfZnJhZ21lbnQ6SnByLHNwZWN1bGFybWFwX3BhcnNfZnJhZ21lbnQ6UXByLHRvbmVtYXBwaW5nX2ZyYWdtZW50OnRkcix0b25lbWFwcGluZ19wYXJzX2ZyYWdtZW50OmVkcix0cmFuc21pc3Npb25fZnJhZ21lbnQ6cmRyLHRyYW5zbWlzc2lvbl9wYXJzX2ZyYWdtZW50Om5kcix1dl9wYXJzX2ZyYWdtZW50Omlkcix1dl9wYXJzX3ZlcnRleDpvZHIsdXZfdmVydGV4OmFkcix1djJfcGFyc19mcmFnbWVudDpzZHIsdXYyX3BhcnNfdmVydGV4Omxkcix1djJfdmVydGV4OmNkcix3b3JsZHBvc192ZXJ0ZXg6dWRyLGJhY2tncm91bmRfdmVydDpoZHIsYmFja2dyb3VuZF9mcmFnOmZkcixjdWJlX3ZlcnQ6cGRyLGN1YmVfZnJhZzpkZHIsZGVwdGhfdmVydDptZHIsZGVwdGhfZnJhZzpnZHIsZGlzdGFuY2VSR0JBX3ZlcnQ6X2RyLGRpc3RhbmNlUkdCQV9mcmFnOnlkcixlcXVpcmVjdF92ZXJ0OnZkcixlcXVpcmVjdF9mcmFnOnhkcixsaW5lZGFzaGVkX3ZlcnQ6YmRyLGxpbmVkYXNoZWRfZnJhZzp3ZHIsbWVzaGJhc2ljX3ZlcnQ6U2RyLG1lc2hiYXNpY19mcmFnOk1kcixtZXNobGFtYmVydF92ZXJ0OkVkcixtZXNobGFtYmVydF9mcmFnOlRkcixtZXNobWF0Y2FwX3ZlcnQ6Q2RyLG1lc2htYXRjYXBfZnJhZzpBZHIsbWVzaG5vcm1hbF92ZXJ0OlBkcixtZXNobm9ybWFsX2ZyYWc6SWRyLG1lc2hwaG9uZ192ZXJ0OkxkcixtZXNocGhvbmdfZnJhZzprZHIsbWVzaHBoeXNpY2FsX3ZlcnQ6UmRyLG1lc2hwaHlzaWNhbF9mcmFnOk5kcixtZXNodG9vbl92ZXJ0OkRkcixtZXNodG9vbl9mcmFnOk9kcixwb2ludHNfdmVydDp6ZHIscG9pbnRzX2ZyYWc6RmRyLHNoYWRvd192ZXJ0OkJkcixzaGFkb3dfZnJhZzpIZHIsc3ByaXRlX3ZlcnQ6VmRyLHNwcml0ZV9mcmFnOlVkcn0scmU9e2NvbW1vbjp7ZGlmZnVzZTp7dmFsdWU6bmV3IG5lKDE2Nzc3MjE1KX0sb3BhY2l0eTp7dmFsdWU6MX0sbWFwOnt2YWx1ZTpudWxsfSx1dlRyYW5zZm9ybTp7dmFsdWU6bmV3IGtpfSx1djJUcmFuc2Zvcm06e3ZhbHVlOm5ldyBraX0sYWxwaGFNYXA6e3ZhbHVlOm51bGx9LGFscGhhVGVzdDp7dmFsdWU6MH19LHNwZWN1bGFybWFwOntzcGVjdWxhck1hcDp7dmFsdWU6bnVsbH19LGVudm1hcDp7ZW52TWFwOnt2YWx1ZTpudWxsfSxmbGlwRW52TWFwOnt2YWx1ZTotMX0scmVmbGVjdGl2aXR5Ont2YWx1ZToxfSxpb3I6e3ZhbHVlOjEuNX0scmVmcmFjdGlvblJhdGlvOnt2YWx1ZTouOTh9fSxhb21hcDp7YW9NYXA6e3ZhbHVlOm51bGx9LGFvTWFwSW50ZW5zaXR5Ont2YWx1ZToxfX0sbGlnaHRtYXA6e2xpZ2h0TWFwOnt2YWx1ZTpudWxsfSxsaWdodE1hcEludGVuc2l0eTp7dmFsdWU6MX19LGVtaXNzaXZlbWFwOntlbWlzc2l2ZU1hcDp7dmFsdWU6bnVsbH19LGJ1bXBtYXA6e2J1bXBNYXA6e3ZhbHVlOm51bGx9LGJ1bXBTY2FsZTp7dmFsdWU6MX19LG5vcm1hbG1hcDp7bm9ybWFsTWFwOnt2YWx1ZTpudWxsfSxub3JtYWxTY2FsZTp7dmFsdWU6bmV3IEx0KDEsMSl9fSxkaXNwbGFjZW1lbnRtYXA6e2Rpc3BsYWNlbWVudE1hcDp7dmFsdWU6bnVsbH0sZGlzcGxhY2VtZW50U2NhbGU6e3ZhbHVlOjF9LGRpc3BsYWNlbWVudEJpYXM6e3ZhbHVlOjB9fSxyb3VnaG5lc3NtYXA6e3JvdWdobmVzc01hcDp7dmFsdWU6bnVsbH19LG1ldGFsbmVzc21hcDp7bWV0YWxuZXNzTWFwOnt2YWx1ZTpudWxsfX0sZ3JhZGllbnRtYXA6e2dyYWRpZW50TWFwOnt2YWx1ZTpudWxsfX0sZm9nOntmb2dEZW5zaXR5Ont2YWx1ZToyNWUtNX0sZm9nTmVhcjp7dmFsdWU6MX0sZm9nRmFyOnt2YWx1ZToyZTN9LGZvZ0NvbG9yOnt2YWx1ZTpuZXcgbmUoMTY3NzcyMTUpfX0sbGlnaHRzOnthbWJpZW50TGlnaHRDb2xvcjp7dmFsdWU6W119LGxpZ2h0UHJvYmU6e3ZhbHVlOltdfSxkaXJlY3Rpb25hbExpZ2h0czp7dmFsdWU6W10scHJvcGVydGllczp7ZGlyZWN0aW9uOnt9LGNvbG9yOnt9fX0sZGlyZWN0aW9uYWxMaWdodFNoYWRvd3M6e3ZhbHVlOltdLHByb3BlcnRpZXM6e3NoYWRvd0JpYXM6e30sc2hhZG93Tm9ybWFsQmlhczp7fSxzaGFkb3dSYWRpdXM6e30sc2hhZG93TWFwU2l6ZTp7fX19LGRpcmVjdGlvbmFsU2hhZG93TWFwOnt2YWx1ZTpbXX0sZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXg6e3ZhbHVlOltdfSxzcG90TGlnaHRzOnt2YWx1ZTpbXSxwcm9wZXJ0aWVzOntjb2xvcjp7fSxwb3NpdGlvbjp7fSxkaXJlY3Rpb246e30sZGlzdGFuY2U6e30sY29uZUNvczp7fSxwZW51bWJyYUNvczp7fSxkZWNheTp7fX19LHNwb3RMaWdodFNoYWRvd3M6e3ZhbHVlOltdLHByb3BlcnRpZXM6e3NoYWRvd0JpYXM6e30sc2hhZG93Tm9ybWFsQmlhczp7fSxzaGFkb3dSYWRpdXM6e30sc2hhZG93TWFwU2l6ZTp7fX19LHNwb3RTaGFkb3dNYXA6e3ZhbHVlOltdfSxzcG90U2hhZG93TWF0cml4Ont2YWx1ZTpbXX0scG9pbnRMaWdodHM6e3ZhbHVlOltdLHByb3BlcnRpZXM6e2NvbG9yOnt9LHBvc2l0aW9uOnt9LGRlY2F5Ont9LGRpc3RhbmNlOnt9fX0scG9pbnRMaWdodFNoYWRvd3M6e3ZhbHVlOltdLHByb3BlcnRpZXM6e3NoYWRvd0JpYXM6e30sc2hhZG93Tm9ybWFsQmlhczp7fSxzaGFkb3dSYWRpdXM6e30sc2hhZG93TWFwU2l6ZTp7fSxzaGFkb3dDYW1lcmFOZWFyOnt9LHNoYWRvd0NhbWVyYUZhcjp7fX19LHBvaW50U2hhZG93TWFwOnt2YWx1ZTpbXX0scG9pbnRTaGFkb3dNYXRyaXg6e3ZhbHVlOltdfSxoZW1pc3BoZXJlTGlnaHRzOnt2YWx1ZTpbXSxwcm9wZXJ0aWVzOntkaXJlY3Rpb246e30sc2t5Q29sb3I6e30sZ3JvdW5kQ29sb3I6e319fSxyZWN0QXJlYUxpZ2h0czp7dmFsdWU6W10scHJvcGVydGllczp7Y29sb3I6e30scG9zaXRpb246e30sd2lkdGg6e30saGVpZ2h0Ont9fX0sbHRjXzE6e3ZhbHVlOm51bGx9LGx0Y18yOnt2YWx1ZTpudWxsfX0scG9pbnRzOntkaWZmdXNlOnt2YWx1ZTpuZXcgbmUoMTY3NzcyMTUpfSxvcGFjaXR5Ont2YWx1ZToxfSxzaXplOnt2YWx1ZToxfSxzY2FsZTp7dmFsdWU6MX0sbWFwOnt2YWx1ZTpudWxsfSxhbHBoYU1hcDp7dmFsdWU6bnVsbH0sYWxwaGFUZXN0Ont2YWx1ZTowfSx1dlRyYW5zZm9ybTp7dmFsdWU6bmV3IGtpfX0sc3ByaXRlOntkaWZmdXNlOnt2YWx1ZTpuZXcgbmUoMTY3NzcyMTUpfSxvcGFjaXR5Ont2YWx1ZToxfSxjZW50ZXI6e3ZhbHVlOm5ldyBMdCguNSwuNSl9LHJvdGF0aW9uOnt2YWx1ZTowfSxtYXA6e3ZhbHVlOm51bGx9LGFscGhhTWFwOnt2YWx1ZTpudWxsfSxhbHBoYVRlc3Q6e3ZhbHVlOjB9LHV2VHJhbnNmb3JtOnt2YWx1ZTpuZXcga2l9fX0sYWg9e2Jhc2ljOnt1bmlmb3JtczpUYShbcmUuY29tbW9uLHJlLnNwZWN1bGFybWFwLHJlLmVudm1hcCxyZS5hb21hcCxyZS5saWdodG1hcCxyZS5mb2ddKSx2ZXJ0ZXhTaGFkZXI6aHIubWVzaGJhc2ljX3ZlcnQsZnJhZ21lbnRTaGFkZXI6aHIubWVzaGJhc2ljX2ZyYWd9LGxhbWJlcnQ6e3VuaWZvcm1zOlRhKFtyZS5jb21tb24scmUuc3BlY3VsYXJtYXAscmUuZW52bWFwLHJlLmFvbWFwLHJlLmxpZ2h0bWFwLHJlLmVtaXNzaXZlbWFwLHJlLmZvZyxyZS5saWdodHMse2VtaXNzaXZlOnt2YWx1ZTpuZXcgbmUoMCl9fV0pLHZlcnRleFNoYWRlcjpoci5tZXNobGFtYmVydF92ZXJ0LGZyYWdtZW50U2hhZGVyOmhyLm1lc2hsYW1iZXJ0X2ZyYWd9LHBob25nOnt1bmlmb3JtczpUYShbcmUuY29tbW9uLHJlLnNwZWN1bGFybWFwLHJlLmVudm1hcCxyZS5hb21hcCxyZS5saWdodG1hcCxyZS5lbWlzc2l2ZW1hcCxyZS5idW1wbWFwLHJlLm5vcm1hbG1hcCxyZS5kaXNwbGFjZW1lbnRtYXAscmUuZm9nLHJlLmxpZ2h0cyx7ZW1pc3NpdmU6e3ZhbHVlOm5ldyBuZSgwKX0sc3BlY3VsYXI6e3ZhbHVlOm5ldyBuZSgxMTE4NDgxKX0sc2hpbmluZXNzOnt2YWx1ZTozMH19XSksdmVydGV4U2hhZGVyOmhyLm1lc2hwaG9uZ192ZXJ0LGZyYWdtZW50U2hhZGVyOmhyLm1lc2hwaG9uZ19mcmFnfSxzdGFuZGFyZDp7dW5pZm9ybXM6VGEoW3JlLmNvbW1vbixyZS5lbnZtYXAscmUuYW9tYXAscmUubGlnaHRtYXAscmUuZW1pc3NpdmVtYXAscmUuYnVtcG1hcCxyZS5ub3JtYWxtYXAscmUuZGlzcGxhY2VtZW50bWFwLHJlLnJvdWdobmVzc21hcCxyZS5tZXRhbG5lc3NtYXAscmUuZm9nLHJlLmxpZ2h0cyx7ZW1pc3NpdmU6e3ZhbHVlOm5ldyBuZSgwKX0scm91Z2huZXNzOnt2YWx1ZToxfSxtZXRhbG5lc3M6e3ZhbHVlOjB9LGVudk1hcEludGVuc2l0eTp7dmFsdWU6MX19XSksdmVydGV4U2hhZGVyOmhyLm1lc2hwaHlzaWNhbF92ZXJ0LGZyYWdtZW50U2hhZGVyOmhyLm1lc2hwaHlzaWNhbF9mcmFnfSx0b29uOnt1bmlmb3JtczpUYShbcmUuY29tbW9uLHJlLmFvbWFwLHJlLmxpZ2h0bWFwLHJlLmVtaXNzaXZlbWFwLHJlLmJ1bXBtYXAscmUubm9ybWFsbWFwLHJlLmRpc3BsYWNlbWVudG1hcCxyZS5ncmFkaWVudG1hcCxyZS5mb2cscmUubGlnaHRzLHtlbWlzc2l2ZTp7dmFsdWU6bmV3IG5lKDApfX1dKSx2ZXJ0ZXhTaGFkZXI6aHIubWVzaHRvb25fdmVydCxmcmFnbWVudFNoYWRlcjpoci5tZXNodG9vbl9mcmFnfSxtYXRjYXA6e3VuaWZvcm1zOlRhKFtyZS5jb21tb24scmUuYnVtcG1hcCxyZS5ub3JtYWxtYXAscmUuZGlzcGxhY2VtZW50bWFwLHJlLmZvZyx7bWF0Y2FwOnt2YWx1ZTpudWxsfX1dKSx2ZXJ0ZXhTaGFkZXI6aHIubWVzaG1hdGNhcF92ZXJ0LGZyYWdtZW50U2hhZGVyOmhyLm1lc2htYXRjYXBfZnJhZ30scG9pbnRzOnt1bmlmb3JtczpUYShbcmUucG9pbnRzLHJlLmZvZ10pLHZlcnRleFNoYWRlcjpoci5wb2ludHNfdmVydCxmcmFnbWVudFNoYWRlcjpoci5wb2ludHNfZnJhZ30sZGFzaGVkOnt1bmlmb3JtczpUYShbcmUuY29tbW9uLHJlLmZvZyx7c2NhbGU6e3ZhbHVlOjF9LGRhc2hTaXplOnt2YWx1ZToxfSx0b3RhbFNpemU6e3ZhbHVlOjJ9fV0pLHZlcnRleFNoYWRlcjpoci5saW5lZGFzaGVkX3ZlcnQsZnJhZ21lbnRTaGFkZXI6aHIubGluZWRhc2hlZF9mcmFnfSxkZXB0aDp7dW5pZm9ybXM6VGEoW3JlLmNvbW1vbixyZS5kaXNwbGFjZW1lbnRtYXBdKSx2ZXJ0ZXhTaGFkZXI6aHIuZGVwdGhfdmVydCxmcmFnbWVudFNoYWRlcjpoci5kZXB0aF9mcmFnfSxub3JtYWw6e3VuaWZvcm1zOlRhKFtyZS5jb21tb24scmUuYnVtcG1hcCxyZS5ub3JtYWxtYXAscmUuZGlzcGxhY2VtZW50bWFwLHtvcGFjaXR5Ont2YWx1ZToxfX1dKSx2ZXJ0ZXhTaGFkZXI6aHIubWVzaG5vcm1hbF92ZXJ0LGZyYWdtZW50U2hhZGVyOmhyLm1lc2hub3JtYWxfZnJhZ30sc3ByaXRlOnt1bmlmb3JtczpUYShbcmUuc3ByaXRlLHJlLmZvZ10pLHZlcnRleFNoYWRlcjpoci5zcHJpdGVfdmVydCxmcmFnbWVudFNoYWRlcjpoci5zcHJpdGVfZnJhZ30sYmFja2dyb3VuZDp7dW5pZm9ybXM6e3V2VHJhbnNmb3JtOnt2YWx1ZTpuZXcga2l9LHQyRDp7dmFsdWU6bnVsbH19LHZlcnRleFNoYWRlcjpoci5iYWNrZ3JvdW5kX3ZlcnQsZnJhZ21lbnRTaGFkZXI6aHIuYmFja2dyb3VuZF9mcmFnfSxjdWJlOnt1bmlmb3JtczpUYShbcmUuZW52bWFwLHtvcGFjaXR5Ont2YWx1ZToxfX1dKSx2ZXJ0ZXhTaGFkZXI6aHIuY3ViZV92ZXJ0LGZyYWdtZW50U2hhZGVyOmhyLmN1YmVfZnJhZ30sZXF1aXJlY3Q6e3VuaWZvcm1zOnt0RXF1aXJlY3Q6e3ZhbHVlOm51bGx9fSx2ZXJ0ZXhTaGFkZXI6aHIuZXF1aXJlY3RfdmVydCxmcmFnbWVudFNoYWRlcjpoci5lcXVpcmVjdF9mcmFnfSxkaXN0YW5jZVJHQkE6e3VuaWZvcm1zOlRhKFtyZS5jb21tb24scmUuZGlzcGxhY2VtZW50bWFwLHtyZWZlcmVuY2VQb3NpdGlvbjp7dmFsdWU6bmV3IGp9LG5lYXJEaXN0YW5jZTp7dmFsdWU6MX0sZmFyRGlzdGFuY2U6e3ZhbHVlOjFlM319XSksdmVydGV4U2hhZGVyOmhyLmRpc3RhbmNlUkdCQV92ZXJ0LGZyYWdtZW50U2hhZGVyOmhyLmRpc3RhbmNlUkdCQV9mcmFnfSxzaGFkb3c6e3VuaWZvcm1zOlRhKFtyZS5saWdodHMscmUuZm9nLHtjb2xvcjp7dmFsdWU6bmV3IG5lKDApfSxvcGFjaXR5Ont2YWx1ZToxfX1dKSx2ZXJ0ZXhTaGFkZXI6aHIuc2hhZG93X3ZlcnQsZnJhZ21lbnRTaGFkZXI6aHIuc2hhZG93X2ZyYWd9fTthaC5waHlzaWNhbD17dW5pZm9ybXM6VGEoW2FoLnN0YW5kYXJkLnVuaWZvcm1zLHtjbGVhcmNvYXQ6e3ZhbHVlOjB9LGNsZWFyY29hdE1hcDp7dmFsdWU6bnVsbH0sY2xlYXJjb2F0Um91Z2huZXNzOnt2YWx1ZTowfSxjbGVhcmNvYXRSb3VnaG5lc3NNYXA6e3ZhbHVlOm51bGx9LGNsZWFyY29hdE5vcm1hbFNjYWxlOnt2YWx1ZTpuZXcgTHQoMSwxKX0sY2xlYXJjb2F0Tm9ybWFsTWFwOnt2YWx1ZTpudWxsfSxzaGVlbjp7dmFsdWU6MH0sc2hlZW5Db2xvcjp7dmFsdWU6bmV3IG5lKDApfSxzaGVlbkNvbG9yTWFwOnt2YWx1ZTpudWxsfSxzaGVlblJvdWdobmVzczp7dmFsdWU6MX0sc2hlZW5Sb3VnaG5lc3NNYXA6e3ZhbHVlOm51bGx9LHRyYW5zbWlzc2lvbjp7dmFsdWU6MH0sdHJhbnNtaXNzaW9uTWFwOnt2YWx1ZTpudWxsfSx0cmFuc21pc3Npb25TYW1wbGVyU2l6ZTp7dmFsdWU6bmV3IEx0fSx0cmFuc21pc3Npb25TYW1wbGVyTWFwOnt2YWx1ZTpudWxsfSx0aGlja25lc3M6e3ZhbHVlOjB9LHRoaWNrbmVzc01hcDp7dmFsdWU6bnVsbH0sYXR0ZW51YXRpb25EaXN0YW5jZTp7dmFsdWU6MH0sYXR0ZW51YXRpb25Db2xvcjp7dmFsdWU6bmV3IG5lKDApfSxzcGVjdWxhckludGVuc2l0eTp7dmFsdWU6MX0sc3BlY3VsYXJJbnRlbnNpdHlNYXA6e3ZhbHVlOm51bGx9LHNwZWN1bGFyQ29sb3I6e3ZhbHVlOm5ldyBuZSgxLDEsMSl9LHNwZWN1bGFyQ29sb3JNYXA6e3ZhbHVlOm51bGx9fV0pLHZlcnRleFNoYWRlcjpoci5tZXNocGh5c2ljYWxfdmVydCxmcmFnbWVudFNoYWRlcjpoci5tZXNocGh5c2ljYWxfZnJhZ307ZnVuY3Rpb24gcWRyKGUsdCxyLG4saSxvKXtsZXQgYT1uZXcgbmUoMCkscz1pPT09ITA/MDoxLGwsYyx1PW51bGwsaD0wLGY9bnVsbDtmdW5jdGlvbiBwKGcsXyl7bGV0IHk9ITEseD1fLmlzU2NlbmU9PT0hMD9fLmJhY2tncm91bmQ6bnVsbDt4JiZ4LmlzVGV4dHVyZSYmKHg9dC5nZXQoeCkpO2xldCBiPWUueHIsUz1iLmdldFNlc3Npb24mJmIuZ2V0U2Vzc2lvbigpO1MmJlMuZW52aXJvbm1lbnRCbGVuZE1vZGU9PT0iYWRkaXRpdmUiJiYoeD1udWxsKSx4PT09bnVsbD9kKGEscyk6eCYmeC5pc0NvbG9yJiYoZCh4LDEpLHk9ITApLChlLmF1dG9DbGVhcnx8eSkmJmUuY2xlYXIoZS5hdXRvQ2xlYXJDb2xvcixlLmF1dG9DbGVhckRlcHRoLGUuYXV0b0NsZWFyU3RlbmNpbCkseCYmKHguaXNDdWJlVGV4dHVyZXx8eC5tYXBwaW5nPT09eE0pPyhjPT09dm9pZCAwJiYoYz1uZXcgZWkobmV3IFFmKDEsMSwxKSxuZXcgbGgoe25hbWU6IkJhY2tncm91bmRDdWJlTWF0ZXJpYWwiLHVuaWZvcm1zOlozKGFoLmN1YmUudW5pZm9ybXMpLHZlcnRleFNoYWRlcjphaC5jdWJlLnZlcnRleFNoYWRlcixmcmFnbWVudFNoYWRlcjphaC5jdWJlLmZyYWdtZW50U2hhZGVyLHNpZGU6SWksZGVwdGhUZXN0OiExLGRlcHRoV3JpdGU6ITEsZm9nOiExfSkpLGMuZ2VvbWV0cnkuZGVsZXRlQXR0cmlidXRlKCJub3JtYWwiKSxjLmdlb21ldHJ5LmRlbGV0ZUF0dHJpYnV0ZSgidXYiKSxjLm9uQmVmb3JlUmVuZGVyPWZ1bmN0aW9uKEMsUCxrKXt0aGlzLm1hdHJpeFdvcmxkLmNvcHlQb3NpdGlvbihrLm1hdHJpeFdvcmxkKX0sT2JqZWN0LmRlZmluZVByb3BlcnR5KGMubWF0ZXJpYWwsImVudk1hcCIse2dldDpmdW5jdGlvbigpe3JldHVybiB0aGlzLnVuaWZvcm1zLmVudk1hcC52YWx1ZX19KSxuLnVwZGF0ZShjKSksYy5tYXRlcmlhbC51bmlmb3Jtcy5lbnZNYXAudmFsdWU9eCxjLm1hdGVyaWFsLnVuaWZvcm1zLmZsaXBFbnZNYXAudmFsdWU9eC5pc0N1YmVUZXh0dXJlJiZ4LmlzUmVuZGVyVGFyZ2V0VGV4dHVyZT09PSExPy0xOjEsKHUhPT14fHxoIT09eC52ZXJzaW9ufHxmIT09ZS50b25lTWFwcGluZykmJihjLm1hdGVyaWFsLm5lZWRzVXBkYXRlPSEwLHU9eCxoPXgudmVyc2lvbixmPWUudG9uZU1hcHBpbmcpLGcudW5zaGlmdChjLGMuZ2VvbWV0cnksYy5tYXRlcmlhbCwwLDAsbnVsbCkpOngmJnguaXNUZXh0dXJlJiYobD09PXZvaWQgMCYmKGw9bmV3IGVpKG5ldyBWMCgyLDIpLG5ldyBsaCh7bmFtZToiQmFja2dyb3VuZE1hdGVyaWFsIix1bmlmb3JtczpaMyhhaC5iYWNrZ3JvdW5kLnVuaWZvcm1zKSx2ZXJ0ZXhTaGFkZXI6YWguYmFja2dyb3VuZC52ZXJ0ZXhTaGFkZXIsZnJhZ21lbnRTaGFkZXI6YWguYmFja2dyb3VuZC5mcmFnbWVudFNoYWRlcixzaWRlOkl2LGRlcHRoVGVzdDohMSxkZXB0aFdyaXRlOiExLGZvZzohMX0pKSxsLmdlb21ldHJ5LmRlbGV0ZUF0dHJpYnV0ZSgibm9ybWFsIiksT2JqZWN0LmRlZmluZVByb3BlcnR5KGwubWF0ZXJpYWwsIm1hcCIse2dldDpmdW5jdGlvbigpe3JldHVybiB0aGlzLnVuaWZvcm1zLnQyRC52YWx1ZX19KSxuLnVwZGF0ZShsKSksbC5tYXRlcmlhbC51bmlmb3Jtcy50MkQudmFsdWU9eCx4Lm1hdHJpeEF1dG9VcGRhdGU9PT0hMCYmeC51cGRhdGVNYXRyaXgoKSxsLm1hdGVyaWFsLnVuaWZvcm1zLnV2VHJhbnNmb3JtLnZhbHVlLmNvcHkoeC5tYXRyaXgpLCh1IT09eHx8aCE9PXgudmVyc2lvbnx8ZiE9PWUudG9uZU1hcHBpbmcpJiYobC5tYXRlcmlhbC5uZWVkc1VwZGF0ZT0hMCx1PXgsaD14LnZlcnNpb24sZj1lLnRvbmVNYXBwaW5nKSxnLnVuc2hpZnQobCxsLmdlb21ldHJ5LGwubWF0ZXJpYWwsMCwwLG51bGwpKX1mdW5jdGlvbiBkKGcsXyl7ci5idWZmZXJzLmNvbG9yLnNldENsZWFyKGcucixnLmcsZy5iLF8sbyl9cmV0dXJue2dldENsZWFyQ29sb3I6ZnVuY3Rpb24oKXtyZXR1cm4gYX0sc2V0Q2xlYXJDb2xvcjpmdW5jdGlvbihnLF89MSl7YS5zZXQoZykscz1fLGQoYSxzKX0sZ2V0Q2xlYXJBbHBoYTpmdW5jdGlvbigpe3JldHVybiBzfSxzZXRDbGVhckFscGhhOmZ1bmN0aW9uKGcpe3M9ZyxkKGEscyl9LHJlbmRlcjpwfX1mdW5jdGlvbiBHZHIoZSx0LHIsbil7bGV0IGk9ZS5nZXRQYXJhbWV0ZXIoMzQ5MjEpLG89bi5pc1dlYkdMMj9udWxsOnQuZ2V0KCJPRVNfdmVydGV4X2FycmF5X29iamVjdCIpLGE9bi5pc1dlYkdMMnx8byE9PW51bGwscz17fSxsPWcobnVsbCksYz1sO2Z1bmN0aW9uIHUoUixGLHosVSxXKXtsZXQgWj0hMTtpZihhKXtsZXQgcnQ9ZChVLHosRik7YyE9PXJ0JiYoYz1ydCxmKGMub2JqZWN0KSksWj1fKFUsVyksWiYmeShVLFcpfWVsc2V7bGV0IHJ0PUYud2lyZWZyYW1lPT09ITA7KGMuZ2VvbWV0cnkhPT1VLmlkfHxjLnByb2dyYW0hPT16LmlkfHxjLndpcmVmcmFtZSE9PXJ0KSYmKGMuZ2VvbWV0cnk9VS5pZCxjLnByb2dyYW09ei5pZCxjLndpcmVmcmFtZT1ydCxaPSEwKX1SLmlzSW5zdGFuY2VkTWVzaD09PSEwJiYoWj0hMCksVyE9PW51bGwmJnIudXBkYXRlKFcsMzQ5NjMpLFomJihrKFIsRix6LFUpLFchPT1udWxsJiZlLmJpbmRCdWZmZXIoMzQ5NjMsci5nZXQoVykuYnVmZmVyKSl9ZnVuY3Rpb24gaCgpe3JldHVybiBuLmlzV2ViR0wyP2UuY3JlYXRlVmVydGV4QXJyYXkoKTpvLmNyZWF0ZVZlcnRleEFycmF5T0VTKCl9ZnVuY3Rpb24gZihSKXtyZXR1cm4gbi5pc1dlYkdMMj9lLmJpbmRWZXJ0ZXhBcnJheShSKTpvLmJpbmRWZXJ0ZXhBcnJheU9FUyhSKX1mdW5jdGlvbiBwKFIpe3JldHVybiBuLmlzV2ViR0wyP2UuZGVsZXRlVmVydGV4QXJyYXkoUik6by5kZWxldGVWZXJ0ZXhBcnJheU9FUyhSKX1mdW5jdGlvbiBkKFIsRix6KXtsZXQgVT16LndpcmVmcmFtZT09PSEwLFc9c1tSLmlkXTtXPT09dm9pZCAwJiYoVz17fSxzW1IuaWRdPVcpO2xldCBaPVdbRi5pZF07Wj09PXZvaWQgMCYmKFo9e30sV1tGLmlkXT1aKTtsZXQgcnQ9WltVXTtyZXR1cm4gcnQ9PT12b2lkIDAmJihydD1nKGgoKSksWltVXT1ydCkscnR9ZnVuY3Rpb24gZyhSKXtsZXQgRj1bXSx6PVtdLFU9W107Zm9yKGxldCBXPTA7VzxpO1crKylGW1ddPTAseltXXT0wLFVbV109MDtyZXR1cm57Z2VvbWV0cnk6bnVsbCxwcm9ncmFtOm51bGwsd2lyZWZyYW1lOiExLG5ld0F0dHJpYnV0ZXM6RixlbmFibGVkQXR0cmlidXRlczp6LGF0dHJpYnV0ZURpdmlzb3JzOlUsb2JqZWN0OlIsYXR0cmlidXRlczp7fSxpbmRleDpudWxsfX1mdW5jdGlvbiBfKFIsRil7bGV0IHo9Yy5hdHRyaWJ1dGVzLFU9Ui5hdHRyaWJ1dGVzLFc9MDtmb3IobGV0IFogaW4gVSl7bGV0IHJ0PXpbWl0sb3Q9VVtaXTtpZihydD09PXZvaWQgMHx8cnQuYXR0cmlidXRlIT09b3R8fHJ0LmRhdGEhPT1vdC5kYXRhKXJldHVybiEwO1crK31yZXR1cm4gYy5hdHRyaWJ1dGVzTnVtIT09V3x8Yy5pbmRleCE9PUZ9ZnVuY3Rpb24geShSLEYpe2xldCB6PXt9LFU9Ui5hdHRyaWJ1dGVzLFc9MDtmb3IobGV0IFogaW4gVSl7bGV0IHJ0PVVbWl0sb3Q9e307b3QuYXR0cmlidXRlPXJ0LHJ0LmRhdGEmJihvdC5kYXRhPXJ0LmRhdGEpLHpbWl09b3QsVysrfWMuYXR0cmlidXRlcz16LGMuYXR0cmlidXRlc051bT1XLGMuaW5kZXg9Rn1mdW5jdGlvbiB4KCl7bGV0IFI9Yy5uZXdBdHRyaWJ1dGVzO2ZvcihsZXQgRj0wLHo9Ui5sZW5ndGg7Rjx6O0YrKylSW0ZdPTB9ZnVuY3Rpb24gYihSKXtTKFIsMCl9ZnVuY3Rpb24gUyhSLEYpe2xldCB6PWMubmV3QXR0cmlidXRlcyxVPWMuZW5hYmxlZEF0dHJpYnV0ZXMsVz1jLmF0dHJpYnV0ZURpdmlzb3JzO3pbUl09MSxVW1JdPT09MCYmKGUuZW5hYmxlVmVydGV4QXR0cmliQXJyYXkoUiksVVtSXT0xKSxXW1JdIT09RiYmKChuLmlzV2ViR0wyP2U6dC5nZXQoIkFOR0xFX2luc3RhbmNlZF9hcnJheXMiKSlbbi5pc1dlYkdMMj8idmVydGV4QXR0cmliRGl2aXNvciI6InZlcnRleEF0dHJpYkRpdmlzb3JBTkdMRSJdKFIsRiksV1tSXT1GKX1mdW5jdGlvbiBDKCl7bGV0IFI9Yy5uZXdBdHRyaWJ1dGVzLEY9Yy5lbmFibGVkQXR0cmlidXRlcztmb3IobGV0IHo9MCxVPUYubGVuZ3RoO3o8VTt6KyspRlt6XSE9PVJbel0mJihlLmRpc2FibGVWZXJ0ZXhBdHRyaWJBcnJheSh6KSxGW3pdPTApfWZ1bmN0aW9uIFAoUixGLHosVSxXLFope24uaXNXZWJHTDI9PT0hMCYmKHo9PT01MTI0fHx6PT09NTEyNSk/ZS52ZXJ0ZXhBdHRyaWJJUG9pbnRlcihSLEYseixXLFopOmUudmVydGV4QXR0cmliUG9pbnRlcihSLEYseixVLFcsWil9ZnVuY3Rpb24gayhSLEYseixVKXtpZihuLmlzV2ViR0wyPT09ITEmJihSLmlzSW5zdGFuY2VkTWVzaHx8VS5pc0luc3RhbmNlZEJ1ZmZlckdlb21ldHJ5KSYmdC5nZXQoIkFOR0xFX2luc3RhbmNlZF9hcnJheXMiKT09PW51bGwpcmV0dXJuO3goKTtsZXQgVz1VLmF0dHJpYnV0ZXMsWj16LmdldEF0dHJpYnV0ZXMoKSxydD1GLmRlZmF1bHRBdHRyaWJ1dGVWYWx1ZXM7Zm9yKGxldCBvdCBpbiBaKXtsZXQgc3Q9WltvdF07aWYoc3QubG9jYXRpb24+PTApe2xldCBTdD1XW290XTtpZihTdD09PXZvaWQgMCYmKG90PT09Imluc3RhbmNlTWF0cml4IiYmUi5pbnN0YW5jZU1hdHJpeCYmKFN0PVIuaW5zdGFuY2VNYXRyaXgpLG90PT09Imluc3RhbmNlQ29sb3IiJiZSLmluc3RhbmNlQ29sb3ImJihTdD1SLmluc3RhbmNlQ29sb3IpKSxTdCE9PXZvaWQgMCl7bGV0IGJ0PVN0Lm5vcm1hbGl6ZWQsTXQ9U3QuaXRlbVNpemUsbHQ9ci5nZXQoU3QpO2lmKGx0PT09dm9pZCAwKWNvbnRpbnVlO2xldCBLdD1sdC5idWZmZXIsX3Q9bHQudHlwZSxjdD1sdC5ieXRlc1BlckVsZW1lbnQ7aWYoU3QuaXNJbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZSl7bGV0IFg9U3QuZGF0YSxldD1YLnN0cmlkZSxkdD1TdC5vZmZzZXQ7aWYoWCYmWC5pc0luc3RhbmNlZEludGVybGVhdmVkQnVmZmVyKXtmb3IobGV0IHE9MDtxPHN0LmxvY2F0aW9uU2l6ZTtxKyspUyhzdC5sb2NhdGlvbitxLFgubWVzaFBlckF0dHJpYnV0ZSk7Ui5pc0luc3RhbmNlZE1lc2ghPT0hMCYmVS5fbWF4SW5zdGFuY2VDb3VudD09PXZvaWQgMCYmKFUuX21heEluc3RhbmNlQ291bnQ9WC5tZXNoUGVyQXR0cmlidXRlKlguY291bnQpfWVsc2UgZm9yKGxldCBxPTA7cTxzdC5sb2NhdGlvblNpemU7cSsrKWIoc3QubG9jYXRpb24rcSk7ZS5iaW5kQnVmZmVyKDM0OTYyLEt0KTtmb3IobGV0IHE9MDtxPHN0LmxvY2F0aW9uU2l6ZTtxKyspUChzdC5sb2NhdGlvbitxLE10L3N0LmxvY2F0aW9uU2l6ZSxfdCxidCxldCpjdCwoZHQrTXQvc3QubG9jYXRpb25TaXplKnEpKmN0KX1lbHNle2lmKFN0LmlzSW5zdGFuY2VkQnVmZmVyQXR0cmlidXRlKXtmb3IobGV0IFg9MDtYPHN0LmxvY2F0aW9uU2l6ZTtYKyspUyhzdC5sb2NhdGlvbitYLFN0Lm1lc2hQZXJBdHRyaWJ1dGUpO1IuaXNJbnN0YW5jZWRNZXNoIT09ITAmJlUuX21heEluc3RhbmNlQ291bnQ9PT12b2lkIDAmJihVLl9tYXhJbnN0YW5jZUNvdW50PVN0Lm1lc2hQZXJBdHRyaWJ1dGUqU3QuY291bnQpfWVsc2UgZm9yKGxldCBYPTA7WDxzdC5sb2NhdGlvblNpemU7WCsrKWIoc3QubG9jYXRpb24rWCk7ZS5iaW5kQnVmZmVyKDM0OTYyLEt0KTtmb3IobGV0IFg9MDtYPHN0LmxvY2F0aW9uU2l6ZTtYKyspUChzdC5sb2NhdGlvbitYLE10L3N0LmxvY2F0aW9uU2l6ZSxfdCxidCxNdCpjdCxNdC9zdC5sb2NhdGlvblNpemUqWCpjdCl9fWVsc2UgaWYocnQhPT12b2lkIDApe2xldCBidD1ydFtvdF07aWYoYnQhPT12b2lkIDApc3dpdGNoKGJ0Lmxlbmd0aCl7Y2FzZSAyOmUudmVydGV4QXR0cmliMmZ2KHN0LmxvY2F0aW9uLGJ0KTticmVhaztjYXNlIDM6ZS52ZXJ0ZXhBdHRyaWIzZnYoc3QubG9jYXRpb24sYnQpO2JyZWFrO2Nhc2UgNDplLnZlcnRleEF0dHJpYjRmdihzdC5sb2NhdGlvbixidCk7YnJlYWs7ZGVmYXVsdDplLnZlcnRleEF0dHJpYjFmdihzdC5sb2NhdGlvbixidCl9fX19QygpfWZ1bmN0aW9uIE8oKXtJKCk7Zm9yKGxldCBSIGluIHMpe2xldCBGPXNbUl07Zm9yKGxldCB6IGluIEYpe2xldCBVPUZbel07Zm9yKGxldCBXIGluIFUpcChVW1ddLm9iamVjdCksZGVsZXRlIFVbV107ZGVsZXRlIEZbel19ZGVsZXRlIHNbUl19fWZ1bmN0aW9uIEQoUil7aWYoc1tSLmlkXT09PXZvaWQgMClyZXR1cm47bGV0IEY9c1tSLmlkXTtmb3IobGV0IHogaW4gRil7bGV0IFU9Rlt6XTtmb3IobGV0IFcgaW4gVSlwKFVbV10ub2JqZWN0KSxkZWxldGUgVVtXXTtkZWxldGUgRlt6XX1kZWxldGUgc1tSLmlkXX1mdW5jdGlvbiBCKFIpe2ZvcihsZXQgRiBpbiBzKXtsZXQgej1zW0ZdO2lmKHpbUi5pZF09PT12b2lkIDApY29udGludWU7bGV0IFU9eltSLmlkXTtmb3IobGV0IFcgaW4gVSlwKFVbV10ub2JqZWN0KSxkZWxldGUgVVtXXTtkZWxldGUgeltSLmlkXX19ZnVuY3Rpb24gSSgpe0woKSxjIT09bCYmKGM9bCxmKGMub2JqZWN0KSl9ZnVuY3Rpb24gTCgpe2wuZ2VvbWV0cnk9bnVsbCxsLnByb2dyYW09bnVsbCxsLndpcmVmcmFtZT0hMX1yZXR1cm57c2V0dXA6dSxyZXNldDpJLHJlc2V0RGVmYXVsdFN0YXRlOkwsZGlzcG9zZTpPLHJlbGVhc2VTdGF0ZXNPZkdlb21ldHJ5OkQscmVsZWFzZVN0YXRlc09mUHJvZ3JhbTpCLGluaXRBdHRyaWJ1dGVzOngsZW5hYmxlQXR0cmlidXRlOmIsZGlzYWJsZVVudXNlZEF0dHJpYnV0ZXM6Q319ZnVuY3Rpb24gV2RyKGUsdCxyLG4pe2xldCBpPW4uaXNXZWJHTDIsbztmdW5jdGlvbiBhKGMpe289Y31mdW5jdGlvbiBzKGMsdSl7ZS5kcmF3QXJyYXlzKG8sYyx1KSxyLnVwZGF0ZSh1LG8sMSl9ZnVuY3Rpb24gbChjLHUsaCl7aWYoaD09PTApcmV0dXJuO2xldCBmLHA7aWYoaSlmPWUscD0iZHJhd0FycmF5c0luc3RhbmNlZCI7ZWxzZSBpZihmPXQuZ2V0KCJBTkdMRV9pbnN0YW5jZWRfYXJyYXlzIikscD0iZHJhd0FycmF5c0luc3RhbmNlZEFOR0xFIixmPT09bnVsbCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xCdWZmZXJSZW5kZXJlcjogdXNpbmcgVEhSRUUuSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnkgYnV0IGhhcmR3YXJlIGRvZXMgbm90IHN1cHBvcnQgZXh0ZW5zaW9uIEFOR0xFX2luc3RhbmNlZF9hcnJheXMuIik7cmV0dXJufWZbcF0obyxjLHUsaCksci51cGRhdGUodSxvLGgpfXRoaXMuc2V0TW9kZT1hLHRoaXMucmVuZGVyPXMsdGhpcy5yZW5kZXJJbnN0YW5jZXM9bH1mdW5jdGlvbiBZZHIoZSx0LHIpe2xldCBuO2Z1bmN0aW9uIGkoKXtpZihuIT09dm9pZCAwKXJldHVybiBuO2lmKHQuaGFzKCJFWFRfdGV4dHVyZV9maWx0ZXJfYW5pc290cm9waWMiKT09PSEwKXtsZXQgaz10LmdldCgiRVhUX3RleHR1cmVfZmlsdGVyX2FuaXNvdHJvcGljIik7bj1lLmdldFBhcmFtZXRlcihrLk1BWF9URVhUVVJFX01BWF9BTklTT1RST1BZX0VYVCl9ZWxzZSBuPTA7cmV0dXJuIG59ZnVuY3Rpb24gbyhrKXtpZihrPT09ImhpZ2hwIil7aWYoZS5nZXRTaGFkZXJQcmVjaXNpb25Gb3JtYXQoMzU2MzMsMzYzMzgpLnByZWNpc2lvbj4wJiZlLmdldFNoYWRlclByZWNpc2lvbkZvcm1hdCgzNTYzMiwzNjMzOCkucHJlY2lzaW9uPjApcmV0dXJuImhpZ2hwIjtrPSJtZWRpdW1wIn1yZXR1cm4gaz09PSJtZWRpdW1wIiYmZS5nZXRTaGFkZXJQcmVjaXNpb25Gb3JtYXQoMzU2MzMsMzYzMzcpLnByZWNpc2lvbj4wJiZlLmdldFNoYWRlclByZWNpc2lvbkZvcm1hdCgzNTYzMiwzNjMzNykucHJlY2lzaW9uPjA/Im1lZGl1bXAiOiJsb3dwIn1sZXQgYT10eXBlb2YgV2ViR0wyUmVuZGVyaW5nQ29udGV4dCE9InVuZGVmaW5lZCImJmUgaW5zdGFuY2VvZiBXZWJHTDJSZW5kZXJpbmdDb250ZXh0fHx0eXBlb2YgV2ViR0wyQ29tcHV0ZVJlbmRlcmluZ0NvbnRleHQhPSJ1bmRlZmluZWQiJiZlIGluc3RhbmNlb2YgV2ViR0wyQ29tcHV0ZVJlbmRlcmluZ0NvbnRleHQscz1yLnByZWNpc2lvbiE9PXZvaWQgMD9yLnByZWNpc2lvbjoiaGlnaHAiLGw9byhzKTtsIT09cyYmKGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjoiLHMsIm5vdCBzdXBwb3J0ZWQsIHVzaW5nIixsLCJpbnN0ZWFkLiIpLHM9bCk7bGV0IGM9YXx8dC5oYXMoIldFQkdMX2RyYXdfYnVmZmVycyIpLHU9ci5sb2dhcml0aG1pY0RlcHRoQnVmZmVyPT09ITAsaD1lLmdldFBhcmFtZXRlcigzNDkzMCksZj1lLmdldFBhcmFtZXRlcigzNTY2MCkscD1lLmdldFBhcmFtZXRlcigzMzc5KSxkPWUuZ2V0UGFyYW1ldGVyKDM0MDc2KSxnPWUuZ2V0UGFyYW1ldGVyKDM0OTIxKSxfPWUuZ2V0UGFyYW1ldGVyKDM2MzQ3KSx5PWUuZ2V0UGFyYW1ldGVyKDM2MzQ4KSx4PWUuZ2V0UGFyYW1ldGVyKDM2MzQ5KSxiPWY+MCxTPWF8fHQuaGFzKCJPRVNfdGV4dHVyZV9mbG9hdCIpLEM9YiYmUyxQPWE/ZS5nZXRQYXJhbWV0ZXIoMzYxODMpOjA7cmV0dXJue2lzV2ViR0wyOmEsZHJhd0J1ZmZlcnM6YyxnZXRNYXhBbmlzb3Ryb3B5OmksZ2V0TWF4UHJlY2lzaW9uOm8scHJlY2lzaW9uOnMsbG9nYXJpdGhtaWNEZXB0aEJ1ZmZlcjp1LG1heFRleHR1cmVzOmgsbWF4VmVydGV4VGV4dHVyZXM6ZixtYXhUZXh0dXJlU2l6ZTpwLG1heEN1YmVtYXBTaXplOmQsbWF4QXR0cmlidXRlczpnLG1heFZlcnRleFVuaWZvcm1zOl8sbWF4VmFyeWluZ3M6eSxtYXhGcmFnbWVudFVuaWZvcm1zOngsdmVydGV4VGV4dHVyZXM6YixmbG9hdEZyYWdtZW50VGV4dHVyZXM6UyxmbG9hdFZlcnRleFRleHR1cmVzOkMsbWF4U2FtcGxlczpQfX1mdW5jdGlvbiBqZHIoZSl7bGV0IHQ9dGhpcyxyPW51bGwsbj0wLGk9ITEsbz0hMSxhPW5ldyAkYyxzPW5ldyBraSxsPXt2YWx1ZTpudWxsLG5lZWRzVXBkYXRlOiExfTt0aGlzLnVuaWZvcm09bCx0aGlzLm51bVBsYW5lcz0wLHRoaXMubnVtSW50ZXJzZWN0aW9uPTAsdGhpcy5pbml0PWZ1bmN0aW9uKGgsZixwKXtsZXQgZD1oLmxlbmd0aCE9PTB8fGZ8fG4hPT0wfHxpO3JldHVybiBpPWYscj11KGgscCwwKSxuPWgubGVuZ3RoLGR9LHRoaXMuYmVnaW5TaGFkb3dzPWZ1bmN0aW9uKCl7bz0hMCx1KG51bGwpfSx0aGlzLmVuZFNoYWRvd3M9ZnVuY3Rpb24oKXtvPSExLGMoKX0sdGhpcy5zZXRTdGF0ZT1mdW5jdGlvbihoLGYscCl7bGV0IGQ9aC5jbGlwcGluZ1BsYW5lcyxnPWguY2xpcEludGVyc2VjdGlvbixfPWguY2xpcFNoYWRvd3MseT1lLmdldChoKTtpZighaXx8ZD09PW51bGx8fGQubGVuZ3RoPT09MHx8byYmIV8pbz91KG51bGwpOmMoKTtlbHNle2xldCB4PW8/MDpuLGI9eCo0LFM9eS5jbGlwcGluZ1N0YXRlfHxudWxsO2wudmFsdWU9UyxTPXUoZCxmLGIscCk7Zm9yKGxldCBDPTA7QyE9PWI7KytDKVNbQ109cltDXTt5LmNsaXBwaW5nU3RhdGU9Uyx0aGlzLm51bUludGVyc2VjdGlvbj1nP3RoaXMubnVtUGxhbmVzOjAsdGhpcy5udW1QbGFuZXMrPXh9fTtmdW5jdGlvbiBjKCl7bC52YWx1ZSE9PXImJihsLnZhbHVlPXIsbC5uZWVkc1VwZGF0ZT1uPjApLHQubnVtUGxhbmVzPW4sdC5udW1JbnRlcnNlY3Rpb249MH1mdW5jdGlvbiB1KGgsZixwLGQpe2xldCBnPWghPT1udWxsP2gubGVuZ3RoOjAsXz1udWxsO2lmKGchPT0wKXtpZihfPWwudmFsdWUsZCE9PSEwfHxfPT09bnVsbCl7bGV0IHk9cCtnKjQseD1mLm1hdHJpeFdvcmxkSW52ZXJzZTtzLmdldE5vcm1hbE1hdHJpeCh4KSwoXz09PW51bGx8fF8ubGVuZ3RoPHkpJiYoXz1uZXcgRmxvYXQzMkFycmF5KHkpKTtmb3IobGV0IGI9MCxTPXA7YiE9PWc7KytiLFMrPTQpYS5jb3B5KGhbYl0pLmFwcGx5TWF0cml4NCh4LHMpLGEubm9ybWFsLnRvQXJyYXkoXyxTKSxfW1MrM109YS5jb25zdGFudH1sLnZhbHVlPV8sbC5uZWVkc1VwZGF0ZT0hMH1yZXR1cm4gdC5udW1QbGFuZXM9Zyx0Lm51bUludGVyc2VjdGlvbj0wLF99fWZ1bmN0aW9uIFhkcihlKXtsZXQgdD1uZXcgV2Vha01hcDtmdW5jdGlvbiByKGEscyl7cmV0dXJuIHM9PT1XUD9hLm1hcHBpbmc9bng6cz09PVlQJiYoYS5tYXBwaW5nPWl4KSxhfWZ1bmN0aW9uIG4oYSl7aWYoYSYmYS5pc1RleHR1cmUmJmEuaXNSZW5kZXJUYXJnZXRUZXh0dXJlPT09ITEpe2xldCBzPWEubWFwcGluZztpZihzPT09V1B8fHM9PT1ZUClpZih0LmhhcyhhKSl7bGV0IGw9dC5nZXQoYSkudGV4dHVyZTtyZXR1cm4gcihsLGEubWFwcGluZyl9ZWxzZXtsZXQgbD1hLmltYWdlO2lmKGwmJmwuaGVpZ2h0PjApe2xldCBjPW5ldyBRMyhsLmhlaWdodC8yKTtyZXR1cm4gYy5mcm9tRXF1aXJlY3Rhbmd1bGFyVGV4dHVyZShlLGEpLHQuc2V0KGEsYyksYS5hZGRFdmVudExpc3RlbmVyKCJkaXNwb3NlIixpKSxyKGMudGV4dHVyZSxhLm1hcHBpbmcpfWVsc2UgcmV0dXJuIG51bGx9fXJldHVybiBhfWZ1bmN0aW9uIGkoYSl7bGV0IHM9YS50YXJnZXQ7cy5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixpKTtsZXQgbD10LmdldChzKTtsIT09dm9pZCAwJiYodC5kZWxldGUocyksbC5kaXNwb3NlKCkpfWZ1bmN0aW9uIG8oKXt0PW5ldyBXZWFrTWFwfXJldHVybntnZXQ6bixkaXNwb3NlOm99fXZhciBEdj1jbGFzcyBleHRlbmRzIFJ2e2NvbnN0cnVjdG9yKHQ9LTEscj0xLG49MSxpPS0xLG89LjEsYT0yZTMpe3N1cGVyKCksdGhpcy50eXBlPSJPcnRob2dyYXBoaWNDYW1lcmEiLHRoaXMuem9vbT0xLHRoaXMudmlldz1udWxsLHRoaXMubGVmdD10LHRoaXMucmlnaHQ9cix0aGlzLnRvcD1uLHRoaXMuYm90dG9tPWksdGhpcy5uZWFyPW8sdGhpcy5mYXI9YSx0aGlzLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX1jb3B5KHQscil7cmV0dXJuIHN1cGVyLmNvcHkodCxyKSx0aGlzLmxlZnQ9dC5sZWZ0LHRoaXMucmlnaHQ9dC5yaWdodCx0aGlzLnRvcD10LnRvcCx0aGlzLmJvdHRvbT10LmJvdHRvbSx0aGlzLm5lYXI9dC5uZWFyLHRoaXMuZmFyPXQuZmFyLHRoaXMuem9vbT10Lnpvb20sdGhpcy52aWV3PXQudmlldz09PW51bGw/bnVsbDpPYmplY3QuYXNzaWduKHt9LHQudmlldyksdGhpc31zZXRWaWV3T2Zmc2V0KHQscixuLGksbyxhKXt0aGlzLnZpZXc9PT1udWxsJiYodGhpcy52aWV3PXtlbmFibGVkOiEwLGZ1bGxXaWR0aDoxLGZ1bGxIZWlnaHQ6MSxvZmZzZXRYOjAsb2Zmc2V0WTowLHdpZHRoOjEsaGVpZ2h0OjF9KSx0aGlzLnZpZXcuZW5hYmxlZD0hMCx0aGlzLnZpZXcuZnVsbFdpZHRoPXQsdGhpcy52aWV3LmZ1bGxIZWlnaHQ9cix0aGlzLnZpZXcub2Zmc2V0WD1uLHRoaXMudmlldy5vZmZzZXRZPWksdGhpcy52aWV3LndpZHRoPW8sdGhpcy52aWV3LmhlaWdodD1hLHRoaXMudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpfWNsZWFyVmlld09mZnNldCgpe3RoaXMudmlldyE9PW51bGwmJih0aGlzLnZpZXcuZW5hYmxlZD0hMSksdGhpcy51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl9dXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpe2xldCB0PSh0aGlzLnJpZ2h0LXRoaXMubGVmdCkvKDIqdGhpcy56b29tKSxyPSh0aGlzLnRvcC10aGlzLmJvdHRvbSkvKDIqdGhpcy56b29tKSxuPSh0aGlzLnJpZ2h0K3RoaXMubGVmdCkvMixpPSh0aGlzLnRvcCt0aGlzLmJvdHRvbSkvMixvPW4tdCxhPW4rdCxzPWkrcixsPWktcjtpZih0aGlzLnZpZXchPT1udWxsJiZ0aGlzLnZpZXcuZW5hYmxlZCl7bGV0IGM9KHRoaXMucmlnaHQtdGhpcy5sZWZ0KS90aGlzLnZpZXcuZnVsbFdpZHRoL3RoaXMuem9vbSx1PSh0aGlzLnRvcC10aGlzLmJvdHRvbSkvdGhpcy52aWV3LmZ1bGxIZWlnaHQvdGhpcy56b29tO28rPWMqdGhpcy52aWV3Lm9mZnNldFgsYT1vK2MqdGhpcy52aWV3LndpZHRoLHMtPXUqdGhpcy52aWV3Lm9mZnNldFksbD1zLXUqdGhpcy52aWV3LmhlaWdodH10aGlzLnByb2plY3Rpb25NYXRyaXgubWFrZU9ydGhvZ3JhcGhpYyhvLGEscyxsLHRoaXMubmVhcix0aGlzLmZhciksdGhpcy5wcm9qZWN0aW9uTWF0cml4SW52ZXJzZS5jb3B5KHRoaXMucHJvamVjdGlvbk1hdHJpeCkuaW52ZXJ0KCl9dG9KU09OKHQpe2xldCByPXN1cGVyLnRvSlNPTih0KTtyZXR1cm4gci5vYmplY3Quem9vbT10aGlzLnpvb20sci5vYmplY3QubGVmdD10aGlzLmxlZnQsci5vYmplY3QucmlnaHQ9dGhpcy5yaWdodCxyLm9iamVjdC50b3A9dGhpcy50b3Asci5vYmplY3QuYm90dG9tPXRoaXMuYm90dG9tLHIub2JqZWN0Lm5lYXI9dGhpcy5uZWFyLHIub2JqZWN0LmZhcj10aGlzLmZhcix0aGlzLnZpZXchPT1udWxsJiYoci5vYmplY3Qudmlldz1PYmplY3QuYXNzaWduKHt9LHRoaXMudmlldykpLHJ9fTtEdi5wcm90b3R5cGUuaXNPcnRob2dyYXBoaWNDYW1lcmE9ITA7dmFyIFUwPWNsYXNzIGV4dGVuZHMgbGh7Y29uc3RydWN0b3IodCl7c3VwZXIodCksdGhpcy50eXBlPSJSYXdTaGFkZXJNYXRlcmlhbCJ9fTtVMC5wcm90b3R5cGUuaXNSYXdTaGFkZXJNYXRlcmlhbD0hMDt2YXIgcTM9NCxGMD04LCRmPU1hdGgucG93KDIsRjApLEZmZT1bLjEyNSwuMjE1LC4zNSwuNDQ2LC41MjYsLjU4Ml0sQmZlPUYwLXEzKzErRmZlLmxlbmd0aCxrMz0yMCxmdXQ9bmV3IER2LHtfbG9kUGxhbmVzOlJQLF9zaXplTG9kczpNdWUsX3NpZ21hczpBVn09JGRyKCksRXVlPW5ldyBuZSxwdXQ9bnVsbCxTdj0oMStNYXRoLnNxcnQoNSkpLzIsUjM9MS9TdixUdWU9W25ldyBqKDEsMSwxKSxuZXcgaigtMSwxLDEpLG5ldyBqKDEsMSwtMSksbmV3IGooLTEsMSwtMSksbmV3IGooMCxTdixSMyksbmV3IGooMCxTdiwtUjMpLG5ldyBqKFIzLDAsU3YpLG5ldyBqKC1SMywwLFN2KSxuZXcgaihTdixSMywwKSxuZXcgaigtU3YsUjMsMCldLHQ2PWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuX3JlbmRlcmVyPXQsdGhpcy5fcGluZ1BvbmdSZW5kZXJUYXJnZXQ9bnVsbCx0aGlzLl9ibHVyTWF0ZXJpYWw9S2RyKGszKSx0aGlzLl9lcXVpcmVjdFNoYWRlcj1udWxsLHRoaXMuX2N1YmVtYXBTaGFkZXI9bnVsbCx0aGlzLl9jb21waWxlTWF0ZXJpYWwodGhpcy5fYmx1ck1hdGVyaWFsKX1mcm9tU2NlbmUodCxyPTAsbj0uMSxpPTEwMCl7cHV0PXRoaXMuX3JlbmRlcmVyLmdldFJlbmRlclRhcmdldCgpO2xldCBvPXRoaXMuX2FsbG9jYXRlVGFyZ2V0cygpO3JldHVybiB0aGlzLl9zY2VuZVRvQ3ViZVVWKHQsbixpLG8pLHI+MCYmdGhpcy5fYmx1cihvLDAsMCxyKSx0aGlzLl9hcHBseVBNUkVNKG8pLHRoaXMuX2NsZWFudXAobyksb31mcm9tRXF1aXJlY3Rhbmd1bGFyKHQscj1udWxsKXtyZXR1cm4gdGhpcy5fZnJvbVRleHR1cmUodCxyKX1mcm9tQ3ViZW1hcCh0LHI9bnVsbCl7cmV0dXJuIHRoaXMuX2Zyb21UZXh0dXJlKHQscil9Y29tcGlsZUN1YmVtYXBTaGFkZXIoKXt0aGlzLl9jdWJlbWFwU2hhZGVyPT09bnVsbCYmKHRoaXMuX2N1YmVtYXBTaGFkZXI9UHVlKCksdGhpcy5fY29tcGlsZU1hdGVyaWFsKHRoaXMuX2N1YmVtYXBTaGFkZXIpKX1jb21waWxlRXF1aXJlY3Rhbmd1bGFyU2hhZGVyKCl7dGhpcy5fZXF1aXJlY3RTaGFkZXI9PT1udWxsJiYodGhpcy5fZXF1aXJlY3RTaGFkZXI9QXVlKCksdGhpcy5fY29tcGlsZU1hdGVyaWFsKHRoaXMuX2VxdWlyZWN0U2hhZGVyKSl9ZGlzcG9zZSgpe3RoaXMuX2JsdXJNYXRlcmlhbC5kaXNwb3NlKCksdGhpcy5fcGluZ1BvbmdSZW5kZXJUYXJnZXQhPT1udWxsJiZ0aGlzLl9waW5nUG9uZ1JlbmRlclRhcmdldC5kaXNwb3NlKCksdGhpcy5fY3ViZW1hcFNoYWRlciE9PW51bGwmJnRoaXMuX2N1YmVtYXBTaGFkZXIuZGlzcG9zZSgpLHRoaXMuX2VxdWlyZWN0U2hhZGVyIT09bnVsbCYmdGhpcy5fZXF1aXJlY3RTaGFkZXIuZGlzcG9zZSgpO2ZvcihsZXQgdD0wO3Q8UlAubGVuZ3RoO3QrKylSUFt0XS5kaXNwb3NlKCl9X2NsZWFudXAodCl7dGhpcy5fcmVuZGVyZXIuc2V0UmVuZGVyVGFyZ2V0KHB1dCksdC5zY2lzc29yVGVzdD0hMSxQVih0LDAsMCx0LndpZHRoLHQuaGVpZ2h0KX1fZnJvbVRleHR1cmUodCxyKXtwdXQ9dGhpcy5fcmVuZGVyZXIuZ2V0UmVuZGVyVGFyZ2V0KCk7bGV0IG49cnx8dGhpcy5fYWxsb2NhdGVUYXJnZXRzKHQpO3JldHVybiB0aGlzLl90ZXh0dXJlVG9DdWJlVVYodCxuKSx0aGlzLl9hcHBseVBNUkVNKG4pLHRoaXMuX2NsZWFudXAobiksbn1fYWxsb2NhdGVUYXJnZXRzKHQpe2xldCByPXttYWdGaWx0ZXI6b2ksbWluRmlsdGVyOm9pLGdlbmVyYXRlTWlwbWFwczohMSx0eXBlOkN2LGZvcm1hdDpRbyxlbmNvZGluZzpRZCxkZXB0aEJ1ZmZlcjohMX0sbj1DdWUocik7cmV0dXJuIG4uZGVwdGhCdWZmZXI9IXQsdGhpcy5fcGluZ1BvbmdSZW5kZXJUYXJnZXQ9PT1udWxsJiYodGhpcy5fcGluZ1BvbmdSZW5kZXJUYXJnZXQ9Q3VlKHIpKSxufV9jb21waWxlTWF0ZXJpYWwodCl7bGV0IHI9bmV3IGVpKFJQWzBdLHQpO3RoaXMuX3JlbmRlcmVyLmNvbXBpbGUocixmdXQpfV9zY2VuZVRvQ3ViZVVWKHQscixuLGkpe2xldCBzPW5ldyBVaSg5MCwxLHIsbiksbD1bMSwtMSwxLDEsMSwxXSxjPVsxLDEsMSwtMSwtMSwtMV0sdT10aGlzLl9yZW5kZXJlcixoPXUuYXV0b0NsZWFyLGY9dS50b25lTWFwcGluZzt1LmdldENsZWFyQ29sb3IoRXVlKSx1LnRvbmVNYXBwaW5nPUtkLHUuYXV0b0NsZWFyPSExO2xldCBwPW5ldyBzaCh7bmFtZToiUE1SRU0uQmFja2dyb3VuZCIsc2lkZTpJaSxkZXB0aFdyaXRlOiExLGRlcHRoVGVzdDohMX0pLGQ9bmV3IGVpKG5ldyBRZixwKSxnPSExLF89dC5iYWNrZ3JvdW5kO18/Xy5pc0NvbG9yJiYocC5jb2xvci5jb3B5KF8pLHQuYmFja2dyb3VuZD1udWxsLGc9ITApOihwLmNvbG9yLmNvcHkoRXVlKSxnPSEwKTtmb3IobGV0IHk9MDt5PDY7eSsrKXtsZXQgeD15JTM7eD09PTA/KHMudXAuc2V0KDAsbFt5XSwwKSxzLmxvb2tBdChjW3ldLDAsMCkpOng9PT0xPyhzLnVwLnNldCgwLDAsbFt5XSkscy5sb29rQXQoMCxjW3ldLDApKToocy51cC5zZXQoMCxsW3ldLDApLHMubG9va0F0KDAsMCxjW3ldKSksUFYoaSx4KiRmLHk+Mj8kZjowLCRmLCRmKSx1LnNldFJlbmRlclRhcmdldChpKSxnJiZ1LnJlbmRlcihkLHMpLHUucmVuZGVyKHQscyl9ZC5nZW9tZXRyeS5kaXNwb3NlKCksZC5tYXRlcmlhbC5kaXNwb3NlKCksdS50b25lTWFwcGluZz1mLHUuYXV0b0NsZWFyPWgsdC5iYWNrZ3JvdW5kPV99X3RleHR1cmVUb0N1YmVVVih0LHIpe2xldCBuPXRoaXMuX3JlbmRlcmVyLGk9dC5tYXBwaW5nPT09bnh8fHQubWFwcGluZz09PWl4O2k/KHRoaXMuX2N1YmVtYXBTaGFkZXI9PT1udWxsJiYodGhpcy5fY3ViZW1hcFNoYWRlcj1QdWUoKSksdGhpcy5fY3ViZW1hcFNoYWRlci51bmlmb3Jtcy5mbGlwRW52TWFwLnZhbHVlPXQuaXNSZW5kZXJUYXJnZXRUZXh0dXJlPT09ITE/LTE6MSk6dGhpcy5fZXF1aXJlY3RTaGFkZXI9PT1udWxsJiYodGhpcy5fZXF1aXJlY3RTaGFkZXI9QXVlKCkpO2xldCBvPWk/dGhpcy5fY3ViZW1hcFNoYWRlcjp0aGlzLl9lcXVpcmVjdFNoYWRlcixhPW5ldyBlaShSUFswXSxvKSxzPW8udW5pZm9ybXM7cy5lbnZNYXAudmFsdWU9dCxpfHxzLnRleGVsU2l6ZS52YWx1ZS5zZXQoMS90LmltYWdlLndpZHRoLDEvdC5pbWFnZS5oZWlnaHQpLFBWKHIsMCwwLDMqJGYsMiokZiksbi5zZXRSZW5kZXJUYXJnZXQociksbi5yZW5kZXIoYSxmdXQpfV9hcHBseVBNUkVNKHQpe2xldCByPXRoaXMuX3JlbmRlcmVyLG49ci5hdXRvQ2xlYXI7ci5hdXRvQ2xlYXI9ITE7Zm9yKGxldCBpPTE7aTxCZmU7aSsrKXtsZXQgbz1NYXRoLnNxcnQoQVZbaV0qQVZbaV0tQVZbaS0xXSpBVltpLTFdKSxhPVR1ZVsoaS0xKSVUdWUubGVuZ3RoXTt0aGlzLl9ibHVyKHQsaS0xLGksbyxhKX1yLmF1dG9DbGVhcj1ufV9ibHVyKHQscixuLGksbyl7bGV0IGE9dGhpcy5fcGluZ1BvbmdSZW5kZXJUYXJnZXQ7dGhpcy5faGFsZkJsdXIodCxhLHIsbixpLCJsYXRpdHVkaW5hbCIsbyksdGhpcy5faGFsZkJsdXIoYSx0LG4sbixpLCJsb25naXR1ZGluYWwiLG8pfV9oYWxmQmx1cih0LHIsbixpLG8sYSxzKXtsZXQgbD10aGlzLl9yZW5kZXJlcixjPXRoaXMuX2JsdXJNYXRlcmlhbDthIT09ImxhdGl0dWRpbmFsIiYmYSE9PSJsb25naXR1ZGluYWwiJiZjb25zb2xlLmVycm9yKCJibHVyIGRpcmVjdGlvbiBtdXN0IGJlIGVpdGhlciBsYXRpdHVkaW5hbCBvciBsb25naXR1ZGluYWwhIik7bGV0IHU9MyxoPW5ldyBlaShSUFtpXSxjKSxmPWMudW5pZm9ybXMscD1NdWVbbl0tMSxkPWlzRmluaXRlKG8pP01hdGguUEkvKDIqcCk6MipNYXRoLlBJLygyKmszLTEpLGc9by9kLF89aXNGaW5pdGUobyk/MStNYXRoLmZsb29yKHUqZyk6azM7Xz5rMyYmY29uc29sZS53YXJuKGBzaWdtYVJhZGlhbnMsICR7b30sIGlzIHRvbyBsYXJnZSBhbmQgd2lsbCBjbGlwLCBhcyBpdCByZXF1ZXN0ZWQgJHtffSBzYW1wbGVzIHdoZW4gdGhlIG1heGltdW0gaXMgc2V0IHRvICR7azN9YCk7bGV0IHk9W10seD0wO2ZvcihsZXQgUD0wO1A8azM7KytQKXtsZXQgaz1QL2csTz1NYXRoLmV4cCgtayprLzIpO3kucHVzaChPKSxQPT09MD94Kz1POlA8XyYmKHgrPTIqTyl9Zm9yKGxldCBQPTA7UDx5Lmxlbmd0aDtQKyspeVtQXT15W1BdL3g7Zi5lbnZNYXAudmFsdWU9dC50ZXh0dXJlLGYuc2FtcGxlcy52YWx1ZT1fLGYud2VpZ2h0cy52YWx1ZT15LGYubGF0aXR1ZGluYWwudmFsdWU9YT09PSJsYXRpdHVkaW5hbCIscyYmKGYucG9sZUF4aXMudmFsdWU9cyksZi5kVGhldGEudmFsdWU9ZCxmLm1pcEludC52YWx1ZT1GMC1uO2xldCBiPU11ZVtpXSxTPTMqTWF0aC5tYXgoMCwkZi0yKmIpLEM9KGk9PT0wPzA6MiokZikrMipiKihpPkYwLXEzP2ktRjArcTM6MCk7UFYocixTLEMsMypiLDIqYiksbC5zZXRSZW5kZXJUYXJnZXQociksbC5yZW5kZXIoaCxmdXQpfX07ZnVuY3Rpb24gJGRyKCl7bGV0IGU9W10sdD1bXSxyPVtdLG49RjA7Zm9yKGxldCBpPTA7aTxCZmU7aSsrKXtsZXQgbz1NYXRoLnBvdygyLG4pO3QucHVzaChvKTtsZXQgYT0xL287aT5GMC1xMz9hPUZmZVtpLUYwK3EzLTFdOmk9PT0wJiYoYT0wKSxyLnB1c2goYSk7bGV0IHM9MS8oby0xKSxsPS1zLzIsYz0xK3MvMix1PVtsLGwsYyxsLGMsYyxsLGwsYyxjLGwsY10saD02LGY9NixwPTMsZD0yLGc9MSxfPW5ldyBGbG9hdDMyQXJyYXkocCpmKmgpLHk9bmV3IEZsb2F0MzJBcnJheShkKmYqaCkseD1uZXcgRmxvYXQzMkFycmF5KGcqZipoKTtmb3IobGV0IFM9MDtTPGg7UysrKXtsZXQgQz1TJTMqMi8zLTEsUD1TPjI/MDotMSxrPVtDLFAsMCxDKzIvMyxQLDAsQysyLzMsUCsxLDAsQyxQLDAsQysyLzMsUCsxLDAsQyxQKzEsMF07Xy5zZXQoayxwKmYqUykseS5zZXQodSxkKmYqUyk7bGV0IE89W1MsUyxTLFMsUyxTXTt4LnNldChPLGcqZipTKX1sZXQgYj1uZXcgUGU7Yi5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgSmUoXyxwKSksYi5zZXRBdHRyaWJ1dGUoInV2IixuZXcgSmUoeSxkKSksYi5zZXRBdHRyaWJ1dGUoImZhY2VJbmRleCIsbmV3IEplKHgsZykpLGUucHVzaChiKSxuPnEzJiZuLS19cmV0dXJue19sb2RQbGFuZXM6ZSxfc2l6ZUxvZHM6dCxfc2lnbWFzOnJ9fWZ1bmN0aW9uIEN1ZShlKXtsZXQgdD1uZXcgdXMoMyokZiwzKiRmLGUpO3JldHVybiB0LnRleHR1cmUubWFwcGluZz14TSx0LnRleHR1cmUubmFtZT0iUE1SRU0uY3ViZVV2Iix0LnNjaXNzb3JUZXN0PSEwLHR9ZnVuY3Rpb24gUFYoZSx0LHIsbixpKXtlLnZpZXdwb3J0LnNldCh0LHIsbixpKSxlLnNjaXNzb3Iuc2V0KHQscixuLGkpfWZ1bmN0aW9uIEtkcihlKXtsZXQgdD1uZXcgRmxvYXQzMkFycmF5KGUpLHI9bmV3IGooMCwxLDApO3JldHVybiBuZXcgVTAoe25hbWU6IlNwaGVyaWNhbEdhdXNzaWFuQmx1ciIsZGVmaW5lczp7bjplfSx1bmlmb3Jtczp7ZW52TWFwOnt2YWx1ZTpudWxsfSxzYW1wbGVzOnt2YWx1ZToxfSx3ZWlnaHRzOnt2YWx1ZTp0fSxsYXRpdHVkaW5hbDp7dmFsdWU6ITF9LGRUaGV0YTp7dmFsdWU6MH0sbWlwSW50Ont2YWx1ZTowfSxwb2xlQXhpczp7dmFsdWU6cn19LHZlcnRleFNoYWRlcjpEaHQoKSxmcmFnbWVudFNoYWRlcjpgCgoJCQlwcmVjaXNpb24gbWVkaXVtcCBmbG9hdDsKCQkJcHJlY2lzaW9uIG1lZGl1bXAgaW50OwoKCQkJdmFyeWluZyB2ZWMzIHZPdXRwdXREaXJlY3Rpb247CgoJCQl1bmlmb3JtIHNhbXBsZXIyRCBlbnZNYXA7CgkJCXVuaWZvcm0gaW50IHNhbXBsZXM7CgkJCXVuaWZvcm0gZmxvYXQgd2VpZ2h0c1sgbiBdOwoJCQl1bmlmb3JtIGJvb2wgbGF0aXR1ZGluYWw7CgkJCXVuaWZvcm0gZmxvYXQgZFRoZXRhOwoJCQl1bmlmb3JtIGZsb2F0IG1pcEludDsKCQkJdW5pZm9ybSB2ZWMzIHBvbGVBeGlzOwoKCQkJI2RlZmluZSBFTlZNQVBfVFlQRV9DVUJFX1VWCgkJCSNpbmNsdWRlIDxjdWJlX3V2X3JlZmxlY3Rpb25fZnJhZ21lbnQ+CgoJCQl2ZWMzIGdldFNhbXBsZSggZmxvYXQgdGhldGEsIHZlYzMgYXhpcyApIHsKCgkJCQlmbG9hdCBjb3NUaGV0YSA9IGNvcyggdGhldGEgKTsKCQkJCS8vIFJvZHJpZ3VlcycgYXhpcy1hbmdsZSByb3RhdGlvbgoJCQkJdmVjMyBzYW1wbGVEaXJlY3Rpb24gPSB2T3V0cHV0RGlyZWN0aW9uICogY29zVGhldGEKCQkJCQkrIGNyb3NzKCBheGlzLCB2T3V0cHV0RGlyZWN0aW9uICkgKiBzaW4oIHRoZXRhICkKCQkJCQkrIGF4aXMgKiBkb3QoIGF4aXMsIHZPdXRwdXREaXJlY3Rpb24gKSAqICggMS4wIC0gY29zVGhldGEgKTsKCgkJCQlyZXR1cm4gYmlsaW5lYXJDdWJlVVYoIGVudk1hcCwgc2FtcGxlRGlyZWN0aW9uLCBtaXBJbnQgKTsKCgkJCX0KCgkJCXZvaWQgbWFpbigpIHsKCgkJCQl2ZWMzIGF4aXMgPSBsYXRpdHVkaW5hbCA/IHBvbGVBeGlzIDogY3Jvc3MoIHBvbGVBeGlzLCB2T3V0cHV0RGlyZWN0aW9uICk7CgoJCQkJaWYgKCBhbGwoIGVxdWFsKCBheGlzLCB2ZWMzKCAwLjAgKSApICkgKSB7CgoJCQkJCWF4aXMgPSB2ZWMzKCB2T3V0cHV0RGlyZWN0aW9uLnosIDAuMCwgLSB2T3V0cHV0RGlyZWN0aW9uLnggKTsKCgkJCQl9CgoJCQkJYXhpcyA9IG5vcm1hbGl6ZSggYXhpcyApOwoKCQkJCWdsX0ZyYWdDb2xvciA9IHZlYzQoIDAuMCwgMC4wLCAwLjAsIDEuMCApOwoJCQkJZ2xfRnJhZ0NvbG9yLnJnYiArPSB3ZWlnaHRzWyAwIF0gKiBnZXRTYW1wbGUoIDAuMCwgYXhpcyApOwoKCQkJCWZvciAoIGludCBpID0gMTsgaSA8IG47IGkrKyApIHsKCgkJCQkJaWYgKCBpID49IHNhbXBsZXMgKSB7CgoJCQkJCQlicmVhazsKCgkJCQkJfQoKCQkJCQlmbG9hdCB0aGV0YSA9IGRUaGV0YSAqIGZsb2F0KCBpICk7CgkJCQkJZ2xfRnJhZ0NvbG9yLnJnYiArPSB3ZWlnaHRzWyBpIF0gKiBnZXRTYW1wbGUoIC0xLjAgKiB0aGV0YSwgYXhpcyApOwoJCQkJCWdsX0ZyYWdDb2xvci5yZ2IgKz0gd2VpZ2h0c1sgaSBdICogZ2V0U2FtcGxlKCB0aGV0YSwgYXhpcyApOwoKCQkJCX0KCgkJCX0KCQlgLGJsZW5kaW5nOiRkLGRlcHRoVGVzdDohMSxkZXB0aFdyaXRlOiExfSl9ZnVuY3Rpb24gQXVlKCl7bGV0IGU9bmV3IEx0KDEsMSk7cmV0dXJuIG5ldyBVMCh7bmFtZToiRXF1aXJlY3Rhbmd1bGFyVG9DdWJlVVYiLHVuaWZvcm1zOntlbnZNYXA6e3ZhbHVlOm51bGx9LHRleGVsU2l6ZTp7dmFsdWU6ZX19LHZlcnRleFNoYWRlcjpEaHQoKSxmcmFnbWVudFNoYWRlcjpgCgoJCQlwcmVjaXNpb24gbWVkaXVtcCBmbG9hdDsKCQkJcHJlY2lzaW9uIG1lZGl1bXAgaW50OwoKCQkJdmFyeWluZyB2ZWMzIHZPdXRwdXREaXJlY3Rpb247CgoJCQl1bmlmb3JtIHNhbXBsZXIyRCBlbnZNYXA7CgkJCXVuaWZvcm0gdmVjMiB0ZXhlbFNpemU7CgoJCQkjaW5jbHVkZSA8Y29tbW9uPgoKCQkJdm9pZCBtYWluKCkgewoKCQkJCWdsX0ZyYWdDb2xvciA9IHZlYzQoIDAuMCwgMC4wLCAwLjAsIDEuMCApOwoKCQkJCXZlYzMgb3V0cHV0RGlyZWN0aW9uID0gbm9ybWFsaXplKCB2T3V0cHV0RGlyZWN0aW9uICk7CgkJCQl2ZWMyIHV2ID0gZXF1aXJlY3RVdiggb3V0cHV0RGlyZWN0aW9uICk7CgoJCQkJdmVjMiBmID0gZnJhY3QoIHV2IC8gdGV4ZWxTaXplIC0gMC41ICk7CgkJCQl1diAtPSBmICogdGV4ZWxTaXplOwoJCQkJdmVjMyB0bCA9IHRleHR1cmUyRCAoIGVudk1hcCwgdXYgKS5yZ2I7CgkJCQl1di54ICs9IHRleGVsU2l6ZS54OwoJCQkJdmVjMyB0ciA9IHRleHR1cmUyRCAoIGVudk1hcCwgdXYgKS5yZ2I7CgkJCQl1di55ICs9IHRleGVsU2l6ZS55OwoJCQkJdmVjMyBiciA9IHRleHR1cmUyRCAoIGVudk1hcCwgdXYgKS5yZ2I7CgkJCQl1di54IC09IHRleGVsU2l6ZS54OwoJCQkJdmVjMyBibCA9IHRleHR1cmUyRCAoIGVudk1hcCwgdXYgKS5yZ2I7CgoJCQkJdmVjMyB0bSA9IG1peCggdGwsIHRyLCBmLnggKTsKCQkJCXZlYzMgYm0gPSBtaXgoIGJsLCBiciwgZi54ICk7CgkJCQlnbF9GcmFnQ29sb3IucmdiID0gbWl4KCB0bSwgYm0sIGYueSApOwoKCQkJfQoJCWAsYmxlbmRpbmc6JGQsZGVwdGhUZXN0OiExLGRlcHRoV3JpdGU6ITF9KX1mdW5jdGlvbiBQdWUoKXtyZXR1cm4gbmV3IFUwKHtuYW1lOiJDdWJlbWFwVG9DdWJlVVYiLHVuaWZvcm1zOntlbnZNYXA6e3ZhbHVlOm51bGx9LGZsaXBFbnZNYXA6e3ZhbHVlOi0xfX0sdmVydGV4U2hhZGVyOkRodCgpLGZyYWdtZW50U2hhZGVyOmAKCgkJCXByZWNpc2lvbiBtZWRpdW1wIGZsb2F0OwoJCQlwcmVjaXNpb24gbWVkaXVtcCBpbnQ7CgoJCQl1bmlmb3JtIGZsb2F0IGZsaXBFbnZNYXA7CgoJCQl2YXJ5aW5nIHZlYzMgdk91dHB1dERpcmVjdGlvbjsKCgkJCXVuaWZvcm0gc2FtcGxlckN1YmUgZW52TWFwOwoKCQkJdm9pZCBtYWluKCkgewoKCQkJCWdsX0ZyYWdDb2xvciA9IHRleHR1cmVDdWJlKCBlbnZNYXAsIHZlYzMoIGZsaXBFbnZNYXAgKiB2T3V0cHV0RGlyZWN0aW9uLngsIHZPdXRwdXREaXJlY3Rpb24ueXogKSApOwoKCQkJfQoJCWAsYmxlbmRpbmc6JGQsZGVwdGhUZXN0OiExLGRlcHRoV3JpdGU6ITF9KX1mdW5jdGlvbiBEaHQoKXtyZXR1cm5gCgoJCXByZWNpc2lvbiBtZWRpdW1wIGZsb2F0OwoJCXByZWNpc2lvbiBtZWRpdW1wIGludDsKCgkJYXR0cmlidXRlIHZlYzMgcG9zaXRpb247CgkJYXR0cmlidXRlIHZlYzIgdXY7CgkJYXR0cmlidXRlIGZsb2F0IGZhY2VJbmRleDsKCgkJdmFyeWluZyB2ZWMzIHZPdXRwdXREaXJlY3Rpb247CgoJCS8vIFJIIGNvb3JkaW5hdGUgc3lzdGVtOyBQTVJFTSBmYWNlLWluZGV4aW5nIGNvbnZlbnRpb24KCQl2ZWMzIGdldERpcmVjdGlvbiggdmVjMiB1diwgZmxvYXQgZmFjZSApIHsKCgkJCXV2ID0gMi4wICogdXYgLSAxLjA7CgoJCQl2ZWMzIGRpcmVjdGlvbiA9IHZlYzMoIHV2LCAxLjAgKTsKCgkJCWlmICggZmFjZSA9PSAwLjAgKSB7CgoJCQkJZGlyZWN0aW9uID0gZGlyZWN0aW9uLnp5eDsgLy8gKCAxLCB2LCB1ICkgcG9zIHgKCgkJCX0gZWxzZSBpZiAoIGZhY2UgPT0gMS4wICkgewoKCQkJCWRpcmVjdGlvbiA9IGRpcmVjdGlvbi54enk7CgkJCQlkaXJlY3Rpb24ueHogKj0gLTEuMDsgLy8gKCAtdSwgMSwgLXYgKSBwb3MgeQoKCQkJfSBlbHNlIGlmICggZmFjZSA9PSAyLjAgKSB7CgoJCQkJZGlyZWN0aW9uLnggKj0gLTEuMDsgLy8gKCAtdSwgdiwgMSApIHBvcyB6CgoJCQl9IGVsc2UgaWYgKCBmYWNlID09IDMuMCApIHsKCgkJCQlkaXJlY3Rpb24gPSBkaXJlY3Rpb24uenl4OwoJCQkJZGlyZWN0aW9uLnh6ICo9IC0xLjA7IC8vICggLTEsIHYsIC11ICkgbmVnIHgKCgkJCX0gZWxzZSBpZiAoIGZhY2UgPT0gNC4wICkgewoKCQkJCWRpcmVjdGlvbiA9IGRpcmVjdGlvbi54enk7CgkJCQlkaXJlY3Rpb24ueHkgKj0gLTEuMDsgLy8gKCAtdSwgLTEsIHYgKSBuZWcgeQoKCQkJfSBlbHNlIGlmICggZmFjZSA9PSA1LjAgKSB7CgoJCQkJZGlyZWN0aW9uLnogKj0gLTEuMDsgLy8gKCB1LCB2LCAtMSApIG5lZyB6CgoJCQl9CgoJCQlyZXR1cm4gZGlyZWN0aW9uOwoKCQl9CgoJCXZvaWQgbWFpbigpIHsKCgkJCXZPdXRwdXREaXJlY3Rpb24gPSBnZXREaXJlY3Rpb24oIHV2LCBmYWNlSW5kZXggKTsKCQkJZ2xfUG9zaXRpb24gPSB2ZWM0KCBwb3NpdGlvbiwgMS4wICk7CgoJCX0KCWB9ZnVuY3Rpb24gWmRyKGUpe2xldCB0PW5ldyBXZWFrTWFwLHI9bnVsbDtmdW5jdGlvbiBuKHMpe2lmKHMmJnMuaXNUZXh0dXJlKXtsZXQgbD1zLm1hcHBpbmcsYz1sPT09V1B8fGw9PT1ZUCx1PWw9PT1ueHx8bD09PWl4O2lmKGN8fHUpaWYocy5pc1JlbmRlclRhcmdldFRleHR1cmUmJnMubmVlZHNQTVJFTVVwZGF0ZT09PSEwKXtzLm5lZWRzUE1SRU1VcGRhdGU9ITE7bGV0IGg9dC5nZXQocyk7cmV0dXJuIHI9PT1udWxsJiYocj1uZXcgdDYoZSkpLGg9Yz9yLmZyb21FcXVpcmVjdGFuZ3VsYXIocyxoKTpyLmZyb21DdWJlbWFwKHMsaCksdC5zZXQocyxoKSxoLnRleHR1cmV9ZWxzZXtpZih0LmhhcyhzKSlyZXR1cm4gdC5nZXQocykudGV4dHVyZTt7bGV0IGg9cy5pbWFnZTtpZihjJiZoJiZoLmhlaWdodD4wfHx1JiZoJiZpKGgpKXtyPT09bnVsbCYmKHI9bmV3IHQ2KGUpKTtsZXQgZj1jP3IuZnJvbUVxdWlyZWN0YW5ndWxhcihzKTpyLmZyb21DdWJlbWFwKHMpO3JldHVybiB0LnNldChzLGYpLHMuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsbyksZi50ZXh0dXJlfWVsc2UgcmV0dXJuIG51bGx9fX1yZXR1cm4gc31mdW5jdGlvbiBpKHMpe2xldCBsPTAsYz02O2ZvcihsZXQgdT0wO3U8Yzt1Kyspc1t1XSE9PXZvaWQgMCYmbCsrO3JldHVybiBsPT09Y31mdW5jdGlvbiBvKHMpe2xldCBsPXMudGFyZ2V0O2wucmVtb3ZlRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsbyk7bGV0IGM9dC5nZXQobCk7YyE9PXZvaWQgMCYmKHQuZGVsZXRlKGwpLGMuZGlzcG9zZSgpKX1mdW5jdGlvbiBhKCl7dD1uZXcgV2Vha01hcCxyIT09bnVsbCYmKHIuZGlzcG9zZSgpLHI9bnVsbCl9cmV0dXJue2dldDpuLGRpc3Bvc2U6YX19ZnVuY3Rpb24gSmRyKGUpe2xldCB0PXt9O2Z1bmN0aW9uIHIobil7aWYodFtuXSE9PXZvaWQgMClyZXR1cm4gdFtuXTtsZXQgaTtzd2l0Y2gobil7Y2FzZSJXRUJHTF9kZXB0aF90ZXh0dXJlIjppPWUuZ2V0RXh0ZW5zaW9uKCJXRUJHTF9kZXB0aF90ZXh0dXJlIil8fGUuZ2V0RXh0ZW5zaW9uKCJNT1pfV0VCR0xfZGVwdGhfdGV4dHVyZSIpfHxlLmdldEV4dGVuc2lvbigiV0VCS0lUX1dFQkdMX2RlcHRoX3RleHR1cmUiKTticmVhaztjYXNlIkVYVF90ZXh0dXJlX2ZpbHRlcl9hbmlzb3Ryb3BpYyI6aT1lLmdldEV4dGVuc2lvbigiRVhUX3RleHR1cmVfZmlsdGVyX2FuaXNvdHJvcGljIil8fGUuZ2V0RXh0ZW5zaW9uKCJNT1pfRVhUX3RleHR1cmVfZmlsdGVyX2FuaXNvdHJvcGljIil8fGUuZ2V0RXh0ZW5zaW9uKCJXRUJLSVRfRVhUX3RleHR1cmVfZmlsdGVyX2FuaXNvdHJvcGljIik7YnJlYWs7Y2FzZSJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfczN0YyI6aT1lLmdldEV4dGVuc2lvbigiV0VCR0xfY29tcHJlc3NlZF90ZXh0dXJlX3MzdGMiKXx8ZS5nZXRFeHRlbnNpb24oIk1PWl9XRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfczN0YyIpfHxlLmdldEV4dGVuc2lvbigiV0VCS0lUX1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjIik7YnJlYWs7Y2FzZSJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfcHZydGMiOmk9ZS5nZXRFeHRlbnNpb24oIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9wdnJ0YyIpfHxlLmdldEV4dGVuc2lvbigiV0VCS0lUX1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9wdnJ0YyIpO2JyZWFrO2RlZmF1bHQ6aT1lLmdldEV4dGVuc2lvbihuKX1yZXR1cm4gdFtuXT1pLGl9cmV0dXJue2hhczpmdW5jdGlvbihuKXtyZXR1cm4gcihuKSE9PW51bGx9LGluaXQ6ZnVuY3Rpb24obil7bi5pc1dlYkdMMj9yKCJFWFRfY29sb3JfYnVmZmVyX2Zsb2F0Iik6KHIoIldFQkdMX2RlcHRoX3RleHR1cmUiKSxyKCJPRVNfdGV4dHVyZV9mbG9hdCIpLHIoIk9FU190ZXh0dXJlX2hhbGZfZmxvYXQiKSxyKCJPRVNfdGV4dHVyZV9oYWxmX2Zsb2F0X2xpbmVhciIpLHIoIk9FU19zdGFuZGFyZF9kZXJpdmF0aXZlcyIpLHIoIk9FU19lbGVtZW50X2luZGV4X3VpbnQiKSxyKCJPRVNfdmVydGV4X2FycmF5X29iamVjdCIpLHIoIkFOR0xFX2luc3RhbmNlZF9hcnJheXMiKSkscigiT0VTX3RleHR1cmVfZmxvYXRfbGluZWFyIikscigiRVhUX2NvbG9yX2J1ZmZlcl9oYWxmX2Zsb2F0IikscigiV0VCR0xfbXVsdGlzYW1wbGVkX3JlbmRlcl90b190ZXh0dXJlIil9LGdldDpmdW5jdGlvbihuKXtsZXQgaT1yKG4pO3JldHVybiBpPT09bnVsbCYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAiK24rIiBleHRlbnNpb24gbm90IHN1cHBvcnRlZC4iKSxpfX19ZnVuY3Rpb24gUWRyKGUsdCxyLG4pe2xldCBpPXt9LG89bmV3IFdlYWtNYXA7ZnVuY3Rpb24gYShoKXtsZXQgZj1oLnRhcmdldDtmLmluZGV4IT09bnVsbCYmdC5yZW1vdmUoZi5pbmRleCk7Zm9yKGxldCBkIGluIGYuYXR0cmlidXRlcyl0LnJlbW92ZShmLmF0dHJpYnV0ZXNbZF0pO2YucmVtb3ZlRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsYSksZGVsZXRlIGlbZi5pZF07bGV0IHA9by5nZXQoZik7cCYmKHQucmVtb3ZlKHApLG8uZGVsZXRlKGYpKSxuLnJlbGVhc2VTdGF0ZXNPZkdlb21ldHJ5KGYpLGYuaXNJbnN0YW5jZWRCdWZmZXJHZW9tZXRyeT09PSEwJiZkZWxldGUgZi5fbWF4SW5zdGFuY2VDb3VudCxyLm1lbW9yeS5nZW9tZXRyaWVzLS19ZnVuY3Rpb24gcyhoLGYpe3JldHVybiBpW2YuaWRdPT09ITB8fChmLmFkZEV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLGEpLGlbZi5pZF09ITAsci5tZW1vcnkuZ2VvbWV0cmllcysrKSxmfWZ1bmN0aW9uIGwoaCl7bGV0IGY9aC5hdHRyaWJ1dGVzO2ZvcihsZXQgZCBpbiBmKXQudXBkYXRlKGZbZF0sMzQ5NjIpO2xldCBwPWgubW9ycGhBdHRyaWJ1dGVzO2ZvcihsZXQgZCBpbiBwKXtsZXQgZz1wW2RdO2ZvcihsZXQgXz0wLHk9Zy5sZW5ndGg7Xzx5O18rKyl0LnVwZGF0ZShnW19dLDM0OTYyKX19ZnVuY3Rpb24gYyhoKXtsZXQgZj1bXSxwPWguaW5kZXgsZD1oLmF0dHJpYnV0ZXMucG9zaXRpb24sZz0wO2lmKHAhPT1udWxsKXtsZXQgeD1wLmFycmF5O2c9cC52ZXJzaW9uO2ZvcihsZXQgYj0wLFM9eC5sZW5ndGg7YjxTO2IrPTMpe2xldCBDPXhbYiswXSxQPXhbYisxXSxrPXhbYisyXTtmLnB1c2goQyxQLFAsayxrLEMpfX1lbHNle2xldCB4PWQuYXJyYXk7Zz1kLnZlcnNpb247Zm9yKGxldCBiPTAsUz14Lmxlbmd0aC8zLTE7YjxTO2IrPTMpe2xldCBDPWIrMCxQPWIrMSxrPWIrMjtmLnB1c2goQyxQLFAsayxrLEMpfX1sZXQgXz1uZXcoTmZlKGYpP0szOiQzKShmLDEpO18udmVyc2lvbj1nO2xldCB5PW8uZ2V0KGgpO3kmJnQucmVtb3ZlKHkpLG8uc2V0KGgsXyl9ZnVuY3Rpb24gdShoKXtsZXQgZj1vLmdldChoKTtpZihmKXtsZXQgcD1oLmluZGV4O3AhPT1udWxsJiZmLnZlcnNpb248cC52ZXJzaW9uJiZjKGgpfWVsc2UgYyhoKTtyZXR1cm4gby5nZXQoaCl9cmV0dXJue2dldDpzLHVwZGF0ZTpsLGdldFdpcmVmcmFtZUF0dHJpYnV0ZTp1fX1mdW5jdGlvbiB0bXIoZSx0LHIsbil7bGV0IGk9bi5pc1dlYkdMMixvO2Z1bmN0aW9uIGEoZil7bz1mfWxldCBzLGw7ZnVuY3Rpb24gYyhmKXtzPWYudHlwZSxsPWYuYnl0ZXNQZXJFbGVtZW50fWZ1bmN0aW9uIHUoZixwKXtlLmRyYXdFbGVtZW50cyhvLHAscyxmKmwpLHIudXBkYXRlKHAsbywxKX1mdW5jdGlvbiBoKGYscCxkKXtpZihkPT09MClyZXR1cm47bGV0IGcsXztpZihpKWc9ZSxfPSJkcmF3RWxlbWVudHNJbnN0YW5jZWQiO2Vsc2UgaWYoZz10LmdldCgiQU5HTEVfaW5zdGFuY2VkX2FycmF5cyIpLF89ImRyYXdFbGVtZW50c0luc3RhbmNlZEFOR0xFIixnPT09bnVsbCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xJbmRleGVkQnVmZmVyUmVuZGVyZXI6IHVzaW5nIFRIUkVFLkluc3RhbmNlZEJ1ZmZlckdlb21ldHJ5IGJ1dCBoYXJkd2FyZSBkb2VzIG5vdCBzdXBwb3J0IGV4dGVuc2lvbiBBTkdMRV9pbnN0YW5jZWRfYXJyYXlzLiIpO3JldHVybn1nW19dKG8scCxzLGYqbCxkKSxyLnVwZGF0ZShwLG8sZCl9dGhpcy5zZXRNb2RlPWEsdGhpcy5zZXRJbmRleD1jLHRoaXMucmVuZGVyPXUsdGhpcy5yZW5kZXJJbnN0YW5jZXM9aH1mdW5jdGlvbiBlbXIoZSl7bGV0IHQ9e2dlb21ldHJpZXM6MCx0ZXh0dXJlczowfSxyPXtmcmFtZTowLGNhbGxzOjAsdHJpYW5nbGVzOjAscG9pbnRzOjAsbGluZXM6MH07ZnVuY3Rpb24gbihvLGEscyl7c3dpdGNoKHIuY2FsbHMrKyxhKXtjYXNlIDQ6ci50cmlhbmdsZXMrPXMqKG8vMyk7YnJlYWs7Y2FzZSAxOnIubGluZXMrPXMqKG8vMik7YnJlYWs7Y2FzZSAzOnIubGluZXMrPXMqKG8tMSk7YnJlYWs7Y2FzZSAyOnIubGluZXMrPXMqbzticmVhaztjYXNlIDA6ci5wb2ludHMrPXMqbzticmVhaztkZWZhdWx0OmNvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMSW5mbzogVW5rbm93biBkcmF3IG1vZGU6IixhKTticmVha319ZnVuY3Rpb24gaSgpe3IuZnJhbWUrKyxyLmNhbGxzPTAsci50cmlhbmdsZXM9MCxyLnBvaW50cz0wLHIubGluZXM9MH1yZXR1cm57bWVtb3J5OnQscmVuZGVyOnIscHJvZ3JhbXM6bnVsbCxhdXRvUmVzZXQ6ITAscmVzZXQ6aSx1cGRhdGU6bn19dmFyIHRNPWNsYXNzIGV4dGVuZHMgeGl7Y29uc3RydWN0b3IodD1udWxsLHI9MSxuPTEsaT0xKXtzdXBlcihudWxsKSx0aGlzLmltYWdlPXtkYXRhOnQsd2lkdGg6cixoZWlnaHQ6bixkZXB0aDppfSx0aGlzLm1hZ0ZpbHRlcj1MaSx0aGlzLm1pbkZpbHRlcj1MaSx0aGlzLndyYXBSPUpvLHRoaXMuZ2VuZXJhdGVNaXBtYXBzPSExLHRoaXMuZmxpcFk9ITEsdGhpcy51bnBhY2tBbGlnbm1lbnQ9MX19O3RNLnByb3RvdHlwZS5pc0RhdGFUZXh0dXJlMkRBcnJheT0hMDtmdW5jdGlvbiBybXIoZSx0KXtyZXR1cm4gZVswXS10WzBdfWZ1bmN0aW9uIG5tcihlLHQpe3JldHVybiBNYXRoLmFicyh0WzFdKS1NYXRoLmFicyhlWzFdKX1mdW5jdGlvbiBJdWUoZSx0KXtsZXQgcj0xLG49dC5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlP3QuZGF0YS5hcnJheTp0LmFycmF5O24gaW5zdGFuY2VvZiBJbnQ4QXJyYXk/cj0xMjc6biBpbnN0YW5jZW9mIEludDE2QXJyYXk/cj0zMjc2NzpuIGluc3RhbmNlb2YgSW50MzJBcnJheT9yPTIxNDc0ODM2NDc6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xNb3JwaHRhcmdldHM6IFVuc3VwcG9ydGVkIG1vcnBoIGF0dHJpYnV0ZSBkYXRhIHR5cGU6ICIsbiksZS5kaXZpZGVTY2FsYXIocil9ZnVuY3Rpb24gaW1yKGUsdCxyKXtsZXQgbj17fSxpPW5ldyBGbG9hdDMyQXJyYXkoOCksbz1uZXcgV2Vha01hcCxhPW5ldyBqLHM9W107Zm9yKGxldCBjPTA7Yzw4O2MrKylzW2NdPVtjLDBdO2Z1bmN0aW9uIGwoYyx1LGgsZil7bGV0IHA9Yy5tb3JwaFRhcmdldEluZmx1ZW5jZXM7aWYodC5pc1dlYkdMMj09PSEwKXtsZXQgZD11Lm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbi5sZW5ndGgsZz1vLmdldCh1KTtpZihnPT09dm9pZCAwfHxnLmNvdW50IT09ZCl7bGV0IEw9ZnVuY3Rpb24oKXtCLmRpc3Bvc2UoKSxvLmRlbGV0ZSh1KSx1LnJlbW92ZUV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLEwpfTtnIT09dm9pZCAwJiZnLnRleHR1cmUuZGlzcG9zZSgpO2xldCB4PXUubW9ycGhBdHRyaWJ1dGVzLm5vcm1hbCE9PXZvaWQgMCxiPXUubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uLFM9dS5tb3JwaEF0dHJpYnV0ZXMubm9ybWFsfHxbXSxDPXUuYXR0cmlidXRlcy5wb3NpdGlvbi5jb3VudCxQPXg9PT0hMD8yOjEsaz1DKlAsTz0xO2s+dC5tYXhUZXh0dXJlU2l6ZSYmKE89TWF0aC5jZWlsKGsvdC5tYXhUZXh0dXJlU2l6ZSksaz10Lm1heFRleHR1cmVTaXplKTtsZXQgRD1uZXcgRmxvYXQzMkFycmF5KGsqTyo0KmQpLEI9bmV3IHRNKEQsayxPLGQpO0IuZm9ybWF0PVFvLEIudHlwZT1qZCxCLm5lZWRzVXBkYXRlPSEwO2xldCBJPVAqNDtmb3IobGV0IFI9MDtSPGQ7UisrKXtsZXQgRj1iW1JdLHo9U1tSXSxVPWsqTyo0KlI7Zm9yKGxldCBXPTA7VzxGLmNvdW50O1crKyl7YS5mcm9tQnVmZmVyQXR0cmlidXRlKEYsVyksRi5ub3JtYWxpemVkPT09ITAmJkl1ZShhLEYpO2xldCBaPVcqSTtEW1UrWiswXT1hLngsRFtVK1orMV09YS55LERbVStaKzJdPWEueixEW1UrWiszXT0wLHg9PT0hMCYmKGEuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh6LFcpLHoubm9ybWFsaXplZD09PSEwJiZJdWUoYSx6KSxEW1UrWis0XT1hLngsRFtVK1orNV09YS55LERbVStaKzZdPWEueixEW1UrWis3XT0wKX19Zz17Y291bnQ6ZCx0ZXh0dXJlOkIsc2l6ZTpuZXcgTHQoayxPKX0sby5zZXQodSxnKSx1LmFkZEV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLEwpfWxldCBfPTA7Zm9yKGxldCB4PTA7eDxwLmxlbmd0aDt4KyspXys9cFt4XTtsZXQgeT11Lm1vcnBoVGFyZ2V0c1JlbGF0aXZlPzE6MS1fO2YuZ2V0VW5pZm9ybXMoKS5zZXRWYWx1ZShlLCJtb3JwaFRhcmdldEJhc2VJbmZsdWVuY2UiLHkpLGYuZ2V0VW5pZm9ybXMoKS5zZXRWYWx1ZShlLCJtb3JwaFRhcmdldEluZmx1ZW5jZXMiLHApLGYuZ2V0VW5pZm9ybXMoKS5zZXRWYWx1ZShlLCJtb3JwaFRhcmdldHNUZXh0dXJlIixnLnRleHR1cmUsciksZi5nZXRVbmlmb3JtcygpLnNldFZhbHVlKGUsIm1vcnBoVGFyZ2V0c1RleHR1cmVTaXplIixnLnNpemUpfWVsc2V7bGV0IGQ9cD09PXZvaWQgMD8wOnAubGVuZ3RoLGc9blt1LmlkXTtpZihnPT09dm9pZCAwfHxnLmxlbmd0aCE9PWQpe2c9W107Zm9yKGxldCBTPTA7UzxkO1MrKylnW1NdPVtTLDBdO25bdS5pZF09Z31mb3IobGV0IFM9MDtTPGQ7UysrKXtsZXQgQz1nW1NdO0NbMF09UyxDWzFdPXBbU119Zy5zb3J0KG5tcik7Zm9yKGxldCBTPTA7Uzw4O1MrKylTPGQmJmdbU11bMV0/KHNbU11bMF09Z1tTXVswXSxzW1NdWzFdPWdbU11bMV0pOihzW1NdWzBdPU51bWJlci5NQVhfU0FGRV9JTlRFR0VSLHNbU11bMV09MCk7cy5zb3J0KHJtcik7bGV0IF89dS5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24seT11Lm1vcnBoQXR0cmlidXRlcy5ub3JtYWwseD0wO2ZvcihsZXQgUz0wO1M8ODtTKyspe2xldCBDPXNbU10sUD1DWzBdLGs9Q1sxXTtQIT09TnVtYmVyLk1BWF9TQUZFX0lOVEVHRVImJms/KF8mJnUuZ2V0QXR0cmlidXRlKCJtb3JwaFRhcmdldCIrUykhPT1fW1BdJiZ1LnNldEF0dHJpYnV0ZSgibW9ycGhUYXJnZXQiK1MsX1tQXSkseSYmdS5nZXRBdHRyaWJ1dGUoIm1vcnBoTm9ybWFsIitTKSE9PXlbUF0mJnUuc2V0QXR0cmlidXRlKCJtb3JwaE5vcm1hbCIrUyx5W1BdKSxpW1NdPWsseCs9ayk6KF8mJnUuaGFzQXR0cmlidXRlKCJtb3JwaFRhcmdldCIrUyk9PT0hMCYmdS5kZWxldGVBdHRyaWJ1dGUoIm1vcnBoVGFyZ2V0IitTKSx5JiZ1Lmhhc0F0dHJpYnV0ZSgibW9ycGhOb3JtYWwiK1MpPT09ITAmJnUuZGVsZXRlQXR0cmlidXRlKCJtb3JwaE5vcm1hbCIrUyksaVtTXT0wKX1sZXQgYj11Lm1vcnBoVGFyZ2V0c1JlbGF0aXZlPzE6MS14O2YuZ2V0VW5pZm9ybXMoKS5zZXRWYWx1ZShlLCJtb3JwaFRhcmdldEJhc2VJbmZsdWVuY2UiLGIpLGYuZ2V0VW5pZm9ybXMoKS5zZXRWYWx1ZShlLCJtb3JwaFRhcmdldEluZmx1ZW5jZXMiLGkpfX1yZXR1cm57dXBkYXRlOmx9fWZ1bmN0aW9uIG9tcihlLHQscixuKXtsZXQgaT1uZXcgV2Vha01hcDtmdW5jdGlvbiBvKGwpe2xldCBjPW4ucmVuZGVyLmZyYW1lLHU9bC5nZW9tZXRyeSxoPXQuZ2V0KGwsdSk7cmV0dXJuIGkuZ2V0KGgpIT09YyYmKHQudXBkYXRlKGgpLGkuc2V0KGgsYykpLGwuaXNJbnN0YW5jZWRNZXNoJiYobC5oYXNFdmVudExpc3RlbmVyKCJkaXNwb3NlIixzKT09PSExJiZsLmFkZEV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLHMpLHIudXBkYXRlKGwuaW5zdGFuY2VNYXRyaXgsMzQ5NjIpLGwuaW5zdGFuY2VDb2xvciE9PW51bGwmJnIudXBkYXRlKGwuaW5zdGFuY2VDb2xvciwzNDk2MikpLGh9ZnVuY3Rpb24gYSgpe2k9bmV3IFdlYWtNYXB9ZnVuY3Rpb24gcyhsKXtsZXQgYz1sLnRhcmdldDtjLnJlbW92ZUV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLHMpLHIucmVtb3ZlKGMuaW5zdGFuY2VNYXRyaXgpLGMuaW5zdGFuY2VDb2xvciE9PW51bGwmJnIucmVtb3ZlKGMuaW5zdGFuY2VDb2xvcil9cmV0dXJue3VwZGF0ZTpvLGRpc3Bvc2U6YX19dmFyIGU2PWNsYXNzIGV4dGVuZHMgeGl7Y29uc3RydWN0b3IodD1udWxsLHI9MSxuPTEsaT0xKXtzdXBlcihudWxsKSx0aGlzLmltYWdlPXtkYXRhOnQsd2lkdGg6cixoZWlnaHQ6bixkZXB0aDppfSx0aGlzLm1hZ0ZpbHRlcj1MaSx0aGlzLm1pbkZpbHRlcj1MaSx0aGlzLndyYXBSPUpvLHRoaXMuZ2VuZXJhdGVNaXBtYXBzPSExLHRoaXMuZmxpcFk9ITEsdGhpcy51bnBhY2tBbGlnbm1lbnQ9MX19O2U2LnByb3RvdHlwZS5pc0RhdGFUZXh0dXJlM0Q9ITA7dmFyIEhmZT1uZXcgeGksVmZlPW5ldyB0TSxVZmU9bmV3IGU2LHFmZT1uZXcgSDAsTHVlPVtdLGt1ZT1bXSxSdWU9bmV3IEZsb2F0MzJBcnJheSgxNiksTnVlPW5ldyBGbG9hdDMyQXJyYXkoOSksRHVlPW5ldyBGbG9hdDMyQXJyYXkoNCk7ZnVuY3Rpb24gYk0oZSx0LHIpe2xldCBuPWVbMF07aWYobjw9MHx8bj4wKXJldHVybiBlO2xldCBpPXQqcixvPUx1ZVtpXTtpZihvPT09dm9pZCAwJiYobz1uZXcgRmxvYXQzMkFycmF5KGkpLEx1ZVtpXT1vKSx0IT09MCl7bi50b0FycmF5KG8sMCk7Zm9yKGxldCBhPTEscz0wO2EhPT10OysrYSlzKz1yLGVbYV0udG9BcnJheShvLHMpfXJldHVybiBvfWZ1bmN0aW9uIGhzKGUsdCl7aWYoZS5sZW5ndGghPT10Lmxlbmd0aClyZXR1cm4hMTtmb3IobGV0IHI9MCxuPWUubGVuZ3RoO3I8bjtyKyspaWYoZVtyXSE9PXRbcl0pcmV0dXJuITE7cmV0dXJuITB9ZnVuY3Rpb24gQ2EoZSx0KXtmb3IobGV0IHI9MCxuPXQubGVuZ3RoO3I8bjtyKyspZVtyXT10W3JdfWZ1bmN0aW9uICRVKGUsdCl7bGV0IHI9a3VlW3RdO3I9PT12b2lkIDAmJihyPW5ldyBJbnQzMkFycmF5KHQpLGt1ZVt0XT1yKTtmb3IobGV0IG49MDtuIT09dDsrK24pcltuXT1lLmFsbG9jYXRlVGV4dHVyZVVuaXQoKTtyZXR1cm4gcn1mdW5jdGlvbiBhbXIoZSx0KXtsZXQgcj10aGlzLmNhY2hlO3JbMF0hPT10JiYoZS51bmlmb3JtMWYodGhpcy5hZGRyLHQpLHJbMF09dCl9ZnVuY3Rpb24gc21yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZTtpZih0LnghPT12b2lkIDApKHJbMF0hPT10Lnh8fHJbMV0hPT10LnkpJiYoZS51bmlmb3JtMmYodGhpcy5hZGRyLHQueCx0LnkpLHJbMF09dC54LHJbMV09dC55KTtlbHNle2lmKGhzKHIsdCkpcmV0dXJuO2UudW5pZm9ybTJmdih0aGlzLmFkZHIsdCksQ2Eocix0KX19ZnVuY3Rpb24gbG1yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZTtpZih0LnghPT12b2lkIDApKHJbMF0hPT10Lnh8fHJbMV0hPT10Lnl8fHJbMl0hPT10LnopJiYoZS51bmlmb3JtM2YodGhpcy5hZGRyLHQueCx0LnksdC56KSxyWzBdPXQueCxyWzFdPXQueSxyWzJdPXQueik7ZWxzZSBpZih0LnIhPT12b2lkIDApKHJbMF0hPT10LnJ8fHJbMV0hPT10Lmd8fHJbMl0hPT10LmIpJiYoZS51bmlmb3JtM2YodGhpcy5hZGRyLHQucix0LmcsdC5iKSxyWzBdPXQucixyWzFdPXQuZyxyWzJdPXQuYik7ZWxzZXtpZihocyhyLHQpKXJldHVybjtlLnVuaWZvcm0zZnYodGhpcy5hZGRyLHQpLENhKHIsdCl9fWZ1bmN0aW9uIGNtcihlLHQpe2xldCByPXRoaXMuY2FjaGU7aWYodC54IT09dm9pZCAwKShyWzBdIT09dC54fHxyWzFdIT09dC55fHxyWzJdIT09dC56fHxyWzNdIT09dC53KSYmKGUudW5pZm9ybTRmKHRoaXMuYWRkcix0LngsdC55LHQueix0LncpLHJbMF09dC54LHJbMV09dC55LHJbMl09dC56LHJbM109dC53KTtlbHNle2lmKGhzKHIsdCkpcmV0dXJuO2UudW5pZm9ybTRmdih0aGlzLmFkZHIsdCksQ2Eocix0KX19ZnVuY3Rpb24gdW1yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZSxuPXQuZWxlbWVudHM7aWYobj09PXZvaWQgMCl7aWYoaHMocix0KSlyZXR1cm47ZS51bmlmb3JtTWF0cml4MmZ2KHRoaXMuYWRkciwhMSx0KSxDYShyLHQpfWVsc2V7aWYoaHMocixuKSlyZXR1cm47RHVlLnNldChuKSxlLnVuaWZvcm1NYXRyaXgyZnYodGhpcy5hZGRyLCExLER1ZSksQ2EocixuKX19ZnVuY3Rpb24gaG1yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZSxuPXQuZWxlbWVudHM7aWYobj09PXZvaWQgMCl7aWYoaHMocix0KSlyZXR1cm47ZS51bmlmb3JtTWF0cml4M2Z2KHRoaXMuYWRkciwhMSx0KSxDYShyLHQpfWVsc2V7aWYoaHMocixuKSlyZXR1cm47TnVlLnNldChuKSxlLnVuaWZvcm1NYXRyaXgzZnYodGhpcy5hZGRyLCExLE51ZSksQ2EocixuKX19ZnVuY3Rpb24gZm1yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZSxuPXQuZWxlbWVudHM7aWYobj09PXZvaWQgMCl7aWYoaHMocix0KSlyZXR1cm47ZS51bmlmb3JtTWF0cml4NGZ2KHRoaXMuYWRkciwhMSx0KSxDYShyLHQpfWVsc2V7aWYoaHMocixuKSlyZXR1cm47UnVlLnNldChuKSxlLnVuaWZvcm1NYXRyaXg0ZnYodGhpcy5hZGRyLCExLFJ1ZSksQ2EocixuKX19ZnVuY3Rpb24gcG1yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZTtyWzBdIT09dCYmKGUudW5pZm9ybTFpKHRoaXMuYWRkcix0KSxyWzBdPXQpfWZ1bmN0aW9uIGRtcihlLHQpe2xldCByPXRoaXMuY2FjaGU7aHMocix0KXx8KGUudW5pZm9ybTJpdih0aGlzLmFkZHIsdCksQ2Eocix0KSl9ZnVuY3Rpb24gbW1yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZTtocyhyLHQpfHwoZS51bmlmb3JtM2l2KHRoaXMuYWRkcix0KSxDYShyLHQpKX1mdW5jdGlvbiBnbXIoZSx0KXtsZXQgcj10aGlzLmNhY2hlO2hzKHIsdCl8fChlLnVuaWZvcm00aXYodGhpcy5hZGRyLHQpLENhKHIsdCkpfWZ1bmN0aW9uIF9tcihlLHQpe2xldCByPXRoaXMuY2FjaGU7clswXSE9PXQmJihlLnVuaWZvcm0xdWkodGhpcy5hZGRyLHQpLHJbMF09dCl9ZnVuY3Rpb24geW1yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZTtocyhyLHQpfHwoZS51bmlmb3JtMnVpdih0aGlzLmFkZHIsdCksQ2Eocix0KSl9ZnVuY3Rpb24gdm1yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZTtocyhyLHQpfHwoZS51bmlmb3JtM3Vpdih0aGlzLmFkZHIsdCksQ2Eocix0KSl9ZnVuY3Rpb24geG1yKGUsdCl7bGV0IHI9dGhpcy5jYWNoZTtocyhyLHQpfHwoZS51bmlmb3JtNHVpdih0aGlzLmFkZHIsdCksQ2Eocix0KSl9ZnVuY3Rpb24gYm1yKGUsdCxyKXtsZXQgbj10aGlzLmNhY2hlLGk9ci5hbGxvY2F0ZVRleHR1cmVVbml0KCk7blswXSE9PWkmJihlLnVuaWZvcm0xaSh0aGlzLmFkZHIsaSksblswXT1pKSxyLnNhZmVTZXRUZXh0dXJlMkQodHx8SGZlLGkpfWZ1bmN0aW9uIHdtcihlLHQscil7bGV0IG49dGhpcy5jYWNoZSxpPXIuYWxsb2NhdGVUZXh0dXJlVW5pdCgpO25bMF0hPT1pJiYoZS51bmlmb3JtMWkodGhpcy5hZGRyLGkpLG5bMF09aSksci5zZXRUZXh0dXJlM0QodHx8VWZlLGkpfWZ1bmN0aW9uIFNtcihlLHQscil7bGV0IG49dGhpcy5jYWNoZSxpPXIuYWxsb2NhdGVUZXh0dXJlVW5pdCgpO25bMF0hPT1pJiYoZS51bmlmb3JtMWkodGhpcy5hZGRyLGkpLG5bMF09aSksci5zYWZlU2V0VGV4dHVyZUN1YmUodHx8cWZlLGkpfWZ1bmN0aW9uIE1tcihlLHQscil7bGV0IG49dGhpcy5jYWNoZSxpPXIuYWxsb2NhdGVUZXh0dXJlVW5pdCgpO25bMF0hPT1pJiYoZS51bmlmb3JtMWkodGhpcy5hZGRyLGkpLG5bMF09aSksci5zZXRUZXh0dXJlMkRBcnJheSh0fHxWZmUsaSl9ZnVuY3Rpb24gRW1yKGUpe3N3aXRjaChlKXtjYXNlIDUxMjY6cmV0dXJuIGFtcjtjYXNlIDM1NjY0OnJldHVybiBzbXI7Y2FzZSAzNTY2NTpyZXR1cm4gbG1yO2Nhc2UgMzU2NjY6cmV0dXJuIGNtcjtjYXNlIDM1Njc0OnJldHVybiB1bXI7Y2FzZSAzNTY3NTpyZXR1cm4gaG1yO2Nhc2UgMzU2NzY6cmV0dXJuIGZtcjtjYXNlIDUxMjQ6Y2FzZSAzNTY3MDpyZXR1cm4gcG1yO2Nhc2UgMzU2Njc6Y2FzZSAzNTY3MTpyZXR1cm4gZG1yO2Nhc2UgMzU2Njg6Y2FzZSAzNTY3MjpyZXR1cm4gbW1yO2Nhc2UgMzU2Njk6Y2FzZSAzNTY3MzpyZXR1cm4gZ21yO2Nhc2UgNTEyNTpyZXR1cm4gX21yO2Nhc2UgMzYyOTQ6cmV0dXJuIHltcjtjYXNlIDM2Mjk1OnJldHVybiB2bXI7Y2FzZSAzNjI5NjpyZXR1cm4geG1yO2Nhc2UgMzU2Nzg6Y2FzZSAzNjE5ODpjYXNlIDM2Mjk4OmNhc2UgMzYzMDY6Y2FzZSAzNTY4MjpyZXR1cm4gYm1yO2Nhc2UgMzU2Nzk6Y2FzZSAzNjI5OTpjYXNlIDM2MzA3OnJldHVybiB3bXI7Y2FzZSAzNTY4MDpjYXNlIDM2MzAwOmNhc2UgMzYzMDg6Y2FzZSAzNjI5MzpyZXR1cm4gU21yO2Nhc2UgMzYyODk6Y2FzZSAzNjMwMzpjYXNlIDM2MzExOmNhc2UgMzYyOTI6cmV0dXJuIE1tcn19ZnVuY3Rpb24gVG1yKGUsdCl7ZS51bmlmb3JtMWZ2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBDbXIoZSx0KXtsZXQgcj1iTSh0LHRoaXMuc2l6ZSwyKTtlLnVuaWZvcm0yZnYodGhpcy5hZGRyLHIpfWZ1bmN0aW9uIEFtcihlLHQpe2xldCByPWJNKHQsdGhpcy5zaXplLDMpO2UudW5pZm9ybTNmdih0aGlzLmFkZHIscil9ZnVuY3Rpb24gUG1yKGUsdCl7bGV0IHI9Yk0odCx0aGlzLnNpemUsNCk7ZS51bmlmb3JtNGZ2KHRoaXMuYWRkcixyKX1mdW5jdGlvbiBJbXIoZSx0KXtsZXQgcj1iTSh0LHRoaXMuc2l6ZSw0KTtlLnVuaWZvcm1NYXRyaXgyZnYodGhpcy5hZGRyLCExLHIpfWZ1bmN0aW9uIExtcihlLHQpe2xldCByPWJNKHQsdGhpcy5zaXplLDkpO2UudW5pZm9ybU1hdHJpeDNmdih0aGlzLmFkZHIsITEscil9ZnVuY3Rpb24ga21yKGUsdCl7bGV0IHI9Yk0odCx0aGlzLnNpemUsMTYpO2UudW5pZm9ybU1hdHJpeDRmdih0aGlzLmFkZHIsITEscil9ZnVuY3Rpb24gUm1yKGUsdCl7ZS51bmlmb3JtMWl2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBObXIoZSx0KXtlLnVuaWZvcm0yaXYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIERtcihlLHQpe2UudW5pZm9ybTNpdih0aGlzLmFkZHIsdCl9ZnVuY3Rpb24gT21yKGUsdCl7ZS51bmlmb3JtNGl2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiB6bXIoZSx0KXtlLnVuaWZvcm0xdWl2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBGbXIoZSx0KXtlLnVuaWZvcm0ydWl2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBCbXIoZSx0KXtlLnVuaWZvcm0zdWl2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBIbXIoZSx0KXtlLnVuaWZvcm00dWl2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBWbXIoZSx0LHIpe2xldCBuPXQubGVuZ3RoLGk9JFUocixuKTtlLnVuaWZvcm0xaXYodGhpcy5hZGRyLGkpO2ZvcihsZXQgbz0wO28hPT1uOysrbylyLnNhZmVTZXRUZXh0dXJlMkQodFtvXXx8SGZlLGlbb10pfWZ1bmN0aW9uIFVtcihlLHQscil7bGV0IG49dC5sZW5ndGgsaT0kVShyLG4pO2UudW5pZm9ybTFpdih0aGlzLmFkZHIsaSk7Zm9yKGxldCBvPTA7byE9PW47KytvKXIuc2V0VGV4dHVyZTNEKHRbb118fFVmZSxpW29dKX1mdW5jdGlvbiBxbXIoZSx0LHIpe2xldCBuPXQubGVuZ3RoLGk9JFUocixuKTtlLnVuaWZvcm0xaXYodGhpcy5hZGRyLGkpO2ZvcihsZXQgbz0wO28hPT1uOysrbylyLnNhZmVTZXRUZXh0dXJlQ3ViZSh0W29dfHxxZmUsaVtvXSl9ZnVuY3Rpb24gR21yKGUsdCxyKXtsZXQgbj10Lmxlbmd0aCxpPSRVKHIsbik7ZS51bmlmb3JtMWl2KHRoaXMuYWRkcixpKTtmb3IobGV0IG89MDtvIT09bjsrK28pci5zZXRUZXh0dXJlMkRBcnJheSh0W29dfHxWZmUsaVtvXSl9ZnVuY3Rpb24gV21yKGUpe3N3aXRjaChlKXtjYXNlIDUxMjY6cmV0dXJuIFRtcjtjYXNlIDM1NjY0OnJldHVybiBDbXI7Y2FzZSAzNTY2NTpyZXR1cm4gQW1yO2Nhc2UgMzU2NjY6cmV0dXJuIFBtcjtjYXNlIDM1Njc0OnJldHVybiBJbXI7Y2FzZSAzNTY3NTpyZXR1cm4gTG1yO2Nhc2UgMzU2NzY6cmV0dXJuIGttcjtjYXNlIDUxMjQ6Y2FzZSAzNTY3MDpyZXR1cm4gUm1yO2Nhc2UgMzU2Njc6Y2FzZSAzNTY3MTpyZXR1cm4gTm1yO2Nhc2UgMzU2Njg6Y2FzZSAzNTY3MjpyZXR1cm4gRG1yO2Nhc2UgMzU2Njk6Y2FzZSAzNTY3MzpyZXR1cm4gT21yO2Nhc2UgNTEyNTpyZXR1cm4gem1yO2Nhc2UgMzYyOTQ6cmV0dXJuIEZtcjtjYXNlIDM2Mjk1OnJldHVybiBCbXI7Y2FzZSAzNjI5NjpyZXR1cm4gSG1yO2Nhc2UgMzU2Nzg6Y2FzZSAzNjE5ODpjYXNlIDM2Mjk4OmNhc2UgMzYzMDY6Y2FzZSAzNTY4MjpyZXR1cm4gVm1yO2Nhc2UgMzU2Nzk6Y2FzZSAzNjI5OTpjYXNlIDM2MzA3OnJldHVybiBVbXI7Y2FzZSAzNTY4MDpjYXNlIDM2MzAwOmNhc2UgMzYzMDg6Y2FzZSAzNjI5MzpyZXR1cm4gcW1yO2Nhc2UgMzYyODk6Y2FzZSAzNjMwMzpjYXNlIDM2MzExOmNhc2UgMzYyOTI6cmV0dXJuIEdtcn19ZnVuY3Rpb24gWW1yKGUsdCxyKXt0aGlzLmlkPWUsdGhpcy5hZGRyPXIsdGhpcy5jYWNoZT1bXSx0aGlzLnNldFZhbHVlPUVtcih0LnR5cGUpfWZ1bmN0aW9uIEdmZShlLHQscil7dGhpcy5pZD1lLHRoaXMuYWRkcj1yLHRoaXMuY2FjaGU9W10sdGhpcy5zaXplPXQuc2l6ZSx0aGlzLnNldFZhbHVlPVdtcih0LnR5cGUpfUdmZS5wcm90b3R5cGUudXBkYXRlQ2FjaGU9ZnVuY3Rpb24oZSl7bGV0IHQ9dGhpcy5jYWNoZTtlIGluc3RhbmNlb2YgRmxvYXQzMkFycmF5JiZ0Lmxlbmd0aCE9PWUubGVuZ3RoJiYodGhpcy5jYWNoZT1uZXcgRmxvYXQzMkFycmF5KGUubGVuZ3RoKSksQ2EodCxlKX07ZnVuY3Rpb24gV2ZlKGUpe3RoaXMuaWQ9ZSx0aGlzLnNlcT1bXSx0aGlzLm1hcD17fX1XZmUucHJvdG90eXBlLnNldFZhbHVlPWZ1bmN0aW9uKGUsdCxyKXtsZXQgbj10aGlzLnNlcTtmb3IobGV0IGk9MCxvPW4ubGVuZ3RoO2khPT1vOysraSl7bGV0IGE9bltpXTthLnNldFZhbHVlKGUsdFthLmlkXSxyKX19O3ZhciBkdXQ9LyhcdyspKFxdKT8oXFt8XC4pPy9nO2Z1bmN0aW9uIE91ZShlLHQpe2Uuc2VxLnB1c2godCksZS5tYXBbdC5pZF09dH1mdW5jdGlvbiBqbXIoZSx0LHIpe2xldCBuPWUubmFtZSxpPW4ubGVuZ3RoO2ZvcihkdXQubGFzdEluZGV4PTA7Oyl7bGV0IG89ZHV0LmV4ZWMobiksYT1kdXQubGFzdEluZGV4LHM9b1sxXSxsPW9bMl09PT0iXSIsYz1vWzNdO2lmKGwmJihzPXN8MCksYz09PXZvaWQgMHx8Yz09PSJbIiYmYSsyPT09aSl7T3VlKHIsYz09PXZvaWQgMD9uZXcgWW1yKHMsZSx0KTpuZXcgR2ZlKHMsZSx0KSk7YnJlYWt9ZWxzZXtsZXQgaD1yLm1hcFtzXTtoPT09dm9pZCAwJiYoaD1uZXcgV2ZlKHMpLE91ZShyLGgpKSxyPWh9fX1mdW5jdGlvbiBCMChlLHQpe3RoaXMuc2VxPVtdLHRoaXMubWFwPXt9O2xldCByPWUuZ2V0UHJvZ3JhbVBhcmFtZXRlcih0LDM1NzE4KTtmb3IobGV0IG49MDtuPHI7KytuKXtsZXQgaT1lLmdldEFjdGl2ZVVuaWZvcm0odCxuKSxvPWUuZ2V0VW5pZm9ybUxvY2F0aW9uKHQsaS5uYW1lKTtqbXIoaSxvLHRoaXMpfX1CMC5wcm90b3R5cGUuc2V0VmFsdWU9ZnVuY3Rpb24oZSx0LHIsbil7bGV0IGk9dGhpcy5tYXBbdF07aSE9PXZvaWQgMCYmaS5zZXRWYWx1ZShlLHIsbil9O0IwLnByb3RvdHlwZS5zZXRPcHRpb25hbD1mdW5jdGlvbihlLHQscil7bGV0IG49dFtyXTtuIT09dm9pZCAwJiZ0aGlzLnNldFZhbHVlKGUscixuKX07QjAudXBsb2FkPWZ1bmN0aW9uKGUsdCxyLG4pe2ZvcihsZXQgaT0wLG89dC5sZW5ndGg7aSE9PW87KytpKXtsZXQgYT10W2ldLHM9clthLmlkXTtzLm5lZWRzVXBkYXRlIT09ITEmJmEuc2V0VmFsdWUoZSxzLnZhbHVlLG4pfX07QjAuc2VxV2l0aFZhbHVlPWZ1bmN0aW9uKGUsdCl7bGV0IHI9W107Zm9yKGxldCBuPTAsaT1lLmxlbmd0aDtuIT09aTsrK24pe2xldCBvPWVbbl07by5pZCBpbiB0JiZyLnB1c2gobyl9cmV0dXJuIHJ9O2Z1bmN0aW9uIHp1ZShlLHQscil7bGV0IG49ZS5jcmVhdGVTaGFkZXIodCk7cmV0dXJuIGUuc2hhZGVyU291cmNlKG4sciksZS5jb21waWxlU2hhZGVyKG4pLG59dmFyIFhtcj0wO2Z1bmN0aW9uICRtcihlKXtsZXQgdD1lLnNwbGl0KGAKYCk7Zm9yKGxldCByPTA7cjx0Lmxlbmd0aDtyKyspdFtyXT1yKzErIjogIit0W3JdO3JldHVybiB0LmpvaW4oYApgKX1mdW5jdGlvbiBLbXIoZSl7c3dpdGNoKGUpe2Nhc2UgUWQ6cmV0dXJuWyJMaW5lYXIiLCIoIHZhbHVlICkiXTtjYXNlIFluOnJldHVyblsic1JHQiIsIiggdmFsdWUgKSJdO2RlZmF1bHQ6cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xQcm9ncmFtOiBVbnN1cHBvcnRlZCBlbmNvZGluZzoiLGUpLFsiTGluZWFyIiwiKCB2YWx1ZSApIl19fWZ1bmN0aW9uIEZ1ZShlLHQscil7bGV0IG49ZS5nZXRTaGFkZXJQYXJhbWV0ZXIodCwzNTcxMyksaT1lLmdldFNoYWRlckluZm9Mb2codCkudHJpbSgpO3JldHVybiBuJiZpPT09IiI/IiI6ci50b1VwcGVyQ2FzZSgpK2AKCmAraStgCgpgKyRtcihlLmdldFNoYWRlclNvdXJjZSh0KSl9ZnVuY3Rpb24gWm1yKGUsdCl7bGV0IHI9S21yKHQpO3JldHVybiJ2ZWM0ICIrZSsiKCB2ZWM0IHZhbHVlICkgeyByZXR1cm4gTGluZWFyVG8iK3JbMF0rclsxXSsiOyB9In1mdW5jdGlvbiBKbXIoZSx0KXtsZXQgcjtzd2l0Y2godCl7Y2FzZSBvZmU6cj0iTGluZWFyIjticmVhaztjYXNlIGFmZTpyPSJSZWluaGFyZCI7YnJlYWs7Y2FzZSBzZmU6cj0iT3B0aW1pemVkQ2luZW9uIjticmVhaztjYXNlIGxmZTpyPSJBQ0VTRmlsbWljIjticmVhaztjYXNlIGNmZTpyPSJDdXN0b20iO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFByb2dyYW06IFVuc3VwcG9ydGVkIHRvbmVNYXBwaW5nOiIsdCkscj0iTGluZWFyIn1yZXR1cm4idmVjMyAiK2UrIiggdmVjMyBjb2xvciApIHsgcmV0dXJuICIrcisiVG9uZU1hcHBpbmcoIGNvbG9yICk7IH0ifWZ1bmN0aW9uIFFtcihlKXtyZXR1cm5bZS5leHRlbnNpb25EZXJpdmF0aXZlc3x8ZS5lbnZNYXBDdWJlVVZ8fGUuYnVtcE1hcHx8ZS50YW5nZW50U3BhY2VOb3JtYWxNYXB8fGUuY2xlYXJjb2F0Tm9ybWFsTWFwfHxlLmZsYXRTaGFkaW5nfHxlLnNoYWRlcklEPT09InBoeXNpY2FsIj8iI2V4dGVuc2lvbiBHTF9PRVNfc3RhbmRhcmRfZGVyaXZhdGl2ZXMgOiBlbmFibGUiOiIiLChlLmV4dGVuc2lvbkZyYWdEZXB0aHx8ZS5sb2dhcml0aG1pY0RlcHRoQnVmZmVyKSYmZS5yZW5kZXJlckV4dGVuc2lvbkZyYWdEZXB0aD8iI2V4dGVuc2lvbiBHTF9FWFRfZnJhZ19kZXB0aCA6IGVuYWJsZSI6IiIsZS5leHRlbnNpb25EcmF3QnVmZmVycyYmZS5yZW5kZXJlckV4dGVuc2lvbkRyYXdCdWZmZXJzPyIjZXh0ZW5zaW9uIEdMX0VYVF9kcmF3X2J1ZmZlcnMgOiByZXF1aXJlIjoiIiwoZS5leHRlbnNpb25TaGFkZXJUZXh0dXJlTE9EfHxlLmVudk1hcHx8ZS50cmFuc21pc3Npb24pJiZlLnJlbmRlcmVyRXh0ZW5zaW9uU2hhZGVyVGV4dHVyZUxvZD8iI2V4dGVuc2lvbiBHTF9FWFRfc2hhZGVyX3RleHR1cmVfbG9kIDogZW5hYmxlIjoiIl0uZmlsdGVyKEJQKS5qb2luKGAKYCl9ZnVuY3Rpb24gdGdyKGUpe2xldCB0PVtdO2ZvcihsZXQgciBpbiBlKXtsZXQgbj1lW3JdO24hPT0hMSYmdC5wdXNoKCIjZGVmaW5lICIrcisiICIrbil9cmV0dXJuIHQuam9pbihgCmApfWZ1bmN0aW9uIGVncihlLHQpe2xldCByPXt9LG49ZS5nZXRQcm9ncmFtUGFyYW1ldGVyKHQsMzU3MjEpO2ZvcihsZXQgaT0wO2k8bjtpKyspe2xldCBvPWUuZ2V0QWN0aXZlQXR0cmliKHQsaSksYT1vLm5hbWUscz0xO28udHlwZT09PTM1Njc0JiYocz0yKSxvLnR5cGU9PT0zNTY3NSYmKHM9Myksby50eXBlPT09MzU2NzYmJihzPTQpLHJbYV09e3R5cGU6by50eXBlLGxvY2F0aW9uOmUuZ2V0QXR0cmliTG9jYXRpb24odCxhKSxsb2NhdGlvblNpemU6c319cmV0dXJuIHJ9ZnVuY3Rpb24gQlAoZSl7cmV0dXJuIGUhPT0iIn1mdW5jdGlvbiBCdWUoZSx0KXtyZXR1cm4gZS5yZXBsYWNlKC9OVU1fRElSX0xJR0hUUy9nLHQubnVtRGlyTGlnaHRzKS5yZXBsYWNlKC9OVU1fU1BPVF9MSUdIVFMvZyx0Lm51bVNwb3RMaWdodHMpLnJlcGxhY2UoL05VTV9SRUNUX0FSRUFfTElHSFRTL2csdC5udW1SZWN0QXJlYUxpZ2h0cykucmVwbGFjZSgvTlVNX1BPSU5UX0xJR0hUUy9nLHQubnVtUG9pbnRMaWdodHMpLnJlcGxhY2UoL05VTV9IRU1JX0xJR0hUUy9nLHQubnVtSGVtaUxpZ2h0cykucmVwbGFjZSgvTlVNX0RJUl9MSUdIVF9TSEFET1dTL2csdC5udW1EaXJMaWdodFNoYWRvd3MpLnJlcGxhY2UoL05VTV9TUE9UX0xJR0hUX1NIQURPV1MvZyx0Lm51bVNwb3RMaWdodFNoYWRvd3MpLnJlcGxhY2UoL05VTV9QT0lOVF9MSUdIVF9TSEFET1dTL2csdC5udW1Qb2ludExpZ2h0U2hhZG93cyl9ZnVuY3Rpb24gSHVlKGUsdCl7cmV0dXJuIGUucmVwbGFjZSgvTlVNX0NMSVBQSU5HX1BMQU5FUy9nLHQubnVtQ2xpcHBpbmdQbGFuZXMpLnJlcGxhY2UoL1VOSU9OX0NMSVBQSU5HX1BMQU5FUy9nLHQubnVtQ2xpcHBpbmdQbGFuZXMtdC5udW1DbGlwSW50ZXJzZWN0aW9uKX12YXIgcmdyPS9eWyBcdF0qI2luY2x1ZGUgKzwoW1x3XGQuL10rKT4vZ207ZnVuY3Rpb24gUXV0KGUpe3JldHVybiBlLnJlcGxhY2UocmdyLG5ncil9ZnVuY3Rpb24gbmdyKGUsdCl7bGV0IHI9aHJbdF07aWYocj09PXZvaWQgMCl0aHJvdyBuZXcgRXJyb3IoIkNhbiBub3QgcmVzb2x2ZSAjaW5jbHVkZSA8Iit0KyI+Iik7cmV0dXJuIFF1dChyKX12YXIgaWdyPS8jcHJhZ21hIHVucm9sbF9sb29wW1xzXSs/Zm9yIFwoIGludCBpIFw9IChcZCspXDsgaSA8IChcZCspXDsgaSBcK1wrIFwpIFx7KFtcc1xTXSs/KSg/PVx9KVx9L2csb2dyPS8jcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0XHMrZm9yXHMqXChccyppbnRccytpXHMqPVxzKihcZCspXHMqO1xzKmlccyo8XHMqKFxkKylccyo7XHMqaVxzKlwrXCtccypcKVxzKnsoW1xzXFNdKz8pfVxzKyNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kL2c7ZnVuY3Rpb24gVnVlKGUpe3JldHVybiBlLnJlcGxhY2Uob2dyLFlmZSkucmVwbGFjZShpZ3IsYWdyKX1mdW5jdGlvbiBhZ3IoZSx0LHIsbil7cmV0dXJuIGNvbnNvbGUud2FybigiV2ViR0xQcm9ncmFtOiAjcHJhZ21hIHVucm9sbF9sb29wIHNoYWRlciBzeW50YXggaXMgZGVwcmVjYXRlZC4gUGxlYXNlIHVzZSAjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0IHN5bnRheCBpbnN0ZWFkLiIpLFlmZShlLHQscixuKX1mdW5jdGlvbiBZZmUoZSx0LHIsbil7bGV0IGk9IiI7Zm9yKGxldCBvPXBhcnNlSW50KHQpO288cGFyc2VJbnQocik7bysrKWkrPW4ucmVwbGFjZSgvXFtccyppXHMqXF0vZywiWyAiK28rIiBdIikucmVwbGFjZSgvVU5ST0xMRURfTE9PUF9JTkRFWC9nLG8pO3JldHVybiBpfWZ1bmN0aW9uIFV1ZShlKXtsZXQgdD0icHJlY2lzaW9uICIrZS5wcmVjaXNpb24rYCBmbG9hdDsKcHJlY2lzaW9uIGArZS5wcmVjaXNpb24rIiBpbnQ7IjtyZXR1cm4gZS5wcmVjaXNpb249PT0iaGlnaHAiP3QrPWAKI2RlZmluZSBISUdIX1BSRUNJU0lPTmA6ZS5wcmVjaXNpb249PT0ibWVkaXVtcCI/dCs9YAojZGVmaW5lIE1FRElVTV9QUkVDSVNJT05gOmUucHJlY2lzaW9uPT09Imxvd3AiJiYodCs9YAojZGVmaW5lIExPV19QUkVDSVNJT05gKSx0fWZ1bmN0aW9uIHNncihlKXtsZXQgdD0iU0hBRE9XTUFQX1RZUEVfQkFTSUMiO3JldHVybiBlLnNoYWRvd01hcFR5cGU9PT1BaHQ/dD0iU0hBRE9XTUFQX1RZUEVfUENGIjplLnNoYWRvd01hcFR5cGU9PT16aGU/dD0iU0hBRE9XTUFQX1RZUEVfUENGX1NPRlQiOmUuc2hhZG93TWFwVHlwZT09PUYzJiYodD0iU0hBRE9XTUFQX1RZUEVfVlNNIiksdH1mdW5jdGlvbiBsZ3IoZSl7bGV0IHQ9IkVOVk1BUF9UWVBFX0NVQkUiO2lmKGUuZW52TWFwKXN3aXRjaChlLmVudk1hcE1vZGUpe2Nhc2Ugbng6Y2FzZSBpeDp0PSJFTlZNQVBfVFlQRV9DVUJFIjticmVhaztjYXNlIHhNOmNhc2UgTzY6dD0iRU5WTUFQX1RZUEVfQ1VCRV9VViI7YnJlYWt9cmV0dXJuIHR9ZnVuY3Rpb24gY2dyKGUpe2xldCB0PSJFTlZNQVBfTU9ERV9SRUZMRUNUSU9OIjtpZihlLmVudk1hcClzd2l0Y2goZS5lbnZNYXBNb2RlKXtjYXNlIGl4OmNhc2UgTzY6dD0iRU5WTUFQX01PREVfUkVGUkFDVElPTiI7YnJlYWt9cmV0dXJuIHR9ZnVuY3Rpb24gdWdyKGUpe2xldCB0PSJFTlZNQVBfQkxFTkRJTkdfTk9ORSI7aWYoZS5lbnZNYXApc3dpdGNoKGUuY29tYmluZSl7Y2FzZSBENjp0PSJFTlZNQVBfQkxFTkRJTkdfTVVMVElQTFkiO2JyZWFrO2Nhc2UgbmZlOnQ9IkVOVk1BUF9CTEVORElOR19NSVgiO2JyZWFrO2Nhc2UgaWZlOnQ9IkVOVk1BUF9CTEVORElOR19BREQiO2JyZWFrfXJldHVybiB0fWZ1bmN0aW9uIGhncihlLHQscixuKXtsZXQgaT1lLmdldENvbnRleHQoKSxvPXIuZGVmaW5lcyxhPXIudmVydGV4U2hhZGVyLHM9ci5mcmFnbWVudFNoYWRlcixsPXNncihyKSxjPWxncihyKSx1PWNncihyKSxoPXVncihyKSxmPXIuaXNXZWJHTDI/IiI6UW1yKHIpLHA9dGdyKG8pLGQ9aS5jcmVhdGVQcm9ncmFtKCksZyxfLHk9ci5nbHNsVmVyc2lvbj8iI3ZlcnNpb24gIityLmdsc2xWZXJzaW9uK2AKYDoiIjtyLmlzUmF3U2hhZGVyTWF0ZXJpYWw/KGc9W3BdLmZpbHRlcihCUCkuam9pbihgCmApLGcubGVuZ3RoPjAmJihnKz1gCmApLF89W2YscF0uZmlsdGVyKEJQKS5qb2luKGAKYCksXy5sZW5ndGg+MCYmKF8rPWAKYCkpOihnPVtVdWUociksIiNkZWZpbmUgU0hBREVSX05BTUUgIityLnNoYWRlck5hbWUscCxyLmluc3RhbmNpbmc/IiNkZWZpbmUgVVNFX0lOU1RBTkNJTkciOiIiLHIuaW5zdGFuY2luZ0NvbG9yPyIjZGVmaW5lIFVTRV9JTlNUQU5DSU5HX0NPTE9SIjoiIixyLnN1cHBvcnRzVmVydGV4VGV4dHVyZXM/IiNkZWZpbmUgVkVSVEVYX1RFWFRVUkVTIjoiIiwiI2RlZmluZSBNQVhfQk9ORVMgIityLm1heEJvbmVzLHIudXNlRm9nJiZyLmZvZz8iI2RlZmluZSBVU0VfRk9HIjoiIixyLnVzZUZvZyYmci5mb2dFeHAyPyIjZGVmaW5lIEZPR19FWFAyIjoiIixyLm1hcD8iI2RlZmluZSBVU0VfTUFQIjoiIixyLmVudk1hcD8iI2RlZmluZSBVU0VfRU5WTUFQIjoiIixyLmVudk1hcD8iI2RlZmluZSAiK3U6IiIsci5saWdodE1hcD8iI2RlZmluZSBVU0VfTElHSFRNQVAiOiIiLHIuYW9NYXA/IiNkZWZpbmUgVVNFX0FPTUFQIjoiIixyLmVtaXNzaXZlTWFwPyIjZGVmaW5lIFVTRV9FTUlTU0lWRU1BUCI6IiIsci5idW1wTWFwPyIjZGVmaW5lIFVTRV9CVU1QTUFQIjoiIixyLm5vcm1hbE1hcD8iI2RlZmluZSBVU0VfTk9STUFMTUFQIjoiIixyLm5vcm1hbE1hcCYmci5vYmplY3RTcGFjZU5vcm1hbE1hcD8iI2RlZmluZSBPQkpFQ1RTUEFDRV9OT1JNQUxNQVAiOiIiLHIubm9ybWFsTWFwJiZyLnRhbmdlbnRTcGFjZU5vcm1hbE1hcD8iI2RlZmluZSBUQU5HRU5UU1BBQ0VfTk9STUFMTUFQIjoiIixyLmNsZWFyY29hdE1hcD8iI2RlZmluZSBVU0VfQ0xFQVJDT0FUTUFQIjoiIixyLmNsZWFyY29hdFJvdWdobmVzc01hcD8iI2RlZmluZSBVU0VfQ0xFQVJDT0FUX1JPVUdITkVTU01BUCI6IiIsci5jbGVhcmNvYXROb3JtYWxNYXA/IiNkZWZpbmUgVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVAiOiIiLHIuZGlzcGxhY2VtZW50TWFwJiZyLnN1cHBvcnRzVmVydGV4VGV4dHVyZXM/IiNkZWZpbmUgVVNFX0RJU1BMQUNFTUVOVE1BUCI6IiIsci5zcGVjdWxhck1hcD8iI2RlZmluZSBVU0VfU1BFQ1VMQVJNQVAiOiIiLHIuc3BlY3VsYXJJbnRlbnNpdHlNYXA/IiNkZWZpbmUgVVNFX1NQRUNVTEFSSU5URU5TSVRZTUFQIjoiIixyLnNwZWN1bGFyQ29sb3JNYXA/IiNkZWZpbmUgVVNFX1NQRUNVTEFSQ09MT1JNQVAiOiIiLHIucm91Z2huZXNzTWFwPyIjZGVmaW5lIFVTRV9ST1VHSE5FU1NNQVAiOiIiLHIubWV0YWxuZXNzTWFwPyIjZGVmaW5lIFVTRV9NRVRBTE5FU1NNQVAiOiIiLHIuYWxwaGFNYXA/IiNkZWZpbmUgVVNFX0FMUEhBTUFQIjoiIixyLnRyYW5zbWlzc2lvbj8iI2RlZmluZSBVU0VfVFJBTlNNSVNTSU9OIjoiIixyLnRyYW5zbWlzc2lvbk1hcD8iI2RlZmluZSBVU0VfVFJBTlNNSVNTSU9OTUFQIjoiIixyLnRoaWNrbmVzc01hcD8iI2RlZmluZSBVU0VfVEhJQ0tORVNTTUFQIjoiIixyLnNoZWVuQ29sb3JNYXA/IiNkZWZpbmUgVVNFX1NIRUVOQ09MT1JNQVAiOiIiLHIuc2hlZW5Sb3VnaG5lc3NNYXA/IiNkZWZpbmUgVVNFX1NIRUVOUk9VR0hORVNTTUFQIjoiIixyLnZlcnRleFRhbmdlbnRzPyIjZGVmaW5lIFVTRV9UQU5HRU5UIjoiIixyLnZlcnRleENvbG9ycz8iI2RlZmluZSBVU0VfQ09MT1IiOiIiLHIudmVydGV4QWxwaGFzPyIjZGVmaW5lIFVTRV9DT0xPUl9BTFBIQSI6IiIsci52ZXJ0ZXhVdnM/IiNkZWZpbmUgVVNFX1VWIjoiIixyLnV2c1ZlcnRleE9ubHk/IiNkZWZpbmUgVVZTX1ZFUlRFWF9PTkxZIjoiIixyLmZsYXRTaGFkaW5nPyIjZGVmaW5lIEZMQVRfU0hBREVEIjoiIixyLnNraW5uaW5nPyIjZGVmaW5lIFVTRV9TS0lOTklORyI6IiIsci51c2VWZXJ0ZXhUZXh0dXJlPyIjZGVmaW5lIEJPTkVfVEVYVFVSRSI6IiIsci5tb3JwaFRhcmdldHM/IiNkZWZpbmUgVVNFX01PUlBIVEFSR0VUUyI6IiIsci5tb3JwaE5vcm1hbHMmJnIuZmxhdFNoYWRpbmc9PT0hMT8iI2RlZmluZSBVU0VfTU9SUEhOT1JNQUxTIjoiIixyLm1vcnBoVGFyZ2V0cyYmci5pc1dlYkdMMj8iI2RlZmluZSBNT1JQSFRBUkdFVFNfVEVYVFVSRSI6IiIsci5tb3JwaFRhcmdldHMmJnIuaXNXZWJHTDI/IiNkZWZpbmUgTU9SUEhUQVJHRVRTX0NPVU5UICIrci5tb3JwaFRhcmdldHNDb3VudDoiIixyLmRvdWJsZVNpZGVkPyIjZGVmaW5lIERPVUJMRV9TSURFRCI6IiIsci5mbGlwU2lkZWQ/IiNkZWZpbmUgRkxJUF9TSURFRCI6IiIsci5zaGFkb3dNYXBFbmFibGVkPyIjZGVmaW5lIFVTRV9TSEFET1dNQVAiOiIiLHIuc2hhZG93TWFwRW5hYmxlZD8iI2RlZmluZSAiK2w6IiIsci5zaXplQXR0ZW51YXRpb24/IiNkZWZpbmUgVVNFX1NJWkVBVFRFTlVBVElPTiI6IiIsci5sb2dhcml0aG1pY0RlcHRoQnVmZmVyPyIjZGVmaW5lIFVTRV9MT0dERVBUSEJVRiI6IiIsci5sb2dhcml0aG1pY0RlcHRoQnVmZmVyJiZyLnJlbmRlcmVyRXh0ZW5zaW9uRnJhZ0RlcHRoPyIjZGVmaW5lIFVTRV9MT0dERVBUSEJVRl9FWFQiOiIiLCJ1bmlmb3JtIG1hdDQgbW9kZWxNYXRyaXg7IiwidW5pZm9ybSBtYXQ0IG1vZGVsVmlld01hdHJpeDsiLCJ1bmlmb3JtIG1hdDQgcHJvamVjdGlvbk1hdHJpeDsiLCJ1bmlmb3JtIG1hdDQgdmlld01hdHJpeDsiLCJ1bmlmb3JtIG1hdDMgbm9ybWFsTWF0cml4OyIsInVuaWZvcm0gdmVjMyBjYW1lcmFQb3NpdGlvbjsiLCJ1bmlmb3JtIGJvb2wgaXNPcnRob2dyYXBoaWM7IiwiI2lmZGVmIFVTRV9JTlNUQU5DSU5HIiwiCWF0dHJpYnV0ZSBtYXQ0IGluc3RhbmNlTWF0cml4OyIsIiNlbmRpZiIsIiNpZmRlZiBVU0VfSU5TVEFOQ0lOR19DT0xPUiIsIglhdHRyaWJ1dGUgdmVjMyBpbnN0YW5jZUNvbG9yOyIsIiNlbmRpZiIsImF0dHJpYnV0ZSB2ZWMzIHBvc2l0aW9uOyIsImF0dHJpYnV0ZSB2ZWMzIG5vcm1hbDsiLCJhdHRyaWJ1dGUgdmVjMiB1djsiLCIjaWZkZWYgVVNFX1RBTkdFTlQiLCIJYXR0cmlidXRlIHZlYzQgdGFuZ2VudDsiLCIjZW5kaWYiLCIjaWYgZGVmaW5lZCggVVNFX0NPTE9SX0FMUEhBICkiLCIJYXR0cmlidXRlIHZlYzQgY29sb3I7IiwiI2VsaWYgZGVmaW5lZCggVVNFX0NPTE9SICkiLCIJYXR0cmlidXRlIHZlYzMgY29sb3I7IiwiI2VuZGlmIiwiI2lmICggZGVmaW5lZCggVVNFX01PUlBIVEFSR0VUUyApICYmICEgZGVmaW5lZCggTU9SUEhUQVJHRVRTX1RFWFRVUkUgKSApIiwiCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0MDsiLCIJYXR0cmlidXRlIHZlYzMgbW9ycGhUYXJnZXQxOyIsIglhdHRyaWJ1dGUgdmVjMyBtb3JwaFRhcmdldDI7IiwiCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0MzsiLCIJI2lmZGVmIFVTRV9NT1JQSE5PUk1BTFMiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoTm9ybWFsMDsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoTm9ybWFsMTsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoTm9ybWFsMjsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoTm9ybWFsMzsiLCIJI2Vsc2UiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0NDsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0NTsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0NjsiLCIJCWF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0NzsiLCIJI2VuZGlmIiwiI2VuZGlmIiwiI2lmZGVmIFVTRV9TS0lOTklORyIsIglhdHRyaWJ1dGUgdmVjNCBza2luSW5kZXg7IiwiCWF0dHJpYnV0ZSB2ZWM0IHNraW5XZWlnaHQ7IiwiI2VuZGlmIixgCmBdLmZpbHRlcihCUCkuam9pbihgCmApLF89W2YsVXVlKHIpLCIjZGVmaW5lIFNIQURFUl9OQU1FICIrci5zaGFkZXJOYW1lLHAsci51c2VGb2cmJnIuZm9nPyIjZGVmaW5lIFVTRV9GT0ciOiIiLHIudXNlRm9nJiZyLmZvZ0V4cDI/IiNkZWZpbmUgRk9HX0VYUDIiOiIiLHIubWFwPyIjZGVmaW5lIFVTRV9NQVAiOiIiLHIubWF0Y2FwPyIjZGVmaW5lIFVTRV9NQVRDQVAiOiIiLHIuZW52TWFwPyIjZGVmaW5lIFVTRV9FTlZNQVAiOiIiLHIuZW52TWFwPyIjZGVmaW5lICIrYzoiIixyLmVudk1hcD8iI2RlZmluZSAiK3U6IiIsci5lbnZNYXA/IiNkZWZpbmUgIitoOiIiLHIubGlnaHRNYXA/IiNkZWZpbmUgVVNFX0xJR0hUTUFQIjoiIixyLmFvTWFwPyIjZGVmaW5lIFVTRV9BT01BUCI6IiIsci5lbWlzc2l2ZU1hcD8iI2RlZmluZSBVU0VfRU1JU1NJVkVNQVAiOiIiLHIuYnVtcE1hcD8iI2RlZmluZSBVU0VfQlVNUE1BUCI6IiIsci5ub3JtYWxNYXA/IiNkZWZpbmUgVVNFX05PUk1BTE1BUCI6IiIsci5ub3JtYWxNYXAmJnIub2JqZWN0U3BhY2VOb3JtYWxNYXA/IiNkZWZpbmUgT0JKRUNUU1BBQ0VfTk9STUFMTUFQIjoiIixyLm5vcm1hbE1hcCYmci50YW5nZW50U3BhY2VOb3JtYWxNYXA/IiNkZWZpbmUgVEFOR0VOVFNQQUNFX05PUk1BTE1BUCI6IiIsci5jbGVhcmNvYXQ/IiNkZWZpbmUgVVNFX0NMRUFSQ09BVCI6IiIsci5jbGVhcmNvYXRNYXA/IiNkZWZpbmUgVVNFX0NMRUFSQ09BVE1BUCI6IiIsci5jbGVhcmNvYXRSb3VnaG5lc3NNYXA/IiNkZWZpbmUgVVNFX0NMRUFSQ09BVF9ST1VHSE5FU1NNQVAiOiIiLHIuY2xlYXJjb2F0Tm9ybWFsTWFwPyIjZGVmaW5lIFVTRV9DTEVBUkNPQVRfTk9STUFMTUFQIjoiIixyLnNwZWN1bGFyTWFwPyIjZGVmaW5lIFVTRV9TUEVDVUxBUk1BUCI6IiIsci5zcGVjdWxhckludGVuc2l0eU1hcD8iI2RlZmluZSBVU0VfU1BFQ1VMQVJJTlRFTlNJVFlNQVAiOiIiLHIuc3BlY3VsYXJDb2xvck1hcD8iI2RlZmluZSBVU0VfU1BFQ1VMQVJDT0xPUk1BUCI6IiIsci5yb3VnaG5lc3NNYXA/IiNkZWZpbmUgVVNFX1JPVUdITkVTU01BUCI6IiIsci5tZXRhbG5lc3NNYXA/IiNkZWZpbmUgVVNFX01FVEFMTkVTU01BUCI6IiIsci5hbHBoYU1hcD8iI2RlZmluZSBVU0VfQUxQSEFNQVAiOiIiLHIuYWxwaGFUZXN0PyIjZGVmaW5lIFVTRV9BTFBIQVRFU1QiOiIiLHIuc2hlZW4/IiNkZWZpbmUgVVNFX1NIRUVOIjoiIixyLnNoZWVuQ29sb3JNYXA/IiNkZWZpbmUgVVNFX1NIRUVOQ09MT1JNQVAiOiIiLHIuc2hlZW5Sb3VnaG5lc3NNYXA/IiNkZWZpbmUgVVNFX1NIRUVOUk9VR0hORVNTTUFQIjoiIixyLnRyYW5zbWlzc2lvbj8iI2RlZmluZSBVU0VfVFJBTlNNSVNTSU9OIjoiIixyLnRyYW5zbWlzc2lvbk1hcD8iI2RlZmluZSBVU0VfVFJBTlNNSVNTSU9OTUFQIjoiIixyLnRoaWNrbmVzc01hcD8iI2RlZmluZSBVU0VfVEhJQ0tORVNTTUFQIjoiIixyLmRlY29kZVZpZGVvVGV4dHVyZT8iI2RlZmluZSBERUNPREVfVklERU9fVEVYVFVSRSI6IiIsci52ZXJ0ZXhUYW5nZW50cz8iI2RlZmluZSBVU0VfVEFOR0VOVCI6IiIsci52ZXJ0ZXhDb2xvcnN8fHIuaW5zdGFuY2luZ0NvbG9yPyIjZGVmaW5lIFVTRV9DT0xPUiI6IiIsci52ZXJ0ZXhBbHBoYXM/IiNkZWZpbmUgVVNFX0NPTE9SX0FMUEhBIjoiIixyLnZlcnRleFV2cz8iI2RlZmluZSBVU0VfVVYiOiIiLHIudXZzVmVydGV4T25seT8iI2RlZmluZSBVVlNfVkVSVEVYX09OTFkiOiIiLHIuZ3JhZGllbnRNYXA/IiNkZWZpbmUgVVNFX0dSQURJRU5UTUFQIjoiIixyLmZsYXRTaGFkaW5nPyIjZGVmaW5lIEZMQVRfU0hBREVEIjoiIixyLmRvdWJsZVNpZGVkPyIjZGVmaW5lIERPVUJMRV9TSURFRCI6IiIsci5mbGlwU2lkZWQ/IiNkZWZpbmUgRkxJUF9TSURFRCI6IiIsci5zaGFkb3dNYXBFbmFibGVkPyIjZGVmaW5lIFVTRV9TSEFET1dNQVAiOiIiLHIuc2hhZG93TWFwRW5hYmxlZD8iI2RlZmluZSAiK2w6IiIsci5wcmVtdWx0aXBsaWVkQWxwaGE/IiNkZWZpbmUgUFJFTVVMVElQTElFRF9BTFBIQSI6IiIsci5waHlzaWNhbGx5Q29ycmVjdExpZ2h0cz8iI2RlZmluZSBQSFlTSUNBTExZX0NPUlJFQ1RfTElHSFRTIjoiIixyLmxvZ2FyaXRobWljRGVwdGhCdWZmZXI/IiNkZWZpbmUgVVNFX0xPR0RFUFRIQlVGIjoiIixyLmxvZ2FyaXRobWljRGVwdGhCdWZmZXImJnIucmVuZGVyZXJFeHRlbnNpb25GcmFnRGVwdGg/IiNkZWZpbmUgVVNFX0xPR0RFUFRIQlVGX0VYVCI6IiIsKHIuZXh0ZW5zaW9uU2hhZGVyVGV4dHVyZUxPRHx8ci5lbnZNYXApJiZyLnJlbmRlcmVyRXh0ZW5zaW9uU2hhZGVyVGV4dHVyZUxvZD8iI2RlZmluZSBURVhUVVJFX0xPRF9FWFQiOiIiLCJ1bmlmb3JtIG1hdDQgdmlld01hdHJpeDsiLCJ1bmlmb3JtIHZlYzMgY2FtZXJhUG9zaXRpb247IiwidW5pZm9ybSBib29sIGlzT3J0aG9ncmFwaGljOyIsci50b25lTWFwcGluZyE9PUtkPyIjZGVmaW5lIFRPTkVfTUFQUElORyI6IiIsci50b25lTWFwcGluZyE9PUtkP2hyLnRvbmVtYXBwaW5nX3BhcnNfZnJhZ21lbnQ6IiIsci50b25lTWFwcGluZyE9PUtkP0ptcigidG9uZU1hcHBpbmciLHIudG9uZU1hcHBpbmcpOiIiLHIuZGl0aGVyaW5nPyIjZGVmaW5lIERJVEhFUklORyI6IiIsci5hbHBoYVdyaXRlPyIiOiIjZGVmaW5lIE9QQVFVRSIsaHIuZW5jb2RpbmdzX3BhcnNfZnJhZ21lbnQsWm1yKCJsaW5lYXJUb091dHB1dFRleGVsIixyLm91dHB1dEVuY29kaW5nKSxyLmRlcHRoUGFja2luZz8iI2RlZmluZSBERVBUSF9QQUNLSU5HICIrci5kZXB0aFBhY2tpbmc6IiIsYApgXS5maWx0ZXIoQlApLmpvaW4oYApgKSksYT1RdXQoYSksYT1CdWUoYSxyKSxhPUh1ZShhLHIpLHM9UXV0KHMpLHM9QnVlKHMscikscz1IdWUocyxyKSxhPVZ1ZShhKSxzPVZ1ZShzKSxyLmlzV2ViR0wyJiZyLmlzUmF3U2hhZGVyTWF0ZXJpYWwhPT0hMCYmKHk9YCN2ZXJzaW9uIDMwMCBlcwpgLGc9WyJwcmVjaXNpb24gbWVkaXVtcCBzYW1wbGVyMkRBcnJheTsiLCIjZGVmaW5lIGF0dHJpYnV0ZSBpbiIsIiNkZWZpbmUgdmFyeWluZyBvdXQiLCIjZGVmaW5lIHRleHR1cmUyRCB0ZXh0dXJlIl0uam9pbihgCmApK2AKYCtnLF89WyIjZGVmaW5lIHZhcnlpbmcgaW4iLHIuZ2xzbFZlcnNpb249PT1adXQ/IiI6ImxheW91dChsb2NhdGlvbiA9IDApIG91dCBoaWdocCB2ZWM0IHBjX2ZyYWdDb2xvcjsiLHIuZ2xzbFZlcnNpb249PT1adXQ/IiI6IiNkZWZpbmUgZ2xfRnJhZ0NvbG9yIHBjX2ZyYWdDb2xvciIsIiNkZWZpbmUgZ2xfRnJhZ0RlcHRoRVhUIGdsX0ZyYWdEZXB0aCIsIiNkZWZpbmUgdGV4dHVyZTJEIHRleHR1cmUiLCIjZGVmaW5lIHRleHR1cmVDdWJlIHRleHR1cmUiLCIjZGVmaW5lIHRleHR1cmUyRFByb2ogdGV4dHVyZVByb2oiLCIjZGVmaW5lIHRleHR1cmUyRExvZEVYVCB0ZXh0dXJlTG9kIiwiI2RlZmluZSB0ZXh0dXJlMkRQcm9qTG9kRVhUIHRleHR1cmVQcm9qTG9kIiwiI2RlZmluZSB0ZXh0dXJlQ3ViZUxvZEVYVCB0ZXh0dXJlTG9kIiwiI2RlZmluZSB0ZXh0dXJlMkRHcmFkRVhUIHRleHR1cmVHcmFkIiwiI2RlZmluZSB0ZXh0dXJlMkRQcm9qR3JhZEVYVCB0ZXh0dXJlUHJvakdyYWQiLCIjZGVmaW5lIHRleHR1cmVDdWJlR3JhZEVYVCB0ZXh0dXJlR3JhZCJdLmpvaW4oYApgKStgCmArXyk7bGV0IHg9eStnK2EsYj15K18rcyxTPXp1ZShpLDM1NjMzLHgpLEM9enVlKGksMzU2MzIsYik7aWYoaS5hdHRhY2hTaGFkZXIoZCxTKSxpLmF0dGFjaFNoYWRlcihkLEMpLHIuaW5kZXgwQXR0cmlidXRlTmFtZSE9PXZvaWQgMD9pLmJpbmRBdHRyaWJMb2NhdGlvbihkLDAsci5pbmRleDBBdHRyaWJ1dGVOYW1lKTpyLm1vcnBoVGFyZ2V0cz09PSEwJiZpLmJpbmRBdHRyaWJMb2NhdGlvbihkLDAsInBvc2l0aW9uIiksaS5saW5rUHJvZ3JhbShkKSxlLmRlYnVnLmNoZWNrU2hhZGVyRXJyb3JzKXtsZXQgTz1pLmdldFByb2dyYW1JbmZvTG9nKGQpLnRyaW0oKSxEPWkuZ2V0U2hhZGVySW5mb0xvZyhTKS50cmltKCksQj1pLmdldFNoYWRlckluZm9Mb2coQykudHJpbSgpLEk9ITAsTD0hMDtpZihpLmdldFByb2dyYW1QYXJhbWV0ZXIoZCwzNTcxNCk9PT0hMSl7ST0hMTtsZXQgUj1GdWUoaSxTLCJ2ZXJ0ZXgiKSxGPUZ1ZShpLEMsImZyYWdtZW50Iik7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xQcm9ncmFtOiBTaGFkZXIgRXJyb3IgIitpLmdldEVycm9yKCkrIiAtIFZBTElEQVRFX1NUQVRVUyAiK2kuZ2V0UHJvZ3JhbVBhcmFtZXRlcihkLDM1NzE1KStgCgpQcm9ncmFtIEluZm8gTG9nOiBgK08rYApgK1IrYApgK0YpfWVsc2UgTyE9PSIiP2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xQcm9ncmFtOiBQcm9ncmFtIEluZm8gTG9nOiIsTyk6KEQ9PT0iInx8Qj09PSIiKSYmKEw9ITEpO0wmJih0aGlzLmRpYWdub3N0aWNzPXtydW5uYWJsZTpJLHByb2dyYW1Mb2c6Tyx2ZXJ0ZXhTaGFkZXI6e2xvZzpELHByZWZpeDpnfSxmcmFnbWVudFNoYWRlcjp7bG9nOkIscHJlZml4Ol99fSl9aS5kZWxldGVTaGFkZXIoUyksaS5kZWxldGVTaGFkZXIoQyk7bGV0IFA7dGhpcy5nZXRVbmlmb3Jtcz1mdW5jdGlvbigpe3JldHVybiBQPT09dm9pZCAwJiYoUD1uZXcgQjAoaSxkKSksUH07bGV0IGs7cmV0dXJuIHRoaXMuZ2V0QXR0cmlidXRlcz1mdW5jdGlvbigpe3JldHVybiBrPT09dm9pZCAwJiYoaz1lZ3IoaSxkKSksa30sdGhpcy5kZXN0cm95PWZ1bmN0aW9uKCl7bi5yZWxlYXNlU3RhdGVzT2ZQcm9ncmFtKHRoaXMpLGkuZGVsZXRlUHJvZ3JhbShkKSx0aGlzLnByb2dyYW09dm9pZCAwfSx0aGlzLm5hbWU9ci5zaGFkZXJOYW1lLHRoaXMuaWQ9WG1yKyssdGhpcy5jYWNoZUtleT10LHRoaXMudXNlZFRpbWVzPTEsdGhpcy5wcm9ncmFtPWQsdGhpcy52ZXJ0ZXhTaGFkZXI9Uyx0aGlzLmZyYWdtZW50U2hhZGVyPUMsdGhpc312YXIgZmdyPTAsdGh0PWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5zaGFkZXJDYWNoZT1uZXcgTWFwLHRoaXMubWF0ZXJpYWxDYWNoZT1uZXcgTWFwfXVwZGF0ZSh0KXtsZXQgcj10LnZlcnRleFNoYWRlcixuPXQuZnJhZ21lbnRTaGFkZXIsaT10aGlzLl9nZXRTaGFkZXJTdGFnZShyKSxvPXRoaXMuX2dldFNoYWRlclN0YWdlKG4pLGE9dGhpcy5fZ2V0U2hhZGVyQ2FjaGVGb3JNYXRlcmlhbCh0KTtyZXR1cm4gYS5oYXMoaSk9PT0hMSYmKGEuYWRkKGkpLGkudXNlZFRpbWVzKyspLGEuaGFzKG8pPT09ITEmJihhLmFkZChvKSxvLnVzZWRUaW1lcysrKSx0aGlzfXJlbW92ZSh0KXtsZXQgcj10aGlzLm1hdGVyaWFsQ2FjaGUuZ2V0KHQpO2ZvcihsZXQgbiBvZiByKW4udXNlZFRpbWVzLS0sbi51c2VkVGltZXM9PT0wJiZ0aGlzLnNoYWRlckNhY2hlLmRlbGV0ZShuKTtyZXR1cm4gdGhpcy5tYXRlcmlhbENhY2hlLmRlbGV0ZSh0KSx0aGlzfWdldFZlcnRleFNoYWRlcklEKHQpe3JldHVybiB0aGlzLl9nZXRTaGFkZXJTdGFnZSh0LnZlcnRleFNoYWRlcikuaWR9Z2V0RnJhZ21lbnRTaGFkZXJJRCh0KXtyZXR1cm4gdGhpcy5fZ2V0U2hhZGVyU3RhZ2UodC5mcmFnbWVudFNoYWRlcikuaWR9ZGlzcG9zZSgpe3RoaXMuc2hhZGVyQ2FjaGUuY2xlYXIoKSx0aGlzLm1hdGVyaWFsQ2FjaGUuY2xlYXIoKX1fZ2V0U2hhZGVyQ2FjaGVGb3JNYXRlcmlhbCh0KXtsZXQgcj10aGlzLm1hdGVyaWFsQ2FjaGU7cmV0dXJuIHIuaGFzKHQpPT09ITEmJnIuc2V0KHQsbmV3IFNldCksci5nZXQodCl9X2dldFNoYWRlclN0YWdlKHQpe2xldCByPXRoaXMuc2hhZGVyQ2FjaGU7aWYoci5oYXModCk9PT0hMSl7bGV0IG49bmV3IGVodDtyLnNldCh0LG4pfXJldHVybiByLmdldCh0KX19LGVodD1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuaWQ9ZmdyKyssdGhpcy51c2VkVGltZXM9MH19O2Z1bmN0aW9uIHBncihlLHQscixuLGksbyxhKXtsZXQgcz1uZXcgWDMsbD1uZXcgdGh0LGM9W10sdT1pLmlzV2ViR0wyLGg9aS5sb2dhcml0aG1pY0RlcHRoQnVmZmVyLGY9aS5mbG9hdFZlcnRleFRleHR1cmVzLHA9aS5tYXhWZXJ0ZXhVbmlmb3JtcyxkPWkudmVydGV4VGV4dHVyZXMsZz1pLnByZWNpc2lvbixfPXtNZXNoRGVwdGhNYXRlcmlhbDoiZGVwdGgiLE1lc2hEaXN0YW5jZU1hdGVyaWFsOiJkaXN0YW5jZVJHQkEiLE1lc2hOb3JtYWxNYXRlcmlhbDoibm9ybWFsIixNZXNoQmFzaWNNYXRlcmlhbDoiYmFzaWMiLE1lc2hMYW1iZXJ0TWF0ZXJpYWw6ImxhbWJlcnQiLE1lc2hQaG9uZ01hdGVyaWFsOiJwaG9uZyIsTWVzaFRvb25NYXRlcmlhbDoidG9vbiIsTWVzaFN0YW5kYXJkTWF0ZXJpYWw6InBoeXNpY2FsIixNZXNoUGh5c2ljYWxNYXRlcmlhbDoicGh5c2ljYWwiLE1lc2hNYXRjYXBNYXRlcmlhbDoibWF0Y2FwIixMaW5lQmFzaWNNYXRlcmlhbDoiYmFzaWMiLExpbmVEYXNoZWRNYXRlcmlhbDoiZGFzaGVkIixQb2ludHNNYXRlcmlhbDoicG9pbnRzIixTaGFkb3dNYXRlcmlhbDoic2hhZG93IixTcHJpdGVNYXRlcmlhbDoic3ByaXRlIn07ZnVuY3Rpb24geShJKXtsZXQgUj1JLnNrZWxldG9uLmJvbmVzO2lmKGYpcmV0dXJuIDEwMjQ7e2xldCB6PU1hdGguZmxvb3IoKHAtMjApLzQpLFU9TWF0aC5taW4oeixSLmxlbmd0aCk7cmV0dXJuIFU8Ui5sZW5ndGg/KGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogU2tlbGV0b24gaGFzICIrUi5sZW5ndGgrIiBib25lcy4gVGhpcyBHUFUgc3VwcG9ydHMgIitVKyIuIiksMCk6VX19ZnVuY3Rpb24geChJLEwsUixGLHope2xldCBVPUYuZm9nLFc9SS5pc01lc2hTdGFuZGFyZE1hdGVyaWFsP0YuZW52aXJvbm1lbnQ6bnVsbCxaPShJLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWw/cjp0KS5nZXQoSS5lbnZNYXB8fFcpLHJ0PV9bSS50eXBlXSxvdD16LmlzU2tpbm5lZE1lc2g/eSh6KTowO0kucHJlY2lzaW9uIT09bnVsbCYmKGc9aS5nZXRNYXhQcmVjaXNpb24oSS5wcmVjaXNpb24pLGchPT1JLnByZWNpc2lvbiYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFByb2dyYW0uZ2V0UGFyYW1ldGVyczoiLEkucHJlY2lzaW9uLCJub3Qgc3VwcG9ydGVkLCB1c2luZyIsZywiaW5zdGVhZC4iKSk7bGV0IHN0LFN0LGJ0LE10O2lmKHJ0KXtsZXQgWD1haFtydF07c3Q9WC52ZXJ0ZXhTaGFkZXIsU3Q9WC5mcmFnbWVudFNoYWRlcn1lbHNlIHN0PUkudmVydGV4U2hhZGVyLFN0PUkuZnJhZ21lbnRTaGFkZXIsbC51cGRhdGUoSSksYnQ9bC5nZXRWZXJ0ZXhTaGFkZXJJRChJKSxNdD1sLmdldEZyYWdtZW50U2hhZGVySUQoSSk7bGV0IGx0PWUuZ2V0UmVuZGVyVGFyZ2V0KCksS3Q9SS5hbHBoYVRlc3Q+MCxfdD1JLmNsZWFyY29hdD4wO3JldHVybntpc1dlYkdMMjp1LHNoYWRlcklEOnJ0LHNoYWRlck5hbWU6SS50eXBlLHZlcnRleFNoYWRlcjpzdCxmcmFnbWVudFNoYWRlcjpTdCxkZWZpbmVzOkkuZGVmaW5lcyxjdXN0b21WZXJ0ZXhTaGFkZXJJRDpidCxjdXN0b21GcmFnbWVudFNoYWRlcklEOk10LGlzUmF3U2hhZGVyTWF0ZXJpYWw6SS5pc1Jhd1NoYWRlck1hdGVyaWFsPT09ITAsZ2xzbFZlcnNpb246SS5nbHNsVmVyc2lvbixwcmVjaXNpb246ZyxpbnN0YW5jaW5nOnouaXNJbnN0YW5jZWRNZXNoPT09ITAsaW5zdGFuY2luZ0NvbG9yOnouaXNJbnN0YW5jZWRNZXNoPT09ITAmJnouaW5zdGFuY2VDb2xvciE9PW51bGwsc3VwcG9ydHNWZXJ0ZXhUZXh0dXJlczpkLG91dHB1dEVuY29kaW5nOmx0PT09bnVsbD9lLm91dHB1dEVuY29kaW5nOmx0LmlzWFJSZW5kZXJUYXJnZXQ9PT0hMD9sdC50ZXh0dXJlLmVuY29kaW5nOlFkLG1hcDohIUkubWFwLG1hdGNhcDohIUkubWF0Y2FwLGVudk1hcDohIVosZW52TWFwTW9kZTpaJiZaLm1hcHBpbmcsZW52TWFwQ3ViZVVWOiEhWiYmKFoubWFwcGluZz09PXhNfHxaLm1hcHBpbmc9PT1PNiksbGlnaHRNYXA6ISFJLmxpZ2h0TWFwLGFvTWFwOiEhSS5hb01hcCxlbWlzc2l2ZU1hcDohIUkuZW1pc3NpdmVNYXAsYnVtcE1hcDohIUkuYnVtcE1hcCxub3JtYWxNYXA6ISFJLm5vcm1hbE1hcCxvYmplY3RTcGFjZU5vcm1hbE1hcDpJLm5vcm1hbE1hcFR5cGU9PT1JZmUsdGFuZ2VudFNwYWNlTm9ybWFsTWFwOkkubm9ybWFsTWFwVHlwZT09PWF4LGRlY29kZVZpZGVvVGV4dHVyZTohIUkubWFwJiZJLm1hcC5pc1ZpZGVvVGV4dHVyZT09PSEwJiZJLm1hcC5lbmNvZGluZz09PVluLGNsZWFyY29hdDpfdCxjbGVhcmNvYXRNYXA6X3QmJiEhSS5jbGVhcmNvYXRNYXAsY2xlYXJjb2F0Um91Z2huZXNzTWFwOl90JiYhIUkuY2xlYXJjb2F0Um91Z2huZXNzTWFwLGNsZWFyY29hdE5vcm1hbE1hcDpfdCYmISFJLmNsZWFyY29hdE5vcm1hbE1hcCxkaXNwbGFjZW1lbnRNYXA6ISFJLmRpc3BsYWNlbWVudE1hcCxyb3VnaG5lc3NNYXA6ISFJLnJvdWdobmVzc01hcCxtZXRhbG5lc3NNYXA6ISFJLm1ldGFsbmVzc01hcCxzcGVjdWxhck1hcDohIUkuc3BlY3VsYXJNYXAsc3BlY3VsYXJJbnRlbnNpdHlNYXA6ISFJLnNwZWN1bGFySW50ZW5zaXR5TWFwLHNwZWN1bGFyQ29sb3JNYXA6ISFJLnNwZWN1bGFyQ29sb3JNYXAsYWxwaGFNYXA6ISFJLmFscGhhTWFwLGFscGhhVGVzdDpLdCxhbHBoYVdyaXRlOkkuYWxwaGFXcml0ZXx8SS50cmFuc3BhcmVudCxncmFkaWVudE1hcDohIUkuZ3JhZGllbnRNYXAsc2hlZW46SS5zaGVlbj4wLHNoZWVuQ29sb3JNYXA6ISFJLnNoZWVuQ29sb3JNYXAsc2hlZW5Sb3VnaG5lc3NNYXA6ISFJLnNoZWVuUm91Z2huZXNzTWFwLHRyYW5zbWlzc2lvbjpJLnRyYW5zbWlzc2lvbj4wLHRyYW5zbWlzc2lvbk1hcDohIUkudHJhbnNtaXNzaW9uTWFwLHRoaWNrbmVzc01hcDohIUkudGhpY2tuZXNzTWFwLGNvbWJpbmU6SS5jb21iaW5lLHZlcnRleFRhbmdlbnRzOiEhSS5ub3JtYWxNYXAmJiEhei5nZW9tZXRyeSYmISF6Lmdlb21ldHJ5LmF0dHJpYnV0ZXMudGFuZ2VudCx2ZXJ0ZXhDb2xvcnM6SS52ZXJ0ZXhDb2xvcnMsdmVydGV4QWxwaGFzOkkudmVydGV4Q29sb3JzPT09ITAmJiEhei5nZW9tZXRyeSYmISF6Lmdlb21ldHJ5LmF0dHJpYnV0ZXMuY29sb3ImJnouZ2VvbWV0cnkuYXR0cmlidXRlcy5jb2xvci5pdGVtU2l6ZT09PTQsdmVydGV4VXZzOiEhSS5tYXB8fCEhSS5idW1wTWFwfHwhIUkubm9ybWFsTWFwfHwhIUkuc3BlY3VsYXJNYXB8fCEhSS5hbHBoYU1hcHx8ISFJLmVtaXNzaXZlTWFwfHwhIUkucm91Z2huZXNzTWFwfHwhIUkubWV0YWxuZXNzTWFwfHwhIUkuY2xlYXJjb2F0TWFwfHwhIUkuY2xlYXJjb2F0Um91Z2huZXNzTWFwfHwhIUkuY2xlYXJjb2F0Tm9ybWFsTWFwfHwhIUkuZGlzcGxhY2VtZW50TWFwfHwhIUkudHJhbnNtaXNzaW9uTWFwfHwhIUkudGhpY2tuZXNzTWFwfHwhIUkuc3BlY3VsYXJJbnRlbnNpdHlNYXB8fCEhSS5zcGVjdWxhckNvbG9yTWFwfHwhIUkuc2hlZW5Db2xvck1hcHx8ISFJLnNoZWVuUm91Z2huZXNzTWFwLHV2c1ZlcnRleE9ubHk6ISghIUkubWFwfHwhIUkuYnVtcE1hcHx8ISFJLm5vcm1hbE1hcHx8ISFJLnNwZWN1bGFyTWFwfHwhIUkuYWxwaGFNYXB8fCEhSS5lbWlzc2l2ZU1hcHx8ISFJLnJvdWdobmVzc01hcHx8ISFJLm1ldGFsbmVzc01hcHx8ISFJLmNsZWFyY29hdE5vcm1hbE1hcHx8SS50cmFuc21pc3Npb24+MHx8ISFJLnRyYW5zbWlzc2lvbk1hcHx8ISFJLnRoaWNrbmVzc01hcHx8ISFJLnNwZWN1bGFySW50ZW5zaXR5TWFwfHwhIUkuc3BlY3VsYXJDb2xvck1hcHx8SS5zaGVlbj4wfHwhIUkuc2hlZW5Db2xvck1hcHx8ISFJLnNoZWVuUm91Z2huZXNzTWFwKSYmISFJLmRpc3BsYWNlbWVudE1hcCxmb2c6ISFVLHVzZUZvZzpJLmZvZyxmb2dFeHAyOlUmJlUuaXNGb2dFeHAyLGZsYXRTaGFkaW5nOiEhSS5mbGF0U2hhZGluZyxzaXplQXR0ZW51YXRpb246SS5zaXplQXR0ZW51YXRpb24sbG9nYXJpdGhtaWNEZXB0aEJ1ZmZlcjpoLHNraW5uaW5nOnouaXNTa2lubmVkTWVzaD09PSEwJiZvdD4wLG1heEJvbmVzOm90LHVzZVZlcnRleFRleHR1cmU6Zixtb3JwaFRhcmdldHM6ISF6Lmdlb21ldHJ5JiYhIXouZ2VvbWV0cnkubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uLG1vcnBoTm9ybWFsczohIXouZ2VvbWV0cnkmJiEhei5nZW9tZXRyeS5tb3JwaEF0dHJpYnV0ZXMubm9ybWFsLG1vcnBoVGFyZ2V0c0NvdW50OiEhei5nZW9tZXRyeSYmISF6Lmdlb21ldHJ5Lm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbj96Lmdlb21ldHJ5Lm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbi5sZW5ndGg6MCxudW1EaXJMaWdodHM6TC5kaXJlY3Rpb25hbC5sZW5ndGgsbnVtUG9pbnRMaWdodHM6TC5wb2ludC5sZW5ndGgsbnVtU3BvdExpZ2h0czpMLnNwb3QubGVuZ3RoLG51bVJlY3RBcmVhTGlnaHRzOkwucmVjdEFyZWEubGVuZ3RoLG51bUhlbWlMaWdodHM6TC5oZW1pLmxlbmd0aCxudW1EaXJMaWdodFNoYWRvd3M6TC5kaXJlY3Rpb25hbFNoYWRvd01hcC5sZW5ndGgsbnVtUG9pbnRMaWdodFNoYWRvd3M6TC5wb2ludFNoYWRvd01hcC5sZW5ndGgsbnVtU3BvdExpZ2h0U2hhZG93czpMLnNwb3RTaGFkb3dNYXAubGVuZ3RoLG51bUNsaXBwaW5nUGxhbmVzOmEubnVtUGxhbmVzLG51bUNsaXBJbnRlcnNlY3Rpb246YS5udW1JbnRlcnNlY3Rpb24sZGl0aGVyaW5nOkkuZGl0aGVyaW5nLHNoYWRvd01hcEVuYWJsZWQ6ZS5zaGFkb3dNYXAuZW5hYmxlZCYmUi5sZW5ndGg+MCxzaGFkb3dNYXBUeXBlOmUuc2hhZG93TWFwLnR5cGUsdG9uZU1hcHBpbmc6SS50b25lTWFwcGVkP2UudG9uZU1hcHBpbmc6S2QscGh5c2ljYWxseUNvcnJlY3RMaWdodHM6ZS5waHlzaWNhbGx5Q29ycmVjdExpZ2h0cyxwcmVtdWx0aXBsaWVkQWxwaGE6SS5wcmVtdWx0aXBsaWVkQWxwaGEsZG91YmxlU2lkZWQ6SS5zaWRlPT09THYsZmxpcFNpZGVkOkkuc2lkZT09PUlpLGRlcHRoUGFja2luZzpJLmRlcHRoUGFja2luZyE9PXZvaWQgMD9JLmRlcHRoUGFja2luZzohMSxpbmRleDBBdHRyaWJ1dGVOYW1lOkkuaW5kZXgwQXR0cmlidXRlTmFtZSxleHRlbnNpb25EZXJpdmF0aXZlczpJLmV4dGVuc2lvbnMmJkkuZXh0ZW5zaW9ucy5kZXJpdmF0aXZlcyxleHRlbnNpb25GcmFnRGVwdGg6SS5leHRlbnNpb25zJiZJLmV4dGVuc2lvbnMuZnJhZ0RlcHRoLGV4dGVuc2lvbkRyYXdCdWZmZXJzOkkuZXh0ZW5zaW9ucyYmSS5leHRlbnNpb25zLmRyYXdCdWZmZXJzLGV4dGVuc2lvblNoYWRlclRleHR1cmVMT0Q6SS5leHRlbnNpb25zJiZJLmV4dGVuc2lvbnMuc2hhZGVyVGV4dHVyZUxPRCxyZW5kZXJlckV4dGVuc2lvbkZyYWdEZXB0aDp1fHxuLmhhcygiRVhUX2ZyYWdfZGVwdGgiKSxyZW5kZXJlckV4dGVuc2lvbkRyYXdCdWZmZXJzOnV8fG4uaGFzKCJXRUJHTF9kcmF3X2J1ZmZlcnMiKSxyZW5kZXJlckV4dGVuc2lvblNoYWRlclRleHR1cmVMb2Q6dXx8bi5oYXMoIkVYVF9zaGFkZXJfdGV4dHVyZV9sb2QiKSxjdXN0b21Qcm9ncmFtQ2FjaGVLZXk6SS5jdXN0b21Qcm9ncmFtQ2FjaGVLZXkoKX19ZnVuY3Rpb24gYihJKXtsZXQgTD1bXTtpZihJLnNoYWRlcklEP0wucHVzaChJLnNoYWRlcklEKTooTC5wdXNoKEkuY3VzdG9tVmVydGV4U2hhZGVySUQpLEwucHVzaChJLmN1c3RvbUZyYWdtZW50U2hhZGVySUQpKSxJLmRlZmluZXMhPT12b2lkIDApZm9yKGxldCBSIGluIEkuZGVmaW5lcylMLnB1c2goUiksTC5wdXNoKEkuZGVmaW5lc1tSXSk7cmV0dXJuIEkuaXNSYXdTaGFkZXJNYXRlcmlhbD09PSExJiYoUyhMLEkpLEMoTCxJKSxMLnB1c2goZS5vdXRwdXRFbmNvZGluZykpLEwucHVzaChJLmN1c3RvbVByb2dyYW1DYWNoZUtleSksTC5qb2luKCl9ZnVuY3Rpb24gUyhJLEwpe0kucHVzaChMLnByZWNpc2lvbiksSS5wdXNoKEwub3V0cHV0RW5jb2RpbmcpLEkucHVzaChMLmVudk1hcE1vZGUpLEkucHVzaChMLmNvbWJpbmUpLEkucHVzaChMLnZlcnRleFV2cyksSS5wdXNoKEwuZm9nRXhwMiksSS5wdXNoKEwuc2l6ZUF0dGVudWF0aW9uKSxJLnB1c2goTC5tYXhCb25lcyksSS5wdXNoKEwubW9ycGhUYXJnZXRzQ291bnQpLEkucHVzaChMLm51bURpckxpZ2h0cyksSS5wdXNoKEwubnVtUG9pbnRMaWdodHMpLEkucHVzaChMLm51bVNwb3RMaWdodHMpLEkucHVzaChMLm51bUhlbWlMaWdodHMpLEkucHVzaChMLm51bVJlY3RBcmVhTGlnaHRzKSxJLnB1c2goTC5udW1EaXJMaWdodFNoYWRvd3MpLEkucHVzaChMLm51bVBvaW50TGlnaHRTaGFkb3dzKSxJLnB1c2goTC5udW1TcG90TGlnaHRTaGFkb3dzKSxJLnB1c2goTC5zaGFkb3dNYXBUeXBlKSxJLnB1c2goTC50b25lTWFwcGluZyksSS5wdXNoKEwubnVtQ2xpcHBpbmdQbGFuZXMpLEkucHVzaChMLm51bUNsaXBJbnRlcnNlY3Rpb24pLEkucHVzaChMLmFscGhhV3JpdGUpfWZ1bmN0aW9uIEMoSSxMKXtzLmRpc2FibGVBbGwoKSxMLmlzV2ViR0wyJiZzLmVuYWJsZSgwKSxMLnN1cHBvcnRzVmVydGV4VGV4dHVyZXMmJnMuZW5hYmxlKDEpLEwuaW5zdGFuY2luZyYmcy5lbmFibGUoMiksTC5pbnN0YW5jaW5nQ29sb3ImJnMuZW5hYmxlKDMpLEwubWFwJiZzLmVuYWJsZSg0KSxMLm1hdGNhcCYmcy5lbmFibGUoNSksTC5lbnZNYXAmJnMuZW5hYmxlKDYpLEwuZW52TWFwQ3ViZVVWJiZzLmVuYWJsZSg3KSxMLmxpZ2h0TWFwJiZzLmVuYWJsZSg4KSxMLmFvTWFwJiZzLmVuYWJsZSg5KSxMLmVtaXNzaXZlTWFwJiZzLmVuYWJsZSgxMCksTC5idW1wTWFwJiZzLmVuYWJsZSgxMSksTC5ub3JtYWxNYXAmJnMuZW5hYmxlKDEyKSxMLm9iamVjdFNwYWNlTm9ybWFsTWFwJiZzLmVuYWJsZSgxMyksTC50YW5nZW50U3BhY2VOb3JtYWxNYXAmJnMuZW5hYmxlKDE0KSxMLmNsZWFyY29hdCYmcy5lbmFibGUoMTUpLEwuY2xlYXJjb2F0TWFwJiZzLmVuYWJsZSgxNiksTC5jbGVhcmNvYXRSb3VnaG5lc3NNYXAmJnMuZW5hYmxlKDE3KSxMLmNsZWFyY29hdE5vcm1hbE1hcCYmcy5lbmFibGUoMTgpLEwuZGlzcGxhY2VtZW50TWFwJiZzLmVuYWJsZSgxOSksTC5zcGVjdWxhck1hcCYmcy5lbmFibGUoMjApLEwucm91Z2huZXNzTWFwJiZzLmVuYWJsZSgyMSksTC5tZXRhbG5lc3NNYXAmJnMuZW5hYmxlKDIyKSxMLmdyYWRpZW50TWFwJiZzLmVuYWJsZSgyMyksTC5hbHBoYU1hcCYmcy5lbmFibGUoMjQpLEwuYWxwaGFUZXN0JiZzLmVuYWJsZSgyNSksTC52ZXJ0ZXhDb2xvcnMmJnMuZW5hYmxlKDI2KSxMLnZlcnRleEFscGhhcyYmcy5lbmFibGUoMjcpLEwudmVydGV4VXZzJiZzLmVuYWJsZSgyOCksTC52ZXJ0ZXhUYW5nZW50cyYmcy5lbmFibGUoMjkpLEwudXZzVmVydGV4T25seSYmcy5lbmFibGUoMzApLEwuZm9nJiZzLmVuYWJsZSgzMSksSS5wdXNoKHMubWFzaykscy5kaXNhYmxlQWxsKCksTC51c2VGb2cmJnMuZW5hYmxlKDApLEwuZmxhdFNoYWRpbmcmJnMuZW5hYmxlKDEpLEwubG9nYXJpdGhtaWNEZXB0aEJ1ZmZlciYmcy5lbmFibGUoMiksTC5za2lubmluZyYmcy5lbmFibGUoMyksTC51c2VWZXJ0ZXhUZXh0dXJlJiZzLmVuYWJsZSg0KSxMLm1vcnBoVGFyZ2V0cyYmcy5lbmFibGUoNSksTC5tb3JwaE5vcm1hbHMmJnMuZW5hYmxlKDYpLEwucHJlbXVsdGlwbGllZEFscGhhJiZzLmVuYWJsZSg3KSxMLnNoYWRvd01hcEVuYWJsZWQmJnMuZW5hYmxlKDgpLEwucGh5c2ljYWxseUNvcnJlY3RMaWdodHMmJnMuZW5hYmxlKDkpLEwuZG91YmxlU2lkZWQmJnMuZW5hYmxlKDEwKSxMLmZsaXBTaWRlZCYmcy5lbmFibGUoMTEpLEwuZGVwdGhQYWNraW5nJiZzLmVuYWJsZSgxMiksTC5kaXRoZXJpbmcmJnMuZW5hYmxlKDEzKSxMLnNwZWN1bGFySW50ZW5zaXR5TWFwJiZzLmVuYWJsZSgxNCksTC5zcGVjdWxhckNvbG9yTWFwJiZzLmVuYWJsZSgxNSksTC50cmFuc21pc3Npb24mJnMuZW5hYmxlKDE2KSxMLnRyYW5zbWlzc2lvbk1hcCYmcy5lbmFibGUoMTcpLEwudGhpY2tuZXNzTWFwJiZzLmVuYWJsZSgxOCksTC5zaGVlbiYmcy5lbmFibGUoMTkpLEwuc2hlZW5Db2xvck1hcCYmcy5lbmFibGUoMjApLEwuc2hlZW5Sb3VnaG5lc3NNYXAmJnMuZW5hYmxlKDIxKSxMLmRlY29kZVZpZGVvVGV4dHVyZSYmcy5lbmFibGUoMjIpLEkucHVzaChzLm1hc2spfWZ1bmN0aW9uIFAoSSl7bGV0IEw9X1tJLnR5cGVdLFI7aWYoTCl7bGV0IEY9YWhbTF07Uj1PZmUuY2xvbmUoRi51bmlmb3Jtcyl9ZWxzZSBSPUkudW5pZm9ybXM7cmV0dXJuIFJ9ZnVuY3Rpb24gayhJLEwpe2xldCBSO2ZvcihsZXQgRj0wLHo9Yy5sZW5ndGg7Rjx6O0YrKyl7bGV0IFU9Y1tGXTtpZihVLmNhY2hlS2V5PT09TCl7Uj1VLCsrUi51c2VkVGltZXM7YnJlYWt9fXJldHVybiBSPT09dm9pZCAwJiYoUj1uZXcgaGdyKGUsTCxJLG8pLGMucHVzaChSKSksUn1mdW5jdGlvbiBPKEkpe2lmKC0tSS51c2VkVGltZXM9PT0wKXtsZXQgTD1jLmluZGV4T2YoSSk7Y1tMXT1jW2MubGVuZ3RoLTFdLGMucG9wKCksSS5kZXN0cm95KCl9fWZ1bmN0aW9uIEQoSSl7bC5yZW1vdmUoSSl9ZnVuY3Rpb24gQigpe2wuZGlzcG9zZSgpfXJldHVybntnZXRQYXJhbWV0ZXJzOngsZ2V0UHJvZ3JhbUNhY2hlS2V5OmIsZ2V0VW5pZm9ybXM6UCxhY3F1aXJlUHJvZ3JhbTprLHJlbGVhc2VQcm9ncmFtOk8scmVsZWFzZVNoYWRlckNhY2hlOkQscHJvZ3JhbXM6YyxkaXNwb3NlOkJ9fWZ1bmN0aW9uIGRncigpe2xldCBlPW5ldyBXZWFrTWFwO2Z1bmN0aW9uIHQobyl7bGV0IGE9ZS5nZXQobyk7cmV0dXJuIGE9PT12b2lkIDAmJihhPXt9LGUuc2V0KG8sYSkpLGF9ZnVuY3Rpb24gcihvKXtlLmRlbGV0ZShvKX1mdW5jdGlvbiBuKG8sYSxzKXtlLmdldChvKVthXT1zfWZ1bmN0aW9uIGkoKXtlPW5ldyBXZWFrTWFwfXJldHVybntnZXQ6dCxyZW1vdmU6cix1cGRhdGU6bixkaXNwb3NlOml9fWZ1bmN0aW9uIG1ncihlLHQpe3JldHVybiBlLmdyb3VwT3JkZXIhPT10Lmdyb3VwT3JkZXI/ZS5ncm91cE9yZGVyLXQuZ3JvdXBPcmRlcjplLnJlbmRlck9yZGVyIT09dC5yZW5kZXJPcmRlcj9lLnJlbmRlck9yZGVyLXQucmVuZGVyT3JkZXI6ZS5tYXRlcmlhbC5pZCE9PXQubWF0ZXJpYWwuaWQ/ZS5tYXRlcmlhbC5pZC10Lm1hdGVyaWFsLmlkOmUueiE9PXQuej9lLnotdC56OmUuaWQtdC5pZH1mdW5jdGlvbiBxdWUoZSx0KXtyZXR1cm4gZS5ncm91cE9yZGVyIT09dC5ncm91cE9yZGVyP2UuZ3JvdXBPcmRlci10Lmdyb3VwT3JkZXI6ZS5yZW5kZXJPcmRlciE9PXQucmVuZGVyT3JkZXI/ZS5yZW5kZXJPcmRlci10LnJlbmRlck9yZGVyOmUueiE9PXQuej90LnotZS56OmUuaWQtdC5pZH1mdW5jdGlvbiBHdWUoKXtsZXQgZT1bXSx0PTAscj1bXSxuPVtdLGk9W107ZnVuY3Rpb24gbygpe3Q9MCxyLmxlbmd0aD0wLG4ubGVuZ3RoPTAsaS5sZW5ndGg9MH1mdW5jdGlvbiBhKGgsZixwLGQsZyxfKXtsZXQgeT1lW3RdO3JldHVybiB5PT09dm9pZCAwPyh5PXtpZDpoLmlkLG9iamVjdDpoLGdlb21ldHJ5OmYsbWF0ZXJpYWw6cCxncm91cE9yZGVyOmQscmVuZGVyT3JkZXI6aC5yZW5kZXJPcmRlcix6OmcsZ3JvdXA6X30sZVt0XT15KTooeS5pZD1oLmlkLHkub2JqZWN0PWgseS5nZW9tZXRyeT1mLHkubWF0ZXJpYWw9cCx5Lmdyb3VwT3JkZXI9ZCx5LnJlbmRlck9yZGVyPWgucmVuZGVyT3JkZXIseS56PWcseS5ncm91cD1fKSx0KysseX1mdW5jdGlvbiBzKGgsZixwLGQsZyxfKXtsZXQgeT1hKGgsZixwLGQsZyxfKTtwLnRyYW5zbWlzc2lvbj4wP24ucHVzaCh5KTpwLnRyYW5zcGFyZW50PT09ITA/aS5wdXNoKHkpOnIucHVzaCh5KX1mdW5jdGlvbiBsKGgsZixwLGQsZyxfKXtsZXQgeT1hKGgsZixwLGQsZyxfKTtwLnRyYW5zbWlzc2lvbj4wP24udW5zaGlmdCh5KTpwLnRyYW5zcGFyZW50PT09ITA/aS51bnNoaWZ0KHkpOnIudW5zaGlmdCh5KX1mdW5jdGlvbiBjKGgsZil7ci5sZW5ndGg+MSYmci5zb3J0KGh8fG1nciksbi5sZW5ndGg+MSYmbi5zb3J0KGZ8fHF1ZSksaS5sZW5ndGg+MSYmaS5zb3J0KGZ8fHF1ZSl9ZnVuY3Rpb24gdSgpe2ZvcihsZXQgaD10LGY9ZS5sZW5ndGg7aDxmO2grKyl7bGV0IHA9ZVtoXTtpZihwLmlkPT09bnVsbClicmVhaztwLmlkPW51bGwscC5vYmplY3Q9bnVsbCxwLmdlb21ldHJ5PW51bGwscC5tYXRlcmlhbD1udWxsLHAuZ3JvdXA9bnVsbH19cmV0dXJue29wYXF1ZTpyLHRyYW5zbWlzc2l2ZTpuLHRyYW5zcGFyZW50OmksaW5pdDpvLHB1c2g6cyx1bnNoaWZ0OmwsZmluaXNoOnUsc29ydDpjfX1mdW5jdGlvbiBnZ3IoKXtsZXQgZT1uZXcgV2Vha01hcDtmdW5jdGlvbiB0KG4saSl7bGV0IG87cmV0dXJuIGUuaGFzKG4pPT09ITE/KG89bmV3IEd1ZSxlLnNldChuLFtvXSkpOmk+PWUuZ2V0KG4pLmxlbmd0aD8obz1uZXcgR3VlLGUuZ2V0KG4pLnB1c2gobykpOm89ZS5nZXQobilbaV0sb31mdW5jdGlvbiByKCl7ZT1uZXcgV2Vha01hcH1yZXR1cm57Z2V0OnQsZGlzcG9zZTpyfX1mdW5jdGlvbiBfZ3IoKXtsZXQgZT17fTtyZXR1cm57Z2V0OmZ1bmN0aW9uKHQpe2lmKGVbdC5pZF0hPT12b2lkIDApcmV0dXJuIGVbdC5pZF07bGV0IHI7c3dpdGNoKHQudHlwZSl7Y2FzZSJEaXJlY3Rpb25hbExpZ2h0IjpyPXtkaXJlY3Rpb246bmV3IGosY29sb3I6bmV3IG5lfTticmVhaztjYXNlIlNwb3RMaWdodCI6cj17cG9zaXRpb246bmV3IGosZGlyZWN0aW9uOm5ldyBqLGNvbG9yOm5ldyBuZSxkaXN0YW5jZTowLGNvbmVDb3M6MCxwZW51bWJyYUNvczowLGRlY2F5OjB9O2JyZWFrO2Nhc2UiUG9pbnRMaWdodCI6cj17cG9zaXRpb246bmV3IGosY29sb3I6bmV3IG5lLGRpc3RhbmNlOjAsZGVjYXk6MH07YnJlYWs7Y2FzZSJIZW1pc3BoZXJlTGlnaHQiOnI9e2RpcmVjdGlvbjpuZXcgaixza3lDb2xvcjpuZXcgbmUsZ3JvdW5kQ29sb3I6bmV3IG5lfTticmVhaztjYXNlIlJlY3RBcmVhTGlnaHQiOnI9e2NvbG9yOm5ldyBuZSxwb3NpdGlvbjpuZXcgaixoYWxmV2lkdGg6bmV3IGosaGFsZkhlaWdodDpuZXcgan07YnJlYWt9cmV0dXJuIGVbdC5pZF09cixyfX19ZnVuY3Rpb24geWdyKCl7bGV0IGU9e307cmV0dXJue2dldDpmdW5jdGlvbih0KXtpZihlW3QuaWRdIT09dm9pZCAwKXJldHVybiBlW3QuaWRdO2xldCByO3N3aXRjaCh0LnR5cGUpe2Nhc2UiRGlyZWN0aW9uYWxMaWdodCI6cj17c2hhZG93QmlhczowLHNoYWRvd05vcm1hbEJpYXM6MCxzaGFkb3dSYWRpdXM6MSxzaGFkb3dNYXBTaXplOm5ldyBMdH07YnJlYWs7Y2FzZSJTcG90TGlnaHQiOnI9e3NoYWRvd0JpYXM6MCxzaGFkb3dOb3JtYWxCaWFzOjAsc2hhZG93UmFkaXVzOjEsc2hhZG93TWFwU2l6ZTpuZXcgTHR9O2JyZWFrO2Nhc2UiUG9pbnRMaWdodCI6cj17c2hhZG93QmlhczowLHNoYWRvd05vcm1hbEJpYXM6MCxzaGFkb3dSYWRpdXM6MSxzaGFkb3dNYXBTaXplOm5ldyBMdCxzaGFkb3dDYW1lcmFOZWFyOjEsc2hhZG93Q2FtZXJhRmFyOjFlM307YnJlYWt9cmV0dXJuIGVbdC5pZF09cixyfX19dmFyIHZncj0wO2Z1bmN0aW9uIHhncihlLHQpe3JldHVybih0LmNhc3RTaGFkb3c/MTowKS0oZS5jYXN0U2hhZG93PzE6MCl9ZnVuY3Rpb24gYmdyKGUsdCl7bGV0IHI9bmV3IF9ncixuPXlncigpLGk9e3ZlcnNpb246MCxoYXNoOntkaXJlY3Rpb25hbExlbmd0aDotMSxwb2ludExlbmd0aDotMSxzcG90TGVuZ3RoOi0xLHJlY3RBcmVhTGVuZ3RoOi0xLGhlbWlMZW5ndGg6LTEsbnVtRGlyZWN0aW9uYWxTaGFkb3dzOi0xLG51bVBvaW50U2hhZG93czotMSxudW1TcG90U2hhZG93czotMX0sYW1iaWVudDpbMCwwLDBdLHByb2JlOltdLGRpcmVjdGlvbmFsOltdLGRpcmVjdGlvbmFsU2hhZG93OltdLGRpcmVjdGlvbmFsU2hhZG93TWFwOltdLGRpcmVjdGlvbmFsU2hhZG93TWF0cml4OltdLHNwb3Q6W10sc3BvdFNoYWRvdzpbXSxzcG90U2hhZG93TWFwOltdLHNwb3RTaGFkb3dNYXRyaXg6W10scmVjdEFyZWE6W10scmVjdEFyZWFMVEMxOm51bGwscmVjdEFyZWFMVEMyOm51bGwscG9pbnQ6W10scG9pbnRTaGFkb3c6W10scG9pbnRTaGFkb3dNYXA6W10scG9pbnRTaGFkb3dNYXRyaXg6W10saGVtaTpbXX07Zm9yKGxldCB1PTA7dTw5O3UrKylpLnByb2JlLnB1c2gobmV3IGopO2xldCBvPW5ldyBqLGE9bmV3IE1lLHM9bmV3IE1lO2Z1bmN0aW9uIGwodSxoKXtsZXQgZj0wLHA9MCxkPTA7Zm9yKGxldCBEPTA7RDw5O0QrKylpLnByb2JlW0RdLnNldCgwLDAsMCk7bGV0IGc9MCxfPTAseT0wLHg9MCxiPTAsUz0wLEM9MCxQPTA7dS5zb3J0KHhncik7bGV0IGs9aCE9PSEwP01hdGguUEk6MTtmb3IobGV0IEQ9MCxCPXUubGVuZ3RoO0Q8QjtEKyspe2xldCBJPXVbRF0sTD1JLmNvbG9yLFI9SS5pbnRlbnNpdHksRj1JLmRpc3RhbmNlLHo9SS5zaGFkb3cmJkkuc2hhZG93Lm1hcD9JLnNoYWRvdy5tYXAudGV4dHVyZTpudWxsO2lmKEkuaXNBbWJpZW50TGlnaHQpZis9TC5yKlIqayxwKz1MLmcqUiprLGQrPUwuYipSKms7ZWxzZSBpZihJLmlzTGlnaHRQcm9iZSlmb3IobGV0IFU9MDtVPDk7VSsrKWkucHJvYmVbVV0uYWRkU2NhbGVkVmVjdG9yKEkuc2guY29lZmZpY2llbnRzW1VdLFIpO2Vsc2UgaWYoSS5pc0RpcmVjdGlvbmFsTGlnaHQpe2xldCBVPXIuZ2V0KEkpO2lmKFUuY29sb3IuY29weShJLmNvbG9yKS5tdWx0aXBseVNjYWxhcihJLmludGVuc2l0eSprKSxJLmNhc3RTaGFkb3cpe2xldCBXPUkuc2hhZG93LFo9bi5nZXQoSSk7Wi5zaGFkb3dCaWFzPVcuYmlhcyxaLnNoYWRvd05vcm1hbEJpYXM9Vy5ub3JtYWxCaWFzLFouc2hhZG93UmFkaXVzPVcucmFkaXVzLFouc2hhZG93TWFwU2l6ZT1XLm1hcFNpemUsaS5kaXJlY3Rpb25hbFNoYWRvd1tnXT1aLGkuZGlyZWN0aW9uYWxTaGFkb3dNYXBbZ109eixpLmRpcmVjdGlvbmFsU2hhZG93TWF0cml4W2ddPUkuc2hhZG93Lm1hdHJpeCxTKyt9aS5kaXJlY3Rpb25hbFtnXT1VLGcrK31lbHNlIGlmKEkuaXNTcG90TGlnaHQpe2xldCBVPXIuZ2V0KEkpO2lmKFUucG9zaXRpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKEkubWF0cml4V29ybGQpLFUuY29sb3IuY29weShMKS5tdWx0aXBseVNjYWxhcihSKmspLFUuZGlzdGFuY2U9RixVLmNvbmVDb3M9TWF0aC5jb3MoSS5hbmdsZSksVS5wZW51bWJyYUNvcz1NYXRoLmNvcyhJLmFuZ2xlKigxLUkucGVudW1icmEpKSxVLmRlY2F5PUkuZGVjYXksSS5jYXN0U2hhZG93KXtsZXQgVz1JLnNoYWRvdyxaPW4uZ2V0KEkpO1ouc2hhZG93Qmlhcz1XLmJpYXMsWi5zaGFkb3dOb3JtYWxCaWFzPVcubm9ybWFsQmlhcyxaLnNoYWRvd1JhZGl1cz1XLnJhZGl1cyxaLnNoYWRvd01hcFNpemU9Vy5tYXBTaXplLGkuc3BvdFNoYWRvd1t5XT1aLGkuc3BvdFNoYWRvd01hcFt5XT16LGkuc3BvdFNoYWRvd01hdHJpeFt5XT1JLnNoYWRvdy5tYXRyaXgsUCsrfWkuc3BvdFt5XT1VLHkrK31lbHNlIGlmKEkuaXNSZWN0QXJlYUxpZ2h0KXtsZXQgVT1yLmdldChJKTtVLmNvbG9yLmNvcHkoTCkubXVsdGlwbHlTY2FsYXIoUiksVS5oYWxmV2lkdGguc2V0KEkud2lkdGgqLjUsMCwwKSxVLmhhbGZIZWlnaHQuc2V0KDAsSS5oZWlnaHQqLjUsMCksaS5yZWN0QXJlYVt4XT1VLHgrK31lbHNlIGlmKEkuaXNQb2ludExpZ2h0KXtsZXQgVT1yLmdldChJKTtpZihVLmNvbG9yLmNvcHkoSS5jb2xvcikubXVsdGlwbHlTY2FsYXIoSS5pbnRlbnNpdHkqayksVS5kaXN0YW5jZT1JLmRpc3RhbmNlLFUuZGVjYXk9SS5kZWNheSxJLmNhc3RTaGFkb3cpe2xldCBXPUkuc2hhZG93LFo9bi5nZXQoSSk7Wi5zaGFkb3dCaWFzPVcuYmlhcyxaLnNoYWRvd05vcm1hbEJpYXM9Vy5ub3JtYWxCaWFzLFouc2hhZG93UmFkaXVzPVcucmFkaXVzLFouc2hhZG93TWFwU2l6ZT1XLm1hcFNpemUsWi5zaGFkb3dDYW1lcmFOZWFyPVcuY2FtZXJhLm5lYXIsWi5zaGFkb3dDYW1lcmFGYXI9Vy5jYW1lcmEuZmFyLGkucG9pbnRTaGFkb3dbX109WixpLnBvaW50U2hhZG93TWFwW19dPXosaS5wb2ludFNoYWRvd01hdHJpeFtfXT1JLnNoYWRvdy5tYXRyaXgsQysrfWkucG9pbnRbX109VSxfKyt9ZWxzZSBpZihJLmlzSGVtaXNwaGVyZUxpZ2h0KXtsZXQgVT1yLmdldChJKTtVLnNreUNvbG9yLmNvcHkoSS5jb2xvcikubXVsdGlwbHlTY2FsYXIoUiprKSxVLmdyb3VuZENvbG9yLmNvcHkoSS5ncm91bmRDb2xvcikubXVsdGlwbHlTY2FsYXIoUiprKSxpLmhlbWlbYl09VSxiKyt9fXg+MCYmKHQuaXNXZWJHTDJ8fGUuaGFzKCJPRVNfdGV4dHVyZV9mbG9hdF9saW5lYXIiKT09PSEwPyhpLnJlY3RBcmVhTFRDMT1yZS5MVENfRkxPQVRfMSxpLnJlY3RBcmVhTFRDMj1yZS5MVENfRkxPQVRfMik6ZS5oYXMoIk9FU190ZXh0dXJlX2hhbGZfZmxvYXRfbGluZWFyIik9PT0hMD8oaS5yZWN0QXJlYUxUQzE9cmUuTFRDX0hBTEZfMSxpLnJlY3RBcmVhTFRDMj1yZS5MVENfSEFMRl8yKTpjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBVbmFibGUgdG8gdXNlIFJlY3RBcmVhTGlnaHQuIE1pc3NpbmcgV2ViR0wgZXh0ZW5zaW9ucy4iKSksaS5hbWJpZW50WzBdPWYsaS5hbWJpZW50WzFdPXAsaS5hbWJpZW50WzJdPWQ7bGV0IE89aS5oYXNoOyhPLmRpcmVjdGlvbmFsTGVuZ3RoIT09Z3x8Ty5wb2ludExlbmd0aCE9PV98fE8uc3BvdExlbmd0aCE9PXl8fE8ucmVjdEFyZWFMZW5ndGghPT14fHxPLmhlbWlMZW5ndGghPT1ifHxPLm51bURpcmVjdGlvbmFsU2hhZG93cyE9PVN8fE8ubnVtUG9pbnRTaGFkb3dzIT09Q3x8Ty5udW1TcG90U2hhZG93cyE9PVApJiYoaS5kaXJlY3Rpb25hbC5sZW5ndGg9ZyxpLnNwb3QubGVuZ3RoPXksaS5yZWN0QXJlYS5sZW5ndGg9eCxpLnBvaW50Lmxlbmd0aD1fLGkuaGVtaS5sZW5ndGg9YixpLmRpcmVjdGlvbmFsU2hhZG93Lmxlbmd0aD1TLGkuZGlyZWN0aW9uYWxTaGFkb3dNYXAubGVuZ3RoPVMsaS5wb2ludFNoYWRvdy5sZW5ndGg9QyxpLnBvaW50U2hhZG93TWFwLmxlbmd0aD1DLGkuc3BvdFNoYWRvdy5sZW5ndGg9UCxpLnNwb3RTaGFkb3dNYXAubGVuZ3RoPVAsaS5kaXJlY3Rpb25hbFNoYWRvd01hdHJpeC5sZW5ndGg9UyxpLnBvaW50U2hhZG93TWF0cml4Lmxlbmd0aD1DLGkuc3BvdFNoYWRvd01hdHJpeC5sZW5ndGg9UCxPLmRpcmVjdGlvbmFsTGVuZ3RoPWcsTy5wb2ludExlbmd0aD1fLE8uc3BvdExlbmd0aD15LE8ucmVjdEFyZWFMZW5ndGg9eCxPLmhlbWlMZW5ndGg9YixPLm51bURpcmVjdGlvbmFsU2hhZG93cz1TLE8ubnVtUG9pbnRTaGFkb3dzPUMsTy5udW1TcG90U2hhZG93cz1QLGkudmVyc2lvbj12Z3IrKyl9ZnVuY3Rpb24gYyh1LGgpe2xldCBmPTAscD0wLGQ9MCxnPTAsXz0wLHk9aC5tYXRyaXhXb3JsZEludmVyc2U7Zm9yKGxldCB4PTAsYj11Lmxlbmd0aDt4PGI7eCsrKXtsZXQgUz11W3hdO2lmKFMuaXNEaXJlY3Rpb25hbExpZ2h0KXtsZXQgQz1pLmRpcmVjdGlvbmFsW2ZdO0MuZGlyZWN0aW9uLnNldEZyb21NYXRyaXhQb3NpdGlvbihTLm1hdHJpeFdvcmxkKSxvLnNldEZyb21NYXRyaXhQb3NpdGlvbihTLnRhcmdldC5tYXRyaXhXb3JsZCksQy5kaXJlY3Rpb24uc3ViKG8pLEMuZGlyZWN0aW9uLnRyYW5zZm9ybURpcmVjdGlvbih5KSxmKyt9ZWxzZSBpZihTLmlzU3BvdExpZ2h0KXtsZXQgQz1pLnNwb3RbZF07Qy5wb3NpdGlvbi5zZXRGcm9tTWF0cml4UG9zaXRpb24oUy5tYXRyaXhXb3JsZCksQy5wb3NpdGlvbi5hcHBseU1hdHJpeDQoeSksQy5kaXJlY3Rpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKFMubWF0cml4V29ybGQpLG8uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKFMudGFyZ2V0Lm1hdHJpeFdvcmxkKSxDLmRpcmVjdGlvbi5zdWIobyksQy5kaXJlY3Rpb24udHJhbnNmb3JtRGlyZWN0aW9uKHkpLGQrK31lbHNlIGlmKFMuaXNSZWN0QXJlYUxpZ2h0KXtsZXQgQz1pLnJlY3RBcmVhW2ddO0MucG9zaXRpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKFMubWF0cml4V29ybGQpLEMucG9zaXRpb24uYXBwbHlNYXRyaXg0KHkpLHMuaWRlbnRpdHkoKSxhLmNvcHkoUy5tYXRyaXhXb3JsZCksYS5wcmVtdWx0aXBseSh5KSxzLmV4dHJhY3RSb3RhdGlvbihhKSxDLmhhbGZXaWR0aC5zZXQoUy53aWR0aCouNSwwLDApLEMuaGFsZkhlaWdodC5zZXQoMCxTLmhlaWdodCouNSwwKSxDLmhhbGZXaWR0aC5hcHBseU1hdHJpeDQocyksQy5oYWxmSGVpZ2h0LmFwcGx5TWF0cml4NChzKSxnKyt9ZWxzZSBpZihTLmlzUG9pbnRMaWdodCl7bGV0IEM9aS5wb2ludFtwXTtDLnBvc2l0aW9uLnNldEZyb21NYXRyaXhQb3NpdGlvbihTLm1hdHJpeFdvcmxkKSxDLnBvc2l0aW9uLmFwcGx5TWF0cml4NCh5KSxwKyt9ZWxzZSBpZihTLmlzSGVtaXNwaGVyZUxpZ2h0KXtsZXQgQz1pLmhlbWlbX107Qy5kaXJlY3Rpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKFMubWF0cml4V29ybGQpLEMuZGlyZWN0aW9uLnRyYW5zZm9ybURpcmVjdGlvbih5KSxDLmRpcmVjdGlvbi5ub3JtYWxpemUoKSxfKyt9fX1yZXR1cm57c2V0dXA6bCxzZXR1cFZpZXc6YyxzdGF0ZTppfX1mdW5jdGlvbiBXdWUoZSx0KXtsZXQgcj1uZXcgYmdyKGUsdCksbj1bXSxpPVtdO2Z1bmN0aW9uIG8oKXtuLmxlbmd0aD0wLGkubGVuZ3RoPTB9ZnVuY3Rpb24gYShoKXtuLnB1c2goaCl9ZnVuY3Rpb24gcyhoKXtpLnB1c2goaCl9ZnVuY3Rpb24gbChoKXtyLnNldHVwKG4saCl9ZnVuY3Rpb24gYyhoKXtyLnNldHVwVmlldyhuLGgpfXJldHVybntpbml0Om8sc3RhdGU6e2xpZ2h0c0FycmF5Om4sc2hhZG93c0FycmF5OmksbGlnaHRzOnJ9LHNldHVwTGlnaHRzOmwsc2V0dXBMaWdodHNWaWV3OmMscHVzaExpZ2h0OmEscHVzaFNoYWRvdzpzfX1mdW5jdGlvbiB3Z3IoZSx0KXtsZXQgcj1uZXcgV2Vha01hcDtmdW5jdGlvbiBuKG8sYT0wKXtsZXQgcztyZXR1cm4gci5oYXMobyk9PT0hMT8ocz1uZXcgV3VlKGUsdCksci5zZXQobyxbc10pKTphPj1yLmdldChvKS5sZW5ndGg/KHM9bmV3IFd1ZShlLHQpLHIuZ2V0KG8pLnB1c2gocykpOnM9ci5nZXQobylbYV0sc31mdW5jdGlvbiBpKCl7cj1uZXcgV2Vha01hcH1yZXR1cm57Z2V0Om4sZGlzcG9zZTppfX12YXIgZU09Y2xhc3MgZXh0ZW5kcyBxaXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaERlcHRoTWF0ZXJpYWwiLHRoaXMuZGVwdGhQYWNraW5nPUFmZSx0aGlzLm1hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLndpcmVmcmFtZT0hMSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD0xLHRoaXMuZm9nPSExLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5kZXB0aFBhY2tpbmc9dC5kZXB0aFBhY2tpbmcsdGhpcy5tYXA9dC5tYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZGlzcGxhY2VtZW50TWFwPXQuZGlzcGxhY2VtZW50TWFwLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9dC5kaXNwbGFjZW1lbnRTY2FsZSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9dC5kaXNwbGFjZW1lbnRCaWFzLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXN9fTtlTS5wcm90b3R5cGUuaXNNZXNoRGVwdGhNYXRlcmlhbD0hMDt2YXIgck09Y2xhc3MgZXh0ZW5kcyBxaXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaERpc3RhbmNlTWF0ZXJpYWwiLHRoaXMucmVmZXJlbmNlUG9zaXRpb249bmV3IGosdGhpcy5uZWFyRGlzdGFuY2U9MSx0aGlzLmZhckRpc3RhbmNlPTFlMyx0aGlzLm1hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLmZvZz0hMSx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMucmVmZXJlbmNlUG9zaXRpb24uY29weSh0LnJlZmVyZW5jZVBvc2l0aW9uKSx0aGlzLm5lYXJEaXN0YW5jZT10Lm5lYXJEaXN0YW5jZSx0aGlzLmZhckRpc3RhbmNlPXQuZmFyRGlzdGFuY2UsdGhpcy5tYXA9dC5tYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZGlzcGxhY2VtZW50TWFwPXQuZGlzcGxhY2VtZW50TWFwLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9dC5kaXNwbGFjZW1lbnRTY2FsZSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9dC5kaXNwbGFjZW1lbnRCaWFzLHRoaXN9fTtyTS5wcm90b3R5cGUuaXNNZXNoRGlzdGFuY2VNYXRlcmlhbD0hMDt2YXIgU2dyPWB2b2lkIG1haW4oKSB7CglnbF9Qb3NpdGlvbiA9IHZlYzQoIHBvc2l0aW9uLCAxLjAgKTsKfWAsTWdyPWB1bmlmb3JtIHNhbXBsZXIyRCBzaGFkb3dfcGFzczsKdW5pZm9ybSB2ZWMyIHJlc29sdXRpb247CnVuaWZvcm0gZmxvYXQgcmFkaXVzOwojaW5jbHVkZSA8cGFja2luZz4Kdm9pZCBtYWluKCkgewoJY29uc3QgZmxvYXQgc2FtcGxlcyA9IGZsb2F0KCBWU01fU0FNUExFUyApOwoJZmxvYXQgbWVhbiA9IDAuMDsKCWZsb2F0IHNxdWFyZWRfbWVhbiA9IDAuMDsKCWZsb2F0IHV2U3RyaWRlID0gc2FtcGxlcyA8PSAxLjAgPyAwLjAgOiAyLjAgLyAoIHNhbXBsZXMgLSAxLjAgKTsKCWZsb2F0IHV2U3RhcnQgPSBzYW1wbGVzIDw9IDEuMCA/IDAuMCA6IC0gMS4wOwoJZm9yICggZmxvYXQgaSA9IDAuMDsgaSA8IHNhbXBsZXM7IGkgKysgKSB7CgkJZmxvYXQgdXZPZmZzZXQgPSB1dlN0YXJ0ICsgaSAqIHV2U3RyaWRlOwoJCSNpZmRlZiBIT1JJWk9OVEFMX1BBU1MKCQkJdmVjMiBkaXN0cmlidXRpb24gPSB1bnBhY2tSR0JBVG8ySGFsZiggdGV4dHVyZTJEKCBzaGFkb3dfcGFzcywgKCBnbF9GcmFnQ29vcmQueHkgKyB2ZWMyKCB1dk9mZnNldCwgMC4wICkgKiByYWRpdXMgKSAvIHJlc29sdXRpb24gKSApOwoJCQltZWFuICs9IGRpc3RyaWJ1dGlvbi54OwoJCQlzcXVhcmVkX21lYW4gKz0gZGlzdHJpYnV0aW9uLnkgKiBkaXN0cmlidXRpb24ueSArIGRpc3RyaWJ1dGlvbi54ICogZGlzdHJpYnV0aW9uLng7CgkJI2Vsc2UKCQkJZmxvYXQgZGVwdGggPSB1bnBhY2tSR0JBVG9EZXB0aCggdGV4dHVyZTJEKCBzaGFkb3dfcGFzcywgKCBnbF9GcmFnQ29vcmQueHkgKyB2ZWMyKCAwLjAsIHV2T2Zmc2V0ICkgKiByYWRpdXMgKSAvIHJlc29sdXRpb24gKSApOwoJCQltZWFuICs9IGRlcHRoOwoJCQlzcXVhcmVkX21lYW4gKz0gZGVwdGggKiBkZXB0aDsKCQkjZW5kaWYKCX0KCW1lYW4gPSBtZWFuIC8gc2FtcGxlczsKCXNxdWFyZWRfbWVhbiA9IHNxdWFyZWRfbWVhbiAvIHNhbXBsZXM7CglmbG9hdCBzdGRfZGV2ID0gc3FydCggc3F1YXJlZF9tZWFuIC0gbWVhbiAqIG1lYW4gKTsKCWdsX0ZyYWdDb2xvciA9IHBhY2sySGFsZlRvUkdCQSggdmVjMiggbWVhbiwgc3RkX2RldiApICk7Cn1gO2Z1bmN0aW9uIGpmZShlLHQscil7bGV0IG49bmV3IE52LGk9bmV3IEx0LG89bmV3IEx0LGE9bmV3IGVuLHM9bmV3IGVNKHtkZXB0aFBhY2tpbmc6UGZlfSksbD1uZXcgck0sYz17fSx1PXIubWF4VGV4dHVyZVNpemUsaD17MDpJaSwxOkl2LDI6THZ9LGY9bmV3IGxoKHtkZWZpbmVzOntWU01fU0FNUExFUzo4fSx1bmlmb3Jtczp7c2hhZG93X3Bhc3M6e3ZhbHVlOm51bGx9LHJlc29sdXRpb246e3ZhbHVlOm5ldyBMdH0scmFkaXVzOnt2YWx1ZTo0fX0sdmVydGV4U2hhZGVyOlNncixmcmFnbWVudFNoYWRlcjpNZ3J9KSxwPWYuY2xvbmUoKTtwLmRlZmluZXMuSE9SSVpPTlRBTF9QQVNTPTE7bGV0IGQ9bmV3IFBlO2Quc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IEplKG5ldyBGbG9hdDMyQXJyYXkoWy0xLC0xLC41LDMsLTEsLjUsLTEsMywuNV0pLDMpKTtsZXQgZz1uZXcgZWkoZCxmKSxfPXRoaXM7dGhpcy5lbmFibGVkPSExLHRoaXMuYXV0b1VwZGF0ZT0hMCx0aGlzLm5lZWRzVXBkYXRlPSExLHRoaXMudHlwZT1BaHQsdGhpcy5yZW5kZXI9ZnVuY3Rpb24oUyxDLFApe2lmKF8uZW5hYmxlZD09PSExfHxfLmF1dG9VcGRhdGU9PT0hMSYmXy5uZWVkc1VwZGF0ZT09PSExfHxTLmxlbmd0aD09PTApcmV0dXJuO2xldCBrPWUuZ2V0UmVuZGVyVGFyZ2V0KCksTz1lLmdldEFjdGl2ZUN1YmVGYWNlKCksRD1lLmdldEFjdGl2ZU1pcG1hcExldmVsKCksQj1lLnN0YXRlO0Iuc2V0QmxlbmRpbmcoJGQpLEIuYnVmZmVycy5jb2xvci5zZXRDbGVhcigxLDEsMSwxKSxCLmJ1ZmZlcnMuZGVwdGguc2V0VGVzdCghMCksQi5zZXRTY2lzc29yVGVzdCghMSk7Zm9yKGxldCBJPTAsTD1TLmxlbmd0aDtJPEw7SSsrKXtsZXQgUj1TW0ldLEY9Ui5zaGFkb3c7aWYoRj09PXZvaWQgMCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFNoYWRvd01hcDoiLFIsImhhcyBubyBzaGFkb3cuIik7Y29udGludWV9aWYoRi5hdXRvVXBkYXRlPT09ITEmJkYubmVlZHNVcGRhdGU9PT0hMSljb250aW51ZTtpLmNvcHkoRi5tYXBTaXplKTtsZXQgej1GLmdldEZyYW1lRXh0ZW50cygpO2lmKGkubXVsdGlwbHkoeiksby5jb3B5KEYubWFwU2l6ZSksKGkueD51fHxpLnk+dSkmJihpLng+dSYmKG8ueD1NYXRoLmZsb29yKHUvei54KSxpLng9by54KnoueCxGLm1hcFNpemUueD1vLngpLGkueT51JiYoby55PU1hdGguZmxvb3IodS96LnkpLGkueT1vLnkqei55LEYubWFwU2l6ZS55PW8ueSkpLEYubWFwPT09bnVsbCYmIUYuaXNQb2ludExpZ2h0U2hhZG93JiZ0aGlzLnR5cGU9PT1GMyl7bGV0IFc9e21pbkZpbHRlcjpvaSxtYWdGaWx0ZXI6b2ksZm9ybWF0OlFvfTtGLm1hcD1uZXcgdXMoaS54LGkueSxXKSxGLm1hcC50ZXh0dXJlLm5hbWU9Ui5uYW1lKyIuc2hhZG93TWFwIixGLm1hcFBhc3M9bmV3IHVzKGkueCxpLnksVyksRi5jYW1lcmEudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpfWlmKEYubWFwPT09bnVsbCl7bGV0IFc9e21pbkZpbHRlcjpMaSxtYWdGaWx0ZXI6TGksZm9ybWF0OlFvfTtGLm1hcD1uZXcgdXMoaS54LGkueSxXKSxGLm1hcC50ZXh0dXJlLm5hbWU9Ui5uYW1lKyIuc2hhZG93TWFwIixGLmNhbWVyYS51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl9ZS5zZXRSZW5kZXJUYXJnZXQoRi5tYXApLGUuY2xlYXIoKTtsZXQgVT1GLmdldFZpZXdwb3J0Q291bnQoKTtmb3IobGV0IFc9MDtXPFU7VysrKXtsZXQgWj1GLmdldFZpZXdwb3J0KFcpO2Euc2V0KG8ueCpaLngsby55KloueSxvLngqWi56LG8ueSpaLncpLEIudmlld3BvcnQoYSksRi51cGRhdGVNYXRyaWNlcyhSLFcpLG49Ri5nZXRGcnVzdHVtKCksYihDLFAsRi5jYW1lcmEsUix0aGlzLnR5cGUpfSFGLmlzUG9pbnRMaWdodFNoYWRvdyYmdGhpcy50eXBlPT09RjMmJnkoRixQKSxGLm5lZWRzVXBkYXRlPSExfV8ubmVlZHNVcGRhdGU9ITEsZS5zZXRSZW5kZXJUYXJnZXQoayxPLEQpfTtmdW5jdGlvbiB5KFMsQyl7bGV0IFA9dC51cGRhdGUoZyk7Zi5kZWZpbmVzLlZTTV9TQU1QTEVTIT09Uy5ibHVyU2FtcGxlcyYmKGYuZGVmaW5lcy5WU01fU0FNUExFUz1TLmJsdXJTYW1wbGVzLHAuZGVmaW5lcy5WU01fU0FNUExFUz1TLmJsdXJTYW1wbGVzLGYubmVlZHNVcGRhdGU9ITAscC5uZWVkc1VwZGF0ZT0hMCksZi51bmlmb3Jtcy5zaGFkb3dfcGFzcy52YWx1ZT1TLm1hcC50ZXh0dXJlLGYudW5pZm9ybXMucmVzb2x1dGlvbi52YWx1ZT1TLm1hcFNpemUsZi51bmlmb3Jtcy5yYWRpdXMudmFsdWU9Uy5yYWRpdXMsZS5zZXRSZW5kZXJUYXJnZXQoUy5tYXBQYXNzKSxlLmNsZWFyKCksZS5yZW5kZXJCdWZmZXJEaXJlY3QoQyxudWxsLFAsZixnLG51bGwpLHAudW5pZm9ybXMuc2hhZG93X3Bhc3MudmFsdWU9Uy5tYXBQYXNzLnRleHR1cmUscC51bmlmb3Jtcy5yZXNvbHV0aW9uLnZhbHVlPVMubWFwU2l6ZSxwLnVuaWZvcm1zLnJhZGl1cy52YWx1ZT1TLnJhZGl1cyxlLnNldFJlbmRlclRhcmdldChTLm1hcCksZS5jbGVhcigpLGUucmVuZGVyQnVmZmVyRGlyZWN0KEMsbnVsbCxQLHAsZyxudWxsKX1mdW5jdGlvbiB4KFMsQyxQLGssTyxELEIpe2xldCBJPW51bGwsTD1rLmlzUG9pbnRMaWdodD09PSEwP1MuY3VzdG9tRGlzdGFuY2VNYXRlcmlhbDpTLmN1c3RvbURlcHRoTWF0ZXJpYWw7aWYoTCE9PXZvaWQgMD9JPUw6ST1rLmlzUG9pbnRMaWdodD09PSEwP2w6cyxlLmxvY2FsQ2xpcHBpbmdFbmFibGVkJiZQLmNsaXBTaGFkb3dzPT09ITAmJlAuY2xpcHBpbmdQbGFuZXMubGVuZ3RoIT09MHx8UC5kaXNwbGFjZW1lbnRNYXAmJlAuZGlzcGxhY2VtZW50U2NhbGUhPT0wfHxQLmFscGhhTWFwJiZQLmFscGhhVGVzdD4wKXtsZXQgUj1JLnV1aWQsRj1QLnV1aWQsej1jW1JdO3o9PT12b2lkIDAmJih6PXt9LGNbUl09eik7bGV0IFU9eltGXTtVPT09dm9pZCAwJiYoVT1JLmNsb25lKCkseltGXT1VKSxJPVV9cmV0dXJuIEkudmlzaWJsZT1QLnZpc2libGUsSS53aXJlZnJhbWU9UC53aXJlZnJhbWUsQj09PUYzP0kuc2lkZT1QLnNoYWRvd1NpZGUhPT1udWxsP1Auc2hhZG93U2lkZTpQLnNpZGU6SS5zaWRlPVAuc2hhZG93U2lkZSE9PW51bGw/UC5zaGFkb3dTaWRlOmhbUC5zaWRlXSxJLmFscGhhTWFwPVAuYWxwaGFNYXAsSS5hbHBoYVRlc3Q9UC5hbHBoYVRlc3QsSS5jbGlwU2hhZG93cz1QLmNsaXBTaGFkb3dzLEkuY2xpcHBpbmdQbGFuZXM9UC5jbGlwcGluZ1BsYW5lcyxJLmNsaXBJbnRlcnNlY3Rpb249UC5jbGlwSW50ZXJzZWN0aW9uLEkuZGlzcGxhY2VtZW50TWFwPVAuZGlzcGxhY2VtZW50TWFwLEkuZGlzcGxhY2VtZW50U2NhbGU9UC5kaXNwbGFjZW1lbnRTY2FsZSxJLmRpc3BsYWNlbWVudEJpYXM9UC5kaXNwbGFjZW1lbnRCaWFzLEkud2lyZWZyYW1lTGluZXdpZHRoPVAud2lyZWZyYW1lTGluZXdpZHRoLEkubGluZXdpZHRoPVAubGluZXdpZHRoLGsuaXNQb2ludExpZ2h0PT09ITAmJkkuaXNNZXNoRGlzdGFuY2VNYXRlcmlhbD09PSEwJiYoSS5yZWZlcmVuY2VQb3NpdGlvbi5zZXRGcm9tTWF0cml4UG9zaXRpb24oay5tYXRyaXhXb3JsZCksSS5uZWFyRGlzdGFuY2U9TyxJLmZhckRpc3RhbmNlPUQpLEl9ZnVuY3Rpb24gYihTLEMsUCxrLE8pe2lmKFMudmlzaWJsZT09PSExKXJldHVybjtpZihTLmxheWVycy50ZXN0KEMubGF5ZXJzKSYmKFMuaXNNZXNofHxTLmlzTGluZXx8Uy5pc1BvaW50cykmJihTLmNhc3RTaGFkb3d8fFMucmVjZWl2ZVNoYWRvdyYmTz09PUYzKSYmKCFTLmZydXN0dW1DdWxsZWR8fG4uaW50ZXJzZWN0c09iamVjdChTKSkpe1MubW9kZWxWaWV3TWF0cml4Lm11bHRpcGx5TWF0cmljZXMoUC5tYXRyaXhXb3JsZEludmVyc2UsUy5tYXRyaXhXb3JsZCk7bGV0IEk9dC51cGRhdGUoUyksTD1TLm1hdGVyaWFsO2lmKEFycmF5LmlzQXJyYXkoTCkpe2xldCBSPUkuZ3JvdXBzO2ZvcihsZXQgRj0wLHo9Ui5sZW5ndGg7Rjx6O0YrKyl7bGV0IFU9UltGXSxXPUxbVS5tYXRlcmlhbEluZGV4XTtpZihXJiZXLnZpc2libGUpe2xldCBaPXgoUyxJLFcsayxQLm5lYXIsUC5mYXIsTyk7ZS5yZW5kZXJCdWZmZXJEaXJlY3QoUCxudWxsLEksWixTLFUpfX19ZWxzZSBpZihMLnZpc2libGUpe2xldCBSPXgoUyxJLEwsayxQLm5lYXIsUC5mYXIsTyk7ZS5yZW5kZXJCdWZmZXJEaXJlY3QoUCxudWxsLEksUixTLG51bGwpfX1sZXQgQj1TLmNoaWxkcmVuO2ZvcihsZXQgST0wLEw9Qi5sZW5ndGg7STxMO0krKyliKEJbSV0sQyxQLGssTyl9fWZ1bmN0aW9uIEVncihlLHQscil7bGV0IG49ci5pc1dlYkdMMjtmdW5jdGlvbiBpKCl7bGV0IGF0PSExLHNlPW5ldyBlbixRdD1udWxsLENlPW5ldyBlbigwLDAsMCwwKTtyZXR1cm57c2V0TWFzazpmdW5jdGlvbihQdCl7UXQhPT1QdCYmIWF0JiYoZS5jb2xvck1hc2soUHQsUHQsUHQsUHQpLFF0PVB0KX0sc2V0TG9ja2VkOmZ1bmN0aW9uKFB0KXthdD1QdH0sc2V0Q2xlYXI6ZnVuY3Rpb24oUHQsTnQsemUseW4sV2kpe1dpPT09ITAmJihQdCo9eW4sTnQqPXluLHplKj15biksc2Uuc2V0KFB0LE50LHplLHluKSxDZS5lcXVhbHMoc2UpPT09ITEmJihlLmNsZWFyQ29sb3IoUHQsTnQsemUseW4pLENlLmNvcHkoc2UpKX0scmVzZXQ6ZnVuY3Rpb24oKXthdD0hMSxRdD1udWxsLENlLnNldCgtMSwwLDAsMCl9fX1mdW5jdGlvbiBvKCl7bGV0IGF0PSExLHNlPW51bGwsUXQ9bnVsbCxDZT1udWxsO3JldHVybntzZXRUZXN0OmZ1bmN0aW9uKFB0KXtQdD9sdCgyOTI5KTpLdCgyOTI5KX0sc2V0TWFzazpmdW5jdGlvbihQdCl7c2UhPT1QdCYmIWF0JiYoZS5kZXB0aE1hc2soUHQpLHNlPVB0KX0sc2V0RnVuYzpmdW5jdGlvbihQdCl7aWYoUXQhPT1QdCl7aWYoUHQpc3dpdGNoKFB0KXtjYXNlIEtoZTplLmRlcHRoRnVuYyg1MTIpO2JyZWFrO2Nhc2UgWmhlOmUuZGVwdGhGdW5jKDUxOSk7YnJlYWs7Y2FzZSBKaGU6ZS5kZXB0aEZ1bmMoNTEzKTticmVhaztjYXNlIG5VOmUuZGVwdGhGdW5jKDUxNSk7YnJlYWs7Y2FzZSBRaGU6ZS5kZXB0aEZ1bmMoNTE0KTticmVhaztjYXNlIHRmZTplLmRlcHRoRnVuYyg1MTgpO2JyZWFrO2Nhc2UgZWZlOmUuZGVwdGhGdW5jKDUxNik7YnJlYWs7Y2FzZSByZmU6ZS5kZXB0aEZ1bmMoNTE3KTticmVhaztkZWZhdWx0OmUuZGVwdGhGdW5jKDUxNSl9ZWxzZSBlLmRlcHRoRnVuYyg1MTUpO1F0PVB0fX0sc2V0TG9ja2VkOmZ1bmN0aW9uKFB0KXthdD1QdH0sc2V0Q2xlYXI6ZnVuY3Rpb24oUHQpe0NlIT09UHQmJihlLmNsZWFyRGVwdGgoUHQpLENlPVB0KX0scmVzZXQ6ZnVuY3Rpb24oKXthdD0hMSxzZT1udWxsLFF0PW51bGwsQ2U9bnVsbH19fWZ1bmN0aW9uIGEoKXtsZXQgYXQ9ITEsc2U9bnVsbCxRdD1udWxsLENlPW51bGwsUHQ9bnVsbCxOdD1udWxsLHplPW51bGwseW49bnVsbCxXaT1udWxsO3JldHVybntzZXRUZXN0OmZ1bmN0aW9uKEFyKXthdHx8KEFyP2x0KDI5NjApOkt0KDI5NjApKX0sc2V0TWFzazpmdW5jdGlvbihBcil7c2UhPT1BciYmIWF0JiYoZS5zdGVuY2lsTWFzayhBciksc2U9QXIpfSxzZXRGdW5jOmZ1bmN0aW9uKEFyLFBhLGhvKXsoUXQhPT1Bcnx8Q2UhPT1QYXx8UHQhPT1obykmJihlLnN0ZW5jaWxGdW5jKEFyLFBhLGhvKSxRdD1BcixDZT1QYSxQdD1obyl9LHNldE9wOmZ1bmN0aW9uKEFyLFBhLGhvKXsoTnQhPT1Bcnx8emUhPT1QYXx8eW4hPT1obykmJihlLnN0ZW5jaWxPcChBcixQYSxobyksTnQ9QXIsemU9UGEseW49aG8pfSxzZXRMb2NrZWQ6ZnVuY3Rpb24oQXIpe2F0PUFyfSxzZXRDbGVhcjpmdW5jdGlvbihBcil7V2khPT1BciYmKGUuY2xlYXJTdGVuY2lsKEFyKSxXaT1Bcil9LHJlc2V0OmZ1bmN0aW9uKCl7YXQ9ITEsc2U9bnVsbCxRdD1udWxsLENlPW51bGwsUHQ9bnVsbCxOdD1udWxsLHplPW51bGwseW49bnVsbCxXaT1udWxsfX19bGV0IHM9bmV3IGksbD1uZXcgbyxjPW5ldyBhLHU9e30saD17fSxmPW5ldyBXZWFrTWFwLHA9W10sZD1udWxsLGc9ITEsXz1udWxsLHk9bnVsbCx4PW51bGwsYj1udWxsLFM9bnVsbCxDPW51bGwsUD1udWxsLGs9ITEsTz1udWxsLEQ9bnVsbCxCPW51bGwsST1udWxsLEw9bnVsbCxSPWUuZ2V0UGFyYW1ldGVyKDM1NjYxKSxGPSExLHo9MCxVPWUuZ2V0UGFyYW1ldGVyKDc5MzgpO1UuaW5kZXhPZigiV2ViR0wiKSE9PS0xPyh6PXBhcnNlRmxvYXQoL15XZWJHTCAoXGQpLy5leGVjKFUpWzFdKSxGPXo+PTEpOlUuaW5kZXhPZigiT3BlbkdMIEVTIikhPT0tMSYmKHo9cGFyc2VGbG9hdCgvXk9wZW5HTCBFUyAoXGQpLy5leGVjKFUpWzFdKSxGPXo+PTIpO2xldCBXPW51bGwsWj17fSxydD1lLmdldFBhcmFtZXRlcigzMDg4KSxvdD1lLmdldFBhcmFtZXRlcigyOTc4KSxzdD1uZXcgZW4oKS5mcm9tQXJyYXkocnQpLFN0PW5ldyBlbigpLmZyb21BcnJheShvdCk7ZnVuY3Rpb24gYnQoYXQsc2UsUXQpe2xldCBDZT1uZXcgVWludDhBcnJheSg0KSxQdD1lLmNyZWF0ZVRleHR1cmUoKTtlLmJpbmRUZXh0dXJlKGF0LFB0KSxlLnRleFBhcmFtZXRlcmkoYXQsMTAyNDEsOTcyOCksZS50ZXhQYXJhbWV0ZXJpKGF0LDEwMjQwLDk3MjgpO2ZvcihsZXQgTnQ9MDtOdDxRdDtOdCsrKWUudGV4SW1hZ2UyRChzZStOdCwwLDY0MDgsMSwxLDAsNjQwOCw1MTIxLENlKTtyZXR1cm4gUHR9bGV0IE10PXt9O010WzM1NTNdPWJ0KDM1NTMsMzU1MywxKSxNdFszNDA2N109YnQoMzQwNjcsMzQwNjksNikscy5zZXRDbGVhcigwLDAsMCwxKSxsLnNldENsZWFyKDEpLGMuc2V0Q2xlYXIoMCksbHQoMjkyOSksbC5zZXRGdW5jKG5VKSxodCghMSksd3QoTXV0KSxsdCgyODg0KSxxKCRkKTtmdW5jdGlvbiBsdChhdCl7dVthdF0hPT0hMCYmKGUuZW5hYmxlKGF0KSx1W2F0XT0hMCl9ZnVuY3Rpb24gS3QoYXQpe3VbYXRdIT09ITEmJihlLmRpc2FibGUoYXQpLHVbYXRdPSExKX1mdW5jdGlvbiBfdChhdCxzZSl7cmV0dXJuIGhbYXRdIT09c2U/KGUuYmluZEZyYW1lYnVmZmVyKGF0LHNlKSxoW2F0XT1zZSxuJiYoYXQ9PT0zNjAwOSYmKGhbMzYxNjBdPXNlKSxhdD09PTM2MTYwJiYoaFszNjAwOV09c2UpKSwhMCk6ITF9ZnVuY3Rpb24gY3QoYXQsc2Upe2xldCBRdD1wLENlPSExO2lmKGF0KWlmKFF0PWYuZ2V0KHNlKSxRdD09PXZvaWQgMCYmKFF0PVtdLGYuc2V0KHNlLFF0KSksYXQuaXNXZWJHTE11bHRpcGxlUmVuZGVyVGFyZ2V0cyl7bGV0IFB0PWF0LnRleHR1cmU7aWYoUXQubGVuZ3RoIT09UHQubGVuZ3RofHxRdFswXSE9PTM2MDY0KXtmb3IobGV0IE50PTAsemU9UHQubGVuZ3RoO050PHplO050KyspUXRbTnRdPTM2MDY0K050O1F0Lmxlbmd0aD1QdC5sZW5ndGgsQ2U9ITB9fWVsc2UgUXRbMF0hPT0zNjA2NCYmKFF0WzBdPTM2MDY0LENlPSEwKTtlbHNlIFF0WzBdIT09MTAyOSYmKFF0WzBdPTEwMjksQ2U9ITApO0NlJiYoci5pc1dlYkdMMj9lLmRyYXdCdWZmZXJzKFF0KTp0LmdldCgiV0VCR0xfZHJhd19idWZmZXJzIikuZHJhd0J1ZmZlcnNXRUJHTChRdCkpfWZ1bmN0aW9uIFgoYXQpe3JldHVybiBkIT09YXQ/KGUudXNlUHJvZ3JhbShhdCksZD1hdCwhMCk6ITF9bGV0IGV0PXtbTXZdOjMyNzc0LFtCaGVdOjMyNzc4LFtIaGVdOjMyNzc5fTtpZihuKWV0W0F1dF09MzI3NzUsZXRbUHV0XT0zMjc3NjtlbHNle2xldCBhdD10LmdldCgiRVhUX2JsZW5kX21pbm1heCIpO2F0IT09bnVsbCYmKGV0W0F1dF09YXQuTUlOX0VYVCxldFtQdXRdPWF0Lk1BWF9FWFQpfWxldCBkdD17W1ZoZV06MCxbVWhlXToxLFtxaGVdOjc2OCxbSWh0XTo3NzAsWyRoZV06Nzc2LFtqaGVdOjc3NCxbV2hlXTo3NzIsW0doZV06NzY5LFtMaHRdOjc3MSxbWGhlXTo3NzUsW1loZV06NzczfTtmdW5jdGlvbiBxKGF0LHNlLFF0LENlLFB0LE50LHplLHluKXtpZihhdD09PSRkKXtnPT09ITAmJihLdCgzMDQyKSxnPSExKTtyZXR1cm59aWYoZz09PSExJiYobHQoMzA0MiksZz0hMCksYXQhPT1GaGUpe2lmKGF0IT09X3x8eW4hPT1rKXtpZigoeSE9PU12fHxTIT09TXYpJiYoZS5ibGVuZEVxdWF0aW9uKDMyNzc0KSx5PU12LFM9TXYpLHluKXN3aXRjaChhdCl7Y2FzZSBWMzplLmJsZW5kRnVuY1NlcGFyYXRlKDEsNzcxLDEsNzcxKTticmVhaztjYXNlIEV1dDplLmJsZW5kRnVuYygxLDEpO2JyZWFrO2Nhc2UgVHV0OmUuYmxlbmRGdW5jU2VwYXJhdGUoMCw3NjksMCwxKTticmVhaztjYXNlIEN1dDplLmJsZW5kRnVuY1NlcGFyYXRlKDAsNzY4LDAsNzcwKTticmVhaztkZWZhdWx0OmNvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IEludmFsaWQgYmxlbmRpbmc6ICIsYXQpO2JyZWFrfWVsc2Ugc3dpdGNoKGF0KXtjYXNlIFYzOmUuYmxlbmRGdW5jU2VwYXJhdGUoNzcwLDc3MSwxLDc3MSk7YnJlYWs7Y2FzZSBFdXQ6ZS5ibGVuZEZ1bmMoNzcwLDEpO2JyZWFrO2Nhc2UgVHV0OmUuYmxlbmRGdW5jU2VwYXJhdGUoMCw3NjksMCwxKTticmVhaztjYXNlIEN1dDplLmJsZW5kRnVuYygwLDc2OCk7YnJlYWs7ZGVmYXVsdDpjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFN0YXRlOiBJbnZhbGlkIGJsZW5kaW5nOiAiLGF0KTticmVha314PW51bGwsYj1udWxsLEM9bnVsbCxQPW51bGwsXz1hdCxrPXlufXJldHVybn1QdD1QdHx8c2UsTnQ9TnR8fFF0LHplPXplfHxDZSwoc2UhPT15fHxQdCE9PVMpJiYoZS5ibGVuZEVxdWF0aW9uU2VwYXJhdGUoZXRbc2VdLGV0W1B0XSkseT1zZSxTPVB0KSwoUXQhPT14fHxDZSE9PWJ8fE50IT09Q3x8emUhPT1QKSYmKGUuYmxlbmRGdW5jU2VwYXJhdGUoZHRbUXRdLGR0W0NlXSxkdFtOdF0sZHRbemVdKSx4PVF0LGI9Q2UsQz1OdCxQPXplKSxfPWF0LGs9bnVsbH1mdW5jdGlvbiBwdChhdCxzZSl7YXQuc2lkZT09PUx2P0t0KDI4ODQpOmx0KDI4ODQpO2xldCBRdD1hdC5zaWRlPT09SWk7c2UmJihRdD0hUXQpLGh0KFF0KSxhdC5ibGVuZGluZz09PVYzJiZhdC50cmFuc3BhcmVudD09PSExP3EoJGQpOnEoYXQuYmxlbmRpbmcsYXQuYmxlbmRFcXVhdGlvbixhdC5ibGVuZFNyYyxhdC5ibGVuZERzdCxhdC5ibGVuZEVxdWF0aW9uQWxwaGEsYXQuYmxlbmRTcmNBbHBoYSxhdC5ibGVuZERzdEFscGhhLGF0LnByZW11bHRpcGxpZWRBbHBoYSksbC5zZXRGdW5jKGF0LmRlcHRoRnVuYyksbC5zZXRUZXN0KGF0LmRlcHRoVGVzdCksbC5zZXRNYXNrKGF0LmRlcHRoV3JpdGUpLHMuc2V0TWFzayhhdC5jb2xvcldyaXRlKTtsZXQgQ2U9YXQuc3RlbmNpbFdyaXRlO2Muc2V0VGVzdChDZSksQ2UmJihjLnNldE1hc2soYXQuc3RlbmNpbFdyaXRlTWFzayksYy5zZXRGdW5jKGF0LnN0ZW5jaWxGdW5jLGF0LnN0ZW5jaWxSZWYsYXQuc3RlbmNpbEZ1bmNNYXNrKSxjLnNldE9wKGF0LnN0ZW5jaWxGYWlsLGF0LnN0ZW5jaWxaRmFpbCxhdC5zdGVuY2lsWlBhc3MpKSxpZShhdC5wb2x5Z29uT2Zmc2V0LGF0LnBvbHlnb25PZmZzZXRGYWN0b3IsYXQucG9seWdvbk9mZnNldFVuaXRzKSxhdC5hbHBoYVRvQ292ZXJhZ2U9PT0hMD9sdCgzMjkyNik6S3QoMzI5MjYpfWZ1bmN0aW9uIGh0KGF0KXtPIT09YXQmJihhdD9lLmZyb250RmFjZSgyMzA0KTplLmZyb250RmFjZSgyMzA1KSxPPWF0KX1mdW5jdGlvbiB3dChhdCl7YXQhPT1EaGU/KGx0KDI4ODQpLGF0IT09RCYmKGF0PT09TXV0P2UuY3VsbEZhY2UoMTAyOSk6YXQ9PT1PaGU/ZS5jdWxsRmFjZSgxMDI4KTplLmN1bGxGYWNlKDEwMzIpKSk6S3QoMjg4NCksRD1hdH1mdW5jdGlvbiBrdChhdCl7YXQhPT1CJiYoRiYmZS5saW5lV2lkdGgoYXQpLEI9YXQpfWZ1bmN0aW9uIGllKGF0LHNlLFF0KXthdD8obHQoMzI4MjMpLChJIT09c2V8fEwhPT1RdCkmJihlLnBvbHlnb25PZmZzZXQoc2UsUXQpLEk9c2UsTD1RdCkpOkt0KDMyODIzKX1mdW5jdGlvbiBlZShhdCl7YXQ/bHQoMzA4OSk6S3QoMzA4OSl9ZnVuY3Rpb24gTGUoYXQpe2F0PT09dm9pZCAwJiYoYXQ9MzM5ODQrUi0xKSxXIT09YXQmJihlLmFjdGl2ZVRleHR1cmUoYXQpLFc9YXQpfWZ1bmN0aW9uIGFyKGF0LHNlKXtXPT09bnVsbCYmTGUoKTtsZXQgUXQ9WltXXTtRdD09PXZvaWQgMCYmKFF0PXt0eXBlOnZvaWQgMCx0ZXh0dXJlOnZvaWQgMH0sWltXXT1RdCksKFF0LnR5cGUhPT1hdHx8UXQudGV4dHVyZSE9PXNlKSYmKGUuYmluZFRleHR1cmUoYXQsc2V8fE10W2F0XSksUXQudHlwZT1hdCxRdC50ZXh0dXJlPXNlKX1mdW5jdGlvbiBmcigpe2xldCBhdD1aW1ddO2F0IT09dm9pZCAwJiZhdC50eXBlIT09dm9pZCAwJiYoZS5iaW5kVGV4dHVyZShhdC50eXBlLG51bGwpLGF0LnR5cGU9dm9pZCAwLGF0LnRleHR1cmU9dm9pZCAwKX1mdW5jdGlvbiB0dCgpe3RyeXtlLmNvbXByZXNzZWRUZXhJbWFnZTJELmFwcGx5KGUsYXJndW1lbnRzKX1jYXRjaChhdCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZToiLGF0KX19ZnVuY3Rpb24gJCgpe3RyeXtlLnRleFN1YkltYWdlMkQuYXBwbHkoZSxhcmd1bWVudHMpfWNhdGNoKGF0KXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFN0YXRlOiIsYXQpfX1mdW5jdGlvbiBJdCgpe3RyeXtlLnRleFN1YkltYWdlM0QuYXBwbHkoZSxhcmd1bWVudHMpfWNhdGNoKGF0KXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFN0YXRlOiIsYXQpfX1mdW5jdGlvbiAkdCgpe3RyeXtlLmNvbXByZXNzZWRUZXhTdWJJbWFnZTJELmFwcGx5KGUsYXJndW1lbnRzKX1jYXRjaChhdCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZToiLGF0KX19ZnVuY3Rpb24gaGUoKXt0cnl7ZS50ZXhTdG9yYWdlMkQuYXBwbHkoZSxhcmd1bWVudHMpfWNhdGNoKGF0KXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFN0YXRlOiIsYXQpfX1mdW5jdGlvbiBUdCgpe3RyeXtlLnRleFN0b3JhZ2UzRC5hcHBseShlLGFyZ3VtZW50cyl9Y2F0Y2goYXQpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IixhdCl9fWZ1bmN0aW9uIGJlKCl7dHJ5e2UudGV4SW1hZ2UyRC5hcHBseShlLGFyZ3VtZW50cyl9Y2F0Y2goYXQpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IixhdCl9fWZ1bmN0aW9uIG50KCl7dHJ5e2UudGV4SW1hZ2UzRC5hcHBseShlLGFyZ3VtZW50cyl9Y2F0Y2goYXQpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IixhdCl9fWZ1bmN0aW9uIEN0KGF0KXtzdC5lcXVhbHMoYXQpPT09ITEmJihlLnNjaXNzb3IoYXQueCxhdC55LGF0LnosYXQudyksc3QuY29weShhdCkpfWZ1bmN0aW9uIFd0KGF0KXtTdC5lcXVhbHMoYXQpPT09ITEmJihlLnZpZXdwb3J0KGF0LngsYXQueSxhdC56LGF0LncpLFN0LmNvcHkoYXQpKX1mdW5jdGlvbiBmZSgpe2UuZGlzYWJsZSgzMDQyKSxlLmRpc2FibGUoMjg4NCksZS5kaXNhYmxlKDI5MjkpLGUuZGlzYWJsZSgzMjgyMyksZS5kaXNhYmxlKDMwODkpLGUuZGlzYWJsZSgyOTYwKSxlLmRpc2FibGUoMzI5MjYpLGUuYmxlbmRFcXVhdGlvbigzMjc3NCksZS5ibGVuZEZ1bmMoMSwwKSxlLmJsZW5kRnVuY1NlcGFyYXRlKDEsMCwxLDApLGUuY29sb3JNYXNrKCEwLCEwLCEwLCEwKSxlLmNsZWFyQ29sb3IoMCwwLDAsMCksZS5kZXB0aE1hc2soITApLGUuZGVwdGhGdW5jKDUxMyksZS5jbGVhckRlcHRoKDEpLGUuc3RlbmNpbE1hc2soNDI5NDk2NzI5NSksZS5zdGVuY2lsRnVuYyg1MTksMCw0Mjk0OTY3Mjk1KSxlLnN0ZW5jaWxPcCg3NjgwLDc2ODAsNzY4MCksZS5jbGVhclN0ZW5jaWwoMCksZS5jdWxsRmFjZSgxMDI5KSxlLmZyb250RmFjZSgyMzA1KSxlLnBvbHlnb25PZmZzZXQoMCwwKSxlLmFjdGl2ZVRleHR1cmUoMzM5ODQpLGUuYmluZEZyYW1lYnVmZmVyKDM2MTYwLG51bGwpLG49PT0hMCYmKGUuYmluZEZyYW1lYnVmZmVyKDM2MDA5LG51bGwpLGUuYmluZEZyYW1lYnVmZmVyKDM2MDA4LG51bGwpKSxlLnVzZVByb2dyYW0obnVsbCksZS5saW5lV2lkdGgoMSksZS5zY2lzc29yKDAsMCxlLmNhbnZhcy53aWR0aCxlLmNhbnZhcy5oZWlnaHQpLGUudmlld3BvcnQoMCwwLGUuY2FudmFzLndpZHRoLGUuY2FudmFzLmhlaWdodCksdT17fSxXPW51bGwsWj17fSxoPXt9LGY9bmV3IFdlYWtNYXAscD1bXSxkPW51bGwsZz0hMSxfPW51bGwseT1udWxsLHg9bnVsbCxiPW51bGwsUz1udWxsLEM9bnVsbCxQPW51bGwsaz0hMSxPPW51bGwsRD1udWxsLEI9bnVsbCxJPW51bGwsTD1udWxsLHN0LnNldCgwLDAsZS5jYW52YXMud2lkdGgsZS5jYW52YXMuaGVpZ2h0KSxTdC5zZXQoMCwwLGUuY2FudmFzLndpZHRoLGUuY2FudmFzLmhlaWdodCkscy5yZXNldCgpLGwucmVzZXQoKSxjLnJlc2V0KCl9cmV0dXJue2J1ZmZlcnM6e2NvbG9yOnMsZGVwdGg6bCxzdGVuY2lsOmN9LGVuYWJsZTpsdCxkaXNhYmxlOkt0LGJpbmRGcmFtZWJ1ZmZlcjpfdCxkcmF3QnVmZmVyczpjdCx1c2VQcm9ncmFtOlgsc2V0QmxlbmRpbmc6cSxzZXRNYXRlcmlhbDpwdCxzZXRGbGlwU2lkZWQ6aHQsc2V0Q3VsbEZhY2U6d3Qsc2V0TGluZVdpZHRoOmt0LHNldFBvbHlnb25PZmZzZXQ6aWUsc2V0U2Npc3NvclRlc3Q6ZWUsYWN0aXZlVGV4dHVyZTpMZSxiaW5kVGV4dHVyZTphcix1bmJpbmRUZXh0dXJlOmZyLGNvbXByZXNzZWRUZXhJbWFnZTJEOnR0LHRleEltYWdlMkQ6YmUsdGV4SW1hZ2UzRDpudCx0ZXhTdG9yYWdlMkQ6aGUsdGV4U3RvcmFnZTNEOlR0LHRleFN1YkltYWdlMkQ6JCx0ZXhTdWJJbWFnZTNEOkl0LGNvbXByZXNzZWRUZXhTdWJJbWFnZTJEOiR0LHNjaXNzb3I6Q3Qsdmlld3BvcnQ6V3QscmVzZXQ6ZmV9fWZ1bmN0aW9uIFRncihlLHQscixuLGksbyxhKXtsZXQgcz1pLmlzV2ViR0wyLGw9aS5tYXhUZXh0dXJlcyxjPWkubWF4Q3ViZW1hcFNpemUsdT1pLm1heFRleHR1cmVTaXplLGg9aS5tYXhTYW1wbGVzLHA9dC5oYXMoIldFQkdMX211bHRpc2FtcGxlZF9yZW5kZXJfdG9fdGV4dHVyZSIpP3QuZ2V0KCJXRUJHTF9tdWx0aXNhbXBsZWRfcmVuZGVyX3RvX3RleHR1cmUiKTp2b2lkIDAsZD1uZXcgV2Vha01hcCxnLF89ITE7dHJ5e189dHlwZW9mIE9mZnNjcmVlbkNhbnZhcyE9InVuZGVmaW5lZCImJm5ldyBPZmZzY3JlZW5DYW52YXMoMSwxKS5nZXRDb250ZXh0KCIyZCIpIT09bnVsbH1jYXRjaCh0dCl7fWZ1bmN0aW9uIHkodHQsJCl7cmV0dXJuIF8/bmV3IE9mZnNjcmVlbkNhbnZhcyh0dCwkKTpRUCgiY2FudmFzIil9ZnVuY3Rpb24geCh0dCwkLEl0LCR0KXtsZXQgaGU9MTtpZigodHQud2lkdGg+JHR8fHR0LmhlaWdodD4kdCkmJihoZT0kdC9NYXRoLm1heCh0dC53aWR0aCx0dC5oZWlnaHQpKSxoZTwxfHwkPT09ITApaWYodHlwZW9mIEhUTUxJbWFnZUVsZW1lbnQhPSJ1bmRlZmluZWQiJiZ0dCBpbnN0YW5jZW9mIEhUTUxJbWFnZUVsZW1lbnR8fHR5cGVvZiBIVE1MQ2FudmFzRWxlbWVudCE9InVuZGVmaW5lZCImJnR0IGluc3RhbmNlb2YgSFRNTENhbnZhc0VsZW1lbnR8fHR5cGVvZiBJbWFnZUJpdG1hcCE9InVuZGVmaW5lZCImJnR0IGluc3RhbmNlb2YgSW1hZ2VCaXRtYXApe2xldCBUdD0kP1JmZTpNYXRoLmZsb29yLGJlPVR0KGhlKnR0LndpZHRoKSxudD1UdChoZSp0dC5oZWlnaHQpO2c9PT12b2lkIDAmJihnPXkoYmUsbnQpKTtsZXQgQ3Q9SXQ/eShiZSxudCk6ZztyZXR1cm4gQ3Qud2lkdGg9YmUsQ3QuaGVpZ2h0PW50LEN0LmdldENvbnRleHQoIjJkIikuZHJhd0ltYWdlKHR0LDAsMCxiZSxudCksY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIGhhcyBiZWVuIHJlc2l6ZWQgZnJvbSAoIit0dC53aWR0aCsieCIrdHQuaGVpZ2h0KyIpIHRvICgiK2JlKyJ4IitudCsiKS4iKSxDdH1lbHNlIHJldHVybiJkYXRhImluIHR0JiZjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IEltYWdlIGluIERhdGFUZXh0dXJlIGlzIHRvbyBiaWcgKCIrdHQud2lkdGgrIngiK3R0LmhlaWdodCsiKS4iKSx0dDtyZXR1cm4gdHR9ZnVuY3Rpb24gYih0dCl7cmV0dXJuIEp1dCh0dC53aWR0aCkmJkp1dCh0dC5oZWlnaHQpfWZ1bmN0aW9uIFModHQpe3JldHVybiBzPyExOnR0LndyYXBTIT09Sm98fHR0LndyYXBUIT09Sm98fHR0Lm1pbkZpbHRlciE9PUxpJiZ0dC5taW5GaWx0ZXIhPT1vaX1mdW5jdGlvbiBDKHR0LCQpe3JldHVybiB0dC5nZW5lcmF0ZU1pcG1hcHMmJiQmJnR0Lm1pbkZpbHRlciE9PUxpJiZ0dC5taW5GaWx0ZXIhPT1vaX1mdW5jdGlvbiBQKHR0KXtlLmdlbmVyYXRlTWlwbWFwKHR0KX1mdW5jdGlvbiBrKHR0LCQsSXQsJHQsaGU9ITEpe2lmKHM9PT0hMSlyZXR1cm4gJDtpZih0dCE9PW51bGwpe2lmKGVbdHRdIT09dm9pZCAwKXJldHVybiBlW3R0XTtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IEF0dGVtcHQgdG8gdXNlIG5vbi1leGlzdGluZyBXZWJHTCBpbnRlcm5hbCBmb3JtYXQgJyIrdHQrIiciKX1sZXQgVHQ9JDtyZXR1cm4gJD09PTY0MDMmJihJdD09PTUxMjYmJihUdD0zMzMyNiksSXQ9PT01MTMxJiYoVHQ9MzMzMjUpLEl0PT09NTEyMSYmKFR0PTMzMzIxKSksJD09PTMzMzE5JiYoSXQ9PT01MTI2JiYoVHQ9MzMzMjgpLEl0PT09NTEzMSYmKFR0PTMzMzI3KSxJdD09PTUxMjEmJihUdD0zMzMyMykpLCQ9PT02NDA4JiYoSXQ9PT01MTI2JiYoVHQ9MzQ4MzYpLEl0PT09NTEzMSYmKFR0PTM0ODQyKSxJdD09PTUxMjEmJihUdD0kdD09PVluJiZoZT09PSExPzM1OTA3OjMyODU2KSxJdD09PTMyODE5JiYoVHQ9MzI4NTQpLEl0PT09MzI4MjAmJihUdD0zMjg1NSkpLChUdD09PTMzMzI1fHxUdD09PTMzMzI2fHxUdD09PTMzMzI3fHxUdD09PTMzMzI4fHxUdD09PTM0ODQyfHxUdD09PTM0ODM2KSYmdC5nZXQoIkVYVF9jb2xvcl9idWZmZXJfZmxvYXQiKSxUdH1mdW5jdGlvbiBPKHR0LCQsSXQpe3JldHVybiBDKHR0LEl0KT09PSEwfHx0dC5pc0ZyYW1lYnVmZmVyVGV4dHVyZSYmdHQubWluRmlsdGVyIT09TGkmJnR0Lm1pbkZpbHRlciE9PW9pP01hdGgubG9nMihNYXRoLm1heCgkLndpZHRoLCQuaGVpZ2h0KSkrMTp0dC5taXBtYXBzIT09dm9pZCAwJiZ0dC5taXBtYXBzLmxlbmd0aD4wP3R0Lm1pcG1hcHMubGVuZ3RoOnR0LmlzQ29tcHJlc3NlZFRleHR1cmUmJkFycmF5LmlzQXJyYXkodHQuaW1hZ2UpPyQubWlwbWFwcy5sZW5ndGg6MX1mdW5jdGlvbiBEKHR0KXtyZXR1cm4gdHQ9PT1MaXx8dHQ9PT1pVXx8dHQ9PT1vVT85NzI4Ojk3Mjl9ZnVuY3Rpb24gQih0dCl7bGV0ICQ9dHQudGFyZ2V0OyQucmVtb3ZlRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsQiksTCgkKSwkLmlzVmlkZW9UZXh0dXJlJiZkLmRlbGV0ZSgkKSxhLm1lbW9yeS50ZXh0dXJlcy0tfWZ1bmN0aW9uIEkodHQpe2xldCAkPXR0LnRhcmdldDskLnJlbW92ZUV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLEkpLFIoJCl9ZnVuY3Rpb24gTCh0dCl7bGV0ICQ9bi5nZXQodHQpOyQuX193ZWJnbEluaXQhPT12b2lkIDAmJihlLmRlbGV0ZVRleHR1cmUoJC5fX3dlYmdsVGV4dHVyZSksbi5yZW1vdmUodHQpKX1mdW5jdGlvbiBSKHR0KXtsZXQgJD10dC50ZXh0dXJlLEl0PW4uZ2V0KHR0KSwkdD1uLmdldCgkKTtpZighIXR0KXtpZigkdC5fX3dlYmdsVGV4dHVyZSE9PXZvaWQgMCYmKGUuZGVsZXRlVGV4dHVyZSgkdC5fX3dlYmdsVGV4dHVyZSksYS5tZW1vcnkudGV4dHVyZXMtLSksdHQuZGVwdGhUZXh0dXJlJiZ0dC5kZXB0aFRleHR1cmUuZGlzcG9zZSgpLHR0LmlzV2ViR0xDdWJlUmVuZGVyVGFyZ2V0KWZvcihsZXQgaGU9MDtoZTw2O2hlKyspZS5kZWxldGVGcmFtZWJ1ZmZlcihJdC5fX3dlYmdsRnJhbWVidWZmZXJbaGVdKSxJdC5fX3dlYmdsRGVwdGhidWZmZXImJmUuZGVsZXRlUmVuZGVyYnVmZmVyKEl0Ll9fd2ViZ2xEZXB0aGJ1ZmZlcltoZV0pO2Vsc2UgZS5kZWxldGVGcmFtZWJ1ZmZlcihJdC5fX3dlYmdsRnJhbWVidWZmZXIpLEl0Ll9fd2ViZ2xEZXB0aGJ1ZmZlciYmZS5kZWxldGVSZW5kZXJidWZmZXIoSXQuX193ZWJnbERlcHRoYnVmZmVyKSxJdC5fX3dlYmdsTXVsdGlzYW1wbGVkRnJhbWVidWZmZXImJmUuZGVsZXRlRnJhbWVidWZmZXIoSXQuX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyKSxJdC5fX3dlYmdsQ29sb3JSZW5kZXJidWZmZXImJmUuZGVsZXRlUmVuZGVyYnVmZmVyKEl0Ll9fd2ViZ2xDb2xvclJlbmRlcmJ1ZmZlciksSXQuX193ZWJnbERlcHRoUmVuZGVyYnVmZmVyJiZlLmRlbGV0ZVJlbmRlcmJ1ZmZlcihJdC5fX3dlYmdsRGVwdGhSZW5kZXJidWZmZXIpO2lmKHR0LmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHMpZm9yKGxldCBoZT0wLFR0PSQubGVuZ3RoO2hlPFR0O2hlKyspe2xldCBiZT1uLmdldCgkW2hlXSk7YmUuX193ZWJnbFRleHR1cmUmJihlLmRlbGV0ZVRleHR1cmUoYmUuX193ZWJnbFRleHR1cmUpLGEubWVtb3J5LnRleHR1cmVzLS0pLG4ucmVtb3ZlKCRbaGVdKX1uLnJlbW92ZSgkKSxuLnJlbW92ZSh0dCl9fWxldCBGPTA7ZnVuY3Rpb24geigpe0Y9MH1mdW5jdGlvbiBVKCl7bGV0IHR0PUY7cmV0dXJuIHR0Pj1sJiZjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMVGV4dHVyZXM6IFRyeWluZyB0byB1c2UgIit0dCsiIHRleHR1cmUgdW5pdHMgd2hpbGUgdGhpcyBHUFUgc3VwcG9ydHMgb25seSAiK2wpLEYrPTEsdHR9ZnVuY3Rpb24gVyh0dCwkKXtsZXQgSXQ9bi5nZXQodHQpO2lmKHR0LmlzVmlkZW9UZXh0dXJlJiZrdCh0dCksdHQudmVyc2lvbj4wJiZJdC5fX3ZlcnNpb24hPT10dC52ZXJzaW9uKXtsZXQgJHQ9dHQuaW1hZ2U7aWYoJHQ9PT12b2lkIDApY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIG1hcmtlZCBmb3IgdXBkYXRlIGJ1dCBpbWFnZSBpcyB1bmRlZmluZWQiKTtlbHNlIGlmKCR0LmNvbXBsZXRlPT09ITEpY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIG1hcmtlZCBmb3IgdXBkYXRlIGJ1dCBpbWFnZSBpcyBpbmNvbXBsZXRlIik7ZWxzZXtsdChJdCx0dCwkKTtyZXR1cm59fXIuYWN0aXZlVGV4dHVyZSgzMzk4NCskKSxyLmJpbmRUZXh0dXJlKDM1NTMsSXQuX193ZWJnbFRleHR1cmUpfWZ1bmN0aW9uIFoodHQsJCl7bGV0IEl0PW4uZ2V0KHR0KTtpZih0dC52ZXJzaW9uPjAmJkl0Ll9fdmVyc2lvbiE9PXR0LnZlcnNpb24pe2x0KEl0LHR0LCQpO3JldHVybn1yLmFjdGl2ZVRleHR1cmUoMzM5ODQrJCksci5iaW5kVGV4dHVyZSgzNTg2NixJdC5fX3dlYmdsVGV4dHVyZSl9ZnVuY3Rpb24gcnQodHQsJCl7bGV0IEl0PW4uZ2V0KHR0KTtpZih0dC52ZXJzaW9uPjAmJkl0Ll9fdmVyc2lvbiE9PXR0LnZlcnNpb24pe2x0KEl0LHR0LCQpO3JldHVybn1yLmFjdGl2ZVRleHR1cmUoMzM5ODQrJCksci5iaW5kVGV4dHVyZSgzMjg3OSxJdC5fX3dlYmdsVGV4dHVyZSl9ZnVuY3Rpb24gb3QodHQsJCl7bGV0IEl0PW4uZ2V0KHR0KTtpZih0dC52ZXJzaW9uPjAmJkl0Ll9fdmVyc2lvbiE9PXR0LnZlcnNpb24pe0t0KEl0LHR0LCQpO3JldHVybn1yLmFjdGl2ZVRleHR1cmUoMzM5ODQrJCksci5iaW5kVGV4dHVyZSgzNDA2NyxJdC5fX3dlYmdsVGV4dHVyZSl9bGV0IHN0PXtbalBdOjEwNDk3LFtKb106MzMwNzEsW1hQXTozMzY0OH0sU3Q9e1tMaV06OTcyOCxbaVVdOjk5ODQsW29VXTo5OTg2LFtvaV06OTcyOSxba2h0XTo5OTg1LFtveF06OTk4N307ZnVuY3Rpb24gYnQodHQsJCxJdCl7aWYoSXQ/KGUudGV4UGFyYW1ldGVyaSh0dCwxMDI0MixzdFskLndyYXBTXSksZS50ZXhQYXJhbWV0ZXJpKHR0LDEwMjQzLHN0WyQud3JhcFRdKSwodHQ9PT0zMjg3OXx8dHQ9PT0zNTg2NikmJmUudGV4UGFyYW1ldGVyaSh0dCwzMjg4MixzdFskLndyYXBSXSksZS50ZXhQYXJhbWV0ZXJpKHR0LDEwMjQwLFN0WyQubWFnRmlsdGVyXSksZS50ZXhQYXJhbWV0ZXJpKHR0LDEwMjQxLFN0WyQubWluRmlsdGVyXSkpOihlLnRleFBhcmFtZXRlcmkodHQsMTAyNDIsMzMwNzEpLGUudGV4UGFyYW1ldGVyaSh0dCwxMDI0MywzMzA3MSksKHR0PT09MzI4Nzl8fHR0PT09MzU4NjYpJiZlLnRleFBhcmFtZXRlcmkodHQsMzI4ODIsMzMwNzEpLCgkLndyYXBTIT09Sm98fCQud3JhcFQhPT1KbykmJmNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogVGV4dHVyZSBpcyBub3QgcG93ZXIgb2YgdHdvLiBUZXh0dXJlLndyYXBTIGFuZCBUZXh0dXJlLndyYXBUIHNob3VsZCBiZSBzZXQgdG8gVEhSRUUuQ2xhbXBUb0VkZ2VXcmFwcGluZy4iKSxlLnRleFBhcmFtZXRlcmkodHQsMTAyNDAsRCgkLm1hZ0ZpbHRlcikpLGUudGV4UGFyYW1ldGVyaSh0dCwxMDI0MSxEKCQubWluRmlsdGVyKSksJC5taW5GaWx0ZXIhPT1MaSYmJC5taW5GaWx0ZXIhPT1vaSYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIGlzIG5vdCBwb3dlciBvZiB0d28uIFRleHR1cmUubWluRmlsdGVyIHNob3VsZCBiZSBzZXQgdG8gVEhSRUUuTmVhcmVzdEZpbHRlciBvciBUSFJFRS5MaW5lYXJGaWx0ZXIuIikpLHQuaGFzKCJFWFRfdGV4dHVyZV9maWx0ZXJfYW5pc290cm9waWMiKT09PSEwKXtsZXQgJHQ9dC5nZXQoIkVYVF90ZXh0dXJlX2ZpbHRlcl9hbmlzb3Ryb3BpYyIpO2lmKCQudHlwZT09PWpkJiZ0LmhhcygiT0VTX3RleHR1cmVfZmxvYXRfbGluZWFyIik9PT0hMXx8cz09PSExJiYkLnR5cGU9PT1DdiYmdC5oYXMoIk9FU190ZXh0dXJlX2hhbGZfZmxvYXRfbGluZWFyIik9PT0hMSlyZXR1cm47KCQuYW5pc290cm9weT4xfHxuLmdldCgkKS5fX2N1cnJlbnRBbmlzb3Ryb3B5KSYmKGUudGV4UGFyYW1ldGVyZih0dCwkdC5URVhUVVJFX01BWF9BTklTT1RST1BZX0VYVCxNYXRoLm1pbigkLmFuaXNvdHJvcHksaS5nZXRNYXhBbmlzb3Ryb3B5KCkpKSxuLmdldCgkKS5fX2N1cnJlbnRBbmlzb3Ryb3B5PSQuYW5pc290cm9weSl9fWZ1bmN0aW9uIE10KHR0LCQpe3R0Ll9fd2ViZ2xJbml0PT09dm9pZCAwJiYodHQuX193ZWJnbEluaXQ9ITAsJC5hZGRFdmVudExpc3RlbmVyKCJkaXNwb3NlIixCKSx0dC5fX3dlYmdsVGV4dHVyZT1lLmNyZWF0ZVRleHR1cmUoKSxhLm1lbW9yeS50ZXh0dXJlcysrKX1mdW5jdGlvbiBsdCh0dCwkLEl0KXtsZXQgJHQ9MzU1MzskLmlzRGF0YVRleHR1cmUyREFycmF5JiYoJHQ9MzU4NjYpLCQuaXNEYXRhVGV4dHVyZTNEJiYoJHQ9MzI4NzkpLE10KHR0LCQpLHIuYWN0aXZlVGV4dHVyZSgzMzk4NCtJdCksci5iaW5kVGV4dHVyZSgkdCx0dC5fX3dlYmdsVGV4dHVyZSksZS5waXhlbFN0b3JlaSgzNzQ0MCwkLmZsaXBZKSxlLnBpeGVsU3RvcmVpKDM3NDQxLCQucHJlbXVsdGlwbHlBbHBoYSksZS5waXhlbFN0b3JlaSgzMzE3LCQudW5wYWNrQWxpZ25tZW50KSxlLnBpeGVsU3RvcmVpKDM3NDQzLDApO2xldCBoZT1TKCQpJiZiKCQuaW1hZ2UpPT09ITEsVHQ9eCgkLmltYWdlLGhlLCExLHUpO1R0PWllKCQsVHQpO2xldCBiZT1iKFR0KXx8cyxudD1vLmNvbnZlcnQoJC5mb3JtYXQsJC5lbmNvZGluZyksQ3Q9by5jb252ZXJ0KCQudHlwZSksV3Q9aygkLmludGVybmFsRm9ybWF0LG50LEN0LCQuZW5jb2RpbmcsJC5pc1ZpZGVvVGV4dHVyZSk7YnQoJHQsJCxiZSk7bGV0IGZlLGF0PSQubWlwbWFwcyxzZT1zJiYkLmlzVmlkZW9UZXh0dXJlIT09ITAsUXQ9dHQuX192ZXJzaW9uPT09dm9pZCAwLENlPU8oJCxUdCxiZSk7aWYoJC5pc0RlcHRoVGV4dHVyZSlXdD02NDAyLHM/JC50eXBlPT09amQ/V3Q9MzYwMTI6JC50eXBlPT09SFA/V3Q9MzMxOTA6JC50eXBlPT09QXY/V3Q9MzUwNTY6V3Q9MzMxODk6JC50eXBlPT09amQmJmNvbnNvbGUuZXJyb3IoIldlYkdMUmVuZGVyZXI6IEZsb2F0aW5nIHBvaW50IGRlcHRoIHRleHR1cmUgcmVxdWlyZXMgV2ViR0wyLiIpLCQuZm9ybWF0PT09ejAmJld0PT09NjQwMiYmJC50eXBlIT09RzMmJiQudHlwZSE9PUhQJiYoY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBVc2UgVW5zaWduZWRTaG9ydFR5cGUgb3IgVW5zaWduZWRJbnRUeXBlIGZvciBEZXB0aEZvcm1hdCBEZXB0aFRleHR1cmUuIiksJC50eXBlPUczLEN0PW8uY29udmVydCgkLnR5cGUpKSwkLmZvcm1hdD09PWt2JiZXdD09PTY0MDImJihXdD0zNDA0MSwkLnR5cGUhPT1BdiYmKGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogVXNlIFVuc2lnbmVkSW50MjQ4VHlwZSBmb3IgRGVwdGhTdGVuY2lsRm9ybWF0IERlcHRoVGV4dHVyZS4iKSwkLnR5cGU9QXYsQ3Q9by5jb252ZXJ0KCQudHlwZSkpKSxzZSYmUXQ/ci50ZXhTdG9yYWdlMkQoMzU1MywxLFd0LFR0LndpZHRoLFR0LmhlaWdodCk6ci50ZXhJbWFnZTJEKDM1NTMsMCxXdCxUdC53aWR0aCxUdC5oZWlnaHQsMCxudCxDdCxudWxsKTtlbHNlIGlmKCQuaXNEYXRhVGV4dHVyZSlpZihhdC5sZW5ndGg+MCYmYmUpe3NlJiZRdCYmci50ZXhTdG9yYWdlMkQoMzU1MyxDZSxXdCxhdFswXS53aWR0aCxhdFswXS5oZWlnaHQpO2ZvcihsZXQgUHQ9MCxOdD1hdC5sZW5ndGg7UHQ8TnQ7UHQrKylmZT1hdFtQdF0sc2U/ci50ZXhTdWJJbWFnZTJEKDM1NTMsMCwwLDAsZmUud2lkdGgsZmUuaGVpZ2h0LG50LEN0LGZlLmRhdGEpOnIudGV4SW1hZ2UyRCgzNTUzLFB0LFd0LGZlLndpZHRoLGZlLmhlaWdodCwwLG50LEN0LGZlLmRhdGEpOyQuZ2VuZXJhdGVNaXBtYXBzPSExfWVsc2Ugc2U/KFF0JiZyLnRleFN0b3JhZ2UyRCgzNTUzLENlLFd0LFR0LndpZHRoLFR0LmhlaWdodCksci50ZXhTdWJJbWFnZTJEKDM1NTMsMCwwLDAsVHQud2lkdGgsVHQuaGVpZ2h0LG50LEN0LFR0LmRhdGEpKTpyLnRleEltYWdlMkQoMzU1MywwLFd0LFR0LndpZHRoLFR0LmhlaWdodCwwLG50LEN0LFR0LmRhdGEpO2Vsc2UgaWYoJC5pc0NvbXByZXNzZWRUZXh0dXJlKXtzZSYmUXQmJnIudGV4U3RvcmFnZTJEKDM1NTMsQ2UsV3QsYXRbMF0ud2lkdGgsYXRbMF0uaGVpZ2h0KTtmb3IobGV0IFB0PTAsTnQ9YXQubGVuZ3RoO1B0PE50O1B0KyspZmU9YXRbUHRdLCQuZm9ybWF0IT09UW8/bnQhPT1udWxsP3NlP3IuY29tcHJlc3NlZFRleFN1YkltYWdlMkQoMzU1MyxQdCwwLDAsZmUud2lkdGgsZmUuaGVpZ2h0LG50LGZlLmRhdGEpOnIuY29tcHJlc3NlZFRleEltYWdlMkQoMzU1MyxQdCxXdCxmZS53aWR0aCxmZS5oZWlnaHQsMCxmZS5kYXRhKTpjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IEF0dGVtcHQgdG8gbG9hZCB1bnN1cHBvcnRlZCBjb21wcmVzc2VkIHRleHR1cmUgZm9ybWF0IGluIC51cGxvYWRUZXh0dXJlKCkiKTpzZT9yLnRleFN1YkltYWdlMkQoMzU1MyxQdCwwLDAsZmUud2lkdGgsZmUuaGVpZ2h0LG50LEN0LGZlLmRhdGEpOnIudGV4SW1hZ2UyRCgzNTUzLFB0LFd0LGZlLndpZHRoLGZlLmhlaWdodCwwLG50LEN0LGZlLmRhdGEpfWVsc2UgaWYoJC5pc0RhdGFUZXh0dXJlMkRBcnJheSlzZT8oUXQmJnIudGV4U3RvcmFnZTNEKDM1ODY2LENlLFd0LFR0LndpZHRoLFR0LmhlaWdodCxUdC5kZXB0aCksci50ZXhTdWJJbWFnZTNEKDM1ODY2LDAsMCwwLDAsVHQud2lkdGgsVHQuaGVpZ2h0LFR0LmRlcHRoLG50LEN0LFR0LmRhdGEpKTpyLnRleEltYWdlM0QoMzU4NjYsMCxXdCxUdC53aWR0aCxUdC5oZWlnaHQsVHQuZGVwdGgsMCxudCxDdCxUdC5kYXRhKTtlbHNlIGlmKCQuaXNEYXRhVGV4dHVyZTNEKXNlPyhRdCYmci50ZXhTdG9yYWdlM0QoMzI4NzksQ2UsV3QsVHQud2lkdGgsVHQuaGVpZ2h0LFR0LmRlcHRoKSxyLnRleFN1YkltYWdlM0QoMzI4NzksMCwwLDAsMCxUdC53aWR0aCxUdC5oZWlnaHQsVHQuZGVwdGgsbnQsQ3QsVHQuZGF0YSkpOnIudGV4SW1hZ2UzRCgzMjg3OSwwLFd0LFR0LndpZHRoLFR0LmhlaWdodCxUdC5kZXB0aCwwLG50LEN0LFR0LmRhdGEpO2Vsc2UgaWYoJC5pc0ZyYW1lYnVmZmVyVGV4dHVyZSlzZSYmUXQ/ci50ZXhTdG9yYWdlMkQoMzU1MyxDZSxXdCxUdC53aWR0aCxUdC5oZWlnaHQpOnIudGV4SW1hZ2UyRCgzNTUzLDAsV3QsVHQud2lkdGgsVHQuaGVpZ2h0LDAsbnQsQ3QsbnVsbCk7ZWxzZSBpZihhdC5sZW5ndGg+MCYmYmUpe3NlJiZRdCYmci50ZXhTdG9yYWdlMkQoMzU1MyxDZSxXdCxhdFswXS53aWR0aCxhdFswXS5oZWlnaHQpO2ZvcihsZXQgUHQ9MCxOdD1hdC5sZW5ndGg7UHQ8TnQ7UHQrKylmZT1hdFtQdF0sc2U/ci50ZXhTdWJJbWFnZTJEKDM1NTMsUHQsMCwwLG50LEN0LGZlKTpyLnRleEltYWdlMkQoMzU1MyxQdCxXdCxudCxDdCxmZSk7JC5nZW5lcmF0ZU1pcG1hcHM9ITF9ZWxzZSBzZT8oUXQmJnIudGV4U3RvcmFnZTJEKDM1NTMsQ2UsV3QsVHQud2lkdGgsVHQuaGVpZ2h0KSxyLnRleFN1YkltYWdlMkQoMzU1MywwLDAsMCxudCxDdCxUdCkpOnIudGV4SW1hZ2UyRCgzNTUzLDAsV3QsbnQsQ3QsVHQpO0MoJCxiZSkmJlAoJHQpLHR0Ll9fdmVyc2lvbj0kLnZlcnNpb24sJC5vblVwZGF0ZSYmJC5vblVwZGF0ZSgkKX1mdW5jdGlvbiBLdCh0dCwkLEl0KXtpZigkLmltYWdlLmxlbmd0aCE9PTYpcmV0dXJuO010KHR0LCQpLHIuYWN0aXZlVGV4dHVyZSgzMzk4NCtJdCksci5iaW5kVGV4dHVyZSgzNDA2Nyx0dC5fX3dlYmdsVGV4dHVyZSksZS5waXhlbFN0b3JlaSgzNzQ0MCwkLmZsaXBZKSxlLnBpeGVsU3RvcmVpKDM3NDQxLCQucHJlbXVsdGlwbHlBbHBoYSksZS5waXhlbFN0b3JlaSgzMzE3LCQudW5wYWNrQWxpZ25tZW50KSxlLnBpeGVsU3RvcmVpKDM3NDQzLDApO2xldCAkdD0kJiYoJC5pc0NvbXByZXNzZWRUZXh0dXJlfHwkLmltYWdlWzBdLmlzQ29tcHJlc3NlZFRleHR1cmUpLGhlPSQuaW1hZ2VbMF0mJiQuaW1hZ2VbMF0uaXNEYXRhVGV4dHVyZSxUdD1bXTtmb3IobGV0IFB0PTA7UHQ8NjtQdCsrKSEkdCYmIWhlP1R0W1B0XT14KCQuaW1hZ2VbUHRdLCExLCEwLGMpOlR0W1B0XT1oZT8kLmltYWdlW1B0XS5pbWFnZTokLmltYWdlW1B0XSxUdFtQdF09aWUoJCxUdFtQdF0pO2xldCBiZT1UdFswXSxudD1iKGJlKXx8cyxDdD1vLmNvbnZlcnQoJC5mb3JtYXQsJC5lbmNvZGluZyksV3Q9by5jb252ZXJ0KCQudHlwZSksZmU9aygkLmludGVybmFsRm9ybWF0LEN0LFd0LCQuZW5jb2RpbmcpLGF0PXMmJiQuaXNWaWRlb1RleHR1cmUhPT0hMCxzZT10dC5fX3ZlcnNpb249PT12b2lkIDAsUXQ9TygkLGJlLG50KTtidCgzNDA2NywkLG50KTtsZXQgQ2U7aWYoJHQpe2F0JiZzZSYmci50ZXhTdG9yYWdlMkQoMzQwNjcsUXQsZmUsYmUud2lkdGgsYmUuaGVpZ2h0KTtmb3IobGV0IFB0PTA7UHQ8NjtQdCsrKXtDZT1UdFtQdF0ubWlwbWFwcztmb3IobGV0IE50PTA7TnQ8Q2UubGVuZ3RoO050Kyspe2xldCB6ZT1DZVtOdF07JC5mb3JtYXQhPT1Rbz9DdCE9PW51bGw/YXQ/ci5jb21wcmVzc2VkVGV4U3ViSW1hZ2UyRCgzNDA2OStQdCxOdCwwLDAsemUud2lkdGgsemUuaGVpZ2h0LEN0LHplLmRhdGEpOnIuY29tcHJlc3NlZFRleEltYWdlMkQoMzQwNjkrUHQsTnQsZmUsemUud2lkdGgsemUuaGVpZ2h0LDAsemUuZGF0YSk6Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBBdHRlbXB0IHRvIGxvYWQgdW5zdXBwb3J0ZWQgY29tcHJlc3NlZCB0ZXh0dXJlIGZvcm1hdCBpbiAuc2V0VGV4dHVyZUN1YmUoKSIpOmF0P3IudGV4U3ViSW1hZ2UyRCgzNDA2OStQdCxOdCwwLDAsemUud2lkdGgsemUuaGVpZ2h0LEN0LFd0LHplLmRhdGEpOnIudGV4SW1hZ2UyRCgzNDA2OStQdCxOdCxmZSx6ZS53aWR0aCx6ZS5oZWlnaHQsMCxDdCxXdCx6ZS5kYXRhKX19fWVsc2V7Q2U9JC5taXBtYXBzLGF0JiZzZSYmKENlLmxlbmd0aD4wJiZRdCsrLHIudGV4U3RvcmFnZTJEKDM0MDY3LFF0LGZlLFR0WzBdLndpZHRoLFR0WzBdLmhlaWdodCkpO2ZvcihsZXQgUHQ9MDtQdDw2O1B0KyspaWYoaGUpe2F0P3IudGV4U3ViSW1hZ2UyRCgzNDA2OStQdCwwLDAsMCxUdFtQdF0ud2lkdGgsVHRbUHRdLmhlaWdodCxDdCxXdCxUdFtQdF0uZGF0YSk6ci50ZXhJbWFnZTJEKDM0MDY5K1B0LDAsZmUsVHRbUHRdLndpZHRoLFR0W1B0XS5oZWlnaHQsMCxDdCxXdCxUdFtQdF0uZGF0YSk7Zm9yKGxldCBOdD0wO050PENlLmxlbmd0aDtOdCsrKXtsZXQgeW49Q2VbTnRdLmltYWdlW1B0XS5pbWFnZTthdD9yLnRleFN1YkltYWdlMkQoMzQwNjkrUHQsTnQrMSwwLDAseW4ud2lkdGgseW4uaGVpZ2h0LEN0LFd0LHluLmRhdGEpOnIudGV4SW1hZ2UyRCgzNDA2OStQdCxOdCsxLGZlLHluLndpZHRoLHluLmhlaWdodCwwLEN0LFd0LHluLmRhdGEpfX1lbHNle2F0P3IudGV4U3ViSW1hZ2UyRCgzNDA2OStQdCwwLDAsMCxDdCxXdCxUdFtQdF0pOnIudGV4SW1hZ2UyRCgzNDA2OStQdCwwLGZlLEN0LFd0LFR0W1B0XSk7Zm9yKGxldCBOdD0wO050PENlLmxlbmd0aDtOdCsrKXtsZXQgemU9Q2VbTnRdO2F0P3IudGV4U3ViSW1hZ2UyRCgzNDA2OStQdCxOdCsxLDAsMCxDdCxXdCx6ZS5pbWFnZVtQdF0pOnIudGV4SW1hZ2UyRCgzNDA2OStQdCxOdCsxLGZlLEN0LFd0LHplLmltYWdlW1B0XSl9fX1DKCQsbnQpJiZQKDM0MDY3KSx0dC5fX3ZlcnNpb249JC52ZXJzaW9uLCQub25VcGRhdGUmJiQub25VcGRhdGUoJCl9ZnVuY3Rpb24gX3QodHQsJCxJdCwkdCxoZSl7bGV0IFR0PW8uY29udmVydChJdC5mb3JtYXQsSXQuZW5jb2RpbmcpLGJlPW8uY29udmVydChJdC50eXBlKSxudD1rKEl0LmludGVybmFsRm9ybWF0LFR0LGJlLEl0LmVuY29kaW5nKTtuLmdldCgkKS5fX2hhc0V4dGVybmFsVGV4dHVyZXN8fChoZT09PTMyODc5fHxoZT09PTM1ODY2P3IudGV4SW1hZ2UzRChoZSwwLG50LCQud2lkdGgsJC5oZWlnaHQsJC5kZXB0aCwwLFR0LGJlLG51bGwpOnIudGV4SW1hZ2UyRChoZSwwLG50LCQud2lkdGgsJC5oZWlnaHQsMCxUdCxiZSxudWxsKSksci5iaW5kRnJhbWVidWZmZXIoMzYxNjAsdHQpLCQudXNlUmVuZGVyVG9UZXh0dXJlP3AuZnJhbWVidWZmZXJUZXh0dXJlMkRNdWx0aXNhbXBsZUVYVCgzNjE2MCwkdCxoZSxuLmdldChJdCkuX193ZWJnbFRleHR1cmUsMCx3dCgkKSk6ZS5mcmFtZWJ1ZmZlclRleHR1cmUyRCgzNjE2MCwkdCxoZSxuLmdldChJdCkuX193ZWJnbFRleHR1cmUsMCksci5iaW5kRnJhbWVidWZmZXIoMzYxNjAsbnVsbCl9ZnVuY3Rpb24gY3QodHQsJCxJdCl7aWYoZS5iaW5kUmVuZGVyYnVmZmVyKDM2MTYxLHR0KSwkLmRlcHRoQnVmZmVyJiYhJC5zdGVuY2lsQnVmZmVyKXtsZXQgJHQ9MzMxODk7aWYoSXR8fCQudXNlUmVuZGVyVG9UZXh0dXJlKXtsZXQgaGU9JC5kZXB0aFRleHR1cmU7aGUmJmhlLmlzRGVwdGhUZXh0dXJlJiYoaGUudHlwZT09PWpkPyR0PTM2MDEyOmhlLnR5cGU9PT1IUCYmKCR0PTMzMTkwKSk7bGV0IFR0PXd0KCQpOyQudXNlUmVuZGVyVG9UZXh0dXJlP3AucmVuZGVyYnVmZmVyU3RvcmFnZU11bHRpc2FtcGxlRVhUKDM2MTYxLFR0LCR0LCQud2lkdGgsJC5oZWlnaHQpOmUucmVuZGVyYnVmZmVyU3RvcmFnZU11bHRpc2FtcGxlKDM2MTYxLFR0LCR0LCQud2lkdGgsJC5oZWlnaHQpfWVsc2UgZS5yZW5kZXJidWZmZXJTdG9yYWdlKDM2MTYxLCR0LCQud2lkdGgsJC5oZWlnaHQpO2UuZnJhbWVidWZmZXJSZW5kZXJidWZmZXIoMzYxNjAsMzYwOTYsMzYxNjEsdHQpfWVsc2UgaWYoJC5kZXB0aEJ1ZmZlciYmJC5zdGVuY2lsQnVmZmVyKXtsZXQgJHQ9d3QoJCk7SXQmJiQudXNlUmVuZGVyYnVmZmVyP2UucmVuZGVyYnVmZmVyU3RvcmFnZU11bHRpc2FtcGxlKDM2MTYxLCR0LDM1MDU2LCQud2lkdGgsJC5oZWlnaHQpOiQudXNlUmVuZGVyVG9UZXh0dXJlP3AucmVuZGVyYnVmZmVyU3RvcmFnZU11bHRpc2FtcGxlRVhUKDM2MTYxLCR0LDM1MDU2LCQud2lkdGgsJC5oZWlnaHQpOmUucmVuZGVyYnVmZmVyU3RvcmFnZSgzNjE2MSwzNDA0MSwkLndpZHRoLCQuaGVpZ2h0KSxlLmZyYW1lYnVmZmVyUmVuZGVyYnVmZmVyKDM2MTYwLDMzMzA2LDM2MTYxLHR0KX1lbHNle2xldCAkdD0kLmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHM9PT0hMD8kLnRleHR1cmVbMF06JC50ZXh0dXJlLGhlPW8uY29udmVydCgkdC5mb3JtYXQsJHQuZW5jb2RpbmcpLFR0PW8uY29udmVydCgkdC50eXBlKSxiZT1rKCR0LmludGVybmFsRm9ybWF0LGhlLFR0LCR0LmVuY29kaW5nKSxudD13dCgkKTtJdCYmJC51c2VSZW5kZXJidWZmZXI/ZS5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGUoMzYxNjEsbnQsYmUsJC53aWR0aCwkLmhlaWdodCk6JC51c2VSZW5kZXJUb1RleHR1cmU/cC5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGVFWFQoMzYxNjEsbnQsYmUsJC53aWR0aCwkLmhlaWdodCk6ZS5yZW5kZXJidWZmZXJTdG9yYWdlKDM2MTYxLGJlLCQud2lkdGgsJC5oZWlnaHQpfWUuYmluZFJlbmRlcmJ1ZmZlcigzNjE2MSxudWxsKX1mdW5jdGlvbiBYKHR0LCQpe2lmKCQmJiQuaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQpdGhyb3cgbmV3IEVycm9yKCJEZXB0aCBUZXh0dXJlIHdpdGggY3ViZSByZW5kZXIgdGFyZ2V0cyBpcyBub3Qgc3VwcG9ydGVkIik7aWYoci5iaW5kRnJhbWVidWZmZXIoMzYxNjAsdHQpLCEoJC5kZXB0aFRleHR1cmUmJiQuZGVwdGhUZXh0dXJlLmlzRGVwdGhUZXh0dXJlKSl0aHJvdyBuZXcgRXJyb3IoInJlbmRlclRhcmdldC5kZXB0aFRleHR1cmUgbXVzdCBiZSBhbiBpbnN0YW5jZSBvZiBUSFJFRS5EZXB0aFRleHR1cmUiKTsoIW4uZ2V0KCQuZGVwdGhUZXh0dXJlKS5fX3dlYmdsVGV4dHVyZXx8JC5kZXB0aFRleHR1cmUuaW1hZ2Uud2lkdGghPT0kLndpZHRofHwkLmRlcHRoVGV4dHVyZS5pbWFnZS5oZWlnaHQhPT0kLmhlaWdodCkmJigkLmRlcHRoVGV4dHVyZS5pbWFnZS53aWR0aD0kLndpZHRoLCQuZGVwdGhUZXh0dXJlLmltYWdlLmhlaWdodD0kLmhlaWdodCwkLmRlcHRoVGV4dHVyZS5uZWVkc1VwZGF0ZT0hMCksVygkLmRlcHRoVGV4dHVyZSwwKTtsZXQgJHQ9bi5nZXQoJC5kZXB0aFRleHR1cmUpLl9fd2ViZ2xUZXh0dXJlLGhlPXd0KCQpO2lmKCQuZGVwdGhUZXh0dXJlLmZvcm1hdD09PXowKSQudXNlUmVuZGVyVG9UZXh0dXJlP3AuZnJhbWVidWZmZXJUZXh0dXJlMkRNdWx0aXNhbXBsZUVYVCgzNjE2MCwzNjA5NiwzNTUzLCR0LDAsaGUpOmUuZnJhbWVidWZmZXJUZXh0dXJlMkQoMzYxNjAsMzYwOTYsMzU1MywkdCwwKTtlbHNlIGlmKCQuZGVwdGhUZXh0dXJlLmZvcm1hdD09PWt2KSQudXNlUmVuZGVyVG9UZXh0dXJlP3AuZnJhbWVidWZmZXJUZXh0dXJlMkRNdWx0aXNhbXBsZUVYVCgzNjE2MCwzMzMwNiwzNTUzLCR0LDAsaGUpOmUuZnJhbWVidWZmZXJUZXh0dXJlMkQoMzYxNjAsMzMzMDYsMzU1MywkdCwwKTtlbHNlIHRocm93IG5ldyBFcnJvcigiVW5rbm93biBkZXB0aFRleHR1cmUgZm9ybWF0Iil9ZnVuY3Rpb24gZXQodHQpe2xldCAkPW4uZ2V0KHR0KSxJdD10dC5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldD09PSEwO2lmKHR0LmRlcHRoVGV4dHVyZSYmISQuX19hdXRvQWxsb2NhdGVEZXB0aEJ1ZmZlcil7aWYoSXQpdGhyb3cgbmV3IEVycm9yKCJ0YXJnZXQuZGVwdGhUZXh0dXJlIG5vdCBzdXBwb3J0ZWQgaW4gQ3ViZSByZW5kZXIgdGFyZ2V0cyIpO1goJC5fX3dlYmdsRnJhbWVidWZmZXIsdHQpfWVsc2UgaWYoSXQpeyQuX193ZWJnbERlcHRoYnVmZmVyPVtdO2ZvcihsZXQgJHQ9MDskdDw2OyR0Kyspci5iaW5kRnJhbWVidWZmZXIoMzYxNjAsJC5fX3dlYmdsRnJhbWVidWZmZXJbJHRdKSwkLl9fd2ViZ2xEZXB0aGJ1ZmZlclskdF09ZS5jcmVhdGVSZW5kZXJidWZmZXIoKSxjdCgkLl9fd2ViZ2xEZXB0aGJ1ZmZlclskdF0sdHQsITEpfWVsc2Ugci5iaW5kRnJhbWVidWZmZXIoMzYxNjAsJC5fX3dlYmdsRnJhbWVidWZmZXIpLCQuX193ZWJnbERlcHRoYnVmZmVyPWUuY3JlYXRlUmVuZGVyYnVmZmVyKCksY3QoJC5fX3dlYmdsRGVwdGhidWZmZXIsdHQsITEpO3IuYmluZEZyYW1lYnVmZmVyKDM2MTYwLG51bGwpfWZ1bmN0aW9uIGR0KHR0LCQsSXQpe2xldCAkdD1uLmdldCh0dCk7JCE9PXZvaWQgMCYmX3QoJHQuX193ZWJnbEZyYW1lYnVmZmVyLHR0LHR0LnRleHR1cmUsMzYwNjQsMzU1MyksSXQhPT12b2lkIDAmJmV0KHR0KX1mdW5jdGlvbiBxKHR0KXtsZXQgJD10dC50ZXh0dXJlLEl0PW4uZ2V0KHR0KSwkdD1uLmdldCgkKTt0dC5hZGRFdmVudExpc3RlbmVyKCJkaXNwb3NlIixJKSx0dC5pc1dlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzIT09ITAmJigkdC5fX3dlYmdsVGV4dHVyZT09PXZvaWQgMCYmKCR0Ll9fd2ViZ2xUZXh0dXJlPWUuY3JlYXRlVGV4dHVyZSgpKSwkdC5fX3ZlcnNpb249JC52ZXJzaW9uLGEubWVtb3J5LnRleHR1cmVzKyspO2xldCBoZT10dC5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldD09PSEwLFR0PXR0LmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHM9PT0hMCxiZT0kLmlzRGF0YVRleHR1cmUzRHx8JC5pc0RhdGFUZXh0dXJlMkRBcnJheSxudD1iKHR0KXx8cztpZihoZSl7SXQuX193ZWJnbEZyYW1lYnVmZmVyPVtdO2ZvcihsZXQgQ3Q9MDtDdDw2O0N0KyspSXQuX193ZWJnbEZyYW1lYnVmZmVyW0N0XT1lLmNyZWF0ZUZyYW1lYnVmZmVyKCl9ZWxzZSBpZihJdC5fX3dlYmdsRnJhbWVidWZmZXI9ZS5jcmVhdGVGcmFtZWJ1ZmZlcigpLFR0KWlmKGkuZHJhd0J1ZmZlcnMpe2xldCBDdD10dC50ZXh0dXJlO2ZvcihsZXQgV3Q9MCxmZT1DdC5sZW5ndGg7V3Q8ZmU7V3QrKyl7bGV0IGF0PW4uZ2V0KEN0W1d0XSk7YXQuX193ZWJnbFRleHR1cmU9PT12b2lkIDAmJihhdC5fX3dlYmdsVGV4dHVyZT1lLmNyZWF0ZVRleHR1cmUoKSxhLm1lbW9yeS50ZXh0dXJlcysrKX19ZWxzZSBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IFdlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzIGNhbiBvbmx5IGJlIHVzZWQgd2l0aCBXZWJHTDIgb3IgV0VCR0xfZHJhd19idWZmZXJzIGV4dGVuc2lvbi4iKTtlbHNlIGlmKHR0LnVzZVJlbmRlcmJ1ZmZlcilpZihzKXtJdC5fX3dlYmdsTXVsdGlzYW1wbGVkRnJhbWVidWZmZXI9ZS5jcmVhdGVGcmFtZWJ1ZmZlcigpLEl0Ll9fd2ViZ2xDb2xvclJlbmRlcmJ1ZmZlcj1lLmNyZWF0ZVJlbmRlcmJ1ZmZlcigpLGUuYmluZFJlbmRlcmJ1ZmZlcigzNjE2MSxJdC5fX3dlYmdsQ29sb3JSZW5kZXJidWZmZXIpO2xldCBDdD1vLmNvbnZlcnQoJC5mb3JtYXQsJC5lbmNvZGluZyksV3Q9by5jb252ZXJ0KCQudHlwZSksZmU9aygkLmludGVybmFsRm9ybWF0LEN0LFd0LCQuZW5jb2RpbmcpLGF0PXd0KHR0KTtlLnJlbmRlcmJ1ZmZlclN0b3JhZ2VNdWx0aXNhbXBsZSgzNjE2MSxhdCxmZSx0dC53aWR0aCx0dC5oZWlnaHQpLHIuYmluZEZyYW1lYnVmZmVyKDM2MTYwLEl0Ll9fd2ViZ2xNdWx0aXNhbXBsZWRGcmFtZWJ1ZmZlciksZS5mcmFtZWJ1ZmZlclJlbmRlcmJ1ZmZlcigzNjE2MCwzNjA2NCwzNjE2MSxJdC5fX3dlYmdsQ29sb3JSZW5kZXJidWZmZXIpLGUuYmluZFJlbmRlcmJ1ZmZlcigzNjE2MSxudWxsKSx0dC5kZXB0aEJ1ZmZlciYmKEl0Ll9fd2ViZ2xEZXB0aFJlbmRlcmJ1ZmZlcj1lLmNyZWF0ZVJlbmRlcmJ1ZmZlcigpLGN0KEl0Ll9fd2ViZ2xEZXB0aFJlbmRlcmJ1ZmZlcix0dCwhMCkpLHIuYmluZEZyYW1lYnVmZmVyKDM2MTYwLG51bGwpfWVsc2UgY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBXZWJHTE11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0IGNhbiBvbmx5IGJlIHVzZWQgd2l0aCBXZWJHTDIuIik7aWYoaGUpe3IuYmluZFRleHR1cmUoMzQwNjcsJHQuX193ZWJnbFRleHR1cmUpLGJ0KDM0MDY3LCQsbnQpO2ZvcihsZXQgQ3Q9MDtDdDw2O0N0KyspX3QoSXQuX193ZWJnbEZyYW1lYnVmZmVyW0N0XSx0dCwkLDM2MDY0LDM0MDY5K0N0KTtDKCQsbnQpJiZQKDM0MDY3KSxyLnVuYmluZFRleHR1cmUoKX1lbHNlIGlmKFR0KXtsZXQgQ3Q9dHQudGV4dHVyZTtmb3IobGV0IFd0PTAsZmU9Q3QubGVuZ3RoO1d0PGZlO1d0Kyspe2xldCBhdD1DdFtXdF0sc2U9bi5nZXQoYXQpO3IuYmluZFRleHR1cmUoMzU1MyxzZS5fX3dlYmdsVGV4dHVyZSksYnQoMzU1MyxhdCxudCksX3QoSXQuX193ZWJnbEZyYW1lYnVmZmVyLHR0LGF0LDM2MDY0K1d0LDM1NTMpLEMoYXQsbnQpJiZQKDM1NTMpfXIudW5iaW5kVGV4dHVyZSgpfWVsc2V7bGV0IEN0PTM1NTM7YmUmJihzP0N0PSQuaXNEYXRhVGV4dHVyZTNEPzMyODc5OjM1ODY2OmNvbnNvbGUud2FybigiVEhSRUUuRGF0YVRleHR1cmUzRCBhbmQgVEhSRUUuRGF0YVRleHR1cmUyREFycmF5IG9ubHkgc3VwcG9ydGVkIHdpdGggV2ViR0wyLiIpKSxyLmJpbmRUZXh0dXJlKEN0LCR0Ll9fd2ViZ2xUZXh0dXJlKSxidChDdCwkLG50KSxfdChJdC5fX3dlYmdsRnJhbWVidWZmZXIsdHQsJCwzNjA2NCxDdCksQygkLG50KSYmUChDdCksci51bmJpbmRUZXh0dXJlKCl9dHQuZGVwdGhCdWZmZXImJmV0KHR0KX1mdW5jdGlvbiBwdCh0dCl7bGV0ICQ9Yih0dCl8fHMsSXQ9dHQuaXNXZWJHTE11bHRpcGxlUmVuZGVyVGFyZ2V0cz09PSEwP3R0LnRleHR1cmU6W3R0LnRleHR1cmVdO2ZvcihsZXQgJHQ9MCxoZT1JdC5sZW5ndGg7JHQ8aGU7JHQrKyl7bGV0IFR0PUl0WyR0XTtpZihDKFR0LCQpKXtsZXQgYmU9dHQuaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQ/MzQwNjc6MzU1MyxudD1uLmdldChUdCkuX193ZWJnbFRleHR1cmU7ci5iaW5kVGV4dHVyZShiZSxudCksUChiZSksci51bmJpbmRUZXh0dXJlKCl9fX1mdW5jdGlvbiBodCh0dCl7aWYodHQudXNlUmVuZGVyYnVmZmVyKWlmKHMpe2xldCAkPXR0LndpZHRoLEl0PXR0LmhlaWdodCwkdD0xNjM4NCxoZT1bMzYwNjRdLFR0PXR0LnN0ZW5jaWxCdWZmZXI/MzMzMDY6MzYwOTY7dHQuZGVwdGhCdWZmZXImJmhlLnB1c2goVHQpLHR0Lmlnbm9yZURlcHRoRm9yTXVsdGlzYW1wbGVDb3B5fHwodHQuZGVwdGhCdWZmZXImJigkdHw9MjU2KSx0dC5zdGVuY2lsQnVmZmVyJiYoJHR8PTEwMjQpKTtsZXQgYmU9bi5nZXQodHQpO3IuYmluZEZyYW1lYnVmZmVyKDM2MDA4LGJlLl9fd2ViZ2xNdWx0aXNhbXBsZWRGcmFtZWJ1ZmZlciksci5iaW5kRnJhbWVidWZmZXIoMzYwMDksYmUuX193ZWJnbEZyYW1lYnVmZmVyKSx0dC5pZ25vcmVEZXB0aEZvck11bHRpc2FtcGxlQ29weSYmKGUuaW52YWxpZGF0ZUZyYW1lYnVmZmVyKDM2MDA4LFtUdF0pLGUuaW52YWxpZGF0ZUZyYW1lYnVmZmVyKDM2MDA5LFtUdF0pKSxlLmJsaXRGcmFtZWJ1ZmZlcigwLDAsJCxJdCwwLDAsJCxJdCwkdCw5NzI4KSxlLmludmFsaWRhdGVGcmFtZWJ1ZmZlcigzNjAwOCxoZSksci5iaW5kRnJhbWVidWZmZXIoMzYwMDgsbnVsbCksci5iaW5kRnJhbWVidWZmZXIoMzYwMDksYmUuX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyKX1lbHNlIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogV2ViR0xNdWx0aXNhbXBsZVJlbmRlclRhcmdldCBjYW4gb25seSBiZSB1c2VkIHdpdGggV2ViR0wyLiIpfWZ1bmN0aW9uIHd0KHR0KXtyZXR1cm4gcyYmKHR0LnVzZVJlbmRlcmJ1ZmZlcnx8dHQudXNlUmVuZGVyVG9UZXh0dXJlKT9NYXRoLm1pbihoLHR0LnNhbXBsZXMpOjB9ZnVuY3Rpb24ga3QodHQpe2xldCAkPWEucmVuZGVyLmZyYW1lO2QuZ2V0KHR0KSE9PSQmJihkLnNldCh0dCwkKSx0dC51cGRhdGUoKSl9ZnVuY3Rpb24gaWUodHQsJCl7bGV0IEl0PXR0LmVuY29kaW5nLCR0PXR0LmZvcm1hdCxoZT10dC50eXBlO3JldHVybiB0dC5pc0NvbXByZXNzZWRUZXh0dXJlPT09ITB8fHR0LmlzVmlkZW9UZXh0dXJlPT09ITB8fHR0LmZvcm1hdD09PWFVfHxJdCE9PVFkJiYoSXQ9PT1Zbj9zPT09ITE/dC5oYXMoIkVYVF9zUkdCIik9PT0hMCYmJHQ9PT1Rbz8odHQuZm9ybWF0PWFVLHR0Lm1pbkZpbHRlcj1vaSx0dC5nZW5lcmF0ZU1pcG1hcHM9ITEpOiQ9S2Yuc1JHQlRvTGluZWFyKCQpOigkdCE9PVFvfHxoZSE9PVpkKSYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFRleHR1cmVzOiBzUkdCIGVuY29kZWQgdGV4dHVyZXMgaGF2ZSB0byB1c2UgUkdCQUZvcm1hdCBhbmQgVW5zaWduZWRCeXRlVHlwZS4iKTpjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFRleHR1cmVzOiBVbnN1cHBvcnRlZCB0ZXh0dXJlIGVuY29kaW5nOiIsSXQpKSwkfWxldCBlZT0hMSxMZT0hMTtmdW5jdGlvbiBhcih0dCwkKXt0dCYmdHQuaXNXZWJHTFJlbmRlclRhcmdldCYmKGVlPT09ITEmJihjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMVGV4dHVyZXMuc2FmZVNldFRleHR1cmUyRDogZG9uJ3QgdXNlIHJlbmRlciB0YXJnZXRzIGFzIHRleHR1cmVzLiBVc2UgdGhlaXIgLnRleHR1cmUgcHJvcGVydHkgaW5zdGVhZC4iKSxlZT0hMCksdHQ9dHQudGV4dHVyZSksVyh0dCwkKX1mdW5jdGlvbiBmcih0dCwkKXt0dCYmdHQuaXNXZWJHTEN1YmVSZW5kZXJUYXJnZXQmJihMZT09PSExJiYoY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFRleHR1cmVzLnNhZmVTZXRUZXh0dXJlQ3ViZTogZG9uJ3QgdXNlIGN1YmUgcmVuZGVyIHRhcmdldHMgYXMgdGV4dHVyZXMuIFVzZSB0aGVpciAudGV4dHVyZSBwcm9wZXJ0eSBpbnN0ZWFkLiIpLExlPSEwKSx0dD10dC50ZXh0dXJlKSxvdCh0dCwkKX10aGlzLmFsbG9jYXRlVGV4dHVyZVVuaXQ9VSx0aGlzLnJlc2V0VGV4dHVyZVVuaXRzPXosdGhpcy5zZXRUZXh0dXJlMkQ9Vyx0aGlzLnNldFRleHR1cmUyREFycmF5PVosdGhpcy5zZXRUZXh0dXJlM0Q9cnQsdGhpcy5zZXRUZXh0dXJlQ3ViZT1vdCx0aGlzLnJlYmluZFRleHR1cmVzPWR0LHRoaXMuc2V0dXBSZW5kZXJUYXJnZXQ9cSx0aGlzLnVwZGF0ZVJlbmRlclRhcmdldE1pcG1hcD1wdCx0aGlzLnVwZGF0ZU11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0PWh0LHRoaXMuc2V0dXBEZXB0aFJlbmRlcmJ1ZmZlcj1ldCx0aGlzLnNldHVwRnJhbWVCdWZmZXJUZXh0dXJlPV90LHRoaXMuc2FmZVNldFRleHR1cmUyRD1hcix0aGlzLnNhZmVTZXRUZXh0dXJlQ3ViZT1mcn1mdW5jdGlvbiBYZmUoZSx0LHIpe2xldCBuPXIuaXNXZWJHTDI7ZnVuY3Rpb24gaShvLGE9bnVsbCl7bGV0IHM7aWYobz09PVpkKXJldHVybiA1MTIxO2lmKG89PT1wZmUpcmV0dXJuIDMyODE5O2lmKG89PT1kZmUpcmV0dXJuIDMyODIwO2lmKG89PT11ZmUpcmV0dXJuIDUxMjA7aWYobz09PWhmZSlyZXR1cm4gNTEyMjtpZihvPT09RzMpcmV0dXJuIDUxMjM7aWYobz09PWZmZSlyZXR1cm4gNTEyNDtpZihvPT09SFApcmV0dXJuIDUxMjU7aWYobz09PWpkKXJldHVybiA1MTI2O2lmKG89PT1DdilyZXR1cm4gbj81MTMxOihzPXQuZ2V0KCJPRVNfdGV4dHVyZV9oYWxmX2Zsb2F0IikscyE9PW51bGw/cy5IQUxGX0ZMT0FUX09FUzpudWxsKTtpZihvPT09bWZlKXJldHVybiA2NDA2O2lmKG89PT1RbylyZXR1cm4gNjQwODtpZihvPT09Z2ZlKXJldHVybiA2NDA5O2lmKG89PT1fZmUpcmV0dXJuIDY0MTA7aWYobz09PXowKXJldHVybiA2NDAyO2lmKG89PT1rdilyZXR1cm4gMzQwNDE7aWYobz09PXlmZSlyZXR1cm4gNjQwMztpZihvPT09YVUpcmV0dXJuIHM9dC5nZXQoIkVYVF9zUkdCIikscyE9PW51bGw/cy5TUkdCX0FMUEhBX0VYVDpudWxsO2lmKG89PT12ZmUpcmV0dXJuIDM2MjQ0O2lmKG89PT14ZmUpcmV0dXJuIDMzMzE5O2lmKG89PT1iZmUpcmV0dXJuIDMzMzIwO2lmKG89PT13ZmUpcmV0dXJuIDM2MjQ5O2lmKG89PT1aVnx8bz09PUpWfHxvPT09UVZ8fG89PT10VSlpZihhPT09WW4paWYocz10LmdldCgiV0VCR0xfY29tcHJlc3NlZF90ZXh0dXJlX3MzdGNfc3JnYiIpLHMhPT1udWxsKXtpZihvPT09WlYpcmV0dXJuIHMuQ09NUFJFU1NFRF9TUkdCX1MzVENfRFhUMV9FWFQ7aWYobz09PUpWKXJldHVybiBzLkNPTVBSRVNTRURfU1JHQl9BTFBIQV9TM1RDX0RYVDFfRVhUO2lmKG89PT1RVilyZXR1cm4gcy5DT01QUkVTU0VEX1NSR0JfQUxQSEFfUzNUQ19EWFQzX0VYVDtpZihvPT09dFUpcmV0dXJuIHMuQ09NUFJFU1NFRF9TUkdCX0FMUEhBX1MzVENfRFhUNV9FWFR9ZWxzZSByZXR1cm4gbnVsbDtlbHNlIGlmKHM9dC5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjIikscyE9PW51bGwpe2lmKG89PT1aVilyZXR1cm4gcy5DT01QUkVTU0VEX1JHQl9TM1RDX0RYVDFfRVhUO2lmKG89PT1KVilyZXR1cm4gcy5DT01QUkVTU0VEX1JHQkFfUzNUQ19EWFQxX0VYVDtpZihvPT09UVYpcmV0dXJuIHMuQ09NUFJFU1NFRF9SR0JBX1MzVENfRFhUM19FWFQ7aWYobz09PXRVKXJldHVybiBzLkNPTVBSRVNTRURfUkdCQV9TM1RDX0RYVDVfRVhUfWVsc2UgcmV0dXJuIG51bGw7aWYobz09PUl1dHx8bz09PUx1dHx8bz09PWt1dHx8bz09PVJ1dClpZihzPXQuZ2V0KCJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfcHZydGMiKSxzIT09bnVsbCl7aWYobz09PUl1dClyZXR1cm4gcy5DT01QUkVTU0VEX1JHQl9QVlJUQ180QlBQVjFfSU1HO2lmKG89PT1MdXQpcmV0dXJuIHMuQ09NUFJFU1NFRF9SR0JfUFZSVENfMkJQUFYxX0lNRztpZihvPT09a3V0KXJldHVybiBzLkNPTVBSRVNTRURfUkdCQV9QVlJUQ180QlBQVjFfSU1HO2lmKG89PT1SdXQpcmV0dXJuIHMuQ09NUFJFU1NFRF9SR0JBX1BWUlRDXzJCUFBWMV9JTUd9ZWxzZSByZXR1cm4gbnVsbDtpZihvPT09U2ZlKXJldHVybiBzPXQuZ2V0KCJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfZXRjMSIpLHMhPT1udWxsP3MuQ09NUFJFU1NFRF9SR0JfRVRDMV9XRUJHTDpudWxsO2lmKG89PT1OdXR8fG89PT1EdXQpaWYocz10LmdldCgiV0VCR0xfY29tcHJlc3NlZF90ZXh0dXJlX2V0YyIpLHMhPT1udWxsKXtpZihvPT09TnV0KXJldHVybiBhPT09WW4/cy5DT01QUkVTU0VEX1NSR0I4X0VUQzI6cy5DT01QUkVTU0VEX1JHQjhfRVRDMjtpZihvPT09RHV0KXJldHVybiBhPT09WW4/cy5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9FVEMyX0VBQzpzLkNPTVBSRVNTRURfUkdCQThfRVRDMl9FQUN9ZWxzZSByZXR1cm4gbnVsbDtpZihvPT09T3V0fHxvPT09enV0fHxvPT09RnV0fHxvPT09QnV0fHxvPT09SHV0fHxvPT09VnV0fHxvPT09VXV0fHxvPT09cXV0fHxvPT09R3V0fHxvPT09V3V0fHxvPT09WXV0fHxvPT09anV0fHxvPT09WHV0fHxvPT09JHV0KWlmKHM9dC5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9hc3RjIikscyE9PW51bGwpe2lmKG89PT1PdXQpcmV0dXJuIGE9PT1Zbj9zLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfNHg0X0tIUjpzLkNPTVBSRVNTRURfUkdCQV9BU1RDXzR4NF9LSFI7aWYobz09PXp1dClyZXR1cm4gYT09PVluP3MuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ181eDRfS0hSOnMuQ09NUFJFU1NFRF9SR0JBX0FTVENfNXg0X0tIUjtpZihvPT09RnV0KXJldHVybiBhPT09WW4/cy5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzV4NV9LSFI6cy5DT01QUkVTU0VEX1JHQkFfQVNUQ181eDVfS0hSO2lmKG89PT1CdXQpcmV0dXJuIGE9PT1Zbj9zLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfNng1X0tIUjpzLkNPTVBSRVNTRURfUkdCQV9BU1RDXzZ4NV9LSFI7aWYobz09PUh1dClyZXR1cm4gYT09PVluP3MuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ182eDZfS0hSOnMuQ09NUFJFU1NFRF9SR0JBX0FTVENfNng2X0tIUjtpZihvPT09VnV0KXJldHVybiBhPT09WW4/cy5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzh4NV9LSFI6cy5DT01QUkVTU0VEX1JHQkFfQVNUQ184eDVfS0hSO2lmKG89PT1VdXQpcmV0dXJuIGE9PT1Zbj9zLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfOHg2X0tIUjpzLkNPTVBSRVNTRURfUkdCQV9BU1RDXzh4Nl9LSFI7aWYobz09PXF1dClyZXR1cm4gYT09PVluP3MuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ184eDhfS0hSOnMuQ09NUFJFU1NFRF9SR0JBX0FTVENfOHg4X0tIUjtpZihvPT09R3V0KXJldHVybiBhPT09WW4/cy5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDVfS0hSOnMuQ09NUFJFU1NFRF9SR0JBX0FTVENfMTB4NV9LSFI7aWYobz09PVd1dClyZXR1cm4gYT09PVluP3MuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ18xMHg2X0tIUjpzLkNPTVBSRVNTRURfUkdCQV9BU1RDXzEweDZfS0hSO2lmKG89PT1ZdXQpcmV0dXJuIGE9PT1Zbj9zLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfMTB4OF9LSFI6cy5DT01QUkVTU0VEX1JHQkFfQVNUQ18xMHg4X0tIUjtpZihvPT09anV0KXJldHVybiBhPT09WW4/cy5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDEwX0tIUjpzLkNPTVBSRVNTRURfUkdCQV9BU1RDXzEweDEwX0tIUjtpZihvPT09WHV0KXJldHVybiBhPT09WW4/cy5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEyeDEwX0tIUjpzLkNPTVBSRVNTRURfUkdCQV9BU1RDXzEyeDEwX0tIUjtpZihvPT09JHV0KXJldHVybiBhPT09WW4/cy5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEyeDEyX0tIUjpzLkNPTVBSRVNTRURfUkdCQV9BU1RDXzEyeDEyX0tIUn1lbHNlIHJldHVybiBudWxsO2lmKG89PT1LdXQpaWYocz10LmdldCgiRVhUX3RleHR1cmVfY29tcHJlc3Npb25fYnB0YyIpLHMhPT1udWxsKXtpZihvPT09S3V0KXJldHVybiBhPT09WW4/cy5DT01QUkVTU0VEX1NSR0JfQUxQSEFfQlBUQ19VTk9STV9FWFQ6cy5DT01QUkVTU0VEX1JHQkFfQlBUQ19VTk9STV9FWFR9ZWxzZSByZXR1cm4gbnVsbDtpZihvPT09QXYpcmV0dXJuIG4/MzQwNDI6KHM9dC5nZXQoIldFQkdMX2RlcHRoX3RleHR1cmUiKSxzIT09bnVsbD9zLlVOU0lHTkVEX0lOVF8yNF84X1dFQkdMOm51bGwpfXJldHVybntjb252ZXJ0Oml9fXZhciByNj1jbGFzcyBleHRlbmRzIFVpe2NvbnN0cnVjdG9yKHQ9W10pe3N1cGVyKCksdGhpcy5jYW1lcmFzPXR9fTtyNi5wcm90b3R5cGUuaXNBcnJheUNhbWVyYT0hMDt2YXIgWGQ9Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy50eXBlPSJHcm91cCJ9fTtYZC5wcm90b3R5cGUuaXNHcm91cD0hMDt2YXIgQ2dyPXt0eXBlOiJtb3ZlIn0sVVA9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLl90YXJnZXRSYXk9bnVsbCx0aGlzLl9ncmlwPW51bGwsdGhpcy5faGFuZD1udWxsfWdldEhhbmRTcGFjZSgpe3JldHVybiB0aGlzLl9oYW5kPT09bnVsbCYmKHRoaXMuX2hhbmQ9bmV3IFhkLHRoaXMuX2hhbmQubWF0cml4QXV0b1VwZGF0ZT0hMSx0aGlzLl9oYW5kLnZpc2libGU9ITEsdGhpcy5faGFuZC5qb2ludHM9e30sdGhpcy5faGFuZC5pbnB1dFN0YXRlPXtwaW5jaGluZzohMX0pLHRoaXMuX2hhbmR9Z2V0VGFyZ2V0UmF5U3BhY2UoKXtyZXR1cm4gdGhpcy5fdGFyZ2V0UmF5PT09bnVsbCYmKHRoaXMuX3RhcmdldFJheT1uZXcgWGQsdGhpcy5fdGFyZ2V0UmF5Lm1hdHJpeEF1dG9VcGRhdGU9ITEsdGhpcy5fdGFyZ2V0UmF5LnZpc2libGU9ITEsdGhpcy5fdGFyZ2V0UmF5Lmhhc0xpbmVhclZlbG9jaXR5PSExLHRoaXMuX3RhcmdldFJheS5saW5lYXJWZWxvY2l0eT1uZXcgaix0aGlzLl90YXJnZXRSYXkuaGFzQW5ndWxhclZlbG9jaXR5PSExLHRoaXMuX3RhcmdldFJheS5hbmd1bGFyVmVsb2NpdHk9bmV3IGopLHRoaXMuX3RhcmdldFJheX1nZXRHcmlwU3BhY2UoKXtyZXR1cm4gdGhpcy5fZ3JpcD09PW51bGwmJih0aGlzLl9ncmlwPW5ldyBYZCx0aGlzLl9ncmlwLm1hdHJpeEF1dG9VcGRhdGU9ITEsdGhpcy5fZ3JpcC52aXNpYmxlPSExLHRoaXMuX2dyaXAuaGFzTGluZWFyVmVsb2NpdHk9ITEsdGhpcy5fZ3JpcC5saW5lYXJWZWxvY2l0eT1uZXcgaix0aGlzLl9ncmlwLmhhc0FuZ3VsYXJWZWxvY2l0eT0hMSx0aGlzLl9ncmlwLmFuZ3VsYXJWZWxvY2l0eT1uZXcgaiksdGhpcy5fZ3JpcH1kaXNwYXRjaEV2ZW50KHQpe3JldHVybiB0aGlzLl90YXJnZXRSYXkhPT1udWxsJiZ0aGlzLl90YXJnZXRSYXkuZGlzcGF0Y2hFdmVudCh0KSx0aGlzLl9ncmlwIT09bnVsbCYmdGhpcy5fZ3JpcC5kaXNwYXRjaEV2ZW50KHQpLHRoaXMuX2hhbmQhPT1udWxsJiZ0aGlzLl9oYW5kLmRpc3BhdGNoRXZlbnQodCksdGhpc31kaXNjb25uZWN0KHQpe3JldHVybiB0aGlzLmRpc3BhdGNoRXZlbnQoe3R5cGU6ImRpc2Nvbm5lY3RlZCIsZGF0YTp0fSksdGhpcy5fdGFyZ2V0UmF5IT09bnVsbCYmKHRoaXMuX3RhcmdldFJheS52aXNpYmxlPSExKSx0aGlzLl9ncmlwIT09bnVsbCYmKHRoaXMuX2dyaXAudmlzaWJsZT0hMSksdGhpcy5faGFuZCE9PW51bGwmJih0aGlzLl9oYW5kLnZpc2libGU9ITEpLHRoaXN9dXBkYXRlKHQscixuKXtsZXQgaT1udWxsLG89bnVsbCxhPW51bGwscz10aGlzLl90YXJnZXRSYXksbD10aGlzLl9ncmlwLGM9dGhpcy5faGFuZDtpZih0JiZyLnNlc3Npb24udmlzaWJpbGl0eVN0YXRlIT09InZpc2libGUtYmx1cnJlZCIpaWYocyE9PW51bGwmJihpPXIuZ2V0UG9zZSh0LnRhcmdldFJheVNwYWNlLG4pLGkhPT1udWxsJiYocy5tYXRyaXguZnJvbUFycmF5KGkudHJhbnNmb3JtLm1hdHJpeCkscy5tYXRyaXguZGVjb21wb3NlKHMucG9zaXRpb24scy5yb3RhdGlvbixzLnNjYWxlKSxpLmxpbmVhclZlbG9jaXR5PyhzLmhhc0xpbmVhclZlbG9jaXR5PSEwLHMubGluZWFyVmVsb2NpdHkuY29weShpLmxpbmVhclZlbG9jaXR5KSk6cy5oYXNMaW5lYXJWZWxvY2l0eT0hMSxpLmFuZ3VsYXJWZWxvY2l0eT8ocy5oYXNBbmd1bGFyVmVsb2NpdHk9ITAscy5hbmd1bGFyVmVsb2NpdHkuY29weShpLmFuZ3VsYXJWZWxvY2l0eSkpOnMuaGFzQW5ndWxhclZlbG9jaXR5PSExLHRoaXMuZGlzcGF0Y2hFdmVudChDZ3IpKSksYyYmdC5oYW5kKXthPSEwO2ZvcihsZXQgZyBvZiB0LmhhbmQudmFsdWVzKCkpe2xldCBfPXIuZ2V0Sm9pbnRQb3NlKGcsbik7aWYoYy5qb2ludHNbZy5qb2ludE5hbWVdPT09dm9pZCAwKXtsZXQgeD1uZXcgWGQ7eC5tYXRyaXhBdXRvVXBkYXRlPSExLHgudmlzaWJsZT0hMSxjLmpvaW50c1tnLmpvaW50TmFtZV09eCxjLmFkZCh4KX1sZXQgeT1jLmpvaW50c1tnLmpvaW50TmFtZV07XyE9PW51bGwmJih5Lm1hdHJpeC5mcm9tQXJyYXkoXy50cmFuc2Zvcm0ubWF0cml4KSx5Lm1hdHJpeC5kZWNvbXBvc2UoeS5wb3NpdGlvbix5LnJvdGF0aW9uLHkuc2NhbGUpLHkuam9pbnRSYWRpdXM9Xy5yYWRpdXMpLHkudmlzaWJsZT1fIT09bnVsbH1sZXQgdT1jLmpvaW50c1siaW5kZXgtZmluZ2VyLXRpcCJdLGg9Yy5qb2ludHNbInRodW1iLXRpcCJdLGY9dS5wb3NpdGlvbi5kaXN0YW5jZVRvKGgucG9zaXRpb24pLHA9LjAyLGQ9LjAwNTtjLmlucHV0U3RhdGUucGluY2hpbmcmJmY+cCtkPyhjLmlucHV0U3RhdGUucGluY2hpbmc9ITEsdGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJwaW5jaGVuZCIsaGFuZGVkbmVzczp0LmhhbmRlZG5lc3MsdGFyZ2V0OnRoaXN9KSk6IWMuaW5wdXRTdGF0ZS5waW5jaGluZyYmZjw9cC1kJiYoYy5pbnB1dFN0YXRlLnBpbmNoaW5nPSEwLHRoaXMuZGlzcGF0Y2hFdmVudCh7dHlwZToicGluY2hzdGFydCIsaGFuZGVkbmVzczp0LmhhbmRlZG5lc3MsdGFyZ2V0OnRoaXN9KSl9ZWxzZSBsIT09bnVsbCYmdC5ncmlwU3BhY2UmJihvPXIuZ2V0UG9zZSh0LmdyaXBTcGFjZSxuKSxvIT09bnVsbCYmKGwubWF0cml4LmZyb21BcnJheShvLnRyYW5zZm9ybS5tYXRyaXgpLGwubWF0cml4LmRlY29tcG9zZShsLnBvc2l0aW9uLGwucm90YXRpb24sbC5zY2FsZSksby5saW5lYXJWZWxvY2l0eT8obC5oYXNMaW5lYXJWZWxvY2l0eT0hMCxsLmxpbmVhclZlbG9jaXR5LmNvcHkoby5saW5lYXJWZWxvY2l0eSkpOmwuaGFzTGluZWFyVmVsb2NpdHk9ITEsby5hbmd1bGFyVmVsb2NpdHk/KGwuaGFzQW5ndWxhclZlbG9jaXR5PSEwLGwuYW5ndWxhclZlbG9jaXR5LmNvcHkoby5hbmd1bGFyVmVsb2NpdHkpKTpsLmhhc0FuZ3VsYXJWZWxvY2l0eT0hMSkpO3JldHVybiBzIT09bnVsbCYmKHMudmlzaWJsZT1pIT09bnVsbCksbCE9PW51bGwmJihsLnZpc2libGU9byE9PW51bGwpLGMhPT1udWxsJiYoYy52aXNpYmxlPWEhPT1udWxsKSx0aGlzfX0sbk09Y2xhc3MgZXh0ZW5kcyB4aXtjb25zdHJ1Y3Rvcih0LHIsbixpLG8sYSxzLGwsYyx1KXtpZih1PXUhPT12b2lkIDA/dTp6MCx1IT09ejAmJnUhPT1rdil0aHJvdyBuZXcgRXJyb3IoIkRlcHRoVGV4dHVyZSBmb3JtYXQgbXVzdCBiZSBlaXRoZXIgVEhSRUUuRGVwdGhGb3JtYXQgb3IgVEhSRUUuRGVwdGhTdGVuY2lsRm9ybWF0Iik7bj09PXZvaWQgMCYmdT09PXowJiYobj1HMyksbj09PXZvaWQgMCYmdT09PWt2JiYobj1Bdiksc3VwZXIobnVsbCxpLG8sYSxzLGwsdSxuLGMpLHRoaXMuaW1hZ2U9e3dpZHRoOnQsaGVpZ2h0OnJ9LHRoaXMubWFnRmlsdGVyPXMhPT12b2lkIDA/czpMaSx0aGlzLm1pbkZpbHRlcj1sIT09dm9pZCAwP2w6TGksdGhpcy5mbGlwWT0hMSx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMX19O25NLnByb3RvdHlwZS5pc0RlcHRoVGV4dHVyZT0hMDt2YXIgcmh0PWNsYXNzIGV4dGVuZHMgVXN7Y29uc3RydWN0b3IodCxyKXtzdXBlcigpO2xldCBuPXRoaXMsaT1udWxsLG89MSxhPW51bGwscz0ibG9jYWwtZmxvb3IiLGw9dC5leHRlbnNpb25zLmhhcygiV0VCR0xfbXVsdGlzYW1wbGVkX3JlbmRlcl90b190ZXh0dXJlIiksYz1udWxsLHU9bnVsbCxoPW51bGwsZj1udWxsLHA9ITEsZD1udWxsLGc9ci5nZXRDb250ZXh0QXR0cmlidXRlcygpLF89bnVsbCx5PW51bGwseD1bXSxiPW5ldyBNYXAsUz1uZXcgVWk7Uy5sYXllcnMuZW5hYmxlKDEpLFMudmlld3BvcnQ9bmV3IGVuO2xldCBDPW5ldyBVaTtDLmxheWVycy5lbmFibGUoMiksQy52aWV3cG9ydD1uZXcgZW47bGV0IFA9W1MsQ10saz1uZXcgcjY7ay5sYXllcnMuZW5hYmxlKDEpLGsubGF5ZXJzLmVuYWJsZSgyKTtsZXQgTz1udWxsLEQ9bnVsbDt0aGlzLmNhbWVyYUF1dG9VcGRhdGU9ITAsdGhpcy5lbmFibGVkPSExLHRoaXMuaXNQcmVzZW50aW5nPSExLHRoaXMuZ2V0Q29udHJvbGxlcj1mdW5jdGlvbihvdCl7bGV0IHN0PXhbb3RdO3JldHVybiBzdD09PXZvaWQgMCYmKHN0PW5ldyBVUCx4W290XT1zdCksc3QuZ2V0VGFyZ2V0UmF5U3BhY2UoKX0sdGhpcy5nZXRDb250cm9sbGVyR3JpcD1mdW5jdGlvbihvdCl7bGV0IHN0PXhbb3RdO3JldHVybiBzdD09PXZvaWQgMCYmKHN0PW5ldyBVUCx4W290XT1zdCksc3QuZ2V0R3JpcFNwYWNlKCl9LHRoaXMuZ2V0SGFuZD1mdW5jdGlvbihvdCl7bGV0IHN0PXhbb3RdO3JldHVybiBzdD09PXZvaWQgMCYmKHN0PW5ldyBVUCx4W290XT1zdCksc3QuZ2V0SGFuZFNwYWNlKCl9O2Z1bmN0aW9uIEIob3Qpe2xldCBzdD1iLmdldChvdC5pbnB1dFNvdXJjZSk7c3QmJnN0LmRpc3BhdGNoRXZlbnQoe3R5cGU6b3QudHlwZSxkYXRhOm90LmlucHV0U291cmNlfSl9ZnVuY3Rpb24gSSgpe2IuZm9yRWFjaChmdW5jdGlvbihvdCxzdCl7b3QuZGlzY29ubmVjdChzdCl9KSxiLmNsZWFyKCksTz1udWxsLEQ9bnVsbCx0LnNldFJlbmRlclRhcmdldChfKSxmPW51bGwsaD1udWxsLHU9bnVsbCxpPW51bGwseT1udWxsLHJ0LnN0b3AoKSxuLmlzUHJlc2VudGluZz0hMSxuLmRpc3BhdGNoRXZlbnQoe3R5cGU6InNlc3Npb25lbmQifSl9dGhpcy5zZXRGcmFtZWJ1ZmZlclNjYWxlRmFjdG9yPWZ1bmN0aW9uKG90KXtvPW90LG4uaXNQcmVzZW50aW5nPT09ITAmJmNvbnNvbGUud2FybigiVEhSRUUuV2ViWFJNYW5hZ2VyOiBDYW5ub3QgY2hhbmdlIGZyYW1lYnVmZmVyIHNjYWxlIHdoaWxlIHByZXNlbnRpbmcuIil9LHRoaXMuc2V0UmVmZXJlbmNlU3BhY2VUeXBlPWZ1bmN0aW9uKG90KXtzPW90LG4uaXNQcmVzZW50aW5nPT09ITAmJmNvbnNvbGUud2FybigiVEhSRUUuV2ViWFJNYW5hZ2VyOiBDYW5ub3QgY2hhbmdlIHJlZmVyZW5jZSBzcGFjZSB0eXBlIHdoaWxlIHByZXNlbnRpbmcuIil9LHRoaXMuZ2V0UmVmZXJlbmNlU3BhY2U9ZnVuY3Rpb24oKXtyZXR1cm4gYX0sdGhpcy5nZXRCYXNlTGF5ZXI9ZnVuY3Rpb24oKXtyZXR1cm4gaCE9PW51bGw/aDpmfSx0aGlzLmdldEJpbmRpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gdX0sdGhpcy5nZXRGcmFtZT1mdW5jdGlvbigpe3JldHVybiBkfSx0aGlzLmdldFNlc3Npb249ZnVuY3Rpb24oKXtyZXR1cm4gaX0sdGhpcy5zZXRTZXNzaW9uPWZ1bmN0aW9uKG90KXtyZXR1cm4gUmkodGhpcyxudWxsLGZ1bmN0aW9uKigpe2lmKGk9b3QsaSE9PW51bGwpe2lmKF89dC5nZXRSZW5kZXJUYXJnZXQoKSxpLmFkZEV2ZW50TGlzdGVuZXIoInNlbGVjdCIsQiksaS5hZGRFdmVudExpc3RlbmVyKCJzZWxlY3RzdGFydCIsQiksaS5hZGRFdmVudExpc3RlbmVyKCJzZWxlY3RlbmQiLEIpLGkuYWRkRXZlbnRMaXN0ZW5lcigic3F1ZWV6ZSIsQiksaS5hZGRFdmVudExpc3RlbmVyKCJzcXVlZXplc3RhcnQiLEIpLGkuYWRkRXZlbnRMaXN0ZW5lcigic3F1ZWV6ZWVuZCIsQiksaS5hZGRFdmVudExpc3RlbmVyKCJlbmQiLEkpLGkuYWRkRXZlbnRMaXN0ZW5lcigiaW5wdXRzb3VyY2VzY2hhbmdlIixMKSxnLnhyQ29tcGF0aWJsZSE9PSEwJiYoeWllbGQgci5tYWtlWFJDb21wYXRpYmxlKCkpLGkucmVuZGVyU3RhdGUubGF5ZXJzPT09dm9pZCAwfHx0LmNhcGFiaWxpdGllcy5pc1dlYkdMMj09PSExKXtsZXQgc3Q9e2FudGlhbGlhczppLnJlbmRlclN0YXRlLmxheWVycz09PXZvaWQgMD9nLmFudGlhbGlhczohMCxhbHBoYTpnLmFscGhhLGRlcHRoOmcuZGVwdGgsc3RlbmNpbDpnLnN0ZW5jaWwsZnJhbWVidWZmZXJTY2FsZUZhY3RvcjpvfTtmPW5ldyBYUldlYkdMTGF5ZXIoaSxyLHN0KSxpLnVwZGF0ZVJlbmRlclN0YXRlKHtiYXNlTGF5ZXI6Zn0pLHk9bmV3IHVzKGYuZnJhbWVidWZmZXJXaWR0aCxmLmZyYW1lYnVmZmVySGVpZ2h0LHtmb3JtYXQ6UW8sdHlwZTpaZCxlbmNvZGluZzp0Lm91dHB1dEVuY29kaW5nfSl9ZWxzZXtwPWcuYW50aWFsaWFzO2xldCBzdD1udWxsLFN0PW51bGwsYnQ9bnVsbDtnLmRlcHRoJiYoYnQ9Zy5zdGVuY2lsPzM1MDU2OjMzMTkwLHN0PWcuc3RlbmNpbD9rdjp6MCxTdD1nLnN0ZW5jaWw/QXY6RzMpO2xldCBNdD17Y29sb3JGb3JtYXQ6dC5vdXRwdXRFbmNvZGluZz09PVluPzM1OTA3OjMyODU2LGRlcHRoRm9ybWF0OmJ0LHNjYWxlRmFjdG9yOm99O3U9bmV3IFhSV2ViR0xCaW5kaW5nKGksciksaD11LmNyZWF0ZVByb2plY3Rpb25MYXllcihNdCksaS51cGRhdGVSZW5kZXJTdGF0ZSh7bGF5ZXJzOltoXX0pLHA/eT1uZXcgajMoaC50ZXh0dXJlV2lkdGgsaC50ZXh0dXJlSGVpZ2h0LHtmb3JtYXQ6UW8sdHlwZTpaZCxkZXB0aFRleHR1cmU6bmV3IG5NKGgudGV4dHVyZVdpZHRoLGgudGV4dHVyZUhlaWdodCxTdCx2b2lkIDAsdm9pZCAwLHZvaWQgMCx2b2lkIDAsdm9pZCAwLHZvaWQgMCxzdCksc3RlbmNpbEJ1ZmZlcjpnLnN0ZW5jaWwsaWdub3JlRGVwdGg6aC5pZ25vcmVEZXB0aFZhbHVlcyx1c2VSZW5kZXJUb1RleHR1cmU6bCxlbmNvZGluZzp0Lm91dHB1dEVuY29kaW5nfSk6eT1uZXcgdXMoaC50ZXh0dXJlV2lkdGgsaC50ZXh0dXJlSGVpZ2h0LHtmb3JtYXQ6UW8sdHlwZTpaZCxkZXB0aFRleHR1cmU6bmV3IG5NKGgudGV4dHVyZVdpZHRoLGgudGV4dHVyZUhlaWdodCxTdCx2b2lkIDAsdm9pZCAwLHZvaWQgMCx2b2lkIDAsdm9pZCAwLHZvaWQgMCxzdCksc3RlbmNpbEJ1ZmZlcjpnLnN0ZW5jaWwsaWdub3JlRGVwdGg6aC5pZ25vcmVEZXB0aFZhbHVlcyxlbmNvZGluZzp0Lm91dHB1dEVuY29kaW5nfSl9eS5pc1hSUmVuZGVyVGFyZ2V0PSEwLHRoaXMuc2V0Rm92ZWF0aW9uKDEpLGE9eWllbGQgaS5yZXF1ZXN0UmVmZXJlbmNlU3BhY2UocykscnQuc2V0Q29udGV4dChpKSxydC5zdGFydCgpLG4uaXNQcmVzZW50aW5nPSEwLG4uZGlzcGF0Y2hFdmVudCh7dHlwZToic2Vzc2lvbnN0YXJ0In0pfX0pfTtmdW5jdGlvbiBMKG90KXtsZXQgc3Q9aS5pbnB1dFNvdXJjZXM7Zm9yKGxldCBTdD0wO1N0PHgubGVuZ3RoO1N0KyspYi5zZXQoc3RbU3RdLHhbU3RdKTtmb3IobGV0IFN0PTA7U3Q8b3QucmVtb3ZlZC5sZW5ndGg7U3QrKyl7bGV0IGJ0PW90LnJlbW92ZWRbU3RdLE10PWIuZ2V0KGJ0KTtNdCYmKE10LmRpc3BhdGNoRXZlbnQoe3R5cGU6ImRpc2Nvbm5lY3RlZCIsZGF0YTpidH0pLGIuZGVsZXRlKGJ0KSl9Zm9yKGxldCBTdD0wO1N0PG90LmFkZGVkLmxlbmd0aDtTdCsrKXtsZXQgYnQ9b3QuYWRkZWRbU3RdLE10PWIuZ2V0KGJ0KTtNdCYmTXQuZGlzcGF0Y2hFdmVudCh7dHlwZToiY29ubmVjdGVkIixkYXRhOmJ0fSl9fWxldCBSPW5ldyBqLEY9bmV3IGo7ZnVuY3Rpb24geihvdCxzdCxTdCl7Ui5zZXRGcm9tTWF0cml4UG9zaXRpb24oc3QubWF0cml4V29ybGQpLEYuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKFN0Lm1hdHJpeFdvcmxkKTtsZXQgYnQ9Ui5kaXN0YW5jZVRvKEYpLE10PXN0LnByb2plY3Rpb25NYXRyaXguZWxlbWVudHMsbHQ9U3QucHJvamVjdGlvbk1hdHJpeC5lbGVtZW50cyxLdD1NdFsxNF0vKE10WzEwXS0xKSxfdD1NdFsxNF0vKE10WzEwXSsxKSxjdD0oTXRbOV0rMSkvTXRbNV0sWD0oTXRbOV0tMSkvTXRbNV0sZXQ9KE10WzhdLTEpL010WzBdLGR0PShsdFs4XSsxKS9sdFswXSxxPUt0KmV0LHB0PUt0KmR0LGh0PWJ0LygtZXQrZHQpLHd0PWh0Ki1ldDtzdC5tYXRyaXhXb3JsZC5kZWNvbXBvc2Uob3QucG9zaXRpb24sb3QucXVhdGVybmlvbixvdC5zY2FsZSksb3QudHJhbnNsYXRlWCh3dCksb3QudHJhbnNsYXRlWihodCksb3QubWF0cml4V29ybGQuY29tcG9zZShvdC5wb3NpdGlvbixvdC5xdWF0ZXJuaW9uLG90LnNjYWxlKSxvdC5tYXRyaXhXb3JsZEludmVyc2UuY29weShvdC5tYXRyaXhXb3JsZCkuaW52ZXJ0KCk7bGV0IGt0PUt0K2h0LGllPV90K2h0LGVlPXEtd3QsTGU9cHQrKGJ0LXd0KSxhcj1jdCpfdC9pZSprdCxmcj1YKl90L2llKmt0O290LnByb2plY3Rpb25NYXRyaXgubWFrZVBlcnNwZWN0aXZlKGVlLExlLGFyLGZyLGt0LGllKX1mdW5jdGlvbiBVKG90LHN0KXtzdD09PW51bGw/b3QubWF0cml4V29ybGQuY29weShvdC5tYXRyaXgpOm90Lm1hdHJpeFdvcmxkLm11bHRpcGx5TWF0cmljZXMoc3QubWF0cml4V29ybGQsb3QubWF0cml4KSxvdC5tYXRyaXhXb3JsZEludmVyc2UuY29weShvdC5tYXRyaXhXb3JsZCkuaW52ZXJ0KCl9dGhpcy51cGRhdGVDYW1lcmE9ZnVuY3Rpb24ob3Qpe2lmKGk9PT1udWxsKXJldHVybjtrLm5lYXI9Qy5uZWFyPVMubmVhcj1vdC5uZWFyLGsuZmFyPUMuZmFyPVMuZmFyPW90LmZhciwoTyE9PWsubmVhcnx8RCE9PWsuZmFyKSYmKGkudXBkYXRlUmVuZGVyU3RhdGUoe2RlcHRoTmVhcjprLm5lYXIsZGVwdGhGYXI6ay5mYXJ9KSxPPWsubmVhcixEPWsuZmFyKTtsZXQgc3Q9b3QucGFyZW50LFN0PWsuY2FtZXJhcztVKGssc3QpO2ZvcihsZXQgTXQ9MDtNdDxTdC5sZW5ndGg7TXQrKylVKFN0W010XSxzdCk7ay5tYXRyaXhXb3JsZC5kZWNvbXBvc2Uoay5wb3NpdGlvbixrLnF1YXRlcm5pb24say5zY2FsZSksb3QucG9zaXRpb24uY29weShrLnBvc2l0aW9uKSxvdC5xdWF0ZXJuaW9uLmNvcHkoay5xdWF0ZXJuaW9uKSxvdC5zY2FsZS5jb3B5KGsuc2NhbGUpLG90Lm1hdHJpeC5jb3B5KGsubWF0cml4KSxvdC5tYXRyaXhXb3JsZC5jb3B5KGsubWF0cml4V29ybGQpO2xldCBidD1vdC5jaGlsZHJlbjtmb3IobGV0IE10PTAsbHQ9YnQubGVuZ3RoO010PGx0O010KyspYnRbTXRdLnVwZGF0ZU1hdHJpeFdvcmxkKCEwKTtTdC5sZW5ndGg9PT0yP3ooayxTLEMpOmsucHJvamVjdGlvbk1hdHJpeC5jb3B5KFMucHJvamVjdGlvbk1hdHJpeCl9LHRoaXMuZ2V0Q2FtZXJhPWZ1bmN0aW9uKCl7cmV0dXJuIGt9LHRoaXMuZ2V0Rm92ZWF0aW9uPWZ1bmN0aW9uKCl7aWYoaCE9PW51bGwpcmV0dXJuIGguZml4ZWRGb3ZlYXRpb247aWYoZiE9PW51bGwpcmV0dXJuIGYuZml4ZWRGb3ZlYXRpb259LHRoaXMuc2V0Rm92ZWF0aW9uPWZ1bmN0aW9uKG90KXtoIT09bnVsbCYmKGguZml4ZWRGb3ZlYXRpb249b3QpLGYhPT1udWxsJiZmLmZpeGVkRm92ZWF0aW9uIT09dm9pZCAwJiYoZi5maXhlZEZvdmVhdGlvbj1vdCl9O2xldCBXPW51bGw7ZnVuY3Rpb24gWihvdCxzdCl7aWYoYz1zdC5nZXRWaWV3ZXJQb3NlKGEpLGQ9c3QsYyE9PW51bGwpe2xldCBidD1jLnZpZXdzO2YhPT1udWxsJiYodC5zZXRSZW5kZXJUYXJnZXRGcmFtZWJ1ZmZlcih5LGYuZnJhbWVidWZmZXIpLHQuc2V0UmVuZGVyVGFyZ2V0KHkpKTtsZXQgTXQ9ITE7YnQubGVuZ3RoIT09ay5jYW1lcmFzLmxlbmd0aCYmKGsuY2FtZXJhcy5sZW5ndGg9MCxNdD0hMCk7Zm9yKGxldCBsdD0wO2x0PGJ0Lmxlbmd0aDtsdCsrKXtsZXQgS3Q9YnRbbHRdLF90PW51bGw7aWYoZiE9PW51bGwpX3Q9Zi5nZXRWaWV3cG9ydChLdCk7ZWxzZXtsZXQgWD11LmdldFZpZXdTdWJJbWFnZShoLEt0KTtfdD1YLnZpZXdwb3J0LGx0PT09MCYmKHQuc2V0UmVuZGVyVGFyZ2V0VGV4dHVyZXMoeSxYLmNvbG9yVGV4dHVyZSxoLmlnbm9yZURlcHRoVmFsdWVzP3ZvaWQgMDpYLmRlcHRoU3RlbmNpbFRleHR1cmUpLHQuc2V0UmVuZGVyVGFyZ2V0KHkpKX1sZXQgY3Q9UFtsdF07Y3QubWF0cml4LmZyb21BcnJheShLdC50cmFuc2Zvcm0ubWF0cml4KSxjdC5wcm9qZWN0aW9uTWF0cml4LmZyb21BcnJheShLdC5wcm9qZWN0aW9uTWF0cml4KSxjdC52aWV3cG9ydC5zZXQoX3QueCxfdC55LF90LndpZHRoLF90LmhlaWdodCksbHQ9PT0wJiZrLm1hdHJpeC5jb3B5KGN0Lm1hdHJpeCksTXQ9PT0hMCYmay5jYW1lcmFzLnB1c2goY3QpfX1sZXQgU3Q9aS5pbnB1dFNvdXJjZXM7Zm9yKGxldCBidD0wO2J0PHgubGVuZ3RoO2J0Kyspe2xldCBNdD14W2J0XSxsdD1TdFtidF07TXQudXBkYXRlKGx0LHN0LGEpfVcmJlcob3Qsc3QpLGQ9bnVsbH1sZXQgcnQ9bmV3IHpmZTtydC5zZXRBbmltYXRpb25Mb29wKFopLHRoaXMuc2V0QW5pbWF0aW9uTG9vcD1mdW5jdGlvbihvdCl7Vz1vdH0sdGhpcy5kaXNwb3NlPWZ1bmN0aW9uKCl7fX19O2Z1bmN0aW9uIEFncihlKXtmdW5jdGlvbiB0KHkseCl7eS5mb2dDb2xvci52YWx1ZS5jb3B5KHguY29sb3IpLHguaXNGb2c/KHkuZm9nTmVhci52YWx1ZT14Lm5lYXIseS5mb2dGYXIudmFsdWU9eC5mYXIpOnguaXNGb2dFeHAyJiYoeS5mb2dEZW5zaXR5LnZhbHVlPXguZGVuc2l0eSl9ZnVuY3Rpb24gcih5LHgsYixTLEMpe3guaXNNZXNoQmFzaWNNYXRlcmlhbD9uKHkseCk6eC5pc01lc2hMYW1iZXJ0TWF0ZXJpYWw/KG4oeSx4KSxsKHkseCkpOnguaXNNZXNoVG9vbk1hdGVyaWFsPyhuKHkseCksdSh5LHgpKTp4LmlzTWVzaFBob25nTWF0ZXJpYWw/KG4oeSx4KSxjKHkseCkpOnguaXNNZXNoU3RhbmRhcmRNYXRlcmlhbD8obih5LHgpLHguaXNNZXNoUGh5c2ljYWxNYXRlcmlhbD9mKHkseCxDKTpoKHkseCkpOnguaXNNZXNoTWF0Y2FwTWF0ZXJpYWw/KG4oeSx4KSxwKHkseCkpOnguaXNNZXNoRGVwdGhNYXRlcmlhbD8obih5LHgpLGQoeSx4KSk6eC5pc01lc2hEaXN0YW5jZU1hdGVyaWFsPyhuKHkseCksZyh5LHgpKTp4LmlzTWVzaE5vcm1hbE1hdGVyaWFsPyhuKHkseCksXyh5LHgpKTp4LmlzTGluZUJhc2ljTWF0ZXJpYWw/KGkoeSx4KSx4LmlzTGluZURhc2hlZE1hdGVyaWFsJiZvKHkseCkpOnguaXNQb2ludHNNYXRlcmlhbD9hKHkseCxiLFMpOnguaXNTcHJpdGVNYXRlcmlhbD9zKHkseCk6eC5pc1NoYWRvd01hdGVyaWFsPyh5LmNvbG9yLnZhbHVlLmNvcHkoeC5jb2xvcikseS5vcGFjaXR5LnZhbHVlPXgub3BhY2l0eSk6eC5pc1NoYWRlck1hdGVyaWFsJiYoeC51bmlmb3Jtc05lZWRVcGRhdGU9ITEpfWZ1bmN0aW9uIG4oeSx4KXt5Lm9wYWNpdHkudmFsdWU9eC5vcGFjaXR5LHguY29sb3ImJnkuZGlmZnVzZS52YWx1ZS5jb3B5KHguY29sb3IpLHguZW1pc3NpdmUmJnkuZW1pc3NpdmUudmFsdWUuY29weSh4LmVtaXNzaXZlKS5tdWx0aXBseVNjYWxhcih4LmVtaXNzaXZlSW50ZW5zaXR5KSx4Lm1hcCYmKHkubWFwLnZhbHVlPXgubWFwKSx4LmFscGhhTWFwJiYoeS5hbHBoYU1hcC52YWx1ZT14LmFscGhhTWFwKSx4LnNwZWN1bGFyTWFwJiYoeS5zcGVjdWxhck1hcC52YWx1ZT14LnNwZWN1bGFyTWFwKSx4LmFscGhhVGVzdD4wJiYoeS5hbHBoYVRlc3QudmFsdWU9eC5hbHBoYVRlc3QpO2xldCBiPWUuZ2V0KHgpLmVudk1hcDtiJiYoeS5lbnZNYXAudmFsdWU9Yix5LmZsaXBFbnZNYXAudmFsdWU9Yi5pc0N1YmVUZXh0dXJlJiZiLmlzUmVuZGVyVGFyZ2V0VGV4dHVyZT09PSExPy0xOjEseS5yZWZsZWN0aXZpdHkudmFsdWU9eC5yZWZsZWN0aXZpdHkseS5pb3IudmFsdWU9eC5pb3IseS5yZWZyYWN0aW9uUmF0aW8udmFsdWU9eC5yZWZyYWN0aW9uUmF0aW8pLHgubGlnaHRNYXAmJih5LmxpZ2h0TWFwLnZhbHVlPXgubGlnaHRNYXAseS5saWdodE1hcEludGVuc2l0eS52YWx1ZT14LmxpZ2h0TWFwSW50ZW5zaXR5KSx4LmFvTWFwJiYoeS5hb01hcC52YWx1ZT14LmFvTWFwLHkuYW9NYXBJbnRlbnNpdHkudmFsdWU9eC5hb01hcEludGVuc2l0eSk7bGV0IFM7eC5tYXA/Uz14Lm1hcDp4LnNwZWN1bGFyTWFwP1M9eC5zcGVjdWxhck1hcDp4LmRpc3BsYWNlbWVudE1hcD9TPXguZGlzcGxhY2VtZW50TWFwOngubm9ybWFsTWFwP1M9eC5ub3JtYWxNYXA6eC5idW1wTWFwP1M9eC5idW1wTWFwOngucm91Z2huZXNzTWFwP1M9eC5yb3VnaG5lc3NNYXA6eC5tZXRhbG5lc3NNYXA/Uz14Lm1ldGFsbmVzc01hcDp4LmFscGhhTWFwP1M9eC5hbHBoYU1hcDp4LmVtaXNzaXZlTWFwP1M9eC5lbWlzc2l2ZU1hcDp4LmNsZWFyY29hdE1hcD9TPXguY2xlYXJjb2F0TWFwOnguY2xlYXJjb2F0Tm9ybWFsTWFwP1M9eC5jbGVhcmNvYXROb3JtYWxNYXA6eC5jbGVhcmNvYXRSb3VnaG5lc3NNYXA/Uz14LmNsZWFyY29hdFJvdWdobmVzc01hcDp4LnNwZWN1bGFySW50ZW5zaXR5TWFwP1M9eC5zcGVjdWxhckludGVuc2l0eU1hcDp4LnNwZWN1bGFyQ29sb3JNYXA/Uz14LnNwZWN1bGFyQ29sb3JNYXA6eC50cmFuc21pc3Npb25NYXA/Uz14LnRyYW5zbWlzc2lvbk1hcDp4LnRoaWNrbmVzc01hcD9TPXgudGhpY2tuZXNzTWFwOnguc2hlZW5Db2xvck1hcD9TPXguc2hlZW5Db2xvck1hcDp4LnNoZWVuUm91Z2huZXNzTWFwJiYoUz14LnNoZWVuUm91Z2huZXNzTWFwKSxTIT09dm9pZCAwJiYoUy5pc1dlYkdMUmVuZGVyVGFyZ2V0JiYoUz1TLnRleHR1cmUpLFMubWF0cml4QXV0b1VwZGF0ZT09PSEwJiZTLnVwZGF0ZU1hdHJpeCgpLHkudXZUcmFuc2Zvcm0udmFsdWUuY29weShTLm1hdHJpeCkpO2xldCBDO3guYW9NYXA/Qz14LmFvTWFwOngubGlnaHRNYXAmJihDPXgubGlnaHRNYXApLEMhPT12b2lkIDAmJihDLmlzV2ViR0xSZW5kZXJUYXJnZXQmJihDPUMudGV4dHVyZSksQy5tYXRyaXhBdXRvVXBkYXRlPT09ITAmJkMudXBkYXRlTWF0cml4KCkseS51djJUcmFuc2Zvcm0udmFsdWUuY29weShDLm1hdHJpeCkpfWZ1bmN0aW9uIGkoeSx4KXt5LmRpZmZ1c2UudmFsdWUuY29weSh4LmNvbG9yKSx5Lm9wYWNpdHkudmFsdWU9eC5vcGFjaXR5fWZ1bmN0aW9uIG8oeSx4KXt5LmRhc2hTaXplLnZhbHVlPXguZGFzaFNpemUseS50b3RhbFNpemUudmFsdWU9eC5kYXNoU2l6ZSt4LmdhcFNpemUseS5zY2FsZS52YWx1ZT14LnNjYWxlfWZ1bmN0aW9uIGEoeSx4LGIsUyl7eS5kaWZmdXNlLnZhbHVlLmNvcHkoeC5jb2xvcikseS5vcGFjaXR5LnZhbHVlPXgub3BhY2l0eSx5LnNpemUudmFsdWU9eC5zaXplKmIseS5zY2FsZS52YWx1ZT1TKi41LHgubWFwJiYoeS5tYXAudmFsdWU9eC5tYXApLHguYWxwaGFNYXAmJih5LmFscGhhTWFwLnZhbHVlPXguYWxwaGFNYXApLHguYWxwaGFUZXN0PjAmJih5LmFscGhhVGVzdC52YWx1ZT14LmFscGhhVGVzdCk7bGV0IEM7eC5tYXA/Qz14Lm1hcDp4LmFscGhhTWFwJiYoQz14LmFscGhhTWFwKSxDIT09dm9pZCAwJiYoQy5tYXRyaXhBdXRvVXBkYXRlPT09ITAmJkMudXBkYXRlTWF0cml4KCkseS51dlRyYW5zZm9ybS52YWx1ZS5jb3B5KEMubWF0cml4KSl9ZnVuY3Rpb24gcyh5LHgpe3kuZGlmZnVzZS52YWx1ZS5jb3B5KHguY29sb3IpLHkub3BhY2l0eS52YWx1ZT14Lm9wYWNpdHkseS5yb3RhdGlvbi52YWx1ZT14LnJvdGF0aW9uLHgubWFwJiYoeS5tYXAudmFsdWU9eC5tYXApLHguYWxwaGFNYXAmJih5LmFscGhhTWFwLnZhbHVlPXguYWxwaGFNYXApLHguYWxwaGFUZXN0PjAmJih5LmFscGhhVGVzdC52YWx1ZT14LmFscGhhVGVzdCk7bGV0IGI7eC5tYXA/Yj14Lm1hcDp4LmFscGhhTWFwJiYoYj14LmFscGhhTWFwKSxiIT09dm9pZCAwJiYoYi5tYXRyaXhBdXRvVXBkYXRlPT09ITAmJmIudXBkYXRlTWF0cml4KCkseS51dlRyYW5zZm9ybS52YWx1ZS5jb3B5KGIubWF0cml4KSl9ZnVuY3Rpb24gbCh5LHgpe3guZW1pc3NpdmVNYXAmJih5LmVtaXNzaXZlTWFwLnZhbHVlPXguZW1pc3NpdmVNYXApfWZ1bmN0aW9uIGMoeSx4KXt5LnNwZWN1bGFyLnZhbHVlLmNvcHkoeC5zcGVjdWxhcikseS5zaGluaW5lc3MudmFsdWU9TWF0aC5tYXgoeC5zaGluaW5lc3MsMWUtNCkseC5lbWlzc2l2ZU1hcCYmKHkuZW1pc3NpdmVNYXAudmFsdWU9eC5lbWlzc2l2ZU1hcCkseC5idW1wTWFwJiYoeS5idW1wTWFwLnZhbHVlPXguYnVtcE1hcCx5LmJ1bXBTY2FsZS52YWx1ZT14LmJ1bXBTY2FsZSx4LnNpZGU9PT1JaSYmKHkuYnVtcFNjYWxlLnZhbHVlKj0tMSkpLHgubm9ybWFsTWFwJiYoeS5ub3JtYWxNYXAudmFsdWU9eC5ub3JtYWxNYXAseS5ub3JtYWxTY2FsZS52YWx1ZS5jb3B5KHgubm9ybWFsU2NhbGUpLHguc2lkZT09PUlpJiZ5Lm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSx4LmRpc3BsYWNlbWVudE1hcCYmKHkuZGlzcGxhY2VtZW50TWFwLnZhbHVlPXguZGlzcGxhY2VtZW50TWFwLHkuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9eC5kaXNwbGFjZW1lbnRTY2FsZSx5LmRpc3BsYWNlbWVudEJpYXMudmFsdWU9eC5kaXNwbGFjZW1lbnRCaWFzKX1mdW5jdGlvbiB1KHkseCl7eC5ncmFkaWVudE1hcCYmKHkuZ3JhZGllbnRNYXAudmFsdWU9eC5ncmFkaWVudE1hcCkseC5lbWlzc2l2ZU1hcCYmKHkuZW1pc3NpdmVNYXAudmFsdWU9eC5lbWlzc2l2ZU1hcCkseC5idW1wTWFwJiYoeS5idW1wTWFwLnZhbHVlPXguYnVtcE1hcCx5LmJ1bXBTY2FsZS52YWx1ZT14LmJ1bXBTY2FsZSx4LnNpZGU9PT1JaSYmKHkuYnVtcFNjYWxlLnZhbHVlKj0tMSkpLHgubm9ybWFsTWFwJiYoeS5ub3JtYWxNYXAudmFsdWU9eC5ub3JtYWxNYXAseS5ub3JtYWxTY2FsZS52YWx1ZS5jb3B5KHgubm9ybWFsU2NhbGUpLHguc2lkZT09PUlpJiZ5Lm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSx4LmRpc3BsYWNlbWVudE1hcCYmKHkuZGlzcGxhY2VtZW50TWFwLnZhbHVlPXguZGlzcGxhY2VtZW50TWFwLHkuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9eC5kaXNwbGFjZW1lbnRTY2FsZSx5LmRpc3BsYWNlbWVudEJpYXMudmFsdWU9eC5kaXNwbGFjZW1lbnRCaWFzKX1mdW5jdGlvbiBoKHkseCl7eS5yb3VnaG5lc3MudmFsdWU9eC5yb3VnaG5lc3MseS5tZXRhbG5lc3MudmFsdWU9eC5tZXRhbG5lc3MseC5yb3VnaG5lc3NNYXAmJih5LnJvdWdobmVzc01hcC52YWx1ZT14LnJvdWdobmVzc01hcCkseC5tZXRhbG5lc3NNYXAmJih5Lm1ldGFsbmVzc01hcC52YWx1ZT14Lm1ldGFsbmVzc01hcCkseC5lbWlzc2l2ZU1hcCYmKHkuZW1pc3NpdmVNYXAudmFsdWU9eC5lbWlzc2l2ZU1hcCkseC5idW1wTWFwJiYoeS5idW1wTWFwLnZhbHVlPXguYnVtcE1hcCx5LmJ1bXBTY2FsZS52YWx1ZT14LmJ1bXBTY2FsZSx4LnNpZGU9PT1JaSYmKHkuYnVtcFNjYWxlLnZhbHVlKj0tMSkpLHgubm9ybWFsTWFwJiYoeS5ub3JtYWxNYXAudmFsdWU9eC5ub3JtYWxNYXAseS5ub3JtYWxTY2FsZS52YWx1ZS5jb3B5KHgubm9ybWFsU2NhbGUpLHguc2lkZT09PUlpJiZ5Lm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSx4LmRpc3BsYWNlbWVudE1hcCYmKHkuZGlzcGxhY2VtZW50TWFwLnZhbHVlPXguZGlzcGxhY2VtZW50TWFwLHkuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9eC5kaXNwbGFjZW1lbnRTY2FsZSx5LmRpc3BsYWNlbWVudEJpYXMudmFsdWU9eC5kaXNwbGFjZW1lbnRCaWFzKSxlLmdldCh4KS5lbnZNYXAmJih5LmVudk1hcEludGVuc2l0eS52YWx1ZT14LmVudk1hcEludGVuc2l0eSl9ZnVuY3Rpb24gZih5LHgsYil7aCh5LHgpLHkuaW9yLnZhbHVlPXguaW9yLHguc2hlZW4+MCYmKHkuc2hlZW5Db2xvci52YWx1ZS5jb3B5KHguc2hlZW5Db2xvcikubXVsdGlwbHlTY2FsYXIoeC5zaGVlbikseS5zaGVlblJvdWdobmVzcy52YWx1ZT14LnNoZWVuUm91Z2huZXNzLHguc2hlZW5Db2xvck1hcCYmKHkuc2hlZW5Db2xvck1hcC52YWx1ZT14LnNoZWVuQ29sb3JNYXApLHguc2hlZW5Sb3VnaG5lc3NNYXAmJih5LnNoZWVuUm91Z2huZXNzTWFwLnZhbHVlPXguc2hlZW5Sb3VnaG5lc3NNYXApKSx4LmNsZWFyY29hdD4wJiYoeS5jbGVhcmNvYXQudmFsdWU9eC5jbGVhcmNvYXQseS5jbGVhcmNvYXRSb3VnaG5lc3MudmFsdWU9eC5jbGVhcmNvYXRSb3VnaG5lc3MseC5jbGVhcmNvYXRNYXAmJih5LmNsZWFyY29hdE1hcC52YWx1ZT14LmNsZWFyY29hdE1hcCkseC5jbGVhcmNvYXRSb3VnaG5lc3NNYXAmJih5LmNsZWFyY29hdFJvdWdobmVzc01hcC52YWx1ZT14LmNsZWFyY29hdFJvdWdobmVzc01hcCkseC5jbGVhcmNvYXROb3JtYWxNYXAmJih5LmNsZWFyY29hdE5vcm1hbFNjYWxlLnZhbHVlLmNvcHkoeC5jbGVhcmNvYXROb3JtYWxTY2FsZSkseS5jbGVhcmNvYXROb3JtYWxNYXAudmFsdWU9eC5jbGVhcmNvYXROb3JtYWxNYXAseC5zaWRlPT09SWkmJnkuY2xlYXJjb2F0Tm9ybWFsU2NhbGUudmFsdWUubmVnYXRlKCkpKSx4LnRyYW5zbWlzc2lvbj4wJiYoeS50cmFuc21pc3Npb24udmFsdWU9eC50cmFuc21pc3Npb24seS50cmFuc21pc3Npb25TYW1wbGVyTWFwLnZhbHVlPWIudGV4dHVyZSx5LnRyYW5zbWlzc2lvblNhbXBsZXJTaXplLnZhbHVlLnNldChiLndpZHRoLGIuaGVpZ2h0KSx4LnRyYW5zbWlzc2lvbk1hcCYmKHkudHJhbnNtaXNzaW9uTWFwLnZhbHVlPXgudHJhbnNtaXNzaW9uTWFwKSx5LnRoaWNrbmVzcy52YWx1ZT14LnRoaWNrbmVzcyx4LnRoaWNrbmVzc01hcCYmKHkudGhpY2tuZXNzTWFwLnZhbHVlPXgudGhpY2tuZXNzTWFwKSx5LmF0dGVudWF0aW9uRGlzdGFuY2UudmFsdWU9eC5hdHRlbnVhdGlvbkRpc3RhbmNlLHkuYXR0ZW51YXRpb25Db2xvci52YWx1ZS5jb3B5KHguYXR0ZW51YXRpb25Db2xvcikpLHkuc3BlY3VsYXJJbnRlbnNpdHkudmFsdWU9eC5zcGVjdWxhckludGVuc2l0eSx5LnNwZWN1bGFyQ29sb3IudmFsdWUuY29weSh4LnNwZWN1bGFyQ29sb3IpLHguc3BlY3VsYXJJbnRlbnNpdHlNYXAmJih5LnNwZWN1bGFySW50ZW5zaXR5TWFwLnZhbHVlPXguc3BlY3VsYXJJbnRlbnNpdHlNYXApLHguc3BlY3VsYXJDb2xvck1hcCYmKHkuc3BlY3VsYXJDb2xvck1hcC52YWx1ZT14LnNwZWN1bGFyQ29sb3JNYXApfWZ1bmN0aW9uIHAoeSx4KXt4Lm1hdGNhcCYmKHkubWF0Y2FwLnZhbHVlPXgubWF0Y2FwKSx4LmJ1bXBNYXAmJih5LmJ1bXBNYXAudmFsdWU9eC5idW1wTWFwLHkuYnVtcFNjYWxlLnZhbHVlPXguYnVtcFNjYWxlLHguc2lkZT09PUlpJiYoeS5idW1wU2NhbGUudmFsdWUqPS0xKSkseC5ub3JtYWxNYXAmJih5Lm5vcm1hbE1hcC52YWx1ZT14Lm5vcm1hbE1hcCx5Lm5vcm1hbFNjYWxlLnZhbHVlLmNvcHkoeC5ub3JtYWxTY2FsZSkseC5zaWRlPT09SWkmJnkubm9ybWFsU2NhbGUudmFsdWUubmVnYXRlKCkpLHguZGlzcGxhY2VtZW50TWFwJiYoeS5kaXNwbGFjZW1lbnRNYXAudmFsdWU9eC5kaXNwbGFjZW1lbnRNYXAseS5kaXNwbGFjZW1lbnRTY2FsZS52YWx1ZT14LmRpc3BsYWNlbWVudFNjYWxlLHkuZGlzcGxhY2VtZW50Qmlhcy52YWx1ZT14LmRpc3BsYWNlbWVudEJpYXMpfWZ1bmN0aW9uIGQoeSx4KXt4LmRpc3BsYWNlbWVudE1hcCYmKHkuZGlzcGxhY2VtZW50TWFwLnZhbHVlPXguZGlzcGxhY2VtZW50TWFwLHkuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9eC5kaXNwbGFjZW1lbnRTY2FsZSx5LmRpc3BsYWNlbWVudEJpYXMudmFsdWU9eC5kaXNwbGFjZW1lbnRCaWFzKX1mdW5jdGlvbiBnKHkseCl7eC5kaXNwbGFjZW1lbnRNYXAmJih5LmRpc3BsYWNlbWVudE1hcC52YWx1ZT14LmRpc3BsYWNlbWVudE1hcCx5LmRpc3BsYWNlbWVudFNjYWxlLnZhbHVlPXguZGlzcGxhY2VtZW50U2NhbGUseS5kaXNwbGFjZW1lbnRCaWFzLnZhbHVlPXguZGlzcGxhY2VtZW50QmlhcykseS5yZWZlcmVuY2VQb3NpdGlvbi52YWx1ZS5jb3B5KHgucmVmZXJlbmNlUG9zaXRpb24pLHkubmVhckRpc3RhbmNlLnZhbHVlPXgubmVhckRpc3RhbmNlLHkuZmFyRGlzdGFuY2UudmFsdWU9eC5mYXJEaXN0YW5jZX1mdW5jdGlvbiBfKHkseCl7eC5idW1wTWFwJiYoeS5idW1wTWFwLnZhbHVlPXguYnVtcE1hcCx5LmJ1bXBTY2FsZS52YWx1ZT14LmJ1bXBTY2FsZSx4LnNpZGU9PT1JaSYmKHkuYnVtcFNjYWxlLnZhbHVlKj0tMSkpLHgubm9ybWFsTWFwJiYoeS5ub3JtYWxNYXAudmFsdWU9eC5ub3JtYWxNYXAseS5ub3JtYWxTY2FsZS52YWx1ZS5jb3B5KHgubm9ybWFsU2NhbGUpLHguc2lkZT09PUlpJiZ5Lm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSx4LmRpc3BsYWNlbWVudE1hcCYmKHkuZGlzcGxhY2VtZW50TWFwLnZhbHVlPXguZGlzcGxhY2VtZW50TWFwLHkuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9eC5kaXNwbGFjZW1lbnRTY2FsZSx5LmRpc3BsYWNlbWVudEJpYXMudmFsdWU9eC5kaXNwbGFjZW1lbnRCaWFzKX1yZXR1cm57cmVmcmVzaEZvZ1VuaWZvcm1zOnQscmVmcmVzaE1hdGVyaWFsVW5pZm9ybXM6cn19ZnVuY3Rpb24gUGdyKCl7bGV0IGU9UVAoImNhbnZhcyIpO3JldHVybiBlLnN0eWxlLmRpc3BsYXk9ImJsb2NrIixlfWZ1bmN0aW9uIHJuKGU9e30pe2xldCB0PWUuY2FudmFzIT09dm9pZCAwP2UuY2FudmFzOlBncigpLHI9ZS5jb250ZXh0IT09dm9pZCAwP2UuY29udGV4dDpudWxsLG49ZS5hbHBoYSE9PXZvaWQgMD9lLmFscGhhOiExLGk9ZS5kZXB0aCE9PXZvaWQgMD9lLmRlcHRoOiEwLG89ZS5zdGVuY2lsIT09dm9pZCAwP2Uuc3RlbmNpbDohMCxhPWUuYW50aWFsaWFzIT09dm9pZCAwP2UuYW50aWFsaWFzOiExLHM9ZS5wcmVtdWx0aXBsaWVkQWxwaGEhPT12b2lkIDA/ZS5wcmVtdWx0aXBsaWVkQWxwaGE6ITAsbD1lLnByZXNlcnZlRHJhd2luZ0J1ZmZlciE9PXZvaWQgMD9lLnByZXNlcnZlRHJhd2luZ0J1ZmZlcjohMSxjPWUucG93ZXJQcmVmZXJlbmNlIT09dm9pZCAwP2UucG93ZXJQcmVmZXJlbmNlOiJkZWZhdWx0Iix1PWUuZmFpbElmTWFqb3JQZXJmb3JtYW5jZUNhdmVhdCE9PXZvaWQgMD9lLmZhaWxJZk1ham9yUGVyZm9ybWFuY2VDYXZlYXQ6ITEsaD1udWxsLGY9bnVsbCxwPVtdLGQ9W107dGhpcy5kb21FbGVtZW50PXQsdGhpcy5kZWJ1Zz17Y2hlY2tTaGFkZXJFcnJvcnM6ITB9LHRoaXMuYXV0b0NsZWFyPSEwLHRoaXMuYXV0b0NsZWFyQ29sb3I9ITAsdGhpcy5hdXRvQ2xlYXJEZXB0aD0hMCx0aGlzLmF1dG9DbGVhclN0ZW5jaWw9ITAsdGhpcy5zb3J0T2JqZWN0cz0hMCx0aGlzLmNsaXBwaW5nUGxhbmVzPVtdLHRoaXMubG9jYWxDbGlwcGluZ0VuYWJsZWQ9ITEsdGhpcy5vdXRwdXRFbmNvZGluZz1RZCx0aGlzLnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzPSExLHRoaXMudG9uZU1hcHBpbmc9S2QsdGhpcy50b25lTWFwcGluZ0V4cG9zdXJlPTE7bGV0IGc9dGhpcyxfPSExLHk9MCx4PTAsYj1udWxsLFM9LTEsQz1udWxsLFA9bmV3IGVuLGs9bmV3IGVuLE89bnVsbCxEPXQud2lkdGgsQj10LmhlaWdodCxJPTEsTD1udWxsLFI9bnVsbCxGPW5ldyBlbigwLDAsRCxCKSx6PW5ldyBlbigwLDAsRCxCKSxVPSExLFc9bmV3IE52LFo9ITEscnQ9ITEsb3Q9bnVsbCxzdD1uZXcgTWUsU3Q9bmV3IGosYnQ9e2JhY2tncm91bmQ6bnVsbCxmb2c6bnVsbCxlbnZpcm9ubWVudDpudWxsLG92ZXJyaWRlTWF0ZXJpYWw6bnVsbCxpc1NjZW5lOiEwfTtmdW5jdGlvbiBNdCgpe3JldHVybiBiPT09bnVsbD9JOjF9bGV0IGx0PXI7ZnVuY3Rpb24gS3QoSyxndCl7Zm9yKGxldCBFdD0wO0V0PEsubGVuZ3RoO0V0Kyspe2xldCB4dD1LW0V0XSxGdD10LmdldENvbnRleHQoeHQsZ3QpO2lmKEZ0IT09bnVsbClyZXR1cm4gRnR9cmV0dXJuIG51bGx9dHJ5e2xldCBLPXthbHBoYTohMCxkZXB0aDppLHN0ZW5jaWw6byxhbnRpYWxpYXM6YSxwcmVtdWx0aXBsaWVkQWxwaGE6cyxwcmVzZXJ2ZURyYXdpbmdCdWZmZXI6bCxwb3dlclByZWZlcmVuY2U6YyxmYWlsSWZNYWpvclBlcmZvcm1hbmNlQ2F2ZWF0OnV9O2lmKCJzZXRBdHRyaWJ1dGUiaW4gdCYmdC5zZXRBdHRyaWJ1dGUoImRhdGEtZW5naW5lIixgdGhyZWUuanMgciR7WVV9YCksdC5hZGRFdmVudExpc3RlbmVyKCJ3ZWJnbGNvbnRleHRsb3N0IixmZSwhMSksdC5hZGRFdmVudExpc3RlbmVyKCJ3ZWJnbGNvbnRleHRyZXN0b3JlZCIsYXQsITEpLGx0PT09bnVsbCl7bGV0IGd0PVsid2ViZ2wyIiwid2ViZ2wiLCJleHBlcmltZW50YWwtd2ViZ2wiXTtpZihnLmlzV2ViR0wxUmVuZGVyZXI9PT0hMCYmZ3Quc2hpZnQoKSxsdD1LdChndCxLKSxsdD09PW51bGwpdGhyb3cgS3QoZ3QpP25ldyBFcnJvcigiRXJyb3IgY3JlYXRpbmcgV2ViR0wgY29udGV4dCB3aXRoIHlvdXIgc2VsZWN0ZWQgYXR0cmlidXRlcy4iKTpuZXcgRXJyb3IoIkVycm9yIGNyZWF0aW5nIFdlYkdMIGNvbnRleHQuIil9bHQuZ2V0U2hhZGVyUHJlY2lzaW9uRm9ybWF0PT09dm9pZCAwJiYobHQuZ2V0U2hhZGVyUHJlY2lzaW9uRm9ybWF0PWZ1bmN0aW9uKCl7cmV0dXJue3JhbmdlTWluOjEscmFuZ2VNYXg6MSxwcmVjaXNpb246MX19KX1jYXRjaChLKXt0aHJvdyBjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAiK0subWVzc2FnZSksS31sZXQgX3QsY3QsWCxldCxkdCxxLHB0LGh0LHd0LGt0LGllLGVlLExlLGFyLGZyLHR0LCQsSXQsJHQsaGUsVHQsYmUsbnQ7ZnVuY3Rpb24gQ3QoKXtfdD1uZXcgSmRyKGx0KSxjdD1uZXcgWWRyKGx0LF90LGUpLF90LmluaXQoY3QpLGJlPW5ldyBYZmUobHQsX3QsY3QpLFg9bmV3IEVncihsdCxfdCxjdCksZXQ9bmV3IGVtcihsdCksZHQ9bmV3IGRncixxPW5ldyBUZ3IobHQsX3QsWCxkdCxjdCxiZSxldCkscHQ9bmV3IFhkcihnKSxodD1uZXcgWmRyKGcpLHd0PW5ldyBnZnIobHQsY3QpLG50PW5ldyBHZHIobHQsX3Qsd3QsY3QpLGt0PW5ldyBRZHIobHQsd3QsZXQsbnQpLGllPW5ldyBvbXIobHQsa3Qsd3QsZXQpLCR0PW5ldyBpbXIobHQsY3QscSksdHQ9bmV3IGpkcihkdCksZWU9bmV3IHBncihnLHB0LGh0LF90LGN0LG50LHR0KSxMZT1uZXcgQWdyKGR0KSxhcj1uZXcgZ2dyLGZyPW5ldyB3Z3IoX3QsY3QpLEl0PW5ldyBxZHIoZyxwdCxYLGllLG4scyksJD1uZXcgamZlKGcsaWUsY3QpLGhlPW5ldyBXZHIobHQsX3QsZXQsY3QpLFR0PW5ldyB0bXIobHQsX3QsZXQsY3QpLGV0LnByb2dyYW1zPWVlLnByb2dyYW1zLGcuY2FwYWJpbGl0aWVzPWN0LGcuZXh0ZW5zaW9ucz1fdCxnLnByb3BlcnRpZXM9ZHQsZy5yZW5kZXJMaXN0cz1hcixnLnNoYWRvd01hcD0kLGcuc3RhdGU9WCxnLmluZm89ZXR9Q3QoKTtsZXQgV3Q9bmV3IHJodChnLGx0KTt0aGlzLnhyPVd0LHRoaXMuZ2V0Q29udGV4dD1mdW5jdGlvbigpe3JldHVybiBsdH0sdGhpcy5nZXRDb250ZXh0QXR0cmlidXRlcz1mdW5jdGlvbigpe3JldHVybiBsdC5nZXRDb250ZXh0QXR0cmlidXRlcygpfSx0aGlzLmZvcmNlQ29udGV4dExvc3M9ZnVuY3Rpb24oKXtsZXQgSz1fdC5nZXQoIldFQkdMX2xvc2VfY29udGV4dCIpO0smJksubG9zZUNvbnRleHQoKX0sdGhpcy5mb3JjZUNvbnRleHRSZXN0b3JlPWZ1bmN0aW9uKCl7bGV0IEs9X3QuZ2V0KCJXRUJHTF9sb3NlX2NvbnRleHQiKTtLJiZLLnJlc3RvcmVDb250ZXh0KCl9LHRoaXMuZ2V0UGl4ZWxSYXRpbz1mdW5jdGlvbigpe3JldHVybiBJfSx0aGlzLnNldFBpeGVsUmF0aW89ZnVuY3Rpb24oSyl7SyE9PXZvaWQgMCYmKEk9Syx0aGlzLnNldFNpemUoRCxCLCExKSl9LHRoaXMuZ2V0U2l6ZT1mdW5jdGlvbihLKXtyZXR1cm4gSy5zZXQoRCxCKX0sdGhpcy5zZXRTaXplPWZ1bmN0aW9uKEssZ3QsRXQpe2lmKFd0LmlzUHJlc2VudGluZyl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBDYW4ndCBjaGFuZ2Ugc2l6ZSB3aGlsZSBWUiBkZXZpY2UgaXMgcHJlc2VudGluZy4iKTtyZXR1cm59RD1LLEI9Z3QsdC53aWR0aD1NYXRoLmZsb29yKEsqSSksdC5oZWlnaHQ9TWF0aC5mbG9vcihndCpJKSxFdCE9PSExJiYodC5zdHlsZS53aWR0aD1LKyJweCIsdC5zdHlsZS5oZWlnaHQ9Z3QrInB4IiksdGhpcy5zZXRWaWV3cG9ydCgwLDAsSyxndCl9LHRoaXMuZ2V0RHJhd2luZ0J1ZmZlclNpemU9ZnVuY3Rpb24oSyl7cmV0dXJuIEsuc2V0KEQqSSxCKkkpLmZsb29yKCl9LHRoaXMuc2V0RHJhd2luZ0J1ZmZlclNpemU9ZnVuY3Rpb24oSyxndCxFdCl7RD1LLEI9Z3QsST1FdCx0LndpZHRoPU1hdGguZmxvb3IoSypFdCksdC5oZWlnaHQ9TWF0aC5mbG9vcihndCpFdCksdGhpcy5zZXRWaWV3cG9ydCgwLDAsSyxndCl9LHRoaXMuZ2V0Q3VycmVudFZpZXdwb3J0PWZ1bmN0aW9uKEspe3JldHVybiBLLmNvcHkoUCl9LHRoaXMuZ2V0Vmlld3BvcnQ9ZnVuY3Rpb24oSyl7cmV0dXJuIEsuY29weShGKX0sdGhpcy5zZXRWaWV3cG9ydD1mdW5jdGlvbihLLGd0LEV0LHh0KXtLLmlzVmVjdG9yND9GLnNldChLLngsSy55LEsueixLLncpOkYuc2V0KEssZ3QsRXQseHQpLFgudmlld3BvcnQoUC5jb3B5KEYpLm11bHRpcGx5U2NhbGFyKEkpLmZsb29yKCkpfSx0aGlzLmdldFNjaXNzb3I9ZnVuY3Rpb24oSyl7cmV0dXJuIEsuY29weSh6KX0sdGhpcy5zZXRTY2lzc29yPWZ1bmN0aW9uKEssZ3QsRXQseHQpe0suaXNWZWN0b3I0P3ouc2V0KEsueCxLLnksSy56LEsudyk6ei5zZXQoSyxndCxFdCx4dCksWC5zY2lzc29yKGsuY29weSh6KS5tdWx0aXBseVNjYWxhcihJKS5mbG9vcigpKX0sdGhpcy5nZXRTY2lzc29yVGVzdD1mdW5jdGlvbigpe3JldHVybiBVfSx0aGlzLnNldFNjaXNzb3JUZXN0PWZ1bmN0aW9uKEspe1guc2V0U2Npc3NvclRlc3QoVT1LKX0sdGhpcy5zZXRPcGFxdWVTb3J0PWZ1bmN0aW9uKEspe0w9S30sdGhpcy5zZXRUcmFuc3BhcmVudFNvcnQ9ZnVuY3Rpb24oSyl7Uj1LfSx0aGlzLmdldENsZWFyQ29sb3I9ZnVuY3Rpb24oSyl7cmV0dXJuIEsuY29weShJdC5nZXRDbGVhckNvbG9yKCkpfSx0aGlzLnNldENsZWFyQ29sb3I9ZnVuY3Rpb24oKXtJdC5zZXRDbGVhckNvbG9yLmFwcGx5KEl0LGFyZ3VtZW50cyl9LHRoaXMuZ2V0Q2xlYXJBbHBoYT1mdW5jdGlvbigpe3JldHVybiBJdC5nZXRDbGVhckFscGhhKCl9LHRoaXMuc2V0Q2xlYXJBbHBoYT1mdW5jdGlvbigpe0l0LnNldENsZWFyQWxwaGEuYXBwbHkoSXQsYXJndW1lbnRzKX0sdGhpcy5jbGVhcj1mdW5jdGlvbihLLGd0LEV0KXtsZXQgeHQ9MDsoSz09PXZvaWQgMHx8SykmJih4dHw9MTYzODQpLChndD09PXZvaWQgMHx8Z3QpJiYoeHR8PTI1NiksKEV0PT09dm9pZCAwfHxFdCkmJih4dHw9MTAyNCksbHQuY2xlYXIoeHQpfSx0aGlzLmNsZWFyQ29sb3I9ZnVuY3Rpb24oKXt0aGlzLmNsZWFyKCEwLCExLCExKX0sdGhpcy5jbGVhckRlcHRoPWZ1bmN0aW9uKCl7dGhpcy5jbGVhcighMSwhMCwhMSl9LHRoaXMuY2xlYXJTdGVuY2lsPWZ1bmN0aW9uKCl7dGhpcy5jbGVhcighMSwhMSwhMCl9LHRoaXMuZGlzcG9zZT1mdW5jdGlvbigpe3QucmVtb3ZlRXZlbnRMaXN0ZW5lcigid2ViZ2xjb250ZXh0bG9zdCIsZmUsITEpLHQucmVtb3ZlRXZlbnRMaXN0ZW5lcigid2ViZ2xjb250ZXh0cmVzdG9yZWQiLGF0LCExKSxhci5kaXNwb3NlKCksZnIuZGlzcG9zZSgpLGR0LmRpc3Bvc2UoKSxwdC5kaXNwb3NlKCksaHQuZGlzcG9zZSgpLGllLmRpc3Bvc2UoKSxudC5kaXNwb3NlKCksZWUuZGlzcG9zZSgpLFd0LmRpc3Bvc2UoKSxXdC5yZW1vdmVFdmVudExpc3RlbmVyKCJzZXNzaW9uc3RhcnQiLHplKSxXdC5yZW1vdmVFdmVudExpc3RlbmVyKCJzZXNzaW9uZW5kIix5biksb3QmJihvdC5kaXNwb3NlKCksb3Q9bnVsbCksV2kuc3RvcCgpfTtmdW5jdGlvbiBmZShLKXtLLnByZXZlbnREZWZhdWx0KCksY29uc29sZS5sb2coIlRIUkVFLldlYkdMUmVuZGVyZXI6IENvbnRleHQgTG9zdC4iKSxfPSEwfWZ1bmN0aW9uIGF0KCl7Y29uc29sZS5sb2coIlRIUkVFLldlYkdMUmVuZGVyZXI6IENvbnRleHQgUmVzdG9yZWQuIiksXz0hMTtsZXQgSz1ldC5hdXRvUmVzZXQsZ3Q9JC5lbmFibGVkLEV0PSQuYXV0b1VwZGF0ZSx4dD0kLm5lZWRzVXBkYXRlLEZ0PSQudHlwZTtDdCgpLGV0LmF1dG9SZXNldD1LLCQuZW5hYmxlZD1ndCwkLmF1dG9VcGRhdGU9RXQsJC5uZWVkc1VwZGF0ZT14dCwkLnR5cGU9RnR9ZnVuY3Rpb24gc2UoSyl7bGV0IGd0PUsudGFyZ2V0O2d0LnJlbW92ZUV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLHNlKSxRdChndCl9ZnVuY3Rpb24gUXQoSyl7Q2UoSyksZHQucmVtb3ZlKEspfWZ1bmN0aW9uIENlKEspe2xldCBndD1kdC5nZXQoSykucHJvZ3JhbXM7Z3QhPT12b2lkIDAmJihndC5mb3JFYWNoKGZ1bmN0aW9uKEV0KXtlZS5yZWxlYXNlUHJvZ3JhbShFdCl9KSxLLmlzU2hhZGVyTWF0ZXJpYWwmJmVlLnJlbGVhc2VTaGFkZXJDYWNoZShLKSl9dGhpcy5yZW5kZXJCdWZmZXJEaXJlY3Q9ZnVuY3Rpb24oSyxndCxFdCx4dCxGdCxWZSl7Z3Q9PT1udWxsJiYoZ3Q9YnQpO2xldCBVZT1GdC5pc01lc2gmJkZ0Lm1hdHJpeFdvcmxkLmRldGVybWluYW50KCk8MCx0cj1jbihLLGd0LEV0LHh0LEZ0KTtYLnNldE1hdGVyaWFsKHh0LFVlKTtsZXQgS2U9RXQuaW5kZXgsWHI9RXQuYXR0cmlidXRlcy5wb3NpdGlvbjtpZihLZT09PW51bGwpe2lmKFhyPT09dm9pZCAwfHxYci5jb3VudD09PTApcmV0dXJufWVsc2UgaWYoS2UuY291bnQ9PT0wKXJldHVybjtsZXQgX3I9MTt4dC53aXJlZnJhbWU9PT0hMCYmKEtlPWt0LmdldFdpcmVmcmFtZUF0dHJpYnV0ZShFdCksX3I9MiksbnQuc2V0dXAoRnQseHQsdHIsRXQsS2UpO2xldCBQcixYbj1oZTtLZSE9PW51bGwmJihQcj13dC5nZXQoS2UpLFhuPVR0LFhuLnNldEluZGV4KFByKSk7bGV0IG5wPUtlIT09bnVsbD9LZS5jb3VudDpYci5jb3VudCx1bT1FdC5kcmF3UmFuZ2Uuc3RhcnQqX3IsbXI9RXQuZHJhd1JhbmdlLmNvdW50Kl9yLEZsPVZlIT09bnVsbD9WZS5zdGFydCpfcjowLCRuPVZlIT09bnVsbD9WZS5jb3VudCpfcjoxLzAsQmw9TWF0aC5tYXgodW0sRmwpLHV4PU1hdGgubWluKG5wLHVtK21yLEZsKyRuKS0xLEhsPU1hdGgubWF4KDAsdXgtQmwrMSk7aWYoSGwhPT0wKXtpZihGdC5pc01lc2gpeHQud2lyZWZyYW1lPT09ITA/KFguc2V0TGluZVdpZHRoKHh0LndpcmVmcmFtZUxpbmV3aWR0aCpNdCgpKSxYbi5zZXRNb2RlKDEpKTpYbi5zZXRNb2RlKDQpO2Vsc2UgaWYoRnQuaXNMaW5lKXtsZXQgVmw9eHQubGluZXdpZHRoO1ZsPT09dm9pZCAwJiYoVmw9MSksWC5zZXRMaW5lV2lkdGgoVmwqTXQoKSksRnQuaXNMaW5lU2VnbWVudHM/WG4uc2V0TW9kZSgxKTpGdC5pc0xpbmVMb29wP1huLnNldE1vZGUoMik6WG4uc2V0TW9kZSgzKX1lbHNlIEZ0LmlzUG9pbnRzP1huLnNldE1vZGUoMCk6RnQuaXNTcHJpdGUmJlhuLnNldE1vZGUoNCk7aWYoRnQuaXNJbnN0YW5jZWRNZXNoKVhuLnJlbmRlckluc3RhbmNlcyhCbCxIbCxGdC5jb3VudCk7ZWxzZSBpZihFdC5pc0luc3RhbmNlZEJ1ZmZlckdlb21ldHJ5KXtsZXQgVmw9TWF0aC5taW4oRXQuaW5zdGFuY2VDb3VudCxFdC5fbWF4SW5zdGFuY2VDb3VudCk7WG4ucmVuZGVySW5zdGFuY2VzKEJsLEhsLFZsKX1lbHNlIFhuLnJlbmRlcihCbCxIbCl9fSx0aGlzLmNvbXBpbGU9ZnVuY3Rpb24oSyxndCl7Zj1mci5nZXQoSyksZi5pbml0KCksZC5wdXNoKGYpLEsudHJhdmVyc2VWaXNpYmxlKGZ1bmN0aW9uKEV0KXtFdC5pc0xpZ2h0JiZFdC5sYXllcnMudGVzdChndC5sYXllcnMpJiYoZi5wdXNoTGlnaHQoRXQpLEV0LmNhc3RTaGFkb3cmJmYucHVzaFNoYWRvdyhFdCkpfSksZi5zZXR1cExpZ2h0cyhnLnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzKSxLLnRyYXZlcnNlKGZ1bmN0aW9uKEV0KXtsZXQgeHQ9RXQubWF0ZXJpYWw7aWYoeHQpaWYoQXJyYXkuaXNBcnJheSh4dCkpZm9yKGxldCBGdD0wO0Z0PHh0Lmxlbmd0aDtGdCsrKXtsZXQgVmU9eHRbRnRdO2NtKFZlLEssRXQpfWVsc2UgY20oeHQsSyxFdCl9KSxkLnBvcCgpLGY9bnVsbH07bGV0IFB0PW51bGw7ZnVuY3Rpb24gTnQoSyl7UHQmJlB0KEspfWZ1bmN0aW9uIHplKCl7V2kuc3RvcCgpfWZ1bmN0aW9uIHluKCl7V2kuc3RhcnQoKX1sZXQgV2k9bmV3IHpmZTtXaS5zZXRBbmltYXRpb25Mb29wKE50KSx0eXBlb2Ygd2luZG93IT0idW5kZWZpbmVkIiYmV2kuc2V0Q29udGV4dCh3aW5kb3cpLHRoaXMuc2V0QW5pbWF0aW9uTG9vcD1mdW5jdGlvbihLKXtQdD1LLFd0LnNldEFuaW1hdGlvbkxvb3AoSyksSz09PW51bGw/V2kuc3RvcCgpOldpLnN0YXJ0KCl9LFd0LmFkZEV2ZW50TGlzdGVuZXIoInNlc3Npb25zdGFydCIsemUpLFd0LmFkZEV2ZW50TGlzdGVuZXIoInNlc3Npb25lbmQiLHluKSx0aGlzLnJlbmRlcj1mdW5jdGlvbihLLGd0KXtpZihndCE9PXZvaWQgMCYmZ3QuaXNDYW1lcmEhPT0hMCl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlci5yZW5kZXI6IGNhbWVyYSBpcyBub3QgYW4gaW5zdGFuY2Ugb2YgVEhSRUUuQ2FtZXJhLiIpO3JldHVybn1pZihfPT09ITApcmV0dXJuO0suYXV0b1VwZGF0ZT09PSEwJiZLLnVwZGF0ZU1hdHJpeFdvcmxkKCksZ3QucGFyZW50PT09bnVsbCYmZ3QudXBkYXRlTWF0cml4V29ybGQoKSxXdC5lbmFibGVkPT09ITAmJld0LmlzUHJlc2VudGluZz09PSEwJiYoV3QuY2FtZXJhQXV0b1VwZGF0ZT09PSEwJiZXdC51cGRhdGVDYW1lcmEoZ3QpLGd0PVd0LmdldENhbWVyYSgpKSxLLmlzU2NlbmU9PT0hMCYmSy5vbkJlZm9yZVJlbmRlcihnLEssZ3QsYiksZj1mci5nZXQoSyxkLmxlbmd0aCksZi5pbml0KCksZC5wdXNoKGYpLHN0Lm11bHRpcGx5TWF0cmljZXMoZ3QucHJvamVjdGlvbk1hdHJpeCxndC5tYXRyaXhXb3JsZEludmVyc2UpLFcuc2V0RnJvbVByb2plY3Rpb25NYXRyaXgoc3QpLHJ0PXRoaXMubG9jYWxDbGlwcGluZ0VuYWJsZWQsWj10dC5pbml0KHRoaXMuY2xpcHBpbmdQbGFuZXMscnQsZ3QpLGg9YXIuZ2V0KEsscC5sZW5ndGgpLGguaW5pdCgpLHAucHVzaChoKSxBcihLLGd0LDAsZy5zb3J0T2JqZWN0cyksaC5maW5pc2goKSxnLnNvcnRPYmplY3RzPT09ITAmJmguc29ydChMLFIpLFo9PT0hMCYmdHQuYmVnaW5TaGFkb3dzKCk7bGV0IEV0PWYuc3RhdGUuc2hhZG93c0FycmF5O2lmKCQucmVuZGVyKEV0LEssZ3QpLFo9PT0hMCYmdHQuZW5kU2hhZG93cygpLHRoaXMuaW5mby5hdXRvUmVzZXQ9PT0hMCYmdGhpcy5pbmZvLnJlc2V0KCksSXQucmVuZGVyKGgsSyksZi5zZXR1cExpZ2h0cyhnLnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzKSxndC5pc0FycmF5Q2FtZXJhKXtsZXQgeHQ9Z3QuY2FtZXJhcztmb3IobGV0IEZ0PTAsVmU9eHQubGVuZ3RoO0Z0PFZlO0Z0Kyspe2xldCBVZT14dFtGdF07UGEoaCxLLFVlLFVlLnZpZXdwb3J0KX19ZWxzZSBQYShoLEssZ3QpO2IhPT1udWxsJiYocS51cGRhdGVNdWx0aXNhbXBsZVJlbmRlclRhcmdldChiKSxxLnVwZGF0ZVJlbmRlclRhcmdldE1pcG1hcChiKSksSy5pc1NjZW5lPT09ITAmJksub25BZnRlclJlbmRlcihnLEssZ3QpLFguYnVmZmVycy5kZXB0aC5zZXRUZXN0KCEwKSxYLmJ1ZmZlcnMuZGVwdGguc2V0TWFzayghMCksWC5idWZmZXJzLmNvbG9yLnNldE1hc2soITApLFguc2V0UG9seWdvbk9mZnNldCghMSksbnQucmVzZXREZWZhdWx0U3RhdGUoKSxTPS0xLEM9bnVsbCxkLnBvcCgpLGQubGVuZ3RoPjA/Zj1kW2QubGVuZ3RoLTFdOmY9bnVsbCxwLnBvcCgpLHAubGVuZ3RoPjA/aD1wW3AubGVuZ3RoLTFdOmg9bnVsbH07ZnVuY3Rpb24gQXIoSyxndCxFdCx4dCl7aWYoSy52aXNpYmxlPT09ITEpcmV0dXJuO2lmKEsubGF5ZXJzLnRlc3QoZ3QubGF5ZXJzKSl7aWYoSy5pc0dyb3VwKUV0PUsucmVuZGVyT3JkZXI7ZWxzZSBpZihLLmlzTE9EKUsuYXV0b1VwZGF0ZT09PSEwJiZLLnVwZGF0ZShndCk7ZWxzZSBpZihLLmlzTGlnaHQpZi5wdXNoTGlnaHQoSyksSy5jYXN0U2hhZG93JiZmLnB1c2hTaGFkb3coSyk7ZWxzZSBpZihLLmlzU3ByaXRlKXtpZighSy5mcnVzdHVtQ3VsbGVkfHxXLmludGVyc2VjdHNTcHJpdGUoSykpe3h0JiZTdC5zZXRGcm9tTWF0cml4UG9zaXRpb24oSy5tYXRyaXhXb3JsZCkuYXBwbHlNYXRyaXg0KHN0KTtsZXQgVWU9aWUudXBkYXRlKEspLHRyPUsubWF0ZXJpYWw7dHIudmlzaWJsZSYmaC5wdXNoKEssVWUsdHIsRXQsU3QueixudWxsKX19ZWxzZSBpZigoSy5pc01lc2h8fEsuaXNMaW5lfHxLLmlzUG9pbnRzKSYmKEsuaXNTa2lubmVkTWVzaCYmSy5za2VsZXRvbi5mcmFtZSE9PWV0LnJlbmRlci5mcmFtZSYmKEsuc2tlbGV0b24udXBkYXRlKCksSy5za2VsZXRvbi5mcmFtZT1ldC5yZW5kZXIuZnJhbWUpLCFLLmZydXN0dW1DdWxsZWR8fFcuaW50ZXJzZWN0c09iamVjdChLKSkpe3h0JiZTdC5zZXRGcm9tTWF0cml4UG9zaXRpb24oSy5tYXRyaXhXb3JsZCkuYXBwbHlNYXRyaXg0KHN0KTtsZXQgVWU9aWUudXBkYXRlKEspLHRyPUsubWF0ZXJpYWw7aWYoQXJyYXkuaXNBcnJheSh0cikpe2xldCBLZT1VZS5ncm91cHM7Zm9yKGxldCBYcj0wLF9yPUtlLmxlbmd0aDtYcjxfcjtYcisrKXtsZXQgUHI9S2VbWHJdLFhuPXRyW1ByLm1hdGVyaWFsSW5kZXhdO1huJiZYbi52aXNpYmxlJiZoLnB1c2goSyxVZSxYbixFdCxTdC56LFByKX19ZWxzZSB0ci52aXNpYmxlJiZoLnB1c2goSyxVZSx0cixFdCxTdC56LG51bGwpfX1sZXQgVmU9Sy5jaGlsZHJlbjtmb3IobGV0IFVlPTAsdHI9VmUubGVuZ3RoO1VlPHRyO1VlKyspQXIoVmVbVWVdLGd0LEV0LHh0KX1mdW5jdGlvbiBQYShLLGd0LEV0LHh0KXtsZXQgRnQ9Sy5vcGFxdWUsVmU9Sy50cmFuc21pc3NpdmUsVWU9Sy50cmFuc3BhcmVudDtmLnNldHVwTGlnaHRzVmlldyhFdCksVmUubGVuZ3RoPjAmJmhvKEZ0LGd0LEV0KSx4dCYmWC52aWV3cG9ydChQLmNvcHkoeHQpKSxGdC5sZW5ndGg+MCYmSWEoRnQsZ3QsRXQpLFZlLmxlbmd0aD4wJiZJYShWZSxndCxFdCksVWUubGVuZ3RoPjAmJklhKFVlLGd0LEV0KX1mdW5jdGlvbiBobyhLLGd0LEV0KXtpZihvdD09PW51bGwpe2xldCBVZT1hPT09ITAmJmN0LmlzV2ViR0wyPT09ITA/ajM6dXM7b3Q9bmV3IFVlKDEwMjQsMTAyNCx7Z2VuZXJhdGVNaXBtYXBzOiEwLHR5cGU6YmUuY29udmVydChDdikhPT1udWxsP0N2OlpkLG1pbkZpbHRlcjpveCxtYWdGaWx0ZXI6TGksd3JhcFM6Sm8sd3JhcFQ6Sm8sdXNlUmVuZGVyVG9UZXh0dXJlOl90LmhhcygiV0VCR0xfbXVsdGlzYW1wbGVkX3JlbmRlcl90b190ZXh0dXJlIil9KX1sZXQgeHQ9Zy5nZXRSZW5kZXJUYXJnZXQoKTtnLnNldFJlbmRlclRhcmdldChvdCksZy5jbGVhcigpO2xldCBGdD1nLnRvbmVNYXBwaW5nO2cudG9uZU1hcHBpbmc9S2QsSWEoSyxndCxFdCksZy50b25lTWFwcGluZz1GdCxxLnVwZGF0ZU11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0KG90KSxxLnVwZGF0ZVJlbmRlclRhcmdldE1pcG1hcChvdCksZy5zZXRSZW5kZXJUYXJnZXQoeHQpfWZ1bmN0aW9uIElhKEssZ3QsRXQpe2xldCB4dD1ndC5pc1NjZW5lPT09ITA/Z3Qub3ZlcnJpZGVNYXRlcmlhbDpudWxsO2ZvcihsZXQgRnQ9MCxWZT1LLmxlbmd0aDtGdDxWZTtGdCsrKXtsZXQgVWU9S1tGdF0sdHI9VWUub2JqZWN0LEtlPVVlLmdlb21ldHJ5LFhyPXh0PT09bnVsbD9VZS5tYXRlcmlhbDp4dCxfcj1VZS5ncm91cDt0ci5sYXllcnMudGVzdChFdC5sYXllcnMpJiZseCh0cixndCxFdCxLZSxYcixfcil9fWZ1bmN0aW9uIGx4KEssZ3QsRXQseHQsRnQsVmUpe0sub25CZWZvcmVSZW5kZXIoZyxndCxFdCx4dCxGdCxWZSksSy5tb2RlbFZpZXdNYXRyaXgubXVsdGlwbHlNYXRyaWNlcyhFdC5tYXRyaXhXb3JsZEludmVyc2UsSy5tYXRyaXhXb3JsZCksSy5ub3JtYWxNYXRyaXguZ2V0Tm9ybWFsTWF0cml4KEsubW9kZWxWaWV3TWF0cml4KSxGdC5vbkJlZm9yZVJlbmRlcihnLGd0LEV0LHh0LEssVmUpLEZ0LnRyYW5zcGFyZW50PT09ITAmJkZ0LnNpZGU9PT1Mdj8oRnQuc2lkZT1JaSxGdC5uZWVkc1VwZGF0ZT0hMCxnLnJlbmRlckJ1ZmZlckRpcmVjdChFdCxndCx4dCxGdCxLLFZlKSxGdC5zaWRlPUl2LEZ0Lm5lZWRzVXBkYXRlPSEwLGcucmVuZGVyQnVmZmVyRGlyZWN0KEV0LGd0LHh0LEZ0LEssVmUpLEZ0LnNpZGU9THYpOmcucmVuZGVyQnVmZmVyRGlyZWN0KEV0LGd0LHh0LEZ0LEssVmUpLEsub25BZnRlclJlbmRlcihnLGd0LEV0LHh0LEZ0LFZlKX1mdW5jdGlvbiBjbShLLGd0LEV0KXtndC5pc1NjZW5lIT09ITAmJihndD1idCk7bGV0IHh0PWR0LmdldChLKSxGdD1mLnN0YXRlLmxpZ2h0cyxWZT1mLnN0YXRlLnNoYWRvd3NBcnJheSxVZT1GdC5zdGF0ZS52ZXJzaW9uLHRyPWVlLmdldFBhcmFtZXRlcnMoSyxGdC5zdGF0ZSxWZSxndCxFdCksS2U9ZWUuZ2V0UHJvZ3JhbUNhY2hlS2V5KHRyKSxYcj14dC5wcm9ncmFtczt4dC5lbnZpcm9ubWVudD1LLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWw/Z3QuZW52aXJvbm1lbnQ6bnVsbCx4dC5mb2c9Z3QuZm9nLHh0LmVudk1hcD0oSy5pc01lc2hTdGFuZGFyZE1hdGVyaWFsP2h0OnB0KS5nZXQoSy5lbnZNYXB8fHh0LmVudmlyb25tZW50KSxYcj09PXZvaWQgMCYmKEsuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsc2UpLFhyPW5ldyBNYXAseHQucHJvZ3JhbXM9WHIpO2xldCBfcj1Yci5nZXQoS2UpO2lmKF9yIT09dm9pZCAwKXtpZih4dC5jdXJyZW50UHJvZ3JhbT09PV9yJiZ4dC5saWdodHNTdGF0ZVZlcnNpb249PT1VZSlyZXR1cm4gSjAoSyx0ciksX3J9ZWxzZSB0ci51bmlmb3Jtcz1lZS5nZXRVbmlmb3JtcyhLKSxLLm9uQnVpbGQoRXQsdHIsZyksSy5vbkJlZm9yZUNvbXBpbGUodHIsZyksX3I9ZWUuYWNxdWlyZVByb2dyYW0odHIsS2UpLFhyLnNldChLZSxfcikseHQudW5pZm9ybXM9dHIudW5pZm9ybXM7bGV0IFByPXh0LnVuaWZvcm1zOyghSy5pc1NoYWRlck1hdGVyaWFsJiYhSy5pc1Jhd1NoYWRlck1hdGVyaWFsfHxLLmNsaXBwaW5nPT09ITApJiYoUHIuY2xpcHBpbmdQbGFuZXM9dHQudW5pZm9ybSksSjAoSyx0cikseHQubmVlZHNMaWdodHM9cnAoSykseHQubGlnaHRzU3RhdGVWZXJzaW9uPVVlLHh0Lm5lZWRzTGlnaHRzJiYoUHIuYW1iaWVudExpZ2h0Q29sb3IudmFsdWU9RnQuc3RhdGUuYW1iaWVudCxQci5saWdodFByb2JlLnZhbHVlPUZ0LnN0YXRlLnByb2JlLFByLmRpcmVjdGlvbmFsTGlnaHRzLnZhbHVlPUZ0LnN0YXRlLmRpcmVjdGlvbmFsLFByLmRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzLnZhbHVlPUZ0LnN0YXRlLmRpcmVjdGlvbmFsU2hhZG93LFByLnNwb3RMaWdodHMudmFsdWU9RnQuc3RhdGUuc3BvdCxQci5zcG90TGlnaHRTaGFkb3dzLnZhbHVlPUZ0LnN0YXRlLnNwb3RTaGFkb3csUHIucmVjdEFyZWFMaWdodHMudmFsdWU9RnQuc3RhdGUucmVjdEFyZWEsUHIubHRjXzEudmFsdWU9RnQuc3RhdGUucmVjdEFyZWFMVEMxLFByLmx0Y18yLnZhbHVlPUZ0LnN0YXRlLnJlY3RBcmVhTFRDMixQci5wb2ludExpZ2h0cy52YWx1ZT1GdC5zdGF0ZS5wb2ludCxQci5wb2ludExpZ2h0U2hhZG93cy52YWx1ZT1GdC5zdGF0ZS5wb2ludFNoYWRvdyxQci5oZW1pc3BoZXJlTGlnaHRzLnZhbHVlPUZ0LnN0YXRlLmhlbWksUHIuZGlyZWN0aW9uYWxTaGFkb3dNYXAudmFsdWU9RnQuc3RhdGUuZGlyZWN0aW9uYWxTaGFkb3dNYXAsUHIuZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXgudmFsdWU9RnQuc3RhdGUuZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXgsUHIuc3BvdFNoYWRvd01hcC52YWx1ZT1GdC5zdGF0ZS5zcG90U2hhZG93TWFwLFByLnNwb3RTaGFkb3dNYXRyaXgudmFsdWU9RnQuc3RhdGUuc3BvdFNoYWRvd01hdHJpeCxQci5wb2ludFNoYWRvd01hcC52YWx1ZT1GdC5zdGF0ZS5wb2ludFNoYWRvd01hcCxQci5wb2ludFNoYWRvd01hdHJpeC52YWx1ZT1GdC5zdGF0ZS5wb2ludFNoYWRvd01hdHJpeCk7bGV0IFhuPV9yLmdldFVuaWZvcm1zKCksbnA9QjAuc2VxV2l0aFZhbHVlKFhuLnNlcSxQcik7cmV0dXJuIHh0LmN1cnJlbnRQcm9ncmFtPV9yLHh0LnVuaWZvcm1zTGlzdD1ucCxfcn1mdW5jdGlvbiBKMChLLGd0KXtsZXQgRXQ9ZHQuZ2V0KEspO0V0Lm91dHB1dEVuY29kaW5nPWd0Lm91dHB1dEVuY29kaW5nLEV0Lmluc3RhbmNpbmc9Z3QuaW5zdGFuY2luZyxFdC5za2lubmluZz1ndC5za2lubmluZyxFdC5tb3JwaFRhcmdldHM9Z3QubW9ycGhUYXJnZXRzLEV0Lm1vcnBoTm9ybWFscz1ndC5tb3JwaE5vcm1hbHMsRXQubW9ycGhUYXJnZXRzQ291bnQ9Z3QubW9ycGhUYXJnZXRzQ291bnQsRXQubnVtQ2xpcHBpbmdQbGFuZXM9Z3QubnVtQ2xpcHBpbmdQbGFuZXMsRXQubnVtSW50ZXJzZWN0aW9uPWd0Lm51bUNsaXBJbnRlcnNlY3Rpb24sRXQudmVydGV4QWxwaGFzPWd0LnZlcnRleEFscGhhcyxFdC52ZXJ0ZXhUYW5nZW50cz1ndC52ZXJ0ZXhUYW5nZW50cyxFdC50b25lTWFwcGluZz1ndC50b25lTWFwcGluZ31mdW5jdGlvbiBjbihLLGd0LEV0LHh0LEZ0KXtndC5pc1NjZW5lIT09ITAmJihndD1idCkscS5yZXNldFRleHR1cmVVbml0cygpO2xldCBWZT1ndC5mb2csVWU9eHQuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbD9ndC5lbnZpcm9ubWVudDpudWxsLHRyPWI9PT1udWxsP2cub3V0cHV0RW5jb2Rpbmc6Yi5pc1hSUmVuZGVyVGFyZ2V0PT09ITA/Yi50ZXh0dXJlLmVuY29kaW5nOlFkLEtlPSh4dC5pc01lc2hTdGFuZGFyZE1hdGVyaWFsP2h0OnB0KS5nZXQoeHQuZW52TWFwfHxVZSksWHI9eHQudmVydGV4Q29sb3JzPT09ITAmJiEhRXQuYXR0cmlidXRlcy5jb2xvciYmRXQuYXR0cmlidXRlcy5jb2xvci5pdGVtU2l6ZT09PTQsX3I9ISF4dC5ub3JtYWxNYXAmJiEhRXQuYXR0cmlidXRlcy50YW5nZW50LFByPSEhRXQubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uLFhuPSEhRXQubW9ycGhBdHRyaWJ1dGVzLm5vcm1hbCxucD1FdC5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24/RXQubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uLmxlbmd0aDowLHVtPXh0LnRvbmVNYXBwZWQ/Zy50b25lTWFwcGluZzpLZCxtcj1kdC5nZXQoeHQpLEZsPWYuc3RhdGUubGlnaHRzO2lmKFo9PT0hMCYmKHJ0PT09ITB8fEshPT1DKSl7bGV0IHFzPUs9PT1DJiZ4dC5pZD09PVM7dHQuc2V0U3RhdGUoeHQsSyxxcyl9bGV0ICRuPSExO3h0LnZlcnNpb249PT1tci5fX3ZlcnNpb24/KG1yLm5lZWRzTGlnaHRzJiZtci5saWdodHNTdGF0ZVZlcnNpb24hPT1GbC5zdGF0ZS52ZXJzaW9ufHxtci5vdXRwdXRFbmNvZGluZyE9PXRyfHxGdC5pc0luc3RhbmNlZE1lc2gmJm1yLmluc3RhbmNpbmc9PT0hMXx8IUZ0LmlzSW5zdGFuY2VkTWVzaCYmbXIuaW5zdGFuY2luZz09PSEwfHxGdC5pc1NraW5uZWRNZXNoJiZtci5za2lubmluZz09PSExfHwhRnQuaXNTa2lubmVkTWVzaCYmbXIuc2tpbm5pbmc9PT0hMHx8bXIuZW52TWFwIT09S2V8fHh0LmZvZyYmbXIuZm9nIT09VmV8fG1yLm51bUNsaXBwaW5nUGxhbmVzIT09dm9pZCAwJiYobXIubnVtQ2xpcHBpbmdQbGFuZXMhPT10dC5udW1QbGFuZXN8fG1yLm51bUludGVyc2VjdGlvbiE9PXR0Lm51bUludGVyc2VjdGlvbil8fG1yLnZlcnRleEFscGhhcyE9PVhyfHxtci52ZXJ0ZXhUYW5nZW50cyE9PV9yfHxtci5tb3JwaFRhcmdldHMhPT1Qcnx8bXIubW9ycGhOb3JtYWxzIT09WG58fG1yLnRvbmVNYXBwaW5nIT09dW18fGN0LmlzV2ViR0wyPT09ITAmJm1yLm1vcnBoVGFyZ2V0c0NvdW50IT09bnApJiYoJG49ITApOigkbj0hMCxtci5fX3ZlcnNpb249eHQudmVyc2lvbik7bGV0IEJsPW1yLmN1cnJlbnRQcm9ncmFtOyRuPT09ITAmJihCbD1jbSh4dCxndCxGdCkpO2xldCB1eD0hMSxIbD0hMSxWbD0hMSxZaT1CbC5nZXRVbmlmb3JtcygpLGhtPW1yLnVuaWZvcm1zO2lmKFgudXNlUHJvZ3JhbShCbC5wcm9ncmFtKSYmKHV4PSEwLEhsPSEwLFZsPSEwKSx4dC5pZCE9PVMmJihTPXh0LmlkLEhsPSEwKSx1eHx8QyE9PUspe2lmKFlpLnNldFZhbHVlKGx0LCJwcm9qZWN0aW9uTWF0cml4IixLLnByb2plY3Rpb25NYXRyaXgpLGN0LmxvZ2FyaXRobWljRGVwdGhCdWZmZXImJllpLnNldFZhbHVlKGx0LCJsb2dEZXB0aEJ1ZkZDIiwyLyhNYXRoLmxvZyhLLmZhcisxKS9NYXRoLkxOMikpLEMhPT1LJiYoQz1LLEhsPSEwLFZsPSEwKSx4dC5pc1NoYWRlck1hdGVyaWFsfHx4dC5pc01lc2hQaG9uZ01hdGVyaWFsfHx4dC5pc01lc2hUb29uTWF0ZXJpYWx8fHh0LmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWx8fHh0LmVudk1hcCl7bGV0IHFzPVlpLm1hcC5jYW1lcmFQb3NpdGlvbjtxcyE9PXZvaWQgMCYmcXMuc2V0VmFsdWUobHQsU3Quc2V0RnJvbU1hdHJpeFBvc2l0aW9uKEsubWF0cml4V29ybGQpKX0oeHQuaXNNZXNoUGhvbmdNYXRlcmlhbHx8eHQuaXNNZXNoVG9vbk1hdGVyaWFsfHx4dC5pc01lc2hMYW1iZXJ0TWF0ZXJpYWx8fHh0LmlzTWVzaEJhc2ljTWF0ZXJpYWx8fHh0LmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWx8fHh0LmlzU2hhZGVyTWF0ZXJpYWwpJiZZaS5zZXRWYWx1ZShsdCwiaXNPcnRob2dyYXBoaWMiLEsuaXNPcnRob2dyYXBoaWNDYW1lcmE9PT0hMCksKHh0LmlzTWVzaFBob25nTWF0ZXJpYWx8fHh0LmlzTWVzaFRvb25NYXRlcmlhbHx8eHQuaXNNZXNoTGFtYmVydE1hdGVyaWFsfHx4dC5pc01lc2hCYXNpY01hdGVyaWFsfHx4dC5pc01lc2hTdGFuZGFyZE1hdGVyaWFsfHx4dC5pc1NoYWRlck1hdGVyaWFsfHx4dC5pc1NoYWRvd01hdGVyaWFsfHxGdC5pc1NraW5uZWRNZXNoKSYmWWkuc2V0VmFsdWUobHQsInZpZXdNYXRyaXgiLEsubWF0cml4V29ybGRJbnZlcnNlKX1pZihGdC5pc1NraW5uZWRNZXNoKXtZaS5zZXRPcHRpb25hbChsdCxGdCwiYmluZE1hdHJpeCIpLFlpLnNldE9wdGlvbmFsKGx0LEZ0LCJiaW5kTWF0cml4SW52ZXJzZSIpO2xldCBxcz1GdC5za2VsZXRvbjtxcyYmKGN0LmZsb2F0VmVydGV4VGV4dHVyZXM/KHFzLmJvbmVUZXh0dXJlPT09bnVsbCYmcXMuY29tcHV0ZUJvbmVUZXh0dXJlKCksWWkuc2V0VmFsdWUobHQsImJvbmVUZXh0dXJlIixxcy5ib25lVGV4dHVyZSxxKSxZaS5zZXRWYWx1ZShsdCwiYm9uZVRleHR1cmVTaXplIixxcy5ib25lVGV4dHVyZVNpemUpKTpZaS5zZXRPcHRpb25hbChsdCxxcywiYm9uZU1hdHJpY2VzIikpfXJldHVybiEhRXQmJihFdC5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24hPT12b2lkIDB8fEV0Lm1vcnBoQXR0cmlidXRlcy5ub3JtYWwhPT12b2lkIDApJiYkdC51cGRhdGUoRnQsRXQseHQsQmwpLChIbHx8bXIucmVjZWl2ZVNoYWRvdyE9PUZ0LnJlY2VpdmVTaGFkb3cpJiYobXIucmVjZWl2ZVNoYWRvdz1GdC5yZWNlaXZlU2hhZG93LFlpLnNldFZhbHVlKGx0LCJyZWNlaXZlU2hhZG93IixGdC5yZWNlaXZlU2hhZG93KSksSGwmJihZaS5zZXRWYWx1ZShsdCwidG9uZU1hcHBpbmdFeHBvc3VyZSIsZy50b25lTWFwcGluZ0V4cG9zdXJlKSxtci5uZWVkc0xpZ2h0cyYmY3goaG0sVmwpLFZlJiZ4dC5mb2cmJkxlLnJlZnJlc2hGb2dVbmlmb3JtcyhobSxWZSksTGUucmVmcmVzaE1hdGVyaWFsVW5pZm9ybXMoaG0seHQsSSxCLG90KSxCMC51cGxvYWQobHQsbXIudW5pZm9ybXNMaXN0LGhtLHEpKSx4dC5pc1NoYWRlck1hdGVyaWFsJiZ4dC51bmlmb3Jtc05lZWRVcGRhdGU9PT0hMCYmKEIwLnVwbG9hZChsdCxtci51bmlmb3Jtc0xpc3QsaG0scSkseHQudW5pZm9ybXNOZWVkVXBkYXRlPSExKSx4dC5pc1Nwcml0ZU1hdGVyaWFsJiZZaS5zZXRWYWx1ZShsdCwiY2VudGVyIixGdC5jZW50ZXIpLFlpLnNldFZhbHVlKGx0LCJtb2RlbFZpZXdNYXRyaXgiLEZ0Lm1vZGVsVmlld01hdHJpeCksWWkuc2V0VmFsdWUobHQsIm5vcm1hbE1hdHJpeCIsRnQubm9ybWFsTWF0cml4KSxZaS5zZXRWYWx1ZShsdCwibW9kZWxNYXRyaXgiLEZ0Lm1hdHJpeFdvcmxkKSxCbH1mdW5jdGlvbiBjeChLLGd0KXtLLmFtYmllbnRMaWdodENvbG9yLm5lZWRzVXBkYXRlPWd0LEsubGlnaHRQcm9iZS5uZWVkc1VwZGF0ZT1ndCxLLmRpcmVjdGlvbmFsTGlnaHRzLm5lZWRzVXBkYXRlPWd0LEsuZGlyZWN0aW9uYWxMaWdodFNoYWRvd3MubmVlZHNVcGRhdGU9Z3QsSy5wb2ludExpZ2h0cy5uZWVkc1VwZGF0ZT1ndCxLLnBvaW50TGlnaHRTaGFkb3dzLm5lZWRzVXBkYXRlPWd0LEsuc3BvdExpZ2h0cy5uZWVkc1VwZGF0ZT1ndCxLLnNwb3RMaWdodFNoYWRvd3MubmVlZHNVcGRhdGU9Z3QsSy5yZWN0QXJlYUxpZ2h0cy5uZWVkc1VwZGF0ZT1ndCxLLmhlbWlzcGhlcmVMaWdodHMubmVlZHNVcGRhdGU9Z3R9ZnVuY3Rpb24gcnAoSyl7cmV0dXJuIEsuaXNNZXNoTGFtYmVydE1hdGVyaWFsfHxLLmlzTWVzaFRvb25NYXRlcmlhbHx8Sy5pc01lc2hQaG9uZ01hdGVyaWFsfHxLLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWx8fEsuaXNTaGFkb3dNYXRlcmlhbHx8Sy5pc1NoYWRlck1hdGVyaWFsJiZLLmxpZ2h0cz09PSEwfXRoaXMuZ2V0QWN0aXZlQ3ViZUZhY2U9ZnVuY3Rpb24oKXtyZXR1cm4geX0sdGhpcy5nZXRBY3RpdmVNaXBtYXBMZXZlbD1mdW5jdGlvbigpe3JldHVybiB4fSx0aGlzLmdldFJlbmRlclRhcmdldD1mdW5jdGlvbigpe3JldHVybiBifSx0aGlzLnNldFJlbmRlclRhcmdldFRleHR1cmVzPWZ1bmN0aW9uKEssZ3QsRXQpe2R0LmdldChLLnRleHR1cmUpLl9fd2ViZ2xUZXh0dXJlPWd0LGR0LmdldChLLmRlcHRoVGV4dHVyZSkuX193ZWJnbFRleHR1cmU9RXQ7bGV0IHh0PWR0LmdldChLKTt4dC5fX2hhc0V4dGVybmFsVGV4dHVyZXM9ITAseHQuX19oYXNFeHRlcm5hbFRleHR1cmVzJiYoeHQuX19hdXRvQWxsb2NhdGVEZXB0aEJ1ZmZlcj1FdD09PXZvaWQgMCx4dC5fX2F1dG9BbGxvY2F0ZURlcHRoQnVmZmVyfHxLLnVzZVJlbmRlclRvVGV4dHVyZSYmKGNvbnNvbGUud2FybigicmVuZGVyLXRvLXRleHR1cmUgZXh0ZW5zaW9uIHdhcyBkaXNhYmxlZCBiZWNhdXNlIGFuIGV4dGVybmFsIHRleHR1cmUgd2FzIHByb3ZpZGVkIiksSy51c2VSZW5kZXJUb1RleHR1cmU9ITEsSy51c2VSZW5kZXJidWZmZXI9ITApKX0sdGhpcy5zZXRSZW5kZXJUYXJnZXRGcmFtZWJ1ZmZlcj1mdW5jdGlvbihLLGd0KXtsZXQgRXQ9ZHQuZ2V0KEspO0V0Ll9fd2ViZ2xGcmFtZWJ1ZmZlcj1ndCxFdC5fX3VzZURlZmF1bHRGcmFtZWJ1ZmZlcj1ndD09PXZvaWQgMH0sdGhpcy5zZXRSZW5kZXJUYXJnZXQ9ZnVuY3Rpb24oSyxndD0wLEV0PTApe2I9Syx5PWd0LHg9RXQ7bGV0IHh0PSEwO2lmKEspe2xldCBLZT1kdC5nZXQoSyk7S2UuX191c2VEZWZhdWx0RnJhbWVidWZmZXIhPT12b2lkIDA/KFguYmluZEZyYW1lYnVmZmVyKDM2MTYwLG51bGwpLHh0PSExKTpLZS5fX3dlYmdsRnJhbWVidWZmZXI9PT12b2lkIDA/cS5zZXR1cFJlbmRlclRhcmdldChLKTpLZS5fX2hhc0V4dGVybmFsVGV4dHVyZXMmJnEucmViaW5kVGV4dHVyZXMoSyxkdC5nZXQoSy50ZXh0dXJlKS5fX3dlYmdsVGV4dHVyZSxkdC5nZXQoSy5kZXB0aFRleHR1cmUpLl9fd2ViZ2xUZXh0dXJlKX1sZXQgRnQ9bnVsbCxWZT0hMSxVZT0hMTtpZihLKXtsZXQgS2U9Sy50ZXh0dXJlOyhLZS5pc0RhdGFUZXh0dXJlM0R8fEtlLmlzRGF0YVRleHR1cmUyREFycmF5KSYmKFVlPSEwKTtsZXQgWHI9ZHQuZ2V0KEspLl9fd2ViZ2xGcmFtZWJ1ZmZlcjtLLmlzV2ViR0xDdWJlUmVuZGVyVGFyZ2V0PyhGdD1YcltndF0sVmU9ITApOksudXNlUmVuZGVyYnVmZmVyP0Z0PWR0LmdldChLKS5fX3dlYmdsTXVsdGlzYW1wbGVkRnJhbWVidWZmZXI6RnQ9WHIsUC5jb3B5KEsudmlld3BvcnQpLGsuY29weShLLnNjaXNzb3IpLE89Sy5zY2lzc29yVGVzdH1lbHNlIFAuY29weShGKS5tdWx0aXBseVNjYWxhcihJKS5mbG9vcigpLGsuY29weSh6KS5tdWx0aXBseVNjYWxhcihJKS5mbG9vcigpLE89VTtpZihYLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxGdCkmJmN0LmRyYXdCdWZmZXJzJiZ4dCYmWC5kcmF3QnVmZmVycyhLLEZ0KSxYLnZpZXdwb3J0KFApLFguc2Npc3NvcihrKSxYLnNldFNjaXNzb3JUZXN0KE8pLFZlKXtsZXQgS2U9ZHQuZ2V0KEsudGV4dHVyZSk7bHQuZnJhbWVidWZmZXJUZXh0dXJlMkQoMzYxNjAsMzYwNjQsMzQwNjkrZ3QsS2UuX193ZWJnbFRleHR1cmUsRXQpfWVsc2UgaWYoVWUpe2xldCBLZT1kdC5nZXQoSy50ZXh0dXJlKSxYcj1ndHx8MDtsdC5mcmFtZWJ1ZmZlclRleHR1cmVMYXllcigzNjE2MCwzNjA2NCxLZS5fX3dlYmdsVGV4dHVyZSxFdHx8MCxYcil9Uz0tMX0sdGhpcy5yZWFkUmVuZGVyVGFyZ2V0UGl4ZWxzPWZ1bmN0aW9uKEssZ3QsRXQseHQsRnQsVmUsVWUpe2lmKCEoSyYmSy5pc1dlYkdMUmVuZGVyVGFyZ2V0KSl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlci5yZWFkUmVuZGVyVGFyZ2V0UGl4ZWxzOiByZW5kZXJUYXJnZXQgaXMgbm90IFRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0LiIpO3JldHVybn1sZXQgdHI9ZHQuZ2V0KEspLl9fd2ViZ2xGcmFtZWJ1ZmZlcjtpZihLLmlzV2ViR0xDdWJlUmVuZGVyVGFyZ2V0JiZVZSE9PXZvaWQgMCYmKHRyPXRyW1VlXSksdHIpe1guYmluZEZyYW1lYnVmZmVyKDM2MTYwLHRyKTt0cnl7bGV0IEtlPUsudGV4dHVyZSxYcj1LZS5mb3JtYXQsX3I9S2UudHlwZTtpZihYciE9PVFvJiZiZS5jb252ZXJ0KFhyKSE9PWx0LmdldFBhcmFtZXRlcigzNTczOSkpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMUmVuZGVyZXIucmVhZFJlbmRlclRhcmdldFBpeGVsczogcmVuZGVyVGFyZ2V0IGlzIG5vdCBpbiBSR0JBIG9yIGltcGxlbWVudGF0aW9uIGRlZmluZWQgZm9ybWF0LiIpO3JldHVybn1sZXQgUHI9X3I9PT1DdiYmKF90LmhhcygiRVhUX2NvbG9yX2J1ZmZlcl9oYWxmX2Zsb2F0Iil8fGN0LmlzV2ViR0wyJiZfdC5oYXMoIkVYVF9jb2xvcl9idWZmZXJfZmxvYXQiKSk7aWYoX3IhPT1aZCYmYmUuY29udmVydChfcikhPT1sdC5nZXRQYXJhbWV0ZXIoMzU3MzgpJiYhKF9yPT09amQmJihjdC5pc1dlYkdMMnx8X3QuaGFzKCJPRVNfdGV4dHVyZV9mbG9hdCIpfHxfdC5oYXMoIldFQkdMX2NvbG9yX2J1ZmZlcl9mbG9hdCIpKSkmJiFQcil7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlci5yZWFkUmVuZGVyVGFyZ2V0UGl4ZWxzOiByZW5kZXJUYXJnZXQgaXMgbm90IGluIFVuc2lnbmVkQnl0ZVR5cGUgb3IgaW1wbGVtZW50YXRpb24gZGVmaW5lZCB0eXBlLiIpO3JldHVybn1sdC5jaGVja0ZyYW1lYnVmZmVyU3RhdHVzKDM2MTYwKT09PTM2MDUzP2d0Pj0wJiZndDw9Sy53aWR0aC14dCYmRXQ+PTAmJkV0PD1LLmhlaWdodC1GdCYmbHQucmVhZFBpeGVscyhndCxFdCx4dCxGdCxiZS5jb252ZXJ0KFhyKSxiZS5jb252ZXJ0KF9yKSxWZSk6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlci5yZWFkUmVuZGVyVGFyZ2V0UGl4ZWxzOiByZWFkUGl4ZWxzIGZyb20gcmVuZGVyVGFyZ2V0IGZhaWxlZC4gRnJhbWVidWZmZXIgbm90IGNvbXBsZXRlLiIpfWZpbmFsbHl7bGV0IEtlPWIhPT1udWxsP2R0LmdldChiKS5fX3dlYmdsRnJhbWVidWZmZXI6bnVsbDtYLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxLZSl9fX0sdGhpcy5jb3B5RnJhbWVidWZmZXJUb1RleHR1cmU9ZnVuY3Rpb24oSyxndCxFdD0wKXtpZihndC5pc0ZyYW1lYnVmZmVyVGV4dHVyZSE9PSEwKXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBjb3B5RnJhbWVidWZmZXJUb1RleHR1cmUoKSBjYW4gb25seSBiZSB1c2VkIHdpdGggRnJhbWVidWZmZXJUZXh0dXJlLiIpO3JldHVybn1sZXQgeHQ9TWF0aC5wb3coMiwtRXQpLEZ0PU1hdGguZmxvb3IoZ3QuaW1hZ2Uud2lkdGgqeHQpLFZlPU1hdGguZmxvb3IoZ3QuaW1hZ2UuaGVpZ2h0Knh0KTtxLnNldFRleHR1cmUyRChndCwwKSxsdC5jb3B5VGV4U3ViSW1hZ2UyRCgzNTUzLEV0LDAsMCxLLngsSy55LEZ0LFZlKSxYLnVuYmluZFRleHR1cmUoKX0sdGhpcy5jb3B5VGV4dHVyZVRvVGV4dHVyZT1mdW5jdGlvbihLLGd0LEV0LHh0PTApe2xldCBGdD1ndC5pbWFnZS53aWR0aCxWZT1ndC5pbWFnZS5oZWlnaHQsVWU9YmUuY29udmVydChFdC5mb3JtYXQpLHRyPWJlLmNvbnZlcnQoRXQudHlwZSk7cS5zZXRUZXh0dXJlMkQoRXQsMCksbHQucGl4ZWxTdG9yZWkoMzc0NDAsRXQuZmxpcFkpLGx0LnBpeGVsU3RvcmVpKDM3NDQxLEV0LnByZW11bHRpcGx5QWxwaGEpLGx0LnBpeGVsU3RvcmVpKDMzMTcsRXQudW5wYWNrQWxpZ25tZW50KSxndC5pc0RhdGFUZXh0dXJlP2x0LnRleFN1YkltYWdlMkQoMzU1Myx4dCxLLngsSy55LEZ0LFZlLFVlLHRyLGd0LmltYWdlLmRhdGEpOmd0LmlzQ29tcHJlc3NlZFRleHR1cmU/bHQuY29tcHJlc3NlZFRleFN1YkltYWdlMkQoMzU1Myx4dCxLLngsSy55LGd0Lm1pcG1hcHNbMF0ud2lkdGgsZ3QubWlwbWFwc1swXS5oZWlnaHQsVWUsZ3QubWlwbWFwc1swXS5kYXRhKTpsdC50ZXhTdWJJbWFnZTJEKDM1NTMseHQsSy54LEsueSxVZSx0cixndC5pbWFnZSkseHQ9PT0wJiZFdC5nZW5lcmF0ZU1pcG1hcHMmJmx0LmdlbmVyYXRlTWlwbWFwKDM1NTMpLFgudW5iaW5kVGV4dHVyZSgpfSx0aGlzLmNvcHlUZXh0dXJlVG9UZXh0dXJlM0Q9ZnVuY3Rpb24oSyxndCxFdCx4dCxGdD0wKXtpZihnLmlzV2ViR0wxUmVuZGVyZXIpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlci5jb3B5VGV4dHVyZVRvVGV4dHVyZTNEOiBjYW4gb25seSBiZSB1c2VkIHdpdGggV2ViR0wyLiIpO3JldHVybn1sZXQgVmU9Sy5tYXgueC1LLm1pbi54KzEsVWU9Sy5tYXgueS1LLm1pbi55KzEsdHI9Sy5tYXguei1LLm1pbi56KzEsS2U9YmUuY29udmVydCh4dC5mb3JtYXQpLFhyPWJlLmNvbnZlcnQoeHQudHlwZSksX3I7aWYoeHQuaXNEYXRhVGV4dHVyZTNEKXEuc2V0VGV4dHVyZTNEKHh0LDApLF9yPTMyODc5O2Vsc2UgaWYoeHQuaXNEYXRhVGV4dHVyZTJEQXJyYXkpcS5zZXRUZXh0dXJlMkRBcnJheSh4dCwwKSxfcj0zNTg2NjtlbHNle2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlci5jb3B5VGV4dHVyZVRvVGV4dHVyZTNEOiBvbmx5IHN1cHBvcnRzIFRIUkVFLkRhdGFUZXh0dXJlM0QgYW5kIFRIUkVFLkRhdGFUZXh0dXJlMkRBcnJheS4iKTtyZXR1cm59bHQucGl4ZWxTdG9yZWkoMzc0NDAseHQuZmxpcFkpLGx0LnBpeGVsU3RvcmVpKDM3NDQxLHh0LnByZW11bHRpcGx5QWxwaGEpLGx0LnBpeGVsU3RvcmVpKDMzMTcseHQudW5wYWNrQWxpZ25tZW50KTtsZXQgUHI9bHQuZ2V0UGFyYW1ldGVyKDMzMTQpLFhuPWx0LmdldFBhcmFtZXRlcigzMjg3OCksbnA9bHQuZ2V0UGFyYW1ldGVyKDMzMTYpLHVtPWx0LmdldFBhcmFtZXRlcigzMzE1KSxtcj1sdC5nZXRQYXJhbWV0ZXIoMzI4NzcpLEZsPUV0LmlzQ29tcHJlc3NlZFRleHR1cmU/RXQubWlwbWFwc1swXTpFdC5pbWFnZTtsdC5waXhlbFN0b3JlaSgzMzE0LEZsLndpZHRoKSxsdC5waXhlbFN0b3JlaSgzMjg3OCxGbC5oZWlnaHQpLGx0LnBpeGVsU3RvcmVpKDMzMTYsSy5taW4ueCksbHQucGl4ZWxTdG9yZWkoMzMxNSxLLm1pbi55KSxsdC5waXhlbFN0b3JlaSgzMjg3NyxLLm1pbi56KSxFdC5pc0RhdGFUZXh0dXJlfHxFdC5pc0RhdGFUZXh0dXJlM0Q/bHQudGV4U3ViSW1hZ2UzRChfcixGdCxndC54LGd0LnksZ3QueixWZSxVZSx0cixLZSxYcixGbC5kYXRhKTpFdC5pc0NvbXByZXNzZWRUZXh0dXJlPyhjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXIuY29weVRleHR1cmVUb1RleHR1cmUzRDogdW50ZXN0ZWQgc3VwcG9ydCBmb3IgY29tcHJlc3NlZCBzcmNUZXh0dXJlLiIpLGx0LmNvbXByZXNzZWRUZXhTdWJJbWFnZTNEKF9yLEZ0LGd0LngsZ3QueSxndC56LFZlLFVlLHRyLEtlLEZsLmRhdGEpKTpsdC50ZXhTdWJJbWFnZTNEKF9yLEZ0LGd0LngsZ3QueSxndC56LFZlLFVlLHRyLEtlLFhyLEZsKSxsdC5waXhlbFN0b3JlaSgzMzE0LFByKSxsdC5waXhlbFN0b3JlaSgzMjg3OCxYbiksbHQucGl4ZWxTdG9yZWkoMzMxNixucCksbHQucGl4ZWxTdG9yZWkoMzMxNSx1bSksbHQucGl4ZWxTdG9yZWkoMzI4NzcsbXIpLEZ0PT09MCYmeHQuZ2VuZXJhdGVNaXBtYXBzJiZsdC5nZW5lcmF0ZU1pcG1hcChfciksWC51bmJpbmRUZXh0dXJlKCl9LHRoaXMuaW5pdFRleHR1cmU9ZnVuY3Rpb24oSyl7cS5zZXRUZXh0dXJlMkQoSywwKSxYLnVuYmluZFRleHR1cmUoKX0sdGhpcy5yZXNldFN0YXRlPWZ1bmN0aW9uKCl7eT0wLHg9MCxiPW51bGwsWC5yZXNldCgpLG50LnJlc2V0KCl9LHR5cGVvZiBfX1RIUkVFX0RFVlRPT0xTX18hPSJ1bmRlZmluZWQiJiZfX1RIUkVFX0RFVlRPT0xTX18uZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoIm9ic2VydmUiLHtkZXRhaWw6dGhpc30pKX1ybi5wcm90b3R5cGUuaXNXZWJHTFJlbmRlcmVyPSEwO3ZhciBtVT1jbGFzcyBleHRlbmRzIHJue307bVUucHJvdG90eXBlLmlzV2ViR0wxUmVuZGVyZXI9ITA7dmFyIE92PWNsYXNze2NvbnN0cnVjdG9yKHQscj0yNWUtNSl7dGhpcy5uYW1lPSIiLHRoaXMuY29sb3I9bmV3IG5lKHQpLHRoaXMuZGVuc2l0eT1yfWNsb25lKCl7cmV0dXJuIG5ldyBPdih0aGlzLmNvbG9yLHRoaXMuZGVuc2l0eSl9dG9KU09OKCl7cmV0dXJue3R5cGU6IkZvZ0V4cDIiLGNvbG9yOnRoaXMuY29sb3IuZ2V0SGV4KCksZGVuc2l0eTp0aGlzLmRlbnNpdHl9fX07T3YucHJvdG90eXBlLmlzRm9nRXhwMj0hMDt2YXIgenY9Y2xhc3N7Y29uc3RydWN0b3IodCxyPTEsbj0xZTMpe3RoaXMubmFtZT0iIix0aGlzLmNvbG9yPW5ldyBuZSh0KSx0aGlzLm5lYXI9cix0aGlzLmZhcj1ufWNsb25lKCl7cmV0dXJuIG5ldyB6dih0aGlzLmNvbG9yLHRoaXMubmVhcix0aGlzLmZhcil9dG9KU09OKCl7cmV0dXJue3R5cGU6IkZvZyIsY29sb3I6dGhpcy5jb2xvci5nZXRIZXgoKSxuZWFyOnRoaXMubmVhcixmYXI6dGhpcy5mYXJ9fX07enYucHJvdG90eXBlLmlzRm9nPSEwO3ZhciBxMD1jbGFzcyBleHRlbmRzIG9ye2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLnR5cGU9IlNjZW5lIix0aGlzLmJhY2tncm91bmQ9bnVsbCx0aGlzLmVudmlyb25tZW50PW51bGwsdGhpcy5mb2c9bnVsbCx0aGlzLm92ZXJyaWRlTWF0ZXJpYWw9bnVsbCx0aGlzLmF1dG9VcGRhdGU9ITAsdHlwZW9mIF9fVEhSRUVfREVWVE9PTFNfXyE9InVuZGVmaW5lZCImJl9fVEhSRUVfREVWVE9PTFNfXy5kaXNwYXRjaEV2ZW50KG5ldyBDdXN0b21FdmVudCgib2JzZXJ2ZSIse2RldGFpbDp0aGlzfSkpfWNvcHkodCxyKXtyZXR1cm4gc3VwZXIuY29weSh0LHIpLHQuYmFja2dyb3VuZCE9PW51bGwmJih0aGlzLmJhY2tncm91bmQ9dC5iYWNrZ3JvdW5kLmNsb25lKCkpLHQuZW52aXJvbm1lbnQhPT1udWxsJiYodGhpcy5lbnZpcm9ubWVudD10LmVudmlyb25tZW50LmNsb25lKCkpLHQuZm9nIT09bnVsbCYmKHRoaXMuZm9nPXQuZm9nLmNsb25lKCkpLHQub3ZlcnJpZGVNYXRlcmlhbCE9PW51bGwmJih0aGlzLm92ZXJyaWRlTWF0ZXJpYWw9dC5vdmVycmlkZU1hdGVyaWFsLmNsb25lKCkpLHRoaXMuYXV0b1VwZGF0ZT10LmF1dG9VcGRhdGUsdGhpcy5tYXRyaXhBdXRvVXBkYXRlPXQubWF0cml4QXV0b1VwZGF0ZSx0aGlzfXRvSlNPTih0KXtsZXQgcj1zdXBlci50b0pTT04odCk7cmV0dXJuIHRoaXMuZm9nIT09bnVsbCYmKHIub2JqZWN0LmZvZz10aGlzLmZvZy50b0pTT04oKSkscn19O3EwLnByb3RvdHlwZS5pc1NjZW5lPSEwO3ZhciBlbT1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIpe3RoaXMuYXJyYXk9dCx0aGlzLnN0cmlkZT1yLHRoaXMuY291bnQ9dCE9PXZvaWQgMD90Lmxlbmd0aC9yOjAsdGhpcy51c2FnZT1XMyx0aGlzLnVwZGF0ZVJhbmdlPXtvZmZzZXQ6MCxjb3VudDotMX0sdGhpcy52ZXJzaW9uPTAsdGhpcy51dWlkPU5sKCl9b25VcGxvYWRDYWxsYmFjaygpe31zZXQgbmVlZHNVcGRhdGUodCl7dD09PSEwJiZ0aGlzLnZlcnNpb24rK31zZXRVc2FnZSh0KXtyZXR1cm4gdGhpcy51c2FnZT10LHRoaXN9Y29weSh0KXtyZXR1cm4gdGhpcy5hcnJheT1uZXcgdC5hcnJheS5jb25zdHJ1Y3Rvcih0LmFycmF5KSx0aGlzLmNvdW50PXQuY291bnQsdGhpcy5zdHJpZGU9dC5zdHJpZGUsdGhpcy51c2FnZT10LnVzYWdlLHRoaXN9Y29weUF0KHQscixuKXt0Kj10aGlzLnN0cmlkZSxuKj1yLnN0cmlkZTtmb3IobGV0IGk9MCxvPXRoaXMuc3RyaWRlO2k8bztpKyspdGhpcy5hcnJheVt0K2ldPXIuYXJyYXlbbitpXTtyZXR1cm4gdGhpc31zZXQodCxyPTApe3JldHVybiB0aGlzLmFycmF5LnNldCh0LHIpLHRoaXN9Y2xvbmUodCl7dC5hcnJheUJ1ZmZlcnM9PT12b2lkIDAmJih0LmFycmF5QnVmZmVycz17fSksdGhpcy5hcnJheS5idWZmZXIuX3V1aWQ9PT12b2lkIDAmJih0aGlzLmFycmF5LmJ1ZmZlci5fdXVpZD1ObCgpKSx0LmFycmF5QnVmZmVyc1t0aGlzLmFycmF5LmJ1ZmZlci5fdXVpZF09PT12b2lkIDAmJih0LmFycmF5QnVmZmVyc1t0aGlzLmFycmF5LmJ1ZmZlci5fdXVpZF09dGhpcy5hcnJheS5zbGljZSgwKS5idWZmZXIpO2xldCByPW5ldyB0aGlzLmFycmF5LmNvbnN0cnVjdG9yKHQuYXJyYXlCdWZmZXJzW3RoaXMuYXJyYXkuYnVmZmVyLl91dWlkXSksbj1uZXcgdGhpcy5jb25zdHJ1Y3RvcihyLHRoaXMuc3RyaWRlKTtyZXR1cm4gbi5zZXRVc2FnZSh0aGlzLnVzYWdlKSxufW9uVXBsb2FkKHQpe3JldHVybiB0aGlzLm9uVXBsb2FkQ2FsbGJhY2s9dCx0aGlzfXRvSlNPTih0KXtyZXR1cm4gdC5hcnJheUJ1ZmZlcnM9PT12b2lkIDAmJih0LmFycmF5QnVmZmVycz17fSksdGhpcy5hcnJheS5idWZmZXIuX3V1aWQ9PT12b2lkIDAmJih0aGlzLmFycmF5LmJ1ZmZlci5fdXVpZD1ObCgpKSx0LmFycmF5QnVmZmVyc1t0aGlzLmFycmF5LmJ1ZmZlci5fdXVpZF09PT12b2lkIDAmJih0LmFycmF5QnVmZmVyc1t0aGlzLmFycmF5LmJ1ZmZlci5fdXVpZF09QXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwobmV3IFVpbnQzMkFycmF5KHRoaXMuYXJyYXkuYnVmZmVyKSkpLHt1dWlkOnRoaXMudXVpZCxidWZmZXI6dGhpcy5hcnJheS5idWZmZXIuX3V1aWQsdHlwZTp0aGlzLmFycmF5LmNvbnN0cnVjdG9yLm5hbWUsc3RyaWRlOnRoaXMuc3RyaWRlfX19O2VtLnByb3RvdHlwZS5pc0ludGVybGVhdmVkQnVmZmVyPSEwO3ZhciBBaT1uZXcgaix0cD1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIsbixpPSExKXt0aGlzLm5hbWU9IiIsdGhpcy5kYXRhPXQsdGhpcy5pdGVtU2l6ZT1yLHRoaXMub2Zmc2V0PW4sdGhpcy5ub3JtYWxpemVkPWk9PT0hMH1nZXQgY291bnQoKXtyZXR1cm4gdGhpcy5kYXRhLmNvdW50fWdldCBhcnJheSgpe3JldHVybiB0aGlzLmRhdGEuYXJyYXl9c2V0IG5lZWRzVXBkYXRlKHQpe3RoaXMuZGF0YS5uZWVkc1VwZGF0ZT10fWFwcGx5TWF0cml4NCh0KXtmb3IobGV0IHI9MCxuPXRoaXMuZGF0YS5jb3VudDtyPG47cisrKUFpLng9dGhpcy5nZXRYKHIpLEFpLnk9dGhpcy5nZXRZKHIpLEFpLno9dGhpcy5nZXRaKHIpLEFpLmFwcGx5TWF0cml4NCh0KSx0aGlzLnNldFhZWihyLEFpLngsQWkueSxBaS56KTtyZXR1cm4gdGhpc31hcHBseU5vcm1hbE1hdHJpeCh0KXtmb3IobGV0IHI9MCxuPXRoaXMuY291bnQ7cjxuO3IrKylBaS54PXRoaXMuZ2V0WChyKSxBaS55PXRoaXMuZ2V0WShyKSxBaS56PXRoaXMuZ2V0WihyKSxBaS5hcHBseU5vcm1hbE1hdHJpeCh0KSx0aGlzLnNldFhZWihyLEFpLngsQWkueSxBaS56KTtyZXR1cm4gdGhpc310cmFuc2Zvcm1EaXJlY3Rpb24odCl7Zm9yKGxldCByPTAsbj10aGlzLmNvdW50O3I8bjtyKyspQWkueD10aGlzLmdldFgociksQWkueT10aGlzLmdldFkociksQWkuej10aGlzLmdldFoociksQWkudHJhbnNmb3JtRGlyZWN0aW9uKHQpLHRoaXMuc2V0WFlaKHIsQWkueCxBaS55LEFpLnopO3JldHVybiB0aGlzfXNldFgodCxyKXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5W3QqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldF09cix0aGlzfXNldFkodCxyKXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5W3QqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldCsxXT1yLHRoaXN9c2V0Wih0LHIpe3JldHVybiB0aGlzLmRhdGEuYXJyYXlbdCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0KzJdPXIsdGhpc31zZXRXKHQscil7cmV0dXJuIHRoaXMuZGF0YS5hcnJheVt0KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQrM109cix0aGlzfWdldFgodCl7cmV0dXJuIHRoaXMuZGF0YS5hcnJheVt0KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXRdfWdldFkodCl7cmV0dXJuIHRoaXMuZGF0YS5hcnJheVt0KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQrMV19Z2V0Wih0KXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5W3QqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldCsyXX1nZXRXKHQpe3JldHVybiB0aGlzLmRhdGEuYXJyYXlbdCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0KzNdfXNldFhZKHQscixuKXtyZXR1cm4gdD10KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQsdGhpcy5kYXRhLmFycmF5W3QrMF09cix0aGlzLmRhdGEuYXJyYXlbdCsxXT1uLHRoaXN9c2V0WFlaKHQscixuLGkpe3JldHVybiB0PXQqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldCx0aGlzLmRhdGEuYXJyYXlbdCswXT1yLHRoaXMuZGF0YS5hcnJheVt0KzFdPW4sdGhpcy5kYXRhLmFycmF5W3QrMl09aSx0aGlzfXNldFhZWlcodCxyLG4saSxvKXtyZXR1cm4gdD10KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQsdGhpcy5kYXRhLmFycmF5W3QrMF09cix0aGlzLmRhdGEuYXJyYXlbdCsxXT1uLHRoaXMuZGF0YS5hcnJheVt0KzJdPWksdGhpcy5kYXRhLmFycmF5W3QrM109byx0aGlzfWNsb25lKHQpe2lmKHQ9PT12b2lkIDApe2NvbnNvbGUubG9nKCJUSFJFRS5JbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZS5jbG9uZSgpOiBDbG9uaW5nIGFuIGludGVybGF2ZWQgYnVmZmVyIGF0dHJpYnV0ZSB3aWxsIGRlaW50ZXJsZWF2ZSBidWZmZXIgZGF0YS4iKTtsZXQgcj1bXTtmb3IobGV0IG49MDtuPHRoaXMuY291bnQ7bisrKXtsZXQgaT1uKnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQ7Zm9yKGxldCBvPTA7bzx0aGlzLml0ZW1TaXplO28rKylyLnB1c2godGhpcy5kYXRhLmFycmF5W2krb10pfXJldHVybiBuZXcgSmUobmV3IHRoaXMuYXJyYXkuY29uc3RydWN0b3IociksdGhpcy5pdGVtU2l6ZSx0aGlzLm5vcm1hbGl6ZWQpfWVsc2UgcmV0dXJuIHQuaW50ZXJsZWF2ZWRCdWZmZXJzPT09dm9pZCAwJiYodC5pbnRlcmxlYXZlZEJ1ZmZlcnM9e30pLHQuaW50ZXJsZWF2ZWRCdWZmZXJzW3RoaXMuZGF0YS51dWlkXT09PXZvaWQgMCYmKHQuaW50ZXJsZWF2ZWRCdWZmZXJzW3RoaXMuZGF0YS51dWlkXT10aGlzLmRhdGEuY2xvbmUodCkpLG5ldyB0cCh0LmludGVybGVhdmVkQnVmZmVyc1t0aGlzLmRhdGEudXVpZF0sdGhpcy5pdGVtU2l6ZSx0aGlzLm9mZnNldCx0aGlzLm5vcm1hbGl6ZWQpfXRvSlNPTih0KXtpZih0PT09dm9pZCAwKXtjb25zb2xlLmxvZygiVEhSRUUuSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGUudG9KU09OKCk6IFNlcmlhbGl6aW5nIGFuIGludGVybGF2ZWQgYnVmZmVyIGF0dHJpYnV0ZSB3aWxsIGRlaW50ZXJsZWF2ZSBidWZmZXIgZGF0YS4iKTtsZXQgcj1bXTtmb3IobGV0IG49MDtuPHRoaXMuY291bnQ7bisrKXtsZXQgaT1uKnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQ7Zm9yKGxldCBvPTA7bzx0aGlzLml0ZW1TaXplO28rKylyLnB1c2godGhpcy5kYXRhLmFycmF5W2krb10pfXJldHVybntpdGVtU2l6ZTp0aGlzLml0ZW1TaXplLHR5cGU6dGhpcy5hcnJheS5jb25zdHJ1Y3Rvci5uYW1lLGFycmF5OnIsbm9ybWFsaXplZDp0aGlzLm5vcm1hbGl6ZWR9fWVsc2UgcmV0dXJuIHQuaW50ZXJsZWF2ZWRCdWZmZXJzPT09dm9pZCAwJiYodC5pbnRlcmxlYXZlZEJ1ZmZlcnM9e30pLHQuaW50ZXJsZWF2ZWRCdWZmZXJzW3RoaXMuZGF0YS51dWlkXT09PXZvaWQgMCYmKHQuaW50ZXJsZWF2ZWRCdWZmZXJzW3RoaXMuZGF0YS51dWlkXT10aGlzLmRhdGEudG9KU09OKHQpKSx7aXNJbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZTohMCxpdGVtU2l6ZTp0aGlzLml0ZW1TaXplLGRhdGE6dGhpcy5kYXRhLnV1aWQsb2Zmc2V0OnRoaXMub2Zmc2V0LG5vcm1hbGl6ZWQ6dGhpcy5ub3JtYWxpemVkfX19O3RwLnByb3RvdHlwZS5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlPSEwO3ZhciBpTT1jbGFzcyBleHRlbmRzIHFpe2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJTcHJpdGVNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgbmUoMTY3NzcyMTUpLHRoaXMubWFwPW51bGwsdGhpcy5hbHBoYU1hcD1udWxsLHRoaXMucm90YXRpb249MCx0aGlzLnNpemVBdHRlbnVhdGlvbj0hMCx0aGlzLnRyYW5zcGFyZW50PSEwLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMubWFwPXQubWFwLHRoaXMuYWxwaGFNYXA9dC5hbHBoYU1hcCx0aGlzLnJvdGF0aW9uPXQucm90YXRpb24sdGhpcy5zaXplQXR0ZW51YXRpb249dC5zaXplQXR0ZW51YXRpb24sdGhpc319O2lNLnByb3RvdHlwZS5pc1Nwcml0ZU1hdGVyaWFsPSEwO3ZhciBOMyxOUD1uZXcgaixEMz1uZXcgaixPMz1uZXcgaix6Mz1uZXcgTHQsRFA9bmV3IEx0LCRmZT1uZXcgTWUsSVY9bmV3IGosT1A9bmV3IGosTFY9bmV3IGosWXVlPW5ldyBMdCxtdXQ9bmV3IEx0LGp1ZT1uZXcgTHQsb009Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcih0KXtpZihzdXBlcigpLHRoaXMudHlwZT0iU3ByaXRlIixOMz09PXZvaWQgMCl7TjM9bmV3IFBlO2xldCByPW5ldyBGbG9hdDMyQXJyYXkoWy0uNSwtLjUsMCwwLDAsLjUsLS41LDAsMSwwLC41LC41LDAsMSwxLC0uNSwuNSwwLDAsMV0pLG49bmV3IGVtKHIsNSk7TjMuc2V0SW5kZXgoWzAsMSwyLDAsMiwzXSksTjMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IHRwKG4sMywwLCExKSksTjMuc2V0QXR0cmlidXRlKCJ1diIsbmV3IHRwKG4sMiwzLCExKSl9dGhpcy5nZW9tZXRyeT1OMyx0aGlzLm1hdGVyaWFsPXQhPT12b2lkIDA/dDpuZXcgaU0sdGhpcy5jZW50ZXI9bmV3IEx0KC41LC41KX1yYXljYXN0KHQscil7dC5jYW1lcmE9PT1udWxsJiZjb25zb2xlLmVycm9yKCdUSFJFRS5TcHJpdGU6ICJSYXljYXN0ZXIuY2FtZXJhIiBuZWVkcyB0byBiZSBzZXQgaW4gb3JkZXIgdG8gcmF5Y2FzdCBhZ2FpbnN0IHNwcml0ZXMuJyksRDMuc2V0RnJvbU1hdHJpeFNjYWxlKHRoaXMubWF0cml4V29ybGQpLCRmZS5jb3B5KHQuY2FtZXJhLm1hdHJpeFdvcmxkKSx0aGlzLm1vZGVsVmlld01hdHJpeC5tdWx0aXBseU1hdHJpY2VzKHQuY2FtZXJhLm1hdHJpeFdvcmxkSW52ZXJzZSx0aGlzLm1hdHJpeFdvcmxkKSxPMy5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5tb2RlbFZpZXdNYXRyaXgpLHQuY2FtZXJhLmlzUGVyc3BlY3RpdmVDYW1lcmEmJnRoaXMubWF0ZXJpYWwuc2l6ZUF0dGVudWF0aW9uPT09ITEmJkQzLm11bHRpcGx5U2NhbGFyKC1PMy56KTtsZXQgbj10aGlzLm1hdGVyaWFsLnJvdGF0aW9uLGksbztuIT09MCYmKG89TWF0aC5jb3MobiksaT1NYXRoLnNpbihuKSk7bGV0IGE9dGhpcy5jZW50ZXI7a1YoSVYuc2V0KC0uNSwtLjUsMCksTzMsYSxEMyxpLG8pLGtWKE9QLnNldCguNSwtLjUsMCksTzMsYSxEMyxpLG8pLGtWKExWLnNldCguNSwuNSwwKSxPMyxhLEQzLGksbyksWXVlLnNldCgwLDApLG11dC5zZXQoMSwwKSxqdWUuc2V0KDEsMSk7bGV0IHM9dC5yYXkuaW50ZXJzZWN0VHJpYW5nbGUoSVYsT1AsTFYsITEsTlApO2lmKHM9PT1udWxsJiYoa1YoT1Auc2V0KC0uNSwuNSwwKSxPMyxhLEQzLGksbyksbXV0LnNldCgwLDEpLHM9dC5yYXkuaW50ZXJzZWN0VHJpYW5nbGUoSVYsTFYsT1AsITEsTlApLHM9PT1udWxsKSlyZXR1cm47bGV0IGw9dC5yYXkub3JpZ2luLmRpc3RhbmNlVG8oTlApO2w8dC5uZWFyfHxsPnQuZmFyfHxyLnB1c2goe2Rpc3RhbmNlOmwscG9pbnQ6TlAuY2xvbmUoKSx1djphaS5nZXRVVihOUCxJVixPUCxMVixZdWUsbXV0LGp1ZSxuZXcgTHQpLGZhY2U6bnVsbCxvYmplY3Q6dGhpc30pfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdC5jZW50ZXIhPT12b2lkIDAmJnRoaXMuY2VudGVyLmNvcHkodC5jZW50ZXIpLHRoaXMubWF0ZXJpYWw9dC5tYXRlcmlhbCx0aGlzfX07b00ucHJvdG90eXBlLmlzU3ByaXRlPSEwO2Z1bmN0aW9uIGtWKGUsdCxyLG4saSxvKXt6My5zdWJWZWN0b3JzKGUscikuYWRkU2NhbGFyKC41KS5tdWx0aXBseShuKSxpIT09dm9pZCAwPyhEUC54PW8qejMueC1pKnozLnksRFAueT1pKnozLngrbyp6My55KTpEUC5jb3B5KHozKSxlLmNvcHkodCksZS54Kz1EUC54LGUueSs9RFAueSxlLmFwcGx5TWF0cml4NCgkZmUpfXZhciBSVj1uZXcgaixYdWU9bmV3IGosZ1U9Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy5fY3VycmVudExldmVsPTAsdGhpcy50eXBlPSJMT0QiLE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMse2xldmVsczp7ZW51bWVyYWJsZTohMCx2YWx1ZTpbXX0saXNMT0Q6e3ZhbHVlOiEwfX0pLHRoaXMuYXV0b1VwZGF0ZT0hMH1jb3B5KHQpe3N1cGVyLmNvcHkodCwhMSk7bGV0IHI9dC5sZXZlbHM7Zm9yKGxldCBuPTAsaT1yLmxlbmd0aDtuPGk7bisrKXtsZXQgbz1yW25dO3RoaXMuYWRkTGV2ZWwoby5vYmplY3QuY2xvbmUoKSxvLmRpc3RhbmNlKX1yZXR1cm4gdGhpcy5hdXRvVXBkYXRlPXQuYXV0b1VwZGF0ZSx0aGlzfWFkZExldmVsKHQscj0wKXtyPU1hdGguYWJzKHIpO2xldCBuPXRoaXMubGV2ZWxzLGk7Zm9yKGk9MDtpPG4ubGVuZ3RoJiYhKHI8bltpXS5kaXN0YW5jZSk7aSsrKTtyZXR1cm4gbi5zcGxpY2UoaSwwLHtkaXN0YW5jZTpyLG9iamVjdDp0fSksdGhpcy5hZGQodCksdGhpc31nZXRDdXJyZW50TGV2ZWwoKXtyZXR1cm4gdGhpcy5fY3VycmVudExldmVsfWdldE9iamVjdEZvckRpc3RhbmNlKHQpe2xldCByPXRoaXMubGV2ZWxzO2lmKHIubGVuZ3RoPjApe2xldCBuLGk7Zm9yKG49MSxpPXIubGVuZ3RoO248aSYmISh0PHJbbl0uZGlzdGFuY2UpO24rKyk7cmV0dXJuIHJbbi0xXS5vYmplY3R9cmV0dXJuIG51bGx9cmF5Y2FzdCh0LHIpe2lmKHRoaXMubGV2ZWxzLmxlbmd0aD4wKXtSVi5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5tYXRyaXhXb3JsZCk7bGV0IGk9dC5yYXkub3JpZ2luLmRpc3RhbmNlVG8oUlYpO3RoaXMuZ2V0T2JqZWN0Rm9yRGlzdGFuY2UoaSkucmF5Y2FzdCh0LHIpfX11cGRhdGUodCl7bGV0IHI9dGhpcy5sZXZlbHM7aWYoci5sZW5ndGg+MSl7UlYuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKHQubWF0cml4V29ybGQpLFh1ZS5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5tYXRyaXhXb3JsZCk7bGV0IG49UlYuZGlzdGFuY2VUbyhYdWUpL3Quem9vbTtyWzBdLm9iamVjdC52aXNpYmxlPSEwO2xldCBpLG87Zm9yKGk9MSxvPXIubGVuZ3RoO2k8byYmbj49cltpXS5kaXN0YW5jZTtpKyspcltpLTFdLm9iamVjdC52aXNpYmxlPSExLHJbaV0ub2JqZWN0LnZpc2libGU9ITA7Zm9yKHRoaXMuX2N1cnJlbnRMZXZlbD1pLTE7aTxvO2krKylyW2ldLm9iamVjdC52aXNpYmxlPSExfX10b0pTT04odCl7bGV0IHI9c3VwZXIudG9KU09OKHQpO3RoaXMuYXV0b1VwZGF0ZT09PSExJiYoci5vYmplY3QuYXV0b1VwZGF0ZT0hMSksci5vYmplY3QubGV2ZWxzPVtdO2xldCBuPXRoaXMubGV2ZWxzO2ZvcihsZXQgaT0wLG89bi5sZW5ndGg7aTxvO2krKyl7bGV0IGE9bltpXTtyLm9iamVjdC5sZXZlbHMucHVzaCh7b2JqZWN0OmEub2JqZWN0LnV1aWQsZGlzdGFuY2U6YS5kaXN0YW5jZX0pfXJldHVybiByfX0sJHVlPW5ldyBqLEt1ZT1uZXcgZW4sWnVlPW5ldyBlbixJZ3I9bmV3IGosSnVlPW5ldyBNZSxhTT1jbGFzcyBleHRlbmRzIGVpe2NvbnN0cnVjdG9yKHQscil7c3VwZXIodCxyKSx0aGlzLnR5cGU9IlNraW5uZWRNZXNoIix0aGlzLmJpbmRNb2RlPSJhdHRhY2hlZCIsdGhpcy5iaW5kTWF0cml4PW5ldyBNZSx0aGlzLmJpbmRNYXRyaXhJbnZlcnNlPW5ldyBNZX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuYmluZE1vZGU9dC5iaW5kTW9kZSx0aGlzLmJpbmRNYXRyaXguY29weSh0LmJpbmRNYXRyaXgpLHRoaXMuYmluZE1hdHJpeEludmVyc2UuY29weSh0LmJpbmRNYXRyaXhJbnZlcnNlKSx0aGlzLnNrZWxldG9uPXQuc2tlbGV0b24sdGhpc31iaW5kKHQscil7dGhpcy5za2VsZXRvbj10LHI9PT12b2lkIDAmJih0aGlzLnVwZGF0ZU1hdHJpeFdvcmxkKCEwKSx0aGlzLnNrZWxldG9uLmNhbGN1bGF0ZUludmVyc2VzKCkscj10aGlzLm1hdHJpeFdvcmxkKSx0aGlzLmJpbmRNYXRyaXguY29weShyKSx0aGlzLmJpbmRNYXRyaXhJbnZlcnNlLmNvcHkocikuaW52ZXJ0KCl9cG9zZSgpe3RoaXMuc2tlbGV0b24ucG9zZSgpfW5vcm1hbGl6ZVNraW5XZWlnaHRzKCl7bGV0IHQ9bmV3IGVuLHI9dGhpcy5nZW9tZXRyeS5hdHRyaWJ1dGVzLnNraW5XZWlnaHQ7Zm9yKGxldCBuPTAsaT1yLmNvdW50O248aTtuKyspe3QueD1yLmdldFgobiksdC55PXIuZ2V0WShuKSx0Lno9ci5nZXRaKG4pLHQudz1yLmdldFcobik7bGV0IG89MS90Lm1hbmhhdHRhbkxlbmd0aCgpO28hPT0xLzA/dC5tdWx0aXBseVNjYWxhcihvKTp0LnNldCgxLDAsMCwwKSxyLnNldFhZWlcobix0LngsdC55LHQueix0LncpfX11cGRhdGVNYXRyaXhXb3JsZCh0KXtzdXBlci51cGRhdGVNYXRyaXhXb3JsZCh0KSx0aGlzLmJpbmRNb2RlPT09ImF0dGFjaGVkIj90aGlzLmJpbmRNYXRyaXhJbnZlcnNlLmNvcHkodGhpcy5tYXRyaXhXb3JsZCkuaW52ZXJ0KCk6dGhpcy5iaW5kTW9kZT09PSJkZXRhY2hlZCI/dGhpcy5iaW5kTWF0cml4SW52ZXJzZS5jb3B5KHRoaXMuYmluZE1hdHJpeCkuaW52ZXJ0KCk6Y29uc29sZS53YXJuKCJUSFJFRS5Ta2lubmVkTWVzaDogVW5yZWNvZ25pemVkIGJpbmRNb2RlOiAiK3RoaXMuYmluZE1vZGUpfWJvbmVUcmFuc2Zvcm0odCxyKXtsZXQgbj10aGlzLnNrZWxldG9uLGk9dGhpcy5nZW9tZXRyeTtLdWUuZnJvbUJ1ZmZlckF0dHJpYnV0ZShpLmF0dHJpYnV0ZXMuc2tpbkluZGV4LHQpLFp1ZS5mcm9tQnVmZmVyQXR0cmlidXRlKGkuYXR0cmlidXRlcy5za2luV2VpZ2h0LHQpLCR1ZS5jb3B5KHIpLmFwcGx5TWF0cml4NCh0aGlzLmJpbmRNYXRyaXgpLHIuc2V0KDAsMCwwKTtmb3IobGV0IG89MDtvPDQ7bysrKXtsZXQgYT1adWUuZ2V0Q29tcG9uZW50KG8pO2lmKGEhPT0wKXtsZXQgcz1LdWUuZ2V0Q29tcG9uZW50KG8pO0p1ZS5tdWx0aXBseU1hdHJpY2VzKG4uYm9uZXNbc10ubWF0cml4V29ybGQsbi5ib25lSW52ZXJzZXNbc10pLHIuYWRkU2NhbGVkVmVjdG9yKElnci5jb3B5KCR1ZSkuYXBwbHlNYXRyaXg0KEp1ZSksYSl9fXJldHVybiByLmFwcGx5TWF0cml4NCh0aGlzLmJpbmRNYXRyaXhJbnZlcnNlKX19O2FNLnByb3RvdHlwZS5pc1NraW5uZWRNZXNoPSEwO3ZhciBzTT1jbGFzcyBleHRlbmRzIG9ye2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLnR5cGU9IkJvbmUifX07c00ucHJvdG90eXBlLmlzQm9uZT0hMDt2YXIgSmQ9Y2xhc3MgZXh0ZW5kcyB4aXtjb25zdHJ1Y3Rvcih0PW51bGwscj0xLG49MSxpLG8sYSxzLGwsYz1MaSx1PUxpLGgsZil7c3VwZXIobnVsbCxhLHMsbCxjLHUsaSxvLGgsZiksdGhpcy5pbWFnZT17ZGF0YTp0LHdpZHRoOnIsaGVpZ2h0Om59LHRoaXMubWFnRmlsdGVyPWMsdGhpcy5taW5GaWx0ZXI9dSx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMSx0aGlzLmZsaXBZPSExLHRoaXMudW5wYWNrQWxpZ25tZW50PTF9fTtKZC5wcm90b3R5cGUuaXNEYXRhVGV4dHVyZT0hMDt2YXIgUXVlPW5ldyBNZSxMZ3I9bmV3IE1lLGxNPWNsYXNze2NvbnN0cnVjdG9yKHQ9W10scj1bXSl7dGhpcy51dWlkPU5sKCksdGhpcy5ib25lcz10LnNsaWNlKDApLHRoaXMuYm9uZUludmVyc2VzPXIsdGhpcy5ib25lTWF0cmljZXM9bnVsbCx0aGlzLmJvbmVUZXh0dXJlPW51bGwsdGhpcy5ib25lVGV4dHVyZVNpemU9MCx0aGlzLmZyYW1lPS0xLHRoaXMuaW5pdCgpfWluaXQoKXtsZXQgdD10aGlzLmJvbmVzLHI9dGhpcy5ib25lSW52ZXJzZXM7aWYodGhpcy5ib25lTWF0cmljZXM9bmV3IEZsb2F0MzJBcnJheSh0Lmxlbmd0aCoxNiksci5sZW5ndGg9PT0wKXRoaXMuY2FsY3VsYXRlSW52ZXJzZXMoKTtlbHNlIGlmKHQubGVuZ3RoIT09ci5sZW5ndGgpe2NvbnNvbGUud2FybigiVEhSRUUuU2tlbGV0b246IE51bWJlciBvZiBpbnZlcnNlIGJvbmUgbWF0cmljZXMgZG9lcyBub3QgbWF0Y2ggYW1vdW50IG9mIGJvbmVzLiIpLHRoaXMuYm9uZUludmVyc2VzPVtdO2ZvcihsZXQgbj0wLGk9dGhpcy5ib25lcy5sZW5ndGg7bjxpO24rKyl0aGlzLmJvbmVJbnZlcnNlcy5wdXNoKG5ldyBNZSl9fWNhbGN1bGF0ZUludmVyc2VzKCl7dGhpcy5ib25lSW52ZXJzZXMubGVuZ3RoPTA7Zm9yKGxldCB0PTAscj10aGlzLmJvbmVzLmxlbmd0aDt0PHI7dCsrKXtsZXQgbj1uZXcgTWU7dGhpcy5ib25lc1t0XSYmbi5jb3B5KHRoaXMuYm9uZXNbdF0ubWF0cml4V29ybGQpLmludmVydCgpLHRoaXMuYm9uZUludmVyc2VzLnB1c2gobil9fXBvc2UoKXtmb3IobGV0IHQ9MCxyPXRoaXMuYm9uZXMubGVuZ3RoO3Q8cjt0Kyspe2xldCBuPXRoaXMuYm9uZXNbdF07biYmbi5tYXRyaXhXb3JsZC5jb3B5KHRoaXMuYm9uZUludmVyc2VzW3RdKS5pbnZlcnQoKX1mb3IobGV0IHQ9MCxyPXRoaXMuYm9uZXMubGVuZ3RoO3Q8cjt0Kyspe2xldCBuPXRoaXMuYm9uZXNbdF07biYmKG4ucGFyZW50JiZuLnBhcmVudC5pc0JvbmU/KG4ubWF0cml4LmNvcHkobi5wYXJlbnQubWF0cml4V29ybGQpLmludmVydCgpLG4ubWF0cml4Lm11bHRpcGx5KG4ubWF0cml4V29ybGQpKTpuLm1hdHJpeC5jb3B5KG4ubWF0cml4V29ybGQpLG4ubWF0cml4LmRlY29tcG9zZShuLnBvc2l0aW9uLG4ucXVhdGVybmlvbixuLnNjYWxlKSl9fXVwZGF0ZSgpe2xldCB0PXRoaXMuYm9uZXMscj10aGlzLmJvbmVJbnZlcnNlcyxuPXRoaXMuYm9uZU1hdHJpY2VzLGk9dGhpcy5ib25lVGV4dHVyZTtmb3IobGV0IG89MCxhPXQubGVuZ3RoO288YTtvKyspe2xldCBzPXRbb10/dFtvXS5tYXRyaXhXb3JsZDpMZ3I7UXVlLm11bHRpcGx5TWF0cmljZXMocyxyW29dKSxRdWUudG9BcnJheShuLG8qMTYpfWkhPT1udWxsJiYoaS5uZWVkc1VwZGF0ZT0hMCl9Y2xvbmUoKXtyZXR1cm4gbmV3IGxNKHRoaXMuYm9uZXMsdGhpcy5ib25lSW52ZXJzZXMpfWNvbXB1dGVCb25lVGV4dHVyZSgpe2xldCB0PU1hdGguc3FydCh0aGlzLmJvbmVzLmxlbmd0aCo0KTt0PWtmZSh0KSx0PU1hdGgubWF4KHQsNCk7bGV0IHI9bmV3IEZsb2F0MzJBcnJheSh0KnQqNCk7ci5zZXQodGhpcy5ib25lTWF0cmljZXMpO2xldCBuPW5ldyBKZChyLHQsdCxRbyxqZCk7cmV0dXJuIG4ubmVlZHNVcGRhdGU9ITAsdGhpcy5ib25lTWF0cmljZXM9cix0aGlzLmJvbmVUZXh0dXJlPW4sdGhpcy5ib25lVGV4dHVyZVNpemU9dCx0aGlzfWdldEJvbmVCeU5hbWUodCl7Zm9yKGxldCByPTAsbj10aGlzLmJvbmVzLmxlbmd0aDtyPG47cisrKXtsZXQgaT10aGlzLmJvbmVzW3JdO2lmKGkubmFtZT09PXQpcmV0dXJuIGl9fWRpc3Bvc2UoKXt0aGlzLmJvbmVUZXh0dXJlIT09bnVsbCYmKHRoaXMuYm9uZVRleHR1cmUuZGlzcG9zZSgpLHRoaXMuYm9uZVRleHR1cmU9bnVsbCl9ZnJvbUpTT04odCxyKXt0aGlzLnV1aWQ9dC51dWlkO2ZvcihsZXQgbj0wLGk9dC5ib25lcy5sZW5ndGg7bjxpO24rKyl7bGV0IG89dC5ib25lc1tuXSxhPXJbb107YT09PXZvaWQgMCYmKGNvbnNvbGUud2FybigiVEhSRUUuU2tlbGV0b246IE5vIGJvbmUgZm91bmQgd2l0aCBVVUlEOiIsbyksYT1uZXcgc00pLHRoaXMuYm9uZXMucHVzaChhKSx0aGlzLmJvbmVJbnZlcnNlcy5wdXNoKG5ldyBNZSgpLmZyb21BcnJheSh0LmJvbmVJbnZlcnNlc1tuXSkpfXJldHVybiB0aGlzLmluaXQoKSx0aGlzfXRvSlNPTigpe2xldCB0PXttZXRhZGF0YTp7dmVyc2lvbjo0LjUsdHlwZToiU2tlbGV0b24iLGdlbmVyYXRvcjoiU2tlbGV0b24udG9KU09OIn0sYm9uZXM6W10sYm9uZUludmVyc2VzOltdfTt0LnV1aWQ9dGhpcy51dWlkO2xldCByPXRoaXMuYm9uZXMsbj10aGlzLmJvbmVJbnZlcnNlcztmb3IobGV0IGk9MCxvPXIubGVuZ3RoO2k8bztpKyspe2xldCBhPXJbaV07dC5ib25lcy5wdXNoKGEudXVpZCk7bGV0IHM9bltpXTt0LmJvbmVJbnZlcnNlcy5wdXNoKHMudG9BcnJheSgpKX1yZXR1cm4gdH19LHJtPWNsYXNzIGV4dGVuZHMgSmV7Y29uc3RydWN0b3IodCxyLG4saT0xKXt0eXBlb2Ygbj09Im51bWJlciImJihpPW4sbj0hMSxjb25zb2xlLmVycm9yKCJUSFJFRS5JbnN0YW5jZWRCdWZmZXJBdHRyaWJ1dGU6IFRoZSBjb25zdHJ1Y3RvciBub3cgZXhwZWN0cyBub3JtYWxpemVkIGFzIHRoZSB0aGlyZCBhcmd1bWVudC4iKSksc3VwZXIodCxyLG4pLHRoaXMubWVzaFBlckF0dHJpYnV0ZT1pfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5tZXNoUGVyQXR0cmlidXRlPXQubWVzaFBlckF0dHJpYnV0ZSx0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3JldHVybiB0Lm1lc2hQZXJBdHRyaWJ1dGU9dGhpcy5tZXNoUGVyQXR0cmlidXRlLHQuaXNJbnN0YW5jZWRCdWZmZXJBdHRyaWJ1dGU9ITAsdH19O3JtLnByb3RvdHlwZS5pc0luc3RhbmNlZEJ1ZmZlckF0dHJpYnV0ZT0hMDt2YXIgdGhlPW5ldyBNZSxlaGU9bmV3IE1lLE5WPVtdLHpQPW5ldyBlaSxuNj1jbGFzcyBleHRlbmRzIGVpe2NvbnN0cnVjdG9yKHQscixuKXtzdXBlcih0LHIpLHRoaXMuaW5zdGFuY2VNYXRyaXg9bmV3IHJtKG5ldyBGbG9hdDMyQXJyYXkobioxNiksMTYpLHRoaXMuaW5zdGFuY2VDb2xvcj1udWxsLHRoaXMuY291bnQ9bix0aGlzLmZydXN0dW1DdWxsZWQ9ITF9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmluc3RhbmNlTWF0cml4LmNvcHkodC5pbnN0YW5jZU1hdHJpeCksdC5pbnN0YW5jZUNvbG9yIT09bnVsbCYmKHRoaXMuaW5zdGFuY2VDb2xvcj10Lmluc3RhbmNlQ29sb3IuY2xvbmUoKSksdGhpcy5jb3VudD10LmNvdW50LHRoaXN9Z2V0Q29sb3JBdCh0LHIpe3IuZnJvbUFycmF5KHRoaXMuaW5zdGFuY2VDb2xvci5hcnJheSx0KjMpfWdldE1hdHJpeEF0KHQscil7ci5mcm9tQXJyYXkodGhpcy5pbnN0YW5jZU1hdHJpeC5hcnJheSx0KjE2KX1yYXljYXN0KHQscil7bGV0IG49dGhpcy5tYXRyaXhXb3JsZCxpPXRoaXMuY291bnQ7aWYoelAuZ2VvbWV0cnk9dGhpcy5nZW9tZXRyeSx6UC5tYXRlcmlhbD10aGlzLm1hdGVyaWFsLHpQLm1hdGVyaWFsIT09dm9pZCAwKWZvcihsZXQgbz0wO288aTtvKyspe3RoaXMuZ2V0TWF0cml4QXQobyx0aGUpLGVoZS5tdWx0aXBseU1hdHJpY2VzKG4sdGhlKSx6UC5tYXRyaXhXb3JsZD1laGUselAucmF5Y2FzdCh0LE5WKTtmb3IobGV0IGE9MCxzPU5WLmxlbmd0aDthPHM7YSsrKXtsZXQgbD1OVlthXTtsLmluc3RhbmNlSWQ9byxsLm9iamVjdD10aGlzLHIucHVzaChsKX1OVi5sZW5ndGg9MH19c2V0Q29sb3JBdCh0LHIpe3RoaXMuaW5zdGFuY2VDb2xvcj09PW51bGwmJih0aGlzLmluc3RhbmNlQ29sb3I9bmV3IHJtKG5ldyBGbG9hdDMyQXJyYXkodGhpcy5pbnN0YW5jZU1hdHJpeC5jb3VudCozKSwzKSksci50b0FycmF5KHRoaXMuaW5zdGFuY2VDb2xvci5hcnJheSx0KjMpfXNldE1hdHJpeEF0KHQscil7ci50b0FycmF5KHRoaXMuaW5zdGFuY2VNYXRyaXguYXJyYXksdCoxNil9dXBkYXRlTW9ycGhUYXJnZXRzKCl7fWRpc3Bvc2UoKXt0aGlzLmRpc3BhdGNoRXZlbnQoe3R5cGU6ImRpc3Bvc2UifSl9fTtuNi5wcm90b3R5cGUuaXNJbnN0YW5jZWRNZXNoPSEwO3ZhciBHaT1jbGFzcyBleHRlbmRzIHFpe2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJMaW5lQmFzaWNNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgbmUoMTY3NzcyMTUpLHRoaXMubGluZXdpZHRoPTEsdGhpcy5saW5lY2FwPSJyb3VuZCIsdGhpcy5saW5lam9pbj0icm91bmQiLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMubGluZXdpZHRoPXQubGluZXdpZHRoLHRoaXMubGluZWNhcD10LmxpbmVjYXAsdGhpcy5saW5lam9pbj10LmxpbmVqb2luLHRoaXN9fTtHaS5wcm90b3R5cGUuaXNMaW5lQmFzaWNNYXRlcmlhbD0hMDt2YXIgcmhlPW5ldyBqLG5oZT1uZXcgaixpaGU9bmV3IE1lLGd1dD1uZXcgSmYsRFY9bmV3IFpmLGNoPWNsYXNzIGV4dGVuZHMgb3J7Y29uc3RydWN0b3IodD1uZXcgUGUscj1uZXcgR2kpe3N1cGVyKCksdGhpcy50eXBlPSJMaW5lIix0aGlzLmdlb21ldHJ5PXQsdGhpcy5tYXRlcmlhbD1yLHRoaXMudXBkYXRlTW9ycGhUYXJnZXRzKCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLm1hdGVyaWFsPXQubWF0ZXJpYWwsdGhpcy5nZW9tZXRyeT10Lmdlb21ldHJ5LHRoaXN9Y29tcHV0ZUxpbmVEaXN0YW5jZXMoKXtsZXQgdD10aGlzLmdlb21ldHJ5O2lmKHQuaXNCdWZmZXJHZW9tZXRyeSlpZih0LmluZGV4PT09bnVsbCl7bGV0IHI9dC5hdHRyaWJ1dGVzLnBvc2l0aW9uLG49WzBdO2ZvcihsZXQgaT0xLG89ci5jb3VudDtpPG87aSsrKXJoZS5mcm9tQnVmZmVyQXR0cmlidXRlKHIsaS0xKSxuaGUuZnJvbUJ1ZmZlckF0dHJpYnV0ZShyLGkpLG5baV09bltpLTFdLG5baV0rPXJoZS5kaXN0YW5jZVRvKG5oZSk7dC5zZXRBdHRyaWJ1dGUoImxpbmVEaXN0YW5jZSIsbmV3IHhlKG4sMSkpfWVsc2UgY29uc29sZS53YXJuKCJUSFJFRS5MaW5lLmNvbXB1dGVMaW5lRGlzdGFuY2VzKCk6IENvbXB1dGF0aW9uIG9ubHkgcG9zc2libGUgd2l0aCBub24taW5kZXhlZCBCdWZmZXJHZW9tZXRyeS4iKTtlbHNlIHQuaXNHZW9tZXRyeSYmY29uc29sZS5lcnJvcigiVEhSRUUuTGluZS5jb21wdXRlTGluZURpc3RhbmNlcygpIG5vIGxvbmdlciBzdXBwb3J0cyBUSFJFRS5HZW9tZXRyeS4gVXNlIFRIUkVFLkJ1ZmZlckdlb21ldHJ5IGluc3RlYWQuIik7cmV0dXJuIHRoaXN9cmF5Y2FzdCh0LHIpe2xldCBuPXRoaXMuZ2VvbWV0cnksaT10aGlzLm1hdHJpeFdvcmxkLG89dC5wYXJhbXMuTGluZS50aHJlc2hvbGQsYT1uLmRyYXdSYW5nZTtpZihuLmJvdW5kaW5nU3BoZXJlPT09bnVsbCYmbi5jb21wdXRlQm91bmRpbmdTcGhlcmUoKSxEVi5jb3B5KG4uYm91bmRpbmdTcGhlcmUpLERWLmFwcGx5TWF0cml4NChpKSxEVi5yYWRpdXMrPW8sdC5yYXkuaW50ZXJzZWN0c1NwaGVyZShEVik9PT0hMSlyZXR1cm47aWhlLmNvcHkoaSkuaW52ZXJ0KCksZ3V0LmNvcHkodC5yYXkpLmFwcGx5TWF0cml4NChpaGUpO2xldCBzPW8vKCh0aGlzLnNjYWxlLngrdGhpcy5zY2FsZS55K3RoaXMuc2NhbGUueikvMyksbD1zKnMsYz1uZXcgaix1PW5ldyBqLGg9bmV3IGosZj1uZXcgaixwPXRoaXMuaXNMaW5lU2VnbWVudHM/MjoxO2lmKG4uaXNCdWZmZXJHZW9tZXRyeSl7bGV0IGQ9bi5pbmRleCxfPW4uYXR0cmlidXRlcy5wb3NpdGlvbjtpZihkIT09bnVsbCl7bGV0IHk9TWF0aC5tYXgoMCxhLnN0YXJ0KSx4PU1hdGgubWluKGQuY291bnQsYS5zdGFydCthLmNvdW50KTtmb3IobGV0IGI9eSxTPXgtMTtiPFM7Yis9cCl7bGV0IEM9ZC5nZXRYKGIpLFA9ZC5nZXRYKGIrMSk7aWYoYy5mcm9tQnVmZmVyQXR0cmlidXRlKF8sQyksdS5mcm9tQnVmZmVyQXR0cmlidXRlKF8sUCksZ3V0LmRpc3RhbmNlU3FUb1NlZ21lbnQoYyx1LGYsaCk+bCljb250aW51ZTtmLmFwcGx5TWF0cml4NCh0aGlzLm1hdHJpeFdvcmxkKTtsZXQgTz10LnJheS5vcmlnaW4uZGlzdGFuY2VUbyhmKTtPPHQubmVhcnx8Tz50LmZhcnx8ci5wdXNoKHtkaXN0YW5jZTpPLHBvaW50OmguY2xvbmUoKS5hcHBseU1hdHJpeDQodGhpcy5tYXRyaXhXb3JsZCksaW5kZXg6YixmYWNlOm51bGwsZmFjZUluZGV4Om51bGwsb2JqZWN0OnRoaXN9KX19ZWxzZXtsZXQgeT1NYXRoLm1heCgwLGEuc3RhcnQpLHg9TWF0aC5taW4oXy5jb3VudCxhLnN0YXJ0K2EuY291bnQpO2ZvcihsZXQgYj15LFM9eC0xO2I8UztiKz1wKXtpZihjLmZyb21CdWZmZXJBdHRyaWJ1dGUoXyxiKSx1LmZyb21CdWZmZXJBdHRyaWJ1dGUoXyxiKzEpLGd1dC5kaXN0YW5jZVNxVG9TZWdtZW50KGMsdSxmLGgpPmwpY29udGludWU7Zi5hcHBseU1hdHJpeDQodGhpcy5tYXRyaXhXb3JsZCk7bGV0IFA9dC5yYXkub3JpZ2luLmRpc3RhbmNlVG8oZik7UDx0Lm5lYXJ8fFA+dC5mYXJ8fHIucHVzaCh7ZGlzdGFuY2U6UCxwb2ludDpoLmNsb25lKCkuYXBwbHlNYXRyaXg0KHRoaXMubWF0cml4V29ybGQpLGluZGV4OmIsZmFjZTpudWxsLGZhY2VJbmRleDpudWxsLG9iamVjdDp0aGlzfSl9fX1lbHNlIG4uaXNHZW9tZXRyeSYmY29uc29sZS5lcnJvcigiVEhSRUUuTGluZS5yYXljYXN0KCkgbm8gbG9uZ2VyIHN1cHBvcnRzIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX11cGRhdGVNb3JwaFRhcmdldHMoKXtsZXQgdD10aGlzLmdlb21ldHJ5O2lmKHQuaXNCdWZmZXJHZW9tZXRyeSl7bGV0IHI9dC5tb3JwaEF0dHJpYnV0ZXMsbj1PYmplY3Qua2V5cyhyKTtpZihuLmxlbmd0aD4wKXtsZXQgaT1yW25bMF1dO2lmKGkhPT12b2lkIDApe3RoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzPVtdLHRoaXMubW9ycGhUYXJnZXREaWN0aW9uYXJ5PXt9O2ZvcihsZXQgbz0wLGE9aS5sZW5ndGg7bzxhO28rKyl7bGV0IHM9aVtvXS5uYW1lfHxTdHJpbmcobyk7dGhpcy5tb3JwaFRhcmdldEluZmx1ZW5jZXMucHVzaCgwKSx0aGlzLm1vcnBoVGFyZ2V0RGljdGlvbmFyeVtzXT1vfX19fWVsc2V7bGV0IHI9dC5tb3JwaFRhcmdldHM7ciE9PXZvaWQgMCYmci5sZW5ndGg+MCYmY29uc29sZS5lcnJvcigiVEhSRUUuTGluZS51cGRhdGVNb3JwaFRhcmdldHMoKSBkb2VzIG5vdCBzdXBwb3J0IFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX19fTtjaC5wcm90b3R5cGUuaXNMaW5lPSEwO3ZhciBvaGU9bmV3IGosYWhlPW5ldyBqLEFhPWNsYXNzIGV4dGVuZHMgY2h7Y29uc3RydWN0b3IodCxyKXtzdXBlcih0LHIpLHRoaXMudHlwZT0iTGluZVNlZ21lbnRzIn1jb21wdXRlTGluZURpc3RhbmNlcygpe2xldCB0PXRoaXMuZ2VvbWV0cnk7aWYodC5pc0J1ZmZlckdlb21ldHJ5KWlmKHQuaW5kZXg9PT1udWxsKXtsZXQgcj10LmF0dHJpYnV0ZXMucG9zaXRpb24sbj1bXTtmb3IobGV0IGk9MCxvPXIuY291bnQ7aTxvO2krPTIpb2hlLmZyb21CdWZmZXJBdHRyaWJ1dGUocixpKSxhaGUuZnJvbUJ1ZmZlckF0dHJpYnV0ZShyLGkrMSksbltpXT1pPT09MD8wOm5baS0xXSxuW2krMV09bltpXStvaGUuZGlzdGFuY2VUbyhhaGUpO3Quc2V0QXR0cmlidXRlKCJsaW5lRGlzdGFuY2UiLG5ldyB4ZShuLDEpKX1lbHNlIGNvbnNvbGUud2FybigiVEhSRUUuTGluZVNlZ21lbnRzLmNvbXB1dGVMaW5lRGlzdGFuY2VzKCk6IENvbXB1dGF0aW9uIG9ubHkgcG9zc2libGUgd2l0aCBub24taW5kZXhlZCBCdWZmZXJHZW9tZXRyeS4iKTtlbHNlIHQuaXNHZW9tZXRyeSYmY29uc29sZS5lcnJvcigiVEhSRUUuTGluZVNlZ21lbnRzLmNvbXB1dGVMaW5lRGlzdGFuY2VzKCkgbm8gbG9uZ2VyIHN1cHBvcnRzIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKTtyZXR1cm4gdGhpc319O0FhLnByb3RvdHlwZS5pc0xpbmVTZWdtZW50cz0hMDt2YXIgaTY9Y2xhc3MgZXh0ZW5kcyBjaHtjb25zdHJ1Y3Rvcih0LHIpe3N1cGVyKHQsciksdGhpcy50eXBlPSJMaW5lTG9vcCJ9fTtpNi5wcm90b3R5cGUuaXNMaW5lTG9vcD0hMDt2YXIgbm09Y2xhc3MgZXh0ZW5kcyBxaXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iUG9pbnRzTWF0ZXJpYWwiLHRoaXMuY29sb3I9bmV3IG5lKDE2Nzc3MjE1KSx0aGlzLm1hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLnNpemU9MSx0aGlzLnNpemVBdHRlbnVhdGlvbj0hMCx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuY29sb3IuY29weSh0LmNvbG9yKSx0aGlzLm1hcD10Lm1hcCx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5zaXplPXQuc2l6ZSx0aGlzLnNpemVBdHRlbnVhdGlvbj10LnNpemVBdHRlbnVhdGlvbix0aGlzfX07bm0ucHJvdG90eXBlLmlzUG9pbnRzTWF0ZXJpYWw9ITA7dmFyIHNoZT1uZXcgTWUsbmh0PW5ldyBKZixPVj1uZXcgWmYselY9bmV3IGosaW09Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcih0PW5ldyBQZSxyPW5ldyBubSl7c3VwZXIoKSx0aGlzLnR5cGU9IlBvaW50cyIsdGhpcy5nZW9tZXRyeT10LHRoaXMubWF0ZXJpYWw9cix0aGlzLnVwZGF0ZU1vcnBoVGFyZ2V0cygpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5tYXRlcmlhbD10Lm1hdGVyaWFsLHRoaXMuZ2VvbWV0cnk9dC5nZW9tZXRyeSx0aGlzfXJheWNhc3QodCxyKXtsZXQgbj10aGlzLmdlb21ldHJ5LGk9dGhpcy5tYXRyaXhXb3JsZCxvPXQucGFyYW1zLlBvaW50cy50aHJlc2hvbGQsYT1uLmRyYXdSYW5nZTtpZihuLmJvdW5kaW5nU3BoZXJlPT09bnVsbCYmbi5jb21wdXRlQm91bmRpbmdTcGhlcmUoKSxPVi5jb3B5KG4uYm91bmRpbmdTcGhlcmUpLE9WLmFwcGx5TWF0cml4NChpKSxPVi5yYWRpdXMrPW8sdC5yYXkuaW50ZXJzZWN0c1NwaGVyZShPVik9PT0hMSlyZXR1cm47c2hlLmNvcHkoaSkuaW52ZXJ0KCksbmh0LmNvcHkodC5yYXkpLmFwcGx5TWF0cml4NChzaGUpO2xldCBzPW8vKCh0aGlzLnNjYWxlLngrdGhpcy5zY2FsZS55K3RoaXMuc2NhbGUueikvMyksbD1zKnM7aWYobi5pc0J1ZmZlckdlb21ldHJ5KXtsZXQgYz1uLmluZGV4LGg9bi5hdHRyaWJ1dGVzLnBvc2l0aW9uO2lmKGMhPT1udWxsKXtsZXQgZj1NYXRoLm1heCgwLGEuc3RhcnQpLHA9TWF0aC5taW4oYy5jb3VudCxhLnN0YXJ0K2EuY291bnQpO2ZvcihsZXQgZD1mLGc9cDtkPGc7ZCsrKXtsZXQgXz1jLmdldFgoZCk7elYuZnJvbUJ1ZmZlckF0dHJpYnV0ZShoLF8pLGxoZSh6VixfLGwsaSx0LHIsdGhpcyl9fWVsc2V7bGV0IGY9TWF0aC5tYXgoMCxhLnN0YXJ0KSxwPU1hdGgubWluKGguY291bnQsYS5zdGFydCthLmNvdW50KTtmb3IobGV0IGQ9ZixnPXA7ZDxnO2QrKyl6Vi5mcm9tQnVmZmVyQXR0cmlidXRlKGgsZCksbGhlKHpWLGQsbCxpLHQscix0aGlzKX19ZWxzZSBjb25zb2xlLmVycm9yKCJUSFJFRS5Qb2ludHMucmF5Y2FzdCgpIG5vIGxvbmdlciBzdXBwb3J0cyBUSFJFRS5HZW9tZXRyeS4gVXNlIFRIUkVFLkJ1ZmZlckdlb21ldHJ5IGluc3RlYWQuIil9dXBkYXRlTW9ycGhUYXJnZXRzKCl7bGV0IHQ9dGhpcy5nZW9tZXRyeTtpZih0LmlzQnVmZmVyR2VvbWV0cnkpe2xldCByPXQubW9ycGhBdHRyaWJ1dGVzLG49T2JqZWN0LmtleXMocik7aWYobi5sZW5ndGg+MCl7bGV0IGk9cltuWzBdXTtpZihpIT09dm9pZCAwKXt0aGlzLm1vcnBoVGFyZ2V0SW5mbHVlbmNlcz1bXSx0aGlzLm1vcnBoVGFyZ2V0RGljdGlvbmFyeT17fTtmb3IobGV0IG89MCxhPWkubGVuZ3RoO288YTtvKyspe2xldCBzPWlbb10ubmFtZXx8U3RyaW5nKG8pO3RoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzLnB1c2goMCksdGhpcy5tb3JwaFRhcmdldERpY3Rpb25hcnlbc109b319fX1lbHNle2xldCByPXQubW9ycGhUYXJnZXRzO3IhPT12b2lkIDAmJnIubGVuZ3RoPjAmJmNvbnNvbGUuZXJyb3IoIlRIUkVFLlBvaW50cy51cGRhdGVNb3JwaFRhcmdldHMoKSBkb2VzIG5vdCBzdXBwb3J0IFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX19fTtpbS5wcm90b3R5cGUuaXNQb2ludHM9ITA7ZnVuY3Rpb24gbGhlKGUsdCxyLG4saSxvLGEpe2xldCBzPW5odC5kaXN0YW5jZVNxVG9Qb2ludChlKTtpZihzPHIpe2xldCBsPW5ldyBqO25odC5jbG9zZXN0UG9pbnRUb1BvaW50KGUsbCksbC5hcHBseU1hdHJpeDQobik7bGV0IGM9aS5yYXkub3JpZ2luLmRpc3RhbmNlVG8obCk7aWYoYzxpLm5lYXJ8fGM+aS5mYXIpcmV0dXJuO28ucHVzaCh7ZGlzdGFuY2U6YyxkaXN0YW5jZVRvUmF5Ok1hdGguc3FydChzKSxwb2ludDpsLGluZGV4OnQsZmFjZTpudWxsLG9iamVjdDphfSl9fXZhciBfVT1jbGFzcyBleHRlbmRzIHhpe2NvbnN0cnVjdG9yKHQscixuLGksbyxhLHMsbCxjKXtzdXBlcih0LHIsbixpLG8sYSxzLGwsYyksdGhpcy5taW5GaWx0ZXI9YSE9PXZvaWQgMD9hOm9pLHRoaXMubWFnRmlsdGVyPW8hPT12b2lkIDA/bzpvaSx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMTtsZXQgdT10aGlzO2Z1bmN0aW9uIGgoKXt1Lm5lZWRzVXBkYXRlPSEwLHQucmVxdWVzdFZpZGVvRnJhbWVDYWxsYmFjayhoKX0icmVxdWVzdFZpZGVvRnJhbWVDYWxsYmFjayJpbiB0JiZ0LnJlcXVlc3RWaWRlb0ZyYW1lQ2FsbGJhY2soaCl9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5pbWFnZSkuY29weSh0aGlzKX11cGRhdGUoKXtsZXQgdD10aGlzLmltYWdlOyJyZXF1ZXN0VmlkZW9GcmFtZUNhbGxiYWNrImluIHQ9PT0hMSYmdC5yZWFkeVN0YXRlPj10LkhBVkVfQ1VSUkVOVF9EQVRBJiYodGhpcy5uZWVkc1VwZGF0ZT0hMCl9fTtfVS5wcm90b3R5cGUuaXNWaWRlb1RleHR1cmU9ITA7dmFyIHlVPWNsYXNzIGV4dGVuZHMgeGl7Y29uc3RydWN0b3IodCxyLG4pe3N1cGVyKHt3aWR0aDp0LGhlaWdodDpyfSksdGhpcy5mb3JtYXQ9bix0aGlzLm1hZ0ZpbHRlcj1MaSx0aGlzLm1pbkZpbHRlcj1MaSx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMSx0aGlzLm5lZWRzVXBkYXRlPSEwfX07eVUucHJvdG90eXBlLmlzRnJhbWVidWZmZXJUZXh0dXJlPSEwO3ZhciBvNj1jbGFzcyBleHRlbmRzIHhpe2NvbnN0cnVjdG9yKHQscixuLGksbyxhLHMsbCxjLHUsaCxmKXtzdXBlcihudWxsLGEscyxsLGMsdSxpLG8saCxmKSx0aGlzLmltYWdlPXt3aWR0aDpyLGhlaWdodDpufSx0aGlzLm1pcG1hcHM9dCx0aGlzLmZsaXBZPSExLHRoaXMuZ2VuZXJhdGVNaXBtYXBzPSExfX07bzYucHJvdG90eXBlLmlzQ29tcHJlc3NlZFRleHR1cmU9ITA7dmFyIHZVPWNsYXNzIGV4dGVuZHMgeGl7Y29uc3RydWN0b3IodCxyLG4saSxvLGEscyxsLGMpe3N1cGVyKHQscixuLGksbyxhLHMsbCxjKSx0aGlzLm5lZWRzVXBkYXRlPSEwfX07dlUucHJvdG90eXBlLmlzQ2FudmFzVGV4dHVyZT0hMDt2YXIgRnY9Y2xhc3MgZXh0ZW5kcyBQZXtjb25zdHJ1Y3Rvcih0PTEscj04LG49MCxpPU1hdGguUEkqMil7c3VwZXIoKSx0aGlzLnR5cGU9IkNpcmNsZUdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3JhZGl1czp0LHNlZ21lbnRzOnIsdGhldGFTdGFydDpuLHRoZXRhTGVuZ3RoOml9LHI9TWF0aC5tYXgoMyxyKTtsZXQgbz1bXSxhPVtdLHM9W10sbD1bXSxjPW5ldyBqLHU9bmV3IEx0O2EucHVzaCgwLDAsMCkscy5wdXNoKDAsMCwxKSxsLnB1c2goLjUsLjUpO2ZvcihsZXQgaD0wLGY9MztoPD1yO2grKyxmKz0zKXtsZXQgcD1uK2gvcippO2MueD10Kk1hdGguY29zKHApLGMueT10Kk1hdGguc2luKHApLGEucHVzaChjLngsYy55LGMueikscy5wdXNoKDAsMCwxKSx1Lng9KGFbZl0vdCsxKS8yLHUueT0oYVtmKzFdL3QrMSkvMixsLnB1c2godS54LHUueSl9Zm9yKGxldCBoPTE7aDw9cjtoKyspby5wdXNoKGgsaCsxLDApO3RoaXMuc2V0SW5kZXgobyksdGhpcy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUoYSwzKSksdGhpcy5zZXRBdHRyaWJ1dGUoIm5vcm1hbCIsbmV3IHhlKHMsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJ1diIsbmV3IHhlKGwsMikpfXN0YXRpYyBmcm9tSlNPTih0KXtyZXR1cm4gbmV3IEZ2KHQucmFkaXVzLHQuc2VnbWVudHMsdC50aGV0YVN0YXJ0LHQudGhldGFMZW5ndGgpfX0sb209Y2xhc3MgZXh0ZW5kcyBQZXtjb25zdHJ1Y3Rvcih0PTEscj0xLG49MSxpPTgsbz0xLGE9ITEscz0wLGw9TWF0aC5QSSoyKXtzdXBlcigpLHRoaXMudHlwZT0iQ3lsaW5kZXJHZW9tZXRyeSIsdGhpcy5wYXJhbWV0ZXJzPXtyYWRpdXNUb3A6dCxyYWRpdXNCb3R0b206cixoZWlnaHQ6bixyYWRpYWxTZWdtZW50czppLGhlaWdodFNlZ21lbnRzOm8sb3BlbkVuZGVkOmEsdGhldGFTdGFydDpzLHRoZXRhTGVuZ3RoOmx9O2xldCBjPXRoaXM7aT1NYXRoLmZsb29yKGkpLG89TWF0aC5mbG9vcihvKTtsZXQgdT1bXSxoPVtdLGY9W10scD1bXSxkPTAsZz1bXSxfPW4vMix5PTA7eCgpLGE9PT0hMSYmKHQ+MCYmYighMCkscj4wJiZiKCExKSksdGhpcy5zZXRJbmRleCh1KSx0aGlzLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyB4ZShoLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgibm9ybWFsIixuZXcgeGUoZiwzKSksdGhpcy5zZXRBdHRyaWJ1dGUoInV2IixuZXcgeGUocCwyKSk7ZnVuY3Rpb24geCgpe2xldCBTPW5ldyBqLEM9bmV3IGosUD0wLGs9KHItdCkvbjtmb3IobGV0IE89MDtPPD1vO08rKyl7bGV0IEQ9W10sQj1PL28sST1CKihyLXQpK3Q7Zm9yKGxldCBMPTA7TDw9aTtMKyspe2xldCBSPUwvaSxGPVIqbCtzLHo9TWF0aC5zaW4oRiksVT1NYXRoLmNvcyhGKTtDLng9SSp6LEMueT0tQipuK18sQy56PUkqVSxoLnB1c2goQy54LEMueSxDLnopLFMuc2V0KHosayxVKS5ub3JtYWxpemUoKSxmLnB1c2goUy54LFMueSxTLnopLHAucHVzaChSLDEtQiksRC5wdXNoKGQrKyl9Zy5wdXNoKEQpfWZvcihsZXQgTz0wO088aTtPKyspZm9yKGxldCBEPTA7RDxvO0QrKyl7bGV0IEI9Z1tEXVtPXSxJPWdbRCsxXVtPXSxMPWdbRCsxXVtPKzFdLFI9Z1tEXVtPKzFdO3UucHVzaChCLEksUiksdS5wdXNoKEksTCxSKSxQKz02fWMuYWRkR3JvdXAoeSxQLDApLHkrPVB9ZnVuY3Rpb24gYihTKXtsZXQgQz1kLFA9bmV3IEx0LGs9bmV3IGosTz0wLEQ9Uz09PSEwP3Q6cixCPVM9PT0hMD8xOi0xO2ZvcihsZXQgTD0xO0w8PWk7TCsrKWgucHVzaCgwLF8qQiwwKSxmLnB1c2goMCxCLDApLHAucHVzaCguNSwuNSksZCsrO2xldCBJPWQ7Zm9yKGxldCBMPTA7TDw9aTtMKyspe2xldCBGPUwvaSpsK3Msej1NYXRoLmNvcyhGKSxVPU1hdGguc2luKEYpO2sueD1EKlUsay55PV8qQixrLno9RCp6LGgucHVzaChrLngsay55LGsueiksZi5wdXNoKDAsQiwwKSxQLng9eiouNSsuNSxQLnk9VSouNSpCKy41LHAucHVzaChQLngsUC55KSxkKyt9Zm9yKGxldCBMPTA7TDxpO0wrKyl7bGV0IFI9QytMLEY9SStMO1M9PT0hMD91LnB1c2goRixGKzEsUik6dS5wdXNoKEYrMSxGLFIpLE8rPTN9Yy5hZGRHcm91cCh5LE8sUz09PSEwPzE6MikseSs9T319c3RhdGljIGZyb21KU09OKHQpe3JldHVybiBuZXcgb20odC5yYWRpdXNUb3AsdC5yYWRpdXNCb3R0b20sdC5oZWlnaHQsdC5yYWRpYWxTZWdtZW50cyx0LmhlaWdodFNlZ21lbnRzLHQub3BlbkVuZGVkLHQudGhldGFTdGFydCx0LnRoZXRhTGVuZ3RoKX19LEJ2PWNsYXNzIGV4dGVuZHMgb217Y29uc3RydWN0b3IodD0xLHI9MSxuPTgsaT0xLG89ITEsYT0wLHM9TWF0aC5QSSoyKXtzdXBlcigwLHQscixuLGksbyxhLHMpLHRoaXMudHlwZT0iQ29uZUdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3JhZGl1czp0LGhlaWdodDpyLHJhZGlhbFNlZ21lbnRzOm4saGVpZ2h0U2VnbWVudHM6aSxvcGVuRW5kZWQ6byx0aGV0YVN0YXJ0OmEsdGhldGFMZW5ndGg6c319c3RhdGljIGZyb21KU09OKHQpe3JldHVybiBuZXcgQnYodC5yYWRpdXMsdC5oZWlnaHQsdC5yYWRpYWxTZWdtZW50cyx0LmhlaWdodFNlZ21lbnRzLHQub3BlbkVuZGVkLHQudGhldGFTdGFydCx0LnRoZXRhTGVuZ3RoKX19LHVoPWNsYXNzIGV4dGVuZHMgUGV7Y29uc3RydWN0b3IodD1bXSxyPVtdLG49MSxpPTApe3N1cGVyKCksdGhpcy50eXBlPSJQb2x5aGVkcm9uR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17dmVydGljZXM6dCxpbmRpY2VzOnIscmFkaXVzOm4sZGV0YWlsOml9O2xldCBvPVtdLGE9W107cyhpKSxjKG4pLHUoKSx0aGlzLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyB4ZShvLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgibm9ybWFsIixuZXcgeGUoby5zbGljZSgpLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyB4ZShhLDIpKSxpPT09MD90aGlzLmNvbXB1dGVWZXJ0ZXhOb3JtYWxzKCk6dGhpcy5ub3JtYWxpemVOb3JtYWxzKCk7ZnVuY3Rpb24gcyh4KXtsZXQgYj1uZXcgaixTPW5ldyBqLEM9bmV3IGo7Zm9yKGxldCBQPTA7UDxyLmxlbmd0aDtQKz0zKXAocltQKzBdLGIpLHAocltQKzFdLFMpLHAocltQKzJdLEMpLGwoYixTLEMseCl9ZnVuY3Rpb24gbCh4LGIsUyxDKXtsZXQgUD1DKzEsaz1bXTtmb3IobGV0IE89MDtPPD1QO08rKyl7a1tPXT1bXTtsZXQgRD14LmNsb25lKCkubGVycChTLE8vUCksQj1iLmNsb25lKCkubGVycChTLE8vUCksST1QLU87Zm9yKGxldCBMPTA7TDw9STtMKyspTD09PTAmJk89PT1QP2tbT11bTF09RDprW09dW0xdPUQuY2xvbmUoKS5sZXJwKEIsTC9JKX1mb3IobGV0IE89MDtPPFA7TysrKWZvcihsZXQgRD0wO0Q8MiooUC1PKS0xO0QrKyl7bGV0IEI9TWF0aC5mbG9vcihELzIpO0QlMj09PTA/KGYoa1tPXVtCKzFdKSxmKGtbTysxXVtCXSksZihrW09dW0JdKSk6KGYoa1tPXVtCKzFdKSxmKGtbTysxXVtCKzFdKSxmKGtbTysxXVtCXSkpfX1mdW5jdGlvbiBjKHgpe2xldCBiPW5ldyBqO2ZvcihsZXQgUz0wO1M8by5sZW5ndGg7Uys9MyliLng9b1tTKzBdLGIueT1vW1MrMV0sYi56PW9bUysyXSxiLm5vcm1hbGl6ZSgpLm11bHRpcGx5U2NhbGFyKHgpLG9bUyswXT1iLngsb1tTKzFdPWIueSxvW1MrMl09Yi56fWZ1bmN0aW9uIHUoKXtsZXQgeD1uZXcgajtmb3IobGV0IGI9MDtiPG8ubGVuZ3RoO2IrPTMpe3gueD1vW2IrMF0seC55PW9bYisxXSx4Lno9b1tiKzJdO2xldCBTPV8oeCkvMi9NYXRoLlBJKy41LEM9eSh4KS9NYXRoLlBJKy41O2EucHVzaChTLDEtQyl9ZCgpLGgoKX1mdW5jdGlvbiBoKCl7Zm9yKGxldCB4PTA7eDxhLmxlbmd0aDt4Kz02KXtsZXQgYj1hW3grMF0sUz1hW3grMl0sQz1hW3grNF0sUD1NYXRoLm1heChiLFMsQyksaz1NYXRoLm1pbihiLFMsQyk7UD4uOSYmazwuMSYmKGI8LjImJihhW3grMF0rPTEpLFM8LjImJihhW3grMl0rPTEpLEM8LjImJihhW3grNF0rPTEpKX19ZnVuY3Rpb24gZih4KXtvLnB1c2goeC54LHgueSx4LnopfWZ1bmN0aW9uIHAoeCxiKXtsZXQgUz14KjM7Yi54PXRbUyswXSxiLnk9dFtTKzFdLGIuej10W1MrMl19ZnVuY3Rpb24gZCgpe2xldCB4PW5ldyBqLGI9bmV3IGosUz1uZXcgaixDPW5ldyBqLFA9bmV3IEx0LGs9bmV3IEx0LE89bmV3IEx0O2ZvcihsZXQgRD0wLEI9MDtEPG8ubGVuZ3RoO0QrPTksQis9Nil7eC5zZXQob1tEKzBdLG9bRCsxXSxvW0QrMl0pLGIuc2V0KG9bRCszXSxvW0QrNF0sb1tEKzVdKSxTLnNldChvW0QrNl0sb1tEKzddLG9bRCs4XSksUC5zZXQoYVtCKzBdLGFbQisxXSksay5zZXQoYVtCKzJdLGFbQiszXSksTy5zZXQoYVtCKzRdLGFbQis1XSksQy5jb3B5KHgpLmFkZChiKS5hZGQoUykuZGl2aWRlU2NhbGFyKDMpO2xldCBJPV8oQyk7ZyhQLEIrMCx4LEkpLGcoayxCKzIsYixJKSxnKE8sQis0LFMsSSl9fWZ1bmN0aW9uIGcoeCxiLFMsQyl7QzwwJiZ4Lng9PT0xJiYoYVtiXT14LngtMSksUy54PT09MCYmUy56PT09MCYmKGFbYl09Qy8yL01hdGguUEkrLjUpfWZ1bmN0aW9uIF8oeCl7cmV0dXJuIE1hdGguYXRhbjIoeC56LC14LngpfWZ1bmN0aW9uIHkoeCl7cmV0dXJuIE1hdGguYXRhbjIoLXgueSxNYXRoLnNxcnQoeC54KngueCt4LnoqeC56KSl9fXN0YXRpYyBmcm9tSlNPTih0KXtyZXR1cm4gbmV3IHVoKHQudmVydGljZXMsdC5pbmRpY2VzLHQucmFkaXVzLHQuZGV0YWlscyl9fSxIdj1jbGFzcyBleHRlbmRzIHVoe2NvbnN0cnVjdG9yKHQ9MSxyPTApe2xldCBuPSgxK01hdGguc3FydCg1KSkvMixpPTEvbixvPVstMSwtMSwtMSwtMSwtMSwxLC0xLDEsLTEsLTEsMSwxLDEsLTEsLTEsMSwtMSwxLDEsMSwtMSwxLDEsMSwwLC1pLC1uLDAsLWksbiwwLGksLW4sMCxpLG4sLWksLW4sMCwtaSxuLDAsaSwtbiwwLGksbiwwLC1uLDAsLWksbiwwLC1pLC1uLDAsaSxuLDAsaV0sYT1bMywxMSw3LDMsNywxNSwzLDE1LDEzLDcsMTksMTcsNywxNyw2LDcsNiwxNSwxNyw0LDgsMTcsOCwxMCwxNywxMCw2LDgsMCwxNiw4LDE2LDIsOCwyLDEwLDAsMTIsMSwwLDEsMTgsMCwxOCwxNiw2LDEwLDIsNiwyLDEzLDYsMTMsMTUsMiwxNiwxOCwyLDE4LDMsMiwzLDEzLDE4LDEsOSwxOCw5LDExLDE4LDExLDMsNCwxNCwxMiw0LDEyLDAsNCwwLDgsMTEsOSw1LDExLDUsMTksMTEsMTksNywxOSw1LDE0LDE5LDE0LDQsMTksNCwxNywxLDEyLDE0LDEsMTQsNSwxLDUsOV07c3VwZXIobyxhLHQsciksdGhpcy50eXBlPSJEb2RlY2FoZWRyb25HZW9tZXRyeSIsdGhpcy5wYXJhbWV0ZXJzPXtyYWRpdXM6dCxkZXRhaWw6cn19c3RhdGljIGZyb21KU09OKHQpe3JldHVybiBuZXcgSHYodC5yYWRpdXMsdC5kZXRhaWwpfX0sRlY9bmV3IGosQlY9bmV3IGosX3V0PW5ldyBqLEhWPW5ldyBhaSxhNj1jbGFzcyBleHRlbmRzIFBle2NvbnN0cnVjdG9yKHQ9bnVsbCxyPTEpe2lmKHN1cGVyKCksdGhpcy50eXBlPSJFZGdlc0dlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e2dlb21ldHJ5OnQsdGhyZXNob2xkQW5nbGU6cn0sdCE9PW51bGwpe2xldCBpPU1hdGgucG93KDEwLDQpLG89TWF0aC5jb3MoUHYqciksYT10LmdldEluZGV4KCkscz10LmdldEF0dHJpYnV0ZSgicG9zaXRpb24iKSxsPWE/YS5jb3VudDpzLmNvdW50LGM9WzAsMCwwXSx1PVsiYSIsImIiLCJjIl0saD1uZXcgQXJyYXkoMyksZj17fSxwPVtdO2ZvcihsZXQgZD0wO2Q8bDtkKz0zKXthPyhjWzBdPWEuZ2V0WChkKSxjWzFdPWEuZ2V0WChkKzEpLGNbMl09YS5nZXRYKGQrMikpOihjWzBdPWQsY1sxXT1kKzEsY1syXT1kKzIpO2xldHthOmcsYjpfLGM6eX09SFY7aWYoZy5mcm9tQnVmZmVyQXR0cmlidXRlKHMsY1swXSksXy5mcm9tQnVmZmVyQXR0cmlidXRlKHMsY1sxXSkseS5mcm9tQnVmZmVyQXR0cmlidXRlKHMsY1syXSksSFYuZ2V0Tm9ybWFsKF91dCksaFswXT1gJHtNYXRoLnJvdW5kKGcueCppKX0sJHtNYXRoLnJvdW5kKGcueSppKX0sJHtNYXRoLnJvdW5kKGcueippKX1gLGhbMV09YCR7TWF0aC5yb3VuZChfLngqaSl9LCR7TWF0aC5yb3VuZChfLnkqaSl9LCR7TWF0aC5yb3VuZChfLnoqaSl9YCxoWzJdPWAke01hdGgucm91bmQoeS54KmkpfSwke01hdGgucm91bmQoeS55KmkpfSwke01hdGgucm91bmQoeS56KmkpfWAsIShoWzBdPT09aFsxXXx8aFsxXT09PWhbMl18fGhbMl09PT1oWzBdKSlmb3IobGV0IHg9MDt4PDM7eCsrKXtsZXQgYj0oeCsxKSUzLFM9aFt4XSxDPWhbYl0sUD1IVlt1W3hdXSxrPUhWW3VbYl1dLE89YCR7U31fJHtDfWAsRD1gJHtDfV8ke1N9YDtEIGluIGYmJmZbRF0/KF91dC5kb3QoZltEXS5ub3JtYWwpPD1vJiYocC5wdXNoKFAueCxQLnksUC56KSxwLnB1c2goay54LGsueSxrLnopKSxmW0RdPW51bGwpOk8gaW4gZnx8KGZbT109e2luZGV4MDpjW3hdLGluZGV4MTpjW2JdLG5vcm1hbDpfdXQuY2xvbmUoKX0pfX1mb3IobGV0IGQgaW4gZilpZihmW2RdKXtsZXR7aW5kZXgwOmcsaW5kZXgxOl99PWZbZF07RlYuZnJvbUJ1ZmZlckF0dHJpYnV0ZShzLGcpLEJWLmZyb21CdWZmZXJBdHRyaWJ1dGUocyxfKSxwLnB1c2goRlYueCxGVi55LEZWLnopLHAucHVzaChCVi54LEJWLnksQlYueil9dGhpcy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUocCwzKSl9fX0sZnM9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnR5cGU9IkN1cnZlIix0aGlzLmFyY0xlbmd0aERpdmlzaW9ucz0yMDB9Z2V0UG9pbnQoKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5DdXJ2ZTogLmdldFBvaW50KCkgbm90IGltcGxlbWVudGVkLiIpLG51bGx9Z2V0UG9pbnRBdCh0LHIpe2xldCBuPXRoaXMuZ2V0VXRvVG1hcHBpbmcodCk7cmV0dXJuIHRoaXMuZ2V0UG9pbnQobixyKX1nZXRQb2ludHModD01KXtsZXQgcj1bXTtmb3IobGV0IG49MDtuPD10O24rKylyLnB1c2godGhpcy5nZXRQb2ludChuL3QpKTtyZXR1cm4gcn1nZXRTcGFjZWRQb2ludHModD01KXtsZXQgcj1bXTtmb3IobGV0IG49MDtuPD10O24rKylyLnB1c2godGhpcy5nZXRQb2ludEF0KG4vdCkpO3JldHVybiByfWdldExlbmd0aCgpe2xldCB0PXRoaXMuZ2V0TGVuZ3RocygpO3JldHVybiB0W3QubGVuZ3RoLTFdfWdldExlbmd0aHModD10aGlzLmFyY0xlbmd0aERpdmlzaW9ucyl7aWYodGhpcy5jYWNoZUFyY0xlbmd0aHMmJnRoaXMuY2FjaGVBcmNMZW5ndGhzLmxlbmd0aD09PXQrMSYmIXRoaXMubmVlZHNVcGRhdGUpcmV0dXJuIHRoaXMuY2FjaGVBcmNMZW5ndGhzO3RoaXMubmVlZHNVcGRhdGU9ITE7bGV0IHI9W10sbixpPXRoaXMuZ2V0UG9pbnQoMCksbz0wO3IucHVzaCgwKTtmb3IobGV0IGE9MTthPD10O2ErKyluPXRoaXMuZ2V0UG9pbnQoYS90KSxvKz1uLmRpc3RhbmNlVG8oaSksci5wdXNoKG8pLGk9bjtyZXR1cm4gdGhpcy5jYWNoZUFyY0xlbmd0aHM9cixyfXVwZGF0ZUFyY0xlbmd0aHMoKXt0aGlzLm5lZWRzVXBkYXRlPSEwLHRoaXMuZ2V0TGVuZ3RocygpfWdldFV0b1RtYXBwaW5nKHQscil7bGV0IG49dGhpcy5nZXRMZW5ndGhzKCksaT0wLG89bi5sZW5ndGgsYTtyP2E9cjphPXQqbltvLTFdO2xldCBzPTAsbD1vLTEsYztmb3IoO3M8PWw7KWlmKGk9TWF0aC5mbG9vcihzKyhsLXMpLzIpLGM9bltpXS1hLGM8MClzPWkrMTtlbHNlIGlmKGM+MClsPWktMTtlbHNle2w9aTticmVha31pZihpPWwsbltpXT09PWEpcmV0dXJuIGkvKG8tMSk7bGV0IHU9bltpXSxmPW5baSsxXS11LHA9KGEtdSkvZjtyZXR1cm4oaStwKS8oby0xKX1nZXRUYW5nZW50KHQscil7bGV0IGk9dC0xZS00LG89dCsxZS00O2k8MCYmKGk9MCksbz4xJiYobz0xKTtsZXQgYT10aGlzLmdldFBvaW50KGkpLHM9dGhpcy5nZXRQb2ludChvKSxsPXJ8fChhLmlzVmVjdG9yMj9uZXcgTHQ6bmV3IGopO3JldHVybiBsLmNvcHkocykuc3ViKGEpLm5vcm1hbGl6ZSgpLGx9Z2V0VGFuZ2VudEF0KHQscil7bGV0IG49dGhpcy5nZXRVdG9UbWFwcGluZyh0KTtyZXR1cm4gdGhpcy5nZXRUYW5nZW50KG4scil9Y29tcHV0ZUZyZW5ldEZyYW1lcyh0LHIpe2xldCBuPW5ldyBqLGk9W10sbz1bXSxhPVtdLHM9bmV3IGosbD1uZXcgTWU7Zm9yKGxldCBwPTA7cDw9dDtwKyspe2xldCBkPXAvdDtpW3BdPXRoaXMuZ2V0VGFuZ2VudEF0KGQsbmV3IGopfW9bMF09bmV3IGosYVswXT1uZXcgajtsZXQgYz1OdW1iZXIuTUFYX1ZBTFVFLHU9TWF0aC5hYnMoaVswXS54KSxoPU1hdGguYWJzKGlbMF0ueSksZj1NYXRoLmFicyhpWzBdLnopO3U8PWMmJihjPXUsbi5zZXQoMSwwLDApKSxoPD1jJiYoYz1oLG4uc2V0KDAsMSwwKSksZjw9YyYmbi5zZXQoMCwwLDEpLHMuY3Jvc3NWZWN0b3JzKGlbMF0sbikubm9ybWFsaXplKCksb1swXS5jcm9zc1ZlY3RvcnMoaVswXSxzKSxhWzBdLmNyb3NzVmVjdG9ycyhpWzBdLG9bMF0pO2ZvcihsZXQgcD0xO3A8PXQ7cCsrKXtpZihvW3BdPW9bcC0xXS5jbG9uZSgpLGFbcF09YVtwLTFdLmNsb25lKCkscy5jcm9zc1ZlY3RvcnMoaVtwLTFdLGlbcF0pLHMubGVuZ3RoKCk+TnVtYmVyLkVQU0lMT04pe3Mubm9ybWFsaXplKCk7bGV0IGQ9TWF0aC5hY29zKFpvKGlbcC0xXS5kb3QoaVtwXSksLTEsMSkpO29bcF0uYXBwbHlNYXRyaXg0KGwubWFrZVJvdGF0aW9uQXhpcyhzLGQpKX1hW3BdLmNyb3NzVmVjdG9ycyhpW3BdLG9bcF0pfWlmKHI9PT0hMCl7bGV0IHA9TWF0aC5hY29zKFpvKG9bMF0uZG90KG9bdF0pLC0xLDEpKTtwLz10LGlbMF0uZG90KHMuY3Jvc3NWZWN0b3JzKG9bMF0sb1t0XSkpPjAmJihwPS1wKTtmb3IobGV0IGQ9MTtkPD10O2QrKylvW2RdLmFwcGx5TWF0cml4NChsLm1ha2VSb3RhdGlvbkF4aXMoaVtkXSxwKmQpKSxhW2RdLmNyb3NzVmVjdG9ycyhpW2RdLG9bZF0pfXJldHVybnt0YW5nZW50czppLG5vcm1hbHM6byxiaW5vcm1hbHM6YX19Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfWNvcHkodCl7cmV0dXJuIHRoaXMuYXJjTGVuZ3RoRGl2aXNpb25zPXQuYXJjTGVuZ3RoRGl2aXNpb25zLHRoaXN9dG9KU09OKCl7bGV0IHQ9e21ldGFkYXRhOnt2ZXJzaW9uOjQuNSx0eXBlOiJDdXJ2ZSIsZ2VuZXJhdG9yOiJDdXJ2ZS50b0pTT04ifX07cmV0dXJuIHQuYXJjTGVuZ3RoRGl2aXNpb25zPXRoaXMuYXJjTGVuZ3RoRGl2aXNpb25zLHQudHlwZT10aGlzLnR5cGUsdH1mcm9tSlNPTih0KXtyZXR1cm4gdGhpcy5hcmNMZW5ndGhEaXZpc2lvbnM9dC5hcmNMZW5ndGhEaXZpc2lvbnMsdGhpc319LFZ2PWNsYXNzIGV4dGVuZHMgZnN7Y29uc3RydWN0b3IodD0wLHI9MCxuPTEsaT0xLG89MCxhPU1hdGguUEkqMixzPSExLGw9MCl7c3VwZXIoKSx0aGlzLnR5cGU9IkVsbGlwc2VDdXJ2ZSIsdGhpcy5hWD10LHRoaXMuYVk9cix0aGlzLnhSYWRpdXM9bix0aGlzLnlSYWRpdXM9aSx0aGlzLmFTdGFydEFuZ2xlPW8sdGhpcy5hRW5kQW5nbGU9YSx0aGlzLmFDbG9ja3dpc2U9cyx0aGlzLmFSb3RhdGlvbj1sfWdldFBvaW50KHQscil7bGV0IG49cnx8bmV3IEx0LGk9TWF0aC5QSSoyLG89dGhpcy5hRW5kQW5nbGUtdGhpcy5hU3RhcnRBbmdsZSxhPU1hdGguYWJzKG8pPE51bWJlci5FUFNJTE9OO2Zvcig7bzwwOylvKz1pO2Zvcig7bz5pOylvLT1pO288TnVtYmVyLkVQU0lMT04mJihhP289MDpvPWkpLHRoaXMuYUNsb2Nrd2lzZT09PSEwJiYhYSYmKG89PT1pP289LWk6bz1vLWkpO2xldCBzPXRoaXMuYVN0YXJ0QW5nbGUrdCpvLGw9dGhpcy5hWCt0aGlzLnhSYWRpdXMqTWF0aC5jb3MocyksYz10aGlzLmFZK3RoaXMueVJhZGl1cypNYXRoLnNpbihzKTtpZih0aGlzLmFSb3RhdGlvbiE9PTApe2xldCB1PU1hdGguY29zKHRoaXMuYVJvdGF0aW9uKSxoPU1hdGguc2luKHRoaXMuYVJvdGF0aW9uKSxmPWwtdGhpcy5hWCxwPWMtdGhpcy5hWTtsPWYqdS1wKmgrdGhpcy5hWCxjPWYqaCtwKnUrdGhpcy5hWX1yZXR1cm4gbi5zZXQobCxjKX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuYVg9dC5hWCx0aGlzLmFZPXQuYVksdGhpcy54UmFkaXVzPXQueFJhZGl1cyx0aGlzLnlSYWRpdXM9dC55UmFkaXVzLHRoaXMuYVN0YXJ0QW5nbGU9dC5hU3RhcnRBbmdsZSx0aGlzLmFFbmRBbmdsZT10LmFFbmRBbmdsZSx0aGlzLmFDbG9ja3dpc2U9dC5hQ2xvY2t3aXNlLHRoaXMuYVJvdGF0aW9uPXQuYVJvdGF0aW9uLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIHQuYVg9dGhpcy5hWCx0LmFZPXRoaXMuYVksdC54UmFkaXVzPXRoaXMueFJhZGl1cyx0LnlSYWRpdXM9dGhpcy55UmFkaXVzLHQuYVN0YXJ0QW5nbGU9dGhpcy5hU3RhcnRBbmdsZSx0LmFFbmRBbmdsZT10aGlzLmFFbmRBbmdsZSx0LmFDbG9ja3dpc2U9dGhpcy5hQ2xvY2t3aXNlLHQuYVJvdGF0aW9uPXRoaXMuYVJvdGF0aW9uLHR9ZnJvbUpTT04odCl7cmV0dXJuIHN1cGVyLmZyb21KU09OKHQpLHRoaXMuYVg9dC5hWCx0aGlzLmFZPXQuYVksdGhpcy54UmFkaXVzPXQueFJhZGl1cyx0aGlzLnlSYWRpdXM9dC55UmFkaXVzLHRoaXMuYVN0YXJ0QW5nbGU9dC5hU3RhcnRBbmdsZSx0aGlzLmFFbmRBbmdsZT10LmFFbmRBbmdsZSx0aGlzLmFDbG9ja3dpc2U9dC5hQ2xvY2t3aXNlLHRoaXMuYVJvdGF0aW9uPXQuYVJvdGF0aW9uLHRoaXN9fTtWdi5wcm90b3R5cGUuaXNFbGxpcHNlQ3VydmU9ITA7dmFyIHM2PWNsYXNzIGV4dGVuZHMgVnZ7Y29uc3RydWN0b3IodCxyLG4saSxvLGEpe3N1cGVyKHQscixuLG4saSxvLGEpLHRoaXMudHlwZT0iQXJjQ3VydmUifX07czYucHJvdG90eXBlLmlzQXJjQ3VydmU9ITA7ZnVuY3Rpb24gT2h0KCl7bGV0IGU9MCx0PTAscj0wLG49MDtmdW5jdGlvbiBpKG8sYSxzLGwpe2U9byx0PXMscj0tMypvKzMqYS0yKnMtbCxuPTIqby0yKmErcytsfXJldHVybntpbml0Q2F0bXVsbFJvbTpmdW5jdGlvbihvLGEscyxsLGMpe2koYSxzLGMqKHMtbyksYyoobC1hKSl9LGluaXROb251bmlmb3JtQ2F0bXVsbFJvbTpmdW5jdGlvbihvLGEscyxsLGMsdSxoKXtsZXQgZj0oYS1vKS9jLShzLW8pLyhjK3UpKyhzLWEpL3UscD0ocy1hKS91LShsLWEpLyh1K2gpKyhsLXMpL2g7Zio9dSxwKj11LGkoYSxzLGYscCl9LGNhbGM6ZnVuY3Rpb24obyl7bGV0IGE9bypvLHM9YSpvO3JldHVybiBlK3QqbytyKmErbipzfX19dmFyIFZWPW5ldyBqLHl1dD1uZXcgT2h0LHZ1dD1uZXcgT2h0LHh1dD1uZXcgT2h0LGw2PWNsYXNzIGV4dGVuZHMgZnN7Y29uc3RydWN0b3IodD1bXSxyPSExLG49ImNlbnRyaXBldGFsIixpPS41KXtzdXBlcigpLHRoaXMudHlwZT0iQ2F0bXVsbFJvbUN1cnZlMyIsdGhpcy5wb2ludHM9dCx0aGlzLmNsb3NlZD1yLHRoaXMuY3VydmVUeXBlPW4sdGhpcy50ZW5zaW9uPWl9Z2V0UG9pbnQodCxyPW5ldyBqKXtsZXQgbj1yLGk9dGhpcy5wb2ludHMsbz1pLmxlbmd0aCxhPShvLSh0aGlzLmNsb3NlZD8wOjEpKSp0LHM9TWF0aC5mbG9vcihhKSxsPWEtczt0aGlzLmNsb3NlZD9zKz1zPjA/MDooTWF0aC5mbG9vcihNYXRoLmFicyhzKS9vKSsxKSpvOmw9PT0wJiZzPT09by0xJiYocz1vLTIsbD0xKTtsZXQgYyx1O3RoaXMuY2xvc2VkfHxzPjA/Yz1pWyhzLTEpJW9dOihWVi5zdWJWZWN0b3JzKGlbMF0saVsxXSkuYWRkKGlbMF0pLGM9VlYpO2xldCBoPWlbcyVvXSxmPWlbKHMrMSklb107aWYodGhpcy5jbG9zZWR8fHMrMjxvP3U9aVsocysyKSVvXTooVlYuc3ViVmVjdG9ycyhpW28tMV0saVtvLTJdKS5hZGQoaVtvLTFdKSx1PVZWKSx0aGlzLmN1cnZlVHlwZT09PSJjZW50cmlwZXRhbCJ8fHRoaXMuY3VydmVUeXBlPT09ImNob3JkYWwiKXtsZXQgcD10aGlzLmN1cnZlVHlwZT09PSJjaG9yZGFsIj8uNTouMjUsZD1NYXRoLnBvdyhjLmRpc3RhbmNlVG9TcXVhcmVkKGgpLHApLGc9TWF0aC5wb3coaC5kaXN0YW5jZVRvU3F1YXJlZChmKSxwKSxfPU1hdGgucG93KGYuZGlzdGFuY2VUb1NxdWFyZWQodSkscCk7ZzwxZS00JiYoZz0xKSxkPDFlLTQmJihkPWcpLF88MWUtNCYmKF89ZykseXV0LmluaXROb251bmlmb3JtQ2F0bXVsbFJvbShjLngsaC54LGYueCx1LngsZCxnLF8pLHZ1dC5pbml0Tm9udW5pZm9ybUNhdG11bGxSb20oYy55LGgueSxmLnksdS55LGQsZyxfKSx4dXQuaW5pdE5vbnVuaWZvcm1DYXRtdWxsUm9tKGMueixoLnosZi56LHUueixkLGcsXyl9ZWxzZSB0aGlzLmN1cnZlVHlwZT09PSJjYXRtdWxscm9tIiYmKHl1dC5pbml0Q2F0bXVsbFJvbShjLngsaC54LGYueCx1LngsdGhpcy50ZW5zaW9uKSx2dXQuaW5pdENhdG11bGxSb20oYy55LGgueSxmLnksdS55LHRoaXMudGVuc2lvbikseHV0LmluaXRDYXRtdWxsUm9tKGMueixoLnosZi56LHUueix0aGlzLnRlbnNpb24pKTtyZXR1cm4gbi5zZXQoeXV0LmNhbGMobCksdnV0LmNhbGMobCkseHV0LmNhbGMobCkpLG59Y29weSh0KXtzdXBlci5jb3B5KHQpLHRoaXMucG9pbnRzPVtdO2ZvcihsZXQgcj0wLG49dC5wb2ludHMubGVuZ3RoO3I8bjtyKyspe2xldCBpPXQucG9pbnRzW3JdO3RoaXMucG9pbnRzLnB1c2goaS5jbG9uZSgpKX1yZXR1cm4gdGhpcy5jbG9zZWQ9dC5jbG9zZWQsdGhpcy5jdXJ2ZVR5cGU9dC5jdXJ2ZVR5cGUsdGhpcy50ZW5zaW9uPXQudGVuc2lvbix0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3QucG9pbnRzPVtdO2ZvcihsZXQgcj0wLG49dGhpcy5wb2ludHMubGVuZ3RoO3I8bjtyKyspe2xldCBpPXRoaXMucG9pbnRzW3JdO3QucG9pbnRzLnB1c2goaS50b0FycmF5KCkpfXJldHVybiB0LmNsb3NlZD10aGlzLmNsb3NlZCx0LmN1cnZlVHlwZT10aGlzLmN1cnZlVHlwZSx0LnRlbnNpb249dGhpcy50ZW5zaW9uLHR9ZnJvbUpTT04odCl7c3VwZXIuZnJvbUpTT04odCksdGhpcy5wb2ludHM9W107Zm9yKGxldCByPTAsbj10LnBvaW50cy5sZW5ndGg7cjxuO3IrKyl7bGV0IGk9dC5wb2ludHNbcl07dGhpcy5wb2ludHMucHVzaChuZXcgaigpLmZyb21BcnJheShpKSl9cmV0dXJuIHRoaXMuY2xvc2VkPXQuY2xvc2VkLHRoaXMuY3VydmVUeXBlPXQuY3VydmVUeXBlLHRoaXMudGVuc2lvbj10LnRlbnNpb24sdGhpc319O2w2LnByb3RvdHlwZS5pc0NhdG11bGxSb21DdXJ2ZTM9ITA7ZnVuY3Rpb24gY2hlKGUsdCxyLG4saSl7bGV0IG89KG4tdCkqLjUsYT0oaS1yKSouNSxzPWUqZSxsPWUqcztyZXR1cm4oMipyLTIqbitvK2EpKmwrKC0zKnIrMypuLTIqby1hKSpzK28qZStyfWZ1bmN0aW9uIGtncihlLHQpe2xldCByPTEtZTtyZXR1cm4gcipyKnR9ZnVuY3Rpb24gUmdyKGUsdCl7cmV0dXJuIDIqKDEtZSkqZSp0fWZ1bmN0aW9uIE5ncihlLHQpe3JldHVybiBlKmUqdH1mdW5jdGlvbiBxUChlLHQscixuKXtyZXR1cm4ga2dyKGUsdCkrUmdyKGUscikrTmdyKGUsbil9ZnVuY3Rpb24gRGdyKGUsdCl7bGV0IHI9MS1lO3JldHVybiByKnIqcip0fWZ1bmN0aW9uIE9ncihlLHQpe2xldCByPTEtZTtyZXR1cm4gMypyKnIqZSp0fWZ1bmN0aW9uIHpncihlLHQpe3JldHVybiAzKigxLWUpKmUqZSp0fWZ1bmN0aW9uIEZncihlLHQpe3JldHVybiBlKmUqZSp0fWZ1bmN0aW9uIEdQKGUsdCxyLG4saSl7cmV0dXJuIERncihlLHQpK09ncihlLHIpK3pncihlLG4pK0ZncihlLGkpfXZhciBjTT1jbGFzcyBleHRlbmRzIGZze2NvbnN0cnVjdG9yKHQ9bmV3IEx0LHI9bmV3IEx0LG49bmV3IEx0LGk9bmV3IEx0KXtzdXBlcigpLHRoaXMudHlwZT0iQ3ViaWNCZXppZXJDdXJ2ZSIsdGhpcy52MD10LHRoaXMudjE9cix0aGlzLnYyPW4sdGhpcy52Mz1pfWdldFBvaW50KHQscj1uZXcgTHQpe2xldCBuPXIsaT10aGlzLnYwLG89dGhpcy52MSxhPXRoaXMudjIscz10aGlzLnYzO3JldHVybiBuLnNldChHUCh0LGkueCxvLngsYS54LHMueCksR1AodCxpLnksby55LGEueSxzLnkpKSxufWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy52MC5jb3B5KHQudjApLHRoaXMudjEuY29weSh0LnYxKSx0aGlzLnYyLmNvcHkodC52MiksdGhpcy52My5jb3B5KHQudjMpLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIHQudjA9dGhpcy52MC50b0FycmF5KCksdC52MT10aGlzLnYxLnRvQXJyYXkoKSx0LnYyPXRoaXMudjIudG9BcnJheSgpLHQudjM9dGhpcy52My50b0FycmF5KCksdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy52MC5mcm9tQXJyYXkodC52MCksdGhpcy52MS5mcm9tQXJyYXkodC52MSksdGhpcy52Mi5mcm9tQXJyYXkodC52MiksdGhpcy52My5mcm9tQXJyYXkodC52MyksdGhpc319O2NNLnByb3RvdHlwZS5pc0N1YmljQmV6aWVyQ3VydmU9ITA7dmFyIGM2PWNsYXNzIGV4dGVuZHMgZnN7Y29uc3RydWN0b3IodD1uZXcgaixyPW5ldyBqLG49bmV3IGosaT1uZXcgail7c3VwZXIoKSx0aGlzLnR5cGU9IkN1YmljQmV6aWVyQ3VydmUzIix0aGlzLnYwPXQsdGhpcy52MT1yLHRoaXMudjI9bix0aGlzLnYzPWl9Z2V0UG9pbnQodCxyPW5ldyBqKXtsZXQgbj1yLGk9dGhpcy52MCxvPXRoaXMudjEsYT10aGlzLnYyLHM9dGhpcy52MztyZXR1cm4gbi5zZXQoR1AodCxpLngsby54LGEueCxzLngpLEdQKHQsaS55LG8ueSxhLnkscy55KSxHUCh0LGkueixvLnosYS56LHMueikpLG59Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLnYwLmNvcHkodC52MCksdGhpcy52MS5jb3B5KHQudjEpLHRoaXMudjIuY29weSh0LnYyKSx0aGlzLnYzLmNvcHkodC52MyksdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC52MD10aGlzLnYwLnRvQXJyYXkoKSx0LnYxPXRoaXMudjEudG9BcnJheSgpLHQudjI9dGhpcy52Mi50b0FycmF5KCksdC52Mz10aGlzLnYzLnRvQXJyYXkoKSx0fWZyb21KU09OKHQpe3JldHVybiBzdXBlci5mcm9tSlNPTih0KSx0aGlzLnYwLmZyb21BcnJheSh0LnYwKSx0aGlzLnYxLmZyb21BcnJheSh0LnYxKSx0aGlzLnYyLmZyb21BcnJheSh0LnYyKSx0aGlzLnYzLmZyb21BcnJheSh0LnYzKSx0aGlzfX07YzYucHJvdG90eXBlLmlzQ3ViaWNCZXppZXJDdXJ2ZTM9ITA7dmFyIFV2PWNsYXNzIGV4dGVuZHMgZnN7Y29uc3RydWN0b3IodD1uZXcgTHQscj1uZXcgTHQpe3N1cGVyKCksdGhpcy50eXBlPSJMaW5lQ3VydmUiLHRoaXMudjE9dCx0aGlzLnYyPXJ9Z2V0UG9pbnQodCxyPW5ldyBMdCl7bGV0IG49cjtyZXR1cm4gdD09PTE/bi5jb3B5KHRoaXMudjIpOihuLmNvcHkodGhpcy52Mikuc3ViKHRoaXMudjEpLG4ubXVsdGlwbHlTY2FsYXIodCkuYWRkKHRoaXMudjEpKSxufWdldFBvaW50QXQodCxyKXtyZXR1cm4gdGhpcy5nZXRQb2ludCh0LHIpfWdldFRhbmdlbnQodCxyKXtsZXQgbj1yfHxuZXcgTHQ7cmV0dXJuIG4uY29weSh0aGlzLnYyKS5zdWIodGhpcy52MSkubm9ybWFsaXplKCksbn1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMudjEuY29weSh0LnYxKSx0aGlzLnYyLmNvcHkodC52MiksdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC52MT10aGlzLnYxLnRvQXJyYXkoKSx0LnYyPXRoaXMudjIudG9BcnJheSgpLHR9ZnJvbUpTT04odCl7cmV0dXJuIHN1cGVyLmZyb21KU09OKHQpLHRoaXMudjEuZnJvbUFycmF5KHQudjEpLHRoaXMudjIuZnJvbUFycmF5KHQudjIpLHRoaXN9fTtVdi5wcm90b3R5cGUuaXNMaW5lQ3VydmU9ITA7dmFyIHhVPWNsYXNzIGV4dGVuZHMgZnN7Y29uc3RydWN0b3IodD1uZXcgaixyPW5ldyBqKXtzdXBlcigpLHRoaXMudHlwZT0iTGluZUN1cnZlMyIsdGhpcy5pc0xpbmVDdXJ2ZTM9ITAsdGhpcy52MT10LHRoaXMudjI9cn1nZXRQb2ludCh0LHI9bmV3IGope2xldCBuPXI7cmV0dXJuIHQ9PT0xP24uY29weSh0aGlzLnYyKToobi5jb3B5KHRoaXMudjIpLnN1Yih0aGlzLnYxKSxuLm11bHRpcGx5U2NhbGFyKHQpLmFkZCh0aGlzLnYxKSksbn1nZXRQb2ludEF0KHQscil7cmV0dXJuIHRoaXMuZ2V0UG9pbnQodCxyKX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMudjEuY29weSh0LnYxKSx0aGlzLnYyLmNvcHkodC52MiksdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC52MT10aGlzLnYxLnRvQXJyYXkoKSx0LnYyPXRoaXMudjIudG9BcnJheSgpLHR9ZnJvbUpTT04odCl7cmV0dXJuIHN1cGVyLmZyb21KU09OKHQpLHRoaXMudjEuZnJvbUFycmF5KHQudjEpLHRoaXMudjIuZnJvbUFycmF5KHQudjIpLHRoaXN9fSx1TT1jbGFzcyBleHRlbmRzIGZze2NvbnN0cnVjdG9yKHQ9bmV3IEx0LHI9bmV3IEx0LG49bmV3IEx0KXtzdXBlcigpLHRoaXMudHlwZT0iUXVhZHJhdGljQmV6aWVyQ3VydmUiLHRoaXMudjA9dCx0aGlzLnYxPXIsdGhpcy52Mj1ufWdldFBvaW50KHQscj1uZXcgTHQpe2xldCBuPXIsaT10aGlzLnYwLG89dGhpcy52MSxhPXRoaXMudjI7cmV0dXJuIG4uc2V0KHFQKHQsaS54LG8ueCxhLngpLHFQKHQsaS55LG8ueSxhLnkpKSxufWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy52MC5jb3B5KHQudjApLHRoaXMudjEuY29weSh0LnYxKSx0aGlzLnYyLmNvcHkodC52MiksdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC52MD10aGlzLnYwLnRvQXJyYXkoKSx0LnYxPXRoaXMudjEudG9BcnJheSgpLHQudjI9dGhpcy52Mi50b0FycmF5KCksdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy52MC5mcm9tQXJyYXkodC52MCksdGhpcy52MS5mcm9tQXJyYXkodC52MSksdGhpcy52Mi5mcm9tQXJyYXkodC52MiksdGhpc319O3VNLnByb3RvdHlwZS5pc1F1YWRyYXRpY0JlemllckN1cnZlPSEwO3ZhciBoTT1jbGFzcyBleHRlbmRzIGZze2NvbnN0cnVjdG9yKHQ9bmV3IGoscj1uZXcgaixuPW5ldyBqKXtzdXBlcigpLHRoaXMudHlwZT0iUXVhZHJhdGljQmV6aWVyQ3VydmUzIix0aGlzLnYwPXQsdGhpcy52MT1yLHRoaXMudjI9bn1nZXRQb2ludCh0LHI9bmV3IGope2xldCBuPXIsaT10aGlzLnYwLG89dGhpcy52MSxhPXRoaXMudjI7cmV0dXJuIG4uc2V0KHFQKHQsaS54LG8ueCxhLngpLHFQKHQsaS55LG8ueSxhLnkpLHFQKHQsaS56LG8ueixhLnopKSxufWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy52MC5jb3B5KHQudjApLHRoaXMudjEuY29weSh0LnYxKSx0aGlzLnYyLmNvcHkodC52MiksdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC52MD10aGlzLnYwLnRvQXJyYXkoKSx0LnYxPXRoaXMudjEudG9BcnJheSgpLHQudjI9dGhpcy52Mi50b0FycmF5KCksdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy52MC5mcm9tQXJyYXkodC52MCksdGhpcy52MS5mcm9tQXJyYXkodC52MSksdGhpcy52Mi5mcm9tQXJyYXkodC52MiksdGhpc319O2hNLnByb3RvdHlwZS5pc1F1YWRyYXRpY0JlemllckN1cnZlMz0hMDt2YXIgZk09Y2xhc3MgZXh0ZW5kcyBmc3tjb25zdHJ1Y3Rvcih0PVtdKXtzdXBlcigpLHRoaXMudHlwZT0iU3BsaW5lQ3VydmUiLHRoaXMucG9pbnRzPXR9Z2V0UG9pbnQodCxyPW5ldyBMdCl7bGV0IG49cixpPXRoaXMucG9pbnRzLG89KGkubGVuZ3RoLTEpKnQsYT1NYXRoLmZsb29yKG8pLHM9by1hLGw9aVthPT09MD9hOmEtMV0sYz1pW2FdLHU9aVthPmkubGVuZ3RoLTI/aS5sZW5ndGgtMTphKzFdLGg9aVthPmkubGVuZ3RoLTM/aS5sZW5ndGgtMTphKzJdO3JldHVybiBuLnNldChjaGUocyxsLngsYy54LHUueCxoLngpLGNoZShzLGwueSxjLnksdS55LGgueSkpLG59Y29weSh0KXtzdXBlci5jb3B5KHQpLHRoaXMucG9pbnRzPVtdO2ZvcihsZXQgcj0wLG49dC5wb2ludHMubGVuZ3RoO3I8bjtyKyspe2xldCBpPXQucG9pbnRzW3JdO3RoaXMucG9pbnRzLnB1c2goaS5jbG9uZSgpKX1yZXR1cm4gdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTt0LnBvaW50cz1bXTtmb3IobGV0IHI9MCxuPXRoaXMucG9pbnRzLmxlbmd0aDtyPG47cisrKXtsZXQgaT10aGlzLnBvaW50c1tyXTt0LnBvaW50cy5wdXNoKGkudG9BcnJheSgpKX1yZXR1cm4gdH1mcm9tSlNPTih0KXtzdXBlci5mcm9tSlNPTih0KSx0aGlzLnBvaW50cz1bXTtmb3IobGV0IHI9MCxuPXQucG9pbnRzLmxlbmd0aDtyPG47cisrKXtsZXQgaT10LnBvaW50c1tyXTt0aGlzLnBvaW50cy5wdXNoKG5ldyBMdCgpLmZyb21BcnJheShpKSl9cmV0dXJuIHRoaXN9fTtmTS5wcm90b3R5cGUuaXNTcGxpbmVDdXJ2ZT0hMDt2YXIgemh0PU9iamVjdC5mcmVlemUoe19fcHJvdG9fXzpudWxsLEFyY0N1cnZlOnM2LENhdG11bGxSb21DdXJ2ZTM6bDYsQ3ViaWNCZXppZXJDdXJ2ZTpjTSxDdWJpY0JlemllckN1cnZlMzpjNixFbGxpcHNlQ3VydmU6VnYsTGluZUN1cnZlOlV2LExpbmVDdXJ2ZTM6eFUsUXVhZHJhdGljQmV6aWVyQ3VydmU6dU0sUXVhZHJhdGljQmV6aWVyQ3VydmUzOmhNLFNwbGluZUN1cnZlOmZNfSksYlU9Y2xhc3MgZXh0ZW5kcyBmc3tjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy50eXBlPSJDdXJ2ZVBhdGgiLHRoaXMuY3VydmVzPVtdLHRoaXMuYXV0b0Nsb3NlPSExfWFkZCh0KXt0aGlzLmN1cnZlcy5wdXNoKHQpfWNsb3NlUGF0aCgpe2xldCB0PXRoaXMuY3VydmVzWzBdLmdldFBvaW50KDApLHI9dGhpcy5jdXJ2ZXNbdGhpcy5jdXJ2ZXMubGVuZ3RoLTFdLmdldFBvaW50KDEpO3QuZXF1YWxzKHIpfHx0aGlzLmN1cnZlcy5wdXNoKG5ldyBVdihyLHQpKX1nZXRQb2ludCh0LHIpe2xldCBuPXQqdGhpcy5nZXRMZW5ndGgoKSxpPXRoaXMuZ2V0Q3VydmVMZW5ndGhzKCksbz0wO2Zvcig7bzxpLmxlbmd0aDspe2lmKGlbb10+PW4pe2xldCBhPWlbb10tbixzPXRoaXMuY3VydmVzW29dLGw9cy5nZXRMZW5ndGgoKSxjPWw9PT0wPzA6MS1hL2w7cmV0dXJuIHMuZ2V0UG9pbnRBdChjLHIpfW8rK31yZXR1cm4gbnVsbH1nZXRMZW5ndGgoKXtsZXQgdD10aGlzLmdldEN1cnZlTGVuZ3RocygpO3JldHVybiB0W3QubGVuZ3RoLTFdfXVwZGF0ZUFyY0xlbmd0aHMoKXt0aGlzLm5lZWRzVXBkYXRlPSEwLHRoaXMuY2FjaGVMZW5ndGhzPW51bGwsdGhpcy5nZXRDdXJ2ZUxlbmd0aHMoKX1nZXRDdXJ2ZUxlbmd0aHMoKXtpZih0aGlzLmNhY2hlTGVuZ3RocyYmdGhpcy5jYWNoZUxlbmd0aHMubGVuZ3RoPT09dGhpcy5jdXJ2ZXMubGVuZ3RoKXJldHVybiB0aGlzLmNhY2hlTGVuZ3RocztsZXQgdD1bXSxyPTA7Zm9yKGxldCBuPTAsaT10aGlzLmN1cnZlcy5sZW5ndGg7bjxpO24rKylyKz10aGlzLmN1cnZlc1tuXS5nZXRMZW5ndGgoKSx0LnB1c2gocik7cmV0dXJuIHRoaXMuY2FjaGVMZW5ndGhzPXQsdH1nZXRTcGFjZWRQb2ludHModD00MCl7bGV0IHI9W107Zm9yKGxldCBuPTA7bjw9dDtuKyspci5wdXNoKHRoaXMuZ2V0UG9pbnQobi90KSk7cmV0dXJuIHRoaXMuYXV0b0Nsb3NlJiZyLnB1c2goclswXSkscn1nZXRQb2ludHModD0xMil7bGV0IHI9W10sbjtmb3IobGV0IGk9MCxvPXRoaXMuY3VydmVzO2k8by5sZW5ndGg7aSsrKXtsZXQgYT1vW2ldLHM9YSYmYS5pc0VsbGlwc2VDdXJ2ZT90KjI6YSYmKGEuaXNMaW5lQ3VydmV8fGEuaXNMaW5lQ3VydmUzKT8xOmEmJmEuaXNTcGxpbmVDdXJ2ZT90KmEucG9pbnRzLmxlbmd0aDp0LGw9YS5nZXRQb2ludHMocyk7Zm9yKGxldCBjPTA7YzxsLmxlbmd0aDtjKyspe2xldCB1PWxbY107biYmbi5lcXVhbHModSl8fChyLnB1c2godSksbj11KX19cmV0dXJuIHRoaXMuYXV0b0Nsb3NlJiZyLmxlbmd0aD4xJiYhcltyLmxlbmd0aC0xXS5lcXVhbHMoclswXSkmJnIucHVzaChyWzBdKSxyfWNvcHkodCl7c3VwZXIuY29weSh0KSx0aGlzLmN1cnZlcz1bXTtmb3IobGV0IHI9MCxuPXQuY3VydmVzLmxlbmd0aDtyPG47cisrKXtsZXQgaT10LmN1cnZlc1tyXTt0aGlzLmN1cnZlcy5wdXNoKGkuY2xvbmUoKSl9cmV0dXJuIHRoaXMuYXV0b0Nsb3NlPXQuYXV0b0Nsb3NlLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7dC5hdXRvQ2xvc2U9dGhpcy5hdXRvQ2xvc2UsdC5jdXJ2ZXM9W107Zm9yKGxldCByPTAsbj10aGlzLmN1cnZlcy5sZW5ndGg7cjxuO3IrKyl7bGV0IGk9dGhpcy5jdXJ2ZXNbcl07dC5jdXJ2ZXMucHVzaChpLnRvSlNPTigpKX1yZXR1cm4gdH1mcm9tSlNPTih0KXtzdXBlci5mcm9tSlNPTih0KSx0aGlzLmF1dG9DbG9zZT10LmF1dG9DbG9zZSx0aGlzLmN1cnZlcz1bXTtmb3IobGV0IHI9MCxuPXQuY3VydmVzLmxlbmd0aDtyPG47cisrKXtsZXQgaT10LmN1cnZlc1tyXTt0aGlzLmN1cnZlcy5wdXNoKG5ldyB6aHRbaS50eXBlXSgpLmZyb21KU09OKGkpKX1yZXR1cm4gdGhpc319LHF2PWNsYXNzIGV4dGVuZHMgYlV7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9IlBhdGgiLHRoaXMuY3VycmVudFBvaW50PW5ldyBMdCx0JiZ0aGlzLnNldEZyb21Qb2ludHModCl9c2V0RnJvbVBvaW50cyh0KXt0aGlzLm1vdmVUbyh0WzBdLngsdFswXS55KTtmb3IobGV0IHI9MSxuPXQubGVuZ3RoO3I8bjtyKyspdGhpcy5saW5lVG8odFtyXS54LHRbcl0ueSk7cmV0dXJuIHRoaXN9bW92ZVRvKHQscil7cmV0dXJuIHRoaXMuY3VycmVudFBvaW50LnNldCh0LHIpLHRoaXN9bGluZVRvKHQscil7bGV0IG49bmV3IFV2KHRoaXMuY3VycmVudFBvaW50LmNsb25lKCksbmV3IEx0KHQscikpO3JldHVybiB0aGlzLmN1cnZlcy5wdXNoKG4pLHRoaXMuY3VycmVudFBvaW50LnNldCh0LHIpLHRoaXN9cXVhZHJhdGljQ3VydmVUbyh0LHIsbixpKXtsZXQgbz1uZXcgdU0odGhpcy5jdXJyZW50UG9pbnQuY2xvbmUoKSxuZXcgTHQodCxyKSxuZXcgTHQobixpKSk7cmV0dXJuIHRoaXMuY3VydmVzLnB1c2gobyksdGhpcy5jdXJyZW50UG9pbnQuc2V0KG4saSksdGhpc31iZXppZXJDdXJ2ZVRvKHQscixuLGksbyxhKXtsZXQgcz1uZXcgY00odGhpcy5jdXJyZW50UG9pbnQuY2xvbmUoKSxuZXcgTHQodCxyKSxuZXcgTHQobixpKSxuZXcgTHQobyxhKSk7cmV0dXJuIHRoaXMuY3VydmVzLnB1c2gocyksdGhpcy5jdXJyZW50UG9pbnQuc2V0KG8sYSksdGhpc31zcGxpbmVUaHJ1KHQpe2xldCByPVt0aGlzLmN1cnJlbnRQb2ludC5jbG9uZSgpXS5jb25jYXQodCksbj1uZXcgZk0ocik7cmV0dXJuIHRoaXMuY3VydmVzLnB1c2gobiksdGhpcy5jdXJyZW50UG9pbnQuY29weSh0W3QubGVuZ3RoLTFdKSx0aGlzfWFyYyh0LHIsbixpLG8sYSl7bGV0IHM9dGhpcy5jdXJyZW50UG9pbnQueCxsPXRoaXMuY3VycmVudFBvaW50Lnk7cmV0dXJuIHRoaXMuYWJzYXJjKHQrcyxyK2wsbixpLG8sYSksdGhpc31hYnNhcmModCxyLG4saSxvLGEpe3JldHVybiB0aGlzLmFic2VsbGlwc2UodCxyLG4sbixpLG8sYSksdGhpc31lbGxpcHNlKHQscixuLGksbyxhLHMsbCl7bGV0IGM9dGhpcy5jdXJyZW50UG9pbnQueCx1PXRoaXMuY3VycmVudFBvaW50Lnk7cmV0dXJuIHRoaXMuYWJzZWxsaXBzZSh0K2Mscit1LG4saSxvLGEscyxsKSx0aGlzfWFic2VsbGlwc2UodCxyLG4saSxvLGEscyxsKXtsZXQgYz1uZXcgVnYodCxyLG4saSxvLGEscyxsKTtpZih0aGlzLmN1cnZlcy5sZW5ndGg+MCl7bGV0IGg9Yy5nZXRQb2ludCgwKTtoLmVxdWFscyh0aGlzLmN1cnJlbnRQb2ludCl8fHRoaXMubGluZVRvKGgueCxoLnkpfXRoaXMuY3VydmVzLnB1c2goYyk7bGV0IHU9Yy5nZXRQb2ludCgxKTtyZXR1cm4gdGhpcy5jdXJyZW50UG9pbnQuY29weSh1KSx0aGlzfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jdXJyZW50UG9pbnQuY29weSh0LmN1cnJlbnRQb2ludCksdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC5jdXJyZW50UG9pbnQ9dGhpcy5jdXJyZW50UG9pbnQudG9BcnJheSgpLHR9ZnJvbUpTT04odCl7cmV0dXJuIHN1cGVyLmZyb21KU09OKHQpLHRoaXMuY3VycmVudFBvaW50LmZyb21BcnJheSh0LmN1cnJlbnRQb2ludCksdGhpc319LEtjPWNsYXNzIGV4dGVuZHMgcXZ7Y29uc3RydWN0b3IodCl7c3VwZXIodCksdGhpcy51dWlkPU5sKCksdGhpcy50eXBlPSJTaGFwZSIsdGhpcy5ob2xlcz1bXX1nZXRQb2ludHNIb2xlcyh0KXtsZXQgcj1bXTtmb3IobGV0IG49MCxpPXRoaXMuaG9sZXMubGVuZ3RoO248aTtuKyspcltuXT10aGlzLmhvbGVzW25dLmdldFBvaW50cyh0KTtyZXR1cm4gcn1leHRyYWN0UG9pbnRzKHQpe3JldHVybntzaGFwZTp0aGlzLmdldFBvaW50cyh0KSxob2xlczp0aGlzLmdldFBvaW50c0hvbGVzKHQpfX1jb3B5KHQpe3N1cGVyLmNvcHkodCksdGhpcy5ob2xlcz1bXTtmb3IobGV0IHI9MCxuPXQuaG9sZXMubGVuZ3RoO3I8bjtyKyspe2xldCBpPXQuaG9sZXNbcl07dGhpcy5ob2xlcy5wdXNoKGkuY2xvbmUoKSl9cmV0dXJuIHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7dC51dWlkPXRoaXMudXVpZCx0LmhvbGVzPVtdO2ZvcihsZXQgcj0wLG49dGhpcy5ob2xlcy5sZW5ndGg7cjxuO3IrKyl7bGV0IGk9dGhpcy5ob2xlc1tyXTt0LmhvbGVzLnB1c2goaS50b0pTT04oKSl9cmV0dXJuIHR9ZnJvbUpTT04odCl7c3VwZXIuZnJvbUpTT04odCksdGhpcy51dWlkPXQudXVpZCx0aGlzLmhvbGVzPVtdO2ZvcihsZXQgcj0wLG49dC5ob2xlcy5sZW5ndGg7cjxuO3IrKyl7bGV0IGk9dC5ob2xlc1tyXTt0aGlzLmhvbGVzLnB1c2gobmV3IHF2KCkuZnJvbUpTT04oaSkpfXJldHVybiB0aGlzfX0sQmdyPXt0cmlhbmd1bGF0ZTpmdW5jdGlvbihlLHQscj0yKXtsZXQgbj10JiZ0Lmxlbmd0aCxpPW4/dFswXSpyOmUubGVuZ3RoLG89S2ZlKGUsMCxpLHIsITApLGE9W107aWYoIW98fG8ubmV4dD09PW8ucHJldilyZXR1cm4gYTtsZXQgcyxsLGMsdSxoLGYscDtpZihuJiYobz1HZ3IoZSx0LG8scikpLGUubGVuZ3RoPjgwKnIpe3M9Yz1lWzBdLGw9dT1lWzFdO2ZvcihsZXQgZD1yO2Q8aTtkKz1yKWg9ZVtkXSxmPWVbZCsxXSxoPHMmJihzPWgpLGY8bCYmKGw9ZiksaD5jJiYoYz1oKSxmPnUmJih1PWYpO3A9TWF0aC5tYXgoYy1zLHUtbCkscD1wIT09MD8xL3A6MH1yZXR1cm4gdTYobyxhLHIscyxsLHApLGF9fTtmdW5jdGlvbiBLZmUoZSx0LHIsbixpKXtsZXQgbyxhO2lmKGk9PT1lMHIoZSx0LHIsbik+MClmb3Iobz10O288cjtvKz1uKWE9dWhlKG8sZVtvXSxlW28rMV0sYSk7ZWxzZSBmb3Iobz1yLW47bz49dDtvLT1uKWE9dWhlKG8sZVtvXSxlW28rMV0sYSk7cmV0dXJuIGEmJktVKGEsYS5uZXh0KSYmKGY2KGEpLGE9YS5uZXh0KSxhfWZ1bmN0aW9uIEcwKGUsdCl7aWYoIWUpcmV0dXJuIGU7dHx8KHQ9ZSk7bGV0IHI9ZSxuO2RvIGlmKG49ITEsIXIuc3RlaW5lciYmKEtVKHIsci5uZXh0KXx8c2koci5wcmV2LHIsci5uZXh0KT09PTApKXtpZihmNihyKSxyPXQ9ci5wcmV2LHI9PT1yLm5leHQpYnJlYWs7bj0hMH1lbHNlIHI9ci5uZXh0O3doaWxlKG58fHIhPT10KTtyZXR1cm4gdH1mdW5jdGlvbiB1NihlLHQscixuLGksbyxhKXtpZighZSlyZXR1cm47IWEmJm8mJiRncihlLG4saSxvKTtsZXQgcz1lLGwsYztmb3IoO2UucHJldiE9PWUubmV4dDspe2lmKGw9ZS5wcmV2LGM9ZS5uZXh0LG8/VmdyKGUsbixpLG8pOkhncihlKSl7dC5wdXNoKGwuaS9yKSx0LnB1c2goZS5pL3IpLHQucHVzaChjLmkvciksZjYoZSksZT1jLm5leHQscz1jLm5leHQ7Y29udGludWV9aWYoZT1jLGU9PT1zKXthP2E9PT0xPyhlPVVncihHMChlKSx0LHIpLHU2KGUsdCxyLG4saSxvLDIpKTphPT09MiYmcWdyKGUsdCxyLG4saSxvKTp1NihHMChlKSx0LHIsbixpLG8sMSk7YnJlYWt9fX1mdW5jdGlvbiBIZ3IoZSl7bGV0IHQ9ZS5wcmV2LHI9ZSxuPWUubmV4dDtpZihzaSh0LHIsbik+PTApcmV0dXJuITE7bGV0IGk9ZS5uZXh0Lm5leHQ7Zm9yKDtpIT09ZS5wcmV2Oyl7aWYoSDModC54LHQueSxyLngsci55LG4ueCxuLnksaS54LGkueSkmJnNpKGkucHJldixpLGkubmV4dCk+PTApcmV0dXJuITE7aT1pLm5leHR9cmV0dXJuITB9ZnVuY3Rpb24gVmdyKGUsdCxyLG4pe2xldCBpPWUucHJldixvPWUsYT1lLm5leHQ7aWYoc2koaSxvLGEpPj0wKXJldHVybiExO2xldCBzPWkueDxvLng/aS54PGEueD9pLng6YS54Om8ueDxhLng/by54OmEueCxsPWkueTxvLnk/aS55PGEueT9pLnk6YS55Om8ueTxhLnk/by55OmEueSxjPWkueD5vLng/aS54PmEueD9pLng6YS54Om8ueD5hLng/by54OmEueCx1PWkueT5vLnk/aS55PmEueT9pLnk6YS55Om8ueT5hLnk/by55OmEueSxoPWlodChzLGwsdCxyLG4pLGY9aWh0KGMsdSx0LHIsbikscD1lLnByZXZaLGQ9ZS5uZXh0Wjtmb3IoO3AmJnAuej49aCYmZCYmZC56PD1mOyl7aWYocCE9PWUucHJldiYmcCE9PWUubmV4dCYmSDMoaS54LGkueSxvLngsby55LGEueCxhLnkscC54LHAueSkmJnNpKHAucHJldixwLHAubmV4dCk+PTB8fChwPXAucHJldlosZCE9PWUucHJldiYmZCE9PWUubmV4dCYmSDMoaS54LGkueSxvLngsby55LGEueCxhLnksZC54LGQueSkmJnNpKGQucHJldixkLGQubmV4dCk+PTApKXJldHVybiExO2Q9ZC5uZXh0Wn1mb3IoO3AmJnAuej49aDspe2lmKHAhPT1lLnByZXYmJnAhPT1lLm5leHQmJkgzKGkueCxpLnksby54LG8ueSxhLngsYS55LHAueCxwLnkpJiZzaShwLnByZXYscCxwLm5leHQpPj0wKXJldHVybiExO3A9cC5wcmV2Wn1mb3IoO2QmJmQuejw9Zjspe2lmKGQhPT1lLnByZXYmJmQhPT1lLm5leHQmJkgzKGkueCxpLnksby54LG8ueSxhLngsYS55LGQueCxkLnkpJiZzaShkLnByZXYsZCxkLm5leHQpPj0wKXJldHVybiExO2Q9ZC5uZXh0Wn1yZXR1cm4hMH1mdW5jdGlvbiBVZ3IoZSx0LHIpe2xldCBuPWU7ZG97bGV0IGk9bi5wcmV2LG89bi5uZXh0Lm5leHQ7IUtVKGksbykmJlpmZShpLG4sbi5uZXh0LG8pJiZoNihpLG8pJiZoNihvLGkpJiYodC5wdXNoKGkuaS9yKSx0LnB1c2gobi5pL3IpLHQucHVzaChvLmkvciksZjYobiksZjYobi5uZXh0KSxuPWU9byksbj1uLm5leHR9d2hpbGUobiE9PWUpO3JldHVybiBHMChuKX1mdW5jdGlvbiBxZ3IoZSx0LHIsbixpLG8pe2xldCBhPWU7ZG97bGV0IHM9YS5uZXh0Lm5leHQ7Zm9yKDtzIT09YS5wcmV2Oyl7aWYoYS5pIT09cy5pJiZKZ3IoYSxzKSl7bGV0IGw9SmZlKGEscyk7YT1HMChhLGEubmV4dCksbD1HMChsLGwubmV4dCksdTYoYSx0LHIsbixpLG8pLHU2KGwsdCxyLG4saSxvKTtyZXR1cm59cz1zLm5leHR9YT1hLm5leHR9d2hpbGUoYSE9PWUpfWZ1bmN0aW9uIEdncihlLHQscixuKXtsZXQgaT1bXSxvLGEscyxsLGM7Zm9yKG89MCxhPXQubGVuZ3RoO288YTtvKyspcz10W29dKm4sbD1vPGEtMT90W28rMV0qbjplLmxlbmd0aCxjPUtmZShlLHMsbCxuLCExKSxjPT09Yy5uZXh0JiYoYy5zdGVpbmVyPSEwKSxpLnB1c2goWmdyKGMpKTtmb3IoaS5zb3J0KFdnciksbz0wO288aS5sZW5ndGg7bysrKVlncihpW29dLHIpLHI9RzAocixyLm5leHQpO3JldHVybiByfWZ1bmN0aW9uIFdncihlLHQpe3JldHVybiBlLngtdC54fWZ1bmN0aW9uIFlncihlLHQpe2lmKHQ9amdyKGUsdCksdCl7bGV0IHI9SmZlKHQsZSk7RzAodCx0Lm5leHQpLEcwKHIsci5uZXh0KX19ZnVuY3Rpb24gamdyKGUsdCl7bGV0IHI9dCxuPWUueCxpPWUueSxvPS0xLzAsYTtkb3tpZihpPD1yLnkmJmk+PXIubmV4dC55JiZyLm5leHQueSE9PXIueSl7bGV0IGY9ci54KyhpLXIueSkqKHIubmV4dC54LXIueCkvKHIubmV4dC55LXIueSk7aWYoZjw9biYmZj5vKXtpZihvPWYsZj09PW4pe2lmKGk9PT1yLnkpcmV0dXJuIHI7aWYoaT09PXIubmV4dC55KXJldHVybiByLm5leHR9YT1yLng8ci5uZXh0Lng/cjpyLm5leHR9fXI9ci5uZXh0fXdoaWxlKHIhPT10KTtpZighYSlyZXR1cm4gbnVsbDtpZihuPT09bylyZXR1cm4gYTtsZXQgcz1hLGw9YS54LGM9YS55LHU9MS8wLGg7cj1hO2RvIG4+PXIueCYmci54Pj1sJiZuIT09ci54JiZIMyhpPGM/bjpvLGksbCxjLGk8Yz9vOm4saSxyLngsci55KSYmKGg9TWF0aC5hYnMoaS1yLnkpLyhuLXIueCksaDYocixlKSYmKGg8dXx8aD09PXUmJihyLng+YS54fHxyLng9PT1hLngmJlhncihhLHIpKSkmJihhPXIsdT1oKSkscj1yLm5leHQ7d2hpbGUociE9PXMpO3JldHVybiBhfWZ1bmN0aW9uIFhncihlLHQpe3JldHVybiBzaShlLnByZXYsZSx0LnByZXYpPDAmJnNpKHQubmV4dCxlLGUubmV4dCk8MH1mdW5jdGlvbiAkZ3IoZSx0LHIsbil7bGV0IGk9ZTtkbyBpLno9PT1udWxsJiYoaS56PWlodChpLngsaS55LHQscixuKSksaS5wcmV2Wj1pLnByZXYsaS5uZXh0Wj1pLm5leHQsaT1pLm5leHQ7d2hpbGUoaSE9PWUpO2kucHJldloubmV4dFo9bnVsbCxpLnByZXZaPW51bGwsS2dyKGkpfWZ1bmN0aW9uIEtncihlKXtsZXQgdCxyLG4saSxvLGEscyxsLGM9MTtkb3tmb3Iocj1lLGU9bnVsbCxvPW51bGwsYT0wO3I7KXtmb3IoYSsrLG49cixzPTAsdD0wO3Q8YyYmKHMrKyxuPW4ubmV4dFosISFuKTt0KyspO2ZvcihsPWM7cz4wfHxsPjAmJm47KXMhPT0wJiYobD09PTB8fCFufHxyLno8PW4ueik/KGk9cixyPXIubmV4dFoscy0tKTooaT1uLG49bi5uZXh0WixsLS0pLG8/by5uZXh0Wj1pOmU9aSxpLnByZXZaPW8sbz1pO3I9bn1vLm5leHRaPW51bGwsYyo9Mn13aGlsZShhPjEpO3JldHVybiBlfWZ1bmN0aW9uIGlodChlLHQscixuLGkpe3JldHVybiBlPTMyNzY3KihlLXIpKmksdD0zMjc2NyoodC1uKSppLGU9KGV8ZTw8OCkmMTY3MTE5MzUsZT0oZXxlPDw0KSYyNTI2NDUxMzUsZT0oZXxlPDwyKSY4NTg5OTM0NTksZT0oZXxlPDwxKSYxNDMxNjU1NzY1LHQ9KHR8dDw8OCkmMTY3MTE5MzUsdD0odHx0PDw0KSYyNTI2NDUxMzUsdD0odHx0PDwyKSY4NTg5OTM0NTksdD0odHx0PDwxKSYxNDMxNjU1NzY1LGV8dDw8MX1mdW5jdGlvbiBaZ3IoZSl7bGV0IHQ9ZSxyPWU7ZG8odC54PHIueHx8dC54PT09ci54JiZ0Lnk8ci55KSYmKHI9dCksdD10Lm5leHQ7d2hpbGUodCE9PWUpO3JldHVybiByfWZ1bmN0aW9uIEgzKGUsdCxyLG4saSxvLGEscyl7cmV0dXJuKGktYSkqKHQtcyktKGUtYSkqKG8tcyk+PTAmJihlLWEpKihuLXMpLShyLWEpKih0LXMpPj0wJiYoci1hKSooby1zKS0oaS1hKSoobi1zKT49MH1mdW5jdGlvbiBKZ3IoZSx0KXtyZXR1cm4gZS5uZXh0LmkhPT10LmkmJmUucHJldi5pIT09dC5pJiYhUWdyKGUsdCkmJihoNihlLHQpJiZoNih0LGUpJiZ0MHIoZSx0KSYmKHNpKGUucHJldixlLHQucHJldil8fHNpKGUsdC5wcmV2LHQpKXx8S1UoZSx0KSYmc2koZS5wcmV2LGUsZS5uZXh0KT4wJiZzaSh0LnByZXYsdCx0Lm5leHQpPjApfWZ1bmN0aW9uIHNpKGUsdCxyKXtyZXR1cm4odC55LWUueSkqKHIueC10LngpLSh0LngtZS54KSooci55LXQueSl9ZnVuY3Rpb24gS1UoZSx0KXtyZXR1cm4gZS54PT09dC54JiZlLnk9PT10Lnl9ZnVuY3Rpb24gWmZlKGUsdCxyLG4pe2xldCBpPXFWKHNpKGUsdCxyKSksbz1xVihzaShlLHQsbikpLGE9cVYoc2kocixuLGUpKSxzPXFWKHNpKHIsbix0KSk7cmV0dXJuISEoaSE9PW8mJmEhPT1zfHxpPT09MCYmVVYoZSxyLHQpfHxvPT09MCYmVVYoZSxuLHQpfHxhPT09MCYmVVYocixlLG4pfHxzPT09MCYmVVYocix0LG4pKX1mdW5jdGlvbiBVVihlLHQscil7cmV0dXJuIHQueDw9TWF0aC5tYXgoZS54LHIueCkmJnQueD49TWF0aC5taW4oZS54LHIueCkmJnQueTw9TWF0aC5tYXgoZS55LHIueSkmJnQueT49TWF0aC5taW4oZS55LHIueSl9ZnVuY3Rpb24gcVYoZSl7cmV0dXJuIGU+MD8xOmU8MD8tMTowfWZ1bmN0aW9uIFFncihlLHQpe2xldCByPWU7ZG97aWYoci5pIT09ZS5pJiZyLm5leHQuaSE9PWUuaSYmci5pIT09dC5pJiZyLm5leHQuaSE9PXQuaSYmWmZlKHIsci5uZXh0LGUsdCkpcmV0dXJuITA7cj1yLm5leHR9d2hpbGUociE9PWUpO3JldHVybiExfWZ1bmN0aW9uIGg2KGUsdCl7cmV0dXJuIHNpKGUucHJldixlLGUubmV4dCk8MD9zaShlLHQsZS5uZXh0KT49MCYmc2koZSxlLnByZXYsdCk+PTA6c2koZSx0LGUucHJldik8MHx8c2koZSxlLm5leHQsdCk8MH1mdW5jdGlvbiB0MHIoZSx0KXtsZXQgcj1lLG49ITEsaT0oZS54K3QueCkvMixvPShlLnkrdC55KS8yO2RvIHIueT5vIT1yLm5leHQueT5vJiZyLm5leHQueSE9PXIueSYmaTwoci5uZXh0Lngtci54KSooby1yLnkpLyhyLm5leHQueS1yLnkpK3IueCYmKG49IW4pLHI9ci5uZXh0O3doaWxlKHIhPT1lKTtyZXR1cm4gbn1mdW5jdGlvbiBKZmUoZSx0KXtsZXQgcj1uZXcgb2h0KGUuaSxlLngsZS55KSxuPW5ldyBvaHQodC5pLHQueCx0LnkpLGk9ZS5uZXh0LG89dC5wcmV2O3JldHVybiBlLm5leHQ9dCx0LnByZXY9ZSxyLm5leHQ9aSxpLnByZXY9cixuLm5leHQ9cixyLnByZXY9bixvLm5leHQ9bixuLnByZXY9byxufWZ1bmN0aW9uIHVoZShlLHQscixuKXtsZXQgaT1uZXcgb2h0KGUsdCxyKTtyZXR1cm4gbj8oaS5uZXh0PW4ubmV4dCxpLnByZXY9bixuLm5leHQucHJldj1pLG4ubmV4dD1pKTooaS5wcmV2PWksaS5uZXh0PWkpLGl9ZnVuY3Rpb24gZjYoZSl7ZS5uZXh0LnByZXY9ZS5wcmV2LGUucHJldi5uZXh0PWUubmV4dCxlLnByZXZaJiYoZS5wcmV2Wi5uZXh0Wj1lLm5leHRaKSxlLm5leHRaJiYoZS5uZXh0Wi5wcmV2Wj1lLnByZXZaKX1mdW5jdGlvbiBvaHQoZSx0LHIpe3RoaXMuaT1lLHRoaXMueD10LHRoaXMueT1yLHRoaXMucHJldj1udWxsLHRoaXMubmV4dD1udWxsLHRoaXMuej1udWxsLHRoaXMucHJldlo9bnVsbCx0aGlzLm5leHRaPW51bGwsdGhpcy5zdGVpbmVyPSExfWZ1bmN0aW9uIGUwcihlLHQscixuKXtsZXQgaT0wO2ZvcihsZXQgbz10LGE9ci1uO288cjtvKz1uKWkrPShlW2FdLWVbb10pKihlW28rMV0rZVthKzFdKSxhPW87cmV0dXJuIGl9dmFyIFpjPWNsYXNze3N0YXRpYyBhcmVhKHQpe2xldCByPXQubGVuZ3RoLG49MDtmb3IobGV0IGk9ci0xLG89MDtvPHI7aT1vKyspbis9dFtpXS54KnRbb10ueS10W29dLngqdFtpXS55O3JldHVybiBuKi41fXN0YXRpYyBpc0Nsb2NrV2lzZSh0KXtyZXR1cm4gWmMuYXJlYSh0KTwwfXN0YXRpYyB0cmlhbmd1bGF0ZVNoYXBlKHQscil7bGV0IG49W10saT1bXSxvPVtdO2hoZSh0KSxmaGUobix0KTtsZXQgYT10Lmxlbmd0aDtyLmZvckVhY2goaGhlKTtmb3IobGV0IGw9MDtsPHIubGVuZ3RoO2wrKylpLnB1c2goYSksYSs9cltsXS5sZW5ndGgsZmhlKG4scltsXSk7bGV0IHM9QmdyLnRyaWFuZ3VsYXRlKG4saSk7Zm9yKGxldCBsPTA7bDxzLmxlbmd0aDtsKz0zKW8ucHVzaChzLnNsaWNlKGwsbCszKSk7cmV0dXJuIG99fTtmdW5jdGlvbiBoaGUoZSl7bGV0IHQ9ZS5sZW5ndGg7dD4yJiZlW3QtMV0uZXF1YWxzKGVbMF0pJiZlLnBvcCgpfWZ1bmN0aW9uIGZoZShlLHQpe2ZvcihsZXQgcj0wO3I8dC5sZW5ndGg7cisrKWUucHVzaCh0W3JdLngpLGUucHVzaCh0W3JdLnkpfXZhciBoaD1jbGFzcyBleHRlbmRzIFBle2NvbnN0cnVjdG9yKHQ9bmV3IEtjKFtuZXcgTHQoLjUsLjUpLG5ldyBMdCgtLjUsLjUpLG5ldyBMdCgtLjUsLS41KSxuZXcgTHQoLjUsLS41KV0pLHI9e30pe3N1cGVyKCksdGhpcy50eXBlPSJFeHRydWRlR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17c2hhcGVzOnQsb3B0aW9uczpyfSx0PUFycmF5LmlzQXJyYXkodCk/dDpbdF07bGV0IG49dGhpcyxpPVtdLG89W107Zm9yKGxldCBzPTAsbD10Lmxlbmd0aDtzPGw7cysrKXtsZXQgYz10W3NdO2EoYyl9dGhpcy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUoaSwzKSksdGhpcy5zZXRBdHRyaWJ1dGUoInV2IixuZXcgeGUobywyKSksdGhpcy5jb21wdXRlVmVydGV4Tm9ybWFscygpO2Z1bmN0aW9uIGEocyl7bGV0IGw9W10sYz1yLmN1cnZlU2VnbWVudHMhPT12b2lkIDA/ci5jdXJ2ZVNlZ21lbnRzOjEyLHU9ci5zdGVwcyE9PXZvaWQgMD9yLnN0ZXBzOjEsaD1yLmRlcHRoIT09dm9pZCAwP3IuZGVwdGg6MSxmPXIuYmV2ZWxFbmFibGVkIT09dm9pZCAwP3IuYmV2ZWxFbmFibGVkOiEwLHA9ci5iZXZlbFRoaWNrbmVzcyE9PXZvaWQgMD9yLmJldmVsVGhpY2tuZXNzOi4yLGQ9ci5iZXZlbFNpemUhPT12b2lkIDA/ci5iZXZlbFNpemU6cC0uMSxnPXIuYmV2ZWxPZmZzZXQhPT12b2lkIDA/ci5iZXZlbE9mZnNldDowLF89ci5iZXZlbFNlZ21lbnRzIT09dm9pZCAwP3IuYmV2ZWxTZWdtZW50czozLHk9ci5leHRydWRlUGF0aCx4PXIuVVZHZW5lcmF0b3IhPT12b2lkIDA/ci5VVkdlbmVyYXRvcjpyMHI7ci5hbW91bnQhPT12b2lkIDAmJihjb25zb2xlLndhcm4oIlRIUkVFLkV4dHJ1ZGVCdWZmZXJHZW9tZXRyeTogYW1vdW50IGhhcyBiZWVuIHJlbmFtZWQgdG8gZGVwdGguIiksaD1yLmFtb3VudCk7bGV0IGIsUz0hMSxDLFAsayxPO3kmJihiPXkuZ2V0U3BhY2VkUG9pbnRzKHUpLFM9ITAsZj0hMSxDPXkuY29tcHV0ZUZyZW5ldEZyYW1lcyh1LCExKSxQPW5ldyBqLGs9bmV3IGosTz1uZXcgaiksZnx8KF89MCxwPTAsZD0wLGc9MCk7bGV0IEQ9cy5leHRyYWN0UG9pbnRzKGMpLEI9RC5zaGFwZSxJPUQuaG9sZXM7aWYoIVpjLmlzQ2xvY2tXaXNlKEIpKXtCPUIucmV2ZXJzZSgpO2ZvcihsZXQgcT0wLHB0PUkubGVuZ3RoO3E8cHQ7cSsrKXtsZXQgaHQ9SVtxXTtaYy5pc0Nsb2NrV2lzZShodCkmJihJW3FdPWh0LnJldmVyc2UoKSl9fWxldCBSPVpjLnRyaWFuZ3VsYXRlU2hhcGUoQixJKSxGPUI7Zm9yKGxldCBxPTAscHQ9SS5sZW5ndGg7cTxwdDtxKyspe2xldCBodD1JW3FdO0I9Qi5jb25jYXQoaHQpfWZ1bmN0aW9uIHoocSxwdCxodCl7cmV0dXJuIHB0fHxjb25zb2xlLmVycm9yKCJUSFJFRS5FeHRydWRlR2VvbWV0cnk6IHZlYyBkb2VzIG5vdCBleGlzdCIpLHB0LmNsb25lKCkubXVsdGlwbHlTY2FsYXIoaHQpLmFkZChxKX1sZXQgVT1CLmxlbmd0aCxXPVIubGVuZ3RoO2Z1bmN0aW9uIFoocSxwdCxodCl7bGV0IHd0LGt0LGllLGVlPXEueC1wdC54LExlPXEueS1wdC55LGFyPWh0LngtcS54LGZyPWh0LnktcS55LHR0PWVlKmVlK0xlKkxlLCQ9ZWUqZnItTGUqYXI7aWYoTWF0aC5hYnMoJCk+TnVtYmVyLkVQU0lMT04pe2xldCBJdD1NYXRoLnNxcnQodHQpLCR0PU1hdGguc3FydChhciphcitmcipmciksaGU9cHQueC1MZS9JdCxUdD1wdC55K2VlL0l0LGJlPWh0LngtZnIvJHQsbnQ9aHQueSthci8kdCxDdD0oKGJlLWhlKSpmci0obnQtVHQpKmFyKS8oZWUqZnItTGUqYXIpO3d0PWhlK2VlKkN0LXEueCxrdD1UdCtMZSpDdC1xLnk7bGV0IFd0PXd0Knd0K2t0Kmt0O2lmKFd0PD0yKXJldHVybiBuZXcgTHQod3Qsa3QpO2llPU1hdGguc3FydChXdC8yKX1lbHNle2xldCBJdD0hMTtlZT5OdW1iZXIuRVBTSUxPTj9hcj5OdW1iZXIuRVBTSUxPTiYmKEl0PSEwKTplZTwtTnVtYmVyLkVQU0lMT04/YXI8LU51bWJlci5FUFNJTE9OJiYoSXQ9ITApOk1hdGguc2lnbihMZSk9PT1NYXRoLnNpZ24oZnIpJiYoSXQ9ITApLEl0Pyh3dD0tTGUsa3Q9ZWUsaWU9TWF0aC5zcXJ0KHR0KSk6KHd0PWVlLGt0PUxlLGllPU1hdGguc3FydCh0dC8yKSl9cmV0dXJuIG5ldyBMdCh3dC9pZSxrdC9pZSl9bGV0IHJ0PVtdO2ZvcihsZXQgcT0wLHB0PUYubGVuZ3RoLGh0PXB0LTEsd3Q9cSsxO3E8cHQ7cSsrLGh0Kyssd3QrKylodD09PXB0JiYoaHQ9MCksd3Q9PT1wdCYmKHd0PTApLHJ0W3FdPVooRltxXSxGW2h0XSxGW3d0XSk7bGV0IG90PVtdLHN0LFN0PXJ0LmNvbmNhdCgpO2ZvcihsZXQgcT0wLHB0PUkubGVuZ3RoO3E8cHQ7cSsrKXtsZXQgaHQ9SVtxXTtzdD1bXTtmb3IobGV0IHd0PTAsa3Q9aHQubGVuZ3RoLGllPWt0LTEsZWU9d3QrMTt3dDxrdDt3dCsrLGllKyssZWUrKylpZT09PWt0JiYoaWU9MCksZWU9PT1rdCYmKGVlPTApLHN0W3d0XT1aKGh0W3d0XSxodFtpZV0saHRbZWVdKTtvdC5wdXNoKHN0KSxTdD1TdC5jb25jYXQoc3QpfWZvcihsZXQgcT0wO3E8XztxKyspe2xldCBwdD1xL18saHQ9cCpNYXRoLmNvcyhwdCpNYXRoLlBJLzIpLHd0PWQqTWF0aC5zaW4ocHQqTWF0aC5QSS8yKStnO2ZvcihsZXQga3Q9MCxpZT1GLmxlbmd0aDtrdDxpZTtrdCsrKXtsZXQgZWU9eihGW2t0XSxydFtrdF0sd3QpO190KGVlLngsZWUueSwtaHQpfWZvcihsZXQga3Q9MCxpZT1JLmxlbmd0aDtrdDxpZTtrdCsrKXtsZXQgZWU9SVtrdF07c3Q9b3Rba3RdO2ZvcihsZXQgTGU9MCxhcj1lZS5sZW5ndGg7TGU8YXI7TGUrKyl7bGV0IGZyPXooZWVbTGVdLHN0W0xlXSx3dCk7X3QoZnIueCxmci55LC1odCl9fX1sZXQgYnQ9ZCtnO2ZvcihsZXQgcT0wO3E8VTtxKyspe2xldCBwdD1mP3ooQltxXSxTdFtxXSxidCk6QltxXTtTPyhrLmNvcHkoQy5ub3JtYWxzWzBdKS5tdWx0aXBseVNjYWxhcihwdC54KSxQLmNvcHkoQy5iaW5vcm1hbHNbMF0pLm11bHRpcGx5U2NhbGFyKHB0LnkpLE8uY29weShiWzBdKS5hZGQoaykuYWRkKFApLF90KE8ueCxPLnksTy56KSk6X3QocHQueCxwdC55LDApfWZvcihsZXQgcT0xO3E8PXU7cSsrKWZvcihsZXQgcHQ9MDtwdDxVO3B0Kyspe2xldCBodD1mP3ooQltwdF0sU3RbcHRdLGJ0KTpCW3B0XTtTPyhrLmNvcHkoQy5ub3JtYWxzW3FdKS5tdWx0aXBseVNjYWxhcihodC54KSxQLmNvcHkoQy5iaW5vcm1hbHNbcV0pLm11bHRpcGx5U2NhbGFyKGh0LnkpLE8uY29weShiW3FdKS5hZGQoaykuYWRkKFApLF90KE8ueCxPLnksTy56KSk6X3QoaHQueCxodC55LGgvdSpxKX1mb3IobGV0IHE9Xy0xO3E+PTA7cS0tKXtsZXQgcHQ9cS9fLGh0PXAqTWF0aC5jb3MocHQqTWF0aC5QSS8yKSx3dD1kKk1hdGguc2luKHB0Kk1hdGguUEkvMikrZztmb3IobGV0IGt0PTAsaWU9Ri5sZW5ndGg7a3Q8aWU7a3QrKyl7bGV0IGVlPXooRltrdF0scnRba3RdLHd0KTtfdChlZS54LGVlLnksaCtodCl9Zm9yKGxldCBrdD0wLGllPUkubGVuZ3RoO2t0PGllO2t0Kyspe2xldCBlZT1JW2t0XTtzdD1vdFtrdF07Zm9yKGxldCBMZT0wLGFyPWVlLmxlbmd0aDtMZTxhcjtMZSsrKXtsZXQgZnI9eihlZVtMZV0sc3RbTGVdLHd0KTtTP190KGZyLngsZnIueStiW3UtMV0ueSxiW3UtMV0ueCtodCk6X3QoZnIueCxmci55LGgraHQpfX19TXQoKSxsdCgpO2Z1bmN0aW9uIE10KCl7bGV0IHE9aS5sZW5ndGgvMztpZihmKXtsZXQgcHQ9MCxodD1VKnB0O2ZvcihsZXQgd3Q9MDt3dDxXO3d0Kyspe2xldCBrdD1SW3d0XTtjdChrdFsyXStodCxrdFsxXStodCxrdFswXStodCl9cHQ9dStfKjIsaHQ9VSpwdDtmb3IobGV0IHd0PTA7d3Q8Vzt3dCsrKXtsZXQga3Q9Ult3dF07Y3Qoa3RbMF0raHQsa3RbMV0raHQsa3RbMl0raHQpfX1lbHNle2ZvcihsZXQgcHQ9MDtwdDxXO3B0Kyspe2xldCBodD1SW3B0XTtjdChodFsyXSxodFsxXSxodFswXSl9Zm9yKGxldCBwdD0wO3B0PFc7cHQrKyl7bGV0IGh0PVJbcHRdO2N0KGh0WzBdK1UqdSxodFsxXStVKnUsaHRbMl0rVSp1KX19bi5hZGRHcm91cChxLGkubGVuZ3RoLzMtcSwwKX1mdW5jdGlvbiBsdCgpe2xldCBxPWkubGVuZ3RoLzMscHQ9MDtLdChGLHB0KSxwdCs9Ri5sZW5ndGg7Zm9yKGxldCBodD0wLHd0PUkubGVuZ3RoO2h0PHd0O2h0Kyspe2xldCBrdD1JW2h0XTtLdChrdCxwdCkscHQrPWt0Lmxlbmd0aH1uLmFkZEdyb3VwKHEsaS5sZW5ndGgvMy1xLDEpfWZ1bmN0aW9uIEt0KHEscHQpe2xldCBodD1xLmxlbmd0aDtmb3IoOy0taHQ+PTA7KXtsZXQgd3Q9aHQsa3Q9aHQtMTtrdDwwJiYoa3Q9cS5sZW5ndGgtMSk7Zm9yKGxldCBpZT0wLGVlPXUrXyoyO2llPGVlO2llKyspe2xldCBMZT1VKmllLGFyPVUqKGllKzEpLGZyPXB0K3d0K0xlLHR0PXB0K2t0K0xlLCQ9cHQra3QrYXIsSXQ9cHQrd3QrYXI7WChmcix0dCwkLEl0KX19fWZ1bmN0aW9uIF90KHEscHQsaHQpe2wucHVzaChxKSxsLnB1c2gocHQpLGwucHVzaChodCl9ZnVuY3Rpb24gY3QocSxwdCxodCl7ZXQocSksZXQocHQpLGV0KGh0KTtsZXQgd3Q9aS5sZW5ndGgvMyxrdD14LmdlbmVyYXRlVG9wVVYobixpLHd0LTMsd3QtMix3dC0xKTtkdChrdFswXSksZHQoa3RbMV0pLGR0KGt0WzJdKX1mdW5jdGlvbiBYKHEscHQsaHQsd3Qpe2V0KHEpLGV0KHB0KSxldCh3dCksZXQocHQpLGV0KGh0KSxldCh3dCk7bGV0IGt0PWkubGVuZ3RoLzMsaWU9eC5nZW5lcmF0ZVNpZGVXYWxsVVYobixpLGt0LTYsa3QtMyxrdC0yLGt0LTEpO2R0KGllWzBdKSxkdChpZVsxXSksZHQoaWVbM10pLGR0KGllWzFdKSxkdChpZVsyXSksZHQoaWVbM10pfWZ1bmN0aW9uIGV0KHEpe2kucHVzaChsW3EqMyswXSksaS5wdXNoKGxbcSozKzFdKSxpLnB1c2gobFtxKjMrMl0pfWZ1bmN0aW9uIGR0KHEpe28ucHVzaChxLngpLG8ucHVzaChxLnkpfX19dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCkscj10aGlzLnBhcmFtZXRlcnMuc2hhcGVzLG49dGhpcy5wYXJhbWV0ZXJzLm9wdGlvbnM7cmV0dXJuIG4wcihyLG4sdCl9c3RhdGljIGZyb21KU09OKHQscil7bGV0IG49W107Zm9yKGxldCBvPTAsYT10LnNoYXBlcy5sZW5ndGg7bzxhO28rKyl7bGV0IHM9clt0LnNoYXBlc1tvXV07bi5wdXNoKHMpfWxldCBpPXQub3B0aW9ucy5leHRydWRlUGF0aDtyZXR1cm4gaSE9PXZvaWQgMCYmKHQub3B0aW9ucy5leHRydWRlUGF0aD1uZXcgemh0W2kudHlwZV0oKS5mcm9tSlNPTihpKSksbmV3IGhoKG4sdC5vcHRpb25zKX19LHIwcj17Z2VuZXJhdGVUb3BVVjpmdW5jdGlvbihlLHQscixuLGkpe2xldCBvPXRbciozXSxhPXRbciozKzFdLHM9dFtuKjNdLGw9dFtuKjMrMV0sYz10W2kqM10sdT10W2kqMysxXTtyZXR1cm5bbmV3IEx0KG8sYSksbmV3IEx0KHMsbCksbmV3IEx0KGMsdSldfSxnZW5lcmF0ZVNpZGVXYWxsVVY6ZnVuY3Rpb24oZSx0LHIsbixpLG8pe2xldCBhPXRbciozXSxzPXRbciozKzFdLGw9dFtyKjMrMl0sYz10W24qM10sdT10W24qMysxXSxoPXRbbiozKzJdLGY9dFtpKjNdLHA9dFtpKjMrMV0sZD10W2kqMysyXSxnPXRbbyozXSxfPXRbbyozKzFdLHk9dFtvKjMrMl07cmV0dXJuIE1hdGguYWJzKHMtdSk8TWF0aC5hYnMoYS1jKT9bbmV3IEx0KGEsMS1sKSxuZXcgTHQoYywxLWgpLG5ldyBMdChmLDEtZCksbmV3IEx0KGcsMS15KV06W25ldyBMdChzLDEtbCksbmV3IEx0KHUsMS1oKSxuZXcgTHQocCwxLWQpLG5ldyBMdChfLDEteSldfX07ZnVuY3Rpb24gbjByKGUsdCxyKXtpZihyLnNoYXBlcz1bXSxBcnJheS5pc0FycmF5KGUpKWZvcihsZXQgbj0wLGk9ZS5sZW5ndGg7bjxpO24rKyl7bGV0IG89ZVtuXTtyLnNoYXBlcy5wdXNoKG8udXVpZCl9ZWxzZSByLnNoYXBlcy5wdXNoKGUudXVpZCk7cmV0dXJuIHQuZXh0cnVkZVBhdGghPT12b2lkIDAmJihyLm9wdGlvbnMuZXh0cnVkZVBhdGg9dC5leHRydWRlUGF0aC50b0pTT04oKSkscn12YXIgR3Y9Y2xhc3MgZXh0ZW5kcyB1aHtjb25zdHJ1Y3Rvcih0PTEscj0wKXtsZXQgbj0oMStNYXRoLnNxcnQoNSkpLzIsaT1bLTEsbiwwLDEsbiwwLC0xLC1uLDAsMSwtbiwwLDAsLTEsbiwwLDEsbiwwLC0xLC1uLDAsMSwtbixuLDAsLTEsbiwwLDEsLW4sMCwtMSwtbiwwLDFdLG89WzAsMTEsNSwwLDUsMSwwLDEsNywwLDcsMTAsMCwxMCwxMSwxLDUsOSw1LDExLDQsMTEsMTAsMiwxMCw3LDYsNywxLDgsMyw5LDQsMyw0LDIsMywyLDYsMyw2LDgsMyw4LDksNCw5LDUsMiw0LDExLDYsMiwxMCw4LDYsNyw5LDgsMV07c3VwZXIoaSxvLHQsciksdGhpcy50eXBlPSJJY29zYWhlZHJvbkdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3JhZGl1czp0LGRldGFpbDpyfX1zdGF0aWMgZnJvbUpTT04odCl7cmV0dXJuIG5ldyBHdih0LnJhZGl1cyx0LmRldGFpbCl9fSxXdj1jbGFzcyBleHRlbmRzIFBle2NvbnN0cnVjdG9yKHQ9W25ldyBMdCgwLC41KSxuZXcgTHQoLjUsMCksbmV3IEx0KDAsLS41KV0scj0xMixuPTAsaT1NYXRoLlBJKjIpe3N1cGVyKCksdGhpcy50eXBlPSJMYXRoZUdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3BvaW50czp0LHNlZ21lbnRzOnIscGhpU3RhcnQ6bixwaGlMZW5ndGg6aX0scj1NYXRoLmZsb29yKHIpLGk9Wm8oaSwwLE1hdGguUEkqMik7bGV0IG89W10sYT1bXSxzPVtdLGw9W10sYz1bXSx1PTEvcixoPW5ldyBqLGY9bmV3IEx0LHA9bmV3IGosZD1uZXcgaixnPW5ldyBqLF89MCx5PTA7Zm9yKGxldCB4PTA7eDw9dC5sZW5ndGgtMTt4Kyspc3dpdGNoKHgpe2Nhc2UgMDpfPXRbeCsxXS54LXRbeF0ueCx5PXRbeCsxXS55LXRbeF0ueSxwLng9eSoxLHAueT0tXyxwLno9eSowLGcuY29weShwKSxwLm5vcm1hbGl6ZSgpLGwucHVzaChwLngscC55LHAueik7YnJlYWs7Y2FzZSB0Lmxlbmd0aC0xOmwucHVzaChnLngsZy55LGcueik7YnJlYWs7ZGVmYXVsdDpfPXRbeCsxXS54LXRbeF0ueCx5PXRbeCsxXS55LXRbeF0ueSxwLng9eSoxLHAueT0tXyxwLno9eSowLGQuY29weShwKSxwLngrPWcueCxwLnkrPWcueSxwLnorPWcueixwLm5vcm1hbGl6ZSgpLGwucHVzaChwLngscC55LHAueiksZy5jb3B5KGQpfWZvcihsZXQgeD0wO3g8PXI7eCsrKXtsZXQgYj1uK3gqdSppLFM9TWF0aC5zaW4oYiksQz1NYXRoLmNvcyhiKTtmb3IobGV0IFA9MDtQPD10Lmxlbmd0aC0xO1ArKyl7aC54PXRbUF0ueCpTLGgueT10W1BdLnksaC56PXRbUF0ueCpDLGEucHVzaChoLngsaC55LGgueiksZi54PXgvcixmLnk9UC8odC5sZW5ndGgtMSkscy5wdXNoKGYueCxmLnkpO2xldCBrPWxbMypQKzBdKlMsTz1sWzMqUCsxXSxEPWxbMypQKzBdKkM7Yy5wdXNoKGssTyxEKX19Zm9yKGxldCB4PTA7eDxyO3grKylmb3IobGV0IGI9MDtiPHQubGVuZ3RoLTE7YisrKXtsZXQgUz1iK3gqdC5sZW5ndGgsQz1TLFA9Uyt0Lmxlbmd0aCxrPVMrdC5sZW5ndGgrMSxPPVMrMTtvLnB1c2goQyxQLE8pLG8ucHVzaChrLE8sUCl9dGhpcy5zZXRJbmRleChvKSx0aGlzLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyB4ZShhLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyB4ZShzLDIpKSx0aGlzLnNldEF0dHJpYnV0ZSgibm9ybWFsIixuZXcgeGUoYywzKSl9c3RhdGljIGZyb21KU09OKHQpe3JldHVybiBuZXcgV3YodC5wb2ludHMsdC5zZWdtZW50cyx0LnBoaVN0YXJ0LHQucGhpTGVuZ3RoKX19LFcwPWNsYXNzIGV4dGVuZHMgdWh7Y29uc3RydWN0b3IodD0xLHI9MCl7bGV0IG49WzEsMCwwLC0xLDAsMCwwLDEsMCwwLC0xLDAsMCwwLDEsMCwwLC0xXSxpPVswLDIsNCwwLDQsMywwLDMsNSwwLDUsMiwxLDIsNSwxLDUsMywxLDMsNCwxLDQsMl07c3VwZXIobixpLHQsciksdGhpcy50eXBlPSJPY3RhaGVkcm9uR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17cmFkaXVzOnQsZGV0YWlsOnJ9fXN0YXRpYyBmcm9tSlNPTih0KXtyZXR1cm4gbmV3IFcwKHQucmFkaXVzLHQuZGV0YWlsKX19LFl2PWNsYXNzIGV4dGVuZHMgUGV7Y29uc3RydWN0b3IodD0uNSxyPTEsbj04LGk9MSxvPTAsYT1NYXRoLlBJKjIpe3N1cGVyKCksdGhpcy50eXBlPSJSaW5nR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17aW5uZXJSYWRpdXM6dCxvdXRlclJhZGl1czpyLHRoZXRhU2VnbWVudHM6bixwaGlTZWdtZW50czppLHRoZXRhU3RhcnQ6byx0aGV0YUxlbmd0aDphfSxuPU1hdGgubWF4KDMsbiksaT1NYXRoLm1heCgxLGkpO2xldCBzPVtdLGw9W10sYz1bXSx1PVtdLGg9dCxmPShyLXQpL2kscD1uZXcgaixkPW5ldyBMdDtmb3IobGV0IGc9MDtnPD1pO2crKyl7Zm9yKGxldCBfPTA7Xzw9bjtfKyspe2xldCB5PW8rXy9uKmE7cC54PWgqTWF0aC5jb3MoeSkscC55PWgqTWF0aC5zaW4oeSksbC5wdXNoKHAueCxwLnkscC56KSxjLnB1c2goMCwwLDEpLGQueD0ocC54L3IrMSkvMixkLnk9KHAueS9yKzEpLzIsdS5wdXNoKGQueCxkLnkpfWgrPWZ9Zm9yKGxldCBnPTA7ZzxpO2crKyl7bGV0IF89ZyoobisxKTtmb3IobGV0IHk9MDt5PG47eSsrKXtsZXQgeD15K18sYj14LFM9eCtuKzEsQz14K24rMixQPXgrMTtzLnB1c2goYixTLFApLHMucHVzaChTLEMsUCl9fXRoaXMuc2V0SW5kZXgocyksdGhpcy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUobCwzKSksdGhpcy5zZXRBdHRyaWJ1dGUoIm5vcm1hbCIsbmV3IHhlKGMsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJ1diIsbmV3IHhlKHUsMikpfXN0YXRpYyBmcm9tSlNPTih0KXtyZXR1cm4gbmV3IFl2KHQuaW5uZXJSYWRpdXMsdC5vdXRlclJhZGl1cyx0LnRoZXRhU2VnbWVudHMsdC5waGlTZWdtZW50cyx0LnRoZXRhU3RhcnQsdC50aGV0YUxlbmd0aCl9fSxZMD1jbGFzcyBleHRlbmRzIFBle2NvbnN0cnVjdG9yKHQ9bmV3IEtjKFtuZXcgTHQoMCwuNSksbmV3IEx0KC0uNSwtLjUpLG5ldyBMdCguNSwtLjUpXSkscj0xMil7c3VwZXIoKSx0aGlzLnR5cGU9IlNoYXBlR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17c2hhcGVzOnQsY3VydmVTZWdtZW50czpyfTtsZXQgbj1bXSxpPVtdLG89W10sYT1bXSxzPTAsbD0wO2lmKEFycmF5LmlzQXJyYXkodCk9PT0hMSljKHQpO2Vsc2UgZm9yKGxldCB1PTA7dTx0Lmxlbmd0aDt1KyspYyh0W3VdKSx0aGlzLmFkZEdyb3VwKHMsbCx1KSxzKz1sLGw9MDt0aGlzLnNldEluZGV4KG4pLHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IHhlKGksMykpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG5ldyB4ZShvLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyB4ZShhLDIpKTtmdW5jdGlvbiBjKHUpe2xldCBoPWkubGVuZ3RoLzMsZj11LmV4dHJhY3RQb2ludHMocikscD1mLnNoYXBlLGQ9Zi5ob2xlcztaYy5pc0Nsb2NrV2lzZShwKT09PSExJiYocD1wLnJldmVyc2UoKSk7Zm9yKGxldCBfPTAseT1kLmxlbmd0aDtfPHk7XysrKXtsZXQgeD1kW19dO1pjLmlzQ2xvY2tXaXNlKHgpPT09ITAmJihkW19dPXgucmV2ZXJzZSgpKX1sZXQgZz1aYy50cmlhbmd1bGF0ZVNoYXBlKHAsZCk7Zm9yKGxldCBfPTAseT1kLmxlbmd0aDtfPHk7XysrKXtsZXQgeD1kW19dO3A9cC5jb25jYXQoeCl9Zm9yKGxldCBfPTAseT1wLmxlbmd0aDtfPHk7XysrKXtsZXQgeD1wW19dO2kucHVzaCh4LngseC55LDApLG8ucHVzaCgwLDAsMSksYS5wdXNoKHgueCx4LnkpfWZvcihsZXQgXz0wLHk9Zy5sZW5ndGg7Xzx5O18rKyl7bGV0IHg9Z1tfXSxiPXhbMF0raCxTPXhbMV0raCxDPXhbMl0raDtuLnB1c2goYixTLEMpLGwrPTN9fX10b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKSxyPXRoaXMucGFyYW1ldGVycy5zaGFwZXM7cmV0dXJuIGkwcihyLHQpfXN0YXRpYyBmcm9tSlNPTih0LHIpe2xldCBuPVtdO2ZvcihsZXQgaT0wLG89dC5zaGFwZXMubGVuZ3RoO2k8bztpKyspe2xldCBhPXJbdC5zaGFwZXNbaV1dO24ucHVzaChhKX1yZXR1cm4gbmV3IFkwKG4sdC5jdXJ2ZVNlZ21lbnRzKX19O2Z1bmN0aW9uIGkwcihlLHQpe2lmKHQuc2hhcGVzPVtdLEFycmF5LmlzQXJyYXkoZSkpZm9yKGxldCByPTAsbj1lLmxlbmd0aDtyPG47cisrKXtsZXQgaT1lW3JdO3Quc2hhcGVzLnB1c2goaS51dWlkKX1lbHNlIHQuc2hhcGVzLnB1c2goZS51dWlkKTtyZXR1cm4gdH12YXIgajA9Y2xhc3MgZXh0ZW5kcyBQZXtjb25zdHJ1Y3Rvcih0PTEscj0zMixuPTE2LGk9MCxvPU1hdGguUEkqMixhPTAscz1NYXRoLlBJKXtzdXBlcigpLHRoaXMudHlwZT0iU3BoZXJlR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17cmFkaXVzOnQsd2lkdGhTZWdtZW50czpyLGhlaWdodFNlZ21lbnRzOm4scGhpU3RhcnQ6aSxwaGlMZW5ndGg6byx0aGV0YVN0YXJ0OmEsdGhldGFMZW5ndGg6c30scj1NYXRoLm1heCgzLE1hdGguZmxvb3IocikpLG49TWF0aC5tYXgoMixNYXRoLmZsb29yKG4pKTtsZXQgbD1NYXRoLm1pbihhK3MsTWF0aC5QSSksYz0wLHU9W10saD1uZXcgaixmPW5ldyBqLHA9W10sZD1bXSxnPVtdLF89W107Zm9yKGxldCB5PTA7eTw9bjt5Kyspe2xldCB4PVtdLGI9eS9uLFM9MDt5PT0wJiZhPT0wP1M9LjUvcjp5PT1uJiZsPT1NYXRoLlBJJiYoUz0tLjUvcik7Zm9yKGxldCBDPTA7Qzw9cjtDKyspe2xldCBQPUMvcjtoLng9LXQqTWF0aC5jb3MoaStQKm8pKk1hdGguc2luKGErYipzKSxoLnk9dCpNYXRoLmNvcyhhK2IqcyksaC56PXQqTWF0aC5zaW4oaStQKm8pKk1hdGguc2luKGErYipzKSxkLnB1c2goaC54LGgueSxoLnopLGYuY29weShoKS5ub3JtYWxpemUoKSxnLnB1c2goZi54LGYueSxmLnopLF8ucHVzaChQK1MsMS1iKSx4LnB1c2goYysrKX11LnB1c2goeCl9Zm9yKGxldCB5PTA7eTxuO3krKylmb3IobGV0IHg9MDt4PHI7eCsrKXtsZXQgYj11W3ldW3grMV0sUz11W3ldW3hdLEM9dVt5KzFdW3hdLFA9dVt5KzFdW3grMV07KHkhPT0wfHxhPjApJiZwLnB1c2goYixTLFApLCh5IT09bi0xfHxsPE1hdGguUEkpJiZwLnB1c2goUyxDLFApfXRoaXMuc2V0SW5kZXgocCksdGhpcy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUoZCwzKSksdGhpcy5zZXRBdHRyaWJ1dGUoIm5vcm1hbCIsbmV3IHhlKGcsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJ1diIsbmV3IHhlKF8sMikpfXN0YXRpYyBmcm9tSlNPTih0KXtyZXR1cm4gbmV3IGowKHQucmFkaXVzLHQud2lkdGhTZWdtZW50cyx0LmhlaWdodFNlZ21lbnRzLHQucGhpU3RhcnQsdC5waGlMZW5ndGgsdC50aGV0YVN0YXJ0LHQudGhldGFMZW5ndGgpfX0sanY9Y2xhc3MgZXh0ZW5kcyB1aHtjb25zdHJ1Y3Rvcih0PTEscj0wKXtsZXQgbj1bMSwxLDEsLTEsLTEsMSwtMSwxLC0xLDEsLTEsLTFdLGk9WzIsMSwwLDAsMywyLDEsMywwLDIsMywxXTtzdXBlcihuLGksdCxyKSx0aGlzLnR5cGU9IlRldHJhaGVkcm9uR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17cmFkaXVzOnQsZGV0YWlsOnJ9fXN0YXRpYyBmcm9tSlNPTih0KXtyZXR1cm4gbmV3IGp2KHQucmFkaXVzLHQuZGV0YWlsKX19LFh2PWNsYXNzIGV4dGVuZHMgUGV7Y29uc3RydWN0b3IodD0xLHI9LjQsbj04LGk9NixvPU1hdGguUEkqMil7c3VwZXIoKSx0aGlzLnR5cGU9IlRvcnVzR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17cmFkaXVzOnQsdHViZTpyLHJhZGlhbFNlZ21lbnRzOm4sdHVidWxhclNlZ21lbnRzOmksYXJjOm99LG49TWF0aC5mbG9vcihuKSxpPU1hdGguZmxvb3IoaSk7bGV0IGE9W10scz1bXSxsPVtdLGM9W10sdT1uZXcgaixoPW5ldyBqLGY9bmV3IGo7Zm9yKGxldCBwPTA7cDw9bjtwKyspZm9yKGxldCBkPTA7ZDw9aTtkKyspe2xldCBnPWQvaSpvLF89cC9uKk1hdGguUEkqMjtoLng9KHQrcipNYXRoLmNvcyhfKSkqTWF0aC5jb3MoZyksaC55PSh0K3IqTWF0aC5jb3MoXykpKk1hdGguc2luKGcpLGguej1yKk1hdGguc2luKF8pLHMucHVzaChoLngsaC55LGgueiksdS54PXQqTWF0aC5jb3MoZyksdS55PXQqTWF0aC5zaW4oZyksZi5zdWJWZWN0b3JzKGgsdSkubm9ybWFsaXplKCksbC5wdXNoKGYueCxmLnksZi56KSxjLnB1c2goZC9pKSxjLnB1c2gocC9uKX1mb3IobGV0IHA9MTtwPD1uO3ArKylmb3IobGV0IGQ9MTtkPD1pO2QrKyl7bGV0IGc9KGkrMSkqcCtkLTEsXz0oaSsxKSoocC0xKStkLTEseT0oaSsxKSoocC0xKStkLHg9KGkrMSkqcCtkO2EucHVzaChnLF8seCksYS5wdXNoKF8seSx4KX10aGlzLnNldEluZGV4KGEpLHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IHhlKHMsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG5ldyB4ZShsLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyB4ZShjLDIpKX1zdGF0aWMgZnJvbUpTT04odCl7cmV0dXJuIG5ldyBYdih0LnJhZGl1cyx0LnR1YmUsdC5yYWRpYWxTZWdtZW50cyx0LnR1YnVsYXJTZWdtZW50cyx0LmFyYyl9fSwkdj1jbGFzcyBleHRlbmRzIFBle2NvbnN0cnVjdG9yKHQ9MSxyPS40LG49NjQsaT04LG89MixhPTMpe3N1cGVyKCksdGhpcy50eXBlPSJUb3J1c0tub3RHZW9tZXRyeSIsdGhpcy5wYXJhbWV0ZXJzPXtyYWRpdXM6dCx0dWJlOnIsdHVidWxhclNlZ21lbnRzOm4scmFkaWFsU2VnbWVudHM6aSxwOm8scTphfSxuPU1hdGguZmxvb3IobiksaT1NYXRoLmZsb29yKGkpO2xldCBzPVtdLGw9W10sYz1bXSx1PVtdLGg9bmV3IGosZj1uZXcgaixwPW5ldyBqLGQ9bmV3IGosZz1uZXcgaixfPW5ldyBqLHk9bmV3IGo7Zm9yKGxldCBiPTA7Yjw9bjsrK2Ipe2xldCBTPWIvbipvKk1hdGguUEkqMjt4KFMsbyxhLHQscCkseChTKy4wMSxvLGEsdCxkKSxfLnN1YlZlY3RvcnMoZCxwKSx5LmFkZFZlY3RvcnMoZCxwKSxnLmNyb3NzVmVjdG9ycyhfLHkpLHkuY3Jvc3NWZWN0b3JzKGcsXyksZy5ub3JtYWxpemUoKSx5Lm5vcm1hbGl6ZSgpO2ZvcihsZXQgQz0wO0M8PWk7KytDKXtsZXQgUD1DL2kqTWF0aC5QSSoyLGs9LXIqTWF0aC5jb3MoUCksTz1yKk1hdGguc2luKFApO2gueD1wLngrKGsqeS54K08qZy54KSxoLnk9cC55KyhrKnkueStPKmcueSksaC56PXAueisoayp5LnorTypnLnopLGwucHVzaChoLngsaC55LGgueiksZi5zdWJWZWN0b3JzKGgscCkubm9ybWFsaXplKCksYy5wdXNoKGYueCxmLnksZi56KSx1LnB1c2goYi9uKSx1LnB1c2goQy9pKX19Zm9yKGxldCBiPTE7Yjw9bjtiKyspZm9yKGxldCBTPTE7Uzw9aTtTKyspe2xldCBDPShpKzEpKihiLTEpKyhTLTEpLFA9KGkrMSkqYisoUy0xKSxrPShpKzEpKmIrUyxPPShpKzEpKihiLTEpK1M7cy5wdXNoKEMsUCxPKSxzLnB1c2goUCxrLE8pfXRoaXMuc2V0SW5kZXgocyksdGhpcy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUobCwzKSksdGhpcy5zZXRBdHRyaWJ1dGUoIm5vcm1hbCIsbmV3IHhlKGMsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJ1diIsbmV3IHhlKHUsMikpO2Z1bmN0aW9uIHgoYixTLEMsUCxrKXtsZXQgTz1NYXRoLmNvcyhiKSxEPU1hdGguc2luKGIpLEI9Qy9TKmIsST1NYXRoLmNvcyhCKTtrLng9UCooMitJKSouNSpPLGsueT1QKigyK0kpKkQqLjUsay56PVAqTWF0aC5zaW4oQikqLjV9fXN0YXRpYyBmcm9tSlNPTih0KXtyZXR1cm4gbmV3ICR2KHQucmFkaXVzLHQudHViZSx0LnR1YnVsYXJTZWdtZW50cyx0LnJhZGlhbFNlZ21lbnRzLHQucCx0LnEpfX0sS3Y9Y2xhc3MgZXh0ZW5kcyBQZXtjb25zdHJ1Y3Rvcih0PW5ldyBoTShuZXcgaigtMSwtMSwwKSxuZXcgaigtMSwxLDApLG5ldyBqKDEsMSwwKSkscj02NCxuPTEsaT04LG89ITEpe3N1cGVyKCksdGhpcy50eXBlPSJUdWJlR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17cGF0aDp0LHR1YnVsYXJTZWdtZW50czpyLHJhZGl1czpuLHJhZGlhbFNlZ21lbnRzOmksY2xvc2VkOm99O2xldCBhPXQuY29tcHV0ZUZyZW5ldEZyYW1lcyhyLG8pO3RoaXMudGFuZ2VudHM9YS50YW5nZW50cyx0aGlzLm5vcm1hbHM9YS5ub3JtYWxzLHRoaXMuYmlub3JtYWxzPWEuYmlub3JtYWxzO2xldCBzPW5ldyBqLGw9bmV3IGosYz1uZXcgTHQsdT1uZXcgaixoPVtdLGY9W10scD1bXSxkPVtdO2coKSx0aGlzLnNldEluZGV4KGQpLHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IHhlKGgsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG5ldyB4ZShmLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyB4ZShwLDIpKTtmdW5jdGlvbiBnKCl7Zm9yKGxldCBiPTA7YjxyO2IrKylfKGIpO18obz09PSExP3I6MCkseCgpLHkoKX1mdW5jdGlvbiBfKGIpe3U9dC5nZXRQb2ludEF0KGIvcix1KTtsZXQgUz1hLm5vcm1hbHNbYl0sQz1hLmJpbm9ybWFsc1tiXTtmb3IobGV0IFA9MDtQPD1pO1ArKyl7bGV0IGs9UC9pKk1hdGguUEkqMixPPU1hdGguc2luKGspLEQ9LU1hdGguY29zKGspO2wueD1EKlMueCtPKkMueCxsLnk9RCpTLnkrTypDLnksbC56PUQqUy56K08qQy56LGwubm9ybWFsaXplKCksZi5wdXNoKGwueCxsLnksbC56KSxzLng9dS54K24qbC54LHMueT11LnkrbipsLnkscy56PXUueituKmwueixoLnB1c2gocy54LHMueSxzLnopfX1mdW5jdGlvbiB5KCl7Zm9yKGxldCBiPTE7Yjw9cjtiKyspZm9yKGxldCBTPTE7Uzw9aTtTKyspe2xldCBDPShpKzEpKihiLTEpKyhTLTEpLFA9KGkrMSkqYisoUy0xKSxrPShpKzEpKmIrUyxPPShpKzEpKihiLTEpK1M7ZC5wdXNoKEMsUCxPKSxkLnB1c2goUCxrLE8pfX1mdW5jdGlvbiB4KCl7Zm9yKGxldCBiPTA7Yjw9cjtiKyspZm9yKGxldCBTPTA7Uzw9aTtTKyspYy54PWIvcixjLnk9Uy9pLHAucHVzaChjLngsYy55KX19dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIHQucGF0aD10aGlzLnBhcmFtZXRlcnMucGF0aC50b0pTT04oKSx0fXN0YXRpYyBmcm9tSlNPTih0KXtyZXR1cm4gbmV3IEt2KG5ldyB6aHRbdC5wYXRoLnR5cGVdKCkuZnJvbUpTT04odC5wYXRoKSx0LnR1YnVsYXJTZWdtZW50cyx0LnJhZGl1cyx0LnJhZGlhbFNlZ21lbnRzLHQuY2xvc2VkKX19LHA2PWNsYXNzIGV4dGVuZHMgUGV7Y29uc3RydWN0b3IodD1udWxsKXtpZihzdXBlcigpLHRoaXMudHlwZT0iV2lyZWZyYW1lR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17Z2VvbWV0cnk6dH0sdCE9PW51bGwpe2xldCByPVtdLG49bmV3IFNldCxpPW5ldyBqLG89bmV3IGo7aWYodC5pbmRleCE9PW51bGwpe2xldCBhPXQuYXR0cmlidXRlcy5wb3NpdGlvbixzPXQuaW5kZXgsbD10Lmdyb3VwcztsLmxlbmd0aD09PTAmJihsPVt7c3RhcnQ6MCxjb3VudDpzLmNvdW50LG1hdGVyaWFsSW5kZXg6MH1dKTtmb3IobGV0IGM9MCx1PWwubGVuZ3RoO2M8dTsrK2Mpe2xldCBoPWxbY10sZj1oLnN0YXJ0LHA9aC5jb3VudDtmb3IobGV0IGQ9ZixnPWYrcDtkPGc7ZCs9Mylmb3IobGV0IF89MDtfPDM7XysrKXtsZXQgeT1zLmdldFgoZCtfKSx4PXMuZ2V0WChkKyhfKzEpJTMpO2kuZnJvbUJ1ZmZlckF0dHJpYnV0ZShhLHkpLG8uZnJvbUJ1ZmZlckF0dHJpYnV0ZShhLHgpLHBoZShpLG8sbik9PT0hMCYmKHIucHVzaChpLngsaS55LGkueiksci5wdXNoKG8ueCxvLnksby56KSl9fX1lbHNle2xldCBhPXQuYXR0cmlidXRlcy5wb3NpdGlvbjtmb3IobGV0IHM9MCxsPWEuY291bnQvMztzPGw7cysrKWZvcihsZXQgYz0wO2M8MztjKyspe2xldCB1PTMqcytjLGg9MypzKyhjKzEpJTM7aS5mcm9tQnVmZmVyQXR0cmlidXRlKGEsdSksby5mcm9tQnVmZmVyQXR0cmlidXRlKGEsaCkscGhlKGksbyxuKT09PSEwJiYoci5wdXNoKGkueCxpLnksaS56KSxyLnB1c2goby54LG8ueSxvLnopKX19dGhpcy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUociwzKSl9fX07ZnVuY3Rpb24gcGhlKGUsdCxyKXtsZXQgbj1gJHtlLnh9LCR7ZS55fSwke2Uuen0tJHt0Lnh9LCR7dC55fSwke3Quen1gLGk9YCR7dC54fSwke3QueX0sJHt0Lnp9LSR7ZS54fSwke2UueX0sJHtlLnp9YDtyZXR1cm4gci5oYXMobik9PT0hMHx8ci5oYXMoaSk9PT0hMD8hMTooci5hZGQobixpKSwhMCl9dmFyIGRoZT1PYmplY3QuZnJlZXplKHtfX3Byb3RvX186bnVsbCxCb3hHZW9tZXRyeTpRZixCb3hCdWZmZXJHZW9tZXRyeTpRZixDaXJjbGVHZW9tZXRyeTpGdixDaXJjbGVCdWZmZXJHZW9tZXRyeTpGdixDb25lR2VvbWV0cnk6QnYsQ29uZUJ1ZmZlckdlb21ldHJ5OkJ2LEN5bGluZGVyR2VvbWV0cnk6b20sQ3lsaW5kZXJCdWZmZXJHZW9tZXRyeTpvbSxEb2RlY2FoZWRyb25HZW9tZXRyeTpIdixEb2RlY2FoZWRyb25CdWZmZXJHZW9tZXRyeTpIdixFZGdlc0dlb21ldHJ5OmE2LEV4dHJ1ZGVHZW9tZXRyeTpoaCxFeHRydWRlQnVmZmVyR2VvbWV0cnk6aGgsSWNvc2FoZWRyb25HZW9tZXRyeTpHdixJY29zYWhlZHJvbkJ1ZmZlckdlb21ldHJ5Okd2LExhdGhlR2VvbWV0cnk6V3YsTGF0aGVCdWZmZXJHZW9tZXRyeTpXdixPY3RhaGVkcm9uR2VvbWV0cnk6VzAsT2N0YWhlZHJvbkJ1ZmZlckdlb21ldHJ5OlcwLFBsYW5lR2VvbWV0cnk6VjAsUGxhbmVCdWZmZXJHZW9tZXRyeTpWMCxQb2x5aGVkcm9uR2VvbWV0cnk6dWgsUG9seWhlZHJvbkJ1ZmZlckdlb21ldHJ5OnVoLFJpbmdHZW9tZXRyeTpZdixSaW5nQnVmZmVyR2VvbWV0cnk6WXYsU2hhcGVHZW9tZXRyeTpZMCxTaGFwZUJ1ZmZlckdlb21ldHJ5OlkwLFNwaGVyZUdlb21ldHJ5OmowLFNwaGVyZUJ1ZmZlckdlb21ldHJ5OmowLFRldHJhaGVkcm9uR2VvbWV0cnk6anYsVGV0cmFoZWRyb25CdWZmZXJHZW9tZXRyeTpqdixUb3J1c0dlb21ldHJ5Olh2LFRvcnVzQnVmZmVyR2VvbWV0cnk6WHYsVG9ydXNLbm90R2VvbWV0cnk6JHYsVG9ydXNLbm90QnVmZmVyR2VvbWV0cnk6JHYsVHViZUdlb21ldHJ5Okt2LFR1YmVCdWZmZXJHZW9tZXRyeTpLdixXaXJlZnJhbWVHZW9tZXRyeTpwNn0pLGQ2PWNsYXNzIGV4dGVuZHMgcWl7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9IlNoYWRvd01hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyBuZSgwKSx0aGlzLnRyYW5zcGFyZW50PSEwLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXN9fTtkNi5wcm90b3R5cGUuaXNTaGFkb3dNYXRlcmlhbD0hMDt2YXIgcE09Y2xhc3MgZXh0ZW5kcyBxaXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMuZGVmaW5lcz17U1RBTkRBUkQ6IiJ9LHRoaXMudHlwZT0iTWVzaFN0YW5kYXJkTWF0ZXJpYWwiLHRoaXMuY29sb3I9bmV3IG5lKDE2Nzc3MjE1KSx0aGlzLnJvdWdobmVzcz0xLHRoaXMubWV0YWxuZXNzPTAsdGhpcy5tYXA9bnVsbCx0aGlzLmxpZ2h0TWFwPW51bGwsdGhpcy5saWdodE1hcEludGVuc2l0eT0xLHRoaXMuYW9NYXA9bnVsbCx0aGlzLmFvTWFwSW50ZW5zaXR5PTEsdGhpcy5lbWlzc2l2ZT1uZXcgbmUoMCksdGhpcy5lbWlzc2l2ZUludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmVNYXA9bnVsbCx0aGlzLmJ1bXBNYXA9bnVsbCx0aGlzLmJ1bXBTY2FsZT0xLHRoaXMubm9ybWFsTWFwPW51bGwsdGhpcy5ub3JtYWxNYXBUeXBlPWF4LHRoaXMubm9ybWFsU2NhbGU9bmV3IEx0KDEsMSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9bnVsbCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPTEsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPTAsdGhpcy5yb3VnaG5lc3NNYXA9bnVsbCx0aGlzLm1ldGFsbmVzc01hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLmVudk1hcD1udWxsLHRoaXMuZW52TWFwSW50ZW5zaXR5PTEsdGhpcy5yZWZyYWN0aW9uUmF0aW89Ljk4LHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy53aXJlZnJhbWVMaW5lY2FwPSJyb3VuZCIsdGhpcy53aXJlZnJhbWVMaW5lam9pbj0icm91bmQiLHRoaXMuZmxhdFNoYWRpbmc9ITEsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmRlZmluZXM9e1NUQU5EQVJEOiIifSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5yb3VnaG5lc3M9dC5yb3VnaG5lc3MsdGhpcy5tZXRhbG5lc3M9dC5tZXRhbG5lc3MsdGhpcy5tYXA9dC5tYXAsdGhpcy5saWdodE1hcD10LmxpZ2h0TWFwLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9dC5saWdodE1hcEludGVuc2l0eSx0aGlzLmFvTWFwPXQuYW9NYXAsdGhpcy5hb01hcEludGVuc2l0eT10LmFvTWFwSW50ZW5zaXR5LHRoaXMuZW1pc3NpdmUuY29weSh0LmVtaXNzaXZlKSx0aGlzLmVtaXNzaXZlTWFwPXQuZW1pc3NpdmVNYXAsdGhpcy5lbWlzc2l2ZUludGVuc2l0eT10LmVtaXNzaXZlSW50ZW5zaXR5LHRoaXMuYnVtcE1hcD10LmJ1bXBNYXAsdGhpcy5idW1wU2NhbGU9dC5idW1wU2NhbGUsdGhpcy5ub3JtYWxNYXA9dC5ub3JtYWxNYXAsdGhpcy5ub3JtYWxNYXBUeXBlPXQubm9ybWFsTWFwVHlwZSx0aGlzLm5vcm1hbFNjYWxlLmNvcHkodC5ub3JtYWxTY2FsZSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9dC5kaXNwbGFjZW1lbnRNYXAsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT10LmRpc3BsYWNlbWVudFNjYWxlLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz10LmRpc3BsYWNlbWVudEJpYXMsdGhpcy5yb3VnaG5lc3NNYXA9dC5yb3VnaG5lc3NNYXAsdGhpcy5tZXRhbG5lc3NNYXA9dC5tZXRhbG5lc3NNYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZW52TWFwPXQuZW52TWFwLHRoaXMuZW52TWFwSW50ZW5zaXR5PXQuZW52TWFwSW50ZW5zaXR5LHRoaXMucmVmcmFjdGlvblJhdGlvPXQucmVmcmFjdGlvblJhdGlvLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXMud2lyZWZyYW1lTGluZWNhcD10LndpcmVmcmFtZUxpbmVjYXAsdGhpcy53aXJlZnJhbWVMaW5lam9pbj10LndpcmVmcmFtZUxpbmVqb2luLHRoaXMuZmxhdFNoYWRpbmc9dC5mbGF0U2hhZGluZyx0aGlzfX07cE0ucHJvdG90eXBlLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWw9ITA7dmFyIG02PWNsYXNzIGV4dGVuZHMgcE17Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLmRlZmluZXM9e1NUQU5EQVJEOiIiLFBIWVNJQ0FMOiIifSx0aGlzLnR5cGU9Ik1lc2hQaHlzaWNhbE1hdGVyaWFsIix0aGlzLmNsZWFyY29hdE1hcD1udWxsLHRoaXMuY2xlYXJjb2F0Um91Z2huZXNzPTAsdGhpcy5jbGVhcmNvYXRSb3VnaG5lc3NNYXA9bnVsbCx0aGlzLmNsZWFyY29hdE5vcm1hbFNjYWxlPW5ldyBMdCgxLDEpLHRoaXMuY2xlYXJjb2F0Tm9ybWFsTWFwPW51bGwsdGhpcy5pb3I9MS41LE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCJyZWZsZWN0aXZpdHkiLHtnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gWm8oMi41Kih0aGlzLmlvci0xKS8odGhpcy5pb3IrMSksMCwxKX0sc2V0OmZ1bmN0aW9uKHIpe3RoaXMuaW9yPSgxKy40KnIpLygxLS40KnIpfX0pLHRoaXMuc2hlZW5Db2xvcj1uZXcgbmUoMCksdGhpcy5zaGVlbkNvbG9yTWFwPW51bGwsdGhpcy5zaGVlblJvdWdobmVzcz0xLHRoaXMuc2hlZW5Sb3VnaG5lc3NNYXA9bnVsbCx0aGlzLnRyYW5zbWlzc2lvbk1hcD1udWxsLHRoaXMudGhpY2tuZXNzPTAsdGhpcy50aGlja25lc3NNYXA9bnVsbCx0aGlzLmF0dGVudWF0aW9uRGlzdGFuY2U9MCx0aGlzLmF0dGVudWF0aW9uQ29sb3I9bmV3IG5lKDEsMSwxKSx0aGlzLnNwZWN1bGFySW50ZW5zaXR5PTEsdGhpcy5zcGVjdWxhckludGVuc2l0eU1hcD1udWxsLHRoaXMuc3BlY3VsYXJDb2xvcj1uZXcgbmUoMSwxLDEpLHRoaXMuc3BlY3VsYXJDb2xvck1hcD1udWxsLHRoaXMuX3NoZWVuPTAsdGhpcy5fY2xlYXJjb2F0PTAsdGhpcy5fdHJhbnNtaXNzaW9uPTAsdGhpcy5zZXRWYWx1ZXModCl9Z2V0IHNoZWVuKCl7cmV0dXJuIHRoaXMuX3NoZWVufXNldCBzaGVlbih0KXt0aGlzLl9zaGVlbj4wIT10PjAmJnRoaXMudmVyc2lvbisrLHRoaXMuX3NoZWVuPXR9Z2V0IGNsZWFyY29hdCgpe3JldHVybiB0aGlzLl9jbGVhcmNvYXR9c2V0IGNsZWFyY29hdCh0KXt0aGlzLl9jbGVhcmNvYXQ+MCE9dD4wJiZ0aGlzLnZlcnNpb24rKyx0aGlzLl9jbGVhcmNvYXQ9dH1nZXQgdHJhbnNtaXNzaW9uKCl7cmV0dXJuIHRoaXMuX3RyYW5zbWlzc2lvbn1zZXQgdHJhbnNtaXNzaW9uKHQpe3RoaXMuX3RyYW5zbWlzc2lvbj4wIT10PjAmJnRoaXMudmVyc2lvbisrLHRoaXMuX3RyYW5zbWlzc2lvbj10fWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5kZWZpbmVzPXtTVEFOREFSRDoiIixQSFlTSUNBTDoiIn0sdGhpcy5jbGVhcmNvYXQ9dC5jbGVhcmNvYXQsdGhpcy5jbGVhcmNvYXRNYXA9dC5jbGVhcmNvYXRNYXAsdGhpcy5jbGVhcmNvYXRSb3VnaG5lc3M9dC5jbGVhcmNvYXRSb3VnaG5lc3MsdGhpcy5jbGVhcmNvYXRSb3VnaG5lc3NNYXA9dC5jbGVhcmNvYXRSb3VnaG5lc3NNYXAsdGhpcy5jbGVhcmNvYXROb3JtYWxNYXA9dC5jbGVhcmNvYXROb3JtYWxNYXAsdGhpcy5jbGVhcmNvYXROb3JtYWxTY2FsZS5jb3B5KHQuY2xlYXJjb2F0Tm9ybWFsU2NhbGUpLHRoaXMuaW9yPXQuaW9yLHRoaXMuc2hlZW49dC5zaGVlbix0aGlzLnNoZWVuQ29sb3IuY29weSh0LnNoZWVuQ29sb3IpLHRoaXMuc2hlZW5Db2xvck1hcD10LnNoZWVuQ29sb3JNYXAsdGhpcy5zaGVlblJvdWdobmVzcz10LnNoZWVuUm91Z2huZXNzLHRoaXMuc2hlZW5Sb3VnaG5lc3NNYXA9dC5zaGVlblJvdWdobmVzc01hcCx0aGlzLnRyYW5zbWlzc2lvbj10LnRyYW5zbWlzc2lvbix0aGlzLnRyYW5zbWlzc2lvbk1hcD10LnRyYW5zbWlzc2lvbk1hcCx0aGlzLnRoaWNrbmVzcz10LnRoaWNrbmVzcyx0aGlzLnRoaWNrbmVzc01hcD10LnRoaWNrbmVzc01hcCx0aGlzLmF0dGVudWF0aW9uRGlzdGFuY2U9dC5hdHRlbnVhdGlvbkRpc3RhbmNlLHRoaXMuYXR0ZW51YXRpb25Db2xvci5jb3B5KHQuYXR0ZW51YXRpb25Db2xvciksdGhpcy5zcGVjdWxhckludGVuc2l0eT10LnNwZWN1bGFySW50ZW5zaXR5LHRoaXMuc3BlY3VsYXJJbnRlbnNpdHlNYXA9dC5zcGVjdWxhckludGVuc2l0eU1hcCx0aGlzLnNwZWN1bGFyQ29sb3IuY29weSh0LnNwZWN1bGFyQ29sb3IpLHRoaXMuc3BlY3VsYXJDb2xvck1hcD10LnNwZWN1bGFyQ29sb3JNYXAsdGhpc319O202LnByb3RvdHlwZS5pc01lc2hQaHlzaWNhbE1hdGVyaWFsPSEwO3ZhciBnNj1jbGFzcyBleHRlbmRzIHFpe2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJNZXNoUGhvbmdNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgbmUoMTY3NzcyMTUpLHRoaXMuc3BlY3VsYXI9bmV3IG5lKDExMTg0ODEpLHRoaXMuc2hpbmluZXNzPTMwLHRoaXMubWFwPW51bGwsdGhpcy5saWdodE1hcD1udWxsLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9MSx0aGlzLmFvTWFwPW51bGwsdGhpcy5hb01hcEludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmU9bmV3IG5lKDApLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9MSx0aGlzLmVtaXNzaXZlTWFwPW51bGwsdGhpcy5idW1wTWFwPW51bGwsdGhpcy5idW1wU2NhbGU9MSx0aGlzLm5vcm1hbE1hcD1udWxsLHRoaXMubm9ybWFsTWFwVHlwZT1heCx0aGlzLm5vcm1hbFNjYWxlPW5ldyBMdCgxLDEpLHRoaXMuZGlzcGxhY2VtZW50TWFwPW51bGwsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT0xLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz0wLHRoaXMuc3BlY3VsYXJNYXA9bnVsbCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy5lbnZNYXA9bnVsbCx0aGlzLmNvbWJpbmU9RDYsdGhpcy5yZWZsZWN0aXZpdHk9MSx0aGlzLnJlZnJhY3Rpb25SYXRpbz0uOTgsdGhpcy53aXJlZnJhbWU9ITEsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9MSx0aGlzLndpcmVmcmFtZUxpbmVjYXA9InJvdW5kIix0aGlzLndpcmVmcmFtZUxpbmVqb2luPSJyb3VuZCIsdGhpcy5mbGF0U2hhZGluZz0hMSx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuY29sb3IuY29weSh0LmNvbG9yKSx0aGlzLnNwZWN1bGFyLmNvcHkodC5zcGVjdWxhciksdGhpcy5zaGluaW5lc3M9dC5zaGluaW5lc3MsdGhpcy5tYXA9dC5tYXAsdGhpcy5saWdodE1hcD10LmxpZ2h0TWFwLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9dC5saWdodE1hcEludGVuc2l0eSx0aGlzLmFvTWFwPXQuYW9NYXAsdGhpcy5hb01hcEludGVuc2l0eT10LmFvTWFwSW50ZW5zaXR5LHRoaXMuZW1pc3NpdmUuY29weSh0LmVtaXNzaXZlKSx0aGlzLmVtaXNzaXZlTWFwPXQuZW1pc3NpdmVNYXAsdGhpcy5lbWlzc2l2ZUludGVuc2l0eT10LmVtaXNzaXZlSW50ZW5zaXR5LHRoaXMuYnVtcE1hcD10LmJ1bXBNYXAsdGhpcy5idW1wU2NhbGU9dC5idW1wU2NhbGUsdGhpcy5ub3JtYWxNYXA9dC5ub3JtYWxNYXAsdGhpcy5ub3JtYWxNYXBUeXBlPXQubm9ybWFsTWFwVHlwZSx0aGlzLm5vcm1hbFNjYWxlLmNvcHkodC5ub3JtYWxTY2FsZSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9dC5kaXNwbGFjZW1lbnRNYXAsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT10LmRpc3BsYWNlbWVudFNjYWxlLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz10LmRpc3BsYWNlbWVudEJpYXMsdGhpcy5zcGVjdWxhck1hcD10LnNwZWN1bGFyTWFwLHRoaXMuYWxwaGFNYXA9dC5hbHBoYU1hcCx0aGlzLmVudk1hcD10LmVudk1hcCx0aGlzLmNvbWJpbmU9dC5jb21iaW5lLHRoaXMucmVmbGVjdGl2aXR5PXQucmVmbGVjdGl2aXR5LHRoaXMucmVmcmFjdGlvblJhdGlvPXQucmVmcmFjdGlvblJhdGlvLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXMud2lyZWZyYW1lTGluZWNhcD10LndpcmVmcmFtZUxpbmVjYXAsdGhpcy53aXJlZnJhbWVMaW5lam9pbj10LndpcmVmcmFtZUxpbmVqb2luLHRoaXMuZmxhdFNoYWRpbmc9dC5mbGF0U2hhZGluZyx0aGlzfX07ZzYucHJvdG90eXBlLmlzTWVzaFBob25nTWF0ZXJpYWw9ITA7dmFyIF82PWNsYXNzIGV4dGVuZHMgcWl7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLmRlZmluZXM9e1RPT046IiJ9LHRoaXMudHlwZT0iTWVzaFRvb25NYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgbmUoMTY3NzcyMTUpLHRoaXMubWFwPW51bGwsdGhpcy5ncmFkaWVudE1hcD1udWxsLHRoaXMubGlnaHRNYXA9bnVsbCx0aGlzLmxpZ2h0TWFwSW50ZW5zaXR5PTEsdGhpcy5hb01hcD1udWxsLHRoaXMuYW9NYXBJbnRlbnNpdHk9MSx0aGlzLmVtaXNzaXZlPW5ldyBuZSgwKSx0aGlzLmVtaXNzaXZlSW50ZW5zaXR5PTEsdGhpcy5lbWlzc2l2ZU1hcD1udWxsLHRoaXMuYnVtcE1hcD1udWxsLHRoaXMuYnVtcFNjYWxlPTEsdGhpcy5ub3JtYWxNYXA9bnVsbCx0aGlzLm5vcm1hbE1hcFR5cGU9YXgsdGhpcy5ub3JtYWxTY2FsZT1uZXcgTHQoMSwxKSx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy53aXJlZnJhbWU9ITEsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9MSx0aGlzLndpcmVmcmFtZUxpbmVjYXA9InJvdW5kIix0aGlzLndpcmVmcmFtZUxpbmVqb2luPSJyb3VuZCIsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5tYXA9dC5tYXAsdGhpcy5ncmFkaWVudE1hcD10LmdyYWRpZW50TWFwLHRoaXMubGlnaHRNYXA9dC5saWdodE1hcCx0aGlzLmxpZ2h0TWFwSW50ZW5zaXR5PXQubGlnaHRNYXBJbnRlbnNpdHksdGhpcy5hb01hcD10LmFvTWFwLHRoaXMuYW9NYXBJbnRlbnNpdHk9dC5hb01hcEludGVuc2l0eSx0aGlzLmVtaXNzaXZlLmNvcHkodC5lbWlzc2l2ZSksdGhpcy5lbWlzc2l2ZU1hcD10LmVtaXNzaXZlTWFwLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9dC5lbWlzc2l2ZUludGVuc2l0eSx0aGlzLmJ1bXBNYXA9dC5idW1wTWFwLHRoaXMuYnVtcFNjYWxlPXQuYnVtcFNjYWxlLHRoaXMubm9ybWFsTWFwPXQubm9ybWFsTWFwLHRoaXMubm9ybWFsTWFwVHlwZT10Lm5vcm1hbE1hcFR5cGUsdGhpcy5ub3JtYWxTY2FsZS5jb3B5KHQubm9ybWFsU2NhbGUpLHRoaXMuZGlzcGxhY2VtZW50TWFwPXQuZGlzcGxhY2VtZW50TWFwLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9dC5kaXNwbGFjZW1lbnRTY2FsZSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9dC5kaXNwbGFjZW1lbnRCaWFzLHRoaXMuYWxwaGFNYXA9dC5hbHBoYU1hcCx0aGlzLndpcmVmcmFtZT10LndpcmVmcmFtZSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD10LndpcmVmcmFtZUxpbmV3aWR0aCx0aGlzLndpcmVmcmFtZUxpbmVjYXA9dC53aXJlZnJhbWVMaW5lY2FwLHRoaXMud2lyZWZyYW1lTGluZWpvaW49dC53aXJlZnJhbWVMaW5lam9pbix0aGlzfX07XzYucHJvdG90eXBlLmlzTWVzaFRvb25NYXRlcmlhbD0hMDt2YXIgeTY9Y2xhc3MgZXh0ZW5kcyBxaXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaE5vcm1hbE1hdGVyaWFsIix0aGlzLmJ1bXBNYXA9bnVsbCx0aGlzLmJ1bXBTY2FsZT0xLHRoaXMubm9ybWFsTWFwPW51bGwsdGhpcy5ub3JtYWxNYXBUeXBlPWF4LHRoaXMubm9ybWFsU2NhbGU9bmV3IEx0KDEsMSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9bnVsbCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPTEsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPTAsdGhpcy53aXJlZnJhbWU9ITEsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9MSx0aGlzLmZvZz0hMSx0aGlzLmZsYXRTaGFkaW5nPSExLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5idW1wTWFwPXQuYnVtcE1hcCx0aGlzLmJ1bXBTY2FsZT10LmJ1bXBTY2FsZSx0aGlzLm5vcm1hbE1hcD10Lm5vcm1hbE1hcCx0aGlzLm5vcm1hbE1hcFR5cGU9dC5ub3JtYWxNYXBUeXBlLHRoaXMubm9ybWFsU2NhbGUuY29weSh0Lm5vcm1hbFNjYWxlKSx0aGlzLmRpc3BsYWNlbWVudE1hcD10LmRpc3BsYWNlbWVudE1hcCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPXQuZGlzcGxhY2VtZW50U2NhbGUsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPXQuZGlzcGxhY2VtZW50Qmlhcyx0aGlzLndpcmVmcmFtZT10LndpcmVmcmFtZSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD10LndpcmVmcmFtZUxpbmV3aWR0aCx0aGlzLmZsYXRTaGFkaW5nPXQuZmxhdFNoYWRpbmcsdGhpc319O3k2LnByb3RvdHlwZS5pc01lc2hOb3JtYWxNYXRlcmlhbD0hMDt2YXIgdjY9Y2xhc3MgZXh0ZW5kcyBxaXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaExhbWJlcnRNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgbmUoMTY3NzcyMTUpLHRoaXMubWFwPW51bGwsdGhpcy5saWdodE1hcD1udWxsLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9MSx0aGlzLmFvTWFwPW51bGwsdGhpcy5hb01hcEludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmU9bmV3IG5lKDApLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9MSx0aGlzLmVtaXNzaXZlTWFwPW51bGwsdGhpcy5zcGVjdWxhck1hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLmVudk1hcD1udWxsLHRoaXMuY29tYmluZT1ENix0aGlzLnJlZmxlY3Rpdml0eT0xLHRoaXMucmVmcmFjdGlvblJhdGlvPS45OCx0aGlzLndpcmVmcmFtZT0hMSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD0xLHRoaXMud2lyZWZyYW1lTGluZWNhcD0icm91bmQiLHRoaXMud2lyZWZyYW1lTGluZWpvaW49InJvdW5kIix0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuY29sb3IuY29weSh0LmNvbG9yKSx0aGlzLm1hcD10Lm1hcCx0aGlzLmxpZ2h0TWFwPXQubGlnaHRNYXAsdGhpcy5saWdodE1hcEludGVuc2l0eT10LmxpZ2h0TWFwSW50ZW5zaXR5LHRoaXMuYW9NYXA9dC5hb01hcCx0aGlzLmFvTWFwSW50ZW5zaXR5PXQuYW9NYXBJbnRlbnNpdHksdGhpcy5lbWlzc2l2ZS5jb3B5KHQuZW1pc3NpdmUpLHRoaXMuZW1pc3NpdmVNYXA9dC5lbWlzc2l2ZU1hcCx0aGlzLmVtaXNzaXZlSW50ZW5zaXR5PXQuZW1pc3NpdmVJbnRlbnNpdHksdGhpcy5zcGVjdWxhck1hcD10LnNwZWN1bGFyTWFwLHRoaXMuYWxwaGFNYXA9dC5hbHBoYU1hcCx0aGlzLmVudk1hcD10LmVudk1hcCx0aGlzLmNvbWJpbmU9dC5jb21iaW5lLHRoaXMucmVmbGVjdGl2aXR5PXQucmVmbGVjdGl2aXR5LHRoaXMucmVmcmFjdGlvblJhdGlvPXQucmVmcmFjdGlvblJhdGlvLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXMud2lyZWZyYW1lTGluZWNhcD10LndpcmVmcmFtZUxpbmVjYXAsdGhpcy53aXJlZnJhbWVMaW5lam9pbj10LndpcmVmcmFtZUxpbmVqb2luLHRoaXN9fTt2Ni5wcm90b3R5cGUuaXNNZXNoTGFtYmVydE1hdGVyaWFsPSEwO3ZhciB4Nj1jbGFzcyBleHRlbmRzIHFpe2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5kZWZpbmVzPXtNQVRDQVA6IiJ9LHRoaXMudHlwZT0iTWVzaE1hdGNhcE1hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyBuZSgxNjc3NzIxNSksdGhpcy5tYXRjYXA9bnVsbCx0aGlzLm1hcD1udWxsLHRoaXMuYnVtcE1hcD1udWxsLHRoaXMuYnVtcFNjYWxlPTEsdGhpcy5ub3JtYWxNYXA9bnVsbCx0aGlzLm5vcm1hbE1hcFR5cGU9YXgsdGhpcy5ub3JtYWxTY2FsZT1uZXcgTHQoMSwxKSx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy5mbGF0U2hhZGluZz0hMSx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuZGVmaW5lcz17TUFUQ0FQOiIifSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5tYXRjYXA9dC5tYXRjYXAsdGhpcy5tYXA9dC5tYXAsdGhpcy5idW1wTWFwPXQuYnVtcE1hcCx0aGlzLmJ1bXBTY2FsZT10LmJ1bXBTY2FsZSx0aGlzLm5vcm1hbE1hcD10Lm5vcm1hbE1hcCx0aGlzLm5vcm1hbE1hcFR5cGU9dC5ub3JtYWxNYXBUeXBlLHRoaXMubm9ybWFsU2NhbGUuY29weSh0Lm5vcm1hbFNjYWxlKSx0aGlzLmRpc3BsYWNlbWVudE1hcD10LmRpc3BsYWNlbWVudE1hcCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPXQuZGlzcGxhY2VtZW50U2NhbGUsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPXQuZGlzcGxhY2VtZW50Qmlhcyx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5mbGF0U2hhZGluZz10LmZsYXRTaGFkaW5nLHRoaXN9fTt4Ni5wcm90b3R5cGUuaXNNZXNoTWF0Y2FwTWF0ZXJpYWw9ITA7dmFyIGI2PWNsYXNzIGV4dGVuZHMgR2l7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9IkxpbmVEYXNoZWRNYXRlcmlhbCIsdGhpcy5zY2FsZT0xLHRoaXMuZGFzaFNpemU9Myx0aGlzLmdhcFNpemU9MSx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuc2NhbGU9dC5zY2FsZSx0aGlzLmRhc2hTaXplPXQuZGFzaFNpemUsdGhpcy5nYXBTaXplPXQuZ2FwU2l6ZSx0aGlzfX07YjYucHJvdG90eXBlLmlzTGluZURhc2hlZE1hdGVyaWFsPSEwO3ZhciBvMHI9T2JqZWN0LmZyZWV6ZSh7X19wcm90b19fOm51bGwsU2hhZG93TWF0ZXJpYWw6ZDYsU3ByaXRlTWF0ZXJpYWw6aU0sUmF3U2hhZGVyTWF0ZXJpYWw6VTAsU2hhZGVyTWF0ZXJpYWw6bGgsUG9pbnRzTWF0ZXJpYWw6bm0sTWVzaFBoeXNpY2FsTWF0ZXJpYWw6bTYsTWVzaFN0YW5kYXJkTWF0ZXJpYWw6cE0sTWVzaFBob25nTWF0ZXJpYWw6ZzYsTWVzaFRvb25NYXRlcmlhbDpfNixNZXNoTm9ybWFsTWF0ZXJpYWw6eTYsTWVzaExhbWJlcnRNYXRlcmlhbDp2NixNZXNoRGVwdGhNYXRlcmlhbDplTSxNZXNoRGlzdGFuY2VNYXRlcmlhbDpyTSxNZXNoQmFzaWNNYXRlcmlhbDpzaCxNZXNoTWF0Y2FwTWF0ZXJpYWw6eDYsTGluZURhc2hlZE1hdGVyaWFsOmI2LExpbmVCYXNpY01hdGVyaWFsOkdpLE1hdGVyaWFsOnFpfSksam49e2FycmF5U2xpY2U6ZnVuY3Rpb24oZSx0LHIpe3JldHVybiBqbi5pc1R5cGVkQXJyYXkoZSk/bmV3IGUuY29uc3RydWN0b3IoZS5zdWJhcnJheSh0LHIhPT12b2lkIDA/cjplLmxlbmd0aCkpOmUuc2xpY2UodCxyKX0sY29udmVydEFycmF5OmZ1bmN0aW9uKGUsdCxyKXtyZXR1cm4hZXx8IXImJmUuY29uc3RydWN0b3I9PT10P2U6dHlwZW9mIHQuQllURVNfUEVSX0VMRU1FTlQ9PSJudW1iZXIiP25ldyB0KGUpOkFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGUpfSxpc1R5cGVkQXJyYXk6ZnVuY3Rpb24oZSl7cmV0dXJuIEFycmF5QnVmZmVyLmlzVmlldyhlKSYmIShlIGluc3RhbmNlb2YgRGF0YVZpZXcpfSxnZXRLZXlmcmFtZU9yZGVyOmZ1bmN0aW9uKGUpe2Z1bmN0aW9uIHQoaSxvKXtyZXR1cm4gZVtpXS1lW29dfWxldCByPWUubGVuZ3RoLG49bmV3IEFycmF5KHIpO2ZvcihsZXQgaT0wO2khPT1yOysraSluW2ldPWk7cmV0dXJuIG4uc29ydCh0KSxufSxzb3J0ZWRBcnJheTpmdW5jdGlvbihlLHQscil7bGV0IG49ZS5sZW5ndGgsaT1uZXcgZS5jb25zdHJ1Y3RvcihuKTtmb3IobGV0IG89MCxhPTA7YSE9PW47KytvKXtsZXQgcz1yW29dKnQ7Zm9yKGxldCBsPTA7bCE9PXQ7KytsKWlbYSsrXT1lW3MrbF19cmV0dXJuIGl9LGZsYXR0ZW5KU09OOmZ1bmN0aW9uKGUsdCxyLG4pe2xldCBpPTEsbz1lWzBdO2Zvcig7byE9PXZvaWQgMCYmb1tuXT09PXZvaWQgMDspbz1lW2krK107aWYobz09PXZvaWQgMClyZXR1cm47bGV0IGE9b1tuXTtpZihhIT09dm9pZCAwKWlmKEFycmF5LmlzQXJyYXkoYSkpZG8gYT1vW25dLGEhPT12b2lkIDAmJih0LnB1c2goby50aW1lKSxyLnB1c2guYXBwbHkocixhKSksbz1lW2krK107d2hpbGUobyE9PXZvaWQgMCk7ZWxzZSBpZihhLnRvQXJyYXkhPT12b2lkIDApZG8gYT1vW25dLGEhPT12b2lkIDAmJih0LnB1c2goby50aW1lKSxhLnRvQXJyYXkocixyLmxlbmd0aCkpLG89ZVtpKytdO3doaWxlKG8hPT12b2lkIDApO2Vsc2UgZG8gYT1vW25dLGEhPT12b2lkIDAmJih0LnB1c2goby50aW1lKSxyLnB1c2goYSkpLG89ZVtpKytdO3doaWxlKG8hPT12b2lkIDApfSxzdWJjbGlwOmZ1bmN0aW9uKGUsdCxyLG4saT0zMCl7bGV0IG89ZS5jbG9uZSgpO28ubmFtZT10O2xldCBhPVtdO2ZvcihsZXQgbD0wO2w8by50cmFja3MubGVuZ3RoOysrbCl7bGV0IGM9by50cmFja3NbbF0sdT1jLmdldFZhbHVlU2l6ZSgpLGg9W10sZj1bXTtmb3IobGV0IHA9MDtwPGMudGltZXMubGVuZ3RoOysrcCl7bGV0IGQ9Yy50aW1lc1twXSppO2lmKCEoZDxyfHxkPj1uKSl7aC5wdXNoKGMudGltZXNbcF0pO2ZvcihsZXQgZz0wO2c8dTsrK2cpZi5wdXNoKGMudmFsdWVzW3AqdStnXSl9fWgubGVuZ3RoIT09MCYmKGMudGltZXM9am4uY29udmVydEFycmF5KGgsYy50aW1lcy5jb25zdHJ1Y3RvciksYy52YWx1ZXM9am4uY29udmVydEFycmF5KGYsYy52YWx1ZXMuY29uc3RydWN0b3IpLGEucHVzaChjKSl9by50cmFja3M9YTtsZXQgcz0xLzA7Zm9yKGxldCBsPTA7bDxvLnRyYWNrcy5sZW5ndGg7KytsKXM+by50cmFja3NbbF0udGltZXNbMF0mJihzPW8udHJhY2tzW2xdLnRpbWVzWzBdKTtmb3IobGV0IGw9MDtsPG8udHJhY2tzLmxlbmd0aDsrK2wpby50cmFja3NbbF0uc2hpZnQoLTEqcyk7cmV0dXJuIG8ucmVzZXREdXJhdGlvbigpLG99LG1ha2VDbGlwQWRkaXRpdmU6ZnVuY3Rpb24oZSx0PTAscj1lLG49MzApe248PTAmJihuPTMwKTtsZXQgaT1yLnRyYWNrcy5sZW5ndGgsbz10L247Zm9yKGxldCBhPTA7YTxpOysrYSl7bGV0IHM9ci50cmFja3NbYV0sbD1zLlZhbHVlVHlwZU5hbWU7aWYobD09PSJib29sInx8bD09PSJzdHJpbmciKWNvbnRpbnVlO2xldCBjPWUudHJhY2tzLmZpbmQoZnVuY3Rpb24oeSl7cmV0dXJuIHkubmFtZT09PXMubmFtZSYmeS5WYWx1ZVR5cGVOYW1lPT09bH0pO2lmKGM9PT12b2lkIDApY29udGludWU7bGV0IHU9MCxoPXMuZ2V0VmFsdWVTaXplKCk7cy5jcmVhdGVJbnRlcnBvbGFudC5pc0ludGVycG9sYW50RmFjdG9yeU1ldGhvZEdMVEZDdWJpY1NwbGluZSYmKHU9aC8zKTtsZXQgZj0wLHA9Yy5nZXRWYWx1ZVNpemUoKTtjLmNyZWF0ZUludGVycG9sYW50LmlzSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kR0xURkN1YmljU3BsaW5lJiYoZj1wLzMpO2xldCBkPXMudGltZXMubGVuZ3RoLTEsZztpZihvPD1zLnRpbWVzWzBdKXtsZXQgeT11LHg9aC11O2c9am4uYXJyYXlTbGljZShzLnZhbHVlcyx5LHgpfWVsc2UgaWYobz49cy50aW1lc1tkXSl7bGV0IHk9ZCpoK3UseD15K2gtdTtnPWpuLmFycmF5U2xpY2Uocy52YWx1ZXMseSx4KX1lbHNle2xldCB5PXMuY3JlYXRlSW50ZXJwb2xhbnQoKSx4PXUsYj1oLXU7eS5ldmFsdWF0ZShvKSxnPWpuLmFycmF5U2xpY2UoeS5yZXN1bHRCdWZmZXIseCxiKX1sPT09InF1YXRlcm5pb24iJiZuZXcgdmkoKS5mcm9tQXJyYXkoZykubm9ybWFsaXplKCkuY29uanVnYXRlKCkudG9BcnJheShnKTtsZXQgXz1jLnRpbWVzLmxlbmd0aDtmb3IobGV0IHk9MDt5PF87Kyt5KXtsZXQgeD15KnArZjtpZihsPT09InF1YXRlcm5pb24iKXZpLm11bHRpcGx5UXVhdGVybmlvbnNGbGF0KGMudmFsdWVzLHgsZywwLGMudmFsdWVzLHgpO2Vsc2V7bGV0IGI9cC1mKjI7Zm9yKGxldCBTPTA7UzxiOysrUyljLnZhbHVlc1t4K1NdLT1nW1NdfX19cmV0dXJuIGUuYmxlbmRNb2RlPVJodCxlfX0sZmg9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4saSl7dGhpcy5wYXJhbWV0ZXJQb3NpdGlvbnM9dCx0aGlzLl9jYWNoZWRJbmRleD0wLHRoaXMucmVzdWx0QnVmZmVyPWkhPT12b2lkIDA/aTpuZXcgci5jb25zdHJ1Y3RvcihuKSx0aGlzLnNhbXBsZVZhbHVlcz1yLHRoaXMudmFsdWVTaXplPW4sdGhpcy5zZXR0aW5ncz1udWxsLHRoaXMuRGVmYXVsdFNldHRpbmdzXz17fX1ldmFsdWF0ZSh0KXtsZXQgcj10aGlzLnBhcmFtZXRlclBvc2l0aW9ucyxuPXRoaXMuX2NhY2hlZEluZGV4LGk9cltuXSxvPXJbbi0xXTt0OntlOntsZXQgYTtyOntuOmlmKCEodDxpKSl7Zm9yKGxldCBzPW4rMjs7KXtpZihpPT09dm9pZCAwKXtpZih0PG8pYnJlYWsgbjtyZXR1cm4gbj1yLmxlbmd0aCx0aGlzLl9jYWNoZWRJbmRleD1uLHRoaXMuYWZ0ZXJFbmRfKG4tMSx0LG8pfWlmKG49PT1zKWJyZWFrO2lmKG89aSxpPXJbKytuXSx0PGkpYnJlYWsgZX1hPXIubGVuZ3RoO2JyZWFrIHJ9aWYoISh0Pj1vKSl7bGV0IHM9clsxXTt0PHMmJihuPTIsbz1zKTtmb3IobGV0IGw9bi0yOzspe2lmKG89PT12b2lkIDApcmV0dXJuIHRoaXMuX2NhY2hlZEluZGV4PTAsdGhpcy5iZWZvcmVTdGFydF8oMCx0LGkpO2lmKG49PT1sKWJyZWFrO2lmKGk9byxvPXJbLS1uLTFdLHQ+PW8pYnJlYWsgZX1hPW4sbj0wO2JyZWFrIHJ9YnJlYWsgdH1mb3IoO248YTspe2xldCBzPW4rYT4+PjE7dDxyW3NdP2E9czpuPXMrMX1pZihpPXJbbl0sbz1yW24tMV0sbz09PXZvaWQgMClyZXR1cm4gdGhpcy5fY2FjaGVkSW5kZXg9MCx0aGlzLmJlZm9yZVN0YXJ0XygwLHQsaSk7aWYoaT09PXZvaWQgMClyZXR1cm4gbj1yLmxlbmd0aCx0aGlzLl9jYWNoZWRJbmRleD1uLHRoaXMuYWZ0ZXJFbmRfKG4tMSxvLHQpfXRoaXMuX2NhY2hlZEluZGV4PW4sdGhpcy5pbnRlcnZhbENoYW5nZWRfKG4sbyxpKX1yZXR1cm4gdGhpcy5pbnRlcnBvbGF0ZV8obixvLHQsaSl9Z2V0U2V0dGluZ3NfKCl7cmV0dXJuIHRoaXMuc2V0dGluZ3N8fHRoaXMuRGVmYXVsdFNldHRpbmdzX31jb3B5U2FtcGxlVmFsdWVfKHQpe2xldCByPXRoaXMucmVzdWx0QnVmZmVyLG49dGhpcy5zYW1wbGVWYWx1ZXMsaT10aGlzLnZhbHVlU2l6ZSxvPXQqaTtmb3IobGV0IGE9MDthIT09aTsrK2EpclthXT1uW28rYV07cmV0dXJuIHJ9aW50ZXJwb2xhdGVfKCl7dGhyb3cgbmV3IEVycm9yKCJjYWxsIHRvIGFic3RyYWN0IG1ldGhvZCIpfWludGVydmFsQ2hhbmdlZF8oKXt9fTtmaC5wcm90b3R5cGUuYmVmb3JlU3RhcnRfPWZoLnByb3RvdHlwZS5jb3B5U2FtcGxlVmFsdWVfO2ZoLnByb3RvdHlwZS5hZnRlckVuZF89ZmgucHJvdG90eXBlLmNvcHlTYW1wbGVWYWx1ZV87dmFyIHdVPWNsYXNzIGV4dGVuZHMgZmh7Y29uc3RydWN0b3IodCxyLG4saSl7c3VwZXIodCxyLG4saSksdGhpcy5fd2VpZ2h0UHJldj0tMCx0aGlzLl9vZmZzZXRQcmV2PS0wLHRoaXMuX3dlaWdodE5leHQ9LTAsdGhpcy5fb2Zmc2V0TmV4dD0tMCx0aGlzLkRlZmF1bHRTZXR0aW5nc189e2VuZGluZ1N0YXJ0OkV2LGVuZGluZ0VuZDpFdn19aW50ZXJ2YWxDaGFuZ2VkXyh0LHIsbil7bGV0IGk9dGhpcy5wYXJhbWV0ZXJQb3NpdGlvbnMsbz10LTIsYT10KzEscz1pW29dLGw9aVthXTtpZihzPT09dm9pZCAwKXN3aXRjaCh0aGlzLmdldFNldHRpbmdzXygpLmVuZGluZ1N0YXJ0KXtjYXNlIFR2Om89dCxzPTIqci1uO2JyZWFrO2Nhc2UgWlA6bz1pLmxlbmd0aC0yLHM9citpW29dLWlbbysxXTticmVhaztkZWZhdWx0Om89dCxzPW59aWYobD09PXZvaWQgMClzd2l0Y2godGhpcy5nZXRTZXR0aW5nc18oKS5lbmRpbmdFbmQpe2Nhc2UgVHY6YT10LGw9MipuLXI7YnJlYWs7Y2FzZSBaUDphPTEsbD1uK2lbMV0taVswXTticmVhaztkZWZhdWx0OmE9dC0xLGw9cn1sZXQgYz0obi1yKSouNSx1PXRoaXMudmFsdWVTaXplO3RoaXMuX3dlaWdodFByZXY9Yy8oci1zKSx0aGlzLl93ZWlnaHROZXh0PWMvKGwtbiksdGhpcy5fb2Zmc2V0UHJldj1vKnUsdGhpcy5fb2Zmc2V0TmV4dD1hKnV9aW50ZXJwb2xhdGVfKHQscixuLGkpe2xldCBvPXRoaXMucmVzdWx0QnVmZmVyLGE9dGhpcy5zYW1wbGVWYWx1ZXMscz10aGlzLnZhbHVlU2l6ZSxsPXQqcyxjPWwtcyx1PXRoaXMuX29mZnNldFByZXYsaD10aGlzLl9vZmZzZXROZXh0LGY9dGhpcy5fd2VpZ2h0UHJldixwPXRoaXMuX3dlaWdodE5leHQsZD0obi1yKS8oaS1yKSxnPWQqZCxfPWcqZCx5PS1mKl8rMipmKmctZipkLHg9KDErZikqXysoLTEuNS0yKmYpKmcrKC0uNStmKSpkKzEsYj0oLTEtcCkqXysoMS41K3ApKmcrLjUqZCxTPXAqXy1wKmc7Zm9yKGxldCBDPTA7QyE9PXM7KytDKW9bQ109eSphW3UrQ10reCphW2MrQ10rYiphW2wrQ10rUyphW2grQ107cmV0dXJuIG99fSx3Nj1jbGFzcyBleHRlbmRzIGZoe2NvbnN0cnVjdG9yKHQscixuLGkpe3N1cGVyKHQscixuLGkpfWludGVycG9sYXRlXyh0LHIsbixpKXtsZXQgbz10aGlzLnJlc3VsdEJ1ZmZlcixhPXRoaXMuc2FtcGxlVmFsdWVzLHM9dGhpcy52YWx1ZVNpemUsbD10KnMsYz1sLXMsdT0obi1yKS8oaS1yKSxoPTEtdTtmb3IobGV0IGY9MDtmIT09czsrK2Ypb1tmXT1hW2MrZl0qaCthW2wrZl0qdTtyZXR1cm4gb319LFNVPWNsYXNzIGV4dGVuZHMgZmh7Y29uc3RydWN0b3IodCxyLG4saSl7c3VwZXIodCxyLG4saSl9aW50ZXJwb2xhdGVfKHQpe3JldHVybiB0aGlzLmNvcHlTYW1wbGVWYWx1ZV8odC0xKX19LERsPWNsYXNze2NvbnN0cnVjdG9yKHQscixuLGkpe2lmKHQ9PT12b2lkIDApdGhyb3cgbmV3IEVycm9yKCJUSFJFRS5LZXlmcmFtZVRyYWNrOiB0cmFjayBuYW1lIGlzIHVuZGVmaW5lZCIpO2lmKHI9PT12b2lkIDB8fHIubGVuZ3RoPT09MCl0aHJvdyBuZXcgRXJyb3IoIlRIUkVFLktleWZyYW1lVHJhY2s6IG5vIGtleWZyYW1lcyBpbiB0cmFjayBuYW1lZCAiK3QpO3RoaXMubmFtZT10LHRoaXMudGltZXM9am4uY29udmVydEFycmF5KHIsdGhpcy5UaW1lQnVmZmVyVHlwZSksdGhpcy52YWx1ZXM9am4uY29udmVydEFycmF5KG4sdGhpcy5WYWx1ZUJ1ZmZlclR5cGUpLHRoaXMuc2V0SW50ZXJwb2xhdGlvbihpfHx0aGlzLkRlZmF1bHRJbnRlcnBvbGF0aW9uKX1zdGF0aWMgdG9KU09OKHQpe2xldCByPXQuY29uc3RydWN0b3IsbjtpZihyLnRvSlNPTiE9PXRoaXMudG9KU09OKW49ci50b0pTT04odCk7ZWxzZXtuPXtuYW1lOnQubmFtZSx0aW1lczpqbi5jb252ZXJ0QXJyYXkodC50aW1lcyxBcnJheSksdmFsdWVzOmpuLmNvbnZlcnRBcnJheSh0LnZhbHVlcyxBcnJheSl9O2xldCBpPXQuZ2V0SW50ZXJwb2xhdGlvbigpO2khPT10LkRlZmF1bHRJbnRlcnBvbGF0aW9uJiYobi5pbnRlcnBvbGF0aW9uPWkpfXJldHVybiBuLnR5cGU9dC5WYWx1ZVR5cGVOYW1lLG59SW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kRGlzY3JldGUodCl7cmV0dXJuIG5ldyBTVSh0aGlzLnRpbWVzLHRoaXMudmFsdWVzLHRoaXMuZ2V0VmFsdWVTaXplKCksdCl9SW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kTGluZWFyKHQpe3JldHVybiBuZXcgdzYodGhpcy50aW1lcyx0aGlzLnZhbHVlcyx0aGlzLmdldFZhbHVlU2l6ZSgpLHQpfUludGVycG9sYW50RmFjdG9yeU1ldGhvZFNtb290aCh0KXtyZXR1cm4gbmV3IHdVKHRoaXMudGltZXMsdGhpcy52YWx1ZXMsdGhpcy5nZXRWYWx1ZVNpemUoKSx0KX1zZXRJbnRlcnBvbGF0aW9uKHQpe2xldCByO3N3aXRjaCh0KXtjYXNlICRQOnI9dGhpcy5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2REaXNjcmV0ZTticmVhaztjYXNlIEtQOnI9dGhpcy5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2RMaW5lYXI7YnJlYWs7Y2FzZSBlVTpyPXRoaXMuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kU21vb3RoO2JyZWFrfWlmKHI9PT12b2lkIDApe2xldCBuPSJ1bnN1cHBvcnRlZCBpbnRlcnBvbGF0aW9uIGZvciAiK3RoaXMuVmFsdWVUeXBlTmFtZSsiIGtleWZyYW1lIHRyYWNrIG5hbWVkICIrdGhpcy5uYW1lO2lmKHRoaXMuY3JlYXRlSW50ZXJwb2xhbnQ9PT12b2lkIDApaWYodCE9PXRoaXMuRGVmYXVsdEludGVycG9sYXRpb24pdGhpcy5zZXRJbnRlcnBvbGF0aW9uKHRoaXMuRGVmYXVsdEludGVycG9sYXRpb24pO2Vsc2UgdGhyb3cgbmV3IEVycm9yKG4pO3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLktleWZyYW1lVHJhY2s6IixuKSx0aGlzfXJldHVybiB0aGlzLmNyZWF0ZUludGVycG9sYW50PXIsdGhpc31nZXRJbnRlcnBvbGF0aW9uKCl7c3dpdGNoKHRoaXMuY3JlYXRlSW50ZXJwb2xhbnQpe2Nhc2UgdGhpcy5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2REaXNjcmV0ZTpyZXR1cm4gJFA7Y2FzZSB0aGlzLkludGVycG9sYW50RmFjdG9yeU1ldGhvZExpbmVhcjpyZXR1cm4gS1A7Y2FzZSB0aGlzLkludGVycG9sYW50RmFjdG9yeU1ldGhvZFNtb290aDpyZXR1cm4gZVV9fWdldFZhbHVlU2l6ZSgpe3JldHVybiB0aGlzLnZhbHVlcy5sZW5ndGgvdGhpcy50aW1lcy5sZW5ndGh9c2hpZnQodCl7aWYodCE9PTApe2xldCByPXRoaXMudGltZXM7Zm9yKGxldCBuPTAsaT1yLmxlbmd0aDtuIT09aTsrK24pcltuXSs9dH1yZXR1cm4gdGhpc31zY2FsZSh0KXtpZih0IT09MSl7bGV0IHI9dGhpcy50aW1lcztmb3IobGV0IG49MCxpPXIubGVuZ3RoO24hPT1pOysrbilyW25dKj10fXJldHVybiB0aGlzfXRyaW0odCxyKXtsZXQgbj10aGlzLnRpbWVzLGk9bi5sZW5ndGgsbz0wLGE9aS0xO2Zvcig7byE9PWkmJm5bb108dDspKytvO2Zvcig7YSE9PS0xJiZuW2FdPnI7KS0tYTtpZigrK2EsbyE9PTB8fGEhPT1pKXtvPj1hJiYoYT1NYXRoLm1heChhLDEpLG89YS0xKTtsZXQgcz10aGlzLmdldFZhbHVlU2l6ZSgpO3RoaXMudGltZXM9am4uYXJyYXlTbGljZShuLG8sYSksdGhpcy52YWx1ZXM9am4uYXJyYXlTbGljZSh0aGlzLnZhbHVlcyxvKnMsYSpzKX1yZXR1cm4gdGhpc312YWxpZGF0ZSgpe2xldCB0PSEwLHI9dGhpcy5nZXRWYWx1ZVNpemUoKTtyLU1hdGguZmxvb3IocikhPT0wJiYoY29uc29sZS5lcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogSW52YWxpZCB2YWx1ZSBzaXplIGluIHRyYWNrLiIsdGhpcyksdD0hMSk7bGV0IG49dGhpcy50aW1lcyxpPXRoaXMudmFsdWVzLG89bi5sZW5ndGg7bz09PTAmJihjb25zb2xlLmVycm9yKCJUSFJFRS5LZXlmcmFtZVRyYWNrOiBUcmFjayBpcyBlbXB0eS4iLHRoaXMpLHQ9ITEpO2xldCBhPW51bGw7Zm9yKGxldCBzPTA7cyE9PW87cysrKXtsZXQgbD1uW3NdO2lmKHR5cGVvZiBsPT0ibnVtYmVyIiYmaXNOYU4obCkpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLktleWZyYW1lVHJhY2s6IFRpbWUgaXMgbm90IGEgdmFsaWQgbnVtYmVyLiIsdGhpcyxzLGwpLHQ9ITE7YnJlYWt9aWYoYSE9PW51bGwmJmE+bCl7Y29uc29sZS5lcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogT3V0IG9mIG9yZGVyIGtleXMuIix0aGlzLHMsbCxhKSx0PSExO2JyZWFrfWE9bH1pZihpIT09dm9pZCAwJiZqbi5pc1R5cGVkQXJyYXkoaSkpZm9yKGxldCBzPTAsbD1pLmxlbmd0aDtzIT09bDsrK3Mpe2xldCBjPWlbc107aWYoaXNOYU4oYykpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLktleWZyYW1lVHJhY2s6IFZhbHVlIGlzIG5vdCBhIHZhbGlkIG51bWJlci4iLHRoaXMscyxjKSx0PSExO2JyZWFrfX1yZXR1cm4gdH1vcHRpbWl6ZSgpe2xldCB0PWpuLmFycmF5U2xpY2UodGhpcy50aW1lcykscj1qbi5hcnJheVNsaWNlKHRoaXMudmFsdWVzKSxuPXRoaXMuZ2V0VmFsdWVTaXplKCksaT10aGlzLmdldEludGVycG9sYXRpb24oKT09PWVVLG89dC5sZW5ndGgtMSxhPTE7Zm9yKGxldCBzPTE7czxvOysrcyl7bGV0IGw9ITEsYz10W3NdLHU9dFtzKzFdO2lmKGMhPT11JiYocyE9PTF8fGMhPT10WzBdKSlpZihpKWw9ITA7ZWxzZXtsZXQgaD1zKm4sZj1oLW4scD1oK247Zm9yKGxldCBkPTA7ZCE9PW47KytkKXtsZXQgZz1yW2grZF07aWYoZyE9PXJbZitkXXx8ZyE9PXJbcCtkXSl7bD0hMDticmVha319fWlmKGwpe2lmKHMhPT1hKXt0W2FdPXRbc107bGV0IGg9cypuLGY9YSpuO2ZvcihsZXQgcD0wO3AhPT1uOysrcClyW2YrcF09cltoK3BdfSsrYX19aWYobz4wKXt0W2FdPXRbb107Zm9yKGxldCBzPW8qbixsPWEqbixjPTA7YyE9PW47KytjKXJbbCtjXT1yW3MrY107KythfXJldHVybiBhIT09dC5sZW5ndGg/KHRoaXMudGltZXM9am4uYXJyYXlTbGljZSh0LDAsYSksdGhpcy52YWx1ZXM9am4uYXJyYXlTbGljZShyLDAsYSpuKSk6KHRoaXMudGltZXM9dCx0aGlzLnZhbHVlcz1yKSx0aGlzfWNsb25lKCl7bGV0IHQ9am4uYXJyYXlTbGljZSh0aGlzLnRpbWVzLDApLHI9am4uYXJyYXlTbGljZSh0aGlzLnZhbHVlcywwKSxuPXRoaXMuY29uc3RydWN0b3IsaT1uZXcgbih0aGlzLm5hbWUsdCxyKTtyZXR1cm4gaS5jcmVhdGVJbnRlcnBvbGFudD10aGlzLmNyZWF0ZUludGVycG9sYW50LGl9fTtEbC5wcm90b3R5cGUuVGltZUJ1ZmZlclR5cGU9RmxvYXQzMkFycmF5O0RsLnByb3RvdHlwZS5WYWx1ZUJ1ZmZlclR5cGU9RmxvYXQzMkFycmF5O0RsLnByb3RvdHlwZS5EZWZhdWx0SW50ZXJwb2xhdGlvbj1LUDt2YXIgYW09Y2xhc3MgZXh0ZW5kcyBEbHt9O2FtLnByb3RvdHlwZS5WYWx1ZVR5cGVOYW1lPSJib29sIjthbS5wcm90b3R5cGUuVmFsdWVCdWZmZXJUeXBlPUFycmF5O2FtLnByb3RvdHlwZS5EZWZhdWx0SW50ZXJwb2xhdGlvbj0kUDthbS5wcm90b3R5cGUuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kTGluZWFyPXZvaWQgMDthbS5wcm90b3R5cGUuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kU21vb3RoPXZvaWQgMDt2YXIgUzY9Y2xhc3MgZXh0ZW5kcyBEbHt9O1M2LnByb3RvdHlwZS5WYWx1ZVR5cGVOYW1lPSJjb2xvciI7dmFyIFp2PWNsYXNzIGV4dGVuZHMgRGx7fTtadi5wcm90b3R5cGUuVmFsdWVUeXBlTmFtZT0ibnVtYmVyIjt2YXIgTVU9Y2xhc3MgZXh0ZW5kcyBmaHtjb25zdHJ1Y3Rvcih0LHIsbixpKXtzdXBlcih0LHIsbixpKX1pbnRlcnBvbGF0ZV8odCxyLG4saSl7bGV0IG89dGhpcy5yZXN1bHRCdWZmZXIsYT10aGlzLnNhbXBsZVZhbHVlcyxzPXRoaXMudmFsdWVTaXplLGw9KG4tcikvKGktciksYz10KnM7Zm9yKGxldCB1PWMrcztjIT09dTtjKz00KXZpLnNsZXJwRmxhdChvLDAsYSxjLXMsYSxjLGwpO3JldHVybiBvfX0sWDA9Y2xhc3MgZXh0ZW5kcyBEbHtJbnRlcnBvbGFudEZhY3RvcnlNZXRob2RMaW5lYXIodCl7cmV0dXJuIG5ldyBNVSh0aGlzLnRpbWVzLHRoaXMudmFsdWVzLHRoaXMuZ2V0VmFsdWVTaXplKCksdCl9fTtYMC5wcm90b3R5cGUuVmFsdWVUeXBlTmFtZT0icXVhdGVybmlvbiI7WDAucHJvdG90eXBlLkRlZmF1bHRJbnRlcnBvbGF0aW9uPUtQO1gwLnByb3RvdHlwZS5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2RTbW9vdGg9dm9pZCAwO3ZhciBzbT1jbGFzcyBleHRlbmRzIERse307c20ucHJvdG90eXBlLlZhbHVlVHlwZU5hbWU9InN0cmluZyI7c20ucHJvdG90eXBlLlZhbHVlQnVmZmVyVHlwZT1BcnJheTtzbS5wcm90b3R5cGUuRGVmYXVsdEludGVycG9sYXRpb249JFA7c20ucHJvdG90eXBlLkludGVycG9sYW50RmFjdG9yeU1ldGhvZExpbmVhcj12b2lkIDA7c20ucHJvdG90eXBlLkludGVycG9sYW50RmFjdG9yeU1ldGhvZFNtb290aD12b2lkIDA7dmFyIEp2PWNsYXNzIGV4dGVuZHMgRGx7fTtKdi5wcm90b3R5cGUuVmFsdWVUeXBlTmFtZT0idmVjdG9yIjt2YXIgUXY9Y2xhc3N7Y29uc3RydWN0b3IodCxyPS0xLG4saT1YVSl7dGhpcy5uYW1lPXQsdGhpcy50cmFja3M9bix0aGlzLmR1cmF0aW9uPXIsdGhpcy5ibGVuZE1vZGU9aSx0aGlzLnV1aWQ9TmwoKSx0aGlzLmR1cmF0aW9uPDAmJnRoaXMucmVzZXREdXJhdGlvbigpfXN0YXRpYyBwYXJzZSh0KXtsZXQgcj1bXSxuPXQudHJhY2tzLGk9MS8odC5mcHN8fDEpO2ZvcihsZXQgYT0wLHM9bi5sZW5ndGg7YSE9PXM7KythKXIucHVzaChzMHIoblthXSkuc2NhbGUoaSkpO2xldCBvPW5ldyB0aGlzKHQubmFtZSx0LmR1cmF0aW9uLHIsdC5ibGVuZE1vZGUpO3JldHVybiBvLnV1aWQ9dC51dWlkLG99c3RhdGljIHRvSlNPTih0KXtsZXQgcj1bXSxuPXQudHJhY2tzLGk9e25hbWU6dC5uYW1lLGR1cmF0aW9uOnQuZHVyYXRpb24sdHJhY2tzOnIsdXVpZDp0LnV1aWQsYmxlbmRNb2RlOnQuYmxlbmRNb2RlfTtmb3IobGV0IG89MCxhPW4ubGVuZ3RoO28hPT1hOysrbylyLnB1c2goRGwudG9KU09OKG5bb10pKTtyZXR1cm4gaX1zdGF0aWMgQ3JlYXRlRnJvbU1vcnBoVGFyZ2V0U2VxdWVuY2UodCxyLG4saSl7bGV0IG89ci5sZW5ndGgsYT1bXTtmb3IobGV0IHM9MDtzPG87cysrKXtsZXQgbD1bXSxjPVtdO2wucHVzaCgocytvLTEpJW8scywocysxKSVvKSxjLnB1c2goMCwxLDApO2xldCB1PWpuLmdldEtleWZyYW1lT3JkZXIobCk7bD1qbi5zb3J0ZWRBcnJheShsLDEsdSksYz1qbi5zb3J0ZWRBcnJheShjLDEsdSksIWkmJmxbMF09PT0wJiYobC5wdXNoKG8pLGMucHVzaChjWzBdKSksYS5wdXNoKG5ldyBadigiLm1vcnBoVGFyZ2V0SW5mbHVlbmNlc1siK3Jbc10ubmFtZSsiXSIsbCxjKS5zY2FsZSgxL24pKX1yZXR1cm4gbmV3IHRoaXModCwtMSxhKX1zdGF0aWMgZmluZEJ5TmFtZSh0LHIpe2xldCBuPXQ7aWYoIUFycmF5LmlzQXJyYXkodCkpe2xldCBpPXQ7bj1pLmdlb21ldHJ5JiZpLmdlb21ldHJ5LmFuaW1hdGlvbnN8fGkuYW5pbWF0aW9uc31mb3IobGV0IGk9MDtpPG4ubGVuZ3RoO2krKylpZihuW2ldLm5hbWU9PT1yKXJldHVybiBuW2ldO3JldHVybiBudWxsfXN0YXRpYyBDcmVhdGVDbGlwc0Zyb21Nb3JwaFRhcmdldFNlcXVlbmNlcyh0LHIsbil7bGV0IGk9e30sbz0vXihbXHctXSo/KShbXGRdKykkLztmb3IobGV0IHM9MCxsPXQubGVuZ3RoO3M8bDtzKyspe2xldCBjPXRbc10sdT1jLm5hbWUubWF0Y2gobyk7aWYodSYmdS5sZW5ndGg+MSl7bGV0IGg9dVsxXSxmPWlbaF07Znx8KGlbaF09Zj1bXSksZi5wdXNoKGMpfX1sZXQgYT1bXTtmb3IobGV0IHMgaW4gaSlhLnB1c2godGhpcy5DcmVhdGVGcm9tTW9ycGhUYXJnZXRTZXF1ZW5jZShzLGlbc10scixuKSk7cmV0dXJuIGF9c3RhdGljIHBhcnNlQW5pbWF0aW9uKHQscil7aWYoIXQpcmV0dXJuIGNvbnNvbGUuZXJyb3IoIlRIUkVFLkFuaW1hdGlvbkNsaXA6IE5vIGFuaW1hdGlvbiBpbiBKU09OTG9hZGVyIGRhdGEuIiksbnVsbDtsZXQgbj1mdW5jdGlvbihoLGYscCxkLGcpe2lmKHAubGVuZ3RoIT09MCl7bGV0IF89W10seT1bXTtqbi5mbGF0dGVuSlNPTihwLF8seSxkKSxfLmxlbmd0aCE9PTAmJmcucHVzaChuZXcgaChmLF8seSkpfX0saT1bXSxvPXQubmFtZXx8ImRlZmF1bHQiLGE9dC5mcHN8fDMwLHM9dC5ibGVuZE1vZGUsbD10Lmxlbmd0aHx8LTEsYz10LmhpZXJhcmNoeXx8W107Zm9yKGxldCBoPTA7aDxjLmxlbmd0aDtoKyspe2xldCBmPWNbaF0ua2V5cztpZighKCFmfHxmLmxlbmd0aD09PTApKWlmKGZbMF0ubW9ycGhUYXJnZXRzKXtsZXQgcD17fSxkO2ZvcihkPTA7ZDxmLmxlbmd0aDtkKyspaWYoZltkXS5tb3JwaFRhcmdldHMpZm9yKGxldCBnPTA7ZzxmW2RdLm1vcnBoVGFyZ2V0cy5sZW5ndGg7ZysrKXBbZltkXS5tb3JwaFRhcmdldHNbZ11dPS0xO2ZvcihsZXQgZyBpbiBwKXtsZXQgXz1bXSx5PVtdO2ZvcihsZXQgeD0wO3ghPT1mW2RdLm1vcnBoVGFyZ2V0cy5sZW5ndGg7Kyt4KXtsZXQgYj1mW2RdO18ucHVzaChiLnRpbWUpLHkucHVzaChiLm1vcnBoVGFyZ2V0PT09Zz8xOjApfWkucHVzaChuZXcgWnYoIi5tb3JwaFRhcmdldEluZmx1ZW5jZVsiK2crIl0iLF8seSkpfWw9cC5sZW5ndGgqKGF8fDEpfWVsc2V7bGV0IHA9Ii5ib25lc1siK3JbaF0ubmFtZSsiXSI7bihKdixwKyIucG9zaXRpb24iLGYsInBvcyIsaSksbihYMCxwKyIucXVhdGVybmlvbiIsZiwicm90IixpKSxuKEp2LHArIi5zY2FsZSIsZiwic2NsIixpKX19cmV0dXJuIGkubGVuZ3RoPT09MD9udWxsOm5ldyB0aGlzKG8sbCxpLHMpfXJlc2V0RHVyYXRpb24oKXtsZXQgdD10aGlzLnRyYWNrcyxyPTA7Zm9yKGxldCBuPTAsaT10Lmxlbmd0aDtuIT09aTsrK24pe2xldCBvPXRoaXMudHJhY2tzW25dO3I9TWF0aC5tYXgocixvLnRpbWVzW28udGltZXMubGVuZ3RoLTFdKX1yZXR1cm4gdGhpcy5kdXJhdGlvbj1yLHRoaXN9dHJpbSgpe2ZvcihsZXQgdD0wO3Q8dGhpcy50cmFja3MubGVuZ3RoO3QrKyl0aGlzLnRyYWNrc1t0XS50cmltKDAsdGhpcy5kdXJhdGlvbik7cmV0dXJuIHRoaXN9dmFsaWRhdGUoKXtsZXQgdD0hMDtmb3IobGV0IHI9MDtyPHRoaXMudHJhY2tzLmxlbmd0aDtyKyspdD10JiZ0aGlzLnRyYWNrc1tyXS52YWxpZGF0ZSgpO3JldHVybiB0fW9wdGltaXplKCl7Zm9yKGxldCB0PTA7dDx0aGlzLnRyYWNrcy5sZW5ndGg7dCsrKXRoaXMudHJhY2tzW3RdLm9wdGltaXplKCk7cmV0dXJuIHRoaXN9Y2xvbmUoKXtsZXQgdD1bXTtmb3IobGV0IHI9MDtyPHRoaXMudHJhY2tzLmxlbmd0aDtyKyspdC5wdXNoKHRoaXMudHJhY2tzW3JdLmNsb25lKCkpO3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3Rvcih0aGlzLm5hbWUsdGhpcy5kdXJhdGlvbix0LHRoaXMuYmxlbmRNb2RlKX10b0pTT04oKXtyZXR1cm4gdGhpcy5jb25zdHJ1Y3Rvci50b0pTT04odGhpcyl9fTtmdW5jdGlvbiBhMHIoZSl7c3dpdGNoKGUudG9Mb3dlckNhc2UoKSl7Y2FzZSJzY2FsYXIiOmNhc2UiZG91YmxlIjpjYXNlImZsb2F0IjpjYXNlIm51bWJlciI6Y2FzZSJpbnRlZ2VyIjpyZXR1cm4gWnY7Y2FzZSJ2ZWN0b3IiOmNhc2UidmVjdG9yMiI6Y2FzZSJ2ZWN0b3IzIjpjYXNlInZlY3RvcjQiOnJldHVybiBKdjtjYXNlImNvbG9yIjpyZXR1cm4gUzY7Y2FzZSJxdWF0ZXJuaW9uIjpyZXR1cm4gWDA7Y2FzZSJib29sIjpjYXNlImJvb2xlYW4iOnJldHVybiBhbTtjYXNlInN0cmluZyI6cmV0dXJuIHNtfXRocm93IG5ldyBFcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogVW5zdXBwb3J0ZWQgdHlwZU5hbWU6ICIrZSl9ZnVuY3Rpb24gczByKGUpe2lmKGUudHlwZT09PXZvaWQgMCl0aHJvdyBuZXcgRXJyb3IoIlRIUkVFLktleWZyYW1lVHJhY2s6IHRyYWNrIHR5cGUgdW5kZWZpbmVkLCBjYW4gbm90IHBhcnNlIik7bGV0IHQ9YTByKGUudHlwZSk7aWYoZS50aW1lcz09PXZvaWQgMCl7bGV0IHI9W10sbj1bXTtqbi5mbGF0dGVuSlNPTihlLmtleXMscixuLCJ2YWx1ZSIpLGUudGltZXM9cixlLnZhbHVlcz1ufXJldHVybiB0LnBhcnNlIT09dm9pZCAwP3QucGFyc2UoZSk6bmV3IHQoZS5uYW1lLGUudGltZXMsZS52YWx1ZXMsZS5pbnRlcnBvbGF0aW9uKX12YXIgdHg9e2VuYWJsZWQ6ITEsZmlsZXM6e30sYWRkOmZ1bmN0aW9uKGUsdCl7dGhpcy5lbmFibGVkIT09ITEmJih0aGlzLmZpbGVzW2VdPXQpfSxnZXQ6ZnVuY3Rpb24oZSl7aWYodGhpcy5lbmFibGVkIT09ITEpcmV0dXJuIHRoaXMuZmlsZXNbZV19LHJlbW92ZTpmdW5jdGlvbihlKXtkZWxldGUgdGhpcy5maWxlc1tlXX0sY2xlYXI6ZnVuY3Rpb24oKXt0aGlzLmZpbGVzPXt9fX0sTTY9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4pe2xldCBpPXRoaXMsbz0hMSxhPTAscz0wLGwsYz1bXTt0aGlzLm9uU3RhcnQ9dm9pZCAwLHRoaXMub25Mb2FkPXQsdGhpcy5vblByb2dyZXNzPXIsdGhpcy5vbkVycm9yPW4sdGhpcy5pdGVtU3RhcnQ9ZnVuY3Rpb24odSl7cysrLG89PT0hMSYmaS5vblN0YXJ0IT09dm9pZCAwJiZpLm9uU3RhcnQodSxhLHMpLG89ITB9LHRoaXMuaXRlbUVuZD1mdW5jdGlvbih1KXthKyssaS5vblByb2dyZXNzIT09dm9pZCAwJiZpLm9uUHJvZ3Jlc3ModSxhLHMpLGE9PT1zJiYobz0hMSxpLm9uTG9hZCE9PXZvaWQgMCYmaS5vbkxvYWQoKSl9LHRoaXMuaXRlbUVycm9yPWZ1bmN0aW9uKHUpe2kub25FcnJvciE9PXZvaWQgMCYmaS5vbkVycm9yKHUpfSx0aGlzLnJlc29sdmVVUkw9ZnVuY3Rpb24odSl7cmV0dXJuIGw/bCh1KTp1fSx0aGlzLnNldFVSTE1vZGlmaWVyPWZ1bmN0aW9uKHUpe3JldHVybiBsPXUsdGhpc30sdGhpcy5hZGRIYW5kbGVyPWZ1bmN0aW9uKHUsaCl7cmV0dXJuIGMucHVzaCh1LGgpLHRoaXN9LHRoaXMucmVtb3ZlSGFuZGxlcj1mdW5jdGlvbih1KXtsZXQgaD1jLmluZGV4T2YodSk7cmV0dXJuIGghPT0tMSYmYy5zcGxpY2UoaCwyKSx0aGlzfSx0aGlzLmdldEhhbmRsZXI9ZnVuY3Rpb24odSl7Zm9yKGxldCBoPTAsZj1jLmxlbmd0aDtoPGY7aCs9Mil7bGV0IHA9Y1toXSxkPWNbaCsxXTtpZihwLmdsb2JhbCYmKHAubGFzdEluZGV4PTApLHAudGVzdCh1KSlyZXR1cm4gZH1yZXR1cm4gbnVsbH19fSxRZmU9bmV3IE02LGVhPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMubWFuYWdlcj10IT09dm9pZCAwP3Q6UWZlLHRoaXMuY3Jvc3NPcmlnaW49ImFub255bW91cyIsdGhpcy53aXRoQ3JlZGVudGlhbHM9ITEsdGhpcy5wYXRoPSIiLHRoaXMucmVzb3VyY2VQYXRoPSIiLHRoaXMucmVxdWVzdEhlYWRlcj17fX1sb2FkKCl7fWxvYWRBc3luYyh0LHIpe2xldCBuPXRoaXM7cmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKGksbyl7bi5sb2FkKHQsaSxyLG8pfSl9cGFyc2UoKXt9c2V0Q3Jvc3NPcmlnaW4odCl7cmV0dXJuIHRoaXMuY3Jvc3NPcmlnaW49dCx0aGlzfXNldFdpdGhDcmVkZW50aWFscyh0KXtyZXR1cm4gdGhpcy53aXRoQ3JlZGVudGlhbHM9dCx0aGlzfXNldFBhdGgodCl7cmV0dXJuIHRoaXMucGF0aD10LHRoaXN9c2V0UmVzb3VyY2VQYXRoKHQpe3JldHVybiB0aGlzLnJlc291cmNlUGF0aD10LHRoaXN9c2V0UmVxdWVzdEhlYWRlcih0KXtyZXR1cm4gdGhpcy5yZXF1ZXN0SGVhZGVyPXQsdGhpc319LFlkPXt9LEpjPWNsYXNzIGV4dGVuZHMgZWF7Y29uc3RydWN0b3IodCl7c3VwZXIodCl9bG9hZCh0LHIsbixpKXt0PT09dm9pZCAwJiYodD0iIiksdGhpcy5wYXRoIT09dm9pZCAwJiYodD10aGlzLnBhdGgrdCksdD10aGlzLm1hbmFnZXIucmVzb2x2ZVVSTCh0KTtsZXQgbz10eC5nZXQodCk7aWYobyE9PXZvaWQgMClyZXR1cm4gdGhpcy5tYW5hZ2VyLml0ZW1TdGFydCh0KSxzZXRUaW1lb3V0KCgpPT57ciYmcihvKSx0aGlzLm1hbmFnZXIuaXRlbUVuZCh0KX0sMCksbztpZihZZFt0XSE9PXZvaWQgMCl7WWRbdF0ucHVzaCh7b25Mb2FkOnIsb25Qcm9ncmVzczpuLG9uRXJyb3I6aX0pO3JldHVybn1ZZFt0XT1bXSxZZFt0XS5wdXNoKHtvbkxvYWQ6cixvblByb2dyZXNzOm4sb25FcnJvcjppfSk7bGV0IGE9bmV3IFJlcXVlc3QodCx7aGVhZGVyczpuZXcgSGVhZGVycyh0aGlzLnJlcXVlc3RIZWFkZXIpLGNyZWRlbnRpYWxzOnRoaXMud2l0aENyZWRlbnRpYWxzPyJpbmNsdWRlIjoic2FtZS1vcmlnaW4ifSkscz10aGlzLm1pbWVUeXBlLGw9dGhpcy5yZXNwb25zZVR5cGU7ZmV0Y2goYSkudGhlbihjPT57aWYoYy5zdGF0dXM9PT0yMDB8fGMuc3RhdHVzPT09MCl7aWYoYy5zdGF0dXM9PT0wJiZjb25zb2xlLndhcm4oIlRIUkVFLkZpbGVMb2FkZXI6IEhUVFAgU3RhdHVzIDAgcmVjZWl2ZWQuIiksdHlwZW9mIFJlYWRhYmxlU3RyZWFtPT0idW5kZWZpbmVkInx8Yy5ib2R5LmdldFJlYWRlcj09PXZvaWQgMClyZXR1cm4gYztsZXQgdT1ZZFt0XSxoPWMuYm9keS5nZXRSZWFkZXIoKSxmPWMuaGVhZGVycy5nZXQoIkNvbnRlbnQtTGVuZ3RoIikscD1mP3BhcnNlSW50KGYpOjAsZD1wIT09MCxnPTAsXz1uZXcgUmVhZGFibGVTdHJlYW0oe3N0YXJ0KHkpe3goKTtmdW5jdGlvbiB4KCl7aC5yZWFkKCkudGhlbigoe2RvbmU6Yix2YWx1ZTpTfSk9PntpZihiKXkuY2xvc2UoKTtlbHNle2crPVMuYnl0ZUxlbmd0aDtsZXQgQz1uZXcgUHJvZ3Jlc3NFdmVudCgicHJvZ3Jlc3MiLHtsZW5ndGhDb21wdXRhYmxlOmQsbG9hZGVkOmcsdG90YWw6cH0pO2ZvcihsZXQgUD0wLGs9dS5sZW5ndGg7UDxrO1ArKyl7bGV0IE89dVtQXTtPLm9uUHJvZ3Jlc3MmJk8ub25Qcm9ncmVzcyhDKX15LmVucXVldWUoUykseCgpfX0pfX19KTtyZXR1cm4gbmV3IFJlc3BvbnNlKF8pfWVsc2UgdGhyb3cgRXJyb3IoYGZldGNoIGZvciAiJHtjLnVybH0iIHJlc3BvbmRlZCB3aXRoICR7Yy5zdGF0dXN9OiAke2Muc3RhdHVzVGV4dH1gKX0pLnRoZW4oYz0+e3N3aXRjaChsKXtjYXNlImFycmF5YnVmZmVyIjpyZXR1cm4gYy5hcnJheUJ1ZmZlcigpO2Nhc2UiYmxvYiI6cmV0dXJuIGMuYmxvYigpO2Nhc2UiZG9jdW1lbnQiOnJldHVybiBjLnRleHQoKS50aGVuKHU9Pm5ldyBET01QYXJzZXIoKS5wYXJzZUZyb21TdHJpbmcodSxzKSk7Y2FzZSJqc29uIjpyZXR1cm4gYy5qc29uKCk7ZGVmYXVsdDppZihzPT09dm9pZCAwKXJldHVybiBjLnRleHQoKTt7bGV0IGg9L2NoYXJzZXQ9Ij8oW147IlxzXSopIj8vaS5leGVjKHMpLGY9aCYmaFsxXT9oWzFdLnRvTG93ZXJDYXNlKCk6dm9pZCAwLHA9bmV3IFRleHREZWNvZGVyKGYpO3JldHVybiBjLmFycmF5QnVmZmVyKCkudGhlbihkPT5wLmRlY29kZShkKSl9fX0pLnRoZW4oYz0+e3R4LmFkZCh0LGMpO2xldCB1PVlkW3RdO2RlbGV0ZSBZZFt0XTtmb3IobGV0IGg9MCxmPXUubGVuZ3RoO2g8ZjtoKyspe2xldCBwPXVbaF07cC5vbkxvYWQmJnAub25Mb2FkKGMpfX0pLmNhdGNoKGM9PntsZXQgdT1ZZFt0XTtpZih1PT09dm9pZCAwKXRocm93IHRoaXMubWFuYWdlci5pdGVtRXJyb3IodCksYztkZWxldGUgWWRbdF07Zm9yKGxldCBoPTAsZj11Lmxlbmd0aDtoPGY7aCsrKXtsZXQgcD11W2hdO3Aub25FcnJvciYmcC5vbkVycm9yKGMpfXRoaXMubWFuYWdlci5pdGVtRXJyb3IodCl9KS5maW5hbGx5KCgpPT57dGhpcy5tYW5hZ2VyLml0ZW1FbmQodCl9KSx0aGlzLm1hbmFnZXIuaXRlbVN0YXJ0KHQpfXNldFJlc3BvbnNlVHlwZSh0KXtyZXR1cm4gdGhpcy5yZXNwb25zZVR5cGU9dCx0aGlzfXNldE1pbWVUeXBlKHQpe3JldHVybiB0aGlzLm1pbWVUeXBlPXQsdGhpc319LGFodD1jbGFzcyBleHRlbmRzIGVhe2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpfWxvYWQodCxyLG4saSl7bGV0IG89dGhpcyxhPW5ldyBKYyh0aGlzLm1hbmFnZXIpO2Euc2V0UGF0aCh0aGlzLnBhdGgpLGEuc2V0UmVxdWVzdEhlYWRlcih0aGlzLnJlcXVlc3RIZWFkZXIpLGEuc2V0V2l0aENyZWRlbnRpYWxzKHRoaXMud2l0aENyZWRlbnRpYWxzKSxhLmxvYWQodCxmdW5jdGlvbihzKXt0cnl7cihvLnBhcnNlKEpTT04ucGFyc2UocykpKX1jYXRjaChsKXtpP2kobCk6Y29uc29sZS5lcnJvcihsKSxvLm1hbmFnZXIuaXRlbUVycm9yKHQpfX0sbixpKX1wYXJzZSh0KXtsZXQgcj1bXTtmb3IobGV0IG49MDtuPHQubGVuZ3RoO24rKyl7bGV0IGk9UXYucGFyc2UodFtuXSk7ci5wdXNoKGkpfXJldHVybiByfX0sc2h0PWNsYXNzIGV4dGVuZHMgZWF7Y29uc3RydWN0b3IodCl7c3VwZXIodCl9bG9hZCh0LHIsbixpKXtsZXQgbz10aGlzLGE9W10scz1uZXcgbzYsbD1uZXcgSmModGhpcy5tYW5hZ2VyKTtsLnNldFBhdGgodGhpcy5wYXRoKSxsLnNldFJlc3BvbnNlVHlwZSgiYXJyYXlidWZmZXIiKSxsLnNldFJlcXVlc3RIZWFkZXIodGhpcy5yZXF1ZXN0SGVhZGVyKSxsLnNldFdpdGhDcmVkZW50aWFscyhvLndpdGhDcmVkZW50aWFscyk7bGV0IGM9MDtmdW5jdGlvbiB1KGgpe2wubG9hZCh0W2hdLGZ1bmN0aW9uKGYpe2xldCBwPW8ucGFyc2UoZiwhMCk7YVtoXT17d2lkdGg6cC53aWR0aCxoZWlnaHQ6cC5oZWlnaHQsZm9ybWF0OnAuZm9ybWF0LG1pcG1hcHM6cC5taXBtYXBzfSxjKz0xLGM9PT02JiYocC5taXBtYXBDb3VudD09PTEmJihzLm1pbkZpbHRlcj1vaSkscy5pbWFnZT1hLHMuZm9ybWF0PXAuZm9ybWF0LHMubmVlZHNVcGRhdGU9ITAsciYmcihzKSl9LG4saSl9aWYoQXJyYXkuaXNBcnJheSh0KSlmb3IobGV0IGg9MCxmPXQubGVuZ3RoO2g8ZjsrK2gpdShoKTtlbHNlIGwubG9hZCh0LGZ1bmN0aW9uKGgpe2xldCBmPW8ucGFyc2UoaCwhMCk7aWYoZi5pc0N1YmVtYXApe2xldCBwPWYubWlwbWFwcy5sZW5ndGgvZi5taXBtYXBDb3VudDtmb3IobGV0IGQ9MDtkPHA7ZCsrKXthW2RdPXttaXBtYXBzOltdfTtmb3IobGV0IGc9MDtnPGYubWlwbWFwQ291bnQ7ZysrKWFbZF0ubWlwbWFwcy5wdXNoKGYubWlwbWFwc1tkKmYubWlwbWFwQ291bnQrZ10pLGFbZF0uZm9ybWF0PWYuZm9ybWF0LGFbZF0ud2lkdGg9Zi53aWR0aCxhW2RdLmhlaWdodD1mLmhlaWdodH1zLmltYWdlPWF9ZWxzZSBzLmltYWdlLndpZHRoPWYud2lkdGgscy5pbWFnZS5oZWlnaHQ9Zi5oZWlnaHQscy5taXBtYXBzPWYubWlwbWFwcztmLm1pcG1hcENvdW50PT09MSYmKHMubWluRmlsdGVyPW9pKSxzLmZvcm1hdD1mLmZvcm1hdCxzLm5lZWRzVXBkYXRlPSEwLHImJnIocyl9LG4saSk7cmV0dXJuIHN9fSxleD1jbGFzcyBleHRlbmRzIGVhe2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpfWxvYWQodCxyLG4saSl7dGhpcy5wYXRoIT09dm9pZCAwJiYodD10aGlzLnBhdGgrdCksdD10aGlzLm1hbmFnZXIucmVzb2x2ZVVSTCh0KTtsZXQgbz10aGlzLGE9dHguZ2V0KHQpO2lmKGEhPT12b2lkIDApcmV0dXJuIG8ubWFuYWdlci5pdGVtU3RhcnQodCksc2V0VGltZW91dChmdW5jdGlvbigpe3ImJnIoYSksby5tYW5hZ2VyLml0ZW1FbmQodCl9LDApLGE7bGV0IHM9UVAoImltZyIpO2Z1bmN0aW9uIGwoKXt1KCksdHguYWRkKHQsdGhpcyksciYmcih0aGlzKSxvLm1hbmFnZXIuaXRlbUVuZCh0KX1mdW5jdGlvbiBjKGgpe3UoKSxpJiZpKGgpLG8ubWFuYWdlci5pdGVtRXJyb3IodCksby5tYW5hZ2VyLml0ZW1FbmQodCl9ZnVuY3Rpb24gdSgpe3MucmVtb3ZlRXZlbnRMaXN0ZW5lcigibG9hZCIsbCwhMSkscy5yZW1vdmVFdmVudExpc3RlbmVyKCJlcnJvciIsYywhMSl9cmV0dXJuIHMuYWRkRXZlbnRMaXN0ZW5lcigibG9hZCIsbCwhMSkscy5hZGRFdmVudExpc3RlbmVyKCJlcnJvciIsYywhMSksdC5zdWJzdHIoMCw1KSE9PSJkYXRhOiImJnRoaXMuY3Jvc3NPcmlnaW4hPT12b2lkIDAmJihzLmNyb3NzT3JpZ2luPXRoaXMuY3Jvc3NPcmlnaW4pLG8ubWFuYWdlci5pdGVtU3RhcnQodCkscy5zcmM9dCxzfX0sRVU9Y2xhc3MgZXh0ZW5kcyBlYXtjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KX1sb2FkKHQscixuLGkpe2xldCBvPW5ldyBIMCxhPW5ldyBleCh0aGlzLm1hbmFnZXIpO2Euc2V0Q3Jvc3NPcmlnaW4odGhpcy5jcm9zc09yaWdpbiksYS5zZXRQYXRoKHRoaXMucGF0aCk7bGV0IHM9MDtmdW5jdGlvbiBsKGMpe2EubG9hZCh0W2NdLGZ1bmN0aW9uKHUpe28uaW1hZ2VzW2NdPXUscysrLHM9PT02JiYoby5uZWVkc1VwZGF0ZT0hMCxyJiZyKG8pKX0sdm9pZCAwLGkpfWZvcihsZXQgYz0wO2M8dC5sZW5ndGg7KytjKWwoYyk7cmV0dXJuIG99fSxUVT1jbGFzcyBleHRlbmRzIGVhe2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpfWxvYWQodCxyLG4saSl7bGV0IG89dGhpcyxhPW5ldyBKZCxzPW5ldyBKYyh0aGlzLm1hbmFnZXIpO3JldHVybiBzLnNldFJlc3BvbnNlVHlwZSgiYXJyYXlidWZmZXIiKSxzLnNldFJlcXVlc3RIZWFkZXIodGhpcy5yZXF1ZXN0SGVhZGVyKSxzLnNldFBhdGgodGhpcy5wYXRoKSxzLnNldFdpdGhDcmVkZW50aWFscyhvLndpdGhDcmVkZW50aWFscykscy5sb2FkKHQsZnVuY3Rpb24obCl7bGV0IGM9by5wYXJzZShsKTshY3x8KGMuaW1hZ2UhPT12b2lkIDA/YS5pbWFnZT1jLmltYWdlOmMuZGF0YSE9PXZvaWQgMCYmKGEuaW1hZ2Uud2lkdGg9Yy53aWR0aCxhLmltYWdlLmhlaWdodD1jLmhlaWdodCxhLmltYWdlLmRhdGE9Yy5kYXRhKSxhLndyYXBTPWMud3JhcFMhPT12b2lkIDA/Yy53cmFwUzpKbyxhLndyYXBUPWMud3JhcFQhPT12b2lkIDA/Yy53cmFwVDpKbyxhLm1hZ0ZpbHRlcj1jLm1hZ0ZpbHRlciE9PXZvaWQgMD9jLm1hZ0ZpbHRlcjpvaSxhLm1pbkZpbHRlcj1jLm1pbkZpbHRlciE9PXZvaWQgMD9jLm1pbkZpbHRlcjpvaSxhLmFuaXNvdHJvcHk9Yy5hbmlzb3Ryb3B5IT09dm9pZCAwP2MuYW5pc290cm9weToxLGMuZW5jb2RpbmchPT12b2lkIDAmJihhLmVuY29kaW5nPWMuZW5jb2RpbmcpLGMuZmxpcFkhPT12b2lkIDAmJihhLmZsaXBZPWMuZmxpcFkpLGMuZm9ybWF0IT09dm9pZCAwJiYoYS5mb3JtYXQ9Yy5mb3JtYXQpLGMudHlwZSE9PXZvaWQgMCYmKGEudHlwZT1jLnR5cGUpLGMubWlwbWFwcyE9PXZvaWQgMCYmKGEubWlwbWFwcz1jLm1pcG1hcHMsYS5taW5GaWx0ZXI9b3gpLGMubWlwbWFwQ291bnQ9PT0xJiYoYS5taW5GaWx0ZXI9b2kpLGMuZ2VuZXJhdGVNaXBtYXBzIT09dm9pZCAwJiYoYS5nZW5lcmF0ZU1pcG1hcHM9Yy5nZW5lcmF0ZU1pcG1hcHMpLGEubmVlZHNVcGRhdGU9ITAsciYmcihhLGMpKX0sbixpKSxhfX0sQ1U9Y2xhc3MgZXh0ZW5kcyBlYXtjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KX1sb2FkKHQscixuLGkpe2xldCBvPW5ldyB4aSxhPW5ldyBleCh0aGlzLm1hbmFnZXIpO3JldHVybiBhLnNldENyb3NzT3JpZ2luKHRoaXMuY3Jvc3NPcmlnaW4pLGEuc2V0UGF0aCh0aGlzLnBhdGgpLGEubG9hZCh0LGZ1bmN0aW9uKHMpe28uaW1hZ2U9cyxvLm5lZWRzVXBkYXRlPSEwLHIhPT12b2lkIDAmJnIobyl9LG4saSksb319LE9sPWNsYXNzIGV4dGVuZHMgb3J7Y29uc3RydWN0b3IodCxyPTEpe3N1cGVyKCksdGhpcy50eXBlPSJMaWdodCIsdGhpcy5jb2xvcj1uZXcgbmUodCksdGhpcy5pbnRlbnNpdHk9cn1kaXNwb3NlKCl7fWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMuaW50ZW5zaXR5PXQuaW50ZW5zaXR5LHRoaXN9dG9KU09OKHQpe2xldCByPXN1cGVyLnRvSlNPTih0KTtyZXR1cm4gci5vYmplY3QuY29sb3I9dGhpcy5jb2xvci5nZXRIZXgoKSxyLm9iamVjdC5pbnRlbnNpdHk9dGhpcy5pbnRlbnNpdHksdGhpcy5ncm91bmRDb2xvciE9PXZvaWQgMCYmKHIub2JqZWN0Lmdyb3VuZENvbG9yPXRoaXMuZ3JvdW5kQ29sb3IuZ2V0SGV4KCkpLHRoaXMuZGlzdGFuY2UhPT12b2lkIDAmJihyLm9iamVjdC5kaXN0YW5jZT10aGlzLmRpc3RhbmNlKSx0aGlzLmFuZ2xlIT09dm9pZCAwJiYoci5vYmplY3QuYW5nbGU9dGhpcy5hbmdsZSksdGhpcy5kZWNheSE9PXZvaWQgMCYmKHIub2JqZWN0LmRlY2F5PXRoaXMuZGVjYXkpLHRoaXMucGVudW1icmEhPT12b2lkIDAmJihyLm9iamVjdC5wZW51bWJyYT10aGlzLnBlbnVtYnJhKSx0aGlzLnNoYWRvdyE9PXZvaWQgMCYmKHIub2JqZWN0LnNoYWRvdz10aGlzLnNoYWRvdy50b0pTT04oKSkscn19O09sLnByb3RvdHlwZS5pc0xpZ2h0PSEwO3ZhciBFNj1jbGFzcyBleHRlbmRzIE9se2NvbnN0cnVjdG9yKHQscixuKXtzdXBlcih0LG4pLHRoaXMudHlwZT0iSGVtaXNwaGVyZUxpZ2h0Iix0aGlzLnBvc2l0aW9uLmNvcHkob3IuRGVmYXVsdFVwKSx0aGlzLnVwZGF0ZU1hdHJpeCgpLHRoaXMuZ3JvdW5kQ29sb3I9bmV3IG5lKHIpfWNvcHkodCl7cmV0dXJuIE9sLnByb3RvdHlwZS5jb3B5LmNhbGwodGhpcyx0KSx0aGlzLmdyb3VuZENvbG9yLmNvcHkodC5ncm91bmRDb2xvciksdGhpc319O0U2LnByb3RvdHlwZS5pc0hlbWlzcGhlcmVMaWdodD0hMDt2YXIgbWhlPW5ldyBNZSxnaGU9bmV3IGosX2hlPW5ldyBqLFQ2PWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuY2FtZXJhPXQsdGhpcy5iaWFzPTAsdGhpcy5ub3JtYWxCaWFzPTAsdGhpcy5yYWRpdXM9MSx0aGlzLmJsdXJTYW1wbGVzPTgsdGhpcy5tYXBTaXplPW5ldyBMdCg1MTIsNTEyKSx0aGlzLm1hcD1udWxsLHRoaXMubWFwUGFzcz1udWxsLHRoaXMubWF0cml4PW5ldyBNZSx0aGlzLmF1dG9VcGRhdGU9ITAsdGhpcy5uZWVkc1VwZGF0ZT0hMSx0aGlzLl9mcnVzdHVtPW5ldyBOdix0aGlzLl9mcmFtZUV4dGVudHM9bmV3IEx0KDEsMSksdGhpcy5fdmlld3BvcnRDb3VudD0xLHRoaXMuX3ZpZXdwb3J0cz1bbmV3IGVuKDAsMCwxLDEpXX1nZXRWaWV3cG9ydENvdW50KCl7cmV0dXJuIHRoaXMuX3ZpZXdwb3J0Q291bnR9Z2V0RnJ1c3R1bSgpe3JldHVybiB0aGlzLl9mcnVzdHVtfXVwZGF0ZU1hdHJpY2VzKHQpe2xldCByPXRoaXMuY2FtZXJhLG49dGhpcy5tYXRyaXg7Z2hlLnNldEZyb21NYXRyaXhQb3NpdGlvbih0Lm1hdHJpeFdvcmxkKSxyLnBvc2l0aW9uLmNvcHkoZ2hlKSxfaGUuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKHQudGFyZ2V0Lm1hdHJpeFdvcmxkKSxyLmxvb2tBdChfaGUpLHIudXBkYXRlTWF0cml4V29ybGQoKSxtaGUubXVsdGlwbHlNYXRyaWNlcyhyLnByb2plY3Rpb25NYXRyaXgsci5tYXRyaXhXb3JsZEludmVyc2UpLHRoaXMuX2ZydXN0dW0uc2V0RnJvbVByb2plY3Rpb25NYXRyaXgobWhlKSxuLnNldCguNSwwLDAsLjUsMCwuNSwwLC41LDAsMCwuNSwuNSwwLDAsMCwxKSxuLm11bHRpcGx5KHIucHJvamVjdGlvbk1hdHJpeCksbi5tdWx0aXBseShyLm1hdHJpeFdvcmxkSW52ZXJzZSl9Z2V0Vmlld3BvcnQodCl7cmV0dXJuIHRoaXMuX3ZpZXdwb3J0c1t0XX1nZXRGcmFtZUV4dGVudHMoKXtyZXR1cm4gdGhpcy5fZnJhbWVFeHRlbnRzfWRpc3Bvc2UoKXt0aGlzLm1hcCYmdGhpcy5tYXAuZGlzcG9zZSgpLHRoaXMubWFwUGFzcyYmdGhpcy5tYXBQYXNzLmRpc3Bvc2UoKX1jb3B5KHQpe3JldHVybiB0aGlzLmNhbWVyYT10LmNhbWVyYS5jbG9uZSgpLHRoaXMuYmlhcz10LmJpYXMsdGhpcy5yYWRpdXM9dC5yYWRpdXMsdGhpcy5tYXBTaXplLmNvcHkodC5tYXBTaXplKSx0aGlzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX10b0pTT04oKXtsZXQgdD17fTtyZXR1cm4gdGhpcy5iaWFzIT09MCYmKHQuYmlhcz10aGlzLmJpYXMpLHRoaXMubm9ybWFsQmlhcyE9PTAmJih0Lm5vcm1hbEJpYXM9dGhpcy5ub3JtYWxCaWFzKSx0aGlzLnJhZGl1cyE9PTEmJih0LnJhZGl1cz10aGlzLnJhZGl1cyksKHRoaXMubWFwU2l6ZS54IT09NTEyfHx0aGlzLm1hcFNpemUueSE9PTUxMikmJih0Lm1hcFNpemU9dGhpcy5tYXBTaXplLnRvQXJyYXkoKSksdC5jYW1lcmE9dGhpcy5jYW1lcmEudG9KU09OKCExKS5vYmplY3QsZGVsZXRlIHQuY2FtZXJhLm1hdHJpeCx0fX0sQVU9Y2xhc3MgZXh0ZW5kcyBUNntjb25zdHJ1Y3Rvcigpe3N1cGVyKG5ldyBVaSg1MCwxLC41LDUwMCkpLHRoaXMuZm9jdXM9MX11cGRhdGVNYXRyaWNlcyh0KXtsZXQgcj10aGlzLmNhbWVyYSxuPUpQKjIqdC5hbmdsZSp0aGlzLmZvY3VzLGk9dGhpcy5tYXBTaXplLndpZHRoL3RoaXMubWFwU2l6ZS5oZWlnaHQsbz10LmRpc3RhbmNlfHxyLmZhcjsobiE9PXIuZm92fHxpIT09ci5hc3BlY3R8fG8hPT1yLmZhcikmJihyLmZvdj1uLHIuYXNwZWN0PWksci5mYXI9byxyLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKSksc3VwZXIudXBkYXRlTWF0cmljZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmZvY3VzPXQuZm9jdXMsdGhpc319O0FVLnByb3RvdHlwZS5pc1Nwb3RMaWdodFNoYWRvdz0hMDt2YXIgQzY9Y2xhc3MgZXh0ZW5kcyBPbHtjb25zdHJ1Y3Rvcih0LHIsbj0wLGk9TWF0aC5QSS8zLG89MCxhPTEpe3N1cGVyKHQsciksdGhpcy50eXBlPSJTcG90TGlnaHQiLHRoaXMucG9zaXRpb24uY29weShvci5EZWZhdWx0VXApLHRoaXMudXBkYXRlTWF0cml4KCksdGhpcy50YXJnZXQ9bmV3IG9yLHRoaXMuZGlzdGFuY2U9bix0aGlzLmFuZ2xlPWksdGhpcy5wZW51bWJyYT1vLHRoaXMuZGVjYXk9YSx0aGlzLnNoYWRvdz1uZXcgQVV9Z2V0IHBvd2VyKCl7cmV0dXJuIHRoaXMuaW50ZW5zaXR5Kk1hdGguUEl9c2V0IHBvd2VyKHQpe3RoaXMuaW50ZW5zaXR5PXQvTWF0aC5QSX1kaXNwb3NlKCl7dGhpcy5zaGFkb3cuZGlzcG9zZSgpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5kaXN0YW5jZT10LmRpc3RhbmNlLHRoaXMuYW5nbGU9dC5hbmdsZSx0aGlzLnBlbnVtYnJhPXQucGVudW1icmEsdGhpcy5kZWNheT10LmRlY2F5LHRoaXMudGFyZ2V0PXQudGFyZ2V0LmNsb25lKCksdGhpcy5zaGFkb3c9dC5zaGFkb3cuY2xvbmUoKSx0aGlzfX07QzYucHJvdG90eXBlLmlzU3BvdExpZ2h0PSEwO3ZhciB5aGU9bmV3IE1lLEZQPW5ldyBqLGJ1dD1uZXcgaixQVT1jbGFzcyBleHRlbmRzIFQ2e2NvbnN0cnVjdG9yKCl7c3VwZXIobmV3IFVpKDkwLDEsLjUsNTAwKSksdGhpcy5fZnJhbWVFeHRlbnRzPW5ldyBMdCg0LDIpLHRoaXMuX3ZpZXdwb3J0Q291bnQ9Nix0aGlzLl92aWV3cG9ydHM9W25ldyBlbigyLDEsMSwxKSxuZXcgZW4oMCwxLDEsMSksbmV3IGVuKDMsMSwxLDEpLG5ldyBlbigxLDEsMSwxKSxuZXcgZW4oMywwLDEsMSksbmV3IGVuKDEsMCwxLDEpXSx0aGlzLl9jdWJlRGlyZWN0aW9ucz1bbmV3IGooMSwwLDApLG5ldyBqKC0xLDAsMCksbmV3IGooMCwwLDEpLG5ldyBqKDAsMCwtMSksbmV3IGooMCwxLDApLG5ldyBqKDAsLTEsMCldLHRoaXMuX2N1YmVVcHM9W25ldyBqKDAsMSwwKSxuZXcgaigwLDEsMCksbmV3IGooMCwxLDApLG5ldyBqKDAsMSwwKSxuZXcgaigwLDAsMSksbmV3IGooMCwwLC0xKV19dXBkYXRlTWF0cmljZXModCxyPTApe2xldCBuPXRoaXMuY2FtZXJhLGk9dGhpcy5tYXRyaXgsbz10LmRpc3RhbmNlfHxuLmZhcjtvIT09bi5mYXImJihuLmZhcj1vLG4udXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpKSxGUC5zZXRGcm9tTWF0cml4UG9zaXRpb24odC5tYXRyaXhXb3JsZCksbi5wb3NpdGlvbi5jb3B5KEZQKSxidXQuY29weShuLnBvc2l0aW9uKSxidXQuYWRkKHRoaXMuX2N1YmVEaXJlY3Rpb25zW3JdKSxuLnVwLmNvcHkodGhpcy5fY3ViZVVwc1tyXSksbi5sb29rQXQoYnV0KSxuLnVwZGF0ZU1hdHJpeFdvcmxkKCksaS5tYWtlVHJhbnNsYXRpb24oLUZQLngsLUZQLnksLUZQLnopLHloZS5tdWx0aXBseU1hdHJpY2VzKG4ucHJvamVjdGlvbk1hdHJpeCxuLm1hdHJpeFdvcmxkSW52ZXJzZSksdGhpcy5fZnJ1c3R1bS5zZXRGcm9tUHJvamVjdGlvbk1hdHJpeCh5aGUpfX07UFUucHJvdG90eXBlLmlzUG9pbnRMaWdodFNoYWRvdz0hMDt2YXIgQTY9Y2xhc3MgZXh0ZW5kcyBPbHtjb25zdHJ1Y3Rvcih0LHIsbj0wLGk9MSl7c3VwZXIodCxyKSx0aGlzLnR5cGU9IlBvaW50TGlnaHQiLHRoaXMuZGlzdGFuY2U9bix0aGlzLmRlY2F5PWksdGhpcy5zaGFkb3c9bmV3IFBVfWdldCBwb3dlcigpe3JldHVybiB0aGlzLmludGVuc2l0eSo0Kk1hdGguUEl9c2V0IHBvd2VyKHQpe3RoaXMuaW50ZW5zaXR5PXQvKDQqTWF0aC5QSSl9ZGlzcG9zZSgpe3RoaXMuc2hhZG93LmRpc3Bvc2UoKX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuZGlzdGFuY2U9dC5kaXN0YW5jZSx0aGlzLmRlY2F5PXQuZGVjYXksdGhpcy5zaGFkb3c9dC5zaGFkb3cuY2xvbmUoKSx0aGlzfX07QTYucHJvdG90eXBlLmlzUG9pbnRMaWdodD0hMDt2YXIgSVU9Y2xhc3MgZXh0ZW5kcyBUNntjb25zdHJ1Y3Rvcigpe3N1cGVyKG5ldyBEdigtNSw1LDUsLTUsLjUsNTAwKSl9fTtJVS5wcm90b3R5cGUuaXNEaXJlY3Rpb25hbExpZ2h0U2hhZG93PSEwO3ZhciBQNj1jbGFzcyBleHRlbmRzIE9se2NvbnN0cnVjdG9yKHQscil7c3VwZXIodCxyKSx0aGlzLnR5cGU9IkRpcmVjdGlvbmFsTGlnaHQiLHRoaXMucG9zaXRpb24uY29weShvci5EZWZhdWx0VXApLHRoaXMudXBkYXRlTWF0cml4KCksdGhpcy50YXJnZXQ9bmV3IG9yLHRoaXMuc2hhZG93PW5ldyBJVX1kaXNwb3NlKCl7dGhpcy5zaGFkb3cuZGlzcG9zZSgpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy50YXJnZXQ9dC50YXJnZXQuY2xvbmUoKSx0aGlzLnNoYWRvdz10LnNoYWRvdy5jbG9uZSgpLHRoaXN9fTtQNi5wcm90b3R5cGUuaXNEaXJlY3Rpb25hbExpZ2h0PSEwO3ZhciBJNj1jbGFzcyBleHRlbmRzIE9se2NvbnN0cnVjdG9yKHQscil7c3VwZXIodCxyKSx0aGlzLnR5cGU9IkFtYmllbnRMaWdodCJ9fTtJNi5wcm90b3R5cGUuaXNBbWJpZW50TGlnaHQ9ITA7dmFyIEw2PWNsYXNzIGV4dGVuZHMgT2x7Y29uc3RydWN0b3IodCxyLG49MTAsaT0xMCl7c3VwZXIodCxyKSx0aGlzLnR5cGU9IlJlY3RBcmVhTGlnaHQiLHRoaXMud2lkdGg9bix0aGlzLmhlaWdodD1pfWdldCBwb3dlcigpe3JldHVybiB0aGlzLmludGVuc2l0eSp0aGlzLndpZHRoKnRoaXMuaGVpZ2h0Kk1hdGguUEl9c2V0IHBvd2VyKHQpe3RoaXMuaW50ZW5zaXR5PXQvKHRoaXMud2lkdGgqdGhpcy5oZWlnaHQqTWF0aC5QSSl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLndpZHRoPXQud2lkdGgsdGhpcy5oZWlnaHQ9dC5oZWlnaHQsdGhpc310b0pTT04odCl7bGV0IHI9c3VwZXIudG9KU09OKHQpO3JldHVybiByLm9iamVjdC53aWR0aD10aGlzLndpZHRoLHIub2JqZWN0LmhlaWdodD10aGlzLmhlaWdodCxyfX07TDYucHJvdG90eXBlLmlzUmVjdEFyZWFMaWdodD0hMDt2YXIgazY9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmNvZWZmaWNpZW50cz1bXTtmb3IobGV0IHQ9MDt0PDk7dCsrKXRoaXMuY29lZmZpY2llbnRzLnB1c2gobmV3IGopfXNldCh0KXtmb3IobGV0IHI9MDtyPDk7cisrKXRoaXMuY29lZmZpY2llbnRzW3JdLmNvcHkodFtyXSk7cmV0dXJuIHRoaXN9emVybygpe2ZvcihsZXQgdD0wO3Q8OTt0KyspdGhpcy5jb2VmZmljaWVudHNbdF0uc2V0KDAsMCwwKTtyZXR1cm4gdGhpc31nZXRBdCh0LHIpe2xldCBuPXQueCxpPXQueSxvPXQueixhPXRoaXMuY29lZmZpY2llbnRzO3JldHVybiByLmNvcHkoYVswXSkubXVsdGlwbHlTY2FsYXIoLjI4MjA5NSksci5hZGRTY2FsZWRWZWN0b3IoYVsxXSwuNDg4NjAzKmkpLHIuYWRkU2NhbGVkVmVjdG9yKGFbMl0sLjQ4ODYwMypvKSxyLmFkZFNjYWxlZFZlY3RvcihhWzNdLC40ODg2MDMqbiksci5hZGRTY2FsZWRWZWN0b3IoYVs0XSwxLjA5MjU0OCoobippKSksci5hZGRTY2FsZWRWZWN0b3IoYVs1XSwxLjA5MjU0OCooaSpvKSksci5hZGRTY2FsZWRWZWN0b3IoYVs2XSwuMzE1MzkyKigzKm8qby0xKSksci5hZGRTY2FsZWRWZWN0b3IoYVs3XSwxLjA5MjU0OCoobipvKSksci5hZGRTY2FsZWRWZWN0b3IoYVs4XSwuNTQ2Mjc0KihuKm4taSppKSkscn1nZXRJcnJhZGlhbmNlQXQodCxyKXtsZXQgbj10LngsaT10Lnksbz10LnosYT10aGlzLmNvZWZmaWNpZW50cztyZXR1cm4gci5jb3B5KGFbMF0pLm11bHRpcGx5U2NhbGFyKC44ODYyMjcpLHIuYWRkU2NhbGVkVmVjdG9yKGFbMV0sMiouNTExNjY0KmkpLHIuYWRkU2NhbGVkVmVjdG9yKGFbMl0sMiouNTExNjY0Km8pLHIuYWRkU2NhbGVkVmVjdG9yKGFbM10sMiouNTExNjY0Km4pLHIuYWRkU2NhbGVkVmVjdG9yKGFbNF0sMiouNDI5MDQzKm4qaSksci5hZGRTY2FsZWRWZWN0b3IoYVs1XSwyKi40MjkwNDMqaSpvKSxyLmFkZFNjYWxlZFZlY3RvcihhWzZdLC43NDMxMjUqbypvLS4yNDc3MDgpLHIuYWRkU2NhbGVkVmVjdG9yKGFbN10sMiouNDI5MDQzKm4qbyksci5hZGRTY2FsZWRWZWN0b3IoYVs4XSwuNDI5MDQzKihuKm4taSppKSkscn1hZGQodCl7Zm9yKGxldCByPTA7cjw5O3IrKyl0aGlzLmNvZWZmaWNpZW50c1tyXS5hZGQodC5jb2VmZmljaWVudHNbcl0pO3JldHVybiB0aGlzfWFkZFNjYWxlZFNIKHQscil7Zm9yKGxldCBuPTA7bjw5O24rKyl0aGlzLmNvZWZmaWNpZW50c1tuXS5hZGRTY2FsZWRWZWN0b3IodC5jb2VmZmljaWVudHNbbl0scik7cmV0dXJuIHRoaXN9c2NhbGUodCl7Zm9yKGxldCByPTA7cjw5O3IrKyl0aGlzLmNvZWZmaWNpZW50c1tyXS5tdWx0aXBseVNjYWxhcih0KTtyZXR1cm4gdGhpc31sZXJwKHQscil7Zm9yKGxldCBuPTA7bjw5O24rKyl0aGlzLmNvZWZmaWNpZW50c1tuXS5sZXJwKHQuY29lZmZpY2llbnRzW25dLHIpO3JldHVybiB0aGlzfWVxdWFscyh0KXtmb3IobGV0IHI9MDtyPDk7cisrKWlmKCF0aGlzLmNvZWZmaWNpZW50c1tyXS5lcXVhbHModC5jb2VmZmljaWVudHNbcl0pKXJldHVybiExO3JldHVybiEwfWNvcHkodCl7cmV0dXJuIHRoaXMuc2V0KHQuY29lZmZpY2llbnRzKX1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyl9ZnJvbUFycmF5KHQscj0wKXtsZXQgbj10aGlzLmNvZWZmaWNpZW50cztmb3IobGV0IGk9MDtpPDk7aSsrKW5baV0uZnJvbUFycmF5KHQscitpKjMpO3JldHVybiB0aGlzfXRvQXJyYXkodD1bXSxyPTApe2xldCBuPXRoaXMuY29lZmZpY2llbnRzO2ZvcihsZXQgaT0wO2k8OTtpKyspbltpXS50b0FycmF5KHQscitpKjMpO3JldHVybiB0fXN0YXRpYyBnZXRCYXNpc0F0KHQscil7bGV0IG49dC54LGk9dC55LG89dC56O3JbMF09LjI4MjA5NSxyWzFdPS40ODg2MDMqaSxyWzJdPS40ODg2MDMqbyxyWzNdPS40ODg2MDMqbixyWzRdPTEuMDkyNTQ4Km4qaSxyWzVdPTEuMDkyNTQ4KmkqbyxyWzZdPS4zMTUzOTIqKDMqbypvLTEpLHJbN109MS4wOTI1NDgqbipvLHJbOF09LjU0NjI3NCoobipuLWkqaSl9fTtrNi5wcm90b3R5cGUuaXNTcGhlcmljYWxIYXJtb25pY3MzPSEwO3ZhciByeD1jbGFzcyBleHRlbmRzIE9se2NvbnN0cnVjdG9yKHQ9bmV3IGs2LHI9MSl7c3VwZXIodm9pZCAwLHIpLHRoaXMuc2g9dH1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuc2guY29weSh0LnNoKSx0aGlzfWZyb21KU09OKHQpe3JldHVybiB0aGlzLmludGVuc2l0eT10LmludGVuc2l0eSx0aGlzLnNoLmZyb21BcnJheSh0LnNoKSx0aGlzfXRvSlNPTih0KXtsZXQgcj1zdXBlci50b0pTT04odCk7cmV0dXJuIHIub2JqZWN0LnNoPXRoaXMuc2gudG9BcnJheSgpLHJ9fTtyeC5wcm90b3R5cGUuaXNMaWdodFByb2JlPSEwO3ZhciBMVT1jbGFzcyBleHRlbmRzIGVhe2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpLHRoaXMudGV4dHVyZXM9e319bG9hZCh0LHIsbixpKXtsZXQgbz10aGlzLGE9bmV3IEpjKG8ubWFuYWdlcik7YS5zZXRQYXRoKG8ucGF0aCksYS5zZXRSZXF1ZXN0SGVhZGVyKG8ucmVxdWVzdEhlYWRlciksYS5zZXRXaXRoQ3JlZGVudGlhbHMoby53aXRoQ3JlZGVudGlhbHMpLGEubG9hZCh0LGZ1bmN0aW9uKHMpe3RyeXtyKG8ucGFyc2UoSlNPTi5wYXJzZShzKSkpfWNhdGNoKGwpe2k/aShsKTpjb25zb2xlLmVycm9yKGwpLG8ubWFuYWdlci5pdGVtRXJyb3IodCl9fSxuLGkpfXBhcnNlKHQpe2xldCByPXRoaXMudGV4dHVyZXM7ZnVuY3Rpb24gbihvKXtyZXR1cm4gcltvXT09PXZvaWQgMCYmY29uc29sZS53YXJuKCJUSFJFRS5NYXRlcmlhbExvYWRlcjogVW5kZWZpbmVkIHRleHR1cmUiLG8pLHJbb119bGV0IGk9bmV3IG8wclt0LnR5cGVdO2lmKHQudXVpZCE9PXZvaWQgMCYmKGkudXVpZD10LnV1aWQpLHQubmFtZSE9PXZvaWQgMCYmKGkubmFtZT10Lm5hbWUpLHQuY29sb3IhPT12b2lkIDAmJmkuY29sb3IhPT12b2lkIDAmJmkuY29sb3Iuc2V0SGV4KHQuY29sb3IpLHQucm91Z2huZXNzIT09dm9pZCAwJiYoaS5yb3VnaG5lc3M9dC5yb3VnaG5lc3MpLHQubWV0YWxuZXNzIT09dm9pZCAwJiYoaS5tZXRhbG5lc3M9dC5tZXRhbG5lc3MpLHQuc2hlZW4hPT12b2lkIDAmJihpLnNoZWVuPXQuc2hlZW4pLHQuc2hlZW5Db2xvciE9PXZvaWQgMCYmKGkuc2hlZW5Db2xvcj1uZXcgbmUoKS5zZXRIZXgodC5zaGVlbkNvbG9yKSksdC5zaGVlblJvdWdobmVzcyE9PXZvaWQgMCYmKGkuc2hlZW5Sb3VnaG5lc3M9dC5zaGVlblJvdWdobmVzcyksdC5lbWlzc2l2ZSE9PXZvaWQgMCYmaS5lbWlzc2l2ZSE9PXZvaWQgMCYmaS5lbWlzc2l2ZS5zZXRIZXgodC5lbWlzc2l2ZSksdC5zcGVjdWxhciE9PXZvaWQgMCYmaS5zcGVjdWxhciE9PXZvaWQgMCYmaS5zcGVjdWxhci5zZXRIZXgodC5zcGVjdWxhciksdC5zcGVjdWxhckludGVuc2l0eSE9PXZvaWQgMCYmKGkuc3BlY3VsYXJJbnRlbnNpdHk9dC5zcGVjdWxhckludGVuc2l0eSksdC5zcGVjdWxhckNvbG9yIT09dm9pZCAwJiZpLnNwZWN1bGFyQ29sb3IhPT12b2lkIDAmJmkuc3BlY3VsYXJDb2xvci5zZXRIZXgodC5zcGVjdWxhckNvbG9yKSx0LnNoaW5pbmVzcyE9PXZvaWQgMCYmKGkuc2hpbmluZXNzPXQuc2hpbmluZXNzKSx0LmNsZWFyY29hdCE9PXZvaWQgMCYmKGkuY2xlYXJjb2F0PXQuY2xlYXJjb2F0KSx0LmNsZWFyY29hdFJvdWdobmVzcyE9PXZvaWQgMCYmKGkuY2xlYXJjb2F0Um91Z2huZXNzPXQuY2xlYXJjb2F0Um91Z2huZXNzKSx0LnRyYW5zbWlzc2lvbiE9PXZvaWQgMCYmKGkudHJhbnNtaXNzaW9uPXQudHJhbnNtaXNzaW9uKSx0LnRoaWNrbmVzcyE9PXZvaWQgMCYmKGkudGhpY2tuZXNzPXQudGhpY2tuZXNzKSx0LmF0dGVudWF0aW9uRGlzdGFuY2UhPT12b2lkIDAmJihpLmF0dGVudWF0aW9uRGlzdGFuY2U9dC5hdHRlbnVhdGlvbkRpc3RhbmNlKSx0LmF0dGVudWF0aW9uQ29sb3IhPT12b2lkIDAmJmkuYXR0ZW51YXRpb25Db2xvciE9PXZvaWQgMCYmaS5hdHRlbnVhdGlvbkNvbG9yLnNldEhleCh0LmF0dGVudWF0aW9uQ29sb3IpLHQuZm9nIT09dm9pZCAwJiYoaS5mb2c9dC5mb2cpLHQuZmxhdFNoYWRpbmchPT12b2lkIDAmJihpLmZsYXRTaGFkaW5nPXQuZmxhdFNoYWRpbmcpLHQuYmxlbmRpbmchPT12b2lkIDAmJihpLmJsZW5kaW5nPXQuYmxlbmRpbmcpLHQuY29tYmluZSE9PXZvaWQgMCYmKGkuY29tYmluZT10LmNvbWJpbmUpLHQuc2lkZSE9PXZvaWQgMCYmKGkuc2lkZT10LnNpZGUpLHQuc2hhZG93U2lkZSE9PXZvaWQgMCYmKGkuc2hhZG93U2lkZT10LnNoYWRvd1NpZGUpLHQub3BhY2l0eSE9PXZvaWQgMCYmKGkub3BhY2l0eT10Lm9wYWNpdHkpLHQudHJhbnNwYXJlbnQhPT12b2lkIDAmJihpLnRyYW5zcGFyZW50PXQudHJhbnNwYXJlbnQpLHQuYWxwaGFUZXN0IT09dm9pZCAwJiYoaS5hbHBoYVRlc3Q9dC5hbHBoYVRlc3QpLHQuZGVwdGhUZXN0IT09dm9pZCAwJiYoaS5kZXB0aFRlc3Q9dC5kZXB0aFRlc3QpLHQuZGVwdGhXcml0ZSE9PXZvaWQgMCYmKGkuZGVwdGhXcml0ZT10LmRlcHRoV3JpdGUpLHQuY29sb3JXcml0ZSE9PXZvaWQgMCYmKGkuY29sb3JXcml0ZT10LmNvbG9yV3JpdGUpLHQuYWxwaGFXcml0ZSE9PXZvaWQgMCYmKGkuYWxwaGFXcml0ZT10LmFscGhhV3JpdGUpLHQuc3RlbmNpbFdyaXRlIT09dm9pZCAwJiYoaS5zdGVuY2lsV3JpdGU9dC5zdGVuY2lsV3JpdGUpLHQuc3RlbmNpbFdyaXRlTWFzayE9PXZvaWQgMCYmKGkuc3RlbmNpbFdyaXRlTWFzaz10LnN0ZW5jaWxXcml0ZU1hc2spLHQuc3RlbmNpbEZ1bmMhPT12b2lkIDAmJihpLnN0ZW5jaWxGdW5jPXQuc3RlbmNpbEZ1bmMpLHQuc3RlbmNpbFJlZiE9PXZvaWQgMCYmKGkuc3RlbmNpbFJlZj10LnN0ZW5jaWxSZWYpLHQuc3RlbmNpbEZ1bmNNYXNrIT09dm9pZCAwJiYoaS5zdGVuY2lsRnVuY01hc2s9dC5zdGVuY2lsRnVuY01hc2spLHQuc3RlbmNpbEZhaWwhPT12b2lkIDAmJihpLnN0ZW5jaWxGYWlsPXQuc3RlbmNpbEZhaWwpLHQuc3RlbmNpbFpGYWlsIT09dm9pZCAwJiYoaS5zdGVuY2lsWkZhaWw9dC5zdGVuY2lsWkZhaWwpLHQuc3RlbmNpbFpQYXNzIT09dm9pZCAwJiYoaS5zdGVuY2lsWlBhc3M9dC5zdGVuY2lsWlBhc3MpLHQud2lyZWZyYW1lIT09dm9pZCAwJiYoaS53aXJlZnJhbWU9dC53aXJlZnJhbWUpLHQud2lyZWZyYW1lTGluZXdpZHRoIT09dm9pZCAwJiYoaS53aXJlZnJhbWVMaW5ld2lkdGg9dC53aXJlZnJhbWVMaW5ld2lkdGgpLHQud2lyZWZyYW1lTGluZWNhcCE9PXZvaWQgMCYmKGkud2lyZWZyYW1lTGluZWNhcD10LndpcmVmcmFtZUxpbmVjYXApLHQud2lyZWZyYW1lTGluZWpvaW4hPT12b2lkIDAmJihpLndpcmVmcmFtZUxpbmVqb2luPXQud2lyZWZyYW1lTGluZWpvaW4pLHQucm90YXRpb24hPT12b2lkIDAmJihpLnJvdGF0aW9uPXQucm90YXRpb24pLHQubGluZXdpZHRoIT09MSYmKGkubGluZXdpZHRoPXQubGluZXdpZHRoKSx0LmRhc2hTaXplIT09dm9pZCAwJiYoaS5kYXNoU2l6ZT10LmRhc2hTaXplKSx0LmdhcFNpemUhPT12b2lkIDAmJihpLmdhcFNpemU9dC5nYXBTaXplKSx0LnNjYWxlIT09dm9pZCAwJiYoaS5zY2FsZT10LnNjYWxlKSx0LnBvbHlnb25PZmZzZXQhPT12b2lkIDAmJihpLnBvbHlnb25PZmZzZXQ9dC5wb2x5Z29uT2Zmc2V0KSx0LnBvbHlnb25PZmZzZXRGYWN0b3IhPT12b2lkIDAmJihpLnBvbHlnb25PZmZzZXRGYWN0b3I9dC5wb2x5Z29uT2Zmc2V0RmFjdG9yKSx0LnBvbHlnb25PZmZzZXRVbml0cyE9PXZvaWQgMCYmKGkucG9seWdvbk9mZnNldFVuaXRzPXQucG9seWdvbk9mZnNldFVuaXRzKSx0LmRpdGhlcmluZyE9PXZvaWQgMCYmKGkuZGl0aGVyaW5nPXQuZGl0aGVyaW5nKSx0LmFscGhhVG9Db3ZlcmFnZSE9PXZvaWQgMCYmKGkuYWxwaGFUb0NvdmVyYWdlPXQuYWxwaGFUb0NvdmVyYWdlKSx0LnByZW11bHRpcGxpZWRBbHBoYSE9PXZvaWQgMCYmKGkucHJlbXVsdGlwbGllZEFscGhhPXQucHJlbXVsdGlwbGllZEFscGhhKSx0LnZpc2libGUhPT12b2lkIDAmJihpLnZpc2libGU9dC52aXNpYmxlKSx0LnRvbmVNYXBwZWQhPT12b2lkIDAmJihpLnRvbmVNYXBwZWQ9dC50b25lTWFwcGVkKSx0LnVzZXJEYXRhIT09dm9pZCAwJiYoaS51c2VyRGF0YT10LnVzZXJEYXRhKSx0LnZlcnRleENvbG9ycyE9PXZvaWQgMCYmKHR5cGVvZiB0LnZlcnRleENvbG9ycz09Im51bWJlciI/aS52ZXJ0ZXhDb2xvcnM9dC52ZXJ0ZXhDb2xvcnM+MDppLnZlcnRleENvbG9ycz10LnZlcnRleENvbG9ycyksdC51bmlmb3JtcyE9PXZvaWQgMClmb3IobGV0IG8gaW4gdC51bmlmb3Jtcyl7bGV0IGE9dC51bmlmb3Jtc1tvXTtzd2l0Y2goaS51bmlmb3Jtc1tvXT17fSxhLnR5cGUpe2Nhc2UidCI6aS51bmlmb3Jtc1tvXS52YWx1ZT1uKGEudmFsdWUpO2JyZWFrO2Nhc2UiYyI6aS51bmlmb3Jtc1tvXS52YWx1ZT1uZXcgbmUoKS5zZXRIZXgoYS52YWx1ZSk7YnJlYWs7Y2FzZSJ2MiI6aS51bmlmb3Jtc1tvXS52YWx1ZT1uZXcgTHQoKS5mcm9tQXJyYXkoYS52YWx1ZSk7YnJlYWs7Y2FzZSJ2MyI6aS51bmlmb3Jtc1tvXS52YWx1ZT1uZXcgaigpLmZyb21BcnJheShhLnZhbHVlKTticmVhaztjYXNlInY0IjppLnVuaWZvcm1zW29dLnZhbHVlPW5ldyBlbigpLmZyb21BcnJheShhLnZhbHVlKTticmVhaztjYXNlIm0zIjppLnVuaWZvcm1zW29dLnZhbHVlPW5ldyBraSgpLmZyb21BcnJheShhLnZhbHVlKTticmVhaztjYXNlIm00IjppLnVuaWZvcm1zW29dLnZhbHVlPW5ldyBNZSgpLmZyb21BcnJheShhLnZhbHVlKTticmVhaztkZWZhdWx0OmkudW5pZm9ybXNbb10udmFsdWU9YS52YWx1ZX19aWYodC5kZWZpbmVzIT09dm9pZCAwJiYoaS5kZWZpbmVzPXQuZGVmaW5lcyksdC52ZXJ0ZXhTaGFkZXIhPT12b2lkIDAmJihpLnZlcnRleFNoYWRlcj10LnZlcnRleFNoYWRlciksdC5mcmFnbWVudFNoYWRlciE9PXZvaWQgMCYmKGkuZnJhZ21lbnRTaGFkZXI9dC5mcmFnbWVudFNoYWRlciksdC5leHRlbnNpb25zIT09dm9pZCAwKWZvcihsZXQgbyBpbiB0LmV4dGVuc2lvbnMpaS5leHRlbnNpb25zW29dPXQuZXh0ZW5zaW9uc1tvXTtpZih0LnNoYWRpbmchPT12b2lkIDAmJihpLmZsYXRTaGFkaW5nPXQuc2hhZGluZz09PTEpLHQuc2l6ZSE9PXZvaWQgMCYmKGkuc2l6ZT10LnNpemUpLHQuc2l6ZUF0dGVudWF0aW9uIT09dm9pZCAwJiYoaS5zaXplQXR0ZW51YXRpb249dC5zaXplQXR0ZW51YXRpb24pLHQubWFwIT09dm9pZCAwJiYoaS5tYXA9bih0Lm1hcCkpLHQubWF0Y2FwIT09dm9pZCAwJiYoaS5tYXRjYXA9bih0Lm1hdGNhcCkpLHQuYWxwaGFNYXAhPT12b2lkIDAmJihpLmFscGhhTWFwPW4odC5hbHBoYU1hcCkpLHQuYnVtcE1hcCE9PXZvaWQgMCYmKGkuYnVtcE1hcD1uKHQuYnVtcE1hcCkpLHQuYnVtcFNjYWxlIT09dm9pZCAwJiYoaS5idW1wU2NhbGU9dC5idW1wU2NhbGUpLHQubm9ybWFsTWFwIT09dm9pZCAwJiYoaS5ub3JtYWxNYXA9bih0Lm5vcm1hbE1hcCkpLHQubm9ybWFsTWFwVHlwZSE9PXZvaWQgMCYmKGkubm9ybWFsTWFwVHlwZT10Lm5vcm1hbE1hcFR5cGUpLHQubm9ybWFsU2NhbGUhPT12b2lkIDApe2xldCBvPXQubm9ybWFsU2NhbGU7QXJyYXkuaXNBcnJheShvKT09PSExJiYobz1bbyxvXSksaS5ub3JtYWxTY2FsZT1uZXcgTHQoKS5mcm9tQXJyYXkobyl9cmV0dXJuIHQuZGlzcGxhY2VtZW50TWFwIT09dm9pZCAwJiYoaS5kaXNwbGFjZW1lbnRNYXA9bih0LmRpc3BsYWNlbWVudE1hcCkpLHQuZGlzcGxhY2VtZW50U2NhbGUhPT12b2lkIDAmJihpLmRpc3BsYWNlbWVudFNjYWxlPXQuZGlzcGxhY2VtZW50U2NhbGUpLHQuZGlzcGxhY2VtZW50QmlhcyE9PXZvaWQgMCYmKGkuZGlzcGxhY2VtZW50Qmlhcz10LmRpc3BsYWNlbWVudEJpYXMpLHQucm91Z2huZXNzTWFwIT09dm9pZCAwJiYoaS5yb3VnaG5lc3NNYXA9bih0LnJvdWdobmVzc01hcCkpLHQubWV0YWxuZXNzTWFwIT09dm9pZCAwJiYoaS5tZXRhbG5lc3NNYXA9bih0Lm1ldGFsbmVzc01hcCkpLHQuZW1pc3NpdmVNYXAhPT12b2lkIDAmJihpLmVtaXNzaXZlTWFwPW4odC5lbWlzc2l2ZU1hcCkpLHQuZW1pc3NpdmVJbnRlbnNpdHkhPT12b2lkIDAmJihpLmVtaXNzaXZlSW50ZW5zaXR5PXQuZW1pc3NpdmVJbnRlbnNpdHkpLHQuc3BlY3VsYXJNYXAhPT12b2lkIDAmJihpLnNwZWN1bGFyTWFwPW4odC5zcGVjdWxhck1hcCkpLHQuc3BlY3VsYXJJbnRlbnNpdHlNYXAhPT12b2lkIDAmJihpLnNwZWN1bGFySW50ZW5zaXR5TWFwPW4odC5zcGVjdWxhckludGVuc2l0eU1hcCkpLHQuc3BlY3VsYXJDb2xvck1hcCE9PXZvaWQgMCYmKGkuc3BlY3VsYXJDb2xvck1hcD1uKHQuc3BlY3VsYXJDb2xvck1hcCkpLHQuZW52TWFwIT09dm9pZCAwJiYoaS5lbnZNYXA9bih0LmVudk1hcCkpLHQuZW52TWFwSW50ZW5zaXR5IT09dm9pZCAwJiYoaS5lbnZNYXBJbnRlbnNpdHk9dC5lbnZNYXBJbnRlbnNpdHkpLHQucmVmbGVjdGl2aXR5IT09dm9pZCAwJiYoaS5yZWZsZWN0aXZpdHk9dC5yZWZsZWN0aXZpdHkpLHQucmVmcmFjdGlvblJhdGlvIT09dm9pZCAwJiYoaS5yZWZyYWN0aW9uUmF0aW89dC5yZWZyYWN0aW9uUmF0aW8pLHQubGlnaHRNYXAhPT12b2lkIDAmJihpLmxpZ2h0TWFwPW4odC5saWdodE1hcCkpLHQubGlnaHRNYXBJbnRlbnNpdHkhPT12b2lkIDAmJihpLmxpZ2h0TWFwSW50ZW5zaXR5PXQubGlnaHRNYXBJbnRlbnNpdHkpLHQuYW9NYXAhPT12b2lkIDAmJihpLmFvTWFwPW4odC5hb01hcCkpLHQuYW9NYXBJbnRlbnNpdHkhPT12b2lkIDAmJihpLmFvTWFwSW50ZW5zaXR5PXQuYW9NYXBJbnRlbnNpdHkpLHQuZ3JhZGllbnRNYXAhPT12b2lkIDAmJihpLmdyYWRpZW50TWFwPW4odC5ncmFkaWVudE1hcCkpLHQuY2xlYXJjb2F0TWFwIT09dm9pZCAwJiYoaS5jbGVhcmNvYXRNYXA9bih0LmNsZWFyY29hdE1hcCkpLHQuY2xlYXJjb2F0Um91Z2huZXNzTWFwIT09dm9pZCAwJiYoaS5jbGVhcmNvYXRSb3VnaG5lc3NNYXA9bih0LmNsZWFyY29hdFJvdWdobmVzc01hcCkpLHQuY2xlYXJjb2F0Tm9ybWFsTWFwIT09dm9pZCAwJiYoaS5jbGVhcmNvYXROb3JtYWxNYXA9bih0LmNsZWFyY29hdE5vcm1hbE1hcCkpLHQuY2xlYXJjb2F0Tm9ybWFsU2NhbGUhPT12b2lkIDAmJihpLmNsZWFyY29hdE5vcm1hbFNjYWxlPW5ldyBMdCgpLmZyb21BcnJheSh0LmNsZWFyY29hdE5vcm1hbFNjYWxlKSksdC50cmFuc21pc3Npb25NYXAhPT12b2lkIDAmJihpLnRyYW5zbWlzc2lvbk1hcD1uKHQudHJhbnNtaXNzaW9uTWFwKSksdC50aGlja25lc3NNYXAhPT12b2lkIDAmJihpLnRoaWNrbmVzc01hcD1uKHQudGhpY2tuZXNzTWFwKSksdC5zaGVlbkNvbG9yTWFwIT09dm9pZCAwJiYoaS5zaGVlbkNvbG9yTWFwPW4odC5zaGVlbkNvbG9yTWFwKSksdC5zaGVlblJvdWdobmVzc01hcCE9PXZvaWQgMCYmKGkuc2hlZW5Sb3VnaG5lc3NNYXA9bih0LnNoZWVuUm91Z2huZXNzTWFwKSksaX1zZXRUZXh0dXJlcyh0KXtyZXR1cm4gdGhpcy50ZXh0dXJlcz10LHRoaXN9fSxkTT1jbGFzc3tzdGF0aWMgZGVjb2RlVGV4dCh0KXtpZih0eXBlb2YgVGV4dERlY29kZXIhPSJ1bmRlZmluZWQiKXJldHVybiBuZXcgVGV4dERlY29kZXIoKS5kZWNvZGUodCk7bGV0IHI9IiI7Zm9yKGxldCBuPTAsaT10Lmxlbmd0aDtuPGk7bisrKXIrPVN0cmluZy5mcm9tQ2hhckNvZGUodFtuXSk7dHJ5e3JldHVybiBkZWNvZGVVUklDb21wb25lbnQoZXNjYXBlKHIpKX1jYXRjaChuKXtyZXR1cm4gcn19c3RhdGljIGV4dHJhY3RVcmxCYXNlKHQpe2xldCByPXQubGFzdEluZGV4T2YoIi8iKTtyZXR1cm4gcj09PS0xPyIuLyI6dC5zdWJzdHIoMCxyKzEpfXN0YXRpYyByZXNvbHZlVVJMKHQscil7cmV0dXJuIHR5cGVvZiB0IT0ic3RyaW5nInx8dD09PSIiPyIiOigvXmh0dHBzPzpcL1wvL2kudGVzdChyKSYmL15cLy8udGVzdCh0KSYmKHI9ci5yZXBsYWNlKC8oXmh0dHBzPzpcL1wvW15cL10rKS4qL2ksIiQxIikpLC9eKGh0dHBzPzopP1wvXC8vaS50ZXN0KHQpfHwvXmRhdGE6LiosLiokL2kudGVzdCh0KXx8L15ibG9iOi4qJC9pLnRlc3QodCk/dDpyK3QpfX0sUjY9Y2xhc3MgZXh0ZW5kcyBQZXtjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy50eXBlPSJJbnN0YW5jZWRCdWZmZXJHZW9tZXRyeSIsdGhpcy5pbnN0YW5jZUNvdW50PTEvMH1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuaW5zdGFuY2VDb3VudD10Lmluc3RhbmNlQ291bnQsdGhpc31jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyl9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKHRoaXMpO3JldHVybiB0Lmluc3RhbmNlQ291bnQ9dGhpcy5pbnN0YW5jZUNvdW50LHQuaXNJbnN0YW5jZWRCdWZmZXJHZW9tZXRyeT0hMCx0fX07UjYucHJvdG90eXBlLmlzSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnk9ITA7dmFyIGtVPWNsYXNzIGV4dGVuZHMgZWF7Y29uc3RydWN0b3IodCl7c3VwZXIodCl9bG9hZCh0LHIsbixpKXtsZXQgbz10aGlzLGE9bmV3IEpjKG8ubWFuYWdlcik7YS5zZXRQYXRoKG8ucGF0aCksYS5zZXRSZXF1ZXN0SGVhZGVyKG8ucmVxdWVzdEhlYWRlciksYS5zZXRXaXRoQ3JlZGVudGlhbHMoby53aXRoQ3JlZGVudGlhbHMpLGEubG9hZCh0LGZ1bmN0aW9uKHMpe3RyeXtyKG8ucGFyc2UoSlNPTi5wYXJzZShzKSkpfWNhdGNoKGwpe2k/aShsKTpjb25zb2xlLmVycm9yKGwpLG8ubWFuYWdlci5pdGVtRXJyb3IodCl9fSxuLGkpfXBhcnNlKHQpe2xldCByPXt9LG49e307ZnVuY3Rpb24gaShwLGQpe2lmKHJbZF0hPT12b2lkIDApcmV0dXJuIHJbZF07bGV0IF89cC5pbnRlcmxlYXZlZEJ1ZmZlcnNbZF0seT1vKHAsXy5idWZmZXIpLHg9QjMoXy50eXBlLHkpLGI9bmV3IGVtKHgsXy5zdHJpZGUpO3JldHVybiBiLnV1aWQ9Xy51dWlkLHJbZF09YixifWZ1bmN0aW9uIG8ocCxkKXtpZihuW2RdIT09dm9pZCAwKXJldHVybiBuW2RdO2xldCBfPXAuYXJyYXlCdWZmZXJzW2RdLHk9bmV3IFVpbnQzMkFycmF5KF8pLmJ1ZmZlcjtyZXR1cm4gbltkXT15LHl9bGV0IGE9dC5pc0luc3RhbmNlZEJ1ZmZlckdlb21ldHJ5P25ldyBSNjpuZXcgUGUscz10LmRhdGEuaW5kZXg7aWYocyE9PXZvaWQgMCl7bGV0IHA9QjMocy50eXBlLHMuYXJyYXkpO2Euc2V0SW5kZXgobmV3IEplKHAsMSkpfWxldCBsPXQuZGF0YS5hdHRyaWJ1dGVzO2ZvcihsZXQgcCBpbiBsKXtsZXQgZD1sW3BdLGc7aWYoZC5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlKXtsZXQgXz1pKHQuZGF0YSxkLmRhdGEpO2c9bmV3IHRwKF8sZC5pdGVtU2l6ZSxkLm9mZnNldCxkLm5vcm1hbGl6ZWQpfWVsc2V7bGV0IF89QjMoZC50eXBlLGQuYXJyYXkpLHk9ZC5pc0luc3RhbmNlZEJ1ZmZlckF0dHJpYnV0ZT9ybTpKZTtnPW5ldyB5KF8sZC5pdGVtU2l6ZSxkLm5vcm1hbGl6ZWQpfWQubmFtZSE9PXZvaWQgMCYmKGcubmFtZT1kLm5hbWUpLGQudXNhZ2UhPT12b2lkIDAmJmcuc2V0VXNhZ2UoZC51c2FnZSksZC51cGRhdGVSYW5nZSE9PXZvaWQgMCYmKGcudXBkYXRlUmFuZ2Uub2Zmc2V0PWQudXBkYXRlUmFuZ2Uub2Zmc2V0LGcudXBkYXRlUmFuZ2UuY291bnQ9ZC51cGRhdGVSYW5nZS5jb3VudCksYS5zZXRBdHRyaWJ1dGUocCxnKX1sZXQgYz10LmRhdGEubW9ycGhBdHRyaWJ1dGVzO2lmKGMpZm9yKGxldCBwIGluIGMpe2xldCBkPWNbcF0sZz1bXTtmb3IobGV0IF89MCx5PWQubGVuZ3RoO188eTtfKyspe2xldCB4PWRbX10sYjtpZih4LmlzSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGUpe2xldCBTPWkodC5kYXRhLHguZGF0YSk7Yj1uZXcgdHAoUyx4Lml0ZW1TaXplLHgub2Zmc2V0LHgubm9ybWFsaXplZCl9ZWxzZXtsZXQgUz1CMyh4LnR5cGUseC5hcnJheSk7Yj1uZXcgSmUoUyx4Lml0ZW1TaXplLHgubm9ybWFsaXplZCl9eC5uYW1lIT09dm9pZCAwJiYoYi5uYW1lPXgubmFtZSksZy5wdXNoKGIpfWEubW9ycGhBdHRyaWJ1dGVzW3BdPWd9dC5kYXRhLm1vcnBoVGFyZ2V0c1JlbGF0aXZlJiYoYS5tb3JwaFRhcmdldHNSZWxhdGl2ZT0hMCk7bGV0IGg9dC5kYXRhLmdyb3Vwc3x8dC5kYXRhLmRyYXdjYWxsc3x8dC5kYXRhLm9mZnNldHM7aWYoaCE9PXZvaWQgMClmb3IobGV0IHA9MCxkPWgubGVuZ3RoO3AhPT1kOysrcCl7bGV0IGc9aFtwXTthLmFkZEdyb3VwKGcuc3RhcnQsZy5jb3VudCxnLm1hdGVyaWFsSW5kZXgpfWxldCBmPXQuZGF0YS5ib3VuZGluZ1NwaGVyZTtpZihmIT09dm9pZCAwKXtsZXQgcD1uZXcgajtmLmNlbnRlciE9PXZvaWQgMCYmcC5mcm9tQXJyYXkoZi5jZW50ZXIpLGEuYm91bmRpbmdTcGhlcmU9bmV3IFpmKHAsZi5yYWRpdXMpfXJldHVybiB0Lm5hbWUmJihhLm5hbWU9dC5uYW1lKSx0LnVzZXJEYXRhJiYoYS51c2VyRGF0YT10LnVzZXJEYXRhKSxhfX0sbGh0PWNsYXNzIGV4dGVuZHMgZWF7Y29uc3RydWN0b3IodCl7c3VwZXIodCl9bG9hZCh0LHIsbixpKXtsZXQgbz10aGlzLGE9dGhpcy5wYXRoPT09IiI/ZE0uZXh0cmFjdFVybEJhc2UodCk6dGhpcy5wYXRoO3RoaXMucmVzb3VyY2VQYXRoPXRoaXMucmVzb3VyY2VQYXRofHxhO2xldCBzPW5ldyBKYyh0aGlzLm1hbmFnZXIpO3Muc2V0UGF0aCh0aGlzLnBhdGgpLHMuc2V0UmVxdWVzdEhlYWRlcih0aGlzLnJlcXVlc3RIZWFkZXIpLHMuc2V0V2l0aENyZWRlbnRpYWxzKHRoaXMud2l0aENyZWRlbnRpYWxzKSxzLmxvYWQodCxmdW5jdGlvbihsKXtsZXQgYz1udWxsO3RyeXtjPUpTT04ucGFyc2UobCl9Y2F0Y2goaCl7aSE9PXZvaWQgMCYmaShoKSxjb25zb2xlLmVycm9yKCJUSFJFRTpPYmplY3RMb2FkZXI6IENhbid0IHBhcnNlICIrdCsiLiIsaC5tZXNzYWdlKTtyZXR1cm59bGV0IHU9Yy5tZXRhZGF0YTtpZih1PT09dm9pZCAwfHx1LnR5cGU9PT12b2lkIDB8fHUudHlwZS50b0xvd2VyQ2FzZSgpPT09Imdlb21ldHJ5Iil7Y29uc29sZS5lcnJvcigiVEhSRUUuT2JqZWN0TG9hZGVyOiBDYW4ndCBsb2FkICIrdCk7cmV0dXJufW8ucGFyc2UoYyxyKX0sbixpKX1sb2FkQXN5bmModCxyKXtyZXR1cm4gUmkodGhpcyxudWxsLGZ1bmN0aW9uKigpe2xldCBuPXRoaXMsaT10aGlzLnBhdGg9PT0iIj9kTS5leHRyYWN0VXJsQmFzZSh0KTp0aGlzLnBhdGg7dGhpcy5yZXNvdXJjZVBhdGg9dGhpcy5yZXNvdXJjZVBhdGh8fGk7bGV0IG89bmV3IEpjKHRoaXMubWFuYWdlcik7by5zZXRQYXRoKHRoaXMucGF0aCksby5zZXRSZXF1ZXN0SGVhZGVyKHRoaXMucmVxdWVzdEhlYWRlciksby5zZXRXaXRoQ3JlZGVudGlhbHModGhpcy53aXRoQ3JlZGVudGlhbHMpO2xldCBhPXlpZWxkIG8ubG9hZEFzeW5jKHQscikscz1KU09OLnBhcnNlKGEpLGw9cy5tZXRhZGF0YTtpZihsPT09dm9pZCAwfHxsLnR5cGU9PT12b2lkIDB8fGwudHlwZS50b0xvd2VyQ2FzZSgpPT09Imdlb21ldHJ5Iil0aHJvdyBuZXcgRXJyb3IoIlRIUkVFLk9iamVjdExvYWRlcjogQ2FuJ3QgbG9hZCAiK3QpO3JldHVybiB5aWVsZCBuLnBhcnNlQXN5bmMocyl9KX1wYXJzZSh0LHIpe2xldCBuPXRoaXMucGFyc2VBbmltYXRpb25zKHQuYW5pbWF0aW9ucyksaT10aGlzLnBhcnNlU2hhcGVzKHQuc2hhcGVzKSxvPXRoaXMucGFyc2VHZW9tZXRyaWVzKHQuZ2VvbWV0cmllcyxpKSxhPXRoaXMucGFyc2VJbWFnZXModC5pbWFnZXMsZnVuY3Rpb24oKXtyIT09dm9pZCAwJiZyKGMpfSkscz10aGlzLnBhcnNlVGV4dHVyZXModC50ZXh0dXJlcyxhKSxsPXRoaXMucGFyc2VNYXRlcmlhbHModC5tYXRlcmlhbHMscyksYz10aGlzLnBhcnNlT2JqZWN0KHQub2JqZWN0LG8sbCxzLG4pLHU9dGhpcy5wYXJzZVNrZWxldG9ucyh0LnNrZWxldG9ucyxjKTtpZih0aGlzLmJpbmRTa2VsZXRvbnMoYyx1KSxyIT09dm9pZCAwKXtsZXQgaD0hMTtmb3IobGV0IGYgaW4gYSlpZihhW2ZdaW5zdGFuY2VvZiBIVE1MSW1hZ2VFbGVtZW50KXtoPSEwO2JyZWFrfWg9PT0hMSYmcihjKX1yZXR1cm4gY31wYXJzZUFzeW5jKHQpe3JldHVybiBSaSh0aGlzLG51bGwsZnVuY3Rpb24qKCl7bGV0IHI9dGhpcy5wYXJzZUFuaW1hdGlvbnModC5hbmltYXRpb25zKSxuPXRoaXMucGFyc2VTaGFwZXModC5zaGFwZXMpLGk9dGhpcy5wYXJzZUdlb21ldHJpZXModC5nZW9tZXRyaWVzLG4pLG89eWllbGQgdGhpcy5wYXJzZUltYWdlc0FzeW5jKHQuaW1hZ2VzKSxhPXRoaXMucGFyc2VUZXh0dXJlcyh0LnRleHR1cmVzLG8pLHM9dGhpcy5wYXJzZU1hdGVyaWFscyh0Lm1hdGVyaWFscyxhKSxsPXRoaXMucGFyc2VPYmplY3QodC5vYmplY3QsaSxzLGEsciksYz10aGlzLnBhcnNlU2tlbGV0b25zKHQuc2tlbGV0b25zLGwpO3JldHVybiB0aGlzLmJpbmRTa2VsZXRvbnMobCxjKSxsfSl9cGFyc2VTaGFwZXModCl7bGV0IHI9e307aWYodCE9PXZvaWQgMClmb3IobGV0IG49MCxpPXQubGVuZ3RoO248aTtuKyspe2xldCBvPW5ldyBLYygpLmZyb21KU09OKHRbbl0pO3Jbby51dWlkXT1vfXJldHVybiByfXBhcnNlU2tlbGV0b25zKHQscil7bGV0IG49e30saT17fTtpZihyLnRyYXZlcnNlKGZ1bmN0aW9uKG8pe28uaXNCb25lJiYoaVtvLnV1aWRdPW8pfSksdCE9PXZvaWQgMClmb3IobGV0IG89MCxhPXQubGVuZ3RoO288YTtvKyspe2xldCBzPW5ldyBsTSgpLmZyb21KU09OKHRbb10saSk7bltzLnV1aWRdPXN9cmV0dXJuIG59cGFyc2VHZW9tZXRyaWVzKHQscil7bGV0IG49e307aWYodCE9PXZvaWQgMCl7bGV0IGk9bmV3IGtVO2ZvcihsZXQgbz0wLGE9dC5sZW5ndGg7bzxhO28rKyl7bGV0IHMsbD10W29dO3N3aXRjaChsLnR5cGUpe2Nhc2UiQnVmZmVyR2VvbWV0cnkiOmNhc2UiSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnkiOnM9aS5wYXJzZShsKTticmVhaztjYXNlIkdlb21ldHJ5Ijpjb25zb2xlLmVycm9yKCJUSFJFRS5PYmplY3RMb2FkZXI6IFRoZSBsZWdhY3kgR2VvbWV0cnkgdHlwZSBpcyBubyBsb25nZXIgc3VwcG9ydGVkLiIpO2JyZWFrO2RlZmF1bHQ6bC50eXBlIGluIGRoZT9zPWRoZVtsLnR5cGVdLmZyb21KU09OKGwscik6Y29uc29sZS53YXJuKGBUSFJFRS5PYmplY3RMb2FkZXI6IFVuc3VwcG9ydGVkIGdlb21ldHJ5IHR5cGUgIiR7bC50eXBlfSJgKX1zLnV1aWQ9bC51dWlkLGwubmFtZSE9PXZvaWQgMCYmKHMubmFtZT1sLm5hbWUpLHMuaXNCdWZmZXJHZW9tZXRyeT09PSEwJiZsLnVzZXJEYXRhIT09dm9pZCAwJiYocy51c2VyRGF0YT1sLnVzZXJEYXRhKSxuW2wudXVpZF09c319cmV0dXJuIG59cGFyc2VNYXRlcmlhbHModCxyKXtsZXQgbj17fSxpPXt9O2lmKHQhPT12b2lkIDApe2xldCBvPW5ldyBMVTtvLnNldFRleHR1cmVzKHIpO2ZvcihsZXQgYT0wLHM9dC5sZW5ndGg7YTxzO2ErKyl7bGV0IGw9dFthXTtpZihsLnR5cGU9PT0iTXVsdGlNYXRlcmlhbCIpe2xldCBjPVtdO2ZvcihsZXQgdT0wO3U8bC5tYXRlcmlhbHMubGVuZ3RoO3UrKyl7bGV0IGg9bC5tYXRlcmlhbHNbdV07bltoLnV1aWRdPT09dm9pZCAwJiYobltoLnV1aWRdPW8ucGFyc2UoaCkpLGMucHVzaChuW2gudXVpZF0pfWlbbC51dWlkXT1jfWVsc2UgbltsLnV1aWRdPT09dm9pZCAwJiYobltsLnV1aWRdPW8ucGFyc2UobCkpLGlbbC51dWlkXT1uW2wudXVpZF19fXJldHVybiBpfXBhcnNlQW5pbWF0aW9ucyh0KXtsZXQgcj17fTtpZih0IT09dm9pZCAwKWZvcihsZXQgbj0wO248dC5sZW5ndGg7bisrKXtsZXQgaT10W25dLG89UXYucGFyc2UoaSk7cltvLnV1aWRdPW99cmV0dXJuIHJ9cGFyc2VJbWFnZXModCxyKXtsZXQgbj10aGlzLGk9e30sbztmdW5jdGlvbiBhKGwpe3JldHVybiBuLm1hbmFnZXIuaXRlbVN0YXJ0KGwpLG8ubG9hZChsLGZ1bmN0aW9uKCl7bi5tYW5hZ2VyLml0ZW1FbmQobCl9LHZvaWQgMCxmdW5jdGlvbigpe24ubWFuYWdlci5pdGVtRXJyb3IobCksbi5tYW5hZ2VyLml0ZW1FbmQobCl9KX1mdW5jdGlvbiBzKGwpe2lmKHR5cGVvZiBsPT0ic3RyaW5nIil7bGV0IGM9bCx1PS9eKFwvXC8pfChbYS16XSs6KFwvXC8pPykvaS50ZXN0KGMpP2M6bi5yZXNvdXJjZVBhdGgrYztyZXR1cm4gYSh1KX1lbHNlIHJldHVybiBsLmRhdGE/e2RhdGE6QjMobC50eXBlLGwuZGF0YSksd2lkdGg6bC53aWR0aCxoZWlnaHQ6bC5oZWlnaHR9Om51bGx9aWYodCE9PXZvaWQgMCYmdC5sZW5ndGg+MCl7bGV0IGw9bmV3IE02KHIpO289bmV3IGV4KGwpLG8uc2V0Q3Jvc3NPcmlnaW4odGhpcy5jcm9zc09yaWdpbik7Zm9yKGxldCBjPTAsdT10Lmxlbmd0aDtjPHU7YysrKXtsZXQgaD10W2NdLGY9aC51cmw7aWYoQXJyYXkuaXNBcnJheShmKSl7aVtoLnV1aWRdPVtdO2ZvcihsZXQgcD0wLGQ9Zi5sZW5ndGg7cDxkO3ArKyl7bGV0IGc9ZltwXSxfPXMoZyk7XyE9PW51bGwmJihfIGluc3RhbmNlb2YgSFRNTEltYWdlRWxlbWVudD9pW2gudXVpZF0ucHVzaChfKTppW2gudXVpZF0ucHVzaChuZXcgSmQoXy5kYXRhLF8ud2lkdGgsXy5oZWlnaHQpKSl9fWVsc2V7bGV0IHA9cyhoLnVybCk7cCE9PW51bGwmJihpW2gudXVpZF09cCl9fX1yZXR1cm4gaX1wYXJzZUltYWdlc0FzeW5jKHQpe3JldHVybiBSaSh0aGlzLG51bGwsZnVuY3Rpb24qKCl7bGV0IHI9dGhpcyxuPXt9LGk7ZnVuY3Rpb24gbyhhKXtyZXR1cm4gUmkodGhpcyxudWxsLGZ1bmN0aW9uKigpe2lmKHR5cGVvZiBhPT0ic3RyaW5nIil7bGV0IHM9YSxsPS9eKFwvXC8pfChbYS16XSs6KFwvXC8pPykvaS50ZXN0KHMpP3M6ci5yZXNvdXJjZVBhdGgrcztyZXR1cm4geWllbGQgaS5sb2FkQXN5bmMobCl9ZWxzZSByZXR1cm4gYS5kYXRhP3tkYXRhOkIzKGEudHlwZSxhLmRhdGEpLHdpZHRoOmEud2lkdGgsaGVpZ2h0OmEuaGVpZ2h0fTpudWxsfSl9aWYodCE9PXZvaWQgMCYmdC5sZW5ndGg+MCl7aT1uZXcgZXgodGhpcy5tYW5hZ2VyKSxpLnNldENyb3NzT3JpZ2luKHRoaXMuY3Jvc3NPcmlnaW4pO2ZvcihsZXQgYT0wLHM9dC5sZW5ndGg7YTxzO2ErKyl7bGV0IGw9dFthXSxjPWwudXJsO2lmKEFycmF5LmlzQXJyYXkoYykpe25bbC51dWlkXT1bXTtmb3IobGV0IHU9MCxoPWMubGVuZ3RoO3U8aDt1Kyspe2xldCBmPWNbdV0scD15aWVsZCBvKGYpO3AhPT1udWxsJiYocCBpbnN0YW5jZW9mIEhUTUxJbWFnZUVsZW1lbnQ/bltsLnV1aWRdLnB1c2gocCk6bltsLnV1aWRdLnB1c2gobmV3IEpkKHAuZGF0YSxwLndpZHRoLHAuaGVpZ2h0KSkpfX1lbHNle2xldCB1PXlpZWxkIG8obC51cmwpO3UhPT1udWxsJiYobltsLnV1aWRdPXUpfX19cmV0dXJuIG59KX1wYXJzZVRleHR1cmVzKHQscil7ZnVuY3Rpb24gbihvLGEpe3JldHVybiB0eXBlb2Ygbz09Im51bWJlciI/bzooY29uc29sZS53YXJuKCJUSFJFRS5PYmplY3RMb2FkZXIucGFyc2VUZXh0dXJlOiBDb25zdGFudCBzaG91bGQgYmUgaW4gbnVtZXJpYyBmb3JtLiIsbyksYVtvXSl9bGV0IGk9e307aWYodCE9PXZvaWQgMClmb3IobGV0IG89MCxhPXQubGVuZ3RoO288YTtvKyspe2xldCBzPXRbb107cy5pbWFnZT09PXZvaWQgMCYmY29uc29sZS53YXJuKCdUSFJFRS5PYmplY3RMb2FkZXI6IE5vICJpbWFnZSIgc3BlY2lmaWVkIGZvcicscy51dWlkKSxyW3MuaW1hZ2VdPT09dm9pZCAwJiZjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdExvYWRlcjogVW5kZWZpbmVkIGltYWdlIixzLmltYWdlKTtsZXQgbCxjPXJbcy5pbWFnZV07QXJyYXkuaXNBcnJheShjKT8obD1uZXcgSDAoYyksYy5sZW5ndGg9PT02JiYobC5uZWVkc1VwZGF0ZT0hMCkpOihjJiZjLmRhdGE/bD1uZXcgSmQoYy5kYXRhLGMud2lkdGgsYy5oZWlnaHQpOmw9bmV3IHhpKGMpLGMmJihsLm5lZWRzVXBkYXRlPSEwKSksbC51dWlkPXMudXVpZCxzLm5hbWUhPT12b2lkIDAmJihsLm5hbWU9cy5uYW1lKSxzLm1hcHBpbmchPT12b2lkIDAmJihsLm1hcHBpbmc9bihzLm1hcHBpbmcsbDByKSkscy5vZmZzZXQhPT12b2lkIDAmJmwub2Zmc2V0LmZyb21BcnJheShzLm9mZnNldCkscy5yZXBlYXQhPT12b2lkIDAmJmwucmVwZWF0LmZyb21BcnJheShzLnJlcGVhdCkscy5jZW50ZXIhPT12b2lkIDAmJmwuY2VudGVyLmZyb21BcnJheShzLmNlbnRlcikscy5yb3RhdGlvbiE9PXZvaWQgMCYmKGwucm90YXRpb249cy5yb3RhdGlvbikscy53cmFwIT09dm9pZCAwJiYobC53cmFwUz1uKHMud3JhcFswXSx2aGUpLGwud3JhcFQ9bihzLndyYXBbMV0sdmhlKSkscy5mb3JtYXQhPT12b2lkIDAmJihsLmZvcm1hdD1zLmZvcm1hdCkscy50eXBlIT09dm9pZCAwJiYobC50eXBlPXMudHlwZSkscy5lbmNvZGluZyE9PXZvaWQgMCYmKGwuZW5jb2Rpbmc9cy5lbmNvZGluZykscy5taW5GaWx0ZXIhPT12b2lkIDAmJihsLm1pbkZpbHRlcj1uKHMubWluRmlsdGVyLHhoZSkpLHMubWFnRmlsdGVyIT09dm9pZCAwJiYobC5tYWdGaWx0ZXI9bihzLm1hZ0ZpbHRlcix4aGUpKSxzLmFuaXNvdHJvcHkhPT12b2lkIDAmJihsLmFuaXNvdHJvcHk9cy5hbmlzb3Ryb3B5KSxzLmZsaXBZIT09dm9pZCAwJiYobC5mbGlwWT1zLmZsaXBZKSxzLnByZW11bHRpcGx5QWxwaGEhPT12b2lkIDAmJihsLnByZW11bHRpcGx5QWxwaGE9cy5wcmVtdWx0aXBseUFscGhhKSxzLnVucGFja0FsaWdubWVudCE9PXZvaWQgMCYmKGwudW5wYWNrQWxpZ25tZW50PXMudW5wYWNrQWxpZ25tZW50KSxzLnVzZXJEYXRhIT09dm9pZCAwJiYobC51c2VyRGF0YT1zLnVzZXJEYXRhKSxpW3MudXVpZF09bH1yZXR1cm4gaX1wYXJzZU9iamVjdCh0LHIsbixpLG8pe2xldCBhO2Z1bmN0aW9uIHMoZil7cmV0dXJuIHJbZl09PT12b2lkIDAmJmNvbnNvbGUud2FybigiVEhSRUUuT2JqZWN0TG9hZGVyOiBVbmRlZmluZWQgZ2VvbWV0cnkiLGYpLHJbZl19ZnVuY3Rpb24gbChmKXtpZihmIT09dm9pZCAwKXtpZihBcnJheS5pc0FycmF5KGYpKXtsZXQgcD1bXTtmb3IobGV0IGQ9MCxnPWYubGVuZ3RoO2Q8ZztkKyspe2xldCBfPWZbZF07bltfXT09PXZvaWQgMCYmY29uc29sZS53YXJuKCJUSFJFRS5PYmplY3RMb2FkZXI6IFVuZGVmaW5lZCBtYXRlcmlhbCIsXykscC5wdXNoKG5bX10pfXJldHVybiBwfXJldHVybiBuW2ZdPT09dm9pZCAwJiZjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdExvYWRlcjogVW5kZWZpbmVkIG1hdGVyaWFsIixmKSxuW2ZdfX1mdW5jdGlvbiBjKGYpe3JldHVybiBpW2ZdPT09dm9pZCAwJiZjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdExvYWRlcjogVW5kZWZpbmVkIHRleHR1cmUiLGYpLGlbZl19bGV0IHUsaDtzd2l0Y2godC50eXBlKXtjYXNlIlNjZW5lIjphPW5ldyBxMCx0LmJhY2tncm91bmQhPT12b2lkIDAmJihOdW1iZXIuaXNJbnRlZ2VyKHQuYmFja2dyb3VuZCk/YS5iYWNrZ3JvdW5kPW5ldyBuZSh0LmJhY2tncm91bmQpOmEuYmFja2dyb3VuZD1jKHQuYmFja2dyb3VuZCkpLHQuZW52aXJvbm1lbnQhPT12b2lkIDAmJihhLmVudmlyb25tZW50PWModC5lbnZpcm9ubWVudCkpLHQuZm9nIT09dm9pZCAwJiYodC5mb2cudHlwZT09PSJGb2ciP2EuZm9nPW5ldyB6dih0LmZvZy5jb2xvcix0LmZvZy5uZWFyLHQuZm9nLmZhcik6dC5mb2cudHlwZT09PSJGb2dFeHAyIiYmKGEuZm9nPW5ldyBPdih0LmZvZy5jb2xvcix0LmZvZy5kZW5zaXR5KSkpO2JyZWFrO2Nhc2UiUGVyc3BlY3RpdmVDYW1lcmEiOmE9bmV3IFVpKHQuZm92LHQuYXNwZWN0LHQubmVhcix0LmZhciksdC5mb2N1cyE9PXZvaWQgMCYmKGEuZm9jdXM9dC5mb2N1cyksdC56b29tIT09dm9pZCAwJiYoYS56b29tPXQuem9vbSksdC5maWxtR2F1Z2UhPT12b2lkIDAmJihhLmZpbG1HYXVnZT10LmZpbG1HYXVnZSksdC5maWxtT2Zmc2V0IT09dm9pZCAwJiYoYS5maWxtT2Zmc2V0PXQuZmlsbU9mZnNldCksdC52aWV3IT09dm9pZCAwJiYoYS52aWV3PU9iamVjdC5hc3NpZ24oe30sdC52aWV3KSk7YnJlYWs7Y2FzZSJPcnRob2dyYXBoaWNDYW1lcmEiOmE9bmV3IER2KHQubGVmdCx0LnJpZ2h0LHQudG9wLHQuYm90dG9tLHQubmVhcix0LmZhciksdC56b29tIT09dm9pZCAwJiYoYS56b29tPXQuem9vbSksdC52aWV3IT09dm9pZCAwJiYoYS52aWV3PU9iamVjdC5hc3NpZ24oe30sdC52aWV3KSk7YnJlYWs7Y2FzZSJBbWJpZW50TGlnaHQiOmE9bmV3IEk2KHQuY29sb3IsdC5pbnRlbnNpdHkpO2JyZWFrO2Nhc2UiRGlyZWN0aW9uYWxMaWdodCI6YT1uZXcgUDYodC5jb2xvcix0LmludGVuc2l0eSk7YnJlYWs7Y2FzZSJQb2ludExpZ2h0IjphPW5ldyBBNih0LmNvbG9yLHQuaW50ZW5zaXR5LHQuZGlzdGFuY2UsdC5kZWNheSk7YnJlYWs7Y2FzZSJSZWN0QXJlYUxpZ2h0IjphPW5ldyBMNih0LmNvbG9yLHQuaW50ZW5zaXR5LHQud2lkdGgsdC5oZWlnaHQpO2JyZWFrO2Nhc2UiU3BvdExpZ2h0IjphPW5ldyBDNih0LmNvbG9yLHQuaW50ZW5zaXR5LHQuZGlzdGFuY2UsdC5hbmdsZSx0LnBlbnVtYnJhLHQuZGVjYXkpO2JyZWFrO2Nhc2UiSGVtaXNwaGVyZUxpZ2h0IjphPW5ldyBFNih0LmNvbG9yLHQuZ3JvdW5kQ29sb3IsdC5pbnRlbnNpdHkpO2JyZWFrO2Nhc2UiTGlnaHRQcm9iZSI6YT1uZXcgcngoKS5mcm9tSlNPTih0KTticmVhaztjYXNlIlNraW5uZWRNZXNoIjp1PXModC5nZW9tZXRyeSksaD1sKHQubWF0ZXJpYWwpLGE9bmV3IGFNKHUsaCksdC5iaW5kTW9kZSE9PXZvaWQgMCYmKGEuYmluZE1vZGU9dC5iaW5kTW9kZSksdC5iaW5kTWF0cml4IT09dm9pZCAwJiZhLmJpbmRNYXRyaXguZnJvbUFycmF5KHQuYmluZE1hdHJpeCksdC5za2VsZXRvbiE9PXZvaWQgMCYmKGEuc2tlbGV0b249dC5za2VsZXRvbik7YnJlYWs7Y2FzZSJNZXNoIjp1PXModC5nZW9tZXRyeSksaD1sKHQubWF0ZXJpYWwpLGE9bmV3IGVpKHUsaCk7YnJlYWs7Y2FzZSJJbnN0YW5jZWRNZXNoIjp1PXModC5nZW9tZXRyeSksaD1sKHQubWF0ZXJpYWwpO2xldCBmPXQuY291bnQscD10Lmluc3RhbmNlTWF0cml4LGQ9dC5pbnN0YW5jZUNvbG9yO2E9bmV3IG42KHUsaCxmKSxhLmluc3RhbmNlTWF0cml4PW5ldyBybShuZXcgRmxvYXQzMkFycmF5KHAuYXJyYXkpLDE2KSxkIT09dm9pZCAwJiYoYS5pbnN0YW5jZUNvbG9yPW5ldyBybShuZXcgRmxvYXQzMkFycmF5KGQuYXJyYXkpLGQuaXRlbVNpemUpKTticmVhaztjYXNlIkxPRCI6YT1uZXcgZ1U7YnJlYWs7Y2FzZSJMaW5lIjphPW5ldyBjaChzKHQuZ2VvbWV0cnkpLGwodC5tYXRlcmlhbCkpO2JyZWFrO2Nhc2UiTGluZUxvb3AiOmE9bmV3IGk2KHModC5nZW9tZXRyeSksbCh0Lm1hdGVyaWFsKSk7YnJlYWs7Y2FzZSJMaW5lU2VnbWVudHMiOmE9bmV3IEFhKHModC5nZW9tZXRyeSksbCh0Lm1hdGVyaWFsKSk7YnJlYWs7Y2FzZSJQb2ludENsb3VkIjpjYXNlIlBvaW50cyI6YT1uZXcgaW0ocyh0Lmdlb21ldHJ5KSxsKHQubWF0ZXJpYWwpKTticmVhaztjYXNlIlNwcml0ZSI6YT1uZXcgb00obCh0Lm1hdGVyaWFsKSk7YnJlYWs7Y2FzZSJHcm91cCI6YT1uZXcgWGQ7YnJlYWs7Y2FzZSJCb25lIjphPW5ldyBzTTticmVhaztkZWZhdWx0OmE9bmV3IG9yfWlmKGEudXVpZD10LnV1aWQsdC5uYW1lIT09dm9pZCAwJiYoYS5uYW1lPXQubmFtZSksdC5tYXRyaXghPT12b2lkIDA/KGEubWF0cml4LmZyb21BcnJheSh0Lm1hdHJpeCksdC5tYXRyaXhBdXRvVXBkYXRlIT09dm9pZCAwJiYoYS5tYXRyaXhBdXRvVXBkYXRlPXQubWF0cml4QXV0b1VwZGF0ZSksYS5tYXRyaXhBdXRvVXBkYXRlJiZhLm1hdHJpeC5kZWNvbXBvc2UoYS5wb3NpdGlvbixhLnF1YXRlcm5pb24sYS5zY2FsZSkpOih0LnBvc2l0aW9uIT09dm9pZCAwJiZhLnBvc2l0aW9uLmZyb21BcnJheSh0LnBvc2l0aW9uKSx0LnJvdGF0aW9uIT09dm9pZCAwJiZhLnJvdGF0aW9uLmZyb21BcnJheSh0LnJvdGF0aW9uKSx0LnF1YXRlcm5pb24hPT12b2lkIDAmJmEucXVhdGVybmlvbi5mcm9tQXJyYXkodC5xdWF0ZXJuaW9uKSx0LnNjYWxlIT09dm9pZCAwJiZhLnNjYWxlLmZyb21BcnJheSh0LnNjYWxlKSksdC5jYXN0U2hhZG93IT09dm9pZCAwJiYoYS5jYXN0U2hhZG93PXQuY2FzdFNoYWRvdyksdC5yZWNlaXZlU2hhZG93IT09dm9pZCAwJiYoYS5yZWNlaXZlU2hhZG93PXQucmVjZWl2ZVNoYWRvdyksdC5zaGFkb3cmJih0LnNoYWRvdy5iaWFzIT09dm9pZCAwJiYoYS5zaGFkb3cuYmlhcz10LnNoYWRvdy5iaWFzKSx0LnNoYWRvdy5ub3JtYWxCaWFzIT09dm9pZCAwJiYoYS5zaGFkb3cubm9ybWFsQmlhcz10LnNoYWRvdy5ub3JtYWxCaWFzKSx0LnNoYWRvdy5yYWRpdXMhPT12b2lkIDAmJihhLnNoYWRvdy5yYWRpdXM9dC5zaGFkb3cucmFkaXVzKSx0LnNoYWRvdy5tYXBTaXplIT09dm9pZCAwJiZhLnNoYWRvdy5tYXBTaXplLmZyb21BcnJheSh0LnNoYWRvdy5tYXBTaXplKSx0LnNoYWRvdy5jYW1lcmEhPT12b2lkIDAmJihhLnNoYWRvdy5jYW1lcmE9dGhpcy5wYXJzZU9iamVjdCh0LnNoYWRvdy5jYW1lcmEpKSksdC52aXNpYmxlIT09dm9pZCAwJiYoYS52aXNpYmxlPXQudmlzaWJsZSksdC5mcnVzdHVtQ3VsbGVkIT09dm9pZCAwJiYoYS5mcnVzdHVtQ3VsbGVkPXQuZnJ1c3R1bUN1bGxlZCksdC5yZW5kZXJPcmRlciE9PXZvaWQgMCYmKGEucmVuZGVyT3JkZXI9dC5yZW5kZXJPcmRlciksdC51c2VyRGF0YSE9PXZvaWQgMCYmKGEudXNlckRhdGE9dC51c2VyRGF0YSksdC5sYXllcnMhPT12b2lkIDAmJihhLmxheWVycy5tYXNrPXQubGF5ZXJzKSx0LmNoaWxkcmVuIT09dm9pZCAwKXtsZXQgZj10LmNoaWxkcmVuO2ZvcihsZXQgcD0wO3A8Zi5sZW5ndGg7cCsrKWEuYWRkKHRoaXMucGFyc2VPYmplY3QoZltwXSxyLG4saSxvKSl9aWYodC5hbmltYXRpb25zIT09dm9pZCAwKXtsZXQgZj10LmFuaW1hdGlvbnM7Zm9yKGxldCBwPTA7cDxmLmxlbmd0aDtwKyspe2xldCBkPWZbcF07YS5hbmltYXRpb25zLnB1c2gob1tkXSl9fWlmKHQudHlwZT09PSJMT0QiKXt0LmF1dG9VcGRhdGUhPT12b2lkIDAmJihhLmF1dG9VcGRhdGU9dC5hdXRvVXBkYXRlKTtsZXQgZj10LmxldmVscztmb3IobGV0IHA9MDtwPGYubGVuZ3RoO3ArKyl7bGV0IGQ9ZltwXSxnPWEuZ2V0T2JqZWN0QnlQcm9wZXJ0eSgidXVpZCIsZC5vYmplY3QpO2chPT12b2lkIDAmJmEuYWRkTGV2ZWwoZyxkLmRpc3RhbmNlKX19cmV0dXJuIGF9YmluZFNrZWxldG9ucyh0LHIpe09iamVjdC5rZXlzKHIpLmxlbmd0aCE9PTAmJnQudHJhdmVyc2UoZnVuY3Rpb24obil7aWYobi5pc1NraW5uZWRNZXNoPT09ITAmJm4uc2tlbGV0b24hPT12b2lkIDApe2xldCBpPXJbbi5za2VsZXRvbl07aT09PXZvaWQgMD9jb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdExvYWRlcjogTm8gc2tlbGV0b24gZm91bmQgd2l0aCBVVUlEOiIsbi5za2VsZXRvbik6bi5iaW5kKGksbi5iaW5kTWF0cml4KX19KX1zZXRUZXh0dXJlUGF0aCh0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5PYmplY3RMb2FkZXI6IC5zZXRUZXh0dXJlUGF0aCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLnNldFJlc291cmNlUGF0aCgpLiIpLHRoaXMuc2V0UmVzb3VyY2VQYXRoKHQpfX0sbDByPXtVVk1hcHBpbmc6alUsQ3ViZVJlZmxlY3Rpb25NYXBwaW5nOm54LEN1YmVSZWZyYWN0aW9uTWFwcGluZzppeCxFcXVpcmVjdGFuZ3VsYXJSZWZsZWN0aW9uTWFwcGluZzpXUCxFcXVpcmVjdGFuZ3VsYXJSZWZyYWN0aW9uTWFwcGluZzpZUCxDdWJlVVZSZWZsZWN0aW9uTWFwcGluZzp4TSxDdWJlVVZSZWZyYWN0aW9uTWFwcGluZzpPNn0sdmhlPXtSZXBlYXRXcmFwcGluZzpqUCxDbGFtcFRvRWRnZVdyYXBwaW5nOkpvLE1pcnJvcmVkUmVwZWF0V3JhcHBpbmc6WFB9LHhoZT17TmVhcmVzdEZpbHRlcjpMaSxOZWFyZXN0TWlwbWFwTmVhcmVzdEZpbHRlcjppVSxOZWFyZXN0TWlwbWFwTGluZWFyRmlsdGVyOm9VLExpbmVhckZpbHRlcjpvaSxMaW5lYXJNaXBtYXBOZWFyZXN0RmlsdGVyOmtodCxMaW5lYXJNaXBtYXBMaW5lYXJGaWx0ZXI6b3h9LFJVPWNsYXNzIGV4dGVuZHMgZWF7Y29uc3RydWN0b3IodCl7c3VwZXIodCksdHlwZW9mIGNyZWF0ZUltYWdlQml0bWFwPT0idW5kZWZpbmVkIiYmY29uc29sZS53YXJuKCJUSFJFRS5JbWFnZUJpdG1hcExvYWRlcjogY3JlYXRlSW1hZ2VCaXRtYXAoKSBub3Qgc3VwcG9ydGVkLiIpLHR5cGVvZiBmZXRjaD09InVuZGVmaW5lZCImJmNvbnNvbGUud2FybigiVEhSRUUuSW1hZ2VCaXRtYXBMb2FkZXI6IGZldGNoKCkgbm90IHN1cHBvcnRlZC4iKSx0aGlzLm9wdGlvbnM9e3ByZW11bHRpcGx5QWxwaGE6Im5vbmUifX1zZXRPcHRpb25zKHQpe3JldHVybiB0aGlzLm9wdGlvbnM9dCx0aGlzfWxvYWQodCxyLG4saSl7dD09PXZvaWQgMCYmKHQ9IiIpLHRoaXMucGF0aCE9PXZvaWQgMCYmKHQ9dGhpcy5wYXRoK3QpLHQ9dGhpcy5tYW5hZ2VyLnJlc29sdmVVUkwodCk7bGV0IG89dGhpcyxhPXR4LmdldCh0KTtpZihhIT09dm9pZCAwKXJldHVybiBvLm1hbmFnZXIuaXRlbVN0YXJ0KHQpLHNldFRpbWVvdXQoZnVuY3Rpb24oKXtyJiZyKGEpLG8ubWFuYWdlci5pdGVtRW5kKHQpfSwwKSxhO2xldCBzPXt9O3MuY3JlZGVudGlhbHM9dGhpcy5jcm9zc09yaWdpbj09PSJhbm9ueW1vdXMiPyJzYW1lLW9yaWdpbiI6ImluY2x1ZGUiLHMuaGVhZGVycz10aGlzLnJlcXVlc3RIZWFkZXIsZmV0Y2godCxzKS50aGVuKGZ1bmN0aW9uKGwpe3JldHVybiBsLmJsb2IoKX0pLnRoZW4oZnVuY3Rpb24obCl7cmV0dXJuIGNyZWF0ZUltYWdlQml0bWFwKGwsT2JqZWN0LmFzc2lnbihvLm9wdGlvbnMse2NvbG9yU3BhY2VDb252ZXJzaW9uOiJub25lIn0pKX0pLnRoZW4oZnVuY3Rpb24obCl7dHguYWRkKHQsbCksciYmcihsKSxvLm1hbmFnZXIuaXRlbUVuZCh0KX0pLmNhdGNoKGZ1bmN0aW9uKGwpe2kmJmkobCksby5tYW5hZ2VyLml0ZW1FcnJvcih0KSxvLm1hbmFnZXIuaXRlbUVuZCh0KX0pLG8ubWFuYWdlci5pdGVtU3RhcnQodCl9fTtSVS5wcm90b3R5cGUuaXNJbWFnZUJpdG1hcExvYWRlcj0hMDt2YXIgR1YsRmh0PXtnZXRDb250ZXh0OmZ1bmN0aW9uKCl7cmV0dXJuIEdWPT09dm9pZCAwJiYoR1Y9bmV3KHdpbmRvdy5BdWRpb0NvbnRleHR8fHdpbmRvdy53ZWJraXRBdWRpb0NvbnRleHQpKSxHVn0sc2V0Q29udGV4dDpmdW5jdGlvbihlKXtHVj1lfX0sTlU9Y2xhc3MgZXh0ZW5kcyBlYXtjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KX1sb2FkKHQscixuLGkpe2xldCBvPXRoaXMsYT1uZXcgSmModGhpcy5tYW5hZ2VyKTthLnNldFJlc3BvbnNlVHlwZSgiYXJyYXlidWZmZXIiKSxhLnNldFBhdGgodGhpcy5wYXRoKSxhLnNldFJlcXVlc3RIZWFkZXIodGhpcy5yZXF1ZXN0SGVhZGVyKSxhLnNldFdpdGhDcmVkZW50aWFscyh0aGlzLndpdGhDcmVkZW50aWFscyksYS5sb2FkKHQsZnVuY3Rpb24ocyl7dHJ5e2xldCBsPXMuc2xpY2UoMCk7Rmh0LmdldENvbnRleHQoKS5kZWNvZGVBdWRpb0RhdGEobCxmdW5jdGlvbih1KXtyKHUpfSl9Y2F0Y2gobCl7aT9pKGwpOmNvbnNvbGUuZXJyb3IobCksby5tYW5hZ2VyLml0ZW1FcnJvcih0KX19LG4saSl9fSxEVT1jbGFzcyBleHRlbmRzIHJ4e2NvbnN0cnVjdG9yKHQscixuPTEpe3N1cGVyKHZvaWQgMCxuKTtsZXQgaT1uZXcgbmUoKS5zZXQodCksbz1uZXcgbmUoKS5zZXQociksYT1uZXcgaihpLnIsaS5nLGkuYikscz1uZXcgaihvLnIsby5nLG8uYiksbD1NYXRoLnNxcnQoTWF0aC5QSSksYz1sKk1hdGguc3FydCguNzUpO3RoaXMuc2guY29lZmZpY2llbnRzWzBdLmNvcHkoYSkuYWRkKHMpLm11bHRpcGx5U2NhbGFyKGwpLHRoaXMuc2guY29lZmZpY2llbnRzWzFdLmNvcHkoYSkuc3ViKHMpLm11bHRpcGx5U2NhbGFyKGMpfX07RFUucHJvdG90eXBlLmlzSGVtaXNwaGVyZUxpZ2h0UHJvYmU9ITA7dmFyIE9VPWNsYXNzIGV4dGVuZHMgcnh7Y29uc3RydWN0b3IodCxyPTEpe3N1cGVyKHZvaWQgMCxyKTtsZXQgbj1uZXcgbmUoKS5zZXQodCk7dGhpcy5zaC5jb2VmZmljaWVudHNbMF0uc2V0KG4ucixuLmcsbi5iKS5tdWx0aXBseVNjYWxhcigyKk1hdGguc3FydChNYXRoLlBJKSl9fTtPVS5wcm90b3R5cGUuaXNBbWJpZW50TGlnaHRQcm9iZT0hMDt2YXIgYmhlPW5ldyBNZSx3aGU9bmV3IE1lLHl2PW5ldyBNZSxjaHQ9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnR5cGU9IlN0ZXJlb0NhbWVyYSIsdGhpcy5hc3BlY3Q9MSx0aGlzLmV5ZVNlcD0uMDY0LHRoaXMuY2FtZXJhTD1uZXcgVWksdGhpcy5jYW1lcmFMLmxheWVycy5lbmFibGUoMSksdGhpcy5jYW1lcmFMLm1hdHJpeEF1dG9VcGRhdGU9ITEsdGhpcy5jYW1lcmFSPW5ldyBVaSx0aGlzLmNhbWVyYVIubGF5ZXJzLmVuYWJsZSgyKSx0aGlzLmNhbWVyYVIubWF0cml4QXV0b1VwZGF0ZT0hMSx0aGlzLl9jYWNoZT17Zm9jdXM6bnVsbCxmb3Y6bnVsbCxhc3BlY3Q6bnVsbCxuZWFyOm51bGwsZmFyOm51bGwsem9vbTpudWxsLGV5ZVNlcDpudWxsfX11cGRhdGUodCl7bGV0IHI9dGhpcy5fY2FjaGU7aWYoci5mb2N1cyE9PXQuZm9jdXN8fHIuZm92IT09dC5mb3Z8fHIuYXNwZWN0IT09dC5hc3BlY3QqdGhpcy5hc3BlY3R8fHIubmVhciE9PXQubmVhcnx8ci5mYXIhPT10LmZhcnx8ci56b29tIT09dC56b29tfHxyLmV5ZVNlcCE9PXRoaXMuZXllU2VwKXtyLmZvY3VzPXQuZm9jdXMsci5mb3Y9dC5mb3Ysci5hc3BlY3Q9dC5hc3BlY3QqdGhpcy5hc3BlY3Qsci5uZWFyPXQubmVhcixyLmZhcj10LmZhcixyLnpvb209dC56b29tLHIuZXllU2VwPXRoaXMuZXllU2VwLHl2LmNvcHkodC5wcm9qZWN0aW9uTWF0cml4KTtsZXQgaT1yLmV5ZVNlcC8yLG89aSpyLm5lYXIvci5mb2N1cyxhPXIubmVhcipNYXRoLnRhbihQdipyLmZvdiouNSkvci56b29tLHMsbDt3aGUuZWxlbWVudHNbMTJdPS1pLGJoZS5lbGVtZW50c1sxMl09aSxzPS1hKnIuYXNwZWN0K28sbD1hKnIuYXNwZWN0K28seXYuZWxlbWVudHNbMF09MipyLm5lYXIvKGwtcykseXYuZWxlbWVudHNbOF09KGwrcykvKGwtcyksdGhpcy5jYW1lcmFMLnByb2plY3Rpb25NYXRyaXguY29weSh5dikscz0tYSpyLmFzcGVjdC1vLGw9YSpyLmFzcGVjdC1vLHl2LmVsZW1lbnRzWzBdPTIqci5uZWFyLyhsLXMpLHl2LmVsZW1lbnRzWzhdPShsK3MpLyhsLXMpLHRoaXMuY2FtZXJhUi5wcm9qZWN0aW9uTWF0cml4LmNvcHkoeXYpfXRoaXMuY2FtZXJhTC5tYXRyaXhXb3JsZC5jb3B5KHQubWF0cml4V29ybGQpLm11bHRpcGx5KHdoZSksdGhpcy5jYW1lcmFSLm1hdHJpeFdvcmxkLmNvcHkodC5tYXRyaXhXb3JsZCkubXVsdGlwbHkoYmhlKX19LG1NPWNsYXNze2NvbnN0cnVjdG9yKHQ9ITApe3RoaXMuYXV0b1N0YXJ0PXQsdGhpcy5zdGFydFRpbWU9MCx0aGlzLm9sZFRpbWU9MCx0aGlzLmVsYXBzZWRUaW1lPTAsdGhpcy5ydW5uaW5nPSExfXN0YXJ0KCl7dGhpcy5zdGFydFRpbWU9U2hlKCksdGhpcy5vbGRUaW1lPXRoaXMuc3RhcnRUaW1lLHRoaXMuZWxhcHNlZFRpbWU9MCx0aGlzLnJ1bm5pbmc9ITB9c3RvcCgpe3RoaXMuZ2V0RWxhcHNlZFRpbWUoKSx0aGlzLnJ1bm5pbmc9ITEsdGhpcy5hdXRvU3RhcnQ9ITF9Z2V0RWxhcHNlZFRpbWUoKXtyZXR1cm4gdGhpcy5nZXREZWx0YSgpLHRoaXMuZWxhcHNlZFRpbWV9Z2V0RGVsdGEoKXtsZXQgdD0wO2lmKHRoaXMuYXV0b1N0YXJ0JiYhdGhpcy5ydW5uaW5nKXJldHVybiB0aGlzLnN0YXJ0KCksMDtpZih0aGlzLnJ1bm5pbmcpe2xldCByPVNoZSgpO3Q9KHItdGhpcy5vbGRUaW1lKS8xZTMsdGhpcy5vbGRUaW1lPXIsdGhpcy5lbGFwc2VkVGltZSs9dH1yZXR1cm4gdH19O2Z1bmN0aW9uIFNoZSgpe3JldHVybih0eXBlb2YgcGVyZm9ybWFuY2U9PSJ1bmRlZmluZWQiP0RhdGU6cGVyZm9ybWFuY2UpLm5vdygpfXZhciB2dj1uZXcgaixNaGU9bmV3IHZpLGMwcj1uZXcgaix4dj1uZXcgaix1aHQ9Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy50eXBlPSJBdWRpb0xpc3RlbmVyIix0aGlzLmNvbnRleHQ9Rmh0LmdldENvbnRleHQoKSx0aGlzLmdhaW49dGhpcy5jb250ZXh0LmNyZWF0ZUdhaW4oKSx0aGlzLmdhaW4uY29ubmVjdCh0aGlzLmNvbnRleHQuZGVzdGluYXRpb24pLHRoaXMuZmlsdGVyPW51bGwsdGhpcy50aW1lRGVsdGE9MCx0aGlzLl9jbG9jaz1uZXcgbU19Z2V0SW5wdXQoKXtyZXR1cm4gdGhpcy5nYWlufXJlbW92ZUZpbHRlcigpe3JldHVybiB0aGlzLmZpbHRlciE9PW51bGwmJih0aGlzLmdhaW4uZGlzY29ubmVjdCh0aGlzLmZpbHRlciksdGhpcy5maWx0ZXIuZGlzY29ubmVjdCh0aGlzLmNvbnRleHQuZGVzdGluYXRpb24pLHRoaXMuZ2Fpbi5jb25uZWN0KHRoaXMuY29udGV4dC5kZXN0aW5hdGlvbiksdGhpcy5maWx0ZXI9bnVsbCksdGhpc31nZXRGaWx0ZXIoKXtyZXR1cm4gdGhpcy5maWx0ZXJ9c2V0RmlsdGVyKHQpe3JldHVybiB0aGlzLmZpbHRlciE9PW51bGw/KHRoaXMuZ2Fpbi5kaXNjb25uZWN0KHRoaXMuZmlsdGVyKSx0aGlzLmZpbHRlci5kaXNjb25uZWN0KHRoaXMuY29udGV4dC5kZXN0aW5hdGlvbikpOnRoaXMuZ2Fpbi5kaXNjb25uZWN0KHRoaXMuY29udGV4dC5kZXN0aW5hdGlvbiksdGhpcy5maWx0ZXI9dCx0aGlzLmdhaW4uY29ubmVjdCh0aGlzLmZpbHRlciksdGhpcy5maWx0ZXIuY29ubmVjdCh0aGlzLmNvbnRleHQuZGVzdGluYXRpb24pLHRoaXN9Z2V0TWFzdGVyVm9sdW1lKCl7cmV0dXJuIHRoaXMuZ2Fpbi5nYWluLnZhbHVlfXNldE1hc3RlclZvbHVtZSh0KXtyZXR1cm4gdGhpcy5nYWluLmdhaW4uc2V0VGFyZ2V0QXRUaW1lKHQsdGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lLC4wMSksdGhpc311cGRhdGVNYXRyaXhXb3JsZCh0KXtzdXBlci51cGRhdGVNYXRyaXhXb3JsZCh0KTtsZXQgcj10aGlzLmNvbnRleHQubGlzdGVuZXIsbj10aGlzLnVwO2lmKHRoaXMudGltZURlbHRhPXRoaXMuX2Nsb2NrLmdldERlbHRhKCksdGhpcy5tYXRyaXhXb3JsZC5kZWNvbXBvc2UodnYsTWhlLGMwcikseHYuc2V0KDAsMCwtMSkuYXBwbHlRdWF0ZXJuaW9uKE1oZSksci5wb3NpdGlvblgpe2xldCBpPXRoaXMuY29udGV4dC5jdXJyZW50VGltZSt0aGlzLnRpbWVEZWx0YTtyLnBvc2l0aW9uWC5saW5lYXJSYW1wVG9WYWx1ZUF0VGltZSh2di54LGkpLHIucG9zaXRpb25ZLmxpbmVhclJhbXBUb1ZhbHVlQXRUaW1lKHZ2LnksaSksci5wb3NpdGlvbloubGluZWFyUmFtcFRvVmFsdWVBdFRpbWUodnYueixpKSxyLmZvcndhcmRYLmxpbmVhclJhbXBUb1ZhbHVlQXRUaW1lKHh2LngsaSksci5mb3J3YXJkWS5saW5lYXJSYW1wVG9WYWx1ZUF0VGltZSh4di55LGkpLHIuZm9yd2FyZFoubGluZWFyUmFtcFRvVmFsdWVBdFRpbWUoeHYueixpKSxyLnVwWC5saW5lYXJSYW1wVG9WYWx1ZUF0VGltZShuLngsaSksci51cFkubGluZWFyUmFtcFRvVmFsdWVBdFRpbWUobi55LGkpLHIudXBaLmxpbmVhclJhbXBUb1ZhbHVlQXRUaW1lKG4ueixpKX1lbHNlIHIuc2V0UG9zaXRpb24odnYueCx2di55LHZ2LnopLHIuc2V0T3JpZW50YXRpb24oeHYueCx4di55LHh2Lnosbi54LG4ueSxuLnopfX0sTjY9Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iQXVkaW8iLHRoaXMubGlzdGVuZXI9dCx0aGlzLmNvbnRleHQ9dC5jb250ZXh0LHRoaXMuZ2Fpbj10aGlzLmNvbnRleHQuY3JlYXRlR2FpbigpLHRoaXMuZ2Fpbi5jb25uZWN0KHQuZ2V0SW5wdXQoKSksdGhpcy5hdXRvcGxheT0hMSx0aGlzLmJ1ZmZlcj1udWxsLHRoaXMuZGV0dW5lPTAsdGhpcy5sb29wPSExLHRoaXMubG9vcFN0YXJ0PTAsdGhpcy5sb29wRW5kPTAsdGhpcy5vZmZzZXQ9MCx0aGlzLmR1cmF0aW9uPXZvaWQgMCx0aGlzLnBsYXliYWNrUmF0ZT0xLHRoaXMuaXNQbGF5aW5nPSExLHRoaXMuaGFzUGxheWJhY2tDb250cm9sPSEwLHRoaXMuc291cmNlPW51bGwsdGhpcy5zb3VyY2VUeXBlPSJlbXB0eSIsdGhpcy5fc3RhcnRlZEF0PTAsdGhpcy5fcHJvZ3Jlc3M9MCx0aGlzLl9jb25uZWN0ZWQ9ITEsdGhpcy5maWx0ZXJzPVtdfWdldE91dHB1dCgpe3JldHVybiB0aGlzLmdhaW59c2V0Tm9kZVNvdXJjZSh0KXtyZXR1cm4gdGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9ITEsdGhpcy5zb3VyY2VUeXBlPSJhdWRpb05vZGUiLHRoaXMuc291cmNlPXQsdGhpcy5jb25uZWN0KCksdGhpc31zZXRNZWRpYUVsZW1lbnRTb3VyY2UodCl7cmV0dXJuIHRoaXMuaGFzUGxheWJhY2tDb250cm9sPSExLHRoaXMuc291cmNlVHlwZT0ibWVkaWFOb2RlIix0aGlzLnNvdXJjZT10aGlzLmNvbnRleHQuY3JlYXRlTWVkaWFFbGVtZW50U291cmNlKHQpLHRoaXMuY29ubmVjdCgpLHRoaXN9c2V0TWVkaWFTdHJlYW1Tb3VyY2UodCl7cmV0dXJuIHRoaXMuaGFzUGxheWJhY2tDb250cm9sPSExLHRoaXMuc291cmNlVHlwZT0ibWVkaWFTdHJlYW1Ob2RlIix0aGlzLnNvdXJjZT10aGlzLmNvbnRleHQuY3JlYXRlTWVkaWFTdHJlYW1Tb3VyY2UodCksdGhpcy5jb25uZWN0KCksdGhpc31zZXRCdWZmZXIodCl7cmV0dXJuIHRoaXMuYnVmZmVyPXQsdGhpcy5zb3VyY2VUeXBlPSJidWZmZXIiLHRoaXMuYXV0b3BsYXkmJnRoaXMucGxheSgpLHRoaXN9cGxheSh0PTApe2lmKHRoaXMuaXNQbGF5aW5nPT09ITApe2NvbnNvbGUud2FybigiVEhSRUUuQXVkaW86IEF1ZGlvIGlzIGFscmVhZHkgcGxheWluZy4iKTtyZXR1cm59aWYodGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9PT0hMSl7Y29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogdGhpcyBBdWRpbyBoYXMgbm8gcGxheWJhY2sgY29udHJvbC4iKTtyZXR1cm59dGhpcy5fc3RhcnRlZEF0PXRoaXMuY29udGV4dC5jdXJyZW50VGltZSt0O2xldCByPXRoaXMuY29udGV4dC5jcmVhdGVCdWZmZXJTb3VyY2UoKTtyZXR1cm4gci5idWZmZXI9dGhpcy5idWZmZXIsci5sb29wPXRoaXMubG9vcCxyLmxvb3BTdGFydD10aGlzLmxvb3BTdGFydCxyLmxvb3BFbmQ9dGhpcy5sb29wRW5kLHIub25lbmRlZD10aGlzLm9uRW5kZWQuYmluZCh0aGlzKSxyLnN0YXJ0KHRoaXMuX3N0YXJ0ZWRBdCx0aGlzLl9wcm9ncmVzcyt0aGlzLm9mZnNldCx0aGlzLmR1cmF0aW9uKSx0aGlzLmlzUGxheWluZz0hMCx0aGlzLnNvdXJjZT1yLHRoaXMuc2V0RGV0dW5lKHRoaXMuZGV0dW5lKSx0aGlzLnNldFBsYXliYWNrUmF0ZSh0aGlzLnBsYXliYWNrUmF0ZSksdGhpcy5jb25uZWN0KCl9cGF1c2UoKXtpZih0aGlzLmhhc1BsYXliYWNrQ29udHJvbD09PSExKXtjb25zb2xlLndhcm4oIlRIUkVFLkF1ZGlvOiB0aGlzIEF1ZGlvIGhhcyBubyBwbGF5YmFjayBjb250cm9sLiIpO3JldHVybn1yZXR1cm4gdGhpcy5pc1BsYXlpbmc9PT0hMCYmKHRoaXMuX3Byb2dyZXNzKz1NYXRoLm1heCh0aGlzLmNvbnRleHQuY3VycmVudFRpbWUtdGhpcy5fc3RhcnRlZEF0LDApKnRoaXMucGxheWJhY2tSYXRlLHRoaXMubG9vcD09PSEwJiYodGhpcy5fcHJvZ3Jlc3M9dGhpcy5fcHJvZ3Jlc3MlKHRoaXMuZHVyYXRpb258fHRoaXMuYnVmZmVyLmR1cmF0aW9uKSksdGhpcy5zb3VyY2Uuc3RvcCgpLHRoaXMuc291cmNlLm9uZW5kZWQ9bnVsbCx0aGlzLmlzUGxheWluZz0hMSksdGhpc31zdG9wKCl7aWYodGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9PT0hMSl7Y29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogdGhpcyBBdWRpbyBoYXMgbm8gcGxheWJhY2sgY29udHJvbC4iKTtyZXR1cm59cmV0dXJuIHRoaXMuX3Byb2dyZXNzPTAsdGhpcy5zb3VyY2Uuc3RvcCgpLHRoaXMuc291cmNlLm9uZW5kZWQ9bnVsbCx0aGlzLmlzUGxheWluZz0hMSx0aGlzfWNvbm5lY3QoKXtpZih0aGlzLmZpbHRlcnMubGVuZ3RoPjApe3RoaXMuc291cmNlLmNvbm5lY3QodGhpcy5maWx0ZXJzWzBdKTtmb3IobGV0IHQ9MSxyPXRoaXMuZmlsdGVycy5sZW5ndGg7dDxyO3QrKyl0aGlzLmZpbHRlcnNbdC0xXS5jb25uZWN0KHRoaXMuZmlsdGVyc1t0XSk7dGhpcy5maWx0ZXJzW3RoaXMuZmlsdGVycy5sZW5ndGgtMV0uY29ubmVjdCh0aGlzLmdldE91dHB1dCgpKX1lbHNlIHRoaXMuc291cmNlLmNvbm5lY3QodGhpcy5nZXRPdXRwdXQoKSk7cmV0dXJuIHRoaXMuX2Nvbm5lY3RlZD0hMCx0aGlzfWRpc2Nvbm5lY3QoKXtpZih0aGlzLmZpbHRlcnMubGVuZ3RoPjApe3RoaXMuc291cmNlLmRpc2Nvbm5lY3QodGhpcy5maWx0ZXJzWzBdKTtmb3IobGV0IHQ9MSxyPXRoaXMuZmlsdGVycy5sZW5ndGg7dDxyO3QrKyl0aGlzLmZpbHRlcnNbdC0xXS5kaXNjb25uZWN0KHRoaXMuZmlsdGVyc1t0XSk7dGhpcy5maWx0ZXJzW3RoaXMuZmlsdGVycy5sZW5ndGgtMV0uZGlzY29ubmVjdCh0aGlzLmdldE91dHB1dCgpKX1lbHNlIHRoaXMuc291cmNlLmRpc2Nvbm5lY3QodGhpcy5nZXRPdXRwdXQoKSk7cmV0dXJuIHRoaXMuX2Nvbm5lY3RlZD0hMSx0aGlzfWdldEZpbHRlcnMoKXtyZXR1cm4gdGhpcy5maWx0ZXJzfXNldEZpbHRlcnModCl7cmV0dXJuIHR8fCh0PVtdKSx0aGlzLl9jb25uZWN0ZWQ9PT0hMD8odGhpcy5kaXNjb25uZWN0KCksdGhpcy5maWx0ZXJzPXQuc2xpY2UoKSx0aGlzLmNvbm5lY3QoKSk6dGhpcy5maWx0ZXJzPXQuc2xpY2UoKSx0aGlzfXNldERldHVuZSh0KXtpZih0aGlzLmRldHVuZT10LHRoaXMuc291cmNlLmRldHVuZSE9PXZvaWQgMClyZXR1cm4gdGhpcy5pc1BsYXlpbmc9PT0hMCYmdGhpcy5zb3VyY2UuZGV0dW5lLnNldFRhcmdldEF0VGltZSh0aGlzLmRldHVuZSx0aGlzLmNvbnRleHQuY3VycmVudFRpbWUsLjAxKSx0aGlzfWdldERldHVuZSgpe3JldHVybiB0aGlzLmRldHVuZX1nZXRGaWx0ZXIoKXtyZXR1cm4gdGhpcy5nZXRGaWx0ZXJzKClbMF19c2V0RmlsdGVyKHQpe3JldHVybiB0aGlzLnNldEZpbHRlcnModD9bdF06W10pfXNldFBsYXliYWNrUmF0ZSh0KXtpZih0aGlzLmhhc1BsYXliYWNrQ29udHJvbD09PSExKXtjb25zb2xlLndhcm4oIlRIUkVFLkF1ZGlvOiB0aGlzIEF1ZGlvIGhhcyBubyBwbGF5YmFjayBjb250cm9sLiIpO3JldHVybn1yZXR1cm4gdGhpcy5wbGF5YmFja1JhdGU9dCx0aGlzLmlzUGxheWluZz09PSEwJiZ0aGlzLnNvdXJjZS5wbGF5YmFja1JhdGUuc2V0VGFyZ2V0QXRUaW1lKHRoaXMucGxheWJhY2tSYXRlLHRoaXMuY29udGV4dC5jdXJyZW50VGltZSwuMDEpLHRoaXN9Z2V0UGxheWJhY2tSYXRlKCl7cmV0dXJuIHRoaXMucGxheWJhY2tSYXRlfW9uRW5kZWQoKXt0aGlzLmlzUGxheWluZz0hMX1nZXRMb29wKCl7cmV0dXJuIHRoaXMuaGFzUGxheWJhY2tDb250cm9sPT09ITE/KGNvbnNvbGUud2FybigiVEhSRUUuQXVkaW86IHRoaXMgQXVkaW8gaGFzIG5vIHBsYXliYWNrIGNvbnRyb2wuIiksITEpOnRoaXMubG9vcH1zZXRMb29wKHQpe2lmKHRoaXMuaGFzUGxheWJhY2tDb250cm9sPT09ITEpe2NvbnNvbGUud2FybigiVEhSRUUuQXVkaW86IHRoaXMgQXVkaW8gaGFzIG5vIHBsYXliYWNrIGNvbnRyb2wuIik7cmV0dXJufXJldHVybiB0aGlzLmxvb3A9dCx0aGlzLmlzUGxheWluZz09PSEwJiYodGhpcy5zb3VyY2UubG9vcD10aGlzLmxvb3ApLHRoaXN9c2V0TG9vcFN0YXJ0KHQpe3JldHVybiB0aGlzLmxvb3BTdGFydD10LHRoaXN9c2V0TG9vcEVuZCh0KXtyZXR1cm4gdGhpcy5sb29wRW5kPXQsdGhpc31nZXRWb2x1bWUoKXtyZXR1cm4gdGhpcy5nYWluLmdhaW4udmFsdWV9c2V0Vm9sdW1lKHQpe3JldHVybiB0aGlzLmdhaW4uZ2Fpbi5zZXRUYXJnZXRBdFRpbWUodCx0aGlzLmNvbnRleHQuY3VycmVudFRpbWUsLjAxKSx0aGlzfX0sYnY9bmV3IGosRWhlPW5ldyB2aSx1MHI9bmV3IGosd3Y9bmV3IGosaGh0PWNsYXNzIGV4dGVuZHMgTjZ7Y29uc3RydWN0b3IodCl7c3VwZXIodCksdGhpcy5wYW5uZXI9dGhpcy5jb250ZXh0LmNyZWF0ZVBhbm5lcigpLHRoaXMucGFubmVyLnBhbm5pbmdNb2RlbD0iSFJURiIsdGhpcy5wYW5uZXIuY29ubmVjdCh0aGlzLmdhaW4pfWdldE91dHB1dCgpe3JldHVybiB0aGlzLnBhbm5lcn1nZXRSZWZEaXN0YW5jZSgpe3JldHVybiB0aGlzLnBhbm5lci5yZWZEaXN0YW5jZX1zZXRSZWZEaXN0YW5jZSh0KXtyZXR1cm4gdGhpcy5wYW5uZXIucmVmRGlzdGFuY2U9dCx0aGlzfWdldFJvbGxvZmZGYWN0b3IoKXtyZXR1cm4gdGhpcy5wYW5uZXIucm9sbG9mZkZhY3Rvcn1zZXRSb2xsb2ZmRmFjdG9yKHQpe3JldHVybiB0aGlzLnBhbm5lci5yb2xsb2ZmRmFjdG9yPXQsdGhpc31nZXREaXN0YW5jZU1vZGVsKCl7cmV0dXJuIHRoaXMucGFubmVyLmRpc3RhbmNlTW9kZWx9c2V0RGlzdGFuY2VNb2RlbCh0KXtyZXR1cm4gdGhpcy5wYW5uZXIuZGlzdGFuY2VNb2RlbD10LHRoaXN9Z2V0TWF4RGlzdGFuY2UoKXtyZXR1cm4gdGhpcy5wYW5uZXIubWF4RGlzdGFuY2V9c2V0TWF4RGlzdGFuY2UodCl7cmV0dXJuIHRoaXMucGFubmVyLm1heERpc3RhbmNlPXQsdGhpc31zZXREaXJlY3Rpb25hbENvbmUodCxyLG4pe3JldHVybiB0aGlzLnBhbm5lci5jb25lSW5uZXJBbmdsZT10LHRoaXMucGFubmVyLmNvbmVPdXRlckFuZ2xlPXIsdGhpcy5wYW5uZXIuY29uZU91dGVyR2Fpbj1uLHRoaXN9dXBkYXRlTWF0cml4V29ybGQodCl7aWYoc3VwZXIudXBkYXRlTWF0cml4V29ybGQodCksdGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9PT0hMCYmdGhpcy5pc1BsYXlpbmc9PT0hMSlyZXR1cm47dGhpcy5tYXRyaXhXb3JsZC5kZWNvbXBvc2UoYnYsRWhlLHUwciksd3Yuc2V0KDAsMCwxKS5hcHBseVF1YXRlcm5pb24oRWhlKTtsZXQgcj10aGlzLnBhbm5lcjtpZihyLnBvc2l0aW9uWCl7bGV0IG49dGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lK3RoaXMubGlzdGVuZXIudGltZURlbHRhO3IucG9zaXRpb25YLmxpbmVhclJhbXBUb1ZhbHVlQXRUaW1lKGJ2Lngsbiksci5wb3NpdGlvblkubGluZWFyUmFtcFRvVmFsdWVBdFRpbWUoYnYueSxuKSxyLnBvc2l0aW9uWi5saW5lYXJSYW1wVG9WYWx1ZUF0VGltZShidi56LG4pLHIub3JpZW50YXRpb25YLmxpbmVhclJhbXBUb1ZhbHVlQXRUaW1lKHd2Lngsbiksci5vcmllbnRhdGlvblkubGluZWFyUmFtcFRvVmFsdWVBdFRpbWUod3YueSxuKSxyLm9yaWVudGF0aW9uWi5saW5lYXJSYW1wVG9WYWx1ZUF0VGltZSh3di56LG4pfWVsc2Ugci5zZXRQb3NpdGlvbihidi54LGJ2LnksYnYueiksci5zZXRPcmllbnRhdGlvbih3di54LHd2Lnksd3Yueil9fSx6VT1jbGFzc3tjb25zdHJ1Y3Rvcih0LHI9MjA0OCl7dGhpcy5hbmFseXNlcj10LmNvbnRleHQuY3JlYXRlQW5hbHlzZXIoKSx0aGlzLmFuYWx5c2VyLmZmdFNpemU9cix0aGlzLmRhdGE9bmV3IFVpbnQ4QXJyYXkodGhpcy5hbmFseXNlci5mcmVxdWVuY3lCaW5Db3VudCksdC5nZXRPdXRwdXQoKS5jb25uZWN0KHRoaXMuYW5hbHlzZXIpfWdldEZyZXF1ZW5jeURhdGEoKXtyZXR1cm4gdGhpcy5hbmFseXNlci5nZXRCeXRlRnJlcXVlbmN5RGF0YSh0aGlzLmRhdGEpLHRoaXMuZGF0YX1nZXRBdmVyYWdlRnJlcXVlbmN5KCl7bGV0IHQ9MCxyPXRoaXMuZ2V0RnJlcXVlbmN5RGF0YSgpO2ZvcihsZXQgbj0wO248ci5sZW5ndGg7bisrKXQrPXJbbl07cmV0dXJuIHQvci5sZW5ndGh9fSxGVT1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIsbil7dGhpcy5iaW5kaW5nPXQsdGhpcy52YWx1ZVNpemU9bjtsZXQgaSxvLGE7c3dpdGNoKHIpe2Nhc2UicXVhdGVybmlvbiI6aT10aGlzLl9zbGVycCxvPXRoaXMuX3NsZXJwQWRkaXRpdmUsYT10aGlzLl9zZXRBZGRpdGl2ZUlkZW50aXR5UXVhdGVybmlvbix0aGlzLmJ1ZmZlcj1uZXcgRmxvYXQ2NEFycmF5KG4qNiksdGhpcy5fd29ya0luZGV4PTU7YnJlYWs7Y2FzZSJzdHJpbmciOmNhc2UiYm9vbCI6aT10aGlzLl9zZWxlY3Qsbz10aGlzLl9zZWxlY3QsYT10aGlzLl9zZXRBZGRpdGl2ZUlkZW50aXR5T3RoZXIsdGhpcy5idWZmZXI9bmV3IEFycmF5KG4qNSk7YnJlYWs7ZGVmYXVsdDppPXRoaXMuX2xlcnAsbz10aGlzLl9sZXJwQWRkaXRpdmUsYT10aGlzLl9zZXRBZGRpdGl2ZUlkZW50aXR5TnVtZXJpYyx0aGlzLmJ1ZmZlcj1uZXcgRmxvYXQ2NEFycmF5KG4qNSl9dGhpcy5fbWl4QnVmZmVyUmVnaW9uPWksdGhpcy5fbWl4QnVmZmVyUmVnaW9uQWRkaXRpdmU9byx0aGlzLl9zZXRJZGVudGl0eT1hLHRoaXMuX29yaWdJbmRleD0zLHRoaXMuX2FkZEluZGV4PTQsdGhpcy5jdW11bGF0aXZlV2VpZ2h0PTAsdGhpcy5jdW11bGF0aXZlV2VpZ2h0QWRkaXRpdmU9MCx0aGlzLnVzZUNvdW50PTAsdGhpcy5yZWZlcmVuY2VDb3VudD0wfWFjY3VtdWxhdGUodCxyKXtsZXQgbj10aGlzLmJ1ZmZlcixpPXRoaXMudmFsdWVTaXplLG89dCppK2ksYT10aGlzLmN1bXVsYXRpdmVXZWlnaHQ7aWYoYT09PTApe2ZvcihsZXQgcz0wO3MhPT1pOysrcyluW28rc109bltzXTthPXJ9ZWxzZXthKz1yO2xldCBzPXIvYTt0aGlzLl9taXhCdWZmZXJSZWdpb24obixvLDAscyxpKX10aGlzLmN1bXVsYXRpdmVXZWlnaHQ9YX1hY2N1bXVsYXRlQWRkaXRpdmUodCl7bGV0IHI9dGhpcy5idWZmZXIsbj10aGlzLnZhbHVlU2l6ZSxpPW4qdGhpcy5fYWRkSW5kZXg7dGhpcy5jdW11bGF0aXZlV2VpZ2h0QWRkaXRpdmU9PT0wJiZ0aGlzLl9zZXRJZGVudGl0eSgpLHRoaXMuX21peEJ1ZmZlclJlZ2lvbkFkZGl0aXZlKHIsaSwwLHQsbiksdGhpcy5jdW11bGF0aXZlV2VpZ2h0QWRkaXRpdmUrPXR9YXBwbHkodCl7bGV0IHI9dGhpcy52YWx1ZVNpemUsbj10aGlzLmJ1ZmZlcixpPXQqcityLG89dGhpcy5jdW11bGF0aXZlV2VpZ2h0LGE9dGhpcy5jdW11bGF0aXZlV2VpZ2h0QWRkaXRpdmUscz10aGlzLmJpbmRpbmc7aWYodGhpcy5jdW11bGF0aXZlV2VpZ2h0PTAsdGhpcy5jdW11bGF0aXZlV2VpZ2h0QWRkaXRpdmU9MCxvPDEpe2xldCBsPXIqdGhpcy5fb3JpZ0luZGV4O3RoaXMuX21peEJ1ZmZlclJlZ2lvbihuLGksbCwxLW8scil9YT4wJiZ0aGlzLl9taXhCdWZmZXJSZWdpb25BZGRpdGl2ZShuLGksdGhpcy5fYWRkSW5kZXgqciwxLHIpO2ZvcihsZXQgbD1yLGM9cityO2whPT1jOysrbClpZihuW2xdIT09bltsK3JdKXtzLnNldFZhbHVlKG4saSk7YnJlYWt9fXNhdmVPcmlnaW5hbFN0YXRlKCl7bGV0IHQ9dGhpcy5iaW5kaW5nLHI9dGhpcy5idWZmZXIsbj10aGlzLnZhbHVlU2l6ZSxpPW4qdGhpcy5fb3JpZ0luZGV4O3QuZ2V0VmFsdWUocixpKTtmb3IobGV0IG89bixhPWk7byE9PWE7KytvKXJbb109cltpK28lbl07dGhpcy5fc2V0SWRlbnRpdHkoKSx0aGlzLmN1bXVsYXRpdmVXZWlnaHQ9MCx0aGlzLmN1bXVsYXRpdmVXZWlnaHRBZGRpdGl2ZT0wfXJlc3RvcmVPcmlnaW5hbFN0YXRlKCl7bGV0IHQ9dGhpcy52YWx1ZVNpemUqMzt0aGlzLmJpbmRpbmcuc2V0VmFsdWUodGhpcy5idWZmZXIsdCl9X3NldEFkZGl0aXZlSWRlbnRpdHlOdW1lcmljKCl7bGV0IHQ9dGhpcy5fYWRkSW5kZXgqdGhpcy52YWx1ZVNpemUscj10K3RoaXMudmFsdWVTaXplO2ZvcihsZXQgbj10O248cjtuKyspdGhpcy5idWZmZXJbbl09MH1fc2V0QWRkaXRpdmVJZGVudGl0eVF1YXRlcm5pb24oKXt0aGlzLl9zZXRBZGRpdGl2ZUlkZW50aXR5TnVtZXJpYygpLHRoaXMuYnVmZmVyW3RoaXMuX2FkZEluZGV4KnRoaXMudmFsdWVTaXplKzNdPTF9X3NldEFkZGl0aXZlSWRlbnRpdHlPdGhlcigpe2xldCB0PXRoaXMuX29yaWdJbmRleCp0aGlzLnZhbHVlU2l6ZSxyPXRoaXMuX2FkZEluZGV4KnRoaXMudmFsdWVTaXplO2ZvcihsZXQgbj0wO248dGhpcy52YWx1ZVNpemU7bisrKXRoaXMuYnVmZmVyW3Irbl09dGhpcy5idWZmZXJbdCtuXX1fc2VsZWN0KHQscixuLGksbyl7aWYoaT49LjUpZm9yKGxldCBhPTA7YSE9PW87KythKXRbcithXT10W24rYV19X3NsZXJwKHQscixuLGkpe3ZpLnNsZXJwRmxhdCh0LHIsdCxyLHQsbixpKX1fc2xlcnBBZGRpdGl2ZSh0LHIsbixpLG8pe2xldCBhPXRoaXMuX3dvcmtJbmRleCpvO3ZpLm11bHRpcGx5UXVhdGVybmlvbnNGbGF0KHQsYSx0LHIsdCxuKSx2aS5zbGVycEZsYXQodCxyLHQscix0LGEsaSl9X2xlcnAodCxyLG4saSxvKXtsZXQgYT0xLWk7Zm9yKGxldCBzPTA7cyE9PW87KytzKXtsZXQgbD1yK3M7dFtsXT10W2xdKmErdFtuK3NdKml9fV9sZXJwQWRkaXRpdmUodCxyLG4saSxvKXtmb3IobGV0IGE9MDthIT09bzsrK2Epe2xldCBzPXIrYTt0W3NdPXRbc10rdFtuK2FdKml9fX0sQmh0PSJcXFtcXF1cXC46XFwvIixoMHI9bmV3IFJlZ0V4cCgiWyIrQmh0KyJdIiwiZyIpLEhodD0iW14iK0JodCsiXSIsZjByPSJbXiIrQmh0LnJlcGxhY2UoIlxcLiIsIiIpKyJdIixwMHI9LygoPzpXQytbXC86XSkqKS8uc291cmNlLnJlcGxhY2UoIldDIixIaHQpLGQwcj0vKFdDT0QrKT8vLnNvdXJjZS5yZXBsYWNlKCJXQ09EIixmMHIpLG0wcj0vKD86XC4oV0MrKSg/OlxbKC4rKVxdKT8pPy8uc291cmNlLnJlcGxhY2UoIldDIixIaHQpLGcwcj0vXC4oV0MrKSg/OlxbKC4rKVxdKT8vLnNvdXJjZS5yZXBsYWNlKCJXQyIsSGh0KSxfMHI9bmV3IFJlZ0V4cCgiXiIrcDByK2QwcittMHIrZzByKyIkIikseTByPVsibWF0ZXJpYWwiLCJtYXRlcmlhbHMiLCJib25lcyJdLGZodD1jbGFzc3tjb25zdHJ1Y3Rvcih0LHIsbil7bGV0IGk9bnx8Q3IucGFyc2VUcmFja05hbWUocik7dGhpcy5fdGFyZ2V0R3JvdXA9dCx0aGlzLl9iaW5kaW5ncz10LnN1YnNjcmliZV8ocixpKX1nZXRWYWx1ZSh0LHIpe3RoaXMuYmluZCgpO2xldCBuPXRoaXMuX3RhcmdldEdyb3VwLm5DYWNoZWRPYmplY3RzXyxpPXRoaXMuX2JpbmRpbmdzW25dO2khPT12b2lkIDAmJmkuZ2V0VmFsdWUodCxyKX1zZXRWYWx1ZSh0LHIpe2xldCBuPXRoaXMuX2JpbmRpbmdzO2ZvcihsZXQgaT10aGlzLl90YXJnZXRHcm91cC5uQ2FjaGVkT2JqZWN0c18sbz1uLmxlbmd0aDtpIT09bzsrK2kpbltpXS5zZXRWYWx1ZSh0LHIpfWJpbmQoKXtsZXQgdD10aGlzLl9iaW5kaW5ncztmb3IobGV0IHI9dGhpcy5fdGFyZ2V0R3JvdXAubkNhY2hlZE9iamVjdHNfLG49dC5sZW5ndGg7ciE9PW47KytyKXRbcl0uYmluZCgpfXVuYmluZCgpe2xldCB0PXRoaXMuX2JpbmRpbmdzO2ZvcihsZXQgcj10aGlzLl90YXJnZXRHcm91cC5uQ2FjaGVkT2JqZWN0c18sbj10Lmxlbmd0aDtyIT09bjsrK3IpdFtyXS51bmJpbmQoKX19LENyPWNsYXNze2NvbnN0cnVjdG9yKHQscixuKXt0aGlzLnBhdGg9cix0aGlzLnBhcnNlZFBhdGg9bnx8Q3IucGFyc2VUcmFja05hbWUociksdGhpcy5ub2RlPUNyLmZpbmROb2RlKHQsdGhpcy5wYXJzZWRQYXRoLm5vZGVOYW1lKXx8dCx0aGlzLnJvb3ROb2RlPXQsdGhpcy5nZXRWYWx1ZT10aGlzLl9nZXRWYWx1ZV91bmJvdW5kLHRoaXMuc2V0VmFsdWU9dGhpcy5fc2V0VmFsdWVfdW5ib3VuZH1zdGF0aWMgY3JlYXRlKHQscixuKXtyZXR1cm4gdCYmdC5pc0FuaW1hdGlvbk9iamVjdEdyb3VwP25ldyBDci5Db21wb3NpdGUodCxyLG4pOm5ldyBDcih0LHIsbil9c3RhdGljIHNhbml0aXplTm9kZU5hbWUodCl7cmV0dXJuIHQucmVwbGFjZSgvXHMvZywiXyIpLnJlcGxhY2UoaDByLCIiKX1zdGF0aWMgcGFyc2VUcmFja05hbWUodCl7bGV0IHI9XzByLmV4ZWModCk7aWYoIXIpdGhyb3cgbmV3IEVycm9yKCJQcm9wZXJ0eUJpbmRpbmc6IENhbm5vdCBwYXJzZSB0cmFja05hbWU6ICIrdCk7bGV0IG49e25vZGVOYW1lOnJbMl0sb2JqZWN0TmFtZTpyWzNdLG9iamVjdEluZGV4OnJbNF0scHJvcGVydHlOYW1lOnJbNV0scHJvcGVydHlJbmRleDpyWzZdfSxpPW4ubm9kZU5hbWUmJm4ubm9kZU5hbWUubGFzdEluZGV4T2YoIi4iKTtpZihpIT09dm9pZCAwJiZpIT09LTEpe2xldCBvPW4ubm9kZU5hbWUuc3Vic3RyaW5nKGkrMSk7eTByLmluZGV4T2YobykhPT0tMSYmKG4ubm9kZU5hbWU9bi5ub2RlTmFtZS5zdWJzdHJpbmcoMCxpKSxuLm9iamVjdE5hbWU9byl9aWYobi5wcm9wZXJ0eU5hbWU9PT1udWxsfHxuLnByb3BlcnR5TmFtZS5sZW5ndGg9PT0wKXRocm93IG5ldyBFcnJvcigiUHJvcGVydHlCaW5kaW5nOiBjYW4gbm90IHBhcnNlIHByb3BlcnR5TmFtZSBmcm9tIHRyYWNrTmFtZTogIit0KTtyZXR1cm4gbn1zdGF0aWMgZmluZE5vZGUodCxyKXtpZighcnx8cj09PSIifHxyPT09Ii4ifHxyPT09LTF8fHI9PT10Lm5hbWV8fHI9PT10LnV1aWQpcmV0dXJuIHQ7aWYodC5za2VsZXRvbil7bGV0IG49dC5za2VsZXRvbi5nZXRCb25lQnlOYW1lKHIpO2lmKG4hPT12b2lkIDApcmV0dXJuIG59aWYodC5jaGlsZHJlbil7bGV0IG49ZnVuY3Rpb24obyl7Zm9yKGxldCBhPTA7YTxvLmxlbmd0aDthKyspe2xldCBzPW9bYV07aWYocy5uYW1lPT09cnx8cy51dWlkPT09cilyZXR1cm4gcztsZXQgbD1uKHMuY2hpbGRyZW4pO2lmKGwpcmV0dXJuIGx9cmV0dXJuIG51bGx9LGk9bih0LmNoaWxkcmVuKTtpZihpKXJldHVybiBpfXJldHVybiBudWxsfV9nZXRWYWx1ZV91bmF2YWlsYWJsZSgpe31fc2V0VmFsdWVfdW5hdmFpbGFibGUoKXt9X2dldFZhbHVlX2RpcmVjdCh0LHIpe3Rbcl09dGhpcy50YXJnZXRPYmplY3RbdGhpcy5wcm9wZXJ0eU5hbWVdfV9nZXRWYWx1ZV9hcnJheSh0LHIpe2xldCBuPXRoaXMucmVzb2x2ZWRQcm9wZXJ0eTtmb3IobGV0IGk9MCxvPW4ubGVuZ3RoO2khPT1vOysraSl0W3IrK109bltpXX1fZ2V0VmFsdWVfYXJyYXlFbGVtZW50KHQscil7dFtyXT10aGlzLnJlc29sdmVkUHJvcGVydHlbdGhpcy5wcm9wZXJ0eUluZGV4XX1fZ2V0VmFsdWVfdG9BcnJheSh0LHIpe3RoaXMucmVzb2x2ZWRQcm9wZXJ0eS50b0FycmF5KHQscil9X3NldFZhbHVlX2RpcmVjdCh0LHIpe3RoaXMudGFyZ2V0T2JqZWN0W3RoaXMucHJvcGVydHlOYW1lXT10W3JdfV9zZXRWYWx1ZV9kaXJlY3Rfc2V0TmVlZHNVcGRhdGUodCxyKXt0aGlzLnRhcmdldE9iamVjdFt0aGlzLnByb3BlcnR5TmFtZV09dFtyXSx0aGlzLnRhcmdldE9iamVjdC5uZWVkc1VwZGF0ZT0hMH1fc2V0VmFsdWVfZGlyZWN0X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGUodCxyKXt0aGlzLnRhcmdldE9iamVjdFt0aGlzLnByb3BlcnR5TmFtZV09dFtyXSx0aGlzLnRhcmdldE9iamVjdC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlPSEwfV9zZXRWYWx1ZV9hcnJheSh0LHIpe2xldCBuPXRoaXMucmVzb2x2ZWRQcm9wZXJ0eTtmb3IobGV0IGk9MCxvPW4ubGVuZ3RoO2khPT1vOysraSluW2ldPXRbcisrXX1fc2V0VmFsdWVfYXJyYXlfc2V0TmVlZHNVcGRhdGUodCxyKXtsZXQgbj10aGlzLnJlc29sdmVkUHJvcGVydHk7Zm9yKGxldCBpPTAsbz1uLmxlbmd0aDtpIT09bzsrK2kpbltpXT10W3IrK107dGhpcy50YXJnZXRPYmplY3QubmVlZHNVcGRhdGU9ITB9X3NldFZhbHVlX2FycmF5X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGUodCxyKXtsZXQgbj10aGlzLnJlc29sdmVkUHJvcGVydHk7Zm9yKGxldCBpPTAsbz1uLmxlbmd0aDtpIT09bzsrK2kpbltpXT10W3IrK107dGhpcy50YXJnZXRPYmplY3QubWF0cml4V29ybGROZWVkc1VwZGF0ZT0hMH1fc2V0VmFsdWVfYXJyYXlFbGVtZW50KHQscil7dGhpcy5yZXNvbHZlZFByb3BlcnR5W3RoaXMucHJvcGVydHlJbmRleF09dFtyXX1fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE5lZWRzVXBkYXRlKHQscil7dGhpcy5yZXNvbHZlZFByb3BlcnR5W3RoaXMucHJvcGVydHlJbmRleF09dFtyXSx0aGlzLnRhcmdldE9iamVjdC5uZWVkc1VwZGF0ZT0hMH1fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGUodCxyKXt0aGlzLnJlc29sdmVkUHJvcGVydHlbdGhpcy5wcm9wZXJ0eUluZGV4XT10W3JdLHRoaXMudGFyZ2V0T2JqZWN0Lm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9ITB9X3NldFZhbHVlX2Zyb21BcnJheSh0LHIpe3RoaXMucmVzb2x2ZWRQcm9wZXJ0eS5mcm9tQXJyYXkodCxyKX1fc2V0VmFsdWVfZnJvbUFycmF5X3NldE5lZWRzVXBkYXRlKHQscil7dGhpcy5yZXNvbHZlZFByb3BlcnR5LmZyb21BcnJheSh0LHIpLHRoaXMudGFyZ2V0T2JqZWN0Lm5lZWRzVXBkYXRlPSEwfV9zZXRWYWx1ZV9mcm9tQXJyYXlfc2V0TWF0cml4V29ybGROZWVkc1VwZGF0ZSh0LHIpe3RoaXMucmVzb2x2ZWRQcm9wZXJ0eS5mcm9tQXJyYXkodCxyKSx0aGlzLnRhcmdldE9iamVjdC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlPSEwfV9nZXRWYWx1ZV91bmJvdW5kKHQscil7dGhpcy5iaW5kKCksdGhpcy5nZXRWYWx1ZSh0LHIpfV9zZXRWYWx1ZV91bmJvdW5kKHQscil7dGhpcy5iaW5kKCksdGhpcy5zZXRWYWx1ZSh0LHIpfWJpbmQoKXtsZXQgdD10aGlzLm5vZGUscj10aGlzLnBhcnNlZFBhdGgsbj1yLm9iamVjdE5hbWUsaT1yLnByb3BlcnR5TmFtZSxvPXIucHJvcGVydHlJbmRleDtpZih0fHwodD1Dci5maW5kTm9kZSh0aGlzLnJvb3ROb2RlLHIubm9kZU5hbWUpfHx0aGlzLnJvb3ROb2RlLHRoaXMubm9kZT10KSx0aGlzLmdldFZhbHVlPXRoaXMuX2dldFZhbHVlX3VuYXZhaWxhYmxlLHRoaXMuc2V0VmFsdWU9dGhpcy5fc2V0VmFsdWVfdW5hdmFpbGFibGUsIXQpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlByb3BlcnR5QmluZGluZzogVHJ5aW5nIHRvIHVwZGF0ZSBub2RlIGZvciB0cmFjazogIit0aGlzLnBhdGgrIiBidXQgaXQgd2Fzbid0IGZvdW5kLiIpO3JldHVybn1pZihuKXtsZXQgYz1yLm9iamVjdEluZGV4O3N3aXRjaChuKXtjYXNlIm1hdGVyaWFscyI6aWYoIXQubWF0ZXJpYWwpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlByb3BlcnR5QmluZGluZzogQ2FuIG5vdCBiaW5kIHRvIG1hdGVyaWFsIGFzIG5vZGUgZG9lcyBub3QgaGF2ZSBhIG1hdGVyaWFsLiIsdGhpcyk7cmV0dXJufWlmKCF0Lm1hdGVyaWFsLm1hdGVyaWFscyl7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbWF0ZXJpYWwubWF0ZXJpYWxzIGFzIG5vZGUubWF0ZXJpYWwgZG9lcyBub3QgaGF2ZSBhIG1hdGVyaWFscyBhcnJheS4iLHRoaXMpO3JldHVybn10PXQubWF0ZXJpYWwubWF0ZXJpYWxzO2JyZWFrO2Nhc2UiYm9uZXMiOmlmKCF0LnNrZWxldG9uKXtjb25zb2xlLmVycm9yKCJUSFJFRS5Qcm9wZXJ0eUJpbmRpbmc6IENhbiBub3QgYmluZCB0byBib25lcyBhcyBub2RlIGRvZXMgbm90IGhhdmUgYSBza2VsZXRvbi4iLHRoaXMpO3JldHVybn10PXQuc2tlbGV0b24uYm9uZXM7Zm9yKGxldCB1PTA7dTx0Lmxlbmd0aDt1KyspaWYodFt1XS5uYW1lPT09Yyl7Yz11O2JyZWFrfWJyZWFrO2RlZmF1bHQ6aWYodFtuXT09PXZvaWQgMCl7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gb2JqZWN0TmFtZSBvZiBub2RlIHVuZGVmaW5lZC4iLHRoaXMpO3JldHVybn10PXRbbl19aWYoYyE9PXZvaWQgMCl7aWYodFtjXT09PXZvaWQgMCl7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBUcnlpbmcgdG8gYmluZCB0byBvYmplY3RJbmRleCBvZiBvYmplY3ROYW1lLCBidXQgaXMgdW5kZWZpbmVkLiIsdGhpcyx0KTtyZXR1cm59dD10W2NdfX1sZXQgYT10W2ldO2lmKGE9PT12b2lkIDApe2xldCBjPXIubm9kZU5hbWU7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBUcnlpbmcgdG8gdXBkYXRlIHByb3BlcnR5IGZvciB0cmFjazogIitjKyIuIitpKyIgYnV0IGl0IHdhc24ndCBmb3VuZC4iLHQpO3JldHVybn1sZXQgcz10aGlzLlZlcnNpb25pbmcuTm9uZTt0aGlzLnRhcmdldE9iamVjdD10LHQubmVlZHNVcGRhdGUhPT12b2lkIDA/cz10aGlzLlZlcnNpb25pbmcuTmVlZHNVcGRhdGU6dC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlIT09dm9pZCAwJiYocz10aGlzLlZlcnNpb25pbmcuTWF0cml4V29ybGROZWVkc1VwZGF0ZSk7bGV0IGw9dGhpcy5CaW5kaW5nVHlwZS5EaXJlY3Q7aWYobyE9PXZvaWQgMCl7aWYoaT09PSJtb3JwaFRhcmdldEluZmx1ZW5jZXMiKXtpZighdC5nZW9tZXRyeSl7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbW9ycGhUYXJnZXRJbmZsdWVuY2VzIGJlY2F1c2Ugbm9kZSBkb2VzIG5vdCBoYXZlIGEgZ2VvbWV0cnkuIix0aGlzKTtyZXR1cm59aWYodC5nZW9tZXRyeS5pc0J1ZmZlckdlb21ldHJ5KXtpZighdC5nZW9tZXRyeS5tb3JwaEF0dHJpYnV0ZXMpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlByb3BlcnR5QmluZGluZzogQ2FuIG5vdCBiaW5kIHRvIG1vcnBoVGFyZ2V0SW5mbHVlbmNlcyBiZWNhdXNlIG5vZGUgZG9lcyBub3QgaGF2ZSBhIGdlb21ldHJ5Lm1vcnBoQXR0cmlidXRlcy4iLHRoaXMpO3JldHVybn10Lm1vcnBoVGFyZ2V0RGljdGlvbmFyeVtvXSE9PXZvaWQgMCYmKG89dC5tb3JwaFRhcmdldERpY3Rpb25hcnlbb10pfWVsc2V7Y29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbW9ycGhUYXJnZXRJbmZsdWVuY2VzIG9uIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iLHRoaXMpO3JldHVybn19bD10aGlzLkJpbmRpbmdUeXBlLkFycmF5RWxlbWVudCx0aGlzLnJlc29sdmVkUHJvcGVydHk9YSx0aGlzLnByb3BlcnR5SW5kZXg9b31lbHNlIGEuZnJvbUFycmF5IT09dm9pZCAwJiZhLnRvQXJyYXkhPT12b2lkIDA/KGw9dGhpcy5CaW5kaW5nVHlwZS5IYXNGcm9tVG9BcnJheSx0aGlzLnJlc29sdmVkUHJvcGVydHk9YSk6QXJyYXkuaXNBcnJheShhKT8obD10aGlzLkJpbmRpbmdUeXBlLkVudGlyZUFycmF5LHRoaXMucmVzb2x2ZWRQcm9wZXJ0eT1hKTp0aGlzLnByb3BlcnR5TmFtZT1pO3RoaXMuZ2V0VmFsdWU9dGhpcy5HZXR0ZXJCeUJpbmRpbmdUeXBlW2xdLHRoaXMuc2V0VmFsdWU9dGhpcy5TZXR0ZXJCeUJpbmRpbmdUeXBlQW5kVmVyc2lvbmluZ1tsXVtzXX11bmJpbmQoKXt0aGlzLm5vZGU9bnVsbCx0aGlzLmdldFZhbHVlPXRoaXMuX2dldFZhbHVlX3VuYm91bmQsdGhpcy5zZXRWYWx1ZT10aGlzLl9zZXRWYWx1ZV91bmJvdW5kfX07Q3IuQ29tcG9zaXRlPWZodDtDci5wcm90b3R5cGUuQmluZGluZ1R5cGU9e0RpcmVjdDowLEVudGlyZUFycmF5OjEsQXJyYXlFbGVtZW50OjIsSGFzRnJvbVRvQXJyYXk6M307Q3IucHJvdG90eXBlLlZlcnNpb25pbmc9e05vbmU6MCxOZWVkc1VwZGF0ZToxLE1hdHJpeFdvcmxkTmVlZHNVcGRhdGU6Mn07Q3IucHJvdG90eXBlLkdldHRlckJ5QmluZGluZ1R5cGU9W0NyLnByb3RvdHlwZS5fZ2V0VmFsdWVfZGlyZWN0LENyLnByb3RvdHlwZS5fZ2V0VmFsdWVfYXJyYXksQ3IucHJvdG90eXBlLl9nZXRWYWx1ZV9hcnJheUVsZW1lbnQsQ3IucHJvdG90eXBlLl9nZXRWYWx1ZV90b0FycmF5XTtDci5wcm90b3R5cGUuU2V0dGVyQnlCaW5kaW5nVHlwZUFuZFZlcnNpb25pbmc9W1tDci5wcm90b3R5cGUuX3NldFZhbHVlX2RpcmVjdCxDci5wcm90b3R5cGUuX3NldFZhbHVlX2RpcmVjdF9zZXROZWVkc1VwZGF0ZSxDci5wcm90b3R5cGUuX3NldFZhbHVlX2RpcmVjdF9zZXRNYXRyaXhXb3JsZE5lZWRzVXBkYXRlXSxbQ3IucHJvdG90eXBlLl9zZXRWYWx1ZV9hcnJheSxDci5wcm90b3R5cGUuX3NldFZhbHVlX2FycmF5X3NldE5lZWRzVXBkYXRlLENyLnByb3RvdHlwZS5fc2V0VmFsdWVfYXJyYXlfc2V0TWF0cml4V29ybGROZWVkc1VwZGF0ZV0sW0NyLnByb3RvdHlwZS5fc2V0VmFsdWVfYXJyYXlFbGVtZW50LENyLnByb3RvdHlwZS5fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE5lZWRzVXBkYXRlLENyLnByb3RvdHlwZS5fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGVdLFtDci5wcm90b3R5cGUuX3NldFZhbHVlX2Zyb21BcnJheSxDci5wcm90b3R5cGUuX3NldFZhbHVlX2Zyb21BcnJheV9zZXROZWVkc1VwZGF0ZSxDci5wcm90b3R5cGUuX3NldFZhbHVlX2Zyb21BcnJheV9zZXRNYXRyaXhXb3JsZE5lZWRzVXBkYXRlXV07dmFyIEJVPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy51dWlkPU5sKCksdGhpcy5fb2JqZWN0cz1BcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChhcmd1bWVudHMpLHRoaXMubkNhY2hlZE9iamVjdHNfPTA7bGV0IHQ9e307dGhpcy5faW5kaWNlc0J5VVVJRD10O2ZvcihsZXQgbj0wLGk9YXJndW1lbnRzLmxlbmd0aDtuIT09aTsrK24pdFthcmd1bWVudHNbbl0udXVpZF09bjt0aGlzLl9wYXRocz1bXSx0aGlzLl9wYXJzZWRQYXRocz1bXSx0aGlzLl9iaW5kaW5ncz1bXSx0aGlzLl9iaW5kaW5nc0luZGljZXNCeVBhdGg9e307bGV0IHI9dGhpczt0aGlzLnN0YXRzPXtvYmplY3RzOntnZXQgdG90YWwoKXtyZXR1cm4gci5fb2JqZWN0cy5sZW5ndGh9LGdldCBpblVzZSgpe3JldHVybiB0aGlzLnRvdGFsLXIubkNhY2hlZE9iamVjdHNffX0sZ2V0IGJpbmRpbmdzUGVyT2JqZWN0KCl7cmV0dXJuIHIuX2JpbmRpbmdzLmxlbmd0aH19fWFkZCgpe2xldCB0PXRoaXMuX29iamVjdHMscj10aGlzLl9pbmRpY2VzQnlVVUlELG49dGhpcy5fcGF0aHMsaT10aGlzLl9wYXJzZWRQYXRocyxvPXRoaXMuX2JpbmRpbmdzLGE9by5sZW5ndGgscyxsPXQubGVuZ3RoLGM9dGhpcy5uQ2FjaGVkT2JqZWN0c187Zm9yKGxldCB1PTAsaD1hcmd1bWVudHMubGVuZ3RoO3UhPT1oOysrdSl7bGV0IGY9YXJndW1lbnRzW3VdLHA9Zi51dWlkLGQ9cltwXTtpZihkPT09dm9pZCAwKXtkPWwrKyxyW3BdPWQsdC5wdXNoKGYpO2ZvcihsZXQgZz0wLF89YTtnIT09XzsrK2cpb1tnXS5wdXNoKG5ldyBDcihmLG5bZ10saVtnXSkpfWVsc2UgaWYoZDxjKXtzPXRbZF07bGV0IGc9LS1jLF89dFtnXTtyW18udXVpZF09ZCx0W2RdPV8scltwXT1nLHRbZ109Zjtmb3IobGV0IHk9MCx4PWE7eSE9PXg7Kyt5KXtsZXQgYj1vW3ldLFM9YltnXSxDPWJbZF07YltkXT1TLEM9PT12b2lkIDAmJihDPW5ldyBDcihmLG5beV0saVt5XSkpLGJbZ109Q319ZWxzZSB0W2RdIT09cyYmY29uc29sZS5lcnJvcigiVEhSRUUuQW5pbWF0aW9uT2JqZWN0R3JvdXA6IERpZmZlcmVudCBvYmplY3RzIHdpdGggdGhlIHNhbWUgVVVJRCBkZXRlY3RlZC4gQ2xlYW4gdGhlIGNhY2hlcyBvciByZWNyZWF0ZSB5b3VyIGluZnJhc3RydWN0dXJlIHdoZW4gcmVsb2FkaW5nIHNjZW5lcy4iKX10aGlzLm5DYWNoZWRPYmplY3RzXz1jfXJlbW92ZSgpe2xldCB0PXRoaXMuX29iamVjdHMscj10aGlzLl9pbmRpY2VzQnlVVUlELG49dGhpcy5fYmluZGluZ3MsaT1uLmxlbmd0aCxvPXRoaXMubkNhY2hlZE9iamVjdHNfO2ZvcihsZXQgYT0wLHM9YXJndW1lbnRzLmxlbmd0aDthIT09czsrK2Epe2xldCBsPWFyZ3VtZW50c1thXSxjPWwudXVpZCx1PXJbY107aWYodSE9PXZvaWQgMCYmdT49byl7bGV0IGg9bysrLGY9dFtoXTtyW2YudXVpZF09dSx0W3VdPWYscltjXT1oLHRbaF09bDtmb3IobGV0IHA9MCxkPWk7cCE9PWQ7KytwKXtsZXQgZz1uW3BdLF89Z1toXSx5PWdbdV07Z1t1XT1fLGdbaF09eX19fXRoaXMubkNhY2hlZE9iamVjdHNfPW99dW5jYWNoZSgpe2xldCB0PXRoaXMuX29iamVjdHMscj10aGlzLl9pbmRpY2VzQnlVVUlELG49dGhpcy5fYmluZGluZ3MsaT1uLmxlbmd0aCxvPXRoaXMubkNhY2hlZE9iamVjdHNfLGE9dC5sZW5ndGg7Zm9yKGxldCBzPTAsbD1hcmd1bWVudHMubGVuZ3RoO3MhPT1sOysrcyl7bGV0IGM9YXJndW1lbnRzW3NdLHU9Yy51dWlkLGg9clt1XTtpZihoIT09dm9pZCAwKWlmKGRlbGV0ZSByW3VdLGg8byl7bGV0IGY9LS1vLHA9dFtmXSxkPS0tYSxnPXRbZF07cltwLnV1aWRdPWgsdFtoXT1wLHJbZy51dWlkXT1mLHRbZl09Zyx0LnBvcCgpO2ZvcihsZXQgXz0wLHk9aTtfIT09eTsrK18pe2xldCB4PW5bX10sYj14W2ZdLFM9eFtkXTt4W2hdPWIseFtmXT1TLHgucG9wKCl9fWVsc2V7bGV0IGY9LS1hLHA9dFtmXTtmPjAmJihyW3AudXVpZF09aCksdFtoXT1wLHQucG9wKCk7Zm9yKGxldCBkPTAsZz1pO2QhPT1nOysrZCl7bGV0IF89bltkXTtfW2hdPV9bZl0sXy5wb3AoKX19fXRoaXMubkNhY2hlZE9iamVjdHNfPW99c3Vic2NyaWJlXyh0LHIpe2xldCBuPXRoaXMuX2JpbmRpbmdzSW5kaWNlc0J5UGF0aCxpPW5bdF0sbz10aGlzLl9iaW5kaW5ncztpZihpIT09dm9pZCAwKXJldHVybiBvW2ldO2xldCBhPXRoaXMuX3BhdGhzLHM9dGhpcy5fcGFyc2VkUGF0aHMsbD10aGlzLl9vYmplY3RzLGM9bC5sZW5ndGgsdT10aGlzLm5DYWNoZWRPYmplY3RzXyxoPW5ldyBBcnJheShjKTtpPW8ubGVuZ3RoLG5bdF09aSxhLnB1c2godCkscy5wdXNoKHIpLG8ucHVzaChoKTtmb3IobGV0IGY9dSxwPWwubGVuZ3RoO2YhPT1wOysrZil7bGV0IGQ9bFtmXTtoW2ZdPW5ldyBDcihkLHQscil9cmV0dXJuIGh9dW5zdWJzY3JpYmVfKHQpe2xldCByPXRoaXMuX2JpbmRpbmdzSW5kaWNlc0J5UGF0aCxuPXJbdF07aWYobiE9PXZvaWQgMCl7bGV0IGk9dGhpcy5fcGF0aHMsbz10aGlzLl9wYXJzZWRQYXRocyxhPXRoaXMuX2JpbmRpbmdzLHM9YS5sZW5ndGgtMSxsPWFbc10sYz10W3NdO3JbY109bixhW25dPWwsYS5wb3AoKSxvW25dPW9bc10sby5wb3AoKSxpW25dPWlbc10saS5wb3AoKX19fTtCVS5wcm90b3R5cGUuaXNBbmltYXRpb25PYmplY3RHcm91cD0hMDt2YXIgcGh0PWNsYXNze2NvbnN0cnVjdG9yKHQscixuPW51bGwsaT1yLmJsZW5kTW9kZSl7dGhpcy5fbWl4ZXI9dCx0aGlzLl9jbGlwPXIsdGhpcy5fbG9jYWxSb290PW4sdGhpcy5ibGVuZE1vZGU9aTtsZXQgbz1yLnRyYWNrcyxhPW8ubGVuZ3RoLHM9bmV3IEFycmF5KGEpLGw9e2VuZGluZ1N0YXJ0OkV2LGVuZGluZ0VuZDpFdn07Zm9yKGxldCBjPTA7YyE9PWE7KytjKXtsZXQgdT1vW2NdLmNyZWF0ZUludGVycG9sYW50KG51bGwpO3NbY109dSx1LnNldHRpbmdzPWx9dGhpcy5faW50ZXJwb2xhbnRTZXR0aW5ncz1sLHRoaXMuX2ludGVycG9sYW50cz1zLHRoaXMuX3Byb3BlcnR5QmluZGluZ3M9bmV3IEFycmF5KGEpLHRoaXMuX2NhY2hlSW5kZXg9bnVsbCx0aGlzLl9ieUNsaXBDYWNoZUluZGV4PW51bGwsdGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ9bnVsbCx0aGlzLl93ZWlnaHRJbnRlcnBvbGFudD1udWxsLHRoaXMubG9vcD1FZmUsdGhpcy5fbG9vcENvdW50PS0xLHRoaXMuX3N0YXJ0VGltZT1udWxsLHRoaXMudGltZT0wLHRoaXMudGltZVNjYWxlPTEsdGhpcy5fZWZmZWN0aXZlVGltZVNjYWxlPTEsdGhpcy53ZWlnaHQ9MSx0aGlzLl9lZmZlY3RpdmVXZWlnaHQ9MSx0aGlzLnJlcGV0aXRpb25zPTEvMCx0aGlzLnBhdXNlZD0hMSx0aGlzLmVuYWJsZWQ9ITAsdGhpcy5jbGFtcFdoZW5GaW5pc2hlZD0hMSx0aGlzLnplcm9TbG9wZUF0U3RhcnQ9ITAsdGhpcy56ZXJvU2xvcGVBdEVuZD0hMH1wbGF5KCl7cmV0dXJuIHRoaXMuX21peGVyLl9hY3RpdmF0ZUFjdGlvbih0aGlzKSx0aGlzfXN0b3AoKXtyZXR1cm4gdGhpcy5fbWl4ZXIuX2RlYWN0aXZhdGVBY3Rpb24odGhpcyksdGhpcy5yZXNldCgpfXJlc2V0KCl7cmV0dXJuIHRoaXMucGF1c2VkPSExLHRoaXMuZW5hYmxlZD0hMCx0aGlzLnRpbWU9MCx0aGlzLl9sb29wQ291bnQ9LTEsdGhpcy5fc3RhcnRUaW1lPW51bGwsdGhpcy5zdG9wRmFkaW5nKCkuc3RvcFdhcnBpbmcoKX1pc1J1bm5pbmcoKXtyZXR1cm4gdGhpcy5lbmFibGVkJiYhdGhpcy5wYXVzZWQmJnRoaXMudGltZVNjYWxlIT09MCYmdGhpcy5fc3RhcnRUaW1lPT09bnVsbCYmdGhpcy5fbWl4ZXIuX2lzQWN0aXZlQWN0aW9uKHRoaXMpfWlzU2NoZWR1bGVkKCl7cmV0dXJuIHRoaXMuX21peGVyLl9pc0FjdGl2ZUFjdGlvbih0aGlzKX1zdGFydEF0KHQpe3JldHVybiB0aGlzLl9zdGFydFRpbWU9dCx0aGlzfXNldExvb3AodCxyKXtyZXR1cm4gdGhpcy5sb29wPXQsdGhpcy5yZXBldGl0aW9ucz1yLHRoaXN9c2V0RWZmZWN0aXZlV2VpZ2h0KHQpe3JldHVybiB0aGlzLndlaWdodD10LHRoaXMuX2VmZmVjdGl2ZVdlaWdodD10aGlzLmVuYWJsZWQ/dDowLHRoaXMuc3RvcEZhZGluZygpfWdldEVmZmVjdGl2ZVdlaWdodCgpe3JldHVybiB0aGlzLl9lZmZlY3RpdmVXZWlnaHR9ZmFkZUluKHQpe3JldHVybiB0aGlzLl9zY2hlZHVsZUZhZGluZyh0LDAsMSl9ZmFkZU91dCh0KXtyZXR1cm4gdGhpcy5fc2NoZWR1bGVGYWRpbmcodCwxLDApfWNyb3NzRmFkZUZyb20odCxyLG4pe2lmKHQuZmFkZU91dChyKSx0aGlzLmZhZGVJbihyKSxuKXtsZXQgaT10aGlzLl9jbGlwLmR1cmF0aW9uLG89dC5fY2xpcC5kdXJhdGlvbixhPW8vaSxzPWkvbzt0LndhcnAoMSxhLHIpLHRoaXMud2FycChzLDEscil9cmV0dXJuIHRoaXN9Y3Jvc3NGYWRlVG8odCxyLG4pe3JldHVybiB0LmNyb3NzRmFkZUZyb20odGhpcyxyLG4pfXN0b3BGYWRpbmcoKXtsZXQgdD10aGlzLl93ZWlnaHRJbnRlcnBvbGFudDtyZXR1cm4gdCE9PW51bGwmJih0aGlzLl93ZWlnaHRJbnRlcnBvbGFudD1udWxsLHRoaXMuX21peGVyLl90YWtlQmFja0NvbnRyb2xJbnRlcnBvbGFudCh0KSksdGhpc31zZXRFZmZlY3RpdmVUaW1lU2NhbGUodCl7cmV0dXJuIHRoaXMudGltZVNjYWxlPXQsdGhpcy5fZWZmZWN0aXZlVGltZVNjYWxlPXRoaXMucGF1c2VkPzA6dCx0aGlzLnN0b3BXYXJwaW5nKCl9Z2V0RWZmZWN0aXZlVGltZVNjYWxlKCl7cmV0dXJuIHRoaXMuX2VmZmVjdGl2ZVRpbWVTY2FsZX1zZXREdXJhdGlvbih0KXtyZXR1cm4gdGhpcy50aW1lU2NhbGU9dGhpcy5fY2xpcC5kdXJhdGlvbi90LHRoaXMuc3RvcFdhcnBpbmcoKX1zeW5jV2l0aCh0KXtyZXR1cm4gdGhpcy50aW1lPXQudGltZSx0aGlzLnRpbWVTY2FsZT10LnRpbWVTY2FsZSx0aGlzLnN0b3BXYXJwaW5nKCl9aGFsdCh0KXtyZXR1cm4gdGhpcy53YXJwKHRoaXMuX2VmZmVjdGl2ZVRpbWVTY2FsZSwwLHQpfXdhcnAodCxyLG4pe2xldCBpPXRoaXMuX21peGVyLG89aS50aW1lLGE9dGhpcy50aW1lU2NhbGUscz10aGlzLl90aW1lU2NhbGVJbnRlcnBvbGFudDtzPT09bnVsbCYmKHM9aS5fbGVuZENvbnRyb2xJbnRlcnBvbGFudCgpLHRoaXMuX3RpbWVTY2FsZUludGVycG9sYW50PXMpO2xldCBsPXMucGFyYW1ldGVyUG9zaXRpb25zLGM9cy5zYW1wbGVWYWx1ZXM7cmV0dXJuIGxbMF09byxsWzFdPW8rbixjWzBdPXQvYSxjWzFdPXIvYSx0aGlzfXN0b3BXYXJwaW5nKCl7bGV0IHQ9dGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ7cmV0dXJuIHQhPT1udWxsJiYodGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ9bnVsbCx0aGlzLl9taXhlci5fdGFrZUJhY2tDb250cm9sSW50ZXJwb2xhbnQodCkpLHRoaXN9Z2V0TWl4ZXIoKXtyZXR1cm4gdGhpcy5fbWl4ZXJ9Z2V0Q2xpcCgpe3JldHVybiB0aGlzLl9jbGlwfWdldFJvb3QoKXtyZXR1cm4gdGhpcy5fbG9jYWxSb290fHx0aGlzLl9taXhlci5fcm9vdH1fdXBkYXRlKHQscixuLGkpe2lmKCF0aGlzLmVuYWJsZWQpe3RoaXMuX3VwZGF0ZVdlaWdodCh0KTtyZXR1cm59bGV0IG89dGhpcy5fc3RhcnRUaW1lO2lmKG8hPT1udWxsKXtsZXQgbD0odC1vKSpuO2lmKGw8MHx8bj09PTApcmV0dXJuO3RoaXMuX3N0YXJ0VGltZT1udWxsLHI9bipsfXIqPXRoaXMuX3VwZGF0ZVRpbWVTY2FsZSh0KTtsZXQgYT10aGlzLl91cGRhdGVUaW1lKHIpLHM9dGhpcy5fdXBkYXRlV2VpZ2h0KHQpO2lmKHM+MCl7bGV0IGw9dGhpcy5faW50ZXJwb2xhbnRzLGM9dGhpcy5fcHJvcGVydHlCaW5kaW5ncztzd2l0Y2godGhpcy5ibGVuZE1vZGUpe2Nhc2UgUmh0OmZvcihsZXQgdT0wLGg9bC5sZW5ndGg7dSE9PWg7Kyt1KWxbdV0uZXZhbHVhdGUoYSksY1t1XS5hY2N1bXVsYXRlQWRkaXRpdmUocyk7YnJlYWs7Y2FzZSBYVTpkZWZhdWx0OmZvcihsZXQgdT0wLGg9bC5sZW5ndGg7dSE9PWg7Kyt1KWxbdV0uZXZhbHVhdGUoYSksY1t1XS5hY2N1bXVsYXRlKGkscyl9fX1fdXBkYXRlV2VpZ2h0KHQpe2xldCByPTA7aWYodGhpcy5lbmFibGVkKXtyPXRoaXMud2VpZ2h0O2xldCBuPXRoaXMuX3dlaWdodEludGVycG9sYW50O2lmKG4hPT1udWxsKXtsZXQgaT1uLmV2YWx1YXRlKHQpWzBdO3IqPWksdD5uLnBhcmFtZXRlclBvc2l0aW9uc1sxXSYmKHRoaXMuc3RvcEZhZGluZygpLGk9PT0wJiYodGhpcy5lbmFibGVkPSExKSl9fXJldHVybiB0aGlzLl9lZmZlY3RpdmVXZWlnaHQ9cixyfV91cGRhdGVUaW1lU2NhbGUodCl7bGV0IHI9MDtpZighdGhpcy5wYXVzZWQpe3I9dGhpcy50aW1lU2NhbGU7bGV0IG49dGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ7biE9PW51bGwmJihyKj1uLmV2YWx1YXRlKHQpWzBdLHQ+bi5wYXJhbWV0ZXJQb3NpdGlvbnNbMV0mJih0aGlzLnN0b3BXYXJwaW5nKCkscj09PTA/dGhpcy5wYXVzZWQ9ITA6dGhpcy50aW1lU2NhbGU9cikpfXJldHVybiB0aGlzLl9lZmZlY3RpdmVUaW1lU2NhbGU9cixyfV91cGRhdGVUaW1lKHQpe2xldCByPXRoaXMuX2NsaXAuZHVyYXRpb24sbj10aGlzLmxvb3AsaT10aGlzLnRpbWUrdCxvPXRoaXMuX2xvb3BDb3VudCxhPW49PT1UZmU7aWYodD09PTApcmV0dXJuIG89PT0tMT9pOmEmJihvJjEpPT09MT9yLWk6aTtpZihuPT09TWZlKXtvPT09LTEmJih0aGlzLl9sb29wQ291bnQ9MCx0aGlzLl9zZXRFbmRpbmdzKCEwLCEwLCExKSk7dDp7aWYoaT49cilpPXI7ZWxzZSBpZihpPDApaT0wO2Vsc2V7dGhpcy50aW1lPWk7YnJlYWsgdH10aGlzLmNsYW1wV2hlbkZpbmlzaGVkP3RoaXMucGF1c2VkPSEwOnRoaXMuZW5hYmxlZD0hMSx0aGlzLnRpbWU9aSx0aGlzLl9taXhlci5kaXNwYXRjaEV2ZW50KHt0eXBlOiJmaW5pc2hlZCIsYWN0aW9uOnRoaXMsZGlyZWN0aW9uOnQ8MD8tMToxfSl9fWVsc2V7aWYobz09PS0xJiYodD49MD8obz0wLHRoaXMuX3NldEVuZGluZ3MoITAsdGhpcy5yZXBldGl0aW9ucz09PTAsYSkpOnRoaXMuX3NldEVuZGluZ3ModGhpcy5yZXBldGl0aW9ucz09PTAsITAsYSkpLGk+PXJ8fGk8MCl7bGV0IHM9TWF0aC5mbG9vcihpL3IpO2ktPXIqcyxvKz1NYXRoLmFicyhzKTtsZXQgbD10aGlzLnJlcGV0aXRpb25zLW87aWYobDw9MCl0aGlzLmNsYW1wV2hlbkZpbmlzaGVkP3RoaXMucGF1c2VkPSEwOnRoaXMuZW5hYmxlZD0hMSxpPXQ+MD9yOjAsdGhpcy50aW1lPWksdGhpcy5fbWl4ZXIuZGlzcGF0Y2hFdmVudCh7dHlwZToiZmluaXNoZWQiLGFjdGlvbjp0aGlzLGRpcmVjdGlvbjp0PjA/MTotMX0pO2Vsc2V7aWYobD09PTEpe2xldCBjPXQ8MDt0aGlzLl9zZXRFbmRpbmdzKGMsIWMsYSl9ZWxzZSB0aGlzLl9zZXRFbmRpbmdzKCExLCExLGEpO3RoaXMuX2xvb3BDb3VudD1vLHRoaXMudGltZT1pLHRoaXMuX21peGVyLmRpc3BhdGNoRXZlbnQoe3R5cGU6Imxvb3AiLGFjdGlvbjp0aGlzLGxvb3BEZWx0YTpzfSl9fWVsc2UgdGhpcy50aW1lPWk7aWYoYSYmKG8mMSk9PT0xKXJldHVybiByLWl9cmV0dXJuIGl9X3NldEVuZGluZ3ModCxyLG4pe2xldCBpPXRoaXMuX2ludGVycG9sYW50U2V0dGluZ3M7bj8oaS5lbmRpbmdTdGFydD1UdixpLmVuZGluZ0VuZD1Udik6KHQ/aS5lbmRpbmdTdGFydD10aGlzLnplcm9TbG9wZUF0U3RhcnQ/VHY6RXY6aS5lbmRpbmdTdGFydD1aUCxyP2kuZW5kaW5nRW5kPXRoaXMuemVyb1Nsb3BlQXRFbmQ/VHY6RXY6aS5lbmRpbmdFbmQ9WlApfV9zY2hlZHVsZUZhZGluZyh0LHIsbil7bGV0IGk9dGhpcy5fbWl4ZXIsbz1pLnRpbWUsYT10aGlzLl93ZWlnaHRJbnRlcnBvbGFudDthPT09bnVsbCYmKGE9aS5fbGVuZENvbnRyb2xJbnRlcnBvbGFudCgpLHRoaXMuX3dlaWdodEludGVycG9sYW50PWEpO2xldCBzPWEucGFyYW1ldGVyUG9zaXRpb25zLGw9YS5zYW1wbGVWYWx1ZXM7cmV0dXJuIHNbMF09byxsWzBdPXIsc1sxXT1vK3QsbFsxXT1uLHRoaXN9fSxIVT1jbGFzcyBleHRlbmRzIFVze2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5fcm9vdD10LHRoaXMuX2luaXRNZW1vcnlNYW5hZ2VyKCksdGhpcy5fYWNjdUluZGV4PTAsdGhpcy50aW1lPTAsdGhpcy50aW1lU2NhbGU9MX1fYmluZEFjdGlvbih0LHIpe2xldCBuPXQuX2xvY2FsUm9vdHx8dGhpcy5fcm9vdCxpPXQuX2NsaXAudHJhY2tzLG89aS5sZW5ndGgsYT10Ll9wcm9wZXJ0eUJpbmRpbmdzLHM9dC5faW50ZXJwb2xhbnRzLGw9bi51dWlkLGM9dGhpcy5fYmluZGluZ3NCeVJvb3RBbmROYW1lLHU9Y1tsXTt1PT09dm9pZCAwJiYodT17fSxjW2xdPXUpO2ZvcihsZXQgaD0wO2ghPT1vOysraCl7bGV0IGY9aVtoXSxwPWYubmFtZSxkPXVbcF07aWYoZCE9PXZvaWQgMCkrK2QucmVmZXJlbmNlQ291bnQsYVtoXT1kO2Vsc2V7aWYoZD1hW2hdLGQhPT12b2lkIDApe2QuX2NhY2hlSW5kZXg9PT1udWxsJiYoKytkLnJlZmVyZW5jZUNvdW50LHRoaXMuX2FkZEluYWN0aXZlQmluZGluZyhkLGwscCkpO2NvbnRpbnVlfWxldCBnPXImJnIuX3Byb3BlcnR5QmluZGluZ3NbaF0uYmluZGluZy5wYXJzZWRQYXRoO2Q9bmV3IEZVKENyLmNyZWF0ZShuLHAsZyksZi5WYWx1ZVR5cGVOYW1lLGYuZ2V0VmFsdWVTaXplKCkpLCsrZC5yZWZlcmVuY2VDb3VudCx0aGlzLl9hZGRJbmFjdGl2ZUJpbmRpbmcoZCxsLHApLGFbaF09ZH1zW2hdLnJlc3VsdEJ1ZmZlcj1kLmJ1ZmZlcn19X2FjdGl2YXRlQWN0aW9uKHQpe2lmKCF0aGlzLl9pc0FjdGl2ZUFjdGlvbih0KSl7aWYodC5fY2FjaGVJbmRleD09PW51bGwpe2xldCBuPSh0Ll9sb2NhbFJvb3R8fHRoaXMuX3Jvb3QpLnV1aWQsaT10Ll9jbGlwLnV1aWQsbz10aGlzLl9hY3Rpb25zQnlDbGlwW2ldO3RoaXMuX2JpbmRBY3Rpb24odCxvJiZvLmtub3duQWN0aW9uc1swXSksdGhpcy5fYWRkSW5hY3RpdmVBY3Rpb24odCxpLG4pfWxldCByPXQuX3Byb3BlcnR5QmluZGluZ3M7Zm9yKGxldCBuPTAsaT1yLmxlbmd0aDtuIT09aTsrK24pe2xldCBvPXJbbl07by51c2VDb3VudCsrPT09MCYmKHRoaXMuX2xlbmRCaW5kaW5nKG8pLG8uc2F2ZU9yaWdpbmFsU3RhdGUoKSl9dGhpcy5fbGVuZEFjdGlvbih0KX19X2RlYWN0aXZhdGVBY3Rpb24odCl7aWYodGhpcy5faXNBY3RpdmVBY3Rpb24odCkpe2xldCByPXQuX3Byb3BlcnR5QmluZGluZ3M7Zm9yKGxldCBuPTAsaT1yLmxlbmd0aDtuIT09aTsrK24pe2xldCBvPXJbbl07LS1vLnVzZUNvdW50PT09MCYmKG8ucmVzdG9yZU9yaWdpbmFsU3RhdGUoKSx0aGlzLl90YWtlQmFja0JpbmRpbmcobykpfXRoaXMuX3Rha2VCYWNrQWN0aW9uKHQpfX1faW5pdE1lbW9yeU1hbmFnZXIoKXt0aGlzLl9hY3Rpb25zPVtdLHRoaXMuX25BY3RpdmVBY3Rpb25zPTAsdGhpcy5fYWN0aW9uc0J5Q2xpcD17fSx0aGlzLl9iaW5kaW5ncz1bXSx0aGlzLl9uQWN0aXZlQmluZGluZ3M9MCx0aGlzLl9iaW5kaW5nc0J5Um9vdEFuZE5hbWU9e30sdGhpcy5fY29udHJvbEludGVycG9sYW50cz1bXSx0aGlzLl9uQWN0aXZlQ29udHJvbEludGVycG9sYW50cz0wO2xldCB0PXRoaXM7dGhpcy5zdGF0cz17YWN0aW9uczp7Z2V0IHRvdGFsKCl7cmV0dXJuIHQuX2FjdGlvbnMubGVuZ3RofSxnZXQgaW5Vc2UoKXtyZXR1cm4gdC5fbkFjdGl2ZUFjdGlvbnN9fSxiaW5kaW5nczp7Z2V0IHRvdGFsKCl7cmV0dXJuIHQuX2JpbmRpbmdzLmxlbmd0aH0sZ2V0IGluVXNlKCl7cmV0dXJuIHQuX25BY3RpdmVCaW5kaW5nc319LGNvbnRyb2xJbnRlcnBvbGFudHM6e2dldCB0b3RhbCgpe3JldHVybiB0Ll9jb250cm9sSW50ZXJwb2xhbnRzLmxlbmd0aH0sZ2V0IGluVXNlKCl7cmV0dXJuIHQuX25BY3RpdmVDb250cm9sSW50ZXJwb2xhbnRzfX19fV9pc0FjdGl2ZUFjdGlvbih0KXtsZXQgcj10Ll9jYWNoZUluZGV4O3JldHVybiByIT09bnVsbCYmcjx0aGlzLl9uQWN0aXZlQWN0aW9uc31fYWRkSW5hY3RpdmVBY3Rpb24odCxyLG4pe2xldCBpPXRoaXMuX2FjdGlvbnMsbz10aGlzLl9hY3Rpb25zQnlDbGlwLGE9b1tyXTtpZihhPT09dm9pZCAwKWE9e2tub3duQWN0aW9uczpbdF0sYWN0aW9uQnlSb290Ont9fSx0Ll9ieUNsaXBDYWNoZUluZGV4PTAsb1tyXT1hO2Vsc2V7bGV0IHM9YS5rbm93bkFjdGlvbnM7dC5fYnlDbGlwQ2FjaGVJbmRleD1zLmxlbmd0aCxzLnB1c2godCl9dC5fY2FjaGVJbmRleD1pLmxlbmd0aCxpLnB1c2godCksYS5hY3Rpb25CeVJvb3Rbbl09dH1fcmVtb3ZlSW5hY3RpdmVBY3Rpb24odCl7bGV0IHI9dGhpcy5fYWN0aW9ucyxuPXJbci5sZW5ndGgtMV0saT10Ll9jYWNoZUluZGV4O24uX2NhY2hlSW5kZXg9aSxyW2ldPW4sci5wb3AoKSx0Ll9jYWNoZUluZGV4PW51bGw7bGV0IG89dC5fY2xpcC51dWlkLGE9dGhpcy5fYWN0aW9uc0J5Q2xpcCxzPWFbb10sbD1zLmtub3duQWN0aW9ucyxjPWxbbC5sZW5ndGgtMV0sdT10Ll9ieUNsaXBDYWNoZUluZGV4O2MuX2J5Q2xpcENhY2hlSW5kZXg9dSxsW3VdPWMsbC5wb3AoKSx0Ll9ieUNsaXBDYWNoZUluZGV4PW51bGw7bGV0IGg9cy5hY3Rpb25CeVJvb3QsZj0odC5fbG9jYWxSb290fHx0aGlzLl9yb290KS51dWlkO2RlbGV0ZSBoW2ZdLGwubGVuZ3RoPT09MCYmZGVsZXRlIGFbb10sdGhpcy5fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nc0ZvckFjdGlvbih0KX1fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nc0ZvckFjdGlvbih0KXtsZXQgcj10Ll9wcm9wZXJ0eUJpbmRpbmdzO2ZvcihsZXQgbj0wLGk9ci5sZW5ndGg7biE9PWk7KytuKXtsZXQgbz1yW25dOy0tby5yZWZlcmVuY2VDb3VudD09PTAmJnRoaXMuX3JlbW92ZUluYWN0aXZlQmluZGluZyhvKX19X2xlbmRBY3Rpb24odCl7bGV0IHI9dGhpcy5fYWN0aW9ucyxuPXQuX2NhY2hlSW5kZXgsaT10aGlzLl9uQWN0aXZlQWN0aW9ucysrLG89cltpXTt0Ll9jYWNoZUluZGV4PWkscltpXT10LG8uX2NhY2hlSW5kZXg9bixyW25dPW99X3Rha2VCYWNrQWN0aW9uKHQpe2xldCByPXRoaXMuX2FjdGlvbnMsbj10Ll9jYWNoZUluZGV4LGk9LS10aGlzLl9uQWN0aXZlQWN0aW9ucyxvPXJbaV07dC5fY2FjaGVJbmRleD1pLHJbaV09dCxvLl9jYWNoZUluZGV4PW4scltuXT1vfV9hZGRJbmFjdGl2ZUJpbmRpbmcodCxyLG4pe2xldCBpPXRoaXMuX2JpbmRpbmdzQnlSb290QW5kTmFtZSxvPXRoaXMuX2JpbmRpbmdzLGE9aVtyXTthPT09dm9pZCAwJiYoYT17fSxpW3JdPWEpLGFbbl09dCx0Ll9jYWNoZUluZGV4PW8ubGVuZ3RoLG8ucHVzaCh0KX1fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nKHQpe2xldCByPXRoaXMuX2JpbmRpbmdzLG49dC5iaW5kaW5nLGk9bi5yb290Tm9kZS51dWlkLG89bi5wYXRoLGE9dGhpcy5fYmluZGluZ3NCeVJvb3RBbmROYW1lLHM9YVtpXSxsPXJbci5sZW5ndGgtMV0sYz10Ll9jYWNoZUluZGV4O2wuX2NhY2hlSW5kZXg9YyxyW2NdPWwsci5wb3AoKSxkZWxldGUgc1tvXSxPYmplY3Qua2V5cyhzKS5sZW5ndGg9PT0wJiZkZWxldGUgYVtpXX1fbGVuZEJpbmRpbmcodCl7bGV0IHI9dGhpcy5fYmluZGluZ3Msbj10Ll9jYWNoZUluZGV4LGk9dGhpcy5fbkFjdGl2ZUJpbmRpbmdzKyssbz1yW2ldO3QuX2NhY2hlSW5kZXg9aSxyW2ldPXQsby5fY2FjaGVJbmRleD1uLHJbbl09b31fdGFrZUJhY2tCaW5kaW5nKHQpe2xldCByPXRoaXMuX2JpbmRpbmdzLG49dC5fY2FjaGVJbmRleCxpPS0tdGhpcy5fbkFjdGl2ZUJpbmRpbmdzLG89cltpXTt0Ll9jYWNoZUluZGV4PWkscltpXT10LG8uX2NhY2hlSW5kZXg9bixyW25dPW99X2xlbmRDb250cm9sSW50ZXJwb2xhbnQoKXtsZXQgdD10aGlzLl9jb250cm9sSW50ZXJwb2xhbnRzLHI9dGhpcy5fbkFjdGl2ZUNvbnRyb2xJbnRlcnBvbGFudHMrKyxuPXRbcl07cmV0dXJuIG49PT12b2lkIDAmJihuPW5ldyB3NihuZXcgRmxvYXQzMkFycmF5KDIpLG5ldyBGbG9hdDMyQXJyYXkoMiksMSx0aGlzLl9jb250cm9sSW50ZXJwb2xhbnRzUmVzdWx0QnVmZmVyKSxuLl9fY2FjaGVJbmRleD1yLHRbcl09biksbn1fdGFrZUJhY2tDb250cm9sSW50ZXJwb2xhbnQodCl7bGV0IHI9dGhpcy5fY29udHJvbEludGVycG9sYW50cyxuPXQuX19jYWNoZUluZGV4LGk9LS10aGlzLl9uQWN0aXZlQ29udHJvbEludGVycG9sYW50cyxvPXJbaV07dC5fX2NhY2hlSW5kZXg9aSxyW2ldPXQsby5fX2NhY2hlSW5kZXg9bixyW25dPW99Y2xpcEFjdGlvbih0LHIsbil7bGV0IGk9cnx8dGhpcy5fcm9vdCxvPWkudXVpZCxhPXR5cGVvZiB0PT0ic3RyaW5nIj9Rdi5maW5kQnlOYW1lKGksdCk6dCxzPWEhPT1udWxsP2EudXVpZDp0LGw9dGhpcy5fYWN0aW9uc0J5Q2xpcFtzXSxjPW51bGw7aWYobj09PXZvaWQgMCYmKGEhPT1udWxsP249YS5ibGVuZE1vZGU6bj1YVSksbCE9PXZvaWQgMCl7bGV0IGg9bC5hY3Rpb25CeVJvb3Rbb107aWYoaCE9PXZvaWQgMCYmaC5ibGVuZE1vZGU9PT1uKXJldHVybiBoO2M9bC5rbm93bkFjdGlvbnNbMF0sYT09PW51bGwmJihhPWMuX2NsaXApfWlmKGE9PT1udWxsKXJldHVybiBudWxsO2xldCB1PW5ldyBwaHQodGhpcyxhLHIsbik7cmV0dXJuIHRoaXMuX2JpbmRBY3Rpb24odSxjKSx0aGlzLl9hZGRJbmFjdGl2ZUFjdGlvbih1LHMsbyksdX1leGlzdGluZ0FjdGlvbih0LHIpe2xldCBuPXJ8fHRoaXMuX3Jvb3QsaT1uLnV1aWQsbz10eXBlb2YgdD09InN0cmluZyI/UXYuZmluZEJ5TmFtZShuLHQpOnQsYT1vP28udXVpZDp0LHM9dGhpcy5fYWN0aW9uc0J5Q2xpcFthXTtyZXR1cm4gcyE9PXZvaWQgMCYmcy5hY3Rpb25CeVJvb3RbaV18fG51bGx9c3RvcEFsbEFjdGlvbigpe2xldCB0PXRoaXMuX2FjdGlvbnMscj10aGlzLl9uQWN0aXZlQWN0aW9ucztmb3IobGV0IG49ci0xO24+PTA7LS1uKXRbbl0uc3RvcCgpO3JldHVybiB0aGlzfXVwZGF0ZSh0KXt0Kj10aGlzLnRpbWVTY2FsZTtsZXQgcj10aGlzLl9hY3Rpb25zLG49dGhpcy5fbkFjdGl2ZUFjdGlvbnMsaT10aGlzLnRpbWUrPXQsbz1NYXRoLnNpZ24odCksYT10aGlzLl9hY2N1SW5kZXhePTE7Zm9yKGxldCBjPTA7YyE9PW47KytjKXJbY10uX3VwZGF0ZShpLHQsbyxhKTtsZXQgcz10aGlzLl9iaW5kaW5ncyxsPXRoaXMuX25BY3RpdmVCaW5kaW5ncztmb3IobGV0IGM9MDtjIT09bDsrK2Mpc1tjXS5hcHBseShhKTtyZXR1cm4gdGhpc31zZXRUaW1lKHQpe3RoaXMudGltZT0wO2ZvcihsZXQgcj0wO3I8dGhpcy5fYWN0aW9ucy5sZW5ndGg7cisrKXRoaXMuX2FjdGlvbnNbcl0udGltZT0wO3JldHVybiB0aGlzLnVwZGF0ZSh0KX1nZXRSb290KCl7cmV0dXJuIHRoaXMuX3Jvb3R9dW5jYWNoZUNsaXAodCl7bGV0IHI9dGhpcy5fYWN0aW9ucyxuPXQudXVpZCxpPXRoaXMuX2FjdGlvbnNCeUNsaXAsbz1pW25dO2lmKG8hPT12b2lkIDApe2xldCBhPW8ua25vd25BY3Rpb25zO2ZvcihsZXQgcz0wLGw9YS5sZW5ndGg7cyE9PWw7KytzKXtsZXQgYz1hW3NdO3RoaXMuX2RlYWN0aXZhdGVBY3Rpb24oYyk7bGV0IHU9Yy5fY2FjaGVJbmRleCxoPXJbci5sZW5ndGgtMV07Yy5fY2FjaGVJbmRleD1udWxsLGMuX2J5Q2xpcENhY2hlSW5kZXg9bnVsbCxoLl9jYWNoZUluZGV4PXUsclt1XT1oLHIucG9wKCksdGhpcy5fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nc0ZvckFjdGlvbihjKX1kZWxldGUgaVtuXX19dW5jYWNoZVJvb3QodCl7bGV0IHI9dC51dWlkLG49dGhpcy5fYWN0aW9uc0J5Q2xpcDtmb3IobGV0IGEgaW4gbil7bGV0IHM9blthXS5hY3Rpb25CeVJvb3QsbD1zW3JdO2whPT12b2lkIDAmJih0aGlzLl9kZWFjdGl2YXRlQWN0aW9uKGwpLHRoaXMuX3JlbW92ZUluYWN0aXZlQWN0aW9uKGwpKX1sZXQgaT10aGlzLl9iaW5kaW5nc0J5Um9vdEFuZE5hbWUsbz1pW3JdO2lmKG8hPT12b2lkIDApZm9yKGxldCBhIGluIG8pe2xldCBzPW9bYV07cy5yZXN0b3JlT3JpZ2luYWxTdGF0ZSgpLHRoaXMuX3JlbW92ZUluYWN0aXZlQmluZGluZyhzKX19dW5jYWNoZUFjdGlvbih0LHIpe2xldCBuPXRoaXMuZXhpc3RpbmdBY3Rpb24odCxyKTtuIT09bnVsbCYmKHRoaXMuX2RlYWN0aXZhdGVBY3Rpb24obiksdGhpcy5fcmVtb3ZlSW5hY3RpdmVBY3Rpb24obikpfX07SFUucHJvdG90eXBlLl9jb250cm9sSW50ZXJwb2xhbnRzUmVzdWx0QnVmZmVyPW5ldyBGbG9hdDMyQXJyYXkoMSk7dmFyIGdNPWNsYXNze2NvbnN0cnVjdG9yKHQpe3R5cGVvZiB0PT0ic3RyaW5nIiYmKGNvbnNvbGUud2FybigiVEhSRUUuVW5pZm9ybTogVHlwZSBwYXJhbWV0ZXIgaXMgbm8gbG9uZ2VyIG5lZWRlZC4iKSx0PWFyZ3VtZW50c1sxXSksdGhpcy52YWx1ZT10fWNsb25lKCl7cmV0dXJuIG5ldyBnTSh0aGlzLnZhbHVlLmNsb25lPT09dm9pZCAwP3RoaXMudmFsdWU6dGhpcy52YWx1ZS5jbG9uZSgpKX19LFZVPWNsYXNzIGV4dGVuZHMgZW17Y29uc3RydWN0b3IodCxyLG49MSl7c3VwZXIodCxyKSx0aGlzLm1lc2hQZXJBdHRyaWJ1dGU9bn1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMubWVzaFBlckF0dHJpYnV0ZT10Lm1lc2hQZXJBdHRyaWJ1dGUsdGhpc31jbG9uZSh0KXtsZXQgcj1zdXBlci5jbG9uZSh0KTtyZXR1cm4gci5tZXNoUGVyQXR0cmlidXRlPXRoaXMubWVzaFBlckF0dHJpYnV0ZSxyfXRvSlNPTih0KXtsZXQgcj1zdXBlci50b0pTT04odCk7cmV0dXJuIHIuaXNJbnN0YW5jZWRJbnRlcmxlYXZlZEJ1ZmZlcj0hMCxyLm1lc2hQZXJBdHRyaWJ1dGU9dGhpcy5tZXNoUGVyQXR0cmlidXRlLHJ9fTtWVS5wcm90b3R5cGUuaXNJbnN0YW5jZWRJbnRlcmxlYXZlZEJ1ZmZlcj0hMDt2YXIgVVU9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG4saSxvKXt0aGlzLmJ1ZmZlcj10LHRoaXMudHlwZT1yLHRoaXMuaXRlbVNpemU9bix0aGlzLmVsZW1lbnRTaXplPWksdGhpcy5jb3VudD1vLHRoaXMudmVyc2lvbj0wfXNldCBuZWVkc1VwZGF0ZSh0KXt0PT09ITAmJnRoaXMudmVyc2lvbisrfXNldEJ1ZmZlcih0KXtyZXR1cm4gdGhpcy5idWZmZXI9dCx0aGlzfXNldFR5cGUodCxyKXtyZXR1cm4gdGhpcy50eXBlPXQsdGhpcy5lbGVtZW50U2l6ZT1yLHRoaXN9c2V0SXRlbVNpemUodCl7cmV0dXJuIHRoaXMuaXRlbVNpemU9dCx0aGlzfXNldENvdW50KHQpe3JldHVybiB0aGlzLmNvdW50PXQsdGhpc319O1VVLnByb3RvdHlwZS5pc0dMQnVmZmVyQXR0cmlidXRlPSEwO3ZhciBkaHQ9Y2xhc3N7Y29uc3RydWN0b3IodCxyLG49MCxpPTEvMCl7dGhpcy5yYXk9bmV3IEpmKHQsciksdGhpcy5uZWFyPW4sdGhpcy5mYXI9aSx0aGlzLmNhbWVyYT1udWxsLHRoaXMubGF5ZXJzPW5ldyBYMyx0aGlzLnBhcmFtcz17TWVzaDp7fSxMaW5lOnt0aHJlc2hvbGQ6MX0sTE9EOnt9LFBvaW50czp7dGhyZXNob2xkOjF9LFNwcml0ZTp7fX19c2V0KHQscil7dGhpcy5yYXkuc2V0KHQscil9c2V0RnJvbUNhbWVyYSh0LHIpe3ImJnIuaXNQZXJzcGVjdGl2ZUNhbWVyYT8odGhpcy5yYXkub3JpZ2luLnNldEZyb21NYXRyaXhQb3NpdGlvbihyLm1hdHJpeFdvcmxkKSx0aGlzLnJheS5kaXJlY3Rpb24uc2V0KHQueCx0LnksLjUpLnVucHJvamVjdChyKS5zdWIodGhpcy5yYXkub3JpZ2luKS5ub3JtYWxpemUoKSx0aGlzLmNhbWVyYT1yKTpyJiZyLmlzT3J0aG9ncmFwaGljQ2FtZXJhPyh0aGlzLnJheS5vcmlnaW4uc2V0KHQueCx0LnksKHIubmVhcityLmZhcikvKHIubmVhci1yLmZhcikpLnVucHJvamVjdChyKSx0aGlzLnJheS5kaXJlY3Rpb24uc2V0KDAsMCwtMSkudHJhbnNmb3JtRGlyZWN0aW9uKHIubWF0cml4V29ybGQpLHRoaXMuY2FtZXJhPXIpOmNvbnNvbGUuZXJyb3IoIlRIUkVFLlJheWNhc3RlcjogVW5zdXBwb3J0ZWQgY2FtZXJhIHR5cGU6ICIrci50eXBlKX1pbnRlcnNlY3RPYmplY3QodCxyPSEwLG49W10pe3JldHVybiBtaHQodCx0aGlzLG4sciksbi5zb3J0KFRoZSksbn1pbnRlcnNlY3RPYmplY3RzKHQscj0hMCxuPVtdKXtmb3IobGV0IGk9MCxvPXQubGVuZ3RoO2k8bztpKyspbWh0KHRbaV0sdGhpcyxuLHIpO3JldHVybiBuLnNvcnQoVGhlKSxufX07ZnVuY3Rpb24gVGhlKGUsdCl7cmV0dXJuIGUuZGlzdGFuY2UtdC5kaXN0YW5jZX1mdW5jdGlvbiBtaHQoZSx0LHIsbil7aWYoZS5sYXllcnMudGVzdCh0LmxheWVycykmJmUucmF5Y2FzdCh0LHIpLG49PT0hMCl7bGV0IGk9ZS5jaGlsZHJlbjtmb3IobGV0IG89MCxhPWkubGVuZ3RoO288YTtvKyspbWh0KGlbb10sdCxyLCEwKX19dmFyIF9NPWNsYXNze2NvbnN0cnVjdG9yKHQ9MSxyPTAsbj0wKXtyZXR1cm4gdGhpcy5yYWRpdXM9dCx0aGlzLnBoaT1yLHRoaXMudGhldGE9bix0aGlzfXNldCh0LHIsbil7cmV0dXJuIHRoaXMucmFkaXVzPXQsdGhpcy5waGk9cix0aGlzLnRoZXRhPW4sdGhpc31jb3B5KHQpe3JldHVybiB0aGlzLnJhZGl1cz10LnJhZGl1cyx0aGlzLnBoaT10LnBoaSx0aGlzLnRoZXRhPXQudGhldGEsdGhpc31tYWtlU2FmZSgpe3JldHVybiB0aGlzLnBoaT1NYXRoLm1heCgxZS02LE1hdGgubWluKE1hdGguUEktMWUtNix0aGlzLnBoaSkpLHRoaXN9c2V0RnJvbVZlY3RvcjModCl7cmV0dXJuIHRoaXMuc2V0RnJvbUNhcnRlc2lhbkNvb3Jkcyh0LngsdC55LHQueil9c2V0RnJvbUNhcnRlc2lhbkNvb3Jkcyh0LHIsbil7cmV0dXJuIHRoaXMucmFkaXVzPU1hdGguc3FydCh0KnQrcipyK24qbiksdGhpcy5yYWRpdXM9PT0wPyh0aGlzLnRoZXRhPTAsdGhpcy5waGk9MCk6KHRoaXMudGhldGE9TWF0aC5hdGFuMih0LG4pLHRoaXMucGhpPU1hdGguYWNvcyhabyhyL3RoaXMucmFkaXVzLC0xLDEpKSksdGhpc31jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3RvcigpLmNvcHkodGhpcyl9fSxnaHQ9Y2xhc3N7Y29uc3RydWN0b3IodD0xLHI9MCxuPTApe3JldHVybiB0aGlzLnJhZGl1cz10LHRoaXMudGhldGE9cix0aGlzLnk9bix0aGlzfXNldCh0LHIsbil7cmV0dXJuIHRoaXMucmFkaXVzPXQsdGhpcy50aGV0YT1yLHRoaXMueT1uLHRoaXN9Y29weSh0KXtyZXR1cm4gdGhpcy5yYWRpdXM9dC5yYWRpdXMsdGhpcy50aGV0YT10LnRoZXRhLHRoaXMueT10LnksdGhpc31zZXRGcm9tVmVjdG9yMyh0KXtyZXR1cm4gdGhpcy5zZXRGcm9tQ2FydGVzaWFuQ29vcmRzKHQueCx0LnksdC56KX1zZXRGcm9tQ2FydGVzaWFuQ29vcmRzKHQscixuKXtyZXR1cm4gdGhpcy5yYWRpdXM9TWF0aC5zcXJ0KHQqdCtuKm4pLHRoaXMudGhldGE9TWF0aC5hdGFuMih0LG4pLHRoaXMueT1yLHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfX0sQ2hlPW5ldyBMdCwkMD1jbGFzc3tjb25zdHJ1Y3Rvcih0PW5ldyBMdCgxLzAsMS8wKSxyPW5ldyBMdCgtMS8wLC0xLzApKXt0aGlzLm1pbj10LHRoaXMubWF4PXJ9c2V0KHQscil7cmV0dXJuIHRoaXMubWluLmNvcHkodCksdGhpcy5tYXguY29weShyKSx0aGlzfXNldEZyb21Qb2ludHModCl7dGhpcy5tYWtlRW1wdHkoKTtmb3IobGV0IHI9MCxuPXQubGVuZ3RoO3I8bjtyKyspdGhpcy5leHBhbmRCeVBvaW50KHRbcl0pO3JldHVybiB0aGlzfXNldEZyb21DZW50ZXJBbmRTaXplKHQscil7bGV0IG49Q2hlLmNvcHkocikubXVsdGlwbHlTY2FsYXIoLjUpO3JldHVybiB0aGlzLm1pbi5jb3B5KHQpLnN1YihuKSx0aGlzLm1heC5jb3B5KHQpLmFkZChuKSx0aGlzfWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKCkuY29weSh0aGlzKX1jb3B5KHQpe3JldHVybiB0aGlzLm1pbi5jb3B5KHQubWluKSx0aGlzLm1heC5jb3B5KHQubWF4KSx0aGlzfW1ha2VFbXB0eSgpe3JldHVybiB0aGlzLm1pbi54PXRoaXMubWluLnk9MS8wLHRoaXMubWF4Lng9dGhpcy5tYXgueT0tMS8wLHRoaXN9aXNFbXB0eSgpe3JldHVybiB0aGlzLm1heC54PHRoaXMubWluLnh8fHRoaXMubWF4Lnk8dGhpcy5taW4ueX1nZXRDZW50ZXIodCl7cmV0dXJuIHRoaXMuaXNFbXB0eSgpP3Quc2V0KDAsMCk6dC5hZGRWZWN0b3JzKHRoaXMubWluLHRoaXMubWF4KS5tdWx0aXBseVNjYWxhciguNSl9Z2V0U2l6ZSh0KXtyZXR1cm4gdGhpcy5pc0VtcHR5KCk/dC5zZXQoMCwwKTp0LnN1YlZlY3RvcnModGhpcy5tYXgsdGhpcy5taW4pfWV4cGFuZEJ5UG9pbnQodCl7cmV0dXJuIHRoaXMubWluLm1pbih0KSx0aGlzLm1heC5tYXgodCksdGhpc31leHBhbmRCeVZlY3Rvcih0KXtyZXR1cm4gdGhpcy5taW4uc3ViKHQpLHRoaXMubWF4LmFkZCh0KSx0aGlzfWV4cGFuZEJ5U2NhbGFyKHQpe3JldHVybiB0aGlzLm1pbi5hZGRTY2FsYXIoLXQpLHRoaXMubWF4LmFkZFNjYWxhcih0KSx0aGlzfWNvbnRhaW5zUG9pbnQodCl7cmV0dXJuISh0Lng8dGhpcy5taW4ueHx8dC54PnRoaXMubWF4Lnh8fHQueTx0aGlzLm1pbi55fHx0Lnk+dGhpcy5tYXgueSl9Y29udGFpbnNCb3godCl7cmV0dXJuIHRoaXMubWluLng8PXQubWluLngmJnQubWF4Lng8PXRoaXMubWF4LngmJnRoaXMubWluLnk8PXQubWluLnkmJnQubWF4Lnk8PXRoaXMubWF4Lnl9Z2V0UGFyYW1ldGVyKHQscil7cmV0dXJuIHIuc2V0KCh0LngtdGhpcy5taW4ueCkvKHRoaXMubWF4LngtdGhpcy5taW4ueCksKHQueS10aGlzLm1pbi55KS8odGhpcy5tYXgueS10aGlzLm1pbi55KSl9aW50ZXJzZWN0c0JveCh0KXtyZXR1cm4hKHQubWF4Lng8dGhpcy5taW4ueHx8dC5taW4ueD50aGlzLm1heC54fHx0Lm1heC55PHRoaXMubWluLnl8fHQubWluLnk+dGhpcy5tYXgueSl9Y2xhbXBQb2ludCh0LHIpe3JldHVybiByLmNvcHkodCkuY2xhbXAodGhpcy5taW4sdGhpcy5tYXgpfWRpc3RhbmNlVG9Qb2ludCh0KXtyZXR1cm4gQ2hlLmNvcHkodCkuY2xhbXAodGhpcy5taW4sdGhpcy5tYXgpLnN1Yih0KS5sZW5ndGgoKX1pbnRlcnNlY3QodCl7cmV0dXJuIHRoaXMubWluLm1heCh0Lm1pbiksdGhpcy5tYXgubWluKHQubWF4KSx0aGlzfXVuaW9uKHQpe3JldHVybiB0aGlzLm1pbi5taW4odC5taW4pLHRoaXMubWF4Lm1heCh0Lm1heCksdGhpc310cmFuc2xhdGUodCl7cmV0dXJuIHRoaXMubWluLmFkZCh0KSx0aGlzLm1heC5hZGQodCksdGhpc31lcXVhbHModCl7cmV0dXJuIHQubWluLmVxdWFscyh0aGlzLm1pbikmJnQubWF4LmVxdWFscyh0aGlzLm1heCl9fTskMC5wcm90b3R5cGUuaXNCb3gyPSEwO3ZhciBBaGU9bmV3IGosV1Y9bmV3IGoscVU9Y2xhc3N7Y29uc3RydWN0b3IodD1uZXcgaixyPW5ldyBqKXt0aGlzLnN0YXJ0PXQsdGhpcy5lbmQ9cn1zZXQodCxyKXtyZXR1cm4gdGhpcy5zdGFydC5jb3B5KHQpLHRoaXMuZW5kLmNvcHkociksdGhpc31jb3B5KHQpe3JldHVybiB0aGlzLnN0YXJ0LmNvcHkodC5zdGFydCksdGhpcy5lbmQuY29weSh0LmVuZCksdGhpc31nZXRDZW50ZXIodCl7cmV0dXJuIHQuYWRkVmVjdG9ycyh0aGlzLnN0YXJ0LHRoaXMuZW5kKS5tdWx0aXBseVNjYWxhciguNSl9ZGVsdGEodCl7cmV0dXJuIHQuc3ViVmVjdG9ycyh0aGlzLmVuZCx0aGlzLnN0YXJ0KX1kaXN0YW5jZVNxKCl7cmV0dXJuIHRoaXMuc3RhcnQuZGlzdGFuY2VUb1NxdWFyZWQodGhpcy5lbmQpfWRpc3RhbmNlKCl7cmV0dXJuIHRoaXMuc3RhcnQuZGlzdGFuY2VUbyh0aGlzLmVuZCl9YXQodCxyKXtyZXR1cm4gdGhpcy5kZWx0YShyKS5tdWx0aXBseVNjYWxhcih0KS5hZGQodGhpcy5zdGFydCl9Y2xvc2VzdFBvaW50VG9Qb2ludFBhcmFtZXRlcih0LHIpe0FoZS5zdWJWZWN0b3JzKHQsdGhpcy5zdGFydCksV1Yuc3ViVmVjdG9ycyh0aGlzLmVuZCx0aGlzLnN0YXJ0KTtsZXQgbj1XVi5kb3QoV1YpLG89V1YuZG90KEFoZSkvbjtyZXR1cm4gciYmKG89Wm8obywwLDEpKSxvfWNsb3Nlc3RQb2ludFRvUG9pbnQodCxyLG4pe2xldCBpPXRoaXMuY2xvc2VzdFBvaW50VG9Qb2ludFBhcmFtZXRlcih0LHIpO3JldHVybiB0aGlzLmRlbHRhKG4pLm11bHRpcGx5U2NhbGFyKGkpLmFkZCh0aGlzLnN0YXJ0KX1hcHBseU1hdHJpeDQodCl7cmV0dXJuIHRoaXMuc3RhcnQuYXBwbHlNYXRyaXg0KHQpLHRoaXMuZW5kLmFwcGx5TWF0cml4NCh0KSx0aGlzfWVxdWFscyh0KXtyZXR1cm4gdC5zdGFydC5lcXVhbHModGhpcy5zdGFydCkmJnQuZW5kLmVxdWFscyh0aGlzLmVuZCl9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IoKS5jb3B5KHRoaXMpfX0sUGhlPW5ldyBqLF9odD1jbGFzcyBleHRlbmRzIG9ye2NvbnN0cnVjdG9yKHQscil7c3VwZXIoKSx0aGlzLmxpZ2h0PXQsdGhpcy5saWdodC51cGRhdGVNYXRyaXhXb3JsZCgpLHRoaXMubWF0cml4PXQubWF0cml4V29ybGQsdGhpcy5tYXRyaXhBdXRvVXBkYXRlPSExLHRoaXMuY29sb3I9cjtsZXQgbj1uZXcgUGUsaT1bMCwwLDAsMCwwLDEsMCwwLDAsMSwwLDEsMCwwLDAsLTEsMCwxLDAsMCwwLDAsMSwxLDAsMCwwLDAsLTEsMV07Zm9yKGxldCBhPTAscz0xLGw9MzI7YTxsO2ErKyxzKyspe2xldCBjPWEvbCpNYXRoLlBJKjIsdT1zL2wqTWF0aC5QSSoyO2kucHVzaChNYXRoLmNvcyhjKSxNYXRoLnNpbihjKSwxLE1hdGguY29zKHUpLE1hdGguc2luKHUpLDEpfW4uc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IHhlKGksMykpO2xldCBvPW5ldyBHaSh7Zm9nOiExLHRvbmVNYXBwZWQ6ITF9KTt0aGlzLmNvbmU9bmV3IEFhKG4sbyksdGhpcy5hZGQodGhpcy5jb25lKSx0aGlzLnVwZGF0ZSgpfWRpc3Bvc2UoKXt0aGlzLmNvbmUuZ2VvbWV0cnkuZGlzcG9zZSgpLHRoaXMuY29uZS5tYXRlcmlhbC5kaXNwb3NlKCl9dXBkYXRlKCl7dGhpcy5saWdodC51cGRhdGVNYXRyaXhXb3JsZCgpO2xldCB0PXRoaXMubGlnaHQuZGlzdGFuY2U/dGhpcy5saWdodC5kaXN0YW5jZToxZTMscj10Kk1hdGgudGFuKHRoaXMubGlnaHQuYW5nbGUpO3RoaXMuY29uZS5zY2FsZS5zZXQocixyLHQpLFBoZS5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5saWdodC50YXJnZXQubWF0cml4V29ybGQpLHRoaXMuY29uZS5sb29rQXQoUGhlKSx0aGlzLmNvbG9yIT09dm9pZCAwP3RoaXMuY29uZS5tYXRlcmlhbC5jb2xvci5zZXQodGhpcy5jb2xvcik6dGhpcy5jb25lLm1hdGVyaWFsLmNvbG9yLmNvcHkodGhpcy5saWdodC5jb2xvcil9fSxPMD1uZXcgaixZVj1uZXcgTWUsd3V0PW5ldyBNZSxHVT1jbGFzcyBleHRlbmRzIEFhe2NvbnN0cnVjdG9yKHQpe2xldCByPXRwZSh0KSxuPW5ldyBQZSxpPVtdLG89W10sYT1uZXcgbmUoMCwwLDEpLHM9bmV3IG5lKDAsMSwwKTtmb3IobGV0IGM9MDtjPHIubGVuZ3RoO2MrKyl7bGV0IHU9cltjXTt1LnBhcmVudCYmdS5wYXJlbnQuaXNCb25lJiYoaS5wdXNoKDAsMCwwKSxpLnB1c2goMCwwLDApLG8ucHVzaChhLnIsYS5nLGEuYiksby5wdXNoKHMucixzLmcscy5iKSl9bi5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUoaSwzKSksbi5zZXRBdHRyaWJ1dGUoImNvbG9yIixuZXcgeGUobywzKSk7bGV0IGw9bmV3IEdpKHt2ZXJ0ZXhDb2xvcnM6ITAsZGVwdGhUZXN0OiExLGRlcHRoV3JpdGU6ITEsdG9uZU1hcHBlZDohMSx0cmFuc3BhcmVudDohMH0pO3N1cGVyKG4sbCksdGhpcy50eXBlPSJTa2VsZXRvbkhlbHBlciIsdGhpcy5pc1NrZWxldG9uSGVscGVyPSEwLHRoaXMucm9vdD10LHRoaXMuYm9uZXM9cix0aGlzLm1hdHJpeD10Lm1hdHJpeFdvcmxkLHRoaXMubWF0cml4QXV0b1VwZGF0ZT0hMX11cGRhdGVNYXRyaXhXb3JsZCh0KXtsZXQgcj10aGlzLmJvbmVzLG49dGhpcy5nZW9tZXRyeSxpPW4uZ2V0QXR0cmlidXRlKCJwb3NpdGlvbiIpO3d1dC5jb3B5KHRoaXMucm9vdC5tYXRyaXhXb3JsZCkuaW52ZXJ0KCk7Zm9yKGxldCBvPTAsYT0wO288ci5sZW5ndGg7bysrKXtsZXQgcz1yW29dO3MucGFyZW50JiZzLnBhcmVudC5pc0JvbmUmJihZVi5tdWx0aXBseU1hdHJpY2VzKHd1dCxzLm1hdHJpeFdvcmxkKSxPMC5zZXRGcm9tTWF0cml4UG9zaXRpb24oWVYpLGkuc2V0WFlaKGEsTzAueCxPMC55LE8wLnopLFlWLm11bHRpcGx5TWF0cmljZXMod3V0LHMucGFyZW50Lm1hdHJpeFdvcmxkKSxPMC5zZXRGcm9tTWF0cml4UG9zaXRpb24oWVYpLGkuc2V0WFlaKGErMSxPMC54LE8wLnksTzAueiksYSs9Mil9bi5nZXRBdHRyaWJ1dGUoInBvc2l0aW9uIikubmVlZHNVcGRhdGU9ITAsc3VwZXIudXBkYXRlTWF0cml4V29ybGQodCl9fTtmdW5jdGlvbiB0cGUoZSl7bGV0IHQ9W107ZSYmZS5pc0JvbmUmJnQucHVzaChlKTtmb3IobGV0IHI9MDtyPGUuY2hpbGRyZW4ubGVuZ3RoO3IrKyl0LnB1c2guYXBwbHkodCx0cGUoZS5jaGlsZHJlbltyXSkpO3JldHVybiB0fXZhciB5aHQ9Y2xhc3MgZXh0ZW5kcyBlaXtjb25zdHJ1Y3Rvcih0LHIsbil7bGV0IGk9bmV3IGowKHIsNCwyKSxvPW5ldyBzaCh7d2lyZWZyYW1lOiEwLGZvZzohMSx0b25lTWFwcGVkOiExfSk7c3VwZXIoaSxvKSx0aGlzLmxpZ2h0PXQsdGhpcy5saWdodC51cGRhdGVNYXRyaXhXb3JsZCgpLHRoaXMuY29sb3I9bix0aGlzLnR5cGU9IlBvaW50TGlnaHRIZWxwZXIiLHRoaXMubWF0cml4PXRoaXMubGlnaHQubWF0cml4V29ybGQsdGhpcy5tYXRyaXhBdXRvVXBkYXRlPSExLHRoaXMudXBkYXRlKCl9ZGlzcG9zZSgpe3RoaXMuZ2VvbWV0cnkuZGlzcG9zZSgpLHRoaXMubWF0ZXJpYWwuZGlzcG9zZSgpfXVwZGF0ZSgpe3RoaXMuY29sb3IhPT12b2lkIDA/dGhpcy5tYXRlcmlhbC5jb2xvci5zZXQodGhpcy5jb2xvcik6dGhpcy5tYXRlcmlhbC5jb2xvci5jb3B5KHRoaXMubGlnaHQuY29sb3IpfX0sdjByPW5ldyBqLEloZT1uZXcgbmUsTGhlPW5ldyBuZSx2aHQ9Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcih0LHIsbil7c3VwZXIoKSx0aGlzLmxpZ2h0PXQsdGhpcy5saWdodC51cGRhdGVNYXRyaXhXb3JsZCgpLHRoaXMubWF0cml4PXQubWF0cml4V29ybGQsdGhpcy5tYXRyaXhBdXRvVXBkYXRlPSExLHRoaXMuY29sb3I9bjtsZXQgaT1uZXcgVzAocik7aS5yb3RhdGVZKE1hdGguUEkqLjUpLHRoaXMubWF0ZXJpYWw9bmV3IHNoKHt3aXJlZnJhbWU6ITAsZm9nOiExLHRvbmVNYXBwZWQ6ITF9KSx0aGlzLmNvbG9yPT09dm9pZCAwJiYodGhpcy5tYXRlcmlhbC52ZXJ0ZXhDb2xvcnM9ITApO2xldCBvPWkuZ2V0QXR0cmlidXRlKCJwb3NpdGlvbiIpLGE9bmV3IEZsb2F0MzJBcnJheShvLmNvdW50KjMpO2kuc2V0QXR0cmlidXRlKCJjb2xvciIsbmV3IEplKGEsMykpLHRoaXMuYWRkKG5ldyBlaShpLHRoaXMubWF0ZXJpYWwpKSx0aGlzLnVwZGF0ZSgpfWRpc3Bvc2UoKXt0aGlzLmNoaWxkcmVuWzBdLmdlb21ldHJ5LmRpc3Bvc2UoKSx0aGlzLmNoaWxkcmVuWzBdLm1hdGVyaWFsLmRpc3Bvc2UoKX11cGRhdGUoKXtsZXQgdD10aGlzLmNoaWxkcmVuWzBdO2lmKHRoaXMuY29sb3IhPT12b2lkIDApdGhpcy5tYXRlcmlhbC5jb2xvci5zZXQodGhpcy5jb2xvcik7ZWxzZXtsZXQgcj10Lmdlb21ldHJ5LmdldEF0dHJpYnV0ZSgiY29sb3IiKTtJaGUuY29weSh0aGlzLmxpZ2h0LmNvbG9yKSxMaGUuY29weSh0aGlzLmxpZ2h0Lmdyb3VuZENvbG9yKTtmb3IobGV0IG49MCxpPXIuY291bnQ7bjxpO24rKyl7bGV0IG89bjxpLzI/SWhlOkxoZTtyLnNldFhZWihuLG8ucixvLmcsby5iKX1yLm5lZWRzVXBkYXRlPSEwfXQubG9va0F0KHYwci5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5saWdodC5tYXRyaXhXb3JsZCkubmVnYXRlKCkpfX0sV1U9Y2xhc3MgZXh0ZW5kcyBBYXtjb25zdHJ1Y3Rvcih0PTEwLHI9MTAsbj00NDczOTI0LGk9ODk0Nzg0OCl7bj1uZXcgbmUobiksaT1uZXcgbmUoaSk7bGV0IG89ci8yLGE9dC9yLHM9dC8yLGw9W10sYz1bXTtmb3IobGV0IGY9MCxwPTAsZD0tcztmPD1yO2YrKyxkKz1hKXtsLnB1c2goLXMsMCxkLHMsMCxkKSxsLnB1c2goZCwwLC1zLGQsMCxzKTtsZXQgZz1mPT09bz9uOmk7Zy50b0FycmF5KGMscCkscCs9MyxnLnRvQXJyYXkoYyxwKSxwKz0zLGcudG9BcnJheShjLHApLHArPTMsZy50b0FycmF5KGMscCkscCs9M31sZXQgdT1uZXcgUGU7dS5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUobCwzKSksdS5zZXRBdHRyaWJ1dGUoImNvbG9yIixuZXcgeGUoYywzKSk7bGV0IGg9bmV3IEdpKHt2ZXJ0ZXhDb2xvcnM6ITAsdG9uZU1hcHBlZDohMX0pO3N1cGVyKHUsaCksdGhpcy50eXBlPSJHcmlkSGVscGVyIn19LHhodD1jbGFzcyBleHRlbmRzIEFhe2NvbnN0cnVjdG9yKHQ9MTAscj0xNixuPTgsaT02NCxvPTQ0NzM5MjQsYT04OTQ3ODQ4KXtvPW5ldyBuZShvKSxhPW5ldyBuZShhKTtsZXQgcz1bXSxsPVtdO2ZvcihsZXQgaD0wO2g8PXI7aCsrKXtsZXQgZj1oL3IqKE1hdGguUEkqMikscD1NYXRoLnNpbihmKSp0LGQ9TWF0aC5jb3MoZikqdDtzLnB1c2goMCwwLDApLHMucHVzaChwLDAsZCk7bGV0IGc9aCYxP286YTtsLnB1c2goZy5yLGcuZyxnLmIpLGwucHVzaChnLnIsZy5nLGcuYil9Zm9yKGxldCBoPTA7aDw9bjtoKyspe2xldCBmPWgmMT9vOmEscD10LXQvbipoO2ZvcihsZXQgZD0wO2Q8aTtkKyspe2xldCBnPWQvaSooTWF0aC5QSSoyKSxfPU1hdGguc2luKGcpKnAseT1NYXRoLmNvcyhnKSpwO3MucHVzaChfLDAseSksbC5wdXNoKGYucixmLmcsZi5iKSxnPShkKzEpL2kqKE1hdGguUEkqMiksXz1NYXRoLnNpbihnKSpwLHk9TWF0aC5jb3MoZykqcCxzLnB1c2goXywwLHkpLGwucHVzaChmLnIsZi5nLGYuYil9fWxldCBjPW5ldyBQZTtjLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyB4ZShzLDMpKSxjLnNldEF0dHJpYnV0ZSgiY29sb3IiLG5ldyB4ZShsLDMpKTtsZXQgdT1uZXcgR2koe3ZlcnRleENvbG9yczohMCx0b25lTWFwcGVkOiExfSk7c3VwZXIoYyx1KSx0aGlzLnR5cGU9IlBvbGFyR3JpZEhlbHBlciJ9fSxraGU9bmV3IGosalY9bmV3IGosUmhlPW5ldyBqLGJodD1jbGFzcyBleHRlbmRzIG9ye2NvbnN0cnVjdG9yKHQscixuKXtzdXBlcigpLHRoaXMubGlnaHQ9dCx0aGlzLmxpZ2h0LnVwZGF0ZU1hdHJpeFdvcmxkKCksdGhpcy5tYXRyaXg9dC5tYXRyaXhXb3JsZCx0aGlzLm1hdHJpeEF1dG9VcGRhdGU9ITEsdGhpcy5jb2xvcj1uLHI9PT12b2lkIDAmJihyPTEpO2xldCBpPW5ldyBQZTtpLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyB4ZShbLXIsciwwLHIsciwwLHIsLXIsMCwtciwtciwwLC1yLHIsMF0sMykpO2xldCBvPW5ldyBHaSh7Zm9nOiExLHRvbmVNYXBwZWQ6ITF9KTt0aGlzLmxpZ2h0UGxhbmU9bmV3IGNoKGksbyksdGhpcy5hZGQodGhpcy5saWdodFBsYW5lKSxpPW5ldyBQZSxpLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyB4ZShbMCwwLDAsMCwwLDFdLDMpKSx0aGlzLnRhcmdldExpbmU9bmV3IGNoKGksbyksdGhpcy5hZGQodGhpcy50YXJnZXRMaW5lKSx0aGlzLnVwZGF0ZSgpfWRpc3Bvc2UoKXt0aGlzLmxpZ2h0UGxhbmUuZ2VvbWV0cnkuZGlzcG9zZSgpLHRoaXMubGlnaHRQbGFuZS5tYXRlcmlhbC5kaXNwb3NlKCksdGhpcy50YXJnZXRMaW5lLmdlb21ldHJ5LmRpc3Bvc2UoKSx0aGlzLnRhcmdldExpbmUubWF0ZXJpYWwuZGlzcG9zZSgpfXVwZGF0ZSgpe2toZS5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5saWdodC5tYXRyaXhXb3JsZCksalYuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKHRoaXMubGlnaHQudGFyZ2V0Lm1hdHJpeFdvcmxkKSxSaGUuc3ViVmVjdG9ycyhqVixraGUpLHRoaXMubGlnaHRQbGFuZS5sb29rQXQoalYpLHRoaXMuY29sb3IhPT12b2lkIDA/KHRoaXMubGlnaHRQbGFuZS5tYXRlcmlhbC5jb2xvci5zZXQodGhpcy5jb2xvciksdGhpcy50YXJnZXRMaW5lLm1hdGVyaWFsLmNvbG9yLnNldCh0aGlzLmNvbG9yKSk6KHRoaXMubGlnaHRQbGFuZS5tYXRlcmlhbC5jb2xvci5jb3B5KHRoaXMubGlnaHQuY29sb3IpLHRoaXMudGFyZ2V0TGluZS5tYXRlcmlhbC5jb2xvci5jb3B5KHRoaXMubGlnaHQuY29sb3IpKSx0aGlzLnRhcmdldExpbmUubG9va0F0KGpWKSx0aGlzLnRhcmdldExpbmUuc2NhbGUuej1SaGUubGVuZ3RoKCl9fSxYVj1uZXcgaix5aT1uZXcgUnYsd2h0PWNsYXNzIGV4dGVuZHMgQWF7Y29uc3RydWN0b3IodCl7bGV0IHI9bmV3IFBlLG49bmV3IEdpKHtjb2xvcjoxNjc3NzIxNSx2ZXJ0ZXhDb2xvcnM6ITAsdG9uZU1hcHBlZDohMX0pLGk9W10sbz1bXSxhPXt9LHM9bmV3IG5lKDE2NzU1MjAwKSxsPW5ldyBuZSgxNjcxMTY4MCksYz1uZXcgbmUoNDM3NzUpLHU9bmV3IG5lKDE2Nzc3MjE1KSxoPW5ldyBuZSgzMzU1NDQzKTtmKCJuMSIsIm4yIixzKSxmKCJuMiIsIm40IixzKSxmKCJuNCIsIm4zIixzKSxmKCJuMyIsIm4xIixzKSxmKCJmMSIsImYyIixzKSxmKCJmMiIsImY0IixzKSxmKCJmNCIsImYzIixzKSxmKCJmMyIsImYxIixzKSxmKCJuMSIsImYxIixzKSxmKCJuMiIsImYyIixzKSxmKCJuMyIsImYzIixzKSxmKCJuNCIsImY0IixzKSxmKCJwIiwibjEiLGwpLGYoInAiLCJuMiIsbCksZigicCIsIm4zIixsKSxmKCJwIiwibjQiLGwpLGYoInUxIiwidTIiLGMpLGYoInUyIiwidTMiLGMpLGYoInUzIiwidTEiLGMpLGYoImMiLCJ0Iix1KSxmKCJwIiwiYyIsaCksZigiY24xIiwiY24yIixoKSxmKCJjbjMiLCJjbjQiLGgpLGYoImNmMSIsImNmMiIsaCksZigiY2YzIiwiY2Y0IixoKTtmdW5jdGlvbiBmKGQsZyxfKXtwKGQsXykscChnLF8pfWZ1bmN0aW9uIHAoZCxnKXtpLnB1c2goMCwwLDApLG8ucHVzaChnLnIsZy5nLGcuYiksYVtkXT09PXZvaWQgMCYmKGFbZF09W10pLGFbZF0ucHVzaChpLmxlbmd0aC8zLTEpfXIuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IHhlKGksMykpLHIuc2V0QXR0cmlidXRlKCJjb2xvciIsbmV3IHhlKG8sMykpLHN1cGVyKHIsbiksdGhpcy50eXBlPSJDYW1lcmFIZWxwZXIiLHRoaXMuY2FtZXJhPXQsdGhpcy5jYW1lcmEudXBkYXRlUHJvamVjdGlvbk1hdHJpeCYmdGhpcy5jYW1lcmEudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpLHRoaXMubWF0cml4PXQubWF0cml4V29ybGQsdGhpcy5tYXRyaXhBdXRvVXBkYXRlPSExLHRoaXMucG9pbnRNYXA9YSx0aGlzLnVwZGF0ZSgpfXVwZGF0ZSgpe2xldCB0PXRoaXMuZ2VvbWV0cnkscj10aGlzLnBvaW50TWFwLG49MSxpPTE7eWkucHJvamVjdGlvbk1hdHJpeEludmVyc2UuY29weSh0aGlzLmNhbWVyYS5wcm9qZWN0aW9uTWF0cml4SW52ZXJzZSksUGkoImMiLHIsdCx5aSwwLDAsLTEpLFBpKCJ0IixyLHQseWksMCwwLDEpLFBpKCJuMSIscix0LHlpLC1uLC1pLC0xKSxQaSgibjIiLHIsdCx5aSxuLC1pLC0xKSxQaSgibjMiLHIsdCx5aSwtbixpLC0xKSxQaSgibjQiLHIsdCx5aSxuLGksLTEpLFBpKCJmMSIscix0LHlpLC1uLC1pLDEpLFBpKCJmMiIscix0LHlpLG4sLWksMSksUGkoImYzIixyLHQseWksLW4saSwxKSxQaSgiZjQiLHIsdCx5aSxuLGksMSksUGkoInUxIixyLHQseWksbiouNyxpKjEuMSwtMSksUGkoInUyIixyLHQseWksLW4qLjcsaSoxLjEsLTEpLFBpKCJ1MyIscix0LHlpLDAsaSoyLC0xKSxQaSgiY2YxIixyLHQseWksLW4sMCwxKSxQaSgiY2YyIixyLHQseWksbiwwLDEpLFBpKCJjZjMiLHIsdCx5aSwwLC1pLDEpLFBpKCJjZjQiLHIsdCx5aSwwLGksMSksUGkoImNuMSIscix0LHlpLC1uLDAsLTEpLFBpKCJjbjIiLHIsdCx5aSxuLDAsLTEpLFBpKCJjbjMiLHIsdCx5aSwwLC1pLC0xKSxQaSgiY240IixyLHQseWksMCxpLC0xKSx0LmdldEF0dHJpYnV0ZSgicG9zaXRpb24iKS5uZWVkc1VwZGF0ZT0hMH1kaXNwb3NlKCl7dGhpcy5nZW9tZXRyeS5kaXNwb3NlKCksdGhpcy5tYXRlcmlhbC5kaXNwb3NlKCl9fTtmdW5jdGlvbiBQaShlLHQscixuLGksbyxhKXtYVi5zZXQoaSxvLGEpLnVucHJvamVjdChuKTtsZXQgcz10W2VdO2lmKHMhPT12b2lkIDApe2xldCBsPXIuZ2V0QXR0cmlidXRlKCJwb3NpdGlvbiIpO2ZvcihsZXQgYz0wLHU9cy5sZW5ndGg7Yzx1O2MrKylsLnNldFhZWihzW2NdLFhWLngsWFYueSxYVi56KX19dmFyICRWPW5ldyB0YSx5TT1jbGFzcyBleHRlbmRzIEFhe2NvbnN0cnVjdG9yKHQscj0xNjc3Njk2MCl7bGV0IG49bmV3IFVpbnQxNkFycmF5KFswLDEsMSwyLDIsMywzLDAsNCw1LDUsNiw2LDcsNyw0LDAsNCwxLDUsMiw2LDMsN10pLGk9bmV3IEZsb2F0MzJBcnJheSg4KjMpLG89bmV3IFBlO28uc2V0SW5kZXgobmV3IEplKG4sMSkpLG8uc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IEplKGksMykpLHN1cGVyKG8sbmV3IEdpKHtjb2xvcjpyLHRvbmVNYXBwZWQ6ITF9KSksdGhpcy5vYmplY3Q9dCx0aGlzLnR5cGU9IkJveEhlbHBlciIsdGhpcy5tYXRyaXhBdXRvVXBkYXRlPSExLHRoaXMudXBkYXRlKCl9dXBkYXRlKHQpe2lmKHQhPT12b2lkIDAmJmNvbnNvbGUud2FybigiVEhSRUUuQm94SGVscGVyOiAudXBkYXRlKCkgaGFzIG5vIGxvbmdlciBhcmd1bWVudHMuIiksdGhpcy5vYmplY3QhPT12b2lkIDAmJiRWLnNldEZyb21PYmplY3QodGhpcy5vYmplY3QpLCRWLmlzRW1wdHkoKSlyZXR1cm47bGV0IHI9JFYubWluLG49JFYubWF4LGk9dGhpcy5nZW9tZXRyeS5hdHRyaWJ1dGVzLnBvc2l0aW9uLG89aS5hcnJheTtvWzBdPW4ueCxvWzFdPW4ueSxvWzJdPW4ueixvWzNdPXIueCxvWzRdPW4ueSxvWzVdPW4ueixvWzZdPXIueCxvWzddPXIueSxvWzhdPW4ueixvWzldPW4ueCxvWzEwXT1yLnksb1sxMV09bi56LG9bMTJdPW4ueCxvWzEzXT1uLnksb1sxNF09ci56LG9bMTVdPXIueCxvWzE2XT1uLnksb1sxN109ci56LG9bMThdPXIueCxvWzE5XT1yLnksb1syMF09ci56LG9bMjFdPW4ueCxvWzIyXT1yLnksb1syM109ci56LGkubmVlZHNVcGRhdGU9ITAsdGhpcy5nZW9tZXRyeS5jb21wdXRlQm91bmRpbmdTcGhlcmUoKX1zZXRGcm9tT2JqZWN0KHQpe3JldHVybiB0aGlzLm9iamVjdD10LHRoaXMudXBkYXRlKCksdGhpc31jb3B5KHQpe3JldHVybiBBYS5wcm90b3R5cGUuY29weS5jYWxsKHRoaXMsdCksdGhpcy5vYmplY3Q9dC5vYmplY3QsdGhpc319LFNodD1jbGFzcyBleHRlbmRzIEFhe2NvbnN0cnVjdG9yKHQscj0xNjc3Njk2MCl7bGV0IG49bmV3IFVpbnQxNkFycmF5KFswLDEsMSwyLDIsMywzLDAsNCw1LDUsNiw2LDcsNyw0LDAsNCwxLDUsMiw2LDMsN10pLGk9WzEsMSwxLC0xLDEsMSwtMSwtMSwxLDEsLTEsMSwxLDEsLTEsLTEsMSwtMSwtMSwtMSwtMSwxLC0xLC0xXSxvPW5ldyBQZTtvLnNldEluZGV4KG5ldyBKZShuLDEpKSxvLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyB4ZShpLDMpKSxzdXBlcihvLG5ldyBHaSh7Y29sb3I6cix0b25lTWFwcGVkOiExfSkpLHRoaXMuYm94PXQsdGhpcy50eXBlPSJCb3gzSGVscGVyIix0aGlzLmdlb21ldHJ5LmNvbXB1dGVCb3VuZGluZ1NwaGVyZSgpfXVwZGF0ZU1hdHJpeFdvcmxkKHQpe2xldCByPXRoaXMuYm94O3IuaXNFbXB0eSgpfHwoci5nZXRDZW50ZXIodGhpcy5wb3NpdGlvbiksci5nZXRTaXplKHRoaXMuc2NhbGUpLHRoaXMuc2NhbGUubXVsdGlwbHlTY2FsYXIoLjUpLHN1cGVyLnVwZGF0ZU1hdHJpeFdvcmxkKHQpKX19LE1odD1jbGFzcyBleHRlbmRzIGNoe2NvbnN0cnVjdG9yKHQscj0xLG49MTY3NzY5NjApe2xldCBpPW4sbz1bMSwtMSwxLC0xLDEsMSwtMSwtMSwxLDEsMSwxLC0xLDEsMSwtMSwtMSwxLDEsLTEsMSwxLDEsMSwwLDAsMSwwLDAsMF0sYT1uZXcgUGU7YS5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUobywzKSksYS5jb21wdXRlQm91bmRpbmdTcGhlcmUoKSxzdXBlcihhLG5ldyBHaSh7Y29sb3I6aSx0b25lTWFwcGVkOiExfSkpLHRoaXMudHlwZT0iUGxhbmVIZWxwZXIiLHRoaXMucGxhbmU9dCx0aGlzLnNpemU9cjtsZXQgcz1bMSwxLDEsLTEsMSwxLC0xLC0xLDEsMSwxLDEsLTEsLTEsMSwxLC0xLDFdLGw9bmV3IFBlO2wuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IHhlKHMsMykpLGwuY29tcHV0ZUJvdW5kaW5nU3BoZXJlKCksdGhpcy5hZGQobmV3IGVpKGwsbmV3IHNoKHtjb2xvcjppLG9wYWNpdHk6LjIsdHJhbnNwYXJlbnQ6ITAsZGVwdGhXcml0ZTohMSx0b25lTWFwcGVkOiExfSkpKX11cGRhdGVNYXRyaXhXb3JsZCh0KXtsZXQgcj0tdGhpcy5wbGFuZS5jb25zdGFudDtNYXRoLmFicyhyKTwxZS04JiYocj0xZS04KSx0aGlzLnNjYWxlLnNldCguNSp0aGlzLnNpemUsLjUqdGhpcy5zaXplLHIpLHRoaXMuY2hpbGRyZW5bMF0ubWF0ZXJpYWwuc2lkZT1yPDA/SWk6SXYsdGhpcy5sb29rQXQodGhpcy5wbGFuZS5ub3JtYWwpLHN1cGVyLnVwZGF0ZU1hdHJpeFdvcmxkKHQpfX0sTmhlPW5ldyBqLEtWLFN1dCxFaHQ9Y2xhc3MgZXh0ZW5kcyBvcntjb25zdHJ1Y3Rvcih0PW5ldyBqKDAsMCwxKSxyPW5ldyBqKDAsMCwwKSxuPTEsaT0xNjc3Njk2MCxvPW4qLjIsYT1vKi4yKXtzdXBlcigpLHRoaXMudHlwZT0iQXJyb3dIZWxwZXIiLEtWPT09dm9pZCAwJiYoS1Y9bmV3IFBlLEtWLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyB4ZShbMCwwLDAsMCwxLDBdLDMpKSxTdXQ9bmV3IG9tKDAsLjUsMSw1LDEpLFN1dC50cmFuc2xhdGUoMCwtLjUsMCkpLHRoaXMucG9zaXRpb24uY29weShyKSx0aGlzLmxpbmU9bmV3IGNoKEtWLG5ldyBHaSh7Y29sb3I6aSx0b25lTWFwcGVkOiExfSkpLHRoaXMubGluZS5tYXRyaXhBdXRvVXBkYXRlPSExLHRoaXMuYWRkKHRoaXMubGluZSksdGhpcy5jb25lPW5ldyBlaShTdXQsbmV3IHNoKHtjb2xvcjppLHRvbmVNYXBwZWQ6ITF9KSksdGhpcy5jb25lLm1hdHJpeEF1dG9VcGRhdGU9ITEsdGhpcy5hZGQodGhpcy5jb25lKSx0aGlzLnNldERpcmVjdGlvbih0KSx0aGlzLnNldExlbmd0aChuLG8sYSl9c2V0RGlyZWN0aW9uKHQpe2lmKHQueT4uOTk5OTkpdGhpcy5xdWF0ZXJuaW9uLnNldCgwLDAsMCwxKTtlbHNlIGlmKHQueTwtLjk5OTk5KXRoaXMucXVhdGVybmlvbi5zZXQoMSwwLDAsMCk7ZWxzZXtOaGUuc2V0KHQueiwwLC10LngpLm5vcm1hbGl6ZSgpO2xldCByPU1hdGguYWNvcyh0LnkpO3RoaXMucXVhdGVybmlvbi5zZXRGcm9tQXhpc0FuZ2xlKE5oZSxyKX19c2V0TGVuZ3RoKHQscj10Ki4yLG49ciouMil7dGhpcy5saW5lLnNjYWxlLnNldCgxLE1hdGgubWF4KDFlLTQsdC1yKSwxKSx0aGlzLmxpbmUudXBkYXRlTWF0cml4KCksdGhpcy5jb25lLnNjYWxlLnNldChuLHIsbiksdGhpcy5jb25lLnBvc2l0aW9uLnk9dCx0aGlzLmNvbmUudXBkYXRlTWF0cml4KCl9c2V0Q29sb3IodCl7dGhpcy5saW5lLm1hdGVyaWFsLmNvbG9yLnNldCh0KSx0aGlzLmNvbmUubWF0ZXJpYWwuY29sb3Iuc2V0KHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCwhMSksdGhpcy5saW5lLmNvcHkodC5saW5lKSx0aGlzLmNvbmUuY29weSh0LmNvbmUpLHRoaXN9fSx2TT1jbGFzcyBleHRlbmRzIEFhe2NvbnN0cnVjdG9yKHQ9MSl7bGV0IHI9WzAsMCwwLHQsMCwwLDAsMCwwLDAsdCwwLDAsMCwwLDAsMCx0XSxuPVsxLDAsMCwxLC42LDAsMCwxLDAsLjYsMSwwLDAsMCwxLDAsLjYsMV0saT1uZXcgUGU7aS5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgeGUociwzKSksaS5zZXRBdHRyaWJ1dGUoImNvbG9yIixuZXcgeGUobiwzKSk7bGV0IG89bmV3IEdpKHt2ZXJ0ZXhDb2xvcnM6ITAsdG9uZU1hcHBlZDohMX0pO3N1cGVyKGksbyksdGhpcy50eXBlPSJBeGVzSGVscGVyIn1zZXRDb2xvcnModCxyLG4pe2xldCBpPW5ldyBuZSxvPXRoaXMuZ2VvbWV0cnkuYXR0cmlidXRlcy5jb2xvci5hcnJheTtyZXR1cm4gaS5zZXQodCksaS50b0FycmF5KG8sMCksaS50b0FycmF5KG8sMyksaS5zZXQociksaS50b0FycmF5KG8sNiksaS50b0FycmF5KG8sOSksaS5zZXQobiksaS50b0FycmF5KG8sMTIpLGkudG9BcnJheShvLDE1KSx0aGlzLmdlb21ldHJ5LmF0dHJpYnV0ZXMuY29sb3IubmVlZHNVcGRhdGU9ITAsdGhpc31kaXNwb3NlKCl7dGhpcy5nZW9tZXRyeS5kaXNwb3NlKCksdGhpcy5tYXRlcmlhbC5kaXNwb3NlKCl9fSxUaHQ9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnR5cGU9IlNoYXBlUGF0aCIsdGhpcy5jb2xvcj1uZXcgbmUsdGhpcy5zdWJQYXRocz1bXSx0aGlzLmN1cnJlbnRQYXRoPW51bGx9bW92ZVRvKHQscil7cmV0dXJuIHRoaXMuY3VycmVudFBhdGg9bmV3IHF2LHRoaXMuc3ViUGF0aHMucHVzaCh0aGlzLmN1cnJlbnRQYXRoKSx0aGlzLmN1cnJlbnRQYXRoLm1vdmVUbyh0LHIpLHRoaXN9bGluZVRvKHQscil7cmV0dXJuIHRoaXMuY3VycmVudFBhdGgubGluZVRvKHQsciksdGhpc31xdWFkcmF0aWNDdXJ2ZVRvKHQscixuLGkpe3JldHVybiB0aGlzLmN1cnJlbnRQYXRoLnF1YWRyYXRpY0N1cnZlVG8odCxyLG4saSksdGhpc31iZXppZXJDdXJ2ZVRvKHQscixuLGksbyxhKXtyZXR1cm4gdGhpcy5jdXJyZW50UGF0aC5iZXppZXJDdXJ2ZVRvKHQscixuLGksbyxhKSx0aGlzfXNwbGluZVRocnUodCl7cmV0dXJuIHRoaXMuY3VycmVudFBhdGguc3BsaW5lVGhydSh0KSx0aGlzfXRvU2hhcGVzKHQscil7ZnVuY3Rpb24gbih4KXtsZXQgYj1bXTtmb3IobGV0IFM9MCxDPXgubGVuZ3RoO1M8QztTKyspe2xldCBQPXhbU10saz1uZXcgS2M7ay5jdXJ2ZXM9UC5jdXJ2ZXMsYi5wdXNoKGspfXJldHVybiBifWZ1bmN0aW9uIGkoeCxiKXtsZXQgUz1iLmxlbmd0aCxDPSExO2ZvcihsZXQgUD1TLTEsaz0wO2s8UztQPWsrKyl7bGV0IE89YltQXSxEPWJba10sQj1ELngtTy54LEk9RC55LU8ueTtpZihNYXRoLmFicyhJKT5OdW1iZXIuRVBTSUxPTil7aWYoSTwwJiYoTz1iW2tdLEI9LUIsRD1iW1BdLEk9LUkpLHgueTxPLnl8fHgueT5ELnkpY29udGludWU7aWYoeC55PT09Ty55KXtpZih4Lng9PT1PLngpcmV0dXJuITB9ZWxzZXtsZXQgTD1JKih4LngtTy54KS1CKih4LnktTy55KTtpZihMPT09MClyZXR1cm4hMDtpZihMPDApY29udGludWU7Qz0hQ319ZWxzZXtpZih4LnkhPT1PLnkpY29udGludWU7aWYoRC54PD14LngmJngueDw9Ty54fHxPLng8PXgueCYmeC54PD1ELngpcmV0dXJuITB9fXJldHVybiBDfWxldCBvPVpjLmlzQ2xvY2tXaXNlLGE9dGhpcy5zdWJQYXRocztpZihhLmxlbmd0aD09PTApcmV0dXJuW107aWYocj09PSEwKXJldHVybiBuKGEpO2xldCBzLGwsYyx1PVtdO2lmKGEubGVuZ3RoPT09MSlyZXR1cm4gbD1hWzBdLGM9bmV3IEtjLGMuY3VydmVzPWwuY3VydmVzLHUucHVzaChjKSx1O2xldCBoPSFvKGFbMF0uZ2V0UG9pbnRzKCkpO2g9dD8haDpoO2xldCBmPVtdLHA9W10sZD1bXSxnPTAsXztwW2ddPXZvaWQgMCxkW2ddPVtdO2ZvcihsZXQgeD0wLGI9YS5sZW5ndGg7eDxiO3grKylsPWFbeF0sXz1sLmdldFBvaW50cygpLHM9byhfKSxzPXQ/IXM6cyxzPyghaCYmcFtnXSYmZysrLHBbZ109e3M6bmV3IEtjLHA6X30scFtnXS5zLmN1cnZlcz1sLmN1cnZlcyxoJiZnKyssZFtnXT1bXSk6ZFtnXS5wdXNoKHtoOmwscDpfWzBdfSk7aWYoIXBbMF0pcmV0dXJuIG4oYSk7aWYocC5sZW5ndGg+MSl7bGV0IHg9ITEsYj1bXTtmb3IobGV0IFM9MCxDPXAubGVuZ3RoO1M8QztTKyspZltTXT1bXTtmb3IobGV0IFM9MCxDPXAubGVuZ3RoO1M8QztTKyspe2xldCBQPWRbU107Zm9yKGxldCBrPTA7azxQLmxlbmd0aDtrKyspe2xldCBPPVBba10sRD0hMDtmb3IobGV0IEI9MDtCPHAubGVuZ3RoO0IrKylpKE8ucCxwW0JdLnApJiYoUyE9PUImJmIucHVzaCh7ZnJvbXM6Uyx0b3M6Qixob2xlOmt9KSxEPyhEPSExLGZbQl0ucHVzaChPKSk6eD0hMCk7RCYmZltTXS5wdXNoKE8pfX1iLmxlbmd0aD4wJiYoeHx8KGQ9ZikpfWxldCB5O2ZvcihsZXQgeD0wLGI9cC5sZW5ndGg7eDxiO3grKyl7Yz1wW3hdLnMsdS5wdXNoKGMpLHk9ZFt4XTtmb3IobGV0IFM9MCxDPXkubGVuZ3RoO1M8QztTKyspYy5ob2xlcy5wdXNoKHlbU10uaCl9cmV0dXJuIHV9fSxlcGU9bmV3IEZsb2F0MzJBcnJheSgxKSx4MHI9bmV3IEludDMyQXJyYXkoZXBlLmJ1ZmZlciksQ2h0PWNsYXNze3N0YXRpYyB0b0hhbGZGbG9hdCh0KXt0PjY1NTA0JiYoY29uc29sZS53YXJuKCJUSFJFRS5EYXRhVXRpbHMudG9IYWxmRmxvYXQoKTogdmFsdWUgZXhjZWVkcyA2NTUwNC4iKSx0PTY1NTA0KSxlcGVbMF09dDtsZXQgcj14MHJbMF0sbj1yPj4xNiYzMjc2OCxpPXI+PjEyJjIwNDcsbz1yPj4yMyYyNTU7cmV0dXJuIG88MTAzP246bz4xNDI/KG58PTMxNzQ0LG58PShvPT0yNTU/MDoxKSYmciY4Mzg4NjA3LG4pOm88MTEzPyhpfD0yMDQ4LG58PShpPj4xMTQtbykrKGk+PjExMy1vJjEpLG4pOihufD1vLTExMjw8MTB8aT4+MSxuKz1pJjEsbil9fSxiMHI9MCx3MHI9MSxTMHI9MCxNMHI9MSxFMHI9MjtmdW5jdGlvbiBUMHIoZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWVzaEZhY2VNYXRlcmlhbCBoYXMgYmVlbiByZW1vdmVkLiBVc2UgYW4gQXJyYXkgaW5zdGVhZC4iKSxlfWZ1bmN0aW9uIEMwcihlPVtdKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NdWx0aU1hdGVyaWFsIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBhbiBBcnJheSBpbnN0ZWFkLiIpLGUuaXNNdWx0aU1hdGVyaWFsPSEwLGUubWF0ZXJpYWxzPWUsZS5jbG9uZT1mdW5jdGlvbigpe3JldHVybiBlLnNsaWNlKCl9LGV9ZnVuY3Rpb24gQTByKGUsdCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUG9pbnRDbG91ZCBoYXMgYmVlbiByZW5hbWVkIHRvIFRIUkVFLlBvaW50cy4iKSxuZXcgaW0oZSx0KX1mdW5jdGlvbiBQMHIoZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUGFydGljbGUgaGFzIGJlZW4gcmVuYW1lZCB0byBUSFJFRS5TcHJpdGUuIiksbmV3IG9NKGUpfWZ1bmN0aW9uIEkwcihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlBhcnRpY2xlU3lzdGVtIGhhcyBiZWVuIHJlbmFtZWQgdG8gVEhSRUUuUG9pbnRzLiIpLG5ldyBpbShlLHQpfWZ1bmN0aW9uIEwwcihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Qb2ludENsb3VkTWF0ZXJpYWwgaGFzIGJlZW4gcmVuYW1lZCB0byBUSFJFRS5Qb2ludHNNYXRlcmlhbC4iKSxuZXcgbm0oZSl9ZnVuY3Rpb24gazByKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlBhcnRpY2xlQmFzaWNNYXRlcmlhbCBoYXMgYmVlbiByZW5hbWVkIHRvIFRIUkVFLlBvaW50c01hdGVyaWFsLiIpLG5ldyBubShlKX1mdW5jdGlvbiBSMHIoZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUGFydGljbGVTeXN0ZW1NYXRlcmlhbCBoYXMgYmVlbiByZW5hbWVkIHRvIFRIUkVFLlBvaW50c01hdGVyaWFsLiIpLG5ldyBubShlKX1mdW5jdGlvbiBOMHIoZSx0LHIpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlcnRleCBoYXMgYmVlbiByZW1vdmVkLiBVc2UgVEhSRUUuVmVjdG9yMyBpbnN0ZWFkLiIpLG5ldyBqKGUsdCxyKX1mdW5jdGlvbiBEMHIoZSx0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5EeW5hbWljQnVmZmVyQXR0cmlidXRlIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBuZXcgVEhSRUUuQnVmZmVyQXR0cmlidXRlKCkuc2V0VXNhZ2UoIFRIUkVFLkR5bmFtaWNEcmF3VXNhZ2UgKSBpbnN0ZWFkLiIpLG5ldyBKZShlLHQpLnNldFVzYWdlKFkzKX1mdW5jdGlvbiBPMHIoZSx0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5JbnQ4QXR0cmlidXRlIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBuZXcgVEhSRUUuSW50OEJ1ZmZlckF0dHJpYnV0ZSgpIGluc3RlYWQuIiksbmV3IGxVKGUsdCl9ZnVuY3Rpb24gejByKGUsdCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVWludDhBdHRyaWJ1dGUgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIG5ldyBUSFJFRS5VaW50OEJ1ZmZlckF0dHJpYnV0ZSgpIGluc3RlYWQuIiksbmV3IGNVKGUsdCl9ZnVuY3Rpb24gRjByKGUsdCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVWludDhDbGFtcGVkQXR0cmlidXRlIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBuZXcgVEhSRUUuVWludDhDbGFtcGVkQnVmZmVyQXR0cmlidXRlKCkgaW5zdGVhZC4iKSxuZXcgdVUoZSx0KX1mdW5jdGlvbiBCMHIoZSx0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5JbnQxNkF0dHJpYnV0ZSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgbmV3IFRIUkVFLkludDE2QnVmZmVyQXR0cmlidXRlKCkgaW5zdGVhZC4iKSxuZXcgaFUoZSx0KX1mdW5jdGlvbiBIMHIoZSx0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5VaW50MTZBdHRyaWJ1dGUgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIG5ldyBUSFJFRS5VaW50MTZCdWZmZXJBdHRyaWJ1dGUoKSBpbnN0ZWFkLiIpLG5ldyAkMyhlLHQpfWZ1bmN0aW9uIFYwcihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkludDMyQXR0cmlidXRlIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBuZXcgVEhSRUUuSW50MzJCdWZmZXJBdHRyaWJ1dGUoKSBpbnN0ZWFkLiIpLG5ldyBmVShlLHQpfWZ1bmN0aW9uIFUwcihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlVpbnQzMkF0dHJpYnV0ZSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgbmV3IFRIUkVFLlVpbnQzMkJ1ZmZlckF0dHJpYnV0ZSgpIGluc3RlYWQuIiksbmV3IEszKGUsdCl9ZnVuY3Rpb24gcTByKGUsdCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuRmxvYXQzMkF0dHJpYnV0ZSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgbmV3IFRIUkVFLkZsb2F0MzJCdWZmZXJBdHRyaWJ1dGUoKSBpbnN0ZWFkLiIpLG5ldyB4ZShlLHQpfWZ1bmN0aW9uIEcwcihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkZsb2F0NjRBdHRyaWJ1dGUgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIG5ldyBUSFJFRS5GbG9hdDY0QnVmZmVyQXR0cmlidXRlKCkgaW5zdGVhZC4iKSxuZXcgZFUoZSx0KX1mcy5jcmVhdGU9ZnVuY3Rpb24oZSx0KXtyZXR1cm4gY29uc29sZS5sb2coIlRIUkVFLkN1cnZlLmNyZWF0ZSgpIGhhcyBiZWVuIGRlcHJlY2F0ZWQiKSxlLnByb3RvdHlwZT1PYmplY3QuY3JlYXRlKGZzLnByb3RvdHlwZSksZS5wcm90b3R5cGUuY29uc3RydWN0b3I9ZSxlLnByb3RvdHlwZS5nZXRQb2ludD10LGV9O3F2LnByb3RvdHlwZS5mcm9tUG9pbnRzPWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlBhdGg6IC5mcm9tUG9pbnRzKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuc2V0RnJvbVBvaW50cygpLiIpLHRoaXMuc2V0RnJvbVBvaW50cyhlKX07ZnVuY3Rpb24gVzByKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkF4aXNIZWxwZXIgaGFzIGJlZW4gcmVuYW1lZCB0byBUSFJFRS5BeGVzSGVscGVyLiIpLG5ldyB2TShlKX1mdW5jdGlvbiBZMHIoZSx0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Cb3VuZGluZ0JveEhlbHBlciBoYXMgYmVlbiBkZXByZWNhdGVkLiBDcmVhdGluZyBhIFRIUkVFLkJveEhlbHBlciBpbnN0ZWFkLiIpLG5ldyB5TShlLHQpfWZ1bmN0aW9uIGowcihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkVkZ2VzSGVscGVyIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBUSFJFRS5FZGdlc0dlb21ldHJ5IGluc3RlYWQuIiksbmV3IEFhKG5ldyBhNihlLmdlb21ldHJ5KSxuZXcgR2koe2NvbG9yOnQhPT12b2lkIDA/dDoxNjc3NzIxNX0pKX1XVS5wcm90b3R5cGUuc2V0Q29sb3JzPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuR3JpZEhlbHBlcjogc2V0Q29sb3JzKCkgaGFzIGJlZW4gZGVwcmVjYXRlZCwgcGFzcyB0aGVtIGluIHRoZSBjb25zdHJ1Y3RvciBpbnN0ZWFkLiIpfTtHVS5wcm90b3R5cGUudXBkYXRlPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuU2tlbGV0b25IZWxwZXI6IHVwZGF0ZSgpIG5vIGxvbmdlciBuZWVkcyB0byBiZSBjYWxsZWQuIil9O2Z1bmN0aW9uIFgwcihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldpcmVmcmFtZUhlbHBlciBoYXMgYmVlbiByZW1vdmVkLiBVc2UgVEhSRUUuV2lyZWZyYW1lR2VvbWV0cnkgaW5zdGVhZC4iKSxuZXcgQWEobmV3IHA2KGUuZ2VvbWV0cnkpLG5ldyBHaSh7Y29sb3I6dCE9PXZvaWQgMD90OjE2Nzc3MjE1fSkpfWVhLnByb3RvdHlwZS5leHRyYWN0VXJsQmFzZT1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Mb2FkZXI6IC5leHRyYWN0VXJsQmFzZSgpIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSBUSFJFRS5Mb2FkZXJVdGlscy5leHRyYWN0VXJsQmFzZSgpIGluc3RlYWQuIiksZE0uZXh0cmFjdFVybEJhc2UoZSl9O2VhLkhhbmRsZXJzPXthZGQ6ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5Mb2FkZXI6IEhhbmRsZXJzLmFkZCgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBMb2FkaW5nTWFuYWdlci5hZGRIYW5kbGVyKCkgaW5zdGVhZC4iKX0sZ2V0OmZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTG9hZGVyOiBIYW5kbGVycy5nZXQoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgTG9hZGluZ01hbmFnZXIuZ2V0SGFuZGxlcigpIGluc3RlYWQuIil9fTtmdW5jdGlvbiAkMHIoZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuWEhSTG9hZGVyIGhhcyBiZWVuIHJlbmFtZWQgdG8gVEhSRUUuRmlsZUxvYWRlci4iKSxuZXcgSmMoZSl9ZnVuY3Rpb24gSzByKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJpbmFyeVRleHR1cmVMb2FkZXIgaGFzIGJlZW4gcmVuYW1lZCB0byBUSFJFRS5EYXRhVGV4dHVyZUxvYWRlci4iKSxuZXcgVFUoZSl9JDAucHJvdG90eXBlLmNlbnRlcj1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Cb3gyOiAuY2VudGVyKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0Q2VudGVyKCkuIiksdGhpcy5nZXRDZW50ZXIoZSl9OyQwLnByb3RvdHlwZS5lbXB0eT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDI6IC5lbXB0eSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmlzRW1wdHkoKS4iKSx0aGlzLmlzRW1wdHkoKX07JDAucHJvdG90eXBlLmlzSW50ZXJzZWN0aW9uQm94PWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDI6IC5pc0ludGVyc2VjdGlvbkJveCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmludGVyc2VjdHNCb3goKS4iKSx0aGlzLmludGVyc2VjdHNCb3goZSl9OyQwLnByb3RvdHlwZS5zaXplPWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDI6IC5zaXplKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0U2l6ZSgpLiIpLHRoaXMuZ2V0U2l6ZShlKX07dGEucHJvdG90eXBlLmNlbnRlcj1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Cb3gzOiAuY2VudGVyKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0Q2VudGVyKCkuIiksdGhpcy5nZXRDZW50ZXIoZSl9O3RhLnByb3RvdHlwZS5lbXB0eT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDM6IC5lbXB0eSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmlzRW1wdHkoKS4iKSx0aGlzLmlzRW1wdHkoKX07dGEucHJvdG90eXBlLmlzSW50ZXJzZWN0aW9uQm94PWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDM6IC5pc0ludGVyc2VjdGlvbkJveCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmludGVyc2VjdHNCb3goKS4iKSx0aGlzLmludGVyc2VjdHNCb3goZSl9O3RhLnByb3RvdHlwZS5pc0ludGVyc2VjdGlvblNwaGVyZT1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Cb3gzOiAuaXNJbnRlcnNlY3Rpb25TcGhlcmUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5pbnRlcnNlY3RzU3BoZXJlKCkuIiksdGhpcy5pbnRlcnNlY3RzU3BoZXJlKGUpfTt0YS5wcm90b3R5cGUuc2l6ZT1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Cb3gzOiAuc2l6ZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldFNpemUoKS4iKSx0aGlzLmdldFNpemUoZSl9O1pmLnByb3RvdHlwZS5lbXB0eT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlNwaGVyZTogLmVtcHR5KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaXNFbXB0eSgpLiIpLHRoaXMuaXNFbXB0eSgpfTtOdi5wcm90b3R5cGUuc2V0RnJvbU1hdHJpeD1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5GcnVzdHVtOiAuc2V0RnJvbU1hdHJpeCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLnNldEZyb21Qcm9qZWN0aW9uTWF0cml4KCkuIiksdGhpcy5zZXRGcm9tUHJvamVjdGlvbk1hdHJpeChlKX07cVUucHJvdG90eXBlLmNlbnRlcj1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5MaW5lMzogLmNlbnRlcigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldENlbnRlcigpLiIpLHRoaXMuZ2V0Q2VudGVyKGUpfTtraS5wcm90b3R5cGUuZmxhdHRlblRvQXJyYXlPZmZzZXQ9ZnVuY3Rpb24oZSx0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXgzOiAuZmxhdHRlblRvQXJyYXlPZmZzZXQoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnRvQXJyYXkoKSBpbnN0ZWFkLiIpLHRoaXMudG9BcnJheShlLHQpfTtraS5wcm90b3R5cGUubXVsdGlwbHlWZWN0b3IzPWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDM6IC5tdWx0aXBseVZlY3RvcjMoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgdmVjdG9yLmFwcGx5TWF0cml4MyggbWF0cml4ICkgaW5zdGVhZC4iKSxlLmFwcGx5TWF0cml4Myh0aGlzKX07a2kucHJvdG90eXBlLm11bHRpcGx5VmVjdG9yM0FycmF5PWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWF0cml4MzogLm11bHRpcGx5VmVjdG9yM0FycmF5KCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07a2kucHJvdG90eXBlLmFwcGx5VG9CdWZmZXJBdHRyaWJ1dGU9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4MzogLmFwcGx5VG9CdWZmZXJBdHRyaWJ1dGUoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgYXR0cmlidXRlLmFwcGx5TWF0cml4MyggbWF0cml4ICkgaW5zdGVhZC4iKSxlLmFwcGx5TWF0cml4Myh0aGlzKX07a2kucHJvdG90eXBlLmFwcGx5VG9WZWN0b3IzQXJyYXk9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXgzOiAuYXBwbHlUb1ZlY3RvcjNBcnJheSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O2tpLnByb3RvdHlwZS5nZXRJbnZlcnNlPWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDM6IC5nZXRJbnZlcnNlKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIG1hdHJpeEludi5jb3B5KCBtYXRyaXggKS5pbnZlcnQoKTsgaW5zdGVhZC4iKSx0aGlzLmNvcHkoZSkuaW52ZXJ0KCl9O01lLnByb3RvdHlwZS5leHRyYWN0UG9zaXRpb249ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLmV4dHJhY3RQb3NpdGlvbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmNvcHlQb3NpdGlvbigpLiIpLHRoaXMuY29weVBvc2l0aW9uKGUpfTtNZS5wcm90b3R5cGUuZmxhdHRlblRvQXJyYXlPZmZzZXQ9ZnVuY3Rpb24oZSx0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAuZmxhdHRlblRvQXJyYXlPZmZzZXQoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnRvQXJyYXkoKSBpbnN0ZWFkLiIpLHRoaXMudG9BcnJheShlLHQpfTtNZS5wcm90b3R5cGUuZ2V0UG9zaXRpb249ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAuZ2V0UG9zaXRpb24oKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgVmVjdG9yMy5zZXRGcm9tTWF0cml4UG9zaXRpb24oIG1hdHJpeCApIGluc3RlYWQuIiksbmV3IGooKS5zZXRGcm9tTWF0cml4Q29sdW1uKHRoaXMsMyl9O01lLnByb3RvdHlwZS5zZXRSb3RhdGlvbkZyb21RdWF0ZXJuaW9uPWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5zZXRSb3RhdGlvbkZyb21RdWF0ZXJuaW9uKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAubWFrZVJvdGF0aW9uRnJvbVF1YXRlcm5pb24oKS4iKSx0aGlzLm1ha2VSb3RhdGlvbkZyb21RdWF0ZXJuaW9uKGUpfTtNZS5wcm90b3R5cGUubXVsdGlwbHlUb0FycmF5PWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAubXVsdGlwbHlUb0FycmF5KCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07TWUucHJvdG90eXBlLm11bHRpcGx5VmVjdG9yMz1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAubXVsdGlwbHlWZWN0b3IzKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIHZlY3Rvci5hcHBseU1hdHJpeDQoIG1hdHJpeCApIGluc3RlYWQuIiksZS5hcHBseU1hdHJpeDQodGhpcyl9O01lLnByb3RvdHlwZS5tdWx0aXBseVZlY3RvcjQ9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLm11bHRpcGx5VmVjdG9yNCgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSB2ZWN0b3IuYXBwbHlNYXRyaXg0KCBtYXRyaXggKSBpbnN0ZWFkLiIpLGUuYXBwbHlNYXRyaXg0KHRoaXMpfTtNZS5wcm90b3R5cGUubXVsdGlwbHlWZWN0b3IzQXJyYXk9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXg0OiAubXVsdGlwbHlWZWN0b3IzQXJyYXkoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtNZS5wcm90b3R5cGUucm90YXRlQXhpcz1mdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5yb3RhdGVBeGlzKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIFZlY3RvcjMudHJhbnNmb3JtRGlyZWN0aW9uKCBtYXRyaXggKSBpbnN0ZWFkLiIpLGUudHJhbnNmb3JtRGlyZWN0aW9uKHRoaXMpfTtNZS5wcm90b3R5cGUuY3Jvc3NWZWN0b3I9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLmNyb3NzVmVjdG9yKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIHZlY3Rvci5hcHBseU1hdHJpeDQoIG1hdHJpeCApIGluc3RlYWQuIiksZS5hcHBseU1hdHJpeDQodGhpcyl9O01lLnByb3RvdHlwZS50cmFuc2xhdGU9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXg0OiAudHJhbnNsYXRlKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07TWUucHJvdG90eXBlLnJvdGF0ZVg9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXg0OiAucm90YXRlWCgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O01lLnByb3RvdHlwZS5yb3RhdGVZPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWF0cml4NDogLnJvdGF0ZVkoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtNZS5wcm90b3R5cGUucm90YXRlWj1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IC5yb3RhdGVaKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07TWUucHJvdG90eXBlLnJvdGF0ZUJ5QXhpcz1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IC5yb3RhdGVCeUF4aXMoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtNZS5wcm90b3R5cGUuYXBwbHlUb0J1ZmZlckF0dHJpYnV0ZT1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAuYXBwbHlUb0J1ZmZlckF0dHJpYnV0ZSgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBhdHRyaWJ1dGUuYXBwbHlNYXRyaXg0KCBtYXRyaXggKSBpbnN0ZWFkLiIpLGUuYXBwbHlNYXRyaXg0KHRoaXMpfTtNZS5wcm90b3R5cGUuYXBwbHlUb1ZlY3RvcjNBcnJheT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IC5hcHBseVRvVmVjdG9yM0FycmF5KCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07TWUucHJvdG90eXBlLm1ha2VGcnVzdHVtPWZ1bmN0aW9uKGUsdCxyLG4saSxvKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAubWFrZUZydXN0dW0oKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLm1ha2VQZXJzcGVjdGl2ZSggbGVmdCwgcmlnaHQsIHRvcCwgYm90dG9tLCBuZWFyLCBmYXIgKSBpbnN0ZWFkLiIpLHRoaXMubWFrZVBlcnNwZWN0aXZlKGUsdCxuLHIsaSxvKX07TWUucHJvdG90eXBlLmdldEludmVyc2U9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLmdldEludmVyc2UoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgbWF0cml4SW52LmNvcHkoIG1hdHJpeCApLmludmVydCgpOyBpbnN0ZWFkLiIpLHRoaXMuY29weShlKS5pbnZlcnQoKX07JGMucHJvdG90eXBlLmlzSW50ZXJzZWN0aW9uTGluZT1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5QbGFuZTogLmlzSW50ZXJzZWN0aW9uTGluZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmludGVyc2VjdHNMaW5lKCkuIiksdGhpcy5pbnRlcnNlY3RzTGluZShlKX07dmkucHJvdG90eXBlLm11bHRpcGx5VmVjdG9yMz1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5RdWF0ZXJuaW9uOiAubXVsdGlwbHlWZWN0b3IzKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIGlzIG5vdyB2ZWN0b3IuYXBwbHlRdWF0ZXJuaW9uKCBxdWF0ZXJuaW9uICkgaW5zdGVhZC4iKSxlLmFwcGx5UXVhdGVybmlvbih0aGlzKX07dmkucHJvdG90eXBlLmludmVyc2U9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5RdWF0ZXJuaW9uOiAuaW52ZXJzZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gaW52ZXJ0KCkuIiksdGhpcy5pbnZlcnQoKX07SmYucHJvdG90eXBlLmlzSW50ZXJzZWN0aW9uQm94PWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlJheTogLmlzSW50ZXJzZWN0aW9uQm94KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaW50ZXJzZWN0c0JveCgpLiIpLHRoaXMuaW50ZXJzZWN0c0JveChlKX07SmYucHJvdG90eXBlLmlzSW50ZXJzZWN0aW9uUGxhbmU9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUmF5OiAuaXNJbnRlcnNlY3Rpb25QbGFuZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmludGVyc2VjdHNQbGFuZSgpLiIpLHRoaXMuaW50ZXJzZWN0c1BsYW5lKGUpfTtKZi5wcm90b3R5cGUuaXNJbnRlcnNlY3Rpb25TcGhlcmU9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUmF5OiAuaXNJbnRlcnNlY3Rpb25TcGhlcmUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5pbnRlcnNlY3RzU3BoZXJlKCkuIiksdGhpcy5pbnRlcnNlY3RzU3BoZXJlKGUpfTthaS5wcm90b3R5cGUuYXJlYT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlRyaWFuZ2xlOiAuYXJlYSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldEFyZWEoKS4iKSx0aGlzLmdldEFyZWEoKX07YWkucHJvdG90eXBlLmJhcnljb29yZEZyb21Qb2ludD1mdW5jdGlvbihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlRyaWFuZ2xlOiAuYmFyeWNvb3JkRnJvbVBvaW50KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0QmFyeWNvb3JkKCkuIiksdGhpcy5nZXRCYXJ5Y29vcmQoZSx0KX07YWkucHJvdG90eXBlLm1pZHBvaW50PWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlRyaWFuZ2xlOiAubWlkcG9pbnQoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRNaWRwb2ludCgpLiIpLHRoaXMuZ2V0TWlkcG9pbnQoZSl9O2FpLnByb3RvdHlwZW5vcm1hbD1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5UcmlhbmdsZTogLm5vcm1hbCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldE5vcm1hbCgpLiIpLHRoaXMuZ2V0Tm9ybWFsKGUpfTthaS5wcm90b3R5cGUucGxhbmU9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVHJpYW5nbGU6IC5wbGFuZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldFBsYW5lKCkuIiksdGhpcy5nZXRQbGFuZShlKX07YWkuYmFyeWNvb3JkRnJvbVBvaW50PWZ1bmN0aW9uKGUsdCxyLG4saSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVHJpYW5nbGU6IC5iYXJ5Y29vcmRGcm9tUG9pbnQoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRCYXJ5Y29vcmQoKS4iKSxhaS5nZXRCYXJ5Y29vcmQoZSx0LHIsbixpKX07YWkubm9ybWFsPWZ1bmN0aW9uKGUsdCxyLG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlRyaWFuZ2xlOiAubm9ybWFsKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0Tm9ybWFsKCkuIiksYWkuZ2V0Tm9ybWFsKGUsdCxyLG4pfTtLYy5wcm90b3R5cGUuZXh0cmFjdEFsbFBvaW50cz1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5TaGFwZTogLmV4dHJhY3RBbGxQb2ludHMoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLmV4dHJhY3RQb2ludHMoKSBpbnN0ZWFkLiIpLHRoaXMuZXh0cmFjdFBvaW50cyhlKX07S2MucHJvdG90eXBlLmV4dHJ1ZGU9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuU2hhcGU6IC5leHRydWRlKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIEV4dHJ1ZGVHZW9tZXRyeSgpIGluc3RlYWQuIiksbmV3IGhoKHRoaXMsZSl9O0tjLnByb3RvdHlwZS5tYWtlR2VvbWV0cnk9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuU2hhcGU6IC5tYWtlR2VvbWV0cnkoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgU2hhcGVHZW9tZXRyeSgpIGluc3RlYWQuIiksbmV3IFkwKHRoaXMsZSl9O0x0LnByb3RvdHlwZS5mcm9tQXR0cmlidXRlPWZ1bmN0aW9uKGUsdCxyKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IyOiAuZnJvbUF0dHJpYnV0ZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmZyb21CdWZmZXJBdHRyaWJ1dGUoKS4iKSx0aGlzLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSx0LHIpfTtMdC5wcm90b3R5cGUuZGlzdGFuY2VUb01hbmhhdHRhbj1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IyOiAuZGlzdGFuY2VUb01hbmhhdHRhbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLm1hbmhhdHRhbkRpc3RhbmNlVG8oKS4iKSx0aGlzLm1hbmhhdHRhbkRpc3RhbmNlVG8oZSl9O0x0LnByb3RvdHlwZS5sZW5ndGhNYW5oYXR0YW49ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IyOiAubGVuZ3RoTWFuaGF0dGFuKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAubWFuaGF0dGFuTGVuZ3RoKCkuIiksdGhpcy5tYW5oYXR0YW5MZW5ndGgoKX07ai5wcm90b3R5cGUuc2V0RXVsZXJGcm9tUm90YXRpb25NYXRyaXg9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5WZWN0b3IzOiAuc2V0RXVsZXJGcm9tUm90YXRpb25NYXRyaXgoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgRXVsZXIuc2V0RnJvbVJvdGF0aW9uTWF0cml4KCkgaW5zdGVhZC4iKX07ai5wcm90b3R5cGUuc2V0RXVsZXJGcm9tUXVhdGVybmlvbj1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlZlY3RvcjM6IC5zZXRFdWxlckZyb21RdWF0ZXJuaW9uKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIEV1bGVyLnNldEZyb21RdWF0ZXJuaW9uKCkgaW5zdGVhZC4iKX07ai5wcm90b3R5cGUuZ2V0UG9zaXRpb25Gcm9tTWF0cml4PWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5nZXRQb3NpdGlvbkZyb21NYXRyaXgoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5zZXRGcm9tTWF0cml4UG9zaXRpb24oKS4iKSx0aGlzLnNldEZyb21NYXRyaXhQb3NpdGlvbihlKX07ai5wcm90b3R5cGUuZ2V0U2NhbGVGcm9tTWF0cml4PWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5nZXRTY2FsZUZyb21NYXRyaXgoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5zZXRGcm9tTWF0cml4U2NhbGUoKS4iKSx0aGlzLnNldEZyb21NYXRyaXhTY2FsZShlKX07ai5wcm90b3R5cGUuZ2V0Q29sdW1uRnJvbU1hdHJpeD1mdW5jdGlvbihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5nZXRDb2x1bW5Gcm9tTWF0cml4KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuc2V0RnJvbU1hdHJpeENvbHVtbigpLiIpLHRoaXMuc2V0RnJvbU1hdHJpeENvbHVtbih0LGUpfTtqLnByb3RvdHlwZS5hcHBseVByb2plY3Rpb249ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogLmFwcGx5UHJvamVjdGlvbigpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSAuYXBwbHlNYXRyaXg0KCBtICkgaW5zdGVhZC4iKSx0aGlzLmFwcGx5TWF0cml4NChlKX07ai5wcm90b3R5cGUuZnJvbUF0dHJpYnV0ZT1mdW5jdGlvbihlLHQscil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogLmZyb21BdHRyaWJ1dGUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5mcm9tQnVmZmVyQXR0cmlidXRlKCkuIiksdGhpcy5mcm9tQnVmZmVyQXR0cmlidXRlKGUsdCxyKX07ai5wcm90b3R5cGUuZGlzdGFuY2VUb01hbmhhdHRhbj1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuZGlzdGFuY2VUb01hbmhhdHRhbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLm1hbmhhdHRhbkRpc3RhbmNlVG8oKS4iKSx0aGlzLm1hbmhhdHRhbkRpc3RhbmNlVG8oZSl9O2oucHJvdG90eXBlLmxlbmd0aE1hbmhhdHRhbj1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5sZW5ndGhNYW5oYXR0YW4oKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5tYW5oYXR0YW5MZW5ndGgoKS4iKSx0aGlzLm1hbmhhdHRhbkxlbmd0aCgpfTtlbi5wcm90b3R5cGUuZnJvbUF0dHJpYnV0ZT1mdW5jdGlvbihlLHQscil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yNDogLmZyb21BdHRyaWJ1dGUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5mcm9tQnVmZmVyQXR0cmlidXRlKCkuIiksdGhpcy5mcm9tQnVmZmVyQXR0cmlidXRlKGUsdCxyKX07ZW4ucHJvdG90eXBlLmxlbmd0aE1hbmhhdHRhbj1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjQ6IC5sZW5ndGhNYW5oYXR0YW4oKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5tYW5oYXR0YW5MZW5ndGgoKS4iKSx0aGlzLm1hbmhhdHRhbkxlbmd0aCgpfTtvci5wcm90b3R5cGUuZ2V0Q2hpbGRCeU5hbWU9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuT2JqZWN0M0Q6IC5nZXRDaGlsZEJ5TmFtZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldE9iamVjdEJ5TmFtZSgpLiIpLHRoaXMuZ2V0T2JqZWN0QnlOYW1lKGUpfTtvci5wcm90b3R5cGUucmVuZGVyRGVwdGg9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdDNEOiAucmVuZGVyRGVwdGggaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIC5yZW5kZXJPcmRlciwgaW5zdGVhZC4iKX07b3IucHJvdG90eXBlLnRyYW5zbGF0ZT1mdW5jdGlvbihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdDNEOiAudHJhbnNsYXRlKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIC50cmFuc2xhdGVPbkF4aXMoIGF4aXMsIGRpc3RhbmNlICkgaW5zdGVhZC4iKSx0aGlzLnRyYW5zbGF0ZU9uQXhpcyh0LGUpfTtvci5wcm90b3R5cGUuZ2V0V29ybGRSb3RhdGlvbj1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk9iamVjdDNEOiAuZ2V0V29ybGRSb3RhdGlvbigpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBUSFJFRS5PYmplY3QzRC5nZXRXb3JsZFF1YXRlcm5pb24oIHRhcmdldCApIGluc3RlYWQuIil9O29yLnByb3RvdHlwZS5hcHBseU1hdHJpeD1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5PYmplY3QzRDogLmFwcGx5TWF0cml4KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuYXBwbHlNYXRyaXg0KCkuIiksdGhpcy5hcHBseU1hdHJpeDQoZSl9O09iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKG9yLnByb3RvdHlwZSx7ZXVsZXJPcmRlcjp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuT2JqZWN0M0Q6IC5ldWxlck9yZGVyIGlzIG5vdyAucm90YXRpb24ub3JkZXIuIiksdGhpcy5yb3RhdGlvbi5vcmRlcn0sc2V0OmZ1bmN0aW9uKGUpe2NvbnNvbGUud2FybigiVEhSRUUuT2JqZWN0M0Q6IC5ldWxlck9yZGVyIGlzIG5vdyAucm90YXRpb24ub3JkZXIuIiksdGhpcy5yb3RhdGlvbi5vcmRlcj1lfX0sdXNlUXVhdGVybmlvbjp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5PYmplY3QzRDogLnVzZVF1YXRlcm5pb24gaGFzIGJlZW4gcmVtb3ZlZC4gVGhlIGxpYnJhcnkgbm93IHVzZXMgcXVhdGVybmlvbnMgYnkgZGVmYXVsdC4iKX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5PYmplY3QzRDogLnVzZVF1YXRlcm5pb24gaGFzIGJlZW4gcmVtb3ZlZC4gVGhlIGxpYnJhcnkgbm93IHVzZXMgcXVhdGVybmlvbnMgYnkgZGVmYXVsdC4iKX19fSk7ZWkucHJvdG90eXBlLnNldERyYXdNb2RlPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWVzaDogLnNldERyYXdNb2RlKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVGhlIHJlbmRlcmVyIG5vdyBhbHdheXMgYXNzdW1lcyBUSFJFRS5UcmlhbmdsZXNEcmF3TW9kZS4gVHJhbnNmb3JtIHlvdXIgZ2VvbWV0cnkgdmlhIEJ1ZmZlckdlb21ldHJ5VXRpbHMudG9UcmlhbmdsZXNEcmF3TW9kZSgpIGlmIG5lY2Vzc2FyeS4iKX07T2JqZWN0LmRlZmluZVByb3BlcnRpZXMoZWkucHJvdG90eXBlLHtkcmF3TW9kZTp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUuZXJyb3IoIlRIUkVFLk1lc2g6IC5kcmF3TW9kZSBoYXMgYmVlbiByZW1vdmVkLiBUaGUgcmVuZGVyZXIgbm93IGFsd2F5cyBhc3N1bWVzIFRIUkVFLlRyaWFuZ2xlc0RyYXdNb2RlLiIpLENmZX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWVzaDogLmRyYXdNb2RlIGhhcyBiZWVuIHJlbW92ZWQuIFRoZSByZW5kZXJlciBub3cgYWx3YXlzIGFzc3VtZXMgVEhSRUUuVHJpYW5nbGVzRHJhd01vZGUuIFRyYW5zZm9ybSB5b3VyIGdlb21ldHJ5IHZpYSBCdWZmZXJHZW9tZXRyeVV0aWxzLnRvVHJpYW5nbGVzRHJhd01vZGUoKSBpZiBuZWNlc3NhcnkuIil9fX0pO2FNLnByb3RvdHlwZS5pbml0Qm9uZXM9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5Ta2lubmVkTWVzaDogaW5pdEJvbmVzKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07VWkucHJvdG90eXBlLnNldExlbnM9ZnVuY3Rpb24oZSx0KXtjb25zb2xlLndhcm4oIlRIUkVFLlBlcnNwZWN0aXZlQ2FtZXJhLnNldExlbnMgaXMgZGVwcmVjYXRlZC4gVXNlIC5zZXRGb2NhbExlbmd0aCBhbmQgLmZpbG1HYXVnZSBmb3IgYSBwaG90b2dyYXBoaWMgc2V0dXAuIiksdCE9PXZvaWQgMCYmKHRoaXMuZmlsbUdhdWdlPXQpLHRoaXMuc2V0Rm9jYWxMZW5ndGgoZSl9O09iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKE9sLnByb3RvdHlwZSx7b25seVNoYWRvdzp7c2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLm9ubHlTaGFkb3cgaGFzIGJlZW4gcmVtb3ZlZC4iKX19LHNoYWRvd0NhbWVyYUZvdjp7c2V0OmZ1bmN0aW9uKGUpe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dDYW1lcmFGb3YgaXMgbm93IC5zaGFkb3cuY2FtZXJhLmZvdi4iKSx0aGlzLnNoYWRvdy5jYW1lcmEuZm92PWV9fSxzaGFkb3dDYW1lcmFMZWZ0OntzZXQ6ZnVuY3Rpb24oZSl7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0NhbWVyYUxlZnQgaXMgbm93IC5zaGFkb3cuY2FtZXJhLmxlZnQuIiksdGhpcy5zaGFkb3cuY2FtZXJhLmxlZnQ9ZX19LHNoYWRvd0NhbWVyYVJpZ2h0OntzZXQ6ZnVuY3Rpb24oZSl7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0NhbWVyYVJpZ2h0IGlzIG5vdyAuc2hhZG93LmNhbWVyYS5yaWdodC4iKSx0aGlzLnNoYWRvdy5jYW1lcmEucmlnaHQ9ZX19LHNoYWRvd0NhbWVyYVRvcDp7c2V0OmZ1bmN0aW9uKGUpe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dDYW1lcmFUb3AgaXMgbm93IC5zaGFkb3cuY2FtZXJhLnRvcC4iKSx0aGlzLnNoYWRvdy5jYW1lcmEudG9wPWV9fSxzaGFkb3dDYW1lcmFCb3R0b206e3NldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93Q2FtZXJhQm90dG9tIGlzIG5vdyAuc2hhZG93LmNhbWVyYS5ib3R0b20uIiksdGhpcy5zaGFkb3cuY2FtZXJhLmJvdHRvbT1lfX0sc2hhZG93Q2FtZXJhTmVhcjp7c2V0OmZ1bmN0aW9uKGUpe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dDYW1lcmFOZWFyIGlzIG5vdyAuc2hhZG93LmNhbWVyYS5uZWFyLiIpLHRoaXMuc2hhZG93LmNhbWVyYS5uZWFyPWV9fSxzaGFkb3dDYW1lcmFGYXI6e3NldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93Q2FtZXJhRmFyIGlzIG5vdyAuc2hhZG93LmNhbWVyYS5mYXIuIiksdGhpcy5zaGFkb3cuY2FtZXJhLmZhcj1lfX0sc2hhZG93Q2FtZXJhVmlzaWJsZTp7c2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0NhbWVyYVZpc2libGUgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIG5ldyBUSFJFRS5DYW1lcmFIZWxwZXIoIGxpZ2h0LnNoYWRvdy5jYW1lcmEgKSBpbnN0ZWFkLiIpfX0sc2hhZG93Qmlhczp7c2V0OmZ1bmN0aW9uKGUpe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dCaWFzIGlzIG5vdyAuc2hhZG93LmJpYXMuIiksdGhpcy5zaGFkb3cuYmlhcz1lfX0sc2hhZG93RGFya25lc3M6e3NldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dEYXJrbmVzcyBoYXMgYmVlbiByZW1vdmVkLiIpfX0sc2hhZG93TWFwV2lkdGg6e3NldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93TWFwV2lkdGggaXMgbm93IC5zaGFkb3cubWFwU2l6ZS53aWR0aC4iKSx0aGlzLnNoYWRvdy5tYXBTaXplLndpZHRoPWV9fSxzaGFkb3dNYXBIZWlnaHQ6e3NldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93TWFwSGVpZ2h0IGlzIG5vdyAuc2hhZG93Lm1hcFNpemUuaGVpZ2h0LiIpLHRoaXMuc2hhZG93Lm1hcFNpemUuaGVpZ2h0PWV9fX0pO09iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKEplLnByb3RvdHlwZSx7bGVuZ3RoOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGU6IC5sZW5ndGggaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIC5jb3VudCBpbnN0ZWFkLiIpLHRoaXMuYXJyYXkubGVuZ3RofX0sZHluYW1pYzp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyQXR0cmlidXRlOiAuZHluYW1pYyBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnVzYWdlIGluc3RlYWQuIiksdGhpcy51c2FnZT09PVkzfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZTogLmR5bmFtaWMgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIC51c2FnZSBpbnN0ZWFkLiIpLHRoaXMuc2V0VXNhZ2UoWTMpfX19KTtKZS5wcm90b3R5cGUuc2V0RHluYW1pYz1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGU6IC5zZXREeW5hbWljKCkgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIC5zZXRVc2FnZSgpIGluc3RlYWQuIiksdGhpcy5zZXRVc2FnZShlPT09ITA/WTM6VzMpLHRoaXN9O0plLnByb3RvdHlwZS5jb3B5SW5kaWNlc0FycmF5PWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuQnVmZmVyQXR0cmlidXRlOiAuY29weUluZGljZXNBcnJheSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9LEplLnByb3RvdHlwZS5zZXRBcnJheT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZTogLnNldEFycmF5IGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBCdWZmZXJHZW9tZXRyeSAuc2V0QXR0cmlidXRlIHRvIHJlcGxhY2UvcmVzaXplIGF0dHJpYnV0ZSBidWZmZXJzIil9O1BlLnByb3RvdHlwZS5hZGRJbmRleD1mdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuYWRkSW5kZXgoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5zZXRJbmRleCgpLiIpLHRoaXMuc2V0SW5kZXgoZSl9O1BlLnByb3RvdHlwZS5hZGRBdHRyaWJ1dGU9ZnVuY3Rpb24oZSx0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLmFkZEF0dHJpYnV0ZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLnNldEF0dHJpYnV0ZSgpLiIpLCEodCYmdC5pc0J1ZmZlckF0dHJpYnV0ZSkmJiEodCYmdC5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlKT8oY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLmFkZEF0dHJpYnV0ZSgpIG5vdyBleHBlY3RzICggbmFtZSwgYXR0cmlidXRlICkuIiksdGhpcy5zZXRBdHRyaWJ1dGUoZSxuZXcgSmUoYXJndW1lbnRzWzFdLGFyZ3VtZW50c1syXSkpKTplPT09ImluZGV4Ij8oY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeS5hZGRBdHRyaWJ1dGU6IFVzZSAuc2V0SW5kZXgoKSBmb3IgaW5kZXggYXR0cmlidXRlLiIpLHRoaXMuc2V0SW5kZXgodCksdGhpcyk6dGhpcy5zZXRBdHRyaWJ1dGUoZSx0KX07UGUucHJvdG90eXBlLmFkZERyYXdDYWxsPWZ1bmN0aW9uKGUsdCxyKXtyIT09dm9pZCAwJiZjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuYWRkRHJhd0NhbGwoKSBubyBsb25nZXIgc3VwcG9ydHMgaW5kZXhPZmZzZXQuIiksY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLmFkZERyYXdDYWxsKCkgaXMgbm93IC5hZGRHcm91cCgpLiIpLHRoaXMuYWRkR3JvdXAoZSx0KX07UGUucHJvdG90eXBlLmNsZWFyRHJhd0NhbGxzPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLmNsZWFyRHJhd0NhbGxzKCkgaXMgbm93IC5jbGVhckdyb3VwcygpLiIpLHRoaXMuY2xlYXJHcm91cHMoKX07UGUucHJvdG90eXBlLmNvbXB1dGVPZmZzZXRzPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLmNvbXB1dGVPZmZzZXRzKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07UGUucHJvdG90eXBlLnJlbW92ZUF0dHJpYnV0ZT1mdW5jdGlvbihlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLnJlbW92ZUF0dHJpYnV0ZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmRlbGV0ZUF0dHJpYnV0ZSgpLiIpLHRoaXMuZGVsZXRlQXR0cmlidXRlKGUpfTtQZS5wcm90b3R5cGUuYXBwbHlNYXRyaXg9ZnVuY3Rpb24oZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5hcHBseU1hdHJpeCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmFwcGx5TWF0cml4NCgpLiIpLHRoaXMuYXBwbHlNYXRyaXg0KGUpfTtPYmplY3QuZGVmaW5lUHJvcGVydGllcyhQZS5wcm90b3R5cGUse2RyYXdjYWxsczp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUuZXJyb3IoIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuZHJhd2NhbGxzIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdyb3Vwcy4iKSx0aGlzLmdyb3Vwc319LG9mZnNldHM6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAub2Zmc2V0cyBoYXMgYmVlbiByZW5hbWVkIHRvIC5ncm91cHMuIiksdGhpcy5ncm91cHN9fX0pO2VtLnByb3RvdHlwZS5zZXREeW5hbWljPWZ1bmN0aW9uKGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkludGVybGVhdmVkQnVmZmVyOiAuc2V0RHluYW1pYygpIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSAuc2V0VXNhZ2UoKSBpbnN0ZWFkLiIpLHRoaXMuc2V0VXNhZ2UoZT09PSEwP1kzOlczKSx0aGlzfTtlbS5wcm90b3R5cGUuc2V0QXJyYXk9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5JbnRlcmxlYXZlZEJ1ZmZlcjogLnNldEFycmF5IGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBCdWZmZXJHZW9tZXRyeSAuc2V0QXR0cmlidXRlIHRvIHJlcGxhY2UvcmVzaXplIGF0dHJpYnV0ZSBidWZmZXJzIil9O2hoLnByb3RvdHlwZS5nZXRBcnJheXM9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5FeHRydWRlR2VvbWV0cnk6IC5nZXRBcnJheXMoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtoaC5wcm90b3R5cGUuYWRkU2hhcGVMaXN0PWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuRXh0cnVkZUdlb21ldHJ5OiAuYWRkU2hhcGVMaXN0KCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07aGgucHJvdG90eXBlLmFkZFNoYXBlPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuRXh0cnVkZUdlb21ldHJ5OiAuYWRkU2hhcGUoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtxMC5wcm90b3R5cGUuZGlzcG9zZT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlNjZW5lOiAuZGlzcG9zZSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O2dNLnByb3RvdHlwZS5vblVwZGF0ZT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlVuaWZvcm06IC5vblVwZGF0ZSgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBvYmplY3Qub25CZWZvcmVSZW5kZXIoKSBpbnN0ZWFkLiIpLHRoaXN9O09iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHFpLnByb3RvdHlwZSx7d3JhcEFyb3VuZDp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5NYXRlcmlhbDogLndyYXBBcm91bmQgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5NYXRlcmlhbDogLndyYXBBcm91bmQgaGFzIGJlZW4gcmVtb3ZlZC4iKX19LG92ZXJkcmF3OntnZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLk1hdGVyaWFsOiAub3ZlcmRyYXcgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5NYXRlcmlhbDogLm92ZXJkcmF3IGhhcyBiZWVuIHJlbW92ZWQuIil9fSx3cmFwUkdCOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRlcmlhbDogLndyYXBSR0IgaGFzIGJlZW4gcmVtb3ZlZC4iKSxuZXcgbmV9fSxzaGFkaW5nOntnZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS4iK3RoaXMudHlwZSsiOiAuc2hhZGluZyBoYXMgYmVlbiByZW1vdmVkLiBVc2UgdGhlIGJvb2xlYW4gLmZsYXRTaGFkaW5nIGluc3RlYWQuIil9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLiIrdGhpcy50eXBlKyI6IC5zaGFkaW5nIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSB0aGUgYm9vbGVhbiAuZmxhdFNoYWRpbmcgaW5zdGVhZC4iKSx0aGlzLmZsYXRTaGFkaW5nPWU9PT1QaHR9fSxzdGVuY2lsTWFzazp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuIit0aGlzLnR5cGUrIjogLnN0ZW5jaWxNYXNrIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSAuc3RlbmNpbEZ1bmNNYXNrIGluc3RlYWQuIiksdGhpcy5zdGVuY2lsRnVuY01hc2t9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLiIrdGhpcy50eXBlKyI6IC5zdGVuY2lsTWFzayBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLnN0ZW5jaWxGdW5jTWFzayBpbnN0ZWFkLiIpLHRoaXMuc3RlbmNpbEZ1bmNNYXNrPWV9fSx2ZXJ0ZXhUYW5nZW50czp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS4iK3RoaXMudHlwZSsiOiAudmVydGV4VGFuZ2VudHMgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS4iK3RoaXMudHlwZSsiOiAudmVydGV4VGFuZ2VudHMgaGFzIGJlZW4gcmVtb3ZlZC4iKX19fSk7T2JqZWN0LmRlZmluZVByb3BlcnRpZXMobGgucHJvdG90eXBlLHtkZXJpdmF0aXZlczp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuU2hhZGVyTWF0ZXJpYWw6IC5kZXJpdmF0aXZlcyBoYXMgYmVlbiBtb3ZlZCB0byAuZXh0ZW5zaW9ucy5kZXJpdmF0aXZlcy4iKSx0aGlzLmV4dGVuc2lvbnMuZGVyaXZhdGl2ZXN9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLiBTaGFkZXJNYXRlcmlhbDogLmRlcml2YXRpdmVzIGhhcyBiZWVuIG1vdmVkIHRvIC5leHRlbnNpb25zLmRlcml2YXRpdmVzLiIpLHRoaXMuZXh0ZW5zaW9ucy5kZXJpdmF0aXZlcz1lfX19KTtybi5wcm90b3R5cGUuY2xlYXJUYXJnZXQ9ZnVuY3Rpb24oZSx0LHIsbil7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuY2xlYXJUYXJnZXQoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnNldFJlbmRlclRhcmdldCgpIGFuZCAuY2xlYXIoKSBpbnN0ZWFkLiIpLHRoaXMuc2V0UmVuZGVyVGFyZ2V0KGUpLHRoaXMuY2xlYXIodCxyLG4pfTtybi5wcm90b3R5cGUuYW5pbWF0ZT1mdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5hbmltYXRlKCkgaXMgbm93IC5zZXRBbmltYXRpb25Mb29wKCkuIiksdGhpcy5zZXRBbmltYXRpb25Mb29wKGUpfTtybi5wcm90b3R5cGUuZ2V0Q3VycmVudFJlbmRlclRhcmdldD1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRDdXJyZW50UmVuZGVyVGFyZ2V0KCkgaXMgbm93IC5nZXRSZW5kZXJUYXJnZXQoKS4iKSx0aGlzLmdldFJlbmRlclRhcmdldCgpfTtybi5wcm90b3R5cGUuZ2V0TWF4QW5pc290cm9weT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRNYXhBbmlzb3Ryb3B5KCkgaXMgbm93IC5jYXBhYmlsaXRpZXMuZ2V0TWF4QW5pc290cm9weSgpLiIpLHRoaXMuY2FwYWJpbGl0aWVzLmdldE1heEFuaXNvdHJvcHkoKX07cm4ucHJvdG90eXBlLmdldFByZWNpc2lvbj1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRQcmVjaXNpb24oKSBpcyBub3cgLmNhcGFiaWxpdGllcy5wcmVjaXNpb24uIiksdGhpcy5jYXBhYmlsaXRpZXMucHJlY2lzaW9ufTtybi5wcm90b3R5cGUucmVzZXRHTFN0YXRlPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnJlc2V0R0xTdGF0ZSgpIGlzIG5vdyAuc3RhdGUucmVzZXQoKS4iKSx0aGlzLnN0YXRlLnJlc2V0KCl9O3JuLnByb3RvdHlwZS5zdXBwb3J0c0Zsb2F0VGV4dHVyZXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNGbG9hdFRleHR1cmVzKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ09FU190ZXh0dXJlX2Zsb2F0JyApLiIpLHRoaXMuZXh0ZW5zaW9ucy5nZXQoIk9FU190ZXh0dXJlX2Zsb2F0Iil9O3JuLnByb3RvdHlwZS5zdXBwb3J0c0hhbGZGbG9hdFRleHR1cmVzPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnN1cHBvcnRzSGFsZkZsb2F0VGV4dHVyZXMoKSBpcyBub3cgLmV4dGVuc2lvbnMuZ2V0KCAnT0VTX3RleHR1cmVfaGFsZl9mbG9hdCcgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJPRVNfdGV4dHVyZV9oYWxmX2Zsb2F0Iil9O3JuLnByb3RvdHlwZS5zdXBwb3J0c1N0YW5kYXJkRGVyaXZhdGl2ZXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNTdGFuZGFyZERlcml2YXRpdmVzKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ09FU19zdGFuZGFyZF9kZXJpdmF0aXZlcycgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJPRVNfc3RhbmRhcmRfZGVyaXZhdGl2ZXMiKX07cm4ucHJvdG90eXBlLnN1cHBvcnRzQ29tcHJlc3NlZFRleHR1cmVTM1RDPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnN1cHBvcnRzQ29tcHJlc3NlZFRleHR1cmVTM1RDKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjJyApLiIpLHRoaXMuZXh0ZW5zaW9ucy5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjIil9O3JuLnByb3RvdHlwZS5zdXBwb3J0c0NvbXByZXNzZWRUZXh0dXJlUFZSVEM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNDb21wcmVzc2VkVGV4dHVyZVBWUlRDKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9wdnJ0YycgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfcHZydGMiKX07cm4ucHJvdG90eXBlLnN1cHBvcnRzQmxlbmRNaW5NYXg9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNCbGVuZE1pbk1heCgpIGlzIG5vdyAuZXh0ZW5zaW9ucy5nZXQoICdFWFRfYmxlbmRfbWlubWF4JyApLiIpLHRoaXMuZXh0ZW5zaW9ucy5nZXQoIkVYVF9ibGVuZF9taW5tYXgiKX07cm4ucHJvdG90eXBlLnN1cHBvcnRzVmVydGV4VGV4dHVyZXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNWZXJ0ZXhUZXh0dXJlcygpIGlzIG5vdyAuY2FwYWJpbGl0aWVzLnZlcnRleFRleHR1cmVzLiIpLHRoaXMuY2FwYWJpbGl0aWVzLnZlcnRleFRleHR1cmVzfTtybi5wcm90b3R5cGUuc3VwcG9ydHNJbnN0YW5jZWRBcnJheXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNJbnN0YW5jZWRBcnJheXMoKSBpcyBub3cgLmV4dGVuc2lvbnMuZ2V0KCAnQU5HTEVfaW5zdGFuY2VkX2FycmF5cycgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJBTkdMRV9pbnN0YW5jZWRfYXJyYXlzIil9O3JuLnByb3RvdHlwZS5lbmFibGVTY2lzc29yVGVzdD1mdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5lbmFibGVTY2lzc29yVGVzdCgpIGlzIG5vdyAuc2V0U2Npc3NvclRlc3QoKS4iKSx0aGlzLnNldFNjaXNzb3JUZXN0KGUpfTtybi5wcm90b3R5cGUuaW5pdE1hdGVyaWFsPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuaW5pdE1hdGVyaWFsKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX07cm4ucHJvdG90eXBlLmFkZFByZVBsdWdpbj1mdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLmFkZFByZVBsdWdpbigpIGhhcyBiZWVuIHJlbW92ZWQuIil9O3JuLnByb3RvdHlwZS5hZGRQb3N0UGx1Z2luPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuYWRkUG9zdFBsdWdpbigpIGhhcyBiZWVuIHJlbW92ZWQuIil9O3JuLnByb3RvdHlwZS51cGRhdGVTaGFkb3dNYXA9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC51cGRhdGVTaGFkb3dNYXAoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtybi5wcm90b3R5cGUuc2V0RmFjZUN1bGxpbmc9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zZXRGYWNlQ3VsbGluZygpIGhhcyBiZWVuIHJlbW92ZWQuIil9O3JuLnByb3RvdHlwZS5hbGxvY1RleHR1cmVVbml0PWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuYWxsb2NUZXh0dXJlVW5pdCgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O3JuLnByb3RvdHlwZS5zZXRUZXh0dXJlPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc2V0VGV4dHVyZSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O3JuLnByb3RvdHlwZS5zZXRUZXh0dXJlMkQ9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zZXRUZXh0dXJlMkQoKSBoYXMgYmVlbiByZW1vdmVkLiIpfTtybi5wcm90b3R5cGUuc2V0VGV4dHVyZUN1YmU9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zZXRUZXh0dXJlQ3ViZSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9O3JuLnByb3RvdHlwZS5nZXRBY3RpdmVNaXBNYXBMZXZlbD1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRBY3RpdmVNaXBNYXBMZXZlbCgpIGlzIG5vdyAuZ2V0QWN0aXZlTWlwbWFwTGV2ZWwoKS4iKSx0aGlzLmdldEFjdGl2ZU1pcG1hcExldmVsKCl9O09iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHJuLnByb3RvdHlwZSx7c2hhZG93TWFwRW5hYmxlZDp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc2hhZG93TWFwLmVuYWJsZWR9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXBFbmFibGVkIGlzIG5vdyAuc2hhZG93TWFwLmVuYWJsZWQuIiksdGhpcy5zaGFkb3dNYXAuZW5hYmxlZD1lfX0sc2hhZG93TWFwVHlwZTp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc2hhZG93TWFwLnR5cGV9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXBUeXBlIGlzIG5vdyAuc2hhZG93TWFwLnR5cGUuIiksdGhpcy5zaGFkb3dNYXAudHlwZT1lfX0sc2hhZG93TWFwQ3VsbEZhY2U6e2dldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcEN1bGxGYWNlIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcEN1bGxGYWNlIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9fSxjb250ZXh0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuY29udGV4dCBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLmdldENvbnRleHQoKSBpbnN0ZWFkLiIpLHRoaXMuZ2V0Q29udGV4dCgpfX0sdnI6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC52ciBoYXMgYmVlbiByZW5hbWVkIHRvIC54ciIpLHRoaXMueHJ9fSxnYW1tYUlucHV0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuZ2FtbWFJbnB1dCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgdGhlIGVuY29kaW5nIGZvciB0ZXh0dXJlcyB2aWEgVGV4dHVyZS5lbmNvZGluZyBpbnN0ZWFkLiIpLCExfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nYW1tYUlucHV0IGhhcyBiZWVuIHJlbW92ZWQuIFNldCB0aGUgZW5jb2RpbmcgZm9yIHRleHR1cmVzIHZpYSBUZXh0dXJlLmVuY29kaW5nIGluc3RlYWQuIil9fSxnYW1tYU91dHB1dDp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLmdhbW1hT3V0cHV0IGhhcyBiZWVuIHJlbW92ZWQuIFNldCBXZWJHTFJlbmRlcmVyLm91dHB1dEVuY29kaW5nIGluc3RlYWQuIiksITF9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nYW1tYU91dHB1dCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgV2ViR0xSZW5kZXJlci5vdXRwdXRFbmNvZGluZyBpbnN0ZWFkLiIpLHRoaXMub3V0cHV0RW5jb2Rpbmc9ZT09PSEwP1luOlFkfX0sdG9uZU1hcHBpbmdXaGl0ZVBvaW50OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAudG9uZU1hcHBpbmdXaGl0ZVBvaW50IGhhcyBiZWVuIHJlbW92ZWQuIiksMX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAudG9uZU1hcHBpbmdXaGl0ZVBvaW50IGhhcyBiZWVuIHJlbW92ZWQuIil9fSxnYW1tYUZhY3Rvcjp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLmdhbW1hRmFjdG9yIGhhcyBiZWVuIHJlbW92ZWQuIiksMn0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuZ2FtbWFGYWN0b3IgaGFzIGJlZW4gcmVtb3ZlZC4iKX19fSk7T2JqZWN0LmRlZmluZVByb3BlcnRpZXMoamZlLnByb3RvdHlwZSx7Y3VsbEZhY2U6e2dldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcC5jdWxsRmFjZSBoYXMgYmVlbiByZW1vdmVkLiBTZXQgTWF0ZXJpYWwuc2hhZG93U2lkZSBpbnN0ZWFkLiIpfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXAuY3VsbEZhY2UgaGFzIGJlZW4gcmVtb3ZlZC4gU2V0IE1hdGVyaWFsLnNoYWRvd1NpZGUgaW5zdGVhZC4iKX19LHJlbmRlclJldmVyc2VTaWRlZDp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc2hhZG93TWFwLnJlbmRlclJldmVyc2VTaWRlZCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgTWF0ZXJpYWwuc2hhZG93U2lkZSBpbnN0ZWFkLiIpfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXAucmVuZGVyUmV2ZXJzZVNpZGVkIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9fSxyZW5kZXJTaW5nbGVTaWRlZDp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc2hhZG93TWFwLnJlbmRlclNpbmdsZVNpZGVkIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcC5yZW5kZXJTaW5nbGVTaWRlZCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgTWF0ZXJpYWwuc2hhZG93U2lkZSBpbnN0ZWFkLiIpfX19KTtmdW5jdGlvbiBaMHIoZSx0LHIpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0Q3ViZSggd2lkdGgsIGhlaWdodCwgb3B0aW9ucyApIGlzIG5vdyBXZWJHTEN1YmVSZW5kZXJUYXJnZXQoIHNpemUsIG9wdGlvbnMgKS4iKSxuZXcgUTMoZSxyKX1PYmplY3QuZGVmaW5lUHJvcGVydGllcyh1cy5wcm90b3R5cGUse3dyYXBTOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLndyYXBTIGlzIG5vdyAudGV4dHVyZS53cmFwUy4iKSx0aGlzLnRleHR1cmUud3JhcFN9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAud3JhcFMgaXMgbm93IC50ZXh0dXJlLndyYXBTLiIpLHRoaXMudGV4dHVyZS53cmFwUz1lfX0sd3JhcFQ6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAud3JhcFQgaXMgbm93IC50ZXh0dXJlLndyYXBULiIpLHRoaXMudGV4dHVyZS53cmFwVH0sc2V0OmZ1bmN0aW9uKGUpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC53cmFwVCBpcyBub3cgLnRleHR1cmUud3JhcFQuIiksdGhpcy50ZXh0dXJlLndyYXBUPWV9fSxtYWdGaWx0ZXI6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAubWFnRmlsdGVyIGlzIG5vdyAudGV4dHVyZS5tYWdGaWx0ZXIuIiksdGhpcy50ZXh0dXJlLm1hZ0ZpbHRlcn0sc2V0OmZ1bmN0aW9uKGUpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5tYWdGaWx0ZXIgaXMgbm93IC50ZXh0dXJlLm1hZ0ZpbHRlci4iKSx0aGlzLnRleHR1cmUubWFnRmlsdGVyPWV9fSxtaW5GaWx0ZXI6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAubWluRmlsdGVyIGlzIG5vdyAudGV4dHVyZS5taW5GaWx0ZXIuIiksdGhpcy50ZXh0dXJlLm1pbkZpbHRlcn0sc2V0OmZ1bmN0aW9uKGUpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5taW5GaWx0ZXIgaXMgbm93IC50ZXh0dXJlLm1pbkZpbHRlci4iKSx0aGlzLnRleHR1cmUubWluRmlsdGVyPWV9fSxhbmlzb3Ryb3B5OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLmFuaXNvdHJvcHkgaXMgbm93IC50ZXh0dXJlLmFuaXNvdHJvcHkuIiksdGhpcy50ZXh0dXJlLmFuaXNvdHJvcHl9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAuYW5pc290cm9weSBpcyBub3cgLnRleHR1cmUuYW5pc290cm9weS4iKSx0aGlzLnRleHR1cmUuYW5pc290cm9weT1lfX0sb2Zmc2V0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLm9mZnNldCBpcyBub3cgLnRleHR1cmUub2Zmc2V0LiIpLHRoaXMudGV4dHVyZS5vZmZzZXR9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAub2Zmc2V0IGlzIG5vdyAudGV4dHVyZS5vZmZzZXQuIiksdGhpcy50ZXh0dXJlLm9mZnNldD1lfX0scmVwZWF0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLnJlcGVhdCBpcyBub3cgLnRleHR1cmUucmVwZWF0LiIpLHRoaXMudGV4dHVyZS5yZXBlYXR9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAucmVwZWF0IGlzIG5vdyAudGV4dHVyZS5yZXBlYXQuIiksdGhpcy50ZXh0dXJlLnJlcGVhdD1lfX0sZm9ybWF0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLmZvcm1hdCBpcyBub3cgLnRleHR1cmUuZm9ybWF0LiIpLHRoaXMudGV4dHVyZS5mb3JtYXR9LHNldDpmdW5jdGlvbihlKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAuZm9ybWF0IGlzIG5vdyAudGV4dHVyZS5mb3JtYXQuIiksdGhpcy50ZXh0dXJlLmZvcm1hdD1lfX0sdHlwZTp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC50eXBlIGlzIG5vdyAudGV4dHVyZS50eXBlLiIpLHRoaXMudGV4dHVyZS50eXBlfSxzZXQ6ZnVuY3Rpb24oZSl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLnR5cGUgaXMgbm93IC50ZXh0dXJlLnR5cGUuIiksdGhpcy50ZXh0dXJlLnR5cGU9ZX19LGdlbmVyYXRlTWlwbWFwczp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5nZW5lcmF0ZU1pcG1hcHMgaXMgbm93IC50ZXh0dXJlLmdlbmVyYXRlTWlwbWFwcy4iKSx0aGlzLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzfSxzZXQ6ZnVuY3Rpb24oZSl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLmdlbmVyYXRlTWlwbWFwcyBpcyBub3cgLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzLiIpLHRoaXMudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM9ZX19fSk7TjYucHJvdG90eXBlLmxvYWQ9ZnVuY3Rpb24oZSl7Y29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogLmxvYWQgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIFRIUkVFLkF1ZGlvTG9hZGVyIGluc3RlYWQuIik7bGV0IHQ9dGhpcztyZXR1cm4gbmV3IE5VKCkubG9hZChlLGZ1bmN0aW9uKG4pe3Quc2V0QnVmZmVyKG4pfSksdGhpc307elUucHJvdG90eXBlLmdldERhdGE9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5BdWRpb0FuYWx5c2VyOiAuZ2V0RGF0YSgpIGlzIG5vdyAuZ2V0RnJlcXVlbmN5RGF0YSgpLiIpLHRoaXMuZ2V0RnJlcXVlbmN5RGF0YSgpfTtKMy5wcm90b3R5cGUudXBkYXRlQ3ViZU1hcD1mdW5jdGlvbihlLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkN1YmVDYW1lcmE6IC51cGRhdGVDdWJlTWFwKCkgaXMgbm93IC51cGRhdGUoKS4iKSx0aGlzLnVwZGF0ZShlLHQpfTtKMy5wcm90b3R5cGUuY2xlYXI9ZnVuY3Rpb24oZSx0LHIsbil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQ3ViZUNhbWVyYTogLmNsZWFyKCkgaXMgbm93IC5yZW5kZXJUYXJnZXQuY2xlYXIoKS4iKSx0aGlzLnJlbmRlclRhcmdldC5jbGVhcihlLHQscixuKX07S2YuY3Jvc3NPcmlnaW49dm9pZCAwO0tmLmxvYWRUZXh0dXJlPWZ1bmN0aW9uKGUsdCxyLG4pe2NvbnNvbGUud2FybigiVEhSRUUuSW1hZ2VVdGlscy5sb2FkVGV4dHVyZSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgVEhSRUUuVGV4dHVyZUxvYWRlcigpIGluc3RlYWQuIik7bGV0IGk9bmV3IENVO2kuc2V0Q3Jvc3NPcmlnaW4odGhpcy5jcm9zc09yaWdpbik7bGV0IG89aS5sb2FkKGUscix2b2lkIDAsbik7cmV0dXJuIHQmJihvLm1hcHBpbmc9dCksb307S2YubG9hZFRleHR1cmVDdWJlPWZ1bmN0aW9uKGUsdCxyLG4pe2NvbnNvbGUud2FybigiVEhSRUUuSW1hZ2VVdGlscy5sb2FkVGV4dHVyZUN1YmUgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIFRIUkVFLkN1YmVUZXh0dXJlTG9hZGVyKCkgaW5zdGVhZC4iKTtsZXQgaT1uZXcgRVU7aS5zZXRDcm9zc09yaWdpbih0aGlzLmNyb3NzT3JpZ2luKTtsZXQgbz1pLmxvYWQoZSxyLHZvaWQgMCxuKTtyZXR1cm4gdCYmKG8ubWFwcGluZz10KSxvfTtLZi5sb2FkQ29tcHJlc3NlZFRleHR1cmU9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5JbWFnZVV0aWxzLmxvYWRDb21wcmVzc2VkVGV4dHVyZSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgVEhSRUUuRERTTG9hZGVyIGluc3RlYWQuIil9O0tmLmxvYWRDb21wcmVzc2VkVGV4dHVyZUN1YmU9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5JbWFnZVV0aWxzLmxvYWRDb21wcmVzc2VkVGV4dHVyZUN1YmUgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIFRIUkVFLkREU0xvYWRlciBpbnN0ZWFkLiIpfTtmdW5jdGlvbiBKMHIoKXtjb25zb2xlLmVycm9yKCJUSFJFRS5DYW52YXNSZW5kZXJlciBoYXMgYmVlbiByZW1vdmVkIil9ZnVuY3Rpb24gUTByKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuSlNPTkxvYWRlciBoYXMgYmVlbiByZW1vdmVkLiIpfXZhciB0X3I9e2NyZWF0ZU11bHRpTWF0ZXJpYWxPYmplY3Q6ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5TY2VuZVV0aWxzIGhhcyBiZWVuIG1vdmVkIHRvIC9leGFtcGxlcy9qc20vdXRpbHMvU2NlbmVVdGlscy5qcyIpfSxkZXRhY2g6ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5TY2VuZVV0aWxzIGhhcyBiZWVuIG1vdmVkIHRvIC9leGFtcGxlcy9qc20vdXRpbHMvU2NlbmVVdGlscy5qcyIpfSxhdHRhY2g6ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5TY2VuZVV0aWxzIGhhcyBiZWVuIG1vdmVkIHRvIC9leGFtcGxlcy9qc20vdXRpbHMvU2NlbmVVdGlscy5qcyIpfX07ZnVuY3Rpb24gZV9yKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTGVuc0ZsYXJlIGhhcyBiZWVuIG1vdmVkIHRvIC9leGFtcGxlcy9qc20vb2JqZWN0cy9MZW5zZmxhcmUuanMiKX1mdW5jdGlvbiByX3IoKXtyZXR1cm4gY29uc29sZS5lcnJvcigiVEhSRUUuUGFyYW1ldHJpY0dlb21ldHJ5IGhhcyBiZWVuIG1vdmVkIHRvIC9leGFtcGxlcy9qc20vZ2VvbWV0cmllcy9QYXJhbWV0cmljR2VvbWV0cnkuanMiKSxuZXcgUGV9ZnVuY3Rpb24gbl9yKCl7cmV0dXJuIGNvbnNvbGUuZXJyb3IoIlRIUkVFLlRleHRHZW9tZXRyeSBoYXMgYmVlbiBtb3ZlZCB0byAvZXhhbXBsZXMvanNtL2dlb21ldHJpZXMvVGV4dEdlb21ldHJ5LmpzIiksbmV3IFBlfWZ1bmN0aW9uIGlfcigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkZvbnRMb2FkZXIgaGFzIGJlZW4gbW92ZWQgdG8gL2V4YW1wbGVzL2pzbS9sb2FkZXJzL0ZvbnRMb2FkZXIuanMiKX1mdW5jdGlvbiBvX3IoKXtjb25zb2xlLmVycm9yKCJUSFJFRS5Gb250IGhhcyBiZWVuIG1vdmVkIHRvIC9leGFtcGxlcy9qc20vbG9hZGVycy9Gb250TG9hZGVyLmpzIil9ZnVuY3Rpb24gYV9yKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuSW1tZWRpYXRlUmVuZGVyT2JqZWN0IGhhcyBiZWVuIHJlbW92ZWQuIil9dHlwZW9mIF9fVEhSRUVfREVWVE9PTFNfXyE9InVuZGVmaW5lZCImJl9fVEhSRUVfREVWVE9PTFNfXy5kaXNwYXRjaEV2ZW50KG5ldyBDdXN0b21FdmVudCgicmVnaXN0ZXIiLHtkZXRhaWw6e3JldmlzaW9uOllVfX0pKTt0eXBlb2Ygd2luZG93IT0idW5kZWZpbmVkIiYmKHdpbmRvdy5fX1RIUkVFX18/Y29uc29sZS53YXJuKCJXQVJOSU5HOiBNdWx0aXBsZSBpbnN0YW5jZXMgb2YgVGhyZWUuanMgYmVpbmcgaW1wb3J0ZWQuIik6d2luZG93Ll9fVEhSRUVfXz1ZVSk7dmFyIHJwZT17dHlwZToiY2hhbmdlIn0sVmh0PXt0eXBlOiJzdGFydCJ9LG5wZT17dHlwZToiZW5kIn0sWlU9Y2xhc3MgZXh0ZW5kcyBVc3tjb25zdHJ1Y3Rvcih0LHIpe3N1cGVyKCkscj09PXZvaWQgMCYmY29uc29sZS53YXJuKCdUSFJFRS5PcmJpdENvbnRyb2xzOiBUaGUgc2Vjb25kIHBhcmFtZXRlciAiZG9tRWxlbWVudCIgaXMgbm93IG1hbmRhdG9yeS4nKSxyPT09ZG9jdW1lbnQmJmNvbnNvbGUuZXJyb3IoJ1RIUkVFLk9yYml0Q29udHJvbHM6ICJkb2N1bWVudCIgc2hvdWxkIG5vdCBiZSB1c2VkIGFzIHRoZSB0YXJnZXQgImRvbUVsZW1lbnQiLiBQbGVhc2UgdXNlICJyZW5kZXJlci5kb21FbGVtZW50IiBpbnN0ZWFkLicpLHRoaXMub2JqZWN0PXQsdGhpcy5kb21FbGVtZW50PXIsdGhpcy5kb21FbGVtZW50LnN0eWxlLnRvdWNoQWN0aW9uPSJub25lIix0aGlzLmVuYWJsZWQ9ITAsdGhpcy50YXJnZXQ9bmV3IGosdGhpcy5taW5EaXN0YW5jZT0wLHRoaXMubWF4RGlzdGFuY2U9MS8wLHRoaXMubWluWm9vbT0wLHRoaXMubWF4Wm9vbT0xLzAsdGhpcy5taW5Qb2xhckFuZ2xlPTAsdGhpcy5tYXhQb2xhckFuZ2xlPU1hdGguUEksdGhpcy5taW5BemltdXRoQW5nbGU9LTEvMCx0aGlzLm1heEF6aW11dGhBbmdsZT0xLzAsdGhpcy5lbmFibGVEYW1waW5nPSExLHRoaXMuZGFtcGluZ0ZhY3Rvcj0uMDUsdGhpcy5lbmFibGVab29tPSEwLHRoaXMuem9vbVNwZWVkPTEsdGhpcy5lbmFibGVSb3RhdGU9ITAsdGhpcy5yb3RhdGVTcGVlZD0xLHRoaXMuZW5hYmxlUGFuPSEwLHRoaXMucGFuU3BlZWQ9MSx0aGlzLnNjcmVlblNwYWNlUGFubmluZz0hMCx0aGlzLmtleVBhblNwZWVkPTcsdGhpcy5hdXRvUm90YXRlPSExLHRoaXMuYXV0b1JvdGF0ZVNwZWVkPTIsdGhpcy5rZXlzPXtMRUZUOiJBcnJvd0xlZnQiLFVQOiJBcnJvd1VwIixSSUdIVDoiQXJyb3dSaWdodCIsQk9UVE9NOiJBcnJvd0Rvd24ifSx0aGlzLm1vdXNlQnV0dG9ucz17TEVGVDpLMC5ST1RBVEUsTUlERExFOkswLkRPTExZLFJJR0hUOkswLlBBTn0sdGhpcy50b3VjaGVzPXtPTkU6WjAuUk9UQVRFLFRXTzpaMC5ET0xMWV9QQU59LHRoaXMudGFyZ2V0MD10aGlzLnRhcmdldC5jbG9uZSgpLHRoaXMucG9zaXRpb24wPXRoaXMub2JqZWN0LnBvc2l0aW9uLmNsb25lKCksdGhpcy56b29tMD10aGlzLm9iamVjdC56b29tLHRoaXMuX2RvbUVsZW1lbnRLZXlFdmVudHM9bnVsbCx0aGlzLmdldFBvbGFyQW5nbGU9ZnVuY3Rpb24oKXtyZXR1cm4gcy5waGl9LHRoaXMuZ2V0QXppbXV0aGFsQW5nbGU9ZnVuY3Rpb24oKXtyZXR1cm4gcy50aGV0YX0sdGhpcy5nZXREaXN0YW5jZT1mdW5jdGlvbigpe3JldHVybiB0aGlzLm9iamVjdC5wb3NpdGlvbi5kaXN0YW5jZVRvKHRoaXMudGFyZ2V0KX0sdGhpcy5saXN0ZW5Ub0tleUV2ZW50cz1mdW5jdGlvbihudCl7bnQuYWRkRXZlbnRMaXN0ZW5lcigia2V5ZG93biIsZnIpLHRoaXMuX2RvbUVsZW1lbnRLZXlFdmVudHM9bnR9LHRoaXMuc2F2ZVN0YXRlPWZ1bmN0aW9uKCl7bi50YXJnZXQwLmNvcHkobi50YXJnZXQpLG4ucG9zaXRpb24wLmNvcHkobi5vYmplY3QucG9zaXRpb24pLG4uem9vbTA9bi5vYmplY3Quem9vbX0sdGhpcy5yZXNldD1mdW5jdGlvbigpe24udGFyZ2V0LmNvcHkobi50YXJnZXQwKSxuLm9iamVjdC5wb3NpdGlvbi5jb3B5KG4ucG9zaXRpb24wKSxuLm9iamVjdC56b29tPW4uem9vbTAsbi5vYmplY3QudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpLG4uZGlzcGF0Y2hFdmVudChycGUpLG4udXBkYXRlKCksbz1pLk5PTkV9LHRoaXMudXBkYXRlPWZ1bmN0aW9uKCl7bGV0IG50PW5ldyBqLEN0PW5ldyB2aSgpLnNldEZyb21Vbml0VmVjdG9ycyh0LnVwLG5ldyBqKDAsMSwwKSksV3Q9Q3QuY2xvbmUoKS5pbnZlcnQoKSxmZT1uZXcgaixhdD1uZXcgdmksc2U9MipNYXRoLlBJO3JldHVybiBmdW5jdGlvbigpe2xldCBDZT1uLm9iamVjdC5wb3NpdGlvbjtudC5jb3B5KENlKS5zdWIobi50YXJnZXQpLG50LmFwcGx5UXVhdGVybmlvbihDdCkscy5zZXRGcm9tVmVjdG9yMyhudCksbi5hdXRvUm90YXRlJiZvPT09aS5OT05FJiZEKGsoKSksbi5lbmFibGVEYW1waW5nPyhzLnRoZXRhKz1sLnRoZXRhKm4uZGFtcGluZ0ZhY3RvcixzLnBoaSs9bC5waGkqbi5kYW1waW5nRmFjdG9yKToocy50aGV0YSs9bC50aGV0YSxzLnBoaSs9bC5waGkpO2xldCBQdD1uLm1pbkF6aW11dGhBbmdsZSxOdD1uLm1heEF6aW11dGhBbmdsZTtyZXR1cm4gaXNGaW5pdGUoUHQpJiZpc0Zpbml0ZShOdCkmJihQdDwtTWF0aC5QST9QdCs9c2U6UHQ+TWF0aC5QSSYmKFB0LT1zZSksTnQ8LU1hdGguUEk/TnQrPXNlOk50Pk1hdGguUEkmJihOdC09c2UpLFB0PD1OdD9zLnRoZXRhPU1hdGgubWF4KFB0LE1hdGgubWluKE50LHMudGhldGEpKTpzLnRoZXRhPXMudGhldGE+KFB0K050KS8yP01hdGgubWF4KFB0LHMudGhldGEpOk1hdGgubWluKE50LHMudGhldGEpKSxzLnBoaT1NYXRoLm1heChuLm1pblBvbGFyQW5nbGUsTWF0aC5taW4obi5tYXhQb2xhckFuZ2xlLHMucGhpKSkscy5tYWtlU2FmZSgpLHMucmFkaXVzKj1jLHMucmFkaXVzPU1hdGgubWF4KG4ubWluRGlzdGFuY2UsTWF0aC5taW4obi5tYXhEaXN0YW5jZSxzLnJhZGl1cykpLG4uZW5hYmxlRGFtcGluZz09PSEwP24udGFyZ2V0LmFkZFNjYWxlZFZlY3Rvcih1LG4uZGFtcGluZ0ZhY3Rvcik6bi50YXJnZXQuYWRkKHUpLG50LnNldEZyb21TcGhlcmljYWwocyksbnQuYXBwbHlRdWF0ZXJuaW9uKFd0KSxDZS5jb3B5KG4udGFyZ2V0KS5hZGQobnQpLG4ub2JqZWN0Lmxvb2tBdChuLnRhcmdldCksbi5lbmFibGVEYW1waW5nPT09ITA/KGwudGhldGEqPTEtbi5kYW1waW5nRmFjdG9yLGwucGhpKj0xLW4uZGFtcGluZ0ZhY3Rvcix1Lm11bHRpcGx5U2NhbGFyKDEtbi5kYW1waW5nRmFjdG9yKSk6KGwuc2V0KDAsMCwwKSx1LnNldCgwLDAsMCkpLGM9MSxofHxmZS5kaXN0YW5jZVRvU3F1YXJlZChuLm9iamVjdC5wb3NpdGlvbik+YXx8OCooMS1hdC5kb3Qobi5vYmplY3QucXVhdGVybmlvbikpPmE/KG4uZGlzcGF0Y2hFdmVudChycGUpLGZlLmNvcHkobi5vYmplY3QucG9zaXRpb24pLGF0LmNvcHkobi5vYmplY3QucXVhdGVybmlvbiksaD0hMSwhMCk6ITF9fSgpLHRoaXMuZGlzcG9zZT1mdW5jdGlvbigpe24uZG9tRWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJjb250ZXh0bWVudSIsSXQpLG4uZG9tRWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJwb2ludGVyZG93biIsaHQpLG4uZG9tRWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJwb2ludGVyY2FuY2VsIixpZSksbi5kb21FbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoIndoZWVsIixhciksbi5kb21FbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoInBvaW50ZXJtb3ZlIix3dCksbi5kb21FbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoInBvaW50ZXJ1cCIsa3QpLG4uX2RvbUVsZW1lbnRLZXlFdmVudHMhPT1udWxsJiZuLl9kb21FbGVtZW50S2V5RXZlbnRzLnJlbW92ZUV2ZW50TGlzdGVuZXIoImtleWRvd24iLGZyKX07bGV0IG49dGhpcyxpPXtOT05FOi0xLFJPVEFURTowLERPTExZOjEsUEFOOjIsVE9VQ0hfUk9UQVRFOjMsVE9VQ0hfUEFOOjQsVE9VQ0hfRE9MTFlfUEFOOjUsVE9VQ0hfRE9MTFlfUk9UQVRFOjZ9LG89aS5OT05FLGE9MWUtNixzPW5ldyBfTSxsPW5ldyBfTSxjPTEsdT1uZXcgaixoPSExLGY9bmV3IEx0LHA9bmV3IEx0LGQ9bmV3IEx0LGc9bmV3IEx0LF89bmV3IEx0LHk9bmV3IEx0LHg9bmV3IEx0LGI9bmV3IEx0LFM9bmV3IEx0LEM9W10sUD17fTtmdW5jdGlvbiBrKCl7cmV0dXJuIDIqTWF0aC5QSS82MC82MCpuLmF1dG9Sb3RhdGVTcGVlZH1mdW5jdGlvbiBPKCl7cmV0dXJuIE1hdGgucG93KC45NSxuLnpvb21TcGVlZCl9ZnVuY3Rpb24gRChudCl7bC50aGV0YS09bnR9ZnVuY3Rpb24gQihudCl7bC5waGktPW50fWxldCBJPWZ1bmN0aW9uKCl7bGV0IG50PW5ldyBqO3JldHVybiBmdW5jdGlvbihXdCxmZSl7bnQuc2V0RnJvbU1hdHJpeENvbHVtbihmZSwwKSxudC5tdWx0aXBseVNjYWxhcigtV3QpLHUuYWRkKG50KX19KCksTD1mdW5jdGlvbigpe2xldCBudD1uZXcgajtyZXR1cm4gZnVuY3Rpb24oV3QsZmUpe24uc2NyZWVuU3BhY2VQYW5uaW5nPT09ITA/bnQuc2V0RnJvbU1hdHJpeENvbHVtbihmZSwxKToobnQuc2V0RnJvbU1hdHJpeENvbHVtbihmZSwwKSxudC5jcm9zc1ZlY3RvcnMobi5vYmplY3QudXAsbnQpKSxudC5tdWx0aXBseVNjYWxhcihXdCksdS5hZGQobnQpfX0oKSxSPWZ1bmN0aW9uKCl7bGV0IG50PW5ldyBqO3JldHVybiBmdW5jdGlvbihXdCxmZSl7bGV0IGF0PW4uZG9tRWxlbWVudDtpZihuLm9iamVjdC5pc1BlcnNwZWN0aXZlQ2FtZXJhKXtsZXQgc2U9bi5vYmplY3QucG9zaXRpb247bnQuY29weShzZSkuc3ViKG4udGFyZ2V0KTtsZXQgUXQ9bnQubGVuZ3RoKCk7UXQqPU1hdGgudGFuKG4ub2JqZWN0LmZvdi8yKk1hdGguUEkvMTgwKSxJKDIqV3QqUXQvYXQuY2xpZW50SGVpZ2h0LG4ub2JqZWN0Lm1hdHJpeCksTCgyKmZlKlF0L2F0LmNsaWVudEhlaWdodCxuLm9iamVjdC5tYXRyaXgpfWVsc2Ugbi5vYmplY3QuaXNPcnRob2dyYXBoaWNDYW1lcmE/KEkoV3QqKG4ub2JqZWN0LnJpZ2h0LW4ub2JqZWN0LmxlZnQpL24ub2JqZWN0Lnpvb20vYXQuY2xpZW50V2lkdGgsbi5vYmplY3QubWF0cml4KSxMKGZlKihuLm9iamVjdC50b3Atbi5vYmplY3QuYm90dG9tKS9uLm9iamVjdC56b29tL2F0LmNsaWVudEhlaWdodCxuLm9iamVjdC5tYXRyaXgpKTooY29uc29sZS53YXJuKCJXQVJOSU5HOiBPcmJpdENvbnRyb2xzLmpzIGVuY291bnRlcmVkIGFuIHVua25vd24gY2FtZXJhIHR5cGUgLSBwYW4gZGlzYWJsZWQuIiksbi5lbmFibGVQYW49ITEpfX0oKTtmdW5jdGlvbiBGKG50KXtuLm9iamVjdC5pc1BlcnNwZWN0aXZlQ2FtZXJhP2MvPW50Om4ub2JqZWN0LmlzT3J0aG9ncmFwaGljQ2FtZXJhPyhuLm9iamVjdC56b29tPU1hdGgubWF4KG4ubWluWm9vbSxNYXRoLm1pbihuLm1heFpvb20sbi5vYmplY3Quem9vbSpudCkpLG4ub2JqZWN0LnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKSxoPSEwKTooY29uc29sZS53YXJuKCJXQVJOSU5HOiBPcmJpdENvbnRyb2xzLmpzIGVuY291bnRlcmVkIGFuIHVua25vd24gY2FtZXJhIHR5cGUgLSBkb2xseS96b29tIGRpc2FibGVkLiIpLG4uZW5hYmxlWm9vbT0hMSl9ZnVuY3Rpb24geihudCl7bi5vYmplY3QuaXNQZXJzcGVjdGl2ZUNhbWVyYT9jKj1udDpuLm9iamVjdC5pc09ydGhvZ3JhcGhpY0NhbWVyYT8obi5vYmplY3Quem9vbT1NYXRoLm1heChuLm1pblpvb20sTWF0aC5taW4obi5tYXhab29tLG4ub2JqZWN0Lnpvb20vbnQpKSxuLm9iamVjdC51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCksaD0hMCk6KGNvbnNvbGUud2FybigiV0FSTklORzogT3JiaXRDb250cm9scy5qcyBlbmNvdW50ZXJlZCBhbiB1bmtub3duIGNhbWVyYSB0eXBlIC0gZG9sbHkvem9vbSBkaXNhYmxlZC4iKSxuLmVuYWJsZVpvb209ITEpfWZ1bmN0aW9uIFUobnQpe2Yuc2V0KG50LmNsaWVudFgsbnQuY2xpZW50WSl9ZnVuY3Rpb24gVyhudCl7eC5zZXQobnQuY2xpZW50WCxudC5jbGllbnRZKX1mdW5jdGlvbiBaKG50KXtnLnNldChudC5jbGllbnRYLG50LmNsaWVudFkpfWZ1bmN0aW9uIHJ0KG50KXtwLnNldChudC5jbGllbnRYLG50LmNsaWVudFkpLGQuc3ViVmVjdG9ycyhwLGYpLm11bHRpcGx5U2NhbGFyKG4ucm90YXRlU3BlZWQpO2xldCBDdD1uLmRvbUVsZW1lbnQ7RCgyKk1hdGguUEkqZC54L0N0LmNsaWVudEhlaWdodCksQigyKk1hdGguUEkqZC55L0N0LmNsaWVudEhlaWdodCksZi5jb3B5KHApLG4udXBkYXRlKCl9ZnVuY3Rpb24gb3QobnQpe2Iuc2V0KG50LmNsaWVudFgsbnQuY2xpZW50WSksUy5zdWJWZWN0b3JzKGIseCksUy55PjA/RihPKCkpOlMueTwwJiZ6KE8oKSkseC5jb3B5KGIpLG4udXBkYXRlKCl9ZnVuY3Rpb24gc3QobnQpe18uc2V0KG50LmNsaWVudFgsbnQuY2xpZW50WSkseS5zdWJWZWN0b3JzKF8sZykubXVsdGlwbHlTY2FsYXIobi5wYW5TcGVlZCksUih5LngseS55KSxnLmNvcHkoXyksbi51cGRhdGUoKX1mdW5jdGlvbiBTdChudCl7bnQuZGVsdGFZPDA/eihPKCkpOm50LmRlbHRhWT4wJiZGKE8oKSksbi51cGRhdGUoKX1mdW5jdGlvbiBidChudCl7bGV0IEN0PSExO3N3aXRjaChudC5jb2RlKXtjYXNlIG4ua2V5cy5VUDpSKDAsbi5rZXlQYW5TcGVlZCksQ3Q9ITA7YnJlYWs7Y2FzZSBuLmtleXMuQk9UVE9NOlIoMCwtbi5rZXlQYW5TcGVlZCksQ3Q9ITA7YnJlYWs7Y2FzZSBuLmtleXMuTEVGVDpSKG4ua2V5UGFuU3BlZWQsMCksQ3Q9ITA7YnJlYWs7Y2FzZSBuLmtleXMuUklHSFQ6Uigtbi5rZXlQYW5TcGVlZCwwKSxDdD0hMDticmVha31DdCYmKG50LnByZXZlbnREZWZhdWx0KCksbi51cGRhdGUoKSl9ZnVuY3Rpb24gTXQoKXtpZihDLmxlbmd0aD09PTEpZi5zZXQoQ1swXS5wYWdlWCxDWzBdLnBhZ2VZKTtlbHNle2xldCBudD0uNSooQ1swXS5wYWdlWCtDWzFdLnBhZ2VYKSxDdD0uNSooQ1swXS5wYWdlWStDWzFdLnBhZ2VZKTtmLnNldChudCxDdCl9fWZ1bmN0aW9uIGx0KCl7aWYoQy5sZW5ndGg9PT0xKWcuc2V0KENbMF0ucGFnZVgsQ1swXS5wYWdlWSk7ZWxzZXtsZXQgbnQ9LjUqKENbMF0ucGFnZVgrQ1sxXS5wYWdlWCksQ3Q9LjUqKENbMF0ucGFnZVkrQ1sxXS5wYWdlWSk7Zy5zZXQobnQsQ3QpfX1mdW5jdGlvbiBLdCgpe2xldCBudD1DWzBdLnBhZ2VYLUNbMV0ucGFnZVgsQ3Q9Q1swXS5wYWdlWS1DWzFdLnBhZ2VZLFd0PU1hdGguc3FydChudCpudCtDdCpDdCk7eC5zZXQoMCxXdCl9ZnVuY3Rpb24gX3QoKXtuLmVuYWJsZVpvb20mJkt0KCksbi5lbmFibGVQYW4mJmx0KCl9ZnVuY3Rpb24gY3QoKXtuLmVuYWJsZVpvb20mJkt0KCksbi5lbmFibGVSb3RhdGUmJk10KCl9ZnVuY3Rpb24gWChudCl7aWYoQy5sZW5ndGg9PTEpcC5zZXQobnQucGFnZVgsbnQucGFnZVkpO2Vsc2V7bGV0IFd0PWJlKG50KSxmZT0uNSoobnQucGFnZVgrV3QueCksYXQ9LjUqKG50LnBhZ2VZK1d0LnkpO3Auc2V0KGZlLGF0KX1kLnN1YlZlY3RvcnMocCxmKS5tdWx0aXBseVNjYWxhcihuLnJvdGF0ZVNwZWVkKTtsZXQgQ3Q9bi5kb21FbGVtZW50O0QoMipNYXRoLlBJKmQueC9DdC5jbGllbnRIZWlnaHQpLEIoMipNYXRoLlBJKmQueS9DdC5jbGllbnRIZWlnaHQpLGYuY29weShwKX1mdW5jdGlvbiBldChudCl7aWYoQy5sZW5ndGg9PT0xKV8uc2V0KG50LnBhZ2VYLG50LnBhZ2VZKTtlbHNle2xldCBDdD1iZShudCksV3Q9LjUqKG50LnBhZ2VYK0N0LngpLGZlPS41KihudC5wYWdlWStDdC55KTtfLnNldChXdCxmZSl9eS5zdWJWZWN0b3JzKF8sZykubXVsdGlwbHlTY2FsYXIobi5wYW5TcGVlZCksUih5LngseS55KSxnLmNvcHkoXyl9ZnVuY3Rpb24gZHQobnQpe2xldCBDdD1iZShudCksV3Q9bnQucGFnZVgtQ3QueCxmZT1udC5wYWdlWS1DdC55LGF0PU1hdGguc3FydChXdCpXdCtmZSpmZSk7Yi5zZXQoMCxhdCksUy5zZXQoMCxNYXRoLnBvdyhiLnkveC55LG4uem9vbVNwZWVkKSksRihTLnkpLHguY29weShiKX1mdW5jdGlvbiBxKG50KXtuLmVuYWJsZVpvb20mJmR0KG50KSxuLmVuYWJsZVBhbiYmZXQobnQpfWZ1bmN0aW9uIHB0KG50KXtuLmVuYWJsZVpvb20mJmR0KG50KSxuLmVuYWJsZVJvdGF0ZSYmWChudCl9ZnVuY3Rpb24gaHQobnQpe24uZW5hYmxlZCE9PSExJiYoQy5sZW5ndGg9PT0wJiYobi5kb21FbGVtZW50LnNldFBvaW50ZXJDYXB0dXJlKG50LnBvaW50ZXJJZCksbi5kb21FbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoInBvaW50ZXJtb3ZlIix3dCksbi5kb21FbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoInBvaW50ZXJ1cCIsa3QpKSwkdChudCksbnQucG9pbnRlclR5cGU9PT0idG91Y2giP3R0KG50KTplZShudCkpfWZ1bmN0aW9uIHd0KG50KXtuLmVuYWJsZWQhPT0hMSYmKG50LnBvaW50ZXJUeXBlPT09InRvdWNoIj8kKG50KTpMZShudCkpfWZ1bmN0aW9uIGt0KG50KXtoZShudCksQy5sZW5ndGg9PT0wJiYobi5kb21FbGVtZW50LnJlbGVhc2VQb2ludGVyQ2FwdHVyZShudC5wb2ludGVySWQpLG4uZG9tRWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJwb2ludGVybW92ZSIsd3QpLG4uZG9tRWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJwb2ludGVydXAiLGt0KSksbi5kaXNwYXRjaEV2ZW50KG5wZSksbz1pLk5PTkV9ZnVuY3Rpb24gaWUobnQpe2hlKG50KX1mdW5jdGlvbiBlZShudCl7bGV0IEN0O3N3aXRjaChudC5idXR0b24pe2Nhc2UgMDpDdD1uLm1vdXNlQnV0dG9ucy5MRUZUO2JyZWFrO2Nhc2UgMTpDdD1uLm1vdXNlQnV0dG9ucy5NSURETEU7YnJlYWs7Y2FzZSAyOkN0PW4ubW91c2VCdXR0b25zLlJJR0hUO2JyZWFrO2RlZmF1bHQ6Q3Q9LTF9c3dpdGNoKEN0KXtjYXNlIEswLkRPTExZOmlmKG4uZW5hYmxlWm9vbT09PSExKXJldHVybjtXKG50KSxvPWkuRE9MTFk7YnJlYWs7Y2FzZSBLMC5ST1RBVEU6aWYobnQuY3RybEtleXx8bnQubWV0YUtleXx8bnQuc2hpZnRLZXkpe2lmKG4uZW5hYmxlUGFuPT09ITEpcmV0dXJuO1oobnQpLG89aS5QQU59ZWxzZXtpZihuLmVuYWJsZVJvdGF0ZT09PSExKXJldHVybjtVKG50KSxvPWkuUk9UQVRFfWJyZWFrO2Nhc2UgSzAuUEFOOmlmKG50LmN0cmxLZXl8fG50Lm1ldGFLZXl8fG50LnNoaWZ0S2V5KXtpZihuLmVuYWJsZVJvdGF0ZT09PSExKXJldHVybjtVKG50KSxvPWkuUk9UQVRFfWVsc2V7aWYobi5lbmFibGVQYW49PT0hMSlyZXR1cm47WihudCksbz1pLlBBTn1icmVhaztkZWZhdWx0Om89aS5OT05FfW8hPT1pLk5PTkUmJm4uZGlzcGF0Y2hFdmVudChWaHQpfWZ1bmN0aW9uIExlKG50KXtpZihuLmVuYWJsZWQhPT0hMSlzd2l0Y2gobyl7Y2FzZSBpLlJPVEFURTppZihuLmVuYWJsZVJvdGF0ZT09PSExKXJldHVybjtydChudCk7YnJlYWs7Y2FzZSBpLkRPTExZOmlmKG4uZW5hYmxlWm9vbT09PSExKXJldHVybjtvdChudCk7YnJlYWs7Y2FzZSBpLlBBTjppZihuLmVuYWJsZVBhbj09PSExKXJldHVybjtzdChudCk7YnJlYWt9fWZ1bmN0aW9uIGFyKG50KXtuLmVuYWJsZWQ9PT0hMXx8bi5lbmFibGVab29tPT09ITF8fG8hPT1pLk5PTkV8fChudC5wcmV2ZW50RGVmYXVsdCgpLG4uZGlzcGF0Y2hFdmVudChWaHQpLFN0KG50KSxuLmRpc3BhdGNoRXZlbnQobnBlKSl9ZnVuY3Rpb24gZnIobnQpe24uZW5hYmxlZD09PSExfHxuLmVuYWJsZVBhbj09PSExfHxidChudCl9ZnVuY3Rpb24gdHQobnQpe3N3aXRjaChUdChudCksQy5sZW5ndGgpe2Nhc2UgMTpzd2l0Y2gobi50b3VjaGVzLk9ORSl7Y2FzZSBaMC5ST1RBVEU6aWYobi5lbmFibGVSb3RhdGU9PT0hMSlyZXR1cm47TXQoKSxvPWkuVE9VQ0hfUk9UQVRFO2JyZWFrO2Nhc2UgWjAuUEFOOmlmKG4uZW5hYmxlUGFuPT09ITEpcmV0dXJuO2x0KCksbz1pLlRPVUNIX1BBTjticmVhaztkZWZhdWx0Om89aS5OT05FfWJyZWFrO2Nhc2UgMjpzd2l0Y2gobi50b3VjaGVzLlRXTyl7Y2FzZSBaMC5ET0xMWV9QQU46aWYobi5lbmFibGVab29tPT09ITEmJm4uZW5hYmxlUGFuPT09ITEpcmV0dXJuO190KCksbz1pLlRPVUNIX0RPTExZX1BBTjticmVhaztjYXNlIFowLkRPTExZX1JPVEFURTppZihuLmVuYWJsZVpvb209PT0hMSYmbi5lbmFibGVSb3RhdGU9PT0hMSlyZXR1cm47Y3QoKSxvPWkuVE9VQ0hfRE9MTFlfUk9UQVRFO2JyZWFrO2RlZmF1bHQ6bz1pLk5PTkV9YnJlYWs7ZGVmYXVsdDpvPWkuTk9ORX1vIT09aS5OT05FJiZuLmRpc3BhdGNoRXZlbnQoVmh0KX1mdW5jdGlvbiAkKG50KXtzd2l0Y2goVHQobnQpLG8pe2Nhc2UgaS5UT1VDSF9ST1RBVEU6aWYobi5lbmFibGVSb3RhdGU9PT0hMSlyZXR1cm47WChudCksbi51cGRhdGUoKTticmVhaztjYXNlIGkuVE9VQ0hfUEFOOmlmKG4uZW5hYmxlUGFuPT09ITEpcmV0dXJuO2V0KG50KSxuLnVwZGF0ZSgpO2JyZWFrO2Nhc2UgaS5UT1VDSF9ET0xMWV9QQU46aWYobi5lbmFibGVab29tPT09ITEmJm4uZW5hYmxlUGFuPT09ITEpcmV0dXJuO3EobnQpLG4udXBkYXRlKCk7YnJlYWs7Y2FzZSBpLlRPVUNIX0RPTExZX1JPVEFURTppZihuLmVuYWJsZVpvb209PT0hMSYmbi5lbmFibGVSb3RhdGU9PT0hMSlyZXR1cm47cHQobnQpLG4udXBkYXRlKCk7YnJlYWs7ZGVmYXVsdDpvPWkuTk9ORX19ZnVuY3Rpb24gSXQobnQpe24uZW5hYmxlZCE9PSExJiZudC5wcmV2ZW50RGVmYXVsdCgpfWZ1bmN0aW9uICR0KG50KXtDLnB1c2gobnQpfWZ1bmN0aW9uIGhlKG50KXtkZWxldGUgUFtudC5wb2ludGVySWRdO2ZvcihsZXQgQ3Q9MDtDdDxDLmxlbmd0aDtDdCsrKWlmKENbQ3RdLnBvaW50ZXJJZD09bnQucG9pbnRlcklkKXtDLnNwbGljZShDdCwxKTtyZXR1cm59fWZ1bmN0aW9uIFR0KG50KXtsZXQgQ3Q9UFtudC5wb2ludGVySWRdO0N0PT09dm9pZCAwJiYoQ3Q9bmV3IEx0LFBbbnQucG9pbnRlcklkXT1DdCksQ3Quc2V0KG50LnBhZ2VYLG50LnBhZ2VZKX1mdW5jdGlvbiBiZShudCl7bGV0IEN0PW50LnBvaW50ZXJJZD09PUNbMF0ucG9pbnRlcklkP0NbMV06Q1swXTtyZXR1cm4gUFtDdC5wb2ludGVySWRdfW4uZG9tRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCJjb250ZXh0bWVudSIsSXQpLG4uZG9tRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCJwb2ludGVyZG93biIsaHQpLG4uZG9tRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCJwb2ludGVyY2FuY2VsIixpZSksbi5kb21FbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoIndoZWVsIixhcix7cGFzc2l2ZTohMX0pLHRoaXMudXBkYXRlKCl9fTt2YXIgejY9Y2xhc3MgZXh0ZW5kcyBVc3tjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMuX2xhc3RNZXNoPW51bGwsdGhpcy5fY2xvY2s9bmV3IG1NLHRoaXMuX2NhbnZhc1NpemU9bnVsbCx0aGlzLl9sYXllcnNDb25maWc9bnVsbCx0aGlzLl9ydW5Db2xvcj10fV9pc09iamVjdCh0KXt2YXIgcj10eXBlb2YgdDtyZXR1cm4gcj09Im9iamVjdCImJnQhPW51bGwmJiFBcnJheS5pc0FycmF5KHQpfV9hcHBseURlZmF1bHRzKHQscil7bGV0IG49e30saT1bdCxyXTtmb3IobGV0IG89MDtvPGkubGVuZ3RoO28rKyl7bGV0IGE9aVtvXTtmb3IobGV0IHMgaW4gYSl7bGV0IGw9cyBpbiBuO3RoaXMuX2lzT2JqZWN0KGFbc10pP25bc109dGhpcy5fYXBwbHlEZWZhdWx0cyhuW3NdfHx7fSxhW3NdKTpsfHwobltzXT1hW3NdKX19cmV0dXJuIG59X2NyZWF0ZUxheWVycygpe2lmKCEoIXRoaXMuX2xheWVyc0NvbmZpZ3x8IXRoaXMuX3NjZW5lfHwhdGhpcy5fbGFzdE1lc2gpKXtpZih0aGlzLl9sYXllcnNDb25maWcuc2hvd0JvdW5kaW5nQm94KXt2YXIgdD1uZXcgeU0odGhpcy5fbGFzdE1lc2gsbmV3IG5lKCJyZ2IoMCwgMCwgMjU1KSIpKTt0aGlzLl9zY2VuZS5hZGQodCl9aWYodGhpcy5fbGF5ZXJzQ29uZmlnLnNob3dBeGVzKXt2YXIgcj1uZXcgdk0oNSk7dGhpcy5fc2NlbmUuYWRkKHIpfX19c2V0TGF5ZXJzQ29uZmlnKHQpe3RoaXMuX2xheWVyc0NvbmZpZz10aGlzLl9hcHBseURlZmF1bHRzKHQsdGhpcy5fbGF5ZXJzQ29uZmlnfHx7fSl9X2NyZWF0ZVdvcmxkKHQscil7dmFyIGEscyxsLGM7aWYodGhpcy5pc1JlYWR5KCkpcmV0dXJuO3RoaXMuX3NjZW5lPW5ldyBxMDt2YXIgbj1uZXcgd01bdC5jYW1lcmEuY2xzXSh0LmNhbWVyYS5mb3YsKChhPXRoaXMuX2NhbnZhc1NpemUpPT1udWxsP3ZvaWQgMDphLndpZHRoKS8oKHM9dGhpcy5fY2FudmFzU2l6ZSk9PW51bGw/dm9pZCAwOnMuaGVpZ2h0KSx0LmNhbWVyYS5uZWFyLHQuY2FtZXJhLmZhcik7dGhpcy5fY2FtZXJhPW4sdGhpcy5pbml0Q2FtZXJhUG9zaXRpb249dm9pZCAwLHQuY2FtZXJhLnBvc2l0aW9uJiYodGhpcy5pbml0Q2FtZXJhUG9zaXRpb249bmV3IGooKS5mcm9tQXJyYXkodC5jYW1lcmEucG9zaXRpb24pKSx0aGlzLmluaXRDYW1lcmFMb29rQXQ9dm9pZCAwLHQuY2FtZXJhLmxvb2tBdCYmKHRoaXMuaW5pdENhbWVyYUxvb2tBdD1uZXcgaigpLmZyb21BcnJheSh0LmNhbWVyYS5sb29rQXQpKTt2YXIgaT1uZXcgWlUobixyKTtsZXQgbz1pO28ubG9va1NwZWVkPS40LG8ubW92ZW1lbnRTcGVlZD0yMCxvLm5vRmx5PSEwLG8ubG9va1ZlcnRpY2FsPSEwLG8uY29uc3RyYWluVmVydGljYWw9ITAsby52ZXJ0aWNhbE1pbj0xLG8udmVydGljYWxNYXg9MixvLmFkZEV2ZW50TGlzdGVuZXIoImNoYW5nZSIsdGhpcy5fb25DYW1lcmFQb3NpdGlvbkNoYW5nZS5iaW5kKHRoaXMpKSx0aGlzLl9jYW1lcmFDb250cm9scz1pLHRoaXMuX3JlbmRlcmVyPW5ldyBybih7YW50aWFsaWFzOiEwfSksdGhpcy5fcmVuZGVyZXIuc2V0UGl4ZWxSYXRpbyh3aW5kb3cuZGV2aWNlUGl4ZWxSYXRpbyksdGhpcy5fcmVuZGVyZXIuc2V0U2l6ZSgobD10aGlzLl9jYW52YXNTaXplKT09bnVsbD92b2lkIDA6bC53aWR0aCwoYz10aGlzLl9jYW52YXNTaXplKT09bnVsbD92b2lkIDA6Yy5oZWlnaHQpLHRoaXMuX3JlbmRlcmVyLnNldENsZWFyQ29sb3IoMTY3NzcyMTUsMSl9X2NsZWFyU2NlbmUoKXt2YXIgdDtpZih0aGlzLl9zY2VuZSlmb3IoO3RoaXMuX3NjZW5lLmNoaWxkcmVuLmxlbmd0aD4wOyl0aGlzLl9zY2VuZS5yZW1vdmUoKHQ9dGhpcy5fc2NlbmUpPT1udWxsP3ZvaWQgMDp0LmNoaWxkcmVuWzBdKX1nZXRSZW5kZXJlcigpe3JldHVybiB0aGlzLl9yZW5kZXJlcn1nZXRDYW1lcmFDb250cm9scygpe3JldHVybiB0aGlzLl9jYW1lcmFDb250cm9sc31pc1JlYWR5KCl7cmV0dXJuISF0aGlzLl9jYW1lcmEmJiEhdGhpcy5fY2FtZXJhQ29udHJvbHN9Z2V0Q2FtZXJhUG9zaXRpb24oKXt2YXIgdCxyLG47cmV0dXJue2ZhcjoodD10aGlzLl9jYW1lcmEpPT1udWxsP3ZvaWQgMDp0LmZhcixwb3NpdGlvbjoocj10aGlzLl9jYW1lcmEpPT1udWxsP3ZvaWQgMDpyLnBvc2l0aW9uLmNsb25lKCksdGFyZ2V0OihuPXRoaXMuX2NhbWVyYUNvbnRyb2xzKT09bnVsbD92b2lkIDA6bi50YXJnZXQuY2xvbmUoKX19c2V0Q2FudmFzU2l6ZSh0KXt0aGlzLl9jYW52YXNTaXplPXR9ZHJhdygpe3ZhciByLG4saSxvO3RoaXMuX2FuaW1hdGlvbkZyYW1lSW5kZXgmJmNhbmNlbEFuaW1hdGlvbkZyYW1lKHRoaXMuX2FuaW1hdGlvbkZyYW1lSW5kZXgpLHRoaXMuX2NhbWVyYSYmKHRoaXMuX2NhbWVyYS5hc3BlY3Q9KChyPXRoaXMuX2NhbnZhc1NpemUpPT1udWxsP3ZvaWQgMDpyLndpZHRoKS8oKG49dGhpcy5fY2FudmFzU2l6ZSk9PW51bGw/dm9pZCAwOm4uaGVpZ2h0KSx0aGlzLl9jYW1lcmEudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpKSx0aGlzLl9yZW5kZXJlci5zZXRTaXplKChpPXRoaXMuX2NhbnZhc1NpemUpPT1udWxsP3ZvaWQgMDppLndpZHRoLChvPXRoaXMuX2NhbnZhc1NpemUpPT1udWxsP3ZvaWQgMDpvLmhlaWdodCk7bGV0IHQ9ZnVuY3Rpb24oKXt2YXIgYT10aGlzLl9jbG9jay5nZXREZWx0YSgpO3RoaXMuX2NhbWVyYUNvbnRyb2xzLnVwZGF0ZShhKSx0aGlzLl9hbmltYXRpb25GcmFtZUluZGV4PXJlcXVlc3RBbmltYXRpb25GcmFtZSh0KSx0aGlzLl9yZW5kZXJlci5yZW5kZXIodGhpcy5fc2NlbmUsdGhpcy5fY2FtZXJhKX0uYmluZCh0aGlzKTt0KCl9dXBkYXRlU2NlbmUodCxyKXtsZXQgbj17fTsiY29uZmlnImluIHQmJnQuY29uZmlnJiYobj1KU09OLnBhcnNlKHQuY29uZmlnKSksdGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJiZWZvcmVVcGRhdGVTY2VuZSJ9KTtsZXQgaT17Y2FtZXJhOntjbHM6IlBlcnNwZWN0aXZlQ2FtZXJhIixmb3Y6NzUsbmVhcjouMSxmYXI6MWUzfSxsaWdodHM6W3tjbHM6IkFtYmllbnRMaWdodCIsY29sb3I6IiNmZmZmZmYiLGludGVuc2l0eTouNzV9LHtjbHM6IkRpcmVjdGlvbmFsTGlnaHQiLGNvbG9yOiIjZmZmZmZmIixpbnRlbnNpdHk6Ljc1LHBvc2l0aW9uOlswLC0xLDJdfV19O249dGhpcy5fYXBwbHlEZWZhdWx0cyhuLGkpLHRoaXMuX2NyZWF0ZVdvcmxkKG4sciksdGhpcy5fY2xlYXJTY2VuZSgpLHRoaXMuX2NyZWF0ZUxpZ2h0cyh0aGlzLl9zY2VuZSxuKSx0aGlzLl9jcmVhdGVHZW9tZXRyeSh0LG4pLHRoaXMuX2NyZWF0ZUxheWVycygpLHRoaXMuZHJhdygpfXJlc2V0Vmlldyh0KXt2YXIgbixpO2lmKCF0aGlzLmlzUmVhZHkoKSlyZXR1cm47KG49dGhpcy5fY2FtZXJhQ29udHJvbHMpPT1udWxsfHxuLnJlc2V0KCk7bGV0IHI7IXQmJnRoaXMuX2xhc3RNZXNoJiYocj10aGlzLl9sYXN0TWVzaCksciYmKHRoaXMuX2ZpdE9iamVjdFRvVmlld3BvcnQociksdGhpcy5fbGFzdE1lc2g9ciksKGk9dGhpcy5fY2FtZXJhQ29udHJvbHMpPT1udWxsfHxpLnVwZGF0ZSgpfV9jcmVhdGVHZW9tZXRyeSh0LHIpe2xldCBuPXQubWVzaDtuLnZlcnRpY2VzJiZuLmZhY2VzJiZuLmZhY2VzLmxlbmd0aD90aGlzLl9jcmVhdGVNZXNoKG4scik6dGhpcy5fY3JlYXRlUG9pbnRDbG91ZChuLHIpfV9jcmVhdGVQb2ludENsb3VkKHQscil7dmFyIGg7bGV0IG49dC52ZXJ0aWNlcyxpPXQuY29sb3JzLG89e21hdGVyaWFsOntjbHM6IlBvaW50c01hdGVyaWFsIixzaXplOi4wMDV9fTtpJiZpLmxlbmd0aD09bi5sZW5ndGg/by5tYXRlcmlhbC52ZXJ0ZXhDb2xvcnM9ITA6by5tYXRlcmlhbC5jb2xvcj10aGlzLl9ydW5Db2xvcjtsZXQgYT10aGlzLl9hcHBseURlZmF1bHRzKHIsbykscz1uZXcgUGUsbD1uZXcgRmxvYXQzMkFycmF5KG4uZmxhdCgpKTtpZihzLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyBKZShsLDMpKSxpJiZpLmxlbmd0aD09bi5sZW5ndGgpe2xldCBmPW5ldyBGbG9hdDMyQXJyYXkoaS5mbGF0KCkpO2ZvcihsZXQgcD0wO3A8Zi5sZW5ndGg7cCsrKWZbcF09ZltwXS8yNTU7cy5zZXRBdHRyaWJ1dGUoImNvbG9yIixuZXcgSmUoZiwzKSl9dmFyIGM9bmV3IHdNW2EubWF0ZXJpYWwuY2xzXShhLm1hdGVyaWFsKSx1PW5ldyBpbShzLGMpOyhoPXRoaXMuX3NjZW5lKT09bnVsbHx8aC5hZGQodSksdGhpcy5fbGFzdE1lc2g9dX1zZXRDYW1lcmFWaWV3cG9pbnQodCxyLG4pe3RoaXMuX3NpbGVudD0hMCx0aGlzLl9jYW1lcmEmJih0aGlzLl9jYW1lcmEuZmFyPXIsdGhpcy5fY2FtZXJhLnBvc2l0aW9uLnNldCh0LngsdC55LHQueiksdGhpcy5fY2FtZXJhLmxvb2tBdChuLmNsb25lKCkpLHRoaXMuX2NhbWVyYS51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCkpLHRoaXMuX2NhbWVyYUNvbnRyb2xzJiYodGhpcy5fY2FtZXJhQ29udHJvbHMudGFyZ2V0PW4uY2xvbmUoKSx0aGlzLl9jYW1lcmFDb250cm9scy51cGRhdGUoKSksdGhpcy5fc2lsZW50PSExfV9vbkNhbWVyYVBvc2l0aW9uQ2hhbmdlKHQpe3RoaXMuX3NpbGVudHx8dGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJjYW1lcmFQb3NpdGlvbkNoYW5nZSIsZXZlbnQ6dH0pfV9maXRPYmplY3RUb1ZpZXdwb3J0KHQpe3ZhciBwLGQsZztsZXQgbj1uZXcgdGEsaT1uZXcgaixvPW5ldyBqO24uc2V0RnJvbU9iamVjdCh0KSxuLmdldENlbnRlcihpKSxuLmdldFNpemUobyk7bGV0IGE9TWF0aC5tYXgoby54LG8ueSxvLnopLHM9KChwPXRoaXMuX2NhbWVyYSk9PW51bGw/dm9pZCAwOnAuZm92KSooTWF0aC5QSS8xODApLGw9TWF0aC5hYnMoYS8oMipNYXRoLnRhbihzLzIpKSkqMS4yNSxjPW4ubWluLnosdT1jPDA/LWMrbDpsLWMsaD0oZD10aGlzLmluaXRDYW1lcmFQb3NpdGlvbikhPW51bGw/ZDpuZXcgaihpLngsaS55LGwpLGY9KGc9dGhpcy5pbml0Q2FtZXJhTG9va0F0KSE9bnVsbD9nOmk7dGhpcy5zZXRDYW1lcmFWaWV3cG9pbnQoaCx1KjMsZil9X2NyZWF0ZU1lc2godCxyKXt2YXIgZjtsZXQgbj10LnZlcnRpY2VzLGk9dC5mYWNlcyxvPXQuY29sb3JzLGE9dGhpcy5fYXBwbHlEZWZhdWx0cyhyLHttYXRlcmlhbDp7Y2xzOiJNZXNoU3RhbmRhcmRNYXRlcmlhbCIsY29sb3I6IiNhMGEwYTAiLHJvdWdobmVzczoxLG1ldGFsbmVzczowfX0pLHM9bmV3IFBlLGw9bmV3IEZsb2F0MzJBcnJheShuLmZsYXQoKSk7cy5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgSmUobCwzKSk7bGV0IGM9bmV3IFVpbnQxNkFycmF5KGkuZmxhdCgpKTtpZihvJiZvLmxlbmd0aCl7bGV0IHA9by5mbGF0KCk7Zm9yKGxldCBkPTA7ZDxwLmxlbmd0aDtkKyspcFtkXT1wW2RdLzI1NTtzLnNldEF0dHJpYnV0ZSgiY29sb3IiLG5ldyBKZShuZXcgRmxvYXQzMkFycmF5KHApLDMpKSxhLm1hdGVyaWFsPWEubWF0ZXJpYWx8fHt9LGEubWF0ZXJpYWwudmVydGV4Q29sb3JzPSEwfXMuY2VudGVyKCkscy5jb21wdXRlQm91bmRpbmdTcGhlcmUoKSxzLnNldEluZGV4KG5ldyBKZShjLDEpKSxzLmNvbXB1dGVWZXJ0ZXhOb3JtYWxzKCk7bGV0IHU9bmV3IHdNW2EubWF0ZXJpYWwuY2xzXShhLm1hdGVyaWFsKSxoPW5ldyBlaShzLHUpO2guY2FzdFNoYWRvdz0hMCxoLnJlY2VpdmVTaGFkb3c9ITAsKGY9dGhpcy5fc2NlbmUpPT1udWxsfHxmLmFkZChoKSx0aGlzLl9sYXN0TWVzaD1ofV9jcmVhdGVMaWdodHModCxyKXtmb3IobGV0IG49MDtuPHIubGlnaHRzLmxlbmd0aDtuKyspe2xldCBpPXIubGlnaHRzW25dLG89bmV3IHdNW2kuY2xzXShpLmNvbG9yLGkuaW50ZW5zaXR5KTtpLnBvc2l0aW9uJiZvLnBvc2l0aW9uLnNldChpLnBvc2l0aW9uWzBdLGkucG9zaXRpb25bMV0saS5wb3NpdGlvblsyXSksdC5hZGQobyl9fX07dmFyIGtuPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLnNlbGVjdGVkVmlldz0iYWxsIix0aGlzLmFjdGl2ZT0hMSx0aGlzLl9jb2xvclNjYWxlRnVuY3Rpb249Zm4sdGhpcy5fc3RlcHM9W10sdGhpcy5fbWVzaFZpZXdlckF0dGFjaGVkPSExLHRoaXMuX2NhbWVyYVBvc2l0aW9uSW5pdGlhbGl6ZWQ9ITEsdGhpcy5faXNNZXNoTG9hZGluZz0hMX1nZXQgX3J1bkNvbG9yKCl7dmFyIHQ9dGhpcy5ydW47cmV0dXJuIHRoaXMuX2NvbG9yU2NhbGVGdW5jdGlvbih0KX1jb25uZWN0ZWRDYWxsYmFjaygpe3N1cGVyLmNvbm5lY3RlZENhbGxiYWNrKCksdGhpcy5fZGF0YVByb3ZpZGVyPW5ldyBQUCh0aGlzLnJlcXVlc3RNYW5hZ2VyKTtsZXQgdD1uZXcgejYodGhpcy5fcnVuQ29sb3IpO3QuYWRkRXZlbnRMaXN0ZW5lcigiYmVmb3JlVXBkYXRlU2NlbmUiLHRoaXMuX3VwZGF0ZUNhbnZhc1NpemUuYmluZCh0aGlzKSksdC5hZGRFdmVudExpc3RlbmVyKCJjYW1lcmFQb3NpdGlvbkNoYW5nZSIsdGhpcy5fb25DYW1lcmFQb3NpdGlvbkNoYW5nZS5iaW5kKHRoaXMpKSx0aGlzLl9tZXNoVmlld2VyPXR9cmVsb2FkKCl7IXRoaXMuYWN0aXZlfHwhdGhpcy5fZGF0YVByb3ZpZGVyfHwodGhpcy5faXNNZXNoTG9hZGluZz0hMCx0aGlzLl9kYXRhUHJvdmlkZXIucmVsb2FkKHRoaXMucnVuLHRoaXMudGFnLHRoaXMuc2FtcGxlKS50aGVuKHQ9PnshdHx8KHRoaXMuX3N0ZXBzPXQsdGhpcy5fc3RlcEluZGV4PXQubGVuZ3RoLTEpfSkuY2F0Y2godD0+e2lmKCF0fHwhdC5jb2RlfHx0LmNvZGUhPWR2LkNBTkNFTExFRCl0aHJvdyB0PXR8fCJSZXNwb25zZSBwcm9jZXNzaW5nIGZhaWxlZC4iLG5ldyBFcnJvcih0KX0pKX1fdXBkYXRlU2NlbmUoKXt2YXIgcjtsZXQgdD10aGlzLl9jdXJyZW50U3RlcDshdHx8IXQubWVzaHx8KHRoaXMuX21lc2hWaWV3ZXIudXBkYXRlU2NlbmUodCx0aGlzKSx0aGlzLl9jYW1lcmFQb3NpdGlvbkluaXRpYWxpemVkfHwodGhpcy5fbWVzaFZpZXdlci5yZXNldFZpZXcoKSx0aGlzLl9jYW1lcmFQb3NpdGlvbkluaXRpYWxpemVkPSEwKSx0aGlzLl9tZXNoVmlld2VyQXR0YWNoZWR8fCgocj10aGlzLnNoYWRvd1Jvb3QpPT1udWxsfHxyLmFwcGVuZENoaWxkKHRoaXMuX21lc2hWaWV3ZXIuZ2V0UmVuZGVyZXIoKS5kb21FbGVtZW50KSx0aGlzLl9tZXNoVmlld2VyQXR0YWNoZWQ9ITApKX1fZGVib3VuY2VkRmV0Y2hNZXNoKCl7dGhpcy5kZWJvdW5jZSgiZmV0Y2hNZXNoIiwoKT0+dGhpcy5fbWF5YmVGZXRjaE1lc2goKSwxMDApfV9tYXliZUZldGNoTWVzaCgpe3JldHVybiBSaSh0aGlzLG51bGwsZnVuY3Rpb24qKCl7bGV0IHQ9dGhpcy5fY3VycmVudFN0ZXA7aWYoISghdHx8dC5tZXNofHx0Lm1lc2hGZXRjaGluZykpe3QubWVzaEZldGNoaW5nPSEwLHRoaXMuX2lzTWVzaExvYWRpbmc9ITA7dHJ5e2xldCByPXlpZWxkIHRoaXMuX2RhdGFQcm92aWRlci5mZXRjaERhdGEodCx0aGlzLnJ1bix0aGlzLnRhZyx0aGlzLnNhbXBsZSk7dC5tZXNoPXJbMF0sdGhpcy5ub3RpZnlQYXRoKCJfY3VycmVudFN0ZXAubWVzaCIpfWNhdGNoKHIpe2lmKCFyfHwhci5jb2RlfHxyLmNvZGUhPWR2LkNBTkNFTExFRCl0aHJvdyByPXJ8fCJSZXNwb25zZSBwcm9jZXNzaW5nIGZhaWxlZC4iLG5ldyBFcnJvcihyKX1maW5hbGx5e3RoaXMuX2lzTWVzaExvYWRpbmc9ITEsdC5tZXNoRmV0Y2hpbmc9ITF9fX0pfV9vbkNhbWVyYVBvc2l0aW9uQ2hhbmdlKCl7aWYoIXRoaXMuX21lc2hWaWV3ZXIuaXNSZWFkeSgpKXJldHVybjtsZXQgdD1uZXcgQ3VzdG9tRXZlbnQoImNhbWVyYS1wb3NpdGlvbi1jaGFuZ2UiLHtkZXRhaWw6dGhpcy5fbWVzaFZpZXdlci5nZXRDYW1lcmFQb3NpdGlvbigpfSk7dGhpcy5kaXNwYXRjaEV2ZW50KHQpfXNldENhbWVyYVZpZXdwb2ludCh0LHIsbil7dGhpcy5fbWVzaFZpZXdlci5zZXRDYW1lcmFWaWV3cG9pbnQodCxyLG4pfV91cGRhdGVDYW52YXNTaXplKCl7bGV0IHQ9dGhpcy5vZmZzZXRXaWR0aCxyPXQsbj10aGlzLiQkKCIudGYtbWVzaC1sb2FkZXItaGVhZGVyIikub2Zmc2V0SGVpZ2h0LGk9e3dpZHRoOnQsaGVpZ2h0OnItbn07dGhpcy5fbWVzaFZpZXdlci5zZXRDYW52YXNTaXplKGkpfXJlZHJhdygpe3RoaXMuX3VwZGF0ZUNhbnZhc1NpemUoKSx0aGlzLmlzQ29ubmVjdGVkJiZ0aGlzLl9tZXNoVmlld2VyLmRyYXcoKX1faGFzQXRMZWFzdE9uZVN0ZXAodCl7cmV0dXJuISF0JiZ0Lmxlbmd0aD4wfV9oYXNNdWx0aXBsZVN0ZXBzKHQpe3JldHVybiEhdCYmdC5sZW5ndGg+MX1nZXQgX2N1cnJlbnRTdGVwKCl7dmFyIHQ9dGhpcy5fc3RlcHMscj10aGlzLl9zdGVwSW5kZXg7cmV0dXJuIHRbcl18fG51bGx9Z2V0IF9zdGVwVmFsdWUoKXtsZXQgdD10aGlzLl9jdXJyZW50U3RlcDtyZXR1cm4gdD90LnN0ZXA6MH1nZXQgX2N1cnJlbnRXYWxsVGltZSgpe2xldCB0PXRoaXMuX2N1cnJlbnRTdGVwO3JldHVybiB0P3MyKHQud2FsbF90aW1lKToiIn1fZ2V0TWF4U3RlcEluZGV4KHQpe3JldHVybiB0Lmxlbmd0aC0xfV9nZXRTYW1wbGVUZXh0KHQpe3JldHVybiBTdHJpbmcodCsxKX1faGFzTXVsdGlwbGVTYW1wbGVzKHQpe3JldHVybiB0PjF9X3VwZGF0ZVZpZXcoKXt2YXIgdD10aGlzLnNlbGVjdGVkVmlldzt0aGlzLl9tZXNoVmlld2VyJiZ0PT0iYWxsIiYmdGhpcy5fbWVzaFZpZXdlci5yZXNldFZpZXcoKX10b0xvY2FsZVN0cmluZ18odCl7cmV0dXJuIHQudG9Mb2NhbGVTdHJpbmcoKX19O2tuLnRlbXBsYXRlPVFgCiAgICA8dGYtY2FyZC1oZWFkaW5nIGNvbG9yPSJbW19ydW5Db2xvcl1dIiBjbGFzcz0idGYtbWVzaC1sb2FkZXItaGVhZGVyIj4KICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19oYXNNdWx0aXBsZVNhbXBsZXMob2ZTYW1wbGVzKV1dIj4KICAgICAgICA8ZGl2PnNhbXBsZTogW1tfZ2V0U2FtcGxlVGV4dChzYW1wbGUpXV0gb2YgW1tvZlNhbXBsZXNdXTwvZGl2PgogICAgICA8L3RlbXBsYXRlPgogICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2hhc0F0TGVhc3RPbmVTdGVwKF9zdGVwcyldXSI+CiAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1yb3ciPgogICAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1sYWJlbCI+CiAgICAgICAgICAgIHN0ZXAKICAgICAgICAgICAgPHNwYW4gc3R5bGU9ImZvbnQtd2VpZ2h0OiBib2xkIgogICAgICAgICAgICAgID5bW3RvTG9jYWxlU3RyaW5nXyhfc3RlcFZhbHVlKV1dPC9zcGFuCiAgICAgICAgICAgID4KICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPGRpdiBjbGFzcz0iaGVhZGluZy1sYWJlbCBoZWFkaW5nLXJpZ2h0Ij4KICAgICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19jdXJyZW50V2FsbFRpbWVdXSI+CiAgICAgICAgICAgICAgW1tfY3VycmVudFdhbGxUaW1lXV0KICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPGRpdiBjbGFzcz0ibGFiZWwgcmlnaHQiPgogICAgICAgICAgICA8cGFwZXItc3Bpbm5lci1saXRlIGFjdGl2ZSBoaWRkZW4kPSJbWyFfaXNNZXNoTG9hZGluZ11dIj4KICAgICAgICAgICAgPC9wYXBlci1zcGlubmVyLWxpdGU+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19oYXNNdWx0aXBsZVN0ZXBzKF9zdGVwcyldXSI+CiAgICAgICAgPGRpdj4KICAgICAgICAgIDxwYXBlci1zbGlkZXIKICAgICAgICAgICAgaWQ9InN0ZXBzIgogICAgICAgICAgICBpbW1lZGlhdGUtdmFsdWU9Int7X3N0ZXBJbmRleH19IgogICAgICAgICAgICBtYXg9IltbX2dldE1heFN0ZXBJbmRleChfc3RlcHMpXV0iCiAgICAgICAgICAgIG1heC1tYXJrZXJzPSJbW19nZXRNYXhTdGVwSW5kZXgoX3N0ZXBzKV1dIgogICAgICAgICAgICBzbmFwcwogICAgICAgICAgICBzdGVwPSIxIgogICAgICAgICAgICB2YWx1ZT0ie3tfc3RlcEluZGV4fX0iCiAgICAgICAgICA+PC9wYXBlci1zbGlkZXI+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvdGVtcGxhdGU+CiAgICA8L3RmLWNhcmQtaGVhZGluZz4KICAgIDxzdHlsZT4KICAgICAgcGFwZXItc2xpZGVyIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgICBtYXJnaW4tbGVmdDogMXB4OwogICAgICAgIG1hcmdpbi1yaWdodDogMXB4OwogICAgICB9CiAgICAgIC50Zi1tZXNoLWxvYWRlci1oZWFkZXIgewogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgIGhlaWdodDogMTA1cHg7CiAgICAgIH0KICAgICAgW2hpZGRlbl0gewogICAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxrbi5wcm90b3R5cGUsInJ1biIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxrbi5wcm90b3R5cGUsInRhZyIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxrbi5wcm90b3R5cGUsInNhbXBsZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk51bWJlcn0pLHcoImRlc2lnbjp0eXBlIixOdW1iZXIpXSxrbi5wcm90b3R5cGUsIm9mU2FtcGxlcyIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxrbi5wcm90b3R5cGUsInNlbGVjdGVkVmlldyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGtuLnByb3RvdHlwZSwiYWN0aXZlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEFlKV0sa24ucHJvdG90eXBlLCJyZXF1ZXN0TWFuYWdlciIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIix6NildLGtuLnByb3RvdHlwZSwiX21lc2hWaWV3ZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsUFApXSxrbi5wcm90b3R5cGUsIl9kYXRhUHJvdmlkZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sa24ucHJvdG90eXBlLCJfY29sb3JTY2FsZUZ1bmN0aW9uIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXksbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sa24ucHJvdG90eXBlLCJfc3RlcHMiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXIsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLGtuLnByb3RvdHlwZSwiX3N0ZXBJbmRleCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGtuLnByb3RvdHlwZSwiX21lc2hWaWV3ZXJBdHRhY2hlZCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGtuLnByb3RvdHlwZSwiX2NhbWVyYVBvc2l0aW9uSW5pdGlhbGl6ZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxrbi5wcm90b3R5cGUsIl9pc01lc2hMb2FkaW5nIix2b2lkIDApO0UoW1J0KCJydW4iKSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxrbi5wcm90b3R5cGUsIl9ydW5Db2xvciIsbnVsbCk7RShbQnQoInJ1biIsInRhZyIsImFjdGl2ZSIsIl9kYXRhUHJvdmlkZXIiLCJfbWVzaFZpZXdlciIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sa24ucHJvdG90eXBlLCJyZWxvYWQiLG51bGwpO0UoW0J0KCJfY3VycmVudFN0ZXAuKiIsIl9tZXNoVmlld2VyIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxrbi5wcm90b3R5cGUsIl91cGRhdGVTY2VuZSIsbnVsbCk7RShbQnQoIl9jdXJyZW50U3RlcCIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sa24ucHJvdG90eXBlLCJfZGVib3VuY2VkRmV0Y2hNZXNoIixudWxsKTtFKFtSdCgiX3N0ZXBzIiwiX3N0ZXBJbmRleCIpLHcoImRlc2lnbjp0eXBlIixPYmplY3QpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLGtuLnByb3RvdHlwZSwiX2N1cnJlbnRTdGVwIixudWxsKTtFKFtSdCgiX2N1cnJlbnRTdGVwIiksdygiZGVzaWduOnR5cGUiLE51bWJlciksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sa24ucHJvdG90eXBlLCJfc3RlcFZhbHVlIixudWxsKTtFKFtSdCgiX2N1cnJlbnRTdGVwIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sa24ucHJvdG90eXBlLCJfY3VycmVudFdhbGxUaW1lIixudWxsKTtFKFtCdCgic2VsZWN0ZWRWaWV3IiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxrbi5wcm90b3R5cGUsIl91cGRhdGVWaWV3IixudWxsKTtrbj1FKFt5dCgidGYtbWVzaC1sb2FkZXIiKV0sa24pO3ZhciBwaD1jbGFzcyBleHRlbmRzIG10e2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLnJlbG9hZE9uUmVhZHk9ITAsdGhpcy5fdGFnRmlsdGVyPSIuKiIsdGhpcy5fc2VsZWN0ZWRWaWV3PSJhbGwiLHRoaXMuX3JlcXVlc3RNYW5hZ2VyPW5ldyBBZSx3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigicmVzaXplIiwoKT0+e3RoaXMuX2hhbmRsZVdpbmRvd1Jlc2l6ZSgpfSwhMSksdGhpcy5yZWxvYWRPblJlYWR5JiZ0aGlzLnJlbG9hZCgpfV9nZXRBbGxDaGlsZHJlbigpe3ZhciB0O3JldHVybiBBcnJheS5mcm9tKCh0PXRoaXMuc2hhZG93Um9vdCk9PW51bGw/dm9pZCAwOnQucXVlcnlTZWxlY3RvckFsbCgidGYtbWVzaC1sb2FkZXIiKSl9X29uQ2FtZXJhUG9zaXRpb25DaGFuZ2VkKHQpe3RoaXMuX3NlbGVjdGVkVmlldz09InNoYXJlIiYmdGhpcy5fZ2V0QWxsQ2hpbGRyZW4oKS5mb3JFYWNoKHI9Pnt0LnRhcmdldCE9ciYmci5zZXRDYW1lcmFWaWV3cG9pbnQodC5kZXRhaWwucG9zaXRpb24sdC5kZXRhaWwuZmFyLHQuZGV0YWlsLnRhcmdldCl9KX1fc2hvdWxkT3Blbih0KXtyZXR1cm4gdDw9Mn1yZWxvYWQoKXt0aGlzLl9mZXRjaFRhZ3MoKS50aGVuKHRoaXMuX3JlbG9hZE1lc2hlcy5iaW5kKHRoaXMpKX1faGFuZGxlV2luZG93UmVzaXplKCl7dGhpcy5fZ2V0QWxsQ2hpbGRyZW4oKS5mb3JFYWNoKHQ9Pnt0LnJlZHJhdygpfSl9X2ZldGNoVGFncygpe2xldCB0PXZlKCkucGx1Z2luUm91dGUoIm1lc2giLCIvdGFncyIpO3JldHVybiB0aGlzLl9yZXF1ZXN0TWFuYWdlci5yZXF1ZXN0KHQpLnRoZW4ocj0+e2lmKHN4LmlzRXF1YWwocix0aGlzLl9ydW5Ub1RhZ0luZm8pKXJldHVybjtsZXQgbj1zeC5tYXBWYWx1ZXMocixvPT5PYmplY3Qua2V5cyhvKSksaT0kaShuKTt0aGlzLl9kYXRhTm90Rm91bmQ9aS5sZW5ndGg9PT0wLHRoaXMuX3J1blRvVGFnSW5mbz1yfSl9X3JlbG9hZE1lc2hlcygpe3RoaXMuX2dldEFsbENoaWxkcmVuKCkuZm9yRWFjaCh0PT57dC5yZWxvYWQoKX0pfWdldCBfY2F0ZWdvcmllcygpe3ZhciB0PXRoaXMuX3J1blRvVGFnSW5mbyxyPXRoaXMuX3NlbGVjdGVkUnVucyxuPXRoaXMuX3RhZ0ZpbHRlcjtsZXQgaT1zeC5tYXBWYWx1ZXModCxsPT5PYmplY3Qua2V5cyhsKSksbz1RbChpLHIsbik7ZnVuY3Rpb24gYShsKXtsZXQgYz10W2wucnVuXVtsLnRhZ10uc2FtcGxlcztyZXR1cm4gc3gucmFuZ2UoYykubWFwKHU9Pk9iamVjdC5hc3NpZ24oe30sbCx7c2FtcGxlOnUsb2ZTYW1wbGVzOmN9KSl9cmV0dXJuIG8ubWFwKGw9Pk9iamVjdC5hc3NpZ24oe30sbCx7aXRlbXM6W10uY29uY2F0LmFwcGx5KFtdLGwuaXRlbXMubWFwKGEpKX0pKX19O3BoLnRlbXBsYXRlPVFgCiAgICA8dGYtZGFzaGJvYXJkLWxheW91dD4KICAgICAgPGRpdiBzbG90PSJzaWRlYmFyIiBjbGFzcz0iYWxsLWNvbnRyb2xzIj4KICAgICAgICA8ZGl2IGNsYXNzPSJzZXR0aW5ncyI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyLXNlY3Rpb24gdmlldy1jb250cm9sIj4KICAgICAgICAgICAgPGgzIGNsYXNzPSJ0aXRsZSI+UG9pbnQgb2YgdmlldzwvaDM+CiAgICAgICAgICAgIDxkaXY+CiAgICAgICAgICAgICAgPHBhcGVyLXJhZGlvLWdyb3VwCiAgICAgICAgICAgICAgICBpZD0idmlldy1yYWRpby1ncm91cCIKICAgICAgICAgICAgICAgIHNlbGVjdGVkPSJ7e19zZWxlY3RlZFZpZXd9fSIKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8cGFwZXItcmFkaW8tYnV0dG9uIGlkPSJhbGwtcmFkaW8tYnV0dG9uIiBuYW1lPSJhbGwiPgogICAgICAgICAgICAgICAgICBEaXNwbGF5IGFsbCBwb2ludHMKICAgICAgICAgICAgICAgIDwvcGFwZXItcmFkaW8tYnV0dG9uPgogICAgICAgICAgICAgICAgPHBhcGVyLXRvb2x0aXAKICAgICAgICAgICAgICAgICAgYW5pbWF0aW9uLWRlbGF5PSIwIgogICAgICAgICAgICAgICAgICBmb3I9ImFsbC1yYWRpby1idXR0b24iCiAgICAgICAgICAgICAgICAgIHBvc2l0aW9uPSJyaWdodCIKICAgICAgICAgICAgICAgICAgb2Zmc2V0PSIwIgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICBab29tIGFuZCBjZW50ZXIgY2FtZXJhIHRvIGRpc3BsYXkgYWxsIHBvaW50cyBhdCBvbmNlLiBOb3RlLAogICAgICAgICAgICAgICAgICB0aGF0IHNvbWUgcG9pbnRzIGNvdWxkIGJlIHRvbyBmYXIgKGkuZS4gdG9vIHNtYWxsKSB0byBiZQogICAgICAgICAgICAgICAgICB2aXNpYmxlLgogICAgICAgICAgICAgICAgPC9wYXBlci10b29sdGlwPgogICAgICAgICAgICAgICAgPHBhcGVyLXJhZGlvLWJ1dHRvbiBpZD0idXNlci1yYWRpby1idXR0b24iIG5hbWU9InVzZXIiPgogICAgICAgICAgICAgICAgICBDdXJyZW50IHZpZXcKICAgICAgICAgICAgICAgIDwvcGFwZXItcmFkaW8tYnV0dG9uPgogICAgICAgICAgICAgICAgPHBhcGVyLXRvb2x0aXAKICAgICAgICAgICAgICAgICAgYW5pbWF0aW9uLWRlbGF5PSIwIgogICAgICAgICAgICAgICAgICBmb3I9InVzZXItcmFkaW8tYnV0dG9uIgogICAgICAgICAgICAgICAgICBwb3NpdGlvbj0icmlnaHQiCiAgICAgICAgICAgICAgICAgIG9mZnNldD0iMCIKICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgS2VlcCBjdXJyZW50IGNhbWVyYSBwb3NpdGlvbiBhbmQgem9vbSBsZXZlbC4KICAgICAgICAgICAgICAgIDwvcGFwZXItdG9vbHRpcD4KICAgICAgICAgICAgICAgIDxwYXBlci1yYWRpby1idXR0b24gaWQ9InNoYXJlLXJhZGlvLWJ1dHRvbiIgbmFtZT0ic2hhcmUiPgogICAgICAgICAgICAgICAgICBTaGFyZSB2aWV3cG9pbnQKICAgICAgICAgICAgICAgIDwvcGFwZXItcmFkaW8tYnV0dG9uPgogICAgICAgICAgICAgICAgPHBhcGVyLXRvb2x0aXAKICAgICAgICAgICAgICAgICAgYW5pbWF0aW9uLWRlbGF5PSIwIgogICAgICAgICAgICAgICAgICBmb3I9InNoYXJlLXJhZGlvLWJ1dHRvbiIKICAgICAgICAgICAgICAgICAgcG9zaXRpb249InJpZ2h0IgogICAgICAgICAgICAgICAgICBvZmZzZXQ9IjAiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIFNoYXJlIHZpZXdwb2ludCBhbW9uZyBhbGwgY2FtZXJhcy4KICAgICAgICAgICAgICAgIDwvcGFwZXItdG9vbHRpcD4KICAgICAgICAgICAgICA8L3BhcGVyLXJhZGlvLWdyb3VwPgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9InNpZGViYXItc2VjdGlvbiBydW5zLXNlbGVjdG9yIj4KICAgICAgICAgIDx0Zi1ydW5zLXNlbGVjdG9yIHNlbGVjdGVkLXJ1bnM9Int7X3NlbGVjdGVkUnVuc319Ij4KICAgICAgICAgIDwvdGYtcnVucy1zZWxlY3Rvcj4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgc2xvdD0iY2VudGVyIj4KICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2RhdGFOb3RGb3VuZF1dIj4KICAgICAgICAgIDxkaXYgY2xhc3M9Im5vLWRhdGEtd2FybmluZyI+CiAgICAgICAgICAgIDxoMz5ObyBwb2ludCBjbG91ZCBkYXRhIHdhcyBmb3VuZC48L2gzPgogICAgICAgICAgICA8cD5Qcm9iYWJsZSBjYXVzZXM6PC9wPgogICAgICAgICAgICA8dWw+CiAgICAgICAgICAgICAgPGxpPgogICAgICAgICAgICAgICAgWW91IGhhdmVu4oCZdCB3cml0dGVuIGFueSBwb2ludCBjbG91ZCBkYXRhIHRvIHlvdXIgZXZlbnQgZmlsZXMuCiAgICAgICAgICAgICAgPC9saT4KICAgICAgICAgICAgICA8bGk+VGVuc29yQm9hcmQgY2Fu4oCZdCBmaW5kIHlvdXIgZXZlbnQgZmlsZXMuPC9saT4KICAgICAgICAgICAgPC91bD4KCiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgIElmIHlvdeKAmXJlIG5ldyB0byB1c2luZyBUZW5zb3JCb2FyZCwgYW5kIHdhbnQgdG8gZmluZCBvdXQgaG93IHRvCiAgICAgICAgICAgICAgYWRkIGRhdGEgYW5kIHNldCB1cCB5b3VyIGV2ZW50IGZpbGVzLCBjaGVjayBvdXQgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCIKICAgICAgICAgICAgICAgID5SRUFETUU8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIHBlcmhhcHMgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiCiAgICAgICAgICAgICAgICA+VGVuc29yQm9hcmQgdHV0b3JpYWw8L2EKICAgICAgICAgICAgICA+LgogICAgICAgICAgICA8L3A+CgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICBJZiB5b3UgdGhpbmsgVGVuc29yQm9hcmQgaXMgY29uZmlndXJlZCBwcm9wZXJseSwgcGxlYXNlIHNlZQogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQjbXktdGVuc29yYm9hcmQtaXNudC1zaG93aW5nLWFueS1kYXRhLXdoYXRzLXdyb25nIgogICAgICAgICAgICAgICAgPnRoZSBzZWN0aW9uIG9mIHRoZSBSRUFETUUgZGV2b3RlZCB0byBtaXNzaW5nIGRhdGEgcHJvYmxlbXM8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIGNvbnNpZGVyIGZpbGluZyBhbiBpc3N1ZSBvbiBHaXRIdWIuCiAgICAgICAgICAgIDwvcD4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbWyFfZGF0YU5vdEZvdW5kXV0iPgogICAgICAgICAgPHRmLXRhZy1maWx0ZXJlciB0YWctZmlsdGVyPSJ7e190YWdGaWx0ZXJ9fSI+PC90Zi10YWctZmlsdGVyZXI+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19jYXRlZ29yaWVzXV0iIGFzPSJjYXRlZ29yeSI+CiAgICAgICAgICAgIDx0Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldwogICAgICAgICAgICAgIGNhdGVnb3J5PSJbW2NhdGVnb3J5XV0iCiAgICAgICAgICAgICAgaW5pdGlhbC1vcGVuZWQ9IltbX3Nob3VsZE9wZW4oaW5kZXgpXV0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8dGYtbWVzaC1sb2FkZXIKICAgICAgICAgICAgICAgICAgYWN0aXZlPSJbW2FjdGl2ZV1dIgogICAgICAgICAgICAgICAgICBzZWxlY3RlZC12aWV3PSJbW19zZWxlY3RlZFZpZXddXSIKICAgICAgICAgICAgICAgICAgcnVuPSJbW2l0ZW0ucnVuXV0iCiAgICAgICAgICAgICAgICAgIHRhZz0iW1tpdGVtLnRhZ11dIgogICAgICAgICAgICAgICAgICBzYW1wbGU9IltbaXRlbS5zYW1wbGVdXSIKICAgICAgICAgICAgICAgICAgb2Ytc2FtcGxlcz0iW1tpdGVtLm9mU2FtcGxlc11dIgogICAgICAgICAgICAgICAgICByZXF1ZXN0LW1hbmFnZXI9IltbX3JlcXVlc3RNYW5hZ2VyXV0iCiAgICAgICAgICAgICAgICAgIGNsYXNzPSJ0Zi1tZXNoLWxvYWRlci1jb250YWluZXIiCiAgICAgICAgICAgICAgICAgIG9uLWNhbWVyYS1wb3NpdGlvbi1jaGFuZ2U9Il9vbkNhbWVyYVBvc2l0aW9uQ2hhbmdlZCIKICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDwvdGYtbWVzaC1sb2FkZXI+CiAgICAgICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICAgICAgPC90Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldz4KICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgPC9kaXY+CiAgICA8L3RmLWRhc2hib2FyZC1sYXlvdXQ+CgogICAgPHN0eWxlIGluY2x1ZGU9ImRhc2hib2FyZC1zdHlsZSI+PC9zdHlsZT4KICAgIDxzdHlsZT4KICAgICAgLm5vLWRhdGEtd2FybmluZyB7CiAgICAgICAgbWF4LXdpZHRoOiA1NDBweDsKICAgICAgICBtYXJnaW46IDgwcHggYXV0byAwIGF1dG87CiAgICAgIH0KICAgICAgcGFwZXItcmFkaW8tYnV0dG9uIHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBwYWRkaW5nOiA1cHg7CiAgICAgIH0KICAgICAgLnNpZGViYXItc2VjdGlvbiBoMyB7CiAgICAgICAgbWFyZ2luOiAwOwogICAgICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7CiAgICAgICAgZm9udC1zaXplOiAxNHB4OwogICAgICAgIG1hcmdpbi1ib3R0b206IDVweDsKICAgICAgfQoKICAgICAgLnJ1bnMtc2VsZWN0b3IgewogICAgICAgIGZsZXgtZ3JvdzogMTsKICAgICAgfQoKICAgICAgdGYtcnVucy1zZWxlY3RvciB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgfQoKICAgICAgLnZpZXctY29udHJvbCB7CiAgICAgICAgZGlzcGxheTogYmxvY2sgIWltcG9ydGFudDsKICAgICAgfQoKICAgICAgLnZpZXctY29udHJvbCBoMy50aXRsZSB7CiAgICAgICAgcGFkZGluZy10b3A6IDE2cHg7CiAgICAgICAgcGFkZGluZy1ib3R0b206IDE2cHg7CiAgICAgIH0KCiAgICAgIC5hbGxjb250cm9scyAudmlldy1jb250cm9sIHBhcGVyLXJhZGlvLWdyb3VwIHsKICAgICAgICBtYXJnaW4tdG9wOiA1cHg7CiAgICAgIH0KICAgICAgLyogTGF5b3V0IG11c3QgYmUgaG9yaXpvbnRhbCwgaS5lLiBpdGVtcyBhcnJhbmdlZCBpbiBhIHJvdy4gSWYgaXRlbXMgY2Fubm90IGZpdCBpbiBhIHJvdywKICAgICAgICogdGhleSBzaG91bGQgYmUgbW92ZWQgdG8gbmV4dCBsaW5lLiBBbGwgaXRlbXMgbXVzdCBiZSBzcXVhcmUgYXQgYWxsIHRpbWVzLiBNaW5pbXVtIHNpemUgb2YKICAgICAgICogdGhlIGl0ZW0gaXMgNDgwcHguIFRoaXMgbWVhbnMgdGhhdCBtYXhpbXVtIHNpemUgb2YgdGhlIGl0ZW0gbXVzdCBiZSA0ODBweCArIDQ3OXB4ID0gOTU5cHguCiAgICAgICAqICovCiAgICAgIC5ob3Jpem9udGFsIHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiByb3c7CiAgICAgICAgZmxleC13cmFwOiB3cmFwOwogICAgICB9CiAgICAgIHRmLW1lc2gtbG9hZGVyIHsKICAgICAgICB3aWR0aDogNDgwcHg7CiAgICAgICAgZmxleC1iYXNpczogNDgwcHg7CiAgICAgICAgZmxleC1ncm93OiAxOwogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxwaC5wcm90b3R5cGUsInJlbG9hZE9uUmVhZHkiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLHBoLnByb3RvdHlwZSwiX3NlbGVjdGVkUnVucyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxwaC5wcm90b3R5cGUsIl9ydW5Ub1RhZ0luZm8iLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxwaC5wcm90b3R5cGUsIl9kYXRhTm90Rm91bmQiLHZvaWQgMCk7RShbQSh7dHlwZTpTdHJpbmd9KSx3KCJkZXNpZ246dHlwZSIsU3RyaW5nKV0scGgucHJvdG90eXBlLCJfdGFnRmlsdGVyIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nLG5vdGlmeTohMH0pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxwaC5wcm90b3R5cGUsIl9zZWxlY3RlZFZpZXciLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0scGgucHJvdG90eXBlLCJfcmVxdWVzdE1hbmFnZXIiLHZvaWQgMCk7RShbUnQoIl9ydW5Ub1RhZ0luZm8iLCJfc2VsZWN0ZWRSdW5zIiwiX3RhZ0ZpbHRlciIpLHcoImRlc2lnbjp0eXBlIixBcnJheSksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0scGgucHJvdG90eXBlLCJfY2F0ZWdvcmllcyIsbnVsbCk7cGg9RShbeXQoIm1lc2gtZGFzaGJvYXJkIiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0scGgpO3ZhciBKVT1jbGFzcyBleHRlbmRzIEd0KG10KXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5faW5zdGFsbENvbW1hbmQ9InBpcCBpbnN0YWxsIC1VIHRlbnNvcmJvYXJkLXBsdWdpbi1wcm9maWxlIn1fY29weUluc3RhbGxDb21tYW5kKCl7cmV0dXJuIFJpKHRoaXMsbnVsbCxmdW5jdGlvbiooKXtsZXQgdD0oKT0+UmkodGhpcyxudWxsLGZ1bmN0aW9uKigpe3RoaXMuJC5jb21tYW5kVGV4dGFyZWEuc2VsZWN0KCk7dHJ5e3lpZWxkIG5hdmlnYXRvci5jbGlwYm9hcmQud3JpdGVUZXh0KHRoaXMuX2luc3RhbGxDb21tYW5kKX1jYXRjaChpKXtpZighZG9jdW1lbnQuZXhlY0NvbW1hbmQoImNvcHkiKSlyZXR1cm4gUHJvbWlzZS5yZWplY3QoKX19KSxyPXRoaXMuJC5jb3BpZWRNZXNzYWdlO3RyeXt5aWVsZCB0KCksci5pbm5lclRleHQ9IkNvcGllZC4ifWNhdGNoKG4pe3IuaW5uZXJUZXh0PSJGYWlsZWQgdG8gY29weSB0byBjbGlwYm9hcmQuIn19KX1fcmVtb3ZlQ29waWVkTWVzc2FnZSgpe2xldCB0PXRoaXMuJC5jb3BpZWRNZXNzYWdlO3QuaW5uZXJUZXh0PSIifX07SlUudGVtcGxhdGU9UWAKICAgIDxkaXYgY2xhc3M9Im1lc3NhZ2UiPgogICAgICA8aDM+VGhlIHByb2ZpbGUgcGx1Z2luIGhhcyBtb3ZlZC48L2gzPgogICAgICA8cD4KICAgICAgICBQbGVhc2UgaW5zdGFsbCB0aGUgbmV3IHZlcnNpb24gb2YgdGhlIHByb2ZpbGUgcGx1Z2luIGZyb20gUHlQSSBieQogICAgICAgIHJ1bm5pbmcgdGhlIGZvbGxvd2luZyBjb21tYW5kIGZyb20gdGhlIG1hY2hpbmUgcnVubmluZyBUZW5zb3JCb2FyZDoKICAgICAgPC9wPgogICAgICA8dGV4dGFyZWEKICAgICAgICBpZD0iY29tbWFuZFRleHRhcmVhIgogICAgICAgIHJlYWRvbmx5PSIiCiAgICAgICAgcm93cz0iMSIKICAgICAgICBvbi1ibHVyPSJfcmVtb3ZlQ29waWVkTWVzc2FnZSIKICAgICAgPgpbW19pbnN0YWxsQ29tbWFuZF1dPC90ZXh0YXJlYQogICAgICA+CiAgICAgIDxkaXYgaWQ9ImNvcHlDb250YWluZXIiPgogICAgICAgIDxzcGFuIGlkPSJjb3BpZWRNZXNzYWdlIj48L3NwYW4+CiAgICAgICAgPHBhcGVyLWJ1dHRvbiByYWlzZWQ9IiIgb24tdGFwPSJfY29weUluc3RhbGxDb21tYW5kIgogICAgICAgICAgPkNvcHkgdG8gY2xpcGJvYXJkPC9wYXBlci1idXR0b24KICAgICAgICA+CiAgICAgIDwvZGl2PgogICAgPC9kaXY+CgogICAgPHN0eWxlPgogICAgICA6aG9zdCB7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgfQoKICAgICAgLm1lc3NhZ2UgewogICAgICAgIG1hcmdpbjogODBweCBhdXRvIDAgYXV0bzsKICAgICAgICBtYXgtd2lkdGg6IDU0MHB4OwogICAgICB9CiAgICAgICNjb21tYW5kVGV4dGFyZWEgewogICAgICAgIG1hcmdpbi10b3A6IDFleDsKICAgICAgICBwYWRkaW5nOiAxZXggMWVtOwogICAgICAgIHJlc2l6ZTogdmVydGljYWw7CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgIH0KICAgICAgI2NvcHlDb250YWluZXIgewogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIH0KICAgICAgI2NvcGllZE1lc3NhZ2UgewogICAgICAgIGFsaWduLXNlbGY6IGNlbnRlcjsKICAgICAgICBmbGV4LWdyb3c6IDE7CiAgICAgICAgZm9udC1zdHlsZTogaXRhbGljOwogICAgICAgIHBhZGRpbmctcmlnaHQ6IDFlbTsKICAgICAgICB0ZXh0LWFsaWduOiByaWdodDsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLEpVLnByb3RvdHlwZSwiX2luc3RhbGxDb21tYW5kIix2b2lkIDApO0pVPUUoW3l0KCJ0Zi1wcm9maWxlLXJlZGlyZWN0LWRhc2hib2FyZCIpXSxKVSk7dmFyIGxtPUVlKE9lKCksMSk7dmFyIHpsPUVlKE9lKCksMSksUVU9RWUod2woKSwxKTt2YXIgbm49Y2xhc3MgZXh0ZW5kcyBtdHtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fZXhwYW5kZWQ9ITEsdGhpcy5fcnVuVG9QckN1cnZlRW50cnk9e30sdGhpcy5fcHJldmlvdXNSdW5Ub1ByQ3VydmVFbnRyeT17fSx0aGlzLl9jb2xvclNjYWxlRnVuY3Rpb249e3NjYWxlOmZufSx0aGlzLl9jYW5jZWxsZXI9bmV3IGFuLHRoaXMuX3hDb21wb25lbnRzQ3JlYXRpb25NZXRob2Q9KCk9PntsZXQgdD1uZXcgUVUuU2NhbGVzLkxpbmVhcjtyZXR1cm57c2NhbGU6dCxheGlzOm5ldyBRVS5BeGVzLk51bWVyaWModCwiYm90dG9tIiksYWNjZXNzb3I6cj0+ci5yZWNhbGx9fSx0aGlzLl95VmFsdWVBY2Nlc3Nvcj10PT50LnByZWNpc2lvbix0aGlzLl90b29sdGlwQ29sdW1ucz0oKCk9PntsZXQgdD1XdShlMCkscj1uPT5pc05hTihuKT8iTmFOIjp0KG4pO3JldHVyblt7dGl0bGU6IlJ1biIsZXZhbHVhdGU6bj0+bi5kYXRhc2V0Lm1ldGFkYXRhKCkubmFtZX0se3RpdGxlOiJUaHJlc2hvbGQiLGV2YWx1YXRlOm49PnIobi5kYXR1bS50aHJlc2hvbGRzKX0se3RpdGxlOiJQcmVjaXNpb24iLGV2YWx1YXRlOm49PnIobi5kYXR1bS5wcmVjaXNpb24pfSx7dGl0bGU6IlJlY2FsbCIsZXZhbHVhdGU6bj0+cihuLmRhdHVtLnJlY2FsbCl9LHt0aXRsZToiVFAiLGV2YWx1YXRlOm49Pm4uZGF0dW0udHJ1ZV9wb3NpdGl2ZXN9LHt0aXRsZToiRlAiLGV2YWx1YXRlOm49Pm4uZGF0dW0uZmFsc2VfcG9zaXRpdmVzfSx7dGl0bGU6IlROIixldmFsdWF0ZTpuPT5uLmRhdHVtLnRydWVfbmVnYXRpdmVzfSx7dGl0bGU6IkZOIixldmFsdWF0ZTpuPT5uLmRhdHVtLmZhbHNlX25lZ2F0aXZlc31dfSkoKSx0aGlzLl9zZXJpZXNEYXRhRmllbGRzPVsidGhyZXNob2xkcyIsInByZWNpc2lvbiIsInJlY2FsbCIsInRydWVfcG9zaXRpdmVzIiwiZmFsc2VfcG9zaXRpdmVzIiwidHJ1ZV9uZWdhdGl2ZXMiLCJmYWxzZV9uZWdhdGl2ZXMiXSx0aGlzLl9kZWZhdWx0WFJhbmdlPVstLjA1LDEuMDVdLHRoaXMuX2RlZmF1bHRZUmFuZ2U9Wy0uMDUsMS4wNV0sdGhpcy5fcmVxdWVzdERhdGE9KHQscixuKT0+e2xldCBvPXZlKCkucGx1Z2luUm91dGUoInByX2N1cnZlcyIsIi9wcl9jdXJ2ZXMiKTtQcm9taXNlLmFsbCh0Lm1hcChhPT57bGV0IHM9YSxsPXRoaXMudGFnLGM9Q24obyx7dGFnOmwscnVuOnN9KTtyZXR1cm4gdGhpcy5yZXF1ZXN0TWFuYWdlci5yZXF1ZXN0KGMpLnRoZW4odT0+dm9pZCByKHtpdGVtOmEsZGF0YTp1fSkpfSkpLmZpbmFsbHkoKCk9PnZvaWQgbigpKX0sdGhpcy5fc21vb3RoaW5nRW5hYmxlZD0hMX1fY3JlYXRlUHJvY2Vzc0RhdGFGdW5jdGlvbigpe3JldHVybih0LHIsbik9Pnt0aGlzLnNldCgiX3J1blRvRGF0YU92ZXJUaW1lIixPYmplY3QuYXNzaWduKHt9LHRoaXMuX3J1blRvRGF0YU92ZXJUaW1lLG4pKX19X2NvbXB1dGVSdW5Db2xvcih0KXtyZXR1cm4gZm4odCl9Y29ubmVjdGVkQ2FsbGJhY2soKXtzdXBlci5jb25uZWN0ZWRDYWxsYmFjaygpLHRoaXMuX2F0dGFjaGVkPSEwLHRoaXMucmVsb2FkKCl9X2dldENoYXJ0RGF0YUxvYWRlcigpe3ZhciB0O3JldHVybih0PXRoaXMuc2hhZG93Um9vdCk9PW51bGw/dm9pZCAwOnQucXVlcnlTZWxlY3RvcigidGYtbGluZS1jaGFydC1kYXRhLWxvYWRlciIpfXJlbG9hZCgpe2lmKCEhdGhpcy5fYXR0YWNoZWQpe2lmKHRoaXMucnVucy5sZW5ndGg9PT0wKXt0aGlzLnNldCgiX3J1blRvRGF0YU92ZXJUaW1lIix7fSk7cmV0dXJufXRoaXMuX2dldENoYXJ0RGF0YUxvYWRlcigpLnJlbG9hZCgpfX1fc2V0Q2hhcnREYXRhKCl7dmFyIHQ9dGhpcy5fcnVuVG9QckN1cnZlRW50cnkscj10aGlzLl9wcmV2aW91c1J1blRvUHJDdXJ2ZUVudHJ5LG49dGhpcy5fc2V0T2ZSZWxldmFudFJ1bnM7emwuZm9yT3duKHQsKGksbyk9PntsZXQgYT1yW29dO2lmKCEoYSYmdFtvXS5zdGVwPT09YS5zdGVwKSl7aWYoIW5bb10pe3RoaXMuX2NsZWFyU2VyaWVzRGF0YShvKTtyZXR1cm59dGhpcy5fdXBkYXRlU2VyaWVzRGF0YUZvclJ1bihvLGkpfX0pfV91cGRhdGVTZXJpZXNEYXRhRm9yUnVuKHQscil7bGV0IG49emwucmVkdWNlKHRoaXMuX3Nlcmllc0RhdGFGaWVsZHMsKGEscyk9PihhW3NdPXJbc10uc2xpY2UoKS5yZXZlcnNlKCksYSkse30pLGk9bmV3IEFycmF5KG5bdGhpcy5fc2VyaWVzRGF0YUZpZWxkc1swXV0ubGVuZ3RoKTtmb3IobGV0IGE9MDthPGkubGVuZ3RoO2ErKylpW2FdPXpsLm1hcFZhbHVlcyhuLHM9PnNbYV0pO2xldCBvPXRoaXMuX2dldENoYXJ0RGF0YUxvYWRlcigpO28uc2V0U2VyaWVzRGF0YSh0LGkpLG8uY29tbWl0Q2hhbmdlcygpfV9jbGVhclNlcmllc0RhdGEodCl7bGV0IHI9dGhpcy5fZ2V0Q2hhcnREYXRhTG9hZGVyKCk7ci5zZXRTZXJpZXNEYXRhKHQsW10pLHIuY29tbWl0Q2hhbmdlcygpfV91cGRhdGVSdW5Ub1ByQ3VydmVFbnRyeSgpe3ZhciB0PXRoaXMuX3J1blRvRGF0YU92ZXJUaW1lLHI9dGhpcy5ydW5Ub1N0ZXBDYXA7bGV0IG49e307emwuZm9yT3duKHQsKGksbyk9PnshaXx8IWkubGVuZ3RofHwobltvXT10aGlzLl9jb21wdXRlRW50cnlDbG9zZXN0T3JFcXVhbFRvU3RlcENhcChyW29dLGkpKX0pLHRoaXMuc2V0KCJfcHJldmlvdXNSdW5Ub1ByQ3VydmVFbnRyeSIsdGhpcy5fcnVuVG9QckN1cnZlRW50cnkpLHRoaXMuc2V0KCJfcnVuVG9QckN1cnZlRW50cnkiLG4pfV9ub3RpZnlEYXRhQ2hhbmdlKCl7dmFyIHQ9dGhpcy5fcnVuVG9EYXRhT3ZlclRpbWU7dGhpcy5vbkRhdGFDaGFuZ2UmJnRoaXMub25EYXRhQ2hhbmdlKHQpfV9jb21wdXRlRW50cnlDbG9zZXN0T3JFcXVhbFRvU3RlcENhcCh0LHIpe2xldCBuPU1hdGgubWluKHpsLnNvcnRlZEluZGV4KHIubWFwKGk9Pmkuc3RlcCksdCksci5sZW5ndGgtMSk7cmV0dXJuIHJbbl19Z2V0IF9ydW5zV2l0aFN0ZXBBdmFpbGFibGUoKXt2YXIgdD10aGlzLnJ1bnMscj10aGlzLl9ydW5Ub1ByQ3VydmVFbnRyeTtyZXR1cm4gemwuZmlsdGVyKHQsbj0+cltuXSkuc29ydCgpfWdldCBfc2V0T2ZSZWxldmFudFJ1bnMoKXt2YXIgdD10aGlzLl9ydW5zV2l0aFN0ZXBBdmFpbGFibGU7bGV0IHI9e307cmV0dXJuIHpsLmZvckVhY2godCxuPT57cltuXT0hMH0pLHJ9X2NvbXB1dGVDdXJyZW50U3RlcEZvclJ1bih0LHIpe2xldCBuPXRbcl07cmV0dXJuIG4/bi5zdGVwOm51bGx9X2NvbXB1dGVDdXJyZW50V2FsbFRpbWVGb3JSdW4odCxyKXtsZXQgbj10W3JdO3JldHVybiBuP25ldyBEYXRlKG4ud2FsbF90aW1lKjFlMykudG9TdHJpbmcoKTpudWxsfV90b2dnbGVFeHBhbmRlZCh0KXt0aGlzLnNldCgiX2V4cGFuZGVkIiwhdGhpcy5fZXhwYW5kZWQpLHRoaXMucmVkcmF3KCl9X3Jlc2V0RG9tYWluKCl7dGhpcy5fZ2V0Q2hhcnREYXRhTG9hZGVyKCkucmVzZXREb21haW4oKX1yZWRyYXcoKXt0aGlzLl9nZXRDaGFydERhdGFMb2FkZXIoKS5yZWRyYXcoKX19O25uLnRlbXBsYXRlPVFgCiAgICA8dGYtY2FyZC1oZWFkaW5nCiAgICAgIHRhZz0iW1t0YWddXSIKICAgICAgZGlzcGxheS1uYW1lPSJbW3RhZ01ldGFkYXRhLmRpc3BsYXlOYW1lXV0iCiAgICAgIGRlc2NyaXB0aW9uPSJbW3RhZ01ldGFkYXRhLmRlc2NyaXB0aW9uXV0iCiAgICA+PC90Zi1jYXJkLWhlYWRpbmc+CgogICAgPHRmLWxpbmUtY2hhcnQtZGF0YS1sb2FkZXIKICAgICAgeC1jb21wb25lbnRzLWNyZWF0aW9uLW1ldGhvZD0iW1tfeENvbXBvbmVudHNDcmVhdGlvbk1ldGhvZF1dIgogICAgICB5LXZhbHVlLWFjY2Vzc29yPSJbW195VmFsdWVBY2Nlc3Nvcl1dIgogICAgICB0b29sdGlwLWNvbHVtbnM9IltbX3Rvb2x0aXBDb2x1bW5zXV0iCiAgICAgIGNvbG9yLXNjYWxlPSJbW19jb2xvclNjYWxlRnVuY3Rpb25dXSIKICAgICAgZGVmYXVsdC14LXJhbmdlPSJbW19kZWZhdWx0WFJhbmdlXV0iCiAgICAgIGRlZmF1bHQteS1yYW5nZT0iW1tfZGVmYXVsdFlSYW5nZV1dIgogICAgICBzbW9vdGhpbmctZW5hYmxlZD0iW1tfc21vb3RoaW5nRW5hYmxlZF1dIgogICAgICByZXF1ZXN0LW1hbmFnZXI9IltbcmVxdWVzdE1hbmFnZXJdXSIKICAgICAgZGF0YS10by1sb2FkPSJbW3J1bnNdXSIKICAgICAgZGF0YS1zZXJpZXM9IltbcnVuc11dIgogICAgICBsb2FkLWtleT0iW1t0YWddXSIKICAgICAgcmVxdWVzdC1kYXRhPSJbW19yZXF1ZXN0RGF0YV1dIgogICAgICBsb2FkLWRhdGEtY2FsbGJhY2s9IltbX2NyZWF0ZVByb2Nlc3NEYXRhRnVuY3Rpb24oKV1dIgogICAgICBhY3RpdmU9IltbYWN0aXZlXV0iCiAgICA+PC90Zi1saW5lLWNoYXJ0LWRhdGEtbG9hZGVyPgoKICAgIDxkaXYgaWQ9ImJ1dHRvbnMtcm93Ij4KICAgICAgPHBhcGVyLWljb24tYnV0dG9uCiAgICAgICAgc2VsZWN0ZWQkPSJbW19leHBhbmRlZF1dIgogICAgICAgIGljb249ImZ1bGxzY3JlZW4iCiAgICAgICAgb24tdGFwPSJfdG9nZ2xlRXhwYW5kZWQiCiAgICAgID48L3BhcGVyLWljb24tYnV0dG9uPgogICAgICA8cGFwZXItaWNvbi1idXR0b24KICAgICAgICBpY29uPSJzZXR0aW5ncy1vdmVyc2NhbiIKICAgICAgICBvbi10YXA9Il9yZXNldERvbWFpbiIKICAgICAgICB0aXRsZT0iUmVzZXQgYXhlcyB0byBbMCwgMV0uIgogICAgICA+PC9wYXBlci1pY29uLWJ1dHRvbj4KICAgIDwvZGl2PgoKICAgIDxkaXYgaWQ9InN0ZXAtbGVnZW5kIj4KICAgICAgPHRlbXBsYXRlIGlzPSJkb20tcmVwZWF0IiBpdGVtcz0iW1tfcnVuc1dpdGhTdGVwQXZhaWxhYmxlXV0iIGFzPSJydW4iPgogICAgICAgIDxkaXYgY2xhc3M9ImxlZ2VuZC1yb3ciPgogICAgICAgICAgPGRpdgogICAgICAgICAgICBjbGFzcz0iY29sb3ItYm94IgogICAgICAgICAgICBzdHlsZT0iYmFja2dyb3VuZDogW1tfY29tcHV0ZVJ1bkNvbG9yKHJ1bildXTsiCiAgICAgICAgICA+PC9kaXY+CiAgICAgICAgICBbW3J1bl1dIGlzIGF0CiAgICAgICAgICA8c3BhbiBjbGFzcz0ic3RlcC1sYWJlbC10ZXh0Ij4KICAgICAgICAgICAgc3RlcCBbW19jb21wdXRlQ3VycmVudFN0ZXBGb3JSdW4oX3J1blRvUHJDdXJ2ZUVudHJ5LCBydW4pXV0gPC9zcGFuCiAgICAgICAgICA+PGJyIC8+CiAgICAgICAgICA8c3BhbiBjbGFzcz0id2FsbC10aW1lLWxhYmVsLXRleHQiPgogICAgICAgICAgICAoW1tfY29tcHV0ZUN1cnJlbnRXYWxsVGltZUZvclJ1bihfcnVuVG9QckN1cnZlRW50cnksIHJ1bildXSkKICAgICAgICAgIDwvc3Bhbj4KICAgICAgICA8L2Rpdj4KICAgICAgPC90ZW1wbGF0ZT4KICAgIDwvZGl2PgoKICAgIDxzdHlsZT4KICAgICAgOmhvc3QgewogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgICAgICB3aWR0aDogNTAwcHg7CiAgICAgICAgbWFyZ2luLXJpZ2h0OiAxMHB4OwogICAgICAgIG1hcmdpbi1ib3R0b206IDI1cHg7CiAgICAgIH0KICAgICAgOmhvc3QoW19leHBhbmRlZF0pIHsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgfQogICAgICB0Zi1saW5lLWNoYXJ0LWRhdGEtbG9hZGVyIHsKICAgICAgICBoZWlnaHQ6IDMwMHB4OwogICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgICAgfQogICAgICA6aG9zdChbX2V4cGFuZGVkXSkgdGYtbGluZS1jaGFydC1kYXRhLWxvYWRlciB7CiAgICAgICAgaGVpZ2h0OiA2MDBweDsKICAgICAgfQogICAgICAjYnV0dG9ucy1yb3cgewogICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgZmxleC1kaXJlY3Rpb246IHJvdzsKICAgICAgfQogICAgICAjYnV0dG9ucy1yb3cgcGFwZXItaWNvbi1idXR0b24gewogICAgICAgIGNvbG9yOiAjMjE5NmYzOwogICAgICAgIGJvcmRlci1yYWRpdXM6IDEwMCU7CiAgICAgICAgd2lkdGg6IDMycHg7CiAgICAgICAgaGVpZ2h0OiAzMnB4OwogICAgICAgIHBhZGRpbmc6IDRweDsKICAgICAgfQogICAgICAjYnV0dG9ucy1yb3cgcGFwZXItaWNvbi1idXR0b25bc2VsZWN0ZWRdIHsKICAgICAgICBiYWNrZ3JvdW5kOiB2YXIoLS10Yi11aS1saWdodC1hY2NlbnQpOwogICAgICB9CiAgICAgICNzdGVwLWxlZ2VuZCB7CiAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsKICAgICAgICBmb250LXNpemU6IDAuOGVtOwogICAgICAgIG1heC1oZWlnaHQ6IDIwMHB4OwogICAgICAgIG92ZXJmbG93LXk6IGF1dG87CiAgICAgICAgcGFkZGluZzogMCAwIDAgMTBweDsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgfQogICAgICAubGVnZW5kLXJvdyB7CiAgICAgICAgbWFyZ2luOiA1cHggMCA1cHggMDsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgfQogICAgICAuY29sb3ItYm94IHsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgICAgYm9yZGVyLXJhZGl1czogMXB4OwogICAgICAgIHdpZHRoOiAxMHB4OwogICAgICAgIGhlaWdodDogMTBweDsKICAgICAgfQogICAgICAuc3RlcC1sYWJlbC10ZXh0IHsKICAgICAgICBmb250LXdlaWdodDogYm9sZDsKICAgICAgfQogICAgICAud2FsbC10aW1lLWxhYmVsLXRleHQgewogICAgICAgIGNvbG9yOiAjODg4OwogICAgICAgIGZvbnQtc2l6ZTogMC44ZW07CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sbm4ucHJvdG90eXBlLCJydW5zIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLG5uLnByb3RvdHlwZSwidGFnIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLG5uLnByb3RvdHlwZSwidGFnTWV0YWRhdGEiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sbm4ucHJvdG90eXBlLCJydW5Ub1N0ZXBDYXAiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsQWUpXSxubi5wcm90b3R5cGUsInJlcXVlc3RNYW5hZ2VyIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sbm4ucHJvdG90eXBlLCJhY3RpdmUiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLHJlZmxlY3RUb0F0dHJpYnV0ZTohMH0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sbm4ucHJvdG90eXBlLCJfZXhwYW5kZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sbm4ucHJvdG90eXBlLCJfcnVuVG9QckN1cnZlRW50cnkiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sbm4ucHJvdG90eXBlLCJfcHJldmlvdXNSdW5Ub1ByQ3VydmVFbnRyeSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxubi5wcm90b3R5cGUsIl9ydW5Ub0RhdGFPdmVyVGltZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbildLG5uLnByb3RvdHlwZSwib25EYXRhQ2hhbmdlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLG5uLnByb3RvdHlwZSwiX2NvbG9yU2NhbGVGdW5jdGlvbiIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixhbildLG5uLnByb3RvdHlwZSwiX2NhbmNlbGxlciIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLG5uLnByb3RvdHlwZSwiX2F0dGFjaGVkIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLG5uLnByb3RvdHlwZSwiX3hDb21wb25lbnRzQ3JlYXRpb25NZXRob2QiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sbm4ucHJvdG90eXBlLCJfeVZhbHVlQWNjZXNzb3IiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLG5uLnByb3RvdHlwZSwiX3Rvb2x0aXBDb2x1bW5zIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxubi5wcm90b3R5cGUsIl9zZXJpZXNEYXRhRmllbGRzIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxubi5wcm90b3R5cGUsIl9kZWZhdWx0WFJhbmdlIix2b2lkIDApO0UoW0Eoe3R5cGU6QXJyYXl9KSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpXSxubi5wcm90b3R5cGUsIl9kZWZhdWx0WVJhbmdlIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKV0sbm4ucHJvdG90eXBlLCJfcmVxdWVzdERhdGEiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxubi5wcm90b3R5cGUsIl9zbW9vdGhpbmdFbmFibGVkIix2b2lkIDApO0UoW0J0KCJydW5zIiwidGFnIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxubi5wcm90b3R5cGUsInJlbG9hZCIsbnVsbCk7RShbQnQoIl9ydW5Ub1ByQ3VydmVFbnRyeSIsIl9wcmV2aW91c1J1blRvUHJDdXJ2ZUVudHJ5IiwiX3NldE9mUmVsZXZhbnRSdW5zIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxubi5wcm90b3R5cGUsIl9zZXRDaGFydERhdGEiLG51bGwpO0UoW0J0KCJfcnVuVG9EYXRhT3ZlclRpbWUiLCJydW5Ub1N0ZXBDYXAiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLG5uLnByb3RvdHlwZSwiX3VwZGF0ZVJ1blRvUHJDdXJ2ZUVudHJ5IixudWxsKTtFKFtCdCgiX3J1blRvRGF0YU92ZXJUaW1lIiksdygiZGVzaWduOnR5cGUiLEZ1bmN0aW9uKSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pLHcoImRlc2lnbjpyZXR1cm50eXBlIix2b2lkIDApXSxubi5wcm90b3R5cGUsIl9ub3RpZnlEYXRhQ2hhbmdlIixudWxsKTtFKFtSdCgicnVucyIsIl9ydW5Ub1ByQ3VydmVFbnRyeSIpLHcoImRlc2lnbjp0eXBlIixBcnJheSksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sbm4ucHJvdG90eXBlLCJfcnVuc1dpdGhTdGVwQXZhaWxhYmxlIixudWxsKTtFKFtSdCgiX3J1bnNXaXRoU3RlcEF2YWlsYWJsZSIpLHcoImRlc2lnbjp0eXBlIixPYmplY3QpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLG5uLnByb3RvdHlwZSwiX3NldE9mUmVsZXZhbnRSdW5zIixudWxsKTtubj1FKFt5dCgidGYtcHItY3VydmUtY2FyZCIpXSxubik7dmFyIFNNPUVlKE9lKCksMSk7dmFyIGVwPWNsYXNzIGV4dGVuZHMgbXR7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX3J1blRvU3RlcEluZGV4PXt9fV9jb21wdXRlQ29sb3JGb3JSdW4odCl7cmV0dXJuIGZuKHQpfV9jb21wdXRlVGltZVRleHRGb3JSdW4odCxyLG4saSl7bGV0IG89cltuXTtpZighU00uaXNOdW1iZXIobykpcmV0dXJuIiI7bGV0IGE9dFtuXTtpZighYSlyZXR1cm4iIjtsZXQgcz1hW29dW2ldO2lmKGk9PT0ic3RlcCIpcmV0dXJuYHN0ZXAgJHtzfWA7aWYoaT09PSJyZWxhdGl2ZSIpcmV0dXJuIHM8MT9gJHsocyoxZTMpLnRvRml4ZWQoMil9IG1zYDpgJHtzLnRvRml4ZWQoMil9IHNgO2lmKGk9PT0id2FsbF90aW1lIilyZXR1cm4gbmV3IERhdGUocyoxZTMpLnRvU3RyaW5nKCk7dGhyb3cgbmV3IEVycm9yKGBUaGUgZGlzcGxheSB0eXBlIG9mICR7aX0gaXMgbm90IHJlY29nbml6ZWQuYCl9X3NsaWRlclZhbHVlQ2hhbmdlZCh0KXtsZXQgcj10LnRhcmdldC5kYXRhc2V0LnJ1bixuPXQudGFyZ2V0LmltbWVkaWF0ZVZhbHVlLGk9T2JqZWN0LmFzc2lnbih7fSx0aGlzLl9ydW5Ub1N0ZXBJbmRleCk7aXNOYU4obik/ZGVsZXRlIGlbcl06aVtyXT10LnRhcmdldC5pbW1lZGlhdGVWYWx1ZSx0aGlzLl9ydW5Ub1N0ZXBJbmRleD1pfV9jb21wdXRlTWF4U3RlcEluZGV4Rm9yUnVuKHQscil7bGV0IG49dFtyXTtyZXR1cm4gbiYmbi5sZW5ndGg/bi5sZW5ndGgtMTowfV91cGRhdGVTdGVwc0Zvck5ld1J1bnMoKXt2YXIgdD10aGlzLnJ1blRvQXZhaWxhYmxlVGltZUVudHJpZXM7bGV0IHI9T2JqZWN0LmFzc2lnbih7fSx0aGlzLl9ydW5Ub1N0ZXBJbmRleCk7U00uZm9yT3duKHQsKG4saSk9PntTTS5pc051bWJlcihyW2ldKXx8KHJbaV09bi5sZW5ndGgtMSl9KSx0aGlzLl9ydW5Ub1N0ZXBJbmRleD1yfV9nZXRTdGVwKHQscil7cmV0dXJuIHRoaXMuX3J1blRvU3RlcEluZGV4P3RoaXMuX3J1blRvU3RlcEluZGV4W3JdOjB9X2NvbXB1dGVSdW5Ub1N0ZXAodCxyKXtsZXQgbj17fTtyZXR1cm4gU00uZm9yT3duKHIsKGksbyk9PntsZXQgYT10W29dOyFhfHwobltvXT1hW2ldLnN0ZXApfSksbn1nZXQgX3J1bnNXaXRoU2xpZGVycygpe3ZhciB0PXRoaXMucnVucyxyPXRoaXMucnVuVG9BdmFpbGFibGVUaW1lRW50cmllcztyZXR1cm4gdC5maWx0ZXIobj0+cltuXSl9fTtlcC50ZW1wbGF0ZT1RYAogICAgPHRlbXBsYXRlIGlzPSJkb20tcmVwZWF0IiBpdGVtcz0iW1tfcnVuc1dpdGhTbGlkZXJzXV0iIGFzPSJydW4iPgogICAgICA8ZGl2IGNsYXNzPSJydW4td2lkZ2V0Ij4KICAgICAgICA8ZGl2IGNsYXNzPSJydW4tZGlzcGxheS1jb250YWluZXIiPgogICAgICAgICAgPGRpdgogICAgICAgICAgICBjbGFzcz0icnVuLWNvbG9yLWJveCIKICAgICAgICAgICAgc3R5bGU9ImJhY2tncm91bmQ6W1tfY29tcHV0ZUNvbG9yRm9yUnVuKHJ1bildXTsiCiAgICAgICAgICA+PC9kaXY+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJydW4tdGV4dCI+W1tydW5dXTwvZGl2PgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9InN0ZXAtZGlzcGxheS1jb250YWluZXIiPgogICAgICAgICAgW1tfY29tcHV0ZVRpbWVUZXh0Rm9yUnVuKHJ1blRvQXZhaWxhYmxlVGltZUVudHJpZXMsIF9ydW5Ub1N0ZXBJbmRleCwKICAgICAgICAgIHJ1biwgdGltZURpc3BsYXlUeXBlKV1dCiAgICAgICAgPC9kaXY+CiAgICAgICAgPHBhcGVyLXNsaWRlcgogICAgICAgICAgZGF0YS1ydW4kPSJbW3J1bl1dIgogICAgICAgICAgc3RlcD0iMSIKICAgICAgICAgIHR5cGU9Im51bWJlciIKICAgICAgICAgIG1pbj0iMCIKICAgICAgICAgIG1heD0iW1tfY29tcHV0ZU1heFN0ZXBJbmRleEZvclJ1bihydW5Ub0F2YWlsYWJsZVRpbWVFbnRyaWVzLCBydW4pXV0iCiAgICAgICAgICB2YWx1ZT0iW1tfZ2V0U3RlcChfcnVuVG9TdGVwSW5kZXgsIHJ1bildXSIKICAgICAgICAgIG9uLWltbWVkaWF0ZS12YWx1ZS1jaGFuZ2VkPSJfc2xpZGVyVmFsdWVDaGFuZ2VkIgogICAgICAgID48L3BhcGVyLXNsaWRlcj4KICAgICAgPC9kaXY+CiAgICA8L3RlbXBsYXRlPgogICAgPHN0eWxlPgogICAgICAucnVuLXdpZGdldCB7CiAgICAgICAgbWFyZ2luOiAxMHB4IDAgMCAwOwogICAgICB9CiAgICAgIHBhcGVyLXNsaWRlciB7CiAgICAgICAgbWFyZ2luOiAtOHB4IDAgMCAtMTVweDsKICAgICAgICB3aWR0aDogMTAwJTsKICAgICAgfQogICAgICAuc3RlcC1kaXNwbGF5LWNvbnRhaW5lciB7CiAgICAgICAgZm9udC1zaXplOiAwLjllbTsKICAgICAgICBtYXJnaW46IDAgMTVweCAwIDA7CiAgICAgIH0KICAgICAgLnJ1bi10ZXh0IHsKICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICAgIH0KICAgICAgLnJ1bi1jb2xvci1ib3ggewogICAgICAgIHdpZHRoOiAxMnB4OwogICAgICAgIGhlaWdodDogMTJweDsKICAgICAgICBib3JkZXItcmFkaXVzOiAzcHg7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLGVwLnByb3RvdHlwZSwicnVucyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxlcC5wcm90b3R5cGUsInJ1blRvQXZhaWxhYmxlVGltZUVudHJpZXMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3Qsbm90aWZ5OiEwLGNvbXB1dGVkOiJfY29tcHV0ZVJ1blRvU3RlcChydW5Ub0F2YWlsYWJsZVRpbWVFbnRyaWVzLCBfcnVuVG9TdGVwSW5kZXgpIn0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxlcC5wcm90b3R5cGUsInJ1blRvU3RlcCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxlcC5wcm90b3R5cGUsInRpbWVEaXNwbGF5VHlwZSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxlcC5wcm90b3R5cGUsIl9ydW5Ub1N0ZXBJbmRleCIsdm9pZCAwKTtFKFtCdCgicnVuVG9BdmFpbGFibGVUaW1lRW50cmllcyIpLHcoImRlc2lnbjp0eXBlIixGdW5jdGlvbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKSx3KCJkZXNpZ246cmV0dXJudHlwZSIsdm9pZCAwKV0sZXAucHJvdG90eXBlLCJfdXBkYXRlU3RlcHNGb3JOZXdSdW5zIixudWxsKTtFKFtSdCgicnVucyIsInJ1blRvQXZhaWxhYmxlVGltZUVudHJpZXMiKSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLGVwLnByb3RvdHlwZSwiX3J1bnNXaXRoU2xpZGVycyIsbnVsbCk7ZXA9RShbeXQoInRmLXByLWN1cnZlLXN0ZXBzLXNlbGVjdG9yIildLGVwKTt2YXIga289Y2xhc3MgZXh0ZW5kcyBHdChtdCl7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMucmVsb2FkT25SZWFkeT0hMCx0aGlzLl90aW1lRGlzcGxheVR5cGU9InN0ZXAiLHRoaXMuX3NlbGVjdGVkUnVucz1bXSx0aGlzLl9ydW5Ub1RhZ0luZm89e30sdGhpcy5fdGFnVG9SdW5Ub0RhdGE9e30sdGhpcy5fZ2V0Q2F0ZWdvcnlJdGVtS2V5PXQ9PnQudGFnLHRoaXMuX3JlcXVlc3RNYW5hZ2VyPW5ldyBBZSx0aGlzLl9zdGVwPTB9cmVhZHkoKXtzdXBlci5yZWFkeSgpLHRoaXMucmVsb2FkT25SZWFkeSYmdGhpcy5yZWxvYWQoKX1yZWxvYWQoKXtQcm9taXNlLmFsbChbdGhpcy5fZmV0Y2hUYWdzKCldKS50aGVuKCgpPT57dGhpcy5fcmVsb2FkQ2FyZHMoKX0pfV9zaG91bGRPcGVuKHQpe3JldHVybiB0PD0yfV9mZXRjaFRhZ3MoKXtsZXQgdD12ZSgpLnBsdWdpblJvdXRlKCJwcl9jdXJ2ZXMiLCIvdGFncyIpO3JldHVybiB0aGlzLl9yZXF1ZXN0TWFuYWdlci5yZXF1ZXN0KHQpLnRoZW4ocj0+e2lmKGxtLmlzRXF1YWwocix0aGlzLl9ydW5Ub1RhZ0luZm8pKXJldHVybjtsZXQgbj1sbS5tYXBWYWx1ZXMocixvPT5sbS5rZXlzKG8pKSxpPSRpKG4pO3RoaXMuc2V0KCJfZGF0YU5vdEZvdW5kIixpLmxlbmd0aD09PTApLHRoaXMuc2V0KCJfcnVuVG9UYWdJbmZvIixyKSx0aGlzLmFzeW5jKCgpPT57dGhpcy5zZXQoIl9jYXRlZ29yaWVzRG9tUmVhZHkiLCEwKX0pfSl9X3JlbG9hZENhcmRzKCl7dmFyIHQ7bG0uZm9yRWFjaCgodD10aGlzLnJvb3QpPT1udWxsP3ZvaWQgMDp0LnF1ZXJ5U2VsZWN0b3JBbGwoInRmLXByLWN1cnZlLWNhcmQiKSxyPT57ci5yZWxvYWQoKX0pfWdldCBfY2F0ZWdvcmllcygpe3ZhciB0PXRoaXMuX3J1blRvVGFnSW5mbyxyPXRoaXMuX3NlbGVjdGVkUnVucyxuPXRoaXMuX3RhZ0ZpbHRlcjtsZXQgaT1sbS5tYXBWYWx1ZXModCxvPT5PYmplY3Qua2V5cyhvKSk7cmV0dXJuIHVFKGkscixuKX1nZXQgX3JlbGV2YW50U2VsZWN0ZWRSdW5zKCl7dmFyIHQ9dGhpcy5fc2VsZWN0ZWRSdW5zLHI9dGhpcy5fcnVuVG9UYWdJbmZvO3JldHVybiB0LmZpbHRlcihuPT5yW25dKX1fdGFnTWV0YWRhdGEodCxyLG4pe2xldCBpPXt9O3IuZm9yRWFjaChhPT57aVthXT10W2FdW25dfSk7bGV0IG89bi5yZXBsYWNlKC9cL3ByX2N1cnZlcyQvLCIiKTtyZXR1cm4gaVIoaSxvKX1fY3JlYXRlRGF0YUNoYW5nZUNhbGxiYWNrKHQpe3JldHVybiByPT57dGhpcy5zZXQoIl90YWdUb1J1blRvRGF0YSIsTXgoS2woe30sdGhpcy5fdGFnVG9SdW5Ub0RhdGEpLHtbdF06cn0pKX19Z2V0IF9ydW5Ub0F2YWlsYWJsZVRpbWVFbnRyaWVzKCl7dmFyIHQ9dGhpcy5fdGFnVG9SdW5Ub0RhdGE7bGV0IHI9e307Zm9yKGxldFtpLG9db2YgT2JqZWN0LmVudHJpZXModCkpZm9yKGxldFthXW9mIE9iamVjdC5lbnRyaWVzKG8pKShyW2FdPT1udWxsfHxpPHJbYV0pJiYoclthXT1pKTtsZXQgbj17fTtmb3IobGV0W2ksb11vZiBPYmplY3QuZW50cmllcyhyKSl7bGV0IGE9dFtvXVtpXTtuW2ldPWEubWFwKHM9Pih7c3RlcDpzLnN0ZXAsd2FsbF90aW1lOnMud2FsbF90aW1lLHJlbGF0aXZlOnMud2FsbF90aW1lLWFbMF0ud2FsbF90aW1lfSkpfXJldHVybiBufX07a28udGVtcGxhdGU9UWAKICAgIDx0Zi1kYXNoYm9hcmQtbGF5b3V0PgogICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyIiBzbG90PSJzaWRlYmFyIj4KICAgICAgICA8ZGl2IGNsYXNzPSJzZXR0aW5ncyI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyLXNlY3Rpb24iPgogICAgICAgICAgICA8dGYtb3B0aW9uLXNlbGVjdG9yCiAgICAgICAgICAgICAgaWQ9InRpbWUtdHlwZS1zZWxlY3RvciIKICAgICAgICAgICAgICBuYW1lPSJUaW1lIERpc3BsYXkgVHlwZSIKICAgICAgICAgICAgICBzZWxlY3RlZC1pZD0ie3tfdGltZURpc3BsYXlUeXBlfX0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uIGlkPSJzdGVwIj5zdGVwPC9wYXBlci1idXR0b24+CiAgICAgICAgICAgICAgPCEtLQogICAgICAgICAgICAtLT4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uIGlkPSJyZWxhdGl2ZSI+cmVsYXRpdmU8L3BhcGVyLWJ1dHRvbj4KICAgICAgICAgICAgICA8IS0tCiAgICAgICAgICAgIC0tPgogICAgICAgICAgICAgIDxwYXBlci1idXR0b24gaWQ9IndhbGxfdGltZSI+d2FsbDwvcGFwZXItYnV0dG9uPgogICAgICAgICAgICA8L3RmLW9wdGlvbi1zZWxlY3Rvcj4KICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19ydW5Ub0F2YWlsYWJsZVRpbWVFbnRyaWVzXV0iPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyLXNlY3Rpb24iIGlkPSJzdGVwcy1zZWxlY3Rvci1jb250YWluZXIiPgogICAgICAgICAgICAgIDx0Zi1wci1jdXJ2ZS1zdGVwcy1zZWxlY3RvcgogICAgICAgICAgICAgICAgcnVucz0iW1tfcmVsZXZhbnRTZWxlY3RlZFJ1bnNdXSIKICAgICAgICAgICAgICAgIHJ1bi10by1zdGVwPSJ7e19ydW5Ub1N0ZXB9fSIKICAgICAgICAgICAgICAgIHJ1bi10by1hdmFpbGFibGUtdGltZS1lbnRyaWVzPSJbW19ydW5Ub0F2YWlsYWJsZVRpbWVFbnRyaWVzXV0iCiAgICAgICAgICAgICAgICB0aW1lLWRpc3BsYXktdHlwZT0iW1tfdGltZURpc3BsYXlUeXBlXV0iCiAgICAgICAgICAgICAgPgogICAgICAgICAgICAgIDwvdGYtcHItY3VydmUtc3RlcHMtc2VsZWN0b3I+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyLXNlY3Rpb24gcnVucy1zZWxlY3RvciI+CiAgICAgICAgICA8dGYtcnVucy1zZWxlY3RvciBzZWxlY3RlZC1ydW5zPSJ7e19zZWxlY3RlZFJ1bnN9fSI+CiAgICAgICAgICA8L3RmLXJ1bnMtc2VsZWN0b3I+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJjZW50ZXIiIHNsb3Q9ImNlbnRlciI+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19kYXRhTm90Rm91bmRdXSI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJuby1kYXRhLXdhcm5pbmciPgogICAgICAgICAgICA8aDM+Tm8gcHJlY2lzaW9u4oCTcmVjYWxsIGN1cnZlIGRhdGEgd2FzIGZvdW5kLjwvaDM+CiAgICAgICAgICAgIDxwPlByb2JhYmxlIGNhdXNlczo8L3A+CiAgICAgICAgICAgIDx1bD4KICAgICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgICBZb3UgaGF2ZW7igJl0IHdyaXR0ZW4gYW55IHByZWNpc2lvbuKAk3JlY2FsbCBkYXRhIHRvIHlvdXIgZXZlbnQKICAgICAgICAgICAgICAgIGZpbGVzLgogICAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICAgICAgPGxpPlRlbnNvckJvYXJkIGNhbuKAmXQgZmluZCB5b3VyIGV2ZW50IGZpbGVzLjwvbGk+CiAgICAgICAgICAgIDwvdWw+CiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgIElmIHlvdeKAmXJlIG5ldyB0byB1c2luZyBUZW5zb3JCb2FyZCwgYW5kIHdhbnQgdG8gZmluZCBvdXQgaG93IHRvCiAgICAgICAgICAgICAgYWRkIGRhdGEgYW5kIHNldCB1cCB5b3VyIGV2ZW50IGZpbGVzLCBjaGVjayBvdXQgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCIKICAgICAgICAgICAgICAgID5SRUFETUU8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIHBlcmhhcHMgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiCiAgICAgICAgICAgICAgICA+VGVuc29yQm9hcmQgdHV0b3JpYWw8L2EKICAgICAgICAgICAgICA+LgogICAgICAgICAgICA8L3A+CgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICBJZiB5b3UgdGhpbmsgVGVuc29yQm9hcmQgaXMgY29uZmlndXJlZCBwcm9wZXJseSwgcGxlYXNlIHNlZQogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQjbXktdGVuc29yYm9hcmQtaXNudC1zaG93aW5nLWFueS1kYXRhLXdoYXRzLXdyb25nIgogICAgICAgICAgICAgICAgPnRoZSBzZWN0aW9uIG9mIHRoZSBSRUFETUUgZGV2b3RlZCB0byBtaXNzaW5nIGRhdGEgcHJvYmxlbXM8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIGNvbnNpZGVyIGZpbGluZyBhbiBpc3N1ZSBvbiBHaXRIdWIuCiAgICAgICAgICAgIDwvcD4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbWyFfZGF0YU5vdEZvdW5kXV0iPgogICAgICAgICAgPHRmLXRhZy1maWx0ZXJlciB0YWctZmlsdGVyPSJ7e190YWdGaWx0ZXJ9fSI+PC90Zi10YWctZmlsdGVyZXI+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19jYXRlZ29yaWVzXV0iIGFzPSJjYXRlZ29yeSI+CiAgICAgICAgICAgIDx0Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldwogICAgICAgICAgICAgIGNhdGVnb3J5PSJbW2NhdGVnb3J5XV0iCiAgICAgICAgICAgICAgaW5pdGlhbC1vcGVuZWQ9IltbX3Nob3VsZE9wZW4oaW5kZXgpXV0iCiAgICAgICAgICAgICAgZ2V0LWNhdGVnb3J5LWl0ZW0ta2V5PSJbW19nZXRDYXRlZ29yeUl0ZW1LZXldXSIKICAgICAgICAgICAgPgogICAgICAgICAgICAgIDx0ZW1wbGF0ZT4KICAgICAgICAgICAgICAgIDx0Zi1wci1jdXJ2ZS1jYXJkCiAgICAgICAgICAgICAgICAgIGFjdGl2ZT0iW1thY3RpdmVdXSIKICAgICAgICAgICAgICAgICAgcnVucz0iW1tpdGVtLnJ1bnNdXSIKICAgICAgICAgICAgICAgICAgdGFnPSJbW2l0ZW0udGFnXV0iCiAgICAgICAgICAgICAgICAgIHRhZy1tZXRhZGF0YT0iW1tfdGFnTWV0YWRhdGEoX3J1blRvVGFnSW5mbywgaXRlbS5ydW5zLCBpdGVtLnRhZyldXSIKICAgICAgICAgICAgICAgICAgcmVxdWVzdC1tYW5hZ2VyPSJbW19yZXF1ZXN0TWFuYWdlcl1dIgogICAgICAgICAgICAgICAgICBydW4tdG8tc3RlcC1jYXA9IltbX3J1blRvU3RlcF1dIgogICAgICAgICAgICAgICAgICBvbi1kYXRhLWNoYW5nZT0iW1tfY3JlYXRlRGF0YUNoYW5nZUNhbGxiYWNrKGl0ZW0udGFnKV1dIgogICAgICAgICAgICAgICAgPjwvdGYtcHItY3VydmUtY2FyZD4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8L3RmLWNhdGVnb3J5LXBhZ2luYXRlZC12aWV3PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICA8L2Rpdj4KICAgIDwvdGYtZGFzaGJvYXJkLWxheW91dD4KCiAgICA8c3R5bGUgaW5jbHVkZT0iZGFzaGJvYXJkLXN0eWxlIj48L3N0eWxlPgogICAgPHN0eWxlPgogICAgICAubm8tZGF0YS13YXJuaW5nIHsKICAgICAgICBtYXgtd2lkdGg6IDU0MHB4OwogICAgICAgIG1hcmdpbjogODBweCBhdXRvIDAgYXV0bzsKICAgICAgfQoKICAgICAgLyoqIERvIG5vdCBsZXQgdGhlIHN0ZXBzIHNlbGVjdG9yIG9jY2x1ZGUgdGhlIHJ1biBzZWxlY3Rvci4gKi8KICAgICAgI3N0ZXBzLXNlbGVjdG9yLWNvbnRhaW5lciB7CiAgICAgICAgbWF4LWhlaWdodDogNjAlOwogICAgICAgIG92ZXJmbG93LXk6IGF1dG87CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgYDtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGtvLnByb3RvdHlwZSwicmVsb2FkT25SZWFkeSIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxrby5wcm90b3R5cGUsIl90aW1lRGlzcGxheVR5cGUiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLGtvLnByb3RvdHlwZSwiX3NlbGVjdGVkUnVucyIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxrby5wcm90b3R5cGUsIl9ydW5Ub1RhZ0luZm8iLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sa28ucHJvdG90eXBlLCJfdGFnVG9SdW5Ub0RhdGEiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3Qsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLGtvLnByb3RvdHlwZSwiX3J1blRvU3RlcCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLGtvLnByb3RvdHlwZSwiX2RhdGFOb3RGb3VuZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSxrby5wcm90b3R5cGUsIl90YWdGaWx0ZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxrby5wcm90b3R5cGUsIl9jYXRlZ29yaWVzRG9tUmVhZHkiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sa28ucHJvdG90eXBlLCJfZ2V0Q2F0ZWdvcnlJdGVtS2V5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEFlKV0sa28ucHJvdG90eXBlLCJfcmVxdWVzdE1hbmFnZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXIsbm90aWZ5OiEwfSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLGtvLnByb3RvdHlwZSwiX3N0ZXAiLHZvaWQgMCk7RShbUnQoIl9ydW5Ub1RhZ0luZm8iLCJfc2VsZWN0ZWRSdW5zIiwiX3RhZ0ZpbHRlciIsIl9jYXRlZ29yaWVzRG9tUmVhZHkiKSx3KCJkZXNpZ246dHlwZSIsQXJyYXkpLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSldLGtvLnByb3RvdHlwZSwiX2NhdGVnb3JpZXMiLG51bGwpO0UoW1J0KCJfc2VsZWN0ZWRSdW5zIiwiX3J1blRvVGFnSW5mbyIpLHcoImRlc2lnbjp0eXBlIixBcnJheSksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sa28ucHJvdG90eXBlLCJfcmVsZXZhbnRTZWxlY3RlZFJ1bnMiLG51bGwpO0UoW1J0KCJfdGFnVG9SdW5Ub0RhdGEiKSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxrby5wcm90b3R5cGUsIl9ydW5Ub0F2YWlsYWJsZVRpbWVFbnRyaWVzIixudWxsKTtrbz1FKFt5dCgidGYtcHItY3VydmUtZGFzaGJvYXJkIildLGtvKTt2YXIgRjY9RWUoT2UoKSwxKTt2YXIgdW89Y2xhc3MgZXh0ZW5kcyBHdChuYil7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMucmVsb2FkT25SZWFkeT0hMCx0aGlzLl9zaG93RG93bmxvYWRMaW5rcz12cCgiX3Nob3dEb3dubG9hZExpbmtzIix7ZGVmYXVsdFZhbHVlOiExLHVzZUxvY2FsU3RvcmFnZTohMH0pLmNhbGwodGhpcyksdGhpcy5fc21vb3RoaW5nV2VpZ2h0PWdFKCJfc21vb3RoaW5nV2VpZ2h0Iix7ZGVmYXVsdFZhbHVlOi42fSkuY2FsbCh0aGlzKSx0aGlzLl9pZ25vcmVZT3V0bGllcnM9dnAoIl9pZ25vcmVZT3V0bGllcnMiLHtkZWZhdWx0VmFsdWU6ITAsdXNlTG9jYWxTdG9yYWdlOiEwfSkuY2FsbCh0aGlzKSx0aGlzLl94VHlwZT1FZC5TVEVQLHRoaXMuX3NlbGVjdGVkUnVucz1bXSx0aGlzLl90YWdGaWx0ZXI9IiIsdGhpcy5fY2F0ZWdvcmllcz1bXSx0aGlzLl9nZXRDYXRlZ29yeUl0ZW1LZXk9dD0+dC50YWcsdGhpcy5fcmVxdWVzdE1hbmFnZXI9bmV3IEFlKDUwKSx0aGlzLl9zaG93RG93bmxvYWRMaW5rc09ic2VydmVyPXhwKCJfc2hvd0Rvd25sb2FkTGlua3MiLHtkZWZhdWx0VmFsdWU6ITEsdXNlTG9jYWxTdG9yYWdlOiEwfSksdGhpcy5fc21vb3RoaW5nV2VpZ2h0T2JzZXJ2ZXI9X0UoIl9zbW9vdGhpbmdXZWlnaHQiLHtkZWZhdWx0VmFsdWU6LjZ9KSx0aGlzLl9pZ25vcmVZT3V0bGllcnNPYnNlcnZlcj14cCgiX2lnbm9yZVlPdXRsaWVycyIse2RlZmF1bHRWYWx1ZTohMCx1c2VMb2NhbFN0b3JhZ2U6ITB9KX1nZXQgX3Ntb290aGluZ0VuYWJsZWQoKXt2YXIgdD10aGlzLl9zbW9vdGhpbmdXZWlnaHQ7cmV0dXJuIHQ+MH1fZ2V0Q2F0ZWdvcnlLZXkodCl7cmV0dXJuIHQubWV0YWRhdGEudHlwZT09TmEuU0VBUkNIX1JFU1VMVFM/IiI6dC5uYW1lfV9zaG91bGRPcGVuKHQpe3JldHVybiB0PD0yfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLnJlbG9hZE9uUmVhZHkmJnRoaXMucmVsb2FkKCl9cmVsb2FkKCl7dGhpcy5fZmV0Y2hUYWdzKCkudGhlbigoKT0+e3RoaXMuX3JlbG9hZENoYXJ0cygpfSl9X2ZldGNoVGFncygpe2xldCB0PXZlKCkucGx1Z2luUm91dGUoInNjYWxhcnMiLCIvdGFncyIpO3JldHVybiB0aGlzLl9yZXF1ZXN0TWFuYWdlci5yZXF1ZXN0KHQpLnRoZW4ocj0+e2lmKEY2LmlzRXF1YWwocix0aGlzLl9ydW5Ub1RhZ0luZm8pKXJldHVybjtsZXQgbj1GNi5tYXBWYWx1ZXMocixvPT5PYmplY3Qua2V5cyhvKSksaT0kaShuKTt0aGlzLnNldCgiX2RhdGFOb3RGb3VuZCIsaS5sZW5ndGg9PT0wKSx0aGlzLnNldCgiX3J1blRvVGFnSW5mbyIsciksdGhpcy5hc3luYygoKT0+e3RoaXMuc2V0KCJfY2F0ZWdvcmllc0RvbVJlYWR5IiwhMCl9KX0pfV9yZWxvYWRDaGFydHMoKXt2YXIgdDsodD10aGlzLnJvb3QpPT1udWxsfHx0LnF1ZXJ5U2VsZWN0b3JBbGwoInRmLXNjYWxhci1jYXJkIikuZm9yRWFjaChyPT57ci5yZWxvYWQoKX0pfV91cGRhdGVDYXRlZ29yaWVzKCl7dmFyIHQ9dGhpcy5fcnVuVG9UYWdJbmZvLHI9dGhpcy5fc2VsZWN0ZWRSdW5zLG49dGhpcy5fdGFnRmlsdGVyO2xldCBpLG89bixhPUY2Lm1hcFZhbHVlcyh0LHM9Pk9iamVjdC5rZXlzKHMpKTtpPXVFKGEscixvKSxpLmZvckVhY2gocz0+e3MuaXRlbXM9cy5pdGVtcy5tYXAobD0+KHt0YWc6bC50YWcsc2VyaWVzOmwucnVucy5tYXAoYz0+KHtydW46Yyx0YWc6bC50YWd9KSl9KSl9KSx0aGlzLnVwZGF0ZUFycmF5UHJvcCgiX2NhdGVnb3JpZXMiLGksdGhpcy5fZ2V0Q2F0ZWdvcnlLZXkpfV90YWdNZXRhZGF0YSh0LHIsbil7bGV0IGk9dC5uYW1lLG89bi50YWcsYT17fTtuLnNlcmllcy5mb3JFYWNoKCh7cnVuOnV9KT0+e2FbdV09clt1XVtvXX0pO2xldCBzPW8ucmVwbGFjZSgvXC9zY2FsYXJfc3VtbWFyeSQvLCIiKSx7ZGVzY3JpcHRpb246bCxkaXNwbGF5TmFtZTpjfT1pUihhLHMpO3JldHVybiB0Lm1ldGFkYXRhLnR5cGU9PU5hLlBSRUZJWF9HUk9VUCYmYy5zdGFydHNXaXRoKGkrIi8iKSYmKGM9Yy5zbGljZShpLmxlbmd0aCsxKSkse2Rlc2NyaXB0aW9uOmwsZGlzcGxheU5hbWU6Y319fTt1by50ZW1wbGF0ZT1RYAogICAgPHRmLWRhc2hib2FyZC1sYXlvdXQ+CiAgICAgIDxkaXYgY2xhc3M9InNpZGViYXIiIHNsb3Q9InNpZGViYXIiPgogICAgICAgIDxkaXYgY2xhc3M9InNldHRpbmdzIj4KICAgICAgICAgIDxkaXYgY2xhc3M9InNpZGViYXItc2VjdGlvbiI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxpbmUtaXRlbSI+CiAgICAgICAgICAgICAgPHBhcGVyLWNoZWNrYm94CiAgICAgICAgICAgICAgICBpZD0ic2hvdy1kb3dubG9hZC1saW5rcyIKICAgICAgICAgICAgICAgIGNoZWNrZWQ9Int7X3Nob3dEb3dubG9hZExpbmtzfX0iCiAgICAgICAgICAgICAgICA+U2hvdyBkYXRhIGRvd25sb2FkIGxpbmtzPC9wYXBlci1jaGVja2JveAogICAgICAgICAgICAgID4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxpbmUtaXRlbSI+CiAgICAgICAgICAgICAgPHBhcGVyLWNoZWNrYm94CiAgICAgICAgICAgICAgICBpZD0iaWdub3JlLXktb3V0bGllciIKICAgICAgICAgICAgICAgIGNoZWNrZWQ9Int7X2lnbm9yZVlPdXRsaWVyc319IgogICAgICAgICAgICAgICAgPklnbm9yZSBvdXRsaWVycyBpbiBjaGFydCBzY2FsaW5nPC9wYXBlci1jaGVja2JveAogICAgICAgICAgICAgID4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgaWQ9InRvb2x0aXAtc29ydGluZyI+CiAgICAgICAgICAgICAgPGRpdj5Ub29sdGlwIHNvcnRpbmcgbWV0aG9kOjwvZGl2PgogICAgICAgICAgICAgIDxwYXBlci1kcm9wZG93bi1tZW51CiAgICAgICAgICAgICAgICBuby1sYWJlbC1mbG9hdAogICAgICAgICAgICAgICAgc2VsZWN0ZWQtaXRlbS1sYWJlbD0ie3tfdG9vbHRpcFNvcnRpbmdNZXRob2R9fSIKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8cGFwZXItbGlzdGJveAogICAgICAgICAgICAgICAgICBjbGFzcz0iZHJvcGRvd24tY29udGVudCIKICAgICAgICAgICAgICAgICAgc2VsZWN0ZWQ9IjAiCiAgICAgICAgICAgICAgICAgIHNsb3Q9ImRyb3Bkb3duLWNvbnRlbnQiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDxwYXBlci1pdGVtPmRlZmF1bHQ8L3BhcGVyLWl0ZW0+CiAgICAgICAgICAgICAgICAgIDxwYXBlci1pdGVtPmRlc2NlbmRpbmc8L3BhcGVyLWl0ZW0+CiAgICAgICAgICAgICAgICAgIDxwYXBlci1pdGVtPmFzY2VuZGluZzwvcGFwZXItaXRlbT4KICAgICAgICAgICAgICAgICAgPHBhcGVyLWl0ZW0+bmVhcmVzdDwvcGFwZXItaXRlbT4KICAgICAgICAgICAgICAgIDwvcGFwZXItbGlzdGJveD4KICAgICAgICAgICAgICA8L3BhcGVyLWRyb3Bkb3duLW1lbnU+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyLXNlY3Rpb24iPgogICAgICAgICAgICA8dGYtc21vb3RoaW5nLWlucHV0CiAgICAgICAgICAgICAgd2VpZ2h0PSJ7e19zbW9vdGhpbmdXZWlnaHR9fSIKICAgICAgICAgICAgICBzdGVwPSIwLjAwMSIKICAgICAgICAgICAgICBtaW49IjAiCiAgICAgICAgICAgICAgbWF4PSIwLjk5OSIKICAgICAgICAgICAgPjwvdGYtc21vb3RoaW5nLWlucHV0PgogICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyLXNlY3Rpb24iPgogICAgICAgICAgICA8dGYtb3B0aW9uLXNlbGVjdG9yCiAgICAgICAgICAgICAgaWQ9IngtdHlwZS1zZWxlY3RvciIKICAgICAgICAgICAgICBuYW1lPSJIb3Jpem9udGFsIEF4aXMiCiAgICAgICAgICAgICAgc2VsZWN0ZWQtaWQ9Int7X3hUeXBlfX0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8cGFwZXItYnV0dG9uIGlkPSJzdGVwIj5zdGVwPC9wYXBlci1idXR0b24KICAgICAgICAgICAgICA+PCEtLQogICAgICAgICAgICAtLT48cGFwZXItYnV0dG9uIGlkPSJyZWxhdGl2ZSI+cmVsYXRpdmU8L3BhcGVyLWJ1dHRvbgogICAgICAgICAgICAgID48IS0tCiAgICAgICAgICAgIC0tPjxwYXBlci1idXR0b24gaWQ9IndhbGxfdGltZSI+d2FsbDwvcGFwZXItYnV0dG9uPgogICAgICAgICAgICA8L3RmLW9wdGlvbi1zZWxlY3Rvcj4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9InNpZGViYXItc2VjdGlvbiBydW5zLXNlbGVjdG9yIj4KICAgICAgICAgIDx0Zi1ydW5zLXNlbGVjdG9yIHNlbGVjdGVkLXJ1bnM9Int7X3NlbGVjdGVkUnVuc319Ij4KICAgICAgICAgIDwvdGYtcnVucy1zZWxlY3Rvcj4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImNlbnRlciIgc2xvdD0iY2VudGVyIj4KICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1pZiIgaWY9IltbX2RhdGFOb3RGb3VuZF1dIj4KICAgICAgICAgIDxkaXYgY2xhc3M9Im5vLWRhdGEtd2FybmluZyI+CiAgICAgICAgICAgIDxoMz5ObyBzY2FsYXIgZGF0YSB3YXMgZm91bmQuPC9oMz4KICAgICAgICAgICAgPHA+UHJvYmFibGUgY2F1c2VzOjwvcD4KICAgICAgICAgICAgPHVsPgogICAgICAgICAgICAgIDxsaT5Zb3UgaGF2ZW7igJl0IHdyaXR0ZW4gYW55IHNjYWxhciBkYXRhIHRvIHlvdXIgZXZlbnQgZmlsZXMuPC9saT4KICAgICAgICAgICAgICA8bGk+VGVuc29yQm9hcmQgY2Fu4oCZdCBmaW5kIHlvdXIgZXZlbnQgZmlsZXMuPC9saT4KICAgICAgICAgICAgPC91bD4KCiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgIElmIHlvdeKAmXJlIG5ldyB0byB1c2luZyBUZW5zb3JCb2FyZCwgYW5kIHdhbnQgdG8gZmluZCBvdXQgaG93IHRvCiAgICAgICAgICAgICAgYWRkIGRhdGEgYW5kIHNldCB1cCB5b3VyIGV2ZW50IGZpbGVzLCBjaGVjayBvdXQgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCIKICAgICAgICAgICAgICAgID5SRUFETUU8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIHBlcmhhcHMgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiCiAgICAgICAgICAgICAgICA+VGVuc29yQm9hcmQgdHV0b3JpYWw8L2EKICAgICAgICAgICAgICA+LgogICAgICAgICAgICA8L3A+CgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICBJZiB5b3UgdGhpbmsgVGVuc29yQm9hcmQgaXMgY29uZmlndXJlZCBwcm9wZXJseSwgcGxlYXNlIHNlZQogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQjbXktdGVuc29yYm9hcmQtaXNudC1zaG93aW5nLWFueS1kYXRhLXdoYXRzLXdyb25nIgogICAgICAgICAgICAgICAgPnRoZSBzZWN0aW9uIG9mIHRoZSBSRUFETUUgZGV2b3RlZCB0byBtaXNzaW5nIGRhdGEgcHJvYmxlbXM8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIGNvbnNpZGVyIGZpbGluZyBhbiBpc3N1ZSBvbiBHaXRIdWIuCiAgICAgICAgICAgIDwvcD4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbWyFfZGF0YU5vdEZvdW5kXV0iPgogICAgICAgICAgPHRmLXRhZy1maWx0ZXJlciB0YWctZmlsdGVyPSJ7e190YWdGaWx0ZXJ9fSI+PC90Zi10YWctZmlsdGVyZXI+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19jYXRlZ29yaWVzXV0iIGFzPSJjYXRlZ29yeSI+CiAgICAgICAgICAgIDx0Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldwogICAgICAgICAgICAgIGNhdGVnb3J5PSJbW2NhdGVnb3J5XV0iCiAgICAgICAgICAgICAgaW5pdGlhbC1vcGVuZWQ9IltbX3Nob3VsZE9wZW4oaW5kZXgpXV0iCiAgICAgICAgICAgICAgZ2V0LWNhdGVnb3J5LWl0ZW0ta2V5PSJbW19nZXRDYXRlZ29yeUl0ZW1LZXldXSIKICAgICAgICAgICAgPgogICAgICAgICAgICAgIDx0ZW1wbGF0ZT4KICAgICAgICAgICAgICAgIDx0Zi1zY2FsYXItY2FyZAogICAgICAgICAgICAgICAgICBhY3RpdmU9IltbYWN0aXZlXV0iCiAgICAgICAgICAgICAgICAgIGRhdGEtdG8tbG9hZD0iW1tpdGVtLnNlcmllc11dIgogICAgICAgICAgICAgICAgICBpZ25vcmUteS1vdXRsaWVycz0iW1tfaWdub3JlWU91dGxpZXJzXV0iCiAgICAgICAgICAgICAgICAgIG11bHRpLWV4cGVyaW1lbnRzPSJbW19nZXRNdWx0aUV4cGVyaW1lbnRzKGRhdGFTZWxlY3Rpb24pXV0iCiAgICAgICAgICAgICAgICAgIHJlcXVlc3QtbWFuYWdlcj0iW1tfcmVxdWVzdE1hbmFnZXJdXSIKICAgICAgICAgICAgICAgICAgc2hvdy1kb3dubG9hZC1saW5rcz0iW1tfc2hvd0Rvd25sb2FkTGlua3NdXSIKICAgICAgICAgICAgICAgICAgc21vb3RoaW5nLWVuYWJsZWQ9IltbX3Ntb290aGluZ0VuYWJsZWRdXSIKICAgICAgICAgICAgICAgICAgc21vb3RoaW5nLXdlaWdodD0iW1tfc21vb3RoaW5nV2VpZ2h0XV0iCiAgICAgICAgICAgICAgICAgIHRhZy1tZXRhZGF0YT0iW1tfdGFnTWV0YWRhdGEoY2F0ZWdvcnksIF9ydW5Ub1RhZ0luZm8sIGl0ZW0pXV0iCiAgICAgICAgICAgICAgICAgIHRhZz0iW1tpdGVtLnRhZ11dIgogICAgICAgICAgICAgICAgICB0b29sdGlwLXNvcnRpbmctbWV0aG9kPSJbW190b29sdGlwU29ydGluZ01ldGhvZF1dIgogICAgICAgICAgICAgICAgICB4LXR5cGU9IltbX3hUeXBlXV0iCiAgICAgICAgICAgICAgICAgIGJhdGNoLXNpemU9IltbZmVhdHVyZUZsYWdzLnNjYWxhcnNCYXRjaFNpemVdXSIKICAgICAgICAgICAgICAgICAgaW4tY29sYWI9IltbZmVhdHVyZUZsYWdzLmluQ29sYWJdXSIKICAgICAgICAgICAgICAgID48L3RmLXNjYWxhci1jYXJkPgogICAgICAgICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgICAgIDwvdGYtY2F0ZWdvcnktcGFnaW5hdGVkLXZpZXc+CiAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgIDwvZGl2PgogICAgPC90Zi1kYXNoYm9hcmQtbGF5b3V0PgoKICAgIDxzdHlsZSBpbmNsdWRlPSJkYXNoYm9hcmQtc3R5bGUiPjwvc3R5bGU+CiAgICA8c3R5bGU+CiAgICAgICN0b29sdGlwLXNvcnRpbmcgewogICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICBmb250LXNpemU6IDE0cHg7CiAgICAgICAgbWFyZ2luLXRvcDogMTVweDsKICAgICAgfQoKICAgICAgI3Rvb2x0aXAtc29ydGluZyBwYXBlci1kcm9wZG93bi1tZW51IHsKICAgICAgICBtYXJnaW4tbGVmdDogMTBweDsKICAgICAgICAtLXBhcGVyLWlucHV0LWNvbnRhaW5lci1mb2N1cy1jb2xvcjogdmFyKC0tdGItb3JhbmdlLXN0cm9uZyk7CiAgICAgICAgd2lkdGg6IDEwNXB4OwogICAgICB9CgogICAgICAubGluZS1pdGVtIHsKICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICBwYWRkaW5nLXRvcDogNXB4OwogICAgICB9CiAgICAgIC5uby1kYXRhLXdhcm5pbmcgewogICAgICAgIG1heC13aWR0aDogNTQwcHg7CiAgICAgICAgbWFyZ2luOiA4MHB4IGF1dG8gMCBhdXRvOwogICAgICB9CiAgICAgIC5jZW50ZXIgewogICAgICAgIG92ZXJmbG93LXg6IGhpZGRlbjsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sdW8ucHJvdG90eXBlLCJyZWxvYWRPblJlYWR5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHVvLnByb3RvdHlwZSwiZmVhdHVyZUZsYWdzIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbixub3RpZnk6ITAsb2JzZXJ2ZXI6Il9zaG93RG93bmxvYWRMaW5rc09ic2VydmVyIn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sdW8ucHJvdG90eXBlLCJfc2hvd0Rvd25sb2FkTGlua3MiLHZvaWQgMCk7RShbQSh7dHlwZTpOdW1iZXIsbm90aWZ5OiEwLG9ic2VydmVyOiJfc21vb3RoaW5nV2VpZ2h0T2JzZXJ2ZXIifSksdygiZGVzaWduOnR5cGUiLE51bWJlcildLHVvLnByb3RvdHlwZSwiX3Ntb290aGluZ1dlaWdodCIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW4sb2JzZXJ2ZXI6Il9pZ25vcmVZT3V0bGllcnNPYnNlcnZlciJ9KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLHVvLnByb3RvdHlwZSwiX2lnbm9yZVlPdXRsaWVycyIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSx1by5wcm90b3R5cGUsIl94VHlwZSIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sdW8ucHJvdG90eXBlLCJfc2VsZWN0ZWRSdW5zIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLHVvLnByb3RvdHlwZSwiX3J1blRvVGFnSW5mbyIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLHVvLnByb3RvdHlwZSwiX2RhdGFOb3RGb3VuZCIsdm9pZCAwKTtFKFtBKHt0eXBlOlN0cmluZ30pLHcoImRlc2lnbjp0eXBlIixTdHJpbmcpXSx1by5wcm90b3R5cGUsIl90YWdGaWx0ZXIiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSx1by5wcm90b3R5cGUsIl9jYXRlZ29yaWVzRG9tUmVhZHkiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLHVvLnByb3RvdHlwZSwiX2NhdGVnb3JpZXMiLHZvaWQgMCk7RShbQSh7dHlwZTpPYmplY3R9KSx3KCJkZXNpZ246dHlwZSIsT2JqZWN0KV0sdW8ucHJvdG90eXBlLCJfZ2V0Q2F0ZWdvcnlJdGVtS2V5Iix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEFlKV0sdW8ucHJvdG90eXBlLCJfcmVxdWVzdE1hbmFnZXIiLHZvaWQgMCk7RShbUnQoIl9zbW9vdGhpbmdXZWlnaHQiKSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbiksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sdW8ucHJvdG90eXBlLCJfc21vb3RoaW5nRW5hYmxlZCIsbnVsbCk7RShbQnQoIl9ydW5Ub1RhZ0luZm8iLCJfc2VsZWN0ZWRSdW5zIiwiX3RhZ0ZpbHRlciIsIl9jYXRlZ29yaWVzRG9tUmVhZHkiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLHVvLnByb3RvdHlwZSwiX3VwZGF0ZUNhdGVnb3JpZXMiLG51bGwpO3VvPUUoW3l0KCJ0Zi1zY2FsYXItZGFzaGJvYXJkIildLHVvKTt2YXIgaXBlPUVlKE9lKCksMSk7dmFyIGRoPWNsYXNzIGV4dGVuZHMgR3QobXQpe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl90ZXh0cz1bXSx0aGlzLl9jYW5jZWxsZXI9bmV3IGFufWdldCBfcnVuQ29sb3IoKXt2YXIgdD10aGlzLnJ1bjtyZXR1cm4gZm4odCl9X2NoYW5nZVJ1bkNvbG9yKCl7dmFyIHQ9dGhpcy5fcnVuQ29sb3I7dGhpcy51cGRhdGVTdHlsZXMoeyItLXRiLXRleHQtbG9hZGVyLW91dGxpbmUiOnR9KX1hdHRhY2hlZCgpe3RoaXMucmVsb2FkKCl9cmVsb2FkKCl7aWYoIXRoaXMuaXNBdHRhY2hlZClyZXR1cm47dGhpcy5fY2FuY2VsbGVyLmNhbmNlbEFsbCgpO2xldCB0PXZlKCkscj1Dbih0LnBsdWdpblJvdXRlKCJ0ZXh0IiwiL3RleHQiKSx7dGFnOnRoaXMudGFnLHJ1bjp0aGlzLnJ1bixtYXJrZG93bjp0aGlzLm1hcmtkb3duRW5hYmxlZD8idHJ1ZSI6ImZhbHNlIn0pLG49dGhpcy5fY2FuY2VsbGVyLmNhbmNlbGxhYmxlKGk9PntpZihpLmNhbmNlbGxlZClyZXR1cm47bGV0IG89aS52YWx1ZS5tYXAoYT0+KHt3YWxsX3RpbWU6bmV3IERhdGUoYS53YWxsX3RpbWUqMWUzKSxzdGVwOmEuc3RlcCx0ZXh0OmEudGV4dH0pKTt0aGlzLnNldCgiX3RleHRzIixvLnNsaWNlKCkucmV2ZXJzZSgpKX0pO3RoaXMucmVxdWVzdE1hbmFnZXIucmVxdWVzdChyKS50aGVuKG4pfV9mb3JtYXRTdGVwKHQpe3JldHVybiB4bigiLCIpKHQpfX07ZGgudGVtcGxhdGU9UWAKICAgIDx0Zi1jYXJkLWhlYWRpbmcgcnVuPSJbW3J1bl1dIiB0YWc9IltbdGFnXV0iIGNvbG9yPSJbW19ydW5Db2xvcl1dIj4KICAgIDwvdGYtY2FyZC1oZWFkaW5nPgogICAgPHBhcGVyLW1hdGVyaWFsCiAgICAgIGVsZXZhdGlvbj0iMSIKICAgICAgaWQ9InN0ZXBzLWNvbnRhaW5lciIKICAgICAgY2xhc3M9ImNvbnRhaW5lciBzY3JvbGxiYXIiCiAgICA+CiAgICAgIDx0ZW1wbGF0ZSBpcz0iZG9tLXJlcGVhdCIgaXRlbXM9IltbX3RleHRzXV0iPgogICAgICAgIDxwYXBlci1tYXRlcmlhbCBlbGV2YXRpb249IjEiIGNsYXNzPSJzdGVwLWNvbnRhaW5lciI+CiAgICAgICAgICBzdGVwIDxzcGFuIGNsYXNzPSJzdGVwLXZhbHVlIj5bW19mb3JtYXRTdGVwKGl0ZW0uc3RlcCldXTwvc3Bhbj4KICAgICAgICA8L3BhcGVyLW1hdGVyaWFsPgogICAgICAgIDxwYXBlci1tYXRlcmlhbCBlbGV2YXRpb249IjEiIGNsYXNzPSJ0ZXh0Ij4KICAgICAgICAgIDx0Zi1tYXJrZG93bi12aWV3IGh0bWw9IltbaXRlbS50ZXh0XV0iPjwvdGYtbWFya2Rvd24tdmlldz4KICAgICAgICA8L3BhcGVyLW1hdGVyaWFsPgogICAgICA8L3RlbXBsYXRlPgogICAgPC9wYXBlci1tYXRlcmlhbD4KICAgIDxzdHlsZSBpbmNsdWRlPSJzY3JvbGxiYXItc3R5bGUiPjwvc3R5bGU+CiAgICA8c3R5bGU+CiAgICAgIDpob3N0IHsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgaGVpZ2h0OiBhdXRvOwogICAgICAgIG1hcmdpbi1yaWdodDogMTBweDsKICAgICAgICBtYXJnaW4tYm90dG9tOiAxNXB4OwogICAgICB9CiAgICAgIC5zY3JvbGxiYXIgewogICAgICAgIHdpbGwtY2hhbmdlOiB0cmFuc2Zvcm07CiAgICAgIH0KICAgICAgI3N0ZXBzLWNvbnRhaW5lciB7CiAgICAgICAgYm9yZGVyLXJhZGl1czogM3B4OwogICAgICAgIGJvcmRlcjogMnB4IHNvbGlkIC8qIGNvbG9yIGNvbXB1dGVkIGFuZCBzZXQgYXMgaW5saW5lIHN0eWxlICovOwogICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgIG1heC1oZWlnaHQ6IDUwMHB4OwogICAgICAgIG92ZXJmbG93OiBhdXRvOwogICAgICAgIHBhZGRpbmc6IDEwcHg7CiAgICAgICAgYm9yZGVyLWNvbG9yOiB2YXIoLS10Yi10ZXh0LWxvYWRlci1vdXRsaW5lKTsKICAgICAgfQogICAgICAudGV4dCB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogaW5oZXJpdDsKICAgICAgICBib3JkZXItcmFkaXVzOiAwIDNweCAzcHggM3B4OwogICAgICAgIHBhZGRpbmc6IDVweDsKICAgICAgICB3b3JkLWJyZWFrOiBicmVhay13b3JkOwogICAgICB9CiAgICAgIC5zdGVwLWNvbnRhaW5lciB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tdGItdWktbGlnaHQtYWNjZW50KTsKICAgICAgICBib3JkZXItYm90dG9tOiBub25lOwogICAgICAgIGJvcmRlci1yYWRpdXM6IDNweCAzcHggMCAwOwogICAgICAgIGJvcmRlcjogMXB4IHNvbGlkIHZhcigtLXRiLXVpLWJvcmRlcik7CiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgICAgIGZvbnQtc2l6ZTogMTJweDsKICAgICAgICBmb250LXN0eWxlOiBpdGFsaWM7CiAgICAgICAgbWFyZ2luLWxlZnQ6IC0xcHg7IC8qIHRvIGNvcnJlY3QgZm9yIGJvcmRlciAqLwogICAgICAgIHBhZGRpbmc6IDNweDsKICAgICAgfQogICAgICAuc3RlcC1jb250YWluZXI6bm90KDpmaXJzdC1jaGlsZCkgewogICAgICAgIG1hcmdpbi10b3A6IDE1cHg7CiAgICAgIH0KCiAgICAgIHRmLWNhcmQtaGVhZGluZyB7CiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTBweDsKICAgICAgfQogICAgPC9zdHlsZT4KICBgO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLGRoLnByb3RvdHlwZSwicnVuIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLGRoLnByb3RvdHlwZSwidGFnIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sZGgucHJvdG90eXBlLCJtYXJrZG93bkVuYWJsZWQiLHZvaWQgMCk7RShbQSh7dHlwZTpBcnJheX0pLHcoImRlc2lnbjp0eXBlIixBcnJheSldLGRoLnByb3RvdHlwZSwiX3RleHRzIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLEFlKV0sZGgucHJvdG90eXBlLCJyZXF1ZXN0TWFuYWdlciIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixhbildLGRoLnByb3RvdHlwZSwiX2NhbmNlbGxlciIsdm9pZCAwKTtFKFtSdCgicnVuIiksdygiZGVzaWduOnR5cGUiLFN0cmluZyksdygiZGVzaWduOnBhcmFtdHlwZXMiLFtdKV0sZGgucHJvdG90eXBlLCJfcnVuQ29sb3IiLG51bGwpO0UoW0J0KCJfcnVuQ29sb3IiKSx3KCJkZXNpZ246dHlwZSIsRnVuY3Rpb24pLHcoImRlc2lnbjpwYXJhbXR5cGVzIixbXSksdygiZGVzaWduOnJldHVybnR5cGUiLHZvaWQgMCldLGRoLnByb3RvdHlwZSwiX2NoYW5nZVJ1bkNvbG9yIixudWxsKTtkaD1FKFt5dCgidGYtdGV4dC1sb2FkZXIiKV0sZGgpO3ZhciBRYz1jbGFzcyBleHRlbmRzIEd0KG10KXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5yZWxvYWRPblJlYWR5PSEwLHRoaXMuX21hcmtkb3duRW5hYmxlZD12cCgiX21hcmtkb3duRW5hYmxlZCIse2RlZmF1bHRWYWx1ZTohMCx1c2VMb2NhbFN0b3JhZ2U6ITB9KS5jYWxsKHRoaXMpLHRoaXMuX3JlcXVlc3RNYW5hZ2VyPW5ldyBBZSx0aGlzLl9tYXJrZG93bkVuYWJsZWRTdG9yYWdlT2JzZXJ2ZXI9eHAoIl9tYXJrZG93bkVuYWJsZWQiLHtkZWZhdWx0VmFsdWU6ITAsdXNlTG9jYWxTdG9yYWdlOiEwfSl9c3RhdGljIGdldCBvYnNlcnZlcnMoKXtyZXR1cm5bIl9tYXJrZG93bkVuYWJsZWRPYnNlcnZlcihfbWFya2Rvd25FbmFibGVkKSJdfXJlYWR5KCl7c3VwZXIucmVhZHkoKSx0aGlzLnJlbG9hZE9uUmVhZHkmJnRoaXMucmVsb2FkKCl9cmVsb2FkKCl7dGhpcy5fZmV0Y2hUYWdzKCkudGhlbigoKT0+e3RoaXMuX3JlbG9hZFRleHRzKCl9KX1fc2hvdWxkT3Blbih0KXtyZXR1cm4gdDw9Mn1fZmV0Y2hUYWdzKCl7bGV0IHQ9dmUoKS5wbHVnaW5Sb3V0ZSgidGV4dCIsIi90YWdzIik7cmV0dXJuIHRoaXMuX3JlcXVlc3RNYW5hZ2VyLnJlcXVlc3QodCkudGhlbihyPT57aWYoaXBlLmlzRXF1YWwocix0aGlzLl9ydW5Ub1RhZykpcmV0dXJuO2xldCBuPSRpKHIpO3RoaXMuc2V0KCJfZGF0YU5vdEZvdW5kIixuLmxlbmd0aD09PTApLHRoaXMuc2V0KCJfcnVuVG9UYWciLHIpLHRoaXMuYXN5bmMoKCk9Pnt0aGlzLnNldCgiX2NhdGVnb3JpZXNEb21SZWFkeSIsITApfSl9KX1fcmVsb2FkVGV4dHMoKXt2YXIgdDsodD10aGlzLnJvb3QpPT1udWxsfHx0LnF1ZXJ5U2VsZWN0b3JBbGwoInRmLXRleHQtbG9hZGVyIikuZm9yRWFjaChyPT57ci5yZWxvYWQoKX0pfWdldCBfY2F0ZWdvcmllcygpe3ZhciB0PXRoaXMuX3J1blRvVGFnLHI9dGhpcy5fc2VsZWN0ZWRSdW5zLG49dGhpcy5fdGFnRmlsdGVyO3JldHVybiBRbCh0LHIsbil9X21hcmtkb3duRW5hYmxlZE9ic2VydmVyKCl7dGhpcy5fcmVsb2FkVGV4dHMoKX19O1FjLnRlbXBsYXRlPVFgCiAgICA8dGYtZGFzaGJvYXJkLWxheW91dD4KICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhciIgc2xvdD0ic2lkZWJhciI+CiAgICAgICAgPGRpdiBjbGFzcz0ic2lkZWJhci1zZWN0aW9uIj4KICAgICAgICAgIDxkaXYgY2xhc3M9ImxpbmUtaXRlbSI+CiAgICAgICAgICAgIDxwYXBlci1jaGVja2JveCBjaGVja2VkPSJ7e19tYXJrZG93bkVuYWJsZWR9fSIKICAgICAgICAgICAgICA+RW5hYmxlIE1hcmtkb3duPC9wYXBlci1jaGVja2JveAogICAgICAgICAgICA+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSJzaWRlYmFyLXNlY3Rpb24gcnVucy1zZWxlY3RvciI+CiAgICAgICAgICA8dGYtcnVucy1zZWxlY3RvciBzZWxlY3RlZC1ydW5zPSJ7e19zZWxlY3RlZFJ1bnN9fSI+CiAgICAgICAgICA8L3RmLXJ1bnMtc2VsZWN0b3I+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJjZW50ZXIiIHNsb3Q9ImNlbnRlciI+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbW19kYXRhTm90Rm91bmRdXSI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJuby1kYXRhLXdhcm5pbmciPgogICAgICAgICAgICA8aDM+Tm8gdGV4dCBkYXRhIHdhcyBmb3VuZC48L2gzPgogICAgICAgICAgICA8cD5Qcm9iYWJsZSBjYXVzZXM6PC9wPgogICAgICAgICAgICA8dWw+CiAgICAgICAgICAgICAgPGxpPllvdSBoYXZlbuKAmXQgd3JpdHRlbiBhbnkgdGV4dCBkYXRhIHRvIHlvdXIgZXZlbnQgZmlsZXMuPC9saT4KICAgICAgICAgICAgICA8bGk+VGVuc29yQm9hcmQgY2Fu4oCZdCBmaW5kIHlvdXIgZXZlbnQgZmlsZXMuPC9saT4KICAgICAgICAgICAgPC91bD4KCiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgIElmIHlvdeKAmXJlIG5ldyB0byB1c2luZyBUZW5zb3JCb2FyZCwgYW5kIHdhbnQgdG8gZmluZCBvdXQgaG93IHRvCiAgICAgICAgICAgICAgYWRkIGRhdGEgYW5kIHNldCB1cCB5b3VyIGV2ZW50IGZpbGVzLCBjaGVjayBvdXQgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCIKICAgICAgICAgICAgICAgID5SRUFETUU8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIHBlcmhhcHMgdGhlCiAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiCiAgICAgICAgICAgICAgICA+VGVuc29yQm9hcmQgdHV0b3JpYWw8L2EKICAgICAgICAgICAgICA+LgogICAgICAgICAgICA8L3A+CgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICBJZiB5b3UgdGhpbmsgVGVuc29yQm9hcmQgaXMgY29uZmlndXJlZCBwcm9wZXJseSwgcGxlYXNlIHNlZQogICAgICAgICAgICAgIDxhCiAgICAgICAgICAgICAgICBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQjbXktdGVuc29yYm9hcmQtaXNudC1zaG93aW5nLWFueS1kYXRhLXdoYXRzLXdyb25nIgogICAgICAgICAgICAgICAgPnRoZSBzZWN0aW9uIG9mIHRoZSBSRUFETUUgZGV2b3RlZCB0byBtaXNzaW5nIGRhdGEgcHJvYmxlbXM8L2EKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgYW5kIGNvbnNpZGVyIGZpbGluZyBhbiBpc3N1ZSBvbiBHaXRIdWIuCiAgICAgICAgICAgIDwvcD4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvdGVtcGxhdGU+CiAgICAgICAgPHRlbXBsYXRlIGlzPSJkb20taWYiIGlmPSJbWyFfZGF0YU5vdEZvdW5kXV0iPgogICAgICAgICAgPHRmLXRhZy1maWx0ZXJlciB0YWctZmlsdGVyPSJ7e190YWdGaWx0ZXJ9fSI+PC90Zi10YWctZmlsdGVyZXI+CiAgICAgICAgICA8dGVtcGxhdGUgaXM9ImRvbS1yZXBlYXQiIGl0ZW1zPSJbW19jYXRlZ29yaWVzXV0iIGFzPSJjYXRlZ29yeSI+CiAgICAgICAgICAgIDx0Zi1jYXRlZ29yeS1wYWdpbmF0ZWQtdmlldwogICAgICAgICAgICAgIGNhdGVnb3J5PSJbW2NhdGVnb3J5XV0iCiAgICAgICAgICAgICAgaW5pdGlhbC1vcGVuZWQ9IltbX3Nob3VsZE9wZW4oaW5kZXgpXV0iCiAgICAgICAgICAgID4KICAgICAgICAgICAgICA8dGVtcGxhdGU+CiAgICAgICAgICAgICAgICA8dGYtdGV4dC1sb2FkZXIKICAgICAgICAgICAgICAgICAgYWN0aXZlPSJbW2FjdGl2ZV1dIgogICAgICAgICAgICAgICAgICB0YWc9IltbaXRlbS50YWddXSIKICAgICAgICAgICAgICAgICAgcnVuPSJbW2l0ZW0ucnVuXV0iCiAgICAgICAgICAgICAgICAgIHJlcXVlc3QtbWFuYWdlcj0iW1tfcmVxdWVzdE1hbmFnZXJdXSIKICAgICAgICAgICAgICAgICAgbWFya2Rvd24tZW5hYmxlZD0iW1tfbWFya2Rvd25FbmFibGVkXV0iCiAgICAgICAgICAgICAgICA+PC90Zi10ZXh0LWxvYWRlcj4KICAgICAgICAgICAgICA8L3RlbXBsYXRlPgogICAgICAgICAgICA8L3RmLWNhdGVnb3J5LXBhZ2luYXRlZC12aWV3PgogICAgICAgICAgPC90ZW1wbGF0ZT4KICAgICAgICA8L3RlbXBsYXRlPgogICAgICA8L2Rpdj4KICAgIDwvdGYtZGFzaGJvYXJkLWxheW91dD4KICAgIDxzdHlsZSBpbmNsdWRlPSJkYXNoYm9hcmQtc3R5bGUiPjwvc3R5bGU+CiAgICA8c3R5bGU+CiAgICAgIC5uby1kYXRhLXdhcm5pbmcgewogICAgICAgIG1heC13aWR0aDogNTQwcHg7CiAgICAgICAgbWFyZ2luOiA4MHB4IGF1dG8gMCBhdXRvOwogICAgICB9CiAgICA8L3N0eWxlPgogIGA7RShbQSh7dHlwZTpCb29sZWFufSksdygiZGVzaWduOnR5cGUiLEJvb2xlYW4pXSxRYy5wcm90b3R5cGUsInJlbG9hZE9uUmVhZHkiLHZvaWQgMCk7RShbQSh7dHlwZTpCb29sZWFuLG5vdGlmeTohMCxvYnNlcnZlcjoiX21hcmtkb3duRW5hYmxlZFN0b3JhZ2VPYnNlcnZlciJ9KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLFFjLnByb3RvdHlwZSwiX21hcmtkb3duRW5hYmxlZCIsdm9pZCAwKTtFKFtBKHt0eXBlOkFycmF5fSksdygiZGVzaWduOnR5cGUiLEFycmF5KV0sUWMucHJvdG90eXBlLCJfc2VsZWN0ZWRSdW5zIix2b2lkIDApO0UoW0Eoe3R5cGU6T2JqZWN0fSksdygiZGVzaWduOnR5cGUiLE9iamVjdCldLFFjLnByb3RvdHlwZSwiX3J1blRvVGFnIix2b2lkIDApO0UoW0Eoe3R5cGU6Qm9vbGVhbn0pLHcoImRlc2lnbjp0eXBlIixCb29sZWFuKV0sUWMucHJvdG90eXBlLCJfZGF0YU5vdEZvdW5kIix2b2lkIDApO0UoW0Eoe3R5cGU6U3RyaW5nfSksdygiZGVzaWduOnR5cGUiLFN0cmluZyldLFFjLnByb3RvdHlwZSwiX3RhZ0ZpbHRlciIsdm9pZCAwKTtFKFtBKHt0eXBlOkJvb2xlYW59KSx3KCJkZXNpZ246dHlwZSIsQm9vbGVhbildLFFjLnByb3RvdHlwZSwiX2NhdGVnb3JpZXNEb21SZWFkeSIsdm9pZCAwKTtFKFtBKHt0eXBlOk9iamVjdH0pLHcoImRlc2lnbjp0eXBlIixPYmplY3QpXSxRYy5wcm90b3R5cGUsIl9yZXF1ZXN0TWFuYWdlciIsdm9pZCAwKTtFKFtSdCgiX3J1blRvVGFnIiwiX3NlbGVjdGVkUnVucyIsIl90YWdGaWx0ZXIiLCJfY2F0ZWdvcmllc0RvbVJlYWR5IiksdygiZGVzaWduOnR5cGUiLEFycmF5KSx3KCJkZXNpZ246cGFyYW10eXBlcyIsW10pXSxRYy5wcm90b3R5cGUsIl9jYXRlZ29yaWVzIixudWxsKTtRYz1FKFt5dCgidGYtdGV4dC1kYXNoYm9hcmQiKV0sUWMpO3ZhciBvcGU9Y2xhc3MgZXh0ZW5kcyBtdHtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fdGVtcGxhdGU9bnVsbCx0aGlzLnRmX2JhY2tlbmQ9a0J9fTtvcGU9RShbeXQoInRmLWJhY2tlbmQiKV0sb3BlKTt2YXIgYXBlPWNsYXNzIGV4dGVuZHMgbXR7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX3RlbXBsYXRlPW51bGwsdGhpcy5ydW5zQ29sb3JTY2FsZT1mbn19O2FwZT1FKFt5dCgidGYtY29sb3Itc2NhbGUiKV0sYXBlKTt2YXIgc3BlPWNsYXNzIGV4dGVuZHMgbXR7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX3RlbXBsYXRlPW51bGwsdGhpcy50Zl9mZWF0dXJlX2ZsYWdzPXRXfX07c3BlPUUoW3l0KCJ0Zi1mZWF0dXJlLWZsYWdzIildLHNwZSk7dmFyIGxwZT1jbGFzcyBleHRlbmRzIG10e2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl90ZW1wbGF0ZT1udWxsLHRoaXMudGZfZ2xvYmFscz1SV319O2xwZT1FKFt5dCgidGYtZ2xvYmFscyIpXSxscGUpO3ZhciBVaHQ9e307S3MoVWh0LHtUZkRvbVJlcGVhdDooKT0+T2ksYWRkTGltaXRMaXN0ZW5lcjooKT0+cVcsZ2V0TGltaXQ6KCk9PldXLHJlbW92ZUxpbWl0TGlzdGVuZXI6KCk9PkdXLHNldExpbWl0OigpPT5yMmV9KTt2YXIgY3BlPWNsYXNzIGV4dGVuZHMgbXR7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX3RlbXBsYXRlPW51bGwsdGhpcy50Zl9wYWdpbmF0ZWRfdmlldz1VaHR9fTtjcGU9RShbeXQoInRmLXBhZ2luYXRlZC12aWV3LXN0b3JlIildLGNwZSk7dmFyIHVwZT1jbGFzcyBleHRlbmRzIG10e2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl90ZW1wbGF0ZT1udWxsLHRoaXMudGZfc3RvcmFnZT1EQn19O3VwZT1FKFt5dCgidGYtc3RvcmFnZSIpXSx1cGUpO30pKCk7Ci8qIQogKiBpcy1wbGFpbi1vYmplY3QgPGh0dHBzOi8vZ2l0aHViLmNvbS9qb25zY2hsaW5rZXJ0L2lzLXBsYWluLW9iamVjdD4KICoKICogQ29weXJpZ2h0IChjKSAyMDE0LTIwMTcsIEpvbiBTY2hsaW5rZXJ0LgogKiBSZWxlYXNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuCiAqLwovKiEKICogaXNvYmplY3QgPGh0dHBzOi8vZ2l0aHViLmNvbS9qb25zY2hsaW5rZXJ0L2lzb2JqZWN0PgogKgogKiBDb3B5cmlnaHQgKGMpIDIwMTQtMjAxNywgSm9uIFNjaGxpbmtlcnQuCiAqIFJlbGVhc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4KICovCi8qISAqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgpDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7IHlvdSBtYXkgbm90IHVzZQp0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4gWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZQpMaWNlbnNlIGF0IGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAoKVEhJUyBDT0RFIElTIFBST1ZJREVEIE9OIEFOICpBUyBJUyogQkFTSVMsIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWQpLSU5ELCBFSVRIRVIgRVhQUkVTUyBPUiBJTVBMSUVELCBJTkNMVURJTkcgV0lUSE9VVCBMSU1JVEFUSU9OIEFOWSBJTVBMSUVECldBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBUSVRMRSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UsCk1FUkNIQU5UQUJMSVRZIE9SIE5PTi1JTkZSSU5HRU1FTlQuCgpTZWUgdGhlIEFwYWNoZSBWZXJzaW9uIDIuMCBMaWNlbnNlIGZvciBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMKYW5kIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiAqLwovKioKICogQGZpbGVvdmVydmlldwogKiBAc3VwcHJlc3Mge2NoZWNrUHJvdG90eXBhbFR5cGVzfQogKiBAbGljZW5zZSBDb3B5cmlnaHQgKGMpIDIwMTcgVGhlIFBvbHltZXIgUHJvamVjdCBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLgogKiBUaGlzIGNvZGUgbWF5IG9ubHkgYmUgdXNlZCB1bmRlciB0aGUgQlNEIHN0eWxlIGxpY2Vuc2UgZm91bmQgYXQKICogaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0xJQ0VOU0UudHh0IFRoZSBjb21wbGV0ZSBzZXQgb2YgYXV0aG9ycyBtYXkgYmUgZm91bmQKICogYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0FVVEhPUlMudHh0IFRoZSBjb21wbGV0ZSBzZXQgb2YgY29udHJpYnV0b3JzIG1heQogKiBiZSBmb3VuZCBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vQ09OVFJJQlVUT1JTLnR4dCBDb2RlIGRpc3RyaWJ1dGVkIGJ5CiAqIEdvb2dsZSBhcyBwYXJ0IG9mIHRoZSBwb2x5bWVyIHByb2plY3QgaXMgYWxzbyBzdWJqZWN0IHRvIGFuIGFkZGl0aW9uYWwgSVAKICogcmlnaHRzIGdyYW50IGZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9QQVRFTlRTLnR4dAogKi8KLyoqCiAqIEBsaWNlbnNlCiAqIENvcHlyaWdodCAoYykgMjAxNiBUaGUgUG9seW1lciBQcm9qZWN0IEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuCiAqIFRoaXMgY29kZSBtYXkgb25seSBiZSB1c2VkIHVuZGVyIHRoZSBCU0Qgc3R5bGUgbGljZW5zZSBmb3VuZCBhdAogKiBodHRwOi8vcG9seW1lci5naXRodWIuaW8vTElDRU5TRS50eHQgVGhlIGNvbXBsZXRlIHNldCBvZiBhdXRob3JzIG1heSBiZSBmb3VuZAogKiBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vQVVUSE9SUy50eHQgVGhlIGNvbXBsZXRlIHNldCBvZiBjb250cmlidXRvcnMgbWF5CiAqIGJlIGZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9DT05UUklCVVRPUlMudHh0IENvZGUgZGlzdHJpYnV0ZWQgYnkKICogR29vZ2xlIGFzIHBhcnQgb2YgdGhlIHBvbHltZXIgcHJvamVjdCBpcyBhbHNvIHN1YmplY3QgdG8gYW4gYWRkaXRpb25hbCBJUAogKiByaWdodHMgZ3JhbnQgZm91bmQgYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL1BBVEVOVFMudHh0CiAqLwovKioKICogQGxpY2Vuc2UKICogQ29weXJpZ2h0IChjKSAyMDE3IFRoZSBQb2x5bWVyIFByb2plY3QgQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KICogVGhpcyBjb2RlIG1heSBvbmx5IGJlIHVzZWQgdW5kZXIgdGhlIEJTRCBzdHlsZSBsaWNlbnNlIGZvdW5kIGF0CiAqIGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9MSUNFTlNFLnR4dAogKiBUaGUgY29tcGxldGUgc2V0IG9mIGF1dGhvcnMgbWF5IGJlIGZvdW5kIGF0CiAqIGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9BVVRIT1JTLnR4dAogKiBUaGUgY29tcGxldGUgc2V0IG9mIGNvbnRyaWJ1dG9ycyBtYXkgYmUgZm91bmQgYXQKICogaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0NPTlRSSUJVVE9SUy50eHQKICogQ29kZSBkaXN0cmlidXRlZCBieSBHb29nbGUgYXMgcGFydCBvZiB0aGUgcG9seW1lciBwcm9qZWN0IGlzIGFsc28KICogc3ViamVjdCB0byBhbiBhZGRpdGlvbmFsIElQIHJpZ2h0cyBncmFudCBmb3VuZCBhdAogKiBodHRwOi8vcG9seW1lci5naXRodWIuaW8vUEFURU5UUy50eHQKICovCi8qKgogKiBAbGljZW5zZQogKiBDb3B5cmlnaHQgKGMpIDIwMTggVGhlIFBvbHltZXIgUHJvamVjdCBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLgogKiBUaGlzIGNvZGUgbWF5IG9ubHkgYmUgdXNlZCB1bmRlciB0aGUgQlNEIHN0eWxlIGxpY2Vuc2UgZm91bmQgYXQKICogaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0xJQ0VOU0UudHh0CiAqIFRoZSBjb21wbGV0ZSBzZXQgb2YgYXV0aG9ycyBtYXkgYmUgZm91bmQgYXQKICogaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0FVVEhPUlMudHh0CiAqIFRoZSBjb21wbGV0ZSBzZXQgb2YgY29udHJpYnV0b3JzIG1heSBiZSBmb3VuZCBhdAogKiBodHRwOi8vcG9seW1lci5naXRodWIuaW8vQ09OVFJJQlVUT1JTLnR4dAogKiBDb2RlIGRpc3RyaWJ1dGVkIGJ5IEdvb2dsZSBhcyBwYXJ0IG9mIHRoZSBwb2x5bWVyIHByb2plY3QgaXMgYWxzbwogKiBzdWJqZWN0IHRvIGFuIGFkZGl0aW9uYWwgSVAgcmlnaHRzIGdyYW50IGZvdW5kIGF0CiAqIGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9QQVRFTlRTLnR4dAogKi8KLyoqCiAqIEBsaWNlbnNlCiAqIENvcHlyaWdodCAoYykgMjAyMSBWYWFkaW4gTHRkLgogKiBUaGlzIHByb2dyYW0gaXMgYXZhaWxhYmxlIHVuZGVyIEFwYWNoZSBMaWNlbnNlIFZlcnNpb24gMi4wLCBhdmFpbGFibGUgYXQgaHR0cHM6Ly92YWFkaW4uY29tL2xpY2Vuc2UvCiAqLwovKioKICogQGxpY2Vuc2UKICogQ29weXJpZ2h0IDIwMTAtMjAyMiBUaHJlZS5qcyBBdXRob3JzCiAqIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBNSVQKICovCi8qKgogKiBAbGljZW5zZQogKiBMb2Rhc2ggPGh0dHBzOi8vbG9kYXNoLmNvbS8+CiAqIENvcHlyaWdodCBPcGVuSlMgRm91bmRhdGlvbiBhbmQgb3RoZXIgY29udHJpYnV0b3JzIDxodHRwczovL29wZW5qc2Yub3JnLz4KICogUmVsZWFzZWQgdW5kZXIgTUlUIGxpY2Vuc2UgPGh0dHBzOi8vbG9kYXNoLmNvbS9saWNlbnNlPgogKiBCYXNlZCBvbiBVbmRlcnNjb3JlLmpzIDEuOC4zIDxodHRwOi8vdW5kZXJzY29yZWpzLm9yZy9MSUNFTlNFPgogKiBDb3B5cmlnaHQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnMKICovCi8qKgogKiBDb3B5cmlnaHQgMjAxNC1wcmVzZW50IFBhbGFudGlyIFRlY2hub2xvZ2llcwogKiBAbGljZW5zZSBNSVQKICoKICogQGZpbGVvdmVydmlldyBtYW51YWxseSBhZGQgZDMtc2VsZWN0aW9uLW11bHRpIHRvIGQzIGRlZmF1bHQgYnVuZGxlLiBNb3N0IG9mIHRoaXMgY29kZSBpcwogKiBjb3BpZWQgZnJvbSBkMy1zZWxlY3Rpb24tbXVsdGlAMS4wLjAuCiAqIFNlZSBodHRwczovL2dpdGh1Yi5jb20vZDMvZDMtc2VsZWN0aW9uLW11bHRpL2lzc3Vlcy8xMSBmb3Igd2h5IHdlIGhhdmUgdG8gZG8gdGhpcwogKi8KLyoqCiAqIENvcHlyaWdodCAyMDE0LXByZXNlbnQgUGFsYW50aXIgVGVjaG5vbG9naWVzCiAqIEBsaWNlbnNlIE1JVAogKiBAZmlsZW92ZXJ2aWV3IEltcGxlbWVudHMgYSBjb252ZW5pZW50IHRodW5rIGZ1bmN0aW9uIHRvIGhhbmRsZSB0aGUgY29tbW9uIGNhc2UKICogb2YgY3JlYXRpbmcgYSBtZW1vaXplZCBmdW5jdGlvbiB0aGF0IHRha2VzIGl0cyBpbnB1dHMgZnJvbSBtdXRhYmxlIGNsYXNzIHByb3BlcnRpZXMuCiAqLwovKioKICogQ29weXJpZ2h0IDIwMTQtcHJlc2VudCBQYWxhbnRpciBUZWNobm9sb2dpZXMKICogQGxpY2Vuc2UgTUlUCiAqIEBmaWxlb3ZlcnZpZXcgSW1wbGVtZW50cyBhIGZ1bmN0aW9uIG1lbW9pemVyIHVzaW5nIHRoZSBTaWduYXR1cmUgQVBJLgogKi8KLyoqCiAqIENvcHlyaWdodCAyMDE0LXByZXNlbnQgUGFsYW50aXIgVGVjaG5vbG9naWVzCiAqIEBsaWNlbnNlIE1JVAogKiBAZmlsZW92ZXJ2aWV3IEltcGxlbWVudHMgdGhlIFNpZ25hdHVyZSBBUEkgdG8gaGVscCBpbiBjb21wYXJpbmcgd2hlbiB0d28KICogUGxvdHRhYmxlIG9iamVjdHMgaGF2ZSAiY2hhbmdlZCIuCiAqCiAqIE1lbW9pemF0aW9uIGluIFBsb3R0YWJsZSBpcyBjb21wbGljYXRlZCBieSBtdXRhYmxlIHNjYWxlcyBhbmQgZGF0YXNldHMuIFdlIGNhbm5vdCBzaW1wbHkKICogcmVmZXJlbmNlIGNvbXBhcmUgdHdvIGUuZy4gc2NhbGVzIHNpbmNlIGl0IG1heSBoYXZlIGludGVybmFsbHkgbXV0YXRlZC4gVG8gcmVzb2x2ZSB0aGlzLAogKiB3ZSB3cml0ZSBhIHJlY3Vyc2l2ZSBTaWduYXR1cmUgaW50ZXJmYWNlIHRoYXQgaG9sZHMgYW4gaW1tdXRhYmxlIHNuYXBzaG90IG9mIHdoYXRldmVyCiAqIHN0YXRlIHRoZSBzY2FsZS9kYXRhIHdhcyBpbiBhdCB0aGUgdGltZS4gVGhlbiBvbiBtZW1vaXplZCBmdW5jdGlvbiBpbnZvY2F0aW9uIHdlIHNpZ24gdGhlCiAqIG5ldyBpbnB1dHMgYW5kIGNvbXBhcmUgdGhlIHNpZ25hdHVyZXMgdG8gZGVjaWRlIGlmIHdlIHNob3VsZCByZWNvbXB1dGUuCiAqCiAqIFdlIG11c3QgaGFuZC13cml0ZSBhIHNpZ25hdHVyZSBmb3IgZWFjaCBjdXN0b20gY2xhc3Mgd2Ugd2lzaCB0byBzdXBwb3J0LgogKi8KLyoqCiAqIENvcHlyaWdodCAyMDE0LXByZXNlbnQgUGFsYW50aXIgVGVjaG5vbG9naWVzCiAqIEBsaWNlbnNlIE1JVAogKi8KLyoqCiAqIENvcHlyaWdodCAyMDE3LXByZXNlbnQgUGFsYW50aXIgVGVjaG5vbG9naWVzCiAqIEBsaWNlbnNlIE1JVAogKi8KLyoqCkBsaWNlbnNlCkNvcHlyaWdodCAoYykgMjAxNCBUaGUgUG9seW1lciBQcm9qZWN0IEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuClRoaXMgY29kZSBtYXkgb25seSBiZSB1c2VkIHVuZGVyIHRoZSBCU0Qgc3R5bGUgbGljZW5zZSBmb3VuZCBhdApodHRwOi8vcG9seW1lci5naXRodWIuaW8vTElDRU5TRS50eHQgVGhlIGNvbXBsZXRlIHNldCBvZiBhdXRob3JzIG1heSBiZSBmb3VuZCBhdApodHRwOi8vcG9seW1lci5naXRodWIuaW8vQVVUSE9SUy50eHQgVGhlIGNvbXBsZXRlIHNldCBvZiBjb250cmlidXRvcnMgbWF5IGJlCmZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9DT05UUklCVVRPUlMudHh0IENvZGUgZGlzdHJpYnV0ZWQgYnkgR29vZ2xlIGFzCnBhcnQgb2YgdGhlIHBvbHltZXIgcHJvamVjdCBpcyBhbHNvIHN1YmplY3QgdG8gYW4gYWRkaXRpb25hbCBJUCByaWdodHMgZ3JhbnQKZm91bmQgYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL1BBVEVOVFMudHh0CiovCi8qKgpAbGljZW5zZQpDb3B5cmlnaHQgKGMpIDIwMTUgVGhlIFBvbHltZXIgUHJvamVjdCBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLgpUaGlzIGNvZGUgbWF5IG9ubHkgYmUgdXNlZCB1bmRlciB0aGUgQlNEIHN0eWxlIGxpY2Vuc2UgZm91bmQgYXQKaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0xJQ0VOU0UudHh0IFRoZSBjb21wbGV0ZSBzZXQgb2YgYXV0aG9ycyBtYXkgYmUgZm91bmQgYXQKaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0FVVEhPUlMudHh0IFRoZSBjb21wbGV0ZSBzZXQgb2YgY29udHJpYnV0b3JzIG1heSBiZQpmb3VuZCBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vQ09OVFJJQlVUT1JTLnR4dCBDb2RlIGRpc3RyaWJ1dGVkIGJ5IEdvb2dsZSBhcwpwYXJ0IG9mIHRoZSBwb2x5bWVyIHByb2plY3QgaXMgYWxzbyBzdWJqZWN0IHRvIGFuIGFkZGl0aW9uYWwgSVAgcmlnaHRzIGdyYW50CmZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9QQVRFTlRTLnR4dAoqLwovKioKQGxpY2Vuc2UKQ29weXJpZ2h0IChjKSAyMDE1IFRoZSBQb2x5bWVyIFByb2plY3QgQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KVGhpcyBjb2RlIG1heSBvbmx5IGJlIHVzZWQgdW5kZXIgdGhlIEJTRCBzdHlsZSBsaWNlbnNlIGZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9MSUNFTlNFLnR4dApUaGUgY29tcGxldGUgc2V0IG9mIGF1dGhvcnMgbWF5IGJlIGZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9BVVRIT1JTLnR4dApUaGUgY29tcGxldGUgc2V0IG9mIGNvbnRyaWJ1dG9ycyBtYXkgYmUgZm91bmQgYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0NPTlRSSUJVVE9SUy50eHQKQ29kZSBkaXN0cmlidXRlZCBieSBHb29nbGUgYXMgcGFydCBvZiB0aGUgcG9seW1lciBwcm9qZWN0IGlzIGFsc28Kc3ViamVjdCB0byBhbiBhZGRpdGlvbmFsIElQIHJpZ2h0cyBncmFudCBmb3VuZCBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vUEFURU5UUy50eHQKKi8KLyoqCkBsaWNlbnNlCkNvcHlyaWdodCAoYykgMjAxNiBUaGUgUG9seW1lciBQcm9qZWN0IEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuClRoaXMgY29kZSBtYXkgb25seSBiZSB1c2VkIHVuZGVyIHRoZSBCU0Qgc3R5bGUgbGljZW5zZSBmb3VuZCBhdApodHRwOi8vcG9seW1lci5naXRodWIuaW8vTElDRU5TRS50eHQgVGhlIGNvbXBsZXRlIHNldCBvZiBhdXRob3JzIG1heSBiZSBmb3VuZCBhdApodHRwOi8vcG9seW1lci5naXRodWIuaW8vQVVUSE9SUy50eHQgVGhlIGNvbXBsZXRlIHNldCBvZiBjb250cmlidXRvcnMgbWF5IGJlCmZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9DT05UUklCVVRPUlMudHh0IENvZGUgZGlzdHJpYnV0ZWQgYnkgR29vZ2xlIGFzCnBhcnQgb2YgdGhlIHBvbHltZXIgcHJvamVjdCBpcyBhbHNvIHN1YmplY3QgdG8gYW4gYWRkaXRpb25hbCBJUCByaWdodHMgZ3JhbnQKZm91bmQgYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL1BBVEVOVFMudHh0CiovCi8qKgpAbGljZW5zZQpDb3B5cmlnaHQgKGMpIDIwMTYgVGhlIFBvbHltZXIgUHJvamVjdCBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLgpUaGlzIGNvZGUgbWF5IG9ubHkgYmUgdXNlZCB1bmRlciB0aGUgQlNEIHN0eWxlIGxpY2Vuc2UgZm91bmQgYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0xJQ0VOU0UudHh0ClRoZSBjb21wbGV0ZSBzZXQgb2YgYXV0aG9ycyBtYXkgYmUgZm91bmQgYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0FVVEhPUlMudHh0ClRoZSBjb21wbGV0ZSBzZXQgb2YgY29udHJpYnV0b3JzIG1heSBiZSBmb3VuZCBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vQ09OVFJJQlVUT1JTLnR4dApDb2RlIGRpc3RyaWJ1dGVkIGJ5IEdvb2dsZSBhcyBwYXJ0IG9mIHRoZSBwb2x5bWVyIHByb2plY3QgaXMgYWxzbwpzdWJqZWN0IHRvIGFuIGFkZGl0aW9uYWwgSVAgcmlnaHRzIGdyYW50IGZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9QQVRFTlRTLnR4dAoqLwovKioKQGxpY2Vuc2UKQ29weXJpZ2h0IChjKSAyMDE3IFRoZSBQb2x5bWVyIFByb2plY3QgQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KVGhpcyBjb2RlIG1heSBvbmx5IGJlIHVzZWQgdW5kZXIgdGhlIEJTRCBzdHlsZSBsaWNlbnNlIGZvdW5kIGF0Cmh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9MSUNFTlNFLnR4dCBUaGUgY29tcGxldGUgc2V0IG9mIGF1dGhvcnMgbWF5IGJlIGZvdW5kIGF0Cmh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9BVVRIT1JTLnR4dCBUaGUgY29tcGxldGUgc2V0IG9mIGNvbnRyaWJ1dG9ycyBtYXkgYmUKZm91bmQgYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0NPTlRSSUJVVE9SUy50eHQgQ29kZSBkaXN0cmlidXRlZCBieSBHb29nbGUgYXMKcGFydCBvZiB0aGUgcG9seW1lciBwcm9qZWN0IGlzIGFsc28gc3ViamVjdCB0byBhbiBhZGRpdGlvbmFsIElQIHJpZ2h0cyBncmFudApmb3VuZCBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vUEFURU5UUy50eHQKKi8KLyoqCkBsaWNlbnNlCkNvcHlyaWdodCAoYykgMjAxNyBUaGUgUG9seW1lciBQcm9qZWN0IEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuClRoaXMgY29kZSBtYXkgb25seSBiZSB1c2VkIHVuZGVyIHRoZSBCU0Qgc3R5bGUgbGljZW5zZSBmb3VuZCBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vTElDRU5TRS50eHQKVGhlIGNvbXBsZXRlIHNldCBvZiBhdXRob3JzIG1heSBiZSBmb3VuZCBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vQVVUSE9SUy50eHQKVGhlIGNvbXBsZXRlIHNldCBvZiBjb250cmlidXRvcnMgbWF5IGJlIGZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9DT05UUklCVVRPUlMudHh0CkNvZGUgZGlzdHJpYnV0ZWQgYnkgR29vZ2xlIGFzIHBhcnQgb2YgdGhlIHBvbHltZXIgcHJvamVjdCBpcyBhbHNvCnN1YmplY3QgdG8gYW4gYWRkaXRpb25hbCBJUCByaWdodHMgZ3JhbnQgZm91bmQgYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL1BBVEVOVFMudHh0CiovCi8qKgpAbGljZW5zZQpDb3B5cmlnaHQgKGMpIDIwMTkgVGhlIFBvbHltZXIgUHJvamVjdCBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLgpUaGlzIGNvZGUgbWF5IG9ubHkgYmUgdXNlZCB1bmRlciB0aGUgQlNEIHN0eWxlIGxpY2Vuc2UgZm91bmQgYXQKaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0xJQ0VOU0UudHh0IFRoZSBjb21wbGV0ZSBzZXQgb2YgYXV0aG9ycyBtYXkgYmUgZm91bmQgYXQKaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0FVVEhPUlMudHh0IFRoZSBjb21wbGV0ZSBzZXQgb2YgY29udHJpYnV0b3JzIG1heSBiZQpmb3VuZCBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vQ09OVFJJQlVUT1JTLnR4dCBDb2RlIGRpc3RyaWJ1dGVkIGJ5IEdvb2dsZSBhcwpwYXJ0IG9mIHRoZSBwb2x5bWVyIHByb2plY3QgaXMgYWxzbyBzdWJqZWN0IHRvIGFuIGFkZGl0aW9uYWwgSVAgcmlnaHRzIGdyYW50CmZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9QQVRFTlRTLnR4dAoqLwovKioKQGxpY2Vuc2UKQ29weXJpZ2h0IChjKSAyMDE5IFRoZSBQb2x5bWVyIFByb2plY3QgQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KVGhpcyBjb2RlIG1heSBvbmx5IGJlIHVzZWQgdW5kZXIgdGhlIEJTRCBzdHlsZSBsaWNlbnNlIGZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9MSUNFTlNFLnR4dApUaGUgY29tcGxldGUgc2V0IG9mIGF1dGhvcnMgbWF5IGJlIGZvdW5kIGF0IGh0dHA6Ly9wb2x5bWVyLmdpdGh1Yi5pby9BVVRIT1JTLnR4dApUaGUgY29tcGxldGUgc2V0IG9mIGNvbnRyaWJ1dG9ycyBtYXkgYmUgZm91bmQgYXQgaHR0cDovL3BvbHltZXIuZ2l0aHViLmlvL0NPTlRSSUJVVE9SUy50eHQKQ29kZSBkaXN0cmlidXRlZCBieSBHb29nbGUgYXMgcGFydCBvZiB0aGUgcG9seW1lciBwcm9qZWN0IGlzIGFsc28Kc3ViamVjdCB0byBhbiBhZGRpdGlvbmFsIElQIHJpZ2h0cyBncmFudCBmb3VuZCBhdCBodHRwOi8vcG9seW1lci5naXRodWIuaW8vUEFURU5UUy50eHQKKi8KCigoKT0+e3ZhciBKZ2U9T2JqZWN0LmNyZWF0ZSxMRT1PYmplY3QuZGVmaW5lUHJvcGVydHksJGdlPU9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IsZTBlPU9iamVjdC5nZXRPd25Qcm9wZXJ0eU5hbWVzLHQwZT1PYmplY3QuZ2V0UHJvdG90eXBlT2YsbjBlPU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHksaG89KG4sdCk9PigpPT4obiYmKHQ9bihuPTApKSx0KSxSZD0obix0KT0+KCk9Pih0fHxuKCh0PXtleHBvcnRzOnt9fSkuZXhwb3J0cyx0KSx0LmV4cG9ydHMpLEJFPShuLHQpPT57Zm9yKHZhciBlIGluIHQpTEUobixlLHtnZXQ6dFtlXSxlbnVtZXJhYmxlOiEwfSl9LEk2PShuLHQsZSxpKT0+e2lmKHQmJiJvYmplY3QiPT10eXBlb2YgdHx8ImZ1bmN0aW9uIj09dHlwZW9mIHQpZm9yKGxldCByIG9mIGUwZSh0KSkhbjBlLmNhbGwobixyKSYmciE9PWUmJkxFKG4scix7Z2V0OigpPT50W3JdLGVudW1lcmFibGU6IShpPSRnZSh0LHIpKXx8aS5lbnVtZXJhYmxlfSk7cmV0dXJuIG59LG9OPShuLHQsZSk9PihlPW51bGwhPW4/SmdlKHQwZShuKSk6e30sSTYoIXQmJm4mJm4uX19lc01vZHVsZT9lOkxFKGUsImRlZmF1bHQiLHt2YWx1ZTpuLGVudW1lcmFibGU6ITB9KSxuKSksdVc9UmQoKERydCxlMSk9Pnt2YXIgajYsRzYsVzYscTYsWTYsWDYsUTYsSzYsWjYsSkUseU4sSjYsJDYsZVcsbTAsdFcsblcsaVcsclcsb1csc1csYVcsbFcsY1csJEU7IWZ1bmN0aW9uKG4pe3ZhciB0PSJvYmplY3QiPT10eXBlb2YgZ2xvYmFsP2dsb2JhbDoib2JqZWN0Ij09dHlwZW9mIHNlbGY/c2VsZjoib2JqZWN0Ij09dHlwZW9mIHRoaXM/dGhpczp7fTtmdW5jdGlvbiBlKGkscil7cmV0dXJuIGkhPT10JiYoImZ1bmN0aW9uIj09dHlwZW9mIE9iamVjdC5jcmVhdGU/T2JqZWN0LmRlZmluZVByb3BlcnR5KGksIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pOmkuX19lc01vZHVsZT0hMCksZnVuY3Rpb24obyxzKXtyZXR1cm4gaVtvXT1yP3IobyxzKTpzfX0iZnVuY3Rpb24iPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZSgidHNsaWIiLFsiZXhwb3J0cyJdLGZ1bmN0aW9uKGkpe24oZSh0LGUoaSkpKX0pOm4oIm9iamVjdCI9PXR5cGVvZiBlMSYmIm9iamVjdCI9PXR5cGVvZiBlMS5leHBvcnRzP2UodCxlKGUxLmV4cG9ydHMpKTplKHQpKX0oZnVuY3Rpb24obil7dmFyIHQ9T2JqZWN0LnNldFByb3RvdHlwZU9mfHx7X19wcm90b19fOltdfWluc3RhbmNlb2YgQXJyYXkmJmZ1bmN0aW9uKGkscil7aS5fX3Byb3RvX189cn18fGZ1bmN0aW9uKGkscil7Zm9yKHZhciBvIGluIHIpT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHIsbykmJihpW29dPXJbb10pfTtqNj1mdW5jdGlvbihpLHIpe2lmKCJmdW5jdGlvbiIhPXR5cGVvZiByJiZudWxsIT09cil0aHJvdyBuZXcgVHlwZUVycm9yKCJDbGFzcyBleHRlbmRzIHZhbHVlICIrU3RyaW5nKHIpKyIgaXMgbm90IGEgY29uc3RydWN0b3Igb3IgbnVsbCIpO2Z1bmN0aW9uIG8oKXt0aGlzLmNvbnN0cnVjdG9yPWl9dChpLHIpLGkucHJvdG90eXBlPW51bGw9PT1yP09iamVjdC5jcmVhdGUocik6KG8ucHJvdG90eXBlPXIucHJvdG90eXBlLG5ldyBvKX0sRzY9T2JqZWN0LmFzc2lnbnx8ZnVuY3Rpb24oaSl7Zm9yKHZhciByLG89MSxzPWFyZ3VtZW50cy5sZW5ndGg7bzxzO28rKylmb3IodmFyIGEgaW4gcj1hcmd1bWVudHNbb10pT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHIsYSkmJihpW2FdPXJbYV0pO3JldHVybiBpfSxXNj1mdW5jdGlvbihpLHIpe3ZhciBvPXt9O2Zvcih2YXIgcyBpbiBpKU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChpLHMpJiZyLmluZGV4T2Yocyk8MCYmKG9bc109aVtzXSk7aWYobnVsbCE9aSYmImZ1bmN0aW9uIj09dHlwZW9mIE9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHMpe3ZhciBhPTA7Zm9yKHM9T2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scyhpKTthPHMubGVuZ3RoO2ErKylyLmluZGV4T2Yoc1thXSk8MCYmT2JqZWN0LnByb3RvdHlwZS5wcm9wZXJ0eUlzRW51bWVyYWJsZS5jYWxsKGksc1thXSkmJihvW3NbYV1dPWlbc1thXV0pfXJldHVybiBvfSxxNj1mdW5jdGlvbihpLHIsbyxzKXt2YXIgYyxhPWFyZ3VtZW50cy5sZW5ndGgsbD1hPDM/cjpudWxsPT09cz9zPU9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IocixvKTpzO2lmKCJvYmplY3QiPT10eXBlb2YgUmVmbGVjdCYmImZ1bmN0aW9uIj09dHlwZW9mIFJlZmxlY3QuZGVjb3JhdGUpbD1SZWZsZWN0LmRlY29yYXRlKGkscixvLHMpO2Vsc2UgZm9yKHZhciB1PWkubGVuZ3RoLTE7dT49MDt1LS0pKGM9aVt1XSkmJihsPShhPDM/YyhsKTphPjM/YyhyLG8sbCk6YyhyLG8pKXx8bCk7cmV0dXJuIGE+MyYmbCYmT2JqZWN0LmRlZmluZVByb3BlcnR5KHIsbyxsKSxsfSxZNj1mdW5jdGlvbihpLHIpe3JldHVybiBmdW5jdGlvbihvLHMpe3IobyxzLGkpfX0sWDY9ZnVuY3Rpb24oaSxyKXtpZigib2JqZWN0Ij09dHlwZW9mIFJlZmxlY3QmJiJmdW5jdGlvbiI9PXR5cGVvZiBSZWZsZWN0Lm1ldGFkYXRhKXJldHVybiBSZWZsZWN0Lm1ldGFkYXRhKGkscil9LFE2PWZ1bmN0aW9uKGkscixvLHMpe3JldHVybiBuZXcob3x8KG89UHJvbWlzZSkpKGZ1bmN0aW9uKGwsYyl7ZnVuY3Rpb24gdShoKXt0cnl7cChzLm5leHQoaCkpfWNhdGNoKGYpe2MoZil9fWZ1bmN0aW9uIGQoaCl7dHJ5e3Aocy50aHJvdyhoKSl9Y2F0Y2goZil7YyhmKX19ZnVuY3Rpb24gcChoKXtoLmRvbmU/bChoLnZhbHVlKTpmdW5jdGlvbihsKXtyZXR1cm4gbCBpbnN0YW5jZW9mIG8/bDpuZXcgbyhmdW5jdGlvbihjKXtjKGwpfSl9KGgudmFsdWUpLnRoZW4odSxkKX1wKChzPXMuYXBwbHkoaSxyfHxbXSkpLm5leHQoKSl9KX0sSzY9ZnVuY3Rpb24oaSxyKXt2YXIgcyxhLGwsYyxvPXtsYWJlbDowLHNlbnQ6ZnVuY3Rpb24oKXtpZigxJmxbMF0pdGhyb3cgbFsxXTtyZXR1cm4gbFsxXX0sdHJ5czpbXSxvcHM6W119O3JldHVybiBjPXtuZXh0OnUoMCksdGhyb3c6dSgxKSxyZXR1cm46dSgyKX0sImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmKGNbU3ltYm9sLml0ZXJhdG9yXT1mdW5jdGlvbigpe3JldHVybiB0aGlzfSksYztmdW5jdGlvbiB1KHApe3JldHVybiBmdW5jdGlvbihoKXtyZXR1cm4gZnVuY3Rpb24ocCl7aWYocyl0aHJvdyBuZXcgVHlwZUVycm9yKCJHZW5lcmF0b3IgaXMgYWxyZWFkeSBleGVjdXRpbmcuIik7Zm9yKDtjJiYoYz0wLHBbMF0mJihvPTApKSxvOyl0cnl7aWYocz0xLGEmJihsPTImcFswXT9hLnJldHVybjpwWzBdP2EudGhyb3d8fCgobD1hLnJldHVybikmJmwuY2FsbChhKSwwKTphLm5leHQpJiYhKGw9bC5jYWxsKGEscFsxXSkpLmRvbmUpcmV0dXJuIGw7c3dpdGNoKGE9MCxsJiYocD1bMiZwWzBdLGwudmFsdWVdKSxwWzBdKXtjYXNlIDA6Y2FzZSAxOmw9cDticmVhaztjYXNlIDQ6cmV0dXJuIG8ubGFiZWwrKyx7dmFsdWU6cFsxXSxkb25lOiExfTtjYXNlIDU6by5sYWJlbCsrLGE9cFsxXSxwPVswXTtjb250aW51ZTtjYXNlIDc6cD1vLm9wcy5wb3AoKSxvLnRyeXMucG9wKCk7Y29udGludWU7ZGVmYXVsdDppZighKGw9KGw9by50cnlzKS5sZW5ndGg+MCYmbFtsLmxlbmd0aC0xXSkmJig2PT09cFswXXx8Mj09PXBbMF0pKXtvPTA7Y29udGludWV9aWYoMz09PXBbMF0mJighbHx8cFsxXT5sWzBdJiZwWzFdPGxbM10pKXtvLmxhYmVsPXBbMV07YnJlYWt9aWYoNj09PXBbMF0mJm8ubGFiZWw8bFsxXSl7by5sYWJlbD1sWzFdLGw9cDticmVha31pZihsJiZvLmxhYmVsPGxbMl0pe28ubGFiZWw9bFsyXSxvLm9wcy5wdXNoKHApO2JyZWFrfWxbMl0mJm8ub3BzLnBvcCgpLG8udHJ5cy5wb3AoKTtjb250aW51ZX1wPXIuY2FsbChpLG8pfWNhdGNoKGgpe3A9WzYsaF0sYT0wfWZpbmFsbHl7cz1sPTB9aWYoNSZwWzBdKXRocm93IHBbMV07cmV0dXJue3ZhbHVlOnBbMF0/cFsxXTp2b2lkIDAsZG9uZTohMH19KFtwLGhdKX19fSxaNj1mdW5jdGlvbihpLHIpe2Zvcih2YXIgbyBpbiBpKSJkZWZhdWx0IiE9PW8mJiFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwocixvKSYmJEUocixpLG8pfSwkRT1PYmplY3QuY3JlYXRlP2Z1bmN0aW9uKGkscixvLHMpe3ZvaWQgMD09PXMmJihzPW8pO3ZhciBhPU9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IocixvKTsoIWF8fCgiZ2V0ImluIGE/IXIuX19lc01vZHVsZTphLndyaXRhYmxlfHxhLmNvbmZpZ3VyYWJsZSkpJiYoYT17ZW51bWVyYWJsZTohMCxnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gcltvXX19KSxPYmplY3QuZGVmaW5lUHJvcGVydHkoaSxzLGEpfTpmdW5jdGlvbihpLHIsbyxzKXt2b2lkIDA9PT1zJiYocz1vKSxpW3NdPXJbb119LEpFPWZ1bmN0aW9uKGkpe3ZhciByPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJlN5bWJvbC5pdGVyYXRvcixvPXImJmlbcl0scz0wO2lmKG8pcmV0dXJuIG8uY2FsbChpKTtpZihpJiYibnVtYmVyIj09dHlwZW9mIGkubGVuZ3RoKXJldHVybntuZXh0OmZ1bmN0aW9uKCl7cmV0dXJuIGkmJnM+PWkubGVuZ3RoJiYoaT12b2lkIDApLHt2YWx1ZTppJiZpW3MrK10sZG9uZTohaX19fTt0aHJvdyBuZXcgVHlwZUVycm9yKHI/Ik9iamVjdCBpcyBub3QgaXRlcmFibGUuIjoiU3ltYm9sLml0ZXJhdG9yIGlzIG5vdCBkZWZpbmVkLiIpfSx5Tj1mdW5jdGlvbihpLHIpe3ZhciBvPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmlbU3ltYm9sLml0ZXJhdG9yXTtpZighbylyZXR1cm4gaTt2YXIgYSxjLHM9by5jYWxsKGkpLGw9W107dHJ5e2Zvcig7KHZvaWQgMD09PXJ8fHItLSA+MCkmJiEoYT1zLm5leHQoKSkuZG9uZTspbC5wdXNoKGEudmFsdWUpfWNhdGNoKHUpe2M9e2Vycm9yOnV9fWZpbmFsbHl7dHJ5e2EmJiFhLmRvbmUmJihvPXMucmV0dXJuKSYmby5jYWxsKHMpfWZpbmFsbHl7aWYoYyl0aHJvdyBjLmVycm9yfX1yZXR1cm4gbH0sSjY9ZnVuY3Rpb24oKXtmb3IodmFyIGk9W10scj0wO3I8YXJndW1lbnRzLmxlbmd0aDtyKyspaT1pLmNvbmNhdCh5Tihhcmd1bWVudHNbcl0pKTtyZXR1cm4gaX0sJDY9ZnVuY3Rpb24oKXtmb3IodmFyIGk9MCxyPTAsbz1hcmd1bWVudHMubGVuZ3RoO3I8bztyKyspaSs9YXJndW1lbnRzW3JdLmxlbmd0aDt2YXIgcz1BcnJheShpKSxhPTA7Zm9yKHI9MDtyPG87cisrKWZvcih2YXIgbD1hcmd1bWVudHNbcl0sYz0wLHU9bC5sZW5ndGg7Yzx1O2MrKyxhKyspc1thXT1sW2NdO3JldHVybiBzfSxlVz1mdW5jdGlvbihpLHIsbyl7aWYob3x8Mj09PWFyZ3VtZW50cy5sZW5ndGgpZm9yKHZhciBsLHM9MCxhPXIubGVuZ3RoO3M8YTtzKyspKGx8fCEocyBpbiByKSkmJihsfHwobD1BcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChyLDAscykpLGxbc109cltzXSk7cmV0dXJuIGkuY29uY2F0KGx8fEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKHIpKX0sbTA9ZnVuY3Rpb24oaSl7cmV0dXJuIHRoaXMgaW5zdGFuY2VvZiBtMD8odGhpcy52PWksdGhpcyk6bmV3IG0wKGkpfSx0Vz1mdW5jdGlvbihpLHIsbyl7aWYoIVN5bWJvbC5hc3luY0l0ZXJhdG9yKXRocm93IG5ldyBUeXBlRXJyb3IoIlN5bWJvbC5hc3luY0l0ZXJhdG9yIGlzIG5vdCBkZWZpbmVkLiIpO3ZhciBhLHM9by5hcHBseShpLHJ8fFtdKSxsPVtdO3JldHVybiBhPXt9LGMoIm5leHQiKSxjKCJ0aHJvdyIpLGMoInJldHVybiIpLGFbU3ltYm9sLmFzeW5jSXRlcmF0b3JdPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXN9LGE7ZnVuY3Rpb24gYyhtKXtzW21dJiYoYVttXT1mdW5jdGlvbih4KXtyZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24oZyxiKXtsLnB1c2goW20seCxnLGJdKT4xfHx1KG0seCl9KX0pfWZ1bmN0aW9uIHUobSx4KXt0cnl7IWZ1bmN0aW9uKG0pe20udmFsdWUgaW5zdGFuY2VvZiBtMD9Qcm9taXNlLnJlc29sdmUobS52YWx1ZS52KS50aGVuKHAsaCk6ZihsWzBdWzJdLG0pfShzW21dKHgpKX1jYXRjaChnKXtmKGxbMF1bM10sZyl9fWZ1bmN0aW9uIHAobSl7dSgibmV4dCIsbSl9ZnVuY3Rpb24gaChtKXt1KCJ0aHJvdyIsbSl9ZnVuY3Rpb24gZihtLHgpe20oeCksbC5zaGlmdCgpLGwubGVuZ3RoJiZ1KGxbMF1bMF0sbFswXVsxXSl9fSxuVz1mdW5jdGlvbihpKXt2YXIgcixvO3JldHVybiByPXt9LHMoIm5leHQiKSxzKCJ0aHJvdyIsZnVuY3Rpb24oYSl7dGhyb3cgYX0pLHMoInJldHVybiIpLHJbU3ltYm9sLml0ZXJhdG9yXT1mdW5jdGlvbigpe3JldHVybiB0aGlzfSxyO2Z1bmN0aW9uIHMoYSxsKXtyW2FdPWlbYV0/ZnVuY3Rpb24oYyl7cmV0dXJuKG89IW8pP3t2YWx1ZTptMChpW2FdKGMpKSxkb25lOiJyZXR1cm4iPT09YX06bD9sKGMpOmN9Omx9fSxpVz1mdW5jdGlvbihpKXtpZighU3ltYm9sLmFzeW5jSXRlcmF0b3IpdGhyb3cgbmV3IFR5cGVFcnJvcigiU3ltYm9sLmFzeW5jSXRlcmF0b3IgaXMgbm90IGRlZmluZWQuIik7dmFyIG8scj1pW1N5bWJvbC5hc3luY0l0ZXJhdG9yXTtyZXR1cm4gcj9yLmNhbGwoaSk6KGk9SkUoaSksbz17fSxzKCJuZXh0IikscygidGhyb3ciKSxzKCJyZXR1cm4iKSxvW1N5bWJvbC5hc3luY0l0ZXJhdG9yXT1mdW5jdGlvbigpe3JldHVybiB0aGlzfSxvKTtmdW5jdGlvbiBzKGwpe29bbF09aVtsXSYmZnVuY3Rpb24oYyl7cmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKHUsZCl7IWZ1bmN0aW9uKGwsYyx1LGQpe1Byb21pc2UucmVzb2x2ZShkKS50aGVuKGZ1bmN0aW9uKHApe2woe3ZhbHVlOnAsZG9uZTp1fSl9LGMpfSh1LGQsKGM9aVtsXShjKSkuZG9uZSxjLnZhbHVlKX0pfX19LHJXPWZ1bmN0aW9uKGkscil7cmV0dXJuIE9iamVjdC5kZWZpbmVQcm9wZXJ0eT9PYmplY3QuZGVmaW5lUHJvcGVydHkoaSwicmF3Iix7dmFsdWU6cn0pOmkucmF3PXIsaX07dmFyIGU9T2JqZWN0LmNyZWF0ZT9mdW5jdGlvbihpLHIpe09iamVjdC5kZWZpbmVQcm9wZXJ0eShpLCJkZWZhdWx0Iix7ZW51bWVyYWJsZTohMCx2YWx1ZTpyfSl9OmZ1bmN0aW9uKGkscil7aS5kZWZhdWx0PXJ9O29XPWZ1bmN0aW9uKGkpe2lmKGkmJmkuX19lc01vZHVsZSlyZXR1cm4gaTt2YXIgcj17fTtpZihudWxsIT1pKWZvcih2YXIgbyBpbiBpKSJkZWZhdWx0IiE9PW8mJk9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChpLG8pJiYkRShyLGksbyk7cmV0dXJuIGUocixpKSxyfSxzVz1mdW5jdGlvbihpKXtyZXR1cm4gaSYmaS5fX2VzTW9kdWxlP2k6e2RlZmF1bHQ6aX19LGFXPWZ1bmN0aW9uKGkscixvLHMpe2lmKCJhIj09PW8mJiFzKXRocm93IG5ldyBUeXBlRXJyb3IoIlByaXZhdGUgYWNjZXNzb3Igd2FzIGRlZmluZWQgd2l0aG91dCBhIGdldHRlciIpO2lmKCJmdW5jdGlvbiI9PXR5cGVvZiByP2khPT1yfHwhczohci5oYXMoaSkpdGhyb3cgbmV3IFR5cGVFcnJvcigiQ2Fubm90IHJlYWQgcHJpdmF0ZSBtZW1iZXIgZnJvbSBhbiBvYmplY3Qgd2hvc2UgY2xhc3MgZGlkIG5vdCBkZWNsYXJlIGl0Iik7cmV0dXJuIm0iPT09bz9zOiJhIj09PW8/cy5jYWxsKGkpOnM/cy52YWx1ZTpyLmdldChpKX0sbFc9ZnVuY3Rpb24oaSxyLG8scyxhKXtpZigibSI9PT1zKXRocm93IG5ldyBUeXBlRXJyb3IoIlByaXZhdGUgbWV0aG9kIGlzIG5vdCB3cml0YWJsZSIpO2lmKCJhIj09PXMmJiFhKXRocm93IG5ldyBUeXBlRXJyb3IoIlByaXZhdGUgYWNjZXNzb3Igd2FzIGRlZmluZWQgd2l0aG91dCBhIHNldHRlciIpO2lmKCJmdW5jdGlvbiI9PXR5cGVvZiByP2khPT1yfHwhYTohci5oYXMoaSkpdGhyb3cgbmV3IFR5cGVFcnJvcigiQ2Fubm90IHdyaXRlIHByaXZhdGUgbWVtYmVyIHRvIGFuIG9iamVjdCB3aG9zZSBjbGFzcyBkaWQgbm90IGRlY2xhcmUgaXQiKTtyZXR1cm4iYSI9PT1zP2EuY2FsbChpLG8pOmE/YS52YWx1ZT1vOnIuc2V0KGksbyksb30sY1c9ZnVuY3Rpb24oaSxyKXtpZihudWxsPT09cnx8Im9iamVjdCIhPXR5cGVvZiByJiYiZnVuY3Rpb24iIT10eXBlb2Ygcil0aHJvdyBuZXcgVHlwZUVycm9yKCJDYW5ub3QgdXNlICdpbicgb3BlcmF0b3Igb24gbm9uLW9iamVjdCIpO3JldHVybiJmdW5jdGlvbiI9PXR5cGVvZiBpP3I9PT1pOmkuaGFzKHIpfSxuKCJfX2V4dGVuZHMiLGo2KSxuKCJfX2Fzc2lnbiIsRzYpLG4oIl9fcmVzdCIsVzYpLG4oIl9fZGVjb3JhdGUiLHE2KSxuKCJfX3BhcmFtIixZNiksbigiX19tZXRhZGF0YSIsWDYpLG4oIl9fYXdhaXRlciIsUTYpLG4oIl9fZ2VuZXJhdG9yIixLNiksbigiX19leHBvcnRTdGFyIixaNiksbigiX19jcmVhdGVCaW5kaW5nIiwkRSksbigiX192YWx1ZXMiLEpFKSxuKCJfX3JlYWQiLHlOKSxuKCJfX3NwcmVhZCIsSjYpLG4oIl9fc3ByZWFkQXJyYXlzIiwkNiksbigiX19zcHJlYWRBcnJheSIsZVcpLG4oIl9fYXdhaXQiLG0wKSxuKCJfX2FzeW5jR2VuZXJhdG9yIix0VyksbigiX19hc3luY0RlbGVnYXRvciIsblcpLG4oIl9fYXN5bmNWYWx1ZXMiLGlXKSxuKCJfX21ha2VUZW1wbGF0ZU9iamVjdCIsclcpLG4oIl9faW1wb3J0U3RhciIsb1cpLG4oIl9faW1wb3J0RGVmYXVsdCIsc1cpLG4oIl9fY2xhc3NQcml2YXRlRmllbGRHZXQiLGFXKSxuKCJfX2NsYXNzUHJpdmF0ZUZpZWxkU2V0IixsVyksbigiX19jbGFzc1ByaXZhdGVGaWVsZEluIixjVyl9KX0pLG1aPVJkKGI1PT57InVzZSBzdHJpY3QiO3ZhciBuLHVoPWI1JiZiNS5fX3NwcmVhZEFycmF5fHxmdW5jdGlvbihuLHQsZSl7aWYoZXx8Mj09PWFyZ3VtZW50cy5sZW5ndGgpZm9yKHZhciBvLGk9MCxyPXQubGVuZ3RoO2k8cjtpKyspKG98fCEoaSBpbiB0KSkmJihvfHwobz1BcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbCh0LDAsaSkpLG9baV09dFtpXSk7cmV0dXJuIG4uY29uY2F0KG98fEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKHQpKX07bj1mdW5jdGlvbigpeyFmdW5jdGlvbihRKXt2YXIgcmU9US5wZXJmb3JtYW5jZTtmdW5jdGlvbiBfZShTdCl7cmUmJnJlLm1hcmsmJnJlLm1hcmsoU3QpfWZ1bmN0aW9uIEkoU3Qsd2Upe3JlJiZyZS5tZWFzdXJlJiZyZS5tZWFzdXJlKFN0LHdlKX1fZSgiWm9uZSIpO3ZhciBYPVEuX19ab25lX3N5bWJvbF9wcmVmaXh8fCJfX3pvbmVfc3ltYm9sX18iO2Z1bmN0aW9uICQoU3Qpe3JldHVybiBYK1N0fXZhciBuZT0hMD09PVFbJCgiZm9yY2VEdXBsaWNhdGVab25lQ2hlY2siKV07aWYoUS5ab25lKXtpZihuZXx8ImZ1bmN0aW9uIiE9dHlwZW9mIFEuWm9uZS5fX3N5bWJvbF9fKXRocm93IG5ldyBFcnJvcigiWm9uZSBhbHJlYWR5IGxvYWRlZC4iKTtyZXR1cm4gUS5ab25lfXZhciBtZT1mdW5jdGlvbigpe2Z1bmN0aW9uIFN0KHdlLEVlKXt0aGlzLl9wYXJlbnQ9d2UsdGhpcy5fbmFtZT1FZT9FZS5uYW1lfHwidW5uYW1lZCI6Ijxyb290PiIsdGhpcy5fcHJvcGVydGllcz1FZSYmRWUucHJvcGVydGllc3x8e30sdGhpcy5fem9uZURlbGVnYXRlPW5ldyBsdCh0aGlzLHRoaXMuX3BhcmVudCYmdGhpcy5fcGFyZW50Ll96b25lRGVsZWdhdGUsRWUpfXJldHVybiBTdC5hc3NlcnRab25lUGF0Y2hlZD1mdW5jdGlvbigpe2lmKFEuUHJvbWlzZSE9PWpuLlpvbmVBd2FyZVByb21pc2UpdGhyb3cgbmV3IEVycm9yKCJab25lLmpzIGhhcyBkZXRlY3RlZCB0aGF0IFpvbmVBd2FyZVByb21pc2UgYCh3aW5kb3d8Z2xvYmFsKS5Qcm9taXNlYCBoYXMgYmVlbiBvdmVyd3JpdHRlbi5cbk1vc3QgbGlrZWx5IGNhdXNlIGlzIHRoYXQgYSBQcm9taXNlIHBvbHlmaWxsIGhhcyBiZWVuIGxvYWRlZCBhZnRlciBab25lLmpzIChQb2x5ZmlsbGluZyBQcm9taXNlIGFwaSBpcyBub3QgbmVjZXNzYXJ5IHdoZW4gem9uZS5qcyBpcyBsb2FkZWQuIElmIHlvdSBtdXN0IGxvYWQgb25lLCBkbyBzbyBiZWZvcmUgbG9hZGluZyB6b25lLmpzLikiKX0sT2JqZWN0LmRlZmluZVByb3BlcnR5KFN0LCJyb290Iix7Z2V0OmZ1bmN0aW9uKCl7Zm9yKHZhciB3ZT1TdC5jdXJyZW50O3dlLnBhcmVudDspd2U9d2UucGFyZW50O3JldHVybiB3ZX0sZW51bWVyYWJsZTohMSxjb25maWd1cmFibGU6ITB9KSxPYmplY3QuZGVmaW5lUHJvcGVydHkoU3QsImN1cnJlbnQiLHtnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gYnIuem9uZX0sZW51bWVyYWJsZTohMSxjb25maWd1cmFibGU6ITB9KSxPYmplY3QuZGVmaW5lUHJvcGVydHkoU3QsImN1cnJlbnRUYXNrIix7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIHhhfSxlbnVtZXJhYmxlOiExLGNvbmZpZ3VyYWJsZTohMH0pLFN0Ll9fbG9hZF9wYXRjaD1mdW5jdGlvbih3ZSxFZSxWZSl7aWYodm9pZCAwPT09VmUmJihWZT0hMSksam4uaGFzT3duUHJvcGVydHkod2UpKXtpZighVmUmJm5lKXRocm93IEVycm9yKCJBbHJlYWR5IGxvYWRlZCBwYXRjaDogIit3ZSl9ZWxzZSBpZighUVsiX19ab25lX2Rpc2FibGVfIit3ZV0pe3ZhciBrbj0iWm9uZToiK3dlO19lKGtuKSxqblt3ZV09RWUoUSxTdCxEciksSShrbixrbil9fSxPYmplY3QuZGVmaW5lUHJvcGVydHkoU3QucHJvdG90eXBlLCJwYXJlbnQiLHtnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fcGFyZW50fSxlbnVtZXJhYmxlOiExLGNvbmZpZ3VyYWJsZTohMH0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eShTdC5wcm90b3R5cGUsIm5hbWUiLHtnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5fbmFtZX0sZW51bWVyYWJsZTohMSxjb25maWd1cmFibGU6ITB9KSxTdC5wcm90b3R5cGUuZ2V0PWZ1bmN0aW9uKHdlKXt2YXIgRWU9dGhpcy5nZXRab25lV2l0aCh3ZSk7aWYoRWUpcmV0dXJuIEVlLl9wcm9wZXJ0aWVzW3dlXX0sU3QucHJvdG90eXBlLmdldFpvbmVXaXRoPWZ1bmN0aW9uKHdlKXtmb3IodmFyIEVlPXRoaXM7RWU7KXtpZihFZS5fcHJvcGVydGllcy5oYXNPd25Qcm9wZXJ0eSh3ZSkpcmV0dXJuIEVlO0VlPUVlLl9wYXJlbnR9cmV0dXJuIG51bGx9LFN0LnByb3RvdHlwZS5mb3JrPWZ1bmN0aW9uKHdlKXtpZighd2UpdGhyb3cgbmV3IEVycm9yKCJab25lU3BlYyByZXF1aXJlZCEiKTtyZXR1cm4gdGhpcy5fem9uZURlbGVnYXRlLmZvcmsodGhpcyx3ZSl9LFN0LnByb3RvdHlwZS53cmFwPWZ1bmN0aW9uKHdlLEVlKXtpZigiZnVuY3Rpb24iIT10eXBlb2Ygd2UpdGhyb3cgbmV3IEVycm9yKCJFeHBlY3RpbmcgZnVuY3Rpb24gZ290OiAiK3dlKTt2YXIgVmU9dGhpcy5fem9uZURlbGVnYXRlLmludGVyY2VwdCh0aGlzLHdlLEVlKSxrbj10aGlzO3JldHVybiBmdW5jdGlvbigpe3JldHVybiBrbi5ydW5HdWFyZGVkKFZlLHRoaXMsYXJndW1lbnRzLEVlKX19LFN0LnByb3RvdHlwZS5ydW49ZnVuY3Rpb24od2UsRWUsVmUsa24pe2JyPXtwYXJlbnQ6YnIsem9uZTp0aGlzfTt0cnl7cmV0dXJuIHRoaXMuX3pvbmVEZWxlZ2F0ZS5pbnZva2UodGhpcyx3ZSxFZSxWZSxrbil9ZmluYWxseXticj1ici5wYXJlbnR9fSxTdC5wcm90b3R5cGUucnVuR3VhcmRlZD1mdW5jdGlvbih3ZSxFZSxWZSxrbil7dm9pZCAwPT09RWUmJihFZT1udWxsKSxicj17cGFyZW50OmJyLHpvbmU6dGhpc307dHJ5e3RyeXtyZXR1cm4gdGhpcy5fem9uZURlbGVnYXRlLmludm9rZSh0aGlzLHdlLEVlLFZlLGtuKX1jYXRjaChJcil7aWYodGhpcy5fem9uZURlbGVnYXRlLmhhbmRsZUVycm9yKHRoaXMsSXIpKXRocm93IElyfX1maW5hbGx5e2JyPWJyLnBhcmVudH19LFN0LnByb3RvdHlwZS5ydW5UYXNrPWZ1bmN0aW9uKHdlLEVlLFZlKXtpZih3ZS56b25lIT10aGlzKXRocm93IG5ldyBFcnJvcigiQSB0YXNrIGNhbiBvbmx5IGJlIHJ1biBpbiB0aGUgem9uZSBvZiBjcmVhdGlvbiEgKENyZWF0aW9uOiAiKyh3ZS56b25lfHxPdCkubmFtZSsiOyBFeGVjdXRpb246ICIrdGhpcy5uYW1lKyIpIik7aWYod2Uuc3RhdGUhPT13aXx8d2UudHlwZSE9PVNpJiZ3ZS50eXBlIT09VW4pe3ZhciBrbj13ZS5zdGF0ZSE9JG47a24mJndlLl90cmFuc2l0aW9uVG8oJG4sWW4pLHdlLnJ1bkNvdW50Kys7dmFyIElyPXhhO3hhPXdlLGJyPXtwYXJlbnQ6YnIsem9uZTp0aGlzfTt0cnl7d2UudHlwZT09VW4mJndlLmRhdGEmJiF3ZS5kYXRhLmlzUGVyaW9kaWMmJih3ZS5jYW5jZWxGbj12b2lkIDApO3RyeXtyZXR1cm4gdGhpcy5fem9uZURlbGVnYXRlLmludm9rZVRhc2sodGhpcyx3ZSxFZSxWZSl9Y2F0Y2goeGMpe2lmKHRoaXMuX3pvbmVEZWxlZ2F0ZS5oYW5kbGVFcnJvcih0aGlzLHhjKSl0aHJvdyB4Y319ZmluYWxseXt3ZS5zdGF0ZSE9PXdpJiZ3ZS5zdGF0ZSE9PVlpJiYod2UudHlwZT09U2l8fHdlLmRhdGEmJndlLmRhdGEuaXNQZXJpb2RpYz9rbiYmd2UuX3RyYW5zaXRpb25UbyhZbiwkbik6KHdlLnJ1bkNvdW50PTAsdGhpcy5fdXBkYXRlVGFza0NvdW50KHdlLC0xKSxrbiYmd2UuX3RyYW5zaXRpb25Ubyh3aSwkbix3aSkpKSxicj1ici5wYXJlbnQseGE9SXJ9fX0sU3QucHJvdG90eXBlLnNjaGVkdWxlVGFzaz1mdW5jdGlvbih3ZSl7aWYod2Uuem9uZSYmd2Uuem9uZSE9PXRoaXMpZm9yKHZhciBFZT10aGlzO0VlOyl7aWYoRWU9PT13ZS56b25lKXRocm93IEVycm9yKCJjYW4gbm90IHJlc2NoZWR1bGUgdGFzayB0byAiLmNvbmNhdCh0aGlzLm5hbWUsIiB3aGljaCBpcyBkZXNjZW5kYW50cyBvZiB0aGUgb3JpZ2luYWwgem9uZSAiKS5jb25jYXQod2Uuem9uZS5uYW1lKSk7RWU9RWUucGFyZW50fXdlLl90cmFuc2l0aW9uVG8oYWksd2kpO3ZhciBWZT1bXTt3ZS5fem9uZURlbGVnYXRlcz1WZSx3ZS5fem9uZT10aGlzO3RyeXt3ZT10aGlzLl96b25lRGVsZWdhdGUuc2NoZWR1bGVUYXNrKHRoaXMsd2UpfWNhdGNoKGtuKXt0aHJvdyB3ZS5fdHJhbnNpdGlvblRvKFlpLGFpLHdpKSx0aGlzLl96b25lRGVsZWdhdGUuaGFuZGxlRXJyb3IodGhpcyxrbiksa259cmV0dXJuIHdlLl96b25lRGVsZWdhdGVzPT09VmUmJnRoaXMuX3VwZGF0ZVRhc2tDb3VudCh3ZSwxKSx3ZS5zdGF0ZT09YWkmJndlLl90cmFuc2l0aW9uVG8oWW4sYWkpLHdlfSxTdC5wcm90b3R5cGUuc2NoZWR1bGVNaWNyb1Rhc2s9ZnVuY3Rpb24od2UsRWUsVmUsa24pe3JldHVybiB0aGlzLnNjaGVkdWxlVGFzayhuZXcgSmUoQW4sd2UsRWUsVmUsa24sdm9pZCAwKSl9LFN0LnByb3RvdHlwZS5zY2hlZHVsZU1hY3JvVGFzaz1mdW5jdGlvbih3ZSxFZSxWZSxrbixJcil7cmV0dXJuIHRoaXMuc2NoZWR1bGVUYXNrKG5ldyBKZShVbix3ZSxFZSxWZSxrbixJcikpfSxTdC5wcm90b3R5cGUuc2NoZWR1bGVFdmVudFRhc2s9ZnVuY3Rpb24od2UsRWUsVmUsa24sSXIpe3JldHVybiB0aGlzLnNjaGVkdWxlVGFzayhuZXcgSmUoU2ksd2UsRWUsVmUsa24sSXIpKX0sU3QucHJvdG90eXBlLmNhbmNlbFRhc2s9ZnVuY3Rpb24od2Upe2lmKHdlLnpvbmUhPXRoaXMpdGhyb3cgbmV3IEVycm9yKCJBIHRhc2sgY2FuIG9ubHkgYmUgY2FuY2VsbGVkIGluIHRoZSB6b25lIG9mIGNyZWF0aW9uISAoQ3JlYXRpb246ICIrKHdlLnpvbmV8fE90KS5uYW1lKyI7IEV4ZWN1dGlvbjogIit0aGlzLm5hbWUrIikiKTtpZih3ZS5zdGF0ZT09PVlufHx3ZS5zdGF0ZT09PSRuKXt3ZS5fdHJhbnNpdGlvblRvKFl0LFluLCRuKTt0cnl7dGhpcy5fem9uZURlbGVnYXRlLmNhbmNlbFRhc2sodGhpcyx3ZSl9Y2F0Y2goRWUpe3Rocm93IHdlLl90cmFuc2l0aW9uVG8oWWksWXQpLHRoaXMuX3pvbmVEZWxlZ2F0ZS5oYW5kbGVFcnJvcih0aGlzLEVlKSxFZX1yZXR1cm4gdGhpcy5fdXBkYXRlVGFza0NvdW50KHdlLC0xKSx3ZS5fdHJhbnNpdGlvblRvKHdpLFl0KSx3ZS5ydW5Db3VudD0wLHdlfX0sU3QucHJvdG90eXBlLl91cGRhdGVUYXNrQ291bnQ9ZnVuY3Rpb24od2UsRWUpe3ZhciBWZT13ZS5fem9uZURlbGVnYXRlczstMT09RWUmJih3ZS5fem9uZURlbGVnYXRlcz1udWxsKTtmb3IodmFyIGtuPTA7a248VmUubGVuZ3RoO2tuKyspVmVba25dLl91cGRhdGVUYXNrQ291bnQod2UudHlwZSxFZSl9LFN0fSgpO21lLl9fc3ltYm9sX189JDt2YXIgcnIsS2U9e25hbWU6IiIsb25IYXNUYXNrOmZ1bmN0aW9uKFN0LHdlLEVlLFZlKXtyZXR1cm4gU3QuaGFzVGFzayhFZSxWZSl9LG9uU2NoZWR1bGVUYXNrOmZ1bmN0aW9uKFN0LHdlLEVlLFZlKXtyZXR1cm4gU3Quc2NoZWR1bGVUYXNrKEVlLFZlKX0sb25JbnZva2VUYXNrOmZ1bmN0aW9uKFN0LHdlLEVlLFZlLGtuLElyKXtyZXR1cm4gU3QuaW52b2tlVGFzayhFZSxWZSxrbixJcil9LG9uQ2FuY2VsVGFzazpmdW5jdGlvbihTdCx3ZSxFZSxWZSl7cmV0dXJuIFN0LmNhbmNlbFRhc2soRWUsVmUpfX0sbHQ9ZnVuY3Rpb24oKXtmdW5jdGlvbiBTdCh3ZSxFZSxWZSl7dGhpcy5fdGFza0NvdW50cz17bWljcm9UYXNrOjAsbWFjcm9UYXNrOjAsZXZlbnRUYXNrOjB9LHRoaXMuem9uZT13ZSx0aGlzLl9wYXJlbnREZWxlZ2F0ZT1FZSx0aGlzLl9mb3JrWlM9VmUmJihWZSYmVmUub25Gb3JrP1ZlOkVlLl9mb3JrWlMpLHRoaXMuX2ZvcmtEbGd0PVZlJiYoVmUub25Gb3JrP0VlOkVlLl9mb3JrRGxndCksdGhpcy5fZm9ya0N1cnJab25lPVZlJiYoVmUub25Gb3JrP3RoaXMuem9uZTpFZS5fZm9ya0N1cnJab25lKSx0aGlzLl9pbnRlcmNlcHRaUz1WZSYmKFZlLm9uSW50ZXJjZXB0P1ZlOkVlLl9pbnRlcmNlcHRaUyksdGhpcy5faW50ZXJjZXB0RGxndD1WZSYmKFZlLm9uSW50ZXJjZXB0P0VlOkVlLl9pbnRlcmNlcHREbGd0KSx0aGlzLl9pbnRlcmNlcHRDdXJyWm9uZT1WZSYmKFZlLm9uSW50ZXJjZXB0P3RoaXMuem9uZTpFZS5faW50ZXJjZXB0Q3VyclpvbmUpLHRoaXMuX2ludm9rZVpTPVZlJiYoVmUub25JbnZva2U/VmU6RWUuX2ludm9rZVpTKSx0aGlzLl9pbnZva2VEbGd0PVZlJiYoVmUub25JbnZva2U/RWU6RWUuX2ludm9rZURsZ3QpLHRoaXMuX2ludm9rZUN1cnJab25lPVZlJiYoVmUub25JbnZva2U/dGhpcy56b25lOkVlLl9pbnZva2VDdXJyWm9uZSksdGhpcy5faGFuZGxlRXJyb3JaUz1WZSYmKFZlLm9uSGFuZGxlRXJyb3I/VmU6RWUuX2hhbmRsZUVycm9yWlMpLHRoaXMuX2hhbmRsZUVycm9yRGxndD1WZSYmKFZlLm9uSGFuZGxlRXJyb3I/RWU6RWUuX2hhbmRsZUVycm9yRGxndCksdGhpcy5faGFuZGxlRXJyb3JDdXJyWm9uZT1WZSYmKFZlLm9uSGFuZGxlRXJyb3I/dGhpcy56b25lOkVlLl9oYW5kbGVFcnJvckN1cnJab25lKSx0aGlzLl9zY2hlZHVsZVRhc2taUz1WZSYmKFZlLm9uU2NoZWR1bGVUYXNrP1ZlOkVlLl9zY2hlZHVsZVRhc2taUyksdGhpcy5fc2NoZWR1bGVUYXNrRGxndD1WZSYmKFZlLm9uU2NoZWR1bGVUYXNrP0VlOkVlLl9zY2hlZHVsZVRhc2tEbGd0KSx0aGlzLl9zY2hlZHVsZVRhc2tDdXJyWm9uZT1WZSYmKFZlLm9uU2NoZWR1bGVUYXNrP3RoaXMuem9uZTpFZS5fc2NoZWR1bGVUYXNrQ3VyclpvbmUpLHRoaXMuX2ludm9rZVRhc2taUz1WZSYmKFZlLm9uSW52b2tlVGFzaz9WZTpFZS5faW52b2tlVGFza1pTKSx0aGlzLl9pbnZva2VUYXNrRGxndD1WZSYmKFZlLm9uSW52b2tlVGFzaz9FZTpFZS5faW52b2tlVGFza0RsZ3QpLHRoaXMuX2ludm9rZVRhc2tDdXJyWm9uZT1WZSYmKFZlLm9uSW52b2tlVGFzaz90aGlzLnpvbmU6RWUuX2ludm9rZVRhc2tDdXJyWm9uZSksdGhpcy5fY2FuY2VsVGFza1pTPVZlJiYoVmUub25DYW5jZWxUYXNrP1ZlOkVlLl9jYW5jZWxUYXNrWlMpLHRoaXMuX2NhbmNlbFRhc2tEbGd0PVZlJiYoVmUub25DYW5jZWxUYXNrP0VlOkVlLl9jYW5jZWxUYXNrRGxndCksdGhpcy5fY2FuY2VsVGFza0N1cnJab25lPVZlJiYoVmUub25DYW5jZWxUYXNrP3RoaXMuem9uZTpFZS5fY2FuY2VsVGFza0N1cnJab25lKSx0aGlzLl9oYXNUYXNrWlM9bnVsbCx0aGlzLl9oYXNUYXNrRGxndD1udWxsLHRoaXMuX2hhc1Rhc2tEbGd0T3duZXI9bnVsbCx0aGlzLl9oYXNUYXNrQ3VyclpvbmU9bnVsbDt2YXIga249VmUmJlZlLm9uSGFzVGFzazsoa258fEVlJiZFZS5faGFzVGFza1pTKSYmKHRoaXMuX2hhc1Rhc2taUz1rbj9WZTpLZSx0aGlzLl9oYXNUYXNrRGxndD1FZSx0aGlzLl9oYXNUYXNrRGxndE93bmVyPXRoaXMsdGhpcy5faGFzVGFza0N1cnJab25lPXdlLFZlLm9uU2NoZWR1bGVUYXNrfHwodGhpcy5fc2NoZWR1bGVUYXNrWlM9S2UsdGhpcy5fc2NoZWR1bGVUYXNrRGxndD1FZSx0aGlzLl9zY2hlZHVsZVRhc2tDdXJyWm9uZT10aGlzLnpvbmUpLFZlLm9uSW52b2tlVGFza3x8KHRoaXMuX2ludm9rZVRhc2taUz1LZSx0aGlzLl9pbnZva2VUYXNrRGxndD1FZSx0aGlzLl9pbnZva2VUYXNrQ3VyclpvbmU9dGhpcy56b25lKSxWZS5vbkNhbmNlbFRhc2t8fCh0aGlzLl9jYW5jZWxUYXNrWlM9S2UsdGhpcy5fY2FuY2VsVGFza0RsZ3Q9RWUsdGhpcy5fY2FuY2VsVGFza0N1cnJab25lPXRoaXMuem9uZSkpfXJldHVybiBTdC5wcm90b3R5cGUuZm9yaz1mdW5jdGlvbih3ZSxFZSl7cmV0dXJuIHRoaXMuX2ZvcmtaUz90aGlzLl9mb3JrWlMub25Gb3JrKHRoaXMuX2ZvcmtEbGd0LHRoaXMuem9uZSx3ZSxFZSk6bmV3IG1lKHdlLEVlKX0sU3QucHJvdG90eXBlLmludGVyY2VwdD1mdW5jdGlvbih3ZSxFZSxWZSl7cmV0dXJuIHRoaXMuX2ludGVyY2VwdFpTP3RoaXMuX2ludGVyY2VwdFpTLm9uSW50ZXJjZXB0KHRoaXMuX2ludGVyY2VwdERsZ3QsdGhpcy5faW50ZXJjZXB0Q3VyclpvbmUsd2UsRWUsVmUpOkVlfSxTdC5wcm90b3R5cGUuaW52b2tlPWZ1bmN0aW9uKHdlLEVlLFZlLGtuLElyKXtyZXR1cm4gdGhpcy5faW52b2tlWlM/dGhpcy5faW52b2tlWlMub25JbnZva2UodGhpcy5faW52b2tlRGxndCx0aGlzLl9pbnZva2VDdXJyWm9uZSx3ZSxFZSxWZSxrbixJcik6RWUuYXBwbHkoVmUsa24pfSxTdC5wcm90b3R5cGUuaGFuZGxlRXJyb3I9ZnVuY3Rpb24od2UsRWUpe3JldHVybiF0aGlzLl9oYW5kbGVFcnJvclpTfHx0aGlzLl9oYW5kbGVFcnJvclpTLm9uSGFuZGxlRXJyb3IodGhpcy5faGFuZGxlRXJyb3JEbGd0LHRoaXMuX2hhbmRsZUVycm9yQ3VyclpvbmUsd2UsRWUpfSxTdC5wcm90b3R5cGUuc2NoZWR1bGVUYXNrPWZ1bmN0aW9uKHdlLEVlKXt2YXIgVmU9RWU7aWYodGhpcy5fc2NoZWR1bGVUYXNrWlMpdGhpcy5faGFzVGFza1pTJiZWZS5fem9uZURlbGVnYXRlcy5wdXNoKHRoaXMuX2hhc1Rhc2tEbGd0T3duZXIpLChWZT10aGlzLl9zY2hlZHVsZVRhc2taUy5vblNjaGVkdWxlVGFzayh0aGlzLl9zY2hlZHVsZVRhc2tEbGd0LHRoaXMuX3NjaGVkdWxlVGFza0N1cnJab25lLHdlLEVlKSl8fChWZT1FZSk7ZWxzZSBpZihFZS5zY2hlZHVsZUZuKUVlLnNjaGVkdWxlRm4oRWUpO2Vsc2V7aWYoRWUudHlwZSE9QW4pdGhyb3cgbmV3IEVycm9yKCJUYXNrIGlzIG1pc3Npbmcgc2NoZWR1bGVGbi4iKTtNbihFZSl9cmV0dXJuIFZlfSxTdC5wcm90b3R5cGUuaW52b2tlVGFzaz1mdW5jdGlvbih3ZSxFZSxWZSxrbil7cmV0dXJuIHRoaXMuX2ludm9rZVRhc2taUz90aGlzLl9pbnZva2VUYXNrWlMub25JbnZva2VUYXNrKHRoaXMuX2ludm9rZVRhc2tEbGd0LHRoaXMuX2ludm9rZVRhc2tDdXJyWm9uZSx3ZSxFZSxWZSxrbik6RWUuY2FsbGJhY2suYXBwbHkoVmUsa24pfSxTdC5wcm90b3R5cGUuY2FuY2VsVGFzaz1mdW5jdGlvbih3ZSxFZSl7dmFyIFZlO2lmKHRoaXMuX2NhbmNlbFRhc2taUylWZT10aGlzLl9jYW5jZWxUYXNrWlMub25DYW5jZWxUYXNrKHRoaXMuX2NhbmNlbFRhc2tEbGd0LHRoaXMuX2NhbmNlbFRhc2tDdXJyWm9uZSx3ZSxFZSk7ZWxzZXtpZighRWUuY2FuY2VsRm4pdGhyb3cgRXJyb3IoIlRhc2sgaXMgbm90IGNhbmNlbGFibGUiKTtWZT1FZS5jYW5jZWxGbihFZSl9cmV0dXJuIFZlfSxTdC5wcm90b3R5cGUuaGFzVGFzaz1mdW5jdGlvbih3ZSxFZSl7dHJ5e3RoaXMuX2hhc1Rhc2taUyYmdGhpcy5faGFzVGFza1pTLm9uSGFzVGFzayh0aGlzLl9oYXNUYXNrRGxndCx0aGlzLl9oYXNUYXNrQ3VyclpvbmUsd2UsRWUpfWNhdGNoKFZlKXt0aGlzLmhhbmRsZUVycm9yKHdlLFZlKX19LFN0LnByb3RvdHlwZS5fdXBkYXRlVGFza0NvdW50PWZ1bmN0aW9uKHdlLEVlKXt2YXIgVmU9dGhpcy5fdGFza0NvdW50cyxrbj1WZVt3ZV0sSXI9VmVbd2VdPWtuK0VlO2lmKElyPDApdGhyb3cgbmV3IEVycm9yKCJNb3JlIHRhc2tzIGV4ZWN1dGVkIHRoZW4gd2VyZSBzY2hlZHVsZWQuIik7MCE9a24mJjAhPUlyfHx0aGlzLmhhc1Rhc2sodGhpcy56b25lLHttaWNyb1Rhc2s6VmUubWljcm9UYXNrPjAsbWFjcm9UYXNrOlZlLm1hY3JvVGFzaz4wLGV2ZW50VGFzazpWZS5ldmVudFRhc2s+MCxjaGFuZ2U6d2V9KX0sU3R9KCksSmU9ZnVuY3Rpb24oKXtmdW5jdGlvbiBTdCh3ZSxFZSxWZSxrbixJcix4Yyl7aWYodGhpcy5fem9uZT1udWxsLHRoaXMucnVuQ291bnQ9MCx0aGlzLl96b25lRGVsZWdhdGVzPW51bGwsdGhpcy5fc3RhdGU9Im5vdFNjaGVkdWxlZCIsdGhpcy50eXBlPXdlLHRoaXMuc291cmNlPUVlLHRoaXMuZGF0YT1rbix0aGlzLnNjaGVkdWxlRm49SXIsdGhpcy5jYW5jZWxGbj14YywhVmUpdGhyb3cgbmV3IEVycm9yKCJjYWxsYmFjayBpcyBub3QgZGVmaW5lZCIpO3RoaXMuY2FsbGJhY2s9VmU7dmFyIGN0PXRoaXM7dGhpcy5pbnZva2U9d2U9PT1TaSYma24mJmtuLnVzZUc/U3QuaW52b2tlVGFzazpmdW5jdGlvbigpe3JldHVybiBTdC5pbnZva2VUYXNrLmNhbGwoUSxjdCx0aGlzLGFyZ3VtZW50cyl9fXJldHVybiBTdC5pbnZva2VUYXNrPWZ1bmN0aW9uKHdlLEVlLFZlKXt3ZXx8KHdlPXRoaXMpLFZyKys7dHJ5e3JldHVybiB3ZS5ydW5Db3VudCsrLHdlLnpvbmUucnVuVGFzayh3ZSxFZSxWZSl9ZmluYWxseXsxPT1WciYmSG4oKSxWci0tfX0sT2JqZWN0LmRlZmluZVByb3BlcnR5KFN0LnByb3RvdHlwZSwiem9uZSIse2dldDpmdW5jdGlvbigpe3JldHVybiB0aGlzLl96b25lfSxlbnVtZXJhYmxlOiExLGNvbmZpZ3VyYWJsZTohMH0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eShTdC5wcm90b3R5cGUsInN0YXRlIix7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuX3N0YXRlfSxlbnVtZXJhYmxlOiExLGNvbmZpZ3VyYWJsZTohMH0pLFN0LnByb3RvdHlwZS5jYW5jZWxTY2hlZHVsZVJlcXVlc3Q9ZnVuY3Rpb24oKXt0aGlzLl90cmFuc2l0aW9uVG8od2ksYWkpfSxTdC5wcm90b3R5cGUuX3RyYW5zaXRpb25Ubz1mdW5jdGlvbih3ZSxFZSxWZSl7aWYodGhpcy5fc3RhdGUhPT1FZSYmdGhpcy5fc3RhdGUhPT1WZSl0aHJvdyBuZXcgRXJyb3IoIiIuY29uY2F0KHRoaXMudHlwZSwiICciKS5jb25jYXQodGhpcy5zb3VyY2UsIic6IGNhbiBub3QgdHJhbnNpdGlvbiB0byAnIikuY29uY2F0KHdlLCInLCBleHBlY3Rpbmcgc3RhdGUgJyIpLmNvbmNhdChFZSwiJyIpLmNvbmNhdChWZT8iIG9yICciK1ZlKyInIjoiIiwiLCB3YXMgJyIpLmNvbmNhdCh0aGlzLl9zdGF0ZSwiJy4iKSk7dGhpcy5fc3RhdGU9d2Usd2U9PXdpJiYodGhpcy5fem9uZURlbGVnYXRlcz1udWxsKX0sU3QucHJvdG90eXBlLnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZGF0YSYmdHlwZW9mIHRoaXMuZGF0YS5oYW5kbGVJZDwidSI/dGhpcy5kYXRhLmhhbmRsZUlkLnRvU3RyaW5nKCk6T2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKHRoaXMpfSxTdC5wcm90b3R5cGUudG9KU09OPWZ1bmN0aW9uKCl7cmV0dXJue3R5cGU6dGhpcy50eXBlLHN0YXRlOnRoaXMuc3RhdGUsc291cmNlOnRoaXMuc291cmNlLHpvbmU6dGhpcy56b25lLm5hbWUscnVuQ291bnQ6dGhpcy5ydW5Db3VudH19LFN0fSgpLGZ0PSQoInNldFRpbWVvdXQiKSxDdD0kKCJQcm9taXNlIiksSXQ9JCgidGhlbiIpLE50PVtdLGJuPSExO2Z1bmN0aW9uIEFpKFN0KXtpZihycnx8UVtDdF0mJihycj1RW0N0XS5yZXNvbHZlKDApKSxycil7dmFyIHdlPXJyW0l0XTt3ZXx8KHdlPXJyLnRoZW4pLHdlLmNhbGwocnIsU3QpfWVsc2UgUVtmdF0oU3QsMCl9ZnVuY3Rpb24gTW4oU3QpezA9PT1WciYmMD09PU50Lmxlbmd0aCYmQWkoSG4pLFN0JiZOdC5wdXNoKFN0KX1mdW5jdGlvbiBIbigpe2lmKCFibil7Zm9yKGJuPSEwO050Lmxlbmd0aDspe3ZhciBTdD1OdDtOdD1bXTtmb3IodmFyIHdlPTA7d2U8U3QubGVuZ3RoO3dlKyspe3ZhciBFZT1TdFt3ZV07dHJ5e0VlLnpvbmUucnVuVGFzayhFZSxudWxsLG51bGwpfWNhdGNoKFZlKXtEci5vblVuaGFuZGxlZEVycm9yKFZlKX19fURyLm1pY3JvdGFza0RyYWluRG9uZSgpLGJuPSExfX12YXIgT3Q9e25hbWU6Ik5PIFpPTkUifSx3aT0ibm90U2NoZWR1bGVkIixhaT0ic2NoZWR1bGluZyIsWW49InNjaGVkdWxlZCIsJG49InJ1bm5pbmciLFl0PSJjYW5jZWxpbmciLFlpPSJ1bmtub3duIixBbj0ibWljcm9UYXNrIixVbj0ibWFjcm9UYXNrIixTaT0iZXZlbnRUYXNrIixqbj17fSxEcj17c3ltYm9sOiQsY3VycmVudFpvbmVGcmFtZTpmdW5jdGlvbigpe3JldHVybiBicn0sb25VbmhhbmRsZWRFcnJvcjpBcixtaWNyb3Rhc2tEcmFpbkRvbmU6QXIsc2NoZWR1bGVNaWNyb1Rhc2s6TW4sc2hvd1VuY2F1Z2h0RXJyb3I6ZnVuY3Rpb24oKXtyZXR1cm4hbWVbJCgiaWdub3JlQ29uc29sZUVycm9yVW5jYXVnaHRFcnJvciIpXX0scGF0Y2hFdmVudFRhcmdldDpmdW5jdGlvbigpe3JldHVybltdfSxwYXRjaE9uUHJvcGVydGllczpBcixwYXRjaE1ldGhvZDpmdW5jdGlvbigpe3JldHVybiBBcn0sYmluZEFyZ3VtZW50czpmdW5jdGlvbigpe3JldHVybltdfSxwYXRjaFRoZW46ZnVuY3Rpb24oKXtyZXR1cm4gQXJ9LHBhdGNoTWFjcm9UYXNrOmZ1bmN0aW9uKCl7cmV0dXJuIEFyfSxwYXRjaEV2ZW50UHJvdG90eXBlOmZ1bmN0aW9uKCl7cmV0dXJuIEFyfSxpc0lFT3JFZGdlOmZ1bmN0aW9uKCl7cmV0dXJuITF9LGdldEdsb2JhbE9iamVjdHM6ZnVuY3Rpb24oKXt9LE9iamVjdERlZmluZVByb3BlcnR5OmZ1bmN0aW9uKCl7cmV0dXJuIEFyfSxPYmplY3RHZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3I6ZnVuY3Rpb24oKXt9LE9iamVjdENyZWF0ZTpmdW5jdGlvbigpe30sQXJyYXlTbGljZTpmdW5jdGlvbigpe3JldHVybltdfSxwYXRjaENsYXNzOmZ1bmN0aW9uKCl7cmV0dXJuIEFyfSx3cmFwV2l0aEN1cnJlbnRab25lOmZ1bmN0aW9uKCl7cmV0dXJuIEFyfSxmaWx0ZXJQcm9wZXJ0aWVzOmZ1bmN0aW9uKCl7cmV0dXJuW119LGF0dGFjaE9yaWdpblRvUGF0Y2hlZDpmdW5jdGlvbigpe3JldHVybiBBcn0sX3JlZGVmaW5lUHJvcGVydHk6ZnVuY3Rpb24oKXtyZXR1cm4gQXJ9LHBhdGNoQ2FsbGJhY2tzOmZ1bmN0aW9uKCl7cmV0dXJuIEFyfSxuYXRpdmVTY2hlZHVsZU1pY3JvVGFzazpBaX0sYnI9e3BhcmVudDpudWxsLHpvbmU6bmV3IG1lKG51bGwsbnVsbCl9LHhhPW51bGwsVnI9MDtmdW5jdGlvbiBBcigpe31JKCJab25lIiwiWm9uZSIpLFEuWm9uZT1tZX0odHlwZW9mIHdpbmRvdzwidSImJndpbmRvd3x8dHlwZW9mIHNlbGY8InUiJiZzZWxmfHxnbG9iYWwpO3ZhciBuPU9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IsdD1PYmplY3QuZGVmaW5lUHJvcGVydHksZT1PYmplY3QuZ2V0UHJvdG90eXBlT2YsaT1PYmplY3QuY3JlYXRlLHI9QXJyYXkucHJvdG90eXBlLnNsaWNlLG89ImFkZEV2ZW50TGlzdGVuZXIiLHM9InJlbW92ZUV2ZW50TGlzdGVuZXIiLGE9Wm9uZS5fX3N5bWJvbF9fKG8pLGw9Wm9uZS5fX3N5bWJvbF9fKHMpLGM9InRydWUiLHU9ImZhbHNlIixkPVpvbmUuX19zeW1ib2xfXygiIik7ZnVuY3Rpb24gcChRLHJlKXtyZXR1cm4gWm9uZS5jdXJyZW50LndyYXAoUSxyZSl9ZnVuY3Rpb24gaChRLHJlLF9lLEksWCl7cmV0dXJuIFpvbmUuY3VycmVudC5zY2hlZHVsZU1hY3JvVGFzayhRLHJlLF9lLEksWCl9dmFyIGY9Wm9uZS5fX3N5bWJvbF9fLG09dHlwZW9mIHdpbmRvdzwidSIseD1tP3dpbmRvdzp2b2lkIDAsZz1tJiZ4fHwib2JqZWN0Ij09dHlwZW9mIHNlbGYmJnNlbGZ8fGdsb2JhbDtmdW5jdGlvbiBEKFEscmUpe2Zvcih2YXIgX2U9US5sZW5ndGgtMTtfZT49MDtfZS0tKSJmdW5jdGlvbiI9PXR5cGVvZiBRW19lXSYmKFFbX2VdPXAoUVtfZV0scmUrIl8iK19lKSk7cmV0dXJuIFF9ZnVuY3Rpb24gayhRKXtyZXR1cm4hUXx8ITEhPT1RLndyaXRhYmxlJiYhKCJmdW5jdGlvbiI9PXR5cGVvZiBRLmdldCYmdHlwZW9mIFEuc2V0PiJ1Iil9dmFyIFo9dHlwZW9mIFdvcmtlckdsb2JhbFNjb3BlPCJ1IiYmc2VsZiBpbnN0YW5jZW9mIFdvcmtlckdsb2JhbFNjb3BlLHo9ISgibnciaW4gZykmJnR5cGVvZiBnLnByb2Nlc3M8InUiJiYiW29iamVjdCBwcm9jZXNzXSI9PT17fS50b1N0cmluZy5jYWxsKGcucHJvY2VzcyksZmU9IXomJiFaJiYhKCFtfHwheC5IVE1MRWxlbWVudCksdWU9dHlwZW9mIGcucHJvY2VzczwidSImJiJbb2JqZWN0IHByb2Nlc3NdIj09PXt9LnRvU3RyaW5nLmNhbGwoZy5wcm9jZXNzKSYmIVomJiEoIW18fCF4LkhUTUxFbGVtZW50KSxoZT17fSx3PWZ1bmN0aW9uKFEpe2lmKFE9UXx8Zy5ldmVudCl7dmFyIHJlPWhlW1EudHlwZV07cmV8fChyZT1oZVtRLnR5cGVdPWYoIk9OX1BST1BFUlRZIitRLnR5cGUpKTt2YXIgWCxfZT10aGlzfHxRLnRhcmdldHx8ZyxJPV9lW3JlXTtpZihmZSYmX2U9PT14JiYiZXJyb3IiPT09US50eXBlKXt2YXIgJD1ROyEwPT09KFg9SSYmSS5jYWxsKHRoaXMsJC5tZXNzYWdlLCQuZmlsZW5hbWUsJC5saW5lbm8sJC5jb2xubywkLmVycm9yKSkmJlEucHJldmVudERlZmF1bHQoKX1lbHNlIG51bGwhPShYPUkmJkkuYXBwbHkodGhpcyxhcmd1bWVudHMpKSYmIVgmJlEucHJldmVudERlZmF1bHQoKTtyZXR1cm4gWH19O2Z1bmN0aW9uIEYoUSxyZSxfZSl7dmFyIEk9bihRLHJlKTtpZighSSYmX2UmJm4oX2UscmUpJiYoST17ZW51bWVyYWJsZTohMCxjb25maWd1cmFibGU6ITB9KSxJJiZJLmNvbmZpZ3VyYWJsZSl7dmFyICQ9Zigib24iK3JlKyJwYXRjaGVkIik7aWYoIVEuaGFzT3duUHJvcGVydHkoJCl8fCFRWyRdKXtkZWxldGUgSS53cml0YWJsZSxkZWxldGUgSS52YWx1ZTt2YXIgbmU9SS5nZXQsbWU9SS5zZXQsS2U9cmUuc2xpY2UoMiksbHQ9aGVbS2VdO2x0fHwobHQ9aGVbS2VdPWYoIk9OX1BST1BFUlRZIitLZSkpLEkuc2V0PWZ1bmN0aW9uKEplKXt2YXIgZnQ9dGhpczshZnQmJlE9PT1nJiYoZnQ9ZyksZnQmJigiZnVuY3Rpb24iPT10eXBlb2YgZnRbbHRdJiZmdC5yZW1vdmVFdmVudExpc3RlbmVyKEtlLHcpLG1lJiZtZS5jYWxsKGZ0LG51bGwpLGZ0W2x0XT1KZSwiZnVuY3Rpb24iPT10eXBlb2YgSmUmJmZ0LmFkZEV2ZW50TGlzdGVuZXIoS2UsdywhMSkpfSxJLmdldD1mdW5jdGlvbigpe3ZhciBKZT10aGlzO2lmKCFKZSYmUT09PWcmJihKZT1nKSwhSmUpcmV0dXJuIG51bGw7dmFyIGZ0PUplW2x0XTtpZihmdClyZXR1cm4gZnQ7aWYobmUpe3ZhciBDdD1uZS5jYWxsKHRoaXMpO2lmKEN0KXJldHVybiBJLnNldC5jYWxsKHRoaXMsQ3QpLCJmdW5jdGlvbiI9PXR5cGVvZiBKZS5yZW1vdmVBdHRyaWJ1dGUmJkplLnJlbW92ZUF0dHJpYnV0ZShyZSksQ3R9cmV0dXJuIG51bGx9LHQoUSxyZSxJKSxRWyRdPSEwfX19ZnVuY3Rpb24gcShRLHJlLF9lKXtpZihyZSlmb3IodmFyIEk9MDtJPHJlLmxlbmd0aDtJKyspRihRLCJvbiIrcmVbSV0sX2UpO2Vsc2V7dmFyIFg9W107Zm9yKHZhciAkIGluIFEpIm9uIj09JC5zbGljZSgwLDIpJiZYLnB1c2goJCk7Zm9yKHZhciBuZT0wO25lPFgubGVuZ3RoO25lKyspRihRLFhbbmVdLF9lKX19dmFyIEs9Zigib3JpZ2luYWxJbnN0YW5jZSIpO2Z1bmN0aW9uIGRlKFEpe3ZhciByZT1nW1FdO2lmKHJlKXtnW2YoUSldPXJlLGdbUV09ZnVuY3Rpb24oKXt2YXIgWD1EKGFyZ3VtZW50cyxRKTtzd2l0Y2goWC5sZW5ndGgpe2Nhc2UgMDp0aGlzW0tdPW5ldyByZTticmVhaztjYXNlIDE6dGhpc1tLXT1uZXcgcmUoWFswXSk7YnJlYWs7Y2FzZSAyOnRoaXNbS109bmV3IHJlKFhbMF0sWFsxXSk7YnJlYWs7Y2FzZSAzOnRoaXNbS109bmV3IHJlKFhbMF0sWFsxXSxYWzJdKTticmVhaztjYXNlIDQ6dGhpc1tLXT1uZXcgcmUoWFswXSxYWzFdLFhbMl0sWFszXSk7YnJlYWs7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoIkFyZyBsaXN0IHRvbyBsb25nLiIpfX0sbGUoZ1tRXSxyZSk7dmFyIEksX2U9bmV3IHJlKGZ1bmN0aW9uKCl7fSk7Zm9yKEkgaW4gX2UpIlhNTEh0dHBSZXF1ZXN0Ij09PVEmJiJyZXNwb25zZUJsb2IiPT09SXx8ZnVuY3Rpb24oWCl7ImZ1bmN0aW9uIj09dHlwZW9mIF9lW1hdP2dbUV0ucHJvdG90eXBlW1hdPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXNbS11bWF0uYXBwbHkodGhpc1tLXSxhcmd1bWVudHMpfTp0KGdbUV0ucHJvdG90eXBlLFgse3NldDpmdW5jdGlvbigkKXsiZnVuY3Rpb24iPT10eXBlb2YgJD8odGhpc1tLXVtYXT1wKCQsUSsiLiIrWCksbGUodGhpc1tLXVtYXSwkKSk6dGhpc1tLXVtYXT0kfSxnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpc1tLXVtYXX19KX0oSSk7Zm9yKEkgaW4gcmUpInByb3RvdHlwZSIhPT1JJiZyZS5oYXNPd25Qcm9wZXJ0eShJKSYmKGdbUV1bSV09cmVbSV0pfX1mdW5jdGlvbiBZKFEscmUsX2Upe2Zvcih2YXIgST1RO0kmJiFJLmhhc093blByb3BlcnR5KHJlKTspST1lKEkpOyFJJiZRW3JlXSYmKEk9USk7dmFyIFg9ZihyZSksJD1udWxsO2lmKEkmJighKCQ9SVtYXSl8fCFJLmhhc093blByb3BlcnR5KFgpKSYmKCQ9SVtYXT1JW3JlXSxrKEkmJm4oSSxyZSkpKSl7dmFyIG1lPV9lKCQsWCxyZSk7SVtyZV09ZnVuY3Rpb24oKXtyZXR1cm4gbWUodGhpcyxhcmd1bWVudHMpfSxsZShJW3JlXSwkKX1yZXR1cm4gJH1mdW5jdGlvbiBhZShRLHJlLF9lKXt2YXIgST1udWxsO2Z1bmN0aW9uIFgoJCl7dmFyIG5lPSQuZGF0YTtyZXR1cm4gbmUuYXJnc1tuZS5jYklkeF09ZnVuY3Rpb24oKXskLmludm9rZS5hcHBseSh0aGlzLGFyZ3VtZW50cyl9LEkuYXBwbHkobmUudGFyZ2V0LG5lLmFyZ3MpLCR9ST1ZKFEscmUsZnVuY3Rpb24oJCl7cmV0dXJuIGZ1bmN0aW9uKG5lLG1lKXt2YXIgS2U9X2UobmUsbWUpO3JldHVybiBLZS5jYklkeD49MCYmImZ1bmN0aW9uIj09dHlwZW9mIG1lW0tlLmNiSWR4XT9oKEtlLm5hbWUsbWVbS2UuY2JJZHhdLEtlLFgpOiQuYXBwbHkobmUsbWUpfX0pfWZ1bmN0aW9uIGxlKFEscmUpe1FbZigiT3JpZ2luYWxEZWxlZ2F0ZSIpXT1yZX12YXIgSWU9ITEsdmU9ITE7ZnVuY3Rpb24gbnQoKXtpZihJZSlyZXR1cm4gdmU7SWU9ITA7dHJ5e3ZhciBRPXgubmF2aWdhdG9yLnVzZXJBZ2VudDsoLTEhPT1RLmluZGV4T2YoIk1TSUUgIil8fC0xIT09US5pbmRleE9mKCJUcmlkZW50LyIpfHwtMSE9PVEuaW5kZXhPZigiRWRnZS8iKSkmJih2ZT0hMCl9Y2F0Y2h7fXJldHVybiB2ZX1ab25lLl9fbG9hZF9wYXRjaCgiWm9uZUF3YXJlUHJvbWlzZSIsZnVuY3Rpb24oUSxyZSxfZSl7dmFyIEk9T2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcixYPU9iamVjdC5kZWZpbmVQcm9wZXJ0eSxuZT1fZS5zeW1ib2wsbWU9W10sS2U9ITA9PT1RW25lKCJESVNBQkxFX1dSQVBQSU5HX1VOQ0FVR0hUX1BST01JU0VfUkVKRUNUSU9OIildLGx0PW5lKCJQcm9taXNlIiksSmU9bmUoInRoZW4iKTtfZS5vblVuaGFuZGxlZEVycm9yPWZ1bmN0aW9uKGN0KXtpZihfZS5zaG93VW5jYXVnaHRFcnJvcigpKXt2YXIgVnQ9Y3QmJmN0LnJlamVjdGlvbjtWdD9jb25zb2xlLmVycm9yKCJVbmhhbmRsZWQgUHJvbWlzZSByZWplY3Rpb246IixWdCBpbnN0YW5jZW9mIEVycm9yP1Z0Lm1lc3NhZ2U6VnQsIjsgWm9uZToiLGN0LnpvbmUubmFtZSwiOyBUYXNrOiIsY3QudGFzayYmY3QudGFzay5zb3VyY2UsIjsgVmFsdWU6IixWdCxWdCBpbnN0YW5jZW9mIEVycm9yP1Z0LnN0YWNrOnZvaWQgMCk6Y29uc29sZS5lcnJvcihjdCl9fSxfZS5taWNyb3Rhc2tEcmFpbkRvbmU9ZnVuY3Rpb24oKXtmb3IodmFyIGN0PWZ1bmN0aW9uKCl7dmFyIFZ0PW1lLnNoaWZ0KCk7dHJ5e1Z0LnpvbmUucnVuR3VhcmRlZChmdW5jdGlvbigpe3Rocm93IFZ0LnRocm93T3JpZ2luYWw/VnQucmVqZWN0aW9uOlZ0fSl9Y2F0Y2goRHQpeyFmdW5jdGlvbihjdCl7X2Uub25VbmhhbmRsZWRFcnJvcihjdCk7dHJ5e3ZhciBWdD1yZVtDdF07ImZ1bmN0aW9uIj09dHlwZW9mIFZ0JiZWdC5jYWxsKHRoaXMsY3QpfWNhdGNoe319KER0KX19O21lLmxlbmd0aDspY3QoKX07dmFyIEN0PW5lKCJ1bmhhbmRsZWRQcm9taXNlUmVqZWN0aW9uSGFuZGxlciIpO2Z1bmN0aW9uIE50KGN0KXtyZXR1cm4gY3QmJmN0LnRoZW59ZnVuY3Rpb24gYm4oY3Qpe3JldHVybiBjdH1mdW5jdGlvbiBycihjdCl7cmV0dXJuIEVlLnJlamVjdChjdCl9dmFyIEFpPW5lKCJzdGF0ZSIpLE1uPW5lKCJ2YWx1ZSIpLEhuPW5lKCJmaW5hbGx5IiksT3Q9bmUoInBhcmVudFByb21pc2VWYWx1ZSIpLHdpPW5lKCJwYXJlbnRQcm9taXNlU3RhdGUiKSxZbj1udWxsLFl0PSExO2Z1bmN0aW9uIEFuKGN0LFZ0KXtyZXR1cm4gZnVuY3Rpb24oRHQpe3RyeXtEcihjdCxWdCxEdCl9Y2F0Y2goaXQpe0RyKGN0LCExLGl0KX19fXZhciBVbj1mdW5jdGlvbigpe3ZhciBjdD0hMTtyZXR1cm4gZnVuY3Rpb24oRHQpe3JldHVybiBmdW5jdGlvbigpe2N0fHwoY3Q9ITAsRHQuYXBwbHkobnVsbCxhcmd1bWVudHMpKX19fSxqbj1uZSgiY3VycmVudFRhc2tUcmFjZSIpO2Z1bmN0aW9uIERyKGN0LFZ0LER0KXt2YXIgaXQ9VW4oKTtpZihjdD09PUR0KXRocm93IG5ldyBUeXBlRXJyb3IoIlByb21pc2UgcmVzb2x2ZWQgd2l0aCBpdHNlbGYiKTtpZihjdFtBaV09PT1Zbil7dmFyIGNuPW51bGw7dHJ5eygib2JqZWN0Ij09dHlwZW9mIER0fHwiZnVuY3Rpb24iPT10eXBlb2YgRHQpJiYoY249RHQmJkR0LnRoZW4pfWNhdGNoKG1pKXtyZXR1cm4gaXQoZnVuY3Rpb24oKXtEcihjdCwhMSxtaSl9KSgpLGN0fWlmKFZ0IT09WXQmJkR0IGluc3RhbmNlb2YgRWUmJkR0Lmhhc093blByb3BlcnR5KEFpKSYmRHQuaGFzT3duUHJvcGVydHkoTW4pJiZEdFtBaV0hPT1Zbil4YShEdCksRHIoY3QsRHRbQWldLER0W01uXSk7ZWxzZSBpZihWdCE9PVl0JiYiZnVuY3Rpb24iPT10eXBlb2YgY24pdHJ5e2NuLmNhbGwoRHQsaXQoQW4oY3QsVnQpKSxpdChBbihjdCwhMSkpKX1jYXRjaChtaSl7aXQoZnVuY3Rpb24oKXtEcihjdCwhMSxtaSl9KSgpfWVsc2V7Y3RbQWldPVZ0O3ZhciBxbj1jdFtNbl07aWYoY3RbTW5dPUR0LGN0W0huXT09PUhuJiYhMD09PVZ0JiYoY3RbQWldPWN0W3dpXSxjdFtNbl09Y3RbT3RdKSxWdD09PVl0JiZEdCBpbnN0YW5jZW9mIEVycm9yKXt2YXIgQm49cmUuY3VycmVudFRhc2smJnJlLmN1cnJlbnRUYXNrLmRhdGEmJnJlLmN1cnJlbnRUYXNrLmRhdGEuX19jcmVhdGlvblRyYWNlX187Qm4mJlgoRHQsam4se2NvbmZpZ3VyYWJsZTohMCxlbnVtZXJhYmxlOiExLHdyaXRhYmxlOiEwLHZhbHVlOkJufSl9Zm9yKHZhciBsaT0wO2xpPHFuLmxlbmd0aDspVnIoY3QscW5bbGkrK10scW5bbGkrK10scW5bbGkrK10scW5bbGkrK10pO2lmKDA9PXFuLmxlbmd0aCYmVnQ9PVl0KXtjdFtBaV09MDt2YXIgY2k9RHQ7dHJ5e3Rocm93IG5ldyBFcnJvcigiVW5jYXVnaHQgKGluIHByb21pc2UpOiAiK2Z1bmN0aW9uKGN0KXtyZXR1cm4gY3QmJmN0LnRvU3RyaW5nPT09T2JqZWN0LnByb3RvdHlwZS50b1N0cmluZz8oY3QuY29uc3RydWN0b3ImJmN0LmNvbnN0cnVjdG9yLm5hbWV8fCIiKSsiOiAiK0pTT04uc3RyaW5naWZ5KGN0KTpjdD9jdC50b1N0cmluZygpOk9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChjdCl9KER0KSsoRHQmJkR0LnN0YWNrPyJcbiIrRHQuc3RhY2s6IiIpKX1jYXRjaChtaSl7Y2k9bWl9S2UmJihjaS50aHJvd09yaWdpbmFsPSEwKSxjaS5yZWplY3Rpb249RHQsY2kucHJvbWlzZT1jdCxjaS56b25lPXJlLmN1cnJlbnQsY2kudGFzaz1yZS5jdXJyZW50VGFzayxtZS5wdXNoKGNpKSxfZS5zY2hlZHVsZU1pY3JvVGFzaygpfX19cmV0dXJuIGN0fXZhciBicj1uZSgicmVqZWN0aW9uSGFuZGxlZEhhbmRsZXIiKTtmdW5jdGlvbiB4YShjdCl7aWYoMD09PWN0W0FpXSl7dHJ5e3ZhciBWdD1yZVticl07VnQmJiJmdW5jdGlvbiI9PXR5cGVvZiBWdCYmVnQuY2FsbCh0aGlzLHtyZWplY3Rpb246Y3RbTW5dLHByb21pc2U6Y3R9KX1jYXRjaHt9Y3RbQWldPVl0O2Zvcih2YXIgRHQ9MDtEdDxtZS5sZW5ndGg7RHQrKyljdD09PW1lW0R0XS5wcm9taXNlJiZtZS5zcGxpY2UoRHQsMSl9fWZ1bmN0aW9uIFZyKGN0LFZ0LER0LGl0LGNuKXt4YShjdCk7dmFyIHFuPWN0W0FpXSxCbj1xbj8iZnVuY3Rpb24iPT10eXBlb2YgaXQ/aXQ6Ym46ImZ1bmN0aW9uIj09dHlwZW9mIGNuP2NuOnJyO1Z0LnNjaGVkdWxlTWljcm9UYXNrKCJQcm9taXNlLnRoZW4iLGZ1bmN0aW9uKCl7dHJ5e3ZhciBsaT1jdFtNbl0sY2k9ISFEdCYmSG49PT1EdFtIbl07Y2kmJihEdFtPdF09bGksRHRbd2ldPXFuKTt2YXIgbWk9VnQucnVuKEJuLHZvaWQgMCxjaSYmQm4hPT1yciYmQm4hPT1ibj9bXTpbbGldKTtEcihEdCwhMCxtaSl9Y2F0Y2goSWkpe0RyKER0LCExLElpKX19LER0KX12YXIgU3Q9ZnVuY3Rpb24oKXt9LHdlPVEuQWdncmVnYXRlRXJyb3IsRWU9ZnVuY3Rpb24oKXtmdW5jdGlvbiBjdChWdCl7dmFyIER0PXRoaXM7aWYoIShEdCBpbnN0YW5jZW9mIGN0KSl0aHJvdyBuZXcgRXJyb3IoIk11c3QgYmUgYW4gaW5zdGFuY2VvZiBQcm9taXNlLiIpO0R0W0FpXT1ZbixEdFtNbl09W107dHJ5e3ZhciBpdD1VbigpO1Z0JiZWdChpdChBbihEdCwhMCkpLGl0KEFuKER0LFl0KSkpfWNhdGNoKGNuKXtEcihEdCwhMSxjbil9fXJldHVybiBjdC50b1N0cmluZz1mdW5jdGlvbigpe3JldHVybiJmdW5jdGlvbiBab25lQXdhcmVQcm9taXNlKCkgeyBbbmF0aXZlIGNvZGVdIH0ifSxjdC5yZXNvbHZlPWZ1bmN0aW9uKFZ0KXtyZXR1cm4gRHIobmV3IHRoaXMobnVsbCksITAsVnQpfSxjdC5yZWplY3Q9ZnVuY3Rpb24oVnQpe3JldHVybiBEcihuZXcgdGhpcyhudWxsKSxZdCxWdCl9LGN0LmFueT1mdW5jdGlvbihWdCl7aWYoIVZ0fHwiZnVuY3Rpb24iIT10eXBlb2YgVnRbU3ltYm9sLml0ZXJhdG9yXSlyZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IHdlKFtdLCJBbGwgcHJvbWlzZXMgd2VyZSByZWplY3RlZCIpKTt2YXIgRHQ9W10saXQ9MDt0cnl7Zm9yKHZhciBjbj0wLHFuPVZ0O2NuPHFuLmxlbmd0aDtjbisrKWl0KyssRHQucHVzaChjdC5yZXNvbHZlKHFuW2NuXSkpfWNhdGNoe3JldHVybiBQcm9taXNlLnJlamVjdChuZXcgd2UoW10sIkFsbCBwcm9taXNlcyB3ZXJlIHJlamVjdGVkIikpfWlmKDA9PT1pdClyZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IHdlKFtdLCJBbGwgcHJvbWlzZXMgd2VyZSByZWplY3RlZCIpKTt2YXIgbGk9ITEsY2k9W107cmV0dXJuIG5ldyBjdChmdW5jdGlvbihtaSxJaSl7Zm9yKHZhciBQcj0wO1ByPER0Lmxlbmd0aDtQcisrKUR0W1ByXS50aGVuKGZ1bmN0aW9uKGZzKXtsaXx8KGxpPSEwLG1pKGZzKSl9LGZ1bmN0aW9uKGZzKXtjaS5wdXNoKGZzKSwwPT0tLWl0JiYobGk9ITAsSWkobmV3IHdlKGNpLCJBbGwgcHJvbWlzZXMgd2VyZSByZWplY3RlZCIpKSl9KX0pfSxjdC5yYWNlPWZ1bmN0aW9uKFZ0KXt2YXIgRHQsaXQsY249bmV3IHRoaXMoZnVuY3Rpb24oSWksUHIpe0R0PUlpLGl0PVByfSk7ZnVuY3Rpb24gcW4oSWkpe0R0KElpKX1mdW5jdGlvbiBCbihJaSl7aXQoSWkpfWZvcih2YXIgbGk9MCxjaT1WdDtsaTxjaS5sZW5ndGg7bGkrKyl7dmFyIG1pPWNpW2xpXTtOdChtaSl8fChtaT10aGlzLnJlc29sdmUobWkpKSxtaS50aGVuKHFuLEJuKX1yZXR1cm4gY259LGN0LmFsbD1mdW5jdGlvbihWdCl7cmV0dXJuIGN0LmFsbFdpdGhDYWxsYmFjayhWdCl9LGN0LmFsbFNldHRsZWQ9ZnVuY3Rpb24oVnQpe3JldHVybih0aGlzJiZ0aGlzLnByb3RvdHlwZSBpbnN0YW5jZW9mIGN0P3RoaXM6Y3QpLmFsbFdpdGhDYWxsYmFjayhWdCx7dGhlbkNhbGxiYWNrOmZ1bmN0aW9uKGl0KXtyZXR1cm57c3RhdHVzOiJmdWxmaWxsZWQiLHZhbHVlOml0fX0sZXJyb3JDYWxsYmFjazpmdW5jdGlvbihpdCl7cmV0dXJue3N0YXR1czoicmVqZWN0ZWQiLHJlYXNvbjppdH19fSl9LGN0LmFsbFdpdGhDYWxsYmFjaz1mdW5jdGlvbihWdCxEdCl7Zm9yKHZhciBpdCxjbixxbj1uZXcgdGhpcyhmdW5jdGlvbihTbyx0cyl7aXQ9U28sY249dHN9KSxCbj0yLGxpPTAsY2k9W10sbWk9ZnVuY3Rpb24oU28pe050KFNvKXx8KFNvPUlpLnJlc29sdmUoU28pKTt2YXIgdHM9bGk7dHJ5e1NvLnRoZW4oZnVuY3Rpb24ocG8pe2NpW3RzXT1EdD9EdC50aGVuQ2FsbGJhY2socG8pOnBvLDA9PS0tQm4mJml0KGNpKX0sZnVuY3Rpb24ocG8pe0R0PyhjaVt0c109RHQuZXJyb3JDYWxsYmFjayhwbyksMD09LS1CbiYmaXQoY2kpKTpjbihwbyl9KX1jYXRjaChwbyl7Y24ocG8pfUJuKyssbGkrK30sSWk9dGhpcyxQcj0wLGZzPVZ0O1ByPGZzLmxlbmd0aDtQcisrKW1pKGZzW1ByXSk7cmV0dXJuIDA9PShCbi09MikmJml0KGNpKSxxbn0sT2JqZWN0LmRlZmluZVByb3BlcnR5KGN0LnByb3RvdHlwZSxTeW1ib2wudG9TdHJpbmdUYWcse2dldDpmdW5jdGlvbigpe3JldHVybiJQcm9taXNlIn0sZW51bWVyYWJsZTohMSxjb25maWd1cmFibGU6ITB9KSxPYmplY3QuZGVmaW5lUHJvcGVydHkoY3QucHJvdG90eXBlLFN5bWJvbC5zcGVjaWVzLHtnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY3R9LGVudW1lcmFibGU6ITEsY29uZmlndXJhYmxlOiEwfSksY3QucHJvdG90eXBlLnRoZW49ZnVuY3Rpb24oVnQsRHQpe3ZhciBpdCxjbj1udWxsPT09KGl0PXRoaXMuY29uc3RydWN0b3IpfHx2b2lkIDA9PT1pdD92b2lkIDA6aXRbU3ltYm9sLnNwZWNpZXNdOyghY258fCJmdW5jdGlvbiIhPXR5cGVvZiBjbikmJihjbj10aGlzLmNvbnN0cnVjdG9yfHxjdCk7dmFyIHFuPW5ldyBjbihTdCksQm49cmUuY3VycmVudDtyZXR1cm4gdGhpc1tBaV09PVluP3RoaXNbTW5dLnB1c2goQm4scW4sVnQsRHQpOlZyKHRoaXMsQm4scW4sVnQsRHQpLHFufSxjdC5wcm90b3R5cGUuY2F0Y2g9ZnVuY3Rpb24oVnQpe3JldHVybiB0aGlzLnRoZW4obnVsbCxWdCl9LGN0LnByb3RvdHlwZS5maW5hbGx5PWZ1bmN0aW9uKFZ0KXt2YXIgRHQsaXQ9bnVsbD09PShEdD10aGlzLmNvbnN0cnVjdG9yKXx8dm9pZCAwPT09RHQ/dm9pZCAwOkR0W1N5bWJvbC5zcGVjaWVzXTsoIWl0fHwiZnVuY3Rpb24iIT10eXBlb2YgaXQpJiYoaXQ9Y3QpO3ZhciBjbj1uZXcgaXQoU3QpO2NuW0huXT1Ibjt2YXIgcW49cmUuY3VycmVudDtyZXR1cm4gdGhpc1tBaV09PVluP3RoaXNbTW5dLnB1c2gocW4sY24sVnQsVnQpOlZyKHRoaXMscW4sY24sVnQsVnQpLGNufSxjdH0oKTtFZS5yZXNvbHZlPUVlLnJlc29sdmUsRWUucmVqZWN0PUVlLnJlamVjdCxFZS5yYWNlPUVlLnJhY2UsRWUuYWxsPUVlLmFsbDt2YXIgVmU9UVtsdF09US5Qcm9taXNlO1EuUHJvbWlzZT1FZTt2YXIga249bmUoInRoZW5QYXRjaGVkIik7ZnVuY3Rpb24gSXIoY3Qpe3ZhciBWdD1jdC5wcm90b3R5cGUsRHQ9SShWdCwidGhlbiIpO2lmKCFEdHx8ITEhPT1EdC53cml0YWJsZSYmRHQuY29uZmlndXJhYmxlKXt2YXIgaXQ9VnQudGhlbjtWdFtKZV09aXQsY3QucHJvdG90eXBlLnRoZW49ZnVuY3Rpb24oY24scW4pe3ZhciBCbj10aGlzO3JldHVybiBuZXcgRWUoZnVuY3Rpb24oY2ksbWkpe2l0LmNhbGwoQm4sY2ksbWkpfSkudGhlbihjbixxbil9LGN0W2tuXT0hMH19cmV0dXJuIF9lLnBhdGNoVGhlbj1JcixWZSYmKElyKFZlKSxZKFEsImZldGNoIixmdW5jdGlvbihjdCl7cmV0dXJuIGZ1bmN0aW9uKGN0KXtyZXR1cm4gZnVuY3Rpb24oVnQsRHQpe3ZhciBpdD1jdC5hcHBseShWdCxEdCk7aWYoaXQgaW5zdGFuY2VvZiBFZSlyZXR1cm4gaXQ7dmFyIGNuPWl0LmNvbnN0cnVjdG9yO3JldHVybiBjbltrbl18fElyKGNuKSxpdH19KGN0KX0pKSxQcm9taXNlW3JlLl9fc3ltYm9sX18oInVuY2F1Z2h0UHJvbWlzZUVycm9ycyIpXT1tZSxFZX0pLFpvbmUuX19sb2FkX3BhdGNoKCJ0b1N0cmluZyIsZnVuY3Rpb24oUSl7dmFyIHJlPUZ1bmN0aW9uLnByb3RvdHlwZS50b1N0cmluZyxfZT1mKCJPcmlnaW5hbERlbGVnYXRlIiksST1mKCJQcm9taXNlIiksWD1mKCJFcnJvciIpLCQ9ZnVuY3Rpb24oKXtpZigiZnVuY3Rpb24iPT10eXBlb2YgdGhpcyl7dmFyIGx0PXRoaXNbX2VdO2lmKGx0KXJldHVybiJmdW5jdGlvbiI9PXR5cGVvZiBsdD9yZS5jYWxsKGx0KTpPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nLmNhbGwobHQpO2lmKHRoaXM9PT1Qcm9taXNlKXt2YXIgSmU9UVtJXTtpZihKZSlyZXR1cm4gcmUuY2FsbChKZSl9aWYodGhpcz09PUVycm9yKXt2YXIgZnQ9UVtYXTtpZihmdClyZXR1cm4gcmUuY2FsbChmdCl9fXJldHVybiByZS5jYWxsKHRoaXMpfTskW19lXT1yZSxGdW5jdGlvbi5wcm90b3R5cGUudG9TdHJpbmc9JDt2YXIgbmU9T2JqZWN0LnByb3RvdHlwZS50b1N0cmluZztPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuImZ1bmN0aW9uIj09dHlwZW9mIFByb21pc2UmJnRoaXMgaW5zdGFuY2VvZiBQcm9taXNlPyJbb2JqZWN0IFByb21pc2VdIjpuZS5jYWxsKHRoaXMpfX0pO3ZhciBndD0hMTtpZih0eXBlb2Ygd2luZG93PCJ1Iil0cnl7dmFyIFVlPU9iamVjdC5kZWZpbmVQcm9wZXJ0eSh7fSwicGFzc2l2ZSIse2dldDpmdW5jdGlvbigpe2d0PSEwfX0pO3dpbmRvdy5hZGRFdmVudExpc3RlbmVyKCJ0ZXN0IixVZSxVZSksd2luZG93LnJlbW92ZUV2ZW50TGlzdGVuZXIoInRlc3QiLFVlLFVlKX1jYXRjaHtndD0hMX12YXIgZmksV2kscWksZWUsVyxBZT17dXNlRzohMH0sdG49e30scHQ9e30sd3Q9bmV3IFJlZ0V4cCgiXiIrZCsiKFxcdyspKHRydWV8ZmFsc2UpJCIpLFRlPWYoInByb3BhZ2F0aW9uU3RvcHBlZCIpO2Z1bmN0aW9uIHh0KFEscmUpe3ZhciBfZT0ocmU/cmUoUSk6USkrdSxJPShyZT9yZShRKTpRKStjLFg9ZCtfZSwkPWQrSTt0bltRXT17fSx0bltRXVt1XT1YLHRuW1FdW2NdPSR9ZnVuY3Rpb24gbXQoUSxyZSxfZSxJKXt2YXIgWD1JJiZJLmFkZHx8bywkPUkmJkkucm18fHMsbmU9SSYmSS5saXN0ZW5lcnN8fCJldmVudExpc3RlbmVycyIsbWU9SSYmSS5ybUFsbHx8InJlbW92ZUFsbExpc3RlbmVycyIsS2U9ZihYKSxsdD0iLiIrWCsiOiIsQ3Q9ZnVuY3Rpb24oSG4sT3Qsd2kpe2lmKCFIbi5pc1JlbW92ZWQpe3ZhciBZbixhaT1Ibi5jYWxsYmFjazsib2JqZWN0Ij09dHlwZW9mIGFpJiZhaS5oYW5kbGVFdmVudCYmKEhuLmNhbGxiYWNrPWZ1bmN0aW9uKFlpKXtyZXR1cm4gYWkuaGFuZGxlRXZlbnQoWWkpfSxIbi5vcmlnaW5hbERlbGVnYXRlPWFpKTt0cnl7SG4uaW52b2tlKEhuLE90LFt3aV0pfWNhdGNoKFlpKXtZbj1ZaX12YXIgJG49SG4ub3B0aW9ucztyZXR1cm4gJG4mJiJvYmplY3QiPT10eXBlb2YgJG4mJiRuLm9uY2UmJk90WyRdLmNhbGwoT3Qsd2kudHlwZSxIbi5vcmlnaW5hbERlbGVnYXRlP0huLm9yaWdpbmFsRGVsZWdhdGU6SG4uY2FsbGJhY2ssJG4pLFlufX07ZnVuY3Rpb24gSXQoSG4sT3Qsd2kpe2lmKE90PU90fHxRLmV2ZW50KXt2YXIgYWk9SG58fE90LnRhcmdldHx8USxZbj1haVt0bltPdC50eXBlXVt3aT9jOnVdXTtpZihZbil7dmFyICRuPVtdO2lmKDE9PT1Zbi5sZW5ndGgpKFl0PUN0KFluWzBdLGFpLE90KSkmJiRuLnB1c2goWXQpO2Vsc2UgZm9yKHZhciBZaT1Zbi5zbGljZSgpLEFuPTA7QW48WWkubGVuZ3RoJiYoIU90fHwhMCE9PU90W1RlXSk7QW4rKyl7dmFyIFl0OyhZdD1DdChZaVtBbl0sYWksT3QpKSYmJG4ucHVzaChZdCl9aWYoMT09PSRuLmxlbmd0aCl0aHJvdyAkblswXTt2YXIgVW49ZnVuY3Rpb24oU2kpe3ZhciBqbj0kbltTaV07cmUubmF0aXZlU2NoZWR1bGVNaWNyb1Rhc2soZnVuY3Rpb24oKXt0aHJvdyBqbn0pfTtmb3IoQW49MDtBbjwkbi5sZW5ndGg7QW4rKylVbihBbil9fX12YXIgTnQ9ZnVuY3Rpb24oSG4pe3JldHVybiBJdCh0aGlzLEhuLCExKX0sYm49ZnVuY3Rpb24oSG4pe3JldHVybiBJdCh0aGlzLEhuLCEwKX07ZnVuY3Rpb24gcnIoSG4sT3Qpe2lmKCFIbilyZXR1cm4hMTt2YXIgd2k9ITA7T3QmJnZvaWQgMCE9PU90LnVzZUcmJih3aT1PdC51c2VHKTt2YXIgYWk9T3QmJk90LnZoLFluPSEwO090JiZ2b2lkIDAhPT1PdC5jaGtEdXAmJihZbj1PdC5jaGtEdXApO3ZhciAkbj0hMTtPdCYmdm9pZCAwIT09T3QucnQmJigkbj1PdC5ydCk7Zm9yKHZhciBZdD1IbjtZdCYmIVl0Lmhhc093blByb3BlcnR5KFgpOylZdD1lKFl0KTtpZighWXQmJkhuW1hdJiYoWXQ9SG4pLCFZdHx8WXRbS2VdKXJldHVybiExO3ZhciBicixZaT1PdCYmT3QuZXZlbnROYW1lVG9TdHJpbmcsQW49e30sVW49WXRbS2VdPVl0W1hdLFNpPVl0W2YoJCldPVl0WyRdLGpuPVl0W2YobmUpXT1ZdFtuZV0sRHI9WXRbZihtZSldPVl0W21lXTtmdW5jdGlvbiB4YShpdCxjbil7cmV0dXJuIWd0JiYib2JqZWN0Ij09dHlwZW9mIGl0JiZpdD8hIWl0LmNhcHR1cmU6Z3QmJmNuPyJib29sZWFuIj09dHlwZW9mIGl0P3tjYXB0dXJlOml0LHBhc3NpdmU6ITB9Oml0PyJvYmplY3QiPT10eXBlb2YgaXQmJiExIT09aXQucGFzc2l2ZT9PYmplY3QuYXNzaWduKE9iamVjdC5hc3NpZ24oe30saXQpLHtwYXNzaXZlOiEwfSk6aXQ6e3Bhc3NpdmU6ITB9Oml0fU90JiZPdC5wcmVwZW5kJiYoYnI9WXRbZihPdC5wcmVwZW5kKV09WXRbT3QucHJlcGVuZF0pO3ZhciBWZT13aT9mdW5jdGlvbihpdCl7aWYoIUFuLmlzRXhpc3RpbmcpcmV0dXJuIFVuLmNhbGwoQW4udGFyZ2V0LEFuLmV2ZW50TmFtZSxBbi5jYXB0dXJlP2JuOk50LEFuLm9wdGlvbnMpfTpmdW5jdGlvbihpdCl7cmV0dXJuIFVuLmNhbGwoQW4udGFyZ2V0LEFuLmV2ZW50TmFtZSxpdC5pbnZva2UsQW4ub3B0aW9ucyl9LGtuPXdpP2Z1bmN0aW9uKGl0KXtpZighaXQuaXNSZW1vdmVkKXt2YXIgY249dG5baXQuZXZlbnROYW1lXSxxbj12b2lkIDA7Y24mJihxbj1jbltpdC5jYXB0dXJlP2M6dV0pO3ZhciBCbj1xbiYmaXQudGFyZ2V0W3FuXTtpZihCbilmb3IodmFyIGxpPTA7bGk8Qm4ubGVuZ3RoO2xpKyspaWYoQm5bbGldPT09aXQpe0JuLnNwbGljZShsaSwxKSxpdC5pc1JlbW92ZWQ9ITAsMD09PUJuLmxlbmd0aCYmKGl0LmFsbFJlbW92ZWQ9ITAsaXQudGFyZ2V0W3FuXT1udWxsKTticmVha319aWYoaXQuYWxsUmVtb3ZlZClyZXR1cm4gU2kuY2FsbChpdC50YXJnZXQsaXQuZXZlbnROYW1lLGl0LmNhcHR1cmU/Ym46TnQsaXQub3B0aW9ucyl9OmZ1bmN0aW9uKGl0KXtyZXR1cm4gU2kuY2FsbChpdC50YXJnZXQsaXQuZXZlbnROYW1lLGl0Lmludm9rZSxpdC5vcHRpb25zKX0seGM9T3QmJk90LmRpZmY/T3QuZGlmZjpmdW5jdGlvbihpdCxjbil7dmFyIHFuPXR5cGVvZiBjbjtyZXR1cm4iZnVuY3Rpb24iPT09cW4mJml0LmNhbGxiYWNrPT09Y258fCJvYmplY3QiPT09cW4mJml0Lm9yaWdpbmFsRGVsZWdhdGU9PT1jbn0sY3Q9Wm9uZVtmKCJVTlBBVENIRURfRVZFTlRTIildLFZ0PVFbZigiUEFTU0lWRV9FVkVOVFMiKV0sRHQ9ZnVuY3Rpb24oaXQsY24scW4sQm4sbGksY2kpe3JldHVybiB2b2lkIDA9PT1saSYmKGxpPSExKSx2b2lkIDA9PT1jaSYmKGNpPSExKSxmdW5jdGlvbigpe3ZhciBtaT10aGlzfHxRLElpPWFyZ3VtZW50c1swXTtPdCYmT3QudHJhbnNmZXJFdmVudE5hbWUmJihJaT1PdC50cmFuc2ZlckV2ZW50TmFtZShJaSkpO3ZhciBQcj1hcmd1bWVudHNbMV07aWYoIVByKXJldHVybiBpdC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7aWYoeiYmInVuY2F1Z2h0RXhjZXB0aW9uIj09PUlpKXJldHVybiBpdC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7dmFyIGZzPSExO2lmKCJmdW5jdGlvbiIhPXR5cGVvZiBQcil7aWYoIVByLmhhbmRsZUV2ZW50KXJldHVybiBpdC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7ZnM9ITB9aWYoIWFpfHxhaShpdCxQcixtaSxhcmd1bWVudHMpKXt2YXIgUGQ9Z3QmJiEhVnQmJi0xIT09VnQuaW5kZXhPZihJaSksU289eGEoYXJndW1lbnRzWzJdLFBkKTtpZihjdClmb3IodmFyIHRzPTA7dHM8Y3QubGVuZ3RoO3RzKyspaWYoSWk9PT1jdFt0c10pcmV0dXJuIFBkP2l0LmNhbGwobWksSWksUHIsU28pOml0LmFwcGx5KHRoaXMsYXJndW1lbnRzKTt2YXIgcG89ISFTbyYmKCJib29sZWFuIj09dHlwZW9mIFNvfHxTby5jYXB0dXJlKSxhMD0hKCFTb3x8Im9iamVjdCIhPXR5cGVvZiBTbykmJlNvLm9uY2UsWmdlPVpvbmUuY3VycmVudCxyTj10bltJaV07ck58fCh4dChJaSxZaSksck49dG5bSWldKTt2YXIgRTY9ck5bcG8/Yzp1XSxsMD1taVtFNl0sVDY9ITE7aWYobDApe2lmKFQ2PSEwLFluKWZvcih0cz0wO3RzPGwwLmxlbmd0aDt0cysrKWlmKHhjKGwwW3RzXSxQcikpcmV0dXJufWVsc2UgbDA9bWlbRTZdPVtdO3ZhciBORSxENj1taS5jb25zdHJ1Y3Rvci5uYW1lLEE2PXB0W0Q2XTtBNiYmKE5FPUE2W0lpXSksTkV8fChORT1ENitjbisoWWk/WWkoSWkpOklpKSksQW4ub3B0aW9ucz1TbyxhMCYmKEFuLm9wdGlvbnMub25jZT0hMSksQW4udGFyZ2V0PW1pLEFuLmNhcHR1cmU9cG8sQW4uZXZlbnROYW1lPUlpLEFuLmlzRXhpc3Rpbmc9VDY7dmFyIHR4PXdpP0FlOnZvaWQgMDt0eCYmKHR4LnRhc2tEYXRhPUFuKTt2YXIgTnA9WmdlLnNjaGVkdWxlRXZlbnRUYXNrKE5FLFByLHR4LHFuLEJuKTtpZihBbi50YXJnZXQ9bnVsbCx0eCYmKHR4LnRhc2tEYXRhPW51bGwpLGEwJiYoU28ub25jZT0hMCksIWd0JiYiYm9vbGVhbiI9PXR5cGVvZiBOcC5vcHRpb25zfHwoTnAub3B0aW9ucz1TbyksTnAudGFyZ2V0PW1pLE5wLmNhcHR1cmU9cG8sTnAuZXZlbnROYW1lPUlpLGZzJiYoTnAub3JpZ2luYWxEZWxlZ2F0ZT1QciksY2k/bDAudW5zaGlmdChOcCk6bDAucHVzaChOcCksbGkpcmV0dXJuIG1pfX19O3JldHVybiBZdFtYXT1EdChVbixsdCxWZSxrbiwkbiksYnImJihZdC5wcmVwZW5kTGlzdGVuZXI9RHQoYnIsIi5wcmVwZW5kTGlzdGVuZXI6IixmdW5jdGlvbihpdCl7cmV0dXJuIGJyLmNhbGwoQW4udGFyZ2V0LEFuLmV2ZW50TmFtZSxpdC5pbnZva2UsQW4ub3B0aW9ucyl9LGtuLCRuLCEwKSksWXRbJF09ZnVuY3Rpb24oKXt2YXIgaXQ9dGhpc3x8USxjbj1hcmd1bWVudHNbMF07T3QmJk90LnRyYW5zZmVyRXZlbnROYW1lJiYoY249T3QudHJhbnNmZXJFdmVudE5hbWUoY24pKTt2YXIgcW49YXJndW1lbnRzWzJdLEJuPSEhcW4mJigiYm9vbGVhbiI9PXR5cGVvZiBxbnx8cW4uY2FwdHVyZSksbGk9YXJndW1lbnRzWzFdO2lmKCFsaSlyZXR1cm4gU2kuYXBwbHkodGhpcyxhcmd1bWVudHMpO2lmKCFhaXx8YWkoU2ksbGksaXQsYXJndW1lbnRzKSl7dmFyIG1pLGNpPXRuW2NuXTtjaSYmKG1pPWNpW0JuP2M6dV0pO3ZhciBJaT1taSYmaXRbbWldO2lmKElpKWZvcih2YXIgUHI9MDtQcjxJaS5sZW5ndGg7UHIrKyl7dmFyIGZzPUlpW1ByXTtpZih4YyhmcyxsaSkpe2lmKElpLnNwbGljZShQciwxKSxmcy5pc1JlbW92ZWQ9ITAsMD09PUlpLmxlbmd0aCYmKGZzLmFsbFJlbW92ZWQ9ITAsaXRbbWldPW51bGwsInN0cmluZyI9PXR5cGVvZiBjbikpe3ZhciBQZD1kKyJPTl9QUk9QRVJUWSIrY247aXRbUGRdPW51bGx9cmV0dXJuIGZzLnpvbmUuY2FuY2VsVGFzayhmcyksJG4/aXQ6dm9pZCAwfX1yZXR1cm4gU2kuYXBwbHkodGhpcyxhcmd1bWVudHMpfX0sWXRbbmVdPWZ1bmN0aW9uKCl7dmFyIGl0PXRoaXN8fFEsY249YXJndW1lbnRzWzBdO090JiZPdC50cmFuc2ZlckV2ZW50TmFtZSYmKGNuPU90LnRyYW5zZmVyRXZlbnROYW1lKGNuKSk7Zm9yKHZhciBxbj1bXSxCbj1jZShpdCxZaT9ZaShjbik6Y24pLGxpPTA7bGk8Qm4ubGVuZ3RoO2xpKyspe3ZhciBjaT1CbltsaV0sbWk9Y2kub3JpZ2luYWxEZWxlZ2F0ZT9jaS5vcmlnaW5hbERlbGVnYXRlOmNpLmNhbGxiYWNrO3FuLnB1c2gobWkpfXJldHVybiBxbn0sWXRbbWVdPWZ1bmN0aW9uKCl7dmFyIGl0PXRoaXN8fFEsY249YXJndW1lbnRzWzBdO2lmKGNuKXtPdCYmT3QudHJhbnNmZXJFdmVudE5hbWUmJihjbj1PdC50cmFuc2ZlckV2ZW50TmFtZShjbikpO3ZhciBJaT10bltjbl07aWYoSWkpe3ZhciBQcj1JaVt1XSxmcz1JaVtjXSxQZD1pdFtQcl0sU289aXRbZnNdO2lmKFBkKWZvcih2YXIgdHM9UGQuc2xpY2UoKSxCbj0wO0JuPHRzLmxlbmd0aDtCbisrKXRoaXNbJF0uY2FsbCh0aGlzLGNuLChwbz10c1tCbl0pLm9yaWdpbmFsRGVsZWdhdGU/cG8ub3JpZ2luYWxEZWxlZ2F0ZTpwby5jYWxsYmFjayxwby5vcHRpb25zKTtpZihTbylmb3IodHM9U28uc2xpY2UoKSxCbj0wO0JuPHRzLmxlbmd0aDtCbisrKXt2YXIgcG87dGhpc1skXS5jYWxsKHRoaXMsY24sKHBvPXRzW0JuXSkub3JpZ2luYWxEZWxlZ2F0ZT9wby5vcmlnaW5hbERlbGVnYXRlOnBvLmNhbGxiYWNrLHBvLm9wdGlvbnMpfX19ZWxzZXt2YXIgcW49T2JqZWN0LmtleXMoaXQpO2ZvcihCbj0wO0JuPHFuLmxlbmd0aDtCbisrKXt2YXIgbGk9cW5bQm5dLGNpPXd0LmV4ZWMobGkpLG1pPWNpJiZjaVsxXTttaSYmInJlbW92ZUxpc3RlbmVyIiE9PW1pJiZ0aGlzW21lXS5jYWxsKHRoaXMsbWkpfXRoaXNbbWVdLmNhbGwodGhpcywicmVtb3ZlTGlzdGVuZXIiKX1pZigkbilyZXR1cm4gdGhpc30sbGUoWXRbWF0sVW4pLGxlKFl0WyRdLFNpKSxEciYmbGUoWXRbbWVdLERyKSxqbiYmbGUoWXRbbmVdLGpuKSwhMH1mb3IodmFyIEFpPVtdLE1uPTA7TW48X2UubGVuZ3RoO01uKyspQWlbTW5dPXJyKF9lW01uXSxJKTtyZXR1cm4gQWl9ZnVuY3Rpb24gY2UoUSxyZSl7aWYoIXJlKXt2YXIgX2U9W107Zm9yKHZhciBJIGluIFEpe3ZhciBYPXd0LmV4ZWMoSSksJD1YJiZYWzFdO2lmKCQmJighcmV8fCQ9PT1yZSkpe3ZhciBuZT1RW0ldO2lmKG5lKWZvcih2YXIgbWU9MDttZTxuZS5sZW5ndGg7bWUrKylfZS5wdXNoKG5lW21lXSl9fXJldHVybiBfZX12YXIgS2U9dG5bcmVdO0tlfHwoeHQocmUpLEtlPXRuW3JlXSk7dmFyIGx0PVFbS2VbdV1dLEplPVFbS2VbY11dO3JldHVybiBsdD9KZT9sdC5jb25jYXQoSmUpOmx0LnNsaWNlKCk6SmU/SmUuc2xpY2UoKTpbXX1mdW5jdGlvbiBkdChRLHJlKXt2YXIgX2U9US5FdmVudDtfZSYmX2UucHJvdG90eXBlJiZyZS5wYXRjaE1ldGhvZChfZS5wcm90b3R5cGUsInN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbiIsZnVuY3Rpb24oSSl7cmV0dXJuIGZ1bmN0aW9uKFgsJCl7WFtUZV09ITAsSSYmSS5hcHBseShYLCQpfX0pfWZ1bmN0aW9uIFdlKFEscmUsX2UsSSxYKXt2YXIgJD1ab25lLl9fc3ltYm9sX18oSSk7aWYoIXJlWyRdKXt2YXIgbmU9cmVbJF09cmVbSV07cmVbSV09ZnVuY3Rpb24obWUsS2UsbHQpe3JldHVybiBLZSYmS2UucHJvdG90eXBlJiZYLmZvckVhY2goZnVuY3Rpb24oSmUpe3ZhciBmdD0iIi5jb25jYXQoX2UsIi4iKS5jb25jYXQoSSwiOjoiKStKZSxDdD1LZS5wcm90b3R5cGU7dHJ5e2lmKEN0Lmhhc093blByb3BlcnR5KEplKSl7dmFyIEl0PVEuT2JqZWN0R2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKEN0LEplKTtJdCYmSXQudmFsdWU/KEl0LnZhbHVlPVEud3JhcFdpdGhDdXJyZW50Wm9uZShJdC52YWx1ZSxmdCksUS5fcmVkZWZpbmVQcm9wZXJ0eShLZS5wcm90b3R5cGUsSmUsSXQpKTpDdFtKZV0mJihDdFtKZV09US53cmFwV2l0aEN1cnJlbnRab25lKEN0W0plXSxmdCkpfWVsc2UgQ3RbSmVdJiYoQ3RbSmVdPVEud3JhcFdpdGhDdXJyZW50Wm9uZShDdFtKZV0sZnQpKX1jYXRjaHt9fSksbmUuY2FsbChyZSxtZSxLZSxsdCl9LFEuYXR0YWNoT3JpZ2luVG9QYXRjaGVkKHJlW0ldLG5lKX19ZnVuY3Rpb24gTXQoUSxyZSxfZSl7aWYoIV9lfHwwPT09X2UubGVuZ3RoKXJldHVybiByZTt2YXIgST1fZS5maWx0ZXIoZnVuY3Rpb24oJCl7cmV0dXJuICQudGFyZ2V0PT09UX0pO2lmKCFJfHwwPT09SS5sZW5ndGgpcmV0dXJuIHJlO3ZhciBYPUlbMF0uaWdub3JlUHJvcGVydGllcztyZXR1cm4gcmUuZmlsdGVyKGZ1bmN0aW9uKCQpe3JldHVybi0xPT09WC5pbmRleE9mKCQpfSl9ZnVuY3Rpb24gYnQoUSxyZSxfZSxJKXtRJiZxKFEsTXQoUSxyZSxfZSksSSl9ZnVuY3Rpb24gaG4oUSl7cmV0dXJuIE9iamVjdC5nZXRPd25Qcm9wZXJ0eU5hbWVzKFEpLmZpbHRlcihmdW5jdGlvbihyZSl7cmV0dXJuIHJlLnN0YXJ0c1dpdGgoIm9uIikmJnJlLmxlbmd0aD4yfSkubWFwKGZ1bmN0aW9uKHJlKXtyZXR1cm4gcmUuc3Vic3RyaW5nKDIpfSl9ZnVuY3Rpb24gVHQoUSxyZSxfZSl7dmFyIEk9X2UuY29uZmlndXJhYmxlO3JldHVybiB3bihRLHJlLF9lPXFlKFEscmUsX2UpLEkpfWZ1bmN0aW9uIG1uKFEscmUpe3JldHVybiBRJiZRW1ddJiZRW1ddW3JlXX1mdW5jdGlvbiBxZShRLHJlLF9lKXtyZXR1cm4gT2JqZWN0LmlzRnJvemVuKF9lKXx8KF9lLmNvbmZpZ3VyYWJsZT0hMCksX2UuY29uZmlndXJhYmxlfHwoIVFbV10mJiFPYmplY3QuaXNGcm96ZW4oUSkmJldpKFEsVyx7d3JpdGFibGU6ITAsdmFsdWU6e319KSxRW1ddJiYoUVtXXVtyZV09ITApKSxfZX1mdW5jdGlvbiB3bihRLHJlLF9lLEkpe3RyeXtyZXR1cm4gV2koUSxyZSxfZSl9Y2F0Y2gobmUpe2lmKCFfZS5jb25maWd1cmFibGUpdGhyb3cgbmU7dHlwZW9mIEk+InUiP2RlbGV0ZSBfZS5jb25maWd1cmFibGU6X2UuY29uZmlndXJhYmxlPUk7dHJ5e3JldHVybiBXaShRLHJlLF9lKX1jYXRjaChtZSl7dmFyIFg9ITE7aWYoKCJjcmVhdGVkQ2FsbGJhY2siPT09cmV8fCJhdHRhY2hlZENhbGxiYWNrIj09PXJlfHwiZGV0YWNoZWRDYWxsYmFjayI9PT1yZXx8ImF0dHJpYnV0ZUNoYW5nZWRDYWxsYmFjayI9PT1yZSkmJihYPSEwKSwhWCl0aHJvdyBtZTt2YXIgJD1udWxsO3RyeXskPUpTT04uc3RyaW5naWZ5KF9lKX1jYXRjaHskPV9lLnRvU3RyaW5nKCl9Y29uc29sZS5sb2coIkF0dGVtcHRpbmcgdG8gY29uZmlndXJlICciLmNvbmNhdChyZSwiJyB3aXRoIGRlc2NyaXB0b3IgJyIpLmNvbmNhdCgkLCInIG9uIG9iamVjdCAnIikuY29uY2F0KFEsIicgYW5kIGdvdCBlcnJvciwgZ2l2aW5nIHVwOiAiKS5jb25jYXQobWUpKX19fVpvbmUuX19sb2FkX3BhdGNoKCJ1dGlsIixmdW5jdGlvbihRLHJlLF9lKXt2YXIgST1obihRKTtfZS5wYXRjaE9uUHJvcGVydGllcz1xLF9lLnBhdGNoTWV0aG9kPVksX2UuYmluZEFyZ3VtZW50cz1ELF9lLnBhdGNoTWFjcm9UYXNrPWFlO3ZhciBYPXJlLl9fc3ltYm9sX18oIkJMQUNLX0xJU1RFRF9FVkVOVFMiKSwkPXJlLl9fc3ltYm9sX18oIlVOUEFUQ0hFRF9FVkVOVFMiKTtRWyRdJiYoUVtYXT1RWyRdKSxRW1hdJiYocmVbWF09cmVbJF09UVtYXSksX2UucGF0Y2hFdmVudFByb3RvdHlwZT1kdCxfZS5wYXRjaEV2ZW50VGFyZ2V0PW10LF9lLmlzSUVPckVkZ2U9bnQsX2UuT2JqZWN0RGVmaW5lUHJvcGVydHk9dCxfZS5PYmplY3RHZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3I9bixfZS5PYmplY3RDcmVhdGU9aSxfZS5BcnJheVNsaWNlPXIsX2UucGF0Y2hDbGFzcz1kZSxfZS53cmFwV2l0aEN1cnJlbnRab25lPXAsX2UuZmlsdGVyUHJvcGVydGllcz1NdCxfZS5hdHRhY2hPcmlnaW5Ub1BhdGNoZWQ9bGUsX2UuX3JlZGVmaW5lUHJvcGVydHk9T2JqZWN0LmRlZmluZVByb3BlcnR5LF9lLnBhdGNoQ2FsbGJhY2tzPVdlLF9lLmdldEdsb2JhbE9iamVjdHM9ZnVuY3Rpb24oKXtyZXR1cm57Z2xvYmFsU291cmNlczpwdCx6b25lU3ltYm9sRXZlbnROYW1lczp0bixldmVudE5hbWVzOkksaXNCcm93c2VyOmZlLGlzTWl4OnVlLGlzTm9kZTp6LFRSVUVfU1RSOmMsRkFMU0VfU1RSOnUsWk9ORV9TWU1CT0xfUFJFRklYOmQsQUREX0VWRU5UX0xJU1RFTkVSX1NUUjpvLFJFTU9WRV9FVkVOVF9MSVNURU5FUl9TVFI6c319fSk7dmFyIFEsRXM9dWgodWgodWgodWgodWgodWgodWgodWgoW10sWyJhYm9ydCIsImFuaW1hdGlvbmNhbmNlbCIsImFuaW1hdGlvbmVuZCIsImFuaW1hdGlvbml0ZXJhdGlvbiIsImF1eGNsaWNrIiwiYmVmb3JlaW5wdXQiLCJibHVyIiwiY2FuY2VsIiwiY2FucGxheSIsImNhbnBsYXl0aHJvdWdoIiwiY2hhbmdlIiwiY29tcG9zaXRpb25zdGFydCIsImNvbXBvc2l0aW9udXBkYXRlIiwiY29tcG9zaXRpb25lbmQiLCJjdWVjaGFuZ2UiLCJjbGljayIsImNsb3NlIiwiY29udGV4dG1lbnUiLCJjdXJlY2hhbmdlIiwiZGJsY2xpY2siLCJkcmFnIiwiZHJhZ2VuZCIsImRyYWdlbnRlciIsImRyYWdleGl0IiwiZHJhZ2xlYXZlIiwiZHJhZ292ZXIiLCJkcm9wIiwiZHVyYXRpb25jaGFuZ2UiLCJlbXB0aWVkIiwiZW5kZWQiLCJlcnJvciIsImZvY3VzIiwiZm9jdXNpbiIsImZvY3Vzb3V0IiwiZ290cG9pbnRlcmNhcHR1cmUiLCJpbnB1dCIsImludmFsaWQiLCJrZXlkb3duIiwia2V5cHJlc3MiLCJrZXl1cCIsImxvYWQiLCJsb2Fkc3RhcnQiLCJsb2FkZWRkYXRhIiwibG9hZGVkbWV0YWRhdGEiLCJsb3N0cG9pbnRlcmNhcHR1cmUiLCJtb3VzZWRvd24iLCJtb3VzZWVudGVyIiwibW91c2VsZWF2ZSIsIm1vdXNlbW92ZSIsIm1vdXNlb3V0IiwibW91c2VvdmVyIiwibW91c2V1cCIsIm1vdXNld2hlZWwiLCJvcmllbnRhdGlvbmNoYW5nZSIsInBhdXNlIiwicGxheSIsInBsYXlpbmciLCJwb2ludGVyY2FuY2VsIiwicG9pbnRlcmRvd24iLCJwb2ludGVyZW50ZXIiLCJwb2ludGVybGVhdmUiLCJwb2ludGVybG9ja2NoYW5nZSIsIm1venBvaW50ZXJsb2NrY2hhbmdlIiwid2Via2l0cG9pbnRlcmxvY2tlcmNoYW5nZSIsInBvaW50ZXJsb2NrZXJyb3IiLCJtb3pwb2ludGVybG9ja2Vycm9yIiwid2Via2l0cG9pbnRlcmxvY2tlcnJvciIsInBvaW50ZXJtb3ZlIiwicG9pbnRvdXQiLCJwb2ludGVyb3ZlciIsInBvaW50ZXJ1cCIsInByb2dyZXNzIiwicmF0ZWNoYW5nZSIsInJlc2V0IiwicmVzaXplIiwic2Nyb2xsIiwic2Vla2VkIiwic2Vla2luZyIsInNlbGVjdCIsInNlbGVjdGlvbmNoYW5nZSIsInNlbGVjdHN0YXJ0Iiwic2hvdyIsInNvcnQiLCJzdGFsbGVkIiwic3VibWl0Iiwic3VzcGVuZCIsInRpbWV1cGRhdGUiLCJ2b2x1bWVjaGFuZ2UiLCJ0b3VjaGNhbmNlbCIsInRvdWNobW92ZSIsInRvdWNoc3RhcnQiLCJ0b3VjaGVuZCIsInRyYW5zaXRpb25jYW5jZWwiLCJ0cmFuc2l0aW9uZW5kIiwid2FpdGluZyIsIndoZWVsIl0sITApLFsid2ViZ2xjb250ZXh0cmVzdG9yZWQiLCJ3ZWJnbGNvbnRleHRsb3N0Iiwid2ViZ2xjb250ZXh0Y3JlYXRpb25lcnJvciJdLCEwKSxbImF1dG9jb21wbGV0ZSIsImF1dG9jb21wbGV0ZWVycm9yIl0sITApLFsidG9nZ2xlIl0sITApLFsiYWZ0ZXJzY3JpcHRleGVjdXRlIiwiYmVmb3Jlc2NyaXB0ZXhlY3V0ZSIsIkRPTUNvbnRlbnRMb2FkZWQiLCJmcmVlemUiLCJmdWxsc2NyZWVuY2hhbmdlIiwibW96ZnVsbHNjcmVlbmNoYW5nZSIsIndlYmtpdGZ1bGxzY3JlZW5jaGFuZ2UiLCJtc2Z1bGxzY3JlZW5jaGFuZ2UiLCJmdWxsc2NyZWVuZXJyb3IiLCJtb3pmdWxsc2NyZWVuZXJyb3IiLCJ3ZWJraXRmdWxsc2NyZWVuZXJyb3IiLCJtc2Z1bGxzY3JlZW5lcnJvciIsInJlYWR5c3RhdGVjaGFuZ2UiLCJ2aXNpYmlsaXR5Y2hhbmdlIiwicmVzdW1lIl0sITApLFsiYWJzb2x1dGVkZXZpY2VvcmllbnRhdGlvbiIsImFmdGVyaW5wdXQiLCJhZnRlcnByaW50IiwiYXBwaW5zdGFsbGVkIiwiYmVmb3JlaW5zdGFsbHByb21wdCIsImJlZm9yZXByaW50IiwiYmVmb3JldW5sb2FkIiwiZGV2aWNlbGlnaHQiLCJkZXZpY2Vtb3Rpb24iLCJkZXZpY2VvcmllbnRhdGlvbiIsImRldmljZW9yaWVudGF0aW9uYWJzb2x1dGUiLCJkZXZpY2Vwcm94aW1pdHkiLCJoYXNoY2hhbmdlIiwibGFuZ3VhZ2VjaGFuZ2UiLCJtZXNzYWdlIiwibW96YmVmb3JlcGFpbnQiLCJvZmZsaW5lIiwib25saW5lIiwicGFpbnQiLCJwYWdlc2hvdyIsInBhZ2VoaWRlIiwicG9wc3RhdGUiLCJyZWplY3Rpb25oYW5kbGVkIiwic3RvcmFnZSIsInVuaGFuZGxlZHJlamVjdGlvbiIsInVubG9hZCIsInVzZXJwcm94aW1pdHkiLCJ2cmRpc3BsYXljb25uZWN0ZWQiLCJ2cmRpc3BsYXlkaXNjb25uZWN0ZWQiLCJ2cmRpc3BsYXlwcmVzZW50Y2hhbmdlIl0sITApLFsiYmVmb3JlY29weSIsImJlZm9yZWN1dCIsImJlZm9yZXBhc3RlIiwiY29weSIsImN1dCIsInBhc3RlIiwiZHJhZ3N0YXJ0IiwibG9hZGVuZCIsImFuaW1hdGlvbnN0YXJ0Iiwic2VhcmNoIiwidHJhbnNpdGlvbnJ1biIsInRyYW5zaXRpb25zdGFydCIsIndlYmtpdGFuaW1hdGlvbmVuZCIsIndlYmtpdGFuaW1hdGlvbml0ZXJhdGlvbiIsIndlYmtpdGFuaW1hdGlvbnN0YXJ0Iiwid2Via2l0dHJhbnNpdGlvbmVuZCJdLCEwKSxbImFjdGl2YXRlIiwiYWZ0ZXJ1cGRhdGUiLCJhcmlhcmVxdWVzdCIsImJlZm9yZWFjdGl2YXRlIiwiYmVmb3JlZGVhY3RpdmF0ZSIsImJlZm9yZWVkaXRmb2N1cyIsImJlZm9yZXVwZGF0ZSIsImNlbGxjaGFuZ2UiLCJjb250cm9sc2VsZWN0IiwiZGF0YWF2YWlsYWJsZSIsImRhdGFzZXRjaGFuZ2VkIiwiZGF0YXNldGNvbXBsZXRlIiwiZXJyb3J1cGRhdGUiLCJmaWx0ZXJjaGFuZ2UiLCJsYXlvdXRjb21wbGV0ZSIsImxvc2VjYXB0dXJlIiwibW92ZSIsIm1vdmVlbmQiLCJtb3Zlc3RhcnQiLCJwcm9wZXJ0eWNoYW5nZSIsInJlc2l6ZWVuZCIsInJlc2l6ZXN0YXJ0Iiwicm93ZW50ZXIiLCJyb3dleGl0Iiwicm93c2RlbGV0ZSIsInJvd3NpbnNlcnRlZCIsImNvbW1hbmQiLCJjb21wYXNzbmVlZHNjYWxpYnJhdGlvbiIsImRlYWN0aXZhdGUiLCJoZWxwIiwibXNjb250ZW50em9vbSIsIm1zbWFuaXB1bGF0aW9uc3RhdGVjaGFuZ2VkIiwibXNnZXN0dXJlY2hhbmdlIiwibXNnZXN0dXJlZG91YmxldGFwIiwibXNnZXN0dXJlZW5kIiwibXNnZXN0dXJlaG9sZCIsIm1zZ2VzdHVyZXN0YXJ0IiwibXNnZXN0dXJldGFwIiwibXNnb3Rwb2ludGVyY2FwdHVyZSIsIm1zaW5lcnRpYXN0YXJ0IiwibXNsb3N0cG9pbnRlcmNhcHR1cmUiLCJtc3BvaW50ZXJjYW5jZWwiLCJtc3BvaW50ZXJkb3duIiwibXNwb2ludGVyZW50ZXIiLCJtc3BvaW50ZXJob3ZlciIsIm1zcG9pbnRlcmxlYXZlIiwibXNwb2ludGVybW92ZSIsIm1zcG9pbnRlcm91dCIsIm1zcG9pbnRlcm92ZXIiLCJtc3BvaW50ZXJ1cCIsInBvaW50ZXJvdXQiLCJtc3NpdGVtb2RlanVtcGxpc3RpdGVtcmVtb3ZlZCIsIm1zdGh1bWJuYWlsY2xpY2siLCJzdG9wIiwic3RvcmFnZWNvbW1pdCJdLCEwKTsoUT10eXBlb2Ygd2luZG93PCJ1Ij93aW5kb3c6dHlwZW9mIGdsb2JhbDwidSI/Z2xvYmFsOnR5cGVvZiBzZWxmPCJ1Ij9zZWxmOnt9KVsoImxlZ2FjeVBhdGNoIiwoUS5fX1pvbmVfc3ltYm9sX3ByZWZpeHx8Il9fem9uZV9zeW1ib2xfXyIpKyJsZWdhY3lQYXRjaCIpXT1mdW5jdGlvbigpe3ZhciBJPVEuWm9uZTtJLl9fbG9hZF9wYXRjaCgiZGVmaW5lUHJvcGVydHkiLGZ1bmN0aW9uKFgsJCxuZSl7bmUuX3JlZGVmaW5lUHJvcGVydHk9VHQsZmk9Wm9uZS5fX3N5bWJvbF9fLFdpPU9iamVjdFtmaSgiZGVmaW5lUHJvcGVydHkiKV09T2JqZWN0LmRlZmluZVByb3BlcnR5LHFpPU9iamVjdFtmaSgiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIildPU9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IsZWU9T2JqZWN0LmNyZWF0ZSxXPWZpKCJ1bmNvbmZpZ3VyYWJsZXMiKSxPYmplY3QuZGVmaW5lUHJvcGVydHk9ZnVuY3Rpb24oUSxyZSxfZSl7aWYobW4oUSxyZSkpdGhyb3cgbmV3IFR5cGVFcnJvcigiQ2Fubm90IGFzc2lnbiB0byByZWFkIG9ubHkgcHJvcGVydHkgJyIrcmUrIicgb2YgIitRKTt2YXIgST1fZS5jb25maWd1cmFibGU7cmV0dXJuInByb3RvdHlwZSIhPT1yZSYmKF9lPXFlKFEscmUsX2UpKSx3bihRLHJlLF9lLEkpfSxPYmplY3QuZGVmaW5lUHJvcGVydGllcz1mdW5jdGlvbihRLHJlKXtPYmplY3Qua2V5cyhyZSkuZm9yRWFjaChmdW5jdGlvbihuZSl7T2JqZWN0LmRlZmluZVByb3BlcnR5KFEsbmUscmVbbmVdKX0pO2Zvcih2YXIgX2U9MCxJPU9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHMocmUpO19lPEkubGVuZ3RoO19lKyspe3ZhciBYPUlbX2VdO09iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IocmUsWCk/LmVudW1lcmFibGUmJk9iamVjdC5kZWZpbmVQcm9wZXJ0eShRLFgscmVbWF0pfXJldHVybiBRfSxPYmplY3QuY3JlYXRlPWZ1bmN0aW9uKFEscmUpe3JldHVybiJvYmplY3QiPT10eXBlb2YgcmUmJiFPYmplY3QuaXNGcm96ZW4ocmUpJiZPYmplY3Qua2V5cyhyZSkuZm9yRWFjaChmdW5jdGlvbihfZSl7cmVbX2VdPXFlKFEsX2UscmVbX2VdKX0pLGVlKFEscmUpfSxPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yPWZ1bmN0aW9uKFEscmUpe3ZhciBfZT1xaShRLHJlKTtyZXR1cm4gX2UmJm1uKFEscmUpJiYoX2UuY29uZmlndXJhYmxlPSExKSxfZX19KSxJLl9fbG9hZF9wYXRjaCgicmVnaXN0ZXJFbGVtZW50IixmdW5jdGlvbihYLCQsbmUpeyFmdW5jdGlvbihRLHJlKXt2YXIgX2U9cmUuZ2V0R2xvYmFsT2JqZWN0cygpOyhfZS5pc0Jyb3dzZXJ8fF9lLmlzTWl4KSYmInJlZ2lzdGVyRWxlbWVudCJpbiBRLmRvY3VtZW50JiZyZS5wYXRjaENhbGxiYWNrcyhyZSxkb2N1bWVudCwiRG9jdW1lbnQiLCJyZWdpc3RlckVsZW1lbnQiLFsiY3JlYXRlZENhbGxiYWNrIiwiYXR0YWNoZWRDYWxsYmFjayIsImRldGFjaGVkQ2FsbGJhY2siLCJhdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2siXSl9KFgsbmUpfSksSS5fX2xvYWRfcGF0Y2goIkV2ZW50VGFyZ2V0TGVnYWN5IixmdW5jdGlvbihYLCQsbmUpeyhmdW5jdGlvbihRLHJlKXt2YXIgX2U9cmUuZ2V0R2xvYmFsT2JqZWN0cygpLEk9X2UuZXZlbnROYW1lcyxYPV9lLmdsb2JhbFNvdXJjZXMsJD1fZS56b25lU3ltYm9sRXZlbnROYW1lcyxuZT1fZS5UUlVFX1NUUixtZT1fZS5GQUxTRV9TVFIsS2U9X2UuWk9ORV9TWU1CT0xfUFJFRklYLEplPSJBcHBsaWNhdGlvbkNhY2hlLEV2ZW50U291cmNlLEZpbGVSZWFkZXIsSW5wdXRNZXRob2RDb250ZXh0LE1lZGlhQ29udHJvbGxlcixNZXNzYWdlUG9ydCxOb2RlLFBlcmZvcm1hbmNlLFNWR0VsZW1lbnRJbnN0YW5jZSxTaGFyZWRXb3JrZXIsVGV4dFRyYWNrLFRleHRUcmFja0N1ZSxUZXh0VHJhY2tMaXN0LFdlYktpdE5hbWVkRmxvdyxXaW5kb3csV29ya2VyLFdvcmtlckdsb2JhbFNjb3BlLFhNTEh0dHBSZXF1ZXN0LFhNTEh0dHBSZXF1ZXN0RXZlbnRUYXJnZXQsWE1MSHR0cFJlcXVlc3RVcGxvYWQsSURCUmVxdWVzdCxJREJPcGVuREJSZXF1ZXN0LElEQkRhdGFiYXNlLElEQlRyYW5zYWN0aW9uLElEQkN1cnNvcixEQkluZGV4LFdlYlNvY2tldCIuc3BsaXQoIiwiKSxmdD0iRXZlbnRUYXJnZXQiLEN0PVtdLEl0PVEud3RmLE50PSJBbmNob3IsQXJlYSxBdWRpbyxCUixCYXNlLEJhc2VGb250LEJvZHksQnV0dG9uLENhbnZhcyxDb250ZW50LERMaXN0LERpcmVjdG9yeSxEaXYsRW1iZWQsRmllbGRTZXQsRm9udCxGb3JtLEZyYW1lLEZyYW1lU2V0LEhSLEhlYWQsSGVhZGluZyxIdG1sLElGcmFtZSxJbWFnZSxJbnB1dCxLZXlnZW4sTEksTGFiZWwsTGVnZW5kLExpbmssTWFwLE1hcnF1ZWUsTWVkaWEsTWVudSxNZXRhLE1ldGVyLE1vZCxPTGlzdCxPYmplY3QsT3B0R3JvdXAsT3B0aW9uLE91dHB1dCxQYXJhZ3JhcGgsUHJlLFByb2dyZXNzLFF1b3RlLFNjcmlwdCxTZWxlY3QsU291cmNlLFNwYW4sU3R5bGUsVGFibGVDYXB0aW9uLFRhYmxlQ2VsbCxUYWJsZUNvbCxUYWJsZSxUYWJsZVJvdyxUYWJsZVNlY3Rpb24sVGV4dEFyZWEsVGl0bGUsVHJhY2ssVUxpc3QsVW5rbm93bixWaWRlbyIuc3BsaXQoIiwiKTtJdD9DdD1OdC5tYXAoZnVuY3Rpb24oVnIpe3JldHVybiJIVE1MIitWcisiRWxlbWVudCJ9KS5jb25jYXQoSmUpOlFbZnRdP0N0LnB1c2goZnQpOkN0PUplO2Zvcih2YXIgYm49US5fX1pvbmVfZGlzYWJsZV9JRV9jaGVja3x8ITEscnI9US5fX1pvbmVfZW5hYmxlX2Nyb3NzX2NvbnRleHRfY2hlY2t8fCExLEFpPXJlLmlzSUVPckVkZ2UoKSxIbj0iW29iamVjdCBGdW5jdGlvbldyYXBwZXJdIixPdD0iZnVuY3Rpb24gX19CUk9XU0VSVE9PTFNfQ09OU09MRV9TQUZFRlVOQygpIHsgW25hdGl2ZSBjb2RlXSB9Iix3aT17TVNQb2ludGVyQ2FuY2VsOiJwb2ludGVyY2FuY2VsIixNU1BvaW50ZXJEb3duOiJwb2ludGVyZG93biIsTVNQb2ludGVyRW50ZXI6InBvaW50ZXJlbnRlciIsTVNQb2ludGVySG92ZXI6InBvaW50ZXJob3ZlciIsTVNQb2ludGVyTGVhdmU6InBvaW50ZXJsZWF2ZSIsTVNQb2ludGVyTW92ZToicG9pbnRlcm1vdmUiLE1TUG9pbnRlck91dDoicG9pbnRlcm91dCIsTVNQb2ludGVyT3ZlcjoicG9pbnRlcm92ZXIiLE1TUG9pbnRlclVwOiJwb2ludGVydXAifSxhaT0wO2FpPEkubGVuZ3RoO2FpKyspe3ZhciBZaT1LZSsoKFluPUlbYWldKSttZSksQW49S2UrKFluK25lKTskW1luXT17fSwkW1luXVttZV09WWksJFtZbl1bbmVdPUFufWZvcihhaT0wO2FpPE50Lmxlbmd0aDthaSsrKWZvcih2YXIgVW49TnRbYWldLFNpPVhbVW5dPXt9LGpuPTA7am48SS5sZW5ndGg7am4rKyl7dmFyIFluO1NpW1luPUlbam5dXT1VbisiLmFkZEV2ZW50TGlzdGVuZXI6IitZbn12YXIgYnI9W107Zm9yKGFpPTA7YWk8Q3QubGVuZ3RoO2FpKyspe3ZhciB4YT1RW0N0W2FpXV07YnIucHVzaCh4YSYmeGEucHJvdG90eXBlKX1yZS5wYXRjaEV2ZW50VGFyZ2V0KFEscmUsYnIse3ZoOmZ1bmN0aW9uKFZyLEFyLFN0LHdlKXtpZighYm4mJkFpKWlmKHJyKXRyeXtpZigoRWU9QXIudG9TdHJpbmcoKSk9PT1Ibnx8RWU9PU90KXJldHVybiBWci5hcHBseShTdCx3ZSksITF9Y2F0Y2h7cmV0dXJuIFZyLmFwcGx5KFN0LHdlKSwhMX1lbHNle3ZhciBFZTtpZigoRWU9QXIudG9TdHJpbmcoKSk9PT1Ibnx8RWU9PU90KXJldHVybiBWci5hcHBseShTdCx3ZSksITF9ZWxzZSBpZihycil0cnl7QXIudG9TdHJpbmcoKX1jYXRjaHtyZXR1cm4gVnIuYXBwbHkoU3Qsd2UpLCExfXJldHVybiEwfSx0cmFuc2ZlckV2ZW50TmFtZTpmdW5jdGlvbihWcil7cmV0dXJuIHdpW1ZyXXx8VnJ9fSksWm9uZVtyZS5zeW1ib2woInBhdGNoRXZlbnRUYXJnZXQiKV09ISFRW2Z0XX0pKFgsbmUpLGZ1bmN0aW9uKFEscmUpe3ZhciBfZT1RLmdldEdsb2JhbE9iamVjdHMoKTtpZigoIV9lLmlzTm9kZXx8X2UuaXNNaXgpJiYhZnVuY3Rpb24oUSxyZSl7dmFyIF9lPVEuZ2V0R2xvYmFsT2JqZWN0cygpO2lmKChfZS5pc0Jyb3dzZXJ8fF9lLmlzTWl4KSYmIVEuT2JqZWN0R2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKEhUTUxFbGVtZW50LnByb3RvdHlwZSwib25jbGljayIpJiZ0eXBlb2YgRWxlbWVudDwidSIpe3ZhciAkPVEuT2JqZWN0R2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKEVsZW1lbnQucHJvdG90eXBlLCJvbmNsaWNrIik7aWYoJCYmISQuY29uZmlndXJhYmxlKXJldHVybiExO2lmKCQpe1EuT2JqZWN0RGVmaW5lUHJvcGVydHkoRWxlbWVudC5wcm90b3R5cGUsIm9uY2xpY2siLHtlbnVtZXJhYmxlOiEwLGNvbmZpZ3VyYWJsZTohMCxnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4hMH19KTt2YXIgbWU9ISFkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKS5vbmNsaWNrO3JldHVybiBRLk9iamVjdERlZmluZVByb3BlcnR5KEVsZW1lbnQucHJvdG90eXBlLCJvbmNsaWNrIiwkKSxtZX19dmFyIEtlPXJlLlhNTEh0dHBSZXF1ZXN0O2lmKCFLZSlyZXR1cm4hMTt2YXIgbHQ9Im9ucmVhZHlzdGF0ZWNoYW5nZSIsSmU9S2UucHJvdG90eXBlLGZ0PVEuT2JqZWN0R2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKEplLGx0KTtpZihmdClyZXR1cm4gUS5PYmplY3REZWZpbmVQcm9wZXJ0eShKZSxsdCx7ZW51bWVyYWJsZTohMCxjb25maWd1cmFibGU6ITAsZ2V0OmZ1bmN0aW9uKCl7cmV0dXJuITB9fSksbWU9ISEoQ3Q9bmV3IEtlKS5vbnJlYWR5c3RhdGVjaGFuZ2UsUS5PYmplY3REZWZpbmVQcm9wZXJ0eShKZSxsdCxmdHx8e30pLG1lO3ZhciBJdD1RLnN5bWJvbCgiZmFrZSIpO1EuT2JqZWN0RGVmaW5lUHJvcGVydHkoSmUsbHQse2VudW1lcmFibGU6ITAsY29uZmlndXJhYmxlOiEwLGdldDpmdW5jdGlvbigpe3JldHVybiB0aGlzW0l0XX0sc2V0OmZ1bmN0aW9uKEFpKXt0aGlzW0l0XT1BaX19KTt2YXIgQ3QsTnQ9ZnVuY3Rpb24oKXt9O3JldHVybihDdD1uZXcgS2UpLm9ucmVhZHlzdGF0ZWNoYW5nZT1OdCxtZT1DdFtJdF09PT1OdCxDdC5vbnJlYWR5c3RhdGVjaGFuZ2U9bnVsbCxtZX0oUSxyZSkpe3ZhciAkPXR5cGVvZiBXZWJTb2NrZXQ8InUiOyhmdW5jdGlvbihRKXtmb3IodmFyIHJlPVEuc3ltYm9sKCJ1bmJvdW5kIiksX2U9ZnVuY3Rpb24oWCl7dmFyICQ9RXNbWF0sbmU9Im9uIiskO3NlbGYuYWRkRXZlbnRMaXN0ZW5lcigkLGZ1bmN0aW9uKG1lKXt2YXIgbHQsSmUsS2U9bWUudGFyZ2V0O2ZvcihKZT1LZT9LZS5jb25zdHJ1Y3Rvci5uYW1lKyIuIituZToidW5rbm93bi4iK25lO0tlOylLZVtuZV0mJiFLZVtuZV1bcmVdJiYoKGx0PVEud3JhcFdpdGhDdXJyZW50Wm9uZShLZVtuZV0sSmUpKVtyZV09S2VbbmVdLEtlW25lXT1sdCksS2U9S2UucGFyZW50RWxlbWVudH0sITApfSxJPTA7STxFcy5sZW5ndGg7SSsrKV9lKEkpfSkoUSksUS5wYXRjaENsYXNzKCJYTUxIdHRwUmVxdWVzdCIpLCQmJmZ1bmN0aW9uKFEscmUpe3ZhciBfZT1RLmdldEdsb2JhbE9iamVjdHMoKSxJPV9lLkFERF9FVkVOVF9MSVNURU5FUl9TVFIsWD1fZS5SRU1PVkVfRVZFTlRfTElTVEVORVJfU1RSLCQ9cmUuV2ViU29ja2V0O3JlLkV2ZW50VGFyZ2V0fHxRLnBhdGNoRXZlbnRUYXJnZXQocmUsUSxbJC5wcm90b3R5cGVdKSxyZS5XZWJTb2NrZXQ9ZnVuY3Rpb24oS2UsbHQpe3ZhciBmdCxDdCxKZT1hcmd1bWVudHMubGVuZ3RoPjE/bmV3ICQoS2UsbHQpOm5ldyAkKEtlKSxJdD1RLk9iamVjdEdldE93blByb3BlcnR5RGVzY3JpcHRvcihKZSwib25tZXNzYWdlIik7cmV0dXJuIEl0JiYhMT09PUl0LmNvbmZpZ3VyYWJsZT8oZnQ9US5PYmplY3RDcmVhdGUoSmUpLEN0PUplLFtJLFgsInNlbmQiLCJjbG9zZSJdLmZvckVhY2goZnVuY3Rpb24oTnQpe2Z0W050XT1mdW5jdGlvbigpe3ZhciBibj1RLkFycmF5U2xpY2UuY2FsbChhcmd1bWVudHMpO2lmKE50PT09SXx8TnQ9PT1YKXt2YXIgcnI9Ym4ubGVuZ3RoPjA/Ym5bMF06dm9pZCAwO2lmKHJyKXt2YXIgQWk9Wm9uZS5fX3N5bWJvbF9fKCJPTl9QUk9QRVJUWSIrcnIpO0plW0FpXT1mdFtBaV19fXJldHVybiBKZVtOdF0uYXBwbHkoSmUsYm4pfX0pKTpmdD1KZSxRLnBhdGNoT25Qcm9wZXJ0aWVzKGZ0LFsiY2xvc2UiLCJlcnJvciIsIm1lc3NhZ2UiLCJvcGVuIl0sQ3QpLGZ0fTt2YXIgbmU9cmUuV2ViU29ja2V0O2Zvcih2YXIgbWUgaW4gJCluZVttZV09JFttZV19KFEscmUpLFpvbmVbUS5zeW1ib2woInBhdGNoRXZlbnRzIildPSEwfX0obmUsWCl9KX07dmFyIGJhPWYoInpvbmVUYXNrIik7ZnVuY3Rpb24gYmMoUSxyZSxfZSxJKXt2YXIgWD1udWxsLCQ9bnVsbDtfZSs9STt2YXIgbmU9e307ZnVuY3Rpb24gbWUobHQpe3ZhciBKZT1sdC5kYXRhO3JldHVybiBKZS5hcmdzWzBdPWZ1bmN0aW9uKCl7cmV0dXJuIGx0Lmludm9rZS5hcHBseSh0aGlzLGFyZ3VtZW50cyl9LEplLmhhbmRsZUlkPVguYXBwbHkoUSxKZS5hcmdzKSxsdH1mdW5jdGlvbiBLZShsdCl7cmV0dXJuICQuY2FsbChRLGx0LmRhdGEuaGFuZGxlSWQpfVg9WShRLHJlKz1JLGZ1bmN0aW9uKGx0KXtyZXR1cm4gZnVuY3Rpb24oSmUsZnQpe2lmKCJmdW5jdGlvbiI9PXR5cGVvZiBmdFswXSl7dmFyIEN0PXtpc1BlcmlvZGljOiJJbnRlcnZhbCI9PT1JLGRlbGF5OiJUaW1lb3V0Ij09PUl8fCJJbnRlcnZhbCI9PT1JP2Z0WzFdfHwwOnZvaWQgMCxhcmdzOmZ0fSxJdD1mdFswXTtmdFswXT1mdW5jdGlvbigpe3RyeXtyZXR1cm4gSXQuYXBwbHkodGhpcyxhcmd1bWVudHMpfWZpbmFsbHl7Q3QuaXNQZXJpb2RpY3x8KCJudW1iZXIiPT10eXBlb2YgQ3QuaGFuZGxlSWQ/ZGVsZXRlIG5lW0N0LmhhbmRsZUlkXTpDdC5oYW5kbGVJZCYmKEN0LmhhbmRsZUlkW2JhXT1udWxsKSl9fTt2YXIgTnQ9aChyZSxmdFswXSxDdCxtZSxLZSk7aWYoIU50KXJldHVybiBOdDt2YXIgYm49TnQuZGF0YS5oYW5kbGVJZDtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIGJuP25lW2JuXT1OdDpibiYmKGJuW2JhXT1OdCksYm4mJmJuLnJlZiYmYm4udW5yZWYmJiJmdW5jdGlvbiI9PXR5cGVvZiBibi5yZWYmJiJmdW5jdGlvbiI9PXR5cGVvZiBibi51bnJlZiYmKE50LnJlZj1ibi5yZWYuYmluZChibiksTnQudW5yZWY9Ym4udW5yZWYuYmluZChibikpLCJudW1iZXIiPT10eXBlb2YgYm58fGJuP2JuOk50fXJldHVybiBsdC5hcHBseShRLGZ0KX19KSwkPVkoUSxfZSxmdW5jdGlvbihsdCl7cmV0dXJuIGZ1bmN0aW9uKEplLGZ0KXt2YXIgSXQsQ3Q9ZnRbMF07Im51bWJlciI9PXR5cGVvZiBDdD9JdD1uZVtDdF06KEl0PUN0JiZDdFtiYV0pfHwoSXQ9Q3QpLEl0JiYic3RyaW5nIj09dHlwZW9mIEl0LnR5cGU/Im5vdFNjaGVkdWxlZCIhPT1JdC5zdGF0ZSYmKEl0LmNhbmNlbEZuJiZJdC5kYXRhLmlzUGVyaW9kaWN8fDA9PT1JdC5ydW5Db3VudCkmJigibnVtYmVyIj09dHlwZW9mIEN0P2RlbGV0ZSBuZVtDdF06Q3QmJihDdFtiYV09bnVsbCksSXQuem9uZS5jYW5jZWxUYXNrKEl0KSk6bHQuYXBwbHkoUSxmdCl9fSl9Wm9uZS5fX2xvYWRfcGF0Y2goImxlZ2FjeSIsZnVuY3Rpb24oUSl7dmFyIHJlPVFbWm9uZS5fX3N5bWJvbF9fKCJsZWdhY3lQYXRjaCIpXTtyZSYmcmUoKX0pLFpvbmUuX19sb2FkX3BhdGNoKCJxdWV1ZU1pY3JvdGFzayIsZnVuY3Rpb24oUSxyZSxfZSl7X2UucGF0Y2hNZXRob2QoUSwicXVldWVNaWNyb3Rhc2siLGZ1bmN0aW9uKEkpe3JldHVybiBmdW5jdGlvbihYLCQpe3JlLmN1cnJlbnQuc2NoZWR1bGVNaWNyb1Rhc2soInF1ZXVlTWljcm90YXNrIiwkWzBdKX19KX0pLFpvbmUuX19sb2FkX3BhdGNoKCJ0aW1lcnMiLGZ1bmN0aW9uKFEpe3ZhciBfZT0iY2xlYXIiO2JjKFEsInNldCIsX2UsIlRpbWVvdXQiKSxiYyhRLCJzZXQiLF9lLCJJbnRlcnZhbCIpLGJjKFEsInNldCIsX2UsIkltbWVkaWF0ZSIpfSksWm9uZS5fX2xvYWRfcGF0Y2goInJlcXVlc3RBbmltYXRpb25GcmFtZSIsZnVuY3Rpb24oUSl7YmMoUSwicmVxdWVzdCIsImNhbmNlbCIsIkFuaW1hdGlvbkZyYW1lIiksYmMoUSwibW96UmVxdWVzdCIsIm1vekNhbmNlbCIsIkFuaW1hdGlvbkZyYW1lIiksYmMoUSwid2Via2l0UmVxdWVzdCIsIndlYmtpdENhbmNlbCIsIkFuaW1hdGlvbkZyYW1lIil9KSxab25lLl9fbG9hZF9wYXRjaCgiYmxvY2tpbmciLGZ1bmN0aW9uKFEscmUpe2Zvcih2YXIgX2U9WyJhbGVydCIsInByb21wdCIsImNvbmZpcm0iXSxJPTA7STxfZS5sZW5ndGg7SSsrKVkoUSxfZVtJXSxmdW5jdGlvbigkLG5lLG1lKXtyZXR1cm4gZnVuY3Rpb24oS2UsbHQpe3JldHVybiByZS5jdXJyZW50LnJ1bigkLFEsbHQsbWUpfX0pfSksWm9uZS5fX2xvYWRfcGF0Y2goIkV2ZW50VGFyZ2V0IixmdW5jdGlvbihRLHJlLF9lKXsoZnVuY3Rpb24oUSxyZSl7cmUucGF0Y2hFdmVudFByb3RvdHlwZShRLHJlKX0pKFEsX2UpLGZ1bmN0aW9uKFEscmUpe2lmKCFab25lW3JlLnN5bWJvbCgicGF0Y2hFdmVudFRhcmdldCIpXSl7Zm9yKHZhciBfZT1yZS5nZXRHbG9iYWxPYmplY3RzKCksST1fZS5ldmVudE5hbWVzLFg9X2Uuem9uZVN5bWJvbEV2ZW50TmFtZXMsJD1fZS5UUlVFX1NUUixuZT1fZS5GQUxTRV9TVFIsbWU9X2UuWk9ORV9TWU1CT0xfUFJFRklYLEtlPTA7S2U8SS5sZW5ndGg7S2UrKyl7dmFyIGx0PUlbS2VdLEN0PW1lKyhsdCtuZSksSXQ9bWUrKGx0KyQpO1hbbHRdPXt9LFhbbHRdW25lXT1DdCxYW2x0XVskXT1JdH12YXIgTnQ9US5FdmVudFRhcmdldDtOdCYmTnQucHJvdG90eXBlJiZyZS5wYXRjaEV2ZW50VGFyZ2V0KFEscmUsW050JiZOdC5wcm90b3R5cGVdKX19KFEsX2UpO3ZhciBJPVEuWE1MSHR0cFJlcXVlc3RFdmVudFRhcmdldDtJJiZJLnByb3RvdHlwZSYmX2UucGF0Y2hFdmVudFRhcmdldChRLF9lLFtJLnByb3RvdHlwZV0pfSksWm9uZS5fX2xvYWRfcGF0Y2goIk11dGF0aW9uT2JzZXJ2ZXIiLGZ1bmN0aW9uKFEscmUsX2Upe2RlKCJNdXRhdGlvbk9ic2VydmVyIiksZGUoIldlYktpdE11dGF0aW9uT2JzZXJ2ZXIiKX0pLFpvbmUuX19sb2FkX3BhdGNoKCJJbnRlcnNlY3Rpb25PYnNlcnZlciIsZnVuY3Rpb24oUSxyZSxfZSl7ZGUoIkludGVyc2VjdGlvbk9ic2VydmVyIil9KSxab25lLl9fbG9hZF9wYXRjaCgiRmlsZVJlYWRlciIsZnVuY3Rpb24oUSxyZSxfZSl7ZGUoIkZpbGVSZWFkZXIiKX0pLFpvbmUuX19sb2FkX3BhdGNoKCJvbl9wcm9wZXJ0eSIsZnVuY3Rpb24oUSxyZSxfZSl7IWZ1bmN0aW9uKFEscmUpe2lmKCghenx8dWUpJiYhWm9uZVtRLnN5bWJvbCgicGF0Y2hFdmVudHMiKV0pe3ZhciBfZT1yZS5fX1pvbmVfaWdub3JlX29uX3Byb3BlcnRpZXMsST1bXTtpZihmZSl7dmFyIFg9d2luZG93O0k9SS5jb25jYXQoWyJEb2N1bWVudCIsIlNWR0VsZW1lbnQiLCJFbGVtZW50IiwiSFRNTEVsZW1lbnQiLCJIVE1MQm9keUVsZW1lbnQiLCJIVE1MTWVkaWFFbGVtZW50IiwiSFRNTEZyYW1lU2V0RWxlbWVudCIsIkhUTUxGcmFtZUVsZW1lbnQiLCJIVE1MSUZyYW1lRWxlbWVudCIsIkhUTUxNYXJxdWVlRWxlbWVudCIsIldvcmtlciJdKTt2YXIgJD1mdW5jdGlvbigpe3RyeXt2YXIgUT14Lm5hdmlnYXRvci51c2VyQWdlbnQ7aWYoLTEhPT1RLmluZGV4T2YoIk1TSUUgIil8fC0xIT09US5pbmRleE9mKCJUcmlkZW50LyIpKXJldHVybiEwfWNhdGNoe31yZXR1cm4hMX0oKT9be3RhcmdldDpYLGlnbm9yZVByb3BlcnRpZXM6WyJlcnJvciJdfV06W107YnQoWCxobihYKSxfZSYmX2UuY29uY2F0KCQpLGUoWCkpfUk9SS5jb25jYXQoWyJYTUxIdHRwUmVxdWVzdCIsIlhNTEh0dHBSZXF1ZXN0RXZlbnRUYXJnZXQiLCJJREJJbmRleCIsIklEQlJlcXVlc3QiLCJJREJPcGVuREJSZXF1ZXN0IiwiSURCRGF0YWJhc2UiLCJJREJUcmFuc2FjdGlvbiIsIklEQkN1cnNvciIsIldlYlNvY2tldCJdKTtmb3IodmFyIG5lPTA7bmU8SS5sZW5ndGg7bmUrKyl7dmFyIG1lPXJlW0lbbmVdXTttZSYmbWUucHJvdG90eXBlJiZidChtZS5wcm90b3R5cGUsaG4obWUucHJvdG90eXBlKSxfZSl9fX0oX2UsUSl9KSxab25lLl9fbG9hZF9wYXRjaCgiY3VzdG9tRWxlbWVudHMiLGZ1bmN0aW9uKFEscmUsX2UpeyFmdW5jdGlvbihRLHJlKXt2YXIgX2U9cmUuZ2V0R2xvYmFsT2JqZWN0cygpOyhfZS5pc0Jyb3dzZXJ8fF9lLmlzTWl4KSYmUS5jdXN0b21FbGVtZW50cyYmImN1c3RvbUVsZW1lbnRzImluIFEmJnJlLnBhdGNoQ2FsbGJhY2tzKHJlLFEuY3VzdG9tRWxlbWVudHMsImN1c3RvbUVsZW1lbnRzIiwiZGVmaW5lIixbImNvbm5lY3RlZENhbGxiYWNrIiwiZGlzY29ubmVjdGVkQ2FsbGJhY2siLCJhZG9wdGVkQ2FsbGJhY2siLCJhdHRyaWJ1dGVDaGFuZ2VkQ2FsbGJhY2siXSl9KFEsX2UpfSksWm9uZS5fX2xvYWRfcGF0Y2goIlhIUiIsZnVuY3Rpb24oUSxyZSl7IWZ1bmN0aW9uKGx0KXt2YXIgSmU9bHQuWE1MSHR0cFJlcXVlc3Q7aWYoSmUpe3ZhciBmdD1KZS5wcm90b3R5cGUsSXQ9ZnRbYV0sTnQ9ZnRbbF07aWYoIUl0KXt2YXIgYm49bHQuWE1MSHR0cFJlcXVlc3RFdmVudFRhcmdldDtpZihibil7dmFyIHJyPWJuLnByb3RvdHlwZTtJdD1yclthXSxOdD1ycltsXX19dmFyIEFpPSJyZWFkeXN0YXRlY2hhbmdlIixNbj0ic2NoZWR1bGVkIixhaT1ZKGZ0LCJvcGVuIixmdW5jdGlvbigpe3JldHVybiBmdW5jdGlvbihVbixTaSl7cmV0dXJuIFVuW0ldPTA9PVNpWzJdLFVuW25lXT1TaVsxXSxhaS5hcHBseShVbixTaSl9fSksJG49ZigiZmV0Y2hUYXNrQWJvcnRpbmciKSxZdD1mKCJmZXRjaFRhc2tTY2hlZHVsaW5nIiksWWk9WShmdCwic2VuZCIsZnVuY3Rpb24oKXtyZXR1cm4gZnVuY3Rpb24oVW4sU2kpe2lmKCEwPT09cmUuY3VycmVudFtZdF18fFVuW0ldKXJldHVybiBZaS5hcHBseShVbixTaSk7dmFyIGpuPXt0YXJnZXQ6VW4sdXJsOlVuW25lXSxpc1BlcmlvZGljOiExLGFyZ3M6U2ksYWJvcnRlZDohMX0sRHI9aCgiWE1MSHR0cFJlcXVlc3Quc2VuZCIsT3Qsam4sSG4sd2kpO1VuJiYhMD09PVVuW21lXSYmIWpuLmFib3J0ZWQmJkRyLnN0YXRlPT09TW4mJkRyLmludm9rZSgpfX0pLEFuPVkoZnQsImFib3J0IixmdW5jdGlvbigpe3JldHVybiBmdW5jdGlvbihVbixTaSl7dmFyIGpuPWZ1bmN0aW9uKFVuKXtyZXR1cm4gVW5bX2VdfShVbik7aWYoam4mJiJzdHJpbmciPT10eXBlb2Ygam4udHlwZSl7aWYobnVsbD09am4uY2FuY2VsRm58fGpuLmRhdGEmJmpuLmRhdGEuYWJvcnRlZClyZXR1cm47am4uem9uZS5jYW5jZWxUYXNrKGpuKX1lbHNlIGlmKCEwPT09cmUuY3VycmVudFskbl0pcmV0dXJuIEFuLmFwcGx5KFVuLFNpKX19KX1mdW5jdGlvbiBIbihVbil7dmFyIFNpPVVuLmRhdGEsam49U2kudGFyZ2V0O2puWyRdPSExLGpuW21lXT0hMTt2YXIgRHI9am5bWF07SXR8fChJdD1qblthXSxOdD1qbltsXSksRHImJk50LmNhbGwoam4sQWksRHIpO3ZhciBicj1qbltYXT1mdW5jdGlvbigpe2lmKGpuLnJlYWR5U3RhdGU9PT1qbi5ET05FKWlmKCFTaS5hYm9ydGVkJiZqblskXSYmVW4uc3RhdGU9PT1Nbil7dmFyIFZyPWpuW3JlLl9fc3ltYm9sX18oImxvYWRmYWxzZSIpXTtpZigwIT09am4uc3RhdHVzJiZWciYmVnIubGVuZ3RoPjApe3ZhciBBcj1Vbi5pbnZva2U7VW4uaW52b2tlPWZ1bmN0aW9uKCl7Zm9yKHZhciBTdD1qbltyZS5fX3N5bWJvbF9fKCJsb2FkZmFsc2UiKV0sd2U9MDt3ZTxTdC5sZW5ndGg7d2UrKylTdFt3ZV09PT1VbiYmU3Quc3BsaWNlKHdlLDEpOyFTaS5hYm9ydGVkJiZVbi5zdGF0ZT09PU1uJiZBci5jYWxsKFVuKX0sVnIucHVzaChVbil9ZWxzZSBVbi5pbnZva2UoKX1lbHNlIVNpLmFib3J0ZWQmJiExPT09am5bJF0mJihqblttZV09ITApfTtyZXR1cm4gSXQuY2FsbChqbixBaSxiciksam5bX2VdfHwoam5bX2VdPVVuKSxZaS5hcHBseShqbixTaS5hcmdzKSxqblskXT0hMCxVbn1mdW5jdGlvbiBPdCgpe31mdW5jdGlvbiB3aShVbil7dmFyIFNpPVVuLmRhdGE7cmV0dXJuIFNpLmFib3J0ZWQ9ITAsQW4uYXBwbHkoU2kudGFyZ2V0LFNpLmFyZ3MpfX0oUSk7dmFyIF9lPWYoInhoclRhc2siKSxJPWYoInhoclN5bmMiKSxYPWYoInhockxpc3RlbmVyIiksJD1mKCJ4aHJTY2hlZHVsZWQiKSxuZT1mKCJ4aHJVUkwiKSxtZT1mKCJ4aHJFcnJvckJlZm9yZVNjaGVkdWxlZCIpfSksWm9uZS5fX2xvYWRfcGF0Y2goImdlb2xvY2F0aW9uIixmdW5jdGlvbihRKXtRLm5hdmlnYXRvciYmUS5uYXZpZ2F0b3IuZ2VvbG9jYXRpb24mJmZ1bmN0aW9uKFEscmUpe2Zvcih2YXIgX2U9US5jb25zdHJ1Y3Rvci5uYW1lLEk9ZnVuY3Rpb24oJCl7dmFyIGx0LEplLG5lPXJlWyRdLG1lPVFbbmVdO2lmKG1lKXtpZighayhuKFEsbmUpKSlyZXR1cm4iY29udGludWUiO1FbbmVdPShKZT1mdW5jdGlvbigpe3JldHVybiBsdC5hcHBseSh0aGlzLEQoYXJndW1lbnRzLF9lKyIuIituZSkpfSxsZShKZSxsdD1tZSksSmUpfX0sWD0wO1g8cmUubGVuZ3RoO1grKylJKFgpfShRLm5hdmlnYXRvci5nZW9sb2NhdGlvbixbImdldEN1cnJlbnRQb3NpdGlvbiIsIndhdGNoUG9zaXRpb24iXSl9KSxab25lLl9fbG9hZF9wYXRjaCgiUHJvbWlzZVJlamVjdGlvbkV2ZW50IixmdW5jdGlvbihRLHJlKXtmdW5jdGlvbiBfZShJKXtyZXR1cm4gZnVuY3Rpb24oWCl7Y2UoUSxJKS5mb3JFYWNoKGZ1bmN0aW9uKG5lKXt2YXIgbWU9US5Qcm9taXNlUmVqZWN0aW9uRXZlbnQ7aWYobWUpe3ZhciBLZT1uZXcgbWUoSSx7cHJvbWlzZTpYLnByb21pc2UscmVhc29uOlgucmVqZWN0aW9ufSk7bmUuaW52b2tlKEtlKX19KX19US5Qcm9taXNlUmVqZWN0aW9uRXZlbnQmJihyZVtmKCJ1bmhhbmRsZWRQcm9taXNlUmVqZWN0aW9uSGFuZGxlciIpXT1fZSgidW5oYW5kbGVkcmVqZWN0aW9uIikscmVbZigicmVqZWN0aW9uSGFuZGxlZEhhbmRsZXIiKV09X2UoInJlamVjdGlvbmhhbmRsZWQiKSl9KX0sImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUobik6bigpfSksSGI9UmQoJG89PnsidXNlIHN0cmljdCI7dmFyIEZKZT0kbyYmJG8uX192YWx1ZXN8fGZ1bmN0aW9uKG4pe3ZhciB0PSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJm5bU3ltYm9sLml0ZXJhdG9yXSxlPTA7cmV0dXJuIHQ/dC5jYWxsKG4pOntuZXh0OmZ1bmN0aW9uKCl7cmV0dXJuIG4mJmU+PW4ubGVuZ3RoJiYobj12b2lkIDApLHt2YWx1ZTpuJiZuW2UrK10sZG9uZTohbn19fX07ZnVuY3Rpb24gVmhlKG4sdCl7cmV0dXJuIE1hdGguZmxvb3IodCgpKm4pfWZ1bmN0aW9uIGxGKG4pe2Zvcih2YXIgdD1bXSxlPTA7ZTxuO2UrKyl0LnB1c2godm9pZCAwKTtyZXR1cm4gdH1mdW5jdGlvbiBRRyhuLHQpe3JldHVybiBsRihuKS5tYXAoZnVuY3Rpb24oKXtyZXR1cm4gdH0pfWZ1bmN0aW9uIEhoZShuKXtyZXR1cm4gUUcobiwwKX1mdW5jdGlvbiBVaGUobil7cmV0dXJuIG4ucmVkdWNlKGZ1bmN0aW9uKHQsZSl7cmV0dXJuIHQrZX0pfU9iamVjdC5kZWZpbmVQcm9wZXJ0eSgkbywiX19lc01vZHVsZSIse3ZhbHVlOiEwfSksJG8udGF1UmFuZEludD1WaGUsJG8udGF1UmFuZD1mdW5jdGlvbihuKXtyZXR1cm4gbigpfSwkby5ub3JtPWZ1bmN0aW9uKG4pe3ZhciB0LGUsaT0wO3RyeXtmb3IodmFyIHI9RkplKG4pLG89ci5uZXh0KCk7IW8uZG9uZTtvPXIubmV4dCgpKWkrPU1hdGgucG93KG8udmFsdWUsMil9Y2F0Y2goYSl7dD17ZXJyb3I6YX19ZmluYWxseXt0cnl7byYmIW8uZG9uZSYmKGU9ci5yZXR1cm4pJiZlLmNhbGwocil9ZmluYWxseXtpZih0KXRocm93IHQuZXJyb3J9fXJldHVybiBNYXRoLnNxcnQoaSl9LCRvLmVtcHR5PWxGLCRvLnJhbmdlPWZ1bmN0aW9uKG4pe3JldHVybiBsRihuKS5tYXAoZnVuY3Rpb24odCxlKXtyZXR1cm4gZX0pfSwkby5maWxsZWQ9UUcsJG8uemVyb3M9SGhlLCRvLm9uZXM9ZnVuY3Rpb24obil7cmV0dXJuIFFHKG4sMSl9LCRvLmxpbmVhcj1mdW5jdGlvbihuLHQsZSl7cmV0dXJuIGxGKGUpLm1hcChmdW5jdGlvbihpLHIpe3JldHVybiBuK3IqKCh0LW4pLyhlLTEpKX0pfSwkby5zdW09VWhlLCRvLm1lYW49ZnVuY3Rpb24obil7cmV0dXJuIFVoZShuKS9uLmxlbmd0aH0sJG8ubWF4PWZ1bmN0aW9uKG4pe2Zvcih2YXIgdD0wLGU9MDtlPG4ubGVuZ3RoO2UrKyl0PW5bZV0+dD9uW2VdOnQ7cmV0dXJuIHR9LCRvLm1heDJkPWZ1bmN0aW9uKG4pe2Zvcih2YXIgdD0wLGU9MDtlPG4ubGVuZ3RoO2UrKylmb3IodmFyIGk9MDtpPG5bZV0ubGVuZ3RoO2krKyl0PW5bZV1baV0+dD9uW2VdW2ldOnQ7cmV0dXJuIHR9LCRvLnJlamVjdGlvblNhbXBsZT1mdW5jdGlvbihuLHQsZSl7Zm9yKHZhciBpPUhoZShuKSxyPTA7cjxuO3IrKylmb3IodmFyIG89ITA7bzspe2Zvcih2YXIgcz1WaGUodCxlKSxhPSExLGw9MDtsPHI7bCsrKWlmKHM9PT1pW2xdKXthPSEwO2JyZWFrfWF8fChvPSExKSxpW3JdPXN9cmV0dXJuIGl9LCRvLnJlc2hhcGUyZD1mdW5jdGlvbihuLHQsZSl7dmFyIGk9W10sbz0wO2lmKG4ubGVuZ3RoIT09dCplKXRocm93IG5ldyBFcnJvcigiQXJyYXkgZGltZW5zaW9ucyBtdXN0IG1hdGNoIGlucHV0IGxlbmd0aC4iKTtmb3IodmFyIHM9MDtzPHQ7cysrKXtmb3IodmFyIGE9W10sbD0wO2w8ZTtsKyspYS5wdXNoKG5bb10pLG8rPTE7aS5wdXNoKGEpfXJldHVybiBpfX0pLFpHPVJkKGZ1PT57InVzZSBzdHJpY3QiO3ZhciBxSmU9ZnUmJmZ1Ll9faW1wb3J0U3Rhcnx8ZnVuY3Rpb24obil7aWYobiYmbi5fX2VzTW9kdWxlKXJldHVybiBuO3ZhciB0PXt9O2lmKG51bGwhPW4pZm9yKHZhciBlIGluIG4pT2JqZWN0Lmhhc093blByb3BlcnR5LmNhbGwobixlKSYmKHRbZV09bltlXSk7cmV0dXJuIHQuZGVmYXVsdD1uLHR9O09iamVjdC5kZWZpbmVQcm9wZXJ0eShmdSwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIHdFPXFKZShIYigpKTtmdW5jdGlvbiB6aGUobix0KXt2YXIgZT1mdW5jdGlvbihyKXtyZXR1cm4gd0UuZW1wdHkobikubWFwKGZ1bmN0aW9uKCl7cmV0dXJuIHdFLmZpbGxlZCh0LHIpfSl9LGk9W107cmV0dXJuIGkucHVzaChlKC0xKSksaS5wdXNoKGUoMS8wKSksaS5wdXNoKGUoMCkpLGl9ZnVuY3Rpb24gS0cobix0LGUsaSxyKXt0PU1hdGguZmxvb3IodCk7dmFyIG89blswXVt0XTtpZihlPj1uWzFdW3RdWzBdKXJldHVybiAwO2Zvcih2YXIgbD0wO2w8by5sZW5ndGg7bCsrKWlmKGk9PT1vW2xdKXJldHVybiAwO3JldHVybiBqaGUobix0LGUsaSxyKX1mdW5jdGlvbiBqaGUobix0LGUsaSxyKXt2YXIgbz1uWzBdW3RdLHM9blsxXVt0XSxhPW5bMl1bdF07aWYoZT49c1swXSlyZXR1cm4gMDtzWzBdPWUsb1swXT1pLGFbMF09cjtmb3IodmFyIGw9MCxjPTA7Oyl7dmFyIHU9MipsKzEsZD11KzEscD1uWzBdWzBdLmxlbmd0aDtpZih1Pj1wKWJyZWFrO2lmKGQ+PXApe2lmKCEoc1t1XT5lKSlicmVhaztjPXV9ZWxzZSBpZihzW3VdPj1zW2RdKXtpZighKGU8c1t1XSkpYnJlYWs7Yz11fWVsc2V7aWYoIShlPHNbZF0pKWJyZWFrO2M9ZH1zW2xdPXNbY10sb1tsXT1vW2NdLGFbbF09YVtjXSxsPWN9cmV0dXJuIHNbbF09ZSxvW2xdPWksYVtsXT1yLDF9ZnVuY3Rpb24gS0plKG4sdCxlLGkpe2Zvcig7MippKzE8ZTspe3ZhciByPTIqaSsxLG89cisxLHM9aTtpZihuW3NdPG5bcl0mJihzPXIpLG88ZSYmbltzXTxuW29dJiYocz1vKSxzPT09aSlicmVhazt2YXIgYT1uW2ldO25baV09bltzXSxuW3NdPWE7dmFyIGw9dFtpXTt0W2ldPXRbc10sdFtzXT1sLGk9c319ZnUubWFrZUhlYXA9emhlLGZ1LnJlamVjdGlvblNhbXBsZT1mdW5jdGlvbihuLHQsZSl7Zm9yKHZhciBpPXdFLnplcm9zKG4pLHI9MDtyPG47cisrKXtmb3IodmFyIG89ITAscz0wO287KXtzPXdFLnRhdVJhbmRJbnQodCxlKTtmb3IodmFyIGE9ITEsbD0wO2w8cjtsKyspaWYocz09PWlbbF0pe2E9ITA7YnJlYWt9YXx8KG89ITEpfWlbcl09c31yZXR1cm4gaX0sZnUuaGVhcFB1c2g9S0csZnUudW5jaGVja2VkSGVhcFB1c2g9amhlLGZ1LmJ1aWxkQ2FuZGlkYXRlcz1mdW5jdGlvbihuLHQsZSxpLHIpe2Zvcih2YXIgbz16aGUodCxpKSxzPTA7czx0O3MrKylmb3IodmFyIGE9MDthPGU7YSsrKWlmKCEoblswXVtzXVthXTwwKSl7dmFyIGw9blswXVtzXVthXSxjPW5bMl1bc11bYV0sdT13RS50YXVSYW5kKHIpO0tHKG8scyx1LGwsYyksS0cobyxsLHUscyxjKSxuWzJdW3NdW2FdPTB9cmV0dXJuIG99LGZ1LmRlaGVhcFNvcnQ9ZnVuY3Rpb24obil7Zm9yKHZhciB0PW5bMF0sZT1uWzFdLGk9MDtpPHQubGVuZ3RoO2krKylmb3IodmFyIHI9dFtpXSxvPWVbaV0scz0wO3M8ci5sZW5ndGgtMTtzKyspe3ZhciBhPXIubGVuZ3RoLXMtMSxsPW8ubGVuZ3RoLXMtMSxjPXJbMF07clswXT1yW2FdLHJbYV09Yzt2YXIgdT1vWzBdO29bMF09b1tsXSxvW2xdPXUsS0plKG8scixsLDApfXJldHVybntpbmRpY2VzOnQsd2VpZ2h0czplfX0sZnUuc21hbGxlc3RGbGFnZ2VkPWZ1bmN0aW9uKG4sdCl7Zm9yKHZhciBlPW5bMF1bdF0saT1uWzFdW3RdLHI9blsyXVt0XSxvPTEvMCxzPS0xLGE9MDthPmUubGVuZ3RoO2ErKykxPT09clthXSYmaVthXTxvJiYobz1pW2FdLHM9YSk7cmV0dXJuIHM+PTA/KHJbc109MCxNYXRoLmZsb29yKGVbc10pKTotMX19KSxKRz1SZChlcz0+eyJ1c2Ugc3RyaWN0Ijt2YXIgY0Y9ZXMmJmVzLl9fcmVhZHx8ZnVuY3Rpb24obix0KXt2YXIgZT0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZuW1N5bWJvbC5pdGVyYXRvcl07aWYoIWUpcmV0dXJuIG47dmFyIHIscyxpPWUuY2FsbChuKSxvPVtdO3RyeXtmb3IoOyh2b2lkIDA9PT10fHx0LS0gPjApJiYhKHI9aS5uZXh0KCkpLmRvbmU7KW8ucHVzaChyLnZhbHVlKX1jYXRjaChhKXtzPXtlcnJvcjphfX1maW5hbGx5e3RyeXtyJiYhci5kb25lJiYoZT1pLnJldHVybikmJmUuY2FsbChpKX1maW5hbGx5e2lmKHMpdGhyb3cgcy5lcnJvcn19cmV0dXJuIG99LEpKZT1lcyYmZXMuX192YWx1ZXN8fGZ1bmN0aW9uKG4pe3ZhciB0PSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJm5bU3ltYm9sLml0ZXJhdG9yXSxlPTA7cmV0dXJuIHQ/dC5jYWxsKG4pOntuZXh0OmZ1bmN0aW9uKCl7cmV0dXJuIG4mJmU+PW4ubGVuZ3RoJiYobj12b2lkIDApLHt2YWx1ZTpuJiZuW2UrK10sZG9uZTohbn19fX0sJEplPWVzJiZlcy5fX2ltcG9ydFN0YXJ8fGZ1bmN0aW9uKG4pe2lmKG4mJm4uX19lc01vZHVsZSlyZXR1cm4gbjt2YXIgdD17fTtpZihudWxsIT1uKWZvcih2YXIgZSBpbiBuKU9iamVjdC5oYXNPd25Qcm9wZXJ0eS5jYWxsKG4sZSkmJih0W2VdPW5bZV0pO3JldHVybiB0LmRlZmF1bHQ9bix0fTtPYmplY3QuZGVmaW5lUHJvcGVydHkoZXMsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBTRSxHaGU9JEplKEhiKCkpLFViPWZ1bmN0aW9uKCl7ZnVuY3Rpb24gbih0LGUsaSxyKXtpZih0aGlzLmVudHJpZXM9bmV3IE1hcCx0aGlzLm5Sb3dzPTAsdGhpcy5uQ29scz0wLHQubGVuZ3RoIT09ZS5sZW5ndGh8fHQubGVuZ3RoIT09aS5sZW5ndGgpdGhyb3cgbmV3IEVycm9yKCJyb3dzLCBjb2xzIGFuZCB2YWx1ZXMgYXJyYXlzIG11c3QgYWxsIGhhdmUgdGhlIHNhbWUgbGVuZ3RoIik7dGhpcy5uUm93cz1yWzBdLHRoaXMubkNvbHM9clsxXTtmb3IodmFyIG89MDtvPGkubGVuZ3RoO28rKyl7dmFyIHM9dFtvXSxhPWVbb107dGhpcy5jaGVja0RpbXMocyxhKTt2YXIgbD10aGlzLm1ha2VLZXkocyxhKTt0aGlzLmVudHJpZXMuc2V0KGwse3ZhbHVlOmlbb10scm93OnMsY29sOmF9KX19cmV0dXJuIG4ucHJvdG90eXBlLm1ha2VLZXk9ZnVuY3Rpb24odCxlKXtyZXR1cm4gdCsiOiIrZX0sbi5wcm90b3R5cGUuY2hlY2tEaW1zPWZ1bmN0aW9uKHQsZSl7aWYoISh0PHRoaXMublJvd3MmJmU8dGhpcy5uQ29scykpdGhyb3cgbmV3IEVycm9yKCJyb3cgYW5kL29yIGNvbCBzcGVjaWZpZWQgb3V0c2lkZSBvZiBtYXRyaXggZGltZW5zaW9ucyIpfSxuLnByb3RvdHlwZS5zZXQ9ZnVuY3Rpb24odCxlLGkpe3RoaXMuY2hlY2tEaW1zKHQsZSk7dmFyIHI9dGhpcy5tYWtlS2V5KHQsZSk7dGhpcy5lbnRyaWVzLmhhcyhyKT90aGlzLmVudHJpZXMuZ2V0KHIpLnZhbHVlPWk6dGhpcy5lbnRyaWVzLnNldChyLHt2YWx1ZTppLHJvdzp0LGNvbDplfSl9LG4ucHJvdG90eXBlLmdldD1mdW5jdGlvbih0LGUsaSl7dm9pZCAwPT09aSYmKGk9MCksdGhpcy5jaGVja0RpbXModCxlKTt2YXIgcj10aGlzLm1ha2VLZXkodCxlKTtyZXR1cm4gdGhpcy5lbnRyaWVzLmhhcyhyKT90aGlzLmVudHJpZXMuZ2V0KHIpLnZhbHVlOml9LG4ucHJvdG90eXBlLmdldEFsbD1mdW5jdGlvbih0KXt2b2lkIDA9PT10JiYodD0hMCk7dmFyIGU9W107cmV0dXJuIHRoaXMuZW50cmllcy5mb3JFYWNoKGZ1bmN0aW9uKGkpe2UucHVzaChpKX0pLHQmJmUuc29ydChmdW5jdGlvbihpLHIpe3JldHVybiBpLnJvdz09PXIucm93P2kuY29sLXIuY29sOmkucm93LXIucm93fSksZX0sbi5wcm90b3R5cGUuZ2V0RGltcz1mdW5jdGlvbigpe3JldHVyblt0aGlzLm5Sb3dzLHRoaXMubkNvbHNdfSxuLnByb3RvdHlwZS5nZXRSb3dzPWZ1bmN0aW9uKCl7cmV0dXJuIEFycmF5LmZyb20odGhpcy5lbnRyaWVzLGZ1bmN0aW9uKHQpe3JldHVybiBjRih0LDIpWzFdLnJvd30pfSxuLnByb3RvdHlwZS5nZXRDb2xzPWZ1bmN0aW9uKCl7cmV0dXJuIEFycmF5LmZyb20odGhpcy5lbnRyaWVzLGZ1bmN0aW9uKHQpe3JldHVybiBjRih0LDIpWzFdLmNvbH0pfSxuLnByb3RvdHlwZS5nZXRWYWx1ZXM9ZnVuY3Rpb24oKXtyZXR1cm4gQXJyYXkuZnJvbSh0aGlzLmVudHJpZXMsZnVuY3Rpb24odCl7cmV0dXJuIGNGKHQsMilbMV0udmFsdWV9KX0sbi5wcm90b3R5cGUuZm9yRWFjaD1mdW5jdGlvbih0KXt0aGlzLmVudHJpZXMuZm9yRWFjaChmdW5jdGlvbihlKXtyZXR1cm4gdChlLnZhbHVlLGUucm93LGUuY29sKX0pfSxuLnByb3RvdHlwZS5tYXA9ZnVuY3Rpb24odCl7dmFyIGU9W107dGhpcy5lbnRyaWVzLmZvckVhY2goZnVuY3Rpb24ocil7ZS5wdXNoKHQoci52YWx1ZSxyLnJvdyxyLmNvbCkpfSk7dmFyIGk9W3RoaXMublJvd3MsdGhpcy5uQ29sc107cmV0dXJuIG5ldyBuKHRoaXMuZ2V0Um93cygpLHRoaXMuZ2V0Q29scygpLGUsaSl9LG4ucHJvdG90eXBlLnRvQXJyYXk9ZnVuY3Rpb24oKXt2YXIgdD10aGlzLGk9R2hlLmVtcHR5KHRoaXMublJvd3MpLm1hcChmdW5jdGlvbigpe3JldHVybiBHaGUuemVyb3ModC5uQ29scyl9KTtyZXR1cm4gdGhpcy5lbnRyaWVzLmZvckVhY2goZnVuY3Rpb24ocil7aVtyLnJvd11bci5jb2xdPXIudmFsdWV9KSxpfSxufSgpO2VzLlNwYXJzZU1hdHJpeD1VYixlcy50cmFuc3Bvc2U9ZnVuY3Rpb24obil7dmFyIHQ9W10sZT1bXSxpPVtdO3JldHVybiBuLmZvckVhY2goZnVuY3Rpb24obyxzLGEpe3QucHVzaChzKSxlLnB1c2goYSksaS5wdXNoKG8pfSksbmV3IFViKGUsdCxpLFtuLm5Db2xzLG4ublJvd3NdKX0sZXMuaWRlbnRpdHk9ZnVuY3Rpb24obil7Zm9yKHZhciBlPWNGKG4sMSlbMF0saT1uZXcgVWIoW10sW10sW10sbikscj0wO3I8ZTtyKyspaS5zZXQocixyLDEpO3JldHVybiBpfSxlcy5wYWlyd2lzZU11bHRpcGx5PWZ1bmN0aW9uKG4sdCl7cmV0dXJuIHVGKG4sdCxmdW5jdGlvbihlLGkpe3JldHVybiBlKml9KX0sZXMuYWRkPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIHVGKG4sdCxmdW5jdGlvbihlLGkpe3JldHVybiBlK2l9KX0sZXMuc3VidHJhY3Q9ZnVuY3Rpb24obix0KXtyZXR1cm4gdUYobix0LGZ1bmN0aW9uKGUsaSl7cmV0dXJuIGUtaX0pfSxlcy5tYXhpbXVtPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIHVGKG4sdCxmdW5jdGlvbihlLGkpe3JldHVybiBlPmk/ZTppfSl9LGVzLm11bHRpcGx5U2NhbGFyPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIG4ubWFwKGZ1bmN0aW9uKGUpe3JldHVybiBlKnR9KX0sZXMuZWxpbWluYXRlWmVyb3M9ZnVuY3Rpb24obil7Zm9yKHZhciB0PW5ldyBTZXQsZT1uLmdldFZhbHVlcygpLGk9bi5nZXRSb3dzKCkscj1uLmdldENvbHMoKSxvPTA7bzxlLmxlbmd0aDtvKyspMD09PWVbb10mJnQuYWRkKG8pO3ZhciBzPWZ1bmN0aW9uKHUsZCl7cmV0dXJuIXQuaGFzKGQpfSxhPWUuZmlsdGVyKHMpLGw9aS5maWx0ZXIocyksYz1yLmZpbHRlcihzKTtyZXR1cm4gbmV3IFViKGwsYyxhLG4uZ2V0RGltcygpKX0sZXMubm9ybWFsaXplPWZ1bmN0aW9uKG4sdCl7dm9pZCAwPT09dCYmKHQ9ImwyIik7dmFyIGUsaSxyPWMkZVt0XSxvPW5ldyBNYXA7bi5mb3JFYWNoKGZ1bmN0aW9uKGQscCxoKXt2YXIgZj1vLmdldChwKXx8W107Zi5wdXNoKGgpLG8uc2V0KHAsZil9KTt2YXIgcz1uZXcgVWIoW10sW10sW10sbi5nZXREaW1zKCkpLGE9ZnVuY3Rpb24oZCl7Zm9yKHZhciBwPW8uZ2V0KGQpLnNvcnQoKSxoPXAubWFwKGZ1bmN0aW9uKHgpe3JldHVybiBuLmdldChkLHgpfSksZj1yKGgpLG09MDttPGYubGVuZ3RoO20rKylzLnNldChkLHBbbV0sZlttXSl9O3RyeXtmb3IodmFyIGw9SkplKG8ua2V5cygpKSxjPWwubmV4dCgpOyFjLmRvbmU7Yz1sLm5leHQoKSlhKGMudmFsdWUpfWNhdGNoKGQpe2U9e2Vycm9yOmR9fWZpbmFsbHl7dHJ5e2MmJiFjLmRvbmUmJihpPWwucmV0dXJuKSYmaS5jYWxsKGwpfWZpbmFsbHl7aWYoZSl0aHJvdyBlLmVycm9yfX1yZXR1cm4gc307dmFyIGMkZT0oKFNFPXt9KS5tYXg9ZnVuY3Rpb24obil7Zm9yKHZhciB0PS0xLzAsZT0wO2U8bi5sZW5ndGg7ZSsrKXQ9bltlXT50P25bZV06dDtyZXR1cm4gbi5tYXAoZnVuY3Rpb24oaSl7cmV0dXJuIGkvdH0pfSxTRS5sMT1mdW5jdGlvbihuKXtmb3IodmFyIHQ9MCxlPTA7ZTxuLmxlbmd0aDtlKyspdCs9bltlXTtyZXR1cm4gbi5tYXAoZnVuY3Rpb24oaSl7cmV0dXJuIGkvdH0pfSxTRS5sMj1mdW5jdGlvbihuKXtmb3IodmFyIHQ9MCxlPTA7ZTxuLmxlbmd0aDtlKyspdCs9TWF0aC5wb3cobltlXSwyKTtyZXR1cm4gbi5tYXAoZnVuY3Rpb24oaSl7cmV0dXJuIE1hdGguc3FydChNYXRoLnBvdyhpLDIpL3QpfSl9LFNFKTtmdW5jdGlvbiB1RihuLHQsZSl7Zm9yKHZhciBpPW5ldyBTZXQscj1bXSxvPVtdLHM9W10sYT1mdW5jdGlvbihELFQpe3IucHVzaChEKSxvLnB1c2goVCk7dmFyIGs9ZShuLmdldChELFQpLHQuZ2V0KEQsVCkpO3MucHVzaChrKX0sbD1uLmdldFZhbHVlcygpLGM9bi5nZXRSb3dzKCksdT1uLmdldENvbHMoKSxkPTA7ZDxsLmxlbmd0aDtkKyspaS5hZGQoKHA9Y1tkXSkrIjoiKyhoPXVbZF0pKSxhKHAsaCk7dmFyIG09dC5nZXRWYWx1ZXMoKSx4PXQuZ2V0Um93cygpLGc9dC5nZXRDb2xzKCk7Zm9yKGQ9MDtkPG0ubGVuZ3RoO2QrKyl7dmFyIHAsaDtpLmhhcygocD14W2RdKSsiOiIrKGg9Z1tkXSkpfHxhKHAsaCl9cmV0dXJuIG5ldyBVYihyLG8scyxbbi5uUm93cyxuLm5Db2xzXSl9ZXMuZ2V0Q1NSPWZ1bmN0aW9uKG4pe3ZhciB0PVtdO24uZm9yRWFjaChmdW5jdGlvbihkLHAsaCl7dC5wdXNoKHt2YWx1ZTpkLHJvdzpwLGNvbDpofSl9KSx0LnNvcnQoZnVuY3Rpb24oZCxwKXtyZXR1cm4gZC5yb3c9PT1wLnJvdz9kLmNvbC1wLmNvbDpkLnJvdy1wLnJvd30pO2Zvcih2YXIgZT1bXSxpPVtdLHI9W10sbz0tMSxzPTA7czx0Lmxlbmd0aDtzKyspe3ZhciBhPXRbc10sbD1hLnJvdyxjPWEuY29sLHU9YS52YWx1ZTtsIT09byYmKG89bCxyLnB1c2gocykpLGUucHVzaChjKSxpLnB1c2godSl9cmV0dXJue2luZGljZXM6ZSx2YWx1ZXM6aSxpbmRwdHI6cn19fSksaTY9UmQoWWE9PnsidXNlIHN0cmljdCI7dmFyIGQkZT1ZYSYmWWEuX19yZWFkfHxmdW5jdGlvbihuLHQpe3ZhciBlPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJm5bU3ltYm9sLml0ZXJhdG9yXTtpZighZSlyZXR1cm4gbjt2YXIgcixzLGk9ZS5jYWxsKG4pLG89W107dHJ5e2Zvcig7KHZvaWQgMD09PXR8fHQtLSA+MCkmJiEocj1pLm5leHQoKSkuZG9uZTspby5wdXNoKHIudmFsdWUpfWNhdGNoKGEpe3M9e2Vycm9yOmF9fWZpbmFsbHl7dHJ5e3ImJiFyLmRvbmUmJihlPWkucmV0dXJuKSYmZS5jYWxsKGkpfWZpbmFsbHl7aWYocyl0aHJvdyBzLmVycm9yfX1yZXR1cm4gb30sV2hlPVlhJiZZYS5fX3NwcmVhZHx8ZnVuY3Rpb24oKXtmb3IodmFyIG49W10sdD0wO3Q8YXJndW1lbnRzLmxlbmd0aDt0Kyspbj1uLmNvbmNhdChkJGUoYXJndW1lbnRzW3RdKSk7cmV0dXJuIG59LHAkZT1ZYSYmWWEuX192YWx1ZXN8fGZ1bmN0aW9uKG4pe3ZhciB0PSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJm5bU3ltYm9sLml0ZXJhdG9yXSxlPTA7cmV0dXJuIHQ/dC5jYWxsKG4pOntuZXh0OmZ1bmN0aW9uKCl7cmV0dXJuIG4mJmU+PW4ubGVuZ3RoJiYobj12b2lkIDApLHt2YWx1ZTpuJiZuW2UrK10sZG9uZTohbn19fX0saCRlPVlhJiZZYS5fX2ltcG9ydFN0YXJ8fGZ1bmN0aW9uKG4pe2lmKG4mJm4uX19lc01vZHVsZSlyZXR1cm4gbjt2YXIgdD17fTtpZihudWxsIT1uKWZvcih2YXIgZSBpbiBuKU9iamVjdC5oYXNPd25Qcm9wZXJ0eS5jYWxsKG4sZSkmJih0W2VdPW5bZV0pO3JldHVybiB0LmRlZmF1bHQ9bix0fTtPYmplY3QuZGVmaW5lUHJvcGVydHkoWWEsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pO3ZhciBYcz1oJGUoSGIoKSkscWhlPWZ1bmN0aW9uKHQsZSxpLHIpe3RoaXMuaHlwZXJwbGFuZXM9dCx0aGlzLm9mZnNldHM9ZSx0aGlzLmNoaWxkcmVuPWksdGhpcy5pbmRpY2VzPXJ9O2Z1bmN0aW9uICRHKG4sdCxlLGkscil7aWYodm9pZCAwPT09ZSYmKGU9MzApLHQubGVuZ3RoPmUpe3ZhciBvPWZ1bmN0aW9uKG4sdCxlKXtmb3IodmFyIGk9blswXS5sZW5ndGgscj1Ycy50YXVSYW5kSW50KHQubGVuZ3RoLGUpLG89WHMudGF1UmFuZEludCh0Lmxlbmd0aCxlKSxzPXRbcl0sYT10W289KG8rPXI9PT1vPzE6MCkldC5sZW5ndGhdLGw9MCxjPVhzLnplcm9zKGkpLHU9MDt1PGMubGVuZ3RoO3UrKyljW3VdPW5bc11bdV0tblthXVt1XSxsLT1jW3VdKihuW3NdW3VdK25bYV1bdV0pLzI7dmFyIGQ9MCxwPTAsaD1Ycy56ZXJvcyh0Lmxlbmd0aCk7Zm9yKHU9MDt1PHQubGVuZ3RoO3UrKyl7Zm9yKHZhciBmPWwsbT0wO208aTttKyspZis9Y1ttXSpuW3RbdV1dW21dOzA9PT1mPyhoW3VdPVhzLnRhdVJhbmRJbnQoMixlKSwwPT09aFt1XT9kKz0xOnArPTEpOmY+MD8oaFt1XT0wLGQrPTEpOihoW3VdPTEscCs9MSl9dmFyIHg9WHMuemVyb3MoZCksZz1Ycy56ZXJvcyhwKTtmb3IoZD0wLHA9MCx1PTA7dTxoLmxlbmd0aDt1KyspMD09PWhbdV0/KHhbZF09dFt1XSxkKz0xKTooZ1twXT10W3VdLHArPTEpO3JldHVybntpbmRpY2VzTGVmdDp4LGluZGljZXNSaWdodDpnLGh5cGVycGxhbmU6YyxvZmZzZXQ6bH19KG4sdCxyKSxhPW8uaW5kaWNlc1JpZ2h0LGw9by5oeXBlcnBsYW5lLGM9by5vZmZzZXQ7cmV0dXJue2xlZnRDaGlsZDokRyhuLG8uaW5kaWNlc0xlZnQsZSxpKzEscikscmlnaHRDaGlsZDokRyhuLGEsZSxpKzEsciksaXNMZWFmOiExLGh5cGVycGxhbmU6bCxvZmZzZXQ6Y319cmV0dXJue2luZGljZXM6dCxpc0xlYWY6ITB9fWZ1bmN0aW9uIGU2KG4sdCxlLGkscixvLHMpe3ZhciBhO2lmKG4uaXNMZWFmKXJldHVybiBpW29dWzBdPS1zLChhPXJbc10pLnNwbGljZS5hcHBseShhLFdoZShbMCxuLmluZGljZXMubGVuZ3RoXSxuLmluZGljZXMpKSx7bm9kZU51bTpvLGxlYWZOdW06cys9MX07dFtvXT1uLmh5cGVycGxhbmUsZVtvXT1uLm9mZnNldCxpW29dWzBdPW8rMTt2YXIgbD1vLGM9ZTYobi5sZWZ0Q2hpbGQsdCxlLGkscixvKzEscyk7cmV0dXJuIHM9Yy5sZWFmTnVtLGlbbF1bMV09KG89Yy5ub2RlTnVtKSsxLHtub2RlTnVtOihjPWU2KG4ucmlnaHRDaGlsZCx0LGUsaSxyLG8rMSxzKSkubm9kZU51bSxsZWFmTnVtOmMubGVhZk51bX19ZnVuY3Rpb24gdDYobil7cmV0dXJuIG4uaXNMZWFmPzE6MSt0NihuLmxlZnRDaGlsZCkrdDYobi5yaWdodENoaWxkKX1mdW5jdGlvbiBuNihuKXtyZXR1cm4gbi5pc0xlYWY/MTpuNihuLmxlZnRDaGlsZCkrbjYobi5yaWdodENoaWxkKX1mdW5jdGlvbiB5JGUobix0LGUsaSl7Zm9yKHZhciByPXQsbz0wO288ZS5sZW5ndGg7bysrKXIrPW5bb10qZVtvXTtyZXR1cm4gMD09PXI/WHMudGF1UmFuZEludCgyLGkpOnI+MD8wOjF9WWEuRmxhdFRyZWU9cWhlLFlhLm1ha2VGb3Jlc3Q9ZnVuY3Rpb24obix0LGUsaSl7dmFyIHI9TWF0aC5tYXgoMTAsdCksbz1Ycy5yYW5nZShlKS5tYXAoZnVuY3Rpb24oYSxsKXtyZXR1cm4gZnVuY3Rpb24obix0LGUsaSl7cmV0dXJuIHZvaWQgMD09PXQmJih0PTMwKSwkRyhuLFhzLnJhbmdlKG4ubGVuZ3RoKSx0LGUsaSl9KG4scixsLGkpfSkscz1vLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gZnVuY3Rpb24obix0KXt2YXIgZT10NihuKSxpPW42KG4pLHI9WHMucmFuZ2UoZSkubWFwKGZ1bmN0aW9uKCl7cmV0dXJuIFhzLnplcm9zKG4uaHlwZXJwbGFuZT9uLmh5cGVycGxhbmUubGVuZ3RoOjApfSksbz1Ycy56ZXJvcyhlKSxzPVhzLnJhbmdlKGUpLm1hcChmdW5jdGlvbigpe3JldHVyblstMSwtMV19KSxhPVhzLnJhbmdlKGkpLm1hcChmdW5jdGlvbigpe3JldHVybiBYcy5yYW5nZSh0KS5tYXAoZnVuY3Rpb24oKXtyZXR1cm4tMX0pfSk7cmV0dXJuIGU2KG4scixvLHMsYSwwLDApLG5ldyBxaGUocixvLHMsYSl9KGEscil9KTtyZXR1cm4gc30sWWEubWFrZUxlYWZBcnJheT1mdW5jdGlvbihuKXt2YXIgdCxlO2lmKG4ubGVuZ3RoPjApe3ZhciBpPVtdO3RyeXtmb3IodmFyIHI9cCRlKG4pLG89ci5uZXh0KCk7IW8uZG9uZTtvPXIubmV4dCgpKWkucHVzaC5hcHBseShpLFdoZShvLnZhbHVlLmluZGljZXMpKX1jYXRjaChhKXt0PXtlcnJvcjphfX1maW5hbGx5e3RyeXtvJiYhby5kb25lJiYoZT1yLnJldHVybikmJmUuY2FsbChyKX1maW5hbGx5e2lmKHQpdGhyb3cgdC5lcnJvcn19cmV0dXJuIGl9cmV0dXJuW1stMV1dfSxZYS5zZWFyY2hGbGF0VHJlZT1mdW5jdGlvbihuLHQsZSl7Zm9yKHZhciBpPTA7dC5jaGlsZHJlbltpXVswXT4wOylpPTA9PT15JGUodC5oeXBlcnBsYW5lc1tpXSx0Lm9mZnNldHNbaV0sbixlKT90LmNoaWxkcmVuW2ldWzBdOnQuY2hpbGRyZW5baV1bMV07cmV0dXJuIHQuaW5kaWNlc1stMSp0LmNoaWxkcmVuW2ldWzBdXX19KSxRaGU9UmQoSWQ9PnsidXNlIHN0cmljdCI7dmFyIFloZT1JZCYmSWQuX192YWx1ZXN8fGZ1bmN0aW9uKG4pe3ZhciB0PSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJm5bU3ltYm9sLml0ZXJhdG9yXSxlPTA7cmV0dXJuIHQ/dC5jYWxsKG4pOntuZXh0OmZ1bmN0aW9uKCl7cmV0dXJuIG4mJmU+PW4ubGVuZ3RoJiYobj12b2lkIDApLHt2YWx1ZTpuJiZuW2UrK10sZG9uZTohbn19fX0sZEY9SWQmJklkLl9faW1wb3J0U3Rhcnx8ZnVuY3Rpb24obil7aWYobiYmbi5fX2VzTW9kdWxlKXJldHVybiBuO3ZhciB0PXt9O2lmKG51bGwhPW4pZm9yKHZhciBlIGluIG4pT2JqZWN0Lmhhc093blByb3BlcnR5LmNhbGwobixlKSYmKHRbZV09bltlXSk7cmV0dXJuIHQuZGVmYXVsdD1uLHR9O09iamVjdC5kZWZpbmVQcm9wZXJ0eShJZCwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSk7dmFyIHlhPWRGKFpHKCkpLHgkZT1kRihKRygpKSxDJGU9ZEYoaTYoKSksWGhlPWRGKEhiKCkpO0lkLm1ha2VOTkRlc2NlbnQ9ZnVuY3Rpb24obix0KXtyZXR1cm4gZnVuY3Rpb24oaSxyLG8scyxhLGwsYyx1KXt2b2lkIDA9PT1zJiYocz0xMCksdm9pZCAwPT09YSYmKGE9NTApLHZvaWQgMD09PWwmJihsPS4wMDEpLHZvaWQgMD09PWMmJihjPS41KSx2b2lkIDA9PT11JiYodT0hMCk7Zm9yKHZhciBkPWkubGVuZ3RoLHA9eWEubWFrZUhlYXAoaS5sZW5ndGgsbyksaD0wO2g8aS5sZW5ndGg7aCsrKWZvcih2YXIgZj15YS5yZWplY3Rpb25TYW1wbGUobyxpLmxlbmd0aCx0KSxtPTA7bTxmLmxlbmd0aDttKyspe3ZhciB4PW4oaVtoXSxpW2ZbbV1dKTt5YS5oZWFwUHVzaChwLGgseCxmW21dLDEpLHlhLmhlYXBQdXNoKHAsZlttXSx4LGgsMSl9aWYodSlmb3IodmFyIGc9MDtnPHIubGVuZ3RoO2crKylmb3IoaD0wO2g8cltnXS5sZW5ndGgmJiEocltnXVtoXTwwKTtoKyspZm9yKG09aCsxO208cltnXS5sZW5ndGgmJiEocltnXVttXTwwKTttKyspeD1uKGlbcltnXVtoXV0saVtyW2ddW21dXSkseWEuaGVhcFB1c2gocCxyW2ddW2hdLHgscltnXVttXSwxKSx5YS5oZWFwUHVzaChwLHJbZ11bbV0seCxyW2ddW2hdLDEpO2ZvcihnPTA7ZzxzO2crKyl7dmFyIGI9eWEuYnVpbGRDYW5kaWRhdGVzKHAsZCxvLGEsdCksRD0wO2ZvcihoPTA7aDxkO2grKylmb3IobT0wO208YTttKyspe3ZhciBUPU1hdGguZmxvb3IoYlswXVtoXVttXSk7aWYoIShUPDB8fFhoZS50YXVSYW5kKHQpPGMpKWZvcih2YXIgaz0wO2s8YTtrKyspe3ZhciBaPU1hdGguZmxvb3IoYlswXVtoXVtrXSk7WjwwfHwhYlsyXVtoXVttXSYmIWJbMl1baF1ba118fCh4PW4oaVtUXSxpW1pdKSxEKz15YS5oZWFwUHVzaChwLFQseCxaLDEpLEQrPXlhLmhlYXBQdXNoKHAsWix4LFQsMSkpfX1pZihEPD1sKm8qaS5sZW5ndGgpYnJlYWt9cmV0dXJuIHlhLmRlaGVhcFNvcnQocCl9fSxJZC5tYWtlSW5pdGlhbGl6YXRpb25zPWZ1bmN0aW9uKG4pe3JldHVybntpbml0RnJvbVJhbmRvbTpmdW5jdGlvbihpLHIsbyxzLGEpe2Zvcih2YXIgbD0wO2w8by5sZW5ndGg7bCsrKWZvcih2YXIgYz1YaGUucmVqZWN0aW9uU2FtcGxlKGksci5sZW5ndGgsYSksdT0wO3U8Yy5sZW5ndGg7dSsrKWlmKCEoY1t1XTwwKSl7dmFyIGQ9bihyW2NbdV1dLG9bbF0pO3lhLmhlYXBQdXNoKHMsbCxkLGNbdV0sMSl9fSxpbml0RnJvbVRyZWU6ZnVuY3Rpb24oaSxyLG8scyxhKXtmb3IodmFyIGw9MDtsPG8ubGVuZ3RoO2wrKylmb3IodmFyIGM9QyRlLnNlYXJjaEZsYXRUcmVlKG9bbF0saSxhKSx1PTA7dTxjLmxlbmd0aDt1Kyspe2lmKGNbdV08MClyZXR1cm47dmFyIGQ9bihyW2NbdV1dLG9bbF0pO3lhLmhlYXBQdXNoKHMsbCxkLGNbdV0sMSl9fX19LElkLm1ha2VJbml0aWFsaXplZE5OU2VhcmNoPWZ1bmN0aW9uKG4pe3JldHVybiBmdW5jdGlvbihlLGkscixvKXtmb3IodmFyIHMsYSxsPXgkZS5nZXRDU1IoaSksYz1sLmluZGljZXMsdT1sLmluZHB0cixkPTA7ZDxvLmxlbmd0aDtkKyspZm9yKHZhciBwPW5ldyBTZXQoclswXVtkXSk7Oyl7dmFyIGg9eWEuc21hbGxlc3RGbGFnZ2VkKHIsZCk7aWYoLTE9PT1oKWJyZWFrO3ZhciBmPWMuc2xpY2UodVtoXSx1W2grMV0pO3RyeXtmb3IodmFyIG09WWhlKGYpLHg9bS5uZXh0KCk7IXguZG9uZTt4PW0ubmV4dCgpKXt2YXIgZz14LnZhbHVlO2lmKGchPT1oJiYtMSE9PWcmJiFwLmhhcyhnKSl7dmFyIGI9bihlW2ddLG9bZF0pO3lhLnVuY2hlY2tlZEhlYXBQdXNoKHIsZCxiLGcsMSkscC5hZGQoZyl9fX1jYXRjaChEKXtzPXtlcnJvcjpEfX1maW5hbGx5e3RyeXt4JiYheC5kb25lJiYoYT1tLnJldHVybikmJmEuY2FsbChtKX1maW5hbGx5e2lmKHMpdGhyb3cgcy5lcnJvcn19fXJldHVybiByfX0sSWQuaW5pdGlhbGl6ZVNlYXJjaD1mdW5jdGlvbihuLHQsZSxpLHIsbyxzKXt2YXIgYSxsLGM9eWEubWFrZUhlYXAoZS5sZW5ndGgsaSk7aWYocihpLHQsZSxjLHMpLG4pdHJ5e2Zvcih2YXIgdT1ZaGUobiksZD11Lm5leHQoKTshZC5kb25lO2Q9dS5uZXh0KCkpbyhkLnZhbHVlLHQsZSxjLHMpfWNhdGNoKGgpe2E9e2Vycm9yOmh9fWZpbmFsbHl7dHJ5e2QmJiFkLmRvbmUmJihsPXUucmV0dXJuKSYmbC5jYWxsKHUpfWZpbmFsbHl7aWYoYSl0aHJvdyBhLmVycm9yfX1yZXR1cm4gY319KTtmdW5jdGlvbiBFRShuKXtyZXR1cm4gVCRlLmNhbGwobikuZW5kc1dpdGgoIkFycmF5XSIpfXZhciBUJGUsS2hlPWhvKCgpPT57VCRlPU9iamVjdC5wcm90b3R5cGUudG9TdHJpbmd9KTtmdW5jdGlvbiBwRihuLHQsZSl7bGV0IGk9MCxyPWUodCk7Zm9yKGxldCBvPTA7bzxuLngubGVuZ3RoO28rKylpKz1NYXRoLmFicyhuLnlbb10tcihuLnhbb10pKTtyZXR1cm4gaX12YXIgWmhlPWhvKCgpPT57fSk7ZnVuY3Rpb24gSWYobil7cmV0dXJuIEQkZS5jYWxsKG4pLmVuZHNXaXRoKCJBcnJheV0iKX12YXIgRCRlLGhGPWhvKCgpPT57RCRlPU9iamVjdC5wcm90b3R5cGUudG9TdHJpbmd9KTtmdW5jdGlvbiBBJGUobil7dmFyIHQ9YXJndW1lbnRzLmxlbmd0aD4xJiZ2b2lkIDAhPT1hcmd1bWVudHNbMV0/YXJndW1lbnRzWzFdOnt9O2lmKCFJZihuKSl0aHJvdyBuZXcgVHlwZUVycm9yKCJpbnB1dCBtdXN0IGJlIGFuIGFycmF5Iik7aWYoMD09PW4ubGVuZ3RoKXRocm93IG5ldyBUeXBlRXJyb3IoImlucHV0IG11c3Qgbm90IGJlIGVtcHR5Iik7dmFyIGU9dC5mcm9tSW5kZXgsaT12b2lkIDA9PT1lPzA6ZSxyPXQudG9JbmRleCxvPXZvaWQgMD09PXI/bi5sZW5ndGg6cjtpZihpPDB8fGk+PW4ubGVuZ3RofHwhTnVtYmVyLmlzSW50ZWdlcihpKSl0aHJvdyBuZXcgRXJyb3IoImZyb21JbmRleCBtdXN0IGJlIGEgcG9zaXRpdmUgaW50ZWdlciBzbWFsbGVyIHRoYW4gbGVuZ3RoIik7aWYobzw9aXx8bz5uLmxlbmd0aHx8IU51bWJlci5pc0ludGVnZXIobykpdGhyb3cgbmV3IEVycm9yKCJ0b0luZGV4IG11c3QgYmUgYW4gaW50ZWdlciBncmVhdGVyIHRoYW4gZnJvbUluZGV4IGFuZCBhdCBtb3N0IGVxdWFsIHRvIGxlbmd0aCIpO2Zvcih2YXIgcz1uW2ldLGE9aSsxO2E8bzthKyspblthXT5zJiYocz1uW2FdKTtyZXR1cm4gc312YXIgSmhlLCRoZT1obygoKT0+e2hGKCksSmhlPUEkZX0pO2Z1bmN0aW9uIEkkZShuKXt2YXIgdD1hcmd1bWVudHMubGVuZ3RoPjEmJnZvaWQgMCE9PWFyZ3VtZW50c1sxXT9hcmd1bWVudHNbMV06e307aWYoIUlmKG4pKXRocm93IG5ldyBUeXBlRXJyb3IoImlucHV0IG11c3QgYmUgYW4gYXJyYXkiKTtpZigwPT09bi5sZW5ndGgpdGhyb3cgbmV3IFR5cGVFcnJvcigiaW5wdXQgbXVzdCBub3QgYmUgZW1wdHkiKTt2YXIgZT10LmZyb21JbmRleCxpPXZvaWQgMD09PWU/MDplLHI9dC50b0luZGV4LG89dm9pZCAwPT09cj9uLmxlbmd0aDpyO2lmKGk8MHx8aT49bi5sZW5ndGh8fCFOdW1iZXIuaXNJbnRlZ2VyKGkpKXRocm93IG5ldyBFcnJvcigiZnJvbUluZGV4IG11c3QgYmUgYSBwb3NpdGl2ZSBpbnRlZ2VyIHNtYWxsZXIgdGhhbiBsZW5ndGgiKTtpZihvPD1pfHxvPm4ubGVuZ3RofHwhTnVtYmVyLmlzSW50ZWdlcihvKSl0aHJvdyBuZXcgRXJyb3IoInRvSW5kZXggbXVzdCBiZSBhbiBpbnRlZ2VyIGdyZWF0ZXIgdGhhbiBmcm9tSW5kZXggYW5kIGF0IG1vc3QgZXF1YWwgdG8gbGVuZ3RoIik7Zm9yKHZhciBzPW5baV0sYT1pKzE7YTxvO2ErKyluW2FdPHMmJihzPW5bYV0pO3JldHVybiBzfXZhciBlZmUsdGZlPWhvKCgpPT57aEYoKSxlZmU9SSRlfSk7ZnVuY3Rpb24gUCRlKG4pe3ZhciBlLHQ9YXJndW1lbnRzLmxlbmd0aD4xJiZ2b2lkIDAhPT1hcmd1bWVudHNbMV0/YXJndW1lbnRzWzFdOnt9O2lmKCFJZihuKSl0aHJvdyBuZXcgVHlwZUVycm9yKCJpbnB1dCBtdXN0IGJlIGFuIGFycmF5Iik7aWYoMD09PW4ubGVuZ3RoKXRocm93IG5ldyBUeXBlRXJyb3IoImlucHV0IG11c3Qgbm90IGJlIGVtcHR5Iik7aWYodm9pZCAwIT09dC5vdXRwdXQpe2lmKCFJZih0Lm91dHB1dCkpdGhyb3cgbmV3IFR5cGVFcnJvcigib3V0cHV0IG9wdGlvbiBtdXN0IGJlIGFuIGFycmF5IGlmIHNwZWNpZmllZCIpO2U9dC5vdXRwdXR9ZWxzZSBlPW5ldyBBcnJheShuLmxlbmd0aCk7dmFyIGk9ZWZlKG4pLHI9SmhlKG4pO2lmKGk9PT1yKXRocm93IG5ldyBSYW5nZUVycm9yKCJtaW5pbXVtIGFuZCBtYXhpbXVtIGlucHV0IHZhbHVlcyBhcmUgZXF1YWwuIENhbm5vdCByZXNjYWxlIGEgY29uc3RhbnQgYXJyYXkiKTt2YXIgbz10Lm1pbixzPXZvaWQgMD09PW8/dC5hdXRvTWluTWF4P2k6MDpvLGE9dC5tYXgsbD12b2lkIDA9PT1hP3QuYXV0b01pbk1heD9yOjE6YTtpZihzPj1sKXRocm93IG5ldyBSYW5nZUVycm9yKCJtaW4gb3B0aW9uIG11c3QgYmUgc21hbGxlciB0aGFuIG1heCBvcHRpb24iKTtmb3IodmFyIGM9KGwtcykvKHItaSksdT0wO3U8bi5sZW5ndGg7dSsrKWVbdV09KG5bdV0taSkqYytzO3JldHVybiBlfXZhciByNixuZmU9aG8oKCk9PntoRigpLCRoZSgpLHRmZSgpLHI2PVAkZX0pO2Z1bmN0aW9uIHJmZSgpe3JldHVybiBvNih0aGlzKX1mdW5jdGlvbiBvNihuLHQ9e30pe2xldHttYXhSb3dzOmU9MTUsbWF4Q29sdW1uczppPTEwLG1heE51bVNpemU6cj04fT10O3JldHVybmAke24uY29uc3RydWN0b3IubmFtZX0ge1xuJHtmRn1bXG4ke2lmZX0ke2Z1bmN0aW9uKG4sdCxlLGkpe2xldHtyb3dzOnIsY29sdW1uczpvfT1uLHM9TWF0aC5taW4ocix0KSxhPU1hdGgubWluKG8sZSksbD1bXTtmb3IobGV0IGM9MDtjPHM7YysrKXtsZXQgdT1bXTtmb3IobGV0IGQ9MDtkPGE7ZCsrKXUucHVzaChPJGUobi5nZXQoYyxkKSxpKSk7bC5wdXNoKGAke3Uuam9pbigiICIpfWApfXJldHVybiBhIT09byYmKGxbbC5sZW5ndGgtMV0rPWAgLi4uICR7by1lfSBtb3JlIGNvbHVtbnNgKSxzIT09ciYmbC5wdXNoKGAuLi4gJHtyLXR9IG1vcmUgcm93c2ApLGwuam9pbihgXG4ke2lmZX1gKX0obixlLGkscil9XG4ke2ZGfV1cbiR7ZkZ9cm93czogJHtuLnJvd3N9XG4ke2ZGfWNvbHVtbnM6ICR7bi5jb2x1bW5zfVxufWB9ZnVuY3Rpb24gTyRlKG4sdCl7bGV0IGU9U3RyaW5nKG4pO2lmKGUubGVuZ3RoPD10KXJldHVybiBlLnBhZEVuZCh0LCIgIik7bGV0IGk9bi50b1ByZWNpc2lvbih0LTIpO2lmKGkubGVuZ3RoPD10KXJldHVybiBpO2xldCByPW4udG9FeHBvbmVudGlhbCh0LTIpLG89ci5pbmRleE9mKCJlIikscz1yLnNsaWNlKG8pO3JldHVybiByLnNsaWNlKDAsdC1zLmxlbmd0aCkrc312YXIgZkYsaWZlLG9mZT1obygoKT0+e2ZGPSIgIi5yZXBlYXQoMiksaWZlPSIgIi5yZXBlYXQoNCl9KSxhZmU9aG8oKCk9Pnt9KTtmdW5jdGlvbiBnYyhuLHQsZSl7aWYodDwwfHx0PihlP24ucm93czpuLnJvd3MtMSkpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIlJvdyBpbmRleCBvdXQgb2YgcmFuZ2UiKX1mdW5jdGlvbiBfYyhuLHQsZSl7aWYodDwwfHx0PihlP24uY29sdW1uczpuLmNvbHVtbnMtMSkpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIkNvbHVtbiBpbmRleCBvdXQgb2YgcmFuZ2UiKX1mdW5jdGlvbiBuMChuLHQpe2lmKHQudG8xREFycmF5JiYodD10LnRvMURBcnJheSgpKSx0Lmxlbmd0aCE9PW4uY29sdW1ucyl0aHJvdyBuZXcgUmFuZ2VFcnJvcigidmVjdG9yIHNpemUgbXVzdCBiZSB0aGUgc2FtZSBhcyB0aGUgbnVtYmVyIG9mIGNvbHVtbnMiKTtyZXR1cm4gdH1mdW5jdGlvbiBpMChuLHQpe2lmKHQudG8xREFycmF5JiYodD10LnRvMURBcnJheSgpKSx0Lmxlbmd0aCE9PW4ucm93cyl0aHJvdyBuZXcgUmFuZ2VFcnJvcigidmVjdG9yIHNpemUgbXVzdCBiZSB0aGUgc2FtZSBhcyB0aGUgbnVtYmVyIG9mIHJvd3MiKTtyZXR1cm4gdH1mdW5jdGlvbiBrJGUobix0KXtpZigib2JqZWN0IiE9dHlwZW9mIHQpdGhyb3cgbmV3IFR5cGVFcnJvcigidW5leHBlY3RlZCB0eXBlIGZvciByb3cgaW5kaWNlcyIpO2lmKHQuc29tZShpPT5pPDB8fGk+PW4ucm93cykpdGhyb3cgbmV3IFJhbmdlRXJyb3IoInJvdyBpbmRpY2VzIGFyZSBvdXQgb2YgcmFuZ2UiKTtyZXR1cm4gQXJyYXkuaXNBcnJheSh0KXx8KHQ9QXJyYXkuZnJvbSh0KSksdH1mdW5jdGlvbiBGJGUobix0KXtpZigib2JqZWN0IiE9dHlwZW9mIHQpdGhyb3cgbmV3IFR5cGVFcnJvcigidW5leHBlY3RlZCB0eXBlIGZvciBjb2x1bW4gaW5kaWNlcyIpO2lmKHQuc29tZShpPT5pPDB8fGk+PW4uY29sdW1ucykpdGhyb3cgbmV3IFJhbmdlRXJyb3IoImNvbHVtbiBpbmRpY2VzIGFyZSBvdXQgb2YgcmFuZ2UiKTtyZXR1cm4gQXJyYXkuaXNBcnJheSh0KXx8KHQ9QXJyYXkuZnJvbSh0KSksdH1mdW5jdGlvbiBzNihuLHQsZSxpLHIpe2lmKDUhPT1hcmd1bWVudHMubGVuZ3RoKXRocm93IG5ldyBSYW5nZUVycm9yKCJleHBlY3RlZCA0IGFyZ3VtZW50cyIpO2lmKG1GKCJzdGFydFJvdyIsdCksbUYoImVuZFJvdyIsZSksbUYoInN0YXJ0Q29sdW1uIixpKSxtRigiZW5kQ29sdW1uIixyKSx0PmV8fGk+cnx8dDwwfHx0Pj1uLnJvd3N8fGU8MHx8ZT49bi5yb3dzfHxpPDB8fGk+PW4uY29sdW1uc3x8cjwwfHxyPj1uLmNvbHVtbnMpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIlN1Ym1hdHJpeCBpbmRpY2VzIGFyZSBvdXQgb2YgcmFuZ2UiKX1mdW5jdGlvbiBURShuLHQ9MCl7bGV0IGU9W107Zm9yKGxldCBpPTA7aTxuO2krKyllLnB1c2godCk7cmV0dXJuIGV9ZnVuY3Rpb24gbUYobix0KXtpZigibnVtYmVyIiE9dHlwZW9mIHQpdGhyb3cgbmV3IFR5cGVFcnJvcihgJHtufSBtdXN0IGJlIGEgbnVtYmVyYCl9ZnVuY3Rpb24gcjAobil7aWYobi5pc0VtcHR5KCkpdGhyb3cgbmV3IEVycm9yKCJFbXB0eSBtYXRyaXggaGFzIG5vIGVsZW1lbnRzIHRvIGluZGV4Iil9dmFyIGE2PWhvKCgpPT57fSksVGZlPWhvKCgpPT57YTYoKX0pO2Z1bmN0aW9uIERmZShuLHQpe3JldHVybiBuLXR9dmFyIHRpLHJuLEFsLEFFLG8wPWhvKCgpPT57dmFyIG4sdDtuZmUoKSxvZmUoKSxhZmUoKSxUZmUoKSxhNigpLHRpPWNsYXNze3N0YXRpYyBmcm9tMURBcnJheSh0LGUsaSl7aWYodCplIT09aS5sZW5ndGgpdGhyb3cgbmV3IFJhbmdlRXJyb3IoImRhdGEgbGVuZ3RoIGRvZXMgbm90IG1hdGNoIGdpdmVuIGRpbWVuc2lvbnMiKTtsZXQgbz1uZXcgcm4odCxlKTtmb3IobGV0IHM9MDtzPHQ7cysrKWZvcihsZXQgYT0wO2E8ZTthKyspby5zZXQocyxhLGlbcyplK2FdKTtyZXR1cm4gb31zdGF0aWMgcm93VmVjdG9yKHQpe2xldCBlPW5ldyBybigxLHQubGVuZ3RoKTtmb3IobGV0IGk9MDtpPHQubGVuZ3RoO2krKyllLnNldCgwLGksdFtpXSk7cmV0dXJuIGV9c3RhdGljIGNvbHVtblZlY3Rvcih0KXtsZXQgZT1uZXcgcm4odC5sZW5ndGgsMSk7Zm9yKGxldCBpPTA7aTx0Lmxlbmd0aDtpKyspZS5zZXQoaSwwLHRbaV0pO3JldHVybiBlfXN0YXRpYyB6ZXJvcyh0LGUpe3JldHVybiBuZXcgcm4odCxlKX1zdGF0aWMgb25lcyh0LGUpe3JldHVybiBuZXcgcm4odCxlKS5maWxsKDEpfXN0YXRpYyByYW5kKHQsZSxpPXt9KXtpZigib2JqZWN0IiE9dHlwZW9mIGkpdGhyb3cgbmV3IFR5cGVFcnJvcigib3B0aW9ucyBtdXN0IGJlIGFuIG9iamVjdCIpO2xldHtyYW5kb206cj1NYXRoLnJhbmRvbX09aSxvPW5ldyBybih0LGUpO2ZvcihsZXQgcz0wO3M8dDtzKyspZm9yKGxldCBhPTA7YTxlO2ErKylvLnNldChzLGEscigpKTtyZXR1cm4gb31zdGF0aWMgcmFuZEludCh0LGUsaT17fSl7aWYoIm9iamVjdCIhPXR5cGVvZiBpKXRocm93IG5ldyBUeXBlRXJyb3IoIm9wdGlvbnMgbXVzdCBiZSBhbiBvYmplY3QiKTtsZXR7bWluOnI9MCxtYXg6bz0xZTMscmFuZG9tOnM9TWF0aC5yYW5kb219PWk7aWYoIU51bWJlci5pc0ludGVnZXIocikpdGhyb3cgbmV3IFR5cGVFcnJvcigibWluIG11c3QgYmUgYW4gaW50ZWdlciIpO2lmKCFOdW1iZXIuaXNJbnRlZ2VyKG8pKXRocm93IG5ldyBUeXBlRXJyb3IoIm1heCBtdXN0IGJlIGFuIGludGVnZXIiKTtpZihyPj1vKXRocm93IG5ldyBSYW5nZUVycm9yKCJtaW4gbXVzdCBiZSBzbWFsbGVyIHRoYW4gbWF4Iik7bGV0IGE9by1yLGw9bmV3IHJuKHQsZSk7Zm9yKGxldCBjPTA7Yzx0O2MrKylmb3IobGV0IHU9MDt1PGU7dSsrKXtsZXQgZD1yK01hdGgucm91bmQocygpKmEpO2wuc2V0KGMsdSxkKX1yZXR1cm4gbH1zdGF0aWMgZXllKHQsZSxpKXt2b2lkIDA9PT1lJiYoZT10KSx2b2lkIDA9PT1pJiYoaT0xKTtsZXQgcj1NYXRoLm1pbih0LGUpLG89dGhpcy56ZXJvcyh0LGUpO2ZvcihsZXQgcz0wO3M8cjtzKyspby5zZXQocyxzLGkpO3JldHVybiBvfXN0YXRpYyBkaWFnKHQsZSxpKXtsZXQgcj10Lmxlbmd0aDt2b2lkIDA9PT1lJiYoZT1yKSx2b2lkIDA9PT1pJiYoaT1lKTtsZXQgbz1NYXRoLm1pbihyLGUsaSkscz10aGlzLnplcm9zKGUsaSk7Zm9yKGxldCBhPTA7YTxvO2ErKylzLnNldChhLGEsdFthXSk7cmV0dXJuIHN9c3RhdGljIG1pbih0LGUpe3Q9dGhpcy5jaGVja01hdHJpeCh0KSxlPXRoaXMuY2hlY2tNYXRyaXgoZSk7bGV0IGk9dC5yb3dzLHI9dC5jb2x1bW5zLG89bmV3IHJuKGkscik7Zm9yKGxldCBzPTA7czxpO3MrKylmb3IobGV0IGE9MDthPHI7YSsrKW8uc2V0KHMsYSxNYXRoLm1pbih0LmdldChzLGEpLGUuZ2V0KHMsYSkpKTtyZXR1cm4gb31zdGF0aWMgbWF4KHQsZSl7dD10aGlzLmNoZWNrTWF0cml4KHQpLGU9dGhpcy5jaGVja01hdHJpeChlKTtsZXQgaT10LnJvd3Mscj10LmNvbHVtbnMsbz1uZXcgdGhpcyhpLHIpO2ZvcihsZXQgcz0wO3M8aTtzKyspZm9yKGxldCBhPTA7YTxyO2ErKylvLnNldChzLGEsTWF0aC5tYXgodC5nZXQocyxhKSxlLmdldChzLGEpKSk7cmV0dXJuIG99c3RhdGljIGNoZWNrTWF0cml4KHQpe3JldHVybiB0aS5pc01hdHJpeCh0KT90Om5ldyBybih0KX1zdGF0aWMgaXNNYXRyaXgodCl7cmV0dXJuIG51bGwhPXQmJiJNYXRyaXgiPT09dC5rbGFzc31nZXQgc2l6ZSgpe3JldHVybiB0aGlzLnJvd3MqdGhpcy5jb2x1bW5zfWFwcGx5KHQpe2lmKCJmdW5jdGlvbiIhPXR5cGVvZiB0KXRocm93IG5ldyBUeXBlRXJyb3IoImNhbGxiYWNrIG11c3QgYmUgYSBmdW5jdGlvbiIpO2ZvcihsZXQgZT0wO2U8dGhpcy5yb3dzO2UrKylmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspdC5jYWxsKHRoaXMsZSxpKTtyZXR1cm4gdGhpc310bzFEQXJyYXkoKXtsZXQgdD1bXTtmb3IobGV0IGU9MDtlPHRoaXMucm93cztlKyspZm9yKGxldCBpPTA7aTx0aGlzLmNvbHVtbnM7aSsrKXQucHVzaCh0aGlzLmdldChlLGkpKTtyZXR1cm4gdH10bzJEQXJyYXkoKXtsZXQgdD1bXTtmb3IobGV0IGU9MDtlPHRoaXMucm93cztlKyspe3QucHVzaChbXSk7Zm9yKGxldCBpPTA7aTx0aGlzLmNvbHVtbnM7aSsrKXRbZV0ucHVzaCh0aGlzLmdldChlLGkpKX1yZXR1cm4gdH10b0pTT04oKXtyZXR1cm4gdGhpcy50bzJEQXJyYXkoKX1pc1Jvd1ZlY3Rvcigpe3JldHVybiAxPT09dGhpcy5yb3dzfWlzQ29sdW1uVmVjdG9yKCl7cmV0dXJuIDE9PT10aGlzLmNvbHVtbnN9aXNWZWN0b3IoKXtyZXR1cm4gMT09PXRoaXMucm93c3x8MT09PXRoaXMuY29sdW1uc31pc1NxdWFyZSgpe3JldHVybiB0aGlzLnJvd3M9PT10aGlzLmNvbHVtbnN9aXNFbXB0eSgpe3JldHVybiAwPT09dGhpcy5yb3dzfHwwPT09dGhpcy5jb2x1bW5zfWlzU3ltbWV0cmljKCl7aWYodGhpcy5pc1NxdWFyZSgpKXtmb3IobGV0IHQ9MDt0PHRoaXMucm93czt0KyspZm9yKGxldCBlPTA7ZTw9dDtlKyspaWYodGhpcy5nZXQodCxlKSE9PXRoaXMuZ2V0KGUsdCkpcmV0dXJuITE7cmV0dXJuITB9cmV0dXJuITF9aXNFY2hlbG9uRm9ybSgpe2xldCB0PTAsZT0wLGk9LTEscj0hMCxvPSExO2Zvcig7dDx0aGlzLnJvd3MmJnI7KXtmb3IoZT0wLG89ITE7ZTx0aGlzLmNvbHVtbnMmJiExPT09bzspMD09PXRoaXMuZ2V0KHQsZSk/ZSsrOjE9PT10aGlzLmdldCh0LGUpJiZlPmk/KG89ITAsaT1lKToocj0hMSxvPSEwKTt0Kyt9cmV0dXJuIHJ9aXNSZWR1Y2VkRWNoZWxvbkZvcm0oKXtsZXQgdD0wLGU9MCxpPS0xLHI9ITAsbz0hMTtmb3IoO3Q8dGhpcy5yb3dzJiZyOyl7Zm9yKGU9MCxvPSExO2U8dGhpcy5jb2x1bW5zJiYhMT09PW87KTA9PT10aGlzLmdldCh0LGUpP2UrKzoxPT09dGhpcy5nZXQodCxlKSYmZT5pPyhvPSEwLGk9ZSk6KHI9ITEsbz0hMCk7Zm9yKGxldCBzPWUrMTtzPHRoaXMucm93cztzKyspMCE9PXRoaXMuZ2V0KHQscykmJihyPSExKTt0Kyt9cmV0dXJuIHJ9ZWNoZWxvbkZvcm0oKXtsZXQgdD10aGlzLmNsb25lKCksZT0wLGk9MDtmb3IoO2U8dC5yb3dzJiZpPHQuY29sdW1uczspe2xldCByPWU7Zm9yKGxldCBvPWU7bzx0LnJvd3M7bysrKXQuZ2V0KG8saSk+dC5nZXQocixpKSYmKHI9byk7aWYoMD09PXQuZ2V0KHIsaSkpaSsrO2Vsc2V7dC5zd2FwUm93cyhlLHIpO2xldCBvPXQuZ2V0KGUsaSk7Zm9yKGxldCBzPWk7czx0LmNvbHVtbnM7cysrKXQuc2V0KGUscyx0LmdldChlLHMpL28pO2ZvcihsZXQgcz1lKzE7czx0LnJvd3M7cysrKXtsZXQgYT10LmdldChzLGkpL3QuZ2V0KGUsaSk7dC5zZXQocyxpLDApO2ZvcihsZXQgbD1pKzE7bDx0LmNvbHVtbnM7bCsrKXQuc2V0KHMsbCx0LmdldChzLGwpLXQuZ2V0KGUsbCkqYSl9ZSsrLGkrK319cmV0dXJuIHR9cmVkdWNlZEVjaGVsb25Gb3JtKCl7bGV0IHQ9dGhpcy5lY2hlbG9uRm9ybSgpLGU9dC5jb2x1bW5zLGk9dC5yb3dzLHI9aS0xO2Zvcig7cj49MDspaWYoMD09PXQubWF4Um93KHIpKXItLTtlbHNle2xldCBvPTAscz0hMTtmb3IoO288aSYmITE9PT1zOykxPT09dC5nZXQocixvKT9zPSEwOm8rKztmb3IobGV0IGE9MDthPHI7YSsrKXtsZXQgbD10LmdldChhLG8pO2ZvcihsZXQgYz1vO2M8ZTtjKyspe2xldCB1PXQuZ2V0KGEsYyktbCp0LmdldChyLGMpO3Quc2V0KGEsYyx1KX19ci0tfXJldHVybiB0fXNldCgpe3Rocm93IG5ldyBFcnJvcigic2V0IG1ldGhvZCBpcyB1bmltcGxlbWVudGVkIil9Z2V0KCl7dGhyb3cgbmV3IEVycm9yKCJnZXQgbWV0aG9kIGlzIHVuaW1wbGVtZW50ZWQiKX1yZXBlYXQodD17fSl7aWYoIm9iamVjdCIhPXR5cGVvZiB0KXRocm93IG5ldyBUeXBlRXJyb3IoIm9wdGlvbnMgbXVzdCBiZSBhbiBvYmplY3QiKTtsZXR7cm93czplPTEsY29sdW1uczppPTF9PXQ7aWYoIU51bWJlci5pc0ludGVnZXIoZSl8fGU8PTApdGhyb3cgbmV3IFR5cGVFcnJvcigicm93cyBtdXN0IGJlIGEgcG9zaXRpdmUgaW50ZWdlciIpO2lmKCFOdW1iZXIuaXNJbnRlZ2VyKGkpfHxpPD0wKXRocm93IG5ldyBUeXBlRXJyb3IoImNvbHVtbnMgbXVzdCBiZSBhIHBvc2l0aXZlIGludGVnZXIiKTtsZXQgcj1uZXcgcm4odGhpcy5yb3dzKmUsdGhpcy5jb2x1bW5zKmkpO2ZvcihsZXQgbz0wO288ZTtvKyspZm9yKGxldCBzPTA7czxpO3MrKylyLnNldFN1Yk1hdHJpeCh0aGlzLHRoaXMucm93cypvLHRoaXMuY29sdW1ucypzKTtyZXR1cm4gcn1maWxsKHQpe2ZvcihsZXQgZT0wO2U8dGhpcy5yb3dzO2UrKylmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspdGhpcy5zZXQoZSxpLHQpO3JldHVybiB0aGlzfW5lZygpe3JldHVybiB0aGlzLm11bFMoLTEpfWdldFJvdyh0KXtnYyh0aGlzLHQpO2xldCBlPVtdO2ZvcihsZXQgaT0wO2k8dGhpcy5jb2x1bW5zO2krKyllLnB1c2godGhpcy5nZXQodCxpKSk7cmV0dXJuIGV9Z2V0Um93VmVjdG9yKHQpe3JldHVybiBybi5yb3dWZWN0b3IodGhpcy5nZXRSb3codCkpfXNldFJvdyh0LGUpe2djKHRoaXMsdCksZT1uMCh0aGlzLGUpO2ZvcihsZXQgaT0wO2k8dGhpcy5jb2x1bW5zO2krKyl0aGlzLnNldCh0LGksZVtpXSk7cmV0dXJuIHRoaXN9c3dhcFJvd3ModCxlKXtnYyh0aGlzLHQpLGdjKHRoaXMsZSk7Zm9yKGxldCBpPTA7aTx0aGlzLmNvbHVtbnM7aSsrKXtsZXQgcj10aGlzLmdldCh0LGkpO3RoaXMuc2V0KHQsaSx0aGlzLmdldChlLGkpKSx0aGlzLnNldChlLGkscil9cmV0dXJuIHRoaXN9Z2V0Q29sdW1uKHQpe19jKHRoaXMsdCk7bGV0IGU9W107Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKWUucHVzaCh0aGlzLmdldChpLHQpKTtyZXR1cm4gZX1nZXRDb2x1bW5WZWN0b3IodCl7cmV0dXJuIHJuLmNvbHVtblZlY3Rvcih0aGlzLmdldENvbHVtbih0KSl9c2V0Q29sdW1uKHQsZSl7X2ModGhpcyx0KSxlPWkwKHRoaXMsZSk7Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKXRoaXMuc2V0KGksdCxlW2ldKTtyZXR1cm4gdGhpc31zd2FwQ29sdW1ucyh0LGUpe19jKHRoaXMsdCksX2ModGhpcyxlKTtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspe2xldCByPXRoaXMuZ2V0KGksdCk7dGhpcy5zZXQoaSx0LHRoaXMuZ2V0KGksZSkpLHRoaXMuc2V0KGksZSxyKX1yZXR1cm4gdGhpc31hZGRSb3dWZWN0b3IodCl7dD1uMCh0aGlzLHQpO2ZvcihsZXQgZT0wO2U8dGhpcy5yb3dzO2UrKylmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspdGhpcy5zZXQoZSxpLHRoaXMuZ2V0KGUsaSkrdFtpXSk7cmV0dXJuIHRoaXN9c3ViUm93VmVjdG9yKHQpe3Q9bjAodGhpcyx0KTtmb3IobGV0IGU9MDtlPHRoaXMucm93cztlKyspZm9yKGxldCBpPTA7aTx0aGlzLmNvbHVtbnM7aSsrKXRoaXMuc2V0KGUsaSx0aGlzLmdldChlLGkpLXRbaV0pO3JldHVybiB0aGlzfW11bFJvd1ZlY3Rvcih0KXt0PW4wKHRoaXMsdCk7Zm9yKGxldCBlPTA7ZTx0aGlzLnJvd3M7ZSsrKWZvcihsZXQgaT0wO2k8dGhpcy5jb2x1bW5zO2krKyl0aGlzLnNldChlLGksdGhpcy5nZXQoZSxpKSp0W2ldKTtyZXR1cm4gdGhpc31kaXZSb3dWZWN0b3IodCl7dD1uMCh0aGlzLHQpO2ZvcihsZXQgZT0wO2U8dGhpcy5yb3dzO2UrKylmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspdGhpcy5zZXQoZSxpLHRoaXMuZ2V0KGUsaSkvdFtpXSk7cmV0dXJuIHRoaXN9YWRkQ29sdW1uVmVjdG9yKHQpe3Q9aTAodGhpcyx0KTtmb3IobGV0IGU9MDtlPHRoaXMucm93cztlKyspZm9yKGxldCBpPTA7aTx0aGlzLmNvbHVtbnM7aSsrKXRoaXMuc2V0KGUsaSx0aGlzLmdldChlLGkpK3RbZV0pO3JldHVybiB0aGlzfXN1YkNvbHVtblZlY3Rvcih0KXt0PWkwKHRoaXMsdCk7Zm9yKGxldCBlPTA7ZTx0aGlzLnJvd3M7ZSsrKWZvcihsZXQgaT0wO2k8dGhpcy5jb2x1bW5zO2krKyl0aGlzLnNldChlLGksdGhpcy5nZXQoZSxpKS10W2VdKTtyZXR1cm4gdGhpc31tdWxDb2x1bW5WZWN0b3IodCl7dD1pMCh0aGlzLHQpO2ZvcihsZXQgZT0wO2U8dGhpcy5yb3dzO2UrKylmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspdGhpcy5zZXQoZSxpLHRoaXMuZ2V0KGUsaSkqdFtlXSk7cmV0dXJuIHRoaXN9ZGl2Q29sdW1uVmVjdG9yKHQpe3Q9aTAodGhpcyx0KTtmb3IobGV0IGU9MDtlPHRoaXMucm93cztlKyspZm9yKGxldCBpPTA7aTx0aGlzLmNvbHVtbnM7aSsrKXRoaXMuc2V0KGUsaSx0aGlzLmdldChlLGkpL3RbZV0pO3JldHVybiB0aGlzfW11bFJvdyh0LGUpe2djKHRoaXMsdCk7Zm9yKGxldCBpPTA7aTx0aGlzLmNvbHVtbnM7aSsrKXRoaXMuc2V0KHQsaSx0aGlzLmdldCh0LGkpKmUpO3JldHVybiB0aGlzfW11bENvbHVtbih0LGUpe19jKHRoaXMsdCk7Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKXRoaXMuc2V0KGksdCx0aGlzLmdldChpLHQpKmUpO3JldHVybiB0aGlzfW1heCgpe2lmKHRoaXMuaXNFbXB0eSgpKXJldHVybiBOYU47bGV0IHQ9dGhpcy5nZXQoMCwwKTtmb3IobGV0IGU9MDtlPHRoaXMucm93cztlKyspZm9yKGxldCBpPTA7aTx0aGlzLmNvbHVtbnM7aSsrKXRoaXMuZ2V0KGUsaSk+dCYmKHQ9dGhpcy5nZXQoZSxpKSk7cmV0dXJuIHR9bWF4SW5kZXgoKXtyMCh0aGlzKTtsZXQgdD10aGlzLmdldCgwLDApLGU9WzAsMF07Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKWZvcihsZXQgcj0wO3I8dGhpcy5jb2x1bW5zO3IrKyl0aGlzLmdldChpLHIpPnQmJih0PXRoaXMuZ2V0KGksciksZVswXT1pLGVbMV09cik7cmV0dXJuIGV9bWluKCl7aWYodGhpcy5pc0VtcHR5KCkpcmV0dXJuIE5hTjtsZXQgdD10aGlzLmdldCgwLDApO2ZvcihsZXQgZT0wO2U8dGhpcy5yb3dzO2UrKylmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspdGhpcy5nZXQoZSxpKTx0JiYodD10aGlzLmdldChlLGkpKTtyZXR1cm4gdH1taW5JbmRleCgpe3IwKHRoaXMpO2xldCB0PXRoaXMuZ2V0KDAsMCksZT1bMCwwXTtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuZ2V0KGkscik8dCYmKHQ9dGhpcy5nZXQoaSxyKSxlWzBdPWksZVsxXT1yKTtyZXR1cm4gZX1tYXhSb3codCl7aWYoZ2ModGhpcyx0KSx0aGlzLmlzRW1wdHkoKSlyZXR1cm4gTmFOO2xldCBlPXRoaXMuZ2V0KHQsMCk7Zm9yKGxldCBpPTE7aTx0aGlzLmNvbHVtbnM7aSsrKXRoaXMuZ2V0KHQsaSk+ZSYmKGU9dGhpcy5nZXQodCxpKSk7cmV0dXJuIGV9bWF4Um93SW5kZXgodCl7Z2ModGhpcyx0KSxyMCh0aGlzKTtsZXQgZT10aGlzLmdldCh0LDApLGk9W3QsMF07Zm9yKGxldCByPTE7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuZ2V0KHQscik+ZSYmKGU9dGhpcy5nZXQodCxyKSxpWzFdPXIpO3JldHVybiBpfW1pblJvdyh0KXtpZihnYyh0aGlzLHQpLHRoaXMuaXNFbXB0eSgpKXJldHVybiBOYU47bGV0IGU9dGhpcy5nZXQodCwwKTtmb3IobGV0IGk9MTtpPHRoaXMuY29sdW1ucztpKyspdGhpcy5nZXQodCxpKTxlJiYoZT10aGlzLmdldCh0LGkpKTtyZXR1cm4gZX1taW5Sb3dJbmRleCh0KXtnYyh0aGlzLHQpLHIwKHRoaXMpO2xldCBlPXRoaXMuZ2V0KHQsMCksaT1bdCwwXTtmb3IobGV0IHI9MTtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5nZXQodCxyKTxlJiYoZT10aGlzLmdldCh0LHIpLGlbMV09cik7cmV0dXJuIGl9bWF4Q29sdW1uKHQpe2lmKF9jKHRoaXMsdCksdGhpcy5pc0VtcHR5KCkpcmV0dXJuIE5hTjtsZXQgZT10aGlzLmdldCgwLHQpO2ZvcihsZXQgaT0xO2k8dGhpcy5yb3dzO2krKyl0aGlzLmdldChpLHQpPmUmJihlPXRoaXMuZ2V0KGksdCkpO3JldHVybiBlfW1heENvbHVtbkluZGV4KHQpe19jKHRoaXMsdCkscjAodGhpcyk7bGV0IGU9dGhpcy5nZXQoMCx0KSxpPVswLHRdO2ZvcihsZXQgcj0xO3I8dGhpcy5yb3dzO3IrKyl0aGlzLmdldChyLHQpPmUmJihlPXRoaXMuZ2V0KHIsdCksaVswXT1yKTtyZXR1cm4gaX1taW5Db2x1bW4odCl7aWYoX2ModGhpcyx0KSx0aGlzLmlzRW1wdHkoKSlyZXR1cm4gTmFOO2xldCBlPXRoaXMuZ2V0KDAsdCk7Zm9yKGxldCBpPTE7aTx0aGlzLnJvd3M7aSsrKXRoaXMuZ2V0KGksdCk8ZSYmKGU9dGhpcy5nZXQoaSx0KSk7cmV0dXJuIGV9bWluQ29sdW1uSW5kZXgodCl7X2ModGhpcyx0KSxyMCh0aGlzKTtsZXQgZT10aGlzLmdldCgwLHQpLGk9WzAsdF07Zm9yKGxldCByPTE7cjx0aGlzLnJvd3M7cisrKXRoaXMuZ2V0KHIsdCk8ZSYmKGU9dGhpcy5nZXQocix0KSxpWzBdPXIpO3JldHVybiBpfWRpYWcoKXtsZXQgdD1NYXRoLm1pbih0aGlzLnJvd3MsdGhpcy5jb2x1bW5zKSxlPVtdO2ZvcihsZXQgaT0wO2k8dDtpKyspZS5wdXNoKHRoaXMuZ2V0KGksaSkpO3JldHVybiBlfW5vcm0odD0iZnJvYmVuaXVzIil7bGV0IGU9MDtpZigibWF4Ij09PXQpcmV0dXJuIHRoaXMubWF4KCk7aWYoImZyb2Jlbml1cyI9PT10KXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKWUrPXRoaXMuZ2V0KGkscikqdGhpcy5nZXQoaSxyKTtyZXR1cm4gTWF0aC5zcXJ0KGUpfXRocm93IG5ldyBSYW5nZUVycm9yKGB1bmtub3duIG5vcm0gdHlwZTogJHt0fWApfWN1bXVsYXRpdmVTdW0oKXtsZXQgdD0wO2ZvcihsZXQgZT0wO2U8dGhpcy5yb3dzO2UrKylmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspdCs9dGhpcy5nZXQoZSxpKSx0aGlzLnNldChlLGksdCk7cmV0dXJuIHRoaXN9ZG90KHQpe3RpLmlzTWF0cml4KHQpJiYodD10LnRvMURBcnJheSgpKTtsZXQgZT10aGlzLnRvMURBcnJheSgpO2lmKGUubGVuZ3RoIT09dC5sZW5ndGgpdGhyb3cgbmV3IFJhbmdlRXJyb3IoInZlY3RvcnMgZG8gbm90IGhhdmUgdGhlIHNhbWUgc2l6ZSIpO2xldCBpPTA7Zm9yKGxldCByPTA7cjxlLmxlbmd0aDtyKyspaSs9ZVtyXSp0W3JdO3JldHVybiBpfW1tdWwodCl7dD1ybi5jaGVja01hdHJpeCh0KTtsZXQgZT10aGlzLnJvd3MsaT10aGlzLmNvbHVtbnMscj10LmNvbHVtbnMsbz1uZXcgcm4oZSxyKSxzPW5ldyBGbG9hdDY0QXJyYXkoaSk7Zm9yKGxldCBhPTA7YTxyO2ErKyl7Zm9yKGxldCBsPTA7bDxpO2wrKylzW2xdPXQuZ2V0KGwsYSk7Zm9yKGxldCBsPTA7bDxlO2wrKyl7bGV0IGM9MDtmb3IobGV0IHU9MDt1PGk7dSsrKWMrPXRoaXMuZ2V0KGwsdSkqc1t1XTtvLnNldChsLGEsYyl9fXJldHVybiBvfXN0cmFzc2VuMngyKHQpe3Q9cm4uY2hlY2tNYXRyaXgodCk7bGV0IGU9bmV3IHJuKDIsMiksaT10aGlzLmdldCgwLDApLHI9dC5nZXQoMCwwKSxvPXRoaXMuZ2V0KDAsMSkscz10LmdldCgwLDEpLGE9dGhpcy5nZXQoMSwwKSxsPXQuZ2V0KDEsMCksYz10aGlzLmdldCgxLDEpLHU9dC5nZXQoMSwxKSxkPShpK2MpKihyK3UpLHA9KGErYykqcixoPWkqKHMtdSksZj1jKihsLXIpLG09KGkrbykqdSxEPWgrbSxUPXArZixrPWQtcCtoKyhhLWkpKihyK3MpO3JldHVybiBlLnNldCgwLDAsZCtmLW0rKG8tYykqKGwrdSkpLGUuc2V0KDAsMSxEKSxlLnNldCgxLDAsVCksZS5zZXQoMSwxLGspLGV9c3RyYXNzZW4zeDModCl7dD1ybi5jaGVja01hdHJpeCh0KTtsZXQgZT1uZXcgcm4oMywzKSxpPXRoaXMuZ2V0KDAsMCkscj10aGlzLmdldCgwLDEpLG89dGhpcy5nZXQoMCwyKSxzPXRoaXMuZ2V0KDEsMCksYT10aGlzLmdldCgxLDEpLGw9dGhpcy5nZXQoMSwyKSxjPXRoaXMuZ2V0KDIsMCksdT10aGlzLmdldCgyLDEpLGQ9dGhpcy5nZXQoMiwyKSxwPXQuZ2V0KDAsMCksaD10LmdldCgwLDEpLGY9dC5nZXQoMCwyKSxtPXQuZ2V0KDEsMCkseD10LmdldCgxLDEpLGc9dC5nZXQoMSwyKSxiPXQuZ2V0KDIsMCksRD10LmdldCgyLDEpLFQ9dC5nZXQoMiwyKSxaPShpLXMpKigtaCt4KSxmZT0oLWkrcythKSoocC1oK3gpLHVlPShzK2EpKigtcCtoKSxoZT1pKnAsdz0oLWkrYyt1KSoocC1mK2cpLEY9KC1pK2MpKihmLWcpLHE9KGMrdSkqKC1wK2YpLFk9KC1vK3UrZCkqKHgrYi1EKSxhZT0oby1kKSooeC1EKSxsZT1vKmIsSWU9KHUrZCkqKC1iK0QpLHZlPSgtbythK2wpKihnK2ItVCksRGU9KG8tbCkqKGctVCksbnQ9KGErbCkqKC1iK1QpLFRlPShpK3Irby1zLWEtdS1kKSp4K2ZlK3VlK2hlK1krbGUrSWUseHQ9aGUrdytxKyhpK3Irby1hLWwtYy11KSpnK2xlK3ZlK250LG10PVorYSooLXAraCttLXgtZy1iK1QpK2ZlK2hlK2xlK3ZlK0RlLGNlPVorZmUrdWUraGUrbCpELGR0PWxlK3ZlK0RlK250K3MqZixXZT1oZSt3K0YrdSooLXArZittLXgtZy1iK0QpK1krYWUrbGUsTXQ9WSthZStsZStJZStjKmgsYnQ9aGUrdytGK3ErZCpUO3JldHVybiBlLnNldCgwLDAsaGUrbGUrciptKSxlLnNldCgwLDEsVGUpLGUuc2V0KDAsMix4dCksZS5zZXQoMSwwLG10KSxlLnNldCgxLDEsY2UpLGUuc2V0KDEsMixkdCksZS5zZXQoMiwwLFdlKSxlLnNldCgyLDEsTXQpLGUuc2V0KDIsMixidCksZX1tbXVsU3RyYXNzZW4odCl7dD1ybi5jaGVja01hdHJpeCh0KTtsZXQgZT10aGlzLmNsb25lKCksaT1lLnJvd3Mscj1lLmNvbHVtbnMsbz10LnJvd3Mscz10LmNvbHVtbnM7ZnVuY3Rpb24gYShkLHAsaCl7aWYoZC5yb3dzPT09cCYmZC5jb2x1bW5zPT09aClyZXR1cm4gZDt7bGV0IHg9dGkuemVyb3MocCxoKTtyZXR1cm4geD14LnNldFN1Yk1hdHJpeChkLDAsMCkseH19ciE9PW8mJmNvbnNvbGUud2FybihgTXVsdGlwbHlpbmcgJHtpfSB4ICR7cn0gYW5kICR7b30geCAke3N9IG1hdHJpeDogZGltZW5zaW9ucyBkbyBub3QgbWF0Y2guYCk7bGV0IGw9TWF0aC5tYXgoaSxvKSxjPU1hdGgubWF4KHIscyk7cmV0dXJuIGU9YShlLGwsYyksZnVuY3Rpb24gdShkLHAsaCxmKXtpZihoPD01MTJ8fGY8PTUxMilyZXR1cm4gZC5tbXVsKHApO2glMj09MSYmZiUyPT0xPyhkPWEoZCxoKzEsZisxKSxwPWEocCxoKzEsZisxKSk6aCUyPT0xPyhkPWEoZCxoKzEsZikscD1hKHAsaCsxLGYpKTpmJTI9PTEmJihkPWEoZCxoLGYrMSkscD1hKHAsaCxmKzEpKTtsZXQgbT1wYXJzZUludChkLnJvd3MvMiwxMCkseD1wYXJzZUludChkLmNvbHVtbnMvMiwxMCksZz1kLnN1Yk1hdHJpeCgwLG0tMSwwLHgtMSksYj1wLnN1Yk1hdHJpeCgwLG0tMSwwLHgtMSksRD1kLnN1Yk1hdHJpeCgwLG0tMSx4LGQuY29sdW1ucy0xKSxUPXAuc3ViTWF0cml4KDAsbS0xLHgscC5jb2x1bW5zLTEpLGs9ZC5zdWJNYXRyaXgobSxkLnJvd3MtMSwwLHgtMSksWj1wLnN1Yk1hdHJpeChtLHAucm93cy0xLDAseC0xKSx6PWQuc3ViTWF0cml4KG0sZC5yb3dzLTEseCxkLmNvbHVtbnMtMSksZmU9cC5zdWJNYXRyaXgobSxwLnJvd3MtMSx4LHAuY29sdW1ucy0xKSx1ZT11KHRpLmFkZChnLHopLHRpLmFkZChiLGZlKSxtLHgpLGhlPXUodGkuYWRkKGsseiksYixtLHgpLHc9dShnLHRpLnN1YihULGZlKSxtLHgpLEY9dSh6LHRpLnN1YihaLGIpLG0seCkscT11KHRpLmFkZChnLEQpLGZlLG0seCksSz11KHRpLnN1YihrLGcpLHRpLmFkZChiLFQpLG0seCksZGU9dSh0aS5zdWIoRCx6KSx0aS5hZGQoWixmZSksbSx4KSxZPXRpLmFkZCh1ZSxGKTtZLnN1YihxKSxZLmFkZChkZSk7bGV0IGFlPXRpLmFkZCh3LHEpLGxlPXRpLmFkZChoZSxGKSxJZT10aS5zdWIodWUsaGUpO0llLmFkZCh3KSxJZS5hZGQoSyk7bGV0IHZlPXRpLnplcm9zKDIqWS5yb3dzLDIqWS5jb2x1bW5zKTtyZXR1cm4gdmU9dmUuc2V0U3ViTWF0cml4KFksMCwwKSx2ZT12ZS5zZXRTdWJNYXRyaXgoYWUsWS5yb3dzLDApLHZlPXZlLnNldFN1Yk1hdHJpeChsZSwwLFkuY29sdW1ucyksdmU9dmUuc2V0U3ViTWF0cml4KEllLFkucm93cyxZLmNvbHVtbnMpLHZlLnN1Yk1hdHJpeCgwLGgtMSwwLGYtMSl9KGUsdD1hKHQsbCxjKSxsLGMpfXNjYWxlUm93cyh0PXt9KXtpZigib2JqZWN0IiE9dHlwZW9mIHQpdGhyb3cgbmV3IFR5cGVFcnJvcigib3B0aW9ucyBtdXN0IGJlIGFuIG9iamVjdCIpO2xldHttaW46ZT0wLG1heDppPTF9PXQ7aWYoIU51bWJlci5pc0Zpbml0ZShlKSl0aHJvdyBuZXcgVHlwZUVycm9yKCJtaW4gbXVzdCBiZSBhIG51bWJlciIpO2lmKCFOdW1iZXIuaXNGaW5pdGUoaSkpdGhyb3cgbmV3IFR5cGVFcnJvcigibWF4IG11c3QgYmUgYSBudW1iZXIiKTtpZihlPj1pKXRocm93IG5ldyBSYW5nZUVycm9yKCJtaW4gbXVzdCBiZSBzbWFsbGVyIHRoYW4gbWF4Iik7bGV0IHI9bmV3IHJuKHRoaXMucm93cyx0aGlzLmNvbHVtbnMpO2ZvcihsZXQgbz0wO288dGhpcy5yb3dzO28rKyl7bGV0IHM9dGhpcy5nZXRSb3cobyk7cy5sZW5ndGg+MCYmcjYocyx7bWluOmUsbWF4Omksb3V0cHV0OnN9KSxyLnNldFJvdyhvLHMpfXJldHVybiByfXNjYWxlQ29sdW1ucyh0PXt9KXtpZigib2JqZWN0IiE9dHlwZW9mIHQpdGhyb3cgbmV3IFR5cGVFcnJvcigib3B0aW9ucyBtdXN0IGJlIGFuIG9iamVjdCIpO2xldHttaW46ZT0wLG1heDppPTF9PXQ7aWYoIU51bWJlci5pc0Zpbml0ZShlKSl0aHJvdyBuZXcgVHlwZUVycm9yKCJtaW4gbXVzdCBiZSBhIG51bWJlciIpO2lmKCFOdW1iZXIuaXNGaW5pdGUoaSkpdGhyb3cgbmV3IFR5cGVFcnJvcigibWF4IG11c3QgYmUgYSBudW1iZXIiKTtpZihlPj1pKXRocm93IG5ldyBSYW5nZUVycm9yKCJtaW4gbXVzdCBiZSBzbWFsbGVyIHRoYW4gbWF4Iik7bGV0IHI9bmV3IHJuKHRoaXMucm93cyx0aGlzLmNvbHVtbnMpO2ZvcihsZXQgbz0wO288dGhpcy5jb2x1bW5zO28rKyl7bGV0IHM9dGhpcy5nZXRDb2x1bW4obyk7cy5sZW5ndGgmJnI2KHMse21pbjplLG1heDppLG91dHB1dDpzfSksci5zZXRDb2x1bW4obyxzKX1yZXR1cm4gcn1mbGlwUm93cygpe2xldCB0PU1hdGguY2VpbCh0aGlzLmNvbHVtbnMvMik7Zm9yKGxldCBlPTA7ZTx0aGlzLnJvd3M7ZSsrKWZvcihsZXQgaT0wO2k8dDtpKyspe2xldCByPXRoaXMuZ2V0KGUsaSksbz10aGlzLmdldChlLHRoaXMuY29sdW1ucy0xLWkpO3RoaXMuc2V0KGUsaSxvKSx0aGlzLnNldChlLHRoaXMuY29sdW1ucy0xLWkscil9cmV0dXJuIHRoaXN9ZmxpcENvbHVtbnMoKXtsZXQgdD1NYXRoLmNlaWwodGhpcy5yb3dzLzIpO2ZvcihsZXQgZT0wO2U8dGhpcy5jb2x1bW5zO2UrKylmb3IobGV0IGk9MDtpPHQ7aSsrKXtsZXQgcj10aGlzLmdldChpLGUpLG89dGhpcy5nZXQodGhpcy5yb3dzLTEtaSxlKTt0aGlzLnNldChpLGUsbyksdGhpcy5zZXQodGhpcy5yb3dzLTEtaSxlLHIpfXJldHVybiB0aGlzfWtyb25lY2tlclByb2R1Y3QodCl7dD1ybi5jaGVja01hdHJpeCh0KTtsZXQgZT10aGlzLnJvd3MsaT10aGlzLmNvbHVtbnMscj10LnJvd3Msbz10LmNvbHVtbnMscz1uZXcgcm4oZSpyLGkqbyk7Zm9yKGxldCBhPTA7YTxlO2ErKylmb3IobGV0IGw9MDtsPGk7bCsrKWZvcihsZXQgYz0wO2M8cjtjKyspZm9yKGxldCB1PTA7dTxvO3UrKylzLnNldChyKmErYyxvKmwrdSx0aGlzLmdldChhLGwpKnQuZ2V0KGMsdSkpO3JldHVybiBzfWtyb25lY2tlclN1bSh0KXtpZih0PXJuLmNoZWNrTWF0cml4KHQpLCF0aGlzLmlzU3F1YXJlKCl8fCF0LmlzU3F1YXJlKCkpdGhyb3cgbmV3IEVycm9yKCJLcm9uZWNrZXIgU3VtIG5lZWRzIHR3byBTcXVhcmUgTWF0cmljZXMiKTtsZXQgZT10aGlzLnJvd3MsaT10LnJvd3Mscj10aGlzLmtyb25lY2tlclByb2R1Y3Qocm4uZXllKGksaSkpLG89cm4uZXllKGUsZSkua3JvbmVja2VyUHJvZHVjdCh0KTtyZXR1cm4gci5hZGQobyl9dHJhbnNwb3NlKCl7bGV0IHQ9bmV3IHJuKHRoaXMuY29sdW1ucyx0aGlzLnJvd3MpO2ZvcihsZXQgZT0wO2U8dGhpcy5yb3dzO2UrKylmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspdC5zZXQoaSxlLHRoaXMuZ2V0KGUsaSkpO3JldHVybiB0fXNvcnRSb3dzKHQ9RGZlKXtmb3IobGV0IGU9MDtlPHRoaXMucm93cztlKyspdGhpcy5zZXRSb3coZSx0aGlzLmdldFJvdyhlKS5zb3J0KHQpKTtyZXR1cm4gdGhpc31zb3J0Q29sdW1ucyh0PURmZSl7Zm9yKGxldCBlPTA7ZTx0aGlzLmNvbHVtbnM7ZSsrKXRoaXMuc2V0Q29sdW1uKGUsdGhpcy5nZXRDb2x1bW4oZSkuc29ydCh0KSk7cmV0dXJuIHRoaXN9c3ViTWF0cml4KHQsZSxpLHIpe3M2KHRoaXMsdCxlLGkscik7bGV0IG89bmV3IHJuKGUtdCsxLHItaSsxKTtmb3IobGV0IHM9dDtzPD1lO3MrKylmb3IobGV0IGE9aTthPD1yO2ErKylvLnNldChzLXQsYS1pLHRoaXMuZ2V0KHMsYSkpO3JldHVybiBvfXN1Yk1hdHJpeFJvdyh0LGUsaSl7aWYodm9pZCAwPT09ZSYmKGU9MCksdm9pZCAwPT09aSYmKGk9dGhpcy5jb2x1bW5zLTEpLGU+aXx8ZTwwfHxlPj10aGlzLmNvbHVtbnN8fGk8MHx8aT49dGhpcy5jb2x1bW5zKXRocm93IG5ldyBSYW5nZUVycm9yKCJBcmd1bWVudCBvdXQgb2YgcmFuZ2UiKTtsZXQgcj1uZXcgcm4odC5sZW5ndGgsaS1lKzEpO2ZvcihsZXQgbz0wO288dC5sZW5ndGg7bysrKWZvcihsZXQgcz1lO3M8PWk7cysrKXtpZih0W29dPDB8fHRbb10+PXRoaXMucm93cyl0aHJvdyBuZXcgUmFuZ2VFcnJvcihgUm93IGluZGV4IG91dCBvZiByYW5nZTogJHt0W29dfWApO3Iuc2V0KG8scy1lLHRoaXMuZ2V0KHRbb10scykpfXJldHVybiByfXN1Yk1hdHJpeENvbHVtbih0LGUsaSl7aWYodm9pZCAwPT09ZSYmKGU9MCksdm9pZCAwPT09aSYmKGk9dGhpcy5yb3dzLTEpLGU+aXx8ZTwwfHxlPj10aGlzLnJvd3N8fGk8MHx8aT49dGhpcy5yb3dzKXRocm93IG5ldyBSYW5nZUVycm9yKCJBcmd1bWVudCBvdXQgb2YgcmFuZ2UiKTtsZXQgcj1uZXcgcm4oaS1lKzEsdC5sZW5ndGgpO2ZvcihsZXQgbz0wO288dC5sZW5ndGg7bysrKWZvcihsZXQgcz1lO3M8PWk7cysrKXtpZih0W29dPDB8fHRbb10+PXRoaXMuY29sdW1ucyl0aHJvdyBuZXcgUmFuZ2VFcnJvcihgQ29sdW1uIGluZGV4IG91dCBvZiByYW5nZTogJHt0W29dfWApO3Iuc2V0KHMtZSxvLHRoaXMuZ2V0KHMsdFtvXSkpfXJldHVybiByfXNldFN1Yk1hdHJpeCh0LGUsaSl7aWYoKHQ9cm4uY2hlY2tNYXRyaXgodCkpLmlzRW1wdHkoKSlyZXR1cm4gdGhpcztzNih0aGlzLGUsZSt0LnJvd3MtMSxpLGkrdC5jb2x1bW5zLTEpO2ZvcihsZXQgcz0wO3M8dC5yb3dzO3MrKylmb3IobGV0IGE9MDthPHQuY29sdW1uczthKyspdGhpcy5zZXQoZStzLGkrYSx0LmdldChzLGEpKTtyZXR1cm4gdGhpc31zZWxlY3Rpb24odCxlKXtsZXQgaT1mdW5jdGlvbihuLHQsZSl7cmV0dXJue3JvdzprJGUobix0KSxjb2x1bW46RiRlKG4sZSl9fSh0aGlzLHQsZSkscj1uZXcgcm4odC5sZW5ndGgsZS5sZW5ndGgpO2ZvcihsZXQgbz0wO288aS5yb3cubGVuZ3RoO28rKyl7bGV0IHM9aS5yb3dbb107Zm9yKGxldCBhPTA7YTxpLmNvbHVtbi5sZW5ndGg7YSsrKXIuc2V0KG8sYSx0aGlzLmdldChzLGkuY29sdW1uW2FdKSl9cmV0dXJuIHJ9dHJhY2UoKXtsZXQgdD1NYXRoLm1pbih0aGlzLnJvd3MsdGhpcy5jb2x1bW5zKSxlPTA7Zm9yKGxldCBpPTA7aTx0O2krKyllKz10aGlzLmdldChpLGkpO3JldHVybiBlfWNsb25lKCl7bGV0IHQ9bmV3IHJuKHRoaXMucm93cyx0aGlzLmNvbHVtbnMpO2ZvcihsZXQgZT0wO2U8dGhpcy5yb3dzO2UrKylmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspdC5zZXQoZSxpLHRoaXMuZ2V0KGUsaSkpO3JldHVybiB0fXN1bSh0KXtzd2l0Y2godCl7Y2FzZSJyb3ciOnJldHVybiBmdW5jdGlvbihuKXtsZXQgdD1URShuLnJvd3MpO2ZvcihsZXQgZT0wO2U8bi5yb3dzOysrZSlmb3IobGV0IGk9MDtpPG4uY29sdW1uczsrK2kpdFtlXSs9bi5nZXQoZSxpKTtyZXR1cm4gdH0odGhpcyk7Y2FzZSJjb2x1bW4iOnJldHVybiBmdW5jdGlvbihuKXtsZXQgdD1URShuLmNvbHVtbnMpO2ZvcihsZXQgZT0wO2U8bi5yb3dzOysrZSlmb3IobGV0IGk9MDtpPG4uY29sdW1uczsrK2kpdFtpXSs9bi5nZXQoZSxpKTtyZXR1cm4gdH0odGhpcyk7Y2FzZSB2b2lkIDA6cmV0dXJuIGZ1bmN0aW9uKG4pe2xldCB0PTA7Zm9yKGxldCBlPTA7ZTxuLnJvd3M7ZSsrKWZvcihsZXQgaT0wO2k8bi5jb2x1bW5zO2krKyl0Kz1uLmdldChlLGkpO3JldHVybiB0fSh0aGlzKTtkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgaW52YWxpZCBvcHRpb246ICR7dH1gKX19cHJvZHVjdCh0KXtzd2l0Y2godCl7Y2FzZSJyb3ciOnJldHVybiBmdW5jdGlvbihuKXtsZXQgdD1URShuLnJvd3MsMSk7Zm9yKGxldCBlPTA7ZTxuLnJvd3M7KytlKWZvcihsZXQgaT0wO2k8bi5jb2x1bW5zOysraSl0W2VdKj1uLmdldChlLGkpO3JldHVybiB0fSh0aGlzKTtjYXNlImNvbHVtbiI6cmV0dXJuIGZ1bmN0aW9uKG4pe2xldCB0PVRFKG4uY29sdW1ucywxKTtmb3IobGV0IGU9MDtlPG4ucm93czsrK2UpZm9yKGxldCBpPTA7aTxuLmNvbHVtbnM7KytpKXRbaV0qPW4uZ2V0KGUsaSk7cmV0dXJuIHR9KHRoaXMpO2Nhc2Ugdm9pZCAwOnJldHVybiBmdW5jdGlvbihuKXtsZXQgdD0xO2ZvcihsZXQgZT0wO2U8bi5yb3dzO2UrKylmb3IobGV0IGk9MDtpPG4uY29sdW1ucztpKyspdCo9bi5nZXQoZSxpKTtyZXR1cm4gdH0odGhpcyk7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoYGludmFsaWQgb3B0aW9uOiAke3R9YCl9fW1lYW4odCl7bGV0IGU9dGhpcy5zdW0odCk7c3dpdGNoKHQpe2Nhc2Uicm93Ijpmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZVtpXS89dGhpcy5jb2x1bW5zO3JldHVybiBlO2Nhc2UiY29sdW1uIjpmb3IobGV0IGk9MDtpPHRoaXMuY29sdW1ucztpKyspZVtpXS89dGhpcy5yb3dzO3JldHVybiBlO2Nhc2Ugdm9pZCAwOnJldHVybiBlL3RoaXMuc2l6ZTtkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgaW52YWxpZCBvcHRpb246ICR7dH1gKX19dmFyaWFuY2UodCxlPXt9KXtpZigib2JqZWN0Ij09dHlwZW9mIHQmJihlPXQsdD12b2lkIDApLCJvYmplY3QiIT10eXBlb2YgZSl0aHJvdyBuZXcgVHlwZUVycm9yKCJvcHRpb25zIG11c3QgYmUgYW4gb2JqZWN0Iik7bGV0e3VuYmlhc2VkOmk9ITAsbWVhbjpyPXRoaXMubWVhbih0KX09ZTtpZigiYm9vbGVhbiIhPXR5cGVvZiBpKXRocm93IG5ldyBUeXBlRXJyb3IoInVuYmlhc2VkIG11c3QgYmUgYSBib29sZWFuIik7c3dpdGNoKHQpe2Nhc2Uicm93IjppZighQXJyYXkuaXNBcnJheShyKSl0aHJvdyBuZXcgVHlwZUVycm9yKCJtZWFuIG11c3QgYmUgYW4gYXJyYXkiKTtyZXR1cm4gZnVuY3Rpb24obix0LGUpe2xldCBpPW4ucm93cyxyPW4uY29sdW1ucyxvPVtdO2ZvcihsZXQgcz0wO3M8aTtzKyspe2xldCBhPTAsbD0wLGM9MDtmb3IobGV0IHU9MDt1PHI7dSsrKWM9bi5nZXQocyx1KS1lW3NdLGErPWMsbCs9YypjO28ucHVzaCh0PyhsLWEqYS9yKS8oci0xKToobC1hKmEvcikvcil9cmV0dXJuIG99KHRoaXMsaSxyKTtjYXNlImNvbHVtbiI6aWYoIUFycmF5LmlzQXJyYXkocikpdGhyb3cgbmV3IFR5cGVFcnJvcigibWVhbiBtdXN0IGJlIGFuIGFycmF5Iik7cmV0dXJuIGZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1uLnJvd3Mscj1uLmNvbHVtbnMsbz1bXTtmb3IobGV0IHM9MDtzPHI7cysrKXtsZXQgYT0wLGw9MCxjPTA7Zm9yKGxldCB1PTA7dTxpO3UrKyljPW4uZ2V0KHUscyktZVtzXSxhKz1jLGwrPWMqYztvLnB1c2godD8obC1hKmEvaSkvKGktMSk6KGwtYSphL2kpL2kpfXJldHVybiBvfSh0aGlzLGkscik7Y2FzZSB2b2lkIDA6aWYoIm51bWJlciIhPXR5cGVvZiByKXRocm93IG5ldyBUeXBlRXJyb3IoIm1lYW4gbXVzdCBiZSBhIG51bWJlciIpO3JldHVybiBmdW5jdGlvbihuLHQsZSl7bGV0IGk9bi5yb3dzLHI9bi5jb2x1bW5zLG89aSpyLHM9MCxhPTAsbD0wO2ZvcihsZXQgYz0wO2M8aTtjKyspZm9yKGxldCB1PTA7dTxyO3UrKylsPW4uZ2V0KGMsdSktZSxzKz1sLGErPWwqbDtyZXR1cm4gdD8oYS1zKnMvbykvKG8tMSk6KGEtcypzL28pL299KHRoaXMsaSxyKTtkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgaW52YWxpZCBvcHRpb246ICR7dH1gKX19c3RhbmRhcmREZXZpYXRpb24odCxlKXsib2JqZWN0Ij09dHlwZW9mIHQmJihlPXQsdD12b2lkIDApO2xldCBpPXRoaXMudmFyaWFuY2UodCxlKTtpZih2b2lkIDA9PT10KXJldHVybiBNYXRoLnNxcnQoaSk7Zm9yKGxldCByPTA7cjxpLmxlbmd0aDtyKyspaVtyXT1NYXRoLnNxcnQoaVtyXSk7cmV0dXJuIGl9Y2VudGVyKHQsZT17fSl7aWYoIm9iamVjdCI9PXR5cGVvZiB0JiYoZT10LHQ9dm9pZCAwKSwib2JqZWN0IiE9dHlwZW9mIGUpdGhyb3cgbmV3IFR5cGVFcnJvcigib3B0aW9ucyBtdXN0IGJlIGFuIG9iamVjdCIpO2xldHtjZW50ZXI6aT10aGlzLm1lYW4odCl9PWU7c3dpdGNoKHQpe2Nhc2Uicm93IjppZighQXJyYXkuaXNBcnJheShpKSl0aHJvdyBuZXcgVHlwZUVycm9yKCJjZW50ZXIgbXVzdCBiZSBhbiBhcnJheSIpO3JldHVybiBmdW5jdGlvbihuLHQpe2ZvcihsZXQgZT0wO2U8bi5yb3dzO2UrKylmb3IobGV0IGk9MDtpPG4uY29sdW1ucztpKyspbi5zZXQoZSxpLG4uZ2V0KGUsaSktdFtlXSl9KHRoaXMsaSksdGhpcztjYXNlImNvbHVtbiI6aWYoIUFycmF5LmlzQXJyYXkoaSkpdGhyb3cgbmV3IFR5cGVFcnJvcigiY2VudGVyIG11c3QgYmUgYW4gYXJyYXkiKTtyZXR1cm4gZnVuY3Rpb24obix0KXtmb3IobGV0IGU9MDtlPG4ucm93cztlKyspZm9yKGxldCBpPTA7aTxuLmNvbHVtbnM7aSsrKW4uc2V0KGUsaSxuLmdldChlLGkpLXRbaV0pfSh0aGlzLGkpLHRoaXM7Y2FzZSB2b2lkIDA6aWYoIm51bWJlciIhPXR5cGVvZiBpKXRocm93IG5ldyBUeXBlRXJyb3IoImNlbnRlciBtdXN0IGJlIGEgbnVtYmVyIik7cmV0dXJuIGZ1bmN0aW9uKG4sdCl7Zm9yKGxldCBlPTA7ZTxuLnJvd3M7ZSsrKWZvcihsZXQgaT0wO2k8bi5jb2x1bW5zO2krKyluLnNldChlLGksbi5nZXQoZSxpKS10KX0odGhpcyxpKSx0aGlzO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKGBpbnZhbGlkIG9wdGlvbjogJHt0fWApfX1zY2FsZSh0LGU9e30pe2lmKCJvYmplY3QiPT10eXBlb2YgdCYmKGU9dCx0PXZvaWQgMCksIm9iamVjdCIhPXR5cGVvZiBlKXRocm93IG5ldyBUeXBlRXJyb3IoIm9wdGlvbnMgbXVzdCBiZSBhbiBvYmplY3QiKTtsZXQgaT1lLnNjYWxlO3N3aXRjaCh0KXtjYXNlInJvdyI6aWYodm9pZCAwPT09aSlpPWZ1bmN0aW9uKG4pe2xldCB0PVtdO2ZvcihsZXQgZT0wO2U8bi5yb3dzO2UrKyl7bGV0IGk9MDtmb3IobGV0IHI9MDtyPG4uY29sdW1ucztyKyspaSs9TWF0aC5wb3cobi5nZXQoZSxyKSwyKS8obi5jb2x1bW5zLTEpO3QucHVzaChNYXRoLnNxcnQoaSkpfXJldHVybiB0fSh0aGlzKTtlbHNlIGlmKCFBcnJheS5pc0FycmF5KGkpKXRocm93IG5ldyBUeXBlRXJyb3IoInNjYWxlIG11c3QgYmUgYW4gYXJyYXkiKTtyZXR1cm4gZnVuY3Rpb24obix0KXtmb3IobGV0IGU9MDtlPG4ucm93cztlKyspZm9yKGxldCBpPTA7aTxuLmNvbHVtbnM7aSsrKW4uc2V0KGUsaSxuLmdldChlLGkpL3RbZV0pfSh0aGlzLGkpLHRoaXM7Y2FzZSJjb2x1bW4iOmlmKHZvaWQgMD09PWkpaT1mdW5jdGlvbihuKXtsZXQgdD1bXTtmb3IobGV0IGU9MDtlPG4uY29sdW1ucztlKyspe2xldCBpPTA7Zm9yKGxldCByPTA7cjxuLnJvd3M7cisrKWkrPU1hdGgucG93KG4uZ2V0KHIsZSksMikvKG4ucm93cy0xKTt0LnB1c2goTWF0aC5zcXJ0KGkpKX1yZXR1cm4gdH0odGhpcyk7ZWxzZSBpZighQXJyYXkuaXNBcnJheShpKSl0aHJvdyBuZXcgVHlwZUVycm9yKCJzY2FsZSBtdXN0IGJlIGFuIGFycmF5Iik7cmV0dXJuIGZ1bmN0aW9uKG4sdCl7Zm9yKGxldCBlPTA7ZTxuLnJvd3M7ZSsrKWZvcihsZXQgaT0wO2k8bi5jb2x1bW5zO2krKyluLnNldChlLGksbi5nZXQoZSxpKS90W2ldKX0odGhpcyxpKSx0aGlzO2Nhc2Ugdm9pZCAwOmlmKHZvaWQgMD09PWkpaT1mdW5jdGlvbihuKXtsZXQgdD1uLnNpemUtMSxlPTA7Zm9yKGxldCBpPTA7aTxuLmNvbHVtbnM7aSsrKWZvcihsZXQgcj0wO3I8bi5yb3dzO3IrKyllKz1NYXRoLnBvdyhuLmdldChyLGkpLDIpL3Q7cmV0dXJuIE1hdGguc3FydChlKX0odGhpcyk7ZWxzZSBpZigibnVtYmVyIiE9dHlwZW9mIGkpdGhyb3cgbmV3IFR5cGVFcnJvcigic2NhbGUgbXVzdCBiZSBhIG51bWJlciIpO3JldHVybiBmdW5jdGlvbihuLHQpe2ZvcihsZXQgZT0wO2U8bi5yb3dzO2UrKylmb3IobGV0IGk9MDtpPG4uY29sdW1ucztpKyspbi5zZXQoZSxpLG4uZ2V0KGUsaSkvdCl9KHRoaXMsaSksdGhpcztkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgaW52YWxpZCBvcHRpb246ICR7dH1gKX19dG9TdHJpbmcodCl7cmV0dXJuIG82KHRoaXMsdCl9fSx0aS5wcm90b3R5cGUua2xhc3M9Ik1hdHJpeCIsdHlwZW9mIFN5bWJvbDwidSImJih0aS5wcm90b3R5cGVbU3ltYm9sLmZvcigibm9kZWpzLnV0aWwuaW5zcGVjdC5jdXN0b20iKV09cmZlKSx0aS5yYW5kb209dGkucmFuZCx0aS5yYW5kb21JbnQ9dGkucmFuZEludCx0aS5kaWFnb25hbD10aS5kaWFnLHRpLnByb3RvdHlwZS5kaWFnb25hbD10aS5wcm90b3R5cGUuZGlhZyx0aS5pZGVudGl0eT10aS5leWUsdGkucHJvdG90eXBlLm5lZ2F0ZT10aS5wcm90b3R5cGUubmVnLHRpLnByb3RvdHlwZS50ZW5zb3JQcm9kdWN0PXRpLnByb3RvdHlwZS5rcm9uZWNrZXJQcm9kdWN0LHJuPWNsYXNzIGV4dGVuZHMgdGl7Y29uc3RydWN0b3IodCxlKXtpZihzdXBlcigpLHJuLmlzTWF0cml4KHQpKXJldHVybiB0LmNsb25lKCk7aWYoTnVtYmVyLmlzSW50ZWdlcih0KSYmdD49MCl7aWYodGhpcy5kYXRhPVtdLCEoTnVtYmVyLmlzSW50ZWdlcihlKSYmZT49MCkpdGhyb3cgbmV3IFR5cGVFcnJvcigibkNvbHVtbnMgbXVzdCBiZSBhIHBvc2l0aXZlIGludGVnZXIiKTtmb3IobGV0IGk9MDtpPHQ7aSsrKXRoaXMuZGF0YS5wdXNoKG5ldyBGbG9hdDY0QXJyYXkoZSkpfWVsc2V7aWYoIUFycmF5LmlzQXJyYXkodCkpdGhyb3cgbmV3IFR5cGVFcnJvcigiRmlyc3QgYXJndW1lbnQgbXVzdCBiZSBhIHBvc2l0aXZlIG51bWJlciBvciBhbiBhcnJheSIpO3tsZXQgaT10O2lmKCJudW1iZXIiIT10eXBlb2YoZT0odD1pLmxlbmd0aCk/aVswXS5sZW5ndGg6MCkpdGhyb3cgbmV3IFR5cGVFcnJvcigiRGF0YSBtdXN0IGJlIGEgMkQgYXJyYXkgd2l0aCBhdCBsZWFzdCBvbmUgZWxlbWVudCIpO3RoaXMuZGF0YT1bXTtmb3IobGV0IHI9MDtyPHQ7cisrKXtpZihpW3JdLmxlbmd0aCE9PWUpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIkluY29uc2lzdGVudCBhcnJheSBkaW1lbnNpb25zIik7dGhpcy5kYXRhLnB1c2goRmxvYXQ2NEFycmF5LmZyb20oaVtyXSkpfX19dGhpcy5yb3dzPXQsdGhpcy5jb2x1bW5zPWV9c2V0KHQsZSxpKXtyZXR1cm4gdGhpcy5kYXRhW3RdW2VdPWksdGhpc31nZXQodCxlKXtyZXR1cm4gdGhpcy5kYXRhW3RdW2VdfXJlbW92ZVJvdyh0KXtyZXR1cm4gZ2ModGhpcyx0KSx0aGlzLmRhdGEuc3BsaWNlKHQsMSksdGhpcy5yb3dzLT0xLHRoaXN9YWRkUm93KHQsZSl7cmV0dXJuIHZvaWQgMD09PWUmJihlPXQsdD10aGlzLnJvd3MpLGdjKHRoaXMsdCwhMCksZT1GbG9hdDY0QXJyYXkuZnJvbShuMCh0aGlzLGUpKSx0aGlzLmRhdGEuc3BsaWNlKHQsMCxlKSx0aGlzLnJvd3MrPTEsdGhpc31yZW1vdmVDb2x1bW4odCl7X2ModGhpcyx0KTtmb3IobGV0IGU9MDtlPHRoaXMucm93cztlKyspe2xldCBpPW5ldyBGbG9hdDY0QXJyYXkodGhpcy5jb2x1bW5zLTEpO2ZvcihsZXQgcj0wO3I8dDtyKyspaVtyXT10aGlzLmRhdGFbZV1bcl07Zm9yKGxldCByPXQrMTtyPHRoaXMuY29sdW1ucztyKyspaVtyLTFdPXRoaXMuZGF0YVtlXVtyXTt0aGlzLmRhdGFbZV09aX1yZXR1cm4gdGhpcy5jb2x1bW5zLT0xLHRoaXN9YWRkQ29sdW1uKHQsZSl7dHlwZW9mIGU+InUiJiYoZT10LHQ9dGhpcy5jb2x1bW5zKSxfYyh0aGlzLHQsITApLGU9aTAodGhpcyxlKTtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspe2xldCByPW5ldyBGbG9hdDY0QXJyYXkodGhpcy5jb2x1bW5zKzEpLG89MDtmb3IoO288dDtvKyspcltvXT10aGlzLmRhdGFbaV1bb107Zm9yKHJbbysrXT1lW2ldO288dGhpcy5jb2x1bW5zKzE7bysrKXJbb109dGhpcy5kYXRhW2ldW28tMV07dGhpcy5kYXRhW2ldPXJ9cmV0dXJuIHRoaXMuY29sdW1ucys9MSx0aGlzfX0sdD1ybiwobj10aSkucHJvdG90eXBlLmFkZD1mdW5jdGlvbihpKXtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIGk/dGhpcy5hZGRTKGkpOnRoaXMuYWRkTShpKX0sbi5wcm90b3R5cGUuYWRkUz1mdW5jdGlvbihpKXtmb3IobGV0IHI9MDtyPHRoaXMucm93cztyKyspZm9yKGxldCBvPTA7bzx0aGlzLmNvbHVtbnM7bysrKXRoaXMuc2V0KHIsbyx0aGlzLmdldChyLG8pK2kpO3JldHVybiB0aGlzfSxuLnByb3RvdHlwZS5hZGRNPWZ1bmN0aW9uKGkpe2lmKGk9dC5jaGVja01hdHJpeChpKSx0aGlzLnJvd3MhPT1pLnJvd3N8fHRoaXMuY29sdW1ucyE9PWkuY29sdW1ucyl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiTWF0cmljZXMgZGltZW5zaW9ucyBtdXN0IGJlIGVxdWFsIik7Zm9yKGxldCByPTA7cjx0aGlzLnJvd3M7cisrKWZvcihsZXQgbz0wO288dGhpcy5jb2x1bW5zO28rKyl0aGlzLnNldChyLG8sdGhpcy5nZXQocixvKStpLmdldChyLG8pKTtyZXR1cm4gdGhpc30sbi5hZGQ9ZnVuY3Rpb24oaSxyKXtyZXR1cm4gbmV3IHQoaSkuYWRkKHIpfSxuLnByb3RvdHlwZS5zdWI9ZnVuY3Rpb24oaSl7cmV0dXJuIm51bWJlciI9PXR5cGVvZiBpP3RoaXMuc3ViUyhpKTp0aGlzLnN1Yk0oaSl9LG4ucHJvdG90eXBlLnN1YlM9ZnVuY3Rpb24oaSl7Zm9yKGxldCByPTA7cjx0aGlzLnJvd3M7cisrKWZvcihsZXQgbz0wO288dGhpcy5jb2x1bW5zO28rKyl0aGlzLnNldChyLG8sdGhpcy5nZXQocixvKS1pKTtyZXR1cm4gdGhpc30sbi5wcm90b3R5cGUuc3ViTT1mdW5jdGlvbihpKXtpZihpPXQuY2hlY2tNYXRyaXgoaSksdGhpcy5yb3dzIT09aS5yb3dzfHx0aGlzLmNvbHVtbnMhPT1pLmNvbHVtbnMpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIk1hdHJpY2VzIGRpbWVuc2lvbnMgbXVzdCBiZSBlcXVhbCIpO2ZvcihsZXQgcj0wO3I8dGhpcy5yb3dzO3IrKylmb3IobGV0IG89MDtvPHRoaXMuY29sdW1ucztvKyspdGhpcy5zZXQocixvLHRoaXMuZ2V0KHIsbyktaS5nZXQocixvKSk7cmV0dXJuIHRoaXN9LG4uc3ViPWZ1bmN0aW9uKGkscil7cmV0dXJuIG5ldyB0KGkpLnN1YihyKX0sbi5wcm90b3R5cGUuc3VidHJhY3Q9bi5wcm90b3R5cGUuc3ViLG4ucHJvdG90eXBlLnN1YnRyYWN0Uz1uLnByb3RvdHlwZS5zdWJTLG4ucHJvdG90eXBlLnN1YnRyYWN0TT1uLnByb3RvdHlwZS5zdWJNLG4uc3VidHJhY3Q9bi5zdWIsbi5wcm90b3R5cGUubXVsPWZ1bmN0aW9uKGkpe3JldHVybiJudW1iZXIiPT10eXBlb2YgaT90aGlzLm11bFMoaSk6dGhpcy5tdWxNKGkpfSxuLnByb3RvdHlwZS5tdWxTPWZ1bmN0aW9uKGkpe2ZvcihsZXQgcj0wO3I8dGhpcy5yb3dzO3IrKylmb3IobGV0IG89MDtvPHRoaXMuY29sdW1ucztvKyspdGhpcy5zZXQocixvLHRoaXMuZ2V0KHIsbykqaSk7cmV0dXJuIHRoaXN9LG4ucHJvdG90eXBlLm11bE09ZnVuY3Rpb24oaSl7aWYoaT10LmNoZWNrTWF0cml4KGkpLHRoaXMucm93cyE9PWkucm93c3x8dGhpcy5jb2x1bW5zIT09aS5jb2x1bW5zKXRocm93IG5ldyBSYW5nZUVycm9yKCJNYXRyaWNlcyBkaW1lbnNpb25zIG11c3QgYmUgZXF1YWwiKTtmb3IobGV0IHI9MDtyPHRoaXMucm93cztyKyspZm9yKGxldCBvPTA7bzx0aGlzLmNvbHVtbnM7bysrKXRoaXMuc2V0KHIsbyx0aGlzLmdldChyLG8pKmkuZ2V0KHIsbykpO3JldHVybiB0aGlzfSxuLm11bD1mdW5jdGlvbihpLHIpe3JldHVybiBuZXcgdChpKS5tdWwocil9LG4ucHJvdG90eXBlLm11bHRpcGx5PW4ucHJvdG90eXBlLm11bCxuLnByb3RvdHlwZS5tdWx0aXBseVM9bi5wcm90b3R5cGUubXVsUyxuLnByb3RvdHlwZS5tdWx0aXBseU09bi5wcm90b3R5cGUubXVsTSxuLm11bHRpcGx5PW4ubXVsLG4ucHJvdG90eXBlLmRpdj1mdW5jdGlvbihpKXtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIGk/dGhpcy5kaXZTKGkpOnRoaXMuZGl2TShpKX0sbi5wcm90b3R5cGUuZGl2Uz1mdW5jdGlvbihpKXtmb3IobGV0IHI9MDtyPHRoaXMucm93cztyKyspZm9yKGxldCBvPTA7bzx0aGlzLmNvbHVtbnM7bysrKXRoaXMuc2V0KHIsbyx0aGlzLmdldChyLG8pL2kpO3JldHVybiB0aGlzfSxuLnByb3RvdHlwZS5kaXZNPWZ1bmN0aW9uKGkpe2lmKGk9dC5jaGVja01hdHJpeChpKSx0aGlzLnJvd3MhPT1pLnJvd3N8fHRoaXMuY29sdW1ucyE9PWkuY29sdW1ucyl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiTWF0cmljZXMgZGltZW5zaW9ucyBtdXN0IGJlIGVxdWFsIik7Zm9yKGxldCByPTA7cjx0aGlzLnJvd3M7cisrKWZvcihsZXQgbz0wO288dGhpcy5jb2x1bW5zO28rKyl0aGlzLnNldChyLG8sdGhpcy5nZXQocixvKS9pLmdldChyLG8pKTtyZXR1cm4gdGhpc30sbi5kaXY9ZnVuY3Rpb24oaSxyKXtyZXR1cm4gbmV3IHQoaSkuZGl2KHIpfSxuLnByb3RvdHlwZS5kaXZpZGU9bi5wcm90b3R5cGUuZGl2LG4ucHJvdG90eXBlLmRpdmlkZVM9bi5wcm90b3R5cGUuZGl2UyxuLnByb3RvdHlwZS5kaXZpZGVNPW4ucHJvdG90eXBlLmRpdk0sbi5kaXZpZGU9bi5kaXYsbi5wcm90b3R5cGUubW9kPWZ1bmN0aW9uKGkpe3JldHVybiJudW1iZXIiPT10eXBlb2YgaT90aGlzLm1vZFMoaSk6dGhpcy5tb2RNKGkpfSxuLnByb3RvdHlwZS5tb2RTPWZ1bmN0aW9uKGkpe2ZvcihsZXQgcj0wO3I8dGhpcy5yb3dzO3IrKylmb3IobGV0IG89MDtvPHRoaXMuY29sdW1ucztvKyspdGhpcy5zZXQocixvLHRoaXMuZ2V0KHIsbyklaSk7cmV0dXJuIHRoaXN9LG4ucHJvdG90eXBlLm1vZE09ZnVuY3Rpb24oaSl7aWYoaT10LmNoZWNrTWF0cml4KGkpLHRoaXMucm93cyE9PWkucm93c3x8dGhpcy5jb2x1bW5zIT09aS5jb2x1bW5zKXRocm93IG5ldyBSYW5nZUVycm9yKCJNYXRyaWNlcyBkaW1lbnNpb25zIG11c3QgYmUgZXF1YWwiKTtmb3IobGV0IHI9MDtyPHRoaXMucm93cztyKyspZm9yKGxldCBvPTA7bzx0aGlzLmNvbHVtbnM7bysrKXRoaXMuc2V0KHIsbyx0aGlzLmdldChyLG8pJWkuZ2V0KHIsbykpO3JldHVybiB0aGlzfSxuLm1vZD1mdW5jdGlvbihpLHIpe3JldHVybiBuZXcgdChpKS5tb2Qocil9LG4ucHJvdG90eXBlLm1vZHVsdXM9bi5wcm90b3R5cGUubW9kLG4ucHJvdG90eXBlLm1vZHVsdXNTPW4ucHJvdG90eXBlLm1vZFMsbi5wcm90b3R5cGUubW9kdWx1c009bi5wcm90b3R5cGUubW9kTSxuLm1vZHVsdXM9bi5tb2Qsbi5wcm90b3R5cGUuYW5kPWZ1bmN0aW9uKGkpe3JldHVybiJudW1iZXIiPT10eXBlb2YgaT90aGlzLmFuZFMoaSk6dGhpcy5hbmRNKGkpfSxuLnByb3RvdHlwZS5hbmRTPWZ1bmN0aW9uKGkpe2ZvcihsZXQgcj0wO3I8dGhpcy5yb3dzO3IrKylmb3IobGV0IG89MDtvPHRoaXMuY29sdW1ucztvKyspdGhpcy5zZXQocixvLHRoaXMuZ2V0KHIsbykmaSk7cmV0dXJuIHRoaXN9LG4ucHJvdG90eXBlLmFuZE09ZnVuY3Rpb24oaSl7aWYoaT10LmNoZWNrTWF0cml4KGkpLHRoaXMucm93cyE9PWkucm93c3x8dGhpcy5jb2x1bW5zIT09aS5jb2x1bW5zKXRocm93IG5ldyBSYW5nZUVycm9yKCJNYXRyaWNlcyBkaW1lbnNpb25zIG11c3QgYmUgZXF1YWwiKTtmb3IobGV0IHI9MDtyPHRoaXMucm93cztyKyspZm9yKGxldCBvPTA7bzx0aGlzLmNvbHVtbnM7bysrKXRoaXMuc2V0KHIsbyx0aGlzLmdldChyLG8pJmkuZ2V0KHIsbykpO3JldHVybiB0aGlzfSxuLmFuZD1mdW5jdGlvbihpLHIpe3JldHVybiBuZXcgdChpKS5hbmQocil9LG4ucHJvdG90eXBlLm9yPWZ1bmN0aW9uKGkpe3JldHVybiJudW1iZXIiPT10eXBlb2YgaT90aGlzLm9yUyhpKTp0aGlzLm9yTShpKX0sbi5wcm90b3R5cGUub3JTPWZ1bmN0aW9uKGkpe2ZvcihsZXQgcj0wO3I8dGhpcy5yb3dzO3IrKylmb3IobGV0IG89MDtvPHRoaXMuY29sdW1ucztvKyspdGhpcy5zZXQocixvLHRoaXMuZ2V0KHIsbyl8aSk7cmV0dXJuIHRoaXN9LG4ucHJvdG90eXBlLm9yTT1mdW5jdGlvbihpKXtpZihpPXQuY2hlY2tNYXRyaXgoaSksdGhpcy5yb3dzIT09aS5yb3dzfHx0aGlzLmNvbHVtbnMhPT1pLmNvbHVtbnMpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIk1hdHJpY2VzIGRpbWVuc2lvbnMgbXVzdCBiZSBlcXVhbCIpO2ZvcihsZXQgcj0wO3I8dGhpcy5yb3dzO3IrKylmb3IobGV0IG89MDtvPHRoaXMuY29sdW1ucztvKyspdGhpcy5zZXQocixvLHRoaXMuZ2V0KHIsbyl8aS5nZXQocixvKSk7cmV0dXJuIHRoaXN9LG4ub3I9ZnVuY3Rpb24oaSxyKXtyZXR1cm4gbmV3IHQoaSkub3Iocil9LG4ucHJvdG90eXBlLnhvcj1mdW5jdGlvbihpKXtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIGk/dGhpcy54b3JTKGkpOnRoaXMueG9yTShpKX0sbi5wcm90b3R5cGUueG9yUz1mdW5jdGlvbihpKXtmb3IobGV0IHI9MDtyPHRoaXMucm93cztyKyspZm9yKGxldCBvPTA7bzx0aGlzLmNvbHVtbnM7bysrKXRoaXMuc2V0KHIsbyx0aGlzLmdldChyLG8pXmkpO3JldHVybiB0aGlzfSxuLnByb3RvdHlwZS54b3JNPWZ1bmN0aW9uKGkpe2lmKGk9dC5jaGVja01hdHJpeChpKSx0aGlzLnJvd3MhPT1pLnJvd3N8fHRoaXMuY29sdW1ucyE9PWkuY29sdW1ucyl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiTWF0cmljZXMgZGltZW5zaW9ucyBtdXN0IGJlIGVxdWFsIik7Zm9yKGxldCByPTA7cjx0aGlzLnJvd3M7cisrKWZvcihsZXQgbz0wO288dGhpcy5jb2x1bW5zO28rKyl0aGlzLnNldChyLG8sdGhpcy5nZXQocixvKV5pLmdldChyLG8pKTtyZXR1cm4gdGhpc30sbi54b3I9ZnVuY3Rpb24oaSxyKXtyZXR1cm4gbmV3IHQoaSkueG9yKHIpfSxuLnByb3RvdHlwZS5sZWZ0U2hpZnQ9ZnVuY3Rpb24oaSl7cmV0dXJuIm51bWJlciI9PXR5cGVvZiBpP3RoaXMubGVmdFNoaWZ0UyhpKTp0aGlzLmxlZnRTaGlmdE0oaSl9LG4ucHJvdG90eXBlLmxlZnRTaGlmdFM9ZnVuY3Rpb24oaSl7Zm9yKGxldCByPTA7cjx0aGlzLnJvd3M7cisrKWZvcihsZXQgbz0wO288dGhpcy5jb2x1bW5zO28rKyl0aGlzLnNldChyLG8sdGhpcy5nZXQocixvKTw8aSk7cmV0dXJuIHRoaXN9LG4ucHJvdG90eXBlLmxlZnRTaGlmdE09ZnVuY3Rpb24oaSl7aWYoaT10LmNoZWNrTWF0cml4KGkpLHRoaXMucm93cyE9PWkucm93c3x8dGhpcy5jb2x1bW5zIT09aS5jb2x1bW5zKXRocm93IG5ldyBSYW5nZUVycm9yKCJNYXRyaWNlcyBkaW1lbnNpb25zIG11c3QgYmUgZXF1YWwiKTtmb3IobGV0IHI9MDtyPHRoaXMucm93cztyKyspZm9yKGxldCBvPTA7bzx0aGlzLmNvbHVtbnM7bysrKXRoaXMuc2V0KHIsbyx0aGlzLmdldChyLG8pPDxpLmdldChyLG8pKTtyZXR1cm4gdGhpc30sbi5sZWZ0U2hpZnQ9ZnVuY3Rpb24oaSxyKXtyZXR1cm4gbmV3IHQoaSkubGVmdFNoaWZ0KHIpfSxuLnByb3RvdHlwZS5zaWduUHJvcGFnYXRpbmdSaWdodFNoaWZ0PWZ1bmN0aW9uKGkpe3JldHVybiJudW1iZXIiPT10eXBlb2YgaT90aGlzLnNpZ25Qcm9wYWdhdGluZ1JpZ2h0U2hpZnRTKGkpOnRoaXMuc2lnblByb3BhZ2F0aW5nUmlnaHRTaGlmdE0oaSl9LG4ucHJvdG90eXBlLnNpZ25Qcm9wYWdhdGluZ1JpZ2h0U2hpZnRTPWZ1bmN0aW9uKGkpe2ZvcihsZXQgcj0wO3I8dGhpcy5yb3dzO3IrKylmb3IobGV0IG89MDtvPHRoaXMuY29sdW1ucztvKyspdGhpcy5zZXQocixvLHRoaXMuZ2V0KHIsbyk+PmkpO3JldHVybiB0aGlzfSxuLnByb3RvdHlwZS5zaWduUHJvcGFnYXRpbmdSaWdodFNoaWZ0TT1mdW5jdGlvbihpKXtpZihpPXQuY2hlY2tNYXRyaXgoaSksdGhpcy5yb3dzIT09aS5yb3dzfHx0aGlzLmNvbHVtbnMhPT1pLmNvbHVtbnMpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIk1hdHJpY2VzIGRpbWVuc2lvbnMgbXVzdCBiZSBlcXVhbCIpO2ZvcihsZXQgcj0wO3I8dGhpcy5yb3dzO3IrKylmb3IobGV0IG89MDtvPHRoaXMuY29sdW1ucztvKyspdGhpcy5zZXQocixvLHRoaXMuZ2V0KHIsbyk+PmkuZ2V0KHIsbykpO3JldHVybiB0aGlzfSxuLnNpZ25Qcm9wYWdhdGluZ1JpZ2h0U2hpZnQ9ZnVuY3Rpb24oaSxyKXtyZXR1cm4gbmV3IHQoaSkuc2lnblByb3BhZ2F0aW5nUmlnaHRTaGlmdChyKX0sbi5wcm90b3R5cGUucmlnaHRTaGlmdD1mdW5jdGlvbihpKXtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIGk/dGhpcy5yaWdodFNoaWZ0UyhpKTp0aGlzLnJpZ2h0U2hpZnRNKGkpfSxuLnByb3RvdHlwZS5yaWdodFNoaWZ0Uz1mdW5jdGlvbihpKXtmb3IobGV0IHI9MDtyPHRoaXMucm93cztyKyspZm9yKGxldCBvPTA7bzx0aGlzLmNvbHVtbnM7bysrKXRoaXMuc2V0KHIsbyx0aGlzLmdldChyLG8pPj4+aSk7cmV0dXJuIHRoaXN9LG4ucHJvdG90eXBlLnJpZ2h0U2hpZnRNPWZ1bmN0aW9uKGkpe2lmKGk9dC5jaGVja01hdHJpeChpKSx0aGlzLnJvd3MhPT1pLnJvd3N8fHRoaXMuY29sdW1ucyE9PWkuY29sdW1ucyl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiTWF0cmljZXMgZGltZW5zaW9ucyBtdXN0IGJlIGVxdWFsIik7Zm9yKGxldCByPTA7cjx0aGlzLnJvd3M7cisrKWZvcihsZXQgbz0wO288dGhpcy5jb2x1bW5zO28rKyl0aGlzLnNldChyLG8sdGhpcy5nZXQocixvKT4+PmkuZ2V0KHIsbykpO3JldHVybiB0aGlzfSxuLnJpZ2h0U2hpZnQ9ZnVuY3Rpb24oaSxyKXtyZXR1cm4gbmV3IHQoaSkucmlnaHRTaGlmdChyKX0sbi5wcm90b3R5cGUuemVyb0ZpbGxSaWdodFNoaWZ0PW4ucHJvdG90eXBlLnJpZ2h0U2hpZnQsbi5wcm90b3R5cGUuemVyb0ZpbGxSaWdodFNoaWZ0Uz1uLnByb3RvdHlwZS5yaWdodFNoaWZ0UyxuLnByb3RvdHlwZS56ZXJvRmlsbFJpZ2h0U2hpZnRNPW4ucHJvdG90eXBlLnJpZ2h0U2hpZnRNLG4uemVyb0ZpbGxSaWdodFNoaWZ0PW4ucmlnaHRTaGlmdCxuLnByb3RvdHlwZS5ub3Q9ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscix+dGhpcy5nZXQoaSxyKSk7cmV0dXJuIHRoaXN9LG4ubm90PWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS5ub3QoKX0sbi5wcm90b3R5cGUuYWJzPWZ1bmN0aW9uKCl7Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKWZvcihsZXQgcj0wO3I8dGhpcy5jb2x1bW5zO3IrKyl0aGlzLnNldChpLHIsTWF0aC5hYnModGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLmFicz1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuYWJzKCl9LG4ucHJvdG90eXBlLmFjb3M9ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLmFjb3ModGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLmFjb3M9ZnVuY3Rpb24oaSl7cmV0dXJuIG5ldyB0KGkpLmFjb3MoKX0sbi5wcm90b3R5cGUuYWNvc2g9ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLmFjb3NoKHRoaXMuZ2V0KGkscikpKTtyZXR1cm4gdGhpc30sbi5hY29zaD1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuYWNvc2goKX0sbi5wcm90b3R5cGUuYXNpbj1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGguYXNpbih0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4uYXNpbj1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuYXNpbigpfSxuLnByb3RvdHlwZS5hc2luaD1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGguYXNpbmgodGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLmFzaW5oPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS5hc2luaCgpfSxuLnByb3RvdHlwZS5hdGFuPWZ1bmN0aW9uKCl7Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKWZvcihsZXQgcj0wO3I8dGhpcy5jb2x1bW5zO3IrKyl0aGlzLnNldChpLHIsTWF0aC5hdGFuKHRoaXMuZ2V0KGkscikpKTtyZXR1cm4gdGhpc30sbi5hdGFuPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS5hdGFuKCl9LG4ucHJvdG90eXBlLmF0YW5oPWZ1bmN0aW9uKCl7Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKWZvcihsZXQgcj0wO3I8dGhpcy5jb2x1bW5zO3IrKyl0aGlzLnNldChpLHIsTWF0aC5hdGFuaCh0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4uYXRhbmg9ZnVuY3Rpb24oaSl7cmV0dXJuIG5ldyB0KGkpLmF0YW5oKCl9LG4ucHJvdG90eXBlLmNicnQ9ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLmNicnQodGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLmNicnQ9ZnVuY3Rpb24oaSl7cmV0dXJuIG5ldyB0KGkpLmNicnQoKX0sbi5wcm90b3R5cGUuY2VpbD1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGguY2VpbCh0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4uY2VpbD1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuY2VpbCgpfSxuLnByb3RvdHlwZS5jbHozMj1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGguY2x6MzIodGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLmNsejMyPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS5jbHozMigpfSxuLnByb3RvdHlwZS5jb3M9ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLmNvcyh0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4uY29zPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS5jb3MoKX0sbi5wcm90b3R5cGUuY29zaD1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGguY29zaCh0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4uY29zaD1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuY29zaCgpfSxuLnByb3RvdHlwZS5leHA9ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLmV4cCh0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4uZXhwPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS5leHAoKX0sbi5wcm90b3R5cGUuZXhwbTE9ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLmV4cG0xKHRoaXMuZ2V0KGkscikpKTtyZXR1cm4gdGhpc30sbi5leHBtMT1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuZXhwbTEoKX0sbi5wcm90b3R5cGUuZmxvb3I9ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLmZsb29yKHRoaXMuZ2V0KGkscikpKTtyZXR1cm4gdGhpc30sbi5mbG9vcj1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuZmxvb3IoKX0sbi5wcm90b3R5cGUuZnJvdW5kPWZ1bmN0aW9uKCl7Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKWZvcihsZXQgcj0wO3I8dGhpcy5jb2x1bW5zO3IrKyl0aGlzLnNldChpLHIsTWF0aC5mcm91bmQodGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLmZyb3VuZD1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuZnJvdW5kKCl9LG4ucHJvdG90eXBlLmxvZz1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGgubG9nKHRoaXMuZ2V0KGkscikpKTtyZXR1cm4gdGhpc30sbi5sb2c9ZnVuY3Rpb24oaSl7cmV0dXJuIG5ldyB0KGkpLmxvZygpfSxuLnByb3RvdHlwZS5sb2cxcD1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGgubG9nMXAodGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLmxvZzFwPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS5sb2cxcCgpfSxuLnByb3RvdHlwZS5sb2cxMD1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGgubG9nMTAodGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLmxvZzEwPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS5sb2cxMCgpfSxuLnByb3RvdHlwZS5sb2cyPWZ1bmN0aW9uKCl7Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKWZvcihsZXQgcj0wO3I8dGhpcy5jb2x1bW5zO3IrKyl0aGlzLnNldChpLHIsTWF0aC5sb2cyKHRoaXMuZ2V0KGkscikpKTtyZXR1cm4gdGhpc30sbi5sb2cyPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS5sb2cyKCl9LG4ucHJvdG90eXBlLnJvdW5kPWZ1bmN0aW9uKCl7Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKWZvcihsZXQgcj0wO3I8dGhpcy5jb2x1bW5zO3IrKyl0aGlzLnNldChpLHIsTWF0aC5yb3VuZCh0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4ucm91bmQ9ZnVuY3Rpb24oaSl7cmV0dXJuIG5ldyB0KGkpLnJvdW5kKCl9LG4ucHJvdG90eXBlLnNpZ249ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLnNpZ24odGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLnNpZ249ZnVuY3Rpb24oaSl7cmV0dXJuIG5ldyB0KGkpLnNpZ24oKX0sbi5wcm90b3R5cGUuc2luPWZ1bmN0aW9uKCl7Zm9yKGxldCBpPTA7aTx0aGlzLnJvd3M7aSsrKWZvcihsZXQgcj0wO3I8dGhpcy5jb2x1bW5zO3IrKyl0aGlzLnNldChpLHIsTWF0aC5zaW4odGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLnNpbj1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuc2luKCl9LG4ucHJvdG90eXBlLnNpbmg9ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLnNpbmgodGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLnNpbmg9ZnVuY3Rpb24oaSl7cmV0dXJuIG5ldyB0KGkpLnNpbmgoKX0sbi5wcm90b3R5cGUuc3FydD1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGguc3FydCh0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4uc3FydD1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkuc3FydCgpfSxuLnByb3RvdHlwZS50YW49ZnVuY3Rpb24oKXtmb3IobGV0IGk9MDtpPHRoaXMucm93cztpKyspZm9yKGxldCByPTA7cjx0aGlzLmNvbHVtbnM7cisrKXRoaXMuc2V0KGkscixNYXRoLnRhbih0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4udGFuPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS50YW4oKX0sbi5wcm90b3R5cGUudGFuaD1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGgudGFuaCh0aGlzLmdldChpLHIpKSk7cmV0dXJuIHRoaXN9LG4udGFuaD1mdW5jdGlvbihpKXtyZXR1cm4gbmV3IHQoaSkudGFuaCgpfSxuLnByb3RvdHlwZS50cnVuYz1mdW5jdGlvbigpe2ZvcihsZXQgaT0wO2k8dGhpcy5yb3dzO2krKylmb3IobGV0IHI9MDtyPHRoaXMuY29sdW1ucztyKyspdGhpcy5zZXQoaSxyLE1hdGgudHJ1bmModGhpcy5nZXQoaSxyKSkpO3JldHVybiB0aGlzfSxuLnRydW5jPWZ1bmN0aW9uKGkpe3JldHVybiBuZXcgdChpKS50cnVuYygpfSxuLnBvdz1mdW5jdGlvbihpLHIpe3JldHVybiBuZXcgdChpKS5wb3cocil9LG4ucHJvdG90eXBlLnBvdz1mdW5jdGlvbihpKXtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIGk/dGhpcy5wb3dTKGkpOnRoaXMucG93TShpKX0sbi5wcm90b3R5cGUucG93Uz1mdW5jdGlvbihpKXtmb3IobGV0IHI9MDtyPHRoaXMucm93cztyKyspZm9yKGxldCBvPTA7bzx0aGlzLmNvbHVtbnM7bysrKXRoaXMuc2V0KHIsbyxNYXRoLnBvdyh0aGlzLmdldChyLG8pLGkpKTtyZXR1cm4gdGhpc30sbi5wcm90b3R5cGUucG93TT1mdW5jdGlvbihpKXtpZihpPXQuY2hlY2tNYXRyaXgoaSksdGhpcy5yb3dzIT09aS5yb3dzfHx0aGlzLmNvbHVtbnMhPT1pLmNvbHVtbnMpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIk1hdHJpY2VzIGRpbWVuc2lvbnMgbXVzdCBiZSBlcXVhbCIpO2ZvcihsZXQgcj0wO3I8dGhpcy5yb3dzO3IrKylmb3IobGV0IG89MDtvPHRoaXMuY29sdW1ucztvKyspdGhpcy5zZXQocixvLE1hdGgucG93KHRoaXMuZ2V0KHIsbyksaS5nZXQocixvKSkpO3JldHVybiB0aGlzfX0pLEFmZT1obygoKT0+e30pLERFPWhvKCgpPT57bzAoKSxBbD1jbGFzcyBleHRlbmRzIHRpe2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5kYXRhPXQsdGhpcy5yb3dzPXQubGVuZ3RoLHRoaXMuY29sdW1ucz10WzBdLmxlbmd0aH1zZXQodCxlLGkpe3JldHVybiB0aGlzLmRhdGFbdF1bZV09aSx0aGlzfWdldCh0LGUpe3JldHVybiB0aGlzLmRhdGFbdF1bZV19fX0pLElmZT1obygoKT0+e28wKCksREUoKSxBRT1jbGFzc3tjb25zdHJ1Y3Rvcih0KXtsZXQgYSxsLGMsdSxkLHAsaCxmLG0sZT0odD1BbC5jaGVja01hdHJpeCh0KSkuY2xvbmUoKSxpPWUucm93cyxyPWUuY29sdW1ucyxvPW5ldyBGbG9hdDY0QXJyYXkoaSkscz0xO2ZvcihhPTA7YTxpO2ErKylvW2FdPWE7Zm9yKGY9bmV3IEZsb2F0NjRBcnJheShpKSxsPTA7bDxyO2wrKyl7Zm9yKGE9MDthPGk7YSsrKWZbYV09ZS5nZXQoYSxsKTtmb3IoYT0wO2E8aTthKyspe2ZvcihtPU1hdGgubWluKGEsbCksZD0wLGM9MDtjPG07YysrKWQrPWUuZ2V0KGEsYykqZltjXTtmW2FdLT1kLGUuc2V0KGEsbCxmW2FdKX1mb3IodT1sLGE9bCsxO2E8aTthKyspTWF0aC5hYnMoZlthXSk+TWF0aC5hYnMoZlt1XSkmJih1PWEpO2lmKHUhPT1sKXtmb3IoYz0wO2M8cjtjKyspcD1lLmdldCh1LGMpLGUuc2V0KHUsYyxlLmdldChsLGMpKSxlLnNldChsLGMscCk7aD1vW3VdLG9bdV09b1tsXSxvW2xdPWgscz0tc31pZihsPGkmJjAhPT1lLmdldChsLGwpKWZvcihhPWwrMTthPGk7YSsrKWUuc2V0KGEsbCxlLmdldChhLGwpL2UuZ2V0KGwsbCkpfXRoaXMuTFU9ZSx0aGlzLnBpdm90VmVjdG9yPW8sdGhpcy5waXZvdFNpZ249c31pc1Npbmd1bGFyKCl7bGV0IHQ9dGhpcy5MVSxlPXQuY29sdW1ucztmb3IobGV0IGk9MDtpPGU7aSsrKWlmKDA9PT10LmdldChpLGkpKXJldHVybiEwO3JldHVybiExfXNvbHZlKHQpe3Q9cm4uY2hlY2tNYXRyaXgodCk7bGV0IGU9dGhpcy5MVTtpZihlLnJvd3MhPT10LnJvd3MpdGhyb3cgbmV3IEVycm9yKCJJbnZhbGlkIG1hdHJpeCBkaW1lbnNpb25zIik7aWYodGhpcy5pc1Npbmd1bGFyKCkpdGhyb3cgbmV3IEVycm9yKCJMVSBtYXRyaXggaXMgc2luZ3VsYXIiKTtsZXQgYSxsLGMscj10LmNvbHVtbnMsbz10LnN1Yk1hdHJpeFJvdyh0aGlzLnBpdm90VmVjdG9yLDAsci0xKSxzPWUuY29sdW1ucztmb3IoYz0wO2M8cztjKyspZm9yKGE9YysxO2E8czthKyspZm9yKGw9MDtsPHI7bCsrKW8uc2V0KGEsbCxvLmdldChhLGwpLW8uZ2V0KGMsbCkqZS5nZXQoYSxjKSk7Zm9yKGM9cy0xO2M+PTA7Yy0tKXtmb3IobD0wO2w8cjtsKyspby5zZXQoYyxsLG8uZ2V0KGMsbCkvZS5nZXQoYyxjKSk7Zm9yKGE9MDthPGM7YSsrKWZvcihsPTA7bDxyO2wrKylvLnNldChhLGwsby5nZXQoYSxsKS1vLmdldChjLGwpKmUuZ2V0KGEsYykpfXJldHVybiBvfWdldCBkZXRlcm1pbmFudCgpe2xldCB0PXRoaXMuTFU7aWYoIXQuaXNTcXVhcmUoKSl0aHJvdyBuZXcgRXJyb3IoIk1hdHJpeCBtdXN0IGJlIHNxdWFyZSIpO2xldCBlPXRoaXMucGl2b3RTaWduLGk9dC5jb2x1bW5zO2ZvcihsZXQgcj0wO3I8aTtyKyspZSo9dC5nZXQocixyKTtyZXR1cm4gZX1nZXQgbG93ZXJUcmlhbmd1bGFyTWF0cml4KCl7bGV0IHQ9dGhpcy5MVSxlPXQucm93cyxpPXQuY29sdW1ucyxyPW5ldyBybihlLGkpO2ZvcihsZXQgbz0wO288ZTtvKyspZm9yKGxldCBzPTA7czxpO3MrKylyLnNldChvLHMsbz5zP3QuZ2V0KG8scyk6bz09PXM/MTowKTtyZXR1cm4gcn1nZXQgdXBwZXJUcmlhbmd1bGFyTWF0cml4KCl7bGV0IHQ9dGhpcy5MVSxlPXQucm93cyxpPXQuY29sdW1ucyxyPW5ldyBybihlLGkpO2ZvcihsZXQgbz0wO288ZTtvKyspZm9yKGxldCBzPTA7czxpO3MrKylyLnNldChvLHMsbzw9cz90LmdldChvLHMpOjApO3JldHVybiByfWdldCBwaXZvdFBlcm11dGF0aW9uVmVjdG9yKCl7cmV0dXJuIEFycmF5LmZyb20odGhpcy5waXZvdFZlY3Rvcil9fX0pO2Z1bmN0aW9uIE9wKG4sdCl7bGV0IGU9MDtyZXR1cm4gTWF0aC5hYnMobik+TWF0aC5hYnModCk/KGU9dC9uLE1hdGguYWJzKG4pKk1hdGguc3FydCgxK2UqZSkpOjAhPT10PyhlPW4vdCxNYXRoLmFicyh0KSpNYXRoLnNxcnQoMStlKmUpKTowfXZhciBJRSx6YixsNj1obygoKT0+e30pLFBmZT1obygoKT0+e28wKCksREUoKSxsNigpLElFPWNsYXNze2NvbnN0cnVjdG9yKHQpe2xldCBzLGEsbCxjLGU9KHQ9QWwuY2hlY2tNYXRyaXgodCkpLmNsb25lKCksaT10LnJvd3Mscj10LmNvbHVtbnMsbz1uZXcgRmxvYXQ2NEFycmF5KHIpO2ZvcihsPTA7bDxyO2wrKyl7bGV0IHU9MDtmb3Iocz1sO3M8aTtzKyspdT1PcCh1LGUuZ2V0KHMsbCkpO2lmKDAhPT11KXtmb3IoZS5nZXQobCxsKTwwJiYodT0tdSkscz1sO3M8aTtzKyspZS5zZXQocyxsLGUuZ2V0KHMsbCkvdSk7Zm9yKGUuc2V0KGwsbCxlLmdldChsLGwpKzEpLGE9bCsxO2E8cjthKyspe2ZvcihjPTAscz1sO3M8aTtzKyspYys9ZS5nZXQocyxsKSplLmdldChzLGEpO2ZvcihjPS1jL2UuZ2V0KGwsbCkscz1sO3M8aTtzKyspZS5zZXQocyxhLGUuZ2V0KHMsYSkrYyplLmdldChzLGwpKX19b1tsXT0tdX10aGlzLlFSPWUsdGhpcy5SZGlhZz1vfXNvbHZlKHQpe3Q9cm4uY2hlY2tNYXRyaXgodCk7bGV0IGU9dGhpcy5RUixpPWUucm93cztpZih0LnJvd3MhPT1pKXRocm93IG5ldyBFcnJvcigiTWF0cml4IHJvdyBkaW1lbnNpb25zIG11c3QgYWdyZWUiKTtpZighdGhpcy5pc0Z1bGxSYW5rKCkpdGhyb3cgbmV3IEVycm9yKCJNYXRyaXggaXMgcmFuayBkZWZpY2llbnQiKTtsZXQgYSxsLGMsdSxyPXQuY29sdW1ucyxvPXQuY2xvbmUoKSxzPWUuY29sdW1ucztmb3IoYz0wO2M8cztjKyspZm9yKGw9MDtsPHI7bCsrKXtmb3IodT0wLGE9YzthPGk7YSsrKXUrPWUuZ2V0KGEsYykqby5nZXQoYSxsKTtmb3IodT0tdS9lLmdldChjLGMpLGE9YzthPGk7YSsrKW8uc2V0KGEsbCxvLmdldChhLGwpK3UqZS5nZXQoYSxjKSl9Zm9yKGM9cy0xO2M+PTA7Yy0tKXtmb3IobD0wO2w8cjtsKyspby5zZXQoYyxsLG8uZ2V0KGMsbCkvdGhpcy5SZGlhZ1tjXSk7Zm9yKGE9MDthPGM7YSsrKWZvcihsPTA7bDxyO2wrKylvLnNldChhLGwsby5nZXQoYSxsKS1vLmdldChjLGwpKmUuZ2V0KGEsYykpfXJldHVybiBvLnN1Yk1hdHJpeCgwLHMtMSwwLHItMSl9aXNGdWxsUmFuaygpe2xldCB0PXRoaXMuUVIuY29sdW1ucztmb3IobGV0IGU9MDtlPHQ7ZSsrKWlmKDA9PT10aGlzLlJkaWFnW2VdKXJldHVybiExO3JldHVybiEwfWdldCB1cHBlclRyaWFuZ3VsYXJNYXRyaXgoKXtsZXQgcixvLHQ9dGhpcy5RUixlPXQuY29sdW1ucyxpPW5ldyBybihlLGUpO2ZvcihyPTA7cjxlO3IrKylmb3Iobz0wO288ZTtvKyspaS5zZXQocixvLHI8bz90LmdldChyLG8pOnI9PT1vP3RoaXMuUmRpYWdbcl06MCk7cmV0dXJuIGl9Z2V0IG9ydGhvZ29uYWxNYXRyaXgoKXtsZXQgbyxzLGEsbCx0PXRoaXMuUVIsZT10LnJvd3MsaT10LmNvbHVtbnMscj1uZXcgcm4oZSxpKTtmb3IoYT1pLTE7YT49MDthLS0pe2ZvcihvPTA7bzxlO28rKylyLnNldChvLGEsMCk7Zm9yKHIuc2V0KGEsYSwxKSxzPWE7czxpO3MrKylpZigwIT09dC5nZXQoYSxhKSl7Zm9yKGw9MCxvPWE7bzxlO28rKylsKz10LmdldChvLGEpKnIuZ2V0KG8scyk7Zm9yKGw9LWwvdC5nZXQoYSxhKSxvPWE7bzxlO28rKylyLnNldChvLHMsci5nZXQobyxzKStsKnQuZ2V0KG8sYSkpfX1yZXR1cm4gcn19fSksUmZlPWhvKCgpPT57bzAoKSxERSgpLGw2KCksemI9Y2xhc3N7Y29uc3RydWN0b3IodCxlPXt9KXtpZigodD1BbC5jaGVja01hdHJpeCh0KSkuaXNFbXB0eSgpKXRocm93IG5ldyBFcnJvcigiTWF0cml4IG11c3QgYmUgbm9uLWVtcHR5Iik7bGV0IGQsaT10LnJvd3Mscj10LmNvbHVtbnMse2NvbXB1dGVMZWZ0U2luZ3VsYXJWZWN0b3JzOm89ITAsY29tcHV0ZVJpZ2h0U2luZ3VsYXJWZWN0b3JzOnM9ITAsYXV0b1RyYW5zcG9zZTphPSExfT1lLGw9Qm9vbGVhbihvKSxjPUJvb2xlYW4ocyksdT0hMTtpZihpPHIpaWYoYSl7ZD10LnRyYW5zcG9zZSgpLGk9ZC5yb3dzLHI9ZC5jb2x1bW5zLHU9ITA7bGV0IHc9bDtsPWMsYz13fWVsc2UgZD10LmNsb25lKCksY29uc29sZS53YXJuKCJDb21wdXRpbmcgU1ZEIG9uIGEgbWF0cml4IHdpdGggbW9yZSBjb2x1bW5zIHRoYW4gcm93cy4gQ29uc2lkZXIgZW5hYmxpbmcgYXV0b1RyYW5zcG9zZSIpO2Vsc2UgZD10LmNsb25lKCk7bGV0IHA9TWF0aC5taW4oaSxyKSxoPU1hdGgubWluKGkrMSxyKSxmPW5ldyBGbG9hdDY0QXJyYXkoaCksbT1uZXcgcm4oaSxwKSx4PW5ldyBybihyLHIpLGc9bmV3IEZsb2F0NjRBcnJheShyKSxiPW5ldyBGbG9hdDY0QXJyYXkoaSksRD1uZXcgRmxvYXQ2NEFycmF5KGgpO2ZvcihsZXQgdz0wO3c8aDt3KyspRFt3XT13O2xldCBUPU1hdGgubWluKGktMSxyKSxrPU1hdGgubWF4KDAsTWF0aC5taW4oci0yLGkpKSxaPU1hdGgubWF4KFQsayk7Zm9yKGxldCB3PTA7dzxaO3crKyl7aWYodzxUKXtmW3ddPTA7Zm9yKGxldCBGPXc7RjxpO0YrKylmW3ddPU9wKGZbd10sZC5nZXQoRix3KSk7aWYoMCE9PWZbd10pe2QuZ2V0KHcsdyk8MCYmKGZbd109LWZbd10pO2ZvcihsZXQgRj13O0Y8aTtGKyspZC5zZXQoRix3LGQuZ2V0KEYsdykvZlt3XSk7ZC5zZXQodyx3LGQuZ2V0KHcsdykrMSl9Zlt3XT0tZlt3XX1mb3IobGV0IEY9dysxO0Y8cjtGKyspe2lmKHc8VCYmMCE9PWZbd10pe2xldCBxPTA7Zm9yKGxldCBLPXc7SzxpO0srKylxKz1kLmdldChLLHcpKmQuZ2V0KEssRik7cT0tcS9kLmdldCh3LHcpO2ZvcihsZXQgSz13O0s8aTtLKyspZC5zZXQoSyxGLGQuZ2V0KEssRikrcSpkLmdldChLLHcpKX1nW0ZdPWQuZ2V0KHcsRil9aWYobCYmdzxUKWZvcihsZXQgRj13O0Y8aTtGKyspbS5zZXQoRix3LGQuZ2V0KEYsdykpO2lmKHc8ayl7Z1t3XT0wO2ZvcihsZXQgRj13KzE7RjxyO0YrKylnW3ddPU9wKGdbd10sZ1tGXSk7aWYoMCE9PWdbd10pe2dbdysxXTwwJiYoZ1t3XT0wLWdbd10pO2ZvcihsZXQgRj13KzE7RjxyO0YrKylnW0ZdLz1nW3ddO2dbdysxXSs9MX1pZihnW3ddPS1nW3ddLHcrMTxpJiYwIT09Z1t3XSl7Zm9yKGxldCBGPXcrMTtGPGk7RisrKWJbRl09MDtmb3IobGV0IEY9dysxO0Y8aTtGKyspZm9yKGxldCBxPXcrMTtxPHI7cSsrKWJbRl0rPWdbcV0qZC5nZXQoRixxKTtmb3IobGV0IEY9dysxO0Y8cjtGKyspe2xldCBxPS1nW0ZdL2dbdysxXTtmb3IobGV0IEs9dysxO0s8aTtLKyspZC5zZXQoSyxGLGQuZ2V0KEssRikrcSpiW0tdKX19aWYoYylmb3IobGV0IEY9dysxO0Y8cjtGKyspeC5zZXQoRix3LGdbRl0pfX1sZXQgej1NYXRoLm1pbihyLGkrMSk7aWYoVDxyJiYoZltUXT1kLmdldChULFQpKSxpPHomJihmW3otMV09MCksaysxPHomJihnW2tdPWQuZ2V0KGssei0xKSksZ1t6LTFdPTAsbCl7Zm9yKGxldCB3PVQ7dzxwO3crKyl7Zm9yKGxldCBGPTA7RjxpO0YrKyltLnNldChGLHcsMCk7bS5zZXQodyx3LDEpfWZvcihsZXQgdz1ULTE7dz49MDt3LS0paWYoMCE9PWZbd10pe2ZvcihsZXQgRj13KzE7RjxwO0YrKyl7bGV0IHE9MDtmb3IobGV0IEs9dztLPGk7SysrKXErPW0uZ2V0KEssdykqbS5nZXQoSyxGKTtxPS1xL20uZ2V0KHcsdyk7Zm9yKGxldCBLPXc7SzxpO0srKyltLnNldChLLEYsbS5nZXQoSyxGKStxKm0uZ2V0KEssdykpfWZvcihsZXQgRj13O0Y8aTtGKyspbS5zZXQoRix3LC1tLmdldChGLHcpKTttLnNldCh3LHcsMSttLmdldCh3LHcpKTtmb3IobGV0IEY9MDtGPHctMTtGKyspbS5zZXQoRix3LDApfWVsc2V7Zm9yKGxldCBGPTA7RjxpO0YrKyltLnNldChGLHcsMCk7bS5zZXQodyx3LDEpfX1pZihjKWZvcihsZXQgdz1yLTE7dz49MDt3LS0pe2lmKHc8ayYmMCE9PWdbd10pZm9yKGxldCBGPXcrMTtGPHI7RisrKXtsZXQgcT0wO2ZvcihsZXQgSz13KzE7SzxyO0srKylxKz14LmdldChLLHcpKnguZ2V0KEssRik7cT0tcS94LmdldCh3KzEsdyk7Zm9yKGxldCBLPXcrMTtLPHI7SysrKXguc2V0KEssRix4LmdldChLLEYpK3EqeC5nZXQoSyx3KSl9Zm9yKGxldCBGPTA7RjxyO0YrKyl4LnNldChGLHcsMCk7eC5zZXQodyx3LDEpfWxldCBmZT16LTEsdWU9MCxoZT1OdW1iZXIuRVBTSUxPTjtmb3IoO3o+MDspe2xldCB3LEY7Zm9yKHc9ei0yO3c+PS0xJiYtMSE9PXc7dy0tKXtsZXQgcT1OdW1iZXIuTUlOX1ZBTFVFK2hlKk1hdGguYWJzKGZbd10rTWF0aC5hYnMoZlt3KzFdKSk7aWYoTWF0aC5hYnMoZ1t3XSk8PXF8fE51bWJlci5pc05hTihnW3ddKSl7Z1t3XT0wO2JyZWFrfX1pZih3PT09ei0yKUY9NDtlbHNle2xldCBxO2ZvcihxPXotMTtxPj13JiZxIT09dztxLS0pe2xldCBLPShxIT09ej9NYXRoLmFicyhnW3FdKTowKSsocSE9PXcrMT9NYXRoLmFicyhnW3EtMV0pOjApO2lmKE1hdGguYWJzKGZbcV0pPD1oZSpLKXtmW3FdPTA7YnJlYWt9fXE9PT13P0Y9MzpxPT09ei0xP0Y9MTooRj0yLHc9cSl9c3dpdGNoKHcrKyxGKXtjYXNlIDE6e2xldCBxPWdbei0yXTtnW3otMl09MDtmb3IobGV0IEs9ei0yO0s+PXc7Sy0tKXtsZXQgZGU9T3AoZltLXSxxKSxZPWZbS10vZGUsYWU9cS9kZTtpZihmW0tdPWRlLEshPT13JiYocT0tYWUqZ1tLLTFdLGdbSy0xXT1ZKmdbSy0xXSksYylmb3IobGV0IGxlPTA7bGU8cjtsZSsrKWRlPVkqeC5nZXQobGUsSykrYWUqeC5nZXQobGUsei0xKSx4LnNldChsZSx6LTEsLWFlKnguZ2V0KGxlLEspK1kqeC5nZXQobGUsei0xKSkseC5zZXQobGUsSyxkZSl9YnJlYWt9Y2FzZSAyOntsZXQgcT1nW3ctMV07Z1t3LTFdPTA7Zm9yKGxldCBLPXc7Szx6O0srKyl7bGV0IGRlPU9wKGZbS10scSksWT1mW0tdL2RlLGFlPXEvZGU7aWYoZltLXT1kZSxxPS1hZSpnW0tdLGdbS109WSpnW0tdLGwpZm9yKGxldCBsZT0wO2xlPGk7bGUrKylkZT1ZKm0uZ2V0KGxlLEspK2FlKm0uZ2V0KGxlLHctMSksbS5zZXQobGUsdy0xLC1hZSptLmdldChsZSxLKStZKm0uZ2V0KGxlLHctMSkpLG0uc2V0KGxlLEssZGUpfWJyZWFrfWNhc2UgMzp7bGV0IHE9TWF0aC5tYXgoTWF0aC5hYnMoZlt6LTFdKSxNYXRoLmFicyhmW3otMl0pLE1hdGguYWJzKGdbei0yXSksTWF0aC5hYnMoZlt3XSksTWF0aC5hYnMoZ1t3XSkpLEs9Zlt6LTFdL3EsZGU9Zlt6LTJdL3EsWT1nW3otMl0vcSxhZT1mW3ddL3EsbGU9Z1t3XS9xLEllPSgoZGUrSykqKGRlLUspK1kqWSkvMix2ZT1LKlkqKEsqWSksRGU9MDsoMCE9PUllfHwwIT09dmUpJiYoRGU9SWU8MD8wLU1hdGguc3FydChJZSpJZSt2ZSk6TWF0aC5zcXJ0KEllKkllK3ZlKSxEZT12ZS8oSWUrRGUpKTtsZXQgbnQ9KGFlK0spKihhZS1LKStEZSxndD1hZSpsZTtmb3IobGV0IFVlPXc7VWU8ei0xO1VlKyspe2xldCBBZT1PcChudCxndCk7MD09PUFlJiYoQWU9TnVtYmVyLk1JTl9WQUxVRSk7bGV0IHRuPW50L0FlLHB0PWd0L0FlO2lmKFVlIT09dyYmKGdbVWUtMV09QWUpLG50PXRuKmZbVWVdK3B0KmdbVWVdLGdbVWVdPXRuKmdbVWVdLXB0KmZbVWVdLGd0PXB0KmZbVWUrMV0sZltVZSsxXT10bipmW1VlKzFdLGMpZm9yKGxldCB3dD0wO3d0PHI7d3QrKylBZT10bip4LmdldCh3dCxVZSkrcHQqeC5nZXQod3QsVWUrMSkseC5zZXQod3QsVWUrMSwtcHQqeC5nZXQod3QsVWUpK3RuKnguZ2V0KHd0LFVlKzEpKSx4LnNldCh3dCxVZSxBZSk7aWYoQWU9T3AobnQsZ3QpLDA9PT1BZSYmKEFlPU51bWJlci5NSU5fVkFMVUUpLHRuPW50L0FlLHB0PWd0L0FlLGZbVWVdPUFlLG50PXRuKmdbVWVdK3B0KmZbVWUrMV0sZltVZSsxXT0tcHQqZ1tVZV0rdG4qZltVZSsxXSxndD1wdCpnW1VlKzFdLGdbVWUrMV09dG4qZ1tVZSsxXSxsJiZVZTxpLTEpZm9yKGxldCB3dD0wO3d0PGk7d3QrKylBZT10biptLmdldCh3dCxVZSkrcHQqbS5nZXQod3QsVWUrMSksbS5zZXQod3QsVWUrMSwtcHQqbS5nZXQod3QsVWUpK3RuKm0uZ2V0KHd0LFVlKzEpKSxtLnNldCh3dCxVZSxBZSl9Z1t6LTJdPW50LHVlKz0xO2JyZWFrfWNhc2UgNDppZihmW3ddPD0wJiYoZlt3XT1mW3ddPDA/LWZbd106MCxjKSlmb3IobGV0IHE9MDtxPD1mZTtxKyspeC5zZXQocSx3LC14LmdldChxLHcpKTtmb3IoO3c8ZmUmJiEoZlt3XT49Zlt3KzFdKTspe2xldCBxPWZbd107aWYoZlt3XT1mW3crMV0sZlt3KzFdPXEsYyYmdzxyLTEpZm9yKGxldCBLPTA7SzxyO0srKylxPXguZ2V0KEssdysxKSx4LnNldChLLHcrMSx4LmdldChLLHcpKSx4LnNldChLLHcscSk7aWYobCYmdzxpLTEpZm9yKGxldCBLPTA7SzxpO0srKylxPW0uZ2V0KEssdysxKSxtLnNldChLLHcrMSxtLmdldChLLHcpKSxtLnNldChLLHcscSk7dysrfXVlPTAsei0tfX1pZih1KXtsZXQgdz14O3g9bSxtPXd9dGhpcy5tPWksdGhpcy5uPXIsdGhpcy5zPWYsdGhpcy5VPW0sdGhpcy5WPXh9c29sdmUodCl7bGV0IGU9dCxpPXRoaXMudGhyZXNob2xkLHI9dGhpcy5zLmxlbmd0aCxvPXJuLnplcm9zKHIscik7Zm9yKGxldCBwPTA7cDxyO3ArKylNYXRoLmFicyh0aGlzLnNbcF0pPD1pP28uc2V0KHAscCwwKTpvLnNldChwLHAsMS90aGlzLnNbcF0pO2xldCBzPXRoaXMuVSxhPXRoaXMucmlnaHRTaW5ndWxhclZlY3RvcnMsbD1hLm1tdWwobyksYz1hLnJvd3MsdT1zLnJvd3MsZD1ybi56ZXJvcyhjLHUpO2ZvcihsZXQgcD0wO3A8YztwKyspZm9yKGxldCBoPTA7aDx1O2grKyl7bGV0IGY9MDtmb3IobGV0IG09MDttPHI7bSsrKWYrPWwuZ2V0KHAsbSkqcy5nZXQoaCxtKTtkLnNldChwLGgsZil9cmV0dXJuIGQubW11bChlKX1zb2x2ZUZvckRpYWdvbmFsKHQpe3JldHVybiB0aGlzLnNvbHZlKHJuLmRpYWcodCkpfWludmVyc2UoKXtsZXQgdD10aGlzLlYsZT10aGlzLnRocmVzaG9sZCxpPXQucm93cyxyPXQuY29sdW1ucyxvPW5ldyBybihpLHRoaXMucy5sZW5ndGgpO2ZvcihsZXQgdT0wO3U8aTt1KyspZm9yKGxldCBkPTA7ZDxyO2QrKylNYXRoLmFicyh0aGlzLnNbZF0pPmUmJm8uc2V0KHUsZCx0LmdldCh1LGQpL3RoaXMuc1tkXSk7bGV0IHM9dGhpcy5VLGE9cy5yb3dzLGw9cy5jb2x1bW5zLGM9bmV3IHJuKGksYSk7Zm9yKGxldCB1PTA7dTxpO3UrKylmb3IobGV0IGQ9MDtkPGE7ZCsrKXtsZXQgcD0wO2ZvcihsZXQgaD0wO2g8bDtoKyspcCs9by5nZXQodSxoKSpzLmdldChkLGgpO2Muc2V0KHUsZCxwKX1yZXR1cm4gY31nZXQgY29uZGl0aW9uKCl7cmV0dXJuIHRoaXMuc1swXS90aGlzLnNbTWF0aC5taW4odGhpcy5tLHRoaXMubiktMV19Z2V0IG5vcm0yKCl7cmV0dXJuIHRoaXMuc1swXX1nZXQgcmFuaygpe2xldCB0PU1hdGgubWF4KHRoaXMubSx0aGlzLm4pKnRoaXMuc1swXSpOdW1iZXIuRVBTSUxPTixlPTAsaT10aGlzLnM7Zm9yKGxldCByPTAsbz1pLmxlbmd0aDtyPG87cisrKWlbcl0+dCYmZSsrO3JldHVybiBlfWdldCBkaWFnb25hbCgpe3JldHVybiBBcnJheS5mcm9tKHRoaXMucyl9Z2V0IHRocmVzaG9sZCgpe3JldHVybiBOdW1iZXIuRVBTSUxPTi8yKk1hdGgubWF4KHRoaXMubSx0aGlzLm4pKnRoaXMuc1swXX1nZXQgbGVmdFNpbmd1bGFyVmVjdG9ycygpe3JldHVybiB0aGlzLlV9Z2V0IHJpZ2h0U2luZ3VsYXJWZWN0b3JzKCl7cmV0dXJuIHRoaXMuVn1nZXQgZGlhZ29uYWxNYXRyaXgoKXtyZXR1cm4gcm4uZGlhZyh0aGlzLnMpfX19KTt2YXIga2ZlPWhvKCgpPT57SWZlKCksUGZlKCksUmZlKCksbzAoKSxERSgpfSksRmZlPWhvKCgpPT57bzAoKSxBZmUoKSxrZmUoKX0pO2Z1bmN0aW9uIHU2KG4sdCxlLGkscil7bGV0IHM9cm4uZXllKHQubGVuZ3RoLHQubGVuZ3RoLGUqaSppKSxhPXIodCksbD1uZXcgRmxvYXQ2NEFycmF5KG4ueC5sZW5ndGgpO2ZvcihsZXQgcD0wO3A8bi54Lmxlbmd0aDtwKyspbFtwXT1hKG4ueFtwXSk7bGV0IGM9ZnVuY3Rpb24obix0LGUsaSxyKXtsZXQgbz1lLmxlbmd0aCxzPW4ueC5sZW5ndGgsYT1uZXcgQXJyYXkobyk7Zm9yKGxldCBsPTA7bDxvO2wrKyl7YVtsXT1uZXcgQXJyYXkocyk7bGV0IGM9ZS5zbGljZSgpO2NbbF0rPWk7bGV0IHU9cihjKTtmb3IobGV0IGQ9MDtkPHM7ZCsrKWFbbF1bZF09dFtkXS11KG4ueFtkXSl9cmV0dXJuIG5ldyBybihhKX0obixsLHQsaSxyKSx1PWZ1bmN0aW9uKG4sdCl7bGV0IGU9bi54Lmxlbmd0aCxpPW5ldyBBcnJheShlKTtmb3IobGV0IHI9MDtyPGU7cisrKWlbcl09W24ueVtyXS10W3JdXTtyZXR1cm4gbmV3IHJuKGkpfShuLGwpLGQ9ZnVuY3Rpb24obix0PSExKXtyZXR1cm4gbj1BbC5jaGVja01hdHJpeChuKSx0P25ldyB6YihuKS5pbnZlcnNlKCk6ZnVuY3Rpb24obix0LGU9ITEpe3JldHVybiBuPUFsLmNoZWNrTWF0cml4KG4pLHQ9QWwuY2hlY2tNYXRyaXgodCksZT9uZXcgemIobikuc29sdmUodCk6bi5pc1NxdWFyZSgpP25ldyBBRShuKS5zb2x2ZSh0KTpuZXcgSUUobikuc29sdmUodCl9KG4scm4uZXllKG4ucm93cykpfShzLmFkZChjLm1tdWwoYy50cmFuc3Bvc2UoKSkpKTtyZXR1cm4odD0odD1uZXcgcm4oW3RdKSkuc3ViKGQubW11bChjKS5tbXVsKHUpLm11bChpKS50cmFuc3Bvc2UoKSkpLnRvMURBcnJheSgpfXZhciBOZmU9aG8oKCk9PntGZmUoKX0pLEJmZT17fTtmdW5jdGlvbiBMZmUobix0LGU9e30pe2xldHttYXhJdGVyYXRpb25zOmk9MTAwLGdyYWRpZW50RGlmZmVyZW5jZTpyPS4xLGRhbXBpbmc6bz0wLGVycm9yVG9sZXJhbmNlOnM9LjAxLG1pblZhbHVlczphLG1heFZhbHVlczpsLGluaXRpYWxWYWx1ZXM6Y309ZTtpZihvPD0wKXRocm93IG5ldyBFcnJvcigiVGhlIGRhbXBpbmcgb3B0aW9uIG11c3QgYmUgYSBwb3NpdGl2ZSBudW1iZXIiKTtpZighbi54fHwhbi55KXRocm93IG5ldyBFcnJvcigiVGhlIGRhdGEgcGFyYW1ldGVyIG11c3QgaGF2ZSB4IGFuZCB5IGVsZW1lbnRzIik7aWYoIUVFKG4ueCl8fG4ueC5sZW5ndGg8Mnx8IUVFKG4ueSl8fG4ueS5sZW5ndGg8Mil0aHJvdyBuZXcgRXJyb3IoIlRoZSBkYXRhIHBhcmFtZXRlciBlbGVtZW50cyBtdXN0IGJlIGFuIGFycmF5IHdpdGggbW9yZSB0aGFuIDIgcG9pbnRzIik7aWYobi54Lmxlbmd0aCE9PW4ueS5sZW5ndGgpdGhyb3cgbmV3IEVycm9yKCJUaGUgZGF0YSBwYXJhbWV0ZXIgZWxlbWVudHMgbXVzdCBoYXZlIHRoZSBzYW1lIHNpemUiKTtsZXQgdT1jfHxuZXcgQXJyYXkodC5sZW5ndGgpLmZpbGwoMSksZD11Lmxlbmd0aDtpZihsPWx8fG5ldyBBcnJheShkKS5maWxsKE51bWJlci5NQVhfU0FGRV9JTlRFR0VSKSxhPWF8fG5ldyBBcnJheShkKS5maWxsKE51bWJlci5NSU5fU0FGRV9JTlRFR0VSKSxsLmxlbmd0aCE9PWEubGVuZ3RoKXRocm93IG5ldyBFcnJvcigibWluVmFsdWVzIGFuZCBtYXhWYWx1ZXMgbXVzdCBiZSB0aGUgc2FtZSBzaXplIik7aWYoIUVFKHUpKXRocm93IG5ldyBFcnJvcigiaW5pdGlhbFZhbHVlcyBtdXN0IGJlIGFuIGFycmF5Iik7bGV0IGYscD1wRihuLHUsdCksaD1wPD1zO2ZvcihmPTA7ZjxpJiYhaDtmKyspe3U9dTYobix1LG8scix0KTtmb3IobGV0IG09MDttPGQ7bSsrKXVbbV09TWF0aC5taW4oTWF0aC5tYXgoYVttXSx1W21dKSxsW21dKTtpZihwPXBGKG4sdSx0KSxpc05hTihwKSlicmVhaztoPXA8PXN9cmV0dXJue3BhcmFtZXRlclZhbHVlczp1LHBhcmFtZXRlckVycm9yOnAsaXRlcmF0aW9uczpmfX1CRShCZmUse2RlZmF1bHQ6KCk9PkxmZX0pO3ZhciBWZmU9aG8oKCk9PntLaGUoKSxaaGUoKSxOZmUoKX0pLEpmZT1SZCh3bz0+eyJ1c2Ugc3RyaWN0Ijt2YXIgSGZlPXdvJiZ3by5fX2F3YWl0ZXJ8fGZ1bmN0aW9uKG4sdCxlLGkpe3JldHVybiBuZXcoZXx8KGU9UHJvbWlzZSkpKGZ1bmN0aW9uKHIsbyl7ZnVuY3Rpb24gcyhjKXt0cnl7bChpLm5leHQoYykpfWNhdGNoKHUpe28odSl9fWZ1bmN0aW9uIGEoYyl7dHJ5e2woaS50aHJvdyhjKSl9Y2F0Y2godSl7byh1KX19ZnVuY3Rpb24gbChjKXtjLmRvbmU/cihjLnZhbHVlKTpuZXcgZShmdW5jdGlvbih1KXt1KGMudmFsdWUpfSkudGhlbihzLGEpfWwoKGk9aS5hcHBseShuLHR8fFtdKSkubmV4dCgpKX0pfSxVZmU9d28mJndvLl9fZ2VuZXJhdG9yfHxmdW5jdGlvbihuLHQpe3ZhciBpLHIsbyxzLGU9e2xhYmVsOjAsc2VudDpmdW5jdGlvbigpe2lmKDEmb1swXSl0aHJvdyBvWzFdO3JldHVybiBvWzFdfSx0cnlzOltdLG9wczpbXX07cmV0dXJuIHM9e25leHQ6YSgwKSx0aHJvdzphKDEpLHJldHVybjphKDIpfSwiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYoc1tTeW1ib2wuaXRlcmF0b3JdPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXN9KSxzO2Z1bmN0aW9uIGEoYyl7cmV0dXJuIGZ1bmN0aW9uKHUpe3JldHVybiBmdW5jdGlvbihjKXtpZihpKXRocm93IG5ldyBUeXBlRXJyb3IoIkdlbmVyYXRvciBpcyBhbHJlYWR5IGV4ZWN1dGluZy4iKTtmb3IoO2U7KXRyeXtpZihpPTEsciYmKG89MiZjWzBdP3IucmV0dXJuOmNbMF0/ci50aHJvd3x8KChvPXIucmV0dXJuKSYmby5jYWxsKHIpLDApOnIubmV4dCkmJiEobz1vLmNhbGwocixjWzFdKSkuZG9uZSlyZXR1cm4gbztzd2l0Y2gocj0wLG8mJihjPVsyJmNbMF0sby52YWx1ZV0pLGNbMF0pe2Nhc2UgMDpjYXNlIDE6bz1jO2JyZWFrO2Nhc2UgNDpyZXR1cm4gZS5sYWJlbCsrLHt2YWx1ZTpjWzFdLGRvbmU6ITF9O2Nhc2UgNTplLmxhYmVsKysscj1jWzFdLGM9WzBdO2NvbnRpbnVlO2Nhc2UgNzpjPWUub3BzLnBvcCgpLGUudHJ5cy5wb3AoKTtjb250aW51ZTtkZWZhdWx0OmlmKCEobz0obz1lLnRyeXMpLmxlbmd0aD4wJiZvW28ubGVuZ3RoLTFdKSYmKDY9PT1jWzBdfHwyPT09Y1swXSkpe2U9MDtjb250aW51ZX1pZigzPT09Y1swXSYmKCFvfHxjWzFdPm9bMF0mJmNbMV08b1szXSkpe2UubGFiZWw9Y1sxXTticmVha31pZig2PT09Y1swXSYmZS5sYWJlbDxvWzFdKXtlLmxhYmVsPW9bMV0sbz1jO2JyZWFrfWlmKG8mJmUubGFiZWw8b1syXSl7ZS5sYWJlbD1vWzJdLGUub3BzLnB1c2goYyk7YnJlYWt9b1syXSYmZS5vcHMucG9wKCksZS50cnlzLnBvcCgpO2NvbnRpbnVlfWM9dC5jYWxsKG4sZSl9Y2F0Y2godSl7Yz1bNix1XSxyPTB9ZmluYWxseXtpPW89MH1pZig1JmNbMF0pdGhyb3cgY1sxXTtyZXR1cm57dmFsdWU6Y1swXT9jWzFdOnZvaWQgMCxkb25lOiEwfX0oW2MsdV0pfX19LGQ2PXdvJiZ3by5fX3JlYWR8fGZ1bmN0aW9uKG4sdCl7dmFyIGU9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmbltTeW1ib2wuaXRlcmF0b3JdO2lmKCFlKXJldHVybiBuO3ZhciByLHMsaT1lLmNhbGwobiksbz1bXTt0cnl7Zm9yKDsodm9pZCAwPT09dHx8dC0tID4wKSYmIShyPWkubmV4dCgpKS5kb25lOylvLnB1c2goci52YWx1ZSl9Y2F0Y2goYSl7cz17ZXJyb3I6YX19ZmluYWxseXt0cnl7ciYmIXIuZG9uZSYmKGU9aS5yZXR1cm4pJiZlLmNhbGwoaSl9ZmluYWxseXtpZihzKXRocm93IHMuZXJyb3J9fXJldHVybiBvfSx6ZmU9d28mJndvLl9fc3ByZWFkfHxmdW5jdGlvbigpe2Zvcih2YXIgbj1bXSx0PTA7dDxhcmd1bWVudHMubGVuZ3RoO3QrKyluPW4uY29uY2F0KGQ2KGFyZ3VtZW50c1t0XSkpO3JldHVybiBufSxQRT13byYmd28uX19pbXBvcnRTdGFyfHxmdW5jdGlvbihuKXtpZihuJiZuLl9fZXNNb2R1bGUpcmV0dXJuIG47dmFyIHQ9e307aWYobnVsbCE9bilmb3IodmFyIGUgaW4gbilPYmplY3QuaGFzT3duUHJvcGVydHkuY2FsbChuLGUpJiYodFtlXT1uW2VdKTtyZXR1cm4gdC5kZWZhdWx0PW4sdH0sQiRlPXdvJiZ3by5fX2ltcG9ydERlZmF1bHR8fGZ1bmN0aW9uKG4pe3JldHVybiBuJiZuLl9fZXNNb2R1bGU/bjp7ZGVmYXVsdDpufX07T2JqZWN0LmRlZmluZVByb3BlcnR5KHdvLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgbixWJGU9UEUoWkcoKSksY289UEUoSkcoKSksZ0Y9UEUoUWhlKCkpLGpmZT1QRShpNigpKSxNbz1QRShIYigpKSxIJGU9QiRlKChWZmUoKSxuPUJmZSxJNihMRSh7fSwiX19lc01vZHVsZSIse3ZhbHVlOiEwfSksbikpKSxfRj0uMDAxLFUkZT1mdW5jdGlvbigpe2Z1bmN0aW9uIG4odCl7dm9pZCAwPT09dCYmKHQ9e30pO3ZhciBlPXRoaXM7dGhpcy5sZWFybmluZ1JhdGU9MSx0aGlzLmxvY2FsQ29ubmVjdGl2aXR5PTEsdGhpcy5taW5EaXN0PS4xLHRoaXMubkNvbXBvbmVudHM9Mix0aGlzLm5FcG9jaHM9MCx0aGlzLm5OZWlnaGJvcnM9MTUsdGhpcy5uZWdhdGl2ZVNhbXBsZVJhdGU9NSx0aGlzLnJhbmRvbT1NYXRoLnJhbmRvbSx0aGlzLnJlcHVsc2lvblN0cmVuZ3RoPTEsdGhpcy5zZXRPcE1peFJhdGlvPTEsdGhpcy5zcHJlYWQ9MSx0aGlzLnRyYW5zZm9ybVF1ZXVlU2l6ZT00LHRoaXMudGFyZ2V0TWV0cmljPSJjYXRlZ29yaWNhbCIsdGhpcy50YXJnZXRXZWlnaHQ9LjUsdGhpcy50YXJnZXROTmVpZ2hib3JzPXRoaXMubk5laWdoYm9ycyx0aGlzLmRpc3RhbmNlRm49WWZlLHRoaXMuaXNJbml0aWFsaXplZD0hMSx0aGlzLnJwRm9yZXN0PVtdLHRoaXMuZW1iZWRkaW5nPVtdLHRoaXMub3B0aW1pemF0aW9uU3RhdGU9bmV3IGokZTt2YXIgaT1mdW5jdGlvbihyKXt2b2lkIDAhPT10W3JdJiYoZVtyXT10W3JdKX07aSgiZGlzdGFuY2VGbiIpLGkoImxlYXJuaW5nUmF0ZSIpLGkoImxvY2FsQ29ubmVjdGl2aXR5IiksaSgibWluRGlzdCIpLGkoIm5Db21wb25lbnRzIiksaSgibkVwb2NocyIpLGkoIm5OZWlnaGJvcnMiKSxpKCJuZWdhdGl2ZVNhbXBsZVJhdGUiKSxpKCJyYW5kb20iKSxpKCJyZXB1bHNpb25TdHJlbmd0aCIpLGkoInNldE9wTWl4UmF0aW8iKSxpKCJzcHJlYWQiKSxpKCJ0cmFuc2Zvcm1RdWV1ZVNpemUiKX1yZXR1cm4gbi5wcm90b3R5cGUuZml0PWZ1bmN0aW9uKHQpe3JldHVybiB0aGlzLmluaXRpYWxpemVGaXQodCksdGhpcy5vcHRpbWl6ZUxheW91dCgpLHRoaXMuZW1iZWRkaW5nfSxuLnByb3RvdHlwZS5maXRBc3luYz1mdW5jdGlvbih0LGUpe3JldHVybiB2b2lkIDA9PT1lJiYoZT1mdW5jdGlvbigpe3JldHVybiEwfSksSGZlKHRoaXMsdm9pZCAwLHZvaWQgMCxmdW5jdGlvbigpe3JldHVybiBVZmUodGhpcyxmdW5jdGlvbihpKXtzd2l0Y2goaS5sYWJlbCl7Y2FzZSAwOnJldHVybiB0aGlzLmluaXRpYWxpemVGaXQodCksWzQsdGhpcy5vcHRpbWl6ZUxheW91dEFzeW5jKGUpXTtjYXNlIDE6cmV0dXJuIGkuc2VudCgpLFsyLHRoaXMuZW1iZWRkaW5nXX19KX0pfSxuLnByb3RvdHlwZS5zZXRTdXBlcnZpc2VkUHJvamVjdGlvbj1mdW5jdGlvbih0LGUpe3ZvaWQgMD09PWUmJihlPXt9KSx0aGlzLlk9dCx0aGlzLnRhcmdldE1ldHJpYz1lLnRhcmdldE1ldHJpY3x8dGhpcy50YXJnZXRNZXRyaWMsdGhpcy50YXJnZXRXZWlnaHQ9ZS50YXJnZXRXZWlnaHR8fHRoaXMudGFyZ2V0V2VpZ2h0LHRoaXMudGFyZ2V0Tk5laWdoYm9ycz1lLnRhcmdldE5OZWlnaGJvcnN8fHRoaXMudGFyZ2V0Tk5laWdoYm9yc30sbi5wcm90b3R5cGUuc2V0UHJlY29tcHV0ZWRLTk49ZnVuY3Rpb24odCxlKXt0aGlzLmtubkluZGljZXM9dCx0aGlzLmtubkRpc3RhbmNlcz1lfSxuLnByb3RvdHlwZS5pbml0aWFsaXplRml0PWZ1bmN0aW9uKHQpe2lmKHQubGVuZ3RoPD10aGlzLm5OZWlnaGJvcnMpdGhyb3cgbmV3IEVycm9yKCJOb3QgZW5vdWdoIGRhdGEgcG9pbnRzICgiK3QubGVuZ3RoKyIpIHRvIGNyZWF0ZSBuTmVpZ2hib3JzOiAiK3RoaXMubk5laWdoYm9ycysiLiAgQWRkIG1vcmUgZGF0YSBwb2ludHMgb3IgYWRqdXN0IHRoZSBjb25maWd1cmF0aW9uLiIpO2lmKHRoaXMuWD09PXQmJnRoaXMuaXNJbml0aWFsaXplZClyZXR1cm4gdGhpcy5nZXRORXBvY2hzKCk7aWYodGhpcy5YPXQsIXRoaXMua25uSW5kaWNlcyYmIXRoaXMua25uRGlzdGFuY2VzKXt2YXIgZT10aGlzLm5lYXJlc3ROZWlnaGJvcnModCk7dGhpcy5rbm5JbmRpY2VzPWUua25uSW5kaWNlcyx0aGlzLmtubkRpc3RhbmNlcz1lLmtubkRpc3RhbmNlc310aGlzLmdyYXBoPXRoaXMuZnV6enlTaW1wbGljaWFsU2V0KHQsdGhpcy5uTmVpZ2hib3JzLHRoaXMuc2V0T3BNaXhSYXRpbyksdGhpcy5tYWtlU2VhcmNoRm5zKCksdGhpcy5zZWFyY2hHcmFwaD10aGlzLm1ha2VTZWFyY2hHcmFwaCh0KSx0aGlzLnByb2Nlc3NHcmFwaEZvclN1cGVydmlzZWRQcm9qZWN0aW9uKCk7dmFyIGk9dGhpcy5pbml0aWFsaXplU2ltcGxpY2lhbFNldEVtYmVkZGluZygpLG89aS50YWlsLHM9aS5lcG9jaHNQZXJTYW1wbGU7cmV0dXJuIHRoaXMub3B0aW1pemF0aW9uU3RhdGUuaGVhZD1pLmhlYWQsdGhpcy5vcHRpbWl6YXRpb25TdGF0ZS50YWlsPW8sdGhpcy5vcHRpbWl6YXRpb25TdGF0ZS5lcG9jaHNQZXJTYW1wbGU9cyx0aGlzLmluaXRpYWxpemVPcHRpbWl6YXRpb24oKSx0aGlzLnByZXBhcmVGb3JPcHRpbWl6YXRpb25Mb29wKCksdGhpcy5pc0luaXRpYWxpemVkPSEwLHRoaXMuZ2V0TkVwb2NocygpfSxuLnByb3RvdHlwZS5tYWtlU2VhcmNoRm5zPWZ1bmN0aW9uKCl7dmFyIHQ9Z0YubWFrZUluaXRpYWxpemF0aW9ucyh0aGlzLmRpc3RhbmNlRm4pLGk9dC5pbml0RnJvbVJhbmRvbTt0aGlzLmluaXRGcm9tVHJlZT10LmluaXRGcm9tVHJlZSx0aGlzLmluaXRGcm9tUmFuZG9tPWksdGhpcy5zZWFyY2g9Z0YubWFrZUluaXRpYWxpemVkTk5TZWFyY2godGhpcy5kaXN0YW5jZUZuKX0sbi5wcm90b3R5cGUubWFrZVNlYXJjaEdyYXBoPWZ1bmN0aW9uKHQpe2Zvcih2YXIgZT10aGlzLmtubkluZGljZXMsaT10aGlzLmtubkRpc3RhbmNlcyxvPW5ldyBjby5TcGFyc2VNYXRyaXgoW10sW10sW10sW3QubGVuZ3RoLHQubGVuZ3RoXSkscz0wO3M8ZS5sZW5ndGg7cysrKWZvcih2YXIgYT1lW3NdLGw9aVtzXSxjPTA7YzxhLmxlbmd0aDtjKyspe3ZhciBkPWxbY107ZD4wJiZvLnNldChzLGFbY10sZCl9dmFyIHA9Y28udHJhbnNwb3NlKG8pO3JldHVybiBjby5tYXhpbXVtKG8scCl9LG4ucHJvdG90eXBlLnRyYW5zZm9ybT1mdW5jdGlvbih0KXt2YXIgZT10aGlzLGk9dGhpcy5YO2lmKHZvaWQgMD09PWl8fDA9PT1pLmxlbmd0aCl0aHJvdyBuZXcgRXJyb3IoIk5vIGRhdGEgaGFzIGJlZW4gZml0LiIpO3ZhciByPU1hdGguZmxvb3IodGhpcy5uTmVpZ2hib3JzKnRoaXMudHJhbnNmb3JtUXVldWVTaXplKTtyPU1hdGgubWluKGkubGVuZ3RoLHIpO3ZhciBvPWdGLmluaXRpYWxpemVTZWFyY2godGhpcy5ycEZvcmVzdCxpLHQscix0aGlzLmluaXRGcm9tUmFuZG9tLHRoaXMuaW5pdEZyb21UcmVlLHRoaXMucmFuZG9tKSxzPXRoaXMuc2VhcmNoKGksdGhpcy5zZWFyY2hHcmFwaCxvLHQpLGE9ViRlLmRlaGVhcFNvcnQocyksbD1hLmluZGljZXMsYz1hLndlaWdodHM7bD1sLm1hcChmdW5jdGlvbihkZSl7cmV0dXJuIGRlLnNsaWNlKDAsZS5uTmVpZ2hib3JzKX0pLGM9Yy5tYXAoZnVuY3Rpb24oZGUpe3JldHVybiBkZS5zbGljZSgwLGUubk5laWdoYm9ycyl9KTt2YXIgdT1NYXRoLm1heCgwLHRoaXMubG9jYWxDb25uZWN0aXZpdHktMSksZD10aGlzLnNtb290aEtOTkRpc3RhbmNlKGMsdGhpcy5uTmVpZ2hib3JzLHUpLGY9dGhpcy5jb21wdXRlTWVtYmVyc2hpcFN0cmVuZ3RocyhsLGMsZC5zaWdtYXMsZC5yaG9zKSxEPW5ldyBjby5TcGFyc2VNYXRyaXgoZi5yb3dzLGYuY29scyxmLnZhbHMsW3QubGVuZ3RoLGkubGVuZ3RoXSksVD1jby5ub3JtYWxpemUoRCwibDEiKSxrPWNvLmdldENTUihUKSxaPXQubGVuZ3RoLHVlPVpmZShNby5yZXNoYXBlMmQoay5pbmRpY2VzLFosdGhpcy5uTmVpZ2hib3JzKSxNby5yZXNoYXBlMmQoay52YWx1ZXMsWix0aGlzLm5OZWlnaGJvcnMpLHRoaXMuZW1iZWRkaW5nKSxoZT10aGlzLm5FcG9jaHM/dGhpcy5uRXBvY2hzLzM6RC5uUm93czw9MWU0PzEwMDozMCx3PUQuZ2V0VmFsdWVzKCkucmVkdWNlKGZ1bmN0aW9uKGRlLFkpe3JldHVybiBZPmRlP1k6ZGV9LDApO0Q9RC5tYXAoZnVuY3Rpb24oZGUpe3JldHVybiBkZTx3L2hlPzA6ZGV9KSxEPWNvLmVsaW1pbmF0ZVplcm9zKEQpO3ZhciBGPXRoaXMubWFrZUVwb2Noc1BlclNhbXBsZShELmdldFZhbHVlcygpLGhlKSxxPUQuZ2V0Um93cygpLEs9RC5nZXRDb2xzKCk7cmV0dXJuIHRoaXMuYXNzaWduT3B0aW1pemF0aW9uU3RhdGVQYXJhbWV0ZXJzKHtoZWFkRW1iZWRkaW5nOnVlLHRhaWxFbWJlZGRpbmc6dGhpcy5lbWJlZGRpbmcsaGVhZDpxLHRhaWw6SyxjdXJyZW50RXBvY2g6MCxuRXBvY2hzOmhlLG5WZXJ0aWNlczpELmdldERpbXMoKVsxXSxlcG9jaHNQZXJTYW1wbGU6Rn0pLHRoaXMucHJlcGFyZUZvck9wdGltaXphdGlvbkxvb3AoKSx0aGlzLm9wdGltaXplTGF5b3V0KCl9LG4ucHJvdG90eXBlLnByb2Nlc3NHcmFwaEZvclN1cGVydmlzZWRQcm9qZWN0aW9uPWZ1bmN0aW9uKCl7dmFyIGU9dGhpcy5ZO2lmKGUpe2lmKGUubGVuZ3RoIT09dGhpcy5YLmxlbmd0aCl0aHJvdyBuZXcgRXJyb3IoIkxlbmd0aCBvZiBYIGFuZCB5IG11c3QgYmUgZXF1YWwiKTsiY2F0ZWdvcmljYWwiPT09dGhpcy50YXJnZXRNZXRyaWMmJih0aGlzLmdyYXBoPXRoaXMuY2F0ZWdvcmljYWxTaW1wbGljaWFsU2V0SW50ZXJzZWN0aW9uKHRoaXMuZ3JhcGgsZSx0aGlzLnRhcmdldFdlaWdodDwxPzEvKDEtdGhpcy50YXJnZXRXZWlnaHQpKjIuNToxZTEyKSl9fSxuLnByb3RvdHlwZS5zdGVwPWZ1bmN0aW9uKCl7dmFyIHQ9dGhpcy5vcHRpbWl6YXRpb25TdGF0ZS5jdXJyZW50RXBvY2g7cmV0dXJuIHQ8dGhpcy5nZXRORXBvY2hzKCkmJnRoaXMub3B0aW1pemVMYXlvdXRTdGVwKHQpLHRoaXMub3B0aW1pemF0aW9uU3RhdGUuY3VycmVudEVwb2NofSxuLnByb3RvdHlwZS5nZXRFbWJlZGRpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5lbWJlZGRpbmd9LG4ucHJvdG90eXBlLm5lYXJlc3ROZWlnaGJvcnM9ZnVuY3Rpb24odCl7dmFyIGYscj10aGlzLm5OZWlnaGJvcnMscz1nRi5tYWtlTk5EZXNjZW50KHRoaXMuZGlzdGFuY2VGbix0aGlzLnJhbmRvbSksbD01K01hdGguZmxvb3IoLjU9PShmPU1hdGgucG93KHQubGVuZ3RoLC41KS8yMCk/MDpNYXRoLnJvdW5kKGYpKSxjPU1hdGgubWF4KDUsTWF0aC5mbG9vcihNYXRoLnJvdW5kKGZ1bmN0aW9uKGYpe3JldHVybiBNYXRoLmxvZyhmKS9NYXRoLmxvZygyKX0odC5sZW5ndGgpKSkpO3RoaXMucnBGb3Jlc3Q9amZlLm1ha2VGb3Jlc3QodCxyLGwsdGhpcy5yYW5kb20pO3ZhciBkPXModCxqZmUubWFrZUxlYWZBcnJheSh0aGlzLnJwRm9yZXN0KSxyLGMpO3JldHVybntrbm5JbmRpY2VzOmQuaW5kaWNlcyxrbm5EaXN0YW5jZXM6ZC53ZWlnaHRzfX0sbi5wcm90b3R5cGUuZnV6enlTaW1wbGljaWFsU2V0PWZ1bmN0aW9uKHQsZSxpKXt2b2lkIDA9PT1pJiYoaT0xKTt2YXIgcj10aGlzLG89ci5rbm5JbmRpY2VzLHM9dm9pZCAwPT09bz9bXTpvLGE9ci5rbm5EaXN0YW5jZXMsbD12b2lkIDA9PT1hP1tdOmEsdT10aGlzLnNtb290aEtOTkRpc3RhbmNlKGwsZSxyLmxvY2FsQ29ubmVjdGl2aXR5KSxoPXRoaXMuY29tcHV0ZU1lbWJlcnNoaXBTdHJlbmd0aHMocyxsLHUuc2lnbWFzLHUucmhvcyksYj1uZXcgY28uU3BhcnNlTWF0cml4KGgucm93cyxoLmNvbHMsaC52YWxzLFt0Lmxlbmd0aCx0Lmxlbmd0aF0pLEQ9Y28udHJhbnNwb3NlKGIpLFQ9Y28ucGFpcndpc2VNdWx0aXBseShiLEQpLGs9Y28uc3VidHJhY3QoY28uYWRkKGIsRCksVCksWj1jby5tdWx0aXBseVNjYWxhcihrLGkpLHo9Y28ubXVsdGlwbHlTY2FsYXIoVCwxLWkpO3JldHVybiBjby5hZGQoWix6KX0sbi5wcm90b3R5cGUuY2F0ZWdvcmljYWxTaW1wbGljaWFsU2V0SW50ZXJzZWN0aW9uPWZ1bmN0aW9uKHQsZSxpLHIpe3ZvaWQgMD09PXImJihyPTEpO3ZhciBvPVFmZSh0LGUscixpKTtyZXR1cm4gS2ZlKG89Y28uZWxpbWluYXRlWmVyb3MobykpfSxuLnByb3RvdHlwZS5zbW9vdGhLTk5EaXN0YW5jZT1mdW5jdGlvbih0LGUsaSxyLG8pe3ZvaWQgMD09PWkmJihpPTEpLHZvaWQgMD09PXImJihyPTY0KSx2b2lkIDA9PT1vJiYobz0xKTtmb3IodmFyIHM9TWF0aC5sb2coZSkvTWF0aC5sb2coMikqbyxhPU1vLnplcm9zKHQubGVuZ3RoKSxsPU1vLnplcm9zKHQubGVuZ3RoKSxjPTA7Yzx0Lmxlbmd0aDtjKyspe3ZhciB1PTAsZD0xLzAscD0xLGg9dFtjXSxmPWguZmlsdGVyKGZ1bmN0aW9uKHope3JldHVybiB6PjB9KTtpZihmLmxlbmd0aD49aSl7dmFyIG09TWF0aC5mbG9vcihpKSx4PWktbTttPjA/KGFbY109ZlttLTFdLHg+MWUtNSYmKGFbY10rPXgqKGZbbV0tZlttLTFdKSkpOmFbY109eCpmWzBdfWVsc2UgZi5sZW5ndGg+MCYmKGFbY109TW8ubWF4KGYpKTtmb3IodmFyIGc9MDtnPHI7ZysrKXtmb3IodmFyIGI9MCxEPTE7RDx0W2NdLmxlbmd0aDtEKyspe3ZhciBUPXRbY11bRF0tYVtjXTtiKz1UPjA/TWF0aC5leHAoLVQvcCk6MX1pZihNYXRoLmFicyhiLXMpPDFlLTUpYnJlYWs7Yj5zP3A9KHUrKGQ9cCkpLzI6KHU9cCxkPT09MS8wP3AqPTI6cD0odStkKS8yKX1pZihsW2NdPXAsYVtjXT4wKXt2YXIgaz1Nby5tZWFuKGgpO2xbY108X0YqayYmKGxbY109X0Yqayl9ZWxzZXt2YXIgWj1Nby5tZWFuKHQubWFwKE1vLm1lYW4pKTtsW2NdPF9GKlomJihsW2NdPV9GKlopfX1yZXR1cm57c2lnbWFzOmwscmhvczphfX0sbi5wcm90b3R5cGUuY29tcHV0ZU1lbWJlcnNoaXBTdHJlbmd0aHM9ZnVuY3Rpb24odCxlLGkscil7Zm9yKHZhciBvPXQubGVuZ3RoLHM9dFswXS5sZW5ndGgsYT1Nby56ZXJvcyhvKnMpLGw9TW8uemVyb3MobypzKSxjPU1vLnplcm9zKG8qcyksdT0wO3U8bzt1KyspZm9yKHZhciBkPTA7ZDxzO2QrKyl7dmFyIHA9MDstMSE9PXRbdV1bZF0mJihwPXRbdV1bZF09PT11PzA6ZVt1XVtkXS1yW3VdPD0wPzE6TWF0aC5leHAoLShlW3VdW2RdLXJbdV0pL2lbdV0pLGFbdSpzK2RdPXUsbFt1KnMrZF09dFt1XVtkXSxjW3UqcytkXT1wKX1yZXR1cm57cm93czphLGNvbHM6bCx2YWxzOmN9fSxuLnByb3RvdHlwZS5pbml0aWFsaXplU2ltcGxpY2lhbFNldEVtYmVkZGluZz1mdW5jdGlvbigpe2Zvcih2YXIgdD10aGlzLGU9dGhpcy5nZXRORXBvY2hzKCksaT10aGlzLm5Db21wb25lbnRzLHI9dGhpcy5ncmFwaC5nZXRWYWx1ZXMoKSxvPTAscz0wO3M8ci5sZW5ndGg7cysrKW88cltzXSYmKG89cltzXSk7dmFyIGw9dGhpcy5ncmFwaC5tYXAoZnVuY3Rpb24obSl7cmV0dXJuIG08by9lPzA6bX0pO3RoaXMuZW1iZWRkaW5nPU1vLnplcm9zKGwublJvd3MpLm1hcChmdW5jdGlvbigpe3JldHVybiBNby56ZXJvcyhpKS5tYXAoZnVuY3Rpb24oKXtyZXR1cm4gMjAqTW8udGF1UmFuZCh0LnJhbmRvbSktMTB9KX0pO3ZhciBjPVtdLHU9W10sZD1bXSxwPWwuZ2V0QWxsKCk7Zm9yKHM9MDtzPHAubGVuZ3RoO3MrKyl7dmFyIGg9cFtzXTtoLnZhbHVlJiYoYy5wdXNoKGgudmFsdWUpLGQucHVzaChoLnJvdyksdS5wdXNoKGguY29sKSl9cmV0dXJue2hlYWQ6dSx0YWlsOmQsZXBvY2hzUGVyU2FtcGxlOnRoaXMubWFrZUVwb2Noc1BlclNhbXBsZShjLGUpfX0sbi5wcm90b3R5cGUubWFrZUVwb2Noc1BlclNhbXBsZT1mdW5jdGlvbih0LGUpe3ZhciBpPU1vLmZpbGxlZCh0Lmxlbmd0aCwtMSkscj1Nby5tYXgodCksbz10Lm1hcChmdW5jdGlvbihzKXtyZXR1cm4gcy9yKmV9KTtyZXR1cm4gby5mb3JFYWNoKGZ1bmN0aW9uKHMsYSl7cz4wJiYoaVthXT1lL29bYV0pfSksaX0sbi5wcm90b3R5cGUuYXNzaWduT3B0aW1pemF0aW9uU3RhdGVQYXJhbWV0ZXJzPWZ1bmN0aW9uKHQpe09iamVjdC5hc3NpZ24odGhpcy5vcHRpbWl6YXRpb25TdGF0ZSx0KX0sbi5wcm90b3R5cGUucHJlcGFyZUZvck9wdGltaXphdGlvbkxvb3A9ZnVuY3Rpb24oKXt2YXIgdD10aGlzLGU9dC5yZXB1bHNpb25TdHJlbmd0aCxpPXQubGVhcm5pbmdSYXRlLHI9dC5uZWdhdGl2ZVNhbXBsZVJhdGUsbz10aGlzLm9wdGltaXphdGlvblN0YXRlLHM9by5lcG9jaHNQZXJTYW1wbGUsYT1vLmhlYWRFbWJlZGRpbmcsYz1hWzBdLmxlbmd0aCx1PWEubGVuZ3RoPT09by50YWlsRW1iZWRkaW5nLmxlbmd0aCxkPXMubWFwKGZ1bmN0aW9uKGYpe3JldHVybiBmL3J9KSxwPXpmZShkKSxoPXpmZShzKTt0aGlzLmFzc2lnbk9wdGltaXphdGlvblN0YXRlUGFyYW1ldGVycyh7ZXBvY2hPZk5leHRTYW1wbGU6aCxlcG9jaE9mTmV4dE5lZ2F0aXZlU2FtcGxlOnAsZXBvY2hzUGVyTmVnYXRpdmVTYW1wbGU6ZCxtb3ZlT3RoZXI6dSxpbml0aWFsQWxwaGE6aSxhbHBoYTppLGdhbW1hOmUsZGltOmN9KX0sbi5wcm90b3R5cGUuaW5pdGlhbGl6ZU9wdGltaXphdGlvbj1mdW5jdGlvbigpe3ZhciB0PXRoaXMuZW1iZWRkaW5nLGU9dGhpcy5lbWJlZGRpbmcsaT10aGlzLm9wdGltaXphdGlvblN0YXRlLHI9aS5oZWFkLG89aS50YWlsLHM9aS5lcG9jaHNQZXJTYW1wbGUsYT10aGlzLmdldE5FcG9jaHMoKSxsPXRoaXMuZ3JhcGgubkNvbHMsYz1YZmUodGhpcy5zcHJlYWQsdGhpcy5taW5EaXN0KTt0aGlzLmFzc2lnbk9wdGltaXphdGlvblN0YXRlUGFyYW1ldGVycyh7aGVhZEVtYmVkZGluZzp0LHRhaWxFbWJlZGRpbmc6ZSxoZWFkOnIsdGFpbDpvLGVwb2Noc1BlclNhbXBsZTpzLGE6Yy5hLGI6Yy5iLG5FcG9jaHM6YSxuVmVydGljZXM6bH0pfSxuLnByb3RvdHlwZS5vcHRpbWl6ZUxheW91dFN0ZXA9ZnVuY3Rpb24odCl7Zm9yKHZhciBlPXRoaXMub3B0aW1pemF0aW9uU3RhdGUsaT1lLmhlYWQscj1lLnRhaWwsbz1lLmhlYWRFbWJlZGRpbmcscz1lLnRhaWxFbWJlZGRpbmcsYT1lLmVwb2Noc1BlclNhbXBsZSxsPWUuZXBvY2hPZk5leHRTYW1wbGUsYz1lLmVwb2NoT2ZOZXh0TmVnYXRpdmVTYW1wbGUsdT1lLmVwb2Noc1Blck5lZ2F0aXZlU2FtcGxlLGQ9ZS5tb3ZlT3RoZXIscD1lLmluaXRpYWxBbHBoYSxoPWUuYWxwaGEsZj1lLmdhbW1hLG09ZS5hLHg9ZS5iLGc9ZS5kaW0sYj1lLm5FcG9jaHMsRD1lLm5WZXJ0aWNlcyxrPTA7azxhLmxlbmd0aDtrKyspaWYoIShsW2tdPnQpKXt2YXIgWj1pW2tdLGZlPW9bWl0sdWU9c1tyW2tdXSxoZT1xZmUoZmUsdWUpLHc9MDtoZT4wJiYodz0tMiptKngqTWF0aC5wb3coaGUseC0xKSx3Lz1tKk1hdGgucG93KGhlLHgpKzEpO2Zvcih2YXIgRj0wO0Y8ZztGKyspe3ZhciBxPVdmZSh3KihmZVtGXS11ZVtGXSksNCk7ZmVbRl0rPXEqaCxkJiYodWVbRl0rPS1xKmgpfWxba10rPWFba107Zm9yKHZhciBLPU1hdGguZmxvb3IoKHQtY1trXSkvdVtrXSksZGU9MDtkZTxLO2RlKyspe3ZhciBZPU1vLnRhdVJhbmRJbnQoRCx0aGlzLnJhbmRvbSksYWU9c1tZXSxsZT1xZmUoZmUsYWUpLEllPTA7aWYobGU+MClJZT0yKmYqeCxJZS89KC4wMDErbGUpKihtKk1hdGgucG93KGxlLHgpKzEpO2Vsc2UgaWYoWj09PVkpY29udGludWU7Zm9yKEY9MDtGPGc7RisrKXE9NCxJZT4wJiYocT1XZmUoSWUqKGZlW0ZdLWFlW0ZdKSw0KSksZmVbRl0rPXEqaH1jW2tdKz1LKnVba119cmV0dXJuIGUuYWxwaGE9cCooMS10L2IpLGUuY3VycmVudEVwb2NoKz0xLG99LG4ucHJvdG90eXBlLm9wdGltaXplTGF5b3V0QXN5bmM9ZnVuY3Rpb24odCl7dmFyIGU9dGhpcztyZXR1cm4gdm9pZCAwPT09dCYmKHQ9ZnVuY3Rpb24oKXtyZXR1cm4hMH0pLG5ldyBQcm9taXNlKGZ1bmN0aW9uKGkscil7dmFyIG89ZnVuY3Rpb24oKXtyZXR1cm4gSGZlKGUsdm9pZCAwLHZvaWQgMCxmdW5jdGlvbigpe3ZhciBzLGEsYyx1LGQ7cmV0dXJuIFVmZSh0aGlzLGZ1bmN0aW9uKHApe3RyeXtpZihhPShzPXRoaXMub3B0aW1pemF0aW9uU3RhdGUpLm5FcG9jaHMsdGhpcy5lbWJlZGRpbmc9dGhpcy5vcHRpbWl6ZUxheW91dFN0ZXAocy5jdXJyZW50RXBvY2gpLHU9ITE9PT10KGM9dGhpcy5vcHRpbWl6YXRpb25TdGF0ZS5jdXJyZW50RXBvY2gpLGQ9Yz09PWEsdXx8ZClyZXR1cm5bMixpKGQpXTtzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7cmV0dXJuIG8oKX0sMCl9Y2F0Y2goaCl7cihoKX1yZXR1cm5bMl19KX0pfTtzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7cmV0dXJuIG8oKX0sMCl9KX0sbi5wcm90b3R5cGUub3B0aW1pemVMYXlvdXQ9ZnVuY3Rpb24odCl7dm9pZCAwPT09dCYmKHQ9ZnVuY3Rpb24oKXtyZXR1cm4hMH0pO2Zvcih2YXIgZT0hMSxpPVtdOyFlOyl7dmFyIHI9dGhpcy5vcHRpbWl6YXRpb25TdGF0ZSxvPXIubkVwb2NocztpPXRoaXMub3B0aW1pemVMYXlvdXRTdGVwKHIuY3VycmVudEVwb2NoKTt2YXIgYT10aGlzLm9wdGltaXphdGlvblN0YXRlLmN1cnJlbnRFcG9jaCxsPSExPT09dChhKTtlPWE9PT1vfHxsfXJldHVybiBpfSxuLnByb3RvdHlwZS5nZXRORXBvY2hzPWZ1bmN0aW9uKCl7aWYodGhpcy5uRXBvY2hzPjApcmV0dXJuIHRoaXMubkVwb2Noczt2YXIgZT10aGlzLmdyYXBoLm5Sb3dzO3JldHVybiBlPD0yNTAwPzUwMDplPD01ZTM/NDAwOmU8PTc1MDA/MzAwOjIwMH0sbn0oKTtmdW5jdGlvbiBZZmUobix0KXtmb3IodmFyIGU9MCxpPTA7aTxuLmxlbmd0aDtpKyspZSs9TWF0aC5wb3cobltpXS10W2ldLDIpO3JldHVybiBNYXRoLnNxcnQoZSl9d28uVU1BUD1VJGUsd28uZXVjbGlkZWFuPVlmZSx3by5jb3NpbmU9ZnVuY3Rpb24obix0KXtmb3IodmFyIGU9MCxpPTAscj0wLG89MDtvPG4ubGVuZ3RoO28rKyllKz1uW29dKnRbb10saSs9TWF0aC5wb3cobltvXSwyKSxyKz1NYXRoLnBvdyh0W29dLDIpO3JldHVybiAwPT09aSYmMD09PXI/MDowPT09aXx8MD09PXI/MToxLWUvTWF0aC5zcXJ0KGkqcil9O3ZhciBqJGU9ZnVuY3Rpb24oKXt0aGlzLmN1cnJlbnRFcG9jaD0wLHRoaXMuaGVhZEVtYmVkZGluZz1bXSx0aGlzLnRhaWxFbWJlZGRpbmc9W10sdGhpcy5oZWFkPVtdLHRoaXMudGFpbD1bXSx0aGlzLmVwb2Noc1BlclNhbXBsZT1bXSx0aGlzLmVwb2NoT2ZOZXh0U2FtcGxlPVtdLHRoaXMuZXBvY2hPZk5leHROZWdhdGl2ZVNhbXBsZT1bXSx0aGlzLmVwb2Noc1Blck5lZ2F0aXZlU2FtcGxlPVtdLHRoaXMubW92ZU90aGVyPSEwLHRoaXMuaW5pdGlhbEFscGhhPTEsdGhpcy5hbHBoYT0xLHRoaXMuZ2FtbWE9MSx0aGlzLmE9MS41NzY5NDM0NjAzMTEzMDc3LHRoaXMuYj0uODk1MDYwODc3OTEwOTczMyx0aGlzLmRpbT0yLHRoaXMubkVwb2Nocz01MDAsdGhpcy5uVmVydGljZXM9MH07ZnVuY3Rpb24gV2ZlKG4sdCl7cmV0dXJuIG4+dD90Om48LXQ/LXQ6bn1mdW5jdGlvbiBxZmUobix0KXtmb3IodmFyIGU9MCxpPTA7aTxuLmxlbmd0aDtpKyspZSs9TWF0aC5wb3cobltpXS10W2ldLDIpO3JldHVybiBlfWZ1bmN0aW9uIFhmZShuLHQpe3ZhciBpPU1vLmxpbmVhcigwLDMqbiwzMDApLm1hcChmdW5jdGlvbihwKXtyZXR1cm4gcDx0PzE6cH0pLHI9TW8uemVyb3MoaS5sZW5ndGgpLm1hcChmdW5jdGlvbihwLGgpe3JldHVybiBpW2hdPj10P01hdGguZXhwKC0oaVtoXS10KS9uKTpwfSksbD1IJGUuZGVmYXVsdCh7eDppLHk6cn0sZnVuY3Rpb24ocCl7dmFyIGg9ZDYocCwyKSxmPWhbMF0sbT1oWzFdO3JldHVybiBmdW5jdGlvbih4KXtyZXR1cm4gMS8oMStmKk1hdGgucG93KHgsMiptKSl9fSx7ZGFtcGluZzoxLjUsaW5pdGlhbFZhbHVlczpbLjUsLjVdLGdyYWRpZW50RGlmZmVyZW5jZTouMSxtYXhJdGVyYXRpb25zOjEwMCxlcnJvclRvbGVyYW5jZTouMDF9KS5wYXJhbWV0ZXJWYWx1ZXMsYz1kNihsLDIpO3JldHVybnthOmNbMF0sYjpjWzFdfX1mdW5jdGlvbiBRZmUobix0LGUsaSl7cmV0dXJuIHZvaWQgMD09PWUmJihlPTEpLHZvaWQgMD09PWkmJihpPTUpLG4ubWFwKGZ1bmN0aW9uKHIsbyxzKXtyZXR1cm4tMT09PXRbb118fC0xPT09dFtzXT9yKk1hdGguZXhwKC1lKTp0W29dIT09dFtzXT9yKk1hdGguZXhwKC1pKTpyfSl9ZnVuY3Rpb24gS2ZlKG4pe249Y28ubm9ybWFsaXplKG4sIm1heCIpO3ZhciB0PWNvLnRyYW5zcG9zZShuKSxlPWNvLnBhaXJ3aXNlTXVsdGlwbHkodCxuKTtyZXR1cm4gbj1jby5hZGQobixjby5zdWJ0cmFjdCh0LGUpKSxjby5lbGltaW5hdGVaZXJvcyhuKX1mdW5jdGlvbiBaZmUobix0LGUpe2Zvcih2YXIgaT1Nby56ZXJvcyhuLmxlbmd0aCkubWFwKGZ1bmN0aW9uKGwpe3JldHVybiBNby56ZXJvcyhlWzBdLmxlbmd0aCl9KSxyPTA7cjxuLmxlbmd0aDtyKyspZm9yKHZhciBvPTA7bzxuWzBdLmxlbmd0aDtvKyspZm9yKHZhciBzPTA7czxlWzBdLmxlbmd0aDtzKyspaVtyXVtzXSs9dFtyXVtvXSplW25bcl1bb11dW3NdO3JldHVybiBpfXdvLmZpbmRBQlBhcmFtcz1YZmUsd28uZmFzdEludGVyc2VjdGlvbj1RZmUsd28ucmVzZXRMb2NhbENvbm5lY3Rpdml0eT1LZmUsd28uaW5pdFRyYW5zZm9ybT1aZmV9KSwkZmU9UmQocDY9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHA2LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KTt2YXIgRyRlPUpmZSgpO3A2LlVNQVA9RyRlLlVNQVB9KTtmdW5jdGlvbiBFbihuKXtyZXR1cm4iZnVuY3Rpb24iPT10eXBlb2Ygbn1mdW5jdGlvbiBjMChuKXtsZXQgZT1uKGk9PntFcnJvci5jYWxsKGkpLGkuc3RhY2s9KG5ldyBFcnJvcikuc3RhY2t9KTtyZXR1cm4gZS5wcm90b3R5cGU9T2JqZWN0LmNyZWF0ZShFcnJvci5wcm90b3R5cGUpLGUucHJvdG90eXBlLmNvbnN0cnVjdG9yPWUsZX12YXIgVkU9YzAobj0+ZnVuY3Rpb24oZSl7bih0aGlzKSx0aGlzLm1lc3NhZ2U9ZT9gJHtlLmxlbmd0aH0gZXJyb3JzIG9jY3VycmVkIGR1cmluZyB1bnN1YnNjcmlwdGlvbjpcbiR7ZS5tYXAoKGkscik9PmAke3IrMX0pICR7aS50b1N0cmluZygpfWApLmpvaW4oIlxuICAiKX1gOiIiLHRoaXMubmFtZT0iVW5zdWJzY3JpcHRpb25FcnJvciIsdGhpcy5lcnJvcnM9ZX0pO2Z1bmN0aW9uIGtmKG4sdCl7aWYobil7bGV0IGU9bi5pbmRleE9mKHQpOzA8PWUmJm4uc3BsaWNlKGUsMSl9fXZhciBTbj1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLmluaXRpYWxUZWFyZG93bj10LHRoaXMuY2xvc2VkPSExLHRoaXMuX3BhcmVudGFnZT1udWxsLHRoaXMuX2ZpbmFsaXplcnM9bnVsbH11bnN1YnNjcmliZSgpe2xldCB0O2lmKCF0aGlzLmNsb3NlZCl7dGhpcy5jbG9zZWQ9ITA7bGV0e19wYXJlbnRhZ2U6ZX09dGhpcztpZihlKWlmKHRoaXMuX3BhcmVudGFnZT1udWxsLEFycmF5LmlzQXJyYXkoZSkpZm9yKGxldCBvIG9mIGUpby5yZW1vdmUodGhpcyk7ZWxzZSBlLnJlbW92ZSh0aGlzKTtsZXR7aW5pdGlhbFRlYXJkb3duOml9PXRoaXM7aWYoRW4oaSkpdHJ5e2koKX1jYXRjaChvKXt0PW8gaW5zdGFuY2VvZiBWRT9vLmVycm9yczpbb119bGV0e19maW5hbGl6ZXJzOnJ9PXRoaXM7aWYocil7dGhpcy5fZmluYWxpemVycz1udWxsO2ZvcihsZXQgbyBvZiByKXRyeXtQNihvKX1jYXRjaChzKXt0PXQ/P1tdLHMgaW5zdGFuY2VvZiBWRT90PVsuLi50LC4uLnMuZXJyb3JzXTp0LnB1c2gocyl9fWlmKHQpdGhyb3cgbmV3IFZFKHQpfX1hZGQodCl7dmFyIGU7aWYodCYmdCE9PXRoaXMpaWYodGhpcy5jbG9zZWQpUDYodCk7ZWxzZXtpZih0IGluc3RhbmNlb2YgU24pe2lmKHQuY2xvc2VkfHx0Ll9oYXNQYXJlbnQodGhpcykpcmV0dXJuO3QuX2FkZFBhcmVudCh0aGlzKX0odGhpcy5fZmluYWxpemVycz1udWxsIT09KGU9dGhpcy5fZmluYWxpemVycykmJnZvaWQgMCE9PWU/ZTpbXSkucHVzaCh0KX19X2hhc1BhcmVudCh0KXtsZXR7X3BhcmVudGFnZTplfT10aGlzO3JldHVybiBlPT09dHx8QXJyYXkuaXNBcnJheShlKSYmZS5pbmNsdWRlcyh0KX1fYWRkUGFyZW50KHQpe2xldHtfcGFyZW50YWdlOmV9PXRoaXM7dGhpcy5fcGFyZW50YWdlPUFycmF5LmlzQXJyYXkoZSk/KGUucHVzaCh0KSxlKTplP1tlLHRdOnR9X3JlbW92ZVBhcmVudCh0KXtsZXR7X3BhcmVudGFnZTplfT10aGlzO2U9PT10P3RoaXMuX3BhcmVudGFnZT1udWxsOkFycmF5LmlzQXJyYXkoZSkmJmtmKGUsdCl9cmVtb3ZlKHQpe2xldHtfZmluYWxpemVyczplfT10aGlzO2UmJmtmKGUsdCksdCBpbnN0YW5jZW9mIFNuJiZ0Ll9yZW1vdmVQYXJlbnQodGhpcyl9fTtTbi5FTVBUWT0oKCk9PntsZXQgbj1uZXcgU247cmV0dXJuIG4uY2xvc2VkPSEwLG59KSgpO3ZhciBzTj1Tbi5FTVBUWTtmdW5jdGlvbiBIRShuKXtyZXR1cm4gbiBpbnN0YW5jZW9mIFNufHxuJiYiY2xvc2VkImluIG4mJkVuKG4ucmVtb3ZlKSYmRW4obi5hZGQpJiZFbihuLnVuc3Vic2NyaWJlKX1mdW5jdGlvbiBQNihuKXtFbihuKT9uKCk6bi51bnN1YnNjcmliZSgpfXZhciBDYz17b25VbmhhbmRsZWRFcnJvcjpudWxsLG9uU3RvcHBlZE5vdGlmaWNhdGlvbjpudWxsLFByb21pc2U6dm9pZCAwLHVzZURlcHJlY2F0ZWRTeW5jaHJvbm91c0Vycm9ySGFuZGxpbmc6ITEsdXNlRGVwcmVjYXRlZE5leHRDb250ZXh0OiExfSx1MD17c2V0VGltZW91dChuLHQsLi4uZSl7bGV0e2RlbGVnYXRlOml9PXUwO3JldHVybiBpPy5zZXRUaW1lb3V0P2kuc2V0VGltZW91dChuLHQsLi4uZSk6c2V0VGltZW91dChuLHQsLi4uZSl9LGNsZWFyVGltZW91dChuKXtsZXR7ZGVsZWdhdGU6dH09dTA7cmV0dXJuKHQ/LmNsZWFyVGltZW91dHx8Y2xlYXJUaW1lb3V0KShuKX0sZGVsZWdhdGU6dm9pZCAwfTtmdW5jdGlvbiBVRShuKXt1MC5zZXRUaW1lb3V0KCgpPT57bGV0e29uVW5oYW5kbGVkRXJyb3I6dH09Q2M7aWYoIXQpdGhyb3cgbjt0KG4pfSl9ZnVuY3Rpb24gTWMoKXt9dmFyIFI2PWFOKCJDIix2b2lkIDAsdm9pZCAwKTtmdW5jdGlvbiBhTihuLHQsZSl7cmV0dXJue2tpbmQ6bix2YWx1ZTp0LGVycm9yOmV9fXZhciBGZj1udWxsO2Z1bmN0aW9uIGQwKG4pe2lmKENjLnVzZURlcHJlY2F0ZWRTeW5jaHJvbm91c0Vycm9ySGFuZGxpbmcpe2xldCB0PSFGZjtpZih0JiYoRmY9e2Vycm9yVGhyb3duOiExLGVycm9yOm51bGx9KSxuKCksdCl7bGV0e2Vycm9yVGhyb3duOmUsZXJyb3I6aX09RmY7aWYoRmY9bnVsbCxlKXRocm93IGl9fWVsc2UgbigpfXZhciBOZj1jbGFzcyBleHRlbmRzIFNue2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5pc1N0b3BwZWQ9ITEsdD8odGhpcy5kZXN0aW5hdGlvbj10LEhFKHQpJiZ0LmFkZCh0aGlzKSk6dGhpcy5kZXN0aW5hdGlvbj1zMGV9c3RhdGljIGNyZWF0ZSh0LGUsaSl7cmV0dXJuIG5ldyBPZCh0LGUsaSl9bmV4dCh0KXt0aGlzLmlzU3RvcHBlZD9jTihhTigiTiIsdCx2b2lkIDApLHRoaXMpOnRoaXMuX25leHQodCl9ZXJyb3IodCl7dGhpcy5pc1N0b3BwZWQ/Y04oYU4oIkUiLHZvaWQgMCx0KSx0aGlzKToodGhpcy5pc1N0b3BwZWQ9ITAsdGhpcy5fZXJyb3IodCkpfWNvbXBsZXRlKCl7dGhpcy5pc1N0b3BwZWQ/Y04oUjYsdGhpcyk6KHRoaXMuaXNTdG9wcGVkPSEwLHRoaXMuX2NvbXBsZXRlKCkpfXVuc3Vic2NyaWJlKCl7dGhpcy5jbG9zZWR8fCh0aGlzLmlzU3RvcHBlZD0hMCxzdXBlci51bnN1YnNjcmliZSgpLHRoaXMuZGVzdGluYXRpb249bnVsbCl9X25leHQodCl7dGhpcy5kZXN0aW5hdGlvbi5uZXh0KHQpfV9lcnJvcih0KXt0cnl7dGhpcy5kZXN0aW5hdGlvbi5lcnJvcih0KX1maW5hbGx5e3RoaXMudW5zdWJzY3JpYmUoKX19X2NvbXBsZXRlKCl7dHJ5e3RoaXMuZGVzdGluYXRpb24uY29tcGxldGUoKX1maW5hbGx5e3RoaXMudW5zdWJzY3JpYmUoKX19fSxyMGU9RnVuY3Rpb24ucHJvdG90eXBlLmJpbmQ7ZnVuY3Rpb24gbE4obix0KXtyZXR1cm4gcjBlLmNhbGwobix0KX12YXIgT2Q9Y2xhc3MgZXh0ZW5kcyBOZntjb25zdHJ1Y3Rvcih0LGUsaSl7bGV0IHI7aWYoc3VwZXIoKSxFbih0KXx8IXQpcj17bmV4dDp0Pz92b2lkIDAsZXJyb3I6ZT8/dm9pZCAwLGNvbXBsZXRlOmk/P3ZvaWQgMH07ZWxzZXtsZXQgbzt0aGlzJiZDYy51c2VEZXByZWNhdGVkTmV4dENvbnRleHQ/KG89T2JqZWN0LmNyZWF0ZSh0KSxvLnVuc3Vic2NyaWJlPSgpPT50aGlzLnVuc3Vic2NyaWJlKCkscj17bmV4dDp0Lm5leHQmJmxOKHQubmV4dCxvKSxlcnJvcjp0LmVycm9yJiZsTih0LmVycm9yLG8pLGNvbXBsZXRlOnQuY29tcGxldGUmJmxOKHQuY29tcGxldGUsbyl9KTpyPXR9dGhpcy5kZXN0aW5hdGlvbj1uZXcgY2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5wYXJ0aWFsT2JzZXJ2ZXI9dH1uZXh0KHQpe2xldHtwYXJ0aWFsT2JzZXJ2ZXI6ZX09dGhpcztpZihlLm5leHQpdHJ5e2UubmV4dCh0KX1jYXRjaChpKXt6RShpKX19ZXJyb3IodCl7bGV0e3BhcnRpYWxPYnNlcnZlcjplfT10aGlzO2lmKGUuZXJyb3IpdHJ5e2UuZXJyb3IodCl9Y2F0Y2goaSl7ekUoaSl9ZWxzZSB6RSh0KX1jb21wbGV0ZSgpe2xldHtwYXJ0aWFsT2JzZXJ2ZXI6dH09dGhpcztpZih0LmNvbXBsZXRlKXRyeXt0LmNvbXBsZXRlKCl9Y2F0Y2goZSl7ekUoZSl9fX0ocil9fTtmdW5jdGlvbiB6RShuKXtDYy51c2VEZXByZWNhdGVkU3luY2hyb25vdXNFcnJvckhhbmRsaW5nP2Z1bmN0aW9uKG4pe0NjLnVzZURlcHJlY2F0ZWRTeW5jaHJvbm91c0Vycm9ySGFuZGxpbmcmJkZmJiYoRmYuZXJyb3JUaHJvd249ITAsRmYuZXJyb3I9bil9KG4pOlVFKG4pfWZ1bmN0aW9uIGNOKG4sdCl7bGV0e29uU3RvcHBlZE5vdGlmaWNhdGlvbjplfT1DYztlJiZ1MC5zZXRUaW1lb3V0KCgpPT5lKG4sdCkpfXZhciBzMGU9e2Nsb3NlZDohMCxuZXh0Ok1jLGVycm9yOmZ1bmN0aW9uKG4pe3Rocm93IG59LGNvbXBsZXRlOk1jfSxwMD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZTeW1ib2wub2JzZXJ2YWJsZXx8IkBAb2JzZXJ2YWJsZSI7ZnVuY3Rpb24gbXMobil7cmV0dXJuIG59ZnVuY3Rpb24gcE4obil7cmV0dXJuIDA9PT1uLmxlbmd0aD9tczoxPT09bi5sZW5ndGg/blswXTpmdW5jdGlvbihlKXtyZXR1cm4gbi5yZWR1Y2UoKGkscik9PnIoaSksZSl9fXZhciB1bj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe2UmJih0aGlzLl9zdWJzY3JpYmU9ZSl9bGlmdChlKXtsZXQgaT1uZXcgbjtyZXR1cm4gaS5zb3VyY2U9dGhpcyxpLm9wZXJhdG9yPWUsaX1zdWJzY3JpYmUoZSxpLHIpe2xldCBvPWZ1bmN0aW9uKG4pe3JldHVybiBuJiZuIGluc3RhbmNlb2YgTmZ8fGZ1bmN0aW9uKG4pe3JldHVybiBuJiZFbihuLm5leHQpJiZFbihuLmVycm9yKSYmRW4obi5jb21wbGV0ZSl9KG4pJiZIRShuKX0oZSk/ZTpuZXcgT2QoZSxpLHIpO3JldHVybiBkMCgoKT0+e2xldHtvcGVyYXRvcjpzLHNvdXJjZTphfT10aGlzO28uYWRkKHM/cy5jYWxsKG8sYSk6YT90aGlzLl9zdWJzY3JpYmUobyk6dGhpcy5fdHJ5U3Vic2NyaWJlKG8pKX0pLG99X3RyeVN1YnNjcmliZShlKXt0cnl7cmV0dXJuIHRoaXMuX3N1YnNjcmliZShlKX1jYXRjaChpKXtlLmVycm9yKGkpfX1mb3JFYWNoKGUsaSl7cmV0dXJuIG5ldyhpPU42KGkpKSgocixvKT0+e2xldCBzPW5ldyBPZCh7bmV4dDphPT57dHJ5e2UoYSl9Y2F0Y2gobCl7byhsKSxzLnVuc3Vic2NyaWJlKCl9fSxlcnJvcjpvLGNvbXBsZXRlOnJ9KTt0aGlzLnN1YnNjcmliZShzKX0pfV9zdWJzY3JpYmUoZSl7dmFyIGk7cmV0dXJuIG51bGw9PT0oaT10aGlzLnNvdXJjZSl8fHZvaWQgMD09PWk/dm9pZCAwOmkuc3Vic2NyaWJlKGUpfVtwMF0oKXtyZXR1cm4gdGhpc31waXBlKC4uLmUpe3JldHVybiBwTihlKSh0aGlzKX10b1Byb21pc2UoZSl7cmV0dXJuIG5ldyhlPU42KGUpKSgoaSxyKT0+e2xldCBvO3RoaXMuc3Vic2NyaWJlKHM9Pm89cyxzPT5yKHMpLCgpPT5pKG8pKX0pfX1yZXR1cm4gbi5jcmVhdGU9dD0+bmV3IG4odCksbn0pKCk7ZnVuY3Rpb24gTjYobil7dmFyIHQ7cmV0dXJuIG51bGwhPT0odD1uPz9DYy5Qcm9taXNlKSYmdm9pZCAwIT09dD90OlByb21pc2V9ZnVuY3Rpb24gaE4obil7cmV0dXJuIEVuKG4/LmxpZnQpfWZ1bmN0aW9uIGVuKG4pe3JldHVybiB0PT57aWYoaE4odCkpcmV0dXJuIHQubGlmdChmdW5jdGlvbihlKXt0cnl7cmV0dXJuIG4oZSx0aGlzKX1jYXRjaChpKXt0aGlzLmVycm9yKGkpfX0pO3Rocm93IG5ldyBUeXBlRXJyb3IoIlVuYWJsZSB0byBsaWZ0IHVua25vd24gT2JzZXJ2YWJsZSB0eXBlIil9fWZ1bmN0aW9uIGp0KG4sdCxlLGkscil7cmV0dXJuIG5ldyBueChuLHQsZSxpLHIpfXZhciBmTixueD1jbGFzcyBleHRlbmRzIE5me2NvbnN0cnVjdG9yKHQsZSxpLHIsbyxzKXtzdXBlcih0KSx0aGlzLm9uRmluYWxpemU9byx0aGlzLnNob3VsZFVuc3Vic2NyaWJlPXMsdGhpcy5fbmV4dD1lP2Z1bmN0aW9uKGEpe3RyeXtlKGEpfWNhdGNoKGwpe3QuZXJyb3IobCl9fTpzdXBlci5fbmV4dCx0aGlzLl9lcnJvcj1yP2Z1bmN0aW9uKGEpe3RyeXtyKGEpfWNhdGNoKGwpe3QuZXJyb3IobCl9ZmluYWxseXt0aGlzLnVuc3Vic2NyaWJlKCl9fTpzdXBlci5fZXJyb3IsdGhpcy5fY29tcGxldGU9aT9mdW5jdGlvbigpe3RyeXtpKCl9Y2F0Y2goYSl7dC5lcnJvcihhKX1maW5hbGx5e3RoaXMudW5zdWJzY3JpYmUoKX19OnN1cGVyLl9jb21wbGV0ZX11bnN1YnNjcmliZSgpe3ZhciB0O2lmKCF0aGlzLnNob3VsZFVuc3Vic2NyaWJlfHx0aGlzLnNob3VsZFVuc3Vic2NyaWJlKCkpe2xldHtjbG9zZWQ6ZX09dGhpcztzdXBlci51bnN1YnNjcmliZSgpLCFlJiYobnVsbD09PSh0PXRoaXMub25GaW5hbGl6ZSl8fHZvaWQgMD09PXR8fHQuY2FsbCh0aGlzKSl9fX0saXg9Y2xhc3MgZXh0ZW5kcyB1bntjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKCksdGhpcy5zb3VyY2U9dCx0aGlzLnN1YmplY3RGYWN0b3J5PWUsdGhpcy5fc3ViamVjdD1udWxsLHRoaXMuX3JlZkNvdW50PTAsdGhpcy5fY29ubmVjdGlvbj1udWxsLGhOKHQpJiYodGhpcy5saWZ0PXQubGlmdCl9X3N1YnNjcmliZSh0KXtyZXR1cm4gdGhpcy5nZXRTdWJqZWN0KCkuc3Vic2NyaWJlKHQpfWdldFN1YmplY3QoKXtsZXQgdD10aGlzLl9zdWJqZWN0O3JldHVybighdHx8dC5pc1N0b3BwZWQpJiYodGhpcy5fc3ViamVjdD10aGlzLnN1YmplY3RGYWN0b3J5KCkpLHRoaXMuX3N1YmplY3R9X3RlYXJkb3duKCl7dGhpcy5fcmVmQ291bnQ9MDtsZXR7X2Nvbm5lY3Rpb246dH09dGhpczt0aGlzLl9zdWJqZWN0PXRoaXMuX2Nvbm5lY3Rpb249bnVsbCx0Py51bnN1YnNjcmliZSgpfWNvbm5lY3QoKXtsZXQgdD10aGlzLl9jb25uZWN0aW9uO2lmKCF0KXt0PXRoaXMuX2Nvbm5lY3Rpb249bmV3IFNuO2xldCBlPXRoaXMuZ2V0U3ViamVjdCgpO3QuYWRkKHRoaXMuc291cmNlLnN1YnNjcmliZShqdChlLHZvaWQgMCwoKT0+e3RoaXMuX3RlYXJkb3duKCksZS5jb21wbGV0ZSgpfSxpPT57dGhpcy5fdGVhcmRvd24oKSxlLmVycm9yKGkpfSwoKT0+dGhpcy5fdGVhcmRvd24oKSkpKSx0LmNsb3NlZCYmKHRoaXMuX2Nvbm5lY3Rpb249bnVsbCx0PVNuLkVNUFRZKX1yZXR1cm4gdH1yZWZDb3VudCgpe3JldHVybiBlbigobix0KT0+e2xldCBlPW51bGw7bi5fcmVmQ291bnQrKztsZXQgaT1qdCh0LHZvaWQgMCx2b2lkIDAsdm9pZCAwLCgpPT57aWYoIW58fG4uX3JlZkNvdW50PD0wfHwwPC0tbi5fcmVmQ291bnQpcmV0dXJuIHZvaWQoZT1udWxsKTtsZXQgcj1uLl9jb25uZWN0aW9uLG89ZTtlPW51bGwsciYmKCFvfHxyPT09bykmJnIudW5zdWJzY3JpYmUoKSx0LnVuc3Vic2NyaWJlKCl9KTtuLnN1YnNjcmliZShpKSxpLmNsb3NlZHx8KGU9bi5jb25uZWN0KCkpfSkodGhpcyl9fSxoMD17c2NoZWR1bGUobil7bGV0IHQ9cmVxdWVzdEFuaW1hdGlvbkZyYW1lLGU9Y2FuY2VsQW5pbWF0aW9uRnJhbWUse2RlbGVnYXRlOml9PWgwO2kmJih0PWkucmVxdWVzdEFuaW1hdGlvbkZyYW1lLGU9aS5jYW5jZWxBbmltYXRpb25GcmFtZSk7bGV0IHI9dChvPT57ZT12b2lkIDAsbihvKX0pO3JldHVybiBuZXcgU24oKCk9PmU/LihyKSl9LHJlcXVlc3RBbmltYXRpb25GcmFtZSguLi5uKXtsZXR7ZGVsZWdhdGU6dH09aDA7cmV0dXJuKHQ/LnJlcXVlc3RBbmltYXRpb25GcmFtZXx8cmVxdWVzdEFuaW1hdGlvbkZyYW1lKSguLi5uKX0sY2FuY2VsQW5pbWF0aW9uRnJhbWUoLi4ubil7bGV0e2RlbGVnYXRlOnR9PWgwO3JldHVybih0Py5jYW5jZWxBbmltYXRpb25GcmFtZXx8Y2FuY2VsQW5pbWF0aW9uRnJhbWUpKC4uLm4pfSxkZWxlZ2F0ZTp2b2lkIDB9LEI2PWMwKG49PmZ1bmN0aW9uKCl7bih0aGlzKSx0aGlzLm5hbWU9Ik9iamVjdFVuc3Vic2NyaWJlZEVycm9yIix0aGlzLm1lc3NhZ2U9Im9iamVjdCB1bnN1YnNjcmliZWQifSksa2U9KCgpPT57Y2xhc3MgbiBleHRlbmRzIHVue2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLmNsb3NlZD0hMSx0aGlzLmN1cnJlbnRPYnNlcnZlcnM9bnVsbCx0aGlzLm9ic2VydmVycz1bXSx0aGlzLmlzU3RvcHBlZD0hMSx0aGlzLmhhc0Vycm9yPSExLHRoaXMudGhyb3duRXJyb3I9bnVsbH1saWZ0KGUpe2xldCBpPW5ldyBqRSh0aGlzLHRoaXMpO3JldHVybiBpLm9wZXJhdG9yPWUsaX1fdGhyb3dJZkNsb3NlZCgpe2lmKHRoaXMuY2xvc2VkKXRocm93IG5ldyBCNn1uZXh0KGUpe2QwKCgpPT57aWYodGhpcy5fdGhyb3dJZkNsb3NlZCgpLCF0aGlzLmlzU3RvcHBlZCl7dGhpcy5jdXJyZW50T2JzZXJ2ZXJzfHwodGhpcy5jdXJyZW50T2JzZXJ2ZXJzPUFycmF5LmZyb20odGhpcy5vYnNlcnZlcnMpKTtmb3IobGV0IGkgb2YgdGhpcy5jdXJyZW50T2JzZXJ2ZXJzKWkubmV4dChlKX19KX1lcnJvcihlKXtkMCgoKT0+e2lmKHRoaXMuX3Rocm93SWZDbG9zZWQoKSwhdGhpcy5pc1N0b3BwZWQpe3RoaXMuaGFzRXJyb3I9dGhpcy5pc1N0b3BwZWQ9ITAsdGhpcy50aHJvd25FcnJvcj1lO2xldHtvYnNlcnZlcnM6aX09dGhpcztmb3IoO2kubGVuZ3RoOylpLnNoaWZ0KCkuZXJyb3IoZSl9fSl9Y29tcGxldGUoKXtkMCgoKT0+e2lmKHRoaXMuX3Rocm93SWZDbG9zZWQoKSwhdGhpcy5pc1N0b3BwZWQpe3RoaXMuaXNTdG9wcGVkPSEwO2xldHtvYnNlcnZlcnM6ZX09dGhpcztmb3IoO2UubGVuZ3RoOyllLnNoaWZ0KCkuY29tcGxldGUoKX19KX11bnN1YnNjcmliZSgpe3RoaXMuaXNTdG9wcGVkPXRoaXMuY2xvc2VkPSEwLHRoaXMub2JzZXJ2ZXJzPXRoaXMuY3VycmVudE9ic2VydmVycz1udWxsfWdldCBvYnNlcnZlZCgpe3ZhciBlO3JldHVybihudWxsPT09KGU9dGhpcy5vYnNlcnZlcnMpfHx2b2lkIDA9PT1lP3ZvaWQgMDplLmxlbmd0aCk+MH1fdHJ5U3Vic2NyaWJlKGUpe3JldHVybiB0aGlzLl90aHJvd0lmQ2xvc2VkKCksc3VwZXIuX3RyeVN1YnNjcmliZShlKX1fc3Vic2NyaWJlKGUpe3JldHVybiB0aGlzLl90aHJvd0lmQ2xvc2VkKCksdGhpcy5fY2hlY2tGaW5hbGl6ZWRTdGF0dXNlcyhlKSx0aGlzLl9pbm5lclN1YnNjcmliZShlKX1faW5uZXJTdWJzY3JpYmUoZSl7bGV0e2hhc0Vycm9yOmksaXNTdG9wcGVkOnIsb2JzZXJ2ZXJzOm99PXRoaXM7cmV0dXJuIGl8fHI/c046KHRoaXMuY3VycmVudE9ic2VydmVycz1udWxsLG8ucHVzaChlKSxuZXcgU24oKCk9Pnt0aGlzLmN1cnJlbnRPYnNlcnZlcnM9bnVsbCxrZihvLGUpfSkpfV9jaGVja0ZpbmFsaXplZFN0YXR1c2VzKGUpe2xldHtoYXNFcnJvcjppLHRocm93bkVycm9yOnIsaXNTdG9wcGVkOm99PXRoaXM7aT9lLmVycm9yKHIpOm8mJmUuY29tcGxldGUoKX1hc09ic2VydmFibGUoKXtsZXQgZT1uZXcgdW47cmV0dXJuIGUuc291cmNlPXRoaXMsZX19cmV0dXJuIG4uY3JlYXRlPSh0LGUpPT5uZXcgakUodCxlKSxufSkoKSxqRT1jbGFzcyBleHRlbmRzIGtle2NvbnN0cnVjdG9yKHQsZSl7c3VwZXIoKSx0aGlzLmRlc3RpbmF0aW9uPXQsdGhpcy5zb3VyY2U9ZX1uZXh0KHQpe3ZhciBlLGk7bnVsbD09PShpPW51bGw9PT0oZT10aGlzLmRlc3RpbmF0aW9uKXx8dm9pZCAwPT09ZT92b2lkIDA6ZS5uZXh0KXx8dm9pZCAwPT09aXx8aS5jYWxsKGUsdCl9ZXJyb3IodCl7dmFyIGUsaTtudWxsPT09KGk9bnVsbD09PShlPXRoaXMuZGVzdGluYXRpb24pfHx2b2lkIDA9PT1lP3ZvaWQgMDplLmVycm9yKXx8dm9pZCAwPT09aXx8aS5jYWxsKGUsdCl9Y29tcGxldGUoKXt2YXIgdCxlO251bGw9PT0oZT1udWxsPT09KHQ9dGhpcy5kZXN0aW5hdGlvbil8fHZvaWQgMD09PXQ/dm9pZCAwOnQuY29tcGxldGUpfHx2b2lkIDA9PT1lfHxlLmNhbGwodCl9X3N1YnNjcmliZSh0KXt2YXIgZSxpO3JldHVybiBudWxsIT09KGk9bnVsbD09PShlPXRoaXMuc291cmNlKXx8dm9pZCAwPT09ZT92b2lkIDA6ZS5zdWJzY3JpYmUodCkpJiZ2b2lkIDAhPT1pP2k6c059fSxocj1jbGFzcyBleHRlbmRzIGtle2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5fdmFsdWU9dH1nZXQgdmFsdWUoKXtyZXR1cm4gdGhpcy5nZXRWYWx1ZSgpfV9zdWJzY3JpYmUodCl7bGV0IGU9c3VwZXIuX3N1YnNjcmliZSh0KTtyZXR1cm4hZS5jbG9zZWQmJnQubmV4dCh0aGlzLl92YWx1ZSksZX1nZXRWYWx1ZSgpe2xldHtoYXNFcnJvcjp0LHRocm93bkVycm9yOmUsX3ZhbHVlOml9PXRoaXM7aWYodCl0aHJvdyBlO3JldHVybiB0aGlzLl90aHJvd0lmQ2xvc2VkKCksaX1uZXh0KHQpe3N1cGVyLm5leHQodGhpcy5fdmFsdWU9dCl9fSxyeD17bm93OigpPT4ocnguZGVsZWdhdGV8fERhdGUpLm5vdygpLGRlbGVnYXRlOnZvaWQgMH0sTGY9Y2xhc3MgZXh0ZW5kcyBrZXtjb25zdHJ1Y3Rvcih0PTEvMCxlPTEvMCxpPXJ4KXtzdXBlcigpLHRoaXMuX2J1ZmZlclNpemU9dCx0aGlzLl93aW5kb3dUaW1lPWUsdGhpcy5fdGltZXN0YW1wUHJvdmlkZXI9aSx0aGlzLl9idWZmZXI9W10sdGhpcy5faW5maW5pdGVUaW1lV2luZG93PSEwLHRoaXMuX2luZmluaXRlVGltZVdpbmRvdz1lPT09MS8wLHRoaXMuX2J1ZmZlclNpemU9TWF0aC5tYXgoMSx0KSx0aGlzLl93aW5kb3dUaW1lPU1hdGgubWF4KDEsZSl9bmV4dCh0KXtsZXR7aXNTdG9wcGVkOmUsX2J1ZmZlcjppLF9pbmZpbml0ZVRpbWVXaW5kb3c6cixfdGltZXN0YW1wUHJvdmlkZXI6byxfd2luZG93VGltZTpzfT10aGlzO2V8fChpLnB1c2godCksIXImJmkucHVzaChvLm5vdygpK3MpKSx0aGlzLl90cmltQnVmZmVyKCksc3VwZXIubmV4dCh0KX1fc3Vic2NyaWJlKHQpe3RoaXMuX3Rocm93SWZDbG9zZWQoKSx0aGlzLl90cmltQnVmZmVyKCk7bGV0IGU9dGhpcy5faW5uZXJTdWJzY3JpYmUodCkse19pbmZpbml0ZVRpbWVXaW5kb3c6aSxfYnVmZmVyOnJ9PXRoaXMsbz1yLnNsaWNlKCk7Zm9yKGxldCBzPTA7czxvLmxlbmd0aCYmIXQuY2xvc2VkO3MrPWk/MToyKXQubmV4dChvW3NdKTtyZXR1cm4gdGhpcy5fY2hlY2tGaW5hbGl6ZWRTdGF0dXNlcyh0KSxlfV90cmltQnVmZmVyKCl7bGV0e19idWZmZXJTaXplOnQsX3RpbWVzdGFtcFByb3ZpZGVyOmUsX2J1ZmZlcjppLF9pbmZpbml0ZVRpbWVXaW5kb3c6cn09dGhpcyxvPShyPzE6MikqdDtpZih0PDEvMCYmbzxpLmxlbmd0aCYmaS5zcGxpY2UoMCxpLmxlbmd0aC1vKSwhcil7bGV0IHM9ZS5ub3coKSxhPTA7Zm9yKGxldCBsPTE7bDxpLmxlbmd0aCYmaVtsXTw9cztsKz0yKWE9bDthJiZpLnNwbGljZSgwLGErMSl9fX0sR0U9Y2xhc3MgZXh0ZW5kcyBTbntjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKCl9c2NoZWR1bGUodCxlPTApe3JldHVybiB0aGlzfX0sb3g9e3NldEludGVydmFsKG4sdCwuLi5lKXtsZXR7ZGVsZWdhdGU6aX09b3g7cmV0dXJuIGk/LnNldEludGVydmFsP2kuc2V0SW50ZXJ2YWwobix0LC4uLmUpOnNldEludGVydmFsKG4sdCwuLi5lKX0sY2xlYXJJbnRlcnZhbChuKXtsZXR7ZGVsZWdhdGU6dH09b3g7cmV0dXJuKHQ/LmNsZWFySW50ZXJ2YWx8fGNsZWFySW50ZXJ2YWwpKG4pfSxkZWxlZ2F0ZTp2b2lkIDB9LGd1PWNsYXNzIGV4dGVuZHMgR0V7Y29uc3RydWN0b3IodCxlKXtzdXBlcih0LGUpLHRoaXMuc2NoZWR1bGVyPXQsdGhpcy53b3JrPWUsdGhpcy5wZW5kaW5nPSExfXNjaGVkdWxlKHQsZT0wKXt2YXIgaTtpZih0aGlzLmNsb3NlZClyZXR1cm4gdGhpczt0aGlzLnN0YXRlPXQ7bGV0IHI9dGhpcy5pZCxvPXRoaXMuc2NoZWR1bGVyO3JldHVybiBudWxsIT1yJiYodGhpcy5pZD10aGlzLnJlY3ljbGVBc3luY0lkKG8scixlKSksdGhpcy5wZW5kaW5nPSEwLHRoaXMuZGVsYXk9ZSx0aGlzLmlkPW51bGwhPT0oaT10aGlzLmlkKSYmdm9pZCAwIT09aT9pOnRoaXMucmVxdWVzdEFzeW5jSWQobyx0aGlzLmlkLGUpLHRoaXN9cmVxdWVzdEFzeW5jSWQodCxlLGk9MCl7cmV0dXJuIG94LnNldEludGVydmFsKHQuZmx1c2guYmluZCh0LHRoaXMpLGkpfXJlY3ljbGVBc3luY0lkKHQsZSxpPTApe2lmKG51bGwhPWkmJnRoaXMuZGVsYXk9PT1pJiYhMT09PXRoaXMucGVuZGluZylyZXR1cm4gZTtudWxsIT1lJiZveC5jbGVhckludGVydmFsKGUpfWV4ZWN1dGUodCxlKXtpZih0aGlzLmNsb3NlZClyZXR1cm4gbmV3IEVycm9yKCJleGVjdXRpbmcgYSBjYW5jZWxsZWQgYWN0aW9uIik7dGhpcy5wZW5kaW5nPSExO2xldCBpPXRoaXMuX2V4ZWN1dGUodCxlKTtpZihpKXJldHVybiBpOyExPT09dGhpcy5wZW5kaW5nJiZudWxsIT10aGlzLmlkJiYodGhpcy5pZD10aGlzLnJlY3ljbGVBc3luY0lkKHRoaXMuc2NoZWR1bGVyLHRoaXMuaWQsbnVsbCkpfV9leGVjdXRlKHQsZSl7bGV0IHIsaT0hMTt0cnl7dGhpcy53b3JrKHQpfWNhdGNoKG8pe2k9ITAscj1vfHxuZXcgRXJyb3IoIlNjaGVkdWxlZCBhY3Rpb24gdGhyZXcgZmFsc3kgZXJyb3IiKX1pZihpKXJldHVybiB0aGlzLnVuc3Vic2NyaWJlKCkscn11bnN1YnNjcmliZSgpe2lmKCF0aGlzLmNsb3NlZCl7bGV0e2lkOnQsc2NoZWR1bGVyOmV9PXRoaXMse2FjdGlvbnM6aX09ZTt0aGlzLndvcms9dGhpcy5zdGF0ZT10aGlzLnNjaGVkdWxlcj1udWxsLHRoaXMucGVuZGluZz0hMSxrZihpLHRoaXMpLG51bGwhPXQmJih0aGlzLmlkPXRoaXMucmVjeWNsZUFzeW5jSWQoZSx0LG51bGwpKSx0aGlzLmRlbGF5PW51bGwsc3VwZXIudW5zdWJzY3JpYmUoKX19fSxjMGU9MSxtTj17fTtmdW5jdGlvbiBWNihuKXtyZXR1cm4gbiBpbiBtTiYmKGRlbGV0ZSBtTltuXSwhMCl9dmFyIEg2PXtzZXRJbW1lZGlhdGUobil7bGV0IHQ9YzBlKys7cmV0dXJuIG1OW3RdPSEwLGZOfHwoZk49UHJvbWlzZS5yZXNvbHZlKCkpLGZOLnRoZW4oKCk9PlY2KHQpJiZuKCkpLHR9LGNsZWFySW1tZWRpYXRlKG4pe1Y2KG4pfX0se3NldEltbWVkaWF0ZTp1MGUsY2xlYXJJbW1lZGlhdGU6ZDBlfT1INixzeD17c2V0SW1tZWRpYXRlKC4uLm4pe2xldHtkZWxlZ2F0ZTp0fT1zeDtyZXR1cm4odD8uc2V0SW1tZWRpYXRlfHx1MGUpKC4uLm4pfSxjbGVhckltbWVkaWF0ZShuKXtsZXR7ZGVsZWdhdGU6dH09c3g7cmV0dXJuKHQ/LmNsZWFySW1tZWRpYXRlfHxkMGUpKG4pfSxkZWxlZ2F0ZTp2b2lkIDB9LExwPWNsYXNze2NvbnN0cnVjdG9yKHQsZT1McC5ub3cpe3RoaXMuc2NoZWR1bGVyQWN0aW9uQ3Rvcj10LHRoaXMubm93PWV9c2NoZWR1bGUodCxlPTAsaSl7cmV0dXJuIG5ldyB0aGlzLnNjaGVkdWxlckFjdGlvbkN0b3IodGhpcyx0KS5zY2hlZHVsZShpLGUpfX07THAubm93PXJ4Lm5vdzt2YXIgX3U9Y2xhc3MgZXh0ZW5kcyBMcHtjb25zdHJ1Y3Rvcih0LGU9THAubm93KXtzdXBlcih0LGUpLHRoaXMuYWN0aW9ucz1bXSx0aGlzLl9hY3RpdmU9ITF9Zmx1c2godCl7bGV0IGkse2FjdGlvbnM6ZX09dGhpcztpZih0aGlzLl9hY3RpdmUpZS5wdXNoKHQpO2Vsc2V7dGhpcy5fYWN0aXZlPSEwO2Rve2lmKGk9dC5leGVjdXRlKHQuc3RhdGUsdC5kZWxheSkpYnJlYWt9d2hpbGUodD1lLnNoaWZ0KCkpO2lmKHRoaXMuX2FjdGl2ZT0hMSxpKXtmb3IoO3Q9ZS5zaGlmdCgpOyl0LnVuc3Vic2NyaWJlKCk7dGhyb3cgaX19fX0sZjA9bmV3IGNsYXNzIGV4dGVuZHMgX3V7Zmx1c2godCl7dGhpcy5fYWN0aXZlPSEwO2xldCBlPXRoaXMuX3NjaGVkdWxlZDt0aGlzLl9zY2hlZHVsZWQ9dm9pZCAwO2xldCByLHthY3Rpb25zOml9PXRoaXM7dD10fHxpLnNoaWZ0KCk7ZG97aWYocj10LmV4ZWN1dGUodC5zdGF0ZSx0LmRlbGF5KSlicmVha313aGlsZSgodD1pWzBdKSYmdC5pZD09PWUmJmkuc2hpZnQoKSk7aWYodGhpcy5fYWN0aXZlPSExLHIpe2Zvcig7KHQ9aVswXSkmJnQuaWQ9PT1lJiZpLnNoaWZ0KCk7KXQudW5zdWJzY3JpYmUoKTt0aHJvdyByfX19KGNsYXNzIGV4dGVuZHMgZ3V7Y29uc3RydWN0b3IodCxlKXtzdXBlcih0LGUpLHRoaXMuc2NoZWR1bGVyPXQsdGhpcy53b3JrPWV9cmVxdWVzdEFzeW5jSWQodCxlLGk9MCl7cmV0dXJuIG51bGwhPT1pJiZpPjA/c3VwZXIucmVxdWVzdEFzeW5jSWQodCxlLGkpOih0LmFjdGlvbnMucHVzaCh0aGlzKSx0Ll9zY2hlZHVsZWR8fCh0Ll9zY2hlZHVsZWQ9c3guc2V0SW1tZWRpYXRlKHQuZmx1c2guYmluZCh0LHZvaWQgMCkpKSl9cmVjeWNsZUFzeW5jSWQodCxlLGk9MCl7dmFyIHI7aWYobnVsbCE9aT9pPjA6dGhpcy5kZWxheT4wKXJldHVybiBzdXBlci5yZWN5Y2xlQXN5bmNJZCh0LGUsaSk7bGV0e2FjdGlvbnM6b309dDtudWxsIT1lJiYobnVsbD09PShyPW9bby5sZW5ndGgtMV0pfHx2b2lkIDA9PT1yP3ZvaWQgMDpyLmlkKSE9PWUmJihzeC5jbGVhckltbWVkaWF0ZShlKSx0Ll9zY2hlZHVsZWQ9dm9pZCAwKX19KSxrZD1uZXcgX3UoZ3UpLFU2PWtkLGdOPW5ldyBjbGFzcyBleHRlbmRzIF91e30oY2xhc3MgZXh0ZW5kcyBndXtjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKHQsZSksdGhpcy5zY2hlZHVsZXI9dCx0aGlzLndvcms9ZX1zY2hlZHVsZSh0LGU9MCl7cmV0dXJuIGU+MD9zdXBlci5zY2hlZHVsZSh0LGUpOih0aGlzLmRlbGF5PWUsdGhpcy5zdGF0ZT10LHRoaXMuc2NoZWR1bGVyLmZsdXNoKHRoaXMpLHRoaXMpfWV4ZWN1dGUodCxlKXtyZXR1cm4gZT4wfHx0aGlzLmNsb3NlZD9zdXBlci5leGVjdXRlKHQsZSk6dGhpcy5fZXhlY3V0ZSh0LGUpfXJlcXVlc3RBc3luY0lkKHQsZSxpPTApe3JldHVybiBudWxsIT1pJiZpPjB8fG51bGw9PWkmJnRoaXMuZGVsYXk+MD9zdXBlci5yZXF1ZXN0QXN5bmNJZCh0LGUsaSk6KHQuZmx1c2godGhpcyksMCl9fSksX049bmV3IGNsYXNzIGV4dGVuZHMgX3V7Zmx1c2godCl7dGhpcy5fYWN0aXZlPSEwO2xldCBlPXRoaXMuX3NjaGVkdWxlZDt0aGlzLl9zY2hlZHVsZWQ9dm9pZCAwO2xldCByLHthY3Rpb25zOml9PXRoaXM7dD10fHxpLnNoaWZ0KCk7ZG97aWYocj10LmV4ZWN1dGUodC5zdGF0ZSx0LmRlbGF5KSlicmVha313aGlsZSgodD1pWzBdKSYmdC5pZD09PWUmJmkuc2hpZnQoKSk7aWYodGhpcy5fYWN0aXZlPSExLHIpe2Zvcig7KHQ9aVswXSkmJnQuaWQ9PT1lJiZpLnNoaWZ0KCk7KXQudW5zdWJzY3JpYmUoKTt0aHJvdyByfX19KGNsYXNzIGV4dGVuZHMgZ3V7Y29uc3RydWN0b3IodCxlKXtzdXBlcih0LGUpLHRoaXMuc2NoZWR1bGVyPXQsdGhpcy53b3JrPWV9cmVxdWVzdEFzeW5jSWQodCxlLGk9MCl7cmV0dXJuIG51bGwhPT1pJiZpPjA/c3VwZXIucmVxdWVzdEFzeW5jSWQodCxlLGkpOih0LmFjdGlvbnMucHVzaCh0aGlzKSx0Ll9zY2hlZHVsZWR8fCh0Ll9zY2hlZHVsZWQ9aDAucmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpPT50LmZsdXNoKHZvaWQgMCkpKSl9cmVjeWNsZUFzeW5jSWQodCxlLGk9MCl7dmFyIHI7aWYobnVsbCE9aT9pPjA6dGhpcy5kZWxheT4wKXJldHVybiBzdXBlci5yZWN5Y2xlQXN5bmNJZCh0LGUsaSk7bGV0e2FjdGlvbnM6b309dDtudWxsIT1lJiYobnVsbD09PShyPW9bby5sZW5ndGgtMV0pfHx2b2lkIDA9PT1yP3ZvaWQgMDpyLmlkKSE9PWUmJihoMC5jYW5jZWxBbmltYXRpb25GcmFtZShlKSx0Ll9zY2hlZHVsZWQ9dm9pZCAwKX19KSxlbz1uZXcgdW4obj0+bi5jb21wbGV0ZSgpKTtmdW5jdGlvbiBaRShuKXtyZXR1cm4gbiYmRW4obi5zY2hlZHVsZSl9ZnVuY3Rpb24gdk4obil7cmV0dXJuIG5bbi5sZW5ndGgtMV19ZnVuY3Rpb24gdnUobil7cmV0dXJuIEVuKHZOKG4pKT9uLnBvcCgpOnZvaWQgMH1mdW5jdGlvbiB5dShuKXtyZXR1cm4gWkUodk4obikpP24ucG9wKCk6dm9pZCAwfXZhciBkVz1vTih1VygpLDEpLHtfX2RlY29yYXRlOnBXLF9fYXdhaXRlcjpoVyxfX2F3YWl0OnQxLF9fYXN5bmNHZW5lcmF0b3I6ZlcsX19hc3luY1ZhbHVlczptV309ZFcuZGVmYXVsdCxnMD1uPT5uJiYibnVtYmVyIj09dHlwZW9mIG4ubGVuZ3RoJiYiZnVuY3Rpb24iIT10eXBlb2YgbjtmdW5jdGlvbiBuMShuKXtyZXR1cm4gRW4obj8udGhlbil9ZnVuY3Rpb24gaTEobil7cmV0dXJuIEVuKG5bcDBdKX1mdW5jdGlvbiByMShuKXtyZXR1cm4gU3ltYm9sLmFzeW5jSXRlcmF0b3ImJkVuKG4/LltTeW1ib2wuYXN5bmNJdGVyYXRvcl0pfWZ1bmN0aW9uIG8xKG4pe3JldHVybiBuZXcgVHlwZUVycm9yKGBZb3UgcHJvdmlkZWQgJHtudWxsIT09biYmIm9iamVjdCI9PXR5cGVvZiBuPyJhbiBpbnZhbGlkIG9iamVjdCI6YCcke259J2B9IHdoZXJlIGEgc3RyZWFtIHdhcyBleHBlY3RlZC4gWW91IGNhbiBwcm92aWRlIGFuIE9ic2VydmFibGUsIFByb21pc2UsIFJlYWRhYmxlU3RyZWFtLCBBcnJheSwgQXN5bmNJdGVyYWJsZSwgb3IgSXRlcmFibGUuYCl9dmFyIHMxPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJlN5bWJvbC5pdGVyYXRvcj9TeW1ib2wuaXRlcmF0b3I6IkBAaXRlcmF0b3IiO2Z1bmN0aW9uIGExKG4pe3JldHVybiBFbihuPy5bczFdKX1mdW5jdGlvbiBsMShuKXtyZXR1cm4gZlcodGhpcyxhcmd1bWVudHMsZnVuY3Rpb24qKCl7bGV0IGU9bi5nZXRSZWFkZXIoKTt0cnl7Zm9yKDs7KXtsZXR7dmFsdWU6aSxkb25lOnJ9PXlpZWxkIHQxKGUucmVhZCgpKTtpZihyKXJldHVybiB5aWVsZCB0MSh2b2lkIDApO3lpZWxkIHlpZWxkIHQxKGkpfX1maW5hbGx5e2UucmVsZWFzZUxvY2soKX19KX1mdW5jdGlvbiBjMShuKXtyZXR1cm4gRW4obj8uZ2V0UmVhZGVyKX1mdW5jdGlvbiBnaShuKXtpZihuIGluc3RhbmNlb2YgdW4pcmV0dXJuIG47aWYobnVsbCE9bil7aWYoaTEobikpcmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBuZXcgdW4odD0+e2xldCBlPW5bcDBdKCk7aWYoRW4oZS5zdWJzY3JpYmUpKXJldHVybiBlLnN1YnNjcmliZSh0KTt0aHJvdyBuZXcgVHlwZUVycm9yKCJQcm92aWRlZCBvYmplY3QgZG9lcyBub3QgY29ycmVjdGx5IGltcGxlbWVudCBTeW1ib2wub2JzZXJ2YWJsZSIpfSl9KG4pO2lmKGcwKG4pKXJldHVybiBmdW5jdGlvbihuKXtyZXR1cm4gbmV3IHVuKHQ9Pntmb3IobGV0IGU9MDtlPG4ubGVuZ3RoJiYhdC5jbG9zZWQ7ZSsrKXQubmV4dChuW2VdKTt0LmNvbXBsZXRlKCl9KX0obik7aWYobjEobikpcmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBuZXcgdW4odD0+e24udGhlbihlPT57dC5jbG9zZWR8fCh0Lm5leHQoZSksdC5jb21wbGV0ZSgpKX0sZT0+dC5lcnJvcihlKSkudGhlbihudWxsLFVFKX0pfShuKTtpZihyMShuKSlyZXR1cm4gZ1cobik7aWYoYTEobikpcmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBuZXcgdW4odD0+e2ZvcihsZXQgZSBvZiBuKWlmKHQubmV4dChlKSx0LmNsb3NlZClyZXR1cm47dC5jb21wbGV0ZSgpfSl9KG4pO2lmKGMxKG4pKXJldHVybiBmdW5jdGlvbihuKXtyZXR1cm4gZ1cobDEobikpfShuKX10aHJvdyBvMShuKX1mdW5jdGlvbiBnVyhuKXtyZXR1cm4gbmV3IHVuKHQ9PnsoZnVuY3Rpb24obix0KXt2YXIgZSxpLHIsbztyZXR1cm4gaFcodGhpcyx2b2lkIDAsdm9pZCAwLGZ1bmN0aW9uKigpe3RyeXtmb3IoZT1tVyhuKTshKGk9eWllbGQgZS5uZXh0KCkpLmRvbmU7KWlmKHQubmV4dChpLnZhbHVlKSx0LmNsb3NlZClyZXR1cm59Y2F0Y2gocyl7cj17ZXJyb3I6c319ZmluYWxseXt0cnl7aSYmIWkuZG9uZSYmKG89ZS5yZXR1cm4pJiYoeWllbGQgby5jYWxsKGUpKX1maW5hbGx5e2lmKHIpdGhyb3cgci5lcnJvcn19dC5jb21wbGV0ZSgpfSl9KShuLHQpLmNhdGNoKGU9PnQuZXJyb3IoZSkpfSl9ZnVuY3Rpb24gQ2Eobix0LGUsaT0wLHI9ITEpe2xldCBvPXQuc2NoZWR1bGUoZnVuY3Rpb24oKXtlKCkscj9uLmFkZCh0aGlzLnNjaGVkdWxlKG51bGwsaSkpOnRoaXMudW5zdWJzY3JpYmUoKX0saSk7aWYobi5hZGQobyksIXIpcmV0dXJuIG99ZnVuY3Rpb24gQmYobix0PTApe3JldHVybiBlbigoZSxpKT0+e2Uuc3Vic2NyaWJlKGp0KGkscj0+Q2EoaSxuLCgpPT5pLm5leHQociksdCksKCk9PkNhKGksbiwoKT0+aS5jb21wbGV0ZSgpLHQpLHI9PkNhKGksbiwoKT0+aS5lcnJvcihyKSx0KSkpfSl9ZnVuY3Rpb24gdTEobix0PTApe3JldHVybiBlbigoZSxpKT0+e2kuYWRkKG4uc2NoZWR1bGUoKCk9PmUuc3Vic2NyaWJlKGkpLHQpKX0pfWZ1bmN0aW9uIGQxKG4sdCl7aWYoIW4pdGhyb3cgbmV3IEVycm9yKCJJdGVyYWJsZSBjYW5ub3QgYmUgbnVsbCIpO3JldHVybiBuZXcgdW4oZT0+e0NhKGUsdCwoKT0+e2xldCBpPW5bU3ltYm9sLmFzeW5jSXRlcmF0b3JdKCk7Q2EoZSx0LCgpPT57aS5uZXh0KCkudGhlbihyPT57ci5kb25lP2UuY29tcGxldGUoKTplLm5leHQoci52YWx1ZSl9KX0sMCwhMCl9KX0pfWZ1bmN0aW9uIEVvKG4sdCl7cmV0dXJuIHQ/ZnVuY3Rpb24obix0KXtpZihudWxsIT1uKXtpZihpMShuKSlyZXR1cm4gZnVuY3Rpb24obix0KXtyZXR1cm4gZ2kobikucGlwZSh1MSh0KSxCZih0KSl9KG4sdCk7aWYoZzAobikpcmV0dXJuIGZ1bmN0aW9uKG4sdCl7cmV0dXJuIG5ldyB1bihlPT57bGV0IGk9MDtyZXR1cm4gdC5zY2hlZHVsZShmdW5jdGlvbigpe2k9PT1uLmxlbmd0aD9lLmNvbXBsZXRlKCk6KGUubmV4dChuW2krK10pLGUuY2xvc2VkfHx0aGlzLnNjaGVkdWxlKCkpfSl9KX0obix0KTtpZihuMShuKSlyZXR1cm4gZnVuY3Rpb24obix0KXtyZXR1cm4gZ2kobikucGlwZSh1MSh0KSxCZih0KSl9KG4sdCk7aWYocjEobikpcmV0dXJuIGQxKG4sdCk7aWYoYTEobikpcmV0dXJuIGZ1bmN0aW9uKG4sdCl7cmV0dXJuIG5ldyB1bihlPT57bGV0IGk7cmV0dXJuIENhKGUsdCwoKT0+e2k9bltzMV0oKSxDYShlLHQsKCk9PntsZXQgcixvO3RyeXsoe3ZhbHVlOnIsZG9uZTpvfT1pLm5leHQoKSl9Y2F0Y2gocyl7cmV0dXJuIHZvaWQgZS5lcnJvcihzKX1vP2UuY29tcGxldGUoKTplLm5leHQocil9LDAsITApfSksKCk9PkVuKGk/LnJldHVybikmJmkucmV0dXJuKCl9KX0obix0KTtpZihjMShuKSlyZXR1cm4gZnVuY3Rpb24obix0KXtyZXR1cm4gZDEobDEobiksdCl9KG4sdCl9dGhyb3cgbzEobil9KG4sdCk6Z2kobil9ZnVuY3Rpb24gWHQoLi4ubil7cmV0dXJuIEVvKG4seXUobikpfWZ1bmN0aW9uIHdjKG4sdCl7bGV0IGU9RW4obik/bjooKT0+bixpPXI9PnIuZXJyb3IoZSgpKTtyZXR1cm4gbmV3IHVuKHQ/cj0+dC5zY2hlZHVsZShpLDAscik6aSl9dmFyIFJsPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLmtpbmQ9dCx0aGlzLnZhbHVlPWUsdGhpcy5lcnJvcj1pLHRoaXMuaGFzVmFsdWU9Ik4iPT09dH1vYnNlcnZlKHQpe3JldHVybiBiTih0aGlzLHQpfWRvKHQsZSxpKXtsZXR7a2luZDpyLHZhbHVlOm8sZXJyb3I6c309dGhpcztyZXR1cm4iTiI9PT1yP3Q/LihvKToiRSI9PT1yP2U/LihzKTppPy4oKX1hY2NlcHQodCxlLGkpe3ZhciByO3JldHVybiBFbihudWxsPT09KHI9dCl8fHZvaWQgMD09PXI/dm9pZCAwOnIubmV4dCk/dGhpcy5vYnNlcnZlKHQpOnRoaXMuZG8odCxlLGkpfXRvT2JzZXJ2YWJsZSgpe2xldHtraW5kOnQsdmFsdWU6ZSxlcnJvcjppfT10aGlzLHI9Ik4iPT09dD9YdChlKToiRSI9PT10P3djKCgpPT5pKToiQyI9PT10P2VvOjA7aWYoIXIpdGhyb3cgbmV3IFR5cGVFcnJvcihgVW5leHBlY3RlZCBub3RpZmljYXRpb24ga2luZCAke3R9YCk7cmV0dXJuIHJ9c3RhdGljIGNyZWF0ZU5leHQodCl7cmV0dXJuIG5ldyBSbCgiTiIsdCl9c3RhdGljIGNyZWF0ZUVycm9yKHQpe3JldHVybiBuZXcgUmwoIkUiLHZvaWQgMCx0KX1zdGF0aWMgY3JlYXRlQ29tcGxldGUoKXtyZXR1cm4gUmwuY29tcGxldGVOb3RpZmljYXRpb259fTtmdW5jdGlvbiBiTihuLHQpe3ZhciBlLGkscjtsZXR7a2luZDpvLHZhbHVlOnMsZXJyb3I6YX09bjtpZigic3RyaW5nIiE9dHlwZW9mIG8pdGhyb3cgbmV3IFR5cGVFcnJvcignSW52YWxpZCBub3RpZmljYXRpb24sIG1pc3NpbmcgImtpbmQiJyk7Ik4iPT09bz9udWxsPT09KGU9dC5uZXh0KXx8dm9pZCAwPT09ZXx8ZS5jYWxsKHQscyk6IkUiPT09bz9udWxsPT09KGk9dC5lcnJvcil8fHZvaWQgMD09PWl8fGkuY2FsbCh0LGEpOm51bGw9PT0ocj10LmNvbXBsZXRlKXx8dm9pZCAwPT09cnx8ci5jYWxsKHQpfWZ1bmN0aW9uIGF4KG4pe3JldHVybiEhbiYmKG4gaW5zdGFuY2VvZiB1bnx8RW4obi5saWZ0KSYmRW4obi5zdWJzY3JpYmUpKX1SbC5jb21wbGV0ZU5vdGlmaWNhdGlvbj1uZXcgUmwoIkMiKTt2YXIgXzA9YzAobj0+ZnVuY3Rpb24oKXtuKHRoaXMpLHRoaXMubmFtZT0iRW1wdHlFcnJvciIsdGhpcy5tZXNzYWdlPSJubyBlbGVtZW50cyBpbiBzZXF1ZW5jZSJ9KTtmdW5jdGlvbiBMKG4sdCl7cmV0dXJuIGVuKChlLGkpPT57bGV0IHI9MDtlLnN1YnNjcmliZShqdChpLG89PntpLm5leHQobi5jYWxsKHQsbyxyKyspKX0pKX0pfXZhcntpc0FycmF5OnkwZX09QXJyYXk7ZnVuY3Rpb24gQnAobil7cmV0dXJuIEwodD0+ZnVuY3Rpb24obix0KXtyZXR1cm4geTBlKHQpP24oLi4udCk6bih0KX0obix0KSl9dmFye2lzQXJyYXk6eDBlfT1BcnJheSx7Z2V0UHJvdG90eXBlT2Y6QzBlLHByb3RvdHlwZTpNMGUsa2V5czp3MGV9PU9iamVjdDtmdW5jdGlvbiBwMShuKXtpZigxPT09bi5sZW5ndGgpe2xldCB0PW5bMF07aWYoeDBlKHQpKXJldHVybnthcmdzOnQsa2V5czpudWxsfTtpZihmdW5jdGlvbihuKXtyZXR1cm4gbiYmIm9iamVjdCI9PXR5cGVvZiBuJiZDMGUobik9PT1NMGV9KHQpKXtsZXQgZT13MGUodCk7cmV0dXJue2FyZ3M6ZS5tYXAoaT0+dFtpXSksa2V5czplfX19cmV0dXJue2FyZ3M6bixrZXlzOm51bGx9fWZ1bmN0aW9uIGgxKG4sdCl7cmV0dXJuIG4ucmVkdWNlKChlLGkscik9PihlW2ldPXRbcl0sZSkse30pfWZ1bmN0aW9uIEx0KC4uLm4pe2xldCB0PXl1KG4pLGU9dnUobikse2FyZ3M6aSxrZXlzOnJ9PXAxKG4pO2lmKDA9PT1pLmxlbmd0aClyZXR1cm4gRW8oW10sdCk7bGV0IG89bmV3IHVuKHhOKGksdCxyP3M9PmgxKHIscyk6bXMpKTtyZXR1cm4gZT9vLnBpcGUoQnAoZSkpOm99ZnVuY3Rpb24geE4obix0LGU9bXMpe3JldHVybiBpPT57d1codCwoKT0+e2xldHtsZW5ndGg6cn09bixvPW5ldyBBcnJheShyKSxzPXIsYT1yO2ZvcihsZXQgbD0wO2w8cjtsKyspd1codCwoKT0+e2xldCBjPUVvKG5bbF0sdCksdT0hMTtjLnN1YnNjcmliZShqdChpLGQ9PntvW2xdPWQsdXx8KHU9ITAsYS0tKSxhfHxpLm5leHQoZShvLnNsaWNlKCkpKX0sKCk9PnstLXN8fGkuY29tcGxldGUoKX0pKX0saSl9LGkpfX1mdW5jdGlvbiB3VyhuLHQsZSl7bj9DYShlLG4sdCk6dCgpfWZ1bmN0aW9uIHhuKG4sdCxlPTEvMCl7cmV0dXJuIEVuKHQpP3huKChpLHIpPT5MKChvLHMpPT50KGksbyxyLHMpKShnaShuKGkscikpKSxlKTooIm51bWJlciI9PXR5cGVvZiB0JiYoZT10KSxlbigoaSxyKT0+ZnVuY3Rpb24obix0LGUsaSxyLG8scyxhKXtsZXQgbD1bXSxjPTAsdT0wLGQ9ITEscD0oKT0+e2QmJiFsLmxlbmd0aCYmIWMmJnQuY29tcGxldGUoKX0saD1tPT5jPGk/ZihtKTpsLnB1c2gobSksZj1tPT57YysrO2xldCB4PSExO2dpKGUobSx1KyspKS5zdWJzY3JpYmUoanQodCxnPT57dC5uZXh0KGcpfSwoKT0+e3g9ITB9LHZvaWQgMCwoKT0+e2lmKHgpdHJ5e2ZvcihjLS07bC5sZW5ndGgmJmM8aTspe2xldCBnPWwuc2hpZnQoKTtmKGcpfXAoKX1jYXRjaChnKXt0LmVycm9yKGcpfX0pKX07cmV0dXJuIG4uc3Vic2NyaWJlKGp0KHQsaCwoKT0+e2Q9ITAscCgpfSkpLCgpPT57fX0oaSxyLG4sZSkpKX1mdW5jdGlvbiBmMShuPTEvMCl7cmV0dXJuIHhuKG1zLG4pfWZ1bmN0aW9uIFZwKC4uLm4pe3JldHVybiBmMSgxKShFbyhuLHl1KG4pKSl9ZnVuY3Rpb24gUWEobil7cmV0dXJuIG5ldyB1bih0PT57Z2kobigpKS5zdWJzY3JpYmUodCl9KX1mdW5jdGlvbiBsciguLi5uKXtsZXQgdD12dShuKSx7YXJnczplLGtleXM6aX09cDEobikscj1uZXcgdW4obz0+e2xldHtsZW5ndGg6c309ZTtpZighcylyZXR1cm4gdm9pZCBvLmNvbXBsZXRlKCk7bGV0IGE9bmV3IEFycmF5KHMpLGw9cyxjPXM7Zm9yKGxldCB1PTA7dTxzO3UrKyl7bGV0IGQ9ITE7Z2koZVt1XSkuc3Vic2NyaWJlKGp0KG8scD0+e2R8fChkPSEwLGMtLSksYVt1XT1wfSwoKT0+bC0tLHZvaWQgMCwoKT0+eyghbHx8IWQpJiYoY3x8by5uZXh0KGk/aDEoaSxhKTphKSxvLmNvbXBsZXRlKCkpfSkpfX0pO3JldHVybiB0P3IucGlwZShCcCh0KSk6cn12YXIgRTBlPVsiYWRkTGlzdGVuZXIiLCJyZW1vdmVMaXN0ZW5lciJdLFQwZT1bImFkZEV2ZW50TGlzdGVuZXIiLCJyZW1vdmVFdmVudExpc3RlbmVyIl0sRDBlPVsib24iLCJvZmYiXTtmdW5jdGlvbiBfaShuLHQsZSxpKXtpZihFbihlKSYmKGk9ZSxlPXZvaWQgMCksaSlyZXR1cm4gX2kobix0LGUpLnBpcGUoQnAoaSkpO2xldFtyLG9dPWZ1bmN0aW9uKG4pe3JldHVybiBFbihuLmFkZEV2ZW50TGlzdGVuZXIpJiZFbihuLnJlbW92ZUV2ZW50TGlzdGVuZXIpfShuKT9UMGUubWFwKHM9PmE9Pm5bc10odCxhLGUpKTpmdW5jdGlvbihuKXtyZXR1cm4gRW4obi5hZGRMaXN0ZW5lcikmJkVuKG4ucmVtb3ZlTGlzdGVuZXIpfShuKT9FMGUubWFwKFRXKG4sdCkpOmZ1bmN0aW9uKG4pe3JldHVybiBFbihuLm9uKSYmRW4obi5vZmYpfShuKT9EMGUubWFwKFRXKG4sdCkpOltdO2lmKCFyJiZnMChuKSlyZXR1cm4geG4ocz0+X2kocyx0LGUpKShnaShuKSk7aWYoIXIpdGhyb3cgbmV3IFR5cGVFcnJvcigiSW52YWxpZCBldmVudCB0YXJnZXQiKTtyZXR1cm4gbmV3IHVuKHM9PntsZXQgYT0oLi4ubCk9PnMubmV4dCgxPGwubGVuZ3RoP2w6bFswXSk7cmV0dXJuIHIoYSksKCk9Pm8oYSl9KX1mdW5jdGlvbiBUVyhuLHQpe3JldHVybiBlPT5pPT5uW2VdKHQsaSl9ZnVuY3Rpb24gS2Eobj0wLHQsZT1VNil7bGV0IGk9LTE7cmV0dXJuIG51bGwhPXQmJihaRSh0KT9lPXQ6aT10KSxuZXcgdW4ocj0+e2xldCBvPWZ1bmN0aW9uKG4pe3JldHVybiBuIGluc3RhbmNlb2YgRGF0ZSYmIWlzTmFOKG4pfShuKT8rbi1lLm5vdygpOm47bzwwJiYobz0wKTtsZXQgcz0wO3JldHVybiBlLnNjaGVkdWxlKGZ1bmN0aW9uKCl7ci5jbG9zZWR8fChyLm5leHQocysrKSwwPD1pP3RoaXMuc2NoZWR1bGUodm9pZCAwLGkpOnIuY29tcGxldGUoKSl9LG8pfSl9ZnVuY3Rpb24gSnQoLi4ubil7bGV0IHQ9eXUobiksZT1mdW5jdGlvbihuLHQpe3JldHVybiJudW1iZXIiPT10eXBlb2Ygdk4obik/bi5wb3AoKToxLzB9KG4pLGk9bjtyZXR1cm4gaS5sZW5ndGg/MT09PWkubGVuZ3RoP2dpKGlbMF0pOmYxKGUpKEVvKGksdCkpOmVvfXZhcntpc0FycmF5OlIwZX09QXJyYXk7ZnVuY3Rpb24gbTEobil7cmV0dXJuIDE9PT1uLmxlbmd0aCYmUjBlKG5bMF0pP25bMF06bn1mdW5jdGlvbiBZZShuLHQpe3JldHVybiBlbigoZSxpKT0+e2xldCByPTA7ZS5zdWJzY3JpYmUoanQoaSxvPT5uLmNhbGwodCxvLHIrKykmJmkubmV4dChvKSkpfSl9ZnVuY3Rpb24gYnUobix0PWtkKXtyZXR1cm4gZnVuY3Rpb24obil7cmV0dXJuIGVuKCh0LGUpPT57bGV0IGk9ITEscj1udWxsLG89bnVsbCxzPSExLGE9KCk9PntpZihvPy51bnN1YnNjcmliZSgpLG89bnVsbCxpKXtpPSExO2xldCBjPXI7cj1udWxsLGUubmV4dChjKX1zJiZlLmNvbXBsZXRlKCl9LGw9KCk9PntvPW51bGwscyYmZS5jb21wbGV0ZSgpfTt0LnN1YnNjcmliZShqdChlLGM9PntpPSEwLHI9YyxvfHxnaShuKCkpLnN1YnNjcmliZShvPWp0KGUsYSxsKSl9LCgpPT57cz0hMCwoIWl8fCFvfHxvLmNsb3NlZCkmJmUuY29tcGxldGUoKX0pKX0pfSgoKT0+S2Eobix0KSl9ZnVuY3Rpb24gZm8obil7cmV0dXJuIGVuKCh0LGUpPT57bGV0IG8saT1udWxsLHI9ITE7aT10LnN1YnNjcmliZShqdChlLHZvaWQgMCx2b2lkIDAscz0+e289Z2kobihzLGZvKG4pKHQpKSksaT8oaS51bnN1YnNjcmliZSgpLGk9bnVsbCxvLnN1YnNjcmliZShlKSk6cj0hMH0pKSxyJiYoaS51bnN1YnNjcmliZSgpLGk9bnVsbCxvLnN1YnNjcmliZShlKSl9KX1mdW5jdGlvbiBBVyhuLHQsZSxpLHIpe3JldHVybihvLHMpPT57bGV0IGE9ZSxsPXQsYz0wO28uc3Vic2NyaWJlKGp0KHMsdT0+e2xldCBkPWMrKztsPWE/bihsLHUsZCk6KGE9ITAsdSksaSYmcy5uZXh0KGwpfSxyJiYoKCk9PnthJiZzLm5leHQobCkscy5jb21wbGV0ZSgpfSkpKX19ZnVuY3Rpb24gd04oLi4ubil7bGV0IHQ9dnUobik7cmV0dXJuIHQ/ZnVuY3Rpb24oLi4ubil7cmV0dXJuIHBOKG4pfSh3TiguLi5uKSxCcCh0KSk6ZW4oKGUsaSk9Pnt4TihbZSwuLi5tMShuKV0pKGkpfSl9ZnVuY3Rpb24gZnIoLi4ubil7cmV0dXJuIHdOKC4uLm4pfWZ1bmN0aW9uIEhyKG4sdD1rZCl7cmV0dXJuIGVuKChlLGkpPT57bGV0IHI9bnVsbCxvPW51bGwscz1udWxsLGE9KCk9PntpZihyKXtyLnVuc3Vic2NyaWJlKCkscj1udWxsO2xldCBjPW87bz1udWxsLGkubmV4dChjKX19O2Z1bmN0aW9uIGwoKXtsZXQgYz1zK24sdT10Lm5vdygpO2lmKHU8YylyZXR1cm4gcj10aGlzLnNjaGVkdWxlKHZvaWQgMCxjLXUpLHZvaWQgaS5hZGQocik7YSgpfWUuc3Vic2NyaWJlKGp0KGksYz0+e289YyxzPXQubm93KCkscnx8KHI9dC5zY2hlZHVsZShsLG4pLGkuYWRkKHIpKX0sKCk9PnthKCksaS5jb21wbGV0ZSgpfSx2b2lkIDAsKCk9PntvPXI9bnVsbH0pKX0pfWZ1bmN0aW9uIF8xKG4pe3JldHVybiBlbigodCxlKT0+e2xldCBpPSExO3Quc3Vic2NyaWJlKGp0KGUscj0+e2k9ITAsZS5uZXh0KHIpfSwoKT0+e2l8fGUubmV4dChuKSxlLmNvbXBsZXRlKCl9KSl9KX1mdW5jdGlvbiBRdChuKXtyZXR1cm4gbjw9MD8oKT0+ZW86ZW4oKHQsZSk9PntsZXQgaT0wO3Quc3Vic2NyaWJlKGp0KGUscj0+eysraTw9biYmKGUubmV4dChyKSxuPD1pJiZlLmNvbXBsZXRlKCkpfSkpfSl9ZnVuY3Rpb24gbHgoKXtyZXR1cm4gZW4oKG4sdCk9PntuLnN1YnNjcmliZShqdCh0LE1jKSl9KX1mdW5jdGlvbiB2MChuLHQpe3JldHVybiB0P2U9PlZwKHQucGlwZShRdCgxKSxseCgpKSxlLnBpcGUodjAobikpKTp4bigoZSxpKT0+bihlLGkpLnBpcGUoUXQoMSksZnVuY3Rpb24obil7cmV0dXJuIEwoKCk9Pm4pfShlKSkpfWZ1bmN0aW9uIE9sKG4sdD1rZCl7bGV0IGU9S2Eobix0KTtyZXR1cm4gdjAoKCk9PmUpfWZ1bmN0aW9uIHlpKG4sdD1tcyl7cmV0dXJuIG49bj8/TzBlLGVuKChlLGkpPT57bGV0IHIsbz0hMDtlLnN1YnNjcmliZShqdChpLHM9PntsZXQgYT10KHMpOyhvfHwhbihyLGEpKSYmKG89ITEscj1hLGkubmV4dChzKSl9KSl9KX1mdW5jdGlvbiBPMGUobix0KXtyZXR1cm4gbj09PXR9ZnVuY3Rpb24gdjEobj1rMGUpe3JldHVybiBlbigodCxlKT0+e2xldCBpPSExO3Quc3Vic2NyaWJlKGp0KGUscj0+e2k9ITAsZS5uZXh0KHIpfSwoKT0+aT9lLmNvbXBsZXRlKCk6ZS5lcnJvcihuKCkpKSl9KX1mdW5jdGlvbiBrMGUoKXtyZXR1cm4gbmV3IF8wfWZ1bmN0aW9uIHkxKG4sdCl7cmV0dXJuIHQ/ZT0+ZS5waXBlKHkxKChpLHIpPT5naShuKGkscikpLnBpcGUoTCgobyxzKT0+dChpLG8scixzKSkpKSk6ZW4oKGUsaSk9PntsZXQgcj0wLG89bnVsbCxzPSExO2Uuc3Vic2NyaWJlKGp0KGksYT0+e298fChvPWp0KGksdm9pZCAwLCgpPT57bz1udWxsLHMmJmkuY29tcGxldGUoKX0pLGdpKG4oYSxyKyspKS5zdWJzY3JpYmUobykpfSwoKT0+e3M9ITAsIW8mJmkuY29tcGxldGUoKX0pKX0pfWZ1bmN0aW9uIHgxKG4sdCxlLGkpe3JldHVybiBlbigocixvKT0+e2xldCBzO3QmJiJmdW5jdGlvbiIhPXR5cGVvZiB0Pyh7ZHVyYXRpb246ZSxlbGVtZW50OnMsY29ubmVjdG9yOml9PXQpOnM9dDtsZXQgYT1uZXcgTWFwLGw9Zj0+e2EuZm9yRWFjaChmKSxmKG8pfSxjPWY9PmwobT0+bS5lcnJvcihmKSksdT0wLGQ9ITEscD1uZXcgbngobyxmPT57dHJ5e2xldCBtPW4oZikseD1hLmdldChtKTtpZigheCl7YS5zZXQobSx4PWk/aSgpOm5ldyBrZSk7bGV0IGc9ZnVuY3Rpb24oZixtKXtsZXQgeD1uZXcgdW4oZz0+e3UrKztsZXQgYj1tLnN1YnNjcmliZShnKTtyZXR1cm4oKT0+e2IudW5zdWJzY3JpYmUoKSwwPT0tLXUmJmQmJnAudW5zdWJzY3JpYmUoKX19KTtyZXR1cm4geC5rZXk9Zix4fShtLHgpO2lmKG8ubmV4dChnKSxlKXtsZXQgYj1qdCh4LCgpPT57eC5jb21wbGV0ZSgpLGI/LnVuc3Vic2NyaWJlKCl9LHZvaWQgMCx2b2lkIDAsKCk9PmEuZGVsZXRlKG0pKTtwLmFkZChnaShlKGcpKS5zdWJzY3JpYmUoYikpfX14Lm5leHQocz9zKGYpOmYpfWNhdGNoKG0pe2MobSl9fSwoKT0+bChmPT5mLmNvbXBsZXRlKCkpLGMsKCk9PmEuY2xlYXIoKSwoKT0+KGQ9ITAsMD09PXUpKTtyLnN1YnNjcmliZShwKX0pfWZ1bmN0aW9uIFBXKG4pe3JldHVybiBuPD0wPygpPT5lbzplbigodCxlKT0+e2xldCBpPVtdO3Quc3Vic2NyaWJlKGp0KGUscj0+e2kucHVzaChyKSxuPGkubGVuZ3RoJiZpLnNoaWZ0KCl9LCgpPT57Zm9yKGxldCByIG9mIGkpZS5uZXh0KHIpO2UuY29tcGxldGUoKX0sdm9pZCAwLCgpPT57aT1udWxsfSkpfSl9ZnVuY3Rpb24geTAoKXtyZXR1cm4gZW4oKG4sdCk9PntsZXQgZSxpPSExO24uc3Vic2NyaWJlKGp0KHQscj0+e2xldCBvPWU7ZT1yLGkmJnQubmV4dChbbyxyXSksaT0hMH0pKX0pfWZ1bmN0aW9uIFRzKG49e30pe2xldHtjb25uZWN0b3I6dD0oKCk9Pm5ldyBrZSkscmVzZXRPbkVycm9yOmU9ITAscmVzZXRPbkNvbXBsZXRlOmk9ITAscmVzZXRPblJlZkNvdW50WmVybzpyPSEwfT1uO3JldHVybiBvPT57bGV0IHMsYSxsLGM9MCx1PSExLGQ9ITEscD0oKT0+e2E/LnVuc3Vic2NyaWJlKCksYT12b2lkIDB9LGg9KCk9PntwKCkscz1sPXZvaWQgMCx1PWQ9ITF9LGY9KCk9PntsZXQgbT1zO2goKSxtPy51bnN1YnNjcmliZSgpfTtyZXR1cm4gZW4oKG0seCk9PntjKyssIWQmJiF1JiZwKCk7bGV0IGc9bD1sPz90KCk7eC5hZGQoKCk9PntjLS0sMD09PWMmJiFkJiYhdSYmKGE9Uk4oZixyKSl9KSxnLnN1YnNjcmliZSh4KSwhcyYmYz4wJiYocz1uZXcgT2Qoe25leHQ6Yj0+Zy5uZXh0KGIpLGVycm9yOmI9PntkPSEwLHAoKSxhPVJOKGgsZSxiKSxnLmVycm9yKGIpfSxjb21wbGV0ZTooKT0+e3U9ITAscCgpLGE9Uk4oaCxpKSxnLmNvbXBsZXRlKCl9fSksZ2kobSkuc3Vic2NyaWJlKHMpKX0pKG8pfX1mdW5jdGlvbiBSTihuLHQsLi4uZSl7aWYoITA9PT10KXJldHVybiB2b2lkIG4oKTtpZighMT09PXQpcmV0dXJuO2xldCBpPW5ldyBPZCh7bmV4dDooKT0+e2kudW5zdWJzY3JpYmUoKSxuKCl9fSk7cmV0dXJuIHQoLi4uZSkuc3Vic2NyaWJlKGkpfWZ1bmN0aW9uIE1hKG4sdCxlKXtsZXQgaSxyPSExO3JldHVybiBuJiYib2JqZWN0Ij09dHlwZW9mIG4/KHtidWZmZXJTaXplOmk9MS8wLHdpbmRvd1RpbWU6dD0xLzAscmVmQ291bnQ6cj0hMSxzY2hlZHVsZXI6ZX09bik6aT1uPz8xLzAsVHMoe2Nvbm5lY3RvcjooKT0+bmV3IExmKGksdCxlKSxyZXNldE9uRXJyb3I6ITAscmVzZXRPbkNvbXBsZXRlOiExLHJlc2V0T25SZWZDb3VudFplcm86cn0pfWZ1bmN0aW9uIFphKG4pe3JldHVybiBZZSgodCxlKT0+bjw9ZSl9ZnVuY3Rpb24gem4oLi4ubil7bGV0IHQ9eXUobik7cmV0dXJuIGVuKChlLGkpPT57KHQ/VnAobixlLHQpOlZwKG4sZSkpLnN1YnNjcmliZShpKX0pfWZ1bmN0aW9uIHVpKG4sdCl7cmV0dXJuIGVuKChlLGkpPT57bGV0IHI9bnVsbCxvPTAscz0hMSxhPSgpPT5zJiYhciYmaS5jb21wbGV0ZSgpO2Uuc3Vic2NyaWJlKGp0KGksbD0+e3I/LnVuc3Vic2NyaWJlKCk7bGV0IGM9MCx1PW8rKztnaShuKGwsdSkpLnN1YnNjcmliZShyPWp0KGksZD0+aS5uZXh0KHQ/dChsLGQsdSxjKyspOmQpLCgpPT57cj1udWxsLGEoKX0pKX0sKCk9PntzPSEwLGEoKX0pKX0pfWZ1bmN0aW9uIHN0KG4pe3JldHVybiBlbigodCxlKT0+e2dpKG4pLnN1YnNjcmliZShqdChlLCgpPT5lLmNvbXBsZXRlKCksTWMpKSwhZS5jbG9zZWQmJnQuc3Vic2NyaWJlKGUpfSl9ZnVuY3Rpb24gY3gobix0PSExKXtyZXR1cm4gZW4oKGUsaSk9PntsZXQgcj0wO2Uuc3Vic2NyaWJlKGp0KGksbz0+e2xldCBzPW4obyxyKyspOyhzfHx0KSYmaS5uZXh0KG8pLCFzJiZpLmNvbXBsZXRlKCl9KSl9KX1mdW5jdGlvbiBrdChuLHQsZSl7bGV0IGk9RW4obil8fHR8fGU/e25leHQ6bixlcnJvcjp0LGNvbXBsZXRlOmV9Om47cmV0dXJuIGk/ZW4oKHIsbyk9Pnt2YXIgcztudWxsPT09KHM9aS5zdWJzY3JpYmUpfHx2b2lkIDA9PT1zfHxzLmNhbGwoaSk7bGV0IGE9ITA7ci5zdWJzY3JpYmUoanQobyxsPT57dmFyIGM7bnVsbD09PShjPWkubmV4dCl8fHZvaWQgMD09PWN8fGMuY2FsbChpLGwpLG8ubmV4dChsKX0sKCk9Pnt2YXIgbDthPSExLG51bGw9PT0obD1pLmNvbXBsZXRlKXx8dm9pZCAwPT09bHx8bC5jYWxsKGkpLG8uY29tcGxldGUoKX0sbD0+e3ZhciBjO2E9ITEsbnVsbD09PShjPWkuZXJyb3IpfHx2b2lkIDA9PT1jfHxjLmNhbGwoaSxsKSxvLmVycm9yKGwpfSwoKT0+e3ZhciBsLGM7YSYmKG51bGw9PT0obD1pLnVuc3Vic2NyaWJlKXx8dm9pZCAwPT09bHx8bC5jYWxsKGkpKSxudWxsPT09KGM9aS5maW5hbGl6ZSl8fHZvaWQgMD09PWN8fGMuY2FsbChpKX0pKX0pOm1zfXZhciBPTj17bGVhZGluZzohMCx0cmFpbGluZzohMX07ZnVuY3Rpb24gYjAobix0PWtkLGU9T04pe2xldCBpPUthKG4sdCk7cmV0dXJuIGZ1bmN0aW9uKG4sdD1PTil7cmV0dXJuIGVuKChlLGkpPT57bGV0e2xlYWRpbmc6cix0cmFpbGluZzpvfT10LHM9ITEsYT1udWxsLGw9bnVsbCxjPSExLHU9KCk9PntsPy51bnN1YnNjcmliZSgpLGw9bnVsbCxvJiYoaCgpLGMmJmkuY29tcGxldGUoKSl9LGQ9KCk9PntsPW51bGwsYyYmaS5jb21wbGV0ZSgpfSxwPWY9Pmw9Z2kobihmKSkuc3Vic2NyaWJlKGp0KGksdSxkKSksaD0oKT0+e2lmKHMpe3M9ITE7bGV0IGY9YTthPW51bGwsaS5uZXh0KGYpLCFjJiZwKGYpfX07ZS5zdWJzY3JpYmUoanQoaSxmPT57cz0hMCxhPWYsKCFsfHxsLmNsb3NlZCkmJihyP2goKTpwKGYpKX0sKCk9PntjPSEwLCghKG8mJnMmJmwpfHxsLmNsb3NlZCkmJmkuY29tcGxldGUoKX0pKX0pfSgoKT0+aSxlKX1mdW5jdGlvbiBXdCguLi5uKXtsZXQgdD12dShuKTtyZXR1cm4gZW4oKGUsaSk9PntsZXQgcj1uLmxlbmd0aCxvPW5ldyBBcnJheShyKSxzPW4ubWFwKCgpPT4hMSksYT0hMTtmb3IobGV0IGw9MDtsPHI7bCsrKWdpKG5bbF0pLnN1YnNjcmliZShqdChpLGM9PntvW2xdPWMsIWEmJiFzW2xdJiYoc1tsXT0hMCwoYT1zLmV2ZXJ5KG1zKSkmJihzPW51bGwpKX0sTWMpKTtlLnN1YnNjcmliZShqdChpLGw9PntpZihhKXtsZXQgYz1bbCwuLi5vXTtpLm5leHQodD90KC4uLmMpOmMpfX0pKX0pfWZ1bmN0aW9uIG1yKG4pe2ZvcihsZXQgdCBpbiBuKWlmKG5bdF09PT1tcilyZXR1cm4gdDt0aHJvdyBFcnJvcigiQ291bGQgbm90IGZpbmQgcmVuYW1lZCBwcm9wZXJ0eSBvbiB0YXJnZXQgb2JqZWN0LiIpfWZ1bmN0aW9uIGtOKG4sdCl7Zm9yKGxldCBlIGluIHQpdC5oYXNPd25Qcm9wZXJ0eShlKSYmIW4uaGFzT3duUHJvcGVydHkoZSkmJihuW2VdPXRbZV0pfWZ1bmN0aW9uIFRvKG4pe2lmKCJzdHJpbmciPT10eXBlb2YgbilyZXR1cm4gbjtpZihBcnJheS5pc0FycmF5KG4pKXJldHVybiJbIituLm1hcChUbykuam9pbigiLCAiKSsiXSI7aWYobnVsbD09bilyZXR1cm4iIituO2lmKG4ub3ZlcnJpZGRlbk5hbWUpcmV0dXJuYCR7bi5vdmVycmlkZGVuTmFtZX1gO2lmKG4ubmFtZSlyZXR1cm5gJHtuLm5hbWV9YDtsZXQgdD1uLnRvU3RyaW5nKCk7aWYobnVsbD09dClyZXR1cm4iIit0O2xldCBlPXQuaW5kZXhPZigiXG4iKTtyZXR1cm4tMT09PWU/dDp0LnN1YnN0cmluZygwLGUpfWZ1bmN0aW9uIHRMKG4sdCl7cmV0dXJuIG51bGw9PW58fCIiPT09bj9udWxsPT09dD8iIjp0Om51bGw9PXR8fCIiPT09dD9uOm4rIiAiK3R9dmFyIEYwZT1tcih7X19mb3J3YXJkX3JlZl9fOm1yfSk7ZnVuY3Rpb24gSm4obil7cmV0dXJuIG4uX19mb3J3YXJkX3JlZl9fPUpuLG4udG9TdHJpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gVG8odGhpcygpKX0sbn1mdW5jdGlvbiBLaShuKXtyZXR1cm4gRTcobik/bigpOm59ZnVuY3Rpb24gRTcobil7cmV0dXJuImZ1bmN0aW9uIj09dHlwZW9mIG4mJm4uaGFzT3duUHJvcGVydHkoRjBlKSYmbi5fX2ZvcndhcmRfcmVmX189PT1Kbn12YXIgQXQ9Y2xhc3MgZXh0ZW5kcyBFcnJvcntjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKGZ1bmN0aW9uKG4sdCl7cmV0dXJuYE5HMCR7TWF0aC5hYnMobil9JHt0PyI6ICIrdC50cmltKCk6IiJ9YH0odCxlKSksdGhpcy5jb2RlPXR9fTtmdW5jdGlvbiBLbihuKXtyZXR1cm4ic3RyaW5nIj09dHlwZW9mIG4/bjpudWxsPT1uPyIiOlN0cmluZyhuKX1mdW5jdGlvbiBvMyhuKXtyZXR1cm4iZnVuY3Rpb24iPT10eXBlb2Ygbj9uLm5hbWV8fG4udG9TdHJpbmcoKToib2JqZWN0Ij09dHlwZW9mIG4mJm51bGwhPW4mJiJmdW5jdGlvbiI9PXR5cGVvZiBuLnR5cGU/bi50eXBlLm5hbWV8fG4udHlwZS50b1N0cmluZygpOktuKG4pfWZ1bmN0aW9uIHMzKG4sdCl7dGhyb3cgbmV3IEF0KC0yMDEsITEpfWZ1bmN0aW9uIFQ3KG4sdCxlLGkpe3Rocm93IG5ldyBFcnJvcihgQVNTRVJUSU9OIEVSUk9SOiAke259YCsobnVsbD09aT8iIjpgIFtFeHBlY3RlZD0+ICR7ZX0gJHtpfSAke3R9IDw9QWN0dWFsXWApKX1mdW5jdGlvbiB5ZShuKXtyZXR1cm57dG9rZW46bi50b2tlbixwcm92aWRlZEluOm4ucHJvdmlkZWRJbnx8bnVsbCxmYWN0b3J5Om4uZmFjdG9yeSx2YWx1ZTp2b2lkIDB9fWZ1bmN0aW9uIFYobil7cmV0dXJue3Byb3ZpZGVyczpuLnByb3ZpZGVyc3x8W10saW1wb3J0czpuLmltcG9ydHN8fFtdfX1mdW5jdGlvbiBhMyhuKXtyZXR1cm4gT1cobixMMSl8fE9XKG4sRDcpfWZ1bmN0aW9uIE9XKG4sdCl7cmV0dXJuIG4uaGFzT3duUHJvcGVydHkodCk/blt0XTpudWxsfWZ1bmN0aW9uIGtXKG4pe3JldHVybiBuJiYobi5oYXNPd25Qcm9wZXJ0eShuTCl8fG4uaGFzT3duUHJvcGVydHkoVTBlKSk/bltuTF06bnVsbH12YXIgaUwsTDE9bXIoeyJcdTAyNzVwcm92Ijptcn0pLG5MPW1yKHsiXHUwMjc1aW5qIjptcn0pLEQ3PW1yKHtuZ0luamVjdGFibGVEZWY6bXJ9KSxVMGU9bXIoe25nSW5qZWN0b3JEZWY6bXJ9KSxkaT0oKCk9PntyZXR1cm4obj1kaXx8KGRpPXt9KSlbbi5EZWZhdWx0PTBdPSJEZWZhdWx0IixuW24uSG9zdD0xXT0iSG9zdCIsbltuLlNlbGY9Ml09IlNlbGYiLG5bbi5Ta2lwU2VsZj00XT0iU2tpcFNlbGYiLG5bbi5PcHRpb25hbD04XT0iT3B0aW9uYWwiLGRpO3ZhciBufSkoKTtmdW5jdGlvbiBrbChuKXtsZXQgdD1pTDtyZXR1cm4gaUw9bix0fWZ1bmN0aW9uIEE3KG4sdCxlKXtsZXQgaT1hMyhuKTtyZXR1cm4gaSYmInJvb3QiPT1pLnByb3ZpZGVkSW4/dm9pZCAwPT09aS52YWx1ZT9pLnZhbHVlPWkuZmFjdG9yeSgpOmkudmFsdWU6ZSZkaS5PcHRpb25hbD9udWxsOnZvaWQgMCE9PXQ/dDp2b2lkIHMzKFRvKG4pKX1mdW5jdGlvbiBaZihuKXtyZXR1cm57dG9TdHJpbmc6bn0udG9TdHJpbmcoKX12YXIgcHg9KCgpPT57cmV0dXJuKG49cHh8fChweD17fSkpW24uT25QdXNoPTBdPSJPblB1c2giLG5bbi5EZWZhdWx0PTFdPSJEZWZhdWx0IixweDt2YXIgbn0pKCksSmE9KCgpPT57cmV0dXJuKG49SmF8fChKYT17fSkpW24uRW11bGF0ZWQ9MF09IkVtdWxhdGVkIixuW24uTm9uZT0yXT0iTm9uZSIsbltuLlNoYWRvd0RvbT0zXT0iU2hhZG93RG9tIixKYTt2YXIgbn0pKCksdG89KCgpPT50eXBlb2YgZ2xvYmFsVGhpczwidSImJmdsb2JhbFRoaXN8fHR5cGVvZiBnbG9iYWw8InUiJiZnbG9iYWx8fHR5cGVvZiB3aW5kb3c8InUiJiZ3aW5kb3d8fHR5cGVvZiBzZWxmPCJ1IiYmdHlwZW9mIFdvcmtlckdsb2JhbFNjb3BlPCJ1IiYmc2VsZiBpbnN0YW5jZW9mIFdvcmtlckdsb2JhbFNjb3BlJiZzZWxmKSgpLEEwPXt9LFFpPVtdLGNUPW1yKHsiXHUwMjc1Y21wIjptcn0pLGwzPW1yKHsiXHUwMjc1ZGlyIjptcn0pLGMzPW1yKHsiXHUwMjc1cGlwZSI6bXJ9KSxJNz1tcih7Ilx1MDI3NW1vZCI6bXJ9KSxOZD1tcih7Ilx1MDI3NWZhYyI6bXJ9KSxoeD1tcih7X19OR19FTEVNRU5UX0lEX186bXJ9KSxqMGU9MDtmdW5jdGlvbiBSKG4pe3JldHVybiBaZigoKT0+e2xldCBlPSEwPT09bi5zdGFuZGFsb25lLGk9e30scj17dHlwZTpuLnR5cGUscHJvdmlkZXJzUmVzb2x2ZXI6bnVsbCxkZWNsczpuLmRlY2xzLHZhcnM6bi52YXJzLGZhY3Rvcnk6bnVsbCx0ZW1wbGF0ZTpuLnRlbXBsYXRlfHxudWxsLGNvbnN0czpuLmNvbnN0c3x8bnVsbCxuZ0NvbnRlbnRTZWxlY3RvcnM6bi5uZ0NvbnRlbnRTZWxlY3RvcnMsaG9zdEJpbmRpbmdzOm4uaG9zdEJpbmRpbmdzfHxudWxsLGhvc3RWYXJzOm4uaG9zdFZhcnN8fDAsaG9zdEF0dHJzOm4uaG9zdEF0dHJzfHxudWxsLGNvbnRlbnRRdWVyaWVzOm4uY29udGVudFF1ZXJpZXN8fG51bGwsZGVjbGFyZWRJbnB1dHM6aSxpbnB1dHM6bnVsbCxvdXRwdXRzOm51bGwsZXhwb3J0QXM6bi5leHBvcnRBc3x8bnVsbCxvblB1c2g6bi5jaGFuZ2VEZXRlY3Rpb249PT1weC5PblB1c2gsZGlyZWN0aXZlRGVmczpudWxsLHBpcGVEZWZzOm51bGwsc3RhbmRhbG9uZTplLGRlcGVuZGVuY2llczplJiZuLmRlcGVuZGVuY2llc3x8bnVsbCxnZXRTdGFuZGFsb25lSW5qZWN0b3I6bnVsbCxzZWxlY3RvcnM6bi5zZWxlY3RvcnN8fFFpLHZpZXdRdWVyeTpuLnZpZXdRdWVyeXx8bnVsbCxmZWF0dXJlczpuLmZlYXR1cmVzfHxudWxsLGRhdGE6bi5kYXRhfHx7fSxlbmNhcHN1bGF0aW9uOm4uZW5jYXBzdWxhdGlvbnx8SmEuRW11bGF0ZWQsaWQ6ImMiK2owZSsrLHN0eWxlczpuLnN0eWxlc3x8UWksXzpudWxsLHNldElucHV0Om51bGwsc2NoZW1hczpuLnNjaGVtYXN8fG51bGwsdFZpZXc6bnVsbH0sbz1uLmRlcGVuZGVuY2llcyxzPW4uZmVhdHVyZXM7cmV0dXJuIHIuaW5wdXRzPU5XKG4uaW5wdXRzLGkpLHIub3V0cHV0cz1OVyhuLm91dHB1dHMpLHMmJnMuZm9yRWFjaChhPT5hKHIpKSxyLmRpcmVjdGl2ZURlZnM9bz8oKT0+KCJmdW5jdGlvbiI9PXR5cGVvZiBvP28oKTpvKS5tYXAoUDcpLmZpbHRlcihGVyk6bnVsbCxyLnBpcGVEZWZzPW8/KCk9PigiZnVuY3Rpb24iPT10eXBlb2Ygbz9vKCk6bykubWFwKExkKS5maWx0ZXIoRlcpOm51bGwscn0pfWZ1bmN0aW9uIE54KG4sdCxlKXtsZXQgaT1uLlx1MDI3NWNtcDtpLmRpcmVjdGl2ZURlZnM9KCk9PigiZnVuY3Rpb24iPT10eXBlb2YgdD90KCk6dCkubWFwKFA3KSxpLnBpcGVEZWZzPSgpPT4oImZ1bmN0aW9uIj09dHlwZW9mIGU/ZSgpOmUpLm1hcChMZCl9ZnVuY3Rpb24gUDcobil7cmV0dXJuIE5sKG4pfHxHZihuKX1mdW5jdGlvbiBGVyhuKXtyZXR1cm4gbnVsbCE9PW59ZnVuY3Rpb24gSChuKXtyZXR1cm4gWmYoKCk9Pih7dHlwZTpuLnR5cGUsYm9vdHN0cmFwOm4uYm9vdHN0cmFwfHxRaSxkZWNsYXJhdGlvbnM6bi5kZWNsYXJhdGlvbnN8fFFpLGltcG9ydHM6bi5pbXBvcnRzfHxRaSxleHBvcnRzOm4uZXhwb3J0c3x8UWksdHJhbnNpdGl2ZUNvbXBpbGVTY29wZXM6bnVsbCxzY2hlbWFzOm4uc2NoZW1hc3x8bnVsbCxpZDpuLmlkfHxudWxsfSkpfWZ1bmN0aW9uIE5XKG4sdCl7aWYobnVsbD09bilyZXR1cm4gQTA7bGV0IGU9e307Zm9yKGxldCBpIGluIG4paWYobi5oYXNPd25Qcm9wZXJ0eShpKSl7bGV0IHI9bltpXSxvPXI7QXJyYXkuaXNBcnJheShyKSYmKG89clsxXSxyPXJbMF0pLGVbcl09aSx0JiYodFtyXT1vKX1yZXR1cm4gZX12YXIgSGU9UjtmdW5jdGlvbiBCMChuKXtyZXR1cm57dHlwZTpuLnR5cGUsbmFtZTpuLm5hbWUsZmFjdG9yeTpudWxsLHB1cmU6ITEhPT1uLnB1cmUsc3RhbmRhbG9uZTohMD09PW4uc3RhbmRhbG9uZSxvbkRlc3Ryb3k6bi50eXBlLnByb3RvdHlwZS5uZ09uRGVzdHJveXx8bnVsbH19ZnVuY3Rpb24gTmwobil7cmV0dXJuIG5bY1RdfHxudWxsfWZ1bmN0aW9uIEdmKG4pe3JldHVybiBuW2wzXXx8bnVsbH1mdW5jdGlvbiBMZChuKXtyZXR1cm4gbltjM118fG51bGx9ZnVuY3Rpb24gUjcobil7bGV0IHQ9Tmwobil8fEdmKG4pfHxMZChuKTtyZXR1cm4gbnVsbCE9PXQmJnQuc3RhbmRhbG9uZX1mdW5jdGlvbiBJMChuLHQpe2xldCBlPW5bSTddfHxudWxsO2lmKCFlJiYhMD09PXQpdGhyb3cgbmV3IEVycm9yKGBUeXBlICR7VG8obil9IGRvZXMgbm90IGhhdmUgJ1x1MDI3NW1vZCcgcHJvcGVydHkuYCk7cmV0dXJuIGV9ZnVuY3Rpb24gemYobil7cmV0dXJuIEFycmF5LmlzQXJyYXkobikmJiJvYmplY3QiPT10eXBlb2YgblsxXX1mdW5jdGlvbiBWZChuKXtyZXR1cm4gQXJyYXkuaXNBcnJheShuKSYmITA9PT1uWzFdfWZ1bmN0aW9uIHAzKG4pe3JldHVybiAwIT0oOCZuLmZsYWdzKX1mdW5jdGlvbiBoMyhuKXtyZXR1cm4gMj09KDImbi5mbGFncyl9ZnVuY3Rpb24gZFQobil7cmV0dXJuIDE9PSgxJm4uZmxhZ3MpfWZ1bmN0aW9uIEFjKG4pe3JldHVybiBudWxsIT09bi50ZW1wbGF0ZX1mdW5jdGlvbiBxMGUobil7cmV0dXJuIDAhPSgyNTYmblsyXSl9ZnVuY3Rpb24gV2Yobix0KXtyZXR1cm4gbi5oYXNPd25Qcm9wZXJ0eShOZCk/bltOZF06bnVsbH1mdW5jdGlvbiBGdCgpe3JldHVybiBGN31mdW5jdGlvbiBGNyhuKXtyZXR1cm4gbi50eXBlLnByb3RvdHlwZS5uZ09uQ2hhbmdlcyYmKG4uc2V0SW5wdXQ9WDBlKSxZMGV9ZnVuY3Rpb24gWTBlKCl7bGV0IG49TDcodGhpcyksdD1uPy5jdXJyZW50O2lmKHQpe2xldCBlPW4ucHJldmlvdXM7aWYoZT09PUEwKW4ucHJldmlvdXM9dDtlbHNlIGZvcihsZXQgaSBpbiB0KWVbaV09dFtpXTtuLmN1cnJlbnQ9bnVsbCx0aGlzLm5nT25DaGFuZ2VzKHQpfX1mdW5jdGlvbiBYMGUobix0LGUsaSl7bGV0IHI9TDcobil8fGZ1bmN0aW9uKG4sdCl7cmV0dXJuIG5bTjddPXR9KG4se3ByZXZpb3VzOkEwLGN1cnJlbnQ6bnVsbH0pLG89ci5jdXJyZW50fHwoci5jdXJyZW50PXt9KSxzPXIucHJldmlvdXMsYT10aGlzLmRlY2xhcmVkSW5wdXRzW2VdLGw9c1thXTtvW2FdPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy5wcmV2aW91c1ZhbHVlPXQsdGhpcy5jdXJyZW50VmFsdWU9ZSx0aGlzLmZpcnN0Q2hhbmdlPWl9aXNGaXJzdENoYW5nZSgpe3JldHVybiB0aGlzLmZpcnN0Q2hhbmdlfX0obCYmbC5jdXJyZW50VmFsdWUsdCxzPT09QTApLG5baV09dH1GdC5uZ0luaGVyaXQ9ITA7dmFyIE43PSJfX25nU2ltcGxlQ2hhbmdlc19fIjtmdW5jdGlvbiBMNyhuKXtyZXR1cm4gbltON118fG51bGx9ZnVuY3Rpb24gJGEobil7Zm9yKDtBcnJheS5pc0FycmF5KG4pOyluPW5bMF07cmV0dXJuIG59ZnVuY3Rpb24gcFQobix0KXtyZXR1cm4gJGEodFtuXSl9ZnVuY3Rpb24gVWwobix0KXtyZXR1cm4gJGEodFtuLmluZGV4XSl9ZnVuY3Rpb24gSDcobix0KXtyZXR1cm4gbi5kYXRhW3RdfWZ1bmN0aW9uIEgwKG4sdCl7cmV0dXJuIG5bdF19ZnVuY3Rpb24gcXAobix0KXtsZXQgZT10W25dO3JldHVybiB6ZihlKT9lOmVbMF19ZnVuY3Rpb24gVTEobil7cmV0dXJuIDY0PT0oNjQmblsyXSl9ZnVuY3Rpb24gVXAobix0KXtyZXR1cm4gbnVsbD09dD9udWxsOm5bdF19ZnVuY3Rpb24gVTcobil7blsxOF09MH1mdW5jdGlvbiBmMyhuLHQpe25bNV0rPXQ7bGV0IGU9bixpPW5bM107Zm9yKDtudWxsIT09aSYmKDE9PT10JiYxPT09ZVs1XXx8LTE9PT10JiYwPT09ZVs1XSk7KWlbNV0rPXQsZT1pLGk9aVszXX12YXIgWm49e2xGcmFtZTpRNyhudWxsKSxiaW5kaW5nc0VuYWJsZWQ6ITB9O2Z1bmN0aW9uIHo3KCl7cmV0dXJuIFpuLmJpbmRpbmdzRW5hYmxlZH1mdW5jdGlvbiBydCgpe3JldHVybiBabi5sRnJhbWUubFZpZXd9ZnVuY3Rpb24gRmkoKXtyZXR1cm4gWm4ubEZyYW1lLnRWaWV3fWZ1bmN0aW9uIG9lKG4pe3JldHVybiBabi5sRnJhbWUuY29udGV4dExWaWV3PW4sbls4XX1mdW5jdGlvbiBzZShuKXtyZXR1cm4gWm4ubEZyYW1lLmNvbnRleHRMVmlldz1udWxsLG59ZnVuY3Rpb24gem8oKXtsZXQgbj1qNygpO2Zvcig7bnVsbCE9PW4mJjY0PT09bi50eXBlOyluPW4ucGFyZW50O3JldHVybiBufWZ1bmN0aW9uIGo3KCl7cmV0dXJuIFpuLmxGcmFtZS5jdXJyZW50VE5vZGV9ZnVuY3Rpb24gd3goKXtsZXQgbj1abi5sRnJhbWUsdD1uLmN1cnJlbnRUTm9kZTtyZXR1cm4gbi5pc1BhcmVudD90OnQucGFyZW50fWZ1bmN0aW9uIE11KG4sdCl7bGV0IGU9Wm4ubEZyYW1lO2UuY3VycmVudFROb2RlPW4sZS5pc1BhcmVudD10fWZ1bmN0aW9uIG0zKCl7cmV0dXJuIFpuLmxGcmFtZS5pc1BhcmVudH1mdW5jdGlvbiBnMygpe1puLmxGcmFtZS5pc1BhcmVudD0hMX1mdW5jdGlvbiBLcygpe2xldCBuPVpuLmxGcmFtZSx0PW4uYmluZGluZ1Jvb3RJbmRleDtyZXR1cm4tMT09PXQmJih0PW4uYmluZGluZ1Jvb3RJbmRleD1uLnRWaWV3LmJpbmRpbmdTdGFydEluZGV4KSx0fWZ1bmN0aW9uIEhkKCl7cmV0dXJuIFpuLmxGcmFtZS5iaW5kaW5nSW5kZXh9ZnVuY3Rpb24gRzcobil7cmV0dXJuIFpuLmxGcmFtZS5iaW5kaW5nSW5kZXg9bn1mdW5jdGlvbiBVMCgpe3JldHVybiBabi5sRnJhbWUuYmluZGluZ0luZGV4Kyt9ZnVuY3Rpb24gVWQobil7bGV0IHQ9Wm4ubEZyYW1lLGU9dC5iaW5kaW5nSW5kZXg7cmV0dXJuIHQuYmluZGluZ0luZGV4PXQuYmluZGluZ0luZGV4K24sZX1mdW5jdGlvbiBXNyhuKXtabi5sRnJhbWUuaW5JMThuPW59ZnVuY3Rpb24gc19lKG4sdCl7bGV0IGU9Wm4ubEZyYW1lO2UuYmluZGluZ0luZGV4PWUuYmluZGluZ1Jvb3RJbmRleD1uLHNMKHQpfWZ1bmN0aW9uIHNMKG4pe1puLmxGcmFtZS5jdXJyZW50RGlyZWN0aXZlSW5kZXg9bn1mdW5jdGlvbiBfMyhuKXtsZXQgdD1abi5sRnJhbWUuY3VycmVudERpcmVjdGl2ZUluZGV4O3JldHVybi0xPT09dD9udWxsOm5bdF19ZnVuY3Rpb24gcTcoKXtyZXR1cm4gWm4ubEZyYW1lLmN1cnJlbnRRdWVyeUluZGV4fWZ1bmN0aW9uIHYzKG4pe1puLmxGcmFtZS5jdXJyZW50UXVlcnlJbmRleD1ufWZ1bmN0aW9uIGxfZShuKXtsZXQgdD1uWzFdO3JldHVybiAyPT09dC50eXBlP3QuZGVjbFROb2RlOjE9PT10LnR5cGU/bls2XTpudWxsfWZ1bmN0aW9uIFk3KG4sdCxlKXtpZihlJmRpLlNraXBTZWxmKXtsZXQgcj10LG89bjtmb3IoOyEocj1yLnBhcmVudCxudWxsIT09cnx8ZSZkaS5Ib3N0fHwocj1sX2UobyksbnVsbD09PXJ8fChvPW9bMTVdLDEwJnIudHlwZSkpKTspO2lmKG51bGw9PT1yKXJldHVybiExO3Q9cixuPW99bGV0IGk9Wm4ubEZyYW1lPVg3KCk7cmV0dXJuIGkuY3VycmVudFROb2RlPXQsaS5sVmlldz1uLCEwfWZ1bmN0aW9uIHkzKG4pe2xldCB0PVg3KCksZT1uWzFdO1puLmxGcmFtZT10LHQuY3VycmVudFROb2RlPWUuZmlyc3RDaGlsZCx0LmxWaWV3PW4sdC50Vmlldz1lLHQuY29udGV4dExWaWV3PW4sdC5iaW5kaW5nSW5kZXg9ZS5iaW5kaW5nU3RhcnRJbmRleCx0LmluSTE4bj0hMX1mdW5jdGlvbiBYNygpe2xldCBuPVpuLmxGcmFtZSx0PW51bGw9PT1uP251bGw6bi5jaGlsZDtyZXR1cm4gbnVsbD09PXQ/UTcobik6dH1mdW5jdGlvbiBRNyhuKXtsZXQgdD17Y3VycmVudFROb2RlOm51bGwsaXNQYXJlbnQ6ITAsbFZpZXc6bnVsbCx0VmlldzpudWxsLHNlbGVjdGVkSW5kZXg6LTEsY29udGV4dExWaWV3Om51bGwsZWxlbWVudERlcHRoQ291bnQ6MCxjdXJyZW50TmFtZXNwYWNlOm51bGwsY3VycmVudERpcmVjdGl2ZUluZGV4Oi0xLGJpbmRpbmdSb290SW5kZXg6LTEsYmluZGluZ0luZGV4Oi0xLGN1cnJlbnRRdWVyeUluZGV4OjAscGFyZW50Om4sY2hpbGQ6bnVsbCxpbkkxOG46ITF9O3JldHVybiBudWxsIT09biYmKG4uY2hpbGQ9dCksdH1mdW5jdGlvbiBLNygpe2xldCBuPVpuLmxGcmFtZTtyZXR1cm4gWm4ubEZyYW1lPW4ucGFyZW50LG4uY3VycmVudFROb2RlPW51bGwsbi5sVmlldz1udWxsLG59dmFyIFo3PUs3O2Z1bmN0aW9uIGIzKCl7bGV0IG49SzcoKTtuLmlzUGFyZW50PSEwLG4udFZpZXc9bnVsbCxuLnNlbGVjdGVkSW5kZXg9LTEsbi5jb250ZXh0TFZpZXc9bnVsbCxuLmVsZW1lbnREZXB0aENvdW50PTAsbi5jdXJyZW50RGlyZWN0aXZlSW5kZXg9LTEsbi5jdXJyZW50TmFtZXNwYWNlPW51bGwsbi5iaW5kaW5nUm9vdEluZGV4PS0xLG4uYmluZGluZ0luZGV4PS0xLG4uY3VycmVudFF1ZXJ5SW5kZXg9MH1mdW5jdGlvbiBacygpe3JldHVybiBabi5sRnJhbWUuc2VsZWN0ZWRJbmRleH1mdW5jdGlvbiB6cChuKXtabi5sRnJhbWUuc2VsZWN0ZWRJbmRleD1ufWZ1bmN0aW9uIG5vKCl7bGV0IG49Wm4ubEZyYW1lO3JldHVybiBINyhuLnRWaWV3LG4uc2VsZWN0ZWRJbmRleCl9ZnVuY3Rpb24gSW4oKXtabi5sRnJhbWUuY3VycmVudE5hbWVzcGFjZT0ic3ZnIn1mdW5jdGlvbiBKcygpe1puLmxGcmFtZS5jdXJyZW50TmFtZXNwYWNlPW51bGx9ZnVuY3Rpb24gaFQobix0KXtmb3IobGV0IGU9dC5kaXJlY3RpdmVTdGFydCxpPXQuZGlyZWN0aXZlRW5kO2U8aTtlKyspe2xldCBvPW4uZGF0YVtlXS50eXBlLnByb3RvdHlwZSx7bmdBZnRlckNvbnRlbnRJbml0OnMsbmdBZnRlckNvbnRlbnRDaGVja2VkOmEsbmdBZnRlclZpZXdJbml0OmwsbmdBZnRlclZpZXdDaGVja2VkOmMsbmdPbkRlc3Ryb3k6dX09bztzJiYobi5jb250ZW50SG9va3N8fChuLmNvbnRlbnRIb29rcz1bXSkpLnB1c2goLWUscyksYSYmKChuLmNvbnRlbnRIb29rc3x8KG4uY29udGVudEhvb2tzPVtdKSkucHVzaChlLGEpLChuLmNvbnRlbnRDaGVja0hvb2tzfHwobi5jb250ZW50Q2hlY2tIb29rcz1bXSkpLnB1c2goZSxhKSksbCYmKG4udmlld0hvb2tzfHwobi52aWV3SG9va3M9W10pKS5wdXNoKC1lLGwpLGMmJigobi52aWV3SG9va3N8fChuLnZpZXdIb29rcz1bXSkpLnB1c2goZSxjKSwobi52aWV3Q2hlY2tIb29rc3x8KG4udmlld0NoZWNrSG9va3M9W10pKS5wdXNoKGUsYykpLG51bGwhPXUmJihuLmRlc3Ryb3lIb29rc3x8KG4uZGVzdHJveUhvb2tzPVtdKSkucHVzaChlLHUpfX1mdW5jdGlvbiBEMShuLHQsZSl7Sjcobix0LDMsZSl9ZnVuY3Rpb24gQTEobix0LGUsaSl7KDMmblsyXSk9PT1lJiZKNyhuLHQsZSxpKX1mdW5jdGlvbiBGTihuLHQpe2xldCBlPW5bMl07KDMmZSk9PT10JiYoZSY9MjA0NyxlKz0xLG5bMl09ZSl9ZnVuY3Rpb24gSjcobix0LGUsaSl7bGV0IG89aT8/LTEscz10Lmxlbmd0aC0xLGE9MDtmb3IobGV0IGw9dm9pZCAwIT09aT82NTUzNSZuWzE4XTowO2w8cztsKyspaWYoIm51bWJlciI9PXR5cGVvZiB0W2wrMV0pe2lmKGE9dFtsXSxudWxsIT1pJiZhPj1pKWJyZWFrfWVsc2UgdFtsXTwwJiYoblsxOF0rPTY1NTM2KSwoYTxvfHwtMT09bykmJihtX2UobixlLHQsbCksblsxOF09KDQyOTQ5MDE3NjAmblsxOF0pK2wrMiksbCsrfWZ1bmN0aW9uIG1fZShuLHQsZSxpKXtsZXQgcj1lW2ldPDAsbz1lW2krMV0sYT1uW3I/LWVbaV06ZVtpXV07aWYocil7aWYoblsyXT4+MTE8blsxOF0+PjE2JiYoMyZuWzJdKT09PXQpe25bMl0rPTIwNDg7dHJ5e28uY2FsbChhKX1maW5hbGx5e319fWVsc2UgdHJ5e28uY2FsbChhKX1maW5hbGx5e319dmFyIHFmPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLmZhY3Rvcnk9dCx0aGlzLnJlc29sdmluZz0hMSx0aGlzLmNhblNlZVZpZXdQcm92aWRlcnM9ZSx0aGlzLmluamVjdEltcGw9aX19O2Z1bmN0aW9uIHoxKG4sdCxlKXtsZXQgaT0wO2Zvcig7aTxlLmxlbmd0aDspe2xldCByPWVbaV07aWYoIm51bWJlciI9PXR5cGVvZiByKXtpZigwIT09cilicmVhaztpKys7bGV0IG89ZVtpKytdLHM9ZVtpKytdLGE9ZVtpKytdO24uc2V0QXR0cmlidXRlKHQscyxhLG8pfWVsc2V7bGV0IG89cixzPWVbKytpXTtiX2Uobyk/bi5zZXRQcm9wZXJ0eSh0LG8scyk6bi5zZXRBdHRyaWJ1dGUodCxvLHMpLGkrK319cmV0dXJuIGl9ZnVuY3Rpb24gJDcobil7cmV0dXJuIDM9PT1ufHw0PT09bnx8Nj09PW59ZnVuY3Rpb24gYl9lKG4pe3JldHVybiA2ND09PW4uY2hhckNvZGVBdCgwKX1mdW5jdGlvbiBqMShuLHQpe2lmKG51bGwhPT10JiYwIT09dC5sZW5ndGgpaWYobnVsbD09PW58fDA9PT1uLmxlbmd0aCluPXQuc2xpY2UoKTtlbHNle2xldCBlPS0xO2ZvcihsZXQgaT0wO2k8dC5sZW5ndGg7aSsrKXtsZXQgcj10W2ldOyJudW1iZXIiPT10eXBlb2Ygcj9lPXI6MD09PWV8fFZXKG4sZSxyLG51bGwsLTE9PT1lfHwyPT09ZT90WysraV06bnVsbCl9fXJldHVybiBufWZ1bmN0aW9uIFZXKG4sdCxlLGkscil7bGV0IG89MCxzPW4ubGVuZ3RoO2lmKC0xPT09dClzPS0xO2Vsc2UgZm9yKDtvPG4ubGVuZ3RoOyl7bGV0IGE9bltvKytdO2lmKCJudW1iZXIiPT10eXBlb2YgYSl7aWYoYT09PXQpe3M9LTE7YnJlYWt9aWYoYT50KXtzPW8tMTticmVha319fWZvcig7bzxuLmxlbmd0aDspe2xldCBhPW5bb107aWYoIm51bWJlciI9PXR5cGVvZiBhKWJyZWFrO2lmKGE9PT1lKXtpZihudWxsPT09aSlyZXR1cm4gdm9pZChudWxsIT09ciYmKG5bbysxXT1yKSk7aWYoaT09PW5bbysxXSlyZXR1cm4gdm9pZChuW28rMl09cil9bysrLG51bGwhPT1pJiZvKyssbnVsbCE9PXImJm8rK30tMSE9PXMmJihuLnNwbGljZShzLDAsdCksbz1zKzEpLG4uc3BsaWNlKG8rKywwLGUpLG51bGwhPT1pJiZuLnNwbGljZShvKyssMCxpKSxudWxsIT09ciYmbi5zcGxpY2UobysrLDAscil9ZnVuY3Rpb24gZTkobil7cmV0dXJuLTEhPT1ufWZ1bmN0aW9uIEcxKG4pe3JldHVybiAzMjc2NyZufWZ1bmN0aW9uIFcxKG4sdCl7bGV0IGU9ZnVuY3Rpb24obil7cmV0dXJuIG4+PjE2fShuKSxpPXQ7Zm9yKDtlPjA7KWk9aVsxNV0sZS0tO3JldHVybiBpfXZhciBhTD0hMDtmdW5jdGlvbiBxMShuKXtsZXQgdD1hTDtyZXR1cm4gYUw9bix0fXZhciBNX2U9MCx4dT17fTtmdW5jdGlvbiBTeChuLHQpe2xldCBlPWk5KG4sdCk7aWYoLTEhPT1lKXJldHVybiBlO2xldCBpPXRbMV07aS5maXJzdENyZWF0ZVBhc3MmJihuLmluamVjdG9ySW5kZXg9dC5sZW5ndGgsTk4oaS5kYXRhLG4pLE5OKHQsbnVsbCksTk4oaS5ibHVlcHJpbnQsbnVsbCkpO2xldCByPUMzKG4sdCksbz1uLmluamVjdG9ySW5kZXg7aWYoZTkocikpe2xldCBzPUcxKHIpLGE9VzEocix0KSxsPWFbMV0uZGF0YTtmb3IobGV0IGM9MDtjPDg7YysrKXRbbytjXT1hW3MrY118bFtzK2NdfXJldHVybiB0W28rOF09cixvfWZ1bmN0aW9uIE5OKG4sdCl7bi5wdXNoKDAsMCwwLDAsMCwwLDAsMCx0KX1mdW5jdGlvbiBpOShuLHQpe3JldHVybi0xPT09bi5pbmplY3RvckluZGV4fHxuLnBhcmVudCYmbi5wYXJlbnQuaW5qZWN0b3JJbmRleD09PW4uaW5qZWN0b3JJbmRleHx8bnVsbD09PXRbbi5pbmplY3RvckluZGV4KzhdPy0xOm4uaW5qZWN0b3JJbmRleH1mdW5jdGlvbiBDMyhuLHQpe2lmKG4ucGFyZW50JiYtMSE9PW4ucGFyZW50LmluamVjdG9ySW5kZXgpcmV0dXJuIG4ucGFyZW50LmluamVjdG9ySW5kZXg7bGV0IGU9MCxpPW51bGwscj10O2Zvcig7bnVsbCE9PXI7KXtpZihpPWw5KHIpLG51bGw9PT1pKXJldHVybi0xO2lmKGUrKyxyPXJbMTVdLC0xIT09aS5pbmplY3RvckluZGV4KXJldHVybiBpLmluamVjdG9ySW5kZXh8ZTw8MTZ9cmV0dXJuLTF9ZnVuY3Rpb24gWTEobix0LGUpeyFmdW5jdGlvbihuLHQsZSl7bGV0IGk7InN0cmluZyI9PXR5cGVvZiBlP2k9ZS5jaGFyQ29kZUF0KDApfHwwOmUuaGFzT3duUHJvcGVydHkoaHgpJiYoaT1lW2h4XSksbnVsbD09aSYmKGk9ZVtoeF09TV9lKyspO2xldCByPTI1NSZpO3QuZGF0YVtuKyhyPj41KV18PTE8PHJ9KG4sdCxlKX1mdW5jdGlvbiByOShuLHQsZSl7aWYoZSZkaS5PcHRpb25hbHx8dm9pZCAwIT09bilyZXR1cm4gbjtzMygpfWZ1bmN0aW9uIG85KG4sdCxlLGkpe2lmKGUmZGkuT3B0aW9uYWwmJnZvaWQgMD09PWkmJihpPW51bGwpLDA9PShlJihkaS5TZWxmfGRpLkhvc3QpKSl7bGV0IHI9bls5XSxvPWtsKHZvaWQgMCk7dHJ5e3JldHVybiByP3IuZ2V0KHQsaSxlJmRpLk9wdGlvbmFsKTpBNyh0LGksZSZkaS5PcHRpb25hbCl9ZmluYWxseXtrbChvKX19cmV0dXJuIHI5KGksMCxlKX1mdW5jdGlvbiBzOShuLHQsZSxpPWRpLkRlZmF1bHQscil7aWYobnVsbCE9PW4pe2lmKDEwMjQmdFsyXSl7bGV0IHM9ZnVuY3Rpb24obix0LGUsaSxyKXtsZXQgbz1uLHM9dDtmb3IoO251bGwhPT1vJiZudWxsIT09cyYmMTAyNCZzWzJdJiYhKDI1NiZzWzJdKTspe2xldCBhPWE5KG8scyxlLGl8ZGkuU2VsZix4dSk7aWYoYSE9PXh1KXJldHVybiBhO2xldCBsPW8ucGFyZW50O2lmKCFsKXtsZXQgYz1zWzIxXTtpZihjKXtsZXQgdT1jLmdldChlLHh1LGkpO2lmKHUhPT14dSlyZXR1cm4gdX1sPWw5KHMpLHM9c1sxNV19bz1sfXJldHVybiByfShuLHQsZSxpLHh1KTtpZihzIT09eHUpcmV0dXJuIHN9bGV0IG89YTkobix0LGUsaSx4dSk7aWYobyE9PXh1KXJldHVybiBvfXJldHVybiBvOSh0LGUsaSxyKX1mdW5jdGlvbiBhOShuLHQsZSxpLHIpe2xldCBvPWZ1bmN0aW9uKG4pe2lmKCJzdHJpbmciPT10eXBlb2YgbilyZXR1cm4gbi5jaGFyQ29kZUF0KDApfHwwO2xldCB0PW4uaGFzT3duUHJvcGVydHkoaHgpP25baHhdOnZvaWQgMDtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIHQ/dD49MD8yNTUmdDpEX2U6dH0oZSk7aWYoImZ1bmN0aW9uIj09dHlwZW9mIG8pe2lmKCFZNyh0LG4saSkpcmV0dXJuIGkmZGkuSG9zdD9yOShyLDAsaSk6bzkodCxlLGkscik7dHJ5e2xldCBzPW8oaSk7aWYobnVsbCE9c3x8aSZkaS5PcHRpb25hbClyZXR1cm4gcztzMygpfWZpbmFsbHl7WjcoKX19ZWxzZSBpZigibnVtYmVyIj09dHlwZW9mIG8pe2xldCBzPW51bGwsYT1pOShuLHQpLGw9LTEsYz1pJmRpLkhvc3Q/dFsxNl1bNl06bnVsbDtmb3IoKC0xPT09YXx8aSZkaS5Ta2lwU2VsZikmJihsPS0xPT09YT9DMyhuLHQpOnRbYSs4XSwtMSE9PWwmJlVXKGksITEpPyhzPXRbMV0sYT1HMShsKSx0PVcxKGwsdCkpOmE9LTEpOy0xIT09YTspe2xldCB1PXRbMV07aWYoSFcobyxhLHUuZGF0YSkpe2xldCBkPUVfZShhLHQsZSxzLGksYyk7aWYoZCE9PXh1KXJldHVybiBkfWw9dFthKzhdLC0xIT09bCYmVVcoaSx0WzFdLmRhdGFbYSs4XT09PWMpJiZIVyhvLGEsdCk/KHM9dSxhPUcxKGwpLHQ9VzEobCx0KSk6YT0tMX19cmV0dXJuIHJ9ZnVuY3Rpb24gRV9lKG4sdCxlLGkscixvKXtsZXQgcz10WzFdLGE9cy5kYXRhW24rOF0sdT1JMShhLHMsZSxudWxsPT1pP2gzKGEpJiZhTDppIT1zJiYwIT0oMyZhLnR5cGUpLHImZGkuSG9zdCYmbz09PWEpO3JldHVybiBudWxsIT09dT9CeCh0LHMsdSxhKTp4dX1mdW5jdGlvbiBJMShuLHQsZSxpLHIpe2xldCBvPW4ucHJvdmlkZXJJbmRleGVzLHM9dC5kYXRhLGE9MTA0ODU3NSZvLGw9bi5kaXJlY3RpdmVTdGFydCx1PW8+PjIwLHA9cj9hK3U6bi5kaXJlY3RpdmVFbmQ7Zm9yKGxldCBoPWk/YTphK3U7aDxwO2grKyl7bGV0IGY9c1toXTtpZihoPGwmJmU9PT1mfHxoPj1sJiZmLnR5cGU9PT1lKXJldHVybiBofWlmKHIpe2xldCBoPXNbbF07aWYoaCYmQWMoaCkmJmgudHlwZT09PWUpcmV0dXJuIGx9cmV0dXJuIG51bGx9ZnVuY3Rpb24gQngobix0LGUsaSl7bGV0IHI9bltlXSxvPXQuZGF0YTtpZihmdW5jdGlvbihuKXtyZXR1cm4gbiBpbnN0YW5jZW9mIHFmfShyKSl7bGV0IHM9cjtzLnJlc29sdmluZyYmZnVuY3Rpb24obix0KXt0aHJvdyBuZXcgQXQoLTIwMCxgQ2lyY3VsYXIgZGVwZW5kZW5jeSBpbiBESSBkZXRlY3RlZCBmb3IgJHtufWApfShvMyhvW2VdKSk7bGV0IGE9cTEocy5jYW5TZWVWaWV3UHJvdmlkZXJzKTtzLnJlc29sdmluZz0hMDtsZXQgbD1zLmluamVjdEltcGw/a2wocy5pbmplY3RJbXBsKTpudWxsO1k3KG4saSxkaS5EZWZhdWx0KTt0cnl7cj1uW2VdPXMuZmFjdG9yeSh2b2lkIDAsbyxuLGkpLHQuZmlyc3RDcmVhdGVQYXNzJiZlPj1pLmRpcmVjdGl2ZVN0YXJ0JiZmdW5jdGlvbihuLHQsZSl7bGV0e25nT25DaGFuZ2VzOmksbmdPbkluaXQ6cixuZ0RvQ2hlY2s6b309dC50eXBlLnByb3RvdHlwZTtpZihpKXtsZXQgcz1GNyh0KTsoZS5wcmVPcmRlckhvb2tzfHwoZS5wcmVPcmRlckhvb2tzPVtdKSkucHVzaChuLHMpLChlLnByZU9yZGVyQ2hlY2tIb29rc3x8KGUucHJlT3JkZXJDaGVja0hvb2tzPVtdKSkucHVzaChuLHMpfXImJihlLnByZU9yZGVySG9va3N8fChlLnByZU9yZGVySG9va3M9W10pKS5wdXNoKDAtbixyKSxvJiYoKGUucHJlT3JkZXJIb29rc3x8KGUucHJlT3JkZXJIb29rcz1bXSkpLnB1c2gobixvKSwoZS5wcmVPcmRlckNoZWNrSG9va3N8fChlLnByZU9yZGVyQ2hlY2tIb29rcz1bXSkpLnB1c2gobixvKSl9KGUsb1tlXSx0KX1maW5hbGx5e251bGwhPT1sJiZrbChsKSxxMShhKSxzLnJlc29sdmluZz0hMSxaNygpfX1yZXR1cm4gcn1mdW5jdGlvbiBIVyhuLHQsZSl7cmV0dXJuISEoZVt0KyhuPj41KV0mMTw8bil9ZnVuY3Rpb24gVVcobix0KXtyZXR1cm4hKG4mZGkuU2VsZnx8biZkaS5Ib3N0JiZ0KX12YXIgamY9Y2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLl90Tm9kZT10LHRoaXMuX2xWaWV3PWV9Z2V0KHQsZSxpKXtyZXR1cm4gczkodGhpcy5fdE5vZGUsdGhpcy5fbFZpZXcsdCxpLGUpfX07ZnVuY3Rpb24gRF9lKCl7cmV0dXJuIG5ldyBqZih6bygpLHJ0KCkpfWZ1bmN0aW9uIHBpKG4pe3JldHVybiBaZigoKT0+e2xldCB0PW4ucHJvdG90eXBlLmNvbnN0cnVjdG9yLGU9dFtOZF18fGxMKHQpLGk9T2JqZWN0LnByb3RvdHlwZSxyPU9iamVjdC5nZXRQcm90b3R5cGVPZihuLnByb3RvdHlwZSkuY29uc3RydWN0b3I7Zm9yKDtyJiZyIT09aTspe2xldCBvPXJbTmRdfHxsTChyKTtpZihvJiZvIT09ZSlyZXR1cm4gbztyPU9iamVjdC5nZXRQcm90b3R5cGVPZihyKX1yZXR1cm4gbz0+bmV3IG99KX1mdW5jdGlvbiBsTChuKXtyZXR1cm4gRTcobik/KCk9PntsZXQgdD1sTChLaShuKSk7cmV0dXJuIHQmJnQoKX06V2Yobil9ZnVuY3Rpb24gbDkobil7bGV0IHQ9blsxXSxlPXQudHlwZTtyZXR1cm4gMj09PWU/dC5kZWNsVE5vZGU6MT09PWU/bls2XTpudWxsfWZ1bmN0aW9uIHZvKG4pe3JldHVybiBmdW5jdGlvbihuLHQpe2lmKCJjbGFzcyI9PT10KXJldHVybiBuLmNsYXNzZXM7aWYoInN0eWxlIj09PXQpcmV0dXJuIG4uc3R5bGVzO2xldCBlPW4uYXR0cnM7aWYoZSl7bGV0IGk9ZS5sZW5ndGgscj0wO2Zvcig7cjxpOyl7bGV0IG89ZVtyXTtpZigkNyhvKSlicmVhaztpZigwPT09bylyKz0yO2Vsc2UgaWYoIm51bWJlciI9PXR5cGVvZiBvKWZvcihyKys7cjxpJiYic3RyaW5nIj09dHlwZW9mIGVbcl07KXIrKztlbHNle2lmKG89PT10KXJldHVybiBlW3IrMV07cis9Mn19fXJldHVybiBudWxsfSh6bygpLG4pfXZhciBNMD0iX19hbm5vdGF0aW9uc19fIix3MD0iX19wYXJhbWV0ZXJzX18iLFMwPSJfX3Byb3BfX21ldGFkYXRhX18iO2Z1bmN0aW9uIFZ4KG4sdCxlLGkscil7cmV0dXJuIFpmKCgpPT57bGV0IG89TTModCk7ZnVuY3Rpb24gcyguLi5hKXtpZih0aGlzIGluc3RhbmNlb2YgcylyZXR1cm4gby5jYWxsKHRoaXMsLi4uYSksdGhpcztsZXQgbD1uZXcgcyguLi5hKTtyZXR1cm4gZnVuY3Rpb24odSl7cmV0dXJuIHImJnIodSwuLi5hKSwodS5oYXNPd25Qcm9wZXJ0eShNMCk/dVtNMF06T2JqZWN0LmRlZmluZVByb3BlcnR5KHUsTTAse3ZhbHVlOltdfSlbTTBdKS5wdXNoKGwpLGkmJmkodSksdX19cmV0dXJuIGUmJihzLnByb3RvdHlwZT1PYmplY3QuY3JlYXRlKGUucHJvdG90eXBlKSkscy5wcm90b3R5cGUubmdNZXRhZGF0YU5hbWU9bixzLmFubm90YXRpb25DbHM9cyxzfSl9ZnVuY3Rpb24gTTMobil7cmV0dXJuIGZ1bmN0aW9uKC4uLmUpe2lmKG4pe2xldCBpPW4oLi4uZSk7Zm9yKGxldCByIGluIGkpdGhpc1tyXT1pW3JdfX19ZnVuY3Rpb24gejAobix0LGUpe3JldHVybiBaZigoKT0+e2xldCBpPU0zKHQpO2Z1bmN0aW9uIHIoLi4ubyl7aWYodGhpcyBpbnN0YW5jZW9mIHIpcmV0dXJuIGkuYXBwbHkodGhpcyxvKSx0aGlzO2xldCBzPW5ldyByKC4uLm8pO3JldHVybiBhLmFubm90YXRpb249cyxhO2Z1bmN0aW9uIGEobCxjLHUpe2xldCBkPWwuaGFzT3duUHJvcGVydHkodzApP2xbdzBdOk9iamVjdC5kZWZpbmVQcm9wZXJ0eShsLHcwLHt2YWx1ZTpbXX0pW3cwXTtmb3IoO2QubGVuZ3RoPD11OylkLnB1c2gobnVsbCk7cmV0dXJuKGRbdV09ZFt1XXx8W10pLnB1c2gocyksbH19cmV0dXJuIGUmJihyLnByb3RvdHlwZT1PYmplY3QuY3JlYXRlKGUucHJvdG90eXBlKSksci5wcm90b3R5cGUubmdNZXRhZGF0YU5hbWU9bixyLmFubm90YXRpb25DbHM9cixyfSl9ZnVuY3Rpb24gWXAobix0LGUsaSl7cmV0dXJuIFpmKCgpPT57bGV0IHI9TTModCk7ZnVuY3Rpb24gbyguLi5zKXtpZih0aGlzIGluc3RhbmNlb2YgbylyZXR1cm4gci5hcHBseSh0aGlzLHMpLHRoaXM7bGV0IGE9bmV3IG8oLi4ucyk7cmV0dXJuIGZ1bmN0aW9uKGMsdSl7bGV0IGQ9Yy5jb25zdHJ1Y3RvcixwPWQuaGFzT3duUHJvcGVydHkoUzApP2RbUzBdOk9iamVjdC5kZWZpbmVQcm9wZXJ0eShkLFMwLHt2YWx1ZTp7fX0pW1MwXTtwW3VdPXAuaGFzT3duUHJvcGVydHkodSkmJnBbdV18fFtdLHBbdV0udW5zaGlmdChhKSxpJiZpKGMsdSwuLi5zKX19cmV0dXJuIGUmJihvLnByb3RvdHlwZT1PYmplY3QuY3JlYXRlKGUucHJvdG90eXBlKSksby5wcm90b3R5cGUubmdNZXRhZGF0YU5hbWU9bixvLmFubm90YXRpb25DbHM9byxvfSl9dmFyIElfZT16MCgiQXR0cmlidXRlIixuPT4oe2F0dHJpYnV0ZU5hbWU6bixfX05HX0VMRU1FTlRfSURfXzooKT0+dm8obil9KSkscGU9Y2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLl9kZXNjPXQsdGhpcy5uZ01ldGFkYXRhTmFtZT0iSW5qZWN0aW9uVG9rZW4iLHRoaXMuXHUwMjc1cHJvdj12b2lkIDAsIm51bWJlciI9PXR5cGVvZiBlP3RoaXMuX19OR19FTEVNRU5UX0lEX189ZTp2b2lkIDAhPT1lJiYodGhpcy5cdTAyNzVwcm92PXllKHt0b2tlbjp0aGlzLHByb3ZpZGVkSW46ZS5wcm92aWRlZElufHwicm9vdCIsZmFjdG9yeTplLmZhY3Rvcnl9KSl9Z2V0IG11bHRpKCl7cmV0dXJuIHRoaXN9dG9TdHJpbmcoKXtyZXR1cm5gSW5qZWN0aW9uVG9rZW4gJHt0aGlzLl9kZXNjfWB9fSxSMD0obmV3IHBlKCJBbmFseXplRm9yRW50cnlDb21wb25lbnRzIiksY2xhc3N7fSk7ZnVuY3Rpb24gTGwobil7bGV0IHQ9dG8ubmc7aWYodCYmdC5cdTAyNzVjb21waWxlckZhY2FkZSlyZXR1cm4gdC5cdTAyNzVjb21waWxlckZhY2FkZTt0aHJvdyBuZXcgRXJyb3IoIkpJVCBjb21waWxlciB1bmF2YWlsYWJsZSIpfVlwKCJDb250ZW50Q2hpbGRyZW4iLChuLHQ9e30pPT4oe3NlbGVjdG9yOm4sZmlyc3Q6ITEsaXNWaWV3UXVlcnk6ITEsZGVzY2VuZGFudHM6ITEsZW1pdERpc3RpbmN0Q2hhbmdlc09ubHk6ITAsLi4udH0pLFIwKSxZcCgiQ29udGVudENoaWxkIiwobix0PXt9KT0+KHtzZWxlY3RvcjpuLGZpcnN0OiEwLGlzVmlld1F1ZXJ5OiExLGRlc2NlbmRhbnRzOiEwLC4uLnR9KSxSMCksWXAoIlZpZXdDaGlsZHJlbiIsKG4sdD17fSk9Pih7c2VsZWN0b3I6bixmaXJzdDohMSxpc1ZpZXdRdWVyeTohMCxkZXNjZW5kYW50czohMCxlbWl0RGlzdGluY3RDaGFuZ2VzT25seTohMCwuLi50fSksUjApLFlwKCJWaWV3Q2hpbGQiLChuLHQpPT4oe3NlbGVjdG9yOm4sZmlyc3Q6ITAsaXNWaWV3UXVlcnk6ITAsZGVzY2VuZGFudHM6ITAsLi4udH0pLFIwKTt2YXIgUF9lPUZ1bmN0aW9uO2Z1bmN0aW9uIHV4KG4pe3JldHVybiJmdW5jdGlvbiI9PXR5cGVvZiBufWZ1bmN0aW9uIEZkKG4sdCl7dm9pZCAwPT09dCYmKHQ9bik7Zm9yKGxldCBlPTA7ZTxuLmxlbmd0aDtlKyspe2xldCBpPW5bZV07QXJyYXkuaXNBcnJheShpKT8odD09PW4mJih0PW4uc2xpY2UoMCxlKSksRmQoaSx0KSk6dCE9PW4mJnQucHVzaChpKX1yZXR1cm4gdH1mdW5jdGlvbiBFeChuLHQpe24uZm9yRWFjaChlPT5BcnJheS5pc0FycmF5KGUpP0V4KGUsdCk6dChlKSl9ZnVuY3Rpb24gdTkobix0LGUpe3Q+PW4ubGVuZ3RoP24ucHVzaChlKTpuLnNwbGljZSh0LDAsZSl9ZnVuY3Rpb24gWDEobix0KXtyZXR1cm4gdD49bi5sZW5ndGgtMT9uLnBvcCgpOm4uc3BsaWNlKHQsMSlbMF19ZnVuY3Rpb24gZngobix0KXtsZXQgZT1bXTtmb3IobGV0IGk9MDtpPG47aSsrKWUucHVzaCh0KTtyZXR1cm4gZX1mdW5jdGlvbiBlbChuLHQsZSl7bGV0IGk9SHgobix0KTtyZXR1cm4gaT49MD9uWzF8aV09ZTooaT1+aSxmdW5jdGlvbihuLHQsZSxpKXtsZXQgcj1uLmxlbmd0aDtpZihyPT10KW4ucHVzaChlLGkpO2Vsc2UgaWYoMT09PXIpbi5wdXNoKGksblswXSksblswXT1lO2Vsc2V7Zm9yKHItLSxuLnB1c2gobltyLTFdLG5bcl0pO3I+dDspbltyXT1uW3ItMl0sci0tO25bdF09ZSxuW3QrMV09aX19KG4saSx0LGUpKSxpfWZ1bmN0aW9uIExOKG4sdCl7bGV0IGU9SHgobix0KTtpZihlPj0wKXJldHVybiBuWzF8ZV19ZnVuY3Rpb24gSHgobix0KXtyZXR1cm4gZnVuY3Rpb24obix0LGUpe2xldCBpPTAscj1uLmxlbmd0aD4+MTtmb3IoO3IhPT1pOyl7bGV0IG89aSsoci1pPj4xKSxzPW5bbzw8MV07aWYodD09PXMpcmV0dXJuIG88PDE7cz50P3I9bzppPW8rMX1yZXR1cm5+KHI8PDEpfShuLHQpfXZhciBGX2U9L15mdW5jdGlvblxzK1xTK1woXClccyp7W1xzXFNdK1wuYXBwbHlcKHRoaXMsXHMqKGFyZ3VtZW50c3woPzpbXigpXStcKFxbXF0sKT9bXigpXStcKGFyZ3VtZW50c1wpLiopXCkvLE5fZT0vXmNsYXNzXHMrW0EtWmEtelxkJF9dKlxzKmV4dGVuZHNccytbXntdK3svLExfZT0vXmNsYXNzXHMrW0EtWmEtelxkJF9dKlxzKmV4dGVuZHNccytbXntdK3tbXHNcU10qY29uc3RydWN0b3JccypcKC8sQl9lPS9eY2xhc3NccytbQS1aYS16XGQkX10qXHMqZXh0ZW5kc1xzK1tee10re1tcc1xTXSpjb25zdHJ1Y3RvclxzKlwoXClccyp7W159XSpzdXBlclwoXC5cLlwuYXJndW1lbnRzXCkvO2Z1bmN0aW9uIEJOKG4pe3JldHVybiBuP24ubWFwKHQ9Pm5ldygwLHQudHlwZS5hbm5vdGF0aW9uQ2xzKSguLi50LmFyZ3M/dC5hcmdzOltdKSk6W119ZnVuY3Rpb24gQzEobil7bGV0IHQ9bi5wcm90b3R5cGU/T2JqZWN0LmdldFByb3RvdHlwZU9mKG4ucHJvdG90eXBlKTpudWxsO3JldHVybih0P3QuY29uc3RydWN0b3I6bnVsbCl8fE9iamVjdH12YXIgbXgsVHg9e30sdUw9Il9fTkdfRElfRkxBR19fIixRMT0ibmdUZW1wVG9rZW5QYXRoIix6X2U9L1xuL2dtLHpXPSJfX3NvdXJjZSI7ZnVuY3Rpb24geDAobil7bGV0IHQ9bXg7cmV0dXJuIG14PW4sdH1mdW5jdGlvbiBHX2Uobix0PWRpLkRlZmF1bHQpe2lmKHZvaWQgMD09PW14KXRocm93IG5ldyBBdCgtMjAzLCExKTtyZXR1cm4gbnVsbD09PW14P0E3KG4sdm9pZCAwLHQpOm14LmdldChuLHQmZGkuT3B0aW9uYWw/bnVsbDp2b2lkIDAsdCl9ZnVuY3Rpb24gaihuLHQ9ZGkuRGVmYXVsdCl7cmV0dXJuKGlMfHxHX2UpKEtpKG4pLHQpfWZ1bmN0aW9uIGQ5KG4pe3Rocm93IG5ldyBBdCgyMDIsITEpfWZ1bmN0aW9uIGpvKG4sdD1kaS5EZWZhdWx0KXtyZXR1cm4ibnVtYmVyIiE9dHlwZW9mIHQmJih0PTB8KHQub3B0aW9uYWwmJjgpfCh0Lmhvc3QmJjEpfCh0LnNlbGYmJjIpfCh0LnNraXBTZWxmJiY0KSksaihuLHQpfWZ1bmN0aW9uIGRMKG4pe2xldCB0PVtdO2ZvcihsZXQgZT0wO2U8bi5sZW5ndGg7ZSsrKXtsZXQgaT1LaShuW2VdKTtpZihBcnJheS5pc0FycmF5KGkpKXtpZigwPT09aS5sZW5ndGgpdGhyb3cgbmV3IEF0KDkwMCwhMSk7bGV0IHIsbz1kaS5EZWZhdWx0O2ZvcihsZXQgcz0wO3M8aS5sZW5ndGg7cysrKXtsZXQgYT1pW3NdLGw9V19lKGEpOyJudW1iZXIiPT10eXBlb2YgbD8tMT09PWw/cj1hLnRva2VuOm98PWw6cj1hfXQucHVzaChqKHIsbykpfWVsc2UgdC5wdXNoKGooaSkpfXJldHVybiB0fWZ1bmN0aW9uIFV4KG4sdCl7cmV0dXJuIG5bdUxdPXQsbi5wcm90b3R5cGVbdUxdPXQsbn1mdW5jdGlvbiBXX2Uobil7cmV0dXJuIG5bdUxdfXZhciBqMD1VeCh6MCgiSW5qZWN0IixuPT4oe3Rva2VuOm59KSksLTEpLG5zPVV4KHowKCJPcHRpb25hbCIpLDgpLHczPVV4KHowKCJTZWxmIiksMiksdGw9VXgoejAoIlNraXBTZWxmIiksNCksWF9lPVV4KHowKCJIb3N0IiksMSksalc9bnVsbDtmdW5jdGlvbiBTMygpe3JldHVybiBqVz1qV3x8bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuX3JlZmxlY3Q9dHx8dG8uUmVmbGVjdH1mYWN0b3J5KHQpe3JldHVybiguLi5lKT0+bmV3IHQoLi4uZSl9X3ppcFR5cGVzQW5kQW5ub3RhdGlvbnModCxlKXtsZXQgaTtpPWZ4KHR5cGVvZiB0PiJ1Ij9lLmxlbmd0aDp0Lmxlbmd0aCk7Zm9yKGxldCByPTA7cjxpLmxlbmd0aDtyKyspaVtyXT10eXBlb2YgdD4idSI/W106dFtyXSYmdFtyXSE9T2JqZWN0P1t0W3JdXTpbXSxlJiZudWxsIT1lW3JdJiYoaVtyXT1pW3JdLmNvbmNhdChlW3JdKSk7cmV0dXJuIGl9X293blBhcmFtZXRlcnModCxlKXtsZXQgaT10LnRvU3RyaW5nKCk7aWYoRl9lLnRlc3Qobj1pKXx8Ql9lLnRlc3Qobil8fE5fZS50ZXN0KG4pJiYhTF9lLnRlc3QobikpcmV0dXJuIG51bGw7dmFyIG47aWYodC5wYXJhbWV0ZXJzJiZ0LnBhcmFtZXRlcnMhPT1lLnBhcmFtZXRlcnMpcmV0dXJuIHQucGFyYW1ldGVycztsZXQgcj10LmN0b3JQYXJhbWV0ZXJzO2lmKHImJnIhPT1lLmN0b3JQYXJhbWV0ZXJzKXtsZXQgYT0iZnVuY3Rpb24iPT10eXBlb2Ygcj9yKCk6cixsPWEubWFwKHU9PnUmJnUudHlwZSksYz1hLm1hcCh1PT51JiZCTih1LmRlY29yYXRvcnMpKTtyZXR1cm4gdGhpcy5femlwVHlwZXNBbmRBbm5vdGF0aW9ucyhsLGMpfWxldCBvPXQuaGFzT3duUHJvcGVydHkodzApJiZ0W3cwXSxzPXRoaXMuX3JlZmxlY3QmJnRoaXMuX3JlZmxlY3QuZ2V0T3duTWV0YWRhdGEmJnRoaXMuX3JlZmxlY3QuZ2V0T3duTWV0YWRhdGEoImRlc2lnbjpwYXJhbXR5cGVzIix0KTtyZXR1cm4gc3x8bz90aGlzLl96aXBUeXBlc0FuZEFubm90YXRpb25zKHMsbyk6ZngodC5sZW5ndGgpfXBhcmFtZXRlcnModCl7aWYoIXV4KHQpKXJldHVybltdO2xldCBlPUMxKHQpLGk9dGhpcy5fb3duUGFyYW1ldGVycyh0LGUpO3JldHVybiFpJiZlIT09T2JqZWN0JiYoaT10aGlzLnBhcmFtZXRlcnMoZSkpLGl8fFtdfV9vd25Bbm5vdGF0aW9ucyh0LGUpe2lmKHQuYW5ub3RhdGlvbnMmJnQuYW5ub3RhdGlvbnMhPT1lLmFubm90YXRpb25zKXtsZXQgaT10LmFubm90YXRpb25zO3JldHVybiJmdW5jdGlvbiI9PXR5cGVvZiBpJiZpLmFubm90YXRpb25zJiYoaT1pLmFubm90YXRpb25zKSxpfXJldHVybiB0LmRlY29yYXRvcnMmJnQuZGVjb3JhdG9ycyE9PWUuZGVjb3JhdG9ycz9CTih0LmRlY29yYXRvcnMpOnQuaGFzT3duUHJvcGVydHkoTTApP3RbTTBdOm51bGx9YW5ub3RhdGlvbnModCl7aWYoIXV4KHQpKXJldHVybltdO2xldCBlPUMxKHQpLGk9dGhpcy5fb3duQW5ub3RhdGlvbnModCxlKXx8W107cmV0dXJuKGUhPT1PYmplY3Q/dGhpcy5hbm5vdGF0aW9ucyhlKTpbXSkuY29uY2F0KGkpfV9vd25Qcm9wTWV0YWRhdGEodCxlKXtpZih0LnByb3BNZXRhZGF0YSYmdC5wcm9wTWV0YWRhdGEhPT1lLnByb3BNZXRhZGF0YSl7bGV0IGk9dC5wcm9wTWV0YWRhdGE7cmV0dXJuImZ1bmN0aW9uIj09dHlwZW9mIGkmJmkucHJvcE1ldGFkYXRhJiYoaT1pLnByb3BNZXRhZGF0YSksaX1pZih0LnByb3BEZWNvcmF0b3JzJiZ0LnByb3BEZWNvcmF0b3JzIT09ZS5wcm9wRGVjb3JhdG9ycyl7bGV0IGk9dC5wcm9wRGVjb3JhdG9ycyxyPXt9O3JldHVybiBPYmplY3Qua2V5cyhpKS5mb3JFYWNoKG89PntyW29dPUJOKGlbb10pfSkscn1yZXR1cm4gdC5oYXNPd25Qcm9wZXJ0eShTMCk/dFtTMF06bnVsbH1wcm9wTWV0YWRhdGEodCl7aWYoIXV4KHQpKXJldHVybnt9O2xldCBlPUMxKHQpLGk9e307aWYoZSE9PU9iamVjdCl7bGV0IG89dGhpcy5wcm9wTWV0YWRhdGEoZSk7T2JqZWN0LmtleXMobykuZm9yRWFjaChzPT57aVtzXT1vW3NdfSl9bGV0IHI9dGhpcy5fb3duUHJvcE1ldGFkYXRhKHQsZSk7cmV0dXJuIHImJk9iamVjdC5rZXlzKHIpLmZvckVhY2gobz0+e2xldCBzPVtdO2kuaGFzT3duUHJvcGVydHkobykmJnMucHVzaCguLi5pW29dKSxzLnB1c2goLi4ucltvXSksaVtvXT1zfSksaX1vd25Qcm9wTWV0YWRhdGEodCl7cmV0dXJuIHV4KHQpJiZ0aGlzLl9vd25Qcm9wTWV0YWRhdGEodCxDMSh0KSl8fHt9fWhhc0xpZmVjeWNsZUhvb2sodCxlKXtyZXR1cm4gdCBpbnN0YW5jZW9mIFBfZSYmZSBpbiB0LnByb3RvdHlwZX19fWZ1bmN0aW9uIGZUKG4pe3JldHVybiBwOShTMygpLnBhcmFtZXRlcnMobikpfWZ1bmN0aW9uIHA5KG4pe3JldHVybiBuLm1hcCh0PT5mdW5jdGlvbihuKXtsZXQgdD17dG9rZW46bnVsbCxhdHRyaWJ1dGU6bnVsbCxob3N0OiExLG9wdGlvbmFsOiExLHNlbGY6ITEsc2tpcFNlbGY6ITF9O2lmKEFycmF5LmlzQXJyYXkobikmJm4ubGVuZ3RoPjApZm9yKGxldCBlPTA7ZTxuLmxlbmd0aDtlKyspe2xldCBpPW5bZV07aWYodm9pZCAwPT09aSljb250aW51ZTtsZXQgcj1PYmplY3QuZ2V0UHJvdG90eXBlT2YoaSk7aWYoaSBpbnN0YW5jZW9mIG5zfHwiT3B0aW9uYWwiPT09ci5uZ01ldGFkYXRhTmFtZSl0Lm9wdGlvbmFsPSEwO2Vsc2UgaWYoaSBpbnN0YW5jZW9mIHRsfHwiU2tpcFNlbGYiPT09ci5uZ01ldGFkYXRhTmFtZSl0LnNraXBTZWxmPSEwO2Vsc2UgaWYoaSBpbnN0YW5jZW9mIHczfHwiU2VsZiI9PT1yLm5nTWV0YWRhdGFOYW1lKXQuc2VsZj0hMDtlbHNlIGlmKGkgaW5zdGFuY2VvZiBYX2V8fCJIb3N0Ij09PXIubmdNZXRhZGF0YU5hbWUpdC5ob3N0PSEwO2Vsc2UgaWYoaSBpbnN0YW5jZW9mIGowKXQudG9rZW49aS50b2tlbjtlbHNlIGlmKGkgaW5zdGFuY2VvZiBJX2Upe2lmKHZvaWQgMD09PWkuYXR0cmlidXRlTmFtZSl0aHJvdyBuZXcgQXQoMjA0LCExKTt0LmF0dHJpYnV0ZT1pLmF0dHJpYnV0ZU5hbWV9ZWxzZSB0LnRva2VuPWl9ZWxzZSB0LnRva2VuPXZvaWQgMD09PW58fEFycmF5LmlzQXJyYXkobikmJjA9PT1uLmxlbmd0aD9udWxsOm47cmV0dXJuIHR9KHQpKX12YXIgRHg9bmV3IE1hcCxoOT1uZXcgU2V0O2Z1bmN0aW9uIGY5KG4pe3JldHVybiEhKG4udGVtcGxhdGVVcmwmJiFuLmhhc093blByb3BlcnR5KCJ0ZW1wbGF0ZSIpfHxuLnN0eWxlVXJscyYmbi5zdHlsZVVybHMubGVuZ3RoKX12YXIgcEwsTTEsdzEsR1c9bmV3IE1hcDtmdW5jdGlvbiBtOShuLHQpeyhmdW5jdGlvbihuLHQsZSl7aWYodCYmdCE9PWUpdGhyb3cgbmV3IEVycm9yKGBEdXBsaWNhdGUgbW9kdWxlIHJlZ2lzdGVyZWQgZm9yICR7bn0gLSAke1RvKHQpfSB2cyAke1RvKHQubmFtZSl9YCl9KSh0LEdXLmdldCh0KXx8bnVsbCxuKSxHVy5zZXQodCxuKX1mdW5jdGlvbiBfOSgpe3JldHVybiB2b2lkIDAhPT1wTD9wTDp0eXBlb2YgZG9jdW1lbnQ8InUiP2RvY3VtZW50OnZvaWQgMH1mdW5jdGlvbiB2OSgpe2lmKHZvaWQgMD09PU0xJiYoTTE9bnVsbCx0by50cnVzdGVkVHlwZXMpKXRyeXtNMT10by50cnVzdGVkVHlwZXMuY3JlYXRlUG9saWN5KCJhbmd1bGFyIix7Y3JlYXRlSFRNTDpuPT5uLGNyZWF0ZVNjcmlwdDpuPT5uLGNyZWF0ZVNjcmlwdFVSTDpuPT5ufSl9Y2F0Y2h7fXJldHVybiBNMX1mdW5jdGlvbiBPMChuKXtyZXR1cm4gdjkoKT8uY3JlYXRlSFRNTChuKXx8bn1mdW5jdGlvbiBFMygpe2lmKHZvaWQgMD09PXcxJiYodzE9bnVsbCx0by50cnVzdGVkVHlwZXMpKXRyeXt3MT10by50cnVzdGVkVHlwZXMuY3JlYXRlUG9saWN5KCJhbmd1bGFyI3Vuc2FmZS1ieXBhc3MiLHtjcmVhdGVIVE1MOm49Pm4sY3JlYXRlU2NyaXB0Om49Pm4sY3JlYXRlU2NyaXB0VVJMOm49Pm59KX1jYXRjaHt9cmV0dXJuIHcxfWZ1bmN0aW9uIFdXKG4pe3JldHVybiBFMygpPy5jcmVhdGVIVE1MKG4pfHxufWZ1bmN0aW9uIHFXKG4pe3JldHVybiBFMygpPy5jcmVhdGVTY3JpcHQobil8fG59ZnVuY3Rpb24gWVcobil7cmV0dXJuIEUzKCk/LmNyZWF0ZVNjcmlwdFVSTChuKXx8bn12YXIgQmQ9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5jaGFuZ2luZ1RoaXNCcmVha3NBcHBsaWNhdGlvblNlY3VyaXR5PXR9dG9TdHJpbmcoKXtyZXR1cm5gU2FmZVZhbHVlIG11c3QgdXNlIFtwcm9wZXJ0eV09YmluZGluZzogJHt0aGlzLmNoYW5naW5nVGhpc0JyZWFrc0FwcGxpY2F0aW9uU2VjdXJpdHl9IChzZWUgaHR0cHM6Ly9nLmNvL25nL3NlY3VyaXR5I3hzcylgfX0saEw9Y2xhc3MgZXh0ZW5kcyBCZHtnZXRUeXBlTmFtZSgpe3JldHVybiJIVE1MIn19LGZMPWNsYXNzIGV4dGVuZHMgQmR7Z2V0VHlwZU5hbWUoKXtyZXR1cm4iU3R5bGUifX0sbUw9Y2xhc3MgZXh0ZW5kcyBCZHtnZXRUeXBlTmFtZSgpe3JldHVybiJTY3JpcHQifX0sZ0w9Y2xhc3MgZXh0ZW5kcyBCZHtnZXRUeXBlTmFtZSgpe3JldHVybiJVUkwifX0sX0w9Y2xhc3MgZXh0ZW5kcyBCZHtnZXRUeXBlTmFtZSgpe3JldHVybiJSZXNvdXJjZVVSTCJ9fTtmdW5jdGlvbiBUYShuKXtyZXR1cm4gbiBpbnN0YW5jZW9mIEJkP24uY2hhbmdpbmdUaGlzQnJlYWtzQXBwbGljYXRpb25TZWN1cml0eTpufWZ1bmN0aW9uIFBjKG4sdCl7bGV0IGU9ZnVuY3Rpb24obil7cmV0dXJuIG4gaW5zdGFuY2VvZiBCZCYmbi5nZXRUeXBlTmFtZSgpfHxudWxsfShuKTtpZihudWxsIT1lJiZlIT09dCl7aWYoIlJlc291cmNlVVJMIj09PWUmJiJVUkwiPT09dClyZXR1cm4hMDt0aHJvdyBuZXcgRXJyb3IoYFJlcXVpcmVkIGEgc2FmZSAke3R9LCBnb3QgYSAke2V9IChzZWUgaHR0cHM6Ly9nLmNvL25nL3NlY3VyaXR5I3hzcylgKX1yZXR1cm4gZT09PXR9ZnVuY3Rpb24gdzkobil7bGV0IHQ9bmV3IHlMKG4pO3JldHVybiBmdW5jdGlvbigpe3RyeXtyZXR1cm4hIShuZXcgd2luZG93LkRPTVBhcnNlcikucGFyc2VGcm9tU3RyaW5nKE8wKCIiKSwidGV4dC9odG1sIil9Y2F0Y2h7cmV0dXJuITF9fSgpP25ldyB2TCh0KTp0fXZhciB2TD1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLmluZXJ0RG9jdW1lbnRIZWxwZXI9dH1nZXRJbmVydEJvZHlFbGVtZW50KHQpe3Q9Ijxib2R5PjxyZW1vdmU+PC9yZW1vdmU+Iit0O3RyeXtsZXQgZT0obmV3IHdpbmRvdy5ET01QYXJzZXIpLnBhcnNlRnJvbVN0cmluZyhPMCh0KSwidGV4dC9odG1sIikuYm9keTtyZXR1cm4gbnVsbD09PWU/dGhpcy5pbmVydERvY3VtZW50SGVscGVyLmdldEluZXJ0Qm9keUVsZW1lbnQodCk6KGUucmVtb3ZlQ2hpbGQoZS5maXJzdENoaWxkKSxlKX1jYXRjaHtyZXR1cm4gbnVsbH19fSx5TD1jbGFzc3tjb25zdHJ1Y3Rvcih0KXtpZih0aGlzLmRlZmF1bHREb2M9dCx0aGlzLmluZXJ0RG9jdW1lbnQ9dGhpcy5kZWZhdWx0RG9jLmltcGxlbWVudGF0aW9uLmNyZWF0ZUhUTUxEb2N1bWVudCgic2FuaXRpemF0aW9uLWluZXJ0IiksbnVsbD09dGhpcy5pbmVydERvY3VtZW50LmJvZHkpe2xldCBlPXRoaXMuaW5lcnREb2N1bWVudC5jcmVhdGVFbGVtZW50KCJodG1sIik7dGhpcy5pbmVydERvY3VtZW50LmFwcGVuZENoaWxkKGUpO2xldCBpPXRoaXMuaW5lcnREb2N1bWVudC5jcmVhdGVFbGVtZW50KCJib2R5Iik7ZS5hcHBlbmRDaGlsZChpKX19Z2V0SW5lcnRCb2R5RWxlbWVudCh0KXtsZXQgZT10aGlzLmluZXJ0RG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGVtcGxhdGUiKTtpZigiY29udGVudCJpbiBlKXJldHVybiBlLmlubmVySFRNTD1PMCh0KSxlO2xldCBpPXRoaXMuaW5lcnREb2N1bWVudC5jcmVhdGVFbGVtZW50KCJib2R5Iik7cmV0dXJuIGkuaW5uZXJIVE1MPU8wKHQpLHRoaXMuZGVmYXVsdERvYy5kb2N1bWVudE1vZGUmJnRoaXMuc3RyaXBDdXN0b21Oc0F0dHJzKGkpLGl9c3RyaXBDdXN0b21Oc0F0dHJzKHQpe2xldCBlPXQuYXR0cmlidXRlcztmb3IobGV0IHI9ZS5sZW5ndGgtMTswPHI7ci0tKXtsZXQgcz1lLml0ZW0ocikubmFtZTsoInhtbG5zOm5zMSI9PT1zfHwwPT09cy5pbmRleE9mKCJuczE6IikpJiZ0LnJlbW92ZUF0dHJpYnV0ZShzKX1sZXQgaT10LmZpcnN0Q2hpbGQ7Zm9yKDtpOylpLm5vZGVUeXBlPT09Tm9kZS5FTEVNRU5UX05PREUmJnRoaXMuc3RyaXBDdXN0b21Oc0F0dHJzKGkpLGk9aS5uZXh0U2libGluZ319LGF2ZT0vXig/Oig/Omh0dHBzP3xtYWlsdG98ZGF0YXxmdHB8dGVsfGZpbGV8c21zKTp8W14mOi8/I10qKD86Wy8/I118JCkpL2dpO2Z1bmN0aW9uIHp4KG4pe3JldHVybihuPVN0cmluZyhuKSkubWF0Y2goYXZlKT9uOiJ1bnNhZmU6IitufWZ1bmN0aW9uIHpkKG4pe2xldCB0PXt9O2ZvcihsZXQgZSBvZiBuLnNwbGl0KCIsIikpdFtlXT0hMDtyZXR1cm4gdH1mdW5jdGlvbiBqeCguLi5uKXtsZXQgdD17fTtmb3IobGV0IGUgb2Ygbilmb3IobGV0IGkgaW4gZSllLmhhc093blByb3BlcnR5KGkpJiYodFtpXT0hMCk7cmV0dXJuIHR9dmFyIFMxLFM5PXpkKCJhcmVhLGJyLGNvbCxocixpbWcsd2JyIiksRTk9emQoImNvbGdyb3VwLGRkLGR0LGxpLHAsdGJvZHksdGQsdGZvb3QsdGgsdGhlYWQsdHIiKSxUOT16ZCgicnAscnQiKSxsdmU9angoVDksRTkpLGN2ZT1qeChFOSx6ZCgiYWRkcmVzcyxhcnRpY2xlLGFzaWRlLGJsb2NrcXVvdGUsY2FwdGlvbixjZW50ZXIsZGVsLGRldGFpbHMsZGlhbG9nLGRpcixkaXYsZGwsZmlndXJlLGZpZ2NhcHRpb24sZm9vdGVyLGgxLGgyLGgzLGg0LGg1LGg2LGhlYWRlcixoZ3JvdXAsaHIsaW5zLG1haW4sbWFwLG1lbnUsbmF2LG9sLHByZSxzZWN0aW9uLHN1bW1hcnksdGFibGUsdWwiKSksdXZlPWp4KFQ5LHpkKCJhLGFiYnIsYWNyb255bSxhdWRpbyxiLGJkaSxiZG8sYmlnLGJyLGNpdGUsY29kZSxkZWwsZGZuLGVtLGZvbnQsaSxpbWcsaW5zLGtiZCxsYWJlbCxtYXAsbWFyayxwaWN0dXJlLHEscnVieSxycCxydCxzLHNhbXAsc21hbGwsc291cmNlLHNwYW4sc3RyaWtlLHN0cm9uZyxzdWIsc3VwLHRpbWUsdHJhY2ssdHQsdSx2YXIsdmlkZW8iKSksYkw9angoUzksY3ZlLHV2ZSxsdmUpLFQzPXpkKCJiYWNrZ3JvdW5kLGNpdGUsaHJlZixpdGVtdHlwZSxsb25nZGVzYyxwb3N0ZXIsc3JjLHhsaW5rOmhyZWYiKSxkdmU9emQoImFiYnIsYWNjZXNza2V5LGFsaWduLGFsdCxhdXRvcGxheSxheGlzLGJnY29sb3IsYm9yZGVyLGNlbGxwYWRkaW5nLGNlbGxzcGFjaW5nLGNsYXNzLGNsZWFyLGNvbG9yLGNvbHMsY29sc3Bhbixjb21wYWN0LGNvbnRyb2xzLGNvb3JkcyxkYXRldGltZSxkZWZhdWx0LGRpcixkb3dubG9hZCxmYWNlLGhlYWRlcnMsaGVpZ2h0LGhpZGRlbixocmVmbGFuZyxoc3BhY2UsaXNtYXAsaXRlbXNjb3BlLGl0ZW1wcm9wLGtpbmQsbGFiZWwsbGFuZyxsYW5ndWFnZSxsb29wLG1lZGlhLG11dGVkLG5vaHJlZixub3dyYXAsb3BlbixwcmVsb2FkLHJlbCxyZXYscm9sZSxyb3dzLHJvd3NwYW4scnVsZXMsc2NvcGUsc2Nyb2xsaW5nLHNoYXBlLHNpemUsc2l6ZXMsc3BhbixzcmNsYW5nLHNyY3NldCxzdGFydCxzdW1tYXJ5LHRhYmluZGV4LHRhcmdldCx0aXRsZSx0cmFuc2xhdGUsdHlwZSx1c2VtYXAsdmFsaWduLHZhbHVlLHZzcGFjZSx3aWR0aCIpLHB2ZT16ZCgiYXJpYS1hY3RpdmVkZXNjZW5kYW50LGFyaWEtYXRvbWljLGFyaWEtYXV0b2NvbXBsZXRlLGFyaWEtYnVzeSxhcmlhLWNoZWNrZWQsYXJpYS1jb2xjb3VudCxhcmlhLWNvbGluZGV4LGFyaWEtY29sc3BhbixhcmlhLWNvbnRyb2xzLGFyaWEtY3VycmVudCxhcmlhLWRlc2NyaWJlZGJ5LGFyaWEtZGV0YWlscyxhcmlhLWRpc2FibGVkLGFyaWEtZHJvcGVmZmVjdCxhcmlhLWVycm9ybWVzc2FnZSxhcmlhLWV4cGFuZGVkLGFyaWEtZmxvd3RvLGFyaWEtZ3JhYmJlZCxhcmlhLWhhc3BvcHVwLGFyaWEtaGlkZGVuLGFyaWEtaW52YWxpZCxhcmlhLWtleXNob3J0Y3V0cyxhcmlhLWxhYmVsLGFyaWEtbGFiZWxsZWRieSxhcmlhLWxldmVsLGFyaWEtbGl2ZSxhcmlhLW1vZGFsLGFyaWEtbXVsdGlsaW5lLGFyaWEtbXVsdGlzZWxlY3RhYmxlLGFyaWEtb3JpZW50YXRpb24sYXJpYS1vd25zLGFyaWEtcGxhY2Vob2xkZXIsYXJpYS1wb3NpbnNldCxhcmlhLXByZXNzZWQsYXJpYS1yZWFkb25seSxhcmlhLXJlbGV2YW50LGFyaWEtcmVxdWlyZWQsYXJpYS1yb2xlZGVzY3JpcHRpb24sYXJpYS1yb3djb3VudCxhcmlhLXJvd2luZGV4LGFyaWEtcm93c3BhbixhcmlhLXNlbGVjdGVkLGFyaWEtc2V0c2l6ZSxhcmlhLXNvcnQsYXJpYS12YWx1ZW1heCxhcmlhLXZhbHVlbWluLGFyaWEtdmFsdWVub3csYXJpYS12YWx1ZXRleHQiKSxEOT1qeChUMyxkdmUscHZlKSxodmU9emQoInNjcmlwdCxzdHlsZSx0ZW1wbGF0ZSIpLGZ2ZT0vW1x1RDgwMC1cdURCRkZdW1x1REMwMC1cdURGRkZdL2csbXZlPS8oW15cIy1+IHwhXSkvZztmdW5jdGlvbiBYVyhuKXtyZXR1cm4gbi5yZXBsYWNlKC8mL2csIiZhbXA7IikucmVwbGFjZShmdmUsZnVuY3Rpb24odCl7cmV0dXJuIiYjIisoMTAyNCoodC5jaGFyQ29kZUF0KDApLTU1Mjk2KSsodC5jaGFyQ29kZUF0KDEpLTU2MzIwKSs2NTUzNikrIjsifSkucmVwbGFjZShtdmUsZnVuY3Rpb24odCl7cmV0dXJuIiYjIit0LmNoYXJDb2RlQXQoMCkrIjsifSkucmVwbGFjZSgvPC9nLCImbHQ7IikucmVwbGFjZSgvPi9nLCImZ3Q7Iil9ZnVuY3Rpb24gRDMobix0KXtsZXQgZT1udWxsO3RyeXtTMT1TMXx8dzkobik7bGV0IGk9dD9TdHJpbmcodCk6IiI7ZT1TMS5nZXRJbmVydEJvZHlFbGVtZW50KGkpO2xldCByPTUsbz1pO2Rve2lmKDA9PT1yKXRocm93IG5ldyBFcnJvcigiRmFpbGVkIHRvIHNhbml0aXplIGh0bWwgYmVjYXVzZSB0aGUgaW5wdXQgaXMgdW5zdGFibGUiKTtyLS0saT1vLG89ZS5pbm5lckhUTUwsZT1TMS5nZXRJbmVydEJvZHlFbGVtZW50KGkpfXdoaWxlKGkhPT1vKTtsZXQgYT0obmV3IGNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5zYW5pdGl6ZWRTb21ldGhpbmc9ITEsdGhpcy5idWY9W119c2FuaXRpemVDaGlsZHJlbih0KXtsZXQgZT10LmZpcnN0Q2hpbGQsaT0hMDtmb3IoO2U7KWlmKGUubm9kZVR5cGU9PT1Ob2RlLkVMRU1FTlRfTk9ERT9pPXRoaXMuc3RhcnRFbGVtZW50KGUpOmUubm9kZVR5cGU9PT1Ob2RlLlRFWFRfTk9ERT90aGlzLmNoYXJzKGUubm9kZVZhbHVlKTp0aGlzLnNhbml0aXplZFNvbWV0aGluZz0hMCxpJiZlLmZpcnN0Q2hpbGQpZT1lLmZpcnN0Q2hpbGQ7ZWxzZSBmb3IoO2U7KXtlLm5vZGVUeXBlPT09Tm9kZS5FTEVNRU5UX05PREUmJnRoaXMuZW5kRWxlbWVudChlKTtsZXQgcj10aGlzLmNoZWNrQ2xvYmJlcmVkRWxlbWVudChlLGUubmV4dFNpYmxpbmcpO2lmKHIpe2U9cjticmVha31lPXRoaXMuY2hlY2tDbG9iYmVyZWRFbGVtZW50KGUsZS5wYXJlbnROb2RlKX1yZXR1cm4gdGhpcy5idWYuam9pbigiIil9c3RhcnRFbGVtZW50KHQpe2xldCBlPXQubm9kZU5hbWUudG9Mb3dlckNhc2UoKTtpZighYkwuaGFzT3duUHJvcGVydHkoZSkpcmV0dXJuIHRoaXMuc2FuaXRpemVkU29tZXRoaW5nPSEwLCFodmUuaGFzT3duUHJvcGVydHkoZSk7dGhpcy5idWYucHVzaCgiPCIpLHRoaXMuYnVmLnB1c2goZSk7bGV0IGk9dC5hdHRyaWJ1dGVzO2ZvcihsZXQgcj0wO3I8aS5sZW5ndGg7cisrKXtsZXQgbz1pLml0ZW0ocikscz1vLm5hbWUsYT1zLnRvTG93ZXJDYXNlKCk7aWYoIUQ5Lmhhc093blByb3BlcnR5KGEpKXt0aGlzLnNhbml0aXplZFNvbWV0aGluZz0hMDtjb250aW51ZX1sZXQgbD1vLnZhbHVlO1QzW2FdJiYobD16eChsKSksdGhpcy5idWYucHVzaCgiICIscywnPSInLFhXKGwpLCciJyl9cmV0dXJuIHRoaXMuYnVmLnB1c2goIj4iKSwhMH1lbmRFbGVtZW50KHQpe2xldCBlPXQubm9kZU5hbWUudG9Mb3dlckNhc2UoKTtiTC5oYXNPd25Qcm9wZXJ0eShlKSYmIVM5Lmhhc093blByb3BlcnR5KGUpJiYodGhpcy5idWYucHVzaCgiPC8iKSx0aGlzLmJ1Zi5wdXNoKGUpLHRoaXMuYnVmLnB1c2goIj4iKSl9Y2hhcnModCl7dGhpcy5idWYucHVzaChYVyh0KSl9Y2hlY2tDbG9iYmVyZWRFbGVtZW50KHQsZSl7aWYoZSYmKHQuY29tcGFyZURvY3VtZW50UG9zaXRpb24oZSkmTm9kZS5ET0NVTUVOVF9QT1NJVElPTl9DT05UQUlORURfQlkpPT09Tm9kZS5ET0NVTUVOVF9QT1NJVElPTl9DT05UQUlORURfQlkpdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gc2FuaXRpemUgaHRtbCBiZWNhdXNlIHRoZSBlbGVtZW50IGlzIGNsb2JiZXJlZDogJHt0Lm91dGVySFRNTH1gKTtyZXR1cm4gZX19KS5zYW5pdGl6ZUNoaWxkcmVuKENMKGUpfHxlKTtyZXR1cm4gTzAoYSl9ZmluYWxseXtpZihlKXtsZXQgaT1DTChlKXx8ZTtmb3IoO2kuZmlyc3RDaGlsZDspaS5yZW1vdmVDaGlsZChpLmZpcnN0Q2hpbGQpfX19ZnVuY3Rpb24gQ0wobil7cmV0dXJuImNvbnRlbnQiaW4gbiYmZnVuY3Rpb24obil7cmV0dXJuIG4ubm9kZVR5cGU9PT1Ob2RlLkVMRU1FTlRfTk9ERSYmIlRFTVBMQVRFIj09PW4ubm9kZU5hbWV9KG4pP24uY29udGVudDpudWxsfXZhciBtbz0oKCk9PntyZXR1cm4obj1tb3x8KG1vPXt9KSlbbi5OT05FPTBdPSJOT05FIixuW24uSFRNTD0xXT0iSFRNTCIsbltuLlNUWUxFPTJdPSJTVFlMRSIsbltuLlNDUklQVD0zXT0iU0NSSVBUIixuW24uVVJMPTRdPSJVUkwiLG5bbi5SRVNPVVJDRV9VUkw9NV09IlJFU09VUkNFX1VSTCIsbW87dmFyIG59KSgpO2Z1bmN0aW9uIEEzKG4pe2xldCB0PUd4KCk7cmV0dXJuIHQ/V1codC5zYW5pdGl6ZShtby5IVE1MLG4pfHwiIik6UGMobiwiSFRNTCIpP1dXKFRhKG4pKTpEMyhfOSgpLEtuKG4pKX1mdW5jdGlvbiB6bChuKXtsZXQgdD1HeCgpO3JldHVybiB0P3Quc2FuaXRpemUobW8uVVJMLG4pfHwiIjpQYyhuLCJVUkwiKT9UYShuKTp6eChLbihuKSl9ZnVuY3Rpb24gQTkobil7bGV0IHQ9R3goKTtpZih0KXJldHVybiBZVyh0LnNhbml0aXplKG1vLlJFU09VUkNFX1VSTCxuKXx8IiIpO2lmKFBjKG4sIlJlc291cmNlVVJMIikpcmV0dXJuIFlXKFRhKG4pKTt0aHJvdyBuZXcgQXQoOTA0LCExKX1mdW5jdGlvbiBHeCgpe2xldCBuPXJ0KCk7cmV0dXJuIG4mJm5bMTJdfXZhciBtVD1uZXcgcGUoIkVOVklST05NRU5UX0lOSVRJQUxJWkVSIiksSTk9bmV3IHBlKCJJTkpFQ1RPUiIsLTEpLFA5PW5ldyBwZSgiSU5KRUNUT1JfREVGX1RZUEVTIiksSzE9Y2xhc3N7Z2V0KHQsZT1UeCl7aWYoZT09PVR4KXtsZXQgaT1uZXcgRXJyb3IoYE51bGxJbmplY3RvckVycm9yOiBObyBwcm92aWRlciBmb3IgJHtUbyh0KX0hYCk7dGhyb3cgaS5uYW1lPSJOdWxsSW5qZWN0b3JFcnJvciIsaX1yZXR1cm4gZX19O2Z1bmN0aW9uIE12ZSguLi5uKXtyZXR1cm57Ilx1MDI3NXByb3ZpZGVycyI6UjkoMCxuKX19ZnVuY3Rpb24gUjkobiwuLi50KXtsZXQgcixlPVtdLGk9bmV3IFNldDtyZXR1cm4gRXgodCxvPT57bGV0IHM9bztNTChzLGUsW10saSkmJihyfHwocj1bXSksci5wdXNoKHMpKX0pLHZvaWQgMCE9PXImJk85KHIsZSksZX1mdW5jdGlvbiBPOShuLHQpe2ZvcihsZXQgZT0wO2U8bi5sZW5ndGg7ZSsrKXtsZXR7cHJvdmlkZXJzOnJ9PW5bZV07RXgocixvPT57dC5wdXNoKG8pfSl9fWZ1bmN0aW9uIE1MKG4sdCxlLGkpe2lmKCEobj1LaShuKSkpcmV0dXJuITE7bGV0IHI9bnVsbCxvPWtXKG4pLHM9IW8mJk5sKG4pO2lmKG98fHMpe2lmKHMmJiFzLnN0YW5kYWxvbmUpcmV0dXJuITE7cj1ufWVsc2V7bGV0IGw9bi5uZ01vZHVsZTtpZihvPWtXKGwpLCFvKXJldHVybiExO3I9bH1sZXQgYT1pLmhhcyhyKTtpZihzKXtpZihhKXJldHVybiExO2lmKGkuYWRkKHIpLHMuZGVwZW5kZW5jaWVzKXtsZXQgbD0iZnVuY3Rpb24iPT10eXBlb2Ygcy5kZXBlbmRlbmNpZXM/cy5kZXBlbmRlbmNpZXMoKTpzLmRlcGVuZGVuY2llcztmb3IobGV0IGMgb2YgbClNTChjLHQsZSxpKX19ZWxzZXtpZighbylyZXR1cm4hMTt7aWYobnVsbCE9by5pbXBvcnRzJiYhYSl7bGV0IGM7aS5hZGQocik7dHJ5e0V4KG8uaW1wb3J0cyx1PT57TUwodSx0LGUsaSkmJihjfHwoYz1bXSksYy5wdXNoKHUpKX0pfWZpbmFsbHl7fXZvaWQgMCE9PWMmJk85KGMsdCl9aWYoIWEpe2xldCBjPVdmKHIpfHwoKCk9Pm5ldyByKTt0LnB1c2goe3Byb3ZpZGU6cix1c2VGYWN0b3J5OmMsZGVwczpRaX0se3Byb3ZpZGU6UDksdXNlVmFsdWU6cixtdWx0aTohMH0se3Byb3ZpZGU6bVQsdXNlVmFsdWU6KCk9PmoociksbXVsdGk6ITB9KX1sZXQgbD1vLnByb3ZpZGVycztudWxsPT1sfHxhfHxFeChsLHU9Pnt0LnB1c2godSl9KX19cmV0dXJuIHIhPT1uJiZ2b2lkIDAhPT1uLnByb3ZpZGVyc312YXIgd3ZlPW1yKHtwcm92aWRlOlN0cmluZyx1c2VWYWx1ZTptcn0pO2Z1bmN0aW9uIGs5KG4pe3JldHVybiBudWxsIT09biYmIm9iamVjdCI9PXR5cGVvZiBuJiZ3dmUgaW4gbn1mdW5jdGlvbiBrMChuKXtyZXR1cm4iZnVuY3Rpb24iPT10eXBlb2Ygbn12YXIgVk4sZ1Q9bmV3IHBlKCJTZXQgSW5qZWN0b3Igc2NvcGUuIiksUDE9e30sRHZlPXt9O2Z1bmN0aW9uIEkzKCl7cmV0dXJuIHZvaWQgMD09PVZOJiYoVk49bmV3IEsxKSxWTn12YXIganA9Y2xhc3N7fSxaMT1jbGFzcyBleHRlbmRzIGpwe2NvbnN0cnVjdG9yKHQsZSxpLHIpe3N1cGVyKCksdGhpcy5wYXJlbnQ9ZSx0aGlzLnNvdXJjZT1pLHRoaXMuc2NvcGVzPXIsdGhpcy5yZWNvcmRzPW5ldyBNYXAsdGhpcy5fbmdPbkRlc3Ryb3lIb29rcz1uZXcgU2V0LHRoaXMuX29uRGVzdHJveUhvb2tzPVtdLHRoaXMuX2Rlc3Ryb3llZD0hMSxTTCh0LHM9PnRoaXMucHJvY2Vzc1Byb3ZpZGVyKHMpKSx0aGlzLnJlY29yZHMuc2V0KEk5LEUwKHZvaWQgMCx0aGlzKSksci5oYXMoImVudmlyb25tZW50IikmJnRoaXMucmVjb3Jkcy5zZXQoanAsRTAodm9pZCAwLHRoaXMpKTtsZXQgbz10aGlzLnJlY29yZHMuZ2V0KGdUKTtudWxsIT1vJiYic3RyaW5nIj09dHlwZW9mIG8udmFsdWUmJnRoaXMuc2NvcGVzLmFkZChvLnZhbHVlKSx0aGlzLmluamVjdG9yRGVmVHlwZXM9bmV3IFNldCh0aGlzLmdldChQOS5tdWx0aSxRaSxkaS5TZWxmKSl9Z2V0IGRlc3Ryb3llZCgpe3JldHVybiB0aGlzLl9kZXN0cm95ZWR9ZGVzdHJveSgpe3RoaXMuYXNzZXJ0Tm90RGVzdHJveWVkKCksdGhpcy5fZGVzdHJveWVkPSEwO3RyeXtmb3IobGV0IHQgb2YgdGhpcy5fbmdPbkRlc3Ryb3lIb29rcyl0Lm5nT25EZXN0cm95KCk7Zm9yKGxldCB0IG9mIHRoaXMuX29uRGVzdHJveUhvb2tzKXQoKX1maW5hbGx5e3RoaXMucmVjb3Jkcy5jbGVhcigpLHRoaXMuX25nT25EZXN0cm95SG9va3MuY2xlYXIoKSx0aGlzLmluamVjdG9yRGVmVHlwZXMuY2xlYXIoKSx0aGlzLl9vbkRlc3Ryb3lIb29rcy5sZW5ndGg9MH19b25EZXN0cm95KHQpe3RoaXMuX29uRGVzdHJveUhvb2tzLnB1c2godCl9cnVuSW5Db250ZXh0KHQpe3RoaXMuYXNzZXJ0Tm90RGVzdHJveWVkKCk7bGV0IGU9eDAodGhpcyksaT1rbCh2b2lkIDApO3RyeXtyZXR1cm4gdCgpfWZpbmFsbHl7eDAoZSksa2woaSl9fWdldCh0LGU9VHgsaT1kaS5EZWZhdWx0KXt0aGlzLmFzc2VydE5vdERlc3Ryb3llZCgpO2xldCByPXgwKHRoaXMpLG89a2wodm9pZCAwKTt0cnl7aWYoIShpJmRpLlNraXBTZWxmKSl7bGV0IGE9dGhpcy5yZWNvcmRzLmdldCh0KTtpZih2b2lkIDA9PT1hKXtsZXQgbD0oImZ1bmN0aW9uIj09dHlwZW9mKG49dCl8fCJvYmplY3QiPT10eXBlb2YgbiYmbiBpbnN0YW5jZW9mIHBlKSYmYTModCk7YT1sJiZ0aGlzLmluamVjdGFibGVEZWZJblNjb3BlKGwpP0UwKHdMKHQpLFAxKTpudWxsLHRoaXMucmVjb3Jkcy5zZXQodCxhKX1pZihudWxsIT1hKXJldHVybiB0aGlzLmh5ZHJhdGUodCxhKX1yZXR1cm4oaSZkaS5TZWxmP0kzKCk6dGhpcy5wYXJlbnQpLmdldCh0LGU9aSZkaS5PcHRpb25hbCYmZT09PVR4P251bGw6ZSl9Y2F0Y2gocyl7aWYoIk51bGxJbmplY3RvckVycm9yIj09PXMubmFtZSl7aWYoKHNbUTFdPXNbUTFdfHxbXSkudW5zaGlmdChUbyh0KSkscil0aHJvdyBzO3JldHVybiBmdW5jdGlvbihuLHQsZSxpKXtsZXQgcj1uW1ExXTt0aHJvdyB0W3pXXSYmci51bnNoaWZ0KHRbelddKSxuLm1lc3NhZ2U9ZnVuY3Rpb24obix0LGUsaT1udWxsKXtuPW4mJiJcbiI9PT1uLmNoYXJBdCgwKSYmIlx1MDI3NSI9PW4uY2hhckF0KDEpP24uc2xpY2UoMik6bjtsZXQgcj1Ubyh0KTtpZihBcnJheS5pc0FycmF5KHQpKXI9dC5tYXAoVG8pLmpvaW4oIiAtPiAiKTtlbHNlIGlmKCJvYmplY3QiPT10eXBlb2YgdCl7bGV0IG89W107Zm9yKGxldCBzIGluIHQpaWYodC5oYXNPd25Qcm9wZXJ0eShzKSl7bGV0IGE9dFtzXTtvLnB1c2gocysiOiIrKCJzdHJpbmciPT10eXBlb2YgYT9KU09OLnN0cmluZ2lmeShhKTpUbyhhKSkpfXI9YHske28uam9pbigiLCAiKX19YH1yZXR1cm5gJHtlfSR7aT8iKCIraSsiKSI6IiJ9WyR7cn1dOiAke24ucmVwbGFjZSh6X2UsIlxuICAiKX1gfSgiXG4iK24ubWVzc2FnZSxyLGUsaSksbi5uZ1Rva2VuUGF0aD1yLG5bUTFdPW51bGwsbn0ocyx0LCJSM0luamVjdG9yRXJyb3IiLHRoaXMuc291cmNlKX10aHJvdyBzfWZpbmFsbHl7a2wobykseDAocil9dmFyIG59cmVzb2x2ZUluamVjdG9ySW5pdGlhbGl6ZXJzKCl7bGV0IHQ9eDAodGhpcyksZT1rbCh2b2lkIDApO3RyeXtsZXQgaT10aGlzLmdldChtVC5tdWx0aSxRaSxkaS5TZWxmKTtmb3IobGV0IHIgb2YgaSlyKCl9ZmluYWxseXt4MCh0KSxrbChlKX19dG9TdHJpbmcoKXtsZXQgdD1bXSxlPXRoaXMucmVjb3Jkcztmb3IobGV0IGkgb2YgZS5rZXlzKCkpdC5wdXNoKFRvKGkpKTtyZXR1cm5gUjNJbmplY3Rvclske3Quam9pbigiLCAiKX1dYH1hc3NlcnROb3REZXN0cm95ZWQoKXtpZih0aGlzLl9kZXN0cm95ZWQpdGhyb3cgbmV3IEF0KDIwNSwhMSl9cHJvY2Vzc1Byb3ZpZGVyKHQpe2xldCBlPWswKHQ9S2kodCkpP3Q6S2kodCYmdC5wcm92aWRlKSxpPWZ1bmN0aW9uKG4pe3JldHVybiBrOShuKT9FMCh2b2lkIDAsbi51c2VWYWx1ZSk6RTAoRjkobiksUDEpfSh0KTtpZihrMCh0KXx8ITAhPT10Lm11bHRpKXRoaXMucmVjb3Jkcy5nZXQoZSk7ZWxzZXtsZXQgcj10aGlzLnJlY29yZHMuZ2V0KGUpO3J8fChyPUUwKHZvaWQgMCxQMSwhMCksci5mYWN0b3J5PSgpPT5kTChyLm11bHRpKSx0aGlzLnJlY29yZHMuc2V0KGUscikpLGU9dCxyLm11bHRpLnB1c2godCl9dGhpcy5yZWNvcmRzLnNldChlLGkpfWh5ZHJhdGUodCxlKXtyZXR1cm4gZS52YWx1ZT09PVAxJiYoZS52YWx1ZT1EdmUsZS52YWx1ZT1lLmZhY3RvcnkoKSksIm9iamVjdCI9PXR5cGVvZiBlLnZhbHVlJiZlLnZhbHVlJiZudWxsIT09KG49ZS52YWx1ZSkmJiJvYmplY3QiPT10eXBlb2YgbiYmImZ1bmN0aW9uIj09dHlwZW9mIG4ubmdPbkRlc3Ryb3kmJnRoaXMuX25nT25EZXN0cm95SG9va3MuYWRkKGUudmFsdWUpLGUudmFsdWU7dmFyIG59aW5qZWN0YWJsZURlZkluU2NvcGUodCl7aWYoIXQucHJvdmlkZWRJbilyZXR1cm4hMTtsZXQgZT1LaSh0LnByb3ZpZGVkSW4pO3JldHVybiJzdHJpbmciPT10eXBlb2YgZT8iYW55Ij09PWV8fHRoaXMuc2NvcGVzLmhhcyhlKTp0aGlzLmluamVjdG9yRGVmVHlwZXMuaGFzKGUpfX07ZnVuY3Rpb24gd0wobil7bGV0IHQ9YTMobiksZT1udWxsIT09dD90LmZhY3Rvcnk6V2Yobik7aWYobnVsbCE9PWUpcmV0dXJuIGU7aWYobiBpbnN0YW5jZW9mIHBlKXRocm93IG5ldyBBdCgyMDQsITEpO2lmKG4gaW5zdGFuY2VvZiBGdW5jdGlvbilyZXR1cm4gZnVuY3Rpb24obil7bGV0IHQ9bi5sZW5ndGg7aWYodD4wKXRocm93IGZ4KHQsIj8iKSxuZXcgQXQoMjA0LCExKTtsZXQgZT1mdW5jdGlvbihuKXtsZXQgdD1uJiYobltMMV18fG5bRDddKTtpZih0KXtsZXQgZT1mdW5jdGlvbihuKXtpZihuLmhhc093blByb3BlcnR5KCJuYW1lIikpcmV0dXJuIG4ubmFtZTtsZXQgdD0oIiIrbikubWF0Y2goL15mdW5jdGlvblxzKihbXlxzKF0rKS8pO3JldHVybiBudWxsPT09dD8iIjp0WzFdfShuKTtyZXR1cm4gY29uc29sZS53YXJuKGBERVBSRUNBVEVEOiBESSBpcyBpbnN0YW50aWF0aW5nIGEgdG9rZW4gIiR7ZX0iIHRoYXQgaW5oZXJpdHMgaXRzIEBJbmplY3RhYmxlIGRlY29yYXRvciBidXQgZG9lcyBub3QgcHJvdmlkZSBvbmUgaXRzZWxmLlxuVGhpcyB3aWxsIGJlY29tZSBhbiBlcnJvciBpbiBhIGZ1dHVyZSB2ZXJzaW9uIG9mIEFuZ3VsYXIuIFBsZWFzZSBhZGQgQEluamVjdGFibGUoKSB0byB0aGUgIiR7ZX0iIGNsYXNzLmApLHR9cmV0dXJuIG51bGx9KG4pO3JldHVybiBudWxsIT09ZT8oKT0+ZS5mYWN0b3J5KG4pOigpPT5uZXcgbn0obik7dGhyb3cgbmV3IEF0KDIwNCwhMSl9ZnVuY3Rpb24gRjkobix0LGUpe2xldCBpO2lmKGswKG4pKXtsZXQgcj1LaShuKTtyZXR1cm4gV2Yocil8fHdMKHIpfWlmKGs5KG4pKWk9KCk9PktpKG4udXNlVmFsdWUpO2Vsc2UgaWYoZnVuY3Rpb24obil7cmV0dXJuISghbnx8IW4udXNlRmFjdG9yeSl9KG4pKWk9KCk9Pm4udXNlRmFjdG9yeSguLi5kTChuLmRlcHN8fFtdKSk7ZWxzZSBpZihmdW5jdGlvbihuKXtyZXR1cm4hKCFufHwhbi51c2VFeGlzdGluZyl9KG4pKWk9KCk9PmooS2kobi51c2VFeGlzdGluZykpO2Vsc2V7bGV0IHI9S2kobiYmKG4udXNlQ2xhc3N8fG4ucHJvdmlkZSkpO2lmKCFmdW5jdGlvbihuKXtyZXR1cm4hIW4uZGVwc30obikpcmV0dXJuIFdmKHIpfHx3TChyKTtpPSgpPT5uZXcgciguLi5kTChuLmRlcHMpKX1yZXR1cm4gaX1mdW5jdGlvbiBFMChuLHQsZT0hMSl7cmV0dXJue2ZhY3Rvcnk6bix2YWx1ZTp0LG11bHRpOmU/W106dm9pZCAwfX1mdW5jdGlvbiBrdmUobil7cmV0dXJuISFuLlx1MDI3NXByb3ZpZGVyc31mdW5jdGlvbiBTTChuLHQpe2ZvcihsZXQgZSBvZiBuKUFycmF5LmlzQXJyYXkoZSk/U0woZSx0KTprdmUoZSk/U0woZS5cdTAyNzVwcm92aWRlcnMsdCk6dChlKX12YXIgRUw9Y2xhc3N7fSxKMT1jbGFzc3t9LFRMPWNsYXNze3Jlc29sdmVDb21wb25lbnRGYWN0b3J5KHQpe3Rocm93IGZ1bmN0aW9uKG4pe2xldCB0PUVycm9yKGBObyBjb21wb25lbnQgZmFjdG9yeSBmb3VuZCBmb3IgJHtUbyhuKX0uIERpZCB5b3UgYWRkIGl0IHRvIEBOZ01vZHVsZS5lbnRyeUNvbXBvbmVudHM/YCk7cmV0dXJuIHQubmdDb21wb25lbnQ9bix0fSh0KX19LGdzPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLk5VTEw9bmV3IFRMLG59KSgpO2Z1bmN0aW9uIEx2ZSgpe3JldHVybiBHMCh6bygpLHJ0KCkpfWZ1bmN0aW9uIEcwKG4sdCl7cmV0dXJuIG5ldyBSZShVbChuLHQpKX12YXIgUmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLm5hdGl2ZUVsZW1lbnQ9ZX19cmV0dXJuIG4uX19OR19FTEVNRU5UX0lEX189THZlLG59KSgpO2Z1bmN0aW9uIEJ2ZShuKXtyZXR1cm4gbiBpbnN0YW5jZW9mIFJlP24ubmF0aXZlRWxlbWVudDpufW5ldyBwZSgiUmVuZGVyZXIySW50ZXJjZXB0b3IiKTt2YXIgd3U9Y2xhc3N7fSxFdT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5fX05HX0VMRU1FTlRfSURfXz0oKT0+ZnVuY3Rpb24oKXtsZXQgbj1ydCgpLGU9cXAoem8oKS5pbmRleCxuKTtyZXR1cm4oemYoZSk/ZTpuKVsxMV19KCksbn0pKCksSHZlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4scHJvdmlkZWRJbjoicm9vdCIsZmFjdG9yeTooKT0+bnVsbH0pLG59KSgpLEljPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuZnVsbD10LHRoaXMubWFqb3I9dC5zcGxpdCgiLiIpWzBdLHRoaXMubWlub3I9dC5zcGxpdCgiLiIpWzFdLHRoaXMucGF0Y2g9dC5zcGxpdCgiLiIpLnNsaWNlKDIpLmpvaW4oIi4iKX19LFV2ZT1uZXcgSWMoIjE0LjIuMTEiKSxITj17fTtmdW5jdGlvbiBVTihuKXtyZXR1cm4gbi5uZ09yaWdpbmFsRXJyb3J9dmFyIFFzPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5fY29uc29sZT1jb25zb2xlfWhhbmRsZUVycm9yKHQpe2xldCBlPXRoaXMuX2ZpbmRPcmlnaW5hbEVycm9yKHQpO3RoaXMuX2NvbnNvbGUuZXJyb3IoIkVSUk9SIix0KSxlJiZ0aGlzLl9jb25zb2xlLmVycm9yKCJPUklHSU5BTCBFUlJPUiIsZSl9X2ZpbmRPcmlnaW5hbEVycm9yKHQpe2xldCBlPXQmJlVOKHQpO2Zvcig7ZSYmVU4oZSk7KWU9VU4oZSk7cmV0dXJuIGV8fG51bGx9fSxqdmU9L14+fF4tPnw8IS0tfC0tPnwtLSE+fDwhLSQvZyxHdmU9Lyg8fD4pLyxOOT1uZXcgTWFwLFl2ZT0wLFFXPSJfX25nQ29udGV4dF9fIjtmdW5jdGlvbiBTdShuLHQpe3pmKHQpPyhuW1FXXT10WzIwXSxmdW5jdGlvbihuKXtOOS5zZXQoblsyMF0sbil9KHQpKTpuW1FXXT10fWZ1bmN0aW9uIFd4KG4pe3JldHVybiBuLm93bmVyRG9jdW1lbnQuZGVmYXVsdFZpZXd9ZnVuY3Rpb24gX1Qobil7cmV0dXJuIG4ub3duZXJEb2N1bWVudH1mdW5jdGlvbiBSMShuKXtyZXR1cm4gbiBpbnN0YW5jZW9mIEZ1bmN0aW9uP24oKTpufXZhciBETCxCbD0oKCk9PntyZXR1cm4obj1CbHx8KEJsPXt9KSlbbi5JbXBvcnRhbnQ9MV09IkltcG9ydGFudCIsbltuLkRhc2hDYXNlPTJdPSJEYXNoQ2FzZSIsQmw7dmFyIG59KSgpO2Z1bmN0aW9uIFAzKG4sdCl7cmV0dXJuIERMKG4sdCl9ZnVuY3Rpb24gUjMobil7bGV0IHQ9blszXTtyZXR1cm4gVmQodCk/dFszXTp0fWZ1bmN0aW9uIE8zKG4pe3JldHVybiBCOShuWzEzXSl9ZnVuY3Rpb24gazMobil7cmV0dXJuIEI5KG5bNF0pfWZ1bmN0aW9uIEI5KG4pe2Zvcig7bnVsbCE9PW4mJiFWZChuKTspbj1uWzRdO3JldHVybiBufWZ1bmN0aW9uIFQwKG4sdCxlLGkscil7aWYobnVsbCE9aSl7bGV0IG8scz0hMTtWZChpKT9vPWk6emYoaSkmJihzPSEwLGk9aVswXSk7bGV0IGE9JGEoaSk7MD09PW4mJm51bGwhPT1lP251bGw9PXI/RzkodCxlLGEpOllmKHQsZSxhLHJ8fG51bGwsITApOjE9PT1uJiZudWxsIT09ZT9ZZih0LGUsYSxyfHxudWxsLCEwKToyPT09bj9LOSh0LGEscyk6Mz09PW4mJnQuZGVzdHJveU5vZGUoYSksbnVsbCE9byYmZnVuY3Rpb24obix0LGUsaSxyKXtsZXQgbz1lWzddO28hPT0kYShlKSYmVDAodCxuLGksbyxyKTtmb3IobGV0IGE9MTA7YTxlLmxlbmd0aDthKyspe2xldCBsPWVbYV07cXgobFsxXSxsLG4sdCxpLG8pfX0odCxuLG8sZSxyKX19ZnVuY3Rpb24gRjMobix0KXtyZXR1cm4gbi5jcmVhdGVUZXh0KHQpfWZ1bmN0aW9uIFY5KG4sdCxlKXtuLnNldFZhbHVlKHQsZSl9ZnVuY3Rpb24gZXllKG4sdCl7cmV0dXJuIG4uY3JlYXRlQ29tbWVudChmdW5jdGlvbihuKXtyZXR1cm4gbi5yZXBsYWNlKGp2ZSx0PT50LnJlcGxhY2UoR3ZlLCJcdTIwMGIkMVx1MjAwYiIpKX0odCkpfWZ1bmN0aW9uIE4zKG4sdCxlKXtyZXR1cm4gbi5jcmVhdGVFbGVtZW50KHQsZSl9ZnVuY3Rpb24gSDkobix0KXtsZXQgZT1uWzldLGk9ZS5pbmRleE9mKHQpLHI9dFszXTs1MTImdFsyXSYmKHRbMl0mPS01MTMsZjMociwtMSkpLGUuc3BsaWNlKGksMSl9ZnVuY3Rpb24gQUwobix0KXtpZihuLmxlbmd0aDw9MTApcmV0dXJuO2xldCBlPTEwK3QsaT1uW2VdO2lmKGkpe2xldCByPWlbMTddO251bGwhPT1yJiZyIT09biYmSDkocixpKSx0PjAmJihuW2UtMV1bNF09aVs0XSk7bGV0IG89WDEobiwxMCt0KTshZnVuY3Rpb24obix0KXtxeChuLHQsdFsxMV0sMixudWxsLG51bGwpLHRbMF09bnVsbCx0WzZdPW51bGx9KGlbMV0saSk7bGV0IHM9b1sxOV07bnVsbCE9PXMmJnMuZGV0YWNoVmlldyhvWzFdKSxpWzNdPW51bGwsaVs0XT1udWxsLGlbMl0mPS02NX1yZXR1cm4gaX1mdW5jdGlvbiBVOShuLHQpe2lmKCEoMTI4JnRbMl0pKXtsZXQgZT10WzExXTtlLmRlc3Ryb3lOb2RlJiZxeChuLHQsZSwzLG51bGwsbnVsbCksZnVuY3Rpb24obil7bGV0IHQ9blsxM107aWYoIXQpcmV0dXJuIHpOKG5bMV0sbik7Zm9yKDt0Oyl7bGV0IGU9bnVsbDtpZih6Zih0KSllPXRbMTNdO2Vsc2V7bGV0IGk9dFsxMF07aSYmKGU9aSl9aWYoIWUpe2Zvcig7dCYmIXRbNF0mJnQhPT1uOyl6Zih0KSYmek4odFsxXSx0KSx0PXRbM107bnVsbD09PXQmJih0PW4pLHpmKHQpJiZ6Tih0WzFdLHQpLGU9dCYmdFs0XX10PWV9fSh0KX19ZnVuY3Rpb24gek4obix0KXtpZighKDEyOCZ0WzJdKSl7dFsyXSY9LTY1LHRbMl18PTEyOCxmdW5jdGlvbihuLHQpe2xldCBlO2lmKG51bGwhPW4mJm51bGwhPShlPW4uZGVzdHJveUhvb2tzKSlmb3IobGV0IGk9MDtpPGUubGVuZ3RoO2krPTIpe2xldCByPXRbZVtpXV07aWYoIShyIGluc3RhbmNlb2YgcWYpKXtsZXQgbz1lW2krMV07aWYoQXJyYXkuaXNBcnJheShvKSlmb3IobGV0IHM9MDtzPG8ubGVuZ3RoO3MrPTIpe2xldCBhPXJbb1tzXV0sbD1vW3MrMV07dHJ5e2wuY2FsbChhKX1maW5hbGx5e319ZWxzZSB0cnl7by5jYWxsKHIpfWZpbmFsbHl7fX19fShuLHQpLGZ1bmN0aW9uKG4sdCl7bGV0IGU9bi5jbGVhbnVwLGk9dFs3XSxyPS0xO2lmKG51bGwhPT1lKWZvcihsZXQgbz0wO288ZS5sZW5ndGgtMTtvKz0yKWlmKCJzdHJpbmciPT10eXBlb2YgZVtvXSl7bGV0IHM9ZVtvKzFdLGE9ImZ1bmN0aW9uIj09dHlwZW9mIHM/cyh0KTokYSh0W3NdKSxsPWlbcj1lW28rMl1dLGM9ZVtvKzNdOyJib29sZWFuIj09dHlwZW9mIGM/YS5yZW1vdmVFdmVudExpc3RlbmVyKGVbb10sbCxjKTpjPj0wP2lbcj1jXSgpOmlbcj0tY10udW5zdWJzY3JpYmUoKSxvKz0yfWVsc2V7bGV0IHM9aVtyPWVbbysxXV07ZVtvXS5jYWxsKHMpfWlmKG51bGwhPT1pKXtmb3IobGV0IG89cisxO288aS5sZW5ndGg7bysrKSgwLGlbb10pKCk7dFs3XT1udWxsfX0obix0KSwxPT09dFsxXS50eXBlJiZ0WzExXS5kZXN0cm95KCk7bGV0IGU9dFsxN107aWYobnVsbCE9PWUmJlZkKHRbM10pKXtlIT09dFszXSYmSDkoZSx0KTtsZXQgaT10WzE5XTtudWxsIT09aSYmaS5kZXRhY2hWaWV3KG4pfSFmdW5jdGlvbihuKXtOOS5kZWxldGUoblsyMF0pfSh0KX19ZnVuY3Rpb24gejkobix0LGUpe3JldHVybiBqOShuLHQucGFyZW50LGUpfWZ1bmN0aW9uIGo5KG4sdCxlKXtsZXQgaT10O2Zvcig7bnVsbCE9PWkmJjQwJmkudHlwZTspaT0odD1pKS5wYXJlbnQ7aWYobnVsbD09PWkpcmV0dXJuIGVbMF07aWYoMiZpLmZsYWdzKXtsZXQgcj1uLmRhdGFbaS5kaXJlY3RpdmVTdGFydF0uZW5jYXBzdWxhdGlvbjtpZihyPT09SmEuTm9uZXx8cj09PUphLkVtdWxhdGVkKXJldHVybiBudWxsfXJldHVybiBVbChpLGUpfWZ1bmN0aW9uIFlmKG4sdCxlLGkscil7bi5pbnNlcnRCZWZvcmUodCxlLGkscil9ZnVuY3Rpb24gRzkobix0LGUpe24uYXBwZW5kQ2hpbGQodCxlKX1mdW5jdGlvbiBLVyhuLHQsZSxpLHIpe251bGwhPT1pP1lmKG4sdCxlLGkscik6Rzkobix0LGUpfWZ1bmN0aW9uIHZUKG4sdCl7cmV0dXJuIG4ucGFyZW50Tm9kZSh0KX1mdW5jdGlvbiBXOShuLHQsZSl7cmV0dXJuIFk5KG4sdCxlKX1mdW5jdGlvbiBxOShuLHQsZSl7cmV0dXJuIDQwJm4udHlwZT9VbChuLGUpOm51bGx9dmFyIElMLFk5PXE5O2Z1bmN0aW9uIFg5KG4sdCl7WTk9bixJTD10fWZ1bmN0aW9uIHlUKG4sdCxlLGkpe2xldCByPXo5KG4saSx0KSxvPXRbMTFdLGE9VzkoaS5wYXJlbnR8fHRbNl0saSx0KTtpZihudWxsIT1yKWlmKEFycmF5LmlzQXJyYXkoZSkpZm9yKGxldCBsPTA7bDxlLmxlbmd0aDtsKyspS1cobyxyLGVbbF0sYSwhMSk7ZWxzZSBLVyhvLHIsZSxhLCExKTt2b2lkIDAhPT1JTCYmSUwobyxpLHQsZSxyKX1mdW5jdGlvbiBPMShuLHQpe2lmKG51bGwhPT10KXtsZXQgZT10LnR5cGU7aWYoMyZlKXJldHVybiBVbCh0LG4pO2lmKDQmZSlyZXR1cm4gUEwoLTEsblt0LmluZGV4XSk7aWYoOCZlKXtsZXQgaT10LmNoaWxkO2lmKG51bGwhPT1pKXJldHVybiBPMShuLGkpO3tsZXQgcj1uW3QuaW5kZXhdO3JldHVybiBWZChyKT9QTCgtMSxyKTokYShyKX19aWYoMzImZSlyZXR1cm4gUDModCxuKSgpfHwkYShuW3QuaW5kZXhdKTt7bGV0IGk9UTkobix0KTtyZXR1cm4gbnVsbCE9PWk/QXJyYXkuaXNBcnJheShpKT9pWzBdOk8xKFIzKG5bMTZdKSxpKTpPMShuLHQubmV4dCl9fXJldHVybiBudWxsfWZ1bmN0aW9uIFE5KG4sdCl7cmV0dXJuIG51bGwhPT10P25bMTZdWzZdLnByb2plY3Rpb25bdC5wcm9qZWN0aW9uXTpudWxsfWZ1bmN0aW9uIFBMKG4sdCl7bGV0IGU9MTArbisxO2lmKGU8dC5sZW5ndGgpe2xldCBpPXRbZV0scj1pWzFdLmZpcnN0Q2hpbGQ7aWYobnVsbCE9PXIpcmV0dXJuIE8xKGkscil9cmV0dXJuIHRbN119ZnVuY3Rpb24gSzkobix0LGUpe2xldCBpPXZUKG4sdCk7aSYmZnVuY3Rpb24obix0LGUsaSl7bi5yZW1vdmVDaGlsZCh0LGUsaSl9KG4saSx0LGUpfWZ1bmN0aW9uIEwzKG4sdCxlLGkscixvLHMpe2Zvcig7bnVsbCE9ZTspe2xldCBhPWlbZS5pbmRleF0sbD1lLnR5cGU7aWYocyYmMD09PXQmJihhJiZTdSgkYShhKSxpKSxlLmZsYWdzfD00KSw2NCE9KDY0JmUuZmxhZ3MpKWlmKDgmbClMMyhuLHQsZS5jaGlsZCxpLHIsbywhMSksVDAodCxuLHIsYSxvKTtlbHNlIGlmKDMyJmwpe2xldCB1LGM9UDMoZSxpKTtmb3IoO3U9YygpOylUMCh0LG4scix1LG8pO1QwKHQsbixyLGEsbyl9ZWxzZSAxNiZsP1o5KG4sdCxpLGUscixvKTpUMCh0LG4scixhLG8pO2U9cz9lLnByb2plY3Rpb25OZXh0OmUubmV4dH19ZnVuY3Rpb24gcXgobix0LGUsaSxyLG8pe0wzKGUsaSxuLmZpcnN0Q2hpbGQsdCxyLG8sITEpfWZ1bmN0aW9uIFo5KG4sdCxlLGkscixvKXtsZXQgcz1lWzE2XSxsPXNbNl0ucHJvamVjdGlvbltpLnByb2plY3Rpb25dO2lmKEFycmF5LmlzQXJyYXkobCkpZm9yKGxldCBjPTA7YzxsLmxlbmd0aDtjKyspVDAodCxuLHIsbFtjXSxvKTtlbHNlIEwzKG4sdCxsLHNbM10scixvLCEwKX1mdW5jdGlvbiBKOShuLHQsZSl7bi5zZXRBdHRyaWJ1dGUodCwic3R5bGUiLGUpfWZ1bmN0aW9uIEIzKG4sdCxlKXsiIj09PWU/bi5yZW1vdmVBdHRyaWJ1dGUodCwiY2xhc3MiKTpuLnNldEF0dHJpYnV0ZSh0LCJjbGFzcyIsZSl9ZnVuY3Rpb24gJDkobix0LGUpe2xldCBpPW4ubGVuZ3RoO2Zvcig7Oyl7bGV0IHI9bi5pbmRleE9mKHQsZSk7aWYoLTE9PT1yKXJldHVybiByO2lmKDA9PT1yfHxuLmNoYXJDb2RlQXQoci0xKTw9MzIpe2xldCBvPXQubGVuZ3RoO2lmKHIrbz09PWl8fG4uY2hhckNvZGVBdChyK28pPD0zMilyZXR1cm4gcn1lPXIrMX19dmFyIGVxPSJuZy10ZW1wbGF0ZSI7ZnVuY3Rpb24gZnllKG4sdCxlKXtsZXQgaT0wO2Zvcig7aTxuLmxlbmd0aDspe2xldCByPW5baSsrXTtpZihlJiYiY2xhc3MiPT09cil7aWYocj1uW2ldLC0xIT09JDkoci50b0xvd2VyQ2FzZSgpLHQsMCkpcmV0dXJuITB9ZWxzZSBpZigxPT09cil7Zm9yKDtpPG4ubGVuZ3RoJiYic3RyaW5nIj09dHlwZW9mKHI9bltpKytdKTspaWYoci50b0xvd2VyQ2FzZSgpPT09dClyZXR1cm4hMDtyZXR1cm4hMX19cmV0dXJuITF9ZnVuY3Rpb24gdHEobil7cmV0dXJuIDQ9PT1uLnR5cGUmJm4udmFsdWUhPT1lcX1mdW5jdGlvbiBteWUobix0LGUpe3JldHVybiB0PT09KDQhPT1uLnR5cGV8fGU/bi52YWx1ZTplcSl9ZnVuY3Rpb24gZ3llKG4sdCxlKXtsZXQgaT00LHI9bi5hdHRyc3x8W10sbz1mdW5jdGlvbihuKXtmb3IobGV0IHQ9MDt0PG4ubGVuZ3RoO3QrKylpZigkNyhuW3RdKSlyZXR1cm4gdDtyZXR1cm4gbi5sZW5ndGh9KHIpLHM9ITE7Zm9yKGxldCBhPTA7YTx0Lmxlbmd0aDthKyspe2xldCBsPXRbYV07aWYoIm51bWJlciIhPXR5cGVvZiBsKXtpZighcylpZig0Jmkpe2lmKGk9MnwxJmksIiIhPT1sJiYhbXllKG4sbCxlKXx8IiI9PT1sJiYxPT09dC5sZW5ndGgpe2lmKFNjKGkpKXJldHVybiExO3M9ITB9fWVsc2V7bGV0IGM9OCZpP2w6dFsrK2FdO2lmKDgmaSYmbnVsbCE9PW4uYXR0cnMpe2lmKCFmeWUobi5hdHRycyxjLGUpKXtpZihTYyhpKSlyZXR1cm4hMTtzPSEwfWNvbnRpbnVlfWxldCBkPV95ZSg4Jmk/ImNsYXNzIjpsLHIsdHEobiksZSk7aWYoLTE9PT1kKXtpZihTYyhpKSlyZXR1cm4hMTtzPSEwO2NvbnRpbnVlfWlmKCIiIT09Yyl7bGV0IHA7cD1kPm8/IiI6cltkKzFdLnRvTG93ZXJDYXNlKCk7bGV0IGg9OCZpP3A6bnVsbDtpZihoJiYtMSE9PSQ5KGgsYywwKXx8MiZpJiZjIT09cCl7aWYoU2MoaSkpcmV0dXJuITE7cz0hMH19fX1lbHNle2lmKCFzJiYhU2MoaSkmJiFTYyhsKSlyZXR1cm4hMTtpZihzJiZTYyhsKSljb250aW51ZTtzPSExLGk9bHwxJml9fXJldHVybiBTYyhpKXx8c31mdW5jdGlvbiBTYyhuKXtyZXR1cm4gMD09KDEmbil9ZnVuY3Rpb24gX3llKG4sdCxlLGkpe2lmKG51bGw9PT10KXJldHVybi0xO2xldCByPTA7aWYoaXx8IWUpe2xldCBvPSExO2Zvcig7cjx0Lmxlbmd0aDspe2xldCBzPXRbcl07aWYocz09PW4pcmV0dXJuIHI7aWYoMz09PXN8fDY9PT1zKW89ITA7ZWxzZXtpZigxPT09c3x8Mj09PXMpe2xldCBhPXRbKytyXTtmb3IoOyJzdHJpbmciPT10eXBlb2YgYTspYT10Wysrcl07Y29udGludWV9aWYoND09PXMpYnJlYWs7aWYoMD09PXMpe3IrPTQ7Y29udGludWV9fXIrPW8/MToyfXJldHVybi0xfXJldHVybiBmdW5jdGlvbihuLHQpe2xldCBlPW4uaW5kZXhPZig0KTtpZihlPi0xKWZvcihlKys7ZTxuLmxlbmd0aDspe2xldCBpPW5bZV07aWYoIm51bWJlciI9PXR5cGVvZiBpKXJldHVybi0xO2lmKGk9PT10KXJldHVybiBlO2UrK31yZXR1cm4tMX0odCxuKX1mdW5jdGlvbiBucShuLHQsZT0hMSl7Zm9yKGxldCBpPTA7aTx0Lmxlbmd0aDtpKyspaWYoZ3llKG4sdFtpXSxlKSlyZXR1cm4hMDtyZXR1cm4hMX1mdW5jdGlvbiB4eWUobix0KXtlOmZvcihsZXQgZT0wO2U8dC5sZW5ndGg7ZSsrKXtsZXQgaT10W2VdO2lmKG4ubGVuZ3RoPT09aS5sZW5ndGgpe2ZvcihsZXQgcj0wO3I8bi5sZW5ndGg7cisrKWlmKG5bcl0hPT1pW3JdKWNvbnRpbnVlIGU7cmV0dXJuITB9fXJldHVybiExfWZ1bmN0aW9uIFpXKG4sdCl7cmV0dXJuIG4/Ijpub3QoIit0LnRyaW0oKSsiKSI6dH1mdW5jdGlvbiBDeWUobil7bGV0IHQ9blswXSxlPTEsaT0yLHI9IiIsbz0hMTtmb3IoO2U8bi5sZW5ndGg7KXtsZXQgcz1uW2VdO2lmKCJzdHJpbmciPT10eXBlb2YgcylpZigyJmkpe2xldCBhPW5bKytlXTtyKz0iWyIrcysoYS5sZW5ndGg+MD8nPSInK2ErJyInOiIiKSsiXSJ9ZWxzZSA4Jmk/cis9Ii4iK3M6NCZpJiYocis9IiAiK3MpO2Vsc2UiIiE9PXImJiFTYyhzKSYmKHQrPVpXKG8scikscj0iIiksaT1zLG89b3x8IVNjKGkpO2UrK31yZXR1cm4iIiE9PXImJih0Kz1aVyhvLHIpKSx0fXZhciBRbj17fTtmdW5jdGlvbiBDKG4pe2lxKEZpKCkscnQoKSxacygpK24sITEpfWZ1bmN0aW9uIGlxKG4sdCxlLGkpe2lmKCFpKWlmKDM9PSgzJnRbMl0pKXtsZXQgbz1uLnByZU9yZGVyQ2hlY2tIb29rcztudWxsIT09byYmRDEodCxvLGUpfWVsc2V7bGV0IG89bi5wcmVPcmRlckhvb2tzO251bGwhPT1vJiZBMSh0LG8sMCxlKX16cChlKX12YXIgSlc9eyJcdTAyNzVcdTAyNzVkZWZpbmVJbmplY3RhYmxlIjp5ZSwiXHUwMjc1XHUwMjc1ZGVmaW5lSW5qZWN0b3IiOlYsIlx1MDI3NVx1MDI3NWluamVjdCI6aiwiXHUwMjc1XHUwMjc1aW52YWxpZEZhY3RvcnlEZXAiOmQ5LHJlc29sdmVGb3J3YXJkUmVmOktpfTt2YXIgRXllPW1yKHtwcm92aWRlOlN0cmluZyx1c2VWYWx1ZTptcn0pO2Z1bmN0aW9uICRXKG4pe3JldHVybiB2b2lkIDAhPT1uLnVzZUNsYXNzfWZ1bmN0aW9uIGU3KG4pe3JldHVybiB2b2lkIDAhPT1uLnVzZUZhY3Rvcnl9dmFyIHJxPVZ4KCJJbmplY3RhYmxlIix2b2lkIDAsdm9pZCAwLHZvaWQgMCwobix0KT0+ZnVuY3Rpb24obix0KXtsZXQgZT1udWxsLGk9bnVsbDtuLmhhc093blByb3BlcnR5KEwxKXx8T2JqZWN0LmRlZmluZVByb3BlcnR5KG4sTDEse2dldDooKT0+KG51bGw9PT1lJiYoZT1MbCgpLmNvbXBpbGVJbmplY3RhYmxlKEpXLGBuZzovLy8ke24ubmFtZX0vXHUwMjc1cHJvdi5qc2AsZnVuY3Rpb24obix0KXtsZXQgZT10fHx7cHJvdmlkZWRJbjpudWxsfSxpPXtuYW1lOm4ubmFtZSx0eXBlOm4sdHlwZUFyZ3VtZW50Q291bnQ6MCxwcm92aWRlZEluOmUucHJvdmlkZWRJbn07cmV0dXJuKCRXKGUpfHxlNyhlKSkmJnZvaWQgMCE9PWUuZGVwcyYmKGkuZGVwcz1wOShlLmRlcHMpKSwkVyhlKT9pLnVzZUNsYXNzPWUudXNlQ2xhc3M6ZnVuY3Rpb24obil7cmV0dXJuIEV5ZSBpbiBufShlKT9pLnVzZVZhbHVlPWUudXNlVmFsdWU6ZTcoZSk/aS51c2VGYWN0b3J5PWUudXNlRmFjdG9yeTpmdW5jdGlvbihuKXtyZXR1cm4gdm9pZCAwIT09bi51c2VFeGlzdGluZ30oZSkmJihpLnVzZUV4aXN0aW5nPWUudXNlRXhpc3RpbmcpLGl9KG4sdCkpKSxlKX0pLG4uaGFzT3duUHJvcGVydHkoTmQpfHxPYmplY3QuZGVmaW5lUHJvcGVydHkobixOZCx7Z2V0OigpPT57aWYobnVsbD09PWkpe2xldCByPUxsKCk7aT1yLmNvbXBpbGVGYWN0b3J5KEpXLGBuZzovLy8ke24ubmFtZX0vXHUwMjc1ZmFjLmpzYCx7bmFtZTpuLm5hbWUsdHlwZTpuLHR5cGVBcmd1bWVudENvdW50OjAsZGVwczpmVChuKSx0YXJnZXQ6ci5GYWN0b3J5VGFyZ2V0LkluamVjdGFibGV9KX1yZXR1cm4gaX0sY29uZmlndXJhYmxlOiEwfSl9KG4sdCkpO2Z1bmN0aW9uIHQ3KG4sdD1udWxsLGU9bnVsbCxpKXtsZXQgcj1vcShuLHQsZSxpKTtyZXR1cm4gci5yZXNvbHZlSW5qZWN0b3JJbml0aWFsaXplcnMoKSxyfWZ1bmN0aW9uIG9xKG4sdD1udWxsLGU9bnVsbCxpLHI9bmV3IFNldCl7bGV0IG89W2V8fFFpLE12ZShuKV07cmV0dXJuIGk9aXx8KCJvYmplY3QiPT10eXBlb2Ygbj92b2lkIDA6VG8obikpLG5ldyBaMShvLHR8fEkzKCksaXx8bnVsbCxyKX12YXIgWG49KCgpPT57Y2xhc3MgbntzdGF0aWMgY3JlYXRlKGUsaSl7aWYoQXJyYXkuaXNBcnJheShlKSlyZXR1cm4gdDcoe25hbWU6IiJ9LGksZSwiIik7e2xldCByPWUubmFtZT8/IiI7cmV0dXJuIHQ3KHtuYW1lOnJ9LGUucGFyZW50LGUucHJvdmlkZXJzLHIpfX19cmV0dXJuIG4uVEhST1dfSUZfTk9UX0ZPVU5EPVR4LG4uTlVMTD1uZXcgSzEsbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLHByb3ZpZGVkSW46ImFueSIsZmFjdG9yeTooKT0+aihJOSl9KSxuLl9fTkdfRUxFTUVOVF9JRF9fPS0xLG59KSgpLGd4PWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7aWYodGhpcy50b2tlbj10LHRoaXMuaWQ9ZSwhdCl0aHJvdyBuZXcgQXQoMjA4LCExKTt0aGlzLmRpc3BsYXlOYW1lPVRvKHRoaXMudG9rZW4pfXN0YXRpYyBnZXQodCl7cmV0dXJuIG43LmdldChLaSh0KSl9c3RhdGljIGdldCBudW1iZXJPZktleXMoKXtyZXR1cm4gbjcubnVtYmVyT2ZLZXlzfX0sbjc9bmV3IGNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5fYWxsS2V5cz1uZXcgTWFwfWdldCh0KXtpZih0IGluc3RhbmNlb2YgZ3gpcmV0dXJuIHQ7aWYodGhpcy5fYWxsS2V5cy5oYXModCkpcmV0dXJuIHRoaXMuX2FsbEtleXMuZ2V0KHQpO2xldCBlPW5ldyBneCh0LGd4Lm51bWJlck9mS2V5cyk7cmV0dXJuIHRoaXMuX2FsbEtleXMuc2V0KHQsZSksZX1nZXQgbnVtYmVyT2ZLZXlzKCl7cmV0dXJuIHRoaXMuX2FsbEtleXMuc2l6ZX19O2Z1bmN0aW9uIE0obix0PWRpLkRlZmF1bHQpe2xldCBlPXJ0KCk7cmV0dXJuIG51bGw9PT1lP2oobix0KTpzOSh6bygpLGUsS2kobiksdCl9ZnVuY3Rpb24gbmwoKXt0aHJvdyBuZXcgRXJyb3IoImludmFsaWQiKX1mdW5jdGlvbiBFMShuLHQpe3JldHVybiBuPDwxN3x0PDwyfWZ1bmN0aW9uIFhmKG4pe3JldHVybiBuPj4xNyYzMjc2N31mdW5jdGlvbiBPTChuKXtyZXR1cm4gMnxufWZ1bmN0aW9uIEYwKG4pe3JldHVybigxMzEwNjgmbik+PjJ9ZnVuY3Rpb24gak4obix0KXtyZXR1cm4tMTMxMDY5Jm58dDw8Mn1mdW5jdGlvbiBrTChuKXtyZXR1cm4gMXxufWZ1bmN0aW9uIHNxKG4sdCl7bGV0IGU9bi5jb250ZW50UXVlcmllcztpZihudWxsIT09ZSlmb3IobGV0IGk9MDtpPGUubGVuZ3RoO2krPTIpe2xldCByPWVbaV0sbz1lW2krMV07aWYoLTEhPT1vKXtsZXQgcz1uLmRhdGFbb107djMocikscy5jb250ZW50UXVlcmllcygyLHRbb10sbyl9fX1mdW5jdGlvbiBiVChuLHQsZSxpLHIsbyxzLGEsbCxjLHUpe2xldCBkPXQuYmx1ZXByaW50LnNsaWNlKCk7cmV0dXJuIGRbMF09cixkWzJdPTc2fGksKG51bGwhPT11fHxuJiYxMDI0Jm5bMl0pJiYoZFsyXXw9MTAyNCksVTcoZCksZFszXT1kWzE1XT1uLGRbOF09ZSxkWzEwXT1zfHxuJiZuWzEwXSxkWzExXT1hfHxuJiZuWzExXSxkWzEyXT1sfHxuJiZuWzEyXXx8bnVsbCxkWzldPWN8fG4mJm5bOV18fG51bGwsZFs2XT1vLGRbMjBdPVl2ZSsrLGRbMjFdPXUsZFsxNl09Mj09dC50eXBlP25bMTZdOmQsZH1mdW5jdGlvbiBXMChuLHQsZSxpLHIpe2xldCBvPW4uZGF0YVt0XTtpZihudWxsPT09bylvPVYzKG4sdCxlLGksciksWm4ubEZyYW1lLmluSTE4biYmKG8uZmxhZ3N8PTY0KTtlbHNlIGlmKDY0Jm8udHlwZSl7by50eXBlPWUsby52YWx1ZT1pLG8uYXR0cnM9cjtsZXQgcz13eCgpO28uaW5qZWN0b3JJbmRleD1udWxsPT09cz8tMTpzLmluamVjdG9ySW5kZXh9cmV0dXJuIE11KG8sITApLG99ZnVuY3Rpb24gVjMobix0LGUsaSxyKXtsZXQgbz1qNygpLHM9bTMoKSxsPW4uZGF0YVt0XT1mdW5jdGlvbihuLHQsZSxpLHIsbyl7cmV0dXJue3R5cGU6ZSxpbmRleDppLGluc2VydEJlZm9yZUluZGV4Om51bGwsaW5qZWN0b3JJbmRleDp0P3QuaW5qZWN0b3JJbmRleDotMSxkaXJlY3RpdmVTdGFydDotMSxkaXJlY3RpdmVFbmQ6LTEsZGlyZWN0aXZlU3R5bGluZ0xhc3Q6LTEscHJvcGVydHlCaW5kaW5nczpudWxsLGZsYWdzOjAscHJvdmlkZXJJbmRleGVzOjAsdmFsdWU6cixhdHRyczpvLG1lcmdlZEF0dHJzOm51bGwsbG9jYWxOYW1lczpudWxsLGluaXRpYWxJbnB1dHM6dm9pZCAwLGlucHV0czpudWxsLG91dHB1dHM6bnVsbCx0Vmlld3M6bnVsbCxuZXh0Om51bGwscHJvamVjdGlvbk5leHQ6bnVsbCxjaGlsZDpudWxsLHBhcmVudDp0LHByb2plY3Rpb246bnVsbCxzdHlsZXM6bnVsbCxzdHlsZXNXaXRob3V0SG9zdDpudWxsLHJlc2lkdWFsU3R5bGVzOnZvaWQgMCxjbGFzc2VzOm51bGwsY2xhc3Nlc1dpdGhvdXRIb3N0Om51bGwscmVzaWR1YWxDbGFzc2VzOnZvaWQgMCxjbGFzc0JpbmRpbmdzOjAsc3R5bGVCaW5kaW5nczowfX0oMCxzP286byYmby5wYXJlbnQsZSx0LGkscik7cmV0dXJuIG51bGw9PT1uLmZpcnN0Q2hpbGQmJihuLmZpcnN0Q2hpbGQ9bCksbnVsbCE9PW8mJihzP251bGw9PW8uY2hpbGQmJm51bGwhPT1sLnBhcmVudCYmKG8uY2hpbGQ9bCk6bnVsbD09PW8ubmV4dCYmKG8ubmV4dD1sKSksbH1mdW5jdGlvbiBxMChuLHQsZSxpKXtpZigwPT09ZSlyZXR1cm4tMTtsZXQgcj10Lmxlbmd0aDtmb3IobGV0IG89MDtvPGU7bysrKXQucHVzaChpKSxuLmJsdWVwcmludC5wdXNoKGkpLG4uZGF0YS5wdXNoKG51bGwpO3JldHVybiByfWZ1bmN0aW9uIEgzKG4sdCxlKXt5Myh0KTt0cnl7bGV0IGk9bi52aWV3UXVlcnk7bnVsbCE9PWkmJk5MKDEsaSxlKTtsZXQgcj1uLnRlbXBsYXRlO251bGwhPT1yJiZhcShuLHQsciwxLGUpLG4uZmlyc3RDcmVhdGVQYXNzJiYobi5maXJzdENyZWF0ZVBhc3M9ITEpLG4uc3RhdGljQ29udGVudFF1ZXJpZXMmJnNxKG4sdCksbi5zdGF0aWNWaWV3UXVlcmllcyYmTkwoMixuLnZpZXdRdWVyeSxlKTtsZXQgbz1uLmNvbXBvbmVudHM7bnVsbCE9PW8mJmZ1bmN0aW9uKG4sdCl7Zm9yKGxldCBlPTA7ZTx0Lmxlbmd0aDtlKyspJHllKG4sdFtlXSl9KHQsbyl9Y2F0Y2goaSl7dGhyb3cgbi5maXJzdENyZWF0ZVBhc3MmJihuLmluY29tcGxldGVGaXJzdFBhc3M9ITAsbi5maXJzdENyZWF0ZVBhc3M9ITEpLGl9ZmluYWxseXt0WzJdJj0tNSxiMygpfX1mdW5jdGlvbiB4VChuLHQsZSxpKXtsZXQgcj10WzJdO2lmKDEyOCE9KDEyOCZyKSl7eTModCk7dHJ5e1U3KHQpLEc3KG4uYmluZGluZ1N0YXJ0SW5kZXgpLG51bGwhPT1lJiZhcShuLHQsZSwyLGkpO2xldCBzPTM9PSgzJnIpO2lmKHMpe2xldCBjPW4ucHJlT3JkZXJDaGVja0hvb2tzO251bGwhPT1jJiZEMSh0LGMsbnVsbCl9ZWxzZXtsZXQgYz1uLnByZU9yZGVySG9va3M7bnVsbCE9PWMmJkExKHQsYywwLG51bGwpLEZOKHQsMCl9aWYoZnVuY3Rpb24obil7Zm9yKGxldCB0PU8zKG4pO251bGwhPT10O3Q9azModCkpe2lmKCF0WzJdKWNvbnRpbnVlO2xldCBlPXRbOV07Zm9yKGxldCBpPTA7aTxlLmxlbmd0aDtpKyspe2xldCByPWVbaV0sbz1yWzNdOzA9PSg1MTImclsyXSkmJmYzKG8sMSksclsyXXw9NTEyfX19KHQpLGZ1bmN0aW9uKG4pe2ZvcihsZXQgdD1PMyhuKTtudWxsIT09dDt0PWszKHQpKWZvcihsZXQgZT0xMDtlPHQubGVuZ3RoO2UrKyl7bGV0IGk9dFtlXSxyPWlbMV07VTEoaSkmJnhUKHIsaSxyLnRlbXBsYXRlLGlbOF0pfX0odCksbnVsbCE9PW4uY29udGVudFF1ZXJpZXMmJnNxKG4sdCkscyl7bGV0IGM9bi5jb250ZW50Q2hlY2tIb29rcztudWxsIT09YyYmRDEodCxjKX1lbHNle2xldCBjPW4uY29udGVudEhvb2tzO251bGwhPT1jJiZBMSh0LGMsMSksRk4odCwxKX0hZnVuY3Rpb24obix0KXtsZXQgZT1uLmhvc3RCaW5kaW5nT3BDb2RlcztpZihudWxsIT09ZSl0cnl7Zm9yKGxldCBpPTA7aTxlLmxlbmd0aDtpKyspe2xldCByPWVbaV07aWYocjwwKXpwKH5yKTtlbHNle2xldCBvPXIscz1lWysraV0sYT1lWysraV07c19lKHMsbyksYSgyLHRbb10pfX19ZmluYWxseXt6cCgtMSl9fShuLHQpO2xldCBhPW4uY29tcG9uZW50cztudWxsIT09YSYmZnVuY3Rpb24obix0KXtmb3IobGV0IGU9MDtlPHQubGVuZ3RoO2UrKylKeWUobix0W2VdKX0odCxhKTtsZXQgbD1uLnZpZXdRdWVyeTtpZihudWxsIT09bCYmTkwoMixsLGkpLHMpe2xldCBjPW4udmlld0NoZWNrSG9va3M7bnVsbCE9PWMmJkQxKHQsYyl9ZWxzZXtsZXQgYz1uLnZpZXdIb29rcztudWxsIT09YyYmQTEodCxjLDIpLEZOKHQsMil9ITA9PT1uLmZpcnN0VXBkYXRlUGFzcyYmKG4uZmlyc3RVcGRhdGVQYXNzPSExKSx0WzJdJj0tNDEsNTEyJnRbMl0mJih0WzJdJj0tNTEzLGYzKHRbM10sLTEpKX1maW5hbGx5e2IzKCl9fX1mdW5jdGlvbiBhcShuLHQsZSxpLHIpe2xldCBvPVpzKCkscz0yJmk7dHJ5e3pwKC0xKSxzJiZ0Lmxlbmd0aD4yMiYmaXEobix0LDIyLCExKSxlKGkscil9ZmluYWxseXt6cChvKX19ZnVuY3Rpb24gbHEobix0LGUpe2lmKHAzKHQpKXtsZXQgcj10LmRpcmVjdGl2ZUVuZDtmb3IobGV0IG89dC5kaXJlY3RpdmVTdGFydDtvPHI7bysrKXtsZXQgcz1uLmRhdGFbb107cy5jb250ZW50UXVlcmllcyYmcy5jb250ZW50UXVlcmllcygxLGVbb10sbyl9fX1mdW5jdGlvbiBVMyhuLHQsZSl7IXo3KCl8fChmdW5jdGlvbihuLHQsZSxpKXtsZXQgcj1lLmRpcmVjdGl2ZVN0YXJ0LG89ZS5kaXJlY3RpdmVFbmQ7bi5maXJzdENyZWF0ZVBhc3N8fFN4KGUsdCksU3UoaSx0KTtsZXQgcz1lLmluaXRpYWxJbnB1dHM7Zm9yKGxldCBhPXI7YTxvO2ErKyl7bGV0IGw9bi5kYXRhW2FdLGM9QWMobCk7YyYmWXllKHQsZSxsKTtsZXQgdT1CeCh0LG4sYSxlKTtTdSh1LHQpLG51bGwhPT1zJiZYeWUoMCxhLXIsdSxsLDAscyksYyYmKHFwKGUuaW5kZXgsdClbOF09dSl9fShuLHQsZSxVbChlLHQpKSwxMjg9PSgxMjgmZS5mbGFncykmJmZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1lLmRpcmVjdGl2ZVN0YXJ0LHI9ZS5kaXJlY3RpdmVFbmQsbz1lLmluZGV4LHM9Wm4ubEZyYW1lLmN1cnJlbnREaXJlY3RpdmVJbmRleDt0cnl7enAobyk7Zm9yKGxldCBhPWk7YTxyO2ErKyl7bGV0IGw9bi5kYXRhW2FdLGM9dFthXTtzTChhKSwobnVsbCE9PWwuaG9zdEJpbmRpbmdzfHwwIT09bC5ob3N0VmFyc3x8bnVsbCE9PWwuaG9zdEF0dHJzKSYmZnEobCxjKX19ZmluYWxseXt6cCgtMSksc0wocyl9fShuLHQsZSkpfWZ1bmN0aW9uIHozKG4sdCxlPVVsKXtsZXQgaT10LmxvY2FsTmFtZXM7aWYobnVsbCE9PWkpe2xldCByPXQuaW5kZXgrMTtmb3IobGV0IG89MDtvPGkubGVuZ3RoO28rPTIpe2xldCBzPWlbbysxXSxhPS0xPT09cz9lKHQsbik6bltzXTtuW3IrK109YX19fWZ1bmN0aW9uIGNxKG4pe2xldCB0PW4udFZpZXc7cmV0dXJuIG51bGw9PT10fHx0LmluY29tcGxldGVGaXJzdFBhc3M/bi50Vmlldz1qMygxLG51bGwsbi50ZW1wbGF0ZSxuLmRlY2xzLG4udmFycyxuLmRpcmVjdGl2ZURlZnMsbi5waXBlRGVmcyxuLnZpZXdRdWVyeSxuLnNjaGVtYXMsbi5jb25zdHMpOnR9ZnVuY3Rpb24gajMobix0LGUsaSxyLG8scyxhLGwsYyl7bGV0IHU9MjIraSxkPXUrcixwPWZ1bmN0aW9uKG4sdCl7bGV0IGU9W107Zm9yKGxldCBpPTA7aTx0O2krKyllLnB1c2goaTxuP251bGw6UW4pO3JldHVybiBlfSh1LGQpLGg9ImZ1bmN0aW9uIj09dHlwZW9mIGM/YygpOmM7cmV0dXJuIHBbMV09e3R5cGU6bixibHVlcHJpbnQ6cCx0ZW1wbGF0ZTplLHF1ZXJpZXM6bnVsbCx2aWV3UXVlcnk6YSxkZWNsVE5vZGU6dCxkYXRhOnAuc2xpY2UoKS5maWxsKG51bGwsdSksYmluZGluZ1N0YXJ0SW5kZXg6dSxleHBhbmRvU3RhcnRJbmRleDpkLGhvc3RCaW5kaW5nT3BDb2RlczpudWxsLGZpcnN0Q3JlYXRlUGFzczohMCxmaXJzdFVwZGF0ZVBhc3M6ITAsc3RhdGljVmlld1F1ZXJpZXM6ITEsc3RhdGljQ29udGVudFF1ZXJpZXM6ITEscHJlT3JkZXJIb29rczpudWxsLHByZU9yZGVyQ2hlY2tIb29rczpudWxsLGNvbnRlbnRIb29rczpudWxsLGNvbnRlbnRDaGVja0hvb2tzOm51bGwsdmlld0hvb2tzOm51bGwsdmlld0NoZWNrSG9va3M6bnVsbCxkZXN0cm95SG9va3M6bnVsbCxjbGVhbnVwOm51bGwsY29udGVudFF1ZXJpZXM6bnVsbCxjb21wb25lbnRzOm51bGwsZGlyZWN0aXZlUmVnaXN0cnk6ImZ1bmN0aW9uIj09dHlwZW9mIG8/bygpOm8scGlwZVJlZ2lzdHJ5OiJmdW5jdGlvbiI9PXR5cGVvZiBzP3MoKTpzLGZpcnN0Q2hpbGQ6bnVsbCxzY2hlbWFzOmwsY29uc3RzOmgsaW5jb21wbGV0ZUZpcnN0UGFzczohMX19ZnVuY3Rpb24gdXEobix0LGUsaSl7bGV0IHI9eHEodCk7bnVsbD09PWU/ci5wdXNoKGkpOihyLnB1c2goZSksbi5maXJzdENyZWF0ZVBhc3MmJkNxKG4pLnB1c2goaSxyLmxlbmd0aC0xKSl9ZnVuY3Rpb24gaTcobix0LGUpe2ZvcihsZXQgaSBpbiBuKWlmKG4uaGFzT3duUHJvcGVydHkoaSkpe2xldCByPW5baV07KGU9bnVsbD09PWU/e306ZSkuaGFzT3duUHJvcGVydHkoaSk/ZVtpXS5wdXNoKHQscik6ZVtpXT1bdCxyXX1yZXR1cm4gZX1mdW5jdGlvbiBkcShuLHQpe2xldCBpPXQuZGlyZWN0aXZlRW5kLHI9bi5kYXRhLG89dC5hdHRycyxzPVtdLGE9bnVsbCxsPW51bGw7Zm9yKGxldCBjPXQuZGlyZWN0aXZlU3RhcnQ7YzxpO2MrKyl7bGV0IHU9cltjXSxkPXUuaW5wdXRzLHA9bnVsbD09PW98fHRxKHQpP251bGw6UXllKGQsbyk7cy5wdXNoKHApLGE9aTcoZCxjLGEpLGw9aTcodS5vdXRwdXRzLGMsbCl9bnVsbCE9PWEmJihhLmhhc093blByb3BlcnR5KCJjbGFzcyIpJiYodC5mbGFnc3w9MTYpLGEuaGFzT3duUHJvcGVydHkoInN0eWxlIikmJih0LmZsYWdzfD0zMikpLHQuaW5pdGlhbElucHV0cz1zLHQuaW5wdXRzPWEsdC5vdXRwdXRzPWx9ZnVuY3Rpb24gaWwobix0LGUsaSxyLG8scyxhKXtsZXQgdSxsPVVsKHQsZSksYz10LmlucHV0czshYSYmbnVsbCE9YyYmKHU9Y1tpXSk/KHEzKG4sZSx1LGksciksaDModCkmJnBxKGUsdC5pbmRleCkpOjMmdC50eXBlJiYoaT1mdW5jdGlvbihuKXtyZXR1cm4iY2xhc3MiPT09bj8iY2xhc3NOYW1lIjoiZm9yIj09PW4/Imh0bWxGb3IiOiJmb3JtYWN0aW9uIj09PW4/ImZvcm1BY3Rpb24iOiJpbm5lckh0bWwiPT09bj8iaW5uZXJIVE1MIjoicmVhZG9ubHkiPT09bj8icmVhZE9ubHkiOiJ0YWJpbmRleCI9PT1uPyJ0YWJJbmRleCI6bn0oaSkscj1udWxsIT1zP3Mocix0LnZhbHVlfHwiIixpKTpyLG8uc2V0UHJvcGVydHkobCxpLHIpKX1mdW5jdGlvbiBwcShuLHQpe2xldCBlPXFwKHQsbik7MTYmZVsyXXx8KGVbMl18PTMyKX1mdW5jdGlvbiBHMyhuLHQsZSxpKXtsZXQgcj0hMTtpZih6NygpKXtsZXQgbz1mdW5jdGlvbihuLHQsZSl7bGV0IGk9bi5kaXJlY3RpdmVSZWdpc3RyeSxyPW51bGw7aWYoaSlmb3IobGV0IG89MDtvPGkubGVuZ3RoO28rKyl7bGV0IHM9aVtvXTtucShlLHMuc2VsZWN0b3JzLCExKSYmKHJ8fChyPVtdKSxZMShTeChlLHQpLG4scy50eXBlKSxBYyhzKT8obXEobixlKSxyLnVuc2hpZnQocykpOnIucHVzaChzKSl9cmV0dXJuIHJ9KG4sdCxlKSxzPW51bGw9PT1pP251bGw6eyIiOi0xfTtpZihudWxsIT09byl7cj0hMCxncShlLG4uZGF0YS5sZW5ndGgsby5sZW5ndGgpO2ZvcihsZXQgdT0wO3U8by5sZW5ndGg7dSsrKXtsZXQgZD1vW3VdO2QucHJvdmlkZXJzUmVzb2x2ZXImJmQucHJvdmlkZXJzUmVzb2x2ZXIoZCl9bGV0IGE9ITEsbD0hMSxjPXEwKG4sdCxvLmxlbmd0aCxudWxsKTtmb3IobGV0IHU9MDt1PG8ubGVuZ3RoO3UrKyl7bGV0IGQ9b1t1XTtlLm1lcmdlZEF0dHJzPWoxKGUubWVyZ2VkQXR0cnMsZC5ob3N0QXR0cnMpLF9xKG4sZSx0LGMsZCkscXllKGMsZCxzKSxudWxsIT09ZC5jb250ZW50UXVlcmllcyYmKGUuZmxhZ3N8PTgpLChudWxsIT09ZC5ob3N0QmluZGluZ3N8fG51bGwhPT1kLmhvc3RBdHRyc3x8MCE9PWQuaG9zdFZhcnMpJiYoZS5mbGFnc3w9MTI4KTtsZXQgcD1kLnR5cGUucHJvdG90eXBlOyFhJiYocC5uZ09uQ2hhbmdlc3x8cC5uZ09uSW5pdHx8cC5uZ0RvQ2hlY2spJiYoKG4ucHJlT3JkZXJIb29rc3x8KG4ucHJlT3JkZXJIb29rcz1bXSkpLnB1c2goZS5pbmRleCksYT0hMCksIWwmJihwLm5nT25DaGFuZ2VzfHxwLm5nRG9DaGVjaykmJigobi5wcmVPcmRlckNoZWNrSG9va3N8fChuLnByZU9yZGVyQ2hlY2tIb29rcz1bXSkpLnB1c2goZS5pbmRleCksbD0hMCksYysrfWRxKG4sZSl9cyYmZnVuY3Rpb24obix0LGUpe2lmKHQpe2xldCBpPW4ubG9jYWxOYW1lcz1bXTtmb3IobGV0IHI9MDtyPHQubGVuZ3RoO3IrPTIpe2xldCBvPWVbdFtyKzFdXTtpZihudWxsPT1vKXRocm93IG5ldyBBdCgtMzAxLCExKTtpLnB1c2godFtyXSxvKX19fShlLGkscyl9cmV0dXJuIGUubWVyZ2VkQXR0cnM9ajEoZS5tZXJnZWRBdHRycyxlLmF0dHJzKSxyfWZ1bmN0aW9uIGhxKG4sdCxlLGkscixvKXtsZXQgcz1vLmhvc3RCaW5kaW5ncztpZihzKXtsZXQgYT1uLmhvc3RCaW5kaW5nT3BDb2RlcztudWxsPT09YSYmKGE9bi5ob3N0QmluZGluZ09wQ29kZXM9W10pO2xldCBsPX50LmluZGV4OyhmdW5jdGlvbihuKXtsZXQgdD1uLmxlbmd0aDtmb3IoO3Q+MDspe2xldCBlPW5bLS10XTtpZigibnVtYmVyIj09dHlwZW9mIGUmJmU8MClyZXR1cm4gZX1yZXR1cm4gMH0pKGEpIT1sJiZhLnB1c2gobCksYS5wdXNoKGkscixzKX19ZnVuY3Rpb24gZnEobix0KXtudWxsIT09bi5ob3N0QmluZGluZ3MmJm4uaG9zdEJpbmRpbmdzKDEsdCl9ZnVuY3Rpb24gbXEobix0KXt0LmZsYWdzfD0yLChuLmNvbXBvbmVudHN8fChuLmNvbXBvbmVudHM9W10pKS5wdXNoKHQuaW5kZXgpfWZ1bmN0aW9uIHF5ZShuLHQsZSl7aWYoZSl7aWYodC5leHBvcnRBcylmb3IobGV0IGk9MDtpPHQuZXhwb3J0QXMubGVuZ3RoO2krKyllW3QuZXhwb3J0QXNbaV1dPW47QWModCkmJihlWyIiXT1uKX19ZnVuY3Rpb24gZ3Eobix0LGUpe24uZmxhZ3N8PTEsbi5kaXJlY3RpdmVTdGFydD10LG4uZGlyZWN0aXZlRW5kPXQrZSxuLnByb3ZpZGVySW5kZXhlcz10fWZ1bmN0aW9uIF9xKG4sdCxlLGkscil7bi5kYXRhW2ldPXI7bGV0IG89ci5mYWN0b3J5fHwoci5mYWN0b3J5PVdmKHIudHlwZSkpLHM9bmV3IHFmKG8sQWMociksTSk7bi5ibHVlcHJpbnRbaV09cyxlW2ldPXMsaHEobix0LDAsaSxxMChuLGUsci5ob3N0VmFycyxRbikscil9ZnVuY3Rpb24gWXllKG4sdCxlKXtsZXQgaT1VbCh0LG4pLHI9Y3EoZSksbz1uWzEwXSxzPUNUKG4sYlQobixyLG51bGwsZS5vblB1c2g/MzI6MTYsaSx0LG8sby5jcmVhdGVSZW5kZXJlcihpLGUpLG51bGwsbnVsbCxudWxsKSk7blt0LmluZGV4XT1zfWZ1bmN0aW9uIFR1KG4sdCxlLGkscixvKXtsZXQgcz1VbChuLHQpO1czKHRbMTFdLHMsbyxuLnZhbHVlLGUsaSxyKX1mdW5jdGlvbiBXMyhuLHQsZSxpLHIsbyxzKXtpZihudWxsPT1vKW4ucmVtb3ZlQXR0cmlidXRlKHQscixlKTtlbHNle2xldCBhPW51bGw9PXM/S24obyk6cyhvLGl8fCIiLHIpO24uc2V0QXR0cmlidXRlKHQscixhLGUpfX1mdW5jdGlvbiBYeWUobix0LGUsaSxyLG8pe2xldCBzPW9bdF07aWYobnVsbCE9PXMpe2xldCBhPWkuc2V0SW5wdXQ7Zm9yKGxldCBsPTA7bDxzLmxlbmd0aDspe2xldCBjPXNbbCsrXSx1PXNbbCsrXSxkPXNbbCsrXTtudWxsIT09YT9pLnNldElucHV0KGUsZCxjLHUpOmVbdV09ZH19fWZ1bmN0aW9uIFF5ZShuLHQpe2xldCBlPW51bGwsaT0wO2Zvcig7aTx0Lmxlbmd0aDspe2xldCByPXRbaV07aWYoMCE9PXIpaWYoNSE9PXIpe2lmKCJudW1iZXIiPT10eXBlb2YgcilicmVhaztuLmhhc093blByb3BlcnR5KHIpJiYobnVsbD09PWUmJihlPVtdKSxlLnB1c2gocixuW3JdLHRbaSsxXSkpLGkrPTJ9ZWxzZSBpKz0yO2Vsc2UgaSs9NH1yZXR1cm4gZX1mdW5jdGlvbiB2cShuLHQsZSxpKXtyZXR1cm4gbmV3IEFycmF5KG4sITAsITEsdCxudWxsLDAsaSxlLG51bGwsbnVsbCl9ZnVuY3Rpb24gSnllKG4sdCl7bGV0IGU9cXAodCxuKTtpZihVMShlKSl7bGV0IGk9ZVsxXTs0OCZlWzJdP3hUKGksZSxpLnRlbXBsYXRlLGVbOF0pOmVbNV0+MCYmRkwoZSl9fWZ1bmN0aW9uIEZMKG4pe2ZvcihsZXQgaT1PMyhuKTtudWxsIT09aTtpPWszKGkpKWZvcihsZXQgcj0xMDtyPGkubGVuZ3RoO3IrKyl7bGV0IG89aVtyXTtpZihVMShvKSlpZig1MTImb1syXSl7bGV0IHM9b1sxXTt4VChzLG8scy50ZW1wbGF0ZSxvWzhdKX1lbHNlIG9bNV0+MCYmRkwobyl9bGV0IGU9blsxXS5jb21wb25lbnRzO2lmKG51bGwhPT1lKWZvcihsZXQgaT0wO2k8ZS5sZW5ndGg7aSsrKXtsZXQgcj1xcChlW2ldLG4pO1UxKHIpJiZyWzVdPjAmJkZMKHIpfX1mdW5jdGlvbiAkeWUobix0KXtsZXQgZT1xcCh0LG4pLGk9ZVsxXTsoZnVuY3Rpb24obix0KXtmb3IobGV0IGU9dC5sZW5ndGg7ZTxuLmJsdWVwcmludC5sZW5ndGg7ZSsrKXQucHVzaChuLmJsdWVwcmludFtlXSl9KShpLGUpLEgzKGksZSxlWzhdKX1mdW5jdGlvbiBDVChuLHQpe3JldHVybiBuWzEzXT9uWzE0XVs0XT10Om5bMTNdPXQsblsxNF09dCx0fWZ1bmN0aW9uIHlxKG4pe2Zvcig7bjspe25bMl18PTMyO2xldCB0PVIzKG4pO2lmKHEwZShuKSYmIXQpcmV0dXJuIG47bj10fXJldHVybiBudWxsfWZ1bmN0aW9uIGJxKG4sdCxlLGk9ITApe2xldCByPXRbMTBdO3IuYmVnaW4mJnIuYmVnaW4oKTt0cnl7eFQobix0LG4udGVtcGxhdGUsZSl9Y2F0Y2gocyl7dGhyb3cgaSYmd3EodCxzKSxzfWZpbmFsbHl7ci5lbmQmJnIuZW5kKCl9fWZ1bmN0aW9uIE5MKG4sdCxlKXt2MygwKSx0KG4sZSl9ZnVuY3Rpb24geHEobil7cmV0dXJuIG5bN118fChuWzddPVtdKX1mdW5jdGlvbiBDcShuKXtyZXR1cm4gbi5jbGVhbnVwfHwobi5jbGVhbnVwPVtdKX1mdW5jdGlvbiBNcShuLHQsZSl7cmV0dXJuKG51bGw9PT1ufHxBYyhuKSkmJihlPWZ1bmN0aW9uKG4pe2Zvcig7QXJyYXkuaXNBcnJheShuKTspe2lmKCJvYmplY3QiPT10eXBlb2YgblsxXSlyZXR1cm4gbjtuPW5bMF19cmV0dXJuIG51bGx9KGVbdC5pbmRleF0pKSxlWzExXX1mdW5jdGlvbiB3cShuLHQpe2xldCBlPW5bOV0saT1lP2UuZ2V0KFFzLG51bGwpOm51bGw7aSYmaS5oYW5kbGVFcnJvcih0KX1mdW5jdGlvbiBxMyhuLHQsZSxpLHIpe2ZvcihsZXQgbz0wO288ZS5sZW5ndGg7KXtsZXQgcz1lW28rK10sYT1lW28rK10sbD10W3NdLGM9bi5kYXRhW3NdO251bGwhPT1jLnNldElucHV0P2Muc2V0SW5wdXQobCxyLGksYSk6bFthXT1yfX1mdW5jdGlvbiBqZChuLHQsZSl7bGV0IGk9cFQodCxuKTtWOShuWzExXSxpLGUpfWZ1bmN0aW9uICQxKG4sdCxlKXtsZXQgaT1lP24uc3R5bGVzOm51bGwscj1lP24uY2xhc3NlczpudWxsLG89MDtpZihudWxsIT09dClmb3IobGV0IHM9MDtzPHQubGVuZ3RoO3MrKyl7bGV0IGE9dFtzXTsibnVtYmVyIj09dHlwZW9mIGE/bz1hOjE9PW8/cj10TChyLGEpOjI9PW8mJihpPXRMKGksYSsiOiAiK3RbKytzXSsiOyIpKX1lP24uc3R5bGVzPWk6bi5zdHlsZXNXaXRob3V0SG9zdD1pLGU/bi5jbGFzc2VzPXI6bi5jbGFzc2VzV2l0aG91dEhvc3Q9cn1mdW5jdGlvbiBrMShuLHQsZSxpLHI9ITEpe2Zvcig7bnVsbCE9PWU7KXtsZXQgbz10W2UuaW5kZXhdO2lmKG51bGwhPT1vJiZpLnB1c2goJGEobykpLFZkKG8pKWZvcihsZXQgYT0xMDthPG8ubGVuZ3RoO2ErKyl7bGV0IGw9b1thXSxjPWxbMV0uZmlyc3RDaGlsZDtudWxsIT09YyYmazEobFsxXSxsLGMsaSl9bGV0IHM9ZS50eXBlO2lmKDgmcylrMShuLHQsZS5jaGlsZCxpKTtlbHNlIGlmKDMyJnMpe2xldCBsLGE9UDMoZSx0KTtmb3IoO2w9YSgpOylpLnB1c2gobCl9ZWxzZSBpZigxNiZzKXtsZXQgYT1ROSh0LGUpO2lmKEFycmF5LmlzQXJyYXkoYSkpaS5wdXNoKC4uLmEpO2Vsc2V7bGV0IGw9UjModFsxNl0pO2sxKGxbMV0sbCxhLGksITApfX1lPXI/ZS5wcm9qZWN0aW9uTmV4dDplLm5leHR9cmV0dXJuIGl9dmFyIFFmPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5fbFZpZXc9dCx0aGlzLl9jZFJlZkluamVjdGluZ1ZpZXc9ZSx0aGlzLl9hcHBSZWY9bnVsbCx0aGlzLl9hdHRhY2hlZFRvVmlld0NvbnRhaW5lcj0hMX1nZXQgcm9vdE5vZGVzKCl7bGV0IHQ9dGhpcy5fbFZpZXcsZT10WzFdO3JldHVybiBrMShlLHQsZS5maXJzdENoaWxkLFtdKX1nZXQgY29udGV4dCgpe3JldHVybiB0aGlzLl9sVmlld1s4XX1zZXQgY29udGV4dCh0KXt0aGlzLl9sVmlld1s4XT10fWdldCBkZXN0cm95ZWQoKXtyZXR1cm4gMTI4PT0oMTI4JnRoaXMuX2xWaWV3WzJdKX1kZXN0cm95KCl7aWYodGhpcy5fYXBwUmVmKXRoaXMuX2FwcFJlZi5kZXRhY2hWaWV3KHRoaXMpO2Vsc2UgaWYodGhpcy5fYXR0YWNoZWRUb1ZpZXdDb250YWluZXIpe2xldCB0PXRoaXMuX2xWaWV3WzNdO2lmKFZkKHQpKXtsZXQgZT10WzhdLGk9ZT9lLmluZGV4T2YodGhpcyk6LTE7aT4tMSYmKEFMKHQsaSksWDEoZSxpKSl9dGhpcy5fYXR0YWNoZWRUb1ZpZXdDb250YWluZXI9ITF9VTkodGhpcy5fbFZpZXdbMV0sdGhpcy5fbFZpZXcpfW9uRGVzdHJveSh0KXt1cSh0aGlzLl9sVmlld1sxXSx0aGlzLl9sVmlldyxudWxsLHQpfW1hcmtGb3JDaGVjaygpe3lxKHRoaXMuX2NkUmVmSW5qZWN0aW5nVmlld3x8dGhpcy5fbFZpZXcpfWRldGFjaCgpe3RoaXMuX2xWaWV3WzJdJj0tNjV9cmVhdHRhY2goKXt0aGlzLl9sVmlld1syXXw9NjR9ZGV0ZWN0Q2hhbmdlcygpe2JxKHRoaXMuX2xWaWV3WzFdLHRoaXMuX2xWaWV3LHRoaXMuY29udGV4dCl9Y2hlY2tOb0NoYW5nZXMoKXt9YXR0YWNoVG9WaWV3Q29udGFpbmVyUmVmKCl7aWYodGhpcy5fYXBwUmVmKXRocm93IG5ldyBBdCg5MDIsITEpO3RoaXMuX2F0dGFjaGVkVG9WaWV3Q29udGFpbmVyPSEwfWRldGFjaEZyb21BcHBSZWYoKXt2YXIgdDt0aGlzLl9hcHBSZWY9bnVsbCxxeCh0aGlzLl9sVmlld1sxXSx0PXRoaXMuX2xWaWV3LHRbMTFdLDIsbnVsbCxudWxsKX1hdHRhY2hUb0FwcFJlZih0KXtpZih0aGlzLl9hdHRhY2hlZFRvVmlld0NvbnRhaW5lcil0aHJvdyBuZXcgQXQoOTAyLCExKTt0aGlzLl9hcHBSZWY9dH19LExMPWNsYXNzIGV4dGVuZHMgUWZ7Y29uc3RydWN0b3IodCl7c3VwZXIodCksdGhpcy5fdmlldz10fWRldGVjdENoYW5nZXMoKXtsZXQgdD10aGlzLl92aWV3O2JxKHRbMV0sdCx0WzhdLCExKX1jaGVja05vQ2hhbmdlcygpe31nZXQgY29udGV4dCgpe3JldHVybiBudWxsfX0sQXg9Y2xhc3MgZXh0ZW5kcyBnc3tjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMubmdNb2R1bGU9dH1yZXNvbHZlQ29tcG9uZW50RmFjdG9yeSh0KXtsZXQgZT1ObCh0KTtyZXR1cm4gbmV3IGVUKGUsdGhpcy5uZ01vZHVsZSl9fTtmdW5jdGlvbiByNyhuKXtsZXQgdD1bXTtmb3IobGV0IGUgaW4gbiluLmhhc093blByb3BlcnR5KGUpJiZ0LnB1c2goe3Byb3BOYW1lOm5bZV0sdGVtcGxhdGVOYW1lOmV9KTtyZXR1cm4gdH12YXIgZVQ9Y2xhc3MgZXh0ZW5kcyBKMXtjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKCksdGhpcy5jb21wb25lbnREZWY9dCx0aGlzLm5nTW9kdWxlPWUsdGhpcy5jb21wb25lbnRUeXBlPXQudHlwZSx0aGlzLnNlbGVjdG9yPXQuc2VsZWN0b3JzLm1hcChDeWUpLmpvaW4oIiwiKSx0aGlzLm5nQ29udGVudFNlbGVjdG9ycz10Lm5nQ29udGVudFNlbGVjdG9ycz90Lm5nQ29udGVudFNlbGVjdG9yczpbXSx0aGlzLmlzQm91bmRUb01vZHVsZT0hIWV9Z2V0IGlucHV0cygpe3JldHVybiByNyh0aGlzLmNvbXBvbmVudERlZi5pbnB1dHMpfWdldCBvdXRwdXRzKCl7cmV0dXJuIHI3KHRoaXMuY29tcG9uZW50RGVmLm91dHB1dHMpfWNyZWF0ZSh0LGUsaSxyKXtsZXQgbz0ocj1yfHx0aGlzLm5nTW9kdWxlKWluc3RhbmNlb2YganA/cjpyPy5pbmplY3RvcjtvJiZudWxsIT09dGhpcy5jb21wb25lbnREZWYuZ2V0U3RhbmRhbG9uZUluamVjdG9yJiYobz10aGlzLmNvbXBvbmVudERlZi5nZXRTdGFuZGFsb25lSW5qZWN0b3Iobyl8fG8pO2xldCBzPW8/bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5pbmplY3Rvcj10LHRoaXMucGFyZW50SW5qZWN0b3I9ZX1nZXQodCxlLGkpe2xldCByPXRoaXMuaW5qZWN0b3IuZ2V0KHQsSE4saSk7cmV0dXJuIHIhPT1ITnx8ZT09PUhOP3I6dGhpcy5wYXJlbnRJbmplY3Rvci5nZXQodCxlLGkpfX0odCxvKTp0LGE9cy5nZXQod3UsbnVsbCk7aWYobnVsbD09PWEpdGhyb3cgbmV3IEF0KDQwNywhMSk7bGV0IG0seCxsPXMuZ2V0KEh2ZSxudWxsKSxjPWEuY3JlYXRlUmVuZGVyZXIobnVsbCx0aGlzLmNvbXBvbmVudERlZiksdT10aGlzLmNvbXBvbmVudERlZi5zZWxlY3RvcnNbMF1bMF18fCJkaXYiLGQ9aT9mdW5jdGlvbihuLHQsZSl7cmV0dXJuIG4uc2VsZWN0Um9vdEVsZW1lbnQodCxlPT09SmEuU2hhZG93RG9tKX0oYyxpLHRoaXMuY29tcG9uZW50RGVmLmVuY2Fwc3VsYXRpb24pOk4zKGMsdSxmdW5jdGlvbihuKXtsZXQgdD1uLnRvTG93ZXJDYXNlKCk7cmV0dXJuInN2ZyI9PT10PyJzdmciOiJtYXRoIj09PXQ/Im1hdGgiOm51bGx9KHUpKSxwPXRoaXMuY29tcG9uZW50RGVmLm9uUHVzaD8yODg6MjcyLGg9ajMoMCxudWxsLG51bGwsMSwwLG51bGwsbnVsbCxudWxsLG51bGwsbnVsbCksZj1iVChudWxsLGgsbnVsbCxwLG51bGwsbnVsbCxhLGMsbCxzLG51bGwpO3kzKGYpO3RyeXtsZXQgZz1mdW5jdGlvbihuLHQsZSxpLHIsbyl7bGV0IHM9ZVsxXTtlWzIyXT1uO2xldCBsPVcwKHMsMjIsMiwiI2hvc3QiLG51bGwpLGM9bC5tZXJnZWRBdHRycz10Lmhvc3RBdHRycztudWxsIT09YyYmKCQxKGwsYywhMCksbnVsbCE9PW4mJih6MShyLG4sYyksbnVsbCE9PWwuY2xhc3NlcyYmQjMocixuLGwuY2xhc3NlcyksbnVsbCE9PWwuc3R5bGVzJiZKOShyLG4sbC5zdHlsZXMpKSk7bGV0IHU9aS5jcmVhdGVSZW5kZXJlcihuLHQpLGQ9YlQoZSxjcSh0KSxudWxsLHQub25QdXNoPzMyOjE2LGVbMjJdLGwsaSx1LG51bGwsbnVsbCxudWxsKTtyZXR1cm4gcy5maXJzdENyZWF0ZVBhc3MmJihZMShTeChsLGUpLHMsdC50eXBlKSxtcShzLGwpLGdxKGwsZS5sZW5ndGgsMSkpLENUKGUsZCksZVsyMl09ZH0oZCx0aGlzLmNvbXBvbmVudERlZixmLGEsYyk7aWYoZClpZihpKXoxKGMsZCxbIm5nLXZlcnNpb24iLFV2ZS5mdWxsXSk7ZWxzZXtsZXR7YXR0cnM6YixjbGFzc2VzOkR9PWZ1bmN0aW9uKG4pe2xldCB0PVtdLGU9W10saT0xLHI9Mjtmb3IoO2k8bi5sZW5ndGg7KXtsZXQgbz1uW2ldO2lmKCJzdHJpbmciPT10eXBlb2YgbykyPT09cj8iIiE9PW8mJnQucHVzaChvLG5bKytpXSk6OD09PXImJmUucHVzaChvKTtlbHNle2lmKCFTYyhyKSlicmVhaztyPW99aSsrfXJldHVybnthdHRyczp0LGNsYXNzZXM6ZX19KHRoaXMuY29tcG9uZW50RGVmLnNlbGVjdG9yc1swXSk7YiYmejEoYyxkLGIpLEQmJkQubGVuZ3RoPjAmJkIzKGMsZCxELmpvaW4oIiAiKSl9aWYoeD1INyhoLDIyKSx2b2lkIDAhPT1lKXtsZXQgYj14LnByb2plY3Rpb249W107Zm9yKGxldCBEPTA7RDx0aGlzLm5nQ29udGVudFNlbGVjdG9ycy5sZW5ndGg7RCsrKXtsZXQgVD1lW0RdO2IucHVzaChudWxsIT1UP0FycmF5LmZyb20oVCk6bnVsbCl9fW09ZnVuY3Rpb24obix0LGUsaSl7bGV0IHI9ZVsxXSxvPWZ1bmN0aW9uKG4sdCxlKXtsZXQgaT16bygpO24uZmlyc3RDcmVhdGVQYXNzJiYoZS5wcm92aWRlcnNSZXNvbHZlciYmZS5wcm92aWRlcnNSZXNvbHZlcihlKSxfcShuLGksdCxxMChuLHQsMSxudWxsKSxlKSxkcShuLGkpKTtsZXQgcj1CeCh0LG4saS5kaXJlY3RpdmVTdGFydCxpKTtTdShyLHQpO2xldCBvPVVsKGksdCk7cmV0dXJuIG8mJlN1KG8sdCkscn0ocixlLHQpO2lmKG5bOF09ZVs4XT1vLG51bGwhPT1pKWZvcihsZXQgYSBvZiBpKWEobyx0KTtpZih0LmNvbnRlbnRRdWVyaWVzKXtsZXQgYT16bygpO3QuY29udGVudFF1ZXJpZXMoMSxvLGEuZGlyZWN0aXZlU3RhcnQpfWxldCBzPXpvKCk7cmV0dXJuIXIuZmlyc3RDcmVhdGVQYXNzfHxudWxsPT09dC5ob3N0QmluZGluZ3MmJm51bGw9PT10Lmhvc3RBdHRyc3x8KHpwKHMuaW5kZXgpLGhxKGVbMV0scywwLHMuZGlyZWN0aXZlU3RhcnQscy5kaXJlY3RpdmVFbmQsdCksZnEodCxvKSksb30oZyx0aGlzLmNvbXBvbmVudERlZixmLFtyYmVdKSxIMyhoLGYsbnVsbCl9ZmluYWxseXtiMygpfXJldHVybiBuZXcgVkwodGhpcy5jb21wb25lbnRUeXBlLG0sRzAoeCxmKSxmLHgpfX0sVkw9KG5ldyBBeCxjbGFzcyBleHRlbmRzIEVMe2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7c3VwZXIoKSx0aGlzLmxvY2F0aW9uPWksdGhpcy5fcm9vdExWaWV3PXIsdGhpcy5fdE5vZGU9byx0aGlzLmluc3RhbmNlPWUsdGhpcy5ob3N0Vmlldz10aGlzLmNoYW5nZURldGVjdG9yUmVmPW5ldyBMTChyKSx0aGlzLmNvbXBvbmVudFR5cGU9dH1zZXRJbnB1dCh0LGUpe2xldCByLGk9dGhpcy5fdE5vZGUuaW5wdXRzO2lmKG51bGwhPT1pJiYocj1pW3RdKSl7bGV0IG89dGhpcy5fcm9vdExWaWV3O3EzKG9bMV0sbyxyLHQsZSkscHEobyx0aGlzLl90Tm9kZS5pbmRleCl9fWdldCBpbmplY3Rvcigpe3JldHVybiBuZXcgamYodGhpcy5fdE5vZGUsdGhpcy5fcm9vdExWaWV3KX1kZXN0cm95KCl7dGhpcy5ob3N0Vmlldy5kZXN0cm95KCl9b25EZXN0cm95KHQpe3RoaXMuaG9zdFZpZXcub25EZXN0cm95KHQpfX0pO2Z1bmN0aW9uIHJiZSgpe2xldCBuPXpvKCk7aFQocnQoKVsxXSxuKX1mdW5jdGlvbiBTcShuKXtyZXR1cm4gT2JqZWN0LmdldFByb3RvdHlwZU9mKG4ucHJvdG90eXBlKS5jb25zdHJ1Y3Rvcn1mdW5jdGlvbiB0dChuKXtsZXQgdD1TcShuLnR5cGUpLGU9ITAsaT1bbl07Zm9yKDt0Oyl7bGV0IHI7aWYoQWMobikpcj10Llx1MDI3NWNtcHx8dC5cdTAyNzVkaXI7ZWxzZXtpZih0Llx1MDI3NWNtcCl0aHJvdyBuZXcgQXQoOTAzLCExKTtyPXQuXHUwMjc1ZGlyfWlmKHIpe2lmKGUpe2kucHVzaChyKTtsZXQgcz1uO3MuaW5wdXRzPUdOKG4uaW5wdXRzKSxzLmRlY2xhcmVkSW5wdXRzPUdOKG4uZGVjbGFyZWRJbnB1dHMpLHMub3V0cHV0cz1HTihuLm91dHB1dHMpO2xldCBhPXIuaG9zdEJpbmRpbmdzO2EmJmxiZShuLGEpO2xldCBsPXIudmlld1F1ZXJ5LGM9ci5jb250ZW50UXVlcmllcztpZihsJiZzYmUobixsKSxjJiZhYmUobixjKSxrTihuLmlucHV0cyxyLmlucHV0cyksa04obi5kZWNsYXJlZElucHV0cyxyLmRlY2xhcmVkSW5wdXRzKSxrTihuLm91dHB1dHMsci5vdXRwdXRzKSxBYyhyKSYmci5kYXRhLmFuaW1hdGlvbil7bGV0IHU9bi5kYXRhO3UuYW5pbWF0aW9uPSh1LmFuaW1hdGlvbnx8W10pLmNvbmNhdChyLmRhdGEuYW5pbWF0aW9uKX19bGV0IG89ci5mZWF0dXJlcztpZihvKWZvcihsZXQgcz0wO3M8by5sZW5ndGg7cysrKXtsZXQgYT1vW3NdO2EmJmEubmdJbmhlcml0JiZhKG4pLGE9PT10dCYmKGU9ITEpfX10PU9iamVjdC5nZXRQcm90b3R5cGVPZih0KX0hZnVuY3Rpb24obil7bGV0IHQ9MCxlPW51bGw7Zm9yKGxldCBpPW4ubGVuZ3RoLTE7aT49MDtpLS0pe2xldCByPW5baV07ci5ob3N0VmFycz10Kz1yLmhvc3RWYXJzLHIuaG9zdEF0dHJzPWoxKHIuaG9zdEF0dHJzLGU9ajEoZSxyLmhvc3RBdHRycykpfX0oaSl9ZnVuY3Rpb24gR04obil7cmV0dXJuIG49PT1BMD97fTpuPT09UWk/W106bn1mdW5jdGlvbiBzYmUobix0KXtsZXQgZT1uLnZpZXdRdWVyeTtuLnZpZXdRdWVyeT1lPyhpLHIpPT57dChpLHIpLGUoaSxyKX06dH1mdW5jdGlvbiBhYmUobix0KXtsZXQgZT1uLmNvbnRlbnRRdWVyaWVzO24uY29udGVudFF1ZXJpZXM9ZT8oaSxyLG8pPT57dChpLHIsbyksZShpLHIsbyl9OnR9ZnVuY3Rpb24gbGJlKG4sdCl7bGV0IGU9bi5ob3N0QmluZGluZ3M7bi5ob3N0QmluZGluZ3M9ZT8oaSxyKT0+e3QoaSxyKSxlKGkscil9OnR9dmFyIGNiZT1bInByb3ZpZGVyc1Jlc29sdmVyIl0sdWJlPVsidGVtcGxhdGUiLCJkZWNscyIsImNvbnN0cyIsInZhcnMiLCJvblB1c2giLCJuZ0NvbnRlbnRTZWxlY3RvcnMiLCJzdHlsZXMiLCJlbmNhcHN1bGF0aW9uIiwic2NoZW1hcyJdO3ZhciBUMT1udWxsO2Z1bmN0aW9uIE1UKCl7aWYoIVQxKXtsZXQgbj10by5TeW1ib2w7aWYobiYmbi5pdGVyYXRvcilUMT1uLml0ZXJhdG9yO2Vsc2V7bGV0IHQ9T2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMoTWFwLnByb3RvdHlwZSk7Zm9yKGxldCBlPTA7ZTx0Lmxlbmd0aDsrK2Upe2xldCBpPXRbZV07ImVudHJpZXMiIT09aSYmInNpemUiIT09aSYmTWFwLnByb3RvdHlwZVtpXT09PU1hcC5wcm90b3R5cGUuZW50cmllcyYmKFQxPWkpfX19cmV0dXJuIFQxfWZ1bmN0aW9uIHdUKG4pe3JldHVybiEhWTMobikmJihBcnJheS5pc0FycmF5KG4pfHwhKG4gaW5zdGFuY2VvZiBNYXApJiZNVCgpaW4gbil9ZnVuY3Rpb24gWTMobil7cmV0dXJuIG51bGwhPT1uJiYoImZ1bmN0aW9uIj09dHlwZW9mIG58fCJvYmplY3QiPT10eXBlb2Ygbil9ZnVuY3Rpb24gRHUobix0LGUpe3JldHVybiBuW3RdPWV9ZnVuY3Rpb24gWXgobix0KXtyZXR1cm4gblt0XX1mdW5jdGlvbiBEcyhuLHQsZSl7cmV0dXJuIU9iamVjdC5pcyhuW3RdLGUpJiYoblt0XT1lLCEwKX1mdW5jdGlvbiBLZihuLHQsZSxpKXtsZXQgcj1EcyhuLHQsZSk7cmV0dXJuIERzKG4sdCsxLGkpfHxyfWZ1bmN0aW9uIFNUKG4sdCxlLGkscil7bGV0IG89S2Yobix0LGUsaSk7cmV0dXJuIERzKG4sdCsyLHIpfHxvfWZ1bmN0aW9uIFZsKG4sdCxlLGkscixvKXtsZXQgcz1LZihuLHQsZSxpKTtyZXR1cm4gS2Yobix0KzIscixvKXx8c31mdW5jdGlvbiB6ZShuLHQsZSxpKXtsZXQgcj1ydCgpO3JldHVybiBEcyhyLFUwKCksdCkmJihGaSgpLFR1KG5vKCkscixuLHQsZSxpKSksemV9ZnVuY3Rpb24gWTAobix0KXtsZXQgZT0hMSxpPUhkKCk7Zm9yKGxldCBvPTE7bzx0Lmxlbmd0aDtvKz0yKWU9RHMobixpKyssdFtvXSl8fGU7aWYoRzcoaSksIWUpcmV0dXJuIFFuO2xldCByPXRbMF07Zm9yKGxldCBvPTE7bzx0Lmxlbmd0aDtvKz0yKXIrPUtuKHRbb10pK3RbbysxXTtyZXR1cm4gcn1mdW5jdGlvbiBYMChuLHQsZSxpKXtyZXR1cm4gRHMobixVMCgpLGUpP3QrS24oZSkraTpRbn1mdW5jdGlvbiBRMChuLHQsZSxpLHIsbyl7bGV0IGE9S2YobixIZCgpLGUscik7cmV0dXJuIFVkKDIpLGE/dCtLbihlKStpK0tuKHIpK286UW59ZnVuY3Rpb24gSzAobix0LGUsaSxyLG8scyxhKXtsZXQgYz1TVChuLEhkKCksZSxyLHMpO3JldHVybiBVZCgzKSxjP3QrS24oZSkraStLbihyKStvK0tuKHMpK2E6UW59ZnVuY3Rpb24gWjAobix0LGUsaSxyLG8scyxhLGwsYyl7bGV0IGQ9VmwobixIZCgpLGUscixzLGwpO3JldHVybiBVZCg0KSxkP3QrS24oZSkraStLbihyKStvK0tuKHMpK2ErS24obCkrYzpRbn1mdW5jdGlvbiBKMChuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCl7bGV0IHA9SGQoKSxoPVZsKG4scCxlLHIscyxsKTtyZXR1cm4gaD1EcyhuLHArNCx1KXx8aCxVZCg1KSxoP3QrS24oZSkraStLbihyKStvK0tuKHMpK2ErS24obCkrYytLbih1KStkOlFufWZ1bmN0aW9uICQwKG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCl7bGV0IGY9SGQoKSxtPVZsKG4sZixlLHIscyxsKTtyZXR1cm4gbT1LZihuLGYrNCx1LHApfHxtLFVkKDYpLG0/dCtLbihlKStpK0tuKHIpK28rS24ocykrYStLbihsKStjK0tuKHUpK2QrS24ocCkraDpRbn1mdW5jdGlvbiBlXyhuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtKXtsZXQgeD1IZCgpLGc9Vmwobix4LGUscixzLGwpO3JldHVybiBnPVNUKG4seCs0LHUscCxmKXx8ZyxVZCg3KSxnP3QrS24oZSkraStLbihyKStvK0tuKHMpK2ErS24obCkrYytLbih1KStkK0tuKHApK2grS24oZikrbTpRbn1mdW5jdGlvbiB0XyhuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtLHgsZyl7bGV0IGI9SGQoKSxEPVZsKG4sYixlLHIscyxsKTtyZXR1cm4gRD1WbChuLGIrNCx1LHAsZix4KXx8RCxVZCg4KSxEP3QrS24oZSkraStLbihyKStvK0tuKHMpK2ErS24obCkrYytLbih1KStkK0tuKHApK2grS24oZikrbStLbih4KStnOlFufWZ1bmN0aW9uIEUobix0LGUsaSxyLG8scyxhKXtsZXQgbD1ydCgpLGM9RmkoKSx1PW4rMjIsZD1jLmZpcnN0Q3JlYXRlUGFzcz9mdW5jdGlvbihuLHQsZSxpLHIsbyxzLGEsbCl7bGV0IGM9dC5jb25zdHMsdT1XMCh0LG4sNCxzfHxudWxsLFVwKGMsYSkpO0czKHQsZSx1LFVwKGMsbCkpLGhUKHQsdSk7bGV0IGQ9dS50Vmlld3M9ajMoMix1LGkscixvLHQuZGlyZWN0aXZlUmVnaXN0cnksdC5waXBlUmVnaXN0cnksbnVsbCx0LnNjaGVtYXMsYyk7cmV0dXJuIG51bGwhPT10LnF1ZXJpZXMmJih0LnF1ZXJpZXMudGVtcGxhdGUodCx1KSxkLnF1ZXJpZXM9dC5xdWVyaWVzLmVtYmVkZGVkVFZpZXcodSkpLHV9KHUsYyxsLHQsZSxpLHIsbyxzKTpjLmRhdGFbdV07TXUoZCwhMSk7bGV0IHA9bFsxMV0uY3JlYXRlQ29tbWVudCgiIik7eVQoYyxsLHAsZCksU3UocCxsKSxDVChsLGxbdV09dnEocCxsLHAsZCkpLGRUKGQpJiZVMyhjLGwsZCksbnVsbCE9cyYmejMobCxkLGEpfWZ1bmN0aW9uICRlKG4pe3JldHVybiBIMChabi5sRnJhbWUuY29udGV4dExWaWV3LDIyK24pfWZ1bmN0aW9uIHkobix0LGUpe2xldCBpPXJ0KCk7cmV0dXJuIERzKGksVTAoKSx0KSYmaWwoRmkoKSxubygpLGksbix0LGlbMTFdLGUsITEpLHl9ZnVuY3Rpb24gSEwobix0LGUsaSxyKXtsZXQgcz1yPyJjbGFzcyI6InN0eWxlIjtxMyhuLGUsdC5pbnB1dHNbc10scyxpKX1mdW5jdGlvbiBfKG4sdCxlLGkpe2xldCByPXJ0KCksbz1GaSgpLHM9MjIrbixhPXJbMTFdLGw9cltzXT1OMyhhLHQsWm4ubEZyYW1lLmN1cnJlbnROYW1lc3BhY2UpLGM9by5maXJzdENyZWF0ZVBhc3M/ZnVuY3Rpb24obix0LGUsaSxyLG8scyl7bGV0IGE9dC5jb25zdHMsYz1XMCh0LG4sMixyLFVwKGEsbykpO3JldHVybiBHMyh0LGUsYyxVcChhLHMpKSxudWxsIT09Yy5hdHRycyYmJDEoYyxjLmF0dHJzLCExKSxudWxsIT09Yy5tZXJnZWRBdHRycyYmJDEoYyxjLm1lcmdlZEF0dHJzLCEwKSxudWxsIT09dC5xdWVyaWVzJiZ0LnF1ZXJpZXMuZWxlbWVudFN0YXJ0KHQsYyksY30ocyxvLHIsMCx0LGUsaSk6by5kYXRhW3NdO011KGMsITApO2xldCB1PWMubWVyZ2VkQXR0cnM7bnVsbCE9PXUmJnoxKGEsbCx1KTtsZXQgZD1jLmNsYXNzZXM7bnVsbCE9PWQmJkIzKGEsbCxkKTtsZXQgcD1jLnN0eWxlcztyZXR1cm4gbnVsbCE9PXAmJko5KGEsbCxwKSw2NCE9KDY0JmMuZmxhZ3MpJiZ5VChvLHIsbCxjKSwwPT09Wm4ubEZyYW1lLmVsZW1lbnREZXB0aENvdW50JiZTdShsLHIpLFpuLmxGcmFtZS5lbGVtZW50RGVwdGhDb3VudCsrLGRUKGMpJiYoVTMobyxyLGMpLGxxKG8sYyxyKSksbnVsbCE9PWkmJnozKHIsYyksX31mdW5jdGlvbiB2KCl7bGV0IG49em8oKTttMygpP2czKCk6KG49bi5wYXJlbnQsTXUobiwhMSkpO2xldCB0PW47Wm4ubEZyYW1lLmVsZW1lbnREZXB0aENvdW50LS07bGV0IGU9RmkoKTtyZXR1cm4gZS5maXJzdENyZWF0ZVBhc3MmJihoVChlLG4pLHAzKG4pJiZlLnF1ZXJpZXMuZWxlbWVudEVuZChuKSksbnVsbCE9dC5jbGFzc2VzV2l0aG91dEhvc3QmJmZ1bmN0aW9uKG4pe3JldHVybiAwIT0oMTYmbi5mbGFncyl9KHQpJiZITChlLHQscnQoKSx0LmNsYXNzZXNXaXRob3V0SG9zdCwhMCksbnVsbCE9dC5zdHlsZXNXaXRob3V0SG9zdCYmZnVuY3Rpb24obil7cmV0dXJuIDAhPSgzMiZuLmZsYWdzKX0odCkmJkhMKGUsdCxydCgpLHQuc3R5bGVzV2l0aG91dEhvc3QsITEpLHZ9ZnVuY3Rpb24gTyhuLHQsZSxpKXtyZXR1cm4gXyhuLHQsZSxpKSx2KCksT31mdW5jdGlvbiBzbihuLHQsZSl7bGV0IGk9cnQoKSxyPUZpKCksbz1uKzIyLHM9ci5maXJzdENyZWF0ZVBhc3M/ZnVuY3Rpb24obix0LGUsaSxyKXtsZXQgbz10LmNvbnN0cyxzPVVwKG8saSksYT1XMCh0LG4sOCwibmctY29udGFpbmVyIixzKTtyZXR1cm4gbnVsbCE9PXMmJiQxKGEscywhMCksRzModCxlLGEsVXAobyxyKSksbnVsbCE9PXQucXVlcmllcyYmdC5xdWVyaWVzLmVsZW1lbnRTdGFydCh0LGEpLGF9KG8scixpLHQsZSk6ci5kYXRhW29dO011KHMsITApO2xldCBhPWlbb109aVsxMV0uY3JlYXRlQ29tbWVudCgiIik7cmV0dXJuIHlUKHIsaSxhLHMpLFN1KGEsaSksZFQocykmJihVMyhyLGkscyksbHEocixzLGkpKSxudWxsIT1lJiZ6MyhpLHMpLHNufWZ1bmN0aW9uIGFuKCl7bGV0IG49em8oKSx0PUZpKCk7cmV0dXJuIG0zKCk/ZzMoKToobj1uLnBhcmVudCxNdShuLCExKSksdC5maXJzdENyZWF0ZVBhc3MmJihoVCh0LG4pLHAzKG4pJiZ0LnF1ZXJpZXMuZWxlbWVudEVuZChuKSksYW59ZnVuY3Rpb24gTmkobix0LGUpe3JldHVybiBzbihuLHQsZSksYW4oKSxOaX1mdW5jdGlvbiBQZSgpe3JldHVybiBydCgpfWZ1bmN0aW9uIG5fKG4pe3JldHVybiEhbiYmImZ1bmN0aW9uIj09dHlwZW9mIG4udGhlbn1mdW5jdGlvbiBYMyhuKXtyZXR1cm4hIW4mJiJmdW5jdGlvbiI9PXR5cGVvZiBuLnN1YnNjcmliZX12YXIgUTM9WDM7ZnVuY3Rpb24gUChuLHQsZSxpKXtsZXQgcj1ydCgpLG89RmkoKSxzPXpvKCk7cmV0dXJuIEZxKG8scixyWzExXSxzLG4sdCwwLGkpLFB9ZnVuY3Rpb24gaV8obix0KXtsZXQgZT16bygpLGk9cnQoKSxyPUZpKCk7cmV0dXJuIEZxKHIsaSxNcShfMyhyLmRhdGEpLGUsaSksZSxuLHQpLGlffWZ1bmN0aW9uIEZxKG4sdCxlLGkscixvLHMsYSl7bGV0IGw9ZFQoaSksdT1uLmZpcnN0Q3JlYXRlUGFzcyYmQ3EobikscD14cSh0KSxoPSEwO2lmKDMmaS50eXBlfHxhKXtsZXQgeD1VbChpLHQpLGc9YT9hKHgpOngsYj1wLmxlbmd0aCxEPWE/az0+YSgkYShrW2kuaW5kZXhdKSk6aS5pbmRleCxUPW51bGw7aWYoIWEmJmwmJihUPWZ1bmN0aW9uKG4sdCxlLGkpe2xldCByPW4uY2xlYW51cDtpZihudWxsIT1yKWZvcihsZXQgbz0wO288ci5sZW5ndGgtMTtvKz0yKXtsZXQgcz1yW29dO2lmKHM9PT1lJiZyW28rMV09PT1pKXtsZXQgYT10WzddLGw9cltvKzJdO3JldHVybiBhLmxlbmd0aD5sP2FbbF06bnVsbH0ic3RyaW5nIj09dHlwZW9mIHMmJihvKz0yKX1yZXR1cm4gbnVsbH0obix0LHIsaS5pbmRleCkpLG51bGwhPT1UKShULl9fbmdMYXN0TGlzdGVuZXJGbl9ffHxUKS5fX25nTmV4dExpc3RlbmVyRm5fXz1vLFQuX19uZ0xhc3RMaXN0ZW5lckZuX189byxoPSExO2Vsc2V7bz1zNyhpLHQsMCxvLCExKTtsZXQgaz1lLmxpc3RlbihnLHIsbyk7cC5wdXNoKG8sayksdSYmdS5wdXNoKHIsRCxiLGIrMSl9fWVsc2Ugbz1zNyhpLHQsMCxvLCExKTtsZXQgbSxmPWkub3V0cHV0cztpZihoJiZudWxsIT09ZiYmKG09ZltyXSkpe2xldCB4PW0ubGVuZ3RoO2lmKHgpZm9yKGxldCBnPTA7Zzx4O2crPTIpe2xldCBaPXRbbVtnXV1bbVtnKzFdXS5zdWJzY3JpYmUobyksej1wLmxlbmd0aDtwLnB1c2gobyxaKSx1JiZ1LnB1c2gocixpLmluZGV4LHosLSh6KzEpKX19fWZ1bmN0aW9uIG83KG4sdCxlLGkpe3RyeXtyZXR1cm4hMSE9PWUoaSl9Y2F0Y2gocil7cmV0dXJuIHdxKG4sciksITF9fWZ1bmN0aW9uIHM3KG4sdCxlLGkscil7cmV0dXJuIGZ1bmN0aW9uIG8ocyl7aWYocz09PUZ1bmN0aW9uKXJldHVybiBpO3lxKDImbi5mbGFncz9xcChuLmluZGV4LHQpOnQpO2xldCBsPW83KHQsMCxpLHMpLGM9by5fX25nTmV4dExpc3RlbmVyRm5fXztmb3IoO2M7KWw9bzcodCwwLGMscykmJmwsYz1jLl9fbmdOZXh0TGlzdGVuZXJGbl9fO3JldHVybiByJiYhMT09PWwmJihzLnByZXZlbnREZWZhdWx0KCkscy5yZXR1cm5WYWx1ZT0hMSksbH19ZnVuY3Rpb24gUyhuPTEpe3JldHVybiBmdW5jdGlvbihuKXtyZXR1cm4oWm4ubEZyYW1lLmNvbnRleHRMVmlldz1mdW5jdGlvbihuLHQpe2Zvcig7bj4wOyl0PXRbMTVdLG4tLTtyZXR1cm4gdH0obixabi5sRnJhbWUuY29udGV4dExWaWV3KSlbOF19KG4pfWZ1bmN0aW9uIHZiZShuLHQpe2xldCBlPW51bGwsaT1mdW5jdGlvbihuKXtsZXQgdD1uLmF0dHJzO2lmKG51bGwhPXQpe2xldCBlPXQuaW5kZXhPZig1KTtpZigwPT0oMSZlKSlyZXR1cm4gdFtlKzFdfXJldHVybiBudWxsfShuKTtmb3IobGV0IHI9MDtyPHQubGVuZ3RoO3IrKyl7bGV0IG89dFtyXTtpZigiKiIhPT1vKXtpZihudWxsPT09aT9ucShuLG8sITApOnh5ZShpLG8pKXJldHVybiByfWVsc2UgZT1yfXJldHVybiBlfWZ1bmN0aW9uIHhpKG4pe2xldCB0PXJ0KClbMTZdWzZdO2lmKCF0LnByb2plY3Rpb24pe2xldCBpPXQucHJvamVjdGlvbj1meChuP24ubGVuZ3RoOjEsbnVsbCkscj1pLnNsaWNlKCksbz10LmNoaWxkO2Zvcig7bnVsbCE9PW87KXtsZXQgcz1uP3ZiZShvLG4pOjA7bnVsbCE9PXMmJihyW3NdP3Jbc10ucHJvamVjdGlvbk5leHQ9bzppW3NdPW8scltzXT1vKSxvPW8ubmV4dH19fWZ1bmN0aW9uIFZuKG4sdD0wLGUpe2xldCBpPXJ0KCkscj1GaSgpLG89VzAociwyMituLDE2LG51bGwsZXx8bnVsbCk7bnVsbD09PW8ucHJvamVjdGlvbiYmKG8ucHJvamVjdGlvbj10KSxnMygpLDY0IT0oNjQmby5mbGFncykmJmZ1bmN0aW9uKG4sdCxlKXtaOSh0WzExXSwwLHQsZSx6OShuLGUsdCksVzkoZS5wYXJlbnR8fHRbNl0sZSx0KSl9KHIsaSxvKX1mdW5jdGlvbiBaaShuLHQsZSl7cmV0dXJuIFh4KG4sIiIsdCwiIixlKSxaaX1mdW5jdGlvbiBYeChuLHQsZSxpLHIpe2xldCBvPXJ0KCkscz1YMChvLHQsZSxpKTtyZXR1cm4gcyE9PVFuJiZpbChGaSgpLG5vKCksbyxuLHMsb1sxMV0sciwhMSksWHh9ZnVuY3Rpb24gRVQobix0LGUsaSxyLG8scyl7bGV0IGE9cnQoKSxsPVEwKGEsdCxlLGkscixvKTtyZXR1cm4gbCE9PVFuJiZpbChGaSgpLG5vKCksYSxuLGwsYVsxMV0scywhMSksRVR9ZnVuY3Rpb24gYTcobix0LGUsaSxyKXtsZXQgbz1uW2UrMV0scz1udWxsPT09dCxhPWk/WGYobyk6RjAobyksbD0hMTtmb3IoOzAhPT1hJiYoITE9PT1sfHxzKTspe2xldCB1PW5bYSsxXTt4YmUoblthXSx0KSYmKGw9ITAsblthKzFdPWk/a0wodSk6T0wodSkpLGE9aT9YZih1KTpGMCh1KX1sJiYobltlKzFdPWk/T0wobyk6a0wobykpfWZ1bmN0aW9uIHhiZShuLHQpe3JldHVybiBudWxsPT09bnx8bnVsbD09dHx8KEFycmF5LmlzQXJyYXkobik/blsxXTpuKT09PXR8fCEoIUFycmF5LmlzQXJyYXkobil8fCJzdHJpbmciIT10eXBlb2YgdCkmJkh4KG4sdCk+PTB9dmFyIFVvPXt0ZXh0RW5kOjAsa2V5OjAsa2V5RW5kOjAsdmFsdWU6MCx2YWx1ZUVuZDowfTtmdW5jdGlvbiBqcShuKXtyZXR1cm4gbi5zdWJzdHJpbmcoVW8ua2V5LFVvLmtleUVuZCl9ZnVuY3Rpb24gQ2JlKG4pe3JldHVybiBuLnN1YnN0cmluZyhVby52YWx1ZSxVby52YWx1ZUVuZCl9ZnVuY3Rpb24gR3Eobix0KXtsZXQgZT1Vby50ZXh0RW5kO3JldHVybiBlPT09dD8tMToodD1Vby5rZXlFbmQ9ZnVuY3Rpb24obix0LGUpe2Zvcig7dDxlJiZuLmNoYXJDb2RlQXQodCk+MzI7KXQrKztyZXR1cm4gdH0obixVby5rZXk9dCxlKSxOMChuLHQsZSkpfWZ1bmN0aW9uIFdxKG4sdCl7bGV0IGU9VW8udGV4dEVuZCxpPVVvLmtleT1OMChuLHQsZSk7cmV0dXJuIGU9PT1pPy0xOihpPVVvLmtleUVuZD1mdW5jdGlvbihuLHQsZSl7bGV0IGk7Zm9yKDt0PGUmJig0NT09PShpPW4uY2hhckNvZGVBdCh0KSl8fDk1PT09aXx8KC0zMyZpKT49NjUmJigtMzMmaSk8PTkwfHxpPj00OCYmaTw9NTcpOyl0Kys7cmV0dXJuIHR9KG4saSxlKSxpPWw3KG4saSxlKSxpPVVvLnZhbHVlPU4wKG4saSxlKSxpPVVvLnZhbHVlRW5kPWZ1bmN0aW9uKG4sdCxlKXtsZXQgaT0tMSxyPS0xLG89LTEscz10LGE9cztmb3IoO3M8ZTspe2xldCBsPW4uY2hhckNvZGVBdChzKyspO2lmKDU5PT09bClyZXR1cm4gYTszND09PWx8fDM5PT09bD9hPXM9YzcobixsLHMsZSk6dD09PXMtNCYmODU9PT1vJiY4Mj09PXImJjc2PT09aSYmNDA9PT1sP2E9cz1jNyhuLDQxLHMsZSk6bD4zMiYmKGE9cyksbz1yLHI9aSxpPS0zMyZsfXJldHVybiBhfShuLGksZSksbDcobixpLGUpKX1mdW5jdGlvbiBxcShuKXtVby5rZXk9MCxVby5rZXlFbmQ9MCxVby52YWx1ZT0wLFVvLnZhbHVlRW5kPTAsVW8udGV4dEVuZD1uLmxlbmd0aH1mdW5jdGlvbiBOMChuLHQsZSl7Zm9yKDt0PGUmJm4uY2hhckNvZGVBdCh0KTw9MzI7KXQrKztyZXR1cm4gdH1mdW5jdGlvbiBsNyhuLHQsZSxpKXtyZXR1cm4odD1OMChuLHQsZSkpPGUmJnQrKyx0fWZ1bmN0aW9uIGM3KG4sdCxlLGkpe2xldCByPS0xLG89ZTtmb3IoO288aTspe2xldCBzPW4uY2hhckNvZGVBdChvKyspO2lmKHM9PXQmJjkyIT09cilyZXR1cm4gbztyPTkyPT1zJiY5Mj09PXI/MDpzfXRocm93IG5ldyBFcnJvcn1mdW5jdGlvbiBQdChuLHQsZSl7cmV0dXJuIFJjKG4sdCxlLCExKSxQdH1mdW5jdGlvbiBldChuLHQpe3JldHVybiBSYyhuLHQsbnVsbCwhMCksZXR9ZnVuY3Rpb24gamwobil7T2MoUXEsRGJlLG4sITEpfWZ1bmN0aW9uIERiZShuLHQpe2ZvcihsZXQgZT1mdW5jdGlvbihuKXtyZXR1cm4gcXEobiksV3EobixOMChuLDAsVW8udGV4dEVuZCkpfSh0KTtlPj0wO2U9V3EodCxlKSlRcShuLGpxKHQpLENiZSh0KSl9ZnVuY3Rpb24gRGEobil7T2MoZWwsQXUsbiwhMCl9ZnVuY3Rpb24gQXUobix0KXtmb3IobGV0IGU9ZnVuY3Rpb24obil7cmV0dXJuIHFxKG4pLEdxKG4sTjAobiwwLFVvLnRleHRFbmQpKX0odCk7ZT49MDtlPUdxKHQsZSkpZWwobixqcSh0KSwhMCl9ZnVuY3Rpb24gUmMobix0LGUsaSl7bGV0IHI9cnQoKSxvPUZpKCkscz1VZCgyKTtvLmZpcnN0VXBkYXRlUGFzcyYmWHEobyxuLHMsaSksdCE9PVFuJiZEcyhyLHMsdCkmJktxKG8sby5kYXRhW1pzKCldLHIsclsxMV0sbixyW3MrMV09ZnVuY3Rpb24obix0KXtyZXR1cm4gbnVsbD09bnx8KCJzdHJpbmciPT10eXBlb2YgdD9uKz10OiJvYmplY3QiPT10eXBlb2YgbiYmKG49VG8oVGEobikpKSksbn0odCxlKSxpLHMpfWZ1bmN0aW9uIE9jKG4sdCxlLGkpe2xldCByPUZpKCksbz1VZCgyKTtyLmZpcnN0VXBkYXRlUGFzcyYmWHEocixudWxsLG8saSk7bGV0IHM9cnQoKTtpZihlIT09UW4mJkRzKHMsbyxlKSl7bGV0IGE9ci5kYXRhW1pzKCldO2lmKFpxKGEsaSkmJiFZcShyLG8pKXtsZXQgbD1pP2EuY2xhc3Nlc1dpdGhvdXRIb3N0OmEuc3R5bGVzV2l0aG91dEhvc3Q7bnVsbCE9PWwmJihlPXRMKGwsZXx8IiIpKSxITChyLGEscyxlLGkpfWVsc2UhZnVuY3Rpb24obix0LGUsaSxyLG8scyxhKXtyPT09UW4mJihyPVFpKTtsZXQgbD0wLGM9MCx1PTA8ci5sZW5ndGg/clswXTpudWxsLGQ9MDxvLmxlbmd0aD9vWzBdOm51bGw7Zm9yKDtudWxsIT09dXx8bnVsbCE9PWQ7KXtsZXQgbSxwPWw8ci5sZW5ndGg/cltsKzFdOnZvaWQgMCxoPWM8by5sZW5ndGg/b1tjKzFdOnZvaWQgMCxmPW51bGw7dT09PWQ/KGwrPTIsYys9MixwIT09aCYmKGY9ZCxtPWgpKTpudWxsPT09ZHx8bnVsbCE9PXUmJnU8ZD8obCs9MixmPXUpOihjKz0yLGY9ZCxtPWgpLG51bGwhPT1mJiZLcShuLHQsZSxpLGYsbSxzLGEpLHU9bDxyLmxlbmd0aD9yW2xdOm51bGwsZD1jPG8ubGVuZ3RoP29bY106bnVsbH19KHIsYSxzLHNbMTFdLHNbbysxXSxzW28rMV09ZnVuY3Rpb24obix0LGUpe2lmKG51bGw9PWV8fCIiPT09ZSlyZXR1cm4gUWk7bGV0IGk9W10scj1UYShlKTtpZihBcnJheS5pc0FycmF5KHIpKWZvcihsZXQgbz0wO288ci5sZW5ndGg7bysrKW4oaSxyW29dLCEwKTtlbHNlIGlmKCJvYmplY3QiPT10eXBlb2Ygcilmb3IobGV0IG8gaW4gcilyLmhhc093blByb3BlcnR5KG8pJiZuKGksbyxyW29dKTtlbHNlInN0cmluZyI9PXR5cGVvZiByJiZ0KGkscik7cmV0dXJuIGl9KG4sdCxlKSxpLG8pfX1mdW5jdGlvbiBZcShuLHQpe3JldHVybiB0Pj1uLmV4cGFuZG9TdGFydEluZGV4fWZ1bmN0aW9uIFhxKG4sdCxlLGkpe2xldCByPW4uZGF0YTtpZihudWxsPT09cltlKzFdKXtsZXQgbz1yW1pzKCldLHM9WXEobixlKTtacShvLGkpJiZudWxsPT09dCYmIXMmJih0PSExKSx0PWZ1bmN0aW9uKG4sdCxlLGkpe2xldCByPV8zKG4pLG89aT90LnJlc2lkdWFsQ2xhc3Nlczp0LnJlc2lkdWFsU3R5bGVzO2lmKG51bGw9PT1yKTA9PT0oaT90LmNsYXNzQmluZGluZ3M6dC5zdHlsZUJpbmRpbmdzKSYmKGU9SXgoZT1XTihudWxsLG4sdCxlLGkpLHQuYXR0cnMsaSksbz1udWxsKTtlbHNle2xldCBzPXQuZGlyZWN0aXZlU3R5bGluZ0xhc3Q7aWYoLTE9PT1zfHxuW3NdIT09cilpZihlPVdOKHIsbix0LGUsaSksbnVsbD09PW8pe2xldCBsPWZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1lP3QuY2xhc3NCaW5kaW5nczp0LnN0eWxlQmluZGluZ3M7aWYoMCE9PUYwKGkpKXJldHVybiBuW1hmKGkpXX0obix0LGkpO3ZvaWQgMCE9PWwmJkFycmF5LmlzQXJyYXkobCkmJihsPVdOKG51bGwsbix0LGxbMV0saSksbD1JeChsLHQuYXR0cnMsaSksZnVuY3Rpb24obix0LGUsaSl7bltYZihlP3QuY2xhc3NCaW5kaW5nczp0LnN0eWxlQmluZGluZ3MpXT1pfShuLHQsaSxsKSl9ZWxzZSBvPWZ1bmN0aW9uKG4sdCxlKXtsZXQgaSxyPXQuZGlyZWN0aXZlRW5kO2ZvcihsZXQgbz0xK3QuZGlyZWN0aXZlU3R5bGluZ0xhc3Q7bzxyO28rKylpPUl4KGksbltvXS5ob3N0QXR0cnMsZSk7cmV0dXJuIEl4KGksdC5hdHRycyxlKX0obix0LGkpfXJldHVybiB2b2lkIDAhPT1vJiYoaT90LnJlc2lkdWFsQ2xhc3Nlcz1vOnQucmVzaWR1YWxTdHlsZXM9byksZX0ocixvLHQsaSksZnVuY3Rpb24obix0LGUsaSxyLG8pe2xldCBzPW8/dC5jbGFzc0JpbmRpbmdzOnQuc3R5bGVCaW5kaW5ncyxhPVhmKHMpLGw9RjAocyk7bltpXT1lO2xldCB1LGM9ITE7aWYoQXJyYXkuaXNBcnJheShlKSl7bGV0IGQ9ZTt1PWRbMV0sKG51bGw9PT11fHxIeChkLHUpPjApJiYoYz0hMCl9ZWxzZSB1PWU7aWYocilpZigwIT09bCl7bGV0IHA9WGYoblthKzFdKTtuW2krMV09RTEocCxhKSwwIT09cCYmKG5bcCsxXT1qTihuW3ArMV0saSkpLG5bYSsxXT1mdW5jdGlvbihuLHQpe3JldHVybiAxMzEwNzEmbnx0PDwxN30oblthKzFdLGkpfWVsc2UgbltpKzFdPUUxKGEsMCksMCE9PWEmJihuW2ErMV09ak4oblthKzFdLGkpKSxhPWk7ZWxzZSBuW2krMV09RTEobCwwKSwwPT09YT9hPWk6bltsKzFdPWpOKG5bbCsxXSxpKSxsPWk7YyYmKG5baSsxXT1PTChuW2krMV0pKSxhNyhuLHUsaSwhMCksYTcobix1LGksITEpLGZ1bmN0aW9uKG4sdCxlLGkscil7bGV0IG89cj9uLnJlc2lkdWFsQ2xhc3NlczpuLnJlc2lkdWFsU3R5bGVzO251bGwhPW8mJiJzdHJpbmciPT10eXBlb2YgdCYmSHgobyx0KT49MCYmKGVbaSsxXT1rTChlW2krMV0pKX0odCx1LG4saSxvKSxzPUUxKGEsbCksbz90LmNsYXNzQmluZGluZ3M9czp0LnN0eWxlQmluZGluZ3M9c30ocixvLHQsZSxzLGkpfX1mdW5jdGlvbiBXTihuLHQsZSxpLHIpe2xldCBvPW51bGwscz1lLmRpcmVjdGl2ZUVuZCxhPWUuZGlyZWN0aXZlU3R5bGluZ0xhc3Q7Zm9yKC0xPT09YT9hPWUuZGlyZWN0aXZlU3RhcnQ6YSsrO2E8cyYmKG89dFthXSxpPUl4KGksby5ob3N0QXR0cnMsciksbyE9PW4pOylhKys7cmV0dXJuIG51bGwhPT1uJiYoZS5kaXJlY3RpdmVTdHlsaW5nTGFzdD1hKSxpfWZ1bmN0aW9uIEl4KG4sdCxlKXtsZXQgaT1lPzE6MixyPS0xO2lmKG51bGwhPT10KWZvcihsZXQgbz0wO288dC5sZW5ndGg7bysrKXtsZXQgcz10W29dOyJudW1iZXIiPT10eXBlb2Ygcz9yPXM6cj09PWkmJihBcnJheS5pc0FycmF5KG4pfHwobj12b2lkIDA9PT1uP1tdOlsiIixuXSksZWwobixzLCEhZXx8dFsrK29dKSl9cmV0dXJuIHZvaWQgMD09PW4/bnVsbDpufWZ1bmN0aW9uIFFxKG4sdCxlKXtlbChuLHQsVGEoZSkpfWZ1bmN0aW9uIEtxKG4sdCxlLGkscixvLHMsYSl7aWYoISgzJnQudHlwZSkpcmV0dXJuO2xldCBsPW4uZGF0YSxjPWxbYSsxXSx1PWZ1bmN0aW9uKG4pe3JldHVybiAxPT0oMSZuKX0oYyk/dTcobCx0LGUscixGMChjKSxzKTp2b2lkIDA7dFQodSl8fCh0VChvKXx8ZnVuY3Rpb24obil7cmV0dXJuIDI9PSgyJm4pfShjKSYmKG89dTcobCxudWxsLGUscixhLHMpKSxmdW5jdGlvbihuLHQsZSxpLHIpe2lmKHQpcj9uLmFkZENsYXNzKGUsaSk6bi5yZW1vdmVDbGFzcyhlLGkpO2Vsc2V7bGV0IG89LTE9PT1pLmluZGV4T2YoIi0iKT92b2lkIDA6QmwuRGFzaENhc2U7bnVsbD09cj9uLnJlbW92ZVN0eWxlKGUsaSxvKTooInN0cmluZyI9PXR5cGVvZiByJiZyLmVuZHNXaXRoKCIhaW1wb3J0YW50IikmJihyPXIuc2xpY2UoMCwtMTApLG98PUJsLkltcG9ydGFudCksbi5zZXRTdHlsZShlLGkscixvKSl9fShpLHMscFQoWnMoKSxlKSxyLG8pKX1mdW5jdGlvbiB1NyhuLHQsZSxpLHIsbyl7bGV0IGEscz1udWxsPT09dDtmb3IoO3I+MDspe2xldCBsPW5bcl0sYz1BcnJheS5pc0FycmF5KGwpLHU9Yz9sWzFdOmwsZD1udWxsPT09dSxwPWVbcisxXTtwPT09UW4mJihwPWQ/UWk6dm9pZCAwKTtsZXQgaD1kP0xOKHAsaSk6dT09PWk/cDp2b2lkIDA7aWYoYyYmIXRUKGgpJiYoaD1MTihsLGkpKSx0VChoKSYmKGE9aCxzKSlyZXR1cm4gYTtsZXQgZj1uW3IrMV07cj1zP1hmKGYpOkYwKGYpfWlmKG51bGwhPT10KXtsZXQgbD1vP3QucmVzaWR1YWxDbGFzc2VzOnQucmVzaWR1YWxTdHlsZXM7bnVsbCE9bCYmKGE9TE4obCxpKSl9cmV0dXJuIGF9ZnVuY3Rpb24gdFQobil7cmV0dXJuIHZvaWQgMCE9PW59ZnVuY3Rpb24gWnEobix0KXtyZXR1cm4gMCE9KG4uZmxhZ3MmKHQ/MTY6MzIpKX1mdW5jdGlvbiBBKG4sdD0iIil7bGV0IGU9cnQoKSxpPUZpKCkscj1uKzIyLG89aS5maXJzdENyZWF0ZVBhc3M/VzAoaSxyLDEsdCxudWxsKTppLmRhdGFbcl0scz1lW3JdPUYzKGVbMTFdLHQpO3lUKGksZSxzLG8pLE11KG8sITEpfWZ1bmN0aW9uIHl0KG4pe3JldHVybiBqZSgiIixuLCIiKSx5dH1mdW5jdGlvbiBqZShuLHQsZSl7bGV0IGk9cnQoKSxyPVgwKGksbix0LGUpO3JldHVybiByIT09UW4mJmpkKGksWnMoKSxyKSxqZX1mdW5jdGlvbiBYcChuLHQsZSxpLHIpe2xldCBvPXJ0KCkscz1RMChvLG4sdCxlLGkscik7cmV0dXJuIHMhPT1RbiYmamQobyxacygpLHMpLFhwfWZ1bmN0aW9uIFRUKG4sdCxlLGkscixvLHMpe2xldCBhPXJ0KCksbD1LMChhLG4sdCxlLGkscixvLHMpO3JldHVybiBsIT09UW4mJmpkKGEsWnMoKSxsKSxUVH1mdW5jdGlvbiBReChuLHQsZSl7T2MoZWwsQXUsWDAocnQoKSxuLHQsZSksITApfWZ1bmN0aW9uIF9zKG4sdCxlKXtsZXQgaT1ydCgpO3JldHVybiBEcyhpLFUwKCksdCkmJmlsKEZpKCksbm8oKSxpLG4sdCxpWzExXSxlLCEwKSxfc31mdW5jdGlvbiByXyhuLHQsZSl7bGV0IGk9cnQoKTtpZihEcyhpLFUwKCksdCkpe2xldCBvPUZpKCkscz1ubygpO2lsKG8scyxpLG4sdCxNcShfMyhvLmRhdGEpLHMsaSksZSwhMCl9cmV0dXJuIHJffXZhciBWZj12b2lkIDAsZXhlPVsiZW4iLFtbImEiLCJwIl0sWyJBTSIsIlBNIl0sVmZdLFtbIkFNIiwiUE0iXSxWZixWZl0sW1siUyIsIk0iLCJUIiwiVyIsIlQiLCJGIiwiUyJdLFsiU3VuIiwiTW9uIiwiVHVlIiwiV2VkIiwiVGh1IiwiRnJpIiwiU2F0Il0sWyJTdW5kYXkiLCJNb25kYXkiLCJUdWVzZGF5IiwiV2VkbmVzZGF5IiwiVGh1cnNkYXkiLCJGcmlkYXkiLCJTYXR1cmRheSJdLFsiU3UiLCJNbyIsIlR1IiwiV2UiLCJUaCIsIkZyIiwiU2EiXV0sVmYsW1siSiIsIkYiLCJNIiwiQSIsIk0iLCJKIiwiSiIsIkEiLCJTIiwiTyIsIk4iLCJEIl0sWyJKYW4iLCJGZWIiLCJNYXIiLCJBcHIiLCJNYXkiLCJKdW4iLCJKdWwiLCJBdWciLCJTZXAiLCJPY3QiLCJOb3YiLCJEZWMiXSxbIkphbnVhcnkiLCJGZWJydWFyeSIsIk1hcmNoIiwiQXByaWwiLCJNYXkiLCJKdW5lIiwiSnVseSIsIkF1Z3VzdCIsIlNlcHRlbWJlciIsIk9jdG9iZXIiLCJOb3ZlbWJlciIsIkRlY2VtYmVyIl1dLFZmLFtbIkIiLCJBIl0sWyJCQyIsIkFEIl0sWyJCZWZvcmUgQ2hyaXN0IiwiQW5ubyBEb21pbmkiXV0sMCxbNiwwXSxbIk0vZC95eSIsIk1NTSBkLCB5IiwiTU1NTSBkLCB5IiwiRUVFRSwgTU1NTSBkLCB5Il0sWyJoOm1tIGEiLCJoOm1tOnNzIGEiLCJoOm1tOnNzIGEgeiIsImg6bW06c3MgYSB6enp6Il0sWyJ7MX0sIHswfSIsVmYsInsxfSAnYXQnIHswfSIsVmZdLFsiLiIsIiwiLCI7IiwiJSIsIisiLCItIiwiRSIsIlx4ZDciLCJcdTIwMzAiLCJcdTIyMWUiLCJOYU4iLCI6Il0sWyIjLCMjMC4jIyMiLCIjLCMjMCUiLCJceGE0IywjIzAuMDAiLCIjRTAiXSwiVVNEIiwiJCIsIlVTIERvbGxhciIse30sImx0ciIsZnVuY3Rpb24obil7bGV0IGU9TWF0aC5mbG9vcihNYXRoLmFicyhuKSksaT1uLnRvU3RyaW5nKCkucmVwbGFjZSgvXlteLl0qXC4/LywiIikubGVuZ3RoO3JldHVybiAxPT09ZSYmMD09PWk/MTo1fV0scU49e307ZnVuY3Rpb24gQWEobil7bGV0IHQ9ZnVuY3Rpb24obil7cmV0dXJuIG4udG9Mb3dlckNhc2UoKS5yZXBsYWNlKC9fL2csIi0iKX0obiksZT1kNyh0KTtpZihlKXJldHVybiBlO2xldCBpPXQuc3BsaXQoIi0iKVswXTtpZihlPWQ3KGkpLGUpcmV0dXJuIGU7aWYoImVuIj09PWkpcmV0dXJuIGV4ZTt0aHJvdyBuZXcgQXQoNzAxLCExKX1mdW5jdGlvbiBkNyhuKXtyZXR1cm4gbiBpbiBxTnx8KHFOW25dPXRvLm5nJiZ0by5uZy5jb21tb24mJnRvLm5nLmNvbW1vbi5sb2NhbGVzJiZ0by5uZy5jb21tb24ubG9jYWxlc1tuXSkscU5bbl19dmFyIFJyPSgoKT0+e3JldHVybihuPVJyfHwoUnI9e30pKVtuLkxvY2FsZUlkPTBdPSJMb2NhbGVJZCIsbltuLkRheVBlcmlvZHNGb3JtYXQ9MV09IkRheVBlcmlvZHNGb3JtYXQiLG5bbi5EYXlQZXJpb2RzU3RhbmRhbG9uZT0yXT0iRGF5UGVyaW9kc1N0YW5kYWxvbmUiLG5bbi5EYXlzRm9ybWF0PTNdPSJEYXlzRm9ybWF0IixuW24uRGF5c1N0YW5kYWxvbmU9NF09IkRheXNTdGFuZGFsb25lIixuW24uTW9udGhzRm9ybWF0PTVdPSJNb250aHNGb3JtYXQiLG5bbi5Nb250aHNTdGFuZGFsb25lPTZdPSJNb250aHNTdGFuZGFsb25lIixuW24uRXJhcz03XT0iRXJhcyIsbltuLkZpcnN0RGF5T2ZXZWVrPThdPSJGaXJzdERheU9mV2VlayIsbltuLldlZWtlbmRSYW5nZT05XT0iV2Vla2VuZFJhbmdlIixuW24uRGF0ZUZvcm1hdD0xMF09IkRhdGVGb3JtYXQiLG5bbi5UaW1lRm9ybWF0PTExXT0iVGltZUZvcm1hdCIsbltuLkRhdGVUaW1lRm9ybWF0PTEyXT0iRGF0ZVRpbWVGb3JtYXQiLG5bbi5OdW1iZXJTeW1ib2xzPTEzXT0iTnVtYmVyU3ltYm9scyIsbltuLk51bWJlckZvcm1hdHM9MTRdPSJOdW1iZXJGb3JtYXRzIixuW24uQ3VycmVuY3lDb2RlPTE1XT0iQ3VycmVuY3lDb2RlIixuW24uQ3VycmVuY3lTeW1ib2w9MTZdPSJDdXJyZW5jeVN5bWJvbCIsbltuLkN1cnJlbmN5TmFtZT0xN109IkN1cnJlbmN5TmFtZSIsbltuLkN1cnJlbmNpZXM9MThdPSJDdXJyZW5jaWVzIixuW24uRGlyZWN0aW9uYWxpdHk9MTldPSJEaXJlY3Rpb25hbGl0eSIsbltuLlBsdXJhbENhc2U9MjBdPSJQbHVyYWxDYXNlIixuW24uRXh0cmFEYXRhPTIxXT0iRXh0cmFEYXRhIixScjt2YXIgbn0pKCksbnhlPVsiemVybyIsIm9uZSIsInR3byIsImZldyIsIm1hbnkiXTt2YXIgblQ9ImVuLVVTIixmWT17bWFya2VyOiJlbGVtZW50In0sbVk9e21hcmtlcjoiSUNVIn0sRWM9KCgpPT57cmV0dXJuKG49RWN8fChFYz17fSkpW24uU0hJRlQ9Ml09IlNISUZUIixuW24uQVBQRU5EX0VBR0VSTFk9MV09IkFQUEVORF9FQUdFUkxZIixuW24uQ09NTUVOVD0yXT0iQ09NTUVOVCIsRWM7dmFyIG59KSgpLGdZPW5UO2Z1bmN0aW9uIF9ZKG4sdCxlKXtsZXQgaT10Lmluc2VydEJlZm9yZUluZGV4LHI9QXJyYXkuaXNBcnJheShpKT9pWzBdOmk7cmV0dXJuIG51bGw9PT1yP3E5KG4sMCxlKTokYShlW3JdKX1mdW5jdGlvbiB2WShuLHQsZSxpLHIpe2xldCBvPXQuaW5zZXJ0QmVmb3JlSW5kZXg7aWYoQXJyYXkuaXNBcnJheShvKSl7bGV0IHM9aSxhPW51bGw7aWYoMyZ0LnR5cGV8fChhPXMscz1yKSxudWxsIT09cyYmMD09KDImdC5mbGFncykpZm9yKGxldCBsPTE7bDxvLmxlbmd0aDtsKyspWWYobixzLGVbb1tsXV0sYSwhMSl9fWZ1bmN0aW9uIHlZKG4sdCl7aWYobi5wdXNoKHQpLG4ubGVuZ3RoPjEpZm9yKGxldCBlPW4ubGVuZ3RoLTI7ZT49MDtlLS0pe2xldCBpPW5bZV07YlkoaSl8fGF4ZShpLHQpJiZudWxsPT09bHhlKGkpJiZjeGUoaSx0LmluZGV4KX19ZnVuY3Rpb24gYlkobil7cmV0dXJuISg2NCZuLnR5cGUpfWZ1bmN0aW9uIGF4ZShuLHQpe3JldHVybiBiWSh0KXx8bi5pbmRleD50LmluZGV4fWZ1bmN0aW9uIGx4ZShuKXtsZXQgdD1uLmluc2VydEJlZm9yZUluZGV4O3JldHVybiBBcnJheS5pc0FycmF5KHQpP3RbMF06dH1mdW5jdGlvbiBjeGUobix0KXtsZXQgZT1uLmluc2VydEJlZm9yZUluZGV4O0FycmF5LmlzQXJyYXkoZSk/ZVswXT10OihYOShfWSx2WSksbi5pbnNlcnRCZWZvcmVJbmRleD10KX1mdW5jdGlvbiBfeChuLHQpe2xldCBlPW4uZGF0YVt0XTtyZXR1cm4gbnVsbD09PWV8fCJzdHJpbmciPT10eXBlb2YgZT9udWxsOmUuaGFzT3duUHJvcGVydHkoImN1cnJlbnRDYXNlTFZpZXdJbmRleCIpP2U6ZS52YWx1ZX1mdW5jdGlvbiBweGUobix0LGUpe2xldCBpPVYzKG4sZSw2NCxudWxsLG51bGwpO3JldHVybiB5WSh0LGkpLGl9ZnVuY3Rpb24gRFQobix0KXtsZXQgZT10W24uY3VycmVudENhc2VMVmlld0luZGV4XTtyZXR1cm4gbnVsbD09PWU/ZTplPDA/fmU6ZX1mdW5jdGlvbiBoeGUobil7cmV0dXJuIG4+Pj4xN31mdW5jdGlvbiBmeGUobil7cmV0dXJuKDEzMTA3MCZuKT4+PjF9dmFyIFB4PTAsdng9MDtmdW5jdGlvbiB4WShuLHQsZSxpKXtsZXQgcyxyPWVbMTFdLG89bnVsbDtmb3IobGV0IGE9MDthPHQubGVuZ3RoO2ErKyl7bGV0IGw9dFthXTtpZigic3RyaW5nIj09dHlwZW9mIGwpe2xldCBjPXRbKythXTtudWxsPT09ZVtjXSYmKGVbY109RjMocixsKSl9ZWxzZSBpZigibnVtYmVyIj09dHlwZW9mIGwpc3dpdGNoKDEmbCl7Y2FzZSAwOmxldCB1LGQsYz1oeGUobCk7aWYobnVsbD09PW8mJihvPWMscz12VChyLGkpKSxjPT09bz8odT1pLGQ9cyk6KHU9bnVsbCxkPSRhKGVbY10pKSxudWxsIT09ZCl7bGV0IG09ZnhlKGwpO1lmKHIsZCxlW21dLHUsITEpO2xldCBnPV94KG4sbSk7aWYobnVsbCE9PWcmJiJvYmplY3QiPT10eXBlb2YgZyl7bGV0IGI9RFQoZyxlKTtudWxsIT09YiYmeFkobixnLmNyZWF0ZVtiXSxlLGVbZy5hbmNob3JJZHhdKX19YnJlYWs7Y2FzZSAxOmxldCBoPXRbKythXSxmPXRbKythXTtXMyhyLHBUKGw+Pj4xLGUpLG51bGwsbnVsbCxoLGYsbnVsbCl9ZWxzZSBzd2l0Y2gobCl7Y2FzZSBtWTpsZXQgYz10WysrYV0sdT10WysrYV07bnVsbD09PWVbdV0mJlN1KGVbdV09ZXllKHIsYyksZSk7YnJlYWs7Y2FzZSBmWTpsZXQgZD10WysrYV0scD10WysrYV07bnVsbD09PWVbcF0mJlN1KGVbcF09TjMocixkLG51bGwpLGUpfX19ZnVuY3Rpb24gQ1kobix0LGUsaSxyKXtmb3IobGV0IG89MDtvPGUubGVuZ3RoO28rKyl7bGV0IHM9ZVtvXSxhPWVbKytvXTtpZihzJnIpe2xldCBsPSIiO2ZvcihsZXQgYz1vKzE7Yzw9bythO2MrKyl7bGV0IHU9ZVtjXTtpZigic3RyaW5nIj09dHlwZW9mIHUpbCs9dTtlbHNlIGlmKCJudW1iZXIiPT10eXBlb2YgdSlpZih1PDApbCs9S24odFtpLXVdKTtlbHNle2xldCBkPXU+Pj4yO3N3aXRjaCgzJnUpe2Nhc2UgMTpsZXQgcD1lWysrY10saD1lWysrY10sZj1uLmRhdGFbZF07InN0cmluZyI9PXR5cGVvZiBmP1czKHRbMTFdLHRbZF0sbnVsbCxmLHAsbCxoKTppbChuLGYsdCxwLGwsdFsxMV0saCwhMSk7YnJlYWs7Y2FzZSAwOmxldCBtPXRbZF07bnVsbCE9PW0mJlY5KHRbMTFdLG0sbCk7YnJlYWs7Y2FzZSAyOnl4ZShuLF94KG4sZCksdCxsKTticmVhaztjYXNlIDM6cDcobixfeChuLGQpLGksdCl9fX19ZWxzZXtsZXQgbD1lW28rMV07aWYobD4wJiYzPT0oMyZsKSl7bGV0IHU9X3gobixsPj4+Mik7dFt1LmN1cnJlbnRDYXNlTFZpZXdJbmRleF08MCYmcDcobix1LGksdCl9fW8rPWF9fWZ1bmN0aW9uIHA3KG4sdCxlLGkpe2xldCByPWlbdC5jdXJyZW50Q2FzZUxWaWV3SW5kZXhdO2lmKG51bGwhPT1yKXtsZXQgbz1QeDtyPDAmJihyPWlbdC5jdXJyZW50Q2FzZUxWaWV3SW5kZXhdPX5yLG89LTEpLENZKG4saSx0LnVwZGF0ZVtyXSxlLG8pfX1mdW5jdGlvbiB5eGUobix0LGUsaSl7bGV0IHI9ZnVuY3Rpb24obix0KXtsZXQgZT1uLmNhc2VzLmluZGV4T2YodCk7aWYoLTE9PT1lKXN3aXRjaChuLnR5cGUpe2Nhc2UgMTp7bGV0IGk9ZnVuY3Rpb24obix0KXtsZXQgZT1mdW5jdGlvbihuKXtyZXR1cm4gQWEobilbUnIuUGx1cmFsQ2FzZV19KHQpKHBhcnNlSW50KG4sMTApKSxpPW54ZVtlXTtyZXR1cm4gdm9pZCAwIT09aT9pOiJvdGhlciJ9KHQsZ1kpO2U9bi5jYXNlcy5pbmRleE9mKGkpLC0xPT09ZSYmIm90aGVyIiE9PWkmJihlPW4uY2FzZXMuaW5kZXhPZigib3RoZXIiKSk7YnJlYWt9Y2FzZSAwOmU9bi5jYXNlcy5pbmRleE9mKCJvdGhlciIpfXJldHVybi0xPT09ZT9udWxsOmV9KHQsaSk7aWYoRFQodCxlKSE9PXImJihNWShuLHQsZSksZVt0LmN1cnJlbnRDYXNlTFZpZXdJbmRleF09bnVsbD09PXI/bnVsbDp+cixudWxsIT09cikpe2xldCBzPWVbdC5hbmNob3JJZHhdO3MmJnhZKG4sdC5jcmVhdGVbcl0sZSxzKX19ZnVuY3Rpb24gTVkobix0LGUpe2xldCBpPURUKHQsZSk7aWYobnVsbCE9PWkpe2xldCByPXQucmVtb3ZlW2ldO2ZvcihsZXQgbz0wO288ci5sZW5ndGg7bysrKXtsZXQgcz1yW29dO2lmKHM+MCl7bGV0IGE9cFQocyxlKTtudWxsIT09YSYmSzkoZVsxMV0sYSl9ZWxzZSBNWShuLF94KG4sfnMpLGUpfX19ZnVuY3Rpb24geHhlKCl7bGV0IGUsaSxuPVtdLHQ9LTE7ZnVuY3Rpb24gbyhhLGwpe3Q9MDtsZXQgYz1EVChhLGwpO2k9bnVsbCE9PWM/YS5yZW1vdmVbY106UWl9ZnVuY3Rpb24gcygpe2lmKHQ8aS5sZW5ndGgpe2xldCBhPWlbdCsrXTtyZXR1cm4gYT4wP2VbYV06KG4ucHVzaCh0LGkpLG8oZVsxXS5kYXRhW35hXSxlKSxzKCkpfXJldHVybiAwPT09bi5sZW5ndGg/bnVsbDooaT1uLnBvcCgpLHQ9bi5wb3AoKSxzKCkpfXJldHVybiBmdW5jdGlvbihhLGwpe2ZvcihlPWw7bi5sZW5ndGg7KW4ucG9wKCk7cmV0dXJuIG8oYS52YWx1ZSxsKSxzfX12YXIgaVQ9L1x1ZmZmZChcZCspOj9cZCpcdWZmZmQvZ2ksQ3hlPS8oe1xzKlx1ZmZmZFxkKzo/XGQqXHVmZmZkXHMqLFxzKlxTezZ9XHMqLFtcc1xTXSp9KS9naSxNeGU9L1x1ZmZmZChcZCspXHVmZmZkLyx3WT0vXlxzKihcdWZmZmRcZCs6P1xkKlx1ZmZmZClccyosXHMqKHNlbGVjdHxwbHVyYWwpXHMqLC8sd3hlPS9cdWZmZmRcLz9cKihcZCs6XGQrKVx1ZmZmZC9naSxTeGU9L1x1ZmZmZChcLz9bIypdXGQrKTo/XGQqXHVmZmZkL2dpLEV4ZT0vXHVFNTAwL2c7ZnVuY3Rpb24gU1kobix0LGUsaSxyLG8scyl7bGV0IGE9cTAobixpLDEsbnVsbCksbD1hPDxFYy5TSElGVCxjPXd4KCk7dD09PWMmJihjPW51bGwpLG51bGw9PT1jJiYobHw9RWMuQVBQRU5EX0VBR0VSTFkpLHMmJihsfD1FYy5DT01NRU5ULGZ1bmN0aW9uKG4pe3ZvaWQgMD09PURMJiYoREw9bigpKX0oeHhlKSksci5wdXNoKGwsbnVsbD09PW8/IiI6byk7bGV0IHU9VjMobixhLHM/MzI6MSxudWxsPT09bz8iIjpvLG51bGwpO3lZKGUsdSk7bGV0IGQ9dS5pbmRleDtyZXR1cm4gTXUodSwhMSksbnVsbCE9PWMmJnQhPT1jJiZmdW5jdGlvbihuLHQpe2xldCBlPW4uaW5zZXJ0QmVmb3JlSW5kZXg7bnVsbD09PWU/KFg5KF9ZLHZZKSxlPW4uaW5zZXJ0QmVmb3JlSW5kZXg9W251bGwsdF0pOihmdW5jdGlvbihuLHQsZSl7MSE9biYmVDcoIkV4cGVjdGluZyBhcnJheSBoZXJlIixuLCEwLCI9PSIpfShBcnJheS5pc0FycmF5KGUpKSxlLnB1c2godCkpfShjLGQpLHV9ZnVuY3Rpb24gQXhlKG4sdCxlLGkscixvLHMpe2xldCBhPXMubWF0Y2goaVQpLGw9U1kobix0LGUsbyxpLGE/bnVsbDpzLCExKTthJiZieChyLHMsbC5pbmRleCxudWxsLDAsbnVsbCl9ZnVuY3Rpb24gYngobix0LGUsaSxyLG8pe2xldCBzPW4ubGVuZ3RoLGE9cysxO24ucHVzaChudWxsLG51bGwpO2xldCBsPXMrMixjPXQuc3BsaXQoaVQpLHU9MDtmb3IobGV0IGQ9MDtkPGMubGVuZ3RoO2QrKyl7bGV0IHA9Y1tkXTtpZigxJmQpe2xldCBoPXIrcGFyc2VJbnQocCwxMCk7bi5wdXNoKC0xLWgpLHV8PUVZKGgpfWVsc2UiIiE9PXAmJm4ucHVzaChwKX1yZXR1cm4gbi5wdXNoKGU8PDJ8KGk/MTowKSksaSYmbi5wdXNoKGksbyksbltzXT11LG5bYV09bi5sZW5ndGgtbCx1fWZ1bmN0aW9uIFB4ZShuKXtsZXQgdD0wO2ZvcihsZXQgZT0wO2U8bi5sZW5ndGg7ZSsrKXtsZXQgaT1uW2VdOyJudW1iZXIiPT10eXBlb2YgaSYmaTwwJiZ0Kyt9cmV0dXJuIHR9ZnVuY3Rpb24gRVkobil7cmV0dXJuIDE8PE1hdGgubWluKG4sMzEpfWZ1bmN0aW9uIGg3KG4pe2xldCB0LG8sZT0iIixpPTAscj0hMTtmb3IoO251bGwhPT0odD13eGUuZXhlYyhuKSk7KXI/dFswXT09PWBcdWZmZmQvKiR7b31cdWZmZmRgJiYoaT10LmluZGV4LHI9ITEpOihlKz1uLnN1YnN0cmluZyhpLHQuaW5kZXgrdFswXS5sZW5ndGgpLG89dFsxXSxyPSEwKTtyZXR1cm4gZSs9bi5zbGljZShpKSxlfWZ1bmN0aW9uIFRZKG4sdCxlLGkscixvKXtsZXQgcz0wLGE9e3R5cGU6ci50eXBlLGN1cnJlbnRDYXNlTFZpZXdJbmRleDpxMChuLHQsMSxudWxsKSxhbmNob3JJZHg6byxjYXNlczpbXSxjcmVhdGU6W10scmVtb3ZlOltdLHVwZGF0ZTpbXX07KGZ1bmN0aW9uKG4sdCxlKXtuLnB1c2goRVkodC5tYWluQmluZGluZyksMiwtMS10Lm1haW5CaW5kaW5nLGU8PDJ8Mil9KShlLHIsbyksZnVuY3Rpb24obix0LGUpe2xldCBpPW4uZGF0YVt0XTtudWxsPT09aT9uLmRhdGFbdF09ZTppLnZhbHVlPWV9KG4sbyxhKTtsZXQgbD1yLnZhbHVlcztmb3IobGV0IGM9MDtjPGwubGVuZ3RoO2MrKyl7bGV0IHU9bFtjXSxkPVtdO2ZvcihsZXQgcD0wO3A8dS5sZW5ndGg7cCsrKXtsZXQgaD11W3BdO2lmKCJzdHJpbmciIT10eXBlb2YgaCl7bGV0IGY9ZC5wdXNoKGgpLTE7dVtwXT1gXHgzYyEtLVx1ZmZmZCR7Zn1cdWZmZmQtLVx4M2VgfX1zPUZ4ZShuLGEsdCxlLGksci5jYXNlc1tjXSx1LmpvaW4oIiIpLGQpfHN9cyYmZnVuY3Rpb24obix0LGUpe24ucHVzaCh0LDEsZTw8MnwzKX0oZSxzLG8pfWZ1bmN0aW9uIGt4ZShuKXtsZXQgdD1bXSxlPVtdLGk9MSxyPTAsbz1VTChuPW4ucmVwbGFjZSh3WSxmdW5jdGlvbihzLGEsbCl7cmV0dXJuIGk9InNlbGVjdCI9PT1sPzA6MSxyPXBhcnNlSW50KGEuc2xpY2UoMSksMTApLCIifSkpO2ZvcihsZXQgcz0wO3M8by5sZW5ndGg7KXtsZXQgYT1vW3MrK10udHJpbSgpOzE9PT1pJiYoYT1hLnJlcGxhY2UoL1xzKig/Oj0pPyhcdyspXHMqLywiJDEiKSksYS5sZW5ndGgmJnQucHVzaChhKTtsZXQgbD1VTChvW3MrK10pO3QubGVuZ3RoPmUubGVuZ3RoJiZlLnB1c2gobCl9cmV0dXJue3R5cGU6aSxtYWluQmluZGluZzpyLGNhc2VzOnQsdmFsdWVzOmV9fWZ1bmN0aW9uIFVMKG4pe2lmKCFuKXJldHVybltdO2xldCBvLHQ9MCxlPVtdLGk9W10scj0vW3t9XS9nO2ZvcihyLmxhc3RJbmRleD0wO289ci5leGVjKG4pOyl7bGV0IGE9by5pbmRleDtpZigifSI9PW9bMF0pe2lmKGUucG9wKCksMD09ZS5sZW5ndGgpe2xldCBsPW4uc3Vic3RyaW5nKHQsYSk7d1kudGVzdChsKT9pLnB1c2goa3hlKGwpKTppLnB1c2gobCksdD1hKzF9fWVsc2V7aWYoMD09ZS5sZW5ndGgpe2xldCBsPW4uc3Vic3RyaW5nKHQsYSk7aS5wdXNoKGwpLHQ9YSsxfWUucHVzaCgieyIpfX1sZXQgcz1uLnN1YnN0cmluZyh0KTtyZXR1cm4gaS5wdXNoKHMpLGl9ZnVuY3Rpb24gRnhlKG4sdCxlLGkscixvLHMsYSl7bGV0IGw9W10sYz1bXSx1PVtdO3QuY2FzZXMucHVzaChvKSx0LmNyZWF0ZS5wdXNoKGwpLHQucmVtb3ZlLnB1c2goYyksdC51cGRhdGUucHVzaCh1KTtsZXQgcD13OShfOSgpKS5nZXRJbmVydEJvZHlFbGVtZW50KHMpLGg9Q0wocCl8fHA7cmV0dXJuIGg/RFkobix0LGUsaSxsLGMsdSxoLHIsYSwwKTowfWZ1bmN0aW9uIERZKG4sdCxlLGkscixvLHMsYSxsLGMsdSl7bGV0IGQ9MCxwPWEuZmlyc3RDaGlsZDtmb3IoO3A7KXtsZXQgaD1xMChuLGUsMSxudWxsKTtzd2l0Y2gocC5ub2RlVHlwZSl7Y2FzZSBOb2RlLkVMRU1FTlRfTk9ERTpsZXQgZj1wLG09Zi50YWdOYW1lLnRvTG93ZXJDYXNlKCk7aWYoYkwuaGFzT3duUHJvcGVydHkobSkpe1lOKHIsZlksbSxsLGgpLG4uZGF0YVtoXT1tO2xldCBEPWYuYXR0cmlidXRlcztmb3IobGV0IFQ9MDtUPEQubGVuZ3RoO1QrKyl7bGV0IGs9RC5pdGVtKFQpLFo9ay5uYW1lLnRvTG93ZXJDYXNlKCk7ay52YWx1ZS5tYXRjaChpVCk/RDkuaGFzT3duUHJvcGVydHkoWikmJmJ4KHMsay52YWx1ZSxoLGsubmFtZSwwLFQzW1pdP3p4Om51bGwpOlZ4ZShyLGgsayl9ZD1EWShuLHQsZSxpLHIsbyxzLHAsaCxjLHUrMSl8ZCxmNyhvLGgsdSl9YnJlYWs7Y2FzZSBOb2RlLlRFWFRfTk9ERTpsZXQgeD1wLnRleHRDb250ZW50fHwiIixnPXgubWF0Y2goaVQpO1lOKHIsbnVsbCxnPyIiOngsbCxoKSxmNyhvLGgsdSksZyYmKGQ9Yngocyx4LGgsbnVsbCwwLG51bGwpfGQpO2JyZWFrO2Nhc2UgTm9kZS5DT01NRU5UX05PREU6bGV0IGI9TXhlLmV4ZWMocC50ZXh0Q29udGVudHx8IiIpO2lmKGIpe2xldCBUPWNbcGFyc2VJbnQoYlsxXSwxMCldO1lOKHIsbVksIiIsbCxoKSxUWShuLGUsaSxsLFQsaCksTnhlKG8saCx1KX19cD1wLm5leHRTaWJsaW5nfXJldHVybiBkfWZ1bmN0aW9uIGY3KG4sdCxlKXswPT09ZSYmbi5wdXNoKHQpfWZ1bmN0aW9uIE54ZShuLHQsZSl7MD09PWUmJihuLnB1c2gofnQpLG4ucHVzaCh0KSl9ZnVuY3Rpb24gWU4obix0LGUsaSxyKXtudWxsIT09dCYmbi5wdXNoKHQpLG4ucHVzaChlLHIsZnVuY3Rpb24obix0LGUpe3JldHVybiAwfHQ8PDE3fGU8PDF9KDAsaSxyKSl9ZnVuY3Rpb24gVnhlKG4sdCxlKXtuLnB1c2godDw8MXwxLGUubmFtZSxlLnZhbHVlKX12YXIgSHhlPS9cWyhcdWZmZmQuKz9cdWZmZmQ/KVxdLyxVeGU9L1xbKFx1ZmZmZC4rP1x1ZmZmZD8pXF18KFx1ZmZmZFwvP1wqXGQrOlxkK1x1ZmZmZCkvZyx6eGU9Lyh7XHMqKShWQVJfKFBMVVJBTHxTRUxFQ1QpKF9cZCspPykoXHMqLCkvZyxqeGU9L3soW0EtWjAtOV9dKyl9L2csR3hlPS9cdWZmZmRJMThOX0VYUF8oSUNVKF9cZCspPylcdWZmZmQvZyxXeGU9L1wvXCovLHF4ZT0vXGQrXDooXGQrKS87ZnVuY3Rpb24gQVkobix0LGU9LTEpe2xldCBpPUZpKCkscj1ydCgpLG89MjIrbixzPVVwKGkuY29uc3RzLHQpLGE9d3goKTtpLmZpcnN0Q3JlYXRlUGFzcyYmZnVuY3Rpb24obix0LGUsaSxyLG8pe2xldCBzPXd4KCksYT1bXSxsPVtdLGM9W1tdXTtyPWZ1bmN0aW9uKG4sdCl7aWYoZnVuY3Rpb24obil7cmV0dXJuLTE9PT1ufSh0KSlyZXR1cm4gaDcobik7e2xldCBlPW4uaW5kZXhPZihgOiR7dH1cdWZmZmRgKSsyK3QudG9TdHJpbmcoKS5sZW5ndGgsaT1uLnNlYXJjaChuZXcgUmVnRXhwKGBcdWZmZmRcXC9cXCpcXGQrOiR7dH1cdWZmZmRgKSk7cmV0dXJuIGg3KG4uc3Vic3RyaW5nKGUsaSkpfX0ocixvKTtsZXQgdT1mdW5jdGlvbihuKXtyZXR1cm4gbi5yZXBsYWNlKEV4ZSwiICIpfShyKS5zcGxpdChTeGUpO2ZvcihsZXQgZD0wO2Q8dS5sZW5ndGg7ZCsrKXtsZXQgcD11W2RdO2lmKDA9PSgxJmQpKXtsZXQgaD1VTChwKTtmb3IobGV0IGY9MDtmPGgubGVuZ3RoO2YrKyl7bGV0IG09aFtmXTtpZigwPT0oMSZmKSl7bGV0IHg9bTsiIiE9PXgmJkF4ZShuLHMsY1swXSxhLGwsZSx4KX1lbHNle2xldCB4PW07aWYoIm9iamVjdCIhPXR5cGVvZiB4KXRocm93IG5ldyBFcnJvcihgVW5hYmxlIHRvIHBhcnNlIElDVSBleHByZXNzaW9uIGluICIke3J9IiBtZXNzYWdlLmApO1RZKG4sZSxsLHQseCxTWShuLHMsY1swXSxlLGEsIiIsITApLmluZGV4KX19fWVsc2V7bGV0IGg9NDc9PT1wLmNoYXJDb2RlQXQoMCksbT0ocC5jaGFyQ29kZUF0KGg/MTowKSwyMitOdW1iZXIucGFyc2VJbnQocC5zdWJzdHJpbmcoaD8yOjEpKSk7aWYoaCljLnNoaWZ0KCksTXUod3goKSwhMSk7ZWxzZXtsZXQgeD1weGUobixjWzBdLG0pO2MudW5zaGlmdChbXSksTXUoeCwhMCl9fX1uLmRhdGFbaV09e2NyZWF0ZTphLHVwZGF0ZTpsfX0oaSxudWxsPT09YT8wOmEuaW5kZXgscixvLHMsZSk7bGV0IGw9aS5kYXRhW29dLHU9ajkoaSxhPT09cls2XT9udWxsOmEscik7KGZ1bmN0aW9uKG4sdCxlLGkpe2xldCByPW5bMTFdO2ZvcihsZXQgbz0wO288dC5sZW5ndGg7bysrKXtsZXQgcz10W28rK10sYT10W29dLGw9KHMmRWMuQ09NTUVOVCk9PT1FYy5DT01NRU5ULGM9KHMmRWMuQVBQRU5EX0VBR0VSTFkpPT09RWMuQVBQRU5EX0VBR0VSTFksdT1zPj4+RWMuU0hJRlQsZD1uW3VdO251bGw9PT1kJiYoZD1uW3VdPWw/ci5jcmVhdGVDb21tZW50KGEpOkYzKHIsYSkpLGMmJm51bGwhPT1lJiZZZihyLGUsZCxpLCExKX19KShyLGwuY3JlYXRlLHUsYSYmOCZhLnR5cGU/clthLmluZGV4XTpudWxsKSxXNyghMCl9ZnVuY3Rpb24gSVkoKXtXNyghMSl9ZnVuY3Rpb24gQVQobix0LGUpe0FZKG4sdCxlKSxJWSgpfWZ1bmN0aW9uIEt4KG4pe3JldHVybiBmdW5jdGlvbihuKXtuJiYoUHh8PTE8PE1hdGgubWluKHZ4LDMxKSksdngrK30oRHMocnQoKSxVMCgpLG4pKSxLeH1mdW5jdGlvbiBJVChuKXshZnVuY3Rpb24obix0LGUpe2lmKHZ4PjApe2xldCBpPW4uZGF0YVtlXTtDWShuLHQsQXJyYXkuaXNBcnJheShpKT9pOmkudXBkYXRlLEhkKCktdngtMSxQeCl9UHg9MCx2eD0wfShGaSgpLHJ0KCksbisyMil9ZnVuY3Rpb24gUFQobix0PXt9KXtyZXR1cm4gZnVuY3Rpb24obix0PXt9KXtsZXQgZT1uO2lmKEh4ZS50ZXN0KG4pKXtsZXQgaT17fSxyPVswXTtlPWUucmVwbGFjZShVeGUsKG8scyxhKT0+e2xldCBsPXN8fGEsYz1pW2xdfHxbXTtpZihjLmxlbmd0aHx8KGwuc3BsaXQoInwiKS5mb3JFYWNoKG09PntsZXQgeD1tLm1hdGNoKHF4ZSksZz14P3BhcnNlSW50KHhbMV0sMTApOjAsYj1XeGUudGVzdChtKTtjLnB1c2goW2csYixtXSl9KSxpW2xdPWMpLCFjLmxlbmd0aCl0aHJvdyBuZXcgRXJyb3IoYGkxOG4gcG9zdHByb2Nlc3M6IHVubWF0Y2hlZCBwbGFjZWhvbGRlciAtICR7bH1gKTtsZXQgdT1yW3IubGVuZ3RoLTFdLGQ9MDtmb3IobGV0IG09MDttPGMubGVuZ3RoO20rKylpZihjW21dWzBdPT09dSl7ZD1tO2JyZWFrfWxldFtwLGgsZl09Y1tkXTtyZXR1cm4gaD9yLnBvcCgpOnUhPT1wJiZyLnB1c2gocCksYy5zcGxpY2UoZCwxKSxmfSl9cmV0dXJuIE9iamVjdC5rZXlzKHQpLmxlbmd0aCYmKGU9ZS5yZXBsYWNlKHp4ZSwoaSxyLG8scyxhLGwpPT50Lmhhc093blByb3BlcnR5KG8pP2Ake3J9JHt0W29dfSR7bH1gOmkpLGU9ZS5yZXBsYWNlKGp4ZSwoaSxyKT0+dC5oYXNPd25Qcm9wZXJ0eShyKT90W3JdOmkpLGU9ZS5yZXBsYWNlKEd4ZSwoaSxyKT0+e2lmKHQuaGFzT3duUHJvcGVydHkocikpe2xldCBvPXRbcl07aWYoIW8ubGVuZ3RoKXRocm93IG5ldyBFcnJvcihgaTE4biBwb3N0cHJvY2VzczogdW5tYXRjaGVkIElDVSAtICR7aX0gd2l0aCBrZXk6ICR7cn1gKTtyZXR1cm4gby5zaGlmdCgpfXJldHVybiBpfSkpLGV9KG4sdCl9ZnVuY3Rpb24gekwobix0LGUsaSxyKXtpZihuPUtpKG4pLEFycmF5LmlzQXJyYXkobikpZm9yKGxldCBvPTA7bzxuLmxlbmd0aDtvKyspekwobltvXSx0LGUsaSxyKTtlbHNle2xldCBvPUZpKCkscz1ydCgpLGE9azAobik/bjpLaShuLnByb3ZpZGUpLGw9RjkobiksYz16bygpLHU9MTA0ODU3NSZjLnByb3ZpZGVySW5kZXhlcyxkPWMuZGlyZWN0aXZlU3RhcnQscD1jLnByb3ZpZGVySW5kZXhlcz4+MjA7aWYoazAobil8fCFuLm11bHRpKXtsZXQgaD1uZXcgcWYobCxyLE0pLGY9UU4oYSx0LHI/dTp1K3AsZCk7LTE9PT1mPyhZMShTeChjLHMpLG8sYSksWE4obyxuLHQubGVuZ3RoKSx0LnB1c2goYSksYy5kaXJlY3RpdmVTdGFydCsrLGMuZGlyZWN0aXZlRW5kKyssciYmKGMucHJvdmlkZXJJbmRleGVzKz0xMDQ4NTc2KSxlLnB1c2goaCkscy5wdXNoKGgpKTooZVtmXT1oLHNbZl09aCl9ZWxzZXtsZXQgaD1RTihhLHQsdStwLGQpLGY9UU4oYSx0LHUsdStwKSxtPWg+PTAmJmVbaF0seD1mPj0wJiZlW2ZdO2lmKHImJiF4fHwhciYmIW0pe1kxKFN4KGMscyksbyxhKTtsZXQgZz1mdW5jdGlvbihuLHQsZSxpLHIpe2xldCBvPW5ldyBxZihuLGUsTSk7cmV0dXJuIG8ubXVsdGk9W10sby5pbmRleD10LG8uY29tcG9uZW50UHJvdmlkZXJzPTAsUFkobyxyLGkmJiFlKSxvfShyP1p4ZTpLeGUsZS5sZW5ndGgscixpLGwpOyFyJiZ4JiYoZVtmXS5wcm92aWRlckZhY3Rvcnk9ZyksWE4obyxuLHQubGVuZ3RoLDApLHQucHVzaChhKSxjLmRpcmVjdGl2ZVN0YXJ0KyssYy5kaXJlY3RpdmVFbmQrKyxyJiYoYy5wcm92aWRlckluZGV4ZXMrPTEwNDg1NzYpLGUucHVzaChnKSxzLnB1c2goZyl9ZWxzZSBYTihvLG4saD4tMT9oOmYsUFkoZVtyP2Y6aF0sbCwhciYmaSkpOyFyJiZpJiZ4JiZlW2ZdLmNvbXBvbmVudFByb3ZpZGVycysrfX19ZnVuY3Rpb24gWE4obix0LGUsaSl7bGV0IHI9azAodCksbz1mdW5jdGlvbihuKXtyZXR1cm4hIW4udXNlQ2xhc3N9KHQpO2lmKHJ8fG8pe2xldCBsPShvP0tpKHQudXNlQ2xhc3MpOnQpLnByb3RvdHlwZS5uZ09uRGVzdHJveTtpZihsKXtsZXQgYz1uLmRlc3Ryb3lIb29rc3x8KG4uZGVzdHJveUhvb2tzPVtdKTtpZighciYmdC5tdWx0aSl7bGV0IHU9Yy5pbmRleE9mKGUpOy0xPT09dT9jLnB1c2goZSxbaSxsXSk6Y1t1KzFdLnB1c2goaSxsKX1lbHNlIGMucHVzaChlLGwpfX19ZnVuY3Rpb24gUFkobix0LGUpe3JldHVybiBlJiZuLmNvbXBvbmVudFByb3ZpZGVycysrLG4ubXVsdGkucHVzaCh0KS0xfWZ1bmN0aW9uIFFOKG4sdCxlLGkpe2ZvcihsZXQgcj1lO3I8aTtyKyspaWYodFtyXT09PW4pcmV0dXJuIHI7cmV0dXJuLTF9ZnVuY3Rpb24gS3hlKG4sdCxlLGkpe3JldHVybiBqTCh0aGlzLm11bHRpLFtdKX1mdW5jdGlvbiBaeGUobix0LGUsaSl7bGV0IG8scj10aGlzLm11bHRpO2lmKHRoaXMucHJvdmlkZXJGYWN0b3J5KXtsZXQgcz10aGlzLnByb3ZpZGVyRmFjdG9yeS5jb21wb25lbnRQcm92aWRlcnMsYT1CeChlLGVbMV0sdGhpcy5wcm92aWRlckZhY3RvcnkuaW5kZXgsaSk7bz1hLnNsaWNlKDAscyksakwocixvKTtmb3IobGV0IGw9cztsPGEubGVuZ3RoO2wrKylvLnB1c2goYVtsXSl9ZWxzZSBvPVtdLGpMKHIsbyk7cmV0dXJuIG99ZnVuY3Rpb24gakwobix0KXtmb3IobGV0IGU9MDtlPG4ubGVuZ3RoO2UrKyl0LnB1c2goKDAsbltlXSkoKSk7cmV0dXJuIHR9ZnVuY3Rpb24gJHQobix0PVtdKXtyZXR1cm4gZT0+e2UucHJvdmlkZXJzUmVzb2x2ZXI9KGkscik9PmZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1GaSgpO2lmKGkuZmlyc3RDcmVhdGVQYXNzKXtsZXQgcj1BYyhuKTt6TChlLGkuZGF0YSxpLmJsdWVwcmludCxyLCEwKSx6TCh0LGkuZGF0YSxpLmJsdWVwcmludCxyLCExKX19KGkscj9yKG4pOm4sdCl9fXZhciBHcD1jbGFzc3t9LEdMPWNsYXNze30sV0w9Y2xhc3MgZXh0ZW5kcyBHcHtjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKCksdGhpcy5fcGFyZW50PWUsdGhpcy5fYm9vdHN0cmFwQ29tcG9uZW50cz1bXSx0aGlzLmRlc3Ryb3lDYnM9W10sdGhpcy5jb21wb25lbnRGYWN0b3J5UmVzb2x2ZXI9bmV3IEF4KHRoaXMpO2xldCBpPUkwKHQpO3RoaXMuX2Jvb3RzdHJhcENvbXBvbmVudHM9UjEoaS5ib290c3RyYXApLHRoaXMuX3IzSW5qZWN0b3I9b3EodCxlLFt7cHJvdmlkZTpHcCx1c2VWYWx1ZTp0aGlzfSx7cHJvdmlkZTpncyx1c2VWYWx1ZTp0aGlzLmNvbXBvbmVudEZhY3RvcnlSZXNvbHZlcn1dLFRvKHQpLG5ldyBTZXQoWyJlbnZpcm9ubWVudCJdKSksdGhpcy5fcjNJbmplY3Rvci5yZXNvbHZlSW5qZWN0b3JJbml0aWFsaXplcnMoKSx0aGlzLmluc3RhbmNlPXRoaXMuX3IzSW5qZWN0b3IuZ2V0KHQpfWdldCBpbmplY3Rvcigpe3JldHVybiB0aGlzLl9yM0luamVjdG9yfWRlc3Ryb3koKXtsZXQgdD10aGlzLl9yM0luamVjdG9yOyF0LmRlc3Ryb3llZCYmdC5kZXN0cm95KCksdGhpcy5kZXN0cm95Q2JzLmZvckVhY2goZT0+ZSgpKSx0aGlzLmRlc3Ryb3lDYnM9bnVsbH1vbkRlc3Ryb3kodCl7dGhpcy5kZXN0cm95Q2JzLnB1c2godCl9fSxxTD1jbGFzcyBleHRlbmRzIEdMe2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5tb2R1bGVUeXBlPXR9Y3JlYXRlKHQpe3JldHVybiBuZXcgV0wodGhpcy5tb2R1bGVUeXBlLHQpfX0sWUw9Y2xhc3MgZXh0ZW5kcyBHcHtjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIoKSx0aGlzLmNvbXBvbmVudEZhY3RvcnlSZXNvbHZlcj1uZXcgQXgodGhpcyksdGhpcy5pbnN0YW5jZT1udWxsO2xldCByPW5ldyBaMShbLi4udCx7cHJvdmlkZTpHcCx1c2VWYWx1ZTp0aGlzfSx7cHJvdmlkZTpncyx1c2VWYWx1ZTp0aGlzLmNvbXBvbmVudEZhY3RvcnlSZXNvbHZlcn1dLGV8fEkzKCksaSxuZXcgU2V0KFsiZW52aXJvbm1lbnQiXSkpO3RoaXMuaW5qZWN0b3I9cixyLnJlc29sdmVJbmplY3RvckluaXRpYWxpemVycygpfWRlc3Ryb3koKXt0aGlzLmluamVjdG9yLmRlc3Ryb3koKX1vbkRlc3Ryb3kodCl7dGhpcy5pbmplY3Rvci5vbkRlc3Ryb3kodCl9fSxlQ2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLl9pbmplY3Rvcj1lLHRoaXMuY2FjaGVkSW5qZWN0b3JzPW5ldyBNYXB9Z2V0T3JDcmVhdGVTdGFuZGFsb25lSW5qZWN0b3IoZSl7aWYoIWUuc3RhbmRhbG9uZSlyZXR1cm4gbnVsbDtpZighdGhpcy5jYWNoZWRJbmplY3RvcnMuaGFzKGUuaWQpKXtsZXQgaT1SOSgwLGUudHlwZSkscj1pLmxlbmd0aD4wP2Z1bmN0aW9uKG4sdCxlPW51bGwpe3JldHVybiBuZXcgWUwobix0LGUpLmluamVjdG9yfShbaV0sdGhpcy5faW5qZWN0b3IsYFN0YW5kYWxvbmVbJHtlLnR5cGUubmFtZX1dYCk6bnVsbDt0aGlzLmNhY2hlZEluamVjdG9ycy5zZXQoZS5pZCxyKX1yZXR1cm4gdGhpcy5jYWNoZWRJbmplY3RvcnMuZ2V0KGUuaWQpfW5nT25EZXN0cm95KCl7dHJ5e2ZvcihsZXQgZSBvZiB0aGlzLmNhY2hlZEluamVjdG9ycy52YWx1ZXMoKSludWxsIT09ZSYmZS5kZXN0cm95KCl9ZmluYWxseXt0aGlzLmNhY2hlZEluamVjdG9ycy5jbGVhcigpfX19cmV0dXJuIG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixwcm92aWRlZEluOiJlbnZpcm9ubWVudCIsZmFjdG9yeTooKT0+bmV3IG4oaihqcCkpfSksbn0pKCk7ZnVuY3Rpb24gUXAobix0LGUpe2xldCBpPUtzKCkrbixyPXJ0KCk7cmV0dXJuIHJbaV09PT1Rbj9EdShyLGksZT90LmNhbGwoZSk6dCgpKTpZeChyLGkpfWZ1bmN0aW9uIE9uKG4sdCxlLGkpe3JldHVybiBSWShydCgpLEtzKCksbix0LGUsaSl9ZnVuY3Rpb24gUXIobix0LGUsaSxyKXtyZXR1cm4gT1kocnQoKSxLcygpLG4sdCxlLGkscil9ZnVuY3Rpb24gWngobix0LGUsaSxyLG8pe3JldHVybiBrWShydCgpLEtzKCksbix0LGUsaSxyLG8pfWZ1bmN0aW9uIEszKG4sdCxlLGkscixvLHMpe3JldHVybiBGWShydCgpLEtzKCksbix0LGUsaSxyLG8scyl9ZnVuY3Rpb24gWjMobix0LGUsaSxyLG8scyxhKXtsZXQgbD1LcygpK24sYz1ydCgpLHU9VmwoYyxsLGUsaSxyLG8pO3JldHVybiBEcyhjLGwrNCxzKXx8dT9EdShjLGwrNSxhP3QuY2FsbChhLGUsaSxyLG8scyk6dChlLGkscixvLHMpKTpZeChjLGwrNSl9ZnVuY3Rpb24gSngobix0KXtsZXQgZT1uW3RdO3JldHVybiBlPT09UW4/dm9pZCAwOmV9ZnVuY3Rpb24gUlkobix0LGUsaSxyLG8pe2xldCBzPXQrZTtyZXR1cm4gRHMobixzLHIpP0R1KG4scysxLG8/aS5jYWxsKG8scik6aShyKSk6SngobixzKzEpfWZ1bmN0aW9uIE9ZKG4sdCxlLGkscixvLHMpe2xldCBhPXQrZTtyZXR1cm4gS2YobixhLHIsbyk/RHUobixhKzIscz9pLmNhbGwocyxyLG8pOmkocixvKSk6SngobixhKzIpfWZ1bmN0aW9uIGtZKG4sdCxlLGkscixvLHMsYSl7bGV0IGw9dCtlO3JldHVybiBTVChuLGwscixvLHMpP0R1KG4sbCszLGE/aS5jYWxsKGEscixvLHMpOmkocixvLHMpKTpKeChuLGwrMyl9ZnVuY3Rpb24gRlkobix0LGUsaSxyLG8scyxhLGwpe2xldCBjPXQrZTtyZXR1cm4gVmwobixjLHIsbyxzLGEpP0R1KG4sYys0LGw/aS5jYWxsKGwscixvLHMsYSk6aShyLG8scyxhKSk6SngobixjKzQpfWZ1bmN0aW9uIE5ZKG4sdCxlLGkscixvKXtsZXQgcz10K2UsYT0hMTtmb3IobGV0IGw9MDtsPHIubGVuZ3RoO2wrKylEcyhuLHMrKyxyW2xdKSYmKGE9ITApO3JldHVybiBhP0R1KG4scyxpLmFwcGx5KG8scikpOkp4KG4scyl9ZnVuY3Rpb24gQihuLHQpe2xldCBpLGU9RmkoKSxyPW4rMjI7ZS5maXJzdENyZWF0ZVBhc3M/KGk9ZnVuY3Rpb24obix0KXtpZih0KWZvcihsZXQgZT10Lmxlbmd0aC0xO2U+PTA7ZS0tKXtsZXQgaT10W2VdO2lmKG49PT1pLm5hbWUpcmV0dXJuIGl9fSh0LGUucGlwZVJlZ2lzdHJ5KSxlLmRhdGFbcl09aSxpLm9uRGVzdHJveSYmKGUuZGVzdHJveUhvb2tzfHwoZS5kZXN0cm95SG9va3M9W10pKS5wdXNoKHIsaS5vbkRlc3Ryb3kpKTppPWUuZGF0YVtyXTtsZXQgbz1pLmZhY3Rvcnl8fChpLmZhY3Rvcnk9V2YoaS50eXBlKSkscz1rbChNKTt0cnl7bGV0IGE9cTEoITEpLGw9bygpO3JldHVybiBxMShhKSxmdW5jdGlvbihuLHQsZSxpKXtlPj1uLmRhdGEubGVuZ3RoJiYobi5kYXRhW2VdPW51bGwsbi5ibHVlcHJpbnRbZV09bnVsbCksdFtlXT1pfShlLHJ0KCkscixsKSxsfWZpbmFsbHl7a2wocyl9fWZ1bmN0aW9uIFUobix0LGUpe2xldCBpPW4rMjIscj1ydCgpLG89SDAocixpKTtyZXR1cm4gJHgocixpKT9SWShyLEtzKCksdCxvLnRyYW5zZm9ybSxlLG8pOm8udHJhbnNmb3JtKGUpfWZ1bmN0aW9uIEpmKG4sdCxlLGkpe2xldCByPW4rMjIsbz1ydCgpLHM9SDAobyxyKTtyZXR1cm4gJHgobyxyKT9PWShvLEtzKCksdCxzLnRyYW5zZm9ybSxlLGkscyk6cy50cmFuc2Zvcm0oZSxpKX1mdW5jdGlvbiBKMyhuLHQsZSxpLHIpe2xldCBvPW4rMjIscz1ydCgpLGE9SDAocyxvKTtyZXR1cm4gJHgocyxvKT9rWShzLEtzKCksdCxhLnRyYW5zZm9ybSxlLGkscixhKTphLnRyYW5zZm9ybShlLGkscil9ZnVuY3Rpb24gJHgobix0KXtyZXR1cm4gblsxXS5kYXRhW3RdLnB1cmV9ZnVuY3Rpb24gS04obil7cmV0dXJuIHQ9PntzZXRUaW1lb3V0KG4sdm9pZCAwLHQpfX12YXIgRz1jbGFzcyBleHRlbmRzIGtle2NvbnN0cnVjdG9yKHQ9ITEpe3N1cGVyKCksdGhpcy5fX2lzQXN5bmM9dH1lbWl0KHQpe3N1cGVyLm5leHQodCl9c3Vic2NyaWJlKHQsZSxpKXtsZXQgcj10LG89ZXx8KCgpPT5udWxsKSxzPWk7aWYodCYmIm9iamVjdCI9PXR5cGVvZiB0KXtsZXQgbD10O3I9bC5uZXh0Py5iaW5kKGwpLG89bC5lcnJvcj8uYmluZChsKSxzPWwuY29tcGxldGU/LmJpbmQobCl9dGhpcy5fX2lzQXN5bmMmJihvPUtOKG8pLHImJihyPUtOKHIpKSxzJiYocz1LTihzKSkpO2xldCBhPXN1cGVyLnN1YnNjcmliZSh7bmV4dDpyLGVycm9yOm8sY29tcGxldGU6c30pO3JldHVybiB0IGluc3RhbmNlb2YgU24mJnQuYWRkKGEpLGF9fTtmdW5jdGlvbiBjQ2UoKXtyZXR1cm4gdGhpcy5fcmVzdWx0c1tNVCgpXSgpfXZhciBIbD1jbGFzc3tjb25zdHJ1Y3Rvcih0PSExKXt0aGlzLl9lbWl0RGlzdGluY3RDaGFuZ2VzT25seT10LHRoaXMuZGlydHk9ITAsdGhpcy5fcmVzdWx0cz1bXSx0aGlzLl9jaGFuZ2VzRGV0ZWN0ZWQ9ITEsdGhpcy5fY2hhbmdlcz1udWxsLHRoaXMubGVuZ3RoPTAsdGhpcy5maXJzdD12b2lkIDAsdGhpcy5sYXN0PXZvaWQgMDtsZXQgZT1NVCgpLGk9SGwucHJvdG90eXBlO2lbZV18fChpW2VdPWNDZSl9Z2V0IGNoYW5nZXMoKXtyZXR1cm4gdGhpcy5fY2hhbmdlc3x8KHRoaXMuX2NoYW5nZXM9bmV3IEcpfWdldCh0KXtyZXR1cm4gdGhpcy5fcmVzdWx0c1t0XX1tYXAodCl7cmV0dXJuIHRoaXMuX3Jlc3VsdHMubWFwKHQpfWZpbHRlcih0KXtyZXR1cm4gdGhpcy5fcmVzdWx0cy5maWx0ZXIodCl9ZmluZCh0KXtyZXR1cm4gdGhpcy5fcmVzdWx0cy5maW5kKHQpfXJlZHVjZSh0LGUpe3JldHVybiB0aGlzLl9yZXN1bHRzLnJlZHVjZSh0LGUpfWZvckVhY2godCl7dGhpcy5fcmVzdWx0cy5mb3JFYWNoKHQpfXNvbWUodCl7cmV0dXJuIHRoaXMuX3Jlc3VsdHMuc29tZSh0KX10b0FycmF5KCl7cmV0dXJuIHRoaXMuX3Jlc3VsdHMuc2xpY2UoKX10b1N0cmluZygpe3JldHVybiB0aGlzLl9yZXN1bHRzLnRvU3RyaW5nKCl9cmVzZXQodCxlKXtsZXQgaT10aGlzO2kuZGlydHk9ITE7bGV0IHI9RmQodCk7KHRoaXMuX2NoYW5nZXNEZXRlY3RlZD0hZnVuY3Rpb24obix0LGUpe2lmKG4ubGVuZ3RoIT09dC5sZW5ndGgpcmV0dXJuITE7Zm9yKGxldCBpPTA7aTxuLmxlbmd0aDtpKyspe2xldCByPW5baV0sbz10W2ldO2lmKGUmJihyPWUociksbz1lKG8pKSxvIT09cilyZXR1cm4hMX1yZXR1cm4hMH0oaS5fcmVzdWx0cyxyLGUpKSYmKGkuX3Jlc3VsdHM9cixpLmxlbmd0aD1yLmxlbmd0aCxpLmxhc3Q9clt0aGlzLmxlbmd0aC0xXSxpLmZpcnN0PXJbMF0pfW5vdGlmeU9uQ2hhbmdlcygpe3RoaXMuX2NoYW5nZXMmJih0aGlzLl9jaGFuZ2VzRGV0ZWN0ZWR8fCF0aGlzLl9lbWl0RGlzdGluY3RDaGFuZ2VzT25seSkmJnRoaXMuX2NoYW5nZXMuZW1pdCh0aGlzKX1zZXREaXJ0eSgpe3RoaXMuZGlydHk9ITB9ZGVzdHJveSgpe3RoaXMuY2hhbmdlcy5jb21wbGV0ZSgpLHRoaXMuY2hhbmdlcy51bnN1YnNjcmliZSgpfX0sVmk9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uX19OR19FTEVNRU5UX0lEX189cENlLG59KSgpLHVDZT1WaSxkQ2U9Y2xhc3MgZXh0ZW5kcyB1Q2V7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKCksdGhpcy5fZGVjbGFyYXRpb25MVmlldz10LHRoaXMuX2RlY2xhcmF0aW9uVENvbnRhaW5lcj1lLHRoaXMuZWxlbWVudFJlZj1pfWNyZWF0ZUVtYmVkZGVkVmlldyh0LGUpe2xldCBpPXRoaXMuX2RlY2xhcmF0aW9uVENvbnRhaW5lci50Vmlld3Mscj1iVCh0aGlzLl9kZWNsYXJhdGlvbkxWaWV3LGksdCwxNixudWxsLGkuZGVjbFROb2RlLG51bGwsbnVsbCxudWxsLG51bGwsZXx8bnVsbCk7clsxN109dGhpcy5fZGVjbGFyYXRpb25MVmlld1t0aGlzLl9kZWNsYXJhdGlvblRDb250YWluZXIuaW5kZXhdO2xldCBzPXRoaXMuX2RlY2xhcmF0aW9uTFZpZXdbMTldO3JldHVybiBudWxsIT09cyYmKHJbMTldPXMuY3JlYXRlRW1iZWRkZWRWaWV3KGkpKSxIMyhpLHIsdCksbmV3IFFmKHIpfX07ZnVuY3Rpb24gcENlKCl7cmV0dXJuIFJUKHpvKCkscnQoKSl9ZnVuY3Rpb24gUlQobix0KXtyZXR1cm4gNCZuLnR5cGU/bmV3IGRDZSh0LG4sRzAobix0KSk6bnVsbH12YXIgT2k9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uX19OR19FTEVNRU5UX0lEX189aENlLG59KSgpO2Z1bmN0aW9uIGhDZSgpe3JldHVybiBCWSh6bygpLHJ0KCkpfXZhciBmQ2U9T2ksTFk9Y2xhc3MgZXh0ZW5kcyBmQ2V7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKCksdGhpcy5fbENvbnRhaW5lcj10LHRoaXMuX2hvc3RUTm9kZT1lLHRoaXMuX2hvc3RMVmlldz1pfWdldCBlbGVtZW50KCl7cmV0dXJuIEcwKHRoaXMuX2hvc3RUTm9kZSx0aGlzLl9ob3N0TFZpZXcpfWdldCBpbmplY3Rvcigpe3JldHVybiBuZXcgamYodGhpcy5faG9zdFROb2RlLHRoaXMuX2hvc3RMVmlldyl9Z2V0IHBhcmVudEluamVjdG9yKCl7bGV0IHQ9QzModGhpcy5faG9zdFROb2RlLHRoaXMuX2hvc3RMVmlldyk7aWYoZTkodCkpe2xldCBlPVcxKHQsdGhpcy5faG9zdExWaWV3KSxpPUcxKHQpO3JldHVybiBuZXcgamYoZVsxXS5kYXRhW2krOF0sZSl9cmV0dXJuIG5ldyBqZihudWxsLHRoaXMuX2hvc3RMVmlldyl9Y2xlYXIoKXtmb3IoO3RoaXMubGVuZ3RoPjA7KXRoaXMucmVtb3ZlKHRoaXMubGVuZ3RoLTEpfWdldCh0KXtsZXQgZT1nNyh0aGlzLl9sQ29udGFpbmVyKTtyZXR1cm4gbnVsbCE9PWUmJmVbdF18fG51bGx9Z2V0IGxlbmd0aCgpe3JldHVybiB0aGlzLl9sQ29udGFpbmVyLmxlbmd0aC0xMH1jcmVhdGVFbWJlZGRlZFZpZXcodCxlLGkpe2xldCByLG87Im51bWJlciI9PXR5cGVvZiBpP3I9aTpudWxsIT1pJiYocj1pLmluZGV4LG89aS5pbmplY3Rvcik7bGV0IHM9dC5jcmVhdGVFbWJlZGRlZFZpZXcoZXx8e30sbyk7cmV0dXJuIHRoaXMuaW5zZXJ0KHMsciksc31jcmVhdGVDb21wb25lbnQodCxlLGkscixvKXtsZXQgYSxzPXQmJiF1eCh0KTtpZihzKWE9ZTtlbHNle2xldCBkPWV8fHt9O2E9ZC5pbmRleCxpPWQuaW5qZWN0b3Iscj1kLnByb2plY3RhYmxlTm9kZXMsbz1kLmVudmlyb25tZW50SW5qZWN0b3J8fGQubmdNb2R1bGVSZWZ9bGV0IGw9cz90Om5ldyBlVChObCh0KSksYz1pfHx0aGlzLnBhcmVudEluamVjdG9yO2lmKCFvJiZudWxsPT1sLm5nTW9kdWxlKXtsZXQgcD0ocz9jOnRoaXMucGFyZW50SW5qZWN0b3IpLmdldChqcCxudWxsKTtwJiYobz1wKX1sZXQgdT1sLmNyZWF0ZShjLHIsdm9pZCAwLG8pO3JldHVybiB0aGlzLmluc2VydCh1Lmhvc3RWaWV3LGEpLHV9aW5zZXJ0KHQsZSl7bGV0IGk9dC5fbFZpZXcscj1pWzFdO2lmKFZkKGlbM10pKXtsZXQgdT10aGlzLmluZGV4T2YodCk7aWYoLTEhPT11KXRoaXMuZGV0YWNoKHUpO2Vsc2V7bGV0IGQ9aVszXSxwPW5ldyBMWShkLGRbNl0sZFszXSk7cC5kZXRhY2gocC5pbmRleE9mKHQpKX19bGV0IG89dGhpcy5fYWRqdXN0SW5kZXgoZSkscz10aGlzLl9sQ29udGFpbmVyOyFmdW5jdGlvbihuLHQsZSxpKXtsZXQgcj0xMCtpLG89ZS5sZW5ndGg7aT4wJiYoZVtyLTFdWzRdPXQpLGk8by0xMD8odFs0XT1lW3JdLHU5KGUsMTAraSx0KSk6KGUucHVzaCh0KSx0WzRdPW51bGwpLHRbM109ZTtsZXQgcz10WzE3XTtudWxsIT09cyYmZSE9PXMmJmZ1bmN0aW9uKG4sdCl7bGV0IGU9bls5XTt0WzE2XSE9PXRbM11bM11bMTZdJiYoblsyXT0hMCksbnVsbD09PWU/bls5XT1bdF06ZS5wdXNoKHQpfShzLHQpO2xldCBhPXRbMTldO251bGwhPT1hJiZhLmluc2VydFZpZXcobiksdFsyXXw9NjR9KHIsaSxzLG8pO2xldCBhPVBMKG8scyksbD1pWzExXSxjPXZUKGwsc1s3XSk7cmV0dXJuIG51bGwhPT1jJiZmdW5jdGlvbihuLHQsZSxpLHIsbyl7aVswXT1yLGlbNl09dCxxeChuLGksZSwxLHIsbyl9KHIsc1s2XSxsLGksYyxhKSx0LmF0dGFjaFRvVmlld0NvbnRhaW5lclJlZigpLHU5KFpOKHMpLG8sdCksdH1tb3ZlKHQsZSl7cmV0dXJuIHRoaXMuaW5zZXJ0KHQsZSl9aW5kZXhPZih0KXtsZXQgZT1nNyh0aGlzLl9sQ29udGFpbmVyKTtyZXR1cm4gbnVsbCE9PWU/ZS5pbmRleE9mKHQpOi0xfXJlbW92ZSh0KXtsZXQgZT10aGlzLl9hZGp1c3RJbmRleCh0LC0xKSxpPUFMKHRoaXMuX2xDb250YWluZXIsZSk7aSYmKFgxKFpOKHRoaXMuX2xDb250YWluZXIpLGUpLFU5KGlbMV0saSkpfWRldGFjaCh0KXtsZXQgZT10aGlzLl9hZGp1c3RJbmRleCh0LC0xKSxpPUFMKHRoaXMuX2xDb250YWluZXIsZSk7cmV0dXJuIGkmJm51bGwhPVgxKFpOKHRoaXMuX2xDb250YWluZXIpLGUpP25ldyBRZihpKTpudWxsfV9hZGp1c3RJbmRleCh0LGU9MCl7cmV0dXJuIHQ/P3RoaXMubGVuZ3RoK2V9fTtmdW5jdGlvbiBnNyhuKXtyZXR1cm4gbls4XX1mdW5jdGlvbiBaTihuKXtyZXR1cm4gbls4XXx8KG5bOF09W10pfWZ1bmN0aW9uIEJZKG4sdCl7bGV0IGUsaT10W24uaW5kZXhdO2lmKFZkKGkpKWU9aTtlbHNle2xldCByO2lmKDgmbi50eXBlKXI9JGEoaSk7ZWxzZXtsZXQgbz10WzExXTtyPW8uY3JlYXRlQ29tbWVudCgiIik7bGV0IHM9VWwobix0KTtZZihvLHZUKG8scykscixmdW5jdGlvbihuLHQpe3JldHVybiBuLm5leHRTaWJsaW5nKHQpfShvLHMpLCExKX10W24uaW5kZXhdPWU9dnEoaSx0LHIsbiksQ1QodCxlKX1yZXR1cm4gbmV3IExZKGUsbix0KX12YXIgUng9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5xdWVyeUxpc3Q9dCx0aGlzLm1hdGNoZXM9bnVsbH1jbG9uZSgpe3JldHVybiBuZXcgUngodGhpcy5xdWVyeUxpc3QpfXNldERpcnR5KCl7dGhpcy5xdWVyeUxpc3Quc2V0RGlydHkoKX19LE94PWNsYXNze2NvbnN0cnVjdG9yKHQ9W10pe3RoaXMucXVlcmllcz10fWNyZWF0ZUVtYmVkZGVkVmlldyh0KXtsZXQgZT10LnF1ZXJpZXM7aWYobnVsbCE9PWUpe2xldCBpPW51bGwhPT10LmNvbnRlbnRRdWVyaWVzP3QuY29udGVudFF1ZXJpZXNbMF06ZS5sZW5ndGgscj1bXTtmb3IobGV0IG89MDtvPGk7bysrKXtsZXQgcz1lLmdldEJ5SW5kZXgobyk7ci5wdXNoKHRoaXMucXVlcmllc1tzLmluZGV4SW5EZWNsYXJhdGlvblZpZXddLmNsb25lKCkpfXJldHVybiBuZXcgT3gocil9cmV0dXJuIG51bGx9aW5zZXJ0Vmlldyh0KXt0aGlzLmRpcnR5UXVlcmllc1dpdGhNYXRjaGVzKHQpfWRldGFjaFZpZXcodCl7dGhpcy5kaXJ0eVF1ZXJpZXNXaXRoTWF0Y2hlcyh0KX1kaXJ0eVF1ZXJpZXNXaXRoTWF0Y2hlcyh0KXtmb3IobGV0IGU9MDtlPHRoaXMucXVlcmllcy5sZW5ndGg7ZSsrKW51bGwhPT16WSh0LGUpLm1hdGNoZXMmJnRoaXMucXVlcmllc1tlXS5zZXREaXJ0eSgpfX0sclQ9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGk9bnVsbCl7dGhpcy5wcmVkaWNhdGU9dCx0aGlzLmZsYWdzPWUsdGhpcy5yZWFkPWl9fSxreD1jbGFzc3tjb25zdHJ1Y3Rvcih0PVtdKXt0aGlzLnF1ZXJpZXM9dH1lbGVtZW50U3RhcnQodCxlKXtmb3IobGV0IGk9MDtpPHRoaXMucXVlcmllcy5sZW5ndGg7aSsrKXRoaXMucXVlcmllc1tpXS5lbGVtZW50U3RhcnQodCxlKX1lbGVtZW50RW5kKHQpe2ZvcihsZXQgZT0wO2U8dGhpcy5xdWVyaWVzLmxlbmd0aDtlKyspdGhpcy5xdWVyaWVzW2VdLmVsZW1lbnRFbmQodCl9ZW1iZWRkZWRUVmlldyh0KXtsZXQgZT1udWxsO2ZvcihsZXQgaT0wO2k8dGhpcy5sZW5ndGg7aSsrKXtsZXQgcj1udWxsIT09ZT9lLmxlbmd0aDowLG89dGhpcy5nZXRCeUluZGV4KGkpLmVtYmVkZGVkVFZpZXcodCxyKTtvJiYoby5pbmRleEluRGVjbGFyYXRpb25WaWV3PWksbnVsbCE9PWU/ZS5wdXNoKG8pOmU9W29dKX1yZXR1cm4gbnVsbCE9PWU/bmV3IGt4KGUpOm51bGx9dGVtcGxhdGUodCxlKXtmb3IobGV0IGk9MDtpPHRoaXMucXVlcmllcy5sZW5ndGg7aSsrKXRoaXMucXVlcmllc1tpXS50ZW1wbGF0ZSh0LGUpfWdldEJ5SW5kZXgodCl7cmV0dXJuIHRoaXMucXVlcmllc1t0XX1nZXQgbGVuZ3RoKCl7cmV0dXJuIHRoaXMucXVlcmllcy5sZW5ndGh9dHJhY2sodCl7dGhpcy5xdWVyaWVzLnB1c2godCl9fSxGeD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGU9LTEpe3RoaXMubWV0YWRhdGE9dCx0aGlzLm1hdGNoZXM9bnVsbCx0aGlzLmluZGV4SW5EZWNsYXJhdGlvblZpZXc9LTEsdGhpcy5jcm9zc2VzTmdUZW1wbGF0ZT0hMSx0aGlzLl9hcHBsaWVzVG9OZXh0Tm9kZT0hMCx0aGlzLl9kZWNsYXJhdGlvbk5vZGVJbmRleD1lfWVsZW1lbnRTdGFydCh0LGUpe3RoaXMuaXNBcHBseWluZ1RvTm9kZShlKSYmdGhpcy5tYXRjaFROb2RlKHQsZSl9ZWxlbWVudEVuZCh0KXt0aGlzLl9kZWNsYXJhdGlvbk5vZGVJbmRleD09PXQuaW5kZXgmJih0aGlzLl9hcHBsaWVzVG9OZXh0Tm9kZT0hMSl9dGVtcGxhdGUodCxlKXt0aGlzLmVsZW1lbnRTdGFydCh0LGUpfWVtYmVkZGVkVFZpZXcodCxlKXtyZXR1cm4gdGhpcy5pc0FwcGx5aW5nVG9Ob2RlKHQpPyh0aGlzLmNyb3NzZXNOZ1RlbXBsYXRlPSEwLHRoaXMuYWRkTWF0Y2goLXQuaW5kZXgsZSksbmV3IEZ4KHRoaXMubWV0YWRhdGEpKTpudWxsfWlzQXBwbHlpbmdUb05vZGUodCl7aWYodGhpcy5fYXBwbGllc1RvTmV4dE5vZGUmJjEhPSgxJnRoaXMubWV0YWRhdGEuZmxhZ3MpKXtsZXQgZT10aGlzLl9kZWNsYXJhdGlvbk5vZGVJbmRleCxpPXQucGFyZW50O2Zvcig7bnVsbCE9PWkmJjgmaS50eXBlJiZpLmluZGV4IT09ZTspaT1pLnBhcmVudDtyZXR1cm4gZT09PShudWxsIT09aT9pLmluZGV4Oi0xKX1yZXR1cm4gdGhpcy5fYXBwbGllc1RvTmV4dE5vZGV9bWF0Y2hUTm9kZSh0LGUpe2xldCBpPXRoaXMubWV0YWRhdGEucHJlZGljYXRlO2lmKEFycmF5LmlzQXJyYXkoaSkpZm9yKGxldCByPTA7cjxpLmxlbmd0aDtyKyspe2xldCBvPWlbcl07dGhpcy5tYXRjaFROb2RlV2l0aFJlYWRPcHRpb24odCxlLF9DZShlLG8pKSx0aGlzLm1hdGNoVE5vZGVXaXRoUmVhZE9wdGlvbih0LGUsSTEoZSx0LG8sITEsITEpKX1lbHNlIGk9PT1WaT80JmUudHlwZSYmdGhpcy5tYXRjaFROb2RlV2l0aFJlYWRPcHRpb24odCxlLC0xKTp0aGlzLm1hdGNoVE5vZGVXaXRoUmVhZE9wdGlvbih0LGUsSTEoZSx0LGksITEsITEpKX1tYXRjaFROb2RlV2l0aFJlYWRPcHRpb24odCxlLGkpe2lmKG51bGwhPT1pKXtsZXQgcj10aGlzLm1ldGFkYXRhLnJlYWQ7aWYobnVsbCE9PXIpaWYocj09PVJlfHxyPT09T2l8fHI9PT1WaSYmNCZlLnR5cGUpdGhpcy5hZGRNYXRjaChlLmluZGV4LC0yKTtlbHNle2xldCBvPUkxKGUsdCxyLCExLCExKTtudWxsIT09byYmdGhpcy5hZGRNYXRjaChlLmluZGV4LG8pfWVsc2UgdGhpcy5hZGRNYXRjaChlLmluZGV4LGkpfX1hZGRNYXRjaCh0LGUpe251bGw9PT10aGlzLm1hdGNoZXM/dGhpcy5tYXRjaGVzPVt0LGVdOnRoaXMubWF0Y2hlcy5wdXNoKHQsZSl9fTtmdW5jdGlvbiBfQ2Uobix0KXtsZXQgZT1uLmxvY2FsTmFtZXM7aWYobnVsbCE9PWUpZm9yKGxldCBpPTA7aTxlLmxlbmd0aDtpKz0yKWlmKGVbaV09PT10KXJldHVybiBlW2krMV07cmV0dXJuIG51bGx9ZnVuY3Rpb24geUNlKG4sdCxlLGkpe3JldHVybi0xPT09ZT9mdW5jdGlvbihuLHQpe3JldHVybiAxMSZuLnR5cGU/RzAobix0KTo0Jm4udHlwZT9SVChuLHQpOm51bGx9KHQsbik6LTI9PT1lP2Z1bmN0aW9uKG4sdCxlKXtyZXR1cm4gZT09PVJlP0cwKHQsbik6ZT09PVZpP1JUKHQsbik6ZT09PU9pP0JZKHQsbik6dm9pZCAwfShuLHQsaSk6QngobixuWzFdLGUsdCl9ZnVuY3Rpb24gVlkobix0LGUsaSl7bGV0IHI9dFsxOV0ucXVlcmllc1tpXTtpZihudWxsPT09ci5tYXRjaGVzKXtsZXQgbz1uLmRhdGEscz1lLm1hdGNoZXMsYT1bXTtmb3IobGV0IGw9MDtsPHMubGVuZ3RoO2wrPTIpe2xldCBjPXNbbF07YS5wdXNoKGM8MD9udWxsOnlDZSh0LG9bY10sc1tsKzFdLGUubWV0YWRhdGEucmVhZCkpfXIubWF0Y2hlcz1hfXJldHVybiByLm1hdGNoZXN9ZnVuY3Rpb24gUUwobix0LGUsaSl7bGV0IHI9bi5xdWVyaWVzLmdldEJ5SW5kZXgoZSksbz1yLm1hdGNoZXM7aWYobnVsbCE9PW8pe2xldCBzPVZZKG4sdCxyLGUpO2ZvcihsZXQgYT0wO2E8by5sZW5ndGg7YSs9Mil7bGV0IGw9b1thXTtpZihsPjApaS5wdXNoKHNbYS8yXSk7ZWxzZXtsZXQgYz1vW2ErMV0sdT10Wy1sXTtmb3IobGV0IGQ9MTA7ZDx1Lmxlbmd0aDtkKyspe2xldCBwPXVbZF07cFsxN109PT1wWzNdJiZRTChwWzFdLHAsYyxpKX1pZihudWxsIT09dVs5XSl7bGV0IGQ9dVs5XTtmb3IobGV0IHA9MDtwPGQubGVuZ3RoO3ArKyl7bGV0IGg9ZFtwXTtRTChoWzFdLGgsYyxpKX19fX19cmV0dXJuIGl9ZnVuY3Rpb24gTmUobil7bGV0IHQ9cnQoKSxlPUZpKCksaT1xNygpO3YzKGkrMSk7bGV0IHI9elkoZSxpKTtpZihuLmRpcnR5JiZmdW5jdGlvbihuKXtyZXR1cm4gND09KDQmblsyXSl9KHQpPT09KDI9PSgyJnIubWV0YWRhdGEuZmxhZ3MpKSl7aWYobnVsbD09PXIubWF0Y2hlcyluLnJlc2V0KFtdKTtlbHNle2xldCBvPXIuY3Jvc3Nlc05nVGVtcGxhdGU/UUwoZSx0LGksW10pOlZZKGUsdCxyLGkpO24ucmVzZXQobyxCdmUpLG4ubm90aWZ5T25DaGFuZ2VzKCl9cmV0dXJuITB9cmV0dXJuITF9ZnVuY3Rpb24gb3Qobix0LGUpe2xldCBpPUZpKCk7aS5maXJzdENyZWF0ZVBhc3MmJihVWShpLG5ldyByVChuLHQsZSksLTEpLDI9PSgyJnQpJiYoaS5zdGF0aWNWaWV3UXVlcmllcz0hMCkpLEhZKGkscnQoKSx0KX1mdW5jdGlvbiBFaShuLHQsZSxpKXtsZXQgcj1GaSgpO2lmKHIuZmlyc3RDcmVhdGVQYXNzKXtsZXQgbz16bygpO1VZKHIsbmV3IHJUKHQsZSxpKSxvLmluZGV4KSxmdW5jdGlvbihuLHQpe2xldCBlPW4uY29udGVudFF1ZXJpZXN8fChuLmNvbnRlbnRRdWVyaWVzPVtdKTt0IT09KGUubGVuZ3RoP2VbZS5sZW5ndGgtMV06LTEpJiZlLnB1c2gobi5xdWVyaWVzLmxlbmd0aC0xLHQpfShyLG4pLDI9PSgyJmUpJiYoci5zdGF0aWNDb250ZW50UXVlcmllcz0hMCl9SFkocixydCgpLGUpfWZ1bmN0aW9uIExlKCl7cmV0dXJuIG49cnQoKSx0PXE3KCksblsxOV0ucXVlcmllc1t0XS5xdWVyeUxpc3Q7dmFyIG4sdH1mdW5jdGlvbiBIWShuLHQsZSl7bGV0IGk9bmV3IEhsKDQ9PSg0JmUpKTt1cShuLHQsaSxpLmRlc3Ryb3kpLG51bGw9PT10WzE5XSYmKHRbMTldPW5ldyBPeCksdFsxOV0ucXVlcmllcy5wdXNoKG5ldyBSeChpKSl9ZnVuY3Rpb24gVVkobix0LGUpe251bGw9PT1uLnF1ZXJpZXMmJihuLnF1ZXJpZXM9bmV3IGt4KSxuLnF1ZXJpZXMudHJhY2sobmV3IEZ4KHQsZSkpfWZ1bmN0aW9uIHpZKG4sdCl7cmV0dXJuIG4ucXVlcmllcy5nZXRCeUluZGV4KHQpfWZ1bmN0aW9uIHF0KG4sdCl7cmV0dXJuIFJUKG4sdCl9dmFyIEhwPXsiXHUwMjc1XHUwMjc1YXR0cmlidXRlIjp6ZSwiXHUwMjc1XHUwMjc1YXR0cmlidXRlSW50ZXJwb2xhdGUxIjpmdW5jdGlvbiBFcShuLHQsZSxpLHIsbyl7bGV0IHM9cnQoKSxhPVgwKHMsdCxlLGkpO3JldHVybiBhIT09UW4mJlR1KG5vKCkscyxuLGEscixvKSxFcX0sIlx1MDI3NVx1MDI3NWF0dHJpYnV0ZUludGVycG9sYXRlMiI6ZnVuY3Rpb24gVHEobix0LGUsaSxyLG8scyxhKXtsZXQgbD1ydCgpLGM9UTAobCx0LGUsaSxyLG8pO3JldHVybiBjIT09UW4mJlR1KG5vKCksbCxuLGMscyxhKSxUcX0sIlx1MDI3NVx1MDI3NWF0dHJpYnV0ZUludGVycG9sYXRlMyI6ZnVuY3Rpb24gRHEobix0LGUsaSxyLG8scyxhLGwsYyl7bGV0IHU9cnQoKSxkPUswKHUsdCxlLGkscixvLHMsYSk7cmV0dXJuIGQhPT1RbiYmVHUobm8oKSx1LG4sZCxsLGMpLERxfSwiXHUwMjc1XHUwMjc1YXR0cmlidXRlSW50ZXJwb2xhdGU0IjpmdW5jdGlvbiBBcShuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCl7bGV0IHA9cnQoKSxoPVowKHAsdCxlLGkscixvLHMsYSxsLGMpO3JldHVybiBoIT09UW4mJlR1KG5vKCkscCxuLGgsdSxkKSxBcX0sIlx1MDI3NVx1MDI3NWF0dHJpYnV0ZUludGVycG9sYXRlNSI6ZnVuY3Rpb24gSXEobix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoKXtsZXQgZj1ydCgpLG09SjAoZix0LGUsaSxyLG8scyxhLGwsYyx1LGQpO3JldHVybiBtIT09UW4mJlR1KG5vKCksZixuLG0scCxoKSxJcX0sIlx1MDI3NVx1MDI3NWF0dHJpYnV0ZUludGVycG9sYXRlNiI6ZnVuY3Rpb24gUHEobix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYsbSl7bGV0IHg9cnQoKSxnPSQwKHgsdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCk7cmV0dXJuIGchPT1RbiYmVHUobm8oKSx4LG4sZyxmLG0pLFBxfSwiXHUwMjc1XHUwMjc1YXR0cmlidXRlSW50ZXJwb2xhdGU3IjpmdW5jdGlvbiBScShuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtLHgsZyl7bGV0IGI9cnQoKSxEPWVfKGIsdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmLG0pO3JldHVybiBEIT09UW4mJlR1KG5vKCksYixuLEQseCxnKSxScX0sIlx1MDI3NVx1MDI3NWF0dHJpYnV0ZUludGVycG9sYXRlOCI6ZnVuY3Rpb24gT3Eobix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYsbSx4LGcsYixEKXtsZXQgVD1ydCgpLGs9dF8oVCx0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYsbSx4LGcpO3JldHVybiBrIT09UW4mJlR1KG5vKCksVCxuLGssYixEKSxPcX0sIlx1MDI3NVx1MDI3NWF0dHJpYnV0ZUludGVycG9sYXRlViI6ZnVuY3Rpb24ga3Eobix0LGUsaSl7bGV0IHI9cnQoKSxvPVkwKHIsdCk7cmV0dXJuIG8hPT1RbiYmVHUobm8oKSxyLG4sbyxlLGkpLGtxfSwiXHUwMjc1XHUwMjc1ZGVmaW5lQ29tcG9uZW50IjpSLCJcdTAyNzVcdTAyNzVkZWZpbmVEaXJlY3RpdmUiOkhlLCJcdTAyNzVcdTAyNzVkZWZpbmVJbmplY3RhYmxlIjp5ZSwiXHUwMjc1XHUwMjc1ZGVmaW5lSW5qZWN0b3IiOlYsIlx1MDI3NVx1MDI3NWRlZmluZU5nTW9kdWxlIjpILCJcdTAyNzVcdTAyNzVkZWZpbmVQaXBlIjpCMCwiXHUwMjc1XHUwMjc1ZGlyZWN0aXZlSW5qZWN0IjpNLCJcdTAyNzVcdTAyNzVnZXRJbmhlcml0ZWRGYWN0b3J5IjpwaSwiXHUwMjc1XHUwMjc1aW5qZWN0IjpqLCJcdTAyNzVcdTAyNzVpbmplY3RBdHRyaWJ1dGUiOnZvLCJcdTAyNzVcdTAyNzVpbnZhbGlkRmFjdG9yeSI6bmwsIlx1MDI3NVx1MDI3NWludmFsaWRGYWN0b3J5RGVwIjpkOSwiXHUwMjc1XHUwMjc1dGVtcGxhdGVSZWZFeHRyYWN0b3IiOnF0LCJcdTAyNzVcdTAyNzVyZXNldFZpZXciOnNlLCJcdTAyNzVcdTAyNzVOZ09uQ2hhbmdlc0ZlYXR1cmUiOkZ0LCJcdTAyNzVcdTAyNzVQcm92aWRlcnNGZWF0dXJlIjokdCwiXHUwMjc1XHUwMjc1Q29weURlZmluaXRpb25GZWF0dXJlIjpmdW5jdGlvbihuKXtsZXQgZSx0PVNxKG4udHlwZSk7ZT1BYyhuKT90Llx1MDI3NWNtcDp0Llx1MDI3NWRpcjtsZXQgaT1uO2ZvcihsZXQgciBvZiBjYmUpaVtyXT1lW3JdO2lmKEFjKGUpKWZvcihsZXQgciBvZiB1YmUpaVtyXT1lW3JdfSwiXHUwMjc1XHUwMjc1SW5oZXJpdERlZmluaXRpb25GZWF0dXJlIjp0dCwiXHUwMjc1XHUwMjc1U3RhbmRhbG9uZUZlYXR1cmUiOmZ1bmN0aW9uKG4pe24uZ2V0U3RhbmRhbG9uZUluamVjdG9yPXQ9PnQuZ2V0KGVDZSkuZ2V0T3JDcmVhdGVTdGFuZGFsb25lSW5qZWN0b3Iobil9LCJcdTAyNzVcdTAyNzVuZXh0Q29udGV4dCI6UywiXHUwMjc1XHUwMjc1bmFtZXNwYWNlSFRNTCI6SnMsIlx1MDI3NVx1MDI3NW5hbWVzcGFjZU1hdGhNTCI6ZnVuY3Rpb24oKXtabi5sRnJhbWUuY3VycmVudE5hbWVzcGFjZT0ibWF0aCJ9LCJcdTAyNzVcdTAyNzVuYW1lc3BhY2VTVkciOkluLCJcdTAyNzVcdTAyNzVlbmFibGVCaW5kaW5ncyI6ZnVuY3Rpb24oKXtabi5iaW5kaW5nc0VuYWJsZWQ9ITB9LCJcdTAyNzVcdTAyNzVkaXNhYmxlQmluZGluZ3MiOmZ1bmN0aW9uKCl7Wm4uYmluZGluZ3NFbmFibGVkPSExfSwiXHUwMjc1XHUwMjc1ZWxlbWVudFN0YXJ0IjpfLCJcdTAyNzVcdTAyNzVlbGVtZW50RW5kIjp2LCJcdTAyNzVcdTAyNzVlbGVtZW50IjpPLCJcdTAyNzVcdTAyNzVlbGVtZW50Q29udGFpbmVyU3RhcnQiOnNuLCJcdTAyNzVcdTAyNzVlbGVtZW50Q29udGFpbmVyRW5kIjphbiwiXHUwMjc1XHUwMjc1ZWxlbWVudENvbnRhaW5lciI6TmksIlx1MDI3NVx1MDI3NXB1cmVGdW5jdGlvbjAiOlFwLCJcdTAyNzVcdTAyNzVwdXJlRnVuY3Rpb24xIjpPbiwiXHUwMjc1XHUwMjc1cHVyZUZ1bmN0aW9uMiI6UXIsIlx1MDI3NVx1MDI3NXB1cmVGdW5jdGlvbjMiOlp4LCJcdTAyNzVcdTAyNzVwdXJlRnVuY3Rpb240IjpLMywiXHUwMjc1XHUwMjc1cHVyZUZ1bmN0aW9uNSI6WjMsIlx1MDI3NVx1MDI3NXB1cmVGdW5jdGlvbjYiOmZ1bmN0aW9uKG4sdCxlLGkscixvLHMsYSxsKXtsZXQgYz1LcygpK24sdT1ydCgpLGQ9VmwodSxjLGUsaSxyLG8pO3JldHVybiBLZih1LGMrNCxzLGEpfHxkP0R1KHUsYys2LGw/dC5jYWxsKGwsZSxpLHIsbyxzLGEpOnQoZSxpLHIsbyxzLGEpKTpZeCh1LGMrNil9LCJcdTAyNzVcdTAyNzVwdXJlRnVuY3Rpb243IjpmdW5jdGlvbihuLHQsZSxpLHIsbyxzLGEsbCxjKXtsZXQgdT1LcygpK24sZD1ydCgpLHA9VmwoZCx1LGUsaSxyLG8pO3JldHVybiBTVChkLHUrNCxzLGEsbCl8fHA/RHUoZCx1KzcsYz90LmNhbGwoYyxlLGkscixvLHMsYSxsKTp0KGUsaSxyLG8scyxhLGwpKTpZeChkLHUrNyl9LCJcdTAyNzVcdTAyNzVwdXJlRnVuY3Rpb244IjpmdW5jdGlvbihuLHQsZSxpLHIsbyxzLGEsbCxjLHUpe2xldCBkPUtzKCkrbixwPXJ0KCksaD1WbChwLGQsZSxpLHIsbyk7cmV0dXJuIFZsKHAsZCs0LHMsYSxsLGMpfHxoP0R1KHAsZCs4LHU/dC5jYWxsKHUsZSxpLHIsbyxzLGEsbCxjKTp0KGUsaSxyLG8scyxhLGwsYykpOll4KHAsZCs4KX0sIlx1MDI3NVx1MDI3NXB1cmVGdW5jdGlvblYiOmZ1bmN0aW9uKG4sdCxlLGkpe3JldHVybiBOWShydCgpLEtzKCksbix0LGUsaSl9LCJcdTAyNzVcdTAyNzVnZXRDdXJyZW50VmlldyI6UGUsIlx1MDI3NVx1MDI3NXJlc3RvcmVWaWV3IjpvZSwiXHUwMjc1XHUwMjc1bGlzdGVuZXIiOlAsIlx1MDI3NVx1MDI3NXByb2plY3Rpb24iOlZuLCJcdTAyNzVcdTAyNzVzeW50aGV0aWNIb3N0UHJvcGVydHkiOnJfLCJcdTAyNzVcdTAyNzVzeW50aGV0aWNIb3N0TGlzdGVuZXIiOmlfLCJcdTAyNzVcdTAyNzVwaXBlQmluZDEiOlUsIlx1MDI3NVx1MDI3NXBpcGVCaW5kMiI6SmYsIlx1MDI3NVx1MDI3NXBpcGVCaW5kMyI6SjMsIlx1MDI3NVx1MDI3NXBpcGVCaW5kNCI6ZnVuY3Rpb24obix0LGUsaSxyLG8pe2xldCBzPW4rMjIsYT1ydCgpLGw9SDAoYSxzKTtyZXR1cm4gJHgoYSxzKT9GWShhLEtzKCksdCxsLnRyYW5zZm9ybSxlLGkscixvLGwpOmwudHJhbnNmb3JtKGUsaSxyLG8pfSwiXHUwMjc1XHUwMjc1cGlwZUJpbmRWIjpmdW5jdGlvbihuLHQsZSl7bGV0IGk9bisyMixyPXJ0KCksbz1IMChyLGkpO3JldHVybiAkeChyLGkpP05ZKHIsS3MoKSx0LG8udHJhbnNmb3JtLGUsbyk6by50cmFuc2Zvcm0uYXBwbHkobyxlKX0sIlx1MDI3NVx1MDI3NXByb2plY3Rpb25EZWYiOnhpLCJcdTAyNzVcdTAyNzVob3N0UHJvcGVydHkiOl9zLCJcdTAyNzVcdTAyNzVwcm9wZXJ0eSI6eSwiXHUwMjc1XHUwMjc1cHJvcGVydHlJbnRlcnBvbGF0ZSI6WmksIlx1MDI3NVx1MDI3NXByb3BlcnR5SW50ZXJwb2xhdGUxIjpYeCwiXHUwMjc1XHUwMjc1cHJvcGVydHlJbnRlcnBvbGF0ZTIiOkVULCJcdTAyNzVcdTAyNzVwcm9wZXJ0eUludGVycG9sYXRlMyI6ZnVuY3Rpb24gTnEobix0LGUsaSxyLG8scyxhLGwpe2xldCBjPXJ0KCksdT1LMChjLHQsZSxpLHIsbyxzLGEpO3JldHVybiB1IT09UW4mJmlsKEZpKCksbm8oKSxjLG4sdSxjWzExXSxsLCExKSxOcX0sIlx1MDI3NVx1MDI3NXByb3BlcnR5SW50ZXJwb2xhdGU0IjpmdW5jdGlvbiBMcShuLHQsZSxpLHIsbyxzLGEsbCxjLHUpe2xldCBkPXJ0KCkscD1aMChkLHQsZSxpLHIsbyxzLGEsbCxjKTtyZXR1cm4gcCE9PVFuJiZpbChGaSgpLG5vKCksZCxuLHAsZFsxMV0sdSwhMSksTHF9LCJcdTAyNzVcdTAyNzVwcm9wZXJ0eUludGVycG9sYXRlNSI6ZnVuY3Rpb24gQnEobix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCl7bGV0IGg9cnQoKSxmPUowKGgsdCxlLGkscixvLHMsYSxsLGMsdSxkKTtyZXR1cm4gZiE9PVFuJiZpbChGaSgpLG5vKCksaCxuLGYsaFsxMV0scCwhMSksQnF9LCJcdTAyNzVcdTAyNzVwcm9wZXJ0eUludGVycG9sYXRlNiI6ZnVuY3Rpb24gVnEobix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYpe2xldCBtPXJ0KCkseD0kMChtLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgpO3JldHVybiB4IT09UW4mJmlsKEZpKCksbm8oKSxtLG4seCxtWzExXSxmLCExKSxWcX0sIlx1MDI3NVx1MDI3NXByb3BlcnR5SW50ZXJwb2xhdGU3IjpmdW5jdGlvbiBIcShuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtLHgpe2xldCBnPXJ0KCksYj1lXyhnLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtKTtyZXR1cm4gYiE9PVFuJiZpbChGaSgpLG5vKCksZyxuLGIsZ1sxMV0seCwhMSksSHF9LCJcdTAyNzVcdTAyNzVwcm9wZXJ0eUludGVycG9sYXRlOCI6ZnVuY3Rpb24gVXEobix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYsbSx4LGcsYil7bGV0IEQ9cnQoKSxUPXRfKEQsdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmLG0seCxnKTtyZXR1cm4gVCE9PVFuJiZpbChGaSgpLG5vKCksRCxuLFQsRFsxMV0sYiwhMSksVXF9LCJcdTAyNzVcdTAyNzVwcm9wZXJ0eUludGVycG9sYXRlViI6ZnVuY3Rpb24genEobix0LGUpe2xldCBpPXJ0KCkscj1ZMChpLHQpO3JldHVybiByIT09UW4mJmlsKEZpKCksbm8oKSxpLG4scixpWzExXSxlLCExKSx6cX0sIlx1MDI3NVx1MDI3NXBpcGUiOkIsIlx1MDI3NVx1MDI3NXF1ZXJ5UmVmcmVzaCI6TmUsIlx1MDI3NVx1MDI3NXZpZXdRdWVyeSI6b3QsIlx1MDI3NVx1MDI3NWxvYWRRdWVyeSI6TGUsIlx1MDI3NVx1MDI3NWNvbnRlbnRRdWVyeSI6RWksIlx1MDI3NVx1MDI3NXJlZmVyZW5jZSI6JGUsIlx1MDI3NVx1MDI3NWNsYXNzTWFwIjpEYSwiXHUwMjc1XHUwMjc1Y2xhc3NNYXBJbnRlcnBvbGF0ZTEiOlF4LCJcdTAyNzVcdTAyNzVjbGFzc01hcEludGVycG9sYXRlMiI6ZnVuY3Rpb24obix0LGUsaSxyKXtPYyhlbCxBdSxRMChydCgpLG4sdCxlLGksciksITApfSwiXHUwMjc1XHUwMjc1Y2xhc3NNYXBJbnRlcnBvbGF0ZTMiOmZ1bmN0aW9uKG4sdCxlLGkscixvLHMpe09jKGVsLEF1LEswKHJ0KCksbix0LGUsaSxyLG8scyksITApfSwiXHUwMjc1XHUwMjc1Y2xhc3NNYXBJbnRlcnBvbGF0ZTQiOmZ1bmN0aW9uKG4sdCxlLGkscixvLHMsYSxsKXtPYyhlbCxBdSxaMChydCgpLG4sdCxlLGkscixvLHMsYSxsKSwhMCl9LCJcdTAyNzVcdTAyNzVjbGFzc01hcEludGVycG9sYXRlNSI6ZnVuY3Rpb24obix0LGUsaSxyLG8scyxhLGwsYyx1KXtPYyhlbCxBdSxKMChydCgpLG4sdCxlLGkscixvLHMsYSxsLGMsdSksITApfSwiXHUwMjc1XHUwMjc1Y2xhc3NNYXBJbnRlcnBvbGF0ZTYiOmZ1bmN0aW9uKG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHApe09jKGVsLEF1LCQwKHJ0KCksbix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCksITApfSwiXHUwMjc1XHUwMjc1Y2xhc3NNYXBJbnRlcnBvbGF0ZTciOmZ1bmN0aW9uKG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmKXtPYyhlbCxBdSxlXyhydCgpLG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmKSwhMCl9LCJcdTAyNzVcdTAyNzVjbGFzc01hcEludGVycG9sYXRlOCI6ZnVuY3Rpb24obix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYsbSx4KXtPYyhlbCxBdSx0XyhydCgpLG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmLG0seCksITApfSwiXHUwMjc1XHUwMjc1Y2xhc3NNYXBJbnRlcnBvbGF0ZVYiOmZ1bmN0aW9uKG4pe09jKGVsLEF1LFkwKHJ0KCksbiksITApfSwiXHUwMjc1XHUwMjc1c3R5bGVNYXAiOmpsLCJcdTAyNzVcdTAyNzVzdHlsZU1hcEludGVycG9sYXRlMSI6ZnVuY3Rpb24obix0LGUpe2psKFgwKHJ0KCksbix0LGUpKX0sIlx1MDI3NVx1MDI3NXN0eWxlTWFwSW50ZXJwb2xhdGUyIjpmdW5jdGlvbihuLHQsZSxpLHIpe2psKFEwKHJ0KCksbix0LGUsaSxyKSl9LCJcdTAyNzVcdTAyNzVzdHlsZU1hcEludGVycG9sYXRlMyI6ZnVuY3Rpb24obix0LGUsaSxyLG8scyl7amwoSzAocnQoKSxuLHQsZSxpLHIsbyxzKSl9LCJcdTAyNzVcdTAyNzVzdHlsZU1hcEludGVycG9sYXRlNCI6ZnVuY3Rpb24obix0LGUsaSxyLG8scyxhLGwpe2psKFowKHJ0KCksbix0LGUsaSxyLG8scyxhLGwpKX0sIlx1MDI3NVx1MDI3NXN0eWxlTWFwSW50ZXJwb2xhdGU1IjpmdW5jdGlvbihuLHQsZSxpLHIsbyxzLGEsbCxjLHUpe2psKEowKHJ0KCksbix0LGUsaSxyLG8scyxhLGwsYyx1KSl9LCJcdTAyNzVcdTAyNzVzdHlsZU1hcEludGVycG9sYXRlNiI6ZnVuY3Rpb24obix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCl7amwoJDAocnQoKSxuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwKSl9LCJcdTAyNzVcdTAyNzVzdHlsZU1hcEludGVycG9sYXRlNyI6ZnVuY3Rpb24obix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYpe2psKGVfKHJ0KCksbix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYpKX0sIlx1MDI3NVx1MDI3NXN0eWxlTWFwSW50ZXJwb2xhdGU4IjpmdW5jdGlvbihuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtLHgpe2psKHRfKHJ0KCksbix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYsbSx4KSl9LCJcdTAyNzVcdTAyNzVzdHlsZU1hcEludGVycG9sYXRlViI6ZnVuY3Rpb24obil7amwoWTAocnQoKSxuKSl9LCJcdTAyNzVcdTAyNzVzdHlsZVByb3AiOlB0LCJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTEiOmZ1bmN0aW9uIHJZKG4sdCxlLGkscil7cmV0dXJuIFJjKG4sWDAocnQoKSx0LGUsaSksciwhMSkscll9LCJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTIiOmZ1bmN0aW9uIG9ZKG4sdCxlLGkscixvLHMpe3JldHVybiBSYyhuLFEwKHJ0KCksdCxlLGkscixvKSxzLCExKSxvWX0sIlx1MDI3NVx1MDI3NXN0eWxlUHJvcEludGVycG9sYXRlMyI6ZnVuY3Rpb24gc1kobix0LGUsaSxyLG8scyxhLGwpe3JldHVybiBSYyhuLEswKHJ0KCksdCxlLGkscixvLHMsYSksbCwhMSksc1l9LCJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTQiOmZ1bmN0aW9uIGFZKG4sdCxlLGkscixvLHMsYSxsLGMsdSl7cmV0dXJuIFJjKG4sWjAocnQoKSx0LGUsaSxyLG8scyxhLGwsYyksdSwhMSksYVl9LCJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTUiOmZ1bmN0aW9uIGxZKG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHApe3JldHVybiBSYyhuLEowKHJ0KCksdCxlLGkscixvLHMsYSxsLGMsdSxkKSxwLCExKSxsWX0sIlx1MDI3NVx1MDI3NXN0eWxlUHJvcEludGVycG9sYXRlNiI6ZnVuY3Rpb24gY1kobix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYpe3JldHVybiBSYyhuLCQwKHJ0KCksdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCksZiwhMSksY1l9LCJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTciOmZ1bmN0aW9uIHVZKG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmLG0seCl7cmV0dXJuIFJjKG4sZV8ocnQoKSx0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYsbSkseCwhMSksdVl9LCJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTgiOmZ1bmN0aW9uIGRZKG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmLG0seCxnLGIpe3JldHVybiBSYyhuLHRfKHJ0KCksdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmLG0seCxnKSxiLCExKSxkWX0sIlx1MDI3NVx1MDI3NXN0eWxlUHJvcEludGVycG9sYXRlViI6ZnVuY3Rpb24gcFkobix0LGUpe3JldHVybiBSYyhuLFkwKHJ0KCksdCksZSwhMSkscFl9LCJcdTAyNzVcdTAyNzVjbGFzc1Byb3AiOmV0LCJcdTAyNzVcdTAyNzVhZHZhbmNlIjpDLCJcdTAyNzVcdTAyNzV0ZW1wbGF0ZSI6RSwiXHUwMjc1XHUwMjc1dGV4dCI6QSwiXHUwMjc1XHUwMjc1dGV4dEludGVycG9sYXRlIjp5dCwiXHUwMjc1XHUwMjc1dGV4dEludGVycG9sYXRlMSI6amUsIlx1MDI3NVx1MDI3NXRleHRJbnRlcnBvbGF0ZTIiOlhwLCJcdTAyNzVcdTAyNzV0ZXh0SW50ZXJwb2xhdGUzIjpUVCwiXHUwMjc1XHUwMjc1dGV4dEludGVycG9sYXRlNCI6ZnVuY3Rpb24gSnEobix0LGUsaSxyLG8scyxhLGwpe2xldCBjPXJ0KCksdT1aMChjLG4sdCxlLGkscixvLHMsYSxsKTtyZXR1cm4gdSE9PVFuJiZqZChjLFpzKCksdSksSnF9LCJcdTAyNzVcdTAyNzV0ZXh0SW50ZXJwb2xhdGU1IjpmdW5jdGlvbiAkcShuLHQsZSxpLHIsbyxzLGEsbCxjLHUpe2xldCBkPXJ0KCkscD1KMChkLG4sdCxlLGkscixvLHMsYSxsLGMsdSk7cmV0dXJuIHAhPT1RbiYmamQoZCxacygpLHApLCRxfSwiXHUwMjc1XHUwMjc1dGV4dEludGVycG9sYXRlNiI6ZnVuY3Rpb24gZVkobix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCl7bGV0IGg9cnQoKSxmPSQwKGgsbix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCk7cmV0dXJuIGYhPT1RbiYmamQoaCxacygpLGYpLGVZfSwiXHUwMjc1XHUwMjc1dGV4dEludGVycG9sYXRlNyI6ZnVuY3Rpb24gdFkobix0LGUsaSxyLG8scyxhLGwsYyx1LGQscCxoLGYpe2xldCBtPXJ0KCkseD1lXyhtLG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmKTtyZXR1cm4geCE9PVFuJiZqZChtLFpzKCkseCksdFl9LCJcdTAyNzVcdTAyNzV0ZXh0SW50ZXJwb2xhdGU4IjpmdW5jdGlvbiBuWShuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtLHgpe2xldCBnPXJ0KCksYj10XyhnLG4sdCxlLGkscixvLHMsYSxsLGMsdSxkLHAsaCxmLG0seCk7cmV0dXJuIGIhPT1RbiYmamQoZyxacygpLGIpLG5ZfSwiXHUwMjc1XHUwMjc1dGV4dEludGVycG9sYXRlViI6ZnVuY3Rpb24gaVkobil7bGV0IHQ9cnQoKSxlPVkwKHQsbik7cmV0dXJuIGUhPT1RbiYmamQodCxacygpLGUpLGlZfSwiXHUwMjc1XHUwMjc1aTE4biI6QVQsIlx1MDI3NVx1MDI3NWkxOG5BdHRyaWJ1dGVzIjpmdW5jdGlvbihuLHQpe2xldCBlPUZpKCk7IWZ1bmN0aW9uKG4sdCxlKXtsZXQgcj16bygpLmluZGV4LG89W107aWYobi5maXJzdENyZWF0ZVBhc3MmJm51bGw9PT1uLmRhdGFbdF0pe2ZvcihsZXQgcz0wO3M8ZS5sZW5ndGg7cys9Mil7bGV0IGE9ZVtzXSxsPWVbcysxXTtpZigiIiE9PWwpe2lmKEN4ZS50ZXN0KGwpKXRocm93IG5ldyBFcnJvcihgSUNVIGV4cHJlc3Npb25zIGFyZSBub3Qgc3VwcG9ydGVkIGluIGF0dHJpYnV0ZXMuIE1lc3NhZ2U6ICIke2x9Ii5gKTtieChvLGwscixhLFB4ZShvKSxudWxsKX19bi5kYXRhW3RdPW99fShlLG4rMjIsVXAoZS5jb25zdHMsdCkpfSwiXHUwMjc1XHUwMjc1aTE4bkV4cCI6S3gsIlx1MDI3NVx1MDI3NWkxOG5TdGFydCI6QVksIlx1MDI3NVx1MDI3NWkxOG5FbmQiOklZLCJcdTAyNzVcdTAyNzVpMThuQXBwbHkiOklULCJcdTAyNzVcdTAyNzVpMThuUG9zdHByb2Nlc3MiOlBULCJcdTAyNzVcdTAyNzVyZXNvbHZlV2luZG93IjpXeCwiXHUwMjc1XHUwMjc1cmVzb2x2ZURvY3VtZW50IjpfVCwiXHUwMjc1XHUwMjc1cmVzb2x2ZUJvZHkiOmZ1bmN0aW9uKG4pe3JldHVybiBuLm93bmVyRG9jdW1lbnQuYm9keX0sIlx1MDI3NVx1MDI3NXNldENvbXBvbmVudFNjb3BlIjpOeCwiXHUwMjc1XHUwMjc1c2V0TmdNb2R1bGVTY29wZSI6ZnVuY3Rpb24obix0KXtyZXR1cm4gWmYoKCk9PntsZXQgZT1JMChuLCEwKTtlLmRlY2xhcmF0aW9ucz10LmRlY2xhcmF0aW9uc3x8UWksZS5pbXBvcnRzPXQuaW1wb3J0c3x8UWksZS5leHBvcnRzPXQuZXhwb3J0c3x8UWl9KX0sIlx1MDI3NVx1MDI3NXJlZ2lzdGVyTmdNb2R1bGVUeXBlIjptOSwiXHUwMjc1XHUwMjc1c2FuaXRpemVIdG1sIjpBMywiXHUwMjc1XHUwMjc1c2FuaXRpemVTdHlsZSI6ZnVuY3Rpb24obil7bGV0IHQ9R3goKTtyZXR1cm4gdD90LnNhbml0aXplKG1vLlNUWUxFLG4pfHwiIjpQYyhuLCJTdHlsZSIpP1RhKG4pOktuKG4pfSwiXHUwMjc1XHUwMjc1c2FuaXRpemVSZXNvdXJjZVVybCI6QTksIlx1MDI3NVx1MDI3NXNhbml0aXplU2NyaXB0IjpmdW5jdGlvbihuKXtsZXQgdD1HeCgpO2lmKHQpcmV0dXJuIHFXKHQuc2FuaXRpemUobW8uU0NSSVBULG4pfHwiIik7aWYoUGMobiwiU2NyaXB0IikpcmV0dXJuIHFXKFRhKG4pKTt0aHJvdyBuZXcgQXQoOTA1LCExKX0sIlx1MDI3NVx1MDI3NXNhbml0aXplVXJsIjp6bCwiXHUwMjc1XHUwMjc1c2FuaXRpemVVcmxPclJlc291cmNlVXJsIjpmdW5jdGlvbihuLHQsZSl7cmV0dXJuIGZ1bmN0aW9uKG4sdCl7cmV0dXJuInNyYyI9PT10JiYoImVtYmVkIj09PW58fCJmcmFtZSI9PT1ufHwiaWZyYW1lIj09PW58fCJtZWRpYSI9PT1ufHwic2NyaXB0Ij09PW4pfHwiaHJlZiI9PT10JiYoImJhc2UiPT09bnx8ImxpbmsiPT09bik/QTk6emx9KHQsZSkobil9LCJcdTAyNzVcdTAyNzV0cnVzdENvbnN0YW50SHRtbCI6ZnVuY3Rpb24obil7cmV0dXJuIE8wKG5bMF0pfSwiXHUwMjc1XHUwMjc1dHJ1c3RDb25zdGFudFJlc291cmNlVXJsIjpmdW5jdGlvbihuKXtyZXR1cm4gZnVuY3Rpb24obil7cmV0dXJuIHY5KCk/LmNyZWF0ZVNjcmlwdFVSTChuKXx8bn0oblswXSl9LGZvcndhcmRSZWY6Sm4scmVzb2x2ZUZvcndhcmRSZWY6S2l9O2Z1bmN0aW9uIGpZKG4pe3JldHVybiEhSTAobil9dmFyIEYxPVtdLEpOPSExO2Z1bmN0aW9uIEdZKG4pe3JldHVybiBBcnJheS5pc0FycmF5KG4pP24uZXZlcnkoR1kpOiEhS2kobil9ZnVuY3Rpb24gSUNlKG4sdCl7bGV0IGU9RmQodC5kZWNsYXJhdGlvbnN8fFFpKSxpPUwwKG4pO2UuZm9yRWFjaChyPT57KHI9S2kocikpLmhhc093blByb3BlcnR5KGNUKT9XWShObChyKSxpKTohci5oYXNPd25Qcm9wZXJ0eShsMykmJiFyLmhhc093blByb3BlcnR5KGMzKSYmKHIubmdTZWxlY3RvclNjb3BlPW4pfSl9ZnVuY3Rpb24gV1kobix0KXtuLmRpcmVjdGl2ZURlZnM9KCk9PkFycmF5LmZyb20odC5jb21waWxhdGlvbi5kaXJlY3RpdmVzKS5tYXAoZT0+ZS5oYXNPd25Qcm9wZXJ0eShjVCk/TmwoZSk6R2YoZSkpLmZpbHRlcihlPT4hIWUpLG4ucGlwZURlZnM9KCk9PkFycmF5LmZyb20odC5jb21waWxhdGlvbi5waXBlcykubWFwKGU9PkxkKGUpKSxuLnNjaGVtYXM9dC5zY2hlbWFzLG4udFZpZXc9bnVsbH1mdW5jdGlvbiBMMChuKXtpZihqWShuKSlyZXR1cm4gZnVuY3Rpb24obil7bGV0IHQ9STAobiwhMCk7aWYobnVsbCE9PXQudHJhbnNpdGl2ZUNvbXBpbGVTY29wZXMpcmV0dXJuIHQudHJhbnNpdGl2ZUNvbXBpbGVTY29wZXM7bGV0IGU9e3NjaGVtYXM6dC5zY2hlbWFzfHxudWxsLGNvbXBpbGF0aW9uOntkaXJlY3RpdmVzOm5ldyBTZXQscGlwZXM6bmV3IFNldH0sZXhwb3J0ZWQ6e2RpcmVjdGl2ZXM6bmV3IFNldCxwaXBlczpuZXcgU2V0fX07cmV0dXJuIFIxKHQuaW1wb3J0cykuZm9yRWFjaChpPT57bGV0IHI9TDAoaSk7ci5leHBvcnRlZC5kaXJlY3RpdmVzLmZvckVhY2gobz0+ZS5jb21waWxhdGlvbi5kaXJlY3RpdmVzLmFkZChvKSksci5leHBvcnRlZC5waXBlcy5mb3JFYWNoKG89PmUuY29tcGlsYXRpb24ucGlwZXMuYWRkKG8pKX0pLFIxKHQuZGVjbGFyYXRpb25zKS5mb3JFYWNoKGk9PntMZChpKT9lLmNvbXBpbGF0aW9uLnBpcGVzLmFkZChpKTplLmNvbXBpbGF0aW9uLmRpcmVjdGl2ZXMuYWRkKGkpfSksUjEodC5leHBvcnRzKS5mb3JFYWNoKGk9PntsZXQgcj1pO2lmKGpZKHIpKXtsZXQgbz1MMChyKTtvLmV4cG9ydGVkLmRpcmVjdGl2ZXMuZm9yRWFjaChzPT57ZS5jb21waWxhdGlvbi5kaXJlY3RpdmVzLmFkZChzKSxlLmV4cG9ydGVkLmRpcmVjdGl2ZXMuYWRkKHMpfSksby5leHBvcnRlZC5waXBlcy5mb3JFYWNoKHM9PntlLmNvbXBpbGF0aW9uLnBpcGVzLmFkZChzKSxlLmV4cG9ydGVkLnBpcGVzLmFkZChzKX0pfWVsc2UgTGQocik/ZS5leHBvcnRlZC5waXBlcy5hZGQocik6ZS5leHBvcnRlZC5kaXJlY3RpdmVzLmFkZChyKX0pLHQudHJhbnNpdGl2ZUNvbXBpbGVTY29wZXM9ZSxlfShuKTtpZihSNyhuKSl7aWYobnVsbCE9PShObChuKXx8R2YobikpKXJldHVybntzY2hlbWFzOm51bGwsY29tcGlsYXRpb246e2RpcmVjdGl2ZXM6bmV3IFNldCxwaXBlczpuZXcgU2V0fSxleHBvcnRlZDp7ZGlyZWN0aXZlczpuZXcgU2V0KFtuXSkscGlwZXM6bmV3IFNldH19O2lmKG51bGwhPT1MZChuKSlyZXR1cm57c2NoZW1hczpudWxsLGNvbXBpbGF0aW9uOntkaXJlY3RpdmVzOm5ldyBTZXQscGlwZXM6bmV3IFNldH0sZXhwb3J0ZWQ6e2RpcmVjdGl2ZXM6bmV3IFNldCxwaXBlczpuZXcgU2V0KFtuXSl9fX10aHJvdyBuZXcgRXJyb3IoYCR7bi5uYW1lfSBkb2VzIG5vdCBoYXZlIGEgbW9kdWxlIGRlZiAoXHUwMjc1bW9kIHByb3BlcnR5KWApfWZ1bmN0aW9uIF83KG4pe3JldHVybiBmdW5jdGlvbihuKXtyZXR1cm4gdm9pZCAwIT09bi5uZ01vZHVsZX0obik/bi5uZ01vZHVsZTpufXZhciAkTj0wO2Z1bmN0aW9uIHFZKG4sdCl7bGV0IGU9bnVsbDtYWShuLHR8fHt9KSxPYmplY3QuZGVmaW5lUHJvcGVydHkobixsMyx7Z2V0OigpPT57aWYobnVsbD09PWUpe2xldCBpPVlZKG4sdHx8e30pO2U9TGwoKS5jb21waWxlRGlyZWN0aXZlKEhwLGkuc291cmNlTWFwVXJsLGkubWV0YWRhdGEpfXJldHVybiBlfSxjb25maWd1cmFibGU6ITF9KX1mdW5jdGlvbiBZWShuLHQpe2xldCBlPW4mJm4ubmFtZSxpPWBuZzovLy8ke2V9L1x1MDI3NWRpci5qc2Ascj1MbCgpLG89UVkobix0KTtyZXR1cm4gby50eXBlU291cmNlU3Bhbj1yLmNyZWF0ZVBhcnNlU291cmNlU3BhbigiRGlyZWN0aXZlIixlLGkpLG8udXNlc0luaGVyaXRhbmNlJiZLWShuKSx7bWV0YWRhdGE6byxzb3VyY2VNYXBVcmw6aX19ZnVuY3Rpb24gWFkobix0KXtsZXQgZT1udWxsO09iamVjdC5kZWZpbmVQcm9wZXJ0eShuLE5kLHtnZXQ6KCk9PntpZihudWxsPT09ZSl7bGV0IGk9WVkobix0KSxyPUxsKCk7ZT1yLmNvbXBpbGVGYWN0b3J5KEhwLGBuZzovLy8ke24ubmFtZX0vXHUwMjc1ZmFjLmpzYCx7bmFtZTppLm1ldGFkYXRhLm5hbWUsdHlwZTppLm1ldGFkYXRhLnR5cGUsdHlwZUFyZ3VtZW50Q291bnQ6MCxkZXBzOmZUKG4pLHRhcmdldDpyLkZhY3RvcnlUYXJnZXQuRGlyZWN0aXZlfSl9cmV0dXJuIGV9LGNvbmZpZ3VyYWJsZTohMX0pfWZ1bmN0aW9uIEZDZShuKXtyZXR1cm4gT2JqZWN0LmdldFByb3RvdHlwZU9mKG4ucHJvdG90eXBlKT09PU9iamVjdC5wcm90b3R5cGV9ZnVuY3Rpb24gUVkobix0KXtsZXQgZT1TMygpLGk9ZS5vd25Qcm9wTWV0YWRhdGEobik7cmV0dXJue25hbWU6bi5uYW1lLHR5cGU6bixzZWxlY3Rvcjp2b2lkIDAhPT10LnNlbGVjdG9yP3Quc2VsZWN0b3I6bnVsbCxob3N0OnQuaG9zdHx8QTAscHJvcE1ldGFkYXRhOmksaW5wdXRzOnQuaW5wdXRzfHxRaSxvdXRwdXRzOnQub3V0cHV0c3x8UWkscXVlcmllczp2NyhuLGksWlkpLGxpZmVjeWNsZTp7dXNlc09uQ2hhbmdlczplLmhhc0xpZmVjeWNsZUhvb2sobiwibmdPbkNoYW5nZXMiKX0sdHlwZVNvdXJjZVNwYW46bnVsbCx1c2VzSW5oZXJpdGFuY2U6IUZDZShuKSxleHBvcnRBczpCQ2UodC5leHBvcnRBcykscHJvdmlkZXJzOnQucHJvdmlkZXJzfHxudWxsLHZpZXdRdWVyaWVzOnY3KG4saSxKWSksaXNTdGFuZGFsb25lOiEhdC5zdGFuZGFsb25lfX1mdW5jdGlvbiBLWShuKXtsZXQgdD1PYmplY3QucHJvdG90eXBlLGU9T2JqZWN0LmdldFByb3RvdHlwZU9mKG4ucHJvdG90eXBlKS5jb25zdHJ1Y3Rvcjtmb3IoO2UmJmUhPT10OykhR2YoZSkmJiFObChlKSYmSENlKGUpJiZxWShlLG51bGwpLGU9T2JqZWN0LmdldFByb3RvdHlwZU9mKGUpfWZ1bmN0aW9uIE5DZShuKXtyZXR1cm4ic3RyaW5nIj09dHlwZW9mIG4/ZVgobik6S2kobil9ZnVuY3Rpb24gTENlKG4sdCl7cmV0dXJue3Byb3BlcnR5TmFtZTpuLHByZWRpY2F0ZTpOQ2UodC5zZWxlY3RvciksZGVzY2VuZGFudHM6dC5kZXNjZW5kYW50cyxmaXJzdDp0LmZpcnN0LHJlYWQ6dC5yZWFkP3QucmVhZDpudWxsLHN0YXRpYzohIXQuc3RhdGljLGVtaXREaXN0aW5jdENoYW5nZXNPbmx5OiEhdC5lbWl0RGlzdGluY3RDaGFuZ2VzT25seX19ZnVuY3Rpb24gdjcobix0LGUpe2xldCBpPVtdO2ZvcihsZXQgciBpbiB0KWlmKHQuaGFzT3duUHJvcGVydHkocikpe2xldCBvPXRbcl07by5mb3JFYWNoKHM9PntpZihlKHMpKXtpZighcy5zZWxlY3Rvcil0aHJvdyBuZXcgRXJyb3IoYENhbid0IGNvbnN0cnVjdCBhIHF1ZXJ5IGZvciB0aGUgcHJvcGVydHkgIiR7cn0iIG9mICIke28zKG4pfSIgc2luY2UgdGhlIHF1ZXJ5IHNlbGVjdG9yIHdhc24ndCBkZWZpbmVkLmApO2lmKG8uc29tZSgkWSkpdGhyb3cgbmV3IEVycm9yKCJDYW5ub3QgY29tYmluZSBASW5wdXQgZGVjb3JhdG9ycyB3aXRoIHF1ZXJ5IGRlY29yYXRvcnMiKTtpLnB1c2goTENlKHIscykpfX0pfXJldHVybiBpfWZ1bmN0aW9uIEJDZShuKXtyZXR1cm4gdm9pZCAwPT09bj9udWxsOmVYKG4pfWZ1bmN0aW9uIFpZKG4pe2xldCB0PW4ubmdNZXRhZGF0YU5hbWU7cmV0dXJuIkNvbnRlbnRDaGlsZCI9PT10fHwiQ29udGVudENoaWxkcmVuIj09PXR9ZnVuY3Rpb24gSlkobil7bGV0IHQ9bi5uZ01ldGFkYXRhTmFtZTtyZXR1cm4iVmlld0NoaWxkIj09PXR8fCJWaWV3Q2hpbGRyZW4iPT09dH1mdW5jdGlvbiAkWShuKXtyZXR1cm4iSW5wdXQiPT09bi5uZ01ldGFkYXRhTmFtZX1mdW5jdGlvbiBlWChuKXtyZXR1cm4gbi5zcGxpdCgiLCIpLm1hcCh0PT50LnRyaW0oKSl9dmFyIFZDZT1bIm5nT25DaGFuZ2VzIiwibmdPbkluaXQiLCJuZ09uRGVzdHJveSIsIm5nRG9DaGVjayIsIm5nQWZ0ZXJWaWV3SW5pdCIsIm5nQWZ0ZXJWaWV3Q2hlY2tlZCIsIm5nQWZ0ZXJDb250ZW50SW5pdCIsIm5nQWZ0ZXJDb250ZW50Q2hlY2tlZCJdO2Z1bmN0aW9uIEhDZShuKXtsZXQgdD1TMygpO2lmKFZDZS5zb21lKGk9PnQuaGFzTGlmZWN5Y2xlSG9vayhuLGkpKSlyZXR1cm4hMDtsZXQgZT10LnByb3BNZXRhZGF0YShuKTtmb3IobGV0IGkgaW4gZSl7bGV0IHI9ZVtpXTtmb3IobGV0IG89MDtvPHIubGVuZ3RoO28rKyl7bGV0IHM9cltvXSxhPXMubmdNZXRhZGF0YU5hbWU7aWYoJFkocyl8fFpZKHMpfHxKWShzKXx8Ik91dHB1dCI9PT1hfHwiSG9zdEJpbmRpbmciPT09YXx8Ikhvc3RMaXN0ZW5lciI9PT1hKXJldHVybiEwfX1yZXR1cm4hMX1mdW5jdGlvbiB5NyhuLHQpe3JldHVybnt0eXBlOm4sbmFtZTpuLm5hbWUscGlwZU5hbWU6dC5uYW1lLHB1cmU6dm9pZCAwPT09dC5wdXJlfHx0LnB1cmUsaXNTdGFuZGFsb25lOiEhdC5zdGFuZGFsb25lfX12YXIgekNlPVZ4KCJEaXJlY3RpdmUiLChuPXt9KT0+bix2b2lkIDAsdm9pZCAwLChuLHQpPT5xWShuLHQpKTtmdW5jdGlvbiBvVCguLi5uKXt9VngoIkNvbXBvbmVudCIsKG49e30pPT4oe2NoYW5nZURldGVjdGlvbjpweC5EZWZhdWx0LC4uLm59KSx6Q2Usdm9pZCAwLChuLHQpPT5mdW5jdGlvbihuLHQpe2xldCBlPW51bGw7KGZ1bmN0aW9uKG4sdCl7ZjkodCkmJihEeC5zZXQobix0KSxoOS5hZGQobikpfSkobix0KSxYWShuLHQpLE9iamVjdC5kZWZpbmVQcm9wZXJ0eShuLGNULHtnZXQ6KCk9PntpZihudWxsPT09ZSl7bGV0IGk9TGwoKTtpZihmOSh0KSl7bGV0IGM9W2BDb21wb25lbnQgJyR7bi5uYW1lfScgaXMgbm90IHJlc29sdmVkOmBdO3Rocm93IHQudGVtcGxhdGVVcmwmJmMucHVzaChgIC0gdGVtcGxhdGVVcmw6ICR7dC50ZW1wbGF0ZVVybH1gKSx0LnN0eWxlVXJscyYmdC5zdHlsZVVybHMubGVuZ3RoJiZjLnB1c2goYCAtIHN0eWxlVXJsczogJHtKU09OLnN0cmluZ2lmeSh0LnN0eWxlVXJscyl9YCksYy5wdXNoKCJEaWQgeW91IHJ1biBhbmQgd2FpdCBmb3IgJ3Jlc29sdmVDb21wb25lbnRSZXNvdXJjZXMoKSc/IiksbmV3IEVycm9yKGMuam9pbigiXG4iKSl9bGV0IHI9bnVsbCxvPXQucHJlc2VydmVXaGl0ZXNwYWNlczt2b2lkIDA9PT1vJiYobz1udWxsIT09ciYmdm9pZCAwIT09ci5wcmVzZXJ2ZVdoaXRlc3BhY2VzJiZyLnByZXNlcnZlV2hpdGVzcGFjZXMpO2xldCBzPXQuZW5jYXBzdWxhdGlvbjt2b2lkIDA9PT1zJiYocz1udWxsIT09ciYmdm9pZCAwIT09ci5kZWZhdWx0RW5jYXBzdWxhdGlvbj9yLmRlZmF1bHRFbmNhcHN1bGF0aW9uOkphLkVtdWxhdGVkKTtsZXQgYT10LnRlbXBsYXRlVXJsfHxgbmc6Ly8vJHtuLm5hbWV9L3RlbXBsYXRlLmh0bWxgLGw9ey4uLlFZKG4sdCksdHlwZVNvdXJjZVNwYW46aS5jcmVhdGVQYXJzZVNvdXJjZVNwYW4oIkNvbXBvbmVudCIsbi5uYW1lLGEpLHRlbXBsYXRlOnQudGVtcGxhdGV8fCIiLHByZXNlcnZlV2hpdGVzcGFjZXM6byxzdHlsZXM6dC5zdHlsZXN8fFFpLGFuaW1hdGlvbnM6dC5hbmltYXRpb25zLGRlY2xhcmF0aW9uczpbXSxjaGFuZ2VEZXRlY3Rpb246dC5jaGFuZ2VEZXRlY3Rpb24sZW5jYXBzdWxhdGlvbjpzLGludGVycG9sYXRpb246dC5pbnRlcnBvbGF0aW9uLHZpZXdQcm92aWRlcnM6dC52aWV3UHJvdmlkZXJzfHxudWxsLGlzU3RhbmRhbG9uZTohIXQuc3RhbmRhbG9uZX07JE4rKzt0cnl7aWYobC51c2VzSW5oZXJpdGFuY2UmJktZKG4pLGU9aS5jb21waWxlQ29tcG9uZW50KEhwLGEsbCksdC5zdGFuZGFsb25lKXtsZXQgYz1GZCh0LmltcG9ydHN8fFFpKSx7ZGlyZWN0aXZlRGVmczp1LHBpcGVEZWZzOmR9PWZ1bmN0aW9uKG4sdCl7bGV0IGU9bnVsbCxpPW51bGw7cmV0dXJue2RpcmVjdGl2ZURlZnM6KCk9PntpZihudWxsPT09ZSl7ZT1bTmwobildO2xldCBzPW5ldyBTZXQ7Zm9yKGxldCBhIG9mIHQpe2xldCBsPUtpKGEpO2lmKCFzLmhhcyhsKSlpZihzLmFkZChsKSxJMChsKSl7bGV0IGM9TDAobCk7Zm9yKGxldCB1IG9mIGMuZXhwb3J0ZWQuZGlyZWN0aXZlcyl7bGV0IGQ9TmwodSl8fEdmKHUpO2QmJiFzLmhhcyh1KSYmKHMuYWRkKHUpLGUucHVzaChkKSl9fWVsc2V7bGV0IGM9TmwobCl8fEdmKGwpO2MmJmUucHVzaChjKX19fXJldHVybiBlfSxwaXBlRGVmczooKT0+e2lmKG51bGw9PT1pKXtpPVtdO2xldCBzPW5ldyBTZXQ7Zm9yKGxldCBhIG9mIHQpe2xldCBsPUtpKGEpO2lmKCFzLmhhcyhsKSlpZihzLmFkZChsKSxJMChsKSl7bGV0IGM9TDAobCk7Zm9yKGxldCB1IG9mIGMuZXhwb3J0ZWQucGlwZXMpe2xldCBkPUxkKHUpO2QmJiFzLmhhcyh1KSYmKHMuYWRkKHUpLGkucHVzaChkKSl9fWVsc2V7bGV0IGM9TGQobCk7YyYmaS5wdXNoKGMpfX19cmV0dXJuIGl9fX0obixjKTtlLmRpcmVjdGl2ZURlZnM9dSxlLnBpcGVEZWZzPWQsZS5kZXBlbmRlbmNpZXM9KCk9PmMubWFwKEtpKX19ZmluYWxseXskTi0tfWlmKDA9PT0kTiYmZnVuY3Rpb24oKXtpZighSk4pe0pOPSEwO3RyeXtmb3IobGV0IG49RjEubGVuZ3RoLTE7bj49MDtuLS0pe2xldHttb2R1bGVUeXBlOnQsbmdNb2R1bGU6ZX09RjFbbl07ZS5kZWNsYXJhdGlvbnMmJmUuZGVjbGFyYXRpb25zLmV2ZXJ5KEdZKSYmKEYxLnNwbGljZShuLDEpLElDZSh0LGUpKX19ZmluYWxseXtKTj0hMX19fSgpLGZ1bmN0aW9uKG4pe3JldHVybiB2b2lkIDAhPT1uLm5nU2VsZWN0b3JTY29wZX0obikpe2xldCBjPUwwKG4ubmdTZWxlY3RvclNjb3BlKTtXWShlLGMpfWlmKHQuc2NoZW1hcyl7aWYoIXQuc3RhbmRhbG9uZSl0aHJvdyBuZXcgRXJyb3IoYFRoZSAnc2NoZW1hcycgd2FzIHNwZWNpZmllZCBmb3IgdGhlICR7bzMobil9IGJ1dCBpcyBvbmx5IHZhbGlkIG9uIGEgY29tcG9uZW50IHRoYXQgaXMgc3RhbmRhbG9uZS5gKTtlLnNjaGVtYXM9dC5zY2hlbWFzfWVsc2UgdC5zdGFuZGFsb25lJiYoZS5zY2hlbWFzPVtdKX1yZXR1cm4gZX0sY29uZmlndXJhYmxlOiExfSl9KG4sdCkpLFZ4KCJQaXBlIixuPT4oe3B1cmU6ITAsLi4ubn0pLHZvaWQgMCx2b2lkIDAsKG4sdCk9PmZ1bmN0aW9uKG4sdCl7bGV0IGU9bnVsbCxpPW51bGw7T2JqZWN0LmRlZmluZVByb3BlcnR5KG4sTmQse2dldDooKT0+e2lmKG51bGw9PT1pKXtsZXQgcj15NyhuLHQpLG89TGwoKTtpPW8uY29tcGlsZUZhY3RvcnkoSHAsYG5nOi8vLyR7ci5uYW1lfS9cdTAyNzVmYWMuanNgLHtuYW1lOnIubmFtZSx0eXBlOnIudHlwZSx0eXBlQXJndW1lbnRDb3VudDowLGRlcHM6ZlQobiksdGFyZ2V0Om8uRmFjdG9yeVRhcmdldC5QaXBlfSl9cmV0dXJuIGl9LGNvbmZpZ3VyYWJsZTohMX0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eShuLGMzLHtnZXQ6KCk9PntpZihudWxsPT09ZSl7bGV0IHI9eTcobix0KTtlPUxsKCkuY29tcGlsZVBpcGUoSHAsYG5nOi8vLyR7ci5uYW1lfS9cdTAyNzVwaXBlLmpzYCxyKX1yZXR1cm4gZX0sY29uZmlndXJhYmxlOiExfSl9KG4sdCkpLFlwKCJJbnB1dCIsbj0+KHtiaW5kaW5nUHJvcGVydHlOYW1lOm59KSksWXAoIk91dHB1dCIsbj0+KHtiaW5kaW5nUHJvcGVydHlOYW1lOm59KSksWXAoIkhvc3RCaW5kaW5nIixuPT4oe2hvc3RQcm9wZXJ0eU5hbWU6bn0pKSxZcCgiSG9zdExpc3RlbmVyIiwobix0KT0+KHtldmVudE5hbWU6bixhcmdzOnR9KSksVngoIk5nTW9kdWxlIixuPT5uLHZvaWQgMCx2b2lkIDAsKG4sdCk9PmZ1bmN0aW9uKG4sdD17fSl7KGZ1bmN0aW9uKG4sdCxlPSExKXtsZXQgaT1GZCh0LmRlY2xhcmF0aW9uc3x8UWkpLHI9bnVsbDtPYmplY3QuZGVmaW5lUHJvcGVydHkobixJNyx7Y29uZmlndXJhYmxlOiEwLGdldDooKT0+KG51bGw9PT1yJiYocj1MbCgpLmNvbXBpbGVOZ01vZHVsZShIcCxgbmc6Ly8vJHtuLm5hbWV9L1x1MDI3NW1vZC5qc2Ase3R5cGU6bixib290c3RyYXA6RmQodC5ib290c3RyYXB8fFFpKS5tYXAoS2kpLGRlY2xhcmF0aW9uczppLm1hcChLaSksaW1wb3J0czpGZCh0LmltcG9ydHN8fFFpKS5tYXAoS2kpLm1hcChfNyksZXhwb3J0czpGZCh0LmV4cG9ydHN8fFFpKS5tYXAoS2kpLm1hcChfNyksc2NoZW1hczp0LnNjaGVtYXM/RmQodC5zY2hlbWFzKTpudWxsLGlkOnQuaWR8fG51bGx9KSxyLnNjaGVtYXN8fChyLnNjaGVtYXM9W10pKSxyKX0pO2xldCBvPW51bGw7T2JqZWN0LmRlZmluZVByb3BlcnR5KG4sTmQse2dldDooKT0+e2lmKG51bGw9PT1vKXtsZXQgYT1MbCgpO289YS5jb21waWxlRmFjdG9yeShIcCxgbmc6Ly8vJHtuLm5hbWV9L1x1MDI3NWZhYy5qc2Ase25hbWU6bi5uYW1lLHR5cGU6bixkZXBzOmZUKG4pLHRhcmdldDphLkZhY3RvcnlUYXJnZXQuTmdNb2R1bGUsdHlwZUFyZ3VtZW50Q291bnQ6MH0pfXJldHVybiBvfSxjb25maWd1cmFibGU6ITF9KTtsZXQgcz1udWxsO09iamVjdC5kZWZpbmVQcm9wZXJ0eShuLG5MLHtnZXQ6KCk9PntpZihudWxsPT09cyl7bGV0IGE9e25hbWU6bi5uYW1lLHR5cGU6bixwcm92aWRlcnM6dC5wcm92aWRlcnN8fFFpLGltcG9ydHM6Wyh0LmltcG9ydHN8fFFpKS5tYXAoS2kpLCh0LmV4cG9ydHN8fFFpKS5tYXAoS2kpXX07cz1MbCgpLmNvbXBpbGVJbmplY3RvcihIcCxgbmc6Ly8vJHtuLm5hbWV9L1x1MDI3NWluai5qc2AsYSl9cmV0dXJuIHN9LGNvbmZpZ3VyYWJsZTohMX0pfSkobix0KSx2b2lkIDAhPT10LmlkJiZtOShuLHQuaWQpLGZ1bmN0aW9uKG4sdCl7RjEucHVzaCh7bW9kdWxlVHlwZTpuLG5nTW9kdWxlOnR9KX0obix0KX0obix0KSk7dmFyICQzPW5ldyBwZSgiQXBwbGljYXRpb24gSW5pdGlhbGl6ZXIiKSxPVD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuYXBwSW5pdHM9ZSx0aGlzLnJlc29sdmU9b1QsdGhpcy5yZWplY3Q9b1QsdGhpcy5pbml0aWFsaXplZD0hMSx0aGlzLmRvbmU9ITEsdGhpcy5kb25lUHJvbWlzZT1uZXcgUHJvbWlzZSgoaSxyKT0+e3RoaXMucmVzb2x2ZT1pLHRoaXMucmVqZWN0PXJ9KX1ydW5Jbml0aWFsaXplcnMoKXtpZih0aGlzLmluaXRpYWxpemVkKXJldHVybjtsZXQgZT1bXSxpPSgpPT57dGhpcy5kb25lPSEwLHRoaXMucmVzb2x2ZSgpfTtpZih0aGlzLmFwcEluaXRzKWZvcihsZXQgcj0wO3I8dGhpcy5hcHBJbml0cy5sZW5ndGg7cisrKXtsZXQgbz10aGlzLmFwcEluaXRzW3JdKCk7aWYobl8obykpZS5wdXNoKG8pO2Vsc2UgaWYoUTMobykpe2xldCBzPW5ldyBQcm9taXNlKChhLGwpPT57by5zdWJzY3JpYmUoe2NvbXBsZXRlOmEsZXJyb3I6bH0pfSk7ZS5wdXNoKHMpfX1Qcm9taXNlLmFsbChlKS50aGVuKCgpPT57aSgpfSkuY2F0Y2gocj0+e3RoaXMucmVqZWN0KHIpfSksMD09PWUubGVuZ3RoJiZpKCksdGhpcy5pbml0aWFsaXplZD0hMH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaigkMyw4KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksJGY9bmV3IHBlKCJBcHBJZCIse3Byb3ZpZGVkSW46InJvb3QiLGZhY3Rvcnk6ZnVuY3Rpb24oKXtyZXR1cm5gJHtlTCgpfSR7ZUwoKX0ke2VMKCl9YH19KTtmdW5jdGlvbiBlTCgpe3JldHVybiBTdHJpbmcuZnJvbUNoYXJDb2RlKDk3K01hdGguZmxvb3IoMjUqTWF0aC5yYW5kb20oKSkpfXZhciBlQj1uZXcgcGUoIlBsYXRmb3JtIEluaXRpYWxpemVyIiksR2Q9bmV3IHBlKCJQbGF0Zm9ybSBJRCIse3Byb3ZpZGVkSW46InBsYXRmb3JtIixmYWN0b3J5OigpPT4idW5rbm93biJ9KSxHQ2U9bmV3IHBlKCJhcHBCb290c3RyYXBMaXN0ZW5lciIpLFBpPShuZXcgcGUoIkFwcGxpY2F0aW9uIFBhY2thZ2VzIFJvb3QgVVJMIiksbmV3IHBlKCJBbmltYXRpb25Nb2R1bGVUeXBlIikpLFdkPW5ldyBwZSgiTG9jYWxlSWQiLHtwcm92aWRlZEluOiJyb290IixmYWN0b3J5OigpPT5qbyhXZCxkaS5PcHRpb25hbHxkaS5Ta2lwU2VsZil8fHR5cGVvZiAkbG9jYWxpemU8InUiJiYkbG9jYWxpemUubG9jYWxlfHxuVH0pLFhDZT0obmV3IHBlKCJEZWZhdWx0Q3VycmVuY3lDb2RlIix7cHJvdmlkZWRJbjoicm9vdCIsZmFjdG9yeTooKT0+IlVTRCJ9KSxuZXcgcGUoIlRyYW5zbGF0aW9ucyIpLG5ldyBwZSgiVHJhbnNsYXRpb25zRm9ybWF0IiksbmV3IHBlKCJjb21waWxlck9wdGlvbnMiKSxQcm9taXNlLnJlc29sdmUoMCkpO2Z1bmN0aW9uIEtMKG4pe3R5cGVvZiBab25lPiJ1Ij9YQ2UudGhlbigoKT0+e24mJm4uYXBwbHkobnVsbCxudWxsKX0pOlpvbmUuY3VycmVudC5zY2hlZHVsZU1pY3JvVGFzaygic2NoZWR1bGVNaWNyb3Rhc2siLG4pfXZhciBfdD1jbGFzc3tjb25zdHJ1Y3Rvcih7ZW5hYmxlTG9uZ1N0YWNrVHJhY2U6dD0hMSxzaG91bGRDb2FsZXNjZUV2ZW50Q2hhbmdlRGV0ZWN0aW9uOmU9ITEsc2hvdWxkQ29hbGVzY2VSdW5DaGFuZ2VEZXRlY3Rpb246aT0hMX0pe2lmKHRoaXMuaGFzUGVuZGluZ01hY3JvdGFza3M9ITEsdGhpcy5oYXNQZW5kaW5nTWljcm90YXNrcz0hMSx0aGlzLmlzU3RhYmxlPSEwLHRoaXMub25VbnN0YWJsZT1uZXcgRyghMSksdGhpcy5vbk1pY3JvdGFza0VtcHR5PW5ldyBHKCExKSx0aGlzLm9uU3RhYmxlPW5ldyBHKCExKSx0aGlzLm9uRXJyb3I9bmV3IEcoITEpLHR5cGVvZiBab25lPiJ1Iil0aHJvdyBuZXcgQXQoOTA4LCExKTtab25lLmFzc2VydFpvbmVQYXRjaGVkKCk7bGV0IHI9dGhpcztpZihyLl9uZXN0aW5nPTAsci5fb3V0ZXI9ci5faW5uZXI9Wm9uZS5jdXJyZW50LFpvbmUuQXN5bmNTdGFja1RhZ2dpbmdab25lU3BlYyl7bGV0IG89Wm9uZS5Bc3luY1N0YWNrVGFnZ2luZ1pvbmVTcGVjO3IuX2lubmVyPXIuX2lubmVyLmZvcmsobmV3IG8oIkFuZ3VsYXIiKSl9Wm9uZS5UYXNrVHJhY2tpbmdab25lU3BlYyYmKHIuX2lubmVyPXIuX2lubmVyLmZvcmsobmV3IFpvbmUuVGFza1RyYWNraW5nWm9uZVNwZWMpKSx0JiZab25lLmxvbmdTdGFja1RyYWNlWm9uZVNwZWMmJihyLl9pbm5lcj1yLl9pbm5lci5mb3JrKFpvbmUubG9uZ1N0YWNrVHJhY2Vab25lU3BlYykpLHIuc2hvdWxkQ29hbGVzY2VFdmVudENoYW5nZURldGVjdGlvbj0haSYmZSxyLnNob3VsZENvYWxlc2NlUnVuQ2hhbmdlRGV0ZWN0aW9uPWksci5sYXN0UmVxdWVzdEFuaW1hdGlvbkZyYW1lSWQ9LTEsci5uYXRpdmVSZXF1ZXN0QW5pbWF0aW9uRnJhbWU9ZnVuY3Rpb24oKXtsZXQgbj10by5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUsdD10by5jYW5jZWxBbmltYXRpb25GcmFtZTtpZih0eXBlb2YgWm9uZTwidSImJm4mJnQpe2xldCBlPW5bWm9uZS5fX3N5bWJvbF9fKCJPcmlnaW5hbERlbGVnYXRlIildO2UmJihuPWUpO2xldCBpPXRbWm9uZS5fX3N5bWJvbF9fKCJPcmlnaW5hbERlbGVnYXRlIildO2kmJih0PWkpfXJldHVybntuYXRpdmVSZXF1ZXN0QW5pbWF0aW9uRnJhbWU6bixuYXRpdmVDYW5jZWxBbmltYXRpb25GcmFtZTp0fX0oKS5uYXRpdmVSZXF1ZXN0QW5pbWF0aW9uRnJhbWUsZnVuY3Rpb24obil7bGV0IHQ9KCk9PnshZnVuY3Rpb24obil7bi5pc0NoZWNrU3RhYmxlUnVubmluZ3x8LTEhPT1uLmxhc3RSZXF1ZXN0QW5pbWF0aW9uRnJhbWVJZHx8KG4ubGFzdFJlcXVlc3RBbmltYXRpb25GcmFtZUlkPW4ubmF0aXZlUmVxdWVzdEFuaW1hdGlvbkZyYW1lLmNhbGwodG8sKCk9PntuLmZha2VUb3BFdmVudFRhc2t8fChuLmZha2VUb3BFdmVudFRhc2s9Wm9uZS5yb290LnNjaGVkdWxlRXZlbnRUYXNrKCJmYWtlVG9wRXZlbnRUYXNrIiwoKT0+e24ubGFzdFJlcXVlc3RBbmltYXRpb25GcmFtZUlkPS0xLFpMKG4pLG4uaXNDaGVja1N0YWJsZVJ1bm5pbmc9ITAsdEIobiksbi5pc0NoZWNrU3RhYmxlUnVubmluZz0hMX0sdm9pZCAwLCgpPT57fSwoKT0+e30pKSxuLmZha2VUb3BFdmVudFRhc2suaW52b2tlKCl9KSxaTChuKSl9KG4pfTtuLl9pbm5lcj1uLl9pbm5lci5mb3JrKHtuYW1lOiJhbmd1bGFyIixwcm9wZXJ0aWVzOntpc0FuZ3VsYXJab25lOiEwfSxvbkludm9rZVRhc2s6KGUsaSxyLG8scyxhKT0+e3RyeXtyZXR1cm4gYjcobiksZS5pbnZva2VUYXNrKHIsbyxzLGEpfWZpbmFsbHl7KG4uc2hvdWxkQ29hbGVzY2VFdmVudENoYW5nZURldGVjdGlvbiYmImV2ZW50VGFzayI9PT1vLnR5cGV8fG4uc2hvdWxkQ29hbGVzY2VSdW5DaGFuZ2VEZXRlY3Rpb24pJiZ0KCkseDcobil9fSxvbkludm9rZTooZSxpLHIsbyxzLGEsbCk9Pnt0cnl7cmV0dXJuIGI3KG4pLGUuaW52b2tlKHIsbyxzLGEsbCl9ZmluYWxseXtuLnNob3VsZENvYWxlc2NlUnVuQ2hhbmdlRGV0ZWN0aW9uJiZ0KCkseDcobil9fSxvbkhhc1Rhc2s6KGUsaSxyLG8pPT57ZS5oYXNUYXNrKHIsbyksaT09PXImJigibWljcm9UYXNrIj09by5jaGFuZ2U/KG4uX2hhc1BlbmRpbmdNaWNyb3Rhc2tzPW8ubWljcm9UYXNrLFpMKG4pLHRCKG4pKToibWFjcm9UYXNrIj09by5jaGFuZ2UmJihuLmhhc1BlbmRpbmdNYWNyb3Rhc2tzPW8ubWFjcm9UYXNrKSl9LG9uSGFuZGxlRXJyb3I6KGUsaSxyLG8pPT4oZS5oYW5kbGVFcnJvcihyLG8pLG4ucnVuT3V0c2lkZUFuZ3VsYXIoKCk9Pm4ub25FcnJvci5lbWl0KG8pKSwhMSl9KX0ocil9c3RhdGljIGlzSW5Bbmd1bGFyWm9uZSgpe3JldHVybiB0eXBlb2YgWm9uZTwidSImJiEwPT09Wm9uZS5jdXJyZW50LmdldCgiaXNBbmd1bGFyWm9uZSIpfXN0YXRpYyBhc3NlcnRJbkFuZ3VsYXJab25lKCl7aWYoIV90LmlzSW5Bbmd1bGFyWm9uZSgpKXRocm93IG5ldyBBdCg5MDksITEpfXN0YXRpYyBhc3NlcnROb3RJbkFuZ3VsYXJab25lKCl7aWYoX3QuaXNJbkFuZ3VsYXJab25lKCkpdGhyb3cgbmV3IEF0KDkwOSwhMSl9cnVuKHQsZSxpKXtyZXR1cm4gdGhpcy5faW5uZXIucnVuKHQsZSxpKX1ydW5UYXNrKHQsZSxpLHIpe2xldCBvPXRoaXMuX2lubmVyLHM9by5zY2hlZHVsZUV2ZW50VGFzaygiTmdab25lRXZlbnQ6ICIrcix0LEtDZSxvVCxvVCk7dHJ5e3JldHVybiBvLnJ1blRhc2socyxlLGkpfWZpbmFsbHl7by5jYW5jZWxUYXNrKHMpfX1ydW5HdWFyZGVkKHQsZSxpKXtyZXR1cm4gdGhpcy5faW5uZXIucnVuR3VhcmRlZCh0LGUsaSl9cnVuT3V0c2lkZUFuZ3VsYXIodCl7cmV0dXJuIHRoaXMuX291dGVyLnJ1bih0KX19LEtDZT17fTtmdW5jdGlvbiB0QihuKXtpZigwPT1uLl9uZXN0aW5nJiYhbi5oYXNQZW5kaW5nTWljcm90YXNrcyYmIW4uaXNTdGFibGUpdHJ5e24uX25lc3RpbmcrKyxuLm9uTWljcm90YXNrRW1wdHkuZW1pdChudWxsKX1maW5hbGx5e2lmKG4uX25lc3RpbmctLSwhbi5oYXNQZW5kaW5nTWljcm90YXNrcyl0cnl7bi5ydW5PdXRzaWRlQW5ndWxhcigoKT0+bi5vblN0YWJsZS5lbWl0KG51bGwpKX1maW5hbGx5e24uaXNTdGFibGU9ITB9fX1mdW5jdGlvbiBaTChuKXtuLmhhc1BlbmRpbmdNaWNyb3Rhc2tzPSEhKG4uX2hhc1BlbmRpbmdNaWNyb3Rhc2tzfHwobi5zaG91bGRDb2FsZXNjZUV2ZW50Q2hhbmdlRGV0ZWN0aW9ufHxuLnNob3VsZENvYWxlc2NlUnVuQ2hhbmdlRGV0ZWN0aW9uKSYmLTEhPT1uLmxhc3RSZXF1ZXN0QW5pbWF0aW9uRnJhbWVJZCl9ZnVuY3Rpb24gYjcobil7bi5fbmVzdGluZysrLG4uaXNTdGFibGUmJihuLmlzU3RhYmxlPSExLG4ub25VbnN0YWJsZS5lbWl0KG51bGwpKX1mdW5jdGlvbiB4NyhuKXtuLl9uZXN0aW5nLS0sdEIobil9dmFyIGlCLG5CPW5ldyBwZSgiIiksZUM9bmV3IHBlKCIiKSxrVD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLl9uZ1pvbmU9ZSx0aGlzLnJlZ2lzdHJ5PWksdGhpcy5fcGVuZGluZ0NvdW50PTAsdGhpcy5faXNab25lU3RhYmxlPSEwLHRoaXMuX2RpZFdvcms9ITEsdGhpcy5fY2FsbGJhY2tzPVtdLHRoaXMudGFza1RyYWNraW5nWm9uZT1udWxsLGlCfHwoZnVuY3Rpb24obil7aUI9bn0ociksci5hZGRUb1dpbmRvdyhpKSksdGhpcy5fd2F0Y2hBbmd1bGFyRXZlbnRzKCksZS5ydW4oKCk9Pnt0aGlzLnRhc2tUcmFja2luZ1pvbmU9dHlwZW9mIFpvbmU+InUiP251bGw6Wm9uZS5jdXJyZW50LmdldCgiVGFza1RyYWNraW5nWm9uZSIpfSl9X3dhdGNoQW5ndWxhckV2ZW50cygpe3RoaXMuX25nWm9uZS5vblVuc3RhYmxlLnN1YnNjcmliZSh7bmV4dDooKT0+e3RoaXMuX2RpZFdvcms9ITAsdGhpcy5faXNab25lU3RhYmxlPSExfX0pLHRoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e3RoaXMuX25nWm9uZS5vblN0YWJsZS5zdWJzY3JpYmUoe25leHQ6KCk9PntfdC5hc3NlcnROb3RJbkFuZ3VsYXJab25lKCksS0woKCk9Pnt0aGlzLl9pc1pvbmVTdGFibGU9ITAsdGhpcy5fcnVuQ2FsbGJhY2tzSWZSZWFkeSgpfSl9fSl9KX1pbmNyZWFzZVBlbmRpbmdSZXF1ZXN0Q291bnQoKXtyZXR1cm4gdGhpcy5fcGVuZGluZ0NvdW50Kz0xLHRoaXMuX2RpZFdvcms9ITAsdGhpcy5fcGVuZGluZ0NvdW50fWRlY3JlYXNlUGVuZGluZ1JlcXVlc3RDb3VudCgpe2lmKHRoaXMuX3BlbmRpbmdDb3VudC09MSx0aGlzLl9wZW5kaW5nQ291bnQ8MCl0aHJvdyBuZXcgRXJyb3IoInBlbmRpbmcgYXN5bmMgcmVxdWVzdHMgYmVsb3cgemVybyIpO3JldHVybiB0aGlzLl9ydW5DYWxsYmFja3NJZlJlYWR5KCksdGhpcy5fcGVuZGluZ0NvdW50fWlzU3RhYmxlKCl7cmV0dXJuIHRoaXMuX2lzWm9uZVN0YWJsZSYmMD09PXRoaXMuX3BlbmRpbmdDb3VudCYmIXRoaXMuX25nWm9uZS5oYXNQZW5kaW5nTWFjcm90YXNrc31fcnVuQ2FsbGJhY2tzSWZSZWFkeSgpe2lmKHRoaXMuaXNTdGFibGUoKSlLTCgoKT0+e2Zvcig7MCE9PXRoaXMuX2NhbGxiYWNrcy5sZW5ndGg7KXtsZXQgZT10aGlzLl9jYWxsYmFja3MucG9wKCk7Y2xlYXJUaW1lb3V0KGUudGltZW91dElkKSxlLmRvbmVDYih0aGlzLl9kaWRXb3JrKX10aGlzLl9kaWRXb3JrPSExfSk7ZWxzZXtsZXQgZT10aGlzLmdldFBlbmRpbmdUYXNrcygpO3RoaXMuX2NhbGxiYWNrcz10aGlzLl9jYWxsYmFja3MuZmlsdGVyKGk9PiFpLnVwZGF0ZUNifHwhaS51cGRhdGVDYihlKXx8KGNsZWFyVGltZW91dChpLnRpbWVvdXRJZCksITEpKSx0aGlzLl9kaWRXb3JrPSEwfX1nZXRQZW5kaW5nVGFza3MoKXtyZXR1cm4gdGhpcy50YXNrVHJhY2tpbmdab25lP3RoaXMudGFza1RyYWNraW5nWm9uZS5tYWNyb1Rhc2tzLm1hcChlPT4oe3NvdXJjZTplLnNvdXJjZSxjcmVhdGlvbkxvY2F0aW9uOmUuY3JlYXRpb25Mb2NhdGlvbixkYXRhOmUuZGF0YX0pKTpbXX1hZGRDYWxsYmFjayhlLGkscil7bGV0IG89LTE7aSYmaT4wJiYobz1zZXRUaW1lb3V0KCgpPT57dGhpcy5fY2FsbGJhY2tzPXRoaXMuX2NhbGxiYWNrcy5maWx0ZXIocz0+cy50aW1lb3V0SWQhPT1vKSxlKHRoaXMuX2RpZFdvcmssdGhpcy5nZXRQZW5kaW5nVGFza3MoKSl9LGkpKSx0aGlzLl9jYWxsYmFja3MucHVzaCh7ZG9uZUNiOmUsdGltZW91dElkOm8sdXBkYXRlQ2I6cn0pfXdoZW5TdGFibGUoZSxpLHIpe2lmKHImJiF0aGlzLnRhc2tUcmFja2luZ1pvbmUpdGhyb3cgbmV3IEVycm9yKCdUYXNrIHRyYWNraW5nIHpvbmUgaXMgcmVxdWlyZWQgd2hlbiBwYXNzaW5nIGFuIHVwZGF0ZSBjYWxsYmFjayB0byB3aGVuU3RhYmxlKCkuIElzICJ6b25lLmpzL3BsdWdpbnMvdGFzay10cmFja2luZyIgbG9hZGVkPycpO3RoaXMuYWRkQ2FsbGJhY2soZSxpLHIpLHRoaXMuX3J1bkNhbGxiYWNrc0lmUmVhZHkoKX1nZXRQZW5kaW5nUmVxdWVzdENvdW50KCl7cmV0dXJuIHRoaXMuX3BlbmRpbmdDb3VudH1yZWdpc3RlckFwcGxpY2F0aW9uKGUpe3RoaXMucmVnaXN0cnkucmVnaXN0ZXJBcHBsaWNhdGlvbihlLHRoaXMpfXVucmVnaXN0ZXJBcHBsaWNhdGlvbihlKXt0aGlzLnJlZ2lzdHJ5LnVucmVnaXN0ZXJBcHBsaWNhdGlvbihlKX1maW5kUHJvdmlkZXJzKGUsaSxyKXtyZXR1cm5bXX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihfdCksaihGVCksaihlQykpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLEZUPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLl9hcHBsaWNhdGlvbnM9bmV3IE1hcH1yZWdpc3RlckFwcGxpY2F0aW9uKGUsaSl7dGhpcy5fYXBwbGljYXRpb25zLnNldChlLGkpfXVucmVnaXN0ZXJBcHBsaWNhdGlvbihlKXt0aGlzLl9hcHBsaWNhdGlvbnMuZGVsZXRlKGUpfXVucmVnaXN0ZXJBbGxBcHBsaWNhdGlvbnMoKXt0aGlzLl9hcHBsaWNhdGlvbnMuY2xlYXIoKX1nZXRUZXN0YWJpbGl0eShlKXtyZXR1cm4gdGhpcy5fYXBwbGljYXRpb25zLmdldChlKXx8bnVsbH1nZXRBbGxUZXN0YWJpbGl0aWVzKCl7cmV0dXJuIEFycmF5LmZyb20odGhpcy5fYXBwbGljYXRpb25zLnZhbHVlcygpKX1nZXRBbGxSb290RWxlbWVudHMoKXtyZXR1cm4gQXJyYXkuZnJvbSh0aGlzLl9hcHBsaWNhdGlvbnMua2V5cygpKX1maW5kVGVzdGFiaWxpdHlJblRyZWUoZSxpPSEwKXtyZXR1cm4gaUI/LmZpbmRUZXN0YWJpbGl0eUluVHJlZSh0aGlzLGUsaSk/P251bGx9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJwbGF0Zm9ybSJ9KSxufSkoKSx4eD1udWxsLHRYPW5ldyBwZSgiQWxsb3dNdWx0aXBsZVRva2VuIiksblg9bmV3IHBlKCJQbGF0Zm9ybURlc3Ryb3lMaXN0ZW5lcnMiKTtmdW5jdGlvbiByQihuLHQsZT1bXSl7bGV0IGk9YFBsYXRmb3JtOiAke3R9YCxyPW5ldyBwZShpKTtyZXR1cm4obz1bXSk9PntsZXQgcz1pWCgpO2lmKCFzfHxzLmluamVjdG9yLmdldCh0WCwhMSkpe2xldCBhPVsuLi5lLC4uLm8se3Byb3ZpZGU6cix1c2VWYWx1ZTohMH1dO24/bihhKTpmdW5jdGlvbihuKXtpZih4eCYmIXh4LmdldCh0WCwhMSkpdGhyb3cgbmV3IEF0KDQwMCwhMSk7eHg9bjtsZXQgdD1uLmdldChyWCk7KGZ1bmN0aW9uKG4pe2xldCB0PW4uZ2V0KGVCLG51bGwpO3QmJnQuZm9yRWFjaChlPT5lKCkpfSkobil9KGZ1bmN0aW9uKG49W10sdCl7cmV0dXJuIFhuLmNyZWF0ZSh7bmFtZTp0LHByb3ZpZGVyczpbe3Byb3ZpZGU6Z1QsdXNlVmFsdWU6InBsYXRmb3JtIn0se3Byb3ZpZGU6blgsdXNlVmFsdWU6bmV3IFNldChbKCk9Pnh4PW51bGxdKX0sLi4ubl19KX0oYSxpKSl9cmV0dXJuIGZ1bmN0aW9uKG4pe2xldCB0PWlYKCk7aWYoIXQpdGhyb3cgbmV3IEF0KDQwMSwhMSk7cmV0dXJuIHR9KCl9fWZ1bmN0aW9uIGlYKCl7cmV0dXJuIHh4Py5nZXQoclgpPz9udWxsfXZhciByWD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuX2luamVjdG9yPWUsdGhpcy5fbW9kdWxlcz1bXSx0aGlzLl9kZXN0cm95TGlzdGVuZXJzPVtdLHRoaXMuX2Rlc3Ryb3llZD0hMX1ib290c3RyYXBNb2R1bGVGYWN0b3J5KGUsaSl7bGV0IHI9ZnVuY3Rpb24obix0KXtsZXQgZTtyZXR1cm4gZT0ibm9vcCI9PT1uP25ldyBjbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuaGFzUGVuZGluZ01pY3JvdGFza3M9ITEsdGhpcy5oYXNQZW5kaW5nTWFjcm90YXNrcz0hMSx0aGlzLmlzU3RhYmxlPSEwLHRoaXMub25VbnN0YWJsZT1uZXcgRyx0aGlzLm9uTWljcm90YXNrRW1wdHk9bmV3IEcsdGhpcy5vblN0YWJsZT1uZXcgRyx0aGlzLm9uRXJyb3I9bmV3IEd9cnVuKHQsZSxpKXtyZXR1cm4gdC5hcHBseShlLGkpfXJ1bkd1YXJkZWQodCxlLGkpe3JldHVybiB0LmFwcGx5KGUsaSl9cnVuT3V0c2lkZUFuZ3VsYXIodCl7cmV0dXJuIHQoKX1ydW5UYXNrKHQsZSxpLHIpe3JldHVybiB0LmFwcGx5KGUsaSl9fTooInpvbmUuanMiPT09bj92b2lkIDA6bil8fG5ldyBfdCh0KSxlfShpPy5uZ1pvbmUsZnVuY3Rpb24obil7cmV0dXJue2VuYWJsZUxvbmdTdGFja1RyYWNlOiExLHNob3VsZENvYWxlc2NlRXZlbnRDaGFuZ2VEZXRlY3Rpb246ISghbnx8IW4ubmdab25lRXZlbnRDb2FsZXNjaW5nKXx8ITEsc2hvdWxkQ29hbGVzY2VSdW5DaGFuZ2VEZXRlY3Rpb246ISghbnx8IW4ubmdab25lUnVuQ29hbGVzY2luZyl8fCExfX0oaSkpLG89W3twcm92aWRlOl90LHVzZVZhbHVlOnJ9XTtyZXR1cm4gci5ydW4oKCk9PntsZXQgcz1Ybi5jcmVhdGUoe3Byb3ZpZGVyczpvLHBhcmVudDp0aGlzLmluamVjdG9yLG5hbWU6ZS5tb2R1bGVUeXBlLm5hbWV9KSxhPWUuY3JlYXRlKHMpLGw9YS5pbmplY3Rvci5nZXQoUXMsbnVsbCk7aWYoIWwpdGhyb3cgbmV3IEF0KDQwMiwhMSk7cmV0dXJuIHIucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntsZXQgYz1yLm9uRXJyb3Iuc3Vic2NyaWJlKHtuZXh0OnU9PntsLmhhbmRsZUVycm9yKHUpfX0pO2Eub25EZXN0cm95KCgpPT57TjEodGhpcy5fbW9kdWxlcyxhKSxjLnVuc3Vic2NyaWJlKCl9KX0pLGZ1bmN0aW9uKG4sdCxlKXt0cnl7bGV0IGk9ZSgpO3JldHVybiBuXyhpKT9pLmNhdGNoKHI9Pnt0aHJvdyB0LnJ1bk91dHNpZGVBbmd1bGFyKCgpPT5uLmhhbmRsZUVycm9yKHIpKSxyfSk6aX1jYXRjaChpKXt0aHJvdyB0LnJ1bk91dHNpZGVBbmd1bGFyKCgpPT5uLmhhbmRsZUVycm9yKGkpKSxpfX0obCxyLCgpPT57bGV0IGM9YS5pbmplY3Rvci5nZXQoT1QpO3JldHVybiBjLnJ1bkluaXRpYWxpemVycygpLGMuZG9uZVByb21pc2UudGhlbigoKT0+KGZ1bmN0aW9uKG4peyhmdW5jdGlvbihuLHQpe251bGw9PW4mJlQ3KCJFeHBlY3RlZCBsb2NhbGVJZCB0byBiZSBkZWZpbmVkIixuLG51bGwsIiE9Iil9KShuKSwic3RyaW5nIj09dHlwZW9mIG4mJihnWT1uLnRvTG93ZXJDYXNlKCkucmVwbGFjZSgvXy9nLCItIikpfShhLmluamVjdG9yLmdldChXZCxuVCl8fG5UKSx0aGlzLl9tb2R1bGVEb0Jvb3RzdHJhcChhKSxhKSl9KX0pfWJvb3RzdHJhcE1vZHVsZShlLGk9W10pe2xldCByPW9YKHt9LGkpO3JldHVybiBmdW5jdGlvbihuLHQsZSl7bGV0IGk9bmV3IHFMKGUpO3JldHVybiBQcm9taXNlLnJlc29sdmUoaSl9KDAsMCxlKS50aGVuKG89PnRoaXMuYm9vdHN0cmFwTW9kdWxlRmFjdG9yeShvLHIpKX1fbW9kdWxlRG9Cb290c3RyYXAoZSl7bGV0IGk9ZS5pbmplY3Rvci5nZXQoSXUpO2lmKGUuX2Jvb3RzdHJhcENvbXBvbmVudHMubGVuZ3RoPjApZS5fYm9vdHN0cmFwQ29tcG9uZW50cy5mb3JFYWNoKHI9PmkuYm9vdHN0cmFwKHIpKTtlbHNle2lmKCFlLmluc3RhbmNlLm5nRG9Cb290c3RyYXApdGhyb3cgbmV3IEF0KDQwMywhMSk7ZS5pbnN0YW5jZS5uZ0RvQm9vdHN0cmFwKGkpfXRoaXMuX21vZHVsZXMucHVzaChlKX1vbkRlc3Ryb3koZSl7dGhpcy5fZGVzdHJveUxpc3RlbmVycy5wdXNoKGUpfWdldCBpbmplY3Rvcigpe3JldHVybiB0aGlzLl9pbmplY3Rvcn1kZXN0cm95KCl7aWYodGhpcy5fZGVzdHJveWVkKXRocm93IG5ldyBBdCg0MDQsITEpO3RoaXMuX21vZHVsZXMuc2xpY2UoKS5mb3JFYWNoKGk9PmkuZGVzdHJveSgpKSx0aGlzLl9kZXN0cm95TGlzdGVuZXJzLmZvckVhY2goaT0+aSgpKTtsZXQgZT10aGlzLl9pbmplY3Rvci5nZXQoblgsbnVsbCk7ZSYmKGUuZm9yRWFjaChpPT5pKCkpLGUuY2xlYXIoKSksdGhpcy5fZGVzdHJveWVkPSEwfWdldCBkZXN0cm95ZWQoKXtyZXR1cm4gdGhpcy5fZGVzdHJveWVkfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKFhuKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InBsYXRmb3JtIn0pLG59KSgpO2Z1bmN0aW9uIG9YKG4sdCl7cmV0dXJuIEFycmF5LmlzQXJyYXkodCk/dC5yZWR1Y2Uob1gsbik6ey4uLm4sLi4udH19dmFyIEl1PSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIpe3RoaXMuX3pvbmU9ZSx0aGlzLl9pbmplY3Rvcj1pLHRoaXMuX2V4Y2VwdGlvbkhhbmRsZXI9cix0aGlzLl9ib290c3RyYXBMaXN0ZW5lcnM9W10sdGhpcy5fdmlld3M9W10sdGhpcy5fcnVubmluZ1RpY2s9ITEsdGhpcy5fc3RhYmxlPSEwLHRoaXMuX2Rlc3Ryb3llZD0hMSx0aGlzLl9kZXN0cm95TGlzdGVuZXJzPVtdLHRoaXMuY29tcG9uZW50VHlwZXM9W10sdGhpcy5jb21wb25lbnRzPVtdLHRoaXMuX29uTWljcm90YXNrRW1wdHlTdWJzY3JpcHRpb249dGhpcy5fem9uZS5vbk1pY3JvdGFza0VtcHR5LnN1YnNjcmliZSh7bmV4dDooKT0+e3RoaXMuX3pvbmUucnVuKCgpPT57dGhpcy50aWNrKCl9KX19KTtsZXQgbz1uZXcgdW4oYT0+e3RoaXMuX3N0YWJsZT10aGlzLl96b25lLmlzU3RhYmxlJiYhdGhpcy5fem9uZS5oYXNQZW5kaW5nTWFjcm90YXNrcyYmIXRoaXMuX3pvbmUuaGFzUGVuZGluZ01pY3JvdGFza3MsdGhpcy5fem9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e2EubmV4dCh0aGlzLl9zdGFibGUpLGEuY29tcGxldGUoKX0pfSkscz1uZXcgdW4oYT0+e2xldCBsO3RoaXMuX3pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntsPXRoaXMuX3pvbmUub25TdGFibGUuc3Vic2NyaWJlKCgpPT57X3QuYXNzZXJ0Tm90SW5Bbmd1bGFyWm9uZSgpLEtMKCgpPT57IXRoaXMuX3N0YWJsZSYmIXRoaXMuX3pvbmUuaGFzUGVuZGluZ01hY3JvdGFza3MmJiF0aGlzLl96b25lLmhhc1BlbmRpbmdNaWNyb3Rhc2tzJiYodGhpcy5fc3RhYmxlPSEwLGEubmV4dCghMCkpfSl9KX0pO2xldCBjPXRoaXMuX3pvbmUub25VbnN0YWJsZS5zdWJzY3JpYmUoKCk9PntfdC5hc3NlcnRJbkFuZ3VsYXJab25lKCksdGhpcy5fc3RhYmxlJiYodGhpcy5fc3RhYmxlPSExLHRoaXMuX3pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PnthLm5leHQoITEpfSkpfSk7cmV0dXJuKCk9PntsLnVuc3Vic2NyaWJlKCksYy51bnN1YnNjcmliZSgpfX0pO3RoaXMuaXNTdGFibGU9SnQobyxzLnBpcGUoVHMoKSkpfWdldCBkZXN0cm95ZWQoKXtyZXR1cm4gdGhpcy5fZGVzdHJveWVkfWdldCBpbmplY3Rvcigpe3JldHVybiB0aGlzLl9pbmplY3Rvcn1ib290c3RyYXAoZSxpKXtsZXQgcyxyPWUgaW5zdGFuY2VvZiBKMTtpZighdGhpcy5faW5qZWN0b3IuZ2V0KE9UKS5kb25lKXRocm93IXImJlI3KGUpLG5ldyBBdCg0MDUsZmFsc2UpO3M9cj9lOnRoaXMuX2luamVjdG9yLmdldChncykucmVzb2x2ZUNvbXBvbmVudEZhY3RvcnkoZSksdGhpcy5jb21wb25lbnRUeXBlcy5wdXNoKHMuY29tcG9uZW50VHlwZSk7bGV0IGE9ZnVuY3Rpb24obil7cmV0dXJuIG4uaXNCb3VuZFRvTW9kdWxlfShzKT92b2lkIDA6dGhpcy5faW5qZWN0b3IuZ2V0KEdwKSxjPXMuY3JlYXRlKFhuLk5VTEwsW10saXx8cy5zZWxlY3RvcixhKSx1PWMubG9jYXRpb24ubmF0aXZlRWxlbWVudCxkPWMuaW5qZWN0b3IuZ2V0KG5CLG51bGwpO3JldHVybiBkPy5yZWdpc3RlckFwcGxpY2F0aW9uKHUpLGMub25EZXN0cm95KCgpPT57dGhpcy5kZXRhY2hWaWV3KGMuaG9zdFZpZXcpLE4xKHRoaXMuY29tcG9uZW50cyxjKSxkPy51bnJlZ2lzdGVyQXBwbGljYXRpb24odSl9KSx0aGlzLl9sb2FkQ29tcG9uZW50KGMpLGN9dGljaygpe2lmKHRoaXMuX3J1bm5pbmdUaWNrKXRocm93IG5ldyBBdCgxMDEsITEpO3RyeXt0aGlzLl9ydW5uaW5nVGljaz0hMDtmb3IobGV0IGUgb2YgdGhpcy5fdmlld3MpZS5kZXRlY3RDaGFuZ2VzKCl9Y2F0Y2goZSl7dGhpcy5fem9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+dGhpcy5fZXhjZXB0aW9uSGFuZGxlci5oYW5kbGVFcnJvcihlKSl9ZmluYWxseXt0aGlzLl9ydW5uaW5nVGljaz0hMX19YXR0YWNoVmlldyhlKXtsZXQgaT1lO3RoaXMuX3ZpZXdzLnB1c2goaSksaS5hdHRhY2hUb0FwcFJlZih0aGlzKX1kZXRhY2hWaWV3KGUpe2xldCBpPWU7TjEodGhpcy5fdmlld3MsaSksaS5kZXRhY2hGcm9tQXBwUmVmKCl9X2xvYWRDb21wb25lbnQoZSl7dGhpcy5hdHRhY2hWaWV3KGUuaG9zdFZpZXcpLHRoaXMudGljaygpLHRoaXMuY29tcG9uZW50cy5wdXNoKGUpLHRoaXMuX2luamVjdG9yLmdldChHQ2UsW10pLmNvbmNhdCh0aGlzLl9ib290c3RyYXBMaXN0ZW5lcnMpLmZvckVhY2gocj0+cihlKSl9bmdPbkRlc3Ryb3koKXtpZighdGhpcy5fZGVzdHJveWVkKXRyeXt0aGlzLl9kZXN0cm95TGlzdGVuZXJzLmZvckVhY2goZT0+ZSgpKSx0aGlzLl92aWV3cy5zbGljZSgpLmZvckVhY2goZT0+ZS5kZXN0cm95KCkpLHRoaXMuX29uTWljcm90YXNrRW1wdHlTdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKX1maW5hbGx5e3RoaXMuX2Rlc3Ryb3llZD0hMCx0aGlzLl92aWV3cz1bXSx0aGlzLl9ib290c3RyYXBMaXN0ZW5lcnM9W10sdGhpcy5fZGVzdHJveUxpc3RlbmVycz1bXX19b25EZXN0cm95KGUpe3JldHVybiB0aGlzLl9kZXN0cm95TGlzdGVuZXJzLnB1c2goZSksKCk9Pk4xKHRoaXMuX2Rlc3Ryb3lMaXN0ZW5lcnMsZSl9ZGVzdHJveSgpe2lmKHRoaXMuX2Rlc3Ryb3llZCl0aHJvdyBuZXcgQXQoNDA2LCExKTtsZXQgZT10aGlzLl9pbmplY3RvcjtlLmRlc3Ryb3kmJiFlLmRlc3Ryb3llZCYmZS5kZXN0cm95KCl9Z2V0IHZpZXdDb3VudCgpe3JldHVybiB0aGlzLl92aWV3cy5sZW5ndGh9d2FybklmRGVzdHJveWVkKCl7fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKF90KSxqKGpwKSxqKFFzKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCk7ZnVuY3Rpb24gTjEobix0KXtsZXQgZT1uLmluZGV4T2YodCk7ZT4tMSYmbi5zcGxpY2UoZSwxKX12YXIgc1g9ITAsYVg9ITE7ZnVuY3Rpb24gdEMoKXtyZXR1cm4gYVg9ITAsc1h9dmFyIG5uPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLl9fTkdfRUxFTUVOVF9JRF9fPXVNZSxufSkoKTtmdW5jdGlvbiB1TWUobil7cmV0dXJuIGZ1bmN0aW9uKG4sdCxlKXtpZihoMyhuKSYmIWUpe2xldCBpPXFwKG4uaW5kZXgsdCk7cmV0dXJuIG5ldyBRZihpLGkpfXJldHVybiA0NyZuLnR5cGU/bmV3IFFmKHRbMTZdLHQpOm51bGx9KHpvKCkscnQoKSwxNj09KDE2Jm4pKX12YXIgc1Q9Y2xhc3N7Y29uc3RydWN0b3IoKXt9c3VwcG9ydHModCl7cmV0dXJuIHdUKHQpfWNyZWF0ZSh0KXtyZXR1cm4gbmV3ICRMKHQpfX0scE1lPShuLHQpPT50LCRMPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMubGVuZ3RoPTAsdGhpcy5fbGlua2VkUmVjb3Jkcz1udWxsLHRoaXMuX3VubGlua2VkUmVjb3Jkcz1udWxsLHRoaXMuX3ByZXZpb3VzSXRIZWFkPW51bGwsdGhpcy5faXRIZWFkPW51bGwsdGhpcy5faXRUYWlsPW51bGwsdGhpcy5fYWRkaXRpb25zSGVhZD1udWxsLHRoaXMuX2FkZGl0aW9uc1RhaWw9bnVsbCx0aGlzLl9tb3Zlc0hlYWQ9bnVsbCx0aGlzLl9tb3Zlc1RhaWw9bnVsbCx0aGlzLl9yZW1vdmFsc0hlYWQ9bnVsbCx0aGlzLl9yZW1vdmFsc1RhaWw9bnVsbCx0aGlzLl9pZGVudGl0eUNoYW5nZXNIZWFkPW51bGwsdGhpcy5faWRlbnRpdHlDaGFuZ2VzVGFpbD1udWxsLHRoaXMuX3RyYWNrQnlGbj10fHxwTWV9Zm9yRWFjaEl0ZW0odCl7bGV0IGU7Zm9yKGU9dGhpcy5faXRIZWFkO251bGwhPT1lO2U9ZS5fbmV4dCl0KGUpfWZvckVhY2hPcGVyYXRpb24odCl7bGV0IGU9dGhpcy5faXRIZWFkLGk9dGhpcy5fcmVtb3ZhbHNIZWFkLHI9MCxvPW51bGw7Zm9yKDtlfHxpOyl7bGV0IHM9IWl8fGUmJmUuY3VycmVudEluZGV4PE03KGkscixvKT9lOmksYT1NNyhzLHIsbyksbD1zLmN1cnJlbnRJbmRleDtpZihzPT09aSlyLS0saT1pLl9uZXh0UmVtb3ZlZDtlbHNlIGlmKGU9ZS5fbmV4dCxudWxsPT1zLnByZXZpb3VzSW5kZXgpcisrO2Vsc2V7b3x8KG89W10pO2xldCBjPWEtcix1PWwtcjtpZihjIT11KXtmb3IobGV0IHA9MDtwPGM7cCsrKXtsZXQgaD1wPG8ubGVuZ3RoP29bcF06b1twXT0wLGY9aCtwO3U8PWYmJmY8YyYmKG9bcF09aCsxKX1vW3MucHJldmlvdXNJbmRleF09dS1jfX1hIT09bCYmdChzLGEsbCl9fWZvckVhY2hQcmV2aW91c0l0ZW0odCl7bGV0IGU7Zm9yKGU9dGhpcy5fcHJldmlvdXNJdEhlYWQ7bnVsbCE9PWU7ZT1lLl9uZXh0UHJldmlvdXMpdChlKX1mb3JFYWNoQWRkZWRJdGVtKHQpe2xldCBlO2ZvcihlPXRoaXMuX2FkZGl0aW9uc0hlYWQ7bnVsbCE9PWU7ZT1lLl9uZXh0QWRkZWQpdChlKX1mb3JFYWNoTW92ZWRJdGVtKHQpe2xldCBlO2ZvcihlPXRoaXMuX21vdmVzSGVhZDtudWxsIT09ZTtlPWUuX25leHRNb3ZlZCl0KGUpfWZvckVhY2hSZW1vdmVkSXRlbSh0KXtsZXQgZTtmb3IoZT10aGlzLl9yZW1vdmFsc0hlYWQ7bnVsbCE9PWU7ZT1lLl9uZXh0UmVtb3ZlZCl0KGUpfWZvckVhY2hJZGVudGl0eUNoYW5nZSh0KXtsZXQgZTtmb3IoZT10aGlzLl9pZGVudGl0eUNoYW5nZXNIZWFkO251bGwhPT1lO2U9ZS5fbmV4dElkZW50aXR5Q2hhbmdlKXQoZSl9ZGlmZih0KXtpZihudWxsPT10JiYodD1bXSksIXdUKHQpKXRocm93IG5ldyBBdCg5MDAsITEpO3JldHVybiB0aGlzLmNoZWNrKHQpP3RoaXM6bnVsbH1vbkRlc3Ryb3koKXt9Y2hlY2sodCl7dGhpcy5fcmVzZXQoKTtsZXQgcixvLHMsZT10aGlzLl9pdEhlYWQsaT0hMTtpZihBcnJheS5pc0FycmF5KHQpKXt0aGlzLmxlbmd0aD10Lmxlbmd0aDtmb3IobGV0IGE9MDthPHRoaXMubGVuZ3RoO2ErKylvPXRbYV0scz10aGlzLl90cmFja0J5Rm4oYSxvKSxudWxsIT09ZSYmT2JqZWN0LmlzKGUudHJhY2tCeUlkLHMpPyhpJiYoZT10aGlzLl92ZXJpZnlSZWluc2VydGlvbihlLG8scyxhKSksT2JqZWN0LmlzKGUuaXRlbSxvKXx8dGhpcy5fYWRkSWRlbnRpdHlDaGFuZ2UoZSxvKSk6KGU9dGhpcy5fbWlzbWF0Y2goZSxvLHMsYSksaT0hMCksZT1lLl9uZXh0fWVsc2Ugcj0wLGZ1bmN0aW9uKG4sdCl7aWYoQXJyYXkuaXNBcnJheShuKSlmb3IobGV0IGU9MDtlPG4ubGVuZ3RoO2UrKyl0KG5bZV0pO2Vsc2V7bGV0IGksZT1uW01UKCldKCk7Zm9yKDshKGk9ZS5uZXh0KCkpLmRvbmU7KXQoaS52YWx1ZSl9fSh0LGE9PntzPXRoaXMuX3RyYWNrQnlGbihyLGEpLG51bGwhPT1lJiZPYmplY3QuaXMoZS50cmFja0J5SWQscyk/KGkmJihlPXRoaXMuX3ZlcmlmeVJlaW5zZXJ0aW9uKGUsYSxzLHIpKSxPYmplY3QuaXMoZS5pdGVtLGEpfHx0aGlzLl9hZGRJZGVudGl0eUNoYW5nZShlLGEpKTooZT10aGlzLl9taXNtYXRjaChlLGEscyxyKSxpPSEwKSxlPWUuX25leHQscisrfSksdGhpcy5sZW5ndGg9cjtyZXR1cm4gdGhpcy5fdHJ1bmNhdGUoZSksdGhpcy5jb2xsZWN0aW9uPXQsdGhpcy5pc0RpcnR5fWdldCBpc0RpcnR5KCl7cmV0dXJuIG51bGwhPT10aGlzLl9hZGRpdGlvbnNIZWFkfHxudWxsIT09dGhpcy5fbW92ZXNIZWFkfHxudWxsIT09dGhpcy5fcmVtb3ZhbHNIZWFkfHxudWxsIT09dGhpcy5faWRlbnRpdHlDaGFuZ2VzSGVhZH1fcmVzZXQoKXtpZih0aGlzLmlzRGlydHkpe2xldCB0O2Zvcih0PXRoaXMuX3ByZXZpb3VzSXRIZWFkPXRoaXMuX2l0SGVhZDtudWxsIT09dDt0PXQuX25leHQpdC5fbmV4dFByZXZpb3VzPXQuX25leHQ7Zm9yKHQ9dGhpcy5fYWRkaXRpb25zSGVhZDtudWxsIT09dDt0PXQuX25leHRBZGRlZCl0LnByZXZpb3VzSW5kZXg9dC5jdXJyZW50SW5kZXg7Zm9yKHRoaXMuX2FkZGl0aW9uc0hlYWQ9dGhpcy5fYWRkaXRpb25zVGFpbD1udWxsLHQ9dGhpcy5fbW92ZXNIZWFkO251bGwhPT10O3Q9dC5fbmV4dE1vdmVkKXQucHJldmlvdXNJbmRleD10LmN1cnJlbnRJbmRleDt0aGlzLl9tb3Zlc0hlYWQ9dGhpcy5fbW92ZXNUYWlsPW51bGwsdGhpcy5fcmVtb3ZhbHNIZWFkPXRoaXMuX3JlbW92YWxzVGFpbD1udWxsLHRoaXMuX2lkZW50aXR5Q2hhbmdlc0hlYWQ9dGhpcy5faWRlbnRpdHlDaGFuZ2VzVGFpbD1udWxsfX1fbWlzbWF0Y2godCxlLGkscil7bGV0IG87cmV0dXJuIG51bGw9PT10P289dGhpcy5faXRUYWlsOihvPXQuX3ByZXYsdGhpcy5fcmVtb3ZlKHQpKSxudWxsIT09KHQ9bnVsbD09PXRoaXMuX3VubGlua2VkUmVjb3Jkcz9udWxsOnRoaXMuX3VubGlua2VkUmVjb3Jkcy5nZXQoaSxudWxsKSk/KE9iamVjdC5pcyh0Lml0ZW0sZSl8fHRoaXMuX2FkZElkZW50aXR5Q2hhbmdlKHQsZSksdGhpcy5fcmVpbnNlcnRBZnRlcih0LG8scikpOm51bGwhPT0odD1udWxsPT09dGhpcy5fbGlua2VkUmVjb3Jkcz9udWxsOnRoaXMuX2xpbmtlZFJlY29yZHMuZ2V0KGkscikpPyhPYmplY3QuaXModC5pdGVtLGUpfHx0aGlzLl9hZGRJZGVudGl0eUNoYW5nZSh0LGUpLHRoaXMuX21vdmVBZnRlcih0LG8scikpOnQ9dGhpcy5fYWRkQWZ0ZXIobmV3IGUzKGUsaSksbyxyKSx0fV92ZXJpZnlSZWluc2VydGlvbih0LGUsaSxyKXtsZXQgbz1udWxsPT09dGhpcy5fdW5saW5rZWRSZWNvcmRzP251bGw6dGhpcy5fdW5saW5rZWRSZWNvcmRzLmdldChpLG51bGwpO3JldHVybiBudWxsIT09bz90PXRoaXMuX3JlaW5zZXJ0QWZ0ZXIobyx0Ll9wcmV2LHIpOnQuY3VycmVudEluZGV4IT1yJiYodC5jdXJyZW50SW5kZXg9cix0aGlzLl9hZGRUb01vdmVzKHQscikpLHR9X3RydW5jYXRlKHQpe2Zvcig7bnVsbCE9PXQ7KXtsZXQgZT10Ll9uZXh0O3RoaXMuX2FkZFRvUmVtb3ZhbHModGhpcy5fdW5saW5rKHQpKSx0PWV9bnVsbCE9PXRoaXMuX3VubGlua2VkUmVjb3JkcyYmdGhpcy5fdW5saW5rZWRSZWNvcmRzLmNsZWFyKCksbnVsbCE9PXRoaXMuX2FkZGl0aW9uc1RhaWwmJih0aGlzLl9hZGRpdGlvbnNUYWlsLl9uZXh0QWRkZWQ9bnVsbCksbnVsbCE9PXRoaXMuX21vdmVzVGFpbCYmKHRoaXMuX21vdmVzVGFpbC5fbmV4dE1vdmVkPW51bGwpLG51bGwhPT10aGlzLl9pdFRhaWwmJih0aGlzLl9pdFRhaWwuX25leHQ9bnVsbCksbnVsbCE9PXRoaXMuX3JlbW92YWxzVGFpbCYmKHRoaXMuX3JlbW92YWxzVGFpbC5fbmV4dFJlbW92ZWQ9bnVsbCksbnVsbCE9PXRoaXMuX2lkZW50aXR5Q2hhbmdlc1RhaWwmJih0aGlzLl9pZGVudGl0eUNoYW5nZXNUYWlsLl9uZXh0SWRlbnRpdHlDaGFuZ2U9bnVsbCl9X3JlaW5zZXJ0QWZ0ZXIodCxlLGkpe251bGwhPT10aGlzLl91bmxpbmtlZFJlY29yZHMmJnRoaXMuX3VubGlua2VkUmVjb3Jkcy5yZW1vdmUodCk7bGV0IHI9dC5fcHJldlJlbW92ZWQsbz10Ll9uZXh0UmVtb3ZlZDtyZXR1cm4gbnVsbD09PXI/dGhpcy5fcmVtb3ZhbHNIZWFkPW86ci5fbmV4dFJlbW92ZWQ9byxudWxsPT09bz90aGlzLl9yZW1vdmFsc1RhaWw9cjpvLl9wcmV2UmVtb3ZlZD1yLHRoaXMuX2luc2VydEFmdGVyKHQsZSxpKSx0aGlzLl9hZGRUb01vdmVzKHQsaSksdH1fbW92ZUFmdGVyKHQsZSxpKXtyZXR1cm4gdGhpcy5fdW5saW5rKHQpLHRoaXMuX2luc2VydEFmdGVyKHQsZSxpKSx0aGlzLl9hZGRUb01vdmVzKHQsaSksdH1fYWRkQWZ0ZXIodCxlLGkpe3JldHVybiB0aGlzLl9pbnNlcnRBZnRlcih0LGUsaSksdGhpcy5fYWRkaXRpb25zVGFpbD1udWxsPT09dGhpcy5fYWRkaXRpb25zVGFpbD90aGlzLl9hZGRpdGlvbnNIZWFkPXQ6dGhpcy5fYWRkaXRpb25zVGFpbC5fbmV4dEFkZGVkPXQsdH1faW5zZXJ0QWZ0ZXIodCxlLGkpe2xldCByPW51bGw9PT1lP3RoaXMuX2l0SGVhZDplLl9uZXh0O3JldHVybiB0Ll9uZXh0PXIsdC5fcHJldj1lLG51bGw9PT1yP3RoaXMuX2l0VGFpbD10OnIuX3ByZXY9dCxudWxsPT09ZT90aGlzLl9pdEhlYWQ9dDplLl9uZXh0PXQsbnVsbD09PXRoaXMuX2xpbmtlZFJlY29yZHMmJih0aGlzLl9saW5rZWRSZWNvcmRzPW5ldyBhVCksdGhpcy5fbGlua2VkUmVjb3Jkcy5wdXQodCksdC5jdXJyZW50SW5kZXg9aSx0fV9yZW1vdmUodCl7cmV0dXJuIHRoaXMuX2FkZFRvUmVtb3ZhbHModGhpcy5fdW5saW5rKHQpKX1fdW5saW5rKHQpe251bGwhPT10aGlzLl9saW5rZWRSZWNvcmRzJiZ0aGlzLl9saW5rZWRSZWNvcmRzLnJlbW92ZSh0KTtsZXQgZT10Ll9wcmV2LGk9dC5fbmV4dDtyZXR1cm4gbnVsbD09PWU/dGhpcy5faXRIZWFkPWk6ZS5fbmV4dD1pLG51bGw9PT1pP3RoaXMuX2l0VGFpbD1lOmkuX3ByZXY9ZSx0fV9hZGRUb01vdmVzKHQsZSl7cmV0dXJuIHQucHJldmlvdXNJbmRleD09PWV8fCh0aGlzLl9tb3Zlc1RhaWw9bnVsbD09PXRoaXMuX21vdmVzVGFpbD90aGlzLl9tb3Zlc0hlYWQ9dDp0aGlzLl9tb3Zlc1RhaWwuX25leHRNb3ZlZD10KSx0fV9hZGRUb1JlbW92YWxzKHQpe3JldHVybiBudWxsPT09dGhpcy5fdW5saW5rZWRSZWNvcmRzJiYodGhpcy5fdW5saW5rZWRSZWNvcmRzPW5ldyBhVCksdGhpcy5fdW5saW5rZWRSZWNvcmRzLnB1dCh0KSx0LmN1cnJlbnRJbmRleD1udWxsLHQuX25leHRSZW1vdmVkPW51bGwsbnVsbD09PXRoaXMuX3JlbW92YWxzVGFpbD8odGhpcy5fcmVtb3ZhbHNUYWlsPXRoaXMuX3JlbW92YWxzSGVhZD10LHQuX3ByZXZSZW1vdmVkPW51bGwpOih0Ll9wcmV2UmVtb3ZlZD10aGlzLl9yZW1vdmFsc1RhaWwsdGhpcy5fcmVtb3ZhbHNUYWlsPXRoaXMuX3JlbW92YWxzVGFpbC5fbmV4dFJlbW92ZWQ9dCksdH1fYWRkSWRlbnRpdHlDaGFuZ2UodCxlKXtyZXR1cm4gdC5pdGVtPWUsdGhpcy5faWRlbnRpdHlDaGFuZ2VzVGFpbD1udWxsPT09dGhpcy5faWRlbnRpdHlDaGFuZ2VzVGFpbD90aGlzLl9pZGVudGl0eUNoYW5nZXNIZWFkPXQ6dGhpcy5faWRlbnRpdHlDaGFuZ2VzVGFpbC5fbmV4dElkZW50aXR5Q2hhbmdlPXQsdH19LGUzPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5pdGVtPXQsdGhpcy50cmFja0J5SWQ9ZSx0aGlzLmN1cnJlbnRJbmRleD1udWxsLHRoaXMucHJldmlvdXNJbmRleD1udWxsLHRoaXMuX25leHRQcmV2aW91cz1udWxsLHRoaXMuX3ByZXY9bnVsbCx0aGlzLl9uZXh0PW51bGwsdGhpcy5fcHJldkR1cD1udWxsLHRoaXMuX25leHREdXA9bnVsbCx0aGlzLl9wcmV2UmVtb3ZlZD1udWxsLHRoaXMuX25leHRSZW1vdmVkPW51bGwsdGhpcy5fbmV4dEFkZGVkPW51bGwsdGhpcy5fbmV4dE1vdmVkPW51bGwsdGhpcy5fbmV4dElkZW50aXR5Q2hhbmdlPW51bGx9fSxhVD1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMubWFwPW5ldyBNYXB9cHV0KHQpe2xldCBlPXQudHJhY2tCeUlkLGk9dGhpcy5tYXAuZ2V0KGUpO2l8fChpPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuX2hlYWQ9bnVsbCx0aGlzLl90YWlsPW51bGx9YWRkKHQpe251bGw9PT10aGlzLl9oZWFkPyh0aGlzLl9oZWFkPXRoaXMuX3RhaWw9dCx0Ll9uZXh0RHVwPW51bGwsdC5fcHJldkR1cD1udWxsKToodGhpcy5fdGFpbC5fbmV4dER1cD10LHQuX3ByZXZEdXA9dGhpcy5fdGFpbCx0Ll9uZXh0RHVwPW51bGwsdGhpcy5fdGFpbD10KX1nZXQodCxlKXtsZXQgaTtmb3IoaT10aGlzLl9oZWFkO251bGwhPT1pO2k9aS5fbmV4dER1cClpZigobnVsbD09PWV8fGU8PWkuY3VycmVudEluZGV4KSYmT2JqZWN0LmlzKGkudHJhY2tCeUlkLHQpKXJldHVybiBpO3JldHVybiBudWxsfXJlbW92ZSh0KXtsZXQgZT10Ll9wcmV2RHVwLGk9dC5fbmV4dER1cDtyZXR1cm4gbnVsbD09PWU/dGhpcy5faGVhZD1pOmUuX25leHREdXA9aSxudWxsPT09aT90aGlzLl90YWlsPWU6aS5fcHJldkR1cD1lLG51bGw9PT10aGlzLl9oZWFkfX0sdGhpcy5tYXAuc2V0KGUsaSkpLGkuYWRkKHQpfWdldCh0LGUpe2xldCByPXRoaXMubWFwLmdldCh0KTtyZXR1cm4gcj9yLmdldCh0LGUpOm51bGx9cmVtb3ZlKHQpe2xldCBlPXQudHJhY2tCeUlkO3JldHVybiB0aGlzLm1hcC5nZXQoZSkucmVtb3ZlKHQpJiZ0aGlzLm1hcC5kZWxldGUoZSksdH1nZXQgaXNFbXB0eSgpe3JldHVybiAwPT09dGhpcy5tYXAuc2l6ZX1jbGVhcigpe3RoaXMubWFwLmNsZWFyKCl9fTtmdW5jdGlvbiBNNyhuLHQsZSl7bGV0IGk9bi5wcmV2aW91c0luZGV4O2lmKG51bGw9PT1pKXJldHVybiBpO2xldCByPTA7cmV0dXJuIGUmJmk8ZS5sZW5ndGgmJihyPWVbaV0pLGkrdCtyfXZhciBsVD1jbGFzc3tjb25zdHJ1Y3Rvcigpe31zdXBwb3J0cyh0KXtyZXR1cm4gdCBpbnN0YW5jZW9mIE1hcHx8WTModCl9Y3JlYXRlKCl7cmV0dXJuIG5ldyBuM319LG4zPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5fcmVjb3Jkcz1uZXcgTWFwLHRoaXMuX21hcEhlYWQ9bnVsbCx0aGlzLl9hcHBlbmRBZnRlcj1udWxsLHRoaXMuX3ByZXZpb3VzTWFwSGVhZD1udWxsLHRoaXMuX2NoYW5nZXNIZWFkPW51bGwsdGhpcy5fY2hhbmdlc1RhaWw9bnVsbCx0aGlzLl9hZGRpdGlvbnNIZWFkPW51bGwsdGhpcy5fYWRkaXRpb25zVGFpbD1udWxsLHRoaXMuX3JlbW92YWxzSGVhZD1udWxsLHRoaXMuX3JlbW92YWxzVGFpbD1udWxsfWdldCBpc0RpcnR5KCl7cmV0dXJuIG51bGwhPT10aGlzLl9hZGRpdGlvbnNIZWFkfHxudWxsIT09dGhpcy5fY2hhbmdlc0hlYWR8fG51bGwhPT10aGlzLl9yZW1vdmFsc0hlYWR9Zm9yRWFjaEl0ZW0odCl7bGV0IGU7Zm9yKGU9dGhpcy5fbWFwSGVhZDtudWxsIT09ZTtlPWUuX25leHQpdChlKX1mb3JFYWNoUHJldmlvdXNJdGVtKHQpe2xldCBlO2ZvcihlPXRoaXMuX3ByZXZpb3VzTWFwSGVhZDtudWxsIT09ZTtlPWUuX25leHRQcmV2aW91cyl0KGUpfWZvckVhY2hDaGFuZ2VkSXRlbSh0KXtsZXQgZTtmb3IoZT10aGlzLl9jaGFuZ2VzSGVhZDtudWxsIT09ZTtlPWUuX25leHRDaGFuZ2VkKXQoZSl9Zm9yRWFjaEFkZGVkSXRlbSh0KXtsZXQgZTtmb3IoZT10aGlzLl9hZGRpdGlvbnNIZWFkO251bGwhPT1lO2U9ZS5fbmV4dEFkZGVkKXQoZSl9Zm9yRWFjaFJlbW92ZWRJdGVtKHQpe2xldCBlO2ZvcihlPXRoaXMuX3JlbW92YWxzSGVhZDtudWxsIT09ZTtlPWUuX25leHRSZW1vdmVkKXQoZSl9ZGlmZih0KXtpZih0KXtpZighKHQgaW5zdGFuY2VvZiBNYXB8fFkzKHQpKSl0aHJvdyBuZXcgQXQoOTAwLCExKX1lbHNlIHQ9bmV3IE1hcDtyZXR1cm4gdGhpcy5jaGVjayh0KT90aGlzOm51bGx9b25EZXN0cm95KCl7fWNoZWNrKHQpe3RoaXMuX3Jlc2V0KCk7bGV0IGU9dGhpcy5fbWFwSGVhZDtpZih0aGlzLl9hcHBlbmRBZnRlcj1udWxsLHRoaXMuX2ZvckVhY2godCwoaSxyKT0+e2lmKGUmJmUua2V5PT09cil0aGlzLl9tYXliZUFkZFRvQ2hhbmdlcyhlLGkpLHRoaXMuX2FwcGVuZEFmdGVyPWUsZT1lLl9uZXh0O2Vsc2V7bGV0IG89dGhpcy5fZ2V0T3JDcmVhdGVSZWNvcmRGb3JLZXkocixpKTtlPXRoaXMuX2luc2VydEJlZm9yZU9yQXBwZW5kKGUsbyl9fSksZSl7ZS5fcHJldiYmKGUuX3ByZXYuX25leHQ9bnVsbCksdGhpcy5fcmVtb3ZhbHNIZWFkPWU7Zm9yKGxldCBpPWU7bnVsbCE9PWk7aT1pLl9uZXh0UmVtb3ZlZClpPT09dGhpcy5fbWFwSGVhZCYmKHRoaXMuX21hcEhlYWQ9bnVsbCksdGhpcy5fcmVjb3Jkcy5kZWxldGUoaS5rZXkpLGkuX25leHRSZW1vdmVkPWkuX25leHQsaS5wcmV2aW91c1ZhbHVlPWkuY3VycmVudFZhbHVlLGkuY3VycmVudFZhbHVlPW51bGwsaS5fcHJldj1udWxsLGkuX25leHQ9bnVsbH1yZXR1cm4gdGhpcy5fY2hhbmdlc1RhaWwmJih0aGlzLl9jaGFuZ2VzVGFpbC5fbmV4dENoYW5nZWQ9bnVsbCksdGhpcy5fYWRkaXRpb25zVGFpbCYmKHRoaXMuX2FkZGl0aW9uc1RhaWwuX25leHRBZGRlZD1udWxsKSx0aGlzLmlzRGlydHl9X2luc2VydEJlZm9yZU9yQXBwZW5kKHQsZSl7aWYodCl7bGV0IGk9dC5fcHJldjtyZXR1cm4gZS5fbmV4dD10LGUuX3ByZXY9aSx0Ll9wcmV2PWUsaSYmKGkuX25leHQ9ZSksdD09PXRoaXMuX21hcEhlYWQmJih0aGlzLl9tYXBIZWFkPWUpLHRoaXMuX2FwcGVuZEFmdGVyPXQsdH1yZXR1cm4gdGhpcy5fYXBwZW5kQWZ0ZXI/KHRoaXMuX2FwcGVuZEFmdGVyLl9uZXh0PWUsZS5fcHJldj10aGlzLl9hcHBlbmRBZnRlcik6dGhpcy5fbWFwSGVhZD1lLHRoaXMuX2FwcGVuZEFmdGVyPWUsbnVsbH1fZ2V0T3JDcmVhdGVSZWNvcmRGb3JLZXkodCxlKXtpZih0aGlzLl9yZWNvcmRzLmhhcyh0KSl7bGV0IHI9dGhpcy5fcmVjb3Jkcy5nZXQodCk7dGhpcy5fbWF5YmVBZGRUb0NoYW5nZXMocixlKTtsZXQgbz1yLl9wcmV2LHM9ci5fbmV4dDtyZXR1cm4gbyYmKG8uX25leHQ9cykscyYmKHMuX3ByZXY9byksci5fbmV4dD1udWxsLHIuX3ByZXY9bnVsbCxyfWxldCBpPW5ldyBpMyh0KTtyZXR1cm4gdGhpcy5fcmVjb3Jkcy5zZXQodCxpKSxpLmN1cnJlbnRWYWx1ZT1lLHRoaXMuX2FkZFRvQWRkaXRpb25zKGkpLGl9X3Jlc2V0KCl7aWYodGhpcy5pc0RpcnR5KXtsZXQgdDtmb3IodGhpcy5fcHJldmlvdXNNYXBIZWFkPXRoaXMuX21hcEhlYWQsdD10aGlzLl9wcmV2aW91c01hcEhlYWQ7bnVsbCE9PXQ7dD10Ll9uZXh0KXQuX25leHRQcmV2aW91cz10Ll9uZXh0O2Zvcih0PXRoaXMuX2NoYW5nZXNIZWFkO251bGwhPT10O3Q9dC5fbmV4dENoYW5nZWQpdC5wcmV2aW91c1ZhbHVlPXQuY3VycmVudFZhbHVlO2Zvcih0PXRoaXMuX2FkZGl0aW9uc0hlYWQ7bnVsbCE9dDt0PXQuX25leHRBZGRlZCl0LnByZXZpb3VzVmFsdWU9dC5jdXJyZW50VmFsdWU7dGhpcy5fY2hhbmdlc0hlYWQ9dGhpcy5fY2hhbmdlc1RhaWw9bnVsbCx0aGlzLl9hZGRpdGlvbnNIZWFkPXRoaXMuX2FkZGl0aW9uc1RhaWw9bnVsbCx0aGlzLl9yZW1vdmFsc0hlYWQ9bnVsbH19X21heWJlQWRkVG9DaGFuZ2VzKHQsZSl7T2JqZWN0LmlzKGUsdC5jdXJyZW50VmFsdWUpfHwodC5wcmV2aW91c1ZhbHVlPXQuY3VycmVudFZhbHVlLHQuY3VycmVudFZhbHVlPWUsdGhpcy5fYWRkVG9DaGFuZ2VzKHQpKX1fYWRkVG9BZGRpdGlvbnModCl7bnVsbD09PXRoaXMuX2FkZGl0aW9uc0hlYWQ/dGhpcy5fYWRkaXRpb25zSGVhZD10aGlzLl9hZGRpdGlvbnNUYWlsPXQ6KHRoaXMuX2FkZGl0aW9uc1RhaWwuX25leHRBZGRlZD10LHRoaXMuX2FkZGl0aW9uc1RhaWw9dCl9X2FkZFRvQ2hhbmdlcyh0KXtudWxsPT09dGhpcy5fY2hhbmdlc0hlYWQ/dGhpcy5fY2hhbmdlc0hlYWQ9dGhpcy5fY2hhbmdlc1RhaWw9dDoodGhpcy5fY2hhbmdlc1RhaWwuX25leHRDaGFuZ2VkPXQsdGhpcy5fY2hhbmdlc1RhaWw9dCl9X2ZvckVhY2godCxlKXt0IGluc3RhbmNlb2YgTWFwP3QuZm9yRWFjaChlKTpPYmplY3Qua2V5cyh0KS5mb3JFYWNoKGk9PmUodFtpXSxpKSl9fSxpMz1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLmtleT10LHRoaXMucHJldmlvdXNWYWx1ZT1udWxsLHRoaXMuY3VycmVudFZhbHVlPW51bGwsdGhpcy5fbmV4dFByZXZpb3VzPW51bGwsdGhpcy5fbmV4dD1udWxsLHRoaXMuX3ByZXY9bnVsbCx0aGlzLl9uZXh0QWRkZWQ9bnVsbCx0aGlzLl9uZXh0UmVtb3ZlZD1udWxsLHRoaXMuX25leHRDaGFuZ2VkPW51bGx9fTtmdW5jdGlvbiB3Nygpe3JldHVybiBuZXcga2MoW25ldyBzVF0pfXZhciBrYz0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuZmFjdG9yaWVzPWV9c3RhdGljIGNyZWF0ZShlLGkpe2lmKG51bGwhPWkpe2xldCByPWkuZmFjdG9yaWVzLnNsaWNlKCk7ZT1lLmNvbmNhdChyKX1yZXR1cm4gbmV3IG4oZSl9c3RhdGljIGV4dGVuZChlKXtyZXR1cm57cHJvdmlkZTpuLHVzZUZhY3Rvcnk6aT0+bi5jcmVhdGUoZSxpfHx3NygpKSxkZXBzOltbbixuZXcgdGwsbmV3IG5zXV19fWZpbmQoZSl7bGV0IGk9dGhpcy5mYWN0b3JpZXMuZmluZChyPT5yLnN1cHBvcnRzKGUpKTtpZihudWxsIT1pKXJldHVybiBpO3Rocm93IG5ldyBBdCg5MDEsITEpfX1yZXR1cm4gbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLHByb3ZpZGVkSW46InJvb3QiLGZhY3Rvcnk6dzd9KSxufSkoKTtmdW5jdGlvbiBTNygpe3JldHVybiBuZXcgbkMoW25ldyBsVF0pfXZhciBuQz0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuZmFjdG9yaWVzPWV9c3RhdGljIGNyZWF0ZShlLGkpe2lmKGkpe2xldCByPWkuZmFjdG9yaWVzLnNsaWNlKCk7ZT1lLmNvbmNhdChyKX1yZXR1cm4gbmV3IG4oZSl9c3RhdGljIGV4dGVuZChlKXtyZXR1cm57cHJvdmlkZTpuLHVzZUZhY3Rvcnk6aT0+bi5jcmVhdGUoZSxpfHxTNygpKSxkZXBzOltbbixuZXcgdGwsbmV3IG5zXV19fWZpbmQoZSl7bGV0IGk9dGhpcy5mYWN0b3JpZXMuZmluZChyPT5yLnN1cHBvcnRzKGUpKTtpZihpKXJldHVybiBpO3Rocm93IG5ldyBBdCg5MDEsITEpfX1yZXR1cm4gbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLHByb3ZpZGVkSW46InJvb3QiLGZhY3Rvcnk6Uzd9KSxufSkoKSxoTWU9W25ldyBsVF0sZk1lPVtuZXcgc1RdLGNYPShuZXcga2MoZk1lKSxuZXcgbkMoaE1lKSxyQihudWxsLCJjb3JlIixbXSkpLHVYPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKEl1KSl9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpO2Z1bmN0aW9uIE5UKG4pe3JldHVybiJib29sZWFuIj09dHlwZW9mIG4/bjpudWxsIT1uJiYiZmFsc2UiIT09bn12YXIgV2w9KCgpPT57cmV0dXJuKG49V2x8fChXbD17fSkpW24uUkFXX1RFWFQ9MF09IlJBV19URVhUIixuW24uRVNDQVBBQkxFX1JBV19URVhUPTFdPSJFU0NBUEFCTEVfUkFXX1RFWFQiLG5bbi5QQVJTQUJMRV9EQVRBPTJdPSJQQVJTQUJMRV9EQVRBIixXbDt2YXIgbn0pKCk7ZnVuY3Rpb24gS2Qobil7aWYoIjoiIT1uWzBdKXJldHVybltudWxsLG5dO2xldCB0PW4uaW5kZXhPZigiOiIsMSk7aWYoLTE9PT10KXRocm93IG5ldyBFcnJvcihgVW5zdXBwb3J0ZWQgZm9ybWF0ICIke259IiBleHBlY3RpbmcgIjpuYW1lc3BhY2U6bmFtZSJgKTtyZXR1cm5bbi5zbGljZSgxLHQpLG4uc2xpY2UodCsxKV19ZnVuY3Rpb24gQ0Iobil7cmV0dXJuIm5nLWNvbnRhaW5lciI9PT1LZChuKVsxXX1mdW5jdGlvbiBNQihuKXtyZXR1cm4ibmctY29udGVudCI9PT1LZChuKVsxXX1mdW5jdGlvbiBkWChuKXtyZXR1cm4gbnVsbD09PW4/bnVsbDpLZChuKVswXX1mdW5jdGlvbiB3QihuLHQpe3JldHVybiBuP2A6JHtufToke3R9YDp0fXZhciBwWCxMVCxlaT1jbGFzc3tjb25zdHJ1Y3Rvcih7Y2xvc2VkQnlDaGlsZHJlbjp0LGltcGxpY2l0TmFtZXNwYWNlUHJlZml4OmUsY29udGVudFR5cGU6aT1XbC5QQVJTQUJMRV9EQVRBLGNsb3NlZEJ5UGFyZW50OnI9ITEsaXNWb2lkOm89ITEsaWdub3JlRmlyc3RMZjpzPSExLHByZXZlbnROYW1lc3BhY2VJbmhlcml0YW5jZTphPSExfT17fSl7dGhpcy5jbG9zZWRCeUNoaWxkcmVuPXt9LHRoaXMuY2xvc2VkQnlQYXJlbnQ9ITEsdGhpcy5jYW5TZWxmQ2xvc2U9ITEsdCYmdC5sZW5ndGg+MCYmdC5mb3JFYWNoKGw9PnRoaXMuY2xvc2VkQnlDaGlsZHJlbltsXT0hMCksdGhpcy5pc1ZvaWQ9byx0aGlzLmNsb3NlZEJ5UGFyZW50PXJ8fG8sdGhpcy5pbXBsaWNpdE5hbWVzcGFjZVByZWZpeD1lfHxudWxsLHRoaXMuY29udGVudFR5cGU9aSx0aGlzLmlnbm9yZUZpcnN0TGY9cyx0aGlzLnByZXZlbnROYW1lc3BhY2VJbmhlcml0YW5jZT1hfWlzQ2xvc2VkQnlDaGlsZCh0KXtyZXR1cm4gdGhpcy5pc1ZvaWR8fHQudG9Mb3dlckNhc2UoKWluIHRoaXMuY2xvc2VkQnlDaGlsZHJlbn1nZXRDb250ZW50VHlwZSh0KXtyZXR1cm4ib2JqZWN0Ij09dHlwZW9mIHRoaXMuY29udGVudFR5cGU/KHZvaWQgMD09PXQ/dm9pZCAwOnRoaXMuY29udGVudFR5cGVbdF0pPz90aGlzLmNvbnRlbnRUeXBlLmRlZmF1bHQ6dGhpcy5jb250ZW50VHlwZX19O2Z1bmN0aW9uIEZWKG4pe3JldHVybiBMVHx8KHBYPW5ldyBlaSxMVD17YmFzZTpuZXcgZWkoe2lzVm9pZDohMH0pLG1ldGE6bmV3IGVpKHtpc1ZvaWQ6ITB9KSxhcmVhOm5ldyBlaSh7aXNWb2lkOiEwfSksZW1iZWQ6bmV3IGVpKHtpc1ZvaWQ6ITB9KSxsaW5rOm5ldyBlaSh7aXNWb2lkOiEwfSksaW1nOm5ldyBlaSh7aXNWb2lkOiEwfSksaW5wdXQ6bmV3IGVpKHtpc1ZvaWQ6ITB9KSxwYXJhbTpuZXcgZWkoe2lzVm9pZDohMH0pLGhyOm5ldyBlaSh7aXNWb2lkOiEwfSksYnI6bmV3IGVpKHtpc1ZvaWQ6ITB9KSxzb3VyY2U6bmV3IGVpKHtpc1ZvaWQ6ITB9KSx0cmFjazpuZXcgZWkoe2lzVm9pZDohMH0pLHdicjpuZXcgZWkoe2lzVm9pZDohMH0pLHA6bmV3IGVpKHtjbG9zZWRCeUNoaWxkcmVuOlsiYWRkcmVzcyIsImFydGljbGUiLCJhc2lkZSIsImJsb2NrcXVvdGUiLCJkaXYiLCJkbCIsImZpZWxkc2V0IiwiZm9vdGVyIiwiZm9ybSIsImgxIiwiaDIiLCJoMyIsImg0IiwiaDUiLCJoNiIsImhlYWRlciIsImhncm91cCIsImhyIiwibWFpbiIsIm5hdiIsIm9sIiwicCIsInByZSIsInNlY3Rpb24iLCJ0YWJsZSIsInVsIl0sY2xvc2VkQnlQYXJlbnQ6ITB9KSx0aGVhZDpuZXcgZWkoe2Nsb3NlZEJ5Q2hpbGRyZW46WyJ0Ym9keSIsInRmb290Il19KSx0Ym9keTpuZXcgZWkoe2Nsb3NlZEJ5Q2hpbGRyZW46WyJ0Ym9keSIsInRmb290Il0sY2xvc2VkQnlQYXJlbnQ6ITB9KSx0Zm9vdDpuZXcgZWkoe2Nsb3NlZEJ5Q2hpbGRyZW46WyJ0Ym9keSJdLGNsb3NlZEJ5UGFyZW50OiEwfSksdHI6bmV3IGVpKHtjbG9zZWRCeUNoaWxkcmVuOlsidHIiXSxjbG9zZWRCeVBhcmVudDohMH0pLHRkOm5ldyBlaSh7Y2xvc2VkQnlDaGlsZHJlbjpbInRkIiwidGgiXSxjbG9zZWRCeVBhcmVudDohMH0pLHRoOm5ldyBlaSh7Y2xvc2VkQnlDaGlsZHJlbjpbInRkIiwidGgiXSxjbG9zZWRCeVBhcmVudDohMH0pLGNvbDpuZXcgZWkoe2lzVm9pZDohMH0pLHN2ZzpuZXcgZWkoe2ltcGxpY2l0TmFtZXNwYWNlUHJlZml4OiJzdmcifSksZm9yZWlnbk9iamVjdDpuZXcgZWkoe2ltcGxpY2l0TmFtZXNwYWNlUHJlZml4OiJzdmciLHByZXZlbnROYW1lc3BhY2VJbmhlcml0YW5jZTohMH0pLG1hdGg6bmV3IGVpKHtpbXBsaWNpdE5hbWVzcGFjZVByZWZpeDoibWF0aCJ9KSxsaTpuZXcgZWkoe2Nsb3NlZEJ5Q2hpbGRyZW46WyJsaSJdLGNsb3NlZEJ5UGFyZW50OiEwfSksZHQ6bmV3IGVpKHtjbG9zZWRCeUNoaWxkcmVuOlsiZHQiLCJkZCJdfSksZGQ6bmV3IGVpKHtjbG9zZWRCeUNoaWxkcmVuOlsiZHQiLCJkZCJdLGNsb3NlZEJ5UGFyZW50OiEwfSkscmI6bmV3IGVpKHtjbG9zZWRCeUNoaWxkcmVuOlsicmIiLCJydCIsInJ0YyIsInJwIl0sY2xvc2VkQnlQYXJlbnQ6ITB9KSxydDpuZXcgZWkoe2Nsb3NlZEJ5Q2hpbGRyZW46WyJyYiIsInJ0IiwicnRjIiwicnAiXSxjbG9zZWRCeVBhcmVudDohMH0pLHJ0YzpuZXcgZWkoe2Nsb3NlZEJ5Q2hpbGRyZW46WyJyYiIsInJ0YyIsInJwIl0sY2xvc2VkQnlQYXJlbnQ6ITB9KSxycDpuZXcgZWkoe2Nsb3NlZEJ5Q2hpbGRyZW46WyJyYiIsInJ0IiwicnRjIiwicnAiXSxjbG9zZWRCeVBhcmVudDohMH0pLG9wdGdyb3VwOm5ldyBlaSh7Y2xvc2VkQnlDaGlsZHJlbjpbIm9wdGdyb3VwIl0sY2xvc2VkQnlQYXJlbnQ6ITB9KSxvcHRpb246bmV3IGVpKHtjbG9zZWRCeUNoaWxkcmVuOlsib3B0aW9uIiwib3B0Z3JvdXAiXSxjbG9zZWRCeVBhcmVudDohMH0pLHByZTpuZXcgZWkoe2lnbm9yZUZpcnN0TGY6ITB9KSxsaXN0aW5nOm5ldyBlaSh7aWdub3JlRmlyc3RMZjohMH0pLHN0eWxlOm5ldyBlaSh7Y29udGVudFR5cGU6V2wuUkFXX1RFWFR9KSxzY3JpcHQ6bmV3IGVpKHtjb250ZW50VHlwZTpXbC5SQVdfVEVYVH0pLHRpdGxlOm5ldyBlaSh7Y29udGVudFR5cGU6e2RlZmF1bHQ6V2wuRVNDQVBBQkxFX1JBV19URVhULHN2ZzpXbC5QQVJTQUJMRV9EQVRBfX0pLHRleHRhcmVhOm5ldyBlaSh7Y29udGVudFR5cGU6V2wuRVNDQVBBQkxFX1JBV19URVhULGlnbm9yZUZpcnN0TGY6ITB9KX0pLExUW25dPz9MVFtuLnRvTG93ZXJDYXNlKCldPz9wWH12YXIgaFg9bmV3IFJlZ0V4cCgiKFxcOm5vdFxcKCl8KChbXFwuXFwjXT8pWy1cXHddKyl8KD86XFxbKFstLlxcdypcXFxcJF0rKSg/Oj0oW1wiJ10/KShbXlxcXVwiJ10qKVxcNSk/XFxdKXwoXFwpKXwoXFxzKixcXHMqKSIsImciKSxaZD1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuZWxlbWVudD1udWxsLHRoaXMuY2xhc3NOYW1lcz1bXSx0aGlzLmF0dHJzPVtdLHRoaXMubm90U2VsZWN0b3JzPVtdfXN0YXRpYyBwYXJzZSh0KXtsZXQgbyxlPVtdLGk9KGwsYyk9PntjLm5vdFNlbGVjdG9ycy5sZW5ndGg+MCYmIWMuZWxlbWVudCYmMD09Yy5jbGFzc05hbWVzLmxlbmd0aCYmMD09Yy5hdHRycy5sZW5ndGgmJihjLmVsZW1lbnQ9IioiKSxsLnB1c2goYyl9LHI9bmV3IFpkLHM9cixhPSExO2ZvcihoWC5sYXN0SW5kZXg9MDtvPWhYLmV4ZWModCk7KXtpZihvWzFdKXtpZihhKXRocm93IG5ldyBFcnJvcigiTmVzdGluZyA6bm90IGluIGEgc2VsZWN0b3IgaXMgbm90IGFsbG93ZWQiKTthPSEwLHM9bmV3IFpkLHIubm90U2VsZWN0b3JzLnB1c2gocyl9bGV0IGw9b1syXTtpZihsKXtsZXQgdT1vWzNdOyIjIj09PXU/cy5hZGRBdHRyaWJ1dGUoImlkIixsLnNsaWNlKDEpKToiLiI9PT11P3MuYWRkQ2xhc3NOYW1lKGwuc2xpY2UoMSkpOnMuc2V0RWxlbWVudChsKX1sZXQgYz1vWzRdO2lmKGMmJnMuYWRkQXR0cmlidXRlKHMudW5lc2NhcGVBdHRyaWJ1dGUoYyksb1s2XSksb1s3XSYmKGE9ITEscz1yKSxvWzhdKXtpZihhKXRocm93IG5ldyBFcnJvcigiTXVsdGlwbGUgc2VsZWN0b3JzIGluIDpub3QgYXJlIG5vdCBzdXBwb3J0ZWQiKTtpKGUscikscj1zPW5ldyBaZH19cmV0dXJuIGkoZSxyKSxlfXVuZXNjYXBlQXR0cmlidXRlKHQpe2xldCBlPSIiLGk9ITE7Zm9yKGxldCByPTA7cjx0Lmxlbmd0aDtyKyspe2xldCBvPXQuY2hhckF0KHIpO2lmKCJcXCIhPT1vKXtpZigiJCI9PT1vJiYhaSl0aHJvdyBuZXcgRXJyb3IoYEVycm9yIGluIGF0dHJpYnV0ZSBzZWxlY3RvciAiJHt0fSIuIFVuZXNjYXBlZCAiJCIgaXMgbm90IHN1cHBvcnRlZC4gUGxlYXNlIGVzY2FwZSB3aXRoICJcXCQiLmApO2k9ITEsZSs9b31lbHNlIGk9ITB9cmV0dXJuIGV9ZXNjYXBlQXR0cmlidXRlKHQpe3JldHVybiB0LnJlcGxhY2UoL1xcL2csIlxcXFwiKS5yZXBsYWNlKC9cJC9nLCJcXCQiKX1pc0VsZW1lbnRTZWxlY3Rvcigpe3JldHVybiB0aGlzLmhhc0VsZW1lbnRTZWxlY3RvcigpJiYwPT10aGlzLmNsYXNzTmFtZXMubGVuZ3RoJiYwPT10aGlzLmF0dHJzLmxlbmd0aCYmMD09PXRoaXMubm90U2VsZWN0b3JzLmxlbmd0aH1oYXNFbGVtZW50U2VsZWN0b3IoKXtyZXR1cm4hIXRoaXMuZWxlbWVudH1zZXRFbGVtZW50KHQ9bnVsbCl7dGhpcy5lbGVtZW50PXR9Z2V0TWF0Y2hpbmdFbGVtZW50VGVtcGxhdGUoKXtsZXQgdD10aGlzLmVsZW1lbnR8fCJkaXYiLGU9dGhpcy5jbGFzc05hbWVzLmxlbmd0aD4wP2AgY2xhc3M9IiR7dGhpcy5jbGFzc05hbWVzLmpvaW4oIiAiKX0iYDoiIixpPSIiO2ZvcihsZXQgcj0wO3I8dGhpcy5hdHRycy5sZW5ndGg7cis9MilpKz1gICR7dGhpcy5hdHRyc1tyXX0keyIiIT09dGhpcy5hdHRyc1tyKzFdP2A9IiR7dGhpcy5hdHRyc1tyKzFdfSJgOiIifWA7cmV0dXJuIEZWKHQpLmlzVm9pZD9gPCR7dH0ke2V9JHtpfS8+YDpgPCR7dH0ke2V9JHtpfT48LyR7dH0+YH1nZXRBdHRycygpe2xldCB0PVtdO3JldHVybiB0aGlzLmNsYXNzTmFtZXMubGVuZ3RoPjAmJnQucHVzaCgiY2xhc3MiLHRoaXMuY2xhc3NOYW1lcy5qb2luKCIgIikpLHQuY29uY2F0KHRoaXMuYXR0cnMpfWFkZEF0dHJpYnV0ZSh0LGU9IiIpe3RoaXMuYXR0cnMucHVzaCh0LGUmJmUudG9Mb3dlckNhc2UoKXx8IiIpfWFkZENsYXNzTmFtZSh0KXt0aGlzLmNsYXNzTmFtZXMucHVzaCh0LnRvTG93ZXJDYXNlKCkpfXRvU3RyaW5nKCl7bGV0IHQ9dGhpcy5lbGVtZW50fHwiIjtpZih0aGlzLmNsYXNzTmFtZXMmJnRoaXMuY2xhc3NOYW1lcy5mb3JFYWNoKGU9PnQrPWAuJHtlfWApLHRoaXMuYXR0cnMpZm9yKGxldCBlPTA7ZTx0aGlzLmF0dHJzLmxlbmd0aDtlKz0yKXtsZXQgaT10aGlzLmVzY2FwZUF0dHJpYnV0ZSh0aGlzLmF0dHJzW2VdKSxyPXRoaXMuYXR0cnNbZSsxXTt0Kz1gWyR7aX0ke3I/Ij0iK3I6IiJ9XWB9cmV0dXJuIHRoaXMubm90U2VsZWN0b3JzLmZvckVhY2goZT0+dCs9YDpub3QoJHtlfSlgKSx0fX0scWQ9KCgpPT57cmV0dXJuKG49cWR8fChxZD17fSkpW24uRW11bGF0ZWQ9MF09IkVtdWxhdGVkIixuW24uTm9uZT0yXT0iTm9uZSIsbltuLlNoYWRvd0RvbT0zXT0iU2hhZG93RG9tIixxZDt2YXIgbn0pKCksY0M9KCgpPT57cmV0dXJuKG49Y0N8fChjQz17fSkpW24uT25QdXNoPTBdPSJPblB1c2giLG5bbi5EZWZhdWx0PTFdPSJEZWZhdWx0IixjQzt2YXIgbn0pKCksaW89KCgpPT57cmV0dXJuKG49aW98fChpbz17fSkpW24uTk9ORT0wXT0iTk9ORSIsbltuLkhUTUw9MV09IkhUTUwiLG5bbi5TVFlMRT0yXT0iU1RZTEUiLG5bbi5TQ1JJUFQ9M109IlNDUklQVCIsbltuLlVSTD00XT0iVVJMIixuW24uUkVTT1VSQ0VfVVJMPTVdPSJSRVNPVVJDRV9VUkwiLGlvO3ZhciBufSkoKTtmdW5jdGlvbiB2TWUobil7bGV0IHQ9ZnVuY3Rpb24obil7bGV0IHQ9bi5jbGFzc05hbWVzJiZuLmNsYXNzTmFtZXMubGVuZ3RoP1s4LC4uLm4uY2xhc3NOYW1lc106W107cmV0dXJuW24uZWxlbWVudCYmIioiIT09bi5lbGVtZW50P24uZWxlbWVudDoiIiwuLi5uLmF0dHJzLC4uLnRdfShuKSxlPW4ubm90U2VsZWN0b3JzJiZuLm5vdFNlbGVjdG9ycy5sZW5ndGg/bi5ub3RTZWxlY3RvcnMubWFwKGk9PmZ1bmN0aW9uKG4pe2xldCB0PW4uY2xhc3NOYW1lcyYmbi5jbGFzc05hbWVzLmxlbmd0aD9bOCwuLi5uLmNsYXNzTmFtZXNdOltdO3JldHVybiBuLmVsZW1lbnQ/WzUsbi5lbGVtZW50LC4uLm4uYXR0cnMsLi4udF06bi5hdHRycy5sZW5ndGg/WzMsLi4ubi5hdHRycywuLi50XTpuLmNsYXNzTmFtZXMmJm4uY2xhc3NOYW1lcy5sZW5ndGg/WzksLi4ubi5jbGFzc05hbWVzXTpbXX0oaSkpOltdO3JldHVybiB0LmNvbmNhdCguLi5lKX1mdW5jdGlvbiBOVihuKXtyZXR1cm4gbj9aZC5wYXJzZShuKS5tYXAodk1lKTpbXX12YXIgeU1lPS8tKyhbYS16MC05XSkvZztmdW5jdGlvbiBPUShuLHQsZSl7bGV0IGk9bi5pbmRleE9mKHQpO3JldHVybi0xPT1pP2U6W24uc2xpY2UoMCxpKS50cmltKCksbi5zbGljZShpKzEpLnRyaW0oKV19ZnVuY3Rpb24gUVQobil7dGhyb3cgbmV3IEVycm9yKGBJbnRlcm5hbCBFcnJvcjogJHtufWApfWZ1bmN0aW9uIExWKG4pe2xldCB0PVtdO2ZvcihsZXQgZT0wO2U8bi5sZW5ndGg7ZSsrKXtsZXQgaT1uLmNoYXJDb2RlQXQoZSk7aWYoaT49NTUyOTYmJmk8PTU2MzE5JiZuLmxlbmd0aD5lKzEpe2xldCByPW4uY2hhckNvZGVBdChlKzEpO3I+PTU2MzIwJiZyPD01NzM0MyYmKGUrKyxpPShpLTU1Mjk2PDwxMCkrci01NjMyMCs2NTUzNil9aTw9MTI3P3QucHVzaChpKTppPD0yMDQ3P3QucHVzaChpPj42JjMxfDE5Miw2MyZpfDEyOCk6aTw9NjU1MzU/dC5wdXNoKGk+PjEyfDIyNCxpPj42JjYzfDEyOCw2MyZpfDEyOCk6aTw9MjA5NzE1MSYmdC5wdXNoKGk+PjE4Jjd8MjQwLGk+PjEyJjYzfDEyOCxpPj42JjYzfDEyOCw2MyZpfDEyOCl9cmV0dXJuIHR9ZnVuY3Rpb24ga1Eobil7aWYoInN0cmluZyI9PXR5cGVvZiBuKXJldHVybiBuO2lmKEFycmF5LmlzQXJyYXkobikpcmV0dXJuIlsiK24ubWFwKGtRKS5qb2luKCIsICIpKyJdIjtpZihudWxsPT1uKXJldHVybiIiK247aWYobi5vdmVycmlkZGVuTmFtZSlyZXR1cm5gJHtuLm92ZXJyaWRkZW5OYW1lfWA7aWYobi5uYW1lKXJldHVybmAke24ubmFtZX1gO2lmKCFuLnRvU3RyaW5nKXJldHVybiJvYmplY3QiO2xldCB0PW4udG9TdHJpbmcoKTtpZihudWxsPT10KXJldHVybiIiK3Q7bGV0IGU9dC5pbmRleE9mKCJcbiIpO3JldHVybi0xPT09ZT90OnQuc3Vic3RyaW5nKDAsZSl9dmFyIHBfPSgoKT0+dHlwZW9mIGdsb2JhbDwidSImJmdsb2JhbHx8dHlwZW9mIHdpbmRvdzwidSImJndpbmRvd3x8dHlwZW9mIHNlbGY8InUiJiZ0eXBlb2YgV29ya2VyR2xvYmFsU2NvcGU8InUiJiZzZWxmIGluc3RhbmNlb2YgV29ya2VyR2xvYmFsU2NvcGUmJnNlbGYpKCksbmg9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5kaWdpdHM9dH1zdGF0aWMgemVybygpe3JldHVybiBuZXcgbmgoWzBdKX1zdGF0aWMgb25lKCl7cmV0dXJuIG5ldyBuaChbMV0pfWNsb25lKCl7cmV0dXJuIG5ldyBuaCh0aGlzLmRpZ2l0cy5zbGljZSgpKX1hZGQodCl7bGV0IGU9dGhpcy5jbG9uZSgpO3JldHVybiBlLmFkZFRvU2VsZih0KSxlfWFkZFRvU2VsZih0KXtsZXQgZT1NYXRoLm1heCh0aGlzLmRpZ2l0cy5sZW5ndGgsdC5kaWdpdHMubGVuZ3RoKSxpPTA7Zm9yKGxldCByPTA7cjxlO3IrKyl7bGV0IG89aTtyPHRoaXMuZGlnaXRzLmxlbmd0aCYmKG8rPXRoaXMuZGlnaXRzW3JdKSxyPHQuZGlnaXRzLmxlbmd0aCYmKG8rPXQuZGlnaXRzW3JdKSxvPj0xMD8odGhpcy5kaWdpdHNbcl09by0xMCxpPTEpOih0aGlzLmRpZ2l0c1tyXT1vLGk9MCl9aT4wJiYodGhpcy5kaWdpdHNbZV09MSl9dG9TdHJpbmcoKXtsZXQgdD0iIjtmb3IobGV0IGU9dGhpcy5kaWdpdHMubGVuZ3RoLTE7ZT49MDtlLS0pdCs9dGhpcy5kaWdpdHNbZV07cmV0dXJuIHR9fSxLVD1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLnBvd2VyT2ZUd29zPVt0XX1nZXRWYWx1ZSgpe3JldHVybiB0aGlzLnBvd2VyT2ZUd29zWzBdfW11bHRpcGx5QnkodCl7bGV0IGU9bmguemVybygpO3JldHVybiB0aGlzLm11bHRpcGx5QnlBbmRBZGRUbyh0LGUpLGV9bXVsdGlwbHlCeUFuZEFkZFRvKHQsZSl7Zm9yKGxldCBpPTA7MCE9PXQ7dD4+Pj0xLGkrKylpZigxJnQpe2xldCByPXRoaXMuZ2V0TXVsdGlwbGllZEJ5UG93ZXJPZlR3byhpKTtlLmFkZFRvU2VsZihyKX19Z2V0TXVsdGlwbGllZEJ5UG93ZXJPZlR3byh0KXtmb3IobGV0IGU9dGhpcy5wb3dlck9mVHdvcy5sZW5ndGg7ZTw9dDtlKyspe2xldCBpPXRoaXMucG93ZXJPZlR3b3NbZS0xXTt0aGlzLnBvd2VyT2ZUd29zW2VdPWkuYWRkKGkpfXJldHVybiB0aGlzLnBvd2VyT2ZUd29zW3RdfX07ZnVuY3Rpb24gU01lKG4pe3JldHVybiBmdW5jdGlvbihuKXtsZXQgdD1MVihuKSxlPWZ1bmN0aW9uKG4sdCl7bGV0IGU9bi5sZW5ndGgrMz4+PjIsaT1bXTtmb3IobGV0IHI9MDtyPGU7cisrKWlbcl09ZW0obiw0KnIsdCk7cmV0dXJuIGl9KHQsRmMuQmlnKSxpPTgqdC5sZW5ndGgscj1mdW5jdGlvbihuLHQpe2xldCBlPVtdO2ZvcihsZXQgaT0wO2k8ODA7aSsrKWUucHVzaCh1bmRlZmluZWQpO3JldHVybiBlfSgpLG89MTczMjU4NDE5MyxzPTQwMjMyMzM0MTcsYT0yNTYyMzgzMTAyLGw9MjcxNzMzODc4LGM9MzI4NTM3NzUyMDtlW2k+PjVdfD0xMjg8PDI0LWklMzIsZVsxNSsoaSs2ND4+OTw8NCldPWk7Zm9yKGxldCB1PTA7dTxlLmxlbmd0aDt1Kz0xNil7bGV0IGQ9byxwPXMsaD1hLGY9bCxtPWM7Zm9yKGxldCB4PTA7eDw4MDt4Kyspe3JbeF09eDwxNj9lW3UreF06b0Ioclt4LTNdXnJbeC04XV5yW3gtMTRdXnJbeC0xNl0sMSk7bGV0IGc9SU1lKHgscyxhLGwpLGI9Z1swXSxEPWdbMV0sVD1bb0Iobyw1KSxiLGMsRCxyW3hdXS5yZWR1Y2UoZWEpO2M9bCxsPWEsYT1vQihzLDMwKSxzPW8sbz1UfW89ZWEobyxkKSxzPWVhKHMscCksYT1lYShhLGgpLGw9ZWEobCxmKSxjPWVhKGMsbSl9cmV0dXJuIGZ1bmN0aW9uKG4pe2xldCB0PSIiO2ZvcihsZXQgZT0wO2U8bi5sZW5ndGg7ZSsrKXtsZXQgaT1EQihuLGUpO3QrPShpPj4+NCkudG9TdHJpbmcoMTYpKygxNSZpKS50b1N0cmluZygxNil9cmV0dXJuIHQudG9Mb3dlckNhc2UoKX0oZnVuY3Rpb24obil7cmV0dXJuIG4ucmVkdWNlKCh0LGUpPT50LmNvbmNhdChmdW5jdGlvbihuKXtsZXQgdD1bXTtmb3IobGV0IGU9MDtlPDQ7ZSsrKXQucHVzaChuPj4+OCooMy1lKSYyNTUpO3JldHVybiB0fShlKSksW10pfShbbyxzLGEsbCxjXSkpfShmdW5jdGlvbihuKXtyZXR1cm4gbi5tYXAodD0+dC52aXNpdChUTWUsbnVsbCkpfShuLm5vZGVzKS5qb2luKCIiKStgWyR7bi5tZWFuaW5nfV1gKX1mdW5jdGlvbiBGUShuKXtsZXQgdD1uZXcgVEI7cmV0dXJuIEFEKG4ubm9kZXMubWFwKGk9PmkudmlzaXQodCxudWxsKSkuam9pbigiIiksbi5tZWFuaW5nKX12YXIgWlQ9Y2xhc3N7dmlzaXRUZXh0KHQsZSl7cmV0dXJuIHQudmFsdWV9dmlzaXRDb250YWluZXIodCxlKXtyZXR1cm5gWyR7dC5jaGlsZHJlbi5tYXAoaT0+aS52aXNpdCh0aGlzKSkuam9pbigiLCAiKX1dYH12aXNpdEljdSh0LGUpe2xldCBpPU9iamVjdC5rZXlzKHQuY2FzZXMpLm1hcChyPT5gJHtyfSB7JHt0LmNhc2VzW3JdLnZpc2l0KHRoaXMpfX1gKTtyZXR1cm5geyR7dC5leHByZXNzaW9ufSwgJHt0LnR5cGV9LCAke2kuam9pbigiLCAiKX19YH12aXNpdFRhZ1BsYWNlaG9sZGVyKHQsZSl7cmV0dXJuIHQuaXNWb2lkP2A8cGggdGFnIG5hbWU9IiR7dC5zdGFydE5hbWV9Ii8+YDpgPHBoIHRhZyBuYW1lPSIke3Quc3RhcnROYW1lfSI+JHt0LmNoaWxkcmVuLm1hcChpPT5pLnZpc2l0KHRoaXMpKS5qb2luKCIsICIpfTwvcGggbmFtZT0iJHt0LmNsb3NlTmFtZX0iPmB9dmlzaXRQbGFjZWhvbGRlcih0LGUpe3JldHVybiB0LnZhbHVlP2A8cGggbmFtZT0iJHt0Lm5hbWV9Ij4ke3QudmFsdWV9PC9waD5gOmA8cGggbmFtZT0iJHt0Lm5hbWV9Ii8+YH12aXNpdEljdVBsYWNlaG9sZGVyKHQsZSl7cmV0dXJuYDxwaCBpY3UgbmFtZT0iJHt0Lm5hbWV9Ij4ke3QudmFsdWUudmlzaXQodGhpcyl9PC9waD5gfX0sVE1lPW5ldyBaVCxUQj1jbGFzcyBleHRlbmRzIFpUe3Zpc2l0SWN1KHQsZSl7bGV0IGk9T2JqZWN0LmtleXModC5jYXNlcykubWFwKHI9PmAke3J9IHske3QuY2FzZXNbcl0udmlzaXQodGhpcyl9fWApO3JldHVybmB7JHt0LnR5cGV9LCAke2kuam9pbigiLCAiKX19YH19O2Z1bmN0aW9uIElNZShuLHQsZSxpKXtyZXR1cm4gbjwyMD9bdCZlfH50JmksMTUxODUwMDI0OV06bjw0MD9bdF5lXmksMTg1OTc3NTM5M106bjw2MD9bdCZlfHQmaXxlJmksMjQwMDk1OTcwOF06W3ReZV5pLDMzOTU0Njk3ODJdfWZ1bmN0aW9uIGdYKG4pe2xldCB0PUxWKG4pLGU9X1godCwwKSxpPV9YKHQsMTAyMDcyKTtyZXR1cm4gMD09ZSYmKDA9PWl8fDE9PWkpJiYoZV49MzE5NzkwMDYzLGlePS0xODAxNDEwMjY0KSxbZSxpXX1mdW5jdGlvbiBBRChuLHQ9IiIpe2xldCBlPWdYKG4pO2lmKHQpe2xldCBvPWdYKHQpO2U9ZnVuY3Rpb24obix0KXtsZXQgZT1uWzBdLHI9dFswXSxzPU5RKG5bMV0sdFsxXSksYT1zWzBdLGw9c1sxXTtyZXR1cm5bZWEoZWEoZSxyKSxhKSxsXX0oZnVuY3Rpb24obix0KXtsZXQgZT1uWzBdLGk9blsxXTtyZXR1cm5bZTw8MXxpPj4+MzEsaTw8MXxlPj4+MzFdfShlKSxvKX1yZXR1cm4gZnVuY3Rpb24obix0KXtsZXQgZT15WC50b1RoZVBvd2VyT2YoMCkubXVsdGlwbHlCeSh0KTtyZXR1cm4geVgudG9UaGVQb3dlck9mKDQpLm11bHRpcGx5QnlBbmRBZGRUbyhuLGUpLGUudG9TdHJpbmcoKX0oMjE0NzQ4MzY0NyZlWzBdLGVbMV0pfWZ1bmN0aW9uIF9YKG4sdCl7bGV0IHIsZT0yNjU0NDM1NzY5LGk9MjY1NDQzNTc2OSxvPW4ubGVuZ3RoO2ZvcihyPTA7cisxMjw9bztyKz0xMil7ZT1lYShlLGVtKG4scixGYy5MaXR0bGUpKSxpPWVhKGksZW0obixyKzQsRmMuTGl0dGxlKSk7bGV0IHM9dlgoZSxpLHQ9ZWEodCxlbShuLHIrOCxGYy5MaXR0bGUpKSk7ZT1zWzBdLGk9c1sxXSx0PXNbMl19cmV0dXJuIGU9ZWEoZSxlbShuLHIsRmMuTGl0dGxlKSksaT1lYShpLGVtKG4scis0LEZjLkxpdHRsZSkpLHQ9ZWEodCxvKSx2WChlLGksdD1lYSh0LGVtKG4scis4LEZjLkxpdHRsZSk8PDgpKVsyXX1mdW5jdGlvbiB2WChuLHQsZSl7cmV0dXJuIG49aXMobix0KSxuPWlzKG4sZSksbl49ZT4+PjEzLHQ9aXModCxlKSx0PWlzKHQsbiksdF49bjw8OCxlPWlzKGUsbiksZT1pcyhlLHQpLGVePXQ+Pj4xMyxuPWlzKG4sdCksbj1pcyhuLGUpLG5ePWU+Pj4xMix0PWlzKHQsZSksdD1pcyh0LG4pLHRePW48PDE2LGU9aXMoZSxuKSxlPWlzKGUsdCksZV49dD4+PjUsbj1pcyhuLHQpLG49aXMobixlKSxuXj1lPj4+Myx0PWlzKHQsZSksdD1pcyh0LG4pLHRePW48PDEwLGU9aXMoZSxuKSxlPWlzKGUsdCksW24sdCxlXj10Pj4+MTVdfXZhciBGYz0oKCk9PntyZXR1cm4obj1GY3x8KEZjPXt9KSlbbi5MaXR0bGU9MF09IkxpdHRsZSIsbltuLkJpZz0xXT0iQmlnIixGYzt2YXIgbn0pKCk7ZnVuY3Rpb24gZWEobix0KXtyZXR1cm4gTlEobix0KVsxXX1mdW5jdGlvbiBOUShuLHQpe2xldCBlPSg2NTUzNSZuKSsoNjU1MzUmdCksaT0obj4+PjE2KSsodD4+PjE2KSsoZT4+PjE2KTtyZXR1cm5baT4+PjE2LGk8PDE2fDY1NTM1JmVdfWZ1bmN0aW9uIGlzKG4sdCl7bGV0IGU9KDY1NTM1Jm4pLSg2NTUzNSZ0KTtyZXR1cm4obj4+MTYpLSh0Pj4xNikrKGU+PjE2KTw8MTZ8NjU1MzUmZX1mdW5jdGlvbiBvQihuLHQpe3JldHVybiBuPDx0fG4+Pj4zMi10fWZ1bmN0aW9uIERCKG4sdCl7cmV0dXJuIHQ+PW4ubGVuZ3RoPzA6blt0XX1mdW5jdGlvbiBlbShuLHQsZSl7bGV0IGk9MDtpZihlPT09RmMuQmlnKWZvcihsZXQgcj0wO3I8NDtyKyspaSs9REIobix0K3IpPDwyNC04KnI7ZWxzZSBmb3IobGV0IHI9MDtyPDQ7cisrKWkrPURCKG4sdCtyKTw8OCpyO3JldHVybiBpfXZhciB5WD1uZXcgY2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5iYXNlPXQsdGhpcy5leHBvbmVudHM9W25ldyBLVChuaC5vbmUoKSldfXRvVGhlUG93ZXJPZih0KXtmb3IobGV0IGU9dGhpcy5leHBvbmVudHMubGVuZ3RoO2U8PXQ7ZSsrKXtsZXQgaT10aGlzLmV4cG9uZW50c1tlLTFdLm11bHRpcGx5QnkodGhpcy5iYXNlKTt0aGlzLmV4cG9uZW50c1tlXT1uZXcgS1QoaSl9cmV0dXJuIHRoaXMuZXhwb25lbnRzW3RdfX0oMjU2KSxqVD0oKCk9PntyZXR1cm4obj1qVHx8KGpUPXt9KSlbbi5Ob25lPTBdPSJOb25lIixuW24uQ29uc3Q9MV09IkNvbnN0IixqVDt2YXIgbn0pKCksSlQ9Y2xhc3N7Y29uc3RydWN0b3IodD1qVC5Ob25lKXt0aGlzLm1vZGlmaWVycz10fWhhc01vZGlmaWVyKHQpe3JldHVybiAwIT0odGhpcy5tb2RpZmllcnMmdCl9fSxCYz0oKCk9PntyZXR1cm4obj1CY3x8KEJjPXt9KSlbbi5EeW5hbWljPTBdPSJEeW5hbWljIixuW24uQm9vbD0xXT0iQm9vbCIsbltuLlN0cmluZz0yXT0iU3RyaW5nIixuW24uSW50PTNdPSJJbnQiLG5bbi5OdW1iZXI9NF09Ik51bWJlciIsbltuLkZ1bmN0aW9uPTVdPSJGdW5jdGlvbiIsbltuLkluZmVycmVkPTZdPSJJbmZlcnJlZCIsbltuLk5vbmU9N109Ik5vbmUiLEJjO3ZhciBufSkoKSxCdT1jbGFzcyBleHRlbmRzIEpUe2NvbnN0cnVjdG9yKHQsZSl7c3VwZXIoZSksdGhpcy5uYW1lPXR9dmlzaXRUeXBlKHQsZSl7cmV0dXJuIHQudmlzaXRCdWlsdGluVHlwZSh0aGlzLGUpfX0sVmM9Y2xhc3MgZXh0ZW5kcyBKVHtjb25zdHJ1Y3Rvcih0LGUsaT1udWxsKXtzdXBlcihlKSx0aGlzLnZhbHVlPXQsdGhpcy50eXBlUGFyYW1zPWl9dmlzaXRUeXBlKHQsZSl7cmV0dXJuIHQudmlzaXRFeHByZXNzaW9uVHlwZSh0aGlzLGUpfX0sVl89bmV3IEJ1KEJjLkR5bmFtaWMpLFBhPW5ldyBCdShCYy5JbmZlcnJlZCksQk1lPW5ldyBCdShCYy5Cb29sKSxaQz0obmV3IEJ1KEJjLkludCksbmV3IEJ1KEJjLk51bWJlcikpLExRPW5ldyBCdShCYy5TdHJpbmcpLEpkPShuZXcgQnUoQmMuRnVuY3Rpb24pLG5ldyBCdShCYy5Ob25lKSksYW09KCgpPT57cmV0dXJuKG49YW18fChhbT17fSkpW24uTWludXM9MF09Ik1pbnVzIixuW24uUGx1cz0xXT0iUGx1cyIsYW07dmFyIG59KSgpLENuPSgoKT0+e3JldHVybihuPUNufHwoQ249e30pKVtuLkVxdWFscz0wXT0iRXF1YWxzIixuW24uTm90RXF1YWxzPTFdPSJOb3RFcXVhbHMiLG5bbi5JZGVudGljYWw9Ml09IklkZW50aWNhbCIsbltuLk5vdElkZW50aWNhbD0zXT0iTm90SWRlbnRpY2FsIixuW24uTWludXM9NF09Ik1pbnVzIixuW24uUGx1cz01XT0iUGx1cyIsbltuLkRpdmlkZT02XT0iRGl2aWRlIixuW24uTXVsdGlwbHk9N109Ik11bHRpcGx5IixuW24uTW9kdWxvPThdPSJNb2R1bG8iLG5bbi5BbmQ9OV09IkFuZCIsbltuLk9yPTEwXT0iT3IiLG5bbi5CaXR3aXNlQW5kPTExXT0iQml0d2lzZUFuZCIsbltuLkxvd2VyPTEyXT0iTG93ZXIiLG5bbi5Mb3dlckVxdWFscz0xM109Ikxvd2VyRXF1YWxzIixuW24uQmlnZ2VyPTE0XT0iQmlnZ2VyIixuW24uQmlnZ2VyRXF1YWxzPTE1XT0iQmlnZ2VyRXF1YWxzIixuW24uTnVsbGlzaENvYWxlc2NlPTE2XT0iTnVsbGlzaENvYWxlc2NlIixDbjt2YXIgbn0pKCk7ZnVuY3Rpb24gQlEobix0LGUpe2xldCBpPW4ubGVuZ3RoO2lmKGkhPT10Lmxlbmd0aClyZXR1cm4hMTtmb3IobGV0IHI9MDtyPGk7cisrKWlmKCFlKG5bcl0sdFtyXSkpcmV0dXJuITE7cmV0dXJuITB9ZnVuY3Rpb24gVWMobix0KXtyZXR1cm4gQlEobix0LChlLGkpPT5lLmlzRXF1aXZhbGVudChpKSl9dmFyIE9yPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy50eXBlPXR8fG51bGwsdGhpcy5zb3VyY2VTcGFuPWV8fG51bGx9cHJvcCh0LGUpe3JldHVybiBuZXcgYl8odGhpcyx0LG51bGwsZSl9a2V5KHQsZSxpKXtyZXR1cm4gbmV3IE1DKHRoaXMsdCxlLGkpfWNhbGxGbih0LGUsaSl7cmV0dXJuIG5ldyBvaCh0aGlzLHQsbnVsbCxlLGkpfWluc3RhbnRpYXRlKHQsZSxpKXtyZXR1cm4gbmV3IGRtKHRoaXMsdCxlLGkpfWNvbmRpdGlvbmFsKHQsZT1udWxsLGkpe3JldHVybiBuZXcgYkModGhpcyx0LGUsbnVsbCxpKX1lcXVhbHModCxlKXtyZXR1cm4gbmV3IGdyKENuLkVxdWFscyx0aGlzLHQsbnVsbCxlKX1ub3RFcXVhbHModCxlKXtyZXR1cm4gbmV3IGdyKENuLk5vdEVxdWFscyx0aGlzLHQsbnVsbCxlKX1pZGVudGljYWwodCxlKXtyZXR1cm4gbmV3IGdyKENuLklkZW50aWNhbCx0aGlzLHQsbnVsbCxlKX1ub3RJZGVudGljYWwodCxlKXtyZXR1cm4gbmV3IGdyKENuLk5vdElkZW50aWNhbCx0aGlzLHQsbnVsbCxlKX1taW51cyh0LGUpe3JldHVybiBuZXcgZ3IoQ24uTWludXMsdGhpcyx0LG51bGwsZSl9cGx1cyh0LGUpe3JldHVybiBuZXcgZ3IoQ24uUGx1cyx0aGlzLHQsbnVsbCxlKX1kaXZpZGUodCxlKXtyZXR1cm4gbmV3IGdyKENuLkRpdmlkZSx0aGlzLHQsbnVsbCxlKX1tdWx0aXBseSh0LGUpe3JldHVybiBuZXcgZ3IoQ24uTXVsdGlwbHksdGhpcyx0LG51bGwsZSl9bW9kdWxvKHQsZSl7cmV0dXJuIG5ldyBncihDbi5Nb2R1bG8sdGhpcyx0LG51bGwsZSl9YW5kKHQsZSl7cmV0dXJuIG5ldyBncihDbi5BbmQsdGhpcyx0LG51bGwsZSl9Yml0d2lzZUFuZCh0LGUsaT0hMCl7cmV0dXJuIG5ldyBncihDbi5CaXR3aXNlQW5kLHRoaXMsdCxudWxsLGUsaSl9b3IodCxlKXtyZXR1cm4gbmV3IGdyKENuLk9yLHRoaXMsdCxudWxsLGUpfWxvd2VyKHQsZSl7cmV0dXJuIG5ldyBncihDbi5Mb3dlcix0aGlzLHQsbnVsbCxlKX1sb3dlckVxdWFscyh0LGUpe3JldHVybiBuZXcgZ3IoQ24uTG93ZXJFcXVhbHMsdGhpcyx0LG51bGwsZSl9YmlnZ2VyKHQsZSl7cmV0dXJuIG5ldyBncihDbi5CaWdnZXIsdGhpcyx0LG51bGwsZSl9YmlnZ2VyRXF1YWxzKHQsZSl7cmV0dXJuIG5ldyBncihDbi5CaWdnZXJFcXVhbHMsdGhpcyx0LG51bGwsZSl9aXNCbGFuayh0KXtyZXR1cm4gdGhpcy5lcXVhbHMoV1QsdCl9bnVsbGlzaENvYWxlc2NlKHQsZSl7cmV0dXJuIG5ldyBncihDbi5OdWxsaXNoQ29hbGVzY2UsdGhpcyx0LG51bGwsZSl9dG9TdG10KCl7cmV0dXJuIG5ldyBIdSh0aGlzLG51bGwpfX0sdW09Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIoZSxpKSx0aGlzLm5hbWU9dH1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiB1bSYmdGhpcy5uYW1lPT09dC5uYW1lfWlzQ29uc3RhbnQoKXtyZXR1cm4hMX12aXNpdEV4cHJlc3Npb24odCxlKXtyZXR1cm4gdC52aXNpdFJlYWRWYXJFeHByKHRoaXMsZSl9c2V0KHQpe3JldHVybiBuZXcgZ0ModGhpcy5uYW1lLHQsbnVsbCx0aGlzLnNvdXJjZVNwYW4pfX0sdl89Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIoZSxpKSx0aGlzLmV4cHI9dH12aXNpdEV4cHJlc3Npb24odCxlKXtyZXR1cm4gdC52aXNpdFR5cGVvZkV4cHIodGhpcyxlKX1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiB2XyYmdC5leHByLmlzRXF1aXZhbGVudCh0aGlzLmV4cHIpfWlzQ29uc3RhbnQoKXtyZXR1cm4gdGhpcy5leHByLmlzQ29uc3RhbnQoKX19LExuPWNsYXNzIGV4dGVuZHMgT3J7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKGUsaSksdGhpcy5ub2RlPXR9aXNFcXVpdmFsZW50KHQpe3JldHVybiB0IGluc3RhbmNlb2YgTG4mJnRoaXMubm9kZT09PXQubm9kZX1pc0NvbnN0YW50KCl7cmV0dXJuITF9dmlzaXRFeHByZXNzaW9uKHQsZSl7cmV0dXJuIHQudmlzaXRXcmFwcGVkTm9kZUV4cHIodGhpcyxlKX19LGdDPWNsYXNzIGV4dGVuZHMgT3J7Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIoaXx8ZS50eXBlLHIpLHRoaXMubmFtZT10LHRoaXMudmFsdWU9ZX1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBnQyYmdGhpcy5uYW1lPT09dC5uYW1lJiZ0aGlzLnZhbHVlLmlzRXF1aXZhbGVudCh0LnZhbHVlKX1pc0NvbnN0YW50KCl7cmV0dXJuITF9dmlzaXRFeHByZXNzaW9uKHQsZSl7cmV0dXJuIHQudmlzaXRXcml0ZVZhckV4cHIodGhpcyxlKX10b0RlY2xTdG10KHQsZSl7cmV0dXJuIG5ldyBWdSh0aGlzLm5hbWUsdGhpcy52YWx1ZSx0LGUsdGhpcy5zb3VyY2VTcGFuKX10b0NvbnN0RGVjbCgpe3JldHVybiB0aGlzLnRvRGVjbFN0bXQoUGEsbGwuRmluYWwpfX0sX0M9Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSxyLG8pe3N1cGVyKHJ8fGkudHlwZSxvKSx0aGlzLnJlY2VpdmVyPXQsdGhpcy5pbmRleD1lLHRoaXMudmFsdWU9aX1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBfQyYmdGhpcy5yZWNlaXZlci5pc0VxdWl2YWxlbnQodC5yZWNlaXZlcikmJnRoaXMuaW5kZXguaXNFcXVpdmFsZW50KHQuaW5kZXgpJiZ0aGlzLnZhbHVlLmlzRXF1aXZhbGVudCh0LnZhbHVlKX1pc0NvbnN0YW50KCl7cmV0dXJuITF9dmlzaXRFeHByZXNzaW9uKHQsZSl7cmV0dXJuIHQudmlzaXRXcml0ZUtleUV4cHIodGhpcyxlKX19LHZDPWNsYXNzIGV4dGVuZHMgT3J7Y29uc3RydWN0b3IodCxlLGkscixvKXtzdXBlcihyfHxpLnR5cGUsbyksdGhpcy5yZWNlaXZlcj10LHRoaXMubmFtZT1lLHRoaXMudmFsdWU9aX1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiB2QyYmdGhpcy5yZWNlaXZlci5pc0VxdWl2YWxlbnQodC5yZWNlaXZlcikmJnRoaXMubmFtZT09PXQubmFtZSYmdGhpcy52YWx1ZS5pc0VxdWl2YWxlbnQodC52YWx1ZSl9aXNDb25zdGFudCgpe3JldHVybiExfXZpc2l0RXhwcmVzc2lvbih0LGUpe3JldHVybiB0LnZpc2l0V3JpdGVQcm9wRXhwcih0aGlzLGUpfX0sb2g9Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSxyLG89ITEpe3N1cGVyKGksciksdGhpcy5mbj10LHRoaXMuYXJncz1lLHRoaXMucHVyZT1vfWlzRXF1aXZhbGVudCh0KXtyZXR1cm4gdCBpbnN0YW5jZW9mIG9oJiZ0aGlzLmZuLmlzRXF1aXZhbGVudCh0LmZuKSYmVWModGhpcy5hcmdzLHQuYXJncykmJnRoaXMucHVyZT09PXQucHVyZX1pc0NvbnN0YW50KCl7cmV0dXJuITF9dmlzaXRFeHByZXNzaW9uKHQsZSl7cmV0dXJuIHQudmlzaXRJbnZva2VGdW5jdGlvbkV4cHIodGhpcyxlKX19LHlDPWNsYXNzIGV4dGVuZHMgT3J7Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIoaSxyKSx0aGlzLnRhZz10LHRoaXMudGVtcGxhdGU9ZX1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiB5QyYmdGhpcy50YWcuaXNFcXVpdmFsZW50KHQudGFnKSYmQlEodGhpcy50ZW1wbGF0ZS5lbGVtZW50cyx0LnRlbXBsYXRlLmVsZW1lbnRzLChlLGkpPT5lLnRleHQ9PT1pLnRleHQpJiZVYyh0aGlzLnRlbXBsYXRlLmV4cHJlc3Npb25zLHQudGVtcGxhdGUuZXhwcmVzc2lvbnMpfWlzQ29uc3RhbnQoKXtyZXR1cm4hMX12aXNpdEV4cHJlc3Npb24odCxlKXtyZXR1cm4gdC52aXNpdFRhZ2dlZFRlbXBsYXRlRXhwcih0aGlzLGUpfX0sZG09Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSxyKXtzdXBlcihpLHIpLHRoaXMuY2xhc3NFeHByPXQsdGhpcy5hcmdzPWV9aXNFcXVpdmFsZW50KHQpe3JldHVybiB0IGluc3RhbmNlb2YgZG0mJnRoaXMuY2xhc3NFeHByLmlzRXF1aXZhbGVudCh0LmNsYXNzRXhwcikmJlVjKHRoaXMuYXJncyx0LmFyZ3MpfWlzQ29uc3RhbnQoKXtyZXR1cm4hMX12aXNpdEV4cHJlc3Npb24odCxlKXtyZXR1cm4gdC52aXNpdEluc3RhbnRpYXRlRXhwcih0aGlzLGUpfX0sY2w9Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIoZSxpKSx0aGlzLnZhbHVlPXR9aXNFcXVpdmFsZW50KHQpe3JldHVybiB0IGluc3RhbmNlb2YgY2wmJnRoaXMudmFsdWU9PT10LnZhbHVlfWlzQ29uc3RhbnQoKXtyZXR1cm4hMH12aXNpdEV4cHJlc3Npb24odCxlKXtyZXR1cm4gdC52aXNpdExpdGVyYWxFeHByKHRoaXMsZSl9fSwkVD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuZWxlbWVudHM9dCx0aGlzLmV4cHJlc3Npb25zPWV9fSxlRD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy50ZXh0PXQsdGhpcy5zb3VyY2VTcGFuPWUsdGhpcy5yYXdUZXh0PWk/P2U/LnRvU3RyaW5nKCk/P0lCKEdUKHQpKX19LGxtPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy50ZXh0PXQsdGhpcy5zb3VyY2VTcGFuPWV9fSxoXz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy50ZXh0PXQsdGhpcy5zb3VyY2VTcGFuPWUsdGhpcy5hc3NvY2lhdGVkTWVzc2FnZT1pfX0sQUI9Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSxyLG8pe3N1cGVyKExRLG8pLHRoaXMubWV0YUJsb2NrPXQsdGhpcy5tZXNzYWdlUGFydHM9ZSx0aGlzLnBsYWNlSG9sZGVyTmFtZXM9aSx0aGlzLmV4cHJlc3Npb25zPXJ9aXNFcXVpdmFsZW50KHQpe3JldHVybiExfWlzQ29uc3RhbnQoKXtyZXR1cm4hMX12aXNpdEV4cHJlc3Npb24odCxlKXtyZXR1cm4gdC52aXNpdExvY2FsaXplZFN0cmluZyh0aGlzLGUpfXNlcmlhbGl6ZUkxOG5IZWFkKCl7bGV0IHQ9dGhpcy5tZXRhQmxvY2suZGVzY3JpcHRpb258fCIiO3JldHVybiB0aGlzLm1ldGFCbG9jay5tZWFuaW5nJiYodD1gJHt0aGlzLm1ldGFCbG9jay5tZWFuaW5nfXwke3R9YCksdGhpcy5tZXRhQmxvY2suY3VzdG9tSWQmJih0PWAke3R9QEAke3RoaXMubWV0YUJsb2NrLmN1c3RvbUlkfWApLHRoaXMubWV0YUJsb2NrLmxlZ2FjeUlkcyYmdGhpcy5tZXRhQmxvY2subGVnYWN5SWRzLmZvckVhY2goZT0+e3Q9YCR7dH1cdTI0MWYke2V9YH0pLHhYKHQsdGhpcy5tZXNzYWdlUGFydHNbMF0udGV4dCx0aGlzLmdldE1lc3NhZ2VQYXJ0U291cmNlU3BhbigwKSl9Z2V0TWVzc2FnZVBhcnRTb3VyY2VTcGFuKHQpe3JldHVybiB0aGlzLm1lc3NhZ2VQYXJ0c1t0XT8uc291cmNlU3Bhbj8/dGhpcy5zb3VyY2VTcGFufWdldFBsYWNlaG9sZGVyU291cmNlU3Bhbih0KXtyZXR1cm4gdGhpcy5wbGFjZUhvbGRlck5hbWVzW3RdPy5zb3VyY2VTcGFuPz90aGlzLmV4cHJlc3Npb25zW3RdPy5zb3VyY2VTcGFuPz90aGlzLnNvdXJjZVNwYW59c2VyaWFsaXplSTE4blRlbXBsYXRlUGFydCh0KXtsZXQgZT10aGlzLnBsYWNlSG9sZGVyTmFtZXNbdC0xXSxpPXRoaXMubWVzc2FnZVBhcnRzW3RdLHI9ZS50ZXh0O3JldHVybiAwPT09ZS5hc3NvY2lhdGVkTWVzc2FnZT8ubGVnYWN5SWRzLmxlbmd0aCYmKHIrPWBAQCR7QUQoZS5hc3NvY2lhdGVkTWVzc2FnZS5tZXNzYWdlU3RyaW5nLGUuYXNzb2NpYXRlZE1lc3NhZ2UubWVhbmluZyl9YCkseFgocixpLnRleHQsdGhpcy5nZXRNZXNzYWdlUGFydFNvdXJjZVNwYW4odCkpfX0sR1Q9bj0+bi5yZXBsYWNlKC9cXC9nLCJcXFxcIiksek1lPW49Pm4ucmVwbGFjZSgvXjovLCJcXDoiKSxqTWU9bj0+bi5yZXBsYWNlKC86L2csIlxcOiIpLElCPW49Pm4ucmVwbGFjZSgvYC9nLCJcXGAiKS5yZXBsYWNlKC9cJHsvZywiJFxceyIpO2Z1bmN0aW9uIHhYKG4sdCxlKXtyZXR1cm4iIj09PW4/e2Nvb2tlZDp0LHJhdzpJQih6TWUoR1QodCkpKSxyYW5nZTplfTp7Y29va2VkOmA6JHtufToke3R9YCxyYXc6SUIoYDoke2pNZShHVChuKSl9OiR7R1QodCl9YCkscmFuZ2U6ZX19dmFyIHlfPWNsYXNzIGV4dGVuZHMgT3J7Y29uc3RydWN0b3IodCxlLGk9bnVsbCxyKXtzdXBlcihlLHIpLHRoaXMudmFsdWU9dCx0aGlzLnR5cGVQYXJhbXM9aX1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiB5XyYmdGhpcy52YWx1ZS5uYW1lPT09dC52YWx1ZS5uYW1lJiZ0aGlzLnZhbHVlLm1vZHVsZU5hbWU9PT10LnZhbHVlLm1vZHVsZU5hbWUmJnRoaXMudmFsdWUucnVudGltZT09PXQudmFsdWUucnVudGltZX1pc0NvbnN0YW50KCl7cmV0dXJuITF9dmlzaXRFeHByZXNzaW9uKHQsZSl7cmV0dXJuIHQudmlzaXRFeHRlcm5hbEV4cHIodGhpcyxlKX19LGJDPWNsYXNzIGV4dGVuZHMgT3J7Y29uc3RydWN0b3IodCxlLGk9bnVsbCxyLG8pe3N1cGVyKHJ8fGUudHlwZSxvKSx0aGlzLmNvbmRpdGlvbj10LHRoaXMuZmFsc2VDYXNlPWksdGhpcy50cnVlQ2FzZT1lfWlzRXF1aXZhbGVudCh0KXtyZXR1cm4gdCBpbnN0YW5jZW9mIGJDJiZ0aGlzLmNvbmRpdGlvbi5pc0VxdWl2YWxlbnQodC5jb25kaXRpb24pJiZ0aGlzLnRydWVDYXNlLmlzRXF1aXZhbGVudCh0LnRydWVDYXNlKSYmZnVuY3Rpb24obix0KXtyZXR1cm4gbnVsbD09bnx8bnVsbD09dD9uPT10Om4uaXNFcXVpdmFsZW50KHQpfSh0aGlzLmZhbHNlQ2FzZSx0LmZhbHNlQ2FzZSl9aXNDb25zdGFudCgpe3JldHVybiExfXZpc2l0RXhwcmVzc2lvbih0LGUpe3JldHVybiB0LnZpc2l0Q29uZGl0aW9uYWxFeHByKHRoaXMsZSl9fSx4Qz1jbGFzcyBleHRlbmRzIE9ye2NvbnN0cnVjdG9yKHQsZSl7c3VwZXIoQk1lLGUpLHRoaXMuY29uZGl0aW9uPXR9aXNFcXVpdmFsZW50KHQpe3JldHVybiB0IGluc3RhbmNlb2YgeEMmJnRoaXMuY29uZGl0aW9uLmlzRXF1aXZhbGVudCh0LmNvbmRpdGlvbil9aXNDb25zdGFudCgpe3JldHVybiExfXZpc2l0RXhwcmVzc2lvbih0LGUpe3JldHVybiB0LnZpc2l0Tm90RXhwcih0aGlzLGUpfX0saWE9Y2xhc3N7Y29uc3RydWN0b3IodCxlPW51bGwpe3RoaXMubmFtZT10LHRoaXMudHlwZT1lfWlzRXF1aXZhbGVudCh0KXtyZXR1cm4gdGhpcy5uYW1lPT09dC5uYW1lfX0scG09Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSxyLG8pe3N1cGVyKGksciksdGhpcy5wYXJhbXM9dCx0aGlzLnN0YXRlbWVudHM9ZSx0aGlzLm5hbWU9b31pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBwbSYmVWModGhpcy5wYXJhbXMsdC5wYXJhbXMpJiZVYyh0aGlzLnN0YXRlbWVudHMsdC5zdGF0ZW1lbnRzKX1pc0NvbnN0YW50KCl7cmV0dXJuITF9dmlzaXRFeHByZXNzaW9uKHQsZSl7cmV0dXJuIHQudmlzaXRGdW5jdGlvbkV4cHIodGhpcyxlKX10b0RlY2xTdG10KHQsZSl7cmV0dXJuIG5ldyB3Qyh0LHRoaXMucGFyYW1zLHRoaXMuc3RhdGVtZW50cyx0aGlzLnR5cGUsZSx0aGlzLnNvdXJjZVNwYW4pfX0sQ0M9Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSxyLG89ITApe3N1cGVyKGl8fFpDLHIpLHRoaXMub3BlcmF0b3I9dCx0aGlzLmV4cHI9ZSx0aGlzLnBhcmVucz1vfWlzRXF1aXZhbGVudCh0KXtyZXR1cm4gdCBpbnN0YW5jZW9mIENDJiZ0aGlzLm9wZXJhdG9yPT09dC5vcGVyYXRvciYmdGhpcy5leHByLmlzRXF1aXZhbGVudCh0LmV4cHIpfWlzQ29uc3RhbnQoKXtyZXR1cm4hMX12aXNpdEV4cHJlc3Npb24odCxlKXtyZXR1cm4gdC52aXNpdFVuYXJ5T3BlcmF0b3JFeHByKHRoaXMsZSl9fSxncj1jbGFzcyBleHRlbmRzIE9ye2NvbnN0cnVjdG9yKHQsZSxpLHIsbyxzPSEwKXtzdXBlcihyfHxlLnR5cGUsbyksdGhpcy5vcGVyYXRvcj10LHRoaXMucmhzPWksdGhpcy5wYXJlbnM9cyx0aGlzLmxocz1lfWlzRXF1aXZhbGVudCh0KXtyZXR1cm4gdCBpbnN0YW5jZW9mIGdyJiZ0aGlzLm9wZXJhdG9yPT09dC5vcGVyYXRvciYmdGhpcy5saHMuaXNFcXVpdmFsZW50KHQubGhzKSYmdGhpcy5yaHMuaXNFcXVpdmFsZW50KHQucmhzKX1pc0NvbnN0YW50KCl7cmV0dXJuITF9dmlzaXRFeHByZXNzaW9uKHQsZSl7cmV0dXJuIHQudmlzaXRCaW5hcnlPcGVyYXRvckV4cHIodGhpcyxlKX19LGJfPWNsYXNzIGV4dGVuZHMgT3J7Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIoaSxyKSx0aGlzLnJlY2VpdmVyPXQsdGhpcy5uYW1lPWV9aXNFcXVpdmFsZW50KHQpe3JldHVybiB0IGluc3RhbmNlb2YgYl8mJnRoaXMucmVjZWl2ZXIuaXNFcXVpdmFsZW50KHQucmVjZWl2ZXIpJiZ0aGlzLm5hbWU9PT10Lm5hbWV9aXNDb25zdGFudCgpe3JldHVybiExfXZpc2l0RXhwcmVzc2lvbih0LGUpe3JldHVybiB0LnZpc2l0UmVhZFByb3BFeHByKHRoaXMsZSl9c2V0KHQpe3JldHVybiBuZXcgdkModGhpcy5yZWNlaXZlcix0aGlzLm5hbWUsdCxudWxsLHRoaXMuc291cmNlU3Bhbil9fSxNQz1jbGFzcyBleHRlbmRzIE9ye2NvbnN0cnVjdG9yKHQsZSxpLHIpe3N1cGVyKGksciksdGhpcy5yZWNlaXZlcj10LHRoaXMuaW5kZXg9ZX1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBNQyYmdGhpcy5yZWNlaXZlci5pc0VxdWl2YWxlbnQodC5yZWNlaXZlcikmJnRoaXMuaW5kZXguaXNFcXVpdmFsZW50KHQuaW5kZXgpfWlzQ29uc3RhbnQoKXtyZXR1cm4hMX12aXNpdEV4cHJlc3Npb24odCxlKXtyZXR1cm4gdC52aXNpdFJlYWRLZXlFeHByKHRoaXMsZSl9c2V0KHQpe3JldHVybiBuZXcgX0ModGhpcy5yZWNlaXZlcix0aGlzLmluZGV4LHQsbnVsbCx0aGlzLnNvdXJjZVNwYW4pfX0saG09Y2xhc3MgZXh0ZW5kcyBPcntjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIoZSxpKSx0aGlzLmVudHJpZXM9dH1pc0NvbnN0YW50KCl7cmV0dXJuIHRoaXMuZW50cmllcy5ldmVyeSh0PT50LmlzQ29uc3RhbnQoKSl9aXNFcXVpdmFsZW50KHQpe3JldHVybiB0IGluc3RhbmNlb2YgaG0mJlVjKHRoaXMuZW50cmllcyx0LmVudHJpZXMpfXZpc2l0RXhwcmVzc2lvbih0LGUpe3JldHVybiB0LnZpc2l0TGl0ZXJhbEFycmF5RXhwcih0aGlzLGUpfX0sdEQ9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3RoaXMua2V5PXQsdGhpcy52YWx1ZT1lLHRoaXMucXVvdGVkPWl9aXNFcXVpdmFsZW50KHQpe3JldHVybiB0aGlzLmtleT09PXQua2V5JiZ0aGlzLnZhbHVlLmlzRXF1aXZhbGVudCh0LnZhbHVlKX19LHhfPWNsYXNzIGV4dGVuZHMgT3J7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKGUsaSksdGhpcy5lbnRyaWVzPXQsdGhpcy52YWx1ZVR5cGU9bnVsbCxlJiYodGhpcy52YWx1ZVR5cGU9ZS52YWx1ZVR5cGUpfWlzRXF1aXZhbGVudCh0KXtyZXR1cm4gdCBpbnN0YW5jZW9mIHhfJiZVYyh0aGlzLmVudHJpZXMsdC5lbnRyaWVzKX1pc0NvbnN0YW50KCl7cmV0dXJuIHRoaXMuZW50cmllcy5ldmVyeSh0PT50LnZhbHVlLmlzQ29uc3RhbnQoKSl9dmlzaXRFeHByZXNzaW9uKHQsZSl7cmV0dXJuIHQudmlzaXRMaXRlcmFsTWFwRXhwcih0aGlzLGUpfX0sUEI9bmV3IGNsKG51bGwsbnVsbCxudWxsKSxXVD1uZXcgY2wobnVsbCxQYSxudWxsKSxsbD0oKCk9PntyZXR1cm4obj1sbHx8KGxsPXt9KSlbbi5Ob25lPTBdPSJOb25lIixuW24uRmluYWw9MV09IkZpbmFsIixuW24uUHJpdmF0ZT0yXT0iUHJpdmF0ZSIsbltuLkV4cG9ydGVkPTRdPSJFeHBvcnRlZCIsbltuLlN0YXRpYz04XT0iU3RhdGljIixsbDt2YXIgbn0pKCksUkI9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3RoaXMudGV4dD10LHRoaXMubXVsdGlsaW5lPWUsdGhpcy50cmFpbGluZ05ld2xpbmU9aX10b1N0cmluZygpe3JldHVybiB0aGlzLm11bHRpbGluZT9gICR7dGhpcy50ZXh0fSBgOnRoaXMudGV4dH19LG5EPWNsYXNzIGV4dGVuZHMgUkJ7Y29uc3RydWN0b3IodCl7c3VwZXIoIiIsITAsITApLHRoaXMudGFncz10fXRvU3RyaW5nKCl7cmV0dXJuIGZ1bmN0aW9uKG4pe2lmKDA9PT1uLmxlbmd0aClyZXR1cm4iIjtpZigxPT09bi5sZW5ndGgmJm5bMF0udGFnTmFtZSYmIW5bMF0udGV4dClyZXR1cm5gKiR7TVgoblswXSl9IGA7bGV0IHQ9IipcbiI7Zm9yKGxldCBlIG9mIG4pdCs9IiAqIix0Kz1NWChlKS5yZXBsYWNlKC9cbi9nLCJcbiAqICIpLHQrPSJcbiI7cmV0dXJuIHQrPSIgIix0fSh0aGlzLnRhZ3MpfX0sZm09Y2xhc3N7Y29uc3RydWN0b3IodD1sbC5Ob25lLGU9bnVsbCxpKXt0aGlzLm1vZGlmaWVycz10LHRoaXMuc291cmNlU3Bhbj1lLHRoaXMubGVhZGluZ0NvbW1lbnRzPWl9aGFzTW9kaWZpZXIodCl7cmV0dXJuIDAhPSh0aGlzLm1vZGlmaWVycyZ0KX1hZGRMZWFkaW5nQ29tbWVudCh0KXt0aGlzLmxlYWRpbmdDb21tZW50cz10aGlzLmxlYWRpbmdDb21tZW50cz8/W10sdGhpcy5sZWFkaW5nQ29tbWVudHMucHVzaCh0KX19LFZ1PWNsYXNzIGV4dGVuZHMgZm17Y29uc3RydWN0b3IodCxlLGkscixvLHMpe3N1cGVyKHIsbyxzKSx0aGlzLm5hbWU9dCx0aGlzLnZhbHVlPWUsdGhpcy50eXBlPWl8fGUmJmUudHlwZXx8bnVsbH1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBWdSYmdGhpcy5uYW1lPT09dC5uYW1lJiYodGhpcy52YWx1ZT8hIXQudmFsdWUmJnRoaXMudmFsdWUuaXNFcXVpdmFsZW50KHQudmFsdWUpOiF0LnZhbHVlKX12aXNpdFN0YXRlbWVudCh0LGUpe3JldHVybiB0LnZpc2l0RGVjbGFyZVZhclN0bXQodGhpcyxlKX19LHdDPWNsYXNzIGV4dGVuZHMgZm17Y29uc3RydWN0b3IodCxlLGkscixvLHMsYSl7c3VwZXIobyxzLGEpLHRoaXMubmFtZT10LHRoaXMucGFyYW1zPWUsdGhpcy5zdGF0ZW1lbnRzPWksdGhpcy50eXBlPXJ8fG51bGx9aXNFcXVpdmFsZW50KHQpe3JldHVybiB0IGluc3RhbmNlb2Ygd0MmJlVjKHRoaXMucGFyYW1zLHQucGFyYW1zKSYmVWModGhpcy5zdGF0ZW1lbnRzLHQuc3RhdGVtZW50cyl9dmlzaXRTdGF0ZW1lbnQodCxlKXtyZXR1cm4gdC52aXNpdERlY2xhcmVGdW5jdGlvblN0bXQodGhpcyxlKX19LEh1PWNsYXNzIGV4dGVuZHMgZm17Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKGxsLk5vbmUsZSxpKSx0aGlzLmV4cHI9dH1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBIdSYmdGhpcy5leHByLmlzRXF1aXZhbGVudCh0LmV4cHIpfXZpc2l0U3RhdGVtZW50KHQsZSl7cmV0dXJuIHQudmlzaXRFeHByZXNzaW9uU3RtdCh0aGlzLGUpfX0sRG89Y2xhc3MgZXh0ZW5kcyBmbXtjb25zdHJ1Y3Rvcih0LGU9bnVsbCxpKXtzdXBlcihsbC5Ob25lLGUsaSksdGhpcy52YWx1ZT10fWlzRXF1aXZhbGVudCh0KXtyZXR1cm4gdCBpbnN0YW5jZW9mIERvJiZ0aGlzLnZhbHVlLmlzRXF1aXZhbGVudCh0LnZhbHVlKX12aXNpdFN0YXRlbWVudCh0LGUpe3JldHVybiB0LnZpc2l0UmV0dXJuU3RtdCh0aGlzLGUpfX0sU0M9Y2xhc3MgZXh0ZW5kcyBmbXtjb25zdHJ1Y3Rvcih0LGUsaT1bXSxyLG8pe3N1cGVyKGxsLk5vbmUscixvKSx0aGlzLmNvbmRpdGlvbj10LHRoaXMudHJ1ZUNhc2U9ZSx0aGlzLmZhbHNlQ2FzZT1pfWlzRXF1aXZhbGVudCh0KXtyZXR1cm4gdCBpbnN0YW5jZW9mIFNDJiZ0aGlzLmNvbmRpdGlvbi5pc0VxdWl2YWxlbnQodC5jb25kaXRpb24pJiZVYyh0aGlzLnRydWVDYXNlLHQudHJ1ZUNhc2UpJiZVYyh0aGlzLmZhbHNlQ2FzZSx0LmZhbHNlQ2FzZSl9dmlzaXRTdGF0ZW1lbnQodCxlKXtyZXR1cm4gdC52aXNpdElmU3RtdCh0aGlzLGUpfX07ZnVuY3Rpb24gUmkobix0LGUpe3JldHVybiBuZXcgdW0obix0LGUpfWZ1bmN0aW9uIFRuKG4sdD1udWxsLGUpe3JldHVybiBuZXcgeV8obixudWxsLHQsZSl9ZnVuY3Rpb24gdWwobix0LGUpe3JldHVybiBuZXcgVmMobix0LGUpfWZ1bmN0aW9uIEJWKG4pe3JldHVybiBuZXcgdl8obil9ZnVuY3Rpb24gX3Iobix0LGUpe3JldHVybiBuZXcgaG0obix0LGUpfWZ1bmN0aW9uIHFsKG4sdD1udWxsKXtyZXR1cm4gbmV3IHhfKG4ubWFwKGU9Pm5ldyB0RChlLmtleSxlLnZhbHVlLGUucXVvdGVkKSksdCxudWxsKX1mdW5jdGlvbiByYShuLHQsZSxpLHIpe3JldHVybiBuZXcgcG0obix0LGUsaSxyKX1mdW5jdGlvbiBWVihuLHQsZSxpLHIpe3JldHVybiBuZXcgU0Mobix0LGUsaSxyKX1mdW5jdGlvbiBDWChuLHQsZSxpKXtyZXR1cm4gbmV3IHlDKG4sdCxlLGkpfWZ1bmN0aW9uIGh0KG4sdCxlKXtyZXR1cm4gbmV3IGNsKG4sdCxlKX1mdW5jdGlvbiBWUShuKXtyZXR1cm4gbiBpbnN0YW5jZW9mIGNsJiZudWxsPT09bi52YWx1ZX1mdW5jdGlvbiBNWChuKXtsZXQgdD0iIjtpZihuLnRhZ05hbWUmJih0Kz1gIEAke24udGFnTmFtZX1gKSxuLnRleHQpe2lmKG4udGV4dC5tYXRjaCgvXC9cKnxcKlwvLykpdGhyb3cgbmV3IEVycm9yKCdKU0RvYyB0ZXh0IGNhbm5vdCBjb250YWluICIvKiIgYW5kICIqLyInKTt0Kz0iICIrbi50ZXh0LnJlcGxhY2UoL0AvZywiXFxAIil9cmV0dXJuIHR9dmFyIHdYPVJpKCI8dW5rbm93bj4iKSxIUT17fSxDXz1jbGFzcyBleHRlbmRzIE9ye2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQudHlwZSksdGhpcy5yZXNvbHZlZD10LHRoaXMub3JpZ2luYWw9dH12aXNpdEV4cHJlc3Npb24odCxlKXtyZXR1cm4gZT09PUhRP3RoaXMub3JpZ2luYWwudmlzaXRFeHByZXNzaW9uKHQsZSk6dGhpcy5yZXNvbHZlZC52aXNpdEV4cHJlc3Npb24odCxlKX1pc0VxdWl2YWxlbnQodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBDXyYmdGhpcy5yZXNvbHZlZC5pc0VxdWl2YWxlbnQodC5yZXNvbHZlZCl9aXNDb25zdGFudCgpe3JldHVybiEwfWZpeHVwKHQpe3RoaXMucmVzb2x2ZWQ9dCx0aGlzLnNoYXJlZD0hMH19LGlEPWNsYXNze2NvbnN0cnVjdG9yKHQ9ITEpe3RoaXMuaXNDbG9zdXJlQ29tcGlsZXJFbmFibGVkPXQsdGhpcy5zdGF0ZW1lbnRzPVtdLHRoaXMubGl0ZXJhbHM9bmV3IE1hcCx0aGlzLmxpdGVyYWxGYWN0b3JpZXM9bmV3IE1hcCx0aGlzLm5leHROYW1lSW5kZXg9MH1nZXRDb25zdExpdGVyYWwodCxlKXtpZih0IGluc3RhbmNlb2YgY2wmJiFTWCh0KXx8dCBpbnN0YW5jZW9mIENfKXJldHVybiB0O2xldCBpPXRoaXMua2V5T2YodCkscj10aGlzLmxpdGVyYWxzLmdldChpKSxvPSExO2lmKHJ8fChyPW5ldyBDXyh0KSx0aGlzLmxpdGVyYWxzLnNldChpLHIpLG89ITApLCFvJiYhci5zaGFyZWR8fG8mJmUpe2xldCBhLGwscz10aGlzLmZyZXNoTmFtZSgpO3RoaXMuaXNDbG9zdXJlQ29tcGlsZXJFbmFibGVkJiZTWCh0KT8oYT1SaShzKS5zZXQobmV3IHBtKFtdLFtuZXcgRG8odCldKSksbD1SaShzKS5jYWxsRm4oW10pKTooYT1SaShzKS5zZXQodCksbD1SaShzKSksdGhpcy5zdGF0ZW1lbnRzLnB1c2goYS50b0RlY2xTdG10KFBhLGxsLkZpbmFsKSksci5maXh1cChsKX1yZXR1cm4gcn1nZXRMaXRlcmFsRmFjdG9yeSh0KXtpZih0IGluc3RhbmNlb2YgaG0pe2xldCBlPXQuZW50cmllcy5tYXAocj0+ci5pc0NvbnN0YW50KCk/cjp3WCksaT10aGlzLmtleU9mKF9yKGUpKTtyZXR1cm4gdGhpcy5fZ2V0TGl0ZXJhbEZhY3RvcnkoaSx0LmVudHJpZXMscj0+X3IocikpfXtsZXQgZT1xbCh0LmVudHJpZXMubWFwKHI9Pih7a2V5OnIua2V5LHZhbHVlOnIudmFsdWUuaXNDb25zdGFudCgpP3IudmFsdWU6d1gscXVvdGVkOnIucXVvdGVkfSkpKSxpPXRoaXMua2V5T2YoZSk7cmV0dXJuIHRoaXMuX2dldExpdGVyYWxGYWN0b3J5KGksdC5lbnRyaWVzLm1hcChyPT5yLnZhbHVlKSxyPT5xbChyLm1hcCgobyxzKT0+KHtrZXk6dC5lbnRyaWVzW3NdLmtleSx2YWx1ZTpvLHF1b3RlZDp0LmVudHJpZXNbc10ucXVvdGVkfSkpKSl9fV9nZXRMaXRlcmFsRmFjdG9yeSh0LGUsaSl7bGV0IHI9dGhpcy5saXRlcmFsRmFjdG9yaWVzLmdldCh0KSxvPWUuZmlsdGVyKHM9PiFzLmlzQ29uc3RhbnQoKSk7aWYoIXIpe2xldCBzPWUubWFwKCh1LGQpPT51LmlzQ29uc3RhbnQoKT90aGlzLmdldENvbnN0TGl0ZXJhbCh1LCEwKTpSaShgYSR7ZH1gKSksbD1yYShzLmZpbHRlcihLTWUpLm1hcCh1PT5uZXcgaWEodS5uYW1lLFZfKSksW25ldyBEbyhpKHMpKV0sUGEpLGM9dGhpcy5mcmVzaE5hbWUoKTt0aGlzLnN0YXRlbWVudHMucHVzaChSaShjKS5zZXQobCkudG9EZWNsU3RtdChQYSxsbC5GaW5hbCkpLHI9UmkoYyksdGhpcy5saXRlcmFsRmFjdG9yaWVzLnNldCh0LHIpfXJldHVybntsaXRlcmFsRmFjdG9yeTpyLGxpdGVyYWxGYWN0b3J5QXJndW1lbnRzOm99fXVuaXF1ZU5hbWUodCl7cmV0dXJuYCR7dH0ke3RoaXMubmV4dE5hbWVJbmRleCsrfWB9ZnJlc2hOYW1lKCl7cmV0dXJuIHRoaXMudW5pcXVlTmFtZSgiX2MiKX1rZXlPZih0KXtyZXR1cm4gdC52aXNpdEV4cHJlc3Npb24obmV3IE9CLEhRKX19LE9CPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy52aXNpdFdyYXBwZWROb2RlRXhwcj1ycyx0aGlzLnZpc2l0V3JpdGVWYXJFeHByPXJzLHRoaXMudmlzaXRXcml0ZUtleUV4cHI9cnMsdGhpcy52aXNpdFdyaXRlUHJvcEV4cHI9cnMsdGhpcy52aXNpdEludm9rZUZ1bmN0aW9uRXhwcj1ycyx0aGlzLnZpc2l0VGFnZ2VkVGVtcGxhdGVFeHByPXJzLHRoaXMudmlzaXRJbnN0YW50aWF0ZUV4cHI9cnMsdGhpcy52aXNpdENvbmRpdGlvbmFsRXhwcj1ycyx0aGlzLnZpc2l0Tm90RXhwcj1ycyx0aGlzLnZpc2l0QXNzZXJ0Tm90TnVsbEV4cHI9cnMsdGhpcy52aXNpdENhc3RFeHByPXJzLHRoaXMudmlzaXRGdW5jdGlvbkV4cHI9cnMsdGhpcy52aXNpdFVuYXJ5T3BlcmF0b3JFeHByPXJzLHRoaXMudmlzaXRCaW5hcnlPcGVyYXRvckV4cHI9cnMsdGhpcy52aXNpdFJlYWRQcm9wRXhwcj1ycyx0aGlzLnZpc2l0UmVhZEtleUV4cHI9cnMsdGhpcy52aXNpdENvbW1hRXhwcj1ycyx0aGlzLnZpc2l0TG9jYWxpemVkU3RyaW5nPXJzfXZpc2l0TGl0ZXJhbEV4cHIodCl7cmV0dXJuYCR7InN0cmluZyI9PXR5cGVvZiB0LnZhbHVlPyciJyt0LnZhbHVlKyciJzp0LnZhbHVlfWB9dmlzaXRMaXRlcmFsQXJyYXlFeHByKHQsZSl7cmV0dXJuYFske3QuZW50cmllcy5tYXAoaT0+aS52aXNpdEV4cHJlc3Npb24odGhpcyxlKSkuam9pbigiLCIpfV1gfXZpc2l0TGl0ZXJhbE1hcEV4cHIodCxlKXtyZXR1cm5geyR7dC5lbnRyaWVzLm1hcChvPT5gJHsobz0+e2xldCBzPW8ucXVvdGVkPyciJzoiIjtyZXR1cm5gJHtzfSR7by5rZXl9JHtzfWB9KShvKX06JHtvLnZhbHVlLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpfWApLmpvaW4oIiwiKX1gfXZpc2l0RXh0ZXJuYWxFeHByKHQpe3JldHVybiB0LnZhbHVlLm1vZHVsZU5hbWU/YEVYOiR7dC52YWx1ZS5tb2R1bGVOYW1lfToke3QudmFsdWUubmFtZX1gOmBFWDoke3QudmFsdWUucnVudGltZS5uYW1lfWB9dmlzaXRSZWFkVmFyRXhwcih0KXtyZXR1cm5gVkFSOiR7dC5uYW1lfWB9dmlzaXRUeXBlb2ZFeHByKHQsZSl7cmV0dXJuYFRZUEVPRjoke3QuZXhwci52aXNpdEV4cHJlc3Npb24odGhpcyxlKX1gfX07ZnVuY3Rpb24gcnMobil7dGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIHN0YXRlOiBWaXNpdG9yICR7dGhpcy5jb25zdHJ1Y3Rvci5uYW1lfSBkb2Vzbid0IGhhbmRsZSAke24uY29uc3RydWN0b3IubmFtZX1gKX1mdW5jdGlvbiBLTWUobil7cmV0dXJuIG4gaW5zdGFuY2VvZiB1bX1mdW5jdGlvbiBTWChuKXtyZXR1cm4gbiBpbnN0YW5jZW9mIGNsJiYic3RyaW5nIj09dHlwZW9mIG4udmFsdWUmJm4udmFsdWUubGVuZ3RoPj01MH12YXIgeGU9IkBhbmd1bGFyL2NvcmUiLHRlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLk5FV19NRVRIT0Q9ImZhY3RvcnkiLG4uVFJBTlNGT1JNX01FVEhPRD0idHJhbnNmb3JtIixuLlBBVENIX0RFUFM9InBhdGNoZWREZXBzIixuLmNvcmU9e25hbWU6bnVsbCxtb2R1bGVOYW1lOnhlfSxuLm5hbWVzcGFjZUhUTUw9e25hbWU6Ilx1MDI3NVx1MDI3NW5hbWVzcGFjZUhUTUwiLG1vZHVsZU5hbWU6eGV9LG4ubmFtZXNwYWNlTWF0aE1MPXtuYW1lOiJcdTAyNzVcdTAyNzVuYW1lc3BhY2VNYXRoTUwiLG1vZHVsZU5hbWU6eGV9LG4ubmFtZXNwYWNlU1ZHPXtuYW1lOiJcdTAyNzVcdTAyNzVuYW1lc3BhY2VTVkciLG1vZHVsZU5hbWU6eGV9LG4uZWxlbWVudD17bmFtZToiXHUwMjc1XHUwMjc1ZWxlbWVudCIsbW9kdWxlTmFtZTp4ZX0sbi5lbGVtZW50U3RhcnQ9e25hbWU6Ilx1MDI3NVx1MDI3NWVsZW1lbnRTdGFydCIsbW9kdWxlTmFtZTp4ZX0sbi5lbGVtZW50RW5kPXtuYW1lOiJcdTAyNzVcdTAyNzVlbGVtZW50RW5kIixtb2R1bGVOYW1lOnhlfSxuLmFkdmFuY2U9e25hbWU6Ilx1MDI3NVx1MDI3NWFkdmFuY2UiLG1vZHVsZU5hbWU6eGV9LG4uc3ludGhldGljSG9zdFByb3BlcnR5PXtuYW1lOiJcdTAyNzVcdTAyNzVzeW50aGV0aWNIb3N0UHJvcGVydHkiLG1vZHVsZU5hbWU6eGV9LG4uc3ludGhldGljSG9zdExpc3RlbmVyPXtuYW1lOiJcdTAyNzVcdTAyNzVzeW50aGV0aWNIb3N0TGlzdGVuZXIiLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlPXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGUiLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlSW50ZXJwb2xhdGUxPXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGVJbnRlcnBvbGF0ZTEiLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlSW50ZXJwb2xhdGUyPXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGVJbnRlcnBvbGF0ZTIiLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlSW50ZXJwb2xhdGUzPXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGVJbnRlcnBvbGF0ZTMiLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlSW50ZXJwb2xhdGU0PXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGVJbnRlcnBvbGF0ZTQiLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlSW50ZXJwb2xhdGU1PXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGVJbnRlcnBvbGF0ZTUiLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlSW50ZXJwb2xhdGU2PXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGVJbnRlcnBvbGF0ZTYiLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlSW50ZXJwb2xhdGU3PXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGVJbnRlcnBvbGF0ZTciLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlSW50ZXJwb2xhdGU4PXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGVJbnRlcnBvbGF0ZTgiLG1vZHVsZU5hbWU6eGV9LG4uYXR0cmlidXRlSW50ZXJwb2xhdGVWPXtuYW1lOiJcdTAyNzVcdTAyNzVhdHRyaWJ1dGVJbnRlcnBvbGF0ZVYiLG1vZHVsZU5hbWU6eGV9LG4uY2xhc3NQcm9wPXtuYW1lOiJcdTAyNzVcdTAyNzVjbGFzc1Byb3AiLG1vZHVsZU5hbWU6eGV9LG4uZWxlbWVudENvbnRhaW5lclN0YXJ0PXtuYW1lOiJcdTAyNzVcdTAyNzVlbGVtZW50Q29udGFpbmVyU3RhcnQiLG1vZHVsZU5hbWU6eGV9LG4uZWxlbWVudENvbnRhaW5lckVuZD17bmFtZToiXHUwMjc1XHUwMjc1ZWxlbWVudENvbnRhaW5lckVuZCIsbW9kdWxlTmFtZTp4ZX0sbi5lbGVtZW50Q29udGFpbmVyPXtuYW1lOiJcdTAyNzVcdTAyNzVlbGVtZW50Q29udGFpbmVyIixtb2R1bGVOYW1lOnhlfSxuLnN0eWxlTWFwPXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZU1hcCIsbW9kdWxlTmFtZTp4ZX0sbi5zdHlsZU1hcEludGVycG9sYXRlMT17bmFtZToiXHUwMjc1XHUwMjc1c3R5bGVNYXBJbnRlcnBvbGF0ZTEiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVNYXBJbnRlcnBvbGF0ZTI9e25hbWU6Ilx1MDI3NVx1MDI3NXN0eWxlTWFwSW50ZXJwb2xhdGUyIixtb2R1bGVOYW1lOnhlfSxuLnN0eWxlTWFwSW50ZXJwb2xhdGUzPXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZU1hcEludGVycG9sYXRlMyIsbW9kdWxlTmFtZTp4ZX0sbi5zdHlsZU1hcEludGVycG9sYXRlND17bmFtZToiXHUwMjc1XHUwMjc1c3R5bGVNYXBJbnRlcnBvbGF0ZTQiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVNYXBJbnRlcnBvbGF0ZTU9e25hbWU6Ilx1MDI3NVx1MDI3NXN0eWxlTWFwSW50ZXJwb2xhdGU1Iixtb2R1bGVOYW1lOnhlfSxuLnN0eWxlTWFwSW50ZXJwb2xhdGU2PXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZU1hcEludGVycG9sYXRlNiIsbW9kdWxlTmFtZTp4ZX0sbi5zdHlsZU1hcEludGVycG9sYXRlNz17bmFtZToiXHUwMjc1XHUwMjc1c3R5bGVNYXBJbnRlcnBvbGF0ZTciLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVNYXBJbnRlcnBvbGF0ZTg9e25hbWU6Ilx1MDI3NVx1MDI3NXN0eWxlTWFwSW50ZXJwb2xhdGU4Iixtb2R1bGVOYW1lOnhlfSxuLnN0eWxlTWFwSW50ZXJwb2xhdGVWPXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZU1hcEludGVycG9sYXRlViIsbW9kdWxlTmFtZTp4ZX0sbi5jbGFzc01hcD17bmFtZToiXHUwMjc1XHUwMjc1Y2xhc3NNYXAiLG1vZHVsZU5hbWU6eGV9LG4uY2xhc3NNYXBJbnRlcnBvbGF0ZTE9e25hbWU6Ilx1MDI3NVx1MDI3NWNsYXNzTWFwSW50ZXJwb2xhdGUxIixtb2R1bGVOYW1lOnhlfSxuLmNsYXNzTWFwSW50ZXJwb2xhdGUyPXtuYW1lOiJcdTAyNzVcdTAyNzVjbGFzc01hcEludGVycG9sYXRlMiIsbW9kdWxlTmFtZTp4ZX0sbi5jbGFzc01hcEludGVycG9sYXRlMz17bmFtZToiXHUwMjc1XHUwMjc1Y2xhc3NNYXBJbnRlcnBvbGF0ZTMiLG1vZHVsZU5hbWU6eGV9LG4uY2xhc3NNYXBJbnRlcnBvbGF0ZTQ9e25hbWU6Ilx1MDI3NVx1MDI3NWNsYXNzTWFwSW50ZXJwb2xhdGU0Iixtb2R1bGVOYW1lOnhlfSxuLmNsYXNzTWFwSW50ZXJwb2xhdGU1PXtuYW1lOiJcdTAyNzVcdTAyNzVjbGFzc01hcEludGVycG9sYXRlNSIsbW9kdWxlTmFtZTp4ZX0sbi5jbGFzc01hcEludGVycG9sYXRlNj17bmFtZToiXHUwMjc1XHUwMjc1Y2xhc3NNYXBJbnRlcnBvbGF0ZTYiLG1vZHVsZU5hbWU6eGV9LG4uY2xhc3NNYXBJbnRlcnBvbGF0ZTc9e25hbWU6Ilx1MDI3NVx1MDI3NWNsYXNzTWFwSW50ZXJwb2xhdGU3Iixtb2R1bGVOYW1lOnhlfSxuLmNsYXNzTWFwSW50ZXJwb2xhdGU4PXtuYW1lOiJcdTAyNzVcdTAyNzVjbGFzc01hcEludGVycG9sYXRlOCIsbW9kdWxlTmFtZTp4ZX0sbi5jbGFzc01hcEludGVycG9sYXRlVj17bmFtZToiXHUwMjc1XHUwMjc1Y2xhc3NNYXBJbnRlcnBvbGF0ZVYiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wPXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3AiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wSW50ZXJwb2xhdGUxPXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTEiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wSW50ZXJwb2xhdGUyPXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTIiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wSW50ZXJwb2xhdGUzPXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTMiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wSW50ZXJwb2xhdGU0PXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTQiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wSW50ZXJwb2xhdGU1PXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTUiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wSW50ZXJwb2xhdGU2PXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTYiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wSW50ZXJwb2xhdGU3PXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTciLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wSW50ZXJwb2xhdGU4PXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZTgiLG1vZHVsZU5hbWU6eGV9LG4uc3R5bGVQcm9wSW50ZXJwb2xhdGVWPXtuYW1lOiJcdTAyNzVcdTAyNzVzdHlsZVByb3BJbnRlcnBvbGF0ZVYiLG1vZHVsZU5hbWU6eGV9LG4ubmV4dENvbnRleHQ9e25hbWU6Ilx1MDI3NVx1MDI3NW5leHRDb250ZXh0Iixtb2R1bGVOYW1lOnhlfSxuLnJlc2V0Vmlldz17bmFtZToiXHUwMjc1XHUwMjc1cmVzZXRWaWV3Iixtb2R1bGVOYW1lOnhlfSxuLnRlbXBsYXRlQ3JlYXRlPXtuYW1lOiJcdTAyNzVcdTAyNzV0ZW1wbGF0ZSIsbW9kdWxlTmFtZTp4ZX0sbi50ZXh0PXtuYW1lOiJcdTAyNzVcdTAyNzV0ZXh0Iixtb2R1bGVOYW1lOnhlfSxuLmVuYWJsZUJpbmRpbmdzPXtuYW1lOiJcdTAyNzVcdTAyNzVlbmFibGVCaW5kaW5ncyIsbW9kdWxlTmFtZTp4ZX0sbi5kaXNhYmxlQmluZGluZ3M9e25hbWU6Ilx1MDI3NVx1MDI3NWRpc2FibGVCaW5kaW5ncyIsbW9kdWxlTmFtZTp4ZX0sbi5nZXRDdXJyZW50Vmlldz17bmFtZToiXHUwMjc1XHUwMjc1Z2V0Q3VycmVudFZpZXciLG1vZHVsZU5hbWU6eGV9LG4udGV4dEludGVycG9sYXRlPXtuYW1lOiJcdTAyNzVcdTAyNzV0ZXh0SW50ZXJwb2xhdGUiLG1vZHVsZU5hbWU6eGV9LG4udGV4dEludGVycG9sYXRlMT17bmFtZToiXHUwMjc1XHUwMjc1dGV4dEludGVycG9sYXRlMSIsbW9kdWxlTmFtZTp4ZX0sbi50ZXh0SW50ZXJwb2xhdGUyPXtuYW1lOiJcdTAyNzVcdTAyNzV0ZXh0SW50ZXJwb2xhdGUyIixtb2R1bGVOYW1lOnhlfSxuLnRleHRJbnRlcnBvbGF0ZTM9e25hbWU6Ilx1MDI3NVx1MDI3NXRleHRJbnRlcnBvbGF0ZTMiLG1vZHVsZU5hbWU6eGV9LG4udGV4dEludGVycG9sYXRlND17bmFtZToiXHUwMjc1XHUwMjc1dGV4dEludGVycG9sYXRlNCIsbW9kdWxlTmFtZTp4ZX0sbi50ZXh0SW50ZXJwb2xhdGU1PXtuYW1lOiJcdTAyNzVcdTAyNzV0ZXh0SW50ZXJwb2xhdGU1Iixtb2R1bGVOYW1lOnhlfSxuLnRleHRJbnRlcnBvbGF0ZTY9e25hbWU6Ilx1MDI3NVx1MDI3NXRleHRJbnRlcnBvbGF0ZTYiLG1vZHVsZU5hbWU6eGV9LG4udGV4dEludGVycG9sYXRlNz17bmFtZToiXHUwMjc1XHUwMjc1dGV4dEludGVycG9sYXRlNyIsbW9kdWxlTmFtZTp4ZX0sbi50ZXh0SW50ZXJwb2xhdGU4PXtuYW1lOiJcdTAyNzVcdTAyNzV0ZXh0SW50ZXJwb2xhdGU4Iixtb2R1bGVOYW1lOnhlfSxuLnRleHRJbnRlcnBvbGF0ZVY9e25hbWU6Ilx1MDI3NVx1MDI3NXRleHRJbnRlcnBvbGF0ZVYiLG1vZHVsZU5hbWU6eGV9LG4ucmVzdG9yZVZpZXc9e25hbWU6Ilx1MDI3NVx1MDI3NXJlc3RvcmVWaWV3Iixtb2R1bGVOYW1lOnhlfSxuLnB1cmVGdW5jdGlvbjA9e25hbWU6Ilx1MDI3NVx1MDI3NXB1cmVGdW5jdGlvbjAiLG1vZHVsZU5hbWU6eGV9LG4ucHVyZUZ1bmN0aW9uMT17bmFtZToiXHUwMjc1XHUwMjc1cHVyZUZ1bmN0aW9uMSIsbW9kdWxlTmFtZTp4ZX0sbi5wdXJlRnVuY3Rpb24yPXtuYW1lOiJcdTAyNzVcdTAyNzVwdXJlRnVuY3Rpb24yIixtb2R1bGVOYW1lOnhlfSxuLnB1cmVGdW5jdGlvbjM9e25hbWU6Ilx1MDI3NVx1MDI3NXB1cmVGdW5jdGlvbjMiLG1vZHVsZU5hbWU6eGV9LG4ucHVyZUZ1bmN0aW9uND17bmFtZToiXHUwMjc1XHUwMjc1cHVyZUZ1bmN0aW9uNCIsbW9kdWxlTmFtZTp4ZX0sbi5wdXJlRnVuY3Rpb241PXtuYW1lOiJcdTAyNzVcdTAyNzVwdXJlRnVuY3Rpb241Iixtb2R1bGVOYW1lOnhlfSxuLnB1cmVGdW5jdGlvbjY9e25hbWU6Ilx1MDI3NVx1MDI3NXB1cmVGdW5jdGlvbjYiLG1vZHVsZU5hbWU6eGV9LG4ucHVyZUZ1bmN0aW9uNz17bmFtZToiXHUwMjc1XHUwMjc1cHVyZUZ1bmN0aW9uNyIsbW9kdWxlTmFtZTp4ZX0sbi5wdXJlRnVuY3Rpb244PXtuYW1lOiJcdTAyNzVcdTAyNzVwdXJlRnVuY3Rpb244Iixtb2R1bGVOYW1lOnhlfSxuLnB1cmVGdW5jdGlvblY9e25hbWU6Ilx1MDI3NVx1MDI3NXB1cmVGdW5jdGlvblYiLG1vZHVsZU5hbWU6eGV9LG4ucGlwZUJpbmQxPXtuYW1lOiJcdTAyNzVcdTAyNzVwaXBlQmluZDEiLG1vZHVsZU5hbWU6eGV9LG4ucGlwZUJpbmQyPXtuYW1lOiJcdTAyNzVcdTAyNzVwaXBlQmluZDIiLG1vZHVsZU5hbWU6eGV9LG4ucGlwZUJpbmQzPXtuYW1lOiJcdTAyNzVcdTAyNzVwaXBlQmluZDMiLG1vZHVsZU5hbWU6eGV9LG4ucGlwZUJpbmQ0PXtuYW1lOiJcdTAyNzVcdTAyNzVwaXBlQmluZDQiLG1vZHVsZU5hbWU6eGV9LG4ucGlwZUJpbmRWPXtuYW1lOiJcdTAyNzVcdTAyNzVwaXBlQmluZFYiLG1vZHVsZU5hbWU6eGV9LG4uaG9zdFByb3BlcnR5PXtuYW1lOiJcdTAyNzVcdTAyNzVob3N0UHJvcGVydHkiLG1vZHVsZU5hbWU6eGV9LG4ucHJvcGVydHk9e25hbWU6Ilx1MDI3NVx1MDI3NXByb3BlcnR5Iixtb2R1bGVOYW1lOnhlfSxuLnByb3BlcnR5SW50ZXJwb2xhdGU9e25hbWU6Ilx1MDI3NVx1MDI3NXByb3BlcnR5SW50ZXJwb2xhdGUiLG1vZHVsZU5hbWU6eGV9LG4ucHJvcGVydHlJbnRlcnBvbGF0ZTE9e25hbWU6Ilx1MDI3NVx1MDI3NXByb3BlcnR5SW50ZXJwb2xhdGUxIixtb2R1bGVOYW1lOnhlfSxuLnByb3BlcnR5SW50ZXJwb2xhdGUyPXtuYW1lOiJcdTAyNzVcdTAyNzVwcm9wZXJ0eUludGVycG9sYXRlMiIsbW9kdWxlTmFtZTp4ZX0sbi5wcm9wZXJ0eUludGVycG9sYXRlMz17bmFtZToiXHUwMjc1XHUwMjc1cHJvcGVydHlJbnRlcnBvbGF0ZTMiLG1vZHVsZU5hbWU6eGV9LG4ucHJvcGVydHlJbnRlcnBvbGF0ZTQ9e25hbWU6Ilx1MDI3NVx1MDI3NXByb3BlcnR5SW50ZXJwb2xhdGU0Iixtb2R1bGVOYW1lOnhlfSxuLnByb3BlcnR5SW50ZXJwb2xhdGU1PXtuYW1lOiJcdTAyNzVcdTAyNzVwcm9wZXJ0eUludGVycG9sYXRlNSIsbW9kdWxlTmFtZTp4ZX0sbi5wcm9wZXJ0eUludGVycG9sYXRlNj17bmFtZToiXHUwMjc1XHUwMjc1cHJvcGVydHlJbnRlcnBvbGF0ZTYiLG1vZHVsZU5hbWU6eGV9LG4ucHJvcGVydHlJbnRlcnBvbGF0ZTc9e25hbWU6Ilx1MDI3NVx1MDI3NXByb3BlcnR5SW50ZXJwb2xhdGU3Iixtb2R1bGVOYW1lOnhlfSxuLnByb3BlcnR5SW50ZXJwb2xhdGU4PXtuYW1lOiJcdTAyNzVcdTAyNzVwcm9wZXJ0eUludGVycG9sYXRlOCIsbW9kdWxlTmFtZTp4ZX0sbi5wcm9wZXJ0eUludGVycG9sYXRlVj17bmFtZToiXHUwMjc1XHUwMjc1cHJvcGVydHlJbnRlcnBvbGF0ZVYiLG1vZHVsZU5hbWU6eGV9LG4uaTE4bj17bmFtZToiXHUwMjc1XHUwMjc1aTE4biIsbW9kdWxlTmFtZTp4ZX0sbi5pMThuQXR0cmlidXRlcz17bmFtZToiXHUwMjc1XHUwMjc1aTE4bkF0dHJpYnV0ZXMiLG1vZHVsZU5hbWU6eGV9LG4uaTE4bkV4cD17bmFtZToiXHUwMjc1XHUwMjc1aTE4bkV4cCIsbW9kdWxlTmFtZTp4ZX0sbi5pMThuU3RhcnQ9e25hbWU6Ilx1MDI3NVx1MDI3NWkxOG5TdGFydCIsbW9kdWxlTmFtZTp4ZX0sbi5pMThuRW5kPXtuYW1lOiJcdTAyNzVcdTAyNzVpMThuRW5kIixtb2R1bGVOYW1lOnhlfSxuLmkxOG5BcHBseT17bmFtZToiXHUwMjc1XHUwMjc1aTE4bkFwcGx5Iixtb2R1bGVOYW1lOnhlfSxuLmkxOG5Qb3N0cHJvY2Vzcz17bmFtZToiXHUwMjc1XHUwMjc1aTE4blBvc3Rwcm9jZXNzIixtb2R1bGVOYW1lOnhlfSxuLnBpcGU9e25hbWU6Ilx1MDI3NVx1MDI3NXBpcGUiLG1vZHVsZU5hbWU6eGV9LG4ucHJvamVjdGlvbj17bmFtZToiXHUwMjc1XHUwMjc1cHJvamVjdGlvbiIsbW9kdWxlTmFtZTp4ZX0sbi5wcm9qZWN0aW9uRGVmPXtuYW1lOiJcdTAyNzVcdTAyNzVwcm9qZWN0aW9uRGVmIixtb2R1bGVOYW1lOnhlfSxuLnJlZmVyZW5jZT17bmFtZToiXHUwMjc1XHUwMjc1cmVmZXJlbmNlIixtb2R1bGVOYW1lOnhlfSxuLmluamVjdD17bmFtZToiXHUwMjc1XHUwMjc1aW5qZWN0Iixtb2R1bGVOYW1lOnhlfSxuLmluamVjdEF0dHJpYnV0ZT17bmFtZToiXHUwMjc1XHUwMjc1aW5qZWN0QXR0cmlidXRlIixtb2R1bGVOYW1lOnhlfSxuLmRpcmVjdGl2ZUluamVjdD17bmFtZToiXHUwMjc1XHUwMjc1ZGlyZWN0aXZlSW5qZWN0Iixtb2R1bGVOYW1lOnhlfSxuLmludmFsaWRGYWN0b3J5PXtuYW1lOiJcdTAyNzVcdTAyNzVpbnZhbGlkRmFjdG9yeSIsbW9kdWxlTmFtZTp4ZX0sbi5pbnZhbGlkRmFjdG9yeURlcD17bmFtZToiXHUwMjc1XHUwMjc1aW52YWxpZEZhY3RvcnlEZXAiLG1vZHVsZU5hbWU6eGV9LG4udGVtcGxhdGVSZWZFeHRyYWN0b3I9e25hbWU6Ilx1MDI3NVx1MDI3NXRlbXBsYXRlUmVmRXh0cmFjdG9yIixtb2R1bGVOYW1lOnhlfSxuLmZvcndhcmRSZWY9e25hbWU6ImZvcndhcmRSZWYiLG1vZHVsZU5hbWU6eGV9LG4ucmVzb2x2ZUZvcndhcmRSZWY9e25hbWU6InJlc29sdmVGb3J3YXJkUmVmIixtb2R1bGVOYW1lOnhlfSxuLlx1MDI3NVx1MDI3NWRlZmluZUluamVjdGFibGU9e25hbWU6Ilx1MDI3NVx1MDI3NWRlZmluZUluamVjdGFibGUiLG1vZHVsZU5hbWU6eGV9LG4uZGVjbGFyZUluamVjdGFibGU9e25hbWU6Ilx1MDI3NVx1MDI3NW5nRGVjbGFyZUluamVjdGFibGUiLG1vZHVsZU5hbWU6eGV9LG4uSW5qZWN0YWJsZURlY2xhcmF0aW9uPXtuYW1lOiJcdTAyNzVcdTAyNzVJbmplY3RhYmxlRGVjbGFyYXRpb24iLG1vZHVsZU5hbWU6eGV9LG4ucmVzb2x2ZVdpbmRvdz17bmFtZToiXHUwMjc1XHUwMjc1cmVzb2x2ZVdpbmRvdyIsbW9kdWxlTmFtZTp4ZX0sbi5yZXNvbHZlRG9jdW1lbnQ9e25hbWU6Ilx1MDI3NVx1MDI3NXJlc29sdmVEb2N1bWVudCIsbW9kdWxlTmFtZTp4ZX0sbi5yZXNvbHZlQm9keT17bmFtZToiXHUwMjc1XHUwMjc1cmVzb2x2ZUJvZHkiLG1vZHVsZU5hbWU6eGV9LG4uZGVmaW5lQ29tcG9uZW50PXtuYW1lOiJcdTAyNzVcdTAyNzVkZWZpbmVDb21wb25lbnQiLG1vZHVsZU5hbWU6eGV9LG4uZGVjbGFyZUNvbXBvbmVudD17bmFtZToiXHUwMjc1XHUwMjc1bmdEZWNsYXJlQ29tcG9uZW50Iixtb2R1bGVOYW1lOnhlfSxuLnNldENvbXBvbmVudFNjb3BlPXtuYW1lOiJcdTAyNzVcdTAyNzVzZXRDb21wb25lbnRTY29wZSIsbW9kdWxlTmFtZTp4ZX0sbi5DaGFuZ2VEZXRlY3Rpb25TdHJhdGVneT17bmFtZToiQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3kiLG1vZHVsZU5hbWU6eGV9LG4uVmlld0VuY2Fwc3VsYXRpb249e25hbWU6IlZpZXdFbmNhcHN1bGF0aW9uIixtb2R1bGVOYW1lOnhlfSxuLkNvbXBvbmVudERlY2xhcmF0aW9uPXtuYW1lOiJcdTAyNzVcdTAyNzVDb21wb25lbnREZWNsYXJhdGlvbiIsbW9kdWxlTmFtZTp4ZX0sbi5GYWN0b3J5RGVjbGFyYXRpb249e25hbWU6Ilx1MDI3NVx1MDI3NUZhY3RvcnlEZWNsYXJhdGlvbiIsbW9kdWxlTmFtZTp4ZX0sbi5kZWNsYXJlRmFjdG9yeT17bmFtZToiXHUwMjc1XHUwMjc1bmdEZWNsYXJlRmFjdG9yeSIsbW9kdWxlTmFtZTp4ZX0sbi5GYWN0b3J5VGFyZ2V0PXtuYW1lOiJcdTAyNzVcdTAyNzVGYWN0b3J5VGFyZ2V0Iixtb2R1bGVOYW1lOnhlfSxuLmRlZmluZURpcmVjdGl2ZT17bmFtZToiXHUwMjc1XHUwMjc1ZGVmaW5lRGlyZWN0aXZlIixtb2R1bGVOYW1lOnhlfSxuLmRlY2xhcmVEaXJlY3RpdmU9e25hbWU6Ilx1MDI3NVx1MDI3NW5nRGVjbGFyZURpcmVjdGl2ZSIsbW9kdWxlTmFtZTp4ZX0sbi5EaXJlY3RpdmVEZWNsYXJhdGlvbj17bmFtZToiXHUwMjc1XHUwMjc1RGlyZWN0aXZlRGVjbGFyYXRpb24iLG1vZHVsZU5hbWU6eGV9LG4uSW5qZWN0b3JEZWY9e25hbWU6Ilx1MDI3NVx1MDI3NUluamVjdG9yRGVmIixtb2R1bGVOYW1lOnhlfSxuLkluamVjdG9yRGVjbGFyYXRpb249e25hbWU6Ilx1MDI3NVx1MDI3NUluamVjdG9yRGVjbGFyYXRpb24iLG1vZHVsZU5hbWU6eGV9LG4uZGVmaW5lSW5qZWN0b3I9e25hbWU6Ilx1MDI3NVx1MDI3NWRlZmluZUluamVjdG9yIixtb2R1bGVOYW1lOnhlfSxuLmRlY2xhcmVJbmplY3Rvcj17bmFtZToiXHUwMjc1XHUwMjc1bmdEZWNsYXJlSW5qZWN0b3IiLG1vZHVsZU5hbWU6eGV9LG4uTmdNb2R1bGVEZWNsYXJhdGlvbj17bmFtZToiXHUwMjc1XHUwMjc1TmdNb2R1bGVEZWNsYXJhdGlvbiIsbW9kdWxlTmFtZTp4ZX0sbi5Nb2R1bGVXaXRoUHJvdmlkZXJzPXtuYW1lOiJNb2R1bGVXaXRoUHJvdmlkZXJzIixtb2R1bGVOYW1lOnhlfSxuLmRlZmluZU5nTW9kdWxlPXtuYW1lOiJcdTAyNzVcdTAyNzVkZWZpbmVOZ01vZHVsZSIsbW9kdWxlTmFtZTp4ZX0sbi5kZWNsYXJlTmdNb2R1bGU9e25hbWU6Ilx1MDI3NVx1MDI3NW5nRGVjbGFyZU5nTW9kdWxlIixtb2R1bGVOYW1lOnhlfSxuLnNldE5nTW9kdWxlU2NvcGU9e25hbWU6Ilx1MDI3NVx1MDI3NXNldE5nTW9kdWxlU2NvcGUiLG1vZHVsZU5hbWU6eGV9LG4ucmVnaXN0ZXJOZ01vZHVsZVR5cGU9e25hbWU6Ilx1MDI3NVx1MDI3NXJlZ2lzdGVyTmdNb2R1bGVUeXBlIixtb2R1bGVOYW1lOnhlfSxuLlBpcGVEZWNsYXJhdGlvbj17bmFtZToiXHUwMjc1XHUwMjc1UGlwZURlY2xhcmF0aW9uIixtb2R1bGVOYW1lOnhlfSxuLmRlZmluZVBpcGU9e25hbWU6Ilx1MDI3NVx1MDI3NWRlZmluZVBpcGUiLG1vZHVsZU5hbWU6eGV9LG4uZGVjbGFyZVBpcGU9e25hbWU6Ilx1MDI3NVx1MDI3NW5nRGVjbGFyZVBpcGUiLG1vZHVsZU5hbWU6eGV9LG4uZGVjbGFyZUNsYXNzTWV0YWRhdGE9e25hbWU6Ilx1MDI3NVx1MDI3NW5nRGVjbGFyZUNsYXNzTWV0YWRhdGEiLG1vZHVsZU5hbWU6eGV9LG4uc2V0Q2xhc3NNZXRhZGF0YT17bmFtZToiXHUwMjc1c2V0Q2xhc3NNZXRhZGF0YSIsbW9kdWxlTmFtZTp4ZX0sbi5xdWVyeVJlZnJlc2g9e25hbWU6Ilx1MDI3NVx1MDI3NXF1ZXJ5UmVmcmVzaCIsbW9kdWxlTmFtZTp4ZX0sbi52aWV3UXVlcnk9e25hbWU6Ilx1MDI3NVx1MDI3NXZpZXdRdWVyeSIsbW9kdWxlTmFtZTp4ZX0sbi5sb2FkUXVlcnk9e25hbWU6Ilx1MDI3NVx1MDI3NWxvYWRRdWVyeSIsbW9kdWxlTmFtZTp4ZX0sbi5jb250ZW50UXVlcnk9e25hbWU6Ilx1MDI3NVx1MDI3NWNvbnRlbnRRdWVyeSIsbW9kdWxlTmFtZTp4ZX0sbi5OZ09uQ2hhbmdlc0ZlYXR1cmU9e25hbWU6Ilx1MDI3NVx1MDI3NU5nT25DaGFuZ2VzRmVhdHVyZSIsbW9kdWxlTmFtZTp4ZX0sbi5Jbmhlcml0RGVmaW5pdGlvbkZlYXR1cmU9e25hbWU6Ilx1MDI3NVx1MDI3NUluaGVyaXREZWZpbml0aW9uRmVhdHVyZSIsbW9kdWxlTmFtZTp4ZX0sbi5Db3B5RGVmaW5pdGlvbkZlYXR1cmU9e25hbWU6Ilx1MDI3NVx1MDI3NUNvcHlEZWZpbml0aW9uRmVhdHVyZSIsbW9kdWxlTmFtZTp4ZX0sbi5TdGFuZGFsb25lRmVhdHVyZT17bmFtZToiXHUwMjc1XHUwMjc1U3RhbmRhbG9uZUZlYXR1cmUiLG1vZHVsZU5hbWU6eGV9LG4uUHJvdmlkZXJzRmVhdHVyZT17bmFtZToiXHUwMjc1XHUwMjc1UHJvdmlkZXJzRmVhdHVyZSIsbW9kdWxlTmFtZTp4ZX0sbi5saXN0ZW5lcj17bmFtZToiXHUwMjc1XHUwMjc1bGlzdGVuZXIiLG1vZHVsZU5hbWU6eGV9LG4uZ2V0SW5oZXJpdGVkRmFjdG9yeT17bmFtZToiXHUwMjc1XHUwMjc1Z2V0SW5oZXJpdGVkRmFjdG9yeSIsbW9kdWxlTmFtZTp4ZX0sbi5zYW5pdGl6ZUh0bWw9e25hbWU6Ilx1MDI3NVx1MDI3NXNhbml0aXplSHRtbCIsbW9kdWxlTmFtZTp4ZX0sbi5zYW5pdGl6ZVN0eWxlPXtuYW1lOiJcdTAyNzVcdTAyNzVzYW5pdGl6ZVN0eWxlIixtb2R1bGVOYW1lOnhlfSxuLnNhbml0aXplUmVzb3VyY2VVcmw9e25hbWU6Ilx1MDI3NVx1MDI3NXNhbml0aXplUmVzb3VyY2VVcmwiLG1vZHVsZU5hbWU6eGV9LG4uc2FuaXRpemVTY3JpcHQ9e25hbWU6Ilx1MDI3NVx1MDI3NXNhbml0aXplU2NyaXB0Iixtb2R1bGVOYW1lOnhlfSxuLnNhbml0aXplVXJsPXtuYW1lOiJcdTAyNzVcdTAyNzVzYW5pdGl6ZVVybCIsbW9kdWxlTmFtZTp4ZX0sbi5zYW5pdGl6ZVVybE9yUmVzb3VyY2VVcmw9e25hbWU6Ilx1MDI3NVx1MDI3NXNhbml0aXplVXJsT3JSZXNvdXJjZVVybCIsbW9kdWxlTmFtZTp4ZX0sbi50cnVzdENvbnN0YW50SHRtbD17bmFtZToiXHUwMjc1XHUwMjc1dHJ1c3RDb25zdGFudEh0bWwiLG1vZHVsZU5hbWU6eGV9LG4udHJ1c3RDb25zdGFudFJlc291cmNlVXJsPXtuYW1lOiJcdTAyNzVcdTAyNzV0cnVzdENvbnN0YW50UmVzb3VyY2VVcmwiLG1vZHVsZU5hbWU6eGV9LG59KSgpO2Z1bmN0aW9uIEJUKG4pe249bjwwPzErKC1uPDwxKTpuPDwxO2xldCB0PSIiO2Rve2xldCBlPTMxJm47KG4+Pj01KT4wJiYoZXw9MzIpLHQrPXNDKGUpfXdoaWxlKG4+MCk7cmV0dXJuIHR9ZnVuY3Rpb24gc0Mobil7aWYobjwwfHxuPj02NCl0aHJvdyBuZXcgRXJyb3IoIkNhbiBvbmx5IGVuY29kZSB2YWx1ZSBpbiB0aGUgcmFuZ2UgWzAsIDYzXSIpO3JldHVybiJBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MDEyMzQ1Njc4OSsvIltuXX12YXIgdHdlPS8nfFxcfFxufFxyfFwkL2csbndlPS9eWyRBLVpfXVswLTlBLVpfJF0qJC9pLHJEPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuaW5kZW50PXQsdGhpcy5wYXJ0c0xlbmd0aD0wLHRoaXMucGFydHM9W10sdGhpcy5zcmNTcGFucz1bXX19LEVDPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuX2luZGVudD10LHRoaXMuX2xpbmVzPVtuZXcgckQodCldfXN0YXRpYyBjcmVhdGVSb290KCl7cmV0dXJuIG5ldyBFQygwKX1nZXQgX2N1cnJlbnRMaW5lKCl7cmV0dXJuIHRoaXMuX2xpbmVzW3RoaXMuX2xpbmVzLmxlbmd0aC0xXX1wcmludGxuKHQsZT0iIil7dGhpcy5wcmludCh0fHxudWxsLGUsITApfWxpbmVJc0VtcHR5KCl7cmV0dXJuIDA9PT10aGlzLl9jdXJyZW50TGluZS5wYXJ0cy5sZW5ndGh9bGluZUxlbmd0aCgpe3JldHVybiB0aGlzLl9jdXJyZW50TGluZS5pbmRlbnQqIiAgIi5sZW5ndGgrdGhpcy5fY3VycmVudExpbmUucGFydHNMZW5ndGh9cHJpbnQodCxlLGk9ITEpe2UubGVuZ3RoPjAmJih0aGlzLl9jdXJyZW50TGluZS5wYXJ0cy5wdXNoKGUpLHRoaXMuX2N1cnJlbnRMaW5lLnBhcnRzTGVuZ3RoKz1lLmxlbmd0aCx0aGlzLl9jdXJyZW50TGluZS5zcmNTcGFucy5wdXNoKHQmJnQuc291cmNlU3Bhbnx8bnVsbCkpLGkmJnRoaXMuX2xpbmVzLnB1c2gobmV3IHJEKHRoaXMuX2luZGVudCkpfXJlbW92ZUVtcHR5TGFzdExpbmUoKXt0aGlzLmxpbmVJc0VtcHR5KCkmJnRoaXMuX2xpbmVzLnBvcCgpfWluY0luZGVudCgpe3RoaXMuX2luZGVudCsrLHRoaXMubGluZUlzRW1wdHkoKSYmKHRoaXMuX2N1cnJlbnRMaW5lLmluZGVudD10aGlzLl9pbmRlbnQpfWRlY0luZGVudCgpe3RoaXMuX2luZGVudC0tLHRoaXMubGluZUlzRW1wdHkoKSYmKHRoaXMuX2N1cnJlbnRMaW5lLmluZGVudD10aGlzLl9pbmRlbnQpfXRvU291cmNlKCl7cmV0dXJuIHRoaXMuc291cmNlTGluZXMubWFwKHQ9PnQucGFydHMubGVuZ3RoPjA/RVgodC5pbmRlbnQpK3QucGFydHMuam9pbigiIik6IiIpLmpvaW4oIlxuIil9dG9Tb3VyY2VNYXBHZW5lcmF0b3IodCxlPTApe2xldCBpPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0PW51bGwpe3RoaXMuZmlsZT10LHRoaXMuc291cmNlc0NvbnRlbnQ9bmV3IE1hcCx0aGlzLmxpbmVzPVtdLHRoaXMubGFzdENvbDA9MCx0aGlzLmhhc01hcHBpbmdzPSExfWFkZFNvdXJjZSh0LGU9bnVsbCl7cmV0dXJuIHRoaXMuc291cmNlc0NvbnRlbnQuaGFzKHQpfHx0aGlzLnNvdXJjZXNDb250ZW50LnNldCh0LGUpLHRoaXN9YWRkTGluZSgpe3JldHVybiB0aGlzLmxpbmVzLnB1c2goW10pLHRoaXMubGFzdENvbDA9MCx0aGlzfWFkZE1hcHBpbmcodCxlLGkscil7aWYoIXRoaXMuY3VycmVudExpbmUpdGhyb3cgbmV3IEVycm9yKCJBIGxpbmUgbXVzdCBiZSBhZGRlZCBiZWZvcmUgbWFwcGluZ3MgY2FuIGJlIGFkZGVkIik7aWYobnVsbCE9ZSYmIXRoaXMuc291cmNlc0NvbnRlbnQuaGFzKGUpKXRocm93IG5ldyBFcnJvcihgVW5rbm93biBzb3VyY2UgZmlsZSAiJHtlfSJgKTtpZihudWxsPT10KXRocm93IG5ldyBFcnJvcigiVGhlIGNvbHVtbiBpbiB0aGUgZ2VuZXJhdGVkIGNvZGUgbXVzdCBiZSBwcm92aWRlZCIpO2lmKHQ8dGhpcy5sYXN0Q29sMCl0aHJvdyBuZXcgRXJyb3IoIk1hcHBpbmcgc2hvdWxkIGJlIGFkZGVkIGluIG91dHB1dCBvcmRlciIpO2lmKGUmJihudWxsPT1pfHxudWxsPT1yKSl0aHJvdyBuZXcgRXJyb3IoIlRoZSBzb3VyY2UgbG9jYXRpb24gbXVzdCBiZSBwcm92aWRlZCB3aGVuIGEgc291cmNlIHVybCBpcyBwcm92aWRlZCIpO3JldHVybiB0aGlzLmhhc01hcHBpbmdzPSEwLHRoaXMubGFzdENvbDA9dCx0aGlzLmN1cnJlbnRMaW5lLnB1c2goe2NvbDA6dCxzb3VyY2VVcmw6ZSxzb3VyY2VMaW5lMDppLHNvdXJjZUNvbDA6cn0pLHRoaXN9Z2V0IGN1cnJlbnRMaW5lKCl7cmV0dXJuIHRoaXMubGluZXMuc2xpY2UoLTEpWzBdfXRvSlNPTigpe2lmKCF0aGlzLmhhc01hcHBpbmdzKXJldHVybiBudWxsO2xldCB0PW5ldyBNYXAsZT1bXSxpPVtdO0FycmF5LmZyb20odGhpcy5zb3VyY2VzQ29udGVudC5rZXlzKCkpLmZvckVhY2goKGMsdSk9Pnt0LnNldChjLHUpLGUucHVzaChjKSxpLnB1c2godGhpcy5zb3VyY2VzQ29udGVudC5nZXQoYyl8fG51bGwpfSk7bGV0IHI9IiIsbz0wLHM9MCxhPTAsbD0wO3JldHVybiB0aGlzLmxpbmVzLmZvckVhY2goYz0+e289MCxyKz1jLm1hcCh1PT57bGV0IGQ9QlQodS5jb2wwLW8pO3JldHVybiBvPXUuY29sMCxudWxsIT11LnNvdXJjZVVybCYmKGQrPUJUKHQuZ2V0KHUuc291cmNlVXJsKS1zKSxzPXQuZ2V0KHUuc291cmNlVXJsKSxkKz1CVCh1LnNvdXJjZUxpbmUwLWEpLGE9dS5zb3VyY2VMaW5lMCxkKz1CVCh1LnNvdXJjZUNvbDAtbCksbD11LnNvdXJjZUNvbDApLGR9KS5qb2luKCIsIikscis9IjsifSkscj1yLnNsaWNlKDAsLTEpLHtmaWxlOnRoaXMuZmlsZXx8IiIsdmVyc2lvbjozLHNvdXJjZVJvb3Q6IiIsc291cmNlczplLHNvdXJjZXNDb250ZW50OmksbWFwcGluZ3M6cn19dG9Kc0NvbW1lbnQoKXtyZXR1cm4gdGhpcy5oYXNNYXBwaW5ncz8iLy8jIHNvdXJjZU1hcHBpbmdVUkw9ZGF0YTphcHBsaWNhdGlvbi9qc29uO2Jhc2U2NCwiK2Z1bmN0aW9uKG4pe2xldCB0PSIiLGU9TFYobik7Zm9yKGxldCBpPTA7aTxlLmxlbmd0aDspe2xldCByPWVbaSsrXSxvPWk8ZS5sZW5ndGg/ZVtpKytdOm51bGwscz1pPGUubGVuZ3RoP2VbaSsrXTpudWxsO3QrPXNDKHI+PjIpLHQrPXNDKCgzJnIpPDw0fChudWxsPT09bz8wOm8+PjQpKSx0Kz1udWxsPT09bz8iPSI6c0MoKDE1Jm8pPDwyfChudWxsPT09cz8wOnM+PjYpKSx0Kz1udWxsPT09b3x8bnVsbD09PXM/Ij0iOnNDKDYzJnMpfXJldHVybiB0fShKU09OLnN0cmluZ2lmeSh0aGlzLG51bGwsMCkpOiIifX0odCkscj0hMSxvPSgpPT57cnx8KGkuYWRkU291cmNlKHQsIiAiKS5hZGRNYXBwaW5nKDAsdCwwLDApLHI9ITApfTtmb3IobGV0IHM9MDtzPGU7cysrKWkuYWRkTGluZSgpLG8oKTtyZXR1cm4gdGhpcy5zb3VyY2VMaW5lcy5mb3JFYWNoKChzLGEpPT57aS5hZGRMaW5lKCk7bGV0IGw9cy5zcmNTcGFucyxjPXMucGFydHMsdT1zLmluZGVudCoiICAiLmxlbmd0aCxkPTA7Zm9yKDtkPGwubGVuZ3RoJiYhbFtkXTspdSs9Y1tkXS5sZW5ndGgsZCsrO2ZvcihkPGwubGVuZ3RoJiYwPT09YSYmMD09PXU/cj0hMDpvKCk7ZDxsLmxlbmd0aDspe2xldCBwPWxbZF0saD1wLnN0YXJ0LmZpbGUsZj1wLnN0YXJ0LmxpbmUsbT1wLnN0YXJ0LmNvbDtmb3IoaS5hZGRTb3VyY2UoaC51cmwsaC5jb250ZW50KS5hZGRNYXBwaW5nKHUsaC51cmwsZixtKSx1Kz1jW2RdLmxlbmd0aCxkKys7ZDxsLmxlbmd0aCYmKHA9PT1sW2RdfHwhbFtkXSk7KXUrPWNbZF0ubGVuZ3RoLGQrK319KSxpfXNwYW5PZih0LGUpe2xldCBpPXRoaXMuX2xpbmVzW3RdO2lmKGkpe2xldCByPWUtRVgoaS5pbmRlbnQpLmxlbmd0aDtmb3IobGV0IG89MDtvPGkucGFydHMubGVuZ3RoO28rKyl7bGV0IHM9aS5wYXJ0c1tvXTtpZihzLmxlbmd0aD5yKXJldHVybiBpLnNyY1NwYW5zW29dO3ItPXMubGVuZ3RofX1yZXR1cm4gbnVsbH1nZXQgc291cmNlTGluZXMoKXtyZXR1cm4gdGhpcy5fbGluZXMubGVuZ3RoJiYwPT09dGhpcy5fbGluZXNbdGhpcy5fbGluZXMubGVuZ3RoLTFdLnBhcnRzLmxlbmd0aD90aGlzLl9saW5lcy5zbGljZSgwLC0xKTp0aGlzLl9saW5lc319LE5CPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuX2VzY2FwZURvbGxhckluU3RyaW5ncz10fXByaW50TGVhZGluZ0NvbW1lbnRzKHQsZSl7aWYodm9pZCAwIT09dC5sZWFkaW5nQ29tbWVudHMpZm9yKGxldCBpIG9mIHQubGVhZGluZ0NvbW1lbnRzKWkgaW5zdGFuY2VvZiBuRD9lLnByaW50KHQsYC8qJHtpLnRvU3RyaW5nKCl9Ki9gLGkudHJhaWxpbmdOZXdsaW5lKTppLm11bHRpbGluZT9lLnByaW50KHQsYC8qICR7aS50ZXh0fSAqL2AsaS50cmFpbGluZ05ld2xpbmUpOmkudGV4dC5zcGxpdCgiXG4iKS5mb3JFYWNoKHI9PntlLnByaW50bG4odCxgLy8gJHtyfWApfSl9dmlzaXRFeHByZXNzaW9uU3RtdCh0LGUpe3JldHVybiB0aGlzLnByaW50TGVhZGluZ0NvbW1lbnRzKHQsZSksdC5leHByLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpLGUucHJpbnRsbih0LCI7IiksbnVsbH12aXNpdFJldHVyblN0bXQodCxlKXtyZXR1cm4gdGhpcy5wcmludExlYWRpbmdDb21tZW50cyh0LGUpLGUucHJpbnQodCwicmV0dXJuICIpLHQudmFsdWUudmlzaXRFeHByZXNzaW9uKHRoaXMsZSksZS5wcmludGxuKHQsIjsiKSxudWxsfXZpc2l0SWZTdG10KHQsZSl7dGhpcy5wcmludExlYWRpbmdDb21tZW50cyh0LGUpLGUucHJpbnQodCwiaWYgKCIpLHQuY29uZGl0aW9uLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpLGUucHJpbnQodCwiKSB7Iik7bGV0IGk9bnVsbCE9dC5mYWxzZUNhc2UmJnQuZmFsc2VDYXNlLmxlbmd0aD4wO3JldHVybiB0LnRydWVDYXNlLmxlbmd0aDw9MSYmIWk/KGUucHJpbnQodCwiICIpLHRoaXMudmlzaXRBbGxTdGF0ZW1lbnRzKHQudHJ1ZUNhc2UsZSksZS5yZW1vdmVFbXB0eUxhc3RMaW5lKCksZS5wcmludCh0LCIgIikpOihlLnByaW50bG4oKSxlLmluY0luZGVudCgpLHRoaXMudmlzaXRBbGxTdGF0ZW1lbnRzKHQudHJ1ZUNhc2UsZSksZS5kZWNJbmRlbnQoKSxpJiYoZS5wcmludGxuKHQsIn0gZWxzZSB7IiksZS5pbmNJbmRlbnQoKSx0aGlzLnZpc2l0QWxsU3RhdGVtZW50cyh0LmZhbHNlQ2FzZSxlKSxlLmRlY0luZGVudCgpKSksZS5wcmludGxuKHQsIn0iKSxudWxsfXZpc2l0V3JpdGVWYXJFeHByKHQsZSl7bGV0IGk9ZS5saW5lSXNFbXB0eSgpO3JldHVybiBpfHxlLnByaW50KHQsIigiKSxlLnByaW50KHQsYCR7dC5uYW1lfSA9IGApLHQudmFsdWUudmlzaXRFeHByZXNzaW9uKHRoaXMsZSksaXx8ZS5wcmludCh0LCIpIiksbnVsbH12aXNpdFdyaXRlS2V5RXhwcih0LGUpe2xldCBpPWUubGluZUlzRW1wdHkoKTtyZXR1cm4gaXx8ZS5wcmludCh0LCIoIiksdC5yZWNlaXZlci52aXNpdEV4cHJlc3Npb24odGhpcyxlKSxlLnByaW50KHQsIlsiKSx0LmluZGV4LnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpLGUucHJpbnQodCwiXSA9ICIpLHQudmFsdWUudmlzaXRFeHByZXNzaW9uKHRoaXMsZSksaXx8ZS5wcmludCh0LCIpIiksbnVsbH12aXNpdFdyaXRlUHJvcEV4cHIodCxlKXtsZXQgaT1lLmxpbmVJc0VtcHR5KCk7cmV0dXJuIGl8fGUucHJpbnQodCwiKCIpLHQucmVjZWl2ZXIudmlzaXRFeHByZXNzaW9uKHRoaXMsZSksZS5wcmludCh0LGAuJHt0Lm5hbWV9ID0gYCksdC52YWx1ZS52aXNpdEV4cHJlc3Npb24odGhpcyxlKSxpfHxlLnByaW50KHQsIikiKSxudWxsfXZpc2l0SW52b2tlRnVuY3Rpb25FeHByKHQsZSl7cmV0dXJuIHQuZm4udmlzaXRFeHByZXNzaW9uKHRoaXMsZSksZS5wcmludCh0LCIoIiksdGhpcy52aXNpdEFsbEV4cHJlc3Npb25zKHQuYXJncyxlLCIsIiksZS5wcmludCh0LCIpIiksbnVsbH12aXNpdFRhZ2dlZFRlbXBsYXRlRXhwcih0LGUpe3QudGFnLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpLGUucHJpbnQodCwiYCIrdC50ZW1wbGF0ZS5lbGVtZW50c1swXS5yYXdUZXh0KTtmb3IobGV0IGk9MTtpPHQudGVtcGxhdGUuZWxlbWVudHMubGVuZ3RoO2krKyllLnByaW50KHQsIiR7IiksdC50ZW1wbGF0ZS5leHByZXNzaW9uc1tpLTFdLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpLGUucHJpbnQodCxgfSR7dC50ZW1wbGF0ZS5lbGVtZW50c1tpXS5yYXdUZXh0fWApO3JldHVybiBlLnByaW50KHQsImAiKSxudWxsfXZpc2l0V3JhcHBlZE5vZGVFeHByKHQsZSl7dGhyb3cgbmV3IEVycm9yKCJBYnN0cmFjdCBlbWl0dGVyIGNhbm5vdCB2aXNpdCBXcmFwcGVkTm9kZUV4cHIuIil9dmlzaXRUeXBlb2ZFeHByKHQsZSl7ZS5wcmludCh0LCJ0eXBlb2YgIiksdC5leHByLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpfXZpc2l0UmVhZFZhckV4cHIodCxlKXtyZXR1cm4gZS5wcmludCh0LHQubmFtZSksbnVsbH12aXNpdEluc3RhbnRpYXRlRXhwcih0LGUpe3JldHVybiBlLnByaW50KHQsIm5ldyAiKSx0LmNsYXNzRXhwci52aXNpdEV4cHJlc3Npb24odGhpcyxlKSxlLnByaW50KHQsIigiKSx0aGlzLnZpc2l0QWxsRXhwcmVzc2lvbnModC5hcmdzLGUsIiwiKSxlLnByaW50KHQsIikiKSxudWxsfXZpc2l0TGl0ZXJhbEV4cHIodCxlKXtsZXQgaT10LnZhbHVlO3JldHVybiBlLnByaW50KHQsInN0cmluZyI9PXR5cGVvZiBpP3JtKGksdGhpcy5fZXNjYXBlRG9sbGFySW5TdHJpbmdzKTpgJHtpfWApLG51bGx9dmlzaXRMb2NhbGl6ZWRTdHJpbmcodCxlKXtsZXQgaT10LnNlcmlhbGl6ZUkxOG5IZWFkKCk7ZS5wcmludCh0LCIkbG9jYWxpemUgYCIraS5yYXcpO2ZvcihsZXQgcj0xO3I8dC5tZXNzYWdlUGFydHMubGVuZ3RoO3IrKyllLnByaW50KHQsIiR7IiksdC5leHByZXNzaW9uc1tyLTFdLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpLGUucHJpbnQodCxgfSR7dC5zZXJpYWxpemVJMThuVGVtcGxhdGVQYXJ0KHIpLnJhd31gKTtyZXR1cm4gZS5wcmludCh0LCJgIiksbnVsbH12aXNpdENvbmRpdGlvbmFsRXhwcih0LGUpe3JldHVybiBlLnByaW50KHQsIigiKSx0LmNvbmRpdGlvbi52aXNpdEV4cHJlc3Npb24odGhpcyxlKSxlLnByaW50KHQsIj8gIiksdC50cnVlQ2FzZS52aXNpdEV4cHJlc3Npb24odGhpcyxlKSxlLnByaW50KHQsIjogIiksdC5mYWxzZUNhc2UudmlzaXRFeHByZXNzaW9uKHRoaXMsZSksZS5wcmludCh0LCIpIiksbnVsbH12aXNpdE5vdEV4cHIodCxlKXtyZXR1cm4gZS5wcmludCh0LCIhIiksdC5jb25kaXRpb24udmlzaXRFeHByZXNzaW9uKHRoaXMsZSksbnVsbH12aXNpdFVuYXJ5T3BlcmF0b3JFeHByKHQsZSl7bGV0IGk7c3dpdGNoKHQub3BlcmF0b3Ipe2Nhc2UgYW0uUGx1czppPSIrIjticmVhaztjYXNlIGFtLk1pbnVzOmk9Ii0iO2JyZWFrO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKGBVbmtub3duIG9wZXJhdG9yICR7dC5vcGVyYXRvcn1gKX1yZXR1cm4gdC5wYXJlbnMmJmUucHJpbnQodCwiKCIpLGUucHJpbnQodCxpKSx0LmV4cHIudmlzaXRFeHByZXNzaW9uKHRoaXMsZSksdC5wYXJlbnMmJmUucHJpbnQodCwiKSIpLG51bGx9dmlzaXRCaW5hcnlPcGVyYXRvckV4cHIodCxlKXtsZXQgaTtzd2l0Y2godC5vcGVyYXRvcil7Y2FzZSBDbi5FcXVhbHM6aT0iPT0iO2JyZWFrO2Nhc2UgQ24uSWRlbnRpY2FsOmk9Ij09PSI7YnJlYWs7Y2FzZSBDbi5Ob3RFcXVhbHM6aT0iIT0iO2JyZWFrO2Nhc2UgQ24uTm90SWRlbnRpY2FsOmk9IiE9PSI7YnJlYWs7Y2FzZSBDbi5BbmQ6aT0iJiYiO2JyZWFrO2Nhc2UgQ24uQml0d2lzZUFuZDppPSImIjticmVhaztjYXNlIENuLk9yOmk9Inx8IjticmVhaztjYXNlIENuLlBsdXM6aT0iKyI7YnJlYWs7Y2FzZSBDbi5NaW51czppPSItIjticmVhaztjYXNlIENuLkRpdmlkZTppPSIvIjticmVhaztjYXNlIENuLk11bHRpcGx5Omk9IioiO2JyZWFrO2Nhc2UgQ24uTW9kdWxvOmk9IiUiO2JyZWFrO2Nhc2UgQ24uTG93ZXI6aT0iPCI7YnJlYWs7Y2FzZSBDbi5Mb3dlckVxdWFsczppPSI8PSI7YnJlYWs7Y2FzZSBDbi5CaWdnZXI6aT0iPiI7YnJlYWs7Y2FzZSBDbi5CaWdnZXJFcXVhbHM6aT0iPj0iO2JyZWFrO2Nhc2UgQ24uTnVsbGlzaENvYWxlc2NlOmk9Ij8/IjticmVhaztkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgVW5rbm93biBvcGVyYXRvciAke3Qub3BlcmF0b3J9YCl9cmV0dXJuIHQucGFyZW5zJiZlLnByaW50KHQsIigiKSx0Lmxocy52aXNpdEV4cHJlc3Npb24odGhpcyxlKSxlLnByaW50KHQsYCAke2l9IGApLHQucmhzLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpLHQucGFyZW5zJiZlLnByaW50KHQsIikiKSxudWxsfXZpc2l0UmVhZFByb3BFeHByKHQsZSl7cmV0dXJuIHQucmVjZWl2ZXIudmlzaXRFeHByZXNzaW9uKHRoaXMsZSksZS5wcmludCh0LCIuIiksZS5wcmludCh0LHQubmFtZSksbnVsbH12aXNpdFJlYWRLZXlFeHByKHQsZSl7cmV0dXJuIHQucmVjZWl2ZXIudmlzaXRFeHByZXNzaW9uKHRoaXMsZSksZS5wcmludCh0LCJbIiksdC5pbmRleC52aXNpdEV4cHJlc3Npb24odGhpcyxlKSxlLnByaW50KHQsIl0iKSxudWxsfXZpc2l0TGl0ZXJhbEFycmF5RXhwcih0LGUpe3JldHVybiBlLnByaW50KHQsIlsiKSx0aGlzLnZpc2l0QWxsRXhwcmVzc2lvbnModC5lbnRyaWVzLGUsIiwiKSxlLnByaW50KHQsIl0iKSxudWxsfXZpc2l0TGl0ZXJhbE1hcEV4cHIodCxlKXtyZXR1cm4gZS5wcmludCh0LCJ7IiksdGhpcy52aXNpdEFsbE9iamVjdHMoaT0+e2UucHJpbnQodCxgJHtybShpLmtleSx0aGlzLl9lc2NhcGVEb2xsYXJJblN0cmluZ3MsaS5xdW90ZWQpfTpgKSxpLnZhbHVlLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpfSx0LmVudHJpZXMsZSwiLCIpLGUucHJpbnQodCwifSIpLG51bGx9dmlzaXRDb21tYUV4cHIodCxlKXtyZXR1cm4gZS5wcmludCh0LCIoIiksdGhpcy52aXNpdEFsbEV4cHJlc3Npb25zKHQucGFydHMsZSwiLCIpLGUucHJpbnQodCwiKSIpLG51bGx9dmlzaXRBbGxFeHByZXNzaW9ucyh0LGUsaSl7dGhpcy52aXNpdEFsbE9iamVjdHMocj0+ci52aXNpdEV4cHJlc3Npb24odGhpcyxlKSx0LGUsaSl9dmlzaXRBbGxPYmplY3RzKHQsZSxpLHIpe2xldCBvPSExO2ZvcihsZXQgcz0wO3M8ZS5sZW5ndGg7cysrKXM+MCYmKGkubGluZUxlbmd0aCgpPjgwPyhpLnByaW50KG51bGwsciwhMCksb3x8KGkuaW5jSW5kZW50KCksaS5pbmNJbmRlbnQoKSxvPSEwKSk6aS5wcmludChudWxsLHIsITEpKSx0KGVbc10pO28mJihpLmRlY0luZGVudCgpLGkuZGVjSW5kZW50KCkpfXZpc2l0QWxsU3RhdGVtZW50cyh0LGUpe3QuZm9yRWFjaChpPT5pLnZpc2l0U3RhdGVtZW50KHRoaXMsZSkpfX07ZnVuY3Rpb24gcm0obix0LGU9ITApe2lmKG51bGw9PW4pcmV0dXJuIG51bGw7bGV0IGk9bi5yZXBsYWNlKHR3ZSwoLi4ubyk9PiIkIj09b1swXT90PyJcXCQiOiIkIjoiXG4iPT1vWzBdPyJcXG4iOiJcciI9PW9bMF0/IlxcciI6YFxcJHtvWzBdfWApO3JldHVybiBlfHwhbndlLnRlc3QoaSk/YCcke2l9J2A6aX1mdW5jdGlvbiBFWChuKXtsZXQgdD0iIjtmb3IobGV0IGU9MDtlPG47ZSsrKXQrPSIgICI7cmV0dXJuIHR9ZnVuY3Rpb24gSUQobix0KXtpZigwPT09dClyZXR1cm4gdWwobik7bGV0IGU9W107Zm9yKGxldCBpPTA7aTx0O2krKyllLnB1c2goVl8pO3JldHVybiB1bChuLHZvaWQgMCxlKX1mdW5jdGlvbiB6UShuKXtyZXR1cm5gQCR7bn1gfWZ1bmN0aW9uIHJ3ZShuLHQpe2xldCBlPXJtKHQsITEsITEpO3JldHVybiBlIT09dD9gJHtufVske2V9XWA6YCR7bn0uJHt0fWB9ZnVuY3Rpb24galEobix0KXtyZXR1cm5gYW5pbWF0aW9uXyR7bn1fJHt0fWB9ZnVuY3Rpb24gQXMobil7bGV0IHQ9bmV3IExuKG4pO3JldHVybnt2YWx1ZTp0LHR5cGU6dH19ZnVuY3Rpb24gb20obix0KXtsZXQgZT1fcihuLm1hcChpPT5pLnZhbHVlKSk7cmV0dXJuIHQ/cmEoW10sW25ldyBEbyhlKV0pOmV9ZnVuY3Rpb24gSFYobix0KXtyZXR1cm57ZXhwcmVzc2lvbjpuLGZvcndhcmRSZWY6dH19dmFyIGZfPSgoKT0+e3JldHVybihuPWZffHwoZl89e30pKVtuLkNsYXNzPTBdPSJDbGFzcyIsbltuLkZ1bmN0aW9uPTFdPSJGdW5jdGlvbiIsZl87dmFyIG59KSgpLE5jPSgoKT0+e3JldHVybihuPU5jfHwoTmM9e30pKVtuLkRpcmVjdGl2ZT0wXT0iRGlyZWN0aXZlIixuW24uQ29tcG9uZW50PTFdPSJDb21wb25lbnQiLG5bbi5JbmplY3RhYmxlPTJdPSJJbmplY3RhYmxlIixuW24uUGlwZT0zXT0iUGlwZSIsbltuLk5nTW9kdWxlPTRdPSJOZ01vZHVsZSIsTmM7dmFyIG59KSgpO2Z1bmN0aW9uIG5tKG4pe2xldCB0PVJpKCJ0IiksZT1udWxsLGk9RFgobik/dDpuZXcgZ3IoQ24uT3IsdCxuLmludGVybmFsVHlwZSkscj1udWxsO251bGwhPT1uLmRlcHM/ImludmFsaWQiIT09bi5kZXBzJiYocj1uZXcgZG0oaSxUWChuLmRlcHMsbi50YXJnZXQpKSk6KGU9UmkoYFx1MDI3NSR7bi5uYW1lfV9CYXNlRmFjdG9yeWApLHI9ZS5jYWxsRm4oW2ldKSk7bGV0IG89W10scz1udWxsO2Z1bmN0aW9uIGEoYyl7bGV0IHU9UmkoInIiKTtvLnB1c2godS5zZXQoUEIpLnRvRGVjbFN0bXQoKSk7bGV0IGQ9bnVsbCE9PXI/dS5zZXQocikudG9TdG10KCk6VG4odGUuaW52YWxpZEZhY3RvcnkpLmNhbGxGbihbXSkudG9TdG10KCk7cmV0dXJuIG8ucHVzaChWVih0LFtkXSxbdS5zZXQoYykudG9TdG10KCldKSksdX1pZihEWChuKSl7bGV0IGM9VFgobi5kZWxlZ2F0ZURlcHMsbi50YXJnZXQpO3M9YShuZXcobi5kZWxlZ2F0ZVR5cGU9PT1mXy5DbGFzcz9kbTpvaCkobi5kZWxlZ2F0ZSxjKSl9ZWxzZSBzPWZ1bmN0aW9uKG4pe3JldHVybiB2b2lkIDAhPT1uLmV4cHJlc3Npb259KG4pP2Eobi5leHByZXNzaW9uKTpyO2lmKG51bGw9PT1zKW8ucHVzaChUbih0ZS5pbnZhbGlkRmFjdG9yeSkuY2FsbEZuKFtdKS50b1N0bXQoKSk7ZWxzZSBpZihudWxsIT09ZSl7bGV0IGM9VG4odGUuZ2V0SW5oZXJpdGVkRmFjdG9yeSkuY2FsbEZuKFtuLmludGVybmFsVHlwZV0pLHU9bmV3IGdyKENuLk9yLGUsZS5zZXQoYykpO28ucHVzaChuZXcgRG8odS5jYWxsRm4oW2ldKSkpfWVsc2Ugby5wdXNoKG5ldyBEbyhzKSk7bGV0IGw9cmEoW25ldyBpYSgidCIsVl8pXSxvLFBhLHZvaWQgMCxgJHtuLm5hbWV9X0ZhY3RvcnlgKTtyZXR1cm4gbnVsbCE9PWUmJihsPXJhKFtdLFtuZXcgVnUoZS5uYW1lKSxuZXcgRG8obCldKS5jYWxsRm4oW10sdm9pZCAwLCEwKSkse2V4cHJlc3Npb246bCxzdGF0ZW1lbnRzOltdLHR5cGU6Y3dlKG4pfX1mdW5jdGlvbiBjd2Uobil7bGV0IHQ9bnVsbCE9PW4uZGVwcyYmImludmFsaWQiIT09bi5kZXBzP2Z1bmN0aW9uKG4pe2xldCB0PSExLGU9bi5tYXAoaT0+e2xldCByPWZ1bmN0aW9uKG4pe2xldCB0PVtdO3JldHVybiBudWxsIT09bi5hdHRyaWJ1dGVOYW1lVHlwZSYmdC5wdXNoKHtrZXk6ImF0dHJpYnV0ZSIsdmFsdWU6bi5hdHRyaWJ1dGVOYW1lVHlwZSxxdW90ZWQ6ITF9KSxuLm9wdGlvbmFsJiZ0LnB1c2goe2tleToib3B0aW9uYWwiLHZhbHVlOmh0KCEwKSxxdW90ZWQ6ITF9KSxuLmhvc3QmJnQucHVzaCh7a2V5OiJob3N0Iix2YWx1ZTpodCghMCkscXVvdGVkOiExfSksbi5zZWxmJiZ0LnB1c2goe2tleToic2VsZiIsdmFsdWU6aHQoITApLHF1b3RlZDohMX0pLG4uc2tpcFNlbGYmJnQucHVzaCh7a2V5OiJza2lwU2VsZiIsdmFsdWU6aHQoITApLHF1b3RlZDohMX0pLHQubGVuZ3RoPjA/cWwodCk6bnVsbH0oaSk7cmV0dXJuIG51bGwhPT1yPyh0PSEwLHIpOmh0KG51bGwpfSk7cmV0dXJuIHQ/dWwoX3IoZSkpOkpkfShuLmRlcHMpOkpkO3JldHVybiB1bChUbih0ZS5GYWN0b3J5RGVjbGFyYXRpb24sW0lEKG4udHlwZS50eXBlLG4udHlwZUFyZ3VtZW50Q291bnQpLHRdKSl9ZnVuY3Rpb24gVFgobix0KXtyZXR1cm4gbi5tYXAoKGUsaSk9PmZ1bmN0aW9uKG4sdCxlKXtpZihudWxsPT09bi50b2tlbilyZXR1cm4gVG4odGUuaW52YWxpZEZhY3RvcnlEZXApLmNhbGxGbihbaHQoZSldKTtpZihudWxsPT09bi5hdHRyaWJ1dGVOYW1lVHlwZSl7bGV0IGk9MHwobi5zZWxmPzI6MCl8KG4uc2tpcFNlbGY/NDowKXwobi5ob3N0PzE6MCl8KG4ub3B0aW9uYWw/ODowKXwodD09PU5jLlBpcGU/MTY6MCkscj0wIT09aXx8bi5vcHRpb25hbD9odChpKTpudWxsLG89W24udG9rZW5dO3ImJm8ucHVzaChyKTtsZXQgcz1mdW5jdGlvbihuKXtzd2l0Y2gobil7Y2FzZSBOYy5Db21wb25lbnQ6Y2FzZSBOYy5EaXJlY3RpdmU6Y2FzZSBOYy5QaXBlOnJldHVybiB0ZS5kaXJlY3RpdmVJbmplY3Q7ZGVmYXVsdDpyZXR1cm4gdGUuaW5qZWN0fX0odCk7cmV0dXJuIFRuKHMpLmNhbGxGbihvKX1yZXR1cm4gVG4odGUuaW5qZWN0QXR0cmlidXRlKS5jYWxsRm4oW24udG9rZW5dKX0oZSx0LGkpKX1mdW5jdGlvbiBEWChuKXtyZXR1cm4gdm9pZCAwIT09bi5kZWxlZ2F0ZVR5cGV9dmFyIE1fPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy52YWx1ZT10LHRoaXMuc291cmNlU3Bhbj1lfXZpc2l0KHQpe3JldHVybiB0LnZpc2l0VGV4dCh0aGlzKX19LFRDPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLnZhbHVlPXQsdGhpcy5zb3VyY2VTcGFuPWUsdGhpcy5pMThuPWl9dmlzaXQodCl7cmV0dXJuIHQudmlzaXRCb3VuZFRleHQodGhpcyl9fSxEQz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyl7dGhpcy5uYW1lPXQsdGhpcy52YWx1ZT1lLHRoaXMuc291cmNlU3Bhbj1pLHRoaXMua2V5U3Bhbj1yLHRoaXMudmFsdWVTcGFuPW8sdGhpcy5pMThuPXN9dmlzaXQodCl7cmV0dXJuIHQudmlzaXRUZXh0QXR0cmlidXRlKHRoaXMpfX0sd189Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscixvLHMsYSxsLGMpe3RoaXMubmFtZT10LHRoaXMudHlwZT1lLHRoaXMuc2VjdXJpdHlDb250ZXh0PWksdGhpcy52YWx1ZT1yLHRoaXMudW5pdD1vLHRoaXMuc291cmNlU3Bhbj1zLHRoaXMua2V5U3Bhbj1hLHRoaXMudmFsdWVTcGFuPWwsdGhpcy5pMThuPWN9c3RhdGljIGZyb21Cb3VuZEVsZW1lbnRQcm9wZXJ0eSh0LGUpe2lmKHZvaWQgMD09PXQua2V5U3Bhbil0aHJvdyBuZXcgRXJyb3IoYFVuZXhwZWN0ZWQgc3RhdGU6IGtleVNwYW4gbXVzdCBiZSBkZWZpbmVkIGZvciBib3VuZCBhdHRyaWJ1dGVzIGJ1dCB3YXMgbm90IGZvciAke3QubmFtZX06ICR7dC5zb3VyY2VTcGFufWApO3JldHVybiBuZXcgd18odC5uYW1lLHQudHlwZSx0LnNlY3VyaXR5Q29udGV4dCx0LnZhbHVlLHQudW5pdCx0LnNvdXJjZVNwYW4sdC5rZXlTcGFuLHQudmFsdWVTcGFuLGUpfXZpc2l0KHQpe3JldHVybiB0LnZpc2l0Qm91bmRBdHRyaWJ1dGUodGhpcyl9fSxTXz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyxhLGwpe3RoaXMubmFtZT10LHRoaXMudHlwZT1lLHRoaXMuaGFuZGxlcj1pLHRoaXMudGFyZ2V0PXIsdGhpcy5waGFzZT1vLHRoaXMuc291cmNlU3Bhbj1zLHRoaXMuaGFuZGxlclNwYW49YSx0aGlzLmtleVNwYW49bH1zdGF0aWMgZnJvbVBhcnNlZEV2ZW50KHQpe2xldCBlPTA9PT10LnR5cGU/dC50YXJnZXRPclBoYXNlOm51bGwsaT0xPT09dC50eXBlP3QudGFyZ2V0T3JQaGFzZTpudWxsO2lmKHZvaWQgMD09PXQua2V5U3Bhbil0aHJvdyBuZXcgRXJyb3IoYFVuZXhwZWN0ZWQgc3RhdGU6IGtleVNwYW4gbXVzdCBiZSBkZWZpbmVkIGZvciBib3VuZCBldmVudCBidXQgd2FzIG5vdCBmb3IgJHt0Lm5hbWV9OiAke3Quc291cmNlU3Bhbn1gKTtyZXR1cm4gbmV3IFNfKHQubmFtZSx0LnR5cGUsdC5oYW5kbGVyLGUsaSx0LnNvdXJjZVNwYW4sdC5oYW5kbGVyU3Bhbix0LmtleVNwYW4pfXZpc2l0KHQpe3JldHVybiB0LnZpc2l0Qm91bmRFdmVudCh0aGlzKX19LEVfPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIsbyxzLGEsbCxjLHUpe3RoaXMubmFtZT10LHRoaXMuYXR0cmlidXRlcz1lLHRoaXMuaW5wdXRzPWksdGhpcy5vdXRwdXRzPXIsdGhpcy5jaGlsZHJlbj1vLHRoaXMucmVmZXJlbmNlcz1zLHRoaXMuc291cmNlU3Bhbj1hLHRoaXMuc3RhcnRTb3VyY2VTcGFuPWwsdGhpcy5lbmRTb3VyY2VTcGFuPWMsdGhpcy5pMThuPXV9dmlzaXQodCl7cmV0dXJuIHQudmlzaXRFbGVtZW50KHRoaXMpfX0sdUM9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscixvLHMsYSxsLGMsdSxkLHApe3RoaXMudGFnTmFtZT10LHRoaXMuYXR0cmlidXRlcz1lLHRoaXMuaW5wdXRzPWksdGhpcy5vdXRwdXRzPXIsdGhpcy50ZW1wbGF0ZUF0dHJzPW8sdGhpcy5jaGlsZHJlbj1zLHRoaXMucmVmZXJlbmNlcz1hLHRoaXMudmFyaWFibGVzPWwsdGhpcy5zb3VyY2VTcGFuPWMsdGhpcy5zdGFydFNvdXJjZVNwYW49dSx0aGlzLmVuZFNvdXJjZVNwYW49ZCx0aGlzLmkxOG49cH12aXNpdCh0KXtyZXR1cm4gdC52aXNpdFRlbXBsYXRlKHRoaXMpfX0sb0Q9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscixvKXt0aGlzLm5hbWU9dCx0aGlzLnZhbHVlPWUsdGhpcy5zb3VyY2VTcGFuPWksdGhpcy5rZXlTcGFuPXIsdGhpcy52YWx1ZVNwYW49b312aXNpdCh0KXtyZXR1cm4gdC52aXNpdFZhcmlhYmxlKHRoaXMpfX0sc0Q9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscil7dGhpcy52YXJzPXQsdGhpcy5wbGFjZWhvbGRlcnM9ZSx0aGlzLnNvdXJjZVNwYW49aSx0aGlzLmkxOG49cn12aXNpdCh0KXtyZXR1cm4gdC52aXNpdEljdSh0aGlzKX19O2Z1bmN0aW9uIEFYKG4sdCl7bGV0IGU9W107aWYobi52aXNpdClmb3IobGV0IGkgb2YgdCluLnZpc2l0KGkpfHxpLnZpc2l0KG4pO2Vsc2UgZm9yKGxldCBpIG9mIHQpe2xldCByPWkudmlzaXQobik7ciYmZS5wdXNoKHIpfXJldHVybiBlfXZhciBGdT1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyl7dGhpcy5ub2Rlcz10LHRoaXMucGxhY2Vob2xkZXJzPWUsdGhpcy5wbGFjZWhvbGRlclRvTWVzc2FnZT1pLHRoaXMubWVhbmluZz1yLHRoaXMuZGVzY3JpcHRpb249byx0aGlzLmN1c3RvbUlkPXMsdGhpcy5pZD10aGlzLmN1c3RvbUlkLHRoaXMubGVnYWN5SWRzPVtdLHRoaXMubWVzc2FnZVN0cmluZz1mdW5jdGlvbihuKXtsZXQgdD1uZXcgVUI7cmV0dXJuIG4ubWFwKGk9PmkudmlzaXQodCkpLmpvaW4oIiIpfSh0aGlzLm5vZGVzKSx0aGlzLnNvdXJjZXM9dC5sZW5ndGg/W3tmaWxlUGF0aDp0WzBdLnNvdXJjZVNwYW4uc3RhcnQuZmlsZS51cmwsc3RhcnRMaW5lOnRbMF0uc291cmNlU3Bhbi5zdGFydC5saW5lKzEsc3RhcnRDb2w6dFswXS5zb3VyY2VTcGFuLnN0YXJ0LmNvbCsxLGVuZExpbmU6dFt0Lmxlbmd0aC0xXS5zb3VyY2VTcGFuLmVuZC5saW5lKzEsZW5kQ29sOnRbMF0uc291cmNlU3Bhbi5zdGFydC5jb2wrMX1dOltdfX0sbF89Y2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLnZhbHVlPXQsdGhpcy5zb3VyY2VTcGFuPWV9dmlzaXQodCxlKXtyZXR1cm4gdC52aXNpdFRleHQodGhpcyxlKX19LG1tPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5jaGlsZHJlbj10LHRoaXMuc291cmNlU3Bhbj1lfXZpc2l0KHQsZSl7cmV0dXJuIHQudmlzaXRDb250YWluZXIodGhpcyxlKX19LGFEPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe3RoaXMuZXhwcmVzc2lvbj10LHRoaXMudHlwZT1lLHRoaXMuY2FzZXM9aSx0aGlzLnNvdXJjZVNwYW49cn12aXNpdCh0LGUpe3JldHVybiB0LnZpc2l0SWN1KHRoaXMsZSl9fSxsRD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy52YWx1ZT10LHRoaXMubmFtZT1lLHRoaXMuc291cmNlU3Bhbj1pfXZpc2l0KHQsZSl7cmV0dXJuIHQudmlzaXRQbGFjZWhvbGRlcih0aGlzLGUpfX0sVF89Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3RoaXMudmFsdWU9dCx0aGlzLm5hbWU9ZSx0aGlzLnNvdXJjZVNwYW49aX12aXNpdCh0LGUpe3JldHVybiB0LnZpc2l0SWN1UGxhY2Vob2xkZXIodGhpcyxlKX19LFVCPWNsYXNze3Zpc2l0VGV4dCh0KXtyZXR1cm4gdC52YWx1ZX12aXNpdENvbnRhaW5lcih0KXtyZXR1cm4gdC5jaGlsZHJlbi5tYXAoZT0+ZS52aXNpdCh0aGlzKSkuam9pbigiIil9dmlzaXRJY3UodCl7bGV0IGU9T2JqZWN0LmtleXModC5jYXNlcykubWFwKGk9PmAke2l9IHske3QuY2FzZXNbaV0udmlzaXQodGhpcyl9fWApO3JldHVybmB7JHt0LmV4cHJlc3Npb25QbGFjZWhvbGRlcn0sICR7dC50eXBlfSwgJHtlLmpvaW4oIiAiKX19YH12aXNpdFRhZ1BsYWNlaG9sZGVyKHQpe2xldCBlPXQuY2hpbGRyZW4ubWFwKGk9PmkudmlzaXQodGhpcykpLmpvaW4oIiIpO3JldHVybmB7JCR7dC5zdGFydE5hbWV9fSR7ZX17JCR7dC5jbG9zZU5hbWV9fWB9dmlzaXRQbGFjZWhvbGRlcih0KXtyZXR1cm5geyQke3QubmFtZX19YH12aXNpdEljdVBsYWNlaG9sZGVyKHQpe3JldHVybmB7JCR7dC5uYW1lfX1gfX07bmV3IGNsYXNze3Zpc2l0VGFnKHQpe2xldCBlPXRoaXMuX3NlcmlhbGl6ZUF0dHJpYnV0ZXModC5hdHRycyk7aWYoMD09dC5jaGlsZHJlbi5sZW5ndGgpcmV0dXJuYDwke3QubmFtZX0ke2V9Lz5gO2xldCBpPXQuY2hpbGRyZW4ubWFwKHI9PnIudmlzaXQodGhpcykpO3JldHVybmA8JHt0Lm5hbWV9JHtlfT4ke2kuam9pbigiIil9PC8ke3QubmFtZX0+YH12aXNpdFRleHQodCl7cmV0dXJuIHQudmFsdWV9dmlzaXREZWNsYXJhdGlvbih0KXtyZXR1cm5gPD94bWwke3RoaXMuX3NlcmlhbGl6ZUF0dHJpYnV0ZXModC5hdHRycyl9ID8+YH1fc2VyaWFsaXplQXR0cmlidXRlcyh0KXtsZXQgZT1PYmplY3Qua2V5cyh0KS5tYXAoaT0+YCR7aX09IiR7dFtpXX0iYCkuam9pbigiICIpO3JldHVybiBlLmxlbmd0aD4wPyIgIitlOiIifXZpc2l0RG9jdHlwZSh0KXtyZXR1cm5gPCFET0NUWVBFICR7dC5yb290VGFnfSBbXG4ke3QuZHRkfVxuXT5gfX07dmFyIGpCPSJpMThuLSI7ZnVuY3Rpb24gQUMobil7cmV0dXJuIG4gaW5zdGFuY2VvZiBGdX1mdW5jdGlvbiBWVChuKXtyZXR1cm4gQUMobikmJjE9PT1uLm5vZGVzLmxlbmd0aCYmbi5ub2Rlc1swXWluc3RhbmNlb2YgYUR9ZnVuY3Rpb24gQ3dlKG4pe3JldHVybiEhbi5pMThufWZ1bmN0aW9uIFdRKG4pe3JldHVybiBuLm5vZGVzWzBdfWZ1bmN0aW9uIFBEKG4sdD0wKXtyZXR1cm5gXHVmZmZkJHtufSR7dD4wP2A6JHt0fWA6IiJ9XHVmZmZkYH1mdW5jdGlvbiBTd2Uobj0wKXtsZXQgdD1uO3JldHVybigpPT50Kyt9ZnVuY3Rpb24gUFgobil7bGV0IHQ9e307cmV0dXJuIG4uZm9yRWFjaCgoZSxpKT0+e3RbaV09aHQoZS5sZW5ndGg+MT9gWyR7ZS5qb2luKCJ8Iil9XWA6ZVswXSl9KSx0fWZ1bmN0aW9uIHFUKG4sdCwuLi5lKXtsZXQgaT1uLmdldCh0KXx8W107aS5wdXNoKC4uLmUpLG4uc2V0KHQsaSl9ZnVuY3Rpb24gcVEobix0PTAsZT0wKXtsZXQgaT10LHI9bmV3IE1hcCxvPW4gaW5zdGFuY2VvZiBGdT9uLm5vZGVzLmZpbmQocz0+cyBpbnN0YW5jZW9mIG1tKTpuO3JldHVybiBvJiZvLmNoaWxkcmVuLmZpbHRlcihzPT5zIGluc3RhbmNlb2YgbEQpLmZvckVhY2goKHMsYSk9PntsZXQgbD1QRChpK2EsZSk7cVQocixzLm5hbWUsbCl9KSxyfWZ1bmN0aW9uIFVWKG49e30sdCl7bGV0IGU9e307cmV0dXJuIG4mJk9iamVjdC5rZXlzKG4pLmxlbmd0aCYmT2JqZWN0LmtleXMobikuZm9yRWFjaChpPT5lW0pDKGksdCldPW5baV0pLGV9ZnVuY3Rpb24gSkMobix0PSEwKXtsZXQgZT1mdW5jdGlvbihuKXtyZXR1cm4gbi50b1VwcGVyQ2FzZSgpLnJlcGxhY2UoL1teQS1aMC05X10vZywiXyIpfShuKTtpZighdClyZXR1cm4gZTtsZXQgcixpPWUuc3BsaXQoIl8iKTtpZigxPT09aS5sZW5ndGgpcmV0dXJuIG4udG9Mb3dlckNhc2UoKTsvXlxkKyQvLnRlc3QoaVtpLmxlbmd0aC0xXSkmJihyPWkucG9wKCkpO2xldCBvPWkuc2hpZnQoKS50b0xvd2VyQ2FzZSgpO3JldHVybiBpLmxlbmd0aCYmKG8rPWkubWFwKHM9PnMuY2hhckF0KDApLnRvVXBwZXJDYXNlKCkrcy5zbGljZSgxKS50b0xvd2VyQ2FzZSgpKS5qb2luKCIiKSkscj9gJHtvfV8ke3J9YDpvfWZ1bmN0aW9uIFJYKG4pe3JldHVybmBNU0dfJHtufWAudG9VcHBlckNhc2UoKX1mdW5jdGlvbiBFd2Uobil7cmV0dXJuIG5ldyBWdShuLm5hbWUsdm9pZCAwLFBhLHZvaWQgMCxuLnNvdXJjZVNwYW4pfXZhciBUd2U9L1stLl0vLEhjPSJjdHgiLCRDPSJyZiIsWVE9InJlc3RvcmVkQ3R4IixSd2U9bmV3IFNldChbdGUuZWxlbWVudCx0ZS5lbGVtZW50U3RhcnQsdGUuZWxlbWVudEVuZCx0ZS5lbGVtZW50Q29udGFpbmVyLHRlLmVsZW1lbnRDb250YWluZXJTdGFydCx0ZS5lbGVtZW50Q29udGFpbmVyRW5kLHRlLmkxOG5FeHAsdGUubGlzdGVuZXIsdGUuY2xhc3NQcm9wLHRlLnN5bnRoZXRpY0hvc3RMaXN0ZW5lcix0ZS5ob3N0UHJvcGVydHksdGUuc3ludGhldGljSG9zdFByb3BlcnR5LHRlLnByb3BlcnR5LHRlLnByb3BlcnR5SW50ZXJwb2xhdGUxLHRlLnByb3BlcnR5SW50ZXJwb2xhdGUyLHRlLnByb3BlcnR5SW50ZXJwb2xhdGUzLHRlLnByb3BlcnR5SW50ZXJwb2xhdGU0LHRlLnByb3BlcnR5SW50ZXJwb2xhdGU1LHRlLnByb3BlcnR5SW50ZXJwb2xhdGU2LHRlLnByb3BlcnR5SW50ZXJwb2xhdGU3LHRlLnByb3BlcnR5SW50ZXJwb2xhdGU4LHRlLnByb3BlcnR5SW50ZXJwb2xhdGVWLHRlLmF0dHJpYnV0ZSx0ZS5hdHRyaWJ1dGVJbnRlcnBvbGF0ZTEsdGUuYXR0cmlidXRlSW50ZXJwb2xhdGUyLHRlLmF0dHJpYnV0ZUludGVycG9sYXRlMyx0ZS5hdHRyaWJ1dGVJbnRlcnBvbGF0ZTQsdGUuYXR0cmlidXRlSW50ZXJwb2xhdGU1LHRlLmF0dHJpYnV0ZUludGVycG9sYXRlNix0ZS5hdHRyaWJ1dGVJbnRlcnBvbGF0ZTcsdGUuYXR0cmlidXRlSW50ZXJwb2xhdGU4LHRlLmF0dHJpYnV0ZUludGVycG9sYXRlVix0ZS5zdHlsZVByb3AsdGUuc3R5bGVQcm9wSW50ZXJwb2xhdGUxLHRlLnN0eWxlUHJvcEludGVycG9sYXRlMix0ZS5zdHlsZVByb3BJbnRlcnBvbGF0ZTMsdGUuc3R5bGVQcm9wSW50ZXJwb2xhdGU0LHRlLnN0eWxlUHJvcEludGVycG9sYXRlNSx0ZS5zdHlsZVByb3BJbnRlcnBvbGF0ZTYsdGUuc3R5bGVQcm9wSW50ZXJwb2xhdGU3LHRlLnN0eWxlUHJvcEludGVycG9sYXRlOCx0ZS5zdHlsZVByb3BJbnRlcnBvbGF0ZVYsdGUudGV4dEludGVycG9sYXRlLHRlLnRleHRJbnRlcnBvbGF0ZTEsdGUudGV4dEludGVycG9sYXRlMix0ZS50ZXh0SW50ZXJwb2xhdGUzLHRlLnRleHRJbnRlcnBvbGF0ZTQsdGUudGV4dEludGVycG9sYXRlNSx0ZS50ZXh0SW50ZXJwb2xhdGU2LHRlLnRleHRJbnRlcnBvbGF0ZTcsdGUudGV4dEludGVycG9sYXRlOCx0ZS50ZXh0SW50ZXJwb2xhdGVWXSk7ZnVuY3Rpb24gZ20obix0LGUpe3JldHVybiBUbih0LG51bGwsbikuY2FsbEZuKGUsbil9ZnVuY3Rpb24gWFEobix0KXtsZXQgZT1udWxsO3JldHVybigpPT4oZXx8KG4ucHVzaChuZXcgVnUoIl90Iix2b2lkIDAsVl8pKSxlPVJpKHQpKSxlKX1mdW5jdGlvbiBpQyhuKXt0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgc3RhdGU6IFZpc2l0b3IgJHt0aGlzLmNvbnN0cnVjdG9yLm5hbWV9IGRvZXNuJ3QgaGFuZGxlICR7bi5jb25zdHJ1Y3Rvci5uYW1lfWApfWZ1bmN0aW9uIE51KG4pe3JldHVybiBBcnJheS5pc0FycmF5KG4pP19yKG4ubWFwKE51KSk6aHQobixQYSl9ZnVuY3Rpb24gT1gobix0KXtyZXR1cm4gT2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMobikubGVuZ3RoPjA/ZnVuY3Rpb24obix0KXtyZXR1cm4gcWwoT2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMobikubWFwKGU9PntsZXQgcixvLHMsYSxpPW5bZV07cmV0dXJuIEFycmF5LmlzQXJyYXkoaSk/KFtvLHJdPWkscz1lLGE9byE9PXIpOihzPXI9ZSxvPWksYT0hMSkse2tleTpzLHF1b3RlZDpUd2UudGVzdChzKSx2YWx1ZTp0JiZhP19yKFtOdShvKSxOdShyKV0pOk51KG8pfX0pKX0obix0KTpudWxsfWZ1bmN0aW9uIHNCKG4pe2Zvcig7VlEobltuLmxlbmd0aC0xXSk7KW4ucG9wKCk7cmV0dXJuIG59ZnVuY3Rpb24ga3dlKG4sdCl7aWYoQXJyYXkuaXNBcnJheShuLnByZWRpY2F0ZSkpe2xldCBlPVtdO3JldHVybiBuLnByZWRpY2F0ZS5mb3JFYWNoKGk9PntsZXQgcj1pLnNwbGl0KCIsIikubWFwKG89Pmh0KG8udHJpbSgpKSk7ZS5wdXNoKC4uLnIpfSksdC5nZXRDb25zdExpdGVyYWwoX3IoZSksITApfXN3aXRjaChuLnByZWRpY2F0ZS5mb3J3YXJkUmVmKXtjYXNlIDA6Y2FzZSAyOnJldHVybiBuLnByZWRpY2F0ZS5leHByZXNzaW9uO2Nhc2UgMTpyZXR1cm4gVG4odGUucmVzb2x2ZUZvcndhcmRSZWYpLmNhbGxGbihbbi5wcmVkaWNhdGUuZXhwcmVzc2lvbl0pfX12YXIgc2g9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnZhbHVlcz1bXX1zZXQodCxlKXtlJiZ0aGlzLnZhbHVlcy5wdXNoKHtrZXk6dCx2YWx1ZTplLHF1b3RlZDohMX0pfXRvTGl0ZXJhbE1hcCgpe3JldHVybiBxbCh0aGlzLnZhbHVlcyl9fTtmdW5jdGlvbiB3bShuKXtsZXR7ZXhwcmVzc2lvbnM6dCxzdHJpbmdzOmV9PW47cmV0dXJuIDE9PT10Lmxlbmd0aCYmMj09PWUubGVuZ3RoJiYiIj09PWVbMF0mJiIiPT09ZVsxXT8xOnQubGVuZ3RoK2UubGVuZ3RofWZ1bmN0aW9uIGNEKG4pe2xldCB0PVtdLGU9bnVsbCxpPW51bGwscj0wO2ZvcihsZXQgbyBvZiBuKXtsZXQgcz0oImZ1bmN0aW9uIj09dHlwZW9mIG8ucGFyYW1zT3JGbj9vLnBhcmFtc09yRm4oKTpvLnBhcmFtc09yRm4pPz9bXSxhPUFycmF5LmlzQXJyYXkocyk/czpbc107cjw1MDAmJmk9PT1vLnJlZmVyZW5jZSYmUndlLmhhcyhpKT8oZT1lLmNhbGxGbihhLGUuc291cmNlU3BhbikscisrKToobnVsbCE9PWUmJnQucHVzaChlLnRvU3RtdCgpKSxlPWdtKG8uc3BhbixvLnJlZmVyZW5jZSxhKSxpPW8ucmVmZXJlbmNlLHI9MCl9cmV0dXJuIG51bGwhPT1lJiZ0LnB1c2goZS50b1N0bXQoKSksdH1mdW5jdGlvbiBrWChuLHQpe2xldCBlPW51bGwsaT17bmFtZTpuLm5hbWUsdHlwZTpuLnR5cGUsaW50ZXJuYWxUeXBlOm4uaW50ZXJuYWxUeXBlLHR5cGVBcmd1bWVudENvdW50Om4udHlwZUFyZ3VtZW50Q291bnQsZGVwczpbXSx0YXJnZXQ6TmMuSW5qZWN0YWJsZX07aWYodm9pZCAwIT09bi51c2VDbGFzcyl7bGV0IGwsYT1uLnVzZUNsYXNzLmV4cHJlc3Npb24uaXNFcXVpdmFsZW50KG4uaW50ZXJuYWxUeXBlKTt2b2lkIDAhPT1uLmRlcHMmJihsPW4uZGVwcyksZT12b2lkIDAhPT1sP25tKHsuLi5pLGRlbGVnYXRlOm4udXNlQ2xhc3MuZXhwcmVzc2lvbixkZWxlZ2F0ZURlcHM6bCxkZWxlZ2F0ZVR5cGU6Zl8uQ2xhc3N9KTphP25tKGkpOntzdGF0ZW1lbnRzOltdLGV4cHJlc3Npb246Rlgobi50eXBlLnZhbHVlLG4udXNlQ2xhc3MuZXhwcmVzc2lvbix0KX19ZWxzZSBlPXZvaWQgMCE9PW4udXNlRmFjdG9yeT92b2lkIDAhPT1uLmRlcHM/bm0oey4uLmksZGVsZWdhdGU6bi51c2VGYWN0b3J5LGRlbGVnYXRlRGVwczpuLmRlcHN8fFtdLGRlbGVnYXRlVHlwZTpmXy5GdW5jdGlvbn0pOntzdGF0ZW1lbnRzOltdLGV4cHJlc3Npb246cmEoW10sW25ldyBEbyhuLnVzZUZhY3RvcnkuY2FsbEZuKFtdKSldKX06dm9pZCAwIT09bi51c2VWYWx1ZT9ubSh7Li4uaSxleHByZXNzaW9uOm4udXNlVmFsdWUuZXhwcmVzc2lvbn0pOnZvaWQgMCE9PW4udXNlRXhpc3Rpbmc/bm0oey4uLmksZXhwcmVzc2lvbjpUbih0ZS5pbmplY3QpLmNhbGxGbihbbi51c2VFeGlzdGluZy5leHByZXNzaW9uXSl9KTp7c3RhdGVtZW50czpbXSxleHByZXNzaW9uOkZYKG4udHlwZS52YWx1ZSxuLmludGVybmFsVHlwZSx0KX07bGV0IHI9bi5pbnRlcm5hbFR5cGUsbz1uZXcgc2g7cmV0dXJuIG8uc2V0KCJ0b2tlbiIsciksby5zZXQoImZhY3RvcnkiLGUuZXhwcmVzc2lvbiksbnVsbCE9PW4ucHJvdmlkZWRJbi5leHByZXNzaW9uLnZhbHVlJiZvLnNldCgicHJvdmlkZWRJbiIsZnVuY3Rpb24oe2V4cHJlc3Npb246bixmb3J3YXJkUmVmOnR9KXtzd2l0Y2godCl7Y2FzZSAwOmNhc2UgMTpyZXR1cm4gbjtjYXNlIDI6cmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBUbih0ZS5mb3J3YXJkUmVmKS5jYWxsRm4oW3JhKFtdLFtuZXcgRG8obildKV0pfShuKX19KG4ucHJvdmlkZWRJbikpLHtleHByZXNzaW9uOlRuKHRlLlx1MDI3NVx1MDI3NWRlZmluZUluamVjdGFibGUpLmNhbGxGbihbby50b0xpdGVyYWxNYXAoKV0sdm9pZCAwLCEwKSx0eXBlOkZ3ZShuKSxzdGF0ZW1lbnRzOmUuc3RhdGVtZW50c319ZnVuY3Rpb24gRndlKG4pe3JldHVybiBuZXcgVmMoVG4odGUuSW5qZWN0YWJsZURlY2xhcmF0aW9uLFtJRChuLnR5cGUudHlwZSxuLnR5cGVBcmd1bWVudENvdW50KV0pKX1mdW5jdGlvbiBGWChuLHQsZSl7cmV0dXJuIG4ubm9kZT09PXQubm9kZT90LnByb3AoIlx1MDI3NWZhYyIpOk5YKGU/VG4odGUucmVzb2x2ZUZvcndhcmRSZWYpLmNhbGxGbihbdF0pOnQpfWZ1bmN0aW9uIE5YKG4pe3JldHVybiByYShbbmV3IGlhKCJ0IixWXyldLFtuZXcgRG8obi5wcm9wKCJcdTAyNzVmYWMiKS5jYWxsRm4oW1JpKCJ0IildKSldKX12YXIgTndlPVsvXlxzKiQvLC9bPD5dLywvXlt7fV0kLywvJigjfFthLXpdKS9pLC9eXC9cLy9dLERfPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5zdGFydD10LHRoaXMuZW5kPWV9c3RhdGljIGZyb21BcnJheSh0KXtyZXR1cm4gdD8oZnVuY3Rpb24obix0KXtpZihudWxsIT10JiYoIUFycmF5LmlzQXJyYXkodCl8fDIhPXQubGVuZ3RoKSl0aHJvdyBuZXcgRXJyb3IoIkV4cGVjdGVkICdpbnRlcnBvbGF0aW9uJyB0byBiZSBhbiBhcnJheSwgW3N0YXJ0LCBlbmRdLiIpO2lmKG51bGwhPXQpe2xldCBlPXRbMF0saT10WzFdO053ZS5mb3JFYWNoKHI9PntpZihyLnRlc3QoZSl8fHIudGVzdChpKSl0aHJvdyBuZXcgRXJyb3IoYFsnJHtlfScsICcke2l9J10gY29udGFpbnMgdW51c2FibGUgaW50ZXJwb2xhdGlvbiBzeW1ib2wuYCl9KX19KDAsdCksbmV3IERfKHRbMF0sdFsxXSkpOlB1fX0sUHU9bmV3IERfKCJ7eyIsIn19IiksdGg9MTIzLE91PTEyNTtmdW5jdGlvbiBRVihuKXtyZXR1cm4gbj49OSYmbjw9MzJ8fDE2MD09bn1mdW5jdGlvbiAkcChuKXtyZXR1cm4gNDg8PW4mJm48PTU3fWZ1bmN0aW9uIEtWKG4pe3JldHVybiBuPj05NyYmbjw9MTIyfHxuPj02NSYmbjw9OTB9ZnVuY3Rpb24gY0sobil7cmV0dXJuIDEwPT09bnx8MTM9PT1ufWZ1bmN0aW9uIFZYKG4pe3JldHVybiA0ODw9biYmbjw9NTV9ZnVuY3Rpb24gV0Iobil7cmV0dXJuIDM5PT09bnx8MzQ9PT1ufHw5Nj09PW59dmFyIHZtPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe3RoaXMuZmlsZT10LHRoaXMub2Zmc2V0PWUsdGhpcy5saW5lPWksdGhpcy5jb2w9cn10b1N0cmluZygpe3JldHVybiBudWxsIT10aGlzLm9mZnNldD9gJHt0aGlzLmZpbGUudXJsfUAke3RoaXMubGluZX06JHt0aGlzLmNvbH1gOnRoaXMuZmlsZS51cmx9bW92ZUJ5KHQpe2xldCBlPXRoaXMuZmlsZS5jb250ZW50LGk9ZS5sZW5ndGgscj10aGlzLm9mZnNldCxvPXRoaXMubGluZSxzPXRoaXMuY29sO2Zvcig7cj4wJiZ0PDA7KWlmKHItLSx0KyssMTA9PWUuY2hhckNvZGVBdChyKSl7by0tO2xldCBsPWUuc3Vic3RyaW5nKDAsci0xKS5sYXN0SW5kZXhPZihTdHJpbmcuZnJvbUNoYXJDb2RlKDEwKSk7cz1sPjA/ci1sOnJ9ZWxzZSBzLS07Zm9yKDtyPGkmJnQ+MDspe2xldCBhPWUuY2hhckNvZGVBdChyKTtyKyssdC0tLDEwPT1hPyhvKysscz0wKTpzKyt9cmV0dXJuIG5ldyB2bSh0aGlzLmZpbGUscixvLHMpfWdldENvbnRleHQodCxlKXtsZXQgaT10aGlzLmZpbGUuY29udGVudCxyPXRoaXMub2Zmc2V0O2lmKG51bGwhPXIpe3I+aS5sZW5ndGgtMSYmKHI9aS5sZW5ndGgtMSk7bGV0IG89cixzPTAsYT0wO2Zvcig7czx0JiZyPjAmJihyLS0scysrLCJcbiIhPWlbcl18fCsrYSE9ZSk7KTtmb3Iocz0wLGE9MDtzPHQmJm88aS5sZW5ndGgtMSYmKG8rKyxzKyssIlxuIiE9aVtvXXx8KythIT1lKTspO3JldHVybntiZWZvcmU6aS5zdWJzdHJpbmcocix0aGlzLm9mZnNldCksYWZ0ZXI6aS5zdWJzdHJpbmcodGhpcy5vZmZzZXQsbysxKX19cmV0dXJuIG51bGx9fSxwRD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuY29udGVudD10LHRoaXMudXJsPWV9fSxHbz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaT10LHI9bnVsbCl7dGhpcy5zdGFydD10LHRoaXMuZW5kPWUsdGhpcy5mdWxsU3RhcnQ9aSx0aGlzLmRldGFpbHM9cn10b1N0cmluZygpe3JldHVybiB0aGlzLnN0YXJ0LmZpbGUuY29udGVudC5zdWJzdHJpbmcodGhpcy5zdGFydC5vZmZzZXQsdGhpcy5lbmQub2Zmc2V0KX19LGt1PSgoKT0+e3JldHVybihuPWt1fHwoa3U9e30pKVtuLldBUk5JTkc9MF09IldBUk5JTkciLG5bbi5FUlJPUj0xXT0iRVJST1IiLGt1O3ZhciBufSkoKSx5bT1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaT1rdS5FUlJPUil7dGhpcy5zcGFuPXQsdGhpcy5tc2c9ZSx0aGlzLmxldmVsPWl9Y29udGV4dHVhbE1lc3NhZ2UoKXtsZXQgdD10aGlzLnNwYW4uc3RhcnQuZ2V0Q29udGV4dCgxMDAsMyk7cmV0dXJuIHQ/YCR7dGhpcy5tc2d9ICgiJHt0LmJlZm9yZX1bJHtrdVt0aGlzLmxldmVsXX0gLT5dJHt0LmFmdGVyfSIpYDp0aGlzLm1zZ310b1N0cmluZygpe2xldCB0PXRoaXMuc3Bhbi5kZXRhaWxzP2AsICR7dGhpcy5zcGFuLmRldGFpbHN9YDoiIjtyZXR1cm5gJHt0aGlzLmNvbnRleHR1YWxNZXNzYWdlKCl9OiAke3RoaXMuc3Bhbi5zdGFydH0ke3R9YH19LFp3ZT0wO2Z1bmN0aW9uIG1fKG4pe3JldHVybiBuLnJlcGxhY2UoL1xXL2csIl8iKX12YXIgSFQsSFg9Jyh0aGlzJiZ0aGlzLl9fbWFrZVRlbXBsYXRlT2JqZWN0fHxmdW5jdGlvbihlLHQpe3JldHVybiBPYmplY3QuZGVmaW5lUHJvcGVydHk/T2JqZWN0LmRlZmluZVByb3BlcnR5KGUsInJhdyIse3ZhbHVlOnR9KTplLnJhdz10LGV9KScscUI9Y2xhc3MgZXh0ZW5kcyBOQntjb25zdHJ1Y3Rvcigpe3N1cGVyKCExKX12aXNpdFdyYXBwZWROb2RlRXhwcih0LGUpe3Rocm93IG5ldyBFcnJvcigiQ2Fubm90IGVtaXQgYSBXcmFwcGVkTm9kZUV4cHIgaW4gSmF2YXNjcmlwdC4iKX12aXNpdERlY2xhcmVWYXJTdG10KHQsZSl7cmV0dXJuIGUucHJpbnQodCxgdmFyICR7dC5uYW1lfWApLHQudmFsdWUmJihlLnByaW50KHQsIiA9ICIpLHQudmFsdWUudmlzaXRFeHByZXNzaW9uKHRoaXMsZSkpLGUucHJpbnRsbih0LCI7IiksbnVsbH12aXNpdFRhZ2dlZFRlbXBsYXRlRXhwcih0LGUpe2xldCBpPXQudGVtcGxhdGUuZWxlbWVudHM7cmV0dXJuIHQudGFnLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpLGUucHJpbnQodCxgKCR7SFh9KGApLGUucHJpbnQodCxgWyR7aS5tYXAocj0+cm0oci50ZXh0LCExKSkuam9pbigiLCAiKX1dLCBgKSxlLnByaW50KHQsYFske2kubWFwKHI9PnJtKHIucmF3VGV4dCwhMSkpLmpvaW4oIiwgIil9XSlgKSx0LnRlbXBsYXRlLmV4cHJlc3Npb25zLmZvckVhY2gocj0+e2UucHJpbnQodCwiLCAiKSxyLnZpc2l0RXhwcmVzc2lvbih0aGlzLGUpfSksZS5wcmludCh0LCIpIiksbnVsbH12aXNpdEZ1bmN0aW9uRXhwcih0LGUpe3JldHVybiBlLnByaW50KHQsYGZ1bmN0aW9uJHt0Lm5hbWU/IiAiK3QubmFtZToiIn0oYCksdGhpcy5fdmlzaXRQYXJhbXModC5wYXJhbXMsZSksZS5wcmludGxuKHQsIikgeyIpLGUuaW5jSW5kZW50KCksdGhpcy52aXNpdEFsbFN0YXRlbWVudHModC5zdGF0ZW1lbnRzLGUpLGUuZGVjSW5kZW50KCksZS5wcmludCh0LCJ9IiksbnVsbH12aXNpdERlY2xhcmVGdW5jdGlvblN0bXQodCxlKXtyZXR1cm4gZS5wcmludCh0LGBmdW5jdGlvbiAke3QubmFtZX0oYCksdGhpcy5fdmlzaXRQYXJhbXModC5wYXJhbXMsZSksZS5wcmludGxuKHQsIikgeyIpLGUuaW5jSW5kZW50KCksdGhpcy52aXNpdEFsbFN0YXRlbWVudHModC5zdGF0ZW1lbnRzLGUpLGUuZGVjSW5kZW50KCksZS5wcmludGxuKHQsIn0iKSxudWxsfXZpc2l0TG9jYWxpemVkU3RyaW5nKHQsZSl7ZS5wcmludCh0LGAkbG9jYWxpemUoJHtIWH0oYCk7bGV0IGk9W3Quc2VyaWFsaXplSTE4bkhlYWQoKV07Zm9yKGxldCByPTE7cjx0Lm1lc3NhZ2VQYXJ0cy5sZW5ndGg7cisrKWkucHVzaCh0LnNlcmlhbGl6ZUkxOG5UZW1wbGF0ZVBhcnQocikpO3JldHVybiBlLnByaW50KHQsYFske2kubWFwKHI9PnJtKHIuY29va2VkLCExKSkuam9pbigiLCAiKX1dLCBgKSxlLnByaW50KHQsYFske2kubWFwKHI9PnJtKHIucmF3LCExKSkuam9pbigiLCAiKX1dKWApLHQuZXhwcmVzc2lvbnMuZm9yRWFjaChyPT57ZS5wcmludCh0LCIsICIpLHIudmlzaXRFeHByZXNzaW9uKHRoaXMsZSl9KSxlLnByaW50KHQsIikiKSxudWxsfV92aXNpdFBhcmFtcyh0LGUpe3RoaXMudmlzaXRBbGxPYmplY3RzKGk9PmUucHJpbnQobnVsbCxpLm5hbWUpLHQsZSwiLCIpfX07ZnVuY3Rpb24gVVgoLi4ubil7aWYoIXBfLnRydXN0ZWRUeXBlcylyZXR1cm4gbmV3IEZ1bmN0aW9uKC4uLm4pO2xldCBpPWAoZnVuY3Rpb24gYW5vbnltb3VzKCR7bi5zbGljZSgwLC0xKS5qb2luKCIsIil9XG4pIHsgJHtuW24ubGVuZ3RoLTFdfVxufSlgLHI9cF8uZXZhbChmdW5jdGlvbihuKXtyZXR1cm4gZnVuY3Rpb24oKXtpZih2b2lkIDA9PT1IVCYmKEhUPW51bGwscF8udHJ1c3RlZFR5cGVzKSl0cnl7SFQ9cF8udHJ1c3RlZFR5cGVzLmNyZWF0ZVBvbGljeSgiYW5ndWxhciN1bnNhZmUtaml0Iix7Y3JlYXRlU2NyaXB0Om49Pm59KX1jYXRjaHt9cmV0dXJuIEhUfSgpPy5jcmVhdGVTY3JpcHQobil8fG59KGkpKTtyZXR1cm4gdm9pZCAwPT09ci5iaW5kP25ldyBGdW5jdGlvbiguLi5uKTooci50b1N0cmluZz0oKT0+aSxyLmJpbmQocF8pKX12YXIgWEI9Y2xhc3MgZXh0ZW5kcyBxQntjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMucmVmUmVzb2x2ZXI9dCx0aGlzLl9ldmFsQXJnTmFtZXM9W10sdGhpcy5fZXZhbEFyZ1ZhbHVlcz1bXSx0aGlzLl9ldmFsRXhwb3J0ZWRWYXJzPVtdfWNyZWF0ZVJldHVyblN0bXQodCl7bmV3IERvKG5ldyB4Xyh0aGlzLl9ldmFsRXhwb3J0ZWRWYXJzLm1hcChpPT5uZXcgdEQoaSxSaShpKSwhMSkpKSkudmlzaXRTdGF0ZW1lbnQodGhpcyx0KX1nZXRBcmdzKCl7bGV0IHQ9e307Zm9yKGxldCBlPTA7ZTx0aGlzLl9ldmFsQXJnTmFtZXMubGVuZ3RoO2UrKyl0W3RoaXMuX2V2YWxBcmdOYW1lc1tlXV09dGhpcy5fZXZhbEFyZ1ZhbHVlc1tlXTtyZXR1cm4gdH12aXNpdEV4dGVybmFsRXhwcih0LGUpe3JldHVybiB0aGlzLl9lbWl0UmVmZXJlbmNlVG9FeHRlcm5hbCh0LHRoaXMucmVmUmVzb2x2ZXIucmVzb2x2ZUV4dGVybmFsUmVmZXJlbmNlKHQudmFsdWUpLGUpLG51bGx9dmlzaXRXcmFwcGVkTm9kZUV4cHIodCxlKXtyZXR1cm4gdGhpcy5fZW1pdFJlZmVyZW5jZVRvRXh0ZXJuYWwodCx0Lm5vZGUsZSksbnVsbH12aXNpdERlY2xhcmVWYXJTdG10KHQsZSl7cmV0dXJuIHQuaGFzTW9kaWZpZXIobGwuRXhwb3J0ZWQpJiZ0aGlzLl9ldmFsRXhwb3J0ZWRWYXJzLnB1c2godC5uYW1lKSxzdXBlci52aXNpdERlY2xhcmVWYXJTdG10KHQsZSl9dmlzaXREZWNsYXJlRnVuY3Rpb25TdG10KHQsZSl7cmV0dXJuIHQuaGFzTW9kaWZpZXIobGwuRXhwb3J0ZWQpJiZ0aGlzLl9ldmFsRXhwb3J0ZWRWYXJzLnB1c2godC5uYW1lKSxzdXBlci52aXNpdERlY2xhcmVGdW5jdGlvblN0bXQodCxlKX1fZW1pdFJlZmVyZW5jZVRvRXh0ZXJuYWwodCxlLGkpe2xldCByPXRoaXMuX2V2YWxBcmdWYWx1ZXMuaW5kZXhPZihlKTtpZigtMT09PXIpe3I9dGhpcy5fZXZhbEFyZ1ZhbHVlcy5sZW5ndGgsdGhpcy5fZXZhbEFyZ1ZhbHVlcy5wdXNoKGUpO2xldCBvPWZ1bmN0aW9uKG4pe2lmKCFufHwhbi5yZWZlcmVuY2UpcmV0dXJuIG51bGw7bGV0IHQ9bi5yZWZlcmVuY2U7aWYodC5fX2Fub255bW91c1R5cGUpcmV0dXJuIHQuX19hbm9ueW1vdXNUeXBlO2lmKHQuX19mb3J3YXJkX3JlZl9fKXJldHVybiJfX2ZvcndhcmRfcmVmX18iO2xldCBlPWtRKHQpO3JldHVybiBlLmluZGV4T2YoIigiKT49MD8oZT0iYW5vbnltb3VzXyIrWndlKyssdC5fX2Fub255bW91c1R5cGU9ZSk6ZT1tXyhlKSxlfSh7cmVmZXJlbmNlOmV9KXx8InZhbCI7dGhpcy5fZXZhbEFyZ05hbWVzLnB1c2goYGppdF8ke299XyR7cn1gKX1pLnByaW50KHQsdGhpcy5fZXZhbEFyZ05hbWVzW3JdKX19O2Z1bmN0aW9uIHpYKG4pe2xldCB0PW5ldyBzaDtudWxsIT09bi5wcm92aWRlcnMmJnQuc2V0KCJwcm92aWRlcnMiLG4ucHJvdmlkZXJzKSxuLmltcG9ydHMubGVuZ3RoPjAmJnQuc2V0KCJpbXBvcnRzIixfcihuLmltcG9ydHMpKTtsZXQgZT1Ubih0ZS5kZWZpbmVJbmplY3RvcikuY2FsbEZuKFt0LnRvTGl0ZXJhbE1hcCgpXSx2b2lkIDAsITApLGk9ZnVuY3Rpb24obil7cmV0dXJuIG5ldyBWYyhUbih0ZS5JbmplY3RvckRlY2xhcmF0aW9uLFtuZXcgVmMobi50eXBlLnR5cGUpXSkpfShuKTtyZXR1cm57ZXhwcmVzc2lvbjplLHR5cGU6aSxzdGF0ZW1lbnRzOltdfX12YXIgZ189KCgpPT57cmV0dXJuKG49Z198fChnXz17fSkpW24uSW5saW5lPTBdPSJJbmxpbmUiLG5bbi5TaWRlRWZmZWN0PTFdPSJTaWRlRWZmZWN0IixuW24uT21pdD0yXT0iT21pdCIsZ187dmFyIG59KSgpO2Z1bmN0aW9uIGlTZShuKXtsZXR7YWRqYWNlbnRUeXBlOnQsaW50ZXJuYWxUeXBlOmUsYm9vdHN0cmFwOmksZGVjbGFyYXRpb25zOnIsaW1wb3J0czpvLGV4cG9ydHM6cyxzY2hlbWFzOmEsY29udGFpbnNGb3J3YXJkRGVjbHM6bCxzZWxlY3RvclNjb3BlTW9kZTpjLGlkOnV9PW4sZD1bXSxwPW5ldyBzaDtpZihwLnNldCgidHlwZSIsZSksaS5sZW5ndGg+MCYmcC5zZXQoImJvb3RzdHJhcCIsb20oaSxsKSksYz09PWdfLklubGluZSlyLmxlbmd0aD4wJiZwLnNldCgiZGVjbGFyYXRpb25zIixvbShyLGwpKSxvLmxlbmd0aD4wJiZwLnNldCgiaW1wb3J0cyIsb20obyxsKSkscy5sZW5ndGg+MCYmcC5zZXQoImV4cG9ydHMiLG9tKHMsbCkpO2Vsc2UgaWYoYz09PWdfLlNpZGVFZmZlY3Qpe2xldCBtPWZ1bmN0aW9uKG4pe2xldHthZGphY2VudFR5cGU6dCxkZWNsYXJhdGlvbnM6ZSxpbXBvcnRzOmksZXhwb3J0czpyLGNvbnRhaW5zRm9yd2FyZERlY2xzOm99PW4scz1uZXcgc2g7aWYoZS5sZW5ndGg+MCYmcy5zZXQoImRlY2xhcmF0aW9ucyIsb20oZSxvKSksaS5sZW5ndGg+MCYmcy5zZXQoImltcG9ydHMiLG9tKGksbykpLHIubGVuZ3RoPjAmJnMuc2V0KCJleHBvcnRzIixvbShyLG8pKSwwPT09T2JqZWN0LmtleXMocy52YWx1ZXMpLmxlbmd0aClyZXR1cm4gbnVsbDtsZXQgbD1mdW5jdGlvbihuKXtyZXR1cm4gZnVuY3Rpb24obix0KXtsZXQgZT1uZXcgeV8oe25hbWU6Im5nSml0TW9kZSIsbW9kdWxlTmFtZTpudWxsfSksaT1uZXcgZ3IoQ24uSWRlbnRpY2FsLG5ldyB2XyhlKSxodCgidW5kZWZpbmVkIikpLHI9bmV3IGdyKENuLk9yLGksZSx2b2lkIDAsdm9pZCAwLCEwKTtyZXR1cm4gbmV3IGdyKENuLkFuZCxyLHQpfSgwLG4pfShuZXcgb2goVG4odGUuc2V0TmdNb2R1bGVTY29wZSksW3Qscy50b0xpdGVyYWxNYXAoKV0pKSxjPW5ldyBwbShbXSxbbC50b1N0bXQoKV0pO3JldHVybiBuZXcgb2goYyxbXSkudG9TdG10KCl9KG4pO251bGwhPT1tJiZkLnB1c2gobSl9bnVsbCE9PWEmJmEubGVuZ3RoPjAmJnAuc2V0KCJzY2hlbWFzIixfcihhLm1hcChtPT5tLnZhbHVlKSkpLG51bGwhPT11JiYocC5zZXQoImlkIix1KSxkLnB1c2goVG4odGUucmVnaXN0ZXJOZ01vZHVsZVR5cGUpLmNhbGxGbihbdCx1XSkudG9TdG10KCkpKTtsZXQgaD1Ubih0ZS5kZWZpbmVOZ01vZHVsZSkuY2FsbEZuKFtwLnRvTGl0ZXJhbE1hcCgpXSx2b2lkIDAsITApLGY9ZnVuY3Rpb24oe3R5cGU6bixkZWNsYXJhdGlvbnM6dCxleHBvcnRzOmUsaW1wb3J0czppLGluY2x1ZGVJbXBvcnRUeXBlczpyLHB1YmxpY0RlY2xhcmF0aW9uVHlwZXM6b30pe3JldHVybiBuZXcgVmMoVG4odGUuTmdNb2R1bGVEZWNsYXJhdGlvbixbbmV3IFZjKG4udHlwZSksbnVsbD09PW8/YUIodCk6YVNlKG8pLHI/YUIoaSk6SmQsYUIoZSldKSl9KG4pO3JldHVybntleHByZXNzaW9uOmgsdHlwZTpmLHN0YXRlbWVudHM6ZH19ZnVuY3Rpb24gYUIobil7bGV0IHQ9bi5tYXAoZT0+QlYoZS50eXBlKSk7cmV0dXJuIG4ubGVuZ3RoPjA/dWwoX3IodCkpOkpkfWZ1bmN0aW9uIGFTZShuKXtsZXQgdD1uLm1hcChlPT5CVihlKSk7cmV0dXJuIG4ubGVuZ3RoPjA/dWwoX3IodCkpOkpkfWZ1bmN0aW9uIGpYKG4pe2xldCB0PVtdO3QucHVzaCh7a2V5OiJuYW1lIix2YWx1ZTpodChuLnBpcGVOYW1lKSxxdW90ZWQ6ITF9KSx0LnB1c2goe2tleToidHlwZSIsdmFsdWU6bi50eXBlLnZhbHVlLHF1b3RlZDohMX0pLHQucHVzaCh7a2V5OiJwdXJlIix2YWx1ZTpodChuLnB1cmUpLHF1b3RlZDohMX0pLG4uaXNTdGFuZGFsb25lJiZ0LnB1c2goe2tleToic3RhbmRhbG9uZSIsdmFsdWU6aHQoITApLHF1b3RlZDohMX0pO2xldCBlPVRuKHRlLmRlZmluZVBpcGUpLmNhbGxGbihbcWwodCldLHZvaWQgMCwhMCksaT1mdW5jdGlvbihuKXtyZXR1cm4gbmV3IFZjKFRuKHRlLlBpcGVEZWNsYXJhdGlvbixbSUQobi50eXBlLnR5cGUsbi50eXBlQXJndW1lbnRDb3VudCksbmV3IFZjKG5ldyBjbChuLnBpcGVOYW1lKSksbmV3IFZjKG5ldyBjbChuLmlzU3RhbmRhbG9uZSkpXSkpfShuKTtyZXR1cm57ZXhwcmVzc2lvbjplLHR5cGU6aSxzdGF0ZW1lbnRzOltdfX12YXIgX189KCgpPT57cmV0dXJuKG49X198fChfXz17fSkpW24uRGlyZWN0aXZlPTBdPSJEaXJlY3RpdmUiLG5bbi5QaXBlPTFdPSJQaXBlIixuW24uTmdNb2R1bGU9Ml09Ik5nTW9kdWxlIixfXzt2YXIgbn0pKCksUkM9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscil7dGhpcy5pbnB1dD1lLHRoaXMuZXJyTG9jYXRpb249aSx0aGlzLmN0eExvY2F0aW9uPXIsdGhpcy5tZXNzYWdlPWBQYXJzZXIgRXJyb3I6ICR7dH0gJHtpfSBbJHtlfV0gaW4gJHtyfWB9fSxibT1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuc3RhcnQ9dCx0aGlzLmVuZD1lfXRvQWJzb2x1dGUodCl7cmV0dXJuIG5ldyBhbCh0K3RoaXMuc3RhcnQsdCt0aGlzLmVuZCl9fSxBbz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuc3Bhbj10LHRoaXMuc291cmNlU3Bhbj1lfXRvU3RyaW5nKCl7cmV0dXJuIkFTVCJ9fSxBXz1jbGFzcyBleHRlbmRzIEFve2NvbnN0cnVjdG9yKHQsZSxpKXtzdXBlcih0LGUpLHRoaXMubmFtZVNwYW49aX19LElhPWNsYXNzIGV4dGVuZHMgQW97dmlzaXQodCxlPW51bGwpe319LHhtPWNsYXNzIGV4dGVuZHMgQW97dmlzaXQodCxlPW51bGwpe3JldHVybiB0LnZpc2l0SW1wbGljaXRSZWNlaXZlcih0aGlzLGUpfX0sT0M9Y2xhc3MgZXh0ZW5kcyB4bXt2aXNpdCh0LGU9bnVsbCl7cmV0dXJuIHQudmlzaXRUaGlzUmVjZWl2ZXI/Lih0aGlzLGUpfX0sa0M9Y2xhc3MgZXh0ZW5kcyBBb3tjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIodCxlKSx0aGlzLmV4cHJlc3Npb25zPWl9dmlzaXQodCxlPW51bGwpe3JldHVybiB0LnZpc2l0Q2hhaW4odGhpcyxlKX19LEZDPWNsYXNzIGV4dGVuZHMgQW97Y29uc3RydWN0b3IodCxlLGkscixvKXtzdXBlcih0LGUpLHRoaXMuY29uZGl0aW9uPWksdGhpcy50cnVlRXhwPXIsdGhpcy5mYWxzZUV4cD1vfXZpc2l0KHQsZT1udWxsKXtyZXR1cm4gdC52aXNpdENvbmRpdGlvbmFsKHRoaXMsZSl9fSxMdT1jbGFzcyBleHRlbmRzIEFfe2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7c3VwZXIodCxlLGkpLHRoaXMucmVjZWl2ZXI9cix0aGlzLm5hbWU9b312aXNpdCh0LGU9bnVsbCl7cmV0dXJuIHQudmlzaXRQcm9wZXJ0eVJlYWQodGhpcyxlKX19LE5DPWNsYXNzIGV4dGVuZHMgQV97Y29uc3RydWN0b3IodCxlLGkscixvLHMpe3N1cGVyKHQsZSxpKSx0aGlzLnJlY2VpdmVyPXIsdGhpcy5uYW1lPW8sdGhpcy52YWx1ZT1zfXZpc2l0KHQsZT1udWxsKXtyZXR1cm4gdC52aXNpdFByb3BlcnR5V3JpdGUodGhpcyxlKX19LExDPWNsYXNzIGV4dGVuZHMgQV97Y29uc3RydWN0b3IodCxlLGkscixvKXtzdXBlcih0LGUsaSksdGhpcy5yZWNlaXZlcj1yLHRoaXMubmFtZT1vfXZpc2l0KHQsZT1udWxsKXtyZXR1cm4gdC52aXNpdFNhZmVQcm9wZXJ0eVJlYWQodGhpcyxlKX19LElfPWNsYXNzIGV4dGVuZHMgQW97Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIodCxlKSx0aGlzLnJlY2VpdmVyPWksdGhpcy5rZXk9cn12aXNpdCh0LGU9bnVsbCl7cmV0dXJuIHQudmlzaXRLZXllZFJlYWQodGhpcyxlKX19LFBfPWNsYXNzIGV4dGVuZHMgQW97Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIodCxlKSx0aGlzLnJlY2VpdmVyPWksdGhpcy5rZXk9cn12aXNpdCh0LGU9bnVsbCl7cmV0dXJuIHQudmlzaXRTYWZlS2V5ZWRSZWFkKHRoaXMsZSl9fSxCQz1jbGFzcyBleHRlbmRzIEFve2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7c3VwZXIodCxlKSx0aGlzLnJlY2VpdmVyPWksdGhpcy5rZXk9cix0aGlzLnZhbHVlPW99dmlzaXQodCxlPW51bGwpe3JldHVybiB0LnZpc2l0S2V5ZWRXcml0ZSh0aGlzLGUpfX0sUl89Y2xhc3MgZXh0ZW5kcyBBX3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyl7c3VwZXIodCxlLHMpLHRoaXMuZXhwPWksdGhpcy5uYW1lPXIsdGhpcy5hcmdzPW99dmlzaXQodCxlPW51bGwpe3JldHVybiB0LnZpc2l0UGlwZSh0aGlzLGUpfX0sdGE9Y2xhc3MgZXh0ZW5kcyBBb3tjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIodCxlKSx0aGlzLnZhbHVlPWl9dmlzaXQodCxlPW51bGwpe3JldHVybiB0LnZpc2l0TGl0ZXJhbFByaW1pdGl2ZSh0aGlzLGUpfX0sT189Y2xhc3MgZXh0ZW5kcyBBb3tjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIodCxlKSx0aGlzLmV4cHJlc3Npb25zPWl9dmlzaXQodCxlPW51bGwpe3JldHVybiB0LnZpc2l0TGl0ZXJhbEFycmF5KHRoaXMsZSl9fSxWQz1jbGFzcyBleHRlbmRzIEFve2NvbnN0cnVjdG9yKHQsZSxpLHIpe3N1cGVyKHQsZSksdGhpcy5rZXlzPWksdGhpcy52YWx1ZXM9cn12aXNpdCh0LGU9bnVsbCl7cmV0dXJuIHQudmlzaXRMaXRlcmFsTWFwKHRoaXMsZSl9fSx2cz1jbGFzcyBleHRlbmRzIEFve2NvbnN0cnVjdG9yKHQsZSxpLHIpe3N1cGVyKHQsZSksdGhpcy5zdHJpbmdzPWksdGhpcy5leHByZXNzaW9ucz1yfXZpc2l0KHQsZT1udWxsKXtyZXR1cm4gdC52aXNpdEludGVycG9sYXRpb24odGhpcyxlKX19LEdsPWNsYXNzIGV4dGVuZHMgQW97Y29uc3RydWN0b3IodCxlLGkscixvKXtzdXBlcih0LGUpLHRoaXMub3BlcmF0aW9uPWksdGhpcy5sZWZ0PXIsdGhpcy5yaWdodD1vfXZpc2l0KHQsZT1udWxsKXtyZXR1cm4gdC52aXNpdEJpbmFyeSh0aGlzLGUpfX0semM9Y2xhc3MgZXh0ZW5kcyBHbHtjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyxhKXtzdXBlcih0LGUsbyxzLGEpLHRoaXMub3BlcmF0b3I9aSx0aGlzLmV4cHI9cix0aGlzLmxlZnQ9bnVsbCx0aGlzLnJpZ2h0PW51bGwsdGhpcy5vcGVyYXRpb249bnVsbH1zdGF0aWMgY3JlYXRlTWludXModCxlLGkpe3JldHVybiBuZXcgemModCxlLCItIixpLCItIixuZXcgdGEodCxlLDApLGkpfXN0YXRpYyBjcmVhdGVQbHVzKHQsZSxpKXtyZXR1cm4gbmV3IHpjKHQsZSwiKyIsaSwiLSIsaSxuZXcgdGEodCxlLDApKX12aXNpdCh0LGU9bnVsbCl7cmV0dXJuIHZvaWQgMCE9PXQudmlzaXRVbmFyeT90LnZpc2l0VW5hcnkodGhpcyxlKTp0LnZpc2l0QmluYXJ5KHRoaXMsZSl9fSxIQz1jbGFzcyBleHRlbmRzIEFve2NvbnN0cnVjdG9yKHQsZSxpKXtzdXBlcih0LGUpLHRoaXMuZXhwcmVzc2lvbj1pfXZpc2l0KHQsZT1udWxsKXtyZXR1cm4gdC52aXNpdFByZWZpeE5vdCh0aGlzLGUpfX0sVUM9Y2xhc3MgZXh0ZW5kcyBBb3tjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIodCxlKSx0aGlzLmV4cHJlc3Npb249aX12aXNpdCh0LGU9bnVsbCl7cmV0dXJuIHQudmlzaXROb25OdWxsQXNzZXJ0KHRoaXMsZSl9fSxhaD1jbGFzcyBleHRlbmRzIEFve2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7c3VwZXIodCxlKSx0aGlzLnJlY2VpdmVyPWksdGhpcy5hcmdzPXIsdGhpcy5hcmd1bWVudFNwYW49b312aXNpdCh0LGU9bnVsbCl7cmV0dXJuIHQudmlzaXRDYWxsKHRoaXMsZSl9fSxrXz1jbGFzcyBleHRlbmRzIEFve2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7c3VwZXIodCxlKSx0aGlzLnJlY2VpdmVyPWksdGhpcy5hcmdzPXIsdGhpcy5hcmd1bWVudFNwYW49b312aXNpdCh0LGU9bnVsbCl7cmV0dXJuIHQudmlzaXRTYWZlQ2FsbCh0aGlzLGUpfX0sYWw9Y2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLnN0YXJ0PXQsdGhpcy5lbmQ9ZX19LFJ1PWNsYXNzIGV4dGVuZHMgQW97Y29uc3RydWN0b3IodCxlLGkscixvKXtzdXBlcihuZXcgYm0oMCxudWxsPT09ZT8wOmUubGVuZ3RoKSxuZXcgYWwocixudWxsPT09ZT9yOnIrZS5sZW5ndGgpKSx0aGlzLmFzdD10LHRoaXMuc291cmNlPWUsdGhpcy5sb2NhdGlvbj1pLHRoaXMuZXJyb3JzPW99dmlzaXQodCxlPW51bGwpe3JldHVybiB0LnZpc2l0QVNUV2l0aFNvdXJjZT90LnZpc2l0QVNUV2l0aFNvdXJjZSh0aGlzLGUpOnRoaXMuYXN0LnZpc2l0KHQsZSl9dG9TdHJpbmcoKXtyZXR1cm5gJHt0aGlzLnNvdXJjZX0gaW4gJHt0aGlzLmxvY2F0aW9ufWB9fSx6Qz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy5zb3VyY2VTcGFuPXQsdGhpcy5rZXk9ZSx0aGlzLnZhbHVlPWl9fSxaQj1jbGFzc3t2aXNpdCh0LGUpe3QudmlzaXQodGhpcyxlKX12aXNpdFVuYXJ5KHQsZSl7dGhpcy52aXNpdCh0LmV4cHIsZSl9dmlzaXRCaW5hcnkodCxlKXt0aGlzLnZpc2l0KHQubGVmdCxlKSx0aGlzLnZpc2l0KHQucmlnaHQsZSl9dmlzaXRDaGFpbih0LGUpe3RoaXMudmlzaXRBbGwodC5leHByZXNzaW9ucyxlKX12aXNpdENvbmRpdGlvbmFsKHQsZSl7dGhpcy52aXNpdCh0LmNvbmRpdGlvbixlKSx0aGlzLnZpc2l0KHQudHJ1ZUV4cCxlKSx0aGlzLnZpc2l0KHQuZmFsc2VFeHAsZSl9dmlzaXRQaXBlKHQsZSl7dGhpcy52aXNpdCh0LmV4cCxlKSx0aGlzLnZpc2l0QWxsKHQuYXJncyxlKX12aXNpdEltcGxpY2l0UmVjZWl2ZXIodCxlKXt9dmlzaXRUaGlzUmVjZWl2ZXIodCxlKXt9dmlzaXRJbnRlcnBvbGF0aW9uKHQsZSl7dGhpcy52aXNpdEFsbCh0LmV4cHJlc3Npb25zLGUpfXZpc2l0S2V5ZWRSZWFkKHQsZSl7dGhpcy52aXNpdCh0LnJlY2VpdmVyLGUpLHRoaXMudmlzaXQodC5rZXksZSl9dmlzaXRLZXllZFdyaXRlKHQsZSl7dGhpcy52aXNpdCh0LnJlY2VpdmVyLGUpLHRoaXMudmlzaXQodC5rZXksZSksdGhpcy52aXNpdCh0LnZhbHVlLGUpfXZpc2l0TGl0ZXJhbEFycmF5KHQsZSl7dGhpcy52aXNpdEFsbCh0LmV4cHJlc3Npb25zLGUpfXZpc2l0TGl0ZXJhbE1hcCh0LGUpe3RoaXMudmlzaXRBbGwodC52YWx1ZXMsZSl9dmlzaXRMaXRlcmFsUHJpbWl0aXZlKHQsZSl7fXZpc2l0UHJlZml4Tm90KHQsZSl7dGhpcy52aXNpdCh0LmV4cHJlc3Npb24sZSl9dmlzaXROb25OdWxsQXNzZXJ0KHQsZSl7dGhpcy52aXNpdCh0LmV4cHJlc3Npb24sZSl9dmlzaXRQcm9wZXJ0eVJlYWQodCxlKXt0aGlzLnZpc2l0KHQucmVjZWl2ZXIsZSl9dmlzaXRQcm9wZXJ0eVdyaXRlKHQsZSl7dGhpcy52aXNpdCh0LnJlY2VpdmVyLGUpLHRoaXMudmlzaXQodC52YWx1ZSxlKX12aXNpdFNhZmVQcm9wZXJ0eVJlYWQodCxlKXt0aGlzLnZpc2l0KHQucmVjZWl2ZXIsZSl9dmlzaXRTYWZlS2V5ZWRSZWFkKHQsZSl7dGhpcy52aXNpdCh0LnJlY2VpdmVyLGUpLHRoaXMudmlzaXQodC5rZXksZSl9dmlzaXRDYWxsKHQsZSl7dGhpcy52aXNpdCh0LnJlY2VpdmVyLGUpLHRoaXMudmlzaXRBbGwodC5hcmdzLGUpfXZpc2l0U2FmZUNhbGwodCxlKXt0aGlzLnZpc2l0KHQucmVjZWl2ZXIsZSksdGhpcy52aXNpdEFsbCh0LmFyZ3MsZSl9dmlzaXRBbGwodCxlKXtmb3IobGV0IGkgb2YgdCl0aGlzLnZpc2l0KGksZSl9fSxKQj1jbGFzc3t2aXNpdEltcGxpY2l0UmVjZWl2ZXIodCxlKXtyZXR1cm4gdH12aXNpdFRoaXNSZWNlaXZlcih0LGUpe3JldHVybiB0fXZpc2l0SW50ZXJwb2xhdGlvbih0LGUpe3JldHVybiBuZXcgdnModC5zcGFuLHQuc291cmNlU3Bhbix0LnN0cmluZ3MsdGhpcy52aXNpdEFsbCh0LmV4cHJlc3Npb25zKSl9dmlzaXRMaXRlcmFsUHJpbWl0aXZlKHQsZSl7cmV0dXJuIG5ldyB0YSh0LnNwYW4sdC5zb3VyY2VTcGFuLHQudmFsdWUpfXZpc2l0UHJvcGVydHlSZWFkKHQsZSl7cmV0dXJuIG5ldyBMdSh0LnNwYW4sdC5zb3VyY2VTcGFuLHQubmFtZVNwYW4sdC5yZWNlaXZlci52aXNpdCh0aGlzKSx0Lm5hbWUpfXZpc2l0UHJvcGVydHlXcml0ZSh0LGUpe3JldHVybiBuZXcgTkModC5zcGFuLHQuc291cmNlU3Bhbix0Lm5hbWVTcGFuLHQucmVjZWl2ZXIudmlzaXQodGhpcyksdC5uYW1lLHQudmFsdWUudmlzaXQodGhpcykpfXZpc2l0U2FmZVByb3BlcnR5UmVhZCh0LGUpe3JldHVybiBuZXcgTEModC5zcGFuLHQuc291cmNlU3Bhbix0Lm5hbWVTcGFuLHQucmVjZWl2ZXIudmlzaXQodGhpcyksdC5uYW1lKX12aXNpdExpdGVyYWxBcnJheSh0LGUpe3JldHVybiBuZXcgT18odC5zcGFuLHQuc291cmNlU3Bhbix0aGlzLnZpc2l0QWxsKHQuZXhwcmVzc2lvbnMpKX12aXNpdExpdGVyYWxNYXAodCxlKXtyZXR1cm4gbmV3IFZDKHQuc3Bhbix0LnNvdXJjZVNwYW4sdC5rZXlzLHRoaXMudmlzaXRBbGwodC52YWx1ZXMpKX12aXNpdFVuYXJ5KHQsZSl7c3dpdGNoKHQub3BlcmF0b3Ipe2Nhc2UiKyI6cmV0dXJuIHpjLmNyZWF0ZVBsdXModC5zcGFuLHQuc291cmNlU3Bhbix0LmV4cHIudmlzaXQodGhpcykpO2Nhc2UiLSI6cmV0dXJuIHpjLmNyZWF0ZU1pbnVzKHQuc3Bhbix0LnNvdXJjZVNwYW4sdC5leHByLnZpc2l0KHRoaXMpKTtkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgVW5rbm93biB1bmFyeSBvcGVyYXRvciAke3Qub3BlcmF0b3J9YCl9fXZpc2l0QmluYXJ5KHQsZSl7cmV0dXJuIG5ldyBHbCh0LnNwYW4sdC5zb3VyY2VTcGFuLHQub3BlcmF0aW9uLHQubGVmdC52aXNpdCh0aGlzKSx0LnJpZ2h0LnZpc2l0KHRoaXMpKX12aXNpdFByZWZpeE5vdCh0LGUpe3JldHVybiBuZXcgSEModC5zcGFuLHQuc291cmNlU3Bhbix0LmV4cHJlc3Npb24udmlzaXQodGhpcykpfXZpc2l0Tm9uTnVsbEFzc2VydCh0LGUpe3JldHVybiBuZXcgVUModC5zcGFuLHQuc291cmNlU3Bhbix0LmV4cHJlc3Npb24udmlzaXQodGhpcykpfXZpc2l0Q29uZGl0aW9uYWwodCxlKXtyZXR1cm4gbmV3IEZDKHQuc3Bhbix0LnNvdXJjZVNwYW4sdC5jb25kaXRpb24udmlzaXQodGhpcyksdC50cnVlRXhwLnZpc2l0KHRoaXMpLHQuZmFsc2VFeHAudmlzaXQodGhpcykpfXZpc2l0UGlwZSh0LGUpe3JldHVybiBuZXcgUl8odC5zcGFuLHQuc291cmNlU3Bhbix0LmV4cC52aXNpdCh0aGlzKSx0Lm5hbWUsdGhpcy52aXNpdEFsbCh0LmFyZ3MpLHQubmFtZVNwYW4pfXZpc2l0S2V5ZWRSZWFkKHQsZSl7cmV0dXJuIG5ldyBJXyh0LnNwYW4sdC5zb3VyY2VTcGFuLHQucmVjZWl2ZXIudmlzaXQodGhpcyksdC5rZXkudmlzaXQodGhpcykpfXZpc2l0S2V5ZWRXcml0ZSh0LGUpe3JldHVybiBuZXcgQkModC5zcGFuLHQuc291cmNlU3Bhbix0LnJlY2VpdmVyLnZpc2l0KHRoaXMpLHQua2V5LnZpc2l0KHRoaXMpLHQudmFsdWUudmlzaXQodGhpcykpfXZpc2l0Q2FsbCh0LGUpe3JldHVybiBuZXcgYWgodC5zcGFuLHQuc291cmNlU3Bhbix0LnJlY2VpdmVyLnZpc2l0KHRoaXMpLHRoaXMudmlzaXRBbGwodC5hcmdzKSx0LmFyZ3VtZW50U3Bhbil9dmlzaXRTYWZlQ2FsbCh0LGUpe3JldHVybiBuZXcga18odC5zcGFuLHQuc291cmNlU3Bhbix0LnJlY2VpdmVyLnZpc2l0KHRoaXMpLHRoaXMudmlzaXRBbGwodC5hcmdzKSx0LmFyZ3VtZW50U3Bhbil9dmlzaXRBbGwodCl7bGV0IGU9W107Zm9yKGxldCBpPTA7aTx0Lmxlbmd0aDsrK2kpZVtpXT10W2ldLnZpc2l0KHRoaXMpO3JldHVybiBlfXZpc2l0Q2hhaW4odCxlKXtyZXR1cm4gbmV3IGtDKHQuc3Bhbix0LnNvdXJjZVNwYW4sdGhpcy52aXNpdEFsbCh0LmV4cHJlc3Npb25zKSl9dmlzaXRTYWZlS2V5ZWRSZWFkKHQsZSl7cmV0dXJuIG5ldyBQXyh0LnNwYW4sdC5zb3VyY2VTcGFuLHQucmVjZWl2ZXIudmlzaXQodGhpcyksdC5rZXkudmlzaXQodGhpcykpfX0sJEI9Y2xhc3N7dmlzaXRJbXBsaWNpdFJlY2VpdmVyKHQsZSl7cmV0dXJuIHR9dmlzaXRUaGlzUmVjZWl2ZXIodCxlKXtyZXR1cm4gdH12aXNpdEludGVycG9sYXRpb24odCxlKXtsZXQgaT10aGlzLnZpc2l0QWxsKHQuZXhwcmVzc2lvbnMpO3JldHVybiBpIT09dC5leHByZXNzaW9ucz9uZXcgdnModC5zcGFuLHQuc291cmNlU3Bhbix0LnN0cmluZ3MsaSk6dH12aXNpdExpdGVyYWxQcmltaXRpdmUodCxlKXtyZXR1cm4gdH12aXNpdFByb3BlcnR5UmVhZCh0LGUpe2xldCBpPXQucmVjZWl2ZXIudmlzaXQodGhpcyk7cmV0dXJuIGkhPT10LnJlY2VpdmVyP25ldyBMdSh0LnNwYW4sdC5zb3VyY2VTcGFuLHQubmFtZVNwYW4saSx0Lm5hbWUpOnR9dmlzaXRQcm9wZXJ0eVdyaXRlKHQsZSl7bGV0IGk9dC5yZWNlaXZlci52aXNpdCh0aGlzKSxyPXQudmFsdWUudmlzaXQodGhpcyk7cmV0dXJuIGkhPT10LnJlY2VpdmVyfHxyIT09dC52YWx1ZT9uZXcgTkModC5zcGFuLHQuc291cmNlU3Bhbix0Lm5hbWVTcGFuLGksdC5uYW1lLHIpOnR9dmlzaXRTYWZlUHJvcGVydHlSZWFkKHQsZSl7bGV0IGk9dC5yZWNlaXZlci52aXNpdCh0aGlzKTtyZXR1cm4gaSE9PXQucmVjZWl2ZXI/bmV3IExDKHQuc3Bhbix0LnNvdXJjZVNwYW4sdC5uYW1lU3BhbixpLHQubmFtZSk6dH12aXNpdExpdGVyYWxBcnJheSh0LGUpe2xldCBpPXRoaXMudmlzaXRBbGwodC5leHByZXNzaW9ucyk7cmV0dXJuIGkhPT10LmV4cHJlc3Npb25zP25ldyBPXyh0LnNwYW4sdC5zb3VyY2VTcGFuLGkpOnR9dmlzaXRMaXRlcmFsTWFwKHQsZSl7bGV0IGk9dGhpcy52aXNpdEFsbCh0LnZhbHVlcyk7cmV0dXJuIGkhPT10LnZhbHVlcz9uZXcgVkModC5zcGFuLHQuc291cmNlU3Bhbix0LmtleXMsaSk6dH12aXNpdFVuYXJ5KHQsZSl7bGV0IGk9dC5leHByLnZpc2l0KHRoaXMpO2lmKGkhPT10LmV4cHIpc3dpdGNoKHQub3BlcmF0b3Ipe2Nhc2UiKyI6cmV0dXJuIHpjLmNyZWF0ZVBsdXModC5zcGFuLHQuc291cmNlU3BhbixpKTtjYXNlIi0iOnJldHVybiB6Yy5jcmVhdGVNaW51cyh0LnNwYW4sdC5zb3VyY2VTcGFuLGkpO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKGBVbmtub3duIHVuYXJ5IG9wZXJhdG9yICR7dC5vcGVyYXRvcn1gKX1yZXR1cm4gdH12aXNpdEJpbmFyeSh0LGUpe2xldCBpPXQubGVmdC52aXNpdCh0aGlzKSxyPXQucmlnaHQudmlzaXQodGhpcyk7cmV0dXJuIGkhPT10LmxlZnR8fHIhPT10LnJpZ2h0P25ldyBHbCh0LnNwYW4sdC5zb3VyY2VTcGFuLHQub3BlcmF0aW9uLGkscik6dH12aXNpdFByZWZpeE5vdCh0LGUpe2xldCBpPXQuZXhwcmVzc2lvbi52aXNpdCh0aGlzKTtyZXR1cm4gaSE9PXQuZXhwcmVzc2lvbj9uZXcgSEModC5zcGFuLHQuc291cmNlU3BhbixpKTp0fXZpc2l0Tm9uTnVsbEFzc2VydCh0LGUpe2xldCBpPXQuZXhwcmVzc2lvbi52aXNpdCh0aGlzKTtyZXR1cm4gaSE9PXQuZXhwcmVzc2lvbj9uZXcgVUModC5zcGFuLHQuc291cmNlU3BhbixpKTp0fXZpc2l0Q29uZGl0aW9uYWwodCxlKXtsZXQgaT10LmNvbmRpdGlvbi52aXNpdCh0aGlzKSxyPXQudHJ1ZUV4cC52aXNpdCh0aGlzKSxvPXQuZmFsc2VFeHAudmlzaXQodGhpcyk7cmV0dXJuIGkhPT10LmNvbmRpdGlvbnx8ciE9PXQudHJ1ZUV4cHx8byE9PXQuZmFsc2VFeHA/bmV3IEZDKHQuc3Bhbix0LnNvdXJjZVNwYW4saSxyLG8pOnR9dmlzaXRQaXBlKHQsZSl7bGV0IGk9dC5leHAudmlzaXQodGhpcykscj10aGlzLnZpc2l0QWxsKHQuYXJncyk7cmV0dXJuIGkhPT10LmV4cHx8ciE9PXQuYXJncz9uZXcgUl8odC5zcGFuLHQuc291cmNlU3BhbixpLHQubmFtZSxyLHQubmFtZVNwYW4pOnR9dmlzaXRLZXllZFJlYWQodCxlKXtsZXQgaT10LnJlY2VpdmVyLnZpc2l0KHRoaXMpLHI9dC5rZXkudmlzaXQodGhpcyk7cmV0dXJuIGkhPT10LnJlY2VpdmVyfHxyIT09dC5rZXk/bmV3IElfKHQuc3Bhbix0LnNvdXJjZVNwYW4saSxyKTp0fXZpc2l0S2V5ZWRXcml0ZSh0LGUpe2xldCBpPXQucmVjZWl2ZXIudmlzaXQodGhpcykscj10LmtleS52aXNpdCh0aGlzKSxvPXQudmFsdWUudmlzaXQodGhpcyk7cmV0dXJuIGkhPT10LnJlY2VpdmVyfHxyIT09dC5rZXl8fG8hPT10LnZhbHVlP25ldyBCQyh0LnNwYW4sdC5zb3VyY2VTcGFuLGkscixvKTp0fXZpc2l0QWxsKHQpe2xldCBlPVtdLGk9ITE7Zm9yKGxldCByPTA7cjx0Lmxlbmd0aDsrK3Ipe2xldCBvPXRbcl0scz1vLnZpc2l0KHRoaXMpO2Vbcl09cyxpPWl8fHMhPT1vfXJldHVybiBpP2U6dH12aXNpdENoYWluKHQsZSl7bGV0IGk9dGhpcy52aXNpdEFsbCh0LmV4cHJlc3Npb25zKTtyZXR1cm4gaSE9PXQuZXhwcmVzc2lvbnM/bmV3IGtDKHQuc3Bhbix0LnNvdXJjZVNwYW4saSk6dH12aXNpdENhbGwodCxlKXtsZXQgaT10LnJlY2VpdmVyLnZpc2l0KHRoaXMpLHI9dGhpcy52aXNpdEFsbCh0LmFyZ3MpO3JldHVybiBpIT09dC5yZWNlaXZlcnx8ciE9PXQuYXJncz9uZXcgYWgodC5zcGFuLHQuc291cmNlU3BhbixpLHIsdC5hcmd1bWVudFNwYW4pOnR9dmlzaXRTYWZlQ2FsbCh0LGUpe2xldCBpPXQucmVjZWl2ZXIudmlzaXQodGhpcykscj10aGlzLnZpc2l0QWxsKHQuYXJncyk7cmV0dXJuIGkhPT10LnJlY2VpdmVyfHxyIT09dC5hcmdzP25ldyBrXyh0LnNwYW4sdC5zb3VyY2VTcGFuLGkscix0LmFyZ3VtZW50U3Bhbik6dH12aXNpdFNhZmVLZXllZFJlYWQodCxlKXtsZXQgaT10LnJlY2VpdmVyLnZpc2l0KHRoaXMpLHI9dC5rZXkudmlzaXQodGhpcyk7cmV0dXJuIGkhPT10LnJlY2VpdmVyfHxyIT09dC5rZXk/bmV3IFBfKHQuc3Bhbix0LnNvdXJjZVNwYW4saSxyKTp0fX0scEM9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscixvLHMpe3RoaXMubmFtZT10LHRoaXMuZXhwcmVzc2lvbj1lLHRoaXMudHlwZT1pLHRoaXMuc291cmNlU3Bhbj1yLHRoaXMua2V5U3Bhbj1vLHRoaXMudmFsdWVTcGFuPXMsdGhpcy5pc0xpdGVyYWw9dGhpcy50eXBlPT09aWguTElURVJBTF9BVFRSLHRoaXMuaXNBbmltYXRpb249dGhpcy50eXBlPT09aWguQU5JTUFUSU9OfX0saWg9KCgpPT57cmV0dXJuKG49aWh8fChpaD17fSkpW24uREVGQVVMVD0wXT0iREVGQVVMVCIsbltuLkxJVEVSQUxfQVRUUj0xXT0iTElURVJBTF9BVFRSIixuW24uQU5JTUFUSU9OPTJdPSJBTklNQVRJT04iLGloO3ZhciBufSkoKSxoRD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyxhKXt0aGlzLm5hbWU9dCx0aGlzLnRhcmdldE9yUGhhc2U9ZSx0aGlzLnR5cGU9aSx0aGlzLmhhbmRsZXI9cix0aGlzLnNvdXJjZVNwYW49byx0aGlzLmhhbmRsZXJTcGFuPXMsdGhpcy5rZXlTcGFuPWF9fSxlVj1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8pe3RoaXMubmFtZT10LHRoaXMudmFsdWU9ZSx0aGlzLnNvdXJjZVNwYW49aSx0aGlzLmtleVNwYW49cix0aGlzLnZhbHVlU3Bhbj1vfX0sZkQ9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscixvLHMsYSxsKXt0aGlzLm5hbWU9dCx0aGlzLnR5cGU9ZSx0aGlzLnNlY3VyaXR5Q29udGV4dD1pLHRoaXMudmFsdWU9cix0aGlzLnVuaXQ9byx0aGlzLnNvdXJjZVNwYW49cyx0aGlzLmtleVNwYW49YSx0aGlzLnZhbHVlU3Bhbj1sfX0sakM9Y2xhc3N7fTtmdW5jdGlvbiBjU2Uobix0LGUsaSxyLG8scyl7bnx8KG49bmV3IG1EKHMpKTtsZXQgYT1mdW5jdGlvbihuLHQpe3JldHVybiBmdW5jdGlvbihuLHQpe2xldCBlPW5ldyBpVihuKTtyZXR1cm4gdC52aXNpdChlKX0obix0KX0oe2NyZWF0ZUxpdGVyYWxBcnJheUNvbnZlcnRlcjpkPT5wPT5fcihwKSxjcmVhdGVMaXRlcmFsTWFwQ29udmVydGVyOmQ9PnA9PnFsKGQubWFwKChmLG0pPT4oe2tleTpmLmtleSx2YWx1ZTpwW21dLHF1b3RlZDpmLnF1b3RlZH0pKSksY3JlYXRlUGlwZUNvbnZlcnRlcjpkPT57dGhyb3cgbmV3IEVycm9yKGBJbGxlZ2FsIFN0YXRlOiBBY3Rpb25zIGFyZSBub3QgYWxsb3dlZCB0byBjb250YWluIHBpcGVzLiBQaXBlOiAke2R9YCl9fSxlKSxsPW5ldyBHQyhuLHQsaSwhMSxyLG8pLGM9W107aEsoYS52aXNpdChsLHppLlN0YXRlbWVudCksYyksZnVuY3Rpb24obix0LGUpe2ZvcihsZXQgaT1uLTE7aT49MDtpLS0pZS51bnNoaWZ0KHBLKHQsaSkpfShsLnRlbXBvcmFyeUNvdW50LGksYyksbC51c2VzSW1wbGljaXRSZWNlaXZlciYmbi5ub3RpZnlJbXBsaWNpdFJlY2VpdmVyVXNlKCk7bGV0IHU9Yy5sZW5ndGgtMTtpZih1Pj0wKXtsZXQgZD1jW3VdO2QgaW5zdGFuY2VvZiBIdSYmKGNbdV09bmV3IERvKGQuZXhwcikpfXJldHVybiBjfWZ1bmN0aW9uIHVLKG4sdCxlLGkpe258fChuPW5ldyBtRCk7bGV0IHI9bmV3IEdDKG4sdCxpLCExKSxvPWUudmlzaXQocix6aS5FeHByZXNzaW9uKSxzPWRLKHIsaSk7cmV0dXJuIHIudXNlc0ltcGxpY2l0UmVjZWl2ZXImJm4ubm90aWZ5SW1wbGljaXRSZWNlaXZlclVzZSgpLG5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuc3RtdHM9dCx0aGlzLmN1cnJWYWxFeHByPWV9fShzLG8pfWZ1bmN0aW9uIGRLKG4sdCl7bGV0IGU9W107Zm9yKGxldCBpPTA7aTxuLnRlbXBvcmFyeUNvdW50O2krKyllLnB1c2gocEsodCxpKSk7cmV0dXJuIGV9ZnVuY3Rpb24gblYobix0KXtyZXR1cm5gdG1wXyR7bn1fJHt0fWB9ZnVuY3Rpb24gcEsobix0KXtyZXR1cm4gbmV3IFZ1KG5WKG4sdCkpfWpDLmV2ZW50PVJpKCIkZXZlbnQiKTt2YXIgemk9KCgpPT57cmV0dXJuKG49eml8fCh6aT17fSkpW24uU3RhdGVtZW50PTBdPSJTdGF0ZW1lbnQiLG5bbi5FeHByZXNzaW9uPTFdPSJFeHByZXNzaW9uIix6aTt2YXIgbn0pKCk7ZnVuY3Rpb24gR1gobix0KXtpZihuIT09emkuRXhwcmVzc2lvbil0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGFuIGV4cHJlc3Npb24sIGJ1dCBzYXcgJHt0fWApfWZ1bmN0aW9uICRzKG4sdCl7cmV0dXJuIG49PT16aS5TdGF0ZW1lbnQ/dC50b1N0bXQoKTp0fXZhciBpVj1jbGFzcyBleHRlbmRzIEpCe2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5fY29udmVydGVyRmFjdG9yeT10fXZpc2l0UGlwZSh0LGUpe2xldCBpPVt0LmV4cCwuLi50LmFyZ3NdLm1hcChyPT5yLnZpc2l0KHRoaXMsZSkpO3JldHVybiBuZXcgcmgodC5zcGFuLHQuc291cmNlU3BhbixpLHRoaXMuX2NvbnZlcnRlckZhY3RvcnkuY3JlYXRlUGlwZUNvbnZlcnRlcih0Lm5hbWUsaS5sZW5ndGgpKX12aXNpdExpdGVyYWxBcnJheSh0LGUpe2xldCBpPXQuZXhwcmVzc2lvbnMubWFwKHI9PnIudmlzaXQodGhpcyxlKSk7cmV0dXJuIG5ldyByaCh0LnNwYW4sdC5zb3VyY2VTcGFuLGksdGhpcy5fY29udmVydGVyRmFjdG9yeS5jcmVhdGVMaXRlcmFsQXJyYXlDb252ZXJ0ZXIodC5leHByZXNzaW9ucy5sZW5ndGgpKX12aXNpdExpdGVyYWxNYXAodCxlKXtsZXQgaT10LnZhbHVlcy5tYXAocj0+ci52aXNpdCh0aGlzLGUpKTtyZXR1cm4gbmV3IHJoKHQuc3Bhbix0LnNvdXJjZVNwYW4saSx0aGlzLl9jb252ZXJ0ZXJGYWN0b3J5LmNyZWF0ZUxpdGVyYWxNYXBDb252ZXJ0ZXIodC5rZXlzKSl9fSxHQz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyl7dGhpcy5fbG9jYWxSZXNvbHZlcj10LHRoaXMuX2ltcGxpY2l0UmVjZWl2ZXI9ZSx0aGlzLmJpbmRpbmdJZD1pLHRoaXMuc3VwcG9ydHNJbnRlcnBvbGF0aW9uPXIsdGhpcy5iYXNlU291cmNlU3Bhbj1vLHRoaXMuaW1wbGljaXRSZWNlaXZlckFjY2Vzc2VzPXMsdGhpcy5fbm9kZU1hcD1uZXcgTWFwLHRoaXMuX3Jlc3VsdE1hcD1uZXcgTWFwLHRoaXMuX2N1cnJlbnRUZW1wb3Jhcnk9MCx0aGlzLnRlbXBvcmFyeUNvdW50PTAsdGhpcy51c2VzSW1wbGljaXRSZWNlaXZlcj0hMX12aXNpdFVuYXJ5KHQsZSl7bGV0IGk7c3dpdGNoKHQub3BlcmF0b3Ipe2Nhc2UiKyI6aT1hbS5QbHVzO2JyZWFrO2Nhc2UiLSI6aT1hbS5NaW51czticmVhaztkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgVW5zdXBwb3J0ZWQgb3BlcmF0b3IgJHt0Lm9wZXJhdG9yfWApfXJldHVybiAkcyhlLG5ldyBDQyhpLHRoaXMuX3Zpc2l0KHQuZXhwcix6aS5FeHByZXNzaW9uKSx2b2lkIDAsdGhpcy5jb252ZXJ0U291cmNlU3Bhbih0LnNwYW4pKSl9dmlzaXRCaW5hcnkodCxlKXtsZXQgaTtzd2l0Y2godC5vcGVyYXRpb24pe2Nhc2UiKyI6aT1Dbi5QbHVzO2JyZWFrO2Nhc2UiLSI6aT1Dbi5NaW51czticmVhaztjYXNlIioiOmk9Q24uTXVsdGlwbHk7YnJlYWs7Y2FzZSIvIjppPUNuLkRpdmlkZTticmVhaztjYXNlIiUiOmk9Q24uTW9kdWxvO2JyZWFrO2Nhc2UiJiYiOmk9Q24uQW5kO2JyZWFrO2Nhc2UifHwiOmk9Q24uT3I7YnJlYWs7Y2FzZSI9PSI6aT1Dbi5FcXVhbHM7YnJlYWs7Y2FzZSIhPSI6aT1Dbi5Ob3RFcXVhbHM7YnJlYWs7Y2FzZSI9PT0iOmk9Q24uSWRlbnRpY2FsO2JyZWFrO2Nhc2UiIT09IjppPUNuLk5vdElkZW50aWNhbDticmVhaztjYXNlIjwiOmk9Q24uTG93ZXI7YnJlYWs7Y2FzZSI+IjppPUNuLkJpZ2dlcjticmVhaztjYXNlIjw9IjppPUNuLkxvd2VyRXF1YWxzO2JyZWFrO2Nhc2UiPj0iOmk9Q24uQmlnZ2VyRXF1YWxzO2JyZWFrO2Nhc2UiPz8iOnJldHVybiB0aGlzLmNvbnZlcnROdWxsaXNoQ29hbGVzY2UodCxlKTtkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgVW5zdXBwb3J0ZWQgb3BlcmF0aW9uICR7dC5vcGVyYXRpb259YCl9cmV0dXJuICRzKGUsbmV3IGdyKGksdGhpcy5fdmlzaXQodC5sZWZ0LHppLkV4cHJlc3Npb24pLHRoaXMuX3Zpc2l0KHQucmlnaHQsemkuRXhwcmVzc2lvbiksdm9pZCAwLHRoaXMuY29udmVydFNvdXJjZVNwYW4odC5zcGFuKSkpfXZpc2l0Q2hhaW4odCxlKXtyZXR1cm4gZnVuY3Rpb24obix0KXtpZihuIT09emkuU3RhdGVtZW50KXRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYSBzdGF0ZW1lbnQsIGJ1dCBzYXcgJHt0fWApfShlLHQpLHRoaXMudmlzaXRBbGwodC5leHByZXNzaW9ucyxlKX12aXNpdENvbmRpdGlvbmFsKHQsZSl7cmV0dXJuICRzKGUsdGhpcy5fdmlzaXQodC5jb25kaXRpb24semkuRXhwcmVzc2lvbikuY29uZGl0aW9uYWwodGhpcy5fdmlzaXQodC50cnVlRXhwLHppLkV4cHJlc3Npb24pLHRoaXMuX3Zpc2l0KHQuZmFsc2VFeHAsemkuRXhwcmVzc2lvbiksdGhpcy5jb252ZXJ0U291cmNlU3Bhbih0LnNwYW4pKSl9dmlzaXRQaXBlKHQsZSl7dGhyb3cgbmV3IEVycm9yKGBJbGxlZ2FsIHN0YXRlOiBQaXBlcyBzaG91bGQgaGF2ZSBiZWVuIGNvbnZlcnRlZCBpbnRvIGZ1bmN0aW9ucy4gUGlwZTogJHt0Lm5hbWV9YCl9dmlzaXRJbXBsaWNpdFJlY2VpdmVyKHQsZSl7cmV0dXJuIEdYKGUsdCksdGhpcy51c2VzSW1wbGljaXRSZWNlaXZlcj0hMCx0aGlzLl9pbXBsaWNpdFJlY2VpdmVyfXZpc2l0VGhpc1JlY2VpdmVyKHQsZSl7cmV0dXJuIHRoaXMudmlzaXRJbXBsaWNpdFJlY2VpdmVyKHQsZSl9dmlzaXRJbnRlcnBvbGF0aW9uKHQsZSl7aWYoIXRoaXMuc3VwcG9ydHNJbnRlcnBvbGF0aW9uKXRocm93IG5ldyBFcnJvcigiVW5leHBlY3RlZCBpbnRlcnBvbGF0aW9uIik7R1goZSx0KTtsZXQgaT1bXTtmb3IobGV0IG89MDtvPHQuc3RyaW5ncy5sZW5ndGgtMTtvKyspaS5wdXNoKGh0KHQuc3RyaW5nc1tvXSkpLGkucHVzaCh0aGlzLl92aXNpdCh0LmV4cHJlc3Npb25zW29dLHppLkV4cHJlc3Npb24pKTtpLnB1c2goaHQodC5zdHJpbmdzW3Quc3RyaW5ncy5sZW5ndGgtMV0pKTtsZXQgcj10LnN0cmluZ3M7cmV0dXJuIDI9PT1yLmxlbmd0aCYmIiI9PT1yWzBdJiYiIj09PXJbMV0/aT1baVsxXV06dC5leHByZXNzaW9ucy5sZW5ndGg+PTkmJihpPVtfcihpKV0pLG5ldyByVihpKX12aXNpdEtleWVkUmVhZCh0LGUpe2xldCBpPXRoaXMubGVmdE1vc3RTYWZlTm9kZSh0KTtyZXR1cm4gaT90aGlzLmNvbnZlcnRTYWZlQWNjZXNzKHQsaSxlKTokcyhlLHRoaXMuX3Zpc2l0KHQucmVjZWl2ZXIsemkuRXhwcmVzc2lvbikua2V5KHRoaXMuX3Zpc2l0KHQua2V5LHppLkV4cHJlc3Npb24pKSl9dmlzaXRLZXllZFdyaXRlKHQsZSl7bGV0IGk9dGhpcy5fdmlzaXQodC5yZWNlaXZlcix6aS5FeHByZXNzaW9uKSxyPXRoaXMuX3Zpc2l0KHQua2V5LHppLkV4cHJlc3Npb24pLG89dGhpcy5fdmlzaXQodC52YWx1ZSx6aS5FeHByZXNzaW9uKTtyZXR1cm4gaT09PXRoaXMuX2ltcGxpY2l0UmVjZWl2ZXImJnRoaXMuX2xvY2FsUmVzb2x2ZXIubWF5YmVSZXN0b3JlVmlldygpLCRzKGUsaS5rZXkocikuc2V0KG8pKX12aXNpdExpdGVyYWxBcnJheSh0LGUpe3Rocm93IG5ldyBFcnJvcigiSWxsZWdhbCBTdGF0ZTogbGl0ZXJhbCBhcnJheXMgc2hvdWxkIGhhdmUgYmVlbiBjb252ZXJ0ZWQgaW50byBmdW5jdGlvbnMiKX12aXNpdExpdGVyYWxNYXAodCxlKXt0aHJvdyBuZXcgRXJyb3IoIklsbGVnYWwgU3RhdGU6IGxpdGVyYWwgbWFwcyBzaG91bGQgaGF2ZSBiZWVuIGNvbnZlcnRlZCBpbnRvIGZ1bmN0aW9ucyIpfXZpc2l0TGl0ZXJhbFByaW1pdGl2ZSh0LGUpe3JldHVybiAkcyhlLGh0KHQudmFsdWUsbnVsbD09dC52YWx1ZXx8ITA9PT10LnZhbHVlfHwhMD09PXQudmFsdWU/UGE6dm9pZCAwLHRoaXMuY29udmVydFNvdXJjZVNwYW4odC5zcGFuKSkpfV9nZXRMb2NhbCh0LGUpe3JldHVybiB0aGlzLl9sb2NhbFJlc29sdmVyLmdsb2JhbHM/Lmhhcyh0KSYmZSBpbnN0YW5jZW9mIE9DP251bGw6dGhpcy5fbG9jYWxSZXNvbHZlci5nZXRMb2NhbCh0KX12aXNpdFByZWZpeE5vdCh0LGUpe3JldHVybiAkcyhlLGZ1bmN0aW9uKG4sdCl7cmV0dXJuIG5ldyB4QyhuLHZvaWQgMCl9KHRoaXMuX3Zpc2l0KHQuZXhwcmVzc2lvbix6aS5FeHByZXNzaW9uKSkpfXZpc2l0Tm9uTnVsbEFzc2VydCh0LGUpe3JldHVybiAkcyhlLHRoaXMuX3Zpc2l0KHQuZXhwcmVzc2lvbix6aS5FeHByZXNzaW9uKSl9dmlzaXRQcm9wZXJ0eVJlYWQodCxlKXtsZXQgaT10aGlzLmxlZnRNb3N0U2FmZU5vZGUodCk7aWYoaSlyZXR1cm4gdGhpcy5jb252ZXJ0U2FmZUFjY2Vzcyh0LGksZSk7e2xldCByPW51bGwsbz10aGlzLnVzZXNJbXBsaWNpdFJlY2VpdmVyLHM9dGhpcy5fdmlzaXQodC5yZWNlaXZlcix6aS5FeHByZXNzaW9uKTtyZXR1cm4gcz09PXRoaXMuX2ltcGxpY2l0UmVjZWl2ZXImJihyPXRoaXMuX2dldExvY2FsKHQubmFtZSx0LnJlY2VpdmVyKSxyJiYodGhpcy51c2VzSW1wbGljaXRSZWNlaXZlcj1vLHRoaXMuYWRkSW1wbGljaXRSZWNlaXZlckFjY2Vzcyh0Lm5hbWUpKSksbnVsbD09ciYmKHI9cy5wcm9wKHQubmFtZSx0aGlzLmNvbnZlcnRTb3VyY2VTcGFuKHQuc3BhbikpKSwkcyhlLHIpfX12aXNpdFByb3BlcnR5V3JpdGUodCxlKXtsZXQgaT10aGlzLl92aXNpdCh0LnJlY2VpdmVyLHppLkV4cHJlc3Npb24pLHI9dGhpcy51c2VzSW1wbGljaXRSZWNlaXZlcixvPW51bGw7aWYoaT09PXRoaXMuX2ltcGxpY2l0UmVjZWl2ZXIpe2xldCBzPXRoaXMuX2dldExvY2FsKHQubmFtZSx0LnJlY2VpdmVyKTtpZihzKXtpZighKHMgaW5zdGFuY2VvZiBiXykpdGhyb3cgbmV3IEVycm9yKGBDYW5ub3QgYXNzaWduIHZhbHVlICIke3QudmFsdWUgaW5zdGFuY2VvZiBMdT90LnZhbHVlLm5hbWU6dm9pZCAwfSIgdG8gdGVtcGxhdGUgdmFyaWFibGUgIiR7dC5uYW1lfSIuIFRlbXBsYXRlIHZhcmlhYmxlcyBhcmUgcmVhZC1vbmx5LmApO289cyx0aGlzLnVzZXNJbXBsaWNpdFJlY2VpdmVyPXIsdGhpcy5hZGRJbXBsaWNpdFJlY2VpdmVyQWNjZXNzKHQubmFtZSl9fXJldHVybiBudWxsPT09byYmKG89aS5wcm9wKHQubmFtZSx0aGlzLmNvbnZlcnRTb3VyY2VTcGFuKHQuc3BhbikpKSwkcyhlLG8uc2V0KHRoaXMuX3Zpc2l0KHQudmFsdWUsemkuRXhwcmVzc2lvbikpKX12aXNpdFNhZmVQcm9wZXJ0eVJlYWQodCxlKXtyZXR1cm4gdGhpcy5jb252ZXJ0U2FmZUFjY2Vzcyh0LHRoaXMubGVmdE1vc3RTYWZlTm9kZSh0KSxlKX12aXNpdFNhZmVLZXllZFJlYWQodCxlKXtyZXR1cm4gdGhpcy5jb252ZXJ0U2FmZUFjY2Vzcyh0LHRoaXMubGVmdE1vc3RTYWZlTm9kZSh0KSxlKX12aXNpdEFsbCh0LGUpe3JldHVybiB0Lm1hcChpPT50aGlzLl92aXNpdChpLGUpKX12aXNpdENhbGwodCxlKXtsZXQgaT10aGlzLmxlZnRNb3N0U2FmZU5vZGUodCk7aWYoaSlyZXR1cm4gdGhpcy5jb252ZXJ0U2FmZUFjY2Vzcyh0LGksZSk7bGV0IHI9dGhpcy52aXNpdEFsbCh0LmFyZ3MsemkuRXhwcmVzc2lvbik7aWYodCBpbnN0YW5jZW9mIHJoKXJldHVybiAkcyhlLHQuY29udmVydGVyKHIpKTtsZXQgbz10LnJlY2VpdmVyO2lmKG8gaW5zdGFuY2VvZiBMdSYmby5yZWNlaXZlciBpbnN0YW5jZW9mIHhtJiYhKG8ucmVjZWl2ZXIgaW5zdGFuY2VvZiBPQykmJiIkYW55Ij09PW8ubmFtZSl7aWYoMSE9PXIubGVuZ3RoKXRocm93IG5ldyBFcnJvcihgSW52YWxpZCBjYWxsIHRvICRhbnksIGV4cGVjdGVkIDEgYXJndW1lbnQgYnV0IHJlY2VpdmVkICR7ci5sZW5ndGh8fCJub25lIn1gKTtyZXR1cm4gJHMoZSxyWzBdKX1yZXR1cm4gJHMoZSx0aGlzLl92aXNpdChvLHppLkV4cHJlc3Npb24pLmNhbGxGbihyLHRoaXMuY29udmVydFNvdXJjZVNwYW4odC5zcGFuKSkpfXZpc2l0U2FmZUNhbGwodCxlKXtyZXR1cm4gdGhpcy5jb252ZXJ0U2FmZUFjY2Vzcyh0LHRoaXMubGVmdE1vc3RTYWZlTm9kZSh0KSxlKX1fdmlzaXQodCxlKXtyZXR1cm4gdGhpcy5fcmVzdWx0TWFwLmdldCh0KXx8KHRoaXMuX25vZGVNYXAuZ2V0KHQpfHx0KS52aXNpdCh0aGlzLGUpfWNvbnZlcnRTYWZlQWNjZXNzKHQsZSxpKXtsZXQgbyxyPXRoaXMuX3Zpc2l0KGUucmVjZWl2ZXIsemkuRXhwcmVzc2lvbik7dGhpcy5uZWVkc1RlbXBvcmFyeUluU2FmZUFjY2VzcyhlLnJlY2VpdmVyKSYmKG89dGhpcy5hbGxvY2F0ZVRlbXBvcmFyeSgpLHI9by5zZXQociksdGhpcy5fcmVzdWx0TWFwLnNldChlLnJlY2VpdmVyLG8pKTtsZXQgcz1yLmlzQmxhbmsoKTt0aGlzLl9ub2RlTWFwLnNldChlLGUgaW5zdGFuY2VvZiBrXz9uZXcgYWgoZS5zcGFuLGUuc291cmNlU3BhbixlLnJlY2VpdmVyLGUuYXJncyxlLmFyZ3VtZW50U3Bhbik6ZSBpbnN0YW5jZW9mIFBfP25ldyBJXyhlLnNwYW4sZS5zb3VyY2VTcGFuLGUucmVjZWl2ZXIsZS5rZXkpOm5ldyBMdShlLnNwYW4sZS5zb3VyY2VTcGFuLGUubmFtZVNwYW4sZS5yZWNlaXZlcixlLm5hbWUpKTtsZXQgYT10aGlzLl92aXNpdCh0LHppLkV4cHJlc3Npb24pO3JldHVybiB0aGlzLl9ub2RlTWFwLmRlbGV0ZShlKSxvJiZ0aGlzLnJlbGVhc2VUZW1wb3JhcnkobyksJHMoaSxzLmNvbmRpdGlvbmFsKFBCLGEpKX1jb252ZXJ0TnVsbGlzaENvYWxlc2NlKHQsZSl7bGV0IGk9dGhpcy5fdmlzaXQodC5sZWZ0LHppLkV4cHJlc3Npb24pLHI9dGhpcy5fdmlzaXQodC5yaWdodCx6aS5FeHByZXNzaW9uKSxvPXRoaXMuYWxsb2NhdGVUZW1wb3JhcnkoKTtyZXR1cm4gdGhpcy5yZWxlYXNlVGVtcG9yYXJ5KG8pLCRzKGUsby5zZXQoaSkubm90SWRlbnRpY2FsKFBCKS5hbmQoby5ub3RJZGVudGljYWwoaHQodm9pZCAwKSkpLmNvbmRpdGlvbmFsKG8scikpfWxlZnRNb3N0U2FmZU5vZGUodCl7bGV0IGU9KGkscik9Pih0aGlzLl9ub2RlTWFwLmdldChyKXx8cikudmlzaXQoaSk7cmV0dXJuIHQudmlzaXQoe3Zpc2l0VW5hcnk6aT0+bnVsbCx2aXNpdEJpbmFyeTppPT5udWxsLHZpc2l0Q2hhaW46aT0+bnVsbCx2aXNpdENvbmRpdGlvbmFsOmk9Pm51bGwsdmlzaXRDYWxsKGkpe3JldHVybiBlKHRoaXMsaS5yZWNlaXZlcil9LHZpc2l0U2FmZUNhbGwoaSl7cmV0dXJuIGUodGhpcyxpLnJlY2VpdmVyKXx8aX0sdmlzaXRJbXBsaWNpdFJlY2VpdmVyOmk9Pm51bGwsdmlzaXRUaGlzUmVjZWl2ZXI6aT0+bnVsbCx2aXNpdEludGVycG9sYXRpb246aT0+bnVsbCx2aXNpdEtleWVkUmVhZChpKXtyZXR1cm4gZSh0aGlzLGkucmVjZWl2ZXIpfSx2aXNpdEtleWVkV3JpdGU6aT0+bnVsbCx2aXNpdExpdGVyYWxBcnJheTppPT5udWxsLHZpc2l0TGl0ZXJhbE1hcDppPT5udWxsLHZpc2l0TGl0ZXJhbFByaW1pdGl2ZTppPT5udWxsLHZpc2l0UGlwZTppPT5udWxsLHZpc2l0UHJlZml4Tm90Omk9Pm51bGwsdmlzaXROb25OdWxsQXNzZXJ0Omk9Pm51bGwsdmlzaXRQcm9wZXJ0eVJlYWQoaSl7cmV0dXJuIGUodGhpcyxpLnJlY2VpdmVyKX0sdmlzaXRQcm9wZXJ0eVdyaXRlOmk9Pm51bGwsdmlzaXRTYWZlUHJvcGVydHlSZWFkKGkpe3JldHVybiBlKHRoaXMsaS5yZWNlaXZlcil8fGl9LHZpc2l0U2FmZUtleWVkUmVhZChpKXtyZXR1cm4gZSh0aGlzLGkucmVjZWl2ZXIpfHxpfX0pfW5lZWRzVGVtcG9yYXJ5SW5TYWZlQWNjZXNzKHQpe2xldCBlPShyLG8pPT5vJiYodGhpcy5fbm9kZU1hcC5nZXQobyl8fG8pLnZpc2l0KHIpO3JldHVybiB0LnZpc2l0KHt2aXNpdFVuYXJ5KHIpe3JldHVybiBlKHRoaXMsci5leHByKX0sdmlzaXRCaW5hcnkocil7cmV0dXJuIGUodGhpcyxyLmxlZnQpfHxlKHRoaXMsci5yaWdodCl9LHZpc2l0Q2hhaW46cj0+ITEsdmlzaXRDb25kaXRpb25hbChyKXtyZXR1cm4gZSh0aGlzLHIuY29uZGl0aW9uKXx8ZSh0aGlzLHIudHJ1ZUV4cCl8fGUodGhpcyxyLmZhbHNlRXhwKX0sdmlzaXRDYWxsOnI9PiEwLHZpc2l0U2FmZUNhbGw6cj0+ITAsdmlzaXRJbXBsaWNpdFJlY2VpdmVyOnI9PiExLHZpc2l0VGhpc1JlY2VpdmVyOnI9PiExLHZpc2l0SW50ZXJwb2xhdGlvbihyKXtyZXR1cm4oKHIsbyk9Pm8uc29tZShzPT5lKHIscykpKSh0aGlzLHIuZXhwcmVzc2lvbnMpfSx2aXNpdEtleWVkUmVhZDpyPT4hMSx2aXNpdEtleWVkV3JpdGU6cj0+ITEsdmlzaXRMaXRlcmFsQXJyYXk6cj0+ITAsdmlzaXRMaXRlcmFsTWFwOnI9PiEwLHZpc2l0TGl0ZXJhbFByaW1pdGl2ZTpyPT4hMSx2aXNpdFBpcGU6cj0+ITAsdmlzaXRQcmVmaXhOb3Qocil7cmV0dXJuIGUodGhpcyxyLmV4cHJlc3Npb24pfSx2aXNpdE5vbk51bGxBc3NlcnQocil7cmV0dXJuIGUodGhpcyxyLmV4cHJlc3Npb24pfSx2aXNpdFByb3BlcnR5UmVhZDpyPT4hMSx2aXNpdFByb3BlcnR5V3JpdGU6cj0+ITEsdmlzaXRTYWZlUHJvcGVydHlSZWFkOnI9PiExLHZpc2l0U2FmZUtleWVkUmVhZDpyPT4hMX0pfWFsbG9jYXRlVGVtcG9yYXJ5KCl7bGV0IHQ9dGhpcy5fY3VycmVudFRlbXBvcmFyeSsrO3JldHVybiB0aGlzLnRlbXBvcmFyeUNvdW50PU1hdGgubWF4KHRoaXMuX2N1cnJlbnRUZW1wb3JhcnksdGhpcy50ZW1wb3JhcnlDb3VudCksbmV3IHVtKG5WKHRoaXMuYmluZGluZ0lkLHQpKX1yZWxlYXNlVGVtcG9yYXJ5KHQpe2lmKHRoaXMuX2N1cnJlbnRUZW1wb3JhcnktLSx0Lm5hbWUhPW5WKHRoaXMuYmluZGluZ0lkLHRoaXMuX2N1cnJlbnRUZW1wb3JhcnkpKXRocm93IG5ldyBFcnJvcihgVGVtcG9yYXJ5ICR7dC5uYW1lfSByZWxlYXNlZCBvdXQgb2Ygb3JkZXJgKX1jb252ZXJ0U291cmNlU3Bhbih0KXtpZih0aGlzLmJhc2VTb3VyY2VTcGFuKXtsZXQgZT10aGlzLmJhc2VTb3VyY2VTcGFuLnN0YXJ0Lm1vdmVCeSh0LnN0YXJ0KSxpPXRoaXMuYmFzZVNvdXJjZVNwYW4uc3RhcnQubW92ZUJ5KHQuZW5kKSxyPXRoaXMuYmFzZVNvdXJjZVNwYW4uZnVsbFN0YXJ0Lm1vdmVCeSh0LnN0YXJ0KTtyZXR1cm4gbmV3IEdvKGUsaSxyKX1yZXR1cm4gbnVsbH1hZGRJbXBsaWNpdFJlY2VpdmVyQWNjZXNzKHQpe3RoaXMuaW1wbGljaXRSZWNlaXZlckFjY2Vzc2VzJiZ0aGlzLmltcGxpY2l0UmVjZWl2ZXJBY2Nlc3Nlcy5hZGQodCl9fTtmdW5jdGlvbiBoSyhuLHQpe0FycmF5LmlzQXJyYXkobik/bi5mb3JFYWNoKGU9PmhLKGUsdCkpOnQucHVzaChuKX1mdW5jdGlvbiBsQigpe3Rocm93IG5ldyBFcnJvcigiVW5zdXBwb3J0ZWQgb3BlcmF0aW9uIil9dmFyIHJWPWNsYXNzIGV4dGVuZHMgT3J7Y29uc3RydWN0b3IodCl7c3VwZXIobnVsbCxudWxsKSx0aGlzLmFyZ3M9dCx0aGlzLmlzQ29uc3RhbnQ9bEIsdGhpcy5pc0VxdWl2YWxlbnQ9bEIsdGhpcy52aXNpdEV4cHJlc3Npb249bEJ9fSxtRD1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLmdsb2JhbHM9dH1ub3RpZnlJbXBsaWNpdFJlY2VpdmVyVXNlKCl7fW1heWJlUmVzdG9yZVZpZXcoKXt9Z2V0TG9jYWwodCl7cmV0dXJuIHQ9PT1qQy5ldmVudC5uYW1lP2pDLmV2ZW50Om51bGx9fSxyaD1jbGFzcyBleHRlbmRzIGFoe2NvbnN0cnVjdG9yKHQsZSxpLHIpe3N1cGVyKHQsZSxuZXcgSWEodCxlKSxpLG51bGwpLHRoaXMuY29udmVydGVyPXJ9fSxtU2U9L3BvbHlmaWxsLW5leHQtc2VsZWN0b3JbXn1dKmNvbnRlbnQ6W1xzXSo/KFsnIl0pKC4qPylcMVs7XHNdKn0oW157XSo/KXsvZ2ltLGdTZT0vKHBvbHlmaWxsLXJ1bGUpW159XSooY29udGVudDpbXHNdKihbJyJdKSguKj8pXDMpWztcc10qW159XSp9L2dpbSxXWD0vKHBvbHlmaWxsLXVuc2NvcGVkLXJ1bGUpW159XSooY29udGVudDpbXHNdKihbJyJdKSguKj8pXDMpWztcc10qW159XSp9L2dpbSxnRD0iLXNoYWRvd2Nzc2hvc3QiLFpWPSItc2hhZG93Y3NzY29udGV4dCIsSlY9Iig/OlxcKCgoPzpcXChbXikoXSpcXCl8W14pKF0qKSs/KVxcKSk/KFteLHtdKikiLF9TZT1uZXcgUmVnRXhwKGdEK0pWLCJnaW0iKSx2U2U9bmV3IFJlZ0V4cChaVitKViwiZ2ltIikseVNlPW5ldyBSZWdFeHAoWlYrSlYsImltIiksdG09Z0QrIi1uby1jb21iaW5hdG9yIixxWD0vLXNoYWRvd2Nzc2hvc3Qtbm8tY29tYmluYXRvcihbXlxzXSopLyxiU2U9Wy86OnNoYWRvdy9nLC86OmNvbnRlbnQvZywvXC9zaGFkb3ctZGVlcFwvL2csL1wvc2hhZG93XC8vZ10sWVg9Lyg/Oj4+Pil8KD86XC9kZWVwXC8pfCg/Ojo6bmctZGVlcCkvZyxkXz0vLXNoYWRvd2Nzc2hvc3QvZ2ltLENTZT0vOmhvc3QvZ2ltLE1TZT0vOmhvc3QtY29udGV4dC9naW0sd1NlPS9cL1wqW1xzXFNdKj9cKlwvL2csRVNlPS9cL1wqXHMqI1xzKnNvdXJjZShNYXBwaW5nKT9VUkw9W1xzXFNdKz9cKlwvL2csY0I9IiVCTE9DSyUiLEFTZT0vKFxzKikoW147XHtcfV0rPykoXHMqKSgoPzp7JUJMT0NLJX0/XHMqOz8pfCg/OlxzKjspKS9nLElTZT0vJVFVT1RFRCUvZyxQU2U9bmV3IE1hcChbWyJ7IiwifSJdXSksUlNlPW5ldyBNYXAoW1snIicsJyInXSxbIiciLCInIl1dKSxXQz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuc2VsZWN0b3I9dCx0aGlzLmNvbnRlbnQ9ZX19O2Z1bmN0aW9uIFhYKG4sdCl7bGV0IGU9UVgobixSU2UsIiVRVU9URUQlIiksaT1RWChlLmVzY2FwZWRTdHJpbmcsUFNlLGNCKSxyPTAsbz0wO3JldHVybiBpLmVzY2FwZWRTdHJpbmcucmVwbGFjZShBU2UsKC4uLnMpPT57bGV0IGE9c1syXSxsPSIiLGM9c1s0XSx1PSIiO2MmJmMuc3RhcnRzV2l0aCgieyIrY0IpJiYobD1pLmJsb2Nrc1tyKytdLGM9Yy5zdWJzdHJpbmcoY0IubGVuZ3RoKzEpLHU9InsiKTtsZXQgZD10KG5ldyBXQyhhLGwpKTtyZXR1cm5gJHtzWzFdfSR7ZC5zZWxlY3Rvcn0ke3NbM119JHt1fSR7ZC5jb250ZW50fSR7Y31gfSkucmVwbGFjZShJU2UsKCk9PmUuYmxvY2tzW28rK10pfWZ1bmN0aW9uIFFYKG4sdCxlKXtsZXQgbCxjLGk9W10scj1bXSxvPTAscz0wLGE9LTE7Zm9yKGxldCB1PTA7dTxuLmxlbmd0aDt1Kyspe2xldCBkPW5bdV07IlxcIj09PWQ/dSsrOmQ9PT1jPyhvLS0sMD09PW8mJihyLnB1c2gobi5zdWJzdHJpbmcoYSx1KSksaS5wdXNoKGUpLHM9dSxhPS0xLGw9Yz12b2lkIDApKTpkPT09bD9vKys6MD09PW8mJnQuaGFzKGQpJiYobD1kLGM9dC5nZXQoZCksbz0xLGE9dSsxLGkucHVzaChuLnN1YnN0cmluZyhzLGEpKSl9cmV0dXJuLTEhPT1hPyhyLnB1c2gobi5zdWJzdHJpbmcoYSkpLGkucHVzaChlKSk6aS5wdXNoKG4uc3Vic3RyaW5nKHMpKSxuZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLmVzY2FwZWRTdHJpbmc9dCx0aGlzLmJsb2Nrcz1lfX0oaS5qb2luKCIiKSxyKX1mdW5jdGlvbiBrU2Uobix0KXtsZXQgZT1uLmxlbmd0aDtmb3IobGV0IGk9MTtpPHQ7aSsrKWZvcihsZXQgcj0wO3I8ZTtyKyspbltyK2kqZV09bltyXS5zbGljZSgwKX1mdW5jdGlvbiBLWChuKXtsZXQgdD1uLmNoYXJDb2RlQXQoMCk7aWYodD09bi5jaGFyQ29kZUF0KG4ubGVuZ3RoLTEpJiYoMzk9PXR8fDM0PT10KSl7bGV0IGk9bi5zdWJzdHJpbmcoMSxuLmxlbmd0aC0xKTstMT09aS5pbmRleE9mKCInIikmJi0xPT1pLmluZGV4T2YoJyInKSYmKG49aSl9cmV0dXJuIG59ZnVuY3Rpb24gZksobil7cmV0dXJuIG4ucmVwbGFjZSgvW2Etel1bQS1aXS9nLHQ9PnQuY2hhckF0KDApKyItIit0LmNoYXJBdCgxKSkudG9Mb3dlckNhc2UoKX12YXIgdkQ9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5fZGlyZWN0aXZlRXhwcj10LHRoaXMuX2hhc0luaXRpYWxWYWx1ZXM9ITEsdGhpcy5oYXNCaW5kaW5ncz0hMSx0aGlzLmhhc0JpbmRpbmdzV2l0aFBpcGVzPSExLHRoaXMuX2NsYXNzTWFwSW5wdXQ9bnVsbCx0aGlzLl9zdHlsZU1hcElucHV0PW51bGwsdGhpcy5fc2luZ2xlU3R5bGVJbnB1dHM9bnVsbCx0aGlzLl9zaW5nbGVDbGFzc0lucHV0cz1udWxsLHRoaXMuX2xhc3RTdHlsaW5nSW5wdXQ9bnVsbCx0aGlzLl9maXJzdFN0eWxpbmdJbnB1dD1udWxsLHRoaXMuX3N0eWxlc0luZGV4PW5ldyBNYXAsdGhpcy5fY2xhc3Nlc0luZGV4PW5ldyBNYXAsdGhpcy5faW5pdGlhbFN0eWxlVmFsdWVzPVtdLHRoaXMuX2luaXRpYWxDbGFzc1ZhbHVlcz1bXX1yZWdpc3RlckJvdW5kSW5wdXQodCl7bGV0IGU9bnVsbCxpPXQubmFtZTtzd2l0Y2godC50eXBlKXtjYXNlIDA6ZT10aGlzLnJlZ2lzdGVySW5wdXRCYXNlZE9uTmFtZShpLHQudmFsdWUsdC5zb3VyY2VTcGFuKTticmVhaztjYXNlIDM6ZT10aGlzLnJlZ2lzdGVyU3R5bGVJbnB1dChpLCExLHQudmFsdWUsdC5zb3VyY2VTcGFuLHQudW5pdCk7YnJlYWs7Y2FzZSAyOmU9dGhpcy5yZWdpc3RlckNsYXNzSW5wdXQoaSwhMSx0LnZhbHVlLHQuc291cmNlU3Bhbil9cmV0dXJuISFlfXJlZ2lzdGVySW5wdXRCYXNlZE9uTmFtZSh0LGUsaSl7bGV0IHI9bnVsbCxvPXQuc3Vic3RyaW5nKDAsNikscz0ic3R5bGUiPT09dHx8InN0eWxlLiI9PT1vfHwic3R5bGUhIj09PW87aWYoc3x8IXMmJigiY2xhc3MiPT09dHx8ImNsYXNzLiI9PT1vfHwiY2xhc3MhIj09PW8pKXtsZXQgbD0iLiIhPT10LmNoYXJBdCg1KSxjPXQuc2xpY2UobD81OjYpO3I9cz90aGlzLnJlZ2lzdGVyU3R5bGVJbnB1dChjLGwsZSxpKTp0aGlzLnJlZ2lzdGVyQ2xhc3NJbnB1dChjLGwsZSxpKX1yZXR1cm4gcn1yZWdpc3RlclN0eWxlSW5wdXQodCxlLGkscixvKXtpZigkWChpKSlyZXR1cm4gbnVsbDt0LnN0YXJ0c1dpdGgoIi0tIil8fCh0PWZLKHQpKTtsZXR7cHJvcGVydHk6cyxoYXNPdmVycmlkZUZsYWc6YSxzdWZmaXg6bH09SlgodCksYz17bmFtZTpzLHN1ZmZpeDpvPSJzdHJpbmciPT10eXBlb2YgbyYmMCE9PW8ubGVuZ3RoP286bCx2YWx1ZTppLHNvdXJjZVNwYW46cixoYXNPdmVycmlkZUZsYWc6YX07cmV0dXJuIGU/dGhpcy5fc3R5bGVNYXBJbnB1dD1jOigodGhpcy5fc2luZ2xlU3R5bGVJbnB1dHM9dGhpcy5fc2luZ2xlU3R5bGVJbnB1dHN8fFtdKS5wdXNoKGMpLFpYKHRoaXMuX3N0eWxlc0luZGV4LHMpKSx0aGlzLl9sYXN0U3R5bGluZ0lucHV0PWMsdGhpcy5fZmlyc3RTdHlsaW5nSW5wdXQ9dGhpcy5fZmlyc3RTdHlsaW5nSW5wdXR8fGMsdGhpcy5fY2hlY2tGb3JQaXBlcyhpKSx0aGlzLmhhc0JpbmRpbmdzPSEwLGN9cmVnaXN0ZXJDbGFzc0lucHV0KHQsZSxpLHIpe2lmKCRYKGkpKXJldHVybiBudWxsO2xldHtwcm9wZXJ0eTpvLGhhc092ZXJyaWRlRmxhZzpzfT1KWCh0KSxhPXtuYW1lOm8sdmFsdWU6aSxzb3VyY2VTcGFuOnIsaGFzT3ZlcnJpZGVGbGFnOnMsc3VmZml4Om51bGx9O3JldHVybiBlP3RoaXMuX2NsYXNzTWFwSW5wdXQ9YTooKHRoaXMuX3NpbmdsZUNsYXNzSW5wdXRzPXRoaXMuX3NpbmdsZUNsYXNzSW5wdXRzfHxbXSkucHVzaChhKSxaWCh0aGlzLl9jbGFzc2VzSW5kZXgsbykpLHRoaXMuX2xhc3RTdHlsaW5nSW5wdXQ9YSx0aGlzLl9maXJzdFN0eWxpbmdJbnB1dD10aGlzLl9maXJzdFN0eWxpbmdJbnB1dHx8YSx0aGlzLl9jaGVja0ZvclBpcGVzKGkpLHRoaXMuaGFzQmluZGluZ3M9ITAsYX1fY2hlY2tGb3JQaXBlcyh0KXt0IGluc3RhbmNlb2YgUnUmJnQuYXN0IGluc3RhbmNlb2YgUl8mJih0aGlzLmhhc0JpbmRpbmdzV2l0aFBpcGVzPSEwKX1yZWdpc3RlclN0eWxlQXR0cih0KXt0aGlzLl9pbml0aWFsU3R5bGVWYWx1ZXM9ZnVuY3Rpb24obil7bGV0IHQ9W10sZT0wLGk9MCxyPTAsbz0wLHM9MCxhPW51bGwsbD0hMTtmb3IoO2U8bi5sZW5ndGg7KXN3aXRjaChuLmNoYXJDb2RlQXQoZSsrKSl7Y2FzZSA0MDppKys7YnJlYWs7Y2FzZSA0MTppLS07YnJlYWs7Y2FzZSAzOTpsPWx8fG8+MCwwPT09cj9yPTM5OjM5PT09ciYmOTIhPT1uLmNoYXJDb2RlQXQoZS0xKSYmKHI9MCk7YnJlYWs7Y2FzZSAzNDpsPWx8fG8+MCwwPT09cj9yPTM0OjM0PT09ciYmOTIhPT1uLmNoYXJDb2RlQXQoZS0xKSYmKHI9MCk7YnJlYWs7Y2FzZSA1ODohYSYmMD09PWkmJjA9PT1yJiYoYT1mSyhuLnN1YnN0cmluZyhzLGUtMSkudHJpbSgpKSxvPWUpO2JyZWFrO2Nhc2UgNTk6aWYoYSYmbz4wJiYwPT09aSYmMD09PXIpe2xldCB1PW4uc3Vic3RyaW5nKG8sZS0xKS50cmltKCk7dC5wdXNoKGEsbD9LWCh1KTp1KSxzPWUsbz0wLGE9bnVsbCxsPSExfX1pZihhJiZvKXtsZXQgYz1uLnNsaWNlKG8pLnRyaW0oKTt0LnB1c2goYSxsP0tYKGMpOmMpfXJldHVybiB0fSh0KSx0aGlzLl9oYXNJbml0aWFsVmFsdWVzPSEwfXJlZ2lzdGVyQ2xhc3NBdHRyKHQpe3RoaXMuX2luaXRpYWxDbGFzc1ZhbHVlcz10LnRyaW0oKS5zcGxpdCgvXHMrL2cpLHRoaXMuX2hhc0luaXRpYWxWYWx1ZXM9ITB9cG9wdWxhdGVJbml0aWFsU3R5bGluZ0F0dHJzKHQpe2lmKHRoaXMuX2luaXRpYWxDbGFzc1ZhbHVlcy5sZW5ndGgpe3QucHVzaChodCgxKSk7Zm9yKGxldCBlPTA7ZTx0aGlzLl9pbml0aWFsQ2xhc3NWYWx1ZXMubGVuZ3RoO2UrKyl0LnB1c2goaHQodGhpcy5faW5pdGlhbENsYXNzVmFsdWVzW2VdKSl9aWYodGhpcy5faW5pdGlhbFN0eWxlVmFsdWVzLmxlbmd0aCl7dC5wdXNoKGh0KDIpKTtmb3IobGV0IGU9MDtlPHRoaXMuX2luaXRpYWxTdHlsZVZhbHVlcy5sZW5ndGg7ZSs9Mil0LnB1c2goaHQodGhpcy5faW5pdGlhbFN0eWxlVmFsdWVzW2VdKSxodCh0aGlzLl9pbml0aWFsU3R5bGVWYWx1ZXNbZSsxXSkpfX1hc3NpZ25Ib3N0QXR0cnModCxlKXt0aGlzLl9kaXJlY3RpdmVFeHByJiYodC5sZW5ndGh8fHRoaXMuX2hhc0luaXRpYWxWYWx1ZXMpJiYodGhpcy5wb3B1bGF0ZUluaXRpYWxTdHlsaW5nQXR0cnModCksZS5zZXQoImhvc3RBdHRycyIsX3IodCkpKX1idWlsZENsYXNzTWFwSW5zdHJ1Y3Rpb24odCl7cmV0dXJuIHRoaXMuX2NsYXNzTWFwSW5wdXQ/dGhpcy5fYnVpbGRNYXBCYXNlZEluc3RydWN0aW9uKHQsITAsdGhpcy5fY2xhc3NNYXBJbnB1dCk6bnVsbH1idWlsZFN0eWxlTWFwSW5zdHJ1Y3Rpb24odCl7cmV0dXJuIHRoaXMuX3N0eWxlTWFwSW5wdXQ/dGhpcy5fYnVpbGRNYXBCYXNlZEluc3RydWN0aW9uKHQsITEsdGhpcy5fc3R5bGVNYXBJbnB1dCk6bnVsbH1fYnVpbGRNYXBCYXNlZEluc3RydWN0aW9uKHQsZSxpKXtsZXQgcyxyPTIsbz1pLnZhbHVlLnZpc2l0KHQpO3JldHVybiBvIGluc3RhbmNlb2YgdnM/KHIrPW8uZXhwcmVzc2lvbnMubGVuZ3RoLHM9ZT9mdW5jdGlvbihuKXtzd2l0Y2god20obikpe2Nhc2UgMTpyZXR1cm4gdGUuY2xhc3NNYXA7Y2FzZSAzOnJldHVybiB0ZS5jbGFzc01hcEludGVycG9sYXRlMTtjYXNlIDU6cmV0dXJuIHRlLmNsYXNzTWFwSW50ZXJwb2xhdGUyO2Nhc2UgNzpyZXR1cm4gdGUuY2xhc3NNYXBJbnRlcnBvbGF0ZTM7Y2FzZSA5OnJldHVybiB0ZS5jbGFzc01hcEludGVycG9sYXRlNDtjYXNlIDExOnJldHVybiB0ZS5jbGFzc01hcEludGVycG9sYXRlNTtjYXNlIDEzOnJldHVybiB0ZS5jbGFzc01hcEludGVycG9sYXRlNjtjYXNlIDE1OnJldHVybiB0ZS5jbGFzc01hcEludGVycG9sYXRlNztjYXNlIDE3OnJldHVybiB0ZS5jbGFzc01hcEludGVycG9sYXRlODtkZWZhdWx0OnJldHVybiB0ZS5jbGFzc01hcEludGVycG9sYXRlVn19KG8pOmZ1bmN0aW9uKG4pe3N3aXRjaCh3bShuKSl7Y2FzZSAxOnJldHVybiB0ZS5zdHlsZU1hcDtjYXNlIDM6cmV0dXJuIHRlLnN0eWxlTWFwSW50ZXJwb2xhdGUxO2Nhc2UgNTpyZXR1cm4gdGUuc3R5bGVNYXBJbnRlcnBvbGF0ZTI7Y2FzZSA3OnJldHVybiB0ZS5zdHlsZU1hcEludGVycG9sYXRlMztjYXNlIDk6cmV0dXJuIHRlLnN0eWxlTWFwSW50ZXJwb2xhdGU0O2Nhc2UgMTE6cmV0dXJuIHRlLnN0eWxlTWFwSW50ZXJwb2xhdGU1O2Nhc2UgMTM6cmV0dXJuIHRlLnN0eWxlTWFwSW50ZXJwb2xhdGU2O2Nhc2UgMTU6cmV0dXJuIHRlLnN0eWxlTWFwSW50ZXJwb2xhdGU3O2Nhc2UgMTc6cmV0dXJuIHRlLnN0eWxlTWFwSW50ZXJwb2xhdGU4O2RlZmF1bHQ6cmV0dXJuIHRlLnN0eWxlTWFwSW50ZXJwb2xhdGVWfX0obykpOnM9ZT90ZS5jbGFzc01hcDp0ZS5zdHlsZU1hcCx7cmVmZXJlbmNlOnMsY2FsbHM6W3tzdXBwb3J0c0ludGVycG9sYXRpb246ITAsc291cmNlU3BhbjppLnNvdXJjZVNwYW4sYWxsb2NhdGVCaW5kaW5nU2xvdHM6cixwYXJhbXM6YT0+e2xldCBsPWEobyk7cmV0dXJuIEFycmF5LmlzQXJyYXkobCk/bDpbbF19fV19fV9idWlsZFNpbmdsZUlucHV0cyh0LGUsaSxyLG8pe2xldCBzPVtdO3JldHVybiBlLmZvckVhY2goYT0+e2xldCBsPXNbcy5sZW5ndGgtMV0sYz1hLnZhbHVlLnZpc2l0KGkpLHU9dCxkPTI7YyBpbnN0YW5jZW9mIHZzJiYoZCs9Yy5leHByZXNzaW9ucy5sZW5ndGgsciYmKHU9cihjKSkpO2xldCBwPXtzb3VyY2VTcGFuOmEuc291cmNlU3BhbixhbGxvY2F0ZUJpbmRpbmdTbG90czpkLHN1cHBvcnRzSW50ZXJwb2xhdGlvbjohIXIscGFyYW1zOmg9PntsZXQgZj1bXTtmLnB1c2goaHQoYS5uYW1lKSk7bGV0IG09aChjKTtyZXR1cm4gQXJyYXkuaXNBcnJheShtKT9mLnB1c2goLi4ubSk6Zi5wdXNoKG0pLCFvJiZudWxsIT09YS5zdWZmaXgmJmYucHVzaChodChhLnN1ZmZpeCkpLGZ9fTtsJiZsLnJlZmVyZW5jZT09PXU/bC5jYWxscy5wdXNoKHApOnMucHVzaCh7cmVmZXJlbmNlOnUsY2FsbHM6W3BdfSl9KSxzfV9idWlsZENsYXNzSW5wdXRzKHQpe3JldHVybiB0aGlzLl9zaW5nbGVDbGFzc0lucHV0cz90aGlzLl9idWlsZFNpbmdsZUlucHV0cyh0ZS5jbGFzc1Byb3AsdGhpcy5fc2luZ2xlQ2xhc3NJbnB1dHMsdCxudWxsLCEwKTpbXX1fYnVpbGRTdHlsZUlucHV0cyh0KXtyZXR1cm4gdGhpcy5fc2luZ2xlU3R5bGVJbnB1dHM/dGhpcy5fYnVpbGRTaW5nbGVJbnB1dHModGUuc3R5bGVQcm9wLHRoaXMuX3NpbmdsZVN0eWxlSW5wdXRzLHQsVlNlLCExKTpbXX1idWlsZFVwZGF0ZUxldmVsSW5zdHJ1Y3Rpb25zKHQpe2xldCBlPVtdO2lmKHRoaXMuaGFzQmluZGluZ3Mpe2xldCBpPXRoaXMuYnVpbGRTdHlsZU1hcEluc3RydWN0aW9uKHQpO2kmJmUucHVzaChpKTtsZXQgcj10aGlzLmJ1aWxkQ2xhc3NNYXBJbnN0cnVjdGlvbih0KTtyJiZlLnB1c2gociksZS5wdXNoKC4uLnRoaXMuX2J1aWxkU3R5bGVJbnB1dHModCkpLGUucHVzaCguLi50aGlzLl9idWlsZENsYXNzSW5wdXRzKHQpKX1yZXR1cm4gZX19O2Z1bmN0aW9uIFpYKG4sdCl7bi5oYXModCl8fG4uc2V0KHQsbi5zaXplKX1mdW5jdGlvbiBKWChuKXtsZXQgdD0hMSxlPW4uaW5kZXhPZigiIWltcG9ydGFudCIpOy0xIT09ZSYmKG49ZT4wP24uc3Vic3RyaW5nKDAsZSk6IiIsdD0hMCk7bGV0IGk9bnVsbCxyPW4sbz1uLmxhc3RJbmRleE9mKCIuIik7cmV0dXJuIG8+MCYmKGk9bi5zbGljZShvKzEpLHI9bi5zdWJzdHJpbmcoMCxvKSkse3Byb3BlcnR5OnIsc3VmZml4OmksaGFzT3ZlcnJpZGVGbGFnOnR9fWZ1bmN0aW9uIFZTZShuKXtzd2l0Y2god20obikpe2Nhc2UgMTpyZXR1cm4gdGUuc3R5bGVQcm9wO2Nhc2UgMzpyZXR1cm4gdGUuc3R5bGVQcm9wSW50ZXJwb2xhdGUxO2Nhc2UgNTpyZXR1cm4gdGUuc3R5bGVQcm9wSW50ZXJwb2xhdGUyO2Nhc2UgNzpyZXR1cm4gdGUuc3R5bGVQcm9wSW50ZXJwb2xhdGUzO2Nhc2UgOTpyZXR1cm4gdGUuc3R5bGVQcm9wSW50ZXJwb2xhdGU0O2Nhc2UgMTE6cmV0dXJuIHRlLnN0eWxlUHJvcEludGVycG9sYXRlNTtjYXNlIDEzOnJldHVybiB0ZS5zdHlsZVByb3BJbnRlcnBvbGF0ZTY7Y2FzZSAxNTpyZXR1cm4gdGUuc3R5bGVQcm9wSW50ZXJwb2xhdGU3O2Nhc2UgMTc6cmV0dXJuIHRlLnN0eWxlUHJvcEludGVycG9sYXRlODtkZWZhdWx0OnJldHVybiB0ZS5zdHlsZVByb3BJbnRlcnBvbGF0ZVZ9fWZ1bmN0aW9uICRYKG4pe3JldHVybiBuIGluc3RhbmNlb2YgUnUmJihuPW4uYXN0KSxuIGluc3RhbmNlb2YgSWF9dmFyIG5pPSgoKT0+e3JldHVybihuPW5pfHwobmk9e30pKVtuLkNoYXJhY3Rlcj0wXT0iQ2hhcmFjdGVyIixuW24uSWRlbnRpZmllcj0xXT0iSWRlbnRpZmllciIsbltuLlByaXZhdGVJZGVudGlmaWVyPTJdPSJQcml2YXRlSWRlbnRpZmllciIsbltuLktleXdvcmQ9M109IktleXdvcmQiLG5bbi5TdHJpbmc9NF09IlN0cmluZyIsbltuLk9wZXJhdG9yPTVdPSJPcGVyYXRvciIsbltuLk51bWJlcj02XT0iTnVtYmVyIixuW24uRXJyb3I9N109IkVycm9yIixuaTt2YXIgbn0pKCksVVNlPVsidmFyIiwibGV0IiwiYXMiLCJudWxsIiwidW5kZWZpbmVkIiwidHJ1ZSIsImZhbHNlIiwiaWYiLCJlbHNlIiwidGhpcyJdLHlEPWNsYXNze3Rva2VuaXplKHQpe2xldCBlPW5ldyBsVih0KSxpPVtdLHI9ZS5zY2FuVG9rZW4oKTtmb3IoO251bGwhPXI7KWkucHVzaChyKSxyPWUuc2NhblRva2VuKCk7cmV0dXJuIGl9fSxqYz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8pe3RoaXMuaW5kZXg9dCx0aGlzLmVuZD1lLHRoaXMudHlwZT1pLHRoaXMubnVtVmFsdWU9cix0aGlzLnN0clZhbHVlPW99aXNDaGFyYWN0ZXIodCl7cmV0dXJuIHRoaXMudHlwZT09bmkuQ2hhcmFjdGVyJiZ0aGlzLm51bVZhbHVlPT10fWlzTnVtYmVyKCl7cmV0dXJuIHRoaXMudHlwZT09bmkuTnVtYmVyfWlzU3RyaW5nKCl7cmV0dXJuIHRoaXMudHlwZT09bmkuU3RyaW5nfWlzT3BlcmF0b3IodCl7cmV0dXJuIHRoaXMudHlwZT09bmkuT3BlcmF0b3ImJnRoaXMuc3RyVmFsdWU9PXR9aXNJZGVudGlmaWVyKCl7cmV0dXJuIHRoaXMudHlwZT09bmkuSWRlbnRpZmllcn1pc1ByaXZhdGVJZGVudGlmaWVyKCl7cmV0dXJuIHRoaXMudHlwZT09bmkuUHJpdmF0ZUlkZW50aWZpZXJ9aXNLZXl3b3JkKCl7cmV0dXJuIHRoaXMudHlwZT09bmkuS2V5d29yZH1pc0tleXdvcmRMZXQoKXtyZXR1cm4gdGhpcy50eXBlPT1uaS5LZXl3b3JkJiYibGV0Ij09dGhpcy5zdHJWYWx1ZX1pc0tleXdvcmRBcygpe3JldHVybiB0aGlzLnR5cGU9PW5pLktleXdvcmQmJiJhcyI9PXRoaXMuc3RyVmFsdWV9aXNLZXl3b3JkTnVsbCgpe3JldHVybiB0aGlzLnR5cGU9PW5pLktleXdvcmQmJiJudWxsIj09dGhpcy5zdHJWYWx1ZX1pc0tleXdvcmRVbmRlZmluZWQoKXtyZXR1cm4gdGhpcy50eXBlPT1uaS5LZXl3b3JkJiYidW5kZWZpbmVkIj09dGhpcy5zdHJWYWx1ZX1pc0tleXdvcmRUcnVlKCl7cmV0dXJuIHRoaXMudHlwZT09bmkuS2V5d29yZCYmInRydWUiPT10aGlzLnN0clZhbHVlfWlzS2V5d29yZEZhbHNlKCl7cmV0dXJuIHRoaXMudHlwZT09bmkuS2V5d29yZCYmImZhbHNlIj09dGhpcy5zdHJWYWx1ZX1pc0tleXdvcmRUaGlzKCl7cmV0dXJuIHRoaXMudHlwZT09bmkuS2V5d29yZCYmInRoaXMiPT10aGlzLnN0clZhbHVlfWlzRXJyb3IoKXtyZXR1cm4gdGhpcy50eXBlPT1uaS5FcnJvcn10b051bWJlcigpe3JldHVybiB0aGlzLnR5cGU9PW5pLk51bWJlcj90aGlzLm51bVZhbHVlOi0xfXRvU3RyaW5nKCl7c3dpdGNoKHRoaXMudHlwZSl7Y2FzZSBuaS5DaGFyYWN0ZXI6Y2FzZSBuaS5JZGVudGlmaWVyOmNhc2UgbmkuS2V5d29yZDpjYXNlIG5pLk9wZXJhdG9yOmNhc2UgbmkuUHJpdmF0ZUlkZW50aWZpZXI6Y2FzZSBuaS5TdHJpbmc6Y2FzZSBuaS5FcnJvcjpyZXR1cm4gdGhpcy5zdHJWYWx1ZTtjYXNlIG5pLk51bWJlcjpyZXR1cm4gdGhpcy5udW1WYWx1ZS50b1N0cmluZygpO2RlZmF1bHQ6cmV0dXJuIG51bGx9fX07ZnVuY3Rpb24gZVEobix0LGUpe3JldHVybiBuZXcgamMobix0LG5pLkNoYXJhY3RlcixlLFN0cmluZy5mcm9tQ2hhckNvZGUoZSkpfWZ1bmN0aW9uIHVCKG4sdCxlKXtyZXR1cm4gbmV3IGpjKG4sdCxuaS5PcGVyYXRvciwwLGUpfXZhciBkQj1uZXcgamMoLTEsLTEsbmkuQ2hhcmFjdGVyLDAsIiIpLGxWPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuaW5wdXQ9dCx0aGlzLnBlZWs9MCx0aGlzLmluZGV4PS0xLHRoaXMubGVuZ3RoPXQubGVuZ3RoLHRoaXMuYWR2YW5jZSgpfWFkdmFuY2UoKXt0aGlzLnBlZWs9Kyt0aGlzLmluZGV4Pj10aGlzLmxlbmd0aD8wOnRoaXMuaW5wdXQuY2hhckNvZGVBdCh0aGlzLmluZGV4KX1zY2FuVG9rZW4oKXtsZXQgdD10aGlzLmlucHV0LGU9dGhpcy5sZW5ndGgsaT10aGlzLnBlZWsscj10aGlzLmluZGV4O2Zvcig7aTw9MzI7KXtpZigrK3I+PWUpe2k9MDticmVha31pPXQuY2hhckNvZGVBdChyKX1pZih0aGlzLnBlZWs9aSx0aGlzLmluZGV4PXIscj49ZSlyZXR1cm4gbnVsbDtpZih0UShpKSlyZXR1cm4gdGhpcy5zY2FuSWRlbnRpZmllcigpO2lmKCRwKGkpKXJldHVybiB0aGlzLnNjYW5OdW1iZXIocik7bGV0IG89cjtzd2l0Y2goaSl7Y2FzZSA0NjpyZXR1cm4gdGhpcy5hZHZhbmNlKCksJHAodGhpcy5wZWVrKT90aGlzLnNjYW5OdW1iZXIobyk6ZVEobyx0aGlzLmluZGV4LDQ2KTtjYXNlIDQwOmNhc2UgNDE6Y2FzZSB0aDpjYXNlIE91OmNhc2UgOTE6Y2FzZSA5MzpjYXNlIDQ0OmNhc2UgNTg6Y2FzZSA1OTpyZXR1cm4gdGhpcy5zY2FuQ2hhcmFjdGVyKG8saSk7Y2FzZSAzOTpjYXNlIDM0OnJldHVybiB0aGlzLnNjYW5TdHJpbmcoKTtjYXNlIDM1OnJldHVybiB0aGlzLnNjYW5Qcml2YXRlSWRlbnRpZmllcigpO2Nhc2UgNDM6Y2FzZSA0NTpjYXNlIDQyOmNhc2UgNDc6Y2FzZSAzNzpjYXNlIDk0OnJldHVybiB0aGlzLnNjYW5PcGVyYXRvcihvLFN0cmluZy5mcm9tQ2hhckNvZGUoaSkpO2Nhc2UgNjM6cmV0dXJuIHRoaXMuc2NhblF1ZXN0aW9uKG8pO2Nhc2UgNjA6Y2FzZSA2MjpyZXR1cm4gdGhpcy5zY2FuQ29tcGxleE9wZXJhdG9yKG8sU3RyaW5nLmZyb21DaGFyQ29kZShpKSw2MSwiPSIpO2Nhc2UgMzM6Y2FzZSA2MTpyZXR1cm4gdGhpcy5zY2FuQ29tcGxleE9wZXJhdG9yKG8sU3RyaW5nLmZyb21DaGFyQ29kZShpKSw2MSwiPSIsNjEsIj0iKTtjYXNlIDM4OnJldHVybiB0aGlzLnNjYW5Db21wbGV4T3BlcmF0b3IobywiJiIsMzgsIiYiKTtjYXNlIDEyNDpyZXR1cm4gdGhpcy5zY2FuQ29tcGxleE9wZXJhdG9yKG8sInwiLDEyNCwifCIpO2Nhc2UgMTYwOmZvcig7UVYodGhpcy5wZWVrKTspdGhpcy5hZHZhbmNlKCk7cmV0dXJuIHRoaXMuc2NhblRva2VuKCl9cmV0dXJuIHRoaXMuYWR2YW5jZSgpLHRoaXMuZXJyb3IoYFVuZXhwZWN0ZWQgY2hhcmFjdGVyIFske1N0cmluZy5mcm9tQ2hhckNvZGUoaSl9XWAsMCl9c2NhbkNoYXJhY3Rlcih0LGUpe3JldHVybiB0aGlzLmFkdmFuY2UoKSxlUSh0LHRoaXMuaW5kZXgsZSl9c2Nhbk9wZXJhdG9yKHQsZSl7cmV0dXJuIHRoaXMuYWR2YW5jZSgpLHVCKHQsdGhpcy5pbmRleCxlKX1zY2FuQ29tcGxleE9wZXJhdG9yKHQsZSxpLHIsbyxzKXt0aGlzLmFkdmFuY2UoKTtsZXQgYT1lO3JldHVybiB0aGlzLnBlZWs9PWkmJih0aGlzLmFkdmFuY2UoKSxhKz1yKSxudWxsIT1vJiZ0aGlzLnBlZWs9PW8mJih0aGlzLmFkdmFuY2UoKSxhKz1zKSx1Qih0LHRoaXMuaW5kZXgsYSl9c2NhbklkZW50aWZpZXIoKXtsZXQgdD10aGlzLmluZGV4O2Zvcih0aGlzLmFkdmFuY2UoKTtuUSh0aGlzLnBlZWspOyl0aGlzLmFkdmFuY2UoKTtsZXQgZT10aGlzLmlucHV0LnN1YnN0cmluZyh0LHRoaXMuaW5kZXgpO3JldHVybiBVU2UuaW5kZXhPZihlKT4tMT9mdW5jdGlvbihuLHQsZSl7cmV0dXJuIG5ldyBqYyhuLHQsbmkuS2V5d29yZCwwLGUpfSh0LHRoaXMuaW5kZXgsZSk6ZnVuY3Rpb24obix0LGUpe3JldHVybiBuZXcgamMobix0LG5pLklkZW50aWZpZXIsMCxlKX0odCx0aGlzLmluZGV4LGUpfXNjYW5Qcml2YXRlSWRlbnRpZmllcigpe2xldCB0PXRoaXMuaW5kZXg7aWYodGhpcy5hZHZhbmNlKCksIXRRKHRoaXMucGVlaykpcmV0dXJuIHRoaXMuZXJyb3IoIkludmFsaWQgY2hhcmFjdGVyIFsjXSIsLTEpO2Zvcig7blEodGhpcy5wZWVrKTspdGhpcy5hZHZhbmNlKCk7bGV0IGU9dGhpcy5pbnB1dC5zdWJzdHJpbmcodCx0aGlzLmluZGV4KTtyZXR1cm4gZnVuY3Rpb24obix0LGUpe3JldHVybiBuZXcgamMobix0LG5pLlByaXZhdGVJZGVudGlmaWVyLDAsZSl9KHQsdGhpcy5pbmRleCxlKX1zY2FuTnVtYmVyKHQpe2xldCBlPXRoaXMuaW5kZXg9PT10LGk9ITE7Zm9yKHRoaXMuYWR2YW5jZSgpOzspe2lmKCEkcCh0aGlzLnBlZWspKWlmKDk1PT09dGhpcy5wZWVrKXtpZighJHAodGhpcy5pbnB1dC5jaGFyQ29kZUF0KHRoaXMuaW5kZXgtMSkpfHwhJHAodGhpcy5pbnB1dC5jaGFyQ29kZUF0KHRoaXMuaW5kZXgrMSkpKXJldHVybiB0aGlzLmVycm9yKCJJbnZhbGlkIG51bWVyaWMgc2VwYXJhdG9yIiwwKTtpPSEwfWVsc2UgaWYoNDY9PT10aGlzLnBlZWspZT0hMTtlbHNle2lmKDEwMSE9KG49dGhpcy5wZWVrKSYmNjkhPW4pYnJlYWs7aWYodGhpcy5hZHZhbmNlKCksUVNlKHRoaXMucGVlaykmJnRoaXMuYWR2YW5jZSgpLCEkcCh0aGlzLnBlZWspKXJldHVybiB0aGlzLmVycm9yKCJJbnZhbGlkIGV4cG9uZW50IiwtMSk7ZT0hMX10aGlzLmFkdmFuY2UoKX12YXIgbjtsZXQgcj10aGlzLmlucHV0LnN1YnN0cmluZyh0LHRoaXMuaW5kZXgpO2kmJihyPXIucmVwbGFjZSgvXy9nLCIiKSk7bGV0IG89ZT9mdW5jdGlvbihuKXtsZXQgdD1wYXJzZUludChuKTtpZihpc05hTih0KSl0aHJvdyBuZXcgRXJyb3IoIkludmFsaWQgaW50ZWdlciBsaXRlcmFsIHdoZW4gcGFyc2luZyAiK24pO3JldHVybiB0fShyKTpwYXJzZUZsb2F0KHIpO3JldHVybiBmdW5jdGlvbihuLHQsZSl7cmV0dXJuIG5ldyBqYyhuLHQsbmkuTnVtYmVyLGUsIiIpfSh0LHRoaXMuaW5kZXgsbyl9c2NhblN0cmluZygpe2xldCB0PXRoaXMuaW5kZXgsZT10aGlzLnBlZWs7dGhpcy5hZHZhbmNlKCk7bGV0IGk9IiIscj10aGlzLmluZGV4LG89dGhpcy5pbnB1dDtmb3IoO3RoaXMucGVlayE9ZTspaWYoOTI9PXRoaXMucGVlayl7bGV0IGE7aWYoaSs9by5zdWJzdHJpbmcocix0aGlzLmluZGV4KSx0aGlzLmFkdmFuY2UoKSx0aGlzLnBlZWs9dGhpcy5wZWVrLDExNz09dGhpcy5wZWVrKXtsZXQgbD1vLnN1YnN0cmluZyh0aGlzLmluZGV4KzEsdGhpcy5pbmRleCs1KTtpZighL15bMC05YS1mXSskL2kudGVzdChsKSlyZXR1cm4gdGhpcy5lcnJvcihgSW52YWxpZCB1bmljb2RlIGVzY2FwZSBbXFx1JHtsfV1gLDApO2E9cGFyc2VJbnQobCwxNik7Zm9yKGxldCBjPTA7Yzw1O2MrKyl0aGlzLmFkdmFuY2UoKX1lbHNlIGE9S1NlKHRoaXMucGVlayksdGhpcy5hZHZhbmNlKCk7aSs9U3RyaW5nLmZyb21DaGFyQ29kZShhKSxyPXRoaXMuaW5kZXh9ZWxzZXtpZigwPT10aGlzLnBlZWspcmV0dXJuIHRoaXMuZXJyb3IoIlVudGVybWluYXRlZCBxdW90ZSIsMCk7dGhpcy5hZHZhbmNlKCl9bGV0IHM9by5zdWJzdHJpbmcocix0aGlzLmluZGV4KTtyZXR1cm4gdGhpcy5hZHZhbmNlKCksZnVuY3Rpb24obix0LGUpe3JldHVybiBuZXcgamMobix0LG5pLlN0cmluZywwLGUpfSh0LHRoaXMuaW5kZXgsaStzKX1zY2FuUXVlc3Rpb24odCl7dGhpcy5hZHZhbmNlKCk7bGV0IGU9Ij8iO3JldHVybig2Mz09PXRoaXMucGVla3x8NDY9PT10aGlzLnBlZWspJiYoZSs9NDY9PT10aGlzLnBlZWs/Ii4iOiI/Iix0aGlzLmFkdmFuY2UoKSksdUIodCx0aGlzLmluZGV4LGUpfWVycm9yKHQsZSl7bGV0IGk9dGhpcy5pbmRleCtlO3JldHVybiBmdW5jdGlvbihuLHQsZSl7cmV0dXJuIG5ldyBqYyhuLHQsbmkuRXJyb3IsMCxlKX0oaSx0aGlzLmluZGV4LGBMZXhlciBFcnJvcjogJHt0fSBhdCBjb2x1bW4gJHtpfSBpbiBleHByZXNzaW9uIFske3RoaXMuaW5wdXR9XWApfX07ZnVuY3Rpb24gdFEobil7cmV0dXJuIDk3PD1uJiZuPD0xMjJ8fDY1PD1uJiZuPD05MHx8OTU9PW58fDM2PT1ufWZ1bmN0aW9uIG5RKG4pe3JldHVybiBLVihuKXx8JHAobil8fDk1PT1ufHwzNj09bn1mdW5jdGlvbiBRU2Uobil7cmV0dXJuIDQ1PT1ufHw0Mz09bn1mdW5jdGlvbiBLU2Uobil7c3dpdGNoKG4pe2Nhc2UgMTEwOnJldHVybiAxMDtjYXNlIDEwMjpyZXR1cm4gMTI7Y2FzZSAxMTQ6cmV0dXJuIDEzO2Nhc2UgMTE2OnJldHVybiA5O2Nhc2UgMTE4OnJldHVybiAxMTtkZWZhdWx0OnJldHVybiBufX12YXIgYkQ9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5fbGV4ZXI9dCx0aGlzLmVycm9ycz1bXX1wYXJzZUFjdGlvbih0LGUsaSxyLG89UHUpe3RoaXMuX2NoZWNrTm9JbnRlcnBvbGF0aW9uKHQsaSxvKTtsZXQgcz10aGlzLl9zdHJpcENvbW1lbnRzKHQpLGE9dGhpcy5fbGV4ZXIudG9rZW5pemUocyksbD0xO2UmJihsfD0yKTtsZXQgYz1uZXcgaW0odCxpLHIsYSxsLHRoaXMuZXJyb3JzLDApLnBhcnNlQ2hhaW4oKTtyZXR1cm4gbmV3IFJ1KGMsdCxpLHIsdGhpcy5lcnJvcnMpfXBhcnNlQmluZGluZyh0LGUsaSxyPVB1KXtsZXQgbz10aGlzLl9wYXJzZUJpbmRpbmdBc3QodCxlLGkscik7cmV0dXJuIG5ldyBSdShvLHQsZSxpLHRoaXMuZXJyb3JzKX1jaGVja1NpbXBsZUV4cHJlc3Npb24odCl7bGV0IGU9bmV3IGRWO3JldHVybiB0LnZpc2l0KGUpLGUuZXJyb3JzfXBhcnNlU2ltcGxlQmluZGluZyh0LGUsaSxyPVB1KXtsZXQgbz10aGlzLl9wYXJzZUJpbmRpbmdBc3QodCxlLGkscikscz10aGlzLmNoZWNrU2ltcGxlRXhwcmVzc2lvbihvKTtyZXR1cm4gcy5sZW5ndGg+MCYmdGhpcy5fcmVwb3J0RXJyb3IoYEhvc3QgYmluZGluZyBleHByZXNzaW9uIGNhbm5vdCBjb250YWluICR7cy5qb2luKCIgIil9YCx0LGUpLG5ldyBSdShvLHQsZSxpLHRoaXMuZXJyb3JzKX1fcmVwb3J0RXJyb3IodCxlLGkscil7dGhpcy5lcnJvcnMucHVzaChuZXcgUkModCxlLGkscikpfV9wYXJzZUJpbmRpbmdBc3QodCxlLGkscil7dGhpcy5fY2hlY2tOb0ludGVycG9sYXRpb24odCxlLHIpO2xldCBvPXRoaXMuX3N0cmlwQ29tbWVudHModCkscz10aGlzLl9sZXhlci50b2tlbml6ZShvKTtyZXR1cm4gbmV3IGltKHQsZSxpLHMsMCx0aGlzLmVycm9ycywwKS5wYXJzZUNoYWluKCl9cGFyc2VUZW1wbGF0ZUJpbmRpbmdzKHQsZSxpLHIsbyl7bGV0IHM9dGhpcy5fbGV4ZXIudG9rZW5pemUoZSk7cmV0dXJuIG5ldyBpbShlLGksbyxzLDAsdGhpcy5lcnJvcnMsMCkucGFyc2VUZW1wbGF0ZUJpbmRpbmdzKHtzb3VyY2U6dCxzcGFuOm5ldyBhbChyLHIrdC5sZW5ndGgpfSl9cGFyc2VJbnRlcnBvbGF0aW9uKHQsZSxpLHIsbz1QdSl7bGV0e3N0cmluZ3M6cyxleHByZXNzaW9uczphLG9mZnNldHM6bH09dGhpcy5zcGxpdEludGVycG9sYXRpb24odCxlLHIsbyk7aWYoMD09PWEubGVuZ3RoKXJldHVybiBudWxsO2xldCBjPVtdO2ZvcihsZXQgdT0wO3U8YS5sZW5ndGg7Kyt1KXtsZXQgcD10aGlzLl9zdHJpcENvbW1lbnRzKGFbdV0udGV4dCksaD10aGlzLl9sZXhlci50b2tlbml6ZShwKSxmPW5ldyBpbSh0LGUsaSxoLDAsdGhpcy5lcnJvcnMsbFt1XSkucGFyc2VDaGFpbigpO2MucHVzaChmKX1yZXR1cm4gdGhpcy5jcmVhdGVJbnRlcnBvbGF0aW9uQXN0KHMubWFwKHU9PnUudGV4dCksYyx0LGUsaSl9cGFyc2VJbnRlcnBvbGF0aW9uRXhwcmVzc2lvbih0LGUsaSl7bGV0IHI9dGhpcy5fc3RyaXBDb21tZW50cyh0KSxvPXRoaXMuX2xleGVyLnRva2VuaXplKHIpLHM9bmV3IGltKHQsZSxpLG8sMCx0aGlzLmVycm9ycywwKS5wYXJzZUNoYWluKCk7cmV0dXJuIHRoaXMuY3JlYXRlSW50ZXJwb2xhdGlvbkFzdChbIiIsIiJdLFtzXSx0LGUsaSl9Y3JlYXRlSW50ZXJwb2xhdGlvbkFzdCh0LGUsaSxyLG8pe2xldCBzPW5ldyBibSgwLGkubGVuZ3RoKSxhPW5ldyB2cyhzLHMudG9BYnNvbHV0ZShvKSx0LGUpO3JldHVybiBuZXcgUnUoYSxpLHIsbyx0aGlzLmVycm9ycyl9c3BsaXRJbnRlcnBvbGF0aW9uKHQsZSxpLHI9UHUpe2xldCBvPVtdLHM9W10sYT1bXSxsPWk/ZnVuY3Rpb24obil7bGV0IHQ9bmV3IE1hcCxlPTAsaT0wLHI9MDtmb3IoO3I8bi5sZW5ndGg7KXtsZXQgbz1uW3JdO2lmKDk9PT1vLnR5cGUpe2xldFtzLGFdPW8ucGFydHM7ZSs9YS5sZW5ndGgsaSs9cy5sZW5ndGh9ZWxzZXtsZXQgcz1vLnBhcnRzLnJlZHVjZSgoYSxsKT0+YStsLmxlbmd0aCwwKTtpKz1zLGUrPXN9dC5zZXQoaSxlKSxyKyt9cmV0dXJuIHR9KGkpOm51bGwsYz0wLHU9ITEsZD0hMSx7c3RhcnQ6cCxlbmQ6aH09cjtmb3IoO2M8dC5sZW5ndGg7KWlmKHUpe2xldCBmPWMsbT1mK3AubGVuZ3RoLHg9dGhpcy5fZ2V0SW50ZXJwb2xhdGlvbkVuZEluZGV4KHQsaCxtKTtpZigtMT09PXgpe3U9ITEsZD0hMDticmVha31sZXQgZz14K2gubGVuZ3RoLGI9dC5zdWJzdHJpbmcobSx4KTswPT09Yi50cmltKCkubGVuZ3RoJiZ0aGlzLl9yZXBvcnRFcnJvcigiQmxhbmsgZXhwcmVzc2lvbnMgYXJlIG5vdCBhbGxvd2VkIGluIGludGVycG9sYXRlZCBzdHJpbmdzIix0LGBhdCBjb2x1bW4gJHtjfSBpbmAsZSkscy5wdXNoKHt0ZXh0OmIsc3RhcnQ6ZixlbmQ6Z30pO2xldCBUPShsPy5nZXQoZik/P2YpK3AubGVuZ3RoO2EucHVzaChUKSxjPWcsdT0hMX1lbHNle2xldCBmPWM7Yz10LmluZGV4T2YocCxjKSwtMT09PWMmJihjPXQubGVuZ3RoKTtsZXQgbT10LnN1YnN0cmluZyhmLGMpO28ucHVzaCh7dGV4dDptLHN0YXJ0OmYsZW5kOmN9KSx1PSEwfWlmKCF1KWlmKGQpe2xldCBmPW9bby5sZW5ndGgtMV07Zi50ZXh0Kz10LnN1YnN0cmluZyhjKSxmLmVuZD10Lmxlbmd0aH1lbHNlIG8ucHVzaCh7dGV4dDp0LnN1YnN0cmluZyhjKSxzdGFydDpjLGVuZDp0Lmxlbmd0aH0pO3JldHVybiBuZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3RoaXMuc3RyaW5ncz10LHRoaXMuZXhwcmVzc2lvbnM9ZSx0aGlzLm9mZnNldHM9aX19KG8scyxhKX13cmFwTGl0ZXJhbFByaW1pdGl2ZSh0LGUsaSl7bGV0IHI9bmV3IGJtKDAsbnVsbD09dD8wOnQubGVuZ3RoKTtyZXR1cm4gbmV3IFJ1KG5ldyB0YShyLHIudG9BYnNvbHV0ZShpKSx0KSx0LGUsaSx0aGlzLmVycm9ycyl9X3N0cmlwQ29tbWVudHModCl7bGV0IGU9dGhpcy5fY29tbWVudFN0YXJ0KHQpO3JldHVybiBudWxsIT1lP3Quc3Vic3RyaW5nKDAsZSk6dH1fY29tbWVudFN0YXJ0KHQpe2xldCBlPW51bGw7Zm9yKGxldCBpPTA7aTx0Lmxlbmd0aC0xO2krKyl7bGV0IHI9dC5jaGFyQ29kZUF0KGkpLG89dC5jaGFyQ29kZUF0KGkrMSk7aWYoNDc9PT1yJiY0Nz09byYmbnVsbD09ZSlyZXR1cm4gaTtlPT09cj9lPW51bGw6bnVsbD09ZSYmV0IocikmJihlPXIpfXJldHVybiBudWxsfV9jaGVja05vSW50ZXJwb2xhdGlvbih0LGUse3N0YXJ0OmksZW5kOnJ9KXtsZXQgbz0tMSxzPS0xO2ZvcihsZXQgYSBvZiB0aGlzLl9mb3JFYWNoVW5xdW90ZWRDaGFyKHQsMCkpaWYoLTE9PT1vKXQuc3RhcnRzV2l0aChpKSYmKG89YSk7ZWxzZSBpZihzPXRoaXMuX2dldEludGVycG9sYXRpb25FbmRJbmRleCh0LHIsYSkscz4tMSlicmVhaztvPi0xJiZzPi0xJiZ0aGlzLl9yZXBvcnRFcnJvcihgR290IGludGVycG9sYXRpb24gKCR7aX0ke3J9KSB3aGVyZSBleHByZXNzaW9uIHdhcyBleHBlY3RlZGAsdCxgYXQgY29sdW1uICR7b30gaW5gLGUpfV9nZXRJbnRlcnBvbGF0aW9uRW5kSW5kZXgodCxlLGkpe2ZvcihsZXQgciBvZiB0aGlzLl9mb3JFYWNoVW5xdW90ZWRDaGFyKHQsaSkpe2lmKHQuc3RhcnRzV2l0aChlLHIpKXJldHVybiByO2lmKHQuc3RhcnRzV2l0aCgiLy8iLHIpKXJldHVybiB0LmluZGV4T2YoZSxyKX1yZXR1cm4tMX0qX2ZvckVhY2hVbnF1b3RlZENoYXIodCxlKXtsZXQgaT1udWxsLHI9MDtmb3IobGV0IG89ZTtvPHQubGVuZ3RoO28rKyl7bGV0IHM9dFtvXTshV0IodC5jaGFyQ29kZUF0KG8pKXx8bnVsbCE9PWkmJmkhPT1zfHxyJTIhPTA/bnVsbD09PWkmJih5aWVsZCBvKTppPW51bGw9PT1pP3M6bnVsbCxyPSJcXCI9PT1zP3IrMTowfX19LHNtPSgoKT0+e3JldHVybihuPXNtfHwoc209e30pKVtuLk5vbmU9MF09Ik5vbmUiLG5bbi5Xcml0YWJsZT0xXT0iV3JpdGFibGUiLHNtO3ZhciBufSkoKSxpbT1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyxhKXt0aGlzLmlucHV0PXQsdGhpcy5sb2NhdGlvbj1lLHRoaXMuYWJzb2x1dGVPZmZzZXQ9aSx0aGlzLnRva2Vucz1yLHRoaXMucGFyc2VGbGFncz1vLHRoaXMuZXJyb3JzPXMsdGhpcy5vZmZzZXQ9YSx0aGlzLnJwYXJlbnNFeHBlY3RlZD0wLHRoaXMucmJyYWNrZXRzRXhwZWN0ZWQ9MCx0aGlzLnJicmFjZXNFeHBlY3RlZD0wLHRoaXMuY29udGV4dD1zbS5Ob25lLHRoaXMuc291cmNlU3BhbkNhY2hlPW5ldyBNYXAsdGhpcy5pbmRleD0wfXBlZWsodCl7bGV0IGU9dGhpcy5pbmRleCt0O3JldHVybiBlPHRoaXMudG9rZW5zLmxlbmd0aD90aGlzLnRva2Vuc1tlXTpkQn1nZXQgbmV4dCgpe3JldHVybiB0aGlzLnBlZWsoMCl9Z2V0IGF0RU9GKCl7cmV0dXJuIHRoaXMuaW5kZXg+PXRoaXMudG9rZW5zLmxlbmd0aH1nZXQgaW5wdXRJbmRleCgpe3JldHVybiB0aGlzLmF0RU9GP3RoaXMuY3VycmVudEVuZEluZGV4OnRoaXMubmV4dC5pbmRleCt0aGlzLm9mZnNldH1nZXQgY3VycmVudEVuZEluZGV4KCl7cmV0dXJuIHRoaXMuaW5kZXg+MD90aGlzLnBlZWsoLTEpLmVuZCt0aGlzLm9mZnNldDowPT09dGhpcy50b2tlbnMubGVuZ3RoP3RoaXMuaW5wdXQubGVuZ3RoK3RoaXMub2Zmc2V0OnRoaXMubmV4dC5pbmRleCt0aGlzLm9mZnNldH1nZXQgY3VycmVudEFic29sdXRlT2Zmc2V0KCl7cmV0dXJuIHRoaXMuYWJzb2x1dGVPZmZzZXQrdGhpcy5pbnB1dEluZGV4fXNwYW4odCxlKXtsZXQgaT10aGlzLmN1cnJlbnRFbmRJbmRleDtpZih2b2lkIDAhPT1lJiZlPnRoaXMuY3VycmVudEVuZEluZGV4JiYoaT1lKSx0Pmkpe2xldCByPWk7aT10LHQ9cn1yZXR1cm4gbmV3IGJtKHQsaSl9c291cmNlU3Bhbih0LGUpe2xldCBpPWAke3R9QCR7dGhpcy5pbnB1dEluZGV4fToke2V9YDtyZXR1cm4gdGhpcy5zb3VyY2VTcGFuQ2FjaGUuaGFzKGkpfHx0aGlzLnNvdXJjZVNwYW5DYWNoZS5zZXQoaSx0aGlzLnNwYW4odCxlKS50b0Fic29sdXRlKHRoaXMuYWJzb2x1dGVPZmZzZXQpKSx0aGlzLnNvdXJjZVNwYW5DYWNoZS5nZXQoaSl9YWR2YW5jZSgpe3RoaXMuaW5kZXgrK313aXRoQ29udGV4dCh0LGUpe3RoaXMuY29udGV4dHw9dDtsZXQgaT1lKCk7cmV0dXJuIHRoaXMuY29udGV4dF49dCxpfWNvbnN1bWVPcHRpb25hbENoYXJhY3Rlcih0KXtyZXR1cm4hIXRoaXMubmV4dC5pc0NoYXJhY3Rlcih0KSYmKHRoaXMuYWR2YW5jZSgpLCEwKX1wZWVrS2V5d29yZExldCgpe3JldHVybiB0aGlzLm5leHQuaXNLZXl3b3JkTGV0KCl9cGVla0tleXdvcmRBcygpe3JldHVybiB0aGlzLm5leHQuaXNLZXl3b3JkQXMoKX1leHBlY3RDaGFyYWN0ZXIodCl7dGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIodCl8fHRoaXMuZXJyb3IoYE1pc3NpbmcgZXhwZWN0ZWQgJHtTdHJpbmcuZnJvbUNoYXJDb2RlKHQpfWApfWNvbnN1bWVPcHRpb25hbE9wZXJhdG9yKHQpe3JldHVybiEhdGhpcy5uZXh0LmlzT3BlcmF0b3IodCkmJih0aGlzLmFkdmFuY2UoKSwhMCl9ZXhwZWN0T3BlcmF0b3IodCl7dGhpcy5jb25zdW1lT3B0aW9uYWxPcGVyYXRvcih0KXx8dGhpcy5lcnJvcihgTWlzc2luZyBleHBlY3RlZCBvcGVyYXRvciAke3R9YCl9cHJldHR5UHJpbnRUb2tlbih0KXtyZXR1cm4gdD09PWRCPyJlbmQgb2YgaW5wdXQiOmB0b2tlbiAke3R9YH1leHBlY3RJZGVudGlmaWVyT3JLZXl3b3JkKCl7bGV0IHQ9dGhpcy5uZXh0O3JldHVybiB0LmlzSWRlbnRpZmllcigpfHx0LmlzS2V5d29yZCgpPyh0aGlzLmFkdmFuY2UoKSx0LnRvU3RyaW5nKCkpOih0LmlzUHJpdmF0ZUlkZW50aWZpZXIoKT90aGlzLl9yZXBvcnRFcnJvckZvclByaXZhdGVJZGVudGlmaWVyKHQsImV4cGVjdGVkIGlkZW50aWZpZXIgb3Iga2V5d29yZCIpOnRoaXMuZXJyb3IoYFVuZXhwZWN0ZWQgJHt0aGlzLnByZXR0eVByaW50VG9rZW4odCl9LCBleHBlY3RlZCBpZGVudGlmaWVyIG9yIGtleXdvcmRgKSxudWxsKX1leHBlY3RJZGVudGlmaWVyT3JLZXl3b3JkT3JTdHJpbmcoKXtsZXQgdD10aGlzLm5leHQ7cmV0dXJuIHQuaXNJZGVudGlmaWVyKCl8fHQuaXNLZXl3b3JkKCl8fHQuaXNTdHJpbmcoKT8odGhpcy5hZHZhbmNlKCksdC50b1N0cmluZygpKToodC5pc1ByaXZhdGVJZGVudGlmaWVyKCk/dGhpcy5fcmVwb3J0RXJyb3JGb3JQcml2YXRlSWRlbnRpZmllcih0LCJleHBlY3RlZCBpZGVudGlmaWVyLCBrZXl3b3JkIG9yIHN0cmluZyIpOnRoaXMuZXJyb3IoYFVuZXhwZWN0ZWQgJHt0aGlzLnByZXR0eVByaW50VG9rZW4odCl9LCBleHBlY3RlZCBpZGVudGlmaWVyLCBrZXl3b3JkLCBvciBzdHJpbmdgKSwiIil9cGFyc2VDaGFpbigpe2xldCB0PVtdLGU9dGhpcy5pbnB1dEluZGV4O2Zvcig7dGhpcy5pbmRleDx0aGlzLnRva2Vucy5sZW5ndGg7KXtsZXQgaT10aGlzLnBhcnNlUGlwZSgpO2lmKHQucHVzaChpKSx0aGlzLmNvbnN1bWVPcHRpb25hbENoYXJhY3Rlcig1OSkpZm9yKDEmdGhpcy5wYXJzZUZsYWdzfHx0aGlzLmVycm9yKCJCaW5kaW5nIGV4cHJlc3Npb24gY2Fubm90IGNvbnRhaW4gY2hhaW5lZCBleHByZXNzaW9uIik7dGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoNTkpOyk7ZWxzZSBpZih0aGlzLmluZGV4PHRoaXMudG9rZW5zLmxlbmd0aCl7bGV0IHI9dGhpcy5pbmRleDtpZih0aGlzLmVycm9yKGBVbmV4cGVjdGVkIHRva2VuICcke3RoaXMubmV4dH0nYCksdGhpcy5pbmRleD09PXIpYnJlYWt9fWlmKDA9PT10Lmxlbmd0aCl7bGV0IGk9dGhpcy5vZmZzZXQscj10aGlzLm9mZnNldCt0aGlzLmlucHV0Lmxlbmd0aDtyZXR1cm4gbmV3IElhKHRoaXMuc3BhbihpLHIpLHRoaXMuc291cmNlU3BhbihpLHIpKX1yZXR1cm4gMT09dC5sZW5ndGg/dFswXTpuZXcga0ModGhpcy5zcGFuKGUpLHRoaXMuc291cmNlU3BhbihlKSx0KX1wYXJzZVBpcGUoKXtsZXQgdD10aGlzLmlucHV0SW5kZXgsZT10aGlzLnBhcnNlRXhwcmVzc2lvbigpO2lmKHRoaXMuY29uc3VtZU9wdGlvbmFsT3BlcmF0b3IoInwiKSl7MSZ0aGlzLnBhcnNlRmxhZ3MmJnRoaXMuZXJyb3IoIkNhbm5vdCBoYXZlIGEgcGlwZSBpbiBhbiBhY3Rpb24gZXhwcmVzc2lvbiIpO2Rve2xldCBvLHMsaT10aGlzLmlucHV0SW5kZXgscj10aGlzLmV4cGVjdElkZW50aWZpZXJPcktleXdvcmQoKTtudWxsIT09cj9vPXRoaXMuc291cmNlU3BhbihpKToocj0iIixzPS0xIT09dGhpcy5uZXh0LmluZGV4P3RoaXMubmV4dC5pbmRleDp0aGlzLmlucHV0Lmxlbmd0aCt0aGlzLm9mZnNldCxvPW5ldyBibShzLHMpLnRvQWJzb2x1dGUodGhpcy5hYnNvbHV0ZU9mZnNldCkpO2xldCBhPVtdO2Zvcig7dGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoNTgpOylhLnB1c2godGhpcy5wYXJzZUV4cHJlc3Npb24oKSk7ZT1uZXcgUl8odGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0LHMpLGUscixhLG8pfXdoaWxlKHRoaXMuY29uc3VtZU9wdGlvbmFsT3BlcmF0b3IoInwiKSl9cmV0dXJuIGV9cGFyc2VFeHByZXNzaW9uKCl7cmV0dXJuIHRoaXMucGFyc2VDb25kaXRpb25hbCgpfXBhcnNlQ29uZGl0aW9uYWwoKXtsZXQgdD10aGlzLmlucHV0SW5kZXgsZT10aGlzLnBhcnNlTG9naWNhbE9yKCk7aWYodGhpcy5jb25zdW1lT3B0aW9uYWxPcGVyYXRvcigiPyIpKXtsZXQgcixpPXRoaXMucGFyc2VQaXBlKCk7aWYodGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoNTgpKXI9dGhpcy5wYXJzZVBpcGUoKTtlbHNle2xldCBzPXRoaXMuaW5wdXQuc3Vic3RyaW5nKHQsdGhpcy5pbnB1dEluZGV4KTt0aGlzLmVycm9yKGBDb25kaXRpb25hbCBleHByZXNzaW9uICR7c30gcmVxdWlyZXMgYWxsIDMgZXhwcmVzc2lvbnNgKSxyPW5ldyBJYSh0aGlzLnNwYW4odCksdGhpcy5zb3VyY2VTcGFuKHQpKX1yZXR1cm4gbmV3IEZDKHRoaXMuc3Bhbih0KSx0aGlzLnNvdXJjZVNwYW4odCksZSxpLHIpfXJldHVybiBlfXBhcnNlTG9naWNhbE9yKCl7bGV0IHQ9dGhpcy5pbnB1dEluZGV4LGU9dGhpcy5wYXJzZUxvZ2ljYWxBbmQoKTtmb3IoO3RoaXMuY29uc3VtZU9wdGlvbmFsT3BlcmF0b3IoInx8Iik7KXtsZXQgaT10aGlzLnBhcnNlTG9naWNhbEFuZCgpO2U9bmV3IEdsKHRoaXMuc3Bhbih0KSx0aGlzLnNvdXJjZVNwYW4odCksInx8IixlLGkpfXJldHVybiBlfXBhcnNlTG9naWNhbEFuZCgpe2xldCB0PXRoaXMuaW5wdXRJbmRleCxlPXRoaXMucGFyc2VOdWxsaXNoQ29hbGVzY2luZygpO2Zvcig7dGhpcy5jb25zdW1lT3B0aW9uYWxPcGVyYXRvcigiJiYiKTspe2xldCBpPXRoaXMucGFyc2VOdWxsaXNoQ29hbGVzY2luZygpO2U9bmV3IEdsKHRoaXMuc3Bhbih0KSx0aGlzLnNvdXJjZVNwYW4odCksIiYmIixlLGkpfXJldHVybiBlfXBhcnNlTnVsbGlzaENvYWxlc2NpbmcoKXtsZXQgdD10aGlzLmlucHV0SW5kZXgsZT10aGlzLnBhcnNlRXF1YWxpdHkoKTtmb3IoO3RoaXMuY29uc3VtZU9wdGlvbmFsT3BlcmF0b3IoIj8/Iik7KXtsZXQgaT10aGlzLnBhcnNlRXF1YWxpdHkoKTtlPW5ldyBHbCh0aGlzLnNwYW4odCksdGhpcy5zb3VyY2VTcGFuKHQpLCI/PyIsZSxpKX1yZXR1cm4gZX1wYXJzZUVxdWFsaXR5KCl7bGV0IHQ9dGhpcy5pbnB1dEluZGV4LGU9dGhpcy5wYXJzZVJlbGF0aW9uYWwoKTtmb3IoO3RoaXMubmV4dC50eXBlPT1uaS5PcGVyYXRvcjspe2xldCBpPXRoaXMubmV4dC5zdHJWYWx1ZTtzd2l0Y2goaSl7Y2FzZSI9PSI6Y2FzZSI9PT0iOmNhc2UiIT0iOmNhc2UiIT09Ijp0aGlzLmFkdmFuY2UoKTtsZXQgcj10aGlzLnBhcnNlUmVsYXRpb25hbCgpO2U9bmV3IEdsKHRoaXMuc3Bhbih0KSx0aGlzLnNvdXJjZVNwYW4odCksaSxlLHIpO2NvbnRpbnVlfWJyZWFrfXJldHVybiBlfXBhcnNlUmVsYXRpb25hbCgpe2xldCB0PXRoaXMuaW5wdXRJbmRleCxlPXRoaXMucGFyc2VBZGRpdGl2ZSgpO2Zvcig7dGhpcy5uZXh0LnR5cGU9PW5pLk9wZXJhdG9yOyl7bGV0IGk9dGhpcy5uZXh0LnN0clZhbHVlO3N3aXRjaChpKXtjYXNlIjwiOmNhc2UiPiI6Y2FzZSI8PSI6Y2FzZSI+PSI6dGhpcy5hZHZhbmNlKCk7bGV0IHI9dGhpcy5wYXJzZUFkZGl0aXZlKCk7ZT1uZXcgR2wodGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSxpLGUscik7Y29udGludWV9YnJlYWt9cmV0dXJuIGV9cGFyc2VBZGRpdGl2ZSgpe2xldCB0PXRoaXMuaW5wdXRJbmRleCxlPXRoaXMucGFyc2VNdWx0aXBsaWNhdGl2ZSgpO2Zvcig7dGhpcy5uZXh0LnR5cGU9PW5pLk9wZXJhdG9yOyl7bGV0IGk9dGhpcy5uZXh0LnN0clZhbHVlO3N3aXRjaChpKXtjYXNlIisiOmNhc2UiLSI6dGhpcy5hZHZhbmNlKCk7bGV0IHI9dGhpcy5wYXJzZU11bHRpcGxpY2F0aXZlKCk7ZT1uZXcgR2wodGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSxpLGUscik7Y29udGludWV9YnJlYWt9cmV0dXJuIGV9cGFyc2VNdWx0aXBsaWNhdGl2ZSgpe2xldCB0PXRoaXMuaW5wdXRJbmRleCxlPXRoaXMucGFyc2VQcmVmaXgoKTtmb3IoO3RoaXMubmV4dC50eXBlPT1uaS5PcGVyYXRvcjspe2xldCBpPXRoaXMubmV4dC5zdHJWYWx1ZTtzd2l0Y2goaSl7Y2FzZSIqIjpjYXNlIiUiOmNhc2UiLyI6dGhpcy5hZHZhbmNlKCk7bGV0IHI9dGhpcy5wYXJzZVByZWZpeCgpO2U9bmV3IEdsKHRoaXMuc3Bhbih0KSx0aGlzLnNvdXJjZVNwYW4odCksaSxlLHIpO2NvbnRpbnVlfWJyZWFrfXJldHVybiBlfXBhcnNlUHJlZml4KCl7aWYodGhpcy5uZXh0LnR5cGU9PW5pLk9wZXJhdG9yKXtsZXQgaSx0PXRoaXMuaW5wdXRJbmRleDtzd2l0Y2godGhpcy5uZXh0LnN0clZhbHVlKXtjYXNlIisiOnJldHVybiB0aGlzLmFkdmFuY2UoKSxpPXRoaXMucGFyc2VQcmVmaXgoKSx6Yy5jcmVhdGVQbHVzKHRoaXMuc3Bhbih0KSx0aGlzLnNvdXJjZVNwYW4odCksaSk7Y2FzZSItIjpyZXR1cm4gdGhpcy5hZHZhbmNlKCksaT10aGlzLnBhcnNlUHJlZml4KCksemMuY3JlYXRlTWludXModGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSxpKTtjYXNlIiEiOnJldHVybiB0aGlzLmFkdmFuY2UoKSxpPXRoaXMucGFyc2VQcmVmaXgoKSxuZXcgSEModGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSxpKX19cmV0dXJuIHRoaXMucGFyc2VDYWxsQ2hhaW4oKX1wYXJzZUNhbGxDaGFpbigpe2xldCB0PXRoaXMuaW5wdXRJbmRleCxlPXRoaXMucGFyc2VQcmltYXJ5KCk7Zm9yKDs7KWlmKHRoaXMuY29uc3VtZU9wdGlvbmFsQ2hhcmFjdGVyKDQ2KSllPXRoaXMucGFyc2VBY2Nlc3NNZW1iZXIoZSx0LCExKTtlbHNlIGlmKHRoaXMuY29uc3VtZU9wdGlvbmFsT3BlcmF0b3IoIj8uIikpZT10aGlzLmNvbnN1bWVPcHRpb25hbENoYXJhY3Rlcig0MCk/dGhpcy5wYXJzZUNhbGwoZSx0LCEwKTp0aGlzLmNvbnN1bWVPcHRpb25hbENoYXJhY3Rlcig5MSk/dGhpcy5wYXJzZUtleWVkUmVhZE9yV3JpdGUoZSx0LCEwKTp0aGlzLnBhcnNlQWNjZXNzTWVtYmVyKGUsdCwhMCk7ZWxzZSBpZih0aGlzLmNvbnN1bWVPcHRpb25hbENoYXJhY3Rlcig5MSkpZT10aGlzLnBhcnNlS2V5ZWRSZWFkT3JXcml0ZShlLHQsITEpO2Vsc2UgaWYodGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoNDApKWU9dGhpcy5wYXJzZUNhbGwoZSx0LCExKTtlbHNle2lmKCF0aGlzLmNvbnN1bWVPcHRpb25hbE9wZXJhdG9yKCIhIikpcmV0dXJuIGU7ZT1uZXcgVUModGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSxlKX19cGFyc2VQcmltYXJ5KCl7bGV0IHQ9dGhpcy5pbnB1dEluZGV4O2lmKHRoaXMuY29uc3VtZU9wdGlvbmFsQ2hhcmFjdGVyKDQwKSl7dGhpcy5ycGFyZW5zRXhwZWN0ZWQrKztsZXQgZT10aGlzLnBhcnNlUGlwZSgpO3JldHVybiB0aGlzLnJwYXJlbnNFeHBlY3RlZC0tLHRoaXMuZXhwZWN0Q2hhcmFjdGVyKDQxKSxlfWlmKHRoaXMubmV4dC5pc0tleXdvcmROdWxsKCkpcmV0dXJuIHRoaXMuYWR2YW5jZSgpLG5ldyB0YSh0aGlzLnNwYW4odCksdGhpcy5zb3VyY2VTcGFuKHQpLG51bGwpO2lmKHRoaXMubmV4dC5pc0tleXdvcmRVbmRlZmluZWQoKSlyZXR1cm4gdGhpcy5hZHZhbmNlKCksbmV3IHRhKHRoaXMuc3Bhbih0KSx0aGlzLnNvdXJjZVNwYW4odCksdm9pZCAwKTtpZih0aGlzLm5leHQuaXNLZXl3b3JkVHJ1ZSgpKXJldHVybiB0aGlzLmFkdmFuY2UoKSxuZXcgdGEodGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSwhMCk7aWYodGhpcy5uZXh0LmlzS2V5d29yZEZhbHNlKCkpcmV0dXJuIHRoaXMuYWR2YW5jZSgpLG5ldyB0YSh0aGlzLnNwYW4odCksdGhpcy5zb3VyY2VTcGFuKHQpLCExKTtpZih0aGlzLm5leHQuaXNLZXl3b3JkVGhpcygpKXJldHVybiB0aGlzLmFkdmFuY2UoKSxuZXcgT0ModGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSk7aWYodGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoOTEpKXt0aGlzLnJicmFja2V0c0V4cGVjdGVkKys7bGV0IGU9dGhpcy5wYXJzZUV4cHJlc3Npb25MaXN0KDkzKTtyZXR1cm4gdGhpcy5yYnJhY2tldHNFeHBlY3RlZC0tLHRoaXMuZXhwZWN0Q2hhcmFjdGVyKDkzKSxuZXcgT18odGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSxlKX1pZih0aGlzLm5leHQuaXNDaGFyYWN0ZXIodGgpKXJldHVybiB0aGlzLnBhcnNlTGl0ZXJhbE1hcCgpO2lmKHRoaXMubmV4dC5pc0lkZW50aWZpZXIoKSlyZXR1cm4gdGhpcy5wYXJzZUFjY2Vzc01lbWJlcihuZXcgeG0odGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSksdCwhMSk7aWYodGhpcy5uZXh0LmlzTnVtYmVyKCkpe2xldCBlPXRoaXMubmV4dC50b051bWJlcigpO3JldHVybiB0aGlzLmFkdmFuY2UoKSxuZXcgdGEodGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSxlKX1pZih0aGlzLm5leHQuaXNTdHJpbmcoKSl7bGV0IGU9dGhpcy5uZXh0LnRvU3RyaW5nKCk7cmV0dXJuIHRoaXMuYWR2YW5jZSgpLG5ldyB0YSh0aGlzLnNwYW4odCksdGhpcy5zb3VyY2VTcGFuKHQpLGUpfXJldHVybiB0aGlzLm5leHQuaXNQcml2YXRlSWRlbnRpZmllcigpPyh0aGlzLl9yZXBvcnRFcnJvckZvclByaXZhdGVJZGVudGlmaWVyKHRoaXMubmV4dCxudWxsKSxuZXcgSWEodGhpcy5zcGFuKHQpLHRoaXMuc291cmNlU3Bhbih0KSkpOnRoaXMuaW5kZXg+PXRoaXMudG9rZW5zLmxlbmd0aD8odGhpcy5lcnJvcihgVW5leHBlY3RlZCBlbmQgb2YgZXhwcmVzc2lvbjogJHt0aGlzLmlucHV0fWApLG5ldyBJYSh0aGlzLnNwYW4odCksdGhpcy5zb3VyY2VTcGFuKHQpKSk6KHRoaXMuZXJyb3IoYFVuZXhwZWN0ZWQgdG9rZW4gJHt0aGlzLm5leHR9YCksbmV3IElhKHRoaXMuc3Bhbih0KSx0aGlzLnNvdXJjZVNwYW4odCkpKX1wYXJzZUV4cHJlc3Npb25MaXN0KHQpe2xldCBlPVtdO2Rve2lmKHRoaXMubmV4dC5pc0NoYXJhY3Rlcih0KSlicmVhaztlLnB1c2godGhpcy5wYXJzZVBpcGUoKSl9d2hpbGUodGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoNDQpKTtyZXR1cm4gZX1wYXJzZUxpdGVyYWxNYXAoKXtsZXQgdD1bXSxlPVtdLGk9dGhpcy5pbnB1dEluZGV4O2lmKHRoaXMuZXhwZWN0Q2hhcmFjdGVyKHRoKSwhdGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoT3UpKXt0aGlzLnJicmFjZXNFeHBlY3RlZCsrO2Rve2xldCByPXRoaXMuaW5wdXRJbmRleCxvPXRoaXMubmV4dC5pc1N0cmluZygpLHM9dGhpcy5leHBlY3RJZGVudGlmaWVyT3JLZXl3b3JkT3JTdHJpbmcoKTtpZih0LnB1c2goe2tleTpzLHF1b3RlZDpvfSksbyl0aGlzLmV4cGVjdENoYXJhY3Rlcig1OCksZS5wdXNoKHRoaXMucGFyc2VQaXBlKCkpO2Vsc2UgaWYodGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoNTgpKWUucHVzaCh0aGlzLnBhcnNlUGlwZSgpKTtlbHNle2xldCBhPXRoaXMuc3BhbihyKSxsPXRoaXMuc291cmNlU3BhbihyKTtlLnB1c2gobmV3IEx1KGEsbCxsLG5ldyB4bShhLGwpLHMpKX19d2hpbGUodGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoNDQpKTt0aGlzLnJicmFjZXNFeHBlY3RlZC0tLHRoaXMuZXhwZWN0Q2hhcmFjdGVyKE91KX1yZXR1cm4gbmV3IFZDKHRoaXMuc3BhbihpKSx0aGlzLnNvdXJjZVNwYW4oaSksdCxlKX1wYXJzZUFjY2Vzc01lbWJlcih0LGUsaSl7bGV0IGEscj10aGlzLmlucHV0SW5kZXgsbz10aGlzLndpdGhDb250ZXh0KHNtLldyaXRhYmxlLCgpPT57bGV0IGw9dGhpcy5leHBlY3RJZGVudGlmaWVyT3JLZXl3b3JkKCk/PyIiO3JldHVybiAwPT09bC5sZW5ndGgmJnRoaXMuZXJyb3IoIkV4cGVjdGVkIGlkZW50aWZpZXIgZm9yIHByb3BlcnR5IGFjY2VzcyIsdC5zcGFuLmVuZCksbH0pLHM9dGhpcy5zb3VyY2VTcGFuKHIpO2lmKGkpdGhpcy5jb25zdW1lT3B0aW9uYWxBc3NpZ25tZW50KCk/KHRoaXMuZXJyb3IoIlRoZSAnPy4nIG9wZXJhdG9yIGNhbm5vdCBiZSB1c2VkIGluIHRoZSBhc3NpZ25tZW50IiksYT1uZXcgSWEodGhpcy5zcGFuKGUpLHRoaXMuc291cmNlU3BhbihlKSkpOmE9bmV3IExDKHRoaXMuc3BhbihlKSx0aGlzLnNvdXJjZVNwYW4oZSkscyx0LG8pO2Vsc2UgaWYodGhpcy5jb25zdW1lT3B0aW9uYWxBc3NpZ25tZW50KCkpe2lmKCEoMSZ0aGlzLnBhcnNlRmxhZ3MpKXJldHVybiB0aGlzLmVycm9yKCJCaW5kaW5ncyBjYW5ub3QgY29udGFpbiBhc3NpZ25tZW50cyIpLG5ldyBJYSh0aGlzLnNwYW4oZSksdGhpcy5zb3VyY2VTcGFuKGUpKTtsZXQgbD10aGlzLnBhcnNlQ29uZGl0aW9uYWwoKTthPW5ldyBOQyh0aGlzLnNwYW4oZSksdGhpcy5zb3VyY2VTcGFuKGUpLHMsdCxvLGwpfWVsc2UgYT1uZXcgTHUodGhpcy5zcGFuKGUpLHRoaXMuc291cmNlU3BhbihlKSxzLHQsbyk7cmV0dXJuIGF9cGFyc2VDYWxsKHQsZSxpKXtsZXQgcj10aGlzLmlucHV0SW5kZXg7dGhpcy5ycGFyZW5zRXhwZWN0ZWQrKztsZXQgbz10aGlzLnBhcnNlQ2FsbEFyZ3VtZW50cygpLHM9dGhpcy5zcGFuKHIsdGhpcy5pbnB1dEluZGV4KS50b0Fic29sdXRlKHRoaXMuYWJzb2x1dGVPZmZzZXQpO3RoaXMuZXhwZWN0Q2hhcmFjdGVyKDQxKSx0aGlzLnJwYXJlbnNFeHBlY3RlZC0tO2xldCBhPXRoaXMuc3BhbihlKSxsPXRoaXMuc291cmNlU3BhbihlKTtyZXR1cm4gaT9uZXcga18oYSxsLHQsbyxzKTpuZXcgYWgoYSxsLHQsbyxzKX1jb25zdW1lT3B0aW9uYWxBc3NpZ25tZW50KCl7cmV0dXJuIDImdGhpcy5wYXJzZUZsYWdzJiZ0aGlzLm5leHQuaXNPcGVyYXRvcigiISIpJiZ0aGlzLnBlZWsoMSkuaXNPcGVyYXRvcigiPSIpPyh0aGlzLmFkdmFuY2UoKSx0aGlzLmFkdmFuY2UoKSwhMCk6dGhpcy5jb25zdW1lT3B0aW9uYWxPcGVyYXRvcigiPSIpfXBhcnNlQ2FsbEFyZ3VtZW50cygpe2lmKHRoaXMubmV4dC5pc0NoYXJhY3Rlcig0MSkpcmV0dXJuW107bGV0IHQ9W107ZG97dC5wdXNoKHRoaXMucGFyc2VQaXBlKCkpfXdoaWxlKHRoaXMuY29uc3VtZU9wdGlvbmFsQ2hhcmFjdGVyKDQ0KSk7cmV0dXJuIHR9ZXhwZWN0VGVtcGxhdGVCaW5kaW5nS2V5KCl7bGV0IHQ9IiIsZT0hMSxpPXRoaXMuY3VycmVudEFic29sdXRlT2Zmc2V0O2Rve3QrPXRoaXMuZXhwZWN0SWRlbnRpZmllck9yS2V5d29yZE9yU3RyaW5nKCksZT10aGlzLmNvbnN1bWVPcHRpb25hbE9wZXJhdG9yKCItIiksZSYmKHQrPSItIil9d2hpbGUoZSk7cmV0dXJue3NvdXJjZTp0LHNwYW46bmV3IGFsKGksaSt0Lmxlbmd0aCl9fXBhcnNlVGVtcGxhdGVCaW5kaW5ncyh0KXtsZXQgZT1bXTtmb3IoZS5wdXNoKC4uLnRoaXMucGFyc2VEaXJlY3RpdmVLZXl3b3JkQmluZGluZ3ModCkpO3RoaXMuaW5kZXg8dGhpcy50b2tlbnMubGVuZ3RoOyl7bGV0IGk9dGhpcy5wYXJzZUxldEJpbmRpbmcoKTtpZihpKWUucHVzaChpKTtlbHNle2xldCByPXRoaXMuZXhwZWN0VGVtcGxhdGVCaW5kaW5nS2V5KCksbz10aGlzLnBhcnNlQXNCaW5kaW5nKHIpO28/ZS5wdXNoKG8pOihyLnNvdXJjZT10LnNvdXJjZStyLnNvdXJjZS5jaGFyQXQoMCkudG9VcHBlckNhc2UoKStyLnNvdXJjZS5zdWJzdHJpbmcoMSksZS5wdXNoKC4uLnRoaXMucGFyc2VEaXJlY3RpdmVLZXl3b3JkQmluZGluZ3MocikpKX10aGlzLmNvbnN1bWVTdGF0ZW1lbnRUZXJtaW5hdG9yKCl9cmV0dXJuIG5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy50ZW1wbGF0ZUJpbmRpbmdzPXQsdGhpcy53YXJuaW5ncz1lLHRoaXMuZXJyb3JzPWl9fShlLFtdLHRoaXMuZXJyb3JzKX1wYXJzZUtleWVkUmVhZE9yV3JpdGUodCxlLGkpe3JldHVybiB0aGlzLndpdGhDb250ZXh0KHNtLldyaXRhYmxlLCgpPT57dGhpcy5yYnJhY2tldHNFeHBlY3RlZCsrO2xldCByPXRoaXMucGFyc2VQaXBlKCk7aWYociBpbnN0YW5jZW9mIElhJiZ0aGlzLmVycm9yKCJLZXkgYWNjZXNzIGNhbm5vdCBiZSBlbXB0eSIpLHRoaXMucmJyYWNrZXRzRXhwZWN0ZWQtLSx0aGlzLmV4cGVjdENoYXJhY3Rlcig5MyksIXRoaXMuY29uc3VtZU9wdGlvbmFsT3BlcmF0b3IoIj0iKSlyZXR1cm4gaT9uZXcgUF8odGhpcy5zcGFuKGUpLHRoaXMuc291cmNlU3BhbihlKSx0LHIpOm5ldyBJXyh0aGlzLnNwYW4oZSksdGhpcy5zb3VyY2VTcGFuKGUpLHQscik7aWYoIWkpe2xldCBvPXRoaXMucGFyc2VDb25kaXRpb25hbCgpO3JldHVybiBuZXcgQkModGhpcy5zcGFuKGUpLHRoaXMuc291cmNlU3BhbihlKSx0LHIsbyl9cmV0dXJuIHRoaXMuZXJyb3IoIlRoZSAnPy4nIG9wZXJhdG9yIGNhbm5vdCBiZSB1c2VkIGluIHRoZSBhc3NpZ25tZW50IiksbmV3IElhKHRoaXMuc3BhbihlKSx0aGlzLnNvdXJjZVNwYW4oZSkpfSl9cGFyc2VEaXJlY3RpdmVLZXl3b3JkQmluZGluZ3ModCl7bGV0IGU9W107dGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoNTgpO2xldCBpPXRoaXMuZ2V0RGlyZWN0aXZlQm91bmRUYXJnZXQoKSxyPXRoaXMuY3VycmVudEFic29sdXRlT2Zmc2V0LG89dGhpcy5wYXJzZUFzQmluZGluZyh0KTtvfHwodGhpcy5jb25zdW1lU3RhdGVtZW50VGVybWluYXRvcigpLHI9dGhpcy5jdXJyZW50QWJzb2x1dGVPZmZzZXQpO2xldCBzPW5ldyBhbCh0LnNwYW4uc3RhcnQscik7cmV0dXJuIGUucHVzaChuZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3RoaXMuc291cmNlU3Bhbj10LHRoaXMua2V5PWUsdGhpcy52YWx1ZT1pfX0ocyx0LGkpKSxvJiZlLnB1c2gobyksZX1nZXREaXJlY3RpdmVCb3VuZFRhcmdldCgpe2lmKHRoaXMubmV4dD09PWRCfHx0aGlzLnBlZWtLZXl3b3JkQXMoKXx8dGhpcy5wZWVrS2V5d29yZExldCgpKXJldHVybiBudWxsO2xldCB0PXRoaXMucGFyc2VQaXBlKCkse3N0YXJ0OmUsZW5kOml9PXQuc3BhbixyPXRoaXMuaW5wdXQuc3Vic3RyaW5nKGUsaSk7cmV0dXJuIG5ldyBSdSh0LHIsdGhpcy5sb2NhdGlvbix0aGlzLmFic29sdXRlT2Zmc2V0K2UsdGhpcy5lcnJvcnMpfXBhcnNlQXNCaW5kaW5nKHQpe2lmKCF0aGlzLnBlZWtLZXl3b3JkQXMoKSlyZXR1cm4gbnVsbDt0aGlzLmFkdmFuY2UoKTtsZXQgZT10aGlzLmV4cGVjdFRlbXBsYXRlQmluZGluZ0tleSgpO3RoaXMuY29uc3VtZVN0YXRlbWVudFRlcm1pbmF0b3IoKTtsZXQgaT1uZXcgYWwodC5zcGFuLnN0YXJ0LHRoaXMuY3VycmVudEFic29sdXRlT2Zmc2V0KTtyZXR1cm4gbmV3IHpDKGksZSx0KX1wYXJzZUxldEJpbmRpbmcoKXtpZighdGhpcy5wZWVrS2V5d29yZExldCgpKXJldHVybiBudWxsO2xldCB0PXRoaXMuY3VycmVudEFic29sdXRlT2Zmc2V0O3RoaXMuYWR2YW5jZSgpO2xldCBlPXRoaXMuZXhwZWN0VGVtcGxhdGVCaW5kaW5nS2V5KCksaT1udWxsO3RoaXMuY29uc3VtZU9wdGlvbmFsT3BlcmF0b3IoIj0iKSYmKGk9dGhpcy5leHBlY3RUZW1wbGF0ZUJpbmRpbmdLZXkoKSksdGhpcy5jb25zdW1lU3RhdGVtZW50VGVybWluYXRvcigpO2xldCByPW5ldyBhbCh0LHRoaXMuY3VycmVudEFic29sdXRlT2Zmc2V0KTtyZXR1cm4gbmV3IHpDKHIsZSxpKX1jb25zdW1lU3RhdGVtZW50VGVybWluYXRvcigpe3RoaXMuY29uc3VtZU9wdGlvbmFsQ2hhcmFjdGVyKDU5KXx8dGhpcy5jb25zdW1lT3B0aW9uYWxDaGFyYWN0ZXIoNDQpfWVycm9yKHQsZT1udWxsKXt0aGlzLmVycm9ycy5wdXNoKG5ldyBSQyh0LHRoaXMuaW5wdXQsdGhpcy5sb2NhdGlvblRleHQoZSksdGhpcy5sb2NhdGlvbikpLHRoaXMuc2tpcCgpfWxvY2F0aW9uVGV4dCh0PW51bGwpe3JldHVybiBudWxsPT10JiYodD10aGlzLmluZGV4KSx0PHRoaXMudG9rZW5zLmxlbmd0aD9gYXQgY29sdW1uICR7dGhpcy50b2tlbnNbdF0uaW5kZXgrMX0gaW5gOiJhdCB0aGUgZW5kIG9mIHRoZSBleHByZXNzaW9uIn1fcmVwb3J0RXJyb3JGb3JQcml2YXRlSWRlbnRpZmllcih0LGUpe2xldCBpPWBQcml2YXRlIGlkZW50aWZpZXJzIGFyZSBub3Qgc3VwcG9ydGVkLiBVbmV4cGVjdGVkIHByaXZhdGUgaWRlbnRpZmllcjogJHt0fWA7bnVsbCE9PWUmJihpKz1gLCAke2V9YCksdGhpcy5lcnJvcihpKX1za2lwKCl7bGV0IHQ9dGhpcy5uZXh0O2Zvcig7dGhpcy5pbmRleDx0aGlzLnRva2Vucy5sZW5ndGgmJiF0LmlzQ2hhcmFjdGVyKDU5KSYmIXQuaXNPcGVyYXRvcigifCIpJiYodGhpcy5ycGFyZW5zRXhwZWN0ZWQ8PTB8fCF0LmlzQ2hhcmFjdGVyKDQxKSkmJih0aGlzLnJicmFjZXNFeHBlY3RlZDw9MHx8IXQuaXNDaGFyYWN0ZXIoT3UpKSYmKHRoaXMucmJyYWNrZXRzRXhwZWN0ZWQ8PTB8fCF0LmlzQ2hhcmFjdGVyKDkzKSkmJiEodGhpcy5jb250ZXh0JnNtLldyaXRhYmxlJiZ0LmlzT3BlcmF0b3IoIj0iKSk7KXRoaXMubmV4dC5pc0Vycm9yKCkmJnRoaXMuZXJyb3JzLnB1c2gobmV3IFJDKHRoaXMubmV4dC50b1N0cmluZygpLHRoaXMuaW5wdXQsdGhpcy5sb2NhdGlvblRleHQoKSx0aGlzLmxvY2F0aW9uKSksdGhpcy5hZHZhbmNlKCksdD10aGlzLm5leHR9fSxkVj1jbGFzcyBleHRlbmRzIFpCe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmVycm9ycz1bXX12aXNpdFBpcGUoKXt0aGlzLmVycm9ycy5wdXNoKCJwaXBlcyIpfX0sQ209Y2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLnNvdXJjZVNwYW49dCx0aGlzLmkxOG49ZX19LEZfPWNsYXNzIGV4dGVuZHMgQ217Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIoZSxyKSx0aGlzLnZhbHVlPXQsdGhpcy50b2tlbnM9aX12aXNpdCh0LGUpe3JldHVybiB0LnZpc2l0VGV4dCh0aGlzLGUpfX0sTl89Y2xhc3MgZXh0ZW5kcyBDbXtjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyl7c3VwZXIocixzKSx0aGlzLnN3aXRjaFZhbHVlPXQsdGhpcy50eXBlPWUsdGhpcy5jYXNlcz1pLHRoaXMuc3dpdGNoVmFsdWVTb3VyY2VTcGFuPW99dmlzaXQodCxlKXtyZXR1cm4gdC52aXNpdEV4cGFuc2lvbih0aGlzLGUpfX0saFY9Y2xhc3MgZXh0ZW5kcyBDbXtjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyxhKXtzdXBlcihpLGEpLHRoaXMubmFtZT10LHRoaXMudmFsdWU9ZSx0aGlzLmtleVNwYW49cix0aGlzLnZhbHVlU3Bhbj1vLHRoaXMudmFsdWVUb2tlbnM9c312aXNpdCh0LGUpe3JldHVybiB0LnZpc2l0QXR0cmlidXRlKHRoaXMsZSl9fSxxQz1jbGFzcyBleHRlbmRzIENte2NvbnN0cnVjdG9yKHQsZSxpLHIsbyxzPW51bGwsYSl7c3VwZXIocixhKSx0aGlzLm5hbWU9dCx0aGlzLmF0dHJzPWUsdGhpcy5jaGlsZHJlbj1pLHRoaXMuc3RhcnRTb3VyY2VTcGFuPW8sdGhpcy5lbmRTb3VyY2VTcGFuPXN9dmlzaXQodCxlKXtyZXR1cm4gdC52aXNpdEVsZW1lbnQodGhpcyxlKX19LHhEPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy52YWx1ZT10LHRoaXMuc291cmNlU3Bhbj1lfXZpc2l0KHQsZSl7cmV0dXJuIHQudmlzaXRDb21tZW50KHRoaXMsZSl9fTtmdW5jdGlvbiBVdShuLHQsZT1udWxsKXtsZXQgaT1bXSxyPW4udmlzaXQ/bz0+bi52aXNpdChvLGUpfHxvLnZpc2l0KG4sZSk6bz0+by52aXNpdChuLGUpO3JldHVybiB0LmZvckVhY2gobz0+e2xldCBzPXIobyk7cyYmaS5wdXNoKHMpfSksaX12YXIgQ0Q9e0FFbGlnOiJceGM2IixBTVA6IiYiLGFtcDoiJiIsQWFjdXRlOiJceGMxIixBYnJldmU6Ilx1MDEwMiIsQWNpcmM6Ilx4YzIiLEFjeToiXHUwNDEwIixBZnI6Ilx1ZDgzNVx1ZGQwNCIsQWdyYXZlOiJceGMwIixBbHBoYToiXHUwMzkxIixBbWFjcjoiXHUwMTAwIixBbmQ6Ilx1MmE1MyIsQW9nb246Ilx1MDEwNCIsQW9wZjoiXHVkODM1XHVkZDM4IixBcHBseUZ1bmN0aW9uOiJcdTIwNjEiLGFmOiJcdTIwNjEiLEFyaW5nOiJceGM1IixhbmdzdDoiXHhjNSIsQXNjcjoiXHVkODM1XHVkYzljIixBc3NpZ246Ilx1MjI1NCIsY29sb25lOiJcdTIyNTQiLGNvbG9uZXE6Ilx1MjI1NCIsQXRpbGRlOiJceGMzIixBdW1sOiJceGM0IixCYWNrc2xhc2g6Ilx1MjIxNiIsc2V0bWludXM6Ilx1MjIxNiIsc2V0bW46Ilx1MjIxNiIsc21hbGxzZXRtaW51czoiXHUyMjE2Iixzc2V0bW46Ilx1MjIxNiIsQmFydjoiXHUyYWU3IixCYXJ3ZWQ6Ilx1MjMwNiIsZG91YmxlYmFyd2VkZ2U6Ilx1MjMwNiIsQmN5OiJcdTA0MTEiLEJlY2F1c2U6Ilx1MjIzNSIsYmVjYXVzOiJcdTIyMzUiLGJlY2F1c2U6Ilx1MjIzNSIsQmVybm91bGxpczoiXHUyMTJjIixCc2NyOiJcdTIxMmMiLGJlcm5vdToiXHUyMTJjIixCZXRhOiJcdTAzOTIiLEJmcjoiXHVkODM1XHVkZDA1IixCb3BmOiJcdWQ4MzVcdWRkMzkiLEJyZXZlOiJcdTAyZDgiLGJyZXZlOiJcdTAyZDgiLEJ1bXBlcToiXHUyMjRlIixIdW1wRG93bkh1bXA6Ilx1MjI0ZSIsYnVtcDoiXHUyMjRlIixDSGN5OiJcdTA0MjciLENPUFk6Ilx4YTkiLGNvcHk6Ilx4YTkiLENhY3V0ZToiXHUwMTA2IixDYXA6Ilx1MjJkMiIsQ2FwaXRhbERpZmZlcmVudGlhbEQ6Ilx1MjE0NSIsREQ6Ilx1MjE0NSIsQ2F5bGV5czoiXHUyMTJkIixDZnI6Ilx1MjEyZCIsQ2Nhcm9uOiJcdTAxMGMiLENjZWRpbDoiXHhjNyIsQ2NpcmM6Ilx1MDEwOCIsQ2NvbmludDoiXHUyMjMwIixDZG90OiJcdTAxMGEiLENlZGlsbGE6Ilx4YjgiLGNlZGlsOiJceGI4IixDZW50ZXJEb3Q6Ilx4YjciLGNlbnRlcmRvdDoiXHhiNyIsbWlkZG90OiJceGI3IixDaGk6Ilx1MDNhNyIsQ2lyY2xlRG90OiJcdTIyOTkiLG9kb3Q6Ilx1MjI5OSIsQ2lyY2xlTWludXM6Ilx1MjI5NiIsb21pbnVzOiJcdTIyOTYiLENpcmNsZVBsdXM6Ilx1MjI5NSIsb3BsdXM6Ilx1MjI5NSIsQ2lyY2xlVGltZXM6Ilx1MjI5NyIsb3RpbWVzOiJcdTIyOTciLENsb2Nrd2lzZUNvbnRvdXJJbnRlZ3JhbDoiXHUyMjMyIixjd2NvbmludDoiXHUyMjMyIixDbG9zZUN1cmx5RG91YmxlUXVvdGU6Ilx1MjAxZCIscmRxdW86Ilx1MjAxZCIscmRxdW9yOiJcdTIwMWQiLENsb3NlQ3VybHlRdW90ZToiXHUyMDE5Iixyc3F1bzoiXHUyMDE5Iixyc3F1b3I6Ilx1MjAxOSIsQ29sb246Ilx1MjIzNyIsUHJvcG9ydGlvbjoiXHUyMjM3IixDb2xvbmU6Ilx1MmE3NCIsQ29uZ3J1ZW50OiJcdTIyNjEiLGVxdWl2OiJcdTIyNjEiLENvbmludDoiXHUyMjJmIixEb3VibGVDb250b3VySW50ZWdyYWw6Ilx1MjIyZiIsQ29udG91ckludGVncmFsOiJcdTIyMmUiLGNvbmludDoiXHUyMjJlIixvaW50OiJcdTIyMmUiLENvcGY6Ilx1MjEwMiIsY29tcGxleGVzOiJcdTIxMDIiLENvcHJvZHVjdDoiXHUyMjEwIixjb3Byb2Q6Ilx1MjIxMCIsQ291bnRlckNsb2Nrd2lzZUNvbnRvdXJJbnRlZ3JhbDoiXHUyMjMzIixhd2NvbmludDoiXHUyMjMzIixDcm9zczoiXHUyYTJmIixDc2NyOiJcdWQ4MzVcdWRjOWUiLEN1cDoiXHUyMmQzIixDdXBDYXA6Ilx1MjI0ZCIsYXN5bXBlcToiXHUyMjRkIixERG90cmFoZDoiXHUyOTExIixESmN5OiJcdTA0MDIiLERTY3k6Ilx1MDQwNSIsRFpjeToiXHUwNDBmIixEYWdnZXI6Ilx1MjAyMSIsZGRhZ2dlcjoiXHUyMDIxIixEYXJyOiJcdTIxYTEiLERhc2h2OiJcdTJhZTQiLERvdWJsZUxlZnRUZWU6Ilx1MmFlNCIsRGNhcm9uOiJcdTAxMGUiLERjeToiXHUwNDE0IixEZWw6Ilx1MjIwNyIsbmFibGE6Ilx1MjIwNyIsRGVsdGE6Ilx1MDM5NCIsRGZyOiJcdWQ4MzVcdWRkMDciLERpYWNyaXRpY2FsQWN1dGU6Ilx4YjQiLGFjdXRlOiJceGI0IixEaWFjcml0aWNhbERvdDoiXHUwMmQ5Iixkb3Q6Ilx1MDJkOSIsRGlhY3JpdGljYWxEb3VibGVBY3V0ZToiXHUwMmRkIixkYmxhYzoiXHUwMmRkIixEaWFjcml0aWNhbEdyYXZlOiJgIixncmF2ZToiYCIsRGlhY3JpdGljYWxUaWxkZToiXHUwMmRjIix0aWxkZToiXHUwMmRjIixEaWFtb25kOiJcdTIyYzQiLGRpYW06Ilx1MjJjNCIsZGlhbW9uZDoiXHUyMmM0IixEaWZmZXJlbnRpYWxEOiJcdTIxNDYiLGRkOiJcdTIxNDYiLERvcGY6Ilx1ZDgzNVx1ZGQzYiIsRG90OiJceGE4IixEb3VibGVEb3Q6Ilx4YTgiLGRpZToiXHhhOCIsdW1sOiJceGE4IixEb3REb3Q6Ilx1MjBkYyIsRG90RXF1YWw6Ilx1MjI1MCIsZG90ZXE6Ilx1MjI1MCIsZXNkb3Q6Ilx1MjI1MCIsRG91YmxlRG93bkFycm93OiJcdTIxZDMiLERvd25hcnJvdzoiXHUyMWQzIixkQXJyOiJcdTIxZDMiLERvdWJsZUxlZnRBcnJvdzoiXHUyMWQwIixMZWZ0YXJyb3c6Ilx1MjFkMCIsbEFycjoiXHUyMWQwIixEb3VibGVMZWZ0UmlnaHRBcnJvdzoiXHUyMWQ0IixMZWZ0cmlnaHRhcnJvdzoiXHUyMWQ0IixoQXJyOiJcdTIxZDQiLGlmZjoiXHUyMWQ0IixEb3VibGVMb25nTGVmdEFycm93OiJcdTI3ZjgiLExvbmdsZWZ0YXJyb3c6Ilx1MjdmOCIseGxBcnI6Ilx1MjdmOCIsRG91YmxlTG9uZ0xlZnRSaWdodEFycm93OiJcdTI3ZmEiLExvbmdsZWZ0cmlnaHRhcnJvdzoiXHUyN2ZhIix4aEFycjoiXHUyN2ZhIixEb3VibGVMb25nUmlnaHRBcnJvdzoiXHUyN2Y5IixMb25ncmlnaHRhcnJvdzoiXHUyN2Y5Iix4ckFycjoiXHUyN2Y5IixEb3VibGVSaWdodEFycm93OiJcdTIxZDIiLEltcGxpZXM6Ilx1MjFkMiIsUmlnaHRhcnJvdzoiXHUyMWQyIixyQXJyOiJcdTIxZDIiLERvdWJsZVJpZ2h0VGVlOiJcdTIyYTgiLHZEYXNoOiJcdTIyYTgiLERvdWJsZVVwQXJyb3c6Ilx1MjFkMSIsVXBhcnJvdzoiXHUyMWQxIix1QXJyOiJcdTIxZDEiLERvdWJsZVVwRG93bkFycm93OiJcdTIxZDUiLFVwZG93bmFycm93OiJcdTIxZDUiLHZBcnI6Ilx1MjFkNSIsRG91YmxlVmVydGljYWxCYXI6Ilx1MjIyNSIscGFyOiJcdTIyMjUiLHBhcmFsbGVsOiJcdTIyMjUiLHNob3J0cGFyYWxsZWw6Ilx1MjIyNSIsc3BhcjoiXHUyMjI1IixEb3duQXJyb3c6Ilx1MjE5MyIsU2hvcnREb3duQXJyb3c6Ilx1MjE5MyIsZGFycjoiXHUyMTkzIixkb3duYXJyb3c6Ilx1MjE5MyIsRG93bkFycm93QmFyOiJcdTI5MTMiLERvd25BcnJvd1VwQXJyb3c6Ilx1MjFmNSIsZHVhcnI6Ilx1MjFmNSIsRG93bkJyZXZlOiJcdTAzMTEiLERvd25MZWZ0UmlnaHRWZWN0b3I6Ilx1Mjk1MCIsRG93bkxlZnRUZWVWZWN0b3I6Ilx1Mjk1ZSIsRG93bkxlZnRWZWN0b3I6Ilx1MjFiZCIsbGVmdGhhcnBvb25kb3duOiJcdTIxYmQiLGxoYXJkOiJcdTIxYmQiLERvd25MZWZ0VmVjdG9yQmFyOiJcdTI5NTYiLERvd25SaWdodFRlZVZlY3RvcjoiXHUyOTVmIixEb3duUmlnaHRWZWN0b3I6Ilx1MjFjMSIscmhhcmQ6Ilx1MjFjMSIscmlnaHRoYXJwb29uZG93bjoiXHUyMWMxIixEb3duUmlnaHRWZWN0b3JCYXI6Ilx1Mjk1NyIsRG93blRlZToiXHUyMmE0Iix0b3A6Ilx1MjJhNCIsRG93blRlZUFycm93OiJcdTIxYTciLG1hcHN0b2Rvd246Ilx1MjFhNyIsRHNjcjoiXHVkODM1XHVkYzlmIixEc3Ryb2s6Ilx1MDExMCIsRU5HOiJcdTAxNGEiLEVUSDoiXHhkMCIsRWFjdXRlOiJceGM5IixFY2Fyb246Ilx1MDExYSIsRWNpcmM6Ilx4Y2EiLEVjeToiXHUwNDJkIixFZG90OiJcdTAxMTYiLEVmcjoiXHVkODM1XHVkZDA4IixFZ3JhdmU6Ilx4YzgiLEVsZW1lbnQ6Ilx1MjIwOCIsaW46Ilx1MjIwOCIsaXNpbjoiXHUyMjA4Iixpc2ludjoiXHUyMjA4IixFbWFjcjoiXHUwMTEyIixFbXB0eVNtYWxsU3F1YXJlOiJcdTI1ZmIiLEVtcHR5VmVyeVNtYWxsU3F1YXJlOiJcdTI1YWIiLEVvZ29uOiJcdTAxMTgiLEVvcGY6Ilx1ZDgzNVx1ZGQzYyIsRXBzaWxvbjoiXHUwMzk1IixFcXVhbDoiXHUyYTc1IixFcXVhbFRpbGRlOiJcdTIyNDIiLGVxc2ltOiJcdTIyNDIiLGVzaW06Ilx1MjI0MiIsRXF1aWxpYnJpdW06Ilx1MjFjYyIscmlnaHRsZWZ0aGFycG9vbnM6Ilx1MjFjYyIscmxoYXI6Ilx1MjFjYyIsRXNjcjoiXHUyMTMwIixleHBlY3RhdGlvbjoiXHUyMTMwIixFc2ltOiJcdTJhNzMiLEV0YToiXHUwMzk3IixFdW1sOiJceGNiIixFeGlzdHM6Ilx1MjIwMyIsZXhpc3Q6Ilx1MjIwMyIsRXhwb25lbnRpYWxFOiJcdTIxNDciLGVlOiJcdTIxNDciLGV4cG9uZW50aWFsZToiXHUyMTQ3IixGY3k6Ilx1MDQyNCIsRmZyOiJcdWQ4MzVcdWRkMDkiLEZpbGxlZFNtYWxsU3F1YXJlOiJcdTI1ZmMiLEZpbGxlZFZlcnlTbWFsbFNxdWFyZToiXHUyNWFhIixibGFja3NxdWFyZToiXHUyNWFhIixzcXVhcmY6Ilx1MjVhYSIsc3F1ZjoiXHUyNWFhIixGb3BmOiJcdWQ4MzVcdWRkM2QiLEZvckFsbDoiXHUyMjAwIixmb3JhbGw6Ilx1MjIwMCIsRm91cmllcnRyZjoiXHUyMTMxIixGc2NyOiJcdTIxMzEiLEdKY3k6Ilx1MDQwMyIsR1Q6Ij4iLGd0OiI+IixHYW1tYToiXHUwMzkzIixHYW1tYWQ6Ilx1MDNkYyIsR2JyZXZlOiJcdTAxMWUiLEdjZWRpbDoiXHUwMTIyIixHY2lyYzoiXHUwMTFjIixHY3k6Ilx1MDQxMyIsR2RvdDoiXHUwMTIwIixHZnI6Ilx1ZDgzNVx1ZGQwYSIsR2c6Ilx1MjJkOSIsZ2dnOiJcdTIyZDkiLEdvcGY6Ilx1ZDgzNVx1ZGQzZSIsR3JlYXRlckVxdWFsOiJcdTIyNjUiLGdlOiJcdTIyNjUiLGdlcToiXHUyMjY1IixHcmVhdGVyRXF1YWxMZXNzOiJcdTIyZGIiLGdlbDoiXHUyMmRiIixndHJlcWxlc3M6Ilx1MjJkYiIsR3JlYXRlckZ1bGxFcXVhbDoiXHUyMjY3IixnRToiXHUyMjY3IixnZXFxOiJcdTIyNjciLEdyZWF0ZXJHcmVhdGVyOiJcdTJhYTIiLEdyZWF0ZXJMZXNzOiJcdTIyNzciLGdsOiJcdTIyNzciLGd0cmxlc3M6Ilx1MjI3NyIsR3JlYXRlclNsYW50RXF1YWw6Ilx1MmE3ZSIsZ2Vxc2xhbnQ6Ilx1MmE3ZSIsZ2VzOiJcdTJhN2UiLEdyZWF0ZXJUaWxkZToiXHUyMjczIixnc2ltOiJcdTIyNzMiLGd0cnNpbToiXHUyMjczIixHc2NyOiJcdWQ4MzVcdWRjYTIiLEd0OiJcdTIyNmIiLE5lc3RlZEdyZWF0ZXJHcmVhdGVyOiJcdTIyNmIiLGdnOiJcdTIyNmIiLEhBUkRjeToiXHUwNDJhIixIYWNlazoiXHUwMmM3IixjYXJvbjoiXHUwMmM3IixIYXQ6Il4iLEhjaXJjOiJcdTAxMjQiLEhmcjoiXHUyMTBjIixQb2luY2FyZXBsYW5lOiJcdTIxMGMiLEhpbGJlcnRTcGFjZToiXHUyMTBiIixIc2NyOiJcdTIxMGIiLGhhbWlsdDoiXHUyMTBiIixIb3BmOiJcdTIxMGQiLHF1YXRlcm5pb25zOiJcdTIxMGQiLEhvcml6b250YWxMaW5lOiJcdTI1MDAiLGJveGg6Ilx1MjUwMCIsSHN0cm9rOiJcdTAxMjYiLEh1bXBFcXVhbDoiXHUyMjRmIixidW1wZToiXHUyMjRmIixidW1wZXE6Ilx1MjI0ZiIsSUVjeToiXHUwNDE1IixJSmxpZzoiXHUwMTMyIixJT2N5OiJcdTA0MDEiLElhY3V0ZToiXHhjZCIsSWNpcmM6Ilx4Y2UiLEljeToiXHUwNDE4IixJZG90OiJcdTAxMzAiLElmcjoiXHUyMTExIixJbToiXHUyMTExIixpbWFnZToiXHUyMTExIixpbWFncGFydDoiXHUyMTExIixJZ3JhdmU6Ilx4Y2MiLEltYWNyOiJcdTAxMmEiLEltYWdpbmFyeUk6Ilx1MjE0OCIsaWk6Ilx1MjE0OCIsSW50OiJcdTIyMmMiLEludGVncmFsOiJcdTIyMmIiLGludDoiXHUyMjJiIixJbnRlcnNlY3Rpb246Ilx1MjJjMiIsYmlnY2FwOiJcdTIyYzIiLHhjYXA6Ilx1MjJjMiIsSW52aXNpYmxlQ29tbWE6Ilx1MjA2MyIsaWM6Ilx1MjA2MyIsSW52aXNpYmxlVGltZXM6Ilx1MjA2MiIsaXQ6Ilx1MjA2MiIsSW9nb246Ilx1MDEyZSIsSW9wZjoiXHVkODM1XHVkZDQwIixJb3RhOiJcdTAzOTkiLElzY3I6Ilx1MjExMCIsaW1hZ2xpbmU6Ilx1MjExMCIsSXRpbGRlOiJcdTAxMjgiLEl1a2N5OiJcdTA0MDYiLEl1bWw6Ilx4Y2YiLEpjaXJjOiJcdTAxMzQiLEpjeToiXHUwNDE5IixKZnI6Ilx1ZDgzNVx1ZGQwZCIsSm9wZjoiXHVkODM1XHVkZDQxIixKc2NyOiJcdWQ4MzVcdWRjYTUiLEpzZXJjeToiXHUwNDA4IixKdWtjeToiXHUwNDA0IixLSGN5OiJcdTA0MjUiLEtKY3k6Ilx1MDQwYyIsS2FwcGE6Ilx1MDM5YSIsS2NlZGlsOiJcdTAxMzYiLEtjeToiXHUwNDFhIixLZnI6Ilx1ZDgzNVx1ZGQwZSIsS29wZjoiXHVkODM1XHVkZDQyIixLc2NyOiJcdWQ4MzVcdWRjYTYiLExKY3k6Ilx1MDQwOSIsTFQ6IjwiLGx0OiI8IixMYWN1dGU6Ilx1MDEzOSIsTGFtYmRhOiJcdTAzOWIiLExhbmc6Ilx1MjdlYSIsTGFwbGFjZXRyZjoiXHUyMTEyIixMc2NyOiJcdTIxMTIiLGxhZ3JhbjoiXHUyMTEyIixMYXJyOiJcdTIxOWUiLHR3b2hlYWRsZWZ0YXJyb3c6Ilx1MjE5ZSIsTGNhcm9uOiJcdTAxM2QiLExjZWRpbDoiXHUwMTNiIixMY3k6Ilx1MDQxYiIsTGVmdEFuZ2xlQnJhY2tldDoiXHUyN2U4IixsYW5nOiJcdTI3ZTgiLGxhbmdsZToiXHUyN2U4IixMZWZ0QXJyb3c6Ilx1MjE5MCIsU2hvcnRMZWZ0QXJyb3c6Ilx1MjE5MCIsbGFycjoiXHUyMTkwIixsZWZ0YXJyb3c6Ilx1MjE5MCIsc2xhcnI6Ilx1MjE5MCIsTGVmdEFycm93QmFyOiJcdTIxZTQiLGxhcnJiOiJcdTIxZTQiLExlZnRBcnJvd1JpZ2h0QXJyb3c6Ilx1MjFjNiIsbGVmdHJpZ2h0YXJyb3dzOiJcdTIxYzYiLGxyYXJyOiJcdTIxYzYiLExlZnRDZWlsaW5nOiJcdTIzMDgiLGxjZWlsOiJcdTIzMDgiLExlZnREb3VibGVCcmFja2V0OiJcdTI3ZTYiLGxvYnJrOiJcdTI3ZTYiLExlZnREb3duVGVlVmVjdG9yOiJcdTI5NjEiLExlZnREb3duVmVjdG9yOiJcdTIxYzMiLGRoYXJsOiJcdTIxYzMiLGRvd25oYXJwb29ubGVmdDoiXHUyMWMzIixMZWZ0RG93blZlY3RvckJhcjoiXHUyOTU5IixMZWZ0Rmxvb3I6Ilx1MjMwYSIsbGZsb29yOiJcdTIzMGEiLExlZnRSaWdodEFycm93OiJcdTIxOTQiLGhhcnI6Ilx1MjE5NCIsbGVmdHJpZ2h0YXJyb3c6Ilx1MjE5NCIsTGVmdFJpZ2h0VmVjdG9yOiJcdTI5NGUiLExlZnRUZWU6Ilx1MjJhMyIsZGFzaHY6Ilx1MjJhMyIsTGVmdFRlZUFycm93OiJcdTIxYTQiLG1hcHN0b2xlZnQ6Ilx1MjFhNCIsTGVmdFRlZVZlY3RvcjoiXHUyOTVhIixMZWZ0VHJpYW5nbGU6Ilx1MjJiMiIsdmFydHJpYW5nbGVsZWZ0OiJcdTIyYjIiLHZsdHJpOiJcdTIyYjIiLExlZnRUcmlhbmdsZUJhcjoiXHUyOWNmIixMZWZ0VHJpYW5nbGVFcXVhbDoiXHUyMmI0IixsdHJpZToiXHUyMmI0Iix0cmlhbmdsZWxlZnRlcToiXHUyMmI0IixMZWZ0VXBEb3duVmVjdG9yOiJcdTI5NTEiLExlZnRVcFRlZVZlY3RvcjoiXHUyOTYwIixMZWZ0VXBWZWN0b3I6Ilx1MjFiZiIsdWhhcmw6Ilx1MjFiZiIsdXBoYXJwb29ubGVmdDoiXHUyMWJmIixMZWZ0VXBWZWN0b3JCYXI6Ilx1Mjk1OCIsTGVmdFZlY3RvcjoiXHUyMWJjIixsZWZ0aGFycG9vbnVwOiJcdTIxYmMiLGxoYXJ1OiJcdTIxYmMiLExlZnRWZWN0b3JCYXI6Ilx1Mjk1MiIsTGVzc0VxdWFsR3JlYXRlcjoiXHUyMmRhIixsZWc6Ilx1MjJkYSIsbGVzc2VxZ3RyOiJcdTIyZGEiLExlc3NGdWxsRXF1YWw6Ilx1MjI2NiIsbEU6Ilx1MjI2NiIsbGVxcToiXHUyMjY2IixMZXNzR3JlYXRlcjoiXHUyMjc2IixsZXNzZ3RyOiJcdTIyNzYiLGxnOiJcdTIyNzYiLExlc3NMZXNzOiJcdTJhYTEiLExlc3NTbGFudEVxdWFsOiJcdTJhN2QiLGxlcXNsYW50OiJcdTJhN2QiLGxlczoiXHUyYTdkIixMZXNzVGlsZGU6Ilx1MjI3MiIsbGVzc3NpbToiXHUyMjcyIixsc2ltOiJcdTIyNzIiLExmcjoiXHVkODM1XHVkZDBmIixMbDoiXHUyMmQ4IixMbGVmdGFycm93OiJcdTIxZGEiLGxBYXJyOiJcdTIxZGEiLExtaWRvdDoiXHUwMTNmIixMb25nTGVmdEFycm93OiJcdTI3ZjUiLGxvbmdsZWZ0YXJyb3c6Ilx1MjdmNSIseGxhcnI6Ilx1MjdmNSIsTG9uZ0xlZnRSaWdodEFycm93OiJcdTI3ZjciLGxvbmdsZWZ0cmlnaHRhcnJvdzoiXHUyN2Y3Iix4aGFycjoiXHUyN2Y3IixMb25nUmlnaHRBcnJvdzoiXHUyN2Y2Iixsb25ncmlnaHRhcnJvdzoiXHUyN2Y2Iix4cmFycjoiXHUyN2Y2IixMb3BmOiJcdWQ4MzVcdWRkNDMiLExvd2VyTGVmdEFycm93OiJcdTIxOTkiLHN3YXJyOiJcdTIxOTkiLHN3YXJyb3c6Ilx1MjE5OSIsTG93ZXJSaWdodEFycm93OiJcdTIxOTgiLHNlYXJyOiJcdTIxOTgiLHNlYXJyb3c6Ilx1MjE5OCIsTHNoOiJcdTIxYjAiLGxzaDoiXHUyMWIwIixMc3Ryb2s6Ilx1MDE0MSIsTHQ6Ilx1MjI2YSIsTmVzdGVkTGVzc0xlc3M6Ilx1MjI2YSIsbGw6Ilx1MjI2YSIsTWFwOiJcdTI5MDUiLE1jeToiXHUwNDFjIixNZWRpdW1TcGFjZToiXHUyMDVmIixNZWxsaW50cmY6Ilx1MjEzMyIsTXNjcjoiXHUyMTMzIixwaG1tYXQ6Ilx1MjEzMyIsTWZyOiJcdWQ4MzVcdWRkMTAiLE1pbnVzUGx1czoiXHUyMjEzIixtbnBsdXM6Ilx1MjIxMyIsbXA6Ilx1MjIxMyIsTW9wZjoiXHVkODM1XHVkZDQ0IixNdToiXHUwMzljIixOSmN5OiJcdTA0MGEiLE5hY3V0ZToiXHUwMTQzIixOY2Fyb246Ilx1MDE0NyIsTmNlZGlsOiJcdTAxNDUiLE5jeToiXHUwNDFkIixOZWdhdGl2ZU1lZGl1bVNwYWNlOiJcdTIwMGIiLE5lZ2F0aXZlVGhpY2tTcGFjZToiXHUyMDBiIixOZWdhdGl2ZVRoaW5TcGFjZToiXHUyMDBiIixOZWdhdGl2ZVZlcnlUaGluU3BhY2U6Ilx1MjAwYiIsWmVyb1dpZHRoU3BhY2U6Ilx1MjAwYiIsTmV3TGluZToiXG4iLE5mcjoiXHVkODM1XHVkZDExIixOb0JyZWFrOiJcdTIwNjAiLE5vbkJyZWFraW5nU3BhY2U6Ilx4YTAiLG5ic3A6Ilx4YTAiLE5vcGY6Ilx1MjExNSIsbmF0dXJhbHM6Ilx1MjExNSIsTm90OiJcdTJhZWMiLE5vdENvbmdydWVudDoiXHUyMjYyIixuZXF1aXY6Ilx1MjI2MiIsTm90Q3VwQ2FwOiJcdTIyNmQiLE5vdERvdWJsZVZlcnRpY2FsQmFyOiJcdTIyMjYiLG5wYXI6Ilx1MjIyNiIsbnBhcmFsbGVsOiJcdTIyMjYiLG5zaG9ydHBhcmFsbGVsOiJcdTIyMjYiLG5zcGFyOiJcdTIyMjYiLE5vdEVsZW1lbnQ6Ilx1MjIwOSIsbm90aW46Ilx1MjIwOSIsbm90aW52YToiXHUyMjA5IixOb3RFcXVhbDoiXHUyMjYwIixuZToiXHUyMjYwIixOb3RFcXVhbFRpbGRlOiJcdTIyNDJcdTAzMzgiLG5lc2ltOiJcdTIyNDJcdTAzMzgiLE5vdEV4aXN0czoiXHUyMjA0IixuZXhpc3Q6Ilx1MjIwNCIsbmV4aXN0czoiXHUyMjA0IixOb3RHcmVhdGVyOiJcdTIyNmYiLG5ndDoiXHUyMjZmIixuZ3RyOiJcdTIyNmYiLE5vdEdyZWF0ZXJFcXVhbDoiXHUyMjcxIixuZ2U6Ilx1MjI3MSIsbmdlcToiXHUyMjcxIixOb3RHcmVhdGVyRnVsbEVxdWFsOiJcdTIyNjdcdTAzMzgiLG5nRToiXHUyMjY3XHUwMzM4IixuZ2VxcToiXHUyMjY3XHUwMzM4IixOb3RHcmVhdGVyR3JlYXRlcjoiXHUyMjZiXHUwMzM4IixuR3R2OiJcdTIyNmJcdTAzMzgiLE5vdEdyZWF0ZXJMZXNzOiJcdTIyNzkiLG50Z2w6Ilx1MjI3OSIsTm90R3JlYXRlclNsYW50RXF1YWw6Ilx1MmE3ZVx1MDMzOCIsbmdlcXNsYW50OiJcdTJhN2VcdTAzMzgiLG5nZXM6Ilx1MmE3ZVx1MDMzOCIsTm90R3JlYXRlclRpbGRlOiJcdTIyNzUiLG5nc2ltOiJcdTIyNzUiLE5vdEh1bXBEb3duSHVtcDoiXHUyMjRlXHUwMzM4IixuYnVtcDoiXHUyMjRlXHUwMzM4IixOb3RIdW1wRXF1YWw6Ilx1MjI0Zlx1MDMzOCIsbmJ1bXBlOiJcdTIyNGZcdTAzMzgiLE5vdExlZnRUcmlhbmdsZToiXHUyMmVhIixubHRyaToiXHUyMmVhIixudHJpYW5nbGVsZWZ0OiJcdTIyZWEiLE5vdExlZnRUcmlhbmdsZUJhcjoiXHUyOWNmXHUwMzM4IixOb3RMZWZ0VHJpYW5nbGVFcXVhbDoiXHUyMmVjIixubHRyaWU6Ilx1MjJlYyIsbnRyaWFuZ2xlbGVmdGVxOiJcdTIyZWMiLE5vdExlc3M6Ilx1MjI2ZSIsbmxlc3M6Ilx1MjI2ZSIsbmx0OiJcdTIyNmUiLE5vdExlc3NFcXVhbDoiXHUyMjcwIixubGU6Ilx1MjI3MCIsbmxlcToiXHUyMjcwIixOb3RMZXNzR3JlYXRlcjoiXHUyMjc4IixudGxnOiJcdTIyNzgiLE5vdExlc3NMZXNzOiJcdTIyNmFcdTAzMzgiLG5MdHY6Ilx1MjI2YVx1MDMzOCIsTm90TGVzc1NsYW50RXF1YWw6Ilx1MmE3ZFx1MDMzOCIsbmxlcXNsYW50OiJcdTJhN2RcdTAzMzgiLG5sZXM6Ilx1MmE3ZFx1MDMzOCIsTm90TGVzc1RpbGRlOiJcdTIyNzQiLG5sc2ltOiJcdTIyNzQiLE5vdE5lc3RlZEdyZWF0ZXJHcmVhdGVyOiJcdTJhYTJcdTAzMzgiLE5vdE5lc3RlZExlc3NMZXNzOiJcdTJhYTFcdTAzMzgiLE5vdFByZWNlZGVzOiJcdTIyODAiLG5wcjoiXHUyMjgwIixucHJlYzoiXHUyMjgwIixOb3RQcmVjZWRlc0VxdWFsOiJcdTJhYWZcdTAzMzgiLG5wcmU6Ilx1MmFhZlx1MDMzOCIsbnByZWNlcToiXHUyYWFmXHUwMzM4IixOb3RQcmVjZWRlc1NsYW50RXF1YWw6Ilx1MjJlMCIsbnByY3VlOiJcdTIyZTAiLE5vdFJldmVyc2VFbGVtZW50OiJcdTIyMGMiLG5vdG5pOiJcdTIyMGMiLG5vdG5pdmE6Ilx1MjIwYyIsTm90UmlnaHRUcmlhbmdsZToiXHUyMmViIixucnRyaToiXHUyMmViIixudHJpYW5nbGVyaWdodDoiXHUyMmViIixOb3RSaWdodFRyaWFuZ2xlQmFyOiJcdTI5ZDBcdTAzMzgiLE5vdFJpZ2h0VHJpYW5nbGVFcXVhbDoiXHUyMmVkIixucnRyaWU6Ilx1MjJlZCIsbnRyaWFuZ2xlcmlnaHRlcToiXHUyMmVkIixOb3RTcXVhcmVTdWJzZXQ6Ilx1MjI4Zlx1MDMzOCIsTm90U3F1YXJlU3Vic2V0RXF1YWw6Ilx1MjJlMiIsbnNxc3ViZToiXHUyMmUyIixOb3RTcXVhcmVTdXBlcnNldDoiXHUyMjkwXHUwMzM4IixOb3RTcXVhcmVTdXBlcnNldEVxdWFsOiJcdTIyZTMiLG5zcXN1cGU6Ilx1MjJlMyIsTm90U3Vic2V0OiJcdTIyODJcdTIwZDIiLG5zdWJzZXQ6Ilx1MjI4Mlx1MjBkMiIsdm5zdWI6Ilx1MjI4Mlx1MjBkMiIsTm90U3Vic2V0RXF1YWw6Ilx1MjI4OCIsbnN1YmU6Ilx1MjI4OCIsbnN1YnNldGVxOiJcdTIyODgiLE5vdFN1Y2NlZWRzOiJcdTIyODEiLG5zYzoiXHUyMjgxIixuc3VjYzoiXHUyMjgxIixOb3RTdWNjZWVkc0VxdWFsOiJcdTJhYjBcdTAzMzgiLG5zY2U6Ilx1MmFiMFx1MDMzOCIsbnN1Y2NlcToiXHUyYWIwXHUwMzM4IixOb3RTdWNjZWVkc1NsYW50RXF1YWw6Ilx1MjJlMSIsbnNjY3VlOiJcdTIyZTEiLE5vdFN1Y2NlZWRzVGlsZGU6Ilx1MjI3Zlx1MDMzOCIsTm90U3VwZXJzZXQ6Ilx1MjI4M1x1MjBkMiIsbnN1cHNldDoiXHUyMjgzXHUyMGQyIix2bnN1cDoiXHUyMjgzXHUyMGQyIixOb3RTdXBlcnNldEVxdWFsOiJcdTIyODkiLG5zdXBlOiJcdTIyODkiLG5zdXBzZXRlcToiXHUyMjg5IixOb3RUaWxkZToiXHUyMjQxIixuc2ltOiJcdTIyNDEiLE5vdFRpbGRlRXF1YWw6Ilx1MjI0NCIsbnNpbWU6Ilx1MjI0NCIsbnNpbWVxOiJcdTIyNDQiLE5vdFRpbGRlRnVsbEVxdWFsOiJcdTIyNDciLG5jb25nOiJcdTIyNDciLE5vdFRpbGRlVGlsZGU6Ilx1MjI0OSIsbmFwOiJcdTIyNDkiLG5hcHByb3g6Ilx1MjI0OSIsTm90VmVydGljYWxCYXI6Ilx1MjIyNCIsbm1pZDoiXHUyMjI0Iixuc2hvcnRtaWQ6Ilx1MjIyNCIsbnNtaWQ6Ilx1MjIyNCIsTnNjcjoiXHVkODM1XHVkY2E5IixOdGlsZGU6Ilx4ZDEiLE51OiJcdTAzOWQiLE9FbGlnOiJcdTAxNTIiLE9hY3V0ZToiXHhkMyIsT2NpcmM6Ilx4ZDQiLE9jeToiXHUwNDFlIixPZGJsYWM6Ilx1MDE1MCIsT2ZyOiJcdWQ4MzVcdWRkMTIiLE9ncmF2ZToiXHhkMiIsT21hY3I6Ilx1MDE0YyIsT21lZ2E6Ilx1MDNhOSIsb2htOiJcdTAzYTkiLE9taWNyb246Ilx1MDM5ZiIsT29wZjoiXHVkODM1XHVkZDQ2IixPcGVuQ3VybHlEb3VibGVRdW90ZToiXHUyMDFjIixsZHF1bzoiXHUyMDFjIixPcGVuQ3VybHlRdW90ZToiXHUyMDE4Iixsc3F1bzoiXHUyMDE4IixPcjoiXHUyYTU0IixPc2NyOiJcdWQ4MzVcdWRjYWEiLE9zbGFzaDoiXHhkOCIsT3RpbGRlOiJceGQ1IixPdGltZXM6Ilx1MmEzNyIsT3VtbDoiXHhkNiIsT3ZlckJhcjoiXHUyMDNlIixvbGluZToiXHUyMDNlIixPdmVyQnJhY2U6Ilx1MjNkZSIsT3ZlckJyYWNrZXQ6Ilx1MjNiNCIsdGJyazoiXHUyM2I0IixPdmVyUGFyZW50aGVzaXM6Ilx1MjNkYyIsUGFydGlhbEQ6Ilx1MjIwMiIscGFydDoiXHUyMjAyIixQY3k6Ilx1MDQxZiIsUGZyOiJcdWQ4MzVcdWRkMTMiLFBoaToiXHUwM2E2IixQaToiXHUwM2EwIixQbHVzTWludXM6Ilx4YjEiLHBsdXNtbjoiXHhiMSIscG06Ilx4YjEiLFBvcGY6Ilx1MjExOSIscHJpbWVzOiJcdTIxMTkiLFByOiJcdTJhYmIiLFByZWNlZGVzOiJcdTIyN2EiLHByOiJcdTIyN2EiLHByZWM6Ilx1MjI3YSIsUHJlY2VkZXNFcXVhbDoiXHUyYWFmIixwcmU6Ilx1MmFhZiIscHJlY2VxOiJcdTJhYWYiLFByZWNlZGVzU2xhbnRFcXVhbDoiXHUyMjdjIixwcmN1ZToiXHUyMjdjIixwcmVjY3VybHllcToiXHUyMjdjIixQcmVjZWRlc1RpbGRlOiJcdTIyN2UiLHByZWNzaW06Ilx1MjI3ZSIscHJzaW06Ilx1MjI3ZSIsUHJpbWU6Ilx1MjAzMyIsUHJvZHVjdDoiXHUyMjBmIixwcm9kOiJcdTIyMGYiLFByb3BvcnRpb25hbDoiXHUyMjFkIixwcm9wOiJcdTIyMWQiLHByb3B0bzoiXHUyMjFkIix2YXJwcm9wdG86Ilx1MjIxZCIsdnByb3A6Ilx1MjIxZCIsUHNjcjoiXHVkODM1XHVkY2FiIixQc2k6Ilx1MDNhOCIsUVVPVDonIicscXVvdDonIicsUWZyOiJcdWQ4MzVcdWRkMTQiLFFvcGY6Ilx1MjExYSIscmF0aW9uYWxzOiJcdTIxMWEiLFFzY3I6Ilx1ZDgzNVx1ZGNhYyIsUkJhcnI6Ilx1MjkxMCIsZHJia2Fyb3c6Ilx1MjkxMCIsUkVHOiJceGFlIixjaXJjbGVkUjoiXHhhZSIscmVnOiJceGFlIixSYWN1dGU6Ilx1MDE1NCIsUmFuZzoiXHUyN2ViIixSYXJyOiJcdTIxYTAiLHR3b2hlYWRyaWdodGFycm93OiJcdTIxYTAiLFJhcnJ0bDoiXHUyOTE2IixSY2Fyb246Ilx1MDE1OCIsUmNlZGlsOiJcdTAxNTYiLFJjeToiXHUwNDIwIixSZToiXHUyMTFjIixSZnI6Ilx1MjExYyIscmVhbDoiXHUyMTFjIixyZWFscGFydDoiXHUyMTFjIixSZXZlcnNlRWxlbWVudDoiXHUyMjBiIixTdWNoVGhhdDoiXHUyMjBiIixuaToiXHUyMjBiIixuaXY6Ilx1MjIwYiIsUmV2ZXJzZUVxdWlsaWJyaXVtOiJcdTIxY2IiLGxlZnRyaWdodGhhcnBvb25zOiJcdTIxY2IiLGxyaGFyOiJcdTIxY2IiLFJldmVyc2VVcEVxdWlsaWJyaXVtOiJcdTI5NmYiLGR1aGFyOiJcdTI5NmYiLFJobzoiXHUwM2ExIixSaWdodEFuZ2xlQnJhY2tldDoiXHUyN2U5IixyYW5nOiJcdTI3ZTkiLHJhbmdsZToiXHUyN2U5IixSaWdodEFycm93OiJcdTIxOTIiLFNob3J0UmlnaHRBcnJvdzoiXHUyMTkyIixyYXJyOiJcdTIxOTIiLHJpZ2h0YXJyb3c6Ilx1MjE5MiIsc3JhcnI6Ilx1MjE5MiIsUmlnaHRBcnJvd0JhcjoiXHUyMWU1IixyYXJyYjoiXHUyMWU1IixSaWdodEFycm93TGVmdEFycm93OiJcdTIxYzQiLHJpZ2h0bGVmdGFycm93czoiXHUyMWM0IixybGFycjoiXHUyMWM0IixSaWdodENlaWxpbmc6Ilx1MjMwOSIscmNlaWw6Ilx1MjMwOSIsUmlnaHREb3VibGVCcmFja2V0OiJcdTI3ZTciLHJvYnJrOiJcdTI3ZTciLFJpZ2h0RG93blRlZVZlY3RvcjoiXHUyOTVkIixSaWdodERvd25WZWN0b3I6Ilx1MjFjMiIsZGhhcnI6Ilx1MjFjMiIsZG93bmhhcnBvb25yaWdodDoiXHUyMWMyIixSaWdodERvd25WZWN0b3JCYXI6Ilx1Mjk1NSIsUmlnaHRGbG9vcjoiXHUyMzBiIixyZmxvb3I6Ilx1MjMwYiIsUmlnaHRUZWU6Ilx1MjJhMiIsdmRhc2g6Ilx1MjJhMiIsUmlnaHRUZWVBcnJvdzoiXHUyMWE2IixtYXA6Ilx1MjFhNiIsbWFwc3RvOiJcdTIxYTYiLFJpZ2h0VGVlVmVjdG9yOiJcdTI5NWIiLFJpZ2h0VHJpYW5nbGU6Ilx1MjJiMyIsdmFydHJpYW5nbGVyaWdodDoiXHUyMmIzIix2cnRyaToiXHUyMmIzIixSaWdodFRyaWFuZ2xlQmFyOiJcdTI5ZDAiLFJpZ2h0VHJpYW5nbGVFcXVhbDoiXHUyMmI1IixydHJpZToiXHUyMmI1Iix0cmlhbmdsZXJpZ2h0ZXE6Ilx1MjJiNSIsUmlnaHRVcERvd25WZWN0b3I6Ilx1Mjk0ZiIsUmlnaHRVcFRlZVZlY3RvcjoiXHUyOTVjIixSaWdodFVwVmVjdG9yOiJcdTIxYmUiLHVoYXJyOiJcdTIxYmUiLHVwaGFycG9vbnJpZ2h0OiJcdTIxYmUiLFJpZ2h0VXBWZWN0b3JCYXI6Ilx1Mjk1NCIsUmlnaHRWZWN0b3I6Ilx1MjFjMCIscmhhcnU6Ilx1MjFjMCIscmlnaHRoYXJwb29udXA6Ilx1MjFjMCIsUmlnaHRWZWN0b3JCYXI6Ilx1Mjk1MyIsUm9wZjoiXHUyMTFkIixyZWFsczoiXHUyMTFkIixSb3VuZEltcGxpZXM6Ilx1Mjk3MCIsUnJpZ2h0YXJyb3c6Ilx1MjFkYiIsckFhcnI6Ilx1MjFkYiIsUnNjcjoiXHUyMTFiIixyZWFsaW5lOiJcdTIxMWIiLFJzaDoiXHUyMWIxIixyc2g6Ilx1MjFiMSIsUnVsZURlbGF5ZWQ6Ilx1MjlmNCIsU0hDSGN5OiJcdTA0MjkiLFNIY3k6Ilx1MDQyOCIsU09GVGN5OiJcdTA0MmMiLFNhY3V0ZToiXHUwMTVhIixTYzoiXHUyYWJjIixTY2Fyb246Ilx1MDE2MCIsU2NlZGlsOiJcdTAxNWUiLFNjaXJjOiJcdTAxNWMiLFNjeToiXHUwNDIxIixTZnI6Ilx1ZDgzNVx1ZGQxNiIsU2hvcnRVcEFycm93OiJcdTIxOTEiLFVwQXJyb3c6Ilx1MjE5MSIsdWFycjoiXHUyMTkxIix1cGFycm93OiJcdTIxOTEiLFNpZ21hOiJcdTAzYTMiLFNtYWxsQ2lyY2xlOiJcdTIyMTgiLGNvbXBmbjoiXHUyMjE4IixTb3BmOiJcdWQ4MzVcdWRkNGEiLFNxcnQ6Ilx1MjIxYSIscmFkaWM6Ilx1MjIxYSIsU3F1YXJlOiJcdTI1YTEiLHNxdToiXHUyNWExIixzcXVhcmU6Ilx1MjVhMSIsU3F1YXJlSW50ZXJzZWN0aW9uOiJcdTIyOTMiLHNxY2FwOiJcdTIyOTMiLFNxdWFyZVN1YnNldDoiXHUyMjhmIixzcXN1YjoiXHUyMjhmIixzcXN1YnNldDoiXHUyMjhmIixTcXVhcmVTdWJzZXRFcXVhbDoiXHUyMjkxIixzcXN1YmU6Ilx1MjI5MSIsc3FzdWJzZXRlcToiXHUyMjkxIixTcXVhcmVTdXBlcnNldDoiXHUyMjkwIixzcXN1cDoiXHUyMjkwIixzcXN1cHNldDoiXHUyMjkwIixTcXVhcmVTdXBlcnNldEVxdWFsOiJcdTIyOTIiLHNxc3VwZToiXHUyMjkyIixzcXN1cHNldGVxOiJcdTIyOTIiLFNxdWFyZVVuaW9uOiJcdTIyOTQiLHNxY3VwOiJcdTIyOTQiLFNzY3I6Ilx1ZDgzNVx1ZGNhZSIsU3RhcjoiXHUyMmM2Iixzc3RhcmY6Ilx1MjJjNiIsU3ViOiJcdTIyZDAiLFN1YnNldDoiXHUyMmQwIixTdWJzZXRFcXVhbDoiXHUyMjg2IixzdWJlOiJcdTIyODYiLHN1YnNldGVxOiJcdTIyODYiLFN1Y2NlZWRzOiJcdTIyN2IiLHNjOiJcdTIyN2IiLHN1Y2M6Ilx1MjI3YiIsU3VjY2VlZHNFcXVhbDoiXHUyYWIwIixzY2U6Ilx1MmFiMCIsc3VjY2VxOiJcdTJhYjAiLFN1Y2NlZWRzU2xhbnRFcXVhbDoiXHUyMjdkIixzY2N1ZToiXHUyMjdkIixzdWNjY3VybHllcToiXHUyMjdkIixTdWNjZWVkc1RpbGRlOiJcdTIyN2YiLHNjc2ltOiJcdTIyN2YiLHN1Y2NzaW06Ilx1MjI3ZiIsU3VtOiJcdTIyMTEiLHN1bToiXHUyMjExIixTdXA6Ilx1MjJkMSIsU3Vwc2V0OiJcdTIyZDEiLFN1cGVyc2V0OiJcdTIyODMiLHN1cDoiXHUyMjgzIixzdXBzZXQ6Ilx1MjI4MyIsU3VwZXJzZXRFcXVhbDoiXHUyMjg3IixzdXBlOiJcdTIyODciLHN1cHNldGVxOiJcdTIyODciLFRIT1JOOiJceGRlIixUUkFERToiXHUyMTIyIix0cmFkZToiXHUyMTIyIixUU0hjeToiXHUwNDBiIixUU2N5OiJcdTA0MjYiLFRhYjoiXHQiLFRhdToiXHUwM2E0IixUY2Fyb246Ilx1MDE2NCIsVGNlZGlsOiJcdTAxNjIiLFRjeToiXHUwNDIyIixUZnI6Ilx1ZDgzNVx1ZGQxNyIsVGhlcmVmb3JlOiJcdTIyMzQiLHRoZXJlNDoiXHUyMjM0Iix0aGVyZWZvcmU6Ilx1MjIzNCIsVGhldGE6Ilx1MDM5OCIsVGhpY2tTcGFjZToiXHUyMDVmXHUyMDBhIixUaGluU3BhY2U6Ilx1MjAwOSIsdGhpbnNwOiJcdTIwMDkiLFRpbGRlOiJcdTIyM2MiLHNpbToiXHUyMjNjIix0aGlja3NpbToiXHUyMjNjIix0aGtzaW06Ilx1MjIzYyIsVGlsZGVFcXVhbDoiXHUyMjQzIixzaW1lOiJcdTIyNDMiLHNpbWVxOiJcdTIyNDMiLFRpbGRlRnVsbEVxdWFsOiJcdTIyNDUiLGNvbmc6Ilx1MjI0NSIsVGlsZGVUaWxkZToiXHUyMjQ4IixhcDoiXHUyMjQ4IixhcHByb3g6Ilx1MjI0OCIsYXN5bXA6Ilx1MjI0OCIsdGhpY2thcHByb3g6Ilx1MjI0OCIsdGhrYXA6Ilx1MjI0OCIsVG9wZjoiXHVkODM1XHVkZDRiIixUcmlwbGVEb3Q6Ilx1MjBkYiIsdGRvdDoiXHUyMGRiIixUc2NyOiJcdWQ4MzVcdWRjYWYiLFRzdHJvazoiXHUwMTY2IixVYWN1dGU6Ilx4ZGEiLFVhcnI6Ilx1MjE5ZiIsVWFycm9jaXI6Ilx1Mjk0OSIsVWJyY3k6Ilx1MDQwZSIsVWJyZXZlOiJcdTAxNmMiLFVjaXJjOiJceGRiIixVY3k6Ilx1MDQyMyIsVWRibGFjOiJcdTAxNzAiLFVmcjoiXHVkODM1XHVkZDE4IixVZ3JhdmU6Ilx4ZDkiLFVtYWNyOiJcdTAxNmEiLFVuZGVyQmFyOiJfIixsb3diYXI6Il8iLFVuZGVyQnJhY2U6Ilx1MjNkZiIsVW5kZXJCcmFja2V0OiJcdTIzYjUiLGJicms6Ilx1MjNiNSIsVW5kZXJQYXJlbnRoZXNpczoiXHUyM2RkIixVbmlvbjoiXHUyMmMzIixiaWdjdXA6Ilx1MjJjMyIseGN1cDoiXHUyMmMzIixVbmlvblBsdXM6Ilx1MjI4ZSIsdXBsdXM6Ilx1MjI4ZSIsVW9nb246Ilx1MDE3MiIsVW9wZjoiXHVkODM1XHVkZDRjIixVcEFycm93QmFyOiJcdTI5MTIiLFVwQXJyb3dEb3duQXJyb3c6Ilx1MjFjNSIsdWRhcnI6Ilx1MjFjNSIsVXBEb3duQXJyb3c6Ilx1MjE5NSIsdXBkb3duYXJyb3c6Ilx1MjE5NSIsdmFycjoiXHUyMTk1IixVcEVxdWlsaWJyaXVtOiJcdTI5NmUiLHVkaGFyOiJcdTI5NmUiLFVwVGVlOiJcdTIyYTUiLGJvdDoiXHUyMmE1Iixib3R0b206Ilx1MjJhNSIscGVycDoiXHUyMmE1IixVcFRlZUFycm93OiJcdTIxYTUiLG1hcHN0b3VwOiJcdTIxYTUiLFVwcGVyTGVmdEFycm93OiJcdTIxOTYiLG53YXJyOiJcdTIxOTYiLG53YXJyb3c6Ilx1MjE5NiIsVXBwZXJSaWdodEFycm93OiJcdTIxOTciLG5lYXJyOiJcdTIxOTciLG5lYXJyb3c6Ilx1MjE5NyIsVXBzaToiXHUwM2QyIix1cHNpaDoiXHUwM2QyIixVcHNpbG9uOiJcdTAzYTUiLFVyaW5nOiJcdTAxNmUiLFVzY3I6Ilx1ZDgzNVx1ZGNiMCIsVXRpbGRlOiJcdTAxNjgiLFV1bWw6Ilx4ZGMiLFZEYXNoOiJcdTIyYWIiLFZiYXI6Ilx1MmFlYiIsVmN5OiJcdTA0MTIiLFZkYXNoOiJcdTIyYTkiLFZkYXNobDoiXHUyYWU2IixWZWU6Ilx1MjJjMSIsYmlndmVlOiJcdTIyYzEiLHh2ZWU6Ilx1MjJjMSIsVmVyYmFyOiJcdTIwMTYiLFZlcnQ6Ilx1MjAxNiIsVmVydGljYWxCYXI6Ilx1MjIyMyIsbWlkOiJcdTIyMjMiLHNob3J0bWlkOiJcdTIyMjMiLHNtaWQ6Ilx1MjIyMyIsVmVydGljYWxMaW5lOiJ8Iix2ZXJiYXI6InwiLHZlcnQ6InwiLFZlcnRpY2FsU2VwYXJhdG9yOiJcdTI3NTgiLFZlcnRpY2FsVGlsZGU6Ilx1MjI0MCIsd3I6Ilx1MjI0MCIsd3JlYXRoOiJcdTIyNDAiLFZlcnlUaGluU3BhY2U6Ilx1MjAwYSIsaGFpcnNwOiJcdTIwMGEiLFZmcjoiXHVkODM1XHVkZDE5IixWb3BmOiJcdWQ4MzVcdWRkNGQiLFZzY3I6Ilx1ZDgzNVx1ZGNiMSIsVnZkYXNoOiJcdTIyYWEiLFdjaXJjOiJcdTAxNzQiLFdlZGdlOiJcdTIyYzAiLGJpZ3dlZGdlOiJcdTIyYzAiLHh3ZWRnZToiXHUyMmMwIixXZnI6Ilx1ZDgzNVx1ZGQxYSIsV29wZjoiXHVkODM1XHVkZDRlIixXc2NyOiJcdWQ4MzVcdWRjYjIiLFhmcjoiXHVkODM1XHVkZDFiIixYaToiXHUwMzllIixYb3BmOiJcdWQ4MzVcdWRkNGYiLFhzY3I6Ilx1ZDgzNVx1ZGNiMyIsWUFjeToiXHUwNDJmIixZSWN5OiJcdTA0MDciLFlVY3k6Ilx1MDQyZSIsWWFjdXRlOiJceGRkIixZY2lyYzoiXHUwMTc2IixZY3k6Ilx1MDQyYiIsWWZyOiJcdWQ4MzVcdWRkMWMiLFlvcGY6Ilx1ZDgzNVx1ZGQ1MCIsWXNjcjoiXHVkODM1XHVkY2I0IixZdW1sOiJcdTAxNzgiLFpIY3k6Ilx1MDQxNiIsWmFjdXRlOiJcdTAxNzkiLFpjYXJvbjoiXHUwMTdkIixaY3k6Ilx1MDQxNyIsWmRvdDoiXHUwMTdiIixaZXRhOiJcdTAzOTYiLFpmcjoiXHUyMTI4Iix6ZWV0cmY6Ilx1MjEyOCIsWm9wZjoiXHUyMTI0IixpbnRlZ2VyczoiXHUyMTI0Iixac2NyOiJcdWQ4MzVcdWRjYjUiLGFhY3V0ZToiXHhlMSIsYWJyZXZlOiJcdTAxMDMiLGFjOiJcdTIyM2UiLG1zdHBvczoiXHUyMjNlIixhY0U6Ilx1MjIzZVx1MDMzMyIsYWNkOiJcdTIyM2YiLGFjaXJjOiJceGUyIixhY3k6Ilx1MDQzMCIsYWVsaWc6Ilx4ZTYiLGFmcjoiXHVkODM1XHVkZDFlIixhZ3JhdmU6Ilx4ZTAiLGFsZWZzeW06Ilx1MjEzNSIsYWxlcGg6Ilx1MjEzNSIsYWxwaGE6Ilx1MDNiMSIsYW1hY3I6Ilx1MDEwMSIsYW1hbGc6Ilx1MmEzZiIsYW5kOiJcdTIyMjciLHdlZGdlOiJcdTIyMjciLGFuZGFuZDoiXHUyYTU1IixhbmRkOiJcdTJhNWMiLGFuZHNsb3BlOiJcdTJhNTgiLGFuZHY6Ilx1MmE1YSIsYW5nOiJcdTIyMjAiLGFuZ2xlOiJcdTIyMjAiLGFuZ2U6Ilx1MjlhNCIsYW5nbXNkOiJcdTIyMjEiLG1lYXN1cmVkYW5nbGU6Ilx1MjIyMSIsYW5nbXNkYWE6Ilx1MjlhOCIsYW5nbXNkYWI6Ilx1MjlhOSIsYW5nbXNkYWM6Ilx1MjlhYSIsYW5nbXNkYWQ6Ilx1MjlhYiIsYW5nbXNkYWU6Ilx1MjlhYyIsYW5nbXNkYWY6Ilx1MjlhZCIsYW5nbXNkYWc6Ilx1MjlhZSIsYW5nbXNkYWg6Ilx1MjlhZiIsYW5ncnQ6Ilx1MjIxZiIsYW5ncnR2YjoiXHUyMmJlIixhbmdydHZiZDoiXHUyOTlkIixhbmdzcGg6Ilx1MjIyMiIsYW5nemFycjoiXHUyMzdjIixhb2dvbjoiXHUwMTA1Iixhb3BmOiJcdWQ4MzVcdWRkNTIiLGFwRToiXHUyYTcwIixhcGFjaXI6Ilx1MmE2ZiIsYXBlOiJcdTIyNGEiLGFwcHJveGVxOiJcdTIyNGEiLGFwaWQ6Ilx1MjI0YiIsYXBvczoiJyIsYXJpbmc6Ilx4ZTUiLGFzY3I6Ilx1ZDgzNVx1ZGNiNiIsYXN0OiIqIixtaWRhc3Q6IioiLGF0aWxkZToiXHhlMyIsYXVtbDoiXHhlNCIsYXdpbnQ6Ilx1MmExMSIsYk5vdDoiXHUyYWVkIixiYWNrY29uZzoiXHUyMjRjIixiY29uZzoiXHUyMjRjIixiYWNrZXBzaWxvbjoiXHUwM2Y2IixiZXBzaToiXHUwM2Y2IixiYWNrcHJpbWU6Ilx1MjAzNSIsYnByaW1lOiJcdTIwMzUiLGJhY2tzaW06Ilx1MjIzZCIsYnNpbToiXHUyMjNkIixiYWNrc2ltZXE6Ilx1MjJjZCIsYnNpbWU6Ilx1MjJjZCIsYmFydmVlOiJcdTIyYmQiLGJhcndlZDoiXHUyMzA1IixiYXJ3ZWRnZToiXHUyMzA1IixiYnJrdGJyazoiXHUyM2I2IixiY3k6Ilx1MDQzMSIsYmRxdW86Ilx1MjAxZSIsbGRxdW9yOiJcdTIwMWUiLGJlbXB0eXY6Ilx1MjliMCIsYmV0YToiXHUwM2IyIixiZXRoOiJcdTIxMzYiLGJldHdlZW46Ilx1MjI2YyIsdHdpeHQ6Ilx1MjI2YyIsYmZyOiJcdWQ4MzVcdWRkMWYiLGJpZ2NpcmM6Ilx1MjVlZiIseGNpcmM6Ilx1MjVlZiIsYmlnb2RvdDoiXHUyYTAwIix4b2RvdDoiXHUyYTAwIixiaWdvcGx1czoiXHUyYTAxIix4b3BsdXM6Ilx1MmEwMSIsYmlnb3RpbWVzOiJcdTJhMDIiLHhvdGltZToiXHUyYTAyIixiaWdzcWN1cDoiXHUyYTA2Iix4c3FjdXA6Ilx1MmEwNiIsYmlnc3RhcjoiXHUyNjA1IixzdGFyZjoiXHUyNjA1IixiaWd0cmlhbmdsZWRvd246Ilx1MjViZCIseGR0cmk6Ilx1MjViZCIsYmlndHJpYW5nbGV1cDoiXHUyNWIzIix4dXRyaToiXHUyNWIzIixiaWd1cGx1czoiXHUyYTA0Iix4dXBsdXM6Ilx1MmEwNCIsYmthcm93OiJcdTI5MGQiLHJiYXJyOiJcdTI5MGQiLGJsYWNrbG96ZW5nZToiXHUyOWViIixsb3pmOiJcdTI5ZWIiLGJsYWNrdHJpYW5nbGU6Ilx1MjViNCIsdXRyaWY6Ilx1MjViNCIsYmxhY2t0cmlhbmdsZWRvd246Ilx1MjViZSIsZHRyaWY6Ilx1MjViZSIsYmxhY2t0cmlhbmdsZWxlZnQ6Ilx1MjVjMiIsbHRyaWY6Ilx1MjVjMiIsYmxhY2t0cmlhbmdsZXJpZ2h0OiJcdTI1YjgiLHJ0cmlmOiJcdTI1YjgiLGJsYW5rOiJcdTI0MjMiLGJsazEyOiJcdTI1OTIiLGJsazE0OiJcdTI1OTEiLGJsazM0OiJcdTI1OTMiLGJsb2NrOiJcdTI1ODgiLGJuZToiPVx1MjBlNSIsYm5lcXVpdjoiXHUyMjYxXHUyMGU1Iixibm90OiJcdTIzMTAiLGJvcGY6Ilx1ZDgzNVx1ZGQ1MyIsYm93dGllOiJcdTIyYzgiLGJveERMOiJcdTI1NTciLGJveERSOiJcdTI1NTQiLGJveERsOiJcdTI1NTYiLGJveERyOiJcdTI1NTMiLGJveEg6Ilx1MjU1MCIsYm94SEQ6Ilx1MjU2NiIsYm94SFU6Ilx1MjU2OSIsYm94SGQ6Ilx1MjU2NCIsYm94SHU6Ilx1MjU2NyIsYm94VUw6Ilx1MjU1ZCIsYm94VVI6Ilx1MjU1YSIsYm94VWw6Ilx1MjU1YyIsYm94VXI6Ilx1MjU1OSIsYm94VjoiXHUyNTUxIixib3hWSDoiXHUyNTZjIixib3hWTDoiXHUyNTYzIixib3hWUjoiXHUyNTYwIixib3hWaDoiXHUyNTZiIixib3hWbDoiXHUyNTYyIixib3hWcjoiXHUyNTVmIixib3hib3g6Ilx1MjljOSIsYm94ZEw6Ilx1MjU1NSIsYm94ZFI6Ilx1MjU1MiIsYm94ZGw6Ilx1MjUxMCIsYm94ZHI6Ilx1MjUwYyIsYm94aEQ6Ilx1MjU2NSIsYm94aFU6Ilx1MjU2OCIsYm94aGQ6Ilx1MjUyYyIsYm94aHU6Ilx1MjUzNCIsYm94bWludXM6Ilx1MjI5ZiIsbWludXNiOiJcdTIyOWYiLGJveHBsdXM6Ilx1MjI5ZSIscGx1c2I6Ilx1MjI5ZSIsYm94dGltZXM6Ilx1MjJhMCIsdGltZXNiOiJcdTIyYTAiLGJveHVMOiJcdTI1NWIiLGJveHVSOiJcdTI1NTgiLGJveHVsOiJcdTI1MTgiLGJveHVyOiJcdTI1MTQiLGJveHY6Ilx1MjUwMiIsYm94dkg6Ilx1MjU2YSIsYm94dkw6Ilx1MjU2MSIsYm94dlI6Ilx1MjU1ZSIsYm94dmg6Ilx1MjUzYyIsYm94dmw6Ilx1MjUyNCIsYm94dnI6Ilx1MjUxYyIsYnJ2YmFyOiJceGE2Iixic2NyOiJcdWQ4MzVcdWRjYjciLGJzZW1pOiJcdTIwNGYiLGJzb2w6IlxcIixic29sYjoiXHUyOWM1Iixic29saHN1YjoiXHUyN2M4IixidWxsOiJcdTIwMjIiLGJ1bGxldDoiXHUyMDIyIixidW1wRToiXHUyYWFlIixjYWN1dGU6Ilx1MDEwNyIsY2FwOiJcdTIyMjkiLGNhcGFuZDoiXHUyYTQ0IixjYXBicmN1cDoiXHUyYTQ5IixjYXBjYXA6Ilx1MmE0YiIsY2FwY3VwOiJcdTJhNDciLGNhcGRvdDoiXHUyYTQwIixjYXBzOiJcdTIyMjlcdWZlMDAiLGNhcmV0OiJcdTIwNDEiLGNjYXBzOiJcdTJhNGQiLGNjYXJvbjoiXHUwMTBkIixjY2VkaWw6Ilx4ZTciLGNjaXJjOiJcdTAxMDkiLGNjdXBzOiJcdTJhNGMiLGNjdXBzc206Ilx1MmE1MCIsY2RvdDoiXHUwMTBiIixjZW1wdHl2OiJcdTI5YjIiLGNlbnQ6Ilx4YTIiLGNmcjoiXHVkODM1XHVkZDIwIixjaGN5OiJcdTA0NDciLGNoZWNrOiJcdTI3MTMiLGNoZWNrbWFyazoiXHUyNzEzIixjaGk6Ilx1MDNjNyIsY2lyOiJcdTI1Y2IiLGNpckU6Ilx1MjljMyIsY2lyYzoiXHUwMmM2IixjaXJjZXE6Ilx1MjI1NyIsY2lyZToiXHUyMjU3IixjaXJjbGVhcnJvd2xlZnQ6Ilx1MjFiYSIsb2xhcnI6Ilx1MjFiYSIsY2lyY2xlYXJyb3dyaWdodDoiXHUyMWJiIixvcmFycjoiXHUyMWJiIixjaXJjbGVkUzoiXHUyNGM4IixvUzoiXHUyNGM4IixjaXJjbGVkYXN0OiJcdTIyOWIiLG9hc3Q6Ilx1MjI5YiIsY2lyY2xlZGNpcmM6Ilx1MjI5YSIsb2NpcjoiXHUyMjlhIixjaXJjbGVkZGFzaDoiXHUyMjlkIixvZGFzaDoiXHUyMjlkIixjaXJmbmludDoiXHUyYTEwIixjaXJtaWQ6Ilx1MmFlZiIsY2lyc2NpcjoiXHUyOWMyIixjbHViczoiXHUyNjYzIixjbHVic3VpdDoiXHUyNjYzIixjb2xvbjoiOiIsY29tbWE6IiwiLGNvbW1hdDoiQCIsY29tcDoiXHUyMjAxIixjb21wbGVtZW50OiJcdTIyMDEiLGNvbmdkb3Q6Ilx1MmE2ZCIsY29wZjoiXHVkODM1XHVkZDU0Iixjb3B5c3I6Ilx1MjExNyIsY3JhcnI6Ilx1MjFiNSIsY3Jvc3M6Ilx1MjcxNyIsY3NjcjoiXHVkODM1XHVkY2I4Iixjc3ViOiJcdTJhY2YiLGNzdWJlOiJcdTJhZDEiLGNzdXA6Ilx1MmFkMCIsY3N1cGU6Ilx1MmFkMiIsY3Rkb3Q6Ilx1MjJlZiIsY3VkYXJybDoiXHUyOTM4IixjdWRhcnJyOiJcdTI5MzUiLGN1ZXByOiJcdTIyZGUiLGN1cmx5ZXFwcmVjOiJcdTIyZGUiLGN1ZXNjOiJcdTIyZGYiLGN1cmx5ZXFzdWNjOiJcdTIyZGYiLGN1bGFycjoiXHUyMWI2IixjdXJ2ZWFycm93bGVmdDoiXHUyMWI2IixjdWxhcnJwOiJcdTI5M2QiLGN1cDoiXHUyMjJhIixjdXBicmNhcDoiXHUyYTQ4IixjdXBjYXA6Ilx1MmE0NiIsY3VwY3VwOiJcdTJhNGEiLGN1cGRvdDoiXHUyMjhkIixjdXBvcjoiXHUyYTQ1IixjdXBzOiJcdTIyMmFcdWZlMDAiLGN1cmFycjoiXHUyMWI3IixjdXJ2ZWFycm93cmlnaHQ6Ilx1MjFiNyIsY3VyYXJybToiXHUyOTNjIixjdXJseXZlZToiXHUyMmNlIixjdXZlZToiXHUyMmNlIixjdXJseXdlZGdlOiJcdTIyY2YiLGN1d2VkOiJcdTIyY2YiLGN1cnJlbjoiXHhhNCIsY3dpbnQ6Ilx1MjIzMSIsY3lsY3R5OiJcdTIzMmQiLGRIYXI6Ilx1Mjk2NSIsZGFnZ2VyOiJcdTIwMjAiLGRhbGV0aDoiXHUyMTM4IixkYXNoOiJcdTIwMTAiLGh5cGhlbjoiXHUyMDEwIixkYmthcm93OiJcdTI5MGYiLHJCYXJyOiJcdTI5MGYiLGRjYXJvbjoiXHUwMTBmIixkY3k6Ilx1MDQzNCIsZGRhcnI6Ilx1MjFjYSIsZG93bmRvd25hcnJvd3M6Ilx1MjFjYSIsZGRvdHNlcToiXHUyYTc3IixlRERvdDoiXHUyYTc3IixkZWc6Ilx4YjAiLGRlbHRhOiJcdTAzYjQiLGRlbXB0eXY6Ilx1MjliMSIsZGZpc2h0OiJcdTI5N2YiLGRmcjoiXHVkODM1XHVkZDIxIixkaWFtb25kc3VpdDoiXHUyNjY2IixkaWFtczoiXHUyNjY2IixkaWdhbW1hOiJcdTAzZGQiLGdhbW1hZDoiXHUwM2RkIixkaXNpbjoiXHUyMmYyIixkaXY6Ilx4ZjciLGRpdmlkZToiXHhmNyIsZGl2aWRlb250aW1lczoiXHUyMmM3IixkaXZvbng6Ilx1MjJjNyIsZGpjeToiXHUwNDUyIixkbGNvcm46Ilx1MjMxZSIsbGxjb3JuZXI6Ilx1MjMxZSIsZGxjcm9wOiJcdTIzMGQiLGRvbGxhcjoiJCIsZG9wZjoiXHVkODM1XHVkZDU1Iixkb3RlcWRvdDoiXHUyMjUxIixlRG90OiJcdTIyNTEiLGRvdG1pbnVzOiJcdTIyMzgiLG1pbnVzZDoiXHUyMjM4Iixkb3RwbHVzOiJcdTIyMTQiLHBsdXNkbzoiXHUyMjE0Iixkb3RzcXVhcmU6Ilx1MjJhMSIsc2RvdGI6Ilx1MjJhMSIsZHJjb3JuOiJcdTIzMWYiLGxyY29ybmVyOiJcdTIzMWYiLGRyY3JvcDoiXHUyMzBjIixkc2NyOiJcdWQ4MzVcdWRjYjkiLGRzY3k6Ilx1MDQ1NSIsZHNvbDoiXHUyOWY2Iixkc3Ryb2s6Ilx1MDExMSIsZHRkb3Q6Ilx1MjJmMSIsZHRyaToiXHUyNWJmIix0cmlhbmdsZWRvd246Ilx1MjViZiIsZHdhbmdsZToiXHUyOWE2IixkemN5OiJcdTA0NWYiLGR6aWdyYXJyOiJcdTI3ZmYiLGVhY3V0ZToiXHhlOSIsZWFzdGVyOiJcdTJhNmUiLGVjYXJvbjoiXHUwMTFiIixlY2lyOiJcdTIyNTYiLGVxY2lyYzoiXHUyMjU2IixlY2lyYzoiXHhlYSIsZWNvbG9uOiJcdTIyNTUiLGVxY29sb246Ilx1MjI1NSIsZWN5OiJcdTA0NGQiLGVkb3Q6Ilx1MDExNyIsZWZEb3Q6Ilx1MjI1MiIsZmFsbGluZ2RvdHNlcToiXHUyMjUyIixlZnI6Ilx1ZDgzNVx1ZGQyMiIsZWc6Ilx1MmE5YSIsZWdyYXZlOiJceGU4IixlZ3M6Ilx1MmE5NiIsZXFzbGFudGd0cjoiXHUyYTk2IixlZ3Nkb3Q6Ilx1MmE5OCIsZWw6Ilx1MmE5OSIsZWxpbnRlcnM6Ilx1MjNlNyIsZWxsOiJcdTIxMTMiLGVsczoiXHUyYTk1IixlcXNsYW50bGVzczoiXHUyYTk1IixlbHNkb3Q6Ilx1MmE5NyIsZW1hY3I6Ilx1MDExMyIsZW1wdHk6Ilx1MjIwNSIsZW1wdHlzZXQ6Ilx1MjIwNSIsZW1wdHl2OiJcdTIyMDUiLHZhcm5vdGhpbmc6Ilx1MjIwNSIsZW1zcDEzOiJcdTIwMDQiLGVtc3AxNDoiXHUyMDA1IixlbXNwOiJcdTIwMDMiLGVuZzoiXHUwMTRiIixlbnNwOiJcdTIwMDIiLGVvZ29uOiJcdTAxMTkiLGVvcGY6Ilx1ZDgzNVx1ZGQ1NiIsZXBhcjoiXHUyMmQ1IixlcGFyc2w6Ilx1MjllMyIsZXBsdXM6Ilx1MmE3MSIsZXBzaToiXHUwM2I1IixlcHNpbG9uOiJcdTAzYjUiLGVwc2l2OiJcdTAzZjUiLHN0cmFpZ2h0ZXBzaWxvbjoiXHUwM2Y1Iix2YXJlcHNpbG9uOiJcdTAzZjUiLGVxdWFsczoiPSIsZXF1ZXN0OiJcdTIyNWYiLHF1ZXN0ZXE6Ilx1MjI1ZiIsZXF1aXZERDoiXHUyYTc4IixlcXZwYXJzbDoiXHUyOWU1IixlckRvdDoiXHUyMjUzIixyaXNpbmdkb3RzZXE6Ilx1MjI1MyIsZXJhcnI6Ilx1Mjk3MSIsZXNjcjoiXHUyMTJmIixldGE6Ilx1MDNiNyIsZXRoOiJceGYwIixldW1sOiJceGViIixldXJvOiJcdTIwYWMiLGV4Y2w6IiEiLGZjeToiXHUwNDQ0IixmZW1hbGU6Ilx1MjY0MCIsZmZpbGlnOiJcdWZiMDMiLGZmbGlnOiJcdWZiMDAiLGZmbGxpZzoiXHVmYjA0IixmZnI6Ilx1ZDgzNVx1ZGQyMyIsZmlsaWc6Ilx1ZmIwMSIsZmpsaWc6ImZqIixmbGF0OiJcdTI2NmQiLGZsbGlnOiJcdWZiMDIiLGZsdG5zOiJcdTI1YjEiLGZub2Y6Ilx1MDE5MiIsZm9wZjoiXHVkODM1XHVkZDU3Iixmb3JrOiJcdTIyZDQiLHBpdGNoZm9yazoiXHUyMmQ0Iixmb3JrdjoiXHUyYWQ5IixmcGFydGludDoiXHUyYTBkIixmcmFjMTI6Ilx4YmQiLGhhbGY6Ilx4YmQiLGZyYWMxMzoiXHUyMTUzIixmcmFjMTQ6Ilx4YmMiLGZyYWMxNToiXHUyMTU1IixmcmFjMTY6Ilx1MjE1OSIsZnJhYzE4OiJcdTIxNWIiLGZyYWMyMzoiXHUyMTU0IixmcmFjMjU6Ilx1MjE1NiIsZnJhYzM0OiJceGJlIixmcmFjMzU6Ilx1MjE1NyIsZnJhYzM4OiJcdTIxNWMiLGZyYWM0NToiXHUyMTU4IixmcmFjNTY6Ilx1MjE1YSIsZnJhYzU4OiJcdTIxNWQiLGZyYWM3ODoiXHUyMTVlIixmcmFzbDoiXHUyMDQ0Iixmcm93bjoiXHUyMzIyIixzZnJvd246Ilx1MjMyMiIsZnNjcjoiXHVkODM1XHVkY2JiIixnRWw6Ilx1MmE4YyIsZ3RyZXFxbGVzczoiXHUyYThjIixnYWN1dGU6Ilx1MDFmNSIsZ2FtbWE6Ilx1MDNiMyIsZ2FwOiJcdTJhODYiLGd0cmFwcHJveDoiXHUyYTg2IixnYnJldmU6Ilx1MDExZiIsZ2NpcmM6Ilx1MDExZCIsZ2N5OiJcdTA0MzMiLGdkb3Q6Ilx1MDEyMSIsZ2VzY2M6Ilx1MmFhOSIsZ2VzZG90OiJcdTJhODAiLGdlc2RvdG86Ilx1MmE4MiIsZ2VzZG90b2w6Ilx1MmE4NCIsZ2VzbDoiXHUyMmRiXHVmZTAwIixnZXNsZXM6Ilx1MmE5NCIsZ2ZyOiJcdWQ4MzVcdWRkMjQiLGdpbWVsOiJcdTIxMzciLGdqY3k6Ilx1MDQ1MyIsZ2xFOiJcdTJhOTIiLGdsYToiXHUyYWE1IixnbGo6Ilx1MmFhNCIsZ25FOiJcdTIyNjkiLGduZXFxOiJcdTIyNjkiLGduYXA6Ilx1MmE4YSIsZ25hcHByb3g6Ilx1MmE4YSIsZ25lOiJcdTJhODgiLGduZXE6Ilx1MmE4OCIsZ25zaW06Ilx1MjJlNyIsZ29wZjoiXHVkODM1XHVkZDU4Iixnc2NyOiJcdTIxMGEiLGdzaW1lOiJcdTJhOGUiLGdzaW1sOiJcdTJhOTAiLGd0Y2M6Ilx1MmFhNyIsZ3RjaXI6Ilx1MmE3YSIsZ3Rkb3Q6Ilx1MjJkNyIsZ3RyZG90OiJcdTIyZDciLGd0bFBhcjoiXHUyOTk1IixndHF1ZXN0OiJcdTJhN2MiLGd0cmFycjoiXHUyOTc4IixndmVydG5lcXE6Ilx1MjI2OVx1ZmUwMCIsZ3ZuRToiXHUyMjY5XHVmZTAwIixoYXJkY3k6Ilx1MDQ0YSIsaGFycmNpcjoiXHUyOTQ4IixoYXJydzoiXHUyMWFkIixsZWZ0cmlnaHRzcXVpZ2Fycm93OiJcdTIxYWQiLGhiYXI6Ilx1MjEwZiIsaHNsYXNoOiJcdTIxMGYiLHBsYW5jazoiXHUyMTBmIixwbGFua3Y6Ilx1MjEwZiIsaGNpcmM6Ilx1MDEyNSIsaGVhcnRzOiJcdTI2NjUiLGhlYXJ0c3VpdDoiXHUyNjY1IixoZWxsaXA6Ilx1MjAyNiIsbWxkcjoiXHUyMDI2IixoZXJjb246Ilx1MjJiOSIsaGZyOiJcdWQ4MzVcdWRkMjUiLGhrc2Vhcm93OiJcdTI5MjUiLHNlYXJoazoiXHUyOTI1Iixoa3N3YXJvdzoiXHUyOTI2Iixzd2FyaGs6Ilx1MjkyNiIsaG9hcnI6Ilx1MjFmZiIsaG9tdGh0OiJcdTIyM2IiLGhvb2tsZWZ0YXJyb3c6Ilx1MjFhOSIsbGFycmhrOiJcdTIxYTkiLGhvb2tyaWdodGFycm93OiJcdTIxYWEiLHJhcnJoazoiXHUyMWFhIixob3BmOiJcdWQ4MzVcdWRkNTkiLGhvcmJhcjoiXHUyMDE1Iixoc2NyOiJcdWQ4MzVcdWRjYmQiLGhzdHJvazoiXHUwMTI3IixoeWJ1bGw6Ilx1MjA0MyIsaWFjdXRlOiJceGVkIixpY2lyYzoiXHhlZSIsaWN5OiJcdTA0MzgiLGllY3k6Ilx1MDQzNSIsaWV4Y2w6Ilx4YTEiLGlmcjoiXHVkODM1XHVkZDI2IixpZ3JhdmU6Ilx4ZWMiLGlpaWludDoiXHUyYTBjIixxaW50OiJcdTJhMGMiLGlpaW50OiJcdTIyMmQiLHRpbnQ6Ilx1MjIyZCIsaWluZmluOiJcdTI5ZGMiLGlpb3RhOiJcdTIxMjkiLGlqbGlnOiJcdTAxMzMiLGltYWNyOiJcdTAxMmIiLGltYXRoOiJcdTAxMzEiLGlub2RvdDoiXHUwMTMxIixpbW9mOiJcdTIyYjciLGltcGVkOiJcdTAxYjUiLGluY2FyZToiXHUyMTA1IixpbmZpbjoiXHUyMjFlIixpbmZpbnRpZToiXHUyOWRkIixpbnRjYWw6Ilx1MjJiYSIsaW50ZXJjYWw6Ilx1MjJiYSIsaW50bGFyaGs6Ilx1MmExNyIsaW50cHJvZDoiXHUyYTNjIixpcHJvZDoiXHUyYTNjIixpb2N5OiJcdTA0NTEiLGlvZ29uOiJcdTAxMmYiLGlvcGY6Ilx1ZDgzNVx1ZGQ1YSIsaW90YToiXHUwM2I5IixpcXVlc3Q6Ilx4YmYiLGlzY3I6Ilx1ZDgzNVx1ZGNiZSIsaXNpbkU6Ilx1MjJmOSIsaXNpbmRvdDoiXHUyMmY1Iixpc2luczoiXHUyMmY0Iixpc2luc3Y6Ilx1MjJmMyIsaXRpbGRlOiJcdTAxMjkiLGl1a2N5OiJcdTA0NTYiLGl1bWw6Ilx4ZWYiLGpjaXJjOiJcdTAxMzUiLGpjeToiXHUwNDM5IixqZnI6Ilx1ZDgzNVx1ZGQyNyIsam1hdGg6Ilx1MDIzNyIsam9wZjoiXHVkODM1XHVkZDViIixqc2NyOiJcdWQ4MzVcdWRjYmYiLGpzZXJjeToiXHUwNDU4IixqdWtjeToiXHUwNDU0IixrYXBwYToiXHUwM2JhIixrYXBwYXY6Ilx1MDNmMCIsdmFya2FwcGE6Ilx1MDNmMCIsa2NlZGlsOiJcdTAxMzciLGtjeToiXHUwNDNhIixrZnI6Ilx1ZDgzNVx1ZGQyOCIsa2dyZWVuOiJcdTAxMzgiLGtoY3k6Ilx1MDQ0NSIsa2pjeToiXHUwNDVjIixrb3BmOiJcdWQ4MzVcdWRkNWMiLGtzY3I6Ilx1ZDgzNVx1ZGNjMCIsbEF0YWlsOiJcdTI5MWIiLGxCYXJyOiJcdTI5MGUiLGxFZzoiXHUyYThiIixsZXNzZXFxZ3RyOiJcdTJhOGIiLGxIYXI6Ilx1Mjk2MiIsbGFjdXRlOiJcdTAxM2EiLGxhZW1wdHl2OiJcdTI5YjQiLGxhbWJkYToiXHUwM2JiIixsYW5nZDoiXHUyOTkxIixsYXA6Ilx1MmE4NSIsbGVzc2FwcHJveDoiXHUyYTg1IixsYXF1bzoiXHhhYiIsbGFycmJmczoiXHUyOTFmIixsYXJyZnM6Ilx1MjkxZCIsbGFycmxwOiJcdTIxYWIiLGxvb3BhcnJvd2xlZnQ6Ilx1MjFhYiIsbGFycnBsOiJcdTI5MzkiLGxhcnJzaW06Ilx1Mjk3MyIsbGFycnRsOiJcdTIxYTIiLGxlZnRhcnJvd3RhaWw6Ilx1MjFhMiIsbGF0OiJcdTJhYWIiLGxhdGFpbDoiXHUyOTE5IixsYXRlOiJcdTJhYWQiLGxhdGVzOiJcdTJhYWRcdWZlMDAiLGxiYXJyOiJcdTI5MGMiLGxiYnJrOiJcdTI3NzIiLGxicmFjZToieyIsbGN1YjoieyIsbGJyYWNrOiJbIixsc3FiOiJbIixsYnJrZToiXHUyOThiIixsYnJrc2xkOiJcdTI5OGYiLGxicmtzbHU6Ilx1Mjk4ZCIsbGNhcm9uOiJcdTAxM2UiLGxjZWRpbDoiXHUwMTNjIixsY3k6Ilx1MDQzYiIsbGRjYToiXHUyOTM2IixsZHJkaGFyOiJcdTI5NjciLGxkcnVzaGFyOiJcdTI5NGIiLGxkc2g6Ilx1MjFiMiIsbGU6Ilx1MjI2NCIsbGVxOiJcdTIyNjQiLGxlZnRsZWZ0YXJyb3dzOiJcdTIxYzciLGxsYXJyOiJcdTIxYzciLGxlZnR0aHJlZXRpbWVzOiJcdTIyY2IiLGx0aHJlZToiXHUyMmNiIixsZXNjYzoiXHUyYWE4IixsZXNkb3Q6Ilx1MmE3ZiIsbGVzZG90bzoiXHUyYTgxIixsZXNkb3RvcjoiXHUyYTgzIixsZXNnOiJcdTIyZGFcdWZlMDAiLGxlc2dlczoiXHUyYTkzIixsZXNzZG90OiJcdTIyZDYiLGx0ZG90OiJcdTIyZDYiLGxmaXNodDoiXHUyOTdjIixsZnI6Ilx1ZDgzNVx1ZGQyOSIsbGdFOiJcdTJhOTEiLGxoYXJ1bDoiXHUyOTZhIixsaGJsazoiXHUyNTg0IixsamN5OiJcdTA0NTkiLGxsaGFyZDoiXHUyOTZiIixsbHRyaToiXHUyNWZhIixsbWlkb3Q6Ilx1MDE0MCIsbG1vdXN0OiJcdTIzYjAiLGxtb3VzdGFjaGU6Ilx1MjNiMCIsbG5FOiJcdTIyNjgiLGxuZXFxOiJcdTIyNjgiLGxuYXA6Ilx1MmE4OSIsbG5hcHByb3g6Ilx1MmE4OSIsbG5lOiJcdTJhODciLGxuZXE6Ilx1MmE4NyIsbG5zaW06Ilx1MjJlNiIsbG9hbmc6Ilx1MjdlYyIsbG9hcnI6Ilx1MjFmZCIsbG9uZ21hcHN0bzoiXHUyN2ZjIix4bWFwOiJcdTI3ZmMiLGxvb3BhcnJvd3JpZ2h0OiJcdTIxYWMiLHJhcnJscDoiXHUyMWFjIixsb3BhcjoiXHUyOTg1Iixsb3BmOiJcdWQ4MzVcdWRkNWQiLGxvcGx1czoiXHUyYTJkIixsb3RpbWVzOiJcdTJhMzQiLGxvd2FzdDoiXHUyMjE3Iixsb3o6Ilx1MjVjYSIsbG96ZW5nZToiXHUyNWNhIixscGFyOiIoIixscGFybHQ6Ilx1Mjk5MyIsbHJoYXJkOiJcdTI5NmQiLGxybToiXHUyMDBlIixscnRyaToiXHUyMmJmIixsc2FxdW86Ilx1MjAzOSIsbHNjcjoiXHVkODM1XHVkY2MxIixsc2ltZToiXHUyYThkIixsc2ltZzoiXHUyYThmIixsc3F1b3I6Ilx1MjAxYSIsc2JxdW86Ilx1MjAxYSIsbHN0cm9rOiJcdTAxNDIiLGx0Y2M6Ilx1MmFhNiIsbHRjaXI6Ilx1MmE3OSIsbHRpbWVzOiJcdTIyYzkiLGx0bGFycjoiXHUyOTc2IixsdHF1ZXN0OiJcdTJhN2IiLGx0clBhcjoiXHUyOTk2IixsdHJpOiJcdTI1YzMiLHRyaWFuZ2xlbGVmdDoiXHUyNWMzIixsdXJkc2hhcjoiXHUyOTRhIixsdXJ1aGFyOiJcdTI5NjYiLGx2ZXJ0bmVxcToiXHUyMjY4XHVmZTAwIixsdm5FOiJcdTIyNjhcdWZlMDAiLG1ERG90OiJcdTIyM2EiLG1hY3I6Ilx4YWYiLHN0cm5zOiJceGFmIixtYWxlOiJcdTI2NDIiLG1hbHQ6Ilx1MjcyMCIsbWFsdGVzZToiXHUyNzIwIixtYXJrZXI6Ilx1MjVhZSIsbWNvbW1hOiJcdTJhMjkiLG1jeToiXHUwNDNjIixtZGFzaDoiXHUyMDE0IixtZnI6Ilx1ZDgzNVx1ZGQyYSIsbWhvOiJcdTIxMjciLG1pY3JvOiJceGI1IixtaWRjaXI6Ilx1MmFmMCIsbWludXM6Ilx1MjIxMiIsbWludXNkdToiXHUyYTJhIixtbGNwOiJcdTJhZGIiLG1vZGVsczoiXHUyMmE3Iixtb3BmOiJcdWQ4MzVcdWRkNWUiLG1zY3I6Ilx1ZDgzNVx1ZGNjMiIsbXU6Ilx1MDNiYyIsbXVsdGltYXA6Ilx1MjJiOCIsbXVtYXA6Ilx1MjJiOCIsbkdnOiJcdTIyZDlcdTAzMzgiLG5HdDoiXHUyMjZiXHUyMGQyIixuTGVmdGFycm93OiJcdTIxY2QiLG5sQXJyOiJcdTIxY2QiLG5MZWZ0cmlnaHRhcnJvdzoiXHUyMWNlIixuaEFycjoiXHUyMWNlIixuTGw6Ilx1MjJkOFx1MDMzOCIsbkx0OiJcdTIyNmFcdTIwZDIiLG5SaWdodGFycm93OiJcdTIxY2YiLG5yQXJyOiJcdTIxY2YiLG5WRGFzaDoiXHUyMmFmIixuVmRhc2g6Ilx1MjJhZSIsbmFjdXRlOiJcdTAxNDQiLG5hbmc6Ilx1MjIyMFx1MjBkMiIsbmFwRToiXHUyYTcwXHUwMzM4IixuYXBpZDoiXHUyMjRiXHUwMzM4IixuYXBvczoiXHUwMTQ5IixuYXR1cjoiXHUyNjZlIixuYXR1cmFsOiJcdTI2NmUiLG5jYXA6Ilx1MmE0MyIsbmNhcm9uOiJcdTAxNDgiLG5jZWRpbDoiXHUwMTQ2IixuY29uZ2RvdDoiXHUyYTZkXHUwMzM4IixuY3VwOiJcdTJhNDIiLG5jeToiXHUwNDNkIixuZGFzaDoiXHUyMDEzIixuZUFycjoiXHUyMWQ3IixuZWFyaGs6Ilx1MjkyNCIsbmVkb3Q6Ilx1MjI1MFx1MDMzOCIsbmVzZWFyOiJcdTI5MjgiLHRvZWE6Ilx1MjkyOCIsbmZyOiJcdWQ4MzVcdWRkMmIiLG5oYXJyOiJcdTIxYWUiLG5sZWZ0cmlnaHRhcnJvdzoiXHUyMWFlIixuaHBhcjoiXHUyYWYyIixuaXM6Ilx1MjJmYyIsbmlzZDoiXHUyMmZhIixuamN5OiJcdTA0NWEiLG5sRToiXHUyMjY2XHUwMzM4IixubGVxcToiXHUyMjY2XHUwMzM4IixubGFycjoiXHUyMTlhIixubGVmdGFycm93OiJcdTIxOWEiLG5sZHI6Ilx1MjAyNSIsbm9wZjoiXHVkODM1XHVkZDVmIixub3Q6Ilx4YWMiLG5vdGluRToiXHUyMmY5XHUwMzM4Iixub3RpbmRvdDoiXHUyMmY1XHUwMzM4Iixub3RpbnZiOiJcdTIyZjciLG5vdGludmM6Ilx1MjJmNiIsbm90bml2YjoiXHUyMmZlIixub3RuaXZjOiJcdTIyZmQiLG5wYXJzbDoiXHUyYWZkXHUyMGU1IixucGFydDoiXHUyMjAyXHUwMzM4IixucG9saW50OiJcdTJhMTQiLG5yYXJyOiJcdTIxOWIiLG5yaWdodGFycm93OiJcdTIxOWIiLG5yYXJyYzoiXHUyOTMzXHUwMzM4IixucmFycnc6Ilx1MjE5ZFx1MDMzOCIsbnNjcjoiXHVkODM1XHVkY2MzIixuc3ViOiJcdTIyODQiLG5zdWJFOiJcdTJhYzVcdTAzMzgiLG5zdWJzZXRlcXE6Ilx1MmFjNVx1MDMzOCIsbnN1cDoiXHUyMjg1Iixuc3VwRToiXHUyYWM2XHUwMzM4Iixuc3Vwc2V0ZXFxOiJcdTJhYzZcdTAzMzgiLG50aWxkZToiXHhmMSIsbnU6Ilx1MDNiZCIsbnVtOiIjIixudW1lcm86Ilx1MjExNiIsbnVtc3A6Ilx1MjAwNyIsbnZEYXNoOiJcdTIyYWQiLG52SGFycjoiXHUyOTA0IixudmFwOiJcdTIyNGRcdTIwZDIiLG52ZGFzaDoiXHUyMmFjIixudmdlOiJcdTIyNjVcdTIwZDIiLG52Z3Q6Ij5cdTIwZDIiLG52aW5maW46Ilx1MjlkZSIsbnZsQXJyOiJcdTI5MDIiLG52bGU6Ilx1MjI2NFx1MjBkMiIsbnZsdDoiPFx1MjBkMiIsbnZsdHJpZToiXHUyMmI0XHUyMGQyIixudnJBcnI6Ilx1MjkwMyIsbnZydHJpZToiXHUyMmI1XHUyMGQyIixudnNpbToiXHUyMjNjXHUyMGQyIixud0FycjoiXHUyMWQ2Iixud2FyaGs6Ilx1MjkyMyIsbnduZWFyOiJcdTI5MjciLG9hY3V0ZToiXHhmMyIsb2NpcmM6Ilx4ZjQiLG9jeToiXHUwNDNlIixvZGJsYWM6Ilx1MDE1MSIsb2RpdjoiXHUyYTM4IixvZHNvbGQ6Ilx1MjliYyIsb2VsaWc6Ilx1MDE1MyIsb2ZjaXI6Ilx1MjliZiIsb2ZyOiJcdWQ4MzVcdWRkMmMiLG9nb246Ilx1MDJkYiIsb2dyYXZlOiJceGYyIixvZ3Q6Ilx1MjljMSIsb2hiYXI6Ilx1MjliNSIsb2xjaXI6Ilx1MjliZSIsb2xjcm9zczoiXHUyOWJiIixvbHQ6Ilx1MjljMCIsb21hY3I6Ilx1MDE0ZCIsb21lZ2E6Ilx1MDNjOSIsb21pY3JvbjoiXHUwM2JmIixvbWlkOiJcdTI5YjYiLG9vcGY6Ilx1ZDgzNVx1ZGQ2MCIsb3BhcjoiXHUyOWI3IixvcGVycDoiXHUyOWI5IixvcjoiXHUyMjI4Iix2ZWU6Ilx1MjIyOCIsb3JkOiJcdTJhNWQiLG9yZGVyOiJcdTIxMzQiLG9yZGVyb2Y6Ilx1MjEzNCIsb3NjcjoiXHUyMTM0IixvcmRmOiJceGFhIixvcmRtOiJceGJhIixvcmlnb2Y6Ilx1MjJiNiIsb3JvcjoiXHUyYTU2IixvcnNsb3BlOiJcdTJhNTciLG9ydjoiXHUyYTViIixvc2xhc2g6Ilx4ZjgiLG9zb2w6Ilx1MjI5OCIsb3RpbGRlOiJceGY1IixvdGltZXNhczoiXHUyYTM2IixvdW1sOiJceGY2IixvdmJhcjoiXHUyMzNkIixwYXJhOiJceGI2IixwYXJzaW06Ilx1MmFmMyIscGFyc2w6Ilx1MmFmZCIscGN5OiJcdTA0M2YiLHBlcmNudDoiJSIscGVyaW9kOiIuIixwZXJtaWw6Ilx1MjAzMCIscGVydGVuazoiXHUyMDMxIixwZnI6Ilx1ZDgzNVx1ZGQyZCIscGhpOiJcdTAzYzYiLHBoaXY6Ilx1MDNkNSIsc3RyYWlnaHRwaGk6Ilx1MDNkNSIsdmFycGhpOiJcdTAzZDUiLHBob25lOiJcdTI2MGUiLHBpOiJcdTAzYzAiLHBpdjoiXHUwM2Q2Iix2YXJwaToiXHUwM2Q2IixwbGFuY2toOiJcdTIxMGUiLHBsdXM6IisiLHBsdXNhY2lyOiJcdTJhMjMiLHBsdXNjaXI6Ilx1MmEyMiIscGx1c2R1OiJcdTJhMjUiLHBsdXNlOiJcdTJhNzIiLHBsdXNzaW06Ilx1MmEyNiIscGx1c3R3bzoiXHUyYTI3Iixwb2ludGludDoiXHUyYTE1Iixwb3BmOiJcdWQ4MzVcdWRkNjEiLHBvdW5kOiJceGEzIixwckU6Ilx1MmFiMyIscHJhcDoiXHUyYWI3IixwcmVjYXBwcm94OiJcdTJhYjciLHByZWNuYXBwcm94OiJcdTJhYjkiLHBybmFwOiJcdTJhYjkiLHByZWNuZXFxOiJcdTJhYjUiLHBybkU6Ilx1MmFiNSIscHJlY25zaW06Ilx1MjJlOCIscHJuc2ltOiJcdTIyZTgiLHByaW1lOiJcdTIwMzIiLHByb2ZhbGFyOiJcdTIzMmUiLHByb2ZsaW5lOiJcdTIzMTIiLHByb2ZzdXJmOiJcdTIzMTMiLHBydXJlbDoiXHUyMmIwIixwc2NyOiJcdWQ4MzVcdWRjYzUiLHBzaToiXHUwM2M4IixwdW5jc3A6Ilx1MjAwOCIscWZyOiJcdWQ4MzVcdWRkMmUiLHFvcGY6Ilx1ZDgzNVx1ZGQ2MiIscXByaW1lOiJcdTIwNTciLHFzY3I6Ilx1ZDgzNVx1ZGNjNiIscXVhdGludDoiXHUyYTE2IixxdWVzdDoiPyIsckF0YWlsOiJcdTI5MWMiLHJIYXI6Ilx1Mjk2NCIscmFjZToiXHUyMjNkXHUwMzMxIixyYWN1dGU6Ilx1MDE1NSIscmFlbXB0eXY6Ilx1MjliMyIscmFuZ2Q6Ilx1Mjk5MiIscmFuZ2U6Ilx1MjlhNSIscmFxdW86Ilx4YmIiLHJhcnJhcDoiXHUyOTc1IixyYXJyYmZzOiJcdTI5MjAiLHJhcnJjOiJcdTI5MzMiLHJhcnJmczoiXHUyOTFlIixyYXJycGw6Ilx1Mjk0NSIscmFycnNpbToiXHUyOTc0IixyYXJydGw6Ilx1MjFhMyIscmlnaHRhcnJvd3RhaWw6Ilx1MjFhMyIscmFycnc6Ilx1MjE5ZCIscmlnaHRzcXVpZ2Fycm93OiJcdTIxOWQiLHJhdGFpbDoiXHUyOTFhIixyYXRpbzoiXHUyMjM2IixyYmJyazoiXHUyNzczIixyYnJhY2U6In0iLHJjdWI6In0iLHJicmFjazoiXSIscnNxYjoiXSIscmJya2U6Ilx1Mjk4YyIscmJya3NsZDoiXHUyOThlIixyYnJrc2x1OiJcdTI5OTAiLHJjYXJvbjoiXHUwMTU5IixyY2VkaWw6Ilx1MDE1NyIscmN5OiJcdTA0NDAiLHJkY2E6Ilx1MjkzNyIscmRsZGhhcjoiXHUyOTY5IixyZHNoOiJcdTIxYjMiLHJlY3Q6Ilx1MjVhZCIscmZpc2h0OiJcdTI5N2QiLHJmcjoiXHVkODM1XHVkZDJmIixyaGFydWw6Ilx1Mjk2YyIscmhvOiJcdTAzYzEiLHJob3Y6Ilx1MDNmMSIsdmFycmhvOiJcdTAzZjEiLHJpZ2h0cmlnaHRhcnJvd3M6Ilx1MjFjOSIscnJhcnI6Ilx1MjFjOSIscmlnaHR0aHJlZXRpbWVzOiJcdTIyY2MiLHJ0aHJlZToiXHUyMmNjIixyaW5nOiJcdTAyZGEiLHJsbToiXHUyMDBmIixybW91c3Q6Ilx1MjNiMSIscm1vdXN0YWNoZToiXHUyM2IxIixybm1pZDoiXHUyYWVlIixyb2FuZzoiXHUyN2VkIixyb2FycjoiXHUyMWZlIixyb3BhcjoiXHUyOTg2Iixyb3BmOiJcdWQ4MzVcdWRkNjMiLHJvcGx1czoiXHUyYTJlIixyb3RpbWVzOiJcdTJhMzUiLHJwYXI6IikiLHJwYXJndDoiXHUyOTk0IixycHBvbGludDoiXHUyYTEyIixyc2FxdW86Ilx1MjAzYSIscnNjcjoiXHVkODM1XHVkY2M3IixydGltZXM6Ilx1MjJjYSIscnRyaToiXHUyNWI5Iix0cmlhbmdsZXJpZ2h0OiJcdTI1YjkiLHJ0cmlsdHJpOiJcdTI5Y2UiLHJ1bHVoYXI6Ilx1Mjk2OCIscng6Ilx1MjExZSIsc2FjdXRlOiJcdTAxNWIiLHNjRToiXHUyYWI0IixzY2FwOiJcdTJhYjgiLHN1Y2NhcHByb3g6Ilx1MmFiOCIsc2Nhcm9uOiJcdTAxNjEiLHNjZWRpbDoiXHUwMTVmIixzY2lyYzoiXHUwMTVkIixzY25FOiJcdTJhYjYiLHN1Y2NuZXFxOiJcdTJhYjYiLHNjbmFwOiJcdTJhYmEiLHN1Y2NuYXBwcm94OiJcdTJhYmEiLHNjbnNpbToiXHUyMmU5IixzdWNjbnNpbToiXHUyMmU5IixzY3BvbGludDoiXHUyYTEzIixzY3k6Ilx1MDQ0MSIsc2RvdDoiXHUyMmM1IixzZG90ZToiXHUyYTY2IixzZUFycjoiXHUyMWQ4IixzZWN0OiJceGE3IixzZW1pOiI7IixzZXN3YXI6Ilx1MjkyOSIsdG9zYToiXHUyOTI5IixzZXh0OiJcdTI3MzYiLHNmcjoiXHVkODM1XHVkZDMwIixzaGFycDoiXHUyNjZmIixzaGNoY3k6Ilx1MDQ0OSIsc2hjeToiXHUwNDQ4IixzaHk6Ilx4YWQiLHNpZ21hOiJcdTAzYzMiLHNpZ21hZjoiXHUwM2MyIixzaWdtYXY6Ilx1MDNjMiIsdmFyc2lnbWE6Ilx1MDNjMiIsc2ltZG90OiJcdTJhNmEiLHNpbWc6Ilx1MmE5ZSIsc2ltZ0U6Ilx1MmFhMCIsc2ltbDoiXHUyYTlkIixzaW1sRToiXHUyYTlmIixzaW1uZToiXHUyMjQ2IixzaW1wbHVzOiJcdTJhMjQiLHNpbXJhcnI6Ilx1Mjk3MiIsc21hc2hwOiJcdTJhMzMiLHNtZXBhcnNsOiJcdTI5ZTQiLHNtaWxlOiJcdTIzMjMiLHNzbWlsZToiXHUyMzIzIixzbXQ6Ilx1MmFhYSIsc210ZToiXHUyYWFjIixzbXRlczoiXHUyYWFjXHVmZTAwIixzb2Z0Y3k6Ilx1MDQ0YyIsc29sOiIvIixzb2xiOiJcdTI5YzQiLHNvbGJhcjoiXHUyMzNmIixzb3BmOiJcdWQ4MzVcdWRkNjQiLHNwYWRlczoiXHUyNjYwIixzcGFkZXN1aXQ6Ilx1MjY2MCIsc3FjYXBzOiJcdTIyOTNcdWZlMDAiLHNxY3VwczoiXHUyMjk0XHVmZTAwIixzc2NyOiJcdWQ4MzVcdWRjYzgiLHN0YXI6Ilx1MjYwNiIsc3ViOiJcdTIyODIiLHN1YnNldDoiXHUyMjgyIixzdWJFOiJcdTJhYzUiLHN1YnNldGVxcToiXHUyYWM1IixzdWJkb3Q6Ilx1MmFiZCIsc3ViZWRvdDoiXHUyYWMzIixzdWJtdWx0OiJcdTJhYzEiLHN1Ym5FOiJcdTJhY2IiLHN1YnNldG5lcXE6Ilx1MmFjYiIsc3VibmU6Ilx1MjI4YSIsc3Vic2V0bmVxOiJcdTIyOGEiLHN1YnBsdXM6Ilx1MmFiZiIsc3VicmFycjoiXHUyOTc5IixzdWJzaW06Ilx1MmFjNyIsc3Vic3ViOiJcdTJhZDUiLHN1YnN1cDoiXHUyYWQzIixzdW5nOiJcdTI2NmEiLHN1cDE6Ilx4YjkiLHN1cDI6Ilx4YjIiLHN1cDM6Ilx4YjMiLHN1cEU6Ilx1MmFjNiIsc3Vwc2V0ZXFxOiJcdTJhYzYiLHN1cGRvdDoiXHUyYWJlIixzdXBkc3ViOiJcdTJhZDgiLHN1cGVkb3Q6Ilx1MmFjNCIsc3VwaHNvbDoiXHUyN2M5IixzdXBoc3ViOiJcdTJhZDciLHN1cGxhcnI6Ilx1Mjk3YiIsc3VwbXVsdDoiXHUyYWMyIixzdXBuRToiXHUyYWNjIixzdXBzZXRuZXFxOiJcdTJhY2MiLHN1cG5lOiJcdTIyOGIiLHN1cHNldG5lcToiXHUyMjhiIixzdXBwbHVzOiJcdTJhYzAiLHN1cHNpbToiXHUyYWM4IixzdXBzdWI6Ilx1MmFkNCIsc3Vwc3VwOiJcdTJhZDYiLHN3QXJyOiJcdTIxZDkiLHN3bndhcjoiXHUyOTJhIixzemxpZzoiXHhkZiIsdGFyZ2V0OiJcdTIzMTYiLHRhdToiXHUwM2M0Iix0Y2Fyb246Ilx1MDE2NSIsdGNlZGlsOiJcdTAxNjMiLHRjeToiXHUwNDQyIix0ZWxyZWM6Ilx1MjMxNSIsdGZyOiJcdWQ4MzVcdWRkMzEiLHRoZXRhOiJcdTAzYjgiLHRoZXRhc3ltOiJcdTAzZDEiLHRoZXRhdjoiXHUwM2QxIix2YXJ0aGV0YToiXHUwM2QxIix0aG9ybjoiXHhmZSIsdGltZXM6Ilx4ZDciLHRpbWVzYmFyOiJcdTJhMzEiLHRpbWVzZDoiXHUyYTMwIix0b3Bib3Q6Ilx1MjMzNiIsdG9wY2lyOiJcdTJhZjEiLHRvcGY6Ilx1ZDgzNVx1ZGQ2NSIsdG9wZm9yazoiXHUyYWRhIix0cHJpbWU6Ilx1MjAzNCIsdHJpYW5nbGU6Ilx1MjViNSIsdXRyaToiXHUyNWI1Iix0cmlhbmdsZXE6Ilx1MjI1YyIsdHJpZToiXHUyMjVjIix0cmlkb3Q6Ilx1MjVlYyIsdHJpbWludXM6Ilx1MmEzYSIsdHJpcGx1czoiXHUyYTM5Iix0cmlzYjoiXHUyOWNkIix0cml0aW1lOiJcdTJhM2IiLHRycGV6aXVtOiJcdTIzZTIiLHRzY3I6Ilx1ZDgzNVx1ZGNjOSIsdHNjeToiXHUwNDQ2Iix0c2hjeToiXHUwNDViIix0c3Ryb2s6Ilx1MDE2NyIsdUhhcjoiXHUyOTYzIix1YWN1dGU6Ilx4ZmEiLHVicmN5OiJcdTA0NWUiLHVicmV2ZToiXHUwMTZkIix1Y2lyYzoiXHhmYiIsdWN5OiJcdTA0NDMiLHVkYmxhYzoiXHUwMTcxIix1ZmlzaHQ6Ilx1Mjk3ZSIsdWZyOiJcdWQ4MzVcdWRkMzIiLHVncmF2ZToiXHhmOSIsdWhibGs6Ilx1MjU4MCIsdWxjb3JuOiJcdTIzMWMiLHVsY29ybmVyOiJcdTIzMWMiLHVsY3JvcDoiXHUyMzBmIix1bHRyaToiXHUyNWY4Iix1bWFjcjoiXHUwMTZiIix1b2dvbjoiXHUwMTczIix1b3BmOiJcdWQ4MzVcdWRkNjYiLHVwc2k6Ilx1MDNjNSIsdXBzaWxvbjoiXHUwM2M1Iix1cHVwYXJyb3dzOiJcdTIxYzgiLHV1YXJyOiJcdTIxYzgiLHVyY29ybjoiXHUyMzFkIix1cmNvcm5lcjoiXHUyMzFkIix1cmNyb3A6Ilx1MjMwZSIsdXJpbmc6Ilx1MDE2ZiIsdXJ0cmk6Ilx1MjVmOSIsdXNjcjoiXHVkODM1XHVkY2NhIix1dGRvdDoiXHUyMmYwIix1dGlsZGU6Ilx1MDE2OSIsdXVtbDoiXHhmYyIsdXdhbmdsZToiXHUyOWE3Iix2QmFyOiJcdTJhZTgiLHZCYXJ2OiJcdTJhZTkiLHZhbmdydDoiXHUyOTljIix2YXJzdWJzZXRuZXE6Ilx1MjI4YVx1ZmUwMCIsdnN1Ym5lOiJcdTIyOGFcdWZlMDAiLHZhcnN1YnNldG5lcXE6Ilx1MmFjYlx1ZmUwMCIsdnN1Ym5FOiJcdTJhY2JcdWZlMDAiLHZhcnN1cHNldG5lcToiXHUyMjhiXHVmZTAwIix2c3VwbmU6Ilx1MjI4Ylx1ZmUwMCIsdmFyc3Vwc2V0bmVxcToiXHUyYWNjXHVmZTAwIix2c3VwbkU6Ilx1MmFjY1x1ZmUwMCIsdmN5OiJcdTA0MzIiLHZlZWJhcjoiXHUyMmJiIix2ZWVlcToiXHUyMjVhIix2ZWxsaXA6Ilx1MjJlZSIsdmZyOiJcdWQ4MzVcdWRkMzMiLHZvcGY6Ilx1ZDgzNVx1ZGQ2NyIsdnNjcjoiXHVkODM1XHVkY2NiIix2emlnemFnOiJcdTI5OWEiLHdjaXJjOiJcdTAxNzUiLHdlZGJhcjoiXHUyYTVmIix3ZWRnZXE6Ilx1MjI1OSIsd2VpZXJwOiJcdTIxMTgiLHdwOiJcdTIxMTgiLHdmcjoiXHVkODM1XHVkZDM0Iix3b3BmOiJcdWQ4MzVcdWRkNjgiLHdzY3I6Ilx1ZDgzNVx1ZGNjYyIseGZyOiJcdWQ4MzVcdWRkMzUiLHhpOiJcdTAzYmUiLHhuaXM6Ilx1MjJmYiIseG9wZjoiXHVkODM1XHVkZDY5Iix4c2NyOiJcdWQ4MzVcdWRjY2QiLHlhY3V0ZToiXHhmZCIseWFjeToiXHUwNDRmIix5Y2lyYzoiXHUwMTc3Iix5Y3k6Ilx1MDQ0YiIseWVuOiJceGE1Iix5ZnI6Ilx1ZDgzNVx1ZGQzNiIseWljeToiXHUwNDU3Iix5b3BmOiJcdWQ4MzVcdWRkNmEiLHlzY3I6Ilx1ZDgzNVx1ZGNjZSIseXVjeToiXHUwNDRlIix5dW1sOiJceGZmIix6YWN1dGU6Ilx1MDE3YSIsemNhcm9uOiJcdTAxN2UiLHpjeToiXHUwNDM3Iix6ZG90OiJcdTAxN2MiLHpldGE6Ilx1MDNiNiIsemZyOiJcdWQ4MzVcdWRkMzciLHpoY3k6Ilx1MDQzNiIsemlncmFycjoiXHUyMWRkIix6b3BmOiJcdWQ4MzVcdWRkNmIiLHpzY3I6Ilx1ZDgzNVx1ZGNjZiIsendqOiJcdTIwMGQiLHp3bmo6Ilx1MjAwYyIsbmdzcDoiXHVlNTAwIn0saEM9Y2xhc3MgZXh0ZW5kcyB5bXtjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIoaSx0KSx0aGlzLnRva2VuVHlwZT1lfX0sZUVlPS9cclxuPy9nO2Z1bmN0aW9uIHJDKG4pe3JldHVybmBVbmV4cGVjdGVkIGNoYXJhY3RlciAiJHswPT09bj8iRU9GIjpTdHJpbmcuZnJvbUNoYXJDb2RlKG4pfSJgfWZ1bmN0aW9uIGlRKG4pe3JldHVybmBVbmtub3duIGVudGl0eSAiJHtufSIgLSB1c2UgdGhlICImIzxkZWNpbWFsPjsiIG9yICAiJiN4PGhleD47IiBzeW50YXhgfXZhciBmQz0oKCk9PntyZXR1cm4obj1mQ3x8KGZDPXt9KSkuSEVYPSJoZXhhZGVjaW1hbCIsbi5ERUM9ImRlY2ltYWwiLGZDO3ZhciBufSkoKSxtQz1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLmVycm9yPXR9fTtmdW5jdGlvbiBybChuKXtyZXR1cm4hUVYobil8fDA9PT1ufWZ1bmN0aW9uIHJRKG4pe3JldHVybiBRVihuKXx8NjI9PT1ufHw2MD09PW58fDQ3PT09bnx8Mzk9PT1ufHwzND09PW58fDYxPT09bnx8MD09PW59ZnVuY3Rpb24gaUVlKG4pe3JldHVybiA1OT09PW58fDA9PT1ufHwhZnVuY3Rpb24obil7cmV0dXJuIG4+PTk3JiZuPD0xMDJ8fG4+PTY1JiZuPD03MHx8JHAobil9KG4pfWZ1bmN0aW9uIHJFZShuKXtyZXR1cm4gNTk9PT1ufHwwPT09bnx8IUtWKG4pfWZ1bmN0aW9uIG9RKG4pe3JldHVybiBuPj05NyYmbjw9MTIyP24tOTcrNjU6bn12YXIgTW09Y2xhc3N7Y29uc3RydWN0b3IodCxlKXtpZih0IGluc3RhbmNlb2YgTW0pe3RoaXMuZmlsZT10LmZpbGUsdGhpcy5pbnB1dD10LmlucHV0LHRoaXMuZW5kPXQuZW5kO2xldCBpPXQuc3RhdGU7dGhpcy5zdGF0ZT17cGVlazppLnBlZWssb2Zmc2V0Omkub2Zmc2V0LGxpbmU6aS5saW5lLGNvbHVtbjppLmNvbHVtbn19ZWxzZXtpZighZSl0aHJvdyBuZXcgRXJyb3IoIlByb2dyYW1taW5nIGVycm9yOiB0aGUgcmFuZ2UgYXJndW1lbnQgbXVzdCBiZSBwcm92aWRlZCB3aXRoIGEgZmlsZSBhcmd1bWVudC4iKTt0aGlzLmZpbGU9dCx0aGlzLmlucHV0PXQuY29udGVudCx0aGlzLmVuZD1lLmVuZFBvcyx0aGlzLnN0YXRlPXtwZWVrOi0xLG9mZnNldDplLnN0YXJ0UG9zLGxpbmU6ZS5zdGFydExpbmUsY29sdW1uOmUuc3RhcnRDb2x9fX1jbG9uZSgpe3JldHVybiBuZXcgTW0odGhpcyl9cGVlaygpe3JldHVybiB0aGlzLnN0YXRlLnBlZWt9Y2hhcnNMZWZ0KCl7cmV0dXJuIHRoaXMuZW5kLXRoaXMuc3RhdGUub2Zmc2V0fWRpZmYodCl7cmV0dXJuIHRoaXMuc3RhdGUub2Zmc2V0LXQuc3RhdGUub2Zmc2V0fWFkdmFuY2UoKXt0aGlzLmFkdmFuY2VTdGF0ZSh0aGlzLnN0YXRlKX1pbml0KCl7dGhpcy51cGRhdGVQZWVrKHRoaXMuc3RhdGUpfWdldFNwYW4odCxlKXtsZXQgaT10PXR8fHRoaXM7aWYoZSlmb3IoO3RoaXMuZGlmZih0KT4wJiYtMSE9PWUuaW5kZXhPZih0LnBlZWsoKSk7KWk9PT10JiYodD10LmNsb25lKCkpLHQuYWR2YW5jZSgpO2xldCByPXRoaXMubG9jYXRpb25Gcm9tQ3Vyc29yKHQpLG89dGhpcy5sb2NhdGlvbkZyb21DdXJzb3IodGhpcykscz1pIT09dD90aGlzLmxvY2F0aW9uRnJvbUN1cnNvcihpKTpyO3JldHVybiBuZXcgR28ocixvLHMpfWdldENoYXJzKHQpe3JldHVybiB0aGlzLmlucHV0LnN1YnN0cmluZyh0LnN0YXRlLm9mZnNldCx0aGlzLnN0YXRlLm9mZnNldCl9Y2hhckF0KHQpe3JldHVybiB0aGlzLmlucHV0LmNoYXJDb2RlQXQodCl9YWR2YW5jZVN0YXRlKHQpe2lmKHQub2Zmc2V0Pj10aGlzLmVuZCl0aHJvdyB0aGlzLnN0YXRlPXQsbmV3IFlDKCdVbmV4cGVjdGVkIGNoYXJhY3RlciAiRU9GIicsdGhpcyk7bGV0IGU9dGhpcy5jaGFyQXQodC5vZmZzZXQpOzEwPT09ZT8odC5saW5lKyssdC5jb2x1bW49MCk6Y0soZSl8fHQuY29sdW1uKyssdC5vZmZzZXQrKyx0aGlzLnVwZGF0ZVBlZWsodCl9dXBkYXRlUGVlayh0KXt0LnBlZWs9dC5vZmZzZXQ+PXRoaXMuZW5kPzA6dGhpcy5jaGFyQXQodC5vZmZzZXQpfWxvY2F0aW9uRnJvbUN1cnNvcih0KXtyZXR1cm4gbmV3IHZtKHQuZmlsZSx0LnN0YXRlLm9mZnNldCx0LnN0YXRlLmxpbmUsdC5zdGF0ZS5jb2x1bW4pfX0sTF89Y2xhc3MgZXh0ZW5kcyBNbXtjb25zdHJ1Y3Rvcih0LGUpe3QgaW5zdGFuY2VvZiBMXz8oc3VwZXIodCksdGhpcy5pbnRlcm5hbFN0YXRlPXsuLi50LmludGVybmFsU3RhdGV9KTooc3VwZXIodCxlKSx0aGlzLmludGVybmFsU3RhdGU9dGhpcy5zdGF0ZSl9YWR2YW5jZSgpe3RoaXMuc3RhdGU9dGhpcy5pbnRlcm5hbFN0YXRlLHN1cGVyLmFkdmFuY2UoKSx0aGlzLnByb2Nlc3NFc2NhcGVTZXF1ZW5jZSgpfWluaXQoKXtzdXBlci5pbml0KCksdGhpcy5wcm9jZXNzRXNjYXBlU2VxdWVuY2UoKX1jbG9uZSgpe3JldHVybiBuZXcgTF8odGhpcyl9Z2V0Q2hhcnModCl7bGV0IGU9dC5jbG9uZSgpLGk9IiI7Zm9yKDtlLmludGVybmFsU3RhdGUub2Zmc2V0PHRoaXMuaW50ZXJuYWxTdGF0ZS5vZmZzZXQ7KWkrPVN0cmluZy5mcm9tQ29kZVBvaW50KGUucGVlaygpKSxlLmFkdmFuY2UoKTtyZXR1cm4gaX1wcm9jZXNzRXNjYXBlU2VxdWVuY2UoKXtsZXQgdD0oKT0+dGhpcy5pbnRlcm5hbFN0YXRlLnBlZWs7aWYoOTI9PT10KCkpaWYodGhpcy5pbnRlcm5hbFN0YXRlPXsuLi50aGlzLnN0YXRlfSx0aGlzLmFkdmFuY2VTdGF0ZSh0aGlzLmludGVybmFsU3RhdGUpLDExMD09PXQoKSl0aGlzLnN0YXRlLnBlZWs9MTA7ZWxzZSBpZigxMTQ9PT10KCkpdGhpcy5zdGF0ZS5wZWVrPTEzO2Vsc2UgaWYoMTE4PT09dCgpKXRoaXMuc3RhdGUucGVlaz0xMTtlbHNlIGlmKDExNj09PXQoKSl0aGlzLnN0YXRlLnBlZWs9OTtlbHNlIGlmKDk4PT09dCgpKXRoaXMuc3RhdGUucGVlaz04O2Vsc2UgaWYoMTAyPT09dCgpKXRoaXMuc3RhdGUucGVlaz0xMjtlbHNlIGlmKDExNz09PXQoKSlpZih0aGlzLmFkdmFuY2VTdGF0ZSh0aGlzLmludGVybmFsU3RhdGUpLHQoKT09PXRoKXt0aGlzLmFkdmFuY2VTdGF0ZSh0aGlzLmludGVybmFsU3RhdGUpO2xldCBlPXRoaXMuY2xvbmUoKSxpPTA7Zm9yKDt0KCkhPT1PdTspdGhpcy5hZHZhbmNlU3RhdGUodGhpcy5pbnRlcm5hbFN0YXRlKSxpKys7dGhpcy5zdGF0ZS5wZWVrPXRoaXMuZGVjb2RlSGV4RGlnaXRzKGUsaSl9ZWxzZXtsZXQgZT10aGlzLmNsb25lKCk7dGhpcy5hZHZhbmNlU3RhdGUodGhpcy5pbnRlcm5hbFN0YXRlKSx0aGlzLmFkdmFuY2VTdGF0ZSh0aGlzLmludGVybmFsU3RhdGUpLHRoaXMuYWR2YW5jZVN0YXRlKHRoaXMuaW50ZXJuYWxTdGF0ZSksdGhpcy5zdGF0ZS5wZWVrPXRoaXMuZGVjb2RlSGV4RGlnaXRzKGUsNCl9ZWxzZSBpZigxMjA9PT10KCkpe3RoaXMuYWR2YW5jZVN0YXRlKHRoaXMuaW50ZXJuYWxTdGF0ZSk7bGV0IGU9dGhpcy5jbG9uZSgpO3RoaXMuYWR2YW5jZVN0YXRlKHRoaXMuaW50ZXJuYWxTdGF0ZSksdGhpcy5zdGF0ZS5wZWVrPXRoaXMuZGVjb2RlSGV4RGlnaXRzKGUsMil9ZWxzZSBpZihWWCh0KCkpKXtsZXQgZT0iIixpPTAscj10aGlzLmNsb25lKCk7Zm9yKDtWWCh0KCkpJiZpPDM7KXI9dGhpcy5jbG9uZSgpLGUrPVN0cmluZy5mcm9tQ29kZVBvaW50KHQoKSksdGhpcy5hZHZhbmNlU3RhdGUodGhpcy5pbnRlcm5hbFN0YXRlKSxpKys7dGhpcy5zdGF0ZS5wZWVrPXBhcnNlSW50KGUsOCksdGhpcy5pbnRlcm5hbFN0YXRlPXIuaW50ZXJuYWxTdGF0ZX1lbHNlIGNLKHRoaXMuaW50ZXJuYWxTdGF0ZS5wZWVrKT8odGhpcy5hZHZhbmNlU3RhdGUodGhpcy5pbnRlcm5hbFN0YXRlKSx0aGlzLnN0YXRlPXRoaXMuaW50ZXJuYWxTdGF0ZSk6dGhpcy5zdGF0ZS5wZWVrPXRoaXMuaW50ZXJuYWxTdGF0ZS5wZWVrfWRlY29kZUhleERpZ2l0cyh0LGUpe2xldCBpPXRoaXMuaW5wdXQuc2xpY2UodC5pbnRlcm5hbFN0YXRlLm9mZnNldCx0LmludGVybmFsU3RhdGUub2Zmc2V0K2UpLHI9cGFyc2VJbnQoaSwxNik7aWYoaXNOYU4ocikpdGhyb3cgdC5zdGF0ZT10LmludGVybmFsU3RhdGUsbmV3IFlDKCJJbnZhbGlkIGhleGFkZWNpbWFsIGVzY2FwZSBzZXF1ZW5jZSIsdCk7cmV0dXJuIHJ9fSxZQz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMubXNnPXQsdGhpcy5jdXJzb3I9ZX19LG9sPWNsYXNzIGV4dGVuZHMgeW17Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKGUsaSksdGhpcy5lbGVtZW50TmFtZT10fXN0YXRpYyBjcmVhdGUodCxlLGkpe3JldHVybiBuZXcgb2wodCxlLGkpfX0sTUQ9Y2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLnJvb3ROb2Rlcz10LHRoaXMuZXJyb3JzPWV9fSxnVj1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLmdldFRhZ0RlZmluaXRpb249dH1wYXJzZSh0LGUsaSl7bGV0IHI9ZnVuY3Rpb24obix0LGUsaT17fSl7bGV0IHI9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLl9nZXRUYWdEZWZpbml0aW9uPWUsdGhpcy5fY3VycmVudFRva2VuU3RhcnQ9bnVsbCx0aGlzLl9jdXJyZW50VG9rZW5UeXBlPW51bGwsdGhpcy5fZXhwYW5zaW9uQ2FzZVN0YWNrPVtdLHRoaXMuX2luSW50ZXJwb2xhdGlvbj0hMSx0aGlzLnRva2Vucz1bXSx0aGlzLmVycm9ycz1bXSx0aGlzLm5vbk5vcm1hbGl6ZWRJY3VFeHByZXNzaW9ucz1bXSx0aGlzLl90b2tlbml6ZUljdT1pLnRva2VuaXplRXhwYW5zaW9uRm9ybXN8fCExLHRoaXMuX2ludGVycG9sYXRpb25Db25maWc9aS5pbnRlcnBvbGF0aW9uQ29uZmlnfHxQdSx0aGlzLl9sZWFkaW5nVHJpdmlhQ29kZVBvaW50cz1pLmxlYWRpbmdUcml2aWFDaGFycyYmaS5sZWFkaW5nVHJpdmlhQ2hhcnMubWFwKG89Pm8uY29kZVBvaW50QXQoMCl8fDApO2xldCByPWkucmFuZ2V8fHtlbmRQb3M6dC5jb250ZW50Lmxlbmd0aCxzdGFydFBvczowLHN0YXJ0TGluZTowLHN0YXJ0Q29sOjB9O3RoaXMuX2N1cnNvcj1pLmVzY2FwZWRTdHJpbmc/bmV3IExfKHQscik6bmV3IE1tKHQsciksdGhpcy5fcHJlc2VydmVMaW5lRW5kaW5ncz1pLnByZXNlcnZlTGluZUVuZGluZ3N8fCExLHRoaXMuX2VzY2FwZWRTdHJpbmc9aS5lc2NhcGVkU3RyaW5nfHwhMSx0aGlzLl9pMThuTm9ybWFsaXplTGluZUVuZGluZ3NJbklDVXM9aS5pMThuTm9ybWFsaXplTGluZUVuZGluZ3NJbklDVXN8fCExO3RyeXt0aGlzLl9jdXJzb3IuaW5pdCgpfWNhdGNoKG8pe3RoaXMuaGFuZGxlRXJyb3Iobyl9fV9wcm9jZXNzQ2FycmlhZ2VSZXR1cm5zKHQpe3JldHVybiB0aGlzLl9wcmVzZXJ2ZUxpbmVFbmRpbmdzP3Q6dC5yZXBsYWNlKGVFZSwiXG4iKX10b2tlbml6ZSgpe2Zvcig7MCE9PXRoaXMuX2N1cnNvci5wZWVrKCk7KXtsZXQgdD10aGlzLl9jdXJzb3IuY2xvbmUoKTt0cnl7dGhpcy5fYXR0ZW1wdENoYXJDb2RlKDYwKT90aGlzLl9hdHRlbXB0Q2hhckNvZGUoMzMpP3RoaXMuX2F0dGVtcHRDaGFyQ29kZSg5MSk/dGhpcy5fY29uc3VtZUNkYXRhKHQpOnRoaXMuX2F0dGVtcHRDaGFyQ29kZSg0NSk/dGhpcy5fY29uc3VtZUNvbW1lbnQodCk6dGhpcy5fY29uc3VtZURvY1R5cGUodCk6dGhpcy5fYXR0ZW1wdENoYXJDb2RlKDQ3KT90aGlzLl9jb25zdW1lVGFnQ2xvc2UodCk6dGhpcy5fY29uc3VtZVRhZ09wZW4odCk6dGhpcy5fdG9rZW5pemVJY3UmJnRoaXMuX3Rva2VuaXplRXhwYW5zaW9uRm9ybSgpfHx0aGlzLl9jb25zdW1lV2l0aEludGVycG9sYXRpb24oNSw4LCgpPT50aGlzLl9pc1RleHRFbmQoKSwoKT0+dGhpcy5faXNUYWdTdGFydCgpKX1jYXRjaChlKXt0aGlzLmhhbmRsZUVycm9yKGUpfX10aGlzLl9iZWdpblRva2VuKDI0KSx0aGlzLl9lbmRUb2tlbihbXSl9X3Rva2VuaXplRXhwYW5zaW9uRm9ybSgpe2lmKHRoaXMuaXNFeHBhbnNpb25Gb3JtU3RhcnQoKSlyZXR1cm4gdGhpcy5fY29uc3VtZUV4cGFuc2lvbkZvcm1TdGFydCgpLCEwO2lmKHRoaXMuX2N1cnNvci5wZWVrKCkhPT1PdSYmdGhpcy5faXNJbkV4cGFuc2lvbkZvcm0oKSlyZXR1cm4gdGhpcy5fY29uc3VtZUV4cGFuc2lvbkNhc2VTdGFydCgpLCEwO2lmKHRoaXMuX2N1cnNvci5wZWVrKCk9PT1PdSl7aWYodGhpcy5faXNJbkV4cGFuc2lvbkNhc2UoKSlyZXR1cm4gdGhpcy5fY29uc3VtZUV4cGFuc2lvbkNhc2VFbmQoKSwhMDtpZih0aGlzLl9pc0luRXhwYW5zaW9uRm9ybSgpKXJldHVybiB0aGlzLl9jb25zdW1lRXhwYW5zaW9uRm9ybUVuZCgpLCEwfXJldHVybiExfV9iZWdpblRva2VuKHQsZT10aGlzLl9jdXJzb3IuY2xvbmUoKSl7dGhpcy5fY3VycmVudFRva2VuU3RhcnQ9ZSx0aGlzLl9jdXJyZW50VG9rZW5UeXBlPXR9X2VuZFRva2VuKHQsZSl7aWYobnVsbD09PXRoaXMuX2N1cnJlbnRUb2tlblN0YXJ0KXRocm93IG5ldyBoQygiUHJvZ3JhbW1pbmcgZXJyb3IgLSBhdHRlbXB0ZWQgdG8gZW5kIGEgdG9rZW4gd2hlbiB0aGVyZSB3YXMgbm8gc3RhcnQgdG8gdGhlIHRva2VuIix0aGlzLl9jdXJyZW50VG9rZW5UeXBlLHRoaXMuX2N1cnNvci5nZXRTcGFuKGUpKTtpZihudWxsPT09dGhpcy5fY3VycmVudFRva2VuVHlwZSl0aHJvdyBuZXcgaEMoIlByb2dyYW1taW5nIGVycm9yIC0gYXR0ZW1wdGVkIHRvIGVuZCBhIHRva2VuIHdoaWNoIGhhcyBubyB0b2tlbiB0eXBlIixudWxsLHRoaXMuX2N1cnNvci5nZXRTcGFuKHRoaXMuX2N1cnJlbnRUb2tlblN0YXJ0KSk7bGV0IGk9e3R5cGU6dGhpcy5fY3VycmVudFRva2VuVHlwZSxwYXJ0czp0LHNvdXJjZVNwYW46KGU/P3RoaXMuX2N1cnNvcikuZ2V0U3Bhbih0aGlzLl9jdXJyZW50VG9rZW5TdGFydCx0aGlzLl9sZWFkaW5nVHJpdmlhQ29kZVBvaW50cyl9O3JldHVybiB0aGlzLnRva2Vucy5wdXNoKGkpLHRoaXMuX2N1cnJlbnRUb2tlblN0YXJ0PW51bGwsdGhpcy5fY3VycmVudFRva2VuVHlwZT1udWxsLGl9X2NyZWF0ZUVycm9yKHQsZSl7dGhpcy5faXNJbkV4cGFuc2lvbkZvcm0oKSYmKHQrPScgKERvIHlvdSBoYXZlIGFuIHVuZXNjYXBlZCAieyIgaW4geW91ciB0ZW1wbGF0ZT8gVXNlICJ7eyBcJ3tcJyB9fSIpIHRvIGVzY2FwZSBpdC4pJyk7bGV0IGk9bmV3IGhDKHQsdGhpcy5fY3VycmVudFRva2VuVHlwZSxlKTtyZXR1cm4gdGhpcy5fY3VycmVudFRva2VuU3RhcnQ9bnVsbCx0aGlzLl9jdXJyZW50VG9rZW5UeXBlPW51bGwsbmV3IG1DKGkpfWhhbmRsZUVycm9yKHQpe2lmKHQgaW5zdGFuY2VvZiBZQyYmKHQ9dGhpcy5fY3JlYXRlRXJyb3IodC5tc2csdGhpcy5fY3Vyc29yLmdldFNwYW4odC5jdXJzb3IpKSksISh0IGluc3RhbmNlb2YgbUMpKXRocm93IHQ7dGhpcy5lcnJvcnMucHVzaCh0LmVycm9yKX1fYXR0ZW1wdENoYXJDb2RlKHQpe3JldHVybiB0aGlzLl9jdXJzb3IucGVlaygpPT09dCYmKHRoaXMuX2N1cnNvci5hZHZhbmNlKCksITApfV9hdHRlbXB0Q2hhckNvZGVDYXNlSW5zZW5zaXRpdmUodCl7cmV0dXJuISFmdW5jdGlvbihuLHQpe3JldHVybiBvUShuKT09PW9RKHQpfSh0aGlzLl9jdXJzb3IucGVlaygpLHQpJiYodGhpcy5fY3Vyc29yLmFkdmFuY2UoKSwhMCl9X3JlcXVpcmVDaGFyQ29kZSh0KXtsZXQgZT10aGlzLl9jdXJzb3IuY2xvbmUoKTtpZighdGhpcy5fYXR0ZW1wdENoYXJDb2RlKHQpKXRocm93IHRoaXMuX2NyZWF0ZUVycm9yKHJDKHRoaXMuX2N1cnNvci5wZWVrKCkpLHRoaXMuX2N1cnNvci5nZXRTcGFuKGUpKX1fYXR0ZW1wdFN0cih0KXtsZXQgZT10Lmxlbmd0aDtpZih0aGlzLl9jdXJzb3IuY2hhcnNMZWZ0KCk8ZSlyZXR1cm4hMTtsZXQgaT10aGlzLl9jdXJzb3IuY2xvbmUoKTtmb3IobGV0IHI9MDtyPGU7cisrKWlmKCF0aGlzLl9hdHRlbXB0Q2hhckNvZGUodC5jaGFyQ29kZUF0KHIpKSlyZXR1cm4gdGhpcy5fY3Vyc29yPWksITE7cmV0dXJuITB9X2F0dGVtcHRTdHJDYXNlSW5zZW5zaXRpdmUodCl7Zm9yKGxldCBlPTA7ZTx0Lmxlbmd0aDtlKyspaWYoIXRoaXMuX2F0dGVtcHRDaGFyQ29kZUNhc2VJbnNlbnNpdGl2ZSh0LmNoYXJDb2RlQXQoZSkpKXJldHVybiExO3JldHVybiEwfV9yZXF1aXJlU3RyKHQpe2xldCBlPXRoaXMuX2N1cnNvci5jbG9uZSgpO2lmKCF0aGlzLl9hdHRlbXB0U3RyKHQpKXRocm93IHRoaXMuX2NyZWF0ZUVycm9yKHJDKHRoaXMuX2N1cnNvci5wZWVrKCkpLHRoaXMuX2N1cnNvci5nZXRTcGFuKGUpKX1fYXR0ZW1wdENoYXJDb2RlVW50aWxGbih0KXtmb3IoOyF0KHRoaXMuX2N1cnNvci5wZWVrKCkpOyl0aGlzLl9jdXJzb3IuYWR2YW5jZSgpfV9yZXF1aXJlQ2hhckNvZGVVbnRpbEZuKHQsZSl7bGV0IGk9dGhpcy5fY3Vyc29yLmNsb25lKCk7aWYodGhpcy5fYXR0ZW1wdENoYXJDb2RlVW50aWxGbih0KSx0aGlzLl9jdXJzb3IuZGlmZihpKTxlKXRocm93IHRoaXMuX2NyZWF0ZUVycm9yKHJDKHRoaXMuX2N1cnNvci5wZWVrKCkpLHRoaXMuX2N1cnNvci5nZXRTcGFuKGkpKX1fYXR0ZW1wdFVudGlsQ2hhcih0KXtmb3IoO3RoaXMuX2N1cnNvci5wZWVrKCkhPT10Oyl0aGlzLl9jdXJzb3IuYWR2YW5jZSgpfV9yZWFkQ2hhcigpe2xldCB0PVN0cmluZy5mcm9tQ29kZVBvaW50KHRoaXMuX2N1cnNvci5wZWVrKCkpO3JldHVybiB0aGlzLl9jdXJzb3IuYWR2YW5jZSgpLHR9X2NvbnN1bWVFbnRpdHkodCl7dGhpcy5fYmVnaW5Ub2tlbig5KTtsZXQgZT10aGlzLl9jdXJzb3IuY2xvbmUoKTtpZih0aGlzLl9jdXJzb3IuYWR2YW5jZSgpLHRoaXMuX2F0dGVtcHRDaGFyQ29kZSgzNSkpe2xldCBpPXRoaXMuX2F0dGVtcHRDaGFyQ29kZSgxMjApfHx0aGlzLl9hdHRlbXB0Q2hhckNvZGUoODgpLHI9dGhpcy5fY3Vyc29yLmNsb25lKCk7aWYodGhpcy5fYXR0ZW1wdENoYXJDb2RlVW50aWxGbihpRWUpLDU5IT10aGlzLl9jdXJzb3IucGVlaygpKXRocm93IHRoaXMuX2N1cnNvci5hZHZhbmNlKCksdGhpcy5fY3JlYXRlRXJyb3IoZnVuY3Rpb24obix0KXtyZXR1cm5gVW5hYmxlIHRvIHBhcnNlIGVudGl0eSAiJHt0fSIgLSAke259IGNoYXJhY3RlciByZWZlcmVuY2UgZW50aXRpZXMgbXVzdCBlbmQgd2l0aCAiOyJgfShpP2ZDLkhFWDpmQy5ERUMsdGhpcy5fY3Vyc29yLmdldENoYXJzKGUpKSx0aGlzLl9jdXJzb3IuZ2V0U3BhbigpKTtsZXQgbz10aGlzLl9jdXJzb3IuZ2V0Q2hhcnMocik7dGhpcy5fY3Vyc29yLmFkdmFuY2UoKTt0cnl7bGV0IHM9cGFyc2VJbnQobyxpPzE2OjEwKTt0aGlzLl9lbmRUb2tlbihbU3RyaW5nLmZyb21DaGFyQ29kZShzKSx0aGlzLl9jdXJzb3IuZ2V0Q2hhcnMoZSldKX1jYXRjaHt0aHJvdyB0aGlzLl9jcmVhdGVFcnJvcihpUSh0aGlzLl9jdXJzb3IuZ2V0Q2hhcnMoZSkpLHRoaXMuX2N1cnNvci5nZXRTcGFuKCkpfX1lbHNle2xldCBpPXRoaXMuX2N1cnNvci5jbG9uZSgpO2lmKHRoaXMuX2F0dGVtcHRDaGFyQ29kZVVudGlsRm4ockVlKSw1OSE9dGhpcy5fY3Vyc29yLnBlZWsoKSl0aGlzLl9iZWdpblRva2VuKHQsZSksdGhpcy5fY3Vyc29yPWksdGhpcy5fZW5kVG9rZW4oWyImIl0pO2Vsc2V7bGV0IHI9dGhpcy5fY3Vyc29yLmdldENoYXJzKGkpO3RoaXMuX2N1cnNvci5hZHZhbmNlKCk7bGV0IG89Q0Rbcl07aWYoIW8pdGhyb3cgdGhpcy5fY3JlYXRlRXJyb3IoaVEociksdGhpcy5fY3Vyc29yLmdldFNwYW4oZSkpO3RoaXMuX2VuZFRva2VuKFtvLGAmJHtyfTtgXSl9fX1fY29uc3VtZVJhd1RleHQodCxlKXt0aGlzLl9iZWdpblRva2VuKHQ/Njo3KTtsZXQgaT1bXTtmb3IoOzspe2xldCByPXRoaXMuX2N1cnNvci5jbG9uZSgpLG89ZSgpO2lmKHRoaXMuX2N1cnNvcj1yLG8pYnJlYWs7dCYmMzg9PT10aGlzLl9jdXJzb3IucGVlaygpPyh0aGlzLl9lbmRUb2tlbihbdGhpcy5fcHJvY2Vzc0NhcnJpYWdlUmV0dXJucyhpLmpvaW4oIiIpKV0pLGkubGVuZ3RoPTAsdGhpcy5fY29uc3VtZUVudGl0eSg2KSx0aGlzLl9iZWdpblRva2VuKDYpKTppLnB1c2godGhpcy5fcmVhZENoYXIoKSl9dGhpcy5fZW5kVG9rZW4oW3RoaXMuX3Byb2Nlc3NDYXJyaWFnZVJldHVybnMoaS5qb2luKCIiKSldKX1fY29uc3VtZUNvbW1lbnQodCl7dGhpcy5fYmVnaW5Ub2tlbigxMCx0KSx0aGlzLl9yZXF1aXJlQ2hhckNvZGUoNDUpLHRoaXMuX2VuZFRva2VuKFtdKSx0aGlzLl9jb25zdW1lUmF3VGV4dCghMSwoKT0+dGhpcy5fYXR0ZW1wdFN0cigiLS1ceDNlIikpLHRoaXMuX2JlZ2luVG9rZW4oMTEpLHRoaXMuX3JlcXVpcmVTdHIoIi0tXHgzZSIpLHRoaXMuX2VuZFRva2VuKFtdKX1fY29uc3VtZUNkYXRhKHQpe3RoaXMuX2JlZ2luVG9rZW4oMTIsdCksdGhpcy5fcmVxdWlyZVN0cigiQ0RBVEFbIiksdGhpcy5fZW5kVG9rZW4oW10pLHRoaXMuX2NvbnN1bWVSYXdUZXh0KCExLCgpPT50aGlzLl9hdHRlbXB0U3RyKCJdXT4iKSksdGhpcy5fYmVnaW5Ub2tlbigxMyksdGhpcy5fcmVxdWlyZVN0cigiXV0+IiksdGhpcy5fZW5kVG9rZW4oW10pfV9jb25zdW1lRG9jVHlwZSh0KXt0aGlzLl9iZWdpblRva2VuKDE4LHQpO2xldCBlPXRoaXMuX2N1cnNvci5jbG9uZSgpO3RoaXMuX2F0dGVtcHRVbnRpbENoYXIoNjIpO2xldCBpPXRoaXMuX2N1cnNvci5nZXRDaGFycyhlKTt0aGlzLl9jdXJzb3IuYWR2YW5jZSgpLHRoaXMuX2VuZFRva2VuKFtpXSl9X2NvbnN1bWVQcmVmaXhBbmROYW1lKCl7bGV0IHQ9dGhpcy5fY3Vyc29yLmNsb25lKCksZT0iIjtmb3IoOzU4IT09dGhpcy5fY3Vyc29yLnBlZWsoKSYmISgoKG49dGhpcy5fY3Vyc29yLnBlZWsoKSk8OTd8fDEyMjxuKSYmKG48NjV8fDkwPG4pJiYobjw0OHx8bj41NykpOyl0aGlzLl9jdXJzb3IuYWR2YW5jZSgpO3ZhciBuO2xldCBpO3JldHVybiA1OD09PXRoaXMuX2N1cnNvci5wZWVrKCk/KGU9dGhpcy5fY3Vyc29yLmdldENoYXJzKHQpLHRoaXMuX2N1cnNvci5hZHZhbmNlKCksaT10aGlzLl9jdXJzb3IuY2xvbmUoKSk6aT10LHRoaXMuX3JlcXVpcmVDaGFyQ29kZVVudGlsRm4oclEsIiI9PT1lPzA6MSksW2UsdGhpcy5fY3Vyc29yLmdldENoYXJzKGkpXX1fY29uc3VtZVRhZ09wZW4odCl7bGV0IGUsaSxyO3RyeXtpZighS1YodGhpcy5fY3Vyc29yLnBlZWsoKSkpdGhyb3cgdGhpcy5fY3JlYXRlRXJyb3IockModGhpcy5fY3Vyc29yLnBlZWsoKSksdGhpcy5fY3Vyc29yLmdldFNwYW4odCkpO2ZvcihyPXRoaXMuX2NvbnN1bWVUYWdPcGVuU3RhcnQodCksaT1yLnBhcnRzWzBdLGU9ci5wYXJ0c1sxXSx0aGlzLl9hdHRlbXB0Q2hhckNvZGVVbnRpbEZuKHJsKTs0NyE9PXRoaXMuX2N1cnNvci5wZWVrKCkmJjYyIT09dGhpcy5fY3Vyc29yLnBlZWsoKSYmNjAhPT10aGlzLl9jdXJzb3IucGVlaygpJiYwIT09dGhpcy5fY3Vyc29yLnBlZWsoKTspdGhpcy5fY29uc3VtZUF0dHJpYnV0ZU5hbWUoKSx0aGlzLl9hdHRlbXB0Q2hhckNvZGVVbnRpbEZuKHJsKSx0aGlzLl9hdHRlbXB0Q2hhckNvZGUoNjEpJiYodGhpcy5fYXR0ZW1wdENoYXJDb2RlVW50aWxGbihybCksdGhpcy5fY29uc3VtZUF0dHJpYnV0ZVZhbHVlKCkpLHRoaXMuX2F0dGVtcHRDaGFyQ29kZVVudGlsRm4ocmwpO3RoaXMuX2NvbnN1bWVUYWdPcGVuRW5kKCl9Y2F0Y2gocyl7aWYocyBpbnN0YW5jZW9mIG1DKXJldHVybiB2b2lkKHI/ci50eXBlPTQ6KHRoaXMuX2JlZ2luVG9rZW4oNSx0KSx0aGlzLl9lbmRUb2tlbihbIjwiXSkpKTt0aHJvdyBzfWxldCBvPXRoaXMuX2dldFRhZ0RlZmluaXRpb24oZSkuZ2V0Q29udGVudFR5cGUoaSk7bz09PVdsLlJBV19URVhUP3RoaXMuX2NvbnN1bWVSYXdUZXh0V2l0aFRhZ0Nsb3NlKGksZSwhMSk6bz09PVdsLkVTQ0FQQUJMRV9SQVdfVEVYVCYmdGhpcy5fY29uc3VtZVJhd1RleHRXaXRoVGFnQ2xvc2UoaSxlLCEwKX1fY29uc3VtZVJhd1RleHRXaXRoVGFnQ2xvc2UodCxlLGkpe3RoaXMuX2NvbnN1bWVSYXdUZXh0KGksKCk9PiEhKHRoaXMuX2F0dGVtcHRDaGFyQ29kZSg2MCkmJnRoaXMuX2F0dGVtcHRDaGFyQ29kZSg0NykmJih0aGlzLl9hdHRlbXB0Q2hhckNvZGVVbnRpbEZuKHJsKSx0aGlzLl9hdHRlbXB0U3RyQ2FzZUluc2Vuc2l0aXZlKGUpKSkmJih0aGlzLl9hdHRlbXB0Q2hhckNvZGVVbnRpbEZuKHJsKSx0aGlzLl9hdHRlbXB0Q2hhckNvZGUoNjIpKSksdGhpcy5fYmVnaW5Ub2tlbigzKSx0aGlzLl9yZXF1aXJlQ2hhckNvZGVVbnRpbEZuKHI9PjYyPT09ciwzKSx0aGlzLl9jdXJzb3IuYWR2YW5jZSgpLHRoaXMuX2VuZFRva2VuKFt0LGVdKX1fY29uc3VtZVRhZ09wZW5TdGFydCh0KXt0aGlzLl9iZWdpblRva2VuKDAsdCk7bGV0IGU9dGhpcy5fY29uc3VtZVByZWZpeEFuZE5hbWUoKTtyZXR1cm4gdGhpcy5fZW5kVG9rZW4oZSl9X2NvbnN1bWVBdHRyaWJ1dGVOYW1lKCl7bGV0IHQ9dGhpcy5fY3Vyc29yLnBlZWsoKTtpZigzOT09PXR8fDM0PT09dCl0aHJvdyB0aGlzLl9jcmVhdGVFcnJvcihyQyh0KSx0aGlzLl9jdXJzb3IuZ2V0U3BhbigpKTt0aGlzLl9iZWdpblRva2VuKDE0KTtsZXQgZT10aGlzLl9jb25zdW1lUHJlZml4QW5kTmFtZSgpO3RoaXMuX2VuZFRva2VuKGUpfV9jb25zdW1lQXR0cmlidXRlVmFsdWUoKXtpZigzOT09PXRoaXMuX2N1cnNvci5wZWVrKCl8fDM0PT09dGhpcy5fY3Vyc29yLnBlZWsoKSl7bGV0IGU9dGhpcy5fY3Vyc29yLnBlZWsoKTt0aGlzLl9jb25zdW1lUXVvdGUoZSk7bGV0IGk9KCk9PnRoaXMuX2N1cnNvci5wZWVrKCk9PT1lO3RoaXMuX2NvbnN1bWVXaXRoSW50ZXJwb2xhdGlvbigxNiwxNyxpLGkpLHRoaXMuX2NvbnN1bWVRdW90ZShlKX1lbHNle2xldCBlPSgpPT5yUSh0aGlzLl9jdXJzb3IucGVlaygpKTt0aGlzLl9jb25zdW1lV2l0aEludGVycG9sYXRpb24oMTYsMTcsZSxlKX19X2NvbnN1bWVRdW90ZSh0KXt0aGlzLl9iZWdpblRva2VuKDE1KSx0aGlzLl9yZXF1aXJlQ2hhckNvZGUodCksdGhpcy5fZW5kVG9rZW4oW1N0cmluZy5mcm9tQ29kZVBvaW50KHQpXSl9X2NvbnN1bWVUYWdPcGVuRW5kKCl7bGV0IHQ9dGhpcy5fYXR0ZW1wdENoYXJDb2RlKDQ3KT8yOjE7dGhpcy5fYmVnaW5Ub2tlbih0KSx0aGlzLl9yZXF1aXJlQ2hhckNvZGUoNjIpLHRoaXMuX2VuZFRva2VuKFtdKX1fY29uc3VtZVRhZ0Nsb3NlKHQpe3RoaXMuX2JlZ2luVG9rZW4oMyx0KSx0aGlzLl9hdHRlbXB0Q2hhckNvZGVVbnRpbEZuKHJsKTtsZXQgZT10aGlzLl9jb25zdW1lUHJlZml4QW5kTmFtZSgpO3RoaXMuX2F0dGVtcHRDaGFyQ29kZVVudGlsRm4ocmwpLHRoaXMuX3JlcXVpcmVDaGFyQ29kZSg2MiksdGhpcy5fZW5kVG9rZW4oZSl9X2NvbnN1bWVFeHBhbnNpb25Gb3JtU3RhcnQoKXt0aGlzLl9iZWdpblRva2VuKDE5KSx0aGlzLl9yZXF1aXJlQ2hhckNvZGUodGgpLHRoaXMuX2VuZFRva2VuKFtdKSx0aGlzLl9leHBhbnNpb25DYXNlU3RhY2sucHVzaCgxOSksdGhpcy5fYmVnaW5Ub2tlbig3KTtsZXQgdD10aGlzLl9yZWFkVW50aWwoNDQpLGU9dGhpcy5fcHJvY2Vzc0NhcnJpYWdlUmV0dXJucyh0KTtpZih0aGlzLl9pMThuTm9ybWFsaXplTGluZUVuZGluZ3NJbklDVXMpdGhpcy5fZW5kVG9rZW4oW2VdKTtlbHNle2xldCByPXRoaXMuX2VuZFRva2VuKFt0XSk7ZSE9PXQmJnRoaXMubm9uTm9ybWFsaXplZEljdUV4cHJlc3Npb25zLnB1c2gocil9dGhpcy5fcmVxdWlyZUNoYXJDb2RlKDQ0KSx0aGlzLl9hdHRlbXB0Q2hhckNvZGVVbnRpbEZuKHJsKSx0aGlzLl9iZWdpblRva2VuKDcpO2xldCBpPXRoaXMuX3JlYWRVbnRpbCg0NCk7dGhpcy5fZW5kVG9rZW4oW2ldKSx0aGlzLl9yZXF1aXJlQ2hhckNvZGUoNDQpLHRoaXMuX2F0dGVtcHRDaGFyQ29kZVVudGlsRm4ocmwpfV9jb25zdW1lRXhwYW5zaW9uQ2FzZVN0YXJ0KCl7dGhpcy5fYmVnaW5Ub2tlbigyMCk7bGV0IHQ9dGhpcy5fcmVhZFVudGlsKHRoKS50cmltKCk7dGhpcy5fZW5kVG9rZW4oW3RdKSx0aGlzLl9hdHRlbXB0Q2hhckNvZGVVbnRpbEZuKHJsKSx0aGlzLl9iZWdpblRva2VuKDIxKSx0aGlzLl9yZXF1aXJlQ2hhckNvZGUodGgpLHRoaXMuX2VuZFRva2VuKFtdKSx0aGlzLl9hdHRlbXB0Q2hhckNvZGVVbnRpbEZuKHJsKSx0aGlzLl9leHBhbnNpb25DYXNlU3RhY2sucHVzaCgyMSl9X2NvbnN1bWVFeHBhbnNpb25DYXNlRW5kKCl7dGhpcy5fYmVnaW5Ub2tlbigyMiksdGhpcy5fcmVxdWlyZUNoYXJDb2RlKE91KSx0aGlzLl9lbmRUb2tlbihbXSksdGhpcy5fYXR0ZW1wdENoYXJDb2RlVW50aWxGbihybCksdGhpcy5fZXhwYW5zaW9uQ2FzZVN0YWNrLnBvcCgpfV9jb25zdW1lRXhwYW5zaW9uRm9ybUVuZCgpe3RoaXMuX2JlZ2luVG9rZW4oMjMpLHRoaXMuX3JlcXVpcmVDaGFyQ29kZShPdSksdGhpcy5fZW5kVG9rZW4oW10pLHRoaXMuX2V4cGFuc2lvbkNhc2VTdGFjay5wb3AoKX1fY29uc3VtZVdpdGhJbnRlcnBvbGF0aW9uKHQsZSxpLHIpe3RoaXMuX2JlZ2luVG9rZW4odCk7bGV0IG89W107Zm9yKDshaSgpOyl7bGV0IHM9dGhpcy5fY3Vyc29yLmNsb25lKCk7dGhpcy5faW50ZXJwb2xhdGlvbkNvbmZpZyYmdGhpcy5fYXR0ZW1wdFN0cih0aGlzLl9pbnRlcnBvbGF0aW9uQ29uZmlnLnN0YXJ0KT8odGhpcy5fZW5kVG9rZW4oW3RoaXMuX3Byb2Nlc3NDYXJyaWFnZVJldHVybnMoby5qb2luKCIiKSldLHMpLG8ubGVuZ3RoPTAsdGhpcy5fY29uc3VtZUludGVycG9sYXRpb24oZSxzLHIpLHRoaXMuX2JlZ2luVG9rZW4odCkpOjM4PT09dGhpcy5fY3Vyc29yLnBlZWsoKT8odGhpcy5fZW5kVG9rZW4oW3RoaXMuX3Byb2Nlc3NDYXJyaWFnZVJldHVybnMoby5qb2luKCIiKSldKSxvLmxlbmd0aD0wLHRoaXMuX2NvbnN1bWVFbnRpdHkodCksdGhpcy5fYmVnaW5Ub2tlbih0KSk6by5wdXNoKHRoaXMuX3JlYWRDaGFyKCkpfXRoaXMuX2luSW50ZXJwb2xhdGlvbj0hMSx0aGlzLl9lbmRUb2tlbihbdGhpcy5fcHJvY2Vzc0NhcnJpYWdlUmV0dXJucyhvLmpvaW4oIiIpKV0pfV9jb25zdW1lSW50ZXJwb2xhdGlvbih0LGUsaSl7bGV0IHI9W107dGhpcy5fYmVnaW5Ub2tlbih0LGUpLHIucHVzaCh0aGlzLl9pbnRlcnBvbGF0aW9uQ29uZmlnLnN0YXJ0KTtsZXQgbz10aGlzLl9jdXJzb3IuY2xvbmUoKSxzPW51bGwsYT0hMTtmb3IoOzAhPT10aGlzLl9jdXJzb3IucGVlaygpJiYobnVsbD09PWl8fCFpKCkpOyl7bGV0IGw9dGhpcy5fY3Vyc29yLmNsb25lKCk7aWYodGhpcy5faXNUYWdTdGFydCgpKXJldHVybiB0aGlzLl9jdXJzb3I9bCxyLnB1c2godGhpcy5fZ2V0UHJvY2Vzc2VkQ2hhcnMobyxsKSksdm9pZCB0aGlzLl9lbmRUb2tlbihyKTtpZihudWxsPT09cyl7aWYodGhpcy5fYXR0ZW1wdFN0cih0aGlzLl9pbnRlcnBvbGF0aW9uQ29uZmlnLmVuZCkpcmV0dXJuIHIucHVzaCh0aGlzLl9nZXRQcm9jZXNzZWRDaGFycyhvLGwpKSxyLnB1c2godGhpcy5faW50ZXJwb2xhdGlvbkNvbmZpZy5lbmQpLHZvaWQgdGhpcy5fZW5kVG9rZW4ocik7dGhpcy5fYXR0ZW1wdFN0cigiLy8iKSYmKGE9ITApfWxldCBjPXRoaXMuX2N1cnNvci5wZWVrKCk7dGhpcy5fY3Vyc29yLmFkdmFuY2UoKSw5Mj09PWM/dGhpcy5fY3Vyc29yLmFkdmFuY2UoKTpjPT09cz9zPW51bGw6IWEmJm51bGw9PT1zJiZXQihjKSYmKHM9Yyl9ci5wdXNoKHRoaXMuX2dldFByb2Nlc3NlZENoYXJzKG8sdGhpcy5fY3Vyc29yKSksdGhpcy5fZW5kVG9rZW4ocil9X2dldFByb2Nlc3NlZENoYXJzKHQsZSl7cmV0dXJuIHRoaXMuX3Byb2Nlc3NDYXJyaWFnZVJldHVybnMoZS5nZXRDaGFycyh0KSl9X2lzVGV4dEVuZCgpe3JldHVybiEhKHRoaXMuX2lzVGFnU3RhcnQoKXx8MD09PXRoaXMuX2N1cnNvci5wZWVrKCl8fHRoaXMuX3Rva2VuaXplSWN1JiYhdGhpcy5faW5JbnRlcnBvbGF0aW9uJiYodGhpcy5pc0V4cGFuc2lvbkZvcm1TdGFydCgpfHx0aGlzLl9jdXJzb3IucGVlaygpPT09T3UmJnRoaXMuX2lzSW5FeHBhbnNpb25DYXNlKCkpKX1faXNUYWdTdGFydCgpe2lmKDYwPT09dGhpcy5fY3Vyc29yLnBlZWsoKSl7bGV0IHQ9dGhpcy5fY3Vyc29yLmNsb25lKCk7dC5hZHZhbmNlKCk7bGV0IGU9dC5wZWVrKCk7aWYoOTc8PWUmJmU8PTEyMnx8NjU8PWUmJmU8PTkwfHw0Nz09PWV8fDMzPT09ZSlyZXR1cm4hMH1yZXR1cm4hMX1fcmVhZFVudGlsKHQpe2xldCBlPXRoaXMuX2N1cnNvci5jbG9uZSgpO3JldHVybiB0aGlzLl9hdHRlbXB0VW50aWxDaGFyKHQpLHRoaXMuX2N1cnNvci5nZXRDaGFycyhlKX1faXNJbkV4cGFuc2lvbkNhc2UoKXtyZXR1cm4gdGhpcy5fZXhwYW5zaW9uQ2FzZVN0YWNrLmxlbmd0aD4wJiYyMT09PXRoaXMuX2V4cGFuc2lvbkNhc2VTdGFja1t0aGlzLl9leHBhbnNpb25DYXNlU3RhY2subGVuZ3RoLTFdfV9pc0luRXhwYW5zaW9uRm9ybSgpe3JldHVybiB0aGlzLl9leHBhbnNpb25DYXNlU3RhY2subGVuZ3RoPjAmJjE5PT09dGhpcy5fZXhwYW5zaW9uQ2FzZVN0YWNrW3RoaXMuX2V4cGFuc2lvbkNhc2VTdGFjay5sZW5ndGgtMV19aXNFeHBhbnNpb25Gb3JtU3RhcnQoKXtpZih0aGlzLl9jdXJzb3IucGVlaygpIT09dGgpcmV0dXJuITE7aWYodGhpcy5faW50ZXJwb2xhdGlvbkNvbmZpZyl7bGV0IHQ9dGhpcy5fY3Vyc29yLmNsb25lKCksZT10aGlzLl9hdHRlbXB0U3RyKHRoaXMuX2ludGVycG9sYXRpb25Db25maWcuc3RhcnQpO3JldHVybiB0aGlzLl9jdXJzb3I9dCwhZX1yZXR1cm4hMH19KG5ldyBwRChuLHQpLGUsaSk7cmV0dXJuIHIudG9rZW5pemUoKSxuZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3RoaXMudG9rZW5zPXQsdGhpcy5lcnJvcnM9ZSx0aGlzLm5vbk5vcm1hbGl6ZWRJY3VFeHByZXNzaW9ucz1pfX0oZnVuY3Rpb24obil7bGV0IGUsdD1bXTtmb3IobGV0IGk9MDtpPG4ubGVuZ3RoO2krKyl7bGV0IHI9bltpXTtlJiY1PT09ZS50eXBlJiY1PT09ci50eXBlfHxlJiYxNj09PWUudHlwZSYmMTY9PT1yLnR5cGU/KGUucGFydHNbMF0rPXIucGFydHNbMF0sZS5zb3VyY2VTcGFuLmVuZD1yLnNvdXJjZVNwYW4uZW5kKTooZT1yLHQucHVzaChlKSl9cmV0dXJuIHR9KHIudG9rZW5zKSxyLmVycm9ycyxyLm5vbk5vcm1hbGl6ZWRJY3VFeHByZXNzaW9ucyl9KHQsZSx0aGlzLmdldFRhZ0RlZmluaXRpb24saSksbz1uZXcgWEMoci50b2tlbnMsdGhpcy5nZXRUYWdEZWZpbml0aW9uKTtyZXR1cm4gby5idWlsZCgpLG5ldyBNRChvLnJvb3ROb2RlcyxyLmVycm9ycy5jb25jYXQoby5lcnJvcnMpKX19LFhDPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy50b2tlbnM9dCx0aGlzLmdldFRhZ0RlZmluaXRpb249ZSx0aGlzLl9pbmRleD0tMSx0aGlzLl9lbGVtZW50U3RhY2s9W10sdGhpcy5yb290Tm9kZXM9W10sdGhpcy5lcnJvcnM9W10sdGhpcy5fYWR2YW5jZSgpfWJ1aWxkKCl7Zm9yKDsyNCE9PXRoaXMuX3BlZWsudHlwZTspMD09PXRoaXMuX3BlZWsudHlwZXx8ND09PXRoaXMuX3BlZWsudHlwZT90aGlzLl9jb25zdW1lU3RhcnRUYWcodGhpcy5fYWR2YW5jZSgpKTozPT09dGhpcy5fcGVlay50eXBlP3RoaXMuX2NvbnN1bWVFbmRUYWcodGhpcy5fYWR2YW5jZSgpKToxMj09PXRoaXMuX3BlZWsudHlwZT8odGhpcy5fY2xvc2VWb2lkRWxlbWVudCgpLHRoaXMuX2NvbnN1bWVDZGF0YSh0aGlzLl9hZHZhbmNlKCkpKToxMD09PXRoaXMuX3BlZWsudHlwZT8odGhpcy5fY2xvc2VWb2lkRWxlbWVudCgpLHRoaXMuX2NvbnN1bWVDb21tZW50KHRoaXMuX2FkdmFuY2UoKSkpOjU9PT10aGlzLl9wZWVrLnR5cGV8fDc9PT10aGlzLl9wZWVrLnR5cGV8fDY9PT10aGlzLl9wZWVrLnR5cGU/KHRoaXMuX2Nsb3NlVm9pZEVsZW1lbnQoKSx0aGlzLl9jb25zdW1lVGV4dCh0aGlzLl9hZHZhbmNlKCkpKToxOT09PXRoaXMuX3BlZWsudHlwZT90aGlzLl9jb25zdW1lRXhwYW5zaW9uKHRoaXMuX2FkdmFuY2UoKSk6dGhpcy5fYWR2YW5jZSgpfV9hZHZhbmNlKCl7bGV0IHQ9dGhpcy5fcGVlaztyZXR1cm4gdGhpcy5faW5kZXg8dGhpcy50b2tlbnMubGVuZ3RoLTEmJnRoaXMuX2luZGV4KyssdGhpcy5fcGVlaz10aGlzLnRva2Vuc1t0aGlzLl9pbmRleF0sdH1fYWR2YW5jZUlmKHQpe3JldHVybiB0aGlzLl9wZWVrLnR5cGU9PT10P3RoaXMuX2FkdmFuY2UoKTpudWxsfV9jb25zdW1lQ2RhdGEodCl7dGhpcy5fY29uc3VtZVRleHQodGhpcy5fYWR2YW5jZSgpKSx0aGlzLl9hZHZhbmNlSWYoMTMpfV9jb25zdW1lQ29tbWVudCh0KXtsZXQgZT10aGlzLl9hZHZhbmNlSWYoNyk7dGhpcy5fYWR2YW5jZUlmKDExKTtsZXQgaT1udWxsIT1lP2UucGFydHNbMF0udHJpbSgpOm51bGw7dGhpcy5fYWRkVG9QYXJlbnQobmV3IHhEKGksdC5zb3VyY2VTcGFuKSl9X2NvbnN1bWVFeHBhbnNpb24odCl7bGV0IGU9dGhpcy5fYWR2YW5jZSgpLGk9dGhpcy5fYWR2YW5jZSgpLHI9W107Zm9yKDsyMD09PXRoaXMuX3BlZWsudHlwZTspe2xldCBzPXRoaXMuX3BhcnNlRXhwYW5zaW9uQ2FzZSgpO2lmKCFzKXJldHVybjtyLnB1c2gocyl9aWYoMjMhPT10aGlzLl9wZWVrLnR5cGUpcmV0dXJuIHZvaWQgdGhpcy5lcnJvcnMucHVzaChvbC5jcmVhdGUobnVsbCx0aGlzLl9wZWVrLnNvdXJjZVNwYW4sIkludmFsaWQgSUNVIG1lc3NhZ2UuIE1pc3NpbmcgJ30nLiIpKTtsZXQgbz1uZXcgR28odC5zb3VyY2VTcGFuLnN0YXJ0LHRoaXMuX3BlZWsuc291cmNlU3Bhbi5lbmQsdC5zb3VyY2VTcGFuLmZ1bGxTdGFydCk7dGhpcy5fYWRkVG9QYXJlbnQobmV3IE5fKGUucGFydHNbMF0saS5wYXJ0c1swXSxyLG8sZS5zb3VyY2VTcGFuKSksdGhpcy5fYWR2YW5jZSgpfV9wYXJzZUV4cGFuc2lvbkNhc2UoKXtsZXQgdD10aGlzLl9hZHZhbmNlKCk7aWYoMjEhPT10aGlzLl9wZWVrLnR5cGUpcmV0dXJuIHRoaXMuZXJyb3JzLnB1c2gob2wuY3JlYXRlKG51bGwsdGhpcy5fcGVlay5zb3VyY2VTcGFuLCJJbnZhbGlkIElDVSBtZXNzYWdlLiBNaXNzaW5nICd7Jy4iKSksbnVsbDtsZXQgZT10aGlzLl9hZHZhbmNlKCksaT10aGlzLl9jb2xsZWN0RXhwYW5zaW9uRXhwVG9rZW5zKGUpO2lmKCFpKXJldHVybiBudWxsO2xldCByPXRoaXMuX2FkdmFuY2UoKTtpLnB1c2goe3R5cGU6MjQscGFydHM6W10sc291cmNlU3BhbjpyLnNvdXJjZVNwYW59KTtsZXQgbz1uZXcgWEMoaSx0aGlzLmdldFRhZ0RlZmluaXRpb24pO2lmKG8uYnVpbGQoKSxvLmVycm9ycy5sZW5ndGg+MClyZXR1cm4gdGhpcy5lcnJvcnM9dGhpcy5lcnJvcnMuY29uY2F0KG8uZXJyb3JzKSxudWxsO2xldCBzPW5ldyBHbyh0LnNvdXJjZVNwYW4uc3RhcnQsci5zb3VyY2VTcGFuLmVuZCx0LnNvdXJjZVNwYW4uZnVsbFN0YXJ0KSxhPW5ldyBHbyhlLnNvdXJjZVNwYW4uc3RhcnQsci5zb3VyY2VTcGFuLmVuZCxlLnNvdXJjZVNwYW4uZnVsbFN0YXJ0KTtyZXR1cm4gbmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7dGhpcy52YWx1ZT10LHRoaXMuZXhwcmVzc2lvbj1lLHRoaXMuc291cmNlU3Bhbj1pLHRoaXMudmFsdWVTb3VyY2VTcGFuPXIsdGhpcy5leHBTb3VyY2VTcGFuPW99dmlzaXQodCxlKXtyZXR1cm4gdC52aXNpdEV4cGFuc2lvbkNhc2UodGhpcyxlKX19KHQucGFydHNbMF0sby5yb290Tm9kZXMscyx0LnNvdXJjZVNwYW4sYSl9X2NvbGxlY3RFeHBhbnNpb25FeHBUb2tlbnModCl7bGV0IGU9W10saT1bMjFdO2Zvcig7Oyl7aWYoKDE5PT09dGhpcy5fcGVlay50eXBlfHwyMT09PXRoaXMuX3BlZWsudHlwZSkmJmkucHVzaCh0aGlzLl9wZWVrLnR5cGUpLDIyPT09dGhpcy5fcGVlay50eXBlKXtpZighc1EoaSwyMSkpcmV0dXJuIHRoaXMuZXJyb3JzLnB1c2gob2wuY3JlYXRlKG51bGwsdC5zb3VyY2VTcGFuLCJJbnZhbGlkIElDVSBtZXNzYWdlLiBNaXNzaW5nICd9Jy4iKSksbnVsbDtpZihpLnBvcCgpLDA9PT1pLmxlbmd0aClyZXR1cm4gZX1pZigyMz09PXRoaXMuX3BlZWsudHlwZSl7aWYoIXNRKGksMTkpKXJldHVybiB0aGlzLmVycm9ycy5wdXNoKG9sLmNyZWF0ZShudWxsLHQuc291cmNlU3BhbiwiSW52YWxpZCBJQ1UgbWVzc2FnZS4gTWlzc2luZyAnfScuIikpLG51bGw7aS5wb3AoKX1pZigyND09PXRoaXMuX3BlZWsudHlwZSlyZXR1cm4gdGhpcy5lcnJvcnMucHVzaChvbC5jcmVhdGUobnVsbCx0LnNvdXJjZVNwYW4sIkludmFsaWQgSUNVIG1lc3NhZ2UuIE1pc3NpbmcgJ30nLiIpKSxudWxsO2UucHVzaCh0aGlzLl9hZHZhbmNlKCkpfX1fY29uc3VtZVRleHQodCl7bGV0IGU9W3RdLGk9dC5zb3VyY2VTcGFuLHI9dC5wYXJ0c1swXTtpZihyLmxlbmd0aD4wJiYiXG4iPT09clswXSl7bGV0IG89dGhpcy5fZ2V0UGFyZW50RWxlbWVudCgpO251bGwhPW8mJjA9PT1vLmNoaWxkcmVuLmxlbmd0aCYmdGhpcy5nZXRUYWdEZWZpbml0aW9uKG8ubmFtZSkuaWdub3JlRmlyc3RMZiYmKHI9ci5zdWJzdHJpbmcoMSksZVswXT17dHlwZTp0LnR5cGUsc291cmNlU3Bhbjp0LnNvdXJjZVNwYW4scGFydHM6W3JdfSl9Zm9yKDs4PT09dGhpcy5fcGVlay50eXBlfHw1PT09dGhpcy5fcGVlay50eXBlfHw5PT09dGhpcy5fcGVlay50eXBlOyl0PXRoaXMuX2FkdmFuY2UoKSxlLnB1c2godCkscis9OD09PXQudHlwZT90LnBhcnRzLmpvaW4oIiIpLnJlcGxhY2UoLyYoW147XSspOy9nLGFRKTo5PT09dC50eXBlP3QucGFydHNbMF06dC5wYXJ0cy5qb2luKCIiKTtyLmxlbmd0aD4wJiZ0aGlzLl9hZGRUb1BhcmVudChuZXcgRl8ocixuZXcgR28oaS5zdGFydCx0LnNvdXJjZVNwYW4uZW5kLGkuZnVsbFN0YXJ0LGkuZGV0YWlscyksZSkpfV9jbG9zZVZvaWRFbGVtZW50KCl7bGV0IHQ9dGhpcy5fZ2V0UGFyZW50RWxlbWVudCgpO3QmJnRoaXMuZ2V0VGFnRGVmaW5pdGlvbih0Lm5hbWUpLmlzVm9pZCYmdGhpcy5fZWxlbWVudFN0YWNrLnBvcCgpfV9jb25zdW1lU3RhcnRUYWcodCl7bGV0W2UsaV09dC5wYXJ0cyxyPVtdO2Zvcig7MTQ9PT10aGlzLl9wZWVrLnR5cGU7KXIucHVzaCh0aGlzLl9jb25zdW1lQXR0cih0aGlzLl9hZHZhbmNlKCkpKTtsZXQgbz10aGlzLl9nZXRFbGVtZW50RnVsbE5hbWUoZSxpLHRoaXMuX2dldFBhcmVudEVsZW1lbnQoKSkscz0hMTtpZigyPT09dGhpcy5fcGVlay50eXBlKXt0aGlzLl9hZHZhbmNlKCkscz0hMDtsZXQgZD10aGlzLmdldFRhZ0RlZmluaXRpb24obyk7ZC5jYW5TZWxmQ2xvc2V8fG51bGwhPT1kWChvKXx8ZC5pc1ZvaWR8fHRoaXMuZXJyb3JzLnB1c2gob2wuY3JlYXRlKG8sdC5zb3VyY2VTcGFuLGBPbmx5IHZvaWQgYW5kIGZvcmVpZ24gZWxlbWVudHMgY2FuIGJlIHNlbGYgY2xvc2VkICIke3QucGFydHNbMV19ImApKX1lbHNlIDE9PT10aGlzLl9wZWVrLnR5cGUmJih0aGlzLl9hZHZhbmNlKCkscz0hMSk7bGV0IGE9dGhpcy5fcGVlay5zb3VyY2VTcGFuLmZ1bGxTdGFydCxsPW5ldyBHbyh0LnNvdXJjZVNwYW4uc3RhcnQsYSx0LnNvdXJjZVNwYW4uZnVsbFN0YXJ0KSxjPW5ldyBHbyh0LnNvdXJjZVNwYW4uc3RhcnQsYSx0LnNvdXJjZVNwYW4uZnVsbFN0YXJ0KSx1PW5ldyBxQyhvLHIsW10sbCxjLHZvaWQgMCk7dGhpcy5fcHVzaEVsZW1lbnQodSkscz90aGlzLl9wb3BFbGVtZW50KG8sbCk6ND09PXQudHlwZSYmKHRoaXMuX3BvcEVsZW1lbnQobyxudWxsKSx0aGlzLmVycm9ycy5wdXNoKG9sLmNyZWF0ZShvLGwsYE9wZW5pbmcgdGFnICIke299IiBub3QgdGVybWluYXRlZC5gKSkpfV9wdXNoRWxlbWVudCh0KXtsZXQgZT10aGlzLl9nZXRQYXJlbnRFbGVtZW50KCk7ZSYmdGhpcy5nZXRUYWdEZWZpbml0aW9uKGUubmFtZSkuaXNDbG9zZWRCeUNoaWxkKHQubmFtZSkmJnRoaXMuX2VsZW1lbnRTdGFjay5wb3AoKSx0aGlzLl9hZGRUb1BhcmVudCh0KSx0aGlzLl9lbGVtZW50U3RhY2sucHVzaCh0KX1fY29uc3VtZUVuZFRhZyh0KXtsZXQgZT10aGlzLl9nZXRFbGVtZW50RnVsbE5hbWUodC5wYXJ0c1swXSx0LnBhcnRzWzFdLHRoaXMuX2dldFBhcmVudEVsZW1lbnQoKSk7dGhpcy5nZXRUYWdEZWZpbml0aW9uKGUpLmlzVm9pZD90aGlzLmVycm9ycy5wdXNoKG9sLmNyZWF0ZShlLHQuc291cmNlU3BhbixgVm9pZCBlbGVtZW50cyBkbyBub3QgaGF2ZSBlbmQgdGFncyAiJHt0LnBhcnRzWzFdfSJgKSk6dGhpcy5fcG9wRWxlbWVudChlLHQuc291cmNlU3Bhbil8fHRoaXMuZXJyb3JzLnB1c2gob2wuY3JlYXRlKGUsdC5zb3VyY2VTcGFuLGBVbmV4cGVjdGVkIGNsb3NpbmcgdGFnICIke2V9Ii4gSXQgbWF5IGhhcHBlbiB3aGVuIHRoZSB0YWcgaGFzIGFscmVhZHkgYmVlbiBjbG9zZWQgYnkgYW5vdGhlciB0YWcuIEZvciBtb3JlIGluZm8gc2VlIGh0dHBzOi8vd3d3LnczLm9yZy9UUi9odG1sNS9zeW50YXguaHRtbCNjbG9zaW5nLWVsZW1lbnRzLXRoYXQtaGF2ZS1pbXBsaWVkLWVuZC10YWdzYCkpfV9wb3BFbGVtZW50KHQsZSl7bGV0IGk9ITE7Zm9yKGxldCByPXRoaXMuX2VsZW1lbnRTdGFjay5sZW5ndGgtMTtyPj0wO3ItLSl7bGV0IG89dGhpcy5fZWxlbWVudFN0YWNrW3JdO2lmKG8ubmFtZT09PXQpcmV0dXJuIG8uZW5kU291cmNlU3Bhbj1lLG8uc291cmNlU3Bhbi5lbmQ9bnVsbCE9PWU/ZS5lbmQ6by5zb3VyY2VTcGFuLmVuZCx0aGlzLl9lbGVtZW50U3RhY2suc3BsaWNlKHIsdGhpcy5fZWxlbWVudFN0YWNrLmxlbmd0aC1yKSwhaTt0aGlzLmdldFRhZ0RlZmluaXRpb24oby5uYW1lKS5jbG9zZWRCeVBhcmVudHx8KGk9ITApfXJldHVybiExfV9jb25zdW1lQXR0cih0KXtsZXQgZT13Qih0LnBhcnRzWzBdLHQucGFydHNbMV0pLGk9dC5zb3VyY2VTcGFuLmVuZDsxNT09PXRoaXMuX3BlZWsudHlwZSYmdGhpcy5fYWR2YW5jZSgpO2xldCBzLGEscj0iIixvPVtdO2lmKDE2PT09dGhpcy5fcGVlay50eXBlKWZvcihzPXRoaXMuX3BlZWsuc291cmNlU3BhbixhPXRoaXMuX3BlZWsuc291cmNlU3Bhbi5lbmQ7MTY9PT10aGlzLl9wZWVrLnR5cGV8fDE3PT09dGhpcy5fcGVlay50eXBlfHw5PT09dGhpcy5fcGVlay50eXBlOyl7bGV0IHU9dGhpcy5fYWR2YW5jZSgpO28ucHVzaCh1KSxyKz0xNz09PXUudHlwZT91LnBhcnRzLmpvaW4oIiIpLnJlcGxhY2UoLyYoW147XSspOy9nLGFRKTo5PT09dS50eXBlP3UucGFydHNbMF06dS5wYXJ0cy5qb2luKCIiKSxhPWk9dS5zb3VyY2VTcGFuLmVuZH0xNT09PXRoaXMuX3BlZWsudHlwZSYmKGk9dGhpcy5fYWR2YW5jZSgpLnNvdXJjZVNwYW4uZW5kKTtsZXQgYz1zJiZhJiZuZXcgR28ocy5zdGFydCxhLHMuZnVsbFN0YXJ0KTtyZXR1cm4gbmV3IGhWKGUscixuZXcgR28odC5zb3VyY2VTcGFuLnN0YXJ0LGksdC5zb3VyY2VTcGFuLmZ1bGxTdGFydCksdC5zb3VyY2VTcGFuLGMsby5sZW5ndGg+MD9vOnZvaWQgMCx2b2lkIDApfV9nZXRQYXJlbnRFbGVtZW50KCl7cmV0dXJuIHRoaXMuX2VsZW1lbnRTdGFjay5sZW5ndGg+MD90aGlzLl9lbGVtZW50U3RhY2tbdGhpcy5fZWxlbWVudFN0YWNrLmxlbmd0aC0xXTpudWxsfV9hZGRUb1BhcmVudCh0KXtsZXQgZT10aGlzLl9nZXRQYXJlbnRFbGVtZW50KCk7bnVsbCE9ZT9lLmNoaWxkcmVuLnB1c2godCk6dGhpcy5yb290Tm9kZXMucHVzaCh0KX1fZ2V0RWxlbWVudEZ1bGxOYW1lKHQsZSxpKXtpZigiIj09PXQmJiIiPT09KHQ9dGhpcy5nZXRUYWdEZWZpbml0aW9uKGUpLmltcGxpY2l0TmFtZXNwYWNlUHJlZml4fHwiIikmJm51bGwhPWkpe2xldCByPUtkKGkubmFtZSlbMV07dGhpcy5nZXRUYWdEZWZpbml0aW9uKHIpLnByZXZlbnROYW1lc3BhY2VJbmhlcml0YW5jZXx8KHQ9ZFgoaS5uYW1lKSl9cmV0dXJuIHdCKHQsZSl9fTtmdW5jdGlvbiBzUShuLHQpe3JldHVybiBuLmxlbmd0aD4wJiZuW24ubGVuZ3RoLTFdPT09dH1mdW5jdGlvbiBhUShuLHQpe3JldHVybiB2b2lkIDAhPT1DRFt0XT9DRFt0XXx8bjovXiN4W2EtZjAtOV0rJC9pLnRlc3QodCk/U3RyaW5nLmZyb21Db2RlUG9pbnQocGFyc2VJbnQodC5zbGljZSgyKSwxNikpOi9eI1xkKyQvLnRlc3QodCk/U3RyaW5nLmZyb21Db2RlUG9pbnQocGFyc2VJbnQodC5zbGljZSgxKSwxMCkpOm59dmFyIFhULF9WPWNsYXNzIGV4dGVuZHMgZ1Z7Y29uc3RydWN0b3IoKXtzdXBlcihGVil9cGFyc2UodCxlLGkpe3JldHVybiBzdXBlci5wYXJzZSh0LGUsaSl9fSxnSz0ibmdQcmVzZXJ2ZVdoaXRlc3BhY2VzIixsRWU9bmV3IFNldChbInByZSIsInRlbXBsYXRlIiwidGV4dGFyZWEiLCJzY3JpcHQiLCJzdHlsZSJdKSxfSz0iIFxmXG5cclx0XHZcdTE2ODBcdTE4MGVcdTIwMDAtXHUyMDBhXHUyMDI4XHUyMDI5XHUyMDJmXHUyMDVmXHUzMDAwXHVmZWZmIixjRWU9bmV3IFJlZ0V4cChgW14ke19LfV1gKSx1RWU9bmV3IFJlZ0V4cChgWyR7X0t9XXsyLH1gLCJnIik7ZnVuY3Rpb24gdksobil7cmV0dXJuIG4ucmVwbGFjZShuZXcgUmVnRXhwKCJcdWU1MDAiLCJnIiksIiAiKX1mdW5jdGlvbiB5SyhuKXtyZXR1cm4gdksobikucmVwbGFjZSh1RWUsIiAiKX1mdW5jdGlvbiB3RChuLHQ9ITEpe3JldHVybiBxbChPYmplY3Qua2V5cyhuKS5tYXAoZT0+KHtrZXk6ZSxxdW90ZWQ6dCx2YWx1ZTpuW2VdfSkpKX1mdW5jdGlvbiBsUSgpe3JldHVybiBYVHx8KFhUPXt9LFVUKGlvLkhUTUwsWyJpZnJhbWV8c3JjZG9jIiwiKnxpbm5lckhUTUwiLCIqfG91dGVySFRNTCJdKSxVVChpby5TVFlMRSxbIip8c3R5bGUiXSksVVQoaW8uVVJMLFsiKnxmb3JtQWN0aW9uIiwiYXJlYXxocmVmIiwiYXJlYXxwaW5nIiwiYXVkaW98c3JjIiwiYXxocmVmIiwiYXxwaW5nIiwiYmxvY2txdW90ZXxjaXRlIiwiYm9keXxiYWNrZ3JvdW5kIiwiZGVsfGNpdGUiLCJmb3JtfGFjdGlvbiIsImltZ3xzcmMiLCJpbnB1dHxzcmMiLCJpbnN8Y2l0ZSIsInF8Y2l0ZSIsInNvdXJjZXxzcmMiLCJ0cmFja3xzcmMiLCJ2aWRlb3xwb3N0ZXIiLCJ2aWRlb3xzcmMiXSksVVQoaW8uUkVTT1VSQ0VfVVJMLFsiYXBwbGV0fGNvZGUiLCJhcHBsZXR8Y29kZWJhc2UiLCJiYXNlfGhyZWYiLCJlbWJlZHxzcmMiLCJmcmFtZXxzcmMiLCJoZWFkfHByb2ZpbGUiLCJodG1sfG1hbmlmZXN0IiwiaWZyYW1lfHNyYyIsImxpbmt8aHJlZiIsIm1lZGlhfHNyYyIsIm9iamVjdHxjb2RlYmFzZSIsIm9iamVjdHxkYXRhIiwic2NyaXB0fHNyYyJdKSksWFR9ZnVuY3Rpb24gVVQobix0KXtmb3IobGV0IGUgb2YgdClYVFtlLnRvTG93ZXJDYXNlKCldPW59dmFyIHlWPWNsYXNze30sdkVlPVsiW0VsZW1lbnRdfHRleHRDb250ZW50LCVjbGFzc0xpc3QsY2xhc3NOYW1lLGlkLGlubmVySFRNTCwqYmVmb3JlY29weSwqYmVmb3JlY3V0LCpiZWZvcmVwYXN0ZSwqY29weSwqY3V0LCpwYXN0ZSwqc2VhcmNoLCpzZWxlY3RzdGFydCwqd2Via2l0ZnVsbHNjcmVlbmNoYW5nZSwqd2Via2l0ZnVsbHNjcmVlbmVycm9yLCp3aGVlbCxvdXRlckhUTUwsI3Njcm9sbExlZnQsI3Njcm9sbFRvcCxzbG90LCptZXNzYWdlLCptb3pmdWxsc2NyZWVuY2hhbmdlLCptb3pmdWxsc2NyZWVuZXJyb3IsKm1venBvaW50ZXJsb2NrY2hhbmdlLCptb3pwb2ludGVybG9ja2Vycm9yLCp3ZWJnbGNvbnRleHRjcmVhdGlvbmVycm9yLCp3ZWJnbGNvbnRleHRsb3N0LCp3ZWJnbGNvbnRleHRyZXN0b3JlZCIsIltIVE1MRWxlbWVudF1eW0VsZW1lbnRdfGFjY2Vzc0tleSxjb250ZW50RWRpdGFibGUsZGlyLCFkcmFnZ2FibGUsIWhpZGRlbixpbm5lclRleHQsbGFuZywqYWJvcnQsKmF1eGNsaWNrLCpibHVyLCpjYW5jZWwsKmNhbnBsYXksKmNhbnBsYXl0aHJvdWdoLCpjaGFuZ2UsKmNsaWNrLCpjbG9zZSwqY29udGV4dG1lbnUsKmN1ZWNoYW5nZSwqZGJsY2xpY2ssKmRyYWcsKmRyYWdlbmQsKmRyYWdlbnRlciwqZHJhZ2xlYXZlLCpkcmFnb3ZlciwqZHJhZ3N0YXJ0LCpkcm9wLCpkdXJhdGlvbmNoYW5nZSwqZW1wdGllZCwqZW5kZWQsKmVycm9yLCpmb2N1cywqZ290cG9pbnRlcmNhcHR1cmUsKmlucHV0LCppbnZhbGlkLCprZXlkb3duLCprZXlwcmVzcywqa2V5dXAsKmxvYWQsKmxvYWRlZGRhdGEsKmxvYWRlZG1ldGFkYXRhLCpsb2Fkc3RhcnQsKmxvc3Rwb2ludGVyY2FwdHVyZSwqbW91c2Vkb3duLCptb3VzZWVudGVyLCptb3VzZWxlYXZlLCptb3VzZW1vdmUsKm1vdXNlb3V0LCptb3VzZW92ZXIsKm1vdXNldXAsKm1vdXNld2hlZWwsKnBhdXNlLCpwbGF5LCpwbGF5aW5nLCpwb2ludGVyY2FuY2VsLCpwb2ludGVyZG93biwqcG9pbnRlcmVudGVyLCpwb2ludGVybGVhdmUsKnBvaW50ZXJtb3ZlLCpwb2ludGVyb3V0LCpwb2ludGVyb3ZlciwqcG9pbnRlcnVwLCpwcm9ncmVzcywqcmF0ZWNoYW5nZSwqcmVzZXQsKnJlc2l6ZSwqc2Nyb2xsLCpzZWVrZWQsKnNlZWtpbmcsKnNlbGVjdCwqc2hvdywqc3RhbGxlZCwqc3VibWl0LCpzdXNwZW5kLCp0aW1ldXBkYXRlLCp0b2dnbGUsKnZvbHVtZWNoYW5nZSwqd2FpdGluZyxvdXRlclRleHQsIXNwZWxsY2hlY2ssJXN0eWxlLCN0YWJJbmRleCx0aXRsZSwhdHJhbnNsYXRlIiwiYWJicixhZGRyZXNzLGFydGljbGUsYXNpZGUsYixiZGksYmRvLGNpdGUsY29kZSxkZCxkZm4sZHQsZW0sZmlnY2FwdGlvbixmaWd1cmUsZm9vdGVyLGhlYWRlcixpLGtiZCxtYWluLG1hcmssbmF2LG5vc2NyaXB0LHJiLHJwLHJ0LHJ0YyxydWJ5LHMsc2FtcCxzZWN0aW9uLHNtYWxsLHN0cm9uZyxzdWIsc3VwLHUsdmFyLHdicl5bSFRNTEVsZW1lbnRdfGFjY2Vzc0tleSxjb250ZW50RWRpdGFibGUsZGlyLCFkcmFnZ2FibGUsIWhpZGRlbixpbm5lclRleHQsbGFuZywqYWJvcnQsKmF1eGNsaWNrLCpibHVyLCpjYW5jZWwsKmNhbnBsYXksKmNhbnBsYXl0aHJvdWdoLCpjaGFuZ2UsKmNsaWNrLCpjbG9zZSwqY29udGV4dG1lbnUsKmN1ZWNoYW5nZSwqZGJsY2xpY2ssKmRyYWcsKmRyYWdlbmQsKmRyYWdlbnRlciwqZHJhZ2xlYXZlLCpkcmFnb3ZlciwqZHJhZ3N0YXJ0LCpkcm9wLCpkdXJhdGlvbmNoYW5nZSwqZW1wdGllZCwqZW5kZWQsKmVycm9yLCpmb2N1cywqZ290cG9pbnRlcmNhcHR1cmUsKmlucHV0LCppbnZhbGlkLCprZXlkb3duLCprZXlwcmVzcywqa2V5dXAsKmxvYWQsKmxvYWRlZGRhdGEsKmxvYWRlZG1ldGFkYXRhLCpsb2Fkc3RhcnQsKmxvc3Rwb2ludGVyY2FwdHVyZSwqbW91c2Vkb3duLCptb3VzZWVudGVyLCptb3VzZWxlYXZlLCptb3VzZW1vdmUsKm1vdXNlb3V0LCptb3VzZW92ZXIsKm1vdXNldXAsKm1vdXNld2hlZWwsKnBhdXNlLCpwbGF5LCpwbGF5aW5nLCpwb2ludGVyY2FuY2VsLCpwb2ludGVyZG93biwqcG9pbnRlcmVudGVyLCpwb2ludGVybGVhdmUsKnBvaW50ZXJtb3ZlLCpwb2ludGVyb3V0LCpwb2ludGVyb3ZlciwqcG9pbnRlcnVwLCpwcm9ncmVzcywqcmF0ZWNoYW5nZSwqcmVzZXQsKnJlc2l6ZSwqc2Nyb2xsLCpzZWVrZWQsKnNlZWtpbmcsKnNlbGVjdCwqc2hvdywqc3RhbGxlZCwqc3VibWl0LCpzdXNwZW5kLCp0aW1ldXBkYXRlLCp0b2dnbGUsKnZvbHVtZWNoYW5nZSwqd2FpdGluZyxvdXRlclRleHQsIXNwZWxsY2hlY2ssJXN0eWxlLCN0YWJJbmRleCx0aXRsZSwhdHJhbnNsYXRlIiwibWVkaWFeW0hUTUxFbGVtZW50XXwhYXV0b3BsYXksIWNvbnRyb2xzLCVjb250cm9sc0xpc3QsJWNyb3NzT3JpZ2luLCNjdXJyZW50VGltZSwhZGVmYXVsdE11dGVkLCNkZWZhdWx0UGxheWJhY2tSYXRlLCFkaXNhYmxlUmVtb3RlUGxheWJhY2ssIWxvb3AsIW11dGVkLCplbmNyeXB0ZWQsKndhaXRpbmdmb3JrZXksI3BsYXliYWNrUmF0ZSxwcmVsb2FkLHNyYywlc3JjT2JqZWN0LCN2b2x1bWUiLCI6c3ZnOl5bSFRNTEVsZW1lbnRdfCphYm9ydCwqYXV4Y2xpY2ssKmJsdXIsKmNhbmNlbCwqY2FucGxheSwqY2FucGxheXRocm91Z2gsKmNoYW5nZSwqY2xpY2ssKmNsb3NlLCpjb250ZXh0bWVudSwqY3VlY2hhbmdlLCpkYmxjbGljaywqZHJhZywqZHJhZ2VuZCwqZHJhZ2VudGVyLCpkcmFnbGVhdmUsKmRyYWdvdmVyLCpkcmFnc3RhcnQsKmRyb3AsKmR1cmF0aW9uY2hhbmdlLCplbXB0aWVkLCplbmRlZCwqZXJyb3IsKmZvY3VzLCpnb3Rwb2ludGVyY2FwdHVyZSwqaW5wdXQsKmludmFsaWQsKmtleWRvd24sKmtleXByZXNzLCprZXl1cCwqbG9hZCwqbG9hZGVkZGF0YSwqbG9hZGVkbWV0YWRhdGEsKmxvYWRzdGFydCwqbG9zdHBvaW50ZXJjYXB0dXJlLCptb3VzZWRvd24sKm1vdXNlZW50ZXIsKm1vdXNlbGVhdmUsKm1vdXNlbW92ZSwqbW91c2VvdXQsKm1vdXNlb3ZlciwqbW91c2V1cCwqbW91c2V3aGVlbCwqcGF1c2UsKnBsYXksKnBsYXlpbmcsKnBvaW50ZXJjYW5jZWwsKnBvaW50ZXJkb3duLCpwb2ludGVyZW50ZXIsKnBvaW50ZXJsZWF2ZSwqcG9pbnRlcm1vdmUsKnBvaW50ZXJvdXQsKnBvaW50ZXJvdmVyLCpwb2ludGVydXAsKnByb2dyZXNzLCpyYXRlY2hhbmdlLCpyZXNldCwqcmVzaXplLCpzY3JvbGwsKnNlZWtlZCwqc2Vla2luZywqc2VsZWN0LCpzaG93LCpzdGFsbGVkLCpzdWJtaXQsKnN1c3BlbmQsKnRpbWV1cGRhdGUsKnRvZ2dsZSwqdm9sdW1lY2hhbmdlLCp3YWl0aW5nLCVzdHlsZSwjdGFiSW5kZXgiLCI6c3ZnOmdyYXBoaWNzXjpzdmc6fCIsIjpzdmc6YW5pbWF0aW9uXjpzdmc6fCpiZWdpbiwqZW5kLCpyZXBlYXQiLCI6c3ZnOmdlb21ldHJ5Xjpzdmc6fCIsIjpzdmc6Y29tcG9uZW50VHJhbnNmZXJGdW5jdGlvbl46c3ZnOnwiLCI6c3ZnOmdyYWRpZW50Xjpzdmc6fCIsIjpzdmc6dGV4dENvbnRlbnReOnN2ZzpncmFwaGljc3wiLCI6c3ZnOnRleHRQb3NpdGlvbmluZ146c3ZnOnRleHRDb250ZW50fCIsImFeW0hUTUxFbGVtZW50XXxjaGFyc2V0LGNvb3Jkcyxkb3dubG9hZCxoYXNoLGhvc3QsaG9zdG5hbWUsaHJlZixocmVmbGFuZyxuYW1lLHBhc3N3b3JkLHBhdGhuYW1lLHBpbmcscG9ydCxwcm90b2NvbCxyZWZlcnJlclBvbGljeSxyZWwscmV2LHNlYXJjaCxzaGFwZSx0YXJnZXQsdGV4dCx0eXBlLHVzZXJuYW1lIiwiYXJlYV5bSFRNTEVsZW1lbnRdfGFsdCxjb29yZHMsZG93bmxvYWQsaGFzaCxob3N0LGhvc3RuYW1lLGhyZWYsIW5vSHJlZixwYXNzd29yZCxwYXRobmFtZSxwaW5nLHBvcnQscHJvdG9jb2wscmVmZXJyZXJQb2xpY3kscmVsLHNlYXJjaCxzaGFwZSx0YXJnZXQsdXNlcm5hbWUiLCJhdWRpb15tZWRpYXwiLCJicl5bSFRNTEVsZW1lbnRdfGNsZWFyIiwiYmFzZV5bSFRNTEVsZW1lbnRdfGhyZWYsdGFyZ2V0IiwiYm9keV5bSFRNTEVsZW1lbnRdfGFMaW5rLGJhY2tncm91bmQsYmdDb2xvcixsaW5rLCpiZWZvcmV1bmxvYWQsKmJsdXIsKmVycm9yLCpmb2N1cywqaGFzaGNoYW5nZSwqbGFuZ3VhZ2VjaGFuZ2UsKmxvYWQsKm1lc3NhZ2UsKm9mZmxpbmUsKm9ubGluZSwqcGFnZWhpZGUsKnBhZ2VzaG93LCpwb3BzdGF0ZSwqcmVqZWN0aW9uaGFuZGxlZCwqcmVzaXplLCpzY3JvbGwsKnN0b3JhZ2UsKnVuaGFuZGxlZHJlamVjdGlvbiwqdW5sb2FkLHRleHQsdkxpbmsiLCJidXR0b25eW0hUTUxFbGVtZW50XXwhYXV0b2ZvY3VzLCFkaXNhYmxlZCxmb3JtQWN0aW9uLGZvcm1FbmN0eXBlLGZvcm1NZXRob2QsIWZvcm1Ob1ZhbGlkYXRlLGZvcm1UYXJnZXQsbmFtZSx0eXBlLHZhbHVlIiwiY2FudmFzXltIVE1MRWxlbWVudF18I2hlaWdodCwjd2lkdGgiLCJjb250ZW50XltIVE1MRWxlbWVudF18c2VsZWN0IiwiZGxeW0hUTUxFbGVtZW50XXwhY29tcGFjdCIsImRhdGFsaXN0XltIVE1MRWxlbWVudF18IiwiZGV0YWlsc15bSFRNTEVsZW1lbnRdfCFvcGVuIiwiZGlhbG9nXltIVE1MRWxlbWVudF18IW9wZW4scmV0dXJuVmFsdWUiLCJkaXJeW0hUTUxFbGVtZW50XXwhY29tcGFjdCIsImRpdl5bSFRNTEVsZW1lbnRdfGFsaWduIiwiZW1iZWReW0hUTUxFbGVtZW50XXxhbGlnbixoZWlnaHQsbmFtZSxzcmMsdHlwZSx3aWR0aCIsImZpZWxkc2V0XltIVE1MRWxlbWVudF18IWRpc2FibGVkLG5hbWUiLCJmb250XltIVE1MRWxlbWVudF18Y29sb3IsZmFjZSxzaXplIiwiZm9ybV5bSFRNTEVsZW1lbnRdfGFjY2VwdENoYXJzZXQsYWN0aW9uLGF1dG9jb21wbGV0ZSxlbmNvZGluZyxlbmN0eXBlLG1ldGhvZCxuYW1lLCFub1ZhbGlkYXRlLHRhcmdldCIsImZyYW1lXltIVE1MRWxlbWVudF18ZnJhbWVCb3JkZXIsbG9uZ0Rlc2MsbWFyZ2luSGVpZ2h0LG1hcmdpbldpZHRoLG5hbWUsIW5vUmVzaXplLHNjcm9sbGluZyxzcmMiLCJmcmFtZXNldF5bSFRNTEVsZW1lbnRdfGNvbHMsKmJlZm9yZXVubG9hZCwqYmx1ciwqZXJyb3IsKmZvY3VzLCpoYXNoY2hhbmdlLCpsYW5ndWFnZWNoYW5nZSwqbG9hZCwqbWVzc2FnZSwqb2ZmbGluZSwqb25saW5lLCpwYWdlaGlkZSwqcGFnZXNob3csKnBvcHN0YXRlLCpyZWplY3Rpb25oYW5kbGVkLCpyZXNpemUsKnNjcm9sbCwqc3RvcmFnZSwqdW5oYW5kbGVkcmVqZWN0aW9uLCp1bmxvYWQscm93cyIsImhyXltIVE1MRWxlbWVudF18YWxpZ24sY29sb3IsIW5vU2hhZGUsc2l6ZSx3aWR0aCIsImhlYWReW0hUTUxFbGVtZW50XXwiLCJoMSxoMixoMyxoNCxoNSxoNl5bSFRNTEVsZW1lbnRdfGFsaWduIiwiaHRtbF5bSFRNTEVsZW1lbnRdfHZlcnNpb24iLCJpZnJhbWVeW0hUTUxFbGVtZW50XXxhbGlnbiwhYWxsb3dGdWxsc2NyZWVuLGZyYW1lQm9yZGVyLGhlaWdodCxsb25nRGVzYyxtYXJnaW5IZWlnaHQsbWFyZ2luV2lkdGgsbmFtZSxyZWZlcnJlclBvbGljeSwlc2FuZGJveCxzY3JvbGxpbmcsc3JjLHNyY2RvYyx3aWR0aCIsImltZ15bSFRNTEVsZW1lbnRdfGFsaWduLGFsdCxib3JkZXIsJWNyb3NzT3JpZ2luLCNoZWlnaHQsI2hzcGFjZSwhaXNNYXAsbG9uZ0Rlc2MsbG93c3JjLG5hbWUscmVmZXJyZXJQb2xpY3ksc2l6ZXMsc3JjLHNyY3NldCx1c2VNYXAsI3ZzcGFjZSwjd2lkdGgiLCJpbnB1dF5bSFRNTEVsZW1lbnRdfGFjY2VwdCxhbGlnbixhbHQsYXV0b2NhcGl0YWxpemUsYXV0b2NvbXBsZXRlLCFhdXRvZm9jdXMsIWNoZWNrZWQsIWRlZmF1bHRDaGVja2VkLGRlZmF1bHRWYWx1ZSxkaXJOYW1lLCFkaXNhYmxlZCwlZmlsZXMsZm9ybUFjdGlvbixmb3JtRW5jdHlwZSxmb3JtTWV0aG9kLCFmb3JtTm9WYWxpZGF0ZSxmb3JtVGFyZ2V0LCNoZWlnaHQsIWluY3JlbWVudGFsLCFpbmRldGVybWluYXRlLG1heCwjbWF4TGVuZ3RoLG1pbiwjbWluTGVuZ3RoLCFtdWx0aXBsZSxuYW1lLHBhdHRlcm4scGxhY2Vob2xkZXIsIXJlYWRPbmx5LCFyZXF1aXJlZCxzZWxlY3Rpb25EaXJlY3Rpb24sI3NlbGVjdGlvbkVuZCwjc2VsZWN0aW9uU3RhcnQsI3NpemUsc3JjLHN0ZXAsdHlwZSx1c2VNYXAsdmFsdWUsJXZhbHVlQXNEYXRlLCN2YWx1ZUFzTnVtYmVyLCN3aWR0aCIsImxpXltIVE1MRWxlbWVudF18dHlwZSwjdmFsdWUiLCJsYWJlbF5bSFRNTEVsZW1lbnRdfGh0bWxGb3IiLCJsZWdlbmReW0hUTUxFbGVtZW50XXxhbGlnbiIsImxpbmteW0hUTUxFbGVtZW50XXxhcyxjaGFyc2V0LCVjcm9zc09yaWdpbiwhZGlzYWJsZWQsaHJlZixocmVmbGFuZyxpbnRlZ3JpdHksbWVkaWEscmVmZXJyZXJQb2xpY3kscmVsLCVyZWxMaXN0LHJldiwlc2l6ZXMsdGFyZ2V0LHR5cGUiLCJtYXBeW0hUTUxFbGVtZW50XXxuYW1lIiwibWFycXVlZV5bSFRNTEVsZW1lbnRdfGJlaGF2aW9yLGJnQ29sb3IsZGlyZWN0aW9uLGhlaWdodCwjaHNwYWNlLCNsb29wLCNzY3JvbGxBbW91bnQsI3Njcm9sbERlbGF5LCF0cnVlU3BlZWQsI3ZzcGFjZSx3aWR0aCIsIm1lbnVeW0hUTUxFbGVtZW50XXwhY29tcGFjdCIsIm1ldGFeW0hUTUxFbGVtZW50XXxjb250ZW50LGh0dHBFcXVpdixuYW1lLHNjaGVtZSIsIm1ldGVyXltIVE1MRWxlbWVudF18I2hpZ2gsI2xvdywjbWF4LCNtaW4sI29wdGltdW0sI3ZhbHVlIiwiaW5zLGRlbF5bSFRNTEVsZW1lbnRdfGNpdGUsZGF0ZVRpbWUiLCJvbF5bSFRNTEVsZW1lbnRdfCFjb21wYWN0LCFyZXZlcnNlZCwjc3RhcnQsdHlwZSIsIm9iamVjdF5bSFRNTEVsZW1lbnRdfGFsaWduLGFyY2hpdmUsYm9yZGVyLGNvZGUsY29kZUJhc2UsY29kZVR5cGUsZGF0YSwhZGVjbGFyZSxoZWlnaHQsI2hzcGFjZSxuYW1lLHN0YW5kYnksdHlwZSx1c2VNYXAsI3ZzcGFjZSx3aWR0aCIsIm9wdGdyb3VwXltIVE1MRWxlbWVudF18IWRpc2FibGVkLGxhYmVsIiwib3B0aW9uXltIVE1MRWxlbWVudF18IWRlZmF1bHRTZWxlY3RlZCwhZGlzYWJsZWQsbGFiZWwsIXNlbGVjdGVkLHRleHQsdmFsdWUiLCJvdXRwdXReW0hUTUxFbGVtZW50XXxkZWZhdWx0VmFsdWUsJWh0bWxGb3IsbmFtZSx2YWx1ZSIsInBeW0hUTUxFbGVtZW50XXxhbGlnbiIsInBhcmFtXltIVE1MRWxlbWVudF18bmFtZSx0eXBlLHZhbHVlLHZhbHVlVHlwZSIsInBpY3R1cmVeW0hUTUxFbGVtZW50XXwiLCJwcmVeW0hUTUxFbGVtZW50XXwjd2lkdGgiLCJwcm9ncmVzc15bSFRNTEVsZW1lbnRdfCNtYXgsI3ZhbHVlIiwicSxibG9ja3F1b3RlLGNpdGVeW0hUTUxFbGVtZW50XXwiLCJzY3JpcHReW0hUTUxFbGVtZW50XXwhYXN5bmMsY2hhcnNldCwlY3Jvc3NPcmlnaW4sIWRlZmVyLGV2ZW50LGh0bWxGb3IsaW50ZWdyaXR5LHNyYyx0ZXh0LHR5cGUiLCJzZWxlY3ReW0hUTUxFbGVtZW50XXxhdXRvY29tcGxldGUsIWF1dG9mb2N1cywhZGlzYWJsZWQsI2xlbmd0aCwhbXVsdGlwbGUsbmFtZSwhcmVxdWlyZWQsI3NlbGVjdGVkSW5kZXgsI3NpemUsdmFsdWUiLCJzaGFkb3deW0hUTUxFbGVtZW50XXwiLCJzbG90XltIVE1MRWxlbWVudF18bmFtZSIsInNvdXJjZV5bSFRNTEVsZW1lbnRdfG1lZGlhLHNpemVzLHNyYyxzcmNzZXQsdHlwZSIsInNwYW5eW0hUTUxFbGVtZW50XXwiLCJzdHlsZV5bSFRNTEVsZW1lbnRdfCFkaXNhYmxlZCxtZWRpYSx0eXBlIiwiY2FwdGlvbl5bSFRNTEVsZW1lbnRdfGFsaWduIiwidGgsdGReW0hUTUxFbGVtZW50XXxhYmJyLGFsaWduLGF4aXMsYmdDb2xvcixjaCxjaE9mZiwjY29sU3BhbixoZWFkZXJzLGhlaWdodCwhbm9XcmFwLCNyb3dTcGFuLHNjb3BlLHZBbGlnbix3aWR0aCIsImNvbCxjb2xncm91cF5bSFRNTEVsZW1lbnRdfGFsaWduLGNoLGNoT2ZmLCNzcGFuLHZBbGlnbix3aWR0aCIsInRhYmxlXltIVE1MRWxlbWVudF18YWxpZ24sYmdDb2xvcixib3JkZXIsJWNhcHRpb24sY2VsbFBhZGRpbmcsY2VsbFNwYWNpbmcsZnJhbWUscnVsZXMsc3VtbWFyeSwldEZvb3QsJXRIZWFkLHdpZHRoIiwidHJeW0hUTUxFbGVtZW50XXxhbGlnbixiZ0NvbG9yLGNoLGNoT2ZmLHZBbGlnbiIsInRmb290LHRoZWFkLHRib2R5XltIVE1MRWxlbWVudF18YWxpZ24sY2gsY2hPZmYsdkFsaWduIiwidGVtcGxhdGVeW0hUTUxFbGVtZW50XXwiLCJ0ZXh0YXJlYV5bSFRNTEVsZW1lbnRdfGF1dG9jYXBpdGFsaXplLGF1dG9jb21wbGV0ZSwhYXV0b2ZvY3VzLCNjb2xzLGRlZmF1bHRWYWx1ZSxkaXJOYW1lLCFkaXNhYmxlZCwjbWF4TGVuZ3RoLCNtaW5MZW5ndGgsbmFtZSxwbGFjZWhvbGRlciwhcmVhZE9ubHksIXJlcXVpcmVkLCNyb3dzLHNlbGVjdGlvbkRpcmVjdGlvbiwjc2VsZWN0aW9uRW5kLCNzZWxlY3Rpb25TdGFydCx2YWx1ZSx3cmFwIiwidGl0bGVeW0hUTUxFbGVtZW50XXx0ZXh0IiwidHJhY2teW0hUTUxFbGVtZW50XXwhZGVmYXVsdCxraW5kLGxhYmVsLHNyYyxzcmNsYW5nIiwidWxeW0hUTUxFbGVtZW50XXwhY29tcGFjdCx0eXBlIiwidW5rbm93bl5bSFRNTEVsZW1lbnRdfCIsInZpZGVvXm1lZGlhfCNoZWlnaHQscG9zdGVyLCN3aWR0aCIsIjpzdmc6YV46c3ZnOmdyYXBoaWNzfCIsIjpzdmc6YW5pbWF0ZV46c3ZnOmFuaW1hdGlvbnwiLCI6c3ZnOmFuaW1hdGVNb3Rpb25eOnN2ZzphbmltYXRpb258IiwiOnN2ZzphbmltYXRlVHJhbnNmb3JtXjpzdmc6YW5pbWF0aW9ufCIsIjpzdmc6Y2lyY2xlXjpzdmc6Z2VvbWV0cnl8IiwiOnN2ZzpjbGlwUGF0aF46c3ZnOmdyYXBoaWNzfCIsIjpzdmc6ZGVmc146c3ZnOmdyYXBoaWNzfCIsIjpzdmc6ZGVzY146c3ZnOnwiLCI6c3ZnOmRpc2NhcmReOnN2Zzp8IiwiOnN2ZzplbGxpcHNlXjpzdmc6Z2VvbWV0cnl8IiwiOnN2ZzpmZUJsZW5kXjpzdmc6fCIsIjpzdmc6ZmVDb2xvck1hdHJpeF46c3ZnOnwiLCI6c3ZnOmZlQ29tcG9uZW50VHJhbnNmZXJeOnN2Zzp8IiwiOnN2ZzpmZUNvbXBvc2l0ZV46c3ZnOnwiLCI6c3ZnOmZlQ29udm9sdmVNYXRyaXheOnN2Zzp8IiwiOnN2ZzpmZURpZmZ1c2VMaWdodGluZ146c3ZnOnwiLCI6c3ZnOmZlRGlzcGxhY2VtZW50TWFwXjpzdmc6fCIsIjpzdmc6ZmVEaXN0YW50TGlnaHReOnN2Zzp8IiwiOnN2ZzpmZURyb3BTaGFkb3deOnN2Zzp8IiwiOnN2ZzpmZUZsb29kXjpzdmc6fCIsIjpzdmc6ZmVGdW5jQV46c3ZnOmNvbXBvbmVudFRyYW5zZmVyRnVuY3Rpb258IiwiOnN2ZzpmZUZ1bmNCXjpzdmc6Y29tcG9uZW50VHJhbnNmZXJGdW5jdGlvbnwiLCI6c3ZnOmZlRnVuY0deOnN2Zzpjb21wb25lbnRUcmFuc2ZlckZ1bmN0aW9ufCIsIjpzdmc6ZmVGdW5jUl46c3ZnOmNvbXBvbmVudFRyYW5zZmVyRnVuY3Rpb258IiwiOnN2ZzpmZUdhdXNzaWFuQmx1cl46c3ZnOnwiLCI6c3ZnOmZlSW1hZ2VeOnN2Zzp8IiwiOnN2ZzpmZU1lcmdlXjpzdmc6fCIsIjpzdmc6ZmVNZXJnZU5vZGVeOnN2Zzp8IiwiOnN2ZzpmZU1vcnBob2xvZ3leOnN2Zzp8IiwiOnN2ZzpmZU9mZnNldF46c3ZnOnwiLCI6c3ZnOmZlUG9pbnRMaWdodF46c3ZnOnwiLCI6c3ZnOmZlU3BlY3VsYXJMaWdodGluZ146c3ZnOnwiLCI6c3ZnOmZlU3BvdExpZ2h0Xjpzdmc6fCIsIjpzdmc6ZmVUaWxlXjpzdmc6fCIsIjpzdmc6ZmVUdXJidWxlbmNlXjpzdmc6fCIsIjpzdmc6ZmlsdGVyXjpzdmc6fCIsIjpzdmc6Zm9yZWlnbk9iamVjdF46c3ZnOmdyYXBoaWNzfCIsIjpzdmc6Z146c3ZnOmdyYXBoaWNzfCIsIjpzdmc6aW1hZ2VeOnN2ZzpncmFwaGljc3wiLCI6c3ZnOmxpbmVeOnN2ZzpnZW9tZXRyeXwiLCI6c3ZnOmxpbmVhckdyYWRpZW50Xjpzdmc6Z3JhZGllbnR8IiwiOnN2ZzptcGF0aF46c3ZnOnwiLCI6c3ZnOm1hcmtlcl46c3ZnOnwiLCI6c3ZnOm1hc2teOnN2Zzp8IiwiOnN2ZzptZXRhZGF0YV46c3ZnOnwiLCI6c3ZnOnBhdGheOnN2ZzpnZW9tZXRyeXwiLCI6c3ZnOnBhdHRlcm5eOnN2Zzp8IiwiOnN2Zzpwb2x5Z29uXjpzdmc6Z2VvbWV0cnl8IiwiOnN2Zzpwb2x5bGluZV46c3ZnOmdlb21ldHJ5fCIsIjpzdmc6cmFkaWFsR3JhZGllbnReOnN2ZzpncmFkaWVudHwiLCI6c3ZnOnJlY3ReOnN2ZzpnZW9tZXRyeXwiLCI6c3ZnOnN2Z146c3ZnOmdyYXBoaWNzfCNjdXJyZW50U2NhbGUsI3pvb21BbmRQYW4iLCI6c3ZnOnNjcmlwdF46c3ZnOnx0eXBlIiwiOnN2ZzpzZXReOnN2ZzphbmltYXRpb258IiwiOnN2ZzpzdG9wXjpzdmc6fCIsIjpzdmc6c3R5bGVeOnN2Zzp8IWRpc2FibGVkLG1lZGlhLHRpdGxlLHR5cGUiLCI6c3ZnOnN3aXRjaF46c3ZnOmdyYXBoaWNzfCIsIjpzdmc6c3ltYm9sXjpzdmc6fCIsIjpzdmc6dHNwYW5eOnN2Zzp0ZXh0UG9zaXRpb25pbmd8IiwiOnN2Zzp0ZXh0Xjpzdmc6dGV4dFBvc2l0aW9uaW5nfCIsIjpzdmc6dGV4dFBhdGheOnN2Zzp0ZXh0Q29udGVudHwiLCI6c3ZnOnRpdGxlXjpzdmc6fCIsIjpzdmc6dXNlXjpzdmc6Z3JhcGhpY3N8IiwiOnN2Zzp2aWV3Xjpzdmc6fCN6b29tQW5kUGFuIiwiZGF0YV5bSFRNTEVsZW1lbnRdfHZhbHVlIiwia2V5Z2VuXltIVE1MRWxlbWVudF18IWF1dG9mb2N1cyxjaGFsbGVuZ2UsIWRpc2FibGVkLGZvcm0sa2V5dHlwZSxuYW1lIiwibWVudWl0ZW1eW0hUTUxFbGVtZW50XXx0eXBlLGxhYmVsLGljb24sIWRpc2FibGVkLCFjaGVja2VkLHJhZGlvZ3JvdXAsIWRlZmF1bHQiLCJzdW1tYXJ5XltIVE1MRWxlbWVudF18IiwidGltZV5bSFRNTEVsZW1lbnRdfGRhdGVUaW1lIiwiOnN2ZzpjdXJzb3JeOnN2Zzp8Il0sYks9bmV3IE1hcChPYmplY3QuZW50cmllcyh7Y2xhc3M6ImNsYXNzTmFtZSIsZm9yOiJodG1sRm9yIixmb3JtYWN0aW9uOiJmb3JtQWN0aW9uIixpbm5lckh0bWw6ImlubmVySFRNTCIscmVhZG9ubHk6InJlYWRPbmx5Iix0YWJpbmRleDoidGFiSW5kZXgifSkpLHlFZT1BcnJheS5mcm9tKGJLKS5yZWR1Y2UoKG4sW3QsZV0pPT4obi5zZXQodCxlKSxuKSxuZXcgTWFwKSxTRD1jbGFzcyBleHRlbmRzIHlWe2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLl9zY2hlbWE9bmV3IE1hcCx0aGlzLl9ldmVudFNjaGVtYT1uZXcgTWFwLHZFZS5mb3JFYWNoKHQ9PntsZXQgZT1uZXcgTWFwLGk9bmV3IFNldCxbcixvXT10LnNwbGl0KCJ8Iikscz1vLnNwbGl0KCIsIiksW2EsbF09ci5zcGxpdCgiXiIpO2Euc3BsaXQoIiwiKS5mb3JFYWNoKHU9Pnt0aGlzLl9zY2hlbWEuc2V0KHUudG9Mb3dlckNhc2UoKSxlKSx0aGlzLl9ldmVudFNjaGVtYS5zZXQodS50b0xvd2VyQ2FzZSgpLGkpfSk7bGV0IGM9bCYmdGhpcy5fc2NoZW1hLmdldChsLnRvTG93ZXJDYXNlKCkpO2lmKGMpe2ZvcihsZXRbdSxkXW9mIGMpZS5zZXQodSxkKTtmb3IobGV0IHUgb2YgdGhpcy5fZXZlbnRTY2hlbWEuZ2V0KGwudG9Mb3dlckNhc2UoKSkpaS5hZGQodSl9cy5mb3JFYWNoKHU9PntpZih1Lmxlbmd0aD4wKXN3aXRjaCh1WzBdKXtjYXNlIioiOmkuYWRkKHUuc3Vic3RyaW5nKDEpKTticmVhaztjYXNlIiEiOmUuc2V0KHUuc3Vic3RyaW5nKDEpLCJib29sZWFuIik7YnJlYWs7Y2FzZSIjIjplLnNldCh1LnN1YnN0cmluZygxKSwibnVtYmVyIik7YnJlYWs7Y2FzZSIlIjplLnNldCh1LnN1YnN0cmluZygxKSwib2JqZWN0Iik7YnJlYWs7ZGVmYXVsdDplLnNldCh1LCJzdHJpbmciKX19KX0pfWhhc1Byb3BlcnR5KHQsZSxpKXtpZihpLnNvbWUobz0+Im5vLWVycm9ycy1zY2hlbWEiPT09by5uYW1lKSlyZXR1cm4hMDtpZih0LmluZGV4T2YoIi0iKT4tMSl7aWYoQ0IodCl8fE1CKHQpKXJldHVybiExO2lmKGkuc29tZShvPT4iY3VzdG9tLWVsZW1lbnRzIj09PW8ubmFtZSkpcmV0dXJuITB9cmV0dXJuKHRoaXMuX3NjaGVtYS5nZXQodC50b0xvd2VyQ2FzZSgpKXx8dGhpcy5fc2NoZW1hLmdldCgidW5rbm93biIpKS5oYXMoZSl9aGFzRWxlbWVudCh0LGUpe3JldHVybiEhKGUuc29tZShpPT4ibm8tZXJyb3JzLXNjaGVtYSI9PT1pLm5hbWUpfHx0LmluZGV4T2YoIi0iKT4tMSYmKENCKHQpfHxNQih0KXx8ZS5zb21lKGk9PiJjdXN0b20tZWxlbWVudHMiPT09aS5uYW1lKSkpfHx0aGlzLl9zY2hlbWEuaGFzKHQudG9Mb3dlckNhc2UoKSl9c2VjdXJpdHlDb250ZXh0KHQsZSxpKXtpJiYoZT10aGlzLmdldE1hcHBlZFByb3BOYW1lKGUpKSx0PXQudG9Mb3dlckNhc2UoKSxlPWUudG9Mb3dlckNhc2UoKTtsZXQgcj1sUSgpW3QrInwiK2VdO3JldHVybiByfHwocj1sUSgpWyIqfCIrZV0scnx8aW8uTk9ORSl9Z2V0TWFwcGVkUHJvcE5hbWUodCl7cmV0dXJuIGJLLmdldCh0KT8/dH1nZXREZWZhdWx0Q29tcG9uZW50RWxlbWVudE5hbWUoKXtyZXR1cm4ibmctY29tcG9uZW50In12YWxpZGF0ZVByb3BlcnR5KHQpe3JldHVybiB0LnRvTG93ZXJDYXNlKCkuc3RhcnRzV2l0aCgib24iKT97ZXJyb3I6ITAsbXNnOmBCaW5kaW5nIHRvIGV2ZW50IHByb3BlcnR5ICcke3R9JyBpcyBkaXNhbGxvd2VkIGZvciBzZWN1cml0eSByZWFzb25zLCBwbGVhc2UgdXNlICgke3Quc2xpY2UoMil9KT0uLi5cbklmICcke3R9JyBpcyBhIGRpcmVjdGl2ZSBpbnB1dCwgbWFrZSBzdXJlIHRoZSBkaXJlY3RpdmUgaXMgaW1wb3J0ZWQgYnkgdGhlIGN1cnJlbnQgbW9kdWxlLmB9OntlcnJvcjohMX19dmFsaWRhdGVBdHRyaWJ1dGUodCl7cmV0dXJuIHQudG9Mb3dlckNhc2UoKS5zdGFydHNXaXRoKCJvbiIpP3tlcnJvcjohMCxtc2c6YEJpbmRpbmcgdG8gZXZlbnQgYXR0cmlidXRlICcke3R9JyBpcyBkaXNhbGxvd2VkIGZvciBzZWN1cml0eSByZWFzb25zLCBwbGVhc2UgdXNlICgke3Quc2xpY2UoMil9KT0uLi5gfTp7ZXJyb3I6ITF9fWFsbEtub3duRWxlbWVudE5hbWVzKCl7cmV0dXJuIEFycmF5LmZyb20odGhpcy5fc2NoZW1hLmtleXMoKSl9YWxsS25vd25BdHRyaWJ1dGVzT2ZFbGVtZW50KHQpe2xldCBlPXRoaXMuX3NjaGVtYS5nZXQodC50b0xvd2VyQ2FzZSgpKXx8dGhpcy5fc2NoZW1hLmdldCgidW5rbm93biIpO3JldHVybiBBcnJheS5mcm9tKGUua2V5cygpKS5tYXAoaT0+eUVlLmdldChpKT8/aSl9YWxsS25vd25FdmVudHNPZkVsZW1lbnQodCl7cmV0dXJuIEFycmF5LmZyb20odGhpcy5fZXZlbnRTY2hlbWEuZ2V0KHQudG9Mb3dlckNhc2UoKSk/P1tdKX1ub3JtYWxpemVBbmltYXRpb25TdHlsZVByb3BlcnR5KHQpe3JldHVybiB0LnJlcGxhY2UoeU1lLCguLi50KT0+dFsxXS50b1VwcGVyQ2FzZSgpKX1ub3JtYWxpemVBbmltYXRpb25TdHlsZVZhbHVlKHQsZSxpKXtsZXQgcj0iIixvPWkudG9TdHJpbmcoKS50cmltKCkscz1udWxsO2lmKGZ1bmN0aW9uKG4pe3N3aXRjaChuKXtjYXNlIndpZHRoIjpjYXNlImhlaWdodCI6Y2FzZSJtaW5XaWR0aCI6Y2FzZSJtaW5IZWlnaHQiOmNhc2UibWF4V2lkdGgiOmNhc2UibWF4SGVpZ2h0IjpjYXNlImxlZnQiOmNhc2UidG9wIjpjYXNlImJvdHRvbSI6Y2FzZSJyaWdodCI6Y2FzZSJmb250U2l6ZSI6Y2FzZSJvdXRsaW5lV2lkdGgiOmNhc2Uib3V0bGluZU9mZnNldCI6Y2FzZSJwYWRkaW5nVG9wIjpjYXNlInBhZGRpbmdMZWZ0IjpjYXNlInBhZGRpbmdCb3R0b20iOmNhc2UicGFkZGluZ1JpZ2h0IjpjYXNlIm1hcmdpblRvcCI6Y2FzZSJtYXJnaW5MZWZ0IjpjYXNlIm1hcmdpbkJvdHRvbSI6Y2FzZSJtYXJnaW5SaWdodCI6Y2FzZSJib3JkZXJSYWRpdXMiOmNhc2UiYm9yZGVyV2lkdGgiOmNhc2UiYm9yZGVyVG9wV2lkdGgiOmNhc2UiYm9yZGVyTGVmdFdpZHRoIjpjYXNlImJvcmRlclJpZ2h0V2lkdGgiOmNhc2UiYm9yZGVyQm90dG9tV2lkdGgiOmNhc2UidGV4dEluZGVudCI6cmV0dXJuITA7ZGVmYXVsdDpyZXR1cm4hMX19KHQpJiYwIT09aSYmIjAiIT09aSlpZigibnVtYmVyIj09dHlwZW9mIGkpcj0icHgiO2Vsc2V7bGV0IGE9aS5tYXRjaCgvXlsrLV0/W1xkXC5dKyhbYS16XSopJC8pO2EmJjA9PWFbMV0ubGVuZ3RoJiYocz1gUGxlYXNlIHByb3ZpZGUgYSBDU1MgdW5pdCB2YWx1ZSBmb3IgJHtlfToke2l9YCl9cmV0dXJue2Vycm9yOnMsdmFsdWU6bytyfX19LGNRPW5ldyBTZXQoWyJpZnJhbWV8c3JjZG9jIiwiKnxpbm5lcmh0bWwiLCIqfG91dGVyaHRtbCIsImVtYmVkfHNyYyIsIm9iamVjdHxjb2RlYmFzZSIsIm9iamVjdHxkYXRhIl0pO2Z1bmN0aW9uIHhLKG4sdCl7cmV0dXJuIG49bi50b0xvd2VyQ2FzZSgpLHQ9dC50b0xvd2VyQ2FzZSgpLGNRLmhhcyhuKyJ8Iit0KXx8Y1EuaGFzKCIqfCIrdCl9dmFyIHBCPSJhbmltYXRlLSI7ZnVuY3Rpb24gaEIobil7cmV0dXJuIkAiPT1uWzBdfWZ1bmN0aW9uIGZCKG4sdCxlLGkpe2xldCByPVtdO3JldHVybiBaZC5wYXJzZSh0KS5mb3JFYWNoKG89PntsZXQgcz1vLmVsZW1lbnQ/W28uZWxlbWVudF06bi5hbGxLbm93bkVsZW1lbnROYW1lcygpLGE9bmV3IFNldChvLm5vdFNlbGVjdG9ycy5maWx0ZXIoYz0+Yy5pc0VsZW1lbnRTZWxlY3RvcigpKS5tYXAoYz0+Yy5lbGVtZW50KSksbD1zLmZpbHRlcihjPT4hYS5oYXMoYykpO3IucHVzaCguLi5sLm1hcChjPT5uLnNlY3VyaXR5Q29udGV4dChjLGUsaSkpKX0pLDA9PT1yLmxlbmd0aD9baW8uTk9ORV06QXJyYXkuZnJvbShuZXcgU2V0KHIpKS5zb3J0KCl9ZnVuY3Rpb24gS3Aobix0KXtsZXQgZT10LnN0YXJ0LW4uc3RhcnQub2Zmc2V0LGk9dC5lbmQtbi5lbmQub2Zmc2V0O3JldHVybiBuZXcgR28obi5zdGFydC5tb3ZlQnkoZSksbi5lbmQubW92ZUJ5KGkpLG4uZnVsbFN0YXJ0Lm1vdmVCeShlKSxuLmRldGFpbHMpfXZhciBFRWU9L14oW146Lz8jXSspOi87ZnVuY3Rpb24gQ0sobil7bGV0IHQ9bnVsbCxlPW51bGwsaT1udWxsLHI9ITEsbz0iIjtuLmF0dHJzLmZvckVhY2gobD0+e2xldCBjPWwubmFtZS50b0xvd2VyQ2FzZSgpOyJzZWxlY3QiPT1jP3Q9bC52YWx1ZToiaHJlZiI9PWM/ZT1sLnZhbHVlOiJyZWwiPT1jP2k9bC52YWx1ZToibmdOb25CaW5kYWJsZSI9PWwubmFtZT9yPSEwOiJuZ1Byb2plY3RBcyI9PWwubmFtZSYmbC52YWx1ZS5sZW5ndGg+MCYmKG89bC52YWx1ZSl9KSx0PWZ1bmN0aW9uKG4pe3JldHVybiBudWxsPT09bnx8MD09PW4ubGVuZ3RoPyIqIjpufSh0KTtsZXQgcz1uLm5hbWUudG9Mb3dlckNhc2UoKSxhPW5hLk9USEVSO3JldHVybiBNQihzKT9hPW5hLk5HX0NPTlRFTlQ6InN0eWxlIj09cz9hPW5hLlNUWUxFOiJzY3JpcHQiPT1zP2E9bmEuU0NSSVBUOiJsaW5rIj09cyYmInN0eWxlc2hlZXQiPT1pJiYoYT1uYS5TVFlMRVNIRUVUKSxuZXcgeFYoYSx0LGUscixvKX12YXIgbmE9KCgpPT57cmV0dXJuKG49bmF8fChuYT17fSkpW24uTkdfQ09OVEVOVD0wXT0iTkdfQ09OVEVOVCIsbltuLlNUWUxFPTFdPSJTVFlMRSIsbltuLlNUWUxFU0hFRVQ9Ml09IlNUWUxFU0hFRVQiLG5bbi5TQ1JJUFQ9M109IlNDUklQVCIsbltuLk9USEVSPTRdPSJPVEhFUiIsbmE7dmFyIG59KSgpLHhWPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7dGhpcy50eXBlPXQsdGhpcy5zZWxlY3RBdHRyPWUsdGhpcy5ocmVmQXR0cj1pLHRoaXMubm9uQmluZGFibGU9cix0aGlzLnByb2plY3RBcz1vfX0sTEVlPS9eKD86KGJpbmQtKXwobGV0LSl8KHJlZi18Iyl8KG9uLSl8KGJpbmRvbi0pfChAKSkoLiopJC8sWnBfQkFOQU5BX0JPWD17c3RhcnQ6IlsoIixlbmQ6IildIn0sWnBfUFJPUEVSVFk9e3N0YXJ0OiJbIixlbmQ6Il0ifSxacF9FVkVOVD17c3RhcnQ6IigiLGVuZDoiKSJ9LEhFZT1uZXcgY2xhc3N7dmlzaXRFbGVtZW50KHQpe2xldCBlPUNLKHQpO2lmKGUudHlwZT09PW5hLlNDUklQVHx8ZS50eXBlPT09bmEuU1RZTEV8fGUudHlwZT09PW5hLlNUWUxFU0hFRVQpcmV0dXJuIG51bGw7bGV0IGk9VXUodGhpcyx0LmNoaWxkcmVuLG51bGwpO3JldHVybiBuZXcgRV8odC5uYW1lLFV1KHRoaXMsdC5hdHRycyksW10sW10saSxbXSx0LnNvdXJjZVNwYW4sdC5zdGFydFNvdXJjZVNwYW4sdC5lbmRTb3VyY2VTcGFuKX12aXNpdENvbW1lbnQodCl7cmV0dXJuIG51bGx9dmlzaXRBdHRyaWJ1dGUodCl7cmV0dXJuIG5ldyBEQyh0Lm5hbWUsdC52YWx1ZSx0LnNvdXJjZVNwYW4sdC5rZXlTcGFuLHQudmFsdWVTcGFuLHQuaTE4bil9dmlzaXRUZXh0KHQpe3JldHVybiBuZXcgTV8odC52YWx1ZSx0LnNvdXJjZVNwYW4pfXZpc2l0RXhwYW5zaW9uKHQpe3JldHVybiBudWxsfXZpc2l0RXhwYW5zaW9uQ2FzZSh0KXtyZXR1cm4gbnVsbH19O2Z1bmN0aW9uIF9RKG4pe3JldHVybi9eZGF0YS0vaS50ZXN0KG4pP24uc3Vic3RyaW5nKDUpOm59ZnVuY3Rpb24gbUIobix0KXt0LnB1c2goLi4ubi5tYXAoZT0+U18uZnJvbVBhcnNlZEV2ZW50KGUpKSl9dmFyIExjPSgoKT0+e3JldHVybihuPUxjfHwoTGM9e30pKVtuLkVMRU1FTlQ9MF09IkVMRU1FTlQiLG5bbi5URU1QTEFURT0xXT0iVEVNUExBVEUiLExjO3ZhciBufSkoKSxRQz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaT0wLHI9bnVsbCxvLHMpe3RoaXMuaW5kZXg9dCx0aGlzLnJlZj1lLHRoaXMubGV2ZWw9aSx0aGlzLnRlbXBsYXRlSW5kZXg9cix0aGlzLm1ldGE9byx0aGlzLnJlZ2lzdHJ5PXMsdGhpcy5iaW5kaW5ncz1uZXcgU2V0LHRoaXMucGxhY2Vob2xkZXJzPW5ldyBNYXAsdGhpcy5pc0VtaXR0ZWQ9ITEsdGhpcy5fdW5yZXNvbHZlZEN0eENvdW50PTAsdGhpcy5fcmVnaXN0cnk9c3x8e2dldFVuaXF1ZUlkOlN3ZSgpLGljdXM6bmV3IE1hcH0sdGhpcy5pZD10aGlzLl9yZWdpc3RyeS5nZXRVbmlxdWVJZCgpfWFwcGVuZFRhZyh0LGUsaSxyKXtlLmlzVm9pZCYmcnx8cVQodGhpcy5wbGFjZWhvbGRlcnMsZS5pc1ZvaWR8fCFyP2Uuc3RhcnROYW1lOmUuY2xvc2VOYW1lLHt0eXBlOnQsaW5kZXg6aSxjdHg6dGhpcy5pZCxpc1ZvaWQ6ZS5pc1ZvaWQsY2xvc2VkOnJ9KX1nZXQgaWN1cygpe3JldHVybiB0aGlzLl9yZWdpc3RyeS5pY3VzfWdldCBpc1Jvb3QoKXtyZXR1cm4gMD09PXRoaXMubGV2ZWx9Z2V0IGlzUmVzb2x2ZWQoKXtyZXR1cm4gMD09PXRoaXMuX3VucmVzb2x2ZWRDdHhDb3VudH1nZXRTZXJpYWxpemVkUGxhY2Vob2xkZXJzKCl7bGV0IHQ9bmV3IE1hcDtyZXR1cm4gdGhpcy5wbGFjZWhvbGRlcnMuZm9yRWFjaCgoZSxpKT0+dC5zZXQoaSxlLm1hcChXRWUpKSksdH1hcHBlbmRCaW5kaW5nKHQpe3RoaXMuYmluZGluZ3MuYWRkKHQpfWFwcGVuZEljdSh0LGUpe3FUKHRoaXMuX3JlZ2lzdHJ5LmljdXMsdCxlKX1hcHBlbmRCb3VuZFRleHQodCl7cVEodCx0aGlzLmJpbmRpbmdzLnNpemUsdGhpcy5pZCkuZm9yRWFjaCgoaSxyKT0+cVQodGhpcy5wbGFjZWhvbGRlcnMsciwuLi5pKSl9YXBwZW5kVGVtcGxhdGUodCxlKXt0aGlzLmFwcGVuZFRhZyhMYy5URU1QTEFURSx0LGUsITEpLHRoaXMuYXBwZW5kVGFnKExjLlRFTVBMQVRFLHQsZSwhMCksdGhpcy5fdW5yZXNvbHZlZEN0eENvdW50Kyt9YXBwZW5kRWxlbWVudCh0LGUsaSl7dGhpcy5hcHBlbmRUYWcoTGMuRUxFTUVOVCx0LGUsaSl9YXBwZW5kUHJvamVjdGlvbih0LGUpe3RoaXMuYXBwZW5kVGFnKExjLkVMRU1FTlQsdCxlLCExKSx0aGlzLmFwcGVuZFRhZyhMYy5FTEVNRU5ULHQsZSwhMCl9Zm9ya0NoaWxkQ29udGV4dCh0LGUsaSl7cmV0dXJuIG5ldyBRQyh0LHRoaXMucmVmLHRoaXMubGV2ZWwrMSxlLGksdGhpcy5fcmVnaXN0cnkpfXJlY29uY2lsZUNoaWxkQ29udGV4dCh0KXtbInN0YXJ0IiwiY2xvc2UiXS5mb3JFYWNoKGk9PntsZXQgcz0odGhpcy5wbGFjZWhvbGRlcnMuZ2V0KHQubWV0YVtgJHtpfU5hbWVgXSl8fFtdKS5maW5kKHZRKHRoaXMuaWQsdC50ZW1wbGF0ZUluZGV4KSk7cyYmKHMuY3R4PXQuaWQpfSksdC5wbGFjZWhvbGRlcnMuZm9yRWFjaCgoaSxyKT0+e2xldCBvPXRoaXMucGxhY2Vob2xkZXJzLmdldChyKTtpZighbylyZXR1cm4gdm9pZCB0aGlzLnBsYWNlaG9sZGVycy5zZXQocixpKTtsZXQgcz1vLmZpbmRJbmRleCh2USh0LmlkLHQudGVtcGxhdGVJbmRleCkpO2lmKHM+PTApe2xldCBhPXIuc3RhcnRzV2l0aCgiQ0xPU0UiKTtyLmVuZHNXaXRoKCJORy1URU1QTEFURSIpP28uc3BsaWNlKHMrKGE/MDoxKSwwLC4uLmkpOihpW2E/aS5sZW5ndGgtMTowXS50bXBsPW9bc10sby5zcGxpY2UocywxLC4uLmkpKX1lbHNlIG8ucHVzaCguLi5pKTt0aGlzLnBsYWNlaG9sZGVycy5zZXQocixvKX0pLHRoaXMuX3VucmVzb2x2ZWRDdHhDb3VudC0tfX07ZnVuY3Rpb24gZ0Iobix0LGUsaSl7cmV0dXJuIFBEKGAke2k/Ii8iOiIifSR7bn0ke3R9YCxlKX1mdW5jdGlvbiBfQihuLHtpbmRleDp0LGN0eDplLGlzVm9pZDppfSxyKXtyZXR1cm4gaT9nQihuLHQsZSkrZ0Iobix0LGUsITApOmdCKG4sdCxlLHIpfWZ1bmN0aW9uIHZRKG4sdCl7cmV0dXJuIGU9PiJvYmplY3QiPT10eXBlb2YgZSYmZS50eXBlPT09TGMuVEVNUExBVEUmJmUuaW5kZXg9PT10JiZlLmN0eD09PW59ZnVuY3Rpb24gV0VlKG4pe2xldCB0PShyLG8pPT5fQigiIyIscixvKSxlPShyLG8pPT5fQigiKiIscixvKTtzd2l0Y2gobi50eXBlKXtjYXNlIExjLkVMRU1FTlQ6cmV0dXJuIG4uY2xvc2VkP3QobiwhMCkrKG4udG1wbD9lKG4udG1wbCwhMCk6IiIpOm4udG1wbD9lKG4udG1wbCkrdChuKSsobi5pc1ZvaWQ/ZShuLnRtcGwsITApOiIiKTp0KG4pO2Nhc2UgTGMuVEVNUExBVEU6cmV0dXJuIGUobixuLmNsb3NlZCk7ZGVmYXVsdDpyZXR1cm4gbn19dmFyIHFFZT1uZXcgY2xhc3N7dmlzaXRUZXh0KHQpe3JldHVybiB0LnZhbHVlfXZpc2l0Q29udGFpbmVyKHQpe3JldHVybiB0LmNoaWxkcmVuLm1hcChlPT5lLnZpc2l0KHRoaXMpKS5qb2luKCIiKX12aXNpdEljdSh0KXtsZXQgZT1PYmplY3Qua2V5cyh0LmNhc2VzKS5tYXAocj0+YCR7cn0geyR7dC5jYXNlc1tyXS52aXNpdCh0aGlzKX19YCk7cmV0dXJuYHske3QuZXhwcmVzc2lvblBsYWNlaG9sZGVyfSwgJHt0LnR5cGV9LCAke2Uuam9pbigiICIpfX1gfXZpc2l0VGFnUGxhY2Vob2xkZXIodCl7cmV0dXJuIHQuaXNWb2lkP3RoaXMuZm9ybWF0UGgodC5zdGFydE5hbWUpOmAke3RoaXMuZm9ybWF0UGgodC5zdGFydE5hbWUpfSR7dC5jaGlsZHJlbi5tYXAoZT0+ZS52aXNpdCh0aGlzKSkuam9pbigiIil9JHt0aGlzLmZvcm1hdFBoKHQuY2xvc2VOYW1lKX1gfXZpc2l0UGxhY2Vob2xkZXIodCl7cmV0dXJuIHRoaXMuZm9ybWF0UGgodC5uYW1lKX12aXNpdEljdVBsYWNlaG9sZGVyKHQsZSl7cmV0dXJuIHRoaXMuZm9ybWF0UGgodC5uYW1lKX1mb3JtYXRQaCh0KXtyZXR1cm5geyR7SkModCwhMSl9fWB9fTtmdW5jdGlvbiBNSyhuKXtyZXR1cm4gbi52aXNpdChxRWUpfXZhciB5UT17QToiTElOSyIsQjoiQk9MRF9URVhUIixCUjoiTElORV9CUkVBSyIsRU06IkVNUEhBU0lTRURfVEVYVCIsSDE6IkhFQURJTkdfTEVWRUwxIixIMjoiSEVBRElOR19MRVZFTDIiLEgzOiJIRUFESU5HX0xFVkVMMyIsSDQ6IkhFQURJTkdfTEVWRUw0IixINToiSEVBRElOR19MRVZFTDUiLEg2OiJIRUFESU5HX0xFVkVMNiIsSFI6IkhPUklaT05UQUxfUlVMRSIsSToiSVRBTElDX1RFWFQiLExJOiJMSVNUX0lURU0iLExJTks6Ik1FRElBX0xJTksiLE9MOiJPUkRFUkVEX0xJU1QiLFA6IlBBUkFHUkFQSCIsUToiUVVPVEFUSU9OIixTOiJTVFJJS0VUSFJPVUdIX1RFWFQiLFNNQUxMOiJTTUFMTF9URVhUIixTVUI6IlNVQlNUUklQVCIsU1VQOiJTVVBFUlNDUklQVCIsVEJPRFk6IlRBQkxFX0JPRFkiLFREOiJUQUJMRV9DRUxMIixURk9PVDoiVEFCTEVfRk9PVEVSIixUSDoiVEFCTEVfSEVBREVSX0NFTEwiLFRIRUFEOiJUQUJMRV9IRUFERVIiLFRSOiJUQUJMRV9ST1ciLFRUOiJNT05PU1BBQ0VEX1RFWFQiLFU6IlVOREVSTElORURfVEVYVCIsVUw6IlVOT1JERVJFRF9MSVNUIn0sU1Y9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLl9wbGFjZUhvbGRlck5hbWVDb3VudHM9e30sdGhpcy5fc2lnbmF0dXJlVG9OYW1lPXt9fWdldFN0YXJ0VGFnUGxhY2Vob2xkZXJOYW1lKHQsZSxpKXtsZXQgcj10aGlzLl9oYXNoVGFnKHQsZSxpKTtpZih0aGlzLl9zaWduYXR1cmVUb05hbWVbcl0pcmV0dXJuIHRoaXMuX3NpZ25hdHVyZVRvTmFtZVtyXTtsZXQgbz10LnRvVXBwZXJDYXNlKCkscz15UVtvXXx8YFRBR18ke299YCxhPXRoaXMuX2dlbmVyYXRlVW5pcXVlTmFtZShpP3M6YFNUQVJUXyR7c31gKTtyZXR1cm4gdGhpcy5fc2lnbmF0dXJlVG9OYW1lW3JdPWEsYX1nZXRDbG9zZVRhZ1BsYWNlaG9sZGVyTmFtZSh0KXtsZXQgZT10aGlzLl9oYXNoQ2xvc2luZ1RhZyh0KTtpZih0aGlzLl9zaWduYXR1cmVUb05hbWVbZV0pcmV0dXJuIHRoaXMuX3NpZ25hdHVyZVRvTmFtZVtlXTtsZXQgaT10LnRvVXBwZXJDYXNlKCksbz10aGlzLl9nZW5lcmF0ZVVuaXF1ZU5hbWUoYENMT1NFXyR7eVFbaV18fGBUQUdfJHtpfWB9YCk7cmV0dXJuIHRoaXMuX3NpZ25hdHVyZVRvTmFtZVtlXT1vLG99Z2V0UGxhY2Vob2xkZXJOYW1lKHQsZSl7bGV0IGk9dC50b1VwcGVyQ2FzZSgpLHI9YFBIOiAke2l9PSR7ZX1gO2lmKHRoaXMuX3NpZ25hdHVyZVRvTmFtZVtyXSlyZXR1cm4gdGhpcy5fc2lnbmF0dXJlVG9OYW1lW3JdO2xldCBvPXRoaXMuX2dlbmVyYXRlVW5pcXVlTmFtZShpKTtyZXR1cm4gdGhpcy5fc2lnbmF0dXJlVG9OYW1lW3JdPW8sb31nZXRVbmlxdWVQbGFjZWhvbGRlcih0KXtyZXR1cm4gdGhpcy5fZ2VuZXJhdGVVbmlxdWVOYW1lKHQudG9VcHBlckNhc2UoKSl9X2hhc2hUYWcodCxlLGkpe3JldHVybmA8JHt0fWArT2JqZWN0LmtleXMoZSkuc29ydCgpLm1hcChhPT5gICR7YX09JHtlW2FdfWApLmpvaW4oIiIpKyhpPyIvPiI6YD48LyR7dH0+YCl9X2hhc2hDbG9zaW5nVGFnKHQpe3JldHVybiB0aGlzLl9oYXNoVGFnKGAvJHt0fWAse30sITEpfV9nZW5lcmF0ZVVuaXF1ZU5hbWUodCl7aWYoIXRoaXMuX3BsYWNlSG9sZGVyTmFtZUNvdW50cy5oYXNPd25Qcm9wZXJ0eSh0KSlyZXR1cm4gdGhpcy5fcGxhY2VIb2xkZXJOYW1lQ291bnRzW3RdPTEsdDtsZXQgaT10aGlzLl9wbGFjZUhvbGRlck5hbWVDb3VudHNbdF07cmV0dXJuIHRoaXMuX3BsYWNlSG9sZGVyTmFtZUNvdW50c1t0XT1pKzEsYCR7dH1fJHtpfWB9fSxZRWU9bmV3IGJEKG5ldyB5RCk7ZnVuY3Rpb24gUUVlKG4sdCl7cmV0dXJuIHR9dmFyICRFZT0vXC9cL1tcc1xTXSppMThuW1xzXFNdKlwoW1xzXFNdKnBoW1xzXFNdKj1bXHNcU10qKCJ8JykoW1xzXFNdKj8pXDFbXHNcU10qXCkvZyxUVj1jbGFzcyBleHRlbmRzIHlte2NvbnN0cnVjdG9yKHQsZSl7c3VwZXIodCxlKX19LHQxZT0obix0KT0+KG4gaW5zdGFuY2VvZiBDbSYmKHQgaW5zdGFuY2VvZiBUXyYmbi5pMThuIGluc3RhbmNlb2YgRnUmJih0LnByZXZpb3VzTWVzc2FnZT1uLmkxOG4pLG4uaTE4bj10KSx0KSxFRD1jbGFzc3tjb25zdHJ1Y3Rvcih0PVB1LGU9ITEsaT0hMSl7dGhpcy5pbnRlcnBvbGF0aW9uQ29uZmlnPXQsdGhpcy5rZWVwSTE4bkF0dHJzPWUsdGhpcy5lbmFibGVJMThuTGVnYWN5TWVzc2FnZUlkRm9ybWF0PWksdGhpcy5oYXNJMThuTWV0YT0hMSx0aGlzLl9lcnJvcnM9W10sdGhpcy5fY3JlYXRlSTE4bk1lc3NhZ2U9ZnVuY3Rpb24obil7bGV0IHQ9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5fZXhwcmVzc2lvblBhcnNlcj10LHRoaXMuX2ludGVycG9sYXRpb25Db25maWc9ZX10b0kxOG5NZXNzYWdlKHQsZT0iIixpPSIiLHI9IiIsbyl7bGV0IHM9e2lzSWN1OjE9PXQubGVuZ3RoJiZ0WzBdaW5zdGFuY2VvZiBOXyxpY3VEZXB0aDowLHBsYWNlaG9sZGVyUmVnaXN0cnk6bmV3IFNWLHBsYWNlaG9sZGVyVG9Db250ZW50Ont9LHBsYWNlaG9sZGVyVG9NZXNzYWdlOnt9LHZpc2l0Tm9kZUZuOm98fFFFZX0sYT1VdSh0aGlzLHQscyk7cmV0dXJuIG5ldyBGdShhLHMucGxhY2Vob2xkZXJUb0NvbnRlbnQscy5wbGFjZWhvbGRlclRvTWVzc2FnZSxlLGkscil9dmlzaXRFbGVtZW50KHQsZSl7bGV0IGk9VXUodGhpcyx0LmNoaWxkcmVuLGUpLHI9e307dC5hdHRycy5mb3JFYWNoKGM9PntyW2MubmFtZV09Yy52YWx1ZX0pO2xldCBvPUZWKHQubmFtZSkuaXNWb2lkLHM9ZS5wbGFjZWhvbGRlclJlZ2lzdHJ5LmdldFN0YXJ0VGFnUGxhY2Vob2xkZXJOYW1lKHQubmFtZSxyLG8pO2UucGxhY2Vob2xkZXJUb0NvbnRlbnRbc109e3RleHQ6dC5zdGFydFNvdXJjZVNwYW4udG9TdHJpbmcoKSxzb3VyY2VTcGFuOnQuc3RhcnRTb3VyY2VTcGFufTtsZXQgYT0iIjtvfHwoYT1lLnBsYWNlaG9sZGVyUmVnaXN0cnkuZ2V0Q2xvc2VUYWdQbGFjZWhvbGRlck5hbWUodC5uYW1lKSxlLnBsYWNlaG9sZGVyVG9Db250ZW50W2FdPXt0ZXh0OmA8LyR7dC5uYW1lfT5gLHNvdXJjZVNwYW46dC5lbmRTb3VyY2VTcGFuPz90LnNvdXJjZVNwYW59KTtsZXQgbD1uZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlLGkscixvLHMsYSxsLGMpe3RoaXMudGFnPXQsdGhpcy5hdHRycz1lLHRoaXMuc3RhcnROYW1lPWksdGhpcy5jbG9zZU5hbWU9cix0aGlzLmNoaWxkcmVuPW8sdGhpcy5pc1ZvaWQ9cyx0aGlzLnNvdXJjZVNwYW49YSx0aGlzLnN0YXJ0U291cmNlU3Bhbj1sLHRoaXMuZW5kU291cmNlU3Bhbj1jfXZpc2l0KHQsZSl7cmV0dXJuIHQudmlzaXRUYWdQbGFjZWhvbGRlcih0aGlzLGUpfX0odC5uYW1lLHIscyxhLGksbyx0LnNvdXJjZVNwYW4sdC5zdGFydFNvdXJjZVNwYW4sdC5lbmRTb3VyY2VTcGFuKTtyZXR1cm4gZS52aXNpdE5vZGVGbih0LGwpfXZpc2l0QXR0cmlidXRlKHQsZSl7bGV0IGk9dm9pZCAwPT09dC52YWx1ZVRva2Vuc3x8MT09PXQudmFsdWVUb2tlbnMubGVuZ3RoP25ldyBsXyh0LnZhbHVlLHQudmFsdWVTcGFufHx0LnNvdXJjZVNwYW4pOnRoaXMuX3Zpc2l0VGV4dFdpdGhJbnRlcnBvbGF0aW9uKHQudmFsdWVUb2tlbnMsdC52YWx1ZVNwYW58fHQuc291cmNlU3BhbixlLHQuaTE4bik7cmV0dXJuIGUudmlzaXROb2RlRm4odCxpKX12aXNpdFRleHQodCxlKXtsZXQgaT0xPT09dC50b2tlbnMubGVuZ3RoP25ldyBsXyh0LnZhbHVlLHQuc291cmNlU3Bhbik6dGhpcy5fdmlzaXRUZXh0V2l0aEludGVycG9sYXRpb24odC50b2tlbnMsdC5zb3VyY2VTcGFuLGUsdC5pMThuKTtyZXR1cm4gZS52aXNpdE5vZGVGbih0LGkpfXZpc2l0Q29tbWVudCh0LGUpe3JldHVybiBudWxsfXZpc2l0RXhwYW5zaW9uKHQsZSl7ZS5pY3VEZXB0aCsrO2xldCBpPXt9LHI9bmV3IGFEKHQuc3dpdGNoVmFsdWUsdC50eXBlLGksdC5zb3VyY2VTcGFuKTtpZih0LmNhc2VzLmZvckVhY2goYT0+e2lbYS52YWx1ZV09bmV3IG1tKGEuZXhwcmVzc2lvbi5tYXAobD0+bC52aXNpdCh0aGlzLGUpKSxhLmV4cFNvdXJjZVNwYW4pfSksZS5pY3VEZXB0aC0tLGUuaXNJY3V8fGUuaWN1RGVwdGg+MCl7bGV0IGE9ZS5wbGFjZWhvbGRlclJlZ2lzdHJ5LmdldFVuaXF1ZVBsYWNlaG9sZGVyKGBWQVJfJHt0LnR5cGV9YCk7cmV0dXJuIHIuZXhwcmVzc2lvblBsYWNlaG9sZGVyPWEsZS5wbGFjZWhvbGRlclRvQ29udGVudFthXT17dGV4dDp0LnN3aXRjaFZhbHVlLHNvdXJjZVNwYW46dC5zd2l0Y2hWYWx1ZVNvdXJjZVNwYW59LGUudmlzaXROb2RlRm4odCxyKX1sZXQgbz1lLnBsYWNlaG9sZGVyUmVnaXN0cnkuZ2V0UGxhY2Vob2xkZXJOYW1lKCJJQ1UiLHQuc291cmNlU3Bhbi50b1N0cmluZygpKTtlLnBsYWNlaG9sZGVyVG9NZXNzYWdlW29dPXRoaXMudG9JMThuTWVzc2FnZShbdF0sIiIsIiIsIiIsdm9pZCAwKTtsZXQgcz1uZXcgVF8ocixvLHQuc291cmNlU3Bhbik7cmV0dXJuIGUudmlzaXROb2RlRm4odCxzKX12aXNpdEV4cGFuc2lvbkNhc2UodCxlKXt0aHJvdyBuZXcgRXJyb3IoIlVucmVhY2hhYmxlIGNvZGUiKX1fdmlzaXRUZXh0V2l0aEludGVycG9sYXRpb24odCxlLGkscil7bGV0IG89W10scz0hMTtmb3IobGV0IGEgb2YgdClzd2l0Y2goYS50eXBlKXtjYXNlIDg6Y2FzZSAxNzpzPSEwO2xldCBsPWEucGFydHNbMV0sYz1sLnNwbGl0KCRFZSlbMl18fCJJTlRFUlBPTEFUSU9OIix1PWkucGxhY2Vob2xkZXJSZWdpc3RyeS5nZXRQbGFjZWhvbGRlck5hbWUoYyxsKTtpLnBsYWNlaG9sZGVyVG9Db250ZW50W3VdPXt0ZXh0OmEucGFydHMuam9pbigiIiksc291cmNlU3BhbjphLnNvdXJjZVNwYW59LG8ucHVzaChuZXcgbEQobCx1LGEuc291cmNlU3BhbikpO2JyZWFrO2RlZmF1bHQ6aWYoYS5wYXJ0c1swXS5sZW5ndGg+MCl7bGV0IGQ9b1tvLmxlbmd0aC0xXTtkIGluc3RhbmNlb2YgbF8/KGQudmFsdWUrPWEucGFydHNbMF0sZC5zb3VyY2VTcGFuPW5ldyBHbyhkLnNvdXJjZVNwYW4uc3RhcnQsYS5zb3VyY2VTcGFuLmVuZCxkLnNvdXJjZVNwYW4uZnVsbFN0YXJ0LGQuc291cmNlU3Bhbi5kZXRhaWxzKSk6by5wdXNoKG5ldyBsXyhhLnBhcnRzWzBdLGEuc291cmNlU3BhbikpfX1yZXR1cm4gcz8oZnVuY3Rpb24obix0KXtpZih0IGluc3RhbmNlb2YgRnUmJihmdW5jdGlvbihuKXtsZXQgdD1uLm5vZGVzO2lmKDEhPT10Lmxlbmd0aHx8ISh0WzBdaW5zdGFuY2VvZiBtbSkpdGhyb3cgbmV3IEVycm9yKCJVbmV4cGVjdGVkIHByZXZpb3VzIGkxOG4gbWVzc2FnZSAtIGV4cGVjdGVkIGl0IHRvIGNvbnNpc3Qgb2Ygb25seSBhIHNpbmdsZSBgQ29udGFpbmVyYCBub2RlLiIpfSh0KSx0PXQubm9kZXNbMF0pLHQgaW5zdGFuY2VvZiBtbSl7IWZ1bmN0aW9uKG4sdCl7aWYobi5sZW5ndGghPT10Lmxlbmd0aCl0aHJvdyBuZXcgRXJyb3IoIlRoZSBudW1iZXIgb2YgaTE4biBtZXNzYWdlIGNoaWxkcmVuIGNoYW5nZWQgYmV0d2VlbiBmaXJzdCBhbmQgc2Vjb25kIHBhc3MuIik7aWYobi5zb21lKChlLGkpPT50W2ldLmNvbnN0cnVjdG9yIT09ZS5jb25zdHJ1Y3RvcikpdGhyb3cgbmV3IEVycm9yKCJUaGUgdHlwZXMgb2YgdGhlIGkxOG4gbWVzc2FnZSBjaGlsZHJlbiBjaGFuZ2VkIGJldHdlZW4gZmlyc3QgYW5kIHNlY29uZCBwYXNzLiIpfSh0LmNoaWxkcmVuLG4pO2ZvcihsZXQgZT0wO2U8bi5sZW5ndGg7ZSsrKW5bZV0uc291cmNlU3Bhbj10LmNoaWxkcmVuW2VdLnNvdXJjZVNwYW59fShvLHIpLG5ldyBtbShvLGUpKTpvWzBdfX0oWUVlLG4pO3JldHVybihlLGkscixvLHMpPT50LnRvSTE4bk1lc3NhZ2UoZSxpLHIsbyxzKX0odGhpcy5pbnRlcnBvbGF0aW9uQ29uZmlnKX1fZ2VuZXJhdGVJMThuTWVzc2FnZSh0LGU9IiIsaSl7bGV0e21lYW5pbmc6cixkZXNjcmlwdGlvbjpvLGN1c3RvbUlkOnN9PXRoaXMuX3BhcnNlTWV0YWRhdGEoZSksYT10aGlzLl9jcmVhdGVJMThuTWVzc2FnZSh0LHIsbyxzLGkpO3JldHVybiB0aGlzLl9zZXRNZXNzYWdlSWQoYSxlKSx0aGlzLl9zZXRMZWdhY3lJZHMoYSxlKSxhfXZpc2l0QWxsV2l0aEVycm9ycyh0KXtsZXQgZT10Lm1hcChpPT5pLnZpc2l0KHRoaXMsbnVsbCkpO3JldHVybiBuZXcgTUQoZSx0aGlzLl9lcnJvcnMpfXZpc2l0RWxlbWVudCh0KXtsZXQgZTtpZihmdW5jdGlvbihuKXtyZXR1cm4gbi5hdHRycy5zb21lKHQ9PmZ1bmN0aW9uKG4pe3JldHVybiJpMThuIj09PW58fG4uc3RhcnRzV2l0aChqQil9KHQubmFtZSkpfSh0KSl7dGhpcy5oYXNJMThuTWV0YT0hMDtsZXQgaT1bXSxyPXt9O2ZvcihsZXQgbyBvZiB0LmF0dHJzKWlmKCJpMThuIj09PW8ubmFtZSllPXRoaXMuX2dlbmVyYXRlSTE4bk1lc3NhZ2UodC5jaGlsZHJlbix0LmkxOG58fG8udmFsdWUsdDFlKSwwPT09ZS5ub2Rlcy5sZW5ndGgmJihlPXZvaWQgMCksdC5pMThuPWU7ZWxzZSBpZihvLm5hbWUuc3RhcnRzV2l0aChqQikpe2xldCBzPW8ubmFtZS5zbGljZShqQi5sZW5ndGgpO3hLKHQubmFtZSxzKT90aGlzLl9yZXBvcnRFcnJvcihvLGBUcmFuc2xhdGluZyBhdHRyaWJ1dGUgJyR7c30nIGlzIGRpc2FsbG93ZWQgZm9yIHNlY3VyaXR5IHJlYXNvbnMuYCk6cltzXT1vLnZhbHVlfWVsc2UgaS5wdXNoKG8pO2lmKE9iamVjdC5rZXlzKHIpLmxlbmd0aClmb3IobGV0IG8gb2YgaSl7bGV0IHM9cltvLm5hbWVdO3ZvaWQgMCE9PXMmJm8udmFsdWUmJihvLmkxOG49dGhpcy5fZ2VuZXJhdGVJMThuTWVzc2FnZShbb10sby5pMThufHxzKSl9dGhpcy5rZWVwSTE4bkF0dHJzfHwodC5hdHRycz1pKX1yZXR1cm4gVXUodGhpcyx0LmNoaWxkcmVuLGUpLHR9dmlzaXRFeHBhbnNpb24odCxlKXtsZXQgaSxyPXQuaTE4bjtpZih0aGlzLmhhc0kxOG5NZXRhPSEwLHIgaW5zdGFuY2VvZiBUXyl7bGV0IG89ci5uYW1lO2k9dGhpcy5fZ2VuZXJhdGVJMThuTWVzc2FnZShbdF0sciksV1EoaSkubmFtZT1vLG51bGwhPT1lJiYoZS5wbGFjZWhvbGRlclRvTWVzc2FnZVtvXT1pKX1lbHNlIGk9dGhpcy5fZ2VuZXJhdGVJMThuTWVzc2FnZShbdF0sZXx8cik7cmV0dXJuIHQuaTE4bj1pLHR9dmlzaXRUZXh0KHQpe3JldHVybiB0fXZpc2l0QXR0cmlidXRlKHQpe3JldHVybiB0fXZpc2l0Q29tbWVudCh0KXtyZXR1cm4gdH12aXNpdEV4cGFuc2lvbkNhc2UodCl7cmV0dXJuIHR9X3BhcnNlTWV0YWRhdGEodCl7cmV0dXJuInN0cmluZyI9PXR5cGVvZiB0P2Z1bmN0aW9uKG49IiIpe2xldCB0LGUsaTtpZihuPW4udHJpbSgpKXtsZXQgcyxyPW4uaW5kZXhPZigiQEAiKSxvPW4uaW5kZXhPZigifCIpO1tzLHRdPXI+LTE/W24uc2xpY2UoMCxyKSxuLnNsaWNlKHIrMildOltuLCIiXSxbZSxpXT1vPi0xP1tzLnNsaWNlKDAsbykscy5zbGljZShvKzEpXTpbIiIsc119cmV0dXJue2N1c3RvbUlkOnQsbWVhbmluZzplLGRlc2NyaXB0aW9uOml9fSh0KTp0IGluc3RhbmNlb2YgRnU/dDp7fX1fc2V0TWVzc2FnZUlkKHQsZSl7dmFyIG47dC5pZHx8KHQuaWQ9ZSBpbnN0YW5jZW9mIEZ1JiZlLmlkfHwobj10KS5pZHx8RlEobikpfV9zZXRMZWdhY3lJZHModCxlKXtpZih0aGlzLmVuYWJsZUkxOG5MZWdhY3lNZXNzYWdlSWRGb3JtYXQpdC5sZWdhY3lJZHM9W1NNZSh0KSxGUSh0KV07ZWxzZSBpZigic3RyaW5nIiE9dHlwZW9mIGUpe2xldCBpPWUgaW5zdGFuY2VvZiBGdT9lOmUgaW5zdGFuY2VvZiBUXz9lLnByZXZpb3VzTWVzc2FnZTp2b2lkIDA7dC5sZWdhY3lJZHM9aT9pLmxlZ2FjeUlkczpbXX19X3JlcG9ydEVycm9yKHQsZSl7dGhpcy5fZXJyb3JzLnB1c2gobmV3IFRWKHQuc291cmNlU3BhbixlKSl9fTtmdW5jdGlvbiBhMWUobix0LGUsaSl7bGV0IHI9ZnVuY3Rpb24obil7cmV0dXJuIG4ubm9kZXMubWFwKHQ9PnQudmlzaXQobDFlLG51bGwpKS5qb2luKCIiKX0odCksbz1baHQocildO09iamVjdC5rZXlzKGkpLmxlbmd0aCYmKG8ucHVzaCh3RChVVihpLCEwKSwhMCkpLG8ucHVzaCh3RCh7b3JpZ2luYWxfY29kZTpxbChPYmplY3Qua2V5cyhpKS5tYXAobD0+KHtrZXk6SkMobCkscXVvdGVkOiEwLHZhbHVlOmh0KHQucGxhY2Vob2xkZXJzW2xdP3QucGxhY2Vob2xkZXJzW2xdLnNvdXJjZVNwYW4udG9TdHJpbmcoKTp0LnBsYWNlaG9sZGVyVG9NZXNzYWdlW2xdLm5vZGVzLm1hcChjPT5jLnNvdXJjZVNwYW4udG9TdHJpbmcoKSkuam9pbigiIikpfSkpKX0pKSk7bGV0IHM9ZS5zZXQoUmkoImdvb2cuZ2V0TXNnIikuY2FsbEZuKG8pKS50b0NvbnN0RGVjbCgpO3JldHVybiBzLmFkZExlYWRpbmdDb21tZW50KGZ1bmN0aW9uKG4pe2xldCB0PVtdO3JldHVybiB0LnB1c2gobi5kZXNjcmlwdGlvbj97dGFnTmFtZToiZGVzYyIsdGV4dDpuLmRlc2NyaXB0aW9ufTp7dGFnTmFtZToic3VwcHJlc3MiLHRleHQ6Inttc2dEZXNjcmlwdGlvbnN9In0pLG4ubWVhbmluZyYmdC5wdXNoKHt0YWdOYW1lOiJtZWFuaW5nIix0ZXh0Om4ubWVhbmluZ30pLGZ1bmN0aW9uKG49W10pe3JldHVybiBuZXcgbkQobil9KHQpfSh0KSksW3MsbmV3IEh1KG4uc2V0KGUpKV19dmFyIGwxZT1uZXcgY2xhc3N7Zm9ybWF0UGgodCl7cmV0dXJuYHskJHtKQyh0KX19YH12aXNpdFRleHQodCl7cmV0dXJuIHQudmFsdWV9dmlzaXRDb250YWluZXIodCl7cmV0dXJuIHQuY2hpbGRyZW4ubWFwKGU9PmUudmlzaXQodGhpcykpLmpvaW4oIiIpfXZpc2l0SWN1KHQpe3JldHVybiBNSyh0KX12aXNpdFRhZ1BsYWNlaG9sZGVyKHQpe3JldHVybiB0LmlzVm9pZD90aGlzLmZvcm1hdFBoKHQuc3RhcnROYW1lKTpgJHt0aGlzLmZvcm1hdFBoKHQuc3RhcnROYW1lKX0ke3QuY2hpbGRyZW4ubWFwKGU9PmUudmlzaXQodGhpcykpLmpvaW4oIiIpfSR7dGhpcy5mb3JtYXRQaCh0LmNsb3NlTmFtZSl9YH12aXNpdFBsYWNlaG9sZGVyKHQpe3JldHVybiB0aGlzLmZvcm1hdFBoKHQubmFtZSl9dmlzaXRJY3VQbGFjZWhvbGRlcih0LGUpe3JldHVybiB0aGlzLmZvcm1hdFBoKHQubmFtZSl9fTtmdW5jdGlvbiB1MWUobix0LGUpe2xldHttZXNzYWdlUGFydHM6aSxwbGFjZUhvbGRlcnM6cn09ZnVuY3Rpb24obil7bGV0IHQ9W10sZT1uZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLnBsYWNlaG9sZGVyVG9NZXNzYWdlPXQsdGhpcy5waWVjZXM9ZX12aXNpdFRleHQodCl7aWYodGhpcy5waWVjZXNbdGhpcy5waWVjZXMubGVuZ3RoLTFdaW5zdGFuY2VvZiBsbSl0aGlzLnBpZWNlc1t0aGlzLnBpZWNlcy5sZW5ndGgtMV0udGV4dCs9dC52YWx1ZTtlbHNle2xldCBlPW5ldyBHbyh0LnNvdXJjZVNwYW4uZnVsbFN0YXJ0LHQuc291cmNlU3Bhbi5lbmQsdC5zb3VyY2VTcGFuLmZ1bGxTdGFydCx0LnNvdXJjZVNwYW4uZGV0YWlscyk7dGhpcy5waWVjZXMucHVzaChuZXcgbG0odC52YWx1ZSxlKSl9fXZpc2l0Q29udGFpbmVyKHQpe3QuY2hpbGRyZW4uZm9yRWFjaChlPT5lLnZpc2l0KHRoaXMpKX12aXNpdEljdSh0KXt0aGlzLnBpZWNlcy5wdXNoKG5ldyBsbShNSyh0KSx0LnNvdXJjZVNwYW4pKX12aXNpdFRhZ1BsYWNlaG9sZGVyKHQpe3RoaXMucGllY2VzLnB1c2godGhpcy5jcmVhdGVQbGFjZWhvbGRlclBpZWNlKHQuc3RhcnROYW1lLHQuc3RhcnRTb3VyY2VTcGFuPz90LnNvdXJjZVNwYW4pKSx0LmlzVm9pZHx8KHQuY2hpbGRyZW4uZm9yRWFjaChlPT5lLnZpc2l0KHRoaXMpKSx0aGlzLnBpZWNlcy5wdXNoKHRoaXMuY3JlYXRlUGxhY2Vob2xkZXJQaWVjZSh0LmNsb3NlTmFtZSx0LmVuZFNvdXJjZVNwYW4/P3Quc291cmNlU3BhbikpKX12aXNpdFBsYWNlaG9sZGVyKHQpe3RoaXMucGllY2VzLnB1c2godGhpcy5jcmVhdGVQbGFjZWhvbGRlclBpZWNlKHQubmFtZSx0LnNvdXJjZVNwYW4pKX12aXNpdEljdVBsYWNlaG9sZGVyKHQpe3RoaXMucGllY2VzLnB1c2godGhpcy5jcmVhdGVQbGFjZWhvbGRlclBpZWNlKHQubmFtZSx0LnNvdXJjZVNwYW4sdGhpcy5wbGFjZWhvbGRlclRvTWVzc2FnZVt0Lm5hbWVdKSl9Y3JlYXRlUGxhY2Vob2xkZXJQaWVjZSh0LGUsaSl7cmV0dXJuIG5ldyBoXyhKQyh0LCExKSxlLGkpfX0obi5wbGFjZWhvbGRlclRvTWVzc2FnZSx0KTtyZXR1cm4gbi5ub2Rlcy5mb3JFYWNoKGk9PmkudmlzaXQoZSkpLGZ1bmN0aW9uKG4pe2xldCB0PVtdLGU9W107blswXWluc3RhbmNlb2YgaF8mJnQucHVzaCh2QihuWzBdLnNvdXJjZVNwYW4uc3RhcnQpKTtmb3IobGV0IGk9MDtpPG4ubGVuZ3RoO2krKyl7bGV0IHI9bltpXTtyIGluc3RhbmNlb2YgbG0/dC5wdXNoKHIpOihlLnB1c2gociksbltpLTFdaW5zdGFuY2VvZiBoXyYmdC5wdXNoKHZCKG5baS0xXS5zb3VyY2VTcGFuLmVuZCkpKX1yZXR1cm4gbltuLmxlbmd0aC0xXWluc3RhbmNlb2YgaF8mJnQucHVzaCh2QihuW24ubGVuZ3RoLTFdLnNvdXJjZVNwYW4uZW5kKSkse21lc3NhZ2VQYXJ0czp0LHBsYWNlSG9sZGVyczplfX0odCl9KHQpLG89ZnVuY3Rpb24obil7bGV0IHQ9bi5ub2Rlc1swXTtyZXR1cm4gbmV3IEdvKHQuc291cmNlU3Bhbi5mdWxsU3RhcnQsbi5ub2Rlc1tuLm5vZGVzLmxlbmd0aC0xXS5zb3VyY2VTcGFuLmVuZCx0LnNvdXJjZVNwYW4uZnVsbFN0YXJ0LHQuc291cmNlU3Bhbi5kZXRhaWxzKX0odCkscz1yLm1hcChjPT5lW2MudGV4dF0pLGE9ZnVuY3Rpb24obix0LGUsaSxyKXtyZXR1cm4gbmV3IEFCKG4sdCxlLGkscil9KHQsaSxyLHMsbyksbD1uLnNldChhKTtyZXR1cm5bbmV3IEh1KGwpXX1mdW5jdGlvbiB2QihuKXtyZXR1cm4gbmV3IGxtKCIiLG5ldyBHbyhuLG4pKX12YXIgd0s9bmV3IFNldChbIiRldmVudCJdKSx5Qj1uZXcgTWFwKFtbIndpbmRvdyIsdGUucmVzb2x2ZVdpbmRvd10sWyJkb2N1bWVudCIsdGUucmVzb2x2ZURvY3VtZW50XSxbImJvZHkiLHRlLnJlc29sdmVCb2R5XV0pLGcxZT1bIiAiLCJcbiIsIlxyIiwiXHQiXTtmdW5jdGlvbiBsaChuLHQpe3JldHVybiBWVihSaSgkQykuYml0d2lzZUFuZChodChuKSxudWxsLCExKSx0KX1mdW5jdGlvbiBTSyhuLHQ9bnVsbCxlPW51bGwpe2xldHt0eXBlOmksbmFtZTpyLHRhcmdldDpvLHBoYXNlOnMsaGFuZGxlcjphfT1uO2lmKG8mJiF5Qi5oYXMobykpdGhyb3cgbmV3IEVycm9yKGBVbmV4cGVjdGVkIGdsb2JhbCB0YXJnZXQgJyR7b30nIGRlZmluZWQgZm9yICcke3J9JyBldmVudC5cbiAgICAgICAgU3VwcG9ydGVkIGxpc3Qgb2YgZ2xvYmFsIHRhcmdldHM6ICR7QXJyYXkuZnJvbSh5Qi5rZXlzKCkpfS5gKTtsZXQgbD0iJGV2ZW50IixjPW5ldyBTZXQsdT1udWxsPT09ZXx8MD09PWUuYmluZGluZ0xldmVsP1JpKEhjKTplLmdldE9yQ3JlYXRlU2hhcmVkQ29udGV4dFZhcigwKSxkPWNTZShlLHUsYSwiYiIsbi5oYW5kbGVyU3BhbixjLHdLKSxwPVtdLGg9ZT8udmFyaWFibGVEZWNsYXJhdGlvbnMoKSxmPWU/LnJlc3RvcmVWaWV3U3RhdGVtZW50KCk7aWYoaCYmcC5wdXNoKC4uLmgpLHAucHVzaCguLi5kKSxmKXtwLnVuc2hpZnQoZik7bGV0IFQ9cFtwLmxlbmd0aC0xXTtUIGluc3RhbmNlb2YgRG8/cFtwLmxlbmd0aC0xXT1uZXcgRG8oZ20oVC52YWx1ZS5zb3VyY2VTcGFuLHRlLnJlc2V0VmlldyxbVC52YWx1ZV0pKTpwLnB1c2gobmV3IEh1KGdtKG51bGwsdGUucmVzZXRWaWV3LFtdKSkpfWxldCBtPTE9PT1pP2Z1bmN0aW9uKG4sdCl7cmV0dXJuYEAke259LiR7dH1gfShyLHMpOnIseD10JiZtXyh0KSxnPVtdO2MuaGFzKGwpJiZnLnB1c2gobmV3IGlhKGwsVl8pKTtsZXQgYj1yYShnLHAsUGEsbnVsbCx4KSxEPVtodChtKSxiXTtyZXR1cm4gbyYmRC5wdXNoKGh0KCExKSxUbih5Qi5nZXQobykpKSxEfXZhciBLQz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaT0wLHIsbyxzLGEsbCxjLHUsZD1mdW5jdGlvbigpe3JldHVybntwcmVwYXJlU3RhdGVtZW50czpbXSxjb25zdEV4cHJlc3Npb25zOltdLGkxOG5WYXJSZWZzQ2FjaGU6bmV3IE1hcH19KCkpe3RoaXMuY29uc3RhbnRQb29sPXQsdGhpcy5sZXZlbD1pLHRoaXMuY29udGV4dE5hbWU9cix0aGlzLmkxOG5Db250ZXh0PW8sdGhpcy50ZW1wbGF0ZUluZGV4PXMsdGhpcy50ZW1wbGF0ZU5hbWU9YSx0aGlzLl9uYW1lc3BhY2U9bCx0aGlzLmkxOG5Vc2VFeHRlcm5hbElkcz11LHRoaXMuX2NvbnN0YW50cz1kLHRoaXMuX2RhdGFJbmRleD0wLHRoaXMuX2JpbmRpbmdDb250ZXh0PTAsdGhpcy5fcHJlZml4Q29kZT1bXSx0aGlzLl9jcmVhdGlvbkNvZGVGbnM9W10sdGhpcy5fdXBkYXRlQ29kZUZucz1bXSx0aGlzLl9jdXJyZW50SW5kZXg9MCx0aGlzLl90ZW1wVmFyaWFibGVzPVtdLHRoaXMuX25lc3RlZFRlbXBsYXRlRm5zPVtdLHRoaXMuaTE4bj1udWxsLHRoaXMuX3B1cmVGdW5jdGlvblNsb3RzPTAsdGhpcy5fYmluZGluZ1Nsb3RzPTAsdGhpcy5fbmdDb250ZW50UmVzZXJ2ZWRTbG90cz1bXSx0aGlzLl9uZ0NvbnRlbnRTZWxlY3RvcnNPZmZzZXQ9MCx0aGlzLl9pbXBsaWNpdFJlY2VpdmVyRXhwcj1udWxsLHRoaXMudmlzaXRSZWZlcmVuY2U9aUMsdGhpcy52aXNpdFZhcmlhYmxlPWlDLHRoaXMudmlzaXRUZXh0QXR0cmlidXRlPWlDLHRoaXMudmlzaXRCb3VuZEF0dHJpYnV0ZT1pQyx0aGlzLnZpc2l0Qm91bmRFdmVudD1pQyx0aGlzLl9iaW5kaW5nU2NvcGU9ZS5uZXN0ZWRTY29wZShpKSx0aGlzLmZpbGVCYXNlZEkxOG5TdWZmaXg9Yy5yZXBsYWNlKC9bXkEtWmEtejAtOV0vZywiXyIpKyJfIix0aGlzLl92YWx1ZUNvbnZlcnRlcj1uZXcgVEQodCwoKT0+dGhpcy5hbGxvY2F0ZURhdGFTbG90KCkscD0+dGhpcy5hbGxvY2F0ZVB1cmVGdW5jdGlvblNsb3RzKHApLChwLGgsZixtKT0+e3RoaXMuX2JpbmRpbmdTY29wZS5zZXQodGhpcy5sZXZlbCxoLG0pLHRoaXMuY3JlYXRpb25JbnN0cnVjdGlvbihudWxsLHRlLnBpcGUsW2h0KGYpLGh0KHApXSl9KX1idWlsZFRlbXBsYXRlRnVuY3Rpb24odCxlLGk9MCxyKXt0aGlzLl9uZ0NvbnRlbnRTZWxlY3RvcnNPZmZzZXQ9aSx0aGlzLl9uYW1lc3BhY2UhPT10ZS5uYW1lc3BhY2VIVE1MJiZ0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24obnVsbCx0aGlzLl9uYW1lc3BhY2UpLGUuZm9yRWFjaChoPT50aGlzLnJlZ2lzdGVyQ29udGV4dFZhcmlhYmxlcyhoKSk7bGV0IG89dGhpcy5pMThuQ29udGV4dHx8QUMocikmJiFWVChyKSYmISgxPT09KG49dCkubGVuZ3RoJiZuWzBdaW5zdGFuY2VvZiBFXyYmdFswXS5pMThuPT09cikscz1iQih0KTt2YXIgbjtpZihvJiZ0aGlzLmkxOG5TdGFydChudWxsLHIscyksQVgodGhpcyx0KSx0aGlzLl9wdXJlRnVuY3Rpb25TbG90cys9dGhpcy5fYmluZGluZ1Nsb3RzLHRoaXMuX3ZhbHVlQ29udmVydGVyLnVwZGF0ZVBpcGVTbG90T2Zmc2V0cyh0aGlzLl9iaW5kaW5nU2xvdHMpLHRoaXMuX25lc3RlZFRlbXBsYXRlRm5zLmZvckVhY2goaD0+aCgpKSwwPT09dGhpcy5sZXZlbCYmdGhpcy5fbmdDb250ZW50UmVzZXJ2ZWRTbG90cy5sZW5ndGgpe2xldCBoPVtdO2lmKHRoaXMuX25nQ29udGVudFJlc2VydmVkU2xvdHMubGVuZ3RoPjF8fCIqIiE9PXRoaXMuX25nQ29udGVudFJlc2VydmVkU2xvdHNbMF0pe2xldCBmPXRoaXMuX25nQ29udGVudFJlc2VydmVkU2xvdHMubWFwKG09PiIqIiE9PW0/TlYobSk6bSk7aC5wdXNoKHRoaXMuY29uc3RhbnRQb29sLmdldENvbnN0TGl0ZXJhbChOdShmKSwhMCkpfXRoaXMuY3JlYXRpb25JbnN0cnVjdGlvbihudWxsLHRlLnByb2plY3Rpb25EZWYsaCwhMCl9byYmdGhpcy5pMThuRW5kKG51bGwscyk7bGV0IGE9Y0QodGhpcy5fY3JlYXRpb25Db2RlRm5zKSxsPWNEKHRoaXMuX3VwZGF0ZUNvZGVGbnMpLGM9dGhpcy5fYmluZGluZ1Njb3BlLnZpZXdTbmFwc2hvdFN0YXRlbWVudHMoKSx1PXRoaXMuX2JpbmRpbmdTY29wZS52YXJpYWJsZURlY2xhcmF0aW9ucygpLmNvbmNhdCh0aGlzLl90ZW1wVmFyaWFibGVzKSxkPWEubGVuZ3RoPjA/W2xoKDEsYy5jb25jYXQoYSkpXTpbXSxwPWwubGVuZ3RoPjA/W2xoKDIsdS5jb25jYXQobCkpXTpbXTtyZXR1cm4gcmEoW25ldyBpYSgkQyxaQyksbmV3IGlhKEhjLG51bGwpXSxbLi4udGhpcy5fcHJlZml4Q29kZSwuLi5kLC4uLnBdLFBhLG51bGwsdGhpcy50ZW1wbGF0ZU5hbWUpfWdldExvY2FsKHQpe3JldHVybiB0aGlzLl9iaW5kaW5nU2NvcGUuZ2V0KHQpfW5vdGlmeUltcGxpY2l0UmVjZWl2ZXJVc2UoKXt0aGlzLl9iaW5kaW5nU2NvcGUubm90aWZ5SW1wbGljaXRSZWNlaXZlclVzZSgpfW1heWJlUmVzdG9yZVZpZXcoKXt0aGlzLl9iaW5kaW5nU2NvcGUubWF5YmVSZXN0b3JlVmlldygpfWkxOG5UcmFuc2xhdGUodCxlPXt9LGkscil7bGV0IG89aXx8dGhpcy5pMThuR2VuZXJhdGVNYWluQmxvY2tWYXIoKSxhPWZ1bmN0aW9uKG4sdCxlLGk9e30scil7bGV0IG89W0V3ZSh0KSxWVihCVihSaShNUSkpLm5vdElkZW50aWNhbChodCgidW5kZWZpbmVkIixMUSkpLmFuZChSaShNUSkpLGExZSh0LG4sZSxpKSx1MWUodCxuLFVWKGksITEpKSldO3JldHVybiByJiZvLnB1c2gobmV3IEh1KHQuc2V0KHIodCkpKSksb30odCxvLHRoaXMuaTE4bkdlbmVyYXRlQ2xvc3VyZVZhcih0LmlkKSxlLHIpO3JldHVybiB0aGlzLl9jb25zdGFudHMucHJlcGFyZVN0YXRlbWVudHMucHVzaCguLi5hKSxvfXJlZ2lzdGVyQ29udGV4dFZhcmlhYmxlcyh0KXtsZXQgZT10aGlzLl9iaW5kaW5nU2NvcGUuZnJlc2hSZWZlcmVuY2VOYW1lKCksaT10aGlzLmxldmVsLHI9UmkodC5uYW1lK2UpO3RoaXMuX2JpbmRpbmdTY29wZS5zZXQoaSx0Lm5hbWUsciwxLChvLHMpPT57bGV0IGE7cmV0dXJuIG8uYmluZGluZ0xldmVsPT09aT9vLmlzTGlzdGVuZXJTY29wZSgpJiZvLmhhc1Jlc3RvcmVWaWV3VmFyaWFibGUoKT8oYT1SaShZUSksby5ub3RpZnlSZXN0b3JlZFZpZXdDb250ZXh0VXNlKCkpOmE9UmkoSGMpOmE9by5nZXRTaGFyZWRDb250ZXh0TmFtZShpKXx8SVYocyksW3Iuc2V0KGEucHJvcCh0LnZhbHVlfHwiJGltcGxpY2l0IikpLnRvQ29uc3REZWNsKCldfSl9aTE4bkFwcGVuZEJpbmRpbmdzKHQpe3QubGVuZ3RoPjAmJnQuZm9yRWFjaChlPT50aGlzLmkxOG4uYXBwZW5kQmluZGluZyhlKSl9aTE4bkJpbmRQcm9wcyh0KXtsZXQgZT17fTtyZXR1cm4gT2JqZWN0LmtleXModCkuZm9yRWFjaChpPT57bGV0IHI9dFtpXTtpZihyIGluc3RhbmNlb2YgTV8pZVtpXT1odChyLnZhbHVlKTtlbHNle2xldCBvPXIudmFsdWUudmlzaXQodGhpcy5fdmFsdWVDb252ZXJ0ZXIpO2lmKHRoaXMuYWxsb2NhdGVCaW5kaW5nU2xvdHMobyksbyBpbnN0YW5jZW9mIHZzKXtsZXR7c3RyaW5nczpzLGV4cHJlc3Npb25zOmF9PW8se2lkOmwsYmluZGluZ3M6Y309dGhpcy5pMThuLHU9ZnVuY3Rpb24obix0PTAsZT0wKXtpZighbi5sZW5ndGgpcmV0dXJuIiI7bGV0IGk9IiIscj1uLmxlbmd0aC0xO2ZvcihsZXQgbz0wO288cjtvKyspaSs9YCR7bltvXX0ke1BEKHQrbyxlKX1gO3JldHVybiBpKz1uW3JdLGl9KHMsYy5zaXplLGwpO3RoaXMuaTE4bkFwcGVuZEJpbmRpbmdzKGEpLGVbaV09aHQodSl9fX0pLGV9aTE4bkdlbmVyYXRlTWFpbkJsb2NrVmFyKCl7cmV0dXJuIFJpKHRoaXMuY29uc3RhbnRQb29sLnVuaXF1ZU5hbWUoImkxOG5fIikpfWkxOG5HZW5lcmF0ZUNsb3N1cmVWYXIodCl7bGV0IGUsaT10aGlzLmZpbGVCYXNlZEkxOG5TdWZmaXgudG9VcHBlckNhc2UoKTtpZih0aGlzLmkxOG5Vc2VFeHRlcm5hbElkcyl7bGV0IHI9UlgoIkVYVEVSTkFMXyIpLG89dGhpcy5jb25zdGFudFBvb2wudW5pcXVlTmFtZShpKTtlPWAke3J9JHttXyh0KX0kJCR7b31gfWVsc2V7bGV0IHI9UlgoaSk7ZT10aGlzLmNvbnN0YW50UG9vbC51bmlxdWVOYW1lKHIpfXJldHVybiBSaShlKX1pMThuVXBkYXRlUmVmKHQpe2xldHtpY3VzOmUsbWV0YTppLGlzUm9vdDpyLGlzUmVzb2x2ZWQ6byxpc0VtaXR0ZWQ6c309dDtpZihyJiZvJiYhcyYmIVZUKGkpKXt0LmlzRW1pdHRlZD0hMDtsZXQgZCxhPXQuZ2V0U2VyaWFsaXplZFBsYWNlaG9sZGVycygpLGw9e30sYz1hLnNpemU/UFgoYSk6e307ZS5zaXplJiZlLmZvckVhY2goKHAsaCk9PntpZigxPT09cC5sZW5ndGgpY1toXT1wWzBdO2Vsc2V7bGV0IGY9UEQoYEkxOE5fRVhQXyR7aH1gKTtjW2hdPWh0KGYpLGxbaF09X3IocCl9fSksKEFycmF5LmZyb20oYS52YWx1ZXMoKSkuc29tZShwPT5wLmxlbmd0aD4xKXx8T2JqZWN0LmtleXMobCkubGVuZ3RoKSYmKGQ9cD0+e2xldCBoPVtwXTtyZXR1cm4gT2JqZWN0LmtleXMobCkubGVuZ3RoJiZoLnB1c2god0QobCwhMCkpLGdtKG51bGwsdGUuaTE4blBvc3Rwcm9jZXNzLGgpfSksdGhpcy5pMThuVHJhbnNsYXRlKGksYyx0LnJlZixkKX19aTE4blN0YXJ0KHQ9bnVsbCxlLGkpe2xldCByPXRoaXMuYWxsb2NhdGVEYXRhU2xvdCgpO3RoaXMuaTE4bj10aGlzLmkxOG5Db250ZXh0P3RoaXMuaTE4bkNvbnRleHQuZm9ya0NoaWxkQ29udGV4dChyLHRoaXMudGVtcGxhdGVJbmRleCxlKTpuZXcgUUMocix0aGlzLmkxOG5HZW5lcmF0ZU1haW5CbG9ja1ZhcigpLDAsdGhpcy50ZW1wbGF0ZUluZGV4LGUpO2xldHtpZDpvLHJlZjpzfT10aGlzLmkxOG4sYT1baHQociksdGhpcy5hZGRUb0NvbnN0cyhzKV07bz4wJiZhLnB1c2goaHQobykpLHRoaXMuY3JlYXRpb25JbnN0cnVjdGlvbih0LGk/dGUuaTE4bjp0ZS5pMThuU3RhcnQsYSl9aTE4bkVuZCh0PW51bGwsZSl7aWYoIXRoaXMuaTE4bil0aHJvdyBuZXcgRXJyb3IoImkxOG5FbmQgaXMgZXhlY3V0ZWQgd2l0aCBubyBpMThuIGNvbnRleHQgcHJlc2VudCIpO3RoaXMuaTE4bkNvbnRleHQ/KHRoaXMuaTE4bkNvbnRleHQucmVjb25jaWxlQ2hpbGRDb250ZXh0KHRoaXMuaTE4biksdGhpcy5pMThuVXBkYXRlUmVmKHRoaXMuaTE4bkNvbnRleHQpKTp0aGlzLmkxOG5VcGRhdGVSZWYodGhpcy5pMThuKTtsZXR7aW5kZXg6aSxiaW5kaW5nczpyfT10aGlzLmkxOG47aWYoci5zaXplKXtmb3IobGV0IG8gb2Ygcil0aGlzLnVwZGF0ZUluc3RydWN0aW9uV2l0aEFkdmFuY2UodGhpcy5nZXRDb25zdENvdW50KCktMSx0LHRlLmkxOG5FeHAsKCk9PnRoaXMuY29udmVydFByb3BlcnR5QmluZGluZyhvKSk7dGhpcy51cGRhdGVJbnN0cnVjdGlvbih0LHRlLmkxOG5BcHBseSxbaHQoaSldKX1lfHx0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24odCx0ZS5pMThuRW5kKSx0aGlzLmkxOG49bnVsbH1pMThuQXR0cmlidXRlc0luc3RydWN0aW9uKHQsZSxpKXtsZXQgcj0hMSxvPVtdO2lmKGUuZm9yRWFjaChzPT57bGV0IGE9cy5pMThuLGw9cy52YWx1ZS52aXNpdCh0aGlzLl92YWx1ZUNvbnZlcnRlcik7aWYodGhpcy5hbGxvY2F0ZUJpbmRpbmdTbG90cyhsKSxsIGluc3RhbmNlb2YgdnMpe2xldCB1PVBYKHFRKGEpKTtvLnB1c2goaHQocy5uYW1lKSx0aGlzLmkxOG5UcmFuc2xhdGUoYSx1KSksbC5leHByZXNzaW9ucy5mb3JFYWNoKGQ9PntyPSEwLHRoaXMudXBkYXRlSW5zdHJ1Y3Rpb25XaXRoQWR2YW5jZSh0LGksdGUuaTE4bkV4cCwoKT0+dGhpcy5jb252ZXJ0UHJvcGVydHlCaW5kaW5nKGQpKX0pfX0pLG8ubGVuZ3RoPjApe2xldCBzPWh0KHRoaXMuYWxsb2NhdGVEYXRhU2xvdCgpKSxhPXRoaXMuYWRkVG9Db25zdHMoX3IobykpO3RoaXMuY3JlYXRpb25JbnN0cnVjdGlvbihpLHRlLmkxOG5BdHRyaWJ1dGVzLFtzLGFdKSxyJiZ0aGlzLnVwZGF0ZUluc3RydWN0aW9uKGksdGUuaTE4bkFwcGx5LFtzXSl9fWdldE5hbWVzcGFjZUluc3RydWN0aW9uKHQpe3N3aXRjaCh0KXtjYXNlIm1hdGgiOnJldHVybiB0ZS5uYW1lc3BhY2VNYXRoTUw7Y2FzZSJzdmciOnJldHVybiB0ZS5uYW1lc3BhY2VTVkc7ZGVmYXVsdDpyZXR1cm4gdGUubmFtZXNwYWNlSFRNTH19YWRkTmFtZXNwYWNlSW5zdHJ1Y3Rpb24odCxlKXt0aGlzLl9uYW1lc3BhY2U9dCx0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24oZS5zdGFydFNvdXJjZVNwYW4sdCl9aW50ZXJwb2xhdGVkVXBkYXRlSW5zdHJ1Y3Rpb24odCxlLGkscixvLHMpe3RoaXMudXBkYXRlSW5zdHJ1Y3Rpb25XaXRoQWR2YW5jZShlLHIuc291cmNlU3Bhbix0LCgpPT5baHQoaSksLi4udGhpcy5nZXRVcGRhdGVJbnN0cnVjdGlvbkFyZ3VtZW50cyhvKSwuLi5zXSl9dmlzaXRDb250ZW50KHQpe2xldCBlPXRoaXMuYWxsb2NhdGVEYXRhU2xvdCgpLGk9dGhpcy5fbmdDb250ZW50U2VsZWN0b3JzT2Zmc2V0K3RoaXMuX25nQ29udGVudFJlc2VydmVkU2xvdHMubGVuZ3RoLHI9W2h0KGUpXTt0aGlzLl9uZ0NvbnRlbnRSZXNlcnZlZFNsb3RzLnB1c2godC5zZWxlY3Rvcik7bGV0IG89dC5hdHRyaWJ1dGVzLmZpbHRlcihhPT4ic2VsZWN0IiE9PWEubmFtZS50b0xvd2VyQ2FzZSgpKSxzPXRoaXMuZ2V0QXR0cmlidXRlRXhwcmVzc2lvbnModC5uYW1lLG8sW10sW10pO3MubGVuZ3RoPjA/ci5wdXNoKGh0KGkpLF9yKHMpKTowIT09aSYmci5wdXNoKGh0KGkpKSx0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24odC5zb3VyY2VTcGFuLHRlLnByb2plY3Rpb24sciksdGhpcy5pMThuJiZ0aGlzLmkxOG4uYXBwZW5kUHJvamVjdGlvbih0LmkxOG4sZSl9dmlzaXRFbGVtZW50KHQpe2xldCBlPXRoaXMuYWxsb2NhdGVEYXRhU2xvdCgpLGk9bmV3IHZEKG51bGwpLHI9ITEsbz1BQyh0LmkxOG4pJiYhVlQodC5pMThuKSxzPVtdLFthLGxdPUtkKHQubmFtZSksYz1DQih0Lm5hbWUpO2ZvcihsZXQgdWUgb2YgdC5hdHRyaWJ1dGVzKXtsZXR7bmFtZTpoZSx2YWx1ZTp3fT11ZTsibmdOb25CaW5kYWJsZSI9PT1oZT9yPSEwOiJzdHlsZSI9PT1oZT9pLnJlZ2lzdGVyU3R5bGVBdHRyKHcpOiJjbGFzcyI9PT1oZT9pLnJlZ2lzdGVyQ2xhc3NBdHRyKHcpOnMucHVzaCh1ZSl9bGV0IHU9W2h0KGUpXTtjfHx1LnB1c2goaHQobCkpO2xldCBkPVtdLHA9W107dC5pbnB1dHMuZm9yRWFjaCh1ZT0+e2kucmVnaXN0ZXJCb3VuZElucHV0KHVlKXx8KDA9PT11ZS50eXBlJiZ1ZS5pMThuP3AucHVzaCh1ZSk6ZC5wdXNoKHVlKSl9KTtsZXQgaD10aGlzLmdldEF0dHJpYnV0ZUV4cHJlc3Npb25zKHQubmFtZSxzLGQsdC5vdXRwdXRzLGksW10scCk7dS5wdXNoKHRoaXMuYWRkQXR0cnNUb0NvbnN0cyhoKSk7bGV0IGY9dGhpcy5wcmVwYXJlUmVmc0FycmF5KHQucmVmZXJlbmNlcyk7dS5wdXNoKHRoaXMuYWRkVG9Db25zdHMoZikpO2xldCBtPXRoaXMuX25hbWVzcGFjZSx4PXRoaXMuZ2V0TmFtZXNwYWNlSW5zdHJ1Y3Rpb24oYSk7eCE9PW0mJnRoaXMuYWRkTmFtZXNwYWNlSW5zdHJ1Y3Rpb24oeCx0KSx0aGlzLmkxOG4mJnRoaXMuaTE4bi5hcHBlbmRFbGVtZW50KHQuaTE4bixlKTtsZXQgZz0hbyYmdGhpcy5pMThuPyFiQih0LmNoaWxkcmVuKTp0LmNoaWxkcmVuLmxlbmd0aD4wLGI9IWkuaGFzQmluZGluZ3NXaXRoUGlwZXMmJjA9PT10Lm91dHB1dHMubGVuZ3RoJiYwPT09cC5sZW5ndGgmJiFnLEQ9IWImJmJCKHQuY2hpbGRyZW4pO2lmKGIpdGhpcy5jcmVhdGlvbkluc3RydWN0aW9uKHQuc291cmNlU3BhbixjP3RlLmVsZW1lbnRDb250YWluZXI6dGUuZWxlbWVudCxzQih1KSk7ZWxzZXtpZih0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24odC5zdGFydFNvdXJjZVNwYW4sYz90ZS5lbGVtZW50Q29udGFpbmVyU3RhcnQ6dGUuZWxlbWVudFN0YXJ0LHNCKHUpKSxyJiZ0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24odC5zdGFydFNvdXJjZVNwYW4sdGUuZGlzYWJsZUJpbmRpbmdzKSxwLmxlbmd0aD4wJiZ0aGlzLmkxOG5BdHRyaWJ1dGVzSW5zdHJ1Y3Rpb24oZSxwLHQuc3RhcnRTb3VyY2VTcGFuPz90LnNvdXJjZVNwYW4pLHQub3V0cHV0cy5sZW5ndGg+MClmb3IobGV0IHVlIG9mIHQub3V0cHV0cyl0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24odWUuc291cmNlU3Bhbix0ZS5saXN0ZW5lcix0aGlzLnByZXBhcmVMaXN0ZW5lclBhcmFtZXRlcih0Lm5hbWUsdWUsZSkpO28mJnRoaXMuaTE4blN0YXJ0KHQuc3RhcnRTb3VyY2VTcGFuLHQuaTE4bixEKX1sZXQgVD1pLmJ1aWxkVXBkYXRlTGV2ZWxJbnN0cnVjdGlvbnModGhpcy5fdmFsdWVDb252ZXJ0ZXIpLGs9VC5sZW5ndGgtMTtmb3IobGV0IHVlPTA7dWU8PWs7dWUrKyl0aGlzLl9iaW5kaW5nU2xvdHMrPXRoaXMucHJvY2Vzc1N0eWxpbmdVcGRhdGVJbnN0cnVjdGlvbihlLFRbdWVdKTtsZXQgWj1odCh2b2lkIDApLHo9W10sZmU9W107ZC5mb3JFYWNoKHVlPT57bGV0IGhlPXVlLnR5cGU7aWYoND09PWhlKXtsZXQgdz11ZS52YWx1ZS52aXNpdCh0aGlzLl92YWx1ZUNvbnZlcnRlciksRj0hKHcgaW5zdGFuY2VvZiB0YSYmIXcudmFsdWUpO3RoaXMuYWxsb2NhdGVCaW5kaW5nU2xvdHModyksei5wdXNoKHtzcGFuOnVlLnNvdXJjZVNwYW4scGFyYW1zT3JGbjp6VCgoKT0+Rj90aGlzLmNvbnZlcnRQcm9wZXJ0eUJpbmRpbmcodyk6Wix6USh1ZS5uYW1lKSl9KX1lbHNle2lmKHVlLmkxOG4pcmV0dXJuO2xldCB3PXVlLnZhbHVlLnZpc2l0KHRoaXMuX3ZhbHVlQ29udmVydGVyKTtpZih2b2lkIDAhPT13KXtsZXQgRj1bXSxbcSxLXT1LZCh1ZS5uYW1lKSxZPVRLKHVlLnNlY3VyaXR5Q29udGV4dCwxPT09aGUpO2lmKFkmJkYucHVzaChZKSxxKXtsZXQgYWU9aHQocSk7WT9GLnB1c2goYWUpOkYucHVzaChodChudWxsKSxhZSl9aWYodGhpcy5hbGxvY2F0ZUJpbmRpbmdTbG90cyh3KSwwPT09aGUpdyBpbnN0YW5jZW9mIHZzP3RoaXMuaW50ZXJwb2xhdGVkVXBkYXRlSW5zdHJ1Y3Rpb24oQ1EodyksZSxLLHVlLHcsRik6ei5wdXNoKHtzcGFuOnVlLnNvdXJjZVNwYW4scGFyYW1zT3JGbjp6VCgoKT0+dGhpcy5jb252ZXJ0UHJvcGVydHlCaW5kaW5nKHcpLEssRil9KTtlbHNlIGlmKDE9PT1oZSlpZih3IGluc3RhbmNlb2YgdnMmJndtKHcpPjEpdGhpcy5pbnRlcnBvbGF0ZWRVcGRhdGVJbnN0cnVjdGlvbihmdW5jdGlvbihuKXtzd2l0Y2god20obikpe2Nhc2UgMzpyZXR1cm4gdGUuYXR0cmlidXRlSW50ZXJwb2xhdGUxO2Nhc2UgNTpyZXR1cm4gdGUuYXR0cmlidXRlSW50ZXJwb2xhdGUyO2Nhc2UgNzpyZXR1cm4gdGUuYXR0cmlidXRlSW50ZXJwb2xhdGUzO2Nhc2UgOTpyZXR1cm4gdGUuYXR0cmlidXRlSW50ZXJwb2xhdGU0O2Nhc2UgMTE6cmV0dXJuIHRlLmF0dHJpYnV0ZUludGVycG9sYXRlNTtjYXNlIDEzOnJldHVybiB0ZS5hdHRyaWJ1dGVJbnRlcnBvbGF0ZTY7Y2FzZSAxNTpyZXR1cm4gdGUuYXR0cmlidXRlSW50ZXJwb2xhdGU3O2Nhc2UgMTc6cmV0dXJuIHRlLmF0dHJpYnV0ZUludGVycG9sYXRlODtkZWZhdWx0OnJldHVybiB0ZS5hdHRyaWJ1dGVJbnRlcnBvbGF0ZVZ9fSh3KSxlLEssdWUsdyxGKTtlbHNle2xldCBhZT13IGluc3RhbmNlb2YgdnM/dy5leHByZXNzaW9uc1swXTp3O2ZlLnB1c2goe3NwYW46dWUuc291cmNlU3BhbixwYXJhbXNPckZuOnpUKCgpPT50aGlzLmNvbnZlcnRQcm9wZXJ0eUJpbmRpbmcoYWUpLEssRil9KX1lbHNlIHRoaXMudXBkYXRlSW5zdHJ1Y3Rpb25XaXRoQWR2YW5jZShlLHVlLnNvdXJjZVNwYW4sdGUuY2xhc3NQcm9wLCgpPT5baHQoZSksaHQoSyksdGhpcy5jb252ZXJ0UHJvcGVydHlCaW5kaW5nKHcpLC4uLkZdKX19fSk7Zm9yKGxldCB1ZSBvZiB6KXRoaXMudXBkYXRlSW5zdHJ1Y3Rpb25XaXRoQWR2YW5jZShlLHVlLnNwYW4sdGUucHJvcGVydHksdWUucGFyYW1zT3JGbik7Zm9yKGxldCB1ZSBvZiBmZSl0aGlzLnVwZGF0ZUluc3RydWN0aW9uV2l0aEFkdmFuY2UoZSx1ZS5zcGFuLHRlLmF0dHJpYnV0ZSx1ZS5wYXJhbXNPckZuKTtpZihBWCh0aGlzLHQuY2hpbGRyZW4pLCFvJiZ0aGlzLmkxOG4mJnRoaXMuaTE4bi5hcHBlbmRFbGVtZW50KHQuaTE4bixlLCEwKSwhYil7bGV0IHVlPXQuZW5kU291cmNlU3Bhbj8/dC5zb3VyY2VTcGFuO28mJnRoaXMuaTE4bkVuZCh1ZSxEKSxyJiZ0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24odWUsdGUuZW5hYmxlQmluZGluZ3MpLHRoaXMuY3JlYXRpb25JbnN0cnVjdGlvbih1ZSxjP3RlLmVsZW1lbnRDb250YWluZXJFbmQ6dGUuZWxlbWVudEVuZCl9fXZpc2l0VGVtcGxhdGUodCl7bGV0IGU9Im5nLXRlbXBsYXRlIixpPXRoaXMuYWxsb2NhdGVEYXRhU2xvdCgpO3RoaXMuaTE4biYmdGhpcy5pMThuLmFwcGVuZFRlbXBsYXRlKHQuaTE4bixpKTtsZXQgcj10LnRhZ05hbWU/S2QodC50YWdOYW1lKVsxXTp0LnRhZ05hbWUsbz1gJHt0aGlzLmNvbnRleHROYW1lfSR7dC50YWdOYW1lPyJfIittXyh0LnRhZ05hbWUpOiIifV8ke2l9YCxzPWAke299X1RlbXBsYXRlYCxhPVtodChpKSxSaShzKSxodChyKV0sbD10aGlzLmdldEF0dHJpYnV0ZUV4cHJlc3Npb25zKGUsdC5hdHRyaWJ1dGVzLHQuaW5wdXRzLHQub3V0cHV0cyx2b2lkIDAsdC50ZW1wbGF0ZUF0dHJzKTtpZihhLnB1c2godGhpcy5hZGRBdHRyc1RvQ29uc3RzKGwpKSx0LnJlZmVyZW5jZXMmJnQucmVmZXJlbmNlcy5sZW5ndGgpe2xldCB1PXRoaXMucHJlcGFyZVJlZnNBcnJheSh0LnJlZmVyZW5jZXMpO2EucHVzaCh0aGlzLmFkZFRvQ29uc3RzKHUpKSxhLnB1c2goVG4odGUudGVtcGxhdGVSZWZFeHRyYWN0b3IpKX1sZXQgYz1uZXcgS0ModGhpcy5jb25zdGFudFBvb2wsdGhpcy5fYmluZGluZ1Njb3BlLHRoaXMubGV2ZWwrMSxvLHRoaXMuaTE4bixpLHMsdGhpcy5fbmFtZXNwYWNlLHRoaXMuZmlsZUJhc2VkSTE4blN1ZmZpeCx0aGlzLmkxOG5Vc2VFeHRlcm5hbElkcyx0aGlzLl9jb25zdGFudHMpO2lmKHRoaXMuX25lc3RlZFRlbXBsYXRlRm5zLnB1c2goKCk9PntsZXQgdT1jLmJ1aWxkVGVtcGxhdGVGdW5jdGlvbih0LmNoaWxkcmVuLHQudmFyaWFibGVzLHRoaXMuX25nQ29udGVudFJlc2VydmVkU2xvdHMubGVuZ3RoK3RoaXMuX25nQ29udGVudFNlbGVjdG9yc09mZnNldCx0LmkxOG4pO3RoaXMuY29uc3RhbnRQb29sLnN0YXRlbWVudHMucHVzaCh1LnRvRGVjbFN0bXQocykpLGMuX25nQ29udGVudFJlc2VydmVkU2xvdHMubGVuZ3RoJiZ0aGlzLl9uZ0NvbnRlbnRSZXNlcnZlZFNsb3RzLnB1c2goLi4uYy5fbmdDb250ZW50UmVzZXJ2ZWRTbG90cyl9KSx0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24odC5zb3VyY2VTcGFuLHRlLnRlbXBsYXRlQ3JlYXRlLCgpPT4oYS5zcGxpY2UoMiwwLGh0KGMuZ2V0Q29uc3RDb3VudCgpKSxodChjLmdldFZhckNvdW50KCkpKSxzQihhKSkpLHRoaXMudGVtcGxhdGVQcm9wZXJ0eUJpbmRpbmdzKGksdC50ZW1wbGF0ZUF0dHJzKSxyPT09ZSl7bGV0W3UsZF09ZnVuY3Rpb24obix0KXtsZXQgZT1bXSxpPVtdO2ZvcihsZXQgciBvZiBuKSh0KHIpP2U6aSkucHVzaChyKTtyZXR1cm5bZSxpXX0odC5pbnB1dHMsQ3dlKTt1Lmxlbmd0aD4wJiZ0aGlzLmkxOG5BdHRyaWJ1dGVzSW5zdHJ1Y3Rpb24oaSx1LHQuc3RhcnRTb3VyY2VTcGFuPz90LnNvdXJjZVNwYW4pLGQubGVuZ3RoPjAmJnRoaXMudGVtcGxhdGVQcm9wZXJ0eUJpbmRpbmdzKGksZCk7Zm9yKGxldCBwIG9mIHQub3V0cHV0cyl0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24ocC5zb3VyY2VTcGFuLHRlLmxpc3RlbmVyLHRoaXMucHJlcGFyZUxpc3RlbmVyUGFyYW1ldGVyKCJuZ190ZW1wbGF0ZSIscCxpKSl9fXZpc2l0Qm91bmRUZXh0KHQpe2lmKHRoaXMuaTE4bil7bGV0IHI9dC52YWx1ZS52aXNpdCh0aGlzLl92YWx1ZUNvbnZlcnRlcik7cmV0dXJuIHRoaXMuYWxsb2NhdGVCaW5kaW5nU2xvdHMociksdm9pZChyIGluc3RhbmNlb2YgdnMmJih0aGlzLmkxOG4uYXBwZW5kQm91bmRUZXh0KHQuaTE4biksdGhpcy5pMThuQXBwZW5kQmluZGluZ3Moci5leHByZXNzaW9ucykpKX1sZXQgZT10aGlzLmFsbG9jYXRlRGF0YVNsb3QoKTt0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24odC5zb3VyY2VTcGFuLHRlLnRleHQsW2h0KGUpXSk7bGV0IGk9dC52YWx1ZS52aXNpdCh0aGlzLl92YWx1ZUNvbnZlcnRlcik7dGhpcy5hbGxvY2F0ZUJpbmRpbmdTbG90cyhpKSxpIGluc3RhbmNlb2YgdnM/dGhpcy51cGRhdGVJbnN0cnVjdGlvbldpdGhBZHZhbmNlKGUsdC5zb3VyY2VTcGFuLGZ1bmN0aW9uKG4pe3N3aXRjaCh3bShuKSl7Y2FzZSAxOnJldHVybiB0ZS50ZXh0SW50ZXJwb2xhdGU7Y2FzZSAzOnJldHVybiB0ZS50ZXh0SW50ZXJwb2xhdGUxO2Nhc2UgNTpyZXR1cm4gdGUudGV4dEludGVycG9sYXRlMjtjYXNlIDc6cmV0dXJuIHRlLnRleHRJbnRlcnBvbGF0ZTM7Y2FzZSA5OnJldHVybiB0ZS50ZXh0SW50ZXJwb2xhdGU0O2Nhc2UgMTE6cmV0dXJuIHRlLnRleHRJbnRlcnBvbGF0ZTU7Y2FzZSAxMzpyZXR1cm4gdGUudGV4dEludGVycG9sYXRlNjtjYXNlIDE1OnJldHVybiB0ZS50ZXh0SW50ZXJwb2xhdGU3O2Nhc2UgMTc6cmV0dXJuIHRlLnRleHRJbnRlcnBvbGF0ZTg7ZGVmYXVsdDpyZXR1cm4gdGUudGV4dEludGVycG9sYXRlVn19KGkpLCgpPT50aGlzLmdldFVwZGF0ZUluc3RydWN0aW9uQXJndW1lbnRzKGkpKTpRVCgiVGV4dCBub2RlcyBzaG91bGQgYmUgaW50ZXJwb2xhdGVkIGFuZCBuZXZlciBib3VuZCBkaXJlY3RseS4iKX12aXNpdFRleHQodCl7dGhpcy5pMThufHx0aGlzLmNyZWF0aW9uSW5zdHJ1Y3Rpb24odC5zb3VyY2VTcGFuLHRlLnRleHQsW2h0KHRoaXMuYWxsb2NhdGVEYXRhU2xvdCgpKSxodCh0LnZhbHVlKV0pfXZpc2l0SWN1KHQpe2xldCBlPSExO3RoaXMuaTE4bnx8KGU9ITAsdGhpcy5pMThuU3RhcnQobnVsbCx0LmkxOG4sITApKTtsZXQgaT10aGlzLmkxOG4scj10aGlzLmkxOG5CaW5kUHJvcHModC52YXJzKSxvPXRoaXMuaTE4bkJpbmRQcm9wcyh0LnBsYWNlaG9sZGVycykscz10LmkxOG4sYT1sPT57bGV0IHU9VVYoey4uLnIsLi4ub30sITEpO3JldHVybiBnbShudWxsLHRlLmkxOG5Qb3N0cHJvY2VzcyxbbCx3RCh1LCEwKV0pfTtpZihWVChpLm1ldGEpKXRoaXMuaTE4blRyYW5zbGF0ZShzLHt9LGkucmVmLGEpO2Vsc2V7bGV0IGw9dGhpcy5pMThuVHJhbnNsYXRlKHMse30sdm9pZCAwLGEpO2kuYXBwZW5kSWN1KFdRKHMpLm5hbWUsbCl9cmV0dXJuIGUmJnRoaXMuaTE4bkVuZChudWxsLCEwKSxudWxsfWFsbG9jYXRlRGF0YVNsb3QoKXtyZXR1cm4gdGhpcy5fZGF0YUluZGV4Kyt9Z2V0Q29uc3RDb3VudCgpe3JldHVybiB0aGlzLl9kYXRhSW5kZXh9Z2V0VmFyQ291bnQoKXtyZXR1cm4gdGhpcy5fcHVyZUZ1bmN0aW9uU2xvdHN9Z2V0Q29uc3RzKCl7cmV0dXJuIHRoaXMuX2NvbnN0YW50c31nZXROZ0NvbnRlbnRTZWxlY3RvcnMoKXtyZXR1cm4gdGhpcy5fbmdDb250ZW50UmVzZXJ2ZWRTbG90cy5sZW5ndGg/dGhpcy5jb25zdGFudFBvb2wuZ2V0Q29uc3RMaXRlcmFsKE51KHRoaXMuX25nQ29udGVudFJlc2VydmVkU2xvdHMpLCEwKTpudWxsfWJpbmRpbmdDb250ZXh0KCl7cmV0dXJuIiIrdGhpcy5fYmluZGluZ0NvbnRleHQrK310ZW1wbGF0ZVByb3BlcnR5QmluZGluZ3ModCxlKXtsZXQgaT1bXTtmb3IobGV0IHIgb2YgZSl7aWYoIShyIGluc3RhbmNlb2Ygd18pKWNvbnRpbnVlO2xldCBvPXIudmFsdWUudmlzaXQodGhpcy5fdmFsdWVDb252ZXJ0ZXIpO2lmKHZvaWQgMCE9PW8paWYodGhpcy5hbGxvY2F0ZUJpbmRpbmdTbG90cyhvKSxvIGluc3RhbmNlb2YgdnMpe2xldCBzPVtdO3RoaXMuaW50ZXJwb2xhdGVkVXBkYXRlSW5zdHJ1Y3Rpb24oQ1EobyksdCxyLm5hbWUscixvLHMpfWVsc2UgaS5wdXNoKHtzcGFuOnIuc291cmNlU3BhbixwYXJhbXNPckZuOnpUKCgpPT50aGlzLmNvbnZlcnRQcm9wZXJ0eUJpbmRpbmcobyksci5uYW1lKX0pfWZvcihsZXQgciBvZiBpKXRoaXMudXBkYXRlSW5zdHJ1Y3Rpb25XaXRoQWR2YW5jZSh0LHIuc3Bhbix0ZS5wcm9wZXJ0eSxyLnBhcmFtc09yRm4pfWluc3RydWN0aW9uRm4odCxlLGkscixvPSExKXt0W28/InVuc2hpZnQiOiJwdXNoIl0oe3NwYW46ZSxyZWZlcmVuY2U6aSxwYXJhbXNPckZuOnJ9KX1wcm9jZXNzU3R5bGluZ1VwZGF0ZUluc3RydWN0aW9uKHQsZSl7bGV0IGk9MDtpZihlKWZvcihsZXQgciBvZiBlLmNhbGxzKWkrPXIuYWxsb2NhdGVCaW5kaW5nU2xvdHMsdGhpcy51cGRhdGVJbnN0cnVjdGlvbldpdGhBZHZhbmNlKHQsci5zb3VyY2VTcGFuLGUucmVmZXJlbmNlLCgpPT5yLnBhcmFtcyhvPT5yLnN1cHBvcnRzSW50ZXJwb2xhdGlvbiYmbyBpbnN0YW5jZW9mIHZzP3RoaXMuZ2V0VXBkYXRlSW5zdHJ1Y3Rpb25Bcmd1bWVudHMobyk6dGhpcy5jb252ZXJ0UHJvcGVydHlCaW5kaW5nKG8pKSk7cmV0dXJuIGl9Y3JlYXRpb25JbnN0cnVjdGlvbih0LGUsaSxyKXt0aGlzLmluc3RydWN0aW9uRm4odGhpcy5fY3JlYXRpb25Db2RlRm5zLHQsZSxpfHxbXSxyKX11cGRhdGVJbnN0cnVjdGlvbldpdGhBZHZhbmNlKHQsZSxpLHIpe3RoaXMuYWRkQWR2YW5jZUluc3RydWN0aW9uSWZOZWNlc3NhcnkodCxlKSx0aGlzLnVwZGF0ZUluc3RydWN0aW9uKGUsaSxyKX11cGRhdGVJbnN0cnVjdGlvbih0LGUsaSl7dGhpcy5pbnN0cnVjdGlvbkZuKHRoaXMuX3VwZGF0ZUNvZGVGbnMsdCxlLGl8fFtdKX1hZGRBZHZhbmNlSW5zdHJ1Y3Rpb25JZk5lY2Vzc2FyeSh0LGUpe2lmKHQhPT10aGlzLl9jdXJyZW50SW5kZXgpe2xldCBpPXQtdGhpcy5fY3VycmVudEluZGV4O2lmKGk8MSl0aHJvdyBuZXcgRXJyb3IoImFkdmFuY2UgaW5zdHJ1Y3Rpb24gY2FuIG9ubHkgZ28gZm9yd2FyZHMiKTt0aGlzLmluc3RydWN0aW9uRm4odGhpcy5fdXBkYXRlQ29kZUZucyxlLHRlLmFkdmFuY2UsW2h0KGkpXSksdGhpcy5fY3VycmVudEluZGV4PXR9fWFsbG9jYXRlUHVyZUZ1bmN0aW9uU2xvdHModCl7bGV0IGU9dGhpcy5fcHVyZUZ1bmN0aW9uU2xvdHM7cmV0dXJuIHRoaXMuX3B1cmVGdW5jdGlvblNsb3RzKz10LGV9YWxsb2NhdGVCaW5kaW5nU2xvdHModCl7dGhpcy5fYmluZGluZ1Nsb3RzKz10IGluc3RhbmNlb2YgdnM/dC5leHByZXNzaW9ucy5sZW5ndGg6MX1nZXRJbXBsaWNpdFJlY2VpdmVyRXhwcigpe3JldHVybiB0aGlzLl9pbXBsaWNpdFJlY2VpdmVyRXhwcj90aGlzLl9pbXBsaWNpdFJlY2VpdmVyRXhwcjp0aGlzLl9pbXBsaWNpdFJlY2VpdmVyRXhwcj0wPT09dGhpcy5sZXZlbD9SaShIYyk6dGhpcy5fYmluZGluZ1Njb3BlLmdldE9yQ3JlYXRlU2hhcmVkQ29udGV4dFZhcigwKX1jb252ZXJ0UHJvcGVydHlCaW5kaW5nKHQpe2xldCBlPXVLKHRoaXMsdGhpcy5nZXRJbXBsaWNpdFJlY2VpdmVyRXhwcigpLHQsdGhpcy5iaW5kaW5nQ29udGV4dCgpKSxpPWUuY3VyclZhbEV4cHI7cmV0dXJuIHRoaXMuX3RlbXBWYXJpYWJsZXMucHVzaCguLi5lLnN0bXRzKSxpfWdldFVwZGF0ZUluc3RydWN0aW9uQXJndW1lbnRzKHQpe2xldHthcmdzOmUsc3RtdHM6aX09ZnVuY3Rpb24obix0LGUsaSl7bGV0IHI9bmV3IEdDKG4sdCxpLCEwKSxvPXIudmlzaXRJbnRlcnBvbGF0aW9uKGUsemkuRXhwcmVzc2lvbik7cmV0dXJuIHIudXNlc0ltcGxpY2l0UmVjZWl2ZXImJm4ubm90aWZ5SW1wbGljaXRSZWNlaXZlclVzZSgpLHtzdG10czpkSyhyLGkpLGFyZ3M6by5hcmdzfX0odGhpcyx0aGlzLmdldEltcGxpY2l0UmVjZWl2ZXJFeHByKCksdCx0aGlzLmJpbmRpbmdDb250ZXh0KCkpO3JldHVybiB0aGlzLl90ZW1wVmFyaWFibGVzLnB1c2goLi4uaSksZX1nZXRBdHRyaWJ1dGVFeHByZXNzaW9ucyh0LGUsaSxyLG8scz1bXSxhPVtdKXtsZXQgdSxsPW5ldyBTZXQsYz1bXTtmb3IobGV0IHAgb2YgZSlpZigibmdQcm9qZWN0QXMiPT09cC5uYW1lJiYodT1wKSxwLmkxOG4pe2xldCBmLHtpMThuVmFyUmVmc0NhY2hlOmh9PXRoaXMuX2NvbnN0YW50cztoLmhhcyhwLmkxOG4pP2Y9aC5nZXQocC5pMThuKTooZj10aGlzLmkxOG5UcmFuc2xhdGUocC5pMThuKSxoLnNldChwLmkxOG4sZikpLGMucHVzaChodChwLm5hbWUpLGYpfWVsc2UgYy5wdXNoKC4uLnhRKHAubmFtZSksRTFlKHQscCkpO2Z1bmN0aW9uIGQocCxoKXsic3RyaW5nIj09dHlwZW9mIHA/bC5oYXMocCl8fChjLnB1c2goLi4ueFEocCkpLHZvaWQgMCE9PWgmJmMucHVzaChoKSxsLmFkZChwKSk6Yy5wdXNoKGh0KHApKX1pZih1JiZjLnB1c2goLi4uZnVuY3Rpb24obil7bGV0IHQ9TlYobi52YWx1ZSlbMF07cmV0dXJuW2h0KDUpLE51KHQpXX0odSkpLG8mJm8ucG9wdWxhdGVJbml0aWFsU3R5bGluZ0F0dHJzKGMpLGkubGVuZ3RofHxyLmxlbmd0aCl7bGV0IHA9Yy5sZW5ndGg7Zm9yKGxldCBoPTA7aDxpLmxlbmd0aDtoKyspe2xldCBmPWlbaF07NCE9PWYudHlwZSYmMSE9PWYudHlwZSYmZChmLm5hbWUpfWZvcihsZXQgaD0wO2g8ci5sZW5ndGg7aCsrKXtsZXQgZj1yW2hdOzEhPT1mLnR5cGUmJmQoZi5uYW1lKX1jLmxlbmd0aCE9PXAmJmMuc3BsaWNlKHAsMCxodCgzKSl9cmV0dXJuIHMubGVuZ3RoJiYoYy5wdXNoKGh0KDQpKSxzLmZvckVhY2gocD0+ZChwLm5hbWUpKSksYS5sZW5ndGgmJihjLnB1c2goaHQoNikpLGEuZm9yRWFjaChwPT5kKHAubmFtZSkpKSxjfWFkZFRvQ29uc3RzKHQpe2lmKFZRKHQpKXJldHVybiBXVDtsZXQgZT10aGlzLl9jb25zdGFudHMuY29uc3RFeHByZXNzaW9ucztmb3IobGV0IGk9MDtpPGUubGVuZ3RoO2krKylpZihlW2ldLmlzRXF1aXZhbGVudCh0KSlyZXR1cm4gaHQoaSk7cmV0dXJuIGh0KGUucHVzaCh0KS0xKX1hZGRBdHRyc1RvQ29uc3RzKHQpe3JldHVybiB0Lmxlbmd0aD4wP3RoaXMuYWRkVG9Db25zdHMoX3IodCkpOldUfXByZXBhcmVSZWZzQXJyYXkodCl7cmV0dXJuIHQmJjAhPT10Lmxlbmd0aD9OdShESyh0Lm1hcChpPT57bGV0IHI9dGhpcy5hbGxvY2F0ZURhdGFTbG90KCksbz10aGlzLl9iaW5kaW5nU2NvcGUuZnJlc2hSZWZlcmVuY2VOYW1lKCkscz10aGlzLmxldmVsLGE9Umkobyk7cmV0dXJuIHRoaXMuX2JpbmRpbmdTY29wZS5zZXQocyxpLm5hbWUsYSwwLChsLGMpPT57bGV0IHU9Yz4wP1tJVihjKS50b1N0bXQoKV06W10sZD1hLnNldChUbih0ZS5yZWZlcmVuY2UpLmNhbGxGbihbaHQocildKSk7cmV0dXJuIHUuY29uY2F0KGQudG9Db25zdERlY2woKSl9LCEwKSxbaS5uYW1lLGkudmFsdWVdfSkpKTpXVH1wcmVwYXJlTGlzdGVuZXJQYXJhbWV0ZXIodCxlLGkpe3JldHVybigpPT57bGV0IHI9ZS5uYW1lLG89MT09PWUudHlwZT9qUShyLGUucGhhc2UpOm1fKHIpLHM9YCR7dGhpcy50ZW1wbGF0ZU5hbWV9XyR7dH1fJHtvfV8ke2l9X2xpc3RlbmVyYCxhPXRoaXMuX2JpbmRpbmdTY29wZS5uZXN0ZWRTY29wZSh0aGlzLl9iaW5kaW5nU2NvcGUuYmluZGluZ0xldmVsLHdLKTtyZXR1cm4gU0soZSxzLGEpfX19LFREPWNsYXNzIGV4dGVuZHMgJEJ7Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIoKSx0aGlzLmNvbnN0YW50UG9vbD10LHRoaXMuYWxsb2NhdGVTbG90PWUsdGhpcy5hbGxvY2F0ZVB1cmVGdW5jdGlvblNsb3RzPWksdGhpcy5kZWZpbmVQaXBlPXIsdGhpcy5fcGlwZUJpbmRFeHBycz1bXX12aXNpdFBpcGUodCxlKXtsZXQgaT10aGlzLmFsbG9jYXRlU2xvdCgpLHI9YFBJUEU6JHtpfWAsbz10aGlzLmFsbG9jYXRlUHVyZUZ1bmN0aW9uU2xvdHMoMit0LmFyZ3MubGVuZ3RoKSxzPW5ldyBMdSh0LnNwYW4sdC5zb3VyY2VTcGFuLHQubmFtZVNwYW4sbmV3IHhtKHQuc3Bhbix0LnNvdXJjZVNwYW4pLHIpLHtpZGVudGlmaWVyOmEsaXNWYXJMZW5ndGg6bH09ZnVuY3Rpb24obil7bGV0IHQ9djFlW24ubGVuZ3RoXTtyZXR1cm57aWRlbnRpZmllcjp0fHx0ZS5waXBlQmluZFYsaXNWYXJMZW5ndGg6IXR9fSh0LmFyZ3MpO3RoaXMuZGVmaW5lUGlwZSh0Lm5hbWUscixpLFRuKGEpKTtsZXQgYz1bdC5leHAsLi4udC5hcmdzXSx1PXRoaXMudmlzaXRBbGwobD9bbmV3IE9fKHQuc3Bhbix0LnNvdXJjZVNwYW4sYyldOmMpLGQ9bmV3IGFoKHQuc3Bhbix0LnNvdXJjZVNwYW4scyxbbmV3IHRhKHQuc3Bhbix0LnNvdXJjZVNwYW4saSksbmV3IHRhKHQuc3Bhbix0LnNvdXJjZVNwYW4sbyksLi4udV0sbnVsbCk7cmV0dXJuIHRoaXMuX3BpcGVCaW5kRXhwcnMucHVzaChkKSxkfXVwZGF0ZVBpcGVTbG90T2Zmc2V0cyh0KXt0aGlzLl9waXBlQmluZEV4cHJzLmZvckVhY2goZT0+e2UuYXJnc1sxXS52YWx1ZSs9dH0pfXZpc2l0TGl0ZXJhbEFycmF5KHQsZSl7cmV0dXJuIG5ldyByaCh0LnNwYW4sdC5zb3VyY2VTcGFuLHRoaXMudmlzaXRBbGwodC5leHByZXNzaW9ucyksaT0+e2xldCByPV9yKGkpO3JldHVybiBiUSh0aGlzLmNvbnN0YW50UG9vbCxyLHRoaXMuYWxsb2NhdGVQdXJlRnVuY3Rpb25TbG90cyl9KX12aXNpdExpdGVyYWxNYXAodCxlKXtyZXR1cm4gbmV3IHJoKHQuc3Bhbix0LnNvdXJjZVNwYW4sdGhpcy52aXNpdEFsbCh0LnZhbHVlcyksaT0+e2xldCByPXFsKGkubWFwKChvLHMpPT4oe2tleTp0LmtleXNbc10ua2V5LHZhbHVlOm8scXVvdGVkOnQua2V5c1tzXS5xdW90ZWR9KSkpO3JldHVybiBiUSh0aGlzLmNvbnN0YW50UG9vbCxyLHRoaXMuYWxsb2NhdGVQdXJlRnVuY3Rpb25TbG90cyl9KX19LHYxZT1bdGUucGlwZUJpbmQxLHRlLnBpcGVCaW5kMix0ZS5waXBlQmluZDMsdGUucGlwZUJpbmQ0XSxiMWU9W3RlLnB1cmVGdW5jdGlvbjAsdGUucHVyZUZ1bmN0aW9uMSx0ZS5wdXJlRnVuY3Rpb24yLHRlLnB1cmVGdW5jdGlvbjMsdGUucHVyZUZ1bmN0aW9uNCx0ZS5wdXJlRnVuY3Rpb241LHRlLnB1cmVGdW5jdGlvbjYsdGUucHVyZUZ1bmN0aW9uNyx0ZS5wdXJlRnVuY3Rpb244XTtmdW5jdGlvbiBJVihuKXtyZXR1cm4gVG4odGUubmV4dENvbnRleHQpLmNhbGxGbihuPjE/W2h0KG4pXTpbXSl9ZnVuY3Rpb24gYlEobix0LGUpe2xldHtsaXRlcmFsRmFjdG9yeTppLGxpdGVyYWxGYWN0b3J5QXJndW1lbnRzOnJ9PW4uZ2V0TGl0ZXJhbEZhY3RvcnkodCksbz1lKDErci5sZW5ndGgpLHtpZGVudGlmaWVyOnMsaXNWYXJMZW5ndGg6YX09ZnVuY3Rpb24obil7bGV0IHQ9YjFlW24ubGVuZ3RoXTtyZXR1cm57aWRlbnRpZmllcjp0fHx0ZS5wdXJlRnVuY3Rpb25WLGlzVmFyTGVuZ3RoOiF0fX0ociksbD1baHQobyksaV07cmV0dXJuIGE/bC5wdXNoKF9yKHIpKTpsLnB1c2goLi4uciksVG4ocykuY2FsbEZuKGwpfWZ1bmN0aW9uIHhRKG4pe2xldFt0LGVdPUtkKG4pLGk9aHQoZSk7cmV0dXJuIHQ/W2h0KDApLGh0KHQpLGldOltpXX12YXIgb189IiQkc2hhcmVkX2N0eCQkIixCXz1jbGFzc3tjb25zdHJ1Y3Rvcih0PTAsZT1udWxsLGkpe2lmKHRoaXMuYmluZGluZ0xldmVsPXQsdGhpcy5wYXJlbnQ9ZSx0aGlzLmdsb2JhbHM9aSx0aGlzLm1hcD1uZXcgTWFwLHRoaXMucmVmZXJlbmNlTmFtZUluZGV4PTAsdGhpcy5yZXN0b3JlVmlld1ZhcmlhYmxlPW51bGwsdGhpcy51c2VzUmVzdG9yZWRWaWV3Q29udGV4dD0hMSx2b2lkIDAhPT1pKWZvcihsZXQgciBvZiBpKXRoaXMuc2V0KDAscixSaShyKSl9c3RhdGljIGNyZWF0ZVJvb3RTY29wZSgpe3JldHVybiBuZXcgQl99Z2V0KHQpe2xldCBlPXRoaXM7Zm9yKDtlOyl7bGV0IGk9ZS5tYXAuZ2V0KHQpO2lmKG51bGwhPWkpcmV0dXJuIGUhPT10aGlzJiYoaT17cmV0cmlldmFsTGV2ZWw6aS5yZXRyaWV2YWxMZXZlbCxsaHM6aS5saHMsZGVjbGFyZUxvY2FsQ2FsbGJhY2s6aS5kZWNsYXJlTG9jYWxDYWxsYmFjayxkZWNsYXJlOiExLHByaW9yaXR5OmkucHJpb3JpdHl9LHRoaXMubWFwLnNldCh0LGkpLHRoaXMubWF5YmVHZW5lcmF0ZVNoYXJlZENvbnRleHRWYXIoaSksdGhpcy5tYXliZVJlc3RvcmVWaWV3KCkpLGkuZGVjbGFyZUxvY2FsQ2FsbGJhY2smJiFpLmRlY2xhcmUmJihpLmRlY2xhcmU9ITApLGkubGhzO2U9ZS5wYXJlbnR9cmV0dXJuIDA9PT10aGlzLmJpbmRpbmdMZXZlbD9udWxsOnRoaXMuZ2V0Q29tcG9uZW50UHJvcGVydHkodCl9c2V0KHQsZSxpLHI9MCxvLHMpe2lmKHRoaXMubWFwLmhhcyhlKSl7aWYocylyZXR1cm4gdGhpcztRVChgVGhlIG5hbWUgJHtlfSBpcyBhbHJlYWR5IGRlZmluZWQgaW4gc2NvcGUgdG8gYmUgJHt0aGlzLm1hcC5nZXQoZSl9YCl9cmV0dXJuIHRoaXMubWFwLnNldChlLHtyZXRyaWV2YWxMZXZlbDp0LGxoczppLGRlY2xhcmU6ITEsZGVjbGFyZUxvY2FsQ2FsbGJhY2s6byxwcmlvcml0eTpyfSksdGhpc31nZXRMb2NhbCh0KXtyZXR1cm4gdGhpcy5nZXQodCl9bm90aWZ5SW1wbGljaXRSZWNlaXZlclVzZSgpezAhPT10aGlzLmJpbmRpbmdMZXZlbCYmKHRoaXMubWFwLmdldChvXyswKS5kZWNsYXJlPSEwKX1uZXN0ZWRTY29wZSh0LGUpe2xldCBpPW5ldyBCXyh0LHRoaXMsZSk7cmV0dXJuIHQ+MCYmaS5nZW5lcmF0ZVNoYXJlZENvbnRleHRWYXIoMCksaX1nZXRPckNyZWF0ZVNoYXJlZENvbnRleHRWYXIodCl7bGV0IGU9b18rdDtyZXR1cm4gdGhpcy5tYXAuaGFzKGUpfHx0aGlzLmdlbmVyYXRlU2hhcmVkQ29udGV4dFZhcih0KSx0aGlzLm1hcC5nZXQoZSkubGhzfWdldFNoYXJlZENvbnRleHROYW1lKHQpe2xldCBlPXRoaXMubWFwLmdldChvXyt0KTtyZXR1cm4gZSYmZS5kZWNsYXJlP2UubGhzOm51bGx9bWF5YmVHZW5lcmF0ZVNoYXJlZENvbnRleHRWYXIodCl7aWYoMT09PXQucHJpb3JpdHkmJnQucmV0cmlldmFsTGV2ZWw8dGhpcy5iaW5kaW5nTGV2ZWwpe2xldCBlPXRoaXMubWFwLmdldChvXyt0LnJldHJpZXZhbExldmVsKTtlP2UuZGVjbGFyZT0hMDp0aGlzLmdlbmVyYXRlU2hhcmVkQ29udGV4dFZhcih0LnJldHJpZXZhbExldmVsKX19Z2VuZXJhdGVTaGFyZWRDb250ZXh0VmFyKHQpe2xldCBlPVJpKEhjK3RoaXMuZnJlc2hSZWZlcmVuY2VOYW1lKCkpO3RoaXMubWFwLnNldChvXyt0LHtyZXRyaWV2YWxMZXZlbDp0LGxoczplLGRlY2xhcmVMb2NhbENhbGxiYWNrOihpLHIpPT5bZS5zZXQoSVYocikpLnRvQ29uc3REZWNsKCldLGRlY2xhcmU6ITEscHJpb3JpdHk6Mn0pfWdldENvbXBvbmVudFByb3BlcnR5KHQpe2xldCBlPXRoaXMubWFwLmdldChvXyswKTtyZXR1cm4gZS5kZWNsYXJlPSEwLHRoaXMubWF5YmVSZXN0b3JlVmlldygpLGUubGhzLnByb3AodCl9bWF5YmVSZXN0b3JlVmlldygpe3RoaXMuaXNMaXN0ZW5lclNjb3BlKCkmJih0aGlzLnBhcmVudC5yZXN0b3JlVmlld1ZhcmlhYmxlfHwodGhpcy5wYXJlbnQucmVzdG9yZVZpZXdWYXJpYWJsZT1SaSh0aGlzLnBhcmVudC5mcmVzaFJlZmVyZW5jZU5hbWUoKSkpLHRoaXMucmVzdG9yZVZpZXdWYXJpYWJsZT10aGlzLnBhcmVudC5yZXN0b3JlVmlld1ZhcmlhYmxlKX1yZXN0b3JlVmlld1N0YXRlbWVudCgpe2lmKHRoaXMucmVzdG9yZVZpZXdWYXJpYWJsZSl7bGV0IHQ9Z20obnVsbCx0ZS5yZXN0b3JlVmlldyxbdGhpcy5yZXN0b3JlVmlld1ZhcmlhYmxlXSk7cmV0dXJuIHRoaXMudXNlc1Jlc3RvcmVkVmlld0NvbnRleHQ/UmkoWVEpLnNldCh0KS50b0NvbnN0RGVjbCgpOnQudG9TdG10KCl9cmV0dXJuIG51bGx9dmlld1NuYXBzaG90U3RhdGVtZW50cygpe3JldHVybiB0aGlzLnJlc3RvcmVWaWV3VmFyaWFibGU/W3RoaXMucmVzdG9yZVZpZXdWYXJpYWJsZS5zZXQoZ20obnVsbCx0ZS5nZXRDdXJyZW50VmlldyxbXSkpLnRvQ29uc3REZWNsKCldOltdfWlzTGlzdGVuZXJTY29wZSgpe3JldHVybiB0aGlzLnBhcmVudCYmdGhpcy5wYXJlbnQuYmluZGluZ0xldmVsPT09dGhpcy5iaW5kaW5nTGV2ZWx9dmFyaWFibGVEZWNsYXJhdGlvbnMoKXtsZXQgdD0wO3JldHVybiBBcnJheS5mcm9tKHRoaXMubWFwLnZhbHVlcygpKS5maWx0ZXIoZT0+ZS5kZWNsYXJlKS5zb3J0KChlLGkpPT5pLnJldHJpZXZhbExldmVsLWUucmV0cmlldmFsTGV2ZWx8fGkucHJpb3JpdHktZS5wcmlvcml0eSkucmVkdWNlKChlLGkpPT57bGV0IHI9dGhpcy5iaW5kaW5nTGV2ZWwtaS5yZXRyaWV2YWxMZXZlbCxvPWkuZGVjbGFyZUxvY2FsQ2FsbGJhY2sodGhpcyxyLXQpO3JldHVybiB0PXIsZS5jb25jYXQobyl9LFtdKX1mcmVzaFJlZmVyZW5jZU5hbWUoKXtsZXQgdD10aGlzO2Zvcig7dC5wYXJlbnQ7KXQ9dC5wYXJlbnQ7cmV0dXJuIl9yIit0LnJlZmVyZW5jZU5hbWVJbmRleCsrfWhhc1Jlc3RvcmVWaWV3VmFyaWFibGUoKXtyZXR1cm4hIXRoaXMucmVzdG9yZVZpZXdWYXJpYWJsZX1ub3RpZnlSZXN0b3JlZFZpZXdDb250ZXh0VXNlKCl7dGhpcy51c2VzUmVzdG9yZWRWaWV3Q29udGV4dD0hMH19O2Z1bmN0aW9uIENRKG4pe3N3aXRjaCh3bShuKSl7Y2FzZSAxOnJldHVybiB0ZS5wcm9wZXJ0eUludGVycG9sYXRlO2Nhc2UgMzpyZXR1cm4gdGUucHJvcGVydHlJbnRlcnBvbGF0ZTE7Y2FzZSA1OnJldHVybiB0ZS5wcm9wZXJ0eUludGVycG9sYXRlMjtjYXNlIDc6cmV0dXJuIHRlLnByb3BlcnR5SW50ZXJwb2xhdGUzO2Nhc2UgOTpyZXR1cm4gdGUucHJvcGVydHlJbnRlcnBvbGF0ZTQ7Y2FzZSAxMTpyZXR1cm4gdGUucHJvcGVydHlJbnRlcnBvbGF0ZTU7Y2FzZSAxMzpyZXR1cm4gdGUucHJvcGVydHlJbnRlcnBvbGF0ZTY7Y2FzZSAxNTpyZXR1cm4gdGUucHJvcGVydHlJbnRlcnBvbGF0ZTc7Y2FzZSAxNzpyZXR1cm4gdGUucHJvcGVydHlJbnRlcnBvbGF0ZTg7ZGVmYXVsdDpyZXR1cm4gdGUucHJvcGVydHlJbnRlcnBvbGF0ZVZ9fWZ1bmN0aW9uIFMxZShuLHQsZT17fSl7bGV0e2ludGVycG9sYXRpb25Db25maWc6aSxwcmVzZXJ2ZVdoaXRlc3BhY2VzOnIsZW5hYmxlSTE4bkxlZ2FjeU1lc3NhZ2VJZEZvcm1hdDpvfT1lLHM9REQoaSksbD0obmV3IF9WKS5wYXJzZShuLHQse2xlYWRpbmdUcml2aWFDaGFyczpnMWUsLi4uZSx0b2tlbml6ZUV4cGFuc2lvbkZvcm1zOiEwfSk7aWYoIWUuYWx3YXlzQXR0ZW1wdEh0bWxUb1IzQXN0Q29udmVyc2lvbiYmbC5lcnJvcnMmJmwuZXJyb3JzLmxlbmd0aD4wKXtsZXQgRD17aW50ZXJwb2xhdGlvbkNvbmZpZzppLHByZXNlcnZlV2hpdGVzcGFjZXM6cixlcnJvcnM6bC5lcnJvcnMsbm9kZXM6W10sc3R5bGVVcmxzOltdLHN0eWxlczpbXSxuZ0NvbnRlbnRTZWxlY3RvcnM6W119O3JldHVybiBlLmNvbGxlY3RDb21tZW50Tm9kZXMmJihELmNvbW1lbnROb2Rlcz1bXSksRH1sZXQgYz1sLnJvb3ROb2Rlcyx1PW5ldyBFRChpLCFyLG8pLGQ9dS52aXNpdEFsbFdpdGhFcnJvcnMoYyk7aWYoIWUuYWx3YXlzQXR0ZW1wdEh0bWxUb1IzQXN0Q29udmVyc2lvbiYmZC5lcnJvcnMmJmQuZXJyb3JzLmxlbmd0aD4wKXtsZXQgRD17aW50ZXJwb2xhdGlvbkNvbmZpZzppLHByZXNlcnZlV2hpdGVzcGFjZXM6cixlcnJvcnM6ZC5lcnJvcnMsbm9kZXM6W10sc3R5bGVVcmxzOltdLHN0eWxlczpbXSxuZ0NvbnRlbnRTZWxlY3RvcnM6W119O3JldHVybiBlLmNvbGxlY3RDb21tZW50Tm9kZXMmJihELmNvbW1lbnROb2Rlcz1bXSksRH1jPWQucm9vdE5vZGVzLHJ8fChjPVV1KG5ldyBjbGFzc3t2aXNpdEVsZW1lbnQodCxlKXtyZXR1cm4gbEVlLmhhcyh0Lm5hbWUpfHx0LmF0dHJzLnNvbWUodD0+dC5uYW1lPT09Z0spP25ldyBxQyh0Lm5hbWUsVXUodGhpcyx0LmF0dHJzKSx0LmNoaWxkcmVuLHQuc291cmNlU3Bhbix0LnN0YXJ0U291cmNlU3Bhbix0LmVuZFNvdXJjZVNwYW4sdC5pMThuKTpuZXcgcUModC5uYW1lLHQuYXR0cnMsZnVuY3Rpb24obix0KXtsZXQgZT1bXTtyZXR1cm4gdC5mb3JFYWNoKChpLHIpPT57bGV0IHM9aS52aXNpdChuLHtwcmV2OnRbci0xXSxuZXh0OnRbcisxXX0pO3MmJmUucHVzaChzKX0pLGV9KHRoaXMsdC5jaGlsZHJlbiksdC5zb3VyY2VTcGFuLHQuc3RhcnRTb3VyY2VTcGFuLHQuZW5kU291cmNlU3Bhbix0LmkxOG4pfXZpc2l0QXR0cmlidXRlKHQsZSl7cmV0dXJuIHQubmFtZSE9PWdLP3Q6bnVsbH12aXNpdFRleHQodCxlKXtpZih0LnZhbHVlLm1hdGNoKGNFZSl8fGUmJihlLnByZXYgaW5zdGFuY2VvZiBOX3x8ZS5uZXh0IGluc3RhbmNlb2YgTl8pKXtsZXQgbz10LnRva2Vucy5tYXAoYT0+NT09PWEudHlwZT9mdW5jdGlvbih7dHlwZTpuLHBhcnRzOnQsc291cmNlU3BhbjplfSl7cmV0dXJue3R5cGU6bixwYXJ0czpbeUsodFswXSldLHNvdXJjZVNwYW46ZX19KGEpOmEpLHM9eUsodC52YWx1ZSk7cmV0dXJuIG5ldyBGXyhzLHQuc291cmNlU3BhbixvLHQuaTE4bil9cmV0dXJuIG51bGx9dmlzaXRDb21tZW50KHQsZSl7cmV0dXJuIHR9dmlzaXRFeHBhbnNpb24odCxlKXtyZXR1cm4gdH12aXNpdEV4cGFuc2lvbkNhc2UodCxlKXtyZXR1cm4gdH19LGMpLHUuaGFzSTE4bk1ldGEmJihjPVV1KG5ldyBFRChpLCExKSxjKSkpO2xldHtub2RlczpwLGVycm9yczpoLHN0eWxlVXJsczpmLHN0eWxlczptLG5nQ29udGVudFNlbGVjdG9yczp4LGNvbW1lbnROb2RlczpnfT1mdW5jdGlvbihuLHQsZSl7bGV0IGk9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5iaW5kaW5nUGFyc2VyPXQsdGhpcy5vcHRpb25zPWUsdGhpcy5lcnJvcnM9W10sdGhpcy5zdHlsZXM9W10sdGhpcy5zdHlsZVVybHM9W10sdGhpcy5uZ0NvbnRlbnRTZWxlY3RvcnM9W10sdGhpcy5jb21tZW50Tm9kZXM9W10sdGhpcy5pbkkxOG5CbG9jaz0hMX12aXNpdEVsZW1lbnQodCl7bGV0IGU9QUModC5pMThuKTtlJiYodGhpcy5pbkkxOG5CbG9jayYmdGhpcy5yZXBvcnRFcnJvcigiQ2Fubm90IG1hcmsgYW4gZWxlbWVudCBhcyB0cmFuc2xhdGFibGUgaW5zaWRlIG9mIGEgdHJhbnNsYXRhYmxlIHNlY3Rpb24uIFBsZWFzZSByZW1vdmUgdGhlIG5lc3RlZCBpMThuIG1hcmtlci4iLHQuc291cmNlU3BhbiksdGhpcy5pbkkxOG5CbG9jaz0hMCk7bGV0IGk9Q0sodCk7aWYoaS50eXBlPT09bmEuU0NSSVBUKXJldHVybiBudWxsO2lmKGkudHlwZT09PW5hLlNUWUxFKXtsZXQgeD0xPT09KG49dCkuY2hpbGRyZW4ubGVuZ3RoJiZuLmNoaWxkcmVuWzBdaW5zdGFuY2VvZiBGXz9uLmNoaWxkcmVuWzBdLnZhbHVlOm51bGw7cmV0dXJuIG51bGwhPT14JiZ0aGlzLnN0eWxlcy5wdXNoKHgpLG51bGx9aWYoaS50eXBlPT09bmEuU1RZTEVTSEVFVCYmZnVuY3Rpb24obil7aWYobnVsbD09bnx8MD09PW4ubGVuZ3RofHwiLyI9PW5bMF0pcmV0dXJuITE7bGV0IHQ9bi5tYXRjaChFRWUpO3JldHVybiBudWxsPT09dHx8InBhY2thZ2UiPT10WzFdfHwiYXNzZXQiPT10WzFdfShpLmhyZWZBdHRyKSlyZXR1cm4gdGhpcy5zdHlsZVVybHMucHVzaChpLmhyZWZBdHRyKSxudWxsO3ZhciBuO2xldCByPWZ1bmN0aW9uKG4pe3JldHVybiJuZy10ZW1wbGF0ZSI9PT1LZChuKVsxXX0odC5uYW1lKSxvPVtdLHM9W10sYT1bXSxsPVtdLGM9W10sdT17fSxkPVtdLHA9W10saD0hMTtmb3IobGV0IHggb2YgdC5hdHRycyl7bGV0IGc9ITEsYj1fUSh4Lm5hbWUpLEQ9ITE7aWYoeC5pMThuJiYodVt4Lm5hbWVdPXguaTE4biksYi5zdGFydHNXaXRoKCIqIikpe2gmJnRoaXMucmVwb3J0RXJyb3IoIkNhbid0IGhhdmUgbXVsdGlwbGUgdGVtcGxhdGUgYmluZGluZ3Mgb24gb25lIGVsZW1lbnQuIFVzZSBvbmx5IG9uZSBhdHRyaWJ1dGUgcHJlZml4ZWQgd2l0aCAqIix4LnNvdXJjZVNwYW4pLEQ9ITAsaD0hMDtsZXQgVD14LnZhbHVlLGs9Yi5zdWJzdHJpbmcoIioiLmxlbmd0aCksWj1bXTt0aGlzLmJpbmRpbmdQYXJzZXIucGFyc2VJbmxpbmVUZW1wbGF0ZUJpbmRpbmcoayxULHguc291cmNlU3Bhbix4LnZhbHVlU3Bhbj94LnZhbHVlU3Bhbi5zdGFydC5vZmZzZXQ6eC5zb3VyY2VTcGFuLnN0YXJ0Lm9mZnNldCt4Lm5hbWUubGVuZ3RoLFtdLGQsWiwhMCkscC5wdXNoKC4uLloubWFwKGZlPT5uZXcgb0QoZmUubmFtZSxmZS52YWx1ZSxmZS5zb3VyY2VTcGFuLGZlLmtleVNwYW4sZmUudmFsdWVTcGFuKSkpfWVsc2UgZz10aGlzLnBhcnNlQXR0cmlidXRlKHIseCxbXSxvLHMsYSxsKTshZyYmIUQmJmMucHVzaCh0aGlzLnZpc2l0QXR0cmlidXRlKHgpKX1sZXQgbSxmPVV1KGkubm9uQmluZGFibGU/SEVlOnRoaXMsdC5jaGlsZHJlbik7aWYoaS50eXBlPT09bmEuTkdfQ09OVEVOVCl7dC5jaGlsZHJlbiYmIXQuY2hpbGRyZW4uZXZlcnkoYj0+ZnVuY3Rpb24obil7cmV0dXJuIG4gaW5zdGFuY2VvZiBGXyYmMD09bi52YWx1ZS50cmltKCkubGVuZ3RofShiKXx8ZnVuY3Rpb24obil7cmV0dXJuIG4gaW5zdGFuY2VvZiB4RH0oYikpJiZ0aGlzLnJlcG9ydEVycm9yKCI8bmctY29udGVudD4gZWxlbWVudCBjYW5ub3QgaGF2ZSBjb250ZW50LiIsdC5zb3VyY2VTcGFuKTtsZXQgeD1pLnNlbGVjdEF0dHIsZz10LmF0dHJzLm1hcChiPT50aGlzLnZpc2l0QXR0cmlidXRlKGIpKTttPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXt0aGlzLnNlbGVjdG9yPXQsdGhpcy5hdHRyaWJ1dGVzPWUsdGhpcy5zb3VyY2VTcGFuPWksdGhpcy5pMThuPXIsdGhpcy5uYW1lPSJuZy1jb250ZW50In12aXNpdCh0KXtyZXR1cm4gdC52aXNpdENvbnRlbnQodGhpcyl9fSh4LGcsdC5zb3VyY2VTcGFuLHQuaTE4biksdGhpcy5uZ0NvbnRlbnRTZWxlY3RvcnMucHVzaCh4KX1lbHNlIGlmKHIpe2xldCB4PXRoaXMuZXh0cmFjdEF0dHJpYnV0ZXModC5uYW1lLG8sdSk7bT1uZXcgdUModC5uYW1lLGMseC5ib3VuZCxzLFtdLGYsbCxhLHQuc291cmNlU3Bhbix0LnN0YXJ0U291cmNlU3Bhbix0LmVuZFNvdXJjZVNwYW4sdC5pMThuKX1lbHNle2xldCB4PXRoaXMuZXh0cmFjdEF0dHJpYnV0ZXModC5uYW1lLG8sdSk7bT1uZXcgRV8odC5uYW1lLGMseC5ib3VuZCxzLGYsbCx0LnNvdXJjZVNwYW4sdC5zdGFydFNvdXJjZVNwYW4sdC5lbmRTb3VyY2VTcGFuLHQuaTE4bil9aWYoaCl7bGV0IHg9dGhpcy5leHRyYWN0QXR0cmlidXRlcygibmctdGVtcGxhdGUiLGQsdSksZz1bXTt4LmxpdGVyYWwuZm9yRWFjaChrPT5nLnB1c2goaykpLHguYm91bmQuZm9yRWFjaChrPT5nLnB1c2goaykpO2xldCBiPW0gaW5zdGFuY2VvZiBFXz97YXR0cmlidXRlczptLmF0dHJpYnV0ZXMsaW5wdXRzOm0uaW5wdXRzLG91dHB1dHM6bS5vdXRwdXRzfTp7YXR0cmlidXRlczpbXSxpbnB1dHM6W10sb3V0cHV0czpbXX07bT1uZXcgdUMobSBpbnN0YW5jZW9mIHVDP251bGw6bS5uYW1lLGIuYXR0cmlidXRlcyxiLmlucHV0cyxiLm91dHB1dHMsZyxbbV0sW10scCx0LnNvdXJjZVNwYW4sdC5zdGFydFNvdXJjZVNwYW4sdC5lbmRTb3VyY2VTcGFuLHImJmU/dm9pZCAwOnQuaTE4bil9cmV0dXJuIGUmJih0aGlzLmluSTE4bkJsb2NrPSExKSxtfXZpc2l0QXR0cmlidXRlKHQpe3JldHVybiBuZXcgREModC5uYW1lLHQudmFsdWUsdC5zb3VyY2VTcGFuLHQua2V5U3Bhbix0LnZhbHVlU3Bhbix0LmkxOG4pfXZpc2l0VGV4dCh0KXtyZXR1cm4gdGhpcy5fdmlzaXRUZXh0V2l0aEludGVycG9sYXRpb24odC52YWx1ZSx0LnNvdXJjZVNwYW4sdC50b2tlbnMsdC5pMThuKX12aXNpdEV4cGFuc2lvbih0KXtpZighdC5pMThuKXJldHVybiBudWxsO2lmKCFBQyh0LmkxOG4pKXRocm93IG5ldyBFcnJvcihgSW52YWxpZCB0eXBlICIke3QuaTE4bi5jb25zdHJ1Y3Rvcn0iIGZvciAiaTE4biIgcHJvcGVydHkgb2YgJHt0LnNvdXJjZVNwYW4udG9TdHJpbmcoKX0uIEV4cGVjdGVkIGEgIk1lc3NhZ2UiYCk7bGV0IGU9dC5pMThuLGk9e30scj17fTtyZXR1cm4gT2JqZWN0LmtleXMoZS5wbGFjZWhvbGRlcnMpLmZvckVhY2gobz0+e2xldCBzPWUucGxhY2Vob2xkZXJzW29dO2lmKG8uc3RhcnRzV2l0aCgiVkFSXyIpKXtsZXQgYT1vLnRyaW0oKSxsPXRoaXMuYmluZGluZ1BhcnNlci5wYXJzZUludGVycG9sYXRpb25FeHByZXNzaW9uKHMudGV4dCxzLnNvdXJjZVNwYW4pO2lbYV09bmV3IFRDKGwscy5zb3VyY2VTcGFuKX1lbHNlIHJbb109dGhpcy5fdmlzaXRUZXh0V2l0aEludGVycG9sYXRpb24ocy50ZXh0LHMuc291cmNlU3BhbixudWxsKX0pLG5ldyBzRChpLHIsdC5zb3VyY2VTcGFuLGUpfXZpc2l0RXhwYW5zaW9uQ2FzZSh0KXtyZXR1cm4gbnVsbH12aXNpdENvbW1lbnQodCl7cmV0dXJuIHRoaXMub3B0aW9ucy5jb2xsZWN0Q29tbWVudE5vZGVzJiZ0aGlzLmNvbW1lbnROb2Rlcy5wdXNoKG5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMudmFsdWU9dCx0aGlzLnNvdXJjZVNwYW49ZX12aXNpdCh0KXt0aHJvdyBuZXcgRXJyb3IoInZpc2l0KCkgbm90IGltcGxlbWVudGVkIGZvciBDb21tZW50Iil9fSh0LnZhbHVlfHwiIix0LnNvdXJjZVNwYW4pKSxudWxsfWV4dHJhY3RBdHRyaWJ1dGVzKHQsZSxpKXtsZXQgcj1bXSxvPVtdO3JldHVybiBlLmZvckVhY2gocz0+e2xldCBhPWlbcy5uYW1lXTtpZihzLmlzTGl0ZXJhbClvLnB1c2gobmV3IERDKHMubmFtZSxzLmV4cHJlc3Npb24uc291cmNlfHwiIixzLnNvdXJjZVNwYW4scy5rZXlTcGFuLHMudmFsdWVTcGFuLGEpKTtlbHNle2xldCBsPXRoaXMuYmluZGluZ1BhcnNlci5jcmVhdGVCb3VuZEVsZW1lbnRQcm9wZXJ0eSh0LHMsITAsITEpO3IucHVzaCh3Xy5mcm9tQm91bmRFbGVtZW50UHJvcGVydHkobCxhKSl9fSkse2JvdW5kOnIsbGl0ZXJhbDpvfX1wYXJzZUF0dHJpYnV0ZSh0LGUsaSxyLG8scyxhKXtsZXQgbD1fUShlLm5hbWUpLGM9ZS52YWx1ZSx1PWUuc291cmNlU3BhbixkPWUudmFsdWVTcGFuP2UudmFsdWVTcGFuLnN0YXJ0Lm9mZnNldDp1LnN0YXJ0Lm9mZnNldDtmdW5jdGlvbiBwKGcsYixEKXtsZXQgaz1nLnN0YXJ0Lm1vdmVCeShiLmxlbmd0aCsoZS5uYW1lLmxlbmd0aC1sLmxlbmd0aCkpLFo9ay5tb3ZlQnkoRC5sZW5ndGgpO3JldHVybiBuZXcgR28oayxaLGssRCl9bGV0IGg9bC5tYXRjaChMRWUpO2lmKGgpe2lmKG51bGwhPWhbMV0pe2xldCBnPWhbN10sYj1wKHUsaFsxXSxnKTt0aGlzLmJpbmRpbmdQYXJzZXIucGFyc2VQcm9wZXJ0eUJpbmRpbmcoZyxjLCExLHUsZCxlLnZhbHVlU3BhbixpLHIsYil9ZWxzZSBpZihoWzJdKWlmKHQpe2xldCBnPWhbN10sYj1wKHUsaFsyXSxnKTt0aGlzLnBhcnNlVmFyaWFibGUoZyxjLHUsYixlLnZhbHVlU3BhbixzKX1lbHNlIHRoaXMucmVwb3J0RXJyb3IoJyJsZXQtIiBpcyBvbmx5IHN1cHBvcnRlZCBvbiBuZy10ZW1wbGF0ZSBlbGVtZW50cy4nLHUpO2Vsc2UgaWYoaFszXSl7bGV0IGc9aFs3XSxiPXAodSxoWzNdLGcpO3RoaXMucGFyc2VSZWZlcmVuY2UoZyxjLHUsYixlLnZhbHVlU3BhbixhKX1lbHNlIGlmKGhbNF0pe2xldCBnPVtdLGI9aFs3XSxEPXAodSxoWzRdLGIpO3RoaXMuYmluZGluZ1BhcnNlci5wYXJzZUV2ZW50KGIsYywhMSx1LGUudmFsdWVTcGFufHx1LGksZyxEKSxtQihnLG8pfWVsc2UgaWYoaFs1XSl7bGV0IGc9aFs3XSxiPXAodSxoWzVdLGcpO3RoaXMuYmluZGluZ1BhcnNlci5wYXJzZVByb3BlcnR5QmluZGluZyhnLGMsITEsdSxkLGUudmFsdWVTcGFuLGkscixiKSx0aGlzLnBhcnNlQXNzaWdubWVudEV2ZW50KGcsYyx1LGUudmFsdWVTcGFuLGksbyxiKX1lbHNlIGlmKGhbNl0pe2xldCBnPXAodSwiIixsKTt0aGlzLmJpbmRpbmdQYXJzZXIucGFyc2VMaXRlcmFsQXR0cihsLGMsdSxkLGUudmFsdWVTcGFuLGkscixnKX1yZXR1cm4hMH1sZXQgZj1udWxsO2lmKGwuc3RhcnRzV2l0aChacF9CQU5BTkFfQk9YLnN0YXJ0KT9mPVpwX0JBTkFOQV9CT1g6bC5zdGFydHNXaXRoKFpwX1BST1BFUlRZLnN0YXJ0KT9mPVpwX1BST1BFUlRZOmwuc3RhcnRzV2l0aChacF9FVkVOVC5zdGFydCkmJihmPVpwX0VWRU5UKSxudWxsIT09ZiYmbC5lbmRzV2l0aChmLmVuZCkmJmwubGVuZ3RoPmYuc3RhcnQubGVuZ3RoK2YuZW5kLmxlbmd0aCl7bGV0IGc9bC5zdWJzdHJpbmcoZi5zdGFydC5sZW5ndGgsbC5sZW5ndGgtZi5lbmQubGVuZ3RoKSxiPXAodSxmLnN0YXJ0LGcpO2lmKGYuc3RhcnQ9PT1acF9CQU5BTkFfQk9YLnN0YXJ0KXRoaXMuYmluZGluZ1BhcnNlci5wYXJzZVByb3BlcnR5QmluZGluZyhnLGMsITEsdSxkLGUudmFsdWVTcGFuLGkscixiKSx0aGlzLnBhcnNlQXNzaWdubWVudEV2ZW50KGcsYyx1LGUudmFsdWVTcGFuLGksbyxiKTtlbHNlIGlmKGYuc3RhcnQ9PT1acF9QUk9QRVJUWS5zdGFydCl0aGlzLmJpbmRpbmdQYXJzZXIucGFyc2VQcm9wZXJ0eUJpbmRpbmcoZyxjLCExLHUsZCxlLnZhbHVlU3BhbixpLHIsYik7ZWxzZXtsZXQgRD1bXTt0aGlzLmJpbmRpbmdQYXJzZXIucGFyc2VFdmVudChnLGMsITEsdSxlLnZhbHVlU3Bhbnx8dSxpLEQsYiksbUIoRCxvKX1yZXR1cm4hMH1sZXQgbT1wKHUsIiIsbCk7cmV0dXJuIHRoaXMuYmluZGluZ1BhcnNlci5wYXJzZVByb3BlcnR5SW50ZXJwb2xhdGlvbihsLGMsdSxlLnZhbHVlU3BhbixpLHIsbSxlLnZhbHVlVG9rZW5zPz9udWxsKX1fdmlzaXRUZXh0V2l0aEludGVycG9sYXRpb24odCxlLGkscil7bGV0IG89dksodCkscz10aGlzLmJpbmRpbmdQYXJzZXIucGFyc2VJbnRlcnBvbGF0aW9uKG8sZSxpKTtyZXR1cm4gcz9uZXcgVEMocyxlLHIpOm5ldyBNXyhvLGUpfXBhcnNlVmFyaWFibGUodCxlLGkscixvLHMpe3QuaW5kZXhPZigiLSIpPi0xP3RoaXMucmVwb3J0RXJyb3IoJyItIiBpcyBub3QgYWxsb3dlZCBpbiB2YXJpYWJsZSBuYW1lcycsaSk6MD09PXQubGVuZ3RoJiZ0aGlzLnJlcG9ydEVycm9yKCJWYXJpYWJsZSBkb2VzIG5vdCBoYXZlIGEgbmFtZSIsaSkscy5wdXNoKG5ldyBvRCh0LGUsaSxyLG8pKX1wYXJzZVJlZmVyZW5jZSh0LGUsaSxyLG8scyl7dC5pbmRleE9mKCItIik+LTE/dGhpcy5yZXBvcnRFcnJvcignIi0iIGlzIG5vdCBhbGxvd2VkIGluIHJlZmVyZW5jZSBuYW1lcycsaSk6MD09PXQubGVuZ3RoP3RoaXMucmVwb3J0RXJyb3IoIlJlZmVyZW5jZSBkb2VzIG5vdCBoYXZlIGEgbmFtZSIsaSk6cy5zb21lKGE9PmEubmFtZT09PXQpJiZ0aGlzLnJlcG9ydEVycm9yKGBSZWZlcmVuY2UgIiMke3R9IiBpcyBkZWZpbmVkIG1vcmUgdGhhbiBvbmNlYCxpKSxzLnB1c2gobmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7dGhpcy5uYW1lPXQsdGhpcy52YWx1ZT1lLHRoaXMuc291cmNlU3Bhbj1pLHRoaXMua2V5U3Bhbj1yLHRoaXMudmFsdWVTcGFuPW99dmlzaXQodCl7cmV0dXJuIHQudmlzaXRSZWZlcmVuY2UodGhpcyl9fSh0LGUsaSxyLG8pKX1wYXJzZUFzc2lnbm1lbnRFdmVudCh0LGUsaSxyLG8scyxhKXtsZXQgbD1bXTt0aGlzLmJpbmRpbmdQYXJzZXIucGFyc2VFdmVudChgJHt0fUNoYW5nZWAsYCR7ZX0gPSRldmVudGAsITAsaSxyfHxpLG8sbCxhKSxtQihsLHMpfXJlcG9ydEVycm9yKHQsZSxpPWt1LkVSUk9SKXt0aGlzLmVycm9ycy5wdXNoKG5ldyB5bShlLHQsaSkpfX0odCxlKSxzPXtub2RlczpVdShpLG4pLGVycm9yczp0LmVycm9ycy5jb25jYXQoaS5lcnJvcnMpLHN0eWxlVXJsczppLnN0eWxlVXJscyxzdHlsZXM6aS5zdHlsZXMsbmdDb250ZW50U2VsZWN0b3JzOmkubmdDb250ZW50U2VsZWN0b3JzfTtyZXR1cm4gZS5jb2xsZWN0Q29tbWVudE5vZGVzJiYocy5jb21tZW50Tm9kZXM9aS5jb21tZW50Tm9kZXMpLHN9KGMscyx7Y29sbGVjdENvbW1lbnROb2RlczohIWUuY29sbGVjdENvbW1lbnROb2Rlc30pO2gucHVzaCguLi5sLmVycm9ycywuLi5kLmVycm9ycyk7bGV0IGI9e2ludGVycG9sYXRpb25Db25maWc6aSxwcmVzZXJ2ZVdoaXRlc3BhY2VzOnIsZXJyb3JzOmgubGVuZ3RoPjA/aDpudWxsLG5vZGVzOnAsc3R5bGVVcmxzOmYsc3R5bGVzOm0sbmdDb250ZW50U2VsZWN0b3JzOnh9O3JldHVybiBlLmNvbGxlY3RDb21tZW50Tm9kZXMmJihiLmNvbW1lbnROb2Rlcz1nKSxifXZhciBFSz1uZXcgU0Q7ZnVuY3Rpb24gREQobj1QdSl7cmV0dXJuIG5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXt0aGlzLl9leHByUGFyc2VyPXQsdGhpcy5faW50ZXJwb2xhdGlvbkNvbmZpZz1lLHRoaXMuX3NjaGVtYVJlZ2lzdHJ5PWksdGhpcy5lcnJvcnM9cn1nZXQgaW50ZXJwb2xhdGlvbkNvbmZpZygpe3JldHVybiB0aGlzLl9pbnRlcnBvbGF0aW9uQ29uZmlnfWNyZWF0ZUJvdW5kSG9zdFByb3BlcnRpZXModCxlKXtsZXQgaT1bXTtmb3IobGV0IHIgb2YgT2JqZWN0LmtleXModCkpe2xldCBvPXRbcl07InN0cmluZyI9PXR5cGVvZiBvP3RoaXMucGFyc2VQcm9wZXJ0eUJpbmRpbmcocixvLCEwLGUsZS5zdGFydC5vZmZzZXQsdm9pZCAwLFtdLGksZSk6dGhpcy5fcmVwb3J0RXJyb3IoYFZhbHVlIG9mIHRoZSBob3N0IHByb3BlcnR5IGJpbmRpbmcgIiR7cn0iIG5lZWRzIHRvIGJlIGEgc3RyaW5nIHJlcHJlc2VudGluZyBhbiBleHByZXNzaW9uIGJ1dCBnb3QgIiR7b30iICgke3R5cGVvZiBvfSlgLGUpfXJldHVybiBpfWNyZWF0ZURpcmVjdGl2ZUhvc3RFdmVudEFzdHModCxlKXtsZXQgaT1bXTtmb3IobGV0IHIgb2YgT2JqZWN0LmtleXModCkpe2xldCBvPXRbcl07InN0cmluZyI9PXR5cGVvZiBvP3RoaXMucGFyc2VFdmVudChyLG8sITEsZSxlLFtdLGksZSk6dGhpcy5fcmVwb3J0RXJyb3IoYFZhbHVlIG9mIHRoZSBob3N0IGxpc3RlbmVyICIke3J9IiBuZWVkcyB0byBiZSBhIHN0cmluZyByZXByZXNlbnRpbmcgYW4gZXhwcmVzc2lvbiBidXQgZ290ICIke299IiAoJHt0eXBlb2Ygb30pYCxlKX1yZXR1cm4gaX1wYXJzZUludGVycG9sYXRpb24odCxlLGkpe2xldCByPWUuc3RhcnQudG9TdHJpbmcoKSxvPWUuZnVsbFN0YXJ0Lm9mZnNldDt0cnl7bGV0IHM9dGhpcy5fZXhwclBhcnNlci5wYXJzZUludGVycG9sYXRpb24odCxyLG8saSx0aGlzLl9pbnRlcnBvbGF0aW9uQ29uZmlnKTtyZXR1cm4gcyYmdGhpcy5fcmVwb3J0RXhwcmVzc2lvblBhcnNlckVycm9ycyhzLmVycm9ycyxlKSxzfWNhdGNoKHMpe3JldHVybiB0aGlzLl9yZXBvcnRFcnJvcihgJHtzfWAsZSksdGhpcy5fZXhwclBhcnNlci53cmFwTGl0ZXJhbFByaW1pdGl2ZSgiRVJST1IiLHIsbyl9fXBhcnNlSW50ZXJwb2xhdGlvbkV4cHJlc3Npb24odCxlKXtsZXQgaT1lLnN0YXJ0LnRvU3RyaW5nKCkscj1lLnN0YXJ0Lm9mZnNldDt0cnl7bGV0IG89dGhpcy5fZXhwclBhcnNlci5wYXJzZUludGVycG9sYXRpb25FeHByZXNzaW9uKHQsaSxyKTtyZXR1cm4gbyYmdGhpcy5fcmVwb3J0RXhwcmVzc2lvblBhcnNlckVycm9ycyhvLmVycm9ycyxlKSxvfWNhdGNoKG8pe3JldHVybiB0aGlzLl9yZXBvcnRFcnJvcihgJHtvfWAsZSksdGhpcy5fZXhwclBhcnNlci53cmFwTGl0ZXJhbFByaW1pdGl2ZSgiRVJST1IiLGkscil9fXBhcnNlSW5saW5lVGVtcGxhdGVCaW5kaW5nKHQsZSxpLHIsbyxzLGEsbCl7bGV0IHU9dGhpcy5fcGFyc2VUZW1wbGF0ZUJpbmRpbmdzKHQsZSxpLGkuc3RhcnQub2Zmc2V0KyIqIi5sZW5ndGgscik7Zm9yKGxldCBkIG9mIHUpe2xldCBwPUtwKGksZC5zb3VyY2VTcGFuKSxoPWQua2V5LnNvdXJjZSxmPUtwKGksZC5rZXkuc3Bhbik7aWYoZCBpbnN0YW5jZW9mIHpDKXtsZXQgbT1kLnZhbHVlP2QudmFsdWUuc291cmNlOiIkaW1wbGljaXQiLHg9ZC52YWx1ZT9LcChpLGQudmFsdWUuc3Bhbik6dm9pZCAwO2EucHVzaChuZXcgZVYoaCxtLHAsZix4KSl9ZWxzZSBpZihkLnZhbHVlKXtsZXQgbT1sP3A6aSx4PUtwKGksZC52YWx1ZS5hc3Quc291cmNlU3Bhbik7dGhpcy5fcGFyc2VQcm9wZXJ0eUFzdChoLGQudmFsdWUsbSxmLHgsbyxzKX1lbHNlIG8ucHVzaChbaCwiIl0pLHRoaXMucGFyc2VMaXRlcmFsQXR0cihoLG51bGwsZixyLHZvaWQgMCxvLHMsZil9fV9wYXJzZVRlbXBsYXRlQmluZGluZ3ModCxlLGkscixvKXtsZXQgcz1pLnN0YXJ0LnRvU3RyaW5nKCk7dHJ5e2xldCBhPXRoaXMuX2V4cHJQYXJzZXIucGFyc2VUZW1wbGF0ZUJpbmRpbmdzKHQsZSxzLHIsbyk7cmV0dXJuIHRoaXMuX3JlcG9ydEV4cHJlc3Npb25QYXJzZXJFcnJvcnMoYS5lcnJvcnMsaSksYS53YXJuaW5ncy5mb3JFYWNoKGw9Pnt0aGlzLl9yZXBvcnRFcnJvcihsLGksa3UuV0FSTklORyl9KSxhLnRlbXBsYXRlQmluZGluZ3N9Y2F0Y2goYSl7cmV0dXJuIHRoaXMuX3JlcG9ydEVycm9yKGAke2F9YCxpKSxbXX19cGFyc2VMaXRlcmFsQXR0cih0LGUsaSxyLG8scyxhLGwpe2hCKHQpPyh0PXQuc3Vic3RyaW5nKDEpLHZvaWQgMCE9PWwmJihsPUtwKGwsbmV3IGFsKGwuc3RhcnQub2Zmc2V0KzEsbC5lbmQub2Zmc2V0KSkpLGUmJnRoaXMuX3JlcG9ydEVycm9yKCdBc3NpZ25pbmcgYW5pbWF0aW9uIHRyaWdnZXJzIHZpYSBAcHJvcD0iZXhwIiBhdHRyaWJ1dGVzIHdpdGggYW4gZXhwcmVzc2lvbiBpcyBpbnZhbGlkLiBVc2UgcHJvcGVydHkgYmluZGluZ3MgKGUuZy4gW0Bwcm9wXT0iZXhwIikgb3IgdXNlIGFuIGF0dHJpYnV0ZSB3aXRob3V0IGEgdmFsdWUgKGUuZy4gQHByb3ApIGluc3RlYWQuJyxpLGt1LkVSUk9SKSx0aGlzLl9wYXJzZUFuaW1hdGlvbih0LGUsaSxyLGwsbyxzLGEpKTphLnB1c2gobmV3IHBDKHQsdGhpcy5fZXhwclBhcnNlci53cmFwTGl0ZXJhbFByaW1pdGl2ZShlLCIiLHIpLGloLkxJVEVSQUxfQVRUUixpLGwsbykpfXBhcnNlUHJvcGVydHlCaW5kaW5nKHQsZSxpLHIsbyxzLGEsbCxjKXswPT09dC5sZW5ndGgmJnRoaXMuX3JlcG9ydEVycm9yKCJQcm9wZXJ0eSBuYW1lIGlzIG1pc3NpbmcgaW4gYmluZGluZyIscik7bGV0IHU9ITE7dC5zdGFydHNXaXRoKHBCKT8odT0hMCx0PXQuc3Vic3RyaW5nKHBCLmxlbmd0aCksdm9pZCAwIT09YyYmKGM9S3AoYyxuZXcgYWwoYy5zdGFydC5vZmZzZXQrcEIubGVuZ3RoLGMuZW5kLm9mZnNldCkpKSk6aEIodCkmJih1PSEwLHQ9dC5zdWJzdHJpbmcoMSksdm9pZCAwIT09YyYmKGM9S3AoYyxuZXcgYWwoYy5zdGFydC5vZmZzZXQrMSxjLmVuZC5vZmZzZXQpKSkpLHU/dGhpcy5fcGFyc2VBbmltYXRpb24odCxlLHIsbyxjLHMsYSxsKTp0aGlzLl9wYXJzZVByb3BlcnR5QXN0KHQsdGhpcy5fcGFyc2VCaW5kaW5nKGUsaSxzfHxyLG8pLHIsYyxzLGEsbCl9cGFyc2VQcm9wZXJ0eUludGVycG9sYXRpb24odCxlLGkscixvLHMsYSxsKXtsZXQgYz10aGlzLnBhcnNlSW50ZXJwb2xhdGlvbihlLHJ8fGksbCk7cmV0dXJuISFjJiYodGhpcy5fcGFyc2VQcm9wZXJ0eUFzdCh0LGMsaSxhLHIsbyxzKSwhMCl9X3BhcnNlUHJvcGVydHlBc3QodCxlLGkscixvLHMsYSl7cy5wdXNoKFt0LGUuc291cmNlXSksYS5wdXNoKG5ldyBwQyh0LGUsaWguREVGQVVMVCxpLHIsbykpfV9wYXJzZUFuaW1hdGlvbih0LGUsaSxyLG8scyxhLGwpezA9PT10Lmxlbmd0aCYmdGhpcy5fcmVwb3J0RXJyb3IoIkFuaW1hdGlvbiB0cmlnZ2VyIGlzIG1pc3NpbmciLGkpO2xldCBjPXRoaXMuX3BhcnNlQmluZGluZyhlfHwidW5kZWZpbmVkIiwhMSxzfHxpLHIpO2EucHVzaChbdCxjLnNvdXJjZV0pLGwucHVzaChuZXcgcEModCxjLGloLkFOSU1BVElPTixpLG8scykpfV9wYXJzZUJpbmRpbmcodCxlLGkscil7bGV0IG89KGkmJmkuc3RhcnR8fCIodW5rbm93bikiKS50b1N0cmluZygpO3RyeXtsZXQgcz1lP3RoaXMuX2V4cHJQYXJzZXIucGFyc2VTaW1wbGVCaW5kaW5nKHQsbyxyLHRoaXMuX2ludGVycG9sYXRpb25Db25maWcpOnRoaXMuX2V4cHJQYXJzZXIucGFyc2VCaW5kaW5nKHQsbyxyLHRoaXMuX2ludGVycG9sYXRpb25Db25maWcpO3JldHVybiBzJiZ0aGlzLl9yZXBvcnRFeHByZXNzaW9uUGFyc2VyRXJyb3JzKHMuZXJyb3JzLGkpLHN9Y2F0Y2gocyl7cmV0dXJuIHRoaXMuX3JlcG9ydEVycm9yKGAke3N9YCxpKSx0aGlzLl9leHByUGFyc2VyLndyYXBMaXRlcmFsUHJpbWl0aXZlKCJFUlJPUiIsbyxyKX19Y3JlYXRlQm91bmRFbGVtZW50UHJvcGVydHkodCxlLGk9ITEscj0hMCl7aWYoZS5pc0FuaW1hdGlvbilyZXR1cm4gbmV3IGZEKGUubmFtZSw0LGlvLk5PTkUsZS5leHByZXNzaW9uLG51bGwsZS5zb3VyY2VTcGFuLGUua2V5U3BhbixlLnZhbHVlU3Bhbik7bGV0IHMsYyxvPW51bGwsYT1udWxsLGw9ZS5uYW1lLnNwbGl0KCIuIik7aWYobC5sZW5ndGg+MSlpZigiYXR0ciI9PWxbMF0pe2E9bC5zbGljZSgxKS5qb2luKCIuIiksaXx8dGhpcy5fdmFsaWRhdGVQcm9wZXJ0eU9yQXR0cmlidXRlTmFtZShhLGUuc291cmNlU3BhbiwhMCksYz1mQih0aGlzLl9zY2hlbWFSZWdpc3RyeSx0LGEsITApO2xldCB1PWEuaW5kZXhPZigiOiIpO2lmKHU+LTEpe2xldCBkPWEuc3Vic3RyaW5nKDAsdSkscD1hLnN1YnN0cmluZyh1KzEpO2E9d0IoZCxwKX1zPTF9ZWxzZSJjbGFzcyI9PWxbMF0/KGE9bFsxXSxzPTIsYz1baW8uTk9ORV0pOiJzdHlsZSI9PWxbMF0mJihvPWwubGVuZ3RoPjI/bFsyXTpudWxsLGE9bFsxXSxzPTMsYz1baW8uU1RZTEVdKTtpZihudWxsPT09YSl7bGV0IHU9dGhpcy5fc2NoZW1hUmVnaXN0cnkuZ2V0TWFwcGVkUHJvcE5hbWUoZS5uYW1lKTthPXI/dTplLm5hbWUsYz1mQih0aGlzLl9zY2hlbWFSZWdpc3RyeSx0LHUsITEpLHM9MCxpfHx0aGlzLl92YWxpZGF0ZVByb3BlcnR5T3JBdHRyaWJ1dGVOYW1lKHUsZS5zb3VyY2VTcGFuLCExKX1yZXR1cm4gbmV3IGZEKGEscyxjWzBdLGUuZXhwcmVzc2lvbixvLGUuc291cmNlU3BhbixlLmtleVNwYW4sZS52YWx1ZVNwYW4pfXBhcnNlRXZlbnQodCxlLGkscixvLHMsYSxsKXswPT09dC5sZW5ndGgmJnRoaXMuX3JlcG9ydEVycm9yKCJFdmVudCBuYW1lIGlzIG1pc3NpbmcgaW4gYmluZGluZyIsciksaEIodCk/KHQ9dC5zbGljZSgxKSx2b2lkIDAhPT1sJiYobD1LcChsLG5ldyBhbChsLnN0YXJ0Lm9mZnNldCsxLGwuZW5kLm9mZnNldCkpKSx0aGlzLl9wYXJzZUFuaW1hdGlvbkV2ZW50KHQsZSxpLHIsbyxhLGwpKTp0aGlzLl9wYXJzZVJlZ3VsYXJFdmVudCh0LGUsaSxyLG8scyxhLGwpfWNhbGNQb3NzaWJsZVNlY3VyaXR5Q29udGV4dHModCxlLGkpe2xldCByPXRoaXMuX3NjaGVtYVJlZ2lzdHJ5LmdldE1hcHBlZFByb3BOYW1lKGUpO3JldHVybiBmQih0aGlzLl9zY2hlbWFSZWdpc3RyeSx0LHIsaSl9X3BhcnNlQW5pbWF0aW9uRXZlbnQodCxlLGkscixvLHMsYSl7bGV0IGw9ZnVuY3Rpb24obix0KXtyZXR1cm4gT1EobiwiLiIsdCl9KHQsW3QsIiJdKSxjPWxbMF0sdT1sWzFdLnRvTG93ZXJDYXNlKCksZD10aGlzLl9wYXJzZUFjdGlvbihlLGksbyk7cy5wdXNoKG5ldyBoRChjLHUsMSxkLHIsbyxhKSksMD09PWMubGVuZ3RoJiZ0aGlzLl9yZXBvcnRFcnJvcigiQW5pbWF0aW9uIGV2ZW50IG5hbWUgaXMgbWlzc2luZyBpbiBiaW5kaW5nIixyKSx1PyJzdGFydCIhPT11JiYiZG9uZSIhPT11JiZ0aGlzLl9yZXBvcnRFcnJvcihgVGhlIHByb3ZpZGVkIGFuaW1hdGlvbiBvdXRwdXQgcGhhc2UgdmFsdWUgIiR7dX0iIGZvciAiQCR7Y30iIGlzIG5vdCBzdXBwb3J0ZWQgKHVzZSBzdGFydCBvciBkb25lKWAscik6dGhpcy5fcmVwb3J0RXJyb3IoYFRoZSBhbmltYXRpb24gdHJpZ2dlciBvdXRwdXQgZXZlbnQgKEAke2N9KSBpcyBtaXNzaW5nIGl0cyBwaGFzZSB2YWx1ZSBuYW1lIChzdGFydCBvciBkb25lIGFyZSBjdXJyZW50bHkgc3VwcG9ydGVkKWAscil9X3BhcnNlUmVndWxhckV2ZW50KHQsZSxpLHIsbyxzLGEsbCl7bGV0W2MsdV09ZnVuY3Rpb24obix0KXtyZXR1cm4gT1EobiwiOiIsdCl9KHQsW251bGwsdF0pLGQ9dGhpcy5fcGFyc2VBY3Rpb24oZSxpLG8pO3MucHVzaChbdCxkLnNvdXJjZV0pLGEucHVzaChuZXcgaEQodSxjLDAsZCxyLG8sbCkpfV9wYXJzZUFjdGlvbih0LGUsaSl7bGV0IHI9KGkmJmkuc3RhcnR8fCIodW5rbm93biIpLnRvU3RyaW5nKCksbz1pJiZpLnN0YXJ0P2kuc3RhcnQub2Zmc2V0OjA7dHJ5e2xldCBzPXRoaXMuX2V4cHJQYXJzZXIucGFyc2VBY3Rpb24odCxlLHIsbyx0aGlzLl9pbnRlcnBvbGF0aW9uQ29uZmlnKTtyZXR1cm4gcyYmdGhpcy5fcmVwb3J0RXhwcmVzc2lvblBhcnNlckVycm9ycyhzLmVycm9ycyxpKSwhc3x8cy5hc3QgaW5zdGFuY2VvZiBJYT8odGhpcy5fcmVwb3J0RXJyb3IoIkVtcHR5IGV4cHJlc3Npb25zIGFyZSBub3QgYWxsb3dlZCIsaSksdGhpcy5fZXhwclBhcnNlci53cmFwTGl0ZXJhbFByaW1pdGl2ZSgiRVJST1IiLHIsbykpOnN9Y2F0Y2gocyl7cmV0dXJuIHRoaXMuX3JlcG9ydEVycm9yKGAke3N9YCxpKSx0aGlzLl9leHByUGFyc2VyLndyYXBMaXRlcmFsUHJpbWl0aXZlKCJFUlJPUiIscixvKX19X3JlcG9ydEVycm9yKHQsZSxpPWt1LkVSUk9SKXt0aGlzLmVycm9ycy5wdXNoKG5ldyB5bShlLHQsaSkpfV9yZXBvcnRFeHByZXNzaW9uUGFyc2VyRXJyb3JzKHQsZSl7Zm9yKGxldCBpIG9mIHQpdGhpcy5fcmVwb3J0RXJyb3IoaS5tZXNzYWdlLGUpfV92YWxpZGF0ZVByb3BlcnR5T3JBdHRyaWJ1dGVOYW1lKHQsZSxpKXtsZXQgcj1pP3RoaXMuX3NjaGVtYVJlZ2lzdHJ5LnZhbGlkYXRlQXR0cmlidXRlKHQpOnRoaXMuX3NjaGVtYVJlZ2lzdHJ5LnZhbGlkYXRlUHJvcGVydHkodCk7ci5lcnJvciYmdGhpcy5fcmVwb3J0RXJyb3Ioci5tc2csZSxrdS5FUlJPUil9fShuZXcgYkQobmV3IHlEKSxuLEVLLFtdKX1mdW5jdGlvbiBUSyhuLHQpe3N3aXRjaChuKXtjYXNlIGlvLkhUTUw6cmV0dXJuIFRuKHRlLnNhbml0aXplSHRtbCk7Y2FzZSBpby5TQ1JJUFQ6cmV0dXJuIFRuKHRlLnNhbml0aXplU2NyaXB0KTtjYXNlIGlvLlNUWUxFOnJldHVybiB0P1RuKHRlLnNhbml0aXplU3R5bGUpOm51bGw7Y2FzZSBpby5VUkw6cmV0dXJuIFRuKHRlLnNhbml0aXplVXJsKTtjYXNlIGlvLlJFU09VUkNFX1VSTDpyZXR1cm4gVG4odGUuc2FuaXRpemVSZXNvdXJjZVVybCk7ZGVmYXVsdDpyZXR1cm4gbnVsbH19ZnVuY3Rpb24gRTFlKG4sdCl7bGV0IGU9TnUodC52YWx1ZSk7aWYoIXhLKG4sdC5uYW1lKSlyZXR1cm4gZTtzd2l0Y2goRUsuc2VjdXJpdHlDb250ZXh0KG4sdC5uYW1lLCEwKSl7Y2FzZSBpby5IVE1MOnJldHVybiBDWChUbih0ZS50cnVzdENvbnN0YW50SHRtbCksbmV3ICRUKFtuZXcgZUQodC52YWx1ZSldLFtdKSx2b2lkIDAsdC52YWx1ZVNwYW4pO2Nhc2UgaW8uUkVTT1VSQ0VfVVJMOnJldHVybiBDWChUbih0ZS50cnVzdENvbnN0YW50UmVzb3VyY2VVcmwpLG5ldyAkVChbbmV3IGVEKHQudmFsdWUpXSxbXSksdm9pZCAwLHQudmFsdWVTcGFuKTtkZWZhdWx0OnJldHVybiBlfX1mdW5jdGlvbiBEMWUobil7cmV0dXJuIG4gaW5zdGFuY2VvZiBNX3x8biBpbnN0YW5jZW9mIFRDfHxuIGluc3RhbmNlb2Ygc0R9ZnVuY3Rpb24gYkIobil7cmV0dXJuIG4uZXZlcnkoRDFlKX1mdW5jdGlvbiB6VChuLHQsZSl7cmV0dXJuKCk9PntsZXQgaT1uKCkscj1BcnJheS5pc0FycmF5KGkpP2k6W2ldO3JldHVybiBlJiZyLnB1c2goLi4uZSksdCYmci51bnNoaWZ0KGh0KHQpKSxyfX12YXIgTVE9Im5nSTE4bkNsb3N1cmVNb2RlIjtmdW5jdGlvbiBESyhuKXtyZXR1cm4gbi5yZWR1Y2UoKHQsZSk9PntsZXQgaT1BcnJheS5pc0FycmF5KGUpP0RLKGUpOmU7cmV0dXJuIHQuY29uY2F0KGkpfSxbXSl9dmFyIFAxZT0vYXR0clwuKFteXF1dKykvO2Z1bmN0aW9uIElLKG4sdCxlKXtsZXQgaT1uZXcgc2gscj1OVihuLnNlbGVjdG9yKTtyZXR1cm4gaS5zZXQoInR5cGUiLG4uaW50ZXJuYWxUeXBlKSxyLmxlbmd0aD4wJiZpLnNldCgic2VsZWN0b3JzIixOdShyKSksbi5xdWVyaWVzLmxlbmd0aD4wJiZpLnNldCgiY29udGVudFF1ZXJpZXMiLGZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1bXSxyPVtdLG89WFEociwiX3QiKTtmb3IobGV0IGEgb2Ygbil7aS5wdXNoKFRuKHRlLmNvbnRlbnRRdWVyeSkuY2FsbEZuKFtSaSgiZGlySW5kZXgiKSwuLi5SSyhhLHQpXSkudG9TdG10KCkpO2xldCBsPW8oKSxjPVRuKHRlLmxvYWRRdWVyeSkuY2FsbEZuKFtdKSx1PVRuKHRlLnF1ZXJ5UmVmcmVzaCkuY2FsbEZuKFtsLnNldChjKV0pLGQ9UmkoSGMpLnByb3AoYS5wcm9wZXJ0eU5hbWUpLnNldChhLmZpcnN0P2wucHJvcCgiZmlyc3QiKTpsKTtyLnB1c2godS5hbmQoZCkudG9TdG10KCkpfWxldCBzPWU/YCR7ZX1fQ29udGVudFF1ZXJpZXNgOm51bGw7cmV0dXJuIHJhKFtuZXcgaWEoJEMsWkMpLG5ldyBpYShIYyxudWxsKSxuZXcgaWEoImRpckluZGV4IixudWxsKV0sW2xoKDEsaSksbGgoMixyKV0sUGEsbnVsbCxzKX0obi5xdWVyaWVzLHQsbi5uYW1lKSksbi52aWV3UXVlcmllcy5sZW5ndGgmJmkuc2V0KCJ2aWV3UXVlcnkiLGZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1bXSxyPVtdLG89WFEociwiX3QiKTtuLmZvckVhY2goYT0+e2xldCBsPVRuKHRlLnZpZXdRdWVyeSkuY2FsbEZuKFJLKGEsdCkpO2kucHVzaChsLnRvU3RtdCgpKTtsZXQgYz1vKCksdT1Ubih0ZS5sb2FkUXVlcnkpLmNhbGxGbihbXSksZD1Ubih0ZS5xdWVyeVJlZnJlc2gpLmNhbGxGbihbYy5zZXQodSldKSxwPVJpKEhjKS5wcm9wKGEucHJvcGVydHlOYW1lKS5zZXQoYS5maXJzdD9jLnByb3AoImZpcnN0Iik6Yyk7ci5wdXNoKGQuYW5kKHApLnRvU3RtdCgpKX0pO2xldCBzPWU/YCR7ZX1fUXVlcnlgOm51bGw7cmV0dXJuIHJhKFtuZXcgaWEoJEMsWkMpLG5ldyBpYShIYyxudWxsKV0sW2xoKDEsaSksbGgoMixyKV0sUGEsbnVsbCxzKX0obi52aWV3UXVlcmllcyx0LG4ubmFtZSkpLGkuc2V0KCJob3N0QmluZGluZ3MiLGZ1bmN0aW9uKG4sdCxlLGkscixvLHMpe2xldCBhPVJpKEhjKSxsPW5ldyB2RChhKSx7c3R5bGVBdHRyOmMsY2xhc3NBdHRyOnV9PW4uc3BlY2lhbEF0dHJpYnV0ZXM7dm9pZCAwIT09YyYmbC5yZWdpc3RlclN0eWxlQXR0cihjKSx2b2lkIDAhPT11JiZsLnJlZ2lzdGVyQ2xhc3NBdHRyKHUpO2xldCBkPVtdLHA9W10saD1bXSxmPXQsbT1lLmNyZWF0ZURpcmVjdGl2ZUhvc3RFdmVudEFzdHMobi5saXN0ZW5lcnMsZik7bSYmbS5sZW5ndGgmJmQucHVzaCguLi5mdW5jdGlvbihuLHQpe2xldCBlPVtdLGk9W10scj1bXTtmb3IobGV0IG8gb2Ygbil7bGV0IHM9by5uYW1lJiZtXyhvLm5hbWUpLGE9MT09PW8udHlwZT9qUShzLG8udGFyZ2V0T3JQaGFzZSk6cyxsPXQmJnM/YCR7dH1fJHthfV9Ib3N0QmluZGluZ0hhbmRsZXJgOm51bGwsYz1TSyhTXy5mcm9tUGFyc2VkRXZlbnQobyksbCk7MT09by50eXBlP2kucHVzaChjKTplLnB1c2goYyl9Zm9yKGxldCBvIG9mIGkpci5wdXNoKHtyZWZlcmVuY2U6dGUuc3ludGhldGljSG9zdExpc3RlbmVyLHBhcmFtc09yRm46byxzcGFuOm51bGx9KTtmb3IobGV0IG8gb2YgZSlyLnB1c2goe3JlZmVyZW5jZTp0ZS5saXN0ZW5lcixwYXJhbXNPckZuOm8sc3BhbjpudWxsfSk7cmV0dXJuIHJ9KG0sbykpO2xldCB4PWUuY3JlYXRlQm91bmRIb3N0UHJvcGVydGllcyhuLnByb3BlcnRpZXMsZiksZz1bXSxiPTA7eCYmeC5mb3JFYWNoKHVlPT57bC5yZWdpc3RlcklucHV0QmFzZWRPbk5hbWUodWUubmFtZSx1ZS5leHByZXNzaW9uLGYpP2IrPTI6KGcucHVzaCh1ZSksYisrKX0pO2xldCBELFQ9KCk9PihEfHwoRD1uZXcgVEQoaSwoKT0+UVQoIlVuZXhwZWN0ZWQgbm9kZSIpLGhlPT57bGV0IHc9YjtyZXR1cm4gYis9aGUsd30sKCk9PlFUKCJVbmV4cGVjdGVkIHBpcGUiKSkpLEQpLGs9W10sWj1bXSx6PVtdO2ZvcihsZXQgdWUgb2YgZyl7bGV0IGhlPXVlLmV4cHJlc3Npb24udmlzaXQoVCgpKSx3PVNRKGEsaGUpLHtiaW5kaW5nTmFtZTpGLGluc3RydWN0aW9uOnEsaXNBdHRyaWJ1dGU6S309cTFlKHVlKSxkZT1lLmNhbGNQb3NzaWJsZVNlY3VyaXR5Q29udGV4dHMocixGLEspLmZpbHRlcihsZT0+bGUhPT1pby5OT05FKSxZPW51bGw7ZGUubGVuZ3RoJiYoWT0yPT09ZGUubGVuZ3RoJiZkZS5pbmRleE9mKGlvLlVSTCk+LTEmJmRlLmluZGV4T2YoaW8uUkVTT1VSQ0VfVVJMKT4tMT9Ubih0ZS5zYW5pdGl6ZVVybE9yUmVzb3VyY2VVcmwpOlRLKGRlWzBdLEspKTtsZXQgYWU9W2h0KEYpLHcuY3VyclZhbEV4cHJdO1kmJmFlLnB1c2goWSksaC5wdXNoKC4uLncuc3RtdHMpLHE9PT10ZS5ob3N0UHJvcGVydHk/ay5wdXNoKGFlKTpxPT09dGUuYXR0cmlidXRlP1oucHVzaChhZSk6cT09PXRlLnN5bnRoZXRpY0hvc3RQcm9wZXJ0eT96LnB1c2goYWUpOnAucHVzaCh7cmVmZXJlbmNlOnEscGFyYW1zT3JGbjphZSxzcGFuOm51bGx9KX1mb3IobGV0IHVlIG9mIGspcC5wdXNoKHtyZWZlcmVuY2U6dGUuaG9zdFByb3BlcnR5LHBhcmFtc09yRm46dWUsc3BhbjpudWxsfSk7Zm9yKGxldCB1ZSBvZiBaKXAucHVzaCh7cmVmZXJlbmNlOnRlLmF0dHJpYnV0ZSxwYXJhbXNPckZuOnVlLHNwYW46bnVsbH0pO2ZvcihsZXQgdWUgb2YgeilwLnB1c2goe3JlZmVyZW5jZTp0ZS5zeW50aGV0aWNIb3N0UHJvcGVydHkscGFyYW1zT3JGbjp1ZSxzcGFuOm51bGx9KTtsZXQgZmU9ZnVuY3Rpb24obil7bGV0IHQ9W107Zm9yKGxldCBlIG9mIE9iamVjdC5nZXRPd25Qcm9wZXJ0eU5hbWVzKG4pKXtsZXQgaT1uW2VdO3QucHVzaChodChlKSxpKX1yZXR1cm4gdH0obi5hdHRyaWJ1dGVzKTtpZihsLmFzc2lnbkhvc3RBdHRycyhmZSxzKSxsLmhhc0JpbmRpbmdzJiZsLmJ1aWxkVXBkYXRlTGV2ZWxJbnN0cnVjdGlvbnMoVCgpKS5mb3JFYWNoKHVlPT57Zm9yKGxldCBoZSBvZiB1ZS5jYWxscyliKz1NYXRoLm1heChoZS5hbGxvY2F0ZUJpbmRpbmdTbG90cy0yLDApLHAucHVzaCh7cmVmZXJlbmNlOnVlLnJlZmVyZW5jZSxwYXJhbXNPckZuOlcxZShoZSxhLFNRKSxzcGFuOm51bGx9KX0pLGImJnMuc2V0KCJob3N0VmFycyIsaHQoYikpLGQubGVuZ3RoPjB8fHAubGVuZ3RoPjApe2xldCB1ZT1vP2Ake299X0hvc3RCaW5kaW5nc2A6bnVsbCxoZT1bXTtyZXR1cm4gZC5sZW5ndGg+MCYmaGUucHVzaChsaCgxLGNEKGQpKSkscC5sZW5ndGg+MCYmaGUucHVzaChsaCgyLGguY29uY2F0KGNEKHApKSkpLHJhKFtuZXcgaWEoJEMsWkMpLG5ldyBpYShIYyxudWxsKV0saGUsUGEsbnVsbCx1ZSl9cmV0dXJuIG51bGx9KG4uaG9zdCxuLnR5cGVTb3VyY2VTcGFuLGUsdCxuLnNlbGVjdG9yfHwiIixuLm5hbWUsaSkpLGkuc2V0KCJpbnB1dHMiLE9YKG4uaW5wdXRzLCEwKSksaS5zZXQoIm91dHB1dHMiLE9YKG4ub3V0cHV0cykpLG51bGwhPT1uLmV4cG9ydEFzJiZpLnNldCgiZXhwb3J0QXMiLF9yKG4uZXhwb3J0QXMubWFwKG89Pmh0KG8pKSkpLG4uaXNTdGFuZGFsb25lJiZpLnNldCgic3RhbmRhbG9uZSIsaHQoITApKSxpfWZ1bmN0aW9uIFBLKG4sdCl7bGV0IGU9W10saT10LnByb3ZpZGVycyxyPXQudmlld1Byb3ZpZGVycztpZihpfHxyKXtsZXQgbz1baXx8bmV3IGhtKFtdKV07ciYmby5wdXNoKHIpLGUucHVzaChUbih0ZS5Qcm92aWRlcnNGZWF0dXJlKS5jYWxsRm4obykpfXQudXNlc0luaGVyaXRhbmNlJiZlLnB1c2goVG4odGUuSW5oZXJpdERlZmluaXRpb25GZWF0dXJlKSksdC5mdWxsSW5oZXJpdGFuY2UmJmUucHVzaChUbih0ZS5Db3B5RGVmaW5pdGlvbkZlYXR1cmUpKSx0LmxpZmVjeWNsZS51c2VzT25DaGFuZ2VzJiZlLnB1c2goVG4odGUuTmdPbkNoYW5nZXNGZWF0dXJlKSksdC5oYXNPd25Qcm9wZXJ0eSgidGVtcGxhdGUiKSYmdC5pc1N0YW5kYWxvbmUmJmUucHVzaChUbih0ZS5TdGFuZGFsb25lRmVhdHVyZSkpLGUubGVuZ3RoJiZuLnNldCgiZmVhdHVyZXMiLF9yKGUpKX1mdW5jdGlvbiBSSyhuLHQpe2xldCBlPVtrd2Uobix0KSxodChCMWUobikpXTtyZXR1cm4gbi5yZWFkJiZlLnB1c2gobi5yZWFkKSxlfWZ1bmN0aW9uIEIxZShuKXtyZXR1cm4obi5kZXNjZW5kYW50cz8xOjApfChuLnN0YXRpYz8yOjApfChuLmVtaXREaXN0aW5jdENoYW5nZXNPbmx5PzQ6MCl9ZnVuY3Rpb24gVTFlKG4pe3JldHVybiB1bChodChuKSl9ZnVuY3Rpb24gd1Eobil7cmV0dXJuIHVsKHFsKE9iamVjdC5rZXlzKG4pLm1hcChlPT4oe2tleTplLHZhbHVlOmh0KEFycmF5LmlzQXJyYXkobltlXSk/bltlXVswXTpuW2VdKSxxdW90ZWQ6ITB9KSkpKX1mdW5jdGlvbiBQVihuKXtyZXR1cm4gbi5sZW5ndGg+MD91bChfcihuLm1hcCh0PT5odCh0KSkpKTpKZH1mdW5jdGlvbiBPSyhuKXtsZXQgdD1udWxsIT09bi5zZWxlY3Rvcj9uLnNlbGVjdG9yLnJlcGxhY2UoL1xuL2csIiIpOm51bGw7cmV0dXJuW0lEKG4udHlwZS50eXBlLG4udHlwZUFyZ3VtZW50Q291bnQpLG51bGwhPT10P1UxZSh0KTpKZCxudWxsIT09bi5leHBvcnRBcz9QVihuLmV4cG9ydEFzKTpKZCx3UShuLmlucHV0cyksd1Eobi5vdXRwdXRzKSxQVihuLnF1ZXJpZXMubWFwKGU9PmUucHJvcGVydHlOYW1lKSldfWZ1bmN0aW9uIFNRKG4sdCl7cmV0dXJuIHVLKG51bGwsbix0LCJiIil9ZnVuY3Rpb24gVzFlKG4sdCxlKXtyZXR1cm4gbi5wYXJhbXMoaT0+ZSh0LGkpLmN1cnJWYWxFeHByKX1mdW5jdGlvbiBxMWUobil7bGV0IGUsdD1uLm5hbWUsaT10Lm1hdGNoKFAxZSk7cmV0dXJuIGk/KHQ9aVsxXSxlPXRlLmF0dHJpYnV0ZSk6bi5pc0FuaW1hdGlvbj8odD16USh0KSxlPXRlLnN5bnRoZXRpY0hvc3RQcm9wZXJ0eSk6ZT10ZS5ob3N0UHJvcGVydHkse2JpbmRpbmdOYW1lOnQsaW5zdHJ1Y3Rpb246ZSxpc0F0dHJpYnV0ZTohIWl9fXZhciBuLFgxZT0vXig/OlxbKFteXF1dKylcXSl8KD86XCgoW15cKV0rKVwpKSQvLFJWPWNsYXNze307ZnVuY3Rpb24gRVEobil7cmV0dXJuey4uLm4scHJlZGljYXRlOmtLKG4ucHJlZGljYXRlKSxyZWFkOm4ucmVhZD9uZXcgTG4obi5yZWFkKTpudWxsLHN0YXRpYzpuLnN0YXRpYyxlbWl0RGlzdGluY3RDaGFuZ2VzT25seTpuLmVtaXREaXN0aW5jdENoYW5nZXNPbmx5fX1mdW5jdGlvbiBUUShuKXtyZXR1cm57cHJvcGVydHlOYW1lOm4ucHJvcGVydHlOYW1lLGZpcnN0Om4uZmlyc3Q/PyExLHByZWRpY2F0ZTprSyhuLnByZWRpY2F0ZSksZGVzY2VuZGFudHM6bi5kZXNjZW5kYW50cz8/ITEscmVhZDpuLnJlYWQ/bmV3IExuKG4ucmVhZCk6bnVsbCxzdGF0aWM6bi5zdGF0aWM/PyExLGVtaXREaXN0aW5jdENoYW5nZXNPbmx5Om4uZW1pdERpc3RpbmN0Q2hhbmdlc09ubHk/PyEwfX1mdW5jdGlvbiBrSyhuKXtyZXR1cm4gQXJyYXkuaXNBcnJheShuKT9uOkhWKG5ldyBMbihuKSwxKX1mdW5jdGlvbiBEUShuKXtsZXQgdD1SUShuLmlucHV0c3x8W10pLGU9UlEobi5vdXRwdXRzfHxbXSksaT1uLnByb3BNZXRhZGF0YSxyPXt9LG89e307Zm9yKGxldCBzIGluIGkpaS5oYXNPd25Qcm9wZXJ0eShzKSYmaVtzXS5mb3JFYWNoKGE9PntsVGUoYSk/cltzXT1hLmJpbmRpbmdQcm9wZXJ0eU5hbWU/W2EuYmluZGluZ1Byb3BlcnR5TmFtZSxzXTpzOmNUZShhKSYmKG9bc109YS5iaW5kaW5nUHJvcGVydHlOYW1lfHxzKX0pO3JldHVybnsuLi5uLHR5cGVBcmd1bWVudENvdW50OjAsdHlwZVNvdXJjZVNwYW46bi50eXBlU291cmNlU3Bhbix0eXBlOkFzKG4udHlwZSksaW50ZXJuYWxUeXBlOm5ldyBMbihuLnR5cGUpLGRlcHM6bnVsbCxob3N0Om9UZShuLnByb3BNZXRhZGF0YSxuLnR5cGVTb3VyY2VTcGFuLG4uaG9zdCksaW5wdXRzOnsuLi50LC4uLnJ9LG91dHB1dHM6ey4uLmUsLi4ub30scXVlcmllczpuLnF1ZXJpZXMubWFwKEVRKSxwcm92aWRlcnM6bnVsbCE9bi5wcm92aWRlcnM/bmV3IExuKG4ucHJvdmlkZXJzKTpudWxsLHZpZXdRdWVyaWVzOm4udmlld1F1ZXJpZXMubWFwKEVRKSxmdWxsSW5oZXJpdGFuY2U6ITF9fWZ1bmN0aW9uIEZLKG4sdCl7cmV0dXJue25hbWU6bi50eXBlLm5hbWUsdHlwZTpBcyhuLnR5cGUpLHR5cGVTb3VyY2VTcGFuOnQsaW50ZXJuYWxUeXBlOm5ldyBMbihuLnR5cGUpLHNlbGVjdG9yOm4uc2VsZWN0b3I/P251bGwsaW5wdXRzOm4uaW5wdXRzPz97fSxvdXRwdXRzOm4ub3V0cHV0cz8/e30saG9zdDpKMWUobi5ob3N0KSxxdWVyaWVzOihuLnF1ZXJpZXM/P1tdKS5tYXAoVFEpLHZpZXdRdWVyaWVzOihuLnZpZXdRdWVyaWVzPz9bXSkubWFwKFRRKSxwcm92aWRlcnM6dm9pZCAwIT09bi5wcm92aWRlcnM/bmV3IExuKG4ucHJvdmlkZXJzKTpudWxsLGV4cG9ydEFzOm4uZXhwb3J0QXM/P251bGwsdXNlc0luaGVyaXRhbmNlOm4udXNlc0luaGVyaXRhbmNlPz8hMSxsaWZlY3ljbGU6e3VzZXNPbkNoYW5nZXM6bi51c2VzT25DaGFuZ2VzPz8hMX0sZGVwczpudWxsLHR5cGVBcmd1bWVudENvdW50OjAsZnVsbEluaGVyaXRhbmNlOiExLGlzU3RhbmRhbG9uZTpuLmlzU3RhbmRhbG9uZT8/ITF9fWZ1bmN0aW9uIEoxZShuPXt9KXtyZXR1cm57YXR0cmlidXRlczokMWUobi5hdHRyaWJ1dGVzPz97fSksbGlzdGVuZXJzOm4ubGlzdGVuZXJzPz97fSxwcm9wZXJ0aWVzOm4ucHJvcGVydGllcz8/e30sc3BlY2lhbEF0dHJpYnV0ZXM6e2NsYXNzQXR0cjpuLmNsYXNzQXR0cmlidXRlLHN0eWxlQXR0cjpuLnN0eWxlQXR0cmlidXRlfX19ZnVuY3Rpb24gJDFlKG4pe2xldCB0PXt9O2ZvcihsZXQgZSBvZiBPYmplY3Qua2V5cyhuKSl0W2VdPW5ldyBMbihuW2VdKTtyZXR1cm4gdH1mdW5jdGlvbiB0VGUobil7cmV0dXJuey4uLm4sdHlwZTpuZXcgTG4obi50eXBlKX19ZnVuY3Rpb24geEIobix0PW51bGwpe3JldHVybntraW5kOl9fLkRpcmVjdGl2ZSxpc0NvbXBvbmVudDp0fHwiY29tcG9uZW50Ij09PW4ua2luZCxzZWxlY3RvcjpuLnNlbGVjdG9yLHR5cGU6bmV3IExuKG4udHlwZSksaW5wdXRzOm4uaW5wdXRzPz9bXSxvdXRwdXRzOm4ub3V0cHV0cz8/W10sZXhwb3J0QXM6bi5leHBvcnRBcz8/bnVsbH19ZnVuY3Rpb24gaVRlKG4pe3JldHVybntraW5kOl9fLlBpcGUsbmFtZTpuLm5hbWUsdHlwZTpuZXcgTG4obi50eXBlKX19ZnVuY3Rpb24gTksobix0LGUsaSxyKXtsZXQgbz1yP0RfLmZyb21BcnJheShyKTpQdSxzPVMxZShuLGUse3ByZXNlcnZlV2hpdGVzcGFjZXM6aSxpbnRlcnBvbGF0aW9uQ29uZmlnOm99KTtpZihudWxsIT09cy5lcnJvcnMpe2xldCBhPXMuZXJyb3JzLm1hcChsPT5sLnRvU3RyaW5nKCkpLmpvaW4oIiwgIik7dGhyb3cgbmV3IEVycm9yKGBFcnJvcnMgZHVyaW5nIEpJVCBjb21waWxhdGlvbiBvZiB0ZW1wbGF0ZSBmb3IgJHt0fTogJHthfWApfXJldHVybnt0ZW1wbGF0ZTpzLGludGVycG9sYXRpb246b319ZnVuY3Rpb24gc18obix0KXtpZihuLmhhc093blByb3BlcnR5KHQpKXJldHVybiBIVihuZXcgTG4oblt0XSksMCl9ZnVuY3Rpb24gQVEobix0KXtpZihuLmhhc093blByb3BlcnR5KHQpKXJldHVybiBuZXcgTG4oblt0XSl9ZnVuY3Rpb24gSVEobil7cmV0dXJuIEhWKCJmdW5jdGlvbiI9PXR5cGVvZiBuP25ldyBMbihuKTpuZXcgY2wobj8/bnVsbCksMCl9ZnVuY3Rpb24gTEsobil7bGV0IHQ9bnVsbCE9bi5hdHRyaWJ1dGUsZT1udWxsPT09bi50b2tlbj9udWxsOm5ldyBMbihuLnRva2VuKTtyZXR1cm4gQksodD9uZXcgTG4obi5hdHRyaWJ1dGUpOmUsdCxuLmhvc3Qsbi5vcHRpb25hbCxuLnNlbGYsbi5za2lwU2VsZil9ZnVuY3Rpb24gUFEobil7bGV0IHQ9bi5hdHRyaWJ1dGU/PyExO3JldHVybiBCSyhudWxsPT09bi50b2tlbj9udWxsOm5ldyBMbihuLnRva2VuKSx0LG4uaG9zdD8/ITEsbi5vcHRpb25hbD8/ITEsbi5zZWxmPz8hMSxuLnNraXBTZWxmPz8hMSl9ZnVuY3Rpb24gQksobix0LGUsaSxyLG8pe3JldHVybnt0b2tlbjpuLGF0dHJpYnV0ZU5hbWVUeXBlOnQ/aHQoInVua25vd24iKTpudWxsLGhvc3Q6ZSxvcHRpb25hbDppLHNlbGY6cixza2lwU2VsZjpvfX1mdW5jdGlvbiBvVGUobix0LGUpe2xldCBpPWZ1bmN0aW9uKG4pe2xldCB0PXt9LGU9e30saT17fSxyPXt9O2ZvcihsZXQgbyBvZiBPYmplY3Qua2V5cyhuKSl7bGV0IHM9bltvXSxhPW8ubWF0Y2goWDFlKTtpZihudWxsPT09YSlzd2l0Y2gobyl7Y2FzZSJjbGFzcyI6aWYoInN0cmluZyIhPXR5cGVvZiBzKXRocm93IG5ldyBFcnJvcigiQ2xhc3MgYmluZGluZyBtdXN0IGJlIHN0cmluZyIpO3IuY2xhc3NBdHRyPXM7YnJlYWs7Y2FzZSJzdHlsZSI6aWYoInN0cmluZyIhPXR5cGVvZiBzKXRocm93IG5ldyBFcnJvcigiU3R5bGUgYmluZGluZyBtdXN0IGJlIHN0cmluZyIpO3Iuc3R5bGVBdHRyPXM7YnJlYWs7ZGVmYXVsdDp0W29dPSJzdHJpbmciPT10eXBlb2Ygcz9odChzKTpzfWVsc2UgaWYobnVsbCE9YVsxXSl7aWYoInN0cmluZyIhPXR5cGVvZiBzKXRocm93IG5ldyBFcnJvcigiUHJvcGVydHkgYmluZGluZyBtdXN0IGJlIHN0cmluZyIpO2lbYVsxXV09c31lbHNlIGlmKG51bGwhPWFbMl0pe2lmKCJzdHJpbmciIT10eXBlb2Ygcyl0aHJvdyBuZXcgRXJyb3IoIkV2ZW50IGJpbmRpbmcgbXVzdCBiZSBzdHJpbmciKTtlW2FbMl1dPXN9fXJldHVybnthdHRyaWJ1dGVzOnQsbGlzdGVuZXJzOmUscHJvcGVydGllczppLHNwZWNpYWxBdHRyaWJ1dGVzOnJ9fShlfHx7fSkscj1mdW5jdGlvbihuLHQpe2xldCBlPUREKCk7cmV0dXJuIGUuY3JlYXRlRGlyZWN0aXZlSG9zdEV2ZW50QXN0cyhuLmxpc3RlbmVycyx0KSxlLmNyZWF0ZUJvdW5kSG9zdFByb3BlcnRpZXMobi5wcm9wZXJ0aWVzLHQpLGUuZXJyb3JzfShpLHQpO2lmKHIubGVuZ3RoKXRocm93IG5ldyBFcnJvcihyLm1hcChvPT5vLm1zZykuam9pbigiXG4iKSk7Zm9yKGxldCBvIGluIG4pbi5oYXNPd25Qcm9wZXJ0eShvKSYmbltvXS5mb3JFYWNoKHM9PntzVGUocyk/aS5wcm9wZXJ0aWVzW3MuaG9zdFByb3BlcnR5TmFtZXx8b109cndlKCJ0aGlzIixvKTphVGUocykmJihpLmxpc3RlbmVyc1tzLmV2ZW50TmFtZXx8b109YCR7b30oJHsocy5hcmdzfHxbXSkuam9pbigiLCIpfSlgKX0pO3JldHVybiBpfWZ1bmN0aW9uIHNUZShuKXtyZXR1cm4iSG9zdEJpbmRpbmciPT09bi5uZ01ldGFkYXRhTmFtZX1mdW5jdGlvbiBhVGUobil7cmV0dXJuIkhvc3RMaXN0ZW5lciI9PT1uLm5nTWV0YWRhdGFOYW1lfWZ1bmN0aW9uIGxUZShuKXtyZXR1cm4iSW5wdXQiPT09bi5uZ01ldGFkYXRhTmFtZX1mdW5jdGlvbiBjVGUobil7cmV0dXJuIk91dHB1dCI9PT1uLm5nTWV0YWRhdGFOYW1lfWZ1bmN0aW9uIFJRKG4pe3JldHVybiBuLnJlZHVjZSgodCxlKT0+e2xldFtpLHJdPWUuc3BsaXQoIjoiLDIpLm1hcChvPT5vLnRyaW0oKSk7cmV0dXJuIHRbaV09cnx8aSx0fSx7fSl9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuZnVsbD10O2xldCBlPXQuc3BsaXQoIi4iKTt0aGlzLm1ham9yPWVbMF0sdGhpcy5taW5vcj1lWzFdLHRoaXMucGF0Y2g9ZS5zbGljZSgyKS5qb2luKCIuIil9fSgiMTQuMi4xMSIpLG5ldyBjbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuY2xvc2VkQnlQYXJlbnQ9ITEsdGhpcy5pc1ZvaWQ9ITEsdGhpcy5pZ25vcmVGaXJzdExmPSExLHRoaXMuY2FuU2VsZkNsb3NlPSEwLHRoaXMucHJldmVudE5hbWVzcGFjZUluaGVyaXRhbmNlPSExfXJlcXVpcmVFeHRyYVBhcmVudCh0KXtyZXR1cm4hMX1pc0Nsb3NlZEJ5Q2hpbGQodCl7cmV0dXJuITF9Z2V0Q29udGVudFR5cGUoKXtyZXR1cm4gV2wuUEFSU0FCTEVfREFUQX19LCgobj1wXykubmd8fChuLm5nPXt9KSkuXHUwMjc1Y29tcGlsZXJGYWNhZGU9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQ9bmV3IGNsYXNze2V2YWx1YXRlU3RhdGVtZW50cyh0LGUsaSxyKXtsZXQgbz1uZXcgWEIoaSkscz1FQy5jcmVhdGVSb290KCk7cmV0dXJuIGUubGVuZ3RoPjAmJiFlWzBdLmlzRXF1aXZhbGVudChodCgidXNlIHN0cmljdCIpLnRvU3RtdCgpKSYmKGU9W2h0KCJ1c2Ugc3RyaWN0IikudG9TdG10KCksLi4uZV0pLG8udmlzaXRBbGxTdGF0ZW1lbnRzKGUscyksby5jcmVhdGVSZXR1cm5TdG10KHMpLHRoaXMuZXZhbHVhdGVDb2RlKHQscyxvLmdldEFyZ3MoKSxyKX1ldmFsdWF0ZUNvZGUodCxlLGkscil7bGV0IG89YCJ1c2Ugc3RyaWN0Ijske2UudG9Tb3VyY2UoKX1cbi8vIyBzb3VyY2VVUkw9JHt0fWAscz1bXSxhPVtdO2ZvcihsZXQgYyBpbiBpKWEucHVzaChpW2NdKSxzLnB1c2goYyk7aWYocil7bGV0IGM9VVgoLi4ucy5jb25jYXQoInJldHVybiBudWxsOyIpKS50b1N0cmluZygpLHU9Yy5zbGljZSgwLGMuaW5kZXhPZigicmV0dXJuIG51bGw7IikpLnNwbGl0KCJcbiIpLmxlbmd0aC0xO28rPWBcbiR7ZS50b1NvdXJjZU1hcEdlbmVyYXRvcih0LHUpLnRvSnNDb21tZW50KCl9YH1sZXQgbD1VWCguLi5zLmNvbmNhdChvKSk7cmV0dXJuIHRoaXMuZXhlY3V0ZUZ1bmN0aW9uKGwsYSl9ZXhlY3V0ZUZ1bmN0aW9uKHQsZSl7cmV0dXJuIHQoLi4uZSl9fSl7dGhpcy5qaXRFdmFsdWF0b3I9dCx0aGlzLkZhY3RvcnlUYXJnZXQ9TmMsdGhpcy5SZXNvdXJjZUxvYWRlcj1SVix0aGlzLmVsZW1lbnRTY2hlbWFSZWdpc3RyeT1uZXcgU0R9Y29tcGlsZVBpcGUodCxlLGkpe2xldCBvPWpYKHtuYW1lOmkubmFtZSx0eXBlOkFzKGkudHlwZSksaW50ZXJuYWxUeXBlOm5ldyBMbihpLnR5cGUpLHR5cGVBcmd1bWVudENvdW50OjAsZGVwczpudWxsLHBpcGVOYW1lOmkucGlwZU5hbWUscHVyZTppLnB1cmUsaXNTdGFuZGFsb25lOmkuaXNTdGFuZGFsb25lfSk7cmV0dXJuIHRoaXMuaml0RXhwcmVzc2lvbihvLmV4cHJlc3Npb24sdCxlLFtdKX1jb21waWxlUGlwZURlY2xhcmF0aW9uKHQsZSxpKXtsZXQgbz1qWCh7bmFtZToobj1pKS50eXBlLm5hbWUsdHlwZTpBcyhuLnR5cGUpLGludGVybmFsVHlwZTpuZXcgTG4obi50eXBlKSx0eXBlQXJndW1lbnRDb3VudDowLHBpcGVOYW1lOm4ubmFtZSxkZXBzOm51bGwscHVyZTpuLnB1cmU/PyEwLGlzU3RhbmRhbG9uZTpuLmlzU3RhbmRhbG9uZT8/ITF9KTt2YXIgbjtyZXR1cm4gdGhpcy5qaXRFeHByZXNzaW9uKG8uZXhwcmVzc2lvbix0LGUsW10pfWNvbXBpbGVJbmplY3RhYmxlKHQsZSxpKXtsZXR7ZXhwcmVzc2lvbjpyLHN0YXRlbWVudHM6b309a1goe25hbWU6aS5uYW1lLHR5cGU6QXMoaS50eXBlKSxpbnRlcm5hbFR5cGU6bmV3IExuKGkudHlwZSksdHlwZUFyZ3VtZW50Q291bnQ6aS50eXBlQXJndW1lbnRDb3VudCxwcm92aWRlZEluOklRKGkucHJvdmlkZWRJbiksdXNlQ2xhc3M6c18oaSwidXNlQ2xhc3MiKSx1c2VGYWN0b3J5OkFRKGksInVzZUZhY3RvcnkiKSx1c2VWYWx1ZTpzXyhpLCJ1c2VWYWx1ZSIpLHVzZUV4aXN0aW5nOnNfKGksInVzZUV4aXN0aW5nIiksZGVwczppLmRlcHM/Lm1hcChMSyl9LCEwKTtyZXR1cm4gdGhpcy5qaXRFeHByZXNzaW9uKHIsdCxlLG8pfWNvbXBpbGVJbmplY3RhYmxlRGVjbGFyYXRpb24odCxlLGkpe2xldHtleHByZXNzaW9uOnIsc3RhdGVtZW50czpvfT1rWCh7bmFtZTppLnR5cGUubmFtZSx0eXBlOkFzKGkudHlwZSksaW50ZXJuYWxUeXBlOm5ldyBMbihpLnR5cGUpLHR5cGVBcmd1bWVudENvdW50OjAscHJvdmlkZWRJbjpJUShpLnByb3ZpZGVkSW4pLHVzZUNsYXNzOnNfKGksInVzZUNsYXNzIiksdXNlRmFjdG9yeTpBUShpLCJ1c2VGYWN0b3J5IiksdXNlVmFsdWU6c18oaSwidXNlVmFsdWUiKSx1c2VFeGlzdGluZzpzXyhpLCJ1c2VFeGlzdGluZyIpLGRlcHM6aS5kZXBzPy5tYXAoUFEpfSwhMCk7cmV0dXJuIHRoaXMuaml0RXhwcmVzc2lvbihyLHQsZSxvKX1jb21waWxlSW5qZWN0b3IodCxlLGkpe2xldCBvPXpYKHtuYW1lOmkubmFtZSx0eXBlOkFzKGkudHlwZSksaW50ZXJuYWxUeXBlOm5ldyBMbihpLnR5cGUpLHByb3ZpZGVyczppLnByb3ZpZGVycyYmaS5wcm92aWRlcnMubGVuZ3RoPjA/bmV3IExuKGkucHJvdmlkZXJzKTpudWxsLGltcG9ydHM6aS5pbXBvcnRzLm1hcChzPT5uZXcgTG4ocykpfSk7cmV0dXJuIHRoaXMuaml0RXhwcmVzc2lvbihvLmV4cHJlc3Npb24sdCxlLFtdKX1jb21waWxlSW5qZWN0b3JEZWNsYXJhdGlvbih0LGUsaSl7bGV0IHI9e25hbWU6KG49aSkudHlwZS5uYW1lLHR5cGU6QXMobi50eXBlKSxpbnRlcm5hbFR5cGU6bmV3IExuKG4udHlwZSkscHJvdmlkZXJzOnZvaWQgMCE9PW4ucHJvdmlkZXJzJiZuLnByb3ZpZGVycy5sZW5ndGg+MD9uZXcgTG4obi5wcm92aWRlcnMpOm51bGwsaW1wb3J0czp2b2lkIDAhPT1uLmltcG9ydHM/bi5pbXBvcnRzLm1hcCh0PT5uZXcgTG4odCkpOltdfSxvPXpYKHIpO3ZhciBuO3JldHVybiB0aGlzLmppdEV4cHJlc3Npb24oby5leHByZXNzaW9uLHQsZSxbXSl9Y29tcGlsZU5nTW9kdWxlKHQsZSxpKXtsZXQgbz1pU2Uoe3R5cGU6QXMoaS50eXBlKSxpbnRlcm5hbFR5cGU6bmV3IExuKGkudHlwZSksYWRqYWNlbnRUeXBlOm5ldyBMbihpLnR5cGUpLGJvb3RzdHJhcDppLmJvb3RzdHJhcC5tYXAoQXMpLGRlY2xhcmF0aW9uczppLmRlY2xhcmF0aW9ucy5tYXAoQXMpLHB1YmxpY0RlY2xhcmF0aW9uVHlwZXM6bnVsbCxpbXBvcnRzOmkuaW1wb3J0cy5tYXAoQXMpLGluY2x1ZGVJbXBvcnRUeXBlczohMCxleHBvcnRzOmkuZXhwb3J0cy5tYXAoQXMpLHNlbGVjdG9yU2NvcGVNb2RlOmdfLklubGluZSxjb250YWluc0ZvcndhcmREZWNsczohMSxzY2hlbWFzOmkuc2NoZW1hcz9pLnNjaGVtYXMubWFwKEFzKTpudWxsLGlkOmkuaWQ/bmV3IExuKGkuaWQpOm51bGx9KTtyZXR1cm4gdGhpcy5qaXRFeHByZXNzaW9uKG8uZXhwcmVzc2lvbix0LGUsW10pfWNvbXBpbGVOZ01vZHVsZURlY2xhcmF0aW9uKHQsZSxpKXtsZXQgcj1mdW5jdGlvbihuKXtsZXQgdD1uZXcgc2g7cmV0dXJuIHQuc2V0KCJ0eXBlIixuZXcgTG4obi50eXBlKSksdm9pZCAwIT09bi5ib290c3RyYXAmJnQuc2V0KCJib290c3RyYXAiLG5ldyBMbihuLmJvb3RzdHJhcCkpLHZvaWQgMCE9PW4uZGVjbGFyYXRpb25zJiZ0LnNldCgiZGVjbGFyYXRpb25zIixuZXcgTG4obi5kZWNsYXJhdGlvbnMpKSx2b2lkIDAhPT1uLmltcG9ydHMmJnQuc2V0KCJpbXBvcnRzIixuZXcgTG4obi5pbXBvcnRzKSksdm9pZCAwIT09bi5leHBvcnRzJiZ0LnNldCgiZXhwb3J0cyIsbmV3IExuKG4uZXhwb3J0cykpLHZvaWQgMCE9PW4uc2NoZW1hcyYmdC5zZXQoInNjaGVtYXMiLG5ldyBMbihuLnNjaGVtYXMpKSx2b2lkIDAhPT1uLmlkJiZ0LnNldCgiaWQiLG5ldyBMbihuLmlkKSksVG4odGUuZGVmaW5lTmdNb2R1bGUpLmNhbGxGbihbdC50b0xpdGVyYWxNYXAoKV0pfShpKTtyZXR1cm4gdGhpcy5qaXRFeHByZXNzaW9uKHIsdCxlLFtdKX1jb21waWxlRGlyZWN0aXZlKHQsZSxpKXtsZXQgcj1EUShpKTtyZXR1cm4gdGhpcy5jb21waWxlRGlyZWN0aXZlRnJvbU1ldGEodCxlLHIpfWNvbXBpbGVEaXJlY3RpdmVEZWNsYXJhdGlvbih0LGUsaSl7bGV0IG89RksoaSx0aGlzLmNyZWF0ZVBhcnNlU291cmNlU3BhbigiRGlyZWN0aXZlIixpLnR5cGUubmFtZSxlKSk7cmV0dXJuIHRoaXMuY29tcGlsZURpcmVjdGl2ZUZyb21NZXRhKHQsZSxvKX1jb21waWxlRGlyZWN0aXZlRnJvbU1ldGEodCxlLGkpe2xldCByPW5ldyBpRCxzPWZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1JSyhuLHQsZSk7UEsoaSxuKTtsZXQgcj1Ubih0ZS5kZWZpbmVEaXJlY3RpdmUpLmNhbGxGbihbaS50b0xpdGVyYWxNYXAoKV0sdm9pZCAwLCEwKSxvPWZ1bmN0aW9uKG4pe2xldCB0PU9LKG4pO3JldHVybiB0LnB1c2goSmQpLHQucHVzaCh1bChodChuLmlzU3RhbmRhbG9uZSkpKSx1bChUbih0ZS5EaXJlY3RpdmVEZWNsYXJhdGlvbix0KSl9KG4pO3JldHVybntleHByZXNzaW9uOnIsdHlwZTpvLHN0YXRlbWVudHM6W119fShpLHIsREQoKSk7cmV0dXJuIHRoaXMuaml0RXhwcmVzc2lvbihzLmV4cHJlc3Npb24sdCxlLHIuc3RhdGVtZW50cyl9Y29tcGlsZUNvbXBvbmVudCh0LGUsaSl7bGV0e3RlbXBsYXRlOnIsaW50ZXJwb2xhdGlvbjpvfT1OSyhpLnRlbXBsYXRlLGkubmFtZSxlLGkucHJlc2VydmVXaGl0ZXNwYWNlcyxpLmludGVycG9sYXRpb24pLHM9ey4uLmksLi4uRFEoaSksc2VsZWN0b3I6aS5zZWxlY3Rvcnx8dGhpcy5lbGVtZW50U2NoZW1hUmVnaXN0cnkuZ2V0RGVmYXVsdENvbXBvbmVudEVsZW1lbnROYW1lKCksdGVtcGxhdGU6cixkZWNsYXJhdGlvbnM6aS5kZWNsYXJhdGlvbnMubWFwKHRUZSksZGVjbGFyYXRpb25MaXN0RW1pdE1vZGU6MCxzdHlsZXM6Wy4uLmkuc3R5bGVzLC4uLnIuc3R5bGVzXSxlbmNhcHN1bGF0aW9uOmkuZW5jYXBzdWxhdGlvbixpbnRlcnBvbGF0aW9uOm8sY2hhbmdlRGV0ZWN0aW9uOmkuY2hhbmdlRGV0ZWN0aW9uLGFuaW1hdGlvbnM6bnVsbCE9aS5hbmltYXRpb25zP25ldyBMbihpLmFuaW1hdGlvbnMpOm51bGwsdmlld1Byb3ZpZGVyczpudWxsIT1pLnZpZXdQcm92aWRlcnM/bmV3IExuKGkudmlld1Byb3ZpZGVycyk6bnVsbCxyZWxhdGl2ZUNvbnRleHRGaWxlUGF0aDoiIixpMThuVXNlRXh0ZXJuYWxJZHM6ITB9O3JldHVybiB0aGlzLmNvbXBpbGVDb21wb25lbnRGcm9tTWV0YSh0LGBuZzovLy8ke2kubmFtZX0uanNgLHMpfWNvbXBpbGVDb21wb25lbnREZWNsYXJhdGlvbih0LGUsaSl7bGV0IG89ZnVuY3Rpb24obix0LGUpe2xldHt0ZW1wbGF0ZTppLGludGVycG9sYXRpb246cn09Tksobi50ZW1wbGF0ZSxuLnR5cGUubmFtZSxlLG4ucHJlc2VydmVXaGl0ZXNwYWNlcz8/ITEsbi5pbnRlcnBvbGF0aW9uKSxvPVtdO2lmKG4uZGVwZW5kZW5jaWVzKWZvcihsZXQgcyBvZiBuLmRlcGVuZGVuY2llcylzd2l0Y2gocy5raW5kKXtjYXNlImRpcmVjdGl2ZSI6Y2FzZSJjb21wb25lbnQiOm8ucHVzaCh4QihzKSk7YnJlYWs7Y2FzZSJwaXBlIjpvLnB1c2goaVRlKHMpKX1lbHNlKG4uY29tcG9uZW50c3x8bi5kaXJlY3RpdmVzfHxuLnBpcGVzKSYmKG4uY29tcG9uZW50cyYmby5wdXNoKC4uLm4uY29tcG9uZW50cy5tYXAocz0+eEIocywhMCkpKSxuLmRpcmVjdGl2ZXMmJm8ucHVzaCguLi5uLmRpcmVjdGl2ZXMubWFwKHM9PnhCKHMpKSksbi5waXBlcyYmby5wdXNoKC4uLmZ1bmN0aW9uKG4pe3JldHVybiBuP09iamVjdC5rZXlzKG4pLm1hcCh0PT4oe2tpbmQ6X18uUGlwZSxuYW1lOnQsdHlwZTpuZXcgTG4oblt0XSl9KSk6W119KG4ucGlwZXMpKSk7cmV0dXJuey4uLkZLKG4sdCksdGVtcGxhdGU6aSxzdHlsZXM6bi5zdHlsZXM/P1tdLGRlY2xhcmF0aW9uczpvLHZpZXdQcm92aWRlcnM6dm9pZCAwIT09bi52aWV3UHJvdmlkZXJzP25ldyBMbihuLnZpZXdQcm92aWRlcnMpOm51bGwsYW5pbWF0aW9uczp2b2lkIDAhPT1uLmFuaW1hdGlvbnM/bmV3IExuKG4uYW5pbWF0aW9ucyk6bnVsbCxjaGFuZ2VEZXRlY3Rpb246bi5jaGFuZ2VEZXRlY3Rpb24/P2NDLkRlZmF1bHQsZW5jYXBzdWxhdGlvbjpuLmVuY2Fwc3VsYXRpb24/P3FkLkVtdWxhdGVkLGludGVycG9sYXRpb246cixkZWNsYXJhdGlvbkxpc3RFbWl0TW9kZToyLHJlbGF0aXZlQ29udGV4dEZpbGVQYXRoOiIiLGkxOG5Vc2VFeHRlcm5hbElkczohMH19KGksdGhpcy5jcmVhdGVQYXJzZVNvdXJjZVNwYW4oIkNvbXBvbmVudCIsaS50eXBlLm5hbWUsZSksZSk7cmV0dXJuIHRoaXMuY29tcGlsZUNvbXBvbmVudEZyb21NZXRhKHQsZSxvKX1jb21waWxlQ29tcG9uZW50RnJvbU1ldGEodCxlLGkpe2xldCByPW5ldyBpRCxzPWZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1JSyhuLHQsZSk7UEsoaSxuKTtsZXQgcj1uLnNlbGVjdG9yJiZaZC5wYXJzZShuLnNlbGVjdG9yKSxvPXImJnJbMF07aWYobyl7bGV0IGc9by5nZXRBdHRycygpO2cubGVuZ3RoJiZpLnNldCgiYXR0cnMiLHQuZ2V0Q29uc3RMaXRlcmFsKF9yKGcubWFwKGI9Pmh0KG51bGwhPWI/Yjp2b2lkIDApKSksITApKX1sZXQgcz1uLm5hbWUsYT1zP2Ake3N9X1RlbXBsYXRlYDpudWxsLGw9bi5jaGFuZ2VEZXRlY3Rpb24sYz1uLnRlbXBsYXRlLHU9bmV3IEtDKHQsQl8uY3JlYXRlUm9vdFNjb3BlKCksMCxzLG51bGwsbnVsbCxhLHRlLm5hbWVzcGFjZUhUTUwsbi5yZWxhdGl2ZUNvbnRleHRGaWxlUGF0aCxuLmkxOG5Vc2VFeHRlcm5hbElkcyksZD11LmJ1aWxkVGVtcGxhdGVGdW5jdGlvbihjLm5vZGVzLFtdKSxwPXUuZ2V0TmdDb250ZW50U2VsZWN0b3JzKCk7cCYmaS5zZXQoIm5nQ29udGVudFNlbGVjdG9ycyIscCksaS5zZXQoImRlY2xzIixodCh1LmdldENvbnN0Q291bnQoKSkpLGkuc2V0KCJ2YXJzIixodCh1LmdldFZhckNvdW50KCkpKTtsZXR7Y29uc3RFeHByZXNzaW9uczpoLHByZXBhcmVTdGF0ZW1lbnRzOmZ9PXUuZ2V0Q29uc3RzKCk7aWYoaC5sZW5ndGg+MCl7bGV0IGc9X3IoaCk7Zi5sZW5ndGg+MCYmKGc9cmEoW10sWy4uLmYsbmV3IERvKGcpXSkpLGkuc2V0KCJjb25zdHMiLGcpfWlmKGkuc2V0KCJ0ZW1wbGF0ZSIsZCksbi5kZWNsYXJhdGlvbnMubGVuZ3RoPjAmJmkuc2V0KCJkZXBlbmRlbmNpZXMiLGZ1bmN0aW9uKG4sdCl7c3dpdGNoKHQpe2Nhc2UgMDpyZXR1cm4gbjtjYXNlIDE6cmV0dXJuIHJhKFtdLFtuZXcgRG8obildKTtjYXNlIDI6bGV0IGU9bi5wcm9wKCJtYXAiKS5jYWxsRm4oW1RuKHRlLnJlc29sdmVGb3J3YXJkUmVmKV0pO3JldHVybiByYShbXSxbbmV3IERvKGUpXSl9fShfcihuLmRlY2xhcmF0aW9ucy5tYXAoZz0+Zy50eXBlKSksbi5kZWNsYXJhdGlvbkxpc3RFbWl0TW9kZSkpLG51bGw9PT1uLmVuY2Fwc3VsYXRpb24mJihuLmVuY2Fwc3VsYXRpb249cWQuRW11bGF0ZWQpLG4uc3R5bGVzJiZuLnN0eWxlcy5sZW5ndGgpe2xldCBiPShuLmVuY2Fwc3VsYXRpb249PXFkLkVtdWxhdGVkP2Z1bmN0aW9uKG4sdCxlKXtsZXQgaT1uZXcgY2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnN0cmljdFN0eWxpbmc9ITB9c2hpbUNzc1RleHQodCxlLGk9IiIpe2xldCByPXQubWF0Y2goRVNlKXx8W107cmV0dXJuIHQ9ZnVuY3Rpb24obil7cmV0dXJuIG4ucmVwbGFjZSh3U2UsIiIpfSh0KSx0PXRoaXMuX2luc2VydERpcmVjdGl2ZXModCksW3RoaXMuX3Njb3BlQ3NzVGV4dCh0LGUsaSksLi4ucl0uam9pbigiXG4iKX1faW5zZXJ0RGlyZWN0aXZlcyh0KXtyZXR1cm4gdD10aGlzLl9pbnNlcnRQb2x5ZmlsbERpcmVjdGl2ZXNJbkNzc1RleHQodCksdGhpcy5faW5zZXJ0UG9seWZpbGxSdWxlc0luQ3NzVGV4dCh0KX1faW5zZXJ0UG9seWZpbGxEaXJlY3RpdmVzSW5Dc3NUZXh0KHQpe3JldHVybiB0LnJlcGxhY2UobVNlLGZ1bmN0aW9uKC4uLmUpe3JldHVybiBlWzJdKyJ7In0pfV9pbnNlcnRQb2x5ZmlsbFJ1bGVzSW5Dc3NUZXh0KHQpe3JldHVybiB0LnJlcGxhY2UoZ1NlLCguLi5lKT0+e2xldCBpPWVbMF0ucmVwbGFjZShlWzFdLCIiKS5yZXBsYWNlKGVbMl0sIiIpO3JldHVybiBlWzRdK2l9KX1fc2NvcGVDc3NUZXh0KHQsZSxpKXtsZXQgcj10aGlzLl9leHRyYWN0VW5zY29wZWRSdWxlc0Zyb21Dc3NUZXh0KHQpO3JldHVybiB0PXRoaXMuX2luc2VydFBvbHlmaWxsSG9zdEluQ3NzVGV4dCh0KSx0PXRoaXMuX2NvbnZlcnRDb2xvbkhvc3QodCksdD10aGlzLl9jb252ZXJ0Q29sb25Ib3N0Q29udGV4dCh0KSx0PXRoaXMuX2NvbnZlcnRTaGFkb3dET01TZWxlY3RvcnModCksZSYmKHQ9dGhpcy5fc2NvcGVTZWxlY3RvcnModCxlLGkpKSwodD10KyJcbiIrcikudHJpbSgpfV9leHRyYWN0VW5zY29wZWRSdWxlc0Zyb21Dc3NUZXh0KHQpe2xldCBpLGU9IiI7Zm9yKFdYLmxhc3RJbmRleD0wO251bGwhPT0oaT1XWC5leGVjKHQpKTspZSs9aVswXS5yZXBsYWNlKGlbMl0sIiIpLnJlcGxhY2UoaVsxXSxpWzRdKSsiXG5cbiI7cmV0dXJuIGV9X2NvbnZlcnRDb2xvbkhvc3QodCl7cmV0dXJuIHQucmVwbGFjZShfU2UsKGUsaSxyKT0+e2lmKGkpe2xldCBvPVtdLHM9aS5zcGxpdCgiLCIpLm1hcChhPT5hLnRyaW0oKSk7Zm9yKGxldCBhIG9mIHMpe2lmKCFhKWJyZWFrO2xldCBsPXRtK2EucmVwbGFjZShnRCwiIikrcjtvLnB1c2gobCl9cmV0dXJuIG8uam9pbigiLCIpfXJldHVybiB0bStyfSl9X2NvbnZlcnRDb2xvbkhvc3RDb250ZXh0KHQpe3JldHVybiB0LnJlcGxhY2UodlNlLGU9PntsZXQgcixpPVtbXV07Zm9yKDtyPXlTZS5leGVjKGUpOyl7bGV0IG89KHJbMV0/PyIiKS50cmltKCkuc3BsaXQoIiwiKS5tYXAoYT0+YS50cmltKCkpLmZpbHRlcihhPT4iIiE9PWEpLHM9aS5sZW5ndGg7a1NlKGksby5sZW5ndGgpO2ZvcihsZXQgYT0wO2E8by5sZW5ndGg7YSsrKWZvcihsZXQgbD0wO2w8cztsKyspaVtsK2Eqc10ucHVzaChvW2FdKTtlPXJbMl19cmV0dXJuIGkubWFwKG89PmZ1bmN0aW9uKG4sdCl7bGV0IGU9dG07ZF8ubGFzdEluZGV4PTA7bGV0IGk9ZF8udGVzdCh0KTtpZigwPT09bi5sZW5ndGgpcmV0dXJuIGUrdDtsZXQgcj1bbi5wb3AoKXx8IiJdO2Zvcig7bi5sZW5ndGg+MDspe2xldCBvPXIubGVuZ3RoLHM9bi5wb3AoKTtmb3IobGV0IGE9MDthPG87YSsrKXtsZXQgbD1yW2FdO3JbMipvK2FdPWwrIiAiK3MscltvK2FdPXMrIiAiK2wsclthXT1zK2x9fXJldHVybiByLm1hcChvPT5pP2Ake299JHt0fWA6YCR7b30ke2V9JHt0fSwgJHtvfSAke2V9JHt0fWApLmpvaW4oIiwiKX0obyxlKSkuam9pbigiLCAiKX0pfV9jb252ZXJ0U2hhZG93RE9NU2VsZWN0b3JzKHQpe3JldHVybiBiU2UucmVkdWNlKChlLGkpPT5lLnJlcGxhY2UoaSwiICIpLHQpfV9zY29wZVNlbGVjdG9ycyh0LGUsaSl7cmV0dXJuIFhYKHQscj0+e2xldCBvPXIuc2VsZWN0b3Iscz1yLmNvbnRlbnQ7cmV0dXJuIkAiIT09ci5zZWxlY3RvclswXT9vPXRoaXMuX3Njb3BlU2VsZWN0b3Ioci5zZWxlY3RvcixlLGksdGhpcy5zdHJpY3RTdHlsaW5nKTpyLnNlbGVjdG9yLnN0YXJ0c1dpdGgoIkBtZWRpYSIpfHxyLnNlbGVjdG9yLnN0YXJ0c1dpdGgoIkBzdXBwb3J0cyIpfHxyLnNlbGVjdG9yLnN0YXJ0c1dpdGgoIkBkb2N1bWVudCIpfHxyLnNlbGVjdG9yLnN0YXJ0c1dpdGgoIkBsYXllciIpP3M9dGhpcy5fc2NvcGVTZWxlY3RvcnMoci5jb250ZW50LGUsaSk6KHIuc2VsZWN0b3Iuc3RhcnRzV2l0aCgiQGZvbnQtZmFjZSIpfHxyLnNlbGVjdG9yLnN0YXJ0c1dpdGgoIkBwYWdlIikpJiYocz10aGlzLl9zdHJpcFNjb3BpbmdTZWxlY3RvcnMoci5jb250ZW50KSksbmV3IFdDKG8scyl9KX1fc3RyaXBTY29waW5nU2VsZWN0b3JzKHQpe3JldHVybiBYWCh0LGU9PntsZXQgaT1lLnNlbGVjdG9yLnJlcGxhY2UoWVgsIiAiKS5yZXBsYWNlKHFYLCIgIik7cmV0dXJuIG5ldyBXQyhpLGUuY29udGVudCl9KX1fc2NvcGVTZWxlY3Rvcih0LGUsaSxyKXtyZXR1cm4gdC5zcGxpdCgiLCIpLm1hcChvPT5vLnRyaW0oKS5zcGxpdChZWCkpLm1hcChvPT57bGV0W3MsLi4uYV09bztyZXR1cm5bKGM9PnRoaXMuX3NlbGVjdG9yTmVlZHNTY29waW5nKGMsZSk/cj90aGlzLl9hcHBseVN0cmljdFNlbGVjdG9yU2NvcGUoYyxlLGkpOnRoaXMuX2FwcGx5U2VsZWN0b3JTY29wZShjLGUsaSk6YykocyksLi4uYV0uam9pbigiICIpfSkuam9pbigiLCAiKX1fc2VsZWN0b3JOZWVkc1Njb3BpbmcodCxlKXtyZXR1cm4hdGhpcy5fbWFrZVNjb3BlTWF0Y2hlcihlKS50ZXN0KHQpfV9tYWtlU2NvcGVNYXRjaGVyKHQpe3JldHVybiB0PXQucmVwbGFjZSgvXFsvZywiXFxbIikucmVwbGFjZSgvXF0vZywiXFxdIiksbmV3IFJlZ0V4cCgiXigiK3QrIikoWz5cXHN+K1suLHs6XVtcXHNcXFNdKik/JCIsIm0iKX1fYXBwbHlTZWxlY3RvclNjb3BlKHQsZSxpKXtyZXR1cm4gdGhpcy5fYXBwbHlTaW1wbGVTZWxlY3RvclNjb3BlKHQsZSxpKX1fYXBwbHlTaW1wbGVTZWxlY3RvclNjb3BlKHQsZSxpKXtpZihkXy5sYXN0SW5kZXg9MCxkXy50ZXN0KHQpKXtsZXQgcj10aGlzLnN0cmljdFN0eWxpbmc/YFske2l9XWA6ZTtyZXR1cm4gdC5yZXBsYWNlKHFYLChvLHMpPT5zLnJlcGxhY2UoLyhbXjpdKikoOiopKC4qKS8sKGEsbCxjLHUpPT5sK3IrYyt1KSkucmVwbGFjZShkXyxyKyIgIil9cmV0dXJuIGUrIiAiK3R9X2FwcGx5U3RyaWN0U2VsZWN0b3JTY29wZSh0LGUsaSl7bGV0IHUsbz0iWyIrKGU9ZS5yZXBsYWNlKC9cW2lzPShbXlxdXSopXF0vZywobSwuLi54KT0+eFswXSkpKyJdIixzPW09PntsZXQgeD1tLnRyaW0oKTtpZigheClyZXR1cm4iIjtpZihtLmluZGV4T2YodG0pPi0xKXg9dGhpcy5fYXBwbHlTaW1wbGVTZWxlY3RvclNjb3BlKG0sZSxpKTtlbHNle2xldCBnPW0ucmVwbGFjZShkXywiIik7aWYoZy5sZW5ndGg+MCl7bGV0IGI9Zy5tYXRjaCgvKFteOl0qKSg6KikoLiopLyk7YiYmKHg9YlsxXStvK2JbMl0rYlszXSl9fXJldHVybiB4fSxhPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLnBsYWNlaG9sZGVycz1bXSx0aGlzLmluZGV4PTAsdD10aGlzLl9lc2NhcGVSZWdleE1hdGNoZXModCwvKFxbW15cXV0qXF0pL2cpLHQ9dGhpcy5fZXNjYXBlUmVnZXhNYXRjaGVzKHQsLyhcXC4pL2cpLHRoaXMuX2NvbnRlbnQ9dC5yZXBsYWNlKC8oOm50aC1bLVx3XSspKFwoW14pXStcKSkvZywoZSxpLHIpPT57bGV0IG89YF9fcGgtJHt0aGlzLmluZGV4fV9fYDtyZXR1cm4gdGhpcy5wbGFjZWhvbGRlcnMucHVzaChyKSx0aGlzLmluZGV4KyssaStvfSl9cmVzdG9yZSh0KXtyZXR1cm4gdC5yZXBsYWNlKC9fX3BoLShcZCspX18vZywoZSxpKT0+dGhpcy5wbGFjZWhvbGRlcnNbK2ldKX1jb250ZW50KCl7cmV0dXJuIHRoaXMuX2NvbnRlbnR9X2VzY2FwZVJlZ2V4TWF0Y2hlcyh0LGUpe3JldHVybiB0LnJlcGxhY2UoZSwoaSxyKT0+e2xldCBvPWBfX3BoLSR7dGhpcy5pbmRleH1fX2A7cmV0dXJuIHRoaXMucGxhY2Vob2xkZXJzLnB1c2gociksdGhpcy5pbmRleCsrLG99KX19KHQpLGw9IiIsYz0wLGQ9LyggfD58XCt8fig/IT0pKVxzKi9nLGg9ISgodD1hLmNvbnRlbnQoKSkuaW5kZXhPZih0bSk+LTEpO2Zvcig7bnVsbCE9PSh1PWQuZXhlYyh0KSk7KXtsZXQgbT11WzFdLHg9dC5zbGljZShjLHUuaW5kZXgpLnRyaW0oKTtoPWh8fHguaW5kZXhPZih0bSk+LTEsbCs9YCR7aD9zKHgpOnh9ICR7bX0gYCxjPWQubGFzdEluZGV4fWxldCBmPXQuc3Vic3RyaW5nKGMpO3JldHVybiBoPWh8fGYuaW5kZXhPZih0bSk+LTEsbCs9aD9zKGYpOmYsYS5yZXN0b3JlKGwpfV9pbnNlcnRQb2x5ZmlsbEhvc3RJbkNzc1RleHQodCl7cmV0dXJuIHQucmVwbGFjZShNU2UsWlYpLnJlcGxhY2UoQ1NlLGdEKX19O3JldHVybiBuLm1hcChyPT5pLnNoaW1Dc3NUZXh0KHIsIl9uZ2NvbnRlbnQtJUNPTVAlIiwiX25naG9zdC0lQ09NUCUiKSl9KG4uc3R5bGVzKTpuLnN0eWxlcykucmVkdWNlKChELFQpPT4oVC50cmltKCkubGVuZ3RoPjAmJkQucHVzaCh0LmdldENvbnN0TGl0ZXJhbChodChUKSkpLEQpLFtdKTtiLmxlbmd0aD4wJiZpLnNldCgic3R5bGVzIixfcihiKSl9ZWxzZSBuLmVuY2Fwc3VsYXRpb249PT1xZC5FbXVsYXRlZCYmKG4uZW5jYXBzdWxhdGlvbj1xZC5Ob25lKTtuLmVuY2Fwc3VsYXRpb24hPT1xZC5FbXVsYXRlZCYmaS5zZXQoImVuY2Fwc3VsYXRpb24iLGh0KG4uZW5jYXBzdWxhdGlvbikpLG51bGwhPT1uLmFuaW1hdGlvbnMmJmkuc2V0KCJkYXRhIixxbChbe2tleToiYW5pbWF0aW9uIix2YWx1ZTpuLmFuaW1hdGlvbnMscXVvdGVkOiExfV0pKSxudWxsIT1sJiZsIT09Y0MuRGVmYXVsdCYmaS5zZXQoImNoYW5nZURldGVjdGlvbiIsaHQobCkpO2xldCBtPVRuKHRlLmRlZmluZUNvbXBvbmVudCkuY2FsbEZuKFtpLnRvTGl0ZXJhbE1hcCgpXSx2b2lkIDAsITApLHg9ZnVuY3Rpb24obil7bGV0IHQ9T0sobik7cmV0dXJuIHQucHVzaChQVihuLnRlbXBsYXRlLm5nQ29udGVudFNlbGVjdG9ycykpLHQucHVzaCh1bChodChuLmlzU3RhbmRhbG9uZSkpKSx1bChUbih0ZS5Db21wb25lbnREZWNsYXJhdGlvbix0KSl9KG4pO3JldHVybntleHByZXNzaW9uOm0sdHlwZTp4LHN0YXRlbWVudHM6W119fShpLHIsREQoaS5pbnRlcnBvbGF0aW9uKSk7cmV0dXJuIHRoaXMuaml0RXhwcmVzc2lvbihzLmV4cHJlc3Npb24sdCxlLHIuc3RhdGVtZW50cyl9Y29tcGlsZUZhY3RvcnkodCxlLGkpe2xldCByPW5tKHtuYW1lOmkubmFtZSx0eXBlOkFzKGkudHlwZSksaW50ZXJuYWxUeXBlOm5ldyBMbihpLnR5cGUpLHR5cGVBcmd1bWVudENvdW50OmkudHlwZUFyZ3VtZW50Q291bnQsZGVwczoobj1pLmRlcHMsbnVsbD09bj9udWxsOm4ubWFwKExLKSksdGFyZ2V0OmkudGFyZ2V0fSk7dmFyIG47cmV0dXJuIHRoaXMuaml0RXhwcmVzc2lvbihyLmV4cHJlc3Npb24sdCxlLHIuc3RhdGVtZW50cyl9Y29tcGlsZUZhY3RvcnlEZWNsYXJhdGlvbih0LGUsaSl7bGV0IHI9bm0oe25hbWU6aS50eXBlLm5hbWUsdHlwZTpBcyhpLnR5cGUpLGludGVybmFsVHlwZTpuZXcgTG4oaS50eXBlKSx0eXBlQXJndW1lbnRDb3VudDowLGRlcHM6QXJyYXkuaXNBcnJheShpLmRlcHMpP2kuZGVwcy5tYXAoUFEpOmkuZGVwcyx0YXJnZXQ6aS50YXJnZXR9KTtyZXR1cm4gdGhpcy5qaXRFeHByZXNzaW9uKHIuZXhwcmVzc2lvbix0LGUsci5zdGF0ZW1lbnRzKX1jcmVhdGVQYXJzZVNvdXJjZVNwYW4odCxlLGkpe3JldHVybiBmdW5jdGlvbihuLHQsZSl7bGV0IHI9bmV3IHBEKCIiLGBpbiAke259ICR7dH0gaW4gJHtlfWApO3JldHVybiBuZXcgR28obmV3IHZtKHIsLTEsLTEsLTEpLG5ldyB2bShyLC0xLC0xLC0xKSl9KHQsZSxpKX1qaXRFeHByZXNzaW9uKHQsZSxpLHIpe2xldCBvPVsuLi5yLG5ldyBWdSgiJGRlZiIsdCx2b2lkIDAsbGwuRXhwb3J0ZWQpXTtyZXR1cm4gdGhpcy5qaXRFdmFsdWF0b3IuZXZhbHVhdGVTdGF0ZW1lbnRzKGksbyxuZXcgY2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5jb250ZXh0PXR9cmVzb2x2ZUV4dGVybmFsUmVmZXJlbmNlKHQpe2lmKCJAYW5ndWxhci9jb3JlIiE9PXQubW9kdWxlTmFtZSl0aHJvdyBuZXcgRXJyb3IoYENhbm5vdCByZXNvbHZlIGV4dGVybmFsIHJlZmVyZW5jZSB0byAke3QubW9kdWxlTmFtZX0sIG9ubHkgcmVmZXJlbmNlcyB0byBAYW5ndWxhci9jb3JlIGFyZSBzdXBwb3J0ZWQuYCk7aWYoIXRoaXMuY29udGV4dC5oYXNPd25Qcm9wZXJ0eSh0Lm5hbWUpKXRocm93IG5ldyBFcnJvcihgTm8gdmFsdWUgcHJvdmlkZWQgZm9yIEBhbmd1bGFyL2NvcmUgc3ltYm9sICcke3QubmFtZX0nLmApO3JldHVybiB0aGlzLmNvbnRleHRbdC5uYW1lXX19KGUpLCEwKS4kZGVmfX07dmFyIE9EPWZ1bmN0aW9uKG4sLi4udCl7aWYoT0QudHJhbnNsYXRlKXtsZXQgaT1PRC50cmFuc2xhdGUobix0KTtuPWlbMF0sdD1pWzFdfWxldCBlPVZLKG5bMF0sbi5yYXdbMF0pO2ZvcihsZXQgaT0xO2k8bi5sZW5ndGg7aSsrKWUrPXRbaS0xXStWSyhuW2ldLG4ucmF3W2ldKTtyZXR1cm4gZX07ZnVuY3Rpb24gVksobix0KXtyZXR1cm4iOiI9PT10LmNoYXJBdCgwKT9uLnN1YnN0cmluZyhmdW5jdGlvbihuLHQpe2ZvcihsZXQgZT0xLGk9MTtlPG4ubGVuZ3RoO2UrKyxpKyspaWYoIlxcIj09PXRbaV0paSsrO2Vsc2UgaWYoIjoiPT09bltlXSlyZXR1cm4gZTt0aHJvdyBuZXcgRXJyb3IoYFVudGVybWluYXRlZCAkbG9jYWxpemUgbWV0YWRhdGEgYmxvY2sgaW4gIiR7dH0iLmApfShuLHQpKzEpOm59KCgpPT50eXBlb2YgZ2xvYmFsVGhpczwidSImJmdsb2JhbFRoaXN8fHR5cGVvZiBnbG9iYWw8InUiJiZnbG9iYWx8fHR5cGVvZiB3aW5kb3c8InUiJiZ3aW5kb3d8fHR5cGVvZiBzZWxmPCJ1IiYmdHlwZW9mIFdvcmtlckdsb2JhbFNjb3BlPCJ1IiYmc2VsZiBpbnN0YW5jZW9mIFdvcmtlckdsb2JhbFNjb3BlJiZzZWxmKSgpLiRsb2NhbGl6ZT1PRDt2YXIgcjU9bnVsbDtmdW5jdGlvbiBZbCgpe3JldHVybiByNX12YXIgekQ9Y2xhc3N7fSxIdD1uZXcgcGUoIkRvY3VtZW50VG9rZW4iKSxLSz0oKCk9PntjbGFzcyBue2hpc3RvcnlHbyhlKXt0aHJvdyBuZXcgRXJyb3IoIk5vdCBpbXBsZW1lbnRlZCIpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6ZnVuY3Rpb24oKXtyZXR1cm4gaihaSyl9LHByb3ZpZGVkSW46InBsYXRmb3JtIn0pLG59KSgpO25ldyBwZSgiTG9jYXRpb24gSW5pdGlhbGl6ZWQiKTt2YXIgWks9KCgpPT57Y2xhc3MgbiBleHRlbmRzIEtLe2NvbnN0cnVjdG9yKGUpe3N1cGVyKCksdGhpcy5fZG9jPWUsdGhpcy5faW5pdCgpfV9pbml0KCl7dGhpcy5sb2NhdGlvbj13aW5kb3cubG9jYXRpb24sdGhpcy5faGlzdG9yeT13aW5kb3cuaGlzdG9yeX1nZXRCYXNlSHJlZkZyb21ET00oKXtyZXR1cm4gWWwoKS5nZXRCYXNlSHJlZih0aGlzLl9kb2MpfW9uUG9wU3RhdGUoZSl7bGV0IGk9WWwoKS5nZXRHbG9iYWxFdmVudFRhcmdldCh0aGlzLl9kb2MsIndpbmRvdyIpO3JldHVybiBpLmFkZEV2ZW50TGlzdGVuZXIoInBvcHN0YXRlIixlLCExKSwoKT0+aS5yZW1vdmVFdmVudExpc3RlbmVyKCJwb3BzdGF0ZSIsZSl9b25IYXNoQ2hhbmdlKGUpe2xldCBpPVlsKCkuZ2V0R2xvYmFsRXZlbnRUYXJnZXQodGhpcy5fZG9jLCJ3aW5kb3ciKTtyZXR1cm4gaS5hZGRFdmVudExpc3RlbmVyKCJoYXNoY2hhbmdlIixlLCExKSwoKT0+aS5yZW1vdmVFdmVudExpc3RlbmVyKCJoYXNoY2hhbmdlIixlKX1nZXQgaHJlZigpe3JldHVybiB0aGlzLmxvY2F0aW9uLmhyZWZ9Z2V0IHByb3RvY29sKCl7cmV0dXJuIHRoaXMubG9jYXRpb24ucHJvdG9jb2x9Z2V0IGhvc3RuYW1lKCl7cmV0dXJuIHRoaXMubG9jYXRpb24uaG9zdG5hbWV9Z2V0IHBvcnQoKXtyZXR1cm4gdGhpcy5sb2NhdGlvbi5wb3J0fWdldCBwYXRobmFtZSgpe3JldHVybiB0aGlzLmxvY2F0aW9uLnBhdGhuYW1lfWdldCBzZWFyY2goKXtyZXR1cm4gdGhpcy5sb2NhdGlvbi5zZWFyY2h9Z2V0IGhhc2goKXtyZXR1cm4gdGhpcy5sb2NhdGlvbi5oYXNofXNldCBwYXRobmFtZShlKXt0aGlzLmxvY2F0aW9uLnBhdGhuYW1lPWV9cHVzaFN0YXRlKGUsaSxyKXtVSygpP3RoaXMuX2hpc3RvcnkucHVzaFN0YXRlKGUsaSxyKTp0aGlzLmxvY2F0aW9uLmhhc2g9cn1yZXBsYWNlU3RhdGUoZSxpLHIpe1VLKCk/dGhpcy5faGlzdG9yeS5yZXBsYWNlU3RhdGUoZSxpLHIpOnRoaXMubG9jYXRpb24uaGFzaD1yfWZvcndhcmQoKXt0aGlzLl9oaXN0b3J5LmZvcndhcmQoKX1iYWNrKCl7dGhpcy5faGlzdG9yeS5iYWNrKCl9aGlzdG9yeUdvKGU9MCl7dGhpcy5faGlzdG9yeS5nbyhlKX1nZXRTdGF0ZSgpe3JldHVybiB0aGlzLl9oaXN0b3J5LnN0YXRlfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKEh0KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5OmZ1bmN0aW9uKCl7cmV0dXJuIG5ldyBaSyhqKEh0KSl9LHByb3ZpZGVkSW46InBsYXRmb3JtIn0pLG59KSgpO2Z1bmN0aW9uIFVLKCl7cmV0dXJuISF3aW5kb3cuaGlzdG9yeS5wdXNoU3RhdGV9ZnVuY3Rpb24gSksobix0KXtpZigwPT1uLmxlbmd0aClyZXR1cm4gdDtpZigwPT10Lmxlbmd0aClyZXR1cm4gbjtsZXQgZT0wO3JldHVybiBuLmVuZHNXaXRoKCIvIikmJmUrKyx0LnN0YXJ0c1dpdGgoIi8iKSYmZSsrLDI9PWU/bit0LnN1YnN0cmluZygxKToxPT1lP24rdDpuKyIvIit0fWZ1bmN0aW9uIHpLKG4pe2xldCB0PW4ubWF0Y2goLyN8XD98JC8pLGU9dCYmdC5pbmRleHx8bi5sZW5ndGg7cmV0dXJuIG4uc2xpY2UoMCxlLSgiLyI9PT1uW2UtMV0/MTowKSkrbi5zbGljZShlKX1mdW5jdGlvbiBTbShuKXtyZXR1cm4gbiYmIj8iIT09blswXT8iPyIrbjpufXZhciBjNT0oKCk9PntjbGFzcyBue2hpc3RvcnlHbyhlKXt0aHJvdyBuZXcgRXJyb3IoIk5vdCBpbXBsZW1lbnRlZCIpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6ZnVuY3Rpb24oKXtyZXR1cm4gam8oeVRlKX0scHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKSx2VGU9bmV3IHBlKCJhcHBCYXNlSHJlZiIpLHlUZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgYzV7Y29uc3RydWN0b3IoZSxpKXtzdXBlcigpLHRoaXMuX3BsYXRmb3JtTG9jYXRpb249ZSx0aGlzLl9yZW1vdmVMaXN0ZW5lckZucz1bXSx0aGlzLl9iYXNlSHJlZj1pPz90aGlzLl9wbGF0Zm9ybUxvY2F0aW9uLmdldEJhc2VIcmVmRnJvbURPTSgpPz9qbyhIdCkubG9jYXRpb24/Lm9yaWdpbj8/IiJ9bmdPbkRlc3Ryb3koKXtmb3IoO3RoaXMuX3JlbW92ZUxpc3RlbmVyRm5zLmxlbmd0aDspdGhpcy5fcmVtb3ZlTGlzdGVuZXJGbnMucG9wKCkoKX1vblBvcFN0YXRlKGUpe3RoaXMuX3JlbW92ZUxpc3RlbmVyRm5zLnB1c2godGhpcy5fcGxhdGZvcm1Mb2NhdGlvbi5vblBvcFN0YXRlKGUpLHRoaXMuX3BsYXRmb3JtTG9jYXRpb24ub25IYXNoQ2hhbmdlKGUpKX1nZXRCYXNlSHJlZigpe3JldHVybiB0aGlzLl9iYXNlSHJlZn1wcmVwYXJlRXh0ZXJuYWxVcmwoZSl7cmV0dXJuIEpLKHRoaXMuX2Jhc2VIcmVmLGUpfXBhdGgoZT0hMSl7bGV0IGk9dGhpcy5fcGxhdGZvcm1Mb2NhdGlvbi5wYXRobmFtZStTbSh0aGlzLl9wbGF0Zm9ybUxvY2F0aW9uLnNlYXJjaCkscj10aGlzLl9wbGF0Zm9ybUxvY2F0aW9uLmhhc2g7cmV0dXJuIHImJmU/YCR7aX0ke3J9YDppfXB1c2hTdGF0ZShlLGkscixvKXtsZXQgcz10aGlzLnByZXBhcmVFeHRlcm5hbFVybChyK1NtKG8pKTt0aGlzLl9wbGF0Zm9ybUxvY2F0aW9uLnB1c2hTdGF0ZShlLGkscyl9cmVwbGFjZVN0YXRlKGUsaSxyLG8pe2xldCBzPXRoaXMucHJlcGFyZUV4dGVybmFsVXJsKHIrU20obykpO3RoaXMuX3BsYXRmb3JtTG9jYXRpb24ucmVwbGFjZVN0YXRlKGUsaSxzKX1mb3J3YXJkKCl7dGhpcy5fcGxhdGZvcm1Mb2NhdGlvbi5mb3J3YXJkKCl9YmFjaygpe3RoaXMuX3BsYXRmb3JtTG9jYXRpb24uYmFjaygpfWdldFN0YXRlKCl7cmV0dXJuIHRoaXMuX3BsYXRmb3JtTG9jYXRpb24uZ2V0U3RhdGUoKX1oaXN0b3J5R28oZT0wKXt0aGlzLl9wbGF0Zm9ybUxvY2F0aW9uLmhpc3RvcnlHbz8uKGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKEtLKSxqKHZUZSw4KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksaU09KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLl9zdWJqZWN0PW5ldyBHLHRoaXMuX3VybENoYW5nZUxpc3RlbmVycz1bXSx0aGlzLl91cmxDaGFuZ2VTdWJzY3JpcHRpb249bnVsbCx0aGlzLl9sb2NhdGlvblN0cmF0ZWd5PWU7bGV0IGk9dGhpcy5fbG9jYXRpb25TdHJhdGVneS5nZXRCYXNlSHJlZigpO3RoaXMuX2Jhc2VIcmVmPXpLKGpLKGkpKSx0aGlzLl9sb2NhdGlvblN0cmF0ZWd5Lm9uUG9wU3RhdGUocj0+e3RoaXMuX3N1YmplY3QuZW1pdCh7dXJsOnRoaXMucGF0aCghMCkscG9wOiEwLHN0YXRlOnIuc3RhdGUsdHlwZTpyLnR5cGV9KX0pfW5nT25EZXN0cm95KCl7dGhpcy5fdXJsQ2hhbmdlU3Vic2NyaXB0aW9uPy51bnN1YnNjcmliZSgpLHRoaXMuX3VybENoYW5nZUxpc3RlbmVycz1bXX1wYXRoKGU9ITEpe3JldHVybiB0aGlzLm5vcm1hbGl6ZSh0aGlzLl9sb2NhdGlvblN0cmF0ZWd5LnBhdGgoZSkpfWdldFN0YXRlKCl7cmV0dXJuIHRoaXMuX2xvY2F0aW9uU3RyYXRlZ3kuZ2V0U3RhdGUoKX1pc0N1cnJlbnRQYXRoRXF1YWxUbyhlLGk9IiIpe3JldHVybiB0aGlzLnBhdGgoKT09dGhpcy5ub3JtYWxpemUoZStTbShpKSl9bm9ybWFsaXplKGUpe3JldHVybiBuLnN0cmlwVHJhaWxpbmdTbGFzaChmdW5jdGlvbihuLHQpe3JldHVybiBuJiZ0LnN0YXJ0c1dpdGgobik/dC5zdWJzdHJpbmcobi5sZW5ndGgpOnR9KHRoaXMuX2Jhc2VIcmVmLGpLKGUpKSl9cHJlcGFyZUV4dGVybmFsVXJsKGUpe3JldHVybiBlJiYiLyIhPT1lWzBdJiYoZT0iLyIrZSksdGhpcy5fbG9jYXRpb25TdHJhdGVneS5wcmVwYXJlRXh0ZXJuYWxVcmwoZSl9Z28oZSxpPSIiLHI9bnVsbCl7dGhpcy5fbG9jYXRpb25TdHJhdGVneS5wdXNoU3RhdGUociwiIixlLGkpLHRoaXMuX25vdGlmeVVybENoYW5nZUxpc3RlbmVycyh0aGlzLnByZXBhcmVFeHRlcm5hbFVybChlK1NtKGkpKSxyKX1yZXBsYWNlU3RhdGUoZSxpPSIiLHI9bnVsbCl7dGhpcy5fbG9jYXRpb25TdHJhdGVneS5yZXBsYWNlU3RhdGUociwiIixlLGkpLHRoaXMuX25vdGlmeVVybENoYW5nZUxpc3RlbmVycyh0aGlzLnByZXBhcmVFeHRlcm5hbFVybChlK1NtKGkpKSxyKX1mb3J3YXJkKCl7dGhpcy5fbG9jYXRpb25TdHJhdGVneS5mb3J3YXJkKCl9YmFjaygpe3RoaXMuX2xvY2F0aW9uU3RyYXRlZ3kuYmFjaygpfWhpc3RvcnlHbyhlPTApe3RoaXMuX2xvY2F0aW9uU3RyYXRlZ3kuaGlzdG9yeUdvPy4oZSl9b25VcmxDaGFuZ2UoZSl7cmV0dXJuIHRoaXMuX3VybENoYW5nZUxpc3RlbmVycy5wdXNoKGUpLHRoaXMuX3VybENoYW5nZVN1YnNjcmlwdGlvbnx8KHRoaXMuX3VybENoYW5nZVN1YnNjcmlwdGlvbj10aGlzLnN1YnNjcmliZShpPT57dGhpcy5fbm90aWZ5VXJsQ2hhbmdlTGlzdGVuZXJzKGkudXJsLGkuc3RhdGUpfSkpLCgpPT57bGV0IGk9dGhpcy5fdXJsQ2hhbmdlTGlzdGVuZXJzLmluZGV4T2YoZSk7dGhpcy5fdXJsQ2hhbmdlTGlzdGVuZXJzLnNwbGljZShpLDEpLDA9PT10aGlzLl91cmxDaGFuZ2VMaXN0ZW5lcnMubGVuZ3RoJiYodGhpcy5fdXJsQ2hhbmdlU3Vic2NyaXB0aW9uPy51bnN1YnNjcmliZSgpLHRoaXMuX3VybENoYW5nZVN1YnNjcmlwdGlvbj1udWxsKX19X25vdGlmeVVybENoYW5nZUxpc3RlbmVycyhlPSIiLGkpe3RoaXMuX3VybENoYW5nZUxpc3RlbmVycy5mb3JFYWNoKHI9PnIoZSxpKSl9c3Vic2NyaWJlKGUsaSxyKXtyZXR1cm4gdGhpcy5fc3ViamVjdC5zdWJzY3JpYmUoe25leHQ6ZSxlcnJvcjppLGNvbXBsZXRlOnJ9KX19cmV0dXJuIG4ubm9ybWFsaXplUXVlcnlQYXJhbXM9U20sbi5qb2luV2l0aFNsYXNoPUpLLG4uc3RyaXBUcmFpbGluZ1NsYXNoPXpLLG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihjNSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpmdW5jdGlvbigpe3JldHVybiBuZXcgaU0oaihjNSkpfSxwcm92aWRlZEluOiJyb290In0pLG59KSgpO2Z1bmN0aW9uIGpLKG4pe3JldHVybiBuLnJlcGxhY2UoL1wvaW5kZXguaHRtbCQvLCIiKX12YXIgSEQ9KCgpPT4oZnVuY3Rpb24obil7bltuLkRlY2ltYWw9MF09IkRlY2ltYWwiLG5bbi5QZXJjZW50PTFdPSJQZXJjZW50IixuW24uQ3VycmVuY3k9Ml09IkN1cnJlbmN5IixuW24uU2NpZW50aWZpYz0zXT0iU2NpZW50aWZpYyJ9KEhEfHwoSEQ9e30pKSxIRCkpKCkseXM9KCgpPT4oZnVuY3Rpb24obil7bltuLkZvcm1hdD0wXT0iRm9ybWF0IixuW24uU3RhbmRhbG9uZT0xXT0iU3RhbmRhbG9uZSJ9KHlzfHwoeXM9e30pKSx5cykpKCksb3I9KCgpPT4oZnVuY3Rpb24obil7bltuLk5hcnJvdz0wXT0iTmFycm93IixuW24uQWJicmV2aWF0ZWQ9MV09IkFiYnJldmlhdGVkIixuW24uV2lkZT0yXT0iV2lkZSIsbltuLlNob3J0PTNdPSJTaG9ydCJ9KG9yfHwob3I9e30pKSxvcikpKCksb2E9KCgpPT4oZnVuY3Rpb24obil7bltuLlNob3J0PTBdPSJTaG9ydCIsbltuLk1lZGl1bT0xXT0iTWVkaXVtIixuW24uTG9uZz0yXT0iTG9uZyIsbltuLkZ1bGw9M109IkZ1bGwifShvYXx8KG9hPXt9KSksb2EpKSgpLElzPSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5EZWNpbWFsPTBdPSJEZWNpbWFsIixuW24uR3JvdXA9MV09Ikdyb3VwIixuW24uTGlzdD0yXT0iTGlzdCIsbltuLlBlcmNlbnRTaWduPTNdPSJQZXJjZW50U2lnbiIsbltuLlBsdXNTaWduPTRdPSJQbHVzU2lnbiIsbltuLk1pbnVzU2lnbj01XT0iTWludXNTaWduIixuW24uRXhwb25lbnRpYWw9Nl09IkV4cG9uZW50aWFsIixuW24uU3VwZXJzY3JpcHRpbmdFeHBvbmVudD03XT0iU3VwZXJzY3JpcHRpbmdFeHBvbmVudCIsbltuLlBlck1pbGxlPThdPSJQZXJNaWxsZSIsbltuLkluZmluaXR5PTldPSJJbmZpbml0eSIsbltuLk5hTj0xMF09Ik5hTiIsbltuLlRpbWVTZXBhcmF0b3I9MTFdPSJUaW1lU2VwYXJhdG9yIixuW24uQ3VycmVuY3lEZWNpbWFsPTEyXT0iQ3VycmVuY3lEZWNpbWFsIixuW24uQ3VycmVuY3lHcm91cD0xM109IkN1cnJlbmN5R3JvdXAifShJc3x8KElzPXt9KSksSXMpKSgpO2Z1bmN0aW9uIGtEKG4sdCl7cmV0dXJuIFhsKEFhKG4pW1JyLkRhdGVGb3JtYXRdLHQpfWZ1bmN0aW9uIEZEKG4sdCl7cmV0dXJuIFhsKEFhKG4pW1JyLlRpbWVGb3JtYXRdLHQpfWZ1bmN0aW9uIE5EKG4sdCl7cmV0dXJuIFhsKEFhKG4pW1JyLkRhdGVUaW1lRm9ybWF0XSx0KX1mdW5jdGlvbiBlcChuLHQpe2xldCBlPUFhKG4pLGk9ZVtSci5OdW1iZXJTeW1ib2xzXVt0XTtpZih0eXBlb2YgaT4idSIpe2lmKHQ9PT1Jcy5DdXJyZW5jeURlY2ltYWwpcmV0dXJuIGVbUnIuTnVtYmVyU3ltYm9sc11bSXMuRGVjaW1hbF07aWYodD09PUlzLkN1cnJlbmN5R3JvdXApcmV0dXJuIGVbUnIuTnVtYmVyU3ltYm9sc11bSXMuR3JvdXBdfXJldHVybiBpfWZ1bmN0aW9uICRLKG4pe2lmKCFuW1JyLkV4dHJhRGF0YV0pdGhyb3cgbmV3IEVycm9yKGBNaXNzaW5nIGV4dHJhIGxvY2FsZSBkYXRhIGZvciB0aGUgbG9jYWxlICIke25bUnIuTG9jYWxlSWRdfSIuIFVzZSAicmVnaXN0ZXJMb2NhbGVEYXRhIiB0byBsb2FkIG5ldyBkYXRhLiBTZWUgdGhlICJJMThuIGd1aWRlIiBvbiBhbmd1bGFyLmlvIHRvIGtub3cgbW9yZS5gKX1mdW5jdGlvbiBYbChuLHQpe2ZvcihsZXQgZT10O2U+LTE7ZS0tKWlmKHR5cGVvZiBuW2VdPCJ1IilyZXR1cm4gbltlXTt0aHJvdyBuZXcgRXJyb3IoIkxvY2FsZSBkYXRhIEFQSTogbG9jYWxlIGRhdGEgdW5kZWZpbmVkIil9ZnVuY3Rpb24gJFYobil7bGV0W3QsZV09bi5zcGxpdCgiOiIpO3JldHVybntob3VyczordCxtaW51dGVzOitlfX12YXIgSVRlPS9eKFxkezQsfSktPyhcZFxkKS0/KFxkXGQpKD86VChcZFxkKSg/Ojo/KFxkXGQpKD86Oj8oXGRcZCkoPzpcLihcZCspKT8pPyk/KFp8KFsrLV0pKFxkXGQpOj8oXGRcZCkpPyk/JC8sdE09e30sUFRlPS8oKD86W15CRUdITE1PU1dZWmFiY2RobXN3eXonXSspfCg/OicoPzpbXiddfCcnKSonKXwoPzpHezEsNX18eXsxLDR9fFl7MSw0fXxNezEsNX18THsxLDV9fHd7MSwyfXxXezF9fGR7MSwyfXxFezEsNn18Y3sxLDZ9fGF7MSw1fXxiezEsNX18QnsxLDV9fGh7MSwyfXxIezEsMn18bXsxLDJ9fHN7MSwyfXxTezEsM318ensxLDR9fFp7MSw1fXxPezEsNH0pKShbXHNcU10qKS8sV2M9KCgpPT4oZnVuY3Rpb24obil7bltuLlNob3J0PTBdPSJTaG9ydCIsbltuLlNob3J0R01UPTFdPSJTaG9ydEdNVCIsbltuLkxvbmc9Ml09IkxvbmciLG5bbi5FeHRlbmRlZD0zXT0iRXh0ZW5kZWQifShXY3x8KFdjPXt9KSksV2MpKSgpLFVpPSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5GdWxsWWVhcj0wXT0iRnVsbFllYXIiLG5bbi5Nb250aD0xXT0iTW9udGgiLG5bbi5EYXRlPTJdPSJEYXRlIixuW24uSG91cnM9M109IkhvdXJzIixuW24uTWludXRlcz00XT0iTWludXRlcyIsbltuLlNlY29uZHM9NV09IlNlY29uZHMiLG5bbi5GcmFjdGlvbmFsU2Vjb25kcz02XT0iRnJhY3Rpb25hbFNlY29uZHMiLG5bbi5EYXk9N109IkRheSJ9KFVpfHwoVWk9e30pKSxVaSkpKCksSGk9KCgpPT4oZnVuY3Rpb24obil7bltuLkRheVBlcmlvZHM9MF09IkRheVBlcmlvZHMiLG5bbi5EYXlzPTFdPSJEYXlzIixuW24uTW9udGhzPTJdPSJNb250aHMiLG5bbi5FcmFzPTNdPSJFcmFzIn0oSGl8fChIaT17fSkpLEhpKSkoKTtmdW5jdGlvbiBSVGUobix0LGUsaSl7bGV0IHI9ZnVuY3Rpb24obil7aWYoR0sobikpcmV0dXJuIG47aWYoIm51bWJlciI9PXR5cGVvZiBuJiYhaXNOYU4obikpcmV0dXJuIG5ldyBEYXRlKG4pO2lmKCJzdHJpbmciPT10eXBlb2Ygbil7aWYobj1uLnRyaW0oKSwvXihcZHs0fSgtXGR7MSwyfSgtXGR7MSwyfSk/KT8pJC8udGVzdChuKSl7bGV0W3Isbz0xLHM9MV09bi5zcGxpdCgiLSIpLm1hcChhPT4rYSk7cmV0dXJuIGpEKHIsby0xLHMpfWxldCBpLGU9cGFyc2VGbG9hdChuKTtpZighaXNOYU4obi1lKSlyZXR1cm4gbmV3IERhdGUoZSk7aWYoaT1uLm1hdGNoKElUZSkpcmV0dXJuIGZ1bmN0aW9uKG4pe2xldCB0PW5ldyBEYXRlKDApLGU9MCxpPTAscj1uWzhdP3Quc2V0VVRDRnVsbFllYXI6dC5zZXRGdWxsWWVhcixvPW5bOF0/dC5zZXRVVENIb3Vyczp0LnNldEhvdXJzO25bOV0mJihlPU51bWJlcihuWzldK25bMTBdKSxpPU51bWJlcihuWzldK25bMTFdKSksci5jYWxsKHQsTnVtYmVyKG5bMV0pLE51bWJlcihuWzJdKS0xLE51bWJlcihuWzNdKSk7bGV0IHM9TnVtYmVyKG5bNF18fDApLWUsYT1OdW1iZXIobls1XXx8MCktaSxsPU51bWJlcihuWzZdfHwwKSxjPU1hdGguZmxvb3IoMWUzKnBhcnNlRmxvYXQoIjAuIisobls3XXx8MCkpKTtyZXR1cm4gby5jYWxsKHQscyxhLGwsYyksdH0oaSl9bGV0IHQ9bmV3IERhdGUobik7aWYoIUdLKHQpKXRocm93IG5ldyBFcnJvcihgVW5hYmxlIHRvIGNvbnZlcnQgIiR7bn0iIGludG8gYSBkYXRlYCk7cmV0dXJuIHR9KG4pO3Q9JGQoZSx0KXx8dDtsZXQgYSxzPVtdO2Zvcig7dDspe2lmKGE9UFRlLmV4ZWModCksIWEpe3MucHVzaCh0KTticmVha317cz1zLmNvbmNhdChhLnNsaWNlKDEpKTtsZXQgdT1zLnBvcCgpO2lmKCF1KWJyZWFrO3Q9dX19bGV0IGw9ci5nZXRUaW1lem9uZU9mZnNldCgpO2kmJihsPXRaKGksbCkscj1mdW5jdGlvbihuLHQsZSl7bGV0IHI9bi5nZXRUaW1lem9uZU9mZnNldCgpO3JldHVybiBmdW5jdGlvbihuLHQpe3JldHVybihuPW5ldyBEYXRlKG4uZ2V0VGltZSgpKSkuc2V0TWludXRlcyhuLmdldE1pbnV0ZXMoKSt0KSxufShuLC0xKih0Wih0LHIpLXIpKX0ocixpKSk7bGV0IGM9IiI7cmV0dXJuIHMuZm9yRWFjaCh1PT57bGV0IGQ9ZnVuY3Rpb24obil7aWYodDVbbl0pcmV0dXJuIHQ1W25dO2xldCB0O3N3aXRjaChuKXtjYXNlIkciOmNhc2UiR0ciOmNhc2UiR0dHIjp0PXhyKEhpLkVyYXMsb3IuQWJicmV2aWF0ZWQpO2JyZWFrO2Nhc2UiR0dHRyI6dD14cihIaS5FcmFzLG9yLldpZGUpO2JyZWFrO2Nhc2UiR0dHR0ciOnQ9eHIoSGkuRXJhcyxvci5OYXJyb3cpO2JyZWFrO2Nhc2UieSI6dD1JbyhVaS5GdWxsWWVhciwxLDAsITEsITApO2JyZWFrO2Nhc2UieXkiOnQ9SW8oVWkuRnVsbFllYXIsMiwwLCEwLCEwKTticmVhaztjYXNlInl5eSI6dD1JbyhVaS5GdWxsWWVhciwzLDAsITEsITApO2JyZWFrO2Nhc2UieXl5eSI6dD1JbyhVaS5GdWxsWWVhciw0LDAsITEsITApO2JyZWFrO2Nhc2UiWSI6dD1WRCgxKTticmVhaztjYXNlIllZIjp0PVZEKDIsITApO2JyZWFrO2Nhc2UiWVlZIjp0PVZEKDMpO2JyZWFrO2Nhc2UiWVlZWSI6dD1WRCg0KTticmVhaztjYXNlIk0iOmNhc2UiTCI6dD1JbyhVaS5Nb250aCwxLDEpO2JyZWFrO2Nhc2UiTU0iOmNhc2UiTEwiOnQ9SW8oVWkuTW9udGgsMiwxKTticmVhaztjYXNlIk1NTSI6dD14cihIaS5Nb250aHMsb3IuQWJicmV2aWF0ZWQpO2JyZWFrO2Nhc2UiTU1NTSI6dD14cihIaS5Nb250aHMsb3IuV2lkZSk7YnJlYWs7Y2FzZSJNTU1NTSI6dD14cihIaS5Nb250aHMsb3IuTmFycm93KTticmVhaztjYXNlIkxMTCI6dD14cihIaS5Nb250aHMsb3IuQWJicmV2aWF0ZWQseXMuU3RhbmRhbG9uZSk7YnJlYWs7Y2FzZSJMTExMIjp0PXhyKEhpLk1vbnRocyxvci5XaWRlLHlzLlN0YW5kYWxvbmUpO2JyZWFrO2Nhc2UiTExMTEwiOnQ9eHIoSGkuTW9udGhzLG9yLk5hcnJvdyx5cy5TdGFuZGFsb25lKTticmVhaztjYXNlInciOnQ9ZTUoMSk7YnJlYWs7Y2FzZSJ3dyI6dD1lNSgyKTticmVhaztjYXNlIlciOnQ9ZTUoMSwhMCk7YnJlYWs7Y2FzZSJkIjp0PUlvKFVpLkRhdGUsMSk7YnJlYWs7Y2FzZSJkZCI6dD1JbyhVaS5EYXRlLDIpO2JyZWFrO2Nhc2UiYyI6Y2FzZSJjYyI6dD1JbyhVaS5EYXksMSk7YnJlYWs7Y2FzZSJjY2MiOnQ9eHIoSGkuRGF5cyxvci5BYmJyZXZpYXRlZCx5cy5TdGFuZGFsb25lKTticmVhaztjYXNlImNjY2MiOnQ9eHIoSGkuRGF5cyxvci5XaWRlLHlzLlN0YW5kYWxvbmUpO2JyZWFrO2Nhc2UiY2NjY2MiOnQ9eHIoSGkuRGF5cyxvci5OYXJyb3cseXMuU3RhbmRhbG9uZSk7YnJlYWs7Y2FzZSJjY2NjY2MiOnQ9eHIoSGkuRGF5cyxvci5TaG9ydCx5cy5TdGFuZGFsb25lKTticmVhaztjYXNlIkUiOmNhc2UiRUUiOmNhc2UiRUVFIjp0PXhyKEhpLkRheXMsb3IuQWJicmV2aWF0ZWQpO2JyZWFrO2Nhc2UiRUVFRSI6dD14cihIaS5EYXlzLG9yLldpZGUpO2JyZWFrO2Nhc2UiRUVFRUUiOnQ9eHIoSGkuRGF5cyxvci5OYXJyb3cpO2JyZWFrO2Nhc2UiRUVFRUVFIjp0PXhyKEhpLkRheXMsb3IuU2hvcnQpO2JyZWFrO2Nhc2UiYSI6Y2FzZSJhYSI6Y2FzZSJhYWEiOnQ9eHIoSGkuRGF5UGVyaW9kcyxvci5BYmJyZXZpYXRlZCk7YnJlYWs7Y2FzZSJhYWFhIjp0PXhyKEhpLkRheVBlcmlvZHMsb3IuV2lkZSk7YnJlYWs7Y2FzZSJhYWFhYSI6dD14cihIaS5EYXlQZXJpb2RzLG9yLk5hcnJvdyk7YnJlYWs7Y2FzZSJiIjpjYXNlImJiIjpjYXNlImJiYiI6dD14cihIaS5EYXlQZXJpb2RzLG9yLkFiYnJldmlhdGVkLHlzLlN0YW5kYWxvbmUsITApO2JyZWFrO2Nhc2UiYmJiYiI6dD14cihIaS5EYXlQZXJpb2RzLG9yLldpZGUseXMuU3RhbmRhbG9uZSwhMCk7YnJlYWs7Y2FzZSJiYmJiYiI6dD14cihIaS5EYXlQZXJpb2RzLG9yLk5hcnJvdyx5cy5TdGFuZGFsb25lLCEwKTticmVhaztjYXNlIkIiOmNhc2UiQkIiOmNhc2UiQkJCIjp0PXhyKEhpLkRheVBlcmlvZHMsb3IuQWJicmV2aWF0ZWQseXMuRm9ybWF0LCEwKTticmVhaztjYXNlIkJCQkIiOnQ9eHIoSGkuRGF5UGVyaW9kcyxvci5XaWRlLHlzLkZvcm1hdCwhMCk7YnJlYWs7Y2FzZSJCQkJCQiI6dD14cihIaS5EYXlQZXJpb2RzLG9yLk5hcnJvdyx5cy5Gb3JtYXQsITApO2JyZWFrO2Nhc2UiaCI6dD1JbyhVaS5Ib3VycywxLC0xMik7YnJlYWs7Y2FzZSJoaCI6dD1JbyhVaS5Ib3VycywyLC0xMik7YnJlYWs7Y2FzZSJIIjp0PUlvKFVpLkhvdXJzLDEpO2JyZWFrO2Nhc2UiSEgiOnQ9SW8oVWkuSG91cnMsMik7YnJlYWs7Y2FzZSJtIjp0PUlvKFVpLk1pbnV0ZXMsMSk7YnJlYWs7Y2FzZSJtbSI6dD1JbyhVaS5NaW51dGVzLDIpO2JyZWFrO2Nhc2UicyI6dD1JbyhVaS5TZWNvbmRzLDEpO2JyZWFrO2Nhc2Uic3MiOnQ9SW8oVWkuU2Vjb25kcywyKTticmVhaztjYXNlIlMiOnQ9SW8oVWkuRnJhY3Rpb25hbFNlY29uZHMsMSk7YnJlYWs7Y2FzZSJTUyI6dD1JbyhVaS5GcmFjdGlvbmFsU2Vjb25kcywyKTticmVhaztjYXNlIlNTUyI6dD1JbyhVaS5GcmFjdGlvbmFsU2Vjb25kcywzKTticmVhaztjYXNlIloiOmNhc2UiWloiOmNhc2UiWlpaIjp0PUJEKFdjLlNob3J0KTticmVhaztjYXNlIlpaWlpaIjp0PUJEKFdjLkV4dGVuZGVkKTticmVhaztjYXNlIk8iOmNhc2UiT08iOmNhc2UiT09PIjpjYXNlInoiOmNhc2UienoiOmNhc2Uienp6Ijp0PUJEKFdjLlNob3J0R01UKTticmVhaztjYXNlIk9PT08iOmNhc2UiWlpaWiI6Y2FzZSJ6enp6Ijp0PUJEKFdjLkxvbmcpO2JyZWFrO2RlZmF1bHQ6cmV0dXJuIG51bGx9cmV0dXJuIHQ1W25dPXQsdH0odSk7Yys9ZD9kKHIsZSxsKToiJyciPT09dT8iJyI6dS5yZXBsYWNlKC8oXid8JyQpL2csIiIpLnJlcGxhY2UoLycnL2csIiciKX0pLGN9ZnVuY3Rpb24gakQobix0LGUpe2xldCBpPW5ldyBEYXRlKDApO3JldHVybiBpLnNldEZ1bGxZZWFyKG4sdCxlKSxpLnNldEhvdXJzKDAsMCwwKSxpfWZ1bmN0aW9uICRkKG4sdCl7bGV0IGU9ZnVuY3Rpb24obil7cmV0dXJuIEFhKG4pW1JyLkxvY2FsZUlkXX0obik7aWYodE1bZV09dE1bZV18fHt9LHRNW2VdW3RdKXJldHVybiB0TVtlXVt0XTtsZXQgaT0iIjtzd2l0Y2godCl7Y2FzZSJzaG9ydERhdGUiOmk9a0QobixvYS5TaG9ydCk7YnJlYWs7Y2FzZSJtZWRpdW1EYXRlIjppPWtEKG4sb2EuTWVkaXVtKTticmVhaztjYXNlImxvbmdEYXRlIjppPWtEKG4sb2EuTG9uZyk7YnJlYWs7Y2FzZSJmdWxsRGF0ZSI6aT1rRChuLG9hLkZ1bGwpO2JyZWFrO2Nhc2Uic2hvcnRUaW1lIjppPUZEKG4sb2EuU2hvcnQpO2JyZWFrO2Nhc2UibWVkaXVtVGltZSI6aT1GRChuLG9hLk1lZGl1bSk7YnJlYWs7Y2FzZSJsb25nVGltZSI6aT1GRChuLG9hLkxvbmcpO2JyZWFrO2Nhc2UiZnVsbFRpbWUiOmk9RkQobixvYS5GdWxsKTticmVhaztjYXNlInNob3J0IjpsZXQgcj0kZChuLCJzaG9ydFRpbWUiKSxvPSRkKG4sInNob3J0RGF0ZSIpO2k9TEQoTkQobixvYS5TaG9ydCksW3Isb10pO2JyZWFrO2Nhc2UibWVkaXVtIjpsZXQgcz0kZChuLCJtZWRpdW1UaW1lIiksYT0kZChuLCJtZWRpdW1EYXRlIik7aT1MRChORChuLG9hLk1lZGl1bSksW3MsYV0pO2JyZWFrO2Nhc2UibG9uZyI6bGV0IGw9JGQobiwibG9uZ1RpbWUiKSxjPSRkKG4sImxvbmdEYXRlIik7aT1MRChORChuLG9hLkxvbmcpLFtsLGNdKTticmVhaztjYXNlImZ1bGwiOmxldCB1PSRkKG4sImZ1bGxUaW1lIiksZD0kZChuLCJmdWxsRGF0ZSIpO2k9TEQoTkQobixvYS5GdWxsKSxbdSxkXSl9cmV0dXJuIGkmJih0TVtlXVt0XT1pKSxpfWZ1bmN0aW9uIExEKG4sdCl7cmV0dXJuIHQmJihuPW4ucmVwbGFjZSgvXHsoW159XSspfS9nLGZ1bmN0aW9uKGUsaSl7cmV0dXJuIG51bGwhPXQmJmkgaW4gdD90W2ldOmV9KSksbn1mdW5jdGlvbiBHYyhuLHQsZT0iLSIsaSxyKXtsZXQgbz0iIjsobjwwfHxyJiZuPD0wKSYmKHI/bj0xLW46KG49LW4sbz1lKSk7bGV0IHM9U3RyaW5nKG4pO2Zvcig7cy5sZW5ndGg8dDspcz0iMCIrcztyZXR1cm4gaSYmKHM9cy5zbGljZShzLmxlbmd0aC10KSksbytzfWZ1bmN0aW9uIElvKG4sdCxlPTAsaT0hMSxyPSExKXtyZXR1cm4gZnVuY3Rpb24obyxzKXtsZXQgYT1mdW5jdGlvbihuLHQpe3N3aXRjaChuKXtjYXNlIFVpLkZ1bGxZZWFyOnJldHVybiB0LmdldEZ1bGxZZWFyKCk7Y2FzZSBVaS5Nb250aDpyZXR1cm4gdC5nZXRNb250aCgpO2Nhc2UgVWkuRGF0ZTpyZXR1cm4gdC5nZXREYXRlKCk7Y2FzZSBVaS5Ib3VyczpyZXR1cm4gdC5nZXRIb3VycygpO2Nhc2UgVWkuTWludXRlczpyZXR1cm4gdC5nZXRNaW51dGVzKCk7Y2FzZSBVaS5TZWNvbmRzOnJldHVybiB0LmdldFNlY29uZHMoKTtjYXNlIFVpLkZyYWN0aW9uYWxTZWNvbmRzOnJldHVybiB0LmdldE1pbGxpc2Vjb25kcygpO2Nhc2UgVWkuRGF5OnJldHVybiB0LmdldERheSgpO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKGBVbmtub3duIERhdGVUeXBlIHZhbHVlICIke259Ii5gKX19KG4sbyk7aWYoKGU+MHx8YT4tZSkmJihhKz1lKSxuPT09VWkuSG91cnMpMD09PWEmJi0xMj09PWUmJihhPTEyKTtlbHNlIGlmKG49PT1VaS5GcmFjdGlvbmFsU2Vjb25kcylyZXR1cm4gZnVuY3Rpb24obix0KXtyZXR1cm4gR2MobiwzKS5zdWJzdHJpbmcoMCx0KX0oYSx0KTtsZXQgbD1lcChzLElzLk1pbnVzU2lnbik7cmV0dXJuIEdjKGEsdCxsLGkscil9fWZ1bmN0aW9uIHhyKG4sdCxlPXlzLkZvcm1hdCxpPSExKXtyZXR1cm4gZnVuY3Rpb24ocixvKXtyZXR1cm4gZnVuY3Rpb24obix0LGUsaSxyLG8pe3N3aXRjaChlKXtjYXNlIEhpLk1vbnRoczpyZXR1cm4gZnVuY3Rpb24obix0LGUpe2xldCBpPUFhKG4pLG89WGwoW2lbUnIuTW9udGhzRm9ybWF0XSxpW1JyLk1vbnRoc1N0YW5kYWxvbmVdXSx0KTtyZXR1cm4gWGwobyxlKX0odCxyLGkpW24uZ2V0TW9udGgoKV07Y2FzZSBIaS5EYXlzOnJldHVybiBmdW5jdGlvbihuLHQsZSl7bGV0IGk9QWEobiksbz1YbChbaVtSci5EYXlzRm9ybWF0XSxpW1JyLkRheXNTdGFuZGFsb25lXV0sdCk7cmV0dXJuIFhsKG8sZSl9KHQscixpKVtuLmdldERheSgpXTtjYXNlIEhpLkRheVBlcmlvZHM6bGV0IHM9bi5nZXRIb3VycygpLGE9bi5nZXRNaW51dGVzKCk7aWYobyl7bGV0IGM9ZnVuY3Rpb24obil7bGV0IHQ9QWEobik7cmV0dXJuICRLKHQpLCh0W1JyLkV4dHJhRGF0YV1bMl18fFtdKS5tYXAoaT0+InN0cmluZyI9PXR5cGVvZiBpPyRWKGkpOlskVihpWzBdKSwkVihpWzFdKV0pfSh0KSx1PWZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1BYShuKTskSyhpKTtsZXQgbz1YbChbaVtSci5FeHRyYURhdGFdWzBdLGlbUnIuRXh0cmFEYXRhXVsxXV0sdCl8fFtdO3JldHVybiBYbChvLGUpfHxbXX0odCxyLGkpLGQ9Yy5maW5kSW5kZXgocD0+e2lmKEFycmF5LmlzQXJyYXkocCkpe2xldFtoLGZdPXAsbT1zPj1oLmhvdXJzJiZhPj1oLm1pbnV0ZXMseD1zPGYuaG91cnN8fHM9PT1mLmhvdXJzJiZhPGYubWludXRlcztpZihoLmhvdXJzPGYuaG91cnMpe2lmKG0mJngpcmV0dXJuITB9ZWxzZSBpZihtfHx4KXJldHVybiEwfWVsc2UgaWYocC5ob3Vycz09PXMmJnAubWludXRlcz09PWEpcmV0dXJuITA7cmV0dXJuITF9KTtpZigtMSE9PWQpcmV0dXJuIHVbZF19cmV0dXJuIGZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1BYShuKSxvPVhsKFtpW1JyLkRheVBlcmlvZHNGb3JtYXRdLGlbUnIuRGF5UGVyaW9kc1N0YW5kYWxvbmVdXSx0KTtyZXR1cm4gWGwobyxlKX0odCxyLGkpW3M8MTI/MDoxXTtjYXNlIEhpLkVyYXM6cmV0dXJuIGZ1bmN0aW9uKG4sdCl7cmV0dXJuIFhsKEFhKG4pW1JyLkVyYXNdLHQpfSh0LGkpW24uZ2V0RnVsbFllYXIoKTw9MD8wOjFdO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKGB1bmV4cGVjdGVkIHRyYW5zbGF0aW9uIHR5cGUgJHtlfWApfX0ocixvLG4sdCxlLGkpfX1mdW5jdGlvbiBCRChuKXtyZXR1cm4gZnVuY3Rpb24odCxlLGkpe2xldCByPS0xKmksbz1lcChlLElzLk1pbnVzU2lnbikscz1yPjA/TWF0aC5mbG9vcihyLzYwKTpNYXRoLmNlaWwoci82MCk7c3dpdGNoKG4pe2Nhc2UgV2MuU2hvcnQ6cmV0dXJuKHI+PTA/IisiOiIiKStHYyhzLDIsbykrR2MoTWF0aC5hYnMociU2MCksMixvKTtjYXNlIFdjLlNob3J0R01UOnJldHVybiJHTVQiKyhyPj0wPyIrIjoiIikrR2MocywxLG8pO2Nhc2UgV2MuTG9uZzpyZXR1cm4iR01UIisocj49MD8iKyI6IiIpK0djKHMsMixvKSsiOiIrR2MoTWF0aC5hYnMociU2MCksMixvKTtjYXNlIFdjLkV4dGVuZGVkOnJldHVybiAwPT09aT8iWiI6KHI+PTA/IisiOiIiKStHYyhzLDIsbykrIjoiK0djKE1hdGguYWJzKHIlNjApLDIsbyk7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gem9uZSB3aWR0aCAiJHtufSJgKX19fWZ1bmN0aW9uIGVaKG4pe3JldHVybiBqRChuLmdldEZ1bGxZZWFyKCksbi5nZXRNb250aCgpLG4uZ2V0RGF0ZSgpKyg0LW4uZ2V0RGF5KCkpKX1mdW5jdGlvbiBlNShuLHQ9ITEpe3JldHVybiBmdW5jdGlvbihlLGkpe2xldCByO2lmKHQpe2xldCBvPW5ldyBEYXRlKGUuZ2V0RnVsbFllYXIoKSxlLmdldE1vbnRoKCksMSkuZ2V0RGF5KCktMSxzPWUuZ2V0RGF0ZSgpO3I9MStNYXRoLmZsb29yKChzK28pLzcpfWVsc2V7bGV0IG89ZVooZSkscz1mdW5jdGlvbihuKXtsZXQgdD1qRChuLDAsMSkuZ2V0RGF5KCk7cmV0dXJuIGpEKG4sMCwxKyh0PD00PzQ6MTEpLXQpfShvLmdldEZ1bGxZZWFyKCkpLGE9by5nZXRUaW1lKCktcy5nZXRUaW1lKCk7cj0xK01hdGgucm91bmQoYS82MDQ4ZTUpfXJldHVybiBHYyhyLG4sZXAoaSxJcy5NaW51c1NpZ24pKX19ZnVuY3Rpb24gVkQobix0PSExKXtyZXR1cm4gZnVuY3Rpb24oZSxpKXtyZXR1cm4gR2MoZVooZSkuZ2V0RnVsbFllYXIoKSxuLGVwKGksSXMuTWludXNTaWduKSx0KX19dmFyIHQ1PXt9O2Z1bmN0aW9uIHRaKG4sdCl7bj1uLnJlcGxhY2UoLzovZywiIik7bGV0IGU9RGF0ZS5wYXJzZSgiSmFuIDAxLCAxOTcwIDAwOjAwOjAwICIrbikvNmU0O3JldHVybiBpc05hTihlKT90OmV9ZnVuY3Rpb24gR0sobil7cmV0dXJuIG4gaW5zdGFuY2VvZiBEYXRlJiYhaXNOYU4obi52YWx1ZU9mKCkpfXZhciBqVGU9L14oXGQrKT9cLigoXGQrKSgtKFxkKykpPyk/JC87ZnVuY3Rpb24gdTUobix0LGUpe2xldCBpPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIEFhKG4pW1JyLk51bWJlckZvcm1hdHNdW3RdfSh0LEhELkRlY2ltYWwpLHI9ZnVuY3Rpb24obix0PSItIil7bGV0IGU9e21pbkludDoxLG1pbkZyYWM6MCxtYXhGcmFjOjAscG9zUHJlOiIiLHBvc1N1ZjoiIixuZWdQcmU6IiIsbmVnU3VmOiIiLGdTaXplOjAsbGdTaXplOjB9LGk9bi5zcGxpdCgiOyIpLHI9aVswXSxvPWlbMV0scz0tMSE9PXIuaW5kZXhPZigiLiIpP3Iuc3BsaXQoIi4iKTpbci5zdWJzdHJpbmcoMCxyLmxhc3RJbmRleE9mKCIwIikrMSksci5zdWJzdHJpbmcoci5sYXN0SW5kZXhPZigiMCIpKzEpXSxhPXNbMF0sbD1zWzFdfHwiIjtlLnBvc1ByZT1hLnN1YnN0cmluZygwLGEuaW5kZXhPZigiIyIpKTtmb3IobGV0IHU9MDt1PGwubGVuZ3RoO3UrKyl7bGV0IGQ9bC5jaGFyQXQodSk7IjAiPT09ZD9lLm1pbkZyYWM9ZS5tYXhGcmFjPXUrMToiIyI9PT1kP2UubWF4RnJhYz11KzE6ZS5wb3NTdWYrPWR9bGV0IGM9YS5zcGxpdCgiLCIpO2lmKGUuZ1NpemU9Y1sxXT9jWzFdLmxlbmd0aDowLGUubGdTaXplPWNbMl18fGNbMV0/KGNbMl18fGNbMV0pLmxlbmd0aDowLG8pe2xldCB1PXIubGVuZ3RoLWUucG9zUHJlLmxlbmd0aC1lLnBvc1N1Zi5sZW5ndGgsZD1vLmluZGV4T2YoIiMiKTtlLm5lZ1ByZT1vLnN1YnN0cmluZygwLGQpLnJlcGxhY2UoLycvZywiIiksZS5uZWdTdWY9by5zbGljZShkK3UpLnJlcGxhY2UoLycvZywiIil9ZWxzZSBlLm5lZ1ByZT10K2UucG9zUHJlLGUubmVnU3VmPWUucG9zU3VmO3JldHVybiBlfShpLGVwKHQsSXMuTWludXNTaWduKSk7cmV0dXJuIGZ1bmN0aW9uKG4sdCxlLGkscixvLHM9ITEpe2xldCBhPSIiLGw9ITE7aWYoaXNGaW5pdGUobikpe2xldCBjPWZ1bmN0aW9uKG4pe2xldCBpLHIsbyxzLGEsdD1NYXRoLmFicyhuKSsiIixlPTA7Zm9yKChyPXQuaW5kZXhPZigiLiIpKT4tMSYmKHQ9dC5yZXBsYWNlKCIuIiwiIikpLChvPXQuc2VhcmNoKC9lL2kpKT4wPyhyPDAmJihyPW8pLHIrPSt0LnNsaWNlKG8rMSksdD10LnN1YnN0cmluZygwLG8pKTpyPDAmJihyPXQubGVuZ3RoKSxvPTA7IjAiPT09dC5jaGFyQXQobyk7bysrKTtpZihvPT09KGE9dC5sZW5ndGgpKWk9WzBdLHI9MTtlbHNle2ZvcihhLS07IjAiPT09dC5jaGFyQXQoYSk7KWEtLTtmb3Ioci09byxpPVtdLHM9MDtvPD1hO28rKyxzKyspaVtzXT1OdW1iZXIodC5jaGFyQXQobykpfXJldHVybiByPjIyJiYoaT1pLnNwbGljZSgwLDIxKSxlPXItMSxyPTEpLHtkaWdpdHM6aSxleHBvbmVudDplLGludGVnZXJMZW46cn19KG4pO3MmJihjPWZ1bmN0aW9uKG4pe2lmKDA9PT1uLmRpZ2l0c1swXSlyZXR1cm4gbjtsZXQgdD1uLmRpZ2l0cy5sZW5ndGgtbi5pbnRlZ2VyTGVuO3JldHVybiBuLmV4cG9uZW50P24uZXhwb25lbnQrPTI6KDA9PT10P24uZGlnaXRzLnB1c2goMCwwKToxPT09dCYmbi5kaWdpdHMucHVzaCgwKSxuLmludGVnZXJMZW4rPTIpLG59KGMpKTtsZXQgdT10Lm1pbkludCxkPXQubWluRnJhYyxwPXQubWF4RnJhYztpZihvKXtsZXQgYj1vLm1hdGNoKGpUZSk7aWYobnVsbD09PWIpdGhyb3cgbmV3IEVycm9yKGAke299IGlzIG5vdCBhIHZhbGlkIGRpZ2l0IGluZm9gKTtsZXQgRD1iWzFdLFQ9YlszXSxrPWJbNV07bnVsbCE9RCYmKHU9aTUoRCkpLG51bGwhPVQmJihkPWk1KFQpKSxudWxsIT1rP3A9aTUoayk6bnVsbCE9VCYmZD5wJiYocD1kKX0hZnVuY3Rpb24obix0LGUpe2lmKHQ+ZSl0aHJvdyBuZXcgRXJyb3IoYFRoZSBtaW5pbXVtIG51bWJlciBvZiBkaWdpdHMgYWZ0ZXIgZnJhY3Rpb24gKCR7dH0pIGlzIGhpZ2hlciB0aGFuIHRoZSBtYXhpbXVtICgke2V9KS5gKTtsZXQgaT1uLmRpZ2l0cyxyPWkubGVuZ3RoLW4uaW50ZWdlckxlbixvPU1hdGgubWluKE1hdGgubWF4KHQsciksZSkscz1vK24uaW50ZWdlckxlbixhPWlbc107aWYocz4wKXtpLnNwbGljZShNYXRoLm1heChuLmludGVnZXJMZW4scykpO2ZvcihsZXQgZD1zO2Q8aS5sZW5ndGg7ZCsrKWlbZF09MH1lbHNle3I9TWF0aC5tYXgoMCxyKSxuLmludGVnZXJMZW49MSxpLmxlbmd0aD1NYXRoLm1heCgxLHM9bysxKSxpWzBdPTA7Zm9yKGxldCBkPTE7ZDxzO2QrKylpW2RdPTB9aWYoYT49NSlpZihzLTE8MCl7Zm9yKGxldCBkPTA7ZD5zO2QtLSlpLnVuc2hpZnQoMCksbi5pbnRlZ2VyTGVuKys7aS51bnNoaWZ0KDEpLG4uaW50ZWdlckxlbisrfWVsc2UgaVtzLTFdKys7Zm9yKDtyPE1hdGgubWF4KDAsbyk7cisrKWkucHVzaCgwKTtsZXQgbD0wIT09byxjPXQrbi5pbnRlZ2VyTGVuLHU9aS5yZWR1Y2VSaWdodChmdW5jdGlvbihkLHAsaCxmKXtyZXR1cm4gZltoXT0ocCs9ZCk8MTA/cDpwLTEwLGwmJigwPT09ZltoXSYmaD49Yz9mLnBvcCgpOmw9ITEpLHA+PTEwPzE6MH0sMCk7dSYmKGkudW5zaGlmdCh1KSxuLmludGVnZXJMZW4rKyl9KGMsZCxwKTtsZXQgaD1jLmRpZ2l0cyxmPWMuaW50ZWdlckxlbixtPWMuZXhwb25lbnQseD1bXTtmb3IobD1oLmV2ZXJ5KGI9PiFiKTtmPHU7ZisrKWgudW5zaGlmdCgwKTtmb3IoO2Y8MDtmKyspaC51bnNoaWZ0KDApO2Y+MD94PWguc3BsaWNlKGYsaC5sZW5ndGgpOih4PWgsaD1bMF0pO2xldCBnPVtdO2ZvcihoLmxlbmd0aD49dC5sZ1NpemUmJmcudW5zaGlmdChoLnNwbGljZSgtdC5sZ1NpemUsaC5sZW5ndGgpLmpvaW4oIiIpKTtoLmxlbmd0aD50LmdTaXplOylnLnVuc2hpZnQoaC5zcGxpY2UoLXQuZ1NpemUsaC5sZW5ndGgpLmpvaW4oIiIpKTtoLmxlbmd0aCYmZy51bnNoaWZ0KGguam9pbigiIikpLGE9Zy5qb2luKGVwKGUsaSkpLHgubGVuZ3RoJiYoYSs9ZXAoZSxyKSt4LmpvaW4oIiIpKSxtJiYoYSs9ZXAoZSxJcy5FeHBvbmVudGlhbCkrIisiK20pfWVsc2UgYT1lcChlLElzLkluZmluaXR5KTtyZXR1cm4gYT1uPDAmJiFsP3QubmVnUHJlK2ErdC5uZWdTdWY6dC5wb3NQcmUrYSt0LnBvc1N1ZixhfShuLHIsdCxJcy5Hcm91cCxJcy5EZWNpbWFsLGUpfWZ1bmN0aW9uIGk1KG4pe2xldCB0PXBhcnNlSW50KG4pO2lmKGlzTmFOKHQpKXRocm93IG5ldyBFcnJvcigiSW52YWxpZCBpbnRlZ2VyIGxpdGVyYWwgd2hlbiBwYXJzaW5nICIrbik7cmV0dXJuIHR9ZnVuY3Rpb24gcUQobix0KXt0PWVuY29kZVVSSUNvbXBvbmVudCh0KTtmb3IobGV0IGUgb2Ygbi5zcGxpdCgiOyIpKXtsZXQgaT1lLmluZGV4T2YoIj0iKSxbcixvXT0tMT09aT9bZSwiIl06W2Uuc2xpY2UoMCxpKSxlLnNsaWNlKGkrMSldO2lmKHIudHJpbSgpPT09dClyZXR1cm4gZGVjb2RlVVJJQ29tcG9uZW50KG8pfXJldHVybiBudWxsfXZhciBGbj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8pe3RoaXMuX2l0ZXJhYmxlRGlmZmVycz1lLHRoaXMuX2tleVZhbHVlRGlmZmVycz1pLHRoaXMuX25nRWw9cix0aGlzLl9yZW5kZXJlcj1vLHRoaXMuX2l0ZXJhYmxlRGlmZmVyPW51bGwsdGhpcy5fa2V5VmFsdWVEaWZmZXI9bnVsbCx0aGlzLl9pbml0aWFsQ2xhc3Nlcz1bXSx0aGlzLl9yYXdDbGFzcz1udWxsfXNldCBrbGFzcyhlKXt0aGlzLl9yZW1vdmVDbGFzc2VzKHRoaXMuX2luaXRpYWxDbGFzc2VzKSx0aGlzLl9pbml0aWFsQ2xhc3Nlcz0ic3RyaW5nIj09dHlwZW9mIGU/ZS5zcGxpdCgvXHMrLyk6W10sdGhpcy5fYXBwbHlDbGFzc2VzKHRoaXMuX2luaXRpYWxDbGFzc2VzKSx0aGlzLl9hcHBseUNsYXNzZXModGhpcy5fcmF3Q2xhc3MpfXNldCBuZ0NsYXNzKGUpe3RoaXMuX3JlbW92ZUNsYXNzZXModGhpcy5fcmF3Q2xhc3MpLHRoaXMuX2FwcGx5Q2xhc3Nlcyh0aGlzLl9pbml0aWFsQ2xhc3NlcyksdGhpcy5faXRlcmFibGVEaWZmZXI9bnVsbCx0aGlzLl9rZXlWYWx1ZURpZmZlcj1udWxsLHRoaXMuX3Jhd0NsYXNzPSJzdHJpbmciPT10eXBlb2YgZT9lLnNwbGl0KC9ccysvKTplLHRoaXMuX3Jhd0NsYXNzJiYod1QodGhpcy5fcmF3Q2xhc3MpP3RoaXMuX2l0ZXJhYmxlRGlmZmVyPXRoaXMuX2l0ZXJhYmxlRGlmZmVycy5maW5kKHRoaXMuX3Jhd0NsYXNzKS5jcmVhdGUoKTp0aGlzLl9rZXlWYWx1ZURpZmZlcj10aGlzLl9rZXlWYWx1ZURpZmZlcnMuZmluZCh0aGlzLl9yYXdDbGFzcykuY3JlYXRlKCkpfW5nRG9DaGVjaygpe2lmKHRoaXMuX2l0ZXJhYmxlRGlmZmVyKXtsZXQgZT10aGlzLl9pdGVyYWJsZURpZmZlci5kaWZmKHRoaXMuX3Jhd0NsYXNzKTtlJiZ0aGlzLl9hcHBseUl0ZXJhYmxlQ2hhbmdlcyhlKX1lbHNlIGlmKHRoaXMuX2tleVZhbHVlRGlmZmVyKXtsZXQgZT10aGlzLl9rZXlWYWx1ZURpZmZlci5kaWZmKHRoaXMuX3Jhd0NsYXNzKTtlJiZ0aGlzLl9hcHBseUtleVZhbHVlQ2hhbmdlcyhlKX19X2FwcGx5S2V5VmFsdWVDaGFuZ2VzKGUpe2UuZm9yRWFjaEFkZGVkSXRlbShpPT50aGlzLl90b2dnbGVDbGFzcyhpLmtleSxpLmN1cnJlbnRWYWx1ZSkpLGUuZm9yRWFjaENoYW5nZWRJdGVtKGk9PnRoaXMuX3RvZ2dsZUNsYXNzKGkua2V5LGkuY3VycmVudFZhbHVlKSksZS5mb3JFYWNoUmVtb3ZlZEl0ZW0oaT0+e2kucHJldmlvdXNWYWx1ZSYmdGhpcy5fdG9nZ2xlQ2xhc3MoaS5rZXksITEpfSl9X2FwcGx5SXRlcmFibGVDaGFuZ2VzKGUpe2UuZm9yRWFjaEFkZGVkSXRlbShpPT57aWYoInN0cmluZyIhPXR5cGVvZiBpLml0ZW0pdGhyb3cgbmV3IEVycm9yKGBOZ0NsYXNzIGNhbiBvbmx5IHRvZ2dsZSBDU1MgY2xhc3NlcyBleHByZXNzZWQgYXMgc3RyaW5ncywgZ290ICR7VG8oaS5pdGVtKX1gKTt0aGlzLl90b2dnbGVDbGFzcyhpLml0ZW0sITApfSksZS5mb3JFYWNoUmVtb3ZlZEl0ZW0oaT0+dGhpcy5fdG9nZ2xlQ2xhc3MoaS5pdGVtLCExKSl9X2FwcGx5Q2xhc3NlcyhlKXtlJiYoQXJyYXkuaXNBcnJheShlKXx8ZSBpbnN0YW5jZW9mIFNldD9lLmZvckVhY2goaT0+dGhpcy5fdG9nZ2xlQ2xhc3MoaSwhMCkpOk9iamVjdC5rZXlzKGUpLmZvckVhY2goaT0+dGhpcy5fdG9nZ2xlQ2xhc3MoaSwhIWVbaV0pKSl9X3JlbW92ZUNsYXNzZXMoZSl7ZSYmKEFycmF5LmlzQXJyYXkoZSl8fGUgaW5zdGFuY2VvZiBTZXQ/ZS5mb3JFYWNoKGk9PnRoaXMuX3RvZ2dsZUNsYXNzKGksITEpKTpPYmplY3Qua2V5cyhlKS5mb3JFYWNoKGk9PnRoaXMuX3RvZ2dsZUNsYXNzKGksITEpKSl9X3RvZ2dsZUNsYXNzKGUsaSl7KGU9ZS50cmltKCkpJiZlLnNwbGl0KC9ccysvZykuZm9yRWFjaChyPT57aT90aGlzLl9yZW5kZXJlci5hZGRDbGFzcyh0aGlzLl9uZ0VsLm5hdGl2ZUVsZW1lbnQscik6dGhpcy5fcmVuZGVyZXIucmVtb3ZlQ2xhc3ModGhpcy5fbmdFbC5uYXRpdmVFbGVtZW50LHIpfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oa2MpLE0obkMpLE0oUmUpLE0oRXUpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwibmdDbGFzcyIsIiJdXSxpbnB1dHM6e2tsYXNzOlsiY2xhc3MiLCJrbGFzcyJdLG5nQ2xhc3M6Im5nQ2xhc3MifSxzdGFuZGFsb25lOiEwfSksbn0pKCksZG49KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5fdmlld0NvbnRhaW5lcj1lLHRoaXMuX3RlbXBsYXRlPWksdGhpcy5fZGlmZmVycz1yLHRoaXMuX25nRm9yT2Y9bnVsbCx0aGlzLl9uZ0Zvck9mRGlydHk9ITAsdGhpcy5fZGlmZmVyPW51bGx9c2V0IG5nRm9yT2YoZSl7dGhpcy5fbmdGb3JPZj1lLHRoaXMuX25nRm9yT2ZEaXJ0eT0hMH1zZXQgbmdGb3JUcmFja0J5KGUpe3RoaXMuX3RyYWNrQnlGbj1lfWdldCBuZ0ZvclRyYWNrQnkoKXtyZXR1cm4gdGhpcy5fdHJhY2tCeUZufXNldCBuZ0ZvclRlbXBsYXRlKGUpe2UmJih0aGlzLl90ZW1wbGF0ZT1lKX1uZ0RvQ2hlY2soKXtpZih0aGlzLl9uZ0Zvck9mRGlydHkpe3RoaXMuX25nRm9yT2ZEaXJ0eT0hMTtsZXQgZT10aGlzLl9uZ0Zvck9mOyF0aGlzLl9kaWZmZXImJmUmJih0aGlzLl9kaWZmZXI9dGhpcy5fZGlmZmVycy5maW5kKGUpLmNyZWF0ZSh0aGlzLm5nRm9yVHJhY2tCeSkpfWlmKHRoaXMuX2RpZmZlcil7bGV0IGU9dGhpcy5fZGlmZmVyLmRpZmYodGhpcy5fbmdGb3JPZik7ZSYmdGhpcy5fYXBwbHlDaGFuZ2VzKGUpfX1fYXBwbHlDaGFuZ2VzKGUpe2xldCBpPXRoaXMuX3ZpZXdDb250YWluZXI7ZS5mb3JFYWNoT3BlcmF0aW9uKChyLG8scyk9PntpZihudWxsPT1yLnByZXZpb3VzSW5kZXgpaS5jcmVhdGVFbWJlZGRlZFZpZXcodGhpcy5fdGVtcGxhdGUsbmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe3RoaXMuJGltcGxpY2l0PXQsdGhpcy5uZ0Zvck9mPWUsdGhpcy5pbmRleD1pLHRoaXMuY291bnQ9cn1nZXQgZmlyc3QoKXtyZXR1cm4gMD09PXRoaXMuaW5kZXh9Z2V0IGxhc3QoKXtyZXR1cm4gdGhpcy5pbmRleD09PXRoaXMuY291bnQtMX1nZXQgZXZlbigpe3JldHVybiB0aGlzLmluZGV4JTI9PTB9Z2V0IG9kZCgpe3JldHVybiF0aGlzLmV2ZW59fShyLml0ZW0sdGhpcy5fbmdGb3JPZiwtMSwtMSksbnVsbD09PXM/dm9pZCAwOnMpO2Vsc2UgaWYobnVsbD09cylpLnJlbW92ZShudWxsPT09bz92b2lkIDA6byk7ZWxzZSBpZihudWxsIT09byl7bGV0IGE9aS5nZXQobyk7aS5tb3ZlKGEscyksWUsoYSxyKX19KTtmb3IobGV0IHI9MCxvPWkubGVuZ3RoO3I8bztyKyspe2xldCBhPWkuZ2V0KHIpLmNvbnRleHQ7YS5pbmRleD1yLGEuY291bnQ9byxhLm5nRm9yT2Y9dGhpcy5fbmdGb3JPZn1lLmZvckVhY2hJZGVudGl0eUNoYW5nZShyPT57WUsoaS5nZXQoci5jdXJyZW50SW5kZXgpLHIpfSl9c3RhdGljIG5nVGVtcGxhdGVDb250ZXh0R3VhcmQoZSxpKXtyZXR1cm4hMH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShPaSksTShWaSksTShrYykpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJuZ0ZvciIsIiIsIm5nRm9yT2YiLCIiXV0saW5wdXRzOntuZ0Zvck9mOiJuZ0Zvck9mIixuZ0ZvclRyYWNrQnk6Im5nRm9yVHJhY2tCeSIsbmdGb3JUZW1wbGF0ZToibmdGb3JUZW1wbGF0ZSJ9LHN0YW5kYWxvbmU6ITB9KSxufSkoKTtmdW5jdGlvbiBZSyhuLHQpe24uY29udGV4dC4kaW1wbGljaXQ9dC5pdGVtfXZhciBCZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSl7dGhpcy5fdmlld0NvbnRhaW5lcj1lLHRoaXMuX2NvbnRleHQ9bmV3IHM1LHRoaXMuX3RoZW5UZW1wbGF0ZVJlZj1udWxsLHRoaXMuX2Vsc2VUZW1wbGF0ZVJlZj1udWxsLHRoaXMuX3RoZW5WaWV3UmVmPW51bGwsdGhpcy5fZWxzZVZpZXdSZWY9bnVsbCx0aGlzLl90aGVuVGVtcGxhdGVSZWY9aX1zZXQgbmdJZihlKXt0aGlzLl9jb250ZXh0LiRpbXBsaWNpdD10aGlzLl9jb250ZXh0Lm5nSWY9ZSx0aGlzLl91cGRhdGVWaWV3KCl9c2V0IG5nSWZUaGVuKGUpe1hLKCJuZ0lmVGhlbiIsZSksdGhpcy5fdGhlblRlbXBsYXRlUmVmPWUsdGhpcy5fdGhlblZpZXdSZWY9bnVsbCx0aGlzLl91cGRhdGVWaWV3KCl9c2V0IG5nSWZFbHNlKGUpe1hLKCJuZ0lmRWxzZSIsZSksdGhpcy5fZWxzZVRlbXBsYXRlUmVmPWUsdGhpcy5fZWxzZVZpZXdSZWY9bnVsbCx0aGlzLl91cGRhdGVWaWV3KCl9X3VwZGF0ZVZpZXcoKXt0aGlzLl9jb250ZXh0LiRpbXBsaWNpdD90aGlzLl90aGVuVmlld1JlZnx8KHRoaXMuX3ZpZXdDb250YWluZXIuY2xlYXIoKSx0aGlzLl9lbHNlVmlld1JlZj1udWxsLHRoaXMuX3RoZW5UZW1wbGF0ZVJlZiYmKHRoaXMuX3RoZW5WaWV3UmVmPXRoaXMuX3ZpZXdDb250YWluZXIuY3JlYXRlRW1iZWRkZWRWaWV3KHRoaXMuX3RoZW5UZW1wbGF0ZVJlZix0aGlzLl9jb250ZXh0KSkpOnRoaXMuX2Vsc2VWaWV3UmVmfHwodGhpcy5fdmlld0NvbnRhaW5lci5jbGVhcigpLHRoaXMuX3RoZW5WaWV3UmVmPW51bGwsdGhpcy5fZWxzZVRlbXBsYXRlUmVmJiYodGhpcy5fZWxzZVZpZXdSZWY9dGhpcy5fdmlld0NvbnRhaW5lci5jcmVhdGVFbWJlZGRlZFZpZXcodGhpcy5fZWxzZVRlbXBsYXRlUmVmLHRoaXMuX2NvbnRleHQpKSl9c3RhdGljIG5nVGVtcGxhdGVDb250ZXh0R3VhcmQoZSxpKXtyZXR1cm4hMH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShPaSksTShWaSkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJuZ0lmIiwiIl1dLGlucHV0czp7bmdJZjoibmdJZiIsbmdJZlRoZW46Im5nSWZUaGVuIixuZ0lmRWxzZToibmdJZkVsc2UifSxzdGFuZGFsb25lOiEwfSksbn0pKCksczU9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLiRpbXBsaWNpdD1udWxsLHRoaXMubmdJZj1udWxsfX07ZnVuY3Rpb24gWEsobix0KXtpZih0JiYhdC5jcmVhdGVFbWJlZGRlZFZpZXcpdGhyb3cgbmV3IEVycm9yKGAke259IG11c3QgYmUgYSBUZW1wbGF0ZVJlZiwgYnV0IHJlY2VpdmVkICcke1RvKHQpfScuYCl9dmFyIFdEPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5fdmlld0NvbnRhaW5lclJlZj10LHRoaXMuX3RlbXBsYXRlUmVmPWUsdGhpcy5fY3JlYXRlZD0hMX1jcmVhdGUoKXt0aGlzLl9jcmVhdGVkPSEwLHRoaXMuX3ZpZXdDb250YWluZXJSZWYuY3JlYXRlRW1iZWRkZWRWaWV3KHRoaXMuX3RlbXBsYXRlUmVmKX1kZXN0cm95KCl7dGhpcy5fY3JlYXRlZD0hMSx0aGlzLl92aWV3Q29udGFpbmVyUmVmLmNsZWFyKCl9ZW5mb3JjZVN0YXRlKHQpe3QmJiF0aGlzLl9jcmVhdGVkP3RoaXMuY3JlYXRlKCk6IXQmJnRoaXMuX2NyZWF0ZWQmJnRoaXMuZGVzdHJveSgpfX0sQ3I9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuX2RlZmF1bHRVc2VkPSExLHRoaXMuX2Nhc2VDb3VudD0wLHRoaXMuX2xhc3RDYXNlQ2hlY2tJbmRleD0wLHRoaXMuX2xhc3RDYXNlc01hdGNoZWQ9ITF9c2V0IG5nU3dpdGNoKGUpe3RoaXMuX25nU3dpdGNoPWUsMD09PXRoaXMuX2Nhc2VDb3VudCYmdGhpcy5fdXBkYXRlRGVmYXVsdENhc2VzKCEwKX1fYWRkQ2FzZSgpe3JldHVybiB0aGlzLl9jYXNlQ291bnQrK31fYWRkRGVmYXVsdChlKXt0aGlzLl9kZWZhdWx0Vmlld3N8fCh0aGlzLl9kZWZhdWx0Vmlld3M9W10pLHRoaXMuX2RlZmF1bHRWaWV3cy5wdXNoKGUpfV9tYXRjaENhc2UoZSl7bGV0IGk9ZT09dGhpcy5fbmdTd2l0Y2g7cmV0dXJuIHRoaXMuX2xhc3RDYXNlc01hdGNoZWQ9dGhpcy5fbGFzdENhc2VzTWF0Y2hlZHx8aSx0aGlzLl9sYXN0Q2FzZUNoZWNrSW5kZXgrKyx0aGlzLl9sYXN0Q2FzZUNoZWNrSW5kZXg9PT10aGlzLl9jYXNlQ291bnQmJih0aGlzLl91cGRhdGVEZWZhdWx0Q2FzZXMoIXRoaXMuX2xhc3RDYXNlc01hdGNoZWQpLHRoaXMuX2xhc3RDYXNlQ2hlY2tJbmRleD0wLHRoaXMuX2xhc3RDYXNlc01hdGNoZWQ9ITEpLGl9X3VwZGF0ZURlZmF1bHRDYXNlcyhlKXtpZih0aGlzLl9kZWZhdWx0Vmlld3MmJmUhPT10aGlzLl9kZWZhdWx0VXNlZCl7dGhpcy5fZGVmYXVsdFVzZWQ9ZTtmb3IobGV0IGk9MDtpPHRoaXMuX2RlZmF1bHRWaWV3cy5sZW5ndGg7aSsrKXRoaXMuX2RlZmF1bHRWaWV3c1tpXS5lbmZvcmNlU3RhdGUoZSl9fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwibmdTd2l0Y2giLCIiXV0saW5wdXRzOntuZ1N3aXRjaDoibmdTd2l0Y2gifSxzdGFuZGFsb25lOiEwfSksbn0pKCksVXI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5uZ1N3aXRjaD1yLHIuX2FkZENhc2UoKSx0aGlzLl92aWV3PW5ldyBXRChlLGkpfW5nRG9DaGVjaygpe3RoaXMuX3ZpZXcuZW5mb3JjZVN0YXRlKHRoaXMubmdTd2l0Y2guX21hdGNoQ2FzZSh0aGlzLm5nU3dpdGNoQ2FzZSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKE9pKSxNKFZpKSxNKENyLDkpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwibmdTd2l0Y2hDYXNlIiwiIl1dLGlucHV0czp7bmdTd2l0Y2hDYXNlOiJuZ1N3aXRjaENhc2UifSxzdGFuZGFsb25lOiEwfSksbn0pKCksY2g9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7ci5fYWRkRGVmYXVsdChuZXcgV0QoZSxpKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oT2kpLE0oVmkpLE0oQ3IsOSkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJuZ1N3aXRjaERlZmF1bHQiLCIiXV0sc3RhbmRhbG9uZTohMH0pLG59KSgpLHp1PSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIpe3RoaXMuX25nRWw9ZSx0aGlzLl9kaWZmZXJzPWksdGhpcy5fcmVuZGVyZXI9cix0aGlzLl9uZ1N0eWxlPW51bGwsdGhpcy5fZGlmZmVyPW51bGx9c2V0IG5nU3R5bGUoZSl7dGhpcy5fbmdTdHlsZT1lLCF0aGlzLl9kaWZmZXImJmUmJih0aGlzLl9kaWZmZXI9dGhpcy5fZGlmZmVycy5maW5kKGUpLmNyZWF0ZSgpKX1uZ0RvQ2hlY2soKXtpZih0aGlzLl9kaWZmZXIpe2xldCBlPXRoaXMuX2RpZmZlci5kaWZmKHRoaXMuX25nU3R5bGUpO2UmJnRoaXMuX2FwcGx5Q2hhbmdlcyhlKX19X3NldFN0eWxlKGUsaSl7bGV0W3Isb109ZS5zcGxpdCgiLiIpLHM9LTE9PT1yLmluZGV4T2YoIi0iKT92b2lkIDA6QmwuRGFzaENhc2U7bnVsbCE9aT90aGlzLl9yZW5kZXJlci5zZXRTdHlsZSh0aGlzLl9uZ0VsLm5hdGl2ZUVsZW1lbnQscixvP2Ake2l9JHtvfWA6aSxzKTp0aGlzLl9yZW5kZXJlci5yZW1vdmVTdHlsZSh0aGlzLl9uZ0VsLm5hdGl2ZUVsZW1lbnQscixzKX1fYXBwbHlDaGFuZ2VzKGUpe2UuZm9yRWFjaFJlbW92ZWRJdGVtKGk9PnRoaXMuX3NldFN0eWxlKGkua2V5LG51bGwpKSxlLmZvckVhY2hBZGRlZEl0ZW0oaT0+dGhpcy5fc2V0U3R5bGUoaS5rZXksaS5jdXJyZW50VmFsdWUpKSxlLmZvckVhY2hDaGFuZ2VkSXRlbShpPT50aGlzLl9zZXRTdHlsZShpLmtleSxpLmN1cnJlbnRWYWx1ZSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKG5DKSxNKEV1KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm5nU3R5bGUiLCIiXV0saW5wdXRzOntuZ1N0eWxlOiJuZ1N0eWxlIn0sc3RhbmRhbG9uZTohMH0pLG59KSgpLG9zPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5fdmlld0NvbnRhaW5lclJlZj1lLHRoaXMuX3ZpZXdSZWY9bnVsbCx0aGlzLm5nVGVtcGxhdGVPdXRsZXRDb250ZXh0PW51bGwsdGhpcy5uZ1RlbXBsYXRlT3V0bGV0PW51bGwsdGhpcy5uZ1RlbXBsYXRlT3V0bGV0SW5qZWN0b3I9bnVsbH1uZ09uQ2hhbmdlcyhlKXtpZihlLm5nVGVtcGxhdGVPdXRsZXR8fGUubmdUZW1wbGF0ZU91dGxldEluamVjdG9yKXtsZXQgaT10aGlzLl92aWV3Q29udGFpbmVyUmVmO2lmKHRoaXMuX3ZpZXdSZWYmJmkucmVtb3ZlKGkuaW5kZXhPZih0aGlzLl92aWV3UmVmKSksdGhpcy5uZ1RlbXBsYXRlT3V0bGV0KXtsZXR7bmdUZW1wbGF0ZU91dGxldDpyLG5nVGVtcGxhdGVPdXRsZXRDb250ZXh0Om8sbmdUZW1wbGF0ZU91dGxldEluamVjdG9yOnN9PXRoaXM7dGhpcy5fdmlld1JlZj1pLmNyZWF0ZUVtYmVkZGVkVmlldyhyLG8scz97aW5qZWN0b3I6c306dm9pZCAwKX1lbHNlIHRoaXMuX3ZpZXdSZWY9bnVsbH1lbHNlIHRoaXMuX3ZpZXdSZWYmJmUubmdUZW1wbGF0ZU91dGxldENvbnRleHQmJnRoaXMubmdUZW1wbGF0ZU91dGxldENvbnRleHQmJih0aGlzLl92aWV3UmVmLmNvbnRleHQ9dGhpcy5uZ1RlbXBsYXRlT3V0bGV0Q29udGV4dCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oT2kpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwibmdUZW1wbGF0ZU91dGxldCIsIiJdXSxpbnB1dHM6e25nVGVtcGxhdGVPdXRsZXRDb250ZXh0OiJuZ1RlbXBsYXRlT3V0bGV0Q29udGV4dCIsbmdUZW1wbGF0ZU91dGxldDoibmdUZW1wbGF0ZU91dGxldCIsbmdUZW1wbGF0ZU91dGxldEluamVjdG9yOiJuZ1RlbXBsYXRlT3V0bGV0SW5qZWN0b3IifSxzdGFuZGFsb25lOiEwLGZlYXR1cmVzOltGdF19KSxufSkoKTtmdW5jdGlvbiBZRChuLHQpe3JldHVybiBuZXcgQXQoMjEwMCwhMSl9dmFyIEpUZT1uZXcgY2xhc3N7Y3JlYXRlU3Vic2NyaXB0aW9uKHQsZSl7cmV0dXJuIHQudGhlbihlLGk9Pnt0aHJvdyBpfSl9ZGlzcG9zZSh0KXt9fSwkVGU9bmV3IGNsYXNze2NyZWF0ZVN1YnNjcmlwdGlvbih0LGUpe3JldHVybiB0LnN1YnNjcmliZSh7bmV4dDplLGVycm9yOmk9Pnt0aHJvdyBpfX0pfWRpc3Bvc2UodCl7dC51bnN1YnNjcmliZSgpfX0sR2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLl9sYXRlc3RWYWx1ZT1udWxsLHRoaXMuX3N1YnNjcmlwdGlvbj1udWxsLHRoaXMuX29iaj1udWxsLHRoaXMuX3N0cmF0ZWd5PW51bGwsdGhpcy5fcmVmPWV9bmdPbkRlc3Ryb3koKXt0aGlzLl9zdWJzY3JpcHRpb24mJnRoaXMuX2Rpc3Bvc2UoKSx0aGlzLl9yZWY9bnVsbH10cmFuc2Zvcm0oZSl7cmV0dXJuIHRoaXMuX29iaj9lIT09dGhpcy5fb2JqPyh0aGlzLl9kaXNwb3NlKCksdGhpcy50cmFuc2Zvcm0oZSkpOnRoaXMuX2xhdGVzdFZhbHVlOihlJiZ0aGlzLl9zdWJzY3JpYmUoZSksdGhpcy5fbGF0ZXN0VmFsdWUpfV9zdWJzY3JpYmUoZSl7dGhpcy5fb2JqPWUsdGhpcy5fc3RyYXRlZ3k9dGhpcy5fc2VsZWN0U3RyYXRlZ3koZSksdGhpcy5fc3Vic2NyaXB0aW9uPXRoaXMuX3N0cmF0ZWd5LmNyZWF0ZVN1YnNjcmlwdGlvbihlLGk9PnRoaXMuX3VwZGF0ZUxhdGVzdFZhbHVlKGUsaSkpfV9zZWxlY3RTdHJhdGVneShlKXtpZihuXyhlKSlyZXR1cm4gSlRlO2lmKFgzKGUpKXJldHVybiAkVGU7dGhyb3cgWUQoKX1fZGlzcG9zZSgpe3RoaXMuX3N0cmF0ZWd5LmRpc3Bvc2UodGhpcy5fc3Vic2NyaXB0aW9uKSx0aGlzLl9sYXRlc3RWYWx1ZT1udWxsLHRoaXMuX3N1YnNjcmlwdGlvbj1udWxsLHRoaXMuX29iaj1udWxsfV91cGRhdGVMYXRlc3RWYWx1ZShlLGkpe2U9PT10aGlzLl9vYmomJih0aGlzLl9sYXRlc3RWYWx1ZT1pLHRoaXMuX3JlZi5tYXJrRm9yQ2hlY2soKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0obm4sMTYpKX0sbi5cdTAyNzVwaXBlPUIwKHtuYW1lOiJhc3luYyIsdHlwZTpuLHB1cmU6ITEsc3RhbmRhbG9uZTohMH0pLG59KSgpLGVEZT1uZXcgcGUoIkRBVEVfUElQRV9ERUZBVUxUX1RJTUVaT05FIiksVV89KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMubG9jYWxlPWUsdGhpcy5kZWZhdWx0VGltZXpvbmU9aX10cmFuc2Zvcm0oZSxpPSJtZWRpdW1EYXRlIixyLG8pe2lmKG51bGw9PWV8fCIiPT09ZXx8ZSE9ZSlyZXR1cm4gbnVsbDt0cnl7cmV0dXJuIFJUZShlLGksb3x8dGhpcy5sb2NhbGUscj8/dGhpcy5kZWZhdWx0VGltZXpvbmU/P3ZvaWQgMCl9Y2F0Y2gocyl7dGhyb3cgWUQoKX19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oV2QsMTYpLE0oZURlLDI0KSl9LG4uXHUwMjc1cGlwZT1CMCh7bmFtZToiZGF0ZSIsdHlwZTpuLHB1cmU6ITAsc3RhbmRhbG9uZTohMH0pLG59KSgpLFFsPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5fbG9jYWxlPWV9dHJhbnNmb3JtKGUsaSxyKXtpZighZnVuY3Rpb24obil7cmV0dXJuIShudWxsPT1ufHwiIj09PW58fG4hPW4pfShlKSlyZXR1cm4gbnVsbDtyPXJ8fHRoaXMuX2xvY2FsZTt0cnl7bGV0IG89ZnVuY3Rpb24obil7aWYoInN0cmluZyI9PXR5cGVvZiBuJiYhaXNOYU4oTnVtYmVyKG4pLXBhcnNlRmxvYXQobikpKXJldHVybiBOdW1iZXIobik7aWYoIm51bWJlciIhPXR5cGVvZiBuKXRocm93IG5ldyBFcnJvcihgJHtufSBpcyBub3QgYSBudW1iZXJgKTtyZXR1cm4gbn0oZSk7cmV0dXJuIHU1KG8scixpKX1jYXRjaChvKXt0aHJvdyBZRCgpfX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShXZCwxNikpfSxuLlx1MDI3NXBpcGU9QjAoe25hbWU6Im51bWJlciIsdHlwZTpuLHB1cmU6ITAsc3RhbmRhbG9uZTohMH0pLG59KSgpLG5aPSgoKT0+e2NsYXNzIG57dHJhbnNmb3JtKGUsaSxyKXtpZihudWxsPT1lKXJldHVybiBudWxsO2lmKCF0aGlzLnN1cHBvcnRzKGUpKXRocm93IFlEKCk7cmV0dXJuIGUuc2xpY2UoaSxyKX1zdXBwb3J0cyhlKXtyZXR1cm4ic3RyaW5nIj09dHlwZW9mIGV8fEFycmF5LmlzQXJyYXkoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXBpcGU9QjAoe25hbWU6InNsaWNlIix0eXBlOm4scHVyZTohMSxzdGFuZGFsb25lOiEwfSksbn0pKCksTWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLGQ1PSJicm93c2VyIjtmdW5jdGlvbiBYRChuKXtyZXR1cm4gbj09PWQ1fW5ldyBJYygiMTQuMi4xMSIpO3ZhciBFbT1jbGFzc3t9O2Z1bmN0aW9uIHNEZShuKXtyZXR1cm4gbi5zdGFydHNXaXRoKCIvIik/bi5zbGljZSgxKTpufW5ldyBwZSgiUFJFQ09OTkVDVF9DSEVDS19CTE9DS0xJU1QiKTt2YXIgYURlPW49Pm4uc3JjLGxEZT1uZXcgcGUoIkltYWdlTG9hZGVyIix7cHJvdmlkZWRJbjoicm9vdCIsZmFjdG9yeTooKT0+YURlfSk7ZnVuY3Rpb24gUUQobix0KXtyZXR1cm4gZnVuY3Rpb24oaSxyPXtlbnN1cmVQcmVjb25uZWN0OiEwfSl7cmV0dXJuIGZ1bmN0aW9uKG4pe2lmKCJzdHJpbmciIT10eXBlb2Ygbnx8IiI9PT1uLnRyaW0oKSlyZXR1cm4hMTt0cnl7cmV0dXJuIG5ldyBVUkwobiksITB9Y2F0Y2h7cmV0dXJuITF9fShpKXx8ZnVuY3Rpb24obix0KXt0aHJvdyBuZXcgQXQoMjk1OSwhMSl9KCksaT1mdW5jdGlvbihuKXtyZXR1cm4gbi5lbmRzV2l0aCgiLyIpP24uc2xpY2UoMCwtMSk6bn0oaSksW3twcm92aWRlOmxEZSx1c2VWYWx1ZTphPT4oZnVuY3Rpb24obil7cmV0dXJuL15odHRwcz86XC9cLy8udGVzdChuKX0oYS5zcmMpJiZmdW5jdGlvbihuLHQpe3Rocm93IG5ldyBBdCgyOTU5LCExKX0oKSxuKGksey4uLmEsc3JjOnNEZShhLnNyYyl9KSl9XX19UUQoZnVuY3Rpb24obix0KXtsZXQgZT0iZm9ybWF0PWF1dG8iO3JldHVybiB0LndpZHRoJiYoZSs9YCx3aWR0aD0ke3Qud2lkdGh9YCksYCR7bn0vY2RuLWNnaS9pbWFnZS8ke2V9LyR7dC5zcmN9YH0pLFFEKGZ1bmN0aW9uKG4sdCl7bGV0IGU9ImZfYXV0byxxX2F1dG8iO3JldHVybiB0LndpZHRoJiYoZSs9YCx3XyR7dC53aWR0aH1gKSxgJHtufS9pbWFnZS91cGxvYWQvJHtlfS8ke3Quc3JjfWB9KSxRRChmdW5jdGlvbihuLHQpe2xldCBlPSJ0cjpxLWF1dG8iO3JldHVybiB0LndpZHRoJiYoZSs9YCx3LSR7dC53aWR0aH1gKSxgJHtufS8ke2V9LyR7dC5zcmN9YH0pLFFEKGZ1bmN0aW9uKG4sdCl7bGV0IGU9bmV3IFVSTChgJHtufS8ke3Quc3JjfWApO3JldHVybiBlLnNlYXJjaFBhcmFtcy5zZXQoImF1dG8iLCJmb3JtYXQiKSx0LndpZHRoJiZlLnNlYXJjaFBhcmFtcy5zZXQoInciLHQud2lkdGgudG9TdHJpbmcoKSksZS5ocmVmfSk7dmFyIEtELGg1PWNsYXNzIGV4dGVuZHMgekR7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuc3VwcG9ydHNET01FdmVudHM9ITB9fSxhTT1jbGFzcyBleHRlbmRzIGg1e3N0YXRpYyBtYWtlQ3VycmVudCgpeyFmdW5jdGlvbihuKXtyNXx8KHI1PW4pfShuZXcgYU0pfW9uQW5kQ2FuY2VsKHQsZSxpKXtyZXR1cm4gdC5hZGRFdmVudExpc3RlbmVyKGUsaSwhMSksKCk9Pnt0LnJlbW92ZUV2ZW50TGlzdGVuZXIoZSxpLCExKX19ZGlzcGF0Y2hFdmVudCh0LGUpe3QuZGlzcGF0Y2hFdmVudChlKX1yZW1vdmUodCl7dC5wYXJlbnROb2RlJiZ0LnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQodCl9Y3JlYXRlRWxlbWVudCh0LGUpe3JldHVybihlPWV8fHRoaXMuZ2V0RGVmYXVsdERvY3VtZW50KCkpLmNyZWF0ZUVsZW1lbnQodCl9Y3JlYXRlSHRtbERvY3VtZW50KCl7cmV0dXJuIGRvY3VtZW50LmltcGxlbWVudGF0aW9uLmNyZWF0ZUhUTUxEb2N1bWVudCgiZmFrZVRpdGxlIil9Z2V0RGVmYXVsdERvY3VtZW50KCl7cmV0dXJuIGRvY3VtZW50fWlzRWxlbWVudE5vZGUodCl7cmV0dXJuIHQubm9kZVR5cGU9PT1Ob2RlLkVMRU1FTlRfTk9ERX1pc1NoYWRvd1Jvb3QodCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBEb2N1bWVudEZyYWdtZW50fWdldEdsb2JhbEV2ZW50VGFyZ2V0KHQsZSl7cmV0dXJuIndpbmRvdyI9PT1lP3dpbmRvdzoiZG9jdW1lbnQiPT09ZT90OiJib2R5Ij09PWU/dC5ib2R5Om51bGx9Z2V0QmFzZUhyZWYodCl7bGV0IGU9KHJNPXJNfHxkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCJiYXNlIikpP3JNLmdldEF0dHJpYnV0ZSgiaHJlZiIpOm51bGw7cmV0dXJuIG51bGw9PWU/bnVsbDpmdW5jdGlvbihuKXsoS0Q9S0R8fGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKSkuc2V0QXR0cmlidXRlKCJocmVmIixuKTtsZXQgdD1LRC5wYXRobmFtZTtyZXR1cm4iLyI9PT10LmNoYXJBdCgwKT90OmAvJHt0fWB9KGUpfXJlc2V0QmFzZUVsZW1lbnQoKXtyTT1udWxsfWdldFVzZXJBZ2VudCgpe3JldHVybiB3aW5kb3cubmF2aWdhdG9yLnVzZXJBZ2VudH1nZXRDb29raWUodCl7cmV0dXJuIHFEKGRvY3VtZW50LmNvb2tpZSx0KX19LHJNPW51bGwsY1o9bmV3IHBlKCJUUkFOU0lUSU9OX0lEIikseURlPVt7cHJvdmlkZTokMyx1c2VGYWN0b3J5OmZ1bmN0aW9uKG4sdCxlKXtyZXR1cm4oKT0+e2UuZ2V0KE9UKS5kb25lUHJvbWlzZS50aGVuKCgpPT57bGV0IGk9WWwoKSxyPXQucXVlcnlTZWxlY3RvckFsbChgc3R5bGVbbmctdHJhbnNpdGlvbj0iJHtufSJdYCk7Zm9yKGxldCBvPTA7bzxyLmxlbmd0aDtvKyspaS5yZW1vdmUocltvXSl9KX19LGRlcHM6W2NaLEh0LFhuXSxtdWx0aTohMH1dLGJEZT0oKCk9PntjbGFzcyBue2J1aWxkKCl7cmV0dXJuIG5ldyBYTUxIdHRwUmVxdWVzdH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksWkQ9bmV3IHBlKCJFdmVudE1hbmFnZXJQbHVnaW5zIiksSkQ9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuX3pvbmU9aSx0aGlzLl9ldmVudE5hbWVUb1BsdWdpbj1uZXcgTWFwLGUuZm9yRWFjaChyPT5yLm1hbmFnZXI9dGhpcyksdGhpcy5fcGx1Z2lucz1lLnNsaWNlKCkucmV2ZXJzZSgpfWFkZEV2ZW50TGlzdGVuZXIoZSxpLHIpe3JldHVybiB0aGlzLl9maW5kUGx1Z2luRm9yKGkpLmFkZEV2ZW50TGlzdGVuZXIoZSxpLHIpfWFkZEdsb2JhbEV2ZW50TGlzdGVuZXIoZSxpLHIpe3JldHVybiB0aGlzLl9maW5kUGx1Z2luRm9yKGkpLmFkZEdsb2JhbEV2ZW50TGlzdGVuZXIoZSxpLHIpfWdldFpvbmUoKXtyZXR1cm4gdGhpcy5fem9uZX1fZmluZFBsdWdpbkZvcihlKXtsZXQgaT10aGlzLl9ldmVudE5hbWVUb1BsdWdpbi5nZXQoZSk7aWYoaSlyZXR1cm4gaTtsZXQgcj10aGlzLl9wbHVnaW5zO2ZvcihsZXQgbz0wO288ci5sZW5ndGg7bysrKXtsZXQgcz1yW29dO2lmKHMuc3VwcG9ydHMoZSkpcmV0dXJuIHRoaXMuX2V2ZW50TmFtZVRvUGx1Z2luLnNldChlLHMpLHN9dGhyb3cgbmV3IEVycm9yKGBObyBldmVudCBtYW5hZ2VyIHBsdWdpbiBmb3VuZCBmb3IgZXZlbnQgJHtlfWApfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKFpEKSxqKF90KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksJEQ9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5fZG9jPXR9YWRkR2xvYmFsRXZlbnRMaXN0ZW5lcih0LGUsaSl7bGV0IHI9WWwoKS5nZXRHbG9iYWxFdmVudFRhcmdldCh0aGlzLl9kb2MsdCk7aWYoIXIpdGhyb3cgbmV3IEVycm9yKGBVbnN1cHBvcnRlZCBldmVudCB0YXJnZXQgJHtyfSBmb3IgZXZlbnQgJHtlfWApO3JldHVybiB0aGlzLmFkZEV2ZW50TGlzdGVuZXIocixlLGkpfX0sdVo9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuX3N0eWxlc1NldD1uZXcgU2V0fWFkZFN0eWxlcyhlKXtsZXQgaT1uZXcgU2V0O2UuZm9yRWFjaChyPT57dGhpcy5fc3R5bGVzU2V0LmhhcyhyKXx8KHRoaXMuX3N0eWxlc1NldC5hZGQociksaS5hZGQocikpfSksdGhpcy5vblN0eWxlc0FkZGVkKGkpfW9uU3R5bGVzQWRkZWQoZSl7fWdldEFsbFN0eWxlcygpe3JldHVybiBBcnJheS5mcm9tKHRoaXMuX3N0eWxlc1NldCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLG9NPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyB1Wntjb25zdHJ1Y3RvcihlKXtzdXBlcigpLHRoaXMuX2RvYz1lLHRoaXMuX2hvc3ROb2Rlcz1uZXcgTWFwLHRoaXMuX2hvc3ROb2Rlcy5zZXQoZS5oZWFkLFtdKX1fYWRkU3R5bGVzVG9Ib3N0KGUsaSxyKXtlLmZvckVhY2gobz0+e2xldCBzPXRoaXMuX2RvYy5jcmVhdGVFbGVtZW50KCJzdHlsZSIpO3MudGV4dENvbnRlbnQ9byxyLnB1c2goaS5hcHBlbmRDaGlsZChzKSl9KX1hZGRIb3N0KGUpe2xldCBpPVtdO3RoaXMuX2FkZFN0eWxlc1RvSG9zdCh0aGlzLl9zdHlsZXNTZXQsZSxpKSx0aGlzLl9ob3N0Tm9kZXMuc2V0KGUsaSl9cmVtb3ZlSG9zdChlKXtsZXQgaT10aGlzLl9ob3N0Tm9kZXMuZ2V0KGUpO2kmJmkuZm9yRWFjaChpWiksdGhpcy5faG9zdE5vZGVzLmRlbGV0ZShlKX1vblN0eWxlc0FkZGVkKGUpe3RoaXMuX2hvc3ROb2Rlcy5mb3JFYWNoKChpLHIpPT57dGhpcy5fYWRkU3R5bGVzVG9Ib3N0KGUscixpKX0pfW5nT25EZXN0cm95KCl7dGhpcy5faG9zdE5vZGVzLmZvckVhY2goZT0+ZS5mb3JFYWNoKGlaKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooSHQpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKTtmdW5jdGlvbiBpWihuKXtZbCgpLnJlbW92ZShuKX12YXIgcDU9e3N2ZzoiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLHhodG1sOiJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIix4bGluazoiaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIseG1sOiJodHRwOi8vd3d3LnczLm9yZy9YTUwvMTk5OC9uYW1lc3BhY2UiLHhtbG5zOiJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3htbG5zLyIsbWF0aDoiaHR0cDovL3d3dy53My5vcmcvMTk5OC9NYXRoTUwvIn0sXzU9LyVDT01QJS9nO2Z1bmN0aW9uIGVBKG4sdCxlKXtmb3IobGV0IGk9MDtpPHQubGVuZ3RoO2krKyl7bGV0IHI9dFtpXTtBcnJheS5pc0FycmF5KHIpP2VBKG4scixlKToocj1yLnJlcGxhY2UoXzUsbiksZS5wdXNoKHIpKX1yZXR1cm4gZX1mdW5jdGlvbiBvWihuKXtyZXR1cm4gdD0+e2lmKCJfX25nVW53cmFwX18iPT09dClyZXR1cm4gbjshMT09PW4odCkmJih0LnByZXZlbnREZWZhdWx0KCksdC5yZXR1cm5WYWx1ZT0hMSl9fXZhciBzTT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLmV2ZW50TWFuYWdlcj1lLHRoaXMuc2hhcmVkU3R5bGVzSG9zdD1pLHRoaXMuYXBwSWQ9cix0aGlzLnJlbmRlcmVyQnlDb21wSWQ9bmV3IE1hcCx0aGlzLmRlZmF1bHRSZW5kZXJlcj1uZXcgbE0oZSl9Y3JlYXRlUmVuZGVyZXIoZSxpKXtpZighZXx8IWkpcmV0dXJuIHRoaXMuZGVmYXVsdFJlbmRlcmVyO3N3aXRjaChpLmVuY2Fwc3VsYXRpb24pe2Nhc2UgSmEuRW11bGF0ZWQ6e2xldCByPXRoaXMucmVuZGVyZXJCeUNvbXBJZC5nZXQoaS5pZCk7cmV0dXJuIHJ8fChyPW5ldyBtNSh0aGlzLmV2ZW50TWFuYWdlcix0aGlzLnNoYXJlZFN0eWxlc0hvc3QsaSx0aGlzLmFwcElkKSx0aGlzLnJlbmRlcmVyQnlDb21wSWQuc2V0KGkuaWQscikpLHIuYXBwbHlUb0hvc3QoZSkscn1jYXNlIDE6Y2FzZSBKYS5TaGFkb3dEb206cmV0dXJuIG5ldyBnNSh0aGlzLmV2ZW50TWFuYWdlcix0aGlzLnNoYXJlZFN0eWxlc0hvc3QsZSxpKTtkZWZhdWx0OmlmKCF0aGlzLnJlbmRlcmVyQnlDb21wSWQuaGFzKGkuaWQpKXtsZXQgcj1lQShpLmlkLGkuc3R5bGVzLFtdKTt0aGlzLnNoYXJlZFN0eWxlc0hvc3QuYWRkU3R5bGVzKHIpLHRoaXMucmVuZGVyZXJCeUNvbXBJZC5zZXQoaS5pZCx0aGlzLmRlZmF1bHRSZW5kZXJlcil9cmV0dXJuIHRoaXMuZGVmYXVsdFJlbmRlcmVyfX1iZWdpbigpe31lbmQoKXt9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooSkQpLGoob00pLGooJGYpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxsTT1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLmV2ZW50TWFuYWdlcj10LHRoaXMuZGF0YT1PYmplY3QuY3JlYXRlKG51bGwpLHRoaXMuZGVzdHJveU5vZGU9bnVsbH1kZXN0cm95KCl7fWNyZWF0ZUVsZW1lbnQodCxlKXtyZXR1cm4gZT9kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMocDVbZV18fGUsdCk6ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCh0KX1jcmVhdGVDb21tZW50KHQpe3JldHVybiBkb2N1bWVudC5jcmVhdGVDb21tZW50KHQpfWNyZWF0ZVRleHQodCl7cmV0dXJuIGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKHQpfWFwcGVuZENoaWxkKHQsZSl7KGFaKHQpP3QuY29udGVudDp0KS5hcHBlbmRDaGlsZChlKX1pbnNlcnRCZWZvcmUodCxlLGkpe3QmJihhWih0KT90LmNvbnRlbnQ6dCkuaW5zZXJ0QmVmb3JlKGUsaSl9cmVtb3ZlQ2hpbGQodCxlKXt0JiZ0LnJlbW92ZUNoaWxkKGUpfXNlbGVjdFJvb3RFbGVtZW50KHQsZSl7bGV0IGk9InN0cmluZyI9PXR5cGVvZiB0P2RvY3VtZW50LnF1ZXJ5U2VsZWN0b3IodCk6dDtpZighaSl0aHJvdyBuZXcgRXJyb3IoYFRoZSBzZWxlY3RvciAiJHt0fSIgZGlkIG5vdCBtYXRjaCBhbnkgZWxlbWVudHNgKTtyZXR1cm4gZXx8KGkudGV4dENvbnRlbnQ9IiIpLGl9cGFyZW50Tm9kZSh0KXtyZXR1cm4gdC5wYXJlbnROb2RlfW5leHRTaWJsaW5nKHQpe3JldHVybiB0Lm5leHRTaWJsaW5nfXNldEF0dHJpYnV0ZSh0LGUsaSxyKXtpZihyKXtlPXIrIjoiK2U7bGV0IG89cDVbcl07bz90LnNldEF0dHJpYnV0ZU5TKG8sZSxpKTp0LnNldEF0dHJpYnV0ZShlLGkpfWVsc2UgdC5zZXRBdHRyaWJ1dGUoZSxpKX1yZW1vdmVBdHRyaWJ1dGUodCxlLGkpe2lmKGkpe2xldCByPXA1W2ldO3I/dC5yZW1vdmVBdHRyaWJ1dGVOUyhyLGUpOnQucmVtb3ZlQXR0cmlidXRlKGAke2l9OiR7ZX1gKX1lbHNlIHQucmVtb3ZlQXR0cmlidXRlKGUpfWFkZENsYXNzKHQsZSl7dC5jbGFzc0xpc3QuYWRkKGUpfXJlbW92ZUNsYXNzKHQsZSl7dC5jbGFzc0xpc3QucmVtb3ZlKGUpfXNldFN0eWxlKHQsZSxpLHIpe3ImKEJsLkRhc2hDYXNlfEJsLkltcG9ydGFudCk/dC5zdHlsZS5zZXRQcm9wZXJ0eShlLGksciZCbC5JbXBvcnRhbnQ/ImltcG9ydGFudCI6IiIpOnQuc3R5bGVbZV09aX1yZW1vdmVTdHlsZSh0LGUsaSl7aSZCbC5EYXNoQ2FzZT90LnN0eWxlLnJlbW92ZVByb3BlcnR5KGUpOnQuc3R5bGVbZV09IiJ9c2V0UHJvcGVydHkodCxlLGkpe3RbZV09aX1zZXRWYWx1ZSh0LGUpe3Qubm9kZVZhbHVlPWV9bGlzdGVuKHQsZSxpKXtyZXR1cm4ic3RyaW5nIj09dHlwZW9mIHQ/dGhpcy5ldmVudE1hbmFnZXIuYWRkR2xvYmFsRXZlbnRMaXN0ZW5lcih0LGUsb1ooaSkpOnRoaXMuZXZlbnRNYW5hZ2VyLmFkZEV2ZW50TGlzdGVuZXIodCxlLG9aKGkpKX19O2Z1bmN0aW9uIGFaKG4pe3JldHVybiJURU1QTEFURSI9PT1uLnRhZ05hbWUmJnZvaWQgMCE9PW4uY29udGVudH0iQCIuY2hhckNvZGVBdCgwKTt2YXIgbTU9Y2xhc3MgZXh0ZW5kcyBsTXtjb25zdHJ1Y3Rvcih0LGUsaSxyKXtzdXBlcih0KSx0aGlzLmNvbXBvbmVudD1pO2xldCBvPWVBKHIrIi0iK2kuaWQsaS5zdHlsZXMsW10pO2UuYWRkU3R5bGVzKG8pLHRoaXMuY29udGVudEF0dHI9ZnVuY3Rpb24obil7cmV0dXJuIl9uZ2NvbnRlbnQtJUNPTVAlIi5yZXBsYWNlKF81LG4pfShyKyItIitpLmlkKSx0aGlzLmhvc3RBdHRyPWZ1bmN0aW9uKG4pe3JldHVybiJfbmdob3N0LSVDT01QJSIucmVwbGFjZShfNSxuKX0ocisiLSIraS5pZCl9YXBwbHlUb0hvc3QodCl7c3VwZXIuc2V0QXR0cmlidXRlKHQsdGhpcy5ob3N0QXR0ciwiIil9Y3JlYXRlRWxlbWVudCh0LGUpe2xldCBpPXN1cGVyLmNyZWF0ZUVsZW1lbnQodCxlKTtyZXR1cm4gc3VwZXIuc2V0QXR0cmlidXRlKGksdGhpcy5jb250ZW50QXR0ciwiIiksaX19LGc1PWNsYXNzIGV4dGVuZHMgbE17Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIodCksdGhpcy5zaGFyZWRTdHlsZXNIb3N0PWUsdGhpcy5ob3N0RWw9aSx0aGlzLnNoYWRvd1Jvb3Q9aS5hdHRhY2hTaGFkb3coe21vZGU6Im9wZW4ifSksdGhpcy5zaGFyZWRTdHlsZXNIb3N0LmFkZEhvc3QodGhpcy5zaGFkb3dSb290KTtsZXQgbz1lQShyLmlkLHIuc3R5bGVzLFtdKTtmb3IobGV0IHM9MDtzPG8ubGVuZ3RoO3MrKyl7bGV0IGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3R5bGUiKTthLnRleHRDb250ZW50PW9bc10sdGhpcy5zaGFkb3dSb290LmFwcGVuZENoaWxkKGEpfX1ub2RlT3JTaGFkb3dSb290KHQpe3JldHVybiB0PT09dGhpcy5ob3N0RWw/dGhpcy5zaGFkb3dSb290OnR9ZGVzdHJveSgpe3RoaXMuc2hhcmVkU3R5bGVzSG9zdC5yZW1vdmVIb3N0KHRoaXMuc2hhZG93Um9vdCl9YXBwZW5kQ2hpbGQodCxlKXtyZXR1cm4gc3VwZXIuYXBwZW5kQ2hpbGQodGhpcy5ub2RlT3JTaGFkb3dSb290KHQpLGUpfWluc2VydEJlZm9yZSh0LGUsaSl7cmV0dXJuIHN1cGVyLmluc2VydEJlZm9yZSh0aGlzLm5vZGVPclNoYWRvd1Jvb3QodCksZSxpKX1yZW1vdmVDaGlsZCh0LGUpe3JldHVybiBzdXBlci5yZW1vdmVDaGlsZCh0aGlzLm5vZGVPclNoYWRvd1Jvb3QodCksZSl9cGFyZW50Tm9kZSh0KXtyZXR1cm4gdGhpcy5ub2RlT3JTaGFkb3dSb290KHN1cGVyLnBhcmVudE5vZGUodGhpcy5ub2RlT3JTaGFkb3dSb290KHQpKSl9fSxFRGU9KCgpPT57Y2xhc3MgbiBleHRlbmRzICREe2NvbnN0cnVjdG9yKGUpe3N1cGVyKGUpfXN1cHBvcnRzKGUpe3JldHVybiEwfWFkZEV2ZW50TGlzdGVuZXIoZSxpLHIpe3JldHVybiBlLmFkZEV2ZW50TGlzdGVuZXIoaSxyLCExKSwoKT0+dGhpcy5yZW1vdmVFdmVudExpc3RlbmVyKGUsaSxyKX1yZW1vdmVFdmVudExpc3RlbmVyKGUsaSxyKXtyZXR1cm4gZS5yZW1vdmVFdmVudExpc3RlbmVyKGkscil9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooSHQpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxsWj1bImFsdCIsImNvbnRyb2wiLCJtZXRhIiwic2hpZnQiXSxURGU9eyJcYiI6IkJhY2tzcGFjZSIsIlx0IjoiVGFiIiwiXHg3ZiI6IkRlbGV0ZSIsIlx4MWIiOiJFc2NhcGUiLERlbDoiRGVsZXRlIixFc2M6IkVzY2FwZSIsTGVmdDoiQXJyb3dMZWZ0IixSaWdodDoiQXJyb3dSaWdodCIsVXA6IkFycm93VXAiLERvd246IkFycm93RG93biIsTWVudToiQ29udGV4dE1lbnUiLFNjcm9sbDoiU2Nyb2xsTG9jayIsV2luOiJPUyJ9LEREZT17YWx0Om49Pm4uYWx0S2V5LGNvbnRyb2w6bj0+bi5jdHJsS2V5LG1ldGE6bj0+bi5tZXRhS2V5LHNoaWZ0Om49Pm4uc2hpZnRLZXl9LEFEZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgJER7Y29uc3RydWN0b3IoZSl7c3VwZXIoZSl9c3VwcG9ydHMoZSl7cmV0dXJuIG51bGwhPW4ucGFyc2VFdmVudE5hbWUoZSl9YWRkRXZlbnRMaXN0ZW5lcihlLGkscil7bGV0IG89bi5wYXJzZUV2ZW50TmFtZShpKSxzPW4uZXZlbnRDYWxsYmFjayhvLmZ1bGxLZXkscix0aGlzLm1hbmFnZXIuZ2V0Wm9uZSgpKTtyZXR1cm4gdGhpcy5tYW5hZ2VyLmdldFpvbmUoKS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+WWwoKS5vbkFuZENhbmNlbChlLG8uZG9tRXZlbnROYW1lLHMpKX1zdGF0aWMgcGFyc2VFdmVudE5hbWUoZSl7bGV0IGk9ZS50b0xvd2VyQ2FzZSgpLnNwbGl0KCIuIikscj1pLnNoaWZ0KCk7aWYoMD09PWkubGVuZ3RofHwia2V5ZG93biIhPT1yJiYia2V5dXAiIT09cilyZXR1cm4gbnVsbDtsZXQgbz1uLl9ub3JtYWxpemVLZXkoaS5wb3AoKSkscz0iIixhPWkuaW5kZXhPZigiY29kZSIpO2lmKGE+LTEmJihpLnNwbGljZShhLDEpLHM9ImNvZGUuIiksbFouZm9yRWFjaChjPT57bGV0IHU9aS5pbmRleE9mKGMpO3U+LTEmJihpLnNwbGljZSh1LDEpLHMrPWMrIi4iKX0pLHMrPW8sMCE9aS5sZW5ndGh8fDA9PT1vLmxlbmd0aClyZXR1cm4gbnVsbDtsZXQgbD17fTtyZXR1cm4gbC5kb21FdmVudE5hbWU9cixsLmZ1bGxLZXk9cyxsfXN0YXRpYyBtYXRjaEV2ZW50RnVsbEtleUNvZGUoZSxpKXtsZXQgcj1URGVbZS5rZXldfHxlLmtleSxvPSIiO3JldHVybiBpLmluZGV4T2YoImNvZGUuIik+LTEmJihyPWUuY29kZSxvPSJjb2RlLiIpLCEobnVsbD09cnx8IXIpJiYocj1yLnRvTG93ZXJDYXNlKCksIiAiPT09cj9yPSJzcGFjZSI6Ii4iPT09ciYmKHI9ImRvdCIpLGxaLmZvckVhY2gocz0+e3MhPT1yJiYoMCxERGVbc10pKGUpJiYobys9cysiLiIpfSksbys9cixvPT09aSl9c3RhdGljIGV2ZW50Q2FsbGJhY2soZSxpLHIpe3JldHVybiBvPT57bi5tYXRjaEV2ZW50RnVsbEtleUNvZGUobyxlKSYmci5ydW5HdWFyZGVkKCgpPT5pKG8pKX19c3RhdGljIF9ub3JtYWxpemVLZXkoZSl7cmV0dXJuImVzYyI9PT1lPyJlc2NhcGUiOmV9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooSHQpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxPRGU9W3twcm92aWRlOkdkLHVzZVZhbHVlOmQ1fSx7cHJvdmlkZTplQix1c2VWYWx1ZTpmdW5jdGlvbigpe2FNLm1ha2VDdXJyZW50KCl9LG11bHRpOiEwfSx7cHJvdmlkZTpIdCx1c2VGYWN0b3J5OmZ1bmN0aW9uKCl7cmV0dXJuIGZ1bmN0aW9uKG4pe3BMPW59KGRvY3VtZW50KSxkb2N1bWVudH0sZGVwczpbXX1dLHk1PXJCKGNYLCJicm93c2VyIixPRGUpLHBaPW5ldyBwZSgiIiksa0RlPVt7cHJvdmlkZTplQyx1c2VDbGFzczpjbGFzc3thZGRUb1dpbmRvdyh0KXt0by5nZXRBbmd1bGFyVGVzdGFiaWxpdHk9KGkscj0hMCk9PntsZXQgbz10LmZpbmRUZXN0YWJpbGl0eUluVHJlZShpLHIpO2lmKG51bGw9PW8pdGhyb3cgbmV3IEVycm9yKCJDb3VsZCBub3QgZmluZCB0ZXN0YWJpbGl0eSBmb3IgZWxlbWVudC4iKTtyZXR1cm4gb30sdG8uZ2V0QWxsQW5ndWxhclRlc3RhYmlsaXRpZXM9KCk9PnQuZ2V0QWxsVGVzdGFiaWxpdGllcygpLHRvLmdldEFsbEFuZ3VsYXJSb290RWxlbWVudHM9KCk9PnQuZ2V0QWxsUm9vdEVsZW1lbnRzKCksdG8uZnJhbWV3b3JrU3RhYmlsaXplcnN8fCh0by5mcmFtZXdvcmtTdGFiaWxpemVycz1bXSksdG8uZnJhbWV3b3JrU3RhYmlsaXplcnMucHVzaChpPT57bGV0IHI9dG8uZ2V0QWxsQW5ndWxhclRlc3RhYmlsaXRpZXMoKSxvPXIubGVuZ3RoLHM9ITEsYT1mdW5jdGlvbihsKXtzPXN8fGwsby0tLDA9PW8mJmkocyl9O3IuZm9yRWFjaChmdW5jdGlvbihsKXtsLndoZW5TdGFibGUoYSl9KX0pfWZpbmRUZXN0YWJpbGl0eUluVHJlZSh0LGUsaSl7cmV0dXJuIG51bGw9PWU/bnVsbDp0LmdldFRlc3RhYmlsaXR5KGUpPz8oaT9ZbCgpLmlzU2hhZG93Um9vdChlKT90aGlzLmZpbmRUZXN0YWJpbGl0eUluVHJlZSh0LGUuaG9zdCwhMCk6dGhpcy5maW5kVGVzdGFiaWxpdHlJblRyZWUodCxlLnBhcmVudEVsZW1lbnQsITApOm51bGwpfX0sZGVwczpbXX0se3Byb3ZpZGU6bkIsdXNlQ2xhc3M6a1QsZGVwczpbX3QsRlQsZUNdfSx7cHJvdmlkZTprVCx1c2VDbGFzczprVCxkZXBzOltfdCxGVCxlQ119XSxGRGU9W3twcm92aWRlOmdULHVzZVZhbHVlOiJyb290In0se3Byb3ZpZGU6UXMsdXNlRmFjdG9yeTpmdW5jdGlvbigpe3JldHVybiBuZXcgUXN9LGRlcHM6W119LHtwcm92aWRlOlpELHVzZUNsYXNzOkVEZSxtdWx0aTohMCxkZXBzOltIdCxfdCxHZF19LHtwcm92aWRlOlpELHVzZUNsYXNzOkFEZSxtdWx0aTohMCxkZXBzOltIdF19LHtwcm92aWRlOnNNLHVzZUNsYXNzOnNNLGRlcHM6W0pELG9NLCRmXX0se3Byb3ZpZGU6d3UsdXNlRXhpc3Rpbmc6c019LHtwcm92aWRlOnVaLHVzZUV4aXN0aW5nOm9NfSx7cHJvdmlkZTpvTSx1c2VDbGFzczpvTSxkZXBzOltIdF19LHtwcm92aWRlOkpELHVzZUNsYXNzOkpELGRlcHM6W1pELF90XX0se3Byb3ZpZGU6RW0sdXNlQ2xhc3M6YkRlLGRlcHM6W119LFtdXSx0QT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe31zdGF0aWMgd2l0aFNlcnZlclRyYW5zaXRpb24oZSl7cmV0dXJue25nTW9kdWxlOm4scHJvdmlkZXJzOlt7cHJvdmlkZTokZix1c2VWYWx1ZTplLmFwcElkfSx7cHJvdmlkZTpjWix1c2VFeGlzdGluZzokZn0seURlXX19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGoocFosMTIpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7cHJvdmlkZXJzOlsuLi5GRGUsLi4ua0RlXSxpbXBvcnRzOltNZSx1WF19KSxufSkoKSxUbT0obmV3IHBlKCJIYW1tZXJHZXN0dXJlQ29uZmlnIiksbmV3IHBlKCJIYW1tZXJMb2FkZXIiKSwoKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6ZnVuY3Rpb24oZSl7bGV0IGk9bnVsbDtyZXR1cm4gaT1lP25ldyhlfHxuKTpqKGhaKSxpfSxwcm92aWRlZEluOiJyb290In0pLG59KSgpKSxoWj0oKCk9PntjbGFzcyBuIGV4dGVuZHMgVG17Y29uc3RydWN0b3IoZSl7c3VwZXIoKSx0aGlzLl9kb2M9ZX1zYW5pdGl6ZShlLGkpe2lmKG51bGw9PWkpcmV0dXJuIG51bGw7c3dpdGNoKGUpe2Nhc2UgbW8uTk9ORTpyZXR1cm4gaTtjYXNlIG1vLkhUTUw6cmV0dXJuIFBjKGksIkhUTUwiKT9UYShpKTpEMyh0aGlzLl9kb2MsU3RyaW5nKGkpKS50b1N0cmluZygpO2Nhc2UgbW8uU1RZTEU6cmV0dXJuIFBjKGksIlN0eWxlIik/VGEoaSk6aTtjYXNlIG1vLlNDUklQVDppZihQYyhpLCJTY3JpcHQiKSlyZXR1cm4gVGEoaSk7dGhyb3cgbmV3IEVycm9yKCJ1bnNhZmUgdmFsdWUgdXNlZCBpbiBhIHNjcmlwdCBjb250ZXh0Iik7Y2FzZSBtby5VUkw6cmV0dXJuIFBjKGksIlVSTCIpP1RhKGkpOnp4KFN0cmluZyhpKSk7Y2FzZSBtby5SRVNPVVJDRV9VUkw6aWYoUGMoaSwiUmVzb3VyY2VVUkwiKSlyZXR1cm4gVGEoaSk7dGhyb3cgbmV3IEVycm9yKCJ1bnNhZmUgdmFsdWUgdXNlZCBpbiBhIHJlc291cmNlIFVSTCBjb250ZXh0IChzZWUgaHR0cHM6Ly9nLmNvL25nL3NlY3VyaXR5I3hzcykiKTtkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgVW5leHBlY3RlZCBTZWN1cml0eUNvbnRleHQgJHtlfSAoc2VlIGh0dHBzOi8vZy5jby9uZy9zZWN1cml0eSN4c3MpYCl9fWJ5cGFzc1NlY3VyaXR5VHJ1c3RIdG1sKGUpe3JldHVybiBmdW5jdGlvbihuKXtyZXR1cm4gbmV3IGhMKG4pfShlKX1ieXBhc3NTZWN1cml0eVRydXN0U3R5bGUoZSl7cmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBuZXcgZkwobil9KGUpfWJ5cGFzc1NlY3VyaXR5VHJ1c3RTY3JpcHQoZSl7cmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBuZXcgbUwobil9KGUpfWJ5cGFzc1NlY3VyaXR5VHJ1c3RVcmwoZSl7cmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBuZXcgZ0wobil9KGUpfWJ5cGFzc1NlY3VyaXR5VHJ1c3RSZXNvdXJjZVVybChlKXtyZXR1cm4gZnVuY3Rpb24obil7cmV0dXJuIG5ldyBfTChuKX0oZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooSHQpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6ZnVuY3Rpb24oZSl7bGV0IGk9bnVsbDtyZXR1cm4gaT1lP25ldyBlOmZ1bmN0aW9uKG4pe3JldHVybiBuZXcgaFoobi5nZXQoSHQpKX0oaihYbikpLGl9LHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksY009KG5ldyBJYygiMTQuMi4xMSIpLG9OKG1aKCksMSksY2xhc3N7fSksbkE9Y2xhc3N7fSxqdT0iKiI7ZnVuY3Rpb24gS3Iobix0KXtyZXR1cm57dHlwZTo3LG5hbWU6bixkZWZpbml0aW9uczp0LG9wdGlvbnM6e319fWZ1bmN0aW9uIGppKG4sdD1udWxsKXtyZXR1cm57dHlwZTo0LHN0eWxlczp0LHRpbWluZ3M6bn19ZnVuY3Rpb24geDUobix0PW51bGwpe3JldHVybnt0eXBlOjMsc3RlcHM6bixvcHRpb25zOnR9fWZ1bmN0aW9uIGlBKG4sdD1udWxsKXtyZXR1cm57dHlwZToyLHN0ZXBzOm4sb3B0aW9uczp0fX1mdW5jdGlvbiBnbihuKXtyZXR1cm57dHlwZTo2LHN0eWxlczpuLG9mZnNldDpudWxsfX1mdW5jdGlvbiBraShuLHQsZSl7cmV0dXJue3R5cGU6MCxuYW1lOm4sc3R5bGVzOnQsb3B0aW9uczplfX1mdW5jdGlvbiBEbShuKXtyZXR1cm57dHlwZTo1LHN0ZXBzOm59fWZ1bmN0aW9uIExpKG4sdCxlPW51bGwpe3JldHVybnt0eXBlOjEsZXhwcjpuLGFuaW1hdGlvbjp0LG9wdGlvbnM6ZX19ZnVuY3Rpb24gQW0obj1udWxsKXtyZXR1cm57dHlwZTo5LG9wdGlvbnM6bn19ZnVuY3Rpb24gSW0obix0LGU9bnVsbCl7cmV0dXJue3R5cGU6MTEsc2VsZWN0b3I6bixhbmltYXRpb246dCxvcHRpb25zOmV9fWZ1bmN0aW9uIGdaKG4pe1Byb21pc2UucmVzb2x2ZSgpLnRoZW4obil9dmFyIGRoPWNsYXNze2NvbnN0cnVjdG9yKHQ9MCxlPTApe3RoaXMuX29uRG9uZUZucz1bXSx0aGlzLl9vblN0YXJ0Rm5zPVtdLHRoaXMuX29uRGVzdHJveUZucz1bXSx0aGlzLl9vcmlnaW5hbE9uRG9uZUZucz1bXSx0aGlzLl9vcmlnaW5hbE9uU3RhcnRGbnM9W10sdGhpcy5fc3RhcnRlZD0hMSx0aGlzLl9kZXN0cm95ZWQ9ITEsdGhpcy5fZmluaXNoZWQ9ITEsdGhpcy5fcG9zaXRpb249MCx0aGlzLnBhcmVudFBsYXllcj1udWxsLHRoaXMudG90YWxUaW1lPXQrZX1fb25GaW5pc2goKXt0aGlzLl9maW5pc2hlZHx8KHRoaXMuX2ZpbmlzaGVkPSEwLHRoaXMuX29uRG9uZUZucy5mb3JFYWNoKHQ9PnQoKSksdGhpcy5fb25Eb25lRm5zPVtdKX1vblN0YXJ0KHQpe3RoaXMuX29yaWdpbmFsT25TdGFydEZucy5wdXNoKHQpLHRoaXMuX29uU3RhcnRGbnMucHVzaCh0KX1vbkRvbmUodCl7dGhpcy5fb3JpZ2luYWxPbkRvbmVGbnMucHVzaCh0KSx0aGlzLl9vbkRvbmVGbnMucHVzaCh0KX1vbkRlc3Ryb3kodCl7dGhpcy5fb25EZXN0cm95Rm5zLnB1c2godCl9aGFzU3RhcnRlZCgpe3JldHVybiB0aGlzLl9zdGFydGVkfWluaXQoKXt9cGxheSgpe3RoaXMuaGFzU3RhcnRlZCgpfHwodGhpcy5fb25TdGFydCgpLHRoaXMudHJpZ2dlck1pY3JvdGFzaygpKSx0aGlzLl9zdGFydGVkPSEwfXRyaWdnZXJNaWNyb3Rhc2soKXtnWigoKT0+dGhpcy5fb25GaW5pc2goKSl9X29uU3RhcnQoKXt0aGlzLl9vblN0YXJ0Rm5zLmZvckVhY2godD0+dCgpKSx0aGlzLl9vblN0YXJ0Rm5zPVtdfXBhdXNlKCl7fXJlc3RhcnQoKXt9ZmluaXNoKCl7dGhpcy5fb25GaW5pc2goKX1kZXN0cm95KCl7dGhpcy5fZGVzdHJveWVkfHwodGhpcy5fZGVzdHJveWVkPSEwLHRoaXMuaGFzU3RhcnRlZCgpfHx0aGlzLl9vblN0YXJ0KCksdGhpcy5maW5pc2goKSx0aGlzLl9vbkRlc3Ryb3lGbnMuZm9yRWFjaCh0PT50KCkpLHRoaXMuX29uRGVzdHJveUZucz1bXSl9cmVzZXQoKXt0aGlzLl9zdGFydGVkPSExLHRoaXMuX2ZpbmlzaGVkPSExLHRoaXMuX29uU3RhcnRGbnM9dGhpcy5fb3JpZ2luYWxPblN0YXJ0Rm5zLHRoaXMuX29uRG9uZUZucz10aGlzLl9vcmlnaW5hbE9uRG9uZUZuc31zZXRQb3NpdGlvbih0KXt0aGlzLl9wb3NpdGlvbj10aGlzLnRvdGFsVGltZT90KnRoaXMudG90YWxUaW1lOjF9Z2V0UG9zaXRpb24oKXtyZXR1cm4gdGhpcy50b3RhbFRpbWU/dGhpcy5fcG9zaXRpb24vdGhpcy50b3RhbFRpbWU6MX10cmlnZ2VyQ2FsbGJhY2sodCl7bGV0IGU9InN0YXJ0Ij09dD90aGlzLl9vblN0YXJ0Rm5zOnRoaXMuX29uRG9uZUZucztlLmZvckVhY2goaT0+aSgpKSxlLmxlbmd0aD0wfX0sdU09Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5fb25Eb25lRm5zPVtdLHRoaXMuX29uU3RhcnRGbnM9W10sdGhpcy5fZmluaXNoZWQ9ITEsdGhpcy5fc3RhcnRlZD0hMSx0aGlzLl9kZXN0cm95ZWQ9ITEsdGhpcy5fb25EZXN0cm95Rm5zPVtdLHRoaXMucGFyZW50UGxheWVyPW51bGwsdGhpcy50b3RhbFRpbWU9MCx0aGlzLnBsYXllcnM9dDtsZXQgZT0wLGk9MCxyPTAsbz10aGlzLnBsYXllcnMubGVuZ3RoOzA9PW8/Z1ooKCk9PnRoaXMuX29uRmluaXNoKCkpOnRoaXMucGxheWVycy5mb3JFYWNoKHM9PntzLm9uRG9uZSgoKT0+eysrZT09byYmdGhpcy5fb25GaW5pc2goKX0pLHMub25EZXN0cm95KCgpPT57KytpPT1vJiZ0aGlzLl9vbkRlc3Ryb3koKX0pLHMub25TdGFydCgoKT0+eysrcj09byYmdGhpcy5fb25TdGFydCgpfSl9KSx0aGlzLnRvdGFsVGltZT10aGlzLnBsYXllcnMucmVkdWNlKChzLGEpPT5NYXRoLm1heChzLGEudG90YWxUaW1lKSwwKX1fb25GaW5pc2goKXt0aGlzLl9maW5pc2hlZHx8KHRoaXMuX2ZpbmlzaGVkPSEwLHRoaXMuX29uRG9uZUZucy5mb3JFYWNoKHQ9PnQoKSksdGhpcy5fb25Eb25lRm5zPVtdKX1pbml0KCl7dGhpcy5wbGF5ZXJzLmZvckVhY2godD0+dC5pbml0KCkpfW9uU3RhcnQodCl7dGhpcy5fb25TdGFydEZucy5wdXNoKHQpfV9vblN0YXJ0KCl7dGhpcy5oYXNTdGFydGVkKCl8fCh0aGlzLl9zdGFydGVkPSEwLHRoaXMuX29uU3RhcnRGbnMuZm9yRWFjaCh0PT50KCkpLHRoaXMuX29uU3RhcnRGbnM9W10pfW9uRG9uZSh0KXt0aGlzLl9vbkRvbmVGbnMucHVzaCh0KX1vbkRlc3Ryb3kodCl7dGhpcy5fb25EZXN0cm95Rm5zLnB1c2godCl9aGFzU3RhcnRlZCgpe3JldHVybiB0aGlzLl9zdGFydGVkfXBsYXkoKXt0aGlzLnBhcmVudFBsYXllcnx8dGhpcy5pbml0KCksdGhpcy5fb25TdGFydCgpLHRoaXMucGxheWVycy5mb3JFYWNoKHQ9PnQucGxheSgpKX1wYXVzZSgpe3RoaXMucGxheWVycy5mb3JFYWNoKHQ9PnQucGF1c2UoKSl9cmVzdGFydCgpe3RoaXMucGxheWVycy5mb3JFYWNoKHQ9PnQucmVzdGFydCgpKX1maW5pc2goKXt0aGlzLl9vbkZpbmlzaCgpLHRoaXMucGxheWVycy5mb3JFYWNoKHQ9PnQuZmluaXNoKCkpfWRlc3Ryb3koKXt0aGlzLl9vbkRlc3Ryb3koKX1fb25EZXN0cm95KCl7dGhpcy5fZGVzdHJveWVkfHwodGhpcy5fZGVzdHJveWVkPSEwLHRoaXMuX29uRmluaXNoKCksdGhpcy5wbGF5ZXJzLmZvckVhY2godD0+dC5kZXN0cm95KCkpLHRoaXMuX29uRGVzdHJveUZucy5mb3JFYWNoKHQ9PnQoKSksdGhpcy5fb25EZXN0cm95Rm5zPVtdKX1yZXNldCgpe3RoaXMucGxheWVycy5mb3JFYWNoKHQ9PnQucmVzZXQoKSksdGhpcy5fZGVzdHJveWVkPSExLHRoaXMuX2ZpbmlzaGVkPSExLHRoaXMuX3N0YXJ0ZWQ9ITF9c2V0UG9zaXRpb24odCl7bGV0IGU9dCp0aGlzLnRvdGFsVGltZTt0aGlzLnBsYXllcnMuZm9yRWFjaChpPT57bGV0IHI9aS50b3RhbFRpbWU/TWF0aC5taW4oMSxlL2kudG90YWxUaW1lKToxO2kuc2V0UG9zaXRpb24ocil9KX1nZXRQb3NpdGlvbigpe2xldCB0PXRoaXMucGxheWVycy5yZWR1Y2UoKGUsaSk9Pm51bGw9PT1lfHxpLnRvdGFsVGltZT5lLnRvdGFsVGltZT9pOmUsbnVsbCk7cmV0dXJuIG51bGwhPXQ/dC5nZXRQb3NpdGlvbigpOjB9YmVmb3JlRGVzdHJveSgpe3RoaXMucGxheWVycy5mb3JFYWNoKHQ9Pnt0LmJlZm9yZURlc3Ryb3kmJnQuYmVmb3JlRGVzdHJveSgpfSl9dHJpZ2dlckNhbGxiYWNrKHQpe2xldCBlPSJzdGFydCI9PXQ/dGhpcy5fb25TdGFydEZuczp0aGlzLl9vbkRvbmVGbnM7ZS5mb3JFYWNoKGk9PmkoKSksZS5sZW5ndGg9MH19O2Z1bmN0aW9uIF9aKG4pe3JldHVybiBuZXcgQXQoM2UzLCExKX1mdW5jdGlvbiB6NSgpe3JldHVybiB0eXBlb2YgcHJvY2VzczwidSImJiJbb2JqZWN0IHByb2Nlc3NdIj09PXt9LnRvU3RyaW5nLmNhbGwocHJvY2Vzcyl9ZnVuY3Rpb24gcGgobil7c3dpdGNoKG4ubGVuZ3RoKXtjYXNlIDA6cmV0dXJuIG5ldyBkaDtjYXNlIDE6cmV0dXJuIG5bMF07ZGVmYXVsdDpyZXR1cm4gbmV3IHVNKG4pfX1mdW5jdGlvbiBSWihuLHQsZSxpLHI9bmV3IE1hcCxvPW5ldyBNYXApe2xldCBzPVtdLGE9W10sbD0tMSxjPW51bGw7aWYoaS5mb3JFYWNoKHU9PntsZXQgZD11LmdldCgib2Zmc2V0IikscD1kPT1sLGg9cCYmY3x8bmV3IE1hcDt1LmZvckVhY2goKGYsbSk9PntsZXQgeD1tLGc9ZjtpZigib2Zmc2V0IiE9PW0pc3dpdGNoKHg9dC5ub3JtYWxpemVQcm9wZXJ0eU5hbWUoeCxzKSxnKXtjYXNlIiEiOmc9ci5nZXQobSk7YnJlYWs7Y2FzZSBqdTpnPW8uZ2V0KG0pO2JyZWFrO2RlZmF1bHQ6Zz10Lm5vcm1hbGl6ZVN0eWxlVmFsdWUobSx4LGcscyl9aC5zZXQoeCxnKX0pLHB8fGEucHVzaChoKSxjPWgsbD1kfSkscy5sZW5ndGgpdGhyb3cgbmV3IEF0KDM1MDIsITEpO3JldHVybiBhfWZ1bmN0aW9uIGo1KG4sdCxlLGkpe3N3aXRjaCh0KXtjYXNlInN0YXJ0IjpuLm9uU3RhcnQoKCk9PmkoZSYmQzUoZSwic3RhcnQiLG4pKSk7YnJlYWs7Y2FzZSJkb25lIjpuLm9uRG9uZSgoKT0+aShlJiZDNShlLCJkb25lIixuKSkpO2JyZWFrO2Nhc2UiZGVzdHJveSI6bi5vbkRlc3Ryb3koKCk9PmkoZSYmQzUoZSwiZGVzdHJveSIsbikpKX19ZnVuY3Rpb24gQzUobix0LGUpe2xldCBvPUc1KG4uZWxlbWVudCxuLnRyaWdnZXJOYW1lLG4uZnJvbVN0YXRlLG4udG9TdGF0ZSx0fHxuLnBoYXNlTmFtZSxlLnRvdGFsVGltZT8/bi50b3RhbFRpbWUsISFlLmRpc2FibGVkKSxzPW4uX2RhdGE7cmV0dXJuIG51bGwhPXMmJihvLl9kYXRhPXMpLG99ZnVuY3Rpb24gRzUobix0LGUsaSxyPSIiLG89MCxzKXtyZXR1cm57ZWxlbWVudDpuLHRyaWdnZXJOYW1lOnQsZnJvbVN0YXRlOmUsdG9TdGF0ZTppLHBoYXNlTmFtZTpyLHRvdGFsVGltZTpvLGRpc2FibGVkOiEhc319ZnVuY3Rpb24gcGwobix0LGUpe2xldCBpPW4uZ2V0KHQpO3JldHVybiBpfHxuLnNldCh0LGk9ZSksaX1mdW5jdGlvbiB2WihuKXtsZXQgdD1uLmluZGV4T2YoIjoiKTtyZXR1cm5bbi5zdWJzdHJpbmcoMSx0KSxuLnNsaWNlKHQrMSldfXZhciBENT0obix0KT0+ITEsT1o9KG4sdCxlKT0+W10sa1o9bnVsbDtmdW5jdGlvbiBXNShuKXtsZXQgdD1uLnBhcmVudE5vZGV8fG4uaG9zdDtyZXR1cm4gdD09PWtaP251bGw6dH0oejUoKXx8dHlwZW9mIEVsZW1lbnQ8InUiKSYmKHR5cGVvZiB3aW5kb3c8InUiJiZ0eXBlb2Ygd2luZG93LmRvY3VtZW50PCJ1Ij8oa1o9KCgpPT5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQpKCksRDU9KG4sdCk9Pntmb3IoO3Q7KXtpZih0PT09bilyZXR1cm4hMDt0PVc1KHQpfXJldHVybiExfSk6RDU9KG4sdCk9Pm4uY29udGFpbnModCksT1o9KG4sdCxlKT0+e2lmKGUpcmV0dXJuIEFycmF5LmZyb20obi5xdWVyeVNlbGVjdG9yQWxsKHQpKTtsZXQgaT1uLnF1ZXJ5U2VsZWN0b3IodCk7cmV0dXJuIGk/W2ldOltdfSk7dmFyIFBtPW51bGwseVo9ITE7dmFyIEZaPUQ1LE5aPU9aLHE1PSgoKT0+e2NsYXNzIG57dmFsaWRhdGVTdHlsZVByb3BlcnR5KGUpe3JldHVybiBmdW5jdGlvbihuKXtQbXx8KFBtPSh0eXBlb2YgZG9jdW1lbnQ8InUiP2RvY3VtZW50LmJvZHk6bnVsbCl8fHt9LHlaPSEhUG0uc3R5bGUmJiJXZWJraXRBcHBlYXJhbmNlImluIFBtLnN0eWxlKTtsZXQgdD0hMDtyZXR1cm4gUG0uc3R5bGUmJiFmdW5jdGlvbihuKXtyZXR1cm4iZWJraXQiPT1uLnN1YnN0cmluZygxLDYpfShuKSYmKHQ9biBpbiBQbS5zdHlsZSwhdCYmeVomJih0PSJXZWJraXQiK24uY2hhckF0KDApLnRvVXBwZXJDYXNlKCkrbi5zbGljZSgxKWluIFBtLnN0eWxlKSksdH0oZSl9bWF0Y2hlc0VsZW1lbnQoZSxpKXtyZXR1cm4hMX1jb250YWluc0VsZW1lbnQoZSxpKXtyZXR1cm4gRlooZSxpKX1nZXRQYXJlbnRFbGVtZW50KGUpe3JldHVybiBXNShlKX1xdWVyeShlLGkscil7cmV0dXJuIE5aKGUsaSxyKX1jb21wdXRlU3R5bGUoZSxpLHIpe3JldHVybiByfHwiIn1hbmltYXRlKGUsaSxyLG8scyxhPVtdLGwpe3JldHVybiBuZXcgZGgocixvKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksdk09KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uTk9PUD1uZXcgcTUsbn0pKCksQlo9Im5nLWVudGVyIixBNT0ibmctbGVhdmUiLG9BPSJuZy10cmlnZ2VyIixjQT0iLm5nLXRyaWdnZXIiLGJaPSJuZy1hbmltYXRpbmciLEk1PSIubmctYW5pbWF0aW5nIjtmdW5jdGlvbiB0cChuKXtpZigibnVtYmVyIj09dHlwZW9mIG4pcmV0dXJuIG47bGV0IHQ9bi5tYXRjaCgvXigtP1tcLlxkXSspKG0/cykvKTtyZXR1cm4hdHx8dC5sZW5ndGg8Mj8wOlA1KHBhcnNlRmxvYXQodFsxXSksdFsyXSl9ZnVuY3Rpb24gUDUobix0KXtyZXR1cm4icyI9PT10PzFlMypuOm59ZnVuY3Rpb24gdUEobix0LGUpe3JldHVybiBuLmhhc093blByb3BlcnR5KCJkdXJhdGlvbiIpP246ZnVuY3Rpb24obix0LGUpe2xldCByLG89MCxzPSIiO2lmKCJzdHJpbmciPT10eXBlb2Ygbil7bGV0IGE9bi5tYXRjaCgvXigtP1tcLlxkXSspKG0/cykoPzpccysoLT9bXC5cZF0rKShtP3MpKT8oPzpccysoWy1hLXpdKyg/OlwoLis/XCkpPykpPyQvaSk7aWYobnVsbD09PWEpcmV0dXJuIHQucHVzaChfWigpKSx7ZHVyYXRpb246MCxkZWxheTowLGVhc2luZzoiIn07cj1QNShwYXJzZUZsb2F0KGFbMV0pLGFbMl0pO2xldCBsPWFbM107bnVsbCE9bCYmKG89UDUocGFyc2VGbG9hdChsKSxhWzRdKSk7bGV0IGM9YVs1XTtjJiYocz1jKX1lbHNlIHI9bjtpZighZSl7bGV0IGE9ITEsbD10Lmxlbmd0aDtyPDAmJih0LnB1c2gobmV3IEF0KDMxMDAsITEpKSxhPSEwKSxvPDAmJih0LnB1c2gobmV3IEF0KDMxMDEsITEpKSxhPSEwKSxhJiZ0LnNwbGljZShsLDAsX1ooKSl9cmV0dXJue2R1cmF0aW9uOnIsZGVsYXk6byxlYXNpbmc6c319KG4sdCxlKX1mdW5jdGlvbiB5TShuLHQ9e30pe3JldHVybiBPYmplY3Qua2V5cyhuKS5mb3JFYWNoKGU9Pnt0W2VdPW5bZV19KSx0fWZ1bmN0aW9uIFZaKG4pe2xldCB0PW5ldyBNYXA7cmV0dXJuIE9iamVjdC5rZXlzKG4pLmZvckVhY2goZT0+e3Quc2V0KGUsbltlXSl9KSx0fWZ1bmN0aW9uIGpfKG4sdD1uZXcgTWFwLGUpe2lmKGUpZm9yKGxldFtpLHJdb2YgZSl0LnNldChpLHIpO2ZvcihsZXRbaSxyXW9mIG4pdC5zZXQoaSxyKTtyZXR1cm4gdH1mdW5jdGlvbiB4WihuLHQsZSl7cmV0dXJuIGU/dCsiOiIrZSsiOyI6IiJ9ZnVuY3Rpb24gSFoobil7bGV0IHQ9IiI7Zm9yKGxldCBlPTA7ZTxuLnN0eWxlLmxlbmd0aDtlKyspe2xldCBpPW4uc3R5bGUuaXRlbShlKTt0Kz14WigwLGksbi5zdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKGkpKX1mb3IobGV0IGUgaW4gbi5zdHlsZSluLnN0eWxlLmhhc093blByb3BlcnR5KGUpJiYhZS5zdGFydHNXaXRoKCJfIikmJih0Kz14WigwLFNBZShlKSxuLnN0eWxlW2VdKSk7bi5zZXRBdHRyaWJ1dGUoInN0eWxlIix0KX1mdW5jdGlvbiBHdShuLHQsZSl7bi5zdHlsZSYmKHQuZm9yRWFjaCgoaSxyKT0+e2xldCBvPVk1KHIpO2UmJiFlLmhhcyhyKSYmZS5zZXQocixuLnN0eWxlW29dKSxuLnN0eWxlW29dPWl9KSx6NSgpJiZIWihuKSl9ZnVuY3Rpb24gT20obix0KXtuLnN0eWxlJiYodC5mb3JFYWNoKChlLGkpPT57bGV0IHI9WTUoaSk7bi5zdHlsZVtyXT0iIn0pLHo1KCkmJkhaKG4pKX1mdW5jdGlvbiBkTShuKXtyZXR1cm4gQXJyYXkuaXNBcnJheShuKT8xPT1uLmxlbmd0aD9uWzBdOmlBKG4pOm59dmFyIFI1PW5ldyBSZWdFeHAoInt7XFxzKiguKz8pXFxzKn19IiwiZyIpO2Z1bmN0aW9uIFVaKG4pe2xldCB0PVtdO2lmKCJzdHJpbmciPT10eXBlb2Ygbil7bGV0IGU7Zm9yKDtlPVI1LmV4ZWMobik7KXQucHVzaChlWzFdKTtSNS5sYXN0SW5kZXg9MH1yZXR1cm4gdH1mdW5jdGlvbiBoTShuLHQsZSl7bGV0IGk9bi50b1N0cmluZygpLHI9aS5yZXBsYWNlKFI1LChvLHMpPT57bGV0IGE9dFtzXTtyZXR1cm4gbnVsbD09YSYmKGUucHVzaChuZXcgQXQoMzAwMywhMSkpLGE9IiIpLGEudG9TdHJpbmcoKX0pO3JldHVybiByPT1pP246cn1mdW5jdGlvbiBkQShuKXtsZXQgdD1bXSxlPW4ubmV4dCgpO2Zvcig7IWUuZG9uZTspdC5wdXNoKGUudmFsdWUpLGU9bi5uZXh0KCk7cmV0dXJuIHR9dmFyIHdBZT0vLSsoW2EtejAtOV0pL2c7ZnVuY3Rpb24gWTUobil7cmV0dXJuIG4ucmVwbGFjZSh3QWUsKC4uLnQpPT50WzFdLnRvVXBwZXJDYXNlKCkpfWZ1bmN0aW9uIFNBZShuKXtyZXR1cm4gbi5yZXBsYWNlKC8oW2Etel0pKFtBLVpdKS9nLCIkMS0kMiIpLnRvTG93ZXJDYXNlKCl9ZnVuY3Rpb24gZGwobix0LGUpe3N3aXRjaCh0LnR5cGUpe2Nhc2UgNzpyZXR1cm4gbi52aXNpdFRyaWdnZXIodCxlKTtjYXNlIDA6cmV0dXJuIG4udmlzaXRTdGF0ZSh0LGUpO2Nhc2UgMTpyZXR1cm4gbi52aXNpdFRyYW5zaXRpb24odCxlKTtjYXNlIDI6cmV0dXJuIG4udmlzaXRTZXF1ZW5jZSh0LGUpO2Nhc2UgMzpyZXR1cm4gbi52aXNpdEdyb3VwKHQsZSk7Y2FzZSA0OnJldHVybiBuLnZpc2l0QW5pbWF0ZSh0LGUpO2Nhc2UgNTpyZXR1cm4gbi52aXNpdEtleWZyYW1lcyh0LGUpO2Nhc2UgNjpyZXR1cm4gbi52aXNpdFN0eWxlKHQsZSk7Y2FzZSA4OnJldHVybiBuLnZpc2l0UmVmZXJlbmNlKHQsZSk7Y2FzZSA5OnJldHVybiBuLnZpc2l0QW5pbWF0ZUNoaWxkKHQsZSk7Y2FzZSAxMDpyZXR1cm4gbi52aXNpdEFuaW1hdGVSZWYodCxlKTtjYXNlIDExOnJldHVybiBuLnZpc2l0UXVlcnkodCxlKTtjYXNlIDEyOnJldHVybiBuLnZpc2l0U3RhZ2dlcih0LGUpO2RlZmF1bHQ6dGhyb3cgbmV3IEF0KDMwMDQsITEpfX1mdW5jdGlvbiB6WihuLHQpe3JldHVybiB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShuKVt0XX1mdW5jdGlvbiBJQWUobix0KXtsZXQgZT1bXTtyZXR1cm4ic3RyaW5nIj09dHlwZW9mIG4/bi5zcGxpdCgvXHMqLFxzKi8pLmZvckVhY2goaT0+ZnVuY3Rpb24obix0LGUpe2lmKCI6Ij09blswXSl7bGV0IGw9ZnVuY3Rpb24obix0KXtzd2l0Y2gobil7Y2FzZSI6ZW50ZXIiOnJldHVybiJ2b2lkID0+ICoiO2Nhc2UiOmxlYXZlIjpyZXR1cm4iKiA9PiB2b2lkIjtjYXNlIjppbmNyZW1lbnQiOnJldHVybihlLGkpPT5wYXJzZUZsb2F0KGkpPnBhcnNlRmxvYXQoZSk7Y2FzZSI6ZGVjcmVtZW50IjpyZXR1cm4oZSxpKT0+cGFyc2VGbG9hdChpKTxwYXJzZUZsb2F0KGUpO2RlZmF1bHQ6cmV0dXJuIHQucHVzaChuZXcgQXQoMzAxNiwhMSkpLCIqID0+ICoifX0obixlKTtpZigiZnVuY3Rpb24iPT10eXBlb2YgbClyZXR1cm4gdm9pZCB0LnB1c2gobCk7bj1sfWxldCBpPW4ubWF0Y2goL14oXCp8Wy1cd10rKVxzKig8P1s9LV0+KVxzKihcKnxbLVx3XSspJC8pO2lmKG51bGw9PWl8fGkubGVuZ3RoPDQpcmV0dXJuIGUucHVzaChuZXcgQXQoMzAxNSwhMSkpLHQ7bGV0IHI9aVsxXSxvPWlbMl0scz1pWzNdO3QucHVzaChDWihyLHMpKSwiPCI9PW9bMF0mJiEoIioiPT1yJiYiKiI9PXMpJiZ0LnB1c2goQ1oocyxyKSl9KGksZSx0KSk6ZS5wdXNoKG4pLGV9dmFyIHNBPW5ldyBTZXQoWyJ0cnVlIiwiMSJdKSxhQT1uZXcgU2V0KFsiZmFsc2UiLCIwIl0pO2Z1bmN0aW9uIENaKG4sdCl7bGV0IGU9c0EuaGFzKG4pfHxhQS5oYXMobiksaT1zQS5oYXModCl8fGFBLmhhcyh0KTtyZXR1cm4ocixvKT0+e2xldCBzPSIqIj09bnx8bj09cixhPSIqIj09dHx8dD09bztyZXR1cm4hcyYmZSYmImJvb2xlYW4iPT10eXBlb2YgciYmKHM9cj9zQS5oYXMobik6YUEuaGFzKG4pKSwhYSYmaSYmImJvb2xlYW4iPT10eXBlb2YgbyYmKGE9bz9zQS5oYXModCk6YUEuaGFzKHQpKSxzJiZhfX12YXIgT0FlPW5ldyBSZWdFeHAoInMqOnNlbGZzKiw/IiwiZyIpO2Z1bmN0aW9uIHFaKG4sdCxlLGkpe3JldHVybiBuZXcgTzUobikuYnVpbGQodCxlLGkpfXZhciBPNT1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLl9kcml2ZXI9dH1idWlsZCh0LGUsaSl7bGV0IHI9bmV3IGs1KGUpO3JldHVybiB0aGlzLl9yZXNldENvbnRleHRTdHlsZVRpbWluZ1N0YXRlKHIpLGRsKHRoaXMsZE0odCkscil9X3Jlc2V0Q29udGV4dFN0eWxlVGltaW5nU3RhdGUodCl7dC5jdXJyZW50UXVlcnlTZWxlY3Rvcj0iIix0LmNvbGxlY3RlZFN0eWxlcz1uZXcgTWFwLHQuY29sbGVjdGVkU3R5bGVzLnNldCgiIixuZXcgTWFwKSx0LmN1cnJlbnRUaW1lPTB9dmlzaXRUcmlnZ2VyKHQsZSl7bGV0IGk9ZS5xdWVyeUNvdW50PTAscj1lLmRlcENvdW50PTAsbz1bXSxzPVtdO3JldHVybiJAIj09dC5uYW1lLmNoYXJBdCgwKSYmZS5lcnJvcnMucHVzaChuZXcgQXQoMzAwNiwhMSkpLHQuZGVmaW5pdGlvbnMuZm9yRWFjaChhPT57aWYodGhpcy5fcmVzZXRDb250ZXh0U3R5bGVUaW1pbmdTdGF0ZShlKSwwPT1hLnR5cGUpe2xldCBsPWEsYz1sLm5hbWU7Yy50b1N0cmluZygpLnNwbGl0KC9ccyosXHMqLykuZm9yRWFjaCh1PT57bC5uYW1lPXUsby5wdXNoKHRoaXMudmlzaXRTdGF0ZShsLGUpKX0pLGwubmFtZT1jfWVsc2UgaWYoMT09YS50eXBlKXtsZXQgbD10aGlzLnZpc2l0VHJhbnNpdGlvbihhLGUpO2krPWwucXVlcnlDb3VudCxyKz1sLmRlcENvdW50LHMucHVzaChsKX1lbHNlIGUuZXJyb3JzLnB1c2gobmV3IEF0KDMwMDcsITEpKX0pLHt0eXBlOjcsbmFtZTp0Lm5hbWUsc3RhdGVzOm8sdHJhbnNpdGlvbnM6cyxxdWVyeUNvdW50OmksZGVwQ291bnQ6cixvcHRpb25zOm51bGx9fXZpc2l0U3RhdGUodCxlKXtsZXQgaT10aGlzLnZpc2l0U3R5bGUodC5zdHlsZXMsZSkscj10Lm9wdGlvbnMmJnQub3B0aW9ucy5wYXJhbXN8fG51bGw7aWYoaS5jb250YWluc0R5bmFtaWNTdHlsZXMpe2xldCBvPW5ldyBTZXQscz1yfHx7fTtpLnN0eWxlcy5mb3JFYWNoKGE9PnthIGluc3RhbmNlb2YgTWFwJiZhLmZvckVhY2gobD0+e1VaKGwpLmZvckVhY2goYz0+e3MuaGFzT3duUHJvcGVydHkoYyl8fG8uYWRkKGMpfSl9KX0pLG8uc2l6ZSYmKGRBKG8udmFsdWVzKCkpLGUuZXJyb3JzLnB1c2gobmV3IEF0KDMwMDgsITEpKSl9cmV0dXJue3R5cGU6MCxuYW1lOnQubmFtZSxzdHlsZTppLG9wdGlvbnM6cj97cGFyYW1zOnJ9Om51bGx9fXZpc2l0VHJhbnNpdGlvbih0LGUpe2UucXVlcnlDb3VudD0wLGUuZGVwQ291bnQ9MDtsZXQgaT1kbCh0aGlzLGRNKHQuYW5pbWF0aW9uKSxlKTtyZXR1cm57dHlwZToxLG1hdGNoZXJzOklBZSh0LmV4cHIsZS5lcnJvcnMpLGFuaW1hdGlvbjppLHF1ZXJ5Q291bnQ6ZS5xdWVyeUNvdW50LGRlcENvdW50OmUuZGVwQ291bnQsb3B0aW9uczpSbSh0Lm9wdGlvbnMpfX12aXNpdFNlcXVlbmNlKHQsZSl7cmV0dXJue3R5cGU6MixzdGVwczp0LnN0ZXBzLm1hcChpPT5kbCh0aGlzLGksZSkpLG9wdGlvbnM6Um0odC5vcHRpb25zKX19dmlzaXRHcm91cCh0LGUpe2xldCBpPWUuY3VycmVudFRpbWUscj0wLG89dC5zdGVwcy5tYXAocz0+e2UuY3VycmVudFRpbWU9aTtsZXQgYT1kbCh0aGlzLHMsZSk7cmV0dXJuIHI9TWF0aC5tYXgocixlLmN1cnJlbnRUaW1lKSxhfSk7cmV0dXJuIGUuY3VycmVudFRpbWU9cix7dHlwZTozLHN0ZXBzOm8sb3B0aW9uczpSbSh0Lm9wdGlvbnMpfX12aXNpdEFuaW1hdGUodCxlKXtsZXQgaT1mdW5jdGlvbihuLHQpe2lmKG4uaGFzT3duUHJvcGVydHkoImR1cmF0aW9uIikpcmV0dXJuIG47aWYoIm51bWJlciI9PXR5cGVvZiBuKXJldHVybiBNNSh1QShuLHQpLmR1cmF0aW9uLDAsIiIpO2xldCBlPW47aWYoZS5zcGxpdCgvXHMrLykuc29tZShvPT4ieyI9PW8uY2hhckF0KDApJiYieyI9PW8uY2hhckF0KDEpKSl7bGV0IG89TTUoMCwwLCIiKTtyZXR1cm4gby5keW5hbWljPSEwLG8uc3RyVmFsdWU9ZSxvfWxldCByPXVBKGUsdCk7cmV0dXJuIE01KHIuZHVyYXRpb24sci5kZWxheSxyLmVhc2luZyl9KHQudGltaW5ncyxlLmVycm9ycyk7ZS5jdXJyZW50QW5pbWF0ZVRpbWluZ3M9aTtsZXQgcixvPXQuc3R5bGVzP3Quc3R5bGVzOmduKHt9KTtpZig1PT1vLnR5cGUpcj10aGlzLnZpc2l0S2V5ZnJhbWVzKG8sZSk7ZWxzZXtsZXQgcz10LnN0eWxlcyxhPSExO2lmKCFzKXthPSEwO2xldCBjPXt9O2kuZWFzaW5nJiYoYy5lYXNpbmc9aS5lYXNpbmcpLHM9Z24oYyl9ZS5jdXJyZW50VGltZSs9aS5kdXJhdGlvbitpLmRlbGF5O2xldCBsPXRoaXMudmlzaXRTdHlsZShzLGUpO2wuaXNFbXB0eVN0ZXA9YSxyPWx9cmV0dXJuIGUuY3VycmVudEFuaW1hdGVUaW1pbmdzPW51bGwse3R5cGU6NCx0aW1pbmdzOmksc3R5bGU6cixvcHRpb25zOm51bGx9fXZpc2l0U3R5bGUodCxlKXtsZXQgaT10aGlzLl9tYWtlU3R5bGVBc3QodCxlKTtyZXR1cm4gdGhpcy5fdmFsaWRhdGVTdHlsZUFzdChpLGUpLGl9X21ha2VTdHlsZUFzdCh0LGUpe2xldCBpPVtdLHI9QXJyYXkuaXNBcnJheSh0LnN0eWxlcyk/dC5zdHlsZXM6W3Quc3R5bGVzXTtmb3IobGV0IGEgb2Ygcikic3RyaW5nIj09dHlwZW9mIGE/YT09PWp1P2kucHVzaChhKTplLmVycm9ycy5wdXNoKG5ldyBBdCgzMDAyLCExKSk6aS5wdXNoKFZaKGEpKTtsZXQgbz0hMSxzPW51bGw7cmV0dXJuIGkuZm9yRWFjaChhPT57aWYoYSBpbnN0YW5jZW9mIE1hcCYmKGEuaGFzKCJlYXNpbmciKSYmKHM9YS5nZXQoImVhc2luZyIpLGEuZGVsZXRlKCJlYXNpbmciKSksIW8pKWZvcihsZXQgbCBvZiBhLnZhbHVlcygpKWlmKGwudG9TdHJpbmcoKS5pbmRleE9mKCJ7eyIpPj0wKXtvPSEwO2JyZWFrfX0pLHt0eXBlOjYsc3R5bGVzOmksZWFzaW5nOnMsb2Zmc2V0OnQub2Zmc2V0LGNvbnRhaW5zRHluYW1pY1N0eWxlczpvLG9wdGlvbnM6bnVsbH19X3ZhbGlkYXRlU3R5bGVBc3QodCxlKXtsZXQgaT1lLmN1cnJlbnRBbmltYXRlVGltaW5ncyxyPWUuY3VycmVudFRpbWUsbz1lLmN1cnJlbnRUaW1lO2kmJm8+MCYmKG8tPWkuZHVyYXRpb24raS5kZWxheSksdC5zdHlsZXMuZm9yRWFjaChzPT57InN0cmluZyIhPXR5cGVvZiBzJiZzLmZvckVhY2goKGEsbCk9PntsZXQgYz1lLmNvbGxlY3RlZFN0eWxlcy5nZXQoZS5jdXJyZW50UXVlcnlTZWxlY3RvciksdT1jLmdldChsKSxkPSEwO3UmJihvIT1yJiZvPj11LnN0YXJ0VGltZSYmcjw9dS5lbmRUaW1lJiYoZS5lcnJvcnMucHVzaChuZXcgQXQoMzAxMCwhMSkpLGQ9ITEpLG89dS5zdGFydFRpbWUpLGQmJmMuc2V0KGwse3N0YXJ0VGltZTpvLGVuZFRpbWU6cn0pLGUub3B0aW9ucyYmZnVuY3Rpb24obix0LGUpe2xldCBpPXQucGFyYW1zfHx7fSxyPVVaKG4pO3IubGVuZ3RoJiZyLmZvckVhY2gobz0+e2kuaGFzT3duUHJvcGVydHkobyl8fGUucHVzaChuZXcgQXQoMzAwMSwhMSkpfSl9KGEsZS5vcHRpb25zLGUuZXJyb3JzKX0pfSl9dmlzaXRLZXlmcmFtZXModCxlKXtsZXQgaT17dHlwZTo1LHN0eWxlczpbXSxvcHRpb25zOm51bGx9O2lmKCFlLmN1cnJlbnRBbmltYXRlVGltaW5ncylyZXR1cm4gZS5lcnJvcnMucHVzaChuZXcgQXQoMzAxMSwhMSkpLGk7bGV0IG89MCxzPVtdLGE9ITEsbD0hMSxjPTAsdT10LnN0ZXBzLm1hcChnPT57bGV0IGI9dGhpcy5fbWFrZVN0eWxlQXN0KGcsZSksRD1udWxsIT1iLm9mZnNldD9iLm9mZnNldDpmdW5jdGlvbihuKXtpZigic3RyaW5nIj09dHlwZW9mIG4pcmV0dXJuIG51bGw7bGV0IHQ9bnVsbDtpZihBcnJheS5pc0FycmF5KG4pKW4uZm9yRWFjaChlPT57aWYoZSBpbnN0YW5jZW9mIE1hcCYmZS5oYXMoIm9mZnNldCIpKXtsZXQgaT1lO3Q9cGFyc2VGbG9hdChpLmdldCgib2Zmc2V0IikpLGkuZGVsZXRlKCJvZmZzZXQiKX19KTtlbHNlIGlmKG4gaW5zdGFuY2VvZiBNYXAmJm4uaGFzKCJvZmZzZXQiKSl7bGV0IGU9bjt0PXBhcnNlRmxvYXQoZS5nZXQoIm9mZnNldCIpKSxlLmRlbGV0ZSgib2Zmc2V0Iil9cmV0dXJuIHR9KGIuc3R5bGVzKSxUPTA7cmV0dXJuIG51bGwhPUQmJihvKyssVD1iLm9mZnNldD1EKSxsPWx8fFQ8MHx8VD4xLGE9YXx8VDxjLGM9VCxzLnB1c2goVCksYn0pO2wmJmUuZXJyb3JzLnB1c2gobmV3IEF0KDMwMTIsITEpKSxhJiZlLmVycm9ycy5wdXNoKG5ldyBBdCgzMjAwLCExKSk7bGV0IGQ9dC5zdGVwcy5sZW5ndGgscD0wO28+MCYmbzxkP2UuZXJyb3JzLnB1c2gobmV3IEF0KDMyMDIsITEpKTowPT1vJiYocD0xLyhkLTEpKTtsZXQgaD1kLTEsZj1lLmN1cnJlbnRUaW1lLG09ZS5jdXJyZW50QW5pbWF0ZVRpbWluZ3MseD1tLmR1cmF0aW9uO3JldHVybiB1LmZvckVhY2goKGcsYik9PntsZXQgRD1wPjA/Yj09aD8xOnAqYjpzW2JdLFQ9RCp4O2UuY3VycmVudFRpbWU9ZittLmRlbGF5K1QsbS5kdXJhdGlvbj1ULHRoaXMuX3ZhbGlkYXRlU3R5bGVBc3QoZyxlKSxnLm9mZnNldD1ELGkuc3R5bGVzLnB1c2goZyl9KSxpfXZpc2l0UmVmZXJlbmNlKHQsZSl7cmV0dXJue3R5cGU6OCxhbmltYXRpb246ZGwodGhpcyxkTSh0LmFuaW1hdGlvbiksZSksb3B0aW9uczpSbSh0Lm9wdGlvbnMpfX12aXNpdEFuaW1hdGVDaGlsZCh0LGUpe3JldHVybiBlLmRlcENvdW50Kysse3R5cGU6OSxvcHRpb25zOlJtKHQub3B0aW9ucyl9fXZpc2l0QW5pbWF0ZVJlZih0LGUpe3JldHVybnt0eXBlOjEwLGFuaW1hdGlvbjp0aGlzLnZpc2l0UmVmZXJlbmNlKHQuYW5pbWF0aW9uLGUpLG9wdGlvbnM6Um0odC5vcHRpb25zKX19dmlzaXRRdWVyeSh0LGUpe2xldCBpPWUuY3VycmVudFF1ZXJ5U2VsZWN0b3Iscj10Lm9wdGlvbnN8fHt9O2UucXVlcnlDb3VudCsrLGUuY3VycmVudFF1ZXJ5PXQ7bGV0W28sc109ZnVuY3Rpb24obil7bGV0IHQ9ISFuLnNwbGl0KC9ccyosXHMqLykuZmluZChlPT4iOnNlbGYiPT1lKTtyZXR1cm4gdCYmKG49bi5yZXBsYWNlKE9BZSwiIikpLG49bi5yZXBsYWNlKC9AXCovZyxjQSkucmVwbGFjZSgvQFx3Ky9nLGU9PmNBKyItIitlLnNsaWNlKDEpKS5yZXBsYWNlKC86YW5pbWF0aW5nL2csSTUpLFtuLHRdfSh0LnNlbGVjdG9yKTtlLmN1cnJlbnRRdWVyeVNlbGVjdG9yPWkubGVuZ3RoP2krIiAiK286byxwbChlLmNvbGxlY3RlZFN0eWxlcyxlLmN1cnJlbnRRdWVyeVNlbGVjdG9yLG5ldyBNYXApO2xldCBhPWRsKHRoaXMsZE0odC5hbmltYXRpb24pLGUpO3JldHVybiBlLmN1cnJlbnRRdWVyeT1udWxsLGUuY3VycmVudFF1ZXJ5U2VsZWN0b3I9aSx7dHlwZToxMSxzZWxlY3RvcjpvLGxpbWl0OnIubGltaXR8fDAsb3B0aW9uYWw6ISFyLm9wdGlvbmFsLGluY2x1ZGVTZWxmOnMsYW5pbWF0aW9uOmEsb3JpZ2luYWxTZWxlY3Rvcjp0LnNlbGVjdG9yLG9wdGlvbnM6Um0odC5vcHRpb25zKX19dmlzaXRTdGFnZ2VyKHQsZSl7ZS5jdXJyZW50UXVlcnl8fGUuZXJyb3JzLnB1c2gobmV3IEF0KDMwMTMsITEpKTtsZXQgaT0iZnVsbCI9PT10LnRpbWluZ3M/e2R1cmF0aW9uOjAsZGVsYXk6MCxlYXNpbmc6ImZ1bGwifTp1QSh0LnRpbWluZ3MsZS5lcnJvcnMsITApO3JldHVybnt0eXBlOjEyLGFuaW1hdGlvbjpkbCh0aGlzLGRNKHQuYW5pbWF0aW9uKSxlKSx0aW1pbmdzOmksb3B0aW9uczpudWxsfX19LGs1PWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMuZXJyb3JzPXQsdGhpcy5xdWVyeUNvdW50PTAsdGhpcy5kZXBDb3VudD0wLHRoaXMuY3VycmVudFRyYW5zaXRpb249bnVsbCx0aGlzLmN1cnJlbnRRdWVyeT1udWxsLHRoaXMuY3VycmVudFF1ZXJ5U2VsZWN0b3I9bnVsbCx0aGlzLmN1cnJlbnRBbmltYXRlVGltaW5ncz1udWxsLHRoaXMuY3VycmVudFRpbWU9MCx0aGlzLmNvbGxlY3RlZFN0eWxlcz1uZXcgTWFwLHRoaXMub3B0aW9ucz1udWxsLHRoaXMudW5zdXBwb3J0ZWRDU1NQcm9wZXJ0aWVzRm91bmQ9bmV3IFNldH19O2Z1bmN0aW9uIFJtKG4pe3JldHVybiBuPyhuPXlNKG4pKS5wYXJhbXMmJihuLnBhcmFtcz1mdW5jdGlvbihuKXtyZXR1cm4gbj95TShuKTpudWxsfShuLnBhcmFtcykpOm49e30sbn1mdW5jdGlvbiBNNShuLHQsZSl7cmV0dXJue2R1cmF0aW9uOm4sZGVsYXk6dCxlYXNpbmc6ZX19ZnVuY3Rpb24gWDUobix0LGUsaSxyLG8scz1udWxsLGE9ITEpe3JldHVybnt0eXBlOjEsZWxlbWVudDpuLGtleWZyYW1lczp0LHByZVN0eWxlUHJvcHM6ZSxwb3N0U3R5bGVQcm9wczppLGR1cmF0aW9uOnIsZGVsYXk6byx0b3RhbFRpbWU6citvLGVhc2luZzpzLHN1YlRpbWVsaW5lOmF9fXZhciBmTT1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuX21hcD1uZXcgTWFwfWdldCh0KXtyZXR1cm4gdGhpcy5fbWFwLmdldCh0KXx8W119YXBwZW5kKHQsZSl7bGV0IGk9dGhpcy5fbWFwLmdldCh0KTtpfHx0aGlzLl9tYXAuc2V0KHQsaT1bXSksaS5wdXNoKC4uLmUpfWhhcyh0KXtyZXR1cm4gdGhpcy5fbWFwLmhhcyh0KX1jbGVhcigpe3RoaXMuX21hcC5jbGVhcigpfX0sSEFlPW5ldyBSZWdFeHAoIjplbnRlciIsImciKSx6QWU9bmV3IFJlZ0V4cCgiOmxlYXZlIiwiZyIpO2Z1bmN0aW9uIFlaKG4sdCxlLGkscixvPW5ldyBNYXAscz1uZXcgTWFwLGEsbCxjPVtdKXtyZXR1cm4obmV3IEY1KS5idWlsZEtleWZyYW1lcyhuLHQsZSxpLHIsbyxzLGEsbCxjKX12YXIgRjU9Y2xhc3N7YnVpbGRLZXlmcmFtZXModCxlLGkscixvLHMsYSxsLGMsdT1bXSl7Yz1jfHxuZXcgZk07bGV0IGQ9bmV3IG1NKHQsZSxjLHIsbyx1LFtdKTtkLm9wdGlvbnM9bDtsZXQgcD1sLmRlbGF5P3RwKGwuZGVsYXkpOjA7ZC5jdXJyZW50VGltZWxpbmUuZGVsYXlOZXh0U3RlcChwKSxkLmN1cnJlbnRUaW1lbGluZS5zZXRTdHlsZXMoW3NdLG51bGwsZC5lcnJvcnMsbCksZGwodGhpcyxpLGQpO2xldCBoPWQudGltZWxpbmVzLmZpbHRlcihmPT5mLmNvbnRhaW5zQW5pbWF0aW9uKCkpO2lmKGgubGVuZ3RoJiZhLnNpemUpe2xldCBmO2ZvcihsZXQgbT1oLmxlbmd0aC0xO20+PTA7bS0tKXtsZXQgeD1oW21dO2lmKHguZWxlbWVudD09PWUpe2Y9eDticmVha319ZiYmIWYuYWxsb3dPbmx5VGltZWxpbmVTdHlsZXMoKSYmZi5zZXRTdHlsZXMoW2FdLG51bGwsZC5lcnJvcnMsbCl9cmV0dXJuIGgubGVuZ3RoP2gubWFwKGY9PmYuYnVpbGRLZXlmcmFtZXMoKSk6W1g1KGUsW10sW10sW10sMCxwLCIiLCExKV19dmlzaXRUcmlnZ2VyKHQsZSl7fXZpc2l0U3RhdGUodCxlKXt9dmlzaXRUcmFuc2l0aW9uKHQsZSl7fXZpc2l0QW5pbWF0ZUNoaWxkKHQsZSl7bGV0IGk9ZS5zdWJJbnN0cnVjdGlvbnMuZ2V0KGUuZWxlbWVudCk7aWYoaSl7bGV0IHI9ZS5jcmVhdGVTdWJDb250ZXh0KHQub3B0aW9ucyksbz1lLmN1cnJlbnRUaW1lbGluZS5jdXJyZW50VGltZSxzPXRoaXMuX3Zpc2l0U3ViSW5zdHJ1Y3Rpb25zKGkscixyLm9wdGlvbnMpO28hPXMmJmUudHJhbnNmb3JtSW50b05ld1RpbWVsaW5lKHMpfWUucHJldmlvdXNOb2RlPXR9dmlzaXRBbmltYXRlUmVmKHQsZSl7bGV0IGk9ZS5jcmVhdGVTdWJDb250ZXh0KHQub3B0aW9ucyk7aS50cmFuc2Zvcm1JbnRvTmV3VGltZWxpbmUoKSx0aGlzLl9hcHBseUFuaW1hdGlvblJlZkRlbGF5cyhbdC5vcHRpb25zLHQuYW5pbWF0aW9uLm9wdGlvbnNdLGUsaSksdGhpcy52aXNpdFJlZmVyZW5jZSh0LmFuaW1hdGlvbixpKSxlLnRyYW5zZm9ybUludG9OZXdUaW1lbGluZShpLmN1cnJlbnRUaW1lbGluZS5jdXJyZW50VGltZSksZS5wcmV2aW91c05vZGU9dH1fYXBwbHlBbmltYXRpb25SZWZEZWxheXModCxlLGkpe2ZvcihsZXQgciBvZiB0KXtsZXQgbz1yPy5kZWxheTtpZihvKXtsZXQgcz0ibnVtYmVyIj09dHlwZW9mIG8/bzp0cChoTShvLHI/LnBhcmFtcz8/e30sZS5lcnJvcnMpKTtpLmRlbGF5TmV4dFN0ZXAocyl9fX1fdmlzaXRTdWJJbnN0cnVjdGlvbnModCxlLGkpe2xldCBvPWUuY3VycmVudFRpbWVsaW5lLmN1cnJlbnRUaW1lLHM9bnVsbCE9aS5kdXJhdGlvbj90cChpLmR1cmF0aW9uKTpudWxsLGE9bnVsbCE9aS5kZWxheT90cChpLmRlbGF5KTpudWxsO3JldHVybiAwIT09cyYmdC5mb3JFYWNoKGw9PntsZXQgYz1lLmFwcGVuZEluc3RydWN0aW9uVG9UaW1lbGluZShsLHMsYSk7bz1NYXRoLm1heChvLGMuZHVyYXRpb24rYy5kZWxheSl9KSxvfXZpc2l0UmVmZXJlbmNlKHQsZSl7ZS51cGRhdGVPcHRpb25zKHQub3B0aW9ucywhMCksZGwodGhpcyx0LmFuaW1hdGlvbixlKSxlLnByZXZpb3VzTm9kZT10fXZpc2l0U2VxdWVuY2UodCxlKXtsZXQgaT1lLnN1YkNvbnRleHRDb3VudCxyPWUsbz10Lm9wdGlvbnM7aWYobyYmKG8ucGFyYW1zfHxvLmRlbGF5KSYmKHI9ZS5jcmVhdGVTdWJDb250ZXh0KG8pLHIudHJhbnNmb3JtSW50b05ld1RpbWVsaW5lKCksbnVsbCE9by5kZWxheSkpezY9PXIucHJldmlvdXNOb2RlLnR5cGUmJihyLmN1cnJlbnRUaW1lbGluZS5zbmFwc2hvdEN1cnJlbnRTdHlsZXMoKSxyLnByZXZpb3VzTm9kZT1oQSk7bGV0IHM9dHAoby5kZWxheSk7ci5kZWxheU5leHRTdGVwKHMpfXQuc3RlcHMubGVuZ3RoJiYodC5zdGVwcy5mb3JFYWNoKHM9PmRsKHRoaXMscyxyKSksci5jdXJyZW50VGltZWxpbmUuYXBwbHlTdHlsZXNUb0tleWZyYW1lKCksci5zdWJDb250ZXh0Q291bnQ+aSYmci50cmFuc2Zvcm1JbnRvTmV3VGltZWxpbmUoKSksZS5wcmV2aW91c05vZGU9dH12aXNpdEdyb3VwKHQsZSl7bGV0IGk9W10scj1lLmN1cnJlbnRUaW1lbGluZS5jdXJyZW50VGltZSxvPXQub3B0aW9ucyYmdC5vcHRpb25zLmRlbGF5P3RwKHQub3B0aW9ucy5kZWxheSk6MDt0LnN0ZXBzLmZvckVhY2gocz0+e2xldCBhPWUuY3JlYXRlU3ViQ29udGV4dCh0Lm9wdGlvbnMpO28mJmEuZGVsYXlOZXh0U3RlcChvKSxkbCh0aGlzLHMsYSkscj1NYXRoLm1heChyLGEuY3VycmVudFRpbWVsaW5lLmN1cnJlbnRUaW1lKSxpLnB1c2goYS5jdXJyZW50VGltZWxpbmUpfSksaS5mb3JFYWNoKHM9PmUuY3VycmVudFRpbWVsaW5lLm1lcmdlVGltZWxpbmVDb2xsZWN0ZWRTdHlsZXMocykpLGUudHJhbnNmb3JtSW50b05ld1RpbWVsaW5lKHIpLGUucHJldmlvdXNOb2RlPXR9X3Zpc2l0VGltaW5nKHQsZSl7aWYodC5keW5hbWljKXtsZXQgaT10LnN0clZhbHVlO3JldHVybiB1QShlLnBhcmFtcz9oTShpLGUucGFyYW1zLGUuZXJyb3JzKTppLGUuZXJyb3JzKX1yZXR1cm57ZHVyYXRpb246dC5kdXJhdGlvbixkZWxheTp0LmRlbGF5LGVhc2luZzp0LmVhc2luZ319dmlzaXRBbmltYXRlKHQsZSl7bGV0IGk9ZS5jdXJyZW50QW5pbWF0ZVRpbWluZ3M9dGhpcy5fdmlzaXRUaW1pbmcodC50aW1pbmdzLGUpLHI9ZS5jdXJyZW50VGltZWxpbmU7aS5kZWxheSYmKGUuaW5jcmVtZW50VGltZShpLmRlbGF5KSxyLnNuYXBzaG90Q3VycmVudFN0eWxlcygpKTtsZXQgbz10LnN0eWxlOzU9PW8udHlwZT90aGlzLnZpc2l0S2V5ZnJhbWVzKG8sZSk6KGUuaW5jcmVtZW50VGltZShpLmR1cmF0aW9uKSx0aGlzLnZpc2l0U3R5bGUobyxlKSxyLmFwcGx5U3R5bGVzVG9LZXlmcmFtZSgpKSxlLmN1cnJlbnRBbmltYXRlVGltaW5ncz1udWxsLGUucHJldmlvdXNOb2RlPXR9dmlzaXRTdHlsZSh0LGUpe2xldCBpPWUuY3VycmVudFRpbWVsaW5lLHI9ZS5jdXJyZW50QW5pbWF0ZVRpbWluZ3M7IXImJmkuaGFzQ3VycmVudFN0eWxlUHJvcGVydGllcygpJiZpLmZvcndhcmRGcmFtZSgpO2xldCBvPXImJnIuZWFzaW5nfHx0LmVhc2luZzt0LmlzRW1wdHlTdGVwP2kuYXBwbHlFbXB0eVN0ZXAobyk6aS5zZXRTdHlsZXModC5zdHlsZXMsbyxlLmVycm9ycyxlLm9wdGlvbnMpLGUucHJldmlvdXNOb2RlPXR9dmlzaXRLZXlmcmFtZXModCxlKXtsZXQgaT1lLmN1cnJlbnRBbmltYXRlVGltaW5ncyxyPWUuY3VycmVudFRpbWVsaW5lLmR1cmF0aW9uLG89aS5kdXJhdGlvbixhPWUuY3JlYXRlU3ViQ29udGV4dCgpLmN1cnJlbnRUaW1lbGluZTthLmVhc2luZz1pLmVhc2luZyx0LnN0eWxlcy5mb3JFYWNoKGw9PnthLmZvcndhcmRUaW1lKChsLm9mZnNldHx8MCkqbyksYS5zZXRTdHlsZXMobC5zdHlsZXMsbC5lYXNpbmcsZS5lcnJvcnMsZS5vcHRpb25zKSxhLmFwcGx5U3R5bGVzVG9LZXlmcmFtZSgpfSksZS5jdXJyZW50VGltZWxpbmUubWVyZ2VUaW1lbGluZUNvbGxlY3RlZFN0eWxlcyhhKSxlLnRyYW5zZm9ybUludG9OZXdUaW1lbGluZShyK28pLGUucHJldmlvdXNOb2RlPXR9dmlzaXRRdWVyeSh0LGUpe2xldCBpPWUuY3VycmVudFRpbWVsaW5lLmN1cnJlbnRUaW1lLHI9dC5vcHRpb25zfHx7fSxvPXIuZGVsYXk/dHAoci5kZWxheSk6MDtvJiYoNj09PWUucHJldmlvdXNOb2RlLnR5cGV8fDA9PWkmJmUuY3VycmVudFRpbWVsaW5lLmhhc0N1cnJlbnRTdHlsZVByb3BlcnRpZXMoKSkmJihlLmN1cnJlbnRUaW1lbGluZS5zbmFwc2hvdEN1cnJlbnRTdHlsZXMoKSxlLnByZXZpb3VzTm9kZT1oQSk7bGV0IHM9aSxhPWUuaW52b2tlUXVlcnkodC5zZWxlY3Rvcix0Lm9yaWdpbmFsU2VsZWN0b3IsdC5saW1pdCx0LmluY2x1ZGVTZWxmLCEhci5vcHRpb25hbCxlLmVycm9ycyk7ZS5jdXJyZW50UXVlcnlUb3RhbD1hLmxlbmd0aDtsZXQgbD1udWxsO2EuZm9yRWFjaCgoYyx1KT0+e2UuY3VycmVudFF1ZXJ5SW5kZXg9dTtsZXQgZD1lLmNyZWF0ZVN1YkNvbnRleHQodC5vcHRpb25zLGMpO28mJmQuZGVsYXlOZXh0U3RlcChvKSxjPT09ZS5lbGVtZW50JiYobD1kLmN1cnJlbnRUaW1lbGluZSksZGwodGhpcyx0LmFuaW1hdGlvbixkKSxkLmN1cnJlbnRUaW1lbGluZS5hcHBseVN0eWxlc1RvS2V5ZnJhbWUoKSxzPU1hdGgubWF4KHMsZC5jdXJyZW50VGltZWxpbmUuY3VycmVudFRpbWUpfSksZS5jdXJyZW50UXVlcnlJbmRleD0wLGUuY3VycmVudFF1ZXJ5VG90YWw9MCxlLnRyYW5zZm9ybUludG9OZXdUaW1lbGluZShzKSxsJiYoZS5jdXJyZW50VGltZWxpbmUubWVyZ2VUaW1lbGluZUNvbGxlY3RlZFN0eWxlcyhsKSxlLmN1cnJlbnRUaW1lbGluZS5zbmFwc2hvdEN1cnJlbnRTdHlsZXMoKSksZS5wcmV2aW91c05vZGU9dH12aXNpdFN0YWdnZXIodCxlKXtsZXQgaT1lLnBhcmVudENvbnRleHQscj1lLmN1cnJlbnRUaW1lbGluZSxvPXQudGltaW5ncyxzPU1hdGguYWJzKG8uZHVyYXRpb24pLGE9cyooZS5jdXJyZW50UXVlcnlUb3RhbC0xKSxsPXMqZS5jdXJyZW50UXVlcnlJbmRleDtzd2l0Y2goby5kdXJhdGlvbjwwPyJyZXZlcnNlIjpvLmVhc2luZyl7Y2FzZSJyZXZlcnNlIjpsPWEtbDticmVhaztjYXNlImZ1bGwiOmw9aS5jdXJyZW50U3RhZ2dlclRpbWV9bGV0IHU9ZS5jdXJyZW50VGltZWxpbmU7bCYmdS5kZWxheU5leHRTdGVwKGwpO2xldCBkPXUuY3VycmVudFRpbWU7ZGwodGhpcyx0LmFuaW1hdGlvbixlKSxlLnByZXZpb3VzTm9kZT10LGkuY3VycmVudFN0YWdnZXJUaW1lPXIuY3VycmVudFRpbWUtZCsoci5zdGFydFRpbWUtaS5jdXJyZW50VGltZWxpbmUuc3RhcnRUaW1lKX19LGhBPXt9LG1NPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIsbyxzLGEsbCl7dGhpcy5fZHJpdmVyPXQsdGhpcy5lbGVtZW50PWUsdGhpcy5zdWJJbnN0cnVjdGlvbnM9aSx0aGlzLl9lbnRlckNsYXNzTmFtZT1yLHRoaXMuX2xlYXZlQ2xhc3NOYW1lPW8sdGhpcy5lcnJvcnM9cyx0aGlzLnRpbWVsaW5lcz1hLHRoaXMucGFyZW50Q29udGV4dD1udWxsLHRoaXMuY3VycmVudEFuaW1hdGVUaW1pbmdzPW51bGwsdGhpcy5wcmV2aW91c05vZGU9aEEsdGhpcy5zdWJDb250ZXh0Q291bnQ9MCx0aGlzLm9wdGlvbnM9e30sdGhpcy5jdXJyZW50UXVlcnlJbmRleD0wLHRoaXMuY3VycmVudFF1ZXJ5VG90YWw9MCx0aGlzLmN1cnJlbnRTdGFnZ2VyVGltZT0wLHRoaXMuY3VycmVudFRpbWVsaW5lPWx8fG5ldyBHXyh0aGlzLl9kcml2ZXIsZSwwKSxhLnB1c2godGhpcy5jdXJyZW50VGltZWxpbmUpfWdldCBwYXJhbXMoKXtyZXR1cm4gdGhpcy5vcHRpb25zLnBhcmFtc311cGRhdGVPcHRpb25zKHQsZSl7aWYoIXQpcmV0dXJuO2xldCBpPXQscj10aGlzLm9wdGlvbnM7bnVsbCE9aS5kdXJhdGlvbiYmKHIuZHVyYXRpb249dHAoaS5kdXJhdGlvbikpLG51bGwhPWkuZGVsYXkmJihyLmRlbGF5PXRwKGkuZGVsYXkpKTtsZXQgbz1pLnBhcmFtcztpZihvKXtsZXQgcz1yLnBhcmFtcztzfHwocz10aGlzLm9wdGlvbnMucGFyYW1zPXt9KSxPYmplY3Qua2V5cyhvKS5mb3JFYWNoKGE9PnsoIWV8fCFzLmhhc093blByb3BlcnR5KGEpKSYmKHNbYV09aE0ob1thXSxzLHRoaXMuZXJyb3JzKSl9KX19X2NvcHlPcHRpb25zKCl7bGV0IHQ9e307aWYodGhpcy5vcHRpb25zKXtsZXQgZT10aGlzLm9wdGlvbnMucGFyYW1zO2lmKGUpe2xldCBpPXQucGFyYW1zPXt9O09iamVjdC5rZXlzKGUpLmZvckVhY2gocj0+e2lbcl09ZVtyXX0pfX1yZXR1cm4gdH1jcmVhdGVTdWJDb250ZXh0KHQ9bnVsbCxlLGkpe2xldCByPWV8fHRoaXMuZWxlbWVudCxvPW5ldyBtTSh0aGlzLl9kcml2ZXIscix0aGlzLnN1Ykluc3RydWN0aW9ucyx0aGlzLl9lbnRlckNsYXNzTmFtZSx0aGlzLl9sZWF2ZUNsYXNzTmFtZSx0aGlzLmVycm9ycyx0aGlzLnRpbWVsaW5lcyx0aGlzLmN1cnJlbnRUaW1lbGluZS5mb3JrKHIsaXx8MCkpO3JldHVybiBvLnByZXZpb3VzTm9kZT10aGlzLnByZXZpb3VzTm9kZSxvLmN1cnJlbnRBbmltYXRlVGltaW5ncz10aGlzLmN1cnJlbnRBbmltYXRlVGltaW5ncyxvLm9wdGlvbnM9dGhpcy5fY29weU9wdGlvbnMoKSxvLnVwZGF0ZU9wdGlvbnModCksby5jdXJyZW50UXVlcnlJbmRleD10aGlzLmN1cnJlbnRRdWVyeUluZGV4LG8uY3VycmVudFF1ZXJ5VG90YWw9dGhpcy5jdXJyZW50UXVlcnlUb3RhbCxvLnBhcmVudENvbnRleHQ9dGhpcyx0aGlzLnN1YkNvbnRleHRDb3VudCsrLG99dHJhbnNmb3JtSW50b05ld1RpbWVsaW5lKHQpe3JldHVybiB0aGlzLnByZXZpb3VzTm9kZT1oQSx0aGlzLmN1cnJlbnRUaW1lbGluZT10aGlzLmN1cnJlbnRUaW1lbGluZS5mb3JrKHRoaXMuZWxlbWVudCx0KSx0aGlzLnRpbWVsaW5lcy5wdXNoKHRoaXMuY3VycmVudFRpbWVsaW5lKSx0aGlzLmN1cnJlbnRUaW1lbGluZX1hcHBlbmRJbnN0cnVjdGlvblRvVGltZWxpbmUodCxlLGkpe2xldCByPXtkdXJhdGlvbjplPz90LmR1cmF0aW9uLGRlbGF5OnRoaXMuY3VycmVudFRpbWVsaW5lLmN1cnJlbnRUaW1lKyhpPz8wKSt0LmRlbGF5LGVhc2luZzoiIn0sbz1uZXcgTjUodGhpcy5fZHJpdmVyLHQuZWxlbWVudCx0LmtleWZyYW1lcyx0LnByZVN0eWxlUHJvcHMsdC5wb3N0U3R5bGVQcm9wcyxyLHQuc3RyZXRjaFN0YXJ0aW5nS2V5ZnJhbWUpO3JldHVybiB0aGlzLnRpbWVsaW5lcy5wdXNoKG8pLHJ9aW5jcmVtZW50VGltZSh0KXt0aGlzLmN1cnJlbnRUaW1lbGluZS5mb3J3YXJkVGltZSh0aGlzLmN1cnJlbnRUaW1lbGluZS5kdXJhdGlvbit0KX1kZWxheU5leHRTdGVwKHQpe3Q+MCYmdGhpcy5jdXJyZW50VGltZWxpbmUuZGVsYXlOZXh0U3RlcCh0KX1pbnZva2VRdWVyeSh0LGUsaSxyLG8scyl7bGV0IGE9W107aWYociYmYS5wdXNoKHRoaXMuZWxlbWVudCksdC5sZW5ndGg+MCl7dD0odD10LnJlcGxhY2UoSEFlLCIuIit0aGlzLl9lbnRlckNsYXNzTmFtZSkpLnJlcGxhY2UoekFlLCIuIit0aGlzLl9sZWF2ZUNsYXNzTmFtZSk7bGV0IGM9dGhpcy5fZHJpdmVyLnF1ZXJ5KHRoaXMuZWxlbWVudCx0LDEhPWkpOzAhPT1pJiYoYz1pPDA/Yy5zbGljZShjLmxlbmd0aCtpLGMubGVuZ3RoKTpjLnNsaWNlKDAsaSkpLGEucHVzaCguLi5jKX1yZXR1cm4hbyYmMD09YS5sZW5ndGgmJnMucHVzaChuZXcgQXQoMzAxNCwhMSkpLGF9fSxHXz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXt0aGlzLl9kcml2ZXI9dCx0aGlzLmVsZW1lbnQ9ZSx0aGlzLnN0YXJ0VGltZT1pLHRoaXMuX2VsZW1lbnRUaW1lbGluZVN0eWxlc0xvb2t1cD1yLHRoaXMuZHVyYXRpb249MCx0aGlzLl9wcmV2aW91c0tleWZyYW1lPW5ldyBNYXAsdGhpcy5fY3VycmVudEtleWZyYW1lPW5ldyBNYXAsdGhpcy5fa2V5ZnJhbWVzPW5ldyBNYXAsdGhpcy5fc3R5bGVTdW1tYXJ5PW5ldyBNYXAsdGhpcy5fbG9jYWxUaW1lbGluZVN0eWxlcz1uZXcgTWFwLHRoaXMuX3BlbmRpbmdTdHlsZXM9bmV3IE1hcCx0aGlzLl9iYWNrRmlsbD1uZXcgTWFwLHRoaXMuX2N1cnJlbnRFbXB0eVN0ZXBLZXlmcmFtZT1udWxsLHRoaXMuX2VsZW1lbnRUaW1lbGluZVN0eWxlc0xvb2t1cHx8KHRoaXMuX2VsZW1lbnRUaW1lbGluZVN0eWxlc0xvb2t1cD1uZXcgTWFwKSx0aGlzLl9nbG9iYWxUaW1lbGluZVN0eWxlcz10aGlzLl9lbGVtZW50VGltZWxpbmVTdHlsZXNMb29rdXAuZ2V0KGUpLHRoaXMuX2dsb2JhbFRpbWVsaW5lU3R5bGVzfHwodGhpcy5fZ2xvYmFsVGltZWxpbmVTdHlsZXM9dGhpcy5fbG9jYWxUaW1lbGluZVN0eWxlcyx0aGlzLl9lbGVtZW50VGltZWxpbmVTdHlsZXNMb29rdXAuc2V0KGUsdGhpcy5fbG9jYWxUaW1lbGluZVN0eWxlcykpLHRoaXMuX2xvYWRLZXlmcmFtZSgpfWNvbnRhaW5zQW5pbWF0aW9uKCl7c3dpdGNoKHRoaXMuX2tleWZyYW1lcy5zaXplKXtjYXNlIDA6cmV0dXJuITE7Y2FzZSAxOnJldHVybiB0aGlzLmhhc0N1cnJlbnRTdHlsZVByb3BlcnRpZXMoKTtkZWZhdWx0OnJldHVybiEwfX1oYXNDdXJyZW50U3R5bGVQcm9wZXJ0aWVzKCl7cmV0dXJuIHRoaXMuX2N1cnJlbnRLZXlmcmFtZS5zaXplPjB9Z2V0IGN1cnJlbnRUaW1lKCl7cmV0dXJuIHRoaXMuc3RhcnRUaW1lK3RoaXMuZHVyYXRpb259ZGVsYXlOZXh0U3RlcCh0KXtsZXQgZT0xPT09dGhpcy5fa2V5ZnJhbWVzLnNpemUmJnRoaXMuX3BlbmRpbmdTdHlsZXMuc2l6ZTt0aGlzLmR1cmF0aW9ufHxlPyh0aGlzLmZvcndhcmRUaW1lKHRoaXMuY3VycmVudFRpbWUrdCksZSYmdGhpcy5zbmFwc2hvdEN1cnJlbnRTdHlsZXMoKSk6dGhpcy5zdGFydFRpbWUrPXR9Zm9yayh0LGUpe3JldHVybiB0aGlzLmFwcGx5U3R5bGVzVG9LZXlmcmFtZSgpLG5ldyBHXyh0aGlzLl9kcml2ZXIsdCxlfHx0aGlzLmN1cnJlbnRUaW1lLHRoaXMuX2VsZW1lbnRUaW1lbGluZVN0eWxlc0xvb2t1cCl9X2xvYWRLZXlmcmFtZSgpe3RoaXMuX2N1cnJlbnRLZXlmcmFtZSYmKHRoaXMuX3ByZXZpb3VzS2V5ZnJhbWU9dGhpcy5fY3VycmVudEtleWZyYW1lKSx0aGlzLl9jdXJyZW50S2V5ZnJhbWU9dGhpcy5fa2V5ZnJhbWVzLmdldCh0aGlzLmR1cmF0aW9uKSx0aGlzLl9jdXJyZW50S2V5ZnJhbWV8fCh0aGlzLl9jdXJyZW50S2V5ZnJhbWU9bmV3IE1hcCx0aGlzLl9rZXlmcmFtZXMuc2V0KHRoaXMuZHVyYXRpb24sdGhpcy5fY3VycmVudEtleWZyYW1lKSl9Zm9yd2FyZEZyYW1lKCl7dGhpcy5kdXJhdGlvbis9MSx0aGlzLl9sb2FkS2V5ZnJhbWUoKX1mb3J3YXJkVGltZSh0KXt0aGlzLmFwcGx5U3R5bGVzVG9LZXlmcmFtZSgpLHRoaXMuZHVyYXRpb249dCx0aGlzLl9sb2FkS2V5ZnJhbWUoKX1fdXBkYXRlU3R5bGUodCxlKXt0aGlzLl9sb2NhbFRpbWVsaW5lU3R5bGVzLnNldCh0LGUpLHRoaXMuX2dsb2JhbFRpbWVsaW5lU3R5bGVzLnNldCh0LGUpLHRoaXMuX3N0eWxlU3VtbWFyeS5zZXQodCx7dGltZTp0aGlzLmN1cnJlbnRUaW1lLHZhbHVlOmV9KX1hbGxvd09ubHlUaW1lbGluZVN0eWxlcygpe3JldHVybiB0aGlzLl9jdXJyZW50RW1wdHlTdGVwS2V5ZnJhbWUhPT10aGlzLl9jdXJyZW50S2V5ZnJhbWV9YXBwbHlFbXB0eVN0ZXAodCl7dCYmdGhpcy5fcHJldmlvdXNLZXlmcmFtZS5zZXQoImVhc2luZyIsdCk7Zm9yKGxldFtlLGldb2YgdGhpcy5fZ2xvYmFsVGltZWxpbmVTdHlsZXMpdGhpcy5fYmFja0ZpbGwuc2V0KGUsaXx8anUpLHRoaXMuX2N1cnJlbnRLZXlmcmFtZS5zZXQoZSxqdSk7dGhpcy5fY3VycmVudEVtcHR5U3RlcEtleWZyYW1lPXRoaXMuX2N1cnJlbnRLZXlmcmFtZX1zZXRTdHlsZXModCxlLGkscil7ZSYmdGhpcy5fcHJldmlvdXNLZXlmcmFtZS5zZXQoImVhc2luZyIsZSk7bGV0IG89ciYmci5wYXJhbXN8fHt9LHM9ZnVuY3Rpb24obix0KXtsZXQgaSxlPW5ldyBNYXA7cmV0dXJuIG4uZm9yRWFjaChyPT57aWYoIioiPT09cil7aT1pfHx0LmtleXMoKTtmb3IobGV0IG8gb2YgaSllLnNldChvLGp1KX1lbHNlIGpfKHIsZSl9KSxlfSh0LHRoaXMuX2dsb2JhbFRpbWVsaW5lU3R5bGVzKTtmb3IobGV0W2EsbF1vZiBzKXtsZXQgYz1oTShsLG8saSk7dGhpcy5fcGVuZGluZ1N0eWxlcy5zZXQoYSxjKSx0aGlzLl9sb2NhbFRpbWVsaW5lU3R5bGVzLmhhcyhhKXx8dGhpcy5fYmFja0ZpbGwuc2V0KGEsdGhpcy5fZ2xvYmFsVGltZWxpbmVTdHlsZXMuZ2V0KGEpPz9qdSksdGhpcy5fdXBkYXRlU3R5bGUoYSxjKX19YXBwbHlTdHlsZXNUb0tleWZyYW1lKCl7MCE9dGhpcy5fcGVuZGluZ1N0eWxlcy5zaXplJiYodGhpcy5fcGVuZGluZ1N0eWxlcy5mb3JFYWNoKCh0LGUpPT57dGhpcy5fY3VycmVudEtleWZyYW1lLnNldChlLHQpfSksdGhpcy5fcGVuZGluZ1N0eWxlcy5jbGVhcigpLHRoaXMuX2xvY2FsVGltZWxpbmVTdHlsZXMuZm9yRWFjaCgodCxlKT0+e3RoaXMuX2N1cnJlbnRLZXlmcmFtZS5oYXMoZSl8fHRoaXMuX2N1cnJlbnRLZXlmcmFtZS5zZXQoZSx0KX0pKX1zbmFwc2hvdEN1cnJlbnRTdHlsZXMoKXtmb3IobGV0W3QsZV1vZiB0aGlzLl9sb2NhbFRpbWVsaW5lU3R5bGVzKXRoaXMuX3BlbmRpbmdTdHlsZXMuc2V0KHQsZSksdGhpcy5fdXBkYXRlU3R5bGUodCxlKX1nZXRGaW5hbEtleWZyYW1lKCl7cmV0dXJuIHRoaXMuX2tleWZyYW1lcy5nZXQodGhpcy5kdXJhdGlvbil9Z2V0IHByb3BlcnRpZXMoKXtsZXQgdD1bXTtmb3IobGV0IGUgaW4gdGhpcy5fY3VycmVudEtleWZyYW1lKXQucHVzaChlKTtyZXR1cm4gdH1tZXJnZVRpbWVsaW5lQ29sbGVjdGVkU3R5bGVzKHQpe3QuX3N0eWxlU3VtbWFyeS5mb3JFYWNoKChlLGkpPT57bGV0IHI9dGhpcy5fc3R5bGVTdW1tYXJ5LmdldChpKTsoIXJ8fGUudGltZT5yLnRpbWUpJiZ0aGlzLl91cGRhdGVTdHlsZShpLGUudmFsdWUpfSl9YnVpbGRLZXlmcmFtZXMoKXt0aGlzLmFwcGx5U3R5bGVzVG9LZXlmcmFtZSgpO2xldCB0PW5ldyBTZXQsZT1uZXcgU2V0LGk9MT09PXRoaXMuX2tleWZyYW1lcy5zaXplJiYwPT09dGhpcy5kdXJhdGlvbixyPVtdO3RoaXMuX2tleWZyYW1lcy5mb3JFYWNoKChhLGwpPT57bGV0IGM9al8oYSxuZXcgTWFwLHRoaXMuX2JhY2tGaWxsKTtjLmZvckVhY2goKHUsZCk9PnsiISI9PT11P3QuYWRkKGQpOnU9PT1qdSYmZS5hZGQoZCl9KSxpfHxjLnNldCgib2Zmc2V0IixsL3RoaXMuZHVyYXRpb24pLHIucHVzaChjKX0pO2xldCBvPXQuc2l6ZT9kQSh0LnZhbHVlcygpKTpbXSxzPWUuc2l6ZT9kQShlLnZhbHVlcygpKTpbXTtpZihpKXtsZXQgYT1yWzBdLGw9bmV3IE1hcChhKTthLnNldCgib2Zmc2V0IiwwKSxsLnNldCgib2Zmc2V0IiwxKSxyPVthLGxdfXJldHVybiBYNSh0aGlzLmVsZW1lbnQscixvLHMsdGhpcy5kdXJhdGlvbix0aGlzLnN0YXJ0VGltZSx0aGlzLmVhc2luZywhMSl9fSxONT1jbGFzcyBleHRlbmRzIEdfe2NvbnN0cnVjdG9yKHQsZSxpLHIsbyxzLGE9ITEpe3N1cGVyKHQsZSxzLmRlbGF5KSx0aGlzLmtleWZyYW1lcz1pLHRoaXMucHJlU3R5bGVQcm9wcz1yLHRoaXMucG9zdFN0eWxlUHJvcHM9byx0aGlzLl9zdHJldGNoU3RhcnRpbmdLZXlmcmFtZT1hLHRoaXMudGltaW5ncz17ZHVyYXRpb246cy5kdXJhdGlvbixkZWxheTpzLmRlbGF5LGVhc2luZzpzLmVhc2luZ319Y29udGFpbnNBbmltYXRpb24oKXtyZXR1cm4gdGhpcy5rZXlmcmFtZXMubGVuZ3RoPjF9YnVpbGRLZXlmcmFtZXMoKXtsZXQgdD10aGlzLmtleWZyYW1lcyx7ZGVsYXk6ZSxkdXJhdGlvbjppLGVhc2luZzpyfT10aGlzLnRpbWluZ3M7aWYodGhpcy5fc3RyZXRjaFN0YXJ0aW5nS2V5ZnJhbWUmJmUpe2xldCBvPVtdLHM9aStlLGE9ZS9zLGw9al8odFswXSk7bC5zZXQoIm9mZnNldCIsMCksby5wdXNoKGwpO2xldCBjPWpfKHRbMF0pO2Muc2V0KCJvZmZzZXQiLHdaKGEpKSxvLnB1c2goYyk7bGV0IHU9dC5sZW5ndGgtMTtmb3IobGV0IGQ9MTtkPD11O2QrKyl7bGV0IHA9al8odFtkXSksaD1wLmdldCgib2Zmc2V0Iik7cC5zZXQoIm9mZnNldCIsd1ooKGUraCppKS9zKSksby5wdXNoKHApfWk9cyxlPTAscj0iIix0PW99cmV0dXJuIFg1KHRoaXMuZWxlbWVudCx0LHRoaXMucHJlU3R5bGVQcm9wcyx0aGlzLnBvc3RTdHlsZVByb3BzLGksZSxyLCEwKX19O2Z1bmN0aW9uIHdaKG4sdD0zKXtsZXQgZT1NYXRoLnBvdygxMCx0LTEpO3JldHVybiBNYXRoLnJvdW5kKG4qZSkvZX12YXIga209Y2xhc3N7fSxHQWU9bmV3IFNldChbIndpZHRoIiwiaGVpZ2h0IiwibWluV2lkdGgiLCJtaW5IZWlnaHQiLCJtYXhXaWR0aCIsIm1heEhlaWdodCIsImxlZnQiLCJ0b3AiLCJib3R0b20iLCJyaWdodCIsImZvbnRTaXplIiwib3V0bGluZVdpZHRoIiwib3V0bGluZU9mZnNldCIsInBhZGRpbmdUb3AiLCJwYWRkaW5nTGVmdCIsInBhZGRpbmdCb3R0b20iLCJwYWRkaW5nUmlnaHQiLCJtYXJnaW5Ub3AiLCJtYXJnaW5MZWZ0IiwibWFyZ2luQm90dG9tIiwibWFyZ2luUmlnaHQiLCJib3JkZXJSYWRpdXMiLCJib3JkZXJXaWR0aCIsImJvcmRlclRvcFdpZHRoIiwiYm9yZGVyTGVmdFdpZHRoIiwiYm9yZGVyUmlnaHRXaWR0aCIsImJvcmRlckJvdHRvbVdpZHRoIiwidGV4dEluZGVudCIsInBlcnNwZWN0aXZlIl0pLGZBPWNsYXNzIGV4dGVuZHMga217bm9ybWFsaXplUHJvcGVydHlOYW1lKHQsZSl7cmV0dXJuIFk1KHQpfW5vcm1hbGl6ZVN0eWxlVmFsdWUodCxlLGkscil7bGV0IG89IiIscz1pLnRvU3RyaW5nKCkudHJpbSgpO2lmKEdBZS5oYXMoZSkmJjAhPT1pJiYiMCIhPT1pKWlmKCJudW1iZXIiPT10eXBlb2YgaSlvPSJweCI7ZWxzZXtsZXQgYT1pLm1hdGNoKC9eWystXT9bXGRcLl0rKFthLXpdKikkLyk7YSYmMD09YVsxXS5sZW5ndGgmJnIucHVzaChuZXcgQXQoMzAwNSwhMSkpfXJldHVybiBzK299fTtmdW5jdGlvbiBTWihuLHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwKXtyZXR1cm57dHlwZTowLGVsZW1lbnQ6bix0cmlnZ2VyTmFtZTp0LGlzUmVtb3ZhbFRyYW5zaXRpb246cixmcm9tU3RhdGU6ZSxmcm9tU3R5bGVzOm8sdG9TdGF0ZTppLHRvU3R5bGVzOnMsdGltZWxpbmVzOmEscXVlcmllZEVsZW1lbnRzOmwscHJlU3R5bGVQcm9wczpjLHBvc3RTdHlsZVByb3BzOnUsdG90YWxUaW1lOmQsZXJyb3JzOnB9fXZhciB3NT17fSxtQT1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy5fdHJpZ2dlck5hbWU9dCx0aGlzLmFzdD1lLHRoaXMuX3N0YXRlU3R5bGVzPWl9bWF0Y2godCxlLGkscil7cmV0dXJuIGZ1bmN0aW9uKG4sdCxlLGkscil7cmV0dXJuIG4uc29tZShvPT5vKHQsZSxpLHIpKX0odGhpcy5hc3QubWF0Y2hlcnMsdCxlLGkscil9YnVpbGRTdHlsZXModCxlLGkpe2xldCByPXRoaXMuX3N0YXRlU3R5bGVzLmdldCgiKiIpO3JldHVybiB2b2lkIDAhPT10JiYocj10aGlzLl9zdGF0ZVN0eWxlcy5nZXQodD8udG9TdHJpbmcoKSl8fHIpLHI/ci5idWlsZFN0eWxlcyhlLGkpOm5ldyBNYXB9YnVpbGQodCxlLGkscixvLHMsYSxsLGMsdSl7bGV0IGQ9W10scD10aGlzLmFzdC5vcHRpb25zJiZ0aGlzLmFzdC5vcHRpb25zLnBhcmFtc3x8dzUsZj10aGlzLmJ1aWxkU3R5bGVzKGksYSYmYS5wYXJhbXN8fHc1LGQpLG09bCYmbC5wYXJhbXN8fHc1LHg9dGhpcy5idWlsZFN0eWxlcyhyLG0sZCksZz1uZXcgU2V0LGI9bmV3IE1hcCxEPW5ldyBNYXAsVD0idm9pZCI9PT1yLGs9e3BhcmFtczpxQWUobSxwKSxkZWxheTp0aGlzLmFzdC5vcHRpb25zPy5kZWxheX0sWj11P1tdOllaKHQsZSx0aGlzLmFzdC5hbmltYXRpb24sbyxzLGYseCxrLGMsZCksej0wO2lmKFouZm9yRWFjaCh1ZT0+e3o9TWF0aC5tYXgodWUuZHVyYXRpb24rdWUuZGVsYXkseil9KSxkLmxlbmd0aClyZXR1cm4gU1ooZSx0aGlzLl90cmlnZ2VyTmFtZSxpLHIsVCxmLHgsW10sW10sYixELHosZCk7Wi5mb3JFYWNoKHVlPT57bGV0IGhlPXVlLmVsZW1lbnQsdz1wbChiLGhlLG5ldyBTZXQpO3VlLnByZVN0eWxlUHJvcHMuZm9yRWFjaChxPT53LmFkZChxKSk7bGV0IEY9cGwoRCxoZSxuZXcgU2V0KTt1ZS5wb3N0U3R5bGVQcm9wcy5mb3JFYWNoKHE9PkYuYWRkKHEpKSxoZSE9PWUmJmcuYWRkKGhlKX0pO2xldCBmZT1kQShnLnZhbHVlcygpKTtyZXR1cm4gU1ooZSx0aGlzLl90cmlnZ2VyTmFtZSxpLHIsVCxmLHgsWixmZSxiLEQseil9fTtmdW5jdGlvbiBxQWUobix0KXtsZXQgZT15TSh0KTtmb3IobGV0IGkgaW4gbiluLmhhc093blByb3BlcnR5KGkpJiZudWxsIT1uW2ldJiYoZVtpXT1uW2ldKTtyZXR1cm4gZX1mdW5jdGlvbiBFWihuLHQsZSl7bi5oYXModCk/bi5oYXMoZSl8fG4uc2V0KGUsbi5nZXQodCkpOm4uaGFzKGUpJiZuLnNldCh0LG4uZ2V0KGUpKX12YXIgUUFlPW5ldyBmTSxUWj0ibmctYW5pbWF0ZS1xdWV1ZWQiLFM1PSJuZy1hbmltYXRlLWRpc2FibGVkIixlSWU9W10sWFo9e25hbWVzcGFjZUlkOiIiLHNldEZvclJlbW92YWw6ITEsc2V0Rm9yTW92ZTohMSxoYXNBbmltYXRpb246ITEscmVtb3ZlZEJlZm9yZVF1ZXJpZWQ6ITF9LHRJZT17bmFtZXNwYWNlSWQ6IiIsc2V0Rm9yTW92ZTohMSxzZXRGb3JSZW1vdmFsOiExLGhhc0FuaW1hdGlvbjohMSxyZW1vdmVkQmVmb3JlUXVlcmllZDohMH0sS2w9Il9fbmdfcmVtb3ZlZCIsZ009Y2xhc3N7Y29uc3RydWN0b3IodCxlPSIiKXt0aGlzLm5hbWVzcGFjZUlkPWU7bGV0IGk9dCYmdC5oYXNPd25Qcm9wZXJ0eSgidmFsdWUiKTtpZih0aGlzLnZhbHVlPWZ1bmN0aW9uKG4pe3JldHVybiBuPz9udWxsfShpP3QudmFsdWU6dCksaSl7bGV0IG89eU0odCk7ZGVsZXRlIG8udmFsdWUsdGhpcy5vcHRpb25zPW99ZWxzZSB0aGlzLm9wdGlvbnM9e307dGhpcy5vcHRpb25zLnBhcmFtc3x8KHRoaXMub3B0aW9ucy5wYXJhbXM9e30pfWdldCBwYXJhbXMoKXtyZXR1cm4gdGhpcy5vcHRpb25zLnBhcmFtc31hYnNvcmJPcHRpb25zKHQpe2xldCBlPXQucGFyYW1zO2lmKGUpe2xldCBpPXRoaXMub3B0aW9ucy5wYXJhbXM7T2JqZWN0LmtleXMoZSkuZm9yRWFjaChyPT57bnVsbD09aVtyXSYmKGlbcl09ZVtyXSl9KX19fSxwTT0idm9pZCIsRTU9bmV3IGdNKHBNKSxfTT1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy5uYW1lc3BhY2VJZD10LHRoaXMudHJpZ2dlck5hbWU9ZSx0aGlzLmVsZW1lbnQ9aSx0aGlzLl9wbGF5ZXI9bmV3IGRoLHRoaXMuX2NvbnRhaW5zUmVhbFBsYXllcj0hMSx0aGlzLl9xdWV1ZWRDYWxsYmFja3M9bmV3IE1hcCx0aGlzLmRlc3Ryb3llZD0hMSx0aGlzLm1hcmtlZEZvckRlc3Ryb3k9ITEsdGhpcy5kaXNhYmxlZD0hMSx0aGlzLnF1ZXVlZD0hMCx0aGlzLnRvdGFsVGltZT0wfXNldFJlYWxQbGF5ZXIodCl7dGhpcy5fY29udGFpbnNSZWFsUGxheWVyfHwodGhpcy5fcGxheWVyPXQsdGhpcy5fcXVldWVkQ2FsbGJhY2tzLmZvckVhY2goKGUsaSk9PntlLmZvckVhY2gocj0+ajUodCxpLHZvaWQgMCxyKSl9KSx0aGlzLl9xdWV1ZWRDYWxsYmFja3MuY2xlYXIoKSx0aGlzLl9jb250YWluc1JlYWxQbGF5ZXI9ITAsdGhpcy5vdmVycmlkZVRvdGFsVGltZSh0LnRvdGFsVGltZSksdGhpcy5xdWV1ZWQ9ITEpfWdldFJlYWxQbGF5ZXIoKXtyZXR1cm4gdGhpcy5fcGxheWVyfW92ZXJyaWRlVG90YWxUaW1lKHQpe3RoaXMudG90YWxUaW1lPXR9c3luY1BsYXllckV2ZW50cyh0KXtsZXQgZT10aGlzLl9wbGF5ZXI7ZS50cmlnZ2VyQ2FsbGJhY2smJnQub25TdGFydCgoKT0+ZS50cmlnZ2VyQ2FsbGJhY2soInN0YXJ0IikpLHQub25Eb25lKCgpPT50aGlzLmZpbmlzaCgpKSx0Lm9uRGVzdHJveSgoKT0+dGhpcy5kZXN0cm95KCkpfV9xdWV1ZUV2ZW50KHQsZSl7cGwodGhpcy5fcXVldWVkQ2FsbGJhY2tzLHQsW10pLnB1c2goZSl9b25Eb25lKHQpe3RoaXMucXVldWVkJiZ0aGlzLl9xdWV1ZUV2ZW50KCJkb25lIix0KSx0aGlzLl9wbGF5ZXIub25Eb25lKHQpfW9uU3RhcnQodCl7dGhpcy5xdWV1ZWQmJnRoaXMuX3F1ZXVlRXZlbnQoInN0YXJ0Iix0KSx0aGlzLl9wbGF5ZXIub25TdGFydCh0KX1vbkRlc3Ryb3kodCl7dGhpcy5xdWV1ZWQmJnRoaXMuX3F1ZXVlRXZlbnQoImRlc3Ryb3kiLHQpLHRoaXMuX3BsYXllci5vbkRlc3Ryb3kodCl9aW5pdCgpe3RoaXMuX3BsYXllci5pbml0KCl9aGFzU3RhcnRlZCgpe3JldHVybiF0aGlzLnF1ZXVlZCYmdGhpcy5fcGxheWVyLmhhc1N0YXJ0ZWQoKX1wbGF5KCl7IXRoaXMucXVldWVkJiZ0aGlzLl9wbGF5ZXIucGxheSgpfXBhdXNlKCl7IXRoaXMucXVldWVkJiZ0aGlzLl9wbGF5ZXIucGF1c2UoKX1yZXN0YXJ0KCl7IXRoaXMucXVldWVkJiZ0aGlzLl9wbGF5ZXIucmVzdGFydCgpfWZpbmlzaCgpe3RoaXMuX3BsYXllci5maW5pc2goKX1kZXN0cm95KCl7dGhpcy5kZXN0cm95ZWQ9ITAsdGhpcy5fcGxheWVyLmRlc3Ryb3koKX1yZXNldCgpeyF0aGlzLnF1ZXVlZCYmdGhpcy5fcGxheWVyLnJlc2V0KCl9c2V0UG9zaXRpb24odCl7dGhpcy5xdWV1ZWR8fHRoaXMuX3BsYXllci5zZXRQb3NpdGlvbih0KX1nZXRQb3NpdGlvbigpe3JldHVybiB0aGlzLnF1ZXVlZD8wOnRoaXMuX3BsYXllci5nZXRQb3NpdGlvbigpfXRyaWdnZXJDYWxsYmFjayh0KXtsZXQgZT10aGlzLl9wbGF5ZXI7ZS50cmlnZ2VyQ2FsbGJhY2smJmUudHJpZ2dlckNhbGxiYWNrKHQpfX07ZnVuY3Rpb24gbEEobil7cmV0dXJuIG4mJjE9PT1uLm5vZGVUeXBlfWZ1bmN0aW9uIERaKG4sdCl7bGV0IGU9bi5zdHlsZS5kaXNwbGF5O3JldHVybiBuLnN0eWxlLmRpc3BsYXk9dD8/Im5vbmUiLGV9ZnVuY3Rpb24gQVoobix0LGUsaSxyKXtsZXQgbz1bXTtlLmZvckVhY2gobD0+by5wdXNoKERaKGwpKSk7bGV0IHM9W107aS5mb3JFYWNoKChsLGMpPT57bGV0IHU9bmV3IE1hcDtsLmZvckVhY2goZD0+e2xldCBwPXQuY29tcHV0ZVN0eWxlKGMsZCxyKTt1LnNldChkLHApLCghcHx8MD09cC5sZW5ndGgpJiYoY1tLbF09dEllLHMucHVzaChjKSl9KSxuLnNldChjLHUpfSk7bGV0IGE9MDtyZXR1cm4gZS5mb3JFYWNoKGw9PkRaKGwsb1thKytdKSksc31mdW5jdGlvbiBJWihuLHQpe2xldCBlPW5ldyBNYXA7aWYobi5mb3JFYWNoKGE9PmUuc2V0KGEsW10pKSwwPT10Lmxlbmd0aClyZXR1cm4gZTtsZXQgcj1uZXcgU2V0KHQpLG89bmV3IE1hcDtmdW5jdGlvbiBzKGEpe2lmKCFhKXJldHVybiAxO2xldCBsPW8uZ2V0KGEpO2lmKGwpcmV0dXJuIGw7bGV0IGM9YS5wYXJlbnROb2RlO3JldHVybiBsPWUuaGFzKGMpP2M6ci5oYXMoYyk/MTpzKGMpLG8uc2V0KGEsbCksbH1yZXR1cm4gdC5mb3JFYWNoKGE9PntsZXQgbD1zKGEpOzEhPT1sJiZlLmdldChsKS5wdXNoKGEpfSksZX1mdW5jdGlvbiBabChuLHQpe24uY2xhc3NMaXN0Py5hZGQodCl9ZnVuY3Rpb24gel8obix0KXtuLmNsYXNzTGlzdD8ucmVtb3ZlKHQpfWZ1bmN0aW9uIG9JZShuLHQsZSl7cGgoZSkub25Eb25lKCgpPT5uLnByb2Nlc3NMZWF2ZU5vZGUodCkpfWZ1bmN0aW9uIFFaKG4sdCl7Zm9yKGxldCBlPTA7ZTxuLmxlbmd0aDtlKyspe2xldCBpPW5bZV07aSBpbnN0YW5jZW9mIHVNP1FaKGkucGxheWVycyx0KTp0LnB1c2goaSl9fWZ1bmN0aW9uIFBaKG4sdCxlKXtsZXQgaT1lLmdldChuKTtpZighaSlyZXR1cm4hMTtsZXQgcj10LmdldChuKTtyZXR1cm4gcj9pLmZvckVhY2gobz0+ci5hZGQobykpOnQuc2V0KG4saSksZS5kZWxldGUobiksITB9dmFyIGhoPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLmJvZHlOb2RlPXQsdGhpcy5fZHJpdmVyPWUsdGhpcy5fbm9ybWFsaXplcj1pLHRoaXMuX3RyaWdnZXJDYWNoZT17fSx0aGlzLm9uUmVtb3ZhbENvbXBsZXRlPShyLG8pPT57fSx0aGlzLl90cmFuc2l0aW9uRW5naW5lPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy5ib2R5Tm9kZT10LHRoaXMuZHJpdmVyPWUsdGhpcy5fbm9ybWFsaXplcj1pLHRoaXMucGxheWVycz1bXSx0aGlzLm5ld0hvc3RFbGVtZW50cz1uZXcgTWFwLHRoaXMucGxheWVyc0J5RWxlbWVudD1uZXcgTWFwLHRoaXMucGxheWVyc0J5UXVlcmllZEVsZW1lbnQ9bmV3IE1hcCx0aGlzLnN0YXRlc0J5RWxlbWVudD1uZXcgTWFwLHRoaXMuZGlzYWJsZWROb2Rlcz1uZXcgU2V0LHRoaXMudG90YWxBbmltYXRpb25zPTAsdGhpcy50b3RhbFF1ZXVlZFBsYXllcnM9MCx0aGlzLl9uYW1lc3BhY2VMb29rdXA9e30sdGhpcy5fbmFtZXNwYWNlTGlzdD1bXSx0aGlzLl9mbHVzaEZucz1bXSx0aGlzLl93aGVuUXVpZXRGbnM9W10sdGhpcy5uYW1lc3BhY2VzQnlIb3N0RWxlbWVudD1uZXcgTWFwLHRoaXMuY29sbGVjdGVkRW50ZXJFbGVtZW50cz1bXSx0aGlzLmNvbGxlY3RlZExlYXZlRWxlbWVudHM9W10sdGhpcy5vblJlbW92YWxDb21wbGV0ZT0ocixvKT0+e319X29uUmVtb3ZhbENvbXBsZXRlKHQsZSl7dGhpcy5vblJlbW92YWxDb21wbGV0ZSh0LGUpfWdldCBxdWV1ZWRQbGF5ZXJzKCl7bGV0IHQ9W107cmV0dXJuIHRoaXMuX25hbWVzcGFjZUxpc3QuZm9yRWFjaChlPT57ZS5wbGF5ZXJzLmZvckVhY2goaT0+e2kucXVldWVkJiZ0LnB1c2goaSl9KX0pLHR9Y3JlYXRlTmFtZXNwYWNlKHQsZSl7bGV0IGk9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLmlkPXQsdGhpcy5ob3N0RWxlbWVudD1lLHRoaXMuX2VuZ2luZT1pLHRoaXMucGxheWVycz1bXSx0aGlzLl90cmlnZ2Vycz1uZXcgTWFwLHRoaXMuX3F1ZXVlPVtdLHRoaXMuX2VsZW1lbnRMaXN0ZW5lcnM9bmV3IE1hcCx0aGlzLl9ob3N0Q2xhc3NOYW1lPSJuZy10bnMtIit0LFpsKGUsdGhpcy5faG9zdENsYXNzTmFtZSl9bGlzdGVuKHQsZSxpLHIpe2lmKCF0aGlzLl90cmlnZ2Vycy5oYXMoZSkpdGhyb3cgbmV3IEF0KDMzMDIsITEpO2lmKG51bGw9PWl8fDA9PWkubGVuZ3RoKXRocm93IG5ldyBBdCgzMzAzLCExKTtpZighZnVuY3Rpb24obil7cmV0dXJuInN0YXJ0Ij09bnx8ImRvbmUiPT1ufShpKSl0aHJvdyBuZXcgQXQoMzQwMCwhMSk7bGV0IG89cGwodGhpcy5fZWxlbWVudExpc3RlbmVycyx0LFtdKSxzPXtuYW1lOmUscGhhc2U6aSxjYWxsYmFjazpyfTtvLnB1c2gocyk7bGV0IGE9cGwodGhpcy5fZW5naW5lLnN0YXRlc0J5RWxlbWVudCx0LG5ldyBNYXApO3JldHVybiBhLmhhcyhlKXx8KFpsKHQsb0EpLFpsKHQsb0ErIi0iK2UpLGEuc2V0KGUsRTUpKSwoKT0+e3RoaXMuX2VuZ2luZS5hZnRlckZsdXNoKCgpPT57bGV0IGw9by5pbmRleE9mKHMpO2w+PTAmJm8uc3BsaWNlKGwsMSksdGhpcy5fdHJpZ2dlcnMuaGFzKGUpfHxhLmRlbGV0ZShlKX0pfX1yZWdpc3Rlcih0LGUpe3JldHVybiF0aGlzLl90cmlnZ2Vycy5oYXModCkmJih0aGlzLl90cmlnZ2Vycy5zZXQodCxlKSwhMCl9X2dldFRyaWdnZXIodCl7bGV0IGU9dGhpcy5fdHJpZ2dlcnMuZ2V0KHQpO2lmKCFlKXRocm93IG5ldyBBdCgzNDAxLCExKTtyZXR1cm4gZX10cmlnZ2VyKHQsZSxpLHI9ITApe2xldCBvPXRoaXMuX2dldFRyaWdnZXIoZSkscz1uZXcgX00odGhpcy5pZCxlLHQpLGE9dGhpcy5fZW5naW5lLnN0YXRlc0J5RWxlbWVudC5nZXQodCk7YXx8KFpsKHQsb0EpLFpsKHQsb0ErIi0iK2UpLHRoaXMuX2VuZ2luZS5zdGF0ZXNCeUVsZW1lbnQuc2V0KHQsYT1uZXcgTWFwKSk7bGV0IGw9YS5nZXQoZSksYz1uZXcgZ00oaSx0aGlzLmlkKTtpZighKGkmJmkuaGFzT3duUHJvcGVydHkoInZhbHVlIikpJiZsJiZjLmFic29yYk9wdGlvbnMobC5vcHRpb25zKSxhLnNldChlLGMpLGx8fChsPUU1KSxjLnZhbHVlIT09cE0mJmwudmFsdWU9PT1jLnZhbHVlKXtpZighZnVuY3Rpb24obix0KXtsZXQgZT1PYmplY3Qua2V5cyhuKSxpPU9iamVjdC5rZXlzKHQpO2lmKGUubGVuZ3RoIT1pLmxlbmd0aClyZXR1cm4hMTtmb3IobGV0IHI9MDtyPGUubGVuZ3RoO3IrKyl7bGV0IG89ZVtyXTtpZighdC5oYXNPd25Qcm9wZXJ0eShvKXx8bltvXSE9PXRbb10pcmV0dXJuITF9cmV0dXJuITB9KGwucGFyYW1zLGMucGFyYW1zKSl7bGV0IG09W10seD1vLm1hdGNoU3R5bGVzKGwudmFsdWUsbC5wYXJhbXMsbSksZz1vLm1hdGNoU3R5bGVzKGMudmFsdWUsYy5wYXJhbXMsbSk7bS5sZW5ndGg/dGhpcy5fZW5naW5lLnJlcG9ydEVycm9yKG0pOnRoaXMuX2VuZ2luZS5hZnRlckZsdXNoKCgpPT57T20odCx4KSxHdSh0LGcpfSl9cmV0dXJufWxldCBwPXBsKHRoaXMuX2VuZ2luZS5wbGF5ZXJzQnlFbGVtZW50LHQsW10pO3AuZm9yRWFjaChtPT57bS5uYW1lc3BhY2VJZD09dGhpcy5pZCYmbS50cmlnZ2VyTmFtZT09ZSYmbS5xdWV1ZWQmJm0uZGVzdHJveSgpfSk7bGV0IGg9by5tYXRjaFRyYW5zaXRpb24obC52YWx1ZSxjLnZhbHVlLHQsYy5wYXJhbXMpLGY9ITE7aWYoIWgpe2lmKCFyKXJldHVybjtoPW8uZmFsbGJhY2tUcmFuc2l0aW9uLGY9ITB9cmV0dXJuIHRoaXMuX2VuZ2luZS50b3RhbFF1ZXVlZFBsYXllcnMrKyx0aGlzLl9xdWV1ZS5wdXNoKHtlbGVtZW50OnQsdHJpZ2dlck5hbWU6ZSx0cmFuc2l0aW9uOmgsZnJvbVN0YXRlOmwsdG9TdGF0ZTpjLHBsYXllcjpzLGlzRmFsbGJhY2tUcmFuc2l0aW9uOmZ9KSxmfHwoWmwodCxUWikscy5vblN0YXJ0KCgpPT57el8odCxUWil9KSkscy5vbkRvbmUoKCk9PntsZXQgbT10aGlzLnBsYXllcnMuaW5kZXhPZihzKTttPj0wJiZ0aGlzLnBsYXllcnMuc3BsaWNlKG0sMSk7bGV0IHg9dGhpcy5fZW5naW5lLnBsYXllcnNCeUVsZW1lbnQuZ2V0KHQpO2lmKHgpe2xldCBnPXguaW5kZXhPZihzKTtnPj0wJiZ4LnNwbGljZShnLDEpfX0pLHRoaXMucGxheWVycy5wdXNoKHMpLHAucHVzaChzKSxzfWRlcmVnaXN0ZXIodCl7dGhpcy5fdHJpZ2dlcnMuZGVsZXRlKHQpLHRoaXMuX2VuZ2luZS5zdGF0ZXNCeUVsZW1lbnQuZm9yRWFjaChlPT5lLmRlbGV0ZSh0KSksdGhpcy5fZWxlbWVudExpc3RlbmVycy5mb3JFYWNoKChlLGkpPT57dGhpcy5fZWxlbWVudExpc3RlbmVycy5zZXQoaSxlLmZpbHRlcihyPT5yLm5hbWUhPXQpKX0pfWNsZWFyRWxlbWVudENhY2hlKHQpe3RoaXMuX2VuZ2luZS5zdGF0ZXNCeUVsZW1lbnQuZGVsZXRlKHQpLHRoaXMuX2VsZW1lbnRMaXN0ZW5lcnMuZGVsZXRlKHQpO2xldCBlPXRoaXMuX2VuZ2luZS5wbGF5ZXJzQnlFbGVtZW50LmdldCh0KTtlJiYoZS5mb3JFYWNoKGk9PmkuZGVzdHJveSgpKSx0aGlzLl9lbmdpbmUucGxheWVyc0J5RWxlbWVudC5kZWxldGUodCkpfV9zaWduYWxSZW1vdmFsRm9ySW5uZXJUcmlnZ2Vycyh0LGUpe2xldCBpPXRoaXMuX2VuZ2luZS5kcml2ZXIucXVlcnkodCxjQSwhMCk7aS5mb3JFYWNoKHI9PntpZihyW0tsXSlyZXR1cm47bGV0IG89dGhpcy5fZW5naW5lLmZldGNoTmFtZXNwYWNlc0J5RWxlbWVudChyKTtvLnNpemU/by5mb3JFYWNoKHM9PnMudHJpZ2dlckxlYXZlQW5pbWF0aW9uKHIsZSwhMSwhMCkpOnRoaXMuY2xlYXJFbGVtZW50Q2FjaGUocil9KSx0aGlzLl9lbmdpbmUuYWZ0ZXJGbHVzaEFuaW1hdGlvbnNEb25lKCgpPT5pLmZvckVhY2gocj0+dGhpcy5jbGVhckVsZW1lbnRDYWNoZShyKSkpfXRyaWdnZXJMZWF2ZUFuaW1hdGlvbih0LGUsaSxyKXtsZXQgbz10aGlzLl9lbmdpbmUuc3RhdGVzQnlFbGVtZW50LmdldCh0KSxzPW5ldyBNYXA7aWYobyl7bGV0IGE9W107aWYoby5mb3JFYWNoKChsLGMpPT57aWYocy5zZXQoYyxsLnZhbHVlKSx0aGlzLl90cmlnZ2Vycy5oYXMoYykpe2xldCB1PXRoaXMudHJpZ2dlcih0LGMscE0scik7dSYmYS5wdXNoKHUpfX0pLGEubGVuZ3RoKXJldHVybiB0aGlzLl9lbmdpbmUubWFya0VsZW1lbnRBc1JlbW92ZWQodGhpcy5pZCx0LCEwLGUscyksaSYmcGgoYSkub25Eb25lKCgpPT50aGlzLl9lbmdpbmUucHJvY2Vzc0xlYXZlTm9kZSh0KSksITB9cmV0dXJuITF9cHJlcGFyZUxlYXZlQW5pbWF0aW9uTGlzdGVuZXJzKHQpe2xldCBlPXRoaXMuX2VsZW1lbnRMaXN0ZW5lcnMuZ2V0KHQpLGk9dGhpcy5fZW5naW5lLnN0YXRlc0J5RWxlbWVudC5nZXQodCk7aWYoZSYmaSl7bGV0IHI9bmV3IFNldDtlLmZvckVhY2gobz0+e2xldCBzPW8ubmFtZTtpZihyLmhhcyhzKSlyZXR1cm47ci5hZGQocyk7bGV0IGw9dGhpcy5fdHJpZ2dlcnMuZ2V0KHMpLmZhbGxiYWNrVHJhbnNpdGlvbixjPWkuZ2V0KHMpfHxFNSx1PW5ldyBnTShwTSksZD1uZXcgX00odGhpcy5pZCxzLHQpO3RoaXMuX2VuZ2luZS50b3RhbFF1ZXVlZFBsYXllcnMrKyx0aGlzLl9xdWV1ZS5wdXNoKHtlbGVtZW50OnQsdHJpZ2dlck5hbWU6cyx0cmFuc2l0aW9uOmwsZnJvbVN0YXRlOmMsdG9TdGF0ZTp1LHBsYXllcjpkLGlzRmFsbGJhY2tUcmFuc2l0aW9uOiEwfSl9KX19cmVtb3ZlTm9kZSh0LGUpe2xldCBpPXRoaXMuX2VuZ2luZTtpZih0LmNoaWxkRWxlbWVudENvdW50JiZ0aGlzLl9zaWduYWxSZW1vdmFsRm9ySW5uZXJUcmlnZ2Vycyh0LGUpLHRoaXMudHJpZ2dlckxlYXZlQW5pbWF0aW9uKHQsZSwhMCkpcmV0dXJuO2xldCByPSExO2lmKGkudG90YWxBbmltYXRpb25zKXtsZXQgbz1pLnBsYXllcnMubGVuZ3RoP2kucGxheWVyc0J5UXVlcmllZEVsZW1lbnQuZ2V0KHQpOltdO2lmKG8mJm8ubGVuZ3RoKXI9ITA7ZWxzZXtsZXQgcz10O2Zvcig7cz1zLnBhcmVudE5vZGU7KWlmKGkuc3RhdGVzQnlFbGVtZW50LmdldChzKSl7cj0hMDticmVha319fWlmKHRoaXMucHJlcGFyZUxlYXZlQW5pbWF0aW9uTGlzdGVuZXJzKHQpLHIpaS5tYXJrRWxlbWVudEFzUmVtb3ZlZCh0aGlzLmlkLHQsITEsZSk7ZWxzZXtsZXQgbz10W0tsXTsoIW98fG89PT1YWikmJihpLmFmdGVyRmx1c2goKCk9PnRoaXMuY2xlYXJFbGVtZW50Q2FjaGUodCkpLGkuZGVzdHJveUlubmVyQW5pbWF0aW9ucyh0KSxpLl9vblJlbW92YWxDb21wbGV0ZSh0LGUpKX19aW5zZXJ0Tm9kZSh0LGUpe1psKHQsdGhpcy5faG9zdENsYXNzTmFtZSl9ZHJhaW5RdWV1ZWRUcmFuc2l0aW9ucyh0KXtsZXQgZT1bXTtyZXR1cm4gdGhpcy5fcXVldWUuZm9yRWFjaChpPT57bGV0IHI9aS5wbGF5ZXI7aWYoci5kZXN0cm95ZWQpcmV0dXJuO2xldCBvPWkuZWxlbWVudCxzPXRoaXMuX2VsZW1lbnRMaXN0ZW5lcnMuZ2V0KG8pO3MmJnMuZm9yRWFjaChhPT57aWYoYS5uYW1lPT1pLnRyaWdnZXJOYW1lKXtsZXQgbD1HNShvLGkudHJpZ2dlck5hbWUsaS5mcm9tU3RhdGUudmFsdWUsaS50b1N0YXRlLnZhbHVlKTtsLl9kYXRhPXQsajUoaS5wbGF5ZXIsYS5waGFzZSxsLGEuY2FsbGJhY2spfX0pLHIubWFya2VkRm9yRGVzdHJveT90aGlzLl9lbmdpbmUuYWZ0ZXJGbHVzaCgoKT0+e3IuZGVzdHJveSgpfSk6ZS5wdXNoKGkpfSksdGhpcy5fcXVldWU9W10sZS5zb3J0KChpLHIpPT57bGV0IG89aS50cmFuc2l0aW9uLmFzdC5kZXBDb3VudCxzPXIudHJhbnNpdGlvbi5hc3QuZGVwQ291bnQ7cmV0dXJuIDA9PW98fDA9PXM/by1zOnRoaXMuX2VuZ2luZS5kcml2ZXIuY29udGFpbnNFbGVtZW50KGkuZWxlbWVudCxyLmVsZW1lbnQpPzE6LTF9KX1kZXN0cm95KHQpe3RoaXMucGxheWVycy5mb3JFYWNoKGU9PmUuZGVzdHJveSgpKSx0aGlzLl9zaWduYWxSZW1vdmFsRm9ySW5uZXJUcmlnZ2Vycyh0aGlzLmhvc3RFbGVtZW50LHQpfWVsZW1lbnRDb250YWluc0RhdGEodCl7bGV0IGU9ITE7cmV0dXJuIHRoaXMuX2VsZW1lbnRMaXN0ZW5lcnMuaGFzKHQpJiYoZT0hMCksZT0hIXRoaXMuX3F1ZXVlLmZpbmQoaT0+aS5lbGVtZW50PT09dCl8fGUsZX19KHQsZSx0aGlzKTtyZXR1cm4gdGhpcy5ib2R5Tm9kZSYmdGhpcy5kcml2ZXIuY29udGFpbnNFbGVtZW50KHRoaXMuYm9keU5vZGUsZSk/dGhpcy5fYmFsYW5jZU5hbWVzcGFjZUxpc3QoaSxlKToodGhpcy5uZXdIb3N0RWxlbWVudHMuc2V0KGUsaSksdGhpcy5jb2xsZWN0RW50ZXJFbGVtZW50KGUpKSx0aGlzLl9uYW1lc3BhY2VMb29rdXBbdF09aX1fYmFsYW5jZU5hbWVzcGFjZUxpc3QodCxlKXtsZXQgaT10aGlzLl9uYW1lc3BhY2VMaXN0LHI9dGhpcy5uYW1lc3BhY2VzQnlIb3N0RWxlbWVudDtpZihpLmxlbmd0aC0xPj0wKXtsZXQgcz0hMSxhPXRoaXMuZHJpdmVyLmdldFBhcmVudEVsZW1lbnQoZSk7Zm9yKDthOyl7bGV0IGw9ci5nZXQoYSk7aWYobCl7bGV0IGM9aS5pbmRleE9mKGwpO2kuc3BsaWNlKGMrMSwwLHQpLHM9ITA7YnJlYWt9YT10aGlzLmRyaXZlci5nZXRQYXJlbnRFbGVtZW50KGEpfXN8fGkudW5zaGlmdCh0KX1lbHNlIGkucHVzaCh0KTtyZXR1cm4gci5zZXQoZSx0KSx0fXJlZ2lzdGVyKHQsZSl7bGV0IGk9dGhpcy5fbmFtZXNwYWNlTG9va3VwW3RdO3JldHVybiBpfHwoaT10aGlzLmNyZWF0ZU5hbWVzcGFjZSh0LGUpKSxpfXJlZ2lzdGVyVHJpZ2dlcih0LGUsaSl7bGV0IHI9dGhpcy5fbmFtZXNwYWNlTG9va3VwW3RdO3ImJnIucmVnaXN0ZXIoZSxpKSYmdGhpcy50b3RhbEFuaW1hdGlvbnMrK31kZXN0cm95KHQsZSl7aWYoIXQpcmV0dXJuO2xldCBpPXRoaXMuX2ZldGNoTmFtZXNwYWNlKHQpO3RoaXMuYWZ0ZXJGbHVzaCgoKT0+e3RoaXMubmFtZXNwYWNlc0J5SG9zdEVsZW1lbnQuZGVsZXRlKGkuaG9zdEVsZW1lbnQpLGRlbGV0ZSB0aGlzLl9uYW1lc3BhY2VMb29rdXBbdF07bGV0IHI9dGhpcy5fbmFtZXNwYWNlTGlzdC5pbmRleE9mKGkpO3I+PTAmJnRoaXMuX25hbWVzcGFjZUxpc3Quc3BsaWNlKHIsMSl9KSx0aGlzLmFmdGVyRmx1c2hBbmltYXRpb25zRG9uZSgoKT0+aS5kZXN0cm95KGUpKX1fZmV0Y2hOYW1lc3BhY2UodCl7cmV0dXJuIHRoaXMuX25hbWVzcGFjZUxvb2t1cFt0XX1mZXRjaE5hbWVzcGFjZXNCeUVsZW1lbnQodCl7bGV0IGU9bmV3IFNldCxpPXRoaXMuc3RhdGVzQnlFbGVtZW50LmdldCh0KTtpZihpKWZvcihsZXQgciBvZiBpLnZhbHVlcygpKWlmKHIubmFtZXNwYWNlSWQpe2xldCBvPXRoaXMuX2ZldGNoTmFtZXNwYWNlKHIubmFtZXNwYWNlSWQpO28mJmUuYWRkKG8pfXJldHVybiBlfXRyaWdnZXIodCxlLGkscil7aWYobEEoZSkpe2xldCBvPXRoaXMuX2ZldGNoTmFtZXNwYWNlKHQpO2lmKG8pcmV0dXJuIG8udHJpZ2dlcihlLGksciksITB9cmV0dXJuITF9aW5zZXJ0Tm9kZSh0LGUsaSxyKXtpZighbEEoZSkpcmV0dXJuO2xldCBvPWVbS2xdO2lmKG8mJm8uc2V0Rm9yUmVtb3ZhbCl7by5zZXRGb3JSZW1vdmFsPSExLG8uc2V0Rm9yTW92ZT0hMDtsZXQgcz10aGlzLmNvbGxlY3RlZExlYXZlRWxlbWVudHMuaW5kZXhPZihlKTtzPj0wJiZ0aGlzLmNvbGxlY3RlZExlYXZlRWxlbWVudHMuc3BsaWNlKHMsMSl9aWYodCl7bGV0IHM9dGhpcy5fZmV0Y2hOYW1lc3BhY2UodCk7cyYmcy5pbnNlcnROb2RlKGUsaSl9ciYmdGhpcy5jb2xsZWN0RW50ZXJFbGVtZW50KGUpfWNvbGxlY3RFbnRlckVsZW1lbnQodCl7dGhpcy5jb2xsZWN0ZWRFbnRlckVsZW1lbnRzLnB1c2godCl9bWFya0VsZW1lbnRBc0Rpc2FibGVkKHQsZSl7ZT90aGlzLmRpc2FibGVkTm9kZXMuaGFzKHQpfHwodGhpcy5kaXNhYmxlZE5vZGVzLmFkZCh0KSxabCh0LFM1KSk6dGhpcy5kaXNhYmxlZE5vZGVzLmhhcyh0KSYmKHRoaXMuZGlzYWJsZWROb2Rlcy5kZWxldGUodCksel8odCxTNSkpfXJlbW92ZU5vZGUodCxlLGkscil7aWYobEEoZSkpe2xldCBvPXQ/dGhpcy5fZmV0Y2hOYW1lc3BhY2UodCk6bnVsbDtpZihvP28ucmVtb3ZlTm9kZShlLHIpOnRoaXMubWFya0VsZW1lbnRBc1JlbW92ZWQodCxlLCExLHIpLGkpe2xldCBzPXRoaXMubmFtZXNwYWNlc0J5SG9zdEVsZW1lbnQuZ2V0KGUpO3MmJnMuaWQhPT10JiZzLnJlbW92ZU5vZGUoZSxyKX19ZWxzZSB0aGlzLl9vblJlbW92YWxDb21wbGV0ZShlLHIpfW1hcmtFbGVtZW50QXNSZW1vdmVkKHQsZSxpLHIsbyl7dGhpcy5jb2xsZWN0ZWRMZWF2ZUVsZW1lbnRzLnB1c2goZSksZVtLbF09e25hbWVzcGFjZUlkOnQsc2V0Rm9yUmVtb3ZhbDpyLGhhc0FuaW1hdGlvbjppLHJlbW92ZWRCZWZvcmVRdWVyaWVkOiExLHByZXZpb3VzVHJpZ2dlcnNWYWx1ZXM6b319bGlzdGVuKHQsZSxpLHIsbyl7cmV0dXJuIGxBKGUpP3RoaXMuX2ZldGNoTmFtZXNwYWNlKHQpLmxpc3RlbihlLGkscixvKTooKT0+e319X2J1aWxkSW5zdHJ1Y3Rpb24odCxlLGkscixvKXtyZXR1cm4gdC50cmFuc2l0aW9uLmJ1aWxkKHRoaXMuZHJpdmVyLHQuZWxlbWVudCx0LmZyb21TdGF0ZS52YWx1ZSx0LnRvU3RhdGUudmFsdWUsaSxyLHQuZnJvbVN0YXRlLm9wdGlvbnMsdC50b1N0YXRlLm9wdGlvbnMsZSxvKX1kZXN0cm95SW5uZXJBbmltYXRpb25zKHQpe2xldCBlPXRoaXMuZHJpdmVyLnF1ZXJ5KHQsY0EsITApO2UuZm9yRWFjaChpPT50aGlzLmRlc3Ryb3lBY3RpdmVBbmltYXRpb25zRm9yRWxlbWVudChpKSksMCE9dGhpcy5wbGF5ZXJzQnlRdWVyaWVkRWxlbWVudC5zaXplJiYoZT10aGlzLmRyaXZlci5xdWVyeSh0LEk1LCEwKSxlLmZvckVhY2goaT0+dGhpcy5maW5pc2hBY3RpdmVRdWVyaWVkQW5pbWF0aW9uT25FbGVtZW50KGkpKSl9ZGVzdHJveUFjdGl2ZUFuaW1hdGlvbnNGb3JFbGVtZW50KHQpe2xldCBlPXRoaXMucGxheWVyc0J5RWxlbWVudC5nZXQodCk7ZSYmZS5mb3JFYWNoKGk9PntpLnF1ZXVlZD9pLm1hcmtlZEZvckRlc3Ryb3k9ITA6aS5kZXN0cm95KCl9KX1maW5pc2hBY3RpdmVRdWVyaWVkQW5pbWF0aW9uT25FbGVtZW50KHQpe2xldCBlPXRoaXMucGxheWVyc0J5UXVlcmllZEVsZW1lbnQuZ2V0KHQpO2UmJmUuZm9yRWFjaChpPT5pLmZpbmlzaCgpKX13aGVuUmVuZGVyaW5nRG9uZSgpe3JldHVybiBuZXcgUHJvbWlzZSh0PT57aWYodGhpcy5wbGF5ZXJzLmxlbmd0aClyZXR1cm4gcGgodGhpcy5wbGF5ZXJzKS5vbkRvbmUoKCk9PnQoKSk7dCgpfSl9cHJvY2Vzc0xlYXZlTm9kZSh0KXtsZXQgZT10W0tsXTtpZihlJiZlLnNldEZvclJlbW92YWwpe2lmKHRbS2xdPVhaLGUubmFtZXNwYWNlSWQpe3RoaXMuZGVzdHJveUlubmVyQW5pbWF0aW9ucyh0KTtsZXQgaT10aGlzLl9mZXRjaE5hbWVzcGFjZShlLm5hbWVzcGFjZUlkKTtpJiZpLmNsZWFyRWxlbWVudENhY2hlKHQpfXRoaXMuX29uUmVtb3ZhbENvbXBsZXRlKHQsZS5zZXRGb3JSZW1vdmFsKX10LmNsYXNzTGlzdD8uY29udGFpbnMoUzUpJiZ0aGlzLm1hcmtFbGVtZW50QXNEaXNhYmxlZCh0LCExKSx0aGlzLmRyaXZlci5xdWVyeSh0LCIubmctYW5pbWF0ZS1kaXNhYmxlZCIsITApLmZvckVhY2goaT0+e3RoaXMubWFya0VsZW1lbnRBc0Rpc2FibGVkKGksITEpfSl9Zmx1c2godD0tMSl7bGV0IGU9W107aWYodGhpcy5uZXdIb3N0RWxlbWVudHMuc2l6ZSYmKHRoaXMubmV3SG9zdEVsZW1lbnRzLmZvckVhY2goKGkscik9PnRoaXMuX2JhbGFuY2VOYW1lc3BhY2VMaXN0KGkscikpLHRoaXMubmV3SG9zdEVsZW1lbnRzLmNsZWFyKCkpLHRoaXMudG90YWxBbmltYXRpb25zJiZ0aGlzLmNvbGxlY3RlZEVudGVyRWxlbWVudHMubGVuZ3RoKWZvcihsZXQgaT0wO2k8dGhpcy5jb2xsZWN0ZWRFbnRlckVsZW1lbnRzLmxlbmd0aDtpKyspWmwodGhpcy5jb2xsZWN0ZWRFbnRlckVsZW1lbnRzW2ldLCJuZy1zdGFyLWluc2VydGVkIik7aWYodGhpcy5fbmFtZXNwYWNlTGlzdC5sZW5ndGgmJih0aGlzLnRvdGFsUXVldWVkUGxheWVyc3x8dGhpcy5jb2xsZWN0ZWRMZWF2ZUVsZW1lbnRzLmxlbmd0aCkpe2xldCBpPVtdO3RyeXtlPXRoaXMuX2ZsdXNoQW5pbWF0aW9ucyhpLHQpfWZpbmFsbHl7Zm9yKGxldCByPTA7cjxpLmxlbmd0aDtyKyspaVtyXSgpfX1lbHNlIGZvcihsZXQgaT0wO2k8dGhpcy5jb2xsZWN0ZWRMZWF2ZUVsZW1lbnRzLmxlbmd0aDtpKyspdGhpcy5wcm9jZXNzTGVhdmVOb2RlKHRoaXMuY29sbGVjdGVkTGVhdmVFbGVtZW50c1tpXSk7aWYodGhpcy50b3RhbFF1ZXVlZFBsYXllcnM9MCx0aGlzLmNvbGxlY3RlZEVudGVyRWxlbWVudHMubGVuZ3RoPTAsdGhpcy5jb2xsZWN0ZWRMZWF2ZUVsZW1lbnRzLmxlbmd0aD0wLHRoaXMuX2ZsdXNoRm5zLmZvckVhY2goaT0+aSgpKSx0aGlzLl9mbHVzaEZucz1bXSx0aGlzLl93aGVuUXVpZXRGbnMubGVuZ3RoKXtsZXQgaT10aGlzLl93aGVuUXVpZXRGbnM7dGhpcy5fd2hlblF1aWV0Rm5zPVtdLGUubGVuZ3RoP3BoKGUpLm9uRG9uZSgoKT0+e2kuZm9yRWFjaChyPT5yKCkpfSk6aS5mb3JFYWNoKHI9PnIoKSl9fXJlcG9ydEVycm9yKHQpe3Rocm93IG5ldyBBdCgzNDAyLCExKX1fZmx1c2hBbmltYXRpb25zKHQsZSl7bGV0IGk9bmV3IGZNLHI9W10sbz1uZXcgTWFwLHM9W10sYT1uZXcgTWFwLGw9bmV3IE1hcCxjPW5ldyBNYXAsdT1uZXcgU2V0O3RoaXMuZGlzYWJsZWROb2Rlcy5mb3JFYWNoKFk9Pnt1LmFkZChZKTtsZXQgYWU9dGhpcy5kcml2ZXIucXVlcnkoWSwiLm5nLWFuaW1hdGUtcXVldWVkIiwhMCk7Zm9yKGxldCBsZT0wO2xlPGFlLmxlbmd0aDtsZSsrKXUuYWRkKGFlW2xlXSl9KTtsZXQgZD10aGlzLmJvZHlOb2RlLHA9QXJyYXkuZnJvbSh0aGlzLnN0YXRlc0J5RWxlbWVudC5rZXlzKCkpLGg9SVoocCx0aGlzLmNvbGxlY3RlZEVudGVyRWxlbWVudHMpLGY9bmV3IE1hcCxtPTA7aC5mb3JFYWNoKChZLGFlKT0+e2xldCBsZT1CWittKys7Zi5zZXQoYWUsbGUpLFkuZm9yRWFjaChJZT0+WmwoSWUsbGUpKX0pO2xldCB4PVtdLGc9bmV3IFNldCxiPW5ldyBTZXQ7Zm9yKGxldCBZPTA7WTx0aGlzLmNvbGxlY3RlZExlYXZlRWxlbWVudHMubGVuZ3RoO1krKyl7bGV0IGFlPXRoaXMuY29sbGVjdGVkTGVhdmVFbGVtZW50c1tZXSxsZT1hZVtLbF07bGUmJmxlLnNldEZvclJlbW92YWwmJih4LnB1c2goYWUpLGcuYWRkKGFlKSxsZS5oYXNBbmltYXRpb24/dGhpcy5kcml2ZXIucXVlcnkoYWUsIi5uZy1zdGFyLWluc2VydGVkIiwhMCkuZm9yRWFjaChJZT0+Zy5hZGQoSWUpKTpiLmFkZChhZSkpfWxldCBEPW5ldyBNYXAsVD1JWihwLEFycmF5LmZyb20oZykpO1QuZm9yRWFjaCgoWSxhZSk9PntsZXQgbGU9QTUrbSsrO0Quc2V0KGFlLGxlKSxZLmZvckVhY2goSWU9PlpsKEllLGxlKSl9KSx0LnB1c2goKCk9PntoLmZvckVhY2goKFksYWUpPT57bGV0IGxlPWYuZ2V0KGFlKTtZLmZvckVhY2goSWU9PnpfKEllLGxlKSl9KSxULmZvckVhY2goKFksYWUpPT57bGV0IGxlPUQuZ2V0KGFlKTtZLmZvckVhY2goSWU9PnpfKEllLGxlKSl9KSx4LmZvckVhY2goWT0+e3RoaXMucHJvY2Vzc0xlYXZlTm9kZShZKX0pfSk7bGV0IGs9W10sWj1bXTtmb3IobGV0IFk9dGhpcy5fbmFtZXNwYWNlTGlzdC5sZW5ndGgtMTtZPj0wO1ktLSl0aGlzLl9uYW1lc3BhY2VMaXN0W1ldLmRyYWluUXVldWVkVHJhbnNpdGlvbnMoZSkuZm9yRWFjaChsZT0+e2xldCBJZT1sZS5wbGF5ZXIsdmU9bGUuZWxlbWVudDtpZihrLnB1c2goSWUpLHRoaXMuY29sbGVjdGVkRW50ZXJFbGVtZW50cy5sZW5ndGgpe2xldCBwdD12ZVtLbF07aWYocHQmJnB0LnNldEZvck1vdmUpe2lmKHB0LnByZXZpb3VzVHJpZ2dlcnNWYWx1ZXMmJnB0LnByZXZpb3VzVHJpZ2dlcnNWYWx1ZXMuaGFzKGxlLnRyaWdnZXJOYW1lKSl7bGV0IHd0PXB0LnByZXZpb3VzVHJpZ2dlcnNWYWx1ZXMuZ2V0KGxlLnRyaWdnZXJOYW1lKSxUZT10aGlzLnN0YXRlc0J5RWxlbWVudC5nZXQobGUuZWxlbWVudCk7aWYoVGUmJlRlLmhhcyhsZS50cmlnZ2VyTmFtZSkpe2xldCB4dD1UZS5nZXQobGUudHJpZ2dlck5hbWUpO3h0LnZhbHVlPXd0LFRlLnNldChsZS50cmlnZ2VyTmFtZSx4dCl9fXJldHVybiB2b2lkIEllLmRlc3Ryb3koKX19bGV0IERlPSFkfHwhdGhpcy5kcml2ZXIuY29udGFpbnNFbGVtZW50KGQsdmUpLG50PUQuZ2V0KHZlKSxndD1mLmdldCh2ZSksVWU9dGhpcy5fYnVpbGRJbnN0cnVjdGlvbihsZSxpLGd0LG50LERlKTtpZihVZS5lcnJvcnMmJlVlLmVycm9ycy5sZW5ndGgpcmV0dXJuIHZvaWQgWi5wdXNoKFVlKTtpZihEZSlyZXR1cm4gSWUub25TdGFydCgoKT0+T20odmUsVWUuZnJvbVN0eWxlcykpLEllLm9uRGVzdHJveSgoKT0+R3UodmUsVWUudG9TdHlsZXMpKSx2b2lkIHIucHVzaChJZSk7aWYobGUuaXNGYWxsYmFja1RyYW5zaXRpb24pcmV0dXJuIEllLm9uU3RhcnQoKCk9Pk9tKHZlLFVlLmZyb21TdHlsZXMpKSxJZS5vbkRlc3Ryb3koKCk9Pkd1KHZlLFVlLnRvU3R5bGVzKSksdm9pZCByLnB1c2goSWUpO2xldCBBZT1bXTtVZS50aW1lbGluZXMuZm9yRWFjaChwdD0+e3B0LnN0cmV0Y2hTdGFydGluZ0tleWZyYW1lPSEwLHRoaXMuZGlzYWJsZWROb2Rlcy5oYXMocHQuZWxlbWVudCl8fEFlLnB1c2gocHQpfSksVWUudGltZWxpbmVzPUFlLGkuYXBwZW5kKHZlLFVlLnRpbWVsaW5lcykscy5wdXNoKHtpbnN0cnVjdGlvbjpVZSxwbGF5ZXI6SWUsZWxlbWVudDp2ZX0pLFVlLnF1ZXJpZWRFbGVtZW50cy5mb3JFYWNoKHB0PT5wbChhLHB0LFtdKS5wdXNoKEllKSksVWUucHJlU3R5bGVQcm9wcy5mb3JFYWNoKChwdCx3dCk9PntpZihwdC5zaXplKXtsZXQgVGU9bC5nZXQod3QpO1RlfHxsLnNldCh3dCxUZT1uZXcgU2V0KSxwdC5mb3JFYWNoKCh4dCxtdCk9PlRlLmFkZChtdCkpfX0pLFVlLnBvc3RTdHlsZVByb3BzLmZvckVhY2goKHB0LHd0KT0+e2xldCBUZT1jLmdldCh3dCk7VGV8fGMuc2V0KHd0LFRlPW5ldyBTZXQpLHB0LmZvckVhY2goKHh0LG10KT0+VGUuYWRkKG10KSl9KX0pO2lmKFoubGVuZ3RoKXtsZXQgWT1bXTtaLmZvckVhY2goYWU9PntZLnB1c2gobmV3IEF0KDM1MDUsITEpKX0pLGsuZm9yRWFjaChhZT0+YWUuZGVzdHJveSgpKSx0aGlzLnJlcG9ydEVycm9yKFkpfWxldCB6PW5ldyBNYXAsZmU9bmV3IE1hcDtzLmZvckVhY2goWT0+e2xldCBhZT1ZLmVsZW1lbnQ7aS5oYXMoYWUpJiYoZmUuc2V0KGFlLGFlKSx0aGlzLl9iZWZvcmVBbmltYXRpb25CdWlsZChZLnBsYXllci5uYW1lc3BhY2VJZCxZLmluc3RydWN0aW9uLHopKX0pLHIuZm9yRWFjaChZPT57bGV0IGFlPVkuZWxlbWVudDt0aGlzLl9nZXRQcmV2aW91c1BsYXllcnMoYWUsITEsWS5uYW1lc3BhY2VJZCxZLnRyaWdnZXJOYW1lLG51bGwpLmZvckVhY2goSWU9PntwbCh6LGFlLFtdKS5wdXNoKEllKSxJZS5kZXN0cm95KCl9KX0pO2xldCB1ZT14LmZpbHRlcihZPT5QWihZLGwsYykpLGhlPW5ldyBNYXA7QVooaGUsdGhpcy5kcml2ZXIsYixjLGp1KS5mb3JFYWNoKFk9PntQWihZLGwsYykmJnVlLnB1c2goWSl9KTtsZXQgRj1uZXcgTWFwO2guZm9yRWFjaCgoWSxhZSk9PntBWihGLHRoaXMuZHJpdmVyLG5ldyBTZXQoWSksbCwiISIpfSksdWUuZm9yRWFjaChZPT57bGV0IGFlPWhlLmdldChZKSxsZT1GLmdldChZKTtoZS5zZXQoWSxuZXcgTWFwKFsuLi5BcnJheS5mcm9tKGFlPy5lbnRyaWVzKCk/P1tdKSwuLi5BcnJheS5mcm9tKGxlPy5lbnRyaWVzKCk/P1tdKV0pKX0pO2xldCBxPVtdLEs9W10sZGU9e307cy5mb3JFYWNoKFk9PntsZXR7ZWxlbWVudDphZSxwbGF5ZXI6bGUsaW5zdHJ1Y3Rpb246SWV9PVk7aWYoaS5oYXMoYWUpKXtpZih1LmhhcyhhZSkpcmV0dXJuIGxlLm9uRGVzdHJveSgoKT0+R3UoYWUsSWUudG9TdHlsZXMpKSxsZS5kaXNhYmxlZD0hMCxsZS5vdmVycmlkZVRvdGFsVGltZShJZS50b3RhbFRpbWUpLHZvaWQgci5wdXNoKGxlKTtsZXQgdmU9ZGU7aWYoZmUuc2l6ZT4xKXtsZXQgbnQ9YWUsZ3Q9W107Zm9yKDtudD1udC5wYXJlbnROb2RlOyl7bGV0IFVlPWZlLmdldChudCk7aWYoVWUpe3ZlPVVlO2JyZWFrfWd0LnB1c2gobnQpfWd0LmZvckVhY2goVWU9PmZlLnNldChVZSx2ZSkpfWxldCBEZT10aGlzLl9idWlsZEFuaW1hdGlvbihsZS5uYW1lc3BhY2VJZCxJZSx6LG8sRixoZSk7aWYobGUuc2V0UmVhbFBsYXllcihEZSksdmU9PT1kZSlxLnB1c2gobGUpO2Vsc2V7bGV0IG50PXRoaXMucGxheWVyc0J5RWxlbWVudC5nZXQodmUpO250JiZudC5sZW5ndGgmJihsZS5wYXJlbnRQbGF5ZXI9cGgobnQpKSxyLnB1c2gobGUpfX1lbHNlIE9tKGFlLEllLmZyb21TdHlsZXMpLGxlLm9uRGVzdHJveSgoKT0+R3UoYWUsSWUudG9TdHlsZXMpKSxLLnB1c2gobGUpLHUuaGFzKGFlKSYmci5wdXNoKGxlKX0pLEsuZm9yRWFjaChZPT57bGV0IGFlPW8uZ2V0KFkuZWxlbWVudCk7aWYoYWUmJmFlLmxlbmd0aCl7bGV0IGxlPXBoKGFlKTtZLnNldFJlYWxQbGF5ZXIobGUpfX0pLHIuZm9yRWFjaChZPT57WS5wYXJlbnRQbGF5ZXI/WS5zeW5jUGxheWVyRXZlbnRzKFkucGFyZW50UGxheWVyKTpZLmRlc3Ryb3koKX0pO2ZvcihsZXQgWT0wO1k8eC5sZW5ndGg7WSsrKXtsZXQgYWU9eFtZXSxsZT1hZVtLbF07aWYoel8oYWUsQTUpLGxlJiZsZS5oYXNBbmltYXRpb24pY29udGludWU7bGV0IEllPVtdO2lmKGEuc2l6ZSl7bGV0IERlPWEuZ2V0KGFlKTtEZSYmRGUubGVuZ3RoJiZJZS5wdXNoKC4uLkRlKTtsZXQgbnQ9dGhpcy5kcml2ZXIucXVlcnkoYWUsSTUsITApO2ZvcihsZXQgZ3Q9MDtndDxudC5sZW5ndGg7Z3QrKyl7bGV0IFVlPWEuZ2V0KG50W2d0XSk7VWUmJlVlLmxlbmd0aCYmSWUucHVzaCguLi5VZSl9fWxldCB2ZT1JZS5maWx0ZXIoRGU9PiFEZS5kZXN0cm95ZWQpO3ZlLmxlbmd0aD9vSWUodGhpcyxhZSx2ZSk6dGhpcy5wcm9jZXNzTGVhdmVOb2RlKGFlKX1yZXR1cm4geC5sZW5ndGg9MCxxLmZvckVhY2goWT0+e3RoaXMucGxheWVycy5wdXNoKFkpLFkub25Eb25lKCgpPT57WS5kZXN0cm95KCk7bGV0IGFlPXRoaXMucGxheWVycy5pbmRleE9mKFkpO3RoaXMucGxheWVycy5zcGxpY2UoYWUsMSl9KSxZLnBsYXkoKX0pLHF9ZWxlbWVudENvbnRhaW5zRGF0YSh0LGUpe2xldCBpPSExLHI9ZVtLbF07cmV0dXJuIHImJnIuc2V0Rm9yUmVtb3ZhbCYmKGk9ITApLHRoaXMucGxheWVyc0J5RWxlbWVudC5oYXMoZSkmJihpPSEwKSx0aGlzLnBsYXllcnNCeVF1ZXJpZWRFbGVtZW50LmhhcyhlKSYmKGk9ITApLHRoaXMuc3RhdGVzQnlFbGVtZW50LmhhcyhlKSYmKGk9ITApLHRoaXMuX2ZldGNoTmFtZXNwYWNlKHQpLmVsZW1lbnRDb250YWluc0RhdGEoZSl8fGl9YWZ0ZXJGbHVzaCh0KXt0aGlzLl9mbHVzaEZucy5wdXNoKHQpfWFmdGVyRmx1c2hBbmltYXRpb25zRG9uZSh0KXt0aGlzLl93aGVuUXVpZXRGbnMucHVzaCh0KX1fZ2V0UHJldmlvdXNQbGF5ZXJzKHQsZSxpLHIsbyl7bGV0IHM9W107aWYoZSl7bGV0IGE9dGhpcy5wbGF5ZXJzQnlRdWVyaWVkRWxlbWVudC5nZXQodCk7YSYmKHM9YSl9ZWxzZXtsZXQgYT10aGlzLnBsYXllcnNCeUVsZW1lbnQuZ2V0KHQpO2lmKGEpe2xldCBsPSFvfHxvPT1wTTthLmZvckVhY2goYz0+e2MucXVldWVkfHwhbCYmYy50cmlnZ2VyTmFtZSE9cnx8cy5wdXNoKGMpfSl9fXJldHVybihpfHxyKSYmKHM9cy5maWx0ZXIoYT0+IShpJiZpIT1hLm5hbWVzcGFjZUlkfHxyJiZyIT1hLnRyaWdnZXJOYW1lKSkpLHN9X2JlZm9yZUFuaW1hdGlvbkJ1aWxkKHQsZSxpKXtsZXQgbz1lLmVsZW1lbnQscz1lLmlzUmVtb3ZhbFRyYW5zaXRpb24/dm9pZCAwOnQsYT1lLmlzUmVtb3ZhbFRyYW5zaXRpb24/dm9pZCAwOmUudHJpZ2dlck5hbWU7Zm9yKGxldCBsIG9mIGUudGltZWxpbmVzKXtsZXQgYz1sLmVsZW1lbnQsdT1jIT09byxkPXBsKGksYyxbXSk7dGhpcy5fZ2V0UHJldmlvdXNQbGF5ZXJzKGMsdSxzLGEsZS50b1N0YXRlKS5mb3JFYWNoKGg9PntsZXQgZj1oLmdldFJlYWxQbGF5ZXIoKTtmLmJlZm9yZURlc3Ryb3kmJmYuYmVmb3JlRGVzdHJveSgpLGguZGVzdHJveSgpLGQucHVzaChoKX0pfU9tKG8sZS5mcm9tU3R5bGVzKX1fYnVpbGRBbmltYXRpb24odCxlLGkscixvLHMpe2xldCBhPWUudHJpZ2dlck5hbWUsbD1lLmVsZW1lbnQsYz1bXSx1PW5ldyBTZXQsZD1uZXcgU2V0LHA9ZS50aW1lbGluZXMubWFwKGY9PntsZXQgbT1mLmVsZW1lbnQ7dS5hZGQobSk7bGV0IHg9bVtLbF07aWYoeCYmeC5yZW1vdmVkQmVmb3JlUXVlcmllZClyZXR1cm4gbmV3IGRoKGYuZHVyYXRpb24sZi5kZWxheSk7bGV0IGc9bSE9PWwsYj1mdW5jdGlvbihuKXtsZXQgdD1bXTtyZXR1cm4gUVoobix0KSx0fSgoaS5nZXQobSl8fGVJZSkubWFwKHo9PnouZ2V0UmVhbFBsYXllcigpKSkuZmlsdGVyKHo9PiEhei5lbGVtZW50JiZ6LmVsZW1lbnQ9PT1tKSxEPW8uZ2V0KG0pLFQ9cy5nZXQobSksaz1SWigwLHRoaXMuX25vcm1hbGl6ZXIsMCxmLmtleWZyYW1lcyxELFQpLFo9dGhpcy5fYnVpbGRQbGF5ZXIoZixrLGIpO2lmKGYuc3ViVGltZWxpbmUmJnImJmQuYWRkKG0pLGcpe2xldCB6PW5ldyBfTSh0LGEsbSk7ei5zZXRSZWFsUGxheWVyKFopLGMucHVzaCh6KX1yZXR1cm4gWn0pO2MuZm9yRWFjaChmPT57cGwodGhpcy5wbGF5ZXJzQnlRdWVyaWVkRWxlbWVudCxmLmVsZW1lbnQsW10pLnB1c2goZiksZi5vbkRvbmUoKCk9PmZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1uLmdldCh0KTtpZihpKXtpZihpLmxlbmd0aCl7bGV0IHI9aS5pbmRleE9mKGUpO2kuc3BsaWNlKHIsMSl9MD09aS5sZW5ndGgmJm4uZGVsZXRlKHQpfXJldHVybiBpfSh0aGlzLnBsYXllcnNCeVF1ZXJpZWRFbGVtZW50LGYuZWxlbWVudCxmKSl9KSx1LmZvckVhY2goZj0+WmwoZixiWikpO2xldCBoPXBoKHApO3JldHVybiBoLm9uRGVzdHJveSgoKT0+e3UuZm9yRWFjaChmPT56XyhmLGJaKSksR3UobCxlLnRvU3R5bGVzKX0pLGQuZm9yRWFjaChmPT57cGwocixmLFtdKS5wdXNoKGgpfSksaH1fYnVpbGRQbGF5ZXIodCxlLGkpe3JldHVybiBlLmxlbmd0aD4wP3RoaXMuZHJpdmVyLmFuaW1hdGUodC5lbGVtZW50LGUsdC5kdXJhdGlvbix0LmRlbGF5LHQuZWFzaW5nLGkpOm5ldyBkaCh0LmR1cmF0aW9uLHQuZGVsYXkpfX0odCxlLGkpLHRoaXMuX3RpbWVsaW5lRW5naW5lPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7dGhpcy5ib2R5Tm9kZT10LHRoaXMuX2RyaXZlcj1lLHRoaXMuX25vcm1hbGl6ZXI9aSx0aGlzLl9hbmltYXRpb25zPW5ldyBNYXAsdGhpcy5fcGxheWVyc0J5SWQ9bmV3IE1hcCx0aGlzLnBsYXllcnM9W119cmVnaXN0ZXIodCxlKXtsZXQgaT1bXSxvPXFaKHRoaXMuX2RyaXZlcixlLGksW10pO2lmKGkubGVuZ3RoKXRocm93IG5ldyBBdCgzNTAzLCExKTt0aGlzLl9hbmltYXRpb25zLnNldCh0LG8pfV9idWlsZFBsYXllcih0LGUsaSl7bGV0IHI9dC5lbGVtZW50LG89UlooMCx0aGlzLl9ub3JtYWxpemVyLDAsdC5rZXlmcmFtZXMsZSxpKTtyZXR1cm4gdGhpcy5fZHJpdmVyLmFuaW1hdGUocixvLHQuZHVyYXRpb24sdC5kZWxheSx0LmVhc2luZyxbXSwhMCl9Y3JlYXRlKHQsZSxpPXt9KXtsZXQgcyxyPVtdLG89dGhpcy5fYW5pbWF0aW9ucy5nZXQodCksYT1uZXcgTWFwO2lmKG8/KHM9WVoodGhpcy5fZHJpdmVyLGUsbyxCWixBNSxuZXcgTWFwLG5ldyBNYXAsaSxRQWUscikscy5mb3JFYWNoKHU9PntsZXQgZD1wbChhLHUuZWxlbWVudCxuZXcgTWFwKTt1LnBvc3RTdHlsZVByb3BzLmZvckVhY2gocD0+ZC5zZXQocCxudWxsKSl9KSk6KHIucHVzaChuZXcgQXQoMzMwMCwhMSkpLHM9W10pLHIubGVuZ3RoKXRocm93IG5ldyBBdCgzNTA0LCExKTthLmZvckVhY2goKHUsZCk9Pnt1LmZvckVhY2goKHAsaCk9Pnt1LnNldChoLHRoaXMuX2RyaXZlci5jb21wdXRlU3R5bGUoZCxoLGp1KSl9KX0pO2xldCBjPXBoKHMubWFwKHU9PntsZXQgZD1hLmdldCh1LmVsZW1lbnQpO3JldHVybiB0aGlzLl9idWlsZFBsYXllcih1LG5ldyBNYXAsZCl9KSk7cmV0dXJuIHRoaXMuX3BsYXllcnNCeUlkLnNldCh0LGMpLGMub25EZXN0cm95KCgpPT50aGlzLmRlc3Ryb3kodCkpLHRoaXMucGxheWVycy5wdXNoKGMpLGN9ZGVzdHJveSh0KXtsZXQgZT10aGlzLl9nZXRQbGF5ZXIodCk7ZS5kZXN0cm95KCksdGhpcy5fcGxheWVyc0J5SWQuZGVsZXRlKHQpO2xldCBpPXRoaXMucGxheWVycy5pbmRleE9mKGUpO2k+PTAmJnRoaXMucGxheWVycy5zcGxpY2UoaSwxKX1fZ2V0UGxheWVyKHQpe2xldCBlPXRoaXMuX3BsYXllcnNCeUlkLmdldCh0KTtpZighZSl0aHJvdyBuZXcgQXQoMzMwMSwhMSk7cmV0dXJuIGV9bGlzdGVuKHQsZSxpLHIpe2xldCBvPUc1KGUsIiIsIiIsIiIpO3JldHVybiBqNSh0aGlzLl9nZXRQbGF5ZXIodCksaSxvLHIpLCgpPT57fX1jb21tYW5kKHQsZSxpLHIpe2lmKCJyZWdpc3RlciI9PWkpcmV0dXJuIHZvaWQgdGhpcy5yZWdpc3Rlcih0LHJbMF0pO2lmKCJjcmVhdGUiPT1pKXJldHVybiB2b2lkIHRoaXMuY3JlYXRlKHQsZSxyWzBdfHx7fSk7bGV0IG89dGhpcy5fZ2V0UGxheWVyKHQpO3N3aXRjaChpKXtjYXNlInBsYXkiOm8ucGxheSgpO2JyZWFrO2Nhc2UicGF1c2UiOm8ucGF1c2UoKTticmVhaztjYXNlInJlc2V0IjpvLnJlc2V0KCk7YnJlYWs7Y2FzZSJyZXN0YXJ0IjpvLnJlc3RhcnQoKTticmVhaztjYXNlImZpbmlzaCI6by5maW5pc2goKTticmVhaztjYXNlImluaXQiOm8uaW5pdCgpO2JyZWFrO2Nhc2Uic2V0UG9zaXRpb24iOm8uc2V0UG9zaXRpb24ocGFyc2VGbG9hdChyWzBdKSk7YnJlYWs7Y2FzZSJkZXN0cm95Ijp0aGlzLmRlc3Ryb3kodCl9fX0odCxlLGkpLHRoaXMuX3RyYW5zaXRpb25FbmdpbmUub25SZW1vdmFsQ29tcGxldGU9KHIsbyk9PnRoaXMub25SZW1vdmFsQ29tcGxldGUocixvKX1yZWdpc3RlclRyaWdnZXIodCxlLGkscixvKXtsZXQgcz10KyItIityLGE9dGhpcy5fdHJpZ2dlckNhY2hlW3NdO2lmKCFhKXtsZXQgbD1bXSx1PXFaKHRoaXMuX2RyaXZlcixvLGwsW10pO2lmKGwubGVuZ3RoKXRocm93IG5ldyBBdCgzNDA0LCExKTthPWZ1bmN0aW9uKG4sdCxlKXtyZXR1cm4gbmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLm5hbWU9dCx0aGlzLmFzdD1lLHRoaXMuX25vcm1hbGl6ZXI9aSx0aGlzLnRyYW5zaXRpb25GYWN0b3JpZXM9W10sdGhpcy5zdGF0ZXM9bmV3IE1hcCxlLnN0YXRlcy5mb3JFYWNoKHI9PntsZXQgbz1yLm9wdGlvbnMmJnIub3B0aW9ucy5wYXJhbXN8fHt9O3RoaXMuc3RhdGVzLnNldChyLm5hbWUsbmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLnN0eWxlcz10LHRoaXMuZGVmYXVsdFBhcmFtcz1lLHRoaXMubm9ybWFsaXplcj1pfWJ1aWxkU3R5bGVzKHQsZSl7bGV0IGk9bmV3IE1hcCxyPXlNKHRoaXMuZGVmYXVsdFBhcmFtcyk7cmV0dXJuIE9iamVjdC5rZXlzKHQpLmZvckVhY2gobz0+e2xldCBzPXRbb107bnVsbCE9PXMmJihyW29dPXMpfSksdGhpcy5zdHlsZXMuc3R5bGVzLmZvckVhY2gobz0+eyJzdHJpbmciIT10eXBlb2YgbyYmby5mb3JFYWNoKChzLGEpPT57cyYmKHM9aE0ocyxyLGUpKTtsZXQgbD10aGlzLm5vcm1hbGl6ZXIubm9ybWFsaXplUHJvcGVydHlOYW1lKGEsZSk7cz10aGlzLm5vcm1hbGl6ZXIubm9ybWFsaXplU3R5bGVWYWx1ZShhLGwscyxlKSxpLnNldChsLHMpfSl9KSxpfX0oci5zdHlsZSxvLGkpKX0pLEVaKHRoaXMuc3RhdGVzLCJ0cnVlIiwiMSIpLEVaKHRoaXMuc3RhdGVzLCJmYWxzZSIsIjAiKSxlLnRyYW5zaXRpb25zLmZvckVhY2gocj0+e3RoaXMudHJhbnNpdGlvbkZhY3Rvcmllcy5wdXNoKG5ldyBtQSh0LHIsdGhpcy5zdGF0ZXMpKX0pLHRoaXMuZmFsbGJhY2tUcmFuc2l0aW9uPWZ1bmN0aW9uKG4sdCxlKXtyZXR1cm4gbmV3IG1BKG4se3R5cGU6MSxhbmltYXRpb246e3R5cGU6MixzdGVwczpbXSxvcHRpb25zOm51bGx9LG1hdGNoZXJzOlsocyxhKT0+ITBdLG9wdGlvbnM6bnVsbCxxdWVyeUNvdW50OjAsZGVwQ291bnQ6MH0sdCl9KHQsdGhpcy5zdGF0ZXMpfWdldCBjb250YWluc1F1ZXJpZXMoKXtyZXR1cm4gdGhpcy5hc3QucXVlcnlDb3VudD4wfW1hdGNoVHJhbnNpdGlvbih0LGUsaSxyKXtyZXR1cm4gdGhpcy50cmFuc2l0aW9uRmFjdG9yaWVzLmZpbmQocz0+cy5tYXRjaCh0LGUsaSxyKSl8fG51bGx9bWF0Y2hTdHlsZXModCxlLGkpe3JldHVybiB0aGlzLmZhbGxiYWNrVHJhbnNpdGlvbi5idWlsZFN0eWxlcyh0LGUsaSl9fShuLHQsZSl9KHIsdSx0aGlzLl9ub3JtYWxpemVyKSx0aGlzLl90cmlnZ2VyQ2FjaGVbc109YX10aGlzLl90cmFuc2l0aW9uRW5naW5lLnJlZ2lzdGVyVHJpZ2dlcihlLHIsYSl9cmVnaXN0ZXIodCxlKXt0aGlzLl90cmFuc2l0aW9uRW5naW5lLnJlZ2lzdGVyKHQsZSl9ZGVzdHJveSh0LGUpe3RoaXMuX3RyYW5zaXRpb25FbmdpbmUuZGVzdHJveSh0LGUpfW9uSW5zZXJ0KHQsZSxpLHIpe3RoaXMuX3RyYW5zaXRpb25FbmdpbmUuaW5zZXJ0Tm9kZSh0LGUsaSxyKX1vblJlbW92ZSh0LGUsaSxyKXt0aGlzLl90cmFuc2l0aW9uRW5naW5lLnJlbW92ZU5vZGUodCxlLHJ8fCExLGkpfWRpc2FibGVBbmltYXRpb25zKHQsZSl7dGhpcy5fdHJhbnNpdGlvbkVuZ2luZS5tYXJrRWxlbWVudEFzRGlzYWJsZWQodCxlKX1wcm9jZXNzKHQsZSxpLHIpe2lmKCJAIj09aS5jaGFyQXQoMCkpe2xldFtvLHNdPXZaKGkpO3RoaXMuX3RpbWVsaW5lRW5naW5lLmNvbW1hbmQobyxlLHMscil9ZWxzZSB0aGlzLl90cmFuc2l0aW9uRW5naW5lLnRyaWdnZXIodCxlLGkscil9bGlzdGVuKHQsZSxpLHIsbyl7aWYoIkAiPT1pLmNoYXJBdCgwKSl7bGV0W3MsYV09dlooaSk7cmV0dXJuIHRoaXMuX3RpbWVsaW5lRW5naW5lLmxpc3RlbihzLGUsYSxvKX1yZXR1cm4gdGhpcy5fdHJhbnNpdGlvbkVuZ2luZS5saXN0ZW4odCxlLGkscixvKX1mbHVzaCh0PS0xKXt0aGlzLl90cmFuc2l0aW9uRW5naW5lLmZsdXNoKHQpfWdldCBwbGF5ZXJzKCl7cmV0dXJuIHRoaXMuX3RyYW5zaXRpb25FbmdpbmUucGxheWVycy5jb25jYXQodGhpcy5fdGltZWxpbmVFbmdpbmUucGxheWVycyl9d2hlblJlbmRlcmluZ0RvbmUoKXtyZXR1cm4gdGhpcy5fdHJhbnNpdGlvbkVuZ2luZS53aGVuUmVuZGVyaW5nRG9uZSgpfX0sY0llPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIpe3RoaXMuX2VsZW1lbnQ9ZSx0aGlzLl9zdGFydFN0eWxlcz1pLHRoaXMuX2VuZFN0eWxlcz1yLHRoaXMuX3N0YXRlPTA7bGV0IG89bi5pbml0aWFsU3R5bGVzQnlFbGVtZW50LmdldChlKTtvfHxuLmluaXRpYWxTdHlsZXNCeUVsZW1lbnQuc2V0KGUsbz1uZXcgTWFwKSx0aGlzLl9pbml0aWFsU3R5bGVzPW99c3RhcnQoKXt0aGlzLl9zdGF0ZTwxJiYodGhpcy5fc3RhcnRTdHlsZXMmJkd1KHRoaXMuX2VsZW1lbnQsdGhpcy5fc3RhcnRTdHlsZXMsdGhpcy5faW5pdGlhbFN0eWxlcyksdGhpcy5fc3RhdGU9MSl9ZmluaXNoKCl7dGhpcy5zdGFydCgpLHRoaXMuX3N0YXRlPDImJihHdSh0aGlzLl9lbGVtZW50LHRoaXMuX2luaXRpYWxTdHlsZXMpLHRoaXMuX2VuZFN0eWxlcyYmKEd1KHRoaXMuX2VsZW1lbnQsdGhpcy5fZW5kU3R5bGVzKSx0aGlzLl9lbmRTdHlsZXM9bnVsbCksdGhpcy5fc3RhdGU9MSl9ZGVzdHJveSgpe3RoaXMuZmluaXNoKCksdGhpcy5fc3RhdGU8MyYmKG4uaW5pdGlhbFN0eWxlc0J5RWxlbWVudC5kZWxldGUodGhpcy5fZWxlbWVudCksdGhpcy5fc3RhcnRTdHlsZXMmJihPbSh0aGlzLl9lbGVtZW50LHRoaXMuX3N0YXJ0U3R5bGVzKSx0aGlzLl9lbmRTdHlsZXM9bnVsbCksdGhpcy5fZW5kU3R5bGVzJiYoT20odGhpcy5fZWxlbWVudCx0aGlzLl9lbmRTdHlsZXMpLHRoaXMuX2VuZFN0eWxlcz1udWxsKSxHdSh0aGlzLl9lbGVtZW50LHRoaXMuX2luaXRpYWxTdHlsZXMpLHRoaXMuX3N0YXRlPTMpfX1yZXR1cm4gbi5pbml0aWFsU3R5bGVzQnlFbGVtZW50PW5ldyBXZWFrTWFwLG59KSgpO2Z1bmN0aW9uIFQ1KG4pe2xldCB0PW51bGw7cmV0dXJuIG4uZm9yRWFjaCgoZSxpKT0+eyhmdW5jdGlvbihuKXtyZXR1cm4iZGlzcGxheSI9PT1ufHwicG9zaXRpb24iPT09bn0pKGkpJiYodD10fHxuZXcgTWFwLHQuc2V0KGksZSkpfSksdH12YXIgZ0E9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscil7dGhpcy5lbGVtZW50PXQsdGhpcy5rZXlmcmFtZXM9ZSx0aGlzLm9wdGlvbnM9aSx0aGlzLl9zcGVjaWFsU3R5bGVzPXIsdGhpcy5fb25Eb25lRm5zPVtdLHRoaXMuX29uU3RhcnRGbnM9W10sdGhpcy5fb25EZXN0cm95Rm5zPVtdLHRoaXMuX2luaXRpYWxpemVkPSExLHRoaXMuX2ZpbmlzaGVkPSExLHRoaXMuX3N0YXJ0ZWQ9ITEsdGhpcy5fZGVzdHJveWVkPSExLHRoaXMuX29yaWdpbmFsT25Eb25lRm5zPVtdLHRoaXMuX29yaWdpbmFsT25TdGFydEZucz1bXSx0aGlzLnRpbWU9MCx0aGlzLnBhcmVudFBsYXllcj1udWxsLHRoaXMuY3VycmVudFNuYXBzaG90PW5ldyBNYXAsdGhpcy5fZHVyYXRpb249aS5kdXJhdGlvbix0aGlzLl9kZWxheT1pLmRlbGF5fHwwLHRoaXMudGltZT10aGlzLl9kdXJhdGlvbit0aGlzLl9kZWxheX1fb25GaW5pc2goKXt0aGlzLl9maW5pc2hlZHx8KHRoaXMuX2ZpbmlzaGVkPSEwLHRoaXMuX29uRG9uZUZucy5mb3JFYWNoKHQ9PnQoKSksdGhpcy5fb25Eb25lRm5zPVtdKX1pbml0KCl7dGhpcy5fYnVpbGRQbGF5ZXIoKSx0aGlzLl9wcmVwYXJlUGxheWVyQmVmb3JlU3RhcnQoKX1fYnVpbGRQbGF5ZXIoKXtpZih0aGlzLl9pbml0aWFsaXplZClyZXR1cm47dGhpcy5faW5pdGlhbGl6ZWQ9ITA7bGV0IHQ9dGhpcy5rZXlmcmFtZXM7dGhpcy5kb21QbGF5ZXI9dGhpcy5fdHJpZ2dlcldlYkFuaW1hdGlvbih0aGlzLmVsZW1lbnQsdCx0aGlzLm9wdGlvbnMpLHRoaXMuX2ZpbmFsS2V5ZnJhbWU9dC5sZW5ndGg/dFt0Lmxlbmd0aC0xXTpuZXcgTWFwLHRoaXMuZG9tUGxheWVyLmFkZEV2ZW50TGlzdGVuZXIoImZpbmlzaCIsKCk9PnRoaXMuX29uRmluaXNoKCkpfV9wcmVwYXJlUGxheWVyQmVmb3JlU3RhcnQoKXt0aGlzLl9kZWxheT90aGlzLl9yZXNldERvbVBsYXllclN0YXRlKCk6dGhpcy5kb21QbGF5ZXIucGF1c2UoKX1fY29udmVydEtleWZyYW1lc1RvT2JqZWN0KHQpe2xldCBlPVtdO3JldHVybiB0LmZvckVhY2goaT0+e2UucHVzaChPYmplY3QuZnJvbUVudHJpZXMoaSkpfSksZX1fdHJpZ2dlcldlYkFuaW1hdGlvbih0LGUsaSl7cmV0dXJuIHQuYW5pbWF0ZSh0aGlzLl9jb252ZXJ0S2V5ZnJhbWVzVG9PYmplY3QoZSksaSl9b25TdGFydCh0KXt0aGlzLl9vcmlnaW5hbE9uU3RhcnRGbnMucHVzaCh0KSx0aGlzLl9vblN0YXJ0Rm5zLnB1c2godCl9b25Eb25lKHQpe3RoaXMuX29yaWdpbmFsT25Eb25lRm5zLnB1c2godCksdGhpcy5fb25Eb25lRm5zLnB1c2godCl9b25EZXN0cm95KHQpe3RoaXMuX29uRGVzdHJveUZucy5wdXNoKHQpfXBsYXkoKXt0aGlzLl9idWlsZFBsYXllcigpLHRoaXMuaGFzU3RhcnRlZCgpfHwodGhpcy5fb25TdGFydEZucy5mb3JFYWNoKHQ9PnQoKSksdGhpcy5fb25TdGFydEZucz1bXSx0aGlzLl9zdGFydGVkPSEwLHRoaXMuX3NwZWNpYWxTdHlsZXMmJnRoaXMuX3NwZWNpYWxTdHlsZXMuc3RhcnQoKSksdGhpcy5kb21QbGF5ZXIucGxheSgpfXBhdXNlKCl7dGhpcy5pbml0KCksdGhpcy5kb21QbGF5ZXIucGF1c2UoKX1maW5pc2goKXt0aGlzLmluaXQoKSx0aGlzLl9zcGVjaWFsU3R5bGVzJiZ0aGlzLl9zcGVjaWFsU3R5bGVzLmZpbmlzaCgpLHRoaXMuX29uRmluaXNoKCksdGhpcy5kb21QbGF5ZXIuZmluaXNoKCl9cmVzZXQoKXt0aGlzLl9yZXNldERvbVBsYXllclN0YXRlKCksdGhpcy5fZGVzdHJveWVkPSExLHRoaXMuX2ZpbmlzaGVkPSExLHRoaXMuX3N0YXJ0ZWQ9ITEsdGhpcy5fb25TdGFydEZucz10aGlzLl9vcmlnaW5hbE9uU3RhcnRGbnMsdGhpcy5fb25Eb25lRm5zPXRoaXMuX29yaWdpbmFsT25Eb25lRm5zfV9yZXNldERvbVBsYXllclN0YXRlKCl7dGhpcy5kb21QbGF5ZXImJnRoaXMuZG9tUGxheWVyLmNhbmNlbCgpfXJlc3RhcnQoKXt0aGlzLnJlc2V0KCksdGhpcy5wbGF5KCl9aGFzU3RhcnRlZCgpe3JldHVybiB0aGlzLl9zdGFydGVkfWRlc3Ryb3koKXt0aGlzLl9kZXN0cm95ZWR8fCh0aGlzLl9kZXN0cm95ZWQ9ITAsdGhpcy5fcmVzZXREb21QbGF5ZXJTdGF0ZSgpLHRoaXMuX29uRmluaXNoKCksdGhpcy5fc3BlY2lhbFN0eWxlcyYmdGhpcy5fc3BlY2lhbFN0eWxlcy5kZXN0cm95KCksdGhpcy5fb25EZXN0cm95Rm5zLmZvckVhY2godD0+dCgpKSx0aGlzLl9vbkRlc3Ryb3lGbnM9W10pfXNldFBvc2l0aW9uKHQpe3ZvaWQgMD09PXRoaXMuZG9tUGxheWVyJiZ0aGlzLmluaXQoKSx0aGlzLmRvbVBsYXllci5jdXJyZW50VGltZT10KnRoaXMudGltZX1nZXRQb3NpdGlvbigpe3JldHVybiB0aGlzLmRvbVBsYXllci5jdXJyZW50VGltZS90aGlzLnRpbWV9Z2V0IHRvdGFsVGltZSgpe3JldHVybiB0aGlzLl9kZWxheSt0aGlzLl9kdXJhdGlvbn1iZWZvcmVEZXN0cm95KCl7bGV0IHQ9bmV3IE1hcDt0aGlzLmhhc1N0YXJ0ZWQoKSYmdGhpcy5fZmluYWxLZXlmcmFtZS5mb3JFYWNoKChpLHIpPT57Im9mZnNldCIhPT1yJiZ0LnNldChyLHRoaXMuX2ZpbmlzaGVkP2k6eloodGhpcy5lbGVtZW50LHIpKX0pLHRoaXMuY3VycmVudFNuYXBzaG90PXR9dHJpZ2dlckNhbGxiYWNrKHQpe2xldCBlPSJzdGFydCI9PT10P3RoaXMuX29uU3RhcnRGbnM6dGhpcy5fb25Eb25lRm5zO2UuZm9yRWFjaChpPT5pKCkpLGUubGVuZ3RoPTB9fSxwSWU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGNNe2NvbnN0cnVjdG9yKGUsaSl7c3VwZXIoKSx0aGlzLl9uZXh0QW5pbWF0aW9uSWQ9MCx0aGlzLl9yZW5kZXJlcj1lLmNyZWF0ZVJlbmRlcmVyKGkuYm9keSx7aWQ6IjAiLGVuY2Fwc3VsYXRpb246SmEuTm9uZSxzdHlsZXM6W10sZGF0YTp7YW5pbWF0aW9uOltdfX0pfWJ1aWxkKGUpe2xldCBpPXRoaXMuX25leHRBbmltYXRpb25JZC50b1N0cmluZygpO3RoaXMuX25leHRBbmltYXRpb25JZCsrO2xldCByPUFycmF5LmlzQXJyYXkoZSk/aUEoZSk6ZTtyZXR1cm4gWloodGhpcy5fcmVuZGVyZXIsbnVsbCxpLCJyZWdpc3RlciIsW3JdKSxuZXcgUTUoaSx0aGlzLl9yZW5kZXJlcil9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGood3UpLGooSHQpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxRNT1jbGFzcyBleHRlbmRzIG5Be2NvbnN0cnVjdG9yKHQsZSl7c3VwZXIoKSx0aGlzLl9pZD10LHRoaXMuX3JlbmRlcmVyPWV9Y3JlYXRlKHQsZSl7cmV0dXJuIG5ldyBLNSh0aGlzLl9pZCx0LGV8fHt9LHRoaXMuX3JlbmRlcmVyKX19LEs1PWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe3RoaXMuaWQ9dCx0aGlzLmVsZW1lbnQ9ZSx0aGlzLl9yZW5kZXJlcj1yLHRoaXMucGFyZW50UGxheWVyPW51bGwsdGhpcy5fc3RhcnRlZD0hMSx0aGlzLnRvdGFsVGltZT0wLHRoaXMuX2NvbW1hbmQoImNyZWF0ZSIsaSl9X2xpc3Rlbih0LGUpe3JldHVybiB0aGlzLl9yZW5kZXJlci5saXN0ZW4odGhpcy5lbGVtZW50LGBAQCR7dGhpcy5pZH06JHt0fWAsZSl9X2NvbW1hbmQodCwuLi5lKXtyZXR1cm4gWloodGhpcy5fcmVuZGVyZXIsdGhpcy5lbGVtZW50LHRoaXMuaWQsdCxlKX1vbkRvbmUodCl7dGhpcy5fbGlzdGVuKCJkb25lIix0KX1vblN0YXJ0KHQpe3RoaXMuX2xpc3Rlbigic3RhcnQiLHQpfW9uRGVzdHJveSh0KXt0aGlzLl9saXN0ZW4oImRlc3Ryb3kiLHQpfWluaXQoKXt0aGlzLl9jb21tYW5kKCJpbml0Iil9aGFzU3RhcnRlZCgpe3JldHVybiB0aGlzLl9zdGFydGVkfXBsYXkoKXt0aGlzLl9jb21tYW5kKCJwbGF5IiksdGhpcy5fc3RhcnRlZD0hMH1wYXVzZSgpe3RoaXMuX2NvbW1hbmQoInBhdXNlIil9cmVzdGFydCgpe3RoaXMuX2NvbW1hbmQoInJlc3RhcnQiKX1maW5pc2goKXt0aGlzLl9jb21tYW5kKCJmaW5pc2giKX1kZXN0cm95KCl7dGhpcy5fY29tbWFuZCgiZGVzdHJveSIpfXJlc2V0KCl7dGhpcy5fY29tbWFuZCgicmVzZXQiKSx0aGlzLl9zdGFydGVkPSExfXNldFBvc2l0aW9uKHQpe3RoaXMuX2NvbW1hbmQoInNldFBvc2l0aW9uIix0KX1nZXRQb3NpdGlvbigpe3JldHVybiB0aGlzLl9yZW5kZXJlci5lbmdpbmUucGxheWVyc1srdGhpcy5pZF0/LmdldFBvc2l0aW9uKCk/PzB9fTtmdW5jdGlvbiBaWihuLHQsZSxpLHIpe3JldHVybiBuLnNldFByb3BlcnR5KHQsYEBAJHtlfToke2l9YCxyKX12YXIgSlo9IkAuZGlzYWJsZWQiLGhJZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLmRlbGVnYXRlPWUsdGhpcy5lbmdpbmU9aSx0aGlzLl96b25lPXIsdGhpcy5fY3VycmVudElkPTAsdGhpcy5fbWljcm90YXNrSWQ9MSx0aGlzLl9hbmltYXRpb25DYWxsYmFja3NCdWZmZXI9W10sdGhpcy5fcmVuZGVyZXJDYWNoZT1uZXcgTWFwLHRoaXMuX2NkUmVjdXJEZXB0aD0wLHRoaXMucHJvbWlzZT1Qcm9taXNlLnJlc29sdmUoMCksaS5vblJlbW92YWxDb21wbGV0ZT0obyxzKT0+e2xldCBhPXM/LnBhcmVudE5vZGUobyk7YSYmcy5yZW1vdmVDaGlsZChhLG8pfX1jcmVhdGVSZW5kZXJlcihlLGkpe2xldCBvPXRoaXMuZGVsZWdhdGUuY3JlYXRlUmVuZGVyZXIoZSxpKTtpZighKGUmJmkmJmkuZGF0YSYmaS5kYXRhLmFuaW1hdGlvbikpe2xldCB1PXRoaXMuX3JlbmRlcmVyQ2FjaGUuZ2V0KG8pO3JldHVybiB1fHwodT1uZXcgeUEoIiIsbyx0aGlzLmVuZ2luZSwoKT0+dGhpcy5fcmVuZGVyZXJDYWNoZS5kZWxldGUobykpLHRoaXMuX3JlbmRlcmVyQ2FjaGUuc2V0KG8sdSkpLHV9bGV0IHM9aS5pZCxhPWkuaWQrIi0iK3RoaXMuX2N1cnJlbnRJZDt0aGlzLl9jdXJyZW50SWQrKyx0aGlzLmVuZ2luZS5yZWdpc3RlcihhLGUpO2xldCBsPXU9PntBcnJheS5pc0FycmF5KHUpP3UuZm9yRWFjaChsKTp0aGlzLmVuZ2luZS5yZWdpc3RlclRyaWdnZXIocyxhLGUsdS5uYW1lLHUpfTtyZXR1cm4gaS5kYXRhLmFuaW1hdGlvbi5mb3JFYWNoKGwpLG5ldyBaNSh0aGlzLGEsbyx0aGlzLmVuZ2luZSl9YmVnaW4oKXt0aGlzLl9jZFJlY3VyRGVwdGgrKyx0aGlzLmRlbGVnYXRlLmJlZ2luJiZ0aGlzLmRlbGVnYXRlLmJlZ2luKCl9X3NjaGVkdWxlQ291bnRUYXNrKCl7dGhpcy5wcm9taXNlLnRoZW4oKCk9Pnt0aGlzLl9taWNyb3Rhc2tJZCsrfSl9c2NoZWR1bGVMaXN0ZW5lckNhbGxiYWNrKGUsaSxyKXtlPj0wJiZlPHRoaXMuX21pY3JvdGFza0lkP3RoaXMuX3pvbmUucnVuKCgpPT5pKHIpKTooMD09dGhpcy5fYW5pbWF0aW9uQ2FsbGJhY2tzQnVmZmVyLmxlbmd0aCYmUHJvbWlzZS5yZXNvbHZlKG51bGwpLnRoZW4oKCk9Pnt0aGlzLl96b25lLnJ1bigoKT0+e3RoaXMuX2FuaW1hdGlvbkNhbGxiYWNrc0J1ZmZlci5mb3JFYWNoKG89PntsZXRbcyxhXT1vO3MoYSl9KSx0aGlzLl9hbmltYXRpb25DYWxsYmFja3NCdWZmZXI9W119KX0pLHRoaXMuX2FuaW1hdGlvbkNhbGxiYWNrc0J1ZmZlci5wdXNoKFtpLHJdKSl9ZW5kKCl7dGhpcy5fY2RSZWN1ckRlcHRoLS0sMD09dGhpcy5fY2RSZWN1ckRlcHRoJiZ0aGlzLl96b25lLnJ1bk91dHNpZGVBbmd1bGFyKCgpPT57dGhpcy5fc2NoZWR1bGVDb3VudFRhc2soKSx0aGlzLmVuZ2luZS5mbHVzaCh0aGlzLl9taWNyb3Rhc2tJZCl9KSx0aGlzLmRlbGVnYXRlLmVuZCYmdGhpcy5kZWxlZ2F0ZS5lbmQoKX13aGVuUmVuZGVyaW5nRG9uZSgpe3JldHVybiB0aGlzLmVuZ2luZS53aGVuUmVuZGVyaW5nRG9uZSgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKHd1KSxqKGhoKSxqKF90KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCkseUE9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscil7dGhpcy5uYW1lc3BhY2VJZD10LHRoaXMuZGVsZWdhdGU9ZSx0aGlzLmVuZ2luZT1pLHRoaXMuX29uRGVzdHJveT1yLHRoaXMuZGVzdHJveU5vZGU9dGhpcy5kZWxlZ2F0ZS5kZXN0cm95Tm9kZT9vPT5lLmRlc3Ryb3lOb2RlKG8pOm51bGx9Z2V0IGRhdGEoKXtyZXR1cm4gdGhpcy5kZWxlZ2F0ZS5kYXRhfWRlc3Ryb3koKXt0aGlzLmVuZ2luZS5kZXN0cm95KHRoaXMubmFtZXNwYWNlSWQsdGhpcy5kZWxlZ2F0ZSksdGhpcy5kZWxlZ2F0ZS5kZXN0cm95KCksdGhpcy5fb25EZXN0cm95Py4oKX1jcmVhdGVFbGVtZW50KHQsZSl7cmV0dXJuIHRoaXMuZGVsZWdhdGUuY3JlYXRlRWxlbWVudCh0LGUpfWNyZWF0ZUNvbW1lbnQodCl7cmV0dXJuIHRoaXMuZGVsZWdhdGUuY3JlYXRlQ29tbWVudCh0KX1jcmVhdGVUZXh0KHQpe3JldHVybiB0aGlzLmRlbGVnYXRlLmNyZWF0ZVRleHQodCl9YXBwZW5kQ2hpbGQodCxlKXt0aGlzLmRlbGVnYXRlLmFwcGVuZENoaWxkKHQsZSksdGhpcy5lbmdpbmUub25JbnNlcnQodGhpcy5uYW1lc3BhY2VJZCxlLHQsITEpfWluc2VydEJlZm9yZSh0LGUsaSxyPSEwKXt0aGlzLmRlbGVnYXRlLmluc2VydEJlZm9yZSh0LGUsaSksdGhpcy5lbmdpbmUub25JbnNlcnQodGhpcy5uYW1lc3BhY2VJZCxlLHQscil9cmVtb3ZlQ2hpbGQodCxlLGkpe3RoaXMuZW5naW5lLm9uUmVtb3ZlKHRoaXMubmFtZXNwYWNlSWQsZSx0aGlzLmRlbGVnYXRlLGkpfXNlbGVjdFJvb3RFbGVtZW50KHQsZSl7cmV0dXJuIHRoaXMuZGVsZWdhdGUuc2VsZWN0Um9vdEVsZW1lbnQodCxlKX1wYXJlbnROb2RlKHQpe3JldHVybiB0aGlzLmRlbGVnYXRlLnBhcmVudE5vZGUodCl9bmV4dFNpYmxpbmcodCl7cmV0dXJuIHRoaXMuZGVsZWdhdGUubmV4dFNpYmxpbmcodCl9c2V0QXR0cmlidXRlKHQsZSxpLHIpe3RoaXMuZGVsZWdhdGUuc2V0QXR0cmlidXRlKHQsZSxpLHIpfXJlbW92ZUF0dHJpYnV0ZSh0LGUsaSl7dGhpcy5kZWxlZ2F0ZS5yZW1vdmVBdHRyaWJ1dGUodCxlLGkpfWFkZENsYXNzKHQsZSl7dGhpcy5kZWxlZ2F0ZS5hZGRDbGFzcyh0LGUpfXJlbW92ZUNsYXNzKHQsZSl7dGhpcy5kZWxlZ2F0ZS5yZW1vdmVDbGFzcyh0LGUpfXNldFN0eWxlKHQsZSxpLHIpe3RoaXMuZGVsZWdhdGUuc2V0U3R5bGUodCxlLGkscil9cmVtb3ZlU3R5bGUodCxlLGkpe3RoaXMuZGVsZWdhdGUucmVtb3ZlU3R5bGUodCxlLGkpfXNldFByb3BlcnR5KHQsZSxpKXsiQCI9PWUuY2hhckF0KDApJiZlPT1KWj90aGlzLmRpc2FibGVBbmltYXRpb25zKHQsISFpKTp0aGlzLmRlbGVnYXRlLnNldFByb3BlcnR5KHQsZSxpKX1zZXRWYWx1ZSh0LGUpe3RoaXMuZGVsZWdhdGUuc2V0VmFsdWUodCxlKX1saXN0ZW4odCxlLGkpe3JldHVybiB0aGlzLmRlbGVnYXRlLmxpc3Rlbih0LGUsaSl9ZGlzYWJsZUFuaW1hdGlvbnModCxlKXt0aGlzLmVuZ2luZS5kaXNhYmxlQW5pbWF0aW9ucyh0LGUpfX0sWjU9Y2xhc3MgZXh0ZW5kcyB5QXtjb25zdHJ1Y3Rvcih0LGUsaSxyLG8pe3N1cGVyKGUsaSxyLG8pLHRoaXMuZmFjdG9yeT10LHRoaXMubmFtZXNwYWNlSWQ9ZX1zZXRQcm9wZXJ0eSh0LGUsaSl7IkAiPT1lLmNoYXJBdCgwKT8iLiI9PWUuY2hhckF0KDEpJiZlPT1KWj90aGlzLmRpc2FibGVBbmltYXRpb25zKHQsaT12b2lkIDA9PT1pfHwhIWkpOnRoaXMuZW5naW5lLnByb2Nlc3ModGhpcy5uYW1lc3BhY2VJZCx0LGUuc2xpY2UoMSksaSk6dGhpcy5kZWxlZ2F0ZS5zZXRQcm9wZXJ0eSh0LGUsaSl9bGlzdGVuKHQsZSxpKXtpZigiQCI9PWUuY2hhckF0KDApKXtsZXQgcj1mdW5jdGlvbihuKXtzd2l0Y2gobil7Y2FzZSJib2R5IjpyZXR1cm4gZG9jdW1lbnQuYm9keTtjYXNlImRvY3VtZW50IjpyZXR1cm4gZG9jdW1lbnQ7Y2FzZSJ3aW5kb3ciOnJldHVybiB3aW5kb3c7ZGVmYXVsdDpyZXR1cm4gbn19KHQpLG89ZS5zbGljZSgxKSxzPSIiO3JldHVybiJAIiE9by5jaGFyQXQoMCkmJihbbyxzXT1mdW5jdGlvbihuKXtsZXQgdD1uLmluZGV4T2YoIi4iKTtyZXR1cm5bbi5zdWJzdHJpbmcoMCx0KSxuLnNsaWNlKHQrMSldfShvKSksdGhpcy5lbmdpbmUubGlzdGVuKHRoaXMubmFtZXNwYWNlSWQscixvLHMsYT0+e3RoaXMuZmFjdG9yeS5zY2hlZHVsZUxpc3RlbmVyQ2FsbGJhY2soYS5fZGF0YXx8LTEsaSxhKX0pfXJldHVybiB0aGlzLmRlbGVnYXRlLmxpc3Rlbih0LGUsaSl9fSxnSWU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGhoe2NvbnN0cnVjdG9yKGUsaSxyLG8pe3N1cGVyKGUuYm9keSxpLHIpfW5nT25EZXN0cm95KCl7dGhpcy5mbHVzaCgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKEh0KSxqKHZNKSxqKGttKSxqKEl1KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksJFo9W3twcm92aWRlOmNNLHVzZUNsYXNzOnBJZX0se3Byb3ZpZGU6a20sdXNlRmFjdG9yeTpmdW5jdGlvbigpe3JldHVybiBuZXcgZkF9fSx7cHJvdmlkZTpoaCx1c2VDbGFzczpnSWV9LHtwcm92aWRlOnd1LHVzZUZhY3Rvcnk6ZnVuY3Rpb24obix0LGUpe3JldHVybiBuZXcgaEllKG4sdCxlKX0sZGVwczpbc00saGgsX3RdfV0sS1o9W3twcm92aWRlOnZNLHVzZUZhY3Rvcnk6KCk9Pm5ldyBjbGFzc3t2YWxpZGF0ZVN0eWxlUHJvcGVydHkodCl7cmV0dXJuITB9dmFsaWRhdGVBbmltYXRhYmxlU3R5bGVQcm9wZXJ0eSh0KXtyZXR1cm4hMH1tYXRjaGVzRWxlbWVudCh0LGUpe3JldHVybiExfWNvbnRhaW5zRWxlbWVudCh0LGUpe3JldHVybiBGWih0LGUpfWdldFBhcmVudEVsZW1lbnQodCl7cmV0dXJuIFc1KHQpfXF1ZXJ5KHQsZSxpKXtyZXR1cm4gTloodCxlLGkpfWNvbXB1dGVTdHlsZSh0LGUsaSl7cmV0dXJuIHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKHQpW2VdfWFuaW1hdGUodCxlLGkscixvLHM9W10pe2xldCBsPXtkdXJhdGlvbjppLGRlbGF5OnIsZmlsbDowPT1yPyJib3RoIjoiZm9yd2FyZHMifTtvJiYobC5lYXNpbmc9byk7bGV0IGM9bmV3IE1hcCx1PXMuZmlsdGVyKGg9PmggaW5zdGFuY2VvZiBnQSk7KGZ1bmN0aW9uKG4sdCl7cmV0dXJuIDA9PT1ufHwwPT09dH0pKGkscikmJnUuZm9yRWFjaChoPT57aC5jdXJyZW50U25hcHNob3QuZm9yRWFjaCgoZixtKT0+Yy5zZXQobSxmKSl9KTtsZXQgZD1mdW5jdGlvbihuKXtyZXR1cm4gbi5sZW5ndGg/blswXWluc3RhbmNlb2YgTWFwP246bi5tYXAodD0+VloodCkpOltdfShlKS5tYXAoaD0+al8oaCkpO2Q9ZnVuY3Rpb24obix0LGUpe2lmKGUuc2l6ZSYmdC5sZW5ndGgpe2xldCBpPXRbMF0scj1bXTtpZihlLmZvckVhY2goKG8scyk9PntpLmhhcyhzKXx8ci5wdXNoKHMpLGkuc2V0KHMsbyl9KSxyLmxlbmd0aClmb3IobGV0IG89MTtvPHQubGVuZ3RoO28rKyl7bGV0IHM9dFtvXTtyLmZvckVhY2goYT0+cy5zZXQoYSx6WihuLGEpKSl9fXJldHVybiB0fSh0LGQsYyk7bGV0IHA9ZnVuY3Rpb24obix0KXtsZXQgZT1udWxsLGk9bnVsbDtyZXR1cm4gQXJyYXkuaXNBcnJheSh0KSYmdC5sZW5ndGg/KGU9VDUodFswXSksdC5sZW5ndGg+MSYmKGk9VDUodFt0Lmxlbmd0aC0xXSkpKTp0IGluc3RhbmNlb2YgTWFwJiYoZT1UNSh0KSksZXx8aT9uZXcgY0llKG4sZSxpKTpudWxsfSh0LGQpO3JldHVybiBuZXcgZ0EodCxkLGwscCl9fX0se3Byb3ZpZGU6UGksdXNlVmFsdWU6IkJyb3dzZXJBbmltYXRpb25zIn0sLi4uJFpdLHlJZT1be3Byb3ZpZGU6dk0sdXNlQ2xhc3M6cTV9LHtwcm92aWRlOlBpLHVzZVZhbHVlOiJOb29wQW5pbWF0aW9ucyJ9LC4uLiRaXSxlSj0oKCk9PntjbGFzcyBue3N0YXRpYyB3aXRoQ29uZmlnKGUpe3JldHVybntuZ01vZHVsZTpuLHByb3ZpZGVyczplLmRpc2FibGVBbmltYXRpb25zP3lJZTpLWn19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6S1osaW1wb3J0czpbdEFdfSksbn0pKCksdDQ9e307ZnVuY3Rpb24gYmUobix0KXtpZih0NFtuXT0odDRbbl18fDApKzEsImZ1bmN0aW9uIj09dHlwZW9mIHQpcmV0dXJuIEo1KG4sKC4uLmkpPT4oey4uLnQoLi4uaSksdHlwZTpufSkpO3N3aXRjaCh0P3QuX2FzOiJlbXB0eSIpe2Nhc2UiZW1wdHkiOnJldHVybiBKNShuLCgpPT4oe3R5cGU6bn0pKTtjYXNlInByb3BzIjpyZXR1cm4gSjUobixpPT4oey4uLmksdHlwZTpufSkpO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKCJVbmV4cGVjdGVkIGNvbmZpZy4iKX19ZnVuY3Rpb24gSjUobix0KXtyZXR1cm4gT2JqZWN0LmRlZmluZVByb3BlcnR5KHQsInR5cGUiLHt2YWx1ZTpuLHdyaXRhYmxlOiExfSl9dmFyIGdKPSJAbmdyeC9zdG9yZS9pbml0IixxXz0oKCk9PntjbGFzcyBuIGV4dGVuZHMgaHJ7Y29uc3RydWN0b3IoKXtzdXBlcih7dHlwZTpnSn0pfW5leHQoZSl7aWYoImZ1bmN0aW9uIj09dHlwZW9mIGUpdGhyb3cgbmV3IFR5cGVFcnJvcigiXG4gICAgICAgIERpc3BhdGNoIGV4cGVjdGVkIGFuIG9iamVjdCwgaW5zdGVhZCBpdCByZWNlaXZlZCBhIGZ1bmN0aW9uLlxuICAgICAgICBJZiB5b3UncmUgdXNpbmcgdGhlIGNyZWF0ZUFjdGlvbiBmdW5jdGlvbiwgbWFrZSBzdXJlIHRvIGludm9rZSB0aGUgZnVuY3Rpb25cbiAgICAgICAgYmVmb3JlIGRpc3BhdGNoaW5nIHRoZSBhY3Rpb24uIEZvciBleGFtcGxlLCBzb21lQWN0aW9uIHNob3VsZCBiZSBzb21lQWN0aW9uKCkuIik7aWYodHlwZW9mIGU+InUiKXRocm93IG5ldyBUeXBlRXJyb3IoIkFjdGlvbnMgbXVzdCBiZSBvYmplY3RzIik7aWYodHlwZW9mIGUudHlwZT4idSIpdGhyb3cgbmV3IFR5cGVFcnJvcigiQWN0aW9ucyBtdXN0IGhhdmUgYSB0eXBlIHByb3BlcnR5Iik7c3VwZXIubmV4dChlKX1jb21wbGV0ZSgpe31uZ09uRGVzdHJveSgpe3N1cGVyLmNvbXBsZXRlKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLGJJZT1bcV9dLF9KPW5ldyBwZSgiQG5ncngvc3RvcmUgSW50ZXJuYWwgUm9vdCBHdWFyZCIpLHRKPW5ldyBwZSgiQG5ncngvc3RvcmUgSW50ZXJuYWwgSW5pdGlhbCBTdGF0ZSIpLG80PW5ldyBwZSgiQG5ncngvc3RvcmUgSW5pdGlhbCBTdGF0ZSIpLHZKPW5ldyBwZSgiQG5ncngvc3RvcmUgUmVkdWNlciBGYWN0b3J5Iiksbko9bmV3IHBlKCJAbmdyeC9zdG9yZSBJbnRlcm5hbCBSZWR1Y2VyIEZhY3RvcnkgUHJvdmlkZXIiKSx5Sj1uZXcgcGUoIkBuZ3J4L3N0b3JlIEluaXRpYWwgUmVkdWNlcnMiKSwkNT1uZXcgcGUoIkBuZ3J4L3N0b3JlIEludGVybmFsIEluaXRpYWwgUmVkdWNlcnMiKSxpSj1uZXcgcGUoIkBuZ3J4L3N0b3JlIFN0b3JlIEZlYXR1cmVzIikscko9bmV3IHBlKCJAbmdyeC9zdG9yZSBJbnRlcm5hbCBTdG9yZSBSZWR1Y2VycyIpLGU0PW5ldyBwZSgiQG5ncngvc3RvcmUgSW50ZXJuYWwgRmVhdHVyZSBSZWR1Y2VycyIpLG9KPW5ldyBwZSgiQG5ncngvc3RvcmUgSW50ZXJuYWwgRmVhdHVyZSBDb25maWdzIiksYko9bmV3IHBlKCJAbmdyeC9zdG9yZSBJbnRlcm5hbCBTdG9yZSBGZWF0dXJlcyIpLHNKPW5ldyBwZSgiQG5ncngvc3RvcmUgSW50ZXJuYWwgRmVhdHVyZSBSZWR1Y2VycyBUb2tlbiIpLHhKPW5ldyBwZSgiQG5ncngvc3RvcmUgRmVhdHVyZSBSZWR1Y2VycyIpLGFKPW5ldyBwZSgiQG5ncngvc3RvcmUgVXNlciBQcm92aWRlZCBNZXRhIFJlZHVjZXJzIiksV189bmV3IHBlKCJAbmdyeC9zdG9yZSBNZXRhIFJlZHVjZXJzIiksbEo9bmV3IHBlKCJAbmdyeC9zdG9yZSBJbnRlcm5hbCBSZXNvbHZlZCBNZXRhIFJlZHVjZXJzIiksY0o9bmV3IHBlKCJAbmdyeC9zdG9yZSBVc2VyIFJ1bnRpbWUgQ2hlY2tzIENvbmZpZyIpLHVKPW5ldyBwZSgiQG5ncngvc3RvcmUgSW50ZXJuYWwgVXNlciBSdW50aW1lIENoZWNrcyBDb25maWciKSxiTT1uZXcgcGUoIkBuZ3J4L3N0b3JlIEludGVybmFsIFJ1bnRpbWUgQ2hlY2tzIiksczQ9bmV3IHBlKCJAbmdyeC9zdG9yZSBDaGVjayBpZiBBY3Rpb24gdHlwZXMgYXJlIHVuaXF1ZSIpO2Z1bmN0aW9uIEZtKG4sdD17fSl7bGV0IGU9T2JqZWN0LmtleXMobiksaT17fTtmb3IobGV0IG89MDtvPGUubGVuZ3RoO28rKyl7bGV0IHM9ZVtvXTsiZnVuY3Rpb24iPT10eXBlb2YgbltzXSYmKGlbc109bltzXSl9bGV0IHI9T2JqZWN0LmtleXMoaSk7cmV0dXJuIGZ1bmN0aW9uKHMsYSl7cz12b2lkIDA9PT1zP3Q6cztsZXQgbD0hMSxjPXt9O2ZvcihsZXQgdT0wO3U8ci5sZW5ndGg7dSsrKXtsZXQgZD1yW3VdLGg9c1tkXSxmPSgwLGlbZF0pKGgsYSk7Y1tkXT1mLGw9bHx8ZiE9PWh9cmV0dXJuIGw/YzpzfX1mdW5jdGlvbiBNQSguLi5uKXtyZXR1cm4gZnVuY3Rpb24odCl7aWYoMD09PW4ubGVuZ3RoKXJldHVybiB0O2xldCBlPW5bbi5sZW5ndGgtMV07cmV0dXJuIG4uc2xpY2UoMCwtMSkucmVkdWNlUmlnaHQoKHIsbyk9Pm8ociksZSh0KSl9fWZ1bmN0aW9uIENKKG4sdCl7cmV0dXJuIEFycmF5LmlzQXJyYXkodCkmJnQubGVuZ3RoPjAmJihuPU1BLmFwcGx5KG51bGwsWy4uLnQsbl0pKSwoZSxpKT0+e2xldCByPW4oZSk7cmV0dXJuKG8scyk9PnIobz12b2lkIDA9PT1vP2k6byxzKX19bmV3IHBlKCJAbmdyeC9zdG9yZSBSb290IFN0b3JlIFByb3ZpZGVyIiksbmV3IHBlKCJAbmdyeC9zdG9yZSBGZWF0dXJlIFN0YXRlIFByb3ZpZGVyIik7dmFyIHhNPWNsYXNzIGV4dGVuZHMgdW57fSxiQT1jbGFzcyBleHRlbmRzIHFfe30seEE9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGhye2NvbnN0cnVjdG9yKGUsaSxyLG8pe3N1cGVyKG8ocixpKSksdGhpcy5kaXNwYXRjaGVyPWUsdGhpcy5pbml0aWFsU3RhdGU9aSx0aGlzLnJlZHVjZXJzPXIsdGhpcy5yZWR1Y2VyRmFjdG9yeT1vfWdldCBjdXJyZW50UmVkdWNlcnMoKXtyZXR1cm4gdGhpcy5yZWR1Y2Vyc31hZGRGZWF0dXJlKGUpe3RoaXMuYWRkRmVhdHVyZXMoW2VdKX1hZGRGZWF0dXJlcyhlKXtsZXQgaT1lLnJlZHVjZSgocix7cmVkdWNlcnM6byxyZWR1Y2VyRmFjdG9yeTpzLG1ldGFSZWR1Y2VyczphLGluaXRpYWxTdGF0ZTpsLGtleTpjfSk9PntsZXQgdT0iZnVuY3Rpb24iPT10eXBlb2Ygbz9mdW5jdGlvbihuKXtsZXQgdD1BcnJheS5pc0FycmF5KG4pJiZuLmxlbmd0aD4wP01BKC4uLm4pOmU9PmU7cmV0dXJuKGUsaSk9PihlPXQoZSksKHIsbyk9PmUocj12b2lkIDA9PT1yP2k6cixvKSl9KGEpKG8sbCk6Q0oocyxhKShvLGwpO3JldHVybiByW2NdPXUscn0se30pO3RoaXMuYWRkUmVkdWNlcnMoaSl9cmVtb3ZlRmVhdHVyZShlKXt0aGlzLnJlbW92ZUZlYXR1cmVzKFtlXSl9cmVtb3ZlRmVhdHVyZXMoZSl7dGhpcy5yZW1vdmVSZWR1Y2VycyhlLm1hcChpPT5pLmtleSkpfWFkZFJlZHVjZXIoZSxpKXt0aGlzLmFkZFJlZHVjZXJzKHtbZV06aX0pfWFkZFJlZHVjZXJzKGUpe3RoaXMucmVkdWNlcnM9ey4uLnRoaXMucmVkdWNlcnMsLi4uZX0sdGhpcy51cGRhdGVSZWR1Y2VycyhPYmplY3Qua2V5cyhlKSl9cmVtb3ZlUmVkdWNlcihlKXt0aGlzLnJlbW92ZVJlZHVjZXJzKFtlXSl9cmVtb3ZlUmVkdWNlcnMoZSl7ZS5mb3JFYWNoKGk9Pnt0aGlzLnJlZHVjZXJzPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIE9iamVjdC5rZXlzKG4pLmZpbHRlcihlPT5lIT09dCkucmVkdWNlKChlLGkpPT5PYmplY3QuYXNzaWduKGUse1tpXTpuW2ldfSkse30pfSh0aGlzLnJlZHVjZXJzLGkpfSksdGhpcy51cGRhdGVSZWR1Y2VycyhlKX11cGRhdGVSZWR1Y2VycyhlKXt0aGlzLm5leHQodGhpcy5yZWR1Y2VyRmFjdG9yeSh0aGlzLnJlZHVjZXJzLHRoaXMuaW5pdGlhbFN0YXRlKSksdGhpcy5kaXNwYXRjaGVyLm5leHQoe3R5cGU6IkBuZ3J4L3N0b3JlL3VwZGF0ZS1yZWR1Y2VycyIsZmVhdHVyZXM6ZX0pfW5nT25EZXN0cm95KCl7dGhpcy5jb21wbGV0ZSgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKGJBKSxqKG80KSxqKHlKKSxqKHZKKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksRUllPVt4QSx7cHJvdmlkZTp4TSx1c2VFeGlzdGluZzp4QX0se3Byb3ZpZGU6YkEsdXNlRXhpc3Rpbmc6cV99XSxDTT0oKCk9PntjbGFzcyBuIGV4dGVuZHMga2V7bmdPbkRlc3Ryb3koKXt0aGlzLmNvbXBsZXRlKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbigpe2xldCB0O3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4odHx8KHQ9cGkobikpKShpfHxuKX19KCksbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxUSWU9W0NNXSxDQT1jbGFzcyBleHRlbmRzIHVue30sZEo9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGhye2NvbnN0cnVjdG9yKGUsaSxyLG8pe3N1cGVyKG8pO2xldCBjPWUucGlwZShCZihnTikpLnBpcGUoV3QoaSkpLnBpcGUoZnVuY3Rpb24obix0KXtyZXR1cm4gZW4oQVcobix0LGFyZ3VtZW50cy5sZW5ndGg+PTIsITApKX0oREllLHtzdGF0ZTpvfSkpO3RoaXMuc3RhdGVTdWJzY3JpcHRpb249Yy5zdWJzY3JpYmUoKHtzdGF0ZTp1LGFjdGlvbjpkfSk9Pnt0aGlzLm5leHQodSksci5uZXh0KGQpfSl9bmdPbkRlc3Ryb3koKXt0aGlzLnN0YXRlU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5jb21wbGV0ZSgpfX1yZXR1cm4gbi5JTklUPWdKLG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihxXyksaih4TSksaihDTSksaihvNCkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpO2Z1bmN0aW9uIERJZShuPXtzdGF0ZTp2b2lkIDB9LFt0LGVdKXtsZXR7c3RhdGU6aX09bjtyZXR1cm57c3RhdGU6ZShpLHQpLGFjdGlvbjp0fX12YXIgQUllPVtkSix7cHJvdmlkZTpDQSx1c2VFeGlzdGluZzpkSn1dLENlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyB1bntjb25zdHJ1Y3RvcihlLGkscil7c3VwZXIoKSx0aGlzLmFjdGlvbnNPYnNlcnZlcj1pLHRoaXMucmVkdWNlck1hbmFnZXI9cix0aGlzLnNvdXJjZT1lfXNlbGVjdChlLC4uLmkpe3JldHVybiB2dC5jYWxsKG51bGwsZSwuLi5pKSh0aGlzKX1saWZ0KGUpe2xldCBpPW5ldyBuKHRoaXMsdGhpcy5hY3Rpb25zT2JzZXJ2ZXIsdGhpcy5yZWR1Y2VyTWFuYWdlcik7cmV0dXJuIGkub3BlcmF0b3I9ZSxpfWRpc3BhdGNoKGUpe3RoaXMuYWN0aW9uc09ic2VydmVyLm5leHQoZSl9bmV4dChlKXt0aGlzLmFjdGlvbnNPYnNlcnZlci5uZXh0KGUpfWVycm9yKGUpe3RoaXMuYWN0aW9uc09ic2VydmVyLmVycm9yKGUpfWNvbXBsZXRlKCl7dGhpcy5hY3Rpb25zT2JzZXJ2ZXIuY29tcGxldGUoKX1hZGRSZWR1Y2VyKGUsaSl7dGhpcy5yZWR1Y2VyTWFuYWdlci5hZGRSZWR1Y2VyKGUsaSl9cmVtb3ZlUmVkdWNlcihlKXt0aGlzLnJlZHVjZXJNYW5hZ2VyLnJlbW92ZVJlZHVjZXIoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooQ0EpLGoocV8pLGooeEEpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxJSWU9W0NlXTtmdW5jdGlvbiB2dChuLHQsLi4uZSl7cmV0dXJuIGZ1bmN0aW9uKHIpe2xldCBvO2lmKCJzdHJpbmciPT10eXBlb2Ygbil7bGV0IHM9W3QsLi4uZV0uZmlsdGVyKEJvb2xlYW4pO289ci5waXBlKGZ1bmN0aW9uKC4uLm4pe2xldCB0PW4ubGVuZ3RoO2lmKDA9PT10KXRocm93IG5ldyBFcnJvcigibGlzdCBvZiBwcm9wZXJ0aWVzIGNhbm5vdCBiZSBlbXB0eS4iKTtyZXR1cm4gTChlPT57bGV0IGk9ZTtmb3IobGV0IHI9MDtyPHQ7cisrKXtsZXQgbz1pPy5bbltyXV07aWYoISh0eXBlb2YgbzwidSIpKXJldHVybjtpPW99cmV0dXJuIGl9KX0obiwuLi5zKSl9ZWxzZXtpZigiZnVuY3Rpb24iIT10eXBlb2Ygbil0aHJvdyBuZXcgVHlwZUVycm9yKGBVbmV4cGVjdGVkIHR5cGUgJyR7dHlwZW9mIG59JyBpbiBzZWxlY3Qgb3BlcmF0b3IsIGV4cGVjdGVkICdzdHJpbmcnIG9yICdmdW5jdGlvbidgKTtvPXIucGlwZShMKHM9Pm4ocyx0KSkpfXJldHVybiBvLnBpcGUoeWkoKSl9fXZhciBhND0iaHR0cHM6Ly9uZ3J4LmlvL2d1aWRlL3N0b3JlL2NvbmZpZ3VyYXRpb24vcnVudGltZS1jaGVja3MiO2Z1bmN0aW9uIHBKKG4pe3JldHVybiB2b2lkIDA9PT1ufWZ1bmN0aW9uIGhKKG4pe3JldHVybiBudWxsPT09bn1mdW5jdGlvbiBNSihuKXtyZXR1cm4gQXJyYXkuaXNBcnJheShuKX1mdW5jdGlvbiB3SihuKXtyZXR1cm4ib2JqZWN0Ij09dHlwZW9mIG4mJm51bGwhPT1ufWZ1bmN0aW9uIG40KG4pe3JldHVybiJmdW5jdGlvbiI9PXR5cGVvZiBufWZ1bmN0aW9uIGZKKG4sdCl7cmV0dXJuIG49PT10fWZ1bmN0aW9uIEhJZShuLHQsZSl7Zm9yKGxldCBpPTA7aTxuLmxlbmd0aDtpKyspaWYoIWUobltpXSx0W2ldKSlyZXR1cm4hMDtyZXR1cm4hMX1mdW5jdGlvbiBTSihuLHQ9ZkosZT1mSil7bGV0IG8saT1udWxsLHI9bnVsbDtyZXR1cm57bWVtb2l6ZWQ6ZnVuY3Rpb24oKXtpZih2b2lkIDAhPT1vKXJldHVybiBvLnJlc3VsdDtpZighaSlyZXR1cm4gcj1uLmFwcGx5KG51bGwsYXJndW1lbnRzKSxpPWFyZ3VtZW50cyxyO2lmKCFISWUoYXJndW1lbnRzLGksdCkpcmV0dXJuIHI7bGV0IHU9bi5hcHBseShudWxsLGFyZ3VtZW50cyk7cmV0dXJuIGk9YXJndW1lbnRzLGUocix1KT9yOihyPXUsdSl9LHJlc2V0OmZ1bmN0aW9uKCl7aT1udWxsLHI9bnVsbH0sc2V0UmVzdWx0OmZ1bmN0aW9uKHUpe289e3Jlc3VsdDp1fX0sY2xlYXJSZXN1bHQ6ZnVuY3Rpb24oKXtvPXZvaWQgMH19fWZ1bmN0aW9uIEooLi4ubil7cmV0dXJuIGZ1bmN0aW9uKG4sdD17c3RhdGVGbjpVSWV9KXtyZXR1cm4gZnVuY3Rpb24oLi4uZSl7bGV0IGk9ZTtpZihBcnJheS5pc0FycmF5KGlbMF0pKXtsZXRbdSwuLi5kXT1pO2k9Wy4uLnUsLi4uZF19bGV0IHI9aS5zbGljZSgwLGkubGVuZ3RoLTEpLG89aVtpLmxlbmd0aC0xXSxzPXIuZmlsdGVyKHU9PnUucmVsZWFzZSYmImZ1bmN0aW9uIj09dHlwZW9mIHUucmVsZWFzZSksYT1uKGZ1bmN0aW9uKC4uLnUpe3JldHVybiBvLmFwcGx5KG51bGwsdSl9KSxsPVNKKGZ1bmN0aW9uKHUsZCl7cmV0dXJuIHQuc3RhdGVGbi5hcHBseShudWxsLFt1LHIsZCxhXSl9KTtyZXR1cm4gT2JqZWN0LmFzc2lnbihsLm1lbW9pemVkLHtyZWxlYXNlOmZ1bmN0aW9uKCl7bC5yZXNldCgpLGEucmVzZXQoKSxzLmZvckVhY2godT0+dS5yZWxlYXNlKCkpfSxwcm9qZWN0b3I6YS5tZW1vaXplZCxzZXRSZXN1bHQ6bC5zZXRSZXN1bHQsY2xlYXJSZXN1bHQ6bC5jbGVhclJlc3VsdH0pfX0oU0opKC4uLm4pfWZ1bmN0aW9uIFVJZShuLHQsZSxpKXtpZih2b2lkIDA9PT1lKXtsZXQgbz10Lm1hcChzPT5zKG4pKTtyZXR1cm4gaS5tZW1vaXplZC5hcHBseShudWxsLG8pfWxldCByPXQubWFwKG89Pm8obixlKSk7cmV0dXJuIGkubWVtb2l6ZWQuYXBwbHkobnVsbCxbLi4ucixlXSl9ZnVuY3Rpb24gTXIobil7cmV0dXJuIEoodD0+e2xldCBlPXRbbl07cmV0dXJuIHRDKCkmJiEobiBpbiB0KSYmY29uc29sZS53YXJuKGBAbmdyeC9zdG9yZTogVGhlIGZlYXR1cmUgbmFtZSAiJHtufSIgZG9lcyBub3QgZXhpc3QgaW4gdGhlIHN0YXRlLCB0aGVyZWZvcmUgY3JlYXRlRmVhdHVyZVNlbGVjdG9yIGNhbm5vdCBhY2Nlc3MgaXQuICBCZSBzdXJlIGl0IGlzIGltcG9ydGVkIGluIGEgbG9hZGVkIG1vZHVsZSB1c2luZyBTdG9yZU1vZHVsZS5mb3JSb290KCcke259JywgLi4uKSBvciBTdG9yZU1vZHVsZS5mb3JGZWF0dXJlKCcke259JywgLi4uKS4gIElmIHRoZSBkZWZhdWx0IHN0YXRlIGlzIGludGVuZGVkIHRvIGJlIHVuZGVmaW5lZCwgYXMgaXMgdGhlIGNhc2Ugd2l0aCByb3V0ZXIgc3RhdGUsIHRoaXMgZGV2ZWxvcG1lbnQtb25seSB3YXJuaW5nIG1lc3NhZ2UgY2FuIGJlIGlnbm9yZWQuYCksZX0sdD0+dCl9ZnVuY3Rpb24gakllKG4sdCl7cmV0dXJuIHQgaW5zdGFuY2VvZiBwZT9uLmdldCh0KTp0fWZ1bmN0aW9uIEdJZShuLHQsZSl7cmV0dXJuIGUubWFwKChpLHIpPT57aWYodFtyXWluc3RhbmNlb2YgcGUpe2xldCBvPW4uZ2V0KHRbcl0pO3JldHVybntrZXk6aS5rZXkscmVkdWNlckZhY3Rvcnk6by5yZWR1Y2VyRmFjdG9yeT9vLnJlZHVjZXJGYWN0b3J5OkZtLG1ldGFSZWR1Y2VyczpvLm1ldGFSZWR1Y2Vycz9vLm1ldGFSZWR1Y2VyczpbXSxpbml0aWFsU3RhdGU6by5pbml0aWFsU3RhdGV9fXJldHVybiBpfSl9ZnVuY3Rpb24gV0llKG4sdCl7cmV0dXJuIHQubWFwKGk9PmkgaW5zdGFuY2VvZiBwZT9uLmdldChpKTppKX1mdW5jdGlvbiBFSihuKXtyZXR1cm4iZnVuY3Rpb24iPT10eXBlb2Ygbj9uKCk6bn1mdW5jdGlvbiBxSWUobix0KXtyZXR1cm4gbi5jb25jYXQodCl9ZnVuY3Rpb24gWUllKG4pe2lmKG4pdGhyb3cgbmV3IFR5cGVFcnJvcigiVGhlIHJvb3QgU3RvcmUgaGFzIGJlZW4gcHJvdmlkZWQgbW9yZSB0aGFuIG9uY2UuIEZlYXR1cmUgbW9kdWxlcyBzaG91bGQgcHJvdmlkZSBmZWF0dXJlIHN0YXRlcyBpbnN0ZWFkLiIpO3JldHVybiJndWFyZGVkIn1mdW5jdGlvbiBpNChuKXtPYmplY3QuZnJlZXplKG4pO2xldCB0PW40KG4pO3JldHVybiBPYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyhuKS5mb3JFYWNoKGU9PntpZighZS5zdGFydHNXaXRoKCJcdTAyNzUiKSYmZnVuY3Rpb24obix0KXtyZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG4sdCl9KG4sZSkmJighdHx8ImNhbGxlciIhPT1lJiYiY2FsbGVlIiE9PWUmJiJhcmd1bWVudHMiIT09ZSkpe2xldCBpPW5bZV07KHdKKGkpfHxuNChpKSkmJiFPYmplY3QuaXNGcm96ZW4oaSkmJmk0KGkpfX0pLG59ZnVuY3Rpb24gcjQobix0PVtdKXtyZXR1cm4ocEoobil8fGhKKG4pKSYmMD09PXQubGVuZ3RoP3twYXRoOlsicm9vdCJdLHZhbHVlOm59Ok9iamVjdC5rZXlzKG4pLnJlZHVjZSgoaSxyKT0+e2lmKGkpcmV0dXJuIGk7bGV0IG89bltyXTtyZXR1cm4gZnVuY3Rpb24obil7cmV0dXJuIG40KG4pJiZuLmhhc093blByb3BlcnR5KCJcdTAyNzVjbXAiKX0obyk/aTohKHBKKG8pfHxoSihvKXx8ZnVuY3Rpb24obil7cmV0dXJuIm51bWJlciI9PXR5cGVvZiBufShvKXx8ZnVuY3Rpb24obil7cmV0dXJuImJvb2xlYW4iPT10eXBlb2Ygbn0obyl8fGZ1bmN0aW9uKG4pe3JldHVybiJzdHJpbmciPT10eXBlb2Ygbn0obyl8fE1KKG8pKSYmKGZ1bmN0aW9uKG4pe2lmKCFmdW5jdGlvbihuKXtyZXR1cm4gd0oobikmJiFNSihuKX0obikpcmV0dXJuITE7bGV0IHQ9T2JqZWN0LmdldFByb3RvdHlwZU9mKG4pO3JldHVybiB0PT09T2JqZWN0LnByb3RvdHlwZXx8bnVsbD09PXR9KG8pP3I0KG8sWy4uLnQscl0pOntwYXRoOlsuLi50LHJdLHZhbHVlOm99KX0sITEpfWZ1bmN0aW9uIG1KKG4sdCl7aWYoITE9PT1uKXJldHVybjtsZXQgZT1uLnBhdGguam9pbigiLiIpLGk9bmV3IEVycm9yKGBEZXRlY3RlZCB1bnNlcmlhbGl6YWJsZSAke3R9IGF0ICIke2V9Ii4gJHthNH0jc3RyaWN0JHt0fXNlcmlhbGl6YWJpbGl0eWApO3Rocm93IGkudmFsdWU9bi52YWx1ZSxpLnVuc2VyaWFsaXphYmxlUGF0aD1lLGl9ZnVuY3Rpb24gWkllKG4pe3JldHVybiB0QygpP3tzdHJpY3RTdGF0ZVNlcmlhbGl6YWJpbGl0eTohMSxzdHJpY3RBY3Rpb25TZXJpYWxpemFiaWxpdHk6ITEsc3RyaWN0U3RhdGVJbW11dGFiaWxpdHk6ITAsc3RyaWN0QWN0aW9uSW1tdXRhYmlsaXR5OiEwLHN0cmljdEFjdGlvbldpdGhpbk5nWm9uZTohMSxzdHJpY3RBY3Rpb25UeXBlVW5pcXVlbmVzczohMSwuLi5ufTp7c3RyaWN0U3RhdGVTZXJpYWxpemFiaWxpdHk6ITEsc3RyaWN0QWN0aW9uU2VyaWFsaXphYmlsaXR5OiExLHN0cmljdFN0YXRlSW1tdXRhYmlsaXR5OiExLHN0cmljdEFjdGlvbkltbXV0YWJpbGl0eTohMSxzdHJpY3RBY3Rpb25XaXRoaW5OZ1pvbmU6ITEsc3RyaWN0QWN0aW9uVHlwZVVuaXF1ZW5lc3M6ITF9fWZ1bmN0aW9uIEpJZSh7c3RyaWN0QWN0aW9uU2VyaWFsaXphYmlsaXR5Om4sc3RyaWN0U3RhdGVTZXJpYWxpemFiaWxpdHk6dH0pe3JldHVybiBlPT5ufHx0P2Z1bmN0aW9uKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKGUsaSl7dC5hY3Rpb24oaSkmJm1KKHI0KGkpLCJhY3Rpb24iKTtsZXQgcj1uKGUsaSk7cmV0dXJuIHQuc3RhdGUoKSYmbUoocjQociksInN0YXRlIikscn19KGUse2FjdGlvbjppPT5uJiYhbDQoaSksc3RhdGU6KCk9PnR9KTplfWZ1bmN0aW9uICRJZSh7c3RyaWN0QWN0aW9uSW1tdXRhYmlsaXR5Om4sc3RyaWN0U3RhdGVJbW11dGFiaWxpdHk6dH0pe3JldHVybiBlPT5ufHx0P2Z1bmN0aW9uKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKGUsaSl7bGV0IHI9dC5hY3Rpb24oaSk/aTQoaSk6aSxvPW4oZSxyKTtyZXR1cm4gdC5zdGF0ZSgpP2k0KG8pOm99fShlLHthY3Rpb246aT0+biYmIWw0KGkpLHN0YXRlOigpPT50fSk6ZX1mdW5jdGlvbiBsNChuKXtyZXR1cm4gbi50eXBlLnN0YXJ0c1dpdGgoIkBuZ3J4Iil9ZnVuY3Rpb24gZTJlKHtzdHJpY3RBY3Rpb25XaXRoaW5OZ1pvbmU6bn0pe3JldHVybiB0PT5uP2Z1bmN0aW9uKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKGUsaSl7aWYodC5hY3Rpb24oaSkmJiFfdC5pc0luQW5ndWxhclpvbmUoKSl0aHJvdyBuZXcgRXJyb3IoYEFjdGlvbiAnJHtpLnR5cGV9JyBydW5uaW5nIG91dHNpZGUgTmdab25lLiAke2E0fSNzdHJpY3RhY3Rpb253aXRoaW5uZ3pvbmVgKTtyZXR1cm4gbihlLGkpfX0odCx7YWN0aW9uOmU9Pm4mJiFsNChlKX0pOnR9ZnVuY3Rpb24gdDJlKG4pe3JldHVyblt7cHJvdmlkZTp1Six1c2VWYWx1ZTpufSx7cHJvdmlkZTpjSix1c2VGYWN0b3J5Om4yZSxkZXBzOlt1Sl19LHtwcm92aWRlOmJNLGRlcHM6W2NKXSx1c2VGYWN0b3J5OlpJZX0se3Byb3ZpZGU6V18sbXVsdGk6ITAsZGVwczpbYk1dLHVzZUZhY3Rvcnk6JEllfSx7cHJvdmlkZTpXXyxtdWx0aTohMCxkZXBzOltiTV0sdXNlRmFjdG9yeTpKSWV9LHtwcm92aWRlOldfLG11bHRpOiEwLGRlcHM6W2JNXSx1c2VGYWN0b3J5OmUyZX1dfWZ1bmN0aW9uIFRKKCl7cmV0dXJuW3twcm92aWRlOnM0LG11bHRpOiEwLGRlcHM6W2JNXSx1c2VGYWN0b3J5OmkyZX1dfWZ1bmN0aW9uIG4yZShuKXtyZXR1cm4gbn1mdW5jdGlvbiBpMmUobil7aWYoIW4uc3RyaWN0QWN0aW9uVHlwZVVuaXF1ZW5lc3MpcmV0dXJuO2xldCB0PU9iamVjdC5lbnRyaWVzKHQ0KS5maWx0ZXIoKFssZV0pPT5lPjEpLm1hcCgoW2VdKT0+ZSk7aWYodC5sZW5ndGgpdGhyb3cgbmV3IEVycm9yKGBBY3Rpb24gdHlwZXMgYXJlIHJlZ2lzdGVyZWQgbW9yZSB0aGFuIG9uY2UsICR7dC5tYXAoZT0+YCIke2V9ImApLmpvaW4oIiwgIil9LiAke2E0fSNzdHJpY3RhY3Rpb250eXBldW5pcXVlbmVzc2ApfWZ1bmN0aW9uIHIyZShuLHQpe3JldHVyblt7cHJvdmlkZTpfSix1c2VGYWN0b3J5OllJZSxkZXBzOltbQ2UsbmV3IG5zLG5ldyB0bF1dfSx7cHJvdmlkZTp0Six1c2VWYWx1ZTp0LmluaXRpYWxTdGF0ZX0se3Byb3ZpZGU6bzQsdXNlRmFjdG9yeTpFSixkZXBzOlt0Sl19LHtwcm92aWRlOiQ1LHVzZVZhbHVlOm59LHtwcm92aWRlOnJKLHVzZUV4aXN0aW5nOm4gaW5zdGFuY2VvZiBwZT9uOiQ1fSx7cHJvdmlkZTp5SixkZXBzOltYbiwkNSxbbmV3IGowKHJKKV1dLHVzZUZhY3Rvcnk6akllfSx7cHJvdmlkZTphSix1c2VWYWx1ZTp0Lm1ldGFSZWR1Y2Vycz90Lm1ldGFSZWR1Y2VyczpbXX0se3Byb3ZpZGU6bEosZGVwczpbV18sYUpdLHVzZUZhY3Rvcnk6cUllfSx7cHJvdmlkZTpuSix1c2VWYWx1ZTp0LnJlZHVjZXJGYWN0b3J5P3QucmVkdWNlckZhY3Rvcnk6Rm19LHtwcm92aWRlOnZKLGRlcHM6W25KLGxKXSx1c2VGYWN0b3J5OkNKfSxiSWUsRUllLFRJZSxBSWUsSUllLHQyZSh0LnJ1bnRpbWVDaGVja3MpLFRKKCldfWZ1bmN0aW9uIG8yZShuLHQsZT17fSl7cmV0dXJuW3twcm92aWRlOm9KLG11bHRpOiEwLHVzZVZhbHVlOm4gaW5zdGFuY2VvZiBPYmplY3Q/e306ZX0se3Byb3ZpZGU6aUosbXVsdGk6ITAsdXNlVmFsdWU6e2tleTpuIGluc3RhbmNlb2YgT2JqZWN0P24ubmFtZTpuLHJlZHVjZXJGYWN0b3J5OmUgaW5zdGFuY2VvZiBwZXx8IWUucmVkdWNlckZhY3Rvcnk/Rm06ZS5yZWR1Y2VyRmFjdG9yeSxtZXRhUmVkdWNlcnM6ZSBpbnN0YW5jZW9mIHBlfHwhZS5tZXRhUmVkdWNlcnM/W106ZS5tZXRhUmVkdWNlcnMsaW5pdGlhbFN0YXRlOmUgaW5zdGFuY2VvZiBwZXx8IWUuaW5pdGlhbFN0YXRlP3ZvaWQgMDplLmluaXRpYWxTdGF0ZX19LHtwcm92aWRlOmJKLGRlcHM6W1huLG9KLGlKXSx1c2VGYWN0b3J5OkdJZX0se3Byb3ZpZGU6ZTQsbXVsdGk6ITAsdXNlVmFsdWU6biBpbnN0YW5jZW9mIE9iamVjdD9uLnJlZHVjZXI6dH0se3Byb3ZpZGU6c0osbXVsdGk6ITAsdXNlRXhpc3Rpbmc6dCBpbnN0YW5jZW9mIHBlP3Q6ZTR9LHtwcm92aWRlOnhKLG11bHRpOiEwLGRlcHM6W1huLGU0LFtuZXcgajAoc0opXV0sdXNlRmFjdG9yeTpXSWV9LFRKKCldfXZhciBNTT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhKXt9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGoocV8pLGooeE0pLGooQ00pLGooQ2UpLGooX0osOCksaihzNCw4KSl9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLHdBPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIsbyxzKXt0aGlzLmZlYXR1cmVzPWUsdGhpcy5mZWF0dXJlUmVkdWNlcnM9aSx0aGlzLnJlZHVjZXJNYW5hZ2VyPXI7bGV0IGE9ZS5tYXAoKGwsYyk9PntsZXQgZD1pLnNoaWZ0KClbY107cmV0dXJuey4uLmwscmVkdWNlcnM6ZCxpbml0aWFsU3RhdGU6RUoobC5pbml0aWFsU3RhdGUpfX0pO3IuYWRkRmVhdHVyZXMoYSl9bmdPbkRlc3Ryb3koKXt0aGlzLnJlZHVjZXJNYW5hZ2VyLnJlbW92ZUZlYXR1cmVzKHRoaXMuZmVhdHVyZXMpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKGJKKSxqKHhKKSxqKHhBKSxqKE1NKSxqKHM0LDgpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksd3I9KCgpPT57Y2xhc3MgbntzdGF0aWMgZm9yUm9vdChlLGk9e30pe3JldHVybntuZ01vZHVsZTpNTSxwcm92aWRlcnM6Wy4uLnIyZShlLGkpXX19c3RhdGljIGZvckZlYXR1cmUoZSxpLHI9e30pe3JldHVybntuZ01vZHVsZTp3QSxwcm92aWRlcnM6Wy4uLm8yZShlLGkscildfX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpO2Z1bmN0aW9uIFNlKC4uLm4pe3JldHVybntyZWR1Y2VyOm4ucG9wKCksdHlwZXM6bi5tYXAoaT0+aS50eXBlKX19ZnVuY3Rpb24gdnIobiwuLi50KXtsZXQgZT1uZXcgTWFwO2ZvcihsZXQgaSBvZiB0KWZvcihsZXQgciBvZiBpLnR5cGVzKXtsZXQgbz1lLmdldChyKTtlLnNldChyLG8/KGEsbCk9PmkucmVkdWNlcihvKGEsbCksbCk6aS5yZWR1Y2VyKX1yZXR1cm4gZnVuY3Rpb24oaT1uLHIpe2xldCBvPWUuZ2V0KHIudHlwZSk7cmV0dXJuIG8/byhpLHIpOml9fXZhciBzMmU9e2Rpc3BhdGNoOiEwLHVzZUVmZmVjdHNFcnJvckhhbmRsZXI6ITB9LEVBPSJfX0BuZ3J4L2VmZmVjdHNfY3JlYXRlX18iO2Z1bmN0aW9uIGNyKG4sdCl7bGV0IGU9bigpLGk9ey4uLnMyZSwuLi50fTtyZXR1cm4gT2JqZWN0LmRlZmluZVByb3BlcnR5KGUsRUEse3ZhbHVlOml9KSxlfWZ1bmN0aW9uIGEyZShuKXtyZXR1cm4gT2JqZWN0LmdldE93blByb3BlcnR5TmFtZXMobikuZmlsdGVyKGk9PiEoIW5baV18fCFuW2ldLmhhc093blByb3BlcnR5KEVBKSkmJm5baV1bRUFdLmhhc093blByb3BlcnR5KCJkaXNwYXRjaCIpKS5tYXAoaT0+KHtwcm9wZXJ0eU5hbWU6aSwuLi5uW2ldW0VBXX0pKX1mdW5jdGlvbiB1NChuKXtyZXR1cm4gT2JqZWN0LmdldFByb3RvdHlwZU9mKG4pfXZhciBJSj0iX19AbmdyeC9lZmZlY3RzX18iO2Z1bmN0aW9uIGwyZShuKXtyZXR1cm4gTUEodTJlLHU0KShuKX1mdW5jdGlvbiB1MmUobil7cmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBuLmNvbnN0cnVjdG9yLmhhc093blByb3BlcnR5KElKKX0obik/bi5jb25zdHJ1Y3RvcltJSl06W119ZnVuY3Rpb24gUEoobix0LGU9MTApe3JldHVybiBuLnBpcGUoZm8oaT0+KHQmJnQuaGFuZGxlRXJyb3IoaSksZTw9MT9uOlBKKG4sdCxlLTEpKSkpfXZhciBQbz0oKCk9PntjbGFzcyBuIGV4dGVuZHMgdW57Y29uc3RydWN0b3IoZSl7c3VwZXIoKSxlJiYodGhpcy5zb3VyY2U9ZSl9bGlmdChlKXtsZXQgaT1uZXcgbjtyZXR1cm4gaS5zb3VyY2U9dGhpcyxpLm9wZXJhdG9yPWUsaX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihDTSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpO2Z1bmN0aW9uIGlpKC4uLm4pe3JldHVybiBZZSh0PT5uLnNvbWUoZT0+InN0cmluZyI9PXR5cGVvZiBlP2U9PT10LnR5cGU6ZS50eXBlPT09dC50eXBlKSl9dmFyIFJKPW5ldyBwZSgiQG5ncngvZWZmZWN0cyBJbnRlcm5hbCBSb290IEd1YXJkIiksU0E9bmV3IHBlKCJAbmdyeC9lZmZlY3RzIFVzZXIgUHJvdmlkZWQgRWZmZWN0cyIpLGM0PW5ldyBwZSgiQG5ncngvZWZmZWN0cyBJbnRlcm5hbCBSb290IEVmZmVjdHMiKSxPSj1uZXcgcGUoIkBuZ3J4L2VmZmVjdHMgUm9vdCBFZmZlY3RzIiksREo9bmV3IHBlKCJAbmdyeC9lZmZlY3RzIEludGVybmFsIEZlYXR1cmUgRWZmZWN0cyIpLGtKPW5ldyBwZSgiQG5ncngvZWZmZWN0cyBGZWF0dXJlIEVmZmVjdHMiKSxmMmU9bmV3IHBlKCJAbmdyeC9lZmZlY3RzIEVmZmVjdHMgRXJyb3IgSGFuZGxlciIse3Byb3ZpZGVkSW46InJvb3QiLGZhY3Rvcnk6KCk9PlBKfSksRko9IkBuZ3J4L2VmZmVjdHMvaW5pdCI7ZnVuY3Rpb24gdzJlKG4pe3JldHVybiBkNChuLCJuZ3J4T25Jbml0RWZmZWN0cyIpfWZ1bmN0aW9uIGQ0KG4sdCl7cmV0dXJuIG4mJnQgaW4gbiYmImZ1bmN0aW9uIj09dHlwZW9mIG5bdF19YmUoRkopO3ZhciBOSj0oKCk9PntjbGFzcyBuIGV4dGVuZHMga2V7Y29uc3RydWN0b3IoZSxpKXtzdXBlcigpLHRoaXMuZXJyb3JIYW5kbGVyPWUsdGhpcy5lZmZlY3RzRXJyb3JIYW5kbGVyPWl9YWRkRWZmZWN0cyhlKXt0aGlzLm5leHQoZSl9dG9BY3Rpb25zKCl7cmV0dXJuIHRoaXMucGlwZSh4MSh1NCkseG4oZT0+ZS5waXBlKHgxKFMyZSkpKSx4bihlPT57bGV0IGk9ZS5waXBlKHkxKG89PmZ1bmN0aW9uKG4sdCl7cmV0dXJuIGU9PntsZXQgaT1mdW5jdGlvbihuLHQsZSl7bGV0IGk9dTQobikuY29uc3RydWN0b3IubmFtZSxyPWZ1bmN0aW9uKG4pe3JldHVybltsMmUsYTJlXS5yZWR1Y2UoKGUsaSk9PmUuY29uY2F0KGkobikpLFtdKX0obikubWFwKCh7cHJvcGVydHlOYW1lOm8sZGlzcGF0Y2g6cyx1c2VFZmZlY3RzRXJyb3JIYW5kbGVyOmF9KT0+e2xldCBsPSJmdW5jdGlvbiI9PXR5cGVvZiBuW29dP25bb10oKTpuW29dLGM9YT9lKGwsdCk6bDtyZXR1cm4hMT09PXM/Yy5waXBlKGx4KCkpOmMucGlwZShlbigobix0KT0+e24uc3Vic2NyaWJlKGp0KHQsZT0+e3QubmV4dChSbC5jcmVhdGVOZXh0KGUpKX0sKCk9Pnt0Lm5leHQoUmwuY3JlYXRlQ29tcGxldGUoKSksdC5jb21wbGV0ZSgpfSxlPT57dC5uZXh0KFJsLmNyZWF0ZUVycm9yKGUpKSx0LmNvbXBsZXRlKCl9KSl9KSkucGlwZShMKGQ9Pih7ZWZmZWN0Om5bb10sbm90aWZpY2F0aW9uOmQscHJvcGVydHlOYW1lOm8sc291cmNlTmFtZTppLHNvdXJjZUluc3RhbmNlOm59KSkpfSk7cmV0dXJuIEp0KC4uLnIpfShlLG4sdCk7cmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBkNChuLCJuZ3J4T25SdW5FZmZlY3RzIil9KGUpP2UubmdyeE9uUnVuRWZmZWN0cyhpKTppfX0odGhpcy5lcnJvckhhbmRsZXIsdGhpcy5lZmZlY3RzRXJyb3JIYW5kbGVyKShvKSksTChvPT4oZnVuY3Rpb24obix0KXtpZigiTiI9PT1uLm5vdGlmaWNhdGlvbi5raW5kKXtsZXQgZT1uLm5vdGlmaWNhdGlvbi52YWx1ZTshZnVuY3Rpb24obil7cmV0dXJuImZ1bmN0aW9uIiE9dHlwZW9mIG4mJm4mJm4udHlwZSYmInN0cmluZyI9PXR5cGVvZiBuLnR5cGV9KGUpJiZ0LmhhbmRsZUVycm9yKG5ldyBFcnJvcihgRWZmZWN0ICR7ZnVuY3Rpb24oe3Byb3BlcnR5TmFtZTpuLHNvdXJjZUluc3RhbmNlOnQsc291cmNlTmFtZTplfSl7bGV0IGk9ImZ1bmN0aW9uIj09dHlwZW9mIHRbbl07cmV0dXJuYCIke2V9LiR7U3RyaW5nKG4pfSR7aT8iKCkiOiIifSJgfShuKX0gZGlzcGF0Y2hlZCBhbiBpbnZhbGlkIGFjdGlvbjogJHtmdW5jdGlvbihuKXt0cnl7cmV0dXJuIEpTT04uc3RyaW5naWZ5KG4pfWNhdGNoe3JldHVybiBufX0oZSl9YCkpfX0obyx0aGlzLmVycm9ySGFuZGxlciksby5ub3RpZmljYXRpb24pKSxZZShvPT4iTiI9PT1vLmtpbmQmJm51bGwhPW8udmFsdWUpLGVuKChuLHQpPT57bi5zdWJzY3JpYmUoanQodCxlPT5iTihlLHQpKSl9KSk7cmV0dXJuIEp0KGksZS5waXBlKFF0KDEpLFllKHcyZSksTChvPT5vLm5ncnhPbkluaXRFZmZlY3RzKCkpKSl9KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooUXMpLGooZjJlKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCk7ZnVuY3Rpb24gUzJlKG4pe3JldHVybiBmdW5jdGlvbihuKXtyZXR1cm4gZDQobiwibmdyeE9uSWRlbnRpZnlFZmZlY3RzIil9KG4pP24ubmdyeE9uSWRlbnRpZnlFZmZlY3RzKCk6IiJ9dmFyIExKPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXt0aGlzLmVmZmVjdFNvdXJjZXM9ZSx0aGlzLnN0b3JlPWksdGhpcy5lZmZlY3RzU3Vic2NyaXB0aW9uPW51bGx9Z2V0IGlzU3RhcnRlZCgpe3JldHVybiEhdGhpcy5lZmZlY3RzU3Vic2NyaXB0aW9ufXN0YXJ0KCl7dGhpcy5lZmZlY3RzU3Vic2NyaXB0aW9ufHwodGhpcy5lZmZlY3RzU3Vic2NyaXB0aW9uPXRoaXMuZWZmZWN0U291cmNlcy50b0FjdGlvbnMoKS5zdWJzY3JpYmUodGhpcy5zdG9yZSkpfW5nT25EZXN0cm95KCl7dGhpcy5lZmZlY3RzU3Vic2NyaXB0aW9uJiYodGhpcy5lZmZlY3RzU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5lZmZlY3RzU3Vic2NyaXB0aW9uPW51bGwpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKE5KKSxqKENlKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksQko9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsKXt0aGlzLnNvdXJjZXM9ZSxpLnN0YXJ0KCksby5mb3JFYWNoKGM9PmUuYWRkRWZmZWN0cyhjKSksci5kaXNwYXRjaCh7dHlwZTpGSn0pfWFkZEVmZmVjdHMoZSl7dGhpcy5zb3VyY2VzLmFkZEVmZmVjdHMoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooTkopLGooTEopLGooQ2UpLGooT0opLGooTU0sOCksaih3QSw4KSxqKFJKLDgpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksVDJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIsbyl7aS5mb3JFYWNoKHM9PnMuZm9yRWFjaChhPT5lLmFkZEVmZmVjdHMoYSkpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihCSiksaihrSiksaihNTSw4KSxqKHdBLDgpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCkscm89KCgpPT57Y2xhc3MgbntzdGF0aWMgZm9yRmVhdHVyZShlPVtdKXtyZXR1cm57bmdNb2R1bGU6VDJlLHByb3ZpZGVyczpbZSx7cHJvdmlkZTpESixtdWx0aTohMCx1c2VWYWx1ZTplfSx7cHJvdmlkZTpTQSxtdWx0aTohMCx1c2VWYWx1ZTpbXX0se3Byb3ZpZGU6a0osbXVsdGk6ITAsdXNlRmFjdG9yeTpBSixkZXBzOltYbixESixTQV19XX19c3RhdGljIGZvclJvb3QoZT1bXSl7cmV0dXJue25nTW9kdWxlOkJKLHByb3ZpZGVyczpbZSx7cHJvdmlkZTpjNCx1c2VWYWx1ZTpbZV19LHtwcm92aWRlOlJKLHVzZUZhY3Rvcnk6QTJlLGRlcHM6W1tMSixuZXcgbnMsbmV3IHRsXSxbYzQsbmV3IHczXV19LHtwcm92aWRlOlNBLG11bHRpOiEwLHVzZVZhbHVlOltdfSx7cHJvdmlkZTpPSix1c2VGYWN0b3J5OkFKLGRlcHM6W1huLGM0LFNBXX1dfX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpO2Z1bmN0aW9uIEFKKG4sdCxlKXtsZXQgaT1bXTtmb3IobGV0IHIgb2YgdClpLnB1c2goLi4ucik7Zm9yKGxldCByIG9mIGUpaS5wdXNoKC4uLnIpO3JldHVybiBmdW5jdGlvbihuLHQpe3JldHVybiB0Lm1hcChlPT5uLmdldChlKSl9KG4saSl9ZnVuY3Rpb24gQTJlKG4sdCl7aWYoKDEhPT10Lmxlbmd0aHx8MCE9PXRbMF0ubGVuZ3RoKSYmbil0aHJvdyBuZXcgVHlwZUVycm9yKCJFZmZlY3RzTW9kdWxlLmZvclJvb3QoKSBjYWxsZWQgdHdpY2UuIEZlYXR1cmUgbW9kdWxlcyBzaG91bGQgdXNlIEVmZmVjdHNNb2R1bGUuZm9yRmVhdHVyZSgpIGluc3RlYWQuIik7cmV0dXJuImd1YXJkZWQifXZhciBoaT0oKCk9PihmdW5jdGlvbihuKXtuW24uVU5LTk9XTj0wXT0iVU5LTk9XTiIsbltuLkVYUEVSSU1FTlRTPTFdPSJFWFBFUklNRU5UUyIsbltuLkVYUEVSSU1FTlQ9Ml09IkVYUEVSSU1FTlQiLG5bbi5DT01QQVJFX0VYUEVSSU1FTlQ9M109IkNPTVBBUkVfRVhQRVJJTUVOVCIsbltuLk5PVF9TRVQ9NF09Ik5PVF9TRVQiLG5bbi5GTEFHUz01XT0iRkxBR1MifShoaXx8KGhpPXt9KSksaGkpKSgpLFRBPSJkZWZhdWx0RXhwZXJpbWVudElkIixZXz0oKCk9PihmdW5jdGlvbihuKXtuW24uRVhQRVJJTUVOVFM9MF09IkVYUEVSSU1FTlRTIixuW24uREFTSEJPQVJEPTFdPSJEQVNIQk9BUkQifShZX3x8KFlfPXt9KSksWV8pKSgpO2Z1bmN0aW9uIFNNKG4pe3JldHVybiBuLnNwbGl0KCIsIikubWFwKHQ9PntsZXQgZT10LmluZGV4T2YoIjoiKTtpZihlPDApdGhyb3cgbmV3IEVycm9yKGBFeHBlY3QgY29sb24gZGVsaW1pdGluZyBuYW1lIGFuZCBJRDogJHt0fWApO2xldCBpPXQuc2xpY2UoMCxlKSxyPXQuc2xpY2UoZSsxKTtpZighcil0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdCBpZCB0byBiZSBub24tZmFsc3k6ICR7dH1gKTtyZXR1cm57bmFtZTppLGlkOnJ9fSl9ZnVuY3Rpb24gVkoobil7cmV0dXJuIG4ubWFwKCh7YWxpYXM6dCxpZDplfSk9PmAke3R9OiR7ZX1gKS5qb2luKCIsIil9ZnVuY3Rpb24gd00obix0KXtzd2l0Y2gobil7Y2FzZSBoaS5FWFBFUklNRU5UOnJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwodCwiZXhwZXJpbWVudElkIik/W3QuZXhwZXJpbWVudElkXTpbVEFdO2Nhc2UgaGkuQ09NUEFSRV9FWFBFUklNRU5UOnJldHVybiBTTSh0LmV4cGVyaW1lbnRJZHMpLm1hcCgoe2lkOml9KT0+aSk7ZGVmYXVsdDpyZXR1cm4gbnVsbH19ZnVuY3Rpb24gUHMobix0KXtpZighbnx8IXQpcmV0dXJuIG49PT10O2lmKG4ucm91dGVLaW5kIT09dC5yb3V0ZUtpbmQpcmV0dXJuITE7bGV0IGU9d00obi5yb3V0ZUtpbmQsbi5wYXJhbXMpLGk9d00odC5yb3V0ZUtpbmQsdC5wYXJhbXMpO2lmKG51bGw9PT1lfHxudWxsPT09aSlyZXR1cm4gZT09PWk7aWYoZS5sZW5ndGghPT1pLmxlbmd0aClyZXR1cm4hMTtsZXQgcj1pLnNvcnQoKTtyZXR1cm4gZS5zb3J0KCkuZXZlcnkoKG8scyk9PnJbc109PT1vKX1mdW5jdGlvbiBwNChuKXtzd2l0Y2gobil7Y2FzZSBoaS5FWFBFUklNRU5UUzpyZXR1cm4gWV8uRVhQRVJJTUVOVFM7Y2FzZSBoaS5FWFBFUklNRU5UOmNhc2UgaGkuQ09NUEFSRV9FWFBFUklNRU5UOnJldHVybiBZXy5EQVNIQk9BUkQ7Y2FzZSBoaS5VTktOT1dOOmNhc2UgaGkuTk9UX1NFVDpjYXNlIGhpLkZMQUdTOnJldHVybiBudWxsfX1mdW5jdGlvbiBEQShuLHQsZSl7bGV0IGk9cDQobik7cmV0dXJuIG51bGwhPT1pJiYhZS5zb21lKHI9PnIuZGVlcExpbmtHcm91cD09PWkmJnIubmFtZXNwYWNlSWQ9PT10KX12YXIgakpfZ2V0SHJlZj0oKT0+d2luZG93LmxvY2F0aW9uLmhyZWYsTm09KCgpPT57Y2xhc3MgbntnZXRIcmVmKCl7cmV0dXJuIGpKX2dldEhyZWYoKX1nZXRTZWFyY2goKXtsZXQgZT1uZXcgVVJMU2VhcmNoUGFyYW1zKHdpbmRvdy5sb2NhdGlvbi5zZWFyY2gpLGk9W107cmV0dXJuIGUuZm9yRWFjaCgocixvKT0+e2kucHVzaCh7a2V5Om8sdmFsdWU6cn0pfSksaX1nZXRIYXNoKCl7cmV0dXJuIHdpbmRvdy5sb2NhdGlvbi5oYXNofWdldFBhdGgoKXtyZXR1cm4gd2luZG93LmxvY2F0aW9uLnBhdGhuYW1lfWdldEhpc3RvcnlTdGF0ZSgpe3JldHVybiB3aW5kb3cuaGlzdG9yeS5zdGF0ZX1yZXBsYWNlU3RhdGVVcmwoZSl7d2luZG93Lmhpc3RvcnkucmVwbGFjZVN0YXRlKHdpbmRvdy5oaXN0b3J5LnN0YXRlLCIiLGUpfXB1c2hTdGF0ZVVybChlKXt3aW5kb3cuaGlzdG9yeS5wdXNoU3RhdGUobnVsbCwiIixlKX1yZXBsYWNlU3RhdGVEYXRhKGUpe3dpbmRvdy5oaXN0b3J5LnJlcGxhY2VTdGF0ZShlLCIiKX1vblBvcFN0YXRlKCl7cmV0dXJuIF9pKHdpbmRvdywicG9wc3RhdGUiKS5waXBlKEwoZT0+KHtwYXRobmFtZTp0aGlzLmdldFBhdGgoKSxzdGF0ZTplLnN0YXRlfSkpKX1nZXRSZXNvbHZlZFBhdGgoZSl7cmV0dXJuIG5ldyBVUkwoZSxqSl9nZXRIcmVmKCkpLnBhdGhuYW1lfWdldEZ1bGxQYXRoKGUsaSxyKXtsZXQgbz10aGlzLmdldFJlc29sdmVkUGF0aChlKSxzPSIiO3JldHVybiBpLmxlbmd0aCYmKHM9Ij8iK2Z1bmN0aW9uKG4pe2xldCB0PW5ldyBVUkxTZWFyY2hQYXJhbXM7Zm9yKGxldHtrZXk6ZSx2YWx1ZTppfW9mIG4pdC5hcHBlbmQoZSxpKTtyZXR1cm4gdH0oaSkudG9TdHJpbmcoKSksYCR7b30ke3N9JHtyP3RoaXMuZ2V0SGFzaCgpOiIifWB9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLExtPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5hcHBSb290PXRoaXMuZ2V0QXBwUm9vdEZyb21NZXRhRWxlbWVudChlKX1nZXRBcHBSb290RnJvbU1ldGFFbGVtZW50KGUpe2xldCBpPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJ2hlYWQgbWV0YVtuYW1lPSJ0Yi1yZWxhdGl2ZS1yb290Il0nKTtpZighaSlyZXR1cm4iLyI7bGV0e3BhdGhuYW1lOnJ9PW5ldyBVUkwoaS5jb250ZW50LGUuZ2V0SHJlZigpKTtyZXR1cm4gci5yZXBsYWNlKC9cLyokLywiLyIpfWdldEFic1BhdGhuYW1lV2l0aEFwcFJvb3QoZSl7cmV0dXJuIHRoaXMuYXBwUm9vdC5zbGljZSgwLC0xKStlfWdldEFwcFJvb3RsZXNzUGF0aG5hbWUoZSl7cmV0dXJuIGUuc3RhcnRzV2l0aCh0aGlzLmFwcFJvb3QpPyIvIitlLnNsaWNlKHRoaXMuYXBwUm9vdC5sZW5ndGgpOmV9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooTm0pKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxYXz0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7cHJvdmlkZXJzOltObV19KSxufSkoKSxRXz0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7cHJvdmlkZXJzOltMbV0saW1wb3J0czpbWF9dfSksbn0pKCksbTQ9bmV3IHBlKCJbQXBwIFJvdXRpbmddIERpcnR5IFVwZGF0ZXMiKSxBQT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuZGlydHlVcGRhdGVzU2VsZWN0b3JGYWN0b3JpZXM9ZX1nZXREaXJ0eVVwZGF0ZXNTZWxlY3RvcnMoKXtyZXR1cm4gdGhpcy5kaXJ0eVVwZGF0ZXNTZWxlY3RvckZhY3Rvcmllcz8/W119c3RhdGljIHJlZ2lzdGVyRGlydHlVcGRhdGVzKGUpe3JldHVybntuZ01vZHVsZTpuLHByb3ZpZGVyczpbe3Byb3ZpZGU6bTQsbXVsdGk6ITAsdXNlRmFjdG9yeTplfV19fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKG00LDgpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksR0o9YmUoIltBcHAgUm91dGluZ10gRGlzY2FyZGluZyBVbnNhdmVkIFVwZGF0ZXMiKSxLXz1iZSgiW0FwcCBSb3V0aW5nXSBTdGF0ZSBSZWh5ZHJhdGVkIEZyb20gVXJsIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksSUE9YmUoIltBcHAgUm91dGluZ10gUm91dGUgQ29uZmlnIExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLGc0PWJlKCJbQXBwIFJvdXRpbmddIEluIEFwcCBOYXZpZ2F0aW9uIFJlcXVlc3RlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFBBPWJlKCJbQXBwIFJvdXRpbmddIEluIEFwcCBOYXZpZ2F0aW5nIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksSmw9YmUoIltBcHAgUm91dGluZ10gSW4gQXBwIE5hdmlnYXRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLF80PW5ldyBwZSgiW0FwcCBSb3V0aW5nXSBQcm9ncmFtbWF0aWNhbCBOYXZpZ2F0aW9uIFByb3ZpZGVyIiksUkE9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnByb3ZpZGVycz1uZXcgTWFwO2ZvcihsZXQgaSBvZiBlfHxbXSl7aWYodGhpcy5wcm92aWRlcnMuaGFzKGkuYWN0aW9uQ3JlYXRvci50eXBlKSl0aHJvdyBuZXcgUmFuZ2VFcnJvcihgIiR7aS5hY3Rpb25DcmVhdG9yLnR5cGV9IiBpcyBhbHJlYWR5IHJlZ2lzdGVyZWQgZm9yIG5hdi4gTXVsdGlwbGUgbmF2aWdhdGlvbnMgb24gc2FtZSBraWNrIGlzIG5vdCBhbGxvd2VkLmApO3RoaXMucHJvdmlkZXJzLnNldChpLmFjdGlvbkNyZWF0b3IudHlwZSxpLmxhbWJkYSl9fWdldE5hdmlnYXRpb24oZSl7bGV0IGk9dGhpcy5wcm92aWRlcnMuZ2V0KGUudHlwZSk7cmV0dXJuIGk/aShlKTpudWxsfXN0YXRpYyByZWdpc3RlclByb2dyYW1tYXRpY2FsTmF2aWdhdGlvbihlKXtyZXR1cm57bmdNb2R1bGU6bixwcm92aWRlcnM6W3twcm92aWRlOl80LG11bHRpOiEwLHVzZUZhY3Rvcnk6ZX1dfX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihfNCw4KSl9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpO2Z1bmN0aW9uIEVNKG4pe3JldHVybiBudWxsIT1uLnJvdXRlS2luZH1mdW5jdGlvbiBiNChuKXtyZXR1cm4gWEoobikubWFwKGU9PntsZXQgaT1lLnN0YXJ0c1dpdGgoIjoiKTtyZXR1cm4gaT97cGF0aFBhcnQ6ZSxpc1BhcmFtOiEwLHBhcmFtTmFtZTplLnNsaWNlKDEpfTp7cGF0aFBhcnQ6ZSxpc1BhcmFtOml9fSl9dmFyIFpfPWNsYXNze2NvbnN0cnVjdG9yKHQpe3RoaXMudmFsaWRhdGVDb25maWcodCksdGhpcy5wYXRoRnJhZ21lbnRzPWI0KHQucGF0aCksdGhpcy5wYXRoTWF0Y2hlcnM9dGhpcy5nZXRQYXRoTWF0Y2hlcnModGhpcy5wYXRoRnJhZ21lbnRzKX1zdGF0aWMgZ2V0TWF0Y2hlcih0KXtyZXR1cm4gRU0odCk/bmV3IFRNKHQpOmZ1bmN0aW9uKG4pe3JldHVybiB2b2lkIDAhPT1uLnJlZGlyZWN0aW9uUGF0aH0odCk/bmV3IHY0KHQpOm5ldyB5NCh0KX12YWxpZGF0ZUNvbmZpZyh7cGF0aDp0fSl7aWYoIXQuc3RhcnRzV2l0aCgiLyIpKXRocm93IG5ldyBSYW5nZUVycm9yKGBjb25maWcucGF0aCBzaG91bGQgc3RhcnQgd2l0aCAnLycuICR7dH1gKTtsZXQgZT0wO2Zvcig7KGU9dC5pbmRleE9mKCI6IixlKzEpKT49MDspe2lmKCIvIiE9PXRbZS0xXSl0aHJvdyBuZXcgUmFuZ2VFcnJvcihgY29uZmlnLnBhdGggcGFyYW1ldGVyIHNob3VsZCBjb21lIGFmdGVyICcvJy4gJHt0fWApO2lmKHZvaWQgMD09PXRbZSsxXXx8Ii8iPT09dFtlKzFdKXRocm93IG5ldyBSYW5nZUVycm9yKGBjb25maWcucGF0aCBwYXJhbWV0ZXIgc2hvdWxkIGhhdmUgbm9uLWVtcHR5IG5hbWUuICR7dH1gKX19Z2V0UGF0aE1hdGNoZXJzKHQpe3JldHVybiB0Lm1hcChlPT57bGV0e3BhdGhQYXJ0Oml9PWU7cmV0dXJuIGUuaXNQYXJhbT9yPT4oe2lzUGFyYW1QYXRoUGFydDohMCxwYXJ0TWF0Y2hlZDohMCxwYXJhbU5hbWU6ZS5wYXJhbU5hbWUscGFyYW1WYWx1ZTpyfSk6cj0+KHtpc1BhcmFtUGF0aFBhcnQ6ITEscGFydE1hdGNoZWQ6cj09PWl9KX0pfW1hdGNoKHQpe2xldCBlPXt9O2lmKHRoaXMucGF0aE1hdGNoZXJzLmxlbmd0aCE9PXQubGVuZ3RoKXJldHVybntyZXN1bHQ6ITF9O2xldCBpPTA7Zm9yKGxldCByIG9mIHRoaXMucGF0aE1hdGNoZXJzKXtsZXQgcz1yKHRbaSsrXSk7aWYoIXMucGFydE1hdGNoZWQpcmV0dXJue3Jlc3VsdDohMX07cy5pc1BhcmFtUGF0aFBhcnQmJihlPXsuLi5lLFtzLnBhcmFtTmFtZV06cy5wYXJhbVZhbHVlfSl9cmV0dXJue3Jlc3VsdDohMCxwYXJhbXM6ZSxwYXRoUGFydHM6dCxpc1JlZGlyZWN0aW9uOiExLHJlZGlyZWN0aW9uUXVlcnlQYXJhbXM6dm9pZCAwfX1tYXRjaEJ5UGFyYW1zKHQpe3JldHVybntyZXN1bHQ6ITAscGFyYW1zOnQscGF0aFBhcnRzOnRoaXMucmVwcm9qZWN0UGF0aEJ5UGFyYW1zKHRoaXMucGF0aEZyYWdtZW50cyx0KSxpc1JlZGlyZWN0aW9uOiExLHJlZGlyZWN0aW9uUXVlcnlQYXJhbXM6dm9pZCAwfX1yZXByb2plY3RQYXRoQnlQYXJhbXModCxlKXtsZXQgaT1bXTtmb3IobGV0IHIgb2YgdClpZihyLmlzUGFyYW0pe2xldHtwYXJhbU5hbWU6b309cjtpZighZS5oYXNPd25Qcm9wZXJ0eShvKSl0aHJvdyBuZXcgUmFuZ2VFcnJvcihgRmFpbGVkIHRvIHJlcHJvamVjdCBwYXJhbWV0ZXIuICIke299IiBwYXJhbWV0ZXIgc2hvdWxkIGJlIHByZXNlbnQuYCk7aS5wdXNoKGVbb10pfWVsc2UgaS5wdXNoKHIucGF0aFBhcnQpO3JldHVybiBpfX0sVE09Y2xhc3MgZXh0ZW5kcyBaX3tjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KSx0aGlzLmRlZmluaXRpb249dH19LHY0PWNsYXNzIGV4dGVuZHMgWl97Y29uc3RydWN0b3IodCl7c3VwZXIodCksdGhpcy5kZWZpbml0aW9uPXQsdGhpcy5yZWRpcmVjdGlvbkZyYWdtZW50cz1iNCh0LnJlZGlyZWN0aW9uUGF0aCl9bWF0Y2godCl7bGV0IGU9c3VwZXIubWF0Y2godCk7aWYoIWUucmVzdWx0KXJldHVybiBlO2xldCBpPXRoaXMucmVwcm9qZWN0UGF0aEJ5UGFyYW1zKHRoaXMucmVkaXJlY3Rpb25GcmFnbWVudHMsZS5wYXJhbXMpO3JldHVybntyZXN1bHQ6ITAscGFyYW1zOmUucGFyYW1zLHBhdGhQYXJ0czppLGlzUmVkaXJlY3Rpb246ITAscmVkaXJlY3Rpb25RdWVyeVBhcmFtczp2b2lkIDB9fX0seTQ9Y2xhc3MgZXh0ZW5kcyBaX3tjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KSx0aGlzLmRlZmluaXRpb249dH1tYXRjaCh0KXtsZXQgZT1zdXBlci5tYXRjaCh0KTtpZighZS5yZXN1bHQpcmV0dXJuIGU7bGV0e3BhdGhQYXJ0czppLHF1ZXJ5UGFyYW1zOnJ9PXRoaXMuZGVmaW5pdGlvbi5yZWRpcmVjdG9yKHQpO3JldHVybntyZXN1bHQ6ITAscGFyYW1zOmUucGFyYW1zLHBhdGhQYXJ0czppLGlzUmVkaXJlY3Rpb246ITAscmVkaXJlY3Rpb25RdWVyeVBhcmFtczpyfX19LERNPWNsYXNze2NvbnN0cnVjdG9yKHQsZT0zKXtpZih0aGlzLm1heFJlZGlyZWN0aW9uPWUsZTwwKXRocm93IG5ldyBSYW5nZUVycm9yKCJtYXhSZWRpcmVjdGlvbiBoYXMgdG8gYmUgbm9uLW5lZ2F0aXZlIG51bWJlciIpO3RoaXMudmFsaWRhdGVSb3V0ZUNvbmZpZ3ModCksdGhpcy5kZWZhdWx0Um91dGVDb25maWc9bnVsbCx0aGlzLnJvdXRlS2luZFRvQ29uY3JldGVDb25maWdNYXRjaGVycz1uZXcgTWFwLHRoaXMuY29uZmlnTWF0Y2hlcnM9W107Zm9yKGxldCBpIG9mIHQpe2xldCByPVpfLmdldE1hdGNoZXIoaSk7dGhpcy5jb25maWdNYXRjaGVycy5wdXNoKHIpLHIgaW5zdGFuY2VvZiBUTSYmKHRoaXMucm91dGVLaW5kVG9Db25jcmV0ZUNvbmZpZ01hdGNoZXJzLnNldChyLmRlZmluaXRpb24ucm91dGVLaW5kLHIpLHIuZGVmaW5pdGlvbi5kZWZhdWx0Um91dGUmJih0aGlzLmRlZmF1bHRSb3V0ZUNvbmZpZz1yKSl9fXZhbGlkYXRlUm91dGVDb25maWdzKHQpe2xldCBlPXQuZmlsdGVyKEVNKSxpPWUuZmlsdGVyKG89Pm8uZGVmYXVsdFJvdXRlKTtpZihpLmxlbmd0aD4xKXtsZXQgbz1pLm1hcCgoe3BhdGg6c30pPT5zKS5qb2luKCIsICIpO3Rocm93IG5ldyBSYW5nZUVycm9yKGBUaGVyZSBhcmUgbW9yZSB0aGFuIG9uZSBkZWZhdWx0Um91dGVzLiAke299YCl9aWYoMT09PWkubGVuZ3RoKXtsZXR7cGF0aDpvfT1pWzBdO2lmKEJvb2xlYW4oYjQobykuZmluZCgoe2lzUGFyYW06YX0pPT5hKSkpdGhyb3cgbmV3IFJhbmdlRXJyb3IoYEEgZGVmYXVsdFJvdXRlIGNhbm5vdCBoYXZlIGFueSBwYXJhbXMuICR7b31gKX1sZXQgcj1uZXcgU2V0O2ZvcihsZXR7cm91dGVLaW5kOm99b2YgZSl7aWYoci5oYXMobykpdGhyb3cgbmV3IFJhbmdlRXJyb3IoYE11bHRpcGxlIHJvdXRlIGNvbmZpZ3VyYXRpb24gZm9yIGtpbmQ6ICR7b30uIENvbmZpZ3VyYXRpb25zIHNob3VsZCBoYXZlIHVuaXF1ZSByb3V0ZUtpbmRzYCk7ci5hZGQobyl9fWdlbmVyYXRlQWN0aW9uKHQsZSl7cmV0dXJuIHQuYWN0aW9uR2VuZXJhdG9yP3QuYWN0aW9uR2VuZXJhdG9yKGUpOm51bGx9bWF0Y2godCl7aWYoIXQucGF0aG5hbWUuc3RhcnRzV2l0aCgiLyIpKXRocm93IG5ldyBSYW5nZUVycm9yKCdOYXZpZ2F0aW9uIGhhcyB0byBtYWRlIHdpdGggcGF0aG5hbWUgdGhhdCBzdGFydHMgd2l0aCAiLyInKTtsZXQgbyxlPVhKKHQucGF0aG5hbWUpLGk9MCxyPSExO2Zvcig7Oyl7bGV0IHM9ITE7Zm9yKGxldCBhIG9mIHRoaXMuY29uZmlnTWF0Y2hlcnMpe2xldCBsPWEubWF0Y2goZSk7aWYobC5yZXN1bHQpe3M9ITA7bGV0e3BhcmFtczpjLHBhdGhQYXJ0czp1LGlzUmVkaXJlY3Rpb246ZH09bDtpZihkKXtlPXUscj0hMCxvPWwucmVkaXJlY3Rpb25RdWVyeVBhcmFtczticmVha31pZighKGEgaW5zdGFuY2VvZiBUTSkpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIk5vIGNvbmNyZXRlIHJvdXRlIGRlZmluaXRpb24gYG1hdGNoYCByZXR1cm4gcmVkaXJlY3Rpb24iKTtsZXR7ZGVmaW5pdGlvbjpwfT1hLGg9e3JvdXRlS2luZDpwLnJvdXRlS2luZCxwYXJhbXM6YyxwYXRobmFtZTpZSih1KSxkZWVwTGlua1Byb3ZpZGVyOnAuZGVlcExpbmtQcm92aWRlcnx8bnVsbCxhY3Rpb246dGhpcy5nZW5lcmF0ZUFjdGlvbihwLHUpfTtyZXR1cm4gcj97Li4uaCxvcmlnaW5hdGVGcm9tUmVkaXJlY3Rpb246ITAscmVkaXJlY3Rpb25Pbmx5UXVlcnlQYXJhbXM6b306ey4uLmgsb3JpZ2luYXRlRnJvbVJlZGlyZWN0aW9uOiExfX19aWYociYmaSsrLCFzfHxpPnRoaXMubWF4UmVkaXJlY3Rpb24pYnJlYWt9aWYoaT50aGlzLm1heFJlZGlyZWN0aW9uKXRocm93IG5ldyBFcnJvcihgUG90ZW50aWFsIHJlZGlyZWN0aW9uIGxvb3AgKHJlZGlyZWN0aW5nIG1vcmUgdGhhbiAke3RoaXMubWF4UmVkaXJlY3Rpb259IHRpbWVzLiBQbGVhc2UgZG8gbm90IGhhdmUgY3ljbGVzIGluIHRoZSByb3V0ZXMuYCk7aWYodGhpcy5kZWZhdWx0Um91dGVDb25maWcpe2xldHtkZWZpbml0aW9uOnN9PXRoaXMuZGVmYXVsdFJvdXRlQ29uZmlnO3JldHVybntyb3V0ZUtpbmQ6cy5yb3V0ZUtpbmQsZGVlcExpbmtQcm92aWRlcjpzLmRlZXBMaW5rUHJvdmlkZXI/P251bGwscGF0aG5hbWU6cy5wYXRoLHBhcmFtczp7fSxvcmlnaW5hdGVGcm9tUmVkaXJlY3Rpb246ITAscmVkaXJlY3Rpb25Pbmx5UXVlcnlQYXJhbXM6dm9pZCAwLGFjdGlvbjp0aGlzLmdlbmVyYXRlQWN0aW9uKHMsZSl9fXJldHVybiBudWxsfW1hdGNoQnlSb3V0ZUtpbmQodCxlKXtsZXQgaT10aGlzLnJvdXRlS2luZFRvQ29uY3JldGVDb25maWdNYXRjaGVycy5nZXQodCk7aWYoIWkpdGhyb3cgbmV3IFJhbmdlRXJyb3IoYFJlcXVpcmVzIGNvbmZpZ3VyYXRpb24gZm9yIHJvdXRlS2luZDogJHt0fWApO2xldCByPWkubWF0Y2hCeVBhcmFtcyhlKTtyZXR1cm57cm91dGVLaW5kOnQscGFyYW1zOmUscGF0aG5hbWU6WUooci5wYXRoUGFydHMpLGRlZXBMaW5rUHJvdmlkZXI6aS5kZWZpbml0aW9uLmRlZXBMaW5rUHJvdmlkZXJ8fG51bGwsb3JpZ2luYXRlRnJvbVJlZGlyZWN0aW9uOiExLGFjdGlvbjp0aGlzLmdlbmVyYXRlQWN0aW9uKGkuZGVmaW5pdGlvbixyLnBhdGhQYXJ0cyl9fX07ZnVuY3Rpb24gWEoobil7cmV0dXJuIG4uc3BsaXQoIi8iKS5zbGljZSgxKX1mdW5jdGlvbiBZSihuKXtyZXR1cm4iLyIrbi5qb2luKCIvIil9dmFyIHg0PW5ldyBwZSgiW0FwcCBSb3V0aW5nXSBSb3V0ZSBDb25maWciKSxxYz0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe2lmKHRoaXMucm91dGVLaW5kVG9OZ0NvbXBvbmVudD1uZXcgTWFwLCFlKXJldHVybiB2b2lkKHRoaXMucm91dGVDb25maWdzPW5ldyBETShbXSkpO2xldCBpPVtdO2ZvcihsZXQgciBvZiBlKWZvcihsZXQgbyBvZiByKWkucHVzaChvKTt0aGlzLnJvdXRlQ29uZmlncz1uZXcgRE0oaSksaS5mb3JFYWNoKHI9PntFTShyKSYmdGhpcy5yb3V0ZUtpbmRUb05nQ29tcG9uZW50LnNldChyLnJvdXRlS2luZCxyLm5nQ29tcG9uZW50KX0pfWdldFJlZ2lzdGVyZWRSb3V0ZUtpbmRzKCl7cmV0dXJuIHRoaXMucm91dGVLaW5kVG9OZ0NvbXBvbmVudC5rZXlzKCl9Z2V0Um91dGVDb25maWdzKCl7cmV0dXJuIHRoaXMucm91dGVDb25maWdzfWdldE5nQ29tcG9uZW50QnlSb3V0ZUtpbmQoZSl7cmV0dXJuIHRoaXMucm91dGVLaW5kVG9OZ0NvbXBvbmVudC5nZXQoZSl8fG51bGx9c3RhdGljIHJlZ2lzdGVyUm91dGVzKGUpe3JldHVybntuZ01vZHVsZTpuLHByb3ZpZGVyczpbe3Byb3ZpZGU6eDQsbXVsdGk6ITAsdXNlRmFjdG9yeTplfV19fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKHg0LDgpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksT0E9ImFwcF9yb3V0aW5nIixBTT1NcihPQSksUmE9SihBTSxuPT5uLmFjdGl2ZVJvdXRlKSxaSj1KKEFNLG49Pm4ubmV4dFJvdXRlKSxKSj1KKEFNLG49Pm4uYWN0aXZlTmFtZXNwYWNlSWQpLCRKPUooQU0sbj0+bi5yZWh5ZHJhdGVkRGVlcExpbmtzKSxlJD1KKEFNLG49Pm4ucmVnaXN0ZXJlZFJvdXRlS2V5cykscXU9SihSYSxuPT5uP24ucm91dGVLaW5kOmhpLk5PVF9TRVQpLE00PUooUmEsbj0+bj9uLnBhcmFtczp7fSksV289SihxdSxNNCwobix0KT0+d00obix0KSksWXU9KEoocXUsTTQsKG4sdCk9PntpZihuIT09aGkuQ09NUEFSRV9FWFBFUklNRU5UKXJldHVybnt9O2xldCBpPWZ1bmN0aW9uKG4pe2xldCB0PW5ldyBNYXAsZT1TTShuLmV4cGVyaW1lbnRJZHMpO2ZvcihsZXR7aWQ6aSxuYW1lOnJ9b2YgZSlyJiZ0LnNldChpLHIpO3JldHVybiB0fSh0KTtyZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKGkuZW50cmllcygpKX0pLEoocXUsTTQsKG4sdCk9PntpZihuIT09aGkuQ09NUEFSRV9FWFBFUklNRU5UKXJldHVybnt9O2xldCBpPWZ1bmN0aW9uKG4pe2xldCB0PW5ldyBNYXAsZT1TTShuLmV4cGVyaW1lbnRJZHMpLGk9MDtmb3IobGV0e2lkOnIsbmFtZTpvfW9mIGUpaSsrLCF0LmhhcyhyKSYmdC5zZXQocix7YWxpYXNUZXh0Om8sYWxpYXNOdW1iZXI6aX0pO3JldHVybiB0fSh0KTtyZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKGkuZW50cmllcygpKX0pKSx3ND1iZSgiW0FwcCBSb3V0aW5nXSBFZmZlY3RzIEluaXQiKSxzYT0oKCk9PihmdW5jdGlvbihuKXtuW24uVU5DSEFOR0VEPTBdPSJVTkNIQU5HRUQiLG5bbi5ORVc9MV09Ik5FVyIsbltuLkZST01fSElTVE9SWT0yXT0iRlJPTV9ISVNUT1JZIn0oc2F8fChzYT17fSkpLHNhKSkoKSx0JD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhLGwpe3RoaXMuYWN0aW9ucyQ9ZSx0aGlzLnN0b3JlPWksdGhpcy5sb2NhdGlvbj1yLHRoaXMuZGlydHlVcGRhdGVzUmVnaXN0cnk9byx0aGlzLnJlZ2lzdHJ5PXMsdGhpcy5wcm9ncmFtbWF0aWNhbE5hdk1vZHVsZT1hLHRoaXMuYXBwUm9vdFByb3ZpZGVyPWwsdGhpcy5vbk5hdmlnYXRpb25SZXF1ZXN0ZWQkPXRoaXMuYWN0aW9ucyQucGlwZShpaShnNCksTChjPT4oe3BhdGhuYW1lOmMucGF0aG5hbWUuc3RhcnRzV2l0aCgiLyIpP3RoaXMuYXBwUm9vdFByb3ZpZGVyLmdldEFic1BhdGhuYW1lV2l0aEFwcFJvb3QoYy5wYXRobmFtZSk6dGhpcy5sb2NhdGlvbi5nZXRSZXNvbHZlZFBhdGgoYy5wYXRobmFtZSksb3B0aW9uczp7YnJvd3NlckluaXRpYXRlZDohMSxyZXBsYWNlU3RhdGU6Yy5yZXBsYWNlU3RhdGU/PyExLG5hbWVzcGFjZVVwZGF0ZTp7b3B0aW9uOmMucmVzZXROYW1lc3BhY2VkU3RhdGU/c2EuTkVXOnNhLlVOQ0hBTkdFRH19fSkpKSx0aGlzLmJvb3RzdHJhcFJlZHVjZXJzJD1jcigoKT0+dGhpcy5hY3Rpb25zJC5waXBlKGlpKHc0KSxMKCgpPT5JQSh7cm91dGVLaW5kczpuZXcgU2V0KHRoaXMucmVnaXN0cnkuZ2V0UmVnaXN0ZXJlZFJvdXRlS2luZHMoKSl9KSkpKSx0aGlzLm9uSW5pdCQ9dGhpcy5hY3Rpb25zJC5waXBlKGlpKHc0KSkucGlwZShPbCgwKSxMKCgpPT57bGV0IGM9dGhpcy5sb2NhdGlvbi5nZXRIaXN0b3J5U3RhdGUoKT8ubmFtZXNwYWNlSWQsdT12b2lkIDA9PT1jP3tvcHRpb246c2EuTkVXfTp7b3B0aW9uOnNhLkZST01fSElTVE9SWSxuYW1lc3BhY2VJZDpjfTtyZXR1cm57cGF0aG5hbWU6dGhpcy5sb2NhdGlvbi5nZXRQYXRoKCksb3B0aW9uczp7YnJvd3NlckluaXRpYXRlZDohMCxyZXBsYWNlU3RhdGU6ITAsbmFtZXNwYWNlVXBkYXRlOnV9fX0pKSx0aGlzLm9uUG9wU3RhdGUkPXRoaXMubG9jYXRpb24ub25Qb3BTdGF0ZSgpLnBpcGUoTChjPT4oe3BhdGhuYW1lOmMucGF0aG5hbWUsb3B0aW9uczp7YnJvd3NlckluaXRpYXRlZDohMCxyZXBsYWNlU3RhdGU6ITAsbmFtZXNwYWNlVXBkYXRlOnZvaWQgMD09PWMuc3RhdGU/Lm5hbWVzcGFjZUlkP3tvcHRpb246c2EuVU5DSEFOR0VEfTp7b3B0aW9uOnNhLkZST01fSElTVE9SWSxuYW1lc3BhY2VJZDpjLnN0YXRlLm5hbWVzcGFjZUlkfX19KSkpLHRoaXMudXNlckluaXROYXZSb3V0ZSQ9SnQodGhpcy5vbk5hdmlnYXRpb25SZXF1ZXN0ZWQkLHRoaXMub25Jbml0JCx0aGlzLm9uUG9wU3RhdGUkKS5waXBlKEwoYz0+e2lmKCFjLnBhdGhuYW1lLnN0YXJ0c1dpdGgoIi8iKSl0aHJvdyBuZXcgRXJyb3IoYFtBcHAgcm91dGluZ10gcGF0aG5hbWUgbXVzdCBzdGFydCB3aXRoICcvJy4gR290OiAke2MucGF0aG5hbWV9YCk7cmV0dXJuey4uLmMscGF0aG5hbWU6dGhpcy5hcHBSb290UHJvdmlkZXIuZ2V0QXBwUm9vdGxlc3NQYXRobmFtZShjLnBhdGhuYW1lKX19KSxMKGM9Pih7cm91dGVNYXRjaDp0aGlzLnJvdXRlQ29uZmlncy5tYXRjaChjKSxvcHRpb25zOmMub3B0aW9uc30pKSksdGhpcy5wcm9ncmFtbWF0aWNhbE5hdlJvdXRlJD10aGlzLmFjdGlvbnMkLnBpcGUoTChjPT50aGlzLnByb2dyYW1tYXRpY2FsTmF2TW9kdWxlLmdldE5hdmlnYXRpb24oYykpLFllKGM9Pm51bGwhPT1jKSxMKGM9PntsZXQgZix1PWMse3JlcGxhY2VTdGF0ZTpkPSExLHJlc2V0TmFtZXNwYWNlZFN0YXRlOnAscm91dGVLaW5kOmh9PXU7cmV0dXJuIGY9dS5yb3V0ZUtpbmQ9PT1oaS5DT01QQVJFX0VYUEVSSU1FTlQ/e2V4cGVyaW1lbnRJZHM6VkoodS5yb3V0ZVBhcmFtcy5hbGlhc0FuZEV4cGVyaW1lbnRJZHMpfTp1LnJvdXRlUGFyYW1zLHtyZXBsYWNlU3RhdGU6ZCxyb3V0ZUtpbmQ6aCxyb3V0ZVBhcmFtczpmLHJlc2V0TmFtZXNwYWNlZFN0YXRlOnB9fSksTCgoe3JlcGxhY2VTdGF0ZTpjLHJvdXRlS2luZDp1LHJvdXRlUGFyYW1zOmQscmVzZXROYW1lc3BhY2VkU3RhdGU6cH0pPT4oe3JvdXRlTWF0Y2g6dGhpcy5yb3V0ZUNvbmZpZ3M/dGhpcy5yb3V0ZUNvbmZpZ3MubWF0Y2hCeVJvdXRlS2luZCh1LGQpOm51bGwsb3B0aW9uczp7cmVwbGFjZVN0YXRlOmMsYnJvd3NlckluaXRpYXRlZDohMSxuYW1lc3BhY2VVcGRhdGU6e29wdGlvbjpwP3NhLk5FVzpzYS5VTkNIQU5HRUR9fX0pKSksdGhpcy52YWxpZGF0ZWRSb3V0ZU1hdGNoJD1KdCh0aGlzLnVzZXJJbml0TmF2Um91dGUkLHRoaXMucHJvZ3JhbW1hdGljYWxOYXZSb3V0ZSQpLnBpcGUoWWUoKHtyb3V0ZU1hdGNoOmN9KT0+Qm9vbGVhbihjKSksTCgoe3JvdXRlTWF0Y2g6YyxvcHRpb25zOnV9KT0+KHtyb3V0ZU1hdGNoOmMsb3B0aW9uczp1fSkpKSx0aGlzLm5hdmlnYXRlJD1jcigoKT0+dGhpcy52YWxpZGF0ZWRSb3V0ZU1hdGNoJC5waXBlKFd0KHRoaXMuc3RvcmUuc2VsZWN0KFJhKSkseG4oKFtkLHBdKT0+e2xldCBoPW51bGwhPT1wJiZQcyhwLGQucm91dGVNYXRjaCksZj10aGlzLmRpcnR5VXBkYXRlc1JlZ2lzdHJ5LmdldERpcnR5VXBkYXRlc1NlbGVjdG9ycygpO3JldHVybiBofHwhZi5sZW5ndGg/WHQoZCk6bHIodGhpcy5kaXJ0eVVwZGF0ZXNSZWdpc3RyeS5nZXREaXJ0eVVwZGF0ZXNTZWxlY3RvcnMoKS5tYXAobT0+dGhpcy5zdG9yZS5zZWxlY3QobSkucGlwZShRdCgxKSkpKS5waXBlKEwobT0+dm9pZCAwIT09bVswXS5leHBlcmltZW50SWRzJiZtWzBdLmV4cGVyaW1lbnRJZHMubGVuZ3RoPjApLFllKG09PntpZihtKXtsZXQgeD13aW5kb3cuY29uZmlybSgiWW91IGhhdmUgdW5zYXZlZCBlZGl0cywgYXJlIHlvdSBzdXJlIHlvdSB3YW50IHRvIGRpc2NhcmQgdGhlbT8iKTtyZXR1cm4geCYmdGhpcy5zdG9yZS5kaXNwYXRjaChHSigpKSx4fXJldHVybiEwfSksTCgoKT0+ZCkpfSksV3QodGhpcy5zdG9yZS5zZWxlY3QoJEopKSxrdCgoW3tyb3V0ZU1hdGNoOmQsb3B0aW9uczpwfSxoXSk9PntpZighcC5icm93c2VySW5pdGlhdGVkfHwhZC5kZWVwTGlua1Byb3ZpZGVyfHxwLm5hbWVzcGFjZVVwZGF0ZS5vcHRpb249PT1zYS5GUk9NX0hJU1RPUlkmJiFEQShkLnJvdXRlS2luZCxwLm5hbWVzcGFjZVVwZGF0ZS5uYW1lc3BhY2VJZCxoKSlyZXR1cm47bGV0IGY9ZC5vcmlnaW5hdGVGcm9tUmVkaXJlY3Rpb24mJmQucmVkaXJlY3Rpb25Pbmx5UXVlcnlQYXJhbXM/ZC5yZWRpcmVjdGlvbk9ubHlRdWVyeVBhcmFtczp0aGlzLmxvY2F0aW9uLmdldFNlYXJjaCgpLG09ZC5kZWVwTGlua1Byb3ZpZGVyLmRlc2VyaWFsaXplUXVlcnlQYXJhbXMoZik7dGhpcy5zdG9yZS5kaXNwYXRjaChLXyh7cm91dGVLaW5kOmQucm91dGVLaW5kLHBhcnRpYWxTdGF0ZTptfSkpfSksa3QoKFt7cm91dGVNYXRjaDpkfV0pPT57ZC5hY3Rpb24mJnRoaXMuc3RvcmUuZGlzcGF0Y2goZC5hY3Rpb24pfSksdWkoKFt7cm91dGVNYXRjaDpkLG9wdGlvbnM6cH1dKT0+bnVsbD09PWQuZGVlcExpbmtQcm92aWRlcj9YdCh7cm91dGU6e3JvdXRlS2luZDpkLnJvdXRlS2luZCxwYXJhbXM6ZC5wYXJhbXN9LHBhdGhuYW1lOmQucGF0aG5hbWUscXVlcnlQYXJhbXM6W10sb3B0aW9uczpwfSk6ZC5kZWVwTGlua1Byb3ZpZGVyLnNlcmlhbGl6ZVN0YXRlVG9RdWVyeVBhcmFtcyh0aGlzLnN0b3JlKS5waXBlKEwoKGgsZik9Pih7cm91dGU6e3JvdXRlS2luZDpkLnJvdXRlS2luZCxwYXJhbXM6ZC5wYXJhbXN9LHBhdGhuYW1lOmQucGF0aG5hbWUscXVlcnlQYXJhbXM6aCxvcHRpb25zOjA9PT1mP3A6ey4uLnAsbmFtZXNwYWNlVXBkYXRlOntvcHRpb246c2EuVU5DSEFOR0VEfSxyZXBsYWNlU3RhdGU6ITB9fSkpKSksa3QoKHtyb3V0ZTpkfSk9Pnt0aGlzLnN0b3JlLmRpc3BhdGNoKFBBKHthZnRlcjpkfSkpfSksSHIoMCkpLnBpcGUoV3QodGhpcy5zdG9yZS5zZWxlY3QoUmEpKSxMKChbZCxwXSk9PntsZXQgaD1udWxsPT09cHx8bnVsbD09PWQucm91dGV8fFBzKHAsZC5yb3V0ZSk7cmV0dXJuey4uLmQscHJlc2VydmVIYXNoOmh9fSksa3QoKHtwcmVzZXJ2ZUhhc2g6ZCxwYXRobmFtZTpwLHF1ZXJ5UGFyYW1zOmgsb3B0aW9uczpmfSk9PnsoZnVuY3Rpb24obix0KXtyZXR1cm4gbi5wYXRobmFtZT09PXQucGF0aG5hbWUmJm4ucXVlcnlQYXJhbXMubGVuZ3RoPT09dC5xdWVyeVBhcmFtcy5sZW5ndGgmJm4ucXVlcnlQYXJhbXMuZXZlcnkoKGUsaSk9PntsZXQgcj10LnF1ZXJ5UGFyYW1zW2ldO3JldHVybiBlLmtleT09PXIua2V5JiZlLnZhbHVlPT09ci52YWx1ZX0pfSkoe3BhdGhuYW1lOnAscXVlcnlQYXJhbXM6aH0se3BhdGhuYW1lOnRoaXMuYXBwUm9vdFByb3ZpZGVyLmdldEFwcFJvb3RsZXNzUGF0aG5hbWUodGhpcy5sb2NhdGlvbi5nZXRQYXRoKCkpLHF1ZXJ5UGFyYW1zOnRoaXMubG9jYXRpb24uZ2V0U2VhcmNoKCl9KXx8KGYucmVwbGFjZVN0YXRlP3RoaXMubG9jYXRpb24ucmVwbGFjZVN0YXRlVXJsKHRoaXMuYXBwUm9vdFByb3ZpZGVyLmdldEFic1BhdGhuYW1lV2l0aEFwcFJvb3QodGhpcy5sb2NhdGlvbi5nZXRGdWxsUGF0aChwLGgsZCkpKTp0aGlzLmxvY2F0aW9uLnB1c2hTdGF0ZVVybCh0aGlzLmFwcFJvb3RQcm92aWRlci5nZXRBYnNQYXRobmFtZVdpdGhBcHBSb290KHRoaXMubG9jYXRpb24uZ2V0RnVsbFBhdGgocCxoLGQpKSkpfSkpLnBpcGUoV3QodGhpcy5zdG9yZS5zZWxlY3QoUmEpLHRoaXMuc3RvcmUuc2VsZWN0KEpKKSksTCgoW3tyb3V0ZTpkLG9wdGlvbnM6cH0saCxmXSk9PntsZXQgbT1mdW5jdGlvbihuLHQsZSl7cmV0dXJuIHQubmFtZXNwYWNlVXBkYXRlLm9wdGlvbj09PXNhLkZST01fSElTVE9SWT90Lm5hbWVzcGFjZVVwZGF0ZS5uYW1lc3BhY2VJZDpudWxsPT1lfHx0Lm5hbWVzcGFjZVVwZGF0ZS5vcHRpb249PT1zYS5ORVc/YCR7RGF0ZS5ub3coKS50b1N0cmluZygpfToke2Z1bmN0aW9uKCl7bGV0IG49bmV3IFVpbnQ4QXJyYXkoMzIpO2NyeXB0by5nZXRSYW5kb21WYWx1ZXMobik7bGV0IHQ9IiI7Zm9yKGxldCBlIG9mIG4pdCs9KGU+PjQpLnRvU3RyaW5nKDE2KTtyZXR1cm4gdH0oKX1gOmV9KDAscCxmKTtyZXR1cm4gdGhpcy5sb2NhdGlvbi5yZXBsYWNlU3RhdGVEYXRhKHsuLi50aGlzLmxvY2F0aW9uLmdldEhpc3RvcnlTdGF0ZSgpLG5hbWVzcGFjZUlkOm19KSxKbCh7YmVmb3JlOmgsYWZ0ZXI6ZCxiZWZvcmVOYW1lc3BhY2VJZDpmLGFmdGVyTmFtZXNwYWNlSWQ6bX0pfSkpKSx0aGlzLnJvdXRlQ29uZmlncz1zLmdldFJvdXRlQ29uZmlncygpfW5ncnhPbkluaXRFZmZlY3RzKCl7cmV0dXJuIHc0KCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooUG8pLGooQ2UpLGooTm0pLGooQUEpLGoocWMpLGooUkEpLGooTG0pKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxrMmU9dnIoe2FjdGl2ZVJvdXRlOm51bGwsbmV4dFJvdXRlOm51bGwsYWN0aXZlTmFtZXNwYWNlSWQ6bnVsbCxyZWh5ZHJhdGVkRGVlcExpbmtzOltdLHJlZ2lzdGVyZWRSb3V0ZUtleXM6bmV3IFNldH0sU2UoUEEsKG4se2FmdGVyOnR9KT0+KHsuLi5uLG5leHRSb3V0ZTp0fSkpLFNlKEpsLChuLHthZnRlcjp0LGFmdGVyTmFtZXNwYWNlSWQ6ZX0pPT57bGV0IGk9bi5yZWh5ZHJhdGVkRGVlcExpbmtzO3JldHVybiBEQSh0LnJvdXRlS2luZCxlLGkpJiYoaT1bLi4uaV0saS5wdXNoKHtkZWVwTGlua0dyb3VwOnA0KHQucm91dGVLaW5kKSxuYW1lc3BhY2VJZDplfSkpLHsuLi5uLGFjdGl2ZVJvdXRlOnQsbmV4dFJvdXRlOm51bGwsYWN0aXZlTmFtZXNwYWNlSWQ6ZSxyZWh5ZHJhdGVkRGVlcExpbmtzOml9fSksU2UoSUEsKG4se3JvdXRlS2luZHM6dH0pPT4oey4uLm4scmVnaXN0ZXJlZFJvdXRlS2V5czp0fSkpKTtmdW5jdGlvbiBuJChuLHQpe3JldHVybiBrMmUobix0KX12YXIgSl89KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbQUEsUkFdLGltcG9ydHM6W3FjLHdyLmZvckZlYXR1cmUoT0EsbiQpLHJvLmZvckZlYXR1cmUoW3QkXSksUV8sWF9dfSksbn0pKCksaSQ9Il9fdGFiX18iLHIkPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLnRmU3RvcmFnZT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0Zi1zdG9yYWdlIiksZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGYtZ2xvYmFscyIpLnRmX2dsb2JhbHMuc2V0VXNlSGFzaCghMCksdGhpcy50ZlN0b3JhZ2UudGZfc3RvcmFnZS5taWdyYXRlTGVnYWN5VVJMU2NoZW1lKCl9Z2V0U3RyaW5nKGUpe3JldHVybiB0aGlzLnRmU3RvcmFnZS50Zl9zdG9yYWdlLmdldFN0cmluZyhlKX1zZXRTdHJpbmcoZSxpLHIpe3RoaXMudGZTdG9yYWdlLnRmX3N0b3JhZ2Uuc2V0U3RyaW5nKGUsaSxyKX1nZXRQbHVnaW5JZCgpe3JldHVybiB0aGlzLmdldFN0cmluZyhpJCl9c2V0UGx1Z2luSWQoZSxpKXt0aGlzLnNldFN0cmluZyhpJCxlLGkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxmaD1jbGFzc3t9LFM0PW5ldyBwZSgiW1BlcnNpc3RlbnQgU2V0dGluZ3NdIEdsb2JhbCBTZXR0aW5ncyIpLFNyPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5nbG9iYWxTZXR0aW5nU2VsZWN0b3JzPVtdLGUmJih0aGlzLmdsb2JhbFNldHRpbmdTZWxlY3RvcnM9ZS5tYXAoaT0+aSgpKSl9Z2V0R2xvYmFsU2V0dGluZ1NlbGVjdG9ycygpe3JldHVybiB0aGlzLmdsb2JhbFNldHRpbmdTZWxlY3RvcnM/P1tdfXN0YXRpYyBkZWZpbmVHbG9iYWxTZXR0aW5nKGUpe3JldHVybntuZ01vZHVsZTpuLHByb3ZpZGVyczpbe3Byb3ZpZGU6UzQsbXVsdGk6ITAsdXNlVmFsdWU6ZX1dfX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihTNCw4KSl9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLE9hPSgoKT0+KGZ1bmN0aW9uKG4pe24uQlJPV1NFUl9ERUZBVUxUPSJicm93c2VyX2RlZmF1bHQiLG4uTElHSFQ9ImxpZ2h0IixuLkRBUks9ImRhcmsifShPYXx8KE9hPXt9KSksT2EpKSgpLG8kPSJfdGJfZ2xvYmFsX3NldHRpbmdzLnRpbWVzZXJpZXMiLHMkPSJfdGJfZ2xvYmFsX3NldHRpbmdzIixhJD0ibm90aWZpY2F0aW9uTGFzdFJlYWRUaW1lc3RhbXAiLGtBPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLEZBPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLEU0PSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBGQXt1aVRvQmFja2VuZChlKXtsZXQgaT17fTtyZXR1cm4gdm9pZCAwIT09ZS5pZ25vcmVPdXRsaWVycyYmKGkuaWdub3JlT3V0bGllcnM9ZS5pZ25vcmVPdXRsaWVycyksdm9pZCAwIT09ZS5zY2FsYXJTbW9vdGhpbmcmJihpLnNjYWxhclNtb290aGluZz1lLnNjYWxhclNtb290aGluZyksdm9pZCAwIT09ZS50b29sdGlwU29ydCYmKGkudG9vbHRpcFNvcnQ9ZS50b29sdGlwU29ydCksdm9pZCAwIT09ZS5hdXRvUmVsb2FkJiYoaS5hdXRvUmVsb2FkPWUuYXV0b1JlbG9hZCksdm9pZCAwIT09ZS5hdXRvUmVsb2FkUGVyaW9kSW5NcyYmKGkuYXV0b1JlbG9hZFBlcmlvZEluTXM9ZS5hdXRvUmVsb2FkUGVyaW9kSW5Ncyksdm9pZCAwIT09ZS5wYWdlU2l6ZSYmKGkucGFnaW5hdGlvblNpemU9ZS5wYWdlU2l6ZSksdm9pZCAwIT09ZS50aGVtZU92ZXJyaWRlJiYoaS50aGVtZT1lLnRoZW1lT3ZlcnJpZGUpLHZvaWQgMCE9PWUubm90aWZpY2F0aW9uTGFzdFJlYWRUaW1lSW5NcyYmKGkubm90aWZpY2F0aW9uTGFzdFJlYWRUaW1lSW5Ncz1lLm5vdGlmaWNhdGlvbkxhc3RSZWFkVGltZUluTXMpLHZvaWQgMCE9PWUuc2lkZUJhcldpZHRoSW5QZXJjZW50JiYoaS5zaWRlQmFyV2lkdGhJblBlcmNlbnQ9ZS5zaWRlQmFyV2lkdGhJblBlcmNlbnQpLHZvaWQgMCE9PWUudGltZVNlcmllc1NldHRpbmdzUGFuZU9wZW5lZCYmKGkudGltZVNlcmllc1NldHRpbmdzUGFuZU9wZW5lZD1lLnRpbWVTZXJpZXNTZXR0aW5nc1BhbmVPcGVuZWQpLHZvaWQgMCE9PWUudGltZVNlcmllc0NhcmRNaW5XaWR0aCYmKGkudGltZVNlcmllc0NhcmRNaW5XaWR0aD1lLnRpbWVTZXJpZXNDYXJkTWluV2lkdGgpLHZvaWQgMCE9PWUuc3RlcFNlbGVjdG9yRW5hYmxlZCYmKGkuc3RlcFNlbGVjdG9yRW5hYmxlZD1lLnN0ZXBTZWxlY3RvckVuYWJsZWQpLHZvaWQgMCE9PWUucmFuZ2VTZWxlY3Rpb25FbmFibGVkJiYoaS5yYW5nZVNlbGVjdGlvbkVuYWJsZWQ9ZS5yYW5nZVNlbGVjdGlvbkVuYWJsZWQpLHZvaWQgMCE9PWUubGlua2VkVGltZUVuYWJsZWQmJihpLmxpbmtlZFRpbWVFbmFibGVkPWUubGlua2VkVGltZUVuYWJsZWQpLGl9YmFja2VuZFRvVWkoZSl7bGV0IGk9e307cmV0dXJuIGUuaGFzT3duUHJvcGVydHkoInNjYWxhclNtb290aGluZyIpJiYibnVtYmVyIj09dHlwZW9mIGUuc2NhbGFyU21vb3RoaW5nJiYoaS5zY2FsYXJTbW9vdGhpbmc9ZS5zY2FsYXJTbW9vdGhpbmcpLGUuaGFzT3duUHJvcGVydHkoImlnbm9yZU91dGxpZXJzIikmJiJib29sZWFuIj09dHlwZW9mIGUuaWdub3JlT3V0bGllcnMmJihpLmlnbm9yZU91dGxpZXJzPWUuaWdub3JlT3V0bGllcnMpLGUuaGFzT3duUHJvcGVydHkoInRvb2x0aXBTb3J0IikmJiJzdHJpbmciPT10eXBlb2YgZS50b29sdGlwU29ydCYmKGkudG9vbHRpcFNvcnQ9ZS50b29sdGlwU29ydCksZS5oYXNPd25Qcm9wZXJ0eSgiYXV0b1JlbG9hZCIpJiYiYm9vbGVhbiI9PXR5cGVvZiBlLmF1dG9SZWxvYWQmJihpLmF1dG9SZWxvYWQ9ZS5hdXRvUmVsb2FkKSxlLmhhc093blByb3BlcnR5KCJhdXRvUmVsb2FkUGVyaW9kSW5NcyIpJiYibnVtYmVyIj09dHlwZW9mIGUuYXV0b1JlbG9hZFBlcmlvZEluTXMmJihpLmF1dG9SZWxvYWRQZXJpb2RJbk1zPWUuYXV0b1JlbG9hZFBlcmlvZEluTXMpLGUuaGFzT3duUHJvcGVydHkoInBhZ2luYXRpb25TaXplIikmJiJudW1iZXIiPT10eXBlb2YgZS5wYWdpbmF0aW9uU2l6ZSYmKGkucGFnZVNpemU9ZS5wYWdpbmF0aW9uU2l6ZSksZS5oYXNPd25Qcm9wZXJ0eSgidGhlbWUiKSYmInN0cmluZyI9PXR5cGVvZiBlLnRoZW1lJiZuZXcgU2V0KE9iamVjdC52YWx1ZXMoT2EpKS5oYXMoZS50aGVtZSkmJihpLnRoZW1lT3ZlcnJpZGU9ZS50aGVtZSksZS5oYXNPd25Qcm9wZXJ0eSgibm90aWZpY2F0aW9uTGFzdFJlYWRUaW1lSW5NcyIpJiYibnVtYmVyIj09dHlwZW9mIGUubm90aWZpY2F0aW9uTGFzdFJlYWRUaW1lSW5NcyYmKGkubm90aWZpY2F0aW9uTGFzdFJlYWRUaW1lSW5Ncz1lLm5vdGlmaWNhdGlvbkxhc3RSZWFkVGltZUluTXMpLGUuaGFzT3duUHJvcGVydHkoInNpZGVCYXJXaWR0aEluUGVyY2VudCIpJiYibnVtYmVyIj09dHlwZW9mIGUuc2lkZUJhcldpZHRoSW5QZXJjZW50JiYoaS5zaWRlQmFyV2lkdGhJblBlcmNlbnQ9ZS5zaWRlQmFyV2lkdGhJblBlcmNlbnQpLGUuaGFzT3duUHJvcGVydHkoInRpbWVTZXJpZXNTZXR0aW5nc1BhbmVPcGVuZWQiKSYmImJvb2xlYW4iPT10eXBlb2YgZS50aW1lU2VyaWVzU2V0dGluZ3NQYW5lT3BlbmVkJiYoaS50aW1lU2VyaWVzU2V0dGluZ3NQYW5lT3BlbmVkPWUudGltZVNlcmllc1NldHRpbmdzUGFuZU9wZW5lZCksZS5oYXNPd25Qcm9wZXJ0eSgidGltZVNlcmllc0NhcmRNaW5XaWR0aCIpJiYibnVtYmVyIj09dHlwZW9mIGUudGltZVNlcmllc0NhcmRNaW5XaWR0aCYmKGkudGltZVNlcmllc0NhcmRNaW5XaWR0aD1lLnRpbWVTZXJpZXNDYXJkTWluV2lkdGgpLGUuaGFzT3duUHJvcGVydHkoInN0ZXBTZWxlY3RvckVuYWJsZWQiKSYmImJvb2xlYW4iPT10eXBlb2YgZS5zdGVwU2VsZWN0b3JFbmFibGVkJiYoaS5zdGVwU2VsZWN0b3JFbmFibGVkPWUuc3RlcFNlbGVjdG9yRW5hYmxlZCksZS5oYXNPd25Qcm9wZXJ0eSgicmFuZ2VTZWxlY3Rpb25FbmFibGVkIikmJiJib29sZWFuIj09dHlwZW9mIGUucmFuZ2VTZWxlY3Rpb25FbmFibGVkJiYoaS5yYW5nZVNlbGVjdGlvbkVuYWJsZWQ9ZS5yYW5nZVNlbGVjdGlvbkVuYWJsZWQpLGUuaGFzT3duUHJvcGVydHkoImxpbmtlZFRpbWVFbmFibGVkIikmJiJib29sZWFuIj09dHlwZW9mIGUubGlua2VkVGltZUVuYWJsZWQmJihpLmxpbmtlZFRpbWVFbmFibGVkPWUubGlua2VkVGltZUVuYWJsZWQpLGl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbigpe2xldCB0O3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4odHx8KHQ9cGkobikpKShpfHxuKX19KCksbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxsJD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuY29udmVydGVyPWV9c2V0U2V0dGluZ3MoZSl7cmV0dXJuIE9iamVjdC5rZXlzKGUpP3RoaXMuZ2V0U2V0dGluZ3MoKS5waXBlKGt0KGk9Pntsb2NhbFN0b3JhZ2Uuc2V0SXRlbShzJCxKU09OLnN0cmluZ2lmeSh0aGlzLmNvbnZlcnRlci51aVRvQmFja2VuZCh7Li4uaSwuLi5lfSkpKSxsb2NhbFN0b3JhZ2UucmVtb3ZlSXRlbShvJCksbG9jYWxTdG9yYWdlLnJlbW92ZUl0ZW0oYSQpfSksTCgoKT0+e30pKTplb31kZXNlcmlhbGl6ZShlKXt0cnl7cmV0dXJuIEpTT04ucGFyc2UoZSl9Y2F0Y2h7cmV0dXJue319fWdldFNldHRpbmdzKCl7bGV0IGU9bG9jYWxTdG9yYWdlLmdldEl0ZW0oYSQpO3JldHVybiBYdCh7Li4udGhpcy5jb252ZXJ0ZXIuYmFja2VuZFRvVWkodGhpcy5kZXNlcmlhbGl6ZShlP0pTT04uc3RyaW5naWZ5KHtub3RpZmljYXRpb25MYXN0UmVhZFRpbWVJbk1zOk51bWJlcihlKX0pOiJ7fSIpKSwuLi50aGlzLmNvbnZlcnRlci5iYWNrZW5kVG9VaSh0aGlzLmRlc2VyaWFsaXplKGxvY2FsU3RvcmFnZS5nZXRJdGVtKG8kKT8/Int9IikpLC4uLnRoaXMuY29udmVydGVyLmJhY2tlbmRUb1VpKHRoaXMuZGVzZXJpYWxpemUobG9jYWxTdG9yYWdlLmdldEl0ZW0ocyQpPz8ie30iKSl9KX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihGQSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLGMkPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W3twcm92aWRlOmtBLHVzZUNsYXNzOmwkfSxFNCx7cHJvdmlkZTpGQSx1c2VFeGlzdGluZzpFNH1dfSksbn0pKCksWWM9YmUoIltQZXJzaXN0ZW50IFNldHRpbmdzXSBHbG9iYWwgU2V0dGluZ3MgTG9hZGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksdSQ9YmUoIltQZXJzaXN0ZW50IFNldHRpbmdzXSBFZmZlY3RzIEluaXQiKSxkJD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8pe3RoaXMuYWN0aW9ucyQ9ZSx0aGlzLnN0b3JlPWksdGhpcy5jb25maWdNb2R1bGU9cix0aGlzLmRhdGFTb3VyY2U9byx0aGlzLmluaXRpYWxpemVBbmRVcGRhdGVTZXR0aW5ncyQ9Y3IoKCk9PntsZXQgcz10aGlzLmFjdGlvbnMkLnBpcGUoaWkodSQpLHhuKCgpPT50aGlzLmRhdGFTb3VyY2UuZ2V0U2V0dGluZ3MoKSksa3QoYT0+e3RoaXMuc3RvcmUuZGlzcGF0Y2goWWMoe3BhcnRpYWxTZXR0aW5nczphfSkpfSksT2woMCkseG4oKCk9Pkp0KC4uLnRoaXMuY29uZmlnTW9kdWxlLmdldEdsb2JhbFNldHRpbmdTZWxlY3RvcnMoKS5tYXAobD0+dGhpcy5zdG9yZS5zZWxlY3QobCkucGlwZSh5aSgoYyx1KT0+e2xldCBkPU9iamVjdC52YWx1ZXMoYykscD1PYmplY3QudmFsdWVzKHUpO3JldHVybiBkLmxlbmd0aD09PXAubGVuZ3RoJiZkLmV2ZXJ5KChoLGYpPT5oPT09cFtmXSl9KSxaYSgxKSkpKSksVHMoKSk7cmV0dXJuIHMucGlwZShmdW5jdGlvbihuKXtyZXR1cm4gZW4oKHQsZSk9PntsZXQgaT1bXTtyZXR1cm4gdC5zdWJzY3JpYmUoanQoZSxyPT5pLnB1c2gociksKCk9PntlLm5leHQoaSksZS5jb21wbGV0ZSgpfSkpLG4uc3Vic2NyaWJlKGp0KGUsKCk9PntsZXQgcj1pO2k9W10sZS5uZXh0KHIpfSxNYykpLCgpPT57aT1udWxsfX0pfShzLnBpcGUoSHIoNTAwKSkpLHhuKGE9PntsZXQgbD17fTtmb3IobGV0IGMgb2YgYSlPYmplY3QuYXNzaWduKGwsYyk7cmV0dXJuIHRoaXMuZGF0YVNvdXJjZS5zZXRTZXR0aW5ncyhsKX0pKX0se2Rpc3BhdGNoOiExfSl9bmdyeE9uSW5pdEVmZmVjdHMoKXtyZXR1cm4gdSQoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihQbyksaihDZSksaihTciksaihrQSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLFQ0PSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W1NyXSxpbXBvcnRzOltyby5mb3JGZWF0dXJlKFtkJF0pLGMkXX0pLG59KSgpLExBPWNsYXNze30sQkE9Y2xhc3N7fSxobD1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLm5vcm1hbGl6ZWROYW1lcz1uZXcgTWFwLHRoaXMubGF6eVVwZGF0ZT1udWxsLHQ/dGhpcy5sYXp5SW5pdD0ic3RyaW5nIj09dHlwZW9mIHQ/KCk9Pnt0aGlzLmhlYWRlcnM9bmV3IE1hcCx0LnNwbGl0KCJcbiIpLmZvckVhY2goZT0+e2xldCBpPWUuaW5kZXhPZigiOiIpO2lmKGk+MCl7bGV0IHI9ZS5zbGljZSgwLGkpLG89ci50b0xvd2VyQ2FzZSgpLHM9ZS5zbGljZShpKzEpLnRyaW0oKTt0aGlzLm1heWJlU2V0Tm9ybWFsaXplZE5hbWUocixvKSx0aGlzLmhlYWRlcnMuaGFzKG8pP3RoaXMuaGVhZGVycy5nZXQobykucHVzaChzKTp0aGlzLmhlYWRlcnMuc2V0KG8sW3NdKX19KX06KCk9Pnt0aGlzLmhlYWRlcnM9bmV3IE1hcCxPYmplY3Qua2V5cyh0KS5mb3JFYWNoKGU9PntsZXQgaT10W2VdLHI9ZS50b0xvd2VyQ2FzZSgpOyJzdHJpbmciPT10eXBlb2YgaSYmKGk9W2ldKSxpLmxlbmd0aD4wJiYodGhpcy5oZWFkZXJzLnNldChyLGkpLHRoaXMubWF5YmVTZXROb3JtYWxpemVkTmFtZShlLHIpKX0pfTp0aGlzLmhlYWRlcnM9bmV3IE1hcH1oYXModCl7cmV0dXJuIHRoaXMuaW5pdCgpLHRoaXMuaGVhZGVycy5oYXModC50b0xvd2VyQ2FzZSgpKX1nZXQodCl7dGhpcy5pbml0KCk7bGV0IGU9dGhpcy5oZWFkZXJzLmdldCh0LnRvTG93ZXJDYXNlKCkpO3JldHVybiBlJiZlLmxlbmd0aD4wP2VbMF06bnVsbH1rZXlzKCl7cmV0dXJuIHRoaXMuaW5pdCgpLEFycmF5LmZyb20odGhpcy5ub3JtYWxpemVkTmFtZXMudmFsdWVzKCkpfWdldEFsbCh0KXtyZXR1cm4gdGhpcy5pbml0KCksdGhpcy5oZWFkZXJzLmdldCh0LnRvTG93ZXJDYXNlKCkpfHxudWxsfWFwcGVuZCh0LGUpe3JldHVybiB0aGlzLmNsb25lKHtuYW1lOnQsdmFsdWU6ZSxvcDoiYSJ9KX1zZXQodCxlKXtyZXR1cm4gdGhpcy5jbG9uZSh7bmFtZTp0LHZhbHVlOmUsb3A6InMifSl9ZGVsZXRlKHQsZSl7cmV0dXJuIHRoaXMuY2xvbmUoe25hbWU6dCx2YWx1ZTplLG9wOiJkIn0pfW1heWJlU2V0Tm9ybWFsaXplZE5hbWUodCxlKXt0aGlzLm5vcm1hbGl6ZWROYW1lcy5oYXMoZSl8fHRoaXMubm9ybWFsaXplZE5hbWVzLnNldChlLHQpfWluaXQoKXt0aGlzLmxhenlJbml0JiYodGhpcy5sYXp5SW5pdCBpbnN0YW5jZW9mIGhsP3RoaXMuY29weUZyb20odGhpcy5sYXp5SW5pdCk6dGhpcy5sYXp5SW5pdCgpLHRoaXMubGF6eUluaXQ9bnVsbCx0aGlzLmxhenlVcGRhdGUmJih0aGlzLmxhenlVcGRhdGUuZm9yRWFjaCh0PT50aGlzLmFwcGx5VXBkYXRlKHQpKSx0aGlzLmxhenlVcGRhdGU9bnVsbCkpfWNvcHlGcm9tKHQpe3QuaW5pdCgpLEFycmF5LmZyb20odC5oZWFkZXJzLmtleXMoKSkuZm9yRWFjaChlPT57dGhpcy5oZWFkZXJzLnNldChlLHQuaGVhZGVycy5nZXQoZSkpLHRoaXMubm9ybWFsaXplZE5hbWVzLnNldChlLHQubm9ybWFsaXplZE5hbWVzLmdldChlKSl9KX1jbG9uZSh0KXtsZXQgZT1uZXcgaGw7cmV0dXJuIGUubGF6eUluaXQ9dGhpcy5sYXp5SW5pdCYmdGhpcy5sYXp5SW5pdCBpbnN0YW5jZW9mIGhsP3RoaXMubGF6eUluaXQ6dGhpcyxlLmxhenlVcGRhdGU9KHRoaXMubGF6eVVwZGF0ZXx8W10pLmNvbmNhdChbdF0pLGV9YXBwbHlVcGRhdGUodCl7bGV0IGU9dC5uYW1lLnRvTG93ZXJDYXNlKCk7c3dpdGNoKHQub3Ape2Nhc2UiYSI6Y2FzZSJzIjpsZXQgaT10LnZhbHVlO2lmKCJzdHJpbmciPT10eXBlb2YgaSYmKGk9W2ldKSwwPT09aS5sZW5ndGgpcmV0dXJuO3RoaXMubWF5YmVTZXROb3JtYWxpemVkTmFtZSh0Lm5hbWUsZSk7bGV0IHI9KCJhIj09PXQub3A/dGhpcy5oZWFkZXJzLmdldChlKTp2b2lkIDApfHxbXTtyLnB1c2goLi4uaSksdGhpcy5oZWFkZXJzLnNldChlLHIpO2JyZWFrO2Nhc2UiZCI6bGV0IG89dC52YWx1ZTtpZihvKXtsZXQgcz10aGlzLmhlYWRlcnMuZ2V0KGUpO2lmKCFzKXJldHVybjtzPXMuZmlsdGVyKGE9Pi0xPT09by5pbmRleE9mKGEpKSwwPT09cy5sZW5ndGg/KHRoaXMuaGVhZGVycy5kZWxldGUoZSksdGhpcy5ub3JtYWxpemVkTmFtZXMuZGVsZXRlKGUpKTp0aGlzLmhlYWRlcnMuc2V0KGUscyl9ZWxzZSB0aGlzLmhlYWRlcnMuZGVsZXRlKGUpLHRoaXMubm9ybWFsaXplZE5hbWVzLmRlbGV0ZShlKX19Zm9yRWFjaCh0KXt0aGlzLmluaXQoKSxBcnJheS5mcm9tKHRoaXMubm9ybWFsaXplZE5hbWVzLmtleXMoKSkuZm9yRWFjaChlPT50KHRoaXMubm9ybWFsaXplZE5hbWVzLmdldChlKSx0aGlzLmhlYWRlcnMuZ2V0KGUpKSl9fSxWMmU9LyUoXGRbYS1mMC05XSkvZ2ksSDJlPXs0MDoiQCIsIjNBIjoiOiIsMjQ6IiQiLCIyQyI6IiwiLCIzQiI6IjsiLCIzRCI6Ij0iLCIzRiI6Ij8iLCIyRiI6Ii8ifTtmdW5jdGlvbiBwJChuKXtyZXR1cm4gZW5jb2RlVVJJQ29tcG9uZW50KG4pLnJlcGxhY2UoVjJlLCh0LGUpPT5IMmVbZV0/P3QpfWZ1bmN0aW9uIE5BKG4pe3JldHVybmAke259YH12YXIgWHU9Y2xhc3N7Y29uc3RydWN0b3IodD17fSl7aWYodGhpcy51cGRhdGVzPW51bGwsdGhpcy5jbG9uZUZyb209bnVsbCx0aGlzLmVuY29kZXI9dC5lbmNvZGVyfHxuZXcgY2xhc3N7ZW5jb2RlS2V5KHQpe3JldHVybiBwJCh0KX1lbmNvZGVWYWx1ZSh0KXtyZXR1cm4gcCQodCl9ZGVjb2RlS2V5KHQpe3JldHVybiBkZWNvZGVVUklDb21wb25lbnQodCl9ZGVjb2RlVmFsdWUodCl7cmV0dXJuIGRlY29kZVVSSUNvbXBvbmVudCh0KX19LHQuZnJvbVN0cmluZyl7aWYodC5mcm9tT2JqZWN0KXRocm93IG5ldyBFcnJvcigiQ2Fubm90IHNwZWNpZnkgYm90aCBmcm9tU3RyaW5nIGFuZCBmcm9tT2JqZWN0LiIpO3RoaXMubWFwPWZ1bmN0aW9uKG4sdCl7bGV0IGU9bmV3IE1hcDtyZXR1cm4gbi5sZW5ndGg+MCYmbi5yZXBsYWNlKC9eXD8vLCIiKS5zcGxpdCgiJiIpLmZvckVhY2gocj0+e2xldCBvPXIuaW5kZXhPZigiPSIpLFtzLGFdPS0xPT1vP1t0LmRlY29kZUtleShyKSwiIl06W3QuZGVjb2RlS2V5KHIuc2xpY2UoMCxvKSksdC5kZWNvZGVWYWx1ZShyLnNsaWNlKG8rMSkpXSxsPWUuZ2V0KHMpfHxbXTtsLnB1c2goYSksZS5zZXQocyxsKX0pLGV9KHQuZnJvbVN0cmluZyx0aGlzLmVuY29kZXIpfWVsc2UgdC5mcm9tT2JqZWN0Pyh0aGlzLm1hcD1uZXcgTWFwLE9iamVjdC5rZXlzKHQuZnJvbU9iamVjdCkuZm9yRWFjaChlPT57bGV0IGk9dC5mcm9tT2JqZWN0W2VdLHI9QXJyYXkuaXNBcnJheShpKT9pLm1hcChOQSk6W05BKGkpXTt0aGlzLm1hcC5zZXQoZSxyKX0pKTp0aGlzLm1hcD1udWxsfWhhcyh0KXtyZXR1cm4gdGhpcy5pbml0KCksdGhpcy5tYXAuaGFzKHQpfWdldCh0KXt0aGlzLmluaXQoKTtsZXQgZT10aGlzLm1hcC5nZXQodCk7cmV0dXJuIGU/ZVswXTpudWxsfWdldEFsbCh0KXtyZXR1cm4gdGhpcy5pbml0KCksdGhpcy5tYXAuZ2V0KHQpfHxudWxsfWtleXMoKXtyZXR1cm4gdGhpcy5pbml0KCksQXJyYXkuZnJvbSh0aGlzLm1hcC5rZXlzKCkpfWFwcGVuZCh0LGUpe3JldHVybiB0aGlzLmNsb25lKHtwYXJhbTp0LHZhbHVlOmUsb3A6ImEifSl9YXBwZW5kQWxsKHQpe2xldCBlPVtdO3JldHVybiBPYmplY3Qua2V5cyh0KS5mb3JFYWNoKGk9PntsZXQgcj10W2ldO0FycmF5LmlzQXJyYXkocik/ci5mb3JFYWNoKG89PntlLnB1c2goe3BhcmFtOmksdmFsdWU6byxvcDoiYSJ9KX0pOmUucHVzaCh7cGFyYW06aSx2YWx1ZTpyLG9wOiJhIn0pfSksdGhpcy5jbG9uZShlKX1zZXQodCxlKXtyZXR1cm4gdGhpcy5jbG9uZSh7cGFyYW06dCx2YWx1ZTplLG9wOiJzIn0pfWRlbGV0ZSh0LGUpe3JldHVybiB0aGlzLmNsb25lKHtwYXJhbTp0LHZhbHVlOmUsb3A6ImQifSl9dG9TdHJpbmcoKXtyZXR1cm4gdGhpcy5pbml0KCksdGhpcy5rZXlzKCkubWFwKHQ9PntsZXQgZT10aGlzLmVuY29kZXIuZW5jb2RlS2V5KHQpO3JldHVybiB0aGlzLm1hcC5nZXQodCkubWFwKGk9PmUrIj0iK3RoaXMuZW5jb2Rlci5lbmNvZGVWYWx1ZShpKSkuam9pbigiJiIpfSkuZmlsdGVyKHQ9PiIiIT09dCkuam9pbigiJiIpfWNsb25lKHQpe2xldCBlPW5ldyBYdSh7ZW5jb2Rlcjp0aGlzLmVuY29kZXJ9KTtyZXR1cm4gZS5jbG9uZUZyb209dGhpcy5jbG9uZUZyb218fHRoaXMsZS51cGRhdGVzPSh0aGlzLnVwZGF0ZXN8fFtdKS5jb25jYXQodCksZX1pbml0KCl7bnVsbD09PXRoaXMubWFwJiYodGhpcy5tYXA9bmV3IE1hcCksbnVsbCE9PXRoaXMuY2xvbmVGcm9tJiYodGhpcy5jbG9uZUZyb20uaW5pdCgpLHRoaXMuY2xvbmVGcm9tLmtleXMoKS5mb3JFYWNoKHQ9PnRoaXMubWFwLnNldCh0LHRoaXMuY2xvbmVGcm9tLm1hcC5nZXQodCkpKSx0aGlzLnVwZGF0ZXMuZm9yRWFjaCh0PT57c3dpdGNoKHQub3Ape2Nhc2UiYSI6Y2FzZSJzIjpsZXQgZT0oImEiPT09dC5vcD90aGlzLm1hcC5nZXQodC5wYXJhbSk6dm9pZCAwKXx8W107ZS5wdXNoKE5BKHQudmFsdWUpKSx0aGlzLm1hcC5zZXQodC5wYXJhbSxlKTticmVhaztjYXNlImQiOmlmKHZvaWQgMD09PXQudmFsdWUpe3RoaXMubWFwLmRlbGV0ZSh0LnBhcmFtKTticmVha317bGV0IGk9dGhpcy5tYXAuZ2V0KHQucGFyYW0pfHxbXSxyPWkuaW5kZXhPZihOQSh0LnZhbHVlKSk7LTEhPT1yJiZpLnNwbGljZShyLDEpLGkubGVuZ3RoPjA/dGhpcy5tYXAuc2V0KHQucGFyYW0saSk6dGhpcy5tYXAuZGVsZXRlKHQucGFyYW0pfX19KSx0aGlzLmNsb25lRnJvbT10aGlzLnVwZGF0ZXM9bnVsbCl9fTtmdW5jdGlvbiBoJChuKXtyZXR1cm4gdHlwZW9mIEFycmF5QnVmZmVyPCJ1IiYmbiBpbnN0YW5jZW9mIEFycmF5QnVmZmVyfWZ1bmN0aW9uIGYkKG4pe3JldHVybiB0eXBlb2YgQmxvYjwidSImJm4gaW5zdGFuY2VvZiBCbG9ifWZ1bmN0aW9uIG0kKG4pe3JldHVybiB0eXBlb2YgRm9ybURhdGE8InUiJiZuIGluc3RhbmNlb2YgRm9ybURhdGF9dmFyIEJtPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe2xldCBvO2lmKHRoaXMudXJsPWUsdGhpcy5ib2R5PW51bGwsdGhpcy5yZXBvcnRQcm9ncmVzcz0hMSx0aGlzLndpdGhDcmVkZW50aWFscz0hMSx0aGlzLnJlc3BvbnNlVHlwZT0ianNvbiIsdGhpcy5tZXRob2Q9dC50b1VwcGVyQ2FzZSgpLGZ1bmN0aW9uKG4pe3N3aXRjaChuKXtjYXNlIkRFTEVURSI6Y2FzZSJHRVQiOmNhc2UiSEVBRCI6Y2FzZSJPUFRJT05TIjpjYXNlIkpTT05QIjpyZXR1cm4hMTtkZWZhdWx0OnJldHVybiEwfX0odGhpcy5tZXRob2QpfHxyPyh0aGlzLmJvZHk9dm9pZCAwIT09aT9pOm51bGwsbz1yKTpvPWksbyYmKHRoaXMucmVwb3J0UHJvZ3Jlc3M9ISFvLnJlcG9ydFByb2dyZXNzLHRoaXMud2l0aENyZWRlbnRpYWxzPSEhby53aXRoQ3JlZGVudGlhbHMsby5yZXNwb25zZVR5cGUmJih0aGlzLnJlc3BvbnNlVHlwZT1vLnJlc3BvbnNlVHlwZSksby5oZWFkZXJzJiYodGhpcy5oZWFkZXJzPW8uaGVhZGVycyksby5jb250ZXh0JiYodGhpcy5jb250ZXh0PW8uY29udGV4dCksby5wYXJhbXMmJih0aGlzLnBhcmFtcz1vLnBhcmFtcykpLHRoaXMuaGVhZGVyc3x8KHRoaXMuaGVhZGVycz1uZXcgaGwpLHRoaXMuY29udGV4dHx8KHRoaXMuY29udGV4dD1uZXcgY2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLm1hcD1uZXcgTWFwfXNldCh0LGUpe3JldHVybiB0aGlzLm1hcC5zZXQodCxlKSx0aGlzfWdldCh0KXtyZXR1cm4gdGhpcy5tYXAuaGFzKHQpfHx0aGlzLm1hcC5zZXQodCx0LmRlZmF1bHRWYWx1ZSgpKSx0aGlzLm1hcC5nZXQodCl9ZGVsZXRlKHQpe3JldHVybiB0aGlzLm1hcC5kZWxldGUodCksdGhpc31oYXModCl7cmV0dXJuIHRoaXMubWFwLmhhcyh0KX1rZXlzKCl7cmV0dXJuIHRoaXMubWFwLmtleXMoKX19KSx0aGlzLnBhcmFtcyl7bGV0IHM9dGhpcy5wYXJhbXMudG9TdHJpbmcoKTtpZigwPT09cy5sZW5ndGgpdGhpcy51cmxXaXRoUGFyYW1zPWU7ZWxzZXtsZXQgYT1lLmluZGV4T2YoIj8iKTt0aGlzLnVybFdpdGhQYXJhbXM9ZSsoLTE9PT1hPyI/IjphPGUubGVuZ3RoLTE/IiYiOiIiKStzfX1lbHNlIHRoaXMucGFyYW1zPW5ldyBYdSx0aGlzLnVybFdpdGhQYXJhbXM9ZX1zZXJpYWxpemVCb2R5KCl7cmV0dXJuIG51bGw9PT10aGlzLmJvZHk/bnVsbDpoJCh0aGlzLmJvZHkpfHxmJCh0aGlzLmJvZHkpfHxtJCh0aGlzLmJvZHkpfHxmdW5jdGlvbihuKXtyZXR1cm4gdHlwZW9mIFVSTFNlYXJjaFBhcmFtczwidSImJm4gaW5zdGFuY2VvZiBVUkxTZWFyY2hQYXJhbXN9KHRoaXMuYm9keSl8fCJzdHJpbmciPT10eXBlb2YgdGhpcy5ib2R5P3RoaXMuYm9keTp0aGlzLmJvZHkgaW5zdGFuY2VvZiBYdT90aGlzLmJvZHkudG9TdHJpbmcoKToib2JqZWN0Ij09dHlwZW9mIHRoaXMuYm9keXx8ImJvb2xlYW4iPT10eXBlb2YgdGhpcy5ib2R5fHxBcnJheS5pc0FycmF5KHRoaXMuYm9keSk/SlNPTi5zdHJpbmdpZnkodGhpcy5ib2R5KTp0aGlzLmJvZHkudG9TdHJpbmcoKX1kZXRlY3RDb250ZW50VHlwZUhlYWRlcigpe3JldHVybiBudWxsPT09dGhpcy5ib2R5fHxtJCh0aGlzLmJvZHkpP251bGw6ZiQodGhpcy5ib2R5KT90aGlzLmJvZHkudHlwZXx8bnVsbDpoJCh0aGlzLmJvZHkpP251bGw6InN0cmluZyI9PXR5cGVvZiB0aGlzLmJvZHk/InRleHQvcGxhaW4iOnRoaXMuYm9keSBpbnN0YW5jZW9mIFh1PyJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQ7Y2hhcnNldD1VVEYtOCI6Im9iamVjdCI9PXR5cGVvZiB0aGlzLmJvZHl8fCJudW1iZXIiPT10eXBlb2YgdGhpcy5ib2R5fHwiYm9vbGVhbiI9PXR5cGVvZiB0aGlzLmJvZHk/ImFwcGxpY2F0aW9uL2pzb24iOm51bGx9Y2xvbmUodD17fSl7bGV0IGU9dC5tZXRob2R8fHRoaXMubWV0aG9kLGk9dC51cmx8fHRoaXMudXJsLHI9dC5yZXNwb25zZVR5cGV8fHRoaXMucmVzcG9uc2VUeXBlLG89dm9pZCAwIT09dC5ib2R5P3QuYm9keTp0aGlzLmJvZHkscz12b2lkIDAhPT10LndpdGhDcmVkZW50aWFscz90LndpdGhDcmVkZW50aWFsczp0aGlzLndpdGhDcmVkZW50aWFscyxhPXZvaWQgMCE9PXQucmVwb3J0UHJvZ3Jlc3M/dC5yZXBvcnRQcm9ncmVzczp0aGlzLnJlcG9ydFByb2dyZXNzLGw9dC5oZWFkZXJzfHx0aGlzLmhlYWRlcnMsYz10LnBhcmFtc3x8dGhpcy5wYXJhbXMsdT10LmNvbnRleHQ/P3RoaXMuY29udGV4dDtyZXR1cm4gdm9pZCAwIT09dC5zZXRIZWFkZXJzJiYobD1PYmplY3Qua2V5cyh0LnNldEhlYWRlcnMpLnJlZHVjZSgoZCxwKT0+ZC5zZXQocCx0LnNldEhlYWRlcnNbcF0pLGwpKSx0LnNldFBhcmFtcyYmKGM9T2JqZWN0LmtleXModC5zZXRQYXJhbXMpLnJlZHVjZSgoZCxwKT0+ZC5zZXQocCx0LnNldFBhcmFtc1twXSksYykpLG5ldyBCbShlLGksbyx7cGFyYW1zOmMsaGVhZGVyczpsLGNvbnRleHQ6dSxyZXBvcnRQcm9ncmVzczphLHJlc3BvbnNlVHlwZTpyLHdpdGhDcmVkZW50aWFsczpzfSl9fSxtaD0oKCk9PihmdW5jdGlvbihuKXtuW24uU2VudD0wXT0iU2VudCIsbltuLlVwbG9hZFByb2dyZXNzPTFdPSJVcGxvYWRQcm9ncmVzcyIsbltuLlJlc3BvbnNlSGVhZGVyPTJdPSJSZXNwb25zZUhlYWRlciIsbltuLkRvd25sb2FkUHJvZ3Jlc3M9M109IkRvd25sb2FkUHJvZ3Jlc3MiLG5bbi5SZXNwb25zZT00XT0iUmVzcG9uc2UiLG5bbi5Vc2VyPTVdPSJVc2VyIn0obWh8fChtaD17fSkpLG1oKSkoKSxQTT1jbGFzc3tjb25zdHJ1Y3Rvcih0LGU9MjAwLGk9Ik9LIil7dGhpcy5oZWFkZXJzPXQuaGVhZGVyc3x8bmV3IGhsLHRoaXMuc3RhdHVzPXZvaWQgMCE9PXQuc3RhdHVzP3Quc3RhdHVzOmUsdGhpcy5zdGF0dXNUZXh0PXQuc3RhdHVzVGV4dHx8aSx0aGlzLnVybD10LnVybHx8bnVsbCx0aGlzLm9rPXRoaXMuc3RhdHVzPj0yMDAmJnRoaXMuc3RhdHVzPDMwMH19LFJNPWNsYXNzIGV4dGVuZHMgUE17Y29uc3RydWN0b3IodD17fSl7c3VwZXIodCksdGhpcy50eXBlPW1oLlJlc3BvbnNlSGVhZGVyfWNsb25lKHQ9e30pe3JldHVybiBuZXcgUk0oe2hlYWRlcnM6dC5oZWFkZXJzfHx0aGlzLmhlYWRlcnMsc3RhdHVzOnZvaWQgMCE9PXQuc3RhdHVzP3Quc3RhdHVzOnRoaXMuc3RhdHVzLHN0YXR1c1RleHQ6dC5zdGF0dXNUZXh0fHx0aGlzLnN0YXR1c1RleHQsdXJsOnQudXJsfHx0aGlzLnVybHx8dm9pZCAwfSl9fSwkXz1jbGFzcyBleHRlbmRzIFBNe2NvbnN0cnVjdG9yKHQ9e30pe3N1cGVyKHQpLHRoaXMudHlwZT1taC5SZXNwb25zZSx0aGlzLmJvZHk9dm9pZCAwIT09dC5ib2R5P3QuYm9keTpudWxsfWNsb25lKHQ9e30pe3JldHVybiBuZXcgJF8oe2JvZHk6dm9pZCAwIT09dC5ib2R5P3QuYm9keTp0aGlzLmJvZHksaGVhZGVyczp0LmhlYWRlcnN8fHRoaXMuaGVhZGVycyxzdGF0dXM6dm9pZCAwIT09dC5zdGF0dXM/dC5zdGF0dXM6dGhpcy5zdGF0dXMsc3RhdHVzVGV4dDp0LnN0YXR1c1RleHR8fHRoaXMuc3RhdHVzVGV4dCx1cmw6dC51cmx8fHRoaXMudXJsfHx2b2lkIDB9KX19LG5wPWNsYXNzIGV4dGVuZHMgUE17Y29uc3RydWN0b3IodCl7c3VwZXIodCwwLCJVbmtub3duIEVycm9yIiksdGhpcy5uYW1lPSJIdHRwRXJyb3JSZXNwb25zZSIsdGhpcy5vaz0hMSx0aGlzLm1lc3NhZ2U9dGhpcy5zdGF0dXM+PTIwMCYmdGhpcy5zdGF0dXM8MzAwP2BIdHRwIGZhaWx1cmUgZHVyaW5nIHBhcnNpbmcgZm9yICR7dC51cmx8fCIodW5rbm93biB1cmwpIn1gOmBIdHRwIGZhaWx1cmUgcmVzcG9uc2UgZm9yICR7dC51cmx8fCIodW5rbm93biB1cmwpIn06ICR7dC5zdGF0dXN9ICR7dC5zdGF0dXNUZXh0fWAsdGhpcy5lcnJvcj10LmVycm9yfHxudWxsfX07ZnVuY3Rpb24gRDQobix0KXtyZXR1cm57Ym9keTp0LGhlYWRlcnM6bi5oZWFkZXJzLGNvbnRleHQ6bi5jb250ZXh0LG9ic2VydmU6bi5vYnNlcnZlLHBhcmFtczpuLnBhcmFtcyxyZXBvcnRQcm9ncmVzczpuLnJlcG9ydFByb2dyZXNzLHJlc3BvbnNlVHlwZTpuLnJlc3BvbnNlVHlwZSx3aXRoQ3JlZGVudGlhbHM6bi53aXRoQ3JlZGVudGlhbHN9fXZhciBWbT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuaGFuZGxlcj1lfXJlcXVlc3QoZSxpLHI9e30pe2xldCBvO2lmKGUgaW5zdGFuY2VvZiBCbSlvPWU7ZWxzZXtsZXQgbCxjO2w9ci5oZWFkZXJzIGluc3RhbmNlb2YgaGw/ci5oZWFkZXJzOm5ldyBobChyLmhlYWRlcnMpLHIucGFyYW1zJiYoYz1yLnBhcmFtcyBpbnN0YW5jZW9mIFh1P3IucGFyYW1zOm5ldyBYdSh7ZnJvbU9iamVjdDpyLnBhcmFtc30pKSxvPW5ldyBCbShlLGksdm9pZCAwIT09ci5ib2R5P3IuYm9keTpudWxsLHtoZWFkZXJzOmwsY29udGV4dDpyLmNvbnRleHQscGFyYW1zOmMscmVwb3J0UHJvZ3Jlc3M6ci5yZXBvcnRQcm9ncmVzcyxyZXNwb25zZVR5cGU6ci5yZXNwb25zZVR5cGV8fCJqc29uIix3aXRoQ3JlZGVudGlhbHM6ci53aXRoQ3JlZGVudGlhbHN9KX1sZXQgcz1YdChvKS5waXBlKGZ1bmN0aW9uKG4sdCl7cmV0dXJuIEVuKHQpP3huKG4sdCwxKTp4bihuLDEpfShsPT50aGlzLmhhbmRsZXIuaGFuZGxlKGwpKSk7aWYoZSBpbnN0YW5jZW9mIEJtfHwiZXZlbnRzIj09PXIub2JzZXJ2ZSlyZXR1cm4gcztsZXQgYT1zLnBpcGUoWWUobD0+bCBpbnN0YW5jZW9mICRfKSk7c3dpdGNoKHIub2JzZXJ2ZXx8ImJvZHkiKXtjYXNlImJvZHkiOnN3aXRjaChvLnJlc3BvbnNlVHlwZSl7Y2FzZSJhcnJheWJ1ZmZlciI6cmV0dXJuIGEucGlwZShMKGw9PntpZihudWxsIT09bC5ib2R5JiYhKGwuYm9keSBpbnN0YW5jZW9mIEFycmF5QnVmZmVyKSl0aHJvdyBuZXcgRXJyb3IoIlJlc3BvbnNlIGlzIG5vdCBhbiBBcnJheUJ1ZmZlci4iKTtyZXR1cm4gbC5ib2R5fSkpO2Nhc2UiYmxvYiI6cmV0dXJuIGEucGlwZShMKGw9PntpZihudWxsIT09bC5ib2R5JiYhKGwuYm9keSBpbnN0YW5jZW9mIEJsb2IpKXRocm93IG5ldyBFcnJvcigiUmVzcG9uc2UgaXMgbm90IGEgQmxvYi4iKTtyZXR1cm4gbC5ib2R5fSkpO2Nhc2UidGV4dCI6cmV0dXJuIGEucGlwZShMKGw9PntpZihudWxsIT09bC5ib2R5JiYic3RyaW5nIiE9dHlwZW9mIGwuYm9keSl0aHJvdyBuZXcgRXJyb3IoIlJlc3BvbnNlIGlzIG5vdCBhIHN0cmluZy4iKTtyZXR1cm4gbC5ib2R5fSkpO2RlZmF1bHQ6cmV0dXJuIGEucGlwZShMKGw9PmwuYm9keSkpfWNhc2UicmVzcG9uc2UiOnJldHVybiBhO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKGBVbnJlYWNoYWJsZTogdW5oYW5kbGVkIG9ic2VydmUgdHlwZSAke3Iub2JzZXJ2ZX19YCl9fWRlbGV0ZShlLGk9e30pe3JldHVybiB0aGlzLnJlcXVlc3QoIkRFTEVURSIsZSxpKX1nZXQoZSxpPXt9KXtyZXR1cm4gdGhpcy5yZXF1ZXN0KCJHRVQiLGUsaSl9aGVhZChlLGk9e30pe3JldHVybiB0aGlzLnJlcXVlc3QoIkhFQUQiLGUsaSl9anNvbnAoZSxpKXtyZXR1cm4gdGhpcy5yZXF1ZXN0KCJKU09OUCIsZSx7cGFyYW1zOihuZXcgWHUpLmFwcGVuZChpLCJKU09OUF9DQUxMQkFDSyIpLG9ic2VydmU6ImJvZHkiLHJlc3BvbnNlVHlwZToianNvbiJ9KX1vcHRpb25zKGUsaT17fSl7cmV0dXJuIHRoaXMucmVxdWVzdCgiT1BUSU9OUyIsZSxpKX1wYXRjaChlLGkscj17fSl7cmV0dXJuIHRoaXMucmVxdWVzdCgiUEFUQ0giLGUsRDQocixpKSl9cG9zdChlLGkscj17fSl7cmV0dXJuIHRoaXMucmVxdWVzdCgiUE9TVCIsZSxENChyLGkpKX1wdXQoZSxpLHI9e30pe3JldHVybiB0aGlzLnJlcXVlc3QoIlBVVCIsZSxENChyLGkpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihMQSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLEhBPW5ldyBwZSgiSFRUUF9JTlRFUkNFUFRPUlMiKSxqMmU9KCgpPT57Y2xhc3MgbntpbnRlcmNlcHQoZSxpKXtyZXR1cm4gaS5oYW5kbGUoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLEcyZT0vXlwpXF1cfScsP1xuLyxnJD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMueGhyRmFjdG9yeT1lfWhhbmRsZShlKXtpZigiSlNPTlAiPT09ZS5tZXRob2QpdGhyb3cgbmV3IEVycm9yKCJBdHRlbXB0ZWQgdG8gY29uc3RydWN0IEpzb25wIHJlcXVlc3Qgd2l0aG91dCBIdHRwQ2xpZW50SnNvbnBNb2R1bGUgaW5zdGFsbGVkLiIpO3JldHVybiBuZXcgdW4oaT0+e2xldCByPXRoaXMueGhyRmFjdG9yeS5idWlsZCgpO2lmKHIub3BlbihlLm1ldGhvZCxlLnVybFdpdGhQYXJhbXMpLGUud2l0aENyZWRlbnRpYWxzJiYoci53aXRoQ3JlZGVudGlhbHM9ITApLGUuaGVhZGVycy5mb3JFYWNoKChoLGYpPT5yLnNldFJlcXVlc3RIZWFkZXIoaCxmLmpvaW4oIiwiKSkpLGUuaGVhZGVycy5oYXMoIkFjY2VwdCIpfHxyLnNldFJlcXVlc3RIZWFkZXIoIkFjY2VwdCIsImFwcGxpY2F0aW9uL2pzb24sIHRleHQvcGxhaW4sICovKiIpLCFlLmhlYWRlcnMuaGFzKCJDb250ZW50LVR5cGUiKSl7bGV0IGg9ZS5kZXRlY3RDb250ZW50VHlwZUhlYWRlcigpO251bGwhPT1oJiZyLnNldFJlcXVlc3RIZWFkZXIoIkNvbnRlbnQtVHlwZSIsaCl9aWYoZS5yZXNwb25zZVR5cGUpe2xldCBoPWUucmVzcG9uc2VUeXBlLnRvTG93ZXJDYXNlKCk7ci5yZXNwb25zZVR5cGU9Impzb24iIT09aD9oOiJ0ZXh0In1sZXQgbz1lLnNlcmlhbGl6ZUJvZHkoKSxzPW51bGwsYT0oKT0+e2lmKG51bGwhPT1zKXJldHVybiBzO2xldCBoPXIuc3RhdHVzVGV4dHx8Ik9LIixmPW5ldyBobChyLmdldEFsbFJlc3BvbnNlSGVhZGVycygpKSxtPWZ1bmN0aW9uKG4pe3JldHVybiJyZXNwb25zZVVSTCJpbiBuJiZuLnJlc3BvbnNlVVJMP24ucmVzcG9uc2VVUkw6L15YLVJlcXVlc3QtVVJMOi9tLnRlc3Qobi5nZXRBbGxSZXNwb25zZUhlYWRlcnMoKSk/bi5nZXRSZXNwb25zZUhlYWRlcigiWC1SZXF1ZXN0LVVSTCIpOm51bGx9KHIpfHxlLnVybDtyZXR1cm4gcz1uZXcgUk0oe2hlYWRlcnM6ZixzdGF0dXM6ci5zdGF0dXMsc3RhdHVzVGV4dDpoLHVybDptfSksc30sbD0oKT0+e2xldHtoZWFkZXJzOmgsc3RhdHVzOmYsc3RhdHVzVGV4dDptLHVybDp4fT1hKCksZz1udWxsOzIwNCE9PWYmJihnPXR5cGVvZiByLnJlc3BvbnNlPiJ1Ij9yLnJlc3BvbnNlVGV4dDpyLnJlc3BvbnNlKSwwPT09ZiYmKGY9Zz8yMDA6MCk7bGV0IGI9Zj49MjAwJiZmPDMwMDtpZigianNvbiI9PT1lLnJlc3BvbnNlVHlwZSYmInN0cmluZyI9PXR5cGVvZiBnKXtsZXQgRD1nO2c9Zy5yZXBsYWNlKEcyZSwiIik7dHJ5e2c9IiIhPT1nP0pTT04ucGFyc2UoZyk6bnVsbH1jYXRjaChUKXtnPUQsYiYmKGI9ITEsZz17ZXJyb3I6VCx0ZXh0Omd9KX19Yj8oaS5uZXh0KG5ldyAkXyh7Ym9keTpnLGhlYWRlcnM6aCxzdGF0dXM6ZixzdGF0dXNUZXh0Om0sdXJsOnh8fHZvaWQgMH0pKSxpLmNvbXBsZXRlKCkpOmkuZXJyb3IobmV3IG5wKHtlcnJvcjpnLGhlYWRlcnM6aCxzdGF0dXM6ZixzdGF0dXNUZXh0Om0sdXJsOnh8fHZvaWQgMH0pKX0sYz1oPT57bGV0e3VybDpmfT1hKCksbT1uZXcgbnAoe2Vycm9yOmgsc3RhdHVzOnIuc3RhdHVzfHwwLHN0YXR1c1RleHQ6ci5zdGF0dXNUZXh0fHwiVW5rbm93biBFcnJvciIsdXJsOmZ8fHZvaWQgMH0pO2kuZXJyb3IobSl9LHU9ITEsZD1oPT57dXx8KGkubmV4dChhKCkpLHU9ITApO2xldCBmPXt0eXBlOm1oLkRvd25sb2FkUHJvZ3Jlc3MsbG9hZGVkOmgubG9hZGVkfTtoLmxlbmd0aENvbXB1dGFibGUmJihmLnRvdGFsPWgudG90YWwpLCJ0ZXh0Ij09PWUucmVzcG9uc2VUeXBlJiYhIXIucmVzcG9uc2VUZXh0JiYoZi5wYXJ0aWFsVGV4dD1yLnJlc3BvbnNlVGV4dCksaS5uZXh0KGYpfSxwPWg9PntsZXQgZj17dHlwZTptaC5VcGxvYWRQcm9ncmVzcyxsb2FkZWQ6aC5sb2FkZWR9O2gubGVuZ3RoQ29tcHV0YWJsZSYmKGYudG90YWw9aC50b3RhbCksaS5uZXh0KGYpfTtyZXR1cm4gci5hZGRFdmVudExpc3RlbmVyKCJsb2FkIixsKSxyLmFkZEV2ZW50TGlzdGVuZXIoImVycm9yIixjKSxyLmFkZEV2ZW50TGlzdGVuZXIoInRpbWVvdXQiLGMpLHIuYWRkRXZlbnRMaXN0ZW5lcigiYWJvcnQiLGMpLGUucmVwb3J0UHJvZ3Jlc3MmJihyLmFkZEV2ZW50TGlzdGVuZXIoInByb2dyZXNzIixkKSxudWxsIT09byYmci51cGxvYWQmJnIudXBsb2FkLmFkZEV2ZW50TGlzdGVuZXIoInByb2dyZXNzIixwKSksci5zZW5kKG8pLGkubmV4dCh7dHlwZTptaC5TZW50fSksKCk9PntyLnJlbW92ZUV2ZW50TGlzdGVuZXIoImVycm9yIixjKSxyLnJlbW92ZUV2ZW50TGlzdGVuZXIoImFib3J0IixjKSxyLnJlbW92ZUV2ZW50TGlzdGVuZXIoImxvYWQiLGwpLHIucmVtb3ZlRXZlbnRMaXN0ZW5lcigidGltZW91dCIsYyksZS5yZXBvcnRQcm9ncmVzcyYmKHIucmVtb3ZlRXZlbnRMaXN0ZW5lcigicHJvZ3Jlc3MiLGQpLG51bGwhPT1vJiZyLnVwbG9hZCYmci51cGxvYWQucmVtb3ZlRXZlbnRMaXN0ZW5lcigicHJvZ3Jlc3MiLHApKSxyLnJlYWR5U3RhdGUhPT1yLkRPTkUmJnIuYWJvcnQoKX19KX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihFbSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLE80PW5ldyBwZSgiWFNSRl9DT09LSUVfTkFNRSIpLGs0PW5ldyBwZSgiWFNSRl9IRUFERVJfTkFNRSIpLFZBPWNsYXNze30scTJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIpe3RoaXMuZG9jPWUsdGhpcy5wbGF0Zm9ybT1pLHRoaXMuY29va2llTmFtZT1yLHRoaXMubGFzdENvb2tpZVN0cmluZz0iIix0aGlzLmxhc3RUb2tlbj1udWxsLHRoaXMucGFyc2VDb3VudD0wfWdldFRva2VuKCl7aWYoInNlcnZlciI9PT10aGlzLnBsYXRmb3JtKXJldHVybiBudWxsO2xldCBlPXRoaXMuZG9jLmNvb2tpZXx8IiI7cmV0dXJuIGUhPT10aGlzLmxhc3RDb29raWVTdHJpbmcmJih0aGlzLnBhcnNlQ291bnQrKyx0aGlzLmxhc3RUb2tlbj1xRChlLHRoaXMuY29va2llTmFtZSksdGhpcy5sYXN0Q29va2llU3RyaW5nPWUpLHRoaXMubGFzdFRva2VufX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKEh0KSxqKEdkKSxqKE80KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksQTQ9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMudG9rZW5TZXJ2aWNlPWUsdGhpcy5oZWFkZXJOYW1lPWl9aW50ZXJjZXB0KGUsaSl7bGV0IHI9ZS51cmwudG9Mb3dlckNhc2UoKTtpZigiR0VUIj09PWUubWV0aG9kfHwiSEVBRCI9PT1lLm1ldGhvZHx8ci5zdGFydHNXaXRoKCJodHRwOi8vIil8fHIuc3RhcnRzV2l0aCgiaHR0cHM6Ly8iKSlyZXR1cm4gaS5oYW5kbGUoZSk7bGV0IG89dGhpcy50b2tlblNlcnZpY2UuZ2V0VG9rZW4oKTtyZXR1cm4gbnVsbCE9PW8mJiFlLmhlYWRlcnMuaGFzKHRoaXMuaGVhZGVyTmFtZSkmJihlPWUuY2xvbmUoe2hlYWRlcnM6ZS5oZWFkZXJzLnNldCh0aGlzLmhlYWRlck5hbWUsbyl9KSksaS5oYW5kbGUoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooVkEpLGooazQpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxZMmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuYmFja2VuZD1lLHRoaXMuaW5qZWN0b3I9aSx0aGlzLmNoYWluPW51bGx9aGFuZGxlKGUpe2lmKG51bGw9PT10aGlzLmNoYWluKXtsZXQgaT10aGlzLmluamVjdG9yLmdldChIQSxbXSk7dGhpcy5jaGFpbj1pLnJlZHVjZVJpZ2h0KChyLG8pPT5uZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLm5leHQ9dCx0aGlzLmludGVyY2VwdG9yPWV9aGFuZGxlKHQpe3JldHVybiB0aGlzLmludGVyY2VwdG9yLmludGVyY2VwdCh0LHRoaXMubmV4dCl9fShyLG8pLHRoaXMuYmFja2VuZCl9cmV0dXJuIHRoaXMuY2hhaW4uaGFuZGxlKGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKEJBKSxqKFhuKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksWDJlPSgoKT0+e2NsYXNzIG57c3RhdGljIGRpc2FibGUoKXtyZXR1cm57bmdNb2R1bGU6bixwcm92aWRlcnM6W3twcm92aWRlOkE0LHVzZUNsYXNzOmoyZX1dfX1zdGF0aWMgd2l0aE9wdGlvbnMoZT17fSl7cmV0dXJue25nTW9kdWxlOm4scHJvdmlkZXJzOltlLmNvb2tpZU5hbWU/e3Byb3ZpZGU6TzQsdXNlVmFsdWU6ZS5jb29raWVOYW1lfTpbXSxlLmhlYWRlck5hbWU/e3Byb3ZpZGU6azQsdXNlVmFsdWU6ZS5oZWFkZXJOYW1lfTpbXV19fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7cHJvdmlkZXJzOltBNCx7cHJvdmlkZTpIQSx1c2VFeGlzdGluZzpBNCxtdWx0aTohMH0se3Byb3ZpZGU6VkEsdXNlQ2xhc3M6cTJlfSx7cHJvdmlkZTpPNCx1c2VWYWx1ZToiWFNSRi1UT0tFTiJ9LHtwcm92aWRlOms0LHVzZVZhbHVlOiJYLVhTUkYtVE9LRU4ifV19KSxufSkoKSxfJD0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7cHJvdmlkZXJzOltWbSx7cHJvdmlkZTpMQSx1c2VDbGFzczpZMmV9LGckLHtwcm92aWRlOkJBLHVzZUV4aXN0aW5nOmckfV0saW1wb3J0czpbWDJlLndpdGhPcHRpb25zKHtjb29raWVOYW1lOiJYU1JGLVRPS0VOIixoZWFkZXJOYW1lOiJYLVhTUkYtVE9LRU4ifSldfSksbn0pKCksVUE9ImZlYXR1cmUiLGV2PU1yKFVBKSxnaD1KKGV2LG49Pm4uaXNGZWF0dXJlRmxhZ3NMb2FkZWQpLGJzPUooZXYsbj0+KHsuLi5uLmRlZmF1bHRGbGFncywuLi5uLmZsYWdPdmVycmlkZXN9KSkseSQ9SihldixuPT5uLmRlZmF1bHRGbGFncyksekE9SihldixuPT5uLmZsYWdPdmVycmlkZXN8fHt9KSxIbT1KKGV2LG49Pm4ubWV0YWRhdGEpLGpBPUooZXYsbj0+e2xldCB0PXt9O2ZvcihsZXQgZSBpbiBuLmZsYWdPdmVycmlkZXMpe2xldCBpPW4ubWV0YWRhdGFbZV07aSYmaS5xdWVyeVBhcmFtT3ZlcnJpZGUmJmkuc2VuZFRvU2VydmVyV2hlbk92ZXJyaWRkZW4mJih0W2VdPW4uZmxhZ092ZXJyaWRlc1tlXSl9cmV0dXJuIHR9KSxiJD1KKGJzLG49Pm4uaXNBdXRvRGFya01vZGVBbGxvd2VkKSxRdT1KKGJzLG49Pm51bGwhPT1uLmVuYWJsZURhcmtNb2RlT3ZlcnJpZGU/bi5lbmFibGVEYXJrTW9kZU92ZXJyaWRlOm4uZGVmYXVsdEVuYWJsZURhcmtNb2RlKSxHQT1KKGJzLG49Pm4uZW5hYmxlRGFya01vZGVPdmVycmlkZSkseCQ9SihicyxuPT5uLmVuYWJsZWRFeHBlcmltZW50YWxQbHVnaW5zKSxDJD1KKGJzLG49Pm4uaW5Db2xhYiksV0E9SihicyxuPT5uLm1ldHJpY3NJbWFnZVN1cHBvcnRFbmFibGVkKSxNJD1KKGJzLG49Pm4uZW5hYmxlZExpbmtlZFRpbWUpLHckPUooYnMsbj0+bi5mb3JjZVN2ZyksUyQ9SihicyxuPT5uLmVuYWJsZWRTY2FsYXJEYXRhVGFibGUpLEUkPUooYnMsbj0+bi5lbmFibGVTaG93RmxhZ3MpLFQkPUooYnMsbj0+bi5hbGxvd1JhbmdlU2VsZWN0aW9uKSxEJD1KKGJzLG49Pm4uZW5hYmxlZFByb3NwZWN0aXZlRm9iKSxxQT1KKGJzLG49Pm4uZW5hYmxlU2NhbGFyQ29sdW1uQ3VzdG9taXphdGlvbik7ZnVuY3Rpb24gUTJlKG4pe2xldCB0PXt9O2ZvcihsZXRbZSxpXW9mIG4uZW50cmllcygpKXRbZV09aTtyZXR1cm4gdH1mdW5jdGlvbiBGNChuKXtsZXQgdD1uLmhlYWRlcnN8fG5ldyBobDtyZXR1cm4gdD10LmFwcGVuZCgiWC1YU1JGLVByb3RlY3RlZCIsIjEiKSx7Li4ubixoZWFkZXJzOnR9fXZhciBrYT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLmFwcFJvb3RQcm92aWRlcj1lLHRoaXMuaHR0cD1pLHRoaXMuc3RvcmU9cn1yZXNvbHZlQXBwUm9vdChlKXtyZXR1cm4gZS5zdGFydHNXaXRoKCIvIik/dGhpcy5hcHBSb290UHJvdmlkZXIuZ2V0QWJzUGF0aG5hbWVXaXRoQXBwUm9vdChlKTplfWdldChlLGk9e30pe3JldHVybiB0aGlzLmh0dHAuZ2V0KHRoaXMucmVzb2x2ZUFwcFJvb3QoZSksaSl9cG9zdChlLGkscj17fSl7cmV0dXJuIHI9RjQociksdGhpcy5zdG9yZS5zZWxlY3QoZ2gpLnBpcGUoWWUobz0+Qm9vbGVhbihvKSksUXQoMSksV3QodGhpcy5zdG9yZS5zZWxlY3QoQyQpKSx4bigoWyxvXSk9PntsZXQgcz10aGlzLnJlc29sdmVBcHBSb290KGUpO3JldHVybiBvP3RoaXMuaHR0cC5nZXQocyx7aGVhZGVyczpyLmhlYWRlcnM/P3t9LHBhcmFtczpRMmUoaSl9KTp0aGlzLmh0dHAucG9zdChzLGkscil9KSl9cHV0KGUsaSxyPXt9KXtyZXR1cm4gdGhpcy5odHRwLnB1dCh0aGlzLnJlc29sdmVBcHBSb290KGUpLGksRjQocikpfWRlbGV0ZShlLGk9e30pe3JldHVybiB0aGlzLmh0dHAuZGVsZXRlKHRoaXMucmVzb2x2ZUFwcFJvb3QoZSksRjQoaSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKExtKSxqKFZtKSxqKENlKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksS3U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpba2FdLGltcG9ydHM6W18kLFFfXX0pLG59KSgpLCRsPSgoKT0+KGZ1bmN0aW9uKG4pe24uVU5LTk9XTj0iVU5LTk9XTiIsbi5OT1RfRk9VTkQ9Ik5PVF9GT1VORCIsbi5QRVJNSVNTSU9OX0RFTklFRD0iUEVSTUlTU0lPTl9ERU5JRUQifSgkbHx8KCRsPXt9KSksJGwpKSgpLEEkPW5ldyBwZSgiVGVuc29yQm9hcmQgYnJhbmQgbmFtZSIpLFJvPSgoKT0+KGZ1bmN0aW9uKG4pe24uU1RFUD0ic3RlcCIsbi5XQUxMX1RJTUU9IndhbGxfdGltZSIsbi5SRUxBVElWRT0icmVsYXRpdmUifShSb3x8KFJvPXt9KSksUm8pKSgpLHpyPSgoKT0+KGZ1bmN0aW9uKG4pe24uT0ZGU0VUPSJvZmZzZXQiLG4uT1ZFUkxBWT0ib3ZlcmxheSJ9KHpyfHwoenI9e30pKSx6cikpKCk7ZnVuY3Rpb24gSSQobil7bGV0IHQ9JGwuVU5LTk9XTjtyZXR1cm4gbiBpbnN0YW5jZW9mIG5wJiYoNDA0PT09bi5zdGF0dXMmJih0PSRsLk5PVF9GT1VORCksNDAzPT09bi5zdGF0dXMmJih0PSRsLlBFUk1JU1NJT05fREVOSUVEKSksd2MobmV3IE9NKHQpKX12YXIgT009Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5mYWlsdXJlQ29kZT10fX0sWUE9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLmh0dHA9ZSx0aGlzLnRmQmFja2VuZD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0Zi1iYWNrZW5kIikudGZfYmFja2VuZH1mZXRjaFBsdWdpbnNMaXN0aW5nKGUpe2xldCBpPWZ1bmN0aW9uKG4pe2lmKCFuLmxlbmd0aClyZXR1cm4gbnVsbDtsZXQgdD1uZXcgVVJMU2VhcmNoUGFyYW1zO2ZvcihsZXQgZSBvZiBuKXQuYXBwZW5kKCJleHBlcmltZW50YWxQbHVnaW4iLGUpO3JldHVybiB0fShlKSxyPWk/YGRhdGEvcGx1Z2luc19saXN0aW5nPyR7aS50b1N0cmluZygpfWA6ImRhdGEvcGx1Z2luc19saXN0aW5nIjtyZXR1cm4gdGhpcy5odHRwLmdldChyKS5waXBlKGZvKEkkKSl9ZmV0Y2hFbnZpcm9ubWVudCgpe3JldHVybiBscihbdGhpcy5odHRwLmdldCgiZGF0YS9lbnZpcm9ubWVudCIpLEVvKHRoaXMudGZCYWNrZW5kLmVudmlyb25tZW50U3RvcmUucmVmcmVzaCgpKV0pLnBpcGUoTCgoW3JdKT0+ciksZm8oSSQpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihrYSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLFAkPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W1lBXSxpbXBvcnRzOltLdV19KSxufSkoKSxPZT0oKCk9PihmdW5jdGlvbihuKXtuW24uTk9UX0xPQURFRD0wXT0iTk9UX0xPQURFRCIsbltuLkxPQURFRD0xXT0iTE9BREVEIixuW24uTE9BRElORz0yXT0iTE9BRElORyIsbltuLkZBSUxFRD0zXT0iRkFJTEVEIn0oT2V8fChPZT17fSkpLE9lKSkoKSxadT1iZSgiW0NvcmVdIFBsdWdpbiBDaGFuZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksWEE9YmUoIltDb3JlXSBQbHVnaW4gVXJsIEhhc2ggQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFIkPWJlKCJbQ29yZV0gTG9hZGVkIiksRmE9YmUoIltDb3JlXSBVc2VyIFRyaWdnZXJlZCBSZWxvYWQiKSxhYT1iZSgiW0NvcmVdIEF1dG8gUmVsb2FkIiksUUE9YmUoIltDb3JlXSBQbHVnaW5MaXN0aW5nIEZldGNoIFJlcXVlc3RlZCIpLFVtPWJlKCJbQ29yZV0gUGx1Z2luTGlzdGluZyBGZXRjaCBTdWNjZXNzZnVsIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksa009YmUoIltDb3JlXSBQbHVnaW5MaXN0aW5nIEZldGNoIEZhaWxlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLEtBPWJlKCJbQ29yZV0gUG9seW1lciBDb21wb25lbnQgUnVucyBGZXRjaCBSZXF1ZXN0ZWQiKSxaQT1iZSgiW0NvcmVdIFBvbHltZXIgQ29tcG9uZW50IFJ1bnMgRmV0Y2ggU3VjY2Vzc2Z1bCIpLEpBPWJlKCJbQ29yZV0gUG9seW1lciBDb21wb25lbnQgUnVucyBGZXRjaCBGYWlsZWQiKSwkQT1iZSgiW0NvcmVdIEVudmlyb25tZW50IEZldGNoIFN1Y2Nlc3NmdWwiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxPJD1iZSgiW0NvcmVdIFJ1biBTZWxlY3Rpb24gQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLGskPWJlKCJbQ29yZV0gUnVuIEZldGNoIFN1Y2Nlc3NmdWwiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxudj1iZSgiW0NvcmVdIFNpZGUgQmFyIFdpZHRoIENoYW5nZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KTtmdW5jdGlvbiB6bShuLHQsZSl7bGV0IGk9T2JqZWN0LmtleXMobikscj17Li4ubiwuLi50LHByaXZhdGVOYW1lc3BhY2VkU3RhdGU6e319O3JldHVybntpbml0aWFsU3RhdGU6cixyZWR1Y2Vyczp2cihyLFNlKEpsLChhLHtiZWZvcmU6bCxhZnRlcjpjLGJlZm9yZU5hbWVzcGFjZUlkOnUsYWZ0ZXJOYW1lc3BhY2VJZDpkfSk9PntsZXQgcD1hO3JldHVybiB1IT09ZCYmKHA9ZnVuY3Rpb24oYSxsLGMpe2xldCB1PXsuLi5hLnByaXZhdGVOYW1lc3BhY2VkU3RhdGV9O2lmKGwpe2xldCBwPXt9O2ZvcihsZXQgaCBvZiBpKXBbaF09YVtoXTt1PXsuLi51LFtsXTpwfX1sZXQgZD17fTtyZXR1cm4gYS5wcml2YXRlTmFtZXNwYWNlZFN0YXRlPy5bY10/ZD1hLnByaXZhdGVOYW1lc3BhY2VkU3RhdGVbY106bCYmKGQ9biksey4uLmEsLi4uZCxwcml2YXRlTmFtZXNwYWNlZFN0YXRlOnV9fShhLHUsZCkpLGUmJihwPWUocCxsLGMpKSxwfSkpfX1mdW5jdGlvbiBqbSguLi5uKXtyZXR1cm4odCxlKT0+e2xldCBpPXQ7Zm9yKGxldCByIG9mIG4paT1yKGksZSk7cmV0dXJuIGl9fXZhciBGTT17YWN0aXZlUGx1Z2luOm51bGwscGx1Z2luczp7fSxjb3JlRGF0YUxvYWRTdGF0ZTp7c3RhdGU6T2UuTk9UX0xPQURFRCxsYXN0TG9hZGVkVGltZUluTXM6bnVsbH0scGx1Z2luc0xpc3RMb2FkZWQ6e3N0YXRlOk9lLk5PVF9MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOm51bGwsZmFpbHVyZUNvZGU6bnVsbH0sZW52aXJvbm1lbnQ6e2RhdGFfbG9jYXRpb246IiIsd2luZG93X3RpdGxlOiIifSxwb2x5bWVyUnVuc0xvYWRTdGF0ZTp7c3RhdGU6T2UuTk9UX0xPQURFRCxsYXN0TG9hZGVkVGltZUluTXM6bnVsbH0scG9seW1lckludGVyb3BSdW5zOltdLHBvbHltZXJJbnRlcm9wUnVuU2VsZWN0aW9uOm5ldyBTZXQsc2lkZUJhcldpZHRoSW5QZXJjZW50OjIwfSwkMmU9dnIoRk0sU2UoWnUsWEEsKG4se3BsdWdpbjp0fSk9Pih7Li4ubixhY3RpdmVQbHVnaW46dH0pKSxTZShRQSxuPT4oey4uLm4sY29yZURhdGFMb2FkU3RhdGU6ey4uLm4uY29yZURhdGFMb2FkU3RhdGUsc3RhdGU6T2UuTE9BRElOR30scGx1Z2luc0xpc3RMb2FkZWQ6ey4uLm4ucGx1Z2luc0xpc3RMb2FkZWQsc3RhdGU6T2UuTE9BRElOR319KSksU2Uoa00sKG4se2ZhaWx1cmVDb2RlOnR9KT0+KHsuLi5uLGNvcmVEYXRhTG9hZFN0YXRlOnsuLi5uLmNvcmVEYXRhTG9hZFN0YXRlLHN0YXRlOk9lLkZBSUxFRH0scGx1Z2luc0xpc3RMb2FkZWQ6ey4uLm4ucGx1Z2luc0xpc3RMb2FkZWQsc3RhdGU6T2UuRkFJTEVELGZhaWx1cmVDb2RlOnR9fSkpLFNlKFVtLChuLHtwbHVnaW5zOnR9KT0+e2xldCBlPU9iamVjdC5rZXlzKHQpLmZpbmQocz0+dFtzXS5lbmFibGVkKXx8bnVsbCxpPW4uYWN0aXZlUGx1Z2lufHxlLHI9RGF0ZS5ub3coKSxvPW4uY29yZURhdGFMb2FkU3RhdGU7cmV0dXJuIG4ucG9seW1lclJ1bnNMb2FkU3RhdGUuc3RhdGU9PT1PZS5MT0FERUQmJihvPXtzdGF0ZTpPZS5MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOnJ9KSx7Li4ubixhY3RpdmVQbHVnaW46aSxjb3JlRGF0YUxvYWRTdGF0ZTpvLHBsdWdpbnM6dCxwbHVnaW5zTGlzdExvYWRlZDp7c3RhdGU6T2UuTE9BREVELGxhc3RMb2FkZWRUaW1lSW5NczpyLGZhaWx1cmVDb2RlOm51bGx9fX0pLFNlKEtBLG49Pih7Li4ubixjb3JlRGF0YUxvYWRTdGF0ZTp7Li4ubi5jb3JlRGF0YUxvYWRTdGF0ZSxzdGF0ZTpPZS5MT0FESU5HfSxwb2x5bWVyUnVuc0xvYWRTdGF0ZTp7Li4ubi5wb2x5bWVyUnVuc0xvYWRTdGF0ZSxzdGF0ZTpPZS5MT0FESU5HfX0pKSxTZShaQSxuPT57bGV0IHQ9RGF0ZS5ub3coKSxlPW4uY29yZURhdGFMb2FkU3RhdGU7cmV0dXJuIG4ucGx1Z2luc0xpc3RMb2FkZWQuc3RhdGU9PT1PZS5MT0FERUQmJihlPXtzdGF0ZTpPZS5MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOnR9KSx7Li4ubixjb3JlRGF0YUxvYWRTdGF0ZTplLHBvbHltZXJSdW5zTG9hZFN0YXRlOnsuLi5uLnBvbHltZXJSdW5zTG9hZFN0YXRlLHN0YXRlOk9lLkxPQURFRCxsYXN0TG9hZGVkVGltZUluTXM6dH19fSksU2UoSkEsbj0+KHsuLi5uLGNvcmVEYXRhTG9hZFN0YXRlOnsuLi5uLmNvcmVEYXRhTG9hZFN0YXRlLHN0YXRlOk9lLkZBSUxFRH0scG9seW1lclJ1bnNMb2FkU3RhdGU6ey4uLm4ucG9seW1lclJ1bnNMb2FkU3RhdGUsc3RhdGU6T2UuRkFJTEVEfX0pKSxTZSgkQSwobix7ZW52aXJvbm1lbnQ6dH0pPT4oey4uLm4sZW52aXJvbm1lbnQ6dH0pKSxTZShrJCwobix7cnVuczp0fSk9Pih7Li4ubixwb2x5bWVySW50ZXJvcFJ1bnM6dH0pKSxTZShPJCwobix7bmV4dFNlbGVjdGlvbjp0fSk9Pih7Li4ubixwb2x5bWVySW50ZXJvcFJ1blNlbGVjdGlvbjpuZXcgU2V0KHQpfSkpLFNlKG52LChuLHt3aWR0aEluUGVyY2VudDp0fSk9Pih7Li4ubixzaWRlQmFyV2lkdGhJblBlcmNlbnQ6TWF0aC5taW4oTWF0aC5tYXgoMCx0KSwxMDApfSkpLFNlKFljLChuLHtwYXJ0aWFsU2V0dGluZ3M6dH0pPT57bGV0IGU9ey4uLm59LGk9dC5zaWRlQmFyV2lkdGhJblBlcmNlbnQ7cmV0dXJuIm51bWJlciI9PXR5cGVvZiBpJiZpPj0wJiZpPD0xMDAmJihlLnNpZGVCYXJXaWR0aEluUGVyY2VudD1pKSxlfSkpLHtyZWR1Y2VyczplUGV9PXptKEZNLHt9KTtmdW5jdGlvbiBGJChuLHQpe3JldHVybiBqbSgkMmUsZVBlKShuLHQpfXZhciBfaD1NcigiY29yZSIpLG5JPUooX2gsbj0+bi5wbHVnaW5zTGlzdExvYWRlZCksTiQ9SihfaCxuPT5uLnBvbHltZXJSdW5zTG9hZFN0YXRlKSxMJD1KKF9oLG49Pm4uY29yZURhdGFMb2FkU3RhdGUuc3RhdGUpLGl2PUooX2gsbj0+bi5jb3JlRGF0YUxvYWRTdGF0ZS5sYXN0TG9hZGVkVGltZUluTXMpLFJzPUooX2gsbj0+bi5hY3RpdmVQbHVnaW4pLHJ2PUooX2gsbj0+bi5wbHVnaW5zKSxvdj1KKF9oLG49Pm4uZW52aXJvbm1lbnQpLGlJPUooX2gsbj0+bi5zaWRlQmFyV2lkdGhJblBlcmNlbnQpLEIkPW5ldyBTZXQoW2hpLkNPTVBBUkVfRVhQRVJJTUVOVCxoaS5FWFBFUklNRU5ULGhpLk5PVF9TRVRdKSxWJD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLmFjdGlvbnMkPWUsdGhpcy5zdG9yZT1pLHRoaXMud2ViYXBwRGF0YVNvdXJjZT1yLHRoaXMudGZCYWNrZW5kPXtyZWY6ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGYtYmFja2VuZCIpLnRmX2JhY2tlbmR9LHRoaXMub25EYXNoYm9hcmRMb2FkJD1KdCh0aGlzLmFjdGlvbnMkLnBpcGUoaWkoUiQsSmwpLFd0KHRoaXMuc3RvcmUuc2VsZWN0KFJhKSkseWkoKFssb10sWyxzXSk9PlBzKG8scykpKSx0aGlzLmFjdGlvbnMkLnBpcGUoaWkoYWEsRmEpKSkucGlwZShXdCh0aGlzLnN0b3JlLnNlbGVjdChxdSkpLFllKChbLG9dKT0+QiQuaGFzKG8pKSxiMCgxLHZvaWQgMCx7bGVhZGluZzohMH0pKSx0aGlzLmZldGNoV2ViQXBwRGF0YSQ9Y3IoKCk9PntsZXQgbz10aGlzLm9uRGFzaGJvYXJkTG9hZCQucGlwZShXdCh0aGlzLnN0b3JlLnNlbGVjdChuSSksdGhpcy5zdG9yZS5zZWxlY3QoeCQpKSxZZSgoWyx7c3RhdGU6YX1dKT0+YSE9PU9lLkxPQURJTkcpLGt0KCgpPT50aGlzLnN0b3JlLmRpc3BhdGNoKFFBKCkpKSx4bigoWywsYV0pPT5mdW5jdGlvbiguLi5uKXtsZXQgdD12dShuKSxlPW0xKG4pO3JldHVybiBlLmxlbmd0aD9uZXcgdW4oaT0+e2xldCByPWUubWFwKCgpPT5bXSksbz1lLm1hcCgoKT0+ITEpO2kuYWRkKCgpPT57cj1vPW51bGx9KTtmb3IobGV0IHM9MDshaS5jbG9zZWQmJnM8ZS5sZW5ndGg7cysrKWdpKGVbc10pLnN1YnNjcmliZShqdChpLGE9PntpZihyW3NdLnB1c2goYSksci5ldmVyeShsPT5sLmxlbmd0aCkpe2xldCBsPXIubWFwKGM9PmMuc2hpZnQoKSk7aS5uZXh0KHQ/dCguLi5sKTpsKSxyLnNvbWUoKGMsdSk9PiFjLmxlbmd0aCYmb1t1XSkmJmkuY29tcGxldGUoKX19LCgpPT57b1tzXT0hMCwhcltzXS5sZW5ndGgmJmkuY29tcGxldGUoKX0pKTtyZXR1cm4oKT0+e3I9bz1udWxsfX0pOmVvfSh0aGlzLndlYmFwcERhdGFTb3VyY2UuZmV0Y2hQbHVnaW5zTGlzdGluZyhhKSx0aGlzLmZldGNoRW52aXJvbm1lbnQoKSkucGlwZShMKChbbF0pPT57dGhpcy5zdG9yZS5kaXNwYXRjaChVbSh7cGx1Z2luczpsfSkpfSksZm8obD0+KHRoaXMuc3RvcmUuZGlzcGF0Y2goa00obCBpbnN0YW5jZW9mIE9NP3tmYWlsdXJlQ29kZTpsLmZhaWx1cmVDb2RlfTp7ZmFpbHVyZUNvZGU6JGwuVU5LTk9XTn0pKSxlbykpKSkpO3JldHVybiBKdChvLHRoaXMub25EYXNoYm9hcmRMb2FkJC5waXBlKEwoKFssYV0pPT5hKSx1aShhPT5hIT09aGkuQ09NUEFSRV9FWFBFUklNRU5UP1h0KFtdKTp0aGlzLnN0b3JlLnNlbGVjdChZdSkucGlwZSh5aSgobCxjKT0+e2xldCB1PU9iamVjdC5lbnRyaWVzKGwpLGQ9bmV3IE1hcChPYmplY3QuZW50cmllcyhjKSk7aWYodS5sZW5ndGghPT1kLnNpemUpcmV0dXJuITE7Zm9yKGxldFtwLGhdb2YgdSlpZighZC5nZXQocCl8fGQuZ2V0KHApLmFsaWFzVGV4dCE9PWguYWxpYXNUZXh0fHxkLmdldChwKS5hbGlhc051bWJlciE9PWguYWxpYXNOdW1iZXIpcmV0dXJuITE7cmV0dXJuITB9KSxPbCgwKSxiMCg1MDAsdm9pZCAwLHtsZWFkaW5nOiEwLHRyYWlsaW5nOiEwfSkpKSxXdCh0aGlzLnN0b3JlLnNlbGVjdChxdSksdGhpcy5zdG9yZS5zZWxlY3QoTiQpKSxZZSgoWyxhLGxdKT0+QiQuaGFzKGEpJiZsLnN0YXRlIT09T2UuTE9BRElORyksa3QoKCk9Pnt0aGlzLnN0b3JlLmRpc3BhdGNoKEtBKCkpfSksdWkoKCk9PnRoaXMucmVmcmVzaFBvbHltZXJSdW5zKCkpLGt0KCgpPT57dGhpcy5zdG9yZS5kaXNwYXRjaChaQSgpKX0pLGZvKCgpPT4odGhpcy5zdG9yZS5kaXNwYXRjaChKQSgpKSxlbykpKSl9LHtkaXNwYXRjaDohMX0pLHRoaXMuZGlzcGF0Y2hDaGFuZ2VQbHVnaW4kPWNyKCgpPT5KdCh0aGlzLm9uRGFzaGJvYXJkTG9hZCQsdGhpcy5hY3Rpb25zJC5waXBlKGlpKFVtKSkpLnBpcGUoV3QodGhpcy5zdG9yZS5zZWxlY3QoUnMpKSxMKChbLG9dKT0+bykseWkoKSxZZShvPT5udWxsIT09byksUXQoMSksa3Qobz0+e3RoaXMuc3RvcmUuZGlzcGF0Y2goWnUoe3BsdWdpbjpvfSkpfSkpLHtkaXNwYXRjaDohMX0pfXJlZnJlc2hQb2x5bWVyUnVucygpe3JldHVybiBFbyh0aGlzLnRmQmFja2VuZC5yZWYucnVuc1N0b3JlLnJlZnJlc2goKSl9ZmV0Y2hFbnZpcm9ubWVudCgpe3JldHVybiB0aGlzLndlYmFwcERhdGFTb3VyY2UuZmV0Y2hFbnZpcm9ubWVudCgpLnBpcGUoa3QoZT0+e3RoaXMuc3RvcmUuZGlzcGF0Y2goJEEoe2Vudmlyb25tZW50OmV9KSl9KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooUG8pLGooQ2UpLGooWUEpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxOND1uZXcgcGUoIkNvcmUgRmVhdHVyZSBDb25maWciKTtmdW5jdGlvbiBIJChuKXtyZXR1cm57aW5pdGlhbFN0YXRlOnsuLi5GTSxhY3RpdmVQbHVnaW46bi5nZXRQbHVnaW5JZCgpfHxudWxsfX19ZnVuY3Rpb24gaVBlKCl7cmV0dXJuIEooaUksbj0+KHtzaWRlQmFyV2lkdGhJblBlcmNlbnQ6bn0pKX12YXIgZWM9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbe3Byb3ZpZGU6TjQsZGVwczpbZmhdLHVzZUZhY3Rvcnk6SCR9XSxpbXBvcnRzOltyby5mb3JGZWF0dXJlKFtWJF0pLHdyLmZvckZlYXR1cmUoImNvcmUiLEYkLE40KSxQJCxTci5kZWZpbmVHbG9iYWxTZXR0aW5nKGlQZSldfSksbn0pKCksVSQ9bmV3IHBlKCJbQWxlcnRdIEFjdGlvbi1Uby1BbGVydCBQcm92aWRlciIpLEp1PSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5wcm92aWRlcnM9bmV3IE1hcDtmb3IobGV0IGkgb2YgZXx8W10pZm9yKGxldCByIG9mIGkpe2lmKHRoaXMucHJvdmlkZXJzLmhhcyhyLmFjdGlvbkNyZWF0b3IudHlwZSkpdGhyb3cgbmV3IFJhbmdlRXJyb3IoYCIke3IuYWN0aW9uQ3JlYXRvci50eXBlfSIgaXMgYWxyZWFkeSByZWdpc3RlcmVkIGZvciBhbGVydHMuIE11bHRpcGxlIGFsZXJ0cyBmb3IgdGhlIHNhbWUgYWN0aW9uIGlzIG5vdCBhbGxvd2VkLmApO3RoaXMucHJvdmlkZXJzLnNldChyLmFjdGlvbkNyZWF0b3IudHlwZSxyLmFsZXJ0RnJvbUFjdGlvbil9fWdldEFsZXJ0RnJvbUFjdGlvbihlKXtsZXQgaT10aGlzLnByb3ZpZGVycy5nZXQoZS50eXBlKTtyZXR1cm4gaT9pKGUpOm51bGx9c3RhdGljIHJlZ2lzdGVyQWxlcnRBY3Rpb25zKGUpe3JldHVybntuZ01vZHVsZTpuLHByb3ZpZGVyczpbe3Byb3ZpZGU6VSQsbXVsdGk6ITAsdXNlRmFjdG9yeTplfV19fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKFUkLDgpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksckk9YmUoIltSdW5zXSBGZXRjaCBSdW5zIFJlcXVlc3RlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLHZoPWJlKCJbUnVuc10gRmV0Y2ggUnVucyBTdWNjZWVkZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxzdj1iZSgiW1J1bnNdIEZldGNoIFJ1bnMgRmFpbGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksb0k9YmUoIltSdW5zXSBSdW4gU2VsZWN0aW9uIFRvZ2dsZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxzST1iZSgiW1J1bnNdIFNpbmdsZSBSdW4gU2VsZWN0ZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxhST1iZSgiW1J1bnNdIFJ1biBQYWdlIFNlbGVjdGlvbiBUb2dnbGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksbEk9YmUoIltSdW5zXSBSdW4gU2VsZWN0b3IgUGFnaW5hdGlvbiBPcHRpb24gQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLGNJPWJlKCJbUnVuc10gUnVuIFNlbGVjdG9yIFNvcnQgQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLE5NPWJlKCJbUnVuc10gUnVuIFNlbGVjdG9yIFJlZ2V4IEZpbHRlciBDaGFuZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksdUk9YmUoIltSdW5zXSBSdW4gQ29sb3IgQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLGRJPWJlKCJbUnVuc10gUnVuIFRhYmxlIFNob3duIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksYXY9YmUoIltSdW5zXSBSdW4gR3JvdXAgQnkgQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLENpPSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5ESVNDUkVURT0wXT0iRElTQ1JFVEUiLG5bbi5JTlRFUlZBTD0xXT0iSU5URVJWQUwifShDaXx8KENpPXt9KSksQ2kpKSgpLHBJPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpLEdtPXt9O0JFKEdtLHtocGFyYW1zRGlzY3JldGVIcGFyYW1GaWx0ZXJDaGFuZ2VkOigpPT5WNCxocGFyYW1zSW50ZXJ2YWxIcGFyYW1GaWx0ZXJDaGFuZ2VkOigpPT5INCxocGFyYW1zTWV0cmljRmlsdGVyQ2hhbmdlZDooKT0+VTR9KTt2YXIgVjQ9YmUoIltIcGFyYW1zXSBIcGFyYW1zIERpc2NyZXRlIEhwYXJhbSBGaWx0ZXIgQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLEg0PWJlKCJbSHBhcmFtc10gSHBhcmFtcyBJbnRlcnZhbCBIcGFyYW0gRmlsdGVyIENoYW5nZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxVND1iZSgiW0hwYXJhbXNdIEhwYXJhbXMgTWV0cmljIEZpbHRlciBDaGFuZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSk7ZnVuY3Rpb24gV20obil7cmV0dXJuIEpTT04uc3RyaW5naWZ5KFsuLi5uXS5zb3J0KCkpfWZ1bmN0aW9uIExNKG4pe2xldCB0PW5ldyBNYXAsZT1uZXcgTWFwLGk9bmV3IE1hcDtmb3IobGV0IHIgb2Ygbilmb3IobGV0W28sc11vZiByKWlmKHMudHlwZT09PUNpLkRJU0NSRVRFKXtsZXR7cG9zc2libGVWYWx1ZXM6YSx2YWx1ZXM6bH09ZS5nZXQobyl8fHtwb3NzaWJsZVZhbHVlczpuZXcgU2V0LHZhbHVlczpuZXcgU2V0fTtmb3IobGV0IGMgb2Ygcy5maWx0ZXJWYWx1ZXMpbC5hZGQoYyk7Zm9yKGxldCBjIG9mIHMucG9zc2libGVWYWx1ZXMpYS5hZGQoYyk7ZS5zZXQobyx7cG9zc2libGVWYWx1ZXM6YSx2YWx1ZXM6bH0pfWVsc2V7bGV0IGE9aS5nZXQobyk7aS5zZXQobyx7ZmlsdGVyTG93ZXJWYWx1ZTpNYXRoLm1pbihzLmZpbHRlckxvd2VyVmFsdWUsYT8uZmlsdGVyTG93ZXJWYWx1ZT8/MS8wKSxmaWx0ZXJVcHBlclZhbHVlOk1hdGgubWF4KHMuZmlsdGVyVXBwZXJWYWx1ZSxhPy5maWx0ZXJVcHBlclZhbHVlPz8tMS8wKSxtaW5WYWx1ZTpNYXRoLm1pbihzLm1pblZhbHVlLGE/Lm1pblZhbHVlPz8xLzApLG1heFZhbHVlOk1hdGgubWF4KHMubWF4VmFsdWUsYT8ubWF4VmFsdWU/Py0xLzApfSl9Zm9yKGxldFtyLHt2YWx1ZXM6byxwb3NzaWJsZVZhbHVlczpzfV1vZiBlKXQuc2V0KHIse3R5cGU6Q2kuRElTQ1JFVEUsaW5jbHVkZVVuZGVmaW5lZDohMCxwb3NzaWJsZVZhbHVlczpbLi4uc10sZmlsdGVyVmFsdWVzOlsuLi5vXX0pO2ZvcihsZXRbcix7bWluVmFsdWU6byxtYXhWYWx1ZTpzLGZpbHRlckxvd2VyVmFsdWU6YSxmaWx0ZXJVcHBlclZhbHVlOmx9XW9mIGkpe2lmKHQuaGFzKHIpKXtsZXQgYz10LmdldChyKTtpZihjLnR5cGU9PT1DaS5ESVNDUkVURSYmYy5wb3NzaWJsZVZhbHVlcy5zb21lKHU9PnUpKXRocm93IG5ldyBSYW5nZUVycm9yKGBDYW5ub3QgY29tYmluZSBocGFyYW0sICR7cn0sIGFzIGl0IGlzIG9mIG1peGVkIHR5cGVzLmApfXQuc2V0KHIse3R5cGU6Q2kuSU5URVJWQUwsaW5jbHVkZVVuZGVmaW5lZDohMCxtaW5WYWx1ZTpvLG1heFZhbHVlOnMsZmlsdGVyTG93ZXJWYWx1ZTphLGZpbHRlclVwcGVyVmFsdWU6bH0pfXJldHVybiB0fWZ1bmN0aW9uIGhJKG4pe2xldCB0PW5ldyBNYXA7Zm9yKGxldCBlIG9mIG4pZm9yKGxldFtpLHJdb2YgZSl7bGV0IG89dC5nZXQoaSk7dC5zZXQoaSx7dHlwZTpDaS5JTlRFUlZBTCxpbmNsdWRlVW5kZWZpbmVkOiEwLC4uLm8sbWluVmFsdWU6TWF0aC5taW4oci5taW5WYWx1ZSxvPy5taW5WYWx1ZT8/MS8wKSxtYXhWYWx1ZTpNYXRoLm1heChyLm1heFZhbHVlLG8/Lm1heFZhbHVlPz8tMS8wKSxmaWx0ZXJMb3dlclZhbHVlOk1hdGgubWluKHIuZmlsdGVyTG93ZXJWYWx1ZSxvPy5maWx0ZXJMb3dlclZhbHVlPz8xLzApLGZpbHRlclVwcGVyVmFsdWU6TWF0aC5tYXgoci5maWx0ZXJVcHBlclZhbHVlLG8/LmZpbHRlclVwcGVyVmFsdWU/Py0xLzApfSl9cmV0dXJuIHR9dmFyIGxQZT12cih7c3BlY3M6e30sZmlsdGVyczp7fX0sU2UoVjQsKG4sdCk9PntsZXR7ZXhwZXJpbWVudElkczplLGhwYXJhbU5hbWU6aSxmaWx0ZXJWYWx1ZXM6cixpbmNsdWRlVW5kZWZpbmVkOm99PXQscz1XbShlKSxhPW4uZmlsdGVyc1tzXT8/e2hwYXJhbXM6bmV3IE1hcH0sbD1hLmhwYXJhbXMuZ2V0KGkpO2lmKGwmJmwudHlwZSE9PUNpLkRJU0NSRVRFKXRocm93IG5ldyBSYW5nZUVycm9yKGBOZXcgZGlzY3JldGUgZmlsdGVyIG9mICR7aX0gY29uZmxpY3RzIGV4aXN0aW5nIGZpbHRlciBvZiBgK0NpW2wudHlwZV0pO2xldCBjPUxNKGUuZmlsdGVyKGg9PkJvb2xlYW4obi5zcGVjc1toXSkpLm1hcChoPT5uLnNwZWNzW2hdLmhwYXJhbS5kZWZhdWx0RmlsdGVycykpLmdldChpKTtpZighYyl0aHJvdyBuZXcgRXJyb3IoYENhbm5vdCBzZXQgaHBhcmFtLCAke2l9LCB3aGVuIGl0IGlzIG5vdCBrbm93biBmb3IgZXhwZXJpbWVudElkczogJHtlLmpvaW4oIiwgIil9YCk7aWYoYy50eXBlIT09Q2kuRElTQ1JFVEUpdGhyb3cgbmV3IEVycm9yKGBDYW5ub3Qgc2V0ICR7aX0gd2hlbiBkZWZhdWx0IGZpbHRlciBpcyBub3Qgb2YgZGlzY3JldGUgdHlwZS5gKTtsZXQgdT1uZXcgU2V0KGMucG9zc2libGVWYWx1ZXMpLGQ9Wy4uLnJdLmZpbHRlcihoPT4hdS5oYXMoaCkpO2lmKGQubGVuZ3RoKXRocm93IG5ldyBFcnJvcihgTmV3IGZpbHRlciBmb3IgJHtpfSBoYXMgbW9yZSB0aGFuIG9uZSB2YWx1ZSB0aGF0IGlzIG5vdCBwcmVzZW50IGluIHRoZSBzcGVjLiBCYWQgdmFsdWVzOiAke2Quam9pbigiLCAiKX1gKTtsZXQgcD1uZXcgTWFwKGEuaHBhcmFtcyk7cmV0dXJuIHAuc2V0KGksey4uLmwsdHlwZTpDaS5ESVNDUkVURSxpbmNsdWRlVW5kZWZpbmVkOm8scG9zc2libGVWYWx1ZXM6Wy4uLnVdLGZpbHRlclZhbHVlczpyfSksey4uLm4sZmlsdGVyczp7Li4ubi5maWx0ZXJzLFtzXTp7Li4uYSxocGFyYW1zOnB9fX19KSxTZShINCwobix0KT0+e2xldHtleHBlcmltZW50SWRzOmUsaHBhcmFtTmFtZTppLGZpbHRlckxvd2VyVmFsdWU6cixmaWx0ZXJVcHBlclZhbHVlOm8saW5jbHVkZVVuZGVmaW5lZDpzfT10LGE9V20oZSksbD1uLmZpbHRlcnNbYV0/P3ttZXRyaWNzOm5ldyBNYXAsaHBhcmFtczpuZXcgTWFwfSxjPWwuaHBhcmFtcy5nZXQoaSk7aWYoYyYmYy50eXBlIT09Q2kuSU5URVJWQUwpdGhyb3cgbmV3IFJhbmdlRXJyb3IoYE5ldyBpbnRlcnZhbCBmaWx0ZXIgb2YgJHtpfSBjb25mbGljdHMgZXhpc3RpbmcgZmlsdGVyIG9mIGArQ2lbYy50eXBlXSk7bGV0IHU9TE0oZS5maWx0ZXIocD0+Qm9vbGVhbihuLnNwZWNzW3BdKSkubWFwKHA9Pm4uc3BlY3NbcF0uaHBhcmFtLmRlZmF1bHRGaWx0ZXJzKSkuZ2V0KGkpO2lmKCF1KXRocm93IG5ldyBFcnJvcihgQ2Fubm90IHNldCBocGFyYSwgJHtpfSwgd2hlbiBpdCBpcyBub3Qga25vd24gZm9yIGV4cGVyaW1lbnRJZHM6ICR7ZS5qb2luKCIsICIpfWApO2lmKHUudHlwZSE9PUNpLklOVEVSVkFMKXRocm93IG5ldyBFcnJvcihgQ2Fubm90IHNldCAke2l9IHdoZW4gZGVmYXVsdCBmaWx0ZXIgaXMgbm90IG9mIGludGVydmFsIHR5cGUuYCk7bGV0IGQ9bmV3IE1hcChsLmhwYXJhbXMpO3JldHVybiBkLnNldChpLHsuLi5jLHR5cGU6Q2kuSU5URVJWQUwsaW5jbHVkZVVuZGVmaW5lZDpzLG1pblZhbHVlOnUubWluVmFsdWUsbWF4VmFsdWU6dS5tYXhWYWx1ZSxmaWx0ZXJMb3dlclZhbHVlOnIsZmlsdGVyVXBwZXJWYWx1ZTpvfSksey4uLm4sZmlsdGVyczp7Li4ubi5maWx0ZXJzLFthXTp7Li4ubCxocGFyYW1zOmR9fX19KSxTZShVNCwobix0KT0+e2xldHtleHBlcmltZW50SWRzOmUsbWV0cmljVGFnOmksZmlsdGVyTG93ZXJWYWx1ZTpyLGZpbHRlclVwcGVyVmFsdWU6byxpbmNsdWRlVW5kZWZpbmVkOnN9PXQsYT1XbShlKSxsPW4uZmlsdGVyc1thXT8/e21ldHJpY3M6bmV3IE1hcCxocGFyYW1zOm5ldyBNYXB9LGM9aEkoZS5maWx0ZXIocD0+Qm9vbGVhbihuLnNwZWNzW3BdKSkubWFwKHA9Pm4uc3BlY3NbcF0ubWV0cmljLmRlZmF1bHRGaWx0ZXJzKSkuZ2V0KGkpO2lmKCFjKXRocm93IG5ldyBFcnJvcihgQ2Fubm90IHNldCBtZXRyaWMsICR7aX0sIHdoZW4gaXQgaXMgbm90IGtub3duIGZvciBleHBlcmltZW50SWRzOiAke2Uuam9pbigiLCAiKX1gKTtsZXQgdT1sLm1ldHJpY3MuZ2V0KGkpLGQ9bmV3IE1hcChsLm1ldHJpY3MpO3JldHVybiBkLnNldChpLHsuLi51LHR5cGU6Q2kuSU5URVJWQUwsaW5jbHVkZVVuZGVmaW5lZDpzLG1pblZhbHVlOmMubWluVmFsdWUsbWF4VmFsdWU6Yy5tYXhWYWx1ZSxmaWx0ZXJMb3dlclZhbHVlOnIsZmlsdGVyVXBwZXJWYWx1ZTpvfSksey4uLm4sZmlsdGVyczp7Li4ubi5maWx0ZXJzLFthXTp7Li4ubCxtZXRyaWNzOmR9fX19KSxTZSh2aCwobix0KT0+e2lmKDA9PT1PYmplY3Qua2V5cyh0Lm5ld1J1bnNBbmRNZXRhZGF0YSkubGVuZ3RoKXJldHVybiBuO2xldCBlPXsuLi5uLnNwZWNzfSxpPW5ldyBNYXAscj1uZXcgU2V0O2ZvcihsZXQgbyBvZiBPYmplY3Qua2V5cyh0Lm5ld1J1bnNBbmRNZXRhZGF0YSkpe2xldCBzPW5ldyBNYXAsYT1uZXcgTWFwLGw9bmV3IE1hcCxjPW5ldyBNYXAse3J1bnM6dSxtZXRhZGF0YTpkfT10Lm5ld1J1bnNBbmRNZXRhZGF0YVtvXTtmb3IobGV0IHAgb2YgdSl7bGV0IGg9ZC5ydW5Ub0hwYXJhbXNBbmRNZXRyaWNzW3AuaWRdO2lmKGgpZm9yKGxldCBmIG9mIGgubWV0cmljcyl7bGV0IG09aS5nZXQoZi50YWcpO2kuc2V0KGYudGFnLHttaW46bT9NYXRoLm1pbihtLm1pbixmLnZhbHVlKTpmLnZhbHVlLG1heDptP01hdGgubWF4KG0ubWF4LGYudmFsdWUpOmYudmFsdWV9KX19Zm9yKGxldHtuYW1lOnAsZG9tYWluOmh9b2YgZC5ocGFyYW1TcGVjcylpZihoLnR5cGU9PT1DaS5ESVNDUkVURSl7bGV0IGY9bC5nZXQocCl8fG5ldyBTZXQ7Zm9yKGxldCBtIG9mIGgudmFsdWVzKWYuYWRkKG0pO2wuc2V0KHAsZil9ZWxzZXtsZXQgZj1jLmdldChwKTtjLnNldChwLHttaW5WYWx1ZTpmP01hdGgubWluKGgubWluVmFsdWUsZi5taW5WYWx1ZSk6aC5taW5WYWx1ZSxtYXhWYWx1ZTpmP01hdGgubWF4KGgubWF4VmFsdWUsZi5tYXhWYWx1ZSk6aC5tYXhWYWx1ZX0pfWZvcihsZXQgcCBvZiBkLm1ldHJpY1NwZWNzKXIuYWRkKHAudGFnKTtmb3IobGV0W3AsaF1vZiBsKXMuc2V0KHAse3R5cGU6Q2kuRElTQ1JFVEUsaW5jbHVkZVVuZGVmaW5lZDohMCxwb3NzaWJsZVZhbHVlczpbLi4uaF0sZmlsdGVyVmFsdWVzOlsuLi5oXX0pO2ZvcihsZXRbcCx7bWluVmFsdWU6aCxtYXhWYWx1ZTpmfV1vZiBjKXMuc2V0KHAse3R5cGU6Q2kuSU5URVJWQUwsaW5jbHVkZVVuZGVmaW5lZDohMCxtaW5WYWx1ZTpoLG1heFZhbHVlOmYsZmlsdGVyTG93ZXJWYWx1ZTpoLGZpbHRlclVwcGVyVmFsdWU6Zn0pO2ZvcihsZXQgcCBvZiByKXtsZXQgaD1pLmdldChwKSxmPWg/Lm1pbj8/MCxtPWg/Lm1heD8/MDthLnNldChwLHt0eXBlOkNpLklOVEVSVkFMLGluY2x1ZGVVbmRlZmluZWQ6ITAsbWluVmFsdWU6ZixtYXhWYWx1ZTptLGZpbHRlckxvd2VyVmFsdWU6ZixmaWx0ZXJVcHBlclZhbHVlOm19KX1lW29dPXtocGFyYW06ey4uLmVbb10/LmhwYXJhbSxzcGVjczpkLmhwYXJhbVNwZWNzLGRlZmF1bHRGaWx0ZXJzOnN9LG1ldHJpYzp7Li4uZVtvXT8ubWV0cmljLHNwZWNzOmQubWV0cmljU3BlY3MsZGVmYXVsdEZpbHRlcnM6YX19fXJldHVybnsuLi5uLHNwZWNzOmV9fSkpO2Z1bmN0aW9uIHokKG4sdCl7cmV0dXJuIGxQZShuLHQpfXZhciBmST0iaHBhcmFtcyIsaiQ9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W3dyLmZvckZlYXR1cmUoZkkseiQpXX0pLG59KSgpLG1JPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltqJF19KSxufSkoKTtmdW5jdGlvbiBjUGUobix0KXtyZXR1cm5gJHt0fS8ke259YH12YXIgRyQ9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLmh0dHA9ZX1mZXRjaFJ1bnMoZSl7cmV0dXJuIHRoaXMuaHR0cC5nZXQoImRhdGEvcnVucyIpLnBpcGUoTChpPT5pLm1hcChyPT4oe2lkOmNQZShyLGUpLG5hbWU6cixzdGFydFRpbWU6MH0pKSkpfWZldGNoSHBhcmFtc01ldGFkYXRhKGUpe3JldHVybiBYdCh7aHBhcmFtU3BlY3M6W10sbWV0cmljU3BlY3M6W10scnVuVG9IcGFyYW1zQW5kTWV0cmljczp7fX0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKGthKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksVyQ9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbe3Byb3ZpZGU6cEksdXNlQ2xhc3M6RyR9XX0pLG59KSgpLHEkPUooTXIoImFsZXJ0cyIpLG49Pm4ubGF0ZXN0QWxlcnQpLF9JPSJleHBlcmltZW50cyIscFBlPUooTXIoX0kpLG49Pm4uZGF0YSksdkk9SihwUGUsKG4sdCk9PntsZXR7ZXhwZXJpbWVudElkOmV9PXQ7cmV0dXJuIG4uZXhwZXJpbWVudE1hcFtlXXx8bnVsbH0pLHJpPSgoKT0+KGZ1bmN0aW9uKG4pe24uU0NBTEFSUz0ic2NhbGFycyIsbi5ISVNUT0dSQU1TPSJoaXN0b2dyYW1zIixuLklNQUdFUz0iaW1hZ2VzIn0ocml8fChyaT17fSkpLHJpKSkoKSxKaT0oKCk9PihmdW5jdGlvbihuKXtuW24uU1RFUD0wXT0iU1RFUCIsbltuLlJFTEFUSVZFPTFdPSJSRUxBVElWRSIsbltuLldBTExfVElNRT0yXT0iV0FMTF9USU1FIn0oSml8fChKaT17fSkpLEppKSkoKSxCTT0idGltZXNlcmllcyI7ZnVuY3Rpb24gWCQobil7cmV0dXJuIG49PT1yaS5TQ0FMQVJTfHxuPT09cmkuSElTVE9HUkFNU3x8bj09PXJpLklNQUdFU312YXIgaFBlPVtyaS5JTUFHRVNdO2Z1bmN0aW9uIGZsKG4pe3JldHVybiBoUGUuaW5jbHVkZXMobil9dmFyIGZQZT1bcmkuSElTVE9HUkFNUyxyaS5JTUFHRVNdO2Z1bmN0aW9uIG1sKG4pe3JldHVybiBmUGUuaW5jbHVkZXMobil9ZnVuY3Rpb24gejQobil7cmV0dXJuIG1sKG4ucGx1Z2luKX12YXIgJHU9Y2xhc3N7fTtmdW5jdGlvbiBiSShuKXtyZXR1cm4gbi5oYXNPd25Qcm9wZXJ0eSgiZXJyb3IiKX12YXIgajQ9ImRhdGEvcGx1Z2luL3RpbWVzZXJpZXMiO2Z1bmN0aW9uIFEkKG4pe2xldCB0PW4uaW5kZXhPZigiLyIpO3JldHVybntydW46bi5zdWJzdHJpbmcodCsxKSxleHBlcmltZW50SWQ6bi5zdWJzdHJpbmcoMCx0KX19ZnVuY3Rpb24gWiQobix0KXtyZXR1cm5gJHt0fS8ke259YH1mdW5jdGlvbiBLJChuLHQpe2xldHtydW5Ub1NlcmllczplLHJ1bjppLC4uLnJ9PW4sbz17Li4ucn07cmV0dXJuIGUmJihvLnJ1blRvU2VyaWVzPUc0KGUsdCkpLGkmJihvLnJ1bklkPVokKGksdCkpLG99ZnVuY3Rpb24gRzQobix0KXtsZXQgZT17fTtmb3IobGV0IGkgaW4gbiluLmhhc093blByb3BlcnR5KGkpJiYoZVtaJChpLHQpXT1uW2ldKTtyZXR1cm4gZX12YXIgSiQ9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuaHR0cD1lLHRoaXMuc3RvcmU9aX1mZXRjaFRhZ01ldGFkYXRhKGUpe2xldCBpPWUubWFwKG89PnRoaXMuaHR0cC5nZXQoYC9leHBlcmltZW50LyR7b30vJHtqNH0vdGFnc2ApLnBpcGUoTChhPT5mdW5jdGlvbihuLHQpe2xldCBlPXt9O2ZvcihsZXQgaSBvZiBPYmplY3Qua2V5cyhuKSl7bGV0IHI9aTtpZihmbChyKSl7bGV0e3RhZ1J1blNhbXBsZWRJbmZvOm8sLi4uc309bltyXSxhPXt9O2ZvcihsZXQgbCBpbiBvKW8uaGFzT3duUHJvcGVydHkobCkmJihhW2xdPUc0KG9bbF0sdCkpO2Vbcl09ey4uLnMsdGFnUnVuU2FtcGxlZEluZm86YX19ZWxzZXtsZXR7cnVuVGFnSW5mbzpvLC4uLnN9PW5bcl07ZVtyXT17Li4ucyxydW5UYWdJbmZvOkc0KG8sdCl9fX1yZXR1cm4gZX0oYSxvKSkpKSxyPXRoaXMuc3RvcmUuc2VsZWN0KGdoKS5waXBlKFllKEJvb2xlYW4pLFF0KDEpLFd0KHRoaXMuc3RvcmUuc2VsZWN0KFdBKSksTCgoWyxvXSk9Pm8pKTtyZXR1cm4gbHIoaSkucGlwZShXdChyKSxMKChbbyxzXSk9PntsZXQgYT1mdW5jdGlvbihuKXtsZXQgdD17fTtmb3IobGV0IGUgb2Ygbilmb3IobGV0IGkgb2YgT2JqZWN0LnZhbHVlcyhyaSkpaWYoZmwoaSkpe3RbaV09dFtpXXx8e3RhZ0Rlc2NyaXB0aW9uczp7fSx0YWdSdW5TYW1wbGVkSW5mbzp7fX07bGV0e3RhZ0Rlc2NyaXB0aW9uczpyLHRhZ1J1blNhbXBsZWRJbmZvOm99PWVbaV07dFtpXS50YWdEZXNjcmlwdGlvbnM9ey4uLnRbaV0udGFnRGVzY3JpcHRpb25zLC4uLnJ9O2xldCBzPXRbaV0udGFnUnVuU2FtcGxlZEluZm87Zm9yKGxldCBhIG9mIE9iamVjdC5rZXlzKG8pKXtzW2FdPXNbYV18fHt9O2ZvcihsZXQgbCBvZiBPYmplY3Qua2V5cyhvW2FdKSlzW2FdW2xdPW9bYV1bbF19fWVsc2V7dFtpXT10W2ldfHx7dGFnRGVzY3JpcHRpb25zOnt9LHJ1blRhZ0luZm86e319O2xldHt0YWdEZXNjcmlwdGlvbnM6cixydW5UYWdJbmZvOm99PWVbaV07dFtpXS50YWdEZXNjcmlwdGlvbnM9ey4uLnRbaV0udGFnRGVzY3JpcHRpb25zLC4uLnJ9LHRbaV0ucnVuVGFnSW5mbz17Li4udFtpXS5ydW5UYWdJbmZvLC4uLm99fXJldHVybiB0fShvKTtyZXR1cm4gc3x8KGFbcmkuSU1BR0VTXT17dGFnRGVzY3JpcHRpb25zOnt9LHRhZ1J1blNhbXBsZWRJbmZvOnt9fSksYX0pKX1mZXRjaFRpbWVTZXJpZXMoZSl7bGV0IGk9ZS5tYXAocj0+e2lmKG1sKHIucGx1Z2luKSl7bGV0e3J1bklkOmwsLi4uY309cix7cnVuOnUsZXhwZXJpbWVudElkOmR9PVEkKGwpLHA9ey4uLmMscnVuOnV9O3JldHVybiB0aGlzLmZldGNoVGltZVNlcmllc0JhY2tlbmRSZXF1ZXN0KHAsZCkucGlwZShMKCh7cmVzcG9uc2U6aCxleHBlcmltZW50SWQ6Zn0pPT5LJChoLGYpKSl9bGV0e2V4cGVyaW1lbnRJZHM6bywuLi5zfT1yO3JldHVybiBscihvLm1hcChsPT50aGlzLmZldGNoVGltZVNlcmllc0JhY2tlbmRSZXF1ZXN0KHMsbCkpKS5waXBlKEwobD0+e2xldHtydW5Ub1NlcmllczpjLGVycm9yOnUsLi4uZH09bFswXS5yZXNwb25zZSxwPWQ7Zm9yKGxldHtyZXNwb25zZTpoLGV4cGVyaW1lbnRJZDpmfW9mIGwpe2xldCBtPUskKGgsZik7aWYocC5lcnJvciljb250aW51ZTtsZXR7cnVuVG9TZXJpZXM6eCxlcnJvcjpnfT1tO2lmKGcpcC5lcnJvcj1nLHAucnVuVG9TZXJpZXM9dm9pZCAwO2Vsc2V7cC5ydW5Ub1Nlcmllcz1wLnJ1blRvU2VyaWVzfHx7fTtmb3IobGV0IGIgb2YgT2JqZWN0LmtleXMoeCkpcC5ydW5Ub1Nlcmllc1tiXT14W2JdfX1yZXR1cm4gcH0pKX0pO3JldHVybiBscihpKX1mZXRjaFRpbWVTZXJpZXNCYWNrZW5kUmVxdWVzdChlLGkpe2xldCByPW5ldyBGb3JtRGF0YTtyZXR1cm4gci5hcHBlbmQoInJlcXVlc3RzIixKU09OLnN0cmluZ2lmeShbZV0pKSx0aGlzLmh0dHAucG9zdChgL2V4cGVyaW1lbnQvJHtpfS8ke2o0fS90aW1lU2VyaWVzYCxyKS5waXBlKEwobz0+KHtyZXNwb25zZTpvWzBdLGV4cGVyaW1lbnRJZDppfSkpKX1pbWFnZVVybChlKXtyZXR1cm5gJHtqNH0vaW1hZ2VEYXRhP2ltYWdlSWQ9JHtlfWB9ZG93bmxvYWRVcmwoZSxpLHIsbyl7bGV0IGwse3J1bjpzLGV4cGVyaW1lbnRJZDphfT1RJChyKTtpZihlIT09cmkuU0NBTEFSUyl0aHJvdyBuZXcgRXJyb3IoYE5vdCBpbXBsZW1lbnRlZDogZG93bmxvYWRVcmwgZm9yICR7ZX0gaXMgbm90IGltcGxlbWVudGVkIHlldGApO2lmKGw9InNjYWxhcnMvc2NhbGFycyIsIWEpdGhyb3cgbmV3IEVycm9yKCJleHBlcmltZW50SWQgaXMgZW1wdHk7IGl0IGlzIHJlcXVpcmVkIHRvIGZvcm0gZG93bmxvYWRVcmwuIik7cmV0dXJuYC9leHBlcmltZW50LyR7YX0vZGF0YS9wbHVnaW4vc2NhbGFycy9zY2FsYXJzPyR7bmV3IFVSTFNlYXJjaFBhcmFtcyh7dGFnOmkscnVuOnMsZm9ybWF0Om99KX1gfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKGthKSxqKENlKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksdlBlPW5ldyBVUkxTZWFyY2hQYXJhbXMod2luZG93LmxvY2F0aW9uLnNlYXJjaCkseEk9KCgpPT57Y2xhc3MgbntnZXRQYXJhbXMoKXtyZXR1cm4gdlBlfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxWTT0idGJfZmVhdHVyZV9mbGFnX3N0b3JhZ2Vfa2V5IixXND0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMucXVlcnlQYXJhbXM9ZX1nZXRGZWF0dXJlcyhlLGkpe2xldCByPWU/dGhpcy5nZXRQYXJ0aWFsRmVhdHVyZXNGcm9tTWVkaWFRdWVyeSgpOnt9LG89ZnVuY3Rpb24obix0KXtyZXR1cm4gT2JqZWN0LmVudHJpZXMobikucmVkdWNlKChlLFtpLHJdKT0+e2xldCBvPWZ1bmN0aW9uKG4sdCl7bGV0IGU9bi5xdWVyeVBhcmFtT3ZlcnJpZGU7aWYoIWV8fCF0LmhhcyhlKSlyZXR1cm4gbnVsbDtsZXQgaT10LmdldChlKTtyZXR1cm4gbnVsbD09aT9udWxsOm4ucGFyc2VWYWx1ZShpKX0ocix0KTtyZXR1cm4gbnVsbCE9PW8mJihlW2ldPW8pLGV9LHt9KX0oaSx0aGlzLnF1ZXJ5UGFyYW1zLmdldFBhcmFtcygpKTtyZXR1cm57Li4uciwuLi5PYmplY3QuZnJvbUVudHJpZXMoT2JqZWN0LmVudHJpZXModGhpcy5nZXRQZXJzaXN0ZW50RmVhdHVyZUZsYWdzKCkpLmZpbHRlcigoW2FdKT0+aVthXSkpLC4uLm99fXBlcnNpc3RGZWF0dXJlRmxhZ3MoZSl7bGV0IHI9ey4uLnRoaXMuZ2V0UGVyc2lzdGVudEZlYXR1cmVGbGFncygpLC4uLmV9O2xvY2FsU3RvcmFnZS5zZXRJdGVtKFZNLEpTT04uc3RyaW5naWZ5KHIpKX1yZXNldFBlcnNpc3RlZEZlYXR1cmVGbGFnKGUpe2xldCBpPXRoaXMuZ2V0UGVyc2lzdGVudEZlYXR1cmVGbGFncygpO2lmKG51bGwhPWlbZV0pe2lmKGRlbGV0ZSBpW2VdLDA9PT1PYmplY3Qua2V5cyhpKS5sZW5ndGgpcmV0dXJuIHZvaWQgbG9jYWxTdG9yYWdlLnJlbW92ZUl0ZW0oVk0pO2xvY2FsU3RvcmFnZS5zZXRJdGVtKFZNLEpTT04uc3RyaW5naWZ5KGkpKX19cmVzZXRBbGxQZXJzaXN0ZWRGZWF0dXJlRmxhZ3MoKXtsb2NhbFN0b3JhZ2UucmVtb3ZlSXRlbShWTSl9Z2V0UGVyc2lzdGVudEZlYXR1cmVGbGFncygpe2xldCBlPWxvY2FsU3RvcmFnZS5nZXRJdGVtKFZNKTtyZXR1cm4gbnVsbD09ZT97fTpKU09OLnBhcnNlKGUpfWdldFBhcnRpYWxGZWF0dXJlc0Zyb21NZWRpYVF1ZXJ5KCl7bGV0IGU9e307cmV0dXJuIHdpbmRvdy5tYXRjaE1lZGlhKCIocHJlZmVycy1jb2xvci1zY2hlbWU6IGRhcmspIikubWF0Y2hlcyYmKGUuZGVmYXVsdEVuYWJsZURhcmtNb2RlPSEwKSxlfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKHhJKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksQ0k9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksdGVlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W1c0LHhJLHtwcm92aWRlOkNJLHVzZUNsYXNzOlc0fV19KSxufSkoKSxITT1iZSgiW0ZFQVRVUkUgRkxBR10gUGFydGlhbCBGZWF0dXJlIEZsYWdzIExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLE1JPWJlKCJbRkVBVFVSRSBGTEFHXSBFbmFibGUgRGFyayBNb2RlIE92ZXJyaWRlIENoYW5nZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxxbT1iZSgiW0ZFQVRVUkUgRkxBR10gU3RvcmUgdGhlIGZlYXR1cmUgZmxhZ3MgaW4gcGVyc2lzdGVudCBsb2NhbFN0b3JhZ2UiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSx5aD1iZSgiW0ZFQVRVUkUgRkxBR10gUmVzZXQgZmVhdHVyZSBmbGFnIG92ZXJyaWRlcyIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLGx2PWJlKCJbRkVBVFVSRSBGTEFHXSBSZXNldCBhbGwgZmVhdHVyZSBmbGFnIG92ZXJyaWRlcyIpLHE0PSJfdGJfZm9yY2Vfc3ZnIix3ST0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7fWdldEZvcmNlU3ZnRmxhZygpe3JldHVybiEhbG9jYWxTdG9yYWdlLmdldEl0ZW0ocTQpfXVwZGF0ZUZvcmNlU3ZnRmxhZyhlKXtlP2xvY2FsU3RvcmFnZS5zZXRJdGVtKHE0LCJwcmVzZW50Iik6bG9jYWxTdG9yYWdlLnJlbW92ZUl0ZW0ocTQpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxuZWU9YmUoIltGRUFUVVJFIEZMQUddIEVmZmVjdHMgSW5pdCIpLGllZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8pe3RoaXMuYWN0aW9ucyQ9ZSx0aGlzLnN0b3JlPWksdGhpcy5kYXRhU291cmNlPXIsdGhpcy5mb3JjZVN2Z0RhdGFTb3VyY2U9byx0aGlzLnRmRmVhdHVyZUZsYWdzPXtyZWY6ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGYtZmVhdHVyZS1mbGFncyIpLnRmX2ZlYXR1cmVfZmxhZ3N9LHRoaXMuZ2V0RmVhdHVyZUZsYWdzJD1jcigoKT0+dGhpcy5hY3Rpb25zJC5waXBlKGlpKG5lZSksZnIodGhpcy5zdG9yZS5zZWxlY3QoYiQpLHRoaXMuc3RvcmUuc2VsZWN0KEhtKSksTCgoWyxzLGFdKT0+e2xldCBsPXRoaXMuZGF0YVNvdXJjZS5nZXRGZWF0dXJlcyhzLGEpO3JldHVybiBudWxsIT1sLmZvcmNlU3ZnP3RoaXMuZm9yY2VTdmdEYXRhU291cmNlLnVwZGF0ZUZvcmNlU3ZnRmxhZyhsLmZvcmNlU3ZnKTp0aGlzLmZvcmNlU3ZnRGF0YVNvdXJjZS5nZXRGb3JjZVN2Z0ZsYWcoKSYmKGwuZm9yY2VTdmc9ITApLEhNKHtmZWF0dXJlczpsfSl9KSkpLHRoaXMudXBkYXRlUG9seW1lckZlYXR1cmVGbGFncyQ9Y3IoKCk9PnRoaXMuYWN0aW9ucyQucGlwZShpaShITSksV3QodGhpcy5zdG9yZS5zZWxlY3QoYnMpLHRoaXMuc3RvcmUuc2VsZWN0KGpBKSksa3QoKFsscyxhXSk9Pnt0aGlzLnRmRmVhdHVyZUZsYWdzLnJlZi5zZXRGZWF0dXJlRmxhZ3MocyxhKX0pKSx7ZGlzcGF0Y2g6ITF9KSx0aGlzLnN0b3JlRmVhdHVyZUZsYWckPWNyKCgpPT50aGlzLmFjdGlvbnMkLnBpcGUoaWkocW0pLGt0KCh7ZmxhZ3M6c30pPT57dGhpcy5kYXRhU291cmNlLnBlcnNpc3RGZWF0dXJlRmxhZ3Mocyl9KSkse2Rpc3BhdGNoOiExfSksdGhpcy5yZXNldEZlYXR1cmVGbGFnT3ZlcnJpZGVzJD1jcigoKT0+dGhpcy5hY3Rpb25zJC5waXBlKGlpKHloKSxrdCgoe2ZsYWdzOnN9KT0+e3MuZm9yRWFjaChhPT57dGhpcy5kYXRhU291cmNlLnJlc2V0UGVyc2lzdGVkRmVhdHVyZUZsYWcoYSl9KX0pKSx7ZGlzcGF0Y2g6ITF9KSx0aGlzLnJlc2V0QWxsRmVhdHVyZUZsYWdPdmVycmlkZXMkPWNyKCgpPT50aGlzLmFjdGlvbnMkLnBpcGUoaWkobHYpLGt0KCgpPT57dGhpcy5kYXRhU291cmNlLnJlc2V0QWxsUGVyc2lzdGVkRmVhdHVyZUZsYWdzKCl9KSkse2Rpc3BhdGNoOiExfSl9bmdyeE9uSW5pdEVmZmVjdHMoKXtyZXR1cm4gbmVlKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooUG8pLGooQ2UpLGooQ0kpLGood0kpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxyZWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbd0ldfSksbn0pKCksc2VlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lfWludGVyY2VwdChlLGkpe3JldHVybiB0aGlzLnN0b3JlLnBpcGUodnQoakEpLGZ1bmN0aW9uKG4sdCl7bGV0IGU9YXJndW1lbnRzLmxlbmd0aD49MjtyZXR1cm4gaT0+aS5waXBlKG4/WWUoKHIsbyk9Pm4ocixvLGkpKTptcyxRdCgxKSxlP18xKHQpOnYxKCgpPT5uZXcgXzApKX0oKSx1aShyPT4oZT1lLmNsb25lKHtoZWFkZXJzOmUuaGVhZGVycy5zZXQoIlgtVGVuc29yQm9hcmQtRmVhdHVyZS1GbGFncyIsSlNPTi5zdHJpbmdpZnkocikpfSksaS5oYW5kbGUoZSkpKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooQ2UpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKTtmdW5jdGlvbiBpcChuKXtyZXR1cm4iZmFsc2UiIT09bn12YXIgWTQ9e3NjYWxhcnNCYXRjaFNpemU6e2RlZmF1bHRWYWx1ZTp2b2lkIDAscXVlcnlQYXJhbU92ZXJyaWRlOiJzY2FsYXJzQmF0Y2hTaXplIixwYXJzZVZhbHVlOnBhcnNlSW50fSxlbmFibGVkRXhwZXJpbWVudGFsUGx1Z2luczp7ZGVmYXVsdFZhbHVlOltdLHF1ZXJ5UGFyYW1PdmVycmlkZToiZXhwZXJpbWVudGFsUGx1Z2luIixwYXJzZVZhbHVlOmZ1bmN0aW9uKG4pe3JldHVybiBuP24uc3BsaXQoIiwiKTpbXX19LGVuYWJsZWRMaW5rZWRUaW1lOntkZWZhdWx0VmFsdWU6ITAscXVlcnlQYXJhbU92ZXJyaWRlOiJlbmFibGVMaW5rZWRUaW1lIixwYXJzZVZhbHVlOmlwfSxlbmFibGVkU2NhbGFyRGF0YVRhYmxlOntkZWZhdWx0VmFsdWU6ITAscXVlcnlQYXJhbU92ZXJyaWRlOiJlbmFibGVEYXRhVGFibGUiLHBhcnNlVmFsdWU6aXB9LGZvcmNlU3ZnOntkZWZhdWx0VmFsdWU6ITEscXVlcnlQYXJhbU92ZXJyaWRlOiJmb3JjZVNWRyIscGFyc2VWYWx1ZTppcH0sZW5hYmxlRGFya01vZGVPdmVycmlkZTp7ZGVmYXVsdFZhbHVlOm51bGwscXVlcnlQYXJhbU92ZXJyaWRlOm51bGx9LGRlZmF1bHRFbmFibGVEYXJrTW9kZTp7ZGVmYXVsdFZhbHVlOiExLHF1ZXJ5UGFyYW1PdmVycmlkZToiZGFya01vZGUiLHBhcnNlVmFsdWU6aXB9LGlzQXV0b0RhcmtNb2RlQWxsb3dlZDp7ZGVmYXVsdFZhbHVlOiEwLHF1ZXJ5UGFyYW1PdmVycmlkZTpudWxsfSxpbkNvbGFiOntkZWZhdWx0VmFsdWU6ITEscXVlcnlQYXJhbU92ZXJyaWRlOiJ0ZW5zb3Jib2FyZENvbGFiIixwYXJzZVZhbHVlOmlwfSxtZXRyaWNzSW1hZ2VTdXBwb3J0RW5hYmxlZDp7ZGVmYXVsdFZhbHVlOiEwLHF1ZXJ5UGFyYW1PdmVycmlkZTpudWxsfSxlbmFibGVTaG93RmxhZ3M6e2RlZmF1bHRWYWx1ZTohMSxxdWVyeVBhcmFtT3ZlcnJpZGU6InNob3dGbGFncyIscGFyc2VWYWx1ZTppcH0sYWxsb3dSYW5nZVNlbGVjdGlvbjp7ZGVmYXVsdFZhbHVlOiEwLHF1ZXJ5UGFyYW1PdmVycmlkZToiYWxsb3dSYW5nZVNlbGVjdGlvbiIscGFyc2VWYWx1ZTppcH0sZW5hYmxlZFByb3NwZWN0aXZlRm9iOntkZWZhdWx0VmFsdWU6ITAscXVlcnlQYXJhbU92ZXJyaWRlOiJlbmFibGVQcm9zcGVjdGl2ZUZvYiIscGFyc2VWYWx1ZTppcH0sZW5hYmxlU2NhbGFyQ29sdW1uQ3VzdG9taXphdGlvbjp7ZGVmYXVsdFZhbHVlOiExLHF1ZXJ5UGFyYW1PdmVycmlkZToiZW5hYmxlU2NhbGFyQ29sdW1uQ3VzdG9taXphdGlvbiIscGFyc2VWYWx1ZTppcH19LFg0PXtpc0ZlYXR1cmVGbGFnc0xvYWRlZDohMSxkZWZhdWx0RmxhZ3M6ZnVuY3Rpb24obil7cmV0dXJuIE9iamVjdC5lbnRyaWVzKG4pLnJlZHVjZSgodCxbZSxpXSk9Pih0W2VdPWkuZGVmYXVsdFZhbHVlLHQpLHt9KX0oWTQpLG1ldGFkYXRhOlk0LGZsYWdPdmVycmlkZXM6e319LFE0PW5ldyBwZSgiW0ZlYXR1cmUgRmxhZ10gU3RvcmUgQ29uZmlnIik7ZnVuY3Rpb24gbGVlKCl7cmV0dXJue2luaXRpYWxTdGF0ZTpYNH19dmFyIEVQZT12cihYNCxTZShITSwobix7ZmVhdHVyZXM6dH0pPT4oey4uLm4saXNGZWF0dXJlRmxhZ3NMb2FkZWQ6ITAsZmxhZ092ZXJyaWRlczp7Li4ubi5mbGFnT3ZlcnJpZGVzLC4uLnR9fSkpLFNlKE1JLChuLHtlbmFibGVEYXJrTW9kZTp0fSk9Pih7Li4ubixmbGFnT3ZlcnJpZGVzOnsuLi5uLmZsYWdPdmVycmlkZXMsZW5hYmxlRGFya01vZGVPdmVycmlkZTp0fX0pKSxTZShxbSwobix0KT0+KHsuLi5uLGZsYWdPdmVycmlkZXM6ey4uLm4uZmxhZ092ZXJyaWRlcywuLi50LmZsYWdzfX0pKSxTZSh5aCwobix0KT0+e2lmKCF0fHwhdC5mbGFnc3x8IXQuZmxhZ3MubGVuZ3RoKXJldHVybiBuO2xldCBlPXsuLi5uLmZsYWdPdmVycmlkZXN9O3JldHVybiB0LmZsYWdzLmZvckVhY2goaT0+e2RlbGV0ZSBlW2ldfSksey4uLm4sZmxhZ092ZXJyaWRlczplfX0pLFNlKGx2LG49Pih7Li4ubixmbGFnT3ZlcnJpZGVzOnt9fSkpLFNlKFljLChuLHtwYXJ0aWFsU2V0dGluZ3M6dH0pPT57aWYoIXQudGhlbWVPdmVycmlkZSlyZXR1cm4gbjtsZXQgZTtzd2l0Y2godC50aGVtZU92ZXJyaWRlKXtjYXNlIE9hLkJST1dTRVJfREVGQVVMVDplPW51bGw7YnJlYWs7Y2FzZSBPYS5EQVJLOmU9ITA7YnJlYWs7Y2FzZSBPYS5MSUdIVDplPSExfXJldHVybnsuLi5uLGZsYWdPdmVycmlkZXM6ey4uLm4uZmxhZ092ZXJyaWRlcyxlbmFibGVEYXJrTW9kZU92ZXJyaWRlOmV9fX0pKTtmdW5jdGlvbiBjZWUobix0KXtyZXR1cm4gRVBlKG4sdCl9ZnVuY3Rpb24gVFBlKCl7cmV0dXJuIEooR0Esbj0+bnVsbD09PW4/e3RoZW1lT3ZlcnJpZGU6T2EuQlJPV1NFUl9ERUZBVUxUfTp7dGhlbWVPdmVycmlkZTpuP09hLkRBUks6T2EuTElHSFR9KX12YXIgY3Y9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbe3Byb3ZpZGU6UTQsdXNlRmFjdG9yeTpsZWV9LHtwcm92aWRlOkhBLHVzZUNsYXNzOnNlZSxtdWx0aTohMH1dLGltcG9ydHM6W3JlZSx0ZWUsd3IuZm9yRmVhdHVyZShVQSxjZWUsUTQpLHJvLmZvckZlYXR1cmUoW2llZV0pLFNyLmRlZmluZUdsb2JhbFNldHRpbmcoVFBlKV19KSxufSkoKSxTST0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7cHJvdmlkZXJzOlt7cHJvdmlkZTokdSx1c2VDbGFzczpKJH1dLGltcG9ydHM6W2N2LEt1XX0pLG59KSgpO2Z1bmN0aW9uIHJwKG4sdCxlLGkpe3JldHVybiBuW3RdLmhhc093blByb3BlcnR5KGUpP2ZsKHQpP25bdF1bZV0uaGFzT3duUHJvcGVydHkoaSk/blt0XVtlXVtpXTpudWxsOm5bdF1bZV06bnVsbH1mdW5jdGlvbiBFSShuLHQsZSxpKXtpZihmbCh0KSl7bGV0IHM9ey4uLm5bdF19LGE9ZnVuY3Rpb24obix0LGUpe2xldCByPW4uaGFzT3duUHJvcGVydHkodCk/ey4uLm5bdF19Ont9LG89ci5oYXNPd25Qcm9wZXJ0eShlKTtyZXR1cm4gcltlXT1vP3suLi5yW2VdfTp7cnVuVG9TZXJpZXM6e30scnVuVG9Mb2FkU3RhdGU6e319LHJ9KHMsZSxpKTtyZXR1cm4gc1tlXT1hLHN9bGV0IHI9ey4uLm5bdF19LG89ci5oYXNPd25Qcm9wZXJ0eShlKTtyZXR1cm4gcltlXT1vP3suLi5yW2VdfTp7cnVuVG9TZXJpZXM6e30scnVuVG9Mb2FkU3RhdGU6e319LHJ9ZnVuY3Rpb24gcGVlKG4pe3JldHVybiBKU09OLnN0cmluZ2lmeShuKX1mdW5jdGlvbiBUSShuLHQsZSl7bGV0IGk9ey4uLmV9O2ZvcihsZXQgciBvZiB0KWlbcl09bjtyZXR1cm4gaX1mdW5jdGlvbiB1dihuLHQsZSxpKXtpZihmbCh0KSl7bGV0IG89blt0XS50YWdSdW5TYW1wbGVkSW5mbztyZXR1cm4gby5oYXNPd25Qcm9wZXJ0eShlKT9PYmplY3Qua2V5cyhvW2VdKS5maWx0ZXIoYT0+aTxvW2VdW2FdLm1heFNhbXBsZXNQZXJTdGVwKTpbXX1sZXQgcj1uW3RdLnRhZ1RvUnVucztyZXR1cm4gci5oYXNPd25Qcm9wZXJ0eShlKT9yW2VdOltdfWZ1bmN0aW9uIFBQZShuLHQpe3JldHVybiBuLnBsdWdpbj09PXQucGx1Z2luJiZuLnRhZz09PXQudGFnJiZuLnNhbXBsZT09PXQuc2FtcGxlJiYobi5ydW5JZD09PXQucnVuSWR8fCFuLnJ1bklkJiYhdC5ydW5JZCl9ZnVuY3Rpb24gSzQobix0LGUsaSxyLG8scyl7bGV0IGE9bmV3IFNldChuKSxsPVtdO2ZvcihsZXQgdSBvZiBuKWZvcihsZXQgZCBvZiB0KWlmKFBQZShlW2RdLHUpKXtsLnB1c2goZCksYS5kZWxldGUodSk7YnJlYWt9aWYoIWwubGVuZ3RoKXJldHVybnt1bnJlc29sdmVkSW1wb3J0ZWRQaW5uZWRDYXJkczpuLGNhcmRNZXRhZGF0YU1hcDplLGNhcmRUb1Bpbm5lZENvcHk6aSxjYXJkVG9QaW5uZWRDb3B5Q2FjaGU6cixwaW5uZWRDYXJkVG9PcmlnaW5hbDpvLGNhcmRTdGVwSW5kZXg6c307bGV0IGM9e2NhcmRUb1Bpbm5lZENvcHk6aSxjYXJkVG9QaW5uZWRDb3B5Q2FjaGU6cixwaW5uZWRDYXJkVG9PcmlnaW5hbDpvLGNhcmRTdGVwSW5kZXg6cyxjYXJkTWV0YWRhdGFNYXA6ZX07Zm9yKGxldCB1IG9mIGwpYz1aNCh1LGMuY2FyZFRvUGlubmVkQ29weSxjLmNhcmRUb1Bpbm5lZENvcHlDYWNoZSxjLnBpbm5lZENhcmRUb09yaWdpbmFsLGMuY2FyZFN0ZXBJbmRleCxjLmNhcmRNZXRhZGF0YU1hcCk7cmV0dXJuey4uLmMsdW5yZXNvbHZlZEltcG9ydGVkUGlubmVkQ2FyZHM6Wy4uLmFdfX1mdW5jdGlvbiBaNChuLHQsZSxpLHIsbyl7aWYodC5oYXMobikpcmV0dXJue2NhcmRUb1Bpbm5lZENvcHk6dCxjYXJkVG9QaW5uZWRDb3B5Q2FjaGU6ZSxwaW5uZWRDYXJkVG9PcmlnaW5hbDppLGNhcmRTdGVwSW5kZXg6cixjYXJkTWV0YWRhdGFNYXA6b307bGV0IHM9bmV3IE1hcCh0KSxhPW5ldyBNYXAoZSksbD1uZXcgTWFwKGkpLGM9ey4uLnJ9LHU9ey4uLm99LGQ9ZnVuY3Rpb24obil7cmV0dXJuIEpTT04uc3RyaW5naWZ5KHtiYXNlQ2FyZElkOm59KX0obik7cy5zZXQobixkKSxhLnNldChuLGQpLGwuc2V0KGQsbiksci5oYXNPd25Qcm9wZXJ0eShuKSYmKGNbZF09cltuXSk7bGV0IHA9b1tuXTtpZighcCl0aHJvdyBuZXcgRXJyb3IoIkNhbm5vdCBwaW4gYSBjYXJkIHdpdGhvdXQgbWV0YWRhdGEiKTtyZXR1cm4gdVtkXT1wLHtjYXJkVG9QaW5uZWRDb3B5OnMsY2FyZFRvUGlubmVkQ29weUNhY2hlOmEscGlubmVkQ2FyZFRvT3JpZ2luYWw6bCxjYXJkU3RlcEluZGV4OmMsY2FyZE1ldGFkYXRhTWFwOnV9fWZ1bmN0aW9uIERJKG4pe3JldHVybiBuLnBpbm5lZENhcmRUb09yaWdpbmFsLnNpemUrbi51bnJlc29sdmVkSW1wb3J0ZWRQaW5uZWRDYXJkcy5sZW5ndGg8MTB9ZnVuY3Rpb24gSjQobix0LGUsaSl7bGV0IHI9ey4uLm59O3JldHVybiBPYmplY3Qua2V5cyhuKS5mb3JFYWNoKG89PntpZighby5pbmNsdWRlcygnInBsdWdpbiI6ImltYWdlcyInKSlyZXR1cm47bGV0IHM9JDQobyx0LGUpLGE9bnVsbDtpZihudWxsPT09aS5lbmQpYT1mdW5jdGlvbihuLHQpe2xldCBlPXQuaW5kZXhPZihuKTtpZigtMSE9PWUpcmV0dXJue2luZGV4OmUsaXNDbG9zZXN0OiExfTtmb3IobGV0IGk9MDtpPHQubGVuZ3RoLTE7aSsrKXtsZXQgcj10W2ldLG89dFtpKzFdLHM9LjEqKG8tcik7aWYobjxyKXJldHVybiBudWxsO2lmKCEobj5vKSl7aWYobi1yPD1zKXJldHVybntpbmRleDppLGlzQ2xvc2VzdDohMH07aWYoby1uPD1zKXJldHVybntpbmRleDppKzEsaXNDbG9zZXN0OiEwfX19cmV0dXJuIG51bGx9KGkuc3RhcnQuc3RlcCxzKTtlbHNle2xldCBjPXNbbltvXS5pbmRleF0sdT1mdW5jdGlvbihuLHQpe2lmKCFuKXJldHVybltdO2lmKG51bGw9PT1uLmVuZClyZXR1cm4tMSE9PXQuaW5kZXhPZihuLnN0YXJ0LnN0ZXApP1tuLnN0YXJ0LnN0ZXBdOltdO2xldCBlPVtdO2ZvcihsZXQgaSBvZiB0KWk+PW4uc3RhcnQuc3RlcCYmaTw9bi5lbmQuc3RlcCYmZS5wdXNoKGkpO3JldHVybiBlfShpLHMpO2E9ZnVuY3Rpb24obix0LGUpe2lmKDA9PT1uLmxlbmd0aClyZXR1cm4gbnVsbDtsZXQgaT1uWzBdLHI9bltuLmxlbmd0aC0xXTtyZXR1cm4gZT5yP3tpbmRleDp0LmluZGV4T2YociksaXNDbG9zZXN0OiExfTplPGk/e2luZGV4OnQuaW5kZXhPZihpKSxpc0Nsb3Nlc3Q6ITF9Om51bGx9KHUscyxjKX1udWxsIT09YSYmKHJbb109YSl9KSxyfWZ1bmN0aW9uICQ0KG4sdCxlKXtpZighdC5oYXNPd25Qcm9wZXJ0eShuKSlyZXR1cm5bXTtsZXR7cGx1Z2luOmksdGFnOnIsc2FtcGxlOm8scnVuSWQ6c309dFtuXTtpZihudWxsPT09cylyZXR1cm5bXTtsZXQgYT1ycChlLGkscixvKTtyZXR1cm4gbnVsbCE9PWEmJmEucnVuVG9TZXJpZXMuaGFzT3duUHJvcGVydHkocyk/YS5ydW5Ub1Nlcmllc1tzXS5tYXAobD0+bC5zdGVwKTpbXX12YXIgT289KCgpPT4oZnVuY3Rpb24obil7bi5ERUZBVUxUPSJkZWZhdWx0IixuLkFMUEhBQkVUSUNBTD0iYWxwaGFiZXRpY2FsIixuLkFTQ0VORElORz0iYXNjZW5kaW5nIixuLkRFU0NFTkRJTkc9ImRlc2NlbmRpbmciLG4uTkVBUkVTVD0ibmVhcmVzdCIsbi5ORUFSRVNUX1k9Im5lYXJlc3RfWSJ9KE9vfHwoT289e30pKSxPbykpKCksQUk9Im1ldHJpY3MiLElJPXtjYXJkTWluV2lkdGg6bnVsbCx0b29sdGlwU29ydDpPby5BTFBIQUJFVElDQUwsaWdub3JlT3V0bGllcnM6ITAseEF4aXNUeXBlOkppLlNURVAsc2NhbGFyU21vb3RoaW5nOi42LHNjYWxhclBhcnRpdGlvbk5vbk1vbm90b25pY1g6ITEsaW1hZ2VCcmlnaHRuZXNzSW5NaWxsaToxZTMsaW1hZ2VDb250cmFzdEluTWlsbGk6MWUzLGltYWdlU2hvd0FjdHVhbFNpemU6ITEsaGlzdG9ncmFtTW9kZTp6ci5PRkZTRVR9LHVyPU1yKEFJKSxVTT1KKHVyLG49Pm4udGFnTWV0YWRhdGFMb2FkU3RhdGUpLExQZT0oSih1cixuPT5uLnRhZ01ldGFkYXRhKSxKKHVyLG49Pm4uY2FyZExpc3QpKSxiaD1KKHVyLChuLHQpPT57aWYoIW4uY2FyZE1ldGFkYXRhTWFwLmhhc093blByb3BlcnR5KHQpKXJldHVybiBPZS5OT1RfTE9BREVEO2xldHtwbHVnaW46ZSx0YWc6aSxydW5JZDpyLHNhbXBsZTpvfT1uLmNhcmRNZXRhZGF0YU1hcFt0XSxzPXJwKG4udGltZVNlcmllc0RhdGEsZSxpLG8pO2lmKCFzKXJldHVybiBPZS5OT1RfTE9BREVEO2xldCBhPXMucnVuVG9Mb2FkU3RhdGU7aWYocilyZXR1cm4gYS5oYXNPd25Qcm9wZXJ0eShyKT9hW3JdOk9lLk5PVF9MT0FERUQ7bGV0IGw9dXYobi50YWdNZXRhZGF0YSxlLGksbyk7aWYoIWwubGVuZ3RoKXRocm93IG5ldyBFcnJvcigiQ2Fubm90IGxvYWQgYSBjYXJkIHdob3NlIHRhZyBoYXMgbm8gcnVucyIpO3JldHVybiBsLmV2ZXJ5KGM9PmFbY109PT1PZS5MT0FERUQpP09lLkxPQURFRDpsLnNvbWUoYz0+YVtjXT09PU9lLkxPQURJTkcpP09lLkxPQURJTkc6T2UuTk9UX0xPQURFRH0pLHhoPUoodXIsKG4sdCk9PntpZighbi5jYXJkTWV0YWRhdGFNYXAuaGFzT3duUHJvcGVydHkodCkpcmV0dXJuIG51bGw7bGV0e3BsdWdpbjplLHRhZzppLHNhbXBsZTpyfT1uLmNhcmRNZXRhZGF0YU1hcFt0XSxvPXJwKG4udGltZVNlcmllc0RhdGEsZSxpLHIpO3JldHVybiBvP28ucnVuVG9TZXJpZXM6bnVsbH0pLGVIPUoodXIsbj0+bi5jYXJkTWV0YWRhdGFNYXApLHRjPUooZUgsKG4sdCk9Pm4uaGFzT3duUHJvcGVydHkodCk/blt0XTpudWxsKSxCUGU9Sih1cixuPT5uLnZpc2libGVDYXJkTWFwKSxtZWU9SihCUGUsbj0+bmV3IFNldChuLnZhbHVlcygpKSksUEk9SihMUGUsZUgsKG4sdCk9Pm4uZmlsdGVyKGU9PnQuaGFzT3duUHJvcGVydHkoZSkpLm1hcChlPT4oe2NhcmRJZDplLC4uLnRbZV19KSkpLHRIPUoodXIsKG4sdCk9Pm4uY2FyZFN0ZXBJbmRleC5oYXNPd25Qcm9wZXJ0eSh0KT9uLmNhcmRTdGVwSW5kZXhbdF06bnVsbCksZ2VlPUoodXIsKG4sdCk9PiQ0KHQsbi5jYXJkTWV0YWRhdGFNYXAsbi50aW1lU2VyaWVzRGF0YSkpLF9lZT1KKHVyLG49Pm4uY2FyZFRvUGlubmVkQ29weSksVlBlPUoodXIsbj0+bi5waW5uZWRDYXJkVG9PcmlnaW5hbCksek09SihfZWUsZUgsKG4sdCk9PlsuLi5uLnZhbHVlcygpXS5maWx0ZXIoZT0+dC5oYXNPd25Qcm9wZXJ0eShlKSkubWFwKGU9Pih7Y2FyZElkOmUsLi4udFtlXX0pKSksQ2g9SihfZWUsVlBlLChuLHQsZSk9Pm4uaGFzKGUpfHx0LmhhcyhlKSksdmVlPUoodXIsbj0+bi51bnJlc29sdmVkSW1wb3J0ZWRQaW5uZWRDYXJkcykseWVlPUoodXIsbj0+REkobikpLGVkPUoodXIsbj0+KHsuLi5uLnNldHRpbmdzLC4uLm4uc2V0dGluZ092ZXJyaWRlc30pKSxiZWU9Sih1cixuPT5uLnNldHRpbmdPdmVycmlkZXMpLGR2PUooZWQsbj0+bi5jYXJkTWluV2lkdGgpLHB2PUooZWQsbj0+bi50b29sdGlwU29ydCksaHY9SihlZCxuPT5uLmlnbm9yZU91dGxpZXJzKSx0ZD1KKGVkLG49Pm4ueEF4aXNUeXBlKSxSST1KKGVkLG49Pm4uaGlzdG9ncmFtTW9kZSksb3A9SihlZCxuPT5uLnNjYWxhclNtb290aGluZyksT0k9SihlZCxuPT5uLnNjYWxhclBhcnRpdGlvbk5vbk1vbm90b25pY1gpLGtJPUooZWQsbj0+bi5pbWFnZUJyaWdodG5lc3NJbk1pbGxpKSxGST1KKGVkLG49Pm4uaW1hZ2VDb250cmFzdEluTWlsbGkpLE5JPUooZWQsbj0+bi5pbWFnZVNob3dBY3R1YWxTaXplKSxYYz1KKHVyLG49Pm4udGFnRmlsdGVyKSxMST1KKHVyLChuLHQpPT5Cb29sZWFuKG4udGFnR3JvdXBFeHBhbmRlZC5nZXQodCkpKSxZbT1KKHVyLG49Pm4ubGlua2VkVGltZUVuYWJsZWQpLGZ2PUoodXIsbj0+bi5zdGVwU2VsZWN0b3JFbmFibGVkKSxtdj1KKHVyLG49Pm4ucmFuZ2VTZWxlY3Rpb25FbmFibGVkKSxuSD1KKHVyLG49PntsZXR7bWluOnQsbWF4OmV9PW4uc3RlcE1pbk1heDtyZXR1cm57bWluOnQ9PT0xLzA/MDp0LG1heDplPT09LTEvMD8xZTM6ZX19KSxCST1KKHVyLG49Pm4uc2luZ2xlU2VsZWN0aW9uSGVhZGVycyksVkk9Sih1cixuPT5uLnJhbmdlU2VsZWN0aW9uSGVhZGVycyksaUg9Sih1cixuSCwobix0KT0+bi5saW5rZWRUaW1lU2VsZWN0aW9uP24ubGlua2VkVGltZVNlbGVjdGlvbjp7c3RhcnQ6e3N0ZXA6dC5taW59LGVuZDpudWxsfSksWG09Sih1cixpSCwobix0KT0+bi5saW5rZWRUaW1lRW5hYmxlZD90Om51bGwpLG5kPUoodXIsbj0+bi5maWx0ZXJlZFBsdWdpblR5cGVzKSxIST1KKHVyLG49Pm4uaXNTZXR0aW5nc1BhbmVPcGVuKSxVST1KKHVyLG49Pm4uaXNTbGlkZW91dE1lbnVPcGVuKSxDZWU9TXIoIm5vdGlmaWNhdGlvbiIpLGlkPShKKENlZSxuPT5uLm5vdGlmaWNhdGlvbnMpLEooQ2VlLG49Pm4ubGFzdFJlYWRUaW1lc3RhbXBJbk1zPz8tMSksKCgpPT4oZnVuY3Rpb24obil7bltuLkVYUEVSSU1FTlRfTkFNRT0wXT0iRVhQRVJJTUVOVF9OQU1FIixuW24uSFBBUkFNPTFdPSJIUEFSQU0iLG5bbi5NRVRSSUM9Ml09Ik1FVFJJQyIsbltuLlJVTl9OQU1FPTNdPSJSVU5fTkFNRSJ9KGlkfHwoaWQ9e30pKSxpZCkpKCkpLHNyPSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5SVU49MF09IlJVTiIsbltuLkVYUEVSSU1FTlQ9MV09IkVYUEVSSU1FTlQiLG5bbi5SRUdFWD0yXT0iUkVHRVgifShzcnx8KHNyPXt9KSksc3IpKSgpO2Z1bmN0aW9uIEdNKG4sdCxlKXtsZXQgaT17fSxyPVtdLG89e21hdGNoZXM6aSxub25NYXRjaGVzOnJ9O3N3aXRjaChuLmtleSl7Y2FzZSBzci5SVU46Zm9yKGxldCBhIG9mIHQpaVthLmlkXT1bYV07YnJlYWs7Y2FzZSBzci5FWFBFUklNRU5UOmZvcihsZXQgYSBvZiB0KXtsZXQgbD1lW2EuaWRdLGM9aVtsXXx8W107Yy5wdXNoKGEpLGlbbF09Y31icmVhaztjYXNlIHNyLlJFR0VYOmlmKCFuLnJlZ2V4U3RyaW5nKWJyZWFrO2xldCBzO3RyeXtzPW5ldyBSZWdFeHAobi5yZWdleFN0cmluZyl9Y2F0Y2h7YnJlYWt9Zm9yKGxldCBhIG9mIHQpe2xldCBsPWEubmFtZS5tYXRjaChzKTtpZihsKXtsZXQgdT1sLmxlbmd0aD4xP0pTT04uc3RyaW5naWZ5KGwuc2xpY2UoMSkpOiJwc2V1ZG9fZ3JvdXAiLGQ9aVt1XXx8W107ZC5wdXNoKGEpLGlbdV09ZH1lbHNlIHIucHVzaChhKX19cmV0dXJuIG99ZnVuY3Rpb24gakkobix0KXtyZXR1cm4gbj09PXNyLlJFR0VYP3trZXk6bixyZWdleFN0cmluZzp0Pz8iIn06e2tleTpufX12YXIgTWVlPU1yKCJydW5zIiksZ2w9SihNZWUsbj0+bi5kYXRhKSx3ZWU9SihnbCxuPT5uLnJ1bklkVG9FeHBJZCksR0k9SihnbCwobix0KT0+bi5ydW5JZFRvRXhwSWRbdC5ydW5JZF0/P251bGwpLFdJPUooZ2wsKG4sdCk9Pm4ucnVuTWV0YWRhdGFbdC5ydW5JZF0/P251bGwpLHJkPUooZ2wsKG4sdCk9PihuLnJ1bklkc1t0LmV4cGVyaW1lbnRJZF18fFtdKS5maWx0ZXIoaT0+Qm9vbGVhbihuLnJ1bk1ldGFkYXRhW2ldKSkubWFwKGk9Pm4ucnVuTWV0YWRhdGFbaV0pKSxTZWU9SihnbCwobix0KT0+bi5ydW5JZHNbdC5leHBlcmltZW50SWRdPz9bXSkscUk9SihnbCxuPT5uZXcgTWFwKE9iamVjdC5lbnRyaWVzKG4ucnVuTWV0YWRhdGEpKSksV009SihnbCwobix0KT0+bi5ydW5zTG9hZFN0YXRlW3QuZXhwZXJpbWVudElkXXx8e2xhc3RMb2FkZWRUaW1lSW5NczpudWxsLHN0YXRlOk9lLk5PVF9MT0FERUR9KSxySD1KKGdsLG49Pm51bGwhPT1uLnVzZXJTZXRHcm91cEJ5S2V5P2pJKG4udXNlclNldEdyb3VwQnlLZXksbi5jb2xvckdyb3VwUmVnZXhTdHJpbmcpOm51bGwpLEVlZT1KKHJILGdsLChuLHQpPT5uPz90LmluaXRpYWxHcm91cEJ5KSxRbT1KKGdsLG49Pm4ucmVnZXhGaWx0ZXIpLG9IPUooTWVlLG49Pm4udWkpLHNIPUoob0gsbj0+bi5wYWdpbmF0aW9uT3B0aW9uKSxhSD1KKG9ILG49Pm4uc29ydCksVGVlPUoob0gsbj0+bi5zZWxlY3Rpb25TdGF0ZSksRGVlPUooZ2wsbj0+bi5ydW5Db2xvck92ZXJyaWRlRm9yR3JvdXBCeSksQWVlPUooZ2wsbj0+bi5kZWZhdWx0UnVuQ29sb3JJZEZvckdyb3VwQnkpLFlJPUooZ2wsbj0+bi5jb2xvckdyb3VwUmVnZXhTdHJpbmcpLFhJPWJlKCJbU2V0dGluZ3NdIFJlbG9hZCBFbmFibGUgVG9nZ2xlZCIpLFFJPWJlKCJbU2V0dGluZ3NdIFJlbG9hZCBQZXJpb2QgQ2hhbmdlIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksS0k9YmUoIltTZXR0aW5nc10gUGFnZSBTaXplIENoYW5nZSIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLE5hPXt9O0JFKE5hLHtnZXRDb2xvclBhbGV0dGU6KCk9PkhQZSxnZXRQYWdlU2l6ZTooKT0+S20sZ2V0UmVsb2FkRW5hYmxlZDooKT0+WU0sZ2V0UmVsb2FkUGVyaW9kSW5NczooKT0+WE0sZ2V0U2V0dGluZ3NMb2FkU3RhdGU6KCk9PmxIfSk7dmFyIFpJPSJzZXR0aW5ncyIsUmVlPXtzdGF0ZTpPZS5MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOkRhdGUubm93KCksc2V0dGluZ3M6e3JlbG9hZFBlcmlvZEluTXM6M2U0LHJlbG9hZEVuYWJsZWQ6ITEscGFnZVNpemU6MTIsY29sb3JQYWxldHRlOntpZDoiZGVmYXVsdCIsbmFtZToiRGVmYWx0Iixjb2xvcnM6W3tuYW1lOiJTbGF0ZSIsbGlnaHRIZXg6IiM0MjUwNjYiLGRhcmtIZXg6IiM4ZTk4YTMifSx7bmFtZToiQ3lhbiIsbGlnaHRIZXg6IiMxMmI1Y2IiLGRhcmtIZXg6IiMxMmI1Y2IifSx7bmFtZToiUGluayIsbGlnaHRIZXg6IiNlNTI1OTIiLGRhcmtIZXg6IiNlNTI1OTIifSx7bmFtZToiWWVsbG93IixsaWdodEhleDoiI2Y5YWIwMCIsZGFya0hleDoiI2Y5YWIwMCJ9LHtuYW1lOiJQdXJwbGUiLGxpZ2h0SGV4OiIjOTMzNGU2IixkYXJrSGV4OiIjOTMzNGU2In0se25hbWU6IkxpZ2h0IEdyZWVuIixsaWdodEhleDoiIzdjYjM0MiIsZGFya0hleDoiIzdjYjM0MiJ9LHtuYW1lOiJPcmFuZ2UiLGxpZ2h0SGV4OiIjZTg3MTBhIixkYXJrSGV4OiIjZTg3MTBhIn1dLGluYWN0aXZlOntuYW1lOiJHcmF5IixsaWdodEhleDoiI2UwZTBlMCIsZGFya0hleDoiIzNiM2IzYiJ9fX19LHFNPU1yKFpJKSxsSD1KKHFNLG49Pm4uc3RhdGUpLFlNPUoocU0sbj0+bi5zZXR0aW5ncy5yZWxvYWRFbmFibGVkKSxYTT1KKHFNLG49Pm4uc2V0dGluZ3MucmVsb2FkUGVyaW9kSW5NcyksS209SihxTSxuPT5uLnNldHRpbmdzLnBhZ2VTaXplKSxIUGU9SihxTSxuPT5uLnNldHRpbmdzLmNvbG9yUGFsZXR0ZSk7ZnVuY3Rpb24gSkkobix0LGUpe2lmKCF0KXJldHVybiEwO2xldCBpO3RyeXtpPW5ldyBSZWdFeHAodCwiaSIpfWNhdGNoe3JldHVybiExfWxldCByPVtuLnJ1bk5hbWVdO3JldHVybiBlJiZyLnB1c2gobi5leHBlcmltZW50QWxpYXMuYWxpYXNUZXh0LGAke24uZXhwZXJpbWVudEFsaWFzLmFsaWFzVGV4dH0vJHtuLnJ1bk5hbWV9YCksci5zb21lKG89PmkudGVzdChvKSl9dmFyIFVQZT1KKFdvLFRlZSx3ZWUsKG4sdCxlKT0+e2lmKCFuKXJldHVybiBuZXcgTWFwO2xldCBpPW5ldyBNYXA7Zm9yKGxldFtyLG9db2YgdC5lbnRyaWVzKCkpe2xldCBzPWVbcl07cyYmbi5pbmRleE9mKHMpPj0wJiZpLnNldChyLG8pfXJldHVybiBpfSksb289SihXbyxVUGUsUW0sbj0+e2xldCB0PVdvKG4pPz9bXSxlPVl1KG4pLGk9bmV3IE1hcDtmb3IobGV0IHIgb2YgdCl7bGV0IG89cmQobix7ZXhwZXJpbWVudElkOnJ9KTtmb3IobGV0IHMgb2YgbylpLnNldChzLmlkLHtydW5OYW1lOnMubmFtZSxleHBlcmltZW50QWxpYXM6ZVtyXX0pfXJldHVybiBpfSxxdSwobix0LGUsaSxyKT0+e2lmKCFuKXJldHVybiBudWxsO2xldCBvPXI9PT1oaS5DT01QQVJFX0VYUEVSSU1FTlQscz1uZXcgTWFwO2ZvcihsZXRbYSxsXW9mIHQuZW50cmllcygpKXtsZXQgYz1pLmdldChhKTtzLnNldChhLEpJKGMsZSxvKSYmbCl9cmV0dXJuIHN9KSxuYz1KKE5hLmdldENvbG9yUGFsZXR0ZSxBZWUsRGVlLFF1LChuLHQsZSxpKT0+e2xldCByPXt9O3JldHVybiB0LmZvckVhY2goKG8scyk9PntsZXQgYT1pP24uaW5hY3RpdmUuZGFya0hleDpuLmluYWN0aXZlLmxpZ2h0SGV4O2lmKGUuaGFzKHMpKWE9ZS5nZXQocyk7ZWxzZSBpZihvPj0wKXtsZXQgbD1uLmNvbG9yc1tvJW4uY29sb3JzLmxlbmd0aF07YT1pP2wuZGFya0hleDpsLmxpZ2h0SGV4fXJbc109YX0pLHJ9KSxPZWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5hY3Rpb25zJD1lLHRoaXMuc3RvcmU9aSx0aGlzLnJ1bnNEYXRhU291cmNlPXIsdGhpcy5sb2FkUnVuc09uUnVuVGFibGVTaG93biQ9Y3IoKCk9PnRoaXMuYWN0aW9ucyQucGlwZShpaShkSSkseG4oKHtleHBlcmltZW50SWRzOm99KT0+dGhpcy5nZXRFeHBlcmltZW50c1dpdGhMb2FkU3RhdGUobyxhPT5hPT09T2UuRkFJTEVEfHxhPT09T2UuTk9UX0xPQURFRCkucGlwZShZZShhPT4hIWEubGVuZ3RoKSx4bihhPT50aGlzLmZldGNoQWxsUnVuc0xpc3QobyxhKSkpKSkse2Rpc3BhdGNoOiExfSksdGhpcy5leHBlcmltZW50c1dpdGhTdGFsZVJ1bnNPblJvdXRlQ2hhbmdlJD10aGlzLmFjdGlvbnMkLnBpcGUoaWkoSmwpLFd0KHRoaXMuc3RvcmUuc2VsZWN0KFJhKSkseWkoKFssb10sWyxzXSk9PlBzKG8scykpLFd0KHRoaXMuc3RvcmUuc2VsZWN0KFdvKSksWWUoKFssb10pPT4hIW8pLEwoKFssb10pPT5vKSx4bihvPT50aGlzLmdldEV4cGVyaW1lbnRzV2l0aExvYWRTdGF0ZShvLHM9PnM9PT1PZS5GQUlMRUR8fHM9PT1PZS5OT1RfTE9BREVEKS5waXBlKEwocz0+KHtleHBlcmltZW50SWRzOm8sZXhwZXJpbWVudElkc1RvQmVGZXRjaGVkOnN9KSkpKSksdGhpcy5leHBlcmltZW50c1dpdGhTdGFsZVJ1bnNPblJlbG9hZCQ9dGhpcy5hY3Rpb25zJC5waXBlKGlpKGFhLEZhKSxXdCh0aGlzLnN0b3JlLnNlbGVjdChXbykpLFllKChbLG9dKT0+ISFvKSxMKChbLG9dKT0+bykseG4obz0+dGhpcy5nZXRFeHBlcmltZW50c1dpdGhMb2FkU3RhdGUobyxzPT5zIT09T2UuTE9BRElORykucGlwZShMKHM9Pih7ZXhwZXJpbWVudElkczpvLGV4cGVyaW1lbnRJZHNUb0JlRmV0Y2hlZDpzfSkpKSkpLHRoaXMubG9hZFJ1bnNPbk5hdmlnYXRpb25PclJlbG9hZCQ9Y3IoKCk9Pkp0KHRoaXMuZXhwZXJpbWVudHNXaXRoU3RhbGVSdW5zT25Sb3V0ZUNoYW5nZSQsdGhpcy5leHBlcmltZW50c1dpdGhTdGFsZVJ1bnNPblJlbG9hZCQpLnBpcGUoeG4oKHtleHBlcmltZW50SWRzOm8sZXhwZXJpbWVudElkc1RvQmVGZXRjaGVkOnN9KT0+dGhpcy5mZXRjaEFsbFJ1bnNMaXN0KG8scykpKSx7ZGlzcGF0Y2g6ITF9KX1nZXRSdW5zTGlzdExvYWRTdGF0ZShlKXtyZXR1cm4gdGhpcy5zdG9yZS5zZWxlY3QoV00se2V4cGVyaW1lbnRJZDplfSkucGlwZShRdCgxKSl9Z2V0RXhwZXJpbWVudHNXaXRoTG9hZFN0YXRlKGUsaSl7cmV0dXJuIGxyKGUubWFwKHI9PnRoaXMuZ2V0UnVuc0xpc3RMb2FkU3RhdGUocikpKS5waXBlKEwocj0+ZS5maWx0ZXIoKG8scyk9PmkocltzXS5zdGF0ZSkpKSl9ZmV0Y2hBbGxSdW5zTGlzdChlLGkpe3JldHVybiBYdCh7ZXhwZXJpbWVudElkczplLGV4cGVyaW1lbnRJZHNUb0JlRmV0Y2hlZDppfSkucGlwZShrdCgoKT0+e3RoaXMuc3RvcmUuZGlzcGF0Y2gockkoe2V4cGVyaW1lbnRJZHM6ZSxyZXF1ZXN0ZWRFeHBlcmltZW50SWRzOml9KSl9KSx4bigoKT0+e2xldCByPW5ldyBTZXQoaSk7cmV0dXJuIGxyKGUubWFwKHM9PnIuaGFzKHMpP3RoaXMuZmV0Y2hSdW5zRm9yRXhwZXJpbWVudChzKTp0aGlzLm1heWJlV2FpdEZvclJ1bnNBbmRHZXRSdW5zKHMpKSl9KSxMKHI9PntsZXQgbz17fSxzPVtdO2ZvcihsZXQgYSBvZiByKXMucHVzaCguLi5hLnJ1bnMpLGEuZnJvbVJlbW90ZSYmKG9bYS5leHBlcmltZW50SWRdPXtydW5zOmEucnVucyxtZXRhZGF0YTphLm1ldGFkYXRhfSk7cmV0dXJue25ld1J1bnNBbmRNZXRhZGF0YTpvLHJ1bnNGb3JBbGxFeHBlcmltZW50czpzfX0pLGt0KCh7bmV3UnVuc0FuZE1ldGFkYXRhOnIscnVuc0ZvckFsbEV4cGVyaW1lbnRzOm99KT0+e3RoaXMuc3RvcmUuZGlzcGF0Y2godmgoe2V4cGVyaW1lbnRJZHM6ZSxuZXdSdW5zQW5kTWV0YWRhdGE6cixydW5zRm9yQWxsRXhwZXJpbWVudHM6b30pKX0pLGZvKHI9Pih0aGlzLnN0b3JlLmRpc3BhdGNoKHN2KHtleHBlcmltZW50SWRzOmUscmVxdWVzdGVkRXhwZXJpbWVudElkczppfSkpLFh0KG51bGwpKSksTCgoKT0+bnVsbCkpfW1heWJlV2FpdEZvclJ1bnNBbmRHZXRSdW5zKGUpe3JldHVybiB0aGlzLnN0b3JlLnNlbGVjdChXTSx7ZXhwZXJpbWVudElkOmV9KS5waXBlKFllKGk9Pmkuc3RhdGUhPT1PZS5MT0FESU5HKSxRdCgxKSx4bihpPT5pLnN0YXRlPT09T2UuRkFJTEVEP3djKG5ldyBFcnJvcigiUGVuZGluZyByZXF1ZXN0IGZhaWxlZCIpKTpYdChpKSksV3QodGhpcy5zdG9yZS5zZWxlY3QocmQse2V4cGVyaW1lbnRJZDplfSkpLEwoKFssaV0pPT4oe2Zyb21SZW1vdGU6ITEsZXhwZXJpbWVudElkOmUscnVuczppfSkpKX1mZXRjaFJ1bnNGb3JFeHBlcmltZW50KGUpe3JldHVybiBscihbdGhpcy5ydW5zRGF0YVNvdXJjZS5mZXRjaFJ1bnMoZSksdGhpcy5ydW5zRGF0YVNvdXJjZS5mZXRjaEhwYXJhbXNNZXRhZGF0YShlKV0pLnBpcGUoTCgoW2kscl0pPT4oe2Zyb21SZW1vdGU6ITAsZXhwZXJpbWVudElkOmUscnVuczppLG1ldGFkYXRhOnJ9KSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKFBvKSxqKENlKSxqKHBJKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksaWM9KCgpPT4oZnVuY3Rpb24obil7bi5BU0M9ImFzYyIsbi5ERVNDPSJkZXNjIixuLlVOU0VUPSIifShpY3x8KGljPXt9KSksaWMpKSgpLHtpbml0aWFsU3RhdGU6elBlLHJlZHVjZXJzOmpQZX09em0oe3J1bkNvbG9yT3ZlcnJpZGVGb3JHcm91cEJ5Om5ldyBNYXAsZGVmYXVsdFJ1bkNvbG9ySWRGb3JHcm91cEJ5Om5ldyBNYXAsZ3JvdXBLZXlUb0NvbG9ySWQ6bmV3IE1hcCxpbml0aWFsR3JvdXBCeTp7a2V5OnNyLlJVTn0sdXNlclNldEdyb3VwQnlLZXk6bnVsbCxjb2xvckdyb3VwUmVnZXhTdHJpbmc6IiIscmVnZXhGaWx0ZXI6IiJ9LHtydW5JZHM6e30scnVuSWRUb0V4cElkOnt9LHJ1bk1ldGFkYXRhOnt9LHJ1bnNMb2FkU3RhdGU6e319LChuLHQsZSk9PlBzKHQsZSk/bjp7Li4ubixpbml0aWFsR3JvdXBCeTp7a2V5OmUucm91dGVLaW5kPT09aGkuQ09NUEFSRV9FWFBFUklNRU5UP3NyLkVYUEVSSU1FTlQ6c3IuUlVOfX0pLEdQZT12cih6UGUsU2UoS18sKG4se3JvdXRlS2luZDp0LHBhcnRpYWxTdGF0ZTplfSk9PntpZih0IT09aGkuQ09NUEFSRV9FWFBFUklNRU5UJiZ0IT09aGkuRVhQRVJJTUVOVClyZXR1cm4gbjtsZXQgcj1lLnJ1bnMuZ3JvdXBCeSxvPWUucnVucy5yZWdleEZpbHRlcj8/IiI7aWYoIXImJiFvKXJldHVybiBuO2xldHtjb2xvckdyb3VwUmVnZXhTdHJpbmc6cyx1c2VyU2V0R3JvdXBCeUtleTphfT1uO3JldHVybiByJiYocz1yLmtleT09PXNyLlJFR0VYP3IucmVnZXhTdHJpbmc6bi5jb2xvckdyb3VwUmVnZXhTdHJpbmcsYT1yLmtleT8/bnVsbCksey4uLm4sY29sb3JHcm91cFJlZ2V4U3RyaW5nOnMscmVnZXhGaWx0ZXI6byx1c2VyU2V0R3JvdXBCeUtleTphfX0pLFNlKHJJLChuLHQpPT57bGV0IGU9ey4uLm4ucnVuc0xvYWRTdGF0ZX07Zm9yKGxldCBpIG9mIHQucmVxdWVzdGVkRXhwZXJpbWVudElkcyllW2ldPWVbaV0/ey4uLmVbaV0sc3RhdGU6T2UuTE9BRElOR306e2xhc3RMb2FkZWRUaW1lSW5NczpudWxsLHN0YXRlOk9lLkxPQURJTkd9O3JldHVybnsuLi5uLHJ1bnNMb2FkU3RhdGU6ZX19KSxTZSh2aCwobix0KT0+e2xldCBlPXsuLi5uLnJ1bklkc30saT17Li4ubi5ydW5NZXRhZGF0YX0scj17Li4ubi5ydW5JZFRvRXhwSWR9LG89ey4uLm4ucnVuc0xvYWRTdGF0ZX07Zm9yKGxldCBzIG9mIE9iamVjdC5rZXlzKHQubmV3UnVuc0FuZE1ldGFkYXRhKSl7bGV0e3J1bnM6YSxtZXRhZGF0YTpsfT10Lm5ld1J1bnNBbmRNZXRhZGF0YVtzXTtlW3NdPWEubWFwKCh7aWQ6Y30pPT5jKSxvW3NdPXsuLi5vW3NdLGxhc3RMb2FkZWRUaW1lSW5NczpEYXRlLm5vdygpLHN0YXRlOk9lLkxPQURFRH07Zm9yKGxldCBjIG9mIGEpe2xldCB1PWwucnVuVG9IcGFyYW1zQW5kTWV0cmljc1tjLmlkXTtpW2MuaWRdPXsuLi5jLGhwYXJhbXM6dT91LmhwYXJhbXM6bnVsbCxtZXRyaWNzOnU/dS5tZXRyaWNzOm51bGx9LHJbYy5pZF09c319cmV0dXJuey4uLm4scnVuSWRzOmUscnVuSWRUb0V4cElkOnIscnVuTWV0YWRhdGE6aSxydW5zTG9hZFN0YXRlOm99fSksU2Uoc3YsKG4sdCk9PntsZXQgZT17Li4ubi5ydW5zTG9hZFN0YXRlfTtmb3IobGV0IGkgb2YgdC5yZXF1ZXN0ZWRFeHBlcmltZW50SWRzKWVbaV09ZVtpXT97Li4uZVtpXSxzdGF0ZTpPZS5GQUlMRUR9OntsYXN0TG9hZGVkVGltZUluTXM6bnVsbCxzdGF0ZTpPZS5GQUlMRUR9O3JldHVybnsuLi5uLHJ1bnNMb2FkU3RhdGU6ZX19KSxTZSh2aCwobix7cnVuc0ZvckFsbEV4cGVyaW1lbnRzOnR9KT0+e2xldCBlPW5ldyBNYXAobi5ncm91cEtleVRvQ29sb3JJZCksaT1uZXcgTWFwKG4uZGVmYXVsdFJ1bkNvbG9ySWRGb3JHcm91cEJ5KSxyPW4uaW5pdGlhbEdyb3VwQnk7bnVsbCE9PW4udXNlclNldEdyb3VwQnlLZXkmJihyPWpJKG4udXNlclNldEdyb3VwQnlLZXksbi5jb2xvckdyb3VwUmVnZXhTdHJpbmcpKTtsZXQgbz1HTShyLHQsbi5ydW5JZFRvRXhwSWQpO09iamVjdC5lbnRyaWVzKG8ubWF0Y2hlcykuZm9yRWFjaCgoW3MsYV0pPT57bGV0IGw9ZS5nZXQocyk/P2Uuc2l6ZTtlLnNldChzLGwpO2ZvcihsZXQgYyBvZiBhKWkuc2V0KGMuaWQsbCl9KTtmb3IobGV0IHMgb2Ygby5ub25NYXRjaGVzKWkuc2V0KHMuaWQsLTEpO3JldHVybnsuLi5uLGRlZmF1bHRSdW5Db2xvcklkRm9yR3JvdXBCeTppLGdyb3VwS2V5VG9Db2xvcklkOmV9fSksU2UoYXYsKG4se2V4cGVyaW1lbnRJZHM6dCxncm91cEJ5OmV9KT0+e2xldCBpPW5ldyBNYXAscj1uZXcgTWFwKG4uZGVmYXVsdFJ1bkNvbG9ySWRGb3JHcm91cEJ5KSxzPUdNKGUsdC5mbGF0TWFwKGw9Pm4ucnVuSWRzW2xdKS5tYXAobD0+bi5ydW5NZXRhZGF0YVtsXSksbi5ydW5JZFRvRXhwSWQpO09iamVjdC5lbnRyaWVzKHMubWF0Y2hlcykuZm9yRWFjaCgoW2wsY10pPT57bGV0IHU9aS5nZXQobCk/P2kuc2l6ZTtpLnNldChsLHUpO2ZvcihsZXQgZCBvZiBjKXIuc2V0KGQuaWQsdSl9KTtmb3IobGV0IGwgb2Ygcy5ub25NYXRjaGVzKXIuc2V0KGwuaWQsLTEpO2xldCBhPWUua2V5PT09c3IuUkVHRVg/ZS5yZWdleFN0cmluZzpuLmNvbG9yR3JvdXBSZWdleFN0cmluZztyZXR1cm57Li4ubixjb2xvckdyb3VwUmVnZXhTdHJpbmc6YSx1c2VyU2V0R3JvdXBCeUtleTplLmtleSxkZWZhdWx0UnVuQ29sb3JJZEZvckdyb3VwQnk6cixncm91cEtleVRvQ29sb3JJZDppLHJ1bkNvbG9yT3ZlcnJpZGVGb3JHcm91cEJ5Om5ldyBNYXB9fSksU2UodUksKG4se3J1bklkOnQsbmV3Q29sb3I6ZX0pPT57bGV0IGk9bmV3IE1hcChuLnJ1bkNvbG9yT3ZlcnJpZGVGb3JHcm91cEJ5KTtyZXR1cm4gaS5zZXQodCxlKSx7Li4ubixydW5Db2xvck92ZXJyaWRlRm9yR3JvdXBCeTppfX0pLFNlKE5NLChuLHQpPT4oey4uLm4scmVnZXhGaWx0ZXI6dC5yZWdleFN0cmluZ30pKSksV1BlPWptKEdQZSxqUGUpLHFQZT17a2V5Om51bGwsZGlyZWN0aW9uOmljLlVOU0VUfSx7aW5pdGlhbFN0YXRlOllQZSxyZWR1Y2VyczpYUGV9PXptKHtwYWdpbmF0aW9uT3B0aW9uOntwYWdlSW5kZXg6MCxwYWdlU2l6ZToxMH0sc29ydDpxUGUsc2VsZWN0aW9uU3RhdGU6bmV3IE1hcH0se30pLFFQZT12cihZUGUsU2UobEksKG4se3BhZ2VTaXplOnQscGFnZUluZGV4OmV9KT0+KHsuLi5uLHBhZ2luYXRpb25PcHRpb246e3BhZ2VTaXplOnQscGFnZUluZGV4OmV9fSkpLFNlKE5NLChuLHQpPT4oey4uLm4scGFnaW5hdGlvbk9wdGlvbjp7Li4ubi5wYWdpbmF0aW9uT3B0aW9uLHBhZ2VJbmRleDowfX0pKSxTZShjSSwobix0KT0+KHsuLi5uLHNvcnQ6e2tleTp0LmtleSxkaXJlY3Rpb246dC5kaXJlY3Rpb259fSkpLFNlKHZoLChuLHQpPT57bGV0IGU9bmV3IE1hcChuLnNlbGVjdGlvblN0YXRlKSxpPXQucnVuc0ZvckFsbEV4cGVyaW1lbnRzLmxlbmd0aDw9NTAwO2ZvcihsZXQgciBvZiB0LnJ1bnNGb3JBbGxFeHBlcmltZW50cyllLmhhcyhyLmlkKXx8ZS5zZXQoci5pZCxpKTtyZXR1cm57Li4ubixzZWxlY3Rpb25TdGF0ZTplfX0pLFNlKG9JLChuLHtydW5JZDp0fSk9PntsZXQgZT1uZXcgTWFwKG4uc2VsZWN0aW9uU3RhdGUpO3JldHVybiBlLnNldCh0LCFCb29sZWFuKGUuZ2V0KHQpKSksey4uLm4sc2VsZWN0aW9uU3RhdGU6ZX19KSxTZShzSSwobix7cnVuSWQ6dH0pPT57bGV0IGU9bmV3IE1hcDtmb3IobGV0IGkgb2Ygbi5zZWxlY3Rpb25TdGF0ZS5rZXlzKCkpZS5zZXQoaSx0PT09aSk7cmV0dXJuey4uLm4sc2VsZWN0aW9uU3RhdGU6ZX19KSxTZShhSSwobix7cnVuSWRzOnR9KT0+e2xldCBlPW5ldyBNYXAobi5zZWxlY3Rpb25TdGF0ZSksaT0hdC5ldmVyeShyPT5Cb29sZWFuKGUuZ2V0KHIpKSk7Zm9yKGxldCByIG9mIHQpZS5zZXQocixpKTtyZXR1cm57Li4ubixzZWxlY3Rpb25TdGF0ZTplfX0pKSxLUGU9am0oUVBlLFhQZSk7ZnVuY3Rpb24ga2VlKG4sdCl7cmV0dXJuIEZtKHtkYXRhOldQZSx1aTpLUGV9KShuLHQpfWZ1bmN0aW9uIFpQZSgpe3JldHVyblt7YWN0aW9uQ3JlYXRvcjpzdixhbGVydEZyb21BY3Rpb246KCk9Pih7bG9jYWxpemVkTWVzc2FnZToiRmFpbGVkIHRvIGZldGNoIHJ1bnMifSl9XX12YXIgJEk9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W3dyLmZvckZlYXR1cmUoInJ1bnMiLGtlZSkscm8uZm9yRmVhdHVyZShbT2VlXSksVyQsSnUucmVnaXN0ZXJBbGVydEFjdGlvbnMoWlBlKSxtSV19KSxufSkoKSxzcD0oKCk9PihmdW5jdGlvbihuKXtuLlJVTlNfQ0hBTkdFRD0iZXhwZXJpbWVudGFsLlJ1bnNDaGFuZ2VkIixuLkdFVF9SVU5TPSJleHBlcmltZW50YWwuR2V0UnVucyIsbi5HRVRfVVJMX0RBVEE9ImV4cGVyaW1lbnRhbC5HZXRVUkxQbHVnaW5EYXRhIixuLkRBVEFfUkVMT0FERUQ9ImV4cGVyaW1lbnRhbC5EYXRhUmVsb2FkZWQifShzcHx8KHNwPXt9KSksc3ApKSgpLE5lZT1uZXcgV2Vha01hcCxndj1uZXcgU2V0LGNIPW5ldyBNYXAsdDI9bmV3IE1hcDtmdW5jdGlvbiBMZWUobix0KXtyZXR1cm4gZT0+e2xldCBpPXQyLmdldCh0KSxyPU5lZS5nZXQoaSl8fG51bGw7cmV0dXJuIG4ocixlKX19d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLG49PntpZigiZXhwZXJpbWVudGFsLmJvb3RzdHJhcCIhPT1uLmRhdGEpcmV0dXJuO2xldCB0PW4ucG9ydHNbMF07aWYoIXQpcmV0dXJuO2xldCBlPW4uc291cmNlP24uc291cmNlLmZyYW1lRWxlbWVudDpudWxsOyFlfHxmdW5jdGlvbihuLHQpe2xldCBlPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLnBvcnQ9dCx0aGlzLmlkPTAsdGhpcy5yZXNwb25zZVdhaXRzPW5ldyBNYXAsdGhpcy5saXN0ZW5lcnM9bmV3IE1hcCx0aGlzLnBvcnQuYWRkRXZlbnRMaXN0ZW5lcigibWVzc2FnZSIsZT0+dGhpcy5vbk1lc3NhZ2UoZSkpfWxpc3Rlbih0LGUpe3RoaXMubGlzdGVuZXJzLnNldCh0LGUpfXVubGlzdGVuKHQpe3RoaXMubGlzdGVuZXJzLmRlbGV0ZSh0KX1hc3luYyBvbk1lc3NhZ2UodCl7bGV0IGU9SlNPTi5wYXJzZSh0LmRhdGEpLGk9ZS50eXBlLHI9ZS5pZCxvPWUucGF5bG9hZCxzPWUuZXJyb3I7aWYoZS5pc1JlcGx5KXtpZighdGhpcy5yZXNwb25zZVdhaXRzLmhhcyhyKSlyZXR1cm47bGV0e3Jlc29sdmU6ZCxyZWplY3Q6cH09dGhpcy5yZXNwb25zZVdhaXRzLmdldChyKTtyZXR1cm4gdGhpcy5yZXNwb25zZVdhaXRzLmRlbGV0ZShyKSx2b2lkKHM/cChuZXcgRXJyb3IocykpOmQobykpfWxldCBsPW51bGwsYz1udWxsO2lmKHRoaXMubGlzdGVuZXJzLmhhcyhpKSl7bGV0IGQ9dGhpcy5saXN0ZW5lcnMuZ2V0KGkpO3RyeXtsPWF3YWl0IGQobyl9Y2F0Y2gocCl7Yz1wfX10aGlzLnBvc3RNZXNzYWdlKHt0eXBlOmksaWQ6cixwYXlsb2FkOmwsZXJyb3I6Yyxpc1JlcGx5OiEwfSl9cG9zdE1lc3NhZ2UodCl7dGhpcy5wb3J0LnBvc3RNZXNzYWdlKEpTT04uc3RyaW5naWZ5KHQpKX1zZW5kTWVzc2FnZSh0LGUpe2xldCBpPXRoaXMuaWQrKztyZXR1cm4gdGhpcy5wb3N0TWVzc2FnZSh7dHlwZTp0LGlkOmkscGF5bG9hZDplLGVycm9yOm51bGwsaXNSZXBseTohMX0pLG5ldyBQcm9taXNlKChvLHMpPT57dGhpcy5yZXNwb25zZVdhaXRzLnNldChpLHtyZXNvbHZlOm8scmVqZWN0OnN9KX0pfX0obik7Z3YuYWRkKGUpLHQyLnNldChlLHQpLG4uc3RhcnQoKTtmb3IobGV0W2kscl1vZiBjSCl7bGV0IG89TGVlKHIsZSk7ZS5saXN0ZW4oaSxvKX19KHQsZSl9KTt2YXIgZEgsX3Y9KCgpPT57Y2xhc3Mgbnticm9hZGNhc3QoZSxpKXtyZXR1cm4gZnVuY3Rpb24obix0KXtmb3IobGV0IGkgb2YgZ3YpdDIuZ2V0KGkpLmlzQ29ubmVjdGVkfHwoZ3YuZGVsZXRlKGkpLHQyLmRlbGV0ZShpKSk7bGV0IGU9Wy4uLmd2XS5tYXAoaT0+aS5zZW5kTWVzc2FnZShuLHQpKTtyZXR1cm4gUHJvbWlzZS5hbGwoZSl9KGUsaSl9bGlzdGVuKGUsaSl7IWZ1bmN0aW9uKG4sdCl7Y0guc2V0KG4sdCk7Zm9yKGxldCBlIG9mIGd2KXtsZXQgaT1MZWUodCxlKTtlLmxpc3RlbihuLGkpfX0oZSxpKX11bmxpc3RlbihlKXshZnVuY3Rpb24obil7Y0guZGVsZXRlKG4pO2ZvcihsZXQgdCBvZiBndil0LnVubGlzdGVuKG4pfShlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksbjI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuaXBjPWUsdGhpcy5zdG9yZT1pfWluaXQoKXtsZXQgZT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0Zi1zdG9yYWdlIik7dGhpcy5pcGMubGlzdGVuKHNwLkdFVF9VUkxfREFUQSxpPT57aWYoIWkpcmV0dXJuO2xldCByPWBwLiR7aS5wbHVnaW5OYW1lfS5gLG89e30scz1lLnRmX3N0b3JhZ2UuZ2V0VXJsSGFzaERpY3QoKTtmb3IobGV0IGEgaW4gcylhLnN0YXJ0c1dpdGgocikmJihvW2Euc3Vic3RyaW5nKHIubGVuZ3RoKV09c1thXSk7cmV0dXJuIG99KSx0aGlzLnN0b3JlLnNlbGVjdChpdikucGlwZShZZShpPT5udWxsIT09aSkseWkoKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5pcGMuYnJvYWRjYXN0KHNwLkRBVEFfUkVMT0FERUQsdm9pZCAwKX0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKF92KSxqKENlKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksaTI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuaXBjPWUsdGhpcy5zdG9yZT1pfWluaXQoKXtsZXQgZT10aGlzLnN0b3JlLnNlbGVjdChXbykucGlwZSh4bihpPT5pP0x0KGkubWFwKG89PnRoaXMuc3RvcmUuc2VsZWN0KHJkLHtleHBlcmltZW50SWQ6b30pKSkucGlwZShMKG89Pm8uZmxhdCgpKSx5aSgobyxzKT0+by5sZW5ndGg9PT1zLmxlbmd0aCYmby5ldmVyeSgoYSxsKT0+c1tsXS5pZD09PWEuaWQpKSxMKG89Pm8ubWFwKCh7bmFtZTpzfSk9PnMpKSk6WHQoW10pKSk7ZS5zdWJzY3JpYmUoaT0+e3RoaXMuaXBjLmJyb2FkY2FzdChzcC5SVU5TX0NIQU5HRUQsaSl9KSx0aGlzLmlwYy5saXN0ZW4oc3AuR0VUX1JVTlMsKCk9PmUucGlwZShRdCgxKSkudG9Qcm9taXNlKCkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKF92KSxqKENlKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCkscjI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe2kuaW5pdCgpLGUuaW5pdCgpfXJlZ2lzdGVyUGx1Z2luSWZyYW1lKGUsaSl7IWZ1bmN0aW9uKG4sdCl7TmVlLnNldChuLHtwbHVnaW5OYW1lOnR9KX0oZSxpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihpMiksaihuMikpfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W192LG4yLGkyXSxpbXBvcnRzOltKXyxlYywkSV19KSxufSkoKSx2dj1iZSgiW0FsZXJ0XSBBbGVydCBSZXBvcnRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLEhlZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLmFjdGlvbnMkPWUsdGhpcy5zdG9yZT1pLHRoaXMuYWxlcnRBY3Rpb25Nb2R1bGU9cix0aGlzLnJlcG9ydFJlZ2lzdGVyZWRBY3Rpb25BbGVydHMkPWNyKCgpPT50aGlzLmFjdGlvbnMkLnBpcGUoa3Qobz0+e2xldCBzPXRoaXMuYWxlcnRBY3Rpb25Nb2R1bGUuZ2V0QWxlcnRGcm9tQWN0aW9uKG8pO3MmJnRoaXMuc3RvcmUuZGlzcGF0Y2godnYocykpfSkpLHtkaXNwYXRjaDohMX0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKFBvKSxqKENlKSxqKEp1KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksYVJlPXZyKHtsYXRlc3RBbGVydDpudWxsfSxTZSh2diwobix7bG9jYWxpemVkTWVzc2FnZTp0LGZvbGxvd3VwQWN0aW9uOmV9KT0+e2xldCBpPXtsb2NhbGl6ZWRNZXNzYWdlOnQsY3JlYXRlZDpEYXRlLm5vdygpfTtyZXR1cm4gZSYmKGkuZm9sbG93dXBBY3Rpb249ZSksey4uLm4sbGF0ZXN0QWxlcnQ6aX19KSk7ZnVuY3Rpb24gVWVlKG4sdCl7cmV0dXJuIGFSZShuLHQpfXRyeXtkSD10eXBlb2YgSW50bDwidSImJkludGwudjhCcmVha0l0ZXJhdG9yfWNhdGNoe2RIPSExfXZhciB5dixRTSxvMixabSx1SCxvaT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuX3BsYXRmb3JtSWQ9ZSx0aGlzLmlzQnJvd3Nlcj10aGlzLl9wbGF0Zm9ybUlkP1hEKHRoaXMuX3BsYXRmb3JtSWQpOiJvYmplY3QiPT10eXBlb2YgZG9jdW1lbnQmJiEhZG9jdW1lbnQsdGhpcy5FREdFPXRoaXMuaXNCcm93c2VyJiYvKGVkZ2UpL2kudGVzdChuYXZpZ2F0b3IudXNlckFnZW50KSx0aGlzLlRSSURFTlQ9dGhpcy5pc0Jyb3dzZXImJi8obXNpZXx0cmlkZW50KS9pLnRlc3QobmF2aWdhdG9yLnVzZXJBZ2VudCksdGhpcy5CTElOSz10aGlzLmlzQnJvd3NlciYmISghd2luZG93LmNocm9tZSYmIWRIKSYmdHlwZW9mIENTUzwidSImJiF0aGlzLkVER0UmJiF0aGlzLlRSSURFTlQsdGhpcy5XRUJLSVQ9dGhpcy5pc0Jyb3dzZXImJi9BcHBsZVdlYktpdC9pLnRlc3QobmF2aWdhdG9yLnVzZXJBZ2VudCkmJiF0aGlzLkJMSU5LJiYhdGhpcy5FREdFJiYhdGhpcy5UUklERU5ULHRoaXMuSU9TPXRoaXMuaXNCcm93c2VyJiYvaVBhZHxpUGhvbmV8aVBvZC8udGVzdChuYXZpZ2F0b3IudXNlckFnZW50KSYmISgiTVNTdHJlYW0iaW4gd2luZG93KSx0aGlzLkZJUkVGT1g9dGhpcy5pc0Jyb3dzZXImJi8oZmlyZWZveHxtaW5lZmllbGQpL2kudGVzdChuYXZpZ2F0b3IudXNlckFnZW50KSx0aGlzLkFORFJPSUQ9dGhpcy5pc0Jyb3dzZXImJi9hbmRyb2lkL2kudGVzdChuYXZpZ2F0b3IudXNlckFnZW50KSYmIXRoaXMuVFJJREVOVCx0aGlzLlNBRkFSST10aGlzLmlzQnJvd3NlciYmL3NhZmFyaS9pLnRlc3QobmF2aWdhdG9yLnVzZXJBZ2VudCkmJnRoaXMuV0VCS0lUfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKEdkKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksemVlPVsiY29sb3IiLCJidXR0b24iLCJjaGVja2JveCIsImRhdGUiLCJkYXRldGltZS1sb2NhbCIsImVtYWlsIiwiZmlsZSIsImhpZGRlbiIsImltYWdlIiwibW9udGgiLCJudW1iZXIiLCJwYXNzd29yZCIsInJhZGlvIiwicmFuZ2UiLCJyZXNldCIsInNlYXJjaCIsInN1Ym1pdCIsInRlbCIsInRleHQiLCJ0aW1lIiwidXJsIiwid2VlayJdO2Z1bmN0aW9uIHBIKCl7aWYoeXYpcmV0dXJuIHl2O2lmKCJvYmplY3QiIT10eXBlb2YgZG9jdW1lbnR8fCFkb2N1bWVudClyZXR1cm4geXY9bmV3IFNldCh6ZWUpO2xldCBuPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImlucHV0Iik7cmV0dXJuIHl2PW5ldyBTZXQoemVlLmZpbHRlcih0PT4obi5zZXRBdHRyaWJ1dGUoInR5cGUiLHQpLG4udHlwZT09PXQpKSl9ZnVuY3Rpb24gbGEobil7cmV0dXJuIGZ1bmN0aW9uKCl7aWYobnVsbD09UU0mJnR5cGVvZiB3aW5kb3c8InUiKXRyeXt3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigidGVzdCIsbnVsbCxPYmplY3QuZGVmaW5lUHJvcGVydHkoe30sInBhc3NpdmUiLHtnZXQ6KCk9PlFNPSEwfSkpfWZpbmFsbHl7UU09UU18fCExfXJldHVybiBRTX0oKT9uOiEhbi5jYXB0dXJlfWZ1bmN0aW9uIHMyKCl7aWYobnVsbD09Wm0pe2lmKCJvYmplY3QiIT10eXBlb2YgZG9jdW1lbnR8fCFkb2N1bWVudHx8ImZ1bmN0aW9uIiE9dHlwZW9mIEVsZW1lbnR8fCFFbGVtZW50KXJldHVybiBabT0hMTtpZigic2Nyb2xsQmVoYXZpb3IiaW4gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnN0eWxlKVptPSEwO2Vsc2V7bGV0IG49RWxlbWVudC5wcm90b3R5cGUuc2Nyb2xsVG87Wm09ISFuJiYhL1x7XHMqXFtuYXRpdmUgY29kZVxdXHMqXH0vLnRlc3Qobi50b1N0cmluZygpKX19cmV0dXJuIFptfWZ1bmN0aW9uIGJ2KCl7aWYoIm9iamVjdCIhPXR5cGVvZiBkb2N1bWVudHx8IWRvY3VtZW50KXJldHVybiAwO2lmKG51bGw9PW8yKXtsZXQgbj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKSx0PW4uc3R5bGU7bi5kaXI9InJ0bCIsdC53aWR0aD0iMXB4Iix0Lm92ZXJmbG93PSJhdXRvIix0LnZpc2liaWxpdHk9ImhpZGRlbiIsdC5wb2ludGVyRXZlbnRzPSJub25lIix0LnBvc2l0aW9uPSJhYnNvbHV0ZSI7bGV0IGU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2IiksaT1lLnN0eWxlO2kud2lkdGg9IjJweCIsaS5oZWlnaHQ9IjFweCIsbi5hcHBlbmRDaGlsZChlKSxkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKG4pLG8yPTAsMD09PW4uc2Nyb2xsTGVmdCYmKG4uc2Nyb2xsTGVmdD0xLG8yPTA9PT1uLnNjcm9sbExlZnQ/MToyKSxuLnJlbW92ZSgpfXJldHVybiBvMn1mdW5jdGlvbiBhMihuKXtpZihmdW5jdGlvbigpe2lmKG51bGw9PXVIKXtsZXQgbj10eXBlb2YgZG9jdW1lbnQ8InUiP2RvY3VtZW50LmhlYWQ6bnVsbDt1SD0hKCFufHwhbi5jcmVhdGVTaGFkb3dSb290JiYhbi5hdHRhY2hTaGFkb3cpfXJldHVybiB1SH0oKSl7bGV0IHQ9bi5nZXRSb290Tm9kZT9uLmdldFJvb3ROb2RlKCk6bnVsbDtpZih0eXBlb2YgU2hhZG93Um9vdDwidSImJlNoYWRvd1Jvb3QmJnQgaW5zdGFuY2VvZiBTaGFkb3dSb290KXJldHVybiB0fXJldHVybiBudWxsfWZ1bmN0aW9uIEtNKCl7bGV0IG49dHlwZW9mIGRvY3VtZW50PCJ1IiYmZG9jdW1lbnQ/ZG9jdW1lbnQuYWN0aXZlRWxlbWVudDpudWxsO2Zvcig7biYmbi5zaGFkb3dSb290Oyl7bGV0IHQ9bi5zaGFkb3dSb290LmFjdGl2ZUVsZW1lbnQ7aWYodD09PW4pYnJlYWs7bj10fXJldHVybiBufWZ1bmN0aW9uIFFjKG4pe3JldHVybiBuLmNvbXBvc2VkUGF0aD9uLmNvbXBvc2VkUGF0aCgpWzBdOm4udGFyZ2V0fWZ1bmN0aW9uIFpNKCl7cmV0dXJuIHR5cGVvZiBfX2thcm1hX188InUiJiYhIV9fa2FybWFfX3x8dHlwZW9mIGphc21pbmU8InUiJiYhIWphc21pbmV8fHR5cGVvZiBqZXN0PCJ1IiYmISFqZXN0fHx0eXBlb2YgTW9jaGE8InUiJiYhIU1vY2hhfWZ1bmN0aW9uIGtyKG4sLi4udCl7cmV0dXJuIHQubGVuZ3RoP3Quc29tZShlPT5uW2VdKTpuLmFsdEtleXx8bi5zaGlmdEtleXx8bi5jdHJsS2V5fHxuLm1ldGFLZXl9ZnVuY3Rpb24gUnQobil7cmV0dXJuIG51bGwhPW4mJiJmYWxzZSIhPWAke259YH1mdW5jdGlvbiBCaShuLHQ9MCl7cmV0dXJuIGhIKG4pP051bWJlcihuKTp0fWZ1bmN0aW9uIGhIKG4pe3JldHVybiFpc05hTihwYXJzZUZsb2F0KG4pKSYmIWlzTmFOKE51bWJlcihuKSl9ZnVuY3Rpb24geHYobil7cmV0dXJuIEFycmF5LmlzQXJyYXkobik/bjpbbl19ZnVuY3Rpb24geW8obil7cmV0dXJuIG51bGw9PW4/IiI6InN0cmluZyI9PXR5cGVvZiBuP246YCR7bn1weGB9ZnVuY3Rpb24gTGEobil7cmV0dXJuIG4gaW5zdGFuY2VvZiBSZT9uLm5hdGl2ZUVsZW1lbnQ6bn12YXIgQ3YsR2VlPSgoKT0+e2NsYXNzIG57Y3JlYXRlKGUpe3JldHVybiB0eXBlb2YgTXV0YXRpb25PYnNlcnZlcj4idSI/bnVsbDpuZXcgTXV0YXRpb25PYnNlcnZlcihlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksdVJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5fbXV0YXRpb25PYnNlcnZlckZhY3Rvcnk9ZSx0aGlzLl9vYnNlcnZlZEVsZW1lbnRzPW5ldyBNYXB9bmdPbkRlc3Ryb3koKXt0aGlzLl9vYnNlcnZlZEVsZW1lbnRzLmZvckVhY2goKGUsaSk9PnRoaXMuX2NsZWFudXBPYnNlcnZlcihpKSl9b2JzZXJ2ZShlKXtsZXQgaT1MYShlKTtyZXR1cm4gbmV3IHVuKHI9PntsZXQgcz10aGlzLl9vYnNlcnZlRWxlbWVudChpKS5zdWJzY3JpYmUocik7cmV0dXJuKCk9PntzLnVuc3Vic2NyaWJlKCksdGhpcy5fdW5vYnNlcnZlRWxlbWVudChpKX19KX1fb2JzZXJ2ZUVsZW1lbnQoZSl7aWYodGhpcy5fb2JzZXJ2ZWRFbGVtZW50cy5oYXMoZSkpdGhpcy5fb2JzZXJ2ZWRFbGVtZW50cy5nZXQoZSkuY291bnQrKztlbHNle2xldCBpPW5ldyBrZSxyPXRoaXMuX211dGF0aW9uT2JzZXJ2ZXJGYWN0b3J5LmNyZWF0ZShvPT5pLm5leHQobykpO3ImJnIub2JzZXJ2ZShlLHtjaGFyYWN0ZXJEYXRhOiEwLGNoaWxkTGlzdDohMCxzdWJ0cmVlOiEwfSksdGhpcy5fb2JzZXJ2ZWRFbGVtZW50cy5zZXQoZSx7b2JzZXJ2ZXI6cixzdHJlYW06aSxjb3VudDoxfSl9cmV0dXJuIHRoaXMuX29ic2VydmVkRWxlbWVudHMuZ2V0KGUpLnN0cmVhbX1fdW5vYnNlcnZlRWxlbWVudChlKXt0aGlzLl9vYnNlcnZlZEVsZW1lbnRzLmhhcyhlKSYmKHRoaXMuX29ic2VydmVkRWxlbWVudHMuZ2V0KGUpLmNvdW50LS0sdGhpcy5fb2JzZXJ2ZWRFbGVtZW50cy5nZXQoZSkuY291bnR8fHRoaXMuX2NsZWFudXBPYnNlcnZlcihlKSl9X2NsZWFudXBPYnNlcnZlcihlKXtpZih0aGlzLl9vYnNlcnZlZEVsZW1lbnRzLmhhcyhlKSl7bGV0e29ic2VydmVyOmksc3RyZWFtOnJ9PXRoaXMuX29ic2VydmVkRWxlbWVudHMuZ2V0KGUpO2kmJmkuZGlzY29ubmVjdCgpLHIuY29tcGxldGUoKSx0aGlzLl9vYnNlcnZlZEVsZW1lbnRzLmRlbGV0ZShlKX19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooR2VlKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksd2g9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5fY29udGVudE9ic2VydmVyPWUsdGhpcy5fZWxlbWVudFJlZj1pLHRoaXMuX25nWm9uZT1yLHRoaXMuZXZlbnQ9bmV3IEcsdGhpcy5fZGlzYWJsZWQ9ITEsdGhpcy5fY3VycmVudFN1YnNjcmlwdGlvbj1udWxsfWdldCBkaXNhYmxlZCgpe3JldHVybiB0aGlzLl9kaXNhYmxlZH1zZXQgZGlzYWJsZWQoZSl7dGhpcy5fZGlzYWJsZWQ9UnQoZSksdGhpcy5fZGlzYWJsZWQ/dGhpcy5fdW5zdWJzY3JpYmUoKTp0aGlzLl9zdWJzY3JpYmUoKX1nZXQgZGVib3VuY2UoKXtyZXR1cm4gdGhpcy5fZGVib3VuY2V9c2V0IGRlYm91bmNlKGUpe3RoaXMuX2RlYm91bmNlPUJpKGUpLHRoaXMuX3N1YnNjcmliZSgpfW5nQWZ0ZXJDb250ZW50SW5pdCgpeyF0aGlzLl9jdXJyZW50U3Vic2NyaXB0aW9uJiYhdGhpcy5kaXNhYmxlZCYmdGhpcy5fc3Vic2NyaWJlKCl9bmdPbkRlc3Ryb3koKXt0aGlzLl91bnN1YnNjcmliZSgpfV9zdWJzY3JpYmUoKXt0aGlzLl91bnN1YnNjcmliZSgpO2xldCBlPXRoaXMuX2NvbnRlbnRPYnNlcnZlci5vYnNlcnZlKHRoaXMuX2VsZW1lbnRSZWYpO3RoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e3RoaXMuX2N1cnJlbnRTdWJzY3JpcHRpb249KHRoaXMuZGVib3VuY2U/ZS5waXBlKEhyKHRoaXMuZGVib3VuY2UpKTplKS5zdWJzY3JpYmUodGhpcy5ldmVudCl9KX1fdW5zdWJzY3JpYmUoKXt0aGlzLl9jdXJyZW50U3Vic2NyaXB0aW9uPy51bnN1YnNjcmliZSgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKHVSZSksTShSZSksTShfdCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJjZGtPYnNlcnZlQ29udGVudCIsIiJdXSxpbnB1dHM6e2Rpc2FibGVkOlsiY2RrT2JzZXJ2ZUNvbnRlbnREaXNhYmxlZCIsImRpc2FibGVkIl0sZGVib3VuY2U6ImRlYm91bmNlIn0sb3V0cHV0czp7ZXZlbnQ6ImNka09ic2VydmVDb250ZW50In0sZXhwb3J0QXM6WyJjZGtPYnNlcnZlQ29udGVudCJdfSksbn0pKCksb2Q9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbR2VlXX0pLG59KSgpLFdlZT1uZXcgU2V0LGRSZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuX3BsYXRmb3JtPWUsdGhpcy5fbWF0Y2hNZWRpYT10aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXImJndpbmRvdy5tYXRjaE1lZGlhP3dpbmRvdy5tYXRjaE1lZGlhLmJpbmQod2luZG93KTpoUmV9bWF0Y2hNZWRpYShlKXtyZXR1cm4odGhpcy5fcGxhdGZvcm0uV0VCS0lUfHx0aGlzLl9wbGF0Zm9ybS5CTElOSykmJmZ1bmN0aW9uKG4pe2lmKCFXZWUuaGFzKG4pKXRyeXtDdnx8KChDdj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzdHlsZSIpKS5zZXRBdHRyaWJ1dGUoInR5cGUiLCJ0ZXh0L2NzcyIpLGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoQ3YpKSxDdi5zaGVldCYmKEN2LnNoZWV0Lmluc2VydFJ1bGUoYEBtZWRpYSAke259IHtib2R5eyB9fWAsMCksV2VlLmFkZChuKSl9Y2F0Y2godCl7Y29uc29sZS5lcnJvcih0KX19KGUpLHRoaXMuX21hdGNoTWVkaWEoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGoob2kpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKTtmdW5jdGlvbiBoUmUobil7cmV0dXJue21hdGNoZXM6ImFsbCI9PT1ufHwiIj09PW4sbWVkaWE6bixhZGRMaXN0ZW5lcjooKT0+e30scmVtb3ZlTGlzdGVuZXI6KCk9Pnt9fX12YXIgSm09KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuX21lZGlhTWF0Y2hlcj1lLHRoaXMuX3pvbmU9aSx0aGlzLl9xdWVyaWVzPW5ldyBNYXAsdGhpcy5fZGVzdHJveVN1YmplY3Q9bmV3IGtlfW5nT25EZXN0cm95KCl7dGhpcy5fZGVzdHJveVN1YmplY3QubmV4dCgpLHRoaXMuX2Rlc3Ryb3lTdWJqZWN0LmNvbXBsZXRlKCl9aXNNYXRjaGVkKGUpe3JldHVybiBxZWUoeHYoZSkpLnNvbWUocj0+dGhpcy5fcmVnaXN0ZXJRdWVyeShyKS5tcWwubWF0Y2hlcyl9b2JzZXJ2ZShlKXtsZXQgbz1MdChxZWUoeHYoZSkpLm1hcChzPT50aGlzLl9yZWdpc3RlclF1ZXJ5KHMpLm9ic2VydmFibGUpKTtyZXR1cm4gbz1WcChvLnBpcGUoUXQoMSkpLG8ucGlwZShaYSgxKSxIcigwKSkpLG8ucGlwZShMKHM9PntsZXQgYT17bWF0Y2hlczohMSxicmVha3BvaW50czp7fX07cmV0dXJuIHMuZm9yRWFjaCgoe21hdGNoZXM6bCxxdWVyeTpjfSk9PnthLm1hdGNoZXM9YS5tYXRjaGVzfHxsLGEuYnJlYWtwb2ludHNbY109bH0pLGF9KSl9X3JlZ2lzdGVyUXVlcnkoZSl7aWYodGhpcy5fcXVlcmllcy5oYXMoZSkpcmV0dXJuIHRoaXMuX3F1ZXJpZXMuZ2V0KGUpO2xldCBpPXRoaXMuX21lZGlhTWF0Y2hlci5tYXRjaE1lZGlhKGUpLG89e29ic2VydmFibGU6bmV3IHVuKHM9PntsZXQgYT1sPT50aGlzLl96b25lLnJ1bigoKT0+cy5uZXh0KGwpKTtyZXR1cm4gaS5hZGRMaXN0ZW5lcihhKSwoKT0+e2kucmVtb3ZlTGlzdGVuZXIoYSl9fSkucGlwZSh6bihpKSxMKCh7bWF0Y2hlczpzfSk9Pih7cXVlcnk6ZSxtYXRjaGVzOnN9KSksc3QodGhpcy5fZGVzdHJveVN1YmplY3QpKSxtcWw6aX07cmV0dXJuIHRoaXMuX3F1ZXJpZXMuc2V0KGUsbyksb319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihkUmUpLGooX3QpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKTtmdW5jdGlvbiBxZWUobil7cmV0dXJuIG4ubWFwKHQ9PnQuc3BsaXQoIiwiKSkucmVkdWNlKCh0LGUpPT50LmNvbmNhdChlKSkubWFwKHQ9PnQudHJpbSgpKX1mdW5jdGlvbiBwMihuLHQpe3JldHVybihuLmdldEF0dHJpYnV0ZSh0KXx8IiIpLm1hdGNoKC9cUysvZyl8fFtdfXZhciAkZWU9ImNkay1kZXNjcmliZWRieS1tZXNzYWdlIix1Mj0iY2RrLWRlc2NyaWJlZGJ5LWhvc3QiLGdIPTAsZjI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuX3BsYXRmb3JtPWksdGhpcy5fbWVzc2FnZVJlZ2lzdHJ5PW5ldyBNYXAsdGhpcy5fbWVzc2FnZXNDb250YWluZXI9bnVsbCx0aGlzLl9pZD0iIitnSCsrLHRoaXMuX2RvY3VtZW50PWUsdGhpcy5faWQ9am8oJGYpKyItIitnSCsrfWRlc2NyaWJlKGUsaSxyKXtpZighdGhpcy5fY2FuQmVEZXNjcmliZWQoZSxpKSlyZXR1cm47bGV0IG89ZkgoaSxyKTsic3RyaW5nIiE9dHlwZW9mIGk/KFhlZShpLHRoaXMuX2lkKSx0aGlzLl9tZXNzYWdlUmVnaXN0cnkuc2V0KG8se21lc3NhZ2VFbGVtZW50OmkscmVmZXJlbmNlQ291bnQ6MH0pKTp0aGlzLl9tZXNzYWdlUmVnaXN0cnkuaGFzKG8pfHx0aGlzLl9jcmVhdGVNZXNzYWdlRWxlbWVudChpLHIpLHRoaXMuX2lzRWxlbWVudERlc2NyaWJlZEJ5TWVzc2FnZShlLG8pfHx0aGlzLl9hZGRNZXNzYWdlUmVmZXJlbmNlKGUsbyl9cmVtb3ZlRGVzY3JpcHRpb24oZSxpLHIpe2lmKCFpfHwhdGhpcy5faXNFbGVtZW50Tm9kZShlKSlyZXR1cm47bGV0IG89ZkgoaSxyKTtpZih0aGlzLl9pc0VsZW1lbnREZXNjcmliZWRCeU1lc3NhZ2UoZSxvKSYmdGhpcy5fcmVtb3ZlTWVzc2FnZVJlZmVyZW5jZShlLG8pLCJzdHJpbmciPT10eXBlb2YgaSl7bGV0IHM9dGhpcy5fbWVzc2FnZVJlZ2lzdHJ5LmdldChvKTtzJiYwPT09cy5yZWZlcmVuY2VDb3VudCYmdGhpcy5fZGVsZXRlTWVzc2FnZUVsZW1lbnQobyl9MD09PXRoaXMuX21lc3NhZ2VzQ29udGFpbmVyPy5jaGlsZE5vZGVzLmxlbmd0aCYmKHRoaXMuX21lc3NhZ2VzQ29udGFpbmVyLnJlbW92ZSgpLHRoaXMuX21lc3NhZ2VzQ29udGFpbmVyPW51bGwpfW5nT25EZXN0cm95KCl7bGV0IGU9dGhpcy5fZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChgWyR7dTJ9PSIke3RoaXMuX2lkfSJdYCk7Zm9yKGxldCBpPTA7aTxlLmxlbmd0aDtpKyspdGhpcy5fcmVtb3ZlQ2RrRGVzY3JpYmVkQnlSZWZlcmVuY2VJZHMoZVtpXSksZVtpXS5yZW1vdmVBdHRyaWJ1dGUodTIpO3RoaXMuX21lc3NhZ2VzQ29udGFpbmVyPy5yZW1vdmUoKSx0aGlzLl9tZXNzYWdlc0NvbnRhaW5lcj1udWxsLHRoaXMuX21lc3NhZ2VSZWdpc3RyeS5jbGVhcigpfV9jcmVhdGVNZXNzYWdlRWxlbWVudChlLGkpe2xldCByPXRoaXMuX2RvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpO1hlZShyLHRoaXMuX2lkKSxyLnRleHRDb250ZW50PWUsaSYmci5zZXRBdHRyaWJ1dGUoInJvbGUiLGkpLHRoaXMuX2NyZWF0ZU1lc3NhZ2VzQ29udGFpbmVyKCksdGhpcy5fbWVzc2FnZXNDb250YWluZXIuYXBwZW5kQ2hpbGQociksdGhpcy5fbWVzc2FnZVJlZ2lzdHJ5LnNldChmSChlLGkpLHttZXNzYWdlRWxlbWVudDpyLHJlZmVyZW5jZUNvdW50OjB9KX1fZGVsZXRlTWVzc2FnZUVsZW1lbnQoZSl7dGhpcy5fbWVzc2FnZVJlZ2lzdHJ5LmdldChlKT8ubWVzc2FnZUVsZW1lbnQ/LnJlbW92ZSgpLHRoaXMuX21lc3NhZ2VSZWdpc3RyeS5kZWxldGUoZSl9X2NyZWF0ZU1lc3NhZ2VzQ29udGFpbmVyKCl7aWYodGhpcy5fbWVzc2FnZXNDb250YWluZXIpcmV0dXJuO2xldCBlPSJjZGstZGVzY3JpYmVkYnktbWVzc2FnZS1jb250YWluZXIiLGk9dGhpcy5fZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChgLiR7ZX1bcGxhdGZvcm09InNlcnZlciJdYCk7Zm9yKGxldCBvPTA7bzxpLmxlbmd0aDtvKyspaVtvXS5yZW1vdmUoKTtsZXQgcj10aGlzLl9kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTtyLnN0eWxlLnZpc2liaWxpdHk9ImhpZGRlbiIsci5jbGFzc0xpc3QuYWRkKGUpLHIuY2xhc3NMaXN0LmFkZCgiY2RrLXZpc3VhbGx5LWhpZGRlbiIpLHRoaXMuX3BsYXRmb3JtJiYhdGhpcy5fcGxhdGZvcm0uaXNCcm93c2VyJiZyLnNldEF0dHJpYnV0ZSgicGxhdGZvcm0iLCJzZXJ2ZXIiKSx0aGlzLl9kb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHIpLHRoaXMuX21lc3NhZ2VzQ29udGFpbmVyPXJ9X3JlbW92ZUNka0Rlc2NyaWJlZEJ5UmVmZXJlbmNlSWRzKGUpe2xldCBpPXAyKGUsImFyaWEtZGVzY3JpYmVkYnkiKS5maWx0ZXIocj0+MCE9ci5pbmRleE9mKCRlZSkpO2Uuc2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IixpLmpvaW4oIiAiKSl9X2FkZE1lc3NhZ2VSZWZlcmVuY2UoZSxpKXtsZXQgcj10aGlzLl9tZXNzYWdlUmVnaXN0cnkuZ2V0KGkpOyhmdW5jdGlvbihuLHQsZSl7bGV0IGk9cDIobix0KTtpLnNvbWUocj0+ci50cmltKCk9PWUudHJpbSgpKXx8KGkucHVzaChlLnRyaW0oKSksbi5zZXRBdHRyaWJ1dGUodCxpLmpvaW4oIiAiKSkpfSkoZSwiYXJpYS1kZXNjcmliZWRieSIsci5tZXNzYWdlRWxlbWVudC5pZCksZS5zZXRBdHRyaWJ1dGUodTIsdGhpcy5faWQpLHIucmVmZXJlbmNlQ291bnQrK31fcmVtb3ZlTWVzc2FnZVJlZmVyZW5jZShlLGkpe2xldCByPXRoaXMuX21lc3NhZ2VSZWdpc3RyeS5nZXQoaSk7ci5yZWZlcmVuY2VDb3VudC0tLGZ1bmN0aW9uKG4sdCxlKXtsZXQgcj1wMihuLHQpLmZpbHRlcihvPT5vIT1lLnRyaW0oKSk7ci5sZW5ndGg/bi5zZXRBdHRyaWJ1dGUodCxyLmpvaW4oIiAiKSk6bi5yZW1vdmVBdHRyaWJ1dGUodCl9KGUsImFyaWEtZGVzY3JpYmVkYnkiLHIubWVzc2FnZUVsZW1lbnQuaWQpLGUucmVtb3ZlQXR0cmlidXRlKHUyKX1faXNFbGVtZW50RGVzY3JpYmVkQnlNZXNzYWdlKGUsaSl7bGV0IHI9cDIoZSwiYXJpYS1kZXNjcmliZWRieSIpLG89dGhpcy5fbWVzc2FnZVJlZ2lzdHJ5LmdldChpKSxzPW8mJm8ubWVzc2FnZUVsZW1lbnQuaWQ7cmV0dXJuISFzJiYtMSE9ci5pbmRleE9mKHMpfV9jYW5CZURlc2NyaWJlZChlLGkpe2lmKCF0aGlzLl9pc0VsZW1lbnROb2RlKGUpKXJldHVybiExO2lmKGkmJiJvYmplY3QiPT10eXBlb2YgaSlyZXR1cm4hMDtsZXQgcj1udWxsPT1pPyIiOmAke2l9YC50cmltKCksbz1lLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpO3JldHVybiEoIXJ8fG8mJm8udHJpbSgpPT09cil9X2lzRWxlbWVudE5vZGUoZSl7cmV0dXJuIGUubm9kZVR5cGU9PT10aGlzLl9kb2N1bWVudC5FTEVNRU5UX05PREV9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooSHQpLGoob2kpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKTtmdW5jdGlvbiBmSChuLHQpe3JldHVybiJzdHJpbmciPT10eXBlb2Ygbj9gJHt0fHwiIn0vJHtufWA6bn1mdW5jdGlvbiBYZWUobix0KXtuLmlkfHwobi5pZD1gJHskZWV9LSR7dH0tJHtnSCsrfWApfXZhciBoMj1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLl9pdGVtcz10LHRoaXMuX2FjdGl2ZUl0ZW1JbmRleD0tMSx0aGlzLl9hY3RpdmVJdGVtPW51bGwsdGhpcy5fd3JhcD0hMSx0aGlzLl9sZXR0ZXJLZXlTdHJlYW09bmV3IGtlLHRoaXMuX3R5cGVhaGVhZFN1YnNjcmlwdGlvbj1Tbi5FTVBUWSx0aGlzLl92ZXJ0aWNhbD0hMCx0aGlzLl9hbGxvd2VkTW9kaWZpZXJLZXlzPVtdLHRoaXMuX2hvbWVBbmRFbmQ9ITEsdGhpcy5fc2tpcFByZWRpY2F0ZUZuPWU9PmUuZGlzYWJsZWQsdGhpcy5fcHJlc3NlZExldHRlcnM9W10sdGhpcy50YWJPdXQ9bmV3IGtlLHRoaXMuY2hhbmdlPW5ldyBrZSx0IGluc3RhbmNlb2YgSGwmJnQuY2hhbmdlcy5zdWJzY3JpYmUoZT0+e2lmKHRoaXMuX2FjdGl2ZUl0ZW0pe2xldCByPWUudG9BcnJheSgpLmluZGV4T2YodGhpcy5fYWN0aXZlSXRlbSk7cj4tMSYmciE9PXRoaXMuX2FjdGl2ZUl0ZW1JbmRleCYmKHRoaXMuX2FjdGl2ZUl0ZW1JbmRleD1yKX19KX1za2lwUHJlZGljYXRlKHQpe3JldHVybiB0aGlzLl9za2lwUHJlZGljYXRlRm49dCx0aGlzfXdpdGhXcmFwKHQ9ITApe3JldHVybiB0aGlzLl93cmFwPXQsdGhpc313aXRoVmVydGljYWxPcmllbnRhdGlvbih0PSEwKXtyZXR1cm4gdGhpcy5fdmVydGljYWw9dCx0aGlzfXdpdGhIb3Jpem9udGFsT3JpZW50YXRpb24odCl7cmV0dXJuIHRoaXMuX2hvcml6b250YWw9dCx0aGlzfXdpdGhBbGxvd2VkTW9kaWZpZXJLZXlzKHQpe3JldHVybiB0aGlzLl9hbGxvd2VkTW9kaWZpZXJLZXlzPXQsdGhpc313aXRoVHlwZUFoZWFkKHQ9MjAwKXtyZXR1cm4gdGhpcy5fdHlwZWFoZWFkU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5fdHlwZWFoZWFkU3Vic2NyaXB0aW9uPXRoaXMuX2xldHRlcktleVN0cmVhbS5waXBlKGt0KGU9PnRoaXMuX3ByZXNzZWRMZXR0ZXJzLnB1c2goZSkpLEhyKHQpLFllKCgpPT50aGlzLl9wcmVzc2VkTGV0dGVycy5sZW5ndGg+MCksTCgoKT0+dGhpcy5fcHJlc3NlZExldHRlcnMuam9pbigiIikpKS5zdWJzY3JpYmUoZT0+e2xldCBpPXRoaXMuX2dldEl0ZW1zQXJyYXkoKTtmb3IobGV0IHI9MTtyPGkubGVuZ3RoKzE7cisrKXtsZXQgbz0odGhpcy5fYWN0aXZlSXRlbUluZGV4K3IpJWkubGVuZ3RoLHM9aVtvXTtpZighdGhpcy5fc2tpcFByZWRpY2F0ZUZuKHMpJiYwPT09cy5nZXRMYWJlbCgpLnRvVXBwZXJDYXNlKCkudHJpbSgpLmluZGV4T2YoZSkpe3RoaXMuc2V0QWN0aXZlSXRlbShvKTticmVha319dGhpcy5fcHJlc3NlZExldHRlcnM9W119KSx0aGlzfXdpdGhIb21lQW5kRW5kKHQ9ITApe3JldHVybiB0aGlzLl9ob21lQW5kRW5kPXQsdGhpc31zZXRBY3RpdmVJdGVtKHQpe2xldCBlPXRoaXMuX2FjdGl2ZUl0ZW07dGhpcy51cGRhdGVBY3RpdmVJdGVtKHQpLHRoaXMuX2FjdGl2ZUl0ZW0hPT1lJiZ0aGlzLmNoYW5nZS5uZXh0KHRoaXMuX2FjdGl2ZUl0ZW1JbmRleCl9b25LZXlkb3duKHQpe2xldCBlPXQua2V5Q29kZSxyPVsiYWx0S2V5IiwiY3RybEtleSIsIm1ldGFLZXkiLCJzaGlmdEtleSJdLmV2ZXJ5KG89PiF0W29dfHx0aGlzLl9hbGxvd2VkTW9kaWZpZXJLZXlzLmluZGV4T2Yobyk+LTEpO3N3aXRjaChlKXtjYXNlIDk6cmV0dXJuIHZvaWQgdGhpcy50YWJPdXQubmV4dCgpO2Nhc2UgNDA6aWYodGhpcy5fdmVydGljYWwmJnIpe3RoaXMuc2V0TmV4dEl0ZW1BY3RpdmUoKTticmVha31yZXR1cm47Y2FzZSAzODppZih0aGlzLl92ZXJ0aWNhbCYmcil7dGhpcy5zZXRQcmV2aW91c0l0ZW1BY3RpdmUoKTticmVha31yZXR1cm47Y2FzZSAzOTppZih0aGlzLl9ob3Jpem9udGFsJiZyKXsicnRsIj09PXRoaXMuX2hvcml6b250YWw/dGhpcy5zZXRQcmV2aW91c0l0ZW1BY3RpdmUoKTp0aGlzLnNldE5leHRJdGVtQWN0aXZlKCk7YnJlYWt9cmV0dXJuO2Nhc2UgMzc6aWYodGhpcy5faG9yaXpvbnRhbCYmcil7InJ0bCI9PT10aGlzLl9ob3Jpem9udGFsP3RoaXMuc2V0TmV4dEl0ZW1BY3RpdmUoKTp0aGlzLnNldFByZXZpb3VzSXRlbUFjdGl2ZSgpO2JyZWFrfXJldHVybjtjYXNlIDM2OmlmKHRoaXMuX2hvbWVBbmRFbmQmJnIpe3RoaXMuc2V0Rmlyc3RJdGVtQWN0aXZlKCk7YnJlYWt9cmV0dXJuO2Nhc2UgMzU6aWYodGhpcy5faG9tZUFuZEVuZCYmcil7dGhpcy5zZXRMYXN0SXRlbUFjdGl2ZSgpO2JyZWFrfXJldHVybjtkZWZhdWx0OnJldHVybiB2b2lkKChyfHxrcih0LCJzaGlmdEtleSIpKSYmKHQua2V5JiYxPT09dC5rZXkubGVuZ3RoP3RoaXMuX2xldHRlcktleVN0cmVhbS5uZXh0KHQua2V5LnRvTG9jYWxlVXBwZXJDYXNlKCkpOihlPj02NSYmZTw9OTB8fGU+PTQ4JiZlPD01NykmJnRoaXMuX2xldHRlcktleVN0cmVhbS5uZXh0KFN0cmluZy5mcm9tQ2hhckNvZGUoZSkpKSl9dGhpcy5fcHJlc3NlZExldHRlcnM9W10sdC5wcmV2ZW50RGVmYXVsdCgpfWdldCBhY3RpdmVJdGVtSW5kZXgoKXtyZXR1cm4gdGhpcy5fYWN0aXZlSXRlbUluZGV4fWdldCBhY3RpdmVJdGVtKCl7cmV0dXJuIHRoaXMuX2FjdGl2ZUl0ZW19aXNUeXBpbmcoKXtyZXR1cm4gdGhpcy5fcHJlc3NlZExldHRlcnMubGVuZ3RoPjB9c2V0Rmlyc3RJdGVtQWN0aXZlKCl7dGhpcy5fc2V0QWN0aXZlSXRlbUJ5SW5kZXgoMCwxKX1zZXRMYXN0SXRlbUFjdGl2ZSgpe3RoaXMuX3NldEFjdGl2ZUl0ZW1CeUluZGV4KHRoaXMuX2l0ZW1zLmxlbmd0aC0xLC0xKX1zZXROZXh0SXRlbUFjdGl2ZSgpe3RoaXMuX2FjdGl2ZUl0ZW1JbmRleDwwP3RoaXMuc2V0Rmlyc3RJdGVtQWN0aXZlKCk6dGhpcy5fc2V0QWN0aXZlSXRlbUJ5RGVsdGEoMSl9c2V0UHJldmlvdXNJdGVtQWN0aXZlKCl7dGhpcy5fYWN0aXZlSXRlbUluZGV4PDAmJnRoaXMuX3dyYXA/dGhpcy5zZXRMYXN0SXRlbUFjdGl2ZSgpOnRoaXMuX3NldEFjdGl2ZUl0ZW1CeURlbHRhKC0xKX11cGRhdGVBY3RpdmVJdGVtKHQpe2xldCBlPXRoaXMuX2dldEl0ZW1zQXJyYXkoKSxpPSJudW1iZXIiPT10eXBlb2YgdD90OmUuaW5kZXhPZih0KTt0aGlzLl9hY3RpdmVJdGVtPWVbaV0/P251bGwsdGhpcy5fYWN0aXZlSXRlbUluZGV4PWl9X3NldEFjdGl2ZUl0ZW1CeURlbHRhKHQpe3RoaXMuX3dyYXA/dGhpcy5fc2V0QWN0aXZlSW5XcmFwTW9kZSh0KTp0aGlzLl9zZXRBY3RpdmVJbkRlZmF1bHRNb2RlKHQpfV9zZXRBY3RpdmVJbldyYXBNb2RlKHQpe2xldCBlPXRoaXMuX2dldEl0ZW1zQXJyYXkoKTtmb3IobGV0IGk9MTtpPD1lLmxlbmd0aDtpKyspe2xldCByPSh0aGlzLl9hY3RpdmVJdGVtSW5kZXgrdCppK2UubGVuZ3RoKSVlLmxlbmd0aDtpZighdGhpcy5fc2tpcFByZWRpY2F0ZUZuKGVbcl0pKXJldHVybiB2b2lkIHRoaXMuc2V0QWN0aXZlSXRlbShyKX19X3NldEFjdGl2ZUluRGVmYXVsdE1vZGUodCl7dGhpcy5fc2V0QWN0aXZlSXRlbUJ5SW5kZXgodGhpcy5fYWN0aXZlSXRlbUluZGV4K3QsdCl9X3NldEFjdGl2ZUl0ZW1CeUluZGV4KHQsZSl7bGV0IGk9dGhpcy5fZ2V0SXRlbXNBcnJheSgpO2lmKGlbdF0pe2Zvcig7dGhpcy5fc2tpcFByZWRpY2F0ZUZuKGlbdF0pOylpZighaVt0Kz1lXSlyZXR1cm47dGhpcy5zZXRBY3RpdmVJdGVtKHQpfX1fZ2V0SXRlbXNBcnJheSgpe3JldHVybiB0aGlzLl9pdGVtcyBpbnN0YW5jZW9mIEhsP3RoaXMuX2l0ZW1zLnRvQXJyYXkoKTp0aGlzLl9pdGVtc319LHd2PWNsYXNzIGV4dGVuZHMgaDJ7c2V0QWN0aXZlSXRlbSh0KXt0aGlzLmFjdGl2ZUl0ZW0mJnRoaXMuYWN0aXZlSXRlbS5zZXRJbmFjdGl2ZVN0eWxlcygpLHN1cGVyLnNldEFjdGl2ZUl0ZW0odCksdGhpcy5hY3RpdmVJdGVtJiZ0aGlzLmFjdGl2ZUl0ZW0uc2V0QWN0aXZlU3R5bGVzKCl9fSxTaD1jbGFzcyBleHRlbmRzIGgye2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLl9vcmlnaW49InByb2dyYW0ifXNldEZvY3VzT3JpZ2luKHQpe3JldHVybiB0aGlzLl9vcmlnaW49dCx0aGlzfXNldEFjdGl2ZUl0ZW0odCl7c3VwZXIuc2V0QWN0aXZlSXRlbSh0KSx0aGlzLmFjdGl2ZUl0ZW0mJnRoaXMuYWN0aXZlSXRlbS5mb2N1cyh0aGlzLl9vcmlnaW4pfX0sU3Y9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLl9wbGF0Zm9ybT1lfWlzRGlzYWJsZWQoZSl7cmV0dXJuIGUuaGFzQXR0cmlidXRlKCJkaXNhYmxlZCIpfWlzVmlzaWJsZShlKXtyZXR1cm4gZnVuY3Rpb24obil7cmV0dXJuISEobi5vZmZzZXRXaWR0aHx8bi5vZmZzZXRIZWlnaHR8fCJmdW5jdGlvbiI9PXR5cGVvZiBuLmdldENsaWVudFJlY3RzJiZuLmdldENsaWVudFJlY3RzKCkubGVuZ3RoKX0oZSkmJiJ2aXNpYmxlIj09PWdldENvbXB1dGVkU3R5bGUoZSkudmlzaWJpbGl0eX1pc1RhYmJhYmxlKGUpe2lmKCF0aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXIpcmV0dXJuITE7bGV0IGk9ZnVuY3Rpb24obil7dHJ5e3JldHVybiBuLmZyYW1lRWxlbWVudH1jYXRjaHtyZXR1cm4gbnVsbH19KGZ1bmN0aW9uKG4pe3JldHVybiBuLm93bmVyRG9jdW1lbnQmJm4ub3duZXJEb2N1bWVudC5kZWZhdWx0Vmlld3x8d2luZG93fShlKSk7aWYoaSYmKC0xPT09UWVlKGkpfHwhdGhpcy5pc1Zpc2libGUoaSkpKXJldHVybiExO2xldCByPWUubm9kZU5hbWUudG9Mb3dlckNhc2UoKSxvPVFlZShlKTtyZXR1cm4gZS5oYXNBdHRyaWJ1dGUoImNvbnRlbnRlZGl0YWJsZSIpPy0xIT09bzohKCJpZnJhbWUiPT09cnx8Im9iamVjdCI9PT1yfHx0aGlzLl9wbGF0Zm9ybS5XRUJLSVQmJnRoaXMuX3BsYXRmb3JtLklPUyYmIWZ1bmN0aW9uKG4pe2xldCB0PW4ubm9kZU5hbWUudG9Mb3dlckNhc2UoKSxlPSJpbnB1dCI9PT10JiZuLnR5cGU7cmV0dXJuInRleHQiPT09ZXx8InBhc3N3b3JkIj09PWV8fCJzZWxlY3QiPT09dHx8InRleHRhcmVhIj09PXR9KGUpKSYmKCJhdWRpbyI9PT1yPyEhZS5oYXNBdHRyaWJ1dGUoImNvbnRyb2xzIikmJi0xIT09bzoidmlkZW8iPT09cj8tMSE9PW8mJihudWxsIT09b3x8dGhpcy5fcGxhdGZvcm0uRklSRUZPWHx8ZS5oYXNBdHRyaWJ1dGUoImNvbnRyb2xzIikpOmUudGFiSW5kZXg+PTApfWlzRm9jdXNhYmxlKGUsaSl7cmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiFmdW5jdGlvbihuKXtyZXR1cm4gZnVuY3Rpb24obil7cmV0dXJuImlucHV0Ij09bi5ub2RlTmFtZS50b0xvd2VyQ2FzZSgpfShuKSYmImhpZGRlbiI9PW4udHlwZX0obikmJihmdW5jdGlvbihuKXtsZXQgdD1uLm5vZGVOYW1lLnRvTG93ZXJDYXNlKCk7cmV0dXJuImlucHV0Ij09PXR8fCJzZWxlY3QiPT09dHx8ImJ1dHRvbiI9PT10fHwidGV4dGFyZWEiPT09dH0obil8fGZ1bmN0aW9uKG4pe3JldHVybiBmdW5jdGlvbihuKXtyZXR1cm4iYSI9PW4ubm9kZU5hbWUudG9Mb3dlckNhc2UoKX0obikmJm4uaGFzQXR0cmlidXRlKCJocmVmIil9KG4pfHxuLmhhc0F0dHJpYnV0ZSgiY29udGVudGVkaXRhYmxlIil8fGV0ZShuKSl9KGUpJiYhdGhpcy5pc0Rpc2FibGVkKGUpJiYoaT8uaWdub3JlVmlzaWJpbGl0eXx8dGhpcy5pc1Zpc2libGUoZSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKG9pKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCk7ZnVuY3Rpb24gZXRlKG4pe2lmKCFuLmhhc0F0dHJpYnV0ZSgidGFiaW5kZXgiKXx8dm9pZCAwPT09bi50YWJJbmRleClyZXR1cm4hMTtsZXQgdD1uLmdldEF0dHJpYnV0ZSgidGFiaW5kZXgiKTtyZXR1cm4hKCF0fHxpc05hTihwYXJzZUludCh0LDEwKSkpfWZ1bmN0aW9uIFFlZShuKXtpZighZXRlKG4pKXJldHVybiBudWxsO2xldCB0PXBhcnNlSW50KG4uZ2V0QXR0cmlidXRlKCJ0YWJpbmRleCIpfHwiIiwxMCk7cmV0dXJuIGlzTmFOKHQpPy0xOnR9dmFyIEpNPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIpe3RoaXMuX2NoZWNrZXI9ZSx0aGlzLl9uZ1pvbmU9aSx0aGlzLl9kb2N1bWVudD1yfWNyZWF0ZShlLGk9ITEpe3JldHVybiBuZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlLGkscixvPSExKXt0aGlzLl9lbGVtZW50PXQsdGhpcy5fY2hlY2tlcj1lLHRoaXMuX25nWm9uZT1pLHRoaXMuX2RvY3VtZW50PXIsdGhpcy5faGFzQXR0YWNoZWQ9ITEsdGhpcy5zdGFydEFuY2hvckxpc3RlbmVyPSgpPT50aGlzLmZvY3VzTGFzdFRhYmJhYmxlRWxlbWVudCgpLHRoaXMuZW5kQW5jaG9yTGlzdGVuZXI9KCk9PnRoaXMuZm9jdXNGaXJzdFRhYmJhYmxlRWxlbWVudCgpLHRoaXMuX2VuYWJsZWQ9ITAsb3x8dGhpcy5hdHRhY2hBbmNob3JzKCl9Z2V0IGVuYWJsZWQoKXtyZXR1cm4gdGhpcy5fZW5hYmxlZH1zZXQgZW5hYmxlZCh0KXt0aGlzLl9lbmFibGVkPXQsdGhpcy5fc3RhcnRBbmNob3ImJnRoaXMuX2VuZEFuY2hvciYmKHRoaXMuX3RvZ2dsZUFuY2hvclRhYkluZGV4KHQsdGhpcy5fc3RhcnRBbmNob3IpLHRoaXMuX3RvZ2dsZUFuY2hvclRhYkluZGV4KHQsdGhpcy5fZW5kQW5jaG9yKSl9ZGVzdHJveSgpe2xldCB0PXRoaXMuX3N0YXJ0QW5jaG9yLGU9dGhpcy5fZW5kQW5jaG9yO3QmJih0LnJlbW92ZUV2ZW50TGlzdGVuZXIoImZvY3VzIix0aGlzLnN0YXJ0QW5jaG9yTGlzdGVuZXIpLHQucmVtb3ZlKCkpLGUmJihlLnJlbW92ZUV2ZW50TGlzdGVuZXIoImZvY3VzIix0aGlzLmVuZEFuY2hvckxpc3RlbmVyKSxlLnJlbW92ZSgpKSx0aGlzLl9zdGFydEFuY2hvcj10aGlzLl9lbmRBbmNob3I9bnVsbCx0aGlzLl9oYXNBdHRhY2hlZD0hMX1hdHRhY2hBbmNob3JzKCl7cmV0dXJuISF0aGlzLl9oYXNBdHRhY2hlZHx8KHRoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e3RoaXMuX3N0YXJ0QW5jaG9yfHwodGhpcy5fc3RhcnRBbmNob3I9dGhpcy5fY3JlYXRlQW5jaG9yKCksdGhpcy5fc3RhcnRBbmNob3IuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXMiLHRoaXMuc3RhcnRBbmNob3JMaXN0ZW5lcikpLHRoaXMuX2VuZEFuY2hvcnx8KHRoaXMuX2VuZEFuY2hvcj10aGlzLl9jcmVhdGVBbmNob3IoKSx0aGlzLl9lbmRBbmNob3IuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXMiLHRoaXMuZW5kQW5jaG9yTGlzdGVuZXIpKX0pLHRoaXMuX2VsZW1lbnQucGFyZW50Tm9kZSYmKHRoaXMuX2VsZW1lbnQucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUodGhpcy5fc3RhcnRBbmNob3IsdGhpcy5fZWxlbWVudCksdGhpcy5fZWxlbWVudC5wYXJlbnROb2RlLmluc2VydEJlZm9yZSh0aGlzLl9lbmRBbmNob3IsdGhpcy5fZWxlbWVudC5uZXh0U2libGluZyksdGhpcy5faGFzQXR0YWNoZWQ9ITApLHRoaXMuX2hhc0F0dGFjaGVkKX1mb2N1c0luaXRpYWxFbGVtZW50V2hlblJlYWR5KHQpe3JldHVybiBuZXcgUHJvbWlzZShlPT57dGhpcy5fZXhlY3V0ZU9uU3RhYmxlKCgpPT5lKHRoaXMuZm9jdXNJbml0aWFsRWxlbWVudCh0KSkpfSl9Zm9jdXNGaXJzdFRhYmJhYmxlRWxlbWVudFdoZW5SZWFkeSh0KXtyZXR1cm4gbmV3IFByb21pc2UoZT0+e3RoaXMuX2V4ZWN1dGVPblN0YWJsZSgoKT0+ZSh0aGlzLmZvY3VzRmlyc3RUYWJiYWJsZUVsZW1lbnQodCkpKX0pfWZvY3VzTGFzdFRhYmJhYmxlRWxlbWVudFdoZW5SZWFkeSh0KXtyZXR1cm4gbmV3IFByb21pc2UoZT0+e3RoaXMuX2V4ZWN1dGVPblN0YWJsZSgoKT0+ZSh0aGlzLmZvY3VzTGFzdFRhYmJhYmxlRWxlbWVudCh0KSkpfSl9X2dldFJlZ2lvbkJvdW5kYXJ5KHQpe2xldCBlPXRoaXMuX2VsZW1lbnQucXVlcnlTZWxlY3RvckFsbChgW2Nkay1mb2N1cy1yZWdpb24tJHt0fV0sIFtjZGtGb2N1c1JlZ2lvbiR7dH1dLCBbY2RrLWZvY3VzLSR7dH1dYCk7cmV0dXJuInN0YXJ0Ij09dD9lLmxlbmd0aD9lWzBdOnRoaXMuX2dldEZpcnN0VGFiYmFibGVFbGVtZW50KHRoaXMuX2VsZW1lbnQpOmUubGVuZ3RoP2VbZS5sZW5ndGgtMV06dGhpcy5fZ2V0TGFzdFRhYmJhYmxlRWxlbWVudCh0aGlzLl9lbGVtZW50KX1mb2N1c0luaXRpYWxFbGVtZW50KHQpe2xldCBlPXRoaXMuX2VsZW1lbnQucXVlcnlTZWxlY3RvcigiW2Nkay1mb2N1cy1pbml0aWFsXSwgW2Nka0ZvY3VzSW5pdGlhbF0iKTtpZihlKXtpZighdGhpcy5fY2hlY2tlci5pc0ZvY3VzYWJsZShlKSl7bGV0IGk9dGhpcy5fZ2V0Rmlyc3RUYWJiYWJsZUVsZW1lbnQoZSk7cmV0dXJuIGk/LmZvY3VzKHQpLCEhaX1yZXR1cm4gZS5mb2N1cyh0KSwhMH1yZXR1cm4gdGhpcy5mb2N1c0ZpcnN0VGFiYmFibGVFbGVtZW50KHQpfWZvY3VzRmlyc3RUYWJiYWJsZUVsZW1lbnQodCl7bGV0IGU9dGhpcy5fZ2V0UmVnaW9uQm91bmRhcnkoInN0YXJ0Iik7cmV0dXJuIGUmJmUuZm9jdXModCksISFlfWZvY3VzTGFzdFRhYmJhYmxlRWxlbWVudCh0KXtsZXQgZT10aGlzLl9nZXRSZWdpb25Cb3VuZGFyeSgiZW5kIik7cmV0dXJuIGUmJmUuZm9jdXModCksISFlfWhhc0F0dGFjaGVkKCl7cmV0dXJuIHRoaXMuX2hhc0F0dGFjaGVkfV9nZXRGaXJzdFRhYmJhYmxlRWxlbWVudCh0KXtpZih0aGlzLl9jaGVja2VyLmlzRm9jdXNhYmxlKHQpJiZ0aGlzLl9jaGVja2VyLmlzVGFiYmFibGUodCkpcmV0dXJuIHQ7bGV0IGU9dC5jaGlsZHJlbjtmb3IobGV0IGk9MDtpPGUubGVuZ3RoO2krKyl7bGV0IHI9ZVtpXS5ub2RlVHlwZT09PXRoaXMuX2RvY3VtZW50LkVMRU1FTlRfTk9ERT90aGlzLl9nZXRGaXJzdFRhYmJhYmxlRWxlbWVudChlW2ldKTpudWxsO2lmKHIpcmV0dXJuIHJ9cmV0dXJuIG51bGx9X2dldExhc3RUYWJiYWJsZUVsZW1lbnQodCl7aWYodGhpcy5fY2hlY2tlci5pc0ZvY3VzYWJsZSh0KSYmdGhpcy5fY2hlY2tlci5pc1RhYmJhYmxlKHQpKXJldHVybiB0O2xldCBlPXQuY2hpbGRyZW47Zm9yKGxldCBpPWUubGVuZ3RoLTE7aT49MDtpLS0pe2xldCByPWVbaV0ubm9kZVR5cGU9PT10aGlzLl9kb2N1bWVudC5FTEVNRU5UX05PREU/dGhpcy5fZ2V0TGFzdFRhYmJhYmxlRWxlbWVudChlW2ldKTpudWxsO2lmKHIpcmV0dXJuIHJ9cmV0dXJuIG51bGx9X2NyZWF0ZUFuY2hvcigpe2xldCB0PXRoaXMuX2RvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpO3JldHVybiB0aGlzLl90b2dnbGVBbmNob3JUYWJJbmRleCh0aGlzLl9lbmFibGVkLHQpLHQuY2xhc3NMaXN0LmFkZCgiY2RrLXZpc3VhbGx5LWhpZGRlbiIpLHQuY2xhc3NMaXN0LmFkZCgiY2RrLWZvY3VzLXRyYXAtYW5jaG9yIiksdC5zZXRBdHRyaWJ1dGUoImFyaWEtaGlkZGVuIiwidHJ1ZSIpLHR9X3RvZ2dsZUFuY2hvclRhYkluZGV4KHQsZSl7dD9lLnNldEF0dHJpYnV0ZSgidGFiaW5kZXgiLCIwIik6ZS5yZW1vdmVBdHRyaWJ1dGUoInRhYmluZGV4Iil9dG9nZ2xlQW5jaG9ycyh0KXt0aGlzLl9zdGFydEFuY2hvciYmdGhpcy5fZW5kQW5jaG9yJiYodGhpcy5fdG9nZ2xlQW5jaG9yVGFiSW5kZXgodCx0aGlzLl9zdGFydEFuY2hvciksdGhpcy5fdG9nZ2xlQW5jaG9yVGFiSW5kZXgodCx0aGlzLl9lbmRBbmNob3IpKX1fZXhlY3V0ZU9uU3RhYmxlKHQpe3RoaXMuX25nWm9uZS5pc1N0YWJsZT90KCk6dGhpcy5fbmdab25lLm9uU3RhYmxlLnBpcGUoUXQoMSkpLnN1YnNjcmliZSh0KX19KGUsdGhpcy5fY2hlY2tlcix0aGlzLl9uZ1pvbmUsdGhpcy5fZG9jdW1lbnQsaSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooU3YpLGooX3QpLGooSHQpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKTtmdW5jdGlvbiAkTShuKXtyZXR1cm4gMD09PW4uYnV0dG9uc3x8MD09PW4ub2Zmc2V0WCYmMD09PW4ub2Zmc2V0WX1mdW5jdGlvbiBldyhuKXtsZXQgdD1uLnRvdWNoZXMmJm4udG91Y2hlc1swXXx8bi5jaGFuZ2VkVG91Y2hlcyYmbi5jaGFuZ2VkVG91Y2hlc1swXTtyZXR1cm4hKCF0fHwtMSE9PXQuaWRlbnRpZmllcnx8bnVsbCE9dC5yYWRpdXNYJiYxIT09dC5yYWRpdXNYfHxudWxsIT10LnJhZGl1c1kmJjEhPT10LnJhZGl1c1kpfW5ldyBwZSgiRk9DVVNfVFJBUF9JTkVSVF9TVFJBVEVHWSIpO3ZhciBrUmU9bmV3IHBlKCJjZGstaW5wdXQtbW9kYWxpdHktZGV0ZWN0b3Itb3B0aW9ucyIpLEZSZT17aWdub3JlS2V5czpbMTgsMTcsMjI0LDkxLDE2XX0sTXY9bGEoe3Bhc3NpdmU6ITAsY2FwdHVyZTohMH0pLE5SZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8pe3RoaXMuX3BsYXRmb3JtPWUsdGhpcy5fbW9zdFJlY2VudFRhcmdldD1udWxsLHRoaXMuX21vZGFsaXR5PW5ldyBocihudWxsKSx0aGlzLl9sYXN0VG91Y2hNcz0wLHRoaXMuX29uS2V5ZG93bj1zPT57dGhpcy5fb3B0aW9ucz8uaWdub3JlS2V5cz8uc29tZShhPT5hPT09cy5rZXlDb2RlKXx8KHRoaXMuX21vZGFsaXR5Lm5leHQoImtleWJvYXJkIiksdGhpcy5fbW9zdFJlY2VudFRhcmdldD1RYyhzKSl9LHRoaXMuX29uTW91c2Vkb3duPXM9PntEYXRlLm5vdygpLXRoaXMuX2xhc3RUb3VjaE1zPDY1MHx8KHRoaXMuX21vZGFsaXR5Lm5leHQoJE0ocyk/ImtleWJvYXJkIjoibW91c2UiKSx0aGlzLl9tb3N0UmVjZW50VGFyZ2V0PVFjKHMpKX0sdGhpcy5fb25Ub3VjaHN0YXJ0PXM9PntldyhzKT90aGlzLl9tb2RhbGl0eS5uZXh0KCJrZXlib2FyZCIpOih0aGlzLl9sYXN0VG91Y2hNcz1EYXRlLm5vdygpLHRoaXMuX21vZGFsaXR5Lm5leHQoInRvdWNoIiksdGhpcy5fbW9zdFJlY2VudFRhcmdldD1RYyhzKSl9LHRoaXMuX29wdGlvbnM9ey4uLkZSZSwuLi5vfSx0aGlzLm1vZGFsaXR5RGV0ZWN0ZWQ9dGhpcy5fbW9kYWxpdHkucGlwZShaYSgxKSksdGhpcy5tb2RhbGl0eUNoYW5nZWQ9dGhpcy5tb2RhbGl0eURldGVjdGVkLnBpcGUoeWkoKSksZS5pc0Jyb3dzZXImJmkucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntyLmFkZEV2ZW50TGlzdGVuZXIoImtleWRvd24iLHRoaXMuX29uS2V5ZG93bixNdiksci5hZGRFdmVudExpc3RlbmVyKCJtb3VzZWRvd24iLHRoaXMuX29uTW91c2Vkb3duLE12KSxyLmFkZEV2ZW50TGlzdGVuZXIoInRvdWNoc3RhcnQiLHRoaXMuX29uVG91Y2hzdGFydCxNdil9KX1nZXQgbW9zdFJlY2VudE1vZGFsaXR5KCl7cmV0dXJuIHRoaXMuX21vZGFsaXR5LnZhbHVlfW5nT25EZXN0cm95KCl7dGhpcy5fbW9kYWxpdHkuY29tcGxldGUoKSx0aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXImJihkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJrZXlkb3duIix0aGlzLl9vbktleWRvd24sTXYpLGRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoIm1vdXNlZG93biIsdGhpcy5fb25Nb3VzZWRvd24sTXYpLGRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoInRvdWNoc3RhcnQiLHRoaXMuX29uVG91Y2hzdGFydCxNdikpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKG9pKSxqKF90KSxqKEh0KSxqKGtSZSw4KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksTFJlPW5ldyBwZSgibGl2ZUFubm91bmNlckVsZW1lbnQiLHtwcm92aWRlZEluOiJyb290IixmYWN0b3J5OmZ1bmN0aW9uKCl7cmV0dXJuIG51bGx9fSksVlJlPW5ldyBwZSgiTElWRV9BTk5PVU5DRVJfREVGQVVMVF9PUFRJT05TIiksdHc9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscixvKXt0aGlzLl9uZ1pvbmU9aSx0aGlzLl9kZWZhdWx0T3B0aW9ucz1vLHRoaXMuX2RvY3VtZW50PXIsdGhpcy5fbGl2ZUVsZW1lbnQ9ZXx8dGhpcy5fY3JlYXRlTGl2ZUVsZW1lbnQoKX1hbm5vdW5jZShlLC4uLmkpe2xldCBvLHMscj10aGlzLl9kZWZhdWx0T3B0aW9ucztyZXR1cm4gMT09PWkubGVuZ3RoJiYibnVtYmVyIj09dHlwZW9mIGlbMF0/cz1pWzBdOltvLHNdPWksdGhpcy5jbGVhcigpLGNsZWFyVGltZW91dCh0aGlzLl9wcmV2aW91c1RpbWVvdXQpLG98fChvPXImJnIucG9saXRlbmVzcz9yLnBvbGl0ZW5lc3M6InBvbGl0ZSIpLG51bGw9PXMmJnImJihzPXIuZHVyYXRpb24pLHRoaXMuX2xpdmVFbGVtZW50LnNldEF0dHJpYnV0ZSgiYXJpYS1saXZlIixvKSx0aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9Pih0aGlzLl9jdXJyZW50UHJvbWlzZXx8KHRoaXMuX2N1cnJlbnRQcm9taXNlPW5ldyBQcm9taXNlKGE9PnRoaXMuX2N1cnJlbnRSZXNvbHZlPWEpKSxjbGVhclRpbWVvdXQodGhpcy5fcHJldmlvdXNUaW1lb3V0KSx0aGlzLl9wcmV2aW91c1RpbWVvdXQ9c2V0VGltZW91dCgoKT0+e3RoaXMuX2xpdmVFbGVtZW50LnRleHRDb250ZW50PWUsIm51bWJlciI9PXR5cGVvZiBzJiYodGhpcy5fcHJldmlvdXNUaW1lb3V0PXNldFRpbWVvdXQoKCk9PnRoaXMuY2xlYXIoKSxzKSksdGhpcy5fY3VycmVudFJlc29sdmUoKSx0aGlzLl9jdXJyZW50UHJvbWlzZT10aGlzLl9jdXJyZW50UmVzb2x2ZT12b2lkIDB9LDEwMCksdGhpcy5fY3VycmVudFByb21pc2UpKX1jbGVhcigpe3RoaXMuX2xpdmVFbGVtZW50JiYodGhpcy5fbGl2ZUVsZW1lbnQudGV4dENvbnRlbnQ9IiIpfW5nT25EZXN0cm95KCl7Y2xlYXJUaW1lb3V0KHRoaXMuX3ByZXZpb3VzVGltZW91dCksdGhpcy5fbGl2ZUVsZW1lbnQ/LnJlbW92ZSgpLHRoaXMuX2xpdmVFbGVtZW50PW51bGwsdGhpcy5fY3VycmVudFJlc29sdmU/LigpLHRoaXMuX2N1cnJlbnRQcm9taXNlPXRoaXMuX2N1cnJlbnRSZXNvbHZlPXZvaWQgMH1fY3JlYXRlTGl2ZUVsZW1lbnQoKXtsZXQgZT0iY2RrLWxpdmUtYW5ub3VuY2VyLWVsZW1lbnQiLGk9dGhpcy5fZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZShlKSxyPXRoaXMuX2RvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpO2ZvcihsZXQgbz0wO288aS5sZW5ndGg7bysrKWlbb10ucmVtb3ZlKCk7cmV0dXJuIHIuY2xhc3NMaXN0LmFkZChlKSxyLmNsYXNzTGlzdC5hZGQoImNkay12aXN1YWxseS1oaWRkZW4iKSxyLnNldEF0dHJpYnV0ZSgiYXJpYS1hdG9taWMiLCJ0cnVlIiksci5zZXRBdHRyaWJ1dGUoImFyaWEtbGl2ZSIsInBvbGl0ZSIpLHRoaXMuX2RvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQocikscn19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihMUmUsOCksaihfdCksaihIdCksaihWUmUsOCkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpLEhSZT1uZXcgcGUoImNkay1mb2N1cy1tb25pdG9yLWRlZmF1bHQtb3B0aW9ucyIpLGQyPWxhKHtwYXNzaXZlOiEwLGNhcHR1cmU6ITB9KSxGcj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8scyl7dGhpcy5fbmdab25lPWUsdGhpcy5fcGxhdGZvcm09aSx0aGlzLl9pbnB1dE1vZGFsaXR5RGV0ZWN0b3I9cix0aGlzLl9vcmlnaW49bnVsbCx0aGlzLl93aW5kb3dGb2N1c2VkPSExLHRoaXMuX29yaWdpbkZyb21Ub3VjaEludGVyYWN0aW9uPSExLHRoaXMuX2VsZW1lbnRJbmZvPW5ldyBNYXAsdGhpcy5fbW9uaXRvcmVkRWxlbWVudENvdW50PTAsdGhpcy5fcm9vdE5vZGVGb2N1c0xpc3RlbmVyQ291bnQ9bmV3IE1hcCx0aGlzLl93aW5kb3dGb2N1c0xpc3RlbmVyPSgpPT57dGhpcy5fd2luZG93Rm9jdXNlZD0hMCx0aGlzLl93aW5kb3dGb2N1c1RpbWVvdXRJZD13aW5kb3cuc2V0VGltZW91dCgoKT0+dGhpcy5fd2luZG93Rm9jdXNlZD0hMSl9LHRoaXMuX3N0b3BJbnB1dE1vZGFsaXR5RGV0ZWN0b3I9bmV3IGtlLHRoaXMuX3Jvb3ROb2RlRm9jdXNBbmRCbHVyTGlzdGVuZXI9YT0+e2ZvcihsZXQgYz1RYyhhKTtjO2M9Yy5wYXJlbnRFbGVtZW50KSJmb2N1cyI9PT1hLnR5cGU/dGhpcy5fb25Gb2N1cyhhLGMpOnRoaXMuX29uQmx1cihhLGMpfSx0aGlzLl9kb2N1bWVudD1vLHRoaXMuX2RldGVjdGlvbk1vZGU9cz8uZGV0ZWN0aW9uTW9kZXx8MH1tb25pdG9yKGUsaT0hMSl7bGV0IHI9TGEoZSk7aWYoIXRoaXMuX3BsYXRmb3JtLmlzQnJvd3Nlcnx8MSE9PXIubm9kZVR5cGUpcmV0dXJuIFh0KG51bGwpO2xldCBvPWEyKHIpfHx0aGlzLl9nZXREb2N1bWVudCgpLHM9dGhpcy5fZWxlbWVudEluZm8uZ2V0KHIpO2lmKHMpcmV0dXJuIGkmJihzLmNoZWNrQ2hpbGRyZW49ITApLHMuc3ViamVjdDtsZXQgYT17Y2hlY2tDaGlsZHJlbjppLHN1YmplY3Q6bmV3IGtlLHJvb3ROb2RlOm99O3JldHVybiB0aGlzLl9lbGVtZW50SW5mby5zZXQocixhKSx0aGlzLl9yZWdpc3Rlckdsb2JhbExpc3RlbmVycyhhKSxhLnN1YmplY3R9c3RvcE1vbml0b3JpbmcoZSl7bGV0IGk9TGEoZSkscj10aGlzLl9lbGVtZW50SW5mby5nZXQoaSk7ciYmKHIuc3ViamVjdC5jb21wbGV0ZSgpLHRoaXMuX3NldENsYXNzZXMoaSksdGhpcy5fZWxlbWVudEluZm8uZGVsZXRlKGkpLHRoaXMuX3JlbW92ZUdsb2JhbExpc3RlbmVycyhyKSl9Zm9jdXNWaWEoZSxpLHIpe2xldCBvPUxhKGUpO289PT10aGlzLl9nZXREb2N1bWVudCgpLmFjdGl2ZUVsZW1lbnQ/dGhpcy5fZ2V0Q2xvc2VzdEVsZW1lbnRzSW5mbyhvKS5mb3JFYWNoKChbYSxsXSk9PnRoaXMuX29yaWdpbkNoYW5nZWQoYSxpLGwpKToodGhpcy5fc2V0T3JpZ2luKGkpLCJmdW5jdGlvbiI9PXR5cGVvZiBvLmZvY3VzJiZvLmZvY3VzKHIpKX1uZ09uRGVzdHJveSgpe3RoaXMuX2VsZW1lbnRJbmZvLmZvckVhY2goKGUsaSk9PnRoaXMuc3RvcE1vbml0b3JpbmcoaSkpfV9nZXREb2N1bWVudCgpe3JldHVybiB0aGlzLl9kb2N1bWVudHx8ZG9jdW1lbnR9X2dldFdpbmRvdygpe3JldHVybiB0aGlzLl9nZXREb2N1bWVudCgpLmRlZmF1bHRWaWV3fHx3aW5kb3d9X2dldEZvY3VzT3JpZ2luKGUpe3JldHVybiB0aGlzLl9vcmlnaW4/dGhpcy5fb3JpZ2luRnJvbVRvdWNoSW50ZXJhY3Rpb24/dGhpcy5fc2hvdWxkQmVBdHRyaWJ1dGVkVG9Ub3VjaChlKT8idG91Y2giOiJwcm9ncmFtIjp0aGlzLl9vcmlnaW46dGhpcy5fd2luZG93Rm9jdXNlZCYmdGhpcy5fbGFzdEZvY3VzT3JpZ2luP3RoaXMuX2xhc3RGb2N1c09yaWdpbjplJiZ0aGlzLl9pc0xhc3RJbnRlcmFjdGlvbkZyb21JbnB1dExhYmVsKGUpPyJtb3VzZSI6InByb2dyYW0ifV9zaG91bGRCZUF0dHJpYnV0ZWRUb1RvdWNoKGUpe3JldHVybiAxPT09dGhpcy5fZGV0ZWN0aW9uTW9kZXx8ISFlPy5jb250YWlucyh0aGlzLl9pbnB1dE1vZGFsaXR5RGV0ZWN0b3IuX21vc3RSZWNlbnRUYXJnZXQpfV9zZXRDbGFzc2VzKGUsaSl7ZS5jbGFzc0xpc3QudG9nZ2xlKCJjZGstZm9jdXNlZCIsISFpKSxlLmNsYXNzTGlzdC50b2dnbGUoImNkay10b3VjaC1mb2N1c2VkIiwidG91Y2giPT09aSksZS5jbGFzc0xpc3QudG9nZ2xlKCJjZGsta2V5Ym9hcmQtZm9jdXNlZCIsImtleWJvYXJkIj09PWkpLGUuY2xhc3NMaXN0LnRvZ2dsZSgiY2RrLW1vdXNlLWZvY3VzZWQiLCJtb3VzZSI9PT1pKSxlLmNsYXNzTGlzdC50b2dnbGUoImNkay1wcm9ncmFtLWZvY3VzZWQiLCJwcm9ncmFtIj09PWkpfV9zZXRPcmlnaW4oZSxpPSExKXt0aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9Pnt0aGlzLl9vcmlnaW49ZSx0aGlzLl9vcmlnaW5Gcm9tVG91Y2hJbnRlcmFjdGlvbj0idG91Y2giPT09ZSYmaSwwPT09dGhpcy5fZGV0ZWN0aW9uTW9kZSYmKGNsZWFyVGltZW91dCh0aGlzLl9vcmlnaW5UaW1lb3V0SWQpLHRoaXMuX29yaWdpblRpbWVvdXRJZD1zZXRUaW1lb3V0KCgpPT50aGlzLl9vcmlnaW49bnVsbCx0aGlzLl9vcmlnaW5Gcm9tVG91Y2hJbnRlcmFjdGlvbj82NTA6MSkpfSl9X29uRm9jdXMoZSxpKXtsZXQgcj10aGlzLl9lbGVtZW50SW5mby5nZXQoaSksbz1RYyhlKTshcnx8IXIuY2hlY2tDaGlsZHJlbiYmaSE9PW98fHRoaXMuX29yaWdpbkNoYW5nZWQoaSx0aGlzLl9nZXRGb2N1c09yaWdpbihvKSxyKX1fb25CbHVyKGUsaSl7bGV0IHI9dGhpcy5fZWxlbWVudEluZm8uZ2V0KGkpOyFyfHxyLmNoZWNrQ2hpbGRyZW4mJmUucmVsYXRlZFRhcmdldCBpbnN0YW5jZW9mIE5vZGUmJmkuY29udGFpbnMoZS5yZWxhdGVkVGFyZ2V0KXx8KHRoaXMuX3NldENsYXNzZXMoaSksdGhpcy5fZW1pdE9yaWdpbihyLG51bGwpKX1fZW1pdE9yaWdpbihlLGkpe2Uuc3ViamVjdC5vYnNlcnZlcnMubGVuZ3RoJiZ0aGlzLl9uZ1pvbmUucnVuKCgpPT5lLnN1YmplY3QubmV4dChpKSl9X3JlZ2lzdGVyR2xvYmFsTGlzdGVuZXJzKGUpe2lmKCF0aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXIpcmV0dXJuO2xldCBpPWUucm9vdE5vZGUscj10aGlzLl9yb290Tm9kZUZvY3VzTGlzdGVuZXJDb3VudC5nZXQoaSl8fDA7cnx8dGhpcy5fbmdab25lLnJ1bk91dHNpZGVBbmd1bGFyKCgpPT57aS5hZGRFdmVudExpc3RlbmVyKCJmb2N1cyIsdGhpcy5fcm9vdE5vZGVGb2N1c0FuZEJsdXJMaXN0ZW5lcixkMiksaS5hZGRFdmVudExpc3RlbmVyKCJibHVyIix0aGlzLl9yb290Tm9kZUZvY3VzQW5kQmx1ckxpc3RlbmVyLGQyKX0pLHRoaXMuX3Jvb3ROb2RlRm9jdXNMaXN0ZW5lckNvdW50LnNldChpLHIrMSksMT09Kyt0aGlzLl9tb25pdG9yZWRFbGVtZW50Q291bnQmJih0aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9Pnt0aGlzLl9nZXRXaW5kb3coKS5hZGRFdmVudExpc3RlbmVyKCJmb2N1cyIsdGhpcy5fd2luZG93Rm9jdXNMaXN0ZW5lcil9KSx0aGlzLl9pbnB1dE1vZGFsaXR5RGV0ZWN0b3IubW9kYWxpdHlEZXRlY3RlZC5waXBlKHN0KHRoaXMuX3N0b3BJbnB1dE1vZGFsaXR5RGV0ZWN0b3IpKS5zdWJzY3JpYmUobz0+e3RoaXMuX3NldE9yaWdpbihvLCEwKX0pKX1fcmVtb3ZlR2xvYmFsTGlzdGVuZXJzKGUpe2xldCBpPWUucm9vdE5vZGU7aWYodGhpcy5fcm9vdE5vZGVGb2N1c0xpc3RlbmVyQ291bnQuaGFzKGkpKXtsZXQgcj10aGlzLl9yb290Tm9kZUZvY3VzTGlzdGVuZXJDb3VudC5nZXQoaSk7cj4xP3RoaXMuX3Jvb3ROb2RlRm9jdXNMaXN0ZW5lckNvdW50LnNldChpLHItMSk6KGkucmVtb3ZlRXZlbnRMaXN0ZW5lcigiZm9jdXMiLHRoaXMuX3Jvb3ROb2RlRm9jdXNBbmRCbHVyTGlzdGVuZXIsZDIpLGkucmVtb3ZlRXZlbnRMaXN0ZW5lcigiYmx1ciIsdGhpcy5fcm9vdE5vZGVGb2N1c0FuZEJsdXJMaXN0ZW5lcixkMiksdGhpcy5fcm9vdE5vZGVGb2N1c0xpc3RlbmVyQ291bnQuZGVsZXRlKGkpKX0tLXRoaXMuX21vbml0b3JlZEVsZW1lbnRDb3VudHx8KHRoaXMuX2dldFdpbmRvdygpLnJlbW92ZUV2ZW50TGlzdGVuZXIoImZvY3VzIix0aGlzLl93aW5kb3dGb2N1c0xpc3RlbmVyKSx0aGlzLl9zdG9wSW5wdXRNb2RhbGl0eURldGVjdG9yLm5leHQoKSxjbGVhclRpbWVvdXQodGhpcy5fd2luZG93Rm9jdXNUaW1lb3V0SWQpLGNsZWFyVGltZW91dCh0aGlzLl9vcmlnaW5UaW1lb3V0SWQpKX1fb3JpZ2luQ2hhbmdlZChlLGkscil7dGhpcy5fc2V0Q2xhc3NlcyhlLGkpLHRoaXMuX2VtaXRPcmlnaW4ocixpKSx0aGlzLl9sYXN0Rm9jdXNPcmlnaW49aX1fZ2V0Q2xvc2VzdEVsZW1lbnRzSW5mbyhlKXtsZXQgaT1bXTtyZXR1cm4gdGhpcy5fZWxlbWVudEluZm8uZm9yRWFjaCgocixvKT0+eyhvPT09ZXx8ci5jaGVja0NoaWxkcmVuJiZvLmNvbnRhaW5zKGUpKSYmaS5wdXNoKFtvLHJdKX0pLGl9X2lzTGFzdEludGVyYWN0aW9uRnJvbUlucHV0TGFiZWwoZSl7bGV0e19tb3N0UmVjZW50VGFyZ2V0OmksbW9zdFJlY2VudE1vZGFsaXR5OnJ9PXRoaXMuX2lucHV0TW9kYWxpdHlEZXRlY3RvcjtpZigibW91c2UiIT09cnx8IWl8fGk9PT1lfHwiSU5QVVQiIT09ZS5ub2RlTmFtZSYmIlRFWFRBUkVBIiE9PWUubm9kZU5hbWV8fGUuZGlzYWJsZWQpcmV0dXJuITE7bGV0IG89ZS5sYWJlbHM7aWYobylmb3IobGV0IHM9MDtzPG8ubGVuZ3RoO3MrKylpZihvW3NdLmNvbnRhaW5zKGkpKXJldHVybiEwO3JldHVybiExfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKF90KSxqKG9pKSxqKE5SZSksaihIdCw4KSxqKEhSZSw4KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksbnRlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXt0aGlzLl9lbGVtZW50UmVmPWUsdGhpcy5fZm9jdXNNb25pdG9yPWksdGhpcy5fZm9jdXNPcmlnaW49bnVsbCx0aGlzLmNka0ZvY3VzQ2hhbmdlPW5ldyBHfWdldCBmb2N1c09yaWdpbigpe3JldHVybiB0aGlzLl9mb2N1c09yaWdpbn1uZ0FmdGVyVmlld0luaXQoKXtsZXQgZT10aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQ7dGhpcy5fbW9uaXRvclN1YnNjcmlwdGlvbj10aGlzLl9mb2N1c01vbml0b3IubW9uaXRvcihlLDE9PT1lLm5vZGVUeXBlJiZlLmhhc0F0dHJpYnV0ZSgiY2RrTW9uaXRvclN1YnRyZWVGb2N1cyIpKS5zdWJzY3JpYmUoaT0+e3RoaXMuX2ZvY3VzT3JpZ2luPWksdGhpcy5jZGtGb2N1c0NoYW5nZS5lbWl0KGkpfSl9bmdPbkRlc3Ryb3koKXt0aGlzLl9mb2N1c01vbml0b3Iuc3RvcE1vbml0b3JpbmcodGhpcy5fZWxlbWVudFJlZiksdGhpcy5fbW9uaXRvclN1YnNjcmlwdGlvbiYmdGhpcy5fbW9uaXRvclN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKEZyKSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsImNka01vbml0b3JFbGVtZW50Rm9jdXMiLCIiXSxbIiIsImNka01vbml0b3JTdWJ0cmVlRm9jdXMiLCIiXV0sb3V0cHV0czp7Y2RrRm9jdXNDaGFuZ2U6ImNka0ZvY3VzQ2hhbmdlIn0sZXhwb3J0QXM6WyJjZGtNb25pdG9yRm9jdXMiXX0pLG59KSgpLEtlZT0iY2RrLWhpZ2gtY29udHJhc3QtYmxhY2stb24td2hpdGUiLFplZT0iY2RrLWhpZ2gtY29udHJhc3Qtd2hpdGUtb24tYmxhY2siLG1IPSJjZGstaGlnaC1jb250cmFzdC1hY3RpdmUiLENIPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXt0aGlzLl9wbGF0Zm9ybT1lLHRoaXMuX2RvY3VtZW50PWksdGhpcy5fYnJlYWtwb2ludFN1YnNjcmlwdGlvbj1qbyhKbSkub2JzZXJ2ZSgiKGZvcmNlZC1jb2xvcnM6IGFjdGl2ZSkiKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9oYXNDaGVja2VkSGlnaENvbnRyYXN0TW9kZSYmKHRoaXMuX2hhc0NoZWNrZWRIaWdoQ29udHJhc3RNb2RlPSExLHRoaXMuX2FwcGx5Qm9keUhpZ2hDb250cmFzdE1vZGVDc3NDbGFzc2VzKCkpfSl9Z2V0SGlnaENvbnRyYXN0TW9kZSgpe2lmKCF0aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXIpcmV0dXJuIDA7bGV0IGU9dGhpcy5fZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7ZS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3I9InJnYigxLDIsMykiLGUuc3R5bGUucG9zaXRpb249ImFic29sdXRlIix0aGlzLl9kb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGUpO2xldCBpPXRoaXMuX2RvY3VtZW50LmRlZmF1bHRWaWV3fHx3aW5kb3cscj1pJiZpLmdldENvbXB1dGVkU3R5bGU/aS5nZXRDb21wdXRlZFN0eWxlKGUpOm51bGwsbz0ociYmci5iYWNrZ3JvdW5kQ29sb3J8fCIiKS5yZXBsYWNlKC8gL2csIiIpO3N3aXRjaChlLnJlbW92ZSgpLG8pe2Nhc2UicmdiKDAsMCwwKSI6Y2FzZSJyZ2IoNDUsNTAsNTQpIjpjYXNlInJnYigzMiwzMiwzMikiOnJldHVybiAyO2Nhc2UicmdiKDI1NSwyNTUsMjU1KSI6Y2FzZSJyZ2IoMjU1LDI1MCwyMzkpIjpyZXR1cm4gMX1yZXR1cm4gMH1uZ09uRGVzdHJveSgpe3RoaXMuX2JyZWFrcG9pbnRTdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKX1fYXBwbHlCb2R5SGlnaENvbnRyYXN0TW9kZUNzc0NsYXNzZXMoKXtpZighdGhpcy5faGFzQ2hlY2tlZEhpZ2hDb250cmFzdE1vZGUmJnRoaXMuX3BsYXRmb3JtLmlzQnJvd3NlciYmdGhpcy5fZG9jdW1lbnQuYm9keSl7bGV0IGU9dGhpcy5fZG9jdW1lbnQuYm9keS5jbGFzc0xpc3Q7ZS5yZW1vdmUobUgsS2VlLFplZSksdGhpcy5faGFzQ2hlY2tlZEhpZ2hDb250cmFzdE1vZGU9ITA7bGV0IGk9dGhpcy5nZXRIaWdoQ29udHJhc3RNb2RlKCk7MT09PWk/ZS5hZGQobUgsS2VlKToyPT09aSYmZS5hZGQobUgsWmVlKX19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGoob2kpLGooSHQpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKSxFdj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe2UuX2FwcGx5Qm9keUhpZ2hDb250cmFzdE1vZGVDc3NDbGFzc2VzKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooQ0gpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbb2RdfSksbn0pKCksVVJlPW5ldyBwZSgiY2RrLWRpci1kb2MiLHtwcm92aWRlZEluOiJyb290IixmYWN0b3J5OmZ1bmN0aW9uKCl7cmV0dXJuIGpvKEh0KX19KSxqUmU9L14oYXJ8Y2tifGR2fGhlfGl3fGZhfG5xb3xwc3xzZHx1Z3x1cnx5aXwuKlstX10oQWRsbXxBcmFifEhlYnJ8Tmtvb3xSb2hnfFRoYWEpKSg/IS4qWy1fXShMYXRufEN5cmwpKCR8LXxfKSkoJHwtfF8pL2ksJGk9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXtpZih0aGlzLnZhbHVlPSJsdHIiLHRoaXMuY2hhbmdlPW5ldyBHLGUpe2xldCByPWUuZG9jdW1lbnRFbGVtZW50P2UuZG9jdW1lbnRFbGVtZW50LmRpcjpudWxsO3RoaXMudmFsdWU9ZnVuY3Rpb24obil7bGV0IHQ9bj8udG9Mb3dlckNhc2UoKXx8IiI7cmV0dXJuImF1dG8iPT09dCYmdHlwZW9mIG5hdmlnYXRvcjwidSImJm5hdmlnYXRvcj8ubGFuZ3VhZ2U/alJlLnRlc3QobmF2aWdhdG9yLmxhbmd1YWdlKT8icnRsIjoibHRyIjoicnRsIj09PXQ/InJ0bCI6Imx0ciJ9KChlLmJvZHk/ZS5ib2R5LmRpcjpudWxsKXx8cnx8Imx0ciIpfX1uZ09uRGVzdHJveSgpe3RoaXMuY2hhbmdlLmNvbXBsZXRlKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooVVJlLDgpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKSxEaD0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCk7ZnVuY3Rpb24gV1JlKG4sdCl7aWYoMSZuJiZPKDAsIm1hdC1wc2V1ZG8tY2hlY2tib3giLDQpLDImbil7bGV0IGU9UygpO3koInN0YXRlIixlLnNlbGVjdGVkPyJjaGVja2VkIjoidW5jaGVja2VkIikoImRpc2FibGVkIixlLmRpc2FibGVkKX19ZnVuY3Rpb24gcVJlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiw1KSxBKDEpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygxKSxqZSgiKCIsZS5ncm91cC5sYWJlbCwiKSIpfX12YXIgWVJlPVsiKiJdLHN0ZT0obmV3IEljKCIxNC4yLjciKSwoKCk9PntjbGFzcyBue31yZXR1cm4gbi5TVEFOREFSRF9DVVJWRT0iY3ViaWMtYmV6aWVyKDAuNCwwLjAsMC4yLDEpIixuLkRFQ0VMRVJBVElPTl9DVVJWRT0iY3ViaWMtYmV6aWVyKDAuMCwwLjAsMC4yLDEpIixuLkFDQ0VMRVJBVElPTl9DVVJWRT0iY3ViaWMtYmV6aWVyKDAuNCwwLjAsMSwxKSIsbi5TSEFSUF9DVVJWRT0iY3ViaWMtYmV6aWVyKDAuNCwwLjAsMC42LDEpIixufSkoKSksYXRlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLkNPTVBMRVg9IjM3NW1zIixuLkVOVEVSSU5HPSIyMjVtcyIsbi5FWElUSU5HPSIxOTVtcyIsbn0pKCksUVJlPW5ldyBwZSgibWF0LXNhbml0eS1jaGVja3MiLHtwcm92aWRlZEluOiJyb290IixmYWN0b3J5OmZ1bmN0aW9uKCl7cmV0dXJuITB9fSksbG49KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5fc2FuaXR5Q2hlY2tzPWksdGhpcy5fZG9jdW1lbnQ9cix0aGlzLl9oYXNEb25lR2xvYmFsQ2hlY2tzPSExLGUuX2FwcGx5Qm9keUhpZ2hDb250cmFzdE1vZGVDc3NDbGFzc2VzKCksdGhpcy5faGFzRG9uZUdsb2JhbENoZWNrc3x8KHRoaXMuX2hhc0RvbmVHbG9iYWxDaGVja3M9ITApfV9jaGVja0lzRW5hYmxlZChlKXtyZXR1cm4hWk0oKSYmKCJib29sZWFuIj09dHlwZW9mIHRoaXMuX3Nhbml0eUNoZWNrcz90aGlzLl9zYW5pdHlDaGVja3M6ISF0aGlzLl9zYW5pdHlDaGVja3NbZV0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKENIKSxqKFFSZSw4KSxqKEh0KSl9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W0RoLERoXX0pLG59KSgpO2Z1bmN0aW9uIHNvKG4pe3JldHVybiBjbGFzcyBleHRlbmRzIG57Y29uc3RydWN0b3IoLi4udCl7c3VwZXIoLi4udCksdGhpcy5fZGlzYWJsZWQ9ITF9Z2V0IGRpc2FibGVkKCl7cmV0dXJuIHRoaXMuX2Rpc2FibGVkfXNldCBkaXNhYmxlZCh0KXt0aGlzLl9kaXNhYmxlZD1SdCh0KX19fWZ1bmN0aW9uIGtvKG4sdCl7cmV0dXJuIGNsYXNzIGV4dGVuZHMgbntjb25zdHJ1Y3RvciguLi5lKXtzdXBlciguLi5lKSx0aGlzLmRlZmF1bHRDb2xvcj10LHRoaXMuY29sb3I9dH1nZXQgY29sb3IoKXtyZXR1cm4gdGhpcy5fY29sb3J9c2V0IGNvbG9yKGUpe2xldCBpPWV8fHRoaXMuZGVmYXVsdENvbG9yO2khPT10aGlzLl9jb2xvciYmKHRoaXMuX2NvbG9yJiZ0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuY2xhc3NMaXN0LnJlbW92ZShgbWF0LSR7dGhpcy5fY29sb3J9YCksaSYmdGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LmNsYXNzTGlzdC5hZGQoYG1hdC0ke2l9YCksdGhpcy5fY29sb3I9aSl9fX1mdW5jdGlvbiBxbyhuKXtyZXR1cm4gY2xhc3MgZXh0ZW5kcyBue2NvbnN0cnVjdG9yKC4uLnQpe3N1cGVyKC4uLnQpLHRoaXMuX2Rpc2FibGVSaXBwbGU9ITF9Z2V0IGRpc2FibGVSaXBwbGUoKXtyZXR1cm4gdGhpcy5fZGlzYWJsZVJpcHBsZX1zZXQgZGlzYWJsZVJpcHBsZSh0KXt0aGlzLl9kaXNhYmxlUmlwcGxlPVJ0KHQpfX19ZnVuY3Rpb24gb2Mobix0PTApe3JldHVybiBjbGFzcyBleHRlbmRzIG57Y29uc3RydWN0b3IoLi4uZSl7c3VwZXIoLi4uZSksdGhpcy5fdGFiSW5kZXg9dCx0aGlzLmRlZmF1bHRUYWJJbmRleD10fWdldCB0YWJJbmRleCgpe3JldHVybiB0aGlzLmRpc2FibGVkPy0xOnRoaXMuX3RhYkluZGV4fXNldCB0YWJJbmRleChlKXt0aGlzLl90YWJJbmRleD1udWxsIT1lP0JpKGUpOnRoaXMuZGVmYXVsdFRhYkluZGV4fX19ZnVuY3Rpb24gRHYobil7cmV0dXJuIGNsYXNzIGV4dGVuZHMgbntjb25zdHJ1Y3RvciguLi50KXtzdXBlciguLi50KSx0aGlzLmVycm9yU3RhdGU9ITF9dXBkYXRlRXJyb3JTdGF0ZSgpe2xldCB0PXRoaXMuZXJyb3JTdGF0ZSxvPSh0aGlzLmVycm9yU3RhdGVNYXRjaGVyfHx0aGlzLl9kZWZhdWx0RXJyb3JTdGF0ZU1hdGNoZXIpLmlzRXJyb3JTdGF0ZSh0aGlzLm5nQ29udHJvbD90aGlzLm5nQ29udHJvbC5jb250cm9sOm51bGwsdGhpcy5fcGFyZW50Rm9ybUdyb3VwfHx0aGlzLl9wYXJlbnRGb3JtKTtvIT09dCYmKHRoaXMuZXJyb3JTdGF0ZT1vLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKSl9fX1mdW5jdGlvbiBtMihuKXtyZXR1cm4gY2xhc3MgZXh0ZW5kcyBue2NvbnN0cnVjdG9yKC4uLnQpe3N1cGVyKC4uLnQpLHRoaXMuX2lzSW5pdGlhbGl6ZWQ9ITEsdGhpcy5fcGVuZGluZ1N1YnNjcmliZXJzPVtdLHRoaXMuaW5pdGlhbGl6ZWQ9bmV3IHVuKGU9Pnt0aGlzLl9pc0luaXRpYWxpemVkP3RoaXMuX25vdGlmeVN1YnNjcmliZXIoZSk6dGhpcy5fcGVuZGluZ1N1YnNjcmliZXJzLnB1c2goZSl9KX1fbWFya0luaXRpYWxpemVkKCl7dGhpcy5faXNJbml0aWFsaXplZD0hMCx0aGlzLl9wZW5kaW5nU3Vic2NyaWJlcnMuZm9yRWFjaCh0aGlzLl9ub3RpZnlTdWJzY3JpYmVyKSx0aGlzLl9wZW5kaW5nU3Vic2NyaWJlcnM9bnVsbH1fbm90aWZ5U3Vic2NyaWJlcih0KXt0Lm5leHQoKSx0LmNvbXBsZXRlKCl9fX1uZXcgcGUoIk1BVF9EQVRFX0xPQ0FMRSIse3Byb3ZpZGVkSW46InJvb3QiLGZhY3Rvcnk6ZnVuY3Rpb24oKXtyZXR1cm4gam8oV2QpfX0pLG5ldyBwZSgibWF0LWRhdGUtZm9ybWF0cyIpO3ZhciBjZD0oKCk9PntjbGFzcyBue2lzRXJyb3JTdGF0ZShlLGkpe3JldHVybiEhKGUmJmUuaW52YWxpZCYmKGUudG91Y2hlZHx8aSYmaS5zdWJtaXR0ZWQpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksaXRlPXtlbnRlckR1cmF0aW9uOjIyNSxleGl0RHVyYXRpb246MTUwfSxNSD1sYSh7cGFzc2l2ZTohMH0pLHJ0ZT1bIm1vdXNlZG93biIsInRvdWNoc3RhcnQiXSxvdGU9WyJtb3VzZXVwIiwibW91c2VsZWF2ZSIsInRvdWNoZW5kIiwidG91Y2hjYW5jZWwiXSxUdj1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXt0aGlzLl90YXJnZXQ9dCx0aGlzLl9uZ1pvbmU9ZSx0aGlzLl9pc1BvaW50ZXJEb3duPSExLHRoaXMuX2FjdGl2ZVJpcHBsZXM9bmV3IE1hcCx0aGlzLl9wb2ludGVyVXBFdmVudHNSZWdpc3RlcmVkPSExLHIuaXNCcm93c2VyJiYodGhpcy5fY29udGFpbmVyRWxlbWVudD1MYShpKSl9ZmFkZUluUmlwcGxlKHQsZSxpPXt9KXtsZXQgcj10aGlzLl9jb250YWluZXJSZWN0PXRoaXMuX2NvbnRhaW5lclJlY3R8fHRoaXMuX2NvbnRhaW5lckVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksbz17Li4uaXRlLC4uLmkuYW5pbWF0aW9ufTtpLmNlbnRlcmVkJiYodD1yLmxlZnQrci53aWR0aC8yLGU9ci50b3Arci5oZWlnaHQvMik7bGV0IHM9aS5yYWRpdXN8fGZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1NYXRoLm1heChNYXRoLmFicyhuLWUubGVmdCksTWF0aC5hYnMobi1lLnJpZ2h0KSkscj1NYXRoLm1heChNYXRoLmFicyh0LWUudG9wKSxNYXRoLmFicyh0LWUuYm90dG9tKSk7cmV0dXJuIE1hdGguc3FydChpKmkrcipyKX0odCxlLHIpLGE9dC1yLmxlZnQsbD1lLXIudG9wLGM9by5lbnRlckR1cmF0aW9uLHU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7dS5jbGFzc0xpc3QuYWRkKCJtYXQtcmlwcGxlLWVsZW1lbnQiKSx1LnN0eWxlLmxlZnQ9YS1zKyJweCIsdS5zdHlsZS50b3A9bC1zKyJweCIsdS5zdHlsZS5oZWlnaHQ9MipzKyJweCIsdS5zdHlsZS53aWR0aD0yKnMrInB4IixudWxsIT1pLmNvbG9yJiYodS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3I9aS5jb2xvciksdS5zdHlsZS50cmFuc2l0aW9uRHVyYXRpb249YCR7Y31tc2AsdGhpcy5fY29udGFpbmVyRWxlbWVudC5hcHBlbmRDaGlsZCh1KTtsZXQgZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSh1KSxoPWQudHJhbnNpdGlvbkR1cmF0aW9uLGY9Im5vbmUiPT09ZC50cmFuc2l0aW9uUHJvcGVydHl8fCIwcyI9PT1ofHwiMHMsIDBzIj09PWgsbT1uZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlLGkscj0hMSl7dGhpcy5fcmVuZGVyZXI9dCx0aGlzLmVsZW1lbnQ9ZSx0aGlzLmNvbmZpZz1pLHRoaXMuX2FuaW1hdGlvbkZvcmNpYmx5RGlzYWJsZWRUaHJvdWdoQ3NzPXIsdGhpcy5zdGF0ZT0zfWZhZGVPdXQoKXt0aGlzLl9yZW5kZXJlci5mYWRlT3V0UmlwcGxlKHRoaXMpfX0odGhpcyx1LGksZik7dS5zdHlsZS50cmFuc2Zvcm09InNjYWxlM2QoMSwgMSwgMSkiLG0uc3RhdGU9MCxpLnBlcnNpc3RlbnR8fCh0aGlzLl9tb3N0UmVjZW50VHJhbnNpZW50UmlwcGxlPW0pO2xldCB4PW51bGw7cmV0dXJuIWYmJihjfHxvLmV4aXREdXJhdGlvbikmJnRoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e2xldCBnPSgpPT50aGlzLl9maW5pc2hSaXBwbGVUcmFuc2l0aW9uKG0pLGI9KCk9PnRoaXMuX2Rlc3Ryb3lSaXBwbGUobSk7dS5hZGRFdmVudExpc3RlbmVyKCJ0cmFuc2l0aW9uZW5kIixnKSx1LmFkZEV2ZW50TGlzdGVuZXIoInRyYW5zaXRpb25jYW5jZWwiLGIpLHg9e29uVHJhbnNpdGlvbkVuZDpnLG9uVHJhbnNpdGlvbkNhbmNlbDpifX0pLHRoaXMuX2FjdGl2ZVJpcHBsZXMuc2V0KG0seCksKGZ8fCFjKSYmdGhpcy5fZmluaXNoUmlwcGxlVHJhbnNpdGlvbihtKSxtfWZhZGVPdXRSaXBwbGUodCl7aWYoMj09PXQuc3RhdGV8fDM9PT10LnN0YXRlKXJldHVybjtsZXQgZT10LmVsZW1lbnQsaT17Li4uaXRlLC4uLnQuY29uZmlnLmFuaW1hdGlvbn07ZS5zdHlsZS50cmFuc2l0aW9uRHVyYXRpb249YCR7aS5leGl0RHVyYXRpb259bXNgLGUuc3R5bGUub3BhY2l0eT0iMCIsdC5zdGF0ZT0yLCh0Ll9hbmltYXRpb25Gb3JjaWJseURpc2FibGVkVGhyb3VnaENzc3x8IWkuZXhpdER1cmF0aW9uKSYmdGhpcy5fZmluaXNoUmlwcGxlVHJhbnNpdGlvbih0KX1mYWRlT3V0QWxsKCl7dGhpcy5fZ2V0QWN0aXZlUmlwcGxlcygpLmZvckVhY2godD0+dC5mYWRlT3V0KCkpfWZhZGVPdXRBbGxOb25QZXJzaXN0ZW50KCl7dGhpcy5fZ2V0QWN0aXZlUmlwcGxlcygpLmZvckVhY2godD0+e3QuY29uZmlnLnBlcnNpc3RlbnR8fHQuZmFkZU91dCgpfSl9c2V0dXBUcmlnZ2VyRXZlbnRzKHQpe2xldCBlPUxhKHQpOyFlfHxlPT09dGhpcy5fdHJpZ2dlckVsZW1lbnR8fCh0aGlzLl9yZW1vdmVUcmlnZ2VyRXZlbnRzKCksdGhpcy5fdHJpZ2dlckVsZW1lbnQ9ZSx0aGlzLl9yZWdpc3RlckV2ZW50cyhydGUpKX1oYW5kbGVFdmVudCh0KXsibW91c2Vkb3duIj09PXQudHlwZT90aGlzLl9vbk1vdXNlZG93bih0KToidG91Y2hzdGFydCI9PT10LnR5cGU/dGhpcy5fb25Ub3VjaFN0YXJ0KHQpOnRoaXMuX29uUG9pbnRlclVwKCksdGhpcy5fcG9pbnRlclVwRXZlbnRzUmVnaXN0ZXJlZHx8KHRoaXMuX3JlZ2lzdGVyRXZlbnRzKG90ZSksdGhpcy5fcG9pbnRlclVwRXZlbnRzUmVnaXN0ZXJlZD0hMCl9X2ZpbmlzaFJpcHBsZVRyYW5zaXRpb24odCl7MD09PXQuc3RhdGU/dGhpcy5fc3RhcnRGYWRlT3V0VHJhbnNpdGlvbih0KToyPT09dC5zdGF0ZSYmdGhpcy5fZGVzdHJveVJpcHBsZSh0KX1fc3RhcnRGYWRlT3V0VHJhbnNpdGlvbih0KXtsZXQgZT10PT09dGhpcy5fbW9zdFJlY2VudFRyYW5zaWVudFJpcHBsZSx7cGVyc2lzdGVudDppfT10LmNvbmZpZzt0LnN0YXRlPTEsIWkmJighZXx8IXRoaXMuX2lzUG9pbnRlckRvd24pJiZ0LmZhZGVPdXQoKX1fZGVzdHJveVJpcHBsZSh0KXtsZXQgZT10aGlzLl9hY3RpdmVSaXBwbGVzLmdldCh0KT8/bnVsbDt0aGlzLl9hY3RpdmVSaXBwbGVzLmRlbGV0ZSh0KSx0aGlzLl9hY3RpdmVSaXBwbGVzLnNpemV8fCh0aGlzLl9jb250YWluZXJSZWN0PW51bGwpLHQ9PT10aGlzLl9tb3N0UmVjZW50VHJhbnNpZW50UmlwcGxlJiYodGhpcy5fbW9zdFJlY2VudFRyYW5zaWVudFJpcHBsZT1udWxsKSx0LnN0YXRlPTMsbnVsbCE9PWUmJih0LmVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcigidHJhbnNpdGlvbmVuZCIsZS5vblRyYW5zaXRpb25FbmQpLHQuZWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJ0cmFuc2l0aW9uY2FuY2VsIixlLm9uVHJhbnNpdGlvbkNhbmNlbCkpLHQuZWxlbWVudC5yZW1vdmUoKX1fb25Nb3VzZWRvd24odCl7bGV0IGU9JE0odCksaT10aGlzLl9sYXN0VG91Y2hTdGFydEV2ZW50JiZEYXRlLm5vdygpPHRoaXMuX2xhc3RUb3VjaFN0YXJ0RXZlbnQrODAwOyF0aGlzLl90YXJnZXQucmlwcGxlRGlzYWJsZWQmJiFlJiYhaSYmKHRoaXMuX2lzUG9pbnRlckRvd249ITAsdGhpcy5mYWRlSW5SaXBwbGUodC5jbGllbnRYLHQuY2xpZW50WSx0aGlzLl90YXJnZXQucmlwcGxlQ29uZmlnKSl9X29uVG91Y2hTdGFydCh0KXtpZighdGhpcy5fdGFyZ2V0LnJpcHBsZURpc2FibGVkJiYhZXcodCkpe3RoaXMuX2xhc3RUb3VjaFN0YXJ0RXZlbnQ9RGF0ZS5ub3coKSx0aGlzLl9pc1BvaW50ZXJEb3duPSEwO2xldCBlPXQuY2hhbmdlZFRvdWNoZXM7Zm9yKGxldCBpPTA7aTxlLmxlbmd0aDtpKyspdGhpcy5mYWRlSW5SaXBwbGUoZVtpXS5jbGllbnRYLGVbaV0uY2xpZW50WSx0aGlzLl90YXJnZXQucmlwcGxlQ29uZmlnKX19X29uUG9pbnRlclVwKCl7IXRoaXMuX2lzUG9pbnRlckRvd258fCh0aGlzLl9pc1BvaW50ZXJEb3duPSExLHRoaXMuX2dldEFjdGl2ZVJpcHBsZXMoKS5mb3JFYWNoKHQ9PnshdC5jb25maWcucGVyc2lzdGVudCYmKDE9PT10LnN0YXRlfHx0LmNvbmZpZy50ZXJtaW5hdGVPblBvaW50ZXJVcCYmMD09PXQuc3RhdGUpJiZ0LmZhZGVPdXQoKX0pKX1fcmVnaXN0ZXJFdmVudHModCl7dGhpcy5fbmdab25lLnJ1bk91dHNpZGVBbmd1bGFyKCgpPT57dC5mb3JFYWNoKGU9Pnt0aGlzLl90cmlnZ2VyRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKGUsdGhpcyxNSCl9KX0pfV9nZXRBY3RpdmVSaXBwbGVzKCl7cmV0dXJuIEFycmF5LmZyb20odGhpcy5fYWN0aXZlUmlwcGxlcy5rZXlzKCkpfV9yZW1vdmVUcmlnZ2VyRXZlbnRzKCl7dGhpcy5fdHJpZ2dlckVsZW1lbnQmJihydGUuZm9yRWFjaCh0PT57dGhpcy5fdHJpZ2dlckVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcih0LHRoaXMsTUgpfSksdGhpcy5fcG9pbnRlclVwRXZlbnRzUmVnaXN0ZXJlZCYmb3RlLmZvckVhY2godD0+e3RoaXMuX3RyaWdnZXJFbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIodCx0aGlzLE1IKX0pKX19LGcyPW5ldyBwZSgibWF0LXJpcHBsZS1nbG9iYWwtb3B0aW9ucyIpLFlvPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIsbyxzKXt0aGlzLl9lbGVtZW50UmVmPWUsdGhpcy5fYW5pbWF0aW9uTW9kZT1zLHRoaXMucmFkaXVzPTAsdGhpcy5fZGlzYWJsZWQ9ITEsdGhpcy5faXNJbml0aWFsaXplZD0hMSx0aGlzLl9nbG9iYWxPcHRpb25zPW98fHt9LHRoaXMuX3JpcHBsZVJlbmRlcmVyPW5ldyBUdih0aGlzLGksZSxyKX1nZXQgZGlzYWJsZWQoKXtyZXR1cm4gdGhpcy5fZGlzYWJsZWR9c2V0IGRpc2FibGVkKGUpe2UmJnRoaXMuZmFkZU91dEFsbE5vblBlcnNpc3RlbnQoKSx0aGlzLl9kaXNhYmxlZD1lLHRoaXMuX3NldHVwVHJpZ2dlckV2ZW50c0lmRW5hYmxlZCgpfWdldCB0cmlnZ2VyKCl7cmV0dXJuIHRoaXMuX3RyaWdnZXJ8fHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudH1zZXQgdHJpZ2dlcihlKXt0aGlzLl90cmlnZ2VyPWUsdGhpcy5fc2V0dXBUcmlnZ2VyRXZlbnRzSWZFbmFibGVkKCl9bmdPbkluaXQoKXt0aGlzLl9pc0luaXRpYWxpemVkPSEwLHRoaXMuX3NldHVwVHJpZ2dlckV2ZW50c0lmRW5hYmxlZCgpfW5nT25EZXN0cm95KCl7dGhpcy5fcmlwcGxlUmVuZGVyZXIuX3JlbW92ZVRyaWdnZXJFdmVudHMoKX1mYWRlT3V0QWxsKCl7dGhpcy5fcmlwcGxlUmVuZGVyZXIuZmFkZU91dEFsbCgpfWZhZGVPdXRBbGxOb25QZXJzaXN0ZW50KCl7dGhpcy5fcmlwcGxlUmVuZGVyZXIuZmFkZU91dEFsbE5vblBlcnNpc3RlbnQoKX1nZXQgcmlwcGxlQ29uZmlnKCl7cmV0dXJue2NlbnRlcmVkOnRoaXMuY2VudGVyZWQscmFkaXVzOnRoaXMucmFkaXVzLGNvbG9yOnRoaXMuY29sb3IsYW5pbWF0aW9uOnsuLi50aGlzLl9nbG9iYWxPcHRpb25zLmFuaW1hdGlvbiwuLi4iTm9vcEFuaW1hdGlvbnMiPT09dGhpcy5fYW5pbWF0aW9uTW9kZT97ZW50ZXJEdXJhdGlvbjowLGV4aXREdXJhdGlvbjowfTp7fSwuLi50aGlzLmFuaW1hdGlvbn0sdGVybWluYXRlT25Qb2ludGVyVXA6dGhpcy5fZ2xvYmFsT3B0aW9ucy50ZXJtaW5hdGVPblBvaW50ZXJVcH19Z2V0IHJpcHBsZURpc2FibGVkKCl7cmV0dXJuIHRoaXMuZGlzYWJsZWR8fCEhdGhpcy5fZ2xvYmFsT3B0aW9ucy5kaXNhYmxlZH1fc2V0dXBUcmlnZ2VyRXZlbnRzSWZFbmFibGVkKCl7IXRoaXMuZGlzYWJsZWQmJnRoaXMuX2lzSW5pdGlhbGl6ZWQmJnRoaXMuX3JpcHBsZVJlbmRlcmVyLnNldHVwVHJpZ2dlckV2ZW50cyh0aGlzLnRyaWdnZXIpfWxhdW5jaChlLGk9MCxyKXtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIGU/dGhpcy5fcmlwcGxlUmVuZGVyZXIuZmFkZUluUmlwcGxlKGUsaSx7Li4udGhpcy5yaXBwbGVDb25maWcsLi4ucn0pOnRoaXMuX3JpcHBsZVJlbmRlcmVyLmZhZGVJblJpcHBsZSgwLDAsey4uLnRoaXMucmlwcGxlQ29uZmlnLC4uLmV9KX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShSZSksTShfdCksTShvaSksTShnMiw4KSxNKFBpLDgpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwibWF0LXJpcHBsZSIsIiJdLFsiIiwibWF0UmlwcGxlIiwiIl1dLGhvc3RBdHRyczpbMSwibWF0LXJpcHBsZSJdLGhvc3RWYXJzOjIsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZldCgibWF0LXJpcHBsZS11bmJvdW5kZWQiLGkudW5ib3VuZGVkKX0saW5wdXRzOntjb2xvcjpbIm1hdFJpcHBsZUNvbG9yIiwiY29sb3IiXSx1bmJvdW5kZWQ6WyJtYXRSaXBwbGVVbmJvdW5kZWQiLCJ1bmJvdW5kZWQiXSxjZW50ZXJlZDpbIm1hdFJpcHBsZUNlbnRlcmVkIiwiY2VudGVyZWQiXSxyYWRpdXM6WyJtYXRSaXBwbGVSYWRpdXMiLCJyYWRpdXMiXSxhbmltYXRpb246WyJtYXRSaXBwbGVBbmltYXRpb24iLCJhbmltYXRpb24iXSxkaXNhYmxlZDpbIm1hdFJpcHBsZURpc2FibGVkIiwiZGlzYWJsZWQiXSx0cmlnZ2VyOlsibWF0UmlwcGxlVHJpZ2dlciIsInRyaWdnZXIiXX0sZXhwb3J0QXM6WyJtYXRSaXBwbGUiXX0pLG59KSgpLF9sPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltsbixsbl19KSxufSkoKSwkUmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLl9hbmltYXRpb25Nb2RlPWUsdGhpcy5zdGF0ZT0idW5jaGVja2VkIix0aGlzLmRpc2FibGVkPSExfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFBpLDgpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtYXQtcHNldWRvLWNoZWNrYm94Il1dLGhvc3RBdHRyczpbMSwibWF0LXBzZXVkby1jaGVja2JveCJdLGhvc3RWYXJzOjgsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZldCgibWF0LXBzZXVkby1jaGVja2JveC1pbmRldGVybWluYXRlIiwiaW5kZXRlcm1pbmF0ZSI9PT1pLnN0YXRlKSgibWF0LXBzZXVkby1jaGVja2JveC1jaGVja2VkIiwiY2hlY2tlZCI9PT1pLnN0YXRlKSgibWF0LXBzZXVkby1jaGVja2JveC1kaXNhYmxlZCIsaS5kaXNhYmxlZCkoIl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIiwiTm9vcEFuaW1hdGlvbnMiPT09aS5fYW5pbWF0aW9uTW9kZSl9LGlucHV0czp7c3RhdGU6InN0YXRlIixkaXNhYmxlZDoiZGlzYWJsZWQifSxkZWNsczowLHZhcnM6MCx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpe30sc3R5bGVzOlsnLm1hdC1wc2V1ZG8tY2hlY2tib3h7d2lkdGg6MTZweDtoZWlnaHQ6MTZweDtib3JkZXI6MnB4IHNvbGlkO2JvcmRlci1yYWRpdXM6MnB4O2N1cnNvcjpwb2ludGVyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOm1pZGRsZTtib3gtc2l6aW5nOmJvcmRlci1ib3g7cG9zaXRpb246cmVsYXRpdmU7ZmxleC1zaHJpbms6MDt0cmFuc2l0aW9uOmJvcmRlci1jb2xvciA5MG1zIGN1YmljLWJlemllcigwLCAwLCAwLjIsIDAuMSksYmFja2dyb3VuZC1jb2xvciA5MG1zIGN1YmljLWJlemllcigwLCAwLCAwLjIsIDAuMSl9Lm1hdC1wc2V1ZG8tY2hlY2tib3g6OmFmdGVye3Bvc2l0aW9uOmFic29sdXRlO29wYWNpdHk6MDtjb250ZW50OiIiO2JvcmRlci1ib3R0b206MnB4IHNvbGlkIGN1cnJlbnRDb2xvcjt0cmFuc2l0aW9uOm9wYWNpdHkgOTBtcyBjdWJpYy1iZXppZXIoMCwgMCwgMC4yLCAwLjEpfS5tYXQtcHNldWRvLWNoZWNrYm94Lm1hdC1wc2V1ZG8tY2hlY2tib3gtY2hlY2tlZCwubWF0LXBzZXVkby1jaGVja2JveC5tYXQtcHNldWRvLWNoZWNrYm94LWluZGV0ZXJtaW5hdGV7Ym9yZGVyLWNvbG9yOnJnYmEoMCwwLDAsMCl9Lm1hdC1wc2V1ZG8tY2hlY2tib3guX21hdC1hbmltYXRpb24tbm9vcGFibGV7dHJhbnNpdGlvbjpub25lICFpbXBvcnRhbnQ7YW5pbWF0aW9uOm5vbmUgIWltcG9ydGFudH0ubWF0LXBzZXVkby1jaGVja2JveC5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZTo6YWZ0ZXJ7dHJhbnNpdGlvbjpub25lfS5tYXQtcHNldWRvLWNoZWNrYm94LWRpc2FibGVke2N1cnNvcjpkZWZhdWx0fS5tYXQtcHNldWRvLWNoZWNrYm94LWluZGV0ZXJtaW5hdGU6OmFmdGVye3RvcDo1cHg7bGVmdDoxcHg7d2lkdGg6MTBweDtvcGFjaXR5OjE7Ym9yZGVyLXJhZGl1czoycHh9Lm1hdC1wc2V1ZG8tY2hlY2tib3gtY2hlY2tlZDo6YWZ0ZXJ7dG9wOjIuNHB4O2xlZnQ6MXB4O3dpZHRoOjhweDtoZWlnaHQ6M3B4O2JvcmRlci1sZWZ0OjJweCBzb2xpZCBjdXJyZW50Q29sb3I7dHJhbnNmb3JtOnJvdGF0ZSgtNDVkZWcpO29wYWNpdHk6MTtib3gtc2l6aW5nOmNvbnRlbnQtYm94fSddLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLGVPZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbbG5dfSksbn0pKCksaXc9bmV3IHBlKCJNQVRfT1BUSU9OX1BBUkVOVF9DT01QT05FTlQiKSxydz0oc28oY2xhc3N7fSksbmV3IHBlKCJNYXRPcHRncm91cCIpKSx0T2U9MCxudz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGU9ITEpe3RoaXMuc291cmNlPXQsdGhpcy5pc1VzZXJJbnB1dD1lfX0sbk9lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIsbyl7dGhpcy5fZWxlbWVudD1lLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmPWksdGhpcy5fcGFyZW50PXIsdGhpcy5ncm91cD1vLHRoaXMuX3NlbGVjdGVkPSExLHRoaXMuX2FjdGl2ZT0hMSx0aGlzLl9kaXNhYmxlZD0hMSx0aGlzLl9tb3N0UmVjZW50Vmlld1ZhbHVlPSIiLHRoaXMuaWQ9Im1hdC1vcHRpb24tIit0T2UrKyx0aGlzLm9uU2VsZWN0aW9uQ2hhbmdlPW5ldyBHLHRoaXMuX3N0YXRlQ2hhbmdlcz1uZXcga2V9Z2V0IG11bHRpcGxlKCl7cmV0dXJuIHRoaXMuX3BhcmVudCYmdGhpcy5fcGFyZW50Lm11bHRpcGxlfWdldCBzZWxlY3RlZCgpe3JldHVybiB0aGlzLl9zZWxlY3RlZH1nZXQgZGlzYWJsZWQoKXtyZXR1cm4gdGhpcy5ncm91cCYmdGhpcy5ncm91cC5kaXNhYmxlZHx8dGhpcy5fZGlzYWJsZWR9c2V0IGRpc2FibGVkKGUpe3RoaXMuX2Rpc2FibGVkPVJ0KGUpfWdldCBkaXNhYmxlUmlwcGxlKCl7cmV0dXJuISghdGhpcy5fcGFyZW50fHwhdGhpcy5fcGFyZW50LmRpc2FibGVSaXBwbGUpfWdldCBhY3RpdmUoKXtyZXR1cm4gdGhpcy5fYWN0aXZlfWdldCB2aWV3VmFsdWUoKXtyZXR1cm4odGhpcy5fZ2V0SG9zdEVsZW1lbnQoKS50ZXh0Q29udGVudHx8IiIpLnRyaW0oKX1zZWxlY3QoKXt0aGlzLl9zZWxlY3RlZHx8KHRoaXMuX3NlbGVjdGVkPSEwLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpLHRoaXMuX2VtaXRTZWxlY3Rpb25DaGFuZ2VFdmVudCgpKX1kZXNlbGVjdCgpe3RoaXMuX3NlbGVjdGVkJiYodGhpcy5fc2VsZWN0ZWQ9ITEsdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCksdGhpcy5fZW1pdFNlbGVjdGlvbkNoYW5nZUV2ZW50KCkpfWZvY3VzKGUsaSl7bGV0IHI9dGhpcy5fZ2V0SG9zdEVsZW1lbnQoKTsiZnVuY3Rpb24iPT10eXBlb2Ygci5mb2N1cyYmci5mb2N1cyhpKX1zZXRBY3RpdmVTdHlsZXMoKXt0aGlzLl9hY3RpdmV8fCh0aGlzLl9hY3RpdmU9ITAsdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCkpfXNldEluYWN0aXZlU3R5bGVzKCl7dGhpcy5fYWN0aXZlJiYodGhpcy5fYWN0aXZlPSExLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpKX1nZXRMYWJlbCgpe3JldHVybiB0aGlzLnZpZXdWYWx1ZX1faGFuZGxlS2V5ZG93bihlKXsoMTM9PT1lLmtleUNvZGV8fDMyPT09ZS5rZXlDb2RlKSYmIWtyKGUpJiYodGhpcy5fc2VsZWN0VmlhSW50ZXJhY3Rpb24oKSxlLnByZXZlbnREZWZhdWx0KCkpfV9zZWxlY3RWaWFJbnRlcmFjdGlvbigpe3RoaXMuZGlzYWJsZWR8fCh0aGlzLl9zZWxlY3RlZD0hdGhpcy5tdWx0aXBsZXx8IXRoaXMuX3NlbGVjdGVkLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpLHRoaXMuX2VtaXRTZWxlY3Rpb25DaGFuZ2VFdmVudCghMCkpfV9nZXRBcmlhU2VsZWN0ZWQoKXtyZXR1cm4gdGhpcy5zZWxlY3RlZHx8IXRoaXMubXVsdGlwbGUmJm51bGx9X2dldFRhYkluZGV4KCl7cmV0dXJuIHRoaXMuZGlzYWJsZWQ/Ii0xIjoiMCJ9X2dldEhvc3RFbGVtZW50KCl7cmV0dXJuIHRoaXMuX2VsZW1lbnQubmF0aXZlRWxlbWVudH1uZ0FmdGVyVmlld0NoZWNrZWQoKXtpZih0aGlzLl9zZWxlY3RlZCl7bGV0IGU9dGhpcy52aWV3VmFsdWU7ZSE9PXRoaXMuX21vc3RSZWNlbnRWaWV3VmFsdWUmJih0aGlzLl9tb3N0UmVjZW50Vmlld1ZhbHVlPWUsdGhpcy5fc3RhdGVDaGFuZ2VzLm5leHQoKSl9fW5nT25EZXN0cm95KCl7dGhpcy5fc3RhdGVDaGFuZ2VzLmNvbXBsZXRlKCl9X2VtaXRTZWxlY3Rpb25DaGFuZ2VFdmVudChlPSExKXt0aGlzLm9uU2VsZWN0aW9uQ2hhbmdlLmVtaXQobmV3IG53KHRoaXMsZSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7bmwoKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixpbnB1dHM6e3ZhbHVlOiJ2YWx1ZSIsaWQ6ImlkIixkaXNhYmxlZDoiZGlzYWJsZWQifSxvdXRwdXRzOntvblNlbGVjdGlvbkNoYW5nZToib25TZWxlY3Rpb25DaGFuZ2UifX0pLG59KSgpLE9zPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBuT2V7Y29uc3RydWN0b3IoZSxpLHIsbyl7c3VwZXIoZSxpLHIsbyl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0obm4pLE0oaXcsOCksTShydyw4KSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LW9wdGlvbiJdXSxob3N0QXR0cnM6WyJyb2xlIiwib3B0aW9uIiwxLCJtYXQtb3B0aW9uIiwibWF0LWZvY3VzLWluZGljYXRvciJdLGhvc3RWYXJzOjEyLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkuX3NlbGVjdFZpYUludGVyYWN0aW9uKCl9KSgia2V5ZG93biIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX2hhbmRsZUtleWRvd24obyl9KSwyJmUmJihfcygiaWQiLGkuaWQpLHplKCJ0YWJpbmRleCIsaS5fZ2V0VGFiSW5kZXgoKSkoImFyaWEtc2VsZWN0ZWQiLGkuX2dldEFyaWFTZWxlY3RlZCgpKSgiYXJpYS1kaXNhYmxlZCIsaS5kaXNhYmxlZC50b1N0cmluZygpKSxldCgibWF0LXNlbGVjdGVkIixpLnNlbGVjdGVkKSgibWF0LW9wdGlvbi1tdWx0aXBsZSIsaS5tdWx0aXBsZSkoIm1hdC1hY3RpdmUiLGkuYWN0aXZlKSgibWF0LW9wdGlvbi1kaXNhYmxlZCIsaS5kaXNhYmxlZCkpfSxleHBvcnRBczpbIm1hdE9wdGlvbiJdLGZlYXR1cmVzOlt0dF0sbmdDb250ZW50U2VsZWN0b3JzOllSZSxkZWNsczo1LHZhcnM6NCxjb25zdHM6W1siY2xhc3MiLCJtYXQtb3B0aW9uLXBzZXVkby1jaGVja2JveCIsMywic3RhdGUiLCJkaXNhYmxlZCIsNCwibmdJZiJdLFsxLCJtYXQtb3B0aW9uLXRleHQiXSxbImNsYXNzIiwiY2RrLXZpc3VhbGx5LWhpZGRlbiIsNCwibmdJZiJdLFsibWF0LXJpcHBsZSIsIiIsMSwibWF0LW9wdGlvbi1yaXBwbGUiLDMsIm1hdFJpcHBsZVRyaWdnZXIiLCJtYXRSaXBwbGVEaXNhYmxlZCJdLFsxLCJtYXQtb3B0aW9uLXBzZXVkby1jaGVja2JveCIsMywic3RhdGUiLCJkaXNhYmxlZCJdLFsxLCJjZGstdmlzdWFsbHktaGlkZGVuIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoeGkoKSxFKDAsV1JlLDEsMiwibWF0LXBzZXVkby1jaGVja2JveCIsMCksXygxLCJzcGFuIiwxKSxWbigyKSx2KCksRSgzLHFSZSwyLDEsInNwYW4iLDIpLE8oNCwiZGl2IiwzKSksMiZlJiYoeSgibmdJZiIsaS5tdWx0aXBsZSksQygzKSx5KCJuZ0lmIixpLmdyb3VwJiZpLmdyb3VwLl9pbmVydCksQygxKSx5KCJtYXRSaXBwbGVUcmlnZ2VyIixpLl9nZXRIb3N0RWxlbWVudCgpKSgibWF0UmlwcGxlRGlzYWJsZWQiLGkuZGlzYWJsZWR8fGkuZGlzYWJsZVJpcHBsZSkpfSxkZXBlbmRlbmNpZXM6W1lvLEJlLCRSZV0sc3R5bGVzOlsnLm1hdC1vcHRpb257d2hpdGUtc3BhY2U6bm93cmFwO292ZXJmbG93OmhpZGRlbjt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzO2Rpc3BsYXk6YmxvY2s7bGluZS1oZWlnaHQ6NDhweDtoZWlnaHQ6NDhweDtwYWRkaW5nOjAgMTZweDt0ZXh0LWFsaWduOmxlZnQ7dGV4dC1kZWNvcmF0aW9uOm5vbmU7bWF4LXdpZHRoOjEwMCU7cG9zaXRpb246cmVsYXRpdmU7Y3Vyc29yOnBvaW50ZXI7b3V0bGluZTpub25lO2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpyb3c7bWF4LXdpZHRoOjEwMCU7Ym94LXNpemluZzpib3JkZXItYm94O2FsaWduLWl0ZW1zOmNlbnRlcjstd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6cmdiYSgwLDAsMCwwKX0ubWF0LW9wdGlvbltkaXNhYmxlZF17Y3Vyc29yOmRlZmF1bHR9W2Rpcj1ydGxdIC5tYXQtb3B0aW9ue3RleHQtYWxpZ246cmlnaHR9Lm1hdC1vcHRpb24gLm1hdC1pY29ue21hcmdpbi1yaWdodDoxNnB4O3ZlcnRpY2FsLWFsaWduOm1pZGRsZX0ubWF0LW9wdGlvbiAubWF0LWljb24gc3Zne3ZlcnRpY2FsLWFsaWduOnRvcH1bZGlyPXJ0bF0gLm1hdC1vcHRpb24gLm1hdC1pY29ue21hcmdpbi1sZWZ0OjE2cHg7bWFyZ2luLXJpZ2h0OjB9Lm1hdC1vcHRpb25bYXJpYS1kaXNhYmxlZD10cnVlXXstd2Via2l0LXVzZXItc2VsZWN0Om5vbmU7dXNlci1zZWxlY3Q6bm9uZTtjdXJzb3I6ZGVmYXVsdH0ubWF0LW9wdGdyb3VwIC5tYXQtb3B0aW9uOm5vdCgubWF0LW9wdGlvbi1tdWx0aXBsZSl7cGFkZGluZy1sZWZ0OjMycHh9W2Rpcj1ydGxdIC5tYXQtb3B0Z3JvdXAgLm1hdC1vcHRpb246bm90KC5tYXQtb3B0aW9uLW11bHRpcGxlKXtwYWRkaW5nLWxlZnQ6MTZweDtwYWRkaW5nLXJpZ2h0OjMycHh9Lm1hdC1vcHRpb24ubWF0LWFjdGl2ZTo6YmVmb3Jle2NvbnRlbnQ6IiJ9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LW9wdGlvblthcmlhLWRpc2FibGVkPXRydWVde29wYWNpdHk6LjV9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LW9wdGlvbi5tYXQtc2VsZWN0ZWQ6bm90KC5tYXQtb3B0aW9uLW11bHRpcGxlKTo6YWZ0ZXJ7Y29udGVudDoiIjtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6NTAlO3JpZ2h0OjE2cHg7dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTUwJSk7d2lkdGg6MTBweDtoZWlnaHQ6MDtib3JkZXItYm90dG9tOnNvbGlkIDEwcHg7Ym9yZGVyLXJhZGl1czoxMHB4fVtkaXI9cnRsXSAuY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtb3B0aW9uLm1hdC1zZWxlY3RlZDpub3QoLm1hdC1vcHRpb24tbXVsdGlwbGUpOjphZnRlcntyaWdodDphdXRvO2xlZnQ6MTZweH0ubWF0LW9wdGlvbi10ZXh0e2Rpc3BsYXk6aW5saW5lLWJsb2NrO2ZsZXgtZ3JvdzoxO292ZXJmbG93OmhpZGRlbjt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzfS5tYXQtb3B0aW9uIC5tYXQtb3B0aW9uLXJpcHBsZXt0b3A6MDtsZWZ0OjA7cmlnaHQ6MDtib3R0b206MDtwb3NpdGlvbjphYnNvbHV0ZTtwb2ludGVyLWV2ZW50czpub25lfS5tYXQtb3B0aW9uLXBzZXVkby1jaGVja2JveHttYXJnaW4tcmlnaHQ6OHB4fVtkaXI9cnRsXSAubWF0LW9wdGlvbi1wc2V1ZG8tY2hlY2tib3h7bWFyZ2luLWxlZnQ6OHB4O21hcmdpbi1yaWdodDowfSddLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIG93KG4sdCxlKXtpZihlLmxlbmd0aCl7bGV0IGk9dC50b0FycmF5KCkscj1lLnRvQXJyYXkoKSxvPTA7Zm9yKGxldCBzPTA7czxuKzE7cysrKWlbc10uZ3JvdXAmJmlbc10uZ3JvdXA9PT1yW29dJiZvKys7cmV0dXJuIG99cmV0dXJuIDB9ZnVuY3Rpb24gXzIobix0LGUsaSl7cmV0dXJuIG48ZT9uOm4rdD5lK2k/TWF0aC5tYXgoMCxuLWkrdCk6ZX12YXIgQXY9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W19sLE1lLGxuLGVPZV19KSxufSkoKSxsdGU9WyJtYXQtYnV0dG9uIiwiIl0sY3RlPVsiKiJdLG9PZT1bIm1hdC1idXR0b24iLCJtYXQtZmxhdC1idXR0b24iLCJtYXQtaWNvbi1idXR0b24iLCJtYXQtcmFpc2VkLWJ1dHRvbiIsIm1hdC1zdHJva2VkLWJ1dHRvbiIsIm1hdC1taW5pLWZhYiIsIm1hdC1mYWIiXSxzT2U9a28oc28ocW8oY2xhc3N7Y29uc3RydWN0b3Iobil7dGhpcy5fZWxlbWVudFJlZj1ufX0pKSksX249KCgpPT57Y2xhc3MgbiBleHRlbmRzIHNPZXtjb25zdHJ1Y3RvcihlLGkscil7c3VwZXIoZSksdGhpcy5fZm9jdXNNb25pdG9yPWksdGhpcy5fYW5pbWF0aW9uTW9kZT1yLHRoaXMuaXNSb3VuZEJ1dHRvbj10aGlzLl9oYXNIb3N0QXR0cmlidXRlcygibWF0LWZhYiIsIm1hdC1taW5pLWZhYiIpLHRoaXMuaXNJY29uQnV0dG9uPXRoaXMuX2hhc0hvc3RBdHRyaWJ1dGVzKCJtYXQtaWNvbi1idXR0b24iKTtmb3IobGV0IG8gb2Ygb09lKXRoaXMuX2hhc0hvc3RBdHRyaWJ1dGVzKG8pJiZ0aGlzLl9nZXRIb3N0RWxlbWVudCgpLmNsYXNzTGlzdC5hZGQobyk7ZS5uYXRpdmVFbGVtZW50LmNsYXNzTGlzdC5hZGQoIm1hdC1idXR0b24tYmFzZSIpLHRoaXMuaXNSb3VuZEJ1dHRvbiYmKHRoaXMuY29sb3I9ImFjY2VudCIpfW5nQWZ0ZXJWaWV3SW5pdCgpe3RoaXMuX2ZvY3VzTW9uaXRvci5tb25pdG9yKHRoaXMuX2VsZW1lbnRSZWYsITApfW5nT25EZXN0cm95KCl7dGhpcy5fZm9jdXNNb25pdG9yLnN0b3BNb25pdG9yaW5nKHRoaXMuX2VsZW1lbnRSZWYpfWZvY3VzKGUsaSl7ZT90aGlzLl9mb2N1c01vbml0b3IuZm9jdXNWaWEodGhpcy5fZ2V0SG9zdEVsZW1lbnQoKSxlLGkpOnRoaXMuX2dldEhvc3RFbGVtZW50KCkuZm9jdXMoaSl9X2dldEhvc3RFbGVtZW50KCl7cmV0dXJuIHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudH1faXNSaXBwbGVEaXNhYmxlZCgpe3JldHVybiB0aGlzLmRpc2FibGVSaXBwbGV8fHRoaXMuZGlzYWJsZWR9X2hhc0hvc3RBdHRyaWJ1dGVzKC4uLmUpe3JldHVybiBlLnNvbWUoaT0+dGhpcy5fZ2V0SG9zdEVsZW1lbnQoKS5oYXNBdHRyaWJ1dGUoaSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKEZyKSxNKFBpLDgpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJidXR0b24iLCJtYXQtYnV0dG9uIiwiIl0sWyJidXR0b24iLCJtYXQtcmFpc2VkLWJ1dHRvbiIsIiJdLFsiYnV0dG9uIiwibWF0LWljb24tYnV0dG9uIiwiIl0sWyJidXR0b24iLCJtYXQtZmFiIiwiIl0sWyJidXR0b24iLCJtYXQtbWluaS1mYWIiLCIiXSxbImJ1dHRvbiIsIm1hdC1zdHJva2VkLWJ1dHRvbiIsIiJdLFsiYnV0dG9uIiwibWF0LWZsYXQtYnV0dG9uIiwiIl1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmb3QoWW8sNSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5yaXBwbGU9ci5maXJzdCl9fSxob3N0QXR0cnM6WzEsIm1hdC1mb2N1cy1pbmRpY2F0b3IiXSxob3N0VmFyczo1LGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmKHplKCJkaXNhYmxlZCIsaS5kaXNhYmxlZHx8bnVsbCksZXQoIl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIiwiTm9vcEFuaW1hdGlvbnMiPT09aS5fYW5pbWF0aW9uTW9kZSkoIm1hdC1idXR0b24tZGlzYWJsZWQiLGkuZGlzYWJsZWQpKX0saW5wdXRzOntkaXNhYmxlZDoiZGlzYWJsZWQiLGRpc2FibGVSaXBwbGU6ImRpc2FibGVSaXBwbGUiLGNvbG9yOiJjb2xvciJ9LGV4cG9ydEFzOlsibWF0QnV0dG9uIl0sZmVhdHVyZXM6W3R0XSxhdHRyczpsdGUsbmdDb250ZW50U2VsZWN0b3JzOmN0ZSxkZWNsczo0LHZhcnM6NSxjb25zdHM6W1sxLCJtYXQtYnV0dG9uLXdyYXBwZXIiXSxbIm1hdFJpcHBsZSIsIiIsMSwibWF0LWJ1dHRvbi1yaXBwbGUiLDMsIm1hdFJpcHBsZURpc2FibGVkIiwibWF0UmlwcGxlQ2VudGVyZWQiLCJtYXRSaXBwbGVUcmlnZ2VyIl0sWzEsIm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKHhpKCksXygwLCJzcGFuIiwwKSxWbigxKSx2KCksTygyLCJzcGFuIiwxKSgzLCJzcGFuIiwyKSksMiZlJiYoQygyKSxldCgibWF0LWJ1dHRvbi1yaXBwbGUtcm91bmQiLGkuaXNSb3VuZEJ1dHRvbnx8aS5pc0ljb25CdXR0b24pLHkoIm1hdFJpcHBsZURpc2FibGVkIixpLl9pc1JpcHBsZURpc2FibGVkKCkpKCJtYXRSaXBwbGVDZW50ZXJlZCIsaS5pc0ljb25CdXR0b24pKCJtYXRSaXBwbGVUcmlnZ2VyIixpLl9nZXRIb3N0RWxlbWVudCgpKSl9LGRlcGVuZGVuY2llczpbWW9dLHN0eWxlczpbIi5tYXQtYnV0dG9uIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1pY29uLWJ1dHRvbiAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e29wYWNpdHk6MH0ubWF0LWJ1dHRvbjpob3Zlcjpub3QoLm1hdC1idXR0b24tZGlzYWJsZWQpIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1zdHJva2VkLWJ1dHRvbjpob3Zlcjpub3QoLm1hdC1idXR0b24tZGlzYWJsZWQpIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7b3BhY2l0eTouMDR9QG1lZGlhKGhvdmVyOiBub25lKXsubWF0LWJ1dHRvbjpob3Zlcjpub3QoLm1hdC1idXR0b24tZGlzYWJsZWQpIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1zdHJva2VkLWJ1dHRvbjpob3Zlcjpub3QoLm1hdC1idXR0b24tZGlzYWJsZWQpIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7b3BhY2l0eTowfX0ubWF0LWJ1dHRvbiwubWF0LWljb24tYnV0dG9uLC5tYXQtc3Ryb2tlZC1idXR0b24sLm1hdC1mbGF0LWJ1dHRvbntib3gtc2l6aW5nOmJvcmRlci1ib3g7cG9zaXRpb246cmVsYXRpdmU7LXdlYmtpdC11c2VyLXNlbGVjdDpub25lO3VzZXItc2VsZWN0Om5vbmU7Y3Vyc29yOnBvaW50ZXI7b3V0bGluZTpub25lO2JvcmRlcjpub25lOy13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjpyZ2JhKDAsMCwwLDApO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3doaXRlLXNwYWNlOm5vd3JhcDt0ZXh0LWRlY29yYXRpb246bm9uZTt2ZXJ0aWNhbC1hbGlnbjpiYXNlbGluZTt0ZXh0LWFsaWduOmNlbnRlcjttYXJnaW46MDttaW4td2lkdGg6NjRweDtsaW5lLWhlaWdodDozNnB4O3BhZGRpbmc6MCAxNnB4O2JvcmRlci1yYWRpdXM6NHB4O292ZXJmbG93OnZpc2libGV9Lm1hdC1idXR0b246Oi1tb3otZm9jdXMtaW5uZXIsLm1hdC1pY29uLWJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lciwubWF0LXN0cm9rZWQtYnV0dG9uOjotbW96LWZvY3VzLWlubmVyLC5tYXQtZmxhdC1idXR0b246Oi1tb3otZm9jdXMtaW5uZXJ7Ym9yZGVyOjB9Lm1hdC1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LWljb24tYnV0dG9uLm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1zdHJva2VkLWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtZmxhdC1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZHtjdXJzb3I6ZGVmYXVsdH0ubWF0LWJ1dHRvbi5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LC5tYXQtYnV0dG9uLmNkay1wcm9ncmFtLWZvY3VzZWQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LWljb24tYnV0dG9uLmNkay1rZXlib2FyZC1mb2N1c2VkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1pY29uLWJ1dHRvbi5jZGstcHJvZ3JhbS1mb2N1c2VkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1zdHJva2VkLWJ1dHRvbi5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LC5tYXQtc3Ryb2tlZC1idXR0b24uY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LC5tYXQtZmxhdC1idXR0b24uY2RrLWtleWJvYXJkLWZvY3VzZWQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LWZsYXQtYnV0dG9uLmNkay1wcm9ncmFtLWZvY3VzZWQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXtvcGFjaXR5Oi4xMn0ubWF0LWJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lciwubWF0LWljb24tYnV0dG9uOjotbW96LWZvY3VzLWlubmVyLC5tYXQtc3Ryb2tlZC1idXR0b246Oi1tb3otZm9jdXMtaW5uZXIsLm1hdC1mbGF0LWJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lcntib3JkZXI6MH0ubWF0LXJhaXNlZC1idXR0b257Ym94LXNpemluZzpib3JkZXItYm94O3Bvc2l0aW9uOnJlbGF0aXZlOy13ZWJraXQtdXNlci1zZWxlY3Q6bm9uZTt1c2VyLXNlbGVjdDpub25lO2N1cnNvcjpwb2ludGVyO291dGxpbmU6bm9uZTtib3JkZXI6bm9uZTstd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6cmdiYSgwLDAsMCwwKTtkaXNwbGF5OmlubGluZS1ibG9jazt3aGl0ZS1zcGFjZTpub3dyYXA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dmVydGljYWwtYWxpZ246YmFzZWxpbmU7dGV4dC1hbGlnbjpjZW50ZXI7bWFyZ2luOjA7bWluLXdpZHRoOjY0cHg7bGluZS1oZWlnaHQ6MzZweDtwYWRkaW5nOjAgMTZweDtib3JkZXItcmFkaXVzOjRweDtvdmVyZmxvdzp2aXNpYmxlO3RyYW5zZm9ybTp0cmFuc2xhdGUzZCgwLCAwLCAwKTt0cmFuc2l0aW9uOmJhY2tncm91bmQgNDAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSksYm94LXNoYWRvdyAyODBtcyBjdWJpYy1iZXppZXIoMC40LCAwLCAwLjIsIDEpfS5tYXQtcmFpc2VkLWJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lcntib3JkZXI6MH0ubWF0LXJhaXNlZC1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZHtjdXJzb3I6ZGVmYXVsdH0ubWF0LXJhaXNlZC1idXR0b24uY2RrLWtleWJvYXJkLWZvY3VzZWQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LXJhaXNlZC1idXR0b24uY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e29wYWNpdHk6LjEyfS5tYXQtcmFpc2VkLWJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lcntib3JkZXI6MH0ubWF0LXJhaXNlZC1idXR0b24uX21hdC1hbmltYXRpb24tbm9vcGFibGV7dHJhbnNpdGlvbjpub25lICFpbXBvcnRhbnQ7YW5pbWF0aW9uOm5vbmUgIWltcG9ydGFudH0ubWF0LXN0cm9rZWQtYnV0dG9ue2JvcmRlcjoxcHggc29saWQgY3VycmVudENvbG9yO3BhZGRpbmc6MCAxNXB4O2xpbmUtaGVpZ2h0OjM0cHh9Lm1hdC1zdHJva2VkLWJ1dHRvbiAubWF0LWJ1dHRvbi1yaXBwbGUubWF0LXJpcHBsZSwubWF0LXN0cm9rZWQtYnV0dG9uIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7dG9wOi0xcHg7bGVmdDotMXB4O3JpZ2h0Oi0xcHg7Ym90dG9tOi0xcHh9Lm1hdC1mYWJ7Ym94LXNpemluZzpib3JkZXItYm94O3Bvc2l0aW9uOnJlbGF0aXZlOy13ZWJraXQtdXNlci1zZWxlY3Q6bm9uZTt1c2VyLXNlbGVjdDpub25lO2N1cnNvcjpwb2ludGVyO291dGxpbmU6bm9uZTtib3JkZXI6bm9uZTstd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6cmdiYSgwLDAsMCwwKTtkaXNwbGF5OmlubGluZS1ibG9jazt3aGl0ZS1zcGFjZTpub3dyYXA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dmVydGljYWwtYWxpZ246YmFzZWxpbmU7dGV4dC1hbGlnbjpjZW50ZXI7bWFyZ2luOjA7bWluLXdpZHRoOjY0cHg7bGluZS1oZWlnaHQ6MzZweDtwYWRkaW5nOjAgMTZweDtib3JkZXItcmFkaXVzOjRweDtvdmVyZmxvdzp2aXNpYmxlO3RyYW5zZm9ybTp0cmFuc2xhdGUzZCgwLCAwLCAwKTt0cmFuc2l0aW9uOmJhY2tncm91bmQgNDAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSksYm94LXNoYWRvdyAyODBtcyBjdWJpYy1iZXppZXIoMC40LCAwLCAwLjIsIDEpO21pbi13aWR0aDowO2JvcmRlci1yYWRpdXM6NTAlO3dpZHRoOjU2cHg7aGVpZ2h0OjU2cHg7cGFkZGluZzowO2ZsZXgtc2hyaW5rOjB9Lm1hdC1mYWI6Oi1tb3otZm9jdXMtaW5uZXJ7Ym9yZGVyOjB9Lm1hdC1mYWIubWF0LWJ1dHRvbi1kaXNhYmxlZHtjdXJzb3I6ZGVmYXVsdH0ubWF0LWZhYi5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LC5tYXQtZmFiLmNkay1wcm9ncmFtLWZvY3VzZWQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXtvcGFjaXR5Oi4xMn0ubWF0LWZhYjo6LW1vei1mb2N1cy1pbm5lcntib3JkZXI6MH0ubWF0LWZhYi5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZXt0cmFuc2l0aW9uOm5vbmUgIWltcG9ydGFudDthbmltYXRpb246bm9uZSAhaW1wb3J0YW50fS5tYXQtZmFiIC5tYXQtYnV0dG9uLXdyYXBwZXJ7cGFkZGluZzoxNnB4IDA7ZGlzcGxheTppbmxpbmUtYmxvY2s7bGluZS1oZWlnaHQ6MjRweH0ubWF0LW1pbmktZmFie2JveC1zaXppbmc6Ym9yZGVyLWJveDtwb3NpdGlvbjpyZWxhdGl2ZTstd2Via2l0LXVzZXItc2VsZWN0Om5vbmU7dXNlci1zZWxlY3Q6bm9uZTtjdXJzb3I6cG9pbnRlcjtvdXRsaW5lOm5vbmU7Ym9yZGVyOm5vbmU7LXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yOnJnYmEoMCwwLDAsMCk7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2hpdGUtc3BhY2U6bm93cmFwO3RleHQtZGVjb3JhdGlvbjpub25lO3ZlcnRpY2FsLWFsaWduOmJhc2VsaW5lO3RleHQtYWxpZ246Y2VudGVyO21hcmdpbjowO21pbi13aWR0aDo2NHB4O2xpbmUtaGVpZ2h0OjM2cHg7cGFkZGluZzowIDE2cHg7Ym9yZGVyLXJhZGl1czo0cHg7b3ZlcmZsb3c6dmlzaWJsZTt0cmFuc2Zvcm06dHJhbnNsYXRlM2QoMCwgMCwgMCk7dHJhbnNpdGlvbjpiYWNrZ3JvdW5kIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpLGJveC1zaGFkb3cgMjgwbXMgY3ViaWMtYmV6aWVyKDAuNCwgMCwgMC4yLCAxKTttaW4td2lkdGg6MDtib3JkZXItcmFkaXVzOjUwJTt3aWR0aDo0MHB4O2hlaWdodDo0MHB4O3BhZGRpbmc6MDtmbGV4LXNocmluazowfS5tYXQtbWluaS1mYWI6Oi1tb3otZm9jdXMtaW5uZXJ7Ym9yZGVyOjB9Lm1hdC1taW5pLWZhYi5tYXQtYnV0dG9uLWRpc2FibGVke2N1cnNvcjpkZWZhdWx0fS5tYXQtbWluaS1mYWIuY2RrLWtleWJvYXJkLWZvY3VzZWQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LW1pbmktZmFiLmNkay1wcm9ncmFtLWZvY3VzZWQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXtvcGFjaXR5Oi4xMn0ubWF0LW1pbmktZmFiOjotbW96LWZvY3VzLWlubmVye2JvcmRlcjowfS5tYXQtbWluaS1mYWIuX21hdC1hbmltYXRpb24tbm9vcGFibGV7dHJhbnNpdGlvbjpub25lICFpbXBvcnRhbnQ7YW5pbWF0aW9uOm5vbmUgIWltcG9ydGFudH0ubWF0LW1pbmktZmFiIC5tYXQtYnV0dG9uLXdyYXBwZXJ7cGFkZGluZzo4cHggMDtkaXNwbGF5OmlubGluZS1ibG9jaztsaW5lLWhlaWdodDoyNHB4fS5tYXQtaWNvbi1idXR0b257cGFkZGluZzowO21pbi13aWR0aDowO3dpZHRoOjQwcHg7aGVpZ2h0OjQwcHg7ZmxleC1zaHJpbms6MDtsaW5lLWhlaWdodDo0MHB4O2JvcmRlci1yYWRpdXM6NTAlfS5tYXQtaWNvbi1idXR0b24gaSwubWF0LWljb24tYnV0dG9uIC5tYXQtaWNvbntsaW5lLWhlaWdodDoyNHB4fS5tYXQtYnV0dG9uLXJpcHBsZS5tYXQtcmlwcGxlLC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7dG9wOjA7bGVmdDowO3JpZ2h0OjA7Ym90dG9tOjA7cG9zaXRpb246YWJzb2x1dGU7cG9pbnRlci1ldmVudHM6bm9uZTtib3JkZXItcmFkaXVzOmluaGVyaXR9Lm1hdC1idXR0b24tcmlwcGxlLm1hdC1yaXBwbGU6bm90KDplbXB0eSl7dHJhbnNmb3JtOnRyYW5zbGF0ZVooMCl9Lm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXtvcGFjaXR5OjA7dHJhbnNpdGlvbjpvcGFjaXR5IDIwMG1zIGN1YmljLWJlemllcigwLjM1LCAwLCAwLjI1LCAxKSxiYWNrZ3JvdW5kLWNvbG9yIDIwMG1zIGN1YmljLWJlemllcigwLjM1LCAwLCAwLjI1LCAxKX0uX21hdC1hbmltYXRpb24tbm9vcGFibGUgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXt0cmFuc2l0aW9uOm5vbmV9Lm1hdC1idXR0b24tcmlwcGxlLXJvdW5ke2JvcmRlci1yYWRpdXM6NTAlO3otaW5kZXg6MX0ubWF0LWJ1dHRvbiAubWF0LWJ1dHRvbi13cmFwcGVyPiosLm1hdC1mbGF0LWJ1dHRvbiAubWF0LWJ1dHRvbi13cmFwcGVyPiosLm1hdC1zdHJva2VkLWJ1dHRvbiAubWF0LWJ1dHRvbi13cmFwcGVyPiosLm1hdC1yYWlzZWQtYnV0dG9uIC5tYXQtYnV0dG9uLXdyYXBwZXI+KiwubWF0LWljb24tYnV0dG9uIC5tYXQtYnV0dG9uLXdyYXBwZXI+KiwubWF0LWZhYiAubWF0LWJ1dHRvbi13cmFwcGVyPiosLm1hdC1taW5pLWZhYiAubWF0LWJ1dHRvbi13cmFwcGVyPip7dmVydGljYWwtYWxpZ246bWlkZGxlfS5tYXQtZm9ybS1maWVsZDpub3QoLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5KSAubWF0LWZvcm0tZmllbGQtcHJlZml4IC5tYXQtaWNvbi1idXR0b24sLm1hdC1mb3JtLWZpZWxkOm5vdCgubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kpIC5tYXQtZm9ybS1maWVsZC1zdWZmaXggLm1hdC1pY29uLWJ1dHRvbntkaXNwbGF5OmlubGluZS1mbGV4O2p1c3RpZnktY29udGVudDpjZW50ZXI7YWxpZ24taXRlbXM6Y2VudGVyO2ZvbnQtc2l6ZTppbmhlcml0O3dpZHRoOjIuNWVtO2hlaWdodDoyLjVlbX0ubWF0LWZsYXQtYnV0dG9uOjpiZWZvcmUsLm1hdC1yYWlzZWQtYnV0dG9uOjpiZWZvcmUsLm1hdC1mYWI6OmJlZm9yZSwubWF0LW1pbmktZmFiOjpiZWZvcmV7bWFyZ2luOmNhbGMoY2FsYyh2YXIoLS1tYXQtZm9jdXMtaW5kaWNhdG9yLWJvcmRlci13aWR0aCwgM3B4KSArIDJweCkgKiAtMSl9Lm1hdC1zdHJva2VkLWJ1dHRvbjo6YmVmb3Jle21hcmdpbjpjYWxjKGNhbGModmFyKC0tbWF0LWZvY3VzLWluZGljYXRvci1ib3JkZXItd2lkdGgsIDNweCkgKyAzcHgpICogLTEpfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1idXR0b24sLmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWZsYXQtYnV0dG9uLC5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1yYWlzZWQtYnV0dG9uLC5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1pY29uLWJ1dHRvbiwuY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtZmFiLC5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1taW5pLWZhYntvdXRsaW5lOnNvbGlkIDFweH0iXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxJdj0oKCk9PntjbGFzcyBuIGV4dGVuZHMgX257Y29uc3RydWN0b3IoZSxpLHIsbyl7c3VwZXIoaSxlLHIpLHRoaXMuX25nWm9uZT1vLHRoaXMuX2hhbHREaXNhYmxlZEV2ZW50cz1zPT57dGhpcy5kaXNhYmxlZCYmKHMucHJldmVudERlZmF1bHQoKSxzLnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpKX19bmdBZnRlclZpZXdJbml0KCl7c3VwZXIubmdBZnRlclZpZXdJbml0KCksdGhpcy5fbmdab25lP3RoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e3RoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsdGhpcy5faGFsdERpc2FibGVkRXZlbnRzKX0pOnRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsdGhpcy5faGFsdERpc2FibGVkRXZlbnRzKX1uZ09uRGVzdHJveSgpe3N1cGVyLm5nT25EZXN0cm95KCksdGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoImNsaWNrIix0aGlzLl9oYWx0RGlzYWJsZWRFdmVudHMpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKEZyKSxNKFJlKSxNKFBpLDgpLE0oX3QsOCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImEiLCJtYXQtYnV0dG9uIiwiIl0sWyJhIiwibWF0LXJhaXNlZC1idXR0b24iLCIiXSxbImEiLCJtYXQtaWNvbi1idXR0b24iLCIiXSxbImEiLCJtYXQtZmFiIiwiIl0sWyJhIiwibWF0LW1pbmktZmFiIiwiIl0sWyJhIiwibWF0LXN0cm9rZWQtYnV0dG9uIiwiIl0sWyJhIiwibWF0LWZsYXQtYnV0dG9uIiwiIl1dLGhvc3RBdHRyczpbMSwibWF0LWZvY3VzLWluZGljYXRvciJdLGhvc3RWYXJzOjcsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiYoemUoInRhYmluZGV4IixpLmRpc2FibGVkPy0xOmkudGFiSW5kZXgpKCJkaXNhYmxlZCIsaS5kaXNhYmxlZHx8bnVsbCkoImFyaWEtZGlzYWJsZWQiLGkuZGlzYWJsZWQudG9TdHJpbmcoKSksZXQoIl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIiwiTm9vcEFuaW1hdGlvbnMiPT09aS5fYW5pbWF0aW9uTW9kZSkoIm1hdC1idXR0b24tZGlzYWJsZWQiLGkuZGlzYWJsZWQpKX0saW5wdXRzOntkaXNhYmxlZDoiZGlzYWJsZWQiLGRpc2FibGVSaXBwbGU6ImRpc2FibGVSaXBwbGUiLGNvbG9yOiJjb2xvciIsdGFiSW5kZXg6InRhYkluZGV4In0sZXhwb3J0QXM6WyJtYXRCdXR0b24iLCJtYXRBbmNob3IiXSxmZWF0dXJlczpbdHRdLGF0dHJzOmx0ZSxuZ0NvbnRlbnRTZWxlY3RvcnM6Y3RlLGRlY2xzOjQsdmFyczo1LGNvbnN0czpbWzEsIm1hdC1idXR0b24td3JhcHBlciJdLFsibWF0UmlwcGxlIiwiIiwxLCJtYXQtYnV0dG9uLXJpcHBsZSIsMywibWF0UmlwcGxlRGlzYWJsZWQiLCJtYXRSaXBwbGVDZW50ZXJlZCIsIm1hdFJpcHBsZVRyaWdnZXIiXSxbMSwibWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoeGkoKSxfKDAsInNwYW4iLDApLFZuKDEpLHYoKSxPKDIsInNwYW4iLDEpKDMsInNwYW4iLDIpKSwyJmUmJihDKDIpLGV0KCJtYXQtYnV0dG9uLXJpcHBsZS1yb3VuZCIsaS5pc1JvdW5kQnV0dG9ufHxpLmlzSWNvbkJ1dHRvbikseSgibWF0UmlwcGxlRGlzYWJsZWQiLGkuX2lzUmlwcGxlRGlzYWJsZWQoKSkoIm1hdFJpcHBsZUNlbnRlcmVkIixpLmlzSWNvbkJ1dHRvbikoIm1hdFJpcHBsZVRyaWdnZXIiLGkuX2dldEhvc3RFbGVtZW50KCkpKX0sZGVwZW5kZW5jaWVzOltZb10sc3R5bGVzOlsiLm1hdC1idXR0b24gLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LWljb24tYnV0dG9uIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7b3BhY2l0eTowfS5tYXQtYnV0dG9uOmhvdmVyOm5vdCgubWF0LWJ1dHRvbi1kaXNhYmxlZCkgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LXN0cm9rZWQtYnV0dG9uOmhvdmVyOm5vdCgubWF0LWJ1dHRvbi1kaXNhYmxlZCkgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXtvcGFjaXR5Oi4wNH1AbWVkaWEoaG92ZXI6IG5vbmUpey5tYXQtYnV0dG9uOmhvdmVyOm5vdCgubWF0LWJ1dHRvbi1kaXNhYmxlZCkgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LXN0cm9rZWQtYnV0dG9uOmhvdmVyOm5vdCgubWF0LWJ1dHRvbi1kaXNhYmxlZCkgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXtvcGFjaXR5OjB9fS5tYXQtYnV0dG9uLC5tYXQtaWNvbi1idXR0b24sLm1hdC1zdHJva2VkLWJ1dHRvbiwubWF0LWZsYXQtYnV0dG9ue2JveC1zaXppbmc6Ym9yZGVyLWJveDtwb3NpdGlvbjpyZWxhdGl2ZTstd2Via2l0LXVzZXItc2VsZWN0Om5vbmU7dXNlci1zZWxlY3Q6bm9uZTtjdXJzb3I6cG9pbnRlcjtvdXRsaW5lOm5vbmU7Ym9yZGVyOm5vbmU7LXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yOnJnYmEoMCwwLDAsMCk7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2hpdGUtc3BhY2U6bm93cmFwO3RleHQtZGVjb3JhdGlvbjpub25lO3ZlcnRpY2FsLWFsaWduOmJhc2VsaW5lO3RleHQtYWxpZ246Y2VudGVyO21hcmdpbjowO21pbi13aWR0aDo2NHB4O2xpbmUtaGVpZ2h0OjM2cHg7cGFkZGluZzowIDE2cHg7Ym9yZGVyLXJhZGl1czo0cHg7b3ZlcmZsb3c6dmlzaWJsZX0ubWF0LWJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lciwubWF0LWljb24tYnV0dG9uOjotbW96LWZvY3VzLWlubmVyLC5tYXQtc3Ryb2tlZC1idXR0b246Oi1tb3otZm9jdXMtaW5uZXIsLm1hdC1mbGF0LWJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lcntib3JkZXI6MH0ubWF0LWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVkLC5tYXQtaWNvbi1idXR0b24ubWF0LWJ1dHRvbi1kaXNhYmxlZCwubWF0LXN0cm9rZWQtYnV0dG9uLm1hdC1idXR0b24tZGlzYWJsZWQsLm1hdC1mbGF0LWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVke2N1cnNvcjpkZWZhdWx0fS5tYXQtYnV0dG9uLmNkay1rZXlib2FyZC1mb2N1c2VkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1idXR0b24uY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LC5tYXQtaWNvbi1idXR0b24uY2RrLWtleWJvYXJkLWZvY3VzZWQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LWljb24tYnV0dG9uLmNkay1wcm9ncmFtLWZvY3VzZWQgLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheSwubWF0LXN0cm9rZWQtYnV0dG9uLmNkay1rZXlib2FyZC1mb2N1c2VkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1zdHJva2VkLWJ1dHRvbi5jZGstcHJvZ3JhbS1mb2N1c2VkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1mbGF0LWJ1dHRvbi5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LC5tYXQtZmxhdC1idXR0b24uY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e29wYWNpdHk6LjEyfS5tYXQtYnV0dG9uOjotbW96LWZvY3VzLWlubmVyLC5tYXQtaWNvbi1idXR0b246Oi1tb3otZm9jdXMtaW5uZXIsLm1hdC1zdHJva2VkLWJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lciwubWF0LWZsYXQtYnV0dG9uOjotbW96LWZvY3VzLWlubmVye2JvcmRlcjowfS5tYXQtcmFpc2VkLWJ1dHRvbntib3gtc2l6aW5nOmJvcmRlci1ib3g7cG9zaXRpb246cmVsYXRpdmU7LXdlYmtpdC11c2VyLXNlbGVjdDpub25lO3VzZXItc2VsZWN0Om5vbmU7Y3Vyc29yOnBvaW50ZXI7b3V0bGluZTpub25lO2JvcmRlcjpub25lOy13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjpyZ2JhKDAsMCwwLDApO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3doaXRlLXNwYWNlOm5vd3JhcDt0ZXh0LWRlY29yYXRpb246bm9uZTt2ZXJ0aWNhbC1hbGlnbjpiYXNlbGluZTt0ZXh0LWFsaWduOmNlbnRlcjttYXJnaW46MDttaW4td2lkdGg6NjRweDtsaW5lLWhlaWdodDozNnB4O3BhZGRpbmc6MCAxNnB4O2JvcmRlci1yYWRpdXM6NHB4O292ZXJmbG93OnZpc2libGU7dHJhbnNmb3JtOnRyYW5zbGF0ZTNkKDAsIDAsIDApO3RyYW5zaXRpb246YmFja2dyb3VuZCA0MDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKSxib3gtc2hhZG93IDI4MG1zIGN1YmljLWJlemllcigwLjQsIDAsIDAuMiwgMSl9Lm1hdC1yYWlzZWQtYnV0dG9uOjotbW96LWZvY3VzLWlubmVye2JvcmRlcjowfS5tYXQtcmFpc2VkLWJ1dHRvbi5tYXQtYnV0dG9uLWRpc2FibGVke2N1cnNvcjpkZWZhdWx0fS5tYXQtcmFpc2VkLWJ1dHRvbi5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LC5tYXQtcmFpc2VkLWJ1dHRvbi5jZGstcHJvZ3JhbS1mb2N1c2VkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXl7b3BhY2l0eTouMTJ9Lm1hdC1yYWlzZWQtYnV0dG9uOjotbW96LWZvY3VzLWlubmVye2JvcmRlcjowfS5tYXQtcmFpc2VkLWJ1dHRvbi5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZXt0cmFuc2l0aW9uOm5vbmUgIWltcG9ydGFudDthbmltYXRpb246bm9uZSAhaW1wb3J0YW50fS5tYXQtc3Ryb2tlZC1idXR0b257Ym9yZGVyOjFweCBzb2xpZCBjdXJyZW50Q29sb3I7cGFkZGluZzowIDE1cHg7bGluZS1oZWlnaHQ6MzRweH0ubWF0LXN0cm9rZWQtYnV0dG9uIC5tYXQtYnV0dG9uLXJpcHBsZS5tYXQtcmlwcGxlLC5tYXQtc3Ryb2tlZC1idXR0b24gLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXt0b3A6LTFweDtsZWZ0Oi0xcHg7cmlnaHQ6LTFweDtib3R0b206LTFweH0ubWF0LWZhYntib3gtc2l6aW5nOmJvcmRlci1ib3g7cG9zaXRpb246cmVsYXRpdmU7LXdlYmtpdC11c2VyLXNlbGVjdDpub25lO3VzZXItc2VsZWN0Om5vbmU7Y3Vyc29yOnBvaW50ZXI7b3V0bGluZTpub25lO2JvcmRlcjpub25lOy13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjpyZ2JhKDAsMCwwLDApO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3doaXRlLXNwYWNlOm5vd3JhcDt0ZXh0LWRlY29yYXRpb246bm9uZTt2ZXJ0aWNhbC1hbGlnbjpiYXNlbGluZTt0ZXh0LWFsaWduOmNlbnRlcjttYXJnaW46MDttaW4td2lkdGg6NjRweDtsaW5lLWhlaWdodDozNnB4O3BhZGRpbmc6MCAxNnB4O2JvcmRlci1yYWRpdXM6NHB4O292ZXJmbG93OnZpc2libGU7dHJhbnNmb3JtOnRyYW5zbGF0ZTNkKDAsIDAsIDApO3RyYW5zaXRpb246YmFja2dyb3VuZCA0MDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKSxib3gtc2hhZG93IDI4MG1zIGN1YmljLWJlemllcigwLjQsIDAsIDAuMiwgMSk7bWluLXdpZHRoOjA7Ym9yZGVyLXJhZGl1czo1MCU7d2lkdGg6NTZweDtoZWlnaHQ6NTZweDtwYWRkaW5nOjA7ZmxleC1zaHJpbms6MH0ubWF0LWZhYjo6LW1vei1mb2N1cy1pbm5lcntib3JkZXI6MH0ubWF0LWZhYi5tYXQtYnV0dG9uLWRpc2FibGVke2N1cnNvcjpkZWZhdWx0fS5tYXQtZmFiLmNkay1rZXlib2FyZC1mb2N1c2VkIC5tYXQtYnV0dG9uLWZvY3VzLW92ZXJsYXksLm1hdC1mYWIuY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e29wYWNpdHk6LjEyfS5tYXQtZmFiOjotbW96LWZvY3VzLWlubmVye2JvcmRlcjowfS5tYXQtZmFiLl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxle3RyYW5zaXRpb246bm9uZSAhaW1wb3J0YW50O2FuaW1hdGlvbjpub25lICFpbXBvcnRhbnR9Lm1hdC1mYWIgLm1hdC1idXR0b24td3JhcHBlcntwYWRkaW5nOjE2cHggMDtkaXNwbGF5OmlubGluZS1ibG9jaztsaW5lLWhlaWdodDoyNHB4fS5tYXQtbWluaS1mYWJ7Ym94LXNpemluZzpib3JkZXItYm94O3Bvc2l0aW9uOnJlbGF0aXZlOy13ZWJraXQtdXNlci1zZWxlY3Q6bm9uZTt1c2VyLXNlbGVjdDpub25lO2N1cnNvcjpwb2ludGVyO291dGxpbmU6bm9uZTtib3JkZXI6bm9uZTstd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6cmdiYSgwLDAsMCwwKTtkaXNwbGF5OmlubGluZS1ibG9jazt3aGl0ZS1zcGFjZTpub3dyYXA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dmVydGljYWwtYWxpZ246YmFzZWxpbmU7dGV4dC1hbGlnbjpjZW50ZXI7bWFyZ2luOjA7bWluLXdpZHRoOjY0cHg7bGluZS1oZWlnaHQ6MzZweDtwYWRkaW5nOjAgMTZweDtib3JkZXItcmFkaXVzOjRweDtvdmVyZmxvdzp2aXNpYmxlO3RyYW5zZm9ybTp0cmFuc2xhdGUzZCgwLCAwLCAwKTt0cmFuc2l0aW9uOmJhY2tncm91bmQgNDAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSksYm94LXNoYWRvdyAyODBtcyBjdWJpYy1iZXppZXIoMC40LCAwLCAwLjIsIDEpO21pbi13aWR0aDowO2JvcmRlci1yYWRpdXM6NTAlO3dpZHRoOjQwcHg7aGVpZ2h0OjQwcHg7cGFkZGluZzowO2ZsZXgtc2hyaW5rOjB9Lm1hdC1taW5pLWZhYjo6LW1vei1mb2N1cy1pbm5lcntib3JkZXI6MH0ubWF0LW1pbmktZmFiLm1hdC1idXR0b24tZGlzYWJsZWR7Y3Vyc29yOmRlZmF1bHR9Lm1hdC1taW5pLWZhYi5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5LC5tYXQtbWluaS1mYWIuY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e29wYWNpdHk6LjEyfS5tYXQtbWluaS1mYWI6Oi1tb3otZm9jdXMtaW5uZXJ7Ym9yZGVyOjB9Lm1hdC1taW5pLWZhYi5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZXt0cmFuc2l0aW9uOm5vbmUgIWltcG9ydGFudDthbmltYXRpb246bm9uZSAhaW1wb3J0YW50fS5tYXQtbWluaS1mYWIgLm1hdC1idXR0b24td3JhcHBlcntwYWRkaW5nOjhweCAwO2Rpc3BsYXk6aW5saW5lLWJsb2NrO2xpbmUtaGVpZ2h0OjI0cHh9Lm1hdC1pY29uLWJ1dHRvbntwYWRkaW5nOjA7bWluLXdpZHRoOjA7d2lkdGg6NDBweDtoZWlnaHQ6NDBweDtmbGV4LXNocmluazowO2xpbmUtaGVpZ2h0OjQwcHg7Ym9yZGVyLXJhZGl1czo1MCV9Lm1hdC1pY29uLWJ1dHRvbiBpLC5tYXQtaWNvbi1idXR0b24gLm1hdC1pY29ue2xpbmUtaGVpZ2h0OjI0cHh9Lm1hdC1idXR0b24tcmlwcGxlLm1hdC1yaXBwbGUsLm1hdC1idXR0b24tZm9jdXMtb3ZlcmxheXt0b3A6MDtsZWZ0OjA7cmlnaHQ6MDtib3R0b206MDtwb3NpdGlvbjphYnNvbHV0ZTtwb2ludGVyLWV2ZW50czpub25lO2JvcmRlci1yYWRpdXM6aW5oZXJpdH0ubWF0LWJ1dHRvbi1yaXBwbGUubWF0LXJpcHBsZTpub3QoOmVtcHR5KXt0cmFuc2Zvcm06dHJhbnNsYXRlWigwKX0ubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e29wYWNpdHk6MDt0cmFuc2l0aW9uOm9wYWNpdHkgMjAwbXMgY3ViaWMtYmV6aWVyKDAuMzUsIDAsIDAuMjUsIDEpLGJhY2tncm91bmQtY29sb3IgMjAwbXMgY3ViaWMtYmV6aWVyKDAuMzUsIDAsIDAuMjUsIDEpfS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LWJ1dHRvbi1mb2N1cy1vdmVybGF5e3RyYW5zaXRpb246bm9uZX0ubWF0LWJ1dHRvbi1yaXBwbGUtcm91bmR7Ym9yZGVyLXJhZGl1czo1MCU7ei1pbmRleDoxfS5tYXQtYnV0dG9uIC5tYXQtYnV0dG9uLXdyYXBwZXI+KiwubWF0LWZsYXQtYnV0dG9uIC5tYXQtYnV0dG9uLXdyYXBwZXI+KiwubWF0LXN0cm9rZWQtYnV0dG9uIC5tYXQtYnV0dG9uLXdyYXBwZXI+KiwubWF0LXJhaXNlZC1idXR0b24gLm1hdC1idXR0b24td3JhcHBlcj4qLC5tYXQtaWNvbi1idXR0b24gLm1hdC1idXR0b24td3JhcHBlcj4qLC5tYXQtZmFiIC5tYXQtYnV0dG9uLXdyYXBwZXI+KiwubWF0LW1pbmktZmFiIC5tYXQtYnV0dG9uLXdyYXBwZXI+Knt2ZXJ0aWNhbC1hbGlnbjptaWRkbGV9Lm1hdC1mb3JtLWZpZWxkOm5vdCgubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kpIC5tYXQtZm9ybS1maWVsZC1wcmVmaXggLm1hdC1pY29uLWJ1dHRvbiwubWF0LWZvcm0tZmllbGQ6bm90KC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSkgLm1hdC1mb3JtLWZpZWxkLXN1ZmZpeCAubWF0LWljb24tYnV0dG9ue2Rpc3BsYXk6aW5saW5lLWZsZXg7anVzdGlmeS1jb250ZW50OmNlbnRlcjthbGlnbi1pdGVtczpjZW50ZXI7Zm9udC1zaXplOmluaGVyaXQ7d2lkdGg6Mi41ZW07aGVpZ2h0OjIuNWVtfS5tYXQtZmxhdC1idXR0b246OmJlZm9yZSwubWF0LXJhaXNlZC1idXR0b246OmJlZm9yZSwubWF0LWZhYjo6YmVmb3JlLC5tYXQtbWluaS1mYWI6OmJlZm9yZXttYXJnaW46Y2FsYyhjYWxjKHZhcigtLW1hdC1mb2N1cy1pbmRpY2F0b3ItYm9yZGVyLXdpZHRoLCAzcHgpICsgMnB4KSAqIC0xKX0ubWF0LXN0cm9rZWQtYnV0dG9uOjpiZWZvcmV7bWFyZ2luOmNhbGMoY2FsYyh2YXIoLS1tYXQtZm9jdXMtaW5kaWNhdG9yLWJvcmRlci13aWR0aCwgM3B4KSArIDNweCkgKiAtMSl9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWJ1dHRvbiwuY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtZmxhdC1idXR0b24sLmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LXJhaXNlZC1idXR0b24sLmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWljb24tYnV0dG9uLC5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1mYWIsLmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LW1pbmktZmFie291dGxpbmU6c29saWQgMXB4fSJdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLFBuPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltfbCxsbixsbl19KSxufSkoKSxQdj1jbGFzc3t9LHYyPWNsYXNzIGV4dGVuZHMgUHZ7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLl9kYXRhPXR9Y29ubmVjdCgpe3JldHVybiBheCh0aGlzLl9kYXRhKT90aGlzLl9kYXRhOlh0KHRoaXMuX2RhdGEpfWRpc2Nvbm5lY3QoKXt9fSx5Mj1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMudmlld0NhY2hlU2l6ZT0yMCx0aGlzLl92aWV3Q2FjaGU9W119YXBwbHlDaGFuZ2VzKHQsZSxpLHIsbyl7dC5mb3JFYWNoT3BlcmF0aW9uKChzLGEsbCk9PntsZXQgYyx1O251bGw9PXMucHJldmlvdXNJbmRleD8oYz10aGlzLl9pbnNlcnRWaWV3KCgpPT5pKHMsYSxsKSxsLGUscihzKSksdT1jPzE6MCk6bnVsbD09bD8odGhpcy5fZGV0YWNoQW5kQ2FjaGVWaWV3KGEsZSksdT0zKTooYz10aGlzLl9tb3ZlVmlldyhhLGwsZSxyKHMpKSx1PTIpLG8mJm8oe2NvbnRleHQ6Yz8uY29udGV4dCxvcGVyYXRpb246dSxyZWNvcmQ6c30pfSl9ZGV0YWNoKCl7Zm9yKGxldCB0IG9mIHRoaXMuX3ZpZXdDYWNoZSl0LmRlc3Ryb3koKTt0aGlzLl92aWV3Q2FjaGU9W119X2luc2VydFZpZXcodCxlLGkscil7bGV0IG89dGhpcy5faW5zZXJ0Vmlld0Zyb21DYWNoZShlLGkpO2lmKG8pcmV0dXJuIHZvaWQoby5jb250ZXh0LiRpbXBsaWNpdD1yKTtsZXQgcz10KCk7cmV0dXJuIGkuY3JlYXRlRW1iZWRkZWRWaWV3KHMudGVtcGxhdGVSZWYscy5jb250ZXh0LHMuaW5kZXgpfV9kZXRhY2hBbmRDYWNoZVZpZXcodCxlKXtsZXQgaT1lLmRldGFjaCh0KTt0aGlzLl9tYXliZUNhY2hlVmlldyhpLGUpfV9tb3ZlVmlldyh0LGUsaSxyKXtsZXQgbz1pLmdldCh0KTtyZXR1cm4gaS5tb3ZlKG8sZSksby5jb250ZXh0LiRpbXBsaWNpdD1yLG99X21heWJlQ2FjaGVWaWV3KHQsZSl7aWYodGhpcy5fdmlld0NhY2hlLmxlbmd0aDx0aGlzLnZpZXdDYWNoZVNpemUpdGhpcy5fdmlld0NhY2hlLnB1c2godCk7ZWxzZXtsZXQgaT1lLmluZGV4T2YodCk7LTE9PT1pP3QuZGVzdHJveSgpOmUucmVtb3ZlKGkpfX1faW5zZXJ0Vmlld0Zyb21DYWNoZSh0LGUpe2xldCBpPXRoaXMuX3ZpZXdDYWNoZS5wb3AoKTtyZXR1cm4gaSYmZS5pbnNlcnQoaSx0KSxpfHxudWxsfX0sQWg9Y2xhc3N7Y29uc3RydWN0b3IodD0hMSxlLGk9ITAscil7dGhpcy5fbXVsdGlwbGU9dCx0aGlzLl9lbWl0Q2hhbmdlcz1pLHRoaXMuY29tcGFyZVdpdGg9cix0aGlzLl9zZWxlY3Rpb249bmV3IFNldCx0aGlzLl9kZXNlbGVjdGVkVG9FbWl0PVtdLHRoaXMuX3NlbGVjdGVkVG9FbWl0PVtdLHRoaXMuY2hhbmdlZD1uZXcga2UsZSYmZS5sZW5ndGgmJih0P2UuZm9yRWFjaChvPT50aGlzLl9tYXJrU2VsZWN0ZWQobykpOnRoaXMuX21hcmtTZWxlY3RlZChlWzBdKSx0aGlzLl9zZWxlY3RlZFRvRW1pdC5sZW5ndGg9MCl9Z2V0IHNlbGVjdGVkKCl7cmV0dXJuIHRoaXMuX3NlbGVjdGVkfHwodGhpcy5fc2VsZWN0ZWQ9QXJyYXkuZnJvbSh0aGlzLl9zZWxlY3Rpb24udmFsdWVzKCkpKSx0aGlzLl9zZWxlY3RlZH1zZWxlY3QoLi4udCl7dGhpcy5fdmVyaWZ5VmFsdWVBc3NpZ25tZW50KHQpLHQuZm9yRWFjaChpPT50aGlzLl9tYXJrU2VsZWN0ZWQoaSkpO2xldCBlPXRoaXMuX2hhc1F1ZXVlZENoYW5nZXMoKTtyZXR1cm4gdGhpcy5fZW1pdENoYW5nZUV2ZW50KCksZX1kZXNlbGVjdCguLi50KXt0aGlzLl92ZXJpZnlWYWx1ZUFzc2lnbm1lbnQodCksdC5mb3JFYWNoKGk9PnRoaXMuX3VubWFya1NlbGVjdGVkKGkpKTtsZXQgZT10aGlzLl9oYXNRdWV1ZWRDaGFuZ2VzKCk7cmV0dXJuIHRoaXMuX2VtaXRDaGFuZ2VFdmVudCgpLGV9c2V0U2VsZWN0aW9uKC4uLnQpe3RoaXMuX3ZlcmlmeVZhbHVlQXNzaWdubWVudCh0KTtsZXQgZT10aGlzLnNlbGVjdGVkLGk9bmV3IFNldCh0KTt0LmZvckVhY2gobz0+dGhpcy5fbWFya1NlbGVjdGVkKG8pKSxlLmZpbHRlcihvPT4haS5oYXMobykpLmZvckVhY2gobz0+dGhpcy5fdW5tYXJrU2VsZWN0ZWQobykpO2xldCByPXRoaXMuX2hhc1F1ZXVlZENoYW5nZXMoKTtyZXR1cm4gdGhpcy5fZW1pdENoYW5nZUV2ZW50KCkscn10b2dnbGUodCl7cmV0dXJuIHRoaXMuaXNTZWxlY3RlZCh0KT90aGlzLmRlc2VsZWN0KHQpOnRoaXMuc2VsZWN0KHQpfWNsZWFyKHQ9ITApe3RoaXMuX3VubWFya0FsbCgpO2xldCBlPXRoaXMuX2hhc1F1ZXVlZENoYW5nZXMoKTtyZXR1cm4gdCYmdGhpcy5fZW1pdENoYW5nZUV2ZW50KCksZX1pc1NlbGVjdGVkKHQpe2lmKHRoaXMuY29tcGFyZVdpdGgpe2ZvcihsZXQgZSBvZiB0aGlzLl9zZWxlY3Rpb24paWYodGhpcy5jb21wYXJlV2l0aChlLHQpKXJldHVybiEwO3JldHVybiExfXJldHVybiB0aGlzLl9zZWxlY3Rpb24uaGFzKHQpfWlzRW1wdHkoKXtyZXR1cm4gMD09PXRoaXMuX3NlbGVjdGlvbi5zaXplfWhhc1ZhbHVlKCl7cmV0dXJuIXRoaXMuaXNFbXB0eSgpfXNvcnQodCl7dGhpcy5fbXVsdGlwbGUmJnRoaXMuc2VsZWN0ZWQmJnRoaXMuX3NlbGVjdGVkLnNvcnQodCl9aXNNdWx0aXBsZVNlbGVjdGlvbigpe3JldHVybiB0aGlzLl9tdWx0aXBsZX1fZW1pdENoYW5nZUV2ZW50KCl7dGhpcy5fc2VsZWN0ZWQ9bnVsbCwodGhpcy5fc2VsZWN0ZWRUb0VtaXQubGVuZ3RofHx0aGlzLl9kZXNlbGVjdGVkVG9FbWl0Lmxlbmd0aCkmJih0aGlzLmNoYW5nZWQubmV4dCh7c291cmNlOnRoaXMsYWRkZWQ6dGhpcy5fc2VsZWN0ZWRUb0VtaXQscmVtb3ZlZDp0aGlzLl9kZXNlbGVjdGVkVG9FbWl0fSksdGhpcy5fZGVzZWxlY3RlZFRvRW1pdD1bXSx0aGlzLl9zZWxlY3RlZFRvRW1pdD1bXSl9X21hcmtTZWxlY3RlZCh0KXt0aGlzLmlzU2VsZWN0ZWQodCl8fCh0aGlzLl9tdWx0aXBsZXx8dGhpcy5fdW5tYXJrQWxsKCksdGhpcy5pc1NlbGVjdGVkKHQpfHx0aGlzLl9zZWxlY3Rpb24uYWRkKHQpLHRoaXMuX2VtaXRDaGFuZ2VzJiZ0aGlzLl9zZWxlY3RlZFRvRW1pdC5wdXNoKHQpKX1fdW5tYXJrU2VsZWN0ZWQodCl7dGhpcy5pc1NlbGVjdGVkKHQpJiYodGhpcy5fc2VsZWN0aW9uLmRlbGV0ZSh0KSx0aGlzLl9lbWl0Q2hhbmdlcyYmdGhpcy5fZGVzZWxlY3RlZFRvRW1pdC5wdXNoKHQpKX1fdW5tYXJrQWxsKCl7dGhpcy5pc0VtcHR5KCl8fHRoaXMuX3NlbGVjdGlvbi5mb3JFYWNoKHQ9PnRoaXMuX3VubWFya1NlbGVjdGVkKHQpKX1fdmVyaWZ5VmFsdWVBc3NpZ25tZW50KHQpe31faGFzUXVldWVkQ2hhbmdlcygpe3JldHVybiEoIXRoaXMuX2Rlc2VsZWN0ZWRUb0VtaXQubGVuZ3RoJiYhdGhpcy5fc2VsZWN0ZWRUb0VtaXQubGVuZ3RoKX19LFNIPW5ldyBwZSgiX1ZpZXdSZXBlYXRlciIpLGFPZT1bImNvbnRlbnRXcmFwcGVyIl0sbE9lPVsiKiJdLGh0ZT1uZXcgcGUoIlZJUlRVQUxfU0NST0xMX1NUUkFURUdZIik7ZnVuY3Rpb24gY09lKG4pe3JldHVybiBuLl9zY3JvbGxTdHJhdGVneX12YXIgYjI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuX2l0ZW1TaXplPTIwLHRoaXMuX21pbkJ1ZmZlclB4PTEwMCx0aGlzLl9tYXhCdWZmZXJQeD0yMDAsdGhpcy5fc2Nyb2xsU3RyYXRlZ3k9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXt0aGlzLl9zY3JvbGxlZEluZGV4Q2hhbmdlPW5ldyBrZSx0aGlzLnNjcm9sbGVkSW5kZXhDaGFuZ2U9dGhpcy5fc2Nyb2xsZWRJbmRleENoYW5nZS5waXBlKHlpKCkpLHRoaXMuX3ZpZXdwb3J0PW51bGwsdGhpcy5faXRlbVNpemU9dCx0aGlzLl9taW5CdWZmZXJQeD1lLHRoaXMuX21heEJ1ZmZlclB4PWl9YXR0YWNoKHQpe3RoaXMuX3ZpZXdwb3J0PXQsdGhpcy5fdXBkYXRlVG90YWxDb250ZW50U2l6ZSgpLHRoaXMuX3VwZGF0ZVJlbmRlcmVkUmFuZ2UoKX1kZXRhY2goKXt0aGlzLl9zY3JvbGxlZEluZGV4Q2hhbmdlLmNvbXBsZXRlKCksdGhpcy5fdmlld3BvcnQ9bnVsbH11cGRhdGVJdGVtQW5kQnVmZmVyU2l6ZSh0LGUsaSl7dGhpcy5faXRlbVNpemU9dCx0aGlzLl9taW5CdWZmZXJQeD1lLHRoaXMuX21heEJ1ZmZlclB4PWksdGhpcy5fdXBkYXRlVG90YWxDb250ZW50U2l6ZSgpLHRoaXMuX3VwZGF0ZVJlbmRlcmVkUmFuZ2UoKX1vbkNvbnRlbnRTY3JvbGxlZCgpe3RoaXMuX3VwZGF0ZVJlbmRlcmVkUmFuZ2UoKX1vbkRhdGFMZW5ndGhDaGFuZ2VkKCl7dGhpcy5fdXBkYXRlVG90YWxDb250ZW50U2l6ZSgpLHRoaXMuX3VwZGF0ZVJlbmRlcmVkUmFuZ2UoKX1vbkNvbnRlbnRSZW5kZXJlZCgpe31vblJlbmRlcmVkT2Zmc2V0Q2hhbmdlZCgpe31zY3JvbGxUb0luZGV4KHQsZSl7dGhpcy5fdmlld3BvcnQmJnRoaXMuX3ZpZXdwb3J0LnNjcm9sbFRvT2Zmc2V0KHQqdGhpcy5faXRlbVNpemUsZSl9X3VwZGF0ZVRvdGFsQ29udGVudFNpemUoKXshdGhpcy5fdmlld3BvcnR8fHRoaXMuX3ZpZXdwb3J0LnNldFRvdGFsQ29udGVudFNpemUodGhpcy5fdmlld3BvcnQuZ2V0RGF0YUxlbmd0aCgpKnRoaXMuX2l0ZW1TaXplKX1fdXBkYXRlUmVuZGVyZWRSYW5nZSgpe2lmKCF0aGlzLl92aWV3cG9ydClyZXR1cm47bGV0IHQ9dGhpcy5fdmlld3BvcnQuZ2V0UmVuZGVyZWRSYW5nZSgpLGU9e3N0YXJ0OnQuc3RhcnQsZW5kOnQuZW5kfSxpPXRoaXMuX3ZpZXdwb3J0LmdldFZpZXdwb3J0U2l6ZSgpLHI9dGhpcy5fdmlld3BvcnQuZ2V0RGF0YUxlbmd0aCgpLG89dGhpcy5fdmlld3BvcnQubWVhc3VyZVNjcm9sbE9mZnNldCgpLHM9dGhpcy5faXRlbVNpemU+MD9vL3RoaXMuX2l0ZW1TaXplOjA7aWYoZS5lbmQ+cil7bGV0IGw9TWF0aC5jZWlsKGkvdGhpcy5faXRlbVNpemUpLGM9TWF0aC5tYXgoMCxNYXRoLm1pbihzLHItbCkpO3MhPWMmJihzPWMsbz1jKnRoaXMuX2l0ZW1TaXplLGUuc3RhcnQ9TWF0aC5mbG9vcihzKSksZS5lbmQ9TWF0aC5tYXgoMCxNYXRoLm1pbihyLGUuc3RhcnQrbCkpfWxldCBhPW8tZS5zdGFydCp0aGlzLl9pdGVtU2l6ZTtpZihhPHRoaXMuX21pbkJ1ZmZlclB4JiYwIT1lLnN0YXJ0KXtsZXQgbD1NYXRoLmNlaWwoKHRoaXMuX21heEJ1ZmZlclB4LWEpL3RoaXMuX2l0ZW1TaXplKTtlLnN0YXJ0PU1hdGgubWF4KDAsZS5zdGFydC1sKSxlLmVuZD1NYXRoLm1pbihyLE1hdGguY2VpbChzKyhpK3RoaXMuX21pbkJ1ZmZlclB4KS90aGlzLl9pdGVtU2l6ZSkpfWVsc2V7bGV0IGw9ZS5lbmQqdGhpcy5faXRlbVNpemUtKG8raSk7aWYobDx0aGlzLl9taW5CdWZmZXJQeCYmZS5lbmQhPXIpe2xldCBjPU1hdGguY2VpbCgodGhpcy5fbWF4QnVmZmVyUHgtbCkvdGhpcy5faXRlbVNpemUpO2M+MCYmKGUuZW5kPU1hdGgubWluKHIsZS5lbmQrYyksZS5zdGFydD1NYXRoLm1heCgwLE1hdGguZmxvb3Iocy10aGlzLl9taW5CdWZmZXJQeC90aGlzLl9pdGVtU2l6ZSkpKX19dGhpcy5fdmlld3BvcnQuc2V0UmVuZGVyZWRSYW5nZShlKSx0aGlzLl92aWV3cG9ydC5zZXRSZW5kZXJlZENvbnRlbnRPZmZzZXQodGhpcy5faXRlbVNpemUqZS5zdGFydCksdGhpcy5fc2Nyb2xsZWRJbmRleENoYW5nZS5uZXh0KE1hdGguZmxvb3IocykpfX0odGhpcy5pdGVtU2l6ZSx0aGlzLm1pbkJ1ZmZlclB4LHRoaXMubWF4QnVmZmVyUHgpfWdldCBpdGVtU2l6ZSgpe3JldHVybiB0aGlzLl9pdGVtU2l6ZX1zZXQgaXRlbVNpemUoZSl7dGhpcy5faXRlbVNpemU9QmkoZSl9Z2V0IG1pbkJ1ZmZlclB4KCl7cmV0dXJuIHRoaXMuX21pbkJ1ZmZlclB4fXNldCBtaW5CdWZmZXJQeChlKXt0aGlzLl9taW5CdWZmZXJQeD1CaShlKX1nZXQgbWF4QnVmZmVyUHgoKXtyZXR1cm4gdGhpcy5fbWF4QnVmZmVyUHh9c2V0IG1heEJ1ZmZlclB4KGUpe3RoaXMuX21heEJ1ZmZlclB4PUJpKGUpfW5nT25DaGFuZ2VzKCl7dGhpcy5fc2Nyb2xsU3RyYXRlZ3kudXBkYXRlSXRlbUFuZEJ1ZmZlclNpemUodGhpcy5pdGVtU2l6ZSx0aGlzLm1pbkJ1ZmZlclB4LHRoaXMubWF4QnVmZmVyUHgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siY2RrLXZpcnR1YWwtc2Nyb2xsLXZpZXdwb3J0IiwiaXRlbVNpemUiLCIiXV0saW5wdXRzOntpdGVtU2l6ZToiaXRlbVNpemUiLG1pbkJ1ZmZlclB4OiJtaW5CdWZmZXJQeCIsbWF4QnVmZmVyUHg6Im1heEJ1ZmZlclB4In0sZmVhdHVyZXM6WyR0KFt7cHJvdmlkZTpodGUsdXNlRmFjdG9yeTpjT2UsZGVwczpbSm4oKCk9Pm4pXX1dKSxGdF19KSxufSkoKSwkbT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLl9uZ1pvbmU9ZSx0aGlzLl9wbGF0Zm9ybT1pLHRoaXMuX3Njcm9sbGVkPW5ldyBrZSx0aGlzLl9nbG9iYWxTdWJzY3JpcHRpb249bnVsbCx0aGlzLl9zY3JvbGxlZENvdW50PTAsdGhpcy5zY3JvbGxDb250YWluZXJzPW5ldyBNYXAsdGhpcy5fZG9jdW1lbnQ9cn1yZWdpc3RlcihlKXt0aGlzLnNjcm9sbENvbnRhaW5lcnMuaGFzKGUpfHx0aGlzLnNjcm9sbENvbnRhaW5lcnMuc2V0KGUsZS5lbGVtZW50U2Nyb2xsZWQoKS5zdWJzY3JpYmUoKCk9PnRoaXMuX3Njcm9sbGVkLm5leHQoZSkpKX1kZXJlZ2lzdGVyKGUpe2xldCBpPXRoaXMuc2Nyb2xsQ29udGFpbmVycy5nZXQoZSk7aSYmKGkudW5zdWJzY3JpYmUoKSx0aGlzLnNjcm9sbENvbnRhaW5lcnMuZGVsZXRlKGUpKX1zY3JvbGxlZChlPTIwKXtyZXR1cm4gdGhpcy5fcGxhdGZvcm0uaXNCcm93c2VyP25ldyB1bihpPT57dGhpcy5fZ2xvYmFsU3Vic2NyaXB0aW9ufHx0aGlzLl9hZGRHbG9iYWxMaXN0ZW5lcigpO2xldCByPWU+MD90aGlzLl9zY3JvbGxlZC5waXBlKGJ1KGUpKS5zdWJzY3JpYmUoaSk6dGhpcy5fc2Nyb2xsZWQuc3Vic2NyaWJlKGkpO3JldHVybiB0aGlzLl9zY3JvbGxlZENvdW50KyssKCk9PntyLnVuc3Vic2NyaWJlKCksdGhpcy5fc2Nyb2xsZWRDb3VudC0tLHRoaXMuX3Njcm9sbGVkQ291bnR8fHRoaXMuX3JlbW92ZUdsb2JhbExpc3RlbmVyKCl9fSk6WHQoKX1uZ09uRGVzdHJveSgpe3RoaXMuX3JlbW92ZUdsb2JhbExpc3RlbmVyKCksdGhpcy5zY3JvbGxDb250YWluZXJzLmZvckVhY2goKGUsaSk9PnRoaXMuZGVyZWdpc3RlcihpKSksdGhpcy5fc2Nyb2xsZWQuY29tcGxldGUoKX1hbmNlc3RvclNjcm9sbGVkKGUsaSl7bGV0IHI9dGhpcy5nZXRBbmNlc3RvclNjcm9sbENvbnRhaW5lcnMoZSk7cmV0dXJuIHRoaXMuc2Nyb2xsZWQoaSkucGlwZShZZShvPT4hb3x8ci5pbmRleE9mKG8pPi0xKSl9Z2V0QW5jZXN0b3JTY3JvbGxDb250YWluZXJzKGUpe2xldCBpPVtdO3JldHVybiB0aGlzLnNjcm9sbENvbnRhaW5lcnMuZm9yRWFjaCgocixvKT0+e3RoaXMuX3Njcm9sbGFibGVDb250YWluc0VsZW1lbnQobyxlKSYmaS5wdXNoKG8pfSksaX1fZ2V0V2luZG93KCl7cmV0dXJuIHRoaXMuX2RvY3VtZW50LmRlZmF1bHRWaWV3fHx3aW5kb3d9X3Njcm9sbGFibGVDb250YWluc0VsZW1lbnQoZSxpKXtsZXQgcj1MYShpKSxvPWUuZ2V0RWxlbWVudFJlZigpLm5hdGl2ZUVsZW1lbnQ7ZG97aWYocj09bylyZXR1cm4hMH13aGlsZShyPXIucGFyZW50RWxlbWVudCk7cmV0dXJuITF9X2FkZEdsb2JhbExpc3RlbmVyKCl7dGhpcy5fZ2xvYmFsU3Vic2NyaXB0aW9uPXRoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+X2kodGhpcy5fZ2V0V2luZG93KCkuZG9jdW1lbnQsInNjcm9sbCIpLnN1YnNjcmliZSgoKT0+dGhpcy5fc2Nyb2xsZWQubmV4dCgpKSl9X3JlbW92ZUdsb2JhbExpc3RlbmVyKCl7dGhpcy5fZ2xvYmFsU3Vic2NyaXB0aW9uJiYodGhpcy5fZ2xvYmFsU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5fZ2xvYmFsU3Vic2NyaXB0aW9uPW51bGwpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKF90KSxqKG9pKSxqKEh0LDgpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKSxJaD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8pe3RoaXMuZWxlbWVudFJlZj1lLHRoaXMuc2Nyb2xsRGlzcGF0Y2hlcj1pLHRoaXMubmdab25lPXIsdGhpcy5kaXI9byx0aGlzLl9kZXN0cm95ZWQ9bmV3IGtlLHRoaXMuX2VsZW1lbnRTY3JvbGxlZD1uZXcgdW4ocz0+dGhpcy5uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9Pl9pKHRoaXMuZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LCJzY3JvbGwiKS5waXBlKHN0KHRoaXMuX2Rlc3Ryb3llZCkpLnN1YnNjcmliZShzKSkpfW5nT25Jbml0KCl7dGhpcy5zY3JvbGxEaXNwYXRjaGVyLnJlZ2lzdGVyKHRoaXMpfW5nT25EZXN0cm95KCl7dGhpcy5zY3JvbGxEaXNwYXRjaGVyLmRlcmVnaXN0ZXIodGhpcyksdGhpcy5fZGVzdHJveWVkLm5leHQoKSx0aGlzLl9kZXN0cm95ZWQuY29tcGxldGUoKX1lbGVtZW50U2Nyb2xsZWQoKXtyZXR1cm4gdGhpcy5fZWxlbWVudFNjcm9sbGVkfWdldEVsZW1lbnRSZWYoKXtyZXR1cm4gdGhpcy5lbGVtZW50UmVmfXNjcm9sbFRvKGUpe2xldCBpPXRoaXMuZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LHI9dGhpcy5kaXImJiJydGwiPT10aGlzLmRpci52YWx1ZTtudWxsPT1lLmxlZnQmJihlLmxlZnQ9cj9lLmVuZDplLnN0YXJ0KSxudWxsPT1lLnJpZ2h0JiYoZS5yaWdodD1yP2Uuc3RhcnQ6ZS5lbmQpLG51bGwhPWUuYm90dG9tJiYoZS50b3A9aS5zY3JvbGxIZWlnaHQtaS5jbGllbnRIZWlnaHQtZS5ib3R0b20pLHImJjAhPWJ2KCk/KG51bGwhPWUubGVmdCYmKGUucmlnaHQ9aS5zY3JvbGxXaWR0aC1pLmNsaWVudFdpZHRoLWUubGVmdCksMj09YnYoKT9lLmxlZnQ9ZS5yaWdodDoxPT1idigpJiYoZS5sZWZ0PWUucmlnaHQ/LWUucmlnaHQ6ZS5yaWdodCkpOm51bGwhPWUucmlnaHQmJihlLmxlZnQ9aS5zY3JvbGxXaWR0aC1pLmNsaWVudFdpZHRoLWUucmlnaHQpLHRoaXMuX2FwcGx5U2Nyb2xsVG9PcHRpb25zKGUpfV9hcHBseVNjcm9sbFRvT3B0aW9ucyhlKXtsZXQgaT10aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudDtzMigpP2kuc2Nyb2xsVG8oZSk6KG51bGwhPWUudG9wJiYoaS5zY3JvbGxUb3A9ZS50b3ApLG51bGwhPWUubGVmdCYmKGkuc2Nyb2xsTGVmdD1lLmxlZnQpKX1tZWFzdXJlU2Nyb2xsT2Zmc2V0KGUpe2xldCBpPSJsZWZ0IixyPSJyaWdodCIsbz10aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudDtpZigidG9wIj09ZSlyZXR1cm4gby5zY3JvbGxUb3A7aWYoImJvdHRvbSI9PWUpcmV0dXJuIG8uc2Nyb2xsSGVpZ2h0LW8uY2xpZW50SGVpZ2h0LW8uc2Nyb2xsVG9wO2xldCBzPXRoaXMuZGlyJiYicnRsIj09dGhpcy5kaXIudmFsdWU7cmV0dXJuInN0YXJ0Ij09ZT9lPXM/cjppOiJlbmQiPT1lJiYoZT1zP2k6cikscyYmMj09YnYoKT9lPT1pP28uc2Nyb2xsV2lkdGgtby5jbGllbnRXaWR0aC1vLnNjcm9sbExlZnQ6by5zY3JvbGxMZWZ0OnMmJjE9PWJ2KCk/ZT09aT9vLnNjcm9sbExlZnQrby5zY3JvbGxXaWR0aC1vLmNsaWVudFdpZHRoOi1vLnNjcm9sbExlZnQ6ZT09aT9vLnNjcm9sbExlZnQ6by5zY3JvbGxXaWR0aC1vLmNsaWVudFdpZHRoLW8uc2Nyb2xsTGVmdH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShSZSksTSgkbSksTShfdCksTSgkaSw4KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsImNkay1zY3JvbGxhYmxlIiwiIl0sWyIiLCJjZGtTY3JvbGxhYmxlIiwiIl1dfSksbn0pKCksVmE9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5fcGxhdGZvcm09ZSx0aGlzLl9jaGFuZ2U9bmV3IGtlLHRoaXMuX2NoYW5nZUxpc3RlbmVyPW89Pnt0aGlzLl9jaGFuZ2UubmV4dChvKX0sdGhpcy5fZG9jdW1lbnQ9cixpLnJ1bk91dHNpZGVBbmd1bGFyKCgpPT57aWYoZS5pc0Jyb3dzZXIpe2xldCBvPXRoaXMuX2dldFdpbmRvdygpO28uYWRkRXZlbnRMaXN0ZW5lcigicmVzaXplIix0aGlzLl9jaGFuZ2VMaXN0ZW5lciksby5hZGRFdmVudExpc3RlbmVyKCJvcmllbnRhdGlvbmNoYW5nZSIsdGhpcy5fY2hhbmdlTGlzdGVuZXIpfXRoaXMuY2hhbmdlKCkuc3Vic2NyaWJlKCgpPT50aGlzLl92aWV3cG9ydFNpemU9bnVsbCl9KX1uZ09uRGVzdHJveSgpe2lmKHRoaXMuX3BsYXRmb3JtLmlzQnJvd3Nlcil7bGV0IGU9dGhpcy5fZ2V0V2luZG93KCk7ZS5yZW1vdmVFdmVudExpc3RlbmVyKCJyZXNpemUiLHRoaXMuX2NoYW5nZUxpc3RlbmVyKSxlLnJlbW92ZUV2ZW50TGlzdGVuZXIoIm9yaWVudGF0aW9uY2hhbmdlIix0aGlzLl9jaGFuZ2VMaXN0ZW5lcil9dGhpcy5fY2hhbmdlLmNvbXBsZXRlKCl9Z2V0Vmlld3BvcnRTaXplKCl7dGhpcy5fdmlld3BvcnRTaXplfHx0aGlzLl91cGRhdGVWaWV3cG9ydFNpemUoKTtsZXQgZT17d2lkdGg6dGhpcy5fdmlld3BvcnRTaXplLndpZHRoLGhlaWdodDp0aGlzLl92aWV3cG9ydFNpemUuaGVpZ2h0fTtyZXR1cm4gdGhpcy5fcGxhdGZvcm0uaXNCcm93c2VyfHwodGhpcy5fdmlld3BvcnRTaXplPW51bGwpLGV9Z2V0Vmlld3BvcnRSZWN0KCl7bGV0IGU9dGhpcy5nZXRWaWV3cG9ydFNjcm9sbFBvc2l0aW9uKCkse3dpZHRoOmksaGVpZ2h0OnJ9PXRoaXMuZ2V0Vmlld3BvcnRTaXplKCk7cmV0dXJue3RvcDplLnRvcCxsZWZ0OmUubGVmdCxib3R0b206ZS50b3ArcixyaWdodDplLmxlZnQraSxoZWlnaHQ6cix3aWR0aDppfX1nZXRWaWV3cG9ydFNjcm9sbFBvc2l0aW9uKCl7aWYoIXRoaXMuX3BsYXRmb3JtLmlzQnJvd3NlcilyZXR1cm57dG9wOjAsbGVmdDowfTtsZXQgZT10aGlzLl9kb2N1bWVudCxpPXRoaXMuX2dldFdpbmRvdygpLHI9ZS5kb2N1bWVudEVsZW1lbnQsbz1yLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO3JldHVybnt0b3A6LW8udG9wfHxlLmJvZHkuc2Nyb2xsVG9wfHxpLnNjcm9sbFl8fHIuc2Nyb2xsVG9wfHwwLGxlZnQ6LW8ubGVmdHx8ZS5ib2R5LnNjcm9sbExlZnR8fGkuc2Nyb2xsWHx8ci5zY3JvbGxMZWZ0fHwwfX1jaGFuZ2UoZT0yMCl7cmV0dXJuIGU+MD90aGlzLl9jaGFuZ2UucGlwZShidShlKSk6dGhpcy5fY2hhbmdlfV9nZXRXaW5kb3coKXtyZXR1cm4gdGhpcy5fZG9jdW1lbnQuZGVmYXVsdFZpZXd8fHdpbmRvd31fdXBkYXRlVmlld3BvcnRTaXplKCl7bGV0IGU9dGhpcy5fZ2V0V2luZG93KCk7dGhpcy5fdmlld3BvcnRTaXplPXRoaXMuX3BsYXRmb3JtLmlzQnJvd3Nlcj97d2lkdGg6ZS5pbm5lcldpZHRoLGhlaWdodDplLmlubmVySGVpZ2h0fTp7d2lkdGg6MCxoZWlnaHQ6MH19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGoob2kpLGooX3QpLGooSHQsOCkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpLGR0ZT1uZXcgcGUoIlZJUlRVQUxfU0NST0xMQUJMRSIpLHBPZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgSWh7Y29uc3RydWN0b3IoZSxpLHIsbyl7c3VwZXIoZSxpLHIsbyl9bWVhc3VyZVZpZXdwb3J0U2l6ZShlKXtsZXQgaT10aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudDtyZXR1cm4iaG9yaXpvbnRhbCI9PT1lP2kuY2xpZW50V2lkdGg6aS5jbGllbnRIZWlnaHR9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0oJG0pLE0oX3QpLE0oJGksOCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLGZlYXR1cmVzOlt0dF19KSxufSkoKSxmT2U9dHlwZW9mIHJlcXVlc3RBbmltYXRpb25GcmFtZTwidSI/X046ZjAsZWc9KCgpPT57Y2xhc3MgbiBleHRlbmRzIHBPZXtjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsLGMpe3N1cGVyKGUsYSxyLHMpLHRoaXMuZWxlbWVudFJlZj1lLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmPWksdGhpcy5fc2Nyb2xsU3RyYXRlZ3k9byx0aGlzLnNjcm9sbGFibGU9Yyx0aGlzLl9wbGF0Zm9ybT1qbyhvaSksdGhpcy5fZGV0YWNoZWRTdWJqZWN0PW5ldyBrZSx0aGlzLl9yZW5kZXJlZFJhbmdlU3ViamVjdD1uZXcga2UsdGhpcy5fb3JpZW50YXRpb249InZlcnRpY2FsIix0aGlzLl9hcHBlbmRPbmx5PSExLHRoaXMuc2Nyb2xsZWRJbmRleENoYW5nZT1uZXcgdW4odT0+dGhpcy5fc2Nyb2xsU3RyYXRlZ3kuc2Nyb2xsZWRJbmRleENoYW5nZS5zdWJzY3JpYmUoZD0+UHJvbWlzZS5yZXNvbHZlKCkudGhlbigoKT0+dGhpcy5uZ1pvbmUucnVuKCgpPT51Lm5leHQoZCkpKSkpLHRoaXMucmVuZGVyZWRSYW5nZVN0cmVhbT10aGlzLl9yZW5kZXJlZFJhbmdlU3ViamVjdCx0aGlzLl90b3RhbENvbnRlbnRTaXplPTAsdGhpcy5fdG90YWxDb250ZW50V2lkdGg9IiIsdGhpcy5fdG90YWxDb250ZW50SGVpZ2h0PSIiLHRoaXMuX3JlbmRlcmVkUmFuZ2U9e3N0YXJ0OjAsZW5kOjB9LHRoaXMuX2RhdGFMZW5ndGg9MCx0aGlzLl92aWV3cG9ydFNpemU9MCx0aGlzLl9yZW5kZXJlZENvbnRlbnRPZmZzZXQ9MCx0aGlzLl9yZW5kZXJlZENvbnRlbnRPZmZzZXROZWVkc1Jld3JpdGU9ITEsdGhpcy5faXNDaGFuZ2VEZXRlY3Rpb25QZW5kaW5nPSExLHRoaXMuX3J1bkFmdGVyQ2hhbmdlRGV0ZWN0aW9uPVtdLHRoaXMuX3ZpZXdwb3J0Q2hhbmdlcz1Tbi5FTVBUWSx0aGlzLl92aWV3cG9ydENoYW5nZXM9bC5jaGFuZ2UoKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLmNoZWNrVmlld3BvcnRTaXplKCl9KSx0aGlzLnNjcm9sbGFibGV8fCh0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5jbGFzc0xpc3QuYWRkKCJjZGstdmlydHVhbC1zY3JvbGxhYmxlIiksdGhpcy5zY3JvbGxhYmxlPXRoaXMpfWdldCBvcmllbnRhdGlvbigpe3JldHVybiB0aGlzLl9vcmllbnRhdGlvbn1zZXQgb3JpZW50YXRpb24oZSl7dGhpcy5fb3JpZW50YXRpb24hPT1lJiYodGhpcy5fb3JpZW50YXRpb249ZSx0aGlzLl9jYWxjdWxhdGVTcGFjZXJTaXplKCkpfWdldCBhcHBlbmRPbmx5KCl7cmV0dXJuIHRoaXMuX2FwcGVuZE9ubHl9c2V0IGFwcGVuZE9ubHkoZSl7dGhpcy5fYXBwZW5kT25seT1SdChlKX1uZ09uSW5pdCgpeyF0aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXJ8fCh0aGlzLnNjcm9sbGFibGU9PT10aGlzJiZzdXBlci5uZ09uSW5pdCgpLHRoaXMubmdab25lLnJ1bk91dHNpZGVBbmd1bGFyKCgpPT5Qcm9taXNlLnJlc29sdmUoKS50aGVuKCgpPT57dGhpcy5fbWVhc3VyZVZpZXdwb3J0U2l6ZSgpLHRoaXMuX3Njcm9sbFN0cmF0ZWd5LmF0dGFjaCh0aGlzKSx0aGlzLnNjcm9sbGFibGUuZWxlbWVudFNjcm9sbGVkKCkucGlwZSh6bihudWxsKSxidSgwLGZPZSkpLnN1YnNjcmliZSgoKT0+dGhpcy5fc2Nyb2xsU3RyYXRlZ3kub25Db250ZW50U2Nyb2xsZWQoKSksdGhpcy5fbWFya0NoYW5nZURldGVjdGlvbk5lZWRlZCgpfSkpKX1uZ09uRGVzdHJveSgpe3RoaXMuZGV0YWNoKCksdGhpcy5fc2Nyb2xsU3RyYXRlZ3kuZGV0YWNoKCksdGhpcy5fcmVuZGVyZWRSYW5nZVN1YmplY3QuY29tcGxldGUoKSx0aGlzLl9kZXRhY2hlZFN1YmplY3QuY29tcGxldGUoKSx0aGlzLl92aWV3cG9ydENoYW5nZXMudW5zdWJzY3JpYmUoKSxzdXBlci5uZ09uRGVzdHJveSgpfWF0dGFjaChlKXt0aGlzLm5nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e3RoaXMuX2Zvck9mPWUsdGhpcy5fZm9yT2YuZGF0YVN0cmVhbS5waXBlKHN0KHRoaXMuX2RldGFjaGVkU3ViamVjdCkpLnN1YnNjcmliZShpPT57bGV0IHI9aS5sZW5ndGg7ciE9PXRoaXMuX2RhdGFMZW5ndGgmJih0aGlzLl9kYXRhTGVuZ3RoPXIsdGhpcy5fc2Nyb2xsU3RyYXRlZ3kub25EYXRhTGVuZ3RoQ2hhbmdlZCgpKSx0aGlzLl9kb0NoYW5nZURldGVjdGlvbigpfSl9KX1kZXRhY2goKXt0aGlzLl9mb3JPZj1udWxsLHRoaXMuX2RldGFjaGVkU3ViamVjdC5uZXh0KCl9Z2V0RGF0YUxlbmd0aCgpe3JldHVybiB0aGlzLl9kYXRhTGVuZ3RofWdldFZpZXdwb3J0U2l6ZSgpe3JldHVybiB0aGlzLl92aWV3cG9ydFNpemV9Z2V0UmVuZGVyZWRSYW5nZSgpe3JldHVybiB0aGlzLl9yZW5kZXJlZFJhbmdlfW1lYXN1cmVCb3VuZGluZ0NsaWVudFJlY3RXaXRoU2Nyb2xsT2Zmc2V0KGUpe3JldHVybiB0aGlzLmdldEVsZW1lbnRSZWYoKS5uYXRpdmVFbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpW2VdfXNldFRvdGFsQ29udGVudFNpemUoZSl7dGhpcy5fdG90YWxDb250ZW50U2l6ZSE9PWUmJih0aGlzLl90b3RhbENvbnRlbnRTaXplPWUsdGhpcy5fY2FsY3VsYXRlU3BhY2VyU2l6ZSgpLHRoaXMuX21hcmtDaGFuZ2VEZXRlY3Rpb25OZWVkZWQoKSl9c2V0UmVuZGVyZWRSYW5nZShlKXsoZnVuY3Rpb24obix0KXtyZXR1cm4gbi5zdGFydD09dC5zdGFydCYmbi5lbmQ9PXQuZW5kfSkodGhpcy5fcmVuZGVyZWRSYW5nZSxlKXx8KHRoaXMuYXBwZW5kT25seSYmKGU9e3N0YXJ0OjAsZW5kOk1hdGgubWF4KHRoaXMuX3JlbmRlcmVkUmFuZ2UuZW5kLGUuZW5kKX0pLHRoaXMuX3JlbmRlcmVkUmFuZ2VTdWJqZWN0Lm5leHQodGhpcy5fcmVuZGVyZWRSYW5nZT1lKSx0aGlzLl9tYXJrQ2hhbmdlRGV0ZWN0aW9uTmVlZGVkKCgpPT50aGlzLl9zY3JvbGxTdHJhdGVneS5vbkNvbnRlbnRSZW5kZXJlZCgpKSl9Z2V0T2Zmc2V0VG9SZW5kZXJlZENvbnRlbnRTdGFydCgpe3JldHVybiB0aGlzLl9yZW5kZXJlZENvbnRlbnRPZmZzZXROZWVkc1Jld3JpdGU/bnVsbDp0aGlzLl9yZW5kZXJlZENvbnRlbnRPZmZzZXR9c2V0UmVuZGVyZWRDb250ZW50T2Zmc2V0KGUsaT0idG8tc3RhcnQiKXtlPXRoaXMuYXBwZW5kT25seSYmInRvLXN0YXJ0Ij09PWk/MDplO2xldCBvPSJob3Jpem9udGFsIj09dGhpcy5vcmllbnRhdGlvbixzPW8/IlgiOiJZIixsPWB0cmFuc2xhdGUke3N9KCR7TnVtYmVyKChvJiZ0aGlzLmRpciYmInJ0bCI9PXRoaXMuZGlyLnZhbHVlPy0xOjEpKmUpfXB4KWA7dGhpcy5fcmVuZGVyZWRDb250ZW50T2Zmc2V0PWUsInRvLWVuZCI9PT1pJiYobCs9YCB0cmFuc2xhdGUke3N9KC0xMDAlKWAsdGhpcy5fcmVuZGVyZWRDb250ZW50T2Zmc2V0TmVlZHNSZXdyaXRlPSEwKSx0aGlzLl9yZW5kZXJlZENvbnRlbnRUcmFuc2Zvcm0hPWwmJih0aGlzLl9yZW5kZXJlZENvbnRlbnRUcmFuc2Zvcm09bCx0aGlzLl9tYXJrQ2hhbmdlRGV0ZWN0aW9uTmVlZGVkKCgpPT57dGhpcy5fcmVuZGVyZWRDb250ZW50T2Zmc2V0TmVlZHNSZXdyaXRlPyh0aGlzLl9yZW5kZXJlZENvbnRlbnRPZmZzZXQtPXRoaXMubWVhc3VyZVJlbmRlcmVkQ29udGVudFNpemUoKSx0aGlzLl9yZW5kZXJlZENvbnRlbnRPZmZzZXROZWVkc1Jld3JpdGU9ITEsdGhpcy5zZXRSZW5kZXJlZENvbnRlbnRPZmZzZXQodGhpcy5fcmVuZGVyZWRDb250ZW50T2Zmc2V0KSk6dGhpcy5fc2Nyb2xsU3RyYXRlZ3kub25SZW5kZXJlZE9mZnNldENoYW5nZWQoKX0pKX1zY3JvbGxUb09mZnNldChlLGk9ImF1dG8iKXtsZXQgcj17YmVoYXZpb3I6aX07Imhvcml6b250YWwiPT09dGhpcy5vcmllbnRhdGlvbj9yLnN0YXJ0PWU6ci50b3A9ZSx0aGlzLnNjcm9sbGFibGUuc2Nyb2xsVG8ocil9c2Nyb2xsVG9JbmRleChlLGk9ImF1dG8iKXt0aGlzLl9zY3JvbGxTdHJhdGVneS5zY3JvbGxUb0luZGV4KGUsaSl9bWVhc3VyZVNjcm9sbE9mZnNldChlKXtsZXQgaTtyZXR1cm4gaT10aGlzLnNjcm9sbGFibGU9PXRoaXM/cj0+c3VwZXIubWVhc3VyZVNjcm9sbE9mZnNldChyKTpyPT50aGlzLnNjcm9sbGFibGUubWVhc3VyZVNjcm9sbE9mZnNldChyKSxNYXRoLm1heCgwLGkoZT8/KCJob3Jpem9udGFsIj09PXRoaXMub3JpZW50YXRpb24/InN0YXJ0IjoidG9wIikpLXRoaXMubWVhc3VyZVZpZXdwb3J0T2Zmc2V0KCkpfW1lYXN1cmVWaWV3cG9ydE9mZnNldChlKXtsZXQgaSxyPSJsZWZ0IixvPSJyaWdodCIscz0icnRsIj09dGhpcy5kaXI/LnZhbHVlO2k9InN0YXJ0Ij09ZT9zP286cjoiZW5kIj09ZT9zP3I6bzplfHwoImhvcml6b250YWwiPT09dGhpcy5vcmllbnRhdGlvbj8ibGVmdCI6InRvcCIpO2xldCBhPXRoaXMuc2Nyb2xsYWJsZS5tZWFzdXJlQm91bmRpbmdDbGllbnRSZWN0V2l0aFNjcm9sbE9mZnNldChpKTtyZXR1cm4gdGhpcy5lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KClbaV0tYX1tZWFzdXJlUmVuZGVyZWRDb250ZW50U2l6ZSgpe2xldCBlPXRoaXMuX2NvbnRlbnRXcmFwcGVyLm5hdGl2ZUVsZW1lbnQ7cmV0dXJuImhvcml6b250YWwiPT09dGhpcy5vcmllbnRhdGlvbj9lLm9mZnNldFdpZHRoOmUub2Zmc2V0SGVpZ2h0fW1lYXN1cmVSYW5nZVNpemUoZSl7cmV0dXJuIHRoaXMuX2Zvck9mP3RoaXMuX2Zvck9mLm1lYXN1cmVSYW5nZVNpemUoZSx0aGlzLm9yaWVudGF0aW9uKTowfWNoZWNrVmlld3BvcnRTaXplKCl7dGhpcy5fbWVhc3VyZVZpZXdwb3J0U2l6ZSgpLHRoaXMuX3Njcm9sbFN0cmF0ZWd5Lm9uRGF0YUxlbmd0aENoYW5nZWQoKX1fbWVhc3VyZVZpZXdwb3J0U2l6ZSgpe3RoaXMuX3ZpZXdwb3J0U2l6ZT10aGlzLnNjcm9sbGFibGUubWVhc3VyZVZpZXdwb3J0U2l6ZSh0aGlzLm9yaWVudGF0aW9uKX1fbWFya0NoYW5nZURldGVjdGlvbk5lZWRlZChlKXtlJiZ0aGlzLl9ydW5BZnRlckNoYW5nZURldGVjdGlvbi5wdXNoKGUpLHRoaXMuX2lzQ2hhbmdlRGV0ZWN0aW9uUGVuZGluZ3x8KHRoaXMuX2lzQ2hhbmdlRGV0ZWN0aW9uUGVuZGluZz0hMCx0aGlzLm5nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+UHJvbWlzZS5yZXNvbHZlKCkudGhlbigoKT0+e3RoaXMuX2RvQ2hhbmdlRGV0ZWN0aW9uKCl9KSkpfV9kb0NoYW5nZURldGVjdGlvbigpe3RoaXMuX2lzQ2hhbmdlRGV0ZWN0aW9uUGVuZGluZz0hMSx0aGlzLl9jb250ZW50V3JhcHBlci5uYXRpdmVFbGVtZW50LnN0eWxlLnRyYW5zZm9ybT10aGlzLl9yZW5kZXJlZENvbnRlbnRUcmFuc2Zvcm0sdGhpcy5uZ1pvbmUucnVuKCgpPT50aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSk7bGV0IGU9dGhpcy5fcnVuQWZ0ZXJDaGFuZ2VEZXRlY3Rpb247dGhpcy5fcnVuQWZ0ZXJDaGFuZ2VEZXRlY3Rpb249W107Zm9yKGxldCBpIG9mIGUpaSgpfV9jYWxjdWxhdGVTcGFjZXJTaXplKCl7dGhpcy5fdG90YWxDb250ZW50SGVpZ2h0PSJob3Jpem9udGFsIj09PXRoaXMub3JpZW50YXRpb24/IiI6YCR7dGhpcy5fdG90YWxDb250ZW50U2l6ZX1weGAsdGhpcy5fdG90YWxDb250ZW50V2lkdGg9Imhvcml6b250YWwiPT09dGhpcy5vcmllbnRhdGlvbj9gJHt0aGlzLl90b3RhbENvbnRlbnRTaXplfXB4YDoiIn19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShSZSksTShubiksTShfdCksTShodGUsOCksTSgkaSw4KSxNKCRtKSxNKFZhKSxNKGR0ZSw4KSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siY2RrLXZpcnR1YWwtc2Nyb2xsLXZpZXdwb3J0Il1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmb3QoYU9lLDcpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkuX2NvbnRlbnRXcmFwcGVyPXIuZmlyc3QpfX0saG9zdEF0dHJzOlsxLCJjZGstdmlydHVhbC1zY3JvbGwtdmlld3BvcnQiXSxob3N0VmFyczo0LGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmZXQoImNkay12aXJ0dWFsLXNjcm9sbC1vcmllbnRhdGlvbi1ob3Jpem9udGFsIiwiaG9yaXpvbnRhbCI9PT1pLm9yaWVudGF0aW9uKSgiY2RrLXZpcnR1YWwtc2Nyb2xsLW9yaWVudGF0aW9uLXZlcnRpY2FsIiwiaG9yaXpvbnRhbCIhPT1pLm9yaWVudGF0aW9uKX0saW5wdXRzOntvcmllbnRhdGlvbjoib3JpZW50YXRpb24iLGFwcGVuZE9ubHk6ImFwcGVuZE9ubHkifSxvdXRwdXRzOntzY3JvbGxlZEluZGV4Q2hhbmdlOiJzY3JvbGxlZEluZGV4Q2hhbmdlIn0sZmVhdHVyZXM6WyR0KFt7cHJvdmlkZTpJaCx1c2VGYWN0b3J5Oih0LGUpPT50fHxlLGRlcHM6W1tuZXcgbnMsbmV3IGowKGR0ZSldLG5dfV0pLHR0XSxuZ0NvbnRlbnRTZWxlY3RvcnM6bE9lLGRlY2xzOjQsdmFyczo0LGNvbnN0czpbWzEsImNkay12aXJ0dWFsLXNjcm9sbC1jb250ZW50LXdyYXBwZXIiXSxbImNvbnRlbnRXcmFwcGVyIiwiIl0sWzEsImNkay12aXJ0dWFsLXNjcm9sbC1zcGFjZXIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJih4aSgpLF8oMCwiZGl2IiwwLDEpLFZuKDIpLHYoKSxPKDMsImRpdiIsMikpLDImZSYmKEMoMyksUHQoIndpZHRoIixpLl90b3RhbENvbnRlbnRXaWR0aCkoImhlaWdodCIsaS5fdG90YWxDb250ZW50SGVpZ2h0KSl9LHN0eWxlczpbImNkay12aXJ0dWFsLXNjcm9sbC12aWV3cG9ydHtkaXNwbGF5OmJsb2NrO3Bvc2l0aW9uOnJlbGF0aXZlO3RyYW5zZm9ybTp0cmFuc2xhdGVaKDApfS5jZGstdmlydHVhbC1zY3JvbGxhYmxle292ZXJmbG93OmF1dG87d2lsbC1jaGFuZ2U6c2Nyb2xsLXBvc2l0aW9uO2NvbnRhaW46c3RyaWN0Oy13ZWJraXQtb3ZlcmZsb3ctc2Nyb2xsaW5nOnRvdWNofS5jZGstdmlydHVhbC1zY3JvbGwtY29udGVudC13cmFwcGVye3Bvc2l0aW9uOmFic29sdXRlO3RvcDowO2xlZnQ6MDtjb250YWluOmNvbnRlbnR9W2Rpcj1ydGxdIC5jZGstdmlydHVhbC1zY3JvbGwtY29udGVudC13cmFwcGVye3JpZ2h0OjA7bGVmdDphdXRvfS5jZGstdmlydHVhbC1zY3JvbGwtb3JpZW50YXRpb24taG9yaXpvbnRhbCAuY2RrLXZpcnR1YWwtc2Nyb2xsLWNvbnRlbnQtd3JhcHBlcnttaW4taGVpZ2h0OjEwMCV9LmNkay12aXJ0dWFsLXNjcm9sbC1vcmllbnRhdGlvbi1ob3Jpem9udGFsIC5jZGstdmlydHVhbC1zY3JvbGwtY29udGVudC13cmFwcGVyPmRsOm5vdChbY2RrVmlydHVhbEZvcl0pLC5jZGstdmlydHVhbC1zY3JvbGwtb3JpZW50YXRpb24taG9yaXpvbnRhbCAuY2RrLXZpcnR1YWwtc2Nyb2xsLWNvbnRlbnQtd3JhcHBlcj5vbDpub3QoW2Nka1ZpcnR1YWxGb3JdKSwuY2RrLXZpcnR1YWwtc2Nyb2xsLW9yaWVudGF0aW9uLWhvcml6b250YWwgLmNkay12aXJ0dWFsLXNjcm9sbC1jb250ZW50LXdyYXBwZXI+dGFibGU6bm90KFtjZGtWaXJ0dWFsRm9yXSksLmNkay12aXJ0dWFsLXNjcm9sbC1vcmllbnRhdGlvbi1ob3Jpem9udGFsIC5jZGstdmlydHVhbC1zY3JvbGwtY29udGVudC13cmFwcGVyPnVsOm5vdChbY2RrVmlydHVhbEZvcl0pe3BhZGRpbmctbGVmdDowO3BhZGRpbmctcmlnaHQ6MDttYXJnaW4tbGVmdDowO21hcmdpbi1yaWdodDowO2JvcmRlci1sZWZ0LXdpZHRoOjA7Ym9yZGVyLXJpZ2h0LXdpZHRoOjA7b3V0bGluZTpub25lfS5jZGstdmlydHVhbC1zY3JvbGwtb3JpZW50YXRpb24tdmVydGljYWwgLmNkay12aXJ0dWFsLXNjcm9sbC1jb250ZW50LXdyYXBwZXJ7bWluLXdpZHRoOjEwMCV9LmNkay12aXJ0dWFsLXNjcm9sbC1vcmllbnRhdGlvbi12ZXJ0aWNhbCAuY2RrLXZpcnR1YWwtc2Nyb2xsLWNvbnRlbnQtd3JhcHBlcj5kbDpub3QoW2Nka1ZpcnR1YWxGb3JdKSwuY2RrLXZpcnR1YWwtc2Nyb2xsLW9yaWVudGF0aW9uLXZlcnRpY2FsIC5jZGstdmlydHVhbC1zY3JvbGwtY29udGVudC13cmFwcGVyPm9sOm5vdChbY2RrVmlydHVhbEZvcl0pLC5jZGstdmlydHVhbC1zY3JvbGwtb3JpZW50YXRpb24tdmVydGljYWwgLmNkay12aXJ0dWFsLXNjcm9sbC1jb250ZW50LXdyYXBwZXI+dGFibGU6bm90KFtjZGtWaXJ0dWFsRm9yXSksLmNkay12aXJ0dWFsLXNjcm9sbC1vcmllbnRhdGlvbi12ZXJ0aWNhbCAuY2RrLXZpcnR1YWwtc2Nyb2xsLWNvbnRlbnQtd3JhcHBlcj51bDpub3QoW2Nka1ZpcnR1YWxGb3JdKXtwYWRkaW5nLXRvcDowO3BhZGRpbmctYm90dG9tOjA7bWFyZ2luLXRvcDowO21hcmdpbi1ib3R0b206MDtib3JkZXItdG9wLXdpZHRoOjA7Ym9yZGVyLWJvdHRvbS13aWR0aDowO291dGxpbmU6bm9uZX0uY2RrLXZpcnR1YWwtc2Nyb2xsLXNwYWNlcntoZWlnaHQ6MXB4O3RyYW5zZm9ybS1vcmlnaW46MCAwO2ZsZXg6MCAwIGF1dG99W2Rpcj1ydGxdIC5jZGstdmlydHVhbC1zY3JvbGwtc3BhY2Vye3RyYW5zZm9ybS1vcmlnaW46MTAwJSAwfSJdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIHB0ZShuLHQsZSl7aWYoIWUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KXJldHVybiAwO2xldCByPWUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7cmV0dXJuImhvcml6b250YWwiPT09bj8ic3RhcnQiPT09dD9yLmxlZnQ6ci5yaWdodDoic3RhcnQiPT09dD9yLnRvcDpyLmJvdHRvbX12YXIgeDI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscixvLHMsYSl7dGhpcy5fdmlld0NvbnRhaW5lclJlZj1lLHRoaXMuX3RlbXBsYXRlPWksdGhpcy5fZGlmZmVycz1yLHRoaXMuX3ZpZXdSZXBlYXRlcj1vLHRoaXMuX3ZpZXdwb3J0PXMsdGhpcy52aWV3Q2hhbmdlPW5ldyBrZSx0aGlzLl9kYXRhU291cmNlQ2hhbmdlcz1uZXcga2UsdGhpcy5kYXRhU3RyZWFtPXRoaXMuX2RhdGFTb3VyY2VDaGFuZ2VzLnBpcGUoem4obnVsbCkseTAoKSx1aSgoW2wsY10pPT50aGlzLl9jaGFuZ2VEYXRhU291cmNlKGwsYykpLE1hKDEpKSx0aGlzLl9kaWZmZXI9bnVsbCx0aGlzLl9uZWVkc1VwZGF0ZT0hMSx0aGlzLl9kZXN0cm95ZWQ9bmV3IGtlLHRoaXMuZGF0YVN0cmVhbS5zdWJzY3JpYmUobD0+e3RoaXMuX2RhdGE9bCx0aGlzLl9vblJlbmRlcmVkRGF0YUNoYW5nZSgpfSksdGhpcy5fdmlld3BvcnQucmVuZGVyZWRSYW5nZVN0cmVhbS5waXBlKHN0KHRoaXMuX2Rlc3Ryb3llZCkpLnN1YnNjcmliZShsPT57dGhpcy5fcmVuZGVyZWRSYW5nZT1sLHRoaXMudmlld0NoYW5nZS5vYnNlcnZlcnMubGVuZ3RoJiZhLnJ1bigoKT0+dGhpcy52aWV3Q2hhbmdlLm5leHQodGhpcy5fcmVuZGVyZWRSYW5nZSkpLHRoaXMuX29uUmVuZGVyZWREYXRhQ2hhbmdlKCl9KSx0aGlzLl92aWV3cG9ydC5hdHRhY2godGhpcyl9Z2V0IGNka1ZpcnR1YWxGb3JPZigpe3JldHVybiB0aGlzLl9jZGtWaXJ0dWFsRm9yT2Z9c2V0IGNka1ZpcnR1YWxGb3JPZihlKXt0aGlzLl9jZGtWaXJ0dWFsRm9yT2Y9ZSxmdW5jdGlvbihuKXtyZXR1cm4gbiYmImZ1bmN0aW9uIj09dHlwZW9mIG4uY29ubmVjdCYmIShuIGluc3RhbmNlb2YgaXgpfShlKT90aGlzLl9kYXRhU291cmNlQ2hhbmdlcy5uZXh0KGUpOnRoaXMuX2RhdGFTb3VyY2VDaGFuZ2VzLm5leHQobmV3IHYyKGF4KGUpP2U6QXJyYXkuZnJvbShlfHxbXSkpKX1nZXQgY2RrVmlydHVhbEZvclRyYWNrQnkoKXtyZXR1cm4gdGhpcy5fY2RrVmlydHVhbEZvclRyYWNrQnl9c2V0IGNka1ZpcnR1YWxGb3JUcmFja0J5KGUpe3RoaXMuX25lZWRzVXBkYXRlPSEwLHRoaXMuX2Nka1ZpcnR1YWxGb3JUcmFja0J5PWU/KGkscik9PmUoaSsodGhpcy5fcmVuZGVyZWRSYW5nZT90aGlzLl9yZW5kZXJlZFJhbmdlLnN0YXJ0OjApLHIpOnZvaWQgMH1zZXQgY2RrVmlydHVhbEZvclRlbXBsYXRlKGUpe2UmJih0aGlzLl9uZWVkc1VwZGF0ZT0hMCx0aGlzLl90ZW1wbGF0ZT1lKX1nZXQgY2RrVmlydHVhbEZvclRlbXBsYXRlQ2FjaGVTaXplKCl7cmV0dXJuIHRoaXMuX3ZpZXdSZXBlYXRlci52aWV3Q2FjaGVTaXplfXNldCBjZGtWaXJ0dWFsRm9yVGVtcGxhdGVDYWNoZVNpemUoZSl7dGhpcy5fdmlld1JlcGVhdGVyLnZpZXdDYWNoZVNpemU9QmkoZSl9bWVhc3VyZVJhbmdlU2l6ZShlLGkpe2lmKGUuc3RhcnQ+PWUuZW5kKXJldHVybiAwO2xldCBzLGEscj1lLnN0YXJ0LXRoaXMuX3JlbmRlcmVkUmFuZ2Uuc3RhcnQsbz1lLmVuZC1lLnN0YXJ0O2ZvcihsZXQgbD0wO2w8bztsKyspe2xldCBjPXRoaXMuX3ZpZXdDb250YWluZXJSZWYuZ2V0KGwrcik7aWYoYyYmYy5yb290Tm9kZXMubGVuZ3RoKXtzPWE9Yy5yb290Tm9kZXNbMF07YnJlYWt9fWZvcihsZXQgbD1vLTE7bD4tMTtsLS0pe2xldCBjPXRoaXMuX3ZpZXdDb250YWluZXJSZWYuZ2V0KGwrcik7aWYoYyYmYy5yb290Tm9kZXMubGVuZ3RoKXthPWMucm9vdE5vZGVzW2Mucm9vdE5vZGVzLmxlbmd0aC0xXTticmVha319cmV0dXJuIHMmJmE/cHRlKGksImVuZCIsYSktcHRlKGksInN0YXJ0IixzKTowfW5nRG9DaGVjaygpe2lmKHRoaXMuX2RpZmZlciYmdGhpcy5fbmVlZHNVcGRhdGUpe2xldCBlPXRoaXMuX2RpZmZlci5kaWZmKHRoaXMuX3JlbmRlcmVkSXRlbXMpO2U/dGhpcy5fYXBwbHlDaGFuZ2VzKGUpOnRoaXMuX3VwZGF0ZUNvbnRleHQoKSx0aGlzLl9uZWVkc1VwZGF0ZT0hMX19bmdPbkRlc3Ryb3koKXt0aGlzLl92aWV3cG9ydC5kZXRhY2goKSx0aGlzLl9kYXRhU291cmNlQ2hhbmdlcy5uZXh0KHZvaWQgMCksdGhpcy5fZGF0YVNvdXJjZUNoYW5nZXMuY29tcGxldGUoKSx0aGlzLnZpZXdDaGFuZ2UuY29tcGxldGUoKSx0aGlzLl9kZXN0cm95ZWQubmV4dCgpLHRoaXMuX2Rlc3Ryb3llZC5jb21wbGV0ZSgpLHRoaXMuX3ZpZXdSZXBlYXRlci5kZXRhY2goKX1fb25SZW5kZXJlZERhdGFDaGFuZ2UoKXshdGhpcy5fcmVuZGVyZWRSYW5nZXx8KHRoaXMuX3JlbmRlcmVkSXRlbXM9dGhpcy5fZGF0YS5zbGljZSh0aGlzLl9yZW5kZXJlZFJhbmdlLnN0YXJ0LHRoaXMuX3JlbmRlcmVkUmFuZ2UuZW5kKSx0aGlzLl9kaWZmZXJ8fCh0aGlzLl9kaWZmZXI9dGhpcy5fZGlmZmVycy5maW5kKHRoaXMuX3JlbmRlcmVkSXRlbXMpLmNyZWF0ZSgoZSxpKT0+dGhpcy5jZGtWaXJ0dWFsRm9yVHJhY2tCeT90aGlzLmNka1ZpcnR1YWxGb3JUcmFja0J5KGUsaSk6aSkpLHRoaXMuX25lZWRzVXBkYXRlPSEwKX1fY2hhbmdlRGF0YVNvdXJjZShlLGkpe3JldHVybiBlJiZlLmRpc2Nvbm5lY3QodGhpcyksdGhpcy5fbmVlZHNVcGRhdGU9ITAsaT9pLmNvbm5lY3QodGhpcyk6WHQoKX1fdXBkYXRlQ29udGV4dCgpe2xldCBlPXRoaXMuX2RhdGEubGVuZ3RoLGk9dGhpcy5fdmlld0NvbnRhaW5lclJlZi5sZW5ndGg7Zm9yKDtpLS07KXtsZXQgcj10aGlzLl92aWV3Q29udGFpbmVyUmVmLmdldChpKTtyLmNvbnRleHQuaW5kZXg9dGhpcy5fcmVuZGVyZWRSYW5nZS5zdGFydCtpLHIuY29udGV4dC5jb3VudD1lLHRoaXMuX3VwZGF0ZUNvbXB1dGVkQ29udGV4dFByb3BlcnRpZXMoci5jb250ZXh0KSxyLmRldGVjdENoYW5nZXMoKX19X2FwcGx5Q2hhbmdlcyhlKXt0aGlzLl92aWV3UmVwZWF0ZXIuYXBwbHlDaGFuZ2VzKGUsdGhpcy5fdmlld0NvbnRhaW5lclJlZiwobyxzLGEpPT50aGlzLl9nZXRFbWJlZGRlZFZpZXdBcmdzKG8sYSksbz0+by5pdGVtKSxlLmZvckVhY2hJZGVudGl0eUNoYW5nZShvPT57dGhpcy5fdmlld0NvbnRhaW5lclJlZi5nZXQoby5jdXJyZW50SW5kZXgpLmNvbnRleHQuJGltcGxpY2l0PW8uaXRlbX0pO2xldCBpPXRoaXMuX2RhdGEubGVuZ3RoLHI9dGhpcy5fdmlld0NvbnRhaW5lclJlZi5sZW5ndGg7Zm9yKDtyLS07KXtsZXQgbz10aGlzLl92aWV3Q29udGFpbmVyUmVmLmdldChyKTtvLmNvbnRleHQuaW5kZXg9dGhpcy5fcmVuZGVyZWRSYW5nZS5zdGFydCtyLG8uY29udGV4dC5jb3VudD1pLHRoaXMuX3VwZGF0ZUNvbXB1dGVkQ29udGV4dFByb3BlcnRpZXMoby5jb250ZXh0KX19X3VwZGF0ZUNvbXB1dGVkQ29udGV4dFByb3BlcnRpZXMoZSl7ZS5maXJzdD0wPT09ZS5pbmRleCxlLmxhc3Q9ZS5pbmRleD09PWUuY291bnQtMSxlLmV2ZW49ZS5pbmRleCUyPT0wLGUub2RkPSFlLmV2ZW59X2dldEVtYmVkZGVkVmlld0FyZ3MoZSxpKXtyZXR1cm57dGVtcGxhdGVSZWY6dGhpcy5fdGVtcGxhdGUsY29udGV4dDp7JGltcGxpY2l0OmUuaXRlbSxjZGtWaXJ0dWFsRm9yT2Y6dGhpcy5fY2RrVmlydHVhbEZvck9mLGluZGV4Oi0xLGNvdW50Oi0xLGZpcnN0OiExLGxhc3Q6ITEsb2RkOiExLGV2ZW46ITF9LGluZGV4Oml9fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKE9pKSxNKFZpKSxNKGtjKSxNKFNIKSxNKGVnLDQpLE0oX3QpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwiY2RrVmlydHVhbEZvciIsIiIsImNka1ZpcnR1YWxGb3JPZiIsIiJdXSxpbnB1dHM6e2Nka1ZpcnR1YWxGb3JPZjoiY2RrVmlydHVhbEZvck9mIixjZGtWaXJ0dWFsRm9yVHJhY2tCeToiY2RrVmlydHVhbEZvclRyYWNrQnkiLGNka1ZpcnR1YWxGb3JUZW1wbGF0ZToiY2RrVmlydHVhbEZvclRlbXBsYXRlIixjZGtWaXJ0dWFsRm9yVGVtcGxhdGVDYWNoZVNpemU6ImNka1ZpcnR1YWxGb3JUZW1wbGF0ZUNhY2hlU2l6ZSJ9LGZlYXR1cmVzOlskdChbe3Byb3ZpZGU6U0gsdXNlQ2xhc3M6eTJ9XSldfSksbn0pKCksdWQ9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLFpjPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltEaCx1ZCxEaCx1ZF19KSxufSkoKSxzdz1jbGFzc3thdHRhY2godCl7cmV0dXJuIHRoaXMuX2F0dGFjaGVkSG9zdD10LHQuYXR0YWNoKHRoaXMpfWRldGFjaCgpe2xldCB0PXRoaXMuX2F0dGFjaGVkSG9zdDtudWxsIT10JiYodGhpcy5fYXR0YWNoZWRIb3N0PW51bGwsdC5kZXRhY2goKSl9Z2V0IGlzQXR0YWNoZWQoKXtyZXR1cm4gbnVsbCE9dGhpcy5fYXR0YWNoZWRIb3N0fXNldEF0dGFjaGVkSG9zdCh0KXt0aGlzLl9hdHRhY2hlZEhvc3Q9dH19LCRjPWNsYXNzIGV4dGVuZHMgc3d7Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIoKSx0aGlzLmNvbXBvbmVudD10LHRoaXMudmlld0NvbnRhaW5lclJlZj1lLHRoaXMuaW5qZWN0b3I9aSx0aGlzLmNvbXBvbmVudEZhY3RvcnlSZXNvbHZlcj1yfX0sa3M9Y2xhc3MgZXh0ZW5kcyBzd3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXtzdXBlcigpLHRoaXMudGVtcGxhdGVSZWY9dCx0aGlzLnZpZXdDb250YWluZXJSZWY9ZSx0aGlzLmNvbnRleHQ9aSx0aGlzLmluamVjdG9yPXJ9Z2V0IG9yaWdpbigpe3JldHVybiB0aGlzLnRlbXBsYXRlUmVmLmVsZW1lbnRSZWZ9YXR0YWNoKHQsZT10aGlzLmNvbnRleHQpe3JldHVybiB0aGlzLmNvbnRleHQ9ZSxzdXBlci5hdHRhY2godCl9ZGV0YWNoKCl7cmV0dXJuIHRoaXMuY29udGV4dD12b2lkIDAsc3VwZXIuZGV0YWNoKCl9fSxUSD1jbGFzcyBleHRlbmRzIHN3e2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5lbGVtZW50PXQgaW5zdGFuY2VvZiBSZT90Lm5hdGl2ZUVsZW1lbnQ6dH19LFBoPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5faXNEaXNwb3NlZD0hMSx0aGlzLmF0dGFjaERvbVBvcnRhbD1udWxsfWhhc0F0dGFjaGVkKCl7cmV0dXJuISF0aGlzLl9hdHRhY2hlZFBvcnRhbH1hdHRhY2godCl7cmV0dXJuIHQgaW5zdGFuY2VvZiAkYz8odGhpcy5fYXR0YWNoZWRQb3J0YWw9dCx0aGlzLmF0dGFjaENvbXBvbmVudFBvcnRhbCh0KSk6dCBpbnN0YW5jZW9mIGtzPyh0aGlzLl9hdHRhY2hlZFBvcnRhbD10LHRoaXMuYXR0YWNoVGVtcGxhdGVQb3J0YWwodCkpOnRoaXMuYXR0YWNoRG9tUG9ydGFsJiZ0IGluc3RhbmNlb2YgVEg/KHRoaXMuX2F0dGFjaGVkUG9ydGFsPXQsdGhpcy5hdHRhY2hEb21Qb3J0YWwodCkpOnZvaWQgMH1kZXRhY2goKXt0aGlzLl9hdHRhY2hlZFBvcnRhbCYmKHRoaXMuX2F0dGFjaGVkUG9ydGFsLnNldEF0dGFjaGVkSG9zdChudWxsKSx0aGlzLl9hdHRhY2hlZFBvcnRhbD1udWxsKSx0aGlzLl9pbnZva2VEaXNwb3NlRm4oKX1kaXNwb3NlKCl7dGhpcy5oYXNBdHRhY2hlZCgpJiZ0aGlzLmRldGFjaCgpLHRoaXMuX2ludm9rZURpc3Bvc2VGbigpLHRoaXMuX2lzRGlzcG9zZWQ9ITB9c2V0RGlzcG9zZUZuKHQpe3RoaXMuX2Rpc3Bvc2VGbj10fV9pbnZva2VEaXNwb3NlRm4oKXt0aGlzLl9kaXNwb3NlRm4mJih0aGlzLl9kaXNwb3NlRm4oKSx0aGlzLl9kaXNwb3NlRm49bnVsbCl9fSxhdz1jbGFzcyBleHRlbmRzIFBoe2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7c3VwZXIoKSx0aGlzLm91dGxldEVsZW1lbnQ9dCx0aGlzLl9jb21wb25lbnRGYWN0b3J5UmVzb2x2ZXI9ZSx0aGlzLl9hcHBSZWY9aSx0aGlzLl9kZWZhdWx0SW5qZWN0b3I9cix0aGlzLmF0dGFjaERvbVBvcnRhbD1zPT57bGV0IGE9cy5lbGVtZW50LGw9dGhpcy5fZG9jdW1lbnQuY3JlYXRlQ29tbWVudCgiZG9tLXBvcnRhbCIpO2EucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUobCxhKSx0aGlzLm91dGxldEVsZW1lbnQuYXBwZW5kQ2hpbGQoYSksdGhpcy5fYXR0YWNoZWRQb3J0YWw9cyxzdXBlci5zZXREaXNwb3NlRm4oKCk9PntsLnBhcmVudE5vZGUmJmwucGFyZW50Tm9kZS5yZXBsYWNlQ2hpbGQoYSxsKX0pfSx0aGlzLl9kb2N1bWVudD1vfWF0dGFjaENvbXBvbmVudFBvcnRhbCh0KXtsZXQgcixpPSh0LmNvbXBvbmVudEZhY3RvcnlSZXNvbHZlcnx8dGhpcy5fY29tcG9uZW50RmFjdG9yeVJlc29sdmVyKS5yZXNvbHZlQ29tcG9uZW50RmFjdG9yeSh0LmNvbXBvbmVudCk7cmV0dXJuIHQudmlld0NvbnRhaW5lclJlZj8ocj10LnZpZXdDb250YWluZXJSZWYuY3JlYXRlQ29tcG9uZW50KGksdC52aWV3Q29udGFpbmVyUmVmLmxlbmd0aCx0LmluamVjdG9yfHx0LnZpZXdDb250YWluZXJSZWYuaW5qZWN0b3IpLHRoaXMuc2V0RGlzcG9zZUZuKCgpPT5yLmRlc3Ryb3koKSkpOihyPWkuY3JlYXRlKHQuaW5qZWN0b3J8fHRoaXMuX2RlZmF1bHRJbmplY3Rvcnx8WG4uTlVMTCksdGhpcy5fYXBwUmVmLmF0dGFjaFZpZXcoci5ob3N0VmlldyksdGhpcy5zZXREaXNwb3NlRm4oKCk9Pnt0aGlzLl9hcHBSZWYudmlld0NvdW50PjAmJnRoaXMuX2FwcFJlZi5kZXRhY2hWaWV3KHIuaG9zdFZpZXcpLHIuZGVzdHJveSgpfSkpLHRoaXMub3V0bGV0RWxlbWVudC5hcHBlbmRDaGlsZCh0aGlzLl9nZXRDb21wb25lbnRSb290Tm9kZShyKSksdGhpcy5fYXR0YWNoZWRQb3J0YWw9dCxyfWF0dGFjaFRlbXBsYXRlUG9ydGFsKHQpe2xldCBlPXQudmlld0NvbnRhaW5lclJlZixpPWUuY3JlYXRlRW1iZWRkZWRWaWV3KHQudGVtcGxhdGVSZWYsdC5jb250ZXh0LHtpbmplY3Rvcjp0LmluamVjdG9yfSk7cmV0dXJuIGkucm9vdE5vZGVzLmZvckVhY2gocj0+dGhpcy5vdXRsZXRFbGVtZW50LmFwcGVuZENoaWxkKHIpKSxpLmRldGVjdENoYW5nZXMoKSx0aGlzLnNldERpc3Bvc2VGbigoKT0+e2xldCByPWUuaW5kZXhPZihpKTstMSE9PXImJmUucmVtb3ZlKHIpfSksdGhpcy5fYXR0YWNoZWRQb3J0YWw9dCxpfWRpc3Bvc2UoKXtzdXBlci5kaXNwb3NlKCksdGhpcy5vdXRsZXRFbGVtZW50LnJlbW92ZSgpfV9nZXRDb21wb25lbnRSb290Tm9kZSh0KXtyZXR1cm4gdC5ob3N0Vmlldy5yb290Tm9kZXNbMF19fSxmdGU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGtze2NvbnN0cnVjdG9yKGUsaSl7c3VwZXIoZSxpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShWaSksTShPaSkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJjZGtQb3J0YWwiLCIiXV0sZXhwb3J0QXM6WyJjZGtQb3J0YWwiXSxmZWF0dXJlczpbdHRdfSksbn0pKCksZGE9KCgpPT57Y2xhc3MgbiBleHRlbmRzIFBoe2NvbnN0cnVjdG9yKGUsaSxyKXtzdXBlcigpLHRoaXMuX2NvbXBvbmVudEZhY3RvcnlSZXNvbHZlcj1lLHRoaXMuX3ZpZXdDb250YWluZXJSZWY9aSx0aGlzLl9pc0luaXRpYWxpemVkPSExLHRoaXMuYXR0YWNoZWQ9bmV3IEcsdGhpcy5hdHRhY2hEb21Qb3J0YWw9bz0+e2xldCBzPW8uZWxlbWVudCxhPXRoaXMuX2RvY3VtZW50LmNyZWF0ZUNvbW1lbnQoImRvbS1wb3J0YWwiKTtvLnNldEF0dGFjaGVkSG9zdCh0aGlzKSxzLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGEscyksdGhpcy5fZ2V0Um9vdE5vZGUoKS5hcHBlbmRDaGlsZChzKSx0aGlzLl9hdHRhY2hlZFBvcnRhbD1vLHN1cGVyLnNldERpc3Bvc2VGbigoKT0+e2EucGFyZW50Tm9kZSYmYS5wYXJlbnROb2RlLnJlcGxhY2VDaGlsZChzLGEpfSl9LHRoaXMuX2RvY3VtZW50PXJ9Z2V0IHBvcnRhbCgpe3JldHVybiB0aGlzLl9hdHRhY2hlZFBvcnRhbH1zZXQgcG9ydGFsKGUpe3RoaXMuaGFzQXR0YWNoZWQoKSYmIWUmJiF0aGlzLl9pc0luaXRpYWxpemVkfHwodGhpcy5oYXNBdHRhY2hlZCgpJiZzdXBlci5kZXRhY2goKSxlJiZzdXBlci5hdHRhY2goZSksdGhpcy5fYXR0YWNoZWRQb3J0YWw9ZXx8bnVsbCl9Z2V0IGF0dGFjaGVkUmVmKCl7cmV0dXJuIHRoaXMuX2F0dGFjaGVkUmVmfW5nT25Jbml0KCl7dGhpcy5faXNJbml0aWFsaXplZD0hMH1uZ09uRGVzdHJveSgpe3N1cGVyLmRpc3Bvc2UoKSx0aGlzLl9hdHRhY2hlZFBvcnRhbD1udWxsLHRoaXMuX2F0dGFjaGVkUmVmPW51bGx9YXR0YWNoQ29tcG9uZW50UG9ydGFsKGUpe2Uuc2V0QXR0YWNoZWRIb3N0KHRoaXMpO2xldCBpPW51bGwhPWUudmlld0NvbnRhaW5lclJlZj9lLnZpZXdDb250YWluZXJSZWY6dGhpcy5fdmlld0NvbnRhaW5lclJlZixvPShlLmNvbXBvbmVudEZhY3RvcnlSZXNvbHZlcnx8dGhpcy5fY29tcG9uZW50RmFjdG9yeVJlc29sdmVyKS5yZXNvbHZlQ29tcG9uZW50RmFjdG9yeShlLmNvbXBvbmVudCkscz1pLmNyZWF0ZUNvbXBvbmVudChvLGkubGVuZ3RoLGUuaW5qZWN0b3J8fGkuaW5qZWN0b3IpO3JldHVybiBpIT09dGhpcy5fdmlld0NvbnRhaW5lclJlZiYmdGhpcy5fZ2V0Um9vdE5vZGUoKS5hcHBlbmRDaGlsZChzLmhvc3RWaWV3LnJvb3ROb2Rlc1swXSksc3VwZXIuc2V0RGlzcG9zZUZuKCgpPT5zLmRlc3Ryb3koKSksdGhpcy5fYXR0YWNoZWRQb3J0YWw9ZSx0aGlzLl9hdHRhY2hlZFJlZj1zLHRoaXMuYXR0YWNoZWQuZW1pdChzKSxzfWF0dGFjaFRlbXBsYXRlUG9ydGFsKGUpe2Uuc2V0QXR0YWNoZWRIb3N0KHRoaXMpO2xldCBpPXRoaXMuX3ZpZXdDb250YWluZXJSZWYuY3JlYXRlRW1iZWRkZWRWaWV3KGUudGVtcGxhdGVSZWYsZS5jb250ZXh0LHtpbmplY3RvcjplLmluamVjdG9yfSk7cmV0dXJuIHN1cGVyLnNldERpc3Bvc2VGbigoKT0+dGhpcy5fdmlld0NvbnRhaW5lclJlZi5jbGVhcigpKSx0aGlzLl9hdHRhY2hlZFBvcnRhbD1lLHRoaXMuX2F0dGFjaGVkUmVmPWksdGhpcy5hdHRhY2hlZC5lbWl0KGkpLGl9X2dldFJvb3ROb2RlKCl7bGV0IGU9dGhpcy5fdmlld0NvbnRhaW5lclJlZi5lbGVtZW50Lm5hdGl2ZUVsZW1lbnQ7cmV0dXJuIGUubm9kZVR5cGU9PT1lLkVMRU1FTlRfTk9ERT9lOmUucGFyZW50Tm9kZX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShncyksTShPaSksTShIdCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJjZGtQb3J0YWxPdXRsZXQiLCIiXV0saW5wdXRzOntwb3J0YWw6WyJjZGtQb3J0YWxPdXRsZXQiLCJwb3J0YWwiXX0sb3V0cHV0czp7YXR0YWNoZWQ6ImF0dGFjaGVkIn0sZXhwb3J0QXM6WyJjZGtQb3J0YWxPdXRsZXQiXSxmZWF0dXJlczpbdHRdfSksbn0pKCksZXU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLG10ZT1zMigpLE0yPWNsYXNze2VuYWJsZSgpe31kaXNhYmxlKCl7fWF0dGFjaCgpe319O2Z1bmN0aW9uIElIKG4sdCl7cmV0dXJuIHQuc29tZShlPT5uLmJvdHRvbTxlLnRvcHx8bi50b3A+ZS5ib3R0b218fG4ucmlnaHQ8ZS5sZWZ0fHxuLmxlZnQ+ZS5yaWdodCl9ZnVuY3Rpb24gZ3RlKG4sdCl7cmV0dXJuIHQuc29tZShlPT5uLnRvcDxlLnRvcHx8bi5ib3R0b20+ZS5ib3R0b218fG4ubGVmdDxlLmxlZnR8fG4ucmlnaHQ+ZS5yaWdodCl9dmFyIG5nPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe3RoaXMuX3Njcm9sbERpc3BhdGNoZXI9dCx0aGlzLl92aWV3cG9ydFJ1bGVyPWUsdGhpcy5fbmdab25lPWksdGhpcy5fY29uZmlnPXIsdGhpcy5fc2Nyb2xsU3Vic2NyaXB0aW9uPW51bGx9YXR0YWNoKHQpe3RoaXMuX292ZXJsYXlSZWY9dH1lbmFibGUoKXt0aGlzLl9zY3JvbGxTdWJzY3JpcHRpb258fCh0aGlzLl9zY3JvbGxTdWJzY3JpcHRpb249dGhpcy5fc2Nyb2xsRGlzcGF0Y2hlci5zY3JvbGxlZCh0aGlzLl9jb25maWc/dGhpcy5fY29uZmlnLnNjcm9sbFRocm90dGxlOjApLnN1YnNjcmliZSgoKT0+e2lmKHRoaXMuX292ZXJsYXlSZWYudXBkYXRlUG9zaXRpb24oKSx0aGlzLl9jb25maWcmJnRoaXMuX2NvbmZpZy5hdXRvQ2xvc2Upe2xldCBlPXRoaXMuX292ZXJsYXlSZWYub3ZlcmxheUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkse3dpZHRoOmksaGVpZ2h0OnJ9PXRoaXMuX3ZpZXdwb3J0UnVsZXIuZ2V0Vmlld3BvcnRTaXplKCk7SUgoZSxbe3dpZHRoOmksaGVpZ2h0OnIsYm90dG9tOnIscmlnaHQ6aSx0b3A6MCxsZWZ0OjB9XSkmJih0aGlzLmRpc2FibGUoKSx0aGlzLl9uZ1pvbmUucnVuKCgpPT50aGlzLl9vdmVybGF5UmVmLmRldGFjaCgpKSl9fSkpfWRpc2FibGUoKXt0aGlzLl9zY3JvbGxTdWJzY3JpcHRpb24mJih0aGlzLl9zY3JvbGxTdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKSx0aGlzLl9zY3JvbGxTdWJzY3JpcHRpb249bnVsbCl9ZGV0YWNoKCl7dGhpcy5kaXNhYmxlKCksdGhpcy5fb3ZlcmxheVJlZj1udWxsfX0sbU9lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIsbyl7dGhpcy5fc2Nyb2xsRGlzcGF0Y2hlcj1lLHRoaXMuX3ZpZXdwb3J0UnVsZXI9aSx0aGlzLl9uZ1pvbmU9cix0aGlzLm5vb3A9KCk9Pm5ldyBNMix0aGlzLmNsb3NlPXM9Pm5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXt0aGlzLl9zY3JvbGxEaXNwYXRjaGVyPXQsdGhpcy5fbmdab25lPWUsdGhpcy5fdmlld3BvcnRSdWxlcj1pLHRoaXMuX2NvbmZpZz1yLHRoaXMuX3Njcm9sbFN1YnNjcmlwdGlvbj1udWxsLHRoaXMuX2RldGFjaD0oKT0+e3RoaXMuZGlzYWJsZSgpLHRoaXMuX292ZXJsYXlSZWYuaGFzQXR0YWNoZWQoKSYmdGhpcy5fbmdab25lLnJ1bigoKT0+dGhpcy5fb3ZlcmxheVJlZi5kZXRhY2goKSl9fWF0dGFjaCh0KXt0aGlzLl9vdmVybGF5UmVmPXR9ZW5hYmxlKCl7aWYodGhpcy5fc2Nyb2xsU3Vic2NyaXB0aW9uKXJldHVybjtsZXQgdD10aGlzLl9zY3JvbGxEaXNwYXRjaGVyLnNjcm9sbGVkKDApO3RoaXMuX2NvbmZpZyYmdGhpcy5fY29uZmlnLnRocmVzaG9sZCYmdGhpcy5fY29uZmlnLnRocmVzaG9sZD4xPyh0aGlzLl9pbml0aWFsU2Nyb2xsUG9zaXRpb249dGhpcy5fdmlld3BvcnRSdWxlci5nZXRWaWV3cG9ydFNjcm9sbFBvc2l0aW9uKCkudG9wLHRoaXMuX3Njcm9sbFN1YnNjcmlwdGlvbj10LnN1YnNjcmliZSgoKT0+e2xldCBlPXRoaXMuX3ZpZXdwb3J0UnVsZXIuZ2V0Vmlld3BvcnRTY3JvbGxQb3NpdGlvbigpLnRvcDtNYXRoLmFicyhlLXRoaXMuX2luaXRpYWxTY3JvbGxQb3NpdGlvbik+dGhpcy5fY29uZmlnLnRocmVzaG9sZD90aGlzLl9kZXRhY2goKTp0aGlzLl9vdmVybGF5UmVmLnVwZGF0ZVBvc2l0aW9uKCl9KSk6dGhpcy5fc2Nyb2xsU3Vic2NyaXB0aW9uPXQuc3Vic2NyaWJlKHRoaXMuX2RldGFjaCl9ZGlzYWJsZSgpe3RoaXMuX3Njcm9sbFN1YnNjcmlwdGlvbiYmKHRoaXMuX3Njcm9sbFN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX3Njcm9sbFN1YnNjcmlwdGlvbj1udWxsKX1kZXRhY2goKXt0aGlzLmRpc2FibGUoKSx0aGlzLl9vdmVybGF5UmVmPW51bGx9fSh0aGlzLl9zY3JvbGxEaXNwYXRjaGVyLHRoaXMuX25nWm9uZSx0aGlzLl92aWV3cG9ydFJ1bGVyLHMpLHRoaXMuYmxvY2s9KCk9Pm5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuX3ZpZXdwb3J0UnVsZXI9dCx0aGlzLl9wcmV2aW91c0hUTUxTdHlsZXM9e3RvcDoiIixsZWZ0OiIifSx0aGlzLl9pc0VuYWJsZWQ9ITEsdGhpcy5fZG9jdW1lbnQ9ZX1hdHRhY2goKXt9ZW5hYmxlKCl7aWYodGhpcy5fY2FuQmVFbmFibGVkKCkpe2xldCB0PXRoaXMuX2RvY3VtZW50LmRvY3VtZW50RWxlbWVudDt0aGlzLl9wcmV2aW91c1Njcm9sbFBvc2l0aW9uPXRoaXMuX3ZpZXdwb3J0UnVsZXIuZ2V0Vmlld3BvcnRTY3JvbGxQb3NpdGlvbigpLHRoaXMuX3ByZXZpb3VzSFRNTFN0eWxlcy5sZWZ0PXQuc3R5bGUubGVmdHx8IiIsdGhpcy5fcHJldmlvdXNIVE1MU3R5bGVzLnRvcD10LnN0eWxlLnRvcHx8IiIsdC5zdHlsZS5sZWZ0PXlvKC10aGlzLl9wcmV2aW91c1Njcm9sbFBvc2l0aW9uLmxlZnQpLHQuc3R5bGUudG9wPXlvKC10aGlzLl9wcmV2aW91c1Njcm9sbFBvc2l0aW9uLnRvcCksdC5jbGFzc0xpc3QuYWRkKCJjZGstZ2xvYmFsLXNjcm9sbGJsb2NrIiksdGhpcy5faXNFbmFibGVkPSEwfX1kaXNhYmxlKCl7aWYodGhpcy5faXNFbmFibGVkKXtsZXQgdD10aGlzLl9kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsaT10LnN0eWxlLHI9dGhpcy5fZG9jdW1lbnQuYm9keS5zdHlsZSxvPWkuc2Nyb2xsQmVoYXZpb3J8fCIiLHM9ci5zY3JvbGxCZWhhdmlvcnx8IiI7dGhpcy5faXNFbmFibGVkPSExLGkubGVmdD10aGlzLl9wcmV2aW91c0hUTUxTdHlsZXMubGVmdCxpLnRvcD10aGlzLl9wcmV2aW91c0hUTUxTdHlsZXMudG9wLHQuY2xhc3NMaXN0LnJlbW92ZSgiY2RrLWdsb2JhbC1zY3JvbGxibG9jayIpLG10ZSYmKGkuc2Nyb2xsQmVoYXZpb3I9ci5zY3JvbGxCZWhhdmlvcj0iYXV0byIpLHdpbmRvdy5zY3JvbGwodGhpcy5fcHJldmlvdXNTY3JvbGxQb3NpdGlvbi5sZWZ0LHRoaXMuX3ByZXZpb3VzU2Nyb2xsUG9zaXRpb24udG9wKSxtdGUmJihpLnNjcm9sbEJlaGF2aW9yPW8sci5zY3JvbGxCZWhhdmlvcj1zKX19X2NhbkJlRW5hYmxlZCgpe2lmKHRoaXMuX2RvY3VtZW50LmRvY3VtZW50RWxlbWVudC5jbGFzc0xpc3QuY29udGFpbnMoImNkay1nbG9iYWwtc2Nyb2xsYmxvY2siKXx8dGhpcy5faXNFbmFibGVkKXJldHVybiExO2xldCBlPXRoaXMuX2RvY3VtZW50LmJvZHksaT10aGlzLl92aWV3cG9ydFJ1bGVyLmdldFZpZXdwb3J0U2l6ZSgpO3JldHVybiBlLnNjcm9sbEhlaWdodD5pLmhlaWdodHx8ZS5zY3JvbGxXaWR0aD5pLndpZHRofX0odGhpcy5fdmlld3BvcnRSdWxlcix0aGlzLl9kb2N1bWVudCksdGhpcy5yZXBvc2l0aW9uPXM9Pm5ldyBuZyh0aGlzLl9zY3JvbGxEaXNwYXRjaGVyLHRoaXMuX3ZpZXdwb3J0UnVsZXIsdGhpcy5fbmdab25lLHMpLHRoaXMuX2RvY3VtZW50PW99fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooJG0pLGooVmEpLGooX3QpLGooSHQpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKSxzYz1jbGFzc3tjb25zdHJ1Y3Rvcih0KXtpZih0aGlzLnNjcm9sbFN0cmF0ZWd5PW5ldyBNMix0aGlzLnBhbmVsQ2xhc3M9IiIsdGhpcy5oYXNCYWNrZHJvcD0hMSx0aGlzLmJhY2tkcm9wQ2xhc3M9ImNkay1vdmVybGF5LWRhcmstYmFja2Ryb3AiLHRoaXMuZGlzcG9zZU9uTmF2aWdhdGlvbj0hMSx0KXtsZXQgZT1PYmplY3Qua2V5cyh0KTtmb3IobGV0IGkgb2YgZSl2b2lkIDAhPT10W2ldJiYodGhpc1tpXT10W2ldKX19fSx4dGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLl9hdHRhY2hlZE92ZXJsYXlzPVtdLHRoaXMuX2RvY3VtZW50PWV9bmdPbkRlc3Ryb3koKXt0aGlzLmRldGFjaCgpfWFkZChlKXt0aGlzLnJlbW92ZShlKSx0aGlzLl9hdHRhY2hlZE92ZXJsYXlzLnB1c2goZSl9cmVtb3ZlKGUpe2xldCBpPXRoaXMuX2F0dGFjaGVkT3ZlcmxheXMuaW5kZXhPZihlKTtpPi0xJiZ0aGlzLl9hdHRhY2hlZE92ZXJsYXlzLnNwbGljZShpLDEpLDA9PT10aGlzLl9hdHRhY2hlZE92ZXJsYXlzLmxlbmd0aCYmdGhpcy5kZXRhY2goKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihIdCkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpLGdPZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgeHRle2NvbnN0cnVjdG9yKGUsaSl7c3VwZXIoZSksdGhpcy5fbmdab25lPWksdGhpcy5fa2V5ZG93bkxpc3RlbmVyPXI9PntsZXQgbz10aGlzLl9hdHRhY2hlZE92ZXJsYXlzO2ZvcihsZXQgcz1vLmxlbmd0aC0xO3M+LTE7cy0tKWlmKG9bc10uX2tleWRvd25FdmVudHMub2JzZXJ2ZXJzLmxlbmd0aD4wKXtsZXQgYT1vW3NdLl9rZXlkb3duRXZlbnRzO3RoaXMuX25nWm9uZT90aGlzLl9uZ1pvbmUucnVuKCgpPT5hLm5leHQocikpOmEubmV4dChyKTticmVha319fWFkZChlKXtzdXBlci5hZGQoZSksdGhpcy5faXNBdHRhY2hlZHx8KHRoaXMuX25nWm9uZT90aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PnRoaXMuX2RvY3VtZW50LmJvZHkuYWRkRXZlbnRMaXN0ZW5lcigia2V5ZG93biIsdGhpcy5fa2V5ZG93bkxpc3RlbmVyKSk6dGhpcy5fZG9jdW1lbnQuYm9keS5hZGRFdmVudExpc3RlbmVyKCJrZXlkb3duIix0aGlzLl9rZXlkb3duTGlzdGVuZXIpLHRoaXMuX2lzQXR0YWNoZWQ9ITApfWRldGFjaCgpe3RoaXMuX2lzQXR0YWNoZWQmJih0aGlzLl9kb2N1bWVudC5ib2R5LnJlbW92ZUV2ZW50TGlzdGVuZXIoImtleWRvd24iLHRoaXMuX2tleWRvd25MaXN0ZW5lciksdGhpcy5faXNBdHRhY2hlZD0hMSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooSHQpLGooX3QsOCkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpLF9PZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgeHRle2NvbnN0cnVjdG9yKGUsaSxyKXtzdXBlcihlKSx0aGlzLl9wbGF0Zm9ybT1pLHRoaXMuX25nWm9uZT1yLHRoaXMuX2N1cnNvclN0eWxlSXNTZXQ9ITEsdGhpcy5fcG9pbnRlckRvd25MaXN0ZW5lcj1vPT57dGhpcy5fcG9pbnRlckRvd25FdmVudFRhcmdldD1RYyhvKX0sdGhpcy5fY2xpY2tMaXN0ZW5lcj1vPT57bGV0IHM9UWMobyksYT0iY2xpY2siPT09by50eXBlJiZ0aGlzLl9wb2ludGVyRG93bkV2ZW50VGFyZ2V0P3RoaXMuX3BvaW50ZXJEb3duRXZlbnRUYXJnZXQ6czt0aGlzLl9wb2ludGVyRG93bkV2ZW50VGFyZ2V0PW51bGw7bGV0IGw9dGhpcy5fYXR0YWNoZWRPdmVybGF5cy5zbGljZSgpO2ZvcihsZXQgYz1sLmxlbmd0aC0xO2M+LTE7Yy0tKXtsZXQgdT1sW2NdO2lmKHUuX291dHNpZGVQb2ludGVyRXZlbnRzLm9ic2VydmVycy5sZW5ndGg8MXx8IXUuaGFzQXR0YWNoZWQoKSljb250aW51ZTtpZih1Lm92ZXJsYXlFbGVtZW50LmNvbnRhaW5zKHMpfHx1Lm92ZXJsYXlFbGVtZW50LmNvbnRhaW5zKGEpKWJyZWFrO2xldCBkPXUuX291dHNpZGVQb2ludGVyRXZlbnRzO3RoaXMuX25nWm9uZT90aGlzLl9uZ1pvbmUucnVuKCgpPT5kLm5leHQobykpOmQubmV4dChvKX19fWFkZChlKXtpZihzdXBlci5hZGQoZSksIXRoaXMuX2lzQXR0YWNoZWQpe2xldCBpPXRoaXMuX2RvY3VtZW50LmJvZHk7dGhpcy5fbmdab25lP3RoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+dGhpcy5fYWRkRXZlbnRMaXN0ZW5lcnMoaSkpOnRoaXMuX2FkZEV2ZW50TGlzdGVuZXJzKGkpLHRoaXMuX3BsYXRmb3JtLklPUyYmIXRoaXMuX2N1cnNvclN0eWxlSXNTZXQmJih0aGlzLl9jdXJzb3JPcmlnaW5hbFZhbHVlPWkuc3R5bGUuY3Vyc29yLGkuc3R5bGUuY3Vyc29yPSJwb2ludGVyIix0aGlzLl9jdXJzb3JTdHlsZUlzU2V0PSEwKSx0aGlzLl9pc0F0dGFjaGVkPSEwfX1kZXRhY2goKXtpZih0aGlzLl9pc0F0dGFjaGVkKXtsZXQgZT10aGlzLl9kb2N1bWVudC5ib2R5O2UucmVtb3ZlRXZlbnRMaXN0ZW5lcigicG9pbnRlcmRvd24iLHRoaXMuX3BvaW50ZXJEb3duTGlzdGVuZXIsITApLGUucmVtb3ZlRXZlbnRMaXN0ZW5lcigiY2xpY2siLHRoaXMuX2NsaWNrTGlzdGVuZXIsITApLGUucmVtb3ZlRXZlbnRMaXN0ZW5lcigiYXV4Y2xpY2siLHRoaXMuX2NsaWNrTGlzdGVuZXIsITApLGUucmVtb3ZlRXZlbnRMaXN0ZW5lcigiY29udGV4dG1lbnUiLHRoaXMuX2NsaWNrTGlzdGVuZXIsITApLHRoaXMuX3BsYXRmb3JtLklPUyYmdGhpcy5fY3Vyc29yU3R5bGVJc1NldCYmKGUuc3R5bGUuY3Vyc29yPXRoaXMuX2N1cnNvck9yaWdpbmFsVmFsdWUsdGhpcy5fY3Vyc29yU3R5bGVJc1NldD0hMSksdGhpcy5faXNBdHRhY2hlZD0hMX19X2FkZEV2ZW50TGlzdGVuZXJzKGUpe2UuYWRkRXZlbnRMaXN0ZW5lcigicG9pbnRlcmRvd24iLHRoaXMuX3BvaW50ZXJEb3duTGlzdGVuZXIsITApLGUuYWRkRXZlbnRMaXN0ZW5lcigiY2xpY2siLHRoaXMuX2NsaWNrTGlzdGVuZXIsITApLGUuYWRkRXZlbnRMaXN0ZW5lcigiYXV4Y2xpY2siLHRoaXMuX2NsaWNrTGlzdGVuZXIsITApLGUuYWRkRXZlbnRMaXN0ZW5lcigiY29udGV4dG1lbnUiLHRoaXMuX2NsaWNrTGlzdGVuZXIsITApfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKEh0KSxqKG9pKSxqKF90LDgpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKSxSdj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSl7dGhpcy5fcGxhdGZvcm09aSx0aGlzLl9kb2N1bWVudD1lfW5nT25EZXN0cm95KCl7dGhpcy5fY29udGFpbmVyRWxlbWVudD8ucmVtb3ZlKCl9Z2V0Q29udGFpbmVyRWxlbWVudCgpe3JldHVybiB0aGlzLl9jb250YWluZXJFbGVtZW50fHx0aGlzLl9jcmVhdGVDb250YWluZXIoKSx0aGlzLl9jb250YWluZXJFbGVtZW50fV9jcmVhdGVDb250YWluZXIoKXtsZXQgZT0iY2RrLW92ZXJsYXktY29udGFpbmVyIjtpZih0aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXJ8fFpNKCkpe2xldCByPXRoaXMuX2RvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoYC4ke2V9W3BsYXRmb3JtPSJzZXJ2ZXIiXSwgLiR7ZX1bcGxhdGZvcm09InRlc3QiXWApO2ZvcihsZXQgbz0wO288ci5sZW5ndGg7bysrKXJbb10ucmVtb3ZlKCl9bGV0IGk9dGhpcy5fZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7aS5jbGFzc0xpc3QuYWRkKGUpLFpNKCk/aS5zZXRBdHRyaWJ1dGUoInBsYXRmb3JtIiwidGVzdCIpOnRoaXMuX3BsYXRmb3JtLmlzQnJvd3Nlcnx8aS5zZXRBdHRyaWJ1dGUoInBsYXRmb3JtIiwic2VydmVyIiksdGhpcy5fZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChpKSx0aGlzLl9jb250YWluZXJFbGVtZW50PWl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooSHQpLGoob2kpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKSxkZD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyxhLGwsYyx1PSExKXt0aGlzLl9wb3J0YWxPdXRsZXQ9dCx0aGlzLl9ob3N0PWUsdGhpcy5fcGFuZT1pLHRoaXMuX2NvbmZpZz1yLHRoaXMuX25nWm9uZT1vLHRoaXMuX2tleWJvYXJkRGlzcGF0Y2hlcj1zLHRoaXMuX2RvY3VtZW50PWEsdGhpcy5fbG9jYXRpb249bCx0aGlzLl9vdXRzaWRlQ2xpY2tEaXNwYXRjaGVyPWMsdGhpcy5fYW5pbWF0aW9uc0Rpc2FibGVkPXUsdGhpcy5fYmFja2Ryb3BFbGVtZW50PW51bGwsdGhpcy5fYmFja2Ryb3BDbGljaz1uZXcga2UsdGhpcy5fYXR0YWNobWVudHM9bmV3IGtlLHRoaXMuX2RldGFjaG1lbnRzPW5ldyBrZSx0aGlzLl9sb2NhdGlvbkNoYW5nZXM9U24uRU1QVFksdGhpcy5fYmFja2Ryb3BDbGlja0hhbmRsZXI9ZD0+dGhpcy5fYmFja2Ryb3BDbGljay5uZXh0KGQpLHRoaXMuX2JhY2tkcm9wVHJhbnNpdGlvbmVuZEhhbmRsZXI9ZD0+e3RoaXMuX2Rpc3Bvc2VCYWNrZHJvcChkLnRhcmdldCl9LHRoaXMuX2tleWRvd25FdmVudHM9bmV3IGtlLHRoaXMuX291dHNpZGVQb2ludGVyRXZlbnRzPW5ldyBrZSxyLnNjcm9sbFN0cmF0ZWd5JiYodGhpcy5fc2Nyb2xsU3RyYXRlZ3k9ci5zY3JvbGxTdHJhdGVneSx0aGlzLl9zY3JvbGxTdHJhdGVneS5hdHRhY2godGhpcykpLHRoaXMuX3Bvc2l0aW9uU3RyYXRlZ3k9ci5wb3NpdGlvblN0cmF0ZWd5fWdldCBvdmVybGF5RWxlbWVudCgpe3JldHVybiB0aGlzLl9wYW5lfWdldCBiYWNrZHJvcEVsZW1lbnQoKXtyZXR1cm4gdGhpcy5fYmFja2Ryb3BFbGVtZW50fWdldCBob3N0RWxlbWVudCgpe3JldHVybiB0aGlzLl9ob3N0fWF0dGFjaCh0KXshdGhpcy5faG9zdC5wYXJlbnRFbGVtZW50JiZ0aGlzLl9wcmV2aW91c0hvc3RQYXJlbnQmJnRoaXMuX3ByZXZpb3VzSG9zdFBhcmVudC5hcHBlbmRDaGlsZCh0aGlzLl9ob3N0KTtsZXQgZT10aGlzLl9wb3J0YWxPdXRsZXQuYXR0YWNoKHQpO3JldHVybiB0aGlzLl9wb3NpdGlvblN0cmF0ZWd5JiZ0aGlzLl9wb3NpdGlvblN0cmF0ZWd5LmF0dGFjaCh0aGlzKSx0aGlzLl91cGRhdGVTdGFja2luZ09yZGVyKCksdGhpcy5fdXBkYXRlRWxlbWVudFNpemUoKSx0aGlzLl91cGRhdGVFbGVtZW50RGlyZWN0aW9uKCksdGhpcy5fc2Nyb2xsU3RyYXRlZ3kmJnRoaXMuX3Njcm9sbFN0cmF0ZWd5LmVuYWJsZSgpLHRoaXMuX25nWm9uZS5vblN0YWJsZS5waXBlKFF0KDEpKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLmhhc0F0dGFjaGVkKCkmJnRoaXMudXBkYXRlUG9zaXRpb24oKX0pLHRoaXMuX3RvZ2dsZVBvaW50ZXJFdmVudHMoITApLHRoaXMuX2NvbmZpZy5oYXNCYWNrZHJvcCYmdGhpcy5fYXR0YWNoQmFja2Ryb3AoKSx0aGlzLl9jb25maWcucGFuZWxDbGFzcyYmdGhpcy5fdG9nZ2xlQ2xhc3Nlcyh0aGlzLl9wYW5lLHRoaXMuX2NvbmZpZy5wYW5lbENsYXNzLCEwKSx0aGlzLl9hdHRhY2htZW50cy5uZXh0KCksdGhpcy5fa2V5Ym9hcmREaXNwYXRjaGVyLmFkZCh0aGlzKSx0aGlzLl9jb25maWcuZGlzcG9zZU9uTmF2aWdhdGlvbiYmKHRoaXMuX2xvY2F0aW9uQ2hhbmdlcz10aGlzLl9sb2NhdGlvbi5zdWJzY3JpYmUoKCk9PnRoaXMuZGlzcG9zZSgpKSksdGhpcy5fb3V0c2lkZUNsaWNrRGlzcGF0Y2hlci5hZGQodGhpcyksImZ1bmN0aW9uIj09dHlwZW9mIGU/Lm9uRGVzdHJveSYmZS5vbkRlc3Ryb3koKCk9Pnt0aGlzLmhhc0F0dGFjaGVkKCkmJnRoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+UHJvbWlzZS5yZXNvbHZlKCkudGhlbigoKT0+dGhpcy5kZXRhY2goKSkpfSksZX1kZXRhY2goKXtpZighdGhpcy5oYXNBdHRhY2hlZCgpKXJldHVybjt0aGlzLmRldGFjaEJhY2tkcm9wKCksdGhpcy5fdG9nZ2xlUG9pbnRlckV2ZW50cyghMSksdGhpcy5fcG9zaXRpb25TdHJhdGVneSYmdGhpcy5fcG9zaXRpb25TdHJhdGVneS5kZXRhY2gmJnRoaXMuX3Bvc2l0aW9uU3RyYXRlZ3kuZGV0YWNoKCksdGhpcy5fc2Nyb2xsU3RyYXRlZ3kmJnRoaXMuX3Njcm9sbFN0cmF0ZWd5LmRpc2FibGUoKTtsZXQgdD10aGlzLl9wb3J0YWxPdXRsZXQuZGV0YWNoKCk7cmV0dXJuIHRoaXMuX2RldGFjaG1lbnRzLm5leHQoKSx0aGlzLl9rZXlib2FyZERpc3BhdGNoZXIucmVtb3ZlKHRoaXMpLHRoaXMuX2RldGFjaENvbnRlbnRXaGVuU3RhYmxlKCksdGhpcy5fbG9jYXRpb25DaGFuZ2VzLnVuc3Vic2NyaWJlKCksdGhpcy5fb3V0c2lkZUNsaWNrRGlzcGF0Y2hlci5yZW1vdmUodGhpcyksdH1kaXNwb3NlKCl7bGV0IHQ9dGhpcy5oYXNBdHRhY2hlZCgpO3RoaXMuX3Bvc2l0aW9uU3RyYXRlZ3kmJnRoaXMuX3Bvc2l0aW9uU3RyYXRlZ3kuZGlzcG9zZSgpLHRoaXMuX2Rpc3Bvc2VTY3JvbGxTdHJhdGVneSgpLHRoaXMuX2Rpc3Bvc2VCYWNrZHJvcCh0aGlzLl9iYWNrZHJvcEVsZW1lbnQpLHRoaXMuX2xvY2F0aW9uQ2hhbmdlcy51bnN1YnNjcmliZSgpLHRoaXMuX2tleWJvYXJkRGlzcGF0Y2hlci5yZW1vdmUodGhpcyksdGhpcy5fcG9ydGFsT3V0bGV0LmRpc3Bvc2UoKSx0aGlzLl9hdHRhY2htZW50cy5jb21wbGV0ZSgpLHRoaXMuX2JhY2tkcm9wQ2xpY2suY29tcGxldGUoKSx0aGlzLl9rZXlkb3duRXZlbnRzLmNvbXBsZXRlKCksdGhpcy5fb3V0c2lkZVBvaW50ZXJFdmVudHMuY29tcGxldGUoKSx0aGlzLl9vdXRzaWRlQ2xpY2tEaXNwYXRjaGVyLnJlbW92ZSh0aGlzKSx0aGlzLl9ob3N0Py5yZW1vdmUoKSx0aGlzLl9wcmV2aW91c0hvc3RQYXJlbnQ9dGhpcy5fcGFuZT10aGlzLl9ob3N0PW51bGwsdCYmdGhpcy5fZGV0YWNobWVudHMubmV4dCgpLHRoaXMuX2RldGFjaG1lbnRzLmNvbXBsZXRlKCl9aGFzQXR0YWNoZWQoKXtyZXR1cm4gdGhpcy5fcG9ydGFsT3V0bGV0Lmhhc0F0dGFjaGVkKCl9YmFja2Ryb3BDbGljaygpe3JldHVybiB0aGlzLl9iYWNrZHJvcENsaWNrfWF0dGFjaG1lbnRzKCl7cmV0dXJuIHRoaXMuX2F0dGFjaG1lbnRzfWRldGFjaG1lbnRzKCl7cmV0dXJuIHRoaXMuX2RldGFjaG1lbnRzfWtleWRvd25FdmVudHMoKXtyZXR1cm4gdGhpcy5fa2V5ZG93bkV2ZW50c31vdXRzaWRlUG9pbnRlckV2ZW50cygpe3JldHVybiB0aGlzLl9vdXRzaWRlUG9pbnRlckV2ZW50c31nZXRDb25maWcoKXtyZXR1cm4gdGhpcy5fY29uZmlnfXVwZGF0ZVBvc2l0aW9uKCl7dGhpcy5fcG9zaXRpb25TdHJhdGVneSYmdGhpcy5fcG9zaXRpb25TdHJhdGVneS5hcHBseSgpfXVwZGF0ZVBvc2l0aW9uU3RyYXRlZ3kodCl7dCE9PXRoaXMuX3Bvc2l0aW9uU3RyYXRlZ3kmJih0aGlzLl9wb3NpdGlvblN0cmF0ZWd5JiZ0aGlzLl9wb3NpdGlvblN0cmF0ZWd5LmRpc3Bvc2UoKSx0aGlzLl9wb3NpdGlvblN0cmF0ZWd5PXQsdGhpcy5oYXNBdHRhY2hlZCgpJiYodC5hdHRhY2godGhpcyksdGhpcy51cGRhdGVQb3NpdGlvbigpKSl9dXBkYXRlU2l6ZSh0KXt0aGlzLl9jb25maWc9ey4uLnRoaXMuX2NvbmZpZywuLi50fSx0aGlzLl91cGRhdGVFbGVtZW50U2l6ZSgpfXNldERpcmVjdGlvbih0KXt0aGlzLl9jb25maWc9ey4uLnRoaXMuX2NvbmZpZyxkaXJlY3Rpb246dH0sdGhpcy5fdXBkYXRlRWxlbWVudERpcmVjdGlvbigpfWFkZFBhbmVsQ2xhc3ModCl7dGhpcy5fcGFuZSYmdGhpcy5fdG9nZ2xlQ2xhc3Nlcyh0aGlzLl9wYW5lLHQsITApfXJlbW92ZVBhbmVsQ2xhc3ModCl7dGhpcy5fcGFuZSYmdGhpcy5fdG9nZ2xlQ2xhc3Nlcyh0aGlzLl9wYW5lLHQsITEpfWdldERpcmVjdGlvbigpe2xldCB0PXRoaXMuX2NvbmZpZy5kaXJlY3Rpb247cmV0dXJuIHQ/InN0cmluZyI9PXR5cGVvZiB0P3Q6dC52YWx1ZToibHRyIn11cGRhdGVTY3JvbGxTdHJhdGVneSh0KXt0IT09dGhpcy5fc2Nyb2xsU3RyYXRlZ3kmJih0aGlzLl9kaXNwb3NlU2Nyb2xsU3RyYXRlZ3koKSx0aGlzLl9zY3JvbGxTdHJhdGVneT10LHRoaXMuaGFzQXR0YWNoZWQoKSYmKHQuYXR0YWNoKHRoaXMpLHQuZW5hYmxlKCkpKX1fdXBkYXRlRWxlbWVudERpcmVjdGlvbigpe3RoaXMuX2hvc3Quc2V0QXR0cmlidXRlKCJkaXIiLHRoaXMuZ2V0RGlyZWN0aW9uKCkpfV91cGRhdGVFbGVtZW50U2l6ZSgpe2lmKCF0aGlzLl9wYW5lKXJldHVybjtsZXQgdD10aGlzLl9wYW5lLnN0eWxlO3Qud2lkdGg9eW8odGhpcy5fY29uZmlnLndpZHRoKSx0LmhlaWdodD15byh0aGlzLl9jb25maWcuaGVpZ2h0KSx0Lm1pbldpZHRoPXlvKHRoaXMuX2NvbmZpZy5taW5XaWR0aCksdC5taW5IZWlnaHQ9eW8odGhpcy5fY29uZmlnLm1pbkhlaWdodCksdC5tYXhXaWR0aD15byh0aGlzLl9jb25maWcubWF4V2lkdGgpLHQubWF4SGVpZ2h0PXlvKHRoaXMuX2NvbmZpZy5tYXhIZWlnaHQpfV90b2dnbGVQb2ludGVyRXZlbnRzKHQpe3RoaXMuX3BhbmUuc3R5bGUucG9pbnRlckV2ZW50cz10PyIiOiJub25lIn1fYXR0YWNoQmFja2Ryb3AoKXtsZXQgdD0iY2RrLW92ZXJsYXktYmFja2Ryb3Atc2hvd2luZyI7dGhpcy5fYmFja2Ryb3BFbGVtZW50PXRoaXMuX2RvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpLHRoaXMuX2JhY2tkcm9wRWxlbWVudC5jbGFzc0xpc3QuYWRkKCJjZGstb3ZlcmxheS1iYWNrZHJvcCIpLHRoaXMuX2FuaW1hdGlvbnNEaXNhYmxlZCYmdGhpcy5fYmFja2Ryb3BFbGVtZW50LmNsYXNzTGlzdC5hZGQoImNkay1vdmVybGF5LWJhY2tkcm9wLW5vb3AtYW5pbWF0aW9uIiksdGhpcy5fY29uZmlnLmJhY2tkcm9wQ2xhc3MmJnRoaXMuX3RvZ2dsZUNsYXNzZXModGhpcy5fYmFja2Ryb3BFbGVtZW50LHRoaXMuX2NvbmZpZy5iYWNrZHJvcENsYXNzLCEwKSx0aGlzLl9ob3N0LnBhcmVudEVsZW1lbnQuaW5zZXJ0QmVmb3JlKHRoaXMuX2JhY2tkcm9wRWxlbWVudCx0aGlzLl9ob3N0KSx0aGlzLl9iYWNrZHJvcEVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigiY2xpY2siLHRoaXMuX2JhY2tkcm9wQ2xpY2tIYW5kbGVyKSwhdGhpcy5fYW5pbWF0aW9uc0Rpc2FibGVkJiZ0eXBlb2YgcmVxdWVzdEFuaW1hdGlvbkZyYW1lPCJ1Ij90aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntyZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCk9Pnt0aGlzLl9iYWNrZHJvcEVsZW1lbnQmJnRoaXMuX2JhY2tkcm9wRWxlbWVudC5jbGFzc0xpc3QuYWRkKHQpfSl9KTp0aGlzLl9iYWNrZHJvcEVsZW1lbnQuY2xhc3NMaXN0LmFkZCh0KX1fdXBkYXRlU3RhY2tpbmdPcmRlcigpe3RoaXMuX2hvc3QubmV4dFNpYmxpbmcmJnRoaXMuX2hvc3QucGFyZW50Tm9kZS5hcHBlbmRDaGlsZCh0aGlzLl9ob3N0KX1kZXRhY2hCYWNrZHJvcCgpe2xldCB0PXRoaXMuX2JhY2tkcm9wRWxlbWVudDtpZih0KXtpZih0aGlzLl9hbmltYXRpb25zRGlzYWJsZWQpcmV0dXJuIHZvaWQgdGhpcy5fZGlzcG9zZUJhY2tkcm9wKHQpO3QuY2xhc3NMaXN0LnJlbW92ZSgiY2RrLW92ZXJsYXktYmFja2Ryb3Atc2hvd2luZyIpLHRoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e3QuYWRkRXZlbnRMaXN0ZW5lcigidHJhbnNpdGlvbmVuZCIsdGhpcy5fYmFja2Ryb3BUcmFuc2l0aW9uZW5kSGFuZGxlcil9KSx0LnN0eWxlLnBvaW50ZXJFdmVudHM9Im5vbmUiLHRoaXMuX2JhY2tkcm9wVGltZW91dD10aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PnNldFRpbWVvdXQoKCk9Pnt0aGlzLl9kaXNwb3NlQmFja2Ryb3AodCl9LDUwMCkpfX1fdG9nZ2xlQ2xhc3Nlcyh0LGUsaSl7bGV0IHI9eHYoZXx8W10pLmZpbHRlcihvPT4hIW8pO3IubGVuZ3RoJiYoaT90LmNsYXNzTGlzdC5hZGQoLi4ucik6dC5jbGFzc0xpc3QucmVtb3ZlKC4uLnIpKX1fZGV0YWNoQ29udGVudFdoZW5TdGFibGUoKXt0aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntsZXQgdD10aGlzLl9uZ1pvbmUub25TdGFibGUucGlwZShzdChKdCh0aGlzLl9hdHRhY2htZW50cyx0aGlzLl9kZXRhY2htZW50cykpKS5zdWJzY3JpYmUoKCk9PnsoIXRoaXMuX3BhbmV8fCF0aGlzLl9ob3N0fHwwPT09dGhpcy5fcGFuZS5jaGlsZHJlbi5sZW5ndGgpJiYodGhpcy5fcGFuZSYmdGhpcy5fY29uZmlnLnBhbmVsQ2xhc3MmJnRoaXMuX3RvZ2dsZUNsYXNzZXModGhpcy5fcGFuZSx0aGlzLl9jb25maWcucGFuZWxDbGFzcywhMSksdGhpcy5faG9zdCYmdGhpcy5faG9zdC5wYXJlbnRFbGVtZW50JiYodGhpcy5fcHJldmlvdXNIb3N0UGFyZW50PXRoaXMuX2hvc3QucGFyZW50RWxlbWVudCx0aGlzLl9ob3N0LnJlbW92ZSgpKSx0LnVuc3Vic2NyaWJlKCkpfSl9KX1fZGlzcG9zZVNjcm9sbFN0cmF0ZWd5KCl7bGV0IHQ9dGhpcy5fc2Nyb2xsU3RyYXRlZ3k7dCYmKHQuZGlzYWJsZSgpLHQuZGV0YWNoJiZ0LmRldGFjaCgpKX1fZGlzcG9zZUJhY2tkcm9wKHQpe3QmJih0LnJlbW92ZUV2ZW50TGlzdGVuZXIoImNsaWNrIix0aGlzLl9iYWNrZHJvcENsaWNrSGFuZGxlciksdC5yZW1vdmVFdmVudExpc3RlbmVyKCJ0cmFuc2l0aW9uZW5kIix0aGlzLl9iYWNrZHJvcFRyYW5zaXRpb25lbmRIYW5kbGVyKSx0LnJlbW92ZSgpLHRoaXMuX2JhY2tkcm9wRWxlbWVudD09PXQmJih0aGlzLl9iYWNrZHJvcEVsZW1lbnQ9bnVsbCkpLHRoaXMuX2JhY2tkcm9wVGltZW91dCYmKGNsZWFyVGltZW91dCh0aGlzLl9iYWNrZHJvcFRpbWVvdXQpLHRoaXMuX2JhY2tkcm9wVGltZW91dD12b2lkIDApfX0sX3RlPSJjZGstb3ZlcmxheS1jb25uZWN0ZWQtcG9zaXRpb24tYm91bmRpbmctYm94Iix2T2U9LyhbQS1aYS16JV0rKSQvO2Z1bmN0aW9uIHRnKG4sdCl7Zm9yKGxldCBlIGluIHQpdC5oYXNPd25Qcm9wZXJ0eShlKSYmKG5bZV09dFtlXSk7cmV0dXJuIG59ZnVuY3Rpb24gdnRlKG4pe2lmKCJudW1iZXIiIT10eXBlb2YgbiYmbnVsbCE9bil7bGV0W3QsZV09bi5zcGxpdCh2T2UpO3JldHVybiBlJiYicHgiIT09ZT9udWxsOnBhcnNlRmxvYXQodCl9cmV0dXJuIG58fG51bGx9ZnVuY3Rpb24geXRlKG4pe3JldHVybnt0b3A6TWF0aC5mbG9vcihuLnRvcCkscmlnaHQ6TWF0aC5mbG9vcihuLnJpZ2h0KSxib3R0b206TWF0aC5mbG9vcihuLmJvdHRvbSksbGVmdDpNYXRoLmZsb29yKG4ubGVmdCksd2lkdGg6TWF0aC5mbG9vcihuLndpZHRoKSxoZWlnaHQ6TWF0aC5mbG9vcihuLmhlaWdodCl9fXZhciBidGU9ImNkay1nbG9iYWwtb3ZlcmxheS13cmFwcGVyIix5T2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscixvKXt0aGlzLl92aWV3cG9ydFJ1bGVyPWUsdGhpcy5fZG9jdW1lbnQ9aSx0aGlzLl9wbGF0Zm9ybT1yLHRoaXMuX292ZXJsYXlDb250YWluZXI9b31nbG9iYWwoKXtyZXR1cm4gbmV3IGNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5fY3NzUG9zaXRpb249InN0YXRpYyIsdGhpcy5fdG9wT2Zmc2V0PSIiLHRoaXMuX2JvdHRvbU9mZnNldD0iIix0aGlzLl9hbGlnbkl0ZW1zPSIiLHRoaXMuX3hQb3NpdGlvbj0iIix0aGlzLl94T2Zmc2V0PSIiLHRoaXMuX3dpZHRoPSIiLHRoaXMuX2hlaWdodD0iIix0aGlzLl9pc0Rpc3Bvc2VkPSExfWF0dGFjaCh0KXtsZXQgZT10LmdldENvbmZpZygpO3RoaXMuX292ZXJsYXlSZWY9dCx0aGlzLl93aWR0aCYmIWUud2lkdGgmJnQudXBkYXRlU2l6ZSh7d2lkdGg6dGhpcy5fd2lkdGh9KSx0aGlzLl9oZWlnaHQmJiFlLmhlaWdodCYmdC51cGRhdGVTaXplKHtoZWlnaHQ6dGhpcy5faGVpZ2h0fSksdC5ob3N0RWxlbWVudC5jbGFzc0xpc3QuYWRkKGJ0ZSksdGhpcy5faXNEaXNwb3NlZD0hMX10b3AodD0iIil7cmV0dXJuIHRoaXMuX2JvdHRvbU9mZnNldD0iIix0aGlzLl90b3BPZmZzZXQ9dCx0aGlzLl9hbGlnbkl0ZW1zPSJmbGV4LXN0YXJ0Iix0aGlzfWxlZnQodD0iIil7cmV0dXJuIHRoaXMuX3hPZmZzZXQ9dCx0aGlzLl94UG9zaXRpb249ImxlZnQiLHRoaXN9Ym90dG9tKHQ9IiIpe3JldHVybiB0aGlzLl90b3BPZmZzZXQ9IiIsdGhpcy5fYm90dG9tT2Zmc2V0PXQsdGhpcy5fYWxpZ25JdGVtcz0iZmxleC1lbmQiLHRoaXN9cmlnaHQodD0iIil7cmV0dXJuIHRoaXMuX3hPZmZzZXQ9dCx0aGlzLl94UG9zaXRpb249InJpZ2h0Iix0aGlzfXN0YXJ0KHQ9IiIpe3JldHVybiB0aGlzLl94T2Zmc2V0PXQsdGhpcy5feFBvc2l0aW9uPSJzdGFydCIsdGhpc31lbmQodD0iIil7cmV0dXJuIHRoaXMuX3hPZmZzZXQ9dCx0aGlzLl94UG9zaXRpb249ImVuZCIsdGhpc313aWR0aCh0PSIiKXtyZXR1cm4gdGhpcy5fb3ZlcmxheVJlZj90aGlzLl9vdmVybGF5UmVmLnVwZGF0ZVNpemUoe3dpZHRoOnR9KTp0aGlzLl93aWR0aD10LHRoaXN9aGVpZ2h0KHQ9IiIpe3JldHVybiB0aGlzLl9vdmVybGF5UmVmP3RoaXMuX292ZXJsYXlSZWYudXBkYXRlU2l6ZSh7aGVpZ2h0OnR9KTp0aGlzLl9oZWlnaHQ9dCx0aGlzfWNlbnRlckhvcml6b250YWxseSh0PSIiKXtyZXR1cm4gdGhpcy5sZWZ0KHQpLHRoaXMuX3hQb3NpdGlvbj0iY2VudGVyIix0aGlzfWNlbnRlclZlcnRpY2FsbHkodD0iIil7cmV0dXJuIHRoaXMudG9wKHQpLHRoaXMuX2FsaWduSXRlbXM9ImNlbnRlciIsdGhpc31hcHBseSgpe2lmKCF0aGlzLl9vdmVybGF5UmVmfHwhdGhpcy5fb3ZlcmxheVJlZi5oYXNBdHRhY2hlZCgpKXJldHVybjtsZXQgdD10aGlzLl9vdmVybGF5UmVmLm92ZXJsYXlFbGVtZW50LnN0eWxlLGU9dGhpcy5fb3ZlcmxheVJlZi5ob3N0RWxlbWVudC5zdHlsZSxpPXRoaXMuX292ZXJsYXlSZWYuZ2V0Q29uZmlnKCkse3dpZHRoOnIsaGVpZ2h0Om8sbWF4V2lkdGg6cyxtYXhIZWlnaHQ6YX09aSxsPSEoIjEwMCUiIT09ciYmIjEwMHZ3IiE9PXJ8fHMmJiIxMDAlIiE9PXMmJiIxMDB2dyIhPT1zKSxjPSEoIjEwMCUiIT09byYmIjEwMHZoIiE9PW98fGEmJiIxMDAlIiE9PWEmJiIxMDB2aCIhPT1hKSx1PXRoaXMuX3hQb3NpdGlvbixkPXRoaXMuX3hPZmZzZXQscD0icnRsIj09PXRoaXMuX292ZXJsYXlSZWYuZ2V0Q29uZmlnKCkuZGlyZWN0aW9uLGg9IiIsZj0iIixtPSIiO2w/bT0iZmxleC1zdGFydCI6ImNlbnRlciI9PT11PyhtPSJjZW50ZXIiLHA/Zj1kOmg9ZCk6cD8ibGVmdCI9PT11fHwiZW5kIj09PXU/KG09ImZsZXgtZW5kIixoPWQpOigicmlnaHQiPT09dXx8InN0YXJ0Ij09PXUpJiYobT0iZmxleC1zdGFydCIsZj1kKToibGVmdCI9PT11fHwic3RhcnQiPT09dT8obT0iZmxleC1zdGFydCIsaD1kKTooInJpZ2h0Ij09PXV8fCJlbmQiPT09dSkmJihtPSJmbGV4LWVuZCIsZj1kKSx0LnBvc2l0aW9uPXRoaXMuX2Nzc1Bvc2l0aW9uLHQubWFyZ2luTGVmdD1sPyIwIjpoLHQubWFyZ2luVG9wPWM/IjAiOnRoaXMuX3RvcE9mZnNldCx0Lm1hcmdpbkJvdHRvbT10aGlzLl9ib3R0b21PZmZzZXQsdC5tYXJnaW5SaWdodD1sPyIwIjpmLGUuanVzdGlmeUNvbnRlbnQ9bSxlLmFsaWduSXRlbXM9Yz8iZmxleC1zdGFydCI6dGhpcy5fYWxpZ25JdGVtc31kaXNwb3NlKCl7aWYodGhpcy5faXNEaXNwb3NlZHx8IXRoaXMuX292ZXJsYXlSZWYpcmV0dXJuO2xldCB0PXRoaXMuX292ZXJsYXlSZWYub3ZlcmxheUVsZW1lbnQuc3R5bGUsZT10aGlzLl9vdmVybGF5UmVmLmhvc3RFbGVtZW50LGk9ZS5zdHlsZTtlLmNsYXNzTGlzdC5yZW1vdmUoYnRlKSxpLmp1c3RpZnlDb250ZW50PWkuYWxpZ25JdGVtcz10Lm1hcmdpblRvcD10Lm1hcmdpbkJvdHRvbT10Lm1hcmdpbkxlZnQ9dC5tYXJnaW5SaWdodD10LnBvc2l0aW9uPSIiLHRoaXMuX292ZXJsYXlSZWY9bnVsbCx0aGlzLl9pc0Rpc3Bvc2VkPSEwfX19ZmxleGlibGVDb25uZWN0ZWRUbyhlKXtyZXR1cm4gbmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIsbyl7dGhpcy5fdmlld3BvcnRSdWxlcj1lLHRoaXMuX2RvY3VtZW50PWksdGhpcy5fcGxhdGZvcm09cix0aGlzLl9vdmVybGF5Q29udGFpbmVyPW8sdGhpcy5fbGFzdEJvdW5kaW5nQm94U2l6ZT17d2lkdGg6MCxoZWlnaHQ6MH0sdGhpcy5faXNQdXNoZWQ9ITEsdGhpcy5fY2FuUHVzaD0hMCx0aGlzLl9ncm93QWZ0ZXJPcGVuPSExLHRoaXMuX2hhc0ZsZXhpYmxlRGltZW5zaW9ucz0hMCx0aGlzLl9wb3NpdGlvbkxvY2tlZD0hMSx0aGlzLl92aWV3cG9ydE1hcmdpbj0wLHRoaXMuX3Njcm9sbGFibGVzPVtdLHRoaXMuX3ByZWZlcnJlZFBvc2l0aW9ucz1bXSx0aGlzLl9wb3NpdGlvbkNoYW5nZXM9bmV3IGtlLHRoaXMuX3Jlc2l6ZVN1YnNjcmlwdGlvbj1Tbi5FTVBUWSx0aGlzLl9vZmZzZXRYPTAsdGhpcy5fb2Zmc2V0WT0wLHRoaXMuX2FwcGxpZWRQYW5lbENsYXNzZXM9W10sdGhpcy5wb3NpdGlvbkNoYW5nZXM9dGhpcy5fcG9zaXRpb25DaGFuZ2VzLHRoaXMuc2V0T3JpZ2luKHQpfWdldCBwb3NpdGlvbnMoKXtyZXR1cm4gdGhpcy5fcHJlZmVycmVkUG9zaXRpb25zfWF0dGFjaCh0KXt0aGlzLl92YWxpZGF0ZVBvc2l0aW9ucygpLHQuaG9zdEVsZW1lbnQuY2xhc3NMaXN0LmFkZChfdGUpLHRoaXMuX292ZXJsYXlSZWY9dCx0aGlzLl9ib3VuZGluZ0JveD10Lmhvc3RFbGVtZW50LHRoaXMuX3BhbmU9dC5vdmVybGF5RWxlbWVudCx0aGlzLl9pc0Rpc3Bvc2VkPSExLHRoaXMuX2lzSW5pdGlhbFJlbmRlcj0hMCx0aGlzLl9sYXN0UG9zaXRpb249bnVsbCx0aGlzLl9yZXNpemVTdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKSx0aGlzLl9yZXNpemVTdWJzY3JpcHRpb249dGhpcy5fdmlld3BvcnRSdWxlci5jaGFuZ2UoKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9pc0luaXRpYWxSZW5kZXI9ITAsdGhpcy5hcHBseSgpfSl9YXBwbHkoKXtpZih0aGlzLl9pc0Rpc3Bvc2VkfHwhdGhpcy5fcGxhdGZvcm0uaXNCcm93c2VyKXJldHVybjtpZighdGhpcy5faXNJbml0aWFsUmVuZGVyJiZ0aGlzLl9wb3NpdGlvbkxvY2tlZCYmdGhpcy5fbGFzdFBvc2l0aW9uKXJldHVybiB2b2lkIHRoaXMucmVhcHBseUxhc3RQb3NpdGlvbigpO3RoaXMuX2NsZWFyUGFuZWxDbGFzc2VzKCksdGhpcy5fcmVzZXRPdmVybGF5RWxlbWVudFN0eWxlcygpLHRoaXMuX3Jlc2V0Qm91bmRpbmdCb3hTdHlsZXMoKSx0aGlzLl92aWV3cG9ydFJlY3Q9dGhpcy5fZ2V0TmFycm93ZWRWaWV3cG9ydFJlY3QoKSx0aGlzLl9vcmlnaW5SZWN0PXRoaXMuX2dldE9yaWdpblJlY3QoKSx0aGlzLl9vdmVybGF5UmVjdD10aGlzLl9wYW5lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLHRoaXMuX2NvbnRhaW5lclJlY3Q9dGhpcy5fb3ZlcmxheUNvbnRhaW5lci5nZXRDb250YWluZXJFbGVtZW50KCkuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7bGV0IHMsdD10aGlzLl9vcmlnaW5SZWN0LGU9dGhpcy5fb3ZlcmxheVJlY3QsaT10aGlzLl92aWV3cG9ydFJlY3Qscj10aGlzLl9jb250YWluZXJSZWN0LG89W107Zm9yKGxldCBhIG9mIHRoaXMuX3ByZWZlcnJlZFBvc2l0aW9ucyl7bGV0IGw9dGhpcy5fZ2V0T3JpZ2luUG9pbnQodCxyLGEpLGM9dGhpcy5fZ2V0T3ZlcmxheVBvaW50KGwsZSxhKSx1PXRoaXMuX2dldE92ZXJsYXlGaXQoYyxlLGksYSk7aWYodS5pc0NvbXBsZXRlbHlXaXRoaW5WaWV3cG9ydClyZXR1cm4gdGhpcy5faXNQdXNoZWQ9ITEsdm9pZCB0aGlzLl9hcHBseVBvc2l0aW9uKGEsbCk7dGhpcy5fY2FuRml0V2l0aEZsZXhpYmxlRGltZW5zaW9ucyh1LGMsaSk/by5wdXNoKHtwb3NpdGlvbjphLG9yaWdpbjpsLG92ZXJsYXlSZWN0OmUsYm91bmRpbmdCb3hSZWN0OnRoaXMuX2NhbGN1bGF0ZUJvdW5kaW5nQm94UmVjdChsLGEpfSk6KCFzfHxzLm92ZXJsYXlGaXQudmlzaWJsZUFyZWE8dS52aXNpYmxlQXJlYSkmJihzPXtvdmVybGF5Rml0OnUsb3ZlcmxheVBvaW50OmMsb3JpZ2luUG9pbnQ6bCxwb3NpdGlvbjphLG92ZXJsYXlSZWN0OmV9KX1pZihvLmxlbmd0aCl7bGV0IGE9bnVsbCxsPS0xO2ZvcihsZXQgYyBvZiBvKXtsZXQgdT1jLmJvdW5kaW5nQm94UmVjdC53aWR0aCpjLmJvdW5kaW5nQm94UmVjdC5oZWlnaHQqKGMucG9zaXRpb24ud2VpZ2h0fHwxKTt1PmwmJihsPXUsYT1jKX1yZXR1cm4gdGhpcy5faXNQdXNoZWQ9ITEsdm9pZCB0aGlzLl9hcHBseVBvc2l0aW9uKGEucG9zaXRpb24sYS5vcmlnaW4pfWlmKHRoaXMuX2NhblB1c2gpcmV0dXJuIHRoaXMuX2lzUHVzaGVkPSEwLHZvaWQgdGhpcy5fYXBwbHlQb3NpdGlvbihzLnBvc2l0aW9uLHMub3JpZ2luUG9pbnQpO3RoaXMuX2FwcGx5UG9zaXRpb24ocy5wb3NpdGlvbixzLm9yaWdpblBvaW50KX1kZXRhY2goKXt0aGlzLl9jbGVhclBhbmVsQ2xhc3NlcygpLHRoaXMuX2xhc3RQb3NpdGlvbj1udWxsLHRoaXMuX3ByZXZpb3VzUHVzaEFtb3VudD1udWxsLHRoaXMuX3Jlc2l6ZVN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpfWRpc3Bvc2UoKXt0aGlzLl9pc0Rpc3Bvc2VkfHwodGhpcy5fYm91bmRpbmdCb3gmJnRnKHRoaXMuX2JvdW5kaW5nQm94LnN0eWxlLHt0b3A6IiIsbGVmdDoiIixyaWdodDoiIixib3R0b206IiIsaGVpZ2h0OiIiLHdpZHRoOiIiLGFsaWduSXRlbXM6IiIsanVzdGlmeUNvbnRlbnQ6IiJ9KSx0aGlzLl9wYW5lJiZ0aGlzLl9yZXNldE92ZXJsYXlFbGVtZW50U3R5bGVzKCksdGhpcy5fb3ZlcmxheVJlZiYmdGhpcy5fb3ZlcmxheVJlZi5ob3N0RWxlbWVudC5jbGFzc0xpc3QucmVtb3ZlKF90ZSksdGhpcy5kZXRhY2goKSx0aGlzLl9wb3NpdGlvbkNoYW5nZXMuY29tcGxldGUoKSx0aGlzLl9vdmVybGF5UmVmPXRoaXMuX2JvdW5kaW5nQm94PW51bGwsdGhpcy5faXNEaXNwb3NlZD0hMCl9cmVhcHBseUxhc3RQb3NpdGlvbigpe2lmKHRoaXMuX2lzRGlzcG9zZWR8fCF0aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXIpcmV0dXJuO2xldCB0PXRoaXMuX2xhc3RQb3NpdGlvbjtpZih0KXt0aGlzLl9vcmlnaW5SZWN0PXRoaXMuX2dldE9yaWdpblJlY3QoKSx0aGlzLl9vdmVybGF5UmVjdD10aGlzLl9wYW5lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLHRoaXMuX3ZpZXdwb3J0UmVjdD10aGlzLl9nZXROYXJyb3dlZFZpZXdwb3J0UmVjdCgpLHRoaXMuX2NvbnRhaW5lclJlY3Q9dGhpcy5fb3ZlcmxheUNvbnRhaW5lci5nZXRDb250YWluZXJFbGVtZW50KCkuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7bGV0IGU9dGhpcy5fZ2V0T3JpZ2luUG9pbnQodGhpcy5fb3JpZ2luUmVjdCx0aGlzLl9jb250YWluZXJSZWN0LHQpO3RoaXMuX2FwcGx5UG9zaXRpb24odCxlKX1lbHNlIHRoaXMuYXBwbHkoKX13aXRoU2Nyb2xsYWJsZUNvbnRhaW5lcnModCl7cmV0dXJuIHRoaXMuX3Njcm9sbGFibGVzPXQsdGhpc313aXRoUG9zaXRpb25zKHQpe3JldHVybiB0aGlzLl9wcmVmZXJyZWRQb3NpdGlvbnM9dCwtMT09PXQuaW5kZXhPZih0aGlzLl9sYXN0UG9zaXRpb24pJiYodGhpcy5fbGFzdFBvc2l0aW9uPW51bGwpLHRoaXMuX3ZhbGlkYXRlUG9zaXRpb25zKCksdGhpc313aXRoVmlld3BvcnRNYXJnaW4odCl7cmV0dXJuIHRoaXMuX3ZpZXdwb3J0TWFyZ2luPXQsdGhpc313aXRoRmxleGlibGVEaW1lbnNpb25zKHQ9ITApe3JldHVybiB0aGlzLl9oYXNGbGV4aWJsZURpbWVuc2lvbnM9dCx0aGlzfXdpdGhHcm93QWZ0ZXJPcGVuKHQ9ITApe3JldHVybiB0aGlzLl9ncm93QWZ0ZXJPcGVuPXQsdGhpc313aXRoUHVzaCh0PSEwKXtyZXR1cm4gdGhpcy5fY2FuUHVzaD10LHRoaXN9d2l0aExvY2tlZFBvc2l0aW9uKHQ9ITApe3JldHVybiB0aGlzLl9wb3NpdGlvbkxvY2tlZD10LHRoaXN9c2V0T3JpZ2luKHQpe3JldHVybiB0aGlzLl9vcmlnaW49dCx0aGlzfXdpdGhEZWZhdWx0T2Zmc2V0WCh0KXtyZXR1cm4gdGhpcy5fb2Zmc2V0WD10LHRoaXN9d2l0aERlZmF1bHRPZmZzZXRZKHQpe3JldHVybiB0aGlzLl9vZmZzZXRZPXQsdGhpc313aXRoVHJhbnNmb3JtT3JpZ2luT24odCl7cmV0dXJuIHRoaXMuX3RyYW5zZm9ybU9yaWdpblNlbGVjdG9yPXQsdGhpc31fZ2V0T3JpZ2luUG9pbnQodCxlLGkpe2xldCByLG87aWYoImNlbnRlciI9PWkub3JpZ2luWClyPXQubGVmdCt0LndpZHRoLzI7ZWxzZXtsZXQgcz10aGlzLl9pc1J0bCgpP3QucmlnaHQ6dC5sZWZ0LGE9dGhpcy5faXNSdGwoKT90LmxlZnQ6dC5yaWdodDtyPSJzdGFydCI9PWkub3JpZ2luWD9zOmF9cmV0dXJuIGUubGVmdDwwJiYoci09ZS5sZWZ0KSxvPSJjZW50ZXIiPT1pLm9yaWdpblk/dC50b3ArdC5oZWlnaHQvMjoidG9wIj09aS5vcmlnaW5ZP3QudG9wOnQuYm90dG9tLGUudG9wPDAmJihvLT1lLnRvcCkse3g6cix5Om99fV9nZXRPdmVybGF5UG9pbnQodCxlLGkpe2xldCByLG87cmV0dXJuIHI9ImNlbnRlciI9PWkub3ZlcmxheVg/LWUud2lkdGgvMjoic3RhcnQiPT09aS5vdmVybGF5WD90aGlzLl9pc1J0bCgpPy1lLndpZHRoOjA6dGhpcy5faXNSdGwoKT8wOi1lLndpZHRoLG89ImNlbnRlciI9PWkub3ZlcmxheVk/LWUuaGVpZ2h0LzI6InRvcCI9PWkub3ZlcmxheVk/MDotZS5oZWlnaHQse3g6dC54K3IseTp0Lnkrb319X2dldE92ZXJsYXlGaXQodCxlLGkscil7bGV0IG89eXRlKGUpLHt4OnMseTphfT10LGw9dGhpcy5fZ2V0T2Zmc2V0KHIsIngiKSxjPXRoaXMuX2dldE9mZnNldChyLCJ5Iik7bCYmKHMrPWwpLGMmJihhKz1jKTtsZXQgcD0wLWEsaD1hK28uaGVpZ2h0LWkuaGVpZ2h0LGY9dGhpcy5fc3VidHJhY3RPdmVyZmxvd3Moby53aWR0aCwwLXMscytvLndpZHRoLWkud2lkdGgpLG09dGhpcy5fc3VidHJhY3RPdmVyZmxvd3Moby5oZWlnaHQscCxoKSx4PWYqbTtyZXR1cm57dmlzaWJsZUFyZWE6eCxpc0NvbXBsZXRlbHlXaXRoaW5WaWV3cG9ydDpvLndpZHRoKm8uaGVpZ2h0PT09eCxmaXRzSW5WaWV3cG9ydFZlcnRpY2FsbHk6bT09PW8uaGVpZ2h0LGZpdHNJblZpZXdwb3J0SG9yaXpvbnRhbGx5OmY9PW8ud2lkdGh9fV9jYW5GaXRXaXRoRmxleGlibGVEaW1lbnNpb25zKHQsZSxpKXtpZih0aGlzLl9oYXNGbGV4aWJsZURpbWVuc2lvbnMpe2xldCByPWkuYm90dG9tLWUueSxvPWkucmlnaHQtZS54LHM9dnRlKHRoaXMuX292ZXJsYXlSZWYuZ2V0Q29uZmlnKCkubWluSGVpZ2h0KSxhPXZ0ZSh0aGlzLl9vdmVybGF5UmVmLmdldENvbmZpZygpLm1pbldpZHRoKSxjPXQuZml0c0luVmlld3BvcnRIb3Jpem9udGFsbHl8fG51bGwhPWEmJmE8PW87cmV0dXJuKHQuZml0c0luVmlld3BvcnRWZXJ0aWNhbGx5fHxudWxsIT1zJiZzPD1yKSYmY31yZXR1cm4hMX1fcHVzaE92ZXJsYXlPblNjcmVlbih0LGUsaSl7aWYodGhpcy5fcHJldmlvdXNQdXNoQW1vdW50JiZ0aGlzLl9wb3NpdGlvbkxvY2tlZClyZXR1cm57eDp0LngrdGhpcy5fcHJldmlvdXNQdXNoQW1vdW50LngseTp0LnkrdGhpcy5fcHJldmlvdXNQdXNoQW1vdW50Lnl9O2xldCByPXl0ZShlKSxvPXRoaXMuX3ZpZXdwb3J0UmVjdCxzPU1hdGgubWF4KHQueCtyLndpZHRoLW8ud2lkdGgsMCksYT1NYXRoLm1heCh0Lnkrci5oZWlnaHQtby5oZWlnaHQsMCksbD1NYXRoLm1heChvLnRvcC1pLnRvcC10LnksMCksYz1NYXRoLm1heChvLmxlZnQtaS5sZWZ0LXQueCwwKSx1PTAsZD0wO3JldHVybiB1PXIud2lkdGg8PW8ud2lkdGg/Y3x8LXM6dC54PHRoaXMuX3ZpZXdwb3J0TWFyZ2luP28ubGVmdC1pLmxlZnQtdC54OjAsZD1yLmhlaWdodDw9by5oZWlnaHQ/bHx8LWE6dC55PHRoaXMuX3ZpZXdwb3J0TWFyZ2luP28udG9wLWkudG9wLXQueTowLHRoaXMuX3ByZXZpb3VzUHVzaEFtb3VudD17eDp1LHk6ZH0se3g6dC54K3UseTp0LnkrZH19X2FwcGx5UG9zaXRpb24odCxlKXtpZih0aGlzLl9zZXRUcmFuc2Zvcm1PcmlnaW4odCksdGhpcy5fc2V0T3ZlcmxheUVsZW1lbnRTdHlsZXMoZSx0KSx0aGlzLl9zZXRCb3VuZGluZ0JveFN0eWxlcyhlLHQpLHQucGFuZWxDbGFzcyYmdGhpcy5fYWRkUGFuZWxDbGFzc2VzKHQucGFuZWxDbGFzcyksdGhpcy5fbGFzdFBvc2l0aW9uPXQsdGhpcy5fcG9zaXRpb25DaGFuZ2VzLm9ic2VydmVycy5sZW5ndGgpe2xldCBpPXRoaXMuX2dldFNjcm9sbFZpc2liaWxpdHkoKSxyPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuY29ubmVjdGlvblBhaXI9dCx0aGlzLnNjcm9sbGFibGVWaWV3UHJvcGVydGllcz1lfX0odCxpKTt0aGlzLl9wb3NpdGlvbkNoYW5nZXMubmV4dChyKX10aGlzLl9pc0luaXRpYWxSZW5kZXI9ITF9X3NldFRyYW5zZm9ybU9yaWdpbih0KXtpZighdGhpcy5fdHJhbnNmb3JtT3JpZ2luU2VsZWN0b3IpcmV0dXJuO2xldCBpLGU9dGhpcy5fYm91bmRpbmdCb3gucXVlcnlTZWxlY3RvckFsbCh0aGlzLl90cmFuc2Zvcm1PcmlnaW5TZWxlY3Rvcikscj10Lm92ZXJsYXlZO2k9ImNlbnRlciI9PT10Lm92ZXJsYXlYPyJjZW50ZXIiOnRoaXMuX2lzUnRsKCk/InN0YXJ0Ij09PXQub3ZlcmxheVg/InJpZ2h0IjoibGVmdCI6InN0YXJ0Ij09PXQub3ZlcmxheVg/ImxlZnQiOiJyaWdodCI7Zm9yKGxldCBvPTA7bzxlLmxlbmd0aDtvKyspZVtvXS5zdHlsZS50cmFuc2Zvcm1PcmlnaW49YCR7aX0gJHtyfWB9X2NhbGN1bGF0ZUJvdW5kaW5nQm94UmVjdCh0LGUpe2xldCBvLHMsYSx1LGQscCxpPXRoaXMuX3ZpZXdwb3J0UmVjdCxyPXRoaXMuX2lzUnRsKCk7aWYoInRvcCI9PT1lLm92ZXJsYXlZKXM9dC55LG89aS5oZWlnaHQtcyt0aGlzLl92aWV3cG9ydE1hcmdpbjtlbHNlIGlmKCJib3R0b20iPT09ZS5vdmVybGF5WSlhPWkuaGVpZ2h0LXQueSsyKnRoaXMuX3ZpZXdwb3J0TWFyZ2luLG89aS5oZWlnaHQtYSt0aGlzLl92aWV3cG9ydE1hcmdpbjtlbHNle2xldCBoPU1hdGgubWluKGkuYm90dG9tLXQueStpLnRvcCx0LnkpLGY9dGhpcy5fbGFzdEJvdW5kaW5nQm94U2l6ZS5oZWlnaHQ7bz0yKmgscz10LnktaCxvPmYmJiF0aGlzLl9pc0luaXRpYWxSZW5kZXImJiF0aGlzLl9ncm93QWZ0ZXJPcGVuJiYocz10LnktZi8yKX1pZigiZW5kIj09PWUub3ZlcmxheVgmJiFyfHwic3RhcnQiPT09ZS5vdmVybGF5WCYmcilwPWkud2lkdGgtdC54K3RoaXMuX3ZpZXdwb3J0TWFyZ2luLHU9dC54LXRoaXMuX3ZpZXdwb3J0TWFyZ2luO2Vsc2UgaWYoInN0YXJ0Ij09PWUub3ZlcmxheVgmJiFyfHwiZW5kIj09PWUub3ZlcmxheVgmJnIpZD10LngsdT1pLnJpZ2h0LXQueDtlbHNle2xldCBoPU1hdGgubWluKGkucmlnaHQtdC54K2kubGVmdCx0LngpLGY9dGhpcy5fbGFzdEJvdW5kaW5nQm94U2l6ZS53aWR0aDt1PTIqaCxkPXQueC1oLHU+ZiYmIXRoaXMuX2lzSW5pdGlhbFJlbmRlciYmIXRoaXMuX2dyb3dBZnRlck9wZW4mJihkPXQueC1mLzIpfXJldHVybnt0b3A6cyxsZWZ0OmQsYm90dG9tOmEscmlnaHQ6cCx3aWR0aDp1LGhlaWdodDpvfX1fc2V0Qm91bmRpbmdCb3hTdHlsZXModCxlKXtsZXQgaT10aGlzLl9jYWxjdWxhdGVCb3VuZGluZ0JveFJlY3QodCxlKTshdGhpcy5faXNJbml0aWFsUmVuZGVyJiYhdGhpcy5fZ3Jvd0FmdGVyT3BlbiYmKGkuaGVpZ2h0PU1hdGgubWluKGkuaGVpZ2h0LHRoaXMuX2xhc3RCb3VuZGluZ0JveFNpemUuaGVpZ2h0KSxpLndpZHRoPU1hdGgubWluKGkud2lkdGgsdGhpcy5fbGFzdEJvdW5kaW5nQm94U2l6ZS53aWR0aCkpO2xldCByPXt9O2lmKHRoaXMuX2hhc0V4YWN0UG9zaXRpb24oKSlyLnRvcD1yLmxlZnQ9IjAiLHIuYm90dG9tPXIucmlnaHQ9ci5tYXhIZWlnaHQ9ci5tYXhXaWR0aD0iIixyLndpZHRoPXIuaGVpZ2h0PSIxMDAlIjtlbHNle2xldCBvPXRoaXMuX292ZXJsYXlSZWYuZ2V0Q29uZmlnKCkubWF4SGVpZ2h0LHM9dGhpcy5fb3ZlcmxheVJlZi5nZXRDb25maWcoKS5tYXhXaWR0aDtyLmhlaWdodD15byhpLmhlaWdodCksci50b3A9eW8oaS50b3ApLHIuYm90dG9tPXlvKGkuYm90dG9tKSxyLndpZHRoPXlvKGkud2lkdGgpLHIubGVmdD15byhpLmxlZnQpLHIucmlnaHQ9eW8oaS5yaWdodCksci5hbGlnbkl0ZW1zPSJjZW50ZXIiPT09ZS5vdmVybGF5WD8iY2VudGVyIjoiZW5kIj09PWUub3ZlcmxheVg/ImZsZXgtZW5kIjoiZmxleC1zdGFydCIsci5qdXN0aWZ5Q29udGVudD0iY2VudGVyIj09PWUub3ZlcmxheVk/ImNlbnRlciI6ImJvdHRvbSI9PT1lLm92ZXJsYXlZPyJmbGV4LWVuZCI6ImZsZXgtc3RhcnQiLG8mJihyLm1heEhlaWdodD15byhvKSkscyYmKHIubWF4V2lkdGg9eW8ocykpfXRoaXMuX2xhc3RCb3VuZGluZ0JveFNpemU9aSx0Zyh0aGlzLl9ib3VuZGluZ0JveC5zdHlsZSxyKX1fcmVzZXRCb3VuZGluZ0JveFN0eWxlcygpe3RnKHRoaXMuX2JvdW5kaW5nQm94LnN0eWxlLHt0b3A6IjAiLGxlZnQ6IjAiLHJpZ2h0OiIwIixib3R0b206IjAiLGhlaWdodDoiIix3aWR0aDoiIixhbGlnbkl0ZW1zOiIiLGp1c3RpZnlDb250ZW50OiIifSl9X3Jlc2V0T3ZlcmxheUVsZW1lbnRTdHlsZXMoKXt0Zyh0aGlzLl9wYW5lLnN0eWxlLHt0b3A6IiIsbGVmdDoiIixib3R0b206IiIscmlnaHQ6IiIscG9zaXRpb246IiIsdHJhbnNmb3JtOiIifSl9X3NldE92ZXJsYXlFbGVtZW50U3R5bGVzKHQsZSl7bGV0IGk9e30scj10aGlzLl9oYXNFeGFjdFBvc2l0aW9uKCksbz10aGlzLl9oYXNGbGV4aWJsZURpbWVuc2lvbnMscz10aGlzLl9vdmVybGF5UmVmLmdldENvbmZpZygpO2lmKHIpe2xldCB1PXRoaXMuX3ZpZXdwb3J0UnVsZXIuZ2V0Vmlld3BvcnRTY3JvbGxQb3NpdGlvbigpO3RnKGksdGhpcy5fZ2V0RXhhY3RPdmVybGF5WShlLHQsdSkpLHRnKGksdGhpcy5fZ2V0RXhhY3RPdmVybGF5WChlLHQsdSkpfWVsc2UgaS5wb3NpdGlvbj0ic3RhdGljIjtsZXQgYT0iIixsPXRoaXMuX2dldE9mZnNldChlLCJ4IiksYz10aGlzLl9nZXRPZmZzZXQoZSwieSIpO2wmJihhKz1gdHJhbnNsYXRlWCgke2x9cHgpIGApLGMmJihhKz1gdHJhbnNsYXRlWSgke2N9cHgpYCksaS50cmFuc2Zvcm09YS50cmltKCkscy5tYXhIZWlnaHQmJihyP2kubWF4SGVpZ2h0PXlvKHMubWF4SGVpZ2h0KTpvJiYoaS5tYXhIZWlnaHQ9IiIpKSxzLm1heFdpZHRoJiYocj9pLm1heFdpZHRoPXlvKHMubWF4V2lkdGgpOm8mJihpLm1heFdpZHRoPSIiKSksdGcodGhpcy5fcGFuZS5zdHlsZSxpKX1fZ2V0RXhhY3RPdmVybGF5WSh0LGUsaSl7bGV0IHI9e3RvcDoiIixib3R0b206IiJ9LG89dGhpcy5fZ2V0T3ZlcmxheVBvaW50KGUsdGhpcy5fb3ZlcmxheVJlY3QsdCk7cmV0dXJuIHRoaXMuX2lzUHVzaGVkJiYobz10aGlzLl9wdXNoT3ZlcmxheU9uU2NyZWVuKG8sdGhpcy5fb3ZlcmxheVJlY3QsaSkpLCJib3R0b20iPT09dC5vdmVybGF5WT9yLmJvdHRvbT10aGlzLl9kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuY2xpZW50SGVpZ2h0LShvLnkrdGhpcy5fb3ZlcmxheVJlY3QuaGVpZ2h0KSsicHgiOnIudG9wPXlvKG8ueSkscn1fZ2V0RXhhY3RPdmVybGF5WCh0LGUsaSl7bGV0IHMscj17bGVmdDoiIixyaWdodDoiIn0sbz10aGlzLl9nZXRPdmVybGF5UG9pbnQoZSx0aGlzLl9vdmVybGF5UmVjdCx0KTtyZXR1cm4gdGhpcy5faXNQdXNoZWQmJihvPXRoaXMuX3B1c2hPdmVybGF5T25TY3JlZW4obyx0aGlzLl9vdmVybGF5UmVjdCxpKSkscz10aGlzLl9pc1J0bCgpPyJlbmQiPT09dC5vdmVybGF5WD8ibGVmdCI6InJpZ2h0IjoiZW5kIj09PXQub3ZlcmxheVg/InJpZ2h0IjoibGVmdCIsInJpZ2h0Ij09PXM/ci5yaWdodD10aGlzLl9kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuY2xpZW50V2lkdGgtKG8ueCt0aGlzLl9vdmVybGF5UmVjdC53aWR0aCkrInB4IjpyLmxlZnQ9eW8oby54KSxyfV9nZXRTY3JvbGxWaXNpYmlsaXR5KCl7bGV0IHQ9dGhpcy5fZ2V0T3JpZ2luUmVjdCgpLGU9dGhpcy5fcGFuZS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxpPXRoaXMuX3Njcm9sbGFibGVzLm1hcChyPT5yLmdldEVsZW1lbnRSZWYoKS5uYXRpdmVFbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpKTtyZXR1cm57aXNPcmlnaW5DbGlwcGVkOmd0ZSh0LGkpLGlzT3JpZ2luT3V0c2lkZVZpZXc6SUgodCxpKSxpc092ZXJsYXlDbGlwcGVkOmd0ZShlLGkpLGlzT3ZlcmxheU91dHNpZGVWaWV3OklIKGUsaSl9fV9zdWJ0cmFjdE92ZXJmbG93cyh0LC4uLmUpe3JldHVybiBlLnJlZHVjZSgoaSxyKT0+aS1NYXRoLm1heChyLDApLHQpfV9nZXROYXJyb3dlZFZpZXdwb3J0UmVjdCgpe2xldCB0PXRoaXMuX2RvY3VtZW50LmRvY3VtZW50RWxlbWVudC5jbGllbnRXaWR0aCxlPXRoaXMuX2RvY3VtZW50LmRvY3VtZW50RWxlbWVudC5jbGllbnRIZWlnaHQsaT10aGlzLl92aWV3cG9ydFJ1bGVyLmdldFZpZXdwb3J0U2Nyb2xsUG9zaXRpb24oKTtyZXR1cm57dG9wOmkudG9wK3RoaXMuX3ZpZXdwb3J0TWFyZ2luLGxlZnQ6aS5sZWZ0K3RoaXMuX3ZpZXdwb3J0TWFyZ2luLHJpZ2h0OmkubGVmdCt0LXRoaXMuX3ZpZXdwb3J0TWFyZ2luLGJvdHRvbTppLnRvcCtlLXRoaXMuX3ZpZXdwb3J0TWFyZ2luLHdpZHRoOnQtMip0aGlzLl92aWV3cG9ydE1hcmdpbixoZWlnaHQ6ZS0yKnRoaXMuX3ZpZXdwb3J0TWFyZ2lufX1faXNSdGwoKXtyZXR1cm4icnRsIj09PXRoaXMuX292ZXJsYXlSZWYuZ2V0RGlyZWN0aW9uKCl9X2hhc0V4YWN0UG9zaXRpb24oKXtyZXR1cm4hdGhpcy5faGFzRmxleGlibGVEaW1lbnNpb25zfHx0aGlzLl9pc1B1c2hlZH1fZ2V0T2Zmc2V0KHQsZSl7cmV0dXJuIngiPT09ZT9udWxsPT10Lm9mZnNldFg/dGhpcy5fb2Zmc2V0WDp0Lm9mZnNldFg6bnVsbD09dC5vZmZzZXRZP3RoaXMuX29mZnNldFk6dC5vZmZzZXRZfV92YWxpZGF0ZVBvc2l0aW9ucygpe31fYWRkUGFuZWxDbGFzc2VzKHQpe3RoaXMuX3BhbmUmJnh2KHQpLmZvckVhY2goZT0+eyIiIT09ZSYmLTE9PT10aGlzLl9hcHBsaWVkUGFuZWxDbGFzc2VzLmluZGV4T2YoZSkmJih0aGlzLl9hcHBsaWVkUGFuZWxDbGFzc2VzLnB1c2goZSksdGhpcy5fcGFuZS5jbGFzc0xpc3QuYWRkKGUpKX0pfV9jbGVhclBhbmVsQ2xhc3Nlcygpe3RoaXMuX3BhbmUmJih0aGlzLl9hcHBsaWVkUGFuZWxDbGFzc2VzLmZvckVhY2godD0+e3RoaXMuX3BhbmUuY2xhc3NMaXN0LnJlbW92ZSh0KX0pLHRoaXMuX2FwcGxpZWRQYW5lbENsYXNzZXM9W10pfV9nZXRPcmlnaW5SZWN0KCl7bGV0IHQ9dGhpcy5fb3JpZ2luO2lmKHQgaW5zdGFuY2VvZiBSZSlyZXR1cm4gdC5uYXRpdmVFbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO2lmKHQgaW5zdGFuY2VvZiBFbGVtZW50KXJldHVybiB0LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO2xldCBlPXQud2lkdGh8fDAsaT10LmhlaWdodHx8MDtyZXR1cm57dG9wOnQueSxib3R0b206dC55K2ksbGVmdDp0LngscmlnaHQ6dC54K2UsaGVpZ2h0Omksd2lkdGg6ZX19fShlLHRoaXMuX3ZpZXdwb3J0UnVsZXIsdGhpcy5fZG9jdW1lbnQsdGhpcy5fcGxhdGZvcm0sdGhpcy5fb3ZlcmxheUNvbnRhaW5lcil9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooVmEpLGooSHQpLGoob2kpLGooUnYpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjoicm9vdCJ9KSxufSkoKSxiT2U9MCx0cj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhLGwsYyx1LGQscCxoKXt0aGlzLnNjcm9sbFN0cmF0ZWdpZXM9ZSx0aGlzLl9vdmVybGF5Q29udGFpbmVyPWksdGhpcy5fY29tcG9uZW50RmFjdG9yeVJlc29sdmVyPXIsdGhpcy5fcG9zaXRpb25CdWlsZGVyPW8sdGhpcy5fa2V5Ym9hcmREaXNwYXRjaGVyPXMsdGhpcy5faW5qZWN0b3I9YSx0aGlzLl9uZ1pvbmU9bCx0aGlzLl9kb2N1bWVudD1jLHRoaXMuX2RpcmVjdGlvbmFsaXR5PXUsdGhpcy5fbG9jYXRpb249ZCx0aGlzLl9vdXRzaWRlQ2xpY2tEaXNwYXRjaGVyPXAsdGhpcy5fYW5pbWF0aW9uc01vZHVsZVR5cGU9aH1jcmVhdGUoZSl7bGV0IGk9dGhpcy5fY3JlYXRlSG9zdEVsZW1lbnQoKSxyPXRoaXMuX2NyZWF0ZVBhbmVFbGVtZW50KGkpLG89dGhpcy5fY3JlYXRlUG9ydGFsT3V0bGV0KHIpLHM9bmV3IHNjKGUpO3JldHVybiBzLmRpcmVjdGlvbj1zLmRpcmVjdGlvbnx8dGhpcy5fZGlyZWN0aW9uYWxpdHkudmFsdWUsbmV3IGRkKG8saSxyLHMsdGhpcy5fbmdab25lLHRoaXMuX2tleWJvYXJkRGlzcGF0Y2hlcix0aGlzLl9kb2N1bWVudCx0aGlzLl9sb2NhdGlvbix0aGlzLl9vdXRzaWRlQ2xpY2tEaXNwYXRjaGVyLCJOb29wQW5pbWF0aW9ucyI9PT10aGlzLl9hbmltYXRpb25zTW9kdWxlVHlwZSl9cG9zaXRpb24oKXtyZXR1cm4gdGhpcy5fcG9zaXRpb25CdWlsZGVyfV9jcmVhdGVQYW5lRWxlbWVudChlKXtsZXQgaT10aGlzLl9kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTtyZXR1cm4gaS5pZD0iY2RrLW92ZXJsYXktIitiT2UrKyxpLmNsYXNzTGlzdC5hZGQoImNkay1vdmVybGF5LXBhbmUiKSxlLmFwcGVuZENoaWxkKGkpLGl9X2NyZWF0ZUhvc3RFbGVtZW50KCl7bGV0IGU9dGhpcy5fZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7cmV0dXJuIHRoaXMuX292ZXJsYXlDb250YWluZXIuZ2V0Q29udGFpbmVyRWxlbWVudCgpLmFwcGVuZENoaWxkKGUpLGV9X2NyZWF0ZVBvcnRhbE91dGxldChlKXtyZXR1cm4gdGhpcy5fYXBwUmVmfHwodGhpcy5fYXBwUmVmPXRoaXMuX2luamVjdG9yLmdldChJdSkpLG5ldyBhdyhlLHRoaXMuX2NvbXBvbmVudEZhY3RvcnlSZXNvbHZlcix0aGlzLl9hcHBSZWYsdGhpcy5faW5qZWN0b3IsdGhpcy5fZG9jdW1lbnQpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKG1PZSksaihSdiksaihncyksaih5T2UpLGooZ09lKSxqKFhuKSxqKF90KSxqKEh0KSxqKCRpKSxqKGlNKSxqKF9PZSksaihQaSw4KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCkseE9lPVt7b3JpZ2luWDoic3RhcnQiLG9yaWdpblk6ImJvdHRvbSIsb3ZlcmxheVg6InN0YXJ0IixvdmVybGF5WToidG9wIn0se29yaWdpblg6InN0YXJ0IixvcmlnaW5ZOiJ0b3AiLG92ZXJsYXlYOiJzdGFydCIsb3ZlcmxheVk6ImJvdHRvbSJ9LHtvcmlnaW5YOiJlbmQiLG9yaWdpblk6InRvcCIsb3ZlcmxheVg6ImVuZCIsb3ZlcmxheVk6ImJvdHRvbSJ9LHtvcmlnaW5YOiJlbmQiLG9yaWdpblk6ImJvdHRvbSIsb3ZlcmxheVg6ImVuZCIsb3ZlcmxheVk6InRvcCJ9XSxDdGU9bmV3IHBlKCJjZGstY29ubmVjdGVkLW92ZXJsYXktc2Nyb2xsLXN0cmF0ZWd5IiksaWc9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLmVsZW1lbnRSZWY9ZX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShSZSkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJjZGstb3ZlcmxheS1vcmlnaW4iLCIiXSxbIiIsIm92ZXJsYXktb3JpZ2luIiwiIl0sWyIiLCJjZGtPdmVybGF5T3JpZ2luIiwiIl1dLGV4cG9ydEFzOlsiY2RrT3ZlcmxheU9yaWdpbiJdfSksbn0pKCksUmg9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscixvLHMpe3RoaXMuX292ZXJsYXk9ZSx0aGlzLl9kaXI9cyx0aGlzLl9oYXNCYWNrZHJvcD0hMSx0aGlzLl9sb2NrUG9zaXRpb249ITEsdGhpcy5fZ3Jvd0FmdGVyT3Blbj0hMSx0aGlzLl9mbGV4aWJsZURpbWVuc2lvbnM9ITEsdGhpcy5fcHVzaD0hMSx0aGlzLl9iYWNrZHJvcFN1YnNjcmlwdGlvbj1Tbi5FTVBUWSx0aGlzLl9hdHRhY2hTdWJzY3JpcHRpb249U24uRU1QVFksdGhpcy5fZGV0YWNoU3Vic2NyaXB0aW9uPVNuLkVNUFRZLHRoaXMuX3Bvc2l0aW9uU3Vic2NyaXB0aW9uPVNuLkVNUFRZLHRoaXMudmlld3BvcnRNYXJnaW49MCx0aGlzLm9wZW49ITEsdGhpcy5kaXNhYmxlQ2xvc2U9ITEsdGhpcy5iYWNrZHJvcENsaWNrPW5ldyBHLHRoaXMucG9zaXRpb25DaGFuZ2U9bmV3IEcsdGhpcy5hdHRhY2g9bmV3IEcsdGhpcy5kZXRhY2g9bmV3IEcsdGhpcy5vdmVybGF5S2V5ZG93bj1uZXcgRyx0aGlzLm92ZXJsYXlPdXRzaWRlQ2xpY2s9bmV3IEcsdGhpcy5fdGVtcGxhdGVQb3J0YWw9bmV3IGtzKGksciksdGhpcy5fc2Nyb2xsU3RyYXRlZ3lGYWN0b3J5PW8sdGhpcy5zY3JvbGxTdHJhdGVneT10aGlzLl9zY3JvbGxTdHJhdGVneUZhY3RvcnkoKX1nZXQgb2Zmc2V0WCgpe3JldHVybiB0aGlzLl9vZmZzZXRYfXNldCBvZmZzZXRYKGUpe3RoaXMuX29mZnNldFg9ZSx0aGlzLl9wb3NpdGlvbiYmdGhpcy5fdXBkYXRlUG9zaXRpb25TdHJhdGVneSh0aGlzLl9wb3NpdGlvbil9Z2V0IG9mZnNldFkoKXtyZXR1cm4gdGhpcy5fb2Zmc2V0WX1zZXQgb2Zmc2V0WShlKXt0aGlzLl9vZmZzZXRZPWUsdGhpcy5fcG9zaXRpb24mJnRoaXMuX3VwZGF0ZVBvc2l0aW9uU3RyYXRlZ3kodGhpcy5fcG9zaXRpb24pfWdldCBoYXNCYWNrZHJvcCgpe3JldHVybiB0aGlzLl9oYXNCYWNrZHJvcH1zZXQgaGFzQmFja2Ryb3AoZSl7dGhpcy5faGFzQmFja2Ryb3A9UnQoZSl9Z2V0IGxvY2tQb3NpdGlvbigpe3JldHVybiB0aGlzLl9sb2NrUG9zaXRpb259c2V0IGxvY2tQb3NpdGlvbihlKXt0aGlzLl9sb2NrUG9zaXRpb249UnQoZSl9Z2V0IGZsZXhpYmxlRGltZW5zaW9ucygpe3JldHVybiB0aGlzLl9mbGV4aWJsZURpbWVuc2lvbnN9c2V0IGZsZXhpYmxlRGltZW5zaW9ucyhlKXt0aGlzLl9mbGV4aWJsZURpbWVuc2lvbnM9UnQoZSl9Z2V0IGdyb3dBZnRlck9wZW4oKXtyZXR1cm4gdGhpcy5fZ3Jvd0FmdGVyT3Blbn1zZXQgZ3Jvd0FmdGVyT3BlbihlKXt0aGlzLl9ncm93QWZ0ZXJPcGVuPVJ0KGUpfWdldCBwdXNoKCl7cmV0dXJuIHRoaXMuX3B1c2h9c2V0IHB1c2goZSl7dGhpcy5fcHVzaD1SdChlKX1nZXQgb3ZlcmxheVJlZigpe3JldHVybiB0aGlzLl9vdmVybGF5UmVmfWdldCBkaXIoKXtyZXR1cm4gdGhpcy5fZGlyP3RoaXMuX2Rpci52YWx1ZToibHRyIn1uZ09uRGVzdHJveSgpe3RoaXMuX2F0dGFjaFN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX2RldGFjaFN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX2JhY2tkcm9wU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5fcG9zaXRpb25TdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKSx0aGlzLl9vdmVybGF5UmVmJiZ0aGlzLl9vdmVybGF5UmVmLmRpc3Bvc2UoKX1uZ09uQ2hhbmdlcyhlKXt0aGlzLl9wb3NpdGlvbiYmKHRoaXMuX3VwZGF0ZVBvc2l0aW9uU3RyYXRlZ3kodGhpcy5fcG9zaXRpb24pLHRoaXMuX292ZXJsYXlSZWYudXBkYXRlU2l6ZSh7d2lkdGg6dGhpcy53aWR0aCxtaW5XaWR0aDp0aGlzLm1pbldpZHRoLGhlaWdodDp0aGlzLmhlaWdodCxtaW5IZWlnaHQ6dGhpcy5taW5IZWlnaHR9KSxlLm9yaWdpbiYmdGhpcy5vcGVuJiZ0aGlzLl9wb3NpdGlvbi5hcHBseSgpKSxlLm9wZW4mJih0aGlzLm9wZW4/dGhpcy5fYXR0YWNoT3ZlcmxheSgpOnRoaXMuX2RldGFjaE92ZXJsYXkoKSl9X2NyZWF0ZU92ZXJsYXkoKXsoIXRoaXMucG9zaXRpb25zfHwhdGhpcy5wb3NpdGlvbnMubGVuZ3RoKSYmKHRoaXMucG9zaXRpb25zPXhPZSk7bGV0IGU9dGhpcy5fb3ZlcmxheVJlZj10aGlzLl9vdmVybGF5LmNyZWF0ZSh0aGlzLl9idWlsZENvbmZpZygpKTt0aGlzLl9hdHRhY2hTdWJzY3JpcHRpb249ZS5hdHRhY2htZW50cygpLnN1YnNjcmliZSgoKT0+dGhpcy5hdHRhY2guZW1pdCgpKSx0aGlzLl9kZXRhY2hTdWJzY3JpcHRpb249ZS5kZXRhY2htZW50cygpLnN1YnNjcmliZSgoKT0+dGhpcy5kZXRhY2guZW1pdCgpKSxlLmtleWRvd25FdmVudHMoKS5zdWJzY3JpYmUoaT0+e3RoaXMub3ZlcmxheUtleWRvd24ubmV4dChpKSwyNz09PWkua2V5Q29kZSYmIXRoaXMuZGlzYWJsZUNsb3NlJiYha3IoaSkmJihpLnByZXZlbnREZWZhdWx0KCksdGhpcy5fZGV0YWNoT3ZlcmxheSgpKX0pLHRoaXMuX292ZXJsYXlSZWYub3V0c2lkZVBvaW50ZXJFdmVudHMoKS5zdWJzY3JpYmUoaT0+e3RoaXMub3ZlcmxheU91dHNpZGVDbGljay5uZXh0KGkpfSl9X2J1aWxkQ29uZmlnKCl7bGV0IGU9dGhpcy5fcG9zaXRpb249dGhpcy5wb3NpdGlvblN0cmF0ZWd5fHx0aGlzLl9jcmVhdGVQb3NpdGlvblN0cmF0ZWd5KCksaT1uZXcgc2Moe2RpcmVjdGlvbjp0aGlzLl9kaXIscG9zaXRpb25TdHJhdGVneTplLHNjcm9sbFN0cmF0ZWd5OnRoaXMuc2Nyb2xsU3RyYXRlZ3ksaGFzQmFja2Ryb3A6dGhpcy5oYXNCYWNrZHJvcH0pO3JldHVybih0aGlzLndpZHRofHwwPT09dGhpcy53aWR0aCkmJihpLndpZHRoPXRoaXMud2lkdGgpLCh0aGlzLmhlaWdodHx8MD09PXRoaXMuaGVpZ2h0KSYmKGkuaGVpZ2h0PXRoaXMuaGVpZ2h0KSwodGhpcy5taW5XaWR0aHx8MD09PXRoaXMubWluV2lkdGgpJiYoaS5taW5XaWR0aD10aGlzLm1pbldpZHRoKSwodGhpcy5taW5IZWlnaHR8fDA9PT10aGlzLm1pbkhlaWdodCkmJihpLm1pbkhlaWdodD10aGlzLm1pbkhlaWdodCksdGhpcy5iYWNrZHJvcENsYXNzJiYoaS5iYWNrZHJvcENsYXNzPXRoaXMuYmFja2Ryb3BDbGFzcyksdGhpcy5wYW5lbENsYXNzJiYoaS5wYW5lbENsYXNzPXRoaXMucGFuZWxDbGFzcyksaX1fdXBkYXRlUG9zaXRpb25TdHJhdGVneShlKXtsZXQgaT10aGlzLnBvc2l0aW9ucy5tYXAocj0+KHtvcmlnaW5YOnIub3JpZ2luWCxvcmlnaW5ZOnIub3JpZ2luWSxvdmVybGF5WDpyLm92ZXJsYXlYLG92ZXJsYXlZOnIub3ZlcmxheVksb2Zmc2V0WDpyLm9mZnNldFh8fHRoaXMub2Zmc2V0WCxvZmZzZXRZOnIub2Zmc2V0WXx8dGhpcy5vZmZzZXRZLHBhbmVsQ2xhc3M6ci5wYW5lbENsYXNzfHx2b2lkIDB9KSk7cmV0dXJuIGUuc2V0T3JpZ2luKHRoaXMuX2dldEZsZXhpYmxlQ29ubmVjdGVkUG9zaXRpb25TdHJhdGVneU9yaWdpbigpKS53aXRoUG9zaXRpb25zKGkpLndpdGhGbGV4aWJsZURpbWVuc2lvbnModGhpcy5mbGV4aWJsZURpbWVuc2lvbnMpLndpdGhQdXNoKHRoaXMucHVzaCkud2l0aEdyb3dBZnRlck9wZW4odGhpcy5ncm93QWZ0ZXJPcGVuKS53aXRoVmlld3BvcnRNYXJnaW4odGhpcy52aWV3cG9ydE1hcmdpbikud2l0aExvY2tlZFBvc2l0aW9uKHRoaXMubG9ja1Bvc2l0aW9uKS53aXRoVHJhbnNmb3JtT3JpZ2luT24odGhpcy50cmFuc2Zvcm1PcmlnaW5TZWxlY3Rvcil9X2NyZWF0ZVBvc2l0aW9uU3RyYXRlZ3koKXtsZXQgZT10aGlzLl9vdmVybGF5LnBvc2l0aW9uKCkuZmxleGlibGVDb25uZWN0ZWRUbyh0aGlzLl9nZXRGbGV4aWJsZUNvbm5lY3RlZFBvc2l0aW9uU3RyYXRlZ3lPcmlnaW4oKSk7cmV0dXJuIHRoaXMuX3VwZGF0ZVBvc2l0aW9uU3RyYXRlZ3koZSksZX1fZ2V0RmxleGlibGVDb25uZWN0ZWRQb3NpdGlvblN0cmF0ZWd5T3JpZ2luKCl7cmV0dXJuIHRoaXMub3JpZ2luIGluc3RhbmNlb2YgaWc/dGhpcy5vcmlnaW4uZWxlbWVudFJlZjp0aGlzLm9yaWdpbn1fYXR0YWNoT3ZlcmxheSgpe3RoaXMuX292ZXJsYXlSZWY/dGhpcy5fb3ZlcmxheVJlZi5nZXRDb25maWcoKS5oYXNCYWNrZHJvcD10aGlzLmhhc0JhY2tkcm9wOnRoaXMuX2NyZWF0ZU92ZXJsYXkoKSx0aGlzLl9vdmVybGF5UmVmLmhhc0F0dGFjaGVkKCl8fHRoaXMuX292ZXJsYXlSZWYuYXR0YWNoKHRoaXMuX3RlbXBsYXRlUG9ydGFsKSx0aGlzLmhhc0JhY2tkcm9wP3RoaXMuX2JhY2tkcm9wU3Vic2NyaXB0aW9uPXRoaXMuX292ZXJsYXlSZWYuYmFja2Ryb3BDbGljaygpLnN1YnNjcmliZShlPT57dGhpcy5iYWNrZHJvcENsaWNrLmVtaXQoZSl9KTp0aGlzLl9iYWNrZHJvcFN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX3Bvc2l0aW9uU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5wb3NpdGlvbkNoYW5nZS5vYnNlcnZlcnMubGVuZ3RoPjAmJih0aGlzLl9wb3NpdGlvblN1YnNjcmlwdGlvbj10aGlzLl9wb3NpdGlvbi5wb3NpdGlvbkNoYW5nZXMucGlwZShjeCgoKT0+dGhpcy5wb3NpdGlvbkNoYW5nZS5vYnNlcnZlcnMubGVuZ3RoPjApKS5zdWJzY3JpYmUoZT0+e3RoaXMucG9zaXRpb25DaGFuZ2UuZW1pdChlKSwwPT09dGhpcy5wb3NpdGlvbkNoYW5nZS5vYnNlcnZlcnMubGVuZ3RoJiZ0aGlzLl9wb3NpdGlvblN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpfSkpfV9kZXRhY2hPdmVybGF5KCl7dGhpcy5fb3ZlcmxheVJlZiYmdGhpcy5fb3ZlcmxheVJlZi5kZXRhY2goKSx0aGlzLl9iYWNrZHJvcFN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX3Bvc2l0aW9uU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0odHIpLE0oVmkpLE0oT2kpLE0oQ3RlKSxNKCRpLDgpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwiY2RrLWNvbm5lY3RlZC1vdmVybGF5IiwiIl0sWyIiLCJjb25uZWN0ZWQtb3ZlcmxheSIsIiJdLFsiIiwiY2RrQ29ubmVjdGVkT3ZlcmxheSIsIiJdXSxpbnB1dHM6e29yaWdpbjpbImNka0Nvbm5lY3RlZE92ZXJsYXlPcmlnaW4iLCJvcmlnaW4iXSxwb3NpdGlvbnM6WyJjZGtDb25uZWN0ZWRPdmVybGF5UG9zaXRpb25zIiwicG9zaXRpb25zIl0scG9zaXRpb25TdHJhdGVneTpbImNka0Nvbm5lY3RlZE92ZXJsYXlQb3NpdGlvblN0cmF0ZWd5IiwicG9zaXRpb25TdHJhdGVneSJdLG9mZnNldFg6WyJjZGtDb25uZWN0ZWRPdmVybGF5T2Zmc2V0WCIsIm9mZnNldFgiXSxvZmZzZXRZOlsiY2RrQ29ubmVjdGVkT3ZlcmxheU9mZnNldFkiLCJvZmZzZXRZIl0sd2lkdGg6WyJjZGtDb25uZWN0ZWRPdmVybGF5V2lkdGgiLCJ3aWR0aCJdLGhlaWdodDpbImNka0Nvbm5lY3RlZE92ZXJsYXlIZWlnaHQiLCJoZWlnaHQiXSxtaW5XaWR0aDpbImNka0Nvbm5lY3RlZE92ZXJsYXlNaW5XaWR0aCIsIm1pbldpZHRoIl0sbWluSGVpZ2h0OlsiY2RrQ29ubmVjdGVkT3ZlcmxheU1pbkhlaWdodCIsIm1pbkhlaWdodCJdLGJhY2tkcm9wQ2xhc3M6WyJjZGtDb25uZWN0ZWRPdmVybGF5QmFja2Ryb3BDbGFzcyIsImJhY2tkcm9wQ2xhc3MiXSxwYW5lbENsYXNzOlsiY2RrQ29ubmVjdGVkT3ZlcmxheVBhbmVsQ2xhc3MiLCJwYW5lbENsYXNzIl0sdmlld3BvcnRNYXJnaW46WyJjZGtDb25uZWN0ZWRPdmVybGF5Vmlld3BvcnRNYXJnaW4iLCJ2aWV3cG9ydE1hcmdpbiJdLHNjcm9sbFN0cmF0ZWd5OlsiY2RrQ29ubmVjdGVkT3ZlcmxheVNjcm9sbFN0cmF0ZWd5Iiwic2Nyb2xsU3RyYXRlZ3kiXSxvcGVuOlsiY2RrQ29ubmVjdGVkT3ZlcmxheU9wZW4iLCJvcGVuIl0sZGlzYWJsZUNsb3NlOlsiY2RrQ29ubmVjdGVkT3ZlcmxheURpc2FibGVDbG9zZSIsImRpc2FibGVDbG9zZSJdLHRyYW5zZm9ybU9yaWdpblNlbGVjdG9yOlsiY2RrQ29ubmVjdGVkT3ZlcmxheVRyYW5zZm9ybU9yaWdpbk9uIiwidHJhbnNmb3JtT3JpZ2luU2VsZWN0b3IiXSxoYXNCYWNrZHJvcDpbImNka0Nvbm5lY3RlZE92ZXJsYXlIYXNCYWNrZHJvcCIsImhhc0JhY2tkcm9wIl0sbG9ja1Bvc2l0aW9uOlsiY2RrQ29ubmVjdGVkT3ZlcmxheUxvY2tQb3NpdGlvbiIsImxvY2tQb3NpdGlvbiJdLGZsZXhpYmxlRGltZW5zaW9uczpbImNka0Nvbm5lY3RlZE92ZXJsYXlGbGV4aWJsZURpbWVuc2lvbnMiLCJmbGV4aWJsZURpbWVuc2lvbnMiXSxncm93QWZ0ZXJPcGVuOlsiY2RrQ29ubmVjdGVkT3ZlcmxheUdyb3dBZnRlck9wZW4iLCJncm93QWZ0ZXJPcGVuIl0scHVzaDpbImNka0Nvbm5lY3RlZE92ZXJsYXlQdXNoIiwicHVzaCJdfSxvdXRwdXRzOntiYWNrZHJvcENsaWNrOiJiYWNrZHJvcENsaWNrIixwb3NpdGlvbkNoYW5nZToicG9zaXRpb25DaGFuZ2UiLGF0dGFjaDoiYXR0YWNoIixkZXRhY2g6ImRldGFjaCIsb3ZlcmxheUtleWRvd246Im92ZXJsYXlLZXlkb3duIixvdmVybGF5T3V0c2lkZUNsaWNrOiJvdmVybGF5T3V0c2lkZUNsaWNrIn0sZXhwb3J0QXM6WyJjZGtDb25uZWN0ZWRPdmVybGF5Il0sZmVhdHVyZXM6W0Z0XX0pLG59KSgpLE1PZT17cHJvdmlkZTpDdGUsZGVwczpbdHJdLHVzZUZhY3Rvcnk6ZnVuY3Rpb24obil7cmV0dXJuKCk9Pm4uc2Nyb2xsU3RyYXRlZ2llcy5yZXBvc2l0aW9uKCl9fSxzcz0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7cHJvdmlkZXJzOlt0cixNT2VdLGltcG9ydHM6W0RoLGV1LFpjLFpjXX0pLG59KSgpO2Z1bmN0aW9uIHdPZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMikoMSwiYnV0dG9uIiwzKSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLmFjdGlvbigpKX0pLEEoMiksdigpKCl9aWYoMiZuKXtsZXQgZT1TKCk7QygyKSx5dChlLmRhdGEuYWN0aW9uKX19ZnVuY3Rpb24gU09lKG4sdCl7fXZhciB3Mj1uZXcgcGUoIk1hdFNuYWNrQmFyRGF0YSIpLE92PWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5wb2xpdGVuZXNzPSJhc3NlcnRpdmUiLHRoaXMuYW5ub3VuY2VtZW50TWVzc2FnZT0iIix0aGlzLmR1cmF0aW9uPTAsdGhpcy5kYXRhPW51bGwsdGhpcy5ob3Jpem9udGFsUG9zaXRpb249ImNlbnRlciIsdGhpcy52ZXJ0aWNhbFBvc2l0aW9uPSJib3R0b20ifX0sRU9lPU1hdGgucG93KDIsMzEpLTEscmc9Y2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLl9vdmVybGF5UmVmPWUsdGhpcy5fYWZ0ZXJEaXNtaXNzZWQ9bmV3IGtlLHRoaXMuX2FmdGVyT3BlbmVkPW5ldyBrZSx0aGlzLl9vbkFjdGlvbj1uZXcga2UsdGhpcy5fZGlzbWlzc2VkQnlBY3Rpb249ITEsdGhpcy5jb250YWluZXJJbnN0YW5jZT10LHQuX29uRXhpdC5zdWJzY3JpYmUoKCk9PnRoaXMuX2ZpbmlzaERpc21pc3MoKSl9ZGlzbWlzcygpe3RoaXMuX2FmdGVyRGlzbWlzc2VkLmNsb3NlZHx8dGhpcy5jb250YWluZXJJbnN0YW5jZS5leGl0KCksY2xlYXJUaW1lb3V0KHRoaXMuX2R1cmF0aW9uVGltZW91dElkKX1kaXNtaXNzV2l0aEFjdGlvbigpe3RoaXMuX29uQWN0aW9uLmNsb3NlZHx8KHRoaXMuX2Rpc21pc3NlZEJ5QWN0aW9uPSEwLHRoaXMuX29uQWN0aW9uLm5leHQoKSx0aGlzLl9vbkFjdGlvbi5jb21wbGV0ZSgpLHRoaXMuZGlzbWlzcygpKSxjbGVhclRpbWVvdXQodGhpcy5fZHVyYXRpb25UaW1lb3V0SWQpfWNsb3NlV2l0aEFjdGlvbigpe3RoaXMuZGlzbWlzc1dpdGhBY3Rpb24oKX1fZGlzbWlzc0FmdGVyKHQpe3RoaXMuX2R1cmF0aW9uVGltZW91dElkPXNldFRpbWVvdXQoKCk9PnRoaXMuZGlzbWlzcygpLE1hdGgubWluKHQsRU9lKSl9X29wZW4oKXt0aGlzLl9hZnRlck9wZW5lZC5jbG9zZWR8fCh0aGlzLl9hZnRlck9wZW5lZC5uZXh0KCksdGhpcy5fYWZ0ZXJPcGVuZWQuY29tcGxldGUoKSl9X2ZpbmlzaERpc21pc3MoKXt0aGlzLl9vdmVybGF5UmVmLmRpc3Bvc2UoKSx0aGlzLl9vbkFjdGlvbi5jbG9zZWR8fHRoaXMuX29uQWN0aW9uLmNvbXBsZXRlKCksdGhpcy5fYWZ0ZXJEaXNtaXNzZWQubmV4dCh7ZGlzbWlzc2VkQnlBY3Rpb246dGhpcy5fZGlzbWlzc2VkQnlBY3Rpb259KSx0aGlzLl9hZnRlckRpc21pc3NlZC5jb21wbGV0ZSgpLHRoaXMuX2Rpc21pc3NlZEJ5QWN0aW9uPSExfWFmdGVyRGlzbWlzc2VkKCl7cmV0dXJuIHRoaXMuX2FmdGVyRGlzbWlzc2VkfWFmdGVyT3BlbmVkKCl7cmV0dXJuIHRoaXMuY29udGFpbmVySW5zdGFuY2UuX29uRW50ZXJ9b25BY3Rpb24oKXtyZXR1cm4gdGhpcy5fb25BY3Rpb259fSxUT2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuc25hY2tCYXJSZWY9ZSx0aGlzLmRhdGE9aX1hY3Rpb24oKXt0aGlzLnNuYWNrQmFyUmVmLmRpc21pc3NXaXRoQWN0aW9uKCl9Z2V0IGhhc0FjdGlvbigpe3JldHVybiEhdGhpcy5kYXRhLmFjdGlvbn19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShyZyksTSh3MikpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInNpbXBsZS1zbmFjay1iYXIiXV0saG9zdEF0dHJzOlsxLCJtYXQtc2ltcGxlLXNuYWNrYmFyIl0sZGVjbHM6Myx2YXJzOjIsY29uc3RzOltbMSwibWF0LXNpbXBsZS1zbmFjay1iYXItY29udGVudCJdLFsiY2xhc3MiLCJtYXQtc2ltcGxlLXNuYWNrYmFyLWFjdGlvbiIsNCwibmdJZiJdLFsxLCJtYXQtc2ltcGxlLXNuYWNrYmFyLWFjdGlvbiJdLFsibWF0LWJ1dHRvbiIsIiIsMywiY2xpY2siXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsInNwYW4iLDApLEEoMSksdigpLEUoMix3T2UsMywxLCJkaXYiLDEpKSwyJmUmJihDKDEpLHl0KGkuZGF0YS5tZXNzYWdlKSxDKDEpLHkoIm5nSWYiLGkuaGFzQWN0aW9uKSl9LGRlcGVuZGVuY2llczpbQmUsX25dLHN0eWxlczpbIi5tYXQtc2ltcGxlLXNuYWNrYmFye2Rpc3BsYXk6ZmxleDtqdXN0aWZ5LWNvbnRlbnQ6c3BhY2UtYmV0d2VlbjthbGlnbi1pdGVtczpjZW50ZXI7bGluZS1oZWlnaHQ6MjBweDtvcGFjaXR5OjF9Lm1hdC1zaW1wbGUtc25hY2tiYXItYWN0aW9ue2ZsZXgtc2hyaW5rOjA7bWFyZ2luOi04cHggLThweCAtOHB4IDhweH0ubWF0LXNpbXBsZS1zbmFja2Jhci1hY3Rpb24gYnV0dG9ue21heC1oZWlnaHQ6MzZweDttaW4td2lkdGg6MH1bZGlyPXJ0bF0gLm1hdC1zaW1wbGUtc25hY2tiYXItYWN0aW9ue21hcmdpbi1sZWZ0Oi04cHg7bWFyZ2luLXJpZ2h0OjhweH0ubWF0LXNpbXBsZS1zbmFjay1iYXItY29udGVudHtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpc30iXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxET2U9e3NuYWNrQmFyU3RhdGU6S3IoInN0YXRlIixba2koInZvaWQsIGhpZGRlbiIsZ24oe3RyYW5zZm9ybToic2NhbGUoMC44KSIsb3BhY2l0eTowfSkpLGtpKCJ2aXNpYmxlIixnbih7dHJhbnNmb3JtOiJzY2FsZSgxKSIsb3BhY2l0eToxfSkpLExpKCIqID0+IHZpc2libGUiLGppKCIxNTBtcyBjdWJpYy1iZXppZXIoMCwgMCwgMC4yLCAxKSIpKSxMaSgiKiA9PiB2b2lkLCAqID0+IGhpZGRlbiIsamkoIjc1bXMgY3ViaWMtYmV6aWVyKDAuNCwgMC4wLCAxLCAxKSIsZ24oe29wYWNpdHk6MH0pKSldKX0sQU9lPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBQaHtjb25zdHJ1Y3RvcihlLGkscixvLHMpe3N1cGVyKCksdGhpcy5fbmdab25lPWUsdGhpcy5fZWxlbWVudFJlZj1pLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmPXIsdGhpcy5fcGxhdGZvcm09byx0aGlzLnNuYWNrQmFyQ29uZmlnPXMsdGhpcy5fYW5ub3VuY2VEZWxheT0xNTAsdGhpcy5fZGVzdHJveWVkPSExLHRoaXMuX29uQW5ub3VuY2U9bmV3IGtlLHRoaXMuX29uRXhpdD1uZXcga2UsdGhpcy5fb25FbnRlcj1uZXcga2UsdGhpcy5fYW5pbWF0aW9uU3RhdGU9InZvaWQiLHRoaXMuYXR0YWNoRG9tUG9ydGFsPWE9Pnt0aGlzLl9hc3NlcnROb3RBdHRhY2hlZCgpO2xldCBsPXRoaXMuX3BvcnRhbE91dGxldC5hdHRhY2hEb21Qb3J0YWwoYSk7cmV0dXJuIHRoaXMuX2FmdGVyUG9ydGFsQXR0YWNoZWQoKSxsfSx0aGlzLl9saXZlPSJhc3NlcnRpdmUiIT09cy5wb2xpdGVuZXNzfHxzLmFubm91bmNlbWVudE1lc3NhZ2U/Im9mZiI9PT1zLnBvbGl0ZW5lc3M/Im9mZiI6InBvbGl0ZSI6ImFzc2VydGl2ZSIsdGhpcy5fcGxhdGZvcm0uRklSRUZPWCYmKCJwb2xpdGUiPT09dGhpcy5fbGl2ZSYmKHRoaXMuX3JvbGU9InN0YXR1cyIpLCJhc3NlcnRpdmUiPT09dGhpcy5fbGl2ZSYmKHRoaXMuX3JvbGU9ImFsZXJ0IikpfWF0dGFjaENvbXBvbmVudFBvcnRhbChlKXt0aGlzLl9hc3NlcnROb3RBdHRhY2hlZCgpO2xldCBpPXRoaXMuX3BvcnRhbE91dGxldC5hdHRhY2hDb21wb25lbnRQb3J0YWwoZSk7cmV0dXJuIHRoaXMuX2FmdGVyUG9ydGFsQXR0YWNoZWQoKSxpfWF0dGFjaFRlbXBsYXRlUG9ydGFsKGUpe3RoaXMuX2Fzc2VydE5vdEF0dGFjaGVkKCk7bGV0IGk9dGhpcy5fcG9ydGFsT3V0bGV0LmF0dGFjaFRlbXBsYXRlUG9ydGFsKGUpO3JldHVybiB0aGlzLl9hZnRlclBvcnRhbEF0dGFjaGVkKCksaX1vbkFuaW1hdGlvbkVuZChlKXtsZXR7ZnJvbVN0YXRlOmksdG9TdGF0ZTpyfT1lO2lmKCgidm9pZCI9PT1yJiYidm9pZCIhPT1pfHwiaGlkZGVuIj09PXIpJiZ0aGlzLl9jb21wbGV0ZUV4aXQoKSwidmlzaWJsZSI9PT1yKXtsZXQgbz10aGlzLl9vbkVudGVyO3RoaXMuX25nWm9uZS5ydW4oKCk9PntvLm5leHQoKSxvLmNvbXBsZXRlKCl9KX19ZW50ZXIoKXt0aGlzLl9kZXN0cm95ZWR8fCh0aGlzLl9hbmltYXRpb25TdGF0ZT0idmlzaWJsZSIsdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYuZGV0ZWN0Q2hhbmdlcygpLHRoaXMuX3NjcmVlblJlYWRlckFubm91bmNlKCkpfWV4aXQoKXtyZXR1cm4gdGhpcy5fbmdab25lLnJ1bigoKT0+e3RoaXMuX2FuaW1hdGlvblN0YXRlPSJoaWRkZW4iLHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5zZXRBdHRyaWJ1dGUoIm1hdC1leGl0IiwiIiksY2xlYXJUaW1lb3V0KHRoaXMuX2Fubm91bmNlVGltZW91dElkKX0pLHRoaXMuX29uRXhpdH1uZ09uRGVzdHJveSgpe3RoaXMuX2Rlc3Ryb3llZD0hMCx0aGlzLl9jb21wbGV0ZUV4aXQoKX1fY29tcGxldGVFeGl0KCl7dGhpcy5fbmdab25lLm9uTWljcm90YXNrRW1wdHkucGlwZShRdCgxKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5fbmdab25lLnJ1bigoKT0+e3RoaXMuX29uRXhpdC5uZXh0KCksdGhpcy5fb25FeGl0LmNvbXBsZXRlKCl9KX0pfV9hZnRlclBvcnRhbEF0dGFjaGVkKCl7bGV0IGU9dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LGk9dGhpcy5zbmFja0JhckNvbmZpZy5wYW5lbENsYXNzO2kmJihBcnJheS5pc0FycmF5KGkpP2kuZm9yRWFjaChyPT5lLmNsYXNzTGlzdC5hZGQocikpOmUuY2xhc3NMaXN0LmFkZChpKSl9X2Fzc2VydE5vdEF0dGFjaGVkKCl7dGhpcy5fcG9ydGFsT3V0bGV0Lmhhc0F0dGFjaGVkKCl9X3NjcmVlblJlYWRlckFubm91bmNlKCl7dGhpcy5fYW5ub3VuY2VUaW1lb3V0SWR8fHRoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e3RoaXMuX2Fubm91bmNlVGltZW91dElkPXNldFRpbWVvdXQoKCk9PntsZXQgZT10aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQucXVlcnlTZWxlY3RvcigiW2FyaWEtaGlkZGVuXSIpLGk9dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LnF1ZXJ5U2VsZWN0b3IoIlthcmlhLWxpdmVdIik7aWYoZSYmaSl7bGV0IHI9bnVsbDt0aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXImJmRvY3VtZW50LmFjdGl2ZUVsZW1lbnQgaW5zdGFuY2VvZiBIVE1MRWxlbWVudCYmZS5jb250YWlucyhkb2N1bWVudC5hY3RpdmVFbGVtZW50KSYmKHI9ZG9jdW1lbnQuYWN0aXZlRWxlbWVudCksZS5yZW1vdmVBdHRyaWJ1dGUoImFyaWEtaGlkZGVuIiksaS5hcHBlbmRDaGlsZChlKSxyPy5mb2N1cygpLHRoaXMuX29uQW5ub3VuY2UubmV4dCgpLHRoaXMuX29uQW5ub3VuY2UuY29tcGxldGUoKX19LHRoaXMuX2Fubm91bmNlRGVsYXkpfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oX3QpLE0oUmUpLE0obm4pLE0ob2kpLE0oT3YpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bix2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJm90KGRhLDcpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkuX3BvcnRhbE91dGxldD1yLmZpcnN0KX19LGZlYXR1cmVzOlt0dF19KSxufSkoKSxJT2U9KCgpPT57Y2xhc3MgbiBleHRlbmRzIEFPZXtfYWZ0ZXJQb3J0YWxBdHRhY2hlZCgpe3N1cGVyLl9hZnRlclBvcnRhbEF0dGFjaGVkKCksImNlbnRlciI9PT10aGlzLnNuYWNrQmFyQ29uZmlnLmhvcml6b250YWxQb3NpdGlvbiYmdGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LmNsYXNzTGlzdC5hZGQoIm1hdC1zbmFjay1iYXItY2VudGVyIiksInRvcCI9PT10aGlzLnNuYWNrQmFyQ29uZmlnLnZlcnRpY2FsUG9zaXRpb24mJnRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5jbGFzc0xpc3QuYWRkKCJtYXQtc25hY2stYmFyLXRvcCIpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sic25hY2stYmFyLWNvbnRhaW5lciJdXSxob3N0QXR0cnM6WzEsIm1hdC1zbmFjay1iYXItY29udGFpbmVyIl0saG9zdFZhcnM6MSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJmlfKCJAc3RhdGUuZG9uZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25BbmltYXRpb25FbmQobyl9KSwyJmUmJnJfKCJAc3RhdGUiLGkuX2FuaW1hdGlvblN0YXRlKX0sZmVhdHVyZXM6W3R0XSxkZWNsczozLHZhcnM6Mixjb25zdHM6W1siYXJpYS1oaWRkZW4iLCJ0cnVlIl0sWyJjZGtQb3J0YWxPdXRsZXQiLCIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCksRSgxLFNPZSwwLDAsIm5nLXRlbXBsYXRlIiwxKSx2KCksTygyLCJkaXYiKSksMiZlJiYoQygyKSx6ZSgiYXJpYS1saXZlIixpLl9saXZlKSgicm9sZSIsaS5fcm9sZSkpfSxkZXBlbmRlbmNpZXM6W2RhXSxzdHlsZXM6WyIubWF0LXNuYWNrLWJhci1jb250YWluZXJ7Ym9yZGVyLXJhZGl1czo0cHg7Ym94LXNpemluZzpib3JkZXItYm94O2Rpc3BsYXk6YmxvY2s7bWFyZ2luOjI0cHg7bWF4LXdpZHRoOjMzdnc7bWluLXdpZHRoOjM0NHB4O3BhZGRpbmc6MTRweCAxNnB4O21pbi1oZWlnaHQ6NDhweDt0cmFuc2Zvcm0tb3JpZ2luOmNlbnRlcn0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtc25hY2stYmFyLWNvbnRhaW5lcntib3JkZXI6c29saWQgMXB4fS5tYXQtc25hY2stYmFyLWhhbmRzZXR7d2lkdGg6MTAwJX0ubWF0LXNuYWNrLWJhci1oYW5kc2V0IC5tYXQtc25hY2stYmFyLWNvbnRhaW5lcnttYXJnaW46OHB4O21heC13aWR0aDoxMDAlO21pbi13aWR0aDowO3dpZHRoOjEwMCV9Il0sZW5jYXBzdWxhdGlvbjoyLGRhdGE6e2FuaW1hdGlvbjpbRE9lLnNuYWNrQmFyU3RhdGVdfX0pLG59KSgpLGtIPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltzcyxldSxNZSxQbixsbixsbl19KSxufSkoKSxNdGU9bmV3IHBlKCJtYXQtc25hY2stYmFyLWRlZmF1bHQtb3B0aW9ucyIse3Byb3ZpZGVkSW46InJvb3QiLGZhY3Rvcnk6ZnVuY3Rpb24oKXtyZXR1cm4gbmV3IE92fX0pLFJPZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhKXt0aGlzLl9vdmVybGF5PWUsdGhpcy5fbGl2ZT1pLHRoaXMuX2luamVjdG9yPXIsdGhpcy5fYnJlYWtwb2ludE9ic2VydmVyPW8sdGhpcy5fcGFyZW50U25hY2tCYXI9cyx0aGlzLl9kZWZhdWx0Q29uZmlnPWEsdGhpcy5fc25hY2tCYXJSZWZBdFRoaXNMZXZlbD1udWxsfWdldCBfb3BlbmVkU25hY2tCYXJSZWYoKXtsZXQgZT10aGlzLl9wYXJlbnRTbmFja0JhcjtyZXR1cm4gZT9lLl9vcGVuZWRTbmFja0JhclJlZjp0aGlzLl9zbmFja0JhclJlZkF0VGhpc0xldmVsfXNldCBfb3BlbmVkU25hY2tCYXJSZWYoZSl7dGhpcy5fcGFyZW50U25hY2tCYXI/dGhpcy5fcGFyZW50U25hY2tCYXIuX29wZW5lZFNuYWNrQmFyUmVmPWU6dGhpcy5fc25hY2tCYXJSZWZBdFRoaXNMZXZlbD1lfW9wZW5Gcm9tQ29tcG9uZW50KGUsaSl7cmV0dXJuIHRoaXMuX2F0dGFjaChlLGkpfW9wZW5Gcm9tVGVtcGxhdGUoZSxpKXtyZXR1cm4gdGhpcy5fYXR0YWNoKGUsaSl9b3BlbihlLGk9IiIscil7bGV0IG89ey4uLnRoaXMuX2RlZmF1bHRDb25maWcsLi4ucn07cmV0dXJuIG8uZGF0YT17bWVzc2FnZTplLGFjdGlvbjppfSxvLmFubm91bmNlbWVudE1lc3NhZ2U9PT1lJiYoby5hbm5vdW5jZW1lbnRNZXNzYWdlPXZvaWQgMCksdGhpcy5vcGVuRnJvbUNvbXBvbmVudCh0aGlzLnNpbXBsZVNuYWNrQmFyQ29tcG9uZW50LG8pfWRpc21pc3MoKXt0aGlzLl9vcGVuZWRTbmFja0JhclJlZiYmdGhpcy5fb3BlbmVkU25hY2tCYXJSZWYuZGlzbWlzcygpfW5nT25EZXN0cm95KCl7dGhpcy5fc25hY2tCYXJSZWZBdFRoaXNMZXZlbCYmdGhpcy5fc25hY2tCYXJSZWZBdFRoaXNMZXZlbC5kaXNtaXNzKCl9X2F0dGFjaFNuYWNrQmFyQ29udGFpbmVyKGUsaSl7bGV0IG89WG4uY3JlYXRlKHtwYXJlbnQ6aSYmaS52aWV3Q29udGFpbmVyUmVmJiZpLnZpZXdDb250YWluZXJSZWYuaW5qZWN0b3J8fHRoaXMuX2luamVjdG9yLHByb3ZpZGVyczpbe3Byb3ZpZGU6T3YsdXNlVmFsdWU6aX1dfSkscz1uZXcgJGModGhpcy5zbmFja0JhckNvbnRhaW5lckNvbXBvbmVudCxpLnZpZXdDb250YWluZXJSZWYsbyksYT1lLmF0dGFjaChzKTtyZXR1cm4gYS5pbnN0YW5jZS5zbmFja0JhckNvbmZpZz1pLGEuaW5zdGFuY2V9X2F0dGFjaChlLGkpe2xldCByPXsuLi5uZXcgT3YsLi4udGhpcy5fZGVmYXVsdENvbmZpZywuLi5pfSxvPXRoaXMuX2NyZWF0ZU92ZXJsYXkocikscz10aGlzLl9hdHRhY2hTbmFja0JhckNvbnRhaW5lcihvLHIpLGE9bmV3IHJnKHMsbyk7aWYoZSBpbnN0YW5jZW9mIFZpKXtsZXQgbD1uZXcga3MoZSxudWxsLHskaW1wbGljaXQ6ci5kYXRhLHNuYWNrQmFyUmVmOmF9KTthLmluc3RhbmNlPXMuYXR0YWNoVGVtcGxhdGVQb3J0YWwobCl9ZWxzZXtsZXQgbD10aGlzLl9jcmVhdGVJbmplY3RvcihyLGEpLGM9bmV3ICRjKGUsdm9pZCAwLGwpLHU9cy5hdHRhY2hDb21wb25lbnRQb3J0YWwoYyk7YS5pbnN0YW5jZT11Lmluc3RhbmNlfXJldHVybiB0aGlzLl9icmVha3BvaW50T2JzZXJ2ZXIub2JzZXJ2ZSgiKG1heC13aWR0aDogNTk5Ljk4cHgpIGFuZCAob3JpZW50YXRpb246IHBvcnRyYWl0KSIpLnBpcGUoc3Qoby5kZXRhY2htZW50cygpKSkuc3Vic2NyaWJlKGw9PntvLm92ZXJsYXlFbGVtZW50LmNsYXNzTGlzdC50b2dnbGUodGhpcy5oYW5kc2V0Q3NzQ2xhc3MsbC5tYXRjaGVzKX0pLHIuYW5ub3VuY2VtZW50TWVzc2FnZSYmcy5fb25Bbm5vdW5jZS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9saXZlLmFubm91bmNlKHIuYW5ub3VuY2VtZW50TWVzc2FnZSxyLnBvbGl0ZW5lc3MpfSksdGhpcy5fYW5pbWF0ZVNuYWNrQmFyKGEsciksdGhpcy5fb3BlbmVkU25hY2tCYXJSZWY9YSx0aGlzLl9vcGVuZWRTbmFja0JhclJlZn1fYW5pbWF0ZVNuYWNrQmFyKGUsaSl7ZS5hZnRlckRpc21pc3NlZCgpLnN1YnNjcmliZSgoKT0+e3RoaXMuX29wZW5lZFNuYWNrQmFyUmVmPT1lJiYodGhpcy5fb3BlbmVkU25hY2tCYXJSZWY9bnVsbCksaS5hbm5vdW5jZW1lbnRNZXNzYWdlJiZ0aGlzLl9saXZlLmNsZWFyKCl9KSx0aGlzLl9vcGVuZWRTbmFja0JhclJlZj8odGhpcy5fb3BlbmVkU25hY2tCYXJSZWYuYWZ0ZXJEaXNtaXNzZWQoKS5zdWJzY3JpYmUoKCk9PntlLmNvbnRhaW5lckluc3RhbmNlLmVudGVyKCl9KSx0aGlzLl9vcGVuZWRTbmFja0JhclJlZi5kaXNtaXNzKCkpOmUuY29udGFpbmVySW5zdGFuY2UuZW50ZXIoKSxpLmR1cmF0aW9uJiZpLmR1cmF0aW9uPjAmJmUuYWZ0ZXJPcGVuZWQoKS5zdWJzY3JpYmUoKCk9PmUuX2Rpc21pc3NBZnRlcihpLmR1cmF0aW9uKSl9X2NyZWF0ZU92ZXJsYXkoZSl7bGV0IGk9bmV3IHNjO2kuZGlyZWN0aW9uPWUuZGlyZWN0aW9uO2xldCByPXRoaXMuX292ZXJsYXkucG9zaXRpb24oKS5nbG9iYWwoKSxvPSJydGwiPT09ZS5kaXJlY3Rpb24scz0ibGVmdCI9PT1lLmhvcml6b250YWxQb3NpdGlvbnx8InN0YXJ0Ij09PWUuaG9yaXpvbnRhbFBvc2l0aW9uJiYhb3x8ImVuZCI9PT1lLmhvcml6b250YWxQb3NpdGlvbiYmbyxhPSFzJiYiY2VudGVyIiE9PWUuaG9yaXpvbnRhbFBvc2l0aW9uO3JldHVybiBzP3IubGVmdCgiMCIpOmE/ci5yaWdodCgiMCIpOnIuY2VudGVySG9yaXpvbnRhbGx5KCksInRvcCI9PT1lLnZlcnRpY2FsUG9zaXRpb24/ci50b3AoIjAiKTpyLmJvdHRvbSgiMCIpLGkucG9zaXRpb25TdHJhdGVneT1yLHRoaXMuX292ZXJsYXkuY3JlYXRlKGkpfV9jcmVhdGVJbmplY3RvcihlLGkpe3JldHVybiBYbi5jcmVhdGUoe3BhcmVudDplJiZlLnZpZXdDb250YWluZXJSZWYmJmUudmlld0NvbnRhaW5lclJlZi5pbmplY3Rvcnx8dGhpcy5faW5qZWN0b3IscHJvdmlkZXJzOlt7cHJvdmlkZTpyZyx1c2VWYWx1ZTppfSx7cHJvdmlkZTp3Mix1c2VWYWx1ZTplLmRhdGF9XX0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKHRyKSxqKHR3KSxqKFhuKSxqKEptKSxqKG4sMTIpLGooTXRlKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksd3RlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBST2V7Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEpe3N1cGVyKGUsaSxyLG8scyxhKSx0aGlzLnNpbXBsZVNuYWNrQmFyQ29tcG9uZW50PVRPZSx0aGlzLnNuYWNrQmFyQ29udGFpbmVyQ29tcG9uZW50PUlPZSx0aGlzLmhhbmRzZXRDc3NDbGFzcz0ibWF0LXNuYWNrLWJhci1oYW5kc2V0In19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaih0ciksaih0dyksaihYbiksaihKbSksaihuLDEyKSxqKE10ZSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOmtIfSksbn0pKCksT09lPS9bXFxeJC4qKz8oKVtcXXt9fF0vZyxFdGU9IlxcdTAwMDAtXFx1MDAyMFxcdTAwN2YtXFx1MDA5ZiIsRk9lPW5ldyBSZWdFeHAoIig/OlthLXpBLVpdW2EtekEtWjAtOSsuLV17Mix9OlxcL1xcL3xkYXRhOnx3d3dcXC4pW15cXHMiK0V0ZSsnIl17Mix9W15cXHMnK0V0ZSsiXCInKX1cXF0sOjsuIT9dIiwiZ3UiKTtmdW5jdGlvbiBEdGUobil7cmV0dXJuIGZ1bmN0aW9uKG4sdCl7dC5mbGFncy5pbmNsdWRlcygiZyIpfHwodD1uZXcgUmVnRXhwKHQsdC5mbGFncysiZyIpKTtsZXQgZT1bXSxpPTA7Zm9yKGxldCByIG9mIG4ubWF0Y2hBbGwodCkpe2xldCBvPXIuaW5kZXgscz1yWzBdO28+aSYmZS5wdXNoKHtpbmRleDppLHRleHQ6bi5zdWJzdHJpbmcoaSxvKSxtYXRjaGVzUmVnZXg6ITF9KSxlLnB1c2goe2luZGV4Om8sdGV4dDpzLG1hdGNoZXNSZWdleDohMH0pLGk9bytzLmxlbmd0aH1yZXR1cm4gbi5sZW5ndGg+aSYmZS5wdXNoKHtpbmRleDppLHRleHQ6bi5zdWJzdHJpbmcoaSxuLmxlbmd0aCksbWF0Y2hlc1JlZ2V4OiExfSksZX0obixGT2UpLm1hcCgoe21hdGNoZXNSZWdleDp0LHRleHQ6ZX0pPT4oe2lzVVJMOnQsdGV4dDplfSkpfWZ1bmN0aW9uIE5PZShuLHQpe2lmKDEmbiYmKHNuKDApLEEoMSksYW4oKSksMiZuKXtsZXQgZT1TKCkuJGltcGxpY2l0O0MoMSksamUoIiAiLGUudGV4dCwiICIpfX1mdW5jdGlvbiBMT2Uobix0KXtpZigxJm4mJihfKDAsImEiLDcpLEEoMSksdigpKSwyJm4pe2xldCBlPVMoKS4kaW1wbGljaXQ7WmkoImhyZWYiLGUudGV4dCx6bCksQygxKSx5dChlLnRleHQpfX1mdW5jdGlvbiBCT2Uobix0KXtpZigxJm4mJihzbigwKSxFKDEsTk9lLDIsMSwibmctY29udGFpbmVyIiw1KSxFKDIsTE9lLDIsMiwibmctdGVtcGxhdGUiLG51bGwsNixxdCksYW4oKSksMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPSRlKDMpO0MoMSkseSgibmdJZiIsIWUuaXNVUkwpKCJuZ0lmRWxzZSIsaSl9fWZ1bmN0aW9uIFZPZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImJ1dHRvbiIsOCksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5vbkFjdGlvbkJ1dHRvbkNsaWNrZWQoKSl9KSxBKDEpLHYoKX1pZigyJm4pe2xldCBlPVMoKTtDKDEpLGplKCIgIixlLmFsZXJ0LmZvbGxvd3VwQWN0aW9uLmxvY2FsaXplZExhYmVsLCIgIil9fXZhciBBdGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5zbmFja0JhclJlZj1lLHRoaXMudW5rbm93bkRhdGE9aSx0aGlzLnN0b3JlPXIsdGhpcy5zcGxpdEJ5VVJMPUR0ZSx0aGlzLmFsZXJ0PWl9YXN5bmMgb25BY3Rpb25CdXR0b25DbGlja2VkKCl7dGhpcy5zbmFja0JhclJlZi5kaXNtaXNzKCk7bGV0IGU9YXdhaXQgdGhpcy5hbGVydC5mb2xsb3d1cEFjdGlvbi5nZXRGb2xsb3d1cEFjdGlvbih0aGlzLnN0b3JlKTt0aGlzLnN0b3JlLmRpc3BhdGNoKGUpfW9uQ2xvc2VCdXR0b25DbGlja2VkKCl7dGhpcy5zbmFja0JhclJlZi5kaXNtaXNzKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0ocmcpLE0odzIpLE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJhbGVydC1kaXNwbGF5LXNuYWNrYmFyIl1dLGRlY2xzOjYsdmFyczoyLGNvbnN0czpmdW5jdGlvbigpe2xldCB0O3JldHVybiB0PSRsb2NhbGl6ZWA6QSBidXR0b24gdG8gY2xvc2UgdGhlIHNuYWNrYmFyIG1lc3NhZ2XikJ9lYTRkOWZlNjE0MjBhM2ZjZTgxY2Y1NGM0YzYxNWUzYzE5YzY0NmE24pCfMTUzNjA4NzUxOTc0MzcwNzM2MjpEaXNtaXNzYCxbWzEsIm1lc3NhZ2UiXSxbNCwibmdGb3IiLCJuZ0Zvck9mIl0sWzEsImNvbnRyb2xzIl0sWyJtYXQtYnV0dG9uIiwiIiwiY2xhc3MiLCJmb2xsb3d1cC1idXR0b24iLDMsImNsaWNrIiw0LCJuZ0lmIl0sWyJtYXQtYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsdCwxLCJkaXNtaXNzLWJ1dHRvbiIsMywiY2xpY2siXSxbNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJsaW5rUGllY2UiLCIiXSxbInJlbCIsIm5vcmVmZXJyZXIgbm9vcGVuZXIiLCJ0YXJnZXQiLCJfYmxhbmsiLDMsImhyZWYiXSxbIm1hdC1idXR0b24iLCIiLDEsImZvbGxvd3VwLWJ1dHRvbiIsMywiY2xpY2siXV19LHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJkaXYiLDApLEUoMSxCT2UsNCwyLCJuZy1jb250YWluZXIiLDEpLHYoKSxfKDIsImRpdiIsMiksRSgzLFZPZSwyLDEsImJ1dHRvbiIsMyksXyg0LCJidXR0b24iLDQpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uQ2xvc2VCdXR0b25DbGlja2VkKCl9KSxBKDUsIiBEaXNtaXNzICIpLHYoKSgpKSwyJmUmJihDKDEpLHkoIm5nRm9yT2YiLGkuc3BsaXRCeVVSTChpLmFsZXJ0LmxvY2FsaXplZE1lc3NhZ2UpKSxDKDIpLHkoIm5nSWYiLGkuYWxlcnQuZm9sbG93dXBBY3Rpb24pKX0sZGVwZW5kZW5jaWVzOltkbixCZSxfbl0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXXtkaXNwbGF5OmZsZXg7ZmxleC13cmFwOndyYXB9Lm1lc3NhZ2VbX25nY29udGVudC0lQ09NUCVde2ZvbnQtc2l6ZToxNHB4O2FsaWduLXNlbGY6Y2VudGVyO21hcmdpbjo1cHggMDt3b3JkLWJyZWFrOmJyZWFrLXdvcmR9Lm1lc3NhZ2VbX25nY29udGVudC0lQ09NUCVdICAgYVtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6aW5oZXJpdH0uY29udHJvbHNbX25nY29udGVudC0lQ09NUCVde3doaXRlLXNwYWNlOm5vd3JhcDttYXJnaW4tbGVmdDphdXRvfWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17dGV4dC10cmFuc2Zvcm06dXBwZXJjYXNlfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksSXRlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXt0aGlzLnN0b3JlPWUsdGhpcy5zbmFja0Jhcj1pLHRoaXMubmdVbnN1YnNjcmliZT1uZXcga2V9bmdPbkluaXQoKXt0aGlzLnN0b3JlLnNlbGVjdChxJCkucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpLFllKGU9PkJvb2xlYW4oZSkpKS5zdWJzY3JpYmUoZT0+e3RoaXMuc2hvd0FsZXJ0KGUpfSl9bmdPbkRlc3Ryb3koKXt0aGlzLm5nVW5zdWJzY3JpYmUubmV4dCgpLHRoaXMubmdVbnN1YnNjcmliZS5jb21wbGV0ZSgpfXNob3dBbGVydChlKXt0aGlzLnNuYWNrQmFyLm9wZW5Gcm9tQ29tcG9uZW50KEF0ZSx7ZHVyYXRpb246NWUzLGhvcml6b250YWxQb3NpdGlvbjoic3RhcnQiLHZlcnRpY2FsUG9zaXRpb246ImJvdHRvbSIsZGF0YTplfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpLE0od3RlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siYWxlcnQtc25hY2tiYXIiXV0sZGVjbHM6MCx2YXJzOjAsdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXt9LGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLFMyPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxQbixrSF19KSxufSkoKSxFMj0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbSnUsUzIsd3IuZm9yRmVhdHVyZSgiYWxlcnRzIixVZWUpLHJvLmZvckZlYXR1cmUoW0hlZV0pXX0pLG59KSgpO2Z1bmN0aW9uIFVPZShuLHQpe312YXIgb2c9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnJvbGU9ImRpYWxvZyIsdGhpcy5wYW5lbENsYXNzPSIiLHRoaXMuaGFzQmFja2Ryb3A9ITAsdGhpcy5iYWNrZHJvcENsYXNzPSIiLHRoaXMuZGlzYWJsZUNsb3NlPSExLHRoaXMud2lkdGg9IiIsdGhpcy5oZWlnaHQ9IiIsdGhpcy5kYXRhPW51bGwsdGhpcy5hcmlhRGVzY3JpYmVkQnk9bnVsbCx0aGlzLmFyaWFMYWJlbGxlZEJ5PW51bGwsdGhpcy5hcmlhTGFiZWw9bnVsbCx0aGlzLmFyaWFNb2RhbD0hMCx0aGlzLmF1dG9Gb2N1cz0iZmlyc3QtdGFiYmFibGUiLHRoaXMucmVzdG9yZUZvY3VzPSEwLHRoaXMuY2xvc2VPbk5hdmlnYXRpb249ITAsdGhpcy5jbG9zZU9uRGVzdHJveT0hMH19LE5IPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBQaHtjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsLGMpe3N1cGVyKCksdGhpcy5fZWxlbWVudFJlZj1lLHRoaXMuX2ZvY3VzVHJhcEZhY3Rvcnk9aSx0aGlzLl9jb25maWc9byx0aGlzLl9pbnRlcmFjdGl2aXR5Q2hlY2tlcj1zLHRoaXMuX25nWm9uZT1hLHRoaXMuX292ZXJsYXlSZWY9bCx0aGlzLl9mb2N1c01vbml0b3I9Yyx0aGlzLl9lbGVtZW50Rm9jdXNlZEJlZm9yZURpYWxvZ1dhc09wZW5lZD1udWxsLHRoaXMuX2Nsb3NlSW50ZXJhY3Rpb25UeXBlPW51bGwsdGhpcy5hdHRhY2hEb21Qb3J0YWw9dT0+e3RoaXMuX3BvcnRhbE91dGxldC5oYXNBdHRhY2hlZCgpO2xldCBkPXRoaXMuX3BvcnRhbE91dGxldC5hdHRhY2hEb21Qb3J0YWwodSk7cmV0dXJuIHRoaXMuX2NvbnRlbnRBdHRhY2hlZCgpLGR9LHRoaXMuX2FyaWFMYWJlbGxlZEJ5PXRoaXMuX2NvbmZpZy5hcmlhTGFiZWxsZWRCeXx8bnVsbCx0aGlzLl9kb2N1bWVudD1yfV9jb250ZW50QXR0YWNoZWQoKXt0aGlzLl9pbml0aWFsaXplRm9jdXNUcmFwKCksdGhpcy5faGFuZGxlQmFja2Ryb3BDbGlja3MoKSx0aGlzLl9jYXB0dXJlSW5pdGlhbEZvY3VzKCl9X2NhcHR1cmVJbml0aWFsRm9jdXMoKXt0aGlzLl90cmFwRm9jdXMoKX1uZ09uRGVzdHJveSgpe3RoaXMuX3Jlc3RvcmVGb2N1cygpfWF0dGFjaENvbXBvbmVudFBvcnRhbChlKXt0aGlzLl9wb3J0YWxPdXRsZXQuaGFzQXR0YWNoZWQoKTtsZXQgaT10aGlzLl9wb3J0YWxPdXRsZXQuYXR0YWNoQ29tcG9uZW50UG9ydGFsKGUpO3JldHVybiB0aGlzLl9jb250ZW50QXR0YWNoZWQoKSxpfWF0dGFjaFRlbXBsYXRlUG9ydGFsKGUpe3RoaXMuX3BvcnRhbE91dGxldC5oYXNBdHRhY2hlZCgpO2xldCBpPXRoaXMuX3BvcnRhbE91dGxldC5hdHRhY2hUZW1wbGF0ZVBvcnRhbChlKTtyZXR1cm4gdGhpcy5fY29udGVudEF0dGFjaGVkKCksaX1fcmVjYXB0dXJlRm9jdXMoKXt0aGlzLl9jb250YWluc0ZvY3VzKCl8fHRoaXMuX3RyYXBGb2N1cygpfV9mb3JjZUZvY3VzKGUsaSl7dGhpcy5faW50ZXJhY3Rpdml0eUNoZWNrZXIuaXNGb2N1c2FibGUoZSl8fChlLnRhYkluZGV4PS0xLHRoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e2xldCByPSgpPT57ZS5yZW1vdmVFdmVudExpc3RlbmVyKCJibHVyIixyKSxlLnJlbW92ZUV2ZW50TGlzdGVuZXIoIm1vdXNlZG93biIsciksZS5yZW1vdmVBdHRyaWJ1dGUoInRhYmluZGV4Iil9O2UuYWRkRXZlbnRMaXN0ZW5lcigiYmx1ciIsciksZS5hZGRFdmVudExpc3RlbmVyKCJtb3VzZWRvd24iLHIpfSkpLGUuZm9jdXMoaSl9X2ZvY3VzQnlDc3NTZWxlY3RvcihlLGkpe2xldCByPXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5xdWVyeVNlbGVjdG9yKGUpO3ImJnRoaXMuX2ZvcmNlRm9jdXMocixpKX1fdHJhcEZvY3VzKCl7bGV0IGU9dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50O3N3aXRjaCh0aGlzLl9jb25maWcuYXV0b0ZvY3VzKXtjYXNlITE6Y2FzZSJkaWFsb2ciOnRoaXMuX2NvbnRhaW5zRm9jdXMoKXx8ZS5mb2N1cygpO2JyZWFrO2Nhc2UhMDpjYXNlImZpcnN0LXRhYmJhYmxlIjp0aGlzLl9mb2N1c1RyYXAuZm9jdXNJbml0aWFsRWxlbWVudFdoZW5SZWFkeSgpLnRoZW4oaT0+e2l8fHRoaXMuX2ZvY3VzRGlhbG9nQ29udGFpbmVyKCl9KTticmVhaztjYXNlImZpcnN0LWhlYWRpbmciOnRoaXMuX2ZvY3VzQnlDc3NTZWxlY3RvcignaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgW3JvbGU9ImhlYWRpbmciXScpO2JyZWFrO2RlZmF1bHQ6dGhpcy5fZm9jdXNCeUNzc1NlbGVjdG9yKHRoaXMuX2NvbmZpZy5hdXRvRm9jdXMpfX1fcmVzdG9yZUZvY3VzKCl7bGV0IGU9dGhpcy5fY29uZmlnLnJlc3RvcmVGb2N1cyxpPW51bGw7aWYoInN0cmluZyI9PXR5cGVvZiBlP2k9dGhpcy5fZG9jdW1lbnQucXVlcnlTZWxlY3RvcihlKToiYm9vbGVhbiI9PXR5cGVvZiBlP2k9ZT90aGlzLl9lbGVtZW50Rm9jdXNlZEJlZm9yZURpYWxvZ1dhc09wZW5lZDpudWxsOmUmJihpPWUpLHRoaXMuX2NvbmZpZy5yZXN0b3JlRm9jdXMmJmkmJiJmdW5jdGlvbiI9PXR5cGVvZiBpLmZvY3VzKXtsZXQgcj1LTSgpLG89dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50Oyghcnx8cj09PXRoaXMuX2RvY3VtZW50LmJvZHl8fHI9PT1vfHxvLmNvbnRhaW5zKHIpKSYmKHRoaXMuX2ZvY3VzTW9uaXRvcj8odGhpcy5fZm9jdXNNb25pdG9yLmZvY3VzVmlhKGksdGhpcy5fY2xvc2VJbnRlcmFjdGlvblR5cGUpLHRoaXMuX2Nsb3NlSW50ZXJhY3Rpb25UeXBlPW51bGwpOmkuZm9jdXMoKSl9dGhpcy5fZm9jdXNUcmFwJiZ0aGlzLl9mb2N1c1RyYXAuZGVzdHJveSgpfV9mb2N1c0RpYWxvZ0NvbnRhaW5lcigpe3RoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5mb2N1cyYmdGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LmZvY3VzKCl9X2NvbnRhaW5zRm9jdXMoKXtsZXQgZT10aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQsaT1LTSgpO3JldHVybiBlPT09aXx8ZS5jb250YWlucyhpKX1faW5pdGlhbGl6ZUZvY3VzVHJhcCgpe3RoaXMuX2ZvY3VzVHJhcD10aGlzLl9mb2N1c1RyYXBGYWN0b3J5LmNyZWF0ZSh0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQpLHRoaXMuX2RvY3VtZW50JiYodGhpcy5fZWxlbWVudEZvY3VzZWRCZWZvcmVEaWFsb2dXYXNPcGVuZWQ9S00oKSl9X2hhbmRsZUJhY2tkcm9wQ2xpY2tzKCl7dGhpcy5fb3ZlcmxheVJlZi5iYWNrZHJvcENsaWNrKCkuc3Vic2NyaWJlKCgpPT57dGhpcy5fY29uZmlnLmRpc2FibGVDbG9zZSYmdGhpcy5fcmVjYXB0dXJlRm9jdXMoKX0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKEpNKSxNKEh0LDgpLE0ob2cpLE0oU3YpLE0oX3QpLE0oZGQpLE0oRnIpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJjZGstZGlhbG9nLWNvbnRhaW5lciJdXSx2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJm90KGRhLDcpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkuX3BvcnRhbE91dGxldD1yLmZpcnN0KX19LGhvc3RBdHRyczpbInRhYmluZGV4IiwiLTEiLDEsImNkay1kaWFsb2ctY29udGFpbmVyIl0saG9zdFZhcnM6Nixob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJnplKCJpZCIsaS5fY29uZmlnLmlkfHxudWxsKSgicm9sZSIsaS5fY29uZmlnLnJvbGUpKCJhcmlhLW1vZGFsIixpLl9jb25maWcuYXJpYU1vZGFsKSgiYXJpYS1sYWJlbGxlZGJ5IixpLl9jb25maWcuYXJpYUxhYmVsP251bGw6aS5fYXJpYUxhYmVsbGVkQnkpKCJhcmlhLWxhYmVsIixpLl9jb25maWcuYXJpYUxhYmVsKSgiYXJpYS1kZXNjcmliZWRieSIsaS5fY29uZmlnLmFyaWFEZXNjcmliZWRCeXx8bnVsbCl9LGZlYXR1cmVzOlt0dF0sZGVjbHM6MSx2YXJzOjAsY29uc3RzOltbImNka1BvcnRhbE91dGxldCIsIiJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmRSgwLFVPZSwwLDAsIm5nLXRlbXBsYXRlIiwwKX0sZGVwZW5kZW5jaWVzOltkYV0sc3R5bGVzOlsiLmNkay1kaWFsb2ctY29udGFpbmVye2Rpc3BsYXk6YmxvY2s7d2lkdGg6MTAwJTtoZWlnaHQ6MTAwJTttaW4taGVpZ2h0OmluaGVyaXQ7bWF4LWhlaWdodDppbmhlcml0fSJdLGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpLGx3PWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5vdmVybGF5UmVmPXQsdGhpcy5jb25maWc9ZSx0aGlzLmNsb3NlZD1uZXcga2UsdGhpcy5kaXNhYmxlQ2xvc2U9ZS5kaXNhYmxlQ2xvc2UsdGhpcy5iYWNrZHJvcENsaWNrPXQuYmFja2Ryb3BDbGljaygpLHRoaXMua2V5ZG93bkV2ZW50cz10LmtleWRvd25FdmVudHMoKSx0aGlzLm91dHNpZGVQb2ludGVyRXZlbnRzPXQub3V0c2lkZVBvaW50ZXJFdmVudHMoKSx0aGlzLmlkPWUuaWQsdGhpcy5rZXlkb3duRXZlbnRzLnN1YnNjcmliZShpPT57Mjc9PT1pLmtleUNvZGUmJiF0aGlzLmRpc2FibGVDbG9zZSYmIWtyKGkpJiYoaS5wcmV2ZW50RGVmYXVsdCgpLHRoaXMuY2xvc2Uodm9pZCAwLHtmb2N1c09yaWdpbjoia2V5Ym9hcmQifSkpfSksdGhpcy5iYWNrZHJvcENsaWNrLnN1YnNjcmliZSgoKT0+e3RoaXMuZGlzYWJsZUNsb3NlfHx0aGlzLmNsb3NlKHZvaWQgMCx7Zm9jdXNPcmlnaW46Im1vdXNlIn0pfSl9Y2xvc2UodCxlKXtpZih0aGlzLmNvbnRhaW5lckluc3RhbmNlKXtsZXQgaT10aGlzLmNsb3NlZDt0aGlzLmNvbnRhaW5lckluc3RhbmNlLl9jbG9zZUludGVyYWN0aW9uVHlwZT1lPy5mb2N1c09yaWdpbnx8InByb2dyYW0iLHRoaXMub3ZlcmxheVJlZi5kaXNwb3NlKCksaS5uZXh0KHQpLGkuY29tcGxldGUoKSx0aGlzLmNvbXBvbmVudEluc3RhbmNlPXRoaXMuY29udGFpbmVySW5zdGFuY2U9bnVsbH19dXBkYXRlUG9zaXRpb24oKXtyZXR1cm4gdGhpcy5vdmVybGF5UmVmLnVwZGF0ZVBvc2l0aW9uKCksdGhpc311cGRhdGVTaXplKHQ9IiIsZT0iIil7cmV0dXJuIHRoaXMub3ZlcmxheVJlZi51cGRhdGVTaXplKHt3aWR0aDp0LGhlaWdodDplfSksdGhpc31hZGRQYW5lbENsYXNzKHQpe3JldHVybiB0aGlzLm92ZXJsYXlSZWYuYWRkUGFuZWxDbGFzcyh0KSx0aGlzfXJlbW92ZVBhbmVsQ2xhc3ModCl7cmV0dXJuIHRoaXMub3ZlcmxheVJlZi5yZW1vdmVQYW5lbENsYXNzKHQpLHRoaXN9fSxQdGU9bmV3IHBlKCJEaWFsb2dTY3JvbGxTdHJhdGVneSIpLHpPZT1uZXcgcGUoIkRpYWxvZ0RhdGEiKSxqT2U9bmV3IHBlKCJEZWZhdWx0RGlhbG9nQ29uZmlnIiksV09lPXtwcm92aWRlOlB0ZSxkZXBzOlt0cl0sdXNlRmFjdG9yeTpmdW5jdGlvbihuKXtyZXR1cm4oKT0+bi5zY3JvbGxTdHJhdGVnaWVzLmJsb2NrKCl9fSxxT2U9MCxMSD0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhKXt0aGlzLl9vdmVybGF5PWUsdGhpcy5faW5qZWN0b3I9aSx0aGlzLl9kZWZhdWx0T3B0aW9ucz1yLHRoaXMuX3BhcmVudERpYWxvZz1vLHRoaXMuX292ZXJsYXlDb250YWluZXI9cyx0aGlzLl9vcGVuRGlhbG9nc0F0VGhpc0xldmVsPVtdLHRoaXMuX2FmdGVyQWxsQ2xvc2VkQXRUaGlzTGV2ZWw9bmV3IGtlLHRoaXMuX2FmdGVyT3BlbmVkQXRUaGlzTGV2ZWw9bmV3IGtlLHRoaXMuX2FyaWFIaWRkZW5FbGVtZW50cz1uZXcgTWFwLHRoaXMuYWZ0ZXJBbGxDbG9zZWQ9UWEoKCk9PnRoaXMub3BlbkRpYWxvZ3MubGVuZ3RoP3RoaXMuX2dldEFmdGVyQWxsQ2xvc2VkKCk6dGhpcy5fZ2V0QWZ0ZXJBbGxDbG9zZWQoKS5waXBlKHpuKHZvaWQgMCkpKSx0aGlzLl9zY3JvbGxTdHJhdGVneT1hfWdldCBvcGVuRGlhbG9ncygpe3JldHVybiB0aGlzLl9wYXJlbnREaWFsb2c/dGhpcy5fcGFyZW50RGlhbG9nLm9wZW5EaWFsb2dzOnRoaXMuX29wZW5EaWFsb2dzQXRUaGlzTGV2ZWx9Z2V0IGFmdGVyT3BlbmVkKCl7cmV0dXJuIHRoaXMuX3BhcmVudERpYWxvZz90aGlzLl9wYXJlbnREaWFsb2cuYWZ0ZXJPcGVuZWQ6dGhpcy5fYWZ0ZXJPcGVuZWRBdFRoaXNMZXZlbH1vcGVuKGUsaSl7KGk9ey4uLnRoaXMuX2RlZmF1bHRPcHRpb25zfHxuZXcgb2csLi4uaX0pLmlkPWkuaWR8fCJjZGstZGlhbG9nLSIrcU9lKyssaS5pZCYmdGhpcy5nZXREaWFsb2dCeUlkKGkuaWQpO2xldCBvPXRoaXMuX2dldE92ZXJsYXlDb25maWcoaSkscz10aGlzLl9vdmVybGF5LmNyZWF0ZShvKSxhPW5ldyBsdyhzLGkpLGw9dGhpcy5fYXR0YWNoQ29udGFpbmVyKHMsYSxpKTtyZXR1cm4gYS5jb250YWluZXJJbnN0YW5jZT1sLHRoaXMuX2F0dGFjaERpYWxvZ0NvbnRlbnQoZSxhLGwsaSksdGhpcy5vcGVuRGlhbG9ncy5sZW5ndGh8fHRoaXMuX2hpZGVOb25EaWFsb2dDb250ZW50RnJvbUFzc2lzdGl2ZVRlY2hub2xvZ3koKSx0aGlzLm9wZW5EaWFsb2dzLnB1c2goYSksYS5jbG9zZWQuc3Vic2NyaWJlKCgpPT50aGlzLl9yZW1vdmVPcGVuRGlhbG9nKGEsITApKSx0aGlzLmFmdGVyT3BlbmVkLm5leHQoYSksYX1jbG9zZUFsbCgpe0ZIKHRoaXMub3BlbkRpYWxvZ3MsZT0+ZS5jbG9zZSgpKX1nZXREaWFsb2dCeUlkKGUpe3JldHVybiB0aGlzLm9wZW5EaWFsb2dzLmZpbmQoaT0+aS5pZD09PWUpfW5nT25EZXN0cm95KCl7RkgodGhpcy5fb3BlbkRpYWxvZ3NBdFRoaXNMZXZlbCxlPT57ITE9PT1lLmNvbmZpZy5jbG9zZU9uRGVzdHJveSYmdGhpcy5fcmVtb3ZlT3BlbkRpYWxvZyhlLCExKX0pLEZIKHRoaXMuX29wZW5EaWFsb2dzQXRUaGlzTGV2ZWwsZT0+ZS5jbG9zZSgpKSx0aGlzLl9hZnRlckFsbENsb3NlZEF0VGhpc0xldmVsLmNvbXBsZXRlKCksdGhpcy5fYWZ0ZXJPcGVuZWRBdFRoaXNMZXZlbC5jb21wbGV0ZSgpLHRoaXMuX29wZW5EaWFsb2dzQXRUaGlzTGV2ZWw9W119X2dldE92ZXJsYXlDb25maWcoZSl7bGV0IGk9bmV3IHNjKHtwb3NpdGlvblN0cmF0ZWd5OmUucG9zaXRpb25TdHJhdGVneXx8dGhpcy5fb3ZlcmxheS5wb3NpdGlvbigpLmdsb2JhbCgpLmNlbnRlckhvcml6b250YWxseSgpLmNlbnRlclZlcnRpY2FsbHkoKSxzY3JvbGxTdHJhdGVneTplLnNjcm9sbFN0cmF0ZWd5fHx0aGlzLl9zY3JvbGxTdHJhdGVneSgpLHBhbmVsQ2xhc3M6ZS5wYW5lbENsYXNzLGhhc0JhY2tkcm9wOmUuaGFzQmFja2Ryb3AsZGlyZWN0aW9uOmUuZGlyZWN0aW9uLG1pbldpZHRoOmUubWluV2lkdGgsbWluSGVpZ2h0OmUubWluSGVpZ2h0LG1heFdpZHRoOmUubWF4V2lkdGgsbWF4SGVpZ2h0OmUubWF4SGVpZ2h0LHdpZHRoOmUud2lkdGgsaGVpZ2h0OmUuaGVpZ2h0LGRpc3Bvc2VPbk5hdmlnYXRpb246ZS5jbG9zZU9uTmF2aWdhdGlvbn0pO3JldHVybiBlLmJhY2tkcm9wQ2xhc3MmJihpLmJhY2tkcm9wQ2xhc3M9ZS5iYWNrZHJvcENsYXNzKSxpfV9hdHRhY2hDb250YWluZXIoZSxpLHIpe2xldCBhLG89ci5pbmplY3Rvcnx8ci52aWV3Q29udGFpbmVyUmVmPy5pbmplY3RvcixzPVt7cHJvdmlkZTpvZyx1c2VWYWx1ZTpyfSx7cHJvdmlkZTpsdyx1c2VWYWx1ZTppfSx7cHJvdmlkZTpkZCx1c2VWYWx1ZTplfV07ci5jb250YWluZXI/ImZ1bmN0aW9uIj09dHlwZW9mIHIuY29udGFpbmVyP2E9ci5jb250YWluZXI6KGE9ci5jb250YWluZXIudHlwZSxzLnB1c2goLi4uci5jb250YWluZXIucHJvdmlkZXJzKHIpKSk6YT1OSDtsZXQgbD1uZXcgJGMoYSxyLnZpZXdDb250YWluZXJSZWYsWG4uY3JlYXRlKHtwYXJlbnQ6b3x8dGhpcy5faW5qZWN0b3IscHJvdmlkZXJzOnN9KSxyLmNvbXBvbmVudEZhY3RvcnlSZXNvbHZlcik7cmV0dXJuIGUuYXR0YWNoKGwpLmluc3RhbmNlfV9hdHRhY2hEaWFsb2dDb250ZW50KGUsaSxyLG8pe2lmKGUgaW5zdGFuY2VvZiBWaSl7bGV0IHM9dGhpcy5fY3JlYXRlSW5qZWN0b3IobyxpLHIsdm9pZCAwKSxhPXskaW1wbGljaXQ6by5kYXRhLGRpYWxvZ1JlZjppfTtvLnRlbXBsYXRlQ29udGV4dCYmKGE9ey4uLmEsLi4uImZ1bmN0aW9uIj09dHlwZW9mIG8udGVtcGxhdGVDb250ZXh0P28udGVtcGxhdGVDb250ZXh0KCk6by50ZW1wbGF0ZUNvbnRleHR9KSxyLmF0dGFjaFRlbXBsYXRlUG9ydGFsKG5ldyBrcyhlLG51bGwsYSxzKSl9ZWxzZXtsZXQgcz10aGlzLl9jcmVhdGVJbmplY3RvcihvLGkscix0aGlzLl9pbmplY3RvciksYT1yLmF0dGFjaENvbXBvbmVudFBvcnRhbChuZXcgJGMoZSxvLnZpZXdDb250YWluZXJSZWYscyxvLmNvbXBvbmVudEZhY3RvcnlSZXNvbHZlcikpO2kuY29tcG9uZW50SW5zdGFuY2U9YS5pbnN0YW5jZX19X2NyZWF0ZUluamVjdG9yKGUsaSxyLG8pe2xldCBzPWUuaW5qZWN0b3J8fGUudmlld0NvbnRhaW5lclJlZj8uaW5qZWN0b3IsYT1be3Byb3ZpZGU6ek9lLHVzZVZhbHVlOmUuZGF0YX0se3Byb3ZpZGU6bHcsdXNlVmFsdWU6aX1dO3JldHVybiBlLnByb3ZpZGVycyYmKCJmdW5jdGlvbiI9PXR5cGVvZiBlLnByb3ZpZGVycz9hLnB1c2goLi4uZS5wcm92aWRlcnMoaSxlLHIpKTphLnB1c2goLi4uZS5wcm92aWRlcnMpKSxlLmRpcmVjdGlvbiYmKCFzfHwhcy5nZXQoJGksbnVsbCxkaS5PcHRpb25hbCkpJiZhLnB1c2goe3Byb3ZpZGU6JGksdXNlVmFsdWU6e3ZhbHVlOmUuZGlyZWN0aW9uLGNoYW5nZTpYdCgpfX0pLFhuLmNyZWF0ZSh7cGFyZW50OnN8fG8scHJvdmlkZXJzOmF9KX1fcmVtb3ZlT3BlbkRpYWxvZyhlLGkpe2xldCByPXRoaXMub3BlbkRpYWxvZ3MuaW5kZXhPZihlKTtyPi0xJiYodGhpcy5vcGVuRGlhbG9ncy5zcGxpY2UociwxKSx0aGlzLm9wZW5EaWFsb2dzLmxlbmd0aHx8KHRoaXMuX2FyaWFIaWRkZW5FbGVtZW50cy5mb3JFYWNoKChvLHMpPT57bz9zLnNldEF0dHJpYnV0ZSgiYXJpYS1oaWRkZW4iLG8pOnMucmVtb3ZlQXR0cmlidXRlKCJhcmlhLWhpZGRlbiIpfSksdGhpcy5fYXJpYUhpZGRlbkVsZW1lbnRzLmNsZWFyKCksaSYmdGhpcy5fZ2V0QWZ0ZXJBbGxDbG9zZWQoKS5uZXh0KCkpKX1faGlkZU5vbkRpYWxvZ0NvbnRlbnRGcm9tQXNzaXN0aXZlVGVjaG5vbG9neSgpe2xldCBlPXRoaXMuX292ZXJsYXlDb250YWluZXIuZ2V0Q29udGFpbmVyRWxlbWVudCgpO2lmKGUucGFyZW50RWxlbWVudCl7bGV0IGk9ZS5wYXJlbnRFbGVtZW50LmNoaWxkcmVuO2ZvcihsZXQgcj1pLmxlbmd0aC0xO3I+LTE7ci0tKXtsZXQgbz1pW3JdO28hPT1lJiYiU0NSSVBUIiE9PW8ubm9kZU5hbWUmJiJTVFlMRSIhPT1vLm5vZGVOYW1lJiYhby5oYXNBdHRyaWJ1dGUoImFyaWEtbGl2ZSIpJiYodGhpcy5fYXJpYUhpZGRlbkVsZW1lbnRzLnNldChvLG8uZ2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIpKSxvLnNldEF0dHJpYnV0ZSgiYXJpYS1oaWRkZW4iLCJ0cnVlIikpfX19X2dldEFmdGVyQWxsQ2xvc2VkKCl7bGV0IGU9dGhpcy5fcGFyZW50RGlhbG9nO3JldHVybiBlP2UuX2dldEFmdGVyQWxsQ2xvc2VkKCk6dGhpcy5fYWZ0ZXJBbGxDbG9zZWRBdFRoaXNMZXZlbH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaih0ciksaihYbiksaihqT2UsOCksaihuLDEyKSxqKFJ2KSxqKFB0ZSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpO2Z1bmN0aW9uIEZIKG4sdCl7bGV0IGU9bi5sZW5ndGg7Zm9yKDtlLS07KXQobltlXSl9dmFyIFJ0ZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7cHJvdmlkZXJzOltMSCxXT2VdLGltcG9ydHM6W3NzLGV1LEV2LGV1XX0pLG59KSgpO2Z1bmN0aW9uIFlPZShuLHQpe312YXIga3Y9e3BhcmFtczp7ZW50ZXJBbmltYXRpb25EdXJhdGlvbjoiMTUwbXMiLGV4aXRBbmltYXRpb25EdXJhdGlvbjoiNzVtcyJ9fSxYT2U9e2RpYWxvZ0NvbnRhaW5lcjpLcigiZGlhbG9nQ29udGFpbmVyIixba2koInZvaWQsIGV4aXQiLGduKHtvcGFjaXR5OjAsdHJhbnNmb3JtOiJzY2FsZSgwLjcpIn0pKSxraSgiZW50ZXIiLGduKHt0cmFuc2Zvcm06Im5vbmUifSkpLExpKCIqID0+IGVudGVyIix4NShbamkoInt7ZW50ZXJBbmltYXRpb25EdXJhdGlvbn19IGN1YmljLWJlemllcigwLCAwLCAwLjIsIDEpIixnbih7dHJhbnNmb3JtOiJub25lIixvcGFjaXR5OjF9KSksSW0oIkAqIixBbSgpLHtvcHRpb25hbDohMH0pXSksa3YpLExpKCIqID0+IHZvaWQsICogPT4gZXhpdCIseDUoW2ppKCJ7e2V4aXRBbmltYXRpb25EdXJhdGlvbn19IGN1YmljLWJlemllcigwLjQsIDAuMCwgMC4yLCAxKSIsZ24oe29wYWNpdHk6MH0pKSxJbSgiQCoiLEFtKCkse29wdGlvbmFsOiEwfSldKSxrdildKX0sRnY9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnJvbGU9ImRpYWxvZyIsdGhpcy5wYW5lbENsYXNzPSIiLHRoaXMuaGFzQmFja2Ryb3A9ITAsdGhpcy5iYWNrZHJvcENsYXNzPSIiLHRoaXMuZGlzYWJsZUNsb3NlPSExLHRoaXMud2lkdGg9IiIsdGhpcy5oZWlnaHQ9IiIsdGhpcy5tYXhXaWR0aD0iODB2dyIsdGhpcy5kYXRhPW51bGwsdGhpcy5hcmlhRGVzY3JpYmVkQnk9bnVsbCx0aGlzLmFyaWFMYWJlbGxlZEJ5PW51bGwsdGhpcy5hcmlhTGFiZWw9bnVsbCx0aGlzLmFyaWFNb2RhbD0hMCx0aGlzLmF1dG9Gb2N1cz0iZmlyc3QtdGFiYmFibGUiLHRoaXMucmVzdG9yZUZvY3VzPSEwLHRoaXMuZGVsYXlGb2N1c1RyYXA9ITAsdGhpcy5jbG9zZU9uTmF2aWdhdGlvbj0hMCx0aGlzLmVudGVyQW5pbWF0aW9uRHVyYXRpb249a3YucGFyYW1zLmVudGVyQW5pbWF0aW9uRHVyYXRpb24sdGhpcy5leGl0QW5pbWF0aW9uRHVyYXRpb249a3YucGFyYW1zLmV4aXRBbmltYXRpb25EdXJhdGlvbn19LFFPZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgTkh7Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEsbCxjKXtzdXBlcihlLGkscixvLHMsYSxsLGMpLHRoaXMuX2FuaW1hdGlvblN0YXRlQ2hhbmdlZD1uZXcgR31fY2FwdHVyZUluaXRpYWxGb2N1cygpe3RoaXMuX2NvbmZpZy5kZWxheUZvY3VzVHJhcHx8dGhpcy5fdHJhcEZvY3VzKCl9X29wZW5BbmltYXRpb25Eb25lKGUpe3RoaXMuX2NvbmZpZy5kZWxheUZvY3VzVHJhcCYmdGhpcy5fdHJhcEZvY3VzKCksdGhpcy5fYW5pbWF0aW9uU3RhdGVDaGFuZ2VkLm5leHQoe3N0YXRlOiJvcGVuZWQiLHRvdGFsVGltZTplfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0oSk0pLE0oSHQsOCksTShGdiksTShTdiksTShfdCksTShkZCksTShGcikpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5nLWNvbXBvbmVudCJdXSxmZWF0dXJlczpbdHRdLGRlY2xzOjAsdmFyczowLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7fSxlbmNhcHN1bGF0aW9uOjJ9KSxufSkoKSxLT2U9KCgpPT57Y2xhc3MgbiBleHRlbmRzIFFPZXtjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsLGMsdSl7c3VwZXIoZSxpLHIsbyxzLGEsbCx1KSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj1jLHRoaXMuX3N0YXRlPSJlbnRlciJ9X29uQW5pbWF0aW9uRG9uZSh7dG9TdGF0ZTplLHRvdGFsVGltZTppfSl7ImVudGVyIj09PWU/dGhpcy5fb3BlbkFuaW1hdGlvbkRvbmUoaSk6ImV4aXQiPT09ZSYmdGhpcy5fYW5pbWF0aW9uU3RhdGVDaGFuZ2VkLm5leHQoe3N0YXRlOiJjbG9zZWQiLHRvdGFsVGltZTppfSl9X29uQW5pbWF0aW9uU3RhcnQoe3RvU3RhdGU6ZSx0b3RhbFRpbWU6aX0peyJlbnRlciI9PT1lP3RoaXMuX2FuaW1hdGlvblN0YXRlQ2hhbmdlZC5uZXh0KHtzdGF0ZToib3BlbmluZyIsdG90YWxUaW1lOml9KTooImV4aXQiPT09ZXx8InZvaWQiPT09ZSkmJnRoaXMuX2FuaW1hdGlvblN0YXRlQ2hhbmdlZC5uZXh0KHtzdGF0ZToiY2xvc2luZyIsdG90YWxUaW1lOml9KX1fc3RhcnRFeGl0QW5pbWF0aW9uKCl7dGhpcy5fc3RhdGU9ImV4aXQiLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpfV9nZXRBbmltYXRpb25TdGF0ZSgpe3JldHVybnt2YWx1ZTp0aGlzLl9zdGF0ZSxwYXJhbXM6e2VudGVyQW5pbWF0aW9uRHVyYXRpb246dGhpcy5fY29uZmlnLmVudGVyQW5pbWF0aW9uRHVyYXRpb258fGt2LnBhcmFtcy5lbnRlckFuaW1hdGlvbkR1cmF0aW9uLGV4aXRBbmltYXRpb25EdXJhdGlvbjp0aGlzLl9jb25maWcuZXhpdEFuaW1hdGlvbkR1cmF0aW9ufHxrdi5wYXJhbXMuZXhpdEFuaW1hdGlvbkR1cmF0aW9ufX19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0oSk0pLE0oSHQsOCksTShGdiksTShTdiksTShfdCksTShkZCksTShubiksTShGcikpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1kaWFsb2ctY29udGFpbmVyIl1dLGhvc3RBdHRyczpbInRhYmluZGV4IiwiLTEiLDEsIm1hdC1kaWFsb2ctY29udGFpbmVyIl0saG9zdFZhcnM6Nyxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJmlfKCJAZGlhbG9nQ29udGFpbmVyLnN0YXJ0IixmdW5jdGlvbihvKXtyZXR1cm4gaS5fb25BbmltYXRpb25TdGFydChvKX0pKCJAZGlhbG9nQ29udGFpbmVyLmRvbmUiLGZ1bmN0aW9uKG8pe3JldHVybiBpLl9vbkFuaW1hdGlvbkRvbmUobyl9KSwyJmUmJihfcygiaWQiLGkuX2NvbmZpZy5pZCksemUoImFyaWEtbW9kYWwiLGkuX2NvbmZpZy5hcmlhTW9kYWwpKCJyb2xlIixpLl9jb25maWcucm9sZSkoImFyaWEtbGFiZWxsZWRieSIsaS5fY29uZmlnLmFyaWFMYWJlbD9udWxsOmkuX2FyaWFMYWJlbGxlZEJ5KSgiYXJpYS1sYWJlbCIsaS5fY29uZmlnLmFyaWFMYWJlbCkoImFyaWEtZGVzY3JpYmVkYnkiLGkuX2NvbmZpZy5hcmlhRGVzY3JpYmVkQnl8fG51bGwpLHJfKCJAZGlhbG9nQ29udGFpbmVyIixpLl9nZXRBbmltYXRpb25TdGF0ZSgpKSl9LGZlYXR1cmVzOlt0dF0sZGVjbHM6MSx2YXJzOjAsY29uc3RzOltbImNka1BvcnRhbE91dGxldCIsIiJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmRSgwLFlPZSwwLDAsIm5nLXRlbXBsYXRlIiwwKX0sZGVwZW5kZW5jaWVzOltkYV0sc3R5bGVzOlsiLm1hdC1kaWFsb2ctY29udGFpbmVye2Rpc3BsYXk6YmxvY2s7cGFkZGluZzoyNHB4O2JvcmRlci1yYWRpdXM6NHB4O2JveC1zaXppbmc6Ym9yZGVyLWJveDtvdmVyZmxvdzphdXRvO291dGxpbmU6MDt3aWR0aDoxMDAlO2hlaWdodDoxMDAlO21pbi1oZWlnaHQ6aW5oZXJpdDttYXgtaGVpZ2h0OmluaGVyaXR9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWRpYWxvZy1jb250YWluZXJ7b3V0bGluZTpzb2xpZCAxcHh9Lm1hdC1kaWFsb2ctY29udGVudHtkaXNwbGF5OmJsb2NrO21hcmdpbjowIC0yNHB4O3BhZGRpbmc6MCAyNHB4O21heC1oZWlnaHQ6NjV2aDtvdmVyZmxvdzphdXRvOy13ZWJraXQtb3ZlcmZsb3ctc2Nyb2xsaW5nOnRvdWNofS5tYXQtZGlhbG9nLXRpdGxle21hcmdpbjowIDAgMjBweDtkaXNwbGF5OmJsb2NrfS5tYXQtZGlhbG9nLWFjdGlvbnN7cGFkZGluZzo4cHggMDtkaXNwbGF5OmZsZXg7ZmxleC13cmFwOndyYXA7bWluLWhlaWdodDo1MnB4O2FsaWduLWl0ZW1zOmNlbnRlcjtib3gtc2l6aW5nOmNvbnRlbnQtYm94O21hcmdpbi1ib3R0b206LTI0cHh9Lm1hdC1kaWFsb2ctYWN0aW9ucy5tYXQtZGlhbG9nLWFjdGlvbnMtYWxpZ24tY2VudGVyLC5tYXQtZGlhbG9nLWFjdGlvbnNbYWxpZ249Y2VudGVyXXtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyfS5tYXQtZGlhbG9nLWFjdGlvbnMubWF0LWRpYWxvZy1hY3Rpb25zLWFsaWduLWVuZCwubWF0LWRpYWxvZy1hY3Rpb25zW2FsaWduPWVuZF17anVzdGlmeS1jb250ZW50OmZsZXgtZW5kfS5tYXQtZGlhbG9nLWFjdGlvbnMgLm1hdC1idXR0b24tYmFzZSsubWF0LWJ1dHRvbi1iYXNlLC5tYXQtZGlhbG9nLWFjdGlvbnMgLm1hdC1tZGMtYnV0dG9uLWJhc2UrLm1hdC1tZGMtYnV0dG9uLWJhc2V7bWFyZ2luLWxlZnQ6OHB4fVtkaXI9cnRsXSAubWF0LWRpYWxvZy1hY3Rpb25zIC5tYXQtYnV0dG9uLWJhc2UrLm1hdC1idXR0b24tYmFzZSxbZGlyPXJ0bF0gLm1hdC1kaWFsb2ctYWN0aW9ucyAubWF0LW1kYy1idXR0b24tYmFzZSsubWF0LW1kYy1idXR0b24tYmFzZXttYXJnaW4tbGVmdDowO21hcmdpbi1yaWdodDo4cHh9Il0sZW5jYXBzdWxhdGlvbjoyLGRhdGE6e2FuaW1hdGlvbjpbWE9lLmRpYWxvZ0NvbnRhaW5lcl19fSksbn0pKCksdHU9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3RoaXMuX3JlZj10LHRoaXMuX2NvbnRhaW5lckluc3RhbmNlPWksdGhpcy5fYWZ0ZXJPcGVuZWQ9bmV3IGtlLHRoaXMuX2JlZm9yZUNsb3NlZD1uZXcga2UsdGhpcy5fc3RhdGU9MCx0aGlzLmRpc2FibGVDbG9zZT1lLmRpc2FibGVDbG9zZSx0aGlzLmlkPXQuaWQsaS5fYW5pbWF0aW9uU3RhdGVDaGFuZ2VkLnBpcGUoWWUocj0+Im9wZW5lZCI9PT1yLnN0YXRlKSxRdCgxKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5fYWZ0ZXJPcGVuZWQubmV4dCgpLHRoaXMuX2FmdGVyT3BlbmVkLmNvbXBsZXRlKCl9KSxpLl9hbmltYXRpb25TdGF0ZUNoYW5nZWQucGlwZShZZShyPT4iY2xvc2VkIj09PXIuc3RhdGUpLFF0KDEpKS5zdWJzY3JpYmUoKCk9PntjbGVhclRpbWVvdXQodGhpcy5fY2xvc2VGYWxsYmFja1RpbWVvdXQpLHRoaXMuX2ZpbmlzaERpYWxvZ0Nsb3NlKCl9KSx0Lm92ZXJsYXlSZWYuZGV0YWNobWVudHMoKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9iZWZvcmVDbG9zZWQubmV4dCh0aGlzLl9yZXN1bHQpLHRoaXMuX2JlZm9yZUNsb3NlZC5jb21wbGV0ZSgpLHRoaXMuX2ZpbmlzaERpYWxvZ0Nsb3NlKCl9KSxKdCh0aGlzLmJhY2tkcm9wQ2xpY2soKSx0aGlzLmtleWRvd25FdmVudHMoKS5waXBlKFllKHI9PjI3PT09ci5rZXlDb2RlJiYhdGhpcy5kaXNhYmxlQ2xvc2UmJiFrcihyKSkpKS5zdWJzY3JpYmUocj0+e3RoaXMuZGlzYWJsZUNsb3NlfHwoci5wcmV2ZW50RGVmYXVsdCgpLE90ZSh0aGlzLCJrZXlkb3duIj09PXIudHlwZT8ia2V5Ym9hcmQiOiJtb3VzZSIpKX0pfWNsb3NlKHQpe3RoaXMuX3Jlc3VsdD10LHRoaXMuX2NvbnRhaW5lckluc3RhbmNlLl9hbmltYXRpb25TdGF0ZUNoYW5nZWQucGlwZShZZShlPT4iY2xvc2luZyI9PT1lLnN0YXRlKSxRdCgxKSkuc3Vic2NyaWJlKGU9Pnt0aGlzLl9iZWZvcmVDbG9zZWQubmV4dCh0KSx0aGlzLl9iZWZvcmVDbG9zZWQuY29tcGxldGUoKSx0aGlzLl9yZWYub3ZlcmxheVJlZi5kZXRhY2hCYWNrZHJvcCgpLHRoaXMuX2Nsb3NlRmFsbGJhY2tUaW1lb3V0PXNldFRpbWVvdXQoKCk9PnRoaXMuX2ZpbmlzaERpYWxvZ0Nsb3NlKCksZS50b3RhbFRpbWUrMTAwKX0pLHRoaXMuX3N0YXRlPTEsdGhpcy5fY29udGFpbmVySW5zdGFuY2UuX3N0YXJ0RXhpdEFuaW1hdGlvbigpfWFmdGVyT3BlbmVkKCl7cmV0dXJuIHRoaXMuX2FmdGVyT3BlbmVkfWFmdGVyQ2xvc2VkKCl7cmV0dXJuIHRoaXMuX3JlZi5jbG9zZWR9YmVmb3JlQ2xvc2VkKCl7cmV0dXJuIHRoaXMuX2JlZm9yZUNsb3NlZH1iYWNrZHJvcENsaWNrKCl7cmV0dXJuIHRoaXMuX3JlZi5iYWNrZHJvcENsaWNrfWtleWRvd25FdmVudHMoKXtyZXR1cm4gdGhpcy5fcmVmLmtleWRvd25FdmVudHN9dXBkYXRlUG9zaXRpb24odCl7bGV0IGU9dGhpcy5fcmVmLmNvbmZpZy5wb3NpdGlvblN0cmF0ZWd5O3JldHVybiB0JiYodC5sZWZ0fHx0LnJpZ2h0KT90LmxlZnQ/ZS5sZWZ0KHQubGVmdCk6ZS5yaWdodCh0LnJpZ2h0KTplLmNlbnRlckhvcml6b250YWxseSgpLHQmJih0LnRvcHx8dC5ib3R0b20pP3QudG9wP2UudG9wKHQudG9wKTplLmJvdHRvbSh0LmJvdHRvbSk6ZS5jZW50ZXJWZXJ0aWNhbGx5KCksdGhpcy5fcmVmLnVwZGF0ZVBvc2l0aW9uKCksdGhpc311cGRhdGVTaXplKHQ9IiIsZT0iIil7cmV0dXJuIHRoaXMuX3JlZi51cGRhdGVTaXplKHQsZSksdGhpc31hZGRQYW5lbENsYXNzKHQpe3JldHVybiB0aGlzLl9yZWYuYWRkUGFuZWxDbGFzcyh0KSx0aGlzfXJlbW92ZVBhbmVsQ2xhc3ModCl7cmV0dXJuIHRoaXMuX3JlZi5yZW1vdmVQYW5lbENsYXNzKHQpLHRoaXN9Z2V0U3RhdGUoKXtyZXR1cm4gdGhpcy5fc3RhdGV9X2ZpbmlzaERpYWxvZ0Nsb3NlKCl7dGhpcy5fc3RhdGU9Mix0aGlzLl9yZWYuY2xvc2UodGhpcy5fcmVzdWx0LHtmb2N1c09yaWdpbjp0aGlzLl9jbG9zZUludGVyYWN0aW9uVHlwZX0pLHRoaXMuY29tcG9uZW50SW5zdGFuY2U9bnVsbH19O2Z1bmN0aW9uIE90ZShuLHQsZSl7cmV0dXJuIG4uX2Nsb3NlSW50ZXJhY3Rpb25UeXBlPXQsbi5jbG9zZShlKX12YXIgY3c9bmV3IHBlKCJNYXREaWFsb2dEYXRhIiksWk9lPW5ldyBwZSgibWF0LWRpYWxvZy1kZWZhdWx0LW9wdGlvbnMiKSxrdGU9bmV3IHBlKCJtYXQtZGlhbG9nLXNjcm9sbC1zdHJhdGVneSIpLCRPZT17cHJvdmlkZTprdGUsZGVwczpbdHJdLHVzZUZhY3Rvcnk6ZnVuY3Rpb24obil7cmV0dXJuKCk9Pm4uc2Nyb2xsU3RyYXRlZ2llcy5ibG9jaygpfX0sZWtlPTAsdGtlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEsbCxjLHUsZCl7dGhpcy5fb3ZlcmxheT1lLHRoaXMuX2RlZmF1bHRPcHRpb25zPXIsdGhpcy5fcGFyZW50RGlhbG9nPW8sdGhpcy5fZGlhbG9nUmVmQ29uc3RydWN0b3I9bCx0aGlzLl9kaWFsb2dDb250YWluZXJUeXBlPWMsdGhpcy5fZGlhbG9nRGF0YVRva2VuPXUsdGhpcy5fb3BlbkRpYWxvZ3NBdFRoaXNMZXZlbD1bXSx0aGlzLl9hZnRlckFsbENsb3NlZEF0VGhpc0xldmVsPW5ldyBrZSx0aGlzLl9hZnRlck9wZW5lZEF0VGhpc0xldmVsPW5ldyBrZSx0aGlzLl9pZFByZWZpeD0ibWF0LWRpYWxvZy0iLHRoaXMuYWZ0ZXJBbGxDbG9zZWQ9UWEoKCk9PnRoaXMub3BlbkRpYWxvZ3MubGVuZ3RoP3RoaXMuX2dldEFmdGVyQWxsQ2xvc2VkKCk6dGhpcy5fZ2V0QWZ0ZXJBbGxDbG9zZWQoKS5waXBlKHpuKHZvaWQgMCkpKSx0aGlzLl9zY3JvbGxTdHJhdGVneT1hLHRoaXMuX2RpYWxvZz1pLmdldChMSCl9Z2V0IG9wZW5EaWFsb2dzKCl7cmV0dXJuIHRoaXMuX3BhcmVudERpYWxvZz90aGlzLl9wYXJlbnREaWFsb2cub3BlbkRpYWxvZ3M6dGhpcy5fb3BlbkRpYWxvZ3NBdFRoaXNMZXZlbH1nZXQgYWZ0ZXJPcGVuZWQoKXtyZXR1cm4gdGhpcy5fcGFyZW50RGlhbG9nP3RoaXMuX3BhcmVudERpYWxvZy5hZnRlck9wZW5lZDp0aGlzLl9hZnRlck9wZW5lZEF0VGhpc0xldmVsfV9nZXRBZnRlckFsbENsb3NlZCgpe2xldCBlPXRoaXMuX3BhcmVudERpYWxvZztyZXR1cm4gZT9lLl9nZXRBZnRlckFsbENsb3NlZCgpOnRoaXMuX2FmdGVyQWxsQ2xvc2VkQXRUaGlzTGV2ZWx9b3BlbihlLGkpe2xldCByOyhpPXsuLi50aGlzLl9kZWZhdWx0T3B0aW9uc3x8bmV3IEZ2LC4uLml9KS5pZD1pLmlkfHxgJHt0aGlzLl9pZFByZWZpeH0ke2VrZSsrfWAsaS5zY3JvbGxTdHJhdGVneT1pLnNjcm9sbFN0cmF0ZWd5fHx0aGlzLl9zY3JvbGxTdHJhdGVneSgpO2xldCBvPXRoaXMuX2RpYWxvZy5vcGVuKGUsey4uLmkscG9zaXRpb25TdHJhdGVneTp0aGlzLl9vdmVybGF5LnBvc2l0aW9uKCkuZ2xvYmFsKCkuY2VudGVySG9yaXpvbnRhbGx5KCkuY2VudGVyVmVydGljYWxseSgpLGRpc2FibGVDbG9zZTohMCxjbG9zZU9uRGVzdHJveTohMSxjb250YWluZXI6e3R5cGU6dGhpcy5fZGlhbG9nQ29udGFpbmVyVHlwZSxwcm92aWRlcnM6KCk9Plt7cHJvdmlkZTpGdix1c2VWYWx1ZTppfSx7cHJvdmlkZTpvZyx1c2VWYWx1ZTppfV19LHRlbXBsYXRlQ29udGV4dDooKT0+KHtkaWFsb2dSZWY6cn0pLHByb3ZpZGVyczoocyxhLGwpPT4ocj1uZXcgdGhpcy5fZGlhbG9nUmVmQ29uc3RydWN0b3IocyxpLGwpLHIudXBkYXRlUG9zaXRpb24oaT8ucG9zaXRpb24pLFt7cHJvdmlkZTp0aGlzLl9kaWFsb2dDb250YWluZXJUeXBlLHVzZVZhbHVlOmx9LHtwcm92aWRlOnRoaXMuX2RpYWxvZ0RhdGFUb2tlbix1c2VWYWx1ZTphLmRhdGF9LHtwcm92aWRlOnRoaXMuX2RpYWxvZ1JlZkNvbnN0cnVjdG9yLHVzZVZhbHVlOnJ9XSl9KTtyZXR1cm4gci5jb21wb25lbnRJbnN0YW5jZT1vLmNvbXBvbmVudEluc3RhbmNlLHRoaXMub3BlbkRpYWxvZ3MucHVzaChyKSx0aGlzLmFmdGVyT3BlbmVkLm5leHQociksci5hZnRlckNsb3NlZCgpLnN1YnNjcmliZSgoKT0+e2xldCBzPXRoaXMub3BlbkRpYWxvZ3MuaW5kZXhPZihyKTtzPi0xJiYodGhpcy5vcGVuRGlhbG9ncy5zcGxpY2UocywxKSx0aGlzLm9wZW5EaWFsb2dzLmxlbmd0aHx8dGhpcy5fZ2V0QWZ0ZXJBbGxDbG9zZWQoKS5uZXh0KCkpfSkscn1jbG9zZUFsbCgpe3RoaXMuX2Nsb3NlRGlhbG9ncyh0aGlzLm9wZW5EaWFsb2dzKX1nZXREaWFsb2dCeUlkKGUpe3JldHVybiB0aGlzLm9wZW5EaWFsb2dzLmZpbmQoaT0+aS5pZD09PWUpfW5nT25EZXN0cm95KCl7dGhpcy5fY2xvc2VEaWFsb2dzKHRoaXMuX29wZW5EaWFsb2dzQXRUaGlzTGV2ZWwpLHRoaXMuX2FmdGVyQWxsQ2xvc2VkQXRUaGlzTGV2ZWwuY29tcGxldGUoKSx0aGlzLl9hZnRlck9wZW5lZEF0VGhpc0xldmVsLmNvbXBsZXRlKCl9X2Nsb3NlRGlhbG9ncyhlKXtsZXQgaT1lLmxlbmd0aDtmb3IoO2ktLTspZVtpXS5jbG9zZSgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7bmwoKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSx2bD0oKCk9PntjbGFzcyBuIGV4dGVuZHMgdGtle2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhLGwsYyl7c3VwZXIoZSxpLG8sYSxsLHMsdHUsS09lLGN3LGMpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKHRyKSxqKFhuKSxqKGlNLDgpLGooWk9lLDgpLGooa3RlKSxqKG4sMTIpLGooUnYpLGooUGksOCkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLG5rZT0wLFQyPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIpe3RoaXMuZGlhbG9nUmVmPWUsdGhpcy5fZWxlbWVudFJlZj1pLHRoaXMuX2RpYWxvZz1yLHRoaXMudHlwZT0iYnV0dG9uIn1uZ09uSW5pdCgpe3RoaXMuZGlhbG9nUmVmfHwodGhpcy5kaWFsb2dSZWY9TnRlKHRoaXMuX2VsZW1lbnRSZWYsdGhpcy5fZGlhbG9nLm9wZW5EaWFsb2dzKSl9bmdPbkNoYW5nZXMoZSl7bGV0IGk9ZS5fbWF0RGlhbG9nQ2xvc2V8fGUuX21hdERpYWxvZ0Nsb3NlUmVzdWx0O2kmJih0aGlzLmRpYWxvZ1Jlc3VsdD1pLmN1cnJlbnRWYWx1ZSl9X29uQnV0dG9uQ2xpY2soZSl7T3RlKHRoaXMuZGlhbG9nUmVmLDA9PT1lLnNjcmVlblgmJjA9PT1lLnNjcmVlblk/ImtleWJvYXJkIjoibW91c2UiLHRoaXMuZGlhbG9nUmVzdWx0KX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTSh0dSw4KSxNKFJlKSxNKHZsKSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdC1kaWFsb2ctY2xvc2UiLCIiXSxbIiIsIm1hdERpYWxvZ0Nsb3NlIiwiIl1dLGhvc3RWYXJzOjIsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MSZlJiZQKCJjbGljayIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX29uQnV0dG9uQ2xpY2sobyl9KSwyJmUmJnplKCJhcmlhLWxhYmVsIixpLmFyaWFMYWJlbHx8bnVsbCkoInR5cGUiLGkudHlwZSl9LGlucHV0czp7YXJpYUxhYmVsOlsiYXJpYS1sYWJlbCIsImFyaWFMYWJlbCJdLHR5cGU6InR5cGUiLGRpYWxvZ1Jlc3VsdDpbIm1hdC1kaWFsb2ctY2xvc2UiLCJkaWFsb2dSZXN1bHQiXSxfbWF0RGlhbG9nQ2xvc2U6WyJtYXREaWFsb2dDbG9zZSIsIl9tYXREaWFsb2dDbG9zZSJdfSxleHBvcnRBczpbIm1hdERpYWxvZ0Nsb3NlIl0sZmVhdHVyZXM6W0Z0XX0pLG59KSgpLEZ0ZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLl9kaWFsb2dSZWY9ZSx0aGlzLl9lbGVtZW50UmVmPWksdGhpcy5fZGlhbG9nPXIsdGhpcy5pZD0ibWF0LWRpYWxvZy10aXRsZS0iK25rZSsrfW5nT25Jbml0KCl7dGhpcy5fZGlhbG9nUmVmfHwodGhpcy5fZGlhbG9nUmVmPU50ZSh0aGlzLl9lbGVtZW50UmVmLHRoaXMuX2RpYWxvZy5vcGVuRGlhbG9ncykpLHRoaXMuX2RpYWxvZ1JlZiYmUHJvbWlzZS5yZXNvbHZlKCkudGhlbigoKT0+e2xldCBlPXRoaXMuX2RpYWxvZ1JlZi5fY29udGFpbmVySW5zdGFuY2U7ZSYmIWUuX2FyaWFMYWJlbGxlZEJ5JiYoZS5fYXJpYUxhYmVsbGVkQnk9dGhpcy5pZCl9KX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTSh0dSw4KSxNKFJlKSxNKHZsKSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdC1kaWFsb2ctdGl0bGUiLCIiXSxbIiIsIm1hdERpYWxvZ1RpdGxlIiwiIl1dLGhvc3RBdHRyczpbMSwibWF0LWRpYWxvZy10aXRsZSJdLGhvc3RWYXJzOjEsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZfcygiaWQiLGkuaWQpfSxpbnB1dHM6e2lkOiJpZCJ9LGV4cG9ydEFzOlsibWF0RGlhbG9nVGl0bGUiXX0pLG59KSgpLEQyPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJtYXQtZGlhbG9nLWNvbnRlbnQiLCIiXSxbIm1hdC1kaWFsb2ctY29udGVudCJdLFsiIiwibWF0RGlhbG9nQ29udGVudCIsIiJdXSxob3N0QXR0cnM6WzEsIm1hdC1kaWFsb2ctY29udGVudCJdfSksbn0pKCksQTI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuYWxpZ249InN0YXJ0In19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdC1kaWFsb2ctYWN0aW9ucyIsIiJdLFsibWF0LWRpYWxvZy1hY3Rpb25zIl0sWyIiLCJtYXREaWFsb2dBY3Rpb25zIiwiIl1dLGhvc3RBdHRyczpbMSwibWF0LWRpYWxvZy1hY3Rpb25zIl0saG9zdFZhcnM6NCxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJmV0KCJtYXQtZGlhbG9nLWFjdGlvbnMtYWxpZ24tY2VudGVyIiwiY2VudGVyIj09PWkuYWxpZ24pKCJtYXQtZGlhbG9nLWFjdGlvbnMtYWxpZ24tZW5kIiwiZW5kIj09PWkuYWxpZ24pfSxpbnB1dHM6e2FsaWduOiJhbGlnbiJ9fSksbn0pKCk7ZnVuY3Rpb24gTnRlKG4sdCl7bGV0IGU9bi5uYXRpdmVFbGVtZW50LnBhcmVudEVsZW1lbnQ7Zm9yKDtlJiYhZS5jbGFzc0xpc3QuY29udGFpbnMoIm1hdC1kaWFsb2ctY29udGFpbmVyIik7KWU9ZS5wYXJlbnRFbGVtZW50O3JldHVybiBlP3QuZmluZChpPT5pLmlkPT09ZS5pZCk6bnVsbH12YXIgT2g9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbdmwsJE9lXSxpbXBvcnRzOltSdGUsc3MsZXUsbG4sbG5dfSksbn0pKCksaWtlPVsiY29ubmVjdGlvbkNvbnRhaW5lciJdLHJrZT1bImlucHV0Q29udGFpbmVyIl0sb2tlPVsibGFiZWwiXTtmdW5jdGlvbiBza2Uobix0KXsxJm4mJihzbigwKSxfKDEsImRpdiIsMTQpLE8oMiwiZGl2IiwxNSkoMywiZGl2IiwxNikoNCwiZGl2IiwxNyksdigpLF8oNSwiZGl2IiwxOCksTyg2LCJkaXYiLDE1KSg3LCJkaXYiLDE2KSg4LCJkaXYiLDE3KSx2KCksYW4oKSl9ZnVuY3Rpb24gYWtlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IiwxOSksUCgiY2RrT2JzZXJ2ZUNvbnRlbnQiLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS51cGRhdGVPdXRsaW5lR2FwKCkpfSksVm4oMSwxKSx2KCl9MiZuJiZ5KCJjZGtPYnNlcnZlQ29udGVudERpc2FibGVkIiwib3V0bGluZSIhPVMoKS5hcHBlYXJhbmNlKX1mdW5jdGlvbiBsa2Uobix0KXtpZigxJm4mJihzbigwKSxWbigxLDIpLF8oMiwic3BhbiIpLEEoMyksdigpLGFuKCkpLDImbil7bGV0IGU9UygyKTtDKDMpLHl0KGUuX2NvbnRyb2wucGxhY2Vob2xkZXIpfX1mdW5jdGlvbiBja2Uobix0KXsxJm4mJlZuKDAsMyxbIipuZ1N3aXRjaENhc2UiLCJ0cnVlIl0pfWZ1bmN0aW9uIHVrZShuLHQpezEmbiYmKF8oMCwic3BhbiIsMjMpLEEoMSwiICoiKSx2KCkpfWZ1bmN0aW9uIGRrZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImxhYmVsIiwyMCwyMSksUCgiY2RrT2JzZXJ2ZUNvbnRlbnQiLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS51cGRhdGVPdXRsaW5lR2FwKCkpfSksRSgyLGxrZSw0LDEsIm5nLWNvbnRhaW5lciIsMTIpLEUoMyxja2UsMSwwLCJuZy1jb250ZW50IiwxMiksRSg0LHVrZSwyLDAsInNwYW4iLDIyKSx2KCl9aWYoMiZuKXtsZXQgZT1TKCk7ZXQoIm1hdC1lbXB0eSIsZS5fY29udHJvbC5lbXB0eSYmIWUuX3Nob3VsZEFsd2F5c0Zsb2F0KCkpKCJtYXQtZm9ybS1maWVsZC1lbXB0eSIsZS5fY29udHJvbC5lbXB0eSYmIWUuX3Nob3VsZEFsd2F5c0Zsb2F0KCkpKCJtYXQtYWNjZW50IiwiYWNjZW50Ij09ZS5jb2xvcikoIm1hdC13YXJuIiwid2FybiI9PWUuY29sb3IpLHkoImNka09ic2VydmVDb250ZW50RGlzYWJsZWQiLCJvdXRsaW5lIiE9ZS5hcHBlYXJhbmNlKSgiaWQiLGUuX2xhYmVsSWQpKCJuZ1N3aXRjaCIsZS5faGFzTGFiZWwoKSksemUoImZvciIsZS5fY29udHJvbC5pZCkoImFyaWEtb3ducyIsZS5fY29udHJvbC5pZCksQygyKSx5KCJuZ1N3aXRjaENhc2UiLCExKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsITApLEMoMSkseSgibmdJZiIsIWUuaGlkZVJlcXVpcmVkTWFya2VyJiZlLl9jb250cm9sLnJlcXVpcmVkJiYhZS5fY29udHJvbC5kaXNhYmxlZCl9fWZ1bmN0aW9uIHBrZShuLHQpezEmbiYmKF8oMCwiZGl2IiwyNCksVm4oMSw0KSx2KCkpfWZ1bmN0aW9uIGhrZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwyNSksTygxLCJzcGFuIiwyNiksdigpKSwyJm4pe2xldCBlPVMoKTtDKDEpLGV0KCJtYXQtYWNjZW50IiwiYWNjZW50Ij09ZS5jb2xvcikoIm1hdC13YXJuIiwid2FybiI9PWUuY29sb3IpfX1mdW5jdGlvbiBma2Uobix0KXsxJm4mJihfKDAsImRpdiIpLFZuKDEsNSksdigpKSwyJm4mJnkoIkB0cmFuc2l0aW9uTWVzc2FnZXMiLFMoKS5fc3Vic2NyaXB0QW5pbWF0aW9uU3RhdGUpfWZ1bmN0aW9uIG1rZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwzMCksQSgxKSx2KCkpLDImbil7bGV0IGU9UygyKTt5KCJpZCIsZS5faGludExhYmVsSWQpLEMoMSkseXQoZS5oaW50TGFiZWwpfX1mdW5jdGlvbiBna2Uobix0KXtpZigxJm4mJihfKDAsImRpdiIsMjcpLEUoMSxta2UsMiwyLCJkaXYiLDI4KSxWbigyLDYpLE8oMywiZGl2IiwyOSksVm4oNCw3KSx2KCkpLDImbil7bGV0IGU9UygpO3koIkB0cmFuc2l0aW9uTWVzc2FnZXMiLGUuX3N1YnNjcmlwdEFuaW1hdGlvblN0YXRlKSxDKDEpLHkoIm5nSWYiLGUuaGludExhYmVsKX19dmFyIF9rZT1bIioiLFtbIiIsIm1hdFByZWZpeCIsIiJdXSxbWyJtYXQtcGxhY2Vob2xkZXIiXV0sW1sibWF0LWxhYmVsIl1dLFtbIiIsIm1hdFN1ZmZpeCIsIiJdXSxbWyJtYXQtZXJyb3IiXV0sW1sibWF0LWhpbnQiLDMsImFsaWduIiwiZW5kIl1dLFtbIm1hdC1oaW50IiwiYWxpZ24iLCJlbmQiXV1dLHZrZT1bIioiLCJbbWF0UHJlZml4XSIsIm1hdC1wbGFjZWhvbGRlciIsIm1hdC1sYWJlbCIsIlttYXRTdWZmaXhdIiwibWF0LWVycm9yIiwibWF0LWhpbnQ6bm90KFthbGlnbj0nZW5kJ10pIiwibWF0LWhpbnRbYWxpZ249J2VuZCddIl0seWtlPTAsVnRlPW5ldyBwZSgiTWF0RXJyb3IiKSxIdGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuaWQ9Im1hdC1lcnJvci0iK3lrZSsrLGV8fGkubmF0aXZlRWxlbWVudC5zZXRBdHRyaWJ1dGUoImFyaWEtbGl2ZSIsInBvbGl0ZSIpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKSh2bygiYXJpYS1saXZlIiksTShSZSkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyJtYXQtZXJyb3IiXV0saG9zdEF0dHJzOlsiYXJpYS1hdG9taWMiLCJ0cnVlIiwxLCJtYXQtZXJyb3IiXSxob3N0VmFyczoxLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmemUoImlkIixpLmlkKX0saW5wdXRzOntpZDoiaWQifSxmZWF0dXJlczpbJHQoW3twcm92aWRlOlZ0ZSx1c2VFeGlzdGluZzpufV0pXX0pLG59KSgpLGJrZT17dHJhbnNpdGlvbk1lc3NhZ2VzOktyKCJ0cmFuc2l0aW9uTWVzc2FnZXMiLFtraSgiZW50ZXIiLGduKHtvcGFjaXR5OjEsdHJhbnNmb3JtOiJ0cmFuc2xhdGVZKDAlKSJ9KSksTGkoInZvaWQgPT4gZW50ZXIiLFtnbih7b3BhY2l0eTowLHRyYW5zZm9ybToidHJhbnNsYXRlWSgtNXB4KSJ9KSxqaSgiMzAwbXMgY3ViaWMtYmV6aWVyKDAuNTUsIDAsIDAuNTUsIDAuMikiKV0pXSl9LGtoPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpufSksbn0pKCkseGtlPW5ldyBwZSgiTWF0SGludCIpLE52PSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyJtYXQtbGFiZWwiXV19KSxufSkoKSxDa2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1wbGFjZWhvbGRlciJdXX0pLG59KSgpLE1rZT1uZXcgcGUoIk1hdFByZWZpeCIpLHdrZT1uZXcgcGUoIk1hdFN1ZmZpeCIpLEx0ZT0wLEVrZT1rbyhjbGFzc3tjb25zdHJ1Y3RvcihuKXt0aGlzLl9lbGVtZW50UmVmPW59fSwicHJpbWFyeSIpLFRrZT1uZXcgcGUoIk1BVF9GT1JNX0ZJRUxEX0RFRkFVTFRfT1BUSU9OUyIpLHNnPW5ldyBwZSgiTWF0Rm9ybUZpZWxkIikscGQ9KCgpPT57Y2xhc3MgbiBleHRlbmRzIEVrZXtjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsKXtzdXBlcihlKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj1pLHRoaXMuX2Rpcj1yLHRoaXMuX2RlZmF1bHRzPW8sdGhpcy5fcGxhdGZvcm09cyx0aGlzLl9uZ1pvbmU9YSx0aGlzLl9vdXRsaW5lR2FwQ2FsY3VsYXRpb25OZWVkZWRJbW1lZGlhdGVseT0hMSx0aGlzLl9vdXRsaW5lR2FwQ2FsY3VsYXRpb25OZWVkZWRPblN0YWJsZT0hMSx0aGlzLl9kZXN0cm95ZWQ9bmV3IGtlLHRoaXMuX2hpZGVSZXF1aXJlZE1hcmtlcj0hMSx0aGlzLl9zaG93QWx3YXlzQW5pbWF0ZT0hMSx0aGlzLl9zdWJzY3JpcHRBbmltYXRpb25TdGF0ZT0iIix0aGlzLl9oaW50TGFiZWw9IiIsdGhpcy5faGludExhYmVsSWQ9Im1hdC1oaW50LSIrTHRlKyssdGhpcy5fbGFiZWxJZD0ibWF0LWZvcm0tZmllbGQtbGFiZWwtIitMdGUrKyx0aGlzLmZsb2F0TGFiZWw9dGhpcy5fZ2V0RGVmYXVsdEZsb2F0TGFiZWxTdGF0ZSgpLHRoaXMuX2FuaW1hdGlvbnNFbmFibGVkPSJOb29wQW5pbWF0aW9ucyIhPT1sLHRoaXMuYXBwZWFyYW5jZT1vPy5hcHBlYXJhbmNlfHwibGVnYWN5IixvJiYodGhpcy5faGlkZVJlcXVpcmVkTWFya2VyPUJvb2xlYW4oby5oaWRlUmVxdWlyZWRNYXJrZXIpLG8uY29sb3ImJih0aGlzLmNvbG9yPXRoaXMuZGVmYXVsdENvbG9yPW8uY29sb3IpKX1nZXQgYXBwZWFyYW5jZSgpe3JldHVybiB0aGlzLl9hcHBlYXJhbmNlfXNldCBhcHBlYXJhbmNlKGUpe2xldCBpPXRoaXMuX2FwcGVhcmFuY2U7dGhpcy5fYXBwZWFyYW5jZT1lfHx0aGlzLl9kZWZhdWx0cz8uYXBwZWFyYW5jZXx8ImxlZ2FjeSIsIm91dGxpbmUiPT09dGhpcy5fYXBwZWFyYW5jZSYmaSE9PWUmJih0aGlzLl9vdXRsaW5lR2FwQ2FsY3VsYXRpb25OZWVkZWRPblN0YWJsZT0hMCl9Z2V0IGhpZGVSZXF1aXJlZE1hcmtlcigpe3JldHVybiB0aGlzLl9oaWRlUmVxdWlyZWRNYXJrZXJ9c2V0IGhpZGVSZXF1aXJlZE1hcmtlcihlKXt0aGlzLl9oaWRlUmVxdWlyZWRNYXJrZXI9UnQoZSl9X3Nob3VsZEFsd2F5c0Zsb2F0KCl7cmV0dXJuImFsd2F5cyI9PT10aGlzLmZsb2F0TGFiZWwmJiF0aGlzLl9zaG93QWx3YXlzQW5pbWF0ZX1fY2FuTGFiZWxGbG9hdCgpe3JldHVybiJuZXZlciIhPT10aGlzLmZsb2F0TGFiZWx9Z2V0IGhpbnRMYWJlbCgpe3JldHVybiB0aGlzLl9oaW50TGFiZWx9c2V0IGhpbnRMYWJlbChlKXt0aGlzLl9oaW50TGFiZWw9ZSx0aGlzLl9wcm9jZXNzSGludHMoKX1nZXQgZmxvYXRMYWJlbCgpe3JldHVybiJsZWdhY3kiIT09dGhpcy5hcHBlYXJhbmNlJiYibmV2ZXIiPT09dGhpcy5fZmxvYXRMYWJlbD8iYXV0byI6dGhpcy5fZmxvYXRMYWJlbH1zZXQgZmxvYXRMYWJlbChlKXtlIT09dGhpcy5fZmxvYXRMYWJlbCYmKHRoaXMuX2Zsb2F0TGFiZWw9ZXx8dGhpcy5fZ2V0RGVmYXVsdEZsb2F0TGFiZWxTdGF0ZSgpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpKX1nZXQgX2NvbnRyb2woKXtyZXR1cm4gdGhpcy5fZXhwbGljaXRGb3JtRmllbGRDb250cm9sfHx0aGlzLl9jb250cm9sTm9uU3RhdGljfHx0aGlzLl9jb250cm9sU3RhdGljfXNldCBfY29udHJvbChlKXt0aGlzLl9leHBsaWNpdEZvcm1GaWVsZENvbnRyb2w9ZX1nZXRMYWJlbElkKCl7cmV0dXJuIHRoaXMuX2hhc0Zsb2F0aW5nTGFiZWwoKT90aGlzLl9sYWJlbElkOm51bGx9Z2V0Q29ubmVjdGVkT3ZlcmxheU9yaWdpbigpe3JldHVybiB0aGlzLl9jb25uZWN0aW9uQ29udGFpbmVyUmVmfHx0aGlzLl9lbGVtZW50UmVmfW5nQWZ0ZXJDb250ZW50SW5pdCgpe3RoaXMuX3ZhbGlkYXRlQ29udHJvbENoaWxkKCk7bGV0IGU9dGhpcy5fY29udHJvbDtlLmNvbnRyb2xUeXBlJiZ0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuY2xhc3NMaXN0LmFkZChgbWF0LWZvcm0tZmllbGQtdHlwZS0ke2UuY29udHJvbFR5cGV9YCksZS5zdGF0ZUNoYW5nZXMucGlwZSh6bihudWxsKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5fdmFsaWRhdGVQbGFjZWhvbGRlcnMoKSx0aGlzLl9zeW5jRGVzY3JpYmVkQnlJZHMoKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKX0pLGUubmdDb250cm9sJiZlLm5nQ29udHJvbC52YWx1ZUNoYW5nZXMmJmUubmdDb250cm9sLnZhbHVlQ2hhbmdlcy5waXBlKHN0KHRoaXMuX2Rlc3Ryb3llZCkpLnN1YnNjcmliZSgoKT0+dGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCkpLHRoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e3RoaXMuX25nWm9uZS5vblN0YWJsZS5waXBlKHN0KHRoaXMuX2Rlc3Ryb3llZCkpLnN1YnNjcmliZSgoKT0+e3RoaXMuX291dGxpbmVHYXBDYWxjdWxhdGlvbk5lZWRlZE9uU3RhYmxlJiZ0aGlzLnVwZGF0ZU91dGxpbmVHYXAoKX0pfSksSnQodGhpcy5fcHJlZml4Q2hpbGRyZW4uY2hhbmdlcyx0aGlzLl9zdWZmaXhDaGlsZHJlbi5jaGFuZ2VzKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9vdXRsaW5lR2FwQ2FsY3VsYXRpb25OZWVkZWRPblN0YWJsZT0hMCx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKX0pLHRoaXMuX2hpbnRDaGlsZHJlbi5jaGFuZ2VzLnBpcGUoem4obnVsbCkpLnN1YnNjcmliZSgoKT0+e3RoaXMuX3Byb2Nlc3NIaW50cygpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpfSksdGhpcy5fZXJyb3JDaGlsZHJlbi5jaGFuZ2VzLnBpcGUoem4obnVsbCkpLnN1YnNjcmliZSgoKT0+e3RoaXMuX3N5bmNEZXNjcmliZWRCeUlkcygpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpfSksdGhpcy5fZGlyJiZ0aGlzLl9kaXIuY2hhbmdlLnBpcGUoc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKCgpPT57ImZ1bmN0aW9uIj09dHlwZW9mIHJlcXVlc3RBbmltYXRpb25GcmFtZT90aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntyZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCk9PnRoaXMudXBkYXRlT3V0bGluZUdhcCgpKX0pOnRoaXMudXBkYXRlT3V0bGluZUdhcCgpfSl9bmdBZnRlckNvbnRlbnRDaGVja2VkKCl7dGhpcy5fdmFsaWRhdGVDb250cm9sQ2hpbGQoKSx0aGlzLl9vdXRsaW5lR2FwQ2FsY3VsYXRpb25OZWVkZWRJbW1lZGlhdGVseSYmdGhpcy51cGRhdGVPdXRsaW5lR2FwKCl9bmdBZnRlclZpZXdJbml0KCl7dGhpcy5fc3Vic2NyaXB0QW5pbWF0aW9uU3RhdGU9ImVudGVyIix0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5kZXRlY3RDaGFuZ2VzKCl9bmdPbkRlc3Ryb3koKXt0aGlzLl9kZXN0cm95ZWQubmV4dCgpLHRoaXMuX2Rlc3Ryb3llZC5jb21wbGV0ZSgpfV9zaG91bGRGb3J3YXJkKGUpe2xldCBpPXRoaXMuX2NvbnRyb2w/dGhpcy5fY29udHJvbC5uZ0NvbnRyb2w6bnVsbDtyZXR1cm4gaSYmaVtlXX1faGFzUGxhY2Vob2xkZXIoKXtyZXR1cm4hISh0aGlzLl9jb250cm9sJiZ0aGlzLl9jb250cm9sLnBsYWNlaG9sZGVyfHx0aGlzLl9wbGFjZWhvbGRlckNoaWxkKX1faGFzTGFiZWwoKXtyZXR1cm4hKCF0aGlzLl9sYWJlbENoaWxkTm9uU3RhdGljJiYhdGhpcy5fbGFiZWxDaGlsZFN0YXRpYyl9X3Nob3VsZExhYmVsRmxvYXQoKXtyZXR1cm4gdGhpcy5fY2FuTGFiZWxGbG9hdCgpJiYodGhpcy5fY29udHJvbCYmdGhpcy5fY29udHJvbC5zaG91bGRMYWJlbEZsb2F0fHx0aGlzLl9zaG91bGRBbHdheXNGbG9hdCgpKX1faGlkZUNvbnRyb2xQbGFjZWhvbGRlcigpe3JldHVybiJsZWdhY3kiPT09dGhpcy5hcHBlYXJhbmNlJiYhdGhpcy5faGFzTGFiZWwoKXx8dGhpcy5faGFzTGFiZWwoKSYmIXRoaXMuX3Nob3VsZExhYmVsRmxvYXQoKX1faGFzRmxvYXRpbmdMYWJlbCgpe3JldHVybiB0aGlzLl9oYXNMYWJlbCgpfHwibGVnYWN5Ij09PXRoaXMuYXBwZWFyYW5jZSYmdGhpcy5faGFzUGxhY2Vob2xkZXIoKX1fZ2V0RGlzcGxheWVkTWVzc2FnZXMoKXtyZXR1cm4gdGhpcy5fZXJyb3JDaGlsZHJlbiYmdGhpcy5fZXJyb3JDaGlsZHJlbi5sZW5ndGg+MCYmdGhpcy5fY29udHJvbC5lcnJvclN0YXRlPyJlcnJvciI6ImhpbnQifV9hbmltYXRlQW5kTG9ja0xhYmVsKCl7dGhpcy5faGFzRmxvYXRpbmdMYWJlbCgpJiZ0aGlzLl9jYW5MYWJlbEZsb2F0KCkmJih0aGlzLl9hbmltYXRpb25zRW5hYmxlZCYmdGhpcy5fbGFiZWwmJih0aGlzLl9zaG93QWx3YXlzQW5pbWF0ZT0hMCxfaSh0aGlzLl9sYWJlbC5uYXRpdmVFbGVtZW50LCJ0cmFuc2l0aW9uZW5kIikucGlwZShRdCgxKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5fc2hvd0Fsd2F5c0FuaW1hdGU9ITF9KSksdGhpcy5mbG9hdExhYmVsPSJhbHdheXMiLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpKX1fdmFsaWRhdGVQbGFjZWhvbGRlcnMoKXt9X3Byb2Nlc3NIaW50cygpe3RoaXMuX3ZhbGlkYXRlSGludHMoKSx0aGlzLl9zeW5jRGVzY3JpYmVkQnlJZHMoKX1fdmFsaWRhdGVIaW50cygpe31fZ2V0RGVmYXVsdEZsb2F0TGFiZWxTdGF0ZSgpe3JldHVybiB0aGlzLl9kZWZhdWx0cyYmdGhpcy5fZGVmYXVsdHMuZmxvYXRMYWJlbHx8ImF1dG8ifV9zeW5jRGVzY3JpYmVkQnlJZHMoKXtpZih0aGlzLl9jb250cm9sKXtsZXQgZT1bXTtpZih0aGlzLl9jb250cm9sLnVzZXJBcmlhRGVzY3JpYmVkQnkmJiJzdHJpbmciPT10eXBlb2YgdGhpcy5fY29udHJvbC51c2VyQXJpYURlc2NyaWJlZEJ5JiZlLnB1c2goLi4udGhpcy5fY29udHJvbC51c2VyQXJpYURlc2NyaWJlZEJ5LnNwbGl0KCIgIikpLCJoaW50Ij09PXRoaXMuX2dldERpc3BsYXllZE1lc3NhZ2VzKCkpe2xldCBpPXRoaXMuX2hpbnRDaGlsZHJlbj90aGlzLl9oaW50Q2hpbGRyZW4uZmluZChvPT4ic3RhcnQiPT09by5hbGlnbik6bnVsbCxyPXRoaXMuX2hpbnRDaGlsZHJlbj90aGlzLl9oaW50Q2hpbGRyZW4uZmluZChvPT4iZW5kIj09PW8uYWxpZ24pOm51bGw7aT9lLnB1c2goaS5pZCk6dGhpcy5faGludExhYmVsJiZlLnB1c2godGhpcy5faGludExhYmVsSWQpLHImJmUucHVzaChyLmlkKX1lbHNlIHRoaXMuX2Vycm9yQ2hpbGRyZW4mJmUucHVzaCguLi50aGlzLl9lcnJvckNoaWxkcmVuLm1hcChpPT5pLmlkKSk7dGhpcy5fY29udHJvbC5zZXREZXNjcmliZWRCeUlkcyhlKX19X3ZhbGlkYXRlQ29udHJvbENoaWxkKCl7fXVwZGF0ZU91dGxpbmVHYXAoKXtsZXQgZT10aGlzLl9sYWJlbD90aGlzLl9sYWJlbC5uYXRpdmVFbGVtZW50Om51bGwsaT10aGlzLl9jb25uZWN0aW9uQ29udGFpbmVyUmVmLm5hdGl2ZUVsZW1lbnQscj0iLm1hdC1mb3JtLWZpZWxkLW91dGxpbmUtc3RhcnQiLG89Ii5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLWdhcCI7aWYoIm91dGxpbmUiIT09dGhpcy5hcHBlYXJhbmNlfHwhdGhpcy5fcGxhdGZvcm0uaXNCcm93c2VyKXJldHVybjtpZighZXx8IWUuY2hpbGRyZW4ubGVuZ3RofHwhZS50ZXh0Q29udGVudC50cmltKCkpe2xldCB1PWkucXVlcnlTZWxlY3RvckFsbChgJHtyfSwgJHtvfWApO2ZvcihsZXQgZD0wO2Q8dS5sZW5ndGg7ZCsrKXVbZF0uc3R5bGUud2lkdGg9IjAiO3JldHVybn1pZighdGhpcy5faXNBdHRhY2hlZFRvRE9NKCkpcmV0dXJuIHZvaWQodGhpcy5fb3V0bGluZUdhcENhbGN1bGF0aW9uTmVlZGVkSW1tZWRpYXRlbHk9ITApO2xldCBzPTAsYT0wLGw9aS5xdWVyeVNlbGVjdG9yQWxsKHIpLGM9aS5xdWVyeVNlbGVjdG9yQWxsKG8pO2lmKHRoaXMuX2xhYmVsJiZ0aGlzLl9sYWJlbC5uYXRpdmVFbGVtZW50LmNoaWxkcmVuLmxlbmd0aCl7bGV0IHU9aS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtpZigwPT09dS53aWR0aCYmMD09PXUuaGVpZ2h0KXJldHVybiB0aGlzLl9vdXRsaW5lR2FwQ2FsY3VsYXRpb25OZWVkZWRPblN0YWJsZT0hMCx2b2lkKHRoaXMuX291dGxpbmVHYXBDYWxjdWxhdGlvbk5lZWRlZEltbWVkaWF0ZWx5PSExKTtsZXQgZD10aGlzLl9nZXRTdGFydEVuZCh1KSxwPWUuY2hpbGRyZW4saD10aGlzLl9nZXRTdGFydEVuZChwWzBdLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpKSxmPTA7Zm9yKGxldCBtPTA7bTxwLmxlbmd0aDttKyspZis9cFttXS5vZmZzZXRXaWR0aDtzPU1hdGguYWJzKGgtZCktNSxhPWY+MD8uNzUqZisxMDowfWZvcihsZXQgdT0wO3U8bC5sZW5ndGg7dSsrKWxbdV0uc3R5bGUud2lkdGg9YCR7c31weGA7Zm9yKGxldCB1PTA7dTxjLmxlbmd0aDt1KyspY1t1XS5zdHlsZS53aWR0aD1gJHthfXB4YDt0aGlzLl9vdXRsaW5lR2FwQ2FsY3VsYXRpb25OZWVkZWRPblN0YWJsZT10aGlzLl9vdXRsaW5lR2FwQ2FsY3VsYXRpb25OZWVkZWRJbW1lZGlhdGVseT0hMX1fZ2V0U3RhcnRFbmQoZSl7cmV0dXJuIHRoaXMuX2RpciYmInJ0bCI9PT10aGlzLl9kaXIudmFsdWU/ZS5yaWdodDplLmxlZnR9X2lzQXR0YWNoZWRUb0RPTSgpe2xldCBlPXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudDtpZihlLmdldFJvb3ROb2RlKXtsZXQgaT1lLmdldFJvb3ROb2RlKCk7cmV0dXJuIGkmJmkhPT1lfXJldHVybiBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuY29udGFpbnMoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0obm4pLE0oJGksOCksTShUa2UsOCksTShvaSksTShfdCksTShQaSw4KSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LWZvcm0tZmllbGQiXV0sY29udGVudFF1ZXJpZXM6ZnVuY3Rpb24oZSxpLHIpe2lmKDEmZSYmKEVpKHIsa2gsNSksRWkocixraCw3KSxFaShyLE52LDUpLEVpKHIsTnYsNyksRWkocixDa2UsNSksRWkocixWdGUsNSksRWkocix4a2UsNSksRWkocixNa2UsNSksRWkocix3a2UsNSkpLDImZSl7bGV0IG87TmUobz1MZSgpKSYmKGkuX2NvbnRyb2xOb25TdGF0aWM9by5maXJzdCksTmUobz1MZSgpKSYmKGkuX2NvbnRyb2xTdGF0aWM9by5maXJzdCksTmUobz1MZSgpKSYmKGkuX2xhYmVsQ2hpbGROb25TdGF0aWM9by5maXJzdCksTmUobz1MZSgpKSYmKGkuX2xhYmVsQ2hpbGRTdGF0aWM9by5maXJzdCksTmUobz1MZSgpKSYmKGkuX3BsYWNlaG9sZGVyQ2hpbGQ9by5maXJzdCksTmUobz1MZSgpKSYmKGkuX2Vycm9yQ2hpbGRyZW49byksTmUobz1MZSgpKSYmKGkuX2hpbnRDaGlsZHJlbj1vKSxOZShvPUxlKCkpJiYoaS5fcHJlZml4Q2hpbGRyZW49byksTmUobz1MZSgpKSYmKGkuX3N1ZmZpeENoaWxkcmVuPW8pfX0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYob3QoaWtlLDcpLG90KHJrZSw1KSxvdChva2UsNSkpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkuX2Nvbm5lY3Rpb25Db250YWluZXJSZWY9ci5maXJzdCksTmUocj1MZSgpKSYmKGkuX2lucHV0Q29udGFpbmVyUmVmPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLl9sYWJlbD1yLmZpcnN0KX19LGhvc3RBdHRyczpbMSwibWF0LWZvcm0tZmllbGQiXSxob3N0VmFyczo0MCxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJmV0KCJtYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLXN0YW5kYXJkIiwic3RhbmRhcmQiPT1pLmFwcGVhcmFuY2UpKCJtYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGwiLCJmaWxsIj09aS5hcHBlYXJhbmNlKSgibWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIiwib3V0bGluZSI9PWkuYXBwZWFyYW5jZSkoIm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5IiwibGVnYWN5Ij09aS5hcHBlYXJhbmNlKSgibWF0LWZvcm0tZmllbGQtaW52YWxpZCIsaS5fY29udHJvbC5lcnJvclN0YXRlKSgibWF0LWZvcm0tZmllbGQtY2FuLWZsb2F0IixpLl9jYW5MYWJlbEZsb2F0KCkpKCJtYXQtZm9ybS1maWVsZC1zaG91bGQtZmxvYXQiLGkuX3Nob3VsZExhYmVsRmxvYXQoKSkoIm1hdC1mb3JtLWZpZWxkLWhhcy1sYWJlbCIsaS5faGFzRmxvYXRpbmdMYWJlbCgpKSgibWF0LWZvcm0tZmllbGQtaGlkZS1wbGFjZWhvbGRlciIsaS5faGlkZUNvbnRyb2xQbGFjZWhvbGRlcigpKSgibWF0LWZvcm0tZmllbGQtZGlzYWJsZWQiLGkuX2NvbnRyb2wuZGlzYWJsZWQpKCJtYXQtZm9ybS1maWVsZC1hdXRvZmlsbGVkIixpLl9jb250cm9sLmF1dG9maWxsZWQpKCJtYXQtZm9jdXNlZCIsaS5fY29udHJvbC5mb2N1c2VkKSgibmctdW50b3VjaGVkIixpLl9zaG91bGRGb3J3YXJkKCJ1bnRvdWNoZWQiKSkoIm5nLXRvdWNoZWQiLGkuX3Nob3VsZEZvcndhcmQoInRvdWNoZWQiKSkoIm5nLXByaXN0aW5lIixpLl9zaG91bGRGb3J3YXJkKCJwcmlzdGluZSIpKSgibmctZGlydHkiLGkuX3Nob3VsZEZvcndhcmQoImRpcnR5IikpKCJuZy12YWxpZCIsaS5fc2hvdWxkRm9yd2FyZCgidmFsaWQiKSkoIm5nLWludmFsaWQiLGkuX3Nob3VsZEZvcndhcmQoImludmFsaWQiKSkoIm5nLXBlbmRpbmciLGkuX3Nob3VsZEZvcndhcmQoInBlbmRpbmciKSkoIl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIiwhaS5fYW5pbWF0aW9uc0VuYWJsZWQpfSxpbnB1dHM6e2NvbG9yOiJjb2xvciIsYXBwZWFyYW5jZToiYXBwZWFyYW5jZSIsaGlkZVJlcXVpcmVkTWFya2VyOiJoaWRlUmVxdWlyZWRNYXJrZXIiLGhpbnRMYWJlbDoiaGludExhYmVsIixmbG9hdExhYmVsOiJmbG9hdExhYmVsIn0sZXhwb3J0QXM6WyJtYXRGb3JtRmllbGQiXSxmZWF0dXJlczpbJHQoW3twcm92aWRlOnNnLHVzZUV4aXN0aW5nOm59XSksdHRdLG5nQ29udGVudFNlbGVjdG9yczp2a2UsZGVjbHM6MTUsdmFyczo4LGNvbnN0czpbWzEsIm1hdC1mb3JtLWZpZWxkLXdyYXBwZXIiXSxbMSwibWF0LWZvcm0tZmllbGQtZmxleCIsMywiY2xpY2siXSxbImNvbm5lY3Rpb25Db250YWluZXIiLCIiXSxbNCwibmdJZiJdLFsiY2xhc3MiLCJtYXQtZm9ybS1maWVsZC1wcmVmaXgiLDMsImNka09ic2VydmVDb250ZW50RGlzYWJsZWQiLCJjZGtPYnNlcnZlQ29udGVudCIsNCwibmdJZiJdLFsxLCJtYXQtZm9ybS1maWVsZC1pbmZpeCJdLFsiaW5wdXRDb250YWluZXIiLCIiXSxbMSwibWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlciJdLFsiY2xhc3MiLCJtYXQtZm9ybS1maWVsZC1sYWJlbCIsMywiY2RrT2JzZXJ2ZUNvbnRlbnREaXNhYmxlZCIsImlkIiwibWF0LWVtcHR5IiwibWF0LWZvcm0tZmllbGQtZW1wdHkiLCJtYXQtYWNjZW50IiwibWF0LXdhcm4iLCJuZ1N3aXRjaCIsImNka09ic2VydmVDb250ZW50Iiw0LCJuZ0lmIl0sWyJjbGFzcyIsIm1hdC1mb3JtLWZpZWxkLXN1ZmZpeCIsNCwibmdJZiJdLFsiY2xhc3MiLCJtYXQtZm9ybS1maWVsZC11bmRlcmxpbmUiLDQsIm5nSWYiXSxbMSwibWF0LWZvcm0tZmllbGQtc3Vic2NyaXB0LXdyYXBwZXIiLDMsIm5nU3dpdGNoIl0sWzQsIm5nU3dpdGNoQ2FzZSJdLFsiY2xhc3MiLCJtYXQtZm9ybS1maWVsZC1oaW50LXdyYXBwZXIiLDQsIm5nU3dpdGNoQ2FzZSJdLFsxLCJtYXQtZm9ybS1maWVsZC1vdXRsaW5lIl0sWzEsIm1hdC1mb3JtLWZpZWxkLW91dGxpbmUtc3RhcnQiXSxbMSwibWF0LWZvcm0tZmllbGQtb3V0bGluZS1nYXAiXSxbMSwibWF0LWZvcm0tZmllbGQtb3V0bGluZS1lbmQiXSxbMSwibWF0LWZvcm0tZmllbGQtb3V0bGluZSIsIm1hdC1mb3JtLWZpZWxkLW91dGxpbmUtdGhpY2siXSxbMSwibWF0LWZvcm0tZmllbGQtcHJlZml4IiwzLCJjZGtPYnNlcnZlQ29udGVudERpc2FibGVkIiwiY2RrT2JzZXJ2ZUNvbnRlbnQiXSxbMSwibWF0LWZvcm0tZmllbGQtbGFiZWwiLDMsImNka09ic2VydmVDb250ZW50RGlzYWJsZWQiLCJpZCIsIm5nU3dpdGNoIiwiY2RrT2JzZXJ2ZUNvbnRlbnQiXSxbImxhYmVsIiwiIl0sWyJjbGFzcyIsIm1hdC1wbGFjZWhvbGRlci1yZXF1aXJlZCBtYXQtZm9ybS1maWVsZC1yZXF1aXJlZC1tYXJrZXIiLCJhcmlhLWhpZGRlbiIsInRydWUiLDQsIm5nSWYiXSxbImFyaWEtaGlkZGVuIiwidHJ1ZSIsMSwibWF0LXBsYWNlaG9sZGVyLXJlcXVpcmVkIiwibWF0LWZvcm0tZmllbGQtcmVxdWlyZWQtbWFya2VyIl0sWzEsIm1hdC1mb3JtLWZpZWxkLXN1ZmZpeCJdLFsxLCJtYXQtZm9ybS1maWVsZC11bmRlcmxpbmUiXSxbMSwibWF0LWZvcm0tZmllbGQtcmlwcGxlIl0sWzEsIm1hdC1mb3JtLWZpZWxkLWhpbnQtd3JhcHBlciJdLFsiY2xhc3MiLCJtYXQtaGludCIsMywiaWQiLDQsIm5nSWYiXSxbMSwibWF0LWZvcm0tZmllbGQtaGludC1zcGFjZXIiXSxbMSwibWF0LWhpbnQiLDMsImlkIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoeGkoX2tlKSxfKDAsImRpdiIsMCkoMSwiZGl2IiwxLDIpLFAoImNsaWNrIixmdW5jdGlvbihvKXtyZXR1cm4gaS5fY29udHJvbC5vbkNvbnRhaW5lckNsaWNrJiZpLl9jb250cm9sLm9uQ29udGFpbmVyQ2xpY2sobyl9KSxFKDMsc2tlLDksMCwibmctY29udGFpbmVyIiwzKSxFKDQsYWtlLDIsMSwiZGl2Iiw0KSxfKDUsImRpdiIsNSw2KSxWbig3KSxfKDgsInNwYW4iLDcpLEUoOSxka2UsNSwxNiwibGFiZWwiLDgpLHYoKSgpLEUoMTAscGtlLDIsMCwiZGl2Iiw5KSx2KCksRSgxMSxoa2UsMiw0LCJkaXYiLDEwKSxfKDEyLCJkaXYiLDExKSxFKDEzLGZrZSwyLDEsImRpdiIsMTIpLEUoMTQsZ2tlLDUsMiwiZGl2IiwxMyksdigpKCkpLDImZSYmKEMoMykseSgibmdJZiIsIm91dGxpbmUiPT1pLmFwcGVhcmFuY2UpLEMoMSkseSgibmdJZiIsaS5fcHJlZml4Q2hpbGRyZW4ubGVuZ3RoKSxDKDUpLHkoIm5nSWYiLGkuX2hhc0Zsb2F0aW5nTGFiZWwoKSksQygxKSx5KCJuZ0lmIixpLl9zdWZmaXhDaGlsZHJlbi5sZW5ndGgpLEMoMSkseSgibmdJZiIsIm91dGxpbmUiIT1pLmFwcGVhcmFuY2UpLEMoMSkseSgibmdTd2l0Y2giLGkuX2dldERpc3BsYXllZE1lc3NhZ2VzKCkpLEMoMSkseSgibmdTd2l0Y2hDYXNlIiwiZXJyb3IiKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsImhpbnQiKSl9LGRlcGVuZGVuY2llczpbQmUsQ3IsVXIsd2hdLHN0eWxlczpbIi5tYXQtZm9ybS1maWVsZHtkaXNwbGF5OmlubGluZS1ibG9jaztwb3NpdGlvbjpyZWxhdGl2ZTt0ZXh0LWFsaWduOmxlZnR9W2Rpcj1ydGxdIC5tYXQtZm9ybS1maWVsZHt0ZXh0LWFsaWduOnJpZ2h0fS5tYXQtZm9ybS1maWVsZC13cmFwcGVye3Bvc2l0aW9uOnJlbGF0aXZlfS5tYXQtZm9ybS1maWVsZC1mbGV4e2Rpc3BsYXk6aW5saW5lLWZsZXg7YWxpZ24taXRlbXM6YmFzZWxpbmU7Ym94LXNpemluZzpib3JkZXItYm94O3dpZHRoOjEwMCV9Lm1hdC1mb3JtLWZpZWxkLXByZWZpeCwubWF0LWZvcm0tZmllbGQtc3VmZml4e3doaXRlLXNwYWNlOm5vd3JhcDtmbGV4Om5vbmU7cG9zaXRpb246cmVsYXRpdmV9Lm1hdC1mb3JtLWZpZWxkLWluZml4e2Rpc3BsYXk6YmxvY2s7cG9zaXRpb246cmVsYXRpdmU7ZmxleDphdXRvO21pbi13aWR0aDowO3dpZHRoOjE4MHB4fS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1mb3JtLWZpZWxkLWluZml4e2JvcmRlci1pbWFnZTpsaW5lYXItZ3JhZGllbnQodHJhbnNwYXJlbnQsIHRyYW5zcGFyZW50KX0ubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlcntwb3NpdGlvbjphYnNvbHV0ZTtsZWZ0OjA7Ym94LXNpemluZzpjb250ZW50LWJveDt3aWR0aDoxMDAlO2hlaWdodDoxMDAlO292ZXJmbG93OmhpZGRlbjtwb2ludGVyLWV2ZW50czpub25lfVtkaXI9cnRsXSAubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlcntsZWZ0OmF1dG87cmlnaHQ6MH0ubWF0LWZvcm0tZmllbGQtbGFiZWx7cG9zaXRpb246YWJzb2x1dGU7bGVmdDowO2ZvbnQ6aW5oZXJpdDtwb2ludGVyLWV2ZW50czpub25lO3dpZHRoOjEwMCU7d2hpdGUtc3BhY2U6bm93cmFwO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXM7b3ZlcmZsb3c6aGlkZGVuO3RyYW5zZm9ybS1vcmlnaW46MCAwO3RyYW5zaXRpb246dHJhbnNmb3JtIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpLGNvbG9yIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpLHdpZHRoIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpO2Rpc3BsYXk6bm9uZX1bZGlyPXJ0bF0gLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybS1vcmlnaW46MTAwJSAwO2xlZnQ6YXV0bztyaWdodDowfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkIC5tYXQtZm9ybS1maWVsZC1sYWJlbHtjb2xvcjpHcmF5VGV4dH0ubWF0LWZvcm0tZmllbGQtZW1wdHkubWF0LWZvcm0tZmllbGQtbGFiZWwsLm1hdC1mb3JtLWZpZWxkLWNhbi1mbG9hdC5tYXQtZm9ybS1maWVsZC1zaG91bGQtZmxvYXQgLm1hdC1mb3JtLWZpZWxkLWxhYmVse2Rpc3BsYXk6YmxvY2t9Lm1hdC1mb3JtLWZpZWxkLWF1dG9maWxsLWNvbnRyb2w6LXdlYmtpdC1hdXRvZmlsbCsubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlciAubWF0LWZvcm0tZmllbGQtbGFiZWx7ZGlzcGxheTpub25lfS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1mb3JtLWZpZWxkLWF1dG9maWxsLWNvbnRyb2w6LXdlYmtpdC1hdXRvZmlsbCsubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlciAubWF0LWZvcm0tZmllbGQtbGFiZWx7ZGlzcGxheTpibG9jazt0cmFuc2l0aW9uOm5vbmV9Lm1hdC1pbnB1dC1zZXJ2ZXI6Zm9jdXMrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVsLC5tYXQtaW5wdXQtc2VydmVyW3BsYWNlaG9sZGVyXTpub3QoOnBsYWNlaG9sZGVyLXNob3duKSsubWF0LWZvcm0tZmllbGQtbGFiZWwtd3JhcHBlciAubWF0LWZvcm0tZmllbGQtbGFiZWx7ZGlzcGxheTpub25lfS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXI6Zm9jdXMrLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1mb3JtLWZpZWxkLWxhYmVsLC5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQgLm1hdC1pbnB1dC1zZXJ2ZXJbcGxhY2Vob2xkZXJdOm5vdCg6cGxhY2Vob2xkZXItc2hvd24pKy5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVyIC5tYXQtZm9ybS1maWVsZC1sYWJlbHtkaXNwbGF5OmJsb2NrfS5tYXQtZm9ybS1maWVsZC1sYWJlbDpub3QoLm1hdC1mb3JtLWZpZWxkLWVtcHR5KXt0cmFuc2l0aW9uOm5vbmV9Lm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtwb3NpdGlvbjphYnNvbHV0ZTt3aWR0aDoxMDAlO3BvaW50ZXItZXZlbnRzOm5vbmU7dHJhbnNmb3JtOnNjYWxlM2QoMSwgMS4wMDAxLCAxKX0ubWF0LWZvcm0tZmllbGQtcmlwcGxle3Bvc2l0aW9uOmFic29sdXRlO2xlZnQ6MDt3aWR0aDoxMDAlO3RyYW5zZm9ybS1vcmlnaW46NTAlO3RyYW5zZm9ybTpzY2FsZVgoMC41KTtvcGFjaXR5OjA7dHJhbnNpdGlvbjpiYWNrZ3JvdW5kLWNvbG9yIDMwMG1zIGN1YmljLWJlemllcigwLjU1LCAwLCAwLjU1LCAwLjIpfS5tYXQtZm9ybS1maWVsZC5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtcmlwcGxlLC5tYXQtZm9ybS1maWVsZC5tYXQtZm9ybS1maWVsZC1pbnZhbGlkIC5tYXQtZm9ybS1maWVsZC1yaXBwbGV7b3BhY2l0eToxO3RyYW5zZm9ybTpub25lO3RyYW5zaXRpb246dHJhbnNmb3JtIDMwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpLG9wYWNpdHkgMTAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSksYmFja2dyb3VuZC1jb2xvciAzMDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKX0ubWF0LWZvcm0tZmllbGQtc3Vic2NyaXB0LXdyYXBwZXJ7cG9zaXRpb246YWJzb2x1dGU7Ym94LXNpemluZzpib3JkZXItYm94O3dpZHRoOjEwMCU7b3ZlcmZsb3c6aGlkZGVufS5tYXQtZm9ybS1maWVsZC1zdWJzY3JpcHQtd3JhcHBlciAubWF0LWljb24sLm1hdC1mb3JtLWZpZWxkLWxhYmVsLXdyYXBwZXIgLm1hdC1pY29ue3dpZHRoOjFlbTtoZWlnaHQ6MWVtO2ZvbnQtc2l6ZTppbmhlcml0O3ZlcnRpY2FsLWFsaWduOmJhc2VsaW5lfS5tYXQtZm9ybS1maWVsZC1oaW50LXdyYXBwZXJ7ZGlzcGxheTpmbGV4fS5tYXQtZm9ybS1maWVsZC1oaW50LXNwYWNlcntmbGV4OjEgMCAxZW19Lm1hdC1lcnJvcntkaXNwbGF5OmJsb2NrfS5tYXQtZm9ybS1maWVsZC1jb250cm9sLXdyYXBwZXJ7cG9zaXRpb246cmVsYXRpdmV9Lm1hdC1mb3JtLWZpZWxkLWhpbnQtZW5ke29yZGVyOjF9Lm1hdC1mb3JtLWZpZWxkLl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIC5tYXQtZm9ybS1maWVsZC1sYWJlbCwubWF0LWZvcm0tZmllbGQuX21hdC1hbmltYXRpb24tbm9vcGFibGUgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZXt0cmFuc2l0aW9uOm5vbmV9IiwnLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbCAubWF0LWZvcm0tZmllbGQtZmxleHtib3JkZXItcmFkaXVzOjRweCA0cHggMCAwO3BhZGRpbmc6Ljc1ZW0gLjc1ZW0gMCAuNzVlbX0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGwgLm1hdC1mb3JtLWZpZWxkLWZsZXh7b3V0bGluZTpzb2xpZCAxcHh9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsLm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkIC5tYXQtZm9ybS1maWVsZC1mbGV4e291dGxpbmUtY29sb3I6R3JheVRleHR9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsLm1hdC1mb2N1c2VkIC5tYXQtZm9ybS1maWVsZC1mbGV4e291dGxpbmU6ZGFzaGVkIDNweH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsIC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmU6OmJlZm9yZXtjb250ZW50OiIiO2Rpc3BsYXk6YmxvY2s7cG9zaXRpb246YWJzb2x1dGU7Ym90dG9tOjA7aGVpZ2h0OjFweDt3aWR0aDoxMDAlfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGwgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZXtib3R0b206MDtoZWlnaHQ6MnB4fS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbCAubWF0LWZvcm0tZmllbGQtcmlwcGxle2hlaWdodDowfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGw6bm90KC5tYXQtZm9ybS1maWVsZC1kaXNhYmxlZCkgLm1hdC1mb3JtLWZpZWxkLWZsZXg6aG92ZXJ+Lm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZSAubWF0LWZvcm0tZmllbGQtcmlwcGxle29wYWNpdHk6MTt0cmFuc2Zvcm06bm9uZTt0cmFuc2l0aW9uOm9wYWNpdHkgNjAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSl9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbC5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZTpub3QoLm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkKSAubWF0LWZvcm0tZmllbGQtZmxleDpob3Zlcn4ubWF0LWZvcm0tZmllbGQtdW5kZXJsaW5lIC5tYXQtZm9ybS1maWVsZC1yaXBwbGV7dHJhbnNpdGlvbjpub25lfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWZpbGwgLm1hdC1mb3JtLWZpZWxkLXN1YnNjcmlwdC13cmFwcGVye3BhZGRpbmc6MCAxZW19JywnLm1hdC1pbnB1dC1lbGVtZW50e2ZvbnQ6aW5oZXJpdDtiYWNrZ3JvdW5kOnJnYmEoMCwwLDAsMCk7Y29sb3I6Y3VycmVudENvbG9yO2JvcmRlcjpub25lO291dGxpbmU6bm9uZTtwYWRkaW5nOjA7bWFyZ2luOjA7d2lkdGg6MTAwJTttYXgtd2lkdGg6MTAwJTt2ZXJ0aWNhbC1hbGlnbjpib3R0b207dGV4dC1hbGlnbjppbmhlcml0O2JveC1zaXppbmc6Y29udGVudC1ib3h9Lm1hdC1pbnB1dC1lbGVtZW50Oi1tb3otdWktaW52YWxpZHtib3gtc2hhZG93Om5vbmV9Lm1hdC1pbnB1dC1lbGVtZW50LC5tYXQtaW5wdXQtZWxlbWVudDo6LXdlYmtpdC1zZWFyY2gtY2FuY2VsLWJ1dHRvbiwubWF0LWlucHV0LWVsZW1lbnQ6Oi13ZWJraXQtc2VhcmNoLWRlY29yYXRpb24sLm1hdC1pbnB1dC1lbGVtZW50Ojotd2Via2l0LXNlYXJjaC1yZXN1bHRzLWJ1dHRvbiwubWF0LWlucHV0LWVsZW1lbnQ6Oi13ZWJraXQtc2VhcmNoLXJlc3VsdHMtZGVjb3JhdGlvbnstd2Via2l0LWFwcGVhcmFuY2U6bm9uZX0ubWF0LWlucHV0LWVsZW1lbnQ6Oi13ZWJraXQtY29udGFjdHMtYXV0by1maWxsLWJ1dHRvbiwubWF0LWlucHV0LWVsZW1lbnQ6Oi13ZWJraXQtY2Fwcy1sb2NrLWluZGljYXRvciwubWF0LWlucHV0LWVsZW1lbnQ6bm90KFt0eXBlPXBhc3N3b3JkXSk6Oi13ZWJraXQtY3JlZGVudGlhbHMtYXV0by1maWxsLWJ1dHRvbnt2aXNpYmlsaXR5OmhpZGRlbn0ubWF0LWlucHV0LWVsZW1lbnRbdHlwZT1kYXRlXSwubWF0LWlucHV0LWVsZW1lbnRbdHlwZT1kYXRldGltZV0sLm1hdC1pbnB1dC1lbGVtZW50W3R5cGU9ZGF0ZXRpbWUtbG9jYWxdLC5tYXQtaW5wdXQtZWxlbWVudFt0eXBlPW1vbnRoXSwubWF0LWlucHV0LWVsZW1lbnRbdHlwZT13ZWVrXSwubWF0LWlucHV0LWVsZW1lbnRbdHlwZT10aW1lXXtsaW5lLWhlaWdodDoxfS5tYXQtaW5wdXQtZWxlbWVudFt0eXBlPWRhdGVdOjphZnRlciwubWF0LWlucHV0LWVsZW1lbnRbdHlwZT1kYXRldGltZV06OmFmdGVyLC5tYXQtaW5wdXQtZWxlbWVudFt0eXBlPWRhdGV0aW1lLWxvY2FsXTo6YWZ0ZXIsLm1hdC1pbnB1dC1lbGVtZW50W3R5cGU9bW9udGhdOjphZnRlciwubWF0LWlucHV0LWVsZW1lbnRbdHlwZT13ZWVrXTo6YWZ0ZXIsLm1hdC1pbnB1dC1lbGVtZW50W3R5cGU9dGltZV06OmFmdGVye2NvbnRlbnQ6IiAiO3doaXRlLXNwYWNlOnByZTt3aWR0aDoxcHh9Lm1hdC1pbnB1dC1lbGVtZW50Ojotd2Via2l0LWlubmVyLXNwaW4tYnV0dG9uLC5tYXQtaW5wdXQtZWxlbWVudDo6LXdlYmtpdC1jYWxlbmRhci1waWNrZXItaW5kaWNhdG9yLC5tYXQtaW5wdXQtZWxlbWVudDo6LXdlYmtpdC1jbGVhci1idXR0b257Zm9udC1zaXplOi43NWVtfS5tYXQtaW5wdXQtZWxlbWVudDo6cGxhY2Vob2xkZXJ7LXdlYmtpdC11c2VyLXNlbGVjdDpub25lO3VzZXItc2VsZWN0Om5vbmU7dHJhbnNpdGlvbjpjb2xvciA0MDBtcyAxMzMuMzMzMzMzMzMzM21zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpfS5tYXQtaW5wdXQtZWxlbWVudDo6LW1vei1wbGFjZWhvbGRlcnstd2Via2l0LXVzZXItc2VsZWN0Om5vbmU7dXNlci1zZWxlY3Q6bm9uZTt0cmFuc2l0aW9uOmNvbG9yIDQwMG1zIDEzMy4zMzMzMzMzMzMzbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSl9Lm1hdC1pbnB1dC1lbGVtZW50Ojotd2Via2l0LWlucHV0LXBsYWNlaG9sZGVyey13ZWJraXQtdXNlci1zZWxlY3Q6bm9uZTt1c2VyLXNlbGVjdDpub25lO3RyYW5zaXRpb246Y29sb3IgNDAwbXMgMTMzLjMzMzMzMzMzMzNtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKX0ubWF0LWlucHV0LWVsZW1lbnQ6LW1zLWlucHV0LXBsYWNlaG9sZGVyey13ZWJraXQtdXNlci1zZWxlY3Q6bm9uZTt1c2VyLXNlbGVjdDpub25lO3RyYW5zaXRpb246Y29sb3IgNDAwbXMgMTMzLjMzMzMzMzMzMzNtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKX0ubWF0LWZvcm0tZmllbGQtaGlkZS1wbGFjZWhvbGRlciAubWF0LWlucHV0LWVsZW1lbnQ6OnBsYWNlaG9sZGVye2NvbG9yOnJnYmEoMCwwLDAsMCkgIWltcG9ydGFudDstd2Via2l0LXRleHQtZmlsbC1jb2xvcjpyZ2JhKDAsMCwwLDApO3RyYW5zaXRpb246bm9uZX0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtZm9ybS1maWVsZC1oaWRlLXBsYWNlaG9sZGVyIC5tYXQtaW5wdXQtZWxlbWVudDo6cGxhY2Vob2xkZXJ7b3BhY2l0eTowfS5tYXQtZm9ybS1maWVsZC1oaWRlLXBsYWNlaG9sZGVyIC5tYXQtaW5wdXQtZWxlbWVudDo6LW1vei1wbGFjZWhvbGRlcntjb2xvcjpyZ2JhKDAsMCwwLDApICFpbXBvcnRhbnQ7LXdlYmtpdC10ZXh0LWZpbGwtY29sb3I6cmdiYSgwLDAsMCwwKTt0cmFuc2l0aW9uOm5vbmV9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWZvcm0tZmllbGQtaGlkZS1wbGFjZWhvbGRlciAubWF0LWlucHV0LWVsZW1lbnQ6Oi1tb3otcGxhY2Vob2xkZXJ7b3BhY2l0eTowfS5tYXQtZm9ybS1maWVsZC1oaWRlLXBsYWNlaG9sZGVyIC5tYXQtaW5wdXQtZWxlbWVudDo6LXdlYmtpdC1pbnB1dC1wbGFjZWhvbGRlcntjb2xvcjpyZ2JhKDAsMCwwLDApICFpbXBvcnRhbnQ7LXdlYmtpdC10ZXh0LWZpbGwtY29sb3I6cmdiYSgwLDAsMCwwKTt0cmFuc2l0aW9uOm5vbmV9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWZvcm0tZmllbGQtaGlkZS1wbGFjZWhvbGRlciAubWF0LWlucHV0LWVsZW1lbnQ6Oi13ZWJraXQtaW5wdXQtcGxhY2Vob2xkZXJ7b3BhY2l0eTowfS5tYXQtZm9ybS1maWVsZC1oaWRlLXBsYWNlaG9sZGVyIC5tYXQtaW5wdXQtZWxlbWVudDotbXMtaW5wdXQtcGxhY2Vob2xkZXJ7Y29sb3I6cmdiYSgwLDAsMCwwKSAhaW1wb3J0YW50Oy13ZWJraXQtdGV4dC1maWxsLWNvbG9yOnJnYmEoMCwwLDAsMCk7dHJhbnNpdGlvbjpub25lfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1mb3JtLWZpZWxkLWhpZGUtcGxhY2Vob2xkZXIgLm1hdC1pbnB1dC1lbGVtZW50Oi1tcy1pbnB1dC1wbGFjZWhvbGRlcntvcGFjaXR5OjB9Ll9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIC5tYXQtaW5wdXQtZWxlbWVudDo6cGxhY2Vob2xkZXJ7dHJhbnNpdGlvbjpub25lfS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LWlucHV0LWVsZW1lbnQ6Oi1tb3otcGxhY2Vob2xkZXJ7dHJhbnNpdGlvbjpub25lfS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LWlucHV0LWVsZW1lbnQ6Oi13ZWJraXQtaW5wdXQtcGxhY2Vob2xkZXJ7dHJhbnNpdGlvbjpub25lfS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LWlucHV0LWVsZW1lbnQ6LW1zLWlucHV0LXBsYWNlaG9sZGVye3RyYW5zaXRpb246bm9uZX10ZXh0YXJlYS5tYXQtaW5wdXQtZWxlbWVudHtyZXNpemU6dmVydGljYWw7b3ZlcmZsb3c6YXV0b310ZXh0YXJlYS5tYXQtaW5wdXQtZWxlbWVudC5jZGstdGV4dGFyZWEtYXV0b3NpemV7cmVzaXplOm5vbmV9dGV4dGFyZWEubWF0LWlucHV0LWVsZW1lbnR7cGFkZGluZzoycHggMDttYXJnaW46LTJweCAwfXNlbGVjdC5tYXQtaW5wdXQtZWxlbWVudHstbW96LWFwcGVhcmFuY2U6bm9uZTstd2Via2l0LWFwcGVhcmFuY2U6bm9uZTtwb3NpdGlvbjpyZWxhdGl2ZTtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCk7ZGlzcGxheTppbmxpbmUtZmxleDtib3gtc2l6aW5nOmJvcmRlci1ib3g7cGFkZGluZy10b3A6MWVtO3RvcDotMWVtO21hcmdpbi1ib3R0b206LTFlbX1zZWxlY3QubWF0LWlucHV0LWVsZW1lbnQ6Oi1tb3otZm9jdXMtaW5uZXJ7Ym9yZGVyOjB9c2VsZWN0Lm1hdC1pbnB1dC1lbGVtZW50Om5vdCg6ZGlzYWJsZWQpe2N1cnNvcjpwb2ludGVyfS5tYXQtZm9ybS1maWVsZC10eXBlLW1hdC1uYXRpdmUtc2VsZWN0IC5tYXQtZm9ybS1maWVsZC1pbmZpeDo6YWZ0ZXJ7Y29udGVudDoiIjt3aWR0aDowO2hlaWdodDowO2JvcmRlci1sZWZ0OjVweCBzb2xpZCByZ2JhKDAsMCwwLDApO2JvcmRlci1yaWdodDo1cHggc29saWQgcmdiYSgwLDAsMCwwKTtib3JkZXItdG9wOjVweCBzb2xpZDtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6NTAlO3JpZ2h0OjA7bWFyZ2luLXRvcDotMi41cHg7cG9pbnRlci1ldmVudHM6bm9uZX1bZGlyPXJ0bF0gLm1hdC1mb3JtLWZpZWxkLXR5cGUtbWF0LW5hdGl2ZS1zZWxlY3QgLm1hdC1mb3JtLWZpZWxkLWluZml4OjphZnRlcntyaWdodDphdXRvO2xlZnQ6MH0ubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtbmF0aXZlLXNlbGVjdCAubWF0LWlucHV0LWVsZW1lbnR7cGFkZGluZy1yaWdodDoxNXB4fVtkaXI9cnRsXSAubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtbmF0aXZlLXNlbGVjdCAubWF0LWlucHV0LWVsZW1lbnR7cGFkZGluZy1yaWdodDowO3BhZGRpbmctbGVmdDoxNXB4fS5tYXQtZm9ybS1maWVsZC10eXBlLW1hdC1uYXRpdmUtc2VsZWN0IC5tYXQtZm9ybS1maWVsZC1sYWJlbC13cmFwcGVye21heC13aWR0aDpjYWxjKDEwMCUgLSAxMHB4KX0ubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtbmF0aXZlLXNlbGVjdC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUgLm1hdC1mb3JtLWZpZWxkLWluZml4OjphZnRlcnttYXJnaW4tdG9wOi01cHh9Lm1hdC1mb3JtLWZpZWxkLXR5cGUtbWF0LW5hdGl2ZS1zZWxlY3QubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsIC5tYXQtZm9ybS1maWVsZC1pbmZpeDo6YWZ0ZXJ7bWFyZ2luLXRvcDotMTBweH0nLCIubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3RyYW5zZm9ybTpwZXJzcGVjdGl2ZSgxMDBweCl9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5IC5tYXQtZm9ybS1maWVsZC1wcmVmaXggLm1hdC1pY29uLC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLWxlZ2FjeSAubWF0LWZvcm0tZmllbGQtc3VmZml4IC5tYXQtaWNvbnt3aWR0aDoxZW19Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5IC5tYXQtZm9ybS1maWVsZC1wcmVmaXggLm1hdC1pY29uLWJ1dHRvbiwubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLXN1ZmZpeCAubWF0LWljb24tYnV0dG9ue2ZvbnQ6aW5oZXJpdDt2ZXJ0aWNhbC1hbGlnbjpiYXNlbGluZX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLXByZWZpeCAubWF0LWljb24tYnV0dG9uIC5tYXQtaWNvbiwubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLXN1ZmZpeCAubWF0LWljb24tYnV0dG9uIC5tYXQtaWNvbntmb250LXNpemU6aW5oZXJpdH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtoZWlnaHQ6MXB4fS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5IC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmV7aGVpZ2h0OjA7Ym9yZGVyLXRvcDpzb2xpZCAxcHh9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5IC5tYXQtZm9ybS1maWVsZC1yaXBwbGV7dG9wOjA7aGVpZ2h0OjJweDtvdmVyZmxvdzpoaWRkZW59LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZXtoZWlnaHQ6MDtib3JkZXItdG9wOnNvbGlkIDJweH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQgLm1hdC1mb3JtLWZpZWxkLXVuZGVybGluZXtiYWNrZ3JvdW5kLXBvc2l0aW9uOjA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLDApfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5Lm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkIC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmV7Ym9yZGVyLXRvcC1zdHlsZTpkb3R0ZWQ7Ym9yZGVyLXRvcC13aWR0aDoycHg7Ym9yZGVyLXRvcC1jb2xvcjpHcmF5VGV4dH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kubWF0LWZvcm0tZmllbGQtaW52YWxpZDpub3QoLm1hdC1mb2N1c2VkKSAubWF0LWZvcm0tZmllbGQtcmlwcGxle2hlaWdodDoxcHh9IiwiLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtd3JhcHBlcnttYXJnaW46LjI1ZW0gMH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1mbGV4e3BhZGRpbmc6MCAuNzVlbSAwIC43NWVtO21hcmdpbi10b3A6LTAuMjVlbTtwb3NpdGlvbjpyZWxhdGl2ZX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1wcmVmaXgsLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtc3VmZml4e3RvcDouMjVlbX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5le2Rpc3BsYXk6ZmxleDtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6LjI1ZW07bGVmdDowO3JpZ2h0OjA7Ym90dG9tOjA7cG9pbnRlci1ldmVudHM6bm9uZX0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLXN0YXJ0LC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUgLm1hdC1mb3JtLWZpZWxkLW91dGxpbmUtZW5ke2JvcmRlcjoxcHggc29saWQgY3VycmVudENvbG9yO21pbi13aWR0aDo1cHh9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtb3V0bGluZS1zdGFydHtib3JkZXItcmFkaXVzOjVweCAwIDAgNXB4O2JvcmRlci1yaWdodC1zdHlsZTpub25lfVtkaXI9cnRsXSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLXN0YXJ0e2JvcmRlci1yaWdodC1zdHlsZTpzb2xpZDtib3JkZXItbGVmdC1zdHlsZTpub25lO2JvcmRlci1yYWRpdXM6MCA1cHggNXB4IDB9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtb3V0bGluZS1lbmR7Ym9yZGVyLXJhZGl1czowIDVweCA1cHggMDtib3JkZXItbGVmdC1zdHlsZTpub25lO2ZsZXgtZ3JvdzoxfVtkaXI9cnRsXSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLWVuZHtib3JkZXItbGVmdC1zdHlsZTpzb2xpZDtib3JkZXItcmlnaHQtc3R5bGU6bm9uZTtib3JkZXItcmFkaXVzOjVweCAwIDAgNXB4fS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUgLm1hdC1mb3JtLWZpZWxkLW91dGxpbmUtZ2Fwe2JvcmRlci1yYWRpdXM6LjAwMDAwMXB4O2JvcmRlcjoxcHggc29saWQgY3VycmVudENvbG9yO2JvcmRlci1sZWZ0LXN0eWxlOm5vbmU7Ym9yZGVyLXJpZ2h0LXN0eWxlOm5vbmV9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9ybS1maWVsZC1jYW4tZmxvYXQubWF0LWZvcm0tZmllbGQtc2hvdWxkLWZsb2F0IC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLWdhcHtib3JkZXItdG9wLWNvbG9yOnJnYmEoMCwwLDAsMCl9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tvcGFjaXR5OjB9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGljayAubWF0LWZvcm0tZmllbGQtb3V0bGluZS1zdGFydCwubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLXRoaWNrIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLWVuZCwubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLXRoaWNrIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLWdhcHtib3JkZXItd2lkdGg6MnB4fS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvY3VzZWQgLm1hdC1mb3JtLWZpZWxkLW91dGxpbmUsLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9ybS1maWVsZC1pbnZhbGlkIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5le29wYWNpdHk6MDt0cmFuc2l0aW9uOm9wYWNpdHkgMTAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSl9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGljaywubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lLm1hdC1mb3JtLWZpZWxkLWludmFsaWQgLm1hdC1mb3JtLWZpZWxkLW91dGxpbmUtdGhpY2t7b3BhY2l0eToxfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5tYXQtZm9jdXNlZCAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tib3JkZXI6M3B4IGRhc2hlZH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lOm5vdCgubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQpIC5tYXQtZm9ybS1maWVsZC1mbGV4OmhvdmVyIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5le29wYWNpdHk6MDt0cmFuc2l0aW9uOm9wYWNpdHkgNjAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSl9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZTpub3QoLm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkKSAubWF0LWZvcm0tZmllbGQtZmxleDpob3ZlciAubWF0LWZvcm0tZmllbGQtb3V0bGluZS10aGlja3tvcGFjaXR5OjF9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LWZvcm0tZmllbGQtc3Vic2NyaXB0LXdyYXBwZXJ7cGFkZGluZzowIDFlbX0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQgLm1hdC1mb3JtLWZpZWxkLW91dGxpbmV7Y29sb3I6R3JheVRleHR9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZTpub3QoLm1hdC1mb3JtLWZpZWxkLWRpc2FibGVkKSAubWF0LWZvcm0tZmllbGQtZmxleDpob3Zlcn4ubWF0LWZvcm0tZmllbGQtb3V0bGluZSwubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1vdXRsaW5lLl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIC5tYXQtZm9ybS1maWVsZC1vdXRsaW5lLC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmUuX21hdC1hbmltYXRpb24tbm9vcGFibGUgLm1hdC1mb3JtLWZpZWxkLW91dGxpbmUtc3RhcnQsLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LWZvcm0tZmllbGQtb3V0bGluZS1lbmQsLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LWZvcm0tZmllbGQtb3V0bGluZS1nYXB7dHJhbnNpdGlvbjpub25lfSIsIi5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLXN0YW5kYXJkIC5tYXQtZm9ybS1maWVsZC1mbGV4e3BhZGRpbmctdG9wOi43NWVtfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLXN0YW5kYXJkIC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmV7aGVpZ2h0OjFweH0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLXN0YW5kYXJkIC5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmV7aGVpZ2h0OjA7Ym9yZGVyLXRvcDpzb2xpZCAxcHh9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utc3RhbmRhcmQgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZXtib3R0b206MDtoZWlnaHQ6MnB4fS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utc3RhbmRhcmQgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZXtoZWlnaHQ6MDtib3JkZXItdG9wOnNvbGlkIDJweH0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1zdGFuZGFyZC5tYXQtZm9ybS1maWVsZC1kaXNhYmxlZCAubWF0LWZvcm0tZmllbGQtdW5kZXJsaW5le2JhY2tncm91bmQtcG9zaXRpb246MDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCl9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1zdGFuZGFyZC5tYXQtZm9ybS1maWVsZC1kaXNhYmxlZCAubWF0LWZvcm0tZmllbGQtdW5kZXJsaW5le2JvcmRlci10b3Atc3R5bGU6ZG90dGVkO2JvcmRlci10b3Atd2lkdGg6MnB4fS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLXN0YW5kYXJkOm5vdCgubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQpIC5tYXQtZm9ybS1maWVsZC1mbGV4OmhvdmVyfi5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmUgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZXtvcGFjaXR5OjE7dHJhbnNmb3JtOm5vbmU7dHJhbnNpdGlvbjpvcGFjaXR5IDYwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLXN0YW5kYXJkLl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlOm5vdCgubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQpIC5tYXQtZm9ybS1maWVsZC1mbGV4OmhvdmVyfi5tYXQtZm9ybS1maWVsZC11bmRlcmxpbmUgLm1hdC1mb3JtLWZpZWxkLXJpcHBsZXt0cmFuc2l0aW9uOm5vbmV9Il0sZW5jYXBzdWxhdGlvbjoyLGRhdGE6e2FuaW1hdGlvbjpbYmtlLnRyYW5zaXRpb25NZXNzYWdlc119LGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksYWc9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLGxuLG9kLGxuXX0pLG59KSgpLEt0ZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSl7dGhpcy5fcmVuZGVyZXI9ZSx0aGlzLl9lbGVtZW50UmVmPWksdGhpcy5vbkNoYW5nZT1yPT57fSx0aGlzLm9uVG91Y2hlZD0oKT0+e319c2V0UHJvcGVydHkoZSxpKXt0aGlzLl9yZW5kZXJlci5zZXRQcm9wZXJ0eSh0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQsZSxpKX1yZWdpc3Rlck9uVG91Y2hlZChlKXt0aGlzLm9uVG91Y2hlZD1lfXJlZ2lzdGVyT25DaGFuZ2UoZSl7dGhpcy5vbkNoYW5nZT1lfXNldERpc2FibGVkU3RhdGUoZSl7dGhpcy5zZXRQcm9wZXJ0eSgiZGlzYWJsZWQiLGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKEV1KSxNKFJlKSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm59KSxufSkoKSxsZz0oKCk9PntjbGFzcyBuIGV4dGVuZHMgS3Rle31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sZmVhdHVyZXM6W3R0XX0pLG59KSgpLE5vPW5ldyBwZSgiTmdWYWx1ZUFjY2Vzc29yIiksRGtlPXtwcm92aWRlOk5vLHVzZUV4aXN0aW5nOkpuKCgpPT5Ba2UpLG11bHRpOiEwfSxBa2U9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGxne3dyaXRlVmFsdWUoZSl7dGhpcy5zZXRQcm9wZXJ0eSgiY2hlY2tlZCIsZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbigpe2xldCB0O3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4odHx8KHQ9cGkobikpKShpfHxuKX19KCksbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siaW5wdXQiLCJ0eXBlIiwiY2hlY2tib3giLCJmb3JtQ29udHJvbE5hbWUiLCIiXSxbImlucHV0IiwidHlwZSIsImNoZWNrYm94IiwiZm9ybUNvbnRyb2wiLCIiXSxbImlucHV0IiwidHlwZSIsImNoZWNrYm94IiwibmdNb2RlbCIsIiJdXSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJlAoImNoYW5nZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25DaGFuZ2Uoby50YXJnZXQuY2hlY2tlZCl9KSgiYmx1ciIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblRvdWNoZWQoKX0pfSxmZWF0dXJlczpbJHQoW0RrZV0pLHR0XX0pLG59KSgpLElrZT17cHJvdmlkZTpObyx1c2VFeGlzdGluZzpKbigoKT0+QnYpLG11bHRpOiEwfSxSa2U9bmV3IHBlKCJDb21wb3NpdGlvbkV2ZW50TW9kZSIpLEJ2PSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBLdGV7Y29uc3RydWN0b3IoZSxpLHIpe3N1cGVyKGUsaSksdGhpcy5fY29tcG9zaXRpb25Nb2RlPXIsdGhpcy5fY29tcG9zaW5nPSExLG51bGw9PXRoaXMuX2NvbXBvc2l0aW9uTW9kZSYmKHRoaXMuX2NvbXBvc2l0aW9uTW9kZT0hZnVuY3Rpb24oKXtsZXQgbj1ZbCgpP1lsKCkuZ2V0VXNlckFnZW50KCk6IiI7cmV0dXJuL2FuZHJvaWQgKFxkKykvLnRlc3Qobi50b0xvd2VyQ2FzZSgpKX0oKSl9d3JpdGVWYWx1ZShlKXt0aGlzLnNldFByb3BlcnR5KCJ2YWx1ZSIsZT8/IiIpfV9oYW5kbGVJbnB1dChlKXsoIXRoaXMuX2NvbXBvc2l0aW9uTW9kZXx8dGhpcy5fY29tcG9zaXRpb25Nb2RlJiYhdGhpcy5fY29tcG9zaW5nKSYmdGhpcy5vbkNoYW5nZShlKX1fY29tcG9zaXRpb25TdGFydCgpe3RoaXMuX2NvbXBvc2luZz0hMH1fY29tcG9zaXRpb25FbmQoZSl7dGhpcy5fY29tcG9zaW5nPSExLHRoaXMuX2NvbXBvc2l0aW9uTW9kZSYmdGhpcy5vbkNoYW5nZShlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShFdSksTShSZSksTShSa2UsOCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyJpbnB1dCIsImZvcm1Db250cm9sTmFtZSIsIiIsMywidHlwZSIsImNoZWNrYm94Il0sWyJ0ZXh0YXJlYSIsImZvcm1Db250cm9sTmFtZSIsIiJdLFsiaW5wdXQiLCJmb3JtQ29udHJvbCIsIiIsMywidHlwZSIsImNoZWNrYm94Il0sWyJ0ZXh0YXJlYSIsImZvcm1Db250cm9sIiwiIl0sWyJpbnB1dCIsIm5nTW9kZWwiLCIiLDMsInR5cGUiLCJjaGVja2JveCJdLFsidGV4dGFyZWEiLCJuZ01vZGVsIiwiIl0sWyIiLCJuZ0RlZmF1bHRDb250cm9sIiwiIl1dLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgiaW5wdXQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLl9oYW5kbGVJbnB1dChvLnRhcmdldC52YWx1ZSl9KSgiYmx1ciIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblRvdWNoZWQoKX0pKCJjb21wb3NpdGlvbnN0YXJ0IixmdW5jdGlvbigpe3JldHVybiBpLl9jb21wb3NpdGlvblN0YXJ0KCl9KSgiY29tcG9zaXRpb25lbmQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLl9jb21wb3NpdGlvbkVuZChvLnRhcmdldC52YWx1ZSl9KX0sZmVhdHVyZXM6WyR0KFtJa2VdKSx0dF19KSxufSkoKTtmdW5jdGlvbiBGaChuKXtyZXR1cm4gbnVsbD09bnx8KCJzdHJpbmciPT10eXBlb2Ygbnx8QXJyYXkuaXNBcnJheShuKSkmJjA9PT1uLmxlbmd0aH1mdW5jdGlvbiBadGUobil7cmV0dXJuIG51bGwhPW4mJiJudW1iZXIiPT10eXBlb2Ygbi5sZW5ndGh9dmFyIExvPW5ldyBwZSgiTmdWYWxpZGF0b3JzIiksTmg9bmV3IHBlKCJOZ0FzeW5jVmFsaWRhdG9ycyIpLGtrZT0vXig/PS57MSwyNTR9JCkoPz0uezEsNjR9QClbYS16QS1aMC05ISMkJSYnKisvPT9eX2B7fH1+LV0rKD86XC5bYS16QS1aMC05ISMkJSYnKisvPT9eX2B7fH1+LV0rKSpAW2EtekEtWjAtOV0oPzpbYS16QS1aMC05LV17MCw2MX1bYS16QS1aMC05XSk/KD86XC5bYS16QS1aMC05XSg/OlthLXpBLVowLTktXXswLDYxfVthLXpBLVowLTldKT8pKiQvLEZvPWNsYXNze3N0YXRpYyBtaW4odCl7cmV0dXJuIEp0ZSh0KX1zdGF0aWMgbWF4KHQpe3JldHVybiAkdGUodCl9c3RhdGljIHJlcXVpcmVkKHQpe3JldHVybiBlbmUodCl9c3RhdGljIHJlcXVpcmVkVHJ1ZSh0KXtyZXR1cm4gdG5lKHQpfXN0YXRpYyBlbWFpbCh0KXtyZXR1cm4gbm5lKHQpfXN0YXRpYyBtaW5MZW5ndGgodCl7cmV0dXJuIGluZSh0KX1zdGF0aWMgbWF4TGVuZ3RoKHQpe3JldHVybiBybmUodCl9c3RhdGljIHBhdHRlcm4odCl7cmV0dXJuIG9uZSh0KX1zdGF0aWMgbnVsbFZhbGlkYXRvcih0KXtyZXR1cm4gbnVsbH1zdGF0aWMgY29tcG9zZSh0KXtyZXR1cm4gZG5lKHQpfXN0YXRpYyBjb21wb3NlQXN5bmModCl7cmV0dXJuIHBuZSh0KX19O2Z1bmN0aW9uIEp0ZShuKXtyZXR1cm4gdD0+e2lmKEZoKHQudmFsdWUpfHxGaChuKSlyZXR1cm4gbnVsbDtsZXQgZT1wYXJzZUZsb2F0KHQudmFsdWUpO3JldHVybiFpc05hTihlKSYmZTxuP3ttaW46e21pbjpuLGFjdHVhbDp0LnZhbHVlfX06bnVsbH19ZnVuY3Rpb24gJHRlKG4pe3JldHVybiB0PT57aWYoRmgodC52YWx1ZSl8fEZoKG4pKXJldHVybiBudWxsO2xldCBlPXBhcnNlRmxvYXQodC52YWx1ZSk7cmV0dXJuIWlzTmFOKGUpJiZlPm4/e21heDp7bWF4Om4sYWN0dWFsOnQudmFsdWV9fTpudWxsfX1mdW5jdGlvbiBlbmUobil7cmV0dXJuIEZoKG4udmFsdWUpP3tyZXF1aXJlZDohMH06bnVsbH1mdW5jdGlvbiB0bmUobil7cmV0dXJuITA9PT1uLnZhbHVlP251bGw6e3JlcXVpcmVkOiEwfX1mdW5jdGlvbiBubmUobil7cmV0dXJuIEZoKG4udmFsdWUpfHxra2UudGVzdChuLnZhbHVlKT9udWxsOntlbWFpbDohMH19ZnVuY3Rpb24gaW5lKG4pe3JldHVybiB0PT5GaCh0LnZhbHVlKXx8IVp0ZSh0LnZhbHVlKT9udWxsOnQudmFsdWUubGVuZ3RoPG4/e21pbmxlbmd0aDp7cmVxdWlyZWRMZW5ndGg6bixhY3R1YWxMZW5ndGg6dC52YWx1ZS5sZW5ndGh9fTpudWxsfWZ1bmN0aW9uIHJuZShuKXtyZXR1cm4gdD0+WnRlKHQudmFsdWUpJiZ0LnZhbHVlLmxlbmd0aD5uP3ttYXhsZW5ndGg6e3JlcXVpcmVkTGVuZ3RoOm4sYWN0dWFsTGVuZ3RoOnQudmFsdWUubGVuZ3RofX06bnVsbH1mdW5jdGlvbiBvbmUobil7aWYoIW4pcmV0dXJuIFAyO2xldCB0LGU7cmV0dXJuInN0cmluZyI9PXR5cGVvZiBuPyhlPSIiLCJeIiE9PW4uY2hhckF0KDApJiYoZSs9Il4iKSxlKz1uLCIkIiE9PW4uY2hhckF0KG4ubGVuZ3RoLTEpJiYoZSs9IiQiKSx0PW5ldyBSZWdFeHAoZSkpOihlPW4udG9TdHJpbmcoKSx0PW4pLGk9PntpZihGaChpLnZhbHVlKSlyZXR1cm4gbnVsbDtsZXQgcj1pLnZhbHVlO3JldHVybiB0LnRlc3Qocik/bnVsbDp7cGF0dGVybjp7cmVxdWlyZWRQYXR0ZXJuOmUsYWN0dWFsVmFsdWU6cn19fX1mdW5jdGlvbiBQMihuKXtyZXR1cm4gbnVsbH1mdW5jdGlvbiBzbmUobil7cmV0dXJuIG51bGwhPW59ZnVuY3Rpb24gYW5lKG4pe3JldHVybiBuXyhuKT9FbyhuKTpufWZ1bmN0aW9uIGxuZShuKXtsZXQgdD17fTtyZXR1cm4gbi5mb3JFYWNoKGU9Pnt0PW51bGwhPWU/ey4uLnQsLi4uZX06dH0pLDA9PT1PYmplY3Qua2V5cyh0KS5sZW5ndGg/bnVsbDp0fWZ1bmN0aW9uIGNuZShuLHQpe3JldHVybiB0Lm1hcChlPT5lKG4pKX1mdW5jdGlvbiB1bmUobil7cmV0dXJuIG4ubWFwKHQ9PmZ1bmN0aW9uKG4pe3JldHVybiFuLnZhbGlkYXRlfSh0KT90OmU9PnQudmFsaWRhdGUoZSkpfWZ1bmN0aW9uIGRuZShuKXtpZighbilyZXR1cm4gbnVsbDtsZXQgdD1uLmZpbHRlcihzbmUpO3JldHVybiAwPT10Lmxlbmd0aD9udWxsOmZ1bmN0aW9uKGUpe3JldHVybiBsbmUoY25lKGUsdCkpfX1mdW5jdGlvbiBVSChuKXtyZXR1cm4gbnVsbCE9bj9kbmUodW5lKG4pKTpudWxsfWZ1bmN0aW9uIHBuZShuKXtpZighbilyZXR1cm4gbnVsbDtsZXQgdD1uLmZpbHRlcihzbmUpO3JldHVybiAwPT10Lmxlbmd0aD9udWxsOmZ1bmN0aW9uKGUpe3JldHVybiBscihjbmUoZSx0KS5tYXAoYW5lKSkucGlwZShMKGxuZSkpfX1mdW5jdGlvbiB6SChuKXtyZXR1cm4gbnVsbCE9bj9wbmUodW5lKG4pKTpudWxsfWZ1bmN0aW9uIFV0ZShuLHQpe3JldHVybiBudWxsPT09bj9bdF06QXJyYXkuaXNBcnJheShuKT9bLi4ubix0XTpbbix0XX1mdW5jdGlvbiBobmUobil7cmV0dXJuIG4uX3Jhd1ZhbGlkYXRvcnN9ZnVuY3Rpb24gZm5lKG4pe3JldHVybiBuLl9yYXdBc3luY1ZhbGlkYXRvcnN9ZnVuY3Rpb24gQkgobil7cmV0dXJuIG4/QXJyYXkuaXNBcnJheShuKT9uOltuXTpbXX1mdW5jdGlvbiBSMihuLHQpe3JldHVybiBBcnJheS5pc0FycmF5KG4pP24uaW5jbHVkZXModCk6bj09PXR9ZnVuY3Rpb24genRlKG4sdCl7bGV0IGU9QkgodCk7cmV0dXJuIEJIKG4pLmZvckVhY2gocj0+e1IyKGUscil8fGUucHVzaChyKX0pLGV9ZnVuY3Rpb24ganRlKG4sdCl7cmV0dXJuIEJIKHQpLmZpbHRlcihlPT4hUjIobixlKSl9dmFyIE8yPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5fcmF3VmFsaWRhdG9ycz1bXSx0aGlzLl9yYXdBc3luY1ZhbGlkYXRvcnM9W10sdGhpcy5fb25EZXN0cm95Q2FsbGJhY2tzPVtdfWdldCB2YWx1ZSgpe3JldHVybiB0aGlzLmNvbnRyb2w/dGhpcy5jb250cm9sLnZhbHVlOm51bGx9Z2V0IHZhbGlkKCl7cmV0dXJuIHRoaXMuY29udHJvbD90aGlzLmNvbnRyb2wudmFsaWQ6bnVsbH1nZXQgaW52YWxpZCgpe3JldHVybiB0aGlzLmNvbnRyb2w/dGhpcy5jb250cm9sLmludmFsaWQ6bnVsbH1nZXQgcGVuZGluZygpe3JldHVybiB0aGlzLmNvbnRyb2w/dGhpcy5jb250cm9sLnBlbmRpbmc6bnVsbH1nZXQgZGlzYWJsZWQoKXtyZXR1cm4gdGhpcy5jb250cm9sP3RoaXMuY29udHJvbC5kaXNhYmxlZDpudWxsfWdldCBlbmFibGVkKCl7cmV0dXJuIHRoaXMuY29udHJvbD90aGlzLmNvbnRyb2wuZW5hYmxlZDpudWxsfWdldCBlcnJvcnMoKXtyZXR1cm4gdGhpcy5jb250cm9sP3RoaXMuY29udHJvbC5lcnJvcnM6bnVsbH1nZXQgcHJpc3RpbmUoKXtyZXR1cm4gdGhpcy5jb250cm9sP3RoaXMuY29udHJvbC5wcmlzdGluZTpudWxsfWdldCBkaXJ0eSgpe3JldHVybiB0aGlzLmNvbnRyb2w/dGhpcy5jb250cm9sLmRpcnR5Om51bGx9Z2V0IHRvdWNoZWQoKXtyZXR1cm4gdGhpcy5jb250cm9sP3RoaXMuY29udHJvbC50b3VjaGVkOm51bGx9Z2V0IHN0YXR1cygpe3JldHVybiB0aGlzLmNvbnRyb2w/dGhpcy5jb250cm9sLnN0YXR1czpudWxsfWdldCB1bnRvdWNoZWQoKXtyZXR1cm4gdGhpcy5jb250cm9sP3RoaXMuY29udHJvbC51bnRvdWNoZWQ6bnVsbH1nZXQgc3RhdHVzQ2hhbmdlcygpe3JldHVybiB0aGlzLmNvbnRyb2w/dGhpcy5jb250cm9sLnN0YXR1c0NoYW5nZXM6bnVsbH1nZXQgdmFsdWVDaGFuZ2VzKCl7cmV0dXJuIHRoaXMuY29udHJvbD90aGlzLmNvbnRyb2wudmFsdWVDaGFuZ2VzOm51bGx9Z2V0IHBhdGgoKXtyZXR1cm4gbnVsbH1fc2V0VmFsaWRhdG9ycyh0KXt0aGlzLl9yYXdWYWxpZGF0b3JzPXR8fFtdLHRoaXMuX2NvbXBvc2VkVmFsaWRhdG9yRm49VUgodGhpcy5fcmF3VmFsaWRhdG9ycyl9X3NldEFzeW5jVmFsaWRhdG9ycyh0KXt0aGlzLl9yYXdBc3luY1ZhbGlkYXRvcnM9dHx8W10sdGhpcy5fY29tcG9zZWRBc3luY1ZhbGlkYXRvckZuPXpIKHRoaXMuX3Jhd0FzeW5jVmFsaWRhdG9ycyl9Z2V0IHZhbGlkYXRvcigpe3JldHVybiB0aGlzLl9jb21wb3NlZFZhbGlkYXRvckZufHxudWxsfWdldCBhc3luY1ZhbGlkYXRvcigpe3JldHVybiB0aGlzLl9jb21wb3NlZEFzeW5jVmFsaWRhdG9yRm58fG51bGx9X3JlZ2lzdGVyT25EZXN0cm95KHQpe3RoaXMuX29uRGVzdHJveUNhbGxiYWNrcy5wdXNoKHQpfV9pbnZva2VPbkRlc3Ryb3lDYWxsYmFja3MoKXt0aGlzLl9vbkRlc3Ryb3lDYWxsYmFja3MuZm9yRWFjaCh0PT50KCkpLHRoaXMuX29uRGVzdHJveUNhbGxiYWNrcz1bXX1yZXNldCh0KXt0aGlzLmNvbnRyb2wmJnRoaXMuY29udHJvbC5yZXNldCh0KX1oYXNFcnJvcih0LGUpe3JldHVybiEhdGhpcy5jb250cm9sJiZ0aGlzLmNvbnRyb2wuaGFzRXJyb3IodCxlKX1nZXRFcnJvcih0LGUpe3JldHVybiB0aGlzLmNvbnRyb2w/dGhpcy5jb250cm9sLmdldEVycm9yKHQsZSk6bnVsbH19LEZzPWNsYXNzIGV4dGVuZHMgTzJ7Z2V0IGZvcm1EaXJlY3RpdmUoKXtyZXR1cm4gbnVsbH1nZXQgcGF0aCgpe3JldHVybiBudWxsfX0sTnM9Y2xhc3MgZXh0ZW5kcyBPMntjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fcGFyZW50PW51bGwsdGhpcy5uYW1lPW51bGwsdGhpcy52YWx1ZUFjY2Vzc29yPW51bGx9fSxWSD1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLl9jZD10fWdldCBpc1RvdWNoZWQoKXtyZXR1cm4hIXRoaXMuX2NkPy5jb250cm9sPy50b3VjaGVkfWdldCBpc1VudG91Y2hlZCgpe3JldHVybiEhdGhpcy5fY2Q/LmNvbnRyb2w/LnVudG91Y2hlZH1nZXQgaXNQcmlzdGluZSgpe3JldHVybiEhdGhpcy5fY2Q/LmNvbnRyb2w/LnByaXN0aW5lfWdldCBpc0RpcnR5KCl7cmV0dXJuISF0aGlzLl9jZD8uY29udHJvbD8uZGlydHl9Z2V0IGlzVmFsaWQoKXtyZXR1cm4hIXRoaXMuX2NkPy5jb250cm9sPy52YWxpZH1nZXQgaXNJbnZhbGlkKCl7cmV0dXJuISF0aGlzLl9jZD8uY29udHJvbD8uaW52YWxpZH1nZXQgaXNQZW5kaW5nKCl7cmV0dXJuISF0aGlzLl9jZD8uY29udHJvbD8ucGVuZGluZ31nZXQgaXNTdWJtaXR0ZWQoKXtyZXR1cm4hIXRoaXMuX2NkPy5zdWJtaXR0ZWR9fSxWMj0oKCk9PntjbGFzcyBuIGV4dGVuZHMgVkh7Y29uc3RydWN0b3IoZSl7c3VwZXIoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oTnMsMikpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJmb3JtQ29udHJvbE5hbWUiLCIiXSxbIiIsIm5nTW9kZWwiLCIiXSxbIiIsImZvcm1Db250cm9sIiwiIl1dLGhvc3RWYXJzOjE0LGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmZXQoIm5nLXVudG91Y2hlZCIsaS5pc1VudG91Y2hlZCkoIm5nLXRvdWNoZWQiLGkuaXNUb3VjaGVkKSgibmctcHJpc3RpbmUiLGkuaXNQcmlzdGluZSkoIm5nLWRpcnR5IixpLmlzRGlydHkpKCJuZy12YWxpZCIsaS5pc1ZhbGlkKSgibmctaW52YWxpZCIsaS5pc0ludmFsaWQpKCJuZy1wZW5kaW5nIixpLmlzUGVuZGluZyl9LGZlYXR1cmVzOlt0dF19KSxufSkoKSxkdz0iVkFMSUQiLEkyPSJJTlZBTElEIixMdj0iUEVORElORyIscHc9IkRJU0FCTEVEIjtmdW5jdGlvbiBnbmUobil7cmV0dXJuKEgyKG4pP24udmFsaWRhdG9yczpuKXx8bnVsbH1mdW5jdGlvbiBHdGUobil7cmV0dXJuIEFycmF5LmlzQXJyYXkobik/VUgobik6bnx8bnVsbH1mdW5jdGlvbiBfbmUobix0KXtyZXR1cm4oSDIodCk/dC5hc3luY1ZhbGlkYXRvcnM6bil8fG51bGx9ZnVuY3Rpb24gV3RlKG4pe3JldHVybiBBcnJheS5pc0FycmF5KG4pP3pIKG4pOm58fG51bGx9ZnVuY3Rpb24gSDIobil7cmV0dXJuIG51bGwhPW4mJiFBcnJheS5pc0FycmF5KG4pJiYib2JqZWN0Ij09dHlwZW9mIG59dmFyIGsyPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5fcGVuZGluZ0RpcnR5PSExLHRoaXMuX2hhc093blBlbmRpbmdBc3luY1ZhbGlkYXRvcj0hMSx0aGlzLl9wZW5kaW5nVG91Y2hlZD0hMSx0aGlzLl9vbkNvbGxlY3Rpb25DaGFuZ2U9KCk9Pnt9LHRoaXMuX3BhcmVudD1udWxsLHRoaXMucHJpc3RpbmU9ITAsdGhpcy50b3VjaGVkPSExLHRoaXMuX29uRGlzYWJsZWRDaGFuZ2U9W10sdGhpcy5fcmF3VmFsaWRhdG9ycz10LHRoaXMuX3Jhd0FzeW5jVmFsaWRhdG9ycz1lLHRoaXMuX2NvbXBvc2VkVmFsaWRhdG9yRm49R3RlKHRoaXMuX3Jhd1ZhbGlkYXRvcnMpLHRoaXMuX2NvbXBvc2VkQXN5bmNWYWxpZGF0b3JGbj1XdGUodGhpcy5fcmF3QXN5bmNWYWxpZGF0b3JzKX1nZXQgdmFsaWRhdG9yKCl7cmV0dXJuIHRoaXMuX2NvbXBvc2VkVmFsaWRhdG9yRm59c2V0IHZhbGlkYXRvcih0KXt0aGlzLl9yYXdWYWxpZGF0b3JzPXRoaXMuX2NvbXBvc2VkVmFsaWRhdG9yRm49dH1nZXQgYXN5bmNWYWxpZGF0b3IoKXtyZXR1cm4gdGhpcy5fY29tcG9zZWRBc3luY1ZhbGlkYXRvckZufXNldCBhc3luY1ZhbGlkYXRvcih0KXt0aGlzLl9yYXdBc3luY1ZhbGlkYXRvcnM9dGhpcy5fY29tcG9zZWRBc3luY1ZhbGlkYXRvckZuPXR9Z2V0IHBhcmVudCgpe3JldHVybiB0aGlzLl9wYXJlbnR9Z2V0IHZhbGlkKCl7cmV0dXJuIHRoaXMuc3RhdHVzPT09ZHd9Z2V0IGludmFsaWQoKXtyZXR1cm4gdGhpcy5zdGF0dXM9PT1JMn1nZXQgcGVuZGluZygpe3JldHVybiB0aGlzLnN0YXR1cz09THZ9Z2V0IGRpc2FibGVkKCl7cmV0dXJuIHRoaXMuc3RhdHVzPT09cHd9Z2V0IGVuYWJsZWQoKXtyZXR1cm4gdGhpcy5zdGF0dXMhPT1wd31nZXQgZGlydHkoKXtyZXR1cm4hdGhpcy5wcmlzdGluZX1nZXQgdW50b3VjaGVkKCl7cmV0dXJuIXRoaXMudG91Y2hlZH1nZXQgdXBkYXRlT24oKXtyZXR1cm4gdGhpcy5fdXBkYXRlT24/dGhpcy5fdXBkYXRlT246dGhpcy5wYXJlbnQ/dGhpcy5wYXJlbnQudXBkYXRlT246ImNoYW5nZSJ9c2V0VmFsaWRhdG9ycyh0KXt0aGlzLl9yYXdWYWxpZGF0b3JzPXQsdGhpcy5fY29tcG9zZWRWYWxpZGF0b3JGbj1HdGUodCl9c2V0QXN5bmNWYWxpZGF0b3JzKHQpe3RoaXMuX3Jhd0FzeW5jVmFsaWRhdG9ycz10LHRoaXMuX2NvbXBvc2VkQXN5bmNWYWxpZGF0b3JGbj1XdGUodCl9YWRkVmFsaWRhdG9ycyh0KXt0aGlzLnNldFZhbGlkYXRvcnMoenRlKHQsdGhpcy5fcmF3VmFsaWRhdG9ycykpfWFkZEFzeW5jVmFsaWRhdG9ycyh0KXt0aGlzLnNldEFzeW5jVmFsaWRhdG9ycyh6dGUodCx0aGlzLl9yYXdBc3luY1ZhbGlkYXRvcnMpKX1yZW1vdmVWYWxpZGF0b3JzKHQpe3RoaXMuc2V0VmFsaWRhdG9ycyhqdGUodCx0aGlzLl9yYXdWYWxpZGF0b3JzKSl9cmVtb3ZlQXN5bmNWYWxpZGF0b3JzKHQpe3RoaXMuc2V0QXN5bmNWYWxpZGF0b3JzKGp0ZSh0LHRoaXMuX3Jhd0FzeW5jVmFsaWRhdG9ycykpfWhhc1ZhbGlkYXRvcih0KXtyZXR1cm4gUjIodGhpcy5fcmF3VmFsaWRhdG9ycyx0KX1oYXNBc3luY1ZhbGlkYXRvcih0KXtyZXR1cm4gUjIodGhpcy5fcmF3QXN5bmNWYWxpZGF0b3JzLHQpfWNsZWFyVmFsaWRhdG9ycygpe3RoaXMudmFsaWRhdG9yPW51bGx9Y2xlYXJBc3luY1ZhbGlkYXRvcnMoKXt0aGlzLmFzeW5jVmFsaWRhdG9yPW51bGx9bWFya0FzVG91Y2hlZCh0PXt9KXt0aGlzLnRvdWNoZWQ9ITAsdGhpcy5fcGFyZW50JiYhdC5vbmx5U2VsZiYmdGhpcy5fcGFyZW50Lm1hcmtBc1RvdWNoZWQodCl9bWFya0FsbEFzVG91Y2hlZCgpe3RoaXMubWFya0FzVG91Y2hlZCh7b25seVNlbGY6ITB9KSx0aGlzLl9mb3JFYWNoQ2hpbGQodD0+dC5tYXJrQWxsQXNUb3VjaGVkKCkpfW1hcmtBc1VudG91Y2hlZCh0PXt9KXt0aGlzLnRvdWNoZWQ9ITEsdGhpcy5fcGVuZGluZ1RvdWNoZWQ9ITEsdGhpcy5fZm9yRWFjaENoaWxkKGU9PntlLm1hcmtBc1VudG91Y2hlZCh7b25seVNlbGY6ITB9KX0pLHRoaXMuX3BhcmVudCYmIXQub25seVNlbGYmJnRoaXMuX3BhcmVudC5fdXBkYXRlVG91Y2hlZCh0KX1tYXJrQXNEaXJ0eSh0PXt9KXt0aGlzLnByaXN0aW5lPSExLHRoaXMuX3BhcmVudCYmIXQub25seVNlbGYmJnRoaXMuX3BhcmVudC5tYXJrQXNEaXJ0eSh0KX1tYXJrQXNQcmlzdGluZSh0PXt9KXt0aGlzLnByaXN0aW5lPSEwLHRoaXMuX3BlbmRpbmdEaXJ0eT0hMSx0aGlzLl9mb3JFYWNoQ2hpbGQoZT0+e2UubWFya0FzUHJpc3RpbmUoe29ubHlTZWxmOiEwfSl9KSx0aGlzLl9wYXJlbnQmJiF0Lm9ubHlTZWxmJiZ0aGlzLl9wYXJlbnQuX3VwZGF0ZVByaXN0aW5lKHQpfW1hcmtBc1BlbmRpbmcodD17fSl7dGhpcy5zdGF0dXM9THYsITEhPT10LmVtaXRFdmVudCYmdGhpcy5zdGF0dXNDaGFuZ2VzLmVtaXQodGhpcy5zdGF0dXMpLHRoaXMuX3BhcmVudCYmIXQub25seVNlbGYmJnRoaXMuX3BhcmVudC5tYXJrQXNQZW5kaW5nKHQpfWRpc2FibGUodD17fSl7bGV0IGU9dGhpcy5fcGFyZW50TWFya2VkRGlydHkodC5vbmx5U2VsZik7dGhpcy5zdGF0dXM9cHcsdGhpcy5lcnJvcnM9bnVsbCx0aGlzLl9mb3JFYWNoQ2hpbGQoaT0+e2kuZGlzYWJsZSh7Li4udCxvbmx5U2VsZjohMH0pfSksdGhpcy5fdXBkYXRlVmFsdWUoKSwhMSE9PXQuZW1pdEV2ZW50JiYodGhpcy52YWx1ZUNoYW5nZXMuZW1pdCh0aGlzLnZhbHVlKSx0aGlzLnN0YXR1c0NoYW5nZXMuZW1pdCh0aGlzLnN0YXR1cykpLHRoaXMuX3VwZGF0ZUFuY2VzdG9ycyh7Li4udCxza2lwUHJpc3RpbmVDaGVjazplfSksdGhpcy5fb25EaXNhYmxlZENoYW5nZS5mb3JFYWNoKGk9PmkoITApKX1lbmFibGUodD17fSl7bGV0IGU9dGhpcy5fcGFyZW50TWFya2VkRGlydHkodC5vbmx5U2VsZik7dGhpcy5zdGF0dXM9ZHcsdGhpcy5fZm9yRWFjaENoaWxkKGk9PntpLmVuYWJsZSh7Li4udCxvbmx5U2VsZjohMH0pfSksdGhpcy51cGRhdGVWYWx1ZUFuZFZhbGlkaXR5KHtvbmx5U2VsZjohMCxlbWl0RXZlbnQ6dC5lbWl0RXZlbnR9KSx0aGlzLl91cGRhdGVBbmNlc3RvcnMoey4uLnQsc2tpcFByaXN0aW5lQ2hlY2s6ZX0pLHRoaXMuX29uRGlzYWJsZWRDaGFuZ2UuZm9yRWFjaChpPT5pKCExKSl9X3VwZGF0ZUFuY2VzdG9ycyh0KXt0aGlzLl9wYXJlbnQmJiF0Lm9ubHlTZWxmJiYodGhpcy5fcGFyZW50LnVwZGF0ZVZhbHVlQW5kVmFsaWRpdHkodCksdC5za2lwUHJpc3RpbmVDaGVja3x8dGhpcy5fcGFyZW50Ll91cGRhdGVQcmlzdGluZSgpLHRoaXMuX3BhcmVudC5fdXBkYXRlVG91Y2hlZCgpKX1zZXRQYXJlbnQodCl7dGhpcy5fcGFyZW50PXR9Z2V0UmF3VmFsdWUoKXtyZXR1cm4gdGhpcy52YWx1ZX11cGRhdGVWYWx1ZUFuZFZhbGlkaXR5KHQ9e30pe3RoaXMuX3NldEluaXRpYWxTdGF0dXMoKSx0aGlzLl91cGRhdGVWYWx1ZSgpLHRoaXMuZW5hYmxlZCYmKHRoaXMuX2NhbmNlbEV4aXN0aW5nU3Vic2NyaXB0aW9uKCksdGhpcy5lcnJvcnM9dGhpcy5fcnVuVmFsaWRhdG9yKCksdGhpcy5zdGF0dXM9dGhpcy5fY2FsY3VsYXRlU3RhdHVzKCksKHRoaXMuc3RhdHVzPT09ZHd8fHRoaXMuc3RhdHVzPT09THYpJiZ0aGlzLl9ydW5Bc3luY1ZhbGlkYXRvcih0LmVtaXRFdmVudCkpLCExIT09dC5lbWl0RXZlbnQmJih0aGlzLnZhbHVlQ2hhbmdlcy5lbWl0KHRoaXMudmFsdWUpLHRoaXMuc3RhdHVzQ2hhbmdlcy5lbWl0KHRoaXMuc3RhdHVzKSksdGhpcy5fcGFyZW50JiYhdC5vbmx5U2VsZiYmdGhpcy5fcGFyZW50LnVwZGF0ZVZhbHVlQW5kVmFsaWRpdHkodCl9X3VwZGF0ZVRyZWVWYWxpZGl0eSh0PXtlbWl0RXZlbnQ6ITB9KXt0aGlzLl9mb3JFYWNoQ2hpbGQoZT0+ZS5fdXBkYXRlVHJlZVZhbGlkaXR5KHQpKSx0aGlzLnVwZGF0ZVZhbHVlQW5kVmFsaWRpdHkoe29ubHlTZWxmOiEwLGVtaXRFdmVudDp0LmVtaXRFdmVudH0pfV9zZXRJbml0aWFsU3RhdHVzKCl7dGhpcy5zdGF0dXM9dGhpcy5fYWxsQ29udHJvbHNEaXNhYmxlZCgpP3B3OmR3fV9ydW5WYWxpZGF0b3IoKXtyZXR1cm4gdGhpcy52YWxpZGF0b3I/dGhpcy52YWxpZGF0b3IodGhpcyk6bnVsbH1fcnVuQXN5bmNWYWxpZGF0b3IodCl7aWYodGhpcy5hc3luY1ZhbGlkYXRvcil7dGhpcy5zdGF0dXM9THYsdGhpcy5faGFzT3duUGVuZGluZ0FzeW5jVmFsaWRhdG9yPSEwO2xldCBlPWFuZSh0aGlzLmFzeW5jVmFsaWRhdG9yKHRoaXMpKTt0aGlzLl9hc3luY1ZhbGlkYXRpb25TdWJzY3JpcHRpb249ZS5zdWJzY3JpYmUoaT0+e3RoaXMuX2hhc093blBlbmRpbmdBc3luY1ZhbGlkYXRvcj0hMSx0aGlzLnNldEVycm9ycyhpLHtlbWl0RXZlbnQ6dH0pfSl9fV9jYW5jZWxFeGlzdGluZ1N1YnNjcmlwdGlvbigpe3RoaXMuX2FzeW5jVmFsaWRhdGlvblN1YnNjcmlwdGlvbiYmKHRoaXMuX2FzeW5jVmFsaWRhdGlvblN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX2hhc093blBlbmRpbmdBc3luY1ZhbGlkYXRvcj0hMSl9c2V0RXJyb3JzKHQsZT17fSl7dGhpcy5lcnJvcnM9dCx0aGlzLl91cGRhdGVDb250cm9sc0Vycm9ycyghMSE9PWUuZW1pdEV2ZW50KX1nZXQodCl7bGV0IGU9dDtyZXR1cm4gbnVsbD09ZXx8KEFycmF5LmlzQXJyYXkoZSl8fChlPWUuc3BsaXQoIi4iKSksMD09PWUubGVuZ3RoKT9udWxsOmUucmVkdWNlKChpLHIpPT5pJiZpLl9maW5kKHIpLHRoaXMpfWdldEVycm9yKHQsZSl7bGV0IGk9ZT90aGlzLmdldChlKTp0aGlzO3JldHVybiBpJiZpLmVycm9ycz9pLmVycm9yc1t0XTpudWxsfWhhc0Vycm9yKHQsZSl7cmV0dXJuISF0aGlzLmdldEVycm9yKHQsZSl9Z2V0IHJvb3QoKXtsZXQgdD10aGlzO2Zvcig7dC5fcGFyZW50Oyl0PXQuX3BhcmVudDtyZXR1cm4gdH1fdXBkYXRlQ29udHJvbHNFcnJvcnModCl7dGhpcy5zdGF0dXM9dGhpcy5fY2FsY3VsYXRlU3RhdHVzKCksdCYmdGhpcy5zdGF0dXNDaGFuZ2VzLmVtaXQodGhpcy5zdGF0dXMpLHRoaXMuX3BhcmVudCYmdGhpcy5fcGFyZW50Ll91cGRhdGVDb250cm9sc0Vycm9ycyh0KX1faW5pdE9ic2VydmFibGVzKCl7dGhpcy52YWx1ZUNoYW5nZXM9bmV3IEcsdGhpcy5zdGF0dXNDaGFuZ2VzPW5ldyBHfV9jYWxjdWxhdGVTdGF0dXMoKXtyZXR1cm4gdGhpcy5fYWxsQ29udHJvbHNEaXNhYmxlZCgpP3B3OnRoaXMuZXJyb3JzP0kyOnRoaXMuX2hhc093blBlbmRpbmdBc3luY1ZhbGlkYXRvcnx8dGhpcy5fYW55Q29udHJvbHNIYXZlU3RhdHVzKEx2KT9Mdjp0aGlzLl9hbnlDb250cm9sc0hhdmVTdGF0dXMoSTIpP0kyOmR3fV9hbnlDb250cm9sc0hhdmVTdGF0dXModCl7cmV0dXJuIHRoaXMuX2FueUNvbnRyb2xzKGU9PmUuc3RhdHVzPT09dCl9X2FueUNvbnRyb2xzRGlydHkoKXtyZXR1cm4gdGhpcy5fYW55Q29udHJvbHModD0+dC5kaXJ0eSl9X2FueUNvbnRyb2xzVG91Y2hlZCgpe3JldHVybiB0aGlzLl9hbnlDb250cm9scyh0PT50LnRvdWNoZWQpfV91cGRhdGVQcmlzdGluZSh0PXt9KXt0aGlzLnByaXN0aW5lPSF0aGlzLl9hbnlDb250cm9sc0RpcnR5KCksdGhpcy5fcGFyZW50JiYhdC5vbmx5U2VsZiYmdGhpcy5fcGFyZW50Ll91cGRhdGVQcmlzdGluZSh0KX1fdXBkYXRlVG91Y2hlZCh0PXt9KXt0aGlzLnRvdWNoZWQ9dGhpcy5fYW55Q29udHJvbHNUb3VjaGVkKCksdGhpcy5fcGFyZW50JiYhdC5vbmx5U2VsZiYmdGhpcy5fcGFyZW50Ll91cGRhdGVUb3VjaGVkKHQpfV9yZWdpc3Rlck9uQ29sbGVjdGlvbkNoYW5nZSh0KXt0aGlzLl9vbkNvbGxlY3Rpb25DaGFuZ2U9dH1fc2V0VXBkYXRlU3RyYXRlZ3kodCl7SDIodCkmJm51bGwhPXQudXBkYXRlT24mJih0aGlzLl91cGRhdGVPbj10LnVwZGF0ZU9uKX1fcGFyZW50TWFya2VkRGlydHkodCl7cmV0dXJuIXQmJiEoIXRoaXMuX3BhcmVudHx8IXRoaXMuX3BhcmVudC5kaXJ0eSkmJiF0aGlzLl9wYXJlbnQuX2FueUNvbnRyb2xzRGlydHkoKX1fZmluZCh0KXtyZXR1cm4gbnVsbH19LEYyPWNsYXNzIGV4dGVuZHMgazJ7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKGduZShlKSxfbmUoaSxlKSksdGhpcy5jb250cm9scz10LHRoaXMuX2luaXRPYnNlcnZhYmxlcygpLHRoaXMuX3NldFVwZGF0ZVN0cmF0ZWd5KGUpLHRoaXMuX3NldFVwQ29udHJvbHMoKSx0aGlzLnVwZGF0ZVZhbHVlQW5kVmFsaWRpdHkoe29ubHlTZWxmOiEwLGVtaXRFdmVudDohIXRoaXMuYXN5bmNWYWxpZGF0b3J9KX1yZWdpc3RlckNvbnRyb2wodCxlKXtyZXR1cm4gdGhpcy5jb250cm9sc1t0XT90aGlzLmNvbnRyb2xzW3RdOih0aGlzLmNvbnRyb2xzW3RdPWUsZS5zZXRQYXJlbnQodGhpcyksZS5fcmVnaXN0ZXJPbkNvbGxlY3Rpb25DaGFuZ2UodGhpcy5fb25Db2xsZWN0aW9uQ2hhbmdlKSxlKX1hZGRDb250cm9sKHQsZSxpPXt9KXt0aGlzLnJlZ2lzdGVyQ29udHJvbCh0LGUpLHRoaXMudXBkYXRlVmFsdWVBbmRWYWxpZGl0eSh7ZW1pdEV2ZW50OmkuZW1pdEV2ZW50fSksdGhpcy5fb25Db2xsZWN0aW9uQ2hhbmdlKCl9cmVtb3ZlQ29udHJvbCh0LGU9e30pe3RoaXMuY29udHJvbHNbdF0mJnRoaXMuY29udHJvbHNbdF0uX3JlZ2lzdGVyT25Db2xsZWN0aW9uQ2hhbmdlKCgpPT57fSksZGVsZXRlIHRoaXMuY29udHJvbHNbdF0sdGhpcy51cGRhdGVWYWx1ZUFuZFZhbGlkaXR5KHtlbWl0RXZlbnQ6ZS5lbWl0RXZlbnR9KSx0aGlzLl9vbkNvbGxlY3Rpb25DaGFuZ2UoKX1zZXRDb250cm9sKHQsZSxpPXt9KXt0aGlzLmNvbnRyb2xzW3RdJiZ0aGlzLmNvbnRyb2xzW3RdLl9yZWdpc3Rlck9uQ29sbGVjdGlvbkNoYW5nZSgoKT0+e30pLGRlbGV0ZSB0aGlzLmNvbnRyb2xzW3RdLGUmJnRoaXMucmVnaXN0ZXJDb250cm9sKHQsZSksdGhpcy51cGRhdGVWYWx1ZUFuZFZhbGlkaXR5KHtlbWl0RXZlbnQ6aS5lbWl0RXZlbnR9KSx0aGlzLl9vbkNvbGxlY3Rpb25DaGFuZ2UoKX1jb250YWlucyh0KXtyZXR1cm4gdGhpcy5jb250cm9scy5oYXNPd25Qcm9wZXJ0eSh0KSYmdGhpcy5jb250cm9sc1t0XS5lbmFibGVkfXNldFZhbHVlKHQsZT17fSl7KGZ1bmN0aW9uKG4sdCxlKXtuLl9mb3JFYWNoQ2hpbGQoKGkscik9PntpZih2b2lkIDA9PT1lW3JdKXRocm93IG5ldyBBdCgxMDAyLCIiKX0pfSkodGhpcywwLHQpLE9iamVjdC5rZXlzKHQpLmZvckVhY2goaT0+eyhmdW5jdGlvbihuLHQsZSl7bGV0IGk9bi5jb250cm9scztpZighKHQ/T2JqZWN0LmtleXMoaSk6aSkubGVuZ3RoKXRocm93IG5ldyBBdCgxZTMsIiIpO2lmKCFpW2VdKXRocm93IG5ldyBBdCgxMDAxLCIiKX0pKHRoaXMsITAsaSksdGhpcy5jb250cm9sc1tpXS5zZXRWYWx1ZSh0W2ldLHtvbmx5U2VsZjohMCxlbWl0RXZlbnQ6ZS5lbWl0RXZlbnR9KX0pLHRoaXMudXBkYXRlVmFsdWVBbmRWYWxpZGl0eShlKX1wYXRjaFZhbHVlKHQsZT17fSl7bnVsbCE9dCYmKE9iamVjdC5rZXlzKHQpLmZvckVhY2goaT0+e2xldCByPXRoaXMuY29udHJvbHNbaV07ciYmci5wYXRjaFZhbHVlKHRbaV0se29ubHlTZWxmOiEwLGVtaXRFdmVudDplLmVtaXRFdmVudH0pfSksdGhpcy51cGRhdGVWYWx1ZUFuZFZhbGlkaXR5KGUpKX1yZXNldCh0PXt9LGU9e30pe3RoaXMuX2ZvckVhY2hDaGlsZCgoaSxyKT0+e2kucmVzZXQodFtyXSx7b25seVNlbGY6ITAsZW1pdEV2ZW50OmUuZW1pdEV2ZW50fSl9KSx0aGlzLl91cGRhdGVQcmlzdGluZShlKSx0aGlzLl91cGRhdGVUb3VjaGVkKGUpLHRoaXMudXBkYXRlVmFsdWVBbmRWYWxpZGl0eShlKX1nZXRSYXdWYWx1ZSgpe3JldHVybiB0aGlzLl9yZWR1Y2VDaGlsZHJlbih7fSwodCxlLGkpPT4odFtpXT1lLmdldFJhd1ZhbHVlKCksdCkpfV9zeW5jUGVuZGluZ0NvbnRyb2xzKCl7bGV0IHQ9dGhpcy5fcmVkdWNlQ2hpbGRyZW4oITEsKGUsaSk9PiEhaS5fc3luY1BlbmRpbmdDb250cm9scygpfHxlKTtyZXR1cm4gdCYmdGhpcy51cGRhdGVWYWx1ZUFuZFZhbGlkaXR5KHtvbmx5U2VsZjohMH0pLHR9X2ZvckVhY2hDaGlsZCh0KXtPYmplY3Qua2V5cyh0aGlzLmNvbnRyb2xzKS5mb3JFYWNoKGU9PntsZXQgaT10aGlzLmNvbnRyb2xzW2VdO2kmJnQoaSxlKX0pfV9zZXRVcENvbnRyb2xzKCl7dGhpcy5fZm9yRWFjaENoaWxkKHQ9Pnt0LnNldFBhcmVudCh0aGlzKSx0Ll9yZWdpc3Rlck9uQ29sbGVjdGlvbkNoYW5nZSh0aGlzLl9vbkNvbGxlY3Rpb25DaGFuZ2UpfSl9X3VwZGF0ZVZhbHVlKCl7dGhpcy52YWx1ZT10aGlzLl9yZWR1Y2VWYWx1ZSgpfV9hbnlDb250cm9scyh0KXtmb3IobGV0W2UsaV1vZiBPYmplY3QuZW50cmllcyh0aGlzLmNvbnRyb2xzKSlpZih0aGlzLmNvbnRhaW5zKGUpJiZ0KGkpKXJldHVybiEwO3JldHVybiExfV9yZWR1Y2VWYWx1ZSgpe3JldHVybiB0aGlzLl9yZWR1Y2VDaGlsZHJlbih7fSwoZSxpLHIpPT4oKGkuZW5hYmxlZHx8dGhpcy5kaXNhYmxlZCkmJihlW3JdPWkudmFsdWUpLGUpKX1fcmVkdWNlQ2hpbGRyZW4odCxlKXtsZXQgaT10O3JldHVybiB0aGlzLl9mb3JFYWNoQ2hpbGQoKHIsbyk9PntpPWUoaSxyLG8pfSksaX1fYWxsQ29udHJvbHNEaXNhYmxlZCgpe2ZvcihsZXQgdCBvZiBPYmplY3Qua2V5cyh0aGlzLmNvbnRyb2xzKSlpZih0aGlzLmNvbnRyb2xzW3RdLmVuYWJsZWQpcmV0dXJuITE7cmV0dXJuIE9iamVjdC5rZXlzKHRoaXMuY29udHJvbHMpLmxlbmd0aD4wfHx0aGlzLmRpc2FibGVkfV9maW5kKHQpe3JldHVybiB0aGlzLmNvbnRyb2xzLmhhc093blByb3BlcnR5KHQpP3RoaXMuY29udHJvbHNbdF06bnVsbH19O2Z1bmN0aW9uIFUyKG4sdCl7cmV0dXJuWy4uLnQucGF0aCxuXX1mdW5jdGlvbiBmdyhuLHQpe2pIKG4sdCksdC52YWx1ZUFjY2Vzc29yLndyaXRlVmFsdWUobi52YWx1ZSksbi5kaXNhYmxlZCYmdC52YWx1ZUFjY2Vzc29yLnNldERpc2FibGVkU3RhdGU/LighMCksZnVuY3Rpb24obix0KXt0LnZhbHVlQWNjZXNzb3IucmVnaXN0ZXJPbkNoYW5nZShlPT57bi5fcGVuZGluZ1ZhbHVlPWUsbi5fcGVuZGluZ0NoYW5nZT0hMCxuLl9wZW5kaW5nRGlydHk9ITAsImNoYW5nZSI9PT1uLnVwZGF0ZU9uJiZ2bmUobix0KX0pfShuLHQpLGZ1bmN0aW9uKG4sdCl7bGV0IGU9KGkscik9Pnt0LnZhbHVlQWNjZXNzb3Iud3JpdGVWYWx1ZShpKSxyJiZ0LnZpZXdUb01vZGVsVXBkYXRlKGkpfTtuLnJlZ2lzdGVyT25DaGFuZ2UoZSksdC5fcmVnaXN0ZXJPbkRlc3Ryb3koKCk9PntuLl91bnJlZ2lzdGVyT25DaGFuZ2UoZSl9KX0obix0KSxmdW5jdGlvbihuLHQpe3QudmFsdWVBY2Nlc3Nvci5yZWdpc3Rlck9uVG91Y2hlZCgoKT0+e24uX3BlbmRpbmdUb3VjaGVkPSEwLCJibHVyIj09PW4udXBkYXRlT24mJm4uX3BlbmRpbmdDaGFuZ2UmJnZuZShuLHQpLCJzdWJtaXQiIT09bi51cGRhdGVPbiYmbi5tYXJrQXNUb3VjaGVkKCl9KX0obix0KSxmdW5jdGlvbihuLHQpe2lmKHQudmFsdWVBY2Nlc3Nvci5zZXREaXNhYmxlZFN0YXRlKXtsZXQgZT1pPT57dC52YWx1ZUFjY2Vzc29yLnNldERpc2FibGVkU3RhdGUoaSl9O24ucmVnaXN0ZXJPbkRpc2FibGVkQ2hhbmdlKGUpLHQuX3JlZ2lzdGVyT25EZXN0cm95KCgpPT57bi5fdW5yZWdpc3Rlck9uRGlzYWJsZWRDaGFuZ2UoZSl9KX19KG4sdCl9ZnVuY3Rpb24gTjIobix0LGU9ITApe2xldCBpPSgpPT57fTt0LnZhbHVlQWNjZXNzb3ImJih0LnZhbHVlQWNjZXNzb3IucmVnaXN0ZXJPbkNoYW5nZShpKSx0LnZhbHVlQWNjZXNzb3IucmVnaXN0ZXJPblRvdWNoZWQoaSkpLEIyKG4sdCksbiYmKHQuX2ludm9rZU9uRGVzdHJveUNhbGxiYWNrcygpLG4uX3JlZ2lzdGVyT25Db2xsZWN0aW9uQ2hhbmdlKCgpPT57fSkpfWZ1bmN0aW9uIEwyKG4sdCl7bi5mb3JFYWNoKGU9PntlLnJlZ2lzdGVyT25WYWxpZGF0b3JDaGFuZ2UmJmUucmVnaXN0ZXJPblZhbGlkYXRvckNoYW5nZSh0KX0pfWZ1bmN0aW9uIGpIKG4sdCl7bGV0IGU9aG5lKG4pO251bGwhPT10LnZhbGlkYXRvcj9uLnNldFZhbGlkYXRvcnMoVXRlKGUsdC52YWxpZGF0b3IpKToiZnVuY3Rpb24iPT10eXBlb2YgZSYmbi5zZXRWYWxpZGF0b3JzKFtlXSk7bGV0IGk9Zm5lKG4pO251bGwhPT10LmFzeW5jVmFsaWRhdG9yP24uc2V0QXN5bmNWYWxpZGF0b3JzKFV0ZShpLHQuYXN5bmNWYWxpZGF0b3IpKToiZnVuY3Rpb24iPT10eXBlb2YgaSYmbi5zZXRBc3luY1ZhbGlkYXRvcnMoW2ldKTtsZXQgcj0oKT0+bi51cGRhdGVWYWx1ZUFuZFZhbGlkaXR5KCk7TDIodC5fcmF3VmFsaWRhdG9ycyxyKSxMMih0Ll9yYXdBc3luY1ZhbGlkYXRvcnMscil9ZnVuY3Rpb24gQjIobix0KXtsZXQgZT0hMTtpZihudWxsIT09bil7aWYobnVsbCE9PXQudmFsaWRhdG9yKXtsZXQgcj1obmUobik7aWYoQXJyYXkuaXNBcnJheShyKSYmci5sZW5ndGg+MCl7bGV0IG89ci5maWx0ZXIocz0+cyE9PXQudmFsaWRhdG9yKTtvLmxlbmd0aCE9PXIubGVuZ3RoJiYoZT0hMCxuLnNldFZhbGlkYXRvcnMobykpfX1pZihudWxsIT09dC5hc3luY1ZhbGlkYXRvcil7bGV0IHI9Zm5lKG4pO2lmKEFycmF5LmlzQXJyYXkocikmJnIubGVuZ3RoPjApe2xldCBvPXIuZmlsdGVyKHM9PnMhPT10LmFzeW5jVmFsaWRhdG9yKTtvLmxlbmd0aCE9PXIubGVuZ3RoJiYoZT0hMCxuLnNldEFzeW5jVmFsaWRhdG9ycyhvKSl9fX1sZXQgaT0oKT0+e307cmV0dXJuIEwyKHQuX3Jhd1ZhbGlkYXRvcnMsaSksTDIodC5fcmF3QXN5bmNWYWxpZGF0b3JzLGkpLGV9ZnVuY3Rpb24gdm5lKG4sdCl7bi5fcGVuZGluZ0RpcnR5JiZuLm1hcmtBc0RpcnR5KCksbi5zZXRWYWx1ZShuLl9wZW5kaW5nVmFsdWUse2VtaXRNb2RlbFRvVmlld0NoYW5nZTohMX0pLHQudmlld1RvTW9kZWxVcGRhdGUobi5fcGVuZGluZ1ZhbHVlKSxuLl9wZW5kaW5nQ2hhbmdlPSExfWZ1bmN0aW9uIHluZShuLHQpe2pIKG4sdCl9ZnVuY3Rpb24gR0gobix0KXtpZighbi5oYXNPd25Qcm9wZXJ0eSgibW9kZWwiKSlyZXR1cm4hMTtsZXQgZT1uLm1vZGVsO3JldHVybiEhZS5pc0ZpcnN0Q2hhbmdlKCl8fCFPYmplY3QuaXModCxlLmN1cnJlbnRWYWx1ZSl9ZnVuY3Rpb24gYm5lKG4sdCl7bi5fc3luY1BlbmRpbmdDb250cm9scygpLHQuZm9yRWFjaChlPT57bGV0IGk9ZS5jb250cm9sOyJzdWJtaXQiPT09aS51cGRhdGVPbiYmaS5fcGVuZGluZ0NoYW5nZSYmKGUudmlld1RvTW9kZWxVcGRhdGUoaS5fcGVuZGluZ1ZhbHVlKSxpLl9wZW5kaW5nQ2hhbmdlPSExKX0pfWZ1bmN0aW9uIFdIKG4sdCl7aWYoIXQpcmV0dXJuIG51bGw7bGV0IGUsaSxyO3JldHVybiBBcnJheS5pc0FycmF5KHQpLHQuZm9yRWFjaChvPT57by5jb25zdHJ1Y3Rvcj09PUJ2P2U9bzpmdW5jdGlvbihuKXtyZXR1cm4gT2JqZWN0LmdldFByb3RvdHlwZU9mKG4uY29uc3RydWN0b3IpPT09bGd9KG8pP2k9bzpyPW99KSxyfHxpfHxlfHxudWxsfXZhciBRa2U9e3Byb3ZpZGU6RnMsdXNlRXhpc3Rpbmc6Sm4oKCk9PkxoKX0saHc9UHJvbWlzZS5yZXNvbHZlKCksTGg9KCgpPT57Y2xhc3MgbiBleHRlbmRzIEZze2NvbnN0cnVjdG9yKGUsaSl7c3VwZXIoKSx0aGlzLnN1Ym1pdHRlZD0hMSx0aGlzLl9kaXJlY3RpdmVzPW5ldyBTZXQsdGhpcy5uZ1N1Ym1pdD1uZXcgRyx0aGlzLmZvcm09bmV3IEYyKHt9LFVIKGUpLHpIKGkpKX1uZ0FmdGVyVmlld0luaXQoKXt0aGlzLl9zZXRVcGRhdGVTdHJhdGVneSgpfWdldCBmb3JtRGlyZWN0aXZlKCl7cmV0dXJuIHRoaXN9Z2V0IGNvbnRyb2woKXtyZXR1cm4gdGhpcy5mb3JtfWdldCBwYXRoKCl7cmV0dXJuW119Z2V0IGNvbnRyb2xzKCl7cmV0dXJuIHRoaXMuZm9ybS5jb250cm9sc31hZGRDb250cm9sKGUpe2h3LnRoZW4oKCk9PntsZXQgaT10aGlzLl9maW5kQ29udGFpbmVyKGUucGF0aCk7ZS5jb250cm9sPWkucmVnaXN0ZXJDb250cm9sKGUubmFtZSxlLmNvbnRyb2wpLGZ3KGUuY29udHJvbCxlKSxlLmNvbnRyb2wudXBkYXRlVmFsdWVBbmRWYWxpZGl0eSh7ZW1pdEV2ZW50OiExfSksdGhpcy5fZGlyZWN0aXZlcy5hZGQoZSl9KX1nZXRDb250cm9sKGUpe3JldHVybiB0aGlzLmZvcm0uZ2V0KGUucGF0aCl9cmVtb3ZlQ29udHJvbChlKXtody50aGVuKCgpPT57bGV0IGk9dGhpcy5fZmluZENvbnRhaW5lcihlLnBhdGgpO2kmJmkucmVtb3ZlQ29udHJvbChlLm5hbWUpLHRoaXMuX2RpcmVjdGl2ZXMuZGVsZXRlKGUpfSl9YWRkRm9ybUdyb3VwKGUpe2h3LnRoZW4oKCk9PntsZXQgaT10aGlzLl9maW5kQ29udGFpbmVyKGUucGF0aCkscj1uZXcgRjIoe30pO3luZShyLGUpLGkucmVnaXN0ZXJDb250cm9sKGUubmFtZSxyKSxyLnVwZGF0ZVZhbHVlQW5kVmFsaWRpdHkoe2VtaXRFdmVudDohMX0pfSl9cmVtb3ZlRm9ybUdyb3VwKGUpe2h3LnRoZW4oKCk9PntsZXQgaT10aGlzLl9maW5kQ29udGFpbmVyKGUucGF0aCk7aSYmaS5yZW1vdmVDb250cm9sKGUubmFtZSl9KX1nZXRGb3JtR3JvdXAoZSl7cmV0dXJuIHRoaXMuZm9ybS5nZXQoZS5wYXRoKX11cGRhdGVNb2RlbChlLGkpe2h3LnRoZW4oKCk9Pnt0aGlzLmZvcm0uZ2V0KGUucGF0aCkuc2V0VmFsdWUoaSl9KX1zZXRWYWx1ZShlKXt0aGlzLmNvbnRyb2wuc2V0VmFsdWUoZSl9b25TdWJtaXQoZSl7cmV0dXJuIHRoaXMuc3VibWl0dGVkPSEwLGJuZSh0aGlzLmZvcm0sdGhpcy5fZGlyZWN0aXZlcyksdGhpcy5uZ1N1Ym1pdC5lbWl0KGUpLCJkaWFsb2ciPT09ZT8udGFyZ2V0Py5tZXRob2R9b25SZXNldCgpe3RoaXMucmVzZXRGb3JtKCl9cmVzZXRGb3JtKGUpe3RoaXMuZm9ybS5yZXNldChlKSx0aGlzLnN1Ym1pdHRlZD0hMX1fc2V0VXBkYXRlU3RyYXRlZ3koKXt0aGlzLm9wdGlvbnMmJm51bGwhPXRoaXMub3B0aW9ucy51cGRhdGVPbiYmKHRoaXMuZm9ybS5fdXBkYXRlT249dGhpcy5vcHRpb25zLnVwZGF0ZU9uKX1fZmluZENvbnRhaW5lcihlKXtyZXR1cm4gZS5wb3AoKSxlLmxlbmd0aD90aGlzLmZvcm0uZ2V0KGUpOnRoaXMuZm9ybX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShMbywxMCksTShOaCwxMCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyJmb3JtIiwzLCJuZ05vRm9ybSIsIiIsMywiZm9ybUdyb3VwIiwiIl0sWyJuZy1mb3JtIl0sWyIiLCJuZ0Zvcm0iLCIiXV0saG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MSZlJiZQKCJzdWJtaXQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uU3VibWl0KG8pfSkoInJlc2V0IixmdW5jdGlvbigpe3JldHVybiBpLm9uUmVzZXQoKX0pfSxpbnB1dHM6e29wdGlvbnM6WyJuZ0Zvcm1PcHRpb25zIiwib3B0aW9ucyJdfSxvdXRwdXRzOntuZ1N1Ym1pdDoibmdTdWJtaXQifSxleHBvcnRBczpbIm5nRm9ybSJdLGZlYXR1cmVzOlskdChbUWtlXSksdHRdfSksbn0pKCk7ZnVuY3Rpb24gcXRlKG4sdCl7bGV0IGU9bi5pbmRleE9mKHQpO2U+LTEmJm4uc3BsaWNlKGUsMSl9ZnVuY3Rpb24gWXRlKG4pe3JldHVybiJvYmplY3QiPT10eXBlb2YgbiYmbnVsbCE9PW4mJjI9PT1PYmplY3Qua2V5cyhuKS5sZW5ndGgmJiJ2YWx1ZSJpbiBuJiYiZGlzYWJsZWQiaW4gbn12YXIgQmg9Y2xhc3MgZXh0ZW5kcyBrMntjb25zdHJ1Y3Rvcih0PW51bGwsZSxpKXtzdXBlcihnbmUoZSksX25lKGksZSkpLHRoaXMuZGVmYXVsdFZhbHVlPW51bGwsdGhpcy5fb25DaGFuZ2U9W10sdGhpcy5fcGVuZGluZ0NoYW5nZT0hMSx0aGlzLl9hcHBseUZvcm1TdGF0ZSh0KSx0aGlzLl9zZXRVcGRhdGVTdHJhdGVneShlKSx0aGlzLl9pbml0T2JzZXJ2YWJsZXMoKSx0aGlzLnVwZGF0ZVZhbHVlQW5kVmFsaWRpdHkoe29ubHlTZWxmOiEwLGVtaXRFdmVudDohIXRoaXMuYXN5bmNWYWxpZGF0b3J9KSxIMihlKSYmKGUubm9uTnVsbGFibGV8fGUuaW5pdGlhbFZhbHVlSXNEZWZhdWx0KSYmKHRoaXMuZGVmYXVsdFZhbHVlPVl0ZSh0KT90LnZhbHVlOnQpfXNldFZhbHVlKHQsZT17fSl7dGhpcy52YWx1ZT10aGlzLl9wZW5kaW5nVmFsdWU9dCx0aGlzLl9vbkNoYW5nZS5sZW5ndGgmJiExIT09ZS5lbWl0TW9kZWxUb1ZpZXdDaGFuZ2UmJnRoaXMuX29uQ2hhbmdlLmZvckVhY2goaT0+aSh0aGlzLnZhbHVlLCExIT09ZS5lbWl0Vmlld1RvTW9kZWxDaGFuZ2UpKSx0aGlzLnVwZGF0ZVZhbHVlQW5kVmFsaWRpdHkoZSl9cGF0Y2hWYWx1ZSh0LGU9e30pe3RoaXMuc2V0VmFsdWUodCxlKX1yZXNldCh0PXRoaXMuZGVmYXVsdFZhbHVlLGU9e30pe3RoaXMuX2FwcGx5Rm9ybVN0YXRlKHQpLHRoaXMubWFya0FzUHJpc3RpbmUoZSksdGhpcy5tYXJrQXNVbnRvdWNoZWQoZSksdGhpcy5zZXRWYWx1ZSh0aGlzLnZhbHVlLGUpLHRoaXMuX3BlbmRpbmdDaGFuZ2U9ITF9X3VwZGF0ZVZhbHVlKCl7fV9hbnlDb250cm9scyh0KXtyZXR1cm4hMX1fYWxsQ29udHJvbHNEaXNhYmxlZCgpe3JldHVybiB0aGlzLmRpc2FibGVkfXJlZ2lzdGVyT25DaGFuZ2UodCl7dGhpcy5fb25DaGFuZ2UucHVzaCh0KX1fdW5yZWdpc3Rlck9uQ2hhbmdlKHQpe3F0ZSh0aGlzLl9vbkNoYW5nZSx0KX1yZWdpc3Rlck9uRGlzYWJsZWRDaGFuZ2UodCl7dGhpcy5fb25EaXNhYmxlZENoYW5nZS5wdXNoKHQpfV91bnJlZ2lzdGVyT25EaXNhYmxlZENoYW5nZSh0KXtxdGUodGhpcy5fb25EaXNhYmxlZENoYW5nZSx0KX1fZm9yRWFjaENoaWxkKHQpe31fc3luY1BlbmRpbmdDb250cm9scygpe3JldHVybiEoInN1Ym1pdCIhPT10aGlzLnVwZGF0ZU9ufHwodGhpcy5fcGVuZGluZ0RpcnR5JiZ0aGlzLm1hcmtBc0RpcnR5KCksdGhpcy5fcGVuZGluZ1RvdWNoZWQmJnRoaXMubWFya0FzVG91Y2hlZCgpLCF0aGlzLl9wZW5kaW5nQ2hhbmdlKXx8KHRoaXMuc2V0VmFsdWUodGhpcy5fcGVuZGluZ1ZhbHVlLHtvbmx5U2VsZjohMCxlbWl0TW9kZWxUb1ZpZXdDaGFuZ2U6ITF9KSwwKSl9X2FwcGx5Rm9ybVN0YXRlKHQpe1l0ZSh0KT8odGhpcy52YWx1ZT10aGlzLl9wZW5kaW5nVmFsdWU9dC52YWx1ZSx0LmRpc2FibGVkP3RoaXMuZGlzYWJsZSh7b25seVNlbGY6ITAsZW1pdEV2ZW50OiExfSk6dGhpcy5lbmFibGUoe29ubHlTZWxmOiEwLGVtaXRFdmVudDohMX0pKTp0aGlzLnZhbHVlPXRoaXMuX3BlbmRpbmdWYWx1ZT10fX0seG5lPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBGc3tuZ09uSW5pdCgpe3RoaXMuX2NoZWNrUGFyZW50VHlwZSgpLHRoaXMuZm9ybURpcmVjdGl2ZS5hZGRGb3JtR3JvdXAodGhpcyl9bmdPbkRlc3Ryb3koKXt0aGlzLmZvcm1EaXJlY3RpdmUmJnRoaXMuZm9ybURpcmVjdGl2ZS5yZW1vdmVGb3JtR3JvdXAodGhpcyl9Z2V0IGNvbnRyb2woKXtyZXR1cm4gdGhpcy5mb3JtRGlyZWN0aXZlLmdldEZvcm1Hcm91cCh0aGlzKX1nZXQgcGF0aCgpe3JldHVybiBVMihudWxsPT10aGlzLm5hbWU/dGhpcy5uYW1lOnRoaXMubmFtZS50b1N0cmluZygpLHRoaXMuX3BhcmVudCl9Z2V0IGZvcm1EaXJlY3RpdmUoKXtyZXR1cm4gdGhpcy5fcGFyZW50P3RoaXMuX3BhcmVudC5mb3JtRGlyZWN0aXZlOm51bGx9X2NoZWNrUGFyZW50VHlwZSgpe319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKCl7bGV0IHQ7cmV0dXJuIGZ1bmN0aW9uKGkpe3JldHVybih0fHwodD1waShuKSkpKGl8fG4pfX0oKSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLGZlYXR1cmVzOlt0dF19KSxufSkoKSxaa2U9e3Byb3ZpZGU6RnMsdXNlRXhpc3Rpbmc6Sm4oKCk9PkprZSl9LEprZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgeG5le2NvbnN0cnVjdG9yKGUsaSxyKXtzdXBlcigpLHRoaXMuX3BhcmVudD1lLHRoaXMuX3NldFZhbGlkYXRvcnMoaSksdGhpcy5fc2V0QXN5bmNWYWxpZGF0b3JzKHIpfV9jaGVja1BhcmVudFR5cGUoKXt9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oRnMsNSksTShMbywxMCksTShOaCwxMCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJuZ01vZGVsR3JvdXAiLCIiXV0saW5wdXRzOntuYW1lOlsibmdNb2RlbEdyb3VwIiwibmFtZSJdfSxleHBvcnRBczpbIm5nTW9kZWxHcm91cCJdLGZlYXR1cmVzOlskdChbWmtlXSksdHRdfSksbn0pKCksJGtlPXtwcm92aWRlOk5zLHVzZUV4aXN0aW5nOkpuKCgpPT5lRmUpfSxYdGU9UHJvbWlzZS5yZXNvbHZlKCksZUZlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBOc3tjb25zdHJ1Y3RvcihlLGkscixvLHMpe3N1cGVyKCksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWY9cyx0aGlzLmNvbnRyb2w9bmV3IEJoLHRoaXMuX3JlZ2lzdGVyZWQ9ITEsdGhpcy51cGRhdGU9bmV3IEcsdGhpcy5fcGFyZW50PWUsdGhpcy5fc2V0VmFsaWRhdG9ycyhpKSx0aGlzLl9zZXRBc3luY1ZhbGlkYXRvcnMociksdGhpcy52YWx1ZUFjY2Vzc29yPVdIKDAsbyl9bmdPbkNoYW5nZXMoZSl7aWYodGhpcy5fY2hlY2tGb3JFcnJvcnMoKSwhdGhpcy5fcmVnaXN0ZXJlZHx8Im5hbWUiaW4gZSl7aWYodGhpcy5fcmVnaXN0ZXJlZCYmKHRoaXMuX2NoZWNrTmFtZSgpLHRoaXMuZm9ybURpcmVjdGl2ZSkpe2xldCBpPWUubmFtZS5wcmV2aW91c1ZhbHVlO3RoaXMuZm9ybURpcmVjdGl2ZS5yZW1vdmVDb250cm9sKHtuYW1lOmkscGF0aDp0aGlzLl9nZXRQYXRoKGkpfSl9dGhpcy5fc2V0VXBDb250cm9sKCl9ImlzRGlzYWJsZWQiaW4gZSYmdGhpcy5fdXBkYXRlRGlzYWJsZWQoZSksR0goZSx0aGlzLnZpZXdNb2RlbCkmJih0aGlzLl91cGRhdGVWYWx1ZSh0aGlzLm1vZGVsKSx0aGlzLnZpZXdNb2RlbD10aGlzLm1vZGVsKX1uZ09uRGVzdHJveSgpe3RoaXMuZm9ybURpcmVjdGl2ZSYmdGhpcy5mb3JtRGlyZWN0aXZlLnJlbW92ZUNvbnRyb2wodGhpcyl9Z2V0IHBhdGgoKXtyZXR1cm4gdGhpcy5fZ2V0UGF0aCh0aGlzLm5hbWUpfWdldCBmb3JtRGlyZWN0aXZlKCl7cmV0dXJuIHRoaXMuX3BhcmVudD90aGlzLl9wYXJlbnQuZm9ybURpcmVjdGl2ZTpudWxsfXZpZXdUb01vZGVsVXBkYXRlKGUpe3RoaXMudmlld01vZGVsPWUsdGhpcy51cGRhdGUuZW1pdChlKX1fc2V0VXBDb250cm9sKCl7dGhpcy5fc2V0VXBkYXRlU3RyYXRlZ3koKSx0aGlzLl9pc1N0YW5kYWxvbmUoKT90aGlzLl9zZXRVcFN0YW5kYWxvbmUoKTp0aGlzLmZvcm1EaXJlY3RpdmUuYWRkQ29udHJvbCh0aGlzKSx0aGlzLl9yZWdpc3RlcmVkPSEwfV9zZXRVcGRhdGVTdHJhdGVneSgpe3RoaXMub3B0aW9ucyYmbnVsbCE9dGhpcy5vcHRpb25zLnVwZGF0ZU9uJiYodGhpcy5jb250cm9sLl91cGRhdGVPbj10aGlzLm9wdGlvbnMudXBkYXRlT24pfV9pc1N0YW5kYWxvbmUoKXtyZXR1cm4hdGhpcy5fcGFyZW50fHwhKCF0aGlzLm9wdGlvbnN8fCF0aGlzLm9wdGlvbnMuc3RhbmRhbG9uZSl9X3NldFVwU3RhbmRhbG9uZSgpe2Z3KHRoaXMuY29udHJvbCx0aGlzKSx0aGlzLmNvbnRyb2wudXBkYXRlVmFsdWVBbmRWYWxpZGl0eSh7ZW1pdEV2ZW50OiExfSl9X2NoZWNrRm9yRXJyb3JzKCl7dGhpcy5faXNTdGFuZGFsb25lKCl8fHRoaXMuX2NoZWNrUGFyZW50VHlwZSgpLHRoaXMuX2NoZWNrTmFtZSgpfV9jaGVja1BhcmVudFR5cGUoKXt9X2NoZWNrTmFtZSgpe3RoaXMub3B0aW9ucyYmdGhpcy5vcHRpb25zLm5hbWUmJih0aGlzLm5hbWU9dGhpcy5vcHRpb25zLm5hbWUpLHRoaXMuX2lzU3RhbmRhbG9uZSgpfV91cGRhdGVWYWx1ZShlKXtYdGUudGhlbigoKT0+e3RoaXMuY29udHJvbC5zZXRWYWx1ZShlLHtlbWl0Vmlld1RvTW9kZWxDaGFuZ2U6ITF9KSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj8ubWFya0ZvckNoZWNrKCl9KX1fdXBkYXRlRGlzYWJsZWQoZSl7bGV0IGk9ZS5pc0Rpc2FibGVkLmN1cnJlbnRWYWx1ZSxyPTAhPT1pJiZOVChpKTtYdGUudGhlbigoKT0+e3ImJiF0aGlzLmNvbnRyb2wuZGlzYWJsZWQ/dGhpcy5jb250cm9sLmRpc2FibGUoKTohciYmdGhpcy5jb250cm9sLmRpc2FibGVkJiZ0aGlzLmNvbnRyb2wuZW5hYmxlKCksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWY/Lm1hcmtGb3JDaGVjaygpfSl9X2dldFBhdGgoZSl7cmV0dXJuIHRoaXMuX3BhcmVudD9VMihlLHRoaXMuX3BhcmVudCk6W2VdfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKEZzLDkpLE0oTG8sMTApLE0oTmgsMTApLE0oTm8sMTApLE0obm4sOCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJuZ01vZGVsIiwiIiwzLCJmb3JtQ29udHJvbE5hbWUiLCIiLDMsImZvcm1Db250cm9sIiwiIl1dLGlucHV0czp7bmFtZToibmFtZSIsaXNEaXNhYmxlZDpbImRpc2FibGVkIiwiaXNEaXNhYmxlZCJdLG1vZGVsOlsibmdNb2RlbCIsIm1vZGVsIl0sb3B0aW9uczpbIm5nTW9kZWxPcHRpb25zIiwib3B0aW9ucyJdfSxvdXRwdXRzOnt1cGRhdGU6Im5nTW9kZWxDaGFuZ2UifSxleHBvcnRBczpbIm5nTW9kZWwiXSxmZWF0dXJlczpbJHQoWyRrZV0pLHR0LEZ0XX0pLG59KSgpLHRGZT17cHJvdmlkZTpObyx1c2VFeGlzdGluZzpKbigoKT0+cUgpLG11bHRpOiEwfSxxSD0oKCk9PntjbGFzcyBuIGV4dGVuZHMgbGd7d3JpdGVWYWx1ZShlKXt0aGlzLnNldFByb3BlcnR5KCJ2YWx1ZSIsZT8/IiIpfXJlZ2lzdGVyT25DaGFuZ2UoZSl7dGhpcy5vbkNoYW5nZT1pPT57ZSgiIj09aT9udWxsOnBhcnNlRmxvYXQoaSkpfX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKCl7bGV0IHQ7cmV0dXJuIGZ1bmN0aW9uKGkpe3JldHVybih0fHwodD1waShuKSkpKGl8fG4pfX0oKSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyJpbnB1dCIsInR5cGUiLCJudW1iZXIiLCJmb3JtQ29udHJvbE5hbWUiLCIiXSxbImlucHV0IiwidHlwZSIsIm51bWJlciIsImZvcm1Db250cm9sIiwiIl0sWyJpbnB1dCIsInR5cGUiLCJudW1iZXIiLCJuZ01vZGVsIiwiIl1dLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgiaW5wdXQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uQ2hhbmdlKG8udGFyZ2V0LnZhbHVlKX0pKCJibHVyIixmdW5jdGlvbigpe3JldHVybiBpLm9uVG91Y2hlZCgpfSl9LGZlYXR1cmVzOlskdChbdEZlXSksdHRdfSksbn0pKCksbkZlPXtwcm92aWRlOk5vLHVzZUV4aXN0aW5nOkpuKCgpPT5yRmUpLG11bHRpOiEwfSxDbmU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLGlGZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5fYWNjZXNzb3JzPVtdfWFkZChlLGkpe3RoaXMuX2FjY2Vzc29ycy5wdXNoKFtlLGldKX1yZW1vdmUoZSl7Zm9yKGxldCBpPXRoaXMuX2FjY2Vzc29ycy5sZW5ndGgtMTtpPj0wOy0taSlpZih0aGlzLl9hY2Nlc3NvcnNbaV1bMV09PT1lKXJldHVybiB2b2lkIHRoaXMuX2FjY2Vzc29ycy5zcGxpY2UoaSwxKX1zZWxlY3QoZSl7dGhpcy5fYWNjZXNzb3JzLmZvckVhY2goaT0+e3RoaXMuX2lzU2FtZUdyb3VwKGksZSkmJmlbMV0hPT1lJiZpWzFdLmZpcmVVbmNoZWNrKGUudmFsdWUpfSl9X2lzU2FtZUdyb3VwKGUsaSl7cmV0dXJuISFlWzBdLmNvbnRyb2wmJmVbMF0uX3BhcmVudD09PWkuX2NvbnRyb2wuX3BhcmVudCYmZVsxXS5uYW1lPT09aS5uYW1lfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWMscHJvdmlkZWRJbjpDbmV9KSxufSkoKSxyRmU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGxne2NvbnN0cnVjdG9yKGUsaSxyLG8pe3N1cGVyKGUsaSksdGhpcy5fcmVnaXN0cnk9cix0aGlzLl9pbmplY3Rvcj1vLHRoaXMub25DaGFuZ2U9KCk9Pnt9fW5nT25Jbml0KCl7dGhpcy5fY29udHJvbD10aGlzLl9pbmplY3Rvci5nZXQoTnMpLHRoaXMuX2NoZWNrTmFtZSgpLHRoaXMuX3JlZ2lzdHJ5LmFkZCh0aGlzLl9jb250cm9sLHRoaXMpfW5nT25EZXN0cm95KCl7dGhpcy5fcmVnaXN0cnkucmVtb3ZlKHRoaXMpfXdyaXRlVmFsdWUoZSl7dGhpcy5fc3RhdGU9ZT09PXRoaXMudmFsdWUsdGhpcy5zZXRQcm9wZXJ0eSgiY2hlY2tlZCIsdGhpcy5fc3RhdGUpfXJlZ2lzdGVyT25DaGFuZ2UoZSl7dGhpcy5fZm49ZSx0aGlzLm9uQ2hhbmdlPSgpPT57ZSh0aGlzLnZhbHVlKSx0aGlzLl9yZWdpc3RyeS5zZWxlY3QodGhpcyl9fWZpcmVVbmNoZWNrKGUpe3RoaXMud3JpdGVWYWx1ZShlKX1fY2hlY2tOYW1lKCl7IXRoaXMubmFtZSYmdGhpcy5mb3JtQ29udHJvbE5hbWUmJih0aGlzLm5hbWU9dGhpcy5mb3JtQ29udHJvbE5hbWUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKEV1KSxNKFJlKSxNKGlGZSksTShYbikpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyJpbnB1dCIsInR5cGUiLCJyYWRpbyIsImZvcm1Db250cm9sTmFtZSIsIiJdLFsiaW5wdXQiLCJ0eXBlIiwicmFkaW8iLCJmb3JtQ29udHJvbCIsIiJdLFsiaW5wdXQiLCJ0eXBlIiwicmFkaW8iLCJuZ01vZGVsIiwiIl1dLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgiY2hhbmdlIixmdW5jdGlvbigpe3JldHVybiBpLm9uQ2hhbmdlKCl9KSgiYmx1ciIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblRvdWNoZWQoKX0pfSxpbnB1dHM6e25hbWU6Im5hbWUiLGZvcm1Db250cm9sTmFtZToiZm9ybUNvbnRyb2xOYW1lIix2YWx1ZToidmFsdWUifSxmZWF0dXJlczpbJHQoW25GZV0pLHR0XX0pLG59KSgpLG9GZT17cHJvdmlkZTpObyx1c2VFeGlzdGluZzpKbigoKT0+c0ZlKSxtdWx0aTohMH0sc0ZlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBsZ3t3cml0ZVZhbHVlKGUpe3RoaXMuc2V0UHJvcGVydHkoInZhbHVlIixwYXJzZUZsb2F0KGUpKX1yZWdpc3Rlck9uQ2hhbmdlKGUpe3RoaXMub25DaGFuZ2U9aT0+e2UoIiI9PWk/bnVsbDpwYXJzZUZsb2F0KGkpKX19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbigpe2xldCB0O3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4odHx8KHQ9cGkobikpKShpfHxuKX19KCksbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siaW5wdXQiLCJ0eXBlIiwicmFuZ2UiLCJmb3JtQ29udHJvbE5hbWUiLCIiXSxbImlucHV0IiwidHlwZSIsInJhbmdlIiwiZm9ybUNvbnRyb2wiLCIiXSxbImlucHV0IiwidHlwZSIsInJhbmdlIiwibmdNb2RlbCIsIiJdXSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJlAoImNoYW5nZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25DaGFuZ2Uoby50YXJnZXQudmFsdWUpfSkoImlucHV0IixmdW5jdGlvbihvKXtyZXR1cm4gaS5vbkNoYW5nZShvLnRhcmdldC52YWx1ZSl9KSgiYmx1ciIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblRvdWNoZWQoKX0pfSxmZWF0dXJlczpbJHQoW29GZV0pLHR0XX0pLG59KSgpLFlIPW5ldyBwZSgiTmdNb2RlbFdpdGhGb3JtQ29udHJvbFdhcm5pbmciKSxhRmU9e3Byb3ZpZGU6TnMsdXNlRXhpc3Rpbmc6Sm4oKCk9Pm13KX0sbXc9KCgpPT57Y2xhc3MgbiBleHRlbmRzIE5ze2NvbnN0cnVjdG9yKGUsaSxyLG8pe3N1cGVyKCksdGhpcy5fbmdNb2RlbFdhcm5pbmdDb25maWc9byx0aGlzLnVwZGF0ZT1uZXcgRyx0aGlzLl9uZ01vZGVsV2FybmluZ1NlbnQ9ITEsdGhpcy5fc2V0VmFsaWRhdG9ycyhlKSx0aGlzLl9zZXRBc3luY1ZhbGlkYXRvcnMoaSksdGhpcy52YWx1ZUFjY2Vzc29yPVdIKDAscil9c2V0IGlzRGlzYWJsZWQoZSl7fW5nT25DaGFuZ2VzKGUpe2lmKHRoaXMuX2lzQ29udHJvbENoYW5nZWQoZSkpe2xldCBpPWUuZm9ybS5wcmV2aW91c1ZhbHVlO2kmJk4yKGksdGhpcywhMSksZncodGhpcy5mb3JtLHRoaXMpLHRoaXMuZm9ybS51cGRhdGVWYWx1ZUFuZFZhbGlkaXR5KHtlbWl0RXZlbnQ6ITF9KX1HSChlLHRoaXMudmlld01vZGVsKSYmKHRoaXMuZm9ybS5zZXRWYWx1ZSh0aGlzLm1vZGVsKSx0aGlzLnZpZXdNb2RlbD10aGlzLm1vZGVsKX1uZ09uRGVzdHJveSgpe3RoaXMuZm9ybSYmTjIodGhpcy5mb3JtLHRoaXMsITEpfWdldCBwYXRoKCl7cmV0dXJuW119Z2V0IGNvbnRyb2woKXtyZXR1cm4gdGhpcy5mb3JtfXZpZXdUb01vZGVsVXBkYXRlKGUpe3RoaXMudmlld01vZGVsPWUsdGhpcy51cGRhdGUuZW1pdChlKX1faXNDb250cm9sQ2hhbmdlZChlKXtyZXR1cm4gZS5oYXNPd25Qcm9wZXJ0eSgiZm9ybSIpfX1yZXR1cm4gbi5fbmdNb2RlbFdhcm5pbmdTZW50T25jZT0hMSxuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oTG8sMTApLE0oTmgsMTApLE0oTm8sMTApLE0oWUgsOCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJmb3JtQ29udHJvbCIsIiJdXSxpbnB1dHM6e2Zvcm06WyJmb3JtQ29udHJvbCIsImZvcm0iXSxpc0Rpc2FibGVkOlsiZGlzYWJsZWQiLCJpc0Rpc2FibGVkIl0sbW9kZWw6WyJuZ01vZGVsIiwibW9kZWwiXX0sb3V0cHV0czp7dXBkYXRlOiJuZ01vZGVsQ2hhbmdlIn0sZXhwb3J0QXM6WyJuZ0Zvcm0iXSxmZWF0dXJlczpbJHQoW2FGZV0pLHR0LEZ0XX0pLG59KSgpLGxGZT17cHJvdmlkZTpGcyx1c2VFeGlzdGluZzpKbigoKT0+VmgpfSxWaD0oKCk9PntjbGFzcyBuIGV4dGVuZHMgRnN7Y29uc3RydWN0b3IoZSxpKXtzdXBlcigpLHRoaXMuc3VibWl0dGVkPSExLHRoaXMuX29uQ29sbGVjdGlvbkNoYW5nZT0oKT0+dGhpcy5fdXBkYXRlRG9tVmFsdWUoKSx0aGlzLmRpcmVjdGl2ZXM9W10sdGhpcy5mb3JtPW51bGwsdGhpcy5uZ1N1Ym1pdD1uZXcgRyx0aGlzLl9zZXRWYWxpZGF0b3JzKGUpLHRoaXMuX3NldEFzeW5jVmFsaWRhdG9ycyhpKX1uZ09uQ2hhbmdlcyhlKXt0aGlzLl9jaGVja0Zvcm1QcmVzZW50KCksZS5oYXNPd25Qcm9wZXJ0eSgiZm9ybSIpJiYodGhpcy5fdXBkYXRlVmFsaWRhdG9ycygpLHRoaXMuX3VwZGF0ZURvbVZhbHVlKCksdGhpcy5fdXBkYXRlUmVnaXN0cmF0aW9ucygpLHRoaXMuX29sZEZvcm09dGhpcy5mb3JtKX1uZ09uRGVzdHJveSgpe3RoaXMuZm9ybSYmKEIyKHRoaXMuZm9ybSx0aGlzKSx0aGlzLmZvcm0uX29uQ29sbGVjdGlvbkNoYW5nZT09PXRoaXMuX29uQ29sbGVjdGlvbkNoYW5nZSYmdGhpcy5mb3JtLl9yZWdpc3Rlck9uQ29sbGVjdGlvbkNoYW5nZSgoKT0+e30pKX1nZXQgZm9ybURpcmVjdGl2ZSgpe3JldHVybiB0aGlzfWdldCBjb250cm9sKCl7cmV0dXJuIHRoaXMuZm9ybX1nZXQgcGF0aCgpe3JldHVybltdfWFkZENvbnRyb2woZSl7bGV0IGk9dGhpcy5mb3JtLmdldChlLnBhdGgpO3JldHVybiBmdyhpLGUpLGkudXBkYXRlVmFsdWVBbmRWYWxpZGl0eSh7ZW1pdEV2ZW50OiExfSksdGhpcy5kaXJlY3RpdmVzLnB1c2goZSksaX1nZXRDb250cm9sKGUpe3JldHVybiB0aGlzLmZvcm0uZ2V0KGUucGF0aCl9cmVtb3ZlQ29udHJvbChlKXtOMihlLmNvbnRyb2x8fG51bGwsZSwhMSksZnVuY3Rpb24obix0KXtsZXQgZT1uLmluZGV4T2YodCk7ZT4tMSYmbi5zcGxpY2UoZSwxKX0odGhpcy5kaXJlY3RpdmVzLGUpfWFkZEZvcm1Hcm91cChlKXt0aGlzLl9zZXRVcEZvcm1Db250YWluZXIoZSl9cmVtb3ZlRm9ybUdyb3VwKGUpe3RoaXMuX2NsZWFuVXBGb3JtQ29udGFpbmVyKGUpfWdldEZvcm1Hcm91cChlKXtyZXR1cm4gdGhpcy5mb3JtLmdldChlLnBhdGgpfWFkZEZvcm1BcnJheShlKXt0aGlzLl9zZXRVcEZvcm1Db250YWluZXIoZSl9cmVtb3ZlRm9ybUFycmF5KGUpe3RoaXMuX2NsZWFuVXBGb3JtQ29udGFpbmVyKGUpfWdldEZvcm1BcnJheShlKXtyZXR1cm4gdGhpcy5mb3JtLmdldChlLnBhdGgpfXVwZGF0ZU1vZGVsKGUsaSl7dGhpcy5mb3JtLmdldChlLnBhdGgpLnNldFZhbHVlKGkpfW9uU3VibWl0KGUpe3JldHVybiB0aGlzLnN1Ym1pdHRlZD0hMCxibmUodGhpcy5mb3JtLHRoaXMuZGlyZWN0aXZlcyksdGhpcy5uZ1N1Ym1pdC5lbWl0KGUpLCJkaWFsb2ciPT09ZT8udGFyZ2V0Py5tZXRob2R9b25SZXNldCgpe3RoaXMucmVzZXRGb3JtKCl9cmVzZXRGb3JtKGUpe3RoaXMuZm9ybS5yZXNldChlKSx0aGlzLnN1Ym1pdHRlZD0hMX1fdXBkYXRlRG9tVmFsdWUoKXt0aGlzLmRpcmVjdGl2ZXMuZm9yRWFjaChlPT57bGV0IGk9ZS5jb250cm9sLHI9dGhpcy5mb3JtLmdldChlLnBhdGgpO2khPT1yJiYoTjIoaXx8bnVsbCxlKSwobj0+biBpbnN0YW5jZW9mIEJoKShyKSYmKGZ3KHIsZSksZS5jb250cm9sPXIpKX0pLHRoaXMuZm9ybS5fdXBkYXRlVHJlZVZhbGlkaXR5KHtlbWl0RXZlbnQ6ITF9KX1fc2V0VXBGb3JtQ29udGFpbmVyKGUpe2xldCBpPXRoaXMuZm9ybS5nZXQoZS5wYXRoKTt5bmUoaSxlKSxpLnVwZGF0ZVZhbHVlQW5kVmFsaWRpdHkoe2VtaXRFdmVudDohMX0pfV9jbGVhblVwRm9ybUNvbnRhaW5lcihlKXtpZih0aGlzLmZvcm0pe2xldCBpPXRoaXMuZm9ybS5nZXQoZS5wYXRoKTtpJiZmdW5jdGlvbihuLHQpe3JldHVybiBCMihuLHQpfShpLGUpJiZpLnVwZGF0ZVZhbHVlQW5kVmFsaWRpdHkoe2VtaXRFdmVudDohMX0pfX1fdXBkYXRlUmVnaXN0cmF0aW9ucygpe3RoaXMuZm9ybS5fcmVnaXN0ZXJPbkNvbGxlY3Rpb25DaGFuZ2UodGhpcy5fb25Db2xsZWN0aW9uQ2hhbmdlKSx0aGlzLl9vbGRGb3JtJiZ0aGlzLl9vbGRGb3JtLl9yZWdpc3Rlck9uQ29sbGVjdGlvbkNoYW5nZSgoKT0+e30pfV91cGRhdGVWYWxpZGF0b3JzKCl7akgodGhpcy5mb3JtLHRoaXMpLHRoaXMuX29sZEZvcm0mJkIyKHRoaXMuX29sZEZvcm0sdGhpcyl9X2NoZWNrRm9ybVByZXNlbnQoKXt9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oTG8sMTApLE0oTmgsMTApKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwiZm9ybUdyb3VwIiwiIl1dLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgic3VibWl0IixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblN1Ym1pdChvKX0pKCJyZXNldCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblJlc2V0KCl9KX0saW5wdXRzOntmb3JtOlsiZm9ybUdyb3VwIiwiZm9ybSJdfSxvdXRwdXRzOntuZ1N1Ym1pdDoibmdTdWJtaXQifSxleHBvcnRBczpbIm5nRm9ybSJdLGZlYXR1cmVzOlskdChbbEZlXSksdHQsRnRdfSksbn0pKCksY0ZlPXtwcm92aWRlOkZzLHVzZUV4aXN0aW5nOkpuKCgpPT5NbmUpfSxNbmU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIHhuZXtjb25zdHJ1Y3RvcihlLGkscil7c3VwZXIoKSx0aGlzLl9wYXJlbnQ9ZSx0aGlzLl9zZXRWYWxpZGF0b3JzKGkpLHRoaXMuX3NldEFzeW5jVmFsaWRhdG9ycyhyKX1fY2hlY2tQYXJlbnRUeXBlKCl7U25lKHRoaXMuX3BhcmVudCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oRnMsMTMpLE0oTG8sMTApLE0oTmgsMTApKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwiZm9ybUdyb3VwTmFtZSIsIiJdXSxpbnB1dHM6e25hbWU6WyJmb3JtR3JvdXBOYW1lIiwibmFtZSJdfSxmZWF0dXJlczpbJHQoW2NGZV0pLHR0XX0pLG59KSgpLHVGZT17cHJvdmlkZTpGcyx1c2VFeGlzdGluZzpKbigoKT0+d25lKX0sd25lPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBGc3tjb25zdHJ1Y3RvcihlLGkscil7c3VwZXIoKSx0aGlzLl9wYXJlbnQ9ZSx0aGlzLl9zZXRWYWxpZGF0b3JzKGkpLHRoaXMuX3NldEFzeW5jVmFsaWRhdG9ycyhyKX1uZ09uSW5pdCgpe3RoaXMuX2NoZWNrUGFyZW50VHlwZSgpLHRoaXMuZm9ybURpcmVjdGl2ZS5hZGRGb3JtQXJyYXkodGhpcyl9bmdPbkRlc3Ryb3koKXt0aGlzLmZvcm1EaXJlY3RpdmUmJnRoaXMuZm9ybURpcmVjdGl2ZS5yZW1vdmVGb3JtQXJyYXkodGhpcyl9Z2V0IGNvbnRyb2woKXtyZXR1cm4gdGhpcy5mb3JtRGlyZWN0aXZlLmdldEZvcm1BcnJheSh0aGlzKX1nZXQgZm9ybURpcmVjdGl2ZSgpe3JldHVybiB0aGlzLl9wYXJlbnQ/dGhpcy5fcGFyZW50LmZvcm1EaXJlY3RpdmU6bnVsbH1nZXQgcGF0aCgpe3JldHVybiBVMihudWxsPT10aGlzLm5hbWU/dGhpcy5uYW1lOnRoaXMubmFtZS50b1N0cmluZygpLHRoaXMuX3BhcmVudCl9X2NoZWNrUGFyZW50VHlwZSgpe1NuZSh0aGlzLl9wYXJlbnQpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKEZzLDEzKSxNKExvLDEwKSxNKE5oLDEwKSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsImZvcm1BcnJheU5hbWUiLCIiXV0saW5wdXRzOntuYW1lOlsiZm9ybUFycmF5TmFtZSIsIm5hbWUiXX0sZmVhdHVyZXM6WyR0KFt1RmVdKSx0dF19KSxufSkoKTtmdW5jdGlvbiBTbmUobil7cmV0dXJuIShuIGluc3RhbmNlb2YgTW5lfHxuIGluc3RhbmNlb2YgVmh8fG4gaW5zdGFuY2VvZiB3bmUpfXZhciBkRmU9e3Byb3ZpZGU6TnMsdXNlRXhpc3Rpbmc6Sm4oKCk9PnBGZSl9LHBGZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgTnN7Y29uc3RydWN0b3IoZSxpLHIsbyxzKXtzdXBlcigpLHRoaXMuX25nTW9kZWxXYXJuaW5nQ29uZmlnPXMsdGhpcy5fYWRkZWQ9ITEsdGhpcy51cGRhdGU9bmV3IEcsdGhpcy5fbmdNb2RlbFdhcm5pbmdTZW50PSExLHRoaXMuX3BhcmVudD1lLHRoaXMuX3NldFZhbGlkYXRvcnMoaSksdGhpcy5fc2V0QXN5bmNWYWxpZGF0b3JzKHIpLHRoaXMudmFsdWVBY2Nlc3Nvcj1XSCgwLG8pfXNldCBpc0Rpc2FibGVkKGUpe31uZ09uQ2hhbmdlcyhlKXt0aGlzLl9hZGRlZHx8dGhpcy5fc2V0VXBDb250cm9sKCksR0goZSx0aGlzLnZpZXdNb2RlbCkmJih0aGlzLnZpZXdNb2RlbD10aGlzLm1vZGVsLHRoaXMuZm9ybURpcmVjdGl2ZS51cGRhdGVNb2RlbCh0aGlzLHRoaXMubW9kZWwpKX1uZ09uRGVzdHJveSgpe3RoaXMuZm9ybURpcmVjdGl2ZSYmdGhpcy5mb3JtRGlyZWN0aXZlLnJlbW92ZUNvbnRyb2wodGhpcyl9dmlld1RvTW9kZWxVcGRhdGUoZSl7dGhpcy52aWV3TW9kZWw9ZSx0aGlzLnVwZGF0ZS5lbWl0KGUpfWdldCBwYXRoKCl7cmV0dXJuIFUyKG51bGw9PXRoaXMubmFtZT90aGlzLm5hbWU6dGhpcy5uYW1lLnRvU3RyaW5nKCksdGhpcy5fcGFyZW50KX1nZXQgZm9ybURpcmVjdGl2ZSgpe3JldHVybiB0aGlzLl9wYXJlbnQ/dGhpcy5fcGFyZW50LmZvcm1EaXJlY3RpdmU6bnVsbH1fY2hlY2tQYXJlbnRUeXBlKCl7fV9zZXRVcENvbnRyb2woKXt0aGlzLl9jaGVja1BhcmVudFR5cGUoKSx0aGlzLmNvbnRyb2w9dGhpcy5mb3JtRGlyZWN0aXZlLmFkZENvbnRyb2wodGhpcyksdGhpcy5fYWRkZWQ9ITB9fXJldHVybiBuLl9uZ01vZGVsV2FybmluZ1NlbnRPbmNlPSExLG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShGcywxMyksTShMbywxMCksTShOaCwxMCksTShObywxMCksTShZSCw4KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsImZvcm1Db250cm9sTmFtZSIsIiJdXSxpbnB1dHM6e25hbWU6WyJmb3JtQ29udHJvbE5hbWUiLCJuYW1lIl0saXNEaXNhYmxlZDpbImRpc2FibGVkIiwiaXNEaXNhYmxlZCJdLG1vZGVsOlsibmdNb2RlbCIsIm1vZGVsIl19LG91dHB1dHM6e3VwZGF0ZToibmdNb2RlbENoYW5nZSJ9LGZlYXR1cmVzOlskdChbZEZlXSksdHQsRnRdfSksbn0pKCksaEZlPXtwcm92aWRlOk5vLHVzZUV4aXN0aW5nOkpuKCgpPT5UbmUpLG11bHRpOiEwfTtmdW5jdGlvbiBFbmUobix0KXtyZXR1cm4gbnVsbD09bj9gJHt0fWA6KHQmJiJvYmplY3QiPT10eXBlb2YgdCYmKHQ9Ik9iamVjdCIpLGAke259OiAke3R9YC5zbGljZSgwLDUwKSl9dmFyIFRuZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgbGd7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX29wdGlvbk1hcD1uZXcgTWFwLHRoaXMuX2lkQ291bnRlcj0wLHRoaXMuX2NvbXBhcmVXaXRoPU9iamVjdC5pc31zZXQgY29tcGFyZVdpdGgoZSl7dGhpcy5fY29tcGFyZVdpdGg9ZX13cml0ZVZhbHVlKGUpe3RoaXMudmFsdWU9ZTtsZXQgcj1FbmUodGhpcy5fZ2V0T3B0aW9uSWQoZSksZSk7dGhpcy5zZXRQcm9wZXJ0eSgidmFsdWUiLHIpfXJlZ2lzdGVyT25DaGFuZ2UoZSl7dGhpcy5vbkNoYW5nZT1pPT57dGhpcy52YWx1ZT10aGlzLl9nZXRPcHRpb25WYWx1ZShpKSxlKHRoaXMudmFsdWUpfX1fcmVnaXN0ZXJPcHRpb24oKXtyZXR1cm4odGhpcy5faWRDb3VudGVyKyspLnRvU3RyaW5nKCl9X2dldE9wdGlvbklkKGUpe2ZvcihsZXQgaSBvZiBBcnJheS5mcm9tKHRoaXMuX29wdGlvbk1hcC5rZXlzKCkpKWlmKHRoaXMuX2NvbXBhcmVXaXRoKHRoaXMuX29wdGlvbk1hcC5nZXQoaSksZSkpcmV0dXJuIGk7cmV0dXJuIG51bGx9X2dldE9wdGlvblZhbHVlKGUpe2xldCBpPWZ1bmN0aW9uKG4pe3JldHVybiBuLnNwbGl0KCI6IilbMF19KGUpO3JldHVybiB0aGlzLl9vcHRpb25NYXAuaGFzKGkpP3RoaXMuX29wdGlvbk1hcC5nZXQoaSk6ZX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKCl7bGV0IHQ7cmV0dXJuIGZ1bmN0aW9uKGkpe3JldHVybih0fHwodD1waShuKSkpKGl8fG4pfX0oKSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyJzZWxlY3QiLCJmb3JtQ29udHJvbE5hbWUiLCIiLDMsIm11bHRpcGxlIiwiIl0sWyJzZWxlY3QiLCJmb3JtQ29udHJvbCIsIiIsMywibXVsdGlwbGUiLCIiXSxbInNlbGVjdCIsIm5nTW9kZWwiLCIiLDMsIm11bHRpcGxlIiwiIl1dLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgiY2hhbmdlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vbkNoYW5nZShvLnRhcmdldC52YWx1ZSl9KSgiYmx1ciIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblRvdWNoZWQoKX0pfSxpbnB1dHM6e2NvbXBhcmVXaXRoOiJjb21wYXJlV2l0aCJ9LGZlYXR1cmVzOlskdChbaEZlXSksdHRdfSksbn0pKCksRG5lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIpe3RoaXMuX2VsZW1lbnQ9ZSx0aGlzLl9yZW5kZXJlcj1pLHRoaXMuX3NlbGVjdD1yLHRoaXMuX3NlbGVjdCYmKHRoaXMuaWQ9dGhpcy5fc2VsZWN0Ll9yZWdpc3Rlck9wdGlvbigpKX1zZXQgbmdWYWx1ZShlKXtudWxsIT10aGlzLl9zZWxlY3QmJih0aGlzLl9zZWxlY3QuX29wdGlvbk1hcC5zZXQodGhpcy5pZCxlKSx0aGlzLl9zZXRFbGVtZW50VmFsdWUoRW5lKHRoaXMuaWQsZSkpLHRoaXMuX3NlbGVjdC53cml0ZVZhbHVlKHRoaXMuX3NlbGVjdC52YWx1ZSkpfXNldCB2YWx1ZShlKXt0aGlzLl9zZXRFbGVtZW50VmFsdWUoZSksdGhpcy5fc2VsZWN0JiZ0aGlzLl9zZWxlY3Qud3JpdGVWYWx1ZSh0aGlzLl9zZWxlY3QudmFsdWUpfV9zZXRFbGVtZW50VmFsdWUoZSl7dGhpcy5fcmVuZGVyZXIuc2V0UHJvcGVydHkodGhpcy5fZWxlbWVudC5uYXRpdmVFbGVtZW50LCJ2YWx1ZSIsZSl9bmdPbkRlc3Ryb3koKXt0aGlzLl9zZWxlY3QmJih0aGlzLl9zZWxlY3QuX29wdGlvbk1hcC5kZWxldGUodGhpcy5pZCksdGhpcy5fc2VsZWN0LndyaXRlVmFsdWUodGhpcy5fc2VsZWN0LnZhbHVlKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0oRXUpLE0oVG5lLDkpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1sib3B0aW9uIl1dLGlucHV0czp7bmdWYWx1ZToibmdWYWx1ZSIsdmFsdWU6InZhbHVlIn19KSxufSkoKSxtRmU9e3Byb3ZpZGU6Tm8sdXNlRXhpc3Rpbmc6Sm4oKCk9PkFuZSksbXVsdGk6ITB9O2Z1bmN0aW9uIFF0ZShuLHQpe3JldHVybiBudWxsPT1uP2Ake3R9YDooInN0cmluZyI9PXR5cGVvZiB0JiYodD1gJyR7dH0nYCksdCYmIm9iamVjdCI9PXR5cGVvZiB0JiYodD0iT2JqZWN0IiksYCR7bn06ICR7dH1gLnNsaWNlKDAsNTApKX12YXIgQW5lPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBsZ3tjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fb3B0aW9uTWFwPW5ldyBNYXAsdGhpcy5faWRDb3VudGVyPTAsdGhpcy5fY29tcGFyZVdpdGg9T2JqZWN0LmlzfXNldCBjb21wYXJlV2l0aChlKXt0aGlzLl9jb21wYXJlV2l0aD1lfXdyaXRlVmFsdWUoZSl7bGV0IGk7aWYodGhpcy52YWx1ZT1lLEFycmF5LmlzQXJyYXkoZSkpe2xldCByPWUubWFwKG89PnRoaXMuX2dldE9wdGlvbklkKG8pKTtpPShvLHMpPT57by5fc2V0U2VsZWN0ZWQoci5pbmRleE9mKHMudG9TdHJpbmcoKSk+LTEpfX1lbHNlIGk9KHIsbyk9PntyLl9zZXRTZWxlY3RlZCghMSl9O3RoaXMuX29wdGlvbk1hcC5mb3JFYWNoKGkpfXJlZ2lzdGVyT25DaGFuZ2UoZSl7dGhpcy5vbkNoYW5nZT1pPT57bGV0IHI9W10sbz1pLnNlbGVjdGVkT3B0aW9ucztpZih2b2lkIDAhPT1vKXtsZXQgcz1vO2ZvcihsZXQgYT0wO2E8cy5sZW5ndGg7YSsrKXtsZXQgYz10aGlzLl9nZXRPcHRpb25WYWx1ZShzW2FdLnZhbHVlKTtyLnB1c2goYyl9fWVsc2V7bGV0IHM9aS5vcHRpb25zO2ZvcihsZXQgYT0wO2E8cy5sZW5ndGg7YSsrKXtsZXQgbD1zW2FdO2lmKGwuc2VsZWN0ZWQpe2xldCBjPXRoaXMuX2dldE9wdGlvblZhbHVlKGwudmFsdWUpO3IucHVzaChjKX19fXRoaXMudmFsdWU9cixlKHIpfX1fcmVnaXN0ZXJPcHRpb24oZSl7bGV0IGk9KHRoaXMuX2lkQ291bnRlcisrKS50b1N0cmluZygpO3JldHVybiB0aGlzLl9vcHRpb25NYXAuc2V0KGksZSksaX1fZ2V0T3B0aW9uSWQoZSl7Zm9yKGxldCBpIG9mIEFycmF5LmZyb20odGhpcy5fb3B0aW9uTWFwLmtleXMoKSkpaWYodGhpcy5fY29tcGFyZVdpdGgodGhpcy5fb3B0aW9uTWFwLmdldChpKS5fdmFsdWUsZSkpcmV0dXJuIGk7cmV0dXJuIG51bGx9X2dldE9wdGlvblZhbHVlKGUpe2xldCBpPWZ1bmN0aW9uKG4pe3JldHVybiBuLnNwbGl0KCI6IilbMF19KGUpO3JldHVybiB0aGlzLl9vcHRpb25NYXAuaGFzKGkpP3RoaXMuX29wdGlvbk1hcC5nZXQoaSkuX3ZhbHVlOmV9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbigpe2xldCB0O3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4odHx8KHQ9cGkobikpKShpfHxuKX19KCksbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1sic2VsZWN0IiwibXVsdGlwbGUiLCIiLCJmb3JtQ29udHJvbE5hbWUiLCIiXSxbInNlbGVjdCIsIm11bHRpcGxlIiwiIiwiZm9ybUNvbnRyb2wiLCIiXSxbInNlbGVjdCIsIm11bHRpcGxlIiwiIiwibmdNb2RlbCIsIiJdXSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJlAoImNoYW5nZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25DaGFuZ2Uoby50YXJnZXQpfSkoImJsdXIiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25Ub3VjaGVkKCl9KX0saW5wdXRzOntjb21wYXJlV2l0aDoiY29tcGFyZVdpdGgifSxmZWF0dXJlczpbJHQoW21GZV0pLHR0XX0pLG59KSgpLEluZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLl9lbGVtZW50PWUsdGhpcy5fcmVuZGVyZXI9aSx0aGlzLl9zZWxlY3Q9cix0aGlzLl9zZWxlY3QmJih0aGlzLmlkPXRoaXMuX3NlbGVjdC5fcmVnaXN0ZXJPcHRpb24odGhpcykpfXNldCBuZ1ZhbHVlKGUpe251bGwhPXRoaXMuX3NlbGVjdCYmKHRoaXMuX3ZhbHVlPWUsdGhpcy5fc2V0RWxlbWVudFZhbHVlKFF0ZSh0aGlzLmlkLGUpKSx0aGlzLl9zZWxlY3Qud3JpdGVWYWx1ZSh0aGlzLl9zZWxlY3QudmFsdWUpKX1zZXQgdmFsdWUoZSl7dGhpcy5fc2VsZWN0Pyh0aGlzLl92YWx1ZT1lLHRoaXMuX3NldEVsZW1lbnRWYWx1ZShRdGUodGhpcy5pZCxlKSksdGhpcy5fc2VsZWN0LndyaXRlVmFsdWUodGhpcy5fc2VsZWN0LnZhbHVlKSk6dGhpcy5fc2V0RWxlbWVudFZhbHVlKGUpfV9zZXRFbGVtZW50VmFsdWUoZSl7dGhpcy5fcmVuZGVyZXIuc2V0UHJvcGVydHkodGhpcy5fZWxlbWVudC5uYXRpdmVFbGVtZW50LCJ2YWx1ZSIsZSl9X3NldFNlbGVjdGVkKGUpe3RoaXMuX3JlbmRlcmVyLnNldFByb3BlcnR5KHRoaXMuX2VsZW1lbnQubmF0aXZlRWxlbWVudCwic2VsZWN0ZWQiLGUpfW5nT25EZXN0cm95KCl7dGhpcy5fc2VsZWN0JiYodGhpcy5fc2VsZWN0Ll9vcHRpb25NYXAuZGVsZXRlKHRoaXMuaWQpLHRoaXMuX3NlbGVjdC53cml0ZVZhbHVlKHRoaXMuX3NlbGVjdC52YWx1ZSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKEV1KSxNKEFuZSw5KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIm9wdGlvbiJdXSxpbnB1dHM6e25nVmFsdWU6Im5nVmFsdWUiLHZhbHVlOiJ2YWx1ZSJ9fSksbn0pKCk7ZnVuY3Rpb24gUG5lKG4pe3JldHVybiJudW1iZXIiPT10eXBlb2Ygbj9uOnBhcnNlSW50KG4sMTApfWZ1bmN0aW9uIFJuZShuKXtyZXR1cm4ibnVtYmVyIj09dHlwZW9mIG4/bjpwYXJzZUZsb2F0KG4pfXZhciBjZz0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5fdmFsaWRhdG9yPVAyfW5nT25DaGFuZ2VzKGUpe2lmKHRoaXMuaW5wdXROYW1lIGluIGUpe2xldCBpPXRoaXMubm9ybWFsaXplSW5wdXQoZVt0aGlzLmlucHV0TmFtZV0uY3VycmVudFZhbHVlKTt0aGlzLl9lbmFibGVkPXRoaXMuZW5hYmxlZChpKSx0aGlzLl92YWxpZGF0b3I9dGhpcy5fZW5hYmxlZD90aGlzLmNyZWF0ZVZhbGlkYXRvcihpKTpQMix0aGlzLl9vbkNoYW5nZSYmdGhpcy5fb25DaGFuZ2UoKX19dmFsaWRhdGUoZSl7cmV0dXJuIHRoaXMuX3ZhbGlkYXRvcihlKX1yZWdpc3Rlck9uVmFsaWRhdG9yQ2hhbmdlKGUpe3RoaXMuX29uQ2hhbmdlPWV9ZW5hYmxlZChlKXtyZXR1cm4gbnVsbCE9ZX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sZmVhdHVyZXM6W0Z0XX0pLG59KSgpLF9GZT17cHJvdmlkZTpMbyx1c2VFeGlzdGluZzpKbigoKT0+dkZlKSxtdWx0aTohMH0sdkZlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBjZ3tjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5pbnB1dE5hbWU9Im1heCIsdGhpcy5ub3JtYWxpemVJbnB1dD1lPT5SbmUoZSksdGhpcy5jcmVhdGVWYWxpZGF0b3I9ZT0+JHRlKGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbImlucHV0IiwidHlwZSIsIm51bWJlciIsIm1heCIsIiIsImZvcm1Db250cm9sTmFtZSIsIiJdLFsiaW5wdXQiLCJ0eXBlIiwibnVtYmVyIiwibWF4IiwiIiwiZm9ybUNvbnRyb2wiLCIiXSxbImlucHV0IiwidHlwZSIsIm51bWJlciIsIm1heCIsIiIsIm5nTW9kZWwiLCIiXV0saG9zdFZhcnM6MSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJnplKCJtYXgiLGkuX2VuYWJsZWQ/aS5tYXg6bnVsbCl9LGlucHV0czp7bWF4OiJtYXgifSxmZWF0dXJlczpbJHQoW19GZV0pLHR0XX0pLG59KSgpLHlGZT17cHJvdmlkZTpMbyx1c2VFeGlzdGluZzpKbigoKT0+YkZlKSxtdWx0aTohMH0sYkZlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBjZ3tjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5pbnB1dE5hbWU9Im1pbiIsdGhpcy5ub3JtYWxpemVJbnB1dD1lPT5SbmUoZSksdGhpcy5jcmVhdGVWYWxpZGF0b3I9ZT0+SnRlKGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbImlucHV0IiwidHlwZSIsIm51bWJlciIsIm1pbiIsIiIsImZvcm1Db250cm9sTmFtZSIsIiJdLFsiaW5wdXQiLCJ0eXBlIiwibnVtYmVyIiwibWluIiwiIiwiZm9ybUNvbnRyb2wiLCIiXSxbImlucHV0IiwidHlwZSIsIm51bWJlciIsIm1pbiIsIiIsIm5nTW9kZWwiLCIiXV0saG9zdFZhcnM6MSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJnplKCJtaW4iLGkuX2VuYWJsZWQ/aS5taW46bnVsbCl9LGlucHV0czp7bWluOiJtaW4ifSxmZWF0dXJlczpbJHQoW3lGZV0pLHR0XX0pLG59KSgpLHhGZT17cHJvdmlkZTpMbyx1c2VFeGlzdGluZzpKbigoKT0+T25lKSxtdWx0aTohMH0sQ0ZlPXtwcm92aWRlOkxvLHVzZUV4aXN0aW5nOkpuKCgpPT5ndyksbXVsdGk6ITB9LE9uZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgY2d7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuaW5wdXROYW1lPSJyZXF1aXJlZCIsdGhpcy5ub3JtYWxpemVJbnB1dD1OVCx0aGlzLmNyZWF0ZVZhbGlkYXRvcj1lPT5lbmV9ZW5hYmxlZChlKXtyZXR1cm4gZX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKCl7bGV0IHQ7cmV0dXJuIGZ1bmN0aW9uKGkpe3JldHVybih0fHwodD1waShuKSkpKGl8fG4pfX0oKSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJyZXF1aXJlZCIsIiIsImZvcm1Db250cm9sTmFtZSIsIiIsMywidHlwZSIsImNoZWNrYm94Il0sWyIiLCJyZXF1aXJlZCIsIiIsImZvcm1Db250cm9sIiwiIiwzLCJ0eXBlIiwiY2hlY2tib3giXSxbIiIsInJlcXVpcmVkIiwiIiwibmdNb2RlbCIsIiIsMywidHlwZSIsImNoZWNrYm94Il1dLGhvc3RWYXJzOjEsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZ6ZSgicmVxdWlyZWQiLGkuX2VuYWJsZWQ/IiI6bnVsbCl9LGlucHV0czp7cmVxdWlyZWQ6InJlcXVpcmVkIn0sZmVhdHVyZXM6WyR0KFt4RmVdKSx0dF19KSxufSkoKSxndz0oKCk9PntjbGFzcyBuIGV4dGVuZHMgT25le2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLmNyZWF0ZVZhbGlkYXRvcj1lPT50bmV9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbigpe2xldCB0O3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4odHx8KHQ9cGkobikpKShpfHxuKX19KCksbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siaW5wdXQiLCJ0eXBlIiwiY2hlY2tib3giLCJyZXF1aXJlZCIsIiIsImZvcm1Db250cm9sTmFtZSIsIiJdLFsiaW5wdXQiLCJ0eXBlIiwiY2hlY2tib3giLCJyZXF1aXJlZCIsIiIsImZvcm1Db250cm9sIiwiIl0sWyJpbnB1dCIsInR5cGUiLCJjaGVja2JveCIsInJlcXVpcmVkIiwiIiwibmdNb2RlbCIsIiJdXSxob3N0VmFyczoxLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmemUoInJlcXVpcmVkIixpLl9lbmFibGVkPyIiOm51bGwpfSxmZWF0dXJlczpbJHQoW0NGZV0pLHR0XX0pLG59KSgpLE1GZT17cHJvdmlkZTpMbyx1c2VFeGlzdGluZzpKbigoKT0+d0ZlKSxtdWx0aTohMH0sd0ZlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBjZ3tjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5pbnB1dE5hbWU9ImVtYWlsIix0aGlzLm5vcm1hbGl6ZUlucHV0PU5ULHRoaXMuY3JlYXRlVmFsaWRhdG9yPWU9Pm5uZX1lbmFibGVkKGUpe3JldHVybiBlfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsImVtYWlsIiwiIiwiZm9ybUNvbnRyb2xOYW1lIiwiIl0sWyIiLCJlbWFpbCIsIiIsImZvcm1Db250cm9sIiwiIl0sWyIiLCJlbWFpbCIsIiIsIm5nTW9kZWwiLCIiXV0saW5wdXRzOntlbWFpbDoiZW1haWwifSxmZWF0dXJlczpbJHQoW01GZV0pLHR0XX0pLG59KSgpLFNGZT17cHJvdmlkZTpMbyx1c2VFeGlzdGluZzpKbigoKT0+RUZlKSxtdWx0aTohMH0sRUZlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBjZ3tjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5pbnB1dE5hbWU9Im1pbmxlbmd0aCIsdGhpcy5ub3JtYWxpemVJbnB1dD1lPT5QbmUoZSksdGhpcy5jcmVhdGVWYWxpZGF0b3I9ZT0+aW5lKGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1pbmxlbmd0aCIsIiIsImZvcm1Db250cm9sTmFtZSIsIiJdLFsiIiwibWlubGVuZ3RoIiwiIiwiZm9ybUNvbnRyb2wiLCIiXSxbIiIsIm1pbmxlbmd0aCIsIiIsIm5nTW9kZWwiLCIiXV0saG9zdFZhcnM6MSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJnplKCJtaW5sZW5ndGgiLGkuX2VuYWJsZWQ/aS5taW5sZW5ndGg6bnVsbCl9LGlucHV0czp7bWlubGVuZ3RoOiJtaW5sZW5ndGgifSxmZWF0dXJlczpbJHQoW1NGZV0pLHR0XX0pLG59KSgpLFRGZT17cHJvdmlkZTpMbyx1c2VFeGlzdGluZzpKbigoKT0+REZlKSxtdWx0aTohMH0sREZlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBjZ3tjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5pbnB1dE5hbWU9Im1heGxlbmd0aCIsdGhpcy5ub3JtYWxpemVJbnB1dD1lPT5QbmUoZSksdGhpcy5jcmVhdGVWYWxpZGF0b3I9ZT0+cm5lKGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1heGxlbmd0aCIsIiIsImZvcm1Db250cm9sTmFtZSIsIiJdLFsiIiwibWF4bGVuZ3RoIiwiIiwiZm9ybUNvbnRyb2wiLCIiXSxbIiIsIm1heGxlbmd0aCIsIiIsIm5nTW9kZWwiLCIiXV0saG9zdFZhcnM6MSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJnplKCJtYXhsZW5ndGgiLGkuX2VuYWJsZWQ/aS5tYXhsZW5ndGg6bnVsbCl9LGlucHV0czp7bWF4bGVuZ3RoOiJtYXhsZW5ndGgifSxmZWF0dXJlczpbJHQoW1RGZV0pLHR0XX0pLG59KSgpLEFGZT17cHJvdmlkZTpMbyx1c2VFeGlzdGluZzpKbigoKT0+SUZlKSxtdWx0aTohMH0sSUZlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBjZ3tjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5pbnB1dE5hbWU9InBhdHRlcm4iLHRoaXMubm9ybWFsaXplSW5wdXQ9ZT0+ZSx0aGlzLmNyZWF0ZVZhbGlkYXRvcj1lPT5vbmUoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbigpe2xldCB0O3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4odHx8KHQ9cGkobikpKShpfHxuKX19KCksbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwicGF0dGVybiIsIiIsImZvcm1Db250cm9sTmFtZSIsIiJdLFsiIiwicGF0dGVybiIsIiIsImZvcm1Db250cm9sIiwiIl0sWyIiLCJwYXR0ZXJuIiwiIiwibmdNb2RlbCIsIiJdXSxob3N0VmFyczoxLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmemUoInBhdHRlcm4iLGkuX2VuYWJsZWQ/aS5wYXR0ZXJuOm51bGwpfSxpbnB1dHM6e3BhdHRlcm46InBhdHRlcm4ifSxmZWF0dXJlczpbJHQoW0FGZV0pLHR0XX0pLG59KSgpLGtuZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbQ25lXX0pLG59KSgpLGpyPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltrbmVdfSksbn0pKCksejI9KCgpPT57Y2xhc3MgbntzdGF0aWMgd2l0aENvbmZpZyhlKXtyZXR1cm57bmdNb2R1bGU6bixwcm92aWRlcnM6W3twcm92aWRlOllILHVzZVZhbHVlOmUud2Fybk9uTmdNb2RlbFdpdGhGb3JtQ29udHJvbH1dfX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W2tuZV19KSxufSkoKSxQRmU9KG5ldyBJYygiMTQuMi4xMSIpLFsidHJpZ2dlciJdKSxSRmU9WyJwYW5lbCJdO2Z1bmN0aW9uIE9GZShuLHQpe2lmKDEmbiYmKF8oMCwic3BhbiIsOCksQSgxKSx2KCkpLDImbil7bGV0IGU9UygpO0MoMSkseXQoZS5wbGFjZWhvbGRlcil9fWZ1bmN0aW9uIGtGZShuLHQpe2lmKDEmbiYmKF8oMCwic3BhbiIsMTIpLEEoMSksdigpKSwyJm4pe2xldCBlPVMoMik7QygxKSx5dChlLnRyaWdnZXJWYWx1ZSl9fWZ1bmN0aW9uIEZGZShuLHQpezEmbiYmVm4oMCwwLFsiKm5nU3dpdGNoQ2FzZSIsInRydWUiXSl9ZnVuY3Rpb24gTkZlKG4sdCl7MSZuJiYoXygwLCJzcGFuIiw5KSxFKDEsa0ZlLDIsMSwic3BhbiIsMTApLEUoMixGRmUsMSwwLCJuZy1jb250ZW50IiwxMSksdigpKSwyJm4mJih5KCJuZ1N3aXRjaCIsISFTKCkuY3VzdG9tVHJpZ2dlciksQygyKSx5KCJuZ1N3aXRjaENhc2UiLCEwKSl9ZnVuY3Rpb24gTEZlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IiwxMykoMSwiZGl2IiwxNCwxNSksUCgiQHRyYW5zZm9ybVBhbmVsLmRvbmUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkuX3BhbmVsRG9uZUFuaW1hdGluZ1N0cmVhbS5uZXh0KHIudG9TdGF0ZSkpfSkoImtleWRvd24iLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkuX2hhbmRsZUtleWRvd24ocikpfSksVm4oMywxKSx2KCkoKX1pZigyJm4pe2xldCBlPVMoKTt5KCJAdHJhbnNmb3JtUGFuZWxXcmFwIix2b2lkIDApLEMoMSksUXgoIm1hdC1zZWxlY3QtcGFuZWwgIixlLl9nZXRQYW5lbFRoZW1lKCksIiIpLFB0KCJ0cmFuc2Zvcm0tb3JpZ2luIixlLl90cmFuc2Zvcm1PcmlnaW4pKCJmb250LXNpemUiLGUuX3RyaWdnZXJGb250U2l6ZSwicHgiKSx5KCJuZ0NsYXNzIixlLnBhbmVsQ2xhc3MpKCJAdHJhbnNmb3JtUGFuZWwiLGUubXVsdGlwbGU/InNob3dpbmctbXVsdGlwbGUiOiJzaG93aW5nIiksemUoImlkIixlLmlkKyItcGFuZWwiKSgiYXJpYS1tdWx0aXNlbGVjdGFibGUiLGUubXVsdGlwbGUpKCJhcmlhLWxhYmVsIixlLmFyaWFMYWJlbHx8bnVsbCkoImFyaWEtbGFiZWxsZWRieSIsZS5fZ2V0UGFuZWxBcmlhTGFiZWxsZWRieSgpKX19dmFyIEJGZT1bW1sibWF0LXNlbGVjdC10cmlnZ2VyIl1dLCIqIl0sVkZlPVsibWF0LXNlbGVjdC10cmlnZ2VyIiwiKiJdLEZuZT17dHJhbnNmb3JtUGFuZWxXcmFwOktyKCJ0cmFuc2Zvcm1QYW5lbFdyYXAiLFtMaSgiKiA9PiB2b2lkIixJbSgiQHRyYW5zZm9ybVBhbmVsIixbQW0oKV0se29wdGlvbmFsOiEwfSkpXSksdHJhbnNmb3JtUGFuZWw6S3IoInRyYW5zZm9ybVBhbmVsIixba2koInZvaWQiLGduKHt0cmFuc2Zvcm06InNjYWxlWSgwLjgpIixtaW5XaWR0aDoiMTAwJSIsb3BhY2l0eTowfSkpLGtpKCJzaG93aW5nIixnbih7b3BhY2l0eToxLG1pbldpZHRoOiJjYWxjKDEwMCUgKyAzMnB4KSIsdHJhbnNmb3JtOiJzY2FsZVkoMSkifSkpLGtpKCJzaG93aW5nLW11bHRpcGxlIixnbih7b3BhY2l0eToxLG1pbldpZHRoOiJjYWxjKDEwMCUgKyA2NHB4KSIsdHJhbnNmb3JtOiJzY2FsZVkoMSkifSkpLExpKCJ2b2lkID0+ICoiLGppKCIxMjBtcyBjdWJpYy1iZXppZXIoMCwgMCwgMC4yLCAxKSIpKSxMaSgiKiA9PiB2b2lkIixqaSgiMTAwbXMgMjVtcyBsaW5lYXIiLGduKHtvcGFjaXR5OjB9KSkpXSl9LE5uZT0wLEJuZT1uZXcgcGUoIm1hdC1zZWxlY3Qtc2Nyb2xsLXN0cmF0ZWd5IiksakZlPW5ldyBwZSgiTUFUX1NFTEVDVF9DT05GSUciKSxHRmU9e3Byb3ZpZGU6Qm5lLGRlcHM6W3RyXSx1c2VGYWN0b3J5OmZ1bmN0aW9uKG4pe3JldHVybigpPT5uLnNjcm9sbFN0cmF0ZWdpZXMucmVwb3NpdGlvbigpfX0sV0ZlPXFvKG9jKHNvKER2KGNsYXNze2NvbnN0cnVjdG9yKG4sdCxlLGkscil7dGhpcy5fZWxlbWVudFJlZj1uLHRoaXMuX2RlZmF1bHRFcnJvclN0YXRlTWF0Y2hlcj10LHRoaXMuX3BhcmVudEZvcm09ZSx0aGlzLl9wYXJlbnRGb3JtR3JvdXA9aSx0aGlzLm5nQ29udHJvbD1yLHRoaXMuc3RhdGVDaGFuZ2VzPW5ldyBrZX19KSkpKSxxRmU9bmV3IHBlKCJNYXRTZWxlY3RUcmlnZ2VyIiksWUZlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBXRmV7Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtKXtzdXBlcihzLG8sbCxjLGQpLHRoaXMuX3ZpZXdwb3J0UnVsZXI9ZSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj1pLHRoaXMuX25nWm9uZT1yLHRoaXMuX2Rpcj1hLHRoaXMuX3BhcmVudEZvcm1GaWVsZD11LHRoaXMuX2xpdmVBbm5vdW5jZXI9Zix0aGlzLl9kZWZhdWx0T3B0aW9ucz1tLHRoaXMuX3BhbmVsT3Blbj0hMSx0aGlzLl9jb21wYXJlV2l0aD0oeCxnKT0+eD09PWcsdGhpcy5fdWlkPSJtYXQtc2VsZWN0LSIrTm5lKyssdGhpcy5fdHJpZ2dlckFyaWFMYWJlbGxlZEJ5PW51bGwsdGhpcy5fZGVzdHJveT1uZXcga2UsdGhpcy5fb25DaGFuZ2U9KCk9Pnt9LHRoaXMuX29uVG91Y2hlZD0oKT0+e30sdGhpcy5fdmFsdWVJZD0ibWF0LXNlbGVjdC12YWx1ZS0iK05uZSsrLHRoaXMuX3BhbmVsRG9uZUFuaW1hdGluZ1N0cmVhbT1uZXcga2UsdGhpcy5fb3ZlcmxheVBhbmVsQ2xhc3M9dGhpcy5fZGVmYXVsdE9wdGlvbnM/Lm92ZXJsYXlQYW5lbENsYXNzfHwiIix0aGlzLl9mb2N1c2VkPSExLHRoaXMuY29udHJvbFR5cGU9Im1hdC1zZWxlY3QiLHRoaXMuX211bHRpcGxlPSExLHRoaXMuX2Rpc2FibGVPcHRpb25DZW50ZXJpbmc9dGhpcy5fZGVmYXVsdE9wdGlvbnM/LmRpc2FibGVPcHRpb25DZW50ZXJpbmc/PyExLHRoaXMuYXJpYUxhYmVsPSIiLHRoaXMub3B0aW9uU2VsZWN0aW9uQ2hhbmdlcz1RYSgoKT0+e2xldCB4PXRoaXMub3B0aW9ucztyZXR1cm4geD94LmNoYW5nZXMucGlwZSh6bih4KSx1aSgoKT0+SnQoLi4ueC5tYXAoZz0+Zy5vblNlbGVjdGlvbkNoYW5nZSkpKSk6dGhpcy5fbmdab25lLm9uU3RhYmxlLnBpcGUoUXQoMSksdWkoKCk9PnRoaXMub3B0aW9uU2VsZWN0aW9uQ2hhbmdlcykpfSksdGhpcy5vcGVuZWRDaGFuZ2U9bmV3IEcsdGhpcy5fb3BlbmVkU3RyZWFtPXRoaXMub3BlbmVkQ2hhbmdlLnBpcGUoWWUoeD0+eCksTCgoKT0+e30pKSx0aGlzLl9jbG9zZWRTdHJlYW09dGhpcy5vcGVuZWRDaGFuZ2UucGlwZShZZSh4PT4heCksTCgoKT0+e30pKSx0aGlzLnNlbGVjdGlvbkNoYW5nZT1uZXcgRyx0aGlzLnZhbHVlQ2hhbmdlPW5ldyBHLHRoaXMubmdDb250cm9sJiYodGhpcy5uZ0NvbnRyb2wudmFsdWVBY2Nlc3Nvcj10aGlzKSxudWxsIT1tPy50eXBlYWhlYWREZWJvdW5jZUludGVydmFsJiYodGhpcy5fdHlwZWFoZWFkRGVib3VuY2VJbnRlcnZhbD1tLnR5cGVhaGVhZERlYm91bmNlSW50ZXJ2YWwpLHRoaXMuX3Njcm9sbFN0cmF0ZWd5RmFjdG9yeT1oLHRoaXMuX3Njcm9sbFN0cmF0ZWd5PXRoaXMuX3Njcm9sbFN0cmF0ZWd5RmFjdG9yeSgpLHRoaXMudGFiSW5kZXg9cGFyc2VJbnQocCl8fDAsdGhpcy5pZD10aGlzLmlkfWdldCBmb2N1c2VkKCl7cmV0dXJuIHRoaXMuX2ZvY3VzZWR8fHRoaXMuX3BhbmVsT3Blbn1nZXQgcGxhY2Vob2xkZXIoKXtyZXR1cm4gdGhpcy5fcGxhY2Vob2xkZXJ9c2V0IHBsYWNlaG9sZGVyKGUpe3RoaXMuX3BsYWNlaG9sZGVyPWUsdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpfWdldCByZXF1aXJlZCgpe3JldHVybiB0aGlzLl9yZXF1aXJlZD8/dGhpcy5uZ0NvbnRyb2w/LmNvbnRyb2w/Lmhhc1ZhbGlkYXRvcihGby5yZXF1aXJlZCk/PyExfXNldCByZXF1aXJlZChlKXt0aGlzLl9yZXF1aXJlZD1SdChlKSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCl9Z2V0IG11bHRpcGxlKCl7cmV0dXJuIHRoaXMuX211bHRpcGxlfXNldCBtdWx0aXBsZShlKXt0aGlzLl9tdWx0aXBsZT1SdChlKX1nZXQgZGlzYWJsZU9wdGlvbkNlbnRlcmluZygpe3JldHVybiB0aGlzLl9kaXNhYmxlT3B0aW9uQ2VudGVyaW5nfXNldCBkaXNhYmxlT3B0aW9uQ2VudGVyaW5nKGUpe3RoaXMuX2Rpc2FibGVPcHRpb25DZW50ZXJpbmc9UnQoZSl9Z2V0IGNvbXBhcmVXaXRoKCl7cmV0dXJuIHRoaXMuX2NvbXBhcmVXaXRofXNldCBjb21wYXJlV2l0aChlKXt0aGlzLl9jb21wYXJlV2l0aD1lLHRoaXMuX3NlbGVjdGlvbk1vZGVsJiZ0aGlzLl9pbml0aWFsaXplU2VsZWN0aW9uKCl9Z2V0IHZhbHVlKCl7cmV0dXJuIHRoaXMuX3ZhbHVlfXNldCB2YWx1ZShlKXt0aGlzLl9hc3NpZ25WYWx1ZShlKSYmdGhpcy5fb25DaGFuZ2UoZSl9Z2V0IHR5cGVhaGVhZERlYm91bmNlSW50ZXJ2YWwoKXtyZXR1cm4gdGhpcy5fdHlwZWFoZWFkRGVib3VuY2VJbnRlcnZhbH1zZXQgdHlwZWFoZWFkRGVib3VuY2VJbnRlcnZhbChlKXt0aGlzLl90eXBlYWhlYWREZWJvdW5jZUludGVydmFsPUJpKGUpfWdldCBpZCgpe3JldHVybiB0aGlzLl9pZH1zZXQgaWQoZSl7dGhpcy5faWQ9ZXx8dGhpcy5fdWlkLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKX1uZ09uSW5pdCgpe3RoaXMuX3NlbGVjdGlvbk1vZGVsPW5ldyBBaCh0aGlzLm11bHRpcGxlKSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCksdGhpcy5fcGFuZWxEb25lQW5pbWF0aW5nU3RyZWFtLnBpcGUoeWkoKSxzdCh0aGlzLl9kZXN0cm95KSkuc3Vic2NyaWJlKCgpPT50aGlzLl9wYW5lbERvbmVBbmltYXRpbmcodGhpcy5wYW5lbE9wZW4pKX1uZ0FmdGVyQ29udGVudEluaXQoKXt0aGlzLl9pbml0S2V5TWFuYWdlcigpLHRoaXMuX3NlbGVjdGlvbk1vZGVsLmNoYW5nZWQucGlwZShzdCh0aGlzLl9kZXN0cm95KSkuc3Vic2NyaWJlKGU9PntlLmFkZGVkLmZvckVhY2goaT0+aS5zZWxlY3QoKSksZS5yZW1vdmVkLmZvckVhY2goaT0+aS5kZXNlbGVjdCgpKX0pLHRoaXMub3B0aW9ucy5jaGFuZ2VzLnBpcGUoem4obnVsbCksc3QodGhpcy5fZGVzdHJveSkpLnN1YnNjcmliZSgoKT0+e3RoaXMuX3Jlc2V0T3B0aW9ucygpLHRoaXMuX2luaXRpYWxpemVTZWxlY3Rpb24oKX0pfW5nRG9DaGVjaygpe2xldCBlPXRoaXMuX2dldFRyaWdnZXJBcmlhTGFiZWxsZWRieSgpLGk9dGhpcy5uZ0NvbnRyb2w7aWYoZSE9PXRoaXMuX3RyaWdnZXJBcmlhTGFiZWxsZWRCeSl7bGV0IHI9dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50O3RoaXMuX3RyaWdnZXJBcmlhTGFiZWxsZWRCeT1lLGU/ci5zZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWxsZWRieSIsZSk6ci5yZW1vdmVBdHRyaWJ1dGUoImFyaWEtbGFiZWxsZWRieSIpfWkmJih0aGlzLl9wcmV2aW91c0NvbnRyb2whPT1pLmNvbnRyb2wmJih2b2lkIDAhPT10aGlzLl9wcmV2aW91c0NvbnRyb2wmJm51bGwhPT1pLmRpc2FibGVkJiZpLmRpc2FibGVkIT09dGhpcy5kaXNhYmxlZCYmKHRoaXMuZGlzYWJsZWQ9aS5kaXNhYmxlZCksdGhpcy5fcHJldmlvdXNDb250cm9sPWkuY29udHJvbCksdGhpcy51cGRhdGVFcnJvclN0YXRlKCkpfW5nT25DaGFuZ2VzKGUpeyhlLmRpc2FibGVkfHxlLnVzZXJBcmlhRGVzY3JpYmVkQnkpJiZ0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCksZS50eXBlYWhlYWREZWJvdW5jZUludGVydmFsJiZ0aGlzLl9rZXlNYW5hZ2VyJiZ0aGlzLl9rZXlNYW5hZ2VyLndpdGhUeXBlQWhlYWQodGhpcy5fdHlwZWFoZWFkRGVib3VuY2VJbnRlcnZhbCl9bmdPbkRlc3Ryb3koKXt0aGlzLl9kZXN0cm95Lm5leHQoKSx0aGlzLl9kZXN0cm95LmNvbXBsZXRlKCksdGhpcy5zdGF0ZUNoYW5nZXMuY29tcGxldGUoKX10b2dnbGUoKXt0aGlzLnBhbmVsT3Blbj90aGlzLmNsb3NlKCk6dGhpcy5vcGVuKCl9b3Blbigpe3RoaXMuX2Nhbk9wZW4oKSYmKHRoaXMuX3BhbmVsT3Blbj0hMCx0aGlzLl9rZXlNYW5hZ2VyLndpdGhIb3Jpem9udGFsT3JpZW50YXRpb24obnVsbCksdGhpcy5faGlnaGxpZ2h0Q29ycmVjdE9wdGlvbigpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpKX1jbG9zZSgpe3RoaXMuX3BhbmVsT3BlbiYmKHRoaXMuX3BhbmVsT3Blbj0hMSx0aGlzLl9rZXlNYW5hZ2VyLndpdGhIb3Jpem9udGFsT3JpZW50YXRpb24odGhpcy5faXNSdGwoKT8icnRsIjoibHRyIiksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCksdGhpcy5fb25Ub3VjaGVkKCkpfXdyaXRlVmFsdWUoZSl7dGhpcy5fYXNzaWduVmFsdWUoZSl9cmVnaXN0ZXJPbkNoYW5nZShlKXt0aGlzLl9vbkNoYW5nZT1lfXJlZ2lzdGVyT25Ub3VjaGVkKGUpe3RoaXMuX29uVG91Y2hlZD1lfXNldERpc2FibGVkU3RhdGUoZSl7dGhpcy5kaXNhYmxlZD1lLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKX1nZXQgcGFuZWxPcGVuKCl7cmV0dXJuIHRoaXMuX3BhbmVsT3Blbn1nZXQgc2VsZWN0ZWQoKXtyZXR1cm4gdGhpcy5tdWx0aXBsZT90aGlzLl9zZWxlY3Rpb25Nb2RlbD8uc2VsZWN0ZWR8fFtdOnRoaXMuX3NlbGVjdGlvbk1vZGVsPy5zZWxlY3RlZFswXX1nZXQgdHJpZ2dlclZhbHVlKCl7aWYodGhpcy5lbXB0eSlyZXR1cm4iIjtpZih0aGlzLl9tdWx0aXBsZSl7bGV0IGU9dGhpcy5fc2VsZWN0aW9uTW9kZWwuc2VsZWN0ZWQubWFwKGk9Pmkudmlld1ZhbHVlKTtyZXR1cm4gdGhpcy5faXNSdGwoKSYmZS5yZXZlcnNlKCksZS5qb2luKCIsICIpfXJldHVybiB0aGlzLl9zZWxlY3Rpb25Nb2RlbC5zZWxlY3RlZFswXS52aWV3VmFsdWV9X2lzUnRsKCl7cmV0dXJuISF0aGlzLl9kaXImJiJydGwiPT09dGhpcy5fZGlyLnZhbHVlfV9oYW5kbGVLZXlkb3duKGUpe3RoaXMuZGlzYWJsZWR8fCh0aGlzLnBhbmVsT3Blbj90aGlzLl9oYW5kbGVPcGVuS2V5ZG93bihlKTp0aGlzLl9oYW5kbGVDbG9zZWRLZXlkb3duKGUpKX1faGFuZGxlQ2xvc2VkS2V5ZG93bihlKXtsZXQgaT1lLmtleUNvZGUscj00MD09PWl8fDM4PT09aXx8Mzc9PT1pfHwzOT09PWksbz0xMz09PWl8fDMyPT09aSxzPXRoaXMuX2tleU1hbmFnZXI7aWYoIXMuaXNUeXBpbmcoKSYmbyYmIWtyKGUpfHwodGhpcy5tdWx0aXBsZXx8ZS5hbHRLZXkpJiZyKWUucHJldmVudERlZmF1bHQoKSx0aGlzLm9wZW4oKTtlbHNlIGlmKCF0aGlzLm11bHRpcGxlKXtsZXQgYT10aGlzLnNlbGVjdGVkO3Mub25LZXlkb3duKGUpO2xldCBsPXRoaXMuc2VsZWN0ZWQ7bCYmYSE9PWwmJnRoaXMuX2xpdmVBbm5vdW5jZXIuYW5ub3VuY2UobC52aWV3VmFsdWUsMWU0KX19X2hhbmRsZU9wZW5LZXlkb3duKGUpe2xldCBpPXRoaXMuX2tleU1hbmFnZXIscj1lLmtleUNvZGUsbz00MD09PXJ8fDM4PT09cixzPWkuaXNUeXBpbmcoKTtpZihvJiZlLmFsdEtleSllLnByZXZlbnREZWZhdWx0KCksdGhpcy5jbG9zZSgpO2Vsc2UgaWYoc3x8MTMhPT1yJiYzMiE9PXJ8fCFpLmFjdGl2ZUl0ZW18fGtyKGUpKWlmKCFzJiZ0aGlzLl9tdWx0aXBsZSYmNjU9PT1yJiZlLmN0cmxLZXkpe2UucHJldmVudERlZmF1bHQoKTtsZXQgYT10aGlzLm9wdGlvbnMuc29tZShsPT4hbC5kaXNhYmxlZCYmIWwuc2VsZWN0ZWQpO3RoaXMub3B0aW9ucy5mb3JFYWNoKGw9PntsLmRpc2FibGVkfHwoYT9sLnNlbGVjdCgpOmwuZGVzZWxlY3QoKSl9KX1lbHNle2xldCBhPWkuYWN0aXZlSXRlbUluZGV4O2kub25LZXlkb3duKGUpLHRoaXMuX211bHRpcGxlJiZvJiZlLnNoaWZ0S2V5JiZpLmFjdGl2ZUl0ZW0mJmkuYWN0aXZlSXRlbUluZGV4IT09YSYmaS5hY3RpdmVJdGVtLl9zZWxlY3RWaWFJbnRlcmFjdGlvbigpfWVsc2UgZS5wcmV2ZW50RGVmYXVsdCgpLGkuYWN0aXZlSXRlbS5fc2VsZWN0VmlhSW50ZXJhY3Rpb24oKX1fb25Gb2N1cygpe3RoaXMuZGlzYWJsZWR8fCh0aGlzLl9mb2N1c2VkPSEwLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKSl9X29uQmx1cigpe3RoaXMuX2ZvY3VzZWQ9ITEsIXRoaXMuZGlzYWJsZWQmJiF0aGlzLnBhbmVsT3BlbiYmKHRoaXMuX29uVG91Y2hlZCgpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKSl9X29uQXR0YWNoZWQoKXt0aGlzLl9vdmVybGF5RGlyLnBvc2l0aW9uQ2hhbmdlLnBpcGUoUXQoMSkpLnN1YnNjcmliZSgoKT0+e3RoaXMuX2NoYW5nZURldGVjdG9yUmVmLmRldGVjdENoYW5nZXMoKSx0aGlzLl9wb3NpdGlvbmluZ1NldHRsZWQoKX0pfV9nZXRQYW5lbFRoZW1lKCl7cmV0dXJuIHRoaXMuX3BhcmVudEZvcm1GaWVsZD9gbWF0LSR7dGhpcy5fcGFyZW50Rm9ybUZpZWxkLmNvbG9yfWA6IiJ9Z2V0IGVtcHR5KCl7cmV0dXJuIXRoaXMuX3NlbGVjdGlvbk1vZGVsfHx0aGlzLl9zZWxlY3Rpb25Nb2RlbC5pc0VtcHR5KCl9X2luaXRpYWxpemVTZWxlY3Rpb24oKXtQcm9taXNlLnJlc29sdmUoKS50aGVuKCgpPT57dGhpcy5uZ0NvbnRyb2wmJih0aGlzLl92YWx1ZT10aGlzLm5nQ29udHJvbC52YWx1ZSksdGhpcy5fc2V0U2VsZWN0aW9uQnlWYWx1ZSh0aGlzLl92YWx1ZSksdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpfSl9X3NldFNlbGVjdGlvbkJ5VmFsdWUoZSl7aWYodGhpcy5fc2VsZWN0aW9uTW9kZWwuc2VsZWN0ZWQuZm9yRWFjaChpPT5pLnNldEluYWN0aXZlU3R5bGVzKCkpLHRoaXMuX3NlbGVjdGlvbk1vZGVsLmNsZWFyKCksdGhpcy5tdWx0aXBsZSYmZSlBcnJheS5pc0FycmF5KGUpLGUuZm9yRWFjaChpPT50aGlzLl9zZWxlY3RPcHRpb25CeVZhbHVlKGkpKSx0aGlzLl9zb3J0VmFsdWVzKCk7ZWxzZXtsZXQgaT10aGlzLl9zZWxlY3RPcHRpb25CeVZhbHVlKGUpO2k/dGhpcy5fa2V5TWFuYWdlci51cGRhdGVBY3RpdmVJdGVtKGkpOnRoaXMucGFuZWxPcGVufHx0aGlzLl9rZXlNYW5hZ2VyLnVwZGF0ZUFjdGl2ZUl0ZW0oLTEpfXRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpfV9zZWxlY3RPcHRpb25CeVZhbHVlKGUpe2xldCBpPXRoaXMub3B0aW9ucy5maW5kKHI9PntpZih0aGlzLl9zZWxlY3Rpb25Nb2RlbC5pc1NlbGVjdGVkKHIpKXJldHVybiExO3RyeXtyZXR1cm4gbnVsbCE9ci52YWx1ZSYmdGhpcy5fY29tcGFyZVdpdGgoci52YWx1ZSxlKX1jYXRjaHtyZXR1cm4hMX19KTtyZXR1cm4gaSYmdGhpcy5fc2VsZWN0aW9uTW9kZWwuc2VsZWN0KGkpLGl9X2Fzc2lnblZhbHVlKGUpe3JldHVybiEhKGUhPT10aGlzLl92YWx1ZXx8dGhpcy5fbXVsdGlwbGUmJkFycmF5LmlzQXJyYXkoZSkpJiYodGhpcy5vcHRpb25zJiZ0aGlzLl9zZXRTZWxlY3Rpb25CeVZhbHVlKGUpLHRoaXMuX3ZhbHVlPWUsITApfV9pbml0S2V5TWFuYWdlcigpe3RoaXMuX2tleU1hbmFnZXI9bmV3IHd2KHRoaXMub3B0aW9ucykud2l0aFR5cGVBaGVhZCh0aGlzLl90eXBlYWhlYWREZWJvdW5jZUludGVydmFsKS53aXRoVmVydGljYWxPcmllbnRhdGlvbigpLndpdGhIb3Jpem9udGFsT3JpZW50YXRpb24odGhpcy5faXNSdGwoKT8icnRsIjoibHRyIikud2l0aEhvbWVBbmRFbmQoKS53aXRoQWxsb3dlZE1vZGlmaWVyS2V5cyhbInNoaWZ0S2V5Il0pLHRoaXMuX2tleU1hbmFnZXIudGFiT3V0LnBpcGUoc3QodGhpcy5fZGVzdHJveSkpLnN1YnNjcmliZSgoKT0+e3RoaXMucGFuZWxPcGVuJiYoIXRoaXMubXVsdGlwbGUmJnRoaXMuX2tleU1hbmFnZXIuYWN0aXZlSXRlbSYmdGhpcy5fa2V5TWFuYWdlci5hY3RpdmVJdGVtLl9zZWxlY3RWaWFJbnRlcmFjdGlvbigpLHRoaXMuZm9jdXMoKSx0aGlzLmNsb3NlKCkpfSksdGhpcy5fa2V5TWFuYWdlci5jaGFuZ2UucGlwZShzdCh0aGlzLl9kZXN0cm95KSkuc3Vic2NyaWJlKCgpPT57dGhpcy5fcGFuZWxPcGVuJiZ0aGlzLnBhbmVsP3RoaXMuX3Njcm9sbE9wdGlvbkludG9WaWV3KHRoaXMuX2tleU1hbmFnZXIuYWN0aXZlSXRlbUluZGV4fHwwKTohdGhpcy5fcGFuZWxPcGVuJiYhdGhpcy5tdWx0aXBsZSYmdGhpcy5fa2V5TWFuYWdlci5hY3RpdmVJdGVtJiZ0aGlzLl9rZXlNYW5hZ2VyLmFjdGl2ZUl0ZW0uX3NlbGVjdFZpYUludGVyYWN0aW9uKCl9KX1fcmVzZXRPcHRpb25zKCl7bGV0IGU9SnQodGhpcy5vcHRpb25zLmNoYW5nZXMsdGhpcy5fZGVzdHJveSk7dGhpcy5vcHRpb25TZWxlY3Rpb25DaGFuZ2VzLnBpcGUoc3QoZSkpLnN1YnNjcmliZShpPT57dGhpcy5fb25TZWxlY3QoaS5zb3VyY2UsaS5pc1VzZXJJbnB1dCksaS5pc1VzZXJJbnB1dCYmIXRoaXMubXVsdGlwbGUmJnRoaXMuX3BhbmVsT3BlbiYmKHRoaXMuY2xvc2UoKSx0aGlzLmZvY3VzKCkpfSksSnQoLi4udGhpcy5vcHRpb25zLm1hcChpPT5pLl9zdGF0ZUNoYW5nZXMpKS5waXBlKHN0KGUpKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCl9KX1fb25TZWxlY3QoZSxpKXtsZXQgcj10aGlzLl9zZWxlY3Rpb25Nb2RlbC5pc1NlbGVjdGVkKGUpO251bGwhPWUudmFsdWV8fHRoaXMuX211bHRpcGxlPyhyIT09ZS5zZWxlY3RlZCYmKGUuc2VsZWN0ZWQ/dGhpcy5fc2VsZWN0aW9uTW9kZWwuc2VsZWN0KGUpOnRoaXMuX3NlbGVjdGlvbk1vZGVsLmRlc2VsZWN0KGUpKSxpJiZ0aGlzLl9rZXlNYW5hZ2VyLnNldEFjdGl2ZUl0ZW0oZSksdGhpcy5tdWx0aXBsZSYmKHRoaXMuX3NvcnRWYWx1ZXMoKSxpJiZ0aGlzLmZvY3VzKCkpKTooZS5kZXNlbGVjdCgpLHRoaXMuX3NlbGVjdGlvbk1vZGVsLmNsZWFyKCksbnVsbCE9dGhpcy52YWx1ZSYmdGhpcy5fcHJvcGFnYXRlQ2hhbmdlcyhlLnZhbHVlKSksciE9PXRoaXMuX3NlbGVjdGlvbk1vZGVsLmlzU2VsZWN0ZWQoZSkmJnRoaXMuX3Byb3BhZ2F0ZUNoYW5nZXMoKSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCl9X3NvcnRWYWx1ZXMoKXtpZih0aGlzLm11bHRpcGxlKXtsZXQgZT10aGlzLm9wdGlvbnMudG9BcnJheSgpO3RoaXMuX3NlbGVjdGlvbk1vZGVsLnNvcnQoKGkscik9PnRoaXMuc29ydENvbXBhcmF0b3I/dGhpcy5zb3J0Q29tcGFyYXRvcihpLHIsZSk6ZS5pbmRleE9mKGkpLWUuaW5kZXhPZihyKSksdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpfX1fcHJvcGFnYXRlQ2hhbmdlcyhlKXtsZXQgaT1udWxsO2k9dGhpcy5tdWx0aXBsZT90aGlzLnNlbGVjdGVkLm1hcChyPT5yLnZhbHVlKTp0aGlzLnNlbGVjdGVkP3RoaXMuc2VsZWN0ZWQudmFsdWU6ZSx0aGlzLl92YWx1ZT1pLHRoaXMudmFsdWVDaGFuZ2UuZW1pdChpKSx0aGlzLl9vbkNoYW5nZShpKSx0aGlzLnNlbGVjdGlvbkNoYW5nZS5lbWl0KHRoaXMuX2dldENoYW5nZUV2ZW50KGkpKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKX1faGlnaGxpZ2h0Q29ycmVjdE9wdGlvbigpe3RoaXMuX2tleU1hbmFnZXImJih0aGlzLmVtcHR5P3RoaXMuX2tleU1hbmFnZXIuc2V0Rmlyc3RJdGVtQWN0aXZlKCk6dGhpcy5fa2V5TWFuYWdlci5zZXRBY3RpdmVJdGVtKHRoaXMuX3NlbGVjdGlvbk1vZGVsLnNlbGVjdGVkWzBdKSl9X2Nhbk9wZW4oKXtyZXR1cm4hdGhpcy5fcGFuZWxPcGVuJiYhdGhpcy5kaXNhYmxlZCYmdGhpcy5vcHRpb25zPy5sZW5ndGg+MH1mb2N1cyhlKXt0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuZm9jdXMoZSl9X2dldFBhbmVsQXJpYUxhYmVsbGVkYnkoKXtpZih0aGlzLmFyaWFMYWJlbClyZXR1cm4gbnVsbDtsZXQgZT10aGlzLl9wYXJlbnRGb3JtRmllbGQ/LmdldExhYmVsSWQoKTtyZXR1cm4gdGhpcy5hcmlhTGFiZWxsZWRieT8oZT9lKyIgIjoiIikrdGhpcy5hcmlhTGFiZWxsZWRieTplfV9nZXRBcmlhQWN0aXZlRGVzY2VuZGFudCgpe3JldHVybiB0aGlzLnBhbmVsT3BlbiYmdGhpcy5fa2V5TWFuYWdlciYmdGhpcy5fa2V5TWFuYWdlci5hY3RpdmVJdGVtP3RoaXMuX2tleU1hbmFnZXIuYWN0aXZlSXRlbS5pZDpudWxsfV9nZXRUcmlnZ2VyQXJpYUxhYmVsbGVkYnkoKXtpZih0aGlzLmFyaWFMYWJlbClyZXR1cm4gbnVsbDtsZXQgZT10aGlzLl9wYXJlbnRGb3JtRmllbGQ/LmdldExhYmVsSWQoKSxpPShlP2UrIiAiOiIiKSt0aGlzLl92YWx1ZUlkO3JldHVybiB0aGlzLmFyaWFMYWJlbGxlZGJ5JiYoaSs9IiAiK3RoaXMuYXJpYUxhYmVsbGVkYnkpLGl9X3BhbmVsRG9uZUFuaW1hdGluZyhlKXt0aGlzLm9wZW5lZENoYW5nZS5lbWl0KGUpfXNldERlc2NyaWJlZEJ5SWRzKGUpe2UubGVuZ3RoP3RoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5zZXRBdHRyaWJ1dGUoImFyaWEtZGVzY3JpYmVkYnkiLGUuam9pbigiICIpKTp0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQucmVtb3ZlQXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5Iil9b25Db250YWluZXJDbGljaygpe3RoaXMuZm9jdXMoKSx0aGlzLm9wZW4oKX1nZXQgc2hvdWxkTGFiZWxGbG9hdCgpe3JldHVybiB0aGlzLl9wYW5lbE9wZW58fCF0aGlzLmVtcHR5fHx0aGlzLl9mb2N1c2VkJiYhIXRoaXMuX3BsYWNlaG9sZGVyfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFZhKSxNKG5uKSxNKF90KSxNKGNkKSxNKFJlKSxNKCRpLDgpLE0oTGgsOCksTShWaCw4KSxNKHNnLDgpLE0oTnMsMTApLHZvKCJ0YWJpbmRleCIpLE0oQm5lKSxNKHR3KSxNKGpGZSw4KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYob3QoUEZlLDUpLG90KFJGZSw1KSxvdChSaCw1KSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS50cmlnZ2VyPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLnBhbmVsPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLl9vdmVybGF5RGlyPXIuZmlyc3QpfX0saW5wdXRzOnt1c2VyQXJpYURlc2NyaWJlZEJ5OlsiYXJpYS1kZXNjcmliZWRieSIsInVzZXJBcmlhRGVzY3JpYmVkQnkiXSxwYW5lbENsYXNzOiJwYW5lbENsYXNzIixwbGFjZWhvbGRlcjoicGxhY2Vob2xkZXIiLHJlcXVpcmVkOiJyZXF1aXJlZCIsbXVsdGlwbGU6Im11bHRpcGxlIixkaXNhYmxlT3B0aW9uQ2VudGVyaW5nOiJkaXNhYmxlT3B0aW9uQ2VudGVyaW5nIixjb21wYXJlV2l0aDoiY29tcGFyZVdpdGgiLHZhbHVlOiJ2YWx1ZSIsYXJpYUxhYmVsOlsiYXJpYS1sYWJlbCIsImFyaWFMYWJlbCJdLGFyaWFMYWJlbGxlZGJ5OlsiYXJpYS1sYWJlbGxlZGJ5IiwiYXJpYUxhYmVsbGVkYnkiXSxlcnJvclN0YXRlTWF0Y2hlcjoiZXJyb3JTdGF0ZU1hdGNoZXIiLHR5cGVhaGVhZERlYm91bmNlSW50ZXJ2YWw6InR5cGVhaGVhZERlYm91bmNlSW50ZXJ2YWwiLHNvcnRDb21wYXJhdG9yOiJzb3J0Q29tcGFyYXRvciIsaWQ6ImlkIn0sb3V0cHV0czp7b3BlbmVkQ2hhbmdlOiJvcGVuZWRDaGFuZ2UiLF9vcGVuZWRTdHJlYW06Im9wZW5lZCIsX2Nsb3NlZFN0cmVhbToiY2xvc2VkIixzZWxlY3Rpb25DaGFuZ2U6InNlbGVjdGlvbkNoYW5nZSIsdmFsdWVDaGFuZ2U6InZhbHVlQ2hhbmdlIn0sZmVhdHVyZXM6W3R0LEZ0XX0pLG59KSgpLEhoPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBZRmV7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX3Njcm9sbFRvcD0wLHRoaXMuX3RyaWdnZXJGb250U2l6ZT0wLHRoaXMuX3RyYW5zZm9ybU9yaWdpbj0idG9wIix0aGlzLl9vZmZzZXRZPTAsdGhpcy5fcG9zaXRpb25zPVt7b3JpZ2luWDoic3RhcnQiLG9yaWdpblk6InRvcCIsb3ZlcmxheVg6InN0YXJ0IixvdmVybGF5WToidG9wIn0se29yaWdpblg6InN0YXJ0IixvcmlnaW5ZOiJib3R0b20iLG92ZXJsYXlYOiJzdGFydCIsb3ZlcmxheVk6ImJvdHRvbSJ9XX1fY2FsY3VsYXRlT3ZlcmxheVNjcm9sbChlLGkscil7bGV0IG89dGhpcy5fZ2V0SXRlbUhlaWdodCgpO3JldHVybiBNYXRoLm1pbihNYXRoLm1heCgwLG8qZS1pK28vMikscil9bmdPbkluaXQoKXtzdXBlci5uZ09uSW5pdCgpLHRoaXMuX3ZpZXdwb3J0UnVsZXIuY2hhbmdlKCkucGlwZShzdCh0aGlzLl9kZXN0cm95KSkuc3Vic2NyaWJlKCgpPT57dGhpcy5wYW5lbE9wZW4mJih0aGlzLl90cmlnZ2VyUmVjdD10aGlzLnRyaWdnZXIubmF0aXZlRWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSl9KX1vcGVuKCl7c3VwZXIuX2Nhbk9wZW4oKSYmKHN1cGVyLm9wZW4oKSx0aGlzLl90cmlnZ2VyUmVjdD10aGlzLnRyaWdnZXIubmF0aXZlRWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSx0aGlzLl90cmlnZ2VyRm9udFNpemU9cGFyc2VJbnQoZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzLnRyaWdnZXIubmF0aXZlRWxlbWVudCkuZm9udFNpemV8fCIwIiksdGhpcy5fY2FsY3VsYXRlT3ZlcmxheVBvc2l0aW9uKCksdGhpcy5fbmdab25lLm9uU3RhYmxlLnBpcGUoUXQoMSkpLnN1YnNjcmliZSgoKT0+e3RoaXMuX3RyaWdnZXJGb250U2l6ZSYmdGhpcy5fb3ZlcmxheURpci5vdmVybGF5UmVmJiZ0aGlzLl9vdmVybGF5RGlyLm92ZXJsYXlSZWYub3ZlcmxheUVsZW1lbnQmJih0aGlzLl9vdmVybGF5RGlyLm92ZXJsYXlSZWYub3ZlcmxheUVsZW1lbnQuc3R5bGUuZm9udFNpemU9YCR7dGhpcy5fdHJpZ2dlckZvbnRTaXplfXB4YCl9KSl9X3Njcm9sbE9wdGlvbkludG9WaWV3KGUpe2xldCBpPW93KGUsdGhpcy5vcHRpb25zLHRoaXMub3B0aW9uR3JvdXBzKSxyPXRoaXMuX2dldEl0ZW1IZWlnaHQoKTt0aGlzLnBhbmVsLm5hdGl2ZUVsZW1lbnQuc2Nyb2xsVG9wPTA9PT1lJiYxPT09aT8wOl8yKChlK2kpKnIscix0aGlzLnBhbmVsLm5hdGl2ZUVsZW1lbnQuc2Nyb2xsVG9wLDI1Nil9X3Bvc2l0aW9uaW5nU2V0dGxlZCgpe3RoaXMuX2NhbGN1bGF0ZU92ZXJsYXlPZmZzZXRYKCksdGhpcy5wYW5lbC5uYXRpdmVFbGVtZW50LnNjcm9sbFRvcD10aGlzLl9zY3JvbGxUb3B9X3BhbmVsRG9uZUFuaW1hdGluZyhlKXt0aGlzLnBhbmVsT3Blbj90aGlzLl9zY3JvbGxUb3A9MDoodGhpcy5fb3ZlcmxheURpci5vZmZzZXRYPTAsdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCkpLHN1cGVyLl9wYW5lbERvbmVBbmltYXRpbmcoZSl9X2dldENoYW5nZUV2ZW50KGUpe3JldHVybiBuZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLnNvdXJjZT10LHRoaXMudmFsdWU9ZX19KHRoaXMsZSl9X2NhbGN1bGF0ZU92ZXJsYXlPZmZzZXRYKCl7bGV0IHMsZT10aGlzLl9vdmVybGF5RGlyLm92ZXJsYXlSZWYub3ZlcmxheUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksaT10aGlzLl92aWV3cG9ydFJ1bGVyLmdldFZpZXdwb3J0U2l6ZSgpLHI9dGhpcy5faXNSdGwoKSxvPXRoaXMubXVsdGlwbGU/NTY6MzI7aWYodGhpcy5tdWx0aXBsZSlzPTQwO2Vsc2UgaWYodGhpcy5kaXNhYmxlT3B0aW9uQ2VudGVyaW5nKXM9MTY7ZWxzZXtsZXQgYz10aGlzLl9zZWxlY3Rpb25Nb2RlbC5zZWxlY3RlZFswXXx8dGhpcy5vcHRpb25zLmZpcnN0O3M9YyYmYy5ncm91cD8zMjoxNn1yfHwocyo9LTEpO2xldCBhPTAtKGUubGVmdCtzLShyP286MCkpLGw9ZS5yaWdodCtzLWkud2lkdGgrKHI/MDpvKTthPjA/cys9YSs4Omw+MCYmKHMtPWwrOCksdGhpcy5fb3ZlcmxheURpci5vZmZzZXRYPU1hdGgucm91bmQocyksdGhpcy5fb3ZlcmxheURpci5vdmVybGF5UmVmLnVwZGF0ZVBvc2l0aW9uKCl9X2NhbGN1bGF0ZU92ZXJsYXlPZmZzZXRZKGUsaSxyKXtsZXQgbCxvPXRoaXMuX2dldEl0ZW1IZWlnaHQoKSxzPShvLXRoaXMuX3RyaWdnZXJSZWN0LmhlaWdodCkvMixhPU1hdGguZmxvb3IoMjU2L28pO3JldHVybiB0aGlzLmRpc2FibGVPcHRpb25DZW50ZXJpbmc/MDoobD0wPT09dGhpcy5fc2Nyb2xsVG9wP2Uqbzp0aGlzLl9zY3JvbGxUb3A9PT1yPyhlLSh0aGlzLl9nZXRJdGVtQ291bnQoKS1hKSkqbysoby0odGhpcy5fZ2V0SXRlbUNvdW50KCkqby0yNTYpJW8pOmktby8yLE1hdGgucm91bmQoLTEqbC1zKSl9X2NoZWNrT3ZlcmxheVdpdGhpblZpZXdwb3J0KGUpe2xldCBpPXRoaXMuX2dldEl0ZW1IZWlnaHQoKSxyPXRoaXMuX3ZpZXdwb3J0UnVsZXIuZ2V0Vmlld3BvcnRTaXplKCksbz10aGlzLl90cmlnZ2VyUmVjdC50b3AtOCxzPXIuaGVpZ2h0LXRoaXMuX3RyaWdnZXJSZWN0LmJvdHRvbS04LGE9TWF0aC5hYnModGhpcy5fb2Zmc2V0WSksYz1NYXRoLm1pbih0aGlzLl9nZXRJdGVtQ291bnQoKSppLDI1NiktYS10aGlzLl90cmlnZ2VyUmVjdC5oZWlnaHQ7Yz5zP3RoaXMuX2FkanVzdFBhbmVsVXAoYyxzKTphPm8/dGhpcy5fYWRqdXN0UGFuZWxEb3duKGEsbyxlKTp0aGlzLl90cmFuc2Zvcm1PcmlnaW49dGhpcy5fZ2V0T3JpZ2luQmFzZWRPbk9wdGlvbigpfV9hZGp1c3RQYW5lbFVwKGUsaSl7bGV0IHI9TWF0aC5yb3VuZChlLWkpO3RoaXMuX3Njcm9sbFRvcC09cix0aGlzLl9vZmZzZXRZLT1yLHRoaXMuX3RyYW5zZm9ybU9yaWdpbj10aGlzLl9nZXRPcmlnaW5CYXNlZE9uT3B0aW9uKCksdGhpcy5fc2Nyb2xsVG9wPD0wJiYodGhpcy5fc2Nyb2xsVG9wPTAsdGhpcy5fb2Zmc2V0WT0wLHRoaXMuX3RyYW5zZm9ybU9yaWdpbj0iNTAlIGJvdHRvbSAwcHgiKX1fYWRqdXN0UGFuZWxEb3duKGUsaSxyKXtsZXQgbz1NYXRoLnJvdW5kKGUtaSk7aWYodGhpcy5fc2Nyb2xsVG9wKz1vLHRoaXMuX29mZnNldFkrPW8sdGhpcy5fdHJhbnNmb3JtT3JpZ2luPXRoaXMuX2dldE9yaWdpbkJhc2VkT25PcHRpb24oKSx0aGlzLl9zY3JvbGxUb3A+PXIpcmV0dXJuIHRoaXMuX3Njcm9sbFRvcD1yLHRoaXMuX29mZnNldFk9MCx2b2lkKHRoaXMuX3RyYW5zZm9ybU9yaWdpbj0iNTAlIHRvcCAwcHgiKX1fY2FsY3VsYXRlT3ZlcmxheVBvc2l0aW9uKCl7bGV0IGEsZT10aGlzLl9nZXRJdGVtSGVpZ2h0KCksaT10aGlzLl9nZXRJdGVtQ291bnQoKSxyPU1hdGgubWluKGkqZSwyNTYpLHM9aSplLXI7YT10aGlzLmVtcHR5PzA6TWF0aC5tYXgodGhpcy5vcHRpb25zLnRvQXJyYXkoKS5pbmRleE9mKHRoaXMuX3NlbGVjdGlvbk1vZGVsLnNlbGVjdGVkWzBdKSwwKSxhKz1vdyhhLHRoaXMub3B0aW9ucyx0aGlzLm9wdGlvbkdyb3Vwcyk7bGV0IGw9ci8yO3RoaXMuX3Njcm9sbFRvcD10aGlzLl9jYWxjdWxhdGVPdmVybGF5U2Nyb2xsKGEsbCxzKSx0aGlzLl9vZmZzZXRZPXRoaXMuX2NhbGN1bGF0ZU92ZXJsYXlPZmZzZXRZKGEsbCxzKSx0aGlzLl9jaGVja092ZXJsYXlXaXRoaW5WaWV3cG9ydChzKX1fZ2V0T3JpZ2luQmFzZWRPbk9wdGlvbigpe2xldCBlPXRoaXMuX2dldEl0ZW1IZWlnaHQoKSxpPShlLXRoaXMuX3RyaWdnZXJSZWN0LmhlaWdodCkvMjtyZXR1cm5gNTAlICR7TWF0aC5hYnModGhpcy5fb2Zmc2V0WSktaStlLzJ9cHggMHB4YH1fZ2V0SXRlbUhlaWdodCgpe3JldHVybiAzKnRoaXMuX3RyaWdnZXJGb250U2l6ZX1fZ2V0SXRlbUNvdW50KCl7cmV0dXJuIHRoaXMub3B0aW9ucy5sZW5ndGgrdGhpcy5vcHRpb25Hcm91cHMubGVuZ3RofX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LXNlbGVjdCJdXSxjb250ZW50UXVlcmllczpmdW5jdGlvbihlLGkscil7aWYoMSZlJiYoRWkocixxRmUsNSksRWkocixPcyw1KSxFaShyLHJ3LDUpKSwyJmUpe2xldCBvO05lKG89TGUoKSkmJihpLmN1c3RvbVRyaWdnZXI9by5maXJzdCksTmUobz1MZSgpKSYmKGkub3B0aW9ucz1vKSxOZShvPUxlKCkpJiYoaS5vcHRpb25Hcm91cHM9byl9fSxob3N0QXR0cnM6WyJyb2xlIiwiY29tYm9ib3giLCJhcmlhLWF1dG9jb21wbGV0ZSIsIm5vbmUiLCJhcmlhLWhhc3BvcHVwIiwidHJ1ZSIsMSwibWF0LXNlbGVjdCJdLGhvc3RWYXJzOjE5LGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgia2V5ZG93biIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX2hhbmRsZUtleWRvd24obyl9KSgiZm9jdXMiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuX29uRm9jdXMoKX0pKCJibHVyIixmdW5jdGlvbigpe3JldHVybiBpLl9vbkJsdXIoKX0pLDImZSYmKHplKCJpZCIsaS5pZCkoInRhYmluZGV4IixpLnRhYkluZGV4KSgiYXJpYS1jb250cm9scyIsaS5wYW5lbE9wZW4/aS5pZCsiLXBhbmVsIjpudWxsKSgiYXJpYS1leHBhbmRlZCIsaS5wYW5lbE9wZW4pKCJhcmlhLWxhYmVsIixpLmFyaWFMYWJlbHx8bnVsbCkoImFyaWEtcmVxdWlyZWQiLGkucmVxdWlyZWQudG9TdHJpbmcoKSkoImFyaWEtZGlzYWJsZWQiLGkuZGlzYWJsZWQudG9TdHJpbmcoKSkoImFyaWEtaW52YWxpZCIsaS5lcnJvclN0YXRlKSgiYXJpYS1hY3RpdmVkZXNjZW5kYW50IixpLl9nZXRBcmlhQWN0aXZlRGVzY2VuZGFudCgpKSxldCgibWF0LXNlbGVjdC1kaXNhYmxlZCIsaS5kaXNhYmxlZCkoIm1hdC1zZWxlY3QtaW52YWxpZCIsaS5lcnJvclN0YXRlKSgibWF0LXNlbGVjdC1yZXF1aXJlZCIsaS5yZXF1aXJlZCkoIm1hdC1zZWxlY3QtZW1wdHkiLGkuZW1wdHkpKCJtYXQtc2VsZWN0LW11bHRpcGxlIixpLm11bHRpcGxlKSl9LGlucHV0czp7ZGlzYWJsZWQ6ImRpc2FibGVkIixkaXNhYmxlUmlwcGxlOiJkaXNhYmxlUmlwcGxlIix0YWJJbmRleDoidGFiSW5kZXgifSxleHBvcnRBczpbIm1hdFNlbGVjdCJdLGZlYXR1cmVzOlskdChbe3Byb3ZpZGU6a2gsdXNlRXhpc3Rpbmc6bn0se3Byb3ZpZGU6aXcsdXNlRXhpc3Rpbmc6bn1dKSx0dF0sbmdDb250ZW50U2VsZWN0b3JzOlZGZSxkZWNsczo5LHZhcnM6MTIsY29uc3RzOltbImNkay1vdmVybGF5LW9yaWdpbiIsIiIsMSwibWF0LXNlbGVjdC10cmlnZ2VyIiwzLCJjbGljayJdLFsib3JpZ2luIiwiY2RrT3ZlcmxheU9yaWdpbiIsInRyaWdnZXIiLCIiXSxbMSwibWF0LXNlbGVjdC12YWx1ZSIsMywibmdTd2l0Y2giXSxbImNsYXNzIiwibWF0LXNlbGVjdC1wbGFjZWhvbGRlciBtYXQtc2VsZWN0LW1pbi1saW5lIiw0LCJuZ1N3aXRjaENhc2UiXSxbImNsYXNzIiwibWF0LXNlbGVjdC12YWx1ZS10ZXh0IiwzLCJuZ1N3aXRjaCIsNCwibmdTd2l0Y2hDYXNlIl0sWzEsIm1hdC1zZWxlY3QtYXJyb3ctd3JhcHBlciJdLFsxLCJtYXQtc2VsZWN0LWFycm93Il0sWyJjZGstY29ubmVjdGVkLW92ZXJsYXkiLCIiLCJjZGtDb25uZWN0ZWRPdmVybGF5TG9ja1Bvc2l0aW9uIiwiIiwiY2RrQ29ubmVjdGVkT3ZlcmxheUhhc0JhY2tkcm9wIiwiIiwiY2RrQ29ubmVjdGVkT3ZlcmxheUJhY2tkcm9wQ2xhc3MiLCJjZGstb3ZlcmxheS10cmFuc3BhcmVudC1iYWNrZHJvcCIsMywiY2RrQ29ubmVjdGVkT3ZlcmxheVBhbmVsQ2xhc3MiLCJjZGtDb25uZWN0ZWRPdmVybGF5U2Nyb2xsU3RyYXRlZ3kiLCJjZGtDb25uZWN0ZWRPdmVybGF5T3JpZ2luIiwiY2RrQ29ubmVjdGVkT3ZlcmxheU9wZW4iLCJjZGtDb25uZWN0ZWRPdmVybGF5UG9zaXRpb25zIiwiY2RrQ29ubmVjdGVkT3ZlcmxheU1pbldpZHRoIiwiY2RrQ29ubmVjdGVkT3ZlcmxheU9mZnNldFkiLCJiYWNrZHJvcENsaWNrIiwiYXR0YWNoIiwiZGV0YWNoIl0sWzEsIm1hdC1zZWxlY3QtcGxhY2Vob2xkZXIiLCJtYXQtc2VsZWN0LW1pbi1saW5lIl0sWzEsIm1hdC1zZWxlY3QtdmFsdWUtdGV4dCIsMywibmdTd2l0Y2giXSxbImNsYXNzIiwibWF0LXNlbGVjdC1taW4tbGluZSIsNCwibmdTd2l0Y2hEZWZhdWx0Il0sWzQsIm5nU3dpdGNoQ2FzZSJdLFsxLCJtYXQtc2VsZWN0LW1pbi1saW5lIl0sWzEsIm1hdC1zZWxlY3QtcGFuZWwtd3JhcCJdLFsicm9sZSIsImxpc3Rib3giLCJ0YWJpbmRleCIsIi0xIiwzLCJuZ0NsYXNzIiwia2V5ZG93biJdLFsicGFuZWwiLCIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJih4aShCRmUpLF8oMCwiZGl2IiwwLDEpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLnRvZ2dsZSgpfSksXygzLCJkaXYiLDIpLEUoNCxPRmUsMiwxLCJzcGFuIiwzKSxFKDUsTkZlLDMsMiwic3BhbiIsNCksdigpLF8oNiwiZGl2Iiw1KSxPKDcsImRpdiIsNiksdigpKCksRSg4LExGZSw0LDE0LCJuZy10ZW1wbGF0ZSIsNyksUCgiYmFja2Ryb3BDbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5jbG9zZSgpfSkoImF0dGFjaCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5fb25BdHRhY2hlZCgpfSkoImRldGFjaCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5jbG9zZSgpfSkpLDImZSl7bGV0IHI9JGUoMSk7emUoImFyaWEtb3ducyIsaS5wYW5lbE9wZW4/aS5pZCsiLXBhbmVsIjpudWxsKSxDKDMpLHkoIm5nU3dpdGNoIixpLmVtcHR5KSx6ZSgiaWQiLGkuX3ZhbHVlSWQpLEMoMSkseSgibmdTd2l0Y2hDYXNlIiwhMCksQygxKSx5KCJuZ1N3aXRjaENhc2UiLCExKSxDKDMpLHkoImNka0Nvbm5lY3RlZE92ZXJsYXlQYW5lbENsYXNzIixpLl9vdmVybGF5UGFuZWxDbGFzcykoImNka0Nvbm5lY3RlZE92ZXJsYXlTY3JvbGxTdHJhdGVneSIsaS5fc2Nyb2xsU3RyYXRlZ3kpKCJjZGtDb25uZWN0ZWRPdmVybGF5T3JpZ2luIixyKSgiY2RrQ29ubmVjdGVkT3ZlcmxheU9wZW4iLGkucGFuZWxPcGVuKSgiY2RrQ29ubmVjdGVkT3ZlcmxheVBvc2l0aW9ucyIsaS5fcG9zaXRpb25zKSgiY2RrQ29ubmVjdGVkT3ZlcmxheU1pbldpZHRoIixudWxsPT1pLl90cmlnZ2VyUmVjdD9udWxsOmkuX3RyaWdnZXJSZWN0LndpZHRoKSgiY2RrQ29ubmVjdGVkT3ZlcmxheU9mZnNldFkiLGkuX29mZnNldFkpfX0sZGVwZW5kZW5jaWVzOltGbixDcixVcixjaCxSaCxpZ10sc3R5bGVzOlsnLm1hdC1zZWxlY3R7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2lkdGg6MTAwJTtvdXRsaW5lOm5vbmV9Lm1hdC1zZWxlY3QtdHJpZ2dlcntkaXNwbGF5OmlubGluZS1mbGV4O2FsaWduLWl0ZW1zOmNlbnRlcjtjdXJzb3I6cG9pbnRlcjtwb3NpdGlvbjpyZWxhdGl2ZTtib3gtc2l6aW5nOmJvcmRlci1ib3g7d2lkdGg6MTAwJX0ubWF0LXNlbGVjdC1kaXNhYmxlZCAubWF0LXNlbGVjdC10cmlnZ2Vyey13ZWJraXQtdXNlci1zZWxlY3Q6bm9uZTt1c2VyLXNlbGVjdDpub25lO2N1cnNvcjpkZWZhdWx0fS5tYXQtc2VsZWN0LXZhbHVle3dpZHRoOjEwMCU7b3ZlcmZsb3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXM7d2hpdGUtc3BhY2U6bm93cmFwfS5tYXQtc2VsZWN0LXZhbHVlLXRleHR7d2hpdGUtc3BhY2U6bm93cmFwO292ZXJmbG93OmhpZGRlbjt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzfS5tYXQtc2VsZWN0LWFycm93LXdyYXBwZXJ7aGVpZ2h0OjE2cHg7ZmxleC1zaHJpbms6MDtkaXNwbGF5OmlubGluZS1mbGV4O2FsaWduLWl0ZW1zOmNlbnRlcn0ubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1maWxsIC5tYXQtc2VsZWN0LWFycm93LXdyYXBwZXJ7dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTUwJSl9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utb3V0bGluZSAubWF0LXNlbGVjdC1hcnJvdy13cmFwcGVye3RyYW5zZm9ybTp0cmFuc2xhdGVZKC0yNSUpfS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLXN0YW5kYXJkLm1hdC1mb3JtLWZpZWxkLWhhcy1sYWJlbCAubWF0LXNlbGVjdDpub3QoLm1hdC1zZWxlY3QtZW1wdHkpIC5tYXQtc2VsZWN0LWFycm93LXdyYXBwZXJ7dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTUwJSl9Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2Utc3RhbmRhcmQgLm1hdC1zZWxlY3QubWF0LXNlbGVjdC1lbXB0eSAubWF0LXNlbGVjdC1hcnJvdy13cmFwcGVye3RyYW5zaXRpb246dHJhbnNmb3JtIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpfS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZS5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLXN0YW5kYXJkIC5tYXQtc2VsZWN0Lm1hdC1zZWxlY3QtZW1wdHkgLm1hdC1zZWxlY3QtYXJyb3ctd3JhcHBlcnt0cmFuc2l0aW9uOm5vbmV9Lm1hdC1zZWxlY3QtYXJyb3d7d2lkdGg6MDtoZWlnaHQ6MDtib3JkZXItbGVmdDo1cHggc29saWQgcmdiYSgwLDAsMCwwKTtib3JkZXItcmlnaHQ6NXB4IHNvbGlkIHJnYmEoMCwwLDAsMCk7Ym9yZGVyLXRvcDo1cHggc29saWQ7bWFyZ2luOjAgNHB4fS5tYXQtZm9ybS1maWVsZC5tYXQtZm9jdXNlZCAubWF0LXNlbGVjdC1hcnJvd3t0cmFuc2Zvcm06dHJhbnNsYXRlWCgwKX0ubWF0LXNlbGVjdC1wYW5lbC13cmFwe2ZsZXgtYmFzaXM6MTAwJX0ubWF0LXNlbGVjdC1wYW5lbHttaW4td2lkdGg6MTEycHg7bWF4LXdpZHRoOjI4MHB4O292ZXJmbG93OmF1dG87LXdlYmtpdC1vdmVyZmxvdy1zY3JvbGxpbmc6dG91Y2g7cGFkZGluZy10b3A6MDtwYWRkaW5nLWJvdHRvbTowO21heC1oZWlnaHQ6MjU2cHg7bWluLXdpZHRoOjEwMCU7Ym9yZGVyLXJhZGl1czo0cHg7b3V0bGluZTowfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1zZWxlY3QtcGFuZWx7b3V0bGluZTpzb2xpZCAxcHh9Lm1hdC1zZWxlY3QtcGFuZWwgLm1hdC1vcHRncm91cC1sYWJlbCwubWF0LXNlbGVjdC1wYW5lbCAubWF0LW9wdGlvbntmb250LXNpemU6aW5oZXJpdDtsaW5lLWhlaWdodDozZW07aGVpZ2h0OjNlbX0ubWF0LWZvcm0tZmllbGQtdHlwZS1tYXQtc2VsZWN0Om5vdCgubWF0LWZvcm0tZmllbGQtZGlzYWJsZWQpIC5tYXQtZm9ybS1maWVsZC1mbGV4e2N1cnNvcjpwb2ludGVyfS5tYXQtZm9ybS1maWVsZC10eXBlLW1hdC1zZWxlY3QgLm1hdC1mb3JtLWZpZWxkLWxhYmVse3dpZHRoOmNhbGMoMTAwJSAtIDE4cHgpfS5tYXQtc2VsZWN0LXBsYWNlaG9sZGVye3RyYW5zaXRpb246Y29sb3IgNDAwbXMgMTMzLjMzMzMzMzMzMzNtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKX0uX21hdC1hbmltYXRpb24tbm9vcGFibGUgLm1hdC1zZWxlY3QtcGxhY2Vob2xkZXJ7dHJhbnNpdGlvbjpub25lfS5tYXQtZm9ybS1maWVsZC1oaWRlLXBsYWNlaG9sZGVyIC5tYXQtc2VsZWN0LXBsYWNlaG9sZGVye2NvbG9yOnJnYmEoMCwwLDAsMCk7LXdlYmtpdC10ZXh0LWZpbGwtY29sb3I6cmdiYSgwLDAsMCwwKTt0cmFuc2l0aW9uOm5vbmU7ZGlzcGxheTpibG9ja30ubWF0LXNlbGVjdC1taW4tbGluZTplbXB0eTo6YmVmb3Jle2NvbnRlbnQ6IiAiO3doaXRlLXNwYWNlOnByZTt3aWR0aDoxcHg7ZGlzcGxheTppbmxpbmUtYmxvY2s7dmlzaWJpbGl0eTpoaWRkZW59J10sZW5jYXBzdWxhdGlvbjoyLGRhdGE6e2FuaW1hdGlvbjpbRm5lLnRyYW5zZm9ybVBhbmVsV3JhcCxGbmUudHJhbnNmb3JtUGFuZWxdfSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLGxjPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W0dGZV0saW1wb3J0czpbTWUsc3MsQXYsbG4sdWQsYWcsQXYsbG5dfSksbn0pKCk7ZnVuY3Rpb24gWEZlKG4sdCl7MSZuJiYoc24oMCksXygxLCJkaXYiLDEpLEEoMiwiIFRoZXJlIGlzIGEgZGlmZmVyZW5jZSBiZXR3ZWVuIERlZmF1bHQgLSAoRW5hYmxlZC9EaXNhYmxlZCkgYW5kIChFbmFibGVkL0Rpc2FibGVkKSAiKSx2KCksXygzLCJkaXYiLDEpLEEoNCwiIE9ubHkgZmxhZ3Mgd2l0aCBub24gZGVmYXVsdCB2YWx1ZXMgYXJlIHNlbnQgdG8gdGhlIGJhY2tlbmQuICIpLHYoKSxhbigpKX1mdW5jdGlvbiBRRmUobix0KXsxJm4mJihfKDAsInN1cCIsMTEpLEEoMSwiMSIpLHYoKSl9ZnVuY3Rpb24gS0ZlKG4sdCl7MSZuJiZOaSgwKX1mdW5jdGlvbiBaRmUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJtYXQtc2VsZWN0IiwxMiksUCgic2VsZWN0aW9uQ2hhbmdlIixmdW5jdGlvbihyKXtvZShlKTtsZXQgbz1TKCkuJGltcGxpY2l0O3JldHVybiBzZShTKCkuZmxhZ0NoYW5nZWQuZW1pdCh7ZmxhZzpvLmZsYWcsc3RhdHVzOnIudmFsdWV9KSl9KSxfKDEsIm1hdC1vcHRpb24iLDEzKSxBKDIpLHYoKSxfKDMsIm1hdC1vcHRpb24iLDE0KSxBKDQsIkVuYWJsZWQiKSx2KCksXyg1LCJtYXQtb3B0aW9uIiwxNSksQSg2LCJEaXNhYmxlZCIpLHYoKSgpfWlmKDImbil7bGV0IGU9UygpLiRpbXBsaWNpdCxpPVMoKTt5KCJ2YWx1ZSIsZS5zdGF0dXMpLEMoMiksamUoIiBEZWZhdWx0ICIsaS5mb3JtYXRGbGFnVmFsdWUoZS5kZWZhdWx0VmFsdWUpLCIgIil9fWZ1bmN0aW9uIEpGZShuLHQpe2lmKDEmbiYmKF8oMCwidGQiKSxBKDEpLHYoKSksMiZuKXtsZXQgZT1TKCkuJGltcGxpY2l0LGk9UygpO0MoMSksamUoIlVuc3VwcG9ydGVkIEJ5IFVJICIsaS5mb3JtYXRGbGFnVmFsdWUoZS52YWx1ZSksIiIpfX1mdW5jdGlvbiAkRmUobix0KXtpZigxJm4mJihzbigwKSxfKDEsInRyIikoMiwidGQiKSgzLCJkaXYiKSxBKDQpLEUoNSxRRmUsMiwwLCJzdXAiLDcpLHYoKSgpLEUoNixLRmUsMSwwLCJuZy1jb250YWluZXIiLDgpLEUoNyxaRmUsNywyLCJuZy10ZW1wbGF0ZSIsbnVsbCw5LHF0KSxFKDksSkZlLDIsMSwibmctdGVtcGxhdGUiLG51bGwsMTAscXQpLHYoKSxhbigpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0LGk9JGUoOCkscj0kZSgxMCksbz1TKCk7Qyg0KSxqZSgiICIsZS5mbGFnLCIgIiksQygxKSx5KCJuZ0lmIixlLnNlbmRUb1NlcnZlcldoZW5PdmVycmlkZGVuKSxDKDEpLHkoIm5nSWYiLG8uaXNFZGl0YWJsZShlKSkoIm5nSWZUaGVuIixpKSgibmdJZkVsc2UiLHIpfX1mdW5jdGlvbiBlTmUobix0KXsxJm4mJihfKDAsImRpdiIsMTEpLEEoMSwiIDEuIFNlbnQgdG8gc2VydmVyIHdoZW4gb3ZlcnJpZGRlbiAiKSx2KCkpfXZhciBxMixWbmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuaGFzRmxhZ3NTZW50VG9TZXJ2ZXI9ITEsdGhpcy5mbGFnQ2hhbmdlZD1uZXcgRyx0aGlzLmFsbEZsYWdzUmVzZXQ9bmV3IEd9c2VyaWFsaXplRmxhZ1ZhbHVlKGUpe3JldHVybiEwPT09ZT8iRW5hYmxlZCI6ITE9PT1lPyJEaXNhYmxlZCI6bnVsbD09ZT8ibnVsbCI6QXJyYXkuaXNBcnJheShlKT9KU09OLnN0cmluZ2lmeShlKTplLnRvU3RyaW5nKCl9aXNFZGl0YWJsZShlKXtyZXR1cm4iYm9vbGVhbiI9PXR5cGVvZiBlLmRlZmF1bHRWYWx1ZX1mb3JtYXRGbGFnVmFsdWUoZSl7bGV0IGk9dGhpcy5zZXJpYWxpemVGbGFnVmFsdWUoZSk7cmV0dXJuIDA9PT1pLmxlbmd0aD8iIjpgLSAke2l9YH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siZmVhdHVyZS1mbGFnLXBhZ2UtY29tcG9uZW50Il1dLGlucHV0czp7ZmVhdHVyZUZsYWdTdGF0dXNlczoiZmVhdHVyZUZsYWdTdGF0dXNlcyIsaGFzRmxhZ3NTZW50VG9TZXJ2ZXI6Imhhc0ZsYWdzU2VudFRvU2VydmVyIn0sb3V0cHV0czp7ZmxhZ0NoYW5nZWQ6ImZsYWdDaGFuZ2VkIixhbGxGbGFnc1Jlc2V0OiJhbGxGbGFnc1Jlc2V0In0sZGVjbHM6MTEsdmFyczozLGNvbnN0czpbWzEsInNjcm9sbGluZy1wYWdlIl0sWzEsIm1lc3NhZ2UiXSxbMSwid2FybmluZyJdLFs0LCJuZ0lmIl0sWzEsImZlYXR1cmUtZmxhZy10YWJsZSJdLFs0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbIm1hdC1idXR0b24iLCIiLDMsImNsaWNrIl0sWyJjbGFzcyIsIm5vdGUtMSIsNCwibmdJZiJdLFs0LCJuZ0lmIiwibmdJZlRoZW4iLCJuZ0lmRWxzZSJdLFsic2VsZWN0QmxvY2siLCIiXSxbInVuc3VwcG9ydGVkQmxvY2siLCIiXSxbMSwibm90ZS0xIl0sWzMsInZhbHVlIiwic2VsZWN0aW9uQ2hhbmdlIl0sWyJ2YWx1ZSIsImRlZmF1bHQiXSxbInZhbHVlIiwiZW5hYmxlZCJdLFsidmFsdWUiLCJkaXNhYmxlZCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IiwwKSgxLCJkaXYiLDEpKDIsImgyIiwyKSxBKDMsIldBUk5JTkc6IEVYUEVSSU1FTlRBTCBGRUFUVVJFUyBBSEVBRCEiKSx2KCksQSg0LCIgQnkgZW5hYmxpbmcgdGhlc2UgZmVhdHVyZXMsIHlvdSBjb3VsZCBwdXQgdGhlIGFwcGxpY2F0aW9uIGluIGFuIHVudXNhYmxlIHN0YXRlIG9yIGV4cG9zZSB5b3Vyc2VsZiB0byB1bnRlc3RlZCBmZWF0dXJlcyBvciBwb3RlbnRpYWwgYnVncy4gIiksdigpLEUoNSxYRmUsNSwwLCJuZy1jb250YWluZXIiLDMpLF8oNiwidGFibGUiLDQpLEUoNywkRmUsMTEsNSwibmctY29udGFpbmVyIiw1KSx2KCksXyg4LCJidXR0b24iLDYpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLmFsbEZsYWdzUmVzZXQuZW1pdCgpfSksQSg5LCJSZXNldCBBbGwiKSx2KCksRSgxMCxlTmUsMiwwLCJkaXYiLDcpLHYoKSksMiZlJiYoQyg1KSx5KCJuZ0lmIixpLmhhc0ZsYWdzU2VudFRvU2VydmVyKSxDKDIpLHkoIm5nRm9yT2YiLGkuZmVhdHVyZUZsYWdTdGF0dXNlcyksQygzKSx5KCJuZ0lmIixpLmhhc0ZsYWdzU2VudFRvU2VydmVyKSl9LGRlcGVuZGVuY2llczpbZG4sQmUsX24sSGgsT3NdLHN0eWxlczpbIi5tZXNzYWdlW19uZ2NvbnRlbnQtJUNPTVAlXXttYXJnaW4tYm90dG9tOjE2cHh9Lm1lc3NhZ2VbX25nY29udGVudC0lQ09NUCVdICAgLndhcm5pbmdbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiNmNDQzMzZ9Lm5vdGUtMVtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6I2ZmOTgwMH0uc2Nyb2xsaW5nLXBhZ2VbX25nY29udGVudC0lQ09NUCVde21heC1oZWlnaHQ6OTB2aH0uZmVhdHVyZS1mbGFnLXRhYmxlW19uZ2NvbnRlbnQtJUNPTVAlXXt3aWR0aDoxMDAlfSJdfSksbn0pKCksVzI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5oYXNGbGFnc1NlbnRUb1NlcnZlciQ9dGhpcy5zdG9yZS5zZWxlY3QoSG0pLnBpcGUoTChpPT5PYmplY3QudmFsdWVzKGkpLnNvbWUocj0+ci5zZW5kVG9TZXJ2ZXJXaGVuT3ZlcnJpZGRlbikpKSx0aGlzLmZlYXR1cmVGbGFncyQ9dGhpcy5zdG9yZS5zZWxlY3QoekEpLnBpcGUoV3QodGhpcy5zdG9yZS5zZWxlY3QoeSQpLHRoaXMuc3RvcmUuc2VsZWN0KEhtKSksTCgoW2kscixvXSk9Pk9iamVjdC5lbnRyaWVzKHIpLm1hcCgoW3MsYV0pPT57bGV0IGw9ZnVuY3Rpb24obix0KXtyZXR1cm4gdm9pZCAwPT09dFtuXT8iZGVmYXVsdCI6dFtuXT8iZW5hYmxlZCI6ImRpc2FibGVkIn0ocyxpKTtyZXR1cm57ZmxhZzpzLGRlZmF1bHRWYWx1ZTphLHN0YXR1czpsLHNlbmRUb1NlcnZlcldoZW5PdmVycmlkZGVuOm9bc10uc2VuZFRvU2VydmVyV2hlbk92ZXJyaWRkZW59fSkpKX1vbkZsYWdDaGFuZ2VkKHtmbGFnOmUsc3RhdHVzOml9KXtzd2l0Y2goaSl7Y2FzZSJkZWZhdWx0Ijp0aGlzLnN0b3JlLmRpc3BhdGNoKHloKHtmbGFnczpbZV19KSk7YnJlYWs7Y2FzZSJlbmFibGVkIjp0aGlzLnN0b3JlLmRpc3BhdGNoKHFtKHtmbGFnczp7W2VdOiEwfX0pKTticmVhaztjYXNlImRpc2FibGVkIjp0aGlzLnN0b3JlLmRpc3BhdGNoKHFtKHtmbGFnczp7W2VdOiExfX0pKTticmVhaztkZWZhdWx0OnRocm93IG5ldyBFcnJvcigiRmxhZyBjaGFuZ2VkIHRvIGludmFsaWQgc3RhdHVzIil9fW9uQWxsRmxhZ3NSZXNldCgpe3RoaXMuc3RvcmUuZGlzcGF0Y2gobHYoKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJmZWF0dXJlLWZsYWctcGFnZSJdXSxkZWNsczozLHZhcnM6Nixjb25zdHM6W1szLCJmZWF0dXJlRmxhZ1N0YXR1c2VzIiwiaGFzRmxhZ3NTZW50VG9TZXJ2ZXIiLCJmbGFnQ2hhbmdlZCIsImFsbEZsYWdzUmVzZXQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImZlYXR1cmUtZmxhZy1wYWdlLWNvbXBvbmVudCIsMCksUCgiZmxhZ0NoYW5nZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uRmxhZ0NoYW5nZWQobyl9KSgiYWxsRmxhZ3NSZXNldCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkFsbEZsYWdzUmVzZXQoKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksdigpKSwyJmUmJnkoImZlYXR1cmVGbGFnU3RhdHVzZXMiLFUoMSwyLGkuZmVhdHVyZUZsYWdzJCkpKCJoYXNGbGFnc1NlbnRUb1NlcnZlciIsVSgyLDQsaS5oYXNGbGFnc1NlbnRUb1NlcnZlciQpKX0sZGVwZW5kZW5jaWVzOltWbmUsR2VdLGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpLEhuZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSl7dGhpcy5zdG9yZT1lLHRoaXMuZGlhbG9nPWksdGhpcy5zaG93RmVhdHVyZUZsYWdzJD10aGlzLnN0b3JlLnNlbGVjdChFJCl9bmdPbkluaXQoKXt0aGlzLnNob3dGZWF0dXJlRmxhZ3MkLnN1YnNjcmliZShlPT57aWYoZSlyZXR1cm4gdGhpcy5mZWF0dXJlRmxhZ3NEaWFsb2c9dGhpcy5kaWFsb2cub3BlbihXMiksdm9pZCB0aGlzLmZlYXR1cmVGbGFnc0RpYWxvZy5hZnRlckNsb3NlZCgpLnN1YnNjcmliZSgoKT0+e3RoaXMuc3RvcmUuZGlzcGF0Y2goeWgoe2ZsYWdzOlsiZW5hYmxlU2hvd0ZsYWdzIl19KSksc2V0VGltZW91dCgoKT0+e3dpbmRvdy5sb2NhdGlvbi5yZWxvYWQoKX0sMSl9KX0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSxNKHZsKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siZmVhdHVyZS1mbGFnLW1vZGFsLXRyaWdnZXIiXV0sZGVjbHM6MCx2YXJzOjAsdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXt9LGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpLG9OZT1bInJvdXRlQ29udGFpbmVyIl0sVW5lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5jb21wb25lbnRGYWN0b3J5UmVzb2x2ZXI9ZX1uZ09uQ2hhbmdlcyhlKXtsZXQgaT1lLmFjdGl2ZU5nQ29tcG9uZW50O2lmKGkmJih0aGlzLnJvdXRlQ29udGFpbmVyLmNsZWFyKCksaS5jdXJyZW50VmFsdWUpKXtsZXQgcj10aGlzLmNvbXBvbmVudEZhY3RvcnlSZXNvbHZlci5yZXNvbHZlQ29tcG9uZW50RmFjdG9yeShpLmN1cnJlbnRWYWx1ZSk7dGhpcy5yb3V0ZUNvbnRhaW5lci5jcmVhdGVDb21wb25lbnQocil9fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKGdzKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sicm91dGVyLW91dGxldC1jb21wb25lbnQiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiZvdChvTmUsNyxPaSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5yb3V0ZUNvbnRhaW5lcj1yLmZpcnN0KX19LGlucHV0czp7YWN0aXZlTmdDb21wb25lbnQ6ImFjdGl2ZU5nQ29tcG9uZW50In0sZmVhdHVyZXM6W0Z0XSxkZWNsczoyLHZhcnM6MCxjb25zdHM6W1sicm91dGVDb250YWluZXIiLCIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJk5pKDAsbnVsbCwwKX0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksem5lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXt0aGlzLnN0b3JlPWUsdGhpcy5yZWdpc3RyeT1pLHRoaXMuYWN0aXZlTmdDb21wb25lbnQkPUx0KFt0aGlzLnN0b3JlLnNlbGVjdChSYSksdGhpcy5zdG9yZS5zZWxlY3QoWkopXSkucGlwZShMKChbcixvXSk9PnImJihudWxsPT09b3x8UHMocixvKSk/dGhpcy5yZWdpc3RyeS5nZXROZ0NvbXBvbmVudEJ5Um91dGVLaW5kKHIucm91dGVLaW5kKTpudWxsKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpLE0ocWMpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJyb3V0ZXItb3V0bGV0Il1dLGRlY2xzOjIsdmFyczozLGNvbnN0czpbWzMsImFjdGl2ZU5nQ29tcG9uZW50Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJyb3V0ZXItb3V0bGV0LWNvbXBvbmVudCIsMCksQigxLCJhc3luYyIpKSwyJmUmJnkoImFjdGl2ZU5nQ29tcG9uZW50IixVKDEsMSxpLmFjdGl2ZU5nQ29tcG9uZW50JCkpfSxkZXBlbmRlbmNpZXM6W1VuZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksam5lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7ZS5zZWxlY3QoUXUpLnN1YnNjcmliZShpPT57ZG9jdW1lbnQuYm9keS5jbGFzc0xpc3QudG9nZ2xlKCJkYXJrLW1vZGUiLGkpfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJkYXJrLW1vZGUtc3VwcG9ydGVyIl1dLGRlY2xzOjAsdmFyczowLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7fSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVdIHtcbiAgICAgICAgZGlzcGxheTogbm9uZTtcbiAgICAgIH0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLFV2PSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5BQ1RJVkVfUExVR0lOPTBdPSJBQ1RJVkVfUExVR0lOIn0oVXZ8fChVdj17fSkpLFV2KSkoKSxHbmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLmRlZXBMaW5rZXI9ZSx0aGlzLm9uVmFsdWVDaGFuZ2U9bmV3IEcsdGhpcy5uZ1Vuc3Vic2NyaWJlPW5ldyBrZSx0aGlzLm9uSGFzaENoYW5nZT1faSh3aW5kb3csInBvcHN0YXRlIix7cGFzc2l2ZTohMH0pLnBpcGUoc3QodGhpcy5uZ1Vuc3Vic2NyaWJlKSl9bmdPbkluaXQoKXt0aGlzLm9uSGFzaENoYW5nZS5zdWJzY3JpYmUoKCk9PntsZXQgZT10aGlzLmRlZXBMaW5rZXIuZ2V0UGx1Z2luSWQoKTtlIT09dGhpcy5hY3RpdmVQbHVnaW5JZCYmdGhpcy5vblZhbHVlQ2hhbmdlLmVtaXQoe3Byb3A6VXYuQUNUSVZFX1BMVUdJTix2YWx1ZTplfSl9KX1uZ09uRGVzdHJveSgpe3RoaXMubmdVbnN1YnNjcmliZS5uZXh0KCksdGhpcy5uZ1Vuc3Vic2NyaWJlLmNvbXBsZXRlKCl9bmdPbkNoYW5nZXMoZSl7aWYoZS5hY3RpdmVQbHVnaW5JZCl7bGV0IGk9ZS5hY3RpdmVQbHVnaW5JZDt0aGlzLmRlZXBMaW5rZXIuc2V0UGx1Z2luSWQobnVsbD09PWkuY3VycmVudFZhbHVlPyIiOmkuY3VycmVudFZhbHVlLHtkZWZhdWx0VmFsdWU6IiIsdXNlTG9jYXRpb25SZXBsYWNlOm51bGw9PT1pLnByZXZpb3VzVmFsdWV8fGkuZmlyc3RDaGFuZ2V9KX19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oZmgpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJoYXNoLXN0b3JhZ2UtY29tcG9uZW50Il1dLGlucHV0czp7YWN0aXZlUGx1Z2luSWQ6ImFjdGl2ZVBsdWdpbklkIn0sb3V0cHV0czp7b25WYWx1ZUNoYW5nZToib25WYWx1ZUNoYW5nZSJ9LGZlYXR1cmVzOltGdF0sZGVjbHM6MCx2YXJzOjAsdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXt9LGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLFduZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLmFjdGl2ZVBsdWdpbklkJD10aGlzLnN0b3JlLnBpcGUodnQoUnMpKX1vblZhbHVlQ2hhbmdlZChlKXtlLnByb3A9PT1Vdi5BQ1RJVkVfUExVR0lOJiZ0aGlzLnN0b3JlLmRpc3BhdGNoKFhBKHtwbHVnaW46ZS52YWx1ZX0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImhhc2gtc3RvcmFnZSJdXSxkZWNsczoyLHZhcnM6Myxjb25zdHM6W1szLCJhY3RpdmVQbHVnaW5JZCIsIm9uVmFsdWVDaGFuZ2UiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImhhc2gtc3RvcmFnZS1jb21wb25lbnQiLDApLFAoIm9uVmFsdWVDaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uVmFsdWVDaGFuZ2VkKG8pfSksQigxLCJhc3luYyIpLHYoKSksMiZlJiZ5KCJhY3RpdmVQbHVnaW5JZCIsVSgxLDEsaS5hY3RpdmVQbHVnaW5JZCQpKX0sZGVwZW5kZW5jaWVzOltHbmUsR2VdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV0ge1xuICAgICAgICBkaXNwbGF5OiBub25lO1xuICAgICAgfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksZE5lPVsiKiJdO2Z1bmN0aW9uIHZ3KG4pe3JldHVybiBmdW5jdGlvbigpe2lmKHZvaWQgMD09PXEyJiYocTI9bnVsbCx0eXBlb2Ygd2luZG93PCJ1Iikpe2xldCBuPXdpbmRvdzt2b2lkIDAhPT1uLnRydXN0ZWRUeXBlcyYmKHEyPW4udHJ1c3RlZFR5cGVzLmNyZWF0ZVBvbGljeSgiYW5ndWxhciNjb21wb25lbnRzIix7Y3JlYXRlSFRNTDp0PT50fSkpfXJldHVybiBxMn0oKT8uY3JlYXRlSFRNTChuKXx8bn1mdW5jdGlvbiBxbmUobil7cmV0dXJuIEVycm9yKGBVbmFibGUgdG8gZmluZCBpY29uIHdpdGggdGhlIG5hbWUgIiR7bn0iYCl9ZnVuY3Rpb24gWW5lKG4pe3JldHVybiBFcnJvcihgVGhlIFVSTCBwcm92aWRlZCB0byBNYXRJY29uUmVnaXN0cnkgd2FzIG5vdCB0cnVzdGVkIGFzIGEgcmVzb3VyY2UgVVJMIHZpYSBBbmd1bGFyJ3MgRG9tU2FuaXRpemVyLiBBdHRlbXB0ZWQgVVJMIHdhcyAiJHtufSIuYCl9ZnVuY3Rpb24gWG5lKG4pe3JldHVybiBFcnJvcihgVGhlIGxpdGVyYWwgcHJvdmlkZWQgdG8gTWF0SWNvblJlZ2lzdHJ5IHdhcyBub3QgdHJ1c3RlZCBhcyBzYWZlIEhUTUwgYnkgQW5ndWxhcidzIERvbVNhbml0aXplci4gQXR0ZW1wdGVkIGxpdGVyYWwgd2FzICIke259Ii5gKX12YXIgY3A9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe3RoaXMudXJsPXQsdGhpcy5zdmdUZXh0PWUsdGhpcy5vcHRpb25zPWl9fSx6dj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8pe3RoaXMuX2h0dHBDbGllbnQ9ZSx0aGlzLl9zYW5pdGl6ZXI9aSx0aGlzLl9lcnJvckhhbmRsZXI9byx0aGlzLl9zdmdJY29uQ29uZmlncz1uZXcgTWFwLHRoaXMuX2ljb25TZXRDb25maWdzPW5ldyBNYXAsdGhpcy5fY2FjaGVkSWNvbnNCeVVybD1uZXcgTWFwLHRoaXMuX2luUHJvZ3Jlc3NVcmxGZXRjaGVzPW5ldyBNYXAsdGhpcy5fZm9udENzc0NsYXNzZXNCeUFsaWFzPW5ldyBNYXAsdGhpcy5fcmVzb2x2ZXJzPVtdLHRoaXMuX2RlZmF1bHRGb250U2V0Q2xhc3M9WyJtYXRlcmlhbC1pY29ucyIsIm1hdC1saWdhdHVyZS1mb250Il0sdGhpcy5fZG9jdW1lbnQ9cn1hZGRTdmdJY29uKGUsaSxyKXtyZXR1cm4gdGhpcy5hZGRTdmdJY29uSW5OYW1lc3BhY2UoIiIsZSxpLHIpfWFkZFN2Z0ljb25MaXRlcmFsKGUsaSxyKXtyZXR1cm4gdGhpcy5hZGRTdmdJY29uTGl0ZXJhbEluTmFtZXNwYWNlKCIiLGUsaSxyKX1hZGRTdmdJY29uSW5OYW1lc3BhY2UoZSxpLHIsbyl7cmV0dXJuIHRoaXMuX2FkZFN2Z0ljb25Db25maWcoZSxpLG5ldyBjcChyLG51bGwsbykpfWFkZFN2Z0ljb25SZXNvbHZlcihlKXtyZXR1cm4gdGhpcy5fcmVzb2x2ZXJzLnB1c2goZSksdGhpc31hZGRTdmdJY29uTGl0ZXJhbEluTmFtZXNwYWNlKGUsaSxyLG8pe2xldCBzPXRoaXMuX3Nhbml0aXplci5zYW5pdGl6ZShtby5IVE1MLHIpO2lmKCFzKXRocm93IFhuZShyKTtsZXQgYT12dyhzKTtyZXR1cm4gdGhpcy5fYWRkU3ZnSWNvbkNvbmZpZyhlLGksbmV3IGNwKCIiLGEsbykpfWFkZFN2Z0ljb25TZXQoZSxpKXtyZXR1cm4gdGhpcy5hZGRTdmdJY29uU2V0SW5OYW1lc3BhY2UoIiIsZSxpKX1hZGRTdmdJY29uU2V0TGl0ZXJhbChlLGkpe3JldHVybiB0aGlzLmFkZFN2Z0ljb25TZXRMaXRlcmFsSW5OYW1lc3BhY2UoIiIsZSxpKX1hZGRTdmdJY29uU2V0SW5OYW1lc3BhY2UoZSxpLHIpe3JldHVybiB0aGlzLl9hZGRTdmdJY29uU2V0Q29uZmlnKGUsbmV3IGNwKGksbnVsbCxyKSl9YWRkU3ZnSWNvblNldExpdGVyYWxJbk5hbWVzcGFjZShlLGkscil7bGV0IG89dGhpcy5fc2FuaXRpemVyLnNhbml0aXplKG1vLkhUTUwsaSk7aWYoIW8pdGhyb3cgWG5lKGkpO2xldCBzPXZ3KG8pO3JldHVybiB0aGlzLl9hZGRTdmdJY29uU2V0Q29uZmlnKGUsbmV3IGNwKCIiLHMscikpfXJlZ2lzdGVyRm9udENsYXNzQWxpYXMoZSxpPWUpe3JldHVybiB0aGlzLl9mb250Q3NzQ2xhc3Nlc0J5QWxpYXMuc2V0KGUsaSksdGhpc31jbGFzc05hbWVGb3JGb250QWxpYXMoZSl7cmV0dXJuIHRoaXMuX2ZvbnRDc3NDbGFzc2VzQnlBbGlhcy5nZXQoZSl8fGV9c2V0RGVmYXVsdEZvbnRTZXRDbGFzcyguLi5lKXtyZXR1cm4gdGhpcy5fZGVmYXVsdEZvbnRTZXRDbGFzcz1lLHRoaXN9Z2V0RGVmYXVsdEZvbnRTZXRDbGFzcygpe3JldHVybiB0aGlzLl9kZWZhdWx0Rm9udFNldENsYXNzfWdldFN2Z0ljb25Gcm9tVXJsKGUpe2xldCBpPXRoaXMuX3Nhbml0aXplci5zYW5pdGl6ZShtby5SRVNPVVJDRV9VUkwsZSk7aWYoIWkpdGhyb3cgWW5lKGUpO2xldCByPXRoaXMuX2NhY2hlZEljb25zQnlVcmwuZ2V0KGkpO3JldHVybiByP1h0KFkyKHIpKTp0aGlzLl9sb2FkU3ZnSWNvbkZyb21Db25maWcobmV3IGNwKGUsbnVsbCkpLnBpcGUoa3Qobz0+dGhpcy5fY2FjaGVkSWNvbnNCeVVybC5zZXQoaSxvKSksTChvPT5ZMihvKSkpfWdldE5hbWVkU3ZnSWNvbihlLGk9IiIpe2xldCByPVFuZShpLGUpLG89dGhpcy5fc3ZnSWNvbkNvbmZpZ3MuZ2V0KHIpO2lmKG8pcmV0dXJuIHRoaXMuX2dldFN2Z0Zyb21Db25maWcobyk7aWYobz10aGlzLl9nZXRJY29uQ29uZmlnRnJvbVJlc29sdmVycyhpLGUpLG8pcmV0dXJuIHRoaXMuX3N2Z0ljb25Db25maWdzLnNldChyLG8pLHRoaXMuX2dldFN2Z0Zyb21Db25maWcobyk7bGV0IHM9dGhpcy5faWNvblNldENvbmZpZ3MuZ2V0KGkpO3JldHVybiBzP3RoaXMuX2dldFN2Z0Zyb21JY29uU2V0Q29uZmlncyhlLHMpOndjKHFuZShyKSl9bmdPbkRlc3Ryb3koKXt0aGlzLl9yZXNvbHZlcnM9W10sdGhpcy5fc3ZnSWNvbkNvbmZpZ3MuY2xlYXIoKSx0aGlzLl9pY29uU2V0Q29uZmlncy5jbGVhcigpLHRoaXMuX2NhY2hlZEljb25zQnlVcmwuY2xlYXIoKX1fZ2V0U3ZnRnJvbUNvbmZpZyhlKXtyZXR1cm4gZS5zdmdUZXh0P1h0KFkyKHRoaXMuX3N2Z0VsZW1lbnRGcm9tQ29uZmlnKGUpKSk6dGhpcy5fbG9hZFN2Z0ljb25Gcm9tQ29uZmlnKGUpLnBpcGUoTChpPT5ZMihpKSkpfV9nZXRTdmdGcm9tSWNvblNldENvbmZpZ3MoZSxpKXtsZXQgcj10aGlzLl9leHRyYWN0SWNvbldpdGhOYW1lRnJvbUFueVNldChlLGkpO3JldHVybiByP1h0KHIpOmxyKGkuZmlsdGVyKHM9PiFzLnN2Z1RleHQpLm1hcChzPT50aGlzLl9sb2FkU3ZnSWNvblNldEZyb21Db25maWcocykucGlwZShmbyhhPT57bGV0IGM9YExvYWRpbmcgaWNvbiBzZXQgVVJMOiAke3RoaXMuX3Nhbml0aXplci5zYW5pdGl6ZShtby5SRVNPVVJDRV9VUkwscy51cmwpfSBmYWlsZWQ6ICR7YS5tZXNzYWdlfWA7cmV0dXJuIHRoaXMuX2Vycm9ySGFuZGxlci5oYW5kbGVFcnJvcihuZXcgRXJyb3IoYykpLFh0KG51bGwpfSkpKSkucGlwZShMKCgpPT57bGV0IHM9dGhpcy5fZXh0cmFjdEljb25XaXRoTmFtZUZyb21BbnlTZXQoZSxpKTtpZighcyl0aHJvdyBxbmUoZSk7cmV0dXJuIHN9KSl9X2V4dHJhY3RJY29uV2l0aE5hbWVGcm9tQW55U2V0KGUsaSl7Zm9yKGxldCByPWkubGVuZ3RoLTE7cj49MDtyLS0pe2xldCBvPWlbcl07aWYoby5zdmdUZXh0JiZvLnN2Z1RleHQudG9TdHJpbmcoKS5pbmRleE9mKGUpPi0xKXtsZXQgcz10aGlzLl9zdmdFbGVtZW50RnJvbUNvbmZpZyhvKSxhPXRoaXMuX2V4dHJhY3RTdmdJY29uRnJvbVNldChzLGUsby5vcHRpb25zKTtpZihhKXJldHVybiBhfX1yZXR1cm4gbnVsbH1fbG9hZFN2Z0ljb25Gcm9tQ29uZmlnKGUpe3JldHVybiB0aGlzLl9mZXRjaEljb24oZSkucGlwZShrdChpPT5lLnN2Z1RleHQ9aSksTCgoKT0+dGhpcy5fc3ZnRWxlbWVudEZyb21Db25maWcoZSkpKX1fbG9hZFN2Z0ljb25TZXRGcm9tQ29uZmlnKGUpe3JldHVybiBlLnN2Z1RleHQ/WHQobnVsbCk6dGhpcy5fZmV0Y2hJY29uKGUpLnBpcGUoa3QoaT0+ZS5zdmdUZXh0PWkpKX1fZXh0cmFjdFN2Z0ljb25Gcm9tU2V0KGUsaSxyKXtsZXQgbz1lLnF1ZXJ5U2VsZWN0b3IoYFtpZD0iJHtpfSJdYCk7aWYoIW8pcmV0dXJuIG51bGw7bGV0IHM9by5jbG9uZU5vZGUoITApO2lmKHMucmVtb3ZlQXR0cmlidXRlKCJpZCIpLCJzdmciPT09cy5ub2RlTmFtZS50b0xvd2VyQ2FzZSgpKXJldHVybiB0aGlzLl9zZXRTdmdBdHRyaWJ1dGVzKHMscik7aWYoInN5bWJvbCI9PT1zLm5vZGVOYW1lLnRvTG93ZXJDYXNlKCkpcmV0dXJuIHRoaXMuX3NldFN2Z0F0dHJpYnV0ZXModGhpcy5fdG9TdmdFbGVtZW50KHMpLHIpO2xldCBhPXRoaXMuX3N2Z0VsZW1lbnRGcm9tU3RyaW5nKHZ3KCI8c3ZnPjwvc3ZnPiIpKTtyZXR1cm4gYS5hcHBlbmRDaGlsZChzKSx0aGlzLl9zZXRTdmdBdHRyaWJ1dGVzKGEscil9X3N2Z0VsZW1lbnRGcm9tU3RyaW5nKGUpe2xldCBpPXRoaXMuX2RvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkRJViIpO2kuaW5uZXJIVE1MPWU7bGV0IHI9aS5xdWVyeVNlbGVjdG9yKCJzdmciKTtpZighcil0aHJvdyBFcnJvcigiPHN2Zz4gdGFnIG5vdCBmb3VuZCIpO3JldHVybiByfV90b1N2Z0VsZW1lbnQoZSl7bGV0IGk9dGhpcy5fc3ZnRWxlbWVudEZyb21TdHJpbmcodncoIjxzdmc+PC9zdmc+IikpLHI9ZS5hdHRyaWJ1dGVzO2ZvcihsZXQgbz0wO288ci5sZW5ndGg7bysrKXtsZXR7bmFtZTpzLHZhbHVlOmF9PXJbb107ImlkIiE9PXMmJmkuc2V0QXR0cmlidXRlKHMsYSl9Zm9yKGxldCBvPTA7bzxlLmNoaWxkTm9kZXMubGVuZ3RoO28rKyllLmNoaWxkTm9kZXNbb10ubm9kZVR5cGU9PT10aGlzLl9kb2N1bWVudC5FTEVNRU5UX05PREUmJmkuYXBwZW5kQ2hpbGQoZS5jaGlsZE5vZGVzW29dLmNsb25lTm9kZSghMCkpO3JldHVybiBpfV9zZXRTdmdBdHRyaWJ1dGVzKGUsaSl7cmV0dXJuIGUuc2V0QXR0cmlidXRlKCJmaXQiLCIiKSxlLnNldEF0dHJpYnV0ZSgiaGVpZ2h0IiwiMTAwJSIpLGUuc2V0QXR0cmlidXRlKCJ3aWR0aCIsIjEwMCUiKSxlLnNldEF0dHJpYnV0ZSgicHJlc2VydmVBc3BlY3RSYXRpbyIsInhNaWRZTWlkIG1lZXQiKSxlLnNldEF0dHJpYnV0ZSgiZm9jdXNhYmxlIiwiZmFsc2UiKSxpJiZpLnZpZXdCb3gmJmUuc2V0QXR0cmlidXRlKCJ2aWV3Qm94IixpLnZpZXdCb3gpLGV9X2ZldGNoSWNvbihlKXtsZXR7dXJsOmksb3B0aW9uczpyfT1lLG89cj8ud2l0aENyZWRlbnRpYWxzPz8hMTtpZighdGhpcy5faHR0cENsaWVudCl0aHJvdyBFcnJvcigiQ291bGQgbm90IGZpbmQgSHR0cENsaWVudCBwcm92aWRlciBmb3IgdXNlIHdpdGggQW5ndWxhciBNYXRlcmlhbCBpY29ucy4gUGxlYXNlIGluY2x1ZGUgdGhlIEh0dHBDbGllbnRNb2R1bGUgZnJvbSBAYW5ndWxhci9jb21tb24vaHR0cCBpbiB5b3VyIGFwcCBpbXBvcnRzLiIpO2lmKG51bGw9PWkpdGhyb3cgRXJyb3IoYENhbm5vdCBmZXRjaCBpY29uIGZyb20gVVJMICIke2l9Ii5gKTtsZXQgcz10aGlzLl9zYW5pdGl6ZXIuc2FuaXRpemUobW8uUkVTT1VSQ0VfVVJMLGkpO2lmKCFzKXRocm93IFluZShpKTtsZXQgYT10aGlzLl9pblByb2dyZXNzVXJsRmV0Y2hlcy5nZXQocyk7aWYoYSlyZXR1cm4gYTtsZXQgbD10aGlzLl9odHRwQ2xpZW50LmdldChzLHtyZXNwb25zZVR5cGU6InRleHQiLHdpdGhDcmVkZW50aWFsczpvfSkucGlwZShMKGM9PnZ3KGMpKSxmdW5jdGlvbihuKXtyZXR1cm4gZW4oKHQsZSk9Pnt0cnl7dC5zdWJzY3JpYmUoZSl9ZmluYWxseXtlLmFkZChuKX19KX0oKCk9PnRoaXMuX2luUHJvZ3Jlc3NVcmxGZXRjaGVzLmRlbGV0ZShzKSksVHMoKSk7cmV0dXJuIHRoaXMuX2luUHJvZ3Jlc3NVcmxGZXRjaGVzLnNldChzLGwpLGx9X2FkZFN2Z0ljb25Db25maWcoZSxpLHIpe3JldHVybiB0aGlzLl9zdmdJY29uQ29uZmlncy5zZXQoUW5lKGUsaSksciksdGhpc31fYWRkU3ZnSWNvblNldENvbmZpZyhlLGkpe2xldCByPXRoaXMuX2ljb25TZXRDb25maWdzLmdldChlKTtyZXR1cm4gcj9yLnB1c2goaSk6dGhpcy5faWNvblNldENvbmZpZ3Muc2V0KGUsW2ldKSx0aGlzfV9zdmdFbGVtZW50RnJvbUNvbmZpZyhlKXtpZighZS5zdmdFbGVtZW50KXtsZXQgaT10aGlzLl9zdmdFbGVtZW50RnJvbVN0cmluZyhlLnN2Z1RleHQpO3RoaXMuX3NldFN2Z0F0dHJpYnV0ZXMoaSxlLm9wdGlvbnMpLGUuc3ZnRWxlbWVudD1pfXJldHVybiBlLnN2Z0VsZW1lbnR9X2dldEljb25Db25maWdGcm9tUmVzb2x2ZXJzKGUsaSl7Zm9yKGxldCByPTA7cjx0aGlzLl9yZXNvbHZlcnMubGVuZ3RoO3IrKyl7bGV0IG89dGhpcy5fcmVzb2x2ZXJzW3JdKGksZSk7aWYobylyZXR1cm4gbU5lKG8pP25ldyBjcChvLnVybCxudWxsLG8ub3B0aW9ucyk6bmV3IGNwKG8sbnVsbCl9fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKFZtLDgpLGooVG0pLGooSHQsOCksaihRcykpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpO2Z1bmN0aW9uIFkyKG4pe3JldHVybiBuLmNsb25lTm9kZSghMCl9ZnVuY3Rpb24gUW5lKG4sdCl7cmV0dXJuIG4rIjoiK3R9ZnVuY3Rpb24gbU5lKG4pe3JldHVybiEoIW4udXJsfHwhbi5vcHRpb25zKX1uZXcgbnMsbmV3IHRsLG5ldyBucyxuZXcgbnM7dmFyIGdOZT1rbyhjbGFzc3tjb25zdHJ1Y3RvcihuKXt0aGlzLl9lbGVtZW50UmVmPW59fSksX05lPW5ldyBwZSgiTUFUX0lDT05fREVGQVVMVF9PUFRJT05TIiksdk5lPW5ldyBwZSgibWF0LWljb24tbG9jYXRpb24iLHtwcm92aWRlZEluOiJyb290IixmYWN0b3J5OmZ1bmN0aW9uKCl7bGV0IG49am8oSHQpLHQ9bj9uLmxvY2F0aW9uOm51bGw7cmV0dXJue2dldFBhdGhuYW1lOigpPT50P3QucGF0aG5hbWUrdC5zZWFyY2g6IiJ9fX0pLEtuZT1bImNsaXAtcGF0aCIsImNvbG9yLXByb2ZpbGUiLCJzcmMiLCJjdXJzb3IiLCJmaWxsIiwiZmlsdGVyIiwibWFya2VyIiwibWFya2VyLXN0YXJ0IiwibWFya2VyLW1pZCIsIm1hcmtlci1lbmQiLCJtYXNrIiwic3Ryb2tlIl0sYk5lPUtuZS5tYXAobj0+YFske259XWApLmpvaW4oIiwgIikseE5lPS9edXJsXChbJyJdPyMoLio/KVsnIl0/XCkkLyxHdD0oKCk9PntjbGFzcyBuIGV4dGVuZHMgZ05le2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhKXtzdXBlcihlKSx0aGlzLl9pY29uUmVnaXN0cnk9aSx0aGlzLl9sb2NhdGlvbj1vLHRoaXMuX2Vycm9ySGFuZGxlcj1zLHRoaXMuX2lubGluZT0hMSx0aGlzLl9wcmV2aW91c0ZvbnRTZXRDbGFzcz1bXSx0aGlzLl9jdXJyZW50SWNvbkZldGNoPVNuLkVNUFRZLGEmJihhLmNvbG9yJiYodGhpcy5jb2xvcj10aGlzLmRlZmF1bHRDb2xvcj1hLmNvbG9yKSxhLmZvbnRTZXQmJih0aGlzLmZvbnRTZXQ9YS5mb250U2V0KSkscnx8ZS5uYXRpdmVFbGVtZW50LnNldEF0dHJpYnV0ZSgiYXJpYS1oaWRkZW4iLCJ0cnVlIil9Z2V0IGlubGluZSgpe3JldHVybiB0aGlzLl9pbmxpbmV9c2V0IGlubGluZShlKXt0aGlzLl9pbmxpbmU9UnQoZSl9Z2V0IHN2Z0ljb24oKXtyZXR1cm4gdGhpcy5fc3ZnSWNvbn1zZXQgc3ZnSWNvbihlKXtlIT09dGhpcy5fc3ZnSWNvbiYmKGU/dGhpcy5fdXBkYXRlU3ZnSWNvbihlKTp0aGlzLl9zdmdJY29uJiZ0aGlzLl9jbGVhclN2Z0VsZW1lbnQoKSx0aGlzLl9zdmdJY29uPWUpfWdldCBmb250U2V0KCl7cmV0dXJuIHRoaXMuX2ZvbnRTZXR9c2V0IGZvbnRTZXQoZSl7bGV0IGk9dGhpcy5fY2xlYW51cEZvbnRWYWx1ZShlKTtpIT09dGhpcy5fZm9udFNldCYmKHRoaXMuX2ZvbnRTZXQ9aSx0aGlzLl91cGRhdGVGb250SWNvbkNsYXNzZXMoKSl9Z2V0IGZvbnRJY29uKCl7cmV0dXJuIHRoaXMuX2ZvbnRJY29ufXNldCBmb250SWNvbihlKXtsZXQgaT10aGlzLl9jbGVhbnVwRm9udFZhbHVlKGUpO2khPT10aGlzLl9mb250SWNvbiYmKHRoaXMuX2ZvbnRJY29uPWksdGhpcy5fdXBkYXRlRm9udEljb25DbGFzc2VzKCkpfV9zcGxpdEljb25OYW1lKGUpe2lmKCFlKXJldHVyblsiIiwiIl07bGV0IGk9ZS5zcGxpdCgiOiIpO3N3aXRjaChpLmxlbmd0aCl7Y2FzZSAxOnJldHVyblsiIixpWzBdXTtjYXNlIDI6cmV0dXJuIGk7ZGVmYXVsdDp0aHJvdyBFcnJvcihgSW52YWxpZCBpY29uIG5hbWU6ICIke2V9ImApfX1uZ09uSW5pdCgpe3RoaXMuX3VwZGF0ZUZvbnRJY29uQ2xhc3NlcygpfW5nQWZ0ZXJWaWV3Q2hlY2tlZCgpe2xldCBlPXRoaXMuX2VsZW1lbnRzV2l0aEV4dGVybmFsUmVmZXJlbmNlcztpZihlJiZlLnNpemUpe2xldCBpPXRoaXMuX2xvY2F0aW9uLmdldFBhdGhuYW1lKCk7aSE9PXRoaXMuX3ByZXZpb3VzUGF0aCYmKHRoaXMuX3ByZXZpb3VzUGF0aD1pLHRoaXMuX3ByZXBlbmRQYXRoVG9SZWZlcmVuY2VzKGkpKX19bmdPbkRlc3Ryb3koKXt0aGlzLl9jdXJyZW50SWNvbkZldGNoLnVuc3Vic2NyaWJlKCksdGhpcy5fZWxlbWVudHNXaXRoRXh0ZXJuYWxSZWZlcmVuY2VzJiZ0aGlzLl9lbGVtZW50c1dpdGhFeHRlcm5hbFJlZmVyZW5jZXMuY2xlYXIoKX1fdXNpbmdGb250SWNvbigpe3JldHVybiF0aGlzLnN2Z0ljb259X3NldFN2Z0VsZW1lbnQoZSl7dGhpcy5fY2xlYXJTdmdFbGVtZW50KCk7bGV0IGk9dGhpcy5fbG9jYXRpb24uZ2V0UGF0aG5hbWUoKTt0aGlzLl9wcmV2aW91c1BhdGg9aSx0aGlzLl9jYWNoZUNoaWxkcmVuV2l0aEV4dGVybmFsUmVmZXJlbmNlcyhlKSx0aGlzLl9wcmVwZW5kUGF0aFRvUmVmZXJlbmNlcyhpKSx0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuYXBwZW5kQ2hpbGQoZSl9X2NsZWFyU3ZnRWxlbWVudCgpe2xldCBlPXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudCxpPWUuY2hpbGROb2Rlcy5sZW5ndGg7Zm9yKHRoaXMuX2VsZW1lbnRzV2l0aEV4dGVybmFsUmVmZXJlbmNlcyYmdGhpcy5fZWxlbWVudHNXaXRoRXh0ZXJuYWxSZWZlcmVuY2VzLmNsZWFyKCk7aS0tOyl7bGV0IHI9ZS5jaGlsZE5vZGVzW2ldOygxIT09ci5ub2RlVHlwZXx8InN2ZyI9PT1yLm5vZGVOYW1lLnRvTG93ZXJDYXNlKCkpJiZyLnJlbW92ZSgpfX1fdXBkYXRlRm9udEljb25DbGFzc2VzKCl7aWYoIXRoaXMuX3VzaW5nRm9udEljb24oKSlyZXR1cm47bGV0IGU9dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LGk9KHRoaXMuZm9udFNldD90aGlzLl9pY29uUmVnaXN0cnkuY2xhc3NOYW1lRm9yRm9udEFsaWFzKHRoaXMuZm9udFNldCkuc3BsaXQoLyArLyk6dGhpcy5faWNvblJlZ2lzdHJ5LmdldERlZmF1bHRGb250U2V0Q2xhc3MoKSkuZmlsdGVyKHI9PnIubGVuZ3RoPjApO3RoaXMuX3ByZXZpb3VzRm9udFNldENsYXNzLmZvckVhY2gocj0+ZS5jbGFzc0xpc3QucmVtb3ZlKHIpKSxpLmZvckVhY2gocj0+ZS5jbGFzc0xpc3QuYWRkKHIpKSx0aGlzLl9wcmV2aW91c0ZvbnRTZXRDbGFzcz1pLHRoaXMuZm9udEljb24hPT10aGlzLl9wcmV2aW91c0ZvbnRJY29uQ2xhc3MmJiFpLmluY2x1ZGVzKCJtYXQtbGlnYXR1cmUtZm9udCIpJiYodGhpcy5fcHJldmlvdXNGb250SWNvbkNsYXNzJiZlLmNsYXNzTGlzdC5yZW1vdmUodGhpcy5fcHJldmlvdXNGb250SWNvbkNsYXNzKSx0aGlzLmZvbnRJY29uJiZlLmNsYXNzTGlzdC5hZGQodGhpcy5mb250SWNvbiksdGhpcy5fcHJldmlvdXNGb250SWNvbkNsYXNzPXRoaXMuZm9udEljb24pfV9jbGVhbnVwRm9udFZhbHVlKGUpe3JldHVybiJzdHJpbmciPT10eXBlb2YgZT9lLnRyaW0oKS5zcGxpdCgiICIpWzBdOmV9X3ByZXBlbmRQYXRoVG9SZWZlcmVuY2VzKGUpe2xldCBpPXRoaXMuX2VsZW1lbnRzV2l0aEV4dGVybmFsUmVmZXJlbmNlcztpJiZpLmZvckVhY2goKHIsbyk9PntyLmZvckVhY2gocz0+e28uc2V0QXR0cmlidXRlKHMubmFtZSxgdXJsKCcke2V9IyR7cy52YWx1ZX0nKWApfSl9KX1fY2FjaGVDaGlsZHJlbldpdGhFeHRlcm5hbFJlZmVyZW5jZXMoZSl7bGV0IGk9ZS5xdWVyeVNlbGVjdG9yQWxsKGJOZSkscj10aGlzLl9lbGVtZW50c1dpdGhFeHRlcm5hbFJlZmVyZW5jZXM9dGhpcy5fZWxlbWVudHNXaXRoRXh0ZXJuYWxSZWZlcmVuY2VzfHxuZXcgTWFwO2ZvcihsZXQgbz0wO288aS5sZW5ndGg7bysrKUtuZS5mb3JFYWNoKHM9PntsZXQgYT1pW29dLGw9YS5nZXRBdHRyaWJ1dGUocyksYz1sP2wubWF0Y2goeE5lKTpudWxsO2lmKGMpe2xldCB1PXIuZ2V0KGEpO3V8fCh1PVtdLHIuc2V0KGEsdSkpLHUucHVzaCh7bmFtZTpzLHZhbHVlOmNbMV19KX19KX1fdXBkYXRlU3ZnSWNvbihlKXtpZih0aGlzLl9zdmdOYW1lc3BhY2U9bnVsbCx0aGlzLl9zdmdOYW1lPW51bGwsdGhpcy5fY3VycmVudEljb25GZXRjaC51bnN1YnNjcmliZSgpLGUpe2xldFtpLHJdPXRoaXMuX3NwbGl0SWNvbk5hbWUoZSk7aSYmKHRoaXMuX3N2Z05hbWVzcGFjZT1pKSxyJiYodGhpcy5fc3ZnTmFtZT1yKSx0aGlzLl9jdXJyZW50SWNvbkZldGNoPXRoaXMuX2ljb25SZWdpc3RyeS5nZXROYW1lZFN2Z0ljb24ocixpKS5waXBlKFF0KDEpKS5zdWJzY3JpYmUobz0+dGhpcy5fc2V0U3ZnRWxlbWVudChvKSxvPT57dGhpcy5fZXJyb3JIYW5kbGVyLmhhbmRsZUVycm9yKG5ldyBFcnJvcihgRXJyb3IgcmV0cmlldmluZyBpY29uICR7aX06JHtyfSEgJHtvLm1lc3NhZ2V9YCkpfSl9fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKHp2KSx2bygiYXJpYS1oaWRkZW4iKSxNKHZOZSksTShRcyksTShfTmUsOCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1pY29uIl1dLGhvc3RBdHRyczpbInJvbGUiLCJpbWciLDEsIm1hdC1pY29uIiwibm90cmFuc2xhdGUiXSxob3N0VmFyczo4LGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmKHplKCJkYXRhLW1hdC1pY29uLXR5cGUiLGkuX3VzaW5nRm9udEljb24oKT8iZm9udCI6InN2ZyIpKCJkYXRhLW1hdC1pY29uLW5hbWUiLGkuX3N2Z05hbWV8fGkuZm9udEljb24pKCJkYXRhLW1hdC1pY29uLW5hbWVzcGFjZSIsaS5fc3ZnTmFtZXNwYWNlfHxpLmZvbnRTZXQpKCJmb250SWNvbiIsaS5fdXNpbmdGb250SWNvbigpP2kuZm9udEljb246bnVsbCksZXQoIm1hdC1pY29uLWlubGluZSIsaS5pbmxpbmUpKCJtYXQtaWNvbi1uby1jb2xvciIsInByaW1hcnkiIT09aS5jb2xvciYmImFjY2VudCIhPT1pLmNvbG9yJiYid2FybiIhPT1pLmNvbG9yKSl9LGlucHV0czp7Y29sb3I6ImNvbG9yIixpbmxpbmU6ImlubGluZSIsc3ZnSWNvbjoic3ZnSWNvbiIsZm9udFNldDoiZm9udFNldCIsZm9udEljb246ImZvbnRJY29uIn0sZXhwb3J0QXM6WyJtYXRJY29uIl0sZmVhdHVyZXM6W3R0XSxuZ0NvbnRlbnRTZWxlY3RvcnM6ZE5lLGRlY2xzOjEsdmFyczowLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoeGkoKSxWbigwKSl9LHN0eWxlczpbIi5tYXQtaWNvbnstd2Via2l0LXVzZXItc2VsZWN0Om5vbmU7dXNlci1zZWxlY3Q6bm9uZTtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7ZGlzcGxheTppbmxpbmUtYmxvY2s7ZmlsbDpjdXJyZW50Q29sb3I7aGVpZ2h0OjI0cHg7d2lkdGg6MjRweDtvdmVyZmxvdzpoaWRkZW59Lm1hdC1pY29uLm1hdC1pY29uLWlubGluZXtmb250LXNpemU6aW5oZXJpdDtoZWlnaHQ6aW5oZXJpdDtsaW5lLWhlaWdodDppbmhlcml0O3dpZHRoOmluaGVyaXR9Lm1hdC1pY29uLm1hdC1saWdhdHVyZS1mb250W2ZvbnRJY29uXTo6YmVmb3Jle2NvbnRlbnQ6YXR0cihmb250SWNvbil9W2Rpcj1ydGxdIC5tYXQtaWNvbi1ydGwtbWlycm9ye3RyYW5zZm9ybTpzY2FsZSgtMSwgMSl9Lm1hdC1mb3JtLWZpZWxkOm5vdCgubWF0LWZvcm0tZmllbGQtYXBwZWFyYW5jZS1sZWdhY3kpIC5tYXQtZm9ybS1maWVsZC1wcmVmaXggLm1hdC1pY29uLC5tYXQtZm9ybS1maWVsZDpub3QoLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5KSAubWF0LWZvcm0tZmllbGQtc3VmZml4IC5tYXQtaWNvbntkaXNwbGF5OmJsb2NrfS5tYXQtZm9ybS1maWVsZDpub3QoLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5KSAubWF0LWZvcm0tZmllbGQtcHJlZml4IC5tYXQtaWNvbi1idXR0b24gLm1hdC1pY29uLC5tYXQtZm9ybS1maWVsZDpub3QoLm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtbGVnYWN5KSAubWF0LWZvcm0tZmllbGQtc3VmZml4IC5tYXQtaWNvbi1idXR0b24gLm1hdC1pY29ue21hcmdpbjphdXRvfSJdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLHBuPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltsbixsbl19KSxufSkoKSxDTmU9WyIqIixbWyJtYXQtdG9vbGJhci1yb3ciXV1dLE1OZT1bIioiLCJtYXQtdG9vbGJhci1yb3ciXSx3TmU9a28oY2xhc3N7Y29uc3RydWN0b3Iobil7dGhpcy5fZWxlbWVudFJlZj1ufX0pLFNOZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LXRvb2xiYXItcm93Il1dLGhvc3RBdHRyczpbMSwibWF0LXRvb2xiYXItcm93Il0sZXhwb3J0QXM6WyJtYXRUb29sYmFyUm93Il19KSxufSkoKSxabmU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIHdOZXtjb25zdHJ1Y3RvcihlLGkscil7c3VwZXIoZSksdGhpcy5fcGxhdGZvcm09aSx0aGlzLl9kb2N1bWVudD1yfW5nQWZ0ZXJWaWV3SW5pdCgpe3RoaXMuX3BsYXRmb3JtLmlzQnJvd3NlciYmKHRoaXMuX2NoZWNrVG9vbGJhck1peGVkTW9kZXMoKSx0aGlzLl90b29sYmFyUm93cy5jaGFuZ2VzLnN1YnNjcmliZSgoKT0+dGhpcy5fY2hlY2tUb29sYmFyTWl4ZWRNb2RlcygpKSl9X2NoZWNrVG9vbGJhck1peGVkTW9kZXMoKXt9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0ob2kpLE0oSHQpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtYXQtdG9vbGJhciJdXSxjb250ZW50UXVlcmllczpmdW5jdGlvbihlLGkscil7aWYoMSZlJiZFaShyLFNOZSw1KSwyJmUpe2xldCBvO05lKG89TGUoKSkmJihpLl90b29sYmFyUm93cz1vKX19LGhvc3RBdHRyczpbMSwibWF0LXRvb2xiYXIiXSxob3N0VmFyczo0LGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmZXQoIm1hdC10b29sYmFyLW11bHRpcGxlLXJvd3MiLGkuX3Rvb2xiYXJSb3dzLmxlbmd0aD4wKSgibWF0LXRvb2xiYXItc2luZ2xlLXJvdyIsMD09PWkuX3Rvb2xiYXJSb3dzLmxlbmd0aCl9LGlucHV0czp7Y29sb3I6ImNvbG9yIn0sZXhwb3J0QXM6WyJtYXRUb29sYmFyIl0sZmVhdHVyZXM6W3R0XSxuZ0NvbnRlbnRTZWxlY3RvcnM6TU5lLGRlY2xzOjIsdmFyczowLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoeGkoQ05lKSxWbigwKSxWbigxLDEpKX0sc3R5bGVzOlsiLmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LXRvb2xiYXJ7b3V0bGluZTpzb2xpZCAxcHh9Lm1hdC10b29sYmFyLXJvdywubWF0LXRvb2xiYXItc2luZ2xlLXJvd3tkaXNwbGF5OmZsZXg7Ym94LXNpemluZzpib3JkZXItYm94O3BhZGRpbmc6MCAxNnB4O3dpZHRoOjEwMCU7ZmxleC1kaXJlY3Rpb246cm93O2FsaWduLWl0ZW1zOmNlbnRlcjt3aGl0ZS1zcGFjZTpub3dyYXB9Lm1hdC10b29sYmFyLW11bHRpcGxlLXJvd3N7ZGlzcGxheTpmbGV4O2JveC1zaXppbmc6Ym9yZGVyLWJveDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47d2lkdGg6MTAwJX0iXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxKbmU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W2xuLGxuXX0pLG59KSgpO2Z1bmN0aW9uIFFIKG4pe3JldHVybiBuLnN0YXRlIT09T2UuTk9UX0xPQURFRCYmbi5zdGF0ZSE9PU9lLkxPQURJTkd9dmFyIFROZT12cihSZWUsU2UoWEksbj0+UUgobik/ey4uLm4sc2V0dGluZ3M6ey4uLm4uc2V0dGluZ3MscmVsb2FkRW5hYmxlZDohbi5zZXR0aW5ncy5yZWxvYWRFbmFibGVkfX06biksU2UoUUksKG4se3BlcmlvZEluTXM6dH0pPT57aWYoIVFIKG4pKXJldHVybiBuO2xldCBlPXQ+PTNlND90Om4uc2V0dGluZ3MucmVsb2FkUGVyaW9kSW5NcztyZXR1cm57Li4ubixzZXR0aW5nczp7Li4ubi5zZXR0aW5ncyxyZWxvYWRQZXJpb2RJbk1zOmV9fX0pLFNlKEtJLChuLHtzaXplOnR9KT0+e2lmKCFRSChuKSlyZXR1cm4gbjtsZXQgZT10PjA/dDpuLnNldHRpbmdzLnBhZ2VTaXplO3JldHVybnsuLi5uLHNldHRpbmdzOnsuLi5uLnNldHRpbmdzLHBhZ2VTaXplOmV9fX0pLFNlKFljLChuLHtwYXJ0aWFsU2V0dGluZ3M6dH0pPT57bGV0IGU9e307cmV0dXJuIE51bWJlci5pc0Zpbml0ZSh0LnBhZ2VTaXplKSYmdC5wYWdlU2l6ZT4wJiYoZS5wYWdlU2l6ZT1OdW1iZXIodC5wYWdlU2l6ZSkpLCJib29sZWFuIj09dHlwZW9mIHQuYXV0b1JlbG9hZCYmKGUucmVsb2FkRW5hYmxlZD10LmF1dG9SZWxvYWQpLE51bWJlci5pc0Zpbml0ZSh0LmF1dG9SZWxvYWRQZXJpb2RJbk1zKSYmdC5hdXRvUmVsb2FkUGVyaW9kSW5Ncz4zZTQmJihlLnJlbG9hZFBlcmlvZEluTXM9TnVtYmVyKHQuYXV0b1JlbG9hZFBlcmlvZEluTXMpKSx7Li4ubixzZXR0aW5nczp7Li4ubi5zZXR0aW5ncywuLi5lfX19KSk7ZnVuY3Rpb24gJG5lKG4sdCl7cmV0dXJuIFROZShuLHQpfXZhciBETmU9WyJpbnB1dCJdLEFOZT1bImxhYmVsIl0sSU5lPWZ1bmN0aW9uKG4pe3JldHVybntlbnRlckR1cmF0aW9uOm59fSxQTmU9WyIqIl0sUk5lPW5ldyBwZSgibWF0LWNoZWNrYm94LWRlZmF1bHQtb3B0aW9ucyIse3Byb3ZpZGVkSW46InJvb3QiLGZhY3Rvcnk6ZnVuY3Rpb24oKXtyZXR1cm57Y29sb3I6ImFjY2VudCIsY2xpY2tBY3Rpb246ImNoZWNrLWluZGV0ZXJtaW5hdGUifX19KTt2YXIgT05lPTAsZWllPXtjb2xvcjoiYWNjZW50IixjbGlja0FjdGlvbjoiY2hlY2staW5kZXRlcm1pbmF0ZSJ9LGtOZT17cHJvdmlkZTpObyx1c2VFeGlzdGluZzpKbigoKT0+eWwpLG11bHRpOiEwfSxGTmU9b2Moa28ocW8oc28oY2xhc3N7Y29uc3RydWN0b3Iobil7dGhpcy5fZWxlbWVudFJlZj1ufX0pKSkpLE5OZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgRk5le2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhLGwpe3N1cGVyKGkpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmPXIsdGhpcy5fbmdab25lPW8sdGhpcy5fYW5pbWF0aW9uTW9kZT1hLHRoaXMuX29wdGlvbnM9bCx0aGlzLmFyaWFMYWJlbD0iIix0aGlzLmFyaWFMYWJlbGxlZGJ5PW51bGwsdGhpcy5sYWJlbFBvc2l0aW9uPSJhZnRlciIsdGhpcy5uYW1lPW51bGwsdGhpcy5jaGFuZ2U9bmV3IEcsdGhpcy5pbmRldGVybWluYXRlQ2hhbmdlPW5ldyBHLHRoaXMuX29uVG91Y2hlZD0oKT0+e30sdGhpcy5fY3VycmVudEFuaW1hdGlvbkNsYXNzPSIiLHRoaXMuX2N1cnJlbnRDaGVja1N0YXRlPTAsdGhpcy5fY29udHJvbFZhbHVlQWNjZXNzb3JDaGFuZ2VGbj0oKT0+e30sdGhpcy5fY2hlY2tlZD0hMSx0aGlzLl9kaXNhYmxlZD0hMSx0aGlzLl9pbmRldGVybWluYXRlPSExLHRoaXMuX29wdGlvbnM9dGhpcy5fb3B0aW9uc3x8ZWllLHRoaXMuY29sb3I9dGhpcy5kZWZhdWx0Q29sb3I9dGhpcy5fb3B0aW9ucy5jb2xvcnx8ZWllLmNvbG9yLHRoaXMudGFiSW5kZXg9cGFyc2VJbnQocyl8fDAsdGhpcy5pZD10aGlzLl91bmlxdWVJZD1gJHtlfSR7KytPTmV9YH1nZXQgaW5wdXRJZCgpe3JldHVybmAke3RoaXMuaWR8fHRoaXMuX3VuaXF1ZUlkfS1pbnB1dGB9Z2V0IHJlcXVpcmVkKCl7cmV0dXJuIHRoaXMuX3JlcXVpcmVkfXNldCByZXF1aXJlZChlKXt0aGlzLl9yZXF1aXJlZD1SdChlKX1uZ0FmdGVyVmlld0luaXQoKXt0aGlzLl9zeW5jSW5kZXRlcm1pbmF0ZSh0aGlzLl9pbmRldGVybWluYXRlKX1nZXQgY2hlY2tlZCgpe3JldHVybiB0aGlzLl9jaGVja2VkfXNldCBjaGVja2VkKGUpe2xldCBpPVJ0KGUpO2khPXRoaXMuY2hlY2tlZCYmKHRoaXMuX2NoZWNrZWQ9aSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSl9Z2V0IGRpc2FibGVkKCl7cmV0dXJuIHRoaXMuX2Rpc2FibGVkfXNldCBkaXNhYmxlZChlKXtsZXQgaT1SdChlKTtpIT09dGhpcy5kaXNhYmxlZCYmKHRoaXMuX2Rpc2FibGVkPWksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCkpfWdldCBpbmRldGVybWluYXRlKCl7cmV0dXJuIHRoaXMuX2luZGV0ZXJtaW5hdGV9c2V0IGluZGV0ZXJtaW5hdGUoZSl7bGV0IGk9ZSE9dGhpcy5faW5kZXRlcm1pbmF0ZTt0aGlzLl9pbmRldGVybWluYXRlPVJ0KGUpLGkmJih0aGlzLl90cmFuc2l0aW9uQ2hlY2tTdGF0ZSh0aGlzLl9pbmRldGVybWluYXRlPzM6dGhpcy5jaGVja2VkPzE6MiksdGhpcy5pbmRldGVybWluYXRlQ2hhbmdlLmVtaXQodGhpcy5faW5kZXRlcm1pbmF0ZSkpLHRoaXMuX3N5bmNJbmRldGVybWluYXRlKHRoaXMuX2luZGV0ZXJtaW5hdGUpfV9pc1JpcHBsZURpc2FibGVkKCl7cmV0dXJuIHRoaXMuZGlzYWJsZVJpcHBsZXx8dGhpcy5kaXNhYmxlZH1fb25MYWJlbFRleHRDaGFuZ2UoKXt0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5kZXRlY3RDaGFuZ2VzKCl9d3JpdGVWYWx1ZShlKXt0aGlzLmNoZWNrZWQ9ISFlfXJlZ2lzdGVyT25DaGFuZ2UoZSl7dGhpcy5fY29udHJvbFZhbHVlQWNjZXNzb3JDaGFuZ2VGbj1lfXJlZ2lzdGVyT25Ub3VjaGVkKGUpe3RoaXMuX29uVG91Y2hlZD1lfXNldERpc2FibGVkU3RhdGUoZSl7dGhpcy5kaXNhYmxlZD1lfV9nZXRBcmlhQ2hlY2tlZCgpe3JldHVybiB0aGlzLmNoZWNrZWQ/InRydWUiOnRoaXMuaW5kZXRlcm1pbmF0ZT8ibWl4ZWQiOiJmYWxzZSJ9X3RyYW5zaXRpb25DaGVja1N0YXRlKGUpe2xldCBpPXRoaXMuX2N1cnJlbnRDaGVja1N0YXRlLHI9dGhpcy5fZ2V0QW5pbWF0aW9uVGFyZ2V0RWxlbWVudCgpO2lmKGkhPT1lJiZyJiYodGhpcy5fY3VycmVudEFuaW1hdGlvbkNsYXNzJiZyLmNsYXNzTGlzdC5yZW1vdmUodGhpcy5fY3VycmVudEFuaW1hdGlvbkNsYXNzKSx0aGlzLl9jdXJyZW50QW5pbWF0aW9uQ2xhc3M9dGhpcy5fZ2V0QW5pbWF0aW9uQ2xhc3NGb3JDaGVja1N0YXRlVHJhbnNpdGlvbihpLGUpLHRoaXMuX2N1cnJlbnRDaGVja1N0YXRlPWUsdGhpcy5fY3VycmVudEFuaW1hdGlvbkNsYXNzLmxlbmd0aD4wKSl7ci5jbGFzc0xpc3QuYWRkKHRoaXMuX2N1cnJlbnRBbmltYXRpb25DbGFzcyk7bGV0IG89dGhpcy5fY3VycmVudEFuaW1hdGlvbkNsYXNzO3RoaXMuX25nWm9uZS5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e3NldFRpbWVvdXQoKCk9PntyLmNsYXNzTGlzdC5yZW1vdmUobyl9LDFlMyl9KX19X2VtaXRDaGFuZ2VFdmVudCgpe3RoaXMuX2NvbnRyb2xWYWx1ZUFjY2Vzc29yQ2hhbmdlRm4odGhpcy5jaGVja2VkKSx0aGlzLmNoYW5nZS5lbWl0KHRoaXMuX2NyZWF0ZUNoYW5nZUV2ZW50KHRoaXMuY2hlY2tlZCkpLHRoaXMuX2lucHV0RWxlbWVudCYmKHRoaXMuX2lucHV0RWxlbWVudC5uYXRpdmVFbGVtZW50LmNoZWNrZWQ9dGhpcy5jaGVja2VkKX10b2dnbGUoKXt0aGlzLmNoZWNrZWQ9IXRoaXMuY2hlY2tlZCx0aGlzLl9jb250cm9sVmFsdWVBY2Nlc3NvckNoYW5nZUZuKHRoaXMuY2hlY2tlZCl9X2hhbmRsZUlucHV0Q2xpY2soKXtsZXQgZT10aGlzLl9vcHRpb25zPy5jbGlja0FjdGlvbjt0aGlzLmRpc2FibGVkfHwibm9vcCI9PT1lPyF0aGlzLmRpc2FibGVkJiYibm9vcCI9PT1lJiYodGhpcy5faW5wdXRFbGVtZW50Lm5hdGl2ZUVsZW1lbnQuY2hlY2tlZD10aGlzLmNoZWNrZWQsdGhpcy5faW5wdXRFbGVtZW50Lm5hdGl2ZUVsZW1lbnQuaW5kZXRlcm1pbmF0ZT10aGlzLmluZGV0ZXJtaW5hdGUpOih0aGlzLmluZGV0ZXJtaW5hdGUmJiJjaGVjayIhPT1lJiZQcm9taXNlLnJlc29sdmUoKS50aGVuKCgpPT57dGhpcy5faW5kZXRlcm1pbmF0ZT0hMSx0aGlzLmluZGV0ZXJtaW5hdGVDaGFuZ2UuZW1pdCh0aGlzLl9pbmRldGVybWluYXRlKX0pLHRoaXMuX2NoZWNrZWQ9IXRoaXMuX2NoZWNrZWQsdGhpcy5fdHJhbnNpdGlvbkNoZWNrU3RhdGUodGhpcy5fY2hlY2tlZD8xOjIpLHRoaXMuX2VtaXRDaGFuZ2VFdmVudCgpKX1fb25JbnRlcmFjdGlvbkV2ZW50KGUpe2Uuc3RvcFByb3BhZ2F0aW9uKCl9X29uQmx1cigpe1Byb21pc2UucmVzb2x2ZSgpLnRoZW4oKCk9Pnt0aGlzLl9vblRvdWNoZWQoKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKX0pfV9nZXRBbmltYXRpb25DbGFzc0ZvckNoZWNrU3RhdGVUcmFuc2l0aW9uKGUsaSl7aWYoIk5vb3BBbmltYXRpb25zIj09PXRoaXMuX2FuaW1hdGlvbk1vZGUpcmV0dXJuIiI7c3dpdGNoKGUpe2Nhc2UgMDppZigxPT09aSlyZXR1cm4gdGhpcy5fYW5pbWF0aW9uQ2xhc3Nlcy51bmNoZWNrZWRUb0NoZWNrZWQ7aWYoMz09aSlyZXR1cm4gdGhpcy5fY2hlY2tlZD90aGlzLl9hbmltYXRpb25DbGFzc2VzLmNoZWNrZWRUb0luZGV0ZXJtaW5hdGU6dGhpcy5fYW5pbWF0aW9uQ2xhc3Nlcy51bmNoZWNrZWRUb0luZGV0ZXJtaW5hdGU7YnJlYWs7Y2FzZSAyOnJldHVybiAxPT09aT90aGlzLl9hbmltYXRpb25DbGFzc2VzLnVuY2hlY2tlZFRvQ2hlY2tlZDp0aGlzLl9hbmltYXRpb25DbGFzc2VzLnVuY2hlY2tlZFRvSW5kZXRlcm1pbmF0ZTtjYXNlIDE6cmV0dXJuIDI9PT1pP3RoaXMuX2FuaW1hdGlvbkNsYXNzZXMuY2hlY2tlZFRvVW5jaGVja2VkOnRoaXMuX2FuaW1hdGlvbkNsYXNzZXMuY2hlY2tlZFRvSW5kZXRlcm1pbmF0ZTtjYXNlIDM6cmV0dXJuIDE9PT1pP3RoaXMuX2FuaW1hdGlvbkNsYXNzZXMuaW5kZXRlcm1pbmF0ZVRvQ2hlY2tlZDp0aGlzLl9hbmltYXRpb25DbGFzc2VzLmluZGV0ZXJtaW5hdGVUb1VuY2hlY2tlZH1yZXR1cm4iIn1fc3luY0luZGV0ZXJtaW5hdGUoZSl7bGV0IGk9dGhpcy5faW5wdXRFbGVtZW50O2kmJihpLm5hdGl2ZUVsZW1lbnQuaW5kZXRlcm1pbmF0ZT1lKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe25sKCl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYob3QoRE5lLDUpLG90KEFOZSw1KSxvdChZbyw1KSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5faW5wdXRFbGVtZW50PXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLl9sYWJlbEVsZW1lbnQ9ci5maXJzdCksTmUocj1MZSgpKSYmKGkucmlwcGxlPXIuZmlyc3QpfX0saW5wdXRzOnthcmlhTGFiZWw6WyJhcmlhLWxhYmVsIiwiYXJpYUxhYmVsIl0sYXJpYUxhYmVsbGVkYnk6WyJhcmlhLWxhYmVsbGVkYnkiLCJhcmlhTGFiZWxsZWRieSJdLGFyaWFEZXNjcmliZWRieTpbImFyaWEtZGVzY3JpYmVkYnkiLCJhcmlhRGVzY3JpYmVkYnkiXSxpZDoiaWQiLHJlcXVpcmVkOiJyZXF1aXJlZCIsbGFiZWxQb3NpdGlvbjoibGFiZWxQb3NpdGlvbiIsbmFtZToibmFtZSIsdmFsdWU6InZhbHVlIixjaGVja2VkOiJjaGVja2VkIixkaXNhYmxlZDoiZGlzYWJsZWQiLGluZGV0ZXJtaW5hdGU6ImluZGV0ZXJtaW5hdGUifSxvdXRwdXRzOntjaGFuZ2U6ImNoYW5nZSIsaW5kZXRlcm1pbmF0ZUNoYW5nZToiaW5kZXRlcm1pbmF0ZUNoYW5nZSJ9LGZlYXR1cmVzOlt0dF19KSxufSkoKSx5bD0oKCk9PntjbGFzcyBuIGV4dGVuZHMgTk5le2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhLGwpe3N1cGVyKCJtYXQtY2hlY2tib3gtIixlLGksbyxzLGEsbCksdGhpcy5fZm9jdXNNb25pdG9yPXIsdGhpcy5fYW5pbWF0aW9uQ2xhc3Nlcz17dW5jaGVja2VkVG9DaGVja2VkOiJtYXQtY2hlY2tib3gtYW5pbS11bmNoZWNrZWQtY2hlY2tlZCIsdW5jaGVja2VkVG9JbmRldGVybWluYXRlOiJtYXQtY2hlY2tib3gtYW5pbS11bmNoZWNrZWQtaW5kZXRlcm1pbmF0ZSIsY2hlY2tlZFRvVW5jaGVja2VkOiJtYXQtY2hlY2tib3gtYW5pbS1jaGVja2VkLXVuY2hlY2tlZCIsY2hlY2tlZFRvSW5kZXRlcm1pbmF0ZToibWF0LWNoZWNrYm94LWFuaW0tY2hlY2tlZC1pbmRldGVybWluYXRlIixpbmRldGVybWluYXRlVG9DaGVja2VkOiJtYXQtY2hlY2tib3gtYW5pbS1pbmRldGVybWluYXRlLWNoZWNrZWQiLGluZGV0ZXJtaW5hdGVUb1VuY2hlY2tlZDoibWF0LWNoZWNrYm94LWFuaW0taW5kZXRlcm1pbmF0ZS11bmNoZWNrZWQifX1fY3JlYXRlQ2hhbmdlRXZlbnQoZSl7bGV0IGk9bmV3IGNsYXNze307cmV0dXJuIGkuc291cmNlPXRoaXMsaS5jaGVja2VkPWUsaX1fZ2V0QW5pbWF0aW9uVGFyZ2V0RWxlbWVudCgpe3JldHVybiB0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnR9bmdBZnRlclZpZXdJbml0KCl7c3VwZXIubmdBZnRlclZpZXdJbml0KCksdGhpcy5fZm9jdXNNb25pdG9yLm1vbml0b3IodGhpcy5fZWxlbWVudFJlZiwhMCkuc3Vic2NyaWJlKGU9PntlfHx0aGlzLl9vbkJsdXIoKX0pfW5nT25EZXN0cm95KCl7dGhpcy5fZm9jdXNNb25pdG9yLnN0b3BNb25pdG9yaW5nKHRoaXMuX2VsZW1lbnRSZWYpfV9vbklucHV0Q2xpY2soZSl7ZS5zdG9wUHJvcGFnYXRpb24oKSxzdXBlci5faGFuZGxlSW5wdXRDbGljaygpfWZvY3VzKGUsaSl7ZT90aGlzLl9mb2N1c01vbml0b3IuZm9jdXNWaWEodGhpcy5faW5wdXRFbGVtZW50LGUsaSk6dGhpcy5faW5wdXRFbGVtZW50Lm5hdGl2ZUVsZW1lbnQuZm9jdXMoaSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0obm4pLE0oRnIpLE0oX3QpLHZvKCJ0YWJpbmRleCIpLE0oUGksOCksTShSTmUsOCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1jaGVja2JveCJdXSxob3N0QXR0cnM6WzEsIm1hdC1jaGVja2JveCJdLGhvc3RWYXJzOjE0LGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmKF9zKCJpZCIsaS5pZCksemUoInRhYmluZGV4IixudWxsKSgiYXJpYS1sYWJlbCIsbnVsbCkoImFyaWEtbGFiZWxsZWRieSIsbnVsbCksZXQoIm1hdC1jaGVja2JveC1pbmRldGVybWluYXRlIixpLmluZGV0ZXJtaW5hdGUpKCJtYXQtY2hlY2tib3gtY2hlY2tlZCIsaS5jaGVja2VkKSgibWF0LWNoZWNrYm94LWRpc2FibGVkIixpLmRpc2FibGVkKSgibWF0LWNoZWNrYm94LWxhYmVsLWJlZm9yZSIsImJlZm9yZSI9PWkubGFiZWxQb3NpdGlvbikoIl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIiwiTm9vcEFuaW1hdGlvbnMiPT09aS5fYW5pbWF0aW9uTW9kZSkpfSxpbnB1dHM6e2Rpc2FibGVSaXBwbGU6ImRpc2FibGVSaXBwbGUiLGNvbG9yOiJjb2xvciIsdGFiSW5kZXg6InRhYkluZGV4In0sZXhwb3J0QXM6WyJtYXRDaGVja2JveCJdLGZlYXR1cmVzOlskdChba05lXSksdHRdLG5nQ29udGVudFNlbGVjdG9yczpQTmUsZGVjbHM6MTcsdmFyczoyMSxjb25zdHM6W1sxLCJtYXQtY2hlY2tib3gtbGF5b3V0Il0sWyJsYWJlbCIsIiJdLFsxLCJtYXQtY2hlY2tib3gtaW5uZXItY29udGFpbmVyIl0sWyJ0eXBlIiwiY2hlY2tib3giLDEsIm1hdC1jaGVja2JveC1pbnB1dCIsImNkay12aXN1YWxseS1oaWRkZW4iLDMsImlkIiwicmVxdWlyZWQiLCJjaGVja2VkIiwiZGlzYWJsZWQiLCJ0YWJJbmRleCIsImNoYW5nZSIsImNsaWNrIl0sWyJpbnB1dCIsIiJdLFsibWF0UmlwcGxlIiwiIiwxLCJtYXQtY2hlY2tib3gtcmlwcGxlIiwibWF0LWZvY3VzLWluZGljYXRvciIsMywibWF0UmlwcGxlVHJpZ2dlciIsIm1hdFJpcHBsZURpc2FibGVkIiwibWF0UmlwcGxlUmFkaXVzIiwibWF0UmlwcGxlQ2VudGVyZWQiLCJtYXRSaXBwbGVBbmltYXRpb24iXSxbMSwibWF0LXJpcHBsZS1lbGVtZW50IiwibWF0LWNoZWNrYm94LXBlcnNpc3RlbnQtcmlwcGxlIl0sWzEsIm1hdC1jaGVja2JveC1mcmFtZSJdLFsxLCJtYXQtY2hlY2tib3gtYmFja2dyb3VuZCJdLFsidmVyc2lvbiIsIjEuMSIsImZvY3VzYWJsZSIsImZhbHNlIiwidmlld0JveCIsIjAgMCAyNCAyNCIsImFyaWEtaGlkZGVuIiwidHJ1ZSIsMSwibWF0LWNoZWNrYm94LWNoZWNrbWFyayJdLFsiZmlsbCIsIm5vbmUiLCJzdHJva2UiLCJ3aGl0ZSIsImQiLCJNNC4xLDEyLjcgOSwxNy42IDIwLjMsNi4zIiwxLCJtYXQtY2hlY2tib3gtY2hlY2ttYXJrLXBhdGgiXSxbMSwibWF0LWNoZWNrYm94LW1peGVkbWFyayJdLFsxLCJtYXQtY2hlY2tib3gtbGFiZWwiLDMsImNka09ic2VydmVDb250ZW50Il0sWyJjaGVja2JveExhYmVsIiwiIl0sWzIsImRpc3BsYXkiLCJub25lIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoeGkoKSxfKDAsImxhYmVsIiwwLDEpKDIsInNwYW4iLDIpKDMsImlucHV0IiwzLDQpLFAoImNoYW5nZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX29uSW50ZXJhY3Rpb25FdmVudChvKX0pKCJjbGljayIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX29uSW5wdXRDbGljayhvKX0pLHYoKSxfKDUsInNwYW4iLDUpLE8oNiwic3BhbiIsNiksdigpLE8oNywic3BhbiIsNyksXyg4LCJzcGFuIiw4KSxJbigpLF8oOSwic3ZnIiw5KSxPKDEwLCJwYXRoIiwxMCksdigpLEpzKCksTygxMSwic3BhbiIsMTEpLHYoKSgpLF8oMTIsInNwYW4iLDEyLDEzKSxQKCJjZGtPYnNlcnZlQ29udGVudCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5fb25MYWJlbFRleHRDaGFuZ2UoKX0pLF8oMTQsInNwYW4iLDE0KSxBKDE1LCJceGEwIiksdigpLFZuKDE2KSx2KCkoKSksMiZlKXtsZXQgcj0kZSgxKSxvPSRlKDEzKTt6ZSgiZm9yIixpLmlucHV0SWQpLEMoMiksZXQoIm1hdC1jaGVja2JveC1pbm5lci1jb250YWluZXItbm8tc2lkZS1tYXJnaW4iLCFvLnRleHRDb250ZW50fHwhby50ZXh0Q29udGVudC50cmltKCkpLEMoMSkseSgiaWQiLGkuaW5wdXRJZCkoInJlcXVpcmVkIixpLnJlcXVpcmVkKSgiY2hlY2tlZCIsaS5jaGVja2VkKSgiZGlzYWJsZWQiLGkuZGlzYWJsZWQpKCJ0YWJJbmRleCIsaS50YWJJbmRleCksemUoInZhbHVlIixpLnZhbHVlKSgibmFtZSIsaS5uYW1lKSgiYXJpYS1sYWJlbCIsaS5hcmlhTGFiZWx8fG51bGwpKCJhcmlhLWxhYmVsbGVkYnkiLGkuYXJpYUxhYmVsbGVkYnkpKCJhcmlhLWNoZWNrZWQiLGkuX2dldEFyaWFDaGVja2VkKCkpKCJhcmlhLWRlc2NyaWJlZGJ5IixpLmFyaWFEZXNjcmliZWRieSksQygyKSx5KCJtYXRSaXBwbGVUcmlnZ2VyIixyKSgibWF0UmlwcGxlRGlzYWJsZWQiLGkuX2lzUmlwcGxlRGlzYWJsZWQoKSkoIm1hdFJpcHBsZVJhZGl1cyIsMjApKCJtYXRSaXBwbGVDZW50ZXJlZCIsITApKCJtYXRSaXBwbGVBbmltYXRpb24iLE9uKDE5LElOZSwiTm9vcEFuaW1hdGlvbnMiPT09aS5fYW5pbWF0aW9uTW9kZT8wOjE1MCkpfX0sZGVwZW5kZW5jaWVzOltZbyx3aF0sc3R5bGVzOlsnQGtleWZyYW1lcyBtYXQtY2hlY2tib3gtZmFkZS1pbi1iYWNrZ3JvdW5kezAle29wYWNpdHk6MH01MCV7b3BhY2l0eToxfX1Aa2V5ZnJhbWVzIG1hdC1jaGVja2JveC1mYWRlLW91dC1iYWNrZ3JvdW5kezAlLDUwJXtvcGFjaXR5OjF9MTAwJXtvcGFjaXR5OjB9fUBrZXlmcmFtZXMgbWF0LWNoZWNrYm94LXVuY2hlY2tlZC1jaGVja2VkLWNoZWNrbWFyay1wYXRoezAlLDUwJXtzdHJva2UtZGFzaG9mZnNldDoyMi45MTAyNTl9NTAle2FuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246Y3ViaWMtYmV6aWVyKDAsIDAsIDAuMiwgMC4xKX0xMDAle3N0cm9rZS1kYXNob2Zmc2V0OjB9fUBrZXlmcmFtZXMgbWF0LWNoZWNrYm94LXVuY2hlY2tlZC1pbmRldGVybWluYXRlLW1peGVkbWFya3swJSw2OC4yJXt0cmFuc2Zvcm06c2NhbGVYKDApfTY4LjIle2FuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246Y3ViaWMtYmV6aWVyKDAsIDAsIDAsIDEpfTEwMCV7dHJhbnNmb3JtOnNjYWxlWCgxKX19QGtleWZyYW1lcyBtYXQtY2hlY2tib3gtY2hlY2tlZC11bmNoZWNrZWQtY2hlY2ttYXJrLXBhdGh7ZnJvbXthbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOmN1YmljLWJlemllcigwLjQsIDAsIDEsIDEpO3N0cm9rZS1kYXNob2Zmc2V0OjB9dG97c3Ryb2tlLWRhc2hvZmZzZXQ6LTIyLjkxMDI1OX19QGtleWZyYW1lcyBtYXQtY2hlY2tib3gtY2hlY2tlZC1pbmRldGVybWluYXRlLWNoZWNrbWFya3tmcm9te2FuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246Y3ViaWMtYmV6aWVyKDAsIDAsIDAuMiwgMC4xKTtvcGFjaXR5OjE7dHJhbnNmb3JtOnJvdGF0ZSgwZGVnKX10b3tvcGFjaXR5OjA7dHJhbnNmb3JtOnJvdGF0ZSg0NWRlZyl9fUBrZXlmcmFtZXMgbWF0LWNoZWNrYm94LWluZGV0ZXJtaW5hdGUtY2hlY2tlZC1jaGVja21hcmt7ZnJvbXthbmltYXRpb24tdGltaW5nLWZ1bmN0aW9uOmN1YmljLWJlemllcigwLjE0LCAwLCAwLCAxKTtvcGFjaXR5OjA7dHJhbnNmb3JtOnJvdGF0ZSg0NWRlZyl9dG97b3BhY2l0eToxO3RyYW5zZm9ybTpyb3RhdGUoMzYwZGVnKX19QGtleWZyYW1lcyBtYXQtY2hlY2tib3gtY2hlY2tlZC1pbmRldGVybWluYXRlLW1peGVkbWFya3tmcm9te2FuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246Y3ViaWMtYmV6aWVyKDAsIDAsIDAuMiwgMC4xKTtvcGFjaXR5OjA7dHJhbnNmb3JtOnJvdGF0ZSgtNDVkZWcpfXRve29wYWNpdHk6MTt0cmFuc2Zvcm06cm90YXRlKDBkZWcpfX1Aa2V5ZnJhbWVzIG1hdC1jaGVja2JveC1pbmRldGVybWluYXRlLWNoZWNrZWQtbWl4ZWRtYXJre2Zyb217YW5pbWF0aW9uLXRpbWluZy1mdW5jdGlvbjpjdWJpYy1iZXppZXIoMC4xNCwgMCwgMCwgMSk7b3BhY2l0eToxO3RyYW5zZm9ybTpyb3RhdGUoMGRlZyl9dG97b3BhY2l0eTowO3RyYW5zZm9ybTpyb3RhdGUoMzE1ZGVnKX19QGtleWZyYW1lcyBtYXQtY2hlY2tib3gtaW5kZXRlcm1pbmF0ZS11bmNoZWNrZWQtbWl4ZWRtYXJrezAle2FuaW1hdGlvbi10aW1pbmctZnVuY3Rpb246bGluZWFyO29wYWNpdHk6MTt0cmFuc2Zvcm06c2NhbGVYKDEpfTMyLjglLDEwMCV7b3BhY2l0eTowO3RyYW5zZm9ybTpzY2FsZVgoMCl9fS5tYXQtY2hlY2tib3gtYmFja2dyb3VuZCwubWF0LWNoZWNrYm94LWZyYW1le3RvcDowO2xlZnQ6MDtyaWdodDowO2JvdHRvbTowO3Bvc2l0aW9uOmFic29sdXRlO2JvcmRlci1yYWRpdXM6MnB4O2JveC1zaXppbmc6Ym9yZGVyLWJveDtwb2ludGVyLWV2ZW50czpub25lfS5tYXQtY2hlY2tib3h7ZGlzcGxheTppbmxpbmUtYmxvY2s7dHJhbnNpdGlvbjpiYWNrZ3JvdW5kIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpLGJveC1zaGFkb3cgMjgwbXMgY3ViaWMtYmV6aWVyKDAuNCwgMCwgMC4yLCAxKTtjdXJzb3I6cG9pbnRlcjstd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6cmdiYSgwLDAsMCwwKTtwb3NpdGlvbjpyZWxhdGl2ZX0ubWF0LWNoZWNrYm94Ll9tYXQtYW5pbWF0aW9uLW5vb3BhYmxle3RyYW5zaXRpb246bm9uZSAhaW1wb3J0YW50O2FuaW1hdGlvbjpub25lICFpbXBvcnRhbnR9Lm1hdC1jaGVja2JveCAubWF0LXJpcHBsZS1lbGVtZW50Om5vdCgubWF0LWNoZWNrYm94LXBlcnNpc3RlbnQtcmlwcGxlKXtvcGFjaXR5Oi4xNn0ubWF0LWNoZWNrYm94IC5tYXQtY2hlY2tib3gtcmlwcGxle3Bvc2l0aW9uOmFic29sdXRlO2xlZnQ6Y2FsYyg1MCUgLSAyMHB4KTt0b3A6Y2FsYyg1MCUgLSAyMHB4KTtoZWlnaHQ6NDBweDt3aWR0aDo0MHB4O3otaW5kZXg6MTtwb2ludGVyLWV2ZW50czpub25lfS5tYXQtY2hlY2tib3gtbGF5b3V0ey13ZWJraXQtdXNlci1zZWxlY3Q6bm9uZTt1c2VyLXNlbGVjdDpub25lO2N1cnNvcjppbmhlcml0O2FsaWduLWl0ZW1zOmJhc2VsaW5lO3ZlcnRpY2FsLWFsaWduOm1pZGRsZTtkaXNwbGF5OmlubGluZS1mbGV4O3doaXRlLXNwYWNlOm5vd3JhcH0ubWF0LWNoZWNrYm94LWxhYmVsey13ZWJraXQtdXNlci1zZWxlY3Q6YXV0bzt1c2VyLXNlbGVjdDphdXRvfS5tYXQtY2hlY2tib3gtaW5uZXItY29udGFpbmVye2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxNnB4O2xpbmUtaGVpZ2h0OjA7bWFyZ2luOmF1dG87bWFyZ2luLXJpZ2h0OjhweDtvcmRlcjowO3Bvc2l0aW9uOnJlbGF0aXZlO3ZlcnRpY2FsLWFsaWduOm1pZGRsZTt3aGl0ZS1zcGFjZTpub3dyYXA7d2lkdGg6MTZweDtmbGV4LXNocmluazowfVtkaXI9cnRsXSAubWF0LWNoZWNrYm94LWlubmVyLWNvbnRhaW5lcnttYXJnaW4tbGVmdDo4cHg7bWFyZ2luLXJpZ2h0OmF1dG99Lm1hdC1jaGVja2JveC1pbm5lci1jb250YWluZXItbm8tc2lkZS1tYXJnaW57bWFyZ2luLWxlZnQ6MDttYXJnaW4tcmlnaHQ6MH0ubWF0LWNoZWNrYm94LWZyYW1le2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwwKTt0cmFuc2l0aW9uOmJvcmRlci1jb2xvciA5MG1zIGN1YmljLWJlemllcigwLCAwLCAwLjIsIDAuMSk7Ym9yZGVyLXdpZHRoOjJweDtib3JkZXItc3R5bGU6c29saWR9Ll9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIC5tYXQtY2hlY2tib3gtZnJhbWV7dHJhbnNpdGlvbjpub25lfS5tYXQtY2hlY2tib3gtYmFja2dyb3VuZHthbGlnbi1pdGVtczpjZW50ZXI7ZGlzcGxheTppbmxpbmUtZmxleDtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyO3RyYW5zaXRpb246YmFja2dyb3VuZC1jb2xvciA5MG1zIGN1YmljLWJlemllcigwLCAwLCAwLjIsIDAuMSksb3BhY2l0eSA5MG1zIGN1YmljLWJlemllcigwLCAwLCAwLjIsIDAuMSk7LXdlYmtpdC1wcmludC1jb2xvci1hZGp1c3Q6ZXhhY3Q7Y29sb3ItYWRqdXN0OmV4YWN0fS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LWNoZWNrYm94LWJhY2tncm91bmR7dHJhbnNpdGlvbjpub25lfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1jaGVja2JveCAubWF0LWNoZWNrYm94LWJhY2tncm91bmR7YmFja2dyb3VuZDpub25lfS5tYXQtY2hlY2tib3gtcGVyc2lzdGVudC1yaXBwbGV7ZGlzcGxheTpibG9jazt3aWR0aDoxMDAlO2hlaWdodDoxMDAlO3RyYW5zZm9ybTpub25lfS5tYXQtY2hlY2tib3gtaW5uZXItY29udGFpbmVyOmhvdmVyIC5tYXQtY2hlY2tib3gtcGVyc2lzdGVudC1yaXBwbGV7b3BhY2l0eTouMDR9Lm1hdC1jaGVja2JveC5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LWNoZWNrYm94LXBlcnNpc3RlbnQtcmlwcGxle29wYWNpdHk6LjEyfS5tYXQtY2hlY2tib3gtcGVyc2lzdGVudC1yaXBwbGUsLm1hdC1jaGVja2JveC5tYXQtY2hlY2tib3gtZGlzYWJsZWQgLm1hdC1jaGVja2JveC1pbm5lci1jb250YWluZXI6aG92ZXIgLm1hdC1jaGVja2JveC1wZXJzaXN0ZW50LXJpcHBsZXtvcGFjaXR5OjB9QG1lZGlhKGhvdmVyOiBub25lKXsubWF0LWNoZWNrYm94LWlubmVyLWNvbnRhaW5lcjpob3ZlciAubWF0LWNoZWNrYm94LXBlcnNpc3RlbnQtcmlwcGxle2Rpc3BsYXk6bm9uZX19Lm1hdC1jaGVja2JveC1jaGVja21hcmt7dG9wOjA7bGVmdDowO3JpZ2h0OjA7Ym90dG9tOjA7cG9zaXRpb246YWJzb2x1dGU7d2lkdGg6MTAwJX0ubWF0LWNoZWNrYm94LWNoZWNrbWFyay1wYXRoe3N0cm9rZS1kYXNob2Zmc2V0OjIyLjkxMDI1OTtzdHJva2UtZGFzaGFycmF5OjIyLjkxMDI1OTtzdHJva2Utd2lkdGg6Mi4xMzMzMzMzMzMzcHh9LmNkay1oaWdoLWNvbnRyYXN0LWJsYWNrLW9uLXdoaXRlIC5tYXQtY2hlY2tib3gtY2hlY2ttYXJrLXBhdGh7c3Ryb2tlOiMwMDAgIWltcG9ydGFudH0ubWF0LWNoZWNrYm94LW1peGVkbWFya3t3aWR0aDpjYWxjKDEwMCUgLSA2cHgpO2hlaWdodDoycHg7b3BhY2l0eTowO3RyYW5zZm9ybTpzY2FsZVgoMCkgcm90YXRlKDBkZWcpO2JvcmRlci1yYWRpdXM6MnB4fS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1jaGVja2JveC1taXhlZG1hcmt7aGVpZ2h0OjA7Ym9yZGVyLXRvcDpzb2xpZCAycHg7bWFyZ2luLXRvcDoycHh9Lm1hdC1jaGVja2JveC1sYWJlbC1iZWZvcmUgLm1hdC1jaGVja2JveC1pbm5lci1jb250YWluZXJ7b3JkZXI6MTttYXJnaW4tbGVmdDo4cHg7bWFyZ2luLXJpZ2h0OmF1dG99W2Rpcj1ydGxdIC5tYXQtY2hlY2tib3gtbGFiZWwtYmVmb3JlIC5tYXQtY2hlY2tib3gtaW5uZXItY29udGFpbmVye21hcmdpbi1sZWZ0OmF1dG87bWFyZ2luLXJpZ2h0OjhweH0ubWF0LWNoZWNrYm94LWNoZWNrZWQgLm1hdC1jaGVja2JveC1jaGVja21hcmt7b3BhY2l0eToxfS5tYXQtY2hlY2tib3gtY2hlY2tlZCAubWF0LWNoZWNrYm94LWNoZWNrbWFyay1wYXRoe3N0cm9rZS1kYXNob2Zmc2V0OjB9Lm1hdC1jaGVja2JveC1jaGVja2VkIC5tYXQtY2hlY2tib3gtbWl4ZWRtYXJre3RyYW5zZm9ybTpzY2FsZVgoMSkgcm90YXRlKC00NWRlZyl9Lm1hdC1jaGVja2JveC1pbmRldGVybWluYXRlIC5tYXQtY2hlY2tib3gtY2hlY2ttYXJre29wYWNpdHk6MDt0cmFuc2Zvcm06cm90YXRlKDQ1ZGVnKX0ubWF0LWNoZWNrYm94LWluZGV0ZXJtaW5hdGUgLm1hdC1jaGVja2JveC1jaGVja21hcmstcGF0aHtzdHJva2UtZGFzaG9mZnNldDowfS5tYXQtY2hlY2tib3gtaW5kZXRlcm1pbmF0ZSAubWF0LWNoZWNrYm94LW1peGVkbWFya3tvcGFjaXR5OjE7dHJhbnNmb3JtOnNjYWxlWCgxKSByb3RhdGUoMGRlZyl9Lm1hdC1jaGVja2JveC11bmNoZWNrZWQgLm1hdC1jaGVja2JveC1iYWNrZ3JvdW5ke2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwwKX0ubWF0LWNoZWNrYm94LWRpc2FibGVke2N1cnNvcjpkZWZhdWx0fS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1jaGVja2JveC1kaXNhYmxlZHtvcGFjaXR5Oi41fS5tYXQtY2hlY2tib3gtYW5pbS11bmNoZWNrZWQtY2hlY2tlZCAubWF0LWNoZWNrYm94LWJhY2tncm91bmR7YW5pbWF0aW9uOjE4MG1zIGxpbmVhciAwbXMgbWF0LWNoZWNrYm94LWZhZGUtaW4tYmFja2dyb3VuZH0ubWF0LWNoZWNrYm94LWFuaW0tdW5jaGVja2VkLWNoZWNrZWQgLm1hdC1jaGVja2JveC1jaGVja21hcmstcGF0aHthbmltYXRpb246MTgwbXMgbGluZWFyIDBtcyBtYXQtY2hlY2tib3gtdW5jaGVja2VkLWNoZWNrZWQtY2hlY2ttYXJrLXBhdGh9Lm1hdC1jaGVja2JveC1hbmltLXVuY2hlY2tlZC1pbmRldGVybWluYXRlIC5tYXQtY2hlY2tib3gtYmFja2dyb3VuZHthbmltYXRpb246MTgwbXMgbGluZWFyIDBtcyBtYXQtY2hlY2tib3gtZmFkZS1pbi1iYWNrZ3JvdW5kfS5tYXQtY2hlY2tib3gtYW5pbS11bmNoZWNrZWQtaW5kZXRlcm1pbmF0ZSAubWF0LWNoZWNrYm94LW1peGVkbWFya3thbmltYXRpb246OTBtcyBsaW5lYXIgMG1zIG1hdC1jaGVja2JveC11bmNoZWNrZWQtaW5kZXRlcm1pbmF0ZS1taXhlZG1hcmt9Lm1hdC1jaGVja2JveC1hbmltLWNoZWNrZWQtdW5jaGVja2VkIC5tYXQtY2hlY2tib3gtYmFja2dyb3VuZHthbmltYXRpb246MTgwbXMgbGluZWFyIDBtcyBtYXQtY2hlY2tib3gtZmFkZS1vdXQtYmFja2dyb3VuZH0ubWF0LWNoZWNrYm94LWFuaW0tY2hlY2tlZC11bmNoZWNrZWQgLm1hdC1jaGVja2JveC1jaGVja21hcmstcGF0aHthbmltYXRpb246OTBtcyBsaW5lYXIgMG1zIG1hdC1jaGVja2JveC1jaGVja2VkLXVuY2hlY2tlZC1jaGVja21hcmstcGF0aH0ubWF0LWNoZWNrYm94LWFuaW0tY2hlY2tlZC1pbmRldGVybWluYXRlIC5tYXQtY2hlY2tib3gtY2hlY2ttYXJre2FuaW1hdGlvbjo5MG1zIGxpbmVhciAwbXMgbWF0LWNoZWNrYm94LWNoZWNrZWQtaW5kZXRlcm1pbmF0ZS1jaGVja21hcmt9Lm1hdC1jaGVja2JveC1hbmltLWNoZWNrZWQtaW5kZXRlcm1pbmF0ZSAubWF0LWNoZWNrYm94LW1peGVkbWFya3thbmltYXRpb246OTBtcyBsaW5lYXIgMG1zIG1hdC1jaGVja2JveC1jaGVja2VkLWluZGV0ZXJtaW5hdGUtbWl4ZWRtYXJrfS5tYXQtY2hlY2tib3gtYW5pbS1pbmRldGVybWluYXRlLWNoZWNrZWQgLm1hdC1jaGVja2JveC1jaGVja21hcmt7YW5pbWF0aW9uOjUwMG1zIGxpbmVhciAwbXMgbWF0LWNoZWNrYm94LWluZGV0ZXJtaW5hdGUtY2hlY2tlZC1jaGVja21hcmt9Lm1hdC1jaGVja2JveC1hbmltLWluZGV0ZXJtaW5hdGUtY2hlY2tlZCAubWF0LWNoZWNrYm94LW1peGVkbWFya3thbmltYXRpb246NTAwbXMgbGluZWFyIDBtcyBtYXQtY2hlY2tib3gtaW5kZXRlcm1pbmF0ZS1jaGVja2VkLW1peGVkbWFya30ubWF0LWNoZWNrYm94LWFuaW0taW5kZXRlcm1pbmF0ZS11bmNoZWNrZWQgLm1hdC1jaGVja2JveC1iYWNrZ3JvdW5ke2FuaW1hdGlvbjoxODBtcyBsaW5lYXIgMG1zIG1hdC1jaGVja2JveC1mYWRlLW91dC1iYWNrZ3JvdW5kfS5tYXQtY2hlY2tib3gtYW5pbS1pbmRldGVybWluYXRlLXVuY2hlY2tlZCAubWF0LWNoZWNrYm94LW1peGVkbWFya3thbmltYXRpb246MzAwbXMgbGluZWFyIDBtcyBtYXQtY2hlY2tib3gtaW5kZXRlcm1pbmF0ZS11bmNoZWNrZWQtbWl4ZWRtYXJrfS5tYXQtY2hlY2tib3gtaW5wdXR7Ym90dG9tOjA7bGVmdDo1MCV9Lm1hdC1jaGVja2JveC1pbnB1dDpmb2N1c34ubWF0LWZvY3VzLWluZGljYXRvcjo6YmVmb3Jle2NvbnRlbnQ6IiJ9J10sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksTE5lPXtwcm92aWRlOkxvLHVzZUV4aXN0aW5nOkpuKCgpPT5CTmUpLG11bHRpOiEwfSxCTmU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGd3e31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1jaGVja2JveCIsInJlcXVpcmVkIiwiIiwiZm9ybUNvbnRyb2xOYW1lIiwiIl0sWyJtYXQtY2hlY2tib3giLCJyZXF1aXJlZCIsIiIsImZvcm1Db250cm9sIiwiIl0sWyJtYXQtY2hlY2tib3giLCJyZXF1aXJlZCIsIiIsIm5nTW9kZWwiLCIiXV0sZmVhdHVyZXM6WyR0KFtMTmVdKSx0dF19KSxufSkoKSx0aWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLExzPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltfbCxsbixvZCx0aWUsbG4sdGllXX0pLG59KSgpLGlpZT1sYSh7cGFzc2l2ZTohMH0pLHJpZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSl7dGhpcy5fcGxhdGZvcm09ZSx0aGlzLl9uZ1pvbmU9aSx0aGlzLl9tb25pdG9yZWRFbGVtZW50cz1uZXcgTWFwfW1vbml0b3IoZSl7aWYoIXRoaXMuX3BsYXRmb3JtLmlzQnJvd3NlcilyZXR1cm4gZW87bGV0IGk9TGEoZSkscj10aGlzLl9tb25pdG9yZWRFbGVtZW50cy5nZXQoaSk7aWYocilyZXR1cm4gci5zdWJqZWN0O2xldCBvPW5ldyBrZSxzPSJjZGstdGV4dC1maWVsZC1hdXRvZmlsbGVkIixhPWw9PnsiY2RrLXRleHQtZmllbGQtYXV0b2ZpbGwtc3RhcnQiIT09bC5hbmltYXRpb25OYW1lfHxpLmNsYXNzTGlzdC5jb250YWlucyhzKT8iY2RrLXRleHQtZmllbGQtYXV0b2ZpbGwtZW5kIj09PWwuYW5pbWF0aW9uTmFtZSYmaS5jbGFzc0xpc3QuY29udGFpbnMocykmJihpLmNsYXNzTGlzdC5yZW1vdmUocyksdGhpcy5fbmdab25lLnJ1bigoKT0+by5uZXh0KHt0YXJnZXQ6bC50YXJnZXQsaXNBdXRvZmlsbGVkOiExfSkpKTooaS5jbGFzc0xpc3QuYWRkKHMpLHRoaXMuX25nWm9uZS5ydW4oKCk9Pm8ubmV4dCh7dGFyZ2V0OmwudGFyZ2V0LGlzQXV0b2ZpbGxlZDohMH0pKSl9O3JldHVybiB0aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntpLmFkZEV2ZW50TGlzdGVuZXIoImFuaW1hdGlvbnN0YXJ0IixhLGlpZSksaS5jbGFzc0xpc3QuYWRkKCJjZGstdGV4dC1maWVsZC1hdXRvZmlsbC1tb25pdG9yZWQiKX0pLHRoaXMuX21vbml0b3JlZEVsZW1lbnRzLnNldChpLHtzdWJqZWN0Om8sdW5saXN0ZW46KCk9PntpLnJlbW92ZUV2ZW50TGlzdGVuZXIoImFuaW1hdGlvbnN0YXJ0IixhLGlpZSl9fSksb31zdG9wTW9uaXRvcmluZyhlKXtsZXQgaT1MYShlKSxyPXRoaXMuX21vbml0b3JlZEVsZW1lbnRzLmdldChpKTtyJiYoci51bmxpc3RlbigpLHIuc3ViamVjdC5jb21wbGV0ZSgpLGkuY2xhc3NMaXN0LnJlbW92ZSgiY2RrLXRleHQtZmllbGQtYXV0b2ZpbGwtbW9uaXRvcmVkIiksaS5jbGFzc0xpc3QucmVtb3ZlKCJjZGstdGV4dC1maWVsZC1hdXRvZmlsbGVkIiksdGhpcy5fbW9uaXRvcmVkRWxlbWVudHMuZGVsZXRlKGkpKX1uZ09uRGVzdHJveSgpe3RoaXMuX21vbml0b3JlZEVsZW1lbnRzLmZvckVhY2goKGUsaSk9PnRoaXMuc3RvcE1vbml0b3JpbmcoaSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKG9pKSxqKF90KSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjLHByb3ZpZGVkSW46InJvb3QifSksbn0pKCksWkg9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLEhOZT1uZXcgcGUoIk1BVF9JTlBVVF9WQUxVRV9BQ0NFU1NPUiIpLFVOZT1bImJ1dHRvbiIsImNoZWNrYm94IiwiZmlsZSIsImhpZGRlbiIsImltYWdlIiwicmFkaW8iLCJyYW5nZSIsInJlc2V0Iiwic3VibWl0Il0sek5lPTAsak5lPUR2KGNsYXNze2NvbnN0cnVjdG9yKG4sdCxlLGkpe3RoaXMuX2RlZmF1bHRFcnJvclN0YXRlTWF0Y2hlcj1uLHRoaXMuX3BhcmVudEZvcm09dCx0aGlzLl9wYXJlbnRGb3JtR3JvdXA9ZSx0aGlzLm5nQ29udHJvbD1pLHRoaXMuc3RhdGVDaGFuZ2VzPW5ldyBrZX19KSxVaD0oKCk9PntjbGFzcyBuIGV4dGVuZHMgak5le2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhLGwsYyx1LGQpe3N1cGVyKGEsbyxzLHIpLHRoaXMuX2VsZW1lbnRSZWY9ZSx0aGlzLl9wbGF0Zm9ybT1pLHRoaXMuX2F1dG9maWxsTW9uaXRvcj1jLHRoaXMuX2Zvcm1GaWVsZD1kLHRoaXMuX3VpZD0ibWF0LWlucHV0LSIrek5lKyssdGhpcy5mb2N1c2VkPSExLHRoaXMuc3RhdGVDaGFuZ2VzPW5ldyBrZSx0aGlzLmNvbnRyb2xUeXBlPSJtYXQtaW5wdXQiLHRoaXMuYXV0b2ZpbGxlZD0hMSx0aGlzLl9kaXNhYmxlZD0hMSx0aGlzLl90eXBlPSJ0ZXh0Iix0aGlzLl9yZWFkb25seT0hMSx0aGlzLl9uZXZlckVtcHR5SW5wdXRUeXBlcz1bImRhdGUiLCJkYXRldGltZSIsImRhdGV0aW1lLWxvY2FsIiwibW9udGgiLCJ0aW1lIiwid2VlayJdLmZpbHRlcihmPT5wSCgpLmhhcyhmKSksdGhpcy5faU9TS2V5dXBMaXN0ZW5lcj1mPT57bGV0IG09Zi50YXJnZXQ7IW0udmFsdWUmJjA9PT1tLnNlbGVjdGlvblN0YXJ0JiYwPT09bS5zZWxlY3Rpb25FbmQmJihtLnNldFNlbGVjdGlvblJhbmdlKDEsMSksbS5zZXRTZWxlY3Rpb25SYW5nZSgwLDApKX07bGV0IHA9dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LGg9cC5ub2RlTmFtZS50b0xvd2VyQ2FzZSgpO3RoaXMuX2lucHV0VmFsdWVBY2Nlc3Nvcj1sfHxwLHRoaXMuX3ByZXZpb3VzTmF0aXZlVmFsdWU9dGhpcy52YWx1ZSx0aGlzLmlkPXRoaXMuaWQsaS5JT1MmJnUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntlLm5hdGl2ZUVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigia2V5dXAiLHRoaXMuX2lPU0tleXVwTGlzdGVuZXIpfSksdGhpcy5faXNTZXJ2ZXI9IXRoaXMuX3BsYXRmb3JtLmlzQnJvd3Nlcix0aGlzLl9pc05hdGl2ZVNlbGVjdD0ic2VsZWN0Ij09PWgsdGhpcy5faXNUZXh0YXJlYT0idGV4dGFyZWEiPT09aCx0aGlzLl9pc0luRm9ybUZpZWxkPSEhZCx0aGlzLl9pc05hdGl2ZVNlbGVjdCYmKHRoaXMuY29udHJvbFR5cGU9cC5tdWx0aXBsZT8ibWF0LW5hdGl2ZS1zZWxlY3QtbXVsdGlwbGUiOiJtYXQtbmF0aXZlLXNlbGVjdCIpfWdldCBkaXNhYmxlZCgpe3JldHVybiB0aGlzLm5nQ29udHJvbCYmbnVsbCE9PXRoaXMubmdDb250cm9sLmRpc2FibGVkP3RoaXMubmdDb250cm9sLmRpc2FibGVkOnRoaXMuX2Rpc2FibGVkfXNldCBkaXNhYmxlZChlKXt0aGlzLl9kaXNhYmxlZD1SdChlKSx0aGlzLmZvY3VzZWQmJih0aGlzLmZvY3VzZWQ9ITEsdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpKX1nZXQgaWQoKXtyZXR1cm4gdGhpcy5faWR9c2V0IGlkKGUpe3RoaXMuX2lkPWV8fHRoaXMuX3VpZH1nZXQgcmVxdWlyZWQoKXtyZXR1cm4gdGhpcy5fcmVxdWlyZWQ/P3RoaXMubmdDb250cm9sPy5jb250cm9sPy5oYXNWYWxpZGF0b3IoRm8ucmVxdWlyZWQpPz8hMX1zZXQgcmVxdWlyZWQoZSl7dGhpcy5fcmVxdWlyZWQ9UnQoZSl9Z2V0IHR5cGUoKXtyZXR1cm4gdGhpcy5fdHlwZX1zZXQgdHlwZShlKXt0aGlzLl90eXBlPWV8fCJ0ZXh0Iix0aGlzLl92YWxpZGF0ZVR5cGUoKSwhdGhpcy5faXNUZXh0YXJlYSYmcEgoKS5oYXModGhpcy5fdHlwZSkmJih0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQudHlwZT10aGlzLl90eXBlKX1nZXQgdmFsdWUoKXtyZXR1cm4gdGhpcy5faW5wdXRWYWx1ZUFjY2Vzc29yLnZhbHVlfXNldCB2YWx1ZShlKXtlIT09dGhpcy52YWx1ZSYmKHRoaXMuX2lucHV0VmFsdWVBY2Nlc3Nvci52YWx1ZT1lLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKSl9Z2V0IHJlYWRvbmx5KCl7cmV0dXJuIHRoaXMuX3JlYWRvbmx5fXNldCByZWFkb25seShlKXt0aGlzLl9yZWFkb25seT1SdChlKX1uZ0FmdGVyVmlld0luaXQoKXt0aGlzLl9wbGF0Zm9ybS5pc0Jyb3dzZXImJnRoaXMuX2F1dG9maWxsTW9uaXRvci5tb25pdG9yKHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudCkuc3Vic2NyaWJlKGU9Pnt0aGlzLmF1dG9maWxsZWQ9ZS5pc0F1dG9maWxsZWQsdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpfSl9bmdPbkNoYW5nZXMoKXt0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCl9bmdPbkRlc3Ryb3koKXt0aGlzLnN0YXRlQ2hhbmdlcy5jb21wbGV0ZSgpLHRoaXMuX3BsYXRmb3JtLmlzQnJvd3NlciYmdGhpcy5fYXV0b2ZpbGxNb25pdG9yLnN0b3BNb25pdG9yaW5nKHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudCksdGhpcy5fcGxhdGZvcm0uSU9TJiZ0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcigia2V5dXAiLHRoaXMuX2lPU0tleXVwTGlzdGVuZXIpfW5nRG9DaGVjaygpe3RoaXMubmdDb250cm9sJiZ0aGlzLnVwZGF0ZUVycm9yU3RhdGUoKSx0aGlzLl9kaXJ0eUNoZWNrTmF0aXZlVmFsdWUoKSx0aGlzLl9kaXJ0eUNoZWNrUGxhY2Vob2xkZXIoKX1mb2N1cyhlKXt0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuZm9jdXMoZSl9X2ZvY3VzQ2hhbmdlZChlKXtlIT09dGhpcy5mb2N1c2VkJiYodGhpcy5mb2N1c2VkPWUsdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpKX1fb25JbnB1dCgpe31fZGlydHlDaGVja1BsYWNlaG9sZGVyKCl7bGV0IGU9dGhpcy5fZm9ybUZpZWxkLGk9ZSYmImxlZ2FjeSI9PT1lLmFwcGVhcmFuY2UmJiFlLl9oYXNMYWJlbD8uKCk/bnVsbDp0aGlzLnBsYWNlaG9sZGVyO2lmKGkhPT10aGlzLl9wcmV2aW91c1BsYWNlaG9sZGVyKXtsZXQgcj10aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQ7dGhpcy5fcHJldmlvdXNQbGFjZWhvbGRlcj1pLGk/ci5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIixpKTpyLnJlbW92ZUF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiKX19X2RpcnR5Q2hlY2tOYXRpdmVWYWx1ZSgpe2xldCBlPXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC52YWx1ZTt0aGlzLl9wcmV2aW91c05hdGl2ZVZhbHVlIT09ZSYmKHRoaXMuX3ByZXZpb3VzTmF0aXZlVmFsdWU9ZSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCkpfV92YWxpZGF0ZVR5cGUoKXtVTmUuaW5kZXhPZih0aGlzLl90eXBlKX1faXNOZXZlckVtcHR5KCl7cmV0dXJuIHRoaXMuX25ldmVyRW1wdHlJbnB1dFR5cGVzLmluZGV4T2YodGhpcy5fdHlwZSk+LTF9X2lzQmFkSW5wdXQoKXtsZXQgZT10aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQudmFsaWRpdHk7cmV0dXJuIGUmJmUuYmFkSW5wdXR9Z2V0IGVtcHR5KCl7cmV0dXJuISh0aGlzLl9pc05ldmVyRW1wdHkoKXx8dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LnZhbHVlfHx0aGlzLl9pc0JhZElucHV0KCl8fHRoaXMuYXV0b2ZpbGxlZCl9Z2V0IHNob3VsZExhYmVsRmxvYXQoKXtpZih0aGlzLl9pc05hdGl2ZVNlbGVjdCl7bGV0IGU9dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LGk9ZS5vcHRpb25zWzBdO3JldHVybiB0aGlzLmZvY3VzZWR8fGUubXVsdGlwbGV8fCF0aGlzLmVtcHR5fHwhIShlLnNlbGVjdGVkSW5kZXg+LTEmJmkmJmkubGFiZWwpfXJldHVybiB0aGlzLmZvY3VzZWR8fCF0aGlzLmVtcHR5fXNldERlc2NyaWJlZEJ5SWRzKGUpe2UubGVuZ3RoP3RoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5zZXRBdHRyaWJ1dGUoImFyaWEtZGVzY3JpYmVkYnkiLGUuam9pbigiICIpKTp0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQucmVtb3ZlQXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5Iil9b25Db250YWluZXJDbGljaygpe3RoaXMuZm9jdXNlZHx8dGhpcy5mb2N1cygpfV9pc0lubGluZVNlbGVjdCgpe2xldCBlPXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudDtyZXR1cm4gdGhpcy5faXNOYXRpdmVTZWxlY3QmJihlLm11bHRpcGxlfHxlLnNpemU+MSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0ob2kpLE0oTnMsMTApLE0oTGgsOCksTShWaCw4KSxNKGNkKSxNKEhOZSwxMCksTShyaWUpLE0oX3QpLE0oc2csOCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyJpbnB1dCIsIm1hdElucHV0IiwiIl0sWyJ0ZXh0YXJlYSIsIm1hdElucHV0IiwiIl0sWyJzZWxlY3QiLCJtYXROYXRpdmVDb250cm9sIiwiIl0sWyJpbnB1dCIsIm1hdE5hdGl2ZUNvbnRyb2wiLCIiXSxbInRleHRhcmVhIiwibWF0TmF0aXZlQ29udHJvbCIsIiJdXSxob3N0QXR0cnM6WzEsIm1hdC1pbnB1dC1lbGVtZW50IiwibWF0LWZvcm0tZmllbGQtYXV0b2ZpbGwtY29udHJvbCJdLGhvc3RWYXJzOjEyLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgiZm9jdXMiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuX2ZvY3VzQ2hhbmdlZCghMCl9KSgiYmx1ciIsZnVuY3Rpb24oKXtyZXR1cm4gaS5fZm9jdXNDaGFuZ2VkKCExKX0pKCJpbnB1dCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5fb25JbnB1dCgpfSksMiZlJiYoX3MoImRpc2FibGVkIixpLmRpc2FibGVkKSgicmVxdWlyZWQiLGkucmVxdWlyZWQpLHplKCJpZCIsaS5pZCkoImRhdGEtcGxhY2Vob2xkZXIiLGkucGxhY2Vob2xkZXIpKCJuYW1lIixpLm5hbWV8fG51bGwpKCJyZWFkb25seSIsaS5yZWFkb25seSYmIWkuX2lzTmF0aXZlU2VsZWN0fHxudWxsKSgiYXJpYS1pbnZhbGlkIixpLmVtcHR5JiZpLnJlcXVpcmVkP251bGw6aS5lcnJvclN0YXRlKSgiYXJpYS1yZXF1aXJlZCIsaS5yZXF1aXJlZCksZXQoIm1hdC1pbnB1dC1zZXJ2ZXIiLGkuX2lzU2VydmVyKSgibWF0LW5hdGl2ZS1zZWxlY3QtaW5saW5lIixpLl9pc0lubGluZVNlbGVjdCgpKSl9LGlucHV0czp7ZGlzYWJsZWQ6ImRpc2FibGVkIixpZDoiaWQiLHBsYWNlaG9sZGVyOiJwbGFjZWhvbGRlciIsbmFtZToibmFtZSIscmVxdWlyZWQ6InJlcXVpcmVkIix0eXBlOiJ0eXBlIixlcnJvclN0YXRlTWF0Y2hlcjoiZXJyb3JTdGF0ZU1hdGNoZXIiLHVzZXJBcmlhRGVzY3JpYmVkQnk6WyJhcmlhLWRlc2NyaWJlZGJ5IiwidXNlckFyaWFEZXNjcmliZWRCeSJdLHZhbHVlOiJ2YWx1ZSIscmVhZG9ubHk6InJlYWRvbmx5In0sZXhwb3J0QXM6WyJtYXRJbnB1dCJdLGZlYXR1cmVzOlskdChbe3Byb3ZpZGU6a2gsdXNlRXhpc3Rpbmc6bn1dKSx0dCxGdF19KSxufSkoKSxIYT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7cHJvdmlkZXJzOltjZF0saW1wb3J0czpbWkgsYWcsbG4sWkgsYWddfSksbn0pKCk7ZnVuY3Rpb24gR05lKG4sdCl7aWYoMSZuJiYoXygwLCJtYXQtZXJyb3IiKSxBKDEpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygxKSxqZSgiIFJlbG9hZCBwZXJpb2QgaGFzIHRvIGJlIG1pbmltdW0gb2YgIixlLk1JTl9SRUxPQURfUEVSSU9EX0lOX1MsIiBzZWNvbmRzLiAiKX19ZnVuY3Rpb24gV05lKG4sdCl7MSZuJiYoXygwLCJtYXQtZXJyb3IiKSxBKDEsIiBQYWdlIHNpemUgaGFzIHRvIGJlIGEgcG9zaXRpdmUgaW50ZWdlci4gIiksdigpKX12YXIgb2llPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLnJlbG9hZFRvZ2dsZWQ9bmV3IEcsdGhpcy5yZWxvYWRQZXJpb2RJbk1zQ2hhbmdlZD1uZXcgRyx0aGlzLnBhZ2VTaXplQ2hhbmdlZD1uZXcgRyx0aGlzLk1JTl9SRUxPQURfUEVSSU9EX0lOX1M9MzAsdGhpcy5yZWxvYWRQZXJpb2RDb250cm9sPW5ldyBCaCh0aGlzLk1JTl9SRUxPQURfUEVSSU9EX0lOX1MsW0ZvLnJlcXVpcmVkLEZvLm1pbih0aGlzLk1JTl9SRUxPQURfUEVSSU9EX0lOX1MpXSksdGhpcy5wYWdpbmF0aW9uQ29udHJvbD1uZXcgQmgoMSxbRm8ucmVxdWlyZWQsRm8ubWluKDEpLG49PntsZXQgdD1OdW1iZXIobi52YWx1ZSk7cmV0dXJuIE1hdGgucm91bmQodCk9PT1uLnZhbHVlP251bGw6e2ludGVnZXI6e3ZhbHVlOm4udmFsdWV9fX1dKSx0aGlzLm5nVW5zdWJzY3JpYmU9bmV3IGtlfW5nT25Jbml0KCl7dGhpcy5yZWxvYWRQZXJpb2RDb250cm9sLnZhbHVlQ2hhbmdlcy5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSksSHIoNTAwKSxZZSgoKT0+dGhpcy5yZWxvYWRQZXJpb2RDb250cm9sLnZhbGlkKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5yZWxvYWRQZXJpb2RDb250cm9sLnZhbGlkJiZ0aGlzLnJlbG9hZFBlcmlvZEluTXNDaGFuZ2VkLmVtaXQoMWUzKnRoaXMucmVsb2FkUGVyaW9kQ29udHJvbC52YWx1ZSl9KSx0aGlzLnBhZ2luYXRpb25Db250cm9sLnZhbHVlQ2hhbmdlcy5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSksSHIoNTAwKSxZZSgoKT0+dGhpcy5wYWdpbmF0aW9uQ29udHJvbC52YWxpZCkpLnN1YnNjcmliZSgoKT0+e3RoaXMucGFnZVNpemVDaGFuZ2VkLmVtaXQodGhpcy5wYWdpbmF0aW9uQ29udHJvbC52YWx1ZSl9KX1uZ09uRGVzdHJveSgpe3RoaXMubmdVbnN1YnNjcmliZS5uZXh0KCksdGhpcy5uZ1Vuc3Vic2NyaWJlLmNvbXBsZXRlKCl9bmdPbkNoYW5nZXMoZSl7aWYoZS5yZWxvYWRQZXJpb2RJbk1zKXtsZXQgaT1lLnJlbG9hZFBlcmlvZEluTXM7aS5wcmV2aW91c1ZhbHVlIT09aS5jdXJyZW50VmFsdWUmJnRoaXMucmVsb2FkUGVyaW9kQ29udHJvbC5zZXRWYWx1ZShpLmN1cnJlbnRWYWx1ZS8xZTMpfWlmKGUucmVsb2FkRW5hYmxlZCYmKGUucmVsb2FkRW5hYmxlZC5jdXJyZW50VmFsdWU/dGhpcy5yZWxvYWRQZXJpb2RDb250cm9sLmVuYWJsZSgpOnRoaXMucmVsb2FkUGVyaW9kQ29udHJvbC5kaXNhYmxlKCkpLGUucGFnZVNpemUpe2xldCBpPWUucGFnZVNpemU7aS5wcmV2aW91c1ZhbHVlIT09aS5jdXJyZW50VmFsdWUmJnRoaXMucGFnaW5hdGlvbkNvbnRyb2wuc2V0VmFsdWUoaS5jdXJyZW50VmFsdWUpfX1vblJlbG9hZFRvZ2dsZSgpe3RoaXMucmVsb2FkVG9nZ2xlZC5lbWl0KCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInNldHRpbmdzLWRpYWxvZy1jb21wb25lbnQiXV0saW5wdXRzOntyZWxvYWRFbmFibGVkOiJyZWxvYWRFbmFibGVkIixyZWxvYWRQZXJpb2RJbk1zOiJyZWxvYWRQZXJpb2RJbk1zIixwYWdlU2l6ZToicGFnZVNpemUifSxvdXRwdXRzOntyZWxvYWRUb2dnbGVkOiJyZWxvYWRUb2dnbGVkIixyZWxvYWRQZXJpb2RJbk1zQ2hhbmdlZDoicmVsb2FkUGVyaW9kSW5Nc0NoYW5nZWQiLHBhZ2VTaXplQ2hhbmdlZDoicGFnZVNpemVDaGFuZ2VkIn0sZmVhdHVyZXM6W0Z0XSxkZWNsczoxNCx2YXJzOjUsY29uc3RzOltbMSwicmVsb2FkLXRvZ2dsZSJdLFszLCJjaGVja2VkIiwiY2hhbmdlIl0sWyJtYXRJbnB1dCIsIiIsInR5cGUiLCJudW1iZXIiLCJwbGFjZWhvbGRlciIsIlJlbG9hZCBQZXJpb2QgKHNlY29uZHMpIiwxLCJyZWxvYWQtcGVyaW9kIiwzLCJmb3JtQ29udHJvbCJdLFs0LCJuZ0lmIl0sWyJtYXRJbnB1dCIsIiIsInR5cGUiLCJudW1iZXIiLCJwbGFjZWhvbGRlciIsIlBhZ2luYXRpb24gTGltaXQiLDEsInBhZ2Utc2l6ZSIsMywiZm9ybUNvbnRyb2wiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImgzIiksQSgxLCJTZXR0aW5ncyIpLHYoKSxfKDIsImRpdiIpKDMsImRpdiIsMCkoNCwibWF0LWNoZWNrYm94IiwxKSxQKCJjaGFuZ2UiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25SZWxvYWRUb2dnbGUoKX0pLEEoNSwiUmVsb2FkIGRhdGEiKSx2KCkoKSxfKDYsImRpdiIpKDcsIm1hdC1mb3JtLWZpZWxkIiksTyg4LCJpbnB1dCIsMiksdigpLEUoOSxHTmUsMiwxLCJtYXQtZXJyb3IiLDMpLHYoKSgpLF8oMTAsImRpdiIpKDExLCJtYXQtZm9ybS1maWVsZCIpLE8oMTIsImlucHV0Iiw0KSx2KCksRSgxMyxXTmUsMiwwLCJtYXQtZXJyb3IiLDMpLHYoKSksMiZlJiYoQyg0KSx5KCJjaGVja2VkIixpLnJlbG9hZEVuYWJsZWQpLEMoNCkseSgiZm9ybUNvbnRyb2wiLGkucmVsb2FkUGVyaW9kQ29udHJvbCksQygxKSx5KCJuZ0lmIixpLnJlbG9hZFBlcmlvZENvbnRyb2wuaGFzRXJyb3IoIm1pbiIpfHxpLnJlbG9hZFBlcmlvZENvbnRyb2wuaGFzRXJyb3IoInJlcXVpcmVkIikpLEMoMykseSgiZm9ybUNvbnRyb2wiLGkucGFnaW5hdGlvbkNvbnRyb2wpLEMoMSkseSgibmdJZiIsaS5wYWdpbmF0aW9uQ29udHJvbC5pbnZhbGlkKSl9LGRlcGVuZGVuY2llczpbQmUsQnYscUgsVjIsbXcseWwsSHRlLHBkLFVoXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVdIHtcbiAgZm9udC1zaXplOiAxNXB4O1xufVxuXG5bX25naG9zdC0lQ09NUCVdICAgID4gZGl2W19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIG1hcmdpbjogMTBweCAwO1xufVxuXG5bX25naG9zdC0lQ09NUCVdICAgID4gW19uZ2NvbnRlbnQtJUNPTVAlXTpmaXJzdC1jaGlsZCB7XG4gIG1hcmdpbi10b3A6IDA7XG59XG5cbltfbmdob3N0LSVDT01QJV0gICAgPiBbX25nY29udGVudC0lQ09NUCVdOmxhc3QtY2hpbGQge1xuICBtYXJnaW4tYm90dG9tOiAwO1xufVxuXG5oM1tfbmdjb250ZW50LSVDT01QJV0ge1xuICBmb250LXNpemU6IDIwcHg7XG59XG5cbi5yZWxvYWQtdG9nZ2xlW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIG1hcmdpbi1ib3R0b206IDEwcHg7XG59Il19KSxufSkoKSxzaWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5yZWxvYWRFbmFibGVkJD10aGlzLnN0b3JlLnNlbGVjdChZTSksdGhpcy5yZWxvYWRQZXJpb2RJbk1zJD10aGlzLnN0b3JlLnNlbGVjdChYTSksdGhpcy5wYWdlU2l6ZSQ9dGhpcy5zdG9yZS5zZWxlY3QoS20pfW9uUmVsb2FkVG9nZ2xlZCgpe3RoaXMuc3RvcmUuZGlzcGF0Y2goWEkoKSl9b25SZWxvYWRQZXJpb2RJbk1zQ2hhbmdlZChlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKFFJKHtwZXJpb2RJbk1zOmV9KSl9b25QYWdlU2l6ZUNoYW5nZWQoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChLSSh7c2l6ZTplfSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sic2V0dGluZ3MtZGlhbG9nIl1dLGRlY2xzOjQsdmFyczo5LGNvbnN0czpbWzMsInJlbG9hZEVuYWJsZWQiLCJyZWxvYWRQZXJpb2RJbk1zIiwicGFnZVNpemUiLCJyZWxvYWRUb2dnbGVkIiwicmVsb2FkUGVyaW9kSW5Nc0NoYW5nZWQiLCJwYWdlU2l6ZUNoYW5nZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsInNldHRpbmdzLWRpYWxvZy1jb21wb25lbnQiLDApLFAoInJlbG9hZFRvZ2dsZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25SZWxvYWRUb2dnbGVkKCl9KSgicmVsb2FkUGVyaW9kSW5Nc0NoYW5nZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uUmVsb2FkUGVyaW9kSW5Nc0NoYW5nZWQobyl9KSgicGFnZVNpemVDaGFuZ2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblBhZ2VTaXplQ2hhbmdlZChvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLHYoKSksMiZlJiZ5KCJyZWxvYWRFbmFibGVkIixVKDEsMyxpLnJlbG9hZEVuYWJsZWQkKSkoInJlbG9hZFBlcmlvZEluTXMiLFUoMiw1LGkucmVsb2FkUGVyaW9kSW5NcyQpKSgicGFnZVNpemUiLFUoMyw3LGkucGFnZVNpemUkKSl9LGRlcGVuZGVuY2llczpbb2llLEdlXSxlbmNhcHN1bGF0aW9uOjJ9KSxufSkoKSxhaWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLmRpYWxvZz1lfWlzQnV0dG9uRGlzYWJsZWQoKXtyZXR1cm4gdGhpcy5zZXR0aW5nc0xvYWRTdGF0ZT09PU9lLk5PVF9MT0FERUR8fHRoaXMuc2V0dGluZ3NMb2FkU3RhdGU9PT1PZS5MT0FESU5HfW9wZW5EaWFsb2coKXt0aGlzLmRpYWxvZy5vcGVuKHNpZSx7d2lkdGg6IjQwMHB4In0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKHZsKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sic2V0dGluZ3MtYnV0dG9uLWNvbXBvbmVudCJdXSxpbnB1dHM6e3NldHRpbmdzTG9hZFN0YXRlOiJzZXR0aW5nc0xvYWRTdGF0ZSJ9LGRlY2xzOjIsdmFyczoxLGNvbnN0czpbWyJtYXQtaWNvbi1idXR0b24iLCIiLDMsImRpc2FibGVkIiwiY2xpY2siXSxbInN2Z0ljb24iLCJzZXR0aW5nc18yNHB4Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJidXR0b24iLDApLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9wZW5EaWFsb2coKX0pLE8oMSwibWF0LWljb24iLDEpLHYoKSksMiZlJiZ5KCJkaXNhYmxlZCIsaS5pc0J1dHRvbkRpc2FibGVkKCkpfSxkZXBlbmRlbmNpZXM6W19uLEd0XSxlbmNhcHN1bGF0aW9uOjJ9KSxufSkoKSxsaWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5zZXR0aW5nc0xvYWRTdGF0ZSQ9dGhpcy5zdG9yZS5zZWxlY3QobEgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sic2V0dGluZ3MtYnV0dG9uIl1dLGRlY2xzOjIsdmFyczozLGNvbnN0czpbWzMsInNldHRpbmdzTG9hZFN0YXRlIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJzZXR0aW5ncy1idXR0b24tY29tcG9uZW50IiwwKSxCKDEsImFzeW5jIikpLDImZSYmeSgic2V0dGluZ3NMb2FkU3RhdGUiLFUoMSwxLGkuc2V0dGluZ3NMb2FkU3RhdGUkKSl9LGRlcGVuZGVuY2llczpbYWllLEdlXSxlbmNhcHN1bGF0aW9uOjJ9KSxufSkoKSxLTmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLl9kb2N1bWVudD1lfWNvcHkoZSl7bGV0IGk9dGhpcy5iZWdpbkNvcHkoZSkscj1pLmNvcHkoKTtyZXR1cm4gaS5kZXN0cm95KCkscn1iZWdpbkNvcHkoZSl7cmV0dXJuIG5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuX2RvY3VtZW50PWU7bGV0IGk9dGhpcy5fdGV4dGFyZWE9dGhpcy5fZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgidGV4dGFyZWEiKSxyPWkuc3R5bGU7ci5wb3NpdGlvbj0iZml4ZWQiLHIudG9wPXIub3BhY2l0eT0iMCIsci5sZWZ0PSItOTk5ZW0iLGkuc2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIsInRydWUiKSxpLnZhbHVlPXQsaS5yZWFkT25seT0hMCx0aGlzLl9kb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGkpfWNvcHkoKXtsZXQgdD10aGlzLl90ZXh0YXJlYSxlPSExO3RyeXtpZih0KXtsZXQgaT10aGlzLl9kb2N1bWVudC5hY3RpdmVFbGVtZW50O3Quc2VsZWN0KCksdC5zZXRTZWxlY3Rpb25SYW5nZSgwLHQudmFsdWUubGVuZ3RoKSxlPXRoaXMuX2RvY3VtZW50LmV4ZWNDb21tYW5kKCJjb3B5IiksaSYmaS5mb2N1cygpfX1jYXRjaHt9cmV0dXJuIGV9ZGVzdHJveSgpe2xldCB0PXRoaXMuX3RleHRhcmVhO3QmJih0LnJlbW92ZSgpLHRoaXMuX3RleHRhcmVhPXZvaWQgMCl9fShlLHRoaXMuX2RvY3VtZW50KX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihIdCkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpLFpOZT1uZXcgcGUoIkNES19DT1BZX1RPX0NMSVBCT0FSRF9DT05GSUciKSxjaWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5fY2xpcGJvYXJkPWUsdGhpcy5fbmdab25lPWksdGhpcy50ZXh0PSIiLHRoaXMuYXR0ZW1wdHM9MSx0aGlzLmNvcGllZD1uZXcgRyx0aGlzLl9wZW5kaW5nPW5ldyBTZXQsciYmbnVsbCE9ci5hdHRlbXB0cyYmKHRoaXMuYXR0ZW1wdHM9ci5hdHRlbXB0cyl9Y29weShlPXRoaXMuYXR0ZW1wdHMpe2lmKGU+MSl7bGV0IGk9ZSxyPXRoaXMuX2NsaXBib2FyZC5iZWdpbkNvcHkodGhpcy50ZXh0KTt0aGlzLl9wZW5kaW5nLmFkZChyKTtsZXQgbz0oKT0+e2xldCBzPXIuY29weSgpO3N8fCEtLWl8fHRoaXMuX2Rlc3Ryb3llZD8odGhpcy5fY3VycmVudFRpbWVvdXQ9bnVsbCx0aGlzLl9wZW5kaW5nLmRlbGV0ZShyKSxyLmRlc3Ryb3koKSx0aGlzLmNvcGllZC5lbWl0KHMpKTp0aGlzLl9jdXJyZW50VGltZW91dD10aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PnNldFRpbWVvdXQobywxKSl9O28oKX1lbHNlIHRoaXMuY29waWVkLmVtaXQodGhpcy5fY2xpcGJvYXJkLmNvcHkodGhpcy50ZXh0KSl9bmdPbkRlc3Ryb3koKXt0aGlzLl9jdXJyZW50VGltZW91dCYmY2xlYXJUaW1lb3V0KHRoaXMuX2N1cnJlbnRUaW1lb3V0KSx0aGlzLl9wZW5kaW5nLmZvckVhY2goZT0+ZS5kZXN0cm95KCkpLHRoaXMuX3BlbmRpbmcuY2xlYXIoKSx0aGlzLl9kZXN0cm95ZWQ9ITB9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oS05lKSxNKF90KSxNKFpOZSw4KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsImNka0NvcHlUb0NsaXBib2FyZCIsIiJdXSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJlAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLmNvcHkoKX0pfSxpbnB1dHM6e3RleHQ6WyJjZGtDb3B5VG9DbGlwYm9hcmQiLCJ0ZXh0Il0sYXR0ZW1wdHM6WyJjZGtDb3B5VG9DbGlwYm9hcmRBdHRlbXB0cyIsImF0dGVtcHRzIl19LG91dHB1dHM6e2NvcGllZDoiY2RrQ29weVRvQ2xpcGJvYXJkQ29waWVkIn19KSxufSkoKSx1aWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLGRpZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuZGlhbG9nUmVmPWUsdGhpcy50ZW5zb3Jib2FyZERvdERldlVybD0iaHR0cHM6Ly90ZW5zb3Jib2FyZC5kZXYvP3V0bV9zb3VyY2U9dGVuc29yYm9hcmQifW9uQ2xvc2UoKXt0aGlzLmRpYWxvZ1JlZi5jbG9zZSgpfWdldENvbW1hbmRUZXh0KCl7cmV0dXJuIHRoaXMubG9nZGlyPyJ0ZW5zb3Jib2FyZCBkZXYgdXBsb2FkIC0tbG9nZGlyIFxcXG4gICAgJyIrdGhpcy5sb2dkaXIucmVwbGFjZSgvJy9nLCInXFwnJyIpKyInIjoidGVuc29yYm9hcmQgZGV2IHVwbG9hZCAtLWxvZ2RpciB7bG9nZGlyfSJ9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0odHUpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ0YmRldi11cGxvYWQtZGlhbG9nLWNvbXBvbmVudCJdXSxpbnB1dHM6e2xvZ2RpcjoibG9nZGlyIn0sZGVjbHM6MjEsdmFyczo0LGNvbnN0czpbWyJ0YXJnZXQiLCJfYmxhbmsiLCJyZWwiLCJub3JlZmVycmVyIG5vb3BlbmVyIiwxLCJhbmNob3ItdGV4dCIsMywiaHJlZiJdLFsxLCJjb21tYW5kIl0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJ0aXRsZSIsIkNsaWNrIHRvIGNvcHkgdGhlIGNvbW1hbmQiLDEsImNvbW1hbmQtY29weSIsMywiY2RrQ29weVRvQ2xpcGJvYXJkIl0sWyJzdmdJY29uIiwiY29udGVudF9jb3B5XzI0cHgiXSxbMSwiYm90dG9tLWJ1dHRvbnMiXSxbIm1hdC1mbGF0LWJ1dHRvbiIsIiIsMSwiY2xvc2UtYnV0dG9uIiwzLCJjbGljayJdLFsibWF0LWZsYXQtYnV0dG9uIiwiIiwidGFyZ2V0IiwiX2JsYW5rIiwicmVsIiwibm9yZWZlcnJlciBub29wZW5lciIsMSwibGVhcm4tbW9yZS1idXR0b24iLDMsImhyZWYiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImgzIiksQSgxLCJVcGxvYWQgdG8gVGVuc29yQm9hcmQuZGV2IiksdigpLF8oMiwicCIpKDMsImEiLDApLEEoNCwiIFRlbnNvckJvYXJkLmRldiIpLHYoKSxBKDUsIiBlbmFibGVzIHlvdSB0byBlYXNpbHkgaG9zdCwgdHJhY2ssIGFuZCBzaGFyZSB5b3VyIE1MIGV4cGVyaW1lbnRzIHdpdGggZXZlcnlvbmUuIFlvdSBjYW4gc2hhcmUgYSBsaW5rIHRvIHRoZSB1cGxvYWRlZCBUZW5zb3JCb2FyZCBpbiBwYXBlcnMsIGJsb2cgcG9zdHMsIGFuZCBzb2NpYWwgbWVkaWEuIFRoaXMgY2FuIHNob3djYXNlIHRoZSByZXN1bHRzIG1vcmUgZWZmZWN0aXZlbHkgYW5kIGhlbHBzIHJlcHJvZHVjaWJpbGl0eS5cbiIpLHYoKSxfKDYsInAiKSxBKDcsIlRvIHVwbG9hZCBhIGxvZ2RpciB0byBUZW5zb3JCb2FyZC5kZXYsIHJ1biB0aGUgY29tbWFuZDoiKSx2KCksXyg4LCJkaXYiLDEpKDksInByZSIpKDEwLCJjb2RlIiksQSgxMSksdigpKCksXygxMiwiYnV0dG9uIiwyKSxPKDEzLCJtYXQtaWNvbiIsMyksdigpKCksXygxNCwicCIpLEEoMTUsIiBPbmx5IGNlcnRhaW4gcGx1Z2lucyBhcmUgY3VycmVudGx5IHN1cHBvcnRlZC4gVXBsb2FkZWQgVGVuc29yQm9hcmRzIGFyZSBwdWJsaWMgYW5kIHZpc2libGUgdG8gZXZlcnlvbmU7IGRvIG5vdCB1cGxvYWQgc2Vuc2l0aXZlIGRhdGEuXG4iKSx2KCksXygxNiwiZGl2Iiw0KSgxNywiYnV0dG9uIiw1KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkNsb3NlKCl9KSxBKDE4LCIgQ2xvc2UgIiksdigpLF8oMTksImEiLDYpLEEoMjAsIiBMZWFybiBtb3JlICIpLHYoKSgpKSwyJmUmJihDKDMpLFppKCJocmVmIixpLnRlbnNvcmJvYXJkRG90RGV2VXJsLHpsKSxDKDgpLHl0KGkuZ2V0Q29tbWFuZFRleHQoKSksQygxKSx5KCJjZGtDb3B5VG9DbGlwYm9hcmQiLGkuZ2V0Q29tbWFuZFRleHQoKSksQyg3KSxaaSgiaHJlZiIsaS50ZW5zb3Jib2FyZERvdERldlVybCx6bCkpfSxkZXBlbmRlbmNpZXM6W2NpZSxfbixJdixHdF0sc3R5bGVzOlsiZGl2W19uZ2NvbnRlbnQtJUNPTVAlXSwgcFtfbmdjb250ZW50LSVDT01QJV17bWFyZ2luOjE2cHggMH1bX25naG9zdC0lQ09NUCVdID4gW19uZ2NvbnRlbnQtJUNPTVAlXTpmaXJzdC1jaGlsZHttYXJnaW4tdG9wOjB9W19uZ2hvc3QtJUNPTVAlXSA+IFtfbmdjb250ZW50LSVDT01QJV06bGFzdC1jaGlsZHttYXJnaW4tYm90dG9tOjB9aDNbX25nY29udGVudC0lQ09NUCVde2ZvbnQtc2l6ZToxNHB4O2ZvbnQtd2VpZ2h0OjUwMDtsaW5lLWhlaWdodDoxLjV9cFtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzIxMjEyMTtmb250LXNpemU6MTJweDtsaW5lLWhlaWdodDoxLjV9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgcFtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIHBbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiNmZmZ9LmFuY2hvci10ZXh0W19uZ2NvbnRlbnQtJUNPTVAlXXt0ZXh0LWRlY29yYXRpb246bm9uZX0uY29tbWFuZFtfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2JhY2tncm91bmQ6I2Y1ZjZmNztib3JkZXItcmFkaXVzOjRweDtkaXNwbGF5OmZsZXg7anVzdGlmeS1jb250ZW50OnNwYWNlLWJldHdlZW47cGFkZGluZzoycHggMTJweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuY29tbWFuZFtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5jb21tYW5kW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiM2MTYxNjF9cHJlW19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdy14OmF1dG99Y29kZVtfbmdjb250ZW50LSVDT01QJV17Zm9udC1zaXplOjE0cHg7bGluZS1oZWlnaHQ6MS41fS5ib3R0b20tYnV0dG9uc1tfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleDtqdXN0aWZ5LWNvbnRlbnQ6ZmxleC1lbmR9LmNsb3NlLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTt0ZXh0LXRyYW5zZm9ybTp1cHBlcmNhc2U7bWFyZ2luLXJpZ2h0OjhweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuY2xvc2UtYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmNsb3NlLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9W19uZ2hvc3QtJUNPTVAlXSAgIC5sZWFybi1tb3JlLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzE5NzZkMjt0ZXh0LXRyYW5zZm9ybTp1cHBlcmNhc2V9Ym9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmxlYXJuLW1vcmUtYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjojNDJhNWY1fSJdfSksbn0pKCksZUxlPUoob3Ysbj0+bi5kYXRhX2xvY2F0aW9uKSxwaWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5sb2dkaXIkPXRoaXMuc3RvcmUucGlwZSh2dChlTGUpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInRiZGV2LXVwbG9hZC1kaWFsb2ciXV0sZGVjbHM6Mix2YXJzOjMsY29uc3RzOltbMywibG9nZGlyIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJ0YmRldi11cGxvYWQtZGlhbG9nLWNvbXBvbmVudCIsMCksQigxLCJhc3luYyIpKSwyJmUmJnkoImxvZ2RpciIsVSgxLDEsaS5sb2dkaXIkKSl9LGRlcGVuZGVuY2llczpbZGllLEdlXSxlbmNhcHN1bGF0aW9uOjJ9KSxufSkoKTtmdW5jdGlvbiB0TGUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJidXR0b24iLDEpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkub3BlbkRpYWxvZygpKX0pLF8oMSwic3BhbiIsMiksTygyLCJtYXQtaWNvbiIsMyksQSgzLCIgVXBsb2FkICIpLHYoKSgpfX12YXIgbkxlPVsibG9jYWxob3N0IiwiMTI3LjAuMC4xIl0saGllPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXt0aGlzLndpbmRvdz1lLHRoaXMuZGlhbG9nPWksdGhpcy5zaG93bj1uTGUuaW5jbHVkZXMoZS5sb2NhdGlvbi5ob3N0bmFtZSl9b3BlbkRpYWxvZygpe3RoaXMuZGlhbG9nLm9wZW4ocGllLHt3aWR0aDoiNTYwcHgifSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oIndpbmRvdyIpLE0odmwpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ0YmRldi11cGxvYWQtYnV0dG9uIl1dLGhvc3RWYXJzOjIsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZldCgic2hvd24iLGkuc2hvd24pfSxkZWNsczoxLHZhcnM6MSxjb25zdHM6W1sibWF0LXN0cm9rZWQtYnV0dG9uIiwiIiwzLCJjbGljayIsNCwibmdJZiJdLFsibWF0LXN0cm9rZWQtYnV0dG9uIiwiIiwzLCJjbGljayJdLFsxLCJidXR0b24tY29udGVudHMiXSxbInN2Z0ljb24iLCJpbmZvX291dGxpbmVfMjRweCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmRSgwLHRMZSw0LDAsImJ1dHRvbiIsMCksMiZlJiZ5KCJuZ0lmIixpLnNob3duKX0sZGVwZW5kZW5jaWVzOltCZSxfbixHdF0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXSAgIGJ1dHRvbi5tYXQtc3Ryb2tlZC1idXR0b25bX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2ZmOTgwMDtib3JkZXI6MXB4IHNvbGlkICNlYmViZWJ9Ym9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgYnV0dG9uLm1hdC1zdHJva2VkLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZWY2YzAwfS5idXR0b24tY29udGVudHNbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtkaXNwbGF5OmZsZXg7dGV4dC10cmFuc2Zvcm06dXBwZXJjYXNlfW1hdC1pY29uW19uZ2NvbnRlbnQtJUNPTVAlXXttYXJnaW4tcmlnaHQ6NnB4fSJdfSksbn0pKCksckxlPVsibWF0LW1lbnUtaXRlbSIsIiJdO2Z1bmN0aW9uIG9MZShuLHQpezEmbiYmKEluKCksXygwLCJzdmciLDIpLE8oMSwicG9seWdvbiIsMyksdigpKX12YXIgbWllPVsiKiJdO2Z1bmN0aW9uIHNMZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMCksUCgia2V5ZG93biIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5faGFuZGxlS2V5ZG93bihyKSl9KSgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5jbG9zZWQuZW1pdCgiY2xpY2siKSl9KSgiQHRyYW5zZm9ybU1lbnUuc3RhcnQiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkuX29uQW5pbWF0aW9uU3RhcnQocikpfSkoIkB0cmFuc2Zvcm1NZW51LmRvbmUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkuX29uQW5pbWF0aW9uRG9uZShyKSl9KSxfKDEsImRpdiIsMSksVm4oMiksdigpKCl9aWYoMiZuKXtsZXQgZT1TKCk7eSgiaWQiLGUucGFuZWxJZCkoIm5nQ2xhc3MiLGUuX2NsYXNzTGlzdCkoIkB0cmFuc2Zvcm1NZW51IixlLl9wYW5lbEFuaW1hdGlvblN0YXRlKSx6ZSgiYXJpYS1sYWJlbCIsZS5hcmlhTGFiZWx8fG51bGwpKCJhcmlhLWxhYmVsbGVkYnkiLGUuYXJpYUxhYmVsbGVkYnl8fG51bGwpKCJhcmlhLWRlc2NyaWJlZGJ5IixlLmFyaWFEZXNjcmliZWRieXx8bnVsbCl9fXZhciBLMj17dHJhbnNmb3JtTWVudTpLcigidHJhbnNmb3JtTWVudSIsW2tpKCJ2b2lkIixnbih7b3BhY2l0eTowLHRyYW5zZm9ybToic2NhbGUoMC44KSJ9KSksTGkoInZvaWQgPT4gZW50ZXIiLGppKCIxMjBtcyBjdWJpYy1iZXppZXIoMCwgMCwgMC4yLCAxKSIsZ24oe29wYWNpdHk6MSx0cmFuc2Zvcm06InNjYWxlKDEpIn0pKSksTGkoIiogPT4gdm9pZCIsamkoIjEwMG1zIDI1bXMgbGluZWFyIixnbih7b3BhY2l0eTowfSkpKV0pLGZhZGVJbkl0ZW1zOktyKCJmYWRlSW5JdGVtcyIsW2tpKCJzaG93aW5nIixnbih7b3BhY2l0eToxfSkpLExpKCJ2b2lkID0+ICoiLFtnbih7b3BhY2l0eTowfSksamkoIjQwMG1zIDEwMG1zIGN1YmljLWJlemllcigwLjU1LCAwLCAwLjU1LCAwLjIpIildKV0pfSxhTGU9bmV3IHBlKCJNYXRNZW51Q29udGVudCIpLCRIPW5ldyBwZSgiTUFUX01FTlVfUEFORUwiKSxsTGU9cW8oc28oY2xhc3N7fSkpLG51PSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBsTGV7Y29uc3RydWN0b3IoZSxpLHIsbyxzKXtzdXBlcigpLHRoaXMuX2VsZW1lbnRSZWY9ZSx0aGlzLl9kb2N1bWVudD1pLHRoaXMuX2ZvY3VzTW9uaXRvcj1yLHRoaXMuX3BhcmVudE1lbnU9byx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj1zLHRoaXMucm9sZT0ibWVudWl0ZW0iLHRoaXMuX2hvdmVyZWQ9bmV3IGtlLHRoaXMuX2ZvY3VzZWQ9bmV3IGtlLHRoaXMuX2hpZ2hsaWdodGVkPSExLHRoaXMuX3RyaWdnZXJzU3VibWVudT0hMSxvPy5hZGRJdGVtPy4odGhpcyl9Zm9jdXMoZSxpKXt0aGlzLl9mb2N1c01vbml0b3ImJmU/dGhpcy5fZm9jdXNNb25pdG9yLmZvY3VzVmlhKHRoaXMuX2dldEhvc3RFbGVtZW50KCksZSxpKTp0aGlzLl9nZXRIb3N0RWxlbWVudCgpLmZvY3VzKGkpLHRoaXMuX2ZvY3VzZWQubmV4dCh0aGlzKX1uZ0FmdGVyVmlld0luaXQoKXt0aGlzLl9mb2N1c01vbml0b3ImJnRoaXMuX2ZvY3VzTW9uaXRvci5tb25pdG9yKHRoaXMuX2VsZW1lbnRSZWYsITEpfW5nT25EZXN0cm95KCl7dGhpcy5fZm9jdXNNb25pdG9yJiZ0aGlzLl9mb2N1c01vbml0b3Iuc3RvcE1vbml0b3JpbmcodGhpcy5fZWxlbWVudFJlZiksdGhpcy5fcGFyZW50TWVudSYmdGhpcy5fcGFyZW50TWVudS5yZW1vdmVJdGVtJiZ0aGlzLl9wYXJlbnRNZW51LnJlbW92ZUl0ZW0odGhpcyksdGhpcy5faG92ZXJlZC5jb21wbGV0ZSgpLHRoaXMuX2ZvY3VzZWQuY29tcGxldGUoKX1fZ2V0VGFiSW5kZXgoKXtyZXR1cm4gdGhpcy5kaXNhYmxlZD8iLTEiOiIwIn1fZ2V0SG9zdEVsZW1lbnQoKXtyZXR1cm4gdGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50fV9jaGVja0Rpc2FibGVkKGUpe3RoaXMuZGlzYWJsZWQmJihlLnByZXZlbnREZWZhdWx0KCksZS5zdG9wUHJvcGFnYXRpb24oKSl9X2hhbmRsZU1vdXNlRW50ZXIoKXt0aGlzLl9ob3ZlcmVkLm5leHQodGhpcyl9Z2V0TGFiZWwoKXtsZXQgZT10aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuY2xvbmVOb2RlKCEwKSxpPWUucXVlcnlTZWxlY3RvckFsbCgibWF0LWljb24sIC5tYXRlcmlhbC1pY29ucyIpO2ZvcihsZXQgcj0wO3I8aS5sZW5ndGg7cisrKWlbcl0ucmVtb3ZlKCk7cmV0dXJuIGUudGV4dENvbnRlbnQ/LnRyaW0oKXx8IiJ9X3NldEhpZ2hsaWdodGVkKGUpe3RoaXMuX2hpZ2hsaWdodGVkPWUsdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWY/Lm1hcmtGb3JDaGVjaygpfV9oYXNGb2N1cygpe3JldHVybiB0aGlzLl9kb2N1bWVudCYmdGhpcy5fZG9jdW1lbnQuYWN0aXZlRWxlbWVudD09PXRoaXMuX2dldEhvc3RFbGVtZW50KCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0oSHQpLE0oRnIpLE0oJEgsOCksTShubikpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdC1tZW51LWl0ZW0iLCIiXV0saG9zdEF0dHJzOlsxLCJtYXQtZm9jdXMtaW5kaWNhdG9yIl0saG9zdFZhcnM6MTAsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MSZlJiZQKCJjbGljayIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX2NoZWNrRGlzYWJsZWQobyl9KSgibW91c2VlbnRlciIsZnVuY3Rpb24oKXtyZXR1cm4gaS5faGFuZGxlTW91c2VFbnRlcigpfSksMiZlJiYoemUoInJvbGUiLGkucm9sZSkoInRhYmluZGV4IixpLl9nZXRUYWJJbmRleCgpKSgiYXJpYS1kaXNhYmxlZCIsaS5kaXNhYmxlZC50b1N0cmluZygpKSgiZGlzYWJsZWQiLGkuZGlzYWJsZWR8fG51bGwpLGV0KCJtYXQtbWVudS1pdGVtIiwhMCkoIm1hdC1tZW51LWl0ZW0taGlnaGxpZ2h0ZWQiLGkuX2hpZ2hsaWdodGVkKSgibWF0LW1lbnUtaXRlbS1zdWJtZW51LXRyaWdnZXIiLGkuX3RyaWdnZXJzU3VibWVudSkpfSxpbnB1dHM6e2Rpc2FibGVkOiJkaXNhYmxlZCIsZGlzYWJsZVJpcHBsZToiZGlzYWJsZVJpcHBsZSIscm9sZToicm9sZSJ9LGV4cG9ydEFzOlsibWF0TWVudUl0ZW0iXSxmZWF0dXJlczpbdHRdLGF0dHJzOnJMZSxuZ0NvbnRlbnRTZWxlY3RvcnM6bWllLGRlY2xzOjMsdmFyczozLGNvbnN0czpbWyJtYXRSaXBwbGUiLCIiLDEsIm1hdC1tZW51LXJpcHBsZSIsMywibWF0UmlwcGxlRGlzYWJsZWQiLCJtYXRSaXBwbGVUcmlnZ2VyIl0sWyJjbGFzcyIsIm1hdC1tZW51LXN1Ym1lbnUtaWNvbiIsInZpZXdCb3giLCIwIDAgNSAxMCIsImZvY3VzYWJsZSIsImZhbHNlIiw0LCJuZ0lmIl0sWyJ2aWV3Qm94IiwiMCAwIDUgMTAiLCJmb2N1c2FibGUiLCJmYWxzZSIsMSwibWF0LW1lbnUtc3VibWVudS1pY29uIl0sWyJwb2ludHMiLCIwLDAgNSw1IDAsMTAiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJih4aSgpLFZuKDApLE8oMSwiZGl2IiwwKSxFKDIsb0xlLDIsMCwic3ZnIiwxKSksMiZlJiYoQygxKSx5KCJtYXRSaXBwbGVEaXNhYmxlZCIsaS5kaXNhYmxlUmlwcGxlfHxpLmRpc2FibGVkKSgibWF0UmlwcGxlVHJpZ2dlciIsaS5fZ2V0SG9zdEVsZW1lbnQoKSksQygxKSx5KCJuZ0lmIixpLl90cmlnZ2Vyc1N1Ym1lbnUpKX0sZGVwZW5kZW5jaWVzOltCZSxZb10sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksZ2llPW5ldyBwZSgibWF0LW1lbnUtZGVmYXVsdC1vcHRpb25zIix7cHJvdmlkZWRJbjoicm9vdCIsZmFjdG9yeTpmdW5jdGlvbigpe3JldHVybntvdmVybGFwVHJpZ2dlcjohMSx4UG9zaXRpb246ImFmdGVyIix5UG9zaXRpb246ImJlbG93IixiYWNrZHJvcENsYXNzOiJjZGstb3ZlcmxheS10cmFuc3BhcmVudC1iYWNrZHJvcCJ9fX0pLHVMZT0wLHl3PSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIsbyl7dGhpcy5fZWxlbWVudFJlZj1lLHRoaXMuX25nWm9uZT1pLHRoaXMuX2RlZmF1bHRPcHRpb25zPXIsdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWY9byx0aGlzLl94UG9zaXRpb249dGhpcy5fZGVmYXVsdE9wdGlvbnMueFBvc2l0aW9uLHRoaXMuX3lQb3NpdGlvbj10aGlzLl9kZWZhdWx0T3B0aW9ucy55UG9zaXRpb24sdGhpcy5fZGlyZWN0RGVzY2VuZGFudEl0ZW1zPW5ldyBIbCx0aGlzLl90YWJTdWJzY3JpcHRpb249U24uRU1QVFksdGhpcy5fY2xhc3NMaXN0PXt9LHRoaXMuX3BhbmVsQW5pbWF0aW9uU3RhdGU9InZvaWQiLHRoaXMuX2FuaW1hdGlvbkRvbmU9bmV3IGtlLHRoaXMub3ZlcmxheVBhbmVsQ2xhc3M9dGhpcy5fZGVmYXVsdE9wdGlvbnMub3ZlcmxheVBhbmVsQ2xhc3N8fCIiLHRoaXMuYmFja2Ryb3BDbGFzcz10aGlzLl9kZWZhdWx0T3B0aW9ucy5iYWNrZHJvcENsYXNzLHRoaXMuX292ZXJsYXBUcmlnZ2VyPXRoaXMuX2RlZmF1bHRPcHRpb25zLm92ZXJsYXBUcmlnZ2VyLHRoaXMuX2hhc0JhY2tkcm9wPXRoaXMuX2RlZmF1bHRPcHRpb25zLmhhc0JhY2tkcm9wLHRoaXMuY2xvc2VkPW5ldyBHLHRoaXMuY2xvc2U9dGhpcy5jbG9zZWQsdGhpcy5wYW5lbElkPSJtYXQtbWVudS1wYW5lbC0iK3VMZSsrfWdldCB4UG9zaXRpb24oKXtyZXR1cm4gdGhpcy5feFBvc2l0aW9ufXNldCB4UG9zaXRpb24oZSl7dGhpcy5feFBvc2l0aW9uPWUsdGhpcy5zZXRQb3NpdGlvbkNsYXNzZXMoKX1nZXQgeVBvc2l0aW9uKCl7cmV0dXJuIHRoaXMuX3lQb3NpdGlvbn1zZXQgeVBvc2l0aW9uKGUpe3RoaXMuX3lQb3NpdGlvbj1lLHRoaXMuc2V0UG9zaXRpb25DbGFzc2VzKCl9Z2V0IG92ZXJsYXBUcmlnZ2VyKCl7cmV0dXJuIHRoaXMuX292ZXJsYXBUcmlnZ2VyfXNldCBvdmVybGFwVHJpZ2dlcihlKXt0aGlzLl9vdmVybGFwVHJpZ2dlcj1SdChlKX1nZXQgaGFzQmFja2Ryb3AoKXtyZXR1cm4gdGhpcy5faGFzQmFja2Ryb3B9c2V0IGhhc0JhY2tkcm9wKGUpe3RoaXMuX2hhc0JhY2tkcm9wPVJ0KGUpfXNldCBwYW5lbENsYXNzKGUpe2xldCBpPXRoaXMuX3ByZXZpb3VzUGFuZWxDbGFzcztpJiZpLmxlbmd0aCYmaS5zcGxpdCgiICIpLmZvckVhY2gocj0+e3RoaXMuX2NsYXNzTGlzdFtyXT0hMX0pLHRoaXMuX3ByZXZpb3VzUGFuZWxDbGFzcz1lLGUmJmUubGVuZ3RoJiYoZS5zcGxpdCgiICIpLmZvckVhY2gocj0+e3RoaXMuX2NsYXNzTGlzdFtyXT0hMH0pLHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5jbGFzc05hbWU9IiIpfWdldCBjbGFzc0xpc3QoKXtyZXR1cm4gdGhpcy5wYW5lbENsYXNzfXNldCBjbGFzc0xpc3QoZSl7dGhpcy5wYW5lbENsYXNzPWV9bmdPbkluaXQoKXt0aGlzLnNldFBvc2l0aW9uQ2xhc3NlcygpfW5nQWZ0ZXJDb250ZW50SW5pdCgpe3RoaXMuX3VwZGF0ZURpcmVjdERlc2NlbmRhbnRzKCksdGhpcy5fa2V5TWFuYWdlcj1uZXcgU2godGhpcy5fZGlyZWN0RGVzY2VuZGFudEl0ZW1zKS53aXRoV3JhcCgpLndpdGhUeXBlQWhlYWQoKS53aXRoSG9tZUFuZEVuZCgpLHRoaXMuX3RhYlN1YnNjcmlwdGlvbj10aGlzLl9rZXlNYW5hZ2VyLnRhYk91dC5zdWJzY3JpYmUoKCk9PnRoaXMuY2xvc2VkLmVtaXQoInRhYiIpKSx0aGlzLl9kaXJlY3REZXNjZW5kYW50SXRlbXMuY2hhbmdlcy5waXBlKHpuKHRoaXMuX2RpcmVjdERlc2NlbmRhbnRJdGVtcyksdWkoZT0+SnQoLi4uZS5tYXAoaT0+aS5fZm9jdXNlZCkpKSkuc3Vic2NyaWJlKGU9PnRoaXMuX2tleU1hbmFnZXIudXBkYXRlQWN0aXZlSXRlbShlKSksdGhpcy5fZGlyZWN0RGVzY2VuZGFudEl0ZW1zLmNoYW5nZXMuc3Vic2NyaWJlKGU9PntsZXQgaT10aGlzLl9rZXlNYW5hZ2VyO2lmKCJlbnRlciI9PT10aGlzLl9wYW5lbEFuaW1hdGlvblN0YXRlJiZpLmFjdGl2ZUl0ZW0/Ll9oYXNGb2N1cygpKXtsZXQgcj1lLnRvQXJyYXkoKSxvPU1hdGgubWF4KDAsTWF0aC5taW4oci5sZW5ndGgtMSxpLmFjdGl2ZUl0ZW1JbmRleHx8MCkpO3Jbb10mJiFyW29dLmRpc2FibGVkP2kuc2V0QWN0aXZlSXRlbShvKTppLnNldE5leHRJdGVtQWN0aXZlKCl9fSl9bmdPbkRlc3Ryb3koKXt0aGlzLl9kaXJlY3REZXNjZW5kYW50SXRlbXMuZGVzdHJveSgpLHRoaXMuX3RhYlN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuY2xvc2VkLmNvbXBsZXRlKCl9X2hvdmVyZWQoKXtyZXR1cm4gdGhpcy5fZGlyZWN0RGVzY2VuZGFudEl0ZW1zLmNoYW5nZXMucGlwZSh6bih0aGlzLl9kaXJlY3REZXNjZW5kYW50SXRlbXMpLHVpKGk9Pkp0KC4uLmkubWFwKHI9PnIuX2hvdmVyZWQpKSkpfWFkZEl0ZW0oZSl7fXJlbW92ZUl0ZW0oZSl7fV9oYW5kbGVLZXlkb3duKGUpe2xldCBpPWUua2V5Q29kZSxyPXRoaXMuX2tleU1hbmFnZXI7c3dpdGNoKGkpe2Nhc2UgMjc6a3IoZSl8fChlLnByZXZlbnREZWZhdWx0KCksdGhpcy5jbG9zZWQuZW1pdCgia2V5ZG93biIpKTticmVhaztjYXNlIDM3OnRoaXMucGFyZW50TWVudSYmImx0ciI9PT10aGlzLmRpcmVjdGlvbiYmdGhpcy5jbG9zZWQuZW1pdCgia2V5ZG93biIpO2JyZWFrO2Nhc2UgMzk6dGhpcy5wYXJlbnRNZW51JiYicnRsIj09PXRoaXMuZGlyZWN0aW9uJiZ0aGlzLmNsb3NlZC5lbWl0KCJrZXlkb3duIik7YnJlYWs7ZGVmYXVsdDpyZXR1cm4oMzg9PT1pfHw0MD09PWkpJiZyLnNldEZvY3VzT3JpZ2luKCJrZXlib2FyZCIpLHZvaWQgci5vbktleWRvd24oZSl9ZS5zdG9wUHJvcGFnYXRpb24oKX1mb2N1c0ZpcnN0SXRlbShlPSJwcm9ncmFtIil7dGhpcy5fbmdab25lLm9uU3RhYmxlLnBpcGUoUXQoMSkpLnN1YnNjcmliZSgoKT0+e2xldCBpPW51bGw7aWYodGhpcy5fZGlyZWN0RGVzY2VuZGFudEl0ZW1zLmxlbmd0aCYmKGk9dGhpcy5fZGlyZWN0RGVzY2VuZGFudEl0ZW1zLmZpcnN0Ll9nZXRIb3N0RWxlbWVudCgpLmNsb3Nlc3QoJ1tyb2xlPSJtZW51Il0nKSksIWl8fCFpLmNvbnRhaW5zKGRvY3VtZW50LmFjdGl2ZUVsZW1lbnQpKXtsZXQgcj10aGlzLl9rZXlNYW5hZ2VyO3Iuc2V0Rm9jdXNPcmlnaW4oZSkuc2V0Rmlyc3RJdGVtQWN0aXZlKCksIXIuYWN0aXZlSXRlbSYmaSYmaS5mb2N1cygpfX0pfXJlc2V0QWN0aXZlSXRlbSgpe3RoaXMuX2tleU1hbmFnZXIuc2V0QWN0aXZlSXRlbSgtMSl9c2V0RWxldmF0aW9uKGUpe2xldCBpPU1hdGgubWluKHRoaXMuX2Jhc2VFbGV2YXRpb24rZSwyNCkscj1gJHt0aGlzLl9lbGV2YXRpb25QcmVmaXh9JHtpfWAsbz1PYmplY3Qua2V5cyh0aGlzLl9jbGFzc0xpc3QpLmZpbmQocz0+cy5zdGFydHNXaXRoKHRoaXMuX2VsZXZhdGlvblByZWZpeCkpOyghb3x8bz09PXRoaXMuX3ByZXZpb3VzRWxldmF0aW9uKSYmKHRoaXMuX3ByZXZpb3VzRWxldmF0aW9uJiYodGhpcy5fY2xhc3NMaXN0W3RoaXMuX3ByZXZpb3VzRWxldmF0aW9uXT0hMSksdGhpcy5fY2xhc3NMaXN0W3JdPSEwLHRoaXMuX3ByZXZpb3VzRWxldmF0aW9uPXIpfXNldFBvc2l0aW9uQ2xhc3NlcyhlPXRoaXMueFBvc2l0aW9uLGk9dGhpcy55UG9zaXRpb24pe2xldCByPXRoaXMuX2NsYXNzTGlzdDtyWyJtYXQtbWVudS1iZWZvcmUiXT0iYmVmb3JlIj09PWUsclsibWF0LW1lbnUtYWZ0ZXIiXT0iYWZ0ZXIiPT09ZSxyWyJtYXQtbWVudS1hYm92ZSJdPSJhYm92ZSI9PT1pLHJbIm1hdC1tZW51LWJlbG93Il09ImJlbG93Ij09PWksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWY/Lm1hcmtGb3JDaGVjaygpfV9zdGFydEFuaW1hdGlvbigpe3RoaXMuX3BhbmVsQW5pbWF0aW9uU3RhdGU9ImVudGVyIn1fcmVzZXRBbmltYXRpb24oKXt0aGlzLl9wYW5lbEFuaW1hdGlvblN0YXRlPSJ2b2lkIn1fb25BbmltYXRpb25Eb25lKGUpe3RoaXMuX2FuaW1hdGlvbkRvbmUubmV4dChlKSx0aGlzLl9pc0FuaW1hdGluZz0hMX1fb25BbmltYXRpb25TdGFydChlKXt0aGlzLl9pc0FuaW1hdGluZz0hMCwiZW50ZXIiPT09ZS50b1N0YXRlJiYwPT09dGhpcy5fa2V5TWFuYWdlci5hY3RpdmVJdGVtSW5kZXgmJihlLmVsZW1lbnQuc2Nyb2xsVG9wPTApfV91cGRhdGVEaXJlY3REZXNjZW5kYW50cygpe3RoaXMuX2FsbEl0ZW1zLmNoYW5nZXMucGlwZSh6bih0aGlzLl9hbGxJdGVtcykpLnN1YnNjcmliZShlPT57dGhpcy5fZGlyZWN0RGVzY2VuZGFudEl0ZW1zLnJlc2V0KGUuZmlsdGVyKGk9PmkuX3BhcmVudE1lbnU9PT10aGlzKSksdGhpcy5fZGlyZWN0RGVzY2VuZGFudEl0ZW1zLm5vdGlmeU9uQ2hhbmdlcygpfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0oX3QpLE0oZ2llKSxNKG5uKSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sY29udGVudFF1ZXJpZXM6ZnVuY3Rpb24oZSxpLHIpe2lmKDEmZSYmKEVpKHIsYUxlLDUpLEVpKHIsbnUsNSksRWkocixudSw0KSksMiZlKXtsZXQgbztOZShvPUxlKCkpJiYoaS5sYXp5Q29udGVudD1vLmZpcnN0KSxOZShvPUxlKCkpJiYoaS5fYWxsSXRlbXM9byksTmUobz1MZSgpKSYmKGkuaXRlbXM9byl9fSx2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJm90KFZpLDUpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkudGVtcGxhdGVSZWY9ci5maXJzdCl9fSxpbnB1dHM6e2JhY2tkcm9wQ2xhc3M6ImJhY2tkcm9wQ2xhc3MiLGFyaWFMYWJlbDpbImFyaWEtbGFiZWwiLCJhcmlhTGFiZWwiXSxhcmlhTGFiZWxsZWRieTpbImFyaWEtbGFiZWxsZWRieSIsImFyaWFMYWJlbGxlZGJ5Il0sYXJpYURlc2NyaWJlZGJ5OlsiYXJpYS1kZXNjcmliZWRieSIsImFyaWFEZXNjcmliZWRieSJdLHhQb3NpdGlvbjoieFBvc2l0aW9uIix5UG9zaXRpb246InlQb3NpdGlvbiIsb3ZlcmxhcFRyaWdnZXI6Im92ZXJsYXBUcmlnZ2VyIixoYXNCYWNrZHJvcDoiaGFzQmFja2Ryb3AiLHBhbmVsQ2xhc3M6WyJjbGFzcyIsInBhbmVsQ2xhc3MiXSxjbGFzc0xpc3Q6ImNsYXNzTGlzdCJ9LG91dHB1dHM6e2Nsb3NlZDoiY2xvc2VkIixjbG9zZToiY2xvc2UifX0pLG59KSgpLGhkPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyB5d3tjb25zdHJ1Y3RvcihlLGkscixvKXtzdXBlcihlLGkscixvKSx0aGlzLl9lbGV2YXRpb25QcmVmaXg9Im1hdC1lbGV2YXRpb24teiIsdGhpcy5fYmFzZUVsZXZhdGlvbj00fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKF90KSxNKGdpZSksTShubikpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1tZW51Il1dLGhvc3RWYXJzOjMsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZ6ZSgiYXJpYS1sYWJlbCIsbnVsbCkoImFyaWEtbGFiZWxsZWRieSIsbnVsbCkoImFyaWEtZGVzY3JpYmVkYnkiLG51bGwpfSxleHBvcnRBczpbIm1hdE1lbnUiXSxmZWF0dXJlczpbJHQoW3twcm92aWRlOiRILHVzZUV4aXN0aW5nOm59XSksdHRdLG5nQ29udGVudFNlbGVjdG9yczptaWUsZGVjbHM6MSx2YXJzOjAsY29uc3RzOltbInRhYmluZGV4IiwiLTEiLCJyb2xlIiwibWVudSIsMSwibWF0LW1lbnUtcGFuZWwiLDMsImlkIiwibmdDbGFzcyIsImtleWRvd24iLCJjbGljayJdLFsxLCJtYXQtbWVudS1jb250ZW50Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoeGkoKSxFKDAsc0xlLDMsNiwibmctdGVtcGxhdGUiKSl9LGRlcGVuZGVuY2llczpbRm5dLHN0eWxlczpbJ21hdC1tZW51e2Rpc3BsYXk6bm9uZX0ubWF0LW1lbnUtcGFuZWx7bWluLXdpZHRoOjExMnB4O21heC13aWR0aDoyODBweDtvdmVyZmxvdzphdXRvOy13ZWJraXQtb3ZlcmZsb3ctc2Nyb2xsaW5nOnRvdWNoO21heC1oZWlnaHQ6Y2FsYygxMDB2aCAtIDQ4cHgpO2JvcmRlci1yYWRpdXM6NHB4O291dGxpbmU6MDttaW4taGVpZ2h0OjY0cHg7cG9zaXRpb246cmVsYXRpdmV9Lm1hdC1tZW51LXBhbmVsLm5nLWFuaW1hdGluZ3twb2ludGVyLWV2ZW50czpub25lfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1tZW51LXBhbmVse291dGxpbmU6c29saWQgMXB4fS5tYXQtbWVudS1jb250ZW50Om5vdCg6ZW1wdHkpe3BhZGRpbmctdG9wOjhweDtwYWRkaW5nLWJvdHRvbTo4cHh9Lm1hdC1tZW51LWl0ZW17LXdlYmtpdC11c2VyLXNlbGVjdDpub25lO3VzZXItc2VsZWN0Om5vbmU7Y3Vyc29yOnBvaW50ZXI7b3V0bGluZTpub25lO2JvcmRlcjpub25lOy13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjpyZ2JhKDAsMCwwLDApO3doaXRlLXNwYWNlOm5vd3JhcDtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpcztkaXNwbGF5OmJsb2NrO2xpbmUtaGVpZ2h0OjQ4cHg7aGVpZ2h0OjQ4cHg7cGFkZGluZzowIDE2cHg7dGV4dC1hbGlnbjpsZWZ0O3RleHQtZGVjb3JhdGlvbjpub25lO21heC13aWR0aDoxMDAlO3Bvc2l0aW9uOnJlbGF0aXZlfS5tYXQtbWVudS1pdGVtOjotbW96LWZvY3VzLWlubmVye2JvcmRlcjowfS5tYXQtbWVudS1pdGVtW2Rpc2FibGVkXXtjdXJzb3I6ZGVmYXVsdH1bZGlyPXJ0bF0gLm1hdC1tZW51LWl0ZW17dGV4dC1hbGlnbjpyaWdodH0ubWF0LW1lbnUtaXRlbSAubWF0LWljb257bWFyZ2luLXJpZ2h0OjE2cHg7dmVydGljYWwtYWxpZ246bWlkZGxlfS5tYXQtbWVudS1pdGVtIC5tYXQtaWNvbiBzdmd7dmVydGljYWwtYWxpZ246dG9wfVtkaXI9cnRsXSAubWF0LW1lbnUtaXRlbSAubWF0LWljb257bWFyZ2luLWxlZnQ6MTZweDttYXJnaW4tcmlnaHQ6MH0ubWF0LW1lbnUtaXRlbVtkaXNhYmxlZF06OmFmdGVye2Rpc3BsYXk6YmxvY2s7cG9zaXRpb246YWJzb2x1dGU7Y29udGVudDoiIjt0b3A6MDtsZWZ0OjA7Ym90dG9tOjA7cmlnaHQ6MH0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtbWVudS1pdGVte21hcmdpbi10b3A6MXB4fS5tYXQtbWVudS1pdGVtLXN1Ym1lbnUtdHJpZ2dlcntwYWRkaW5nLXJpZ2h0OjMycHh9W2Rpcj1ydGxdIC5tYXQtbWVudS1pdGVtLXN1Ym1lbnUtdHJpZ2dlcntwYWRkaW5nLXJpZ2h0OjE2cHg7cGFkZGluZy1sZWZ0OjMycHh9Lm1hdC1tZW51LXN1Ym1lbnUtaWNvbntwb3NpdGlvbjphYnNvbHV0ZTt0b3A6NTAlO3JpZ2h0OjE2cHg7dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTUwJSk7d2lkdGg6NXB4O2hlaWdodDoxMHB4O2ZpbGw6Y3VycmVudENvbG9yfVtkaXI9cnRsXSAubWF0LW1lbnUtc3VibWVudS1pY29ue3JpZ2h0OmF1dG87bGVmdDoxNnB4O3RyYW5zZm9ybTp0cmFuc2xhdGVZKC01MCUpIHNjYWxlWCgtMSl9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LW1lbnUtc3VibWVudS1pY29ue2ZpbGw6Q2FudmFzVGV4dH1idXR0b24ubWF0LW1lbnUtaXRlbXt3aWR0aDoxMDAlfS5tYXQtbWVudS1pdGVtIC5tYXQtbWVudS1yaXBwbGV7dG9wOjA7bGVmdDowO3JpZ2h0OjA7Ym90dG9tOjA7cG9zaXRpb246YWJzb2x1dGU7cG9pbnRlci1ldmVudHM6bm9uZX0nXSxlbmNhcHN1bGF0aW9uOjIsZGF0YTp7YW5pbWF0aW9uOltLMi50cmFuc2Zvcm1NZW51LEsyLmZhZGVJbkl0ZW1zXX0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxfaWU9bmV3IHBlKCJtYXQtbWVudS1zY3JvbGwtc3RyYXRlZ3kiKSxwTGU9e3Byb3ZpZGU6X2llLGRlcHM6W3RyXSx1c2VGYWN0b3J5OmZ1bmN0aW9uKG4pe3JldHVybigpPT5uLnNjcm9sbFN0cmF0ZWdpZXMucmVwb3NpdGlvbigpfX0sZmllPWxhKHtwYXNzaXZlOiEwfSksaExlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEsbCxjLHUpe3RoaXMuX292ZXJsYXk9ZSx0aGlzLl9lbGVtZW50PWksdGhpcy5fdmlld0NvbnRhaW5lclJlZj1yLHRoaXMuX21lbnVJdGVtSW5zdGFuY2U9YSx0aGlzLl9kaXI9bCx0aGlzLl9mb2N1c01vbml0b3I9Yyx0aGlzLl9uZ1pvbmU9dSx0aGlzLl9vdmVybGF5UmVmPW51bGwsdGhpcy5fbWVudU9wZW49ITEsdGhpcy5fY2xvc2luZ0FjdGlvbnNTdWJzY3JpcHRpb249U24uRU1QVFksdGhpcy5faG92ZXJTdWJzY3JpcHRpb249U24uRU1QVFksdGhpcy5fbWVudUNsb3NlU3Vic2NyaXB0aW9uPVNuLkVNUFRZLHRoaXMuX2hhbmRsZVRvdWNoU3RhcnQ9ZD0+e2V3KGQpfHwodGhpcy5fb3BlbmVkQnk9InRvdWNoIil9LHRoaXMuX29wZW5lZEJ5PXZvaWQgMCx0aGlzLnJlc3RvcmVGb2N1cz0hMCx0aGlzLm1lbnVPcGVuZWQ9bmV3IEcsdGhpcy5vbk1lbnVPcGVuPXRoaXMubWVudU9wZW5lZCx0aGlzLm1lbnVDbG9zZWQ9bmV3IEcsdGhpcy5vbk1lbnVDbG9zZT10aGlzLm1lbnVDbG9zZWQsdGhpcy5fc2Nyb2xsU3RyYXRlZ3k9byx0aGlzLl9wYXJlbnRNYXRlcmlhbE1lbnU9cyBpbnN0YW5jZW9mIHl3P3M6dm9pZCAwLGkubmF0aXZlRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCJ0b3VjaHN0YXJ0Iix0aGlzLl9oYW5kbGVUb3VjaFN0YXJ0LGZpZSksYSYmKGEuX3RyaWdnZXJzU3VibWVudT10aGlzLnRyaWdnZXJzU3VibWVudSgpKX1nZXQgX2RlcHJlY2F0ZWRNYXRNZW51VHJpZ2dlckZvcigpe3JldHVybiB0aGlzLm1lbnV9c2V0IF9kZXByZWNhdGVkTWF0TWVudVRyaWdnZXJGb3IoZSl7dGhpcy5tZW51PWV9Z2V0IG1lbnUoKXtyZXR1cm4gdGhpcy5fbWVudX1zZXQgbWVudShlKXtlIT09dGhpcy5fbWVudSYmKHRoaXMuX21lbnU9ZSx0aGlzLl9tZW51Q2xvc2VTdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKSxlJiYodGhpcy5fbWVudUNsb3NlU3Vic2NyaXB0aW9uPWUuY2xvc2Uuc3Vic2NyaWJlKGk9Pnt0aGlzLl9kZXN0cm95TWVudShpKSwoImNsaWNrIj09PWl8fCJ0YWIiPT09aSkmJnRoaXMuX3BhcmVudE1hdGVyaWFsTWVudSYmdGhpcy5fcGFyZW50TWF0ZXJpYWxNZW51LmNsb3NlZC5lbWl0KGkpfSkpKX1uZ0FmdGVyQ29udGVudEluaXQoKXt0aGlzLl9oYW5kbGVIb3ZlcigpfW5nT25EZXN0cm95KCl7dGhpcy5fb3ZlcmxheVJlZiYmKHRoaXMuX292ZXJsYXlSZWYuZGlzcG9zZSgpLHRoaXMuX292ZXJsYXlSZWY9bnVsbCksdGhpcy5fZWxlbWVudC5uYXRpdmVFbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoInRvdWNoc3RhcnQiLHRoaXMuX2hhbmRsZVRvdWNoU3RhcnQsZmllKSx0aGlzLl9tZW51Q2xvc2VTdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKSx0aGlzLl9jbG9zaW5nQWN0aW9uc1N1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX2hvdmVyU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCl9Z2V0IG1lbnVPcGVuKCl7cmV0dXJuIHRoaXMuX21lbnVPcGVufWdldCBkaXIoKXtyZXR1cm4gdGhpcy5fZGlyJiYicnRsIj09PXRoaXMuX2Rpci52YWx1ZT8icnRsIjoibHRyIn10cmlnZ2Vyc1N1Ym1lbnUoKXtyZXR1cm4hKCF0aGlzLl9tZW51SXRlbUluc3RhbmNlfHwhdGhpcy5fcGFyZW50TWF0ZXJpYWxNZW51KX10b2dnbGVNZW51KCl7cmV0dXJuIHRoaXMuX21lbnVPcGVuP3RoaXMuY2xvc2VNZW51KCk6dGhpcy5vcGVuTWVudSgpfW9wZW5NZW51KCl7bGV0IGU9dGhpcy5tZW51O2lmKHRoaXMuX21lbnVPcGVufHwhZSlyZXR1cm47bGV0IGk9dGhpcy5fY3JlYXRlT3ZlcmxheShlKSxyPWkuZ2V0Q29uZmlnKCksbz1yLnBvc2l0aW9uU3RyYXRlZ3k7dGhpcy5fc2V0UG9zaXRpb24oZSxvKSxyLmhhc0JhY2tkcm9wPW51bGw9PWUuaGFzQmFja2Ryb3A/IXRoaXMudHJpZ2dlcnNTdWJtZW51KCk6ZS5oYXNCYWNrZHJvcCxpLmF0dGFjaCh0aGlzLl9nZXRQb3J0YWwoZSkpLGUubGF6eUNvbnRlbnQmJmUubGF6eUNvbnRlbnQuYXR0YWNoKHRoaXMubWVudURhdGEpLHRoaXMuX2Nsb3NpbmdBY3Rpb25zU3Vic2NyaXB0aW9uPXRoaXMuX21lbnVDbG9zaW5nQWN0aW9ucygpLnN1YnNjcmliZSgoKT0+dGhpcy5jbG9zZU1lbnUoKSksdGhpcy5faW5pdE1lbnUoZSksZSBpbnN0YW5jZW9mIHl3JiYoZS5fc3RhcnRBbmltYXRpb24oKSxlLl9kaXJlY3REZXNjZW5kYW50SXRlbXMuY2hhbmdlcy5waXBlKHN0KGUuY2xvc2UpKS5zdWJzY3JpYmUoKCk9PntvLndpdGhMb2NrZWRQb3NpdGlvbighMSkucmVhcHBseUxhc3RQb3NpdGlvbigpLG8ud2l0aExvY2tlZFBvc2l0aW9uKCEwKX0pKX1jbG9zZU1lbnUoKXt0aGlzLm1lbnU/LmNsb3NlLmVtaXQoKX1mb2N1cyhlLGkpe3RoaXMuX2ZvY3VzTW9uaXRvciYmZT90aGlzLl9mb2N1c01vbml0b3IuZm9jdXNWaWEodGhpcy5fZWxlbWVudCxlLGkpOnRoaXMuX2VsZW1lbnQubmF0aXZlRWxlbWVudC5mb2N1cyhpKX11cGRhdGVQb3NpdGlvbigpe3RoaXMuX292ZXJsYXlSZWY/LnVwZGF0ZVBvc2l0aW9uKCl9X2Rlc3Ryb3lNZW51KGUpe2lmKCF0aGlzLl9vdmVybGF5UmVmfHwhdGhpcy5tZW51T3BlbilyZXR1cm47bGV0IGk9dGhpcy5tZW51O3RoaXMuX2Nsb3NpbmdBY3Rpb25zU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5fb3ZlcmxheVJlZi5kZXRhY2goKSx0aGlzLnJlc3RvcmVGb2N1cyYmKCJrZXlkb3duIj09PWV8fCF0aGlzLl9vcGVuZWRCeXx8IXRoaXMudHJpZ2dlcnNTdWJtZW51KCkpJiZ0aGlzLmZvY3VzKHRoaXMuX29wZW5lZEJ5KSx0aGlzLl9vcGVuZWRCeT12b2lkIDAsaSBpbnN0YW5jZW9mIHl3PyhpLl9yZXNldEFuaW1hdGlvbigpLGkubGF6eUNvbnRlbnQ/aS5fYW5pbWF0aW9uRG9uZS5waXBlKFllKHI9PiJ2b2lkIj09PXIudG9TdGF0ZSksUXQoMSksc3QoaS5sYXp5Q29udGVudC5fYXR0YWNoZWQpKS5zdWJzY3JpYmUoe25leHQ6KCk9PmkubGF6eUNvbnRlbnQuZGV0YWNoKCksY29tcGxldGU6KCk9PnRoaXMuX3NldElzTWVudU9wZW4oITEpfSk6dGhpcy5fc2V0SXNNZW51T3BlbighMSkpOih0aGlzLl9zZXRJc01lbnVPcGVuKCExKSxpPy5sYXp5Q29udGVudD8uZGV0YWNoKCkpfV9pbml0TWVudShlKXtlLnBhcmVudE1lbnU9dGhpcy50cmlnZ2Vyc1N1Ym1lbnUoKT90aGlzLl9wYXJlbnRNYXRlcmlhbE1lbnU6dm9pZCAwLGUuZGlyZWN0aW9uPXRoaXMuZGlyLHRoaXMuX3NldE1lbnVFbGV2YXRpb24oZSksZS5mb2N1c0ZpcnN0SXRlbSh0aGlzLl9vcGVuZWRCeXx8InByb2dyYW0iKSx0aGlzLl9zZXRJc01lbnVPcGVuKCEwKX1fc2V0TWVudUVsZXZhdGlvbihlKXtpZihlLnNldEVsZXZhdGlvbil7bGV0IGk9MCxyPWUucGFyZW50TWVudTtmb3IoO3I7KWkrKyxyPXIucGFyZW50TWVudTtlLnNldEVsZXZhdGlvbihpKX19X3NldElzTWVudU9wZW4oZSl7dGhpcy5fbWVudU9wZW49ZSx0aGlzLl9tZW51T3Blbj90aGlzLm1lbnVPcGVuZWQuZW1pdCgpOnRoaXMubWVudUNsb3NlZC5lbWl0KCksdGhpcy50cmlnZ2Vyc1N1Ym1lbnUoKSYmdGhpcy5fbWVudUl0ZW1JbnN0YW5jZS5fc2V0SGlnaGxpZ2h0ZWQoZSl9X2NyZWF0ZU92ZXJsYXkoZSl7aWYoIXRoaXMuX292ZXJsYXlSZWYpe2xldCBpPXRoaXMuX2dldE92ZXJsYXlDb25maWcoZSk7dGhpcy5fc3Vic2NyaWJlVG9Qb3NpdGlvbnMoZSxpLnBvc2l0aW9uU3RyYXRlZ3kpLHRoaXMuX292ZXJsYXlSZWY9dGhpcy5fb3ZlcmxheS5jcmVhdGUoaSksdGhpcy5fb3ZlcmxheVJlZi5rZXlkb3duRXZlbnRzKCkuc3Vic2NyaWJlKCl9cmV0dXJuIHRoaXMuX292ZXJsYXlSZWZ9X2dldE92ZXJsYXlDb25maWcoZSl7cmV0dXJuIG5ldyBzYyh7cG9zaXRpb25TdHJhdGVneTp0aGlzLl9vdmVybGF5LnBvc2l0aW9uKCkuZmxleGlibGVDb25uZWN0ZWRUbyh0aGlzLl9lbGVtZW50KS53aXRoTG9ja2VkUG9zaXRpb24oKS53aXRoR3Jvd0FmdGVyT3BlbigpLndpdGhUcmFuc2Zvcm1PcmlnaW5PbigiLm1hdC1tZW51LXBhbmVsLCAubWF0LW1kYy1tZW51LXBhbmVsIiksYmFja2Ryb3BDbGFzczplLmJhY2tkcm9wQ2xhc3N8fCJjZGstb3ZlcmxheS10cmFuc3BhcmVudC1iYWNrZHJvcCIscGFuZWxDbGFzczplLm92ZXJsYXlQYW5lbENsYXNzLHNjcm9sbFN0cmF0ZWd5OnRoaXMuX3Njcm9sbFN0cmF0ZWd5KCksZGlyZWN0aW9uOnRoaXMuX2Rpcn0pfV9zdWJzY3JpYmVUb1Bvc2l0aW9ucyhlLGkpe2Uuc2V0UG9zaXRpb25DbGFzc2VzJiZpLnBvc2l0aW9uQ2hhbmdlcy5zdWJzY3JpYmUocj0+e2xldCBvPSJzdGFydCI9PT1yLmNvbm5lY3Rpb25QYWlyLm92ZXJsYXlYPyJhZnRlciI6ImJlZm9yZSIscz0idG9wIj09PXIuY29ubmVjdGlvblBhaXIub3ZlcmxheVk/ImJlbG93IjoiYWJvdmUiO3RoaXMuX25nWm9uZT90aGlzLl9uZ1pvbmUucnVuKCgpPT5lLnNldFBvc2l0aW9uQ2xhc3NlcyhvLHMpKTplLnNldFBvc2l0aW9uQ2xhc3NlcyhvLHMpfSl9X3NldFBvc2l0aW9uKGUsaSl7bGV0W3Isb109ImJlZm9yZSI9PT1lLnhQb3NpdGlvbj9bImVuZCIsInN0YXJ0Il06WyJzdGFydCIsImVuZCJdLFtzLGFdPSJhYm92ZSI9PT1lLnlQb3NpdGlvbj9bImJvdHRvbSIsInRvcCJdOlsidG9wIiwiYm90dG9tIl0sW2wsY109W3MsYV0sW3UsZF09W3Isb10scD0wO2lmKHRoaXMudHJpZ2dlcnNTdWJtZW51KCkpe2lmKGQ9cj0iYmVmb3JlIj09PWUueFBvc2l0aW9uPyJzdGFydCI6ImVuZCIsbz11PSJlbmQiPT09cj8ic3RhcnQiOiJlbmQiLHRoaXMuX3BhcmVudE1hdGVyaWFsTWVudSl7aWYobnVsbD09dGhpcy5fcGFyZW50SW5uZXJQYWRkaW5nKXtsZXQgaD10aGlzLl9wYXJlbnRNYXRlcmlhbE1lbnUuaXRlbXMuZmlyc3Q7dGhpcy5fcGFyZW50SW5uZXJQYWRkaW5nPWg/aC5fZ2V0SG9zdEVsZW1lbnQoKS5vZmZzZXRUb3A6MH1wPSJib3R0b20iPT09cz90aGlzLl9wYXJlbnRJbm5lclBhZGRpbmc6LXRoaXMuX3BhcmVudElubmVyUGFkZGluZ319ZWxzZSBlLm92ZXJsYXBUcmlnZ2VyfHwobD0idG9wIj09PXM/ImJvdHRvbSI6InRvcCIsYz0idG9wIj09PWE/ImJvdHRvbSI6InRvcCIpO2kud2l0aFBvc2l0aW9ucyhbe29yaWdpblg6cixvcmlnaW5ZOmwsb3ZlcmxheVg6dSxvdmVybGF5WTpzLG9mZnNldFk6cH0se29yaWdpblg6byxvcmlnaW5ZOmwsb3ZlcmxheVg6ZCxvdmVybGF5WTpzLG9mZnNldFk6cH0se29yaWdpblg6cixvcmlnaW5ZOmMsb3ZlcmxheVg6dSxvdmVybGF5WTphLG9mZnNldFk6LXB9LHtvcmlnaW5YOm8sb3JpZ2luWTpjLG92ZXJsYXlYOmQsb3ZlcmxheVk6YSxvZmZzZXRZOi1wfV0pfV9tZW51Q2xvc2luZ0FjdGlvbnMoKXtsZXQgZT10aGlzLl9vdmVybGF5UmVmLmJhY2tkcm9wQ2xpY2soKSxpPXRoaXMuX292ZXJsYXlSZWYuZGV0YWNobWVudHMoKTtyZXR1cm4gSnQoZSx0aGlzLl9wYXJlbnRNYXRlcmlhbE1lbnU/dGhpcy5fcGFyZW50TWF0ZXJpYWxNZW51LmNsb3NlZDpYdCgpLHRoaXMuX3BhcmVudE1hdGVyaWFsTWVudT90aGlzLl9wYXJlbnRNYXRlcmlhbE1lbnUuX2hvdmVyZWQoKS5waXBlKFllKHM9PnMhPT10aGlzLl9tZW51SXRlbUluc3RhbmNlKSxZZSgoKT0+dGhpcy5fbWVudU9wZW4pKTpYdCgpLGkpfV9oYW5kbGVNb3VzZWRvd24oZSl7JE0oZSl8fCh0aGlzLl9vcGVuZWRCeT0wPT09ZS5idXR0b24/Im1vdXNlIjp2b2lkIDAsdGhpcy50cmlnZ2Vyc1N1Ym1lbnUoKSYmZS5wcmV2ZW50RGVmYXVsdCgpKX1faGFuZGxlS2V5ZG93bihlKXtsZXQgaT1lLmtleUNvZGU7KDEzPT09aXx8MzI9PT1pKSYmKHRoaXMuX29wZW5lZEJ5PSJrZXlib2FyZCIpLHRoaXMudHJpZ2dlcnNTdWJtZW51KCkmJigzOT09PWkmJiJsdHIiPT09dGhpcy5kaXJ8fDM3PT09aSYmInJ0bCI9PT10aGlzLmRpcikmJih0aGlzLl9vcGVuZWRCeT0ia2V5Ym9hcmQiLHRoaXMub3Blbk1lbnUoKSl9X2hhbmRsZUNsaWNrKGUpe3RoaXMudHJpZ2dlcnNTdWJtZW51KCk/KGUuc3RvcFByb3BhZ2F0aW9uKCksdGhpcy5vcGVuTWVudSgpKTp0aGlzLnRvZ2dsZU1lbnUoKX1faGFuZGxlSG92ZXIoKXshdGhpcy50cmlnZ2Vyc1N1Ym1lbnUoKXx8IXRoaXMuX3BhcmVudE1hdGVyaWFsTWVudXx8KHRoaXMuX2hvdmVyU3Vic2NyaXB0aW9uPXRoaXMuX3BhcmVudE1hdGVyaWFsTWVudS5faG92ZXJlZCgpLnBpcGUoWWUoZT0+ZT09PXRoaXMuX21lbnVJdGVtSW5zdGFuY2UmJiFlLmRpc2FibGVkKSxPbCgwLGYwKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5fb3BlbmVkQnk9Im1vdXNlIix0aGlzLm1lbnUgaW5zdGFuY2VvZiB5dyYmdGhpcy5tZW51Ll9pc0FuaW1hdGluZz90aGlzLm1lbnUuX2FuaW1hdGlvbkRvbmUucGlwZShRdCgxKSxPbCgwLGYwKSxzdCh0aGlzLl9wYXJlbnRNYXRlcmlhbE1lbnUuX2hvdmVyZWQoKSkpLnN1YnNjcmliZSgoKT0+dGhpcy5vcGVuTWVudSgpKTp0aGlzLm9wZW5NZW51KCl9KSl9X2dldFBvcnRhbChlKXtyZXR1cm4oIXRoaXMuX3BvcnRhbHx8dGhpcy5fcG9ydGFsLnRlbXBsYXRlUmVmIT09ZS50ZW1wbGF0ZVJlZikmJih0aGlzLl9wb3J0YWw9bmV3IGtzKGUudGVtcGxhdGVSZWYsdGhpcy5fdmlld0NvbnRhaW5lclJlZikpLHRoaXMuX3BvcnRhbH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTSh0ciksTShSZSksTShPaSksTShfaWUpLE0oJEgsOCksTShudSwxMCksTSgkaSw4KSxNKEZyKSxNKF90KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4saG9zdFZhcnM6Myxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJlAoImNsaWNrIixmdW5jdGlvbihvKXtyZXR1cm4gaS5faGFuZGxlQ2xpY2sobyl9KSgibW91c2Vkb3duIixmdW5jdGlvbihvKXtyZXR1cm4gaS5faGFuZGxlTW91c2Vkb3duKG8pfSkoImtleWRvd24iLGZ1bmN0aW9uKG8pe3JldHVybiBpLl9oYW5kbGVLZXlkb3duKG8pfSksMiZlJiZ6ZSgiYXJpYS1oYXNwb3B1cCIsaS5tZW51PyJtZW51IjpudWxsKSgiYXJpYS1leHBhbmRlZCIsaS5tZW51T3Blbnx8bnVsbCkoImFyaWEtY29udHJvbHMiLGkubWVudU9wZW4/aS5tZW51LnBhbmVsSWQ6bnVsbCl9LGlucHV0czp7X2RlcHJlY2F0ZWRNYXRNZW51VHJpZ2dlckZvcjpbIm1hdC1tZW51LXRyaWdnZXItZm9yIiwiX2RlcHJlY2F0ZWRNYXRNZW51VHJpZ2dlckZvciJdLG1lbnU6WyJtYXRNZW51VHJpZ2dlckZvciIsIm1lbnUiXSxtZW51RGF0YTpbIm1hdE1lbnVUcmlnZ2VyRGF0YSIsIm1lbnVEYXRhIl0scmVzdG9yZUZvY3VzOlsibWF0TWVudVRyaWdnZXJSZXN0b3JlRm9jdXMiLCJyZXN0b3JlRm9jdXMiXX0sb3V0cHV0czp7bWVudU9wZW5lZDoibWVudU9wZW5lZCIsb25NZW51T3Blbjoib25NZW51T3BlbiIsbWVudUNsb3NlZDoibWVudUNsb3NlZCIsb25NZW51Q2xvc2U6Im9uTWVudUNsb3NlIn19KSxufSkoKSxmZD0oKCk9PntjbGFzcyBuIGV4dGVuZHMgaExle31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdC1tZW51LXRyaWdnZXItZm9yIiwiIl0sWyIiLCJtYXRNZW51VHJpZ2dlckZvciIsIiJdXSxob3N0QXR0cnM6WzEsIm1hdC1tZW51LXRyaWdnZXIiXSxleHBvcnRBczpbIm1hdE1lbnVUcmlnZ2VyIl0sZmVhdHVyZXM6W3R0XX0pLG59KSgpLHpoPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W3BMZV0saW1wb3J0czpbTWUsbG4sX2wsc3MsdWQsbG5dfSksbn0pKCk7ZnVuY3Rpb24gZkxlKG4sdCl7MSZuJiZPKDAsIm1hdC1pY29uIiw4KX1mdW5jdGlvbiBtTGUobix0KXsxJm4mJk8oMCwibWF0LWljb24iLDkpfWZ1bmN0aW9uIGdMZShuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsMTApfXZhciBVYT0oKCk9PihmdW5jdGlvbihuKXtuW24uREVGQVVMVD0wXT0iREVGQVVMVCIsbltuLkRBUktfTU9ERV9PTj0xXT0iREFSS19NT0RFX09OIixuW24uREFSS19NT0RFX09GRj0yXT0iREFSS19NT0RFX09GRiJ9KFVhfHwoVWE9e30pKSxVYSkpKCksdmllPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLkRhcmtNb2RlT3ZlcnJpZGU9VWEsdGhpcy5vbk92ZXJyaWRlQ2hhbmdlZD1uZXcgR31nZXRCdXR0b25UaXRsZSgpe2xldCBlO3N3aXRjaCh0aGlzLmRhcmtNb2RlT3ZlcnJpZGUpe2Nhc2UgVWEuREVGQVVMVDplPSJCcm93c2VyIGRlZmF1bHQiO2JyZWFrO2Nhc2UgVWEuREFSS19NT0RFX09OOmU9IkRhcmsgbW9kZSI7YnJlYWs7Y2FzZSBVYS5EQVJLX01PREVfT0ZGOmU9IkxpZ2h0IG1vZGUifXJldHVybmBDdXJyZW50IG1vZGU6IFske2V9XS4gU3dpdGNoIGJldHdlZW4gYnJvd3NlciBkZWZhdWx0LCBsaWdodCwgb3IgZGFyayB0aGVtZS5gfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJhcHAtaGVhZGVyLWRhcmstbW9kZS10b2dnbGUtY29tcG9uZW50Il1dLGlucHV0czp7ZGFya01vZGVPdmVycmlkZToiZGFya01vZGVPdmVycmlkZSJ9LG91dHB1dHM6e29uT3ZlcnJpZGVDaGFuZ2VkOiJvbk92ZXJyaWRlQ2hhbmdlZCJ9LGRlY2xzOjE1LHZhcnM6Nixjb25zdHM6W1sibWF0LWljb24tYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsIk1lbnUgZm9yIGNoYW5naW5nIGxpZ2h0IG9yIGRhcmsgdGhlbWUiLDMsIm1hdE1lbnVUcmlnZ2VyRm9yIiwibmdTd2l0Y2giLCJ0aXRsZSJdLFsic3ZnSWNvbiIsImJyaWdodG5lc3NfNl8yNHB4Iiw0LCJuZ1N3aXRjaENhc2UiXSxbInN2Z0ljb24iLCJsaWdodF9tb2RlXzI0cHgiLDQsIm5nU3dpdGNoQ2FzZSJdLFsic3ZnSWNvbiIsImRhcmtfbW9kZV8yNHB4Iiw0LCJuZ1N3aXRjaENhc2UiXSxbIm1lbnUiLCJtYXRNZW51Il0sWyJtYXQtbWVudS1pdGVtIiwiIiwidGl0bGUiLCJTZXQgdGhlIHRoZW1lIHRvIG1hdGNoIHRoZSBkZWZhdWx0IG1vZGUgaW4gdGhlIGJyb3dzZXIuIiwzLCJjbGljayJdLFsibWF0LW1lbnUtaXRlbSIsIiIsInRpdGxlIiwiRm9yY2UgbGlnaHQgVGVuc29yQm9hcmQgdGhlbWUuIiwzLCJjbGljayJdLFsibWF0LW1lbnUtaXRlbSIsIiIsInRpdGxlIiwiRm9yY2UgZGFyayBUZW5zb3JCb2FyZCB0aGVtZS4iLDMsImNsaWNrIl0sWyJzdmdJY29uIiwiYnJpZ2h0bmVzc182XzI0cHgiXSxbInN2Z0ljb24iLCJsaWdodF9tb2RlXzI0cHgiXSxbInN2Z0ljb24iLCJkYXJrX21vZGVfMjRweCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiYnV0dG9uIiwwKSxFKDEsZkxlLDEsMCwibWF0LWljb24iLDEpLEUoMixtTGUsMSwwLCJtYXQtaWNvbiIsMiksRSgzLGdMZSwxLDAsIm1hdC1pY29uIiwzKSx2KCksXyg0LCJtYXQtbWVudSIsbnVsbCw0KSg2LCJidXR0b24iLDUpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uT3ZlcnJpZGVDaGFuZ2VkLmVtaXQoaS5EYXJrTW9kZU92ZXJyaWRlLkRFRkFVTFQpfSksXyg3LCJsYWJlbCIpLEEoOCwiQnJvd3NlciBkZWZhdWx0IiksdigpKCksXyg5LCJidXR0b24iLDYpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uT3ZlcnJpZGVDaGFuZ2VkLmVtaXQoaS5EYXJrTW9kZU92ZXJyaWRlLkRBUktfTU9ERV9PRkYpfSksXygxMCwibGFiZWwiKSxBKDExLCJMaWdodCIpLHYoKSgpLF8oMTIsImJ1dHRvbiIsNyksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25PdmVycmlkZUNoYW5nZWQuZW1pdChpLkRhcmtNb2RlT3ZlcnJpZGUuREFSS19NT0RFX09OKX0pLF8oMTMsImxhYmVsIiksQSgxNCwiRGFyayIpLHYoKSgpKCkpLDImZSYmKHkoIm1hdE1lbnVUcmlnZ2VyRm9yIiwkZSg1KSkoIm5nU3dpdGNoIixpLmRhcmtNb2RlT3ZlcnJpZGUpKCJ0aXRsZSIsaS5nZXRCdXR0b25UaXRsZSgpKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsaS5EYXJrTW9kZU92ZXJyaWRlLkRFRkFVTFQpLEMoMSkseSgibmdTd2l0Y2hDYXNlIixpLkRhcmtNb2RlT3ZlcnJpZGUuREFSS19NT0RFX09GRiksQygxKSx5KCJuZ1N3aXRjaENhc2UiLGkuRGFya01vZGVPdmVycmlkZS5EQVJLX01PREVfT04pKX0sZGVwZW5kZW5jaWVzOltfbixHdCxoZCxudSxmZCxDcixVcl0sZW5jYXBzdWxhdGlvbjoyfSksbn0pKCkseWllPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuZGFya01vZGVPdmVycmlkZSQ9dGhpcy5zdG9yZS5zZWxlY3QoR0EpLnBpcGUoTChpPT5udWxsPT09aT9VYS5ERUZBVUxUOmk/VWEuREFSS19NT0RFX09OOlVhLkRBUktfTU9ERV9PRkYpKX1jaGFuZ2VEYXJrTW9kZShlKXtsZXQgaT1udWxsO3N3aXRjaChlKXtjYXNlIFVhLkRFRkFVTFQ6aT1udWxsO2JyZWFrO2Nhc2UgVWEuREFSS19NT0RFX09GRjppPSExO2JyZWFrO2Nhc2UgVWEuREFSS19NT0RFX09OOmk9ITB9dGhpcy5zdG9yZS5kaXNwYXRjaChNSSh7ZW5hYmxlRGFya01vZGU6aX0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImFwcC1oZWFkZXItZGFyay1tb2RlLXRvZ2dsZSJdXSxkZWNsczoyLHZhcnM6Myxjb25zdHM6W1szLCJkYXJrTW9kZU92ZXJyaWRlIiwib25PdmVycmlkZUNoYW5nZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImFwcC1oZWFkZXItZGFyay1tb2RlLXRvZ2dsZS1jb21wb25lbnQiLDApLFAoIm9uT3ZlcnJpZGVDaGFuZ2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5jaGFuZ2VEYXJrTW9kZShvKX0pLEIoMSwiYXN5bmMiKSx2KCkpLDImZSYmeSgiZGFya01vZGVPdmVycmlkZSIsVSgxLDEsaS5kYXJrTW9kZU92ZXJyaWRlJCkpfSxkZXBlbmRlbmNpZXM6W3ZpZSxHZV0sZW5jYXBzdWxhdGlvbjoyfSksbn0pKCk7ZnVuY3Rpb24geUxlKG4sdCl7MSZuJiZWbigwKX12YXIgTWllPVsiKiJdO2Z1bmN0aW9uIGJMZShuLHQpe312YXIgeExlPWZ1bmN0aW9uKG4pe3JldHVybnthbmltYXRpb25EdXJhdGlvbjpufX0sQ0xlPWZ1bmN0aW9uKG4sdCl7cmV0dXJue3ZhbHVlOm4scGFyYW1zOnR9fSxNTGU9WyJ0YWJMaXN0Q29udGFpbmVyIl0sd0xlPVsidGFiTGlzdCJdLFNMZT1bInRhYkxpc3RJbm5lciJdLEVMZT1bIm5leHRQYWdpbmF0b3IiXSxUTGU9WyJwcmV2aW91c1BhZ2luYXRvciJdLERMZT1bInRhYkJvZHlXcmFwcGVyIl0sQUxlPVsidGFiSGVhZGVyIl07ZnVuY3Rpb24gSUxlKG4sdCl7fWZ1bmN0aW9uIFBMZShuLHQpezEmbiYmRSgwLElMZSwwLDAsIm5nLXRlbXBsYXRlIiwxMCksMiZuJiZ5KCJjZGtQb3J0YWxPdXRsZXQiLFMoKS4kaW1wbGljaXQudGVtcGxhdGVMYWJlbCl9ZnVuY3Rpb24gUkxlKG4sdCl7MSZuJiZBKDApLDImbiYmeXQoUygpLiRpbXBsaWNpdC50ZXh0TGFiZWwpfWZ1bmN0aW9uIE9MZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsNiksUCgiY2xpY2siLGZ1bmN0aW9uKCl7bGV0IHI9b2UoZSksbz1yLiRpbXBsaWNpdCxzPXIuaW5kZXgsYT1TKCksbD0kZSgxKTtyZXR1cm4gc2UoYS5faGFuZGxlQ2xpY2sobyxsLHMpKX0pKCJjZGtGb2N1c0NoYW5nZSIsZnVuY3Rpb24ocil7bGV0IHM9b2UoZSkuaW5kZXg7cmV0dXJuIHNlKFMoKS5fdGFiRm9jdXNDaGFuZ2VkKHIscykpfSksXygxLCJkaXYiLDcpLEUoMixQTGUsMSwxLCJuZy10ZW1wbGF0ZSIsOCksRSgzLFJMZSwxLDEsIm5nLXRlbXBsYXRlIixudWxsLDkscXQpLHYoKSgpfWlmKDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT10LmluZGV4LHI9JGUoNCksbz1TKCk7ZXQoIm1hdC10YWItbGFiZWwtYWN0aXZlIixvLnNlbGVjdGVkSW5kZXg9PT1pKSx5KCJpZCIsby5fZ2V0VGFiTGFiZWxJZChpKSkoIm5nQ2xhc3MiLGUubGFiZWxDbGFzcykoImRpc2FibGVkIixlLmRpc2FibGVkKSgibWF0UmlwcGxlRGlzYWJsZWQiLGUuZGlzYWJsZWR8fG8uZGlzYWJsZVJpcHBsZSksemUoInRhYkluZGV4IixvLl9nZXRUYWJJbmRleChlLGkpKSgiYXJpYS1wb3NpbnNldCIsaSsxKSgiYXJpYS1zZXRzaXplIixvLl90YWJzLmxlbmd0aCkoImFyaWEtY29udHJvbHMiLG8uX2dldFRhYkNvbnRlbnRJZChpKSkoImFyaWEtc2VsZWN0ZWQiLG8uc2VsZWN0ZWRJbmRleD09PWkpKCJhcmlhLWxhYmVsIixlLmFyaWFMYWJlbHx8bnVsbCkoImFyaWEtbGFiZWxsZWRieSIsIWUuYXJpYUxhYmVsJiZlLmFyaWFMYWJlbGxlZGJ5P2UuYXJpYUxhYmVsbGVkYnk6bnVsbCksQygyKSx5KCJuZ0lmIixlLnRlbXBsYXRlTGFiZWwpKCJuZ0lmRWxzZSIscil9fWZ1bmN0aW9uIGtMZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsIm1hdC10YWItYm9keSIsMTEpLFAoIl9vbkNlbnRlcmVkIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkuX3JlbW92ZVRhYkJvZHlXcmFwcGVySGVpZ2h0KCkpfSkoIl9vbkNlbnRlcmluZyIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5fc2V0VGFiQm9keVdyYXBwZXJIZWlnaHQocikpfSksdigpfWlmKDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT10LmluZGV4LHI9UygpO2V0KCJtYXQtdGFiLWJvZHktYWN0aXZlIixyLnNlbGVjdGVkSW5kZXg9PT1pKSx5KCJpZCIsci5fZ2V0VGFiQ29udGVudElkKGkpKSgibmdDbGFzcyIsZS5ib2R5Q2xhc3MpKCJjb250ZW50IixlLmNvbnRlbnQpKCJwb3NpdGlvbiIsZS5wb3NpdGlvbikoIm9yaWdpbiIsZS5vcmlnaW4pKCJhbmltYXRpb25EdXJhdGlvbiIsci5hbmltYXRpb25EdXJhdGlvbikoInByZXNlcnZlQ29udGVudCIsci5wcmVzZXJ2ZUNvbnRlbnQpLHplKCJ0YWJpbmRleCIsbnVsbCE9ci5jb250ZW50VGFiSW5kZXgmJnIuc2VsZWN0ZWRJbmRleD09PWk/ci5jb250ZW50VGFiSW5kZXg6bnVsbCkoImFyaWEtbGFiZWxsZWRieSIsci5fZ2V0VGFiTGFiZWxJZChpKSl9fXZhciBGTGU9bmV3IHBlKCJNYXRJbmtCYXJQb3NpdGlvbmVyIix7cHJvdmlkZWRJbjoicm9vdCIsZmFjdG9yeTpmdW5jdGlvbigpe3JldHVybiB0PT4oe2xlZnQ6dD8odC5vZmZzZXRMZWZ0fHwwKSsicHgiOiIwIix3aWR0aDp0Pyh0Lm9mZnNldFdpZHRofHwwKSsicHgiOiIwIn0pfX0pLGJpZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8pe3RoaXMuX2VsZW1lbnRSZWY9ZSx0aGlzLl9uZ1pvbmU9aSx0aGlzLl9pbmtCYXJQb3NpdGlvbmVyPXIsdGhpcy5fYW5pbWF0aW9uTW9kZT1vfWFsaWduVG9FbGVtZW50KGUpe3RoaXMuc2hvdygpLHRoaXMuX25nWm9uZS5ydW4oKCk9Pnt0aGlzLl9uZ1pvbmUub25TdGFibGUucGlwZShRdCgxKSkuc3Vic2NyaWJlKCgpPT57bGV0IGk9dGhpcy5faW5rQmFyUG9zaXRpb25lcihlKSxyPXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudDtyLnN0eWxlLmxlZnQ9aS5sZWZ0LHIuc3R5bGUud2lkdGg9aS53aWR0aH0pfSl9c2hvdygpe3RoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5zdHlsZS52aXNpYmlsaXR5PSJ2aXNpYmxlIn1oaWRlKCl7dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LnN0eWxlLnZpc2liaWxpdHk9ImhpZGRlbiJ9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0oX3QpLE0oRkxlKSxNKFBpLDgpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LWluay1iYXIiXV0saG9zdEF0dHJzOlsxLCJtYXQtaW5rLWJhciJdLGhvc3RWYXJzOjIsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZldCgiX21hdC1hbmltYXRpb24tbm9vcGFibGUiLCJOb29wQW5pbWF0aW9ucyI9PT1pLl9hbmltYXRpb25Nb2RlKX19KSxufSkoKSxMTGU9bmV3IHBlKCJNYXRUYWJDb250ZW50Iiksd2llPW5ldyBwZSgiTWF0VGFiTGFiZWwiKSxTaWU9bmV3IHBlKCJNQVRfVEFCIiksRWllPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBmdGV7Y29uc3RydWN0b3IoZSxpLHIpe3N1cGVyKGUsaSksdGhpcy5fY2xvc2VzdFRhYj1yfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFZpKSxNKE9pKSxNKFNpZSw4KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdC10YWItbGFiZWwiLCIiXSxbIiIsIm1hdFRhYkxhYmVsIiwiIl1dLGZlYXR1cmVzOlskdChbe3Byb3ZpZGU6d2llLHVzZUV4aXN0aW5nOm59XSksdHRdfSksbn0pKCksQkxlPXNvKGNsYXNze30pLFRpZT1uZXcgcGUoIk1BVF9UQUJfR1JPVVAiKSx4dz0oKCk9PntjbGFzcyBuIGV4dGVuZHMgQkxle2NvbnN0cnVjdG9yKGUsaSl7c3VwZXIoKSx0aGlzLl92aWV3Q29udGFpbmVyUmVmPWUsdGhpcy5fY2xvc2VzdFRhYkdyb3VwPWksdGhpcy50ZXh0TGFiZWw9IiIsdGhpcy5fY29udGVudFBvcnRhbD1udWxsLHRoaXMuX3N0YXRlQ2hhbmdlcz1uZXcga2UsdGhpcy5wb3NpdGlvbj1udWxsLHRoaXMub3JpZ2luPW51bGwsdGhpcy5pc0FjdGl2ZT0hMX1nZXQgdGVtcGxhdGVMYWJlbCgpe3JldHVybiB0aGlzLl90ZW1wbGF0ZUxhYmVsfXNldCB0ZW1wbGF0ZUxhYmVsKGUpe3RoaXMuX3NldFRlbXBsYXRlTGFiZWxJbnB1dChlKX1nZXQgY29udGVudCgpe3JldHVybiB0aGlzLl9jb250ZW50UG9ydGFsfW5nT25DaGFuZ2VzKGUpeyhlLmhhc093blByb3BlcnR5KCJ0ZXh0TGFiZWwiKXx8ZS5oYXNPd25Qcm9wZXJ0eSgiZGlzYWJsZWQiKSkmJnRoaXMuX3N0YXRlQ2hhbmdlcy5uZXh0KCl9bmdPbkRlc3Ryb3koKXt0aGlzLl9zdGF0ZUNoYW5nZXMuY29tcGxldGUoKX1uZ09uSW5pdCgpe3RoaXMuX2NvbnRlbnRQb3J0YWw9bmV3IGtzKHRoaXMuX2V4cGxpY2l0Q29udGVudHx8dGhpcy5faW1wbGljaXRDb250ZW50LHRoaXMuX3ZpZXdDb250YWluZXJSZWYpfV9zZXRUZW1wbGF0ZUxhYmVsSW5wdXQoZSl7ZSYmZS5fY2xvc2VzdFRhYj09PXRoaXMmJih0aGlzLl90ZW1wbGF0ZUxhYmVsPWUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKE9pKSxNKFRpZSw4KSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LXRhYiJdXSxjb250ZW50UXVlcmllczpmdW5jdGlvbihlLGkscil7aWYoMSZlJiYoRWkocix3aWUsNSksRWkocixMTGUsNyxWaSkpLDImZSl7bGV0IG87TmUobz1MZSgpKSYmKGkudGVtcGxhdGVMYWJlbD1vLmZpcnN0KSxOZShvPUxlKCkpJiYoaS5fZXhwbGljaXRDb250ZW50PW8uZmlyc3QpfX0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiZvdChWaSw3KSwyJmUpe2xldCByO05lKHI9TGUoKSkmJihpLl9pbXBsaWNpdENvbnRlbnQ9ci5maXJzdCl9fSxpbnB1dHM6e2Rpc2FibGVkOiJkaXNhYmxlZCIsdGV4dExhYmVsOlsibGFiZWwiLCJ0ZXh0TGFiZWwiXSxhcmlhTGFiZWw6WyJhcmlhLWxhYmVsIiwiYXJpYUxhYmVsIl0sYXJpYUxhYmVsbGVkYnk6WyJhcmlhLWxhYmVsbGVkYnkiLCJhcmlhTGFiZWxsZWRieSJdLGxhYmVsQ2xhc3M6ImxhYmVsQ2xhc3MiLGJvZHlDbGFzczoiYm9keUNsYXNzIn0sZXhwb3J0QXM6WyJtYXRUYWIiXSxmZWF0dXJlczpbJHQoW3twcm92aWRlOlNpZSx1c2VFeGlzdGluZzpufV0pLHR0LEZ0XSxuZ0NvbnRlbnRTZWxlY3RvcnM6TWllLGRlY2xzOjEsdmFyczowLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoeGkoKSxFKDAseUxlLDEsMCwibmctdGVtcGxhdGUiKSl9LGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpLFZMZT17dHJhbnNsYXRlVGFiOktyKCJ0cmFuc2xhdGVUYWIiLFtraSgiY2VudGVyLCB2b2lkLCBsZWZ0LW9yaWdpbi1jZW50ZXIsIHJpZ2h0LW9yaWdpbi1jZW50ZXIiLGduKHt0cmFuc2Zvcm06Im5vbmUifSkpLGtpKCJsZWZ0Iixnbih7dHJhbnNmb3JtOiJ0cmFuc2xhdGUzZCgtMTAwJSwgMCwgMCkiLG1pbkhlaWdodDoiMXB4Iix2aXNpYmlsaXR5OiJoaWRkZW4ifSkpLGtpKCJyaWdodCIsZ24oe3RyYW5zZm9ybToidHJhbnNsYXRlM2QoMTAwJSwgMCwgMCkiLG1pbkhlaWdodDoiMXB4Iix2aXNpYmlsaXR5OiJoaWRkZW4ifSkpLExpKCIqID0+IGxlZnQsICogPT4gcmlnaHQsIGxlZnQgPT4gY2VudGVyLCByaWdodCA9PiBjZW50ZXIiLGppKCJ7e2FuaW1hdGlvbkR1cmF0aW9ufX0gY3ViaWMtYmV6aWVyKDAuMzUsIDAsIDAuMjUsIDEpIikpLExpKCJ2b2lkID0+IGxlZnQtb3JpZ2luLWNlbnRlciIsW2duKHt0cmFuc2Zvcm06InRyYW5zbGF0ZTNkKC0xMDAlLCAwLCAwKSIsdmlzaWJpbGl0eToiaGlkZGVuIn0pLGppKCJ7e2FuaW1hdGlvbkR1cmF0aW9ufX0gY3ViaWMtYmV6aWVyKDAuMzUsIDAsIDAuMjUsIDEpIildKSxMaSgidm9pZCA9PiByaWdodC1vcmlnaW4tY2VudGVyIixbZ24oe3RyYW5zZm9ybToidHJhbnNsYXRlM2QoMTAwJSwgMCwgMCkiLHZpc2liaWxpdHk6ImhpZGRlbiJ9KSxqaSgie3thbmltYXRpb25EdXJhdGlvbn19IGN1YmljLWJlemllcigwLjM1LCAwLCAwLjI1LCAxKSIpXSldKX0sSExlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBkYXtjb25zdHJ1Y3RvcihlLGkscixvKXtzdXBlcihlLGksbyksdGhpcy5faG9zdD1yLHRoaXMuX2NlbnRlcmluZ1N1Yj1Tbi5FTVBUWSx0aGlzLl9sZWF2aW5nU3ViPVNuLkVNUFRZfW5nT25Jbml0KCl7c3VwZXIubmdPbkluaXQoKSx0aGlzLl9jZW50ZXJpbmdTdWI9dGhpcy5faG9zdC5fYmVmb3JlQ2VudGVyaW5nLnBpcGUoem4odGhpcy5faG9zdC5faXNDZW50ZXJQb3NpdGlvbih0aGlzLl9ob3N0Ll9wb3NpdGlvbikpKS5zdWJzY3JpYmUoZT0+e2UmJiF0aGlzLmhhc0F0dGFjaGVkKCkmJnRoaXMuYXR0YWNoKHRoaXMuX2hvc3QuX2NvbnRlbnQpfSksdGhpcy5fbGVhdmluZ1N1Yj10aGlzLl9ob3N0Ll9hZnRlckxlYXZpbmdDZW50ZXIuc3Vic2NyaWJlKCgpPT57dGhpcy5faG9zdC5wcmVzZXJ2ZUNvbnRlbnR8fHRoaXMuZGV0YWNoKCl9KX1uZ09uRGVzdHJveSgpe3N1cGVyLm5nT25EZXN0cm95KCksdGhpcy5fY2VudGVyaW5nU3ViLnVuc3Vic2NyaWJlKCksdGhpcy5fbGVhdmluZ1N1Yi51bnN1YnNjcmliZSgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKGdzKSxNKE9pKSxNKEpuKCgpPT5EaWUpKSxNKEh0KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdFRhYkJvZHlIb3N0IiwiIl1dLGZlYXR1cmVzOlt0dF19KSxufSkoKSxVTGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5fZWxlbWVudFJlZj1lLHRoaXMuX2Rpcj1pLHRoaXMuX2RpckNoYW5nZVN1YnNjcmlwdGlvbj1Tbi5FTVBUWSx0aGlzLl90cmFuc2xhdGVUYWJDb21wbGV0ZT1uZXcga2UsdGhpcy5fb25DZW50ZXJpbmc9bmV3IEcsdGhpcy5fYmVmb3JlQ2VudGVyaW5nPW5ldyBHLHRoaXMuX2FmdGVyTGVhdmluZ0NlbnRlcj1uZXcgRyx0aGlzLl9vbkNlbnRlcmVkPW5ldyBHKCEwKSx0aGlzLmFuaW1hdGlvbkR1cmF0aW9uPSI1MDBtcyIsdGhpcy5wcmVzZXJ2ZUNvbnRlbnQ9ITEsaSYmKHRoaXMuX2RpckNoYW5nZVN1YnNjcmlwdGlvbj1pLmNoYW5nZS5zdWJzY3JpYmUobz0+e3RoaXMuX2NvbXB1dGVQb3NpdGlvbkFuaW1hdGlvblN0YXRlKG8pLHIubWFya0ZvckNoZWNrKCl9KSksdGhpcy5fdHJhbnNsYXRlVGFiQ29tcGxldGUucGlwZSh5aSgobyxzKT0+by5mcm9tU3RhdGU9PT1zLmZyb21TdGF0ZSYmby50b1N0YXRlPT09cy50b1N0YXRlKSkuc3Vic2NyaWJlKG89Pnt0aGlzLl9pc0NlbnRlclBvc2l0aW9uKG8udG9TdGF0ZSkmJnRoaXMuX2lzQ2VudGVyUG9zaXRpb24odGhpcy5fcG9zaXRpb24pJiZ0aGlzLl9vbkNlbnRlcmVkLmVtaXQoKSx0aGlzLl9pc0NlbnRlclBvc2l0aW9uKG8uZnJvbVN0YXRlKSYmIXRoaXMuX2lzQ2VudGVyUG9zaXRpb24odGhpcy5fcG9zaXRpb24pJiZ0aGlzLl9hZnRlckxlYXZpbmdDZW50ZXIuZW1pdCgpfSl9c2V0IHBvc2l0aW9uKGUpe3RoaXMuX3Bvc2l0aW9uSW5kZXg9ZSx0aGlzLl9jb21wdXRlUG9zaXRpb25BbmltYXRpb25TdGF0ZSgpfW5nT25Jbml0KCl7ImNlbnRlciI9PXRoaXMuX3Bvc2l0aW9uJiZudWxsIT10aGlzLm9yaWdpbiYmKHRoaXMuX3Bvc2l0aW9uPXRoaXMuX2NvbXB1dGVQb3NpdGlvbkZyb21PcmlnaW4odGhpcy5vcmlnaW4pKX1uZ09uRGVzdHJveSgpe3RoaXMuX2RpckNoYW5nZVN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX3RyYW5zbGF0ZVRhYkNvbXBsZXRlLmNvbXBsZXRlKCl9X29uVHJhbnNsYXRlVGFiU3RhcnRlZChlKXtsZXQgaT10aGlzLl9pc0NlbnRlclBvc2l0aW9uKGUudG9TdGF0ZSk7dGhpcy5fYmVmb3JlQ2VudGVyaW5nLmVtaXQoaSksaSYmdGhpcy5fb25DZW50ZXJpbmcuZW1pdCh0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuY2xpZW50SGVpZ2h0KX1fZ2V0TGF5b3V0RGlyZWN0aW9uKCl7cmV0dXJuIHRoaXMuX2RpciYmInJ0bCI9PT10aGlzLl9kaXIudmFsdWU/InJ0bCI6Imx0ciJ9X2lzQ2VudGVyUG9zaXRpb24oZSl7cmV0dXJuImNlbnRlciI9PWV8fCJsZWZ0LW9yaWdpbi1jZW50ZXIiPT1lfHwicmlnaHQtb3JpZ2luLWNlbnRlciI9PWV9X2NvbXB1dGVQb3NpdGlvbkFuaW1hdGlvblN0YXRlKGU9dGhpcy5fZ2V0TGF5b3V0RGlyZWN0aW9uKCkpe3RoaXMuX3Bvc2l0aW9uPXRoaXMuX3Bvc2l0aW9uSW5kZXg8MD8ibHRyIj09ZT8ibGVmdCI6InJpZ2h0Ijp0aGlzLl9wb3NpdGlvbkluZGV4PjA/Imx0ciI9PWU/InJpZ2h0IjoibGVmdCI6ImNlbnRlciJ9X2NvbXB1dGVQb3NpdGlvbkZyb21PcmlnaW4oZSl7bGV0IGk9dGhpcy5fZ2V0TGF5b3V0RGlyZWN0aW9uKCk7cmV0dXJuImx0ciI9PWkmJmU8PTB8fCJydGwiPT1pJiZlPjA/ImxlZnQtb3JpZ2luLWNlbnRlciI6InJpZ2h0LW9yaWdpbi1jZW50ZXIifX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKCRpLDgpLE0obm4pKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixpbnB1dHM6e19jb250ZW50OlsiY29udGVudCIsIl9jb250ZW50Il0sb3JpZ2luOiJvcmlnaW4iLGFuaW1hdGlvbkR1cmF0aW9uOiJhbmltYXRpb25EdXJhdGlvbiIscHJlc2VydmVDb250ZW50OiJwcmVzZXJ2ZUNvbnRlbnQiLHBvc2l0aW9uOiJwb3NpdGlvbiJ9LG91dHB1dHM6e19vbkNlbnRlcmluZzoiX29uQ2VudGVyaW5nIixfYmVmb3JlQ2VudGVyaW5nOiJfYmVmb3JlQ2VudGVyaW5nIixfYWZ0ZXJMZWF2aW5nQ2VudGVyOiJfYWZ0ZXJMZWF2aW5nQ2VudGVyIixfb25DZW50ZXJlZDoiX29uQ2VudGVyZWQifX0pLG59KSgpLERpZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgVUxle2NvbnN0cnVjdG9yKGUsaSxyKXtzdXBlcihlLGkscil9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0oJGksOCksTShubikpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC10YWItYm9keSJdXSx2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJm90KGRhLDUpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkuX3BvcnRhbEhvc3Q9ci5maXJzdCl9fSxob3N0QXR0cnM6WzEsIm1hdC10YWItYm9keSJdLGZlYXR1cmVzOlt0dF0sZGVjbHM6Myx2YXJzOjYsY29uc3RzOltbImNka1Njcm9sbGFibGUiLCIiLDEsIm1hdC10YWItYm9keS1jb250ZW50Il0sWyJjb250ZW50IiwiIl0sWyJtYXRUYWJCb2R5SG9zdCIsIiJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IiwwLDEpLFAoIkB0cmFuc2xhdGVUYWIuc3RhcnQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLl9vblRyYW5zbGF0ZVRhYlN0YXJ0ZWQobyl9KSgiQHRyYW5zbGF0ZVRhYi5kb25lIixmdW5jdGlvbihvKXtyZXR1cm4gaS5fdHJhbnNsYXRlVGFiQ29tcGxldGUubmV4dChvKX0pLEUoMixiTGUsMCwwLCJuZy10ZW1wbGF0ZSIsMiksdigpKSwyJmUmJnkoIkB0cmFuc2xhdGVUYWIiLFFyKDMsQ0xlLGkuX3Bvc2l0aW9uLE9uKDEseExlLGkuYW5pbWF0aW9uRHVyYXRpb24pKSl9LGRlcGVuZGVuY2llczpbSExlXSxzdHlsZXM6WycubWF0LXRhYi1ib2R5LWNvbnRlbnR7aGVpZ2h0OjEwMCU7b3ZlcmZsb3c6YXV0b30ubWF0LXRhYi1ncm91cC1keW5hbWljLWhlaWdodCAubWF0LXRhYi1ib2R5LWNvbnRlbnR7b3ZlcmZsb3c6aGlkZGVufS5tYXQtdGFiLWJvZHktY29udGVudFtzdHlsZSo9InZpc2liaWxpdHk6IGhpZGRlbiJde2Rpc3BsYXk6bm9uZX0nXSxlbmNhcHN1bGF0aW9uOjIsZGF0YTp7YW5pbWF0aW9uOltWTGUudHJhbnNsYXRlVGFiXX19KSxufSkoKSxBaWU9bmV3IHBlKCJNQVRfVEFCU19DT05GSUciKSx6TGU9c28oY2xhc3N7fSksSWllPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyB6TGV7Y29uc3RydWN0b3IoZSl7c3VwZXIoKSx0aGlzLmVsZW1lbnRSZWY9ZX1mb2N1cygpe3RoaXMuZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LmZvY3VzKCl9Z2V0T2Zmc2V0TGVmdCgpe3JldHVybiB0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5vZmZzZXRMZWZ0fWdldE9mZnNldFdpZHRoKCl7cmV0dXJuIHRoaXMuZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50Lm9mZnNldFdpZHRofX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdFRhYkxhYmVsV3JhcHBlciIsIiJdXSxob3N0VmFyczozLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmKHplKCJhcmlhLWRpc2FibGVkIiwhIWkuZGlzYWJsZWQpLGV0KCJtYXQtdGFiLWRpc2FibGVkIixpLmRpc2FibGVkKSl9LGlucHV0czp7ZGlzYWJsZWQ6ImRpc2FibGVkIn0sZmVhdHVyZXM6W3R0XX0pLG59KSgpLHhpZT1sYSh7cGFzc2l2ZTohMH0pLFdMZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhLGwpe3RoaXMuX2VsZW1lbnRSZWY9ZSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj1pLHRoaXMuX3ZpZXdwb3J0UnVsZXI9cix0aGlzLl9kaXI9byx0aGlzLl9uZ1pvbmU9cyx0aGlzLl9wbGF0Zm9ybT1hLHRoaXMuX2FuaW1hdGlvbk1vZGU9bCx0aGlzLl9zY3JvbGxEaXN0YW5jZT0wLHRoaXMuX3NlbGVjdGVkSW5kZXhDaGFuZ2VkPSExLHRoaXMuX2Rlc3Ryb3llZD1uZXcga2UsdGhpcy5fc2hvd1BhZ2luYXRpb25Db250cm9scz0hMSx0aGlzLl9kaXNhYmxlU2Nyb2xsQWZ0ZXI9ITAsdGhpcy5fZGlzYWJsZVNjcm9sbEJlZm9yZT0hMCx0aGlzLl9zdG9wU2Nyb2xsaW5nPW5ldyBrZSx0aGlzLl9kaXNhYmxlUGFnaW5hdGlvbj0hMSx0aGlzLl9zZWxlY3RlZEluZGV4PTAsdGhpcy5zZWxlY3RGb2N1c2VkSW5kZXg9bmV3IEcsdGhpcy5pbmRleEZvY3VzZWQ9bmV3IEcscy5ydW5PdXRzaWRlQW5ndWxhcigoKT0+e19pKGUubmF0aXZlRWxlbWVudCwibW91c2VsZWF2ZSIpLnBpcGUoc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5fc3RvcEludGVydmFsKCl9KX0pfWdldCBkaXNhYmxlUGFnaW5hdGlvbigpe3JldHVybiB0aGlzLl9kaXNhYmxlUGFnaW5hdGlvbn1zZXQgZGlzYWJsZVBhZ2luYXRpb24oZSl7dGhpcy5fZGlzYWJsZVBhZ2luYXRpb249UnQoZSl9Z2V0IHNlbGVjdGVkSW5kZXgoKXtyZXR1cm4gdGhpcy5fc2VsZWN0ZWRJbmRleH1zZXQgc2VsZWN0ZWRJbmRleChlKXtlPUJpKGUpLHRoaXMuX3NlbGVjdGVkSW5kZXghPWUmJih0aGlzLl9zZWxlY3RlZEluZGV4Q2hhbmdlZD0hMCx0aGlzLl9zZWxlY3RlZEluZGV4PWUsdGhpcy5fa2V5TWFuYWdlciYmdGhpcy5fa2V5TWFuYWdlci51cGRhdGVBY3RpdmVJdGVtKGUpKX1uZ0FmdGVyVmlld0luaXQoKXtfaSh0aGlzLl9wcmV2aW91c1BhZ2luYXRvci5uYXRpdmVFbGVtZW50LCJ0b3VjaHN0YXJ0Iix4aWUpLnBpcGUoc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5faGFuZGxlUGFnaW5hdG9yUHJlc3MoImJlZm9yZSIpfSksX2kodGhpcy5fbmV4dFBhZ2luYXRvci5uYXRpdmVFbGVtZW50LCJ0b3VjaHN0YXJ0Iix4aWUpLnBpcGUoc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5faGFuZGxlUGFnaW5hdG9yUHJlc3MoImFmdGVyIil9KX1uZ0FmdGVyQ29udGVudEluaXQoKXtsZXQgZT10aGlzLl9kaXI/dGhpcy5fZGlyLmNoYW5nZTpYdCgibHRyIiksaT10aGlzLl92aWV3cG9ydFJ1bGVyLmNoYW5nZSgxNTApLHI9KCk9Pnt0aGlzLnVwZGF0ZVBhZ2luYXRpb24oKSx0aGlzLl9hbGlnbklua0JhclRvU2VsZWN0ZWRUYWIoKX07dGhpcy5fa2V5TWFuYWdlcj1uZXcgU2godGhpcy5faXRlbXMpLndpdGhIb3Jpem9udGFsT3JpZW50YXRpb24odGhpcy5fZ2V0TGF5b3V0RGlyZWN0aW9uKCkpLndpdGhIb21lQW5kRW5kKCkud2l0aFdyYXAoKSx0aGlzLl9rZXlNYW5hZ2VyLnVwZGF0ZUFjdGl2ZUl0ZW0odGhpcy5fc2VsZWN0ZWRJbmRleCksdGhpcy5fbmdab25lLm9uU3RhYmxlLnBpcGUoUXQoMSkpLnN1YnNjcmliZShyKSxKdChlLGksdGhpcy5faXRlbXMuY2hhbmdlcyx0aGlzLl9pdGVtc1Jlc2l6ZWQoKSkucGlwZShzdCh0aGlzLl9kZXN0cm95ZWQpKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9uZ1pvbmUucnVuKCgpPT57UHJvbWlzZS5yZXNvbHZlKCkudGhlbigoKT0+e3RoaXMuX3Njcm9sbERpc3RhbmNlPU1hdGgubWF4KDAsTWF0aC5taW4odGhpcy5fZ2V0TWF4U2Nyb2xsRGlzdGFuY2UoKSx0aGlzLl9zY3JvbGxEaXN0YW5jZSkpLHIoKX0pfSksdGhpcy5fa2V5TWFuYWdlci53aXRoSG9yaXpvbnRhbE9yaWVudGF0aW9uKHRoaXMuX2dldExheW91dERpcmVjdGlvbigpKX0pLHRoaXMuX2tleU1hbmFnZXIuY2hhbmdlLnBpcGUoc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKG89Pnt0aGlzLmluZGV4Rm9jdXNlZC5lbWl0KG8pLHRoaXMuX3NldFRhYkZvY3VzKG8pfSl9X2l0ZW1zUmVzaXplZCgpe3JldHVybiJmdW5jdGlvbiIhPXR5cGVvZiBSZXNpemVPYnNlcnZlcj9lbzp0aGlzLl9pdGVtcy5jaGFuZ2VzLnBpcGUoem4odGhpcy5faXRlbXMpLHVpKGU9Pm5ldyB1bihpPT50aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntsZXQgcj1uZXcgUmVzaXplT2JzZXJ2ZXIobz0+aS5uZXh0KG8pKTtyZXR1cm4gZS5mb3JFYWNoKG89PnIub2JzZXJ2ZShvLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudCkpLCgpPT57ci5kaXNjb25uZWN0KCl9fSkpKSxaYSgxKSxZZShlPT5lLnNvbWUoaT0+aS5jb250ZW50UmVjdC53aWR0aD4wJiZpLmNvbnRlbnRSZWN0LmhlaWdodD4wKSkpfW5nQWZ0ZXJDb250ZW50Q2hlY2tlZCgpe3RoaXMuX3RhYkxhYmVsQ291bnQhPXRoaXMuX2l0ZW1zLmxlbmd0aCYmKHRoaXMudXBkYXRlUGFnaW5hdGlvbigpLHRoaXMuX3RhYkxhYmVsQ291bnQ9dGhpcy5faXRlbXMubGVuZ3RoLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpKSx0aGlzLl9zZWxlY3RlZEluZGV4Q2hhbmdlZCYmKHRoaXMuX3Njcm9sbFRvTGFiZWwodGhpcy5fc2VsZWN0ZWRJbmRleCksdGhpcy5fY2hlY2tTY3JvbGxpbmdDb250cm9scygpLHRoaXMuX2FsaWduSW5rQmFyVG9TZWxlY3RlZFRhYigpLHRoaXMuX3NlbGVjdGVkSW5kZXhDaGFuZ2VkPSExLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpKSx0aGlzLl9zY3JvbGxEaXN0YW5jZUNoYW5nZWQmJih0aGlzLl91cGRhdGVUYWJTY3JvbGxQb3NpdGlvbigpLHRoaXMuX3Njcm9sbERpc3RhbmNlQ2hhbmdlZD0hMSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSl9bmdPbkRlc3Ryb3koKXt0aGlzLl9kZXN0cm95ZWQubmV4dCgpLHRoaXMuX2Rlc3Ryb3llZC5jb21wbGV0ZSgpLHRoaXMuX3N0b3BTY3JvbGxpbmcuY29tcGxldGUoKX1faGFuZGxlS2V5ZG93bihlKXtpZigha3IoZSkpc3dpdGNoKGUua2V5Q29kZSl7Y2FzZSAxMzpjYXNlIDMyOnRoaXMuZm9jdXNJbmRleCE9PXRoaXMuc2VsZWN0ZWRJbmRleCYmKHRoaXMuc2VsZWN0Rm9jdXNlZEluZGV4LmVtaXQodGhpcy5mb2N1c0luZGV4KSx0aGlzLl9pdGVtU2VsZWN0ZWQoZSkpO2JyZWFrO2RlZmF1bHQ6dGhpcy5fa2V5TWFuYWdlci5vbktleWRvd24oZSl9fV9vbkNvbnRlbnRDaGFuZ2VzKCl7bGV0IGU9dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LnRleHRDb250ZW50O2UhPT10aGlzLl9jdXJyZW50VGV4dENvbnRlbnQmJih0aGlzLl9jdXJyZW50VGV4dENvbnRlbnQ9ZXx8IiIsdGhpcy5fbmdab25lLnJ1bigoKT0+e3RoaXMudXBkYXRlUGFnaW5hdGlvbigpLHRoaXMuX2FsaWduSW5rQmFyVG9TZWxlY3RlZFRhYigpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpfSkpfXVwZGF0ZVBhZ2luYXRpb24oKXt0aGlzLl9jaGVja1BhZ2luYXRpb25FbmFibGVkKCksdGhpcy5fY2hlY2tTY3JvbGxpbmdDb250cm9scygpLHRoaXMuX3VwZGF0ZVRhYlNjcm9sbFBvc2l0aW9uKCl9Z2V0IGZvY3VzSW5kZXgoKXtyZXR1cm4gdGhpcy5fa2V5TWFuYWdlcj90aGlzLl9rZXlNYW5hZ2VyLmFjdGl2ZUl0ZW1JbmRleDowfXNldCBmb2N1c0luZGV4KGUpeyF0aGlzLl9pc1ZhbGlkSW5kZXgoZSl8fHRoaXMuZm9jdXNJbmRleD09PWV8fCF0aGlzLl9rZXlNYW5hZ2VyfHx0aGlzLl9rZXlNYW5hZ2VyLnNldEFjdGl2ZUl0ZW0oZSl9X2lzVmFsaWRJbmRleChlKXtpZighdGhpcy5faXRlbXMpcmV0dXJuITA7bGV0IGk9dGhpcy5faXRlbXM/dGhpcy5faXRlbXMudG9BcnJheSgpW2VdOm51bGw7cmV0dXJuISFpJiYhaS5kaXNhYmxlZH1fc2V0VGFiRm9jdXMoZSl7aWYodGhpcy5fc2hvd1BhZ2luYXRpb25Db250cm9scyYmdGhpcy5fc2Nyb2xsVG9MYWJlbChlKSx0aGlzLl9pdGVtcyYmdGhpcy5faXRlbXMubGVuZ3RoKXt0aGlzLl9pdGVtcy50b0FycmF5KClbZV0uZm9jdXMoKTtsZXQgaT10aGlzLl90YWJMaXN0Q29udGFpbmVyLm5hdGl2ZUVsZW1lbnQ7aS5zY3JvbGxMZWZ0PSJsdHIiPT10aGlzLl9nZXRMYXlvdXREaXJlY3Rpb24oKT8wOmkuc2Nyb2xsV2lkdGgtaS5vZmZzZXRXaWR0aH19X2dldExheW91dERpcmVjdGlvbigpe3JldHVybiB0aGlzLl9kaXImJiJydGwiPT09dGhpcy5fZGlyLnZhbHVlPyJydGwiOiJsdHIifV91cGRhdGVUYWJTY3JvbGxQb3NpdGlvbigpe2lmKHRoaXMuZGlzYWJsZVBhZ2luYXRpb24pcmV0dXJuO2xldCBlPXRoaXMuc2Nyb2xsRGlzdGFuY2UsaT0ibHRyIj09PXRoaXMuX2dldExheW91dERpcmVjdGlvbigpPy1lOmU7dGhpcy5fdGFiTGlzdC5uYXRpdmVFbGVtZW50LnN0eWxlLnRyYW5zZm9ybT1gdHJhbnNsYXRlWCgke01hdGgucm91bmQoaSl9cHgpYCwodGhpcy5fcGxhdGZvcm0uVFJJREVOVHx8dGhpcy5fcGxhdGZvcm0uRURHRSkmJih0aGlzLl90YWJMaXN0Q29udGFpbmVyLm5hdGl2ZUVsZW1lbnQuc2Nyb2xsTGVmdD0wKX1nZXQgc2Nyb2xsRGlzdGFuY2UoKXtyZXR1cm4gdGhpcy5fc2Nyb2xsRGlzdGFuY2V9c2V0IHNjcm9sbERpc3RhbmNlKGUpe3RoaXMuX3Njcm9sbFRvKGUpfV9zY3JvbGxIZWFkZXIoZSl7cmV0dXJuIHRoaXMuX3Njcm9sbFRvKHRoaXMuX3Njcm9sbERpc3RhbmNlKygiYmVmb3JlIj09ZT8tMToxKSp0aGlzLl90YWJMaXN0Q29udGFpbmVyLm5hdGl2ZUVsZW1lbnQub2Zmc2V0V2lkdGgvMyl9X2hhbmRsZVBhZ2luYXRvckNsaWNrKGUpe3RoaXMuX3N0b3BJbnRlcnZhbCgpLHRoaXMuX3Njcm9sbEhlYWRlcihlKX1fc2Nyb2xsVG9MYWJlbChlKXtpZih0aGlzLmRpc2FibGVQYWdpbmF0aW9uKXJldHVybjtsZXQgaT10aGlzLl9pdGVtcz90aGlzLl9pdGVtcy50b0FycmF5KClbZV06bnVsbDtpZighaSlyZXR1cm47bGV0IGEsbCxyPXRoaXMuX3RhYkxpc3RDb250YWluZXIubmF0aXZlRWxlbWVudC5vZmZzZXRXaWR0aCx7b2Zmc2V0TGVmdDpvLG9mZnNldFdpZHRoOnN9PWkuZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50OyJsdHIiPT10aGlzLl9nZXRMYXlvdXREaXJlY3Rpb24oKT8oYT1vLGw9YStzKToobD10aGlzLl90YWJMaXN0SW5uZXIubmF0aXZlRWxlbWVudC5vZmZzZXRXaWR0aC1vLGE9bC1zKTtsZXQgYz10aGlzLnNjcm9sbERpc3RhbmNlLHU9dGhpcy5zY3JvbGxEaXN0YW5jZStyO2E8Yz90aGlzLnNjcm9sbERpc3RhbmNlLT1jLWErNjA6bD51JiYodGhpcy5zY3JvbGxEaXN0YW5jZSs9bC11KzYwKX1fY2hlY2tQYWdpbmF0aW9uRW5hYmxlZCgpe2lmKHRoaXMuZGlzYWJsZVBhZ2luYXRpb24pdGhpcy5fc2hvd1BhZ2luYXRpb25Db250cm9scz0hMTtlbHNle2xldCBlPXRoaXMuX3RhYkxpc3RJbm5lci5uYXRpdmVFbGVtZW50LnNjcm9sbFdpZHRoPnRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5vZmZzZXRXaWR0aDtlfHwodGhpcy5zY3JvbGxEaXN0YW5jZT0wKSxlIT09dGhpcy5fc2hvd1BhZ2luYXRpb25Db250cm9scyYmdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCksdGhpcy5fc2hvd1BhZ2luYXRpb25Db250cm9scz1lfX1fY2hlY2tTY3JvbGxpbmdDb250cm9scygpe3RoaXMuZGlzYWJsZVBhZ2luYXRpb24/dGhpcy5fZGlzYWJsZVNjcm9sbEFmdGVyPXRoaXMuX2Rpc2FibGVTY3JvbGxCZWZvcmU9ITA6KHRoaXMuX2Rpc2FibGVTY3JvbGxCZWZvcmU9MD09dGhpcy5zY3JvbGxEaXN0YW5jZSx0aGlzLl9kaXNhYmxlU2Nyb2xsQWZ0ZXI9dGhpcy5zY3JvbGxEaXN0YW5jZT09dGhpcy5fZ2V0TWF4U2Nyb2xsRGlzdGFuY2UoKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSl9X2dldE1heFNjcm9sbERpc3RhbmNlKCl7cmV0dXJuIHRoaXMuX3RhYkxpc3RJbm5lci5uYXRpdmVFbGVtZW50LnNjcm9sbFdpZHRoLXRoaXMuX3RhYkxpc3RDb250YWluZXIubmF0aXZlRWxlbWVudC5vZmZzZXRXaWR0aHx8MH1fYWxpZ25JbmtCYXJUb1NlbGVjdGVkVGFiKCl7bGV0IGU9dGhpcy5faXRlbXMmJnRoaXMuX2l0ZW1zLmxlbmd0aD90aGlzLl9pdGVtcy50b0FycmF5KClbdGhpcy5zZWxlY3RlZEluZGV4XTpudWxsLGk9ZT9lLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudDpudWxsO2k/dGhpcy5faW5rQmFyLmFsaWduVG9FbGVtZW50KGkpOnRoaXMuX2lua0Jhci5oaWRlKCl9X3N0b3BJbnRlcnZhbCgpe3RoaXMuX3N0b3BTY3JvbGxpbmcubmV4dCgpfV9oYW5kbGVQYWdpbmF0b3JQcmVzcyhlLGkpe2kmJm51bGwhPWkuYnV0dG9uJiYwIT09aS5idXR0b258fCh0aGlzLl9zdG9wSW50ZXJ2YWwoKSxLYSg2NTAsMTAwKS5waXBlKHN0KEp0KHRoaXMuX3N0b3BTY3JvbGxpbmcsdGhpcy5fZGVzdHJveWVkKSkpLnN1YnNjcmliZSgoKT0+e2xldHttYXhTY3JvbGxEaXN0YW5jZTpyLGRpc3RhbmNlOm99PXRoaXMuX3Njcm9sbEhlYWRlcihlKTsoMD09PW98fG8+PXIpJiZ0aGlzLl9zdG9wSW50ZXJ2YWwoKX0pKX1fc2Nyb2xsVG8oZSl7aWYodGhpcy5kaXNhYmxlUGFnaW5hdGlvbilyZXR1cm57bWF4U2Nyb2xsRGlzdGFuY2U6MCxkaXN0YW5jZTowfTtsZXQgaT10aGlzLl9nZXRNYXhTY3JvbGxEaXN0YW5jZSgpO3JldHVybiB0aGlzLl9zY3JvbGxEaXN0YW5jZT1NYXRoLm1heCgwLE1hdGgubWluKGksZSkpLHRoaXMuX3Njcm9sbERpc3RhbmNlQ2hhbmdlZD0hMCx0aGlzLl9jaGVja1Njcm9sbGluZ0NvbnRyb2xzKCkse21heFNjcm9sbERpc3RhbmNlOmksZGlzdGFuY2U6dGhpcy5fc2Nyb2xsRGlzdGFuY2V9fX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKG5uKSxNKFZhKSxNKCRpLDgpLE0oX3QpLE0ob2kpLE0oUGksOCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLGlucHV0czp7ZGlzYWJsZVBhZ2luYXRpb246ImRpc2FibGVQYWdpbmF0aW9uIn19KSxufSkoKSxxTGU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIFdMZXtjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsKXtzdXBlcihlLGkscixvLHMsYSxsKSx0aGlzLl9kaXNhYmxlUmlwcGxlPSExfWdldCBkaXNhYmxlUmlwcGxlKCl7cmV0dXJuIHRoaXMuX2Rpc2FibGVSaXBwbGV9c2V0IGRpc2FibGVSaXBwbGUoZSl7dGhpcy5fZGlzYWJsZVJpcHBsZT1SdChlKX1faXRlbVNlbGVjdGVkKGUpe2UucHJldmVudERlZmF1bHQoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShSZSksTShubiksTShWYSksTSgkaSw4KSxNKF90KSxNKG9pKSxNKFBpLDgpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixpbnB1dHM6e2Rpc2FibGVSaXBwbGU6ImRpc2FibGVSaXBwbGUifSxmZWF0dXJlczpbdHRdfSksbn0pKCksWUxlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBxTGV7Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEsbCl7c3VwZXIoZSxpLHIsbyxzLGEsbCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0obm4pLE0oVmEpLE0oJGksOCksTShfdCksTShvaSksTShQaSw4KSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LXRhYi1oZWFkZXIiXV0sY29udGVudFF1ZXJpZXM6ZnVuY3Rpb24oZSxpLHIpe2lmKDEmZSYmRWkocixJaWUsNCksMiZlKXtsZXQgbztOZShvPUxlKCkpJiYoaS5faXRlbXM9byl9fSx2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJihvdChiaWUsNyksb3QoTUxlLDcpLG90KHdMZSw3KSxvdChTTGUsNyksb3QoRUxlLDUpLG90KFRMZSw1KSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5faW5rQmFyPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLl90YWJMaXN0Q29udGFpbmVyPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLl90YWJMaXN0PXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLl90YWJMaXN0SW5uZXI9ci5maXJzdCksTmUocj1MZSgpKSYmKGkuX25leHRQYWdpbmF0b3I9ci5maXJzdCksTmUocj1MZSgpKSYmKGkuX3ByZXZpb3VzUGFnaW5hdG9yPXIuZmlyc3QpfX0saG9zdEF0dHJzOlsxLCJtYXQtdGFiLWhlYWRlciJdLGhvc3RWYXJzOjQsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZldCgibWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jb250cm9scy1lbmFibGVkIixpLl9zaG93UGFnaW5hdGlvbkNvbnRyb2xzKSgibWF0LXRhYi1oZWFkZXItcnRsIiwicnRsIj09aS5fZ2V0TGF5b3V0RGlyZWN0aW9uKCkpfSxpbnB1dHM6e3NlbGVjdGVkSW5kZXg6InNlbGVjdGVkSW5kZXgifSxvdXRwdXRzOntzZWxlY3RGb2N1c2VkSW5kZXg6InNlbGVjdEZvY3VzZWRJbmRleCIsaW5kZXhGb2N1c2VkOiJpbmRleEZvY3VzZWQifSxmZWF0dXJlczpbdHRdLG5nQ29udGVudFNlbGVjdG9yczpNaWUsZGVjbHM6MTQsdmFyczoxMCxjb25zdHM6W1siYXJpYS1oaWRkZW4iLCJ0cnVlIiwidHlwZSIsImJ1dHRvbiIsIm1hdC1yaXBwbGUiLCIiLCJ0YWJpbmRleCIsIi0xIiwxLCJtYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uIiwibWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1iZWZvcmUiLCJtYXQtZWxldmF0aW9uLXo0IiwzLCJtYXRSaXBwbGVEaXNhYmxlZCIsImRpc2FibGVkIiwiY2xpY2siLCJtb3VzZWRvd24iLCJ0b3VjaGVuZCJdLFsicHJldmlvdXNQYWdpbmF0b3IiLCIiXSxbMSwibWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uIl0sWzEsIm1hdC10YWItbGFiZWwtY29udGFpbmVyIiwzLCJrZXlkb3duIl0sWyJ0YWJMaXN0Q29udGFpbmVyIiwiIl0sWyJyb2xlIiwidGFibGlzdCIsMSwibWF0LXRhYi1saXN0IiwzLCJjZGtPYnNlcnZlQ29udGVudCJdLFsidGFiTGlzdCIsIiJdLFsxLCJtYXQtdGFiLWxhYmVscyJdLFsidGFiTGlzdElubmVyIiwiIl0sWyJhcmlhLWhpZGRlbiIsInRydWUiLCJ0eXBlIiwiYnV0dG9uIiwibWF0LXJpcHBsZSIsIiIsInRhYmluZGV4IiwiLTEiLDEsIm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24iLCJtYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWFmdGVyIiwibWF0LWVsZXZhdGlvbi16NCIsMywibWF0UmlwcGxlRGlzYWJsZWQiLCJkaXNhYmxlZCIsIm1vdXNlZG93biIsImNsaWNrIiwidG91Y2hlbmQiXSxbIm5leHRQYWdpbmF0b3IiLCIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJih4aSgpLF8oMCwiYnV0dG9uIiwwLDEpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLl9oYW5kbGVQYWdpbmF0b3JDbGljaygiYmVmb3JlIil9KSgibW91c2Vkb3duIixmdW5jdGlvbihvKXtyZXR1cm4gaS5faGFuZGxlUGFnaW5hdG9yUHJlc3MoImJlZm9yZSIsbyl9KSgidG91Y2hlbmQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuX3N0b3BJbnRlcnZhbCgpfSksTygyLCJkaXYiLDIpLHYoKSxfKDMsImRpdiIsMyw0KSxQKCJrZXlkb3duIixmdW5jdGlvbihvKXtyZXR1cm4gaS5faGFuZGxlS2V5ZG93bihvKX0pLF8oNSwiZGl2Iiw1LDYpLFAoImNka09ic2VydmVDb250ZW50IixmdW5jdGlvbigpe3JldHVybiBpLl9vbkNvbnRlbnRDaGFuZ2VzKCl9KSxfKDcsImRpdiIsNyw4KSxWbig5KSx2KCksTygxMCwibWF0LWluay1iYXIiKSx2KCkoKSxfKDExLCJidXR0b24iLDksMTApLFAoIm1vdXNlZG93biIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX2hhbmRsZVBhZ2luYXRvclByZXNzKCJhZnRlciIsbyl9KSgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkuX2hhbmRsZVBhZ2luYXRvckNsaWNrKCJhZnRlciIpfSkoInRvdWNoZW5kIixmdW5jdGlvbigpe3JldHVybiBpLl9zdG9wSW50ZXJ2YWwoKX0pLE8oMTMsImRpdiIsMiksdigpKSwyJmUmJihldCgibWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZCIsaS5fZGlzYWJsZVNjcm9sbEJlZm9yZSkseSgibWF0UmlwcGxlRGlzYWJsZWQiLGkuX2Rpc2FibGVTY3JvbGxCZWZvcmV8fGkuZGlzYWJsZVJpcHBsZSkoImRpc2FibGVkIixpLl9kaXNhYmxlU2Nyb2xsQmVmb3JlfHxudWxsKSxDKDUpLGV0KCJfbWF0LWFuaW1hdGlvbi1ub29wYWJsZSIsIk5vb3BBbmltYXRpb25zIj09PWkuX2FuaW1hdGlvbk1vZGUpLEMoNiksZXQoIm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tZGlzYWJsZWQiLGkuX2Rpc2FibGVTY3JvbGxBZnRlcikseSgibWF0UmlwcGxlRGlzYWJsZWQiLGkuX2Rpc2FibGVTY3JvbGxBZnRlcnx8aS5kaXNhYmxlUmlwcGxlKSgiZGlzYWJsZWQiLGkuX2Rpc2FibGVTY3JvbGxBZnRlcnx8bnVsbCkpfSxkZXBlbmRlbmNpZXM6W1lvLHdoLGJpZV0sc3R5bGVzOlsiLm1hdC10YWItaGVhZGVye2Rpc3BsYXk6ZmxleDtvdmVyZmxvdzpoaWRkZW47cG9zaXRpb246cmVsYXRpdmU7ZmxleC1zaHJpbms6MH0ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbnstd2Via2l0LXVzZXItc2VsZWN0Om5vbmU7dXNlci1zZWxlY3Q6bm9uZTtwb3NpdGlvbjpyZWxhdGl2ZTtkaXNwbGF5Om5vbmU7anVzdGlmeS1jb250ZW50OmNlbnRlcjthbGlnbi1pdGVtczpjZW50ZXI7bWluLXdpZHRoOjMycHg7Y3Vyc29yOnBvaW50ZXI7ei1pbmRleDoyOy13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjpyZ2JhKDAsMCwwLDApO3RvdWNoLWFjdGlvbjpub25lO2JveC1zaXppbmc6Y29udGVudC1ib3g7YmFja2dyb3VuZDpub25lO2JvcmRlcjpub25lO291dGxpbmU6MDtwYWRkaW5nOjB9Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb246Oi1tb3otZm9jdXMtaW5uZXJ7Ym9yZGVyOjB9Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY29udHJvbHMtZW5hYmxlZCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbntkaXNwbGF5OmZsZXh9Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tYmVmb3JlLC5tYXQtdGFiLWhlYWRlci1ydGwgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tYWZ0ZXJ7cGFkZGluZy1sZWZ0OjRweH0ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1iZWZvcmUgLm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbiwubWF0LXRhYi1oZWFkZXItcnRsIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWFmdGVyIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb257dHJhbnNmb3JtOnJvdGF0ZSgtMTM1ZGVnKX0ubWF0LXRhYi1oZWFkZXItcnRsIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWJlZm9yZSwubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1hZnRlcntwYWRkaW5nLXJpZ2h0OjRweH0ubWF0LXRhYi1oZWFkZXItcnRsIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWJlZm9yZSAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9uLC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWFmdGVyIC5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWNoZXZyb257dHJhbnNmb3JtOnJvdGF0ZSg0NWRlZyl9Lm1hdC10YWItaGVhZGVyLXBhZ2luYXRpb24tY2hldnJvbntib3JkZXItc3R5bGU6c29saWQ7Ym9yZGVyLXdpZHRoOjJweCAycHggMCAwO2hlaWdodDo4cHg7d2lkdGg6OHB4fS5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9uLWRpc2FibGVke2JveC1zaGFkb3c6bm9uZTtjdXJzb3I6ZGVmYXVsdH0ubWF0LXRhYi1saXN0e2ZsZXgtZ3JvdzoxO3Bvc2l0aW9uOnJlbGF0aXZlO3RyYW5zaXRpb246dHJhbnNmb3JtIDUwMG1zIGN1YmljLWJlemllcigwLjM1LCAwLCAwLjI1LCAxKX0ubWF0LWluay1iYXJ7cG9zaXRpb246YWJzb2x1dGU7Ym90dG9tOjA7aGVpZ2h0OjJweDt0cmFuc2l0aW9uOjUwMG1zIGN1YmljLWJlemllcigwLjM1LCAwLCAwLjI1LCAxKX0ubWF0LWluay1iYXIuX21hdC1hbmltYXRpb24tbm9vcGFibGV7dHJhbnNpdGlvbjpub25lICFpbXBvcnRhbnQ7YW5pbWF0aW9uOm5vbmUgIWltcG9ydGFudH0ubWF0LXRhYi1ncm91cC1pbnZlcnRlZC1oZWFkZXIgLm1hdC1pbmstYmFye2JvdHRvbTphdXRvO3RvcDowfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1pbmstYmFye291dGxpbmU6c29saWQgMnB4O2hlaWdodDowfS5tYXQtdGFiLWxhYmVsc3tkaXNwbGF5OmZsZXh9W21hdC1hbGlnbi10YWJzPWNlbnRlcl0+Lm1hdC10YWItaGVhZGVyIC5tYXQtdGFiLWxhYmVsc3tqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyfVttYXQtYWxpZ24tdGFicz1lbmRdPi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1sYWJlbHN7anVzdGlmeS1jb250ZW50OmZsZXgtZW5kfS5tYXQtdGFiLWxhYmVsLWNvbnRhaW5lcntkaXNwbGF5OmZsZXg7ZmxleC1ncm93OjE7b3ZlcmZsb3c6aGlkZGVuO3otaW5kZXg6MX0ubWF0LXRhYi1saXN0Ll9tYXQtYW5pbWF0aW9uLW5vb3BhYmxle3RyYW5zaXRpb246bm9uZSAhaW1wb3J0YW50O2FuaW1hdGlvbjpub25lICFpbXBvcnRhbnR9Lm1hdC10YWItbGFiZWx7aGVpZ2h0OjQ4cHg7cGFkZGluZzowIDI0cHg7Y3Vyc29yOnBvaW50ZXI7Ym94LXNpemluZzpib3JkZXItYm94O29wYWNpdHk6LjY7bWluLXdpZHRoOjE2MHB4O3RleHQtYWxpZ246Y2VudGVyO2Rpc3BsYXk6aW5saW5lLWZsZXg7anVzdGlmeS1jb250ZW50OmNlbnRlcjthbGlnbi1pdGVtczpjZW50ZXI7d2hpdGUtc3BhY2U6bm93cmFwO3Bvc2l0aW9uOnJlbGF0aXZlfS5tYXQtdGFiLWxhYmVsOmZvY3Vze291dGxpbmU6bm9uZX0ubWF0LXRhYi1sYWJlbDpmb2N1czpub3QoLm1hdC10YWItZGlzYWJsZWQpe29wYWNpdHk6MX0ubWF0LXRhYi1sYWJlbC5tYXQtdGFiLWRpc2FibGVke2N1cnNvcjpkZWZhdWx0fS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC10YWItbGFiZWwubWF0LXRhYi1kaXNhYmxlZHtvcGFjaXR5Oi41fS5tYXQtdGFiLWxhYmVsIC5tYXQtdGFiLWxhYmVsLWNvbnRlbnR7ZGlzcGxheTppbmxpbmUtZmxleDtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyO2FsaWduLWl0ZW1zOmNlbnRlcjt3aGl0ZS1zcGFjZTpub3dyYXB9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LXRhYi1sYWJlbHtvcGFjaXR5OjF9Lm1hdC10YWItbGFiZWw6OmJlZm9yZXttYXJnaW46NXB4fUBtZWRpYShtYXgtd2lkdGg6IDU5OXB4KXsubWF0LXRhYi1sYWJlbHttaW4td2lkdGg6NzJweH19Il0sZW5jYXBzdWxhdGlvbjoyfSksbn0pKCksWExlPTAsUUxlPWtvKHFvKGNsYXNze2NvbnN0cnVjdG9yKG4pe3RoaXMuX2VsZW1lbnRSZWY9bn19KSwicHJpbWFyeSIpLEtMZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgUUxle2NvbnN0cnVjdG9yKGUsaSxyLG8pe3N1cGVyKGUpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmPWksdGhpcy5fYW5pbWF0aW9uTW9kZT1vLHRoaXMuX3RhYnM9bmV3IEhsLHRoaXMuX2luZGV4VG9TZWxlY3Q9MCx0aGlzLl9sYXN0Rm9jdXNlZFRhYkluZGV4PW51bGwsdGhpcy5fdGFiQm9keVdyYXBwZXJIZWlnaHQ9MCx0aGlzLl90YWJzU3Vic2NyaXB0aW9uPVNuLkVNUFRZLHRoaXMuX3RhYkxhYmVsU3Vic2NyaXB0aW9uPVNuLkVNUFRZLHRoaXMuX2R5bmFtaWNIZWlnaHQ9ITEsdGhpcy5fc2VsZWN0ZWRJbmRleD1udWxsLHRoaXMuaGVhZGVyUG9zaXRpb249ImFib3ZlIix0aGlzLl9kaXNhYmxlUGFnaW5hdGlvbj0hMSx0aGlzLl9wcmVzZXJ2ZUNvbnRlbnQ9ITEsdGhpcy5zZWxlY3RlZEluZGV4Q2hhbmdlPW5ldyBHLHRoaXMuZm9jdXNDaGFuZ2U9bmV3IEcsdGhpcy5hbmltYXRpb25Eb25lPW5ldyBHLHRoaXMuc2VsZWN0ZWRUYWJDaGFuZ2U9bmV3IEcoITApLHRoaXMuX2dyb3VwSWQ9WExlKyssdGhpcy5hbmltYXRpb25EdXJhdGlvbj1yJiZyLmFuaW1hdGlvbkR1cmF0aW9uP3IuYW5pbWF0aW9uRHVyYXRpb246IjUwMG1zIix0aGlzLmRpc2FibGVQYWdpbmF0aW9uPSEoIXJ8fG51bGw9PXIuZGlzYWJsZVBhZ2luYXRpb24pJiZyLmRpc2FibGVQYWdpbmF0aW9uLHRoaXMuZHluYW1pY0hlaWdodD0hKCFyfHxudWxsPT1yLmR5bmFtaWNIZWlnaHQpJiZyLmR5bmFtaWNIZWlnaHQsdGhpcy5jb250ZW50VGFiSW5kZXg9cj8uY29udGVudFRhYkluZGV4Pz9udWxsLHRoaXMucHJlc2VydmVDb250ZW50PSEhcj8ucHJlc2VydmVDb250ZW50fWdldCBkeW5hbWljSGVpZ2h0KCl7cmV0dXJuIHRoaXMuX2R5bmFtaWNIZWlnaHR9c2V0IGR5bmFtaWNIZWlnaHQoZSl7dGhpcy5fZHluYW1pY0hlaWdodD1SdChlKX1nZXQgc2VsZWN0ZWRJbmRleCgpe3JldHVybiB0aGlzLl9zZWxlY3RlZEluZGV4fXNldCBzZWxlY3RlZEluZGV4KGUpe3RoaXMuX2luZGV4VG9TZWxlY3Q9QmkoZSxudWxsKX1nZXQgYW5pbWF0aW9uRHVyYXRpb24oKXtyZXR1cm4gdGhpcy5fYW5pbWF0aW9uRHVyYXRpb259c2V0IGFuaW1hdGlvbkR1cmF0aW9uKGUpe3RoaXMuX2FuaW1hdGlvbkR1cmF0aW9uPS9eXGQrJC8udGVzdChlKyIiKT9lKyJtcyI6ZX1nZXQgY29udGVudFRhYkluZGV4KCl7cmV0dXJuIHRoaXMuX2NvbnRlbnRUYWJJbmRleH1zZXQgY29udGVudFRhYkluZGV4KGUpe3RoaXMuX2NvbnRlbnRUYWJJbmRleD1CaShlLG51bGwpfWdldCBkaXNhYmxlUGFnaW5hdGlvbigpe3JldHVybiB0aGlzLl9kaXNhYmxlUGFnaW5hdGlvbn1zZXQgZGlzYWJsZVBhZ2luYXRpb24oZSl7dGhpcy5fZGlzYWJsZVBhZ2luYXRpb249UnQoZSl9Z2V0IHByZXNlcnZlQ29udGVudCgpe3JldHVybiB0aGlzLl9wcmVzZXJ2ZUNvbnRlbnR9c2V0IHByZXNlcnZlQ29udGVudChlKXt0aGlzLl9wcmVzZXJ2ZUNvbnRlbnQ9UnQoZSl9Z2V0IGJhY2tncm91bmRDb2xvcigpe3JldHVybiB0aGlzLl9iYWNrZ3JvdW5kQ29sb3J9c2V0IGJhY2tncm91bmRDb2xvcihlKXtsZXQgaT10aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQ7aS5jbGFzc0xpc3QucmVtb3ZlKGBtYXQtYmFja2dyb3VuZC0ke3RoaXMuYmFja2dyb3VuZENvbG9yfWApLGUmJmkuY2xhc3NMaXN0LmFkZChgbWF0LWJhY2tncm91bmQtJHtlfWApLHRoaXMuX2JhY2tncm91bmRDb2xvcj1lfW5nQWZ0ZXJDb250ZW50Q2hlY2tlZCgpe2xldCBlPXRoaXMuX2luZGV4VG9TZWxlY3Q9dGhpcy5fY2xhbXBUYWJJbmRleCh0aGlzLl9pbmRleFRvU2VsZWN0KTtpZih0aGlzLl9zZWxlY3RlZEluZGV4IT1lKXtsZXQgaT1udWxsPT10aGlzLl9zZWxlY3RlZEluZGV4O2lmKCFpKXt0aGlzLnNlbGVjdGVkVGFiQ2hhbmdlLmVtaXQodGhpcy5fY3JlYXRlQ2hhbmdlRXZlbnQoZSkpO2xldCByPXRoaXMuX3RhYkJvZHlXcmFwcGVyLm5hdGl2ZUVsZW1lbnQ7ci5zdHlsZS5taW5IZWlnaHQ9ci5jbGllbnRIZWlnaHQrInB4In1Qcm9taXNlLnJlc29sdmUoKS50aGVuKCgpPT57dGhpcy5fdGFicy5mb3JFYWNoKChyLG8pPT5yLmlzQWN0aXZlPW89PT1lKSxpfHwodGhpcy5zZWxlY3RlZEluZGV4Q2hhbmdlLmVtaXQoZSksdGhpcy5fdGFiQm9keVdyYXBwZXIubmF0aXZlRWxlbWVudC5zdHlsZS5taW5IZWlnaHQ9IiIpfSl9dGhpcy5fdGFicy5mb3JFYWNoKChpLHIpPT57aS5wb3NpdGlvbj1yLWUsbnVsbCE9dGhpcy5fc2VsZWN0ZWRJbmRleCYmMD09aS5wb3NpdGlvbiYmIWkub3JpZ2luJiYoaS5vcmlnaW49ZS10aGlzLl9zZWxlY3RlZEluZGV4KX0pLHRoaXMuX3NlbGVjdGVkSW5kZXghPT1lJiYodGhpcy5fc2VsZWN0ZWRJbmRleD1lLHRoaXMuX2xhc3RGb2N1c2VkVGFiSW5kZXg9bnVsbCx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSl9bmdBZnRlckNvbnRlbnRJbml0KCl7dGhpcy5fc3Vic2NyaWJlVG9BbGxUYWJDaGFuZ2VzKCksdGhpcy5fc3Vic2NyaWJlVG9UYWJMYWJlbHMoKSx0aGlzLl90YWJzU3Vic2NyaXB0aW9uPXRoaXMuX3RhYnMuY2hhbmdlcy5zdWJzY3JpYmUoKCk9PntsZXQgZT10aGlzLl9jbGFtcFRhYkluZGV4KHRoaXMuX2luZGV4VG9TZWxlY3QpO2lmKGU9PT10aGlzLl9zZWxlY3RlZEluZGV4KXtsZXQgcixpPXRoaXMuX3RhYnMudG9BcnJheSgpO2ZvcihsZXQgbz0wO288aS5sZW5ndGg7bysrKWlmKGlbb10uaXNBY3RpdmUpe3RoaXMuX2luZGV4VG9TZWxlY3Q9dGhpcy5fc2VsZWN0ZWRJbmRleD1vLHRoaXMuX2xhc3RGb2N1c2VkVGFiSW5kZXg9bnVsbCxyPWlbb107YnJlYWt9IXImJmlbZV0mJlByb21pc2UucmVzb2x2ZSgpLnRoZW4oKCk9PntpW2VdLmlzQWN0aXZlPSEwLHRoaXMuc2VsZWN0ZWRUYWJDaGFuZ2UuZW1pdCh0aGlzLl9jcmVhdGVDaGFuZ2VFdmVudChlKSl9KX10aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKX0pfV9zdWJzY3JpYmVUb0FsbFRhYkNoYW5nZXMoKXt0aGlzLl9hbGxUYWJzLmNoYW5nZXMucGlwZSh6bih0aGlzLl9hbGxUYWJzKSkuc3Vic2NyaWJlKGU9Pnt0aGlzLl90YWJzLnJlc2V0KGUuZmlsdGVyKGk9PmkuX2Nsb3Nlc3RUYWJHcm91cD09PXRoaXN8fCFpLl9jbG9zZXN0VGFiR3JvdXApKSx0aGlzLl90YWJzLm5vdGlmeU9uQ2hhbmdlcygpfSl9bmdPbkRlc3Ryb3koKXt0aGlzLl90YWJzLmRlc3Ryb3koKSx0aGlzLl90YWJzU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5fdGFiTGFiZWxTdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKX1yZWFsaWduSW5rQmFyKCl7dGhpcy5fdGFiSGVhZGVyJiZ0aGlzLl90YWJIZWFkZXIuX2FsaWduSW5rQmFyVG9TZWxlY3RlZFRhYigpfXVwZGF0ZVBhZ2luYXRpb24oKXt0aGlzLl90YWJIZWFkZXImJnRoaXMuX3RhYkhlYWRlci51cGRhdGVQYWdpbmF0aW9uKCl9Zm9jdXNUYWIoZSl7bGV0IGk9dGhpcy5fdGFiSGVhZGVyO2kmJihpLmZvY3VzSW5kZXg9ZSl9X2ZvY3VzQ2hhbmdlZChlKXt0aGlzLl9sYXN0Rm9jdXNlZFRhYkluZGV4PWUsdGhpcy5mb2N1c0NoYW5nZS5lbWl0KHRoaXMuX2NyZWF0ZUNoYW5nZUV2ZW50KGUpKX1fY3JlYXRlQ2hhbmdlRXZlbnQoZSl7bGV0IGk9bmV3IGNsYXNze307cmV0dXJuIGkuaW5kZXg9ZSx0aGlzLl90YWJzJiZ0aGlzLl90YWJzLmxlbmd0aCYmKGkudGFiPXRoaXMuX3RhYnMudG9BcnJheSgpW2VdKSxpfV9zdWJzY3JpYmVUb1RhYkxhYmVscygpe3RoaXMuX3RhYkxhYmVsU3Vic2NyaXB0aW9uJiZ0aGlzLl90YWJMYWJlbFN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX3RhYkxhYmVsU3Vic2NyaXB0aW9uPUp0KC4uLnRoaXMuX3RhYnMubWFwKGU9PmUuX3N0YXRlQ2hhbmdlcykpLnN1YnNjcmliZSgoKT0+dGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCkpfV9jbGFtcFRhYkluZGV4KGUpe3JldHVybiBNYXRoLm1pbih0aGlzLl90YWJzLmxlbmd0aC0xLE1hdGgubWF4KGV8fDAsMCkpfV9nZXRUYWJMYWJlbElkKGUpe3JldHVybmBtYXQtdGFiLWxhYmVsLSR7dGhpcy5fZ3JvdXBJZH0tJHtlfWB9X2dldFRhYkNvbnRlbnRJZChlKXtyZXR1cm5gbWF0LXRhYi1jb250ZW50LSR7dGhpcy5fZ3JvdXBJZH0tJHtlfWB9X3NldFRhYkJvZHlXcmFwcGVySGVpZ2h0KGUpe2lmKCF0aGlzLl9keW5hbWljSGVpZ2h0fHwhdGhpcy5fdGFiQm9keVdyYXBwZXJIZWlnaHQpcmV0dXJuO2xldCBpPXRoaXMuX3RhYkJvZHlXcmFwcGVyLm5hdGl2ZUVsZW1lbnQ7aS5zdHlsZS5oZWlnaHQ9dGhpcy5fdGFiQm9keVdyYXBwZXJIZWlnaHQrInB4Iix0aGlzLl90YWJCb2R5V3JhcHBlci5uYXRpdmVFbGVtZW50Lm9mZnNldEhlaWdodCYmKGkuc3R5bGUuaGVpZ2h0PWUrInB4Iil9X3JlbW92ZVRhYkJvZHlXcmFwcGVySGVpZ2h0KCl7bGV0IGU9dGhpcy5fdGFiQm9keVdyYXBwZXIubmF0aXZlRWxlbWVudDt0aGlzLl90YWJCb2R5V3JhcHBlckhlaWdodD1lLmNsaWVudEhlaWdodCxlLnN0eWxlLmhlaWdodD0iIix0aGlzLmFuaW1hdGlvbkRvbmUuZW1pdCgpfV9oYW5kbGVDbGljayhlLGkscil7ZS5kaXNhYmxlZHx8KHRoaXMuc2VsZWN0ZWRJbmRleD1pLmZvY3VzSW5kZXg9cil9X2dldFRhYkluZGV4KGUsaSl7cmV0dXJuIGUuZGlzYWJsZWQ/bnVsbDppPT09KHRoaXMuX2xhc3RGb2N1c2VkVGFiSW5kZXg/P3RoaXMuc2VsZWN0ZWRJbmRleCk/MDotMX1fdGFiRm9jdXNDaGFuZ2VkKGUsaSl7ZSYmIm1vdXNlIiE9PWUmJiJ0b3VjaCIhPT1lJiYodGhpcy5fdGFiSGVhZGVyLmZvY3VzSW5kZXg9aSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0obm4pLE0oQWllLDgpLE0oUGksOCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLGlucHV0czp7ZHluYW1pY0hlaWdodDoiZHluYW1pY0hlaWdodCIsc2VsZWN0ZWRJbmRleDoic2VsZWN0ZWRJbmRleCIsaGVhZGVyUG9zaXRpb246ImhlYWRlclBvc2l0aW9uIixhbmltYXRpb25EdXJhdGlvbjoiYW5pbWF0aW9uRHVyYXRpb24iLGNvbnRlbnRUYWJJbmRleDoiY29udGVudFRhYkluZGV4IixkaXNhYmxlUGFnaW5hdGlvbjoiZGlzYWJsZVBhZ2luYXRpb24iLHByZXNlcnZlQ29udGVudDoicHJlc2VydmVDb250ZW50IixiYWNrZ3JvdW5kQ29sb3I6ImJhY2tncm91bmRDb2xvciJ9LG91dHB1dHM6e3NlbGVjdGVkSW5kZXhDaGFuZ2U6InNlbGVjdGVkSW5kZXhDaGFuZ2UiLGZvY3VzQ2hhbmdlOiJmb2N1c0NoYW5nZSIsYW5pbWF0aW9uRG9uZToiYW5pbWF0aW9uRG9uZSIsc2VsZWN0ZWRUYWJDaGFuZ2U6InNlbGVjdGVkVGFiQ2hhbmdlIn0sZmVhdHVyZXM6W3R0XX0pLG59KSgpLFoyPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBLTGV7Y29uc3RydWN0b3IoZSxpLHIsbyl7c3VwZXIoZSxpLHIsbyl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0obm4pLE0oQWllLDgpLE0oUGksOCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC10YWItZ3JvdXAiXV0sY29udGVudFF1ZXJpZXM6ZnVuY3Rpb24oZSxpLHIpe2lmKDEmZSYmRWkocix4dyw1KSwyJmUpe2xldCBvO05lKG89TGUoKSkmJihpLl9hbGxUYWJzPW8pfX0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYob3QoRExlLDUpLG90KEFMZSw1KSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5fdGFiQm9keVdyYXBwZXI9ci5maXJzdCksTmUocj1MZSgpKSYmKGkuX3RhYkhlYWRlcj1yLmZpcnN0KX19LGhvc3RBdHRyczpbMSwibWF0LXRhYi1ncm91cCJdLGhvc3RWYXJzOjQsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZldCgibWF0LXRhYi1ncm91cC1keW5hbWljLWhlaWdodCIsaS5keW5hbWljSGVpZ2h0KSgibWF0LXRhYi1ncm91cC1pbnZlcnRlZC1oZWFkZXIiLCJiZWxvdyI9PT1pLmhlYWRlclBvc2l0aW9uKX0saW5wdXRzOntjb2xvcjoiY29sb3IiLGRpc2FibGVSaXBwbGU6ImRpc2FibGVSaXBwbGUifSxleHBvcnRBczpbIm1hdFRhYkdyb3VwIl0sZmVhdHVyZXM6WyR0KFt7cHJvdmlkZTpUaWUsdXNlRXhpc3Rpbmc6bn1dKSx0dF0sZGVjbHM6Nix2YXJzOjcsY29uc3RzOltbMywic2VsZWN0ZWRJbmRleCIsImRpc2FibGVSaXBwbGUiLCJkaXNhYmxlUGFnaW5hdGlvbiIsImluZGV4Rm9jdXNlZCIsInNlbGVjdEZvY3VzZWRJbmRleCJdLFsidGFiSGVhZGVyIiwiIl0sWyJjbGFzcyIsIm1hdC10YWItbGFiZWwgbWF0LWZvY3VzLWluZGljYXRvciIsInJvbGUiLCJ0YWIiLCJtYXRUYWJMYWJlbFdyYXBwZXIiLCIiLCJtYXQtcmlwcGxlIiwiIiwiY2RrTW9uaXRvckVsZW1lbnRGb2N1cyIsIiIsMywiaWQiLCJtYXQtdGFiLWxhYmVsLWFjdGl2ZSIsIm5nQ2xhc3MiLCJkaXNhYmxlZCIsIm1hdFJpcHBsZURpc2FibGVkIiwiY2xpY2siLCJjZGtGb2N1c0NoYW5nZSIsNCwibmdGb3IiLCJuZ0Zvck9mIl0sWzEsIm1hdC10YWItYm9keS13cmFwcGVyIl0sWyJ0YWJCb2R5V3JhcHBlciIsIiJdLFsicm9sZSIsInRhYnBhbmVsIiwzLCJpZCIsIm1hdC10YWItYm9keS1hY3RpdmUiLCJuZ0NsYXNzIiwiY29udGVudCIsInBvc2l0aW9uIiwib3JpZ2luIiwiYW5pbWF0aW9uRHVyYXRpb24iLCJwcmVzZXJ2ZUNvbnRlbnQiLCJfb25DZW50ZXJlZCIsIl9vbkNlbnRlcmluZyIsNCwibmdGb3IiLCJuZ0Zvck9mIl0sWyJyb2xlIiwidGFiIiwibWF0VGFiTGFiZWxXcmFwcGVyIiwiIiwibWF0LXJpcHBsZSIsIiIsImNka01vbml0b3JFbGVtZW50Rm9jdXMiLCIiLDEsIm1hdC10YWItbGFiZWwiLCJtYXQtZm9jdXMtaW5kaWNhdG9yIiwzLCJpZCIsIm5nQ2xhc3MiLCJkaXNhYmxlZCIsIm1hdFJpcHBsZURpc2FibGVkIiwiY2xpY2siLCJjZGtGb2N1c0NoYW5nZSJdLFsxLCJtYXQtdGFiLWxhYmVsLWNvbnRlbnQiXSxbMywibmdJZiIsIm5nSWZFbHNlIl0sWyJ0YWJUZXh0TGFiZWwiLCIiXSxbMywiY2RrUG9ydGFsT3V0bGV0Il0sWyJyb2xlIiwidGFicGFuZWwiLDMsImlkIiwibmdDbGFzcyIsImNvbnRlbnQiLCJwb3NpdGlvbiIsIm9yaWdpbiIsImFuaW1hdGlvbkR1cmF0aW9uIiwicHJlc2VydmVDb250ZW50IiwiX29uQ2VudGVyZWQiLCJfb25DZW50ZXJpbmciXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1hdC10YWItaGVhZGVyIiwwLDEpLFAoImluZGV4Rm9jdXNlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX2ZvY3VzQ2hhbmdlZChvKX0pKCJzZWxlY3RGb2N1c2VkSW5kZXgiLGZ1bmN0aW9uKG8pe3JldHVybiBpLnNlbGVjdGVkSW5kZXg9b30pLEUoMixPTGUsNSwxNSwiZGl2IiwyKSx2KCksXygzLCJkaXYiLDMsNCksRSg1LGtMZSwxLDExLCJtYXQtdGFiLWJvZHkiLDUpLHYoKSksMiZlJiYoeSgic2VsZWN0ZWRJbmRleCIsaS5zZWxlY3RlZEluZGV4fHwwKSgiZGlzYWJsZVJpcHBsZSIsaS5kaXNhYmxlUmlwcGxlKSgiZGlzYWJsZVBhZ2luYXRpb24iLGkuZGlzYWJsZVBhZ2luYXRpb24pLEMoMikseSgibmdGb3JPZiIsaS5fdGFicyksQygxKSxldCgiX21hdC1hbmltYXRpb24tbm9vcGFibGUiLCJOb29wQW5pbWF0aW9ucyI9PT1pLl9hbmltYXRpb25Nb2RlKSxDKDIpLHkoIm5nRm9yT2YiLGkuX3RhYnMpKX0sZGVwZW5kZW5jaWVzOltGbixkbixCZSxkYSxZbyxudGUsSWllLERpZSxZTGVdLHN0eWxlczpbIi5tYXQtdGFiLWdyb3Vwe2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47bWF4LXdpZHRoOjEwMCV9Lm1hdC10YWItZ3JvdXAubWF0LXRhYi1ncm91cC1pbnZlcnRlZC1oZWFkZXJ7ZmxleC1kaXJlY3Rpb246Y29sdW1uLXJldmVyc2V9Lm1hdC10YWItbGFiZWx7aGVpZ2h0OjQ4cHg7cGFkZGluZzowIDI0cHg7Y3Vyc29yOnBvaW50ZXI7Ym94LXNpemluZzpib3JkZXItYm94O29wYWNpdHk6LjY7bWluLXdpZHRoOjE2MHB4O3RleHQtYWxpZ246Y2VudGVyO2Rpc3BsYXk6aW5saW5lLWZsZXg7anVzdGlmeS1jb250ZW50OmNlbnRlcjthbGlnbi1pdGVtczpjZW50ZXI7d2hpdGUtc3BhY2U6bm93cmFwO3Bvc2l0aW9uOnJlbGF0aXZlfS5tYXQtdGFiLWxhYmVsOmZvY3Vze291dGxpbmU6bm9uZX0ubWF0LXRhYi1sYWJlbDpmb2N1czpub3QoLm1hdC10YWItZGlzYWJsZWQpe29wYWNpdHk6MX0ubWF0LXRhYi1sYWJlbC5tYXQtdGFiLWRpc2FibGVke2N1cnNvcjpkZWZhdWx0fS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC10YWItbGFiZWwubWF0LXRhYi1kaXNhYmxlZHtvcGFjaXR5Oi41fS5tYXQtdGFiLWxhYmVsIC5tYXQtdGFiLWxhYmVsLWNvbnRlbnR7ZGlzcGxheTppbmxpbmUtZmxleDtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyO2FsaWduLWl0ZW1zOmNlbnRlcjt3aGl0ZS1zcGFjZTpub3dyYXB9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LXRhYi1sYWJlbHtvcGFjaXR5OjF9QG1lZGlhKG1heC13aWR0aDogNTk5cHgpey5tYXQtdGFiLWxhYmVse3BhZGRpbmc6MCAxMnB4fX1AbWVkaWEobWF4LXdpZHRoOiA5NTlweCl7Lm1hdC10YWItbGFiZWx7cGFkZGluZzowIDEycHh9fS5tYXQtdGFiLWdyb3VwW21hdC1zdHJldGNoLXRhYnNdPi5tYXQtdGFiLWhlYWRlciAubWF0LXRhYi1sYWJlbHtmbGV4LWJhc2lzOjA7ZmxleC1ncm93OjF9Lm1hdC10YWItYm9keS13cmFwcGVye3Bvc2l0aW9uOnJlbGF0aXZlO292ZXJmbG93OmhpZGRlbjtkaXNwbGF5OmZsZXg7dHJhbnNpdGlvbjpoZWlnaHQgNTAwbXMgY3ViaWMtYmV6aWVyKDAuMzUsIDAsIDAuMjUsIDEpfS5tYXQtdGFiLWJvZHktd3JhcHBlci5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZXt0cmFuc2l0aW9uOm5vbmUgIWltcG9ydGFudDthbmltYXRpb246bm9uZSAhaW1wb3J0YW50fS5tYXQtdGFiLWJvZHl7dG9wOjA7bGVmdDowO3JpZ2h0OjA7Ym90dG9tOjA7cG9zaXRpb246YWJzb2x1dGU7ZGlzcGxheTpibG9jaztvdmVyZmxvdzpoaWRkZW47b3V0bGluZTowO2ZsZXgtYmFzaXM6MTAwJX0ubWF0LXRhYi1ib2R5Lm1hdC10YWItYm9keS1hY3RpdmV7cG9zaXRpb246cmVsYXRpdmU7b3ZlcmZsb3cteDpoaWRkZW47b3ZlcmZsb3cteTphdXRvO3otaW5kZXg6MTtmbGV4LWdyb3c6MX0ubWF0LXRhYi1ncm91cC5tYXQtdGFiLWdyb3VwLWR5bmFtaWMtaGVpZ2h0IC5tYXQtdGFiLWJvZHkubWF0LXRhYi1ib2R5LWFjdGl2ZXtvdmVyZmxvdy15OmhpZGRlbn0iXSxlbmNhcHN1bGF0aW9uOjJ9KSxufSkoKSxKMj0ob2MocW8oc28oY2xhc3N7fSkpKSwoKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsbG4sZXUsX2wsb2QsRXYsbG5dfSksbn0pKCkpO2Z1bmN0aW9uIFpMZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsInNwYW4iLDUpLFAoImNsaWNrIixmdW5jdGlvbihyKXtvZShlKTtsZXQgbz1TKCkuJGltcGxpY2l0O3JldHVybiBzZShTKCkub25BY3RpdmVQbHVnaW5TZWxlY3Rpb24ocixvLmlkKSl9KSxBKDEpLHYoKX1pZigyJm4pe2xldCBlPVMoKS4kaW1wbGljaXQ7emUoImRhdGEtcGx1Z2luLWlkIixlLmlkKSxDKDEpLGplKCIgIixlLnRhYl9uYW1lLCIgIil9fWZ1bmN0aW9uIEpMZShuLHQpezEmbiYmKF8oMCwibWF0LXRhYiIsMyksRSgxLFpMZSwyLDIsIm5nLXRlbXBsYXRlIiw0KSx2KCkpLDImbiYmeSgiZGlzYWJsZWQiLCF0LiRpbXBsaWNpdC5lbmFibGVkKX1mdW5jdGlvbiAkTGUobix0KXtpZigxJm4mJihfKDAsIm1hdC1vcHRpb24iLDkpLEEoMSksdigpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0O3koInZhbHVlIixlLmlkKSx6ZSgiZGF0YS1wbHVnaW4taWQiLGUuaWQpLEMoMSksamUoIiAiLGUudGFiX25hbWUsIiAiKX19ZnVuY3Rpb24gZTNlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwibWF0LWZvcm0tZmllbGQiLDYpKDEsIm1hdC1sYWJlbCIpLEEoMiwiSW5hY3RpdmUiKSx2KCksXygzLCJtYXQtc2VsZWN0Iiw3KSxQKCJzZWxlY3Rpb25DaGFuZ2UiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25EaXNhYmxlZFBsdWdpblNlbGVjdGlvbkNoYW5nZWQocikpfSksRSg0LCRMZSwyLDMsIm1hdC1vcHRpb24iLDgpLHYoKSgpfWlmKDImbil7bGV0IGU9UygpO0MoMykseSgidmFsdWUiLGUuc2VsZWN0ZWRQbHVnaW4pLEMoMSkseSgibmdGb3JPZiIsZS5kaXNhYmxlZFBsdWdpbnMpfX12YXIgUmllPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLm9uUGx1Z2luU2VsZWN0aW9uQ2hhbmdlZD1uZXcgR31nZXRBY3RpdmVQbHVnaW5JbmRleCgpe3JldHVybiB0aGlzLmFjdGl2ZVBsdWdpbnMuZmluZEluZGV4KCh7aWQ6ZX0pPT5lPT09dGhpcy5zZWxlY3RlZFBsdWdpbil9b25BY3RpdmVQbHVnaW5TZWxlY3Rpb24oZSxpKXtlLnN0b3BQcm9wYWdhdGlvbigpLHRoaXMub25QbHVnaW5TZWxlY3Rpb25DaGFuZ2VkLmVtaXQoaSl9b25EaXNhYmxlZFBsdWdpblNlbGVjdGlvbkNoYW5nZWQoZSl7dGhpcy5vblBsdWdpblNlbGVjdGlvbkNoYW5nZWQuZW1pdChlLnZhbHVlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sicGx1Z2luLXNlbGVjdG9yLWNvbXBvbmVudCJdXSxpbnB1dHM6e2FjdGl2ZVBsdWdpbnM6ImFjdGl2ZVBsdWdpbnMiLGRpc2FibGVkUGx1Z2luczoiZGlzYWJsZWRQbHVnaW5zIixzZWxlY3RlZFBsdWdpbjoic2VsZWN0ZWRQbHVnaW4ifSxvdXRwdXRzOntvblBsdWdpblNlbGVjdGlvbkNoYW5nZWQ6Im9uUGx1Z2luU2VsZWN0aW9uQ2hhbmdlZCJ9LGRlY2xzOjMsdmFyczozLGNvbnN0czpbWyJhbmltYXRpb25EdXJhdGlvbiIsIjEwMG1zIiwxLCJhY3RpdmUtcGx1Z2luLWxpc3QiLDMsInNlbGVjdGVkSW5kZXgiXSxbMywiZGlzYWJsZWQiLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsiZmxvYXRMYWJlbCIsIm5ldmVyIiw0LCJuZ0lmIl0sWzMsImRpc2FibGVkIl0sWyJtYXQtdGFiLWxhYmVsIiwiIl0sWzEsInBsdWdpbi1uYW1lIiwzLCJjbGljayJdLFsiZmxvYXRMYWJlbCIsIm5ldmVyIl0sWzMsInZhbHVlIiwic2VsZWN0aW9uQ2hhbmdlIl0sWzMsInZhbHVlIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbMywidmFsdWUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1hdC10YWItZ3JvdXAiLDApLEUoMSxKTGUsMiwxLCJtYXQtdGFiIiwxKSx2KCksRSgyLGUzZSw1LDIsIm1hdC1mb3JtLWZpZWxkIiwyKSksMiZlJiYoeSgic2VsZWN0ZWRJbmRleCIsaS5nZXRBY3RpdmVQbHVnaW5JbmRleCgpKSxDKDEpLHkoIm5nRm9yT2YiLGkuYWN0aXZlUGx1Z2lucyksQygxKSx5KCJuZ0lmIixpLmRpc2FibGVkUGx1Z2lucy5sZW5ndGg+MCkpfSxkZXBlbmRlbmNpZXM6W1oyLEVpZSx4dyxwZCxOdixIaCxPcyxkbixCZV0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7ZGlzcGxheTpmbGV4O2ZsZXg6MSAxIGF1dG87Zm9udC1zaXplOjE0cHg7aGVpZ2h0OjEwMCU7b3ZlcmZsb3c6aGlkZGVufW1hdC1mb3JtLWZpZWxkW19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4OjAgMDttYXJnaW4tdG9wOjVweDt3aWR0aDoxMzBweH1tYXQtbGFiZWxbX25nY29udGVudC0lQ09NUCVdLCBtYXQtc2VsZWN0W19uZ2NvbnRlbnQtJUNPTVAlXSwgbWF0LW9wdGlvbltfbmdjb250ZW50LSVDT01QJV17Zm9udC13ZWlnaHQ6NTAwO3RleHQtdHJhbnNmb3JtOnVwcGVyY2FzZX0uYWN0aXZlLXBsdWdpbi1saXN0W19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1zZWxmOnN0cmV0Y2g7ZmxleDoxIDEgYXV0bztvdmVyZmxvdzpoaWRkZW59LnBsdWdpbi1uYW1lW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7ZGlzcGxheTppbmxpbmUtZmxleDtoZWlnaHQ6MTAwJTtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyO3BhZGRpbmc6MCAxMnB4O3dpZHRoOjEwMCV9W19uZ2hvc3QtJUNPTVAlXSAgICAgLmFjdGl2ZS1wbHVnaW4tbGlzdC5tYXQtcHJpbWFyeSAubWF0LXRhYi1saXN0IC5tYXQtaW5rLWJhcntiYWNrZ3JvdW5kLWNvbG9yOmN1cnJlbnRDb2xvcn1bX25naG9zdC0lQ09NUCVdICAgICAuYWN0aXZlLXBsdWdpbi1saXN0IC5tYXQtdGFiLWxhYmVsLCBbX25naG9zdC0lQ09NUCVdICAgICAuYWN0aXZlLXBsdWdpbi1saXN0IC5tYXQtdGFiLWxpbmt7Y29sb3I6aW5oZXJpdDtvcGFjaXR5Oi43fVtfbmdob3N0LSVDT01QJV0gICAgIC5hY3RpdmUtcGx1Z2luLWxpc3QgLm1hdC10YWItbGFiZWwubWF0LXRhYi1sYWJlbC1hY3RpdmUsIFtfbmdob3N0LSVDT01QJV0gICAgIC5hY3RpdmUtcGx1Z2luLWxpc3QgLm1hdC10YWItbGluay5tYXQtdGFiLWxhYmVsLWFjdGl2ZXtvcGFjaXR5OjF9W19uZ2hvc3QtJUNPTVAlXSAgICAgLmFjdGl2ZS1wbHVnaW4tbGlzdCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1jaGV2cm9ue2JvcmRlci1jb2xvcjpjdXJyZW50Q29sb3J9W19uZ2hvc3QtJUNPTVAlXSAgICAgLmFjdGl2ZS1wbHVnaW4tbGlzdCAubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbi1kaXNhYmxlZHt2aXNpYmlsaXR5OmhpZGRlbn1bX25naG9zdC0lQ09NUCVdICAgICAuYWN0aXZlLXBsdWdpbi1saXN0IC5tYXQtdGFiLWRpc2FibGVke2Rpc3BsYXk6bm9uZX1bX25naG9zdC0lQ09NUCVdICAgICAuYWN0aXZlLXBsdWdpbi1saXN0IG1hdC10YWItbGlzdCwgW19uZ2hvc3QtJUNPTVAlXSAgICAgLmFjdGl2ZS1wbHVnaW4tbGlzdCAubWF0LXRhYi1oZWFkZXIsIFtfbmdob3N0LSVDT01QJV0gICAgIC5hY3RpdmUtcGx1Z2luLWxpc3QgLm1hdC10YWItbGFiZWxzLCBbX25naG9zdC0lQ09NUCVdICAgICAuYWN0aXZlLXBsdWdpbi1saXN0IC5tYXQtdGFiLWxhYmVse2hlaWdodDoxMDAlfVtfbmdob3N0LSVDT01QJV0gICAgIC5hY3RpdmUtcGx1Z2luLWxpc3QgLm1hdC10YWItbGFiZWx7bWluLXdpZHRoOjQ4cHg7cGFkZGluZzowO3RleHQtdHJhbnNmb3JtOnVwcGVyY2FzZX1bX25naG9zdC0lQ09NUCVdICAgICAuYWN0aXZlLXBsdWdpbi1saXN0IC5tYXQtdGFiLWxhYmVsLWNvbnRlbnR7aGVpZ2h0OjEwMCV9W19uZ2hvc3QtJUNPTVAlXSAgICAgLmFjdGl2ZS1wbHVnaW4tbGlzdCBtYXQtdGFiLWhlYWRlciAubWF0LXRhYi1saXN0e3BhZGRpbmc6MCAzNnB4fVtfbmdob3N0LSVDT01QJV0gICAgIC5hY3RpdmUtcGx1Z2luLWxpc3QgbWF0LXRhYi1oZWFkZXI+OmZpcnN0LWNoaWxkLCBbX25naG9zdC0lQ09NUCVdICAgICAuYWN0aXZlLXBsdWdpbi1saXN0IG1hdC10YWItaGVhZGVyPi5tYXQtdGFiLWxhYmVsLWNvbnRhaW5lciwgW19uZ2hvc3QtJUNPTVAlXSAgICAgLmFjdGl2ZS1wbHVnaW4tbGlzdCBtYXQtdGFiLWhlYWRlcj46bGFzdC1jaGlsZHtib3R0b206MDtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MH1bX25naG9zdC0lQ09NUCVdICAgICAuYWN0aXZlLXBsdWdpbi1saXN0IG1hdC10YWItaGVhZGVyPjpmaXJzdC1jaGlsZCwgW19uZ2hvc3QtJUNPTVAlXSAgICAgLmFjdGl2ZS1wbHVnaW4tbGlzdCBtYXQtdGFiLWhlYWRlcj4ubWF0LXRhYi1sYWJlbC1jb250YWluZXJ7bGVmdDowfVtfbmdob3N0LSVDT01QJV0gICAgIC5hY3RpdmUtcGx1Z2luLWxpc3QgbWF0LXRhYi1oZWFkZXI+Omxhc3QtY2hpbGQsIFtfbmdob3N0LSVDT01QJV0gICAgIC5hY3RpdmUtcGx1Z2luLWxpc3QgbWF0LXRhYi1oZWFkZXI+Lm1hdC10YWItbGFiZWwtY29udGFpbmVye3JpZ2h0OjB9W19uZ2hvc3QtJUNPTVAlXSAgICAgLmFjdGl2ZS1wbHVnaW4tbGlzdCBtYXQtdGFiLWhlYWRlcj4ubWF0LXRhYi1oZWFkZXItcGFnaW5hdGlvbntiYWNrZ3JvdW5kLWNvbG9yOiNmNTdjMDB9Ym9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgICAuYWN0aXZlLXBsdWdpbi1saXN0IG1hdC10YWItaGVhZGVyPi5tYXQtdGFiLWhlYWRlci1wYWdpbmF0aW9ue2JhY2tncm91bmQtY29sb3I6I2VmNmMwMH0iXX0pLG59KSgpLE9pZT1KKHJ2LG49Pk9iamVjdC5rZXlzKG4pLm1hcCh0PT5PYmplY3QuYXNzaWduKHt9LHtpZDp0fSxuW3RdKSkpLG4zZT1KKE9pZSxuPT5uLmZpbHRlcih0PT4hdC5lbmFibGVkKSksa2llPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuYWN0aXZlUGx1Z2luJD10aGlzLnN0b3JlLnBpcGUodnQoUnMpKSx0aGlzLnBsdWdpbnMkPXRoaXMuc3RvcmUucGlwZSh2dChPaWUpKSx0aGlzLmRpc2FibGVkUGx1Z2lucyQ9dGhpcy5zdG9yZS5waXBlKHZ0KG4zZSkpfW9uUGx1Z2luU2VsZWN0aW9uQ2hhbmdlKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goWnUoe3BsdWdpbjplfSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sicGx1Z2luLXNlbGVjdG9yIl1dLGRlY2xzOjQsdmFyczo5LGNvbnN0czpbWzMsImFjdGl2ZVBsdWdpbnMiLCJkaXNhYmxlZFBsdWdpbnMiLCJzZWxlY3RlZFBsdWdpbiIsIm9uUGx1Z2luU2VsZWN0aW9uQ2hhbmdlZCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwicGx1Z2luLXNlbGVjdG9yLWNvbXBvbmVudCIsMCksUCgib25QbHVnaW5TZWxlY3Rpb25DaGFuZ2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblBsdWdpblNlbGVjdGlvbkNoYW5nZShvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLHYoKSksMiZlJiZ5KCJhY3RpdmVQbHVnaW5zIixVKDEsMyxpLnBsdWdpbnMkKSkoImRpc2FibGVkUGx1Z2lucyIsVSgyLDUsaS5kaXNhYmxlZFBsdWdpbnMkKSkoInNlbGVjdGVkUGx1Z2luIixVKDMsNyxpLmFjdGl2ZVBsdWdpbiQpKX0sZGVwZW5kZW5jaWVzOltSaWUsR2VdLGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpLHIzZT1KKHJ2LFJzLChuLHQpPT4hKCF0fHwhblt0XSkmJm5bdF0uZGlzYWJsZV9yZWxvYWQpLEZpZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLnJlbG9hZERpc2FibGVkJD10aGlzLnN0b3JlLnNlbGVjdChyM2UpLHRoaXMuaXNSZWxvYWRpbmckPXRoaXMuc3RvcmUuc2VsZWN0KEwkKS5waXBlKGZyKHRoaXMucmVsb2FkRGlzYWJsZWQkKSxMKChbaSxyXSk9PiFyJiZpPT09T2UuTE9BRElORykpLHRoaXMubGFzdExvYWRlZFRpbWVJbk1zJD10aGlzLnN0b3JlLnNlbGVjdChpdil9dHJpZ2dlclJlbG9hZCgpe3RoaXMuc3RvcmUuZGlzcGF0Y2goRmEoKSl9Z2V0UmVsb2FkVGl0bGUoZSl7cmV0dXJuIGU/YExhc3QgVXBkYXRlZDogJHtlfWA6IkxvYWRpbmcuLi4ifX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siYXBwLWhlYWRlci1yZWxvYWQiXV0sZGVjbHM6Nix2YXJzOjEzLGNvbnN0czpbWyJtYXQtaWNvbi1idXR0b24iLCIiLDEsInJlbG9hZC1idXR0b24iLDMsInRpdGxlIiwiZGlzYWJsZWQiLCJjbGljayJdLFsic3ZnSWNvbiIsInJlZnJlc2hfMjRweCIsMSwicmVmcmVzaC1pY29uIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJidXR0b24iLDApLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLnRyaWdnZXJSZWxvYWQoKX0pLEIoMSwiYXN5bmMiKSxCKDIsImRhdGUiKSxCKDMsImFzeW5jIiksQig0LCJhc3luYyIpLE8oNSwibWF0LWljb24iLDEpLHYoKSksMiZlJiYoZXQoImxvYWRpbmciLFUoMSw0LGkuaXNSZWxvYWRpbmckKSkseSgidGl0bGUiLGkuZ2V0UmVsb2FkVGl0bGUoSmYoMiw2LFUoMyw5LGkubGFzdExvYWRlZFRpbWVJbk1zJCksIm1lZGl1bSIpKSkoImRpc2FibGVkIixVKDQsMTEsaS5yZWxvYWREaXNhYmxlZCQpKSl9LGRlcGVuZGVuY2llczpbX24sR3QsR2UsVV9dLHN0eWxlczpbIi5yZWxvYWQtYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXSwgLnJlZnJlc2gtaWNvbltfbmdjb250ZW50LSVDT01QJV0ge1xuICAgICAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgICAgICBkaXNwbGF5OiBmbGV4O1xuICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgICAgIH1cblxuICAgICAgLnJlbG9hZC1idXR0b24ubG9hZGluZ1tfbmdjb250ZW50LSVDT01QJV0ge1xuICAgICAgICBhbmltYXRpb246IHJvdGF0ZSAycyBsaW5lYXIgaW5maW5pdGU7XG4gICAgICB9XG5cbiAgICAgIEBrZXlmcmFtZXMgcm90YXRlIHtcbiAgICAgICAgMCUge1xuICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKDBkZWcpO1xuICAgICAgICB9XG4gICAgICAgIDUwJSB7XG4gICAgICAgICAgdHJhbnNmb3JtOiByb3RhdGUoMTgwZGVnKTtcbiAgICAgICAgfVxuICAgICAgICAxMDAlIHtcbiAgICAgICAgICB0cmFuc2Zvcm06IHJvdGF0ZSgzNjBkZWcpO1xuICAgICAgICB9XG4gICAgICB9Il19KSxufSkoKSxOaWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siYXBwLWhlYWRlciJdXSxkZWNsczoxMCx2YXJzOjAsY29uc3RzOltbMSwiYnJhbmQiXSxbMSwicGx1Z2lucyJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwiaHJlZiIsImh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCIsInJlbCIsIm5vb3BlbmVyIG5vcmVmZXJyZXIiLCJ0YXJnZXQiLCJfYmxhbmsiLCJhcmlhLWxhYmVsIiwiSGVscCIsMSwicmVhZG1lIl0sWyJzdmdJY29uIiwiaGVscF9vdXRsaW5lXzI0cHgiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1hdC10b29sYmFyIikoMSwic3BhbiIsMCksQSgyLCJUZW5zb3JCb2FyZCIpLHYoKSxPKDMsInBsdWdpbi1zZWxlY3RvciIsMSkoNCwidGJkZXYtdXBsb2FkLWJ1dHRvbiIpKDUsImFwcC1oZWFkZXItZGFyay1tb2RlLXRvZ2dsZSIpKDYsImFwcC1oZWFkZXItcmVsb2FkIikoNywic2V0dGluZ3MtYnV0dG9uIiksXyg4LCJhIiwyKSxPKDksIm1hdC1pY29uIiwzKSx2KCkoKSl9LGRlcGVuZGVuY2llczpbSXYsR3QsWm5lLGxpZSxoaWUseWllLGtpZSxGaWVdLHN0eWxlczpbIm1hdC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7Y29sb3I6I2ZmZjtkaXNwbGF5OmZsZXg7aGVpZ2h0OjY0cHg7b3ZlcmZsb3c6aGlkZGVuO3dpZHRoOjEwMCV9dGJkZXYtdXBsb2FkLWJ1dHRvbi5zaG93bltfbmdjb250ZW50LSVDT01QJV17bWFyZ2luOjAgOHB4IDAgMTZweH0uYnJhbmRbX25nY29udGVudC0lQ09NUCVdLCAucmVhZG1lW19uZ2NvbnRlbnQtJUNPTVAlXSwgYXBwLWhlYWRlci1yZWxvYWRbX25nY29udGVudC0lQ09NUCVdLCBzZXR0aW5ncy1idXR0b25bX25nY29udGVudC0lQ09NUCVde2ZsZXg6MCAwIGF1dG99LmJyYW5kW19uZ2NvbnRlbnQtJUNPTVAlXXtsZXR0ZXItc3BhY2luZzotMC4wMjVlbTttYXJnaW4tbGVmdDoxMHB4O3RleHQtcmVuZGVyaW5nOm9wdGltaXplTGVnaWJpbGl0eX0ucGx1Z2luc1tfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleDtmbGV4OjEgMSBhdXRvO2ZvbnQtc2l6ZToxNHB4O2hlaWdodDoxMDAlO292ZXJmbG93OmhpZGRlbn0iXX0pLG59KSgpLGwzZV9zZXREb2N1bWVudFRpdGxlPWZ1bmN0aW9uKG4pe2RvY3VtZW50LnRpdGxlPW59LExpZT0oKCk9PntjbGFzcyBue25nT25DaGFuZ2VzKGUpe2UudGl0bGUmJmwzZV9zZXREb2N1bWVudFRpdGxlKGUudGl0bGUuY3VycmVudFZhbHVlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sicGFnZS10aXRsZS1jb21wb25lbnQiXV0saW5wdXRzOnt0aXRsZToidGl0bGUifSxmZWF0dXJlczpbRnRdLGRlY2xzOjAsdmFyczowLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7fSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxCaWU9IlRlbnNvckJvYXJkIixWaWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuc3RvcmU9ZSx0aGlzLmN1c3RvbUJyYW5kTmFtZT1pLHRoaXMuZ2V0RXhwZXJpbWVudElkJD10aGlzLnN0b3JlLnNlbGVjdChXbykucGlwZShMKHI9PnI/LlswXSkpLHRoaXMuZXhwZXJpbWVudE5hbWUkPXRoaXMuZ2V0RXhwZXJpbWVudElkJC5waXBlKFllKEJvb2xlYW4pLHhuKHI9PnRoaXMuc3RvcmUuc2VsZWN0KHZJLHtleHBlcmltZW50SWQ6cn0pKSxMKHI9PnI/ci5uYW1lOm51bGwpKSx0aGlzLnRpdGxlJD10aGlzLnN0b3JlLnNlbGVjdChvdikucGlwZShmcih0aGlzLnN0b3JlLnNlbGVjdChxdSksdGhpcy5leHBlcmltZW50TmFtZSQpLEwoKFtyLG8sc10pPT57bGV0IGE9dGhpcy5jdXN0b21CcmFuZE5hbWV8fEJpZTtyZXR1cm4gci53aW5kb3dfdGl0bGU/ci53aW5kb3dfdGl0bGU6bz09PWhpLkVYUEVSSU1FTlQmJnM/YCR7c30gLSAke2F9YDphfSksem4odGhpcy5jdXN0b21CcmFuZE5hbWV8fEJpZSkseWkoKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpLE0oQSQsOCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInBhZ2UtdGl0bGUiXV0sZGVjbHM6Mix2YXJzOjMsY29uc3RzOltbMywidGl0bGUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihPKDAsInBhZ2UtdGl0bGUtY29tcG9uZW50IiwwKSxCKDEsImFzeW5jIikpLDImZSYmeSgidGl0bGUiLFUoMSwxLGkudGl0bGUkKSl9LGRlcGVuZGVuY2llczpbTGllLEdlXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVdIHtcbiAgICAgICAgZGlzcGxheTogbm9uZTtcbiAgICAgIH0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLEhpZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLm5nVW5zdWJzY3JpYmU9bmV3IGtlLHRoaXMuZ2V0UGFnZVNpemUkPXRoaXMuc3RvcmUucGlwZSh2dChLbSkpLHRoaXMucGFnaW5hdGVkVmlld1N0b3JlPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInRmLXBhZ2luYXRlZC12aWV3LXN0b3JlIikudGZfcGFnaW5hdGVkX3ZpZXd9bmdPbkluaXQoKXt0aGlzLmdldFBhZ2VTaXplJC5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSkseWkoKSkuc3Vic2NyaWJlKGU9Pnt0aGlzLnBhZ2luYXRlZFZpZXdTdG9yZS5zZXRMaW1pdChlKX0pfW5nT25EZXN0cm95KCl7dGhpcy5uZ1Vuc3Vic2NyaWJlLm5leHQoKSx0aGlzLm5nVW5zdWJzY3JpYmUuY29tcGxldGUoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInNldHRpbmdzLXBvbHltZXItaW50ZXJvcCJdXSxkZWNsczowLHZhcnM6MCx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpe30sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksVWllPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy52Y1JlZj1lfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKE9pKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGItd2ViYXBwIl1dLGRlY2xzOjksdmFyczowLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJhcHAtaGVhZGVyIiksXygxLCJtYWluIiksTygyLCJyb3V0ZXItb3V0bGV0IiksdigpLE8oMywiYWxlcnQtc25hY2tiYXIiKSg0LCJoYXNoLXN0b3JhZ2UiKSg1LCJwYWdlLXRpdGxlIikoNiwic2V0dGluZ3MtcG9seW1lci1pbnRlcm9wIikoNywiZGFyay1tb2RlLXN1cHBvcnRlciIpKDgsImZlYXR1cmUtZmxhZy1tb2RhbC10cmlnZ2VyIikpfSxkZXBlbmRlbmNpZXM6W0huZSx6bmUsSXRlLGpuZSxXbmUsTmllLFZpZSxIaWVdLHN0eWxlczpbImh0bWxbX25nY29udGVudC0lQ09NUCVdLCBib2R5W19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LWZhbWlseTpSb2JvdG8sc2Fucy1zZXJpZjtoZWlnaHQ6MTAwJTttYXJnaW46MDtwYWRkaW5nOjB9W19uZ2hvc3QtJUNPTVAlXXtiYWNrZ3JvdW5kOiNmNWY1ZjU7ZGlzcGxheTpmbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjtoZWlnaHQ6MTAwJX1hcHAtaGVhZGVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3gtc2hhZG93OjAgMXB4IDNweCAzcHggcmdiYSgwLDAsMCwuMjUpO2ZsZXg6MCAwO3otaW5kZXg6MX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICBhcHAtaGVhZGVyW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgYXBwLWhlYWRlcltfbmdjb250ZW50LSVDT01QJV17Ym94LXNoYWRvdzowIDFweCAzcHggM3B4IHJnYmEoMjU1LDI1NSwyNTUsLjEpfW1haW5bX25nY29udGVudC0lQ09NUCVde2ZsZXgtZ3JvdzoxO292ZXJmbG93OmF1dG99Il19KSxufSkoKSx6aWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLFFfLFhfLHFjXX0pLG59KSgpLGppZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksR2llPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W3twcm92aWRlOmZoLHVzZUNsYXNzOnIkfV19KSxufSkoKSxXaWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLEdpZV19KSxufSkoKSxxaWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lXX0pLG59KSgpLFlpZT17aWQ6VEEsbmFtZToiIixzdGFydF90aW1lOjB9LGgzZT12cih7ZXhwZXJpbWVudE1hcDp7W1lpZS5pZF06WWllfX0pO2Z1bmN0aW9uIFhpZShuLHQpe3JldHVybiBGbSh7ZGF0YTpoM2V9KShuLHQpfXZhciBRaWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W3dyLmZvckZlYXR1cmUoX0ksWGllKV19KSxufSkoKSxLaWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLFBuLGxjXX0pLG59KSgpLFppZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsS2llXX0pLG59KSgpLEppZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsanIsejIsUG4sTHMsT2gscG4sSGFdfSksbn0pKCk7ZnVuY3Rpb24gZjNlKCl7cmV0dXJuIEooWU0sbj0+KHthdXRvUmVsb2FkOm59KSl9ZnVuY3Rpb24gbTNlKCl7cmV0dXJuIEooWE0sbj0+KHthdXRvUmVsb2FkUGVyaW9kSW5NczpufSkpfWZ1bmN0aW9uIGczZSgpe3JldHVybiBKKEttLG49Pih7cGFnZVNpemU6bn0pKX12YXIgJDI9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W3dyLmZvckZlYXR1cmUoWkksJG5lKSxTci5kZWZpbmVHbG9iYWxTZXR0aW5nKGYzZSksU3IuZGVmaW5lR2xvYmFsU2V0dGluZyhtM2UpLFNyLmRlZmluZUdsb2JhbFNldHRpbmcoZzNlKSxKaWVdfSksbn0pKCksJGllPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W3twcm92aWRlOiJ3aW5kb3ciLHVzZVZhbHVlOndpbmRvd31dLGltcG9ydHM6W3VpZSxNZSxQbixPaCxwbl19KSxufSkoKSxlcmU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W1BuLHBuLEoyLEpuZSxsYyx6aCxNZSxlYywkMiwkaWVdfSksbn0pKCksdHJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXtsZXQgcj1lLmJ5cGFzc1NlY3VyaXR5VHJ1c3RSZXNvdXJjZVVybCgiLi9pY29uX2J1bmRsZS5zdmciKTtpLmFkZFN2Z0ljb25TZXQocil9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooVG0pLGooenYpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbcG5dfSksbn0pKCksdFU9bmV3IHBlKCJbcGx1Z2luc10gUGx1Z2luIHJlZ2lzdHJ5IGNvbmZpZyIpLG5yZT1uZXcgTWFwLEJzPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7aWYoIWUpcmV0dXJuO2xldCBpPW5ldyBTZXQoZS5tYXAocj0+ci5wbHVnaW5OYW1lKSk7Y29uc29sZS5hc3NlcnQoaS5zaXplPT09ZS5sZW5ndGgsIkNhbm5vdCByZWdpc3RlciB0aGUgc2FtZSBwbHVnaW4gbXVsdGlwbGUgdGltZXMuIik7Zm9yKGxldCByIG9mIGUpe2xldHtwbHVnaW5OYW1lOm8sY29tcG9uZW50Q2xhc3M6c309cjtucmUuc2V0KG8scyl9fXN0YXRpYyBmb3JQbHVnaW4oZSxpKXtyZXR1cm57bmdNb2R1bGU6bixwcm92aWRlcnM6W3twcm92aWRlOnRVLG11bHRpOiEwLHVzZVZhbHVlOntwbHVnaW5OYW1lOmUsY29tcG9uZW50Q2xhc3M6aX19XX19Z2V0Q29tcG9uZW50KGUpe3JldHVybiBucmUuZ2V0KGUpfHxudWxsfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKHRVLDgpKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksZVA9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLmh0dHA9ZSx0aGlzLmh0dHBQYXRoUHJlZml4PSJkYXRhL3BsdWdpbi9kZWJ1Z2dlci12MiJ9ZmV0Y2hSdW5zKCl7cmV0dXJuIHRoaXMuaHR0cC5nZXQodGhpcy5odHRwUGF0aFByZWZpeCsiL3J1bnMiKX1mZXRjaEV4ZWN1dGlvbkRpZ2VzdHMoZSxpLHIpe3JldHVybiB0aGlzLmh0dHAuZ2V0KHRoaXMuaHR0cFBhdGhQcmVmaXgrIi9leGVjdXRpb24vZGlnZXN0cyIse3BhcmFtczp7cnVuOmUsYmVnaW46U3RyaW5nKGkpLGVuZDpTdHJpbmcocil9fSl9ZmV0Y2hFeGVjdXRpb25EYXRhKGUsaSxyKXtyZXR1cm4gdGhpcy5odHRwLmdldCh0aGlzLmh0dHBQYXRoUHJlZml4KyIvZXhlY3V0aW9uL2RhdGEiLHtwYXJhbXM6e3J1bjplLGJlZ2luOlN0cmluZyhpKSxlbmQ6U3RyaW5nKHIpfX0pfWZldGNoR3JhcGhFeGVjdXRpb25EaWdlc3RzKGUsaSxyLG8pe2lmKHZvaWQgMCE9PW8pdGhyb3cgbmV3IEVycm9yKCJ0cmFjZV9pZCBpcyBub3QgaW1wbGVtZW50ZWQgZm9yIGZldGNoR3JhcGhFeGVjdXRpb25EaWdlc3RzKCkgeWV0Iik7cmV0dXJuIHRoaXMuaHR0cC5nZXQodGhpcy5odHRwUGF0aFByZWZpeCsiL2dyYXBoX2V4ZWN1dGlvbi9kaWdlc3RzIix7cGFyYW1zOntydW46ZSxiZWdpbjpTdHJpbmcoaSksZW5kOlN0cmluZyhyKX19KX1mZXRjaEdyYXBoRXhlY3V0aW9uRGF0YShlLGkscixvKXtpZih2b2lkIDAhPT1vKXRocm93IG5ldyBFcnJvcigidHJhY2VfaWQgaXMgbm90IGltcGxlbWVudGVkIGZvciBmZXRjaEdyYXBoRXhlY3V0aW9uRGF0YSgpIHlldCIpO3JldHVybiB0aGlzLmh0dHAuZ2V0KHRoaXMuaHR0cFBhdGhQcmVmaXgrIi9ncmFwaF9leGVjdXRpb24vZGF0YSIse3BhcmFtczp7cnVuOmUsYmVnaW46U3RyaW5nKGkpLGVuZDpTdHJpbmcocil9fSl9ZmV0Y2hHcmFwaE9wSW5mbyhlLGkscil7cmV0dXJuIHRoaXMuaHR0cC5nZXQodGhpcy5odHRwUGF0aFByZWZpeCsiL2dyYXBocy9vcF9pbmZvIix7cGFyYW1zOntydW46ZSxncmFwaF9pZDppLG9wX25hbWU6cn19KX1mZXRjaFNvdXJjZUZpbGVMaXN0KGUpe3JldHVybiB0aGlzLmh0dHAuZ2V0KHRoaXMuaHR0cFBhdGhQcmVmaXgrIi9zb3VyY2VfZmlsZXMvbGlzdCIse3BhcmFtczp7cnVuOmV9fSl9ZmV0Y2hTb3VyY2VGaWxlKGUsaSl7cmV0dXJuIHRoaXMuaHR0cC5nZXQodGhpcy5odHRwUGF0aFByZWZpeCsiL3NvdXJjZV9maWxlcy9maWxlIix7cGFyYW1zOntydW46ZSxpbmRleDpTdHJpbmcoaSl9fSl9ZmV0Y2hTdGFja0ZyYW1lcyhlLGkpe3JldHVybiB0aGlzLmh0dHAuZ2V0KHRoaXMuaHR0cFBhdGhQcmVmaXgrIi9zdGFja19mcmFtZXMvc3RhY2tfZnJhbWVzIix7cGFyYW1zOntydW46ZSxzdGFja19mcmFtZV9pZHM6aS5qb2luKCIsIil9fSkucGlwZShMKHI9Pih7c3RhY2tfZnJhbWVzOnIuc3RhY2tfZnJhbWVzLm1hcChvPT5mdW5jdGlvbihuKXtyZXR1cm57aG9zdF9uYW1lOm5bMF0sZmlsZV9wYXRoOm5bMV0sbGluZW5vOm5bMl0sZnVuY3Rpb25fbmFtZTpuWzNdfX0obykpfSkpKX1mZXRjaEFsZXJ0cyhlLGkscixvKXtsZXQgcz17cnVuOmUsYmVnaW46U3RyaW5nKGkpLGVuZDpTdHJpbmcocil9O3JldHVybiB2b2lkIDAhPT1vJiYocy5hbGVydF90eXBlPW8pLHRoaXMuaHR0cC5nZXQodGhpcy5odHRwUGF0aFByZWZpeCsiL2FsZXJ0cyIse3BhcmFtczpzfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooa2EpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxpcmU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbZVBdLGltcG9ydHM6W0t1XX0pLG59KSgpLHRQPWJlKCJbRGVidWdnZXJdIERlYnVnZ2VyIExvYWRlZCIpLG5QPWJlKCJbRGVidWdnZXJdIERlYnVnZ2VyIFVubG9hZGVkIiksTXc9YmUoIltEZWJ1Z2dlcl0gQSBOZXcgRGVidWdnZXIgRGF0YSBQb2xsaW5nIEV2ZW50IEJlZ2lucyIpLGlQPWJlKCJbRGVidWdnZXJdIERlYnVnZ2VyIFJ1bnMgUmVxdWVzdGVkIiksclA9YmUoIltEZWJ1Z2dlcl0gRGVidWdnZXIgUnVucyBMb2FkZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxycmU9YmUoIltEZWJ1Z2dlcl0gRGVidWdnZXIgUnVucyBSZXF1ZXN0IEZhaWxlZCIpLHd3PWJlKCJbRGVidWdnZXJdIE51bWJlciBhbmQgQnJlYWtkb3duIG9mIEFsZXJ0cyBSZXF1ZXN0ZWQiKSxvUD1iZSgiW0RlYnVnZ2VyXSBOdW1iZXIgYW5kIEJyZWFrZG93biBvZiBBbGVydHMgTG9hZGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksc1A9YmUoIltEZWJ1Z2dlcl0gQWxlcnRzIERhdGEgb2YgYW4gQWxlcnRUeXBlIElzIExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLEd2PWJlKCJbRGVidWdnZXJdIEFsZXJ0IFR5cGUgRm9jdXMgVG9nZ2xlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLGFQPWJlKCJbRGVidWdnZXJdIE51bWJlciBvZiBUb3AtTGV2ZWwgRXhlY3V0aW9ucyBSZXF1ZXN0ZWQiKSxsUD1iZSgiW0RlYnVnZ2VyXSBOdW1iZXIgb2YgVG9wLUxldmVsIEV4ZWN1dGlvbnMgTG9hZGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksY1A9YmUoIltEZWJ1Z2dlcl0gRXhlY3V0aW9uRGlnZXN0cyBSZXF1ZXN0ZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSx1UD1iZSgiW0RlYnVnZ2VyXSBFeGVjdXRpb25EaWdlc3RzIExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFd2PWJlKCJbRGVidWdnZXJdIFNjcm9sbCBMZWZ0d2FyZCBvbiB0aGUgRXhlY3V0aW9uIFRpbWVsaW5lIikscXY9YmUoIltEZWJ1Z2dlcl0gU2Nyb2xsIFJpZ2h0d2FyZCBvbiB0aGUgRXhlY3V0aW9uIFRpbWVsaW5lIiksWXY9YmUoIltEZWJ1Z2dlcl0gU2Nyb2xsIHRoZSBFeGVjdXRpb24gVGltZWxpbmUgdG8gR2l2ZW4gSW5kZXgiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxYdj1iZSgiW0RlYnVnZ2VyXSBFeGVjdXRpb24gRGF0YSBPYmplY3RzIEJlaW5nIEZvY3VzZWQgT24iLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxkUD1iZSgiW0RlYnVnZ2VyXSBFeGVjdXRpb24gRGF0YSBPYmplY3RzIExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLHBQPWJlKCJbRGVidWdnZXJdIE51bWJlciBvZiBJbnRyYS1HcmFwaCBFeGVjdXRpb25zIFJlcXVlc3RlZCIpLGhQPWJlKCJbRGVidWdnZXJdIE51bWJlciBvZiBJbnRyYS1HcmFwaCBFeGVjdXRpb25zIExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLGZQPWJlKCJbRGVidWdnZXJdIEludHJhLUdyYXBoIEV4ZWN1dGlvbiBEYXRhIFJlcXVlc3RlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLG1QPWJlKCJbRGVidWdnZXJdIEludHJhLUdyYXBoIEV4ZWN1dGlvbiBEYXRhIExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFF2PWJlKCJbRGVidWdnZXJdIFNjcm9sbCBJbnRyYS1HcmFwaCBFeGVjdXRpb24gTGlzdCB0byBHaXZlbiBJbmRleCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLEt2PWJlKCJbRGVidWdnZXJdIEdyYXBoIEV4ZWN1dGlvbiBpcyBGb2N1c2VkIE9uIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksWnY9YmUoIltEZWJ1Z2dlcl0gR3JhcGggT3AgSXMgRm9jdXNlZCBPbiIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLGdQPWJlKCJbRGVidWdnZXJdIEdyYXBoIE9wIEluZm8gUmVxdWVzdGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksX1A9YmUoIltEZWJ1Z2dlcl0gR3JhcGggT3AgSW5mbyBMb2FkZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSx2UD1iZSgiW0RlYnVnZ2VyXSBTb3VyY2UgRmlsZSBMaXN0IFJlcXVlc3RlZC4iKSx5UD1iZSgiW0RlYnVnZ2VyXSBTb3VyY2UgRmlsZSBMaXN0IExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLEp2PWJlKCJbRGVidWdnZXJdIFNvdXJjZSBGaWxlIExpbmUgSXMgRm9jdXNlZCBvbiIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLGJQPWJlKCJbRGVidWdnZXJdIFNvdXJjZSBGaWxlIFJlcXVlc3RlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLHhQPWJlKCJbRGVidWdnZXJdIFNvdXJjZSBGaWxlIExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFN3PWJlKCJbRGVidWdnZXJdIEEgU2V0IG9mIFN0YWNrIEZyYW1lcyBIYXZlIEJlZW4gTG9hZGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksRXc9ImRlYnVnZ2VyIixhcz0oKCk9PihmdW5jdGlvbihuKXtuW24uVU5TUEVDSUZJRUQ9MF09IlVOU1BFQ0lGSUVEIixuW24uTk9fVEVOU09SPTFdPSJOT19URU5TT1IiLG5bbi5DVVJUX0hFQUxUSD0yXT0iQ1VSVF9IRUFMVEgiLG5bbi5DT05DSVNFX0hFQUxUSD0zXT0iQ09OQ0lTRV9IRUFMVEgiLG5bbi5GVUxMX0hFQUxUSD00XT0iRlVMTF9IRUFMVEgiLG5bbi5TSEFQRT01XT0iU0hBUEUiLG5bbi5GVUxMX05VTUVSSUNTPTZdPSJGVUxMX05VTUVSSUNTIixuW24uRlVMTF9URU5TT1I9N109IkZVTExfVEVOU09SIixuW24uUkVEVUNFX0lORl9OQU5fVEhSRUVfU0xPVFM9OF09IlJFRFVDRV9JTkZfTkFOX1RIUkVFX1NMT1RTIn0oYXN8fChhcz17fSkpLGFzKSkoKSxtZD0oKCk9PihmdW5jdGlvbihuKXtuLkZVTkNUSU9OX1JFQ09NUElMRV9BTEVSVD0iRnVuY3Rpb25SZWNvbXBpbGVzQWxlcnQiLG4uSU5GX05BTl9BTEVSVD0iSW5mTmFuQWxlcnQiLG4uVEVOU09SX1NIQVBFX0FMRVJUPSJUZW5zb3JTaGFwZUFsZXJ0In0obWR8fChtZD17fSkpLG1kKSkoKSx4cz0oKCk9PihmdW5jdGlvbihuKXtuW24uRVhFQ1VUSU9OPTBdPSJFWEVDVVRJT04iLG5bbi5HUkFQSF9PUF9DUkVBVElPTj0xXT0iR1JBUEhfT1BfQ1JFQVRJT04ifSh4c3x8KHhzPXt9KSkseHMpKSgpO2Z1bmN0aW9uICR2KG4pe2lmKG51bGw9PT1uLmNvZGVMb2NhdGlvbkZvY3VzVHlwZSlyZXR1cm4gbnVsbDtsZXQgdD1bXTtpZihuLmNvZGVMb2NhdGlvbkZvY3VzVHlwZT09PXhzLkVYRUNVVElPTil7bGV0e2ZvY3VzSW5kZXg6aSxleGVjdXRpb25EYXRhOnJ9PW4uZXhlY3V0aW9ucztpZihudWxsPT09aXx8dm9pZCAwPT09cltpXSlyZXR1cm4gbnVsbDt0PXJbaV0uc3RhY2tfZnJhbWVfaWRzfWVsc2V7aWYobnVsbD09PW4uZ3JhcGhzLmZvY3VzZWRPcClyZXR1cm4gbnVsbDtsZXR7Z3JhcGhJZDppLG9wTmFtZTpyfT1uLmdyYXBocy5mb2N1c2VkT3A7aWYodm9pZCAwPT09bi5ncmFwaHMub3BzW2ldfHwhbi5ncmFwaHMub3BzW2ldLmhhcyhyKSlyZXR1cm4gbnVsbDt0PW4uZ3JhcGhzLm9wc1tpXS5nZXQocikuc3RhY2tfZnJhbWVfaWRzfWxldCBlPVtdO2ZvcihsZXQgaSBvZiB0KXtpZihudWxsPT1uLnN0YWNrRnJhbWVzW2ldKXJldHVybiBudWxsO2UucHVzaChuLnN0YWNrRnJhbWVzW2ldKX1yZXR1cm4gZX1mdW5jdGlvbiBUdyhuLHQpe3JldHVybiBuLmZpbmRJbmRleChlPT5lLmhvc3RfbmFtZT09PXQuaG9zdF9uYW1lJiZlLmZpbGVfcGF0aD09PXQuZmlsZV9wYXRoKX1mdW5jdGlvbiBuVShuLHQsZSl7aWYodD49ZSl0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGJlZ2luIHRvIGJlIGxlc3MgdGhhbiBlbmQsIGJ1dCBnb3QgYmVnaW49JHt0fSwgZW5kPSR7ZX1gKTtyZXR1cm4gbi5maW5kSW5kZXgoaT0+aS5iZWdpbj09PXQmJmkuZW5kPT09ZSl9ZnVuY3Rpb24gQ1Aobil7bGV0IHQ9bi5zb3VyY2VDb2RlLmZvY3VzTGluZVNwZWM7aWYoIW4uc3RpY2tUb0JvdHRvbW1vc3RGcmFtZUluRm9jdXNlZEZpbGUpcmV0dXJuIHQ7bGV0IGU9JHYobik7aWYobnVsbD09PWUpcmV0dXJuIHQ7bGV0IGk9ZnVuY3Rpb24obix0KXtpZihudWxsPT09dClyZXR1cm4gbnVsbDtmb3IobGV0IGU9bi5sZW5ndGgtMTtlPj0wOy0tZSl7bGV0IGk9bltlXSx7aG9zdF9uYW1lOnIsZmlsZV9wYXRoOm99PWk7aWYocj09PXQuaG9zdF9uYW1lJiZvPT09dC5maWxlX3BhdGgpcmV0dXJuIGl9cmV0dXJuIG51bGx9KGUsdCk7cmV0dXJuIG51bGw9PT1pP3Q6aX12YXIgRDNlPXZyKHtydW5zOnt9LHJ1bnNMb2FkZWQ6e3N0YXRlOk9lLk5PVF9MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOm51bGx9LGFjdGl2ZVJ1bklkOm51bGwsbGFzdERhdGFQb2xsT25zZXRUaW1lTXM6LTEsbGFzdE5vbkVtcHR5UG9sbERhdGFUaW1lTXM6MSxhbGVydHM6e2FsZXJ0c0xvYWRlZDp7c3RhdGU6T2UuTk9UX0xPQURFRCxsYXN0TG9hZGVkVGltZUluTXM6bnVsbH0sbnVtQWxlcnRzOjAsYWxlcnRzQnJlYWtkb3duOnt9LGFsZXJ0czp7fSxleGVjdXRpb25JbmRpY2VzOnt9LGdyYXBoRXhlY3V0aW9uSW5kaWNlczp7fSxmb2N1c1R5cGU6bnVsbH0sZXhlY3V0aW9uczp7bnVtRXhlY3V0aW9uc0xvYWRlZDp7c3RhdGU6T2UuTk9UX0xPQURFRCxsYXN0TG9hZGVkVGltZUluTXM6bnVsbH0sZXhlY3V0aW9uRGlnZXN0c0xvYWRlZDp7bG9hZGluZ1JhbmdlczpbXSxudW1FeGVjdXRpb25zOjAscGFnZUxvYWRlZFNpemVzOnt9fSxkaXNwbGF5Q291bnQ6NTAscGFnZVNpemU6MTAwLHNjcm9sbEJlZ2luSW5kZXg6MCxmb2N1c0luZGV4Om51bGwsZXhlY3V0aW9uRGlnZXN0czp7fSxleGVjdXRpb25EYXRhOnt9fSxncmFwaEV4ZWN1dGlvbnM6e251bUV4ZWN1dGlvbnNMb2FkZWQ6e3N0YXRlOk9lLk5PVF9MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOm51bGx9LGV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQ6e2xvYWRpbmdSYW5nZXM6W10sbnVtRXhlY3V0aW9uczowLHBhZ2VMb2FkZWRTaXplczp7fX0sZGlzcGxheUNvdW50OjEwMCxwYWdlU2l6ZToyMDAsc2Nyb2xsQmVnaW5JbmRleDowLGZvY3VzSW5kZXg6bnVsbCxncmFwaEV4ZWN1dGlvbkRpZ2VzdHM6e30sZ3JhcGhFeGVjdXRpb25EYXRhTG9hZGluZ1BhZ2VzOltdLGdyYXBoRXhlY3V0aW9uRGF0YVBhZ2VMb2FkZWRTaXplczp7fSxncmFwaEV4ZWN1dGlvbkRhdGE6e319LGdyYXBoczp7b3BzOnt9LGxvYWRpbmdPcHM6e30sZm9jdXNlZE9wOm51bGx9LHN0YWNrRnJhbWVzOnt9LGNvZGVMb2NhdGlvbkZvY3VzVHlwZTpudWxsLHN0aWNrVG9Cb3R0b21tb3N0RnJhbWVJbkZvY3VzZWRGaWxlOiExLHNvdXJjZUNvZGU6e3NvdXJjZUZpbGVMaXN0TG9hZGVkOntzdGF0ZTpPZS5OT1RfTE9BREVELGxhc3RMb2FkZWRUaW1lSW5NczpudWxsfSxzb3VyY2VGaWxlTGlzdDpbXSxmaWxlQ29udGVudHM6W10sZm9jdXNMaW5lU3BlYzpudWxsfX0sU2UoaVAsbj0+KHsuLi5uLHJ1bnNMb2FkZWQ6ey4uLm4ucnVuc0xvYWRlZCxzdGF0ZTpPZS5MT0FESU5HfX0pKSxTZShycmUsbj0+KHsuLi5uLHJ1bnNMb2FkZWQ6ey4uLm4ucnVuc0xvYWRlZCxzdGF0ZTpPZS5GQUlMRUR9fSkpLFNlKHJQLChuLHtydW5zOnR9KT0+e2xldCBlPU9iamVjdC5rZXlzKHQpLGk9ZS5sZW5ndGg+MCYmbnVsbD09PW4uYWN0aXZlUnVuSWQ7cmV0dXJuey4uLm4sbGFzdE5vbkVtcHR5UG9sbERhdGFUaW1lTXM6aT9EYXRlLm5vdygpOm4ubGFzdE5vbkVtcHR5UG9sbERhdGFUaW1lTXMscnVuczp0LHJ1bnNMb2FkZWQ6e3N0YXRlOk9lLkxPQURFRCxsYXN0TG9hZGVkVGltZUluTXM6RGF0ZS5ub3coKX0sYWN0aXZlUnVuSWQ6ZS5sZW5ndGg+MD9lWzBdOm51bGx9fSksU2UoTXcsbj0+KHsuLi5uLGxhc3REYXRhUG9sbE9uc2V0VGltZU1zOkRhdGUubm93KCl9KSksU2Uod3csbj0+bnVsbD09PW4uYWN0aXZlUnVuSWQ/bjp7Li4ubixhbGVydHM6ey4uLm4uYWxlcnRzLGFsZXJ0c0xvYWRlZDp7Li4ubi5hbGVydHMuYWxlcnRzTG9hZGVkLHN0YXRlOk9lLkxPQURJTkd9fX0pLFNlKG9QLChuLHtudW1BbGVydHM6dCxhbGVydHNCcmVha2Rvd246ZX0pPT57aWYobnVsbD09PW4uYWN0aXZlUnVuSWQpcmV0dXJuIG47bGV0IHI9dD5uLmFsZXJ0cy5udW1BbGVydHM7cmV0dXJuey4uLm4sbGFzdE5vbkVtcHR5UG9sbERhdGFUaW1lTXM6cj9EYXRlLm5vdygpOm4ubGFzdE5vbkVtcHR5UG9sbERhdGFUaW1lTXMsYWxlcnRzOnsuLi5uLmFsZXJ0cyxhbGVydHNMb2FkZWQ6ey4uLm4uYWxlcnRzLmFsZXJ0c0xvYWRlZCxzdGF0ZTpPZS5MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOkRhdGUubm93KCl9LG51bUFsZXJ0czp0LGFsZXJ0c0JyZWFrZG93bjplfX19KSxTZShzUCwobix7bnVtQWxlcnRzOnQsYWxlcnRzQnJlYWtkb3duOmUsYWxlcnRUeXBlOmksYmVnaW46cixhbGVydHM6b30pPT57aWYobnVsbD09PW4uYWN0aXZlUnVuSWQpcmV0dXJuIG47bGV0IGE9e30sbD1uLmFsZXJ0cy5leGVjdXRpb25JbmRpY2VzW2ldP24uYWxlcnRzLmV4ZWN1dGlvbkluZGljZXNbaV0uc2xpY2UoKTpbXSxjPW4uYWxlcnRzLmdyYXBoRXhlY3V0aW9uSW5kaWNlc1tpXT9uLmFsZXJ0cy5ncmFwaEV4ZWN1dGlvbkluZGljZXNbaV0uc2xpY2UoKTpbXTtmb3IobGV0IHA9MDtwPG8ubGVuZ3RoOysrcCl7bGV0IGg9citwLGY9b1twXTtpZihhW2hdPWYsZi5hbGVydF90eXBlPT09bWQuSU5GX05BTl9BTEVSVCl7bGV0IG09ZjtsW2hdPW0uZXhlY3V0aW9uX2luZGV4LG51bGwhPT1tLmdyYXBoX2V4ZWN1dGlvbl90cmFjZV9pbmRleCYmKGNbaF09bS5ncmFwaF9leGVjdXRpb25fdHJhY2VfaW5kZXgpfX12b2lkIDAhPT1uLmFsZXJ0cy5hbGVydHNbaV0mJk9iamVjdC5hc3NpZ24oYSxuLmFsZXJ0cy5hbGVydHNbaV0pO2xldCB1PW4uZXhlY3V0aW9ucy5zY3JvbGxCZWdpbkluZGV4LGQ9bi5ncmFwaEV4ZWN1dGlvbnMuZm9jdXNJbmRleDtpZihpPT09bWQuSU5GX05BTl9BTEVSVCYmMD09PXIpe2xldCBwPW9bMF07dT1NYXRoLm1heCgwLHAuZXhlY3V0aW9uX2luZGV4LU1hdGguZmxvb3Iobi5leGVjdXRpb25zLmRpc3BsYXlDb3VudC8yKSksbnVsbCE9PXAuZ3JhcGhfZXhlY3V0aW9uX3RyYWNlX2luZGV4JiYoZD1wLmdyYXBoX2V4ZWN1dGlvbl90cmFjZV9pbmRleCl9cmV0dXJuey4uLm4sZXhlY3V0aW9uczp7Li4ubi5leGVjdXRpb25zLHNjcm9sbEJlZ2luSW5kZXg6dX0sZ3JhcGhFeGVjdXRpb25zOnsuLi5uLmdyYXBoRXhlY3V0aW9ucyxmb2N1c0luZGV4OmR9LGFsZXJ0czp7Li4ubi5hbGVydHMsYWxlcnRzTG9hZGVkOnsuLi5uLmFsZXJ0cy5hbGVydHNMb2FkZWQsc3RhdGU6T2UuTE9BREVELGxhc3RMb2FkZWRUaW1lSW5NczpEYXRlLm5vdygpfSxudW1BbGVydHM6dCxhbGVydHNCcmVha2Rvd246ZSxhbGVydHM6ey4uLm4uYWxlcnRzLmFsZXJ0cyxbaV06YX0sZXhlY3V0aW9uSW5kaWNlczp7Li4ubi5hbGVydHMuZXhlY3V0aW9uSW5kaWNlcyxbaV06bH0sZ3JhcGhFeGVjdXRpb25JbmRpY2VzOnsuLi5uLmFsZXJ0cy5ncmFwaEV4ZWN1dGlvbkluZGljZXMsW2ldOmN9fX19KSxTZShHdiwobix7YWxlcnRUeXBlOnR9KT0+e2xldCBlPXsuLi5uLGFsZXJ0czp7Li4ubi5hbGVydHMsZm9jdXNUeXBlOm4uYWxlcnRzLmZvY3VzVHlwZT09PXQ/bnVsbDp0fX0saT1lLmFsZXJ0cy5mb2N1c1R5cGU7aWYobnVsbCE9PWkpe2xldCByPWUuYWxlcnRzLmV4ZWN1dGlvbkluZGljZXNbaV18fFtdO3ZvaWQgMCE9PXJbMF0mJihlLmV4ZWN1dGlvbnM9ey4uLmUuZXhlY3V0aW9ucyxzY3JvbGxCZWdpbkluZGV4Ok1hdGgubWF4KDAsTnVtYmVyKHJbMF0pLU1hdGguZmxvb3IoZS5leGVjdXRpb25zLmRpc3BsYXlDb3VudC8yKSl9KX1yZXR1cm4gZX0pLFNlKGFQLG49Pm51bGw9PT1uLmFjdGl2ZVJ1bklkP246ey4uLm4sZXhlY3V0aW9uczp7Li4ubi5leGVjdXRpb25zLG51bUV4ZWN1dGlvbnNMb2FkZWQ6ey4uLm4uZXhlY3V0aW9ucy5udW1FeGVjdXRpb25zTG9hZGVkLHN0YXRlOk9lLkxPQURJTkd9fX0pLFNlKGxQLChuLHtudW1FeGVjdXRpb25zOnR9KT0+e2lmKG51bGw9PT1uLmFjdGl2ZVJ1bklkKXJldHVybiBuO2xldCBpPXQ+bi5leGVjdXRpb25zLmV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQubnVtRXhlY3V0aW9ucyxyPXsuLi5uLGxhc3ROb25FbXB0eVBvbGxEYXRhVGltZU1zOmk/RGF0ZS5ub3coKTpuLmxhc3ROb25FbXB0eVBvbGxEYXRhVGltZU1zLGV4ZWN1dGlvbnM6ey4uLm4uZXhlY3V0aW9ucyxudW1FeGVjdXRpb25zTG9hZGVkOnsuLi5uLmV4ZWN1dGlvbnMubnVtRXhlY3V0aW9uc0xvYWRlZCxzdGF0ZTpPZS5MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOkRhdGUubm93KCl9LGV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQ6ey4uLm4uZXhlY3V0aW9ucy5leGVjdXRpb25EaWdlc3RzTG9hZGVkLG51bUV4ZWN1dGlvbnM6dH19fTtyZXR1cm4gdD4wJiZudWxsPT09bi5leGVjdXRpb25zLmZvY3VzSW5kZXgmJihyLmV4ZWN1dGlvbnMuZm9jdXNJbmRleD0wKSxyfSksU2UoY1AsKG4sdCk9PntpZihudWxsPT09bi5hY3RpdmVSdW5JZClyZXR1cm4gbjtsZXQgaT1bLi4ubi5leGVjdXRpb25zLmV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQubG9hZGluZ1Jhbmdlc107cmV0dXJuLTE9PT1uVShpLHQuYmVnaW4sdC5lbmQpJiZpLnB1c2goe2JlZ2luOnQuYmVnaW4sZW5kOnQuZW5kfSksey4uLm4sZXhlY3V0aW9uczp7Li4ubi5leGVjdXRpb25zLGV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQ6ey4uLm4uZXhlY3V0aW9ucy5leGVjdXRpb25EaWdlc3RzTG9hZGVkLGxvYWRpbmdSYW5nZXM6aX19fX0pLFNlKHVQLChuLHQpPT57aWYobnVsbD09PW4uYWN0aXZlUnVuSWQpcmV0dXJuIG47bGV0IGk9Wy4uLm4uZXhlY3V0aW9ucy5leGVjdXRpb25EaWdlc3RzTG9hZGVkLmxvYWRpbmdSYW5nZXNdLHI9blUoaSx0LmJlZ2luLHQuZW5kKTstMSE9PXImJmkuc3BsaWNlKHIsMSk7bGV0IG89ey4uLm4sZXhlY3V0aW9uczp7Li4ubi5leGVjdXRpb25zLGV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQ6ey4uLm4uZXhlY3V0aW9ucy5leGVjdXRpb25EaWdlc3RzTG9hZGVkLG51bUV4ZWN1dGlvbnM6dC5udW1fZGlnZXN0cyxsb2FkaW5nUmFuZ2VzOml9LGV4ZWN1dGlvbkRpZ2VzdHM6ey4uLm4uZXhlY3V0aW9ucy5leGVjdXRpb25EaWdlc3RzfX19O2ZvcihsZXQgcz10LmJlZ2luO3M8dC5lbmQ7KytzKW8uZXhlY3V0aW9ucy5leGVjdXRpb25EaWdlc3RzW3NdPXQuZXhlY3V0aW9uX2RpZ2VzdHNbcy10LmJlZ2luXTtyZXR1cm4gdC5lbmQ+dC5iZWdpbiYmKG8uZXhlY3V0aW9ucy5leGVjdXRpb25EaWdlc3RzTG9hZGVkLnBhZ2VMb2FkZWRTaXplcz17Li4uby5leGVjdXRpb25zLmV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQucGFnZUxvYWRlZFNpemVzLFt0LmJlZ2luL24uZXhlY3V0aW9ucy5wYWdlU2l6ZV06dC5lbmQtdC5iZWdpbn0pLG99KSxTZShXdixuPT57aWYobnVsbD09PW4uYWN0aXZlUnVuSWQpcmV0dXJuIG47bGV0IGU9bi5leGVjdXRpb25zLnNjcm9sbEJlZ2luSW5kZXg7cmV0dXJuIGU+MCYmZS0tLHsuLi5uLGV4ZWN1dGlvbnM6ey4uLm4uZXhlY3V0aW9ucyxzY3JvbGxCZWdpbkluZGV4OmV9fX0pLFNlKHF2LG49PntpZihudWxsPT09bi5hY3RpdmVSdW5JZClyZXR1cm4gbjtsZXQgZT1uLmV4ZWN1dGlvbnMuc2Nyb2xsQmVnaW5JbmRleDtyZXR1cm4gZStuLmV4ZWN1dGlvbnMuZGlzcGxheUNvdW50KzE8PW4uZXhlY3V0aW9ucy5leGVjdXRpb25EaWdlc3RzTG9hZGVkLm51bUV4ZWN1dGlvbnMmJmUrKyx7Li4ubixleGVjdXRpb25zOnsuLi5uLmV4ZWN1dGlvbnMsc2Nyb2xsQmVnaW5JbmRleDplfX19KSxTZShZdiwobix0KT0+e2lmKHQuaW5kZXg8MHx8IU51bWJlci5pc0ludGVnZXIodC5pbmRleCkpdGhyb3cgbmV3IEVycm9yKGBBdHRlbXB0IHRvIHNjcm9sbCB0byBuZWdhdGl2ZSBvciBub24taW50ZWdlciBleGVjdXRpb24gaW5kZXggKCR7dC5pbmRleH0pYCk7bGV0e2Rpc3BsYXlDb3VudDplfT1uLmV4ZWN1dGlvbnMse251bUV4ZWN1dGlvbnM6aX09bi5leGVjdXRpb25zLmV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQ7aWYodC5pbmRleD5NYXRoLm1heCgwLGktZSkpdGhyb3cgbmV3IEVycm9yKGBBdHRlbXB0IHRvIHNjcm9sbCB0byBleGVjdXRpb24gaW5kZXggKCR7dC5pbmRleH0pLCB3aGljaCBleGNlZWRzIG1heGltdW0gYWxsb3dlZCBpbmRleCAobnVtRXhlY3V0aW9ucz0ke2l9OyBkaXNwbGF5Q291bnQ9JHtlfSlgKTtyZXR1cm57Li4ubixleGVjdXRpb25zOnsuLi5uLmV4ZWN1dGlvbnMsc2Nyb2xsQmVnaW5JbmRleDp0LmluZGV4fX19KSxTZShYdiwobix0KT0+e2xldCBlPXsuLi5uLGV4ZWN1dGlvbnM6ey4uLm4uZXhlY3V0aW9ucyxmb2N1c0luZGV4Om4uZXhlY3V0aW9ucy5zY3JvbGxCZWdpbkluZGV4K3QuZGlzcGxheUluZGV4fSxjb2RlTG9jYXRpb25Gb2N1c1R5cGU6eHMuRVhFQ1VUSU9OLHNvdXJjZUNvZGU6ey4uLm4uc291cmNlQ29kZX19O3JldHVybiBlLnNvdXJjZUNvZGUuZm9jdXNMaW5lU3BlYz1DUChlKSxlfSksU2UoZFAsKG4sdCk9PntpZihudWxsPT09bi5hY3RpdmVSdW5JZClyZXR1cm4gbjtsZXQgaT17Li4ubixleGVjdXRpb25zOnsuLi5uLmV4ZWN1dGlvbnMsZXhlY3V0aW9uRGF0YTp7Li4ubi5leGVjdXRpb25zLmV4ZWN1dGlvbkRhdGF9fX07Zm9yKGxldCByPXQuYmVnaW47cjx0LmVuZDsrK3IpaS5leGVjdXRpb25zLmV4ZWN1dGlvbkRhdGFbcl09dC5leGVjdXRpb25zW3ItdC5iZWdpbl07cmV0dXJuIGl9KSxTZShwUCxuPT5udWxsPT09bi5hY3RpdmVSdW5JZD9uOnsuLi5uLGdyYXBoRXhlY3V0aW9uczp7Li4ubi5ncmFwaEV4ZWN1dGlvbnMsbnVtRXhlY3V0aW9uc0xvYWRlZDp7Li4ubi5ncmFwaEV4ZWN1dGlvbnMubnVtRXhlY3V0aW9uc0xvYWRlZCxzdGF0ZTpPZS5MT0FESU5HfX19KSxTZShoUCwobix7bnVtR3JhcGhFeGVjdXRpb25zOnR9KT0+e2lmKG51bGw9PT1uLmFjdGl2ZVJ1bklkKXJldHVybiBuO2xldCBlPXQ+bi5ncmFwaEV4ZWN1dGlvbnMuZXhlY3V0aW9uRGlnZXN0c0xvYWRlZC5udW1FeGVjdXRpb25zLGk9ey4uLm4sbGFzdE5vbkVtcHR5UG9sbERhdGFUaW1lTXM6ZT9EYXRlLm5vdygpOm4ubGFzdE5vbkVtcHR5UG9sbERhdGFUaW1lTXMsZ3JhcGhFeGVjdXRpb25zOnsuLi5uLmdyYXBoRXhlY3V0aW9ucyxudW1FeGVjdXRpb25zTG9hZGVkOnsuLi5uLmdyYXBoRXhlY3V0aW9ucy5udW1FeGVjdXRpb25zTG9hZGVkLHN0YXRlOk9lLkxPQURFRCxsYXN0TG9hZGVkVGltZUluTXM6RGF0ZS5ub3coKX0sZXhlY3V0aW9uRGlnZXN0c0xvYWRlZDp7Li4ubi5ncmFwaEV4ZWN1dGlvbnMuZXhlY3V0aW9uRGlnZXN0c0xvYWRlZCxudW1FeGVjdXRpb25zOnR9fX07cmV0dXJuIHQ+MCYmbnVsbD09PW4uZ3JhcGhFeGVjdXRpb25zLmZvY3VzSW5kZXgmJihpLmdyYXBoRXhlY3V0aW9ucy5mb2N1c0luZGV4PTApLGl9KSxTZShmUCwobix7cGFnZUluZGV4OnR9KT0+e2lmKG51bGw9PT1uLmFjdGl2ZVJ1bklkKXJldHVybiBuO2xldCBlPW4uZ3JhcGhFeGVjdXRpb25zLmdyYXBoRXhlY3V0aW9uRGF0YUxvYWRpbmdQYWdlcy5zbGljZSgpO3JldHVybi0xPT09ZS5pbmRleE9mKHQpJiZlLnB1c2godCksey4uLm4sZ3JhcGhFeGVjdXRpb25zOnsuLi5uLmdyYXBoRXhlY3V0aW9ucyxncmFwaEV4ZWN1dGlvbkRhdGFMb2FkaW5nUGFnZXM6ZX19fSksU2UobVAsKG4sdCk9PntpZihudWxsPT09bi5hY3RpdmVSdW5JZClyZXR1cm4gbjtsZXR7cGFnZVNpemU6ZX09bi5ncmFwaEV4ZWN1dGlvbnMsaT1uLmdyYXBoRXhlY3V0aW9ucy5ncmFwaEV4ZWN1dGlvbkRhdGFMb2FkaW5nUGFnZXMuc2xpY2UoKSxyPXsuLi5uLmdyYXBoRXhlY3V0aW9ucy5ncmFwaEV4ZWN1dGlvbkRhdGFQYWdlTG9hZGVkU2l6ZXN9LG89ey4uLm4uZ3JhcGhFeGVjdXRpb25zLmdyYXBoRXhlY3V0aW9uRGF0YX07Zm9yKGxldCBzPXQuYmVnaW47czx0LmVuZDsrK3Mpe2xldCBhPU1hdGguZmxvb3Iocy9lKTstMSE9PWkuaW5kZXhPZihhKSYmaS5zcGxpY2UoaS5pbmRleE9mKGEpLDEpLHZvaWQgMD09PXJbYV0mJihyW2FdPTApLHZvaWQgMD09PW9bc10mJnJbYV0rKyxvW3NdPXQuZ3JhcGhfZXhlY3V0aW9uc1tzLXQuYmVnaW5dfXJldHVybnsuLi5uLGdyYXBoRXhlY3V0aW9uczp7Li4ubi5ncmFwaEV4ZWN1dGlvbnMsZ3JhcGhFeGVjdXRpb25EYXRhTG9hZGluZ1BhZ2VzOmksZ3JhcGhFeGVjdXRpb25EYXRhUGFnZUxvYWRlZFNpemVzOnIsZ3JhcGhFeGVjdXRpb25EYXRhOm99fX0pLFNlKFF2LChuLHQpPT57aWYodC5pbmRleDwwfHwhTnVtYmVyLmlzSW50ZWdlcih0LmluZGV4KSl0aHJvdyBuZXcgRXJyb3IoYEF0dGVtcHQgdG8gc2Nyb2xsIHRvIG5lZ2F0aXZlIG9yIG5vbi1pbnRlZ2VyIGdyYXBoLWV4ZWN1dGlvbiBpbmRleCAoJHt0LmluZGV4fSlgKTtyZXR1cm57Li4ubixncmFwaEV4ZWN1dGlvbnM6ey4uLm4uZ3JhcGhFeGVjdXRpb25zLHNjcm9sbEJlZ2luSW5kZXg6dC5pbmRleH19fSksU2UoS3YsKG4sdCk9PmFyZShuLHQuZ3JhcGhfaWQsdC5vcF9uYW1lLHQuaW5kZXgpKSxTZShadiwobix0KT0+YXJlKG4sdC5ncmFwaF9pZCx0Lm9wX25hbWUpKSxTZShnUCwobix0KT0+e2xldHtncmFwaF9pZDplLG9wX25hbWU6aX09dCxyPXsuLi5uLGdyYXBoczp7Li4ubi5ncmFwaHMsbG9hZGluZ09wczp7Li4ubi5ncmFwaHMubG9hZGluZ09wc319fTtyZXR1cm4gdm9pZCAwPT09ci5ncmFwaHMubG9hZGluZ09wc1tlXSYmKHIuZ3JhcGhzLmxvYWRpbmdPcHNbZV09bmV3IE1hcCksci5ncmFwaHMubG9hZGluZ09wc1tlXS5oYXMoaSl8fHIuZ3JhcGhzLmxvYWRpbmdPcHNbZV0uc2V0KGksT2UuTE9BRElORykscn0pLFNlKF9QLChuLHQpPT57bGV0e2dyYXBoT3BJbmZvUmVzcG9uc2U6ZX09dCx7Z3JhcGhfaWRzOml9PWUscj1pW2kubGVuZ3RoLTFdLG89ey4uLm4sZ3JhcGhzOnsuLi5uLmdyYXBocyxvcHM6ey4uLm4uZ3JhcGhzLm9wcyxbcl06bmV3IE1hcChuLmdyYXBocy5vcHNbcl0pfSxsb2FkaW5nT3BzOnsuLi5uLmdyYXBocy5sb2FkaW5nT3BzLFtyXTpuZXcgTWFwKG4uZ3JhcGhzLmxvYWRpbmdPcHNbcl0pfX19O2ZvcihsZXQgcyBvZiBlLmlucHV0cykhcy5kYXRhfHxvLmdyYXBocy5vcHNbcl0uc2V0KHMub3BfbmFtZSxzLmRhdGEpO2ZvcihsZXQgcz0wO3M8ZS5jb25zdW1lcnMubGVuZ3RoOysrcylmb3IobGV0IGEgb2YgZS5jb25zdW1lcnNbc10pIWEuZGF0YXx8by5ncmFwaHMub3BzW3JdLnNldChhLm9wX25hbWUsYS5kYXRhKTtyZXR1cm4gby5ncmFwaHMub3BzW3JdLnNldChlLm9wX25hbWUsey4uLmUsaW5wdXRzOmUuaW5wdXRzLm1hcChzPT4oe29wX25hbWU6cy5vcF9uYW1lLG91dHB1dF9zbG90OnMub3V0cHV0X3Nsb3R9KSksY29uc3VtZXJzOmUuY29uc3VtZXJzLm1hcChzPT5zLm1hcChhPT4oe29wX25hbWU6YS5vcF9uYW1lLGlucHV0X3Nsb3Q6YS5pbnB1dF9zbG90fSkpKX0pLG8uZ3JhcGhzLmxvYWRpbmdPcHNbcl0uc2V0KGUub3BfbmFtZSxPZS5MT0FERUQpLG99KSxTZSh2UCxuPT4oey4uLm4sc291cmNlQ29kZTp7Li4ubi5zb3VyY2VDb2RlLHNvdXJjZUZpbGVMaXN0TG9hZGVkOnsuLi5uLnNvdXJjZUNvZGUuc291cmNlRmlsZUxpc3RMb2FkZWQsc3RhdGU6T2UuTE9BRElOR319fSkpLFNlKHlQLChuLHQpPT57bGV0IGU9ey4uLm4sc291cmNlQ29kZTp7Li4ubi5zb3VyY2VDb2RlLHNvdXJjZUZpbGVMaXN0TG9hZGVkOnsuLi5uLnNvdXJjZUNvZGUuc291cmNlRmlsZUxpc3RMb2FkZWQsc3RhdGU6T2UuTE9BREVELGxhc3RMb2FkZWRUaW1lSW5NczpEYXRlLm5vdygpfSxzb3VyY2VGaWxlTGlzdDp0LnNvdXJjZUZpbGVzLGZpbGVDb250ZW50czpuLnNvdXJjZUNvZGUuZmlsZUNvbnRlbnRzLnNsaWNlKCl9fSxpPXQuc291cmNlRmlsZXMubGVuZ3RoLHtmaWxlQ29udGVudHM6cn09ZS5zb3VyY2VDb2RlO2ZvcihsZXQgbz0wO288aTsrK28pcltvXT1uLnNvdXJjZUNvZGUuZmlsZUNvbnRlbnRzW29dPz97bG9hZFN0YXRlOk9lLk5PVF9MT0FERUQsbGluZXM6bnVsbH07cmV0dXJuIGV9KSxTZShKdiwobix0KT0+e2xldCBlPSR2KG4pLGk9ey4uLm4sc291cmNlQ29kZTp7Li4ubi5zb3VyY2VDb2RlLGZvY3VzTGluZVNwZWM6dC5zdGFja0ZyYW1lfX07cmV0dXJuIG51bGwhPT1lJiYoaS5zdGlja1RvQm90dG9tbW9zdEZyYW1lSW5Gb2N1c2VkRmlsZT1mdW5jdGlvbihuLHQpe2xldCBlPS0xLGk9LTE7aWYobi5mb3JFYWNoKCh7ZmlsZV9wYXRoOnIsbGluZW5vOm99LHMpPT57cj09PXQuZmlsZV9wYXRoJiYoaT1zLG89PT10LmxpbmVubyYmKGU9cykpfSksLTE9PT1lKXRocm93IG5ldyBFcnJvcihgU3RhY2sgZnJhbWUgJHtKU09OLnN0cmluZ2lmeSh0KX0gaXMgbm90IGZvdW5kLmApO3JldHVybiBlPT09aX0oZSx0LnN0YWNrRnJhbWUpKSxpfSksU2UoYlAsKG4sdCk9PntsZXQgZT17Li4ubixzb3VyY2VDb2RlOnsuLi5uLnNvdXJjZUNvZGUsZmlsZUNvbnRlbnRzOm4uc291cmNlQ29kZS5maWxlQ29udGVudHMuc2xpY2UoKX19LGk9VHcoZS5zb3VyY2VDb2RlLnNvdXJjZUZpbGVMaXN0LHQpO2lmKCEoaT49MCkpdGhyb3cgbmV3IEVycm9yKGBDYW5ub3QgZmluZCB0aGUgZm9sbG93aW5nIGZpbGUgaW4gZmlsZSBsaXN0OiBob3N0X25hbWU9IiR7dC5ob3N0X25hbWV9IiwgZmlsZV9wYXRoPSIke3QuZmlsZV9wYXRofSJgKTtyZXR1cm4gZS5zb3VyY2VDb2RlLmZpbGVDb250ZW50cy5zcGxpY2UoaSwxLHsuLi5lLnNvdXJjZUNvZGUuZmlsZUNvbnRlbnRzW2ldLGxvYWRTdGF0ZTpPZS5MT0FESU5HfSksZX0pLFNlKHhQLChuLHQpPT57bGV0IGU9ey4uLm4sc291cmNlQ29kZTp7Li4ubi5zb3VyY2VDb2RlLGZpbGVDb250ZW50czpuLnNvdXJjZUNvZGUuZmlsZUNvbnRlbnRzLnNsaWNlKCl9fSxpPVR3KGUuc291cmNlQ29kZS5zb3VyY2VGaWxlTGlzdCx0KTtpZighKGk+PTApKXRocm93IG5ldyBFcnJvcihgQ2Fubm90IGZpbmQgdGhlIGZvbGxvd2luZyBmaWxlIGluIGZpbGUgbGlzdDogaG9zdF9uYW1lPSIke3QuaG9zdF9uYW1lfSIsIGZpbGVfcGF0aD0iJHt0LmZpbGVfcGF0aH0iYCk7cmV0dXJuIGUuc291cmNlQ29kZS5maWxlQ29udGVudHMuc3BsaWNlKGksMSx7bG9hZFN0YXRlOk9lLkxPQURFRCxsaW5lczp0LmxpbmVzfSksZX0pLFNlKFN3LChuLHQpPT57aWYobnVsbD09PW4uYWN0aXZlUnVuSWQpcmV0dXJuIG47bGV0IGk9ey4uLm4sc3RhY2tGcmFtZXM6ey4uLm4uc3RhY2tGcmFtZXMsLi4udC5zdGFja0ZyYW1lc30sc291cmNlQ29kZTp7Li4ubi5zb3VyY2VDb2RlfX07cmV0dXJuIGkuc291cmNlQ29kZS5mb2N1c0xpbmVTcGVjPUNQKGkpLGl9KSk7ZnVuY3Rpb24gYXJlKG4sdCxlLGkpe2xldCByPXsuLi5uLGdyYXBoczp7Li4ubi5ncmFwaHMsZm9jdXNlZE9wOntncmFwaElkOnQsb3BOYW1lOmV9fSxjb2RlTG9jYXRpb25Gb2N1c1R5cGU6eHMuR1JBUEhfT1BfQ1JFQVRJT04sc291cmNlQ29kZTp7Li4ubi5zb3VyY2VDb2RlfX07cmV0dXJuIHIuc291cmNlQ29kZS5mb2N1c0xpbmVTcGVjPUNQKHIpLHZvaWQgMCE9PWkmJihyLmdyYXBoRXhlY3V0aW9ucz17Li4ubi5ncmFwaEV4ZWN1dGlvbnMsZm9jdXNJbmRleDppfSkscn1mdW5jdGlvbiBscmUobix0KXtyZXR1cm4gRDNlKG4sdCl9dmFyIGxzPU1yKEV3KSx1Zz1KKGxzLG49Pm4ucnVucyksaVU9SihscyxuPT5uLnJ1bnNMb2FkZWQpLFZzPUoobHMsbj0+bi5hY3RpdmVSdW5JZCksY3JlPUoobHMsbj0+bi5sYXN0RGF0YVBvbGxPbnNldFRpbWVNcy1uLmxhc3ROb25FbXB0eVBvbGxEYXRhVGltZU1zKSxleT1KKGxzLG49Pm4uYWxlcnRzKSxyVT1KKGV5LG49Pm4uYWxlcnRzTG9hZGVkKSx1cmU9SihleSxuPT5uLm51bUFsZXJ0cyksTVA9SihleSxuPT5uLmZvY3VzVHlwZSksZHJlPUooZXksbj0+bnVsbD09PW4uZm9jdXNUeXBlPzA6bi5hbGVydHNCcmVha2Rvd25bbi5mb2N1c1R5cGVdfHwwKSxwcmU9SihleSxuPT5udWxsPT09bi5mb2N1c1R5cGV8fHZvaWQgMD09PW4uYWxlcnRzW24uZm9jdXNUeXBlXT9udWxsOm4uYWxlcnRzW24uZm9jdXNUeXBlXSksaHJlPUooZXksbj0+bi5hbGVydHNCcmVha2Rvd24pLGRnPUoobHMsbj0+bi5leGVjdXRpb25zKSx3UD1KKGRnLG49Pm4ubnVtRXhlY3V0aW9uc0xvYWRlZCksRHc9SihkZyxuPT5uLmV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQpLGpoPUooZGcsbj0+bi5leGVjdXRpb25EaWdlc3RzTG9hZGVkLm51bUV4ZWN1dGlvbnMpLEF3PUooZGcsbj0+bi5zY3JvbGxCZWdpbkluZGV4KSx0eT1KKGRnLG49Pm4ucGFnZVNpemUpLG55PUooZGcsbj0+bi5kaXNwbGF5Q291bnQpLGZyZT1KKGRnLG49PntsZXQgdD1bXTtmb3IobGV0IGU9bi5zY3JvbGxCZWdpbkluZGV4O2U8bi5zY3JvbGxCZWdpbkluZGV4K24uZGlzcGxheUNvdW50OysrZSl0LnB1c2goZSBpbiBuLmV4ZWN1dGlvbkRpZ2VzdHM/bi5leGVjdXRpb25EaWdlc3RzW2VdOm51bGwpO3JldHVybiB0fSksR2g9SihscyxuPT5uLmdyYXBoRXhlY3V0aW9ucyksbXJlPUooR2gsbj0+bi5udW1FeGVjdXRpb25zTG9hZGVkKSxJdz1KKGxzLG49Pm4uZ3JhcGhFeGVjdXRpb25zLmV4ZWN1dGlvbkRpZ2VzdHNMb2FkZWQubnVtRXhlY3V0aW9ucyksZ3JlPUooR2gsbj0+bi5zY3JvbGxCZWdpbkluZGV4KSxfcmU9SihHaCxuPT5uLmRpc3BsYXlDb3VudCksdnJlPUooR2gsbj0+bi5wYWdlU2l6ZSkseXJlPUooR2gsbj0+bi5ncmFwaEV4ZWN1dGlvbkRhdGFMb2FkaW5nUGFnZXMpLGJyZT1KKEdoLG49Pm4uZ3JhcGhFeGVjdXRpb25EYXRhUGFnZUxvYWRlZFNpemVzKSxvVT1KKEdoLG49Pm4uZ3JhcGhFeGVjdXRpb25EYXRhKSxzVT1KKEdoLG49Pm4uZm9jdXNJbmRleCksYVU9SihscyxuPT5uLmdyYXBocyksbFU9SihhVSxuPT57bGV0e2ZvY3VzZWRPcDp0LG9wczplfT1uO3JldHVybiBudWxsPT09dHx8dm9pZCAwPT09ZVt0LmdyYXBoSWRdP251bGw6ZVt0LmdyYXBoSWRdLmdldCh0Lm9wTmFtZSl8fG51bGx9KSxjVT1KKGFVLG49PntsZXR7Zm9jdXNlZE9wOnQsb3BzOmV9PW47aWYobnVsbD09PXR8fHZvaWQgMD09PWVbdC5ncmFwaElkXXx8IWVbdC5ncmFwaElkXS5oYXModC5vcE5hbWUpKXJldHVybiBudWxsO3tsZXQgaT1lW3QuZ3JhcGhJZF0se2lucHV0czpyfT1pLmdldCh0Lm9wTmFtZSk7cmV0dXJuIHIubWFwKG89PntsZXQgcz17Li4ub307cmV0dXJuIGkuaGFzKG8ub3BfbmFtZSkmJihzLmRhdGE9aS5nZXQoby5vcF9uYW1lKSksc30pfX0pLHhyZT1KKHNVLG9VLGNVLChuLHQsZSk9PntpZihudWxsPT09bnx8bnVsbD09PWUpcmV0dXJuIG51bGw7bGV0IGk9ZS5tYXAoYT0+ITEpLHI9W107aWYoMD09PWUubGVuZ3RoKXJldHVybiByO2xldCBvPXRbbl0uZ3JhcGhfaWQscz1NYXRoLm1heCgwLG4tMjAwKTtmb3IobGV0IGE9bi0xO2E+PXM7LS1hKWlmKHZvaWQgMCE9PXRbYV0pZm9yKGxldCBsPTA7bDxlLmxlbmd0aCYmKGlbbF18fHRbYV0uZ3JhcGhfaWQhPT1vfHx0W2FdLm9wX25hbWUhPT1lW2xdLm9wX25hbWV8fHRbYV0ub3V0cHV0X3Nsb3QhPT1lW2xdLm91dHB1dF9zbG90fHwoci5wdXNoKGEpLGlbbF09ITAsci5sZW5ndGghPT1lLmxlbmd0aCkpOysrbCk7cmV0dXJuIHJ9KSxDcmU9SihhVSxuPT57bGV0e2ZvY3VzZWRPcDp0LG9wczplfT1uO2lmKG51bGw9PT10fHx2b2lkIDA9PT1lW3QuZ3JhcGhJZF18fCFlW3QuZ3JhcGhJZF0uaGFzKHQub3BOYW1lKSlyZXR1cm4gbnVsbDt7bGV0IGk9ZVt0LmdyYXBoSWRdLHtjb25zdW1lcnM6cn09aS5nZXQodC5vcE5hbWUpO3JldHVybiByLm1hcChvPT5vLm1hcChzPT57bGV0IGE9ey4uLnN9O3JldHVybiBpLmhhcyhzLm9wX25hbWUpJiYoYS5kYXRhPWkuZ2V0KHMub3BfbmFtZSkpLGF9KSl9fSksTXJlPUoobHMsbj0+e2xldCB0PW4uZXhlY3V0aW9ucy5zY3JvbGxCZWdpbkluZGV4LGU9bi5leGVjdXRpb25zLnNjcm9sbEJlZ2luSW5kZXgrbi5leGVjdXRpb25zLmRpc3BsYXlDb3VudCxpPW5ldyBBcnJheShlLXQpLmZpbGwobnVsbCkscj1uLmFsZXJ0cy5mb2N1c1R5cGU7aWYobnVsbD09PXIpcmV0dXJuIGk7bGV0IG89bi5hbGVydHMuZXhlY3V0aW9uSW5kaWNlc1tyXTtpZih2b2lkIDA9PT1vKXJldHVybiBpO2ZvcihsZXQgcz10O3M8ZTsrK3Mpby5pbmNsdWRlcyhzKSYmKGlbcy10XT1uLmFsZXJ0cy5mb2N1c1R5cGUpO3JldHVybiBpfSksU1A9SihscyxuPT5uLmV4ZWN1dGlvbnMpLHVVPUooU1Asbj0+bi5mb2N1c0luZGV4KSx3cmU9SihTUCxuPT57aWYobnVsbD09PW4uZm9jdXNJbmRleClyZXR1cm4gbnVsbDtsZXR7Zm9jdXNJbmRleDp0LHNjcm9sbEJlZ2luSW5kZXg6ZSxkaXNwbGF5Q291bnQ6aX09bjtyZXR1cm4gdDxlfHx0Pj1lK2k/bnVsbDp0LWV9KSxkVT1KKFNQLG49Pm4uZXhlY3V0aW9uRGF0YSksU3JlPUoobHMsbj0+bi5ncmFwaHMubG9hZGluZ09wcykscFU9SihscyxuPT5uLnN0YWNrRnJhbWVzKSxwZz1KKFNQLG49PntsZXR7Zm9jdXNJbmRleDp0LGV4ZWN1dGlvbkRhdGE6ZX09bjtyZXR1cm4gbnVsbD09PXR8fHZvaWQgMD09PWVbdF0/bnVsbDplW3RdfSksUHc9Sihscyx1VSxwZyxsVSwobix0LGUsaSk9PntsZXR7Y29kZUxvY2F0aW9uRm9jdXNUeXBlOnJ9PW47cmV0dXJuIG51bGw9PT1yP251bGw6cj09PXhzLkVYRUNVVElPTj9udWxsPT09dHx8bnVsbD09PWU/bnVsbDp7Y29kZUxvY2F0aW9uVHlwZTp4cy5FWEVDVVRJT04sb3BUeXBlOmUub3BfdHlwZSxleGVjdXRpb25JbmRleDp0fTpudWxsPT09aT9udWxsOntjb2RlTG9jYXRpb25UeXBlOnhzLkdSQVBIX09QX0NSRUFUSU9OLG9wVHlwZTppLm9wX3R5cGUsb3BOYW1lOmkub3BfbmFtZX19KSxFcmU9SihscywkdiksRVA9SihscyxuPT5uLnNvdXJjZUNvZGUpLFRyZT1KKEVQLG49Pm4uc291cmNlRmlsZUxpc3RMb2FkZWQpLGhVPShKKEVQLG49Pm4uc291cmNlRmlsZUxpc3QpLEooRVAsbj0+e2xldHtzb3VyY2VGaWxlTGlzdDp0LGZvY3VzTGluZVNwZWM6ZX09bjtyZXR1cm4gbnVsbD09PWU/LTE6VHcodCxlKX0pKSxUUD1KKEVQLGhVLChuLHQpPT4tMT09PXQ/bnVsbDpuLmZpbGVDb250ZW50c1t0XXx8bnVsbCksRFA9SihscyxuPT5uLnNvdXJjZUNvZGUuZm9jdXNMaW5lU3BlYyksRHJlPUoobHMsbj0+bi5zdGlja1RvQm90dG9tbW9zdEZyYW1lSW5Gb2N1c2VkRmlsZSksQXJlPWZ1bmN0aW9uKG4pe3JldHVybltuXX07ZnVuY3Rpb24gSTNlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2Iiw3KSxQKCJjbGljayIsZnVuY3Rpb24oKXtsZXQgbz1vZShlKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoKS5vblRvZ2dsZUZvY3VzVHlwZS5lbWl0KG8udHlwZSkpfSksXygxLCJkaXYiLDgpLEEoMiksdigpLF8oMywiZGl2Iiw5KSxBKDQpLHYoKSxPKDUsImRpdiIpLHYoKX1pZigyJm4pe2xldCBlPXQuJGltcGxpY2l0LGk9UygpO3koIm5nQ2xhc3MiLE9uKDQsQXJlLGUudHlwZT09PWkuZm9jdXNUeXBlPyJmb2N1cyI6IiIpKSxDKDIpLHl0KGUuZGlzcGxheU5hbWUpLEMoMiksWHAoIiAiLGUuZGlzcGxheVN5bWJvbCwiOiAiLGUuY291bnQsIiAiKX19dmFyIElyZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5udW1BbGVydHM9MCx0aGlzLmFsZXJ0c0JyZWFrZG93bj1bXSx0aGlzLmZvY3VzVHlwZT1udWxsLHRoaXMub25Ub2dnbGVGb2N1c1R5cGU9bmV3IEd9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImFsZXJ0cy1jb21wb25lbnQiXV0saW5wdXRzOntudW1BbGVydHM6Im51bUFsZXJ0cyIsYWxlcnRzQnJlYWtkb3duOiJhbGVydHNCcmVha2Rvd24iLGZvY3VzVHlwZToiZm9jdXNUeXBlIn0sb3V0cHV0czp7b25Ub2dnbGVGb2N1c1R5cGU6Im9uVG9nZ2xlRm9jdXNUeXBlIn0sZGVjbHM6MTAsdmFyczo1LGNvbnN0czpbWzEsImFsZXJ0cy1jb250YWluZXIiXSxbMSwiZGVidWdnaW5nLXRpdGxlIl0sWzEsIm51bS1hbGVydHMtY29udGFpbmVyIl0sWzEsIm51bS1hbGVydHMtbGFiZWwiXSxbMSwibnVtLWFsZXJ0cy12YWx1ZSIsMywibmdDbGFzcyJdLFsxLCJhbGVydHMtYnJlYWtkb3duLWNvbnRhaW5lciJdLFsiY2xhc3MiLCJhbGVydHMtYnJlYWtkb3duLXR5cGUiLDMsIm5nQ2xhc3MiLCJjbGljayIsNCwibmdGb3IiLCJuZ0Zvck9mIl0sWzEsImFsZXJ0cy1icmVha2Rvd24tdHlwZSIsMywibmdDbGFzcyIsImNsaWNrIl0sWzEsImFsZXJ0LXR5cGUtbmFtZSJdLFsxLCJhbGVydC10eXBlLWNvdW50Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJkaXYiLDApKDEsImRpdiIsMSksQSgyLCJEZWJ1Z2dpbmciKSx2KCksXygzLCJkaXYiLDIpKDQsImRpdiIsMyksQSg1LCJBbGVydHMiKSx2KCksXyg2LCJkaXYiLDQpLEEoNyksdigpKCksXyg4LCJkaXYiLDUpLEUoOSxJM2UsNiw2LCJkaXYiLDYpLHYoKSgpKSwyJmUmJihDKDYpLHkoIm5nQ2xhc3MiLE9uKDMsQXJlLGkubnVtQWxlcnRzPjA/Im5vbi16ZXJvIjoiIikpLEMoMSksamUoIiAiLGkubnVtQWxlcnRzLCIgIiksQygyKSx5KCJuZ0Zvck9mIixpLmFsZXJ0c0JyZWFrZG93bikpfSxkZXBlbmRlbmNpZXM6W0ZuLGRuXSxzdHlsZXM6WyIuYWxlcnRzLWJyZWFrZG93bi1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZm9udC1zaXplOiAxM3B4O1xuICBwYWRkaW5nOiAxMHB4IDEwcHggMTBweDtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xufVxuXG4uYWxlcnRzLWJyZWFrZG93bi10eXBlW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIGJvcmRlci1yYWRpdXM6IDAgMTBweCAxMHB4IDA7XG4gIGN1cnNvcjogcG9pbnRlcjtcbiAgZGlzcGxheTogZmxleDtcbiAgcGFkZGluZzogNnB4IDAgNnB4IDUwcHg7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG59XG5cbi5hbGVydHMtYnJlYWtkb3duLXR5cGUuZm9jdXNbX25nY29udGVudC0lQ09NUCVdIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZWVlMDtcbn1cblxuLmFsZXJ0cy1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZm9udC1mYW1pbHk6ICdSb2JvdG8nLCBBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmO1xufVxuXG4uYWxlcnQtdHlwZS1jb3VudFtfbmdjb250ZW50LSVDT01QJV0ge1xuICBcbiAgYmFja2dyb3VuZC1jb2xvcjogI2U1MjU5MjtcbiAgYm9yZGVyLXJhZGl1czogM3B4O1xuICBjb2xvcjogI2ZmZjtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBwYWRkaW5nOiAzcHg7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgcmlnaHQ6IDIwcHg7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG59XG5cbi5hbGVydC10eXBlLW5hbWVbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBwYWRkaW5nOiAzcHg7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG59XG5cbi5kZWJ1Z2dpbmctdGl0bGVbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZm9udC1zaXplOiAxOHB4O1xufVxuXG4ubnVtLWFsZXJ0cy1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZm9udC13ZWlnaHQ6IGJvbGQ7XG4gIHBhZGRpbmc6IDEwcHggMTBweCAxMHB4IDMwcHg7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcbn1cblxuLm51bS1hbGVydHMtbGFiZWxbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBmb250LXNpemU6IDEzcHg7XG59XG5cbi5udW0tYWxlcnRzLXZhbHVlW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIGJvcmRlci1yYWRpdXM6IDEycHg7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgZm9udC1zaXplOiAxM3B4O1xuICBmb250LXdlaWdodDogbm9ybWFsO1xuICBsaW5lLWhlaWdodDogMjRweDtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICByaWdodDogMjBweDtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICB3aWR0aDogMjRweDtcbn1cblxuLm51bS1hbGVydHMtdmFsdWUubm9uLXplcm9bX25nY29udGVudC0lQ09NUCVdIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2ZmYjc4MDtcbiAgZm9udC13ZWlnaHQ6IGJvbGQ7XG59Il19KSxufSkoKSxSM2U9e1ttZC5GVU5DVElPTl9SRUNPTVBJTEVfQUxFUlRdOntkaXNwbGF5TmFtZToiRnVuY3Rpb24gcmVjb21waWxlcyIsZGlzcGxheVN5bWJvbDoiQyJ9LFttZC5JTkZfTkFOX0FMRVJUXTp7ZGlzcGxheU5hbWU6Ik5hTi9cdTIyMWUiLGRpc3BsYXlTeW1ib2w6Ilx1MjIxZSJ9LFttZC5URU5TT1JfU0hBUEVfQUxFUlRdOntkaXNwbGF5TmFtZToiVGVuc29yIHNoYXBlIixkaXNwbGF5U3ltYm9sOiJcdTI1YTAifX0sUHJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMubnVtQWxlcnRzJD10aGlzLnN0b3JlLnBpcGUodnQodXJlKSksdGhpcy5hbGVydHNCcmVha2Rvd24kPXRoaXMuc3RvcmUucGlwZSh2dChKKGhyZSxpPT57bGV0IHI9T2JqZWN0LmtleXMoaSk7cmV0dXJuIHIuc29ydCgpLHIubWFwKG89Pih7dHlwZTpvLC4uLlIzZVtvXSxjb3VudDppW29dfSkpfSkpKSx0aGlzLmZvY3VzVHlwZSQ9dGhpcy5zdG9yZS5waXBlKHZ0KE1QKSl9b25Ub2dnbGVGb2N1c1R5cGUoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChHdih7YWxlcnRUeXBlOmV9KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ0Zi1kZWJ1Z2dlci12Mi1hbGVydHMiXV0sZGVjbHM6NCx2YXJzOjksY29uc3RzOltbMywibnVtQWxlcnRzIiwiYWxlcnRzQnJlYWtkb3duIiwiZm9jdXNUeXBlIiwib25Ub2dnbGVGb2N1c1R5cGUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImFsZXJ0cy1jb21wb25lbnQiLDApLFAoIm9uVG9nZ2xlRm9jdXNUeXBlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblRvZ2dsZUZvY3VzVHlwZShvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLHYoKSksMiZlJiZ5KCJudW1BbGVydHMiLFUoMSwzLGkubnVtQWxlcnRzJCkpKCJhbGVydHNCcmVha2Rvd24iLFUoMiw1LGkuYWxlcnRzQnJlYWtkb3duJCkpKCJmb2N1c1R5cGUiLFUoMyw3LGkuZm9jdXNUeXBlJCkpfSxkZXBlbmRlbmNpZXM6W0lyZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksUnc9ezE5OiJmbG9hdDE2IiwxOiJmbG9hdDMyIiwyOiJmbG9hdDY0IiwzOiJpbnQzMiIsNDoidWludDgiLDE3OiJ1aW50MTYiLDIyOiJ1aW50MzIiLDIzOiJ1aW50NjQiLDU6ImludDE2Iiw2OiJpbnQ4Iiw3OiJzdHJpbmciLDg6ImNvbXBsZXg2NCIsMTg6ImNvbXBsZXgxMjgiLDk6ImludDY0IiwxMDoiYm9vbCIsMTE6InFpbnQ4IiwxMjoicXVpbnQ4IiwxNToicWludDE2IiwxNjoicXVpbnQxNiIsMTM6InFpbnQzMiIsMTQ6ImJmbG9hdDE2IiwyMDoicmVzb3VyY2UiLDIxOiJ2YXJpYW50IiwxMTk6ImZsb2F0MTZfcmVmIiwxMDE6ImZsb2F0MzJfcmVmIiwxMDI6ImZsb2F0NjRfcmVmIiwxMDM6ImludDMyX3JlZiIsMTIyOiJ1aW50MzJfcmVmIiwxMDQ6InVpbnQ4X3JlZiIsMTE3OiJ1aW50MTZfcmVmIiwxMDU6ImludDE2X3JlZiIsMTA2OiJpbnQ4X3JlZiIsMTA3OiJzdHJpbmdfcmVmIiwxMDg6ImNvbXBsZXg2NF9yZWYiLDExODoiY29tcGxleDEyOF9yZWYiLDEwOToiaW50NjRfcmVmIiwxMjM6InVpbnQ2NF9yZWYiLDExMDoiYm9vbF9yZWYiLDExMToicWludDhfcmVmIiwxMTI6InF1aW50OF9yZWYiLDExNToicWludDE2X3JlZiIsMTE2OiJxdWludDE2X3JlZiIsMTEzOiJxaW50MzJfcmVmIiwxMTQ6ImJmbG9hdDE2X3JlZiIsMTIwOiJyZXNvdXJjZV9yZWYiLDEyMToidmFyaWFudF9yZWYifTtmdW5jdGlvbiBBUChuKXtsZXR7dGVuc29yRGVidWdNb2RlOnQsYXJyYXk6ZX09bjtzd2l0Y2godCl7Y2FzZSBhcy5OT19URU5TT1I6aWYobnVsbCE9PWUpdGhyb3cgbmV3IEVycm9yKCJVbmV4cGVjdGVkbHkgcmVjZWl2ZWQgbm9uLW51bGwgZGVidWctdGVuc29yLXZhbHVlIGFycmF5IHVuZGVyIE5PX1RFTlNPUiBtb2RlIik7cmV0dXJue307Y2FzZSBhcy5DVVJUX0hFQUxUSDppZihudWxsPT09ZXx8MiE9PWUubGVuZ3RoKXRocm93IG5ldyBFcnJvcihgVW5kZXIgQ1VSVF9IRUFMVEggbW9kZSwgZXhwZWN0ZWQgZGVidWctdGVuc29yLXZhbHVlIGFycmF5IHRvIGhhdmUgbGVuZ3RoIDIsIGJ1dCBnb3QgJHtKU09OLnN0cmluZ2lmeShlKX1gKTtyZXR1cm57aGFzSW5mT3JOYU46Qm9vbGVhbihlWzFdKX07Y2FzZSBhcy5DT05DSVNFX0hFQUxUSDp7aWYobnVsbD09PWV8fDUhPT1lLmxlbmd0aCl0aHJvdyBuZXcgRXJyb3IoYFVuZGVyIENPTkNJU0VfSEVBTFRIIG1vZGUsIGV4cGVjdGVkIGRlYnVnLXRlbnNvci12YWx1ZSBhcnJheSB0byBoYXZlIGxlbmd0aCA1LCBidXQgZ290ICR7SlNPTi5zdHJpbmdpZnkoZSl9YCk7bGV0IGk9e3NpemU6ZVsxXX07cmV0dXJuIGVbMl0+MCYmKGkubnVtTmVnYXRpdmVJbmZzPWVbMl0pLGVbM10+MCYmKGkubnVtUG9zaXRpdmVJbmZzPWVbM10pLGVbNF0+MCYmKGkubnVtTmFOcz1lWzRdKSxpfWNhc2UgYXMuU0hBUEU6e2lmKG51bGw9PT1lfHwxMCE9PWUubGVuZ3RoKXRocm93IG5ldyBFcnJvcihgVW5kZXIgU0hBUEUgbW9kZSwgZXhwZWN0ZWQgZGVidWctdGVuc29yLXZhbHVlIGFycmF5IHRvIGhhdmUgbGVuZ3RoIDEwLCBidXQgZ290ICR7SlNPTi5zdHJpbmdpZnkoZSl9YCk7bGV0IGk9ZVsyXSxyPWUuc2xpY2UoNCxNYXRoLm1pbig0K2ksZS5sZW5ndGgpKTtyZXR1cm4gci5sZW5ndGg8aSYmKHI9bmV3IEFycmF5KGktci5sZW5ndGgpLmNvbmNhdChyKSkse2R0eXBlOlJ3W2VbMV1dLHJhbms6aSxzaXplOmVbM10sc2hhcGU6cn19Y2FzZSBhcy5GVUxMX0hFQUxUSDp7aWYobnVsbD09PWV8fDExIT09ZS5sZW5ndGgpdGhyb3cgbmV3IEVycm9yKGBVbmRlciBGVUxMX0hFQUxUSCBtb2RlLCBleHBlY3RlZCBkZWJ1Zy10ZW5zb3ItdmFsdWUgYXJyYXkgdG8gaGF2ZSBsZW5ndGggMTEsIGJ1dCBnb3QgJHtKU09OLnN0cmluZ2lmeShlKX1gKTtsZXQgcj17ZHR5cGU6UndbZVsyXV0scmFuazplWzNdLHNpemU6ZVs0XX07cmV0dXJuIGVbNV0+MCYmKHIubnVtTmVnYXRpdmVJbmZzPWVbNV0pLGVbNl0+MCYmKHIubnVtUG9zaXRpdmVJbmZzPWVbNl0pLGVbN10+MCYmKHIubnVtTmFOcz1lWzddKSxlWzhdPjAmJihyLm51bU5lZ2F0aXZlRmluaXRlcz1lWzhdKSxlWzldPjAmJihyLm51bVplcm9zPWVbOV0pLGVbMTBdPjAmJihyLm51bVBvc2l0aXZlRmluaXRlcz1lWzEwXSkscn1jYXNlIGFzLkZVTExfVEVOU09SOmlmKG51bGwhPT1lKXRocm93IG5ldyBFcnJvcigiVW5leHBlY3RlZGx5IHJlY2VpdmVkIG5vbi1udWxsIGRlYnVnLXRlbnNvci12YWx1ZSBhcnJheSB1bmRlciBGVUxMX1RFTlNPUiBtb2RlIik7cmV0dXJue307ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoYFVucmVjb2duaXplZCB0ZW5zb3JEZWJ1Z01vZGU6ICR7dH1gKX19dmFyIGZVPSJbX25naG9zdC0lQ09NUCVdIHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZTNlNWU4O1xuICAgIGJvcmRlcjogMXB4IHNvbGlkICNjMGMwYzA7XG4gICAgYm9yZGVyLXJhZGl1czogNHB4O1xuICAgIGZvbnQtZmFtaWx5OiAnUm9ib3RvIE1vbm8nLCBtb25vc3BhY2U7XG4gICAgaGVpZ2h0OiAxNHB4O1xuICAgIGxpbmUtaGVpZ2h0OiAxNHB4O1xuICAgIG1hcmdpbjogMCAycHg7XG4gICAgcGFkZGluZzogMXB4IDNweDtcbiAgICB3aWR0aDogbWF4LWNvbnRlbnQ7XG4gIH0iO2Z1bmN0aW9uIGszZShuLHQpezEmbiYmTygwLCJkaXYiLDQpfWZ1bmN0aW9uIEYzZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2Iiw3KSgxLCJzcGFuIiw4KSxBKDIsIk5hTiIpLHYoKSxfKDMsInNwYW4iLDkpLEEoNCksdigpKCkpLDImbil7bGV0IGU9UygyKTtDKDQpLGplKCJceGQ3IixlLm51bU5hTnMsIiIpfX1mdW5jdGlvbiBOM2Uobix0KXtpZigxJm4mJihfKDAsImRpdiIsNykoMSwic3BhbiIsOCksQSgyLCItXHUyMjFlIiksdigpLF8oMywic3BhbiIsOSksQSg0KSx2KCkoKSksMiZuKXtsZXQgZT1TKDIpO0MoNCksamUoIlx4ZDciLGUubnVtTmVnYXRpdmVJbmZzLCIiKX19ZnVuY3Rpb24gTDNlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDcpKDEsInNwYW4iLDgpLEEoMiwiK1x1MjIxZSIpLHYoKSxfKDMsInNwYW4iLDkpLEEoNCksdigpKCkpLDImbil7bGV0IGU9UygyKTtDKDQpLGplKCJceGQ3IixlLm51bVBvc2l0aXZlSW5mcywiIil9fWZ1bmN0aW9uIEIzZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2Iiw3KSgxLCJzcGFuIiwxMCksQSgyLCItIiksdigpLF8oMywic3BhbiIsOSksQSg0KSx2KCkoKSksMiZuKXtsZXQgZT1TKDIpO0MoNCksamUoIlx4ZDciLGUubnVtTmVnYXRpdmVGaW5pdGVzLCIiKX19ZnVuY3Rpb24gVjNlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDcpKDEsInNwYW4iLDEwKSxBKDIsIjAiKSx2KCksXygzLCJzcGFuIiw5KSxBKDQpLHYoKSgpKSwyJm4pe2xldCBlPVMoMik7Qyg0KSxqZSgiXHhkNyIsZS5udW1aZXJvcywiIil9fWZ1bmN0aW9uIEgzZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2Iiw3KSgxLCJzcGFuIiwxMCksQSgyLCIrIiksdigpLF8oMywic3BhbiIsOSksQSg0KSx2KCkoKSksMiZuKXtsZXQgZT1TKDIpO0MoNCksamUoIlx4ZDciLGUubnVtUG9zaXRpdmVGaW5pdGVzLCIiKX19ZnVuY3Rpb24gVTNlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDUpLEUoMSxGM2UsNSwxLCJkaXYiLDYpLEUoMixOM2UsNSwxLCJkaXYiLDYpLEUoMyxMM2UsNSwxLCJkaXYiLDYpLEUoNCxCM2UsNSwxLCJkaXYiLDYpLEUoNSxWM2UsNSwxLCJkaXYiLDYpLEUoNixIM2UsNSwxLCJkaXYiLDYpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygxKSx5KCJuZ0lmIix2b2lkIDAhPT1lLm51bU5hTnMmJmUubnVtTmFOcz4wKSxDKDEpLHkoIm5nSWYiLHZvaWQgMCE9PWUubnVtTmVnYXRpdmVJbmZzJiZlLm51bU5lZ2F0aXZlSW5mcz4wKSxDKDEpLHkoIm5nSWYiLHZvaWQgMCE9PWUubnVtUG9zaXRpdmVJbmZzJiZlLm51bVBvc2l0aXZlSW5mcz4wKSxDKDEpLHkoIm5nSWYiLHZvaWQgMCE9PWUubnVtTmVnYXRpdmVGaW5pdGVzJiZlLm51bU5lZ2F0aXZlRmluaXRlcz4wKSxDKDEpLHkoIm5nSWYiLHZvaWQgMCE9PWUubnVtWmVyb3MmJmUubnVtWmVyb3M+MCksQygxKSx5KCJuZ0lmIix2b2lkIDAhPT1lLm51bVBvc2l0aXZlRmluaXRlcyYmZS5udW1Qb3NpdGl2ZUZpbml0ZXM+MCl9fXZhciB6M2U9ZnVuY3Rpb24obil7cmV0dXJuWyJjb250YWluZXIiLG5dfTtmdW5jdGlvbiBqM2Uobix0KXsxJm4mJk8oMCwiZGVidWctdGVuc29yLWR0eXBlIiw1KSwyJm4mJnkoImR0eXBlIixTKCkuZGVidWdUZW5zb3JWYWx1ZS5kdHlwZSl9ZnVuY3Rpb24gRzNlKG4sdCl7MSZuJiZPKDAsImRlYnVnLXRlbnNvci1yYW5rIiw2KSwyJm4mJnkoInJhbmsiLFMoKS5kZWJ1Z1RlbnNvclZhbHVlLnJhbmspfWZ1bmN0aW9uIFczZShuLHQpezEmbiYmTygwLCJkZWJ1Zy10ZW5zb3Itc2hhcGUiLDcpLDImbiYmeSgic2hhcGUiLFMoKS5kZWJ1Z1RlbnNvclZhbHVlLnNoYXBlKX1mdW5jdGlvbiBxM2Uobix0KXsxJm4mJk8oMCwiZGVidWctdGVuc29yLWhhcy1pbmYtb3ItbmFuIiw4KSwyJm4mJnkoImhhc0luZk9yTmFOIixTKCkuZGVidWdUZW5zb3JWYWx1ZS5oYXNJbmZPck5hTil9ZnVuY3Rpb24gWTNlKG4sdCl7aWYoMSZuJiZPKDAsImRlYnVnLXRlbnNvci1udW1lcmljLWJyZWFrZG93biIsOSksMiZuKXtsZXQgZT1TKCk7WmkoInNpemUiLGUuZGVidWdUZW5zb3JWYWx1ZS5zaXplKSx5KCJudW1OZWdhdGl2ZUluZnMiLGUuZGVidWdUZW5zb3JWYWx1ZS5udW1OZWdhdGl2ZUluZnMpKCJudW1Qb3NpdGl2ZUluZnMiLGUuZGVidWdUZW5zb3JWYWx1ZS5udW1Qb3NpdGl2ZUluZnMpKCJudW1OYU5zIixlLmRlYnVnVGVuc29yVmFsdWUubnVtTmFOcykoIm51bU5lZ2F0aXZlRmluaXRlcyIsZS5kZWJ1Z1RlbnNvclZhbHVlLm51bU5lZ2F0aXZlRmluaXRlcykoIm51bVplcm9zIixlLmRlYnVnVGVuc29yVmFsdWUubnVtWmVyb3MpKCJudW1Qb3NpdGl2ZUZpbml0ZXMiLGUuZGVidWdUZW5zb3JWYWx1ZS5udW1Qb3NpdGl2ZUZpbml0ZXMpfX12YXIgWDNlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImRlYnVnLXRlbnNvci1kdHlwZSJdXSxpbnB1dHM6e2R0eXBlOiJkdHlwZSJ9LGRlY2xzOjEsdmFyczoxLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiZBKDApLDImZSYmamUoIiAiLGkuZHR5cGUsIiAiKX0sc3R5bGVzOltmVV19KSxufSkoKSxRM2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siZGVidWctdGVuc29yLXJhbmsiXV0saW5wdXRzOntyYW5rOiJyYW5rIn0sZGVjbHM6MSx2YXJzOjEsdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJkEoMCksMiZlJiZqZSgiICIsaS5yYW5rLCJEICIpfSxzdHlsZXM6W2ZVXX0pLG59KSgpLEszZT0oKCk9PntjbGFzcyBue2dldCBzaGFwZVN0cmluZygpe3JldHVybiJbIit0aGlzLnNoYXBlLm1hcChlPT52b2lkIDA9PT1lPyI/IjpTdHJpbmcoZSkpLmpvaW4oIiwiKSsiXSJ9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImRlYnVnLXRlbnNvci1zaGFwZSJdXSxpbnB1dHM6e3NoYXBlOiJzaGFwZSJ9LGRlY2xzOjEsdmFyczoxLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiZBKDApLDImZSYmamUoIiBzaGFwZToiLGkuc2hhcGVTdHJpbmcsIiAiKX0sc3R5bGVzOltmVV19KSxufSkoKSxaM2U9KCgpPT57Y2xhc3MgbntnZXQgYnJlYWtkb3duRXhpc3RzKCl7cmV0dXJuIHZvaWQgMCE9PXRoaXMubnVtTmFOc3x8dm9pZCAwIT09dGhpcy5udW1OZWdhdGl2ZUluZnN8fHZvaWQgMCE9PXRoaXMubnVtUG9zaXRpdmVJbmZzfHx2b2lkIDAhPT10aGlzLm51bU5lZ2F0aXZlRmluaXRlc3x8dm9pZCAwIT09dGhpcy5udW1aZXJvc3x8dm9pZCAwIT09dGhpcy5udW1Qb3NpdGl2ZUZpbml0ZXN9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImRlYnVnLXRlbnNvci1udW1lcmljLWJyZWFrZG93biJdXSxpbnB1dHM6e3NpemU6InNpemUiLG51bU5hTnM6Im51bU5hTnMiLG51bU5lZ2F0aXZlSW5mczoibnVtTmVnYXRpdmVJbmZzIixudW1Qb3NpdGl2ZUluZnM6Im51bVBvc2l0aXZlSW5mcyIsbnVtTmVnYXRpdmVGaW5pdGVzOiJudW1OZWdhdGl2ZUZpbml0ZXMiLG51bVplcm9zOiJudW1aZXJvcyIsbnVtUG9zaXRpdmVGaW5pdGVzOiJudW1Qb3NpdGl2ZUZpbml0ZXMifSxkZWNsczo3LHZhcnM6Myxjb25zdHM6W1sxLCJzaXplIl0sWzEsInNpemUtdmFsdWUiXSxbImNsYXNzIiwiYnJlYWsiLDQsIm5nSWYiXSxbImNsYXNzIiwiYnJlYWtkb3duIiw0LCJuZ0lmIl0sWzEsImJyZWFrIl0sWzEsImJyZWFrZG93biJdLFsiY2xhc3MiLCJjYXRlZ29yeSIsNCwibmdJZiJdLFsxLCJjYXRlZ29yeSJdLFsxLCJjYXRlZ29yeS10YWciLCJpbmZpbml0ZSJdLFsxLCJjYXRlZ29yeS1jb3VudCJdLFsxLCJjYXRlZ29yeS10YWciLCJmaW5pdGUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCkoMSwic3BhbiIpLEEoMiwic2l6ZToiKSx2KCksXygzLCJzcGFuIiwxKSxBKDQpLHYoKSgpLEUoNSxrM2UsMSwwLCJkaXYiLDIpLEUoNixVM2UsNyw2LCJkaXYiLDMpKSwyJmUmJihDKDQpLHl0KGkuc2l6ZSksQygxKSx5KCJuZ0lmIixpLmJyZWFrZG93bkV4aXN0cyksQygxKSx5KCJuZ0lmIixpLmJyZWFrZG93bkV4aXN0cykpfSxkZXBlbmRlbmNpZXM6W0JlXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVdIHtcbiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2UzZTVlODtcbiAgICAgICAgYm9yZGVyOiAxcHggc29saWQgI2MwYzBjMDtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogNHB4O1xuICAgICAgICBmb250LWZhbWlseTogJ1JvYm90byBNb25vJywgbW9ub3NwYWNlO1xuICAgICAgICBmb250LXNpemU6IDEwcHg7XG4gICAgICAgIG1hcmdpbjogMCAycHg7XG4gICAgICAgIHBhZGRpbmc6IDFweDtcbiAgICAgIH1cbiAgICAgIC5icmVha1tfbmdjb250ZW50LSVDT01QJV0ge1xuICAgICAgICBmbGV4LWJhc2lzOiAxMDAlO1xuICAgICAgICB3aWR0aDogMDtcbiAgICAgIH1cbiAgICAgIC5zaXplW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gICAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgICBoZWlnaHQ6IDExcHg7XG4gICAgICAgIGxpbmUtaGVpZ2h0OiAxMXB4O1xuICAgICAgICBtYXJnaW46IDAgM3B4O1xuICAgICAgICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICAgICAgfVxuICAgICAgLmJyZWFrZG93bltfbmdjb250ZW50LSVDT01QJV0ge1xuICAgICAgICBib3JkZXItdG9wOiAxcHggc29saWQgcmdiYSgwLCAwLCAwLCAwLjEyKTtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgaGVpZ2h0OiAxMXB4O1xuICAgICAgICBsaW5lLWhlaWdodDogMTFweDtcbiAgICAgICAgcGFkZGluZzogMnB4O1xuICAgICAgICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICAgICAgfVxuICAgICAgLmNhdGVnb3J5W19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gICAgICAgIG1hcmdpbi1ib3R0b206IDJweDtcbiAgICAgICAgbWFyZ2luLWxlZnQ6IDRweDtcbiAgICAgICAgbWFyZ2luLXRvcDogMnB4O1xuICAgICAgICBoZWlndGg6IDEwMCU7XG4gICAgICAgIHdpZHRoOiBtYXgtY29udGVudDtcbiAgICAgIH1cbiAgICAgIC5jYXRlZ29yeS10YWdbX25nY29udGVudC0lQ09NUCVdIHtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogMnB4O1xuICAgICAgICBwYWRkaW5nOiAwIDJweDtcbiAgICAgIH1cbiAgICAgIC5maW5pdGVbX25nY29udGVudC0lQ09NUCVdIHtcbiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2FhYTtcbiAgICAgICAgY29sb3I6ICNmZWZlZmU7XG4gICAgICB9XG4gICAgICAuaW5maW5pdGVbX25nY29udGVudC0lQ09NUCVdIHtcbiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2U1MjU5MjtcbiAgICAgICAgY29sb3I6ICNmZmY7XG4gICAgICB9Il19KSxufSkoKSxKM2U9KCgpPT57Y2xhc3MgbntnZXQgaW5mb1N0cmluZygpe3JldHVybiB0aGlzLmhhc0luZk9yTmFOPyJIYXMgXHUyMjFlL05hTiI6Ik5vIFx1MjIxZS9OYU4ifX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJkZWJ1Zy10ZW5zb3ItaGFzLWluZi1vci1uYW4iXV0saW5wdXRzOntoYXNJbmZPck5hTjoiaGFzSW5mT3JOYU4ifSxkZWNsczoyLHZhcnM6NCxjb25zdHM6W1szLCJuZ0NsYXNzIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJkaXYiLDApLEEoMSksdigpKSwyJmUmJih5KCJuZ0NsYXNzIixPbigyLHozZSxpLmhhc0luZk9yTmFOPyJoYXMtaW5mLW9yLW5hbiI6IiIpKSxDKDEpLGplKCIgIixpLmluZm9TdHJpbmcsIiAiKSl9LGRlcGVuZGVuY2llczpbRm5dLHN0eWxlczpbIi5jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdIHtcbiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2UzZTVlODtcbiAgICAgICAgYm9yZGVyOiAxcHggc29saWQgI2MwYzBjMDtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogNHB4O1xuICAgICAgICBjb2xvcjogIzY2NjY2NjtcbiAgICAgICAgZm9udC1mYW1pbHk6ICdSb2JvdG8gTW9ubycsIG1vbm9zcGFjZTtcbiAgICAgICAgaGVpZ2h0OiAxNHB4O1xuICAgICAgICBsaW5lLWhlaWdodDogMTRweDtcbiAgICAgICAgbWFyZ2luOiAwIDJweDtcbiAgICAgICAgcGFkZGluZzogMXB4IDNweDtcbiAgICAgICAgd2lkdGg6IG1heC1jb250ZW50O1xuICAgICAgfVxuICAgICAgLmhhcy1pbmYtb3ItbmFuW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gICAgICAgIGJhY2tncm91bmQtY29sb3I6ICNlNTI1OTI7XG4gICAgICAgIGNvbG9yOiAjZmZmO1xuICAgICAgfSJdfSksbn0pKCksSVA9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siZGVidWctdGVuc29yLXZhbHVlIl1dLGlucHV0czp7ZGVidWdUZW5zb3JWYWx1ZToiZGVidWdUZW5zb3JWYWx1ZSJ9LGRlY2xzOjUsdmFyczo1LGNvbnN0czpbWzMsImR0eXBlIiw0LCJuZ0lmIl0sWzMsInJhbmsiLDQsIm5nSWYiXSxbMywic2hhcGUiLDQsIm5nSWYiXSxbMywiaGFzSW5mT3JOYU4iLDQsIm5nSWYiXSxbMywic2l6ZSIsIm51bU5lZ2F0aXZlSW5mcyIsIm51bVBvc2l0aXZlSW5mcyIsIm51bU5hTnMiLCJudW1OZWdhdGl2ZUZpbml0ZXMiLCJudW1aZXJvcyIsIm51bVBvc2l0aXZlRmluaXRlcyIsNCwibmdJZiJdLFszLCJkdHlwZSJdLFszLCJyYW5rIl0sWzMsInNoYXBlIl0sWzMsImhhc0luZk9yTmFOIl0sWzMsInNpemUiLCJudW1OZWdhdGl2ZUluZnMiLCJudW1Qb3NpdGl2ZUluZnMiLCJudW1OYU5zIiwibnVtTmVnYXRpdmVGaW5pdGVzIiwibnVtWmVyb3MiLCJudW1Qb3NpdGl2ZUZpbml0ZXMiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihFKDAsajNlLDEsMSwiZGVidWctdGVuc29yLWR0eXBlIiwwKSxFKDEsRzNlLDEsMSwiZGVidWctdGVuc29yLXJhbmsiLDEpLEUoMixXM2UsMSwxLCJkZWJ1Zy10ZW5zb3Itc2hhcGUiLDIpLEUoMyxxM2UsMSwxLCJkZWJ1Zy10ZW5zb3ItaGFzLWluZi1vci1uYW4iLDMpLEUoNCxZM2UsMSw3LCJkZWJ1Zy10ZW5zb3ItbnVtZXJpYy1icmVha2Rvd24iLDQpKSwyJmUmJih5KCJuZ0lmIix2b2lkIDAhPT1pLmRlYnVnVGVuc29yVmFsdWUuZHR5cGUpLEMoMSkseSgibmdJZiIsdm9pZCAwIT09aS5kZWJ1Z1RlbnNvclZhbHVlLnJhbmspLEMoMSkseSgibmdJZiIsdm9pZCAwIT09aS5kZWJ1Z1RlbnNvclZhbHVlLnNoYXBlKSxDKDEpLHkoIm5nSWYiLHZvaWQgMCE9PWkuZGVidWdUZW5zb3JWYWx1ZS5oYXNJbmZPck5hTiksQygxKSx5KCJuZ0lmIix2b2lkIDAhPT1pLmRlYnVnVGVuc29yVmFsdWUuc2l6ZSkpfSxkZXBlbmRlbmNpZXM6W0JlLFgzZSxKM2UsWjNlLFEzZSxLM2VdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV0ge1xuICAgICAgICBhbGlnbi1pdGVtczogZmxleC1zdGFydDtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgZmxleC13cmFwOiBub3dyYXA7XG4gICAgICAgIG92ZXJmbG93OiBoaWRkZW47XG4gICAgICAgIHZlcnRpY2FsLWFsaWduOiB0b3A7XG4gICAgICB9XG4gICAgICBkZWJ1Zy10ZW5zb3ItbnVtZXJpYy1icmVha2Rvd25bX25nY29udGVudC0lQ09NUCVdIHtcbiAgICAgICAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICAgICAgfSJdfSksbn0pKCk7ZnVuY3Rpb24gJDNlKG4sdCl7MSZuJiYoXygwLCJkaXYiLDEyKSxBKDEsIlx1MjViNiIpLHYoKSl9dmFyIGVCZT1mdW5jdGlvbihuLHQpe3JldHVybnt0ZW5zb3JEZWJ1Z01vZGU6bixhcnJheTp0fX07ZnVuY3Rpb24gdEJlKG4sdCl7aWYoMSZuJiZPKDAsImRlYnVnLXRlbnNvci12YWx1ZSIsMTcpLDImbil7bGV0IGU9UygyKS4kaW1wbGljaXQsaT1TKDIpO3koImRlYnVnVGVuc29yVmFsdWUiLGkucGFyc2VEZWJ1Z1RlbnNvclZhbHVlKFFyKDEsZUJlLGkuZ3JhcGhFeGVjdXRpb25EYXRhW2VdLnRlbnNvcl9kZWJ1Z19tb2RlLGkuZ3JhcGhFeGVjdXRpb25EYXRhW2VdLmRlYnVnX3RlbnNvcl92YWx1ZSkpKX19ZnVuY3Rpb24gbkJlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IikoMSwiZGl2IiwxMykoMiwiYnV0dG9uIiwxNCksUCgiY2xpY2siLGZ1bmN0aW9uKCl7b2UoZSk7bGV0IHI9UygpLiRpbXBsaWNpdCxvPVMoMik7cmV0dXJuIHNlKG8ub25UZW5zb3JOYW1lQ2xpY2suZW1pdCh7aW5kZXg6cixncmFwaF9pZDpvLmdyYXBoRXhlY3V0aW9uRGF0YVtyXS5ncmFwaF9pZCxvcF9uYW1lOm8uZ3JhcGhFeGVjdXRpb25EYXRhW3JdLm9wX25hbWV9KSl9KSxBKDMpLHYoKSxfKDQsImRpdiIsMTUpLEEoNSksdigpKCksRSg2LHRCZSwxLDQsImRlYnVnLXRlbnNvci12YWx1ZSIsMTYpLHYoKX1pZigyJm4pe2xldCBlPVMoKS4kaW1wbGljaXQsaT1TKDIpO0MoMiksWmkoInRpdGxlIixpLmdldFRlbnNvck5hbWUoZSkpLEMoMSksamUoIiAiLGkuZ2V0VGVuc29yTmFtZShlKSwiICIpLEMoMikseXQoaS5ncmFwaEV4ZWN1dGlvbkRhdGFbZV0ub3BfdHlwZSksQygxKSx5KCJuZ0lmIixudWxsIT09aS5ncmFwaEV4ZWN1dGlvbkRhdGFbZV0uZGVidWdfdGVuc29yX3ZhbHVlKX19ZnVuY3Rpb24gaUJlKG4sdCl7MSZuJiYoXygwLCJkaXYiLDE4KSxBKDEsIiBMb2FkaW5nLi4uICIpLHYoKSl9dmFyIHJCZT1mdW5jdGlvbihuKXtyZXR1cm57ImlucHV0LW9mLWZvY3VzIjpufX07ZnVuY3Rpb24gb0JlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDUpKDEsImRpdiIsNikoMiwiZGl2Iiw3KSxFKDMsJDNlLDIsMCwiZGl2Iiw4KSxBKDQpLHYoKSxFKDUsbkJlLDcsNCwiZGl2Iiw5KSxFKDYsaUJlLDIsMCwibmctdGVtcGxhdGUiLDEwLDExLHF0KSx2KCkoKSksMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPSRlKDcpLHI9UygyKTtDKDEpLHkoIm5nQ2xhc3MiLE9uKDUsckJlLHIuaXNJbnB1dE9mRm9jdXMoZSkpKSxDKDIpLHkoIm5nSWYiLGU9PT1yLmZvY3VzSW5kZXgpLEMoMSksamUoIiAiLGUsIiAiKSxDKDEpLHkoIm5nSWYiLHIuZ3JhcGhFeGVjdXRpb25EYXRhW2VdKSgibmdJZkVsc2UiLGkpfX1mdW5jdGlvbiBzQmUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJjZGstdmlydHVhbC1zY3JvbGwtdmlld3BvcnQiLDMpLFAoInNjcm9sbGVkSW5kZXhDaGFuZ2UiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25TY3JvbGxlZEluZGV4Q2hhbmdlLmVtaXQocikpfSksRSgxLG9CZSw4LDcsImRpdiIsNCksdigpfWlmKDImbil7bGV0IGU9UygpO0MoMSkseSgiY2RrVmlydHVhbEZvck9mIixlLmdyYXBoRXhlY3V0aW9uSW5kaWNlcyl9fXZhciBPcmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMub25TY3JvbGxlZEluZGV4Q2hhbmdlPW5ldyBHLHRoaXMub25UZW5zb3JOYW1lQ2xpY2s9bmV3IEcsdGhpcy5wYXJzZURlYnVnVGVuc29yVmFsdWU9QVAsdGhpcy5URVNUX09OTFk9e2dldFZpZXdQb3J0OigpPT50aGlzLnZpZXdQb3J0fX1uZ09uQ2hhbmdlcyhlKXtpZih0aGlzLnZpZXdQb3J0JiZlLmZvY3VzSW5kZXgmJm51bGwhPT1lLmZvY3VzSW5kZXguY3VycmVudFZhbHVlKXtsZXQgaT10aGlzLnZpZXdQb3J0LmdldFJlbmRlcmVkUmFuZ2UoKSxyPWUuZm9jdXNJbmRleC5jdXJyZW50VmFsdWUsbz1NYXRoLnJvdW5kKChpLmVuZC1pLnN0YXJ0KS8zKSxzPU1hdGgubWF4KHItbywwKTt0aGlzLnZpZXdQb3J0LnNjcm9sbFRvSW5kZXgocyxyPj1pLnN0YXJ0JiZyPGkuZW5kPyJzbW9vdGgiOnZvaWQgMCl9fWdldFRlbnNvck5hbWUoZSl7cmV0dXJuYCR7dGhpcy5ncmFwaEV4ZWN1dGlvbkRhdGFbZV0ub3BfbmFtZX06JHt0aGlzLmdyYXBoRXhlY3V0aW9uRGF0YVtlXS5vdXRwdXRfc2xvdH1gfWlzSW5wdXRPZkZvY3VzKGUpe3JldHVybiBudWxsIT09dGhpcy5mb2N1c0lucHV0SW5kaWNlcyYmdGhpcy5mb2N1c0lucHV0SW5kaWNlcy5pbmNsdWRlcyhlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siZ3JhcGgtZXhlY3V0aW9ucy1jb21wb25lbnQiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiZvdChlZyw1KSwyJmUpe2xldCByO05lKHI9TGUoKSkmJihpLnZpZXdQb3J0PXIuZmlyc3QpfX0saW5wdXRzOntudW1HcmFwaEV4ZWN1dGlvbnM6Im51bUdyYXBoRXhlY3V0aW9ucyIsZ3JhcGhFeGVjdXRpb25EYXRhOiJncmFwaEV4ZWN1dGlvbkRhdGEiLGdyYXBoRXhlY3V0aW9uSW5kaWNlczoiZ3JhcGhFeGVjdXRpb25JbmRpY2VzIixmb2N1c0luZGV4OiJmb2N1c0luZGV4Iixmb2N1c0lucHV0SW5kaWNlczoiZm9jdXNJbnB1dEluZGljZXMifSxvdXRwdXRzOntvblNjcm9sbGVkSW5kZXhDaGFuZ2U6Im9uU2Nyb2xsZWRJbmRleENoYW5nZSIsb25UZW5zb3JOYW1lQ2xpY2s6Im9uVGVuc29yTmFtZUNsaWNrIn0sZmVhdHVyZXM6W0Z0XSxkZWNsczo0LHZhcnM6Mixjb25zdHM6W1sxLCJncmFwaC1leGVjdXRpb25zLWNvbnRhaW5lciJdLFsxLCJncmFwaC1leGVjdXRpb25zLXRpdGxlIl0sWyJpdGVtU2l6ZSIsIjM4IiwiY2xhc3MiLCJncmFwaC1leGVjdXRpb25zLXZpZXdwb3J0IiwzLCJzY3JvbGxlZEluZGV4Q2hhbmdlIiw0LCJuZ0lmIl0sWyJpdGVtU2l6ZSIsIjM4IiwxLCJncmFwaC1leGVjdXRpb25zLXZpZXdwb3J0IiwzLCJzY3JvbGxlZEluZGV4Q2hhbmdlIl0sWyJjbGFzcyIsInRlbnNvci1jb250YWluZXIiLDQsImNka1ZpcnR1YWxGb3IiLCJjZGtWaXJ0dWFsRm9yT2YiXSxbMSwidGVuc29yLWNvbnRhaW5lciJdLFsxLCJ0ZW5zb3ItaXRlbSIsMywibmdDbGFzcyJdLFsxLCJncmFwaC1leGVjdXRpb24taW5kZXgiXSxbImNsYXNzIiwiZ3JhcGgtZXhlY3V0aW9uLWZvY3VzIiw0LCJuZ0lmIl0sWzQsIm5nSWYiLCJuZ0lmRWxzZSJdLFsiY2xhc3MiLCJ0ZW5zb3ItaXRlbSJdLFsiZGF0YUxvYWRpbmciLCIiXSxbMSwiZ3JhcGgtZXhlY3V0aW9uLWZvY3VzIl0sWzEsInRlbnNvci1uYW1lLWFuZC1vcC10eXBlIl0sWzEsInRlbnNvci1uYW1lIiwzLCJ0aXRsZSIsImNsaWNrIl0sWzEsIm9wLXR5cGUiXSxbMywiZGVidWdUZW5zb3JWYWx1ZSIsNCwibmdJZiJdLFszLCJkZWJ1Z1RlbnNvclZhbHVlIl0sWzEsImxvYWRpbmctc3Bpbm5lciJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IiwwKSgxLCJkaXYiLDEpLEEoMiksdigpLEUoMyxzQmUsMiwxLCJjZGstdmlydHVhbC1zY3JvbGwtdmlld3BvcnQiLDIpLHYoKSksMiZlJiYoQygyKSxqZSgiIEdyYXBoIEV4ZWN1dGlvbnMgKCIsaS5udW1HcmFwaEV4ZWN1dGlvbnMsIikgIiksQygxKSx5KCJuZ0lmIixudWxsIT09aS5udW1HcmFwaEV4ZWN1dGlvbnMmJmkubnVtR3JhcGhFeGVjdXRpb25zPjApKX0sZGVwZW5kZW5jaWVzOltGbixCZSxJUCxiMix4MixlZ10sc3R5bGVzOlsnLmdyYXBoLWV4ZWN1dGlvbnMtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItbGVmdDoxcHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDAlO21hcmdpbi1sZWZ0OjhweDtwYWRkaW5nLWxlZnQ6MTBweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuZ3JhcGgtZXhlY3V0aW9ucy1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JhcGgtZXhlY3V0aW9ucy1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjFweCBzb2xpZCAjNTU1fS5ncmFwaC1leGVjdXRpb24tZm9jdXNbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6aW5saW5lLWJsb2NrfS5ncmFwaC1leGVjdXRpb24taW5kZXhbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiM2MTYxNjE7ZGlzcGxheTppbmxpbmUtYmxvY2s7cGFkZGluZy1yaWdodDo0cHg7dGV4dC1hbGlnbjpyaWdodDt3aWR0aDo0MHB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5ncmFwaC1leGVjdXRpb24taW5kZXhbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JhcGgtZXhlY3V0aW9uLWluZGV4W19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX0uZ3JhcGgtZXhlY3V0aW9ucy10aXRsZVtfbmdjb250ZW50LSVDT01QJV17Ym94LXNoYWRvdzowIDVweCAzcHggLTNweCAjY2NjO3BhZGRpbmctYm90dG9tOjVweH0uZ3JhcGgtZXhlY3V0aW9ucy12aWV3cG9ydFtfbmdjb250ZW50LSVDT01QJV17ZmxleC1ncm93OjE7Zm9udC1zaXplOjEycHg7d2lkdGg6MTAwJTtvdmVyZmxvdy14OmhpZGRlbn0uaW5wdXQtb2YtZm9jdXNbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2ZmZjA5OX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuaW5wdXQtb2YtZm9jdXNbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuaW5wdXQtb2YtZm9jdXNbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2U2NTEwMH0ubG9hZGluZy1zcGlubmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9ja30ub3AtdHlwZVtfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZWNlZmYxO2JvcmRlcjoxcHggc29saWQgI2ViZWJlYjtib3JkZXItcmFkaXVzOjRweDtmb250LWZhbWlseToiUm9ib3RvIE1vbm8iLG1vbm9zcGFjZTtmb250LXNpemU6MTBweDtoZWlnaHQ6MTRweDtsaW5lLWhlaWdodDoxNHB4O3BhZGRpbmc6MXB4IDNweDt3aWR0aDptYXgtY29udGVudDtkaXJlY3Rpb246cnRsO2Rpc3BsYXk6YmxvY2t9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLm9wLXR5cGVbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAub3AtdHlwZVtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyOjFweCBzb2xpZCAjNTU1fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5vcC10eXBlW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLm9wLXR5cGVbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6IzQ1NWE2NH0udGVuc29yLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17d2lkdGg6MTAwJX0udGVuc29yLWl0ZW1bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7ZGlzcGxheTpmbGV4O2ZsZXgtd3JhcDpub3dyYXA7aGVpZ2h0OjM4cHg7bGluZS1oZWlnaHQ6MzhweDt0ZXh0LWFsaWduOmxlZnQ7dmVydGljYWwtYWxpZ246bWlkZGxlO3doaXRlLXNwYWNlOm5vd3JhcDt3aWR0aDoxMDAlfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC50ZW5zb3ItaXRlbVtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC50ZW5zb3ItaXRlbVtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWJvdHRvbToxcHggc29saWQgIzU1NX0udGVuc29yLW5hbWVbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwwKTtib3JkZXI6bm9uZTtib3gtc2l6aW5nOmJvcmRlci1ib3g7Y29sb3I6aW5oZXJpdDtjdXJzb3I6cG9pbnRlcjtkaXJlY3Rpb246cnRsO2Rpc3BsYXk6YmxvY2s7aGVpZ2h0OjE2cHg7bGluZS1oZWlnaHQ6MTZweDttYXJnaW46MnB4IDAgMXB4O21heC13aWR0aDpjYWxjKDEwMCUgLSAycHgpO292ZXJmbG93OmhpZGRlbjtwYWRkaW5nOjAgMnB4O3RleHQtYWxpZ246cmlnaHQ7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZTt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzO3doaXRlLXNwYWNlOm5vd3JhcH0udGVuc29yLW5hbWVbX25nY29udGVudC0lQ09NUCVdOmZvY3Vze291dGxpbmU6MXB4IHNvbGlkICNjNmNhZDF9LnRlbnNvci1uYW1lLWFuZC1vcC10eXBlW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXJlY3Rpb246cnRsO2Rpc3BsYXk6aW5saW5lLWJsb2NrO292ZXJmbG93OmhpZGRlbjtwYWRkaW5nLXJpZ2h0OjhweDt0ZXh0LWFsaWduOnJpZ2h0O3dpZHRoOjI0MHB4fWRlYnVnLXRlbnNvci12YWx1ZVtfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTppbmxpbmUtYmxvY2s7bWFyZ2luOjJweCAwfSddLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksa3JlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMubnVtR3JhcGhFeGVjdXRpb25zJD10aGlzLnN0b3JlLnBpcGUodnQoSXcpKSx0aGlzLmdyYXBoRXhlY3V0aW9uRGF0YSQ9dGhpcy5zdG9yZS5waXBlKHZ0KG9VKSksdGhpcy5ncmFwaEV4ZWN1dGlvbkluZGljZXMkPXRoaXMuc3RvcmUucGlwZSh2dChKKEl3LGk9PjA9PT1pP251bGw6QXJyYXkuZnJvbSh7bGVuZ3RoOml9KS5tYXAoKHIsbyk9Pm8pKSkpLHRoaXMuZm9jdXNJbmRleCQ9dGhpcy5zdG9yZS5waXBlKHZ0KHNVKSksdGhpcy5mb2N1c0lucHV0SW5kaWNlcyQ9dGhpcy5zdG9yZS5waXBlKHZ0KHhyZSkpfW9uU2Nyb2xsZWRJbmRleENoYW5nZShlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKFF2KHtpbmRleDplfSkpfW9uVGVuc29yTmFtZUNsaWNrKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goS3YoZSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGYtZGVidWdnZXItdjItZ3JhcGgtZXhlY3V0aW9ucyJdXSxkZWNsczo2LHZhcnM6MTUsY29uc3RzOltbMywibnVtR3JhcGhFeGVjdXRpb25zIiwiZ3JhcGhFeGVjdXRpb25EYXRhIiwiZ3JhcGhFeGVjdXRpb25JbmRpY2VzIiwiZm9jdXNJbmRleCIsImZvY3VzSW5wdXRJbmRpY2VzIiwib25TY3JvbGxlZEluZGV4Q2hhbmdlIiwib25UZW5zb3JOYW1lQ2xpY2siXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImdyYXBoLWV4ZWN1dGlvbnMtY29tcG9uZW50IiwwKSxQKCJvblNjcm9sbGVkSW5kZXhDaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uU2Nyb2xsZWRJbmRleENoYW5nZShvKX0pKCJvblRlbnNvck5hbWVDbGljayIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25UZW5zb3JOYW1lQ2xpY2sobyl9KSxCKDEsImFzeW5jIiksQigyLCJhc3luYyIpLEIoMywiYXN5bmMiKSxCKDQsImFzeW5jIiksQig1LCJhc3luYyIpLHYoKSksMiZlJiZ5KCJudW1HcmFwaEV4ZWN1dGlvbnMiLFUoMSw1LGkubnVtR3JhcGhFeGVjdXRpb25zJCkpKCJncmFwaEV4ZWN1dGlvbkRhdGEiLFUoMiw3LGkuZ3JhcGhFeGVjdXRpb25EYXRhJCkpKCJncmFwaEV4ZWN1dGlvbkluZGljZXMiLFUoMyw5LGkuZ3JhcGhFeGVjdXRpb25JbmRpY2VzJCkpKCJmb2N1c0luZGV4IixVKDQsMTEsaS5mb2N1c0luZGV4JCkpKCJmb2N1c0lucHV0SW5kaWNlcyIsVSg1LDEzLGkuZm9jdXNJbnB1dEluZGljZXMkKSl9LGRlcGVuZGVuY2llczpbT3JlLEdlXSxlbmNhcHN1bGF0aW9uOjJ9KSxufSkoKTtmdW5jdGlvbiBjQmUobix0KXsxJm4mJihfKDAsInNwYW4iKSxBKDEsIiBPdXRwdXQgIiksdigpKX1mdW5jdGlvbiB1QmUobix0KXsxJm4mJihfKDAsInNwYW4iKSxBKDEsIiBJbnB1dCAiKSx2KCkpfWZ1bmN0aW9uIGRCZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2Iiw2KSgxLCJzcGFuIiw3KSxFKDIsY0JlLDIsMCwic3BhbiIsOCksRSgzLHVCZSwyLDAsInNwYW4iLDgpLHYoKSxBKDQpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygxKSx5KCJuZ1N3aXRjaCIsZS5raW5kKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsImlucHV0IiksQygxKSx5KCJuZ1N3aXRjaENhc2UiLCJjb25zdW1lciIpLEMoMSksamUoIiBzbG90OiAiLGUuc2xvdCwiICIpfX1mdW5jdGlvbiBwQmUobix0KXtpZigxJm4mJihfKDAsImRpdiIsOSksQSgxKSx2KCkpLDImbil7bGV0IGU9UygpO0MoMSksamUoIiAiLGUub3BEYXRhLm9wX3R5cGUsIiAiKX19ZnVuY3Rpb24gaEJlKG4sdCl7MSZuJiYoXygwLCJzcGFuIiwxMCksQSgxLCIgKE9wIGluZm8gdW5hdmFpbGFibGUuKSAiKSx2KCkpfXZhciBmQmU9ZnVuY3Rpb24obil7cmV0dXJuW25dfSxGcmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMub25PcE5hbWVDbGljaz1uZXcgR319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siZ3JhcGgtb3AiXV0saW5wdXRzOntraW5kOiJraW5kIixvcE5hbWU6Im9wTmFtZSIsc2xvdDoic2xvdCIsb3BEYXRhOiJvcERhdGEifSxvdXRwdXRzOntvbk9wTmFtZUNsaWNrOiJvbk9wTmFtZUNsaWNrIn0sZGVjbHM6OSx2YXJzOjcsY29uc3RzOltbMSwib3AtY29udGFpbmVyIl0sWzEsImlucHV0LXRlbnNvci1uYW1lIl0sWzEsIm9wLW5hbWUiLDMsIm5nQ2xhc3MiLCJjbGljayJdLFsiY2xhc3MiLCJzbG90Iiw0LCJuZ0lmIl0sWyJjbGFzcyIsIm9wLXR5cGUiLDQsIm5nSWYiLCJuZ0lmRWxzZSJdLFsib3BJbmZvTWlzc2luZyIsIiJdLFsxLCJzbG90Il0sWzMsIm5nU3dpdGNoIl0sWzQsIm5nU3dpdGNoQ2FzZSJdLFsxLCJvcC10eXBlIl0sWzEsIm9wLWluZm8tbWlzc2luZyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKF8oMCwiYnV0dG9uIiwwKSgxLCJkaXYiLDEpKDIsImJ1dHRvbiIsMiksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25PcE5hbWVDbGljay5lbWl0KHtvcF9uYW1lOmkub3BOYW1lfSl9KSxfKDMsInNwYW4iKSxBKDQpLHYoKSgpLEUoNSxkQmUsNSw0LCJkaXYiLDMpLHYoKSxFKDYscEJlLDIsMSwiZGl2Iiw0KSxFKDcsaEJlLDIsMCwibmctdGVtcGxhdGUiLG51bGwsNSxxdCksdigpKSwyJmUpe2xldCByPSRlKDgpO0MoMikseSgibmdDbGFzcyIsT24oNSxmQmUsInNlbGYiPT09aS5raW5kPyJzZWxmLW9wLW5hbWUiOiIiKSksQygyKSx5dChpLm9wTmFtZSksQygxKSx5KCJuZ0lmIiwic2VsZiIhPT1pLmtpbmQpLEMoMSkseSgibmdJZiIsdm9pZCAwIT09aS5vcERhdGEpKCJuZ0lmRWxzZSIscil9fSxkZXBlbmRlbmNpZXM6W0ZuLEJlLENyLFVyXSxzdHlsZXM6Wycub3AtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXSwgLm9wLW5hbWVbX25nY29udGVudC0lQ09NUCVde2NvbG9yOmluaGVyaXQ7YmFja2dyb3VuZC1jb2xvcjppbmhlcml0fS5vcC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlcjoycHggc29saWQgI2ViZWJlYjtib3JkZXItcmFkaXVzOjRweDtib3gtc2hhZG93OjFweCAzcHggI2VlZTtjdXJzb3I6cG9pbnRlcjttYXJnaW46MCA1cHggMCAwO3BhZGRpbmc6MnB4IDZweDt0ZXh0LWFsaWduOnJpZ2h0O3dpZHRoOjIwMHB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5vcC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAub3AtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXI6MnB4IHNvbGlkICM1NTV9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLm9wLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5vcC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2JveC1zaGFkb3c6MXB4IDNweCAjNzU3NTc1fS5vcC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdOmZvY3Vze291dGxpbmU6MH0ub3AtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXTpob3Zlcntib3JkZXI6MnB4IHNvbGlkICNmZmQzYjJ9Lm9wLWluZm8tbWlzc2luZ1tfbmdjb250ZW50LSVDT01QJV17Y29sb3I6Z3JheX0ub3AtbmFtZVtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyOm5vbmU7Y3Vyc29yOnBvaW50ZXI7ZGlzcGxheTppbmxpbmUtYmxvY2s7b3ZlcmZsb3ctd3JhcDphbnl3aGVyZTtwYWRkaW5nOjA7dGV4dC1hbGlnbjpyaWdodDt0ZXh0LWRlY29yYXRpb246dW5kZXJsaW5lO3doaXRlLXNwYWNlOnByZS13cmFwfS5vcC1uYW1lW19uZ2NvbnRlbnQtJUNPTVAlXTpmb2N1c3tvdXRsaW5lOjB9Lm9wLXR5cGVbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2VjZWZmMTtib3JkZXI6MXB4IHNvbGlkICNlYmViZWI7Ym9yZGVyLXJhZGl1czo0cHg7Zm9udC1mYW1pbHk6IlJvYm90byBNb25vIixtb25vc3BhY2U7Zm9udC1zaXplOjEwcHg7aGVpZ2h0OjE0cHg7bGluZS1oZWlnaHQ6MTRweDtwYWRkaW5nOjFweCAzcHg7d2lkdGg6bWF4LWNvbnRlbnQ7ZGlzcGxheTppbmxpbmUtYmxvY2s7bWFyZ2luLXRvcDozcHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLm9wLXR5cGVbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAub3AtdHlwZVtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyOjFweCBzb2xpZCAjNTU1fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5vcC10eXBlW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLm9wLXR5cGVbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6IzQ1NWE2NH0uc2VsZi1vcC1uYW1lW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXdlaWdodDpib2xkO3RleHQtZGVjb3JhdGlvbjpub25lfS5zbG90W19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjojNjE2MTYxfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5zbG90W19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnNsb3RbX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfSddfSksbn0pKCk7ZnVuY3Rpb24gZ0JlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IiwxMykoMSwiZGl2IiwxNCksQSgyKSx2KCksXygzLCJncmFwaC1vcCIsMTUpLFAoIm9uT3BOYW1lQ2xpY2siLGZ1bmN0aW9uKHIpe29lKGUpO2xldCBvPVMoMyk7cmV0dXJuIHNlKG8ub25HcmFwaE9wTmF2aWdhdGUuZW1pdCh7b3BfbmFtZTpyLm9wX25hbWUsZ3JhcGhfaWQ6by5ncmFwaElkfSkpfSksdigpKCl9aWYoMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPXQuaW5kZXg7QygyKSxqZSgiSW5wdXQgc2xvdCAiLGksIjoiKSxDKDEpLHkoImtpbmQiLCJpbnB1dCIpKCJvcE5hbWUiLGUub3BfbmFtZSkoInNsb3QiLGUub3V0cHV0X3Nsb3QpKCJvcERhdGEiLGUuZGF0YSl9fWZ1bmN0aW9uIF9CZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwxMSkoMSwiZGl2IiksRSgyLGdCZSw0LDUsImRpdiIsMTIpLHYoKSgpKSwyJm4pe2xldCBlPVMoMik7QygyKSx5KCJuZ0Zvck9mIixlLmlucHV0T3BzKX19ZnVuY3Rpb24gdkJlKG4sdCl7MSZuJiYoXygwLCJkaXYiLDE2KSxBKDEsIiAoVGhpcyBvcCBoYXMgbm8gaW5wdXQgdGVuc29yLikgIiksdigpKX1mdW5jdGlvbiB5QmUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDIzKSgxLCJncmFwaC1vcCIsMTUpLFAoIm9uT3BOYW1lQ2xpY2siLGZ1bmN0aW9uKHIpe29lKGUpO2xldCBvPVMoNCk7cmV0dXJuIHNlKG8ub25HcmFwaE9wTmF2aWdhdGUuZW1pdCh7b3BfbmFtZTpyLm9wX25hbWUsZ3JhcGhfaWQ6by5ncmFwaElkfSkpfSksdigpKCl9aWYoMiZuKXtsZXQgZT10LiRpbXBsaWNpdDtDKDEpLHkoImtpbmQiLCJjb25zdW1lciIpKCJvcE5hbWUiLGUub3BfbmFtZSkoInNsb3QiLGUuaW5wdXRfc2xvdCkoIm9wRGF0YSIsZS5kYXRhKX19ZnVuY3Rpb24gYkJlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDE5KSgxLCJkaXYiLDIwKSxBKDIpLF8oMywic3BhbiIpLEFUKDQsMjEpLHYoKSxBKDUsIikgIiksdigpLEUoNix5QmUsMiw0LCJkaXYiLDIyKSx2KCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT10LmluZGV4O0MoMiksWHAoIiBPdXRwdXQgc2xvdCAiLGksIjogKCIsZS5sZW5ndGgsIiAiKSxDKDIpLEt4KGUubGVuZ3RoKSxJVCg0KSxDKDIpLHkoIm5nRm9yT2YiLGUpfX1mdW5jdGlvbiB4QmUobix0KXtpZigxJm4mJihfKDAsImRpdiIsMTcpKDEsImRpdiIpLEUoMixiQmUsNyw0LCJkaXYiLDE4KSx2KCkoKSksMiZuKXtsZXQgZT1TKDIpO0MoMikseSgibmdGb3JPZiIsZS5jb25zdW1lck9wcyl9fWZ1bmN0aW9uIENCZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwyNCksQSgxKSxfKDIsInNwYW4iKSxBVCgzLDI1KSx2KCksQSg0LCIgYW5kIG5vIGNvbnN1bWVyLikgIiksdigpKSwyJm4pe2xldCBlPVMoMik7QygxKSxqZSgiIChUaGlzIG9wIGhhcyAiLGUub3BJbmZvLmNvbnN1bWVycy5sZW5ndGgsIiBvdXRwdXQgIiksQygyKSxLeChlLm9wSW5mby5jb25zdW1lcnMubGVuZ3RoKSxJVCgzKX19ZnVuY3Rpb24gTUJlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IiksRSgxLF9CZSwzLDEsImRpdiIsNCksRSgyLHZCZSwyLDAsIm5nLXRlbXBsYXRlIixudWxsLDUscXQpLF8oNCwiZGl2Iiw2KSg1LCJkaXYiLDcpLEEoNiwiT3A6IiksdigpLF8oNywiZ3JhcGgtb3AiLDgpLFAoIm9uT3BOYW1lQ2xpY2siLGZ1bmN0aW9uKHIpe29lKGUpO2xldCBvPVMoKTtyZXR1cm4gc2Uoby5vbkdyYXBoT3BOYXZpZ2F0ZS5lbWl0KHtvcF9uYW1lOnIub3BfbmFtZSxncmFwaF9pZDpvLmdyYXBoSWR9KSl9KSx2KCkoKSxFKDgseEJlLDMsMSwiZGl2Iiw5KSxFKDksQ0JlLDUsMiwibmctdGVtcGxhdGUiLG51bGwsMTAscXQpLHYoKX1pZigyJm4pe2xldCBlPSRlKDMpLGk9JGUoMTApLHI9UygpO0MoMSkseSgibmdJZiIsci5pbnB1dE9wcy5sZW5ndGg+MCkoIm5nSWZFbHNlIixlKSxDKDYpLHkoImtpbmQiLCJzZWxmIikoIm9wTmFtZSIsci5vcEluZm8ub3BfbmFtZSkoIm9wRGF0YSIsci5vcEluZm8pLEMoMSkseSgibmdJZiIsci50b3RhbE51bUNvbnN1bWVycz4wKSgibmdJZkVsc2UiLGkpfX1mdW5jdGlvbiB3QmUobix0KXsxJm4mJihfKDAsInNwYW4iLDI2KSxBKDEsIiAoT3AgaW5mbyB1bmF2YWlsYWJsZS4pICIpLHYoKSl9ZnVuY3Rpb24gU0JlKG4sdCl7MSZuJiYoXygwLCJkaXYiLDI3KSxBKDEsIiBObyBncmFwaCBvcCBzZWxlY3RlZC4gQ2xpY2sgYSB0ZW5zb3IgbmFtZSBpbiB0aGUgR3JhcGggRXhlY3V0aW9ucyB0YWJsZSB0byB2aWV3IHRoZSBuZWlnaGJvcmhvb2Qgb2YgdGhlIHRlbnNvcidzIG9wIGluIGl0cyBncmFwaC4gIiksdigpKX12YXIgTnJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLm9uR3JhcGhPcE5hdmlnYXRlPW5ldyBHfWdldCBncmFwaElkKCl7cmV0dXJuIHRoaXMub3BJbmZvLmdyYXBoX2lkc1t0aGlzLm9wSW5mby5ncmFwaF9pZHMubGVuZ3RoLTFdfWdldCB0b3RhbE51bUNvbnN1bWVycygpe3JldHVybiB0aGlzLmNvbnN1bWVyT3BzLnJlZHVjZSgoZSxpKT0+ZStpLmxlbmd0aCwwKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siZ3JhcGgtY29tcG9uZW50Il1dLGlucHV0czp7b3BJbmZvOiJvcEluZm8iLGlucHV0T3BzOiJpbnB1dE9wcyIsY29uc3VtZXJPcHM6ImNvbnN1bWVyT3BzIn0sb3V0cHV0czp7b25HcmFwaE9wTmF2aWdhdGU6Im9uR3JhcGhPcE5hdmlnYXRlIn0sZGVjbHM6OSx2YXJzOjIsY29uc3RzOmZ1bmN0aW9uKCl7bGV0IHQsZSxpLHI7cmV0dXJuIHQ9JGxvY2FsaXplYDrikJ9mZTU1ZjliMTkzZWEyMGFhZTViNTYzNWU2OGQ5Mzg2NTAzODQ3NzQ24pCfNDk1NTEzMzc0MDg0MTI5OTg1MTp7VkFSX1BMVVJBTCwgcGx1cmFsLCA9MCB7Y29uc3VtZXJ9ID0xIHtjb25zdW1lcn0gb3RoZXIge2NvbnN1bWVyc319YCx0PVBUKHQse1ZBUl9QTFVSQUw6Ilx1ZmZmZDBcdWZmZmQifSksZT0kbG9jYWxpemVgOuKQn2JhYTQ2MGUyZjJiODU3ZTI2MjkyYjI0NmZjMThhZTBlYTliNWU1MzfikJ81NTU2MzQwMzQzODUwMTY1NTE2OiAke3R9OklDVTpgLGk9JGxvY2FsaXplYDrikJ82YWE3NWY2MjdlMGRjMTYxNTBlZjQ0ODQ2NGUwYzg1N2FhYTBkYzE44pCfNTE1NjcxMjkzNTE1MDU4Njg3ODp7VkFSX1BMVVJBTCwgcGx1cmFsLCA9MCB7dGVuc29yfSA9MSB7dGVuc29yfSBvdGhlciB7dGVuc29yc319YCxpPVBUKGkse1ZBUl9QTFVSQUw6Ilx1ZmZmZDBcdWZmZmQifSkscj0kbG9jYWxpemVgOuKQnzg5MzQ3NmMyYzQyMWNlZTQ3NjYzYzk3MzJmYTQxYTc1MGQzYTczZGbikJ8yNDYwNjcwNTM3MzUxNjI2MzQ6ICR7aX06SUNVOmAsW1sxLCJncmFwaC1zdHJ1Y3R1cmUtY29udGFpbmVyIl0sWzQsIm5nSWYiLCJuZ0lmRWxzZSJdLFsib3BJbmZvTWlzc2luZyIsIiJdLFsibm9PcEZvY3VzZWQiLCIiXSxbImNsYXNzIiwiaW5wdXRzLWNvbnRhaW5lciIsNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJub0lucHV0cyIsIiJdLFsxLCJzZWxmLW9wLWNvbnRhaW5lciJdLFsxLCJzZWxmLW9wLWhlYWRlciJdLFszLCJraW5kIiwib3BOYW1lIiwib3BEYXRhIiwib25PcE5hbWVDbGljayJdLFsiY2xhc3MiLCJjb25zdW1lcnMtY29udGFpbmVyIiw0LCJuZ0lmIiwibmdJZkVsc2UiXSxbIm5vQ29uc3VtZXJzIiwiIl0sWzEsImlucHV0cy1jb250YWluZXIiXSxbImNsYXNzIiwiaW5wdXQtb3Atc2VjdGlvbiIsNCwibmdGb3IiLCJuZ0Zvck9mIl0sWzEsImlucHV0LW9wLXNlY3Rpb24iXSxbMSwiaW5wdXQtc2xvdC1oZWFkZXIiXSxbMywia2luZCIsIm9wTmFtZSIsInNsb3QiLCJvcERhdGEiLCJvbk9wTmFtZUNsaWNrIl0sWzEsImlucHV0cy1jb250YWluZXIiLCJuby1pbnB1dHMtaW5kaWNhdG9yIl0sWzEsImNvbnN1bWVycy1jb250YWluZXIiXSxbImNsYXNzIiwic2xvdC1jb25zdW1lcnMtY29udGFpbmVyIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbMSwic2xvdC1jb25zdW1lcnMtY29udGFpbmVyIl0sWzEsInNsb3QtY29uc3VtZXJzLWhlYWRlciJdLGUsWyJjbGFzcyIsImNvbnN1bWVyLXNlY3Rpb24iLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsxLCJjb25zdW1lci1zZWN0aW9uIl0sWzEsIm9wLWNvbnN1bWVycy1jb250YWluZXIiXSxyLFsxLCJvcC1pbmZvLW1pc3NpbmciXSxbMSwibm8tb3AtZm9jdXNlZCJdXX0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJihfKDAsImRpdiIpKDEsImRpdiIpLEEoMiwiR3JhcGggU3RydWN0dXJlIiksdigpLF8oMywiZGl2IiwwKSxFKDQsTUJlLDExLDcsImRpdiIsMSksdigpLEUoNSx3QmUsMiwwLCJuZy10ZW1wbGF0ZSIsbnVsbCwyLHF0KSxFKDcsU0JlLDIsMCwibmctdGVtcGxhdGUiLG51bGwsMyxxdCksdigpKSwyJmUpe2xldCByPSRlKDgpO0MoNCkseSgibmdJZiIsbnVsbCE9aS5vcEluZm8pKCJuZ0lmRWxzZSIscil9fSxkZXBlbmRlbmNpZXM6W2RuLEJlLEZyZV0sc3R5bGVzOlsnW19uZ2hvc3QtJUNPTVAlXXtvdmVyZmxvdy15OmF1dG99LmNvbnN1bWVycy1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde3BhZGRpbmctYm90dG9tOjVweDtvdmVyZmxvdy14OmF1dG87d2hpdGUtc3BhY2U6bm93cmFwfS5jb25zdW1lci1zZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmJsb2NrO21hcmdpbjo1cHggMH0uZ3JhcGgtc3RydWN0dXJlLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17Zm9udC1zaXplOjEycHg7b3ZlcmZsb3cteTphdXRvO3doaXRlLXNwYWNlOm5vd3JhcH0uaW5wdXRzLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWJvdHRvbToxcHggc29saWQgcmdiYSgwLDAsMCwuMTIpO21hcmdpbi10b3A6NXB4O292ZXJmbG93LXg6YXV0bztwYWRkaW5nLWJvdHRvbTowO3doaXRlLXNwYWNlOm5vd3JhcH0uaW5wdXQtb3Atc2VjdGlvbltfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLXJpZ2h0OjFweCBzb2xpZCByZ2JhKDAsMCwwLC4xMik7ZGlzcGxheTppbmxpbmUtYmxvY2s7bWFyZ2luLXJpZ2h0OjVweDtwYWRkaW5nLWJvdHRvbTo1cHh9LmlucHV0LXNsb3QtaGVhZGVyW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNmZmYwOTk7bWFyZ2luLWJvdHRvbTo1cHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmlucHV0LXNsb3QtaGVhZGVyW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmlucHV0LXNsb3QtaGVhZGVyW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNlNjUxMDB9LmlucHV0LXRlbnNvci1uYW1lW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmJsb2NrO3doaXRlLXNwYWNlOm5vd3JhcH0ubm8tb3AtZm9jdXNlZFtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6Z3JheTtmb250LWZhbWlseToiUm9ib3RvIixBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtmb250LXNpemU6MTNweDt3aGl0ZS1zcGFjZTpub3JtYWx9LnNlbGYtb3AtaGVhZGVyW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXdlaWdodDpib2xkO21hcmdpbi1ib3R0b206NXB4fS5zZWxmLW9wLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWJvdHRvbToxcHggc29saWQgcmdiYSgwLDAsMCwuMTIpO3BhZGRpbmctYm90dG9tOjVweH0uc2xvdC1jb25zdW1lcnMtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItcmlnaHQ6MXB4IHNvbGlkIHJnYmEoMCwwLDAsLjEyKTtkaXNwbGF5OmlubGluZS1ibG9jazttYXJnaW4tcmlnaHQ6NXB4O3BhZGRpbmctdG9wOjVweDt2ZXJ0aWNhbC1hbGlnbjp0b3B9LnNsb3QtY29uc3VtZXJzLWhlYWRlcltfbmdjb250ZW50LSVDT01QJV17d2hpdGUtc3BhY2U6bm93cmFwfSddLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksTHJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMub3BJbmZvJD10aGlzLnN0b3JlLnBpcGUodnQobFUpKSx0aGlzLmlucHV0T3BzJD10aGlzLnN0b3JlLnBpcGUodnQoY1UpKSx0aGlzLmNvbnN1bWVyT3BzJD10aGlzLnN0b3JlLnBpcGUodnQoQ3JlKSl9b25HcmFwaE9wTmF2aWdhdGUoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChadihlKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ0Zi1kZWJ1Z2dlci12Mi1ncmFwaCJdXSxkZWNsczo0LHZhcnM6OSxjb25zdHM6W1szLCJvcEluZm8iLCJpbnB1dE9wcyIsImNvbnN1bWVyT3BzIiwib25HcmFwaE9wTmF2aWdhdGUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImdyYXBoLWNvbXBvbmVudCIsMCksUCgib25HcmFwaE9wTmF2aWdhdGUiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uR3JhcGhPcE5hdmlnYXRlKG8pfSksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIiksdigpKSwyJmUmJnkoIm9wSW5mbyIsVSgxLDMsaS5vcEluZm8kKSkoImlucHV0T3BzIixVKDIsNSxpLmlucHV0T3BzJCkpKCJjb25zdW1lck9wcyIsVSgzLDcsaS5jb25zdW1lck9wcyQpKX0sZGVwZW5kZW5jaWVzOltOcmUsR2VdLGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpLEJyZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJpbmFjdGl2ZS1jb21wb25lbnQiXV0sZGVjbHM6NTQsdmFyczowLGNvbnN0czpbWzEsImNvbnRhaW5lciJdLFsxLCJ0aXRsZSJdLFsxLCJjb2RlIl0sWzEsImFyZyJdLFsxLCJleGhpYml0cy1jb250YWluZXIiXSxbMSwiZXhoaWJpdCJdLFsxLCJzY3JlZW5zaG90Il0sWyJzcmMiLCJkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQVRZQUFBRTJDQVlBQUFEcnZMNnBBQUFveUhwVVdIUlNZWGNnY0hKdlptbHNaU0IwZVhCbElHVjRhV1lBQUhqYXJaeHBkaHk1Y29YL1l4VmVBdVpoT1JnQzUzZ0hYcjYvaTJKcmVtcXJuKzFXaTZTS3hVd2tFSEdIUUlETy91cy9yL3NQL2h1dFpaZEw2M1hVNnZrdmp6emk1SXZ1UC8rTjl6SDQvRDYrLzFMOStpcjgvTHI3OW8zSTU2UjNmcjdSNXVkem1MeGV2di9BWC9jSTYrZlhYZi82VHV4ZkYvcjZCaGYrakVCMzF0Zm54MEh5ZXZ5OEh2TFhoWVo5dnFpanR4K0h1cjR1dEwvZStJYnk5VGQvRzlibmsvN3RmbnFoTVV1bmNLTVVvNldRL1B1WVB5TkluNytUdisxOWpMd3Y4TnBNS1FYMytmUjFNU2JrcDhmNzY3UDNQMDdRVDVQODExZnUxOW4vOXRVdmt4L24xK3ZwbDdtc1gzUGs2KysvRWNvdnI2ZHZ0NGsvaGNPM0VjV2Z2M0Y3elAveU9GOS83ejM5WHZzODNjeVZHYTFmRWZVbSs2OFowaHNYVTU3ZWoxWCtOUDRXdm03dnorQlA5OU52bHZ6NDdSZC9kaGdoc2lyWGhSeE9tT0VHZTU5MzJBd3hSNHVOenpIdW1ONXJQYlU0NGs1YXA2dy80Y2FXUmpxcHMyNDdtbVBOY29yZnhoTGVmY2U3M3c2ZE81L0FXMlBnWWxycXYvM2ovcWR2L2p0LzNMMWJVeFI4L3paWGpDc3FyaG1HVms0ZmVSZExFTzdYdXBVM3dYLzkrVnArLzBQOEVLcXNZSG5UM0huQTZkZm5FcXVFNzdHVjNqb24zbGY0L0ZuajROcjV1Z0JUeEwwTGd3bUpGZkExcEJKcThDM0dGZ0x6MkZtZ3ljaGp5bkd4QXFHVWVCaGt6Q25WNkZyc1VmZm1aMXA0NzQwbDFxaVh3U1lXb3FSS2JuVldhTEpZT1JmaXArVk9ETTJTU2k2bDFOSktkMldVV1ZQTnRkUmFXeFhJelpaYWJxWFYxbHB2bzgyZWV1NmwxOTU2NzZQUEVVY0NBOHVvbzQwK3hwZ3p1c21OSnRlYXZIL3l5b29ycmJ6S3FxdXR2c2FhbS9EWmVaZGRkOXQ5anoxUFBPa0FFNmVlZHZvWloxcHdCbEpZdG1MVm1uVWJOaSt4ZHRQTnQ5eDYyKzEzM1BsdDFiNVc5Vi8rL0J1ckZyNVdMYjZWMHZ2YXQxWGpWZGZhWDVjSWdwT2lOV1BGWWc2c2VOTUtFTkJSYStaN3lEbHE1YlJtZmtTU29rUUdXYlEyN2dTdEdFdVlMY1J5dzdlMSs3NXkvMmpkWE9uL2FOM2luMWJPYWVuK1AxYk9zWFQvdW02L1diVWpudHR2eFQ1WnFEbjFpZXk3Wlp5NDNJNXgxVExudEpsYVMyWHVGWE1MYVplUVd6MjFybnFCazl2UEx0M0h4cmU2WjVZUG5NTWtITDl5UGFIb1FyMm1mT1B4RTNoZnBkMDEwMkZwazU2eU00NjlUcjY1MzFVWUxaY0cvODQ4TzdSajNMU3N1RmRZNXRvMG5pdWxmWG5rbHZJK25xUXNCd0JkZlc4bWRkOFdvbStzWk9vOTU1Nys0b1lrS0w5bG5qWmpKeUQ1T0hWem02V3VXYzVjWlplVEp5bnZ6Zk80MlljNlJvOFJyU0dTUG5sTVMyM25jUW0xZkkvQ3hhVWVUaTN3ZGRzZ3d0cDM3YnB1eU1YYVliSnJ2MVlBN3VYVGFjYlQ1ZERhM0t6RENtMHlmNVV4TTMzYjVRazloRjFuNGFaM1NSU0VzMjhmeldJNlZtZUl5V3hzNDFxK3JoN1dPQzBhVnpEaXVLL0dQQkVRd1NtSkNPbit4RVJhWTk3TTBFYmJ2aHppdG5heW9MR0VQTUpzdTFwaWtRZmdOY3RsNlhzSTF0Zk5saDF6M0c4b2RwS0Z3Y01odXk2UDRsRVRRT0h1TTQyeTdCN05TQ0lPVDdwOVh5WS9IT1gyUWlLMWZSWnoxSFlFT1hpWWZrck4rYVJsdS9oVmZSK1JtR1J0eVRJN25abW9pY2c3L3VaeUQ4RzlEems2RW9Ob3NhR1BhZ3g1WlJLTmNZWjhXem43aUF2UDliUFpLR1lrSnZjTlBORDFuWWMrUENSd1BZMVF5SER0YXRYYzZyWEN4SmRKYlpPUk1oZVZkTEIrYTB1Um14SVJoY2xrMk9lMGJvY3c3aXlFSitaelZNRHlnZitkeE4yWWVzcnJyL1hUeU5pOVdwbjU5TTAwM1dPTkJTWVZFdmRWNkJhdUhQWktERE5XR3hWWUtiZTRXRWNTY0d3K0U2RWt4c25KMEJNOXRCS1l0NkFiVnoxT0dndWdJZmZ2SEVUNkpGb0w0d1Fjem1DeUs4eldTS01haHdmSHlHR0drcVlHRU5ZQlRuT0RqTXRjWnhGWTJiWVZjQU9RSTJJaHQ3dU5wU1JGOWtSQ2RDWVlQT1RyR3lBL1ZyY3VZbUhjWGErRm5SbHlPWW5adFZEUzNqd09vZHB0U2ZBa3dVOHlOeTZaMVhqQUthQll1Wit3aGJVa0ZBaGFBL1JKRnZvK1FRVmJBekN1YlNjRm95ZGtKcE4vYmlIbUhJSE90d3VodG5kT2UwbUV3UWZMRzhoUjB3Z2xLeFJKMjJKeEUxQ0RmTjRUZXJkYndFREd5UFZXZERzdEFHTzJDOExiNkMvbVJnUExtSmg0Z0tyV3p5U21nZjRGRmxmd2o1Y0pVRkRrS2t3T2tYUG5jWTFaSmdLUEVWYWdHNm5Rb0pTSWRrc3hkOFZRTzFteStTNGttUzJGeEJZRVZOWXVseVFrWTVGUS9pRU1iNmNRanVPc2t4SmlsRmdETW96dzhLWUJHVkU2UmhpeDFoSTl0MjlrWHlkS1lqeXpMbFloSTJ1WXlwR0NnZU0xQVhmZUd5QTA4OU9rcUp0TWVtZjBpMXdITUY2K2YyNGtKTWhGaEVYUWlqZ0NIYkVPNUJ2OHVDLzVJbUlsQmdPTUNtb0NnMlA3VGFpQjNBRUFCVnRSbUpNRkppU3pxR0hBS2E3dUFVT1BCcHAwQm1TUTRnVU5JUk5mWVAxRlZOemMvRDRBZDBHZW9vMHJVdTBDWVJBQlR5cVd1Mms0VUJUdHRYaFdJd1JOckFzeERqSjNyZDFYN2NJS2hXdVRLU0FTZVNCbUgwaVl5dFdOc0FhaGhybWpsU1dWV0NMejJ6SkVPc1k5RlE2cnd2YmViVjhpa0V1a3NsbHlMTkNOc3phQTZLWUozeTJMQWN5MnVSZzB5WmhXbXVpVXhoVUVBTFZaSWxRUGNNVFZHelJQc3ZMb214Q2NGeEFhcldSQ3d0YTFDRW02VFBnUXV4aS9qYTZ2K0FBZUdzUWlxdzE4SXRwWVIwSmVPQ3dXQVVsTFl1cjJLc01JMG80bklOWE41VFNZK2h4WjlYUDVGcGRjQ2xlZ3NFUEhZR0poNFVqMlVsWWcvTnJsR25Nc0ptTWpWTGhaWUVYVGxtSURGVm1XeEpTam96QU94QmNxMllQbFVVNDNONUhVL1lhRnYvL3M5RVVHdGtzbWFyb0FkTzhMQlpFRVp5SWxka0pROVRCclJSOGNVcmJJWG9NUE1rM01TZ096Wm9GRjdCSVhGekZZR3FOWnJCeDZ5TE5lZ0VzaW4vcUd1SUt1amh6UEhUbXlSSENFQnJsbUVqUjhlOVRsa09kbmxhdFpQYXVud3dmam5kdzhJMW9XZEFiN295TWs0OUJlcVhIUEdNbGxFbU9VUnNBTU5FdzM1cWhBbndCeU9jZzhrRE9UbTJ2NFl3VWhnS0hJQktVRnBDRFBDL3F4dFA1NlVHUnNZTDhGQ1E4Z0xUbUlucDh4S1pBdWRNQ0k4c09YZkFPb0FMazhEZytWemphV3AwcEhuQWVSNkNsbTVhVFppNVhURjltL3oxN0VKMW1NY29sNUF4MU1KZUFPd2NxOTRCMlJQUWdKUWhqVUh1Z1ZwQnlNZEFXNEFHV1VPSFB0amdJOC8vS3lYbTNLQXd4dzJRTlFRaHFRTjhGU1gvbm9FYkdXNVdBOTBhN01yWGNMVzJWb1NpSXlDZWt2ckgrVHhLb0V6ZzFpMTBaVzJEcEc4Z3pZZDNkNEkyOG1nWFVoY3FSZGowTmlpWGVRQm1RK1M1b0JxMVNRQ1BQRnZNSUV6RzVGb3JTUitJaEc1QndpT3l0bUU3Q0FySjg3T1RpSU5ZUmx4Szl6MzVtWTk3b1dJZ2ZvWmthZ01TN000SUFKY0M0ZG5wbUJjb3VMNllkSHR0ZWt1Q0JzcXVlQUpPUTJxeW54REhzc3pBRFlUSERCQndobkhwVXZDSWNObVBZSjgyRUlnaEl6U053dmg2Mi9xRnN3aGFlVGtDSTBZQ3hTSFd5WCtyOURUbUtCVGowYjhFQTRzKzRzQ3graGM1K1JhWHZESW9oaFk3cTE1SHhqNEpwS1lXYUd5VUVVeURxc1JreDE2NTNGdU1JV3hYVkZ6RUdaQ0kyVEpYaGRRaHpBVzRDcHVBSHlaUlJvTkFZTC9SZ1AyNUVSZ1hrbzhHZmVpbStVUTBmQUxjS1NqT0NCUWJMdDluN2dhdkNUc2ZEY25qUzE5QVpWaUhuTUZvTWY4eFJCR3FnNUdCRGZnK2w2dzAxSndtNzB1MXRnV0ZXZ1hDUnpKdjJSTlEyR0o5WW5pTG13NTBSckpraU15UVZwMG9KS2tUUXFzczBuSm9ZbTNCVUYvRUM5Nk1ZRVNVTkc4MHp4elJEQWpHdG9PTHVxZFYrRTJFbGQ2b0g1S0tyVGxmRDEyWVZmWHZqMU04OEJrWnNuQ2xqeXhtSXlVRkszeXRvTkxROHFzVStFMXNCNElGZzJ1aDgrUkJJaEQ4a2VSRGh2d1MwdUZBVDJVK2FwaXE4Tm8xZ0RWZ1hycGVVRE0yRkljMENleDlDMHArUEJMNklSVGE0TStkMnJLeWRFeW54Z2pSMENia0VhTUFkVnl3dUpyTHhkczQ3c3hYemlVZ2xtRm40bHdCazdBZEhqYUpzUGJTMnV3R0FOeG9CZTBhR1FGN0JHSEhrVW9pY3hUd2ZSVkVFQkFJb2l2NHBGaWZEdEt3NG40UzBHRTl3SmRWZ1JiRHRuczhCNGlhVEtRSE1YSllJVzZ2SXNUSzdoaXVEQ25RRkpKZ3k5aDdjR1MwRzV4RnZKdnFhRWgzSW1TYmRJbWFxVWFNMXgzNHJjUVBmaUk5Q0h2VWFGanczbXNoS0FneGhINXNIT0FEazBFRlBBcVI1VmhkZlgwdkp4WmdlTUVpNmJxU01mRVM5ZHc3RWI1Q01sUjdrZDdEUElWN0lCRzd0RUNaZndHZzBGSEFjVGZnakE3YkErdDZKTUk0eHRrK25FVGx5L0lJMkRsMDFaTXIwVTRuVURuU2o5UFVIOUtqa1A5VndFSmk1aGdHK090NkFZS3ZpQmJDRE1NdFFtZHN6a3V5b3RkK0V2c1g1UU1JbVA2aVg0V1pLTzlRZWxPMkFaOFR1NU9HdzVVQXloK1FaUmpndVhiaUJOZEVsK0d3NktDNDFFa205aU9aQTgxYTlSMFp5NExpaEFoVzFXRm9Ja3NSdkUreWkxd1Zrek4yUmpCdCt3dGdTU0tndTZHM0d5QWVPN2hDbEhWb2hib1lhaWJKQk5Sd0EyNW83Y1JsZVN1MWMyaGNWbFpnRmcxcDB4bU9TMm5ad05hZ0dJV0JYUUVpV0ZZNjdnc0RMWlRUQzhOVlZCeUg5dUxXRjN5SHdNZlZSZDEwZklsREJEQmFhUzVsQlEvZTdtempRMndMWkMxNzhaMnovK3J2c2Z2bzNnSFppM3lkUlVLTHMyU1JDTWpPRkV2Q3BEeU1rNFR3RHplblRvNEh0NXRCVjVOdWdhVE45OFo0bTI4WWZwQ1dKc2Y4OGtmU2pJNmdUK0lrVENTSUF5dXJic0J0ZzVlRE5sL0dWdHdXcTFEWlYxZ2crZGpoQ3p3enlwTm9OMFFiNStnTFhVdnZRVjJnWDJSVmt0ZVV5RVZwbXFRbnZCSjgrR2ljZCs0VHdidW5RVDJ3RDVCV3R5UkE4TlZCdlBReURqSTlBYXI4YUFSV0FzanR4dkNFVjRFRy9qc1JGSFFrUUNFK2VoMFYvQktrbERYQlhRTU9GWFVWcGdGbVNuWWtadnFtNzhia1NBZEVTbkk2VEp4YmhOUG5ManBnMnNCSmNiTW1nOU0wOWlOenkyN2NCRkhYWlU4Z1I5eDNTcmFzRWlCbGJ3eFNYNEpXMFhrQ25rZUltSXhiTEZIK1RFNUNtWktXVkZHc2NqajNFdTRBZ0RJWUxSMkNVT3BBL0tESzJMWXVZOWV5cXVxeTM0NEY1TEI4T2xTN3lQY1dMSTdpMUVkZ0cxZmxWck9LbUdqTzhwUVdtdjJuaUlOeGd4NDdUUVF3dnpYUHd3SDZKUWlvczQzQis0K0Q3Z0JQQkk2WE5Scm9DSUdmS29xUkljaEVnNGlRQmgvWUpsblBUS0VCbFNDSFlES2h4a2tvVEdqM2hPd1d5UlN3Z3ozbTg1U0JrZkNGQWVoVnMycG9tWHFrejlRRUExK0FJS0Jmek5YY2djalRraE1xOEtDbjRra2hoQVJwWHJEUEM4eUJ5OHhBRk1aQmJPWkpjcWY0ZHd3NWloQ3RFaEpDM3hDUVpQUm92YjUyYUFPdmZuN2ZCSzBWY0JqVkZ3UEoybkl3NXNWN1FYREVKQUVFRW92Z2h5NG1tTk1iSlM0YXJZU3JxcS9uc21TNFg1Z3g4TGVuNkFIN3RNQldiZ3NmZFc5UlBraHNrTmtMdzVZdGNCSmN5MmFxd05UMEd1SGRZRHBUVnhtc3dmQUZRQitva3hJUUx3NDdWMjhKTTF4REdlY0ZGM0dlck95Y1dBQk9NdWlHZ0VFS3BxeUptUWF3Tks2VU9LR21JakVyYVBlRTlHdmJoQ1hUbDNNcEoxTkluZmlvaEEyeTFrUEs0UitZSU00NTFRVHVYZkNIT1ZFWkprUEpQVVdkSUxNR1JCckh5cjdBVjZIVTY0clRwWkttd3ZWSE1TYkhleGVndWJuVU92U09uRXh6RVFZeXQ2OFpMSENTQUxpVytDQkFWTWpzWVpuczZXamJ2SUFVTVdjR015cXdrSGtRZ2VFaXFxdmZDb3dQaVJlY0pUZGVVYmdnTGlPb1FJeVMzVFZGM1FzN0FpdmpPZnM5L1NvbTI0QzBzYUlZZUxvbXpuVlVISEVEa3o1cXg2K2V4NUF4ZXFRL2haZ1pGd3ROa2grY2c2VEdOdFRTVmY4TnkvRWgyb2craERQYitxSnZZblFNdkdjckVFUTdWNEx5ZTl0aHU0YnB4RHdBMmdWVkJCWHBWbk12L2pKMVhyQjgyRFp5RklYaS9CZUJMdUJUV0hZcS9JZWlRVW93TDh4ZGpucUp5QWRBdEYrUUZRc2taalNDVkRyVWkzaEhLekRJRmdzanpDbzhyd2t6M2pxdFRIdUYzRGlXT0U4YXhZSHV5b1NJSlVWSFg1N3JWQVE2MzlVQjFzVE92b1NEakdCazcrNjR2TnRVODBnSTJIR3VoQXhOclZac0VKMG1QTUlCR0tDR3h5cGZkZ0xTN1B0VU9kcWdocHg4WmpvVlIzTjJUaHJLNUNZK09CQ0xvMXZ5LzFSU0V5RGNrVytEZmpCKzlJamZqQWxTOUJ1bDRFbmJ4aTVTR1lReGVHOTdXK3FDeVc5dmFFa3IrOStFOC8vZTFIQmJ2dTY2ZGJUS28rQkZSeHJkdHZaQkI4cHZJTU1ZazZSR2FHcSsyZEZqc3FUb1Zuckkrc1hSZlYxNGlGR0JGenF0MGU2ZE5LbHRqV3JvQkM4RXJOR1FCenVEczBiM0QwMWc1RU9wWHBxcXJONExRclYzZmlDb0tpbzN1Z1JrZ1UwVG9BYzlJclJCRlpzaXpQZ3pNVzh4VFZLckEya0k1OEhzYTZFNWtXcjBzSnNBaHJxQm9DdjBraEpIU1dhb20vRzFsNFN2UG9WY1VKV1h1RkpnRVlHZkJVeE1aZmtoK2ZtOFl6MG9oNExBaGNva0pKUFdtVEVQQ3oxMjZxYW8zWmd5YUVRQnl3ekNJRW5mYTJ0MHBSOEF5YVBMM1NrWFF5Y3JxWG15S1lseU93d3Bmb1k3NUdPYUpBbURFWXZnRmRMeVN1dzk1RldFRlZ0Z3lLeGF6QkxQVDhsbWJFNlNIclMwaGVWWFNnSFltZXNRWXJJYWpKNXZGWVZzYkwvWTZFWVpwNHh1YUtlUkVtYUNaQm55bDg4R2FrWXpEdWM4Nkk4UENBWEJHVmJuaG1LaWc5cXh5em5CbUFQQkhqcUZrVG1zQjVWc2lKMXlReVpjWmhPU2dURUlBV3lIUVZmeG93d2t3RFdFQTBzdTRtd0lYVVRIUENtb0N0cXJSa2tyZ0tMREk1R1dBVHhzdFBocU9YVlpCQzdUaXNRWUIrTjZrRVpSdExLaFJFUnFPL0ZvTWxnUUU4bnBYd3cvc3hOaWdROXE0cXl1d2xtRVltWm5tUkhzbDlNa2RDUytVVnhMMzJFdTYrTUNMM2JDUUhJUWoxUWFYNGhBNXVwSkx4VUVIaUY0YXV6SzAyNmd4RnpqS0RnN1djakRNbjQ3UlpodHNrK3ZGM0NQMkw1Z0NwNFoybG5RazBIaUZmTVREY3E0b3FYY1owcXV3bnZRSTlWTmdYbGtsaHk2RmNaRE9HK3c2YnB5RC9zTHBIbXcvWXlDQ3IyN1kyK0haUCt5TFlJME1IdUNkaWhBdGdFd25aQ1Y1V3hBaEJLS09YQ3BGYVlvRmlYM0tRaXVRL09ZS2VaUVdRUE9wQVdOeDg1WXZiM3VvNVdOSVRnMEY2bVpJd2lxbzFoUWdKU3htTnQwSldGdEV2Y2hUV2tyUHlpMXdEMUhlOGVtTkVSblZ0MjE3cHE0a01aaDRZRXJvZmhCeWw5NlpLVmRjbU1oSUk1UTBxSTV6OHdXQTRlVjYwQzdPWjRNb3J6VVRLemp1Snk1MmFzT1lFN1J3aVJyVEZGQUxFS0psZVZVeU9qY1dmUkNaTVN3NlNNUVBnREYrN2NtSFBYM2NjL3Z6Wi9jMDNYbUhTRTV3L1NOMnZjdVV2cjZwSGpDZHlxa3lrbExDV2MyenNYY0tHSTduZ1FwQURhM0VhR05xVGdsSklBcC9Bc1FOTlVtUFR0cit4Rmp3Kzh2Z1FGUjFRZ05HeTdHWUNRbFJneTB3U3JwKzhnZUlJRER5c29UcDIyRThjVzBmanNjNE5FRzU0ZlRmbFdyVm5KamRWdER0ZEQ3SkxHYWlFdmQ2eWpKc0I3Smloc0hidkdkT2pLaG1rc3lDVEtOVStISFN2NG1Rd3dHbStUWCt5dFdFak5obUpvb0Z4MWdzMWZGR1Q3dUhua05mSnczS2RJR1ZjbUQwdWhNYmNJaWhNaUJuV2E4bUtTcVdOeXVPQm1WZktIQjJETnNaVHR6V0lrUnJYMVA0S3VLeFNMZmVPcmszeUdXbU8zMVB4a2ZqUk5oekQ1ejRCTHdwbUl6dTFWY2ZQQUgycURRTkhRTmxSdXd4UWMzS0pIcWdGY2VHbHF0MlUzaGx3V0RGQmZIaEcxSWdLOXlTanRPd2JBZ21PaDJNMlVMc2tMRlFKaUYvRk9ibDJtVlRVSVl6Ynh1RXlUTE5YcHFQRlZKQVBTdTVXNE9CbHFwS1JGZHJTYWRwSkNVYzRnaWk3M1NuQU1NQ0d4c0dPNTFLODRhb1hEOGZ6NTNhczdQSElTRnhZVkZCU0ZXaEZuaHRHeFpCbU1nVzhkK2lKZ1UwR0I0SGF1ckZmQUFQTXBuSWZRczJQaVhzYVVBYlVyRktBcWNoVWJ4bDNlaFUzVkF3MWRMc2pFTXRaU0VVaUorTVN4emI4T2pQVUNJUEpkR093eXArS2xYeDJmM3JEKzV6YlUwRS9FVno4K1ZYM2VUbXJDd0UxTE13SFNHUzNlVms4OHNjODQvTWJFZHIwZEw1b2llQWVpSm5iU1pwVG1TQkFpSHlwUS9VbVZvMmxlYzBPc29tZ2N1U3hRUzRpdmlnVUhTcS9Cay9VUnVJWkE4Q2l3UUdtYlpFSWhWbzVvbHl1dDBsc0VCQlhTSlRXeVcwcnQ4UWNUZlN6YmFmUzFsRlR5V2lWY0ZSUkNXN1dQdWtjUWxHWWtyUVA1RXdJNlBrQkFJajE0R21HaGp4QVNFT3VDekVLd2o1ZmpRcktoUWtpNmswYmhUdy9kQTZwUUp2RWc5NVRDVDZVTk0rTFdVcnlIS2ovaGhRblRSekFqT0pDWkdRWWE2bUtmREdKQUJONmZXdFBFZk84a3UyQ0xrRTlNT2NnUTVCcFNWUGtXREVMclk2dHpRT3k1THlaNTVIOXY0L1duOC91LzRyV24ralFoVjZBSU41ZWlCRDd2NG1tRGZlZUViQnYyQlkrYS9EYUdwb3p2NjRlZEs1MzBoandONFltTE1nV2FZcHJrTy9ydUpWVSt1enBaSytleWJtOGR1bXk5aENLWkRvNm5pa2g1VHI2d3FuSkROZ1p1QlQxVFhqVjdHQVhkV0FZd3Z1bzFZREFBeW40dVlsbkRiMUwzbFFWWVZHQy9hSWJOeFlDMkxQWCtZQXhSS1BhUmUwbGhMbEt2OU1EUmg2VTJFVzlVb0FBeHV5cWsyR3MwaHUyWjlaU0ZwTlIwVWNzT0pNV3NXVEF3bGpNUUZON0JSSitxV21reTZXaUFmak9QZ1Q0eEFWbStCb0l4b2JoNDlDVENQdnV3QmNHcHgzcUhMc3VMcS9aMVFnSm10WE5jc0ZhRFh2S2FuQVhWUlhVMHptMVNkQlU1c1hUSTBtU3d6S2poMC90WGIwNm5WREV0dkxvVU1aUkZZaEhyMWVsdmZESzR2QnZVTCtOak93Wmw4bEVBNnBMeG1ISTc5dTJld1Z4K2JKanIzcXZIV3ZFSWFsTDhpQjlHMUJ2dGNicFZRZGZvQUorQWZyN0NrRDN2dzdsbnlOWVl2UTNJZnhMeGUySEVQNFI0SWdUTUxJaFhtdFJpMTZPK0dQOGVDeUw5Y1pLVjVRdkQ0OVZxN2FySERpQzBCUEJYcVVjbkVCQ0E3SUEwRm1NV0FodGwzT2hzMnRsUFZ2SGZLcFhFZ0VhdWpvc0pyb1kwdUUyUFZlVlptQk5FQWpFUzB1WFpBV1hDaHNwNVJyVU10eng5QW9kbUNoT1JPWUdhSTh3Vjl4VlFHQitKRGVwL0ppdmpGaHFUTUNEZlJ6SDNSSVdNejJDSEtnWVRRVzhoVGhPRlFXMHNHVG9Eeit4eWwwTmZhQmNVaytiL2k0UURuN1Vaa2RoTlJIU1l6Z1ZHRGZ6K3ZiYTBVZmhVNlhFREJiKzc5cHpqM0hYcmRSVnFWY0RrTFNlNGg2aUdyRXd3SURBWk1QUFVIcDdaU3A4UllqU1hnSmw4bE43bnR5bG81SDBzNEM4dXFud2ZKZ1dGYXR0cVAyTlZHcGdkbjZkWXZBeXdrcjlrdHEzU3lKbmlBT1JvSjJEQTkxalFmRVRKS1J5UXJZOVk3MTRYbEQ0WXAzZFpMVm11bjZwbFEybHN4VHoxWU1HNnJVaXNDN1NYcDE3dnF1dmkxRUFhVW5tVUFvR3hjNlRzL0xaZVF6SzNsUGNienEzRUNmbUZPL2VGay96UXdyOEtjTGRQMDJCUDJXQSs2NjVKekZBa0JiMWRCZ3FHWGFDdHZkRDlMWXdITkFpR01lSG91YlZYNkRkL1F1MkEzU1laZ1QweHVnRm1lMEE2azBZTDJybkhpU0RIOHBTR0dzbi9WV2RtV1duTWlnc2dEZ3ZTZDJDVzVvV1RReG9FdnBGRUJkR1pPSDUzMlFFTW9EZlZIb2xKcXdrTlZXbjFZZER6a0VwRXYvMXFwemVFWUpqYWRkaEVwblZ0NkZPeTJxM1JJWGNBUm1uOW82STJ5RXNaOXl2NThnVmJRL2tHbGxwaG5ZelNaS2x1NWNoR2dyM0s0WG5BUno1Qm1HNk10azJjTll3aVhaUXlBaThPQ0RndUFwRVB0Uk9jTkRPZlRMUlJPQmMrTHJJRFhpQ0pIMGFkSExnempDWmFyQy9nOHIxWEJnc1pneGpQbTVuOWZpTk96TGVEOEhEaTY5ZlM1VkV5R0U4ejRwMXFRbkIzOVh3ZVJQTElRSFA4aFhiMEoxNk5Sd2pWL0Z0bWtwcGlIMVNLbDMxY1FIdnh5Y216QWg0RldiZzRueW55ZzdFK1ZCaGJEUVB3aStHYjhBSXBrZm9yaklENlExV29heUREaFBjWHRSQWhzSTI2V3Z0cDVkWkEwNFZGY2VqcVg5cGtDcHEwNTdOWmRidTlsZnh2cjNKREtzWGlKd3BhZ0dNMmlWSTJrSks1b1dRaDRmMDhBNW9CYVdwQ1dQQzZBQ25xd2hUVm8vcGhtTUJ3U1VkcGRNMVVUdmNYZlg2Qm9ZdmhDU2VBb1NlYzN6MjNIdFZuWFIxdENwcjRuRDBtRFVjRUVhNzR0WEIxalQ3dDQzNGYvelovYzAzL25FSFFQY1JWUkVXVEt1ZThDSExrclNCMVpCQlZhVWlnSzBBS09SNCtvV2hnRHJkOExGSjdFeGZxQU5abzIvcWNBbkN5cXVpRnIyYWtoSEtSSEx5YWlsUjQycEdqZUZhTUhkb0tCd2RZbDRkSE9GRDdUZ3RKKzljQitIaXRWbU85OFIydC9xcVZNZ1BOV3pPOWJ4OFhnZ1J3Y0x4T0RiQUNwOS9rQ0E0Y2lJNTRpQ3pmLzRZZUpycUF0N0k0bDdVMXhZeUQ2ditYM2dMZ3RLbXFUUUxrU28ySlpFSjhTNjZ6a3daNWxpbm4xajhya000TllsUWg3WWxWaW5pKzVxMzZwMktmdlNhUWZiaHFLcll5SjVkeTJjWElkZmt1RW1CVVB2Q2VNdktYVzNZc0JMWWZ1WTlJSGFPT3V5Z0FqWExRWmx4bjAvSEYzQ0djaWQvOFR6WnUyenc1Rm95SityZ1hjMTRaMVE1d1I2cDZGaktWSXNoZHA4MVNETENaS0dGdlhielF5MXorRXVmbkh5a1IvaDkrcjlSV2xtTjhpZ09pMVBaUnFaM2JhdFVCYzRlTEtpNkwwRTFra0FkTzlBWkNCdTNrOUtOeld0N3E2azNsOHhnbXRXbEhkUXVodU80QVdTOEltSXNqcW0rRGtIa3JGdWdUQmVhMHplYnp2ZlhMWEswY3VvTnEvOVdZbnovN1A1dmlmRTlMOXkzeEFqNFJPUS9EaW1EbUFpVEVWNERJbDYzditSSW1ESDVaZE9STWVTWnJuZWUxOU0vbklEQU5yNERhUUw5cTF1TURBdEw0cWwrZGhqZVA4UnJCbkkyWENDejFpUUxwdHBNa0NERWZuTmVMZXNJeXNKdE85eDJjWXhSTzlITWNvM3F0Y2NxNDM0YWNZUGdZbUlEWkJRR0xNRmRXU2h0ditDRUFUWjdIZW5ZR3BCZDUxOUFhUlI4S3dqRFlRdUZBZmJtZ2xHRzc1TzBxMDQ4cU85eXFYWUdHdXN3eVhSWHgwUkl1NkJHV2ZqYkFxRjhjTVFXa0FUcTQ5ZXVkb1pCc1RQY1BodnVOSlBVR1YrN3RXSHBwemIrSGU3RTk1bmdPU1FEbzJ3cjk3NTBLSVhvUWRkbEZVUmlid2ZrM1dCcHFxcVdvVWt3TUJOWXpVR2xpTkljendCckFNdWdNemxOdEFMUzZwRDFLdVlhMU5KS1VISjViTlpONEQ1S051eXRYc1FiTXBPZzB4K1ltbWllYWI3dnVDVDJwVEl6NEdNSm1HM0U0dFgyUE10NVh1OHpDU2lOaHNyME9nVDBObnZGRVVnQTVzaFVwQjNxMG93YTU2dmlxdlZsRjUzQ1BGc05TaVRNK25ESFVDRXJWSlJuSndxekN1cisxVjJjSGV0b2cvMTZDWm9rbXRxcjBpei9xSXp6L2JQN1RYM25kOVdkSDE5N3BSM01KZGF1N3BYUmFZQUp5NytCWGlrVk1tZnNqQWkxUWZ3VXNtWm9uOWxPUUhHb0tGaTBwM0xRWUhWNFNlM1MxQmFyblhmc3RBdnF3a2YvcWxxcFhpL2tJZFpheHVZTXNTYlhCeDVtMGVHS2lVQUNKa2tVUUZ6MVEzQk9MWk9xL2pnU3BXbi9ITnFXSkF2aUFMUlVsWmtZYW1RRi9kV29SRmpoUEdUUjFMYWhSbGtOSFRXRlpqanpIQWNUbFk1R3JWanBScENDbStSNG03ZTkwdzdWSnh6Rzk5RWtaUTlzRlkvQ3BOU21QV2xaQkxkaGtLcnhvUDM5OEFkaXFub1dLN3laWVBIYW53c0pWT0hIZEJmMUl2WWdad3VuUWEyRC9LMUhVSnZVMjV2R2F5bC9iYnhEWGJoN2FZY1F5RmlZa0tGMk9nRldyWEpqVENOY3E2M1JxdGFuVHZRV1p3dzhvcURtNitEUjlnU3U0U0o4SG1UaG9kWU5RV1lvUGRqR0NaWUxDSUpBb2FreVRTWkttM3FuYlUwVWRtWkE2dEFONmo1ZytSRVFHUk5ISE1VK0lDcG1UNzN0aUdxenRxTzJOOW9wVVhLUFVJYWVkWDR0cVdXOWRaOWY0eDNxS2VyZkNaeVE3MEZnQ0MzUm1GeEN4NFZXLzV5cDFsRndTVWhzSS9iV1NTWWpudFhwaSt5ZGIrV205bTZVekZXVlZSd0xNaG5RanhrL2dISlhHeXlwT3UvQ0hFSklrdS9kYVk4TFRZR3Mxb2JLN0MzcXpKdHdqMXpndmUwbEs1NG5nUVU2bVljVzZ0bFhuU3dkUElmT2tMRnNIdTQvRnpPb0dySk93T21JVTFFTDY2aEs0NlBqQmR0UVFIVVAxTTNTeHVMcm4rU0Jub0VJa2xUQWlzc3FIazAxUFlNVzB1T01DUU9wcG5VZ25vakJZQ05tb1QzZXBmWHNDN0JjRzVWbXpCNzRDQUNHRnB3S3BkeWZod3JhMmxISkM4R0U2bFZqdjQ1WVJwRDFuUmxwMGtvdzNJcnFRZHdxTVBTa1l6WHZvSTVMQkRyV2cyQUdqRFRRRmRXc2lZd2p1M2xzeFdzaUtGRitoYlFoanordG5CZVYzbEVRMjdUcGhSVmRxYXRCd1ZUb2dqMks5aUIxK28vazA2bXlyaGFjUHh3NmVKL2RyeS9NOUx1ZDIvRTNyN09lcUp1R2wzT2Vod0dzVklaWXRoSWNxYk5ocFl5a1V3RkVYSzVxR0F5cWRNRTNwaU5qcUZCN3UvTnFNRGJaQnZ4YThOTkQxN3ZML2NrNk1qajFPRWlCYmpWb1Mrc09uWTlVcElXRzZEMUZpS0IrVkFsRXdwcC9kS2Q4SldZai9PRUpMa2xUbmVuRDBsaW9SM1pKdlcxQkZxK1Qvd0ZLVTZwbGpCanlCQnRFN3N2dnVsV0lhL1JwbThBTllVRld2Z1hkRXBrWjhFQzhJZ2ttNHBVTVc4R3Zyak9NWWFzckNBRFEwU0FBRDMyRWJoektwbDVRUUtBbmowMEVHR014OVZ1eTZPcjF3SGxsSFd1OXdONlZLdTlsSUtYcWErVU53SlJqUGRTVHJ6Nm5yYTBTMVVFS1FPVjVaNS9vUkJEUWdwN2d5Tm9pT1dXbHBvUzJwV2hiSllzT1FqdThicWhkbG5PaFg4dFdSN2pzdkZ3N3M2eno2SVMyQ0Z3MUFlZ3ZxV2FUWDZkUndCS2kwT2ZyaVloWnZmbEpoakF4djFxMk9zTE1WODNtcjlPQ3BBUW1jdG9kSlVUZWJSMW5KS202am5Rd3o2Qld5dWJVZHJpbkFxZG1nSHJ4K0pNbEJac2dWL1dBNExxM2ZQeGxGQ1NjZW9KN2YzUUVaL0tkcWpPWEl6cENVNms2bmdwcmFqU0trbjJXZGZvTDJwaXZzcTVOZzJaYkFnejNQSnBrcStxRkVhUWw2ZFVQU1U0djlVbGovT1BWNFF4Ri9sVGZvamI4MEh1R3hvTVRab1NwSjIrcFlIbDhqV0ZMVFdiYzhiVUVPZkpiSjE2QUVTT0psMXBJY0FaM0UwWGExenFmUWo1Q093ZVVLZ1lwTTljWHM0eGVyVkREVmVFZWduTEk2bGRxMGpHZjVGK1BPZ0NLd1BDQVRYenQxOGhLS09rZFNuMEhVT01lQ1JmZVdOS3dKWkZMeGZmUHRIR0lVZTcwQzBPSEhnS0ZyT09IT29YeHJpbTJBTE5ST0dyM1VSZE1mbTM3T2dCVU00YlBmVzdac0RzNmpEdDFJclBLUG8xSU1uRDV3NHJzcWUwcklwMjViNm8ydk9PLytNMnJmakVHaXZTQSs2K09oYkdzQ3l0d0dmUmdEVUdKRmdPR0xSbDhBSGV5M01qdHc4K2xGMXM2andscDQ0YUlhbFhRM0swNkpzUFVraUo4NUp1SFJBT1BqeDZvaXpNQkh5RzJPb1M5UEF5V3NHdlZwODRuSWFnUU5iNDZqM01rK0hCcnlFMUNmYXB3eTlCRXQ0eFlQUVY0a0lKT1B5cFFGQjBQMVc3VGh2a0JabFI4RWxjV0xvUjlrbGJEcWYwQmxuOUNZMzdpZzd0Q1hiQ1l5SmF0VmxsaEU2RkJKNGVaRXgzekcyQUd5eiswVjJJZW9GMEJrVTB1QnpLQW4yTHdPcGFtazNoNFN2RG82S3duWVkyaFZJT0tnRTBLdEttQVhWdFc2VGNEc1dTY2prc09uaHBKb1VLampvUyt2Z2ZVU0hKRUFoNTZYdlZuRDBDMzZWaWdOSXlLRnNBcXJub2RuUmJzNnRYSTFscUdKMGFYekh0T1NzZVhSeWx1dmhNREtlb2FnVlU2NzhqTXVrWG5VTFlSRXNnaXVKajV5Zm1kazlGT1RJMDR5cXN5czdxeHRSUHNKSlNoQXJWU1YwbUZoZVBXcjYyNTZ0N1BxaG52WkUrYkEvdzFxYnRLblNpaUpwQWN5T2tnSXVIdFZEY0pSeWM1TXNHVG1ZWnlGRTl4RERXdUVRb290S1E2ZGJpdlpmTXBiVlRHOFRyWXIrbzFkSnltVytDbEt2UEFIZ1kwSmprcTRMRXVkVTJUcm9yNU9Lc2lWaHlLZXJEUGZuWldSNHlCUk9LQ3Q4SFMxRlErOVFzSU1objhkajlWcWlQUloxQ0h0YW9YcUFVSlQ5UHZxQ0RSZ0hOV2k0VFNyMkpJNnFFRWFoa2RXVlJQMmEveUErOWNEV1FPN2ZITHZwTzRCRS9TTWJzZG1lV1Z0QnNjTUpVSFlhMFRJLzZDMlZPSFgvRHN3T1c0YjRjQk80bzhOa25RcTlOR05wTmEzbC9mS05tZ00yTUExd0Y4VlZLeExBUnRGYWJGZUNQQkNrK1BMMG1LM3U1ZitYNnQxNE5DV0h5MEI2UnFxUHJ4ZFY5MTNNMm54blh2dDhIeTYrM1Y0TVlBanJReW5LL0RqSkx3RVJ0UTYrZjRxOXJpd05KZnk0ZHBaS1VaTVlYMXRUN0dVY3k4WlZXaGEwb0pCcEFrTGNHaXVnNjVERzhJK3QwTkRFdktmK24zVkRTMTM2b1NFZ1JOYllYUCtUNGNIY2dUMWRHaVhzSlFsQmZJM1hjbTBkVE9OUHgrSGNVNlRuM2Vob3JZcHg2TmlTVGYydWtETUp2T1Ztb2ZVSUN2WDdZQlpWYnBWY0VPVWU2MUo0NEN1TXl4bTE3dE9QaHM5QmI2QkFwODlJT0thV3JDMFdaUGwzZ0YxOEE0Uk5kYThCS282MGRQSFMrTjZBRWxzdE8yVndPeldhRFh1UXR2WDIvSTBVU0dLTlBpaWpvQkgxR0VvS0VlR1E4RThSSHhhcVdhUkJqTGtWd1JQTC9xQmE1Tk94dzRmLzArRFpWMmVsRWxBUmFFRW9QTVU5cFBoT0ZkdVYvZlg3VUEyVDBCVzNqOUppaFNMRTFDRjl1dU9xK2wzeFFROUJ0KzV0YXZwakFVMzJnNkUzdWZMNzBIVWJrTVMwVjB4Szcrby9kVHI1Nm9JOWY4R3hSbFRoRStUUzVTcGU4aFFZcHE0T25SdlYzU1FEMC9yRVpRWHd4eGFnN3VuSW9RSkNwZnFLWDY4N3Nmam42ekNXOWE4QXNvZFpkeUdyUWg5WEVkVytjS2NsVGJzazRHQWE2T1dmU01ZdWhYNmFoelg0cDFmQjBnSU1hQUNJdVNuVG1yWkJySGZNejcyVmZsV2RSdnRJd25kWDNqaWZEcWFtTnFLTUFJVE90d0lpS1ZtTitXL1k2Q2ZHZ3ZhZzk2cFBTTTRINlNGeXlEd05VMDUvQ0NBZERXV1pDc0xkd3VjYUhmemhXQ1RxcW9rNlFoTVZqOGhiaEMrcGFqWHlLaEJoQjFXT2diMkxnVm5Gd3E0azZUcXM0dHBuYXRYWlorcFljNjUvVmRzYTVPQzZKbG1PaGNsVWdyYWFjQ2tHbytlVDJqQXhLMzJzOVgwTG1OZEZqTmdSTm5wcHJ5UThkdzFTeWNWQ01zUERKSmtMWEZxWk54T2pMejE2L01jZ2lFOWZlVklSd2o1dFQ5TjlDdXRoamc2dDE0QUFBQmcybERRMUJKUTBNZ2NISnZabWxzWlFBQWVKeDlrVDFJdzBBY3hWOVRwYUlWQlR1SU9HU29UaFpFUlJ5MUNrV29FR3FGVmgxTUx2MkNKZzFKaW91ajRGcHc4R094NnVEaXJLdURxeUFJZm9BNE9Ub3B1a2lKLzBzS0xXSThPTzdIdTN1UHUzZUFVQzh6emVvWUJ6VGRObE9KdUpqSnJvcWhWNFFnb0I4OUNNak1NdVlrS1FuZjhYV1BBRi92WWp6TC85eWZvMWZOV1F3SWlNU3p6REJ0NGczaTZVM2I0THhQSEdGRldTVStKeDR6NllMRWoxeFhQSDdqWEhCWjRKa1JNNTJhSjQ0UWk0VTJWdHFZRlUyTmVJbzRxbW82NVFzWmoxWE9XNXkxY3BVMTc4bGZHTTdwSzh0Y3B6bU1CQmF4QkFraUZGUlJRaGsyWXJUcXBGaEkwWDdjeHovaytpVnlLZVFxZ1pGakFSVm9rRjAvK0IvODd0YktUMDU0U2VFNDBQbmlPQjhqUUdnWGFOUWM1L3ZZY1JvblFQQVp1TkpiL2tvZG1Qa2t2ZGJTb2tkQTN6WndjZDNTbEQzZ2NnY1lmREprVTNhbElFMGhud2ZleitpYnNzREFMZEM5NXZYVzNNZnBBNUNtcnBJM3dNRWhNRnFnN0hXZmQzZTE5L2J2bVdaL1A4YTdjbUx3MFh4dkFBQUFCbUpMUjBRQS93RC9BUCtndmFlVEFBQUFDWEJJV1hNQUFBc1RBQUFMRXdFQW1wd1lBQUFBQjNSSlRVVUg1QWNDRUI4ZkJWeEh0QUFBSUFCSlJFRlVlTnJzblhsOFZPVzkvOS9QbVMwN2tJV3doTVVFQVJFRXBBVzBJbW9ScFlxMWlGV3cva1RGdXJTaVhjQzYwTnFDWGJEMXV0d3FXbXF4cmREMklyZFhzYlRJOVlwTEN5aWlVdGtERVJJZ2V6S1RXYytjOC96K21ITW1KNU1KaXlJazhMeGZyL01LWkNZemt6Tm5Qdm51WC9IOXIxM0FtcTFWUkgxOUdUYnhadVpkK0FHM3oxdEYwUjJyV0R0dkdGRExpaG5qbUxkaEhJcytXTTcwUEtEOEdhNjQ5RkhLTDEzTTltY24wNHFmRmJQR011L04wU3pZc0p3Yml1enZSeWxmc1lDSEZyL0M1ajBCZklWbk1mN21lN2ltYmdGMy9DN0t6R1ViV1RpZUxrazRIT2JBZ1FQVTFOUlFWMWRIWTJNalRVMU5CQUlCV2xwYUNBYURoTU5od3VFdzBXaVVhRFJLTEJZakZvdWg2enE2cmhPUHg1T0hZUmpKcjRaaFlKcW04OS9DTkUyY2g1VFMvaXFrbE5nSElGSy9Ba2dwaGZYU2sxOVR2aWQyN2RwVlhGQlEwTnZuOHhXN1hLNWlUZE9LTkUwckVFTGtBejJBUENGRUhwQU5aQUdaZ05jNlhJQm1QWjRKR0VETU9zSkFDQWhLS2YyQVgwclpKS1Zza0ZMV0c0WlJwK3Q2ZFNRU3FUbDQ4T0RCa1NOSDFsaVBJeDFIdS84TElWSnZzNzhISUlVUTdiNDZEcWxwR2tJSU5FMUxQYVRMNWNMbGNxRnBHdmEvM1c1MzhxdDllRHdlM0c0M2htSFF2WHQzZkQ0ZlBwK1B6TXhNTWpNenljN09KaWNuaDl6Y1hMcDM3MDZQSGowb0xDeWtaOCtlOU9uVGg4ek16R080NnZ5c216dUptemRQWmRYYStReERrWXA5MGJlZXNoV3pHRHZ2VFVZdjJNRHlWbVg2SElpeWJzNVlibDVWeEIycjFqS3ZpNzA3c1ZpTUF3Y09jT2pRSWVycTZtaG9hS0NwcVFtLzM1OFV0RkFvUkNRU0lSS0pIRkhRbkdMbVBFelRGTGJBcFloWkd5R3p4Y2twVXFtQzVmeC9UVTNONEp5Y25FRnV0N3RNMDdSU0ljUkFJY1FBWUFDUTBVbE9jd1RZSjZYOHhEVE5Dc013OXNaaXNUMk5qWTNsQXdZTTJKMGljUEl3Z3RkT0FHM2hFMEswRVRwTjAyU3F5Rm5DbGhRNTUrRVVONWZMaFdFWTVPWGw0ZlY2OFhxOStIdytNakl5eU1qSUlDc3JLeWx3ZVhsNWRPL2VuZno4ZkFvTEMrblZxeGQ5K3ZUQjYvVWVWdEMycm5pUmw5NWN4WXBWbFF4YjhDckxieWhSS3BZR2R5M1FhbGh0WmZIdjFoUGpMQzRjWDNUY0JHenJNM05aVmpLUCtWTkw4Tm5mTFYvQjRuVUI2RHVWUzd1WXFPM2Z2NTlEaHc1UlcxdExmWDA5alkyTitQMStBb0ZBVXREQzRmQmhCVTNYOWFSMWxpcHE2Y1RNdHNwc1liUC9LS1VSc2piLzM3VnJWODgrZmZxTThIZzhJMXd1MTlsQ2lMT0JzeUQ1Vm5SbU1vREJRb2pCdHBCNHZWNXljbkl3VFRNS2JETk5jMnM4SHQ4YURvYy8zcmx6NThmanhvMnJzeXhGS1JNblN0ci9kNHFjbEZJS0llejdKQVhPTUl5azlXWmJjOVloT2hJNXA3Q1pwb21tYVhnOEhqd2VEMTZ2bDNBNGpNL25JeFFLRVF3R2FXbHBJUkFJNFBmN2FXcHFvckd4a1lhR0JtcHJhK25WcXhmOSt2WHIrTE8wNmdsK3Q3NkljVGMveVpOSzFEcTIySVpjY0QyVEp3NmppRm8ycjF2REIxVlFldk5TVnM0ZlQ5NXhlWXBLVnN5YXhydzMvZlFkTlpHSm8wdkF2NVgxcjJ4Z1Q2d3ZWeTVleVpPVGk3ckV5YXF1cm03bmRqWTNOeWZkVHFlZ1JTS1Jkb0oyQkN0TjJHNW5pcUNKRHF5eWRoYlp0ZGRlNjFxeVpNa1hNekl5dnVCeXVjWUlJYzRGQnA5bTEvUXUwelRmMTNWOXM5L3ZmLy9yWC8vNisrdldyVE1kNHBZVU9TR0VtYzZpUzJmTmFab20wMWh4dUZ5dU5pSW5wU1FyS3l2cG5qb0Z6dXYxSnEyM3pNeE1zckt5a3U1cHQyN2Qycm1ueGNYRlNxRStyYkI5LzZhdnNHNzlOdXJJcGJCc05GTnZuc2M5MDRjZEoxR3pxV1g5aTArd2VOazZOcGRYRWFDUTB2R1R1Zm1lZTdoaGRPY1h0ZWJtWnFxcXFwSldXanEzTTEwY1RkZDFZckZZMGtKTEUwY1R6amhhR3N0TXBJbVgyVmFaK043M3Z1ZDYrT0dITC9ENWZPZHBtbmErRU9JOElFZGQxbTFva1ZLdWo4Zmo2d09Cd01hNzdycHIvVi8rOHBkNEdxRXpuYTZyUStTY2NUbVphc21seE4ra2xKS01qSXgyc1RldjE1c1V1STdpYjA3M3RLaW9pRjY5ZXRHM2IxKzZkZXVtM3NYUEdtTlR0S0xyT3Z2MzcyOWpwVFUxTlNXdHRLT0pvNlZMRHRpQ2xtS2RKZDFNaDBXR1U4aWtsRnBEUThPd25KeWNpMXd1MTRWQ2lJbUF1dXFQOGUrVWFacHY2YnIrZG5WMTlkc0RCdzdjYm9sYk82dk9hZEhaMXB2ejMzYUN3U2x5VWtwOFBwOU1sMXhJdGQ3U3hkOXM2NjE3OSs1dHJMZCsvZnJoOFhqVXU2ZUU3Yk54Nk5BaEtpc3IyeVVIbkc2bkxXcTJsV1piYUVjcmFJWmhKTjFNMHpRN0ZMTlFLSFN4MSt1ZHBHbmFKR0NFZW5lT0svK094K092Ky8zK053b0xDOThta2NVMVU4V3VJNUd6a3cyMnV3cmc5WHFURmx4SEFtZGJjTGIxWm91YjB6MU5UUzZVbEpUUXExY3Y5WTRwWVR0MmdzRWcrL2J0NDhDQkExUlhWNmRORGh5cGZDUEY3UlFwaVFHbmRTWlNMTFNrbUlYRDRTbGVyM2VLRUdJSzBGZTlNeWVFS3NNdy9oRUlCTmJtNStldnBiVmNKYTNJT1dOeXRxc0s0SGE3cGJNOHhCYTVJMWx2cWU1cGJtNHVlWGw1OU9qUmc0S0NBb3FMaStuVHB3LzkrL2NuT3p0YnZWdEsySTZPL2Z2M0o2MDBaeXd0WFhJZ05aYVd4a283a3FDSjFKaFpNQmk4eE9melhTV0V1RktKV2FjUXVkVk5UVTJyaTRxS2JFdXVqY2lsV25IMnZ4M3VhVHVCYzd2ZEhRcWMwM3BMVFM2a3h0NUtTa29Pa3oxVktHRURBb0VBbjN6eUNWVlZWVzJzdE9ibTVqWTFhVWRaWkN0czE5TjJOdzlqbldtTmpZM0RjM0p5dnFacDJ0V2dhaTA3S2R0aXNkZ3JCdzRjK0Z0cGFlbFdoOEFaRHBGenh1YmFXSEYyOXRRV09VdllaRHIzTk5WNmM5YSsyWmxUMjNycjI3Y3ZBd1lNSURjM1Y3MURTdGphVzJsMmdpQmR4ck9qRWc1YjFOSUptck5Md0Nsb3RwaXRXclVxWTlLa1NkZTYzZTdwUW9qTDFHWFlkVEJOYzAwb0ZIcjVxYWVlZXVYQkJ4OE1BL0dPUkM2ZHdEbHI0TklKbkMxdUhaV0dwR1pPN2NTQ3N0NlVzQUVRQ29YWXUzZHZ1d1NCYmFXMXRMUWtZMmxINkJ3NG5LQUpwOXZaMU5RMElqczcrenBOMDY0RDFKWFl4ZjhtNnJyKzB2NzkrLzg2YU5DZ2JRNVgxVWgxVlIwQ0o0OGtjQjFsVHUzWVcwNU9UdEo2UzAwc25ISEdHV1JsWmFsMzVuUVZ0b01IRHlaZHo1cWFtallKZ2lQVnBUbEVMYTJnV1FXMWJkek5VQ2cweGV2MXpoQkNYS3N1dVZQU2lsdloxTlQwVW1GaDRldFN5cmdRd2lsd2h0T0NzNFhPR1lkTEZiaU9ZbS9wNnQ1czE3Um56NTVKMTdSMzc5Nm4vWHZpUHAxK1djTXdLQzh2WjkrK2ZSdzhlRERaRXRYYzNJemY3eWNZRENaRkxScU5wdTBlc0FYTkVVZExLMmczM25pamE4bVNKVGU2M2U0YmdRdlV4Ly9VUmRPMGFmbjUrZE5pc2RnN08zZnUvUE10dDl6eTBzYU5HK09XbTJwSUtkdGxWZzNEa0tacFNrM1Q3RmlzZEpZRXhlTng2U3dYc2owRVp5akUvcU5yL3hFT2hVTEpkcTJ5c2pKY0xwY1N0bE9kcHFZbTl1eloweWJyNld5Sk9sS3hiUnEzVXppNkJKS0N0bW5UcHR3UkkwYk1jcmxjTjZGcXprNHY5MGVJTDJWblozL3BYLy82MXpjamtjanlaY3VXL2VXMjIyN3pXOVpidkNPQmsxSktSNkcyVStCa2FndWVzOWY0U0FKWFdscEs5KzdkbFN0NnFsSlpXVWxGUlFXVmxaVnBhOVBTRmRzZXhrcExHMFBidG0xYmo3S3lzbHRjTHRjdHdDRDFNVC85TUF5RHlzcEtCZ3dZWUgrclBCS0ovT0dWVjE3NTgzWFhYZGRvVzNEMlljWGd6SFF4T0dmZG05TTlUUzBMc1l0NjA5VzhsWlNVTUhEZ1FFcEtTcFN3blVwSUtkbTVjeWVmZlBKSnN1RDJjRm5QMUE2Q0R0eE9POHVwU1NuRmh4OSttSGZXV1dmTmRybGNzNEV5OWZGV3d1WVFOcHM5NFhENDkzLzYwNStXMzNycnJjMUhFRGpUMFhTZjJya2cwM1VzZEpRMXRRdDZCd3dZd09EQmc3RzZKWlN3ZFdVQ2dRQzdkKzlPeHROcWFtcVNXVTlud1cwSHJxZEliWVZLc2RLMCtmUG51K2JQbi85Tmw4djFUVlQ5MlFsOFl3OUI5UTVvcUlCd1UrSUF5T3llT1BJSFF2RVF5TzNWbVlUTi9rTzdMUlFLdmZEMXIzOTkyZXJWcTJPV3dOa2laem9Femt5MTNwd0M1L0Y0WkVmOXBzNXBJZm41K2ZUczJaUGV2WHZUdjM5L0JnMGFkTnJVdkoyU3dsWmRYVTE1ZVRuNzkrL240TUdEN1VvNVV1TnB6b0JzT3JmVFRnellXYzVJSkhLOXgrTzVFeGlubE9ZRTBWQUJ1OWNsdmg0TitRTmgwTVRFMTA0aWJBNkIyK2ozKzVkMjc5NzlGU0dFbmlKdzdWeFVSd2VEVExYZVVtdmVVZ3Q2N1pLUTNyMTcwNjlmUDhyS3lrNkxjVWluWFBLZ29xS2lUWkxBbnB2V1VUek5hYVhaZ1ZtbjIrbElER2loVU9nU3I5ZDdsOVh5cERoUmJQa3JWSDE0N0VLNHNRTDZqb0lSWCsxYzFvUVFZN3QxNnpiV01Jd3JhMnBxbHZidTNmdWZUb0ZMVFRMWUNRWnJXSUpNbWQ5bi8vK29rZ3lSU0lSd09NekFnUU9Wc0hXVmVOcU9IVHZZczJkUHNqWEtIalBVVVR5dEE5ZXpuZHZaME5Bd09DOHY3MXRDaUR1VXlweEE0aEhZOEVMQy9meTBWSDJRY0ZmUHZRN2NHWjNxMTlNMDdTdTlldlg2U2l3V1cvTHh4eC8vZnZUbzBlVU9nYlBMUkF6QU5FM1R0UGRiMk5sVHUyWFBLVzZwSW1jbndGTEZMUktKTUdUSWtGTTI3blpLQ0ZzNEhHYkhqaDFVVkZTMDZmZTA2OU02aXFlbFdtbVdxQ1hkemxtelpybWVlKzY1dTEwdTF4eEF6V0UrMFd4WUNvSHE0K1BHdnY5bkdIdFRwL3cxUFI3UDdGR2pSazBKQm9QUFRwZ3c0ZmViTjIrT0FicGx2V20yaXlxbE5PMXg1bFlDU3pxSEs2UUtYTHBTRVdmWXhSYTNZMXNrbzRUdGhORFkyTWpPblR1cHFLaElEb1IwVHVWSVY1OW0vUlZ6aXByVFN0T2tsQ0lVQ2szMitYejNBbDlXQ25PUzNNL2pJV3BPY2R2eVA1M09MWFhRTnlzcjZ5ZnZ2dnZ1eFRVMU5VdDY5Kzc5bGhBaTVuQlBCYTA5cVRMRmduTUtuRXkzRkNpZGUycFhBUXdlUEpnZVBYcWNZc0xtWDhHTVVmUFlrSHNOdi92d1VTWjJvUmRmWFYzZHBwekRibUpQVjNUcktPVVF0bm1leGtyVEtpc3JleFVYRjkrcmFkcDNsTHFjSkJvcWpqMm1kclJ1YWQrUkp6U2g4Q25jMDR0NzllcDFjVFFhL2ZYcnI3Lyt1eWxUcGh5eTNGUE5LWENKWFRUQzdNaDZjN2lxSEkyYk9uanc0Rk1xcWRCbExiYjkrL2V6YTljdVB2bmtrMlE1aDdPVElGMlNJTlZLTXd6RGFhVnBrVWhrdXNmaitRNHdScW5MU1dUWEc2My8xZy94MEkvL3diSUdHRGZqNnl5ZmtBa1lWSDY0aVlkZTNjdjZ5Z2hrWmpONjVBam1UUi9DYUtzSFBMcC9Od3RXYk9HVnZYNENaRkE2dUQ5M1RCL0w5TjNyWU96QVRuOEt2Rjd2dHk2NzdMTHo2K3Zybnkwb0tGZ3RoSEJaN3FudWlMMEpaL3d0amNCMUdIdEx0ZUJzZ2V2eVUwTDg1YXg3WlJudVNhUG1zUWNnOEJJM2w2NWo1a3NiV1ZpMmxSY1hQTVRpTlZ1cGl2cm9PMndpTjg5ZnlDMmo4enJGYXk4dkwyZjM3dDFKVWJQTE9WS2IyRHNTTldmR1UwcXBWVmRYOXlzb0tQaXVFT0l1cFNvbm1jQWhhUHdrK2QrdHIyOWtSVVBLdGZ2eHY1anhiRGxWZUNnZFdJeXZvWllONjljem94N1dmbWNJSmMxN21mUDRPN3dXaHNMaVlvYlJ6SWF0TzVuM2VJeThIN3FZZk5haGsxTG5kcXdJSVVibjUrY3Zqc1ZpdjNubm5YZWV2L2ppaXl1RkVKckRQWTFqdGZMWmswVHN6S2xUM0p4Yno1ekxnK3pZc25PNFF5d1dvNnlzSzlhWmw3Tml6aHdXcmRtRzM1ZUxlK2JNQy9uZHNqZXA4cFp5NlEzWGNHbVJuM1VQeldMK0tqOTlMNXpPSFNYbHZMSnNGUXRuK2NsYnU1VHBKM21wMVBidDI1T0Z0ODZlejhOa1BvV3oxOU5SbDZaSktVVWtFcG5tOFhpK0IzeEJxVXBuaUM5c2IvMTMvUTRXdk5wSUxPVXVXN2ZXRXMzTDVzSXJMbWZwaEJ3STdXYldEOTdoemIzN1dCOGF3dVNLZld6MVpOQjMyRmhXM25vR1JiVHcvS0tYV0ZoUnhhcTlNTG0rb2tzSW00M0g0N2x0NHNTSlkycHJheGNYRlJXdHNXSnZtaVZxdHZWR3duQ1RxZGFiU0luRGtlcWlPamVuMlFJM2RPalFMbmJoUktIb0d1WXZuOHJrdkJXNGI1bDNKYTh0ZTVNcTMyaG16citUaVpUelRIa2RVTXJVZWZPWk55ektwV2N0WnRXZVhQTDhPTFlybjFpa2xIejg4Y2ZzM3IyN3pjSmllMnVVUFprakpVbVFLbXJKZU5yMjdkdTdsNVdWZlYvVHRIbEtUVG9SOVJYV1AyS3MrZXNIYktBSDR3YTJzS0ZDVDk1bC9MVmZZMk82QVZBZUwza2V5QnM1a2JkR3BudHdGM2tlU3p3SGp1OVNwMFVJY1c1aFllRnprVWpraVQvKzhZKy9uVDE3ZHFObHZlbTBqc2d5YkVGenh0N3NRYWRPY1V0ZCs1Z1F1U2hORmUveTV5ZjJVampyUDNsczV0bGRxQnhrR05QbkQ3T050M1F4dGpJbVhqbUtKN1o5d09Jcng3Sm0xSGpHWDNvTk0rK1p6TENUNUlucXVzN0hIMytjN0NaSUxieE5MZWV3MHRtcFdjOWtnaUFZREY3bzgvbm1DaUVtS3lYcFpGZ3RVdEc5bTFpd0tVTGZDeVp5YzN4ZEcyR3pxZjF3RXd2ZWE2WjJieFViNGhsY2VPMG9KanMzMUIzYXpVT3ZWbEZaZjRqMUZScDl4NHpsanNGQVBOcGxUNC9QNTd2bjVwdHZQbnZTcEVuUERodzQ4RjByOWhhelhGTWhoREFTaGxwcjNNM3BtcVpNRWtrSVd6eEMzZDR0dkw1L1B3ZjhKdTZNWGt5ZHZJTVBQNHh6OXRsbmQ4bTFmMm1UQjhQdVhNN2EwU3RZdHV3MTFxMWZ4N0pIWDJQWjc2NWs4ZG9ubVh5Q3hTMFNpYVFWTmJ2d050MCtBb2VsbHVwNmFyRlk3SGFYeTNVZjBFZXBTQ2NrMGd3MDgvenkzVlJsOXVPSnEzdVJ0eUw5WGYxNzk3RnFrei94bjd4dURDdE9xY2Z5MTdKbVV3VjFBTzVzaHAzUkxiRUkvTE1VL0hZQ05FMmJOR0RBZ0dGK3YvOC84L0x5L2d4b2xudWF0Tnh3SkJac2o4ZXkzb1Rsc21LYVVScXJ5cWx0Q3FPajRjM3J5YUJ6aHpQMjNITTRTNnRnNTg2RXEzcjIyV2VUa1pIUnRjNFJnTS95VWFOUndMK2VaK2JNWmRGcmVjeDhjaW12Ymx6TFE2T0F1bldzMm54aVgxd3dHT1Nqano0NmJKMWFTbHhOeEdJeFlmZDhXbWEyWnBxbVZsVlYxU2NlanovbWNybWVVS0xXdWFuOTEwWVdWOEs0cThjeTlUQ1Ryc3V1L2hwN252NEdHKzQ5aTdOQzFTeCs5bCtzQ1RudU1QZzhOajU5RTlzV1hNdzFCVUZlVy9FNmkvWWFwOHBwNnBPYm0vdlRhRFI2LzZwVnEwcWtsQm5XUjlrTGVLU1VMaW1sWmwvL1ZneE4yTjVNSkJJUjRXQXoxUTBob3FZZ28rZ01SbzRZekJrRkdlak5OUnc0Y0lDS2lncDI3dHpKUng5OVJEQVk3R0lXbTYrSXZGd2dzSVlGMDJaUnZuQWhlYlZyV0xWcUhWc3JQMkJ5U1MzcnlnRnZHYU5PWUxJa0VBaTBzZFNjTFZLMm9EbkhkOXV1cDFXZjFpYnJHUXdHSi9oOHZ2dUVFSmNxMmVqa1pCaXMrTDhEQklBTnkxK2lkSG5yVFJ1Vy80VUorNy9DcTVkNXFkVmQ1QlhrVU9SeFVUUjRHTmVVYkdOaFJTMGJxbUZpZmd1VllRTmZYamRLc3NCWDBKK1p3N0o1cVRySTVwMHRjTTdnVStaMGViM2UyVk9tVENuZHUzZnZNMmVjY2NaN1Z0ek5UaXdJSVlSaG1xWnBMM2h1RzNkemlXN2RNbVZMTUVLNGVqY2JhejhodDdBdlpXY09ZcWpEWGJXUHM4OCt1OHRNQjNIam04Zzk4NjlrNjRJMTdDa3ZweXBhd3AxUExzWC8wQUordCs1M0xJNTVLVHpyVXU2WXQ1QmJUbEJUVVhOemMxTFU3R2IyK3ZyNmR1N25ZVEtmeVhoYU5CcjlodHZ0dmg4MS9MRnJrTm1Uc2pQN2NLRWo1RkZiZVlodGZwUGM0bUxHbnlGWXMveGw1bTJGY1RPK3h2SUpPUkJxNElNR0FDK0ZtVkM1L25VdS9aOUdjc2RjekZ1MzlpZVBNT1hWWVVBakw4L2I2WHBHajROcmVzbUFBUU1HTkRRMFBKV2ZuLytLNVpxS0ZOY1VaOXpORmptUEoxTjBMK3dtUGVnRUEzNmFhei9oZzlvcWF1T1hjcUhWMmVEc1VUMzc3TFBwMXExYjE0aXhsVTEva3JYVG5kOGV6NTNQdnNxZEorRUZOVFUxZldwUmN5UUp0RnR1dWNXMWVQSGlIMmlhOWlDbjJXNkhMazNCY0NaZmV5bk9yTTc2My8rWm1lc2pETHRrSW8rZWwwbXRweStGVy9lellmbkxUSG92SDE5MUxkdjhrSHZtRUtiMmdoTFBFTWF0V2MrR1RldVlWRjFFbWQ3TWhtb1Q4dm94YzFnbUZBODk1VTZiRUtLc1I0OGV2d3lGUW4zR2p4Ky81S09QUHRJYzFwdHVpVmt5N2hhUHg5dGtUS1hYSzNNS2VsUFlHMkxCSU81b000Mk55YktSTnNmWlo1L2Q2VWVPZDZvUGZHTmo0eEZGTFozN21ab2tPSFRvVUwrQ2dvTDdoUkN6bFZKME1ZcUhKdWF1SFlhaUwweGtPWnRZc0dZdjYzZFZRMlllNHk0WXdmeXJCeVVtRlJRTTRkbDdYU3hhc1lVMWU2dlpRQVpuRFR1TE82YVBZV28zb0dEZ3FYcjIzSm1abWZQZWUrKzk0dGRmZi8wM2wxOStlYVZ0dVZuV1c5eStZN3FrUXVLTGo4emNBakpsQzgzTjZZWE5GcmZPM0YvYWFRWk5Pa1hOem43YW91YU1xYVd4MU96V0tNMDBUUkVJQkw2UW1abjVnSnFaMW9YWnNMUk45OEZ4cGNjQUdEZnJjM25vb3gwMGVTSXdER1AxenAwN0Z3OGJObXdMRUxXeXBqcVFYQStvYVpvVVFwaldkRjVwN1ZTUXpwSGo5a1RlN3QyN1UxQlFRSzlldlpJREt6dXp1SFVLaTgzcGZxYUsydUhjVDZ2Zk0rbCtoc1BoeTd4ZTc0T295YlpkbXpNdmdvMHZmSDZQZlJyZ2NybW1EQjA2dEtDeXN2THBrcEtTdDZ4RWdtMjVJWVRBbVZTd2cyNnRsbHN5RnNmaGpKL2h3NGQzU3JmMHBBdWJNMUhnRkxYR3hzYTBFenBTM00ra3FFV2owUmx1dC90QjRFeWxERjJjL0lHSktSekhlOEpISjUvczhUbkUzY2IyNmRNbnY2NnU3cW5Dd3NLL1NTbWRTUVVkUjFMQklXRFNjbDNicVZrNmdSTkNNSHo0OEU2WFVEaXB3dVlzNlhERzFHeFJPNXo3NlJTMVdDeDJoOHZsZW9pVDF2Q2xPTzZNdUJyOGg0N2ZUTFllQXhLUGVab2hoQmhVVUZEd1VGTlRVMDczN3QzL3kyRzV0UWxGMmVJV2o4ZlRpbHRIb21ZZkkwYU02RlNsSUNkTjJJTEJZSWVKZ2pSMWFzSzU0OU1wYXJxdXo5VTA3U0VnUTZuQktjYTRXY2RuaW02UEFURG0rdFA1VEJaMTY5WnRma3RMUzNaT1RzN3ZuVzZwVTdUU2ladWRYRWdWdEZSaDB6U05FU05Ha0oyZDNTbCtZZTFrUEttelRlb29zcC9DdVVIS2pxazkvZlRUSGwzWGY2aHAya0lsYXFjbzdnejQwaDBKRi9LenVKL2pacDF5dFd1ZmdvenM3T3dIUXFIUTdWZGVlV1cybzFQQkE3aXRUZ1dYYVpyQ01Bemg2RklnR28wSysvTVlEQWJ4Ky8wME5UVlJYMS9Qb1VPSHFLeXNwTHk4bkk4Ly9waElKTklwZmxuWHd3OC9mRUtmVU5kMS92M3ZmN2NaUGVTMDFGSmlhc0xleUc0bkNxU1U0dDEzMzgzNXlsZSs4aU5OMDM2Z1B2Mm5BY1ZERTdHeFVKUFZTM3FVVnRvNVY1L1FLUjVTU3Z4K2Y2ZXU4Zko0UE9kTm16WU5sOHUxZGQyNmRmRU9wbmZJTklrRGtlcVNPcHJzMnh4RlJVVzRYSzdUUjlpa2xHelpzcVdOcUtYYkpKVTZkc2dwYWp0MjdPZ3hkT2pRSDJxYTlsMzFpVCtOeU93T0phTVNJdWZMU1RoS25neUlXVDJNdWNXUVhaZ1FzaUdUb1BTQ3hNK2NRTHFDc0FHNDNlNng1NTkvdmlndUx0Nyt0Ny85VFhkbVJqc1NONmNBT3V2WlVnWE8vbHBjWEh4U1J4NmQwQmhiUjcyZlR2ZXpvNUlPS2FVb0x5OHY2TisvLzROQ2lMdlZKLzAwSmJkWDRsQU5jcDhKbjg5MysyMjMzYVo1dmQ0bHQ5OStleTIweHM0Y1ZwbGhHSWJ0YWJWSktEamphODQ0bTh2bHd0NWFQM3o0OEZOZjJPekp0eDJOSGpwTThXM1NVbE9pcGxBY1YzRzc3YWFiYmlJYWpUNDNaODZjQnFkbGxrN2NjR1JMT3hLMlZIRTdXWk40VDRncldsNWVudHdtZGZEZ3dRN0hlYWNyNmJCamFwYjdPVWRkam9yT1NsZHhSVlBjMGpFalI0NlVRb2gvdi9ubW00WmxqYlZ6UzFQRXJsMjh6WGtPbk82cHkrVWlQei8vMUxQWTl1L2ZmOVF4dFhSMWFrOC8vYlJuNU1pUkQycWFkby82NkNnVXg1K01qSXpiSDNqZ2dmaW1UWnVlWGIxNk5la3NOOU0wRFdndEJiSEtSZVRoM0ZHWHk0WFZwblhDdDE5OXJzSldYVjJkWEpHWHVuamxjREUxWjUzYTdObXpIOVEwN2Z2cThsTW9Qajh5TXpPL3RXTEZpbGgyZHZadmJVRnppSnZFV3RKcythYkptSnNRUWpyZFVPZGh1Nk8ydUozSXZhV2ZtN0RaRzlxZDdtZEg0N3hUWTJvcHhiY1Bxc3RPb2ZqOHljcksrazRnRUlqazV1YitrYlpaVXBsYXhHdC9YeVNRSGNYWmJLdk5GcmNUMVRUL3VRaGJPQnh1STJyMjRwWG01dWJEdXAvV0tPOWttNVRWVWFCUUtFNFFPVGs1MzJsc2JBejE2TkZqcFdXMVNWSmliSmE0T1MwMklZU1F0cWc1QmM2MjJHeGhPK2VjYzhqTXpPeDZ3aWFsWk1lT0hlMTJGS1JhYXRZMktlY1NZK3lGSzlGb2RJYlYrNms2Q2hTS0UwdEc5KzdkNTFSWFZ3ZUxpNHYvTGhQOVYyMCszL1pYZTJDbFphMjFzZHljRnB2VEpmWDVmSXdjT2ZKenIzRTc3c0xtRkxYVTdLZTk5N09qeWJlbWFZcHdPSHlaTmFWRE5iUXJGQ2VIb3FLaW9ydjI3dDNyUCtPTU0vNlpZcm5aOFRhNzlBT0hvQWxOMDJSSGNUWmIyREl5TWo3M01wRGpLbXdWRlJYczJiT0hxcXFxcEtXVzZuNDZ1Z3BJS2V2UUFvSEFGNng1YW1yMGtFSnhFaEZDRE9yZnYvL3RtemR2YmhvOWV2VEhsdVdXbk5zbWhHaGp0WkdTSVUyTnM3bmRidHh1TjE2dkY2L1hTMFpHQmdNSER1ejh3bFpkWFowVXRlcnE2cVNvMmYyZkR2ZXpUYXVVYzV4M1ptYm1BNmdoa1FwRnAwRFR0QytPR0RGaTlzcVZLeCtkTm0xYWxVUGNwR04yRzZacG1uYW1OSjNWWnNmYW5CbFNlMEx2NTVVcFBTN0NGZ2dFa3BNNnFxdXIyelMxTzJOcTZjbzZwSlRpbGx0dWNWazdDdFE0YjRXaUUrRnl1UzYvNG9vcmFvY09IZnJZOXUzYnpSUnhzek9uMHQ0cWI0dWJNNWxnVzJ6MlY2L1htM1JKczdLeVBwYzVicDlaMktTVTdWcWxuSlphdXFiMjFBem80c1dMZjZBV3J5Z1VuUk92MTN2anUrKytXNU9ibS90N1M5RE1GSEdUcVpsU1RkUGFXRzdwU2o5OFBoK1ptWm1NR2pYcXVDY1RQck93N2R5NWszMzc5aVhMT281bVRWNUtCdlFicWxaTm9lamM1T1RrM0ZOZFhYMm91TGo0NzRDWnhpMXRFM096eWo2UzRuYTRXRnRXVmhaRGhnenBQTUpXV1ZuWnJsYXRnMVlwMG1WQWc4SGdCR3Vac2RyN3FWQjBidHhGUlVWM2JOMjY5ZEN3WWNQZXQ2d3oyMHBybHltTngrT3lvNExkZExHMjdPeHNTa3FPMzBiMlR5MG9UVTFOYVd2Vk9oQTFrYktsWFR0dzRFQWZuODkzSDJvQWpVTFJKUkJDbEEwZVBQaVdGMTk4OGNBTk45eHd3T0dTSmkyNGxQcTJwRXVhVHRpYzRwYVZsVVZPVHM1eEd5RGcvalQ3R3czVFpNKy90MUs1ZXcvVis2dG9PRlJEYzMwOWdhWm1RdjRBa1dDSVNDaEVOQkpCajhTRXJzZUk2M0ZoR3FhUU1pRnNQVDNoZWFKcDM2WHFjbEdjbkUrcEJpNFBhSjdFd0VwdnRqb25SNEhMNWJwNDJyUnBGY0JUdE1iYlROSWtFMUxGelpsSWNMcWt6a1RDeUpFamo4djAzVTlsc1pYdi9ZVEtxb05VMTlUUjBOQklzOTlQUzB1UVVDaE1PQkloRW8wU2pjWFFZN3JRNDNIaWNTTXhObGhLSVNWYXJIcjM3VUtJdTlSbG9qaHBTQlBpVVNBS3NSWUlOVUpHTG1UazJkNlVvZ015TWpKdXJxK3ZyeWdvS1BncllIU1VUQkJDU0R2ZWxtcTVPZDFScDdEbDVPUXdlUERnRXk5c0I2dHIyRmRaeGFIcVd1b2JHbWxxOWhNSXRCQU1oUWlGSTBRaVVhTFJHSHBNSnlGcWNSSXhOU21rbENKWXRlMUNsOHQxbjdvOEZKMUw2QXdJTjRFZWhweWVDWXRPMFNINStmbDNiTjI2dGNLS3Q1a3BoNVJTeW9TMkNlTHhlREk3R28xRzZVamNNak16aytVZnZYdjNQbkhDRmdxRitXUi9KUWNQMVZCYlgwOWpVek1vTWpJQkFBQWdBRWxFUVZUK1FBc3RTVkdMRUkzRmlPa3g5SGhjNlBFNGh0RTZnbWo3aHYvdDd2UDU1Z0o5MUtXaDZKVEVvK0EvbUJBM2wrZHpmN3AxNjlaMW1sLzlvb3N1T3BhNzl3WnVBdmFTaUxHMUVUWkFXaVZkcG1FWXhPTnhHWXZGUkRRYWxmYTJxNWFXbHVTMnErcnFhcXFxcXFpb3FHRG56cDJVbFpXUmtmSHBXOFdQU2RqMjd0dFAxY0ZxYXV0c1VRdlFFblJZYXJFWXNaaU9yc2VGcnNjeERCTkRXaTRvaUxJekJueGZ3R1QxNlZGMGFzdzRoT29UdXhWT0FHVm5mcmEreWZKZDJ3SHc5ZjNpcDM2TWFOVzdBRlNkK2VTeC9OZ2xZVE8yYlZENTkzOUxZcXU4Z1oxSUFDa1FVa2dwTUUxSlhFTFVnR0JjYUNJbU5XSm9Jb0pMaEhDSklHNlhIN2VyQ1krbkRvLzNFQWNYWlhIR0dXZHc1bTBmZmFyZjU2anQ3ZjFWQjZpc09raE5iUjMxalUwMCt3TUVXb0tFd21FaTRVUmNMUmFMRWROMWREMU8zREF3REZOSU14RlhpeHpjT1UzVHRIbnFVNlBvTXBiYjBhNzZPNDNKMUx6ZmVuZmdqeWVRbU1UakE3eVd3ZVNTU0pjRVRTS0ZpUlFHa2pnbU9nWXg0a1JsbklqVUNjc29RVE5Ld0F6VGFBU3BpL3VwcmEybHBxYm1VNyt1b3hLMlFFc0wreXNQY0tpbWx2cjZScHFiL1FSYVdoTEpBcWVvV2RaYXNsN055b0JXNzNpdm44ZmovcDY2REJSZGluQnpJc21nT0x4UDZ1ays2OW1pV2IwN0VEZE5nbVk2eEUyWHBraUtHem9ScVJPU1VWck1DSDRqVEdNOG1IUlBXNHhQdDRENXFGelJUL1pWY2FDNmhycjZCaHFiRTNHMVlEQk15SkVCalNXU0JiYW9ZWm9TS2FXUVNLMGd2OGQzZ1Mrb1MrQVU4dGFhWXdTZTIwSGtYOVVZVmFFdThacGRmYlBKT0s4bnVkOGNndGJOZXhRL0lSTjdTMzI1SiswMWYvTFhKM21HYS9uNTFiMVA5dG5EOS9PYktMaWlqc1l4THhPT3RkNGlFS012elJ2eGRXcjV0ZTJTU2pDRU01a0Ewa1JLQXhNQmFOSVFMaUdrUzJxNGhZWmJ1dkNZVWJ6Q2pjOXdvelUzazVtWlNhVWVZYWpyMkVQeVI3VFk5bGNkcE9yZ0lXcnI2bW13a2dXSkRLaXpzVjF2NjRKYUdkQ0VDN3BydWlydE9QWHdQL0V4d1JWN3U0eW9BUmhWUVlJcjl1Si80dU9qL3lFOXJON3NvOENuZVc1K2IrQlBKamdzTmkvZ0Fkd1NxUUdhQkdGQ1dwYzBLblhDVXJkYzBnaCt2NSttcGlZTzZVMGMwQnVQcjhVV0RJYW9QSENRNnRvNkdocWFrdlZxd1pBdGFqR2lscWpGNDNFUk42d3NxT1dDVm42OHZwZkg0LzZPZXR0UFBTTC9xdW15cnoyNnFmN283eHlQZHNuZnNmb2ZqL0RTM21JSzNINWFHZ080aDAxa2NHQUxlNXI4TkllTEdmK05temkzNlBobWZYdDV1dDBFZkp6T2FwTklDVUthU0ZNQUJsTEdwU2swWVVnM2NhSm91SEVSbGk2OHBoc1JETkxjM0V5dEhpVkh5NkM3SzRzc3pYZDhMTFpFdlZwTnNsN05GclZFRVc0c1VZU2JHQmhwdWFBbXBqUnRGMVFVRnhYZEM0eFJNbkJxSVFNNlprTzB5NzUrb3phTUdkQ1A4cGZ0dWpHMmVPR1grT3JzNzNMcnR5OG5ZK05hNnMrOWd4dnVlSkNaWS8yOHZhN2l1RCtmUUl3Q3BsdFdtdy93U2l2V1ptbU5JR0cxWVZ0dGNXbUlHQVl4YVJDVk9oRVpJeVNqaEVJaFdscGFhSXdIcVkzN3FZbzFIQjlYOUZCMUxRY09WVk5iMTVBbzdXaHBvU1VZSWh3SkU3V0tjR05KYTgyd3V3dnNMS2dJSGRneFdkT0VzdFpPUVVTdXAydi9Bb1pFNitxL3d4SHhrSjJibTFnYWtwbFBkazR4L2ZzbGZ1ZWMzRXppa2MvTnhmN21ILy80eDNQVHVLUjJsbFNrWmtuakdPakVpVWtyVTJycVJDSVJRcUVRVFVhSStuaUFRM296TmJyL3N3bWJIbzliTG1nOURWWnB4eWZ2ck9LdmY5L0EzdVlva1ZnMFVZU3I2OFNqQWRIVTdDZXN0M1lYekpweGpjdm45ZDZiN3JFMy8vUjZTcys3bHhjUHBOeFE5ekl6enB2R1ErK2xjUjMrN3llTXZXa1o1VXBUVGlPeXlQajFMZlRaK1RXeUN4MFg3SVR6eUY5OUM3MTMzazN2ZDY2bnh6Zjd0N21JajNTNzR2T25UNTgrTXdlSTdoa09xODFqaGIwMGFjWGFaRUxZRXVMV0prc2FKMEpDMk1MaGNLSUVKQjZpTGg3Z29ONUlYQnFmWHRpU3BSME5qVFQ1L2JRRWcwUjBBMHcvRmVYVmhHSzZuUVVsYmlSTWRkbGFpS3M5OS9qUDd3YSszT0d6eGo1azBhOWVwL2FvWG1JTGExWjlRTW5VU3loVDE4enBZeFZPbWtDM1MxTWEwMHZIa1AvMENGd2IvMG45VlgrazdwZDF1Tysrbk81Znl6cTYyeFVuQkUzVEpxNCs0d2RUSFZhYnAxWGNwSXVrdUxXNnBEcEc0cEJ4WWxJbkdvMFNpVVFJR2hIOFJvakdlSXZsa2paK09tRnI5Z2M0Y0tnNlVkclIxSnpvQXcyR2lNUk5wQURaY3BDcXhvamRCeXJpaGt5NDExSUtwQlFONVI4TWRybGNjdzc3ckRuWjhNWi9zdWlmTFVkK2hYVnY4OUo3ZzVoNVVTOTF4Wnd1NVBZbjc0RUJHSzlWWWpwQ2VlNUpaK0twM1Vuekk5dUpiVzlFLys4M2Fkbmd3amVwNzFIZHJqaHhkSE5sM2ZUSDR0djdPY1dOdHJWdHdsbmI1clRhWXNTSnhXSkVvMUZDTWthTEdhSFpDRkVmYjZFNjNrVEFDQis3c0ZVZFNIUVhORFEyT1ZxbXdzVGlKcmk3a1o4aENkZlhFZFRqMXVESTVLUVNJWkZhWHA3NDF1WmxQeW1aTm5reXBXTW1NL2FhZVN6Nlo5dkFuM2ZnVmR3ekdWNzY2WE5zUGtJTXV2YU52N1A1QzVjenVmQnc0cmVQelZ2MjRWZlhVeWNUcUI1NEovVEY3U3dERzNzUlJlL2RRTjdZamtiVHVQRGNQWUhNdWswMC8zZmJVcEw0MHYvaTBPVnZFWXVsL0lqUGRWUzNkMFVHWEQzblU5V3dGVjgyajFzdnN3YzNuc21WYysvbUhHdFBzZnVjTy9udU44NDV4a2MwaVA3Z2VRNk1hRnZEZGhqNm5wOHorR3BTRXdsQ3VLeERReE5KcTgxSUpCS0lXNGtFWGRlSnhXS0VaWXlRR1NWZ1JHaUtCNms3b3kvaDMxeEV6OWR2U2g1Ri8zRm11M2tzYmNvOXFtdnJPRlJUUzUwOXRjTnFtUXBIb3NUaUVreEJabEUzWEFlYThEZTdSSmEzdFE4VWlRZ2YySDVKM2FyNWQ4eDZOc1lOUDF2Q3M0Tmg2L0tmTStmK3h6aHIxVUttSmkvd2ZDWi83OXRzdU9iblBQVGNWYng2ZDBlekpnL3h5aXY3R0Q5ckxIbjJ0dzY4ellLZkxtTnpyRC9YM1AxdEpnZWVZODdTRnNwNnQ3RDVxVkU4K3RSTWh2bVVwcHh3dklWay9lSmlza3ZqeEZmK2srWU5SWFQ3eVptd0o0cnJnYkZFdnZNeUxkc05xTnBINU5VbytwNE9ZaVZEeDlKOUdvUnUvUUM5YUZKS0NNTkFPZ1RRTldFY09lT2lSTzdiZDNTM255WWMyRmZCQzgvK3h4SHZOK21Lci9IRjh5ODZxc2YwWFRTUWd1ZW1BaEJhdVkybUg2dzk4czlvbnB2ZTZQZkFPeGZ0LytrbUlHNGRpWDVTcEFuQ05FR0F4QUJwSUlXT0lWM0VFWmF3UldTY2tCbkRKeUprR2w2YW0zYlF3QmVvOTRTVEc2NWlkYU9RN09wWTJBNGNUR1JCbTVxYUNiUWtDbkhEWWFzSTE1QklhYURMREhKOVFqUkd3a1EwajlCTUtVaThPTTNyOWQ2VmQ5NjNXYms4bjdJK09RQk1uSEU1dzVZdDQ0TUttRHJDOFdTRmx6RC83cGVaOUt2SGVQNmFwN2tsWFNINDN0ZDU2ZUFvNXAyZmsvelcrcVhMNFBiSFdkbDdLNHNldUlzWnVkTjU5cm1yS0FQVy8vUmVYdG81azJFamxNNmM4TGpLdFBQSnFuMlQydnRpWlB6aU1vcXU5K1AvMW44VDNnUGlhMWRSY0VVUFdyYlhRZFVlQWovYTA4R2o5Q0Q3SnlNUXIvNmR3SWNHVE9yQW9udmdlZ3BuNUNOOGNlSXIvMEh6MzBMSGNQdXBUNS8rQTduL2tTZU82Mk5HMzZqZ3dPQ25qdm5uU24wOXJ3SCtEZWlBTHFXTUM3dk9UYUtSNkVyQUJPS1lVcE5DeElVaFJUeWVzTnBrbkFodVFtYkNKVzJvckdISGRTOFF6eWhtZUdhL0k3dWkrNnRhWGRCbWY4SUZUZlNCSmtZUjZZWUVhUkNQeDhIbnd5VWtoaDdIcXZMUjd2L2JydXVGRUZmNkNuT28vY2RqVEx2eVNvYWVONW5TeS82RERiRVk2VHpPa3VuZjVwNkJ1M25pcDMrbk1zM3RXMWY5SGY5RlZ6SGVZWUg1dkZDNVl4OWJkKzZtUE9vbEw3Q1B5Z0JBQy81QTRuYkZpVWY2NDRpaUhuaEhGZVByQzJiTWg2Y3c4V1pvZVM1azRNalpMTmYxRThqdCt3bitYKzV6V0Y3dFhTTDl1YjlSZSsxZmFQamhMdVNrU2ZTNHFjY3gzSzQ0a2JpRWR0bW1nVCs1d09HU2VscHIyNUlkQ1poT2w1VEU5RjFkMTVOZENSRVpTM1FsR0dHYWpDQjFldUN3SFFsdWdKaXVjNmk2cHRVRkRiWk93N1hyMWVKbVF0aU1lRnpFRFlISEpZUVJONFZoQ20xb1hxRkxjN252QkNoZjloQ3pYdXJGd2wvOW5xbEQ4dkVGL3M2TXljOTE4UFNEdU9XQnEzaHAxbk1zZW1ONnFxeXg3Qjh4SnY5c0ZFN1BjdlRkUCtESzU1Ynh1NHBCM1B5ckpZeXVmNW01Mzc2THhiUVFIVGlMSjRlb2krbWtDTnZmM3NCZmREN1pNK0xFbm5xVjVtMDl5UHVQYVJUbHVZQnFBbmNkSVp2bExTYjdtd1BRaXFESFAwdXR2Mkp1Qk5EdC8rNGthK2wvVWZ1ck9pdW0ya2k4RHVMYjZ6RFA2a3ZCdEtHNFgvZ1g4V1RNOVFpM0swNG9QVDNkcmgyaDlmN1hGdk9nYmxsdVRwZFVncEFTYVZyaUp1UFNGQ0llbHk2WEN4MHpVUUlpZGNKbWpCWWpRck1XcHNGb29VWnZwc2lkaDBlNDBndmJBYXNYdExHcHlXcHdUOHhZaTlvTjdycE8zQUNrYVk4andoUXVOQkVYcHBUQ2s5L3ZtOEE0YUdIelAzZVROK2FiVEIrU24zaUdRQXVIeXcvNFJzeGkvcFd2Yy9OVHk4aU5lWk1sSGRIM1htYU43eEtXcHJxVnZ2NU12ZnNIVEUyNnRGZng1QXRYRVkyQ1Q4WFdUaUlob2krc0pmcUMvZjhBL2h2M2dkY0ZzYU9vUFlyVjBUSjdPU0dueFQzcWZBcC9ra1h3MXJXRTkvanhQbkE5M1V1MzBURDd3NlJJeVNpSm5Cc3V2QTljZTVqYkZTY3RUSUg0NHJJQmQwOGVzZmVCVlVtWEZHeVgxR3E1UWlSTXAwVDVoNVlZVG9rdVRYUmNSTkVKeXhnKzAwT0xFYkVLZDFzNHBEZlJ6MXZRL2puRGtRaUhhbHBuckxVRWc0bkJrZEZXYTAzWGRlSlNJakdGRVU4dU9oWkNDSEZOV2UrOEZpRyttWGk0SE1vRzVsUDNyMlU4LzgvZGJIM3Y3engwLzFJK2lFRzBRM1hMWWVMZDMrWlNnZ1JhcjNMV3ZiU1JvaXN2WjloUm5qd2xhcDJVVkZIclcwcnV6OC9EVjlqZXhUVDMxQkhmN2pqcW9oQ05KcjVmRjBQZldJY1lONGE4YjViaUx1MkJaOUpZOHE3SUl2N1dua1QxK21GdlY1eE1lcml6cjcwNzU4dTVwRFRJSjF4UnFhVjJKQmlHSVF6RHdGa0NrbWlVanhFMEl3U3M4VWExdXArSTJiNDlUbXRYczVaY25kY3Fhcm9leDdDR2o1aW1hUzFtUVVpRWVHenUyTmxlV3ZWbjlPMC81S0VSaDNqaWU3Y3g3WUVWK0MrYnhjemhPVVRyRDFPelZuZ0o4KzhlMmZxSE5mQTJMLzJ6RjlkYzFsOWRFYWNhZmZ1VE9ha1VYOTlqTDhHUWEvK1AraC91UVh4dEVrVi8vd2FGUHk1RnZycVd4bDlXSDlYdGlwT0hRQXk5cytlbGw5SzJhTGROSDZsZHRHc21oQTNEU0pSLzZOTHVKVTJJVzhoMlNZMFE5VVlMMVhwVCsrZjc1ejllWWxkNVJic0ZMZjRXeXlWdDNUd2xvdEhFTG9PNFlRaHBTbTNyaHYvTkgxeDJ4anR3bkpzQ0Ryek44Ly9NWWVyMFVSU3BhNkpUY21EY3kxMzY5ZmZaY05VeG1Cc0RqdXB1aG1GUVdWbkpnQUVEanZxaHUvRE9ndzU1NDQwMzB2OWhrbkxQSC83d2h6dWVmLzc1T2lBSWhJQ3dFQ0ptdWFpR0VNSzB0bGxKYTlHTHRMZFkyY3RlY25KeTZOYXRHL241K1JRWEYxTlNVc0tnUVlQYTdFaHcxOVczcjFtelZ1Y2xyRFZyMDFROGJtQlkxcHFVQ1hVdEd6amdsdU11YWdCOUx1Q1c2VW84RktjK0V5ZE83RFN2cFhVdDZPZEc2YWhSb3lZOS8venpLKzFZbXhCQ3h5ci9FRUtZUW9qa21qN24zbEY3VzN4T1RnNTVlWG5rNStkVFZGUkU3OTY5NmQrL1A0TUdEYUtzckZXS05Ic3BTekRvcUZtemR4ZllvbWI1dTZacEN0TnFuZHIwZjZ0eVhTN3RGblZwS2hTS282VmJ0MjdYekowN056ZnBqa3Jwc2R6UnBFdWFXTmRwQ211N2xiQkxQeExyQnhLdFZ2YW1LNy9mVDJOakl6VTFOWVJDcmZXS21qMW5MUlFPdDQ3NXR1SnF1cDRVdG5iVzJvaGhRMllCZzlSYmRYcmk2cFYxZXJ4MnphWGU3T09JRUtMc3Z2dnV1NWoyU1FTWGxGSkxUTjZXd2pSTkxHRkwxclRaNG1ZM3lOc3oyK3dWZm9jT0hXcDkyeElkQmdrWE5HS04rVTY0b0RweG82MjFKaTFyN2NhdlgrMXl1VnczcWJmcDlNVTNwcURydnZZdkZCNkRDcXBha2VOTmZuNytWMHRMUzcxcHJEYk50dG9zY1d0bnRkbmlabzgxQ2dhREJBSUJHaHNicWEydEpSSkpMSC9Sbk5hYU13dHFENDgwREFQVE1LM2xMQWxyYmNtVGkyNEVWT1BTYVV6ZVBXZVRkWFYvWEgyenU0NmwxamVMN09sbmtEZG4yTkgva0NkVHZkbkgzMm83KysyMzM1NXdORmJiNFN3M3A5WFczTnpjeG1wekovWVhSTlBHMWhLMUpDYUdaYTFaRnB2bWRydHZWRy9QNlkzV3pVdjMrMGVkNHA5QURidzU2czMrSENndUxyNENlTk1TdHBpVTBpMkVjSk5JSkdneWdiREtQcVJoR0NJZWo4dlVXRnNrRW1sanRkWFYxVkZTVW9MYkt1V3dkNEltbFRGdTFaRVlwb0VwSlZKS0pJalF3UjFUZ0F2VVc2TTQ1Y25zQmtKOC9zOWpHbkJvSzdUVW5EWmJzVFE0YitlcmkwY052dUtPalpiRjVrRktIWUZMU2d3U2hoU21OREdrZ1NGMTRtYVV1QkVtSGcraXh3UEVZazFFb2cyRUl6VzBoQS9RSFB5RStwYmRIUEwvT3lGc3lXTGN1RzJ0R1JpMkcycEtwSmxZemdKU2VEMmVHZXFLVjV6eXVIM2d5L3Y4bnlmVUNKV2JRUStkZHFkNFlOL2lTY0FIU2FzTlBFSVNSMGdYQ0ZNaWhaUmdTaWtOdzhRd1RPS0dpVzRZNkxwQlRJOFQxWFhDa1NqQmNKUkFNRXhUb0lXNnhtYTBhQ1RoZ3VweHZXMFcxSEpEcmFRQlVpS2E5bTRaSVlTNFZsMzFpbFBibkhCQlRzOFQ4MXd0TmFlbHFBRjQzTzZyL3ZlM0Mvc2xMYlpFbkMxbE53S1lVZ29qRVdzVGNTTVIrOWVOT0hvOFRpeW1FNDFaNGhZSzB4d0kwZEFVUUhPV2R5UUxjVzBYMURRVGJtakNXdE95czdPdVUxZTk0cFMzMVBKNkorSnJKNExtQTZmMTZSNDdZc2lGcEd5elFsb1pVb2ttcFJTbWxKaW14TEFUQ2JhNHhRMWk4VGpSbUU0a0dpTVlpUklJaG1nTUJOR1NUZTVPVVV0YWEzYkNBTEhxVDcvTDBEUk5DWnZpMUVTNHdKc051YjBTL3o1Um5PYWI1bk95TXE2WU9XVkNobTIxU1Z2Y0VyUGFCSW1aUmlKaHRVa013eFFKbDlTYS9xSEhpZWx4SXBiVjFoS080QThFMGV3c3FHNjVvSGJTSUdtdFdTVWVreTY2NEZxZ24vb0VLRTROSWRNUzFwazNCN0lMb1hzSmJmYjhuU2k2OEVMbTQwVEpvM052SFpmR0hYVkowS1MwaFkyazFSWTNUT0p4RTkyMjJ2UldxeTBVamhJSWhYSHJ1SW5qeHNCRlhBb01LWVFoQllZbGFKWnFhdTdDMHVrbkpFT2tVQ2hPSzNvV2RMOEVlTnRodGJtRnhJV1FXdUl2a0pSU0lnd3BNVXdwRGRNUWNjT1F0dVdteDFPc3RsQUVyZFVOalNkSGhTUmFwNlI5aU1iR3h1RkNpTXZVVzZCUWREMmliLzZHb1hlOHpOWk8rdnJjTHRjbGE1Y3NTRTBpdUN5ckxUbk9TSnJTS3RnMUxjdXROZFptSnhJU1Zsc0VkN0p1elJJMk05a1RtaEExUU9UazVIeE5YUjRLeFFtazhqV3V1UE1WdHJWKy9NbnRPWkRKMDZjeGYwb0poeTFFYWR6RmkvL3JaL3owTVNkd3lmZ3VIcnJ4S1phbGprYnpuTXZpbGJPWWZJU2YvdUx3d2VPQVQxS3NOaDJCaGlSUitpR1FwbWtsRXF6U0R6dDgxamJXRm1zVnR0YWtnVFhGd3pSdE4xUm9tbmExdXRJVWloT01aeEFMZmp1SEczb0E2RlJ1V3MyY1I1OWlidmFQZVBiQ3d6VHlOMjdqZDh1cXlMdDhER1VudEhIQ3phVVBQc2F6NDQvOUozT3pNeWNCLzAzYk9Kc2JaTnlxa2hhbUZXc3pURk1haVQ1U21ZaTNHZWhHYTZ3dEhJMmhwYnFnOW9IVmlCb01CaThCaHFtclRLRTRxU3BIeVpncDNEeEVaL09XR2lyLzUxZHQzY3ZxTjVnMjdSZjg4citlWWV3RGI3QkgzOFhjMlE4emQ1TjF1MTdOUzQvL2lnblQ1akQwK29YTVdWdWQzRVVTcmR6RVEvYy96TWhwMzJYb2piL2c5aFc3cUxWdVcvLzRnNHo5MForWmUvOHZtSFRqZll5ODR4bWUzMzZNZFhleFNwNlo4MTJ1V0ZxWmVNN0dUZHgrL1lQYy9xYTE0cno2SXhZKzhNalFRZjM2RGJVRXJkVWRsYmhNS1RYRGxDSnVtQ0pxbUJpbXhOUWpIS3p6VXgrenl6L0M3UDd3Mzd5NVA4VHVEVytoMmFLV0xyNEdDSi9QZDVXNnFCU0trNDFPN2ZaM1dMYkR3N0FoK1pSY09JN1JOUit4eWxyUld2ditSMnd0T1pldlhYc25HMzk2RWFXZU0zbDB5Y004T3NiNjhab3Fhb2QvZzFkWC9vcTFzNHRaditSbDFyUUFzUW9XTGZ3TFc0ZmN3TnFWai9IaHp5YmhXNzJFT2F2cms4OWNWeG5tMG52dlkrMGZGckowVEQyTFhuZy9LWHhIaGJlRVcrNjlDUDcrWjE2czlMUHVOeXZaUFB3cUZsNllCN0VLRnZ6b1JUWVB1WTdYLy9yNGVjNDRtN1F5bzRDbUNTRmNBakJOSW9iRU1FRkNNdWFXR0swR3BtSFM4K3h6RTY2b3cvM0VibmEzRGswSWNhVzZxQlNLazZGbHU1bi8vK1l3UHhsajY4dkU2Yk9aUHlrUE9JZHJocXhrOFp1VnpDdk5aOTM2S3NvdXZLN2ptRnJmYzdsalVqRjVRTjZZY3loNytnMXFneERkOFRZcmdpTjRkdWFaaVRIOEpXT1lQLzBkSnF4K24vSXBsd0pRT09KTFRDNU9XSTJqUjVUQSttb3FJYzNZL2ppdlBUS0hVc2QzTHJ6bjV5eWRsSVd2ZEFxUFhyNk5HUXVmSkM5WXdyd254MUVFUkxlOHdZcmdDSjZjZVNiOXZGd0NMRTIxMm9SSWRDSWdoS21CUUVyTWhQVWxUTU9VaVRpYmhpSEJ0RnhTZDZxMVpydWhnQWlIdzFPQXZ1b0tVeWhPZG93dGxUd21mdmxNRnJ5OGpmTHArYnkySTU4cmJ5cytXcTgyNFlMcUVHME1FQzAraXhMSDJMbWludm40R3FzN3RNcDhueXJHNW1IWVZ5OWk5TXN2VXY3bDY1aHUvVTdSUmorQnBncHV2LzU5Z0Q0REJ2UWY4Y2tuKzk1ckcyZERCd1FTSVlRUW1rQ2ExaFJ6MHpTdGpnUXdwVXdLbTVZcWFwYWxCaUM4WHU4VWRYVXBGSjJUb3ZGakdWM3pQc3Yrdm9YTkplT1lXbkxzaitIcmtZbXZ1cHJLV092M2Ftc2FpUFlvUHM2TGxFS3NYN2FhelNVRDhMMjFtaGV0eFdHK25EeHllMzZKNVNzZlkvdkt4OWkrOGorK21HcXhTZG5haVdCS2lTR2xNR1dxSzJvbUZwU2FpZElQelJJMGU2aWJzTXM4TERkVUNadEMwVm5KT1l0cmhqZnc0cktQS0prd2dxU3VlVFB4RWFLeTZjaEJmdCtJaTVqdWZaOUZ5eXFvQmFMVm0xaXdvb3JSVTg0OXJxVWkwUzJ2TVBldGZPWTlPSWRIdit4bjBlTnZVd240aG85bE1odFp0S0lDUDVDaE4xNlVZcTBsVi9SSnBHWmFvbWFDRUVpaUVWM29ScHh3d0U5ZEhLU1pxR3R6cHlZTmJEYzBGQXBkck54UWhhSXprOFhFU1lQd2JmQno1WGpIcVBhU2M3ajUzSGQ0Nk00ZnMrM0JoVHg2dUlmd0RtVGVqNy9PZ2w4dlpkSTBQOUhzZkNaK2RUWlBUdmswbzkvYng5amdMQmE5TUlrUGZyMkJvaG4zY1VPeEIyWk1ZL0k5THpCMzlWa3NuektNaFQrZXhvSmZMMlhDTWo5UmIxNmZ5MlorZThnL2x2M25oeVQ3Um5GSm1SZ1pqaEJDQXlHbGtGNlhJS3BIT1ZnZncrVnk0OU1rcGxYK0lYcjI3RWswR2hXeFdJeDRQQzVNMDlTa2xDNWQxMyttYWRwMzFNV2pVQndkbjJhdktCK3YrbXlXMEtZL01tbDVDY3QvZVJFbHA4aDVESVlpUzNMR1h2c2kwQUlFZ0tDQU1JS29RT2hDWUdoQ21HNlhKcjBlRno2dlIyYjZ2R1Q2ZkdSbCtzak96RUJMellhU3FGL1RORTJicEM1VmhhTHpFbTJzNFBrVjJ5ajU4cm1uaktnQlpHWDZ6a3ZuaW1MTmFjTnVzNUpTSkRvUlRHdUZnWkhzUk5CU2luS1JVb3FHaG9aaHFHVXRDa1huWmMvTFhISHJNN3pVNHlvV1RzazdwWDQxSWNUWnE1LzVVZThPeEUxSWlVQ0NLYTFNcUxPSDFEcmNxUTN2SkhwREwxSlhqa0xSaVNtOWlyVXJUOTNhK1RGbm56a00yRzhMbXdTWGtHZ0kyMktUUW9LVWR1K29kZGhiclpKWlVWcEhGQW1YeTNXaHVuSVVDc1hKb250ZXppakxTa3RPK2tnY3NsV3JwTjAvS3BPVkhZWjFhS2tXMi9lKzl6MlhFR0tpT3JVS2hlSms0WEc3emh0NVpyK1VodmhrbksxMWxCR1dLeXJidXFOYWlodkt3dzgvZkFIUVRaMWFoVUp4RXVuMi9DUGZMWFZhYlRMUkZKL1lGaS9CbXE2THRFUXRPZExJYWJGWkNKL1BkNTQ2cHdxRjRtUlRXdEpyU0h0WEZLM1ZIVTBZWmFaMUdMSTFFYW81eXp4SXpGNDdYNTFTaFVKeHNzbkp6anpIWWJHMUtmdXc5VXBhV1ZKcDdUODJaR0tza1daYmExSktjZTIxMTdxRUVNcGlVeWdVSngyM3kvV0ZBYjN5WFNrV204c1NORTFLQkZoeHRqYnVhR3VNVFFCaXlaSWxYd1J5MUNsVktCU2R3V2hiOXVoOS9aMmlKcTE5b3poVzh5WGliTTZhTm9mRkJwQ1JrZkVGZFM0VkNrVm40Y3dCZmRza0VHZ2JaeU0xemlhbGlTbk5WdVVqVWI4MlJwMUtoVUxSV2VpV216V0Vkc21EdG5FMkhIRTIwMHkwV2JrZFk0cUVFT0pjZFNvVkNrVm53ZU4yRDZkOWpDM1pMNHF6bmkzcGpyWmFiT3phdGFzbk1GaWRTb1ZDMFZrUVFnejY1ZmRtNVhRa2JpUlVMVm5QWnJ1bGRveE45T25UUnpXOUt4U0tUc2VsNTUvYnh4SXlhK0FrR2hKaFQ5V1ZXSlVkVmhlQ2xJN3Nnc2ZqVWNLbVVDZzZIWDJMQy9vN3JMUjJ0V3hZSTR4YXV4QVN3b2FVVXJoY3JyUFZLVlFvRkoyTm5Lek1NeHlpMWtiWXJEaGJ3aU9WQ01zVkZacmRJeXFFVU1LbVVDZzZIUjZQZTFDS3FMa2NGaHV0NHBaSUlOaXVLTllkemxLblVLRlFkRFkwSVlZNHJMVDJKUjlwRWdnYUlHcHFhZ1p6dUhXQkNvVkNjU1NxTnpGM3pvTU1uWG9mYzlicngvT1JmU3NmdjcvUUtXalNtc2RHc3VURFRpQTRMTGFjbkp4QjZsMVJLRTVYL0x4NC95OVlWQWxVdjhHMDc3OUI1VEUvaHM2YUYvN0N1cDVYc2ZhbFgvRGtlRS9ibTdlL3hJUWJsN0ltOXVsZTRkRFNma1drTGRLMU94QVNncWJyQnVHNFpiRzUzZTR5OWVZcUZLY3BMYnQ0czNFZ0Y1WkE3Wlp0UkljUCtoVExZY0xVTnVxVWpCalVacXQ4a3RLTGVITGVGTVo3UDkxTExPeVJWNXpHSFJWdER5a00weFM2QVc0cHBkQTByVlM5dXdyRjZjWlc1dDc2SXV1Q1llcGlIamJmK0JIUllJQ290NUlKTmRleGR0NDViZU5Uc1VwZWZQclBMRjVmUlMyWkREdDNDZ3Z2dW9CaE9aVTg4LzFuZUdKSG5OaU9SWXhkUFpabkYxL0RhT2ZQN2xqTDdZL29QUHFuYnpEK3pkOHdjaG5jVUJwbTNZNXFhbU9aVEx6cE5oNmRWSXl2OFcxbTNQb09lUmRtVXJtOWdkb21LUHZ5TkJiY09LUlBXMHN0R1Y4VEpsSm9nRzdheG1NMDRZb0tJUWFxTjFtaE9OMFl4cU8vZllTVk0vb3lhdVozMmZpSE9keFJNb2o1enp6Q1c2bWlSb2cxanovREU0MWplSGJKWTJ4ZmNpZlhCRmN6Ni9GTjFGTENuYis4ai9uRDNZeTZhUjRiVTBVdEhUVU5NR1VXYTMvN0NHL2RPNUQxUzE1bVRVdXJhOHp3RzFpNStHRTJQak9Ob3ZVdjhwTTNRNzB0eTZ5OXVBRlNDT0YyaWNRZDNON0VEVUtJQWVwTlZpaE9SM1MyN2doUWRrWXh0RlR5UWF3dnczcWtjMWUzc0d4OUp0TnZ1b2hoT1VCT0NUZmNObzY4OTk5aFhlT25lTnFlWjNITmlNVGF3THpTTXltTE5WQWJ0RzhzNXNJeEJRbGg3WEVPZDB6SVplTTdPMG82RURWN3NRc2tCeFZKM05ZTlN0Z1VpdE9NeXRYUE1HTlpKZjZtTUd4NWtIV0VxUXQ2Mkh4ckJUYy8rRDN1ZEFhb212elVVa0JwVDZjNDlhV0VqOWpUQ1BUNERDL0V5ak5FZFNCTkRLNm9PQS8rSGVtWFlyRUpDWnBJbWFacnk1dTJhOWV1WWlCRHZjMEt4ZWxGeVpRN2VldVphWXp2K3lXVy91RVJYcDE1Sm1kZGRUY2JmNXNpYWdEZDh5aWluajAxYmQzSld2SW83Zkg1dnM3S3lnYkl6czM0MmJ4YmNqcTAyRkxRQ2dvS2VxdTNXS0U0VGFtcG9Ka3ZCcUFBQUNBQVNVUkJWTHk0aERLZ2ZFODlKYVhGNmUrWE00S1o0OE9zZU9GdHRyWUFMWlc4dVBRTmFzLzlFaE9QdTdCVnNlcC9LL0FEMGNxM2VlTE5NT01ubk1rWEI1K1I1eEN5MW5xMkZLUnA0dmI1Zk1YcTNWVW9Uay84bFExUU1vWTgvSlJYZWlpYjR1bmdubGxNdnZkT2FwLytNN05tcjhRZnk2UnMvQlNldldzTVJjZjlWZVdTVjdtYWFkZnZvaktXeCtncE4vRG9wRHlhZC9mSTY4QmFhMTFHSlJDR2FVZ1JqVWJ2OUhxOVQ2dTNXS0g0YkJpR1FXVmxKUU1HSEVQSSt1TlY2c1E1YVh5YkdiZSt6NVcvbmNNTktaWmdaWFhkZ241ZnZ2a2RJR0FmQWtJSUlnSVJFd0pERThKMGFacDBhNXBXcE02bVFxSG83R1JsK1BJY2JtZ2JTODA1NVFNa21xWnBCZXFVS1JTS3pvN1g0MjRYWXlOZGpBMXdDeUh5MVNsVEtCU2RnaDRYc0h6bEJXbHZjcnZiQ0Z2N1F5SnNtZFA0YkJVb0NvVkNjVUp3YVZvNlY1UTJWcHRWcUtzQmVlcVVLUlNLem82bWlSelN4ZFprRzNFVEV0Q0VFRXJZRkFwRnAwZUk5c0tXYXJuWlhWVWFrSzFPbVVLaDZQVENsdEFxcC91WnB1dEFKb1V0UzUweWhVTFJCU3kyekJSUmMyaWViQ053R3BDcFRwbENvZWdDWkJ6R0RXMWp0V21rN2FkWEtCU0tUb2UzdlhlYVR0WlF3cVpRS0k2R0VKdFhQTU9FNjcvTDBPKy9Sdm5KRTdaMkNZTjJBaWRiNTRjckZJcFRsUzEvWnRMQ2o0aWlzMjdSUXVadStoUWJwQ3JmNGFGbGZpWS92SkR0djd5VTQ3Y2twWjUxLy9NR2E2cVA2czZ1dzFoczFrWjRNR2x0UzFBb0ZLY29XemRWVURSbUlENnFlRzFQTVJjTzhSejdnelEyVUp2ZGx3dUhIdTljWXdPdnJWak5hMGUzRmt2cjBFcXpUTFhFSm5nUVVrcXAzbnFGNHJQVDJhWjdsUC9Qazh4WVVZVy9TY2ZYUFJPZm5samFVcGpkazV0LzNINllaTzJtbDVuN20zZFlYNlBqNjNrbTAyZDluZm5qQzRpdVg4cUV4eitpTGdpNTJabU1uMzBmejA1cVcvNGFyZHpFZ2wrL3dpczcvRVN6aTVuNDFXa3NuSDRtUmNDNlJmY3hOM3MyRzc5MVp1TE82My9EME4va3MvekhlU3k0ZnkxYm04S1FuVXZaNWJmeDZsY3IweTV6ZWZTMmN5Z0J4UENwMDRBZ2lla2VmcUFGQ0FxSVN0Q0JPQ0ExRXBhYlFxRTR4U2o3Nmh3Mi92WW1wdlk4aDBkLyt3aHYzWHNPZlMrY3pjWS9wSm1RVy9rYXN4NzlpSkpaOC9odzVTOVllMXMrNng1ZndxSTk0QnMvaTQwUGpxT3crN2s4K2FkSDJva2FzUW9XTGZ3TFc0ZmN3TnFWai9IaHp5YmhXNzJFT2F2ckQvOENTeTVsNVI5bU03MTdKbFBuUHNLcnN3WmFON1JmNXZMUVdqK0gxU3FSbUJPZUdNcW1vUUdHdWdRVWlsT1VtaXJLZXc1aW1CZkt0MWRUTnJ4djJydHRYdjBPbGNPbk1HOThBVDQ4RkkyWnlyemhEYXhZWFhIRXA0aHVlWnNWd1JITW01bXcwSHdsWTVnL3ZTK2JWNy8vS1pNTTdaZTVySDlyRy83Mld0VytWOVRDRGNSSXJsTlFLQlNuQmx0NTZJNi9zS2JKVDEwc2syazNyazdzRE0xK2hBbnZUK1BWZVdQYU5JblhOb2JKS3k1d2ZDK0xrcEpjL0pYMVJCbVlzb292UmRnYUEwU0x6MnF6S0xtb1p6Nit4bXBxajhOdmtsam1Fc2FmMEtxalF1TVk3cXhRS0xvS3cxaTQrR0dXZmptZkMrOTZrSTEvbU1YVW5pTVNMbW1LcUFFVTljakVYMTJQUC9rZG5jcWFBSGs5Q2c0cmFnQytIcG40cXF1cGRDaEpiVTBEMFI3RmliSGhuN0dnekZybVFsNTdyWklwWDlzSVcxaGRCQXJGcVVpSThtb29LOG1DeG1yS3ZjV1VkU0F5bzZkOGlaSi9yMlhSSmo5UkVvbUVKOTdQWi9xVWdVZDhGdCtJaTVqdWZaOUZ5eXFvQmFMVm0xaXdvb3JSVTg2bERDZ3B5Y2UvNVNQV04rcEVHeXQ0Zm5XRlE2RTgrTHc2dFRYMVJKUGZTNy9NSlE4aVIvVnJ5NFFyR2xJWGdFSnhLdExBQjVVRm5GVUM3SzJpdHVlZ2p1dlBTaTVsNmR3d2MzL3pDQ01mc2JLaTk4NW1YdWxSUEkxM0lQTisvSFVXL0hvcGs2YjVpV2JuTS9HcnMzbHlTbUk0ZDlubDEzSFBscVhjL3YvV0VlMCtnS2tqOHNsTi9uQmZycGt5a0ZtL2VZUkplKzdrclpuUTBUSVhLV1U0alhYbStMOUFDSWtwVFNta2xCOEFJOVZGb0ZCOE50UXlsK1BBWVphNW1GSnVjWTI0Nm9lMGxuczRGN3BFQlVJWGdyZ21oS2xKS2YzcWJDb1VpczZPbExLbFkydk5ucDJiUUFPVXNDa1VpazZQYWNvV2g0REpkSUptZWFTNHBaUk5RZ2gxMWhRS3hjbm5NTXRjRE1NSUhNWmljK29hbXBTeVFaMU5oVUxSMllrYmhqL0ZZcFBweFUyZ1NTbnIxU2xUS0JTZG5XaE05M2ZnZmtySHVnTUFOTU13NnRRcFV5Z1VuWjFRSkJxZ2ZXeE50bmRFUWROMXZWcWRNb1ZDMGRscDhnYzdja1dUWW1kbkM5eVJTS1FtSnlkSG5UV0ZvaFB4eVlFYWRSSlMrR0Q3bmtESG9pYXRGYU9KUVd6dWd3Y1BIaXdzTEZSblRhSG9SQXpvMDFPZGhCU3E2eHZ0Y2cvVEZqV1I4RDZsN1lJS0lkQTBnVFp5NU1nYWpyWUhTNkZRS0U0T2tibS9XaG82akN2Ni85dDc4L2lvNm52Ly8vVTUyK3hMbHNrZUVoS1dFRVJBSytEdmlsaC82TldxYlM5WVdyUzlTbHUrYVB1cjFHOEwxUXE5dHFCdG9jdEZieXRXcjFoYnNGcWdWV3BSUkJUVUZsY1VOU3lhUkNBUVFzakN6R1NabVhQTzUvZkhuSFB5bVpNelNiQnN3dWYxZUJ3VEFtYVpNL1BNNjcxOTN0VGFDWSsrbzNiMzg4ZU5pNHZyVEJXbDlJQU5aRHJqM0RKRURMQlJTdWsrL3RCeGNYR2RxZEowL2FBTmFobHV6ZHlZYkFTa2FiRHB1djR4ZitpNHVMak9WQ1dUYXJPRFkzTnMrU0FrdmZPQWFwcld5Qjg2TGk2dU0xWHg3cDVtaHhBMGErdUhsS1poc2tGUitONWtMaTZ1TTBFcGJGajZBOHgvVFUzL3NXQWFmbjdIWjFveGhCd2JJU1JkR1FWQU96bzY2dm1EeWNWMVppblI5QllXM1hrM3hsOTNHMnErOWtzczJOekVuREk3TkVEVWJYNGVhM2VmckxOa1c3QnA3U3ZZM25HaVA2K002eGI5Q3J2Vy9STHZMQmdIQlVCRC9iNDJCbVkyMTJhT1V4SHJ2d0lBV2xGUjhSRnduSThaRnhmWHlWUHlReXhaK2lSMlZNM0Erc2QvaHMwTHowZlR3dzlnd2ZiamdWUVBkcnl3RVd2Mm5LelQvMXV3YnMxTDJOWnhjajY3UzVFUlRBZVNpVHUrdjd6RDd0UUlvSU5rOUxTbEN3Z2tIWXFhMW00WGdBbjhHY1hGZGZvVjNmNFNObUFTSHAxN2Z2bzQ3M0ZYWU9uMWIrT2FwOTVENjVUSmlEaWNOTHRwNmYvRmtzTGI4UExjU3V4NDlPZVk5Mnc3b2wwcWtudVdZZnpqQU9RUldIemZYRnlmQTBRM1A0QkpheVZjVjlpT0hRM3RhRlZLY2YzY0c3RjRTdm80NzhTMmh6QitUU0hXci93OGFnRUFIMkxSMXg1RzlMcy94MzBYdHVDUk8rL0Rpa1lWaVZRUHR2N3dCMWdOd0RYOGFqeiswOHV5SHorZW9XNXNYN3NhUzU3YWhmb3VJRmcyQm5QbWZobTNqZ3M2L0Z1Nnh3WTFIWUJPQVFvS0NxUzN2aE5pTEJhbGVqckhoblJsdEU0UUJBNDJMcTR6UUUwTkxVRFZaQU1xYVZXUHJrVHdxUTlSajhucDdVOERhT0xOUDhEck4wZXgrczY3c1c3S1Fxei9RbDcvZjlTcFlzS2kvNHZsWlRKYXR6K0tHZis5R2xVcmJzT05oWU45ZDRYNCtrL3Z3ZGV4RS9ObVBJM3FleGNOYlRjQ0cyYS90UjYzUFFVcy9PblBjWDFaQ25VYmY0K2I3MW1QNm9kdnhwVzJDVTlkMSt2UlA3ZW1BOUNKMlpSTFFUVktJWUdBQ0NJRVFnZ0ZRRlZWcmVOUEp5NnVNME9KTGhVdVJjcGNmZWVUNEVxcEp5NW5WREFDVThyU0s0VWpVNjdHRFFVZjQyL3ZuYUlEdFJVWnJsUVBXanVpaU1LTDJxdS9pYzBQek1JMGg3RjFYVXZ1US8vOEdoT0NFaW9RQUpSQ0ExTThBRUI3ZW5vKzRFOG5McTR6UXk2ZmhFVFNCckV1RlFsWkduVFA1eWRUQUdVNVFMVHoxR3pqZEkyYmdjZS9Pd2E3MWp5QTZWKzVDOVAvYXowMnRLaU9QMXV5TjM3UUJqUU45cFlQUWloSlQ4SlRRdnBHcXVqZXZYczUyTGk0emhCVjF4UUNlejVFWFVaNDJvUm8yVWdqaHlVRFNDRnh3dGFkeDlEVUFnVERualI0RkJtdVZPcWtWUlFUTFMxSWpMNE05LzEwRVY3LzAxMjRiMm9QVnQ2OUdtc2RDaEhSOWdNdEJzQTBCbXJHWlZSRUtRVUZJQkNTdmt6aVRaNDgrU2lBRC9sVGlvdnJESEJzRjF5QjYrWFhzZVNoT3RUSHU5RzYreVVzV051T0s3OHdMcDFmeXlsRmJjRkIvTzNsSmtTUlF1dnU1N0hxZmRYMldUd295NUZSLy9ZdTFDZUJSRHlLS0F2Q0l6dXhhWGMzZ0JUcU56K05WVWNxY2EyWnZCOWVpZXFPWGZqYmU5MEF1bEczK1NWczZyUi9sMEdVNWJSais5dE5pQUtJZG5RUEdZU3QyNS9BTlhjK2dVMHRLUUJlUkFvRGNDWDdnN1EzZ1krT0hkN1pnNnl0SHVsTHB3Q0lBSm1raXdoVzhRQ0FydXY2MjRJZ2pPUlBLeTZ1MHl5bEVnc1h6Y0tTM3p5SmEyYTNBK0ZTWEhuRHJWZyt4V3Y4Z3pMTS8vWmxtUGZmdjhLRTM4c29QZThDWEhsZUFFMFpuMFRHdEp0bTRNcDduc1kxTTU4RXdoV1lmOWYzY0d1TjhkYytMM1k5L2l0TWVxOGQwWnhLWEwvd3hyN0NRZUcvWWVsTkgrSzJleFpoVmRLRENaZGVnQ21sOW0reUVyZk1uWXg1di9rVkp2d2VDSlQrRys2N2J5YW1EYUhYdit3TE4rUEJqdlZZOXYwZjRMWXV3RlZRaWV1L2U2TlI0ZTFyMEtXVWZ1QVVocEowVlZTbjZlUFhBRUtvTEtiZEdpRUVSQlJGV2RkMUNZRGMwOU56cTh2bCtobC9WbkZ4SGI4K1RRdVRvNXNmd0tTblIrQ1orNjRZWW52RzZkSGhveDIvTEw3c1AxOUZla2x5SEpsTGtuc0JraElJVkVLSUxna0NWU1NCS29xY3pyR1psZEZvTlBvMmYzcHljWEdkS2RyZGNHQ2Z6YTJ4T1RaS2pKRXFvekdYa25TT2pRcHNuRHByMXF5M0RTcHljWEZ4blc3RnYvYUQ1UzNJcklSbWdNMjhDQ0dVa0w0VGRDVkNpSEZLT1BTdFc3ZnFsTkx0aEpEcC9ESGw0anA3Rlp4K0szYWY0YTl5VmRQZWFtcnQ3QWMxWTVTS0FzUndiZWFNYUJwcWJCOGJqTGU2cXFyYitXM240dUk2M1lyR3U5OTNDRVBaY0pTbVkxQllibzJZN1I3R0g2aVpaNHZGWXEvemg1U0xpK3QwYSsvSEJ4c0dnaHJKT0RtWFFDQkdINXNnSUNQSEJrRC8xcmUrdFIzQU1mNndjbkZ4blVZZCs4YWlYemRsZ1pwRGpnMlVDQVNDUUtnZ0dJNk5FWDN5eVNkVlhkZGY1bzhyRnhlWGt5ZzkrVjhqcGFxdjFUVWVZc05RTlJOdWZXZXdNZUVuQ0JFZ0VDRmRQQ0NFbUgxdUZJQ2VTcVZlY2JsYzEvSmJ5TVhGQmFTN1lRK25QRGlTVUtDQklKN1FUdXJYYTJ0cjI1a3RES1hwQWdLbDV0UUJDRktVQUZRRXFBaXFpMm13QVZZdm0wNHBwUzB0TGE4TUd6YU0zMDB1TGk3b0ZOZ1JDd0t1QVB4NVFmaDhmaFNlNUsvNTRVZC9yM2VBbWdaQU0xbEZDS0ZFRUNBSUFtUlpocUs0NEhLNTRIYTcwOGNXbVlVRDA3RlZWbGJ1QnZBK3Y2VmNYRnlIVTI3QUZVQmhVUWw4UHY4cENIVnAzZHh2ZnIzTkZvYXFCdFJzUnhhWlBXeUNrVjlMZzA0UUJBRk1uczJDbTZxcVcvZ3Q1ZUxpT3BKUTRBOEVUOW5YNitycWVnM091YlcraW1qYWtCa3RIZ0lFZ2NDQ21paFk3UjVtS0FyamY5U2owZWhML0paeWNaM2JvaFRRcUpEZHFiVnR4OHJ2ZlFXZnUveGlUTC9xUC9ERHZ6Y0RBSnFmL0NZdXZlUmk0L29tL3R3ODlLOVpYLzlSSFRMemE3YkNBWFRMaUJtVEJvSWdnQmhRRXdVUmt1bllHTGhSU3FtZW41Ly9pcDdldmx6S2J5OFgxN2twUW9CNFVzdVNVNHZoNWZzVzQxbDhDeXVlK2crTVZHS0lJUUFBS0o3MU1MWjlJWUZFKzkreDRNWm5qdWRMSHJyMmMxZnR0NFdneG1VUFF5azBWWWNrcDZFbXNvN05zRzlPUzBjMVRkT2U0N2VXaTR2TFdjMzQ0TU1rYXEvK0hFWUdBTGdDQ0xCSDRMcGNjQjNudXVLZW5wNXQyZHdhSWRDUXNYS1BVbDNYcUo1MmJGUVFSSWlDQ0ZIdjdPL1l6TGxSQUhvc0Z0c2NEb2Uvem04Z0Z4Y1hFMzlpNDUzL2lmdDNKSkdNSjRGZlhJdlAvUUtBTWdWMy9ua0pwcnFHOWpsZSsrTXlQUGprZHV5UEt5aWFlQTNtZmYrN3lPOXNmTmNHTmF0d1lMUjU2QVJJQTgzNFRNbDRITzN4T0FSUEJGVmxQb2pFblFhYmVlbTZuZ0czM056Y3pUd2M1ZUxpeWxRZXJ2N3BNN2dhZTdIeWhyblkvL1cvNGQ3cHg3ZUpZZCtUUDhDUC8xNkJPeC9jaEttNWNieTI4bmI4K0h1L1BmVGUxcDgwR3NaS3RZTU5sT29naElJUUtnb2lSS0locVJKNHdpRUVqVFlQVVJRZ2VuSWdwQzJja0RFdnlwUlVkVTNUTnZJYnljWEZkZUswRnh2LytpRnF2L3BkVEMxMkFhNDhUTDdsRzVoNDlPbVg0SmhiNno5S1JVaDZPeFVoQURIQ1VFRVVJWW9pUkVtQ0lJcWk1ZGpNa0JSOSsvdTB6czVPRGpZdUxxNFRxSGEwdC90UlZCem8rNUNyQWdGdjI0NXNZYWdGdDR3MkR3QWdTRU10WFEwVlJSR1NLS2JCWnNLTmdaclZ6eGFKUkY1QmVrczhGeGNYMXdsUUxuSno0empjRnJNK1FudWI5L3ptalFQMmFtaktoQnJKUERIWE9DazMvZit5VUJORkNaSVc2NnVLbW0rWlk0eXNBZFJrTXJtQjN3d3VMcTVQckl3MWdhTnc5UmRIb3U2UjMrTGw1Z1NBTnJ6eTJ3ZGVRdjlKZzR6Q2dYV3dwTUVvUVJSQW9OTkVra0lVQ1hRS1NKSUlpY1lnc1k2TnliVVJwanFxSFRwMDZPK1ZsWlVMK2QzaDR1SWFpcHFmL0NhK2ZGL2ZxdUozYnJ3WTl3T1k5TU5OK01YbkFxaVk5WFA4VjlzUzNEL3ZTdnc0cmtBTGpYalh3YTNaRzNNcFRPTWxDQkJrQlg2dEcvR09JMmpzSkpEOVpSaVhtd001TUJKa3hJZ1I2TzN0Ulc5dkwwa2tFa2lsVWtSVlZhTHJ1a0FwbFpEZXpPcFNWZldQZ2lCY3lXOFpGNWV6UGsxYnFvWXFTb0czWTJFVVZweThyWnlxcXI0NHZLSnNKWUJlOUcyak1xOXVRa2dDUUlvUW9oSkNkRkVVcVNUSlZGRVV1Tnd1Nm5hNzRYRjc0UFY2NGZQN0VRd0VJRWlTWkRxMmpPcW9QUnp0N3U1K21qOTF1YmpPTFJFQ1NFUkhWOWZKMi9IVTNIem9WWWNRTkFWQUpZUmticVV5MkNRSUFrUlJvRlp1VFJJaFNSSmtTWUtzS0gzRkEzc1J3VllkMWUrLy8vNE5BQTd3VzgzRmRXNnBRRW1nSzNaeUR0V21sRGI5MTQ4VzcyTEFscktGb1RyTUk0ck1uUWJXMkZRZnR5UlJTb05ObHFFb1NwOWpZL05zZ2lDQW9hTU9RTHZycnJ0NlVxblVPbjZidWJqT0xSVXBDZEJFSEVlYW0wNjRjNHZINDg4OXYrbTVsSk5iQTRnOXY1YUdtekVYYW9lYUpNdVFGUVV1bHd1U0pLVS9hUHdqS29vaTBUU05Db0lBVGRNbzQ5clVBd2NPL0xXcXF1cTcvRlp6Y1oxYjRlZ0ZnU2lPSkxweHVDMk8yRkVSWGNrVGM0THVLNis4OG5ZV3Q2WUMxQng4VDNPSUd0SjFhSnBHTlUyRnFxYVFTb2xJSmtWSXZTSjZaUWs5THFVUGJFeXVqUTFITTJaSFI0d1lzVXRWMWZXQ0lNemd0NXVMNjl5Q1c2RmJSYUU3QmtxQnpGVXBuMHlKWk9wdmx5MWFkTlRtMUZJQVVnUlFRY3d3RkZRZ29DSUJaQkZRSk1DdEVIZ1V3T3NDdkc0ZzRBVkNmaUEzQ0JUa0FJSXN5N0M1TnRnSDQ0MVJLdzNwU1FRZWpuSnhuZU9RT3hIYTNYamdWUWUzbHRIbWtiRml6emgzVFJUVG9hZ2tpcEFrRWJJa1FwWWtLSW9NdDB1QjErUHVBNXNvaXVaYmFvYWx6SkhoVm5VMFB6OS9DNlgwVlg1N3ViaTRQcWswVGQ4K1llYjh4Z0hjbXRaMy9scmFYSW1FVUZFZ0VBV1JTcUlJVVV6RFRaWWtLTElFdHlMRDQzYkI3M1ZEVUJRRnNpekQ3dHhzRGJ0V0VRR0EydFhWOVFTL05WeGNYSjlVQnc2M3ZqQ0FXMU1OdDZhVDlKWjNLaEJBRUlqaDFBUklrbUE0TlJHS0xNRmx1VFVYQWw1UEg5Z2NpZ2ptaUZYRzdDZ0FiZnIwNmVzb3BYelpDeGNYMTNGTHA3UnU2bGUvLzM0V3QyYk1oL1lWRGRKZ005eWFLRkRUcWFYYk8yeHV6ZU5HTU9DRDRISzVrQVZ1R2E2TkRVZGZmLzExdGJlMzkzRitpN2k0dUk1WFI5bzZuMjFxN1dTaGxqU3VOTlNJZVpKSHVuOU5NUFlhbUMwZWJHNU5rZnJjbXMvdFFzRG5SWTRkYkRhNHNhN05QaGl2cmxtejVra0E5ZncyY1hGeERWV1Uwb1lmM2YrSEhZT0VvZFpKdVFSQTJxMEpFTVYwYmsyU1JNaWlaQlVNWElaYjgzazlDQVc4eUEwSElIZzhIampCelZZaFpVTlNEWUEyZCs3Y2FHOXY3eC80cmVMaTRocXFXanVpR3g1YTkzekN5YTBSSUdVVURUVEFQRWd5N2RoRVVUQ0tCUUprVVlRc0c3azEyWUNheDRXQXo0Tnd3SS84bkZBYWJHNjN1MTg0bXFYOUk2UDFZOE9HRFU4QWFPQzNpNHVMYXdodXJYSFp3MCsrWllNYTY5ajZXanpTUlFNSWhFQzBpZ1pwdDJiT2hDcXlETGRMaHNlbHdPLzFJQlR3SVM4Y1FGRmVEZ1NmendlMzIyMjVOaFp3OXRZUFc2NU4vZktYdjl6UjA5UHpHTDlsWEZ4Y2c3cTE5bU5QLy9LeHAzdHRJYWpwMWxTUXZoVjdwbHNUQlVMVGJpMGRocWFCbGxrSjlYbmNDUGc4eUFtbTNab2tpUkQ4ZmorOFhtK0dhenVlMW84Ly9lbFBqMU5LK1FtN1hGeGNBN20xUFhmLzVvOXZPa0ROZEcwT0xSNUcwVUFValJZUGV6T3VESy9iQmIvUGNHdWhBSXJ5Y3dBQVFpQVFnTS9uZ3owa1plRG0xUHBoVlVpLzhZMXZIT3Z1N3Y0OXYzVmNYRnpaMU56YS92UURUejQzV0c0dG95RlhNTnlhSkFxMEg5UVVHUjZYSzhPdFJYSkRjQnVMVElWd09Beld0UTFRSmMzcTJtYk5tcldHVXZvNnYzMWNYRngyNmJyKzV2UnYvSENIQTlRR2NHdUFLQkJJZ2psZDBEZGg0SkpsdUpWME02N2Y1MEU0NEVOZU9JaWkvRnpyYXdvNU9Ua0lCb09XYTNQS3RiR3VqZGthYjIxcjNyaHhZeklhalQ3S2J5RVhGNWRkRFUySG45blZlRWpGNEpWUUhTQlVTQjhrU1VWQmdDU0pobHRMUTAyUm1mWU9qeHRCbnhjNXdRQUtjc1B3ZXZwMm13cjUrZmtJaDhNSUJBTHdlcjBXM0V6SE5sVFhGZzZITitpNi9uZCtHN200dUV5bFZQWDVrWitidDh2QnFRM3Uxc1JNdHlZYkJRT1AyeGlkOG5rUUR2cVFueE5FVVNRbjQrc0tCUVVGeU0zTlJUQVloTi92dDNKdERoTUpBN2sybFJDU09uTGtDSGR0WEZ4Y2xuYnNxdCtFekdaY0MyeUdXMU9INnRaY1ZudUhNVHJsOXlJM0ZFQkJYaGcranpzVGJDVWxKY2pQejBkT1RnN01Rc0pRY20xbU1ZRjFiY1hGeGY5SXBWSVA4OXZKeGNYVjFkTzdadkxzN3grd1FTMlJ6YTBKUmlYVXlhMHBzZ1NYeTJ6dmNDSGc5MXJ0SFNXUnZINWZXL0I0UENncUtrSmVYaDVDb1ZEV1FvSlRoZFRlMTBZSVNYM3d3UWVQQVRqSWJ5c1gxN2tyQ2h6ODYrWi9ib056d1lCMWF4bDlhMVlsMU83V0ZLTmc0SGFuMnp2OFh1U0ZnaWpLejRISHJmUUhHd0NVbEpRZ0VvbUFMU1I0dlY2NFhDNWtHNUxQbG11Yk9IRmlmWGQzOTRQODFuSnhuYnM2Y3JUanlhL2UrYXNPbTFQTDZ0WklobHNUTTg1WmN5a3lYSW9DcjF1Qnoyc1VERUlCUkhKREtDbkljL3o2QWdBb2lvS2lvaUprS3lUWXFxVFVuRXF3citnelhkdlVxVk1mMDNYOVJYNTd1YmdHa2VJNzYzNGtWZE5lL3N5WGJudGpDRzVOTTl5YWJrNFpTRWJmV3I4cXFIRXlic0NiYnUvSUR3ZFJGTW1GSWt2WndRWUE1ZVhsTUFzSlpranFWRWhnaCtTWkdWS2RkVzA3ZHV4SUhqbHloT2ZhdUxnR1U2amtyUHVSZHU1cGZLYXB0ZE0rWFpBQWtDRE1CaXJEcmVuRU9rU1NXRTVOdG8xTmVUenBrM0hOMHpzSzhzSW9MOHJQK2owSTdCL01rTlIwYlFQMHRsbXVqWmxJWUoxYnFyaTQrT1ZrTXZrYi9zemw0aHBBa1pGbmxXdUx4cnNmdlhEVzdSOGJibzJGV3QvQWUvL3oxcWhJQ0UwUHVJczBmU3F1bk9IVzBoTUdYb1NEZmtSeXNvZWdqbUFyTEN6TUdwSTZqRnRSU1pJeUNnbXNheU9FSkxkczJiS0tVcnFEUDN1NXVMS0lDRUQxcFVCT0JTQjdQdFUvaXE3VGR4LzQwelAyZ29HVld5TkEwZ3hCbWZQV3FHQ0ZvR21vWlp5SzZ4U0M1dWVpTUM4ODRQZlNMMEF0TFMxRmUzczdvdEVvNHZFNGVucDZrRWdra0V3bWtVd21rVXFsb0tvcVZGV0ZwbW5RTkEyNnJvTlowNmRSU2dVQTZ0VlhYMzI0cmEzdHdkemMzSlg4R2N6RmxjMWVpRURKdUUvOWovSGhuajNyNy9qdngrSU9VRXM3TmtMU0JRTkNOQ045UlFWUmhDakxrRnd1U0M0WFpJOEhpdGNMbDk4UGR6QUliemdNZjE0ZWdvV0Z5QjAyREFValJxQjAzRGdnRkJxNll3T0FVQ2dFcDk2MkxJMjdHU0VwSEpwMjgvTHlOcVpTcVlmNHM1ZUw2K3hWUEI1ZlhWTlRzNGNKUVRPZ1JnaGgxK3JwQU5LTnVLSkl6Vll5V1phaEdKdmMzVzQzUEI0UGZENGZBb0VBY25KeWtKK2ZqNUtTRW9RR2dab2oySUIwSWNIc2JXT0g1TFBOa3RxYWRuVkNpTlZwVEFoSnZmcnFxNDlRU3QvbXQ1K0w2K3lUcnV2dlBQcm9veStpZjE2TmhWcktNRHNhSVVRM0pwaXN0WjhtMUV5d2VUd2VlTDFlK1AxK2hNTmg1T1hsb2Fpb0NPWGw1VU16d1U0ZmxHVVpaV1ZsS0N3czdGY2xkVGh4ZDZCQ2dnb2c5ZG5QZnJhcHJhMk5oNk5jWEdlaDl1elpzLzQ3My9sT05Hc0kydGUzWm14MkoxbmRtdW5VUEI0UC9INC9RcUVRY25OelVWaFlpTEt5TXNpeVBLVHZTY3IyRjBWRlJlam82RUEwR2tWWFYxZldYQnViWjlOMW5lcTZibTYxMGlpbHhIQnRRaVFTMmRUYjI3dkM1WExONTA4RkxxNnpROGVPSFh1NHRyWjI3d0J1emRvK1piZzFhb0xOYlBobm5acnAxdngrUDRMQklISnljaENKUkZCU1VvS2lvcUloZjEvU1FIODViTmd3ZEhaMkloYUxvYnU3T3dOdVpnSEJ1S2l1NjhSV1NLQUFkRXFwVlNYOTR4Ly8rTDl6NXN3Wkt3akNkUDZVNE9JQzl1M2I5Nm45M2xWVmZlbWVlKzU1MlFhMVhwdGJ5OGlyQWFDRUVPdHpHQ1lJQUpER1J2cXR3UkhyWThmN1dBMElOcC9QaDdLeU1zdTFkWGQzVzJCTHBWSjIxMFkxVFNPNnJsUGpHOU1OeDJibTI0UnZmdk9iSGRPblQzK3dvcUtpRmtBSmYxcHpuZXVxcUtqNHRIN3J6YSsrK3VxR1ZhdFc5VGlFb0wyd0ZReU12SnB1T0RWcU9EUnE1dExNSWtFNEhFWnViaTRLQ2dwUVZsYUdxcW9xMU5iV0RqbTNOaVN3QWVsQ3dyRmp4eXpYMXR2Ym13RTN0dlhEQ0VkQjA0TFIxMFlvcFdvYXprU29yS3g4SXhxTi9rOGdFTGlYUDYyNXVENmRPbmp3NEI4dnVlU1NKZ05xTE5Ec0lhaHFUaVlSUXF5OG1yMVk0SGE3TGNBRmcwSGs1dVlpRW9tZ3RMVDB1S0VHWkNrZU9QMVdZVnRBMkVGNVd3c0lkUmlTWjhldFVvU1FaREFZZkNLWlRQS1JLeTZ1TTFSMVM2YWlxdXBtckkzMi83dDRQTDY2ckt6c2JXUzJkbGdocUMydlprSE5WZ1dsMmFER3RuWjhVa2M3SkxBRkFvR01GcEJRS09RNGxXQ0RtMWtselJpU054K0k1NTkvL21GZDE3ZndweEFYVjFyUnVyVllNdThhVEIxZmc2cXFHb3lmTkIyekZ6eUNyYTFuenZlb3F1cldsU3RYYm9GekJkU0FHMDFTU2xWS3FhYnJ1cTVwbXE1cE90Vmh1VFdxS0FwRXRRdnQ3VEVrNVhRNEdnZ0VFQXFGTWxvN0FvSEFKL28rcGFIK3cvTHljc2RwQklkY1c3YVFGRWJPVFNXRUpLKzk5dHBEalkyTkQxUlVWRlFRUXFyNTA1cnJYRmJyMWdXWVBXOGRHb0lUY08zMTgzRkRLUkRidFEwYk5pekZuSzJ2WWNVekQrSzZ5T245SGltbERkdTNiMzk2d1lJRk1ZY1F0QmZwSWZja3BXYkJnR2lFcEVlbktOVnBLdEdEaE1lSGdHR0N4R2d6bW84b0NOZWNsd0UxTTcvMlNVTFE0d1liQUF3ZlB0eXhRdW93WmtWMVhTZG1aUU1BMVhXZGJRRWhoQkJoK1BEaGI3YTN0OStmazVQemkrUDlYcmk0emg2cnRoWExGcXhEUTJRbVZxMWZqbWtXd0c3RkxUY3N3WXlacTdCazJWWmN1WHdhWEtmUnJEVTJOcTZaT25WcWs4MnRaWWFnbEtZTEJrVFFSTUhxVjRNa2FPanVTYUUzbnFCQ1hoQXVsd3VpTElBUUVXNXZabXRIYVdrcGhnOGYvaTk5czhMeC9HT3YxNHVLaWdvVUZ4ZG5IRXlaNWRSZEtzdHl2NURVTnBXUXlNM04zZERUMC9Nci91em1PbmU1dGdZYmppcTRkUDVDMFNBeHNnQUFJQUJKUkVGVUJtcHBCU2ZlZ3NVTGJzQ1ZrUVJhYmZtdlphc1hZTWJVR2xTTlg0Q3RhZCtIclEvY2hobFR4Nk9tcWdvMWs2N0J6VXMyb1NuQmZNS3R0MkY4MVhRc1dyc2FpMlpQeC9pYUt0U01uNG9aQzlhaXppR2ZscWpmZ0VXenAyUHNpSXFIcXF1cjMyZWNXaTk3RVVJU1lBK1FUSi9jb1p1TnVMTExCNzhrVUlIcUlIbzc5bjd3QWQ0N2xBUWhjYnkvNlM5NDVLL3ZJeFdKb1BqSVU1Z3phdzV1Ly8yZmNOczFrMUJUTlJWTDZnQnNtb2VhcWtsWXNEM3orOXU2WUR5cWF1WmhFL3ZCcHEzSDc1S0tpNHNIZEcxbU9NcUdwR1lMaUJHYXN2azJBWUF3WmNxVWg5OTg4ODFDV1padjRrOXpybk5OZGEvdFFCSzF1R0tLVTZ3WndiUmJsMkphdjQ5dnc4cGxWYmppK3ZsWVZGV0xha1N4ZGRFTXpGbVR3T1FiNW1QcEJCZGFYMXVIbGF0dXdlem9LbXpPY0hzTldMTm9GYTZkUHgvM3pROGl1bTBWbHExY2lObFJGelkvZUIzNnZvdnRXSFp6SFNvKysva245cmY4ZysxWDZ4ZUNzbEFEb0ZNS1NvekZUK204bWdKL1VTbUt2Vjc0UEJKeXhwWUJ4eHJ3d1dFM3hsNHhIUmVQSG9OeFJVVVk1ZzFCa1JKNGZ2a0tqTG4yUnN5ZldZV0pFUUJOUTNlL0MyYlArV1RoWDNWMU5lTHhlTmIyRHdac0dTRXBwWlRxdW01dkFTRTdkKzRVdG16Wjh0RDA2ZE1MUkZHOG1qL1Z1YzRkSmREYUdnV1VDQ0lzMTZLdGFHV2RGbHdJUm9JTW5Fb3g4NzcxV0Q0dGFQejdUVml4SFpod3k0TjRkT0hFOUwrNy9qcEVXaWRoNGFhL1lmdnlhUXdjRlV4ZS9EanV1OUg0Z2xPbW9Dd3hIVE5YcmNDcXV1dXdzTGJ2YTQ2Lzg0bk5uem15NGZrTjhSNm5YalY3RlZRbEF0R2hVNTFTbmFaU0tWQUlVRVFac2l4VGE3amRsdzQ5SmJrWnU0NTRVVGx1SWk0NXZ3TER5OHBRZlN5OVJxL3Fsc2V4Zm1IdGNZZmVkU3VYWU4zQnFrOEdObEVVVVZWVjFROXM3RFNDYmN5S21JVUVJK2ZHRmhNSUlVUzQ2cXFybXVycTZsYlcxTlRrRVVJbThTYzgxN2tDdGtRaURSRVg4eXJldnVRYTNMRHVhTjhIQWpPeDZ0M2xESnlxTVhsaWtJbFpyOFR5elZmYVBuY1ExV1VSWUhzcm9nbWdqeEl1Uk1vaUdkQ2NPUE02VksxYWllMDdXb0ZhOCs4bXZGV3U3SDE2L3Z6NVVWdE9yY2U0ZW0zOWF1bEdYSWxRVUoxcXVrN1ZaQytPdFNkbzNPVkhVWGtJZVVaclJ5QVFnQlNWSVlvdUJQTUxyV1pjOFYwQ0lJRGF5YldmSUo5WWo2MWJHNEF4Q3o1NXdqNGNEcU95c3RLQ1c3WUtLZXZjVExnWlV3a2doSmpGQklFUVFtcHJhOTlyYW1yNmJVbEpTUzRoWkFSLzBuT2QvVEtCWmdET2VEWFgzbklmVmwyYkFCREZ0bVh6c1dvb29WaTBEbXRYck1DYVRkdFJkekNHcEdYUXF0TklHb2dVWlZVb0ExRGYwQVFnQWtwUjM5UFQ5dWQ3WjE3VllvTmFYd1dVeWFzWnVYTXJyeWJMTGlyTE1tUkJwMnFpRi9HZU9Kb2JQZ0wxVGNia2tyUmprenNVU0pJTDRjSVNWRlpXSWh3Ty80dVBaU3NhV2dGbFl2Vy9Wb2tzS3l1ekJ1Ulp1R1hMdFJsUU00ZS96TEJVTXg0Y0FrQW9LeXQ3K2VqUm8vZm41ZVV0QWhEaFQzeXVzeDFza1VnUVNEYWhxUlZBbWVHMXFxZGdXalVBUk5HNlNobENqcWtPRDh5ZWdlV3RFekZuNFhJc3JDNUQwQVhVcmJ3WkN6Y04xVHRhT25xZ3ZYdjE0Y092TkNKenNMM1hJYStXTXZOcTVqSjFkcnJBN1hiRGsxK0VTaVdGcHIzN2NLVGhNRENoQ2prNU9WRGFYWkFrRC9MTEtsQldWblpDSDlWL3VjVmkxS2hSR2E2TkRVbnRKMzlRU3RsOG0yNE93eHJoS1NHRUVFb3B5Yy9QLzN0blo2Yy9GQW90QnVEbVQzNnVzMW0xbDA1RVlNMVcvRzFURTc3KzlVLzRBdCt4Qm10MkFaZXVlQkNMcitzTFVSUEJJUVowOWUrZ0NVQ2txcXozNE1HRHE2NWU4WFlkK3RvNnpQQ1RkV3Y5UWxCQ3FLNG1rMGlKTG5oOU1oUkZzZkpxM2tBZXl2TVBvTE10aVZRZ2pQejhmTGlPdXFFb1BoUlZqUnJTTHdBZ2tVSGYvb3FnS2dJa201cU9yOTNEU1lRUWpCZ3h3cHBNeU0vUHR5WVQ3R05YWmd1SWJmR3lPWEpsdG9Ba0FTVEM0ZkNmdTdxNmVCc0kxMW12NExSYmNIMXBFdStzV0lCSDZoTDl3cXZXMXVRUTdGWUNVUUNKS1BQL0orcncvSTVXdXhzREVFTjlYVDBidzJMVHFrMDRpREdZTWdvUEdlTlNMTlI2R2JobG5MRm12bjdOZmpXQmdGSTloU1RwS3haNHZWNzRQQ25FNGdTQ1B3OWxoZmtvS2lwQ1JXRUlpcUlnbVNTRC8zeVJDQ0tJb2I2K0tlUG5lNjB1eHZ5amFreWJWZ1hzV25OaW1tSURnUUNxcTZ2N0ZSSTBUY3MySUQ5WU1ZRlFTb25mNzMrc3U3dmI0L0Y0YnVkUGY2NnpOeHFkaUlVUExrTGQ3S1ZZT21NNm5yL3VTbHc2cGhRNDJvRFhObTNBdG9ZQUp0d3lFeE1IdEgzWDRzcjhkVmkzWkRibU5WeUphclJpeDZZTjJIRXdDU0NLUkJRQVUydll0WHcyWmpSY2oyc241T1BvYTJ1dyttOUhFYjcwOWdkL01LbmdIdzZoWnc4eWp5T3lPVFd6RVZlaWJoOFE2MHJTK05FV0pMdUR5QW1uMEJOcnc3NzJOclFuWGFpZVBoVVhHSWRHam5TTlF1aUpMVml6WkFsd3hhVzQ3c1pwS0J2bzV5dGRoVlhMYnNhOGd6TXh3WFVRNzJ6YWdPY2JBRENMNEd0dldZeVpHK2FjdUc3L3dzSkN4MXlicXFwc1AxdEdXTW9XRTR4UG85bkQwbG16WmozODV6Ly9XWEc3M2QvbXJ3Q3VzNVp0dFYvSDQ1dHI4Y2l5RlZpemRTMVdySXNocWVTanFuWWFibGs1SC9PdnJCNjRTaGljaHFXUEw0TnIwUXBzV0wwU1cxMVZtSEw5ZlhoOHd2Tll0TElKQjVuOEhSREFGWXNXbzJ6YkNxeGMwb0NvcXhRanI3MzdrZnBYZjdrVi9hY0tyQkNVRUNRb05WZm9XVkRyYThLVlpTaUtqNWFHS0hxaWNjUjdZMmc5SE1OUlFVRzRzQXBUcDE2T2EvOXRKRXBMUzFGVlZZWHl5Ky9FNGplYnNXekRLcXhvemNlMEc2Y05EUDlIbHlHNllBVTJyVnFPcllFcVRMeHVNWlpOWElHRkd6SWZoK1dQcndSaEQzSTdFZHE5ZXpkMjc5Nk54c1pHSERwMENFZU9IRUY3ZTN2RzBVY0dBQWtEUUdMMHV3a0FSRXFwQkVBRzRLS1V1bi8wb3grRmYvakRIOTdpY3JubThaY0ExNWtxVGRQUTFOUjBacCt4dHZVMmpKK3pGZE5Xdll2N0RJN0VZckhmZi92YjM5Nzhoei84b1lkeFpUMEF1bzIzUFlRUXEyQkFqRzFUN1BscXpOSGUxaGxyNXZ5bmViNWFTVWtKaGc4ZmpwcWFHdFRVMUp6VUgvT0V6MmVPSGozYUNrbnQ3UjlaWEp0NTRpNlFXU20xd3RLZi9PUW5uWkZJNU9HNWMrY0tMcGRyTG44SmNYR2RHTVZpc1Q4c1hyeDRpd0cxSkFPMUhnWnEvVTdEZGFxQXNzVUNuOC9uZUx4M1pXVWxSbzhlZmRKL3JoTU9Oa0lJUm84ZWJZSE4zdnFoYVpwMTVDK1RiOHRXS2JVKzUzZSs4NTAyUlZFZXZ1bW1tOERoeHNWMVlxQzJkT25TelN0V3JPaENacUdBYmNETmxsZlRCVUZnTjB4UmN4R0wvY0JJMDYyWlVHT1BCdi9VZ0EwQVBCNFBSbzBhMWE5cDE5YlAxcStZWUxnMDA3V3hjQ09FRURKdjNyeldSQ0x4dTdsejUrcHV0NXVIcFZ4Y24xREo3dGp2Rnk5ZXZHVUFxTEZPcmQvcVBPUEFTR3AzYXViS3ZGQW9aQjBZV1Z4Y2pJcUtDb3dhTlFvZXo2blpkaS9lZmZmZEorVVRtd2RRbW5Beks2UXMzR3pYWUJpbkFMQng0OFlVSWVUOWl5KytXSlZsbVk5ZWNaMHhvcFFpR28yZWdBNzZrNmpLcTNIRHpUYzg4dlN5LzIvTDczNzN1KzVzVGcwT2t3VnNvY0NFbXN2bG91d0p1UGFWZVdhaFlNeVlNY2pQeno5bFArWkpQUU90c0xDdzN6U0NRNFVVVEo3TnFwUUNnRzFnSG9RUVVFcXhkT2xTdlBYV1d3K3VYYnMyNmZWNmVTc0lGOWNRMWRIUjhlQVZWMXl4OWEyMzNrcG1jMnF3SFVQRVZqOHpLNkI5VHMzYzJzN20xSXFLaWxCUlVZR1JJMGVpc0xEd2xQNmNKLzF3eC9MeThnelh4azRqMkYxYitwY2VKUUNvcXFvbTNEUTJMRFhodG5IalJ2aDh2ditOeFdLOWZyLy9kdkFKQlM2dWdkVGIydHI2VUVGQndUL1EvL1RiZ2FDbUVVSTBvZThJb3F6aFp6QVlSRGljbmlvb0tpckNzR0hEck9iOVU2MVRjbXB0ZFhWMTF1UERtVk4yVFR0djVkcFVWVFgvemhGdUFCQUlCUDdZMGRIUkhRNkhid09mTGVYaWN0TFJnd2NQcmpJbUN0alp6eDViK09rSU5XYkJjUWJVbkZibm1WQXJMeS9IaUJFalVGMTllazc5UDJYSGNkZlUxR1RiaStEbzNFeTRBZW4rb0FIZ1JuTnljdGEzdExSMFJTS1JiL0ZUUWJpNHdCcUYrdnI2K3RValI0NnNHOFNwSmJKQmpjMnBaUXMvbmFCMnNudlZ6Z2l3QWNEWXNXUDd3UzBMMkN5NG1RbTNiSEF6LzF4WVdQaHNZMk5qZE5pd1lmTUVRYmlJUDZXNXpuVnBtdmJXdSsrKysrY0xMN3l3RWYxblB3ZHI2YkNnTmxoT3pZUmFZV0VoeXN2TFVWMWRqYkZqeDU3V24vMlVnbzBRZ3JGangyWjFiUTYvYlZnSDV3ZzM0L05TU2lrZFBuejRQM2JzMk5FNWJ0eTRiNHFpZUJWL2FuT2Q4VHIydTVQeWFWTXBiZk9XYmJ1ZXZtcm0vUzFNK0dtZi9ld2h4cnE4VEtoUmpSQktSWUZRVWFCVWxuVElvZzZYcE1JdHEvQXFLZmpkU1FROUNZUzlQY2p6ZDZFd0ZFZDVYaFRWaFowWVc5NEdFbjM5M0FFYkFNaXliTUZ0RU1mRzV0dXl3WTBhSDZkbWFEcHg0c1FQMXE5ZnYveWFhNjVwVlJUbGEveVZ3M1d1cWJzNytjVERqMjE3ZnY0ZGY0NE9BTFZlQWlSQVlLdCtRaU1FYWFpSmhNcXlBRVVXNFhKSjFPMlc0ZlVvOFBsY0NQamRDSWU4eU12MW82Z3doUExTWEZRUEw4RFltbExJa25qYUg0UFRzdkxPN1haajdOaXhqbmsyMXEyeElTa2hCS2xVS24wVVNQci8wL3JZbHVuY1pzeVljYkNtcHVaWGI3enh4aEcvM3o4ZmZMVWYxN2todGEwOS90RDRmL3V2bHc4MngrMDdDdGp6MUV5bzJad2FkSUdBQ2dLaGtnRTFXUmFRNkV6UXpxQUxZM0xTVUFzR0dLZ1ZoRkJXbW92cTRSR01IVk1DdDFzK0l4NkkwL2FDOS9sOEZ0enNZTE9IcFlRUWF1VFQ3SzBnOWdrRmFzSnQ5KzdkZWlBUWVLeWxwZVZ3SkJLNWhTOWw1anBUdGYyZSszSHowZjhYNy82NjloUHZEYVdVTmpUdU83cW1lc0tpOTVHNVRhcmZlV29NMU5TQm9LWW9JblVwSXFpUVJGS1I0ZmU1RVRDaGx1ZEhVVUd3RDJvMXBmQjVYYWgvOXAvWW5qOE9OMzdHNy9CZHhySDF2bWV3NU9uOWFJcEppSXdiaDhVL21ZNHJTODRpc0FIcGM5ekdqaDNyNk5qWXZCeDcvd2FCRzJXZEd3QmFXRmo0YkYxZDNlRlJvMFo5WFJURnovS1hFZGRaWjlOVWJldjJOeHFlbm5yMUw1eVdHZHVQODA2QTlCM3AzUWMxUWdVQlZKSUVLa3ZFZ0pvTXQxdUFKaEtJTGhjQ0FUZHlXS2RXMWdlMVFNQU5RRVhkYzY5Z1pVbVZJOWhhbjMwR3R6MHRZZWxqdCtPNmtsNXMvZEVhekx2akRXeCs3Q0tVblUxZ0E0QlFLR1RCYlJDbzlZT2JrVzlqejNPemdHYkFUUWVnMTliV3ZyMTY5ZXBETTJiTStOanRkcy9oTHdXdU0xV0paOWRoL0lQQWphTVQyTHF6RGExSkY2YmROaFBMUDU4SEYxcXc3RC9XWVB2b0tyajJ0S0FwbG9CWU5XYjFlU1BWTGIrNTV5OHhaTFp6Mk1OUGErNlRVcVFFUWpRUTZDYlVSQUZVVGVrMG1kUUJRcWdnRTVTVnl2QjZSR2dTME8xMkdWRHpRRHUwSDM5NkxvWk8zWXZ5Qzhmamh6OGVqdXVDKzdIa1A1N0Mya01hWWxpRFNYc3V3ZU1QWDRTK01FbkZqdWNPSWZqdnMzQmRpUVRBajJsenhxSDZLeDlpZSt3aVhKK293N3l2YkVYa1o3ZGk2V2ZPQXJBQjZZMVhBNVdIVGNBUlFzeUxFa0tJbVhNemMyMjZybE8ydjQxMWJqZmVlT01oQVBlM3RiVjluSnViZXd1QVl2NHk0am9qZGVnWWNOY3NiUDZaSDlGWG44SDBPMTdDcHMvT3hIVUJBRWlnVlJtRDlYLzVRblB5NC8xL0xKdHd6OXRiKzVhcUpCbUlzVWQ1SndoQmtsS2tRS0FLSUJvSU5JR0FFb0hvb2tCQVV4clZpSUNjUEJmMVN4VHRyUW0wZElzb0szQkJsUVVvM25UNDZUbjZNVmJ2Q3VLMkg4L0F2TXR6OGZGanorTG1iMjlHNUM5WFlmRmZic1dFMjMrTlpTVTM0T1VGOWhHcVhyVEdWRVJLR0NlWDcwY0V2V2c5Q21ENE1OeHkxNzhqT080c2NXeW1jbkp5TXVER1FLd2YyT3pPRFFCaDVrcjdPVGNBdXVIZWFGNWUzbC9yNnVvK0hqVnExRTJpS0Y3T1gwVmNaNXhLcWpEVENPV0NvNGVoT3ZrR1dtTUFBZ0Fnb21aeXhVdDd0Mys0NFpLcmZ0R0VnYmV6OTlwQ1R4VkloNTZFUUJjRVFnV0JRSklJSlJwQlVnY2xnZ2lYMzRVUnVVRW9YaStDQVFrcFJVRFU3ME54Z1F2MXIzV2gra3VmeDd6TGh5RW43RVhPYlpmaHVxZlhZYzAvcG1QS1lJbWVmcXNiUkFBcVlra0E4R1BpWi8xblR5aHFoOXQ1NTUzWEQyb0RRSTZTdFBybDNDd2IxOWNPWW9hbVdtMXQ3ZHMvKzluUEdyL3puZS9zOG5xOS9NaHhyak5YUmpVaFlXeGo2VWxwRDcveTJOcVhIMzV1VzQ4dG45Wi83eWVRcEVDS1dGQ0REdlJCVFJRSmxTUUNXUktnZUNYcTdra2gydDZOenJZZUJDSzVPTC9Zalp5d2dxUmJSR2NvaVBJeU54cFVMMm92SElHY3NOZE1KcUdxUk1YelIzc3g2TGkyWXYrQUJrQkNRRG54RDlzWjF3WVJEb2VIQkRkN1dHcStieHlOcEp0QVk0b0tPaUhFZEc3NkhYZmNvZDl4eHgzL2UrREFnYjJscGFVM0UwSW04bGNSMTVrcXF0TjNkdTA1c3Y3dVZ4cjNBbzJxUXo0dHc2VVJnaVNvQlRTTkVHaWcwRUZBQlpGUVNTQlVrdEx0SElvaVVna0V2a0kveXYwS2ZMS081bytPNHQybWZFd1k0VWZTSTZFdE53Y2pxdk1RclhnZnIwWjE1anVMbytHb2hFaitZR2RRdUJISmw5QjZLQTRnbFA3UTBUaGE0VVBrSkp4bUpKeUpOekVVQ21IY3VIRVlPWElraGcwYmh1TGlZa1FpRWVUazVHUmI3VWNWUlFHejJvOHlxLzAwNDR6MkZJQWtJU1JCQ0RIUGMrOHVMeS9mdW03ZHVoLzI5dmF1NGk4ZnJqTlJxcTZ0ZnZHSkxmOVRPL2wvZGlOeno2ZjFQR1l1YTBVZU5aMmFjWlphK2hjODI4NGhHSTIzSWxMSGVyQy9UWVhzZFNPVTQwZStYNExpODZHNEtJaDhuNEpBUVQ3R2phbkJGMmNQUjkycUY3RGhrQXFnRnpzZTJZb05HSU1iL2g4SmdBU1hTMEtpN1JoYUUvMDkxSlRMaGlINjNIYXNiVlNCeERGc1d2VU82c2VOd3BSQUdwQTdYbXhBZmVJc2RXeW1Bb0VBeG8wYkIwRVFIQzlDaVAydDVkeFNxWlJaTVdXTENwUUpUVTMzcGdIUXZ2U2xMelVCK0UxVFU5UE9rcEtTbXdnaEUvakxpZXQwUzlmcHV4L1d0NjlmKytHYmU3RHNUWlVKUFpPT0xvMkJHYVY5K3dsSXVqOU5GeWhGU3RkcFQxS0V6eXRDVVNUcWRrbHd1MlhrajFDZ0hJemhuVGZqb0tLTWdvcGhtSDFWR1NyS3ZVZ0VYRGhZV29KQXdJM0E1NytBQjQ4K2d5WC8rV3NzaUVtSWpCNkR4YitaamlsR3lEemwrbkVvdStNcFRQM1BTL0RNRXhlRGJSNE5Ydlh2ZVBEUWMxZzA5OWRZWlBTeDNmZVRpZWxXajZQN3NmSkhXMUgybTF1eCtQeC8vYkU3NFZ1cVRyUjZlM3Z4d1FjZm9MNitIZ2NPSE1EaHc0ZHg5T2hSZEhaMklocU5vcXVyeTlwOGxVZ2t6RjBMaE5sK0JWM1hDYVZVTUM4amF5a0JrQ21saXBISmNBRndyVnExS2pKcjFxenJ2Vjd2LytFdkxhN2owU2ZhVXBWbFZqUWE2M24wZ2Y5OWNkc2RkejhWUjErQmdCMlBZc1BRZE9nSk52UWtPa0gvSW9Fc0NaQmxrYnFVdEx2eXVHVjR2YTZNaVlMOHZBQ0tDa0lvTDh1eHhxVE9sSW1DVDcxanN5Snp0eHZubjM4K1JGSHNkN0h1emViZ3NoVVZLSk43TTRzS090SlZVdzJBT21mT0hHM09uRGtQN2QyNzk2MnFxcW9iUkZHY3hsK3lYS2NzN0ZUMWwzZStmK0NaQ3krNzkyTURVbmFYeHViVWtnUklaa3dSb0s4L3pYQnFWQlNZSW9FaVVrV1I0SGJKY0J0UTgvdGNDQVk4eUFsNWtaL0h6SDVXRldEc21ETmo5dk9zQXh1UUhwd2ZQMzQ4SkVucUJ6YjdXd1oyVkJBRUlnZ0NWRldsekw2RjlONi92dERVcUJRUnpReE5BYWlqUm8xNmZjU0lFVHRmZi8zMTYzSnljbTRDVU1wZmRsd25TNVRpNEpIV1kwOStadHBQM21oS3ozbXlVRXM0WEVrSGw5WVhlcHFUQkdKZmtTRHQxR1M0M1JMY2JnVStiM3IyTXhUd0lCejJJV0pDclN3WEk0WVhZdXlZVXB5Q2hWTG5MdGlBZEh2SGVlZWRCMG1Tamdkdy9TcW1oQkJxaEtaZ25adVpkME42RzcwR1FQM29vNC9VM056YzlSczNibnpqc3NzdSs2TGI3YjZKdndTNVRxaEMvd2RkWFYxci92clh2Mjc3NmxmbmRRemkwZ3lnRVJOb0tlTVhzWFYrR3JPYkFKSWtPZTc4TkUrK3RlLzlaSS96UHAySFJKNVRZRE5WVTFNRFdaYk5DaWlNTldDT0lhb0pOK010WWVBR1BTM3JTQ1N6Z2RmSnZWMTk5ZFdOQUg1YlYxZjM2c2lSSTJkS2t2VHYvQlhKOWE4cWxVbzl2MlBIamsyVEowOCt3QUF0RzlTU0ROUlU5QTJ3bXhHSHRXekZPTWJiZkoxUVJWRmdQeURTaEZwdWJtN0dpcnpUZVp6M09RMDJJTDFEd1d6dk1DOFdjRTZocVhIRHJkQ1VxWnBhcm8wSlRUWEd2VmxQb3RyYTJyY0F2Ti9VMVBSQ2NYSHhsL2hKdlZ5ZlJMcXV2OW5RMFBETXlKRWpkNW0vUEcxQVN6cTR0SlNEUzlPWml4cS8yTE5DemV2MXd1djFJaEFJV0hzL0N3b0tMS2lOSERueXRDeGU0V0JqVkY1ZW5nRTM4MmFhb2FyZHhSbWdzK0NXU3FVb0ljUTg4TkplV05BWndHVzROd0Nwc3JLeUZ5Kzg4TUovUHZ2c3MxZm01ZVY5aVJCU3cxK3VYSVBuMGVpZTV1Ym1wNmRQbjc1ajE2NWRLcE1mWTNOcDdOdWtEV29xODB2WEFwcTVFazhVUmZhWFBYVzVYQmxRTTVldW1Icy9JNUVJU2twS3JHWEdwM3BGM3NuVVNWdVlmQ3BrMm1tbmZKejVkb0J4TEh0YTFKd3JoUUU0R0hDekduMU4yQUhRbTV1YnRlWExsemVrVXFrWExyamdnbWEzMjExQ0NNbmhMOTl6R2x5T0M1TXBwWTJ0cmEyUExWeTQ4RSt6WjgvZWYvVG9VYnNyNjNka045TExWWHJ0K1RSbWNiSFRSbllvaWtJVlJlbVhTMk9CeGk1ZHFhNnVQdVhMakxsakc0Snljbkp3L3ZubncvenR4RXdnWk9UaDJDdVJTRGlHcG94N000ZnJCVnQ0cWdGUWpmQTBCU0IxNzczM0p1Kzk5OTYvTEYyNjlJVnZmL3ZiMDBPaDBFeCtxQ1dYQWJTRzF0YldEY3VXTFh2cmw3LzhaUytURzNQS3BiRmhhQXJwNDRWVVFtQkdDeHBBK2tZRkFVcEFhUHFwU2tCQlFDbW9UZ0ZLQ1hRS2FEcUZwZ09xUnBIU2dKUUdKRldLaEFva1VrQnZpcUFuUmJEN293TUFEcHhWai8ybjJyR1prbVVaaFlXRmpxZUFESEhlMUc3Z3pCTjcyUk5DN05WVHk3MEIwTFpzMlpMNCtjOS92cmU3dS92NUN5NjRvTW5qOGVRU1FncjR5L3ZjYzJ6QllMQ3VwYVhsRDkvNzN2Y2VuejE3ZHVNLy8vblBYZ2VIMW0rbnB5Mm5waEppZ2RESXA2V2ZpNFFJRUFXUmlwSUlTVEp5YWJJTVJVbi9jbmU1M1hDN1BmQjZmZkQ1L0FnRUF3aUZ3c2pKeVVWK3BBREZ4U1VvSHpZTVZkVWpJRW55V1hrdnp2akpnK1BWeHg5L2pJYUdCalExTlZsVENoMGRIWWhHbzRqRll1anU3a1ozZHpkNmUzdVJTQ1NRVENiTlRmV0VYZXFzNnpveEwrTnhNcWNXMk1rRmlWSXFBNUNSUHJ2QXVxcXFxcFJYWG5sbGFtRmg0VFdDSUZ6TVgvWm52NUxKNVBZMzNuampoYTk4NVN2dk56VTFtU05OckVPekZ3Z3lISm94MDh5TVFoSHpseWhsSzU1bW9Zd3RFR1FyRXBqdEhHYjRXVlpXaHFxcUtsUldWcDdWOStLc1czSlNXVmtKajhkakRzZWJlWWVoRkJpb1VVMGxxcXBhNGFuWjkyYTZOU004MVFjSVQ1TUFsSWFHQnFXa3BPUUZBTnYyN3QwN29iS3ljcm9zeTUvbkwvK3pUNGxFNG0rN2QrOStkY0tFQ1kwTXpOaXdNK2tBTlJaMnFnRTFxOXBwZ0kyYXhRSGp1VW1aNTY0Rk5ET254aFlKL0g1L1JrNnR1TGpZeXFtZFRVV0Njd1pzQUZCWVdBaXYxMnNCenUxMmczMFMyT0hXMjl0ckFTNlpUR2JMdlZGS0tXSFBlRFBQZDJPZW1DbkR3Vm1BQTZDTUdqWHFkUUR2dlBEQ0MwOU1talRwVXIvZmZ3MXd3bzk1NXpxMVlXZFRMQlo3N3NVWFgzejdpMS84NHRFc1FIT0NtdW5PckdGMUk0K20yNnVkQnRSZ1Zqek41NnVUU3pPaFppOFVtTzBjWnVOdElCQTRKKzdQV1JlSzJuTWVlL2Z1eGI1OSszRG8wQ0cwdExTZ3ZiM2RHcUNQeCtQV0FMMFptaHBEOUVnbWsxQlYxWFJ2R2VHcHNYaEdvSlFTdzhHWjRhbDVzU0VxRzZyS0FKUWJicmpCdlh6NThza0ZCUVdYUzVMRVQvSDlGRWxWMVJjUEhEanc2dTIzMzc3cnFhZWVTZzBSYUNrNzBBQzJLSkFCTTkyY2QyYkRUdGFseWJJTXRwWERQa2tRRG9lUm01dUx3c0xDakhZTzhtbWRqK0pnYzFaVFV4TSsvdmhqTkRVMW9hV2xCVzF0YlFQbTNjemNXeXFWZ25sS0NBTTRZcXdNSk93MUFPQ2tMSUNUQWNpYk4yOHV2K2lpaXlZSEFvSHB2Qi91alAwRnVhZXpzL09sYmR1MnZmdkZMMzZ4alFHU0U5QlNXWUNtMm9GbUhueHFocHQybDhZQ3pleFBNMTNhUVBtMHZMdzhGQllXb3F5c0RKV1ZsU2dyTy9lQ2czTUNiQURRMmRtWlVWUm9iVzFGUjBjSGpoMDdobGdzWmgxLzFOdmI2MVJZZ0FFMllybzNGbkJNZ1lFRkhGdGtFQTBIWjRkY3h2WGVlKy9WVkZkWFgrenhlQzRIVU1LUmNscGhkcWk3dS91bHVycTZIWk1tVGRxUHZpcjRZRUN6NTg1WVYyY1BPYWw1SUFPVDQyVmRHanZyNlJoNnNrMjM3TXluV1NTdzk5UnhzSjJGMGpRTjlmWDEyTDkvUDVxYm05SGEyb3EydGpZY08zYk1PdHV0cTZ2TE90dXR0N2MzQTI0TzdzME1UODB6M3dZQzNGQWdaMzVNMnIxNzk3aUtpb3FMM0c3M1pSeHlwdzVtUFQwOTJ6NzY2S04zeDQ4ZjMyZ0xGNTJBcGc0Ulp2MkFCbXNQQnlnUkNCUlo3aGQyc2k3TkxCQzRYQzVyM3RQbjh5RVlEQ0lVQ2lFdkx3K1JTTVRLcDFWWFYwTVV4WFAyWHA1VFlEUFYzTnlNZmZ2MjRlREJnemh5NUVoR2FCcVB4eTI0c1lkWHNuQmpBY2NVRndZQ0hESEFKamlFcVpJTmFyTHRZOUxPblR0SFYxVlZUZkI2dlJjVFFzWnlCSjA0NmJwZTE5WFY5ZHFlUFh2cUxycm9JdGFaNlF5WUJnS2F5Z0JOcFpTcVJxWGM2bkUwTjZXeFFETXJuYnF1VTBFUTRYYTcrZ0hOSG5wNlBKNStRK3htNkZsUVVJRFMwbEpVVkZTZ3VKaHZsandud1FZQTNkM2RhR3hzek9oM2EyOXZ4N0ZqeHhDUHh4R1B4eTI0T1lXbVdjSlRDM0FPT1RqQ09MaU1YQndET0NmUVpmemR4bzBiaXkrODhNTGFjRGc4UVpibGkyRnR4dUFhb282bFVxblgydHJhZHY3em4vK3NuekZqUnBzQklIMEFkK1lFTmZhRWpYN0ZBTWFoV2Jrek5vZG1GZ1kwVFljb2l2RDV2T3p3ZXIvUTA0U2EzKy92MThwaGhwN0RodytIMSt2bGQvaGNCcHVwQXdjTzRNQ0JBemgwNkJCYVcxc0hySnF5b2FrdDl6WVk0TUFXR1d5UU01MmNHYWFLZHBnNUFRNkFPSDc4ZVBtUlJ4NnBxcXFxR3UzMys4K1hKT2t6QVB6OGFaMmh1S3FxYjBXajBmZjM3dDNiOEkxdmZLT3BycTZ1MytTSUxYVE1CalFXWnV6L285dUFaamswNDRJZGFHYllxYW9hRlNVSkFiOHZBMmhzYjFxMnFxYzV4RjVlWG43V25NckJ3WFlDRll2RnJOQ1VyWnFhN3MxcHIwSVc5NVlCT0YzWDdWVlVaQWxUN1pBelExVXJMK2NBTnNuMmQySkZSWVc0WnMyYVlTTkhqcXdLaFVLalpWaytqeEF5NGh6TGszMlVUQ1kvNk9qbytHajM3dDM3dnZhMXI3VTBOVFhwTmxmbTVNNnl1VFRyNzh4bWJOdm55SUNaemFHQnJYS2F4Mm14aFlGa1NvVWtTUWlIZ2hrdXpRdzk3UTIzYk5YVEREM1BsZDQwRHJaL3diMnhWVlBUdmNWaXNhdzlidzY1Tnd0d3Buc3pJV2NQVXhuQU9ZV3E5bkJWdEFITzZYM1JmdjNpRjcvd1gzSEZGU1dscGFYRC9INy9jRm1XUndpQ01CcldLdDVQclJLNnJ1OUpKcFAxc1ZoczMvNzkrdzgrOTl4ekxYZmRkVmVQQ1poQllKWU5haG52TTg0c0k4eGtRMDNZNW9tWncwMklwUWlYQUFBR0dVbEVRVlF0bURFdWpUS050a2drVTVBa0dYbTU0YXk5YVlGQUlNT2xtYUVuZDJrY2JFTldWMWNYOXUvZmJ6WDAybnZlc2hVV3NnRk8wN1RCQUdkM2NTYmdpQVBrQkFmSURmYSs2T0FHaGZYcjErZlgxTlJFOHZQekMvMStmNGtzeThXaUtKWVJRc294NkVydlU2WmVTdWtCVGRNT0pwUEo1bmc4M256a3lKSFd1cnE2dGk5Lytjc2RZQTRtR0FSbTJSeWE0L3YyNUQ4TE0vUXRBS0pzTWNCMFp3QWdTVkkvb0xGOWFXeHhvS2MzbVQ3RW9TQy9YNEhBcVRldHBLUUV3NFlOZzgvbjR5OVdEcmJqMStIRGgvc1ZGdXp1emQ3VWEwNHNPTGczcS9lTkJaeW1hU2JZd1BUQ3djSEYyU0ZuQVlxQjNHQ1hZSHRmc0wxdjVmOSsvZXRmKzhlTkd4Y3NLaW9LaGtLaG9OZnJEU3FLRXBRa0tTaUtZbEFRQkQ4aHhFOEk4UkZDUEFZSXpRTUF6TThKQmd6VzJqaEthUStsdEl0U0d0ZDFQYTVwV2t4VjFXZ2lrWWgyZDNmSE9qczdvODNOemJHZE8zZkdGeXhZME0zQWk5cEFSZ2VBbVc1eldOcGdsLzI4dld3d1kwRm11ak5tU3hvRkFFVlJMSmpaV2pneWlnT3lMS083SndsRlVWQlNYR0ExMjlwZEdsc2dLQ29xNGk5T0RyWi9UYWxVeWlvc0hEbHl4TnBuT3RTbTNxRUN6dWJpV0FjM0VPUWNZY2RNUHdnRFFFMTBBSnY5WXIvT1FCY2MzdHJmejBpRE9ielB2aDNvc29QTWZtVnphZjNlTjQ5K2Q0TFlZREF6MzdmdDFJQW9pcUNVd3VWeVpRV2F2ZUlaNytxRnkrVkN4YkRTakdiYmNEaU0vUHg4RkJRVVdBVUNXWmI1aTVLRDdjVHAyTEZqT0hqd1lML2MyMUQ2M295WlUzdDRtaEdtR3U3Tmdwd3h6V0NGcXN4OXN1ZmtpQU9Fc3NGT3lBSXpjUWhnWXdFbk9FQnRJTUFOQkRjNzBPQUFzWUVjbXA3RnJka1QrNE5DakNrQVVGdVlhY0xNS2dRd0ZjNk1IUnVpS0ZKS0tkeHVkd2JRSkVteUhKcTlPQkNOZGNQdDhXRFVpT0g5S3A1RlJVVW9MUzFGS01RN2VqallUcUphV2xveTNCczdralZRYTBnMjk4YTJoNWpWVTV1TEF6TjBiN28zZTA0T0RvNXVVT0RacXJGT1FCc0lic2ZqM0k0SGJNY1Rkam9DanFsT1pnVVlBeThXbm1Celp1YWYyVENUWFE3RUFJMnl1VFJLS2J4ZWIxYVhabS9oNkl4MndldjE0Znp6YXF4ejAweVhkaTRjTDhUQmRnYnB3SUVEbG50ektpN1lxNmRPclNHcFZNcHliamJBUWRkMTRnQTQxc25CRnFyYTNaejl6M1lvWlhWaUR1RXVHUVJzMlZ5YlBTU2xBOEJ0SU1EcFRuOW1vRFNRczdQL1B4bGZpLzJ6TGRTRTNablpnRVp0bFU3cmJEOWQxK0h6K1Fac3RHVm5QTnM2WWdnRWdwZ3lhYUxsMG5pMWs0UHR0Q21aVE9MUW9VUDlpZ3RzZURwWS9tMEE5ellnNU14aUErUGs3RzRPV1J5ZEhVUk8xNkRPakEySFQwYU96YlpVaHc0UmVQWUx0aVBkWVFlWjNaV1p6b3pKbjJXRm1SMXFKdGcwVFVNd0dCeHdhSjF0dEcwNWVnemhjQTZ1dVB3U2xKU1VRRkVVL3VMaVlEdjk2dW5wNlJlZXN0WFRvYlNIMk9GbW0yQndta2UxUTQ1dEhZRkRYZzQyS01IQjJXRUFKemJVZkpvVHpMSTV0cUVXRXV5QXd3QUFneDFjOWp3Wmt5K0RMVzltMzBPYkFUTzJmWU5kMUczUHBXbWFobkE0bkhXKzA2eDJtbUhud2NQdHlJOFU0TEpMK0lyYUV5bUpQd1QvbWp3ZUQ2cXJxMUZVVklURGh3OW5ETld6K1RjVGNIWUhOMUQxMUFGd2xNbkRFUnZncUExMDVrVk5vRkZLemNNRzJWOW9nNExLQ1lZRHdHeW9weG5Tb1VMT1lTMGliQ0VsKzI4ejNtZWNtU1BJSElCRzdVdTNCd01hZTJtYWhweWNuSDViMTltamhjeWg5YUtpSWtDcTV5OGlEcll6Vno2ZkQ5WFYxU2d1THU2WGYyUGJRK3lBczgrZVpta1BjUXBScVFrMVRkT3MvSnNUNkV6QU1hN09oRjAvMTg1QWJEQm9aUVBZSndWYlZzQXhKNy8yMGJnUGRyQTVzZ3lJMmNMTWZpQXpONmpiUTgvQndrNTdEczI4VkZWRmZuNitkYXlRSFdobUhvMFBxM093ZmFyazlYcFJWVldGa3BJU0svL1cxdGFXMGYvbVZFRTlIZ2RuQzA4ejNCeWJnM053Y0hiWW1YQmozMlpBai8yN1U1SENZQjJYN1dQOTRHVjc2d2d4QjZCbDVOQllxTm5EenNFY21qMkg1bmE3a1VxbFVGSlNrdEdQbHBlWFp6WFp1dDF1L2lMaFlQdjB5dTEyVzhjeXN3VUd0b0xLQXU1NGNuQU83czJwVGNRT3VuNEZCOVBaMlFISC9Ka3lSUW5XMWZXam1oUDhodUxVbk03aFp6L0dnc3ZteHV6dUxBTm1iSXRHbHZ5Wlk3ZzVWSWRtejZHWlV3T3BWQXFWbFpYSXljbkptQnFRSlA1U081WDYvd0hGMUZuWjhqMWl4d0FBQUFCSlJVNUVya0pnZ2c9PSJdLFsxLCJkZXNjcmlwdGlvbiJdLFsic3JjIiwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFUWUFBQUUyQ0FZQUFBRHJ2TDZwQUFCTUMzcFVXSFJTWVhjZ2NISnZabWxzWlNCMGVYQmxJR1Y0YVdZQUFIamFyZjFadGlXNWxXVUwva3Nyb2draXFBUm9EaW9aNC9VZ201OXo0aWlOUnRJOUl2eGxtbElMM251dUZNQXUxdG9WcnYzLytYKys2My85ci8vMXZLbWtLK1czbGxiS3pYK3BwUlk2LzZqMzc3OTIvbnp1ZFA0OC84WHk1MS9QdjM3OSt1c2JnYitqbi94OTQrMi92NS9PMS9NL2YrQWY5M2pHdjM3OXFuKytFK3FmQy8zNUJoZitQWUYzOXQvcjd3L0oxOFB2NjAvNmM2RzJmLzhvcmI1L2Y5VHg1MEx6endmUG8vejVuZjU2ck45Zi92L3JYNzd3c2tvcmM2TVl3bzVQdk0rZjZmY0U4ZmU3OC9zOWZ3WSs5L0MxSG1Nc0YzODk4ZjF6TVJia1gxN3ZIMy9mOTk4WDZGOFcrUi8vdXY1OTlmLzYxNzh0ZnVoL3ZoNy9iUzNMbnpXNnkzLzlqU2YvMjlmalg3Y0oveUlPZnoxUitOZHYzQ0hGLzNpZFA3Ky9iOVh2MjcrMzYwaGhTK1dQUkozRmZ2NXhHVDQ0V1BKNGZxenc2K1YzNXQvditkWDRWZTkrVDdaODNmTWUvSnBQZXdLNzhsMVBldGJUbisvWjUrLzVUQjR4aFIxZS9nNWhobmkrVnVNYldwalJmVXIrZXI3d3hoWlhyT3piRFB0aTYxSU1mejNMYys3Ynp2M21VN256ZXZob2VMaVlXLzNmL3JyK2Q5LzhuL3k2dm0rNlJNOWQvMW9ybmlzbzF6eUdPK2VmZklvTmViNC8rNWJQQXYvajE1L3R2LzhtUDRncU81alBNbGRlc04vamQ0bVJuMy9LVmp6N0hQbGM1dStmQ2ozWHUvNWNnQ1hpM3BtSGVTSTdjSmNuNXFjODl4dkMrenlzWTJXRE9rOGVZZ3FESFhoeURvdUhSRjVpQ2RjYmF2RGUvTXo3bk0rR0hFcnd5OWdtTmlMSGdtNVZkcWl6V1NsbDVPZE5GUm5xT2VhVWN5NzV6ZlhLTGZjU1N5cTVsUElXalZ4LzQ1dmUvSmIzZmV2YjNsNWpUVFhYVXQ5YWE2dTloUmF4Z2JtVjlyYmFXdXM5WEowYmRhN1YrWHpuS3lPTU9OTElvNHgzMU5GR240alBURFBQTXQ5Wlo1dDloUlVYWm1LVjlhNjYydXI3dVRhV1lxZWRkOW52cnJ2dC9pRnJYL3pTbDcveXZWLzkydGYvMnJVL3Uvb2Z2LzRIdS9iODJiVndkc3JQdlgvdEdsKzkzdmNmbDNnMEo5azlZOGRDZXRqeDF4MUFvSU43ZHRjbnBlRE91V2QzQ3loRkRqeGtkbSt1OWJoamJHSGFUOGpmODlmZS9YUG4vcS8yN2NyMS8ycmZ3djlwNXk2Mzd2OGZPM2V4ZGYrNWIvL0ZyaTM5M0R3Nzl0TkMxL1NPYUIrZjZhRmUvTDV2L3ZqcmJ4NnA3cDdMNkhuMWtXZGVxYU1pOTc3ZkY0ZnljTm1aWW45M3YxTllPNlV2aHBUZUs5YlFhOFpPNWErTnNkNTdiTmM4c0Radit1cFQ4RnJQZHFuaXpwVTFybjIrYlkvNjFMN2pPOHZIaDNkdVY0bTR5QkV3WllWbHIyRnpFNHhmLzdob1N5ekJ5Ty9xZmMzMDNqUHcxbndYYnh2WTBkeGEvWW8rT283N1lzTy9Pbm1aK0w1ZnFXekQyaG1mSHNZSXZleU1Sd1FJUEx4bG5hT3R0Tk5ZTGZPY3VZWE1wbjdJRXdKVXI0RVJYcFhuQ3FuMVBWL2V1ZWIxZm04cjlldTFqUFkrWDBFb0lxKzVXMThkTzlNeisvSHVaM0NoTjIwOHdYczk3QjR1b3ZZdjU0OHYxUHlsOGQxblQvSGdkY2U2MlUvMnJxZUVvSTVVU3docnR2ZkZ3MVJXWkIzZHVjbzdubDE0elZsSGlqek5ZQWsvMUFJZHdINDlwYWtNbjFMNTdmemwrWlhKbzhWbnQ3ejJxdStlSDF0Y1VKRjVmMzN0d2Q1K01kWTc5ekJTdXRtZDk1bDF6YVVYV3QvZGVjYjhEbDV2ZlVoUFdPTmIrL2xtcnl1OSs3dDRpcXg0N1BjcDczZHZmTUhMejh4UjJteFl5UGQ5NHNhdnpwQnY5Z3VkRDNmSmRhUHJHNmYyN0luRVBTdGNJMDFla2o4aXQrbys5aGZualpjYjNyT2xYZGt5ZGk2dSs5c2pvVm85OFlnN3NJTVlsekpDclNHeC9ST1JhQ24wdlZXbnVUN1dsbHVQK2IwbzNVNEZ1WWozN3JQaWRYZGdDOUM1d2M2OUxoMVh3L1lnRi9uQ1BvVGt4U3BxRm5meC9kdHV3Yi9YTjgvL2YzcGhzNTZuWlRTNzNURTJmWGdZYjNpLzJIZGVJNjFyTEY0bTNQVkxyR0ZpRHpzT0tiY1JlL3RRY09CdVcyTmdtQ2Jid2Y4d1ZteFRiL0Vib1dDcGtLblUzdHd2Zk5OQ013YzdIVkpCcWdlR0I3QUFzcHZJRnpadWJZVGpyZXZMQTJVWmUvS1lOeS9sRFNKcjIrWm10NitOVGoxekZVeGJRMWI3eWx2ZGY3Rkk5K0JSRUtHMVFJeHo4ckUzSEQwc1dJK2V4aHorYnF2MDlhQzAyaWJjNTQ1ZUxjekdNOTJycHJLUnI0YXRpYngxZjduTGcwenNqcWxhcTdRV01GQXBsZHJ2dkxnT3lMOWl4UFljRHlZWlJOWEJWeEdiSWk1ZTZkdFAvbGhXVmd2SlNia2lNYTN4a2JYcUxPbDdFTlFYRlUxeFFpRSszalJyVEJIVittSXdzQVgrTDhUQjVzNCtaOFFmb0x4M1RoZ2w5QmdsMzl3OUxwVDNld0Y2b1E5c2RydmRZdlFUK01YYjgrK1krMTJ3emJIM3Q5NWpZaGZpM2RvM0s0dkNVOTI1WVUxNGhUbEIvckVzbHZ2Q3pNWE5qc3hjbnBSN1RSanE4S1EzSUlNZExxQ0h1d3VMZ3lmSWc0MGFoZWNjdXdGV3h1eGxsWVNIQUVUVW1QSjRja1RVSjJwWHNPOFQzTHJiZk4rdU1MSDNyT1BEc2lEc3ZQbHVHQTNXMFYzRXA3RWsvVVl2THBhNXBNRExEMjFMNHNWUUlDNTJmLzU0WGl4ZndnRU5SR1cvMk4rTVNxTDBmSzQyYm82d2g4MEY5dFc0WTR0NXJyY08xQnpuc0hCeXVES0VEdS9WMGZmWTU1MUtLTS9IdjJ2NVVGOU1PVDh4UDl4RjNEdmp4YUZaRzV1WEU1L0FBS1krc0U2OGJ1VXZURDVlQXVGOGU2cWozQk9GQm1VRE5qRnhlWHdGYlVZaU16N2p3V1d2aFJ0cFhoOW5tOTdhZW1aVjBRaFdGdkhINm1LR2ZlZkJNeU5ocjVMeGZJTW5hL1Y0M3ZXMVdhOUhDeUt3NXhuUkZzeDVMd0ZCd0k2Ni9BOCtHeWVSdElqTlZlVGJCYkRQVzdEUzdGaFQwbDRacEZ1TVVRTktZSGtqdHBSWHkxaFJWaWEvNkQrQ1dWckdrQ00rK0dGMHJRcW52N2xiU1lEUmtqdk9aVnpTZ3c5RzhJWVJac1VBeVZYQzl1SWRtVUpsa0o1VVBrREtoaXYwZ1lnakgwWFA5V0hNY1loTGEzMDlxT1VScFhYano5YUgycmR2K1lIN1F4S2hFQVVRa0dDbUxDYnl4WThoQTdtaU5COHJ6UjgzOXZZdDExc25GMGNKVndlODdiZXlJd3JkTXhZdTgxYmVzTjVUd0lBdmpCcTJyMmM4MkF4NHh3OVIrZmIrOENLOEsrZ0E0SUlSd29oajhZQmxqMjZkN1d2N2d4ME5FTTlDS0VGQktDNkM4RzBXUDdKNkg3NEJ4OUFSaHV2R0NvUjNERnowRER6ajNXTlJGVmtzU0FLUDJwQ0QyWW9jV0JQelljQUJPRHdTOThhVllranc3UkhqdjNqWmpkRkp3Q1ZZSVRkYWVPMWo5TWVHWlAzOUp4ckt6YnAvNzVHZmtBdTdnWVpYRFBlRjdlVWlEU2d4cEVUbFJWdEJGRzk5WE5WYnhlZEdQQ3U3RVNhZVQwY0xhdnhhamdwT3hnMWkwRjhBT3lZVUJYdWZVUEFLZUErMGxnc0F5OUJaeEhoL2d4WExHS0Rlbm5leStxazhENWduZ1RoNUlsWlFnM2E5RS9QTE9wWlBVK2xLN2FjQ0gwWmw2M3NyZWJUMEZxQVcwb2lmSGpqeStRRFlwakdDcXJpaTNUd1hRR3ZQOXVFQUZld0Y2c0dsY0dIVUZDZVBpUk01Z3Qyd1QwL0hGVWo5VURwc1ZQdHV0dkVEZk1PTm5uSTFuQVlxZzNIWkJiREY1b0Fmc1ZxWmpYM0FPTTEzNTJZVmc4NC84WDJRWGN3bC9qR093aWJqYVJIQWRDSHRTRmxjUVVNT21uL1RnaUVDVnNRNG9VQXFjb0JLbEl4anVKVnRkQWlaUDhnRE5BRXVMMkhnbGE1Ym1JTm9LWk1ReDNHV0hoZFhnYVlvOVlPdkJnSytHR04rUEpTV2xueVMxd3NaZ2NYL0ljWjNuZW1hRlRPMVZlOG13Y2REeC91TjBGWFdUY1NSMFV6RUhTL09Wb05YY3BoekF3cnhpWUhMM0N3N05xaVZLN0lIV0xnRy9BTTdJNzFvT1N2MXZLN3lnNTZIaGcvRCtBWXdOZ1FQdVBzaGVlS3Rtcms1cXBVZ1RQdnE2QlF3SVJlY1dBSXpJQWdvUHE4Rk9ZWWFKWWdIZDBIeUoyQ1ZIWi9JRlpqMXczTnZ2QUhmT1Z4K1hWaW14OGpKQ3lYNXg5ZVB2eTUxUFRLRzlmc0FGdVR2SDVuSHB2M3RKNi8vOXovNnJ6OTVuUi9kbytTN1lneDdhdGllQ0pxY1kySk9ZU21JNklQUmhaYUM5TERmQ0duTEVJQ1hIU3Fpa2QvYlh2TklLOWJoeGUyLzgrQUFkTFQrWXlWKzM0QkYrQzJWOTBBWlRORE56YUthenkzblRoY0NPUEZ5b0wrQUhtRnRYaUFyNm9PNkh1VEhQa0prOGdzZDJGMkhrekh1N0tDNEhVQlgvZlBETTErTDdVRzczeFVFZGcvTUFNVU9NczhjdlRXa1plRG1jVzk0dXcrdkpFNkxnTVEzZGpqMERSQWZSam91Y0hEQkdXT0JzWnNOZ2NHQWh4ZTRDS1ptS2JnbmRtMEFjZ0diUEdwTWNXZ1RFcWgxcEx1QmFoTnkzT04xYnk0RnFjR1BiUlpub0Myc0JPYVBqd1RKSUs0K1JGd1lWb2xIR2JBanJMRFJUNGhqYjN1UHZkYW8zMlhvQ3VBcW9GTFAwU3pVRFo0Y1dCQVlOWnRiRW1hdnMvME5HWjA0cHBtZk1NU243RFBVRzJOMGYvbXFjR3VBSmRDZ3ZrcEppaG4zQlB2NEtwNDVENXgrcnR4emRoRllTVktNaENHYSttN3Mwb0kyTDY1OVRUUVNTbGpmSVRwaWpYQ1djVHpwRzlEL1BsZzFlUDZIek1DU3gxZnhkanNISEJaaTFXU1JEMjYrclBGZzJManpCK3dBamVCRElBanZtNTk4S0FSK0FsU0dDUFhDWXQwaS9UZE05TDY5QVY1UVJTSnc2d1JrWHhlcUhmUlVCYnVCdEgxZis0UEhBTzl4WTVqYUVWaGdJZFlzd25nL1hOUDg4RHZvaUJ5eEF0VFp6eXRqL1Jxd0g2eitwTUJ0NWVMbmgxR0tHL3pBNCtYSTNtTjdlQzNlbjMxbmxaOE1Gb3BBOFk0RkRlMEZzYkVmUUUyZUExb0IyVUtxTm9SaXB3UjVZVWVDekF1VThvQ2NNSjZZeEFnOXd0bWh1Q3pYRGVTU29sekZYY2ZEQVhSMW5CbW1KYm8zcGlnd0xlZ2ZzQUdVenU0ME9JQXNtcC9sSGJZdWFjSjlNV1BodVg3OHJPTHRvSmE3djJ3VG9wQjVkNVQxWmxWdVREdTIxVEFHREI2ekNaaUxPTUNobjAyOFhrcW83cnc2SUJ0RWhrNFViUytPQ0hqVFdVejhDWTVCem9Ub1llamhNeDFFQ0JPRlRraTIyTTRIYUFYa2ZEUnNDVDNvN3g0c1BSc3k4S3FHSDlDV0RQRERjMkVuQk1NM2tPY2IyQUJzVHhBbmVqMGVMMkRNV2IzeVhtVWplOXNjQXpDczMreFN5ZTlCaDIvR3Y3RkVPTlpWV1JMamJzOExUT2tiRzdMd2RMT0FqaHJFbW5XL1FOWllpaUJaS3ZuQmlSbzFBYWJBMk8rV0gzWHpoTUxFdkRqWXdyTzNGY01xQUZ4NENlanZaUTN2Y3EzcUhtU2xjSFRNUm9YaUpMNElCSTJTWXZBTnNHM2hqWWFTQXR5SWlnV2k5T29IMVFud3o0MktIQ1lQbU1KVThTQnJ2U0I2bEIvaDRMNmJWY0tKWWM0Q0NpMFpHNVdOQmNITlc3Vzkzd0VoaEc3c0M0SUw5a0hVd2RmNS9NdWRnK096WXBDcWdJMUdZUGtTV05lUTR0d3ZPQmVibnNFdWhpUlFYQVM4WDRDWWdGL0FDNWFOUE9DS3RWdFRtTUR5dWV5b1RsOEdDbUtvYnc0cWNwZTJzbTRMRVF5VkZ3UDZzVGdZRFVRTXRVUmN1VGV5RVJxTUhNd0dhaENsVkd3OEhrcXZpejFselpDTXpUYm1nVEhSaE9QYnI1TTZBYUF2c1JXd0JtejVIU2dkY0NEZjIzSHVyQ0c2L0FFT2NKT2Y0ZllYSEtrQ0FYNDJjdUsvVFVKcEVNYnhaNE5iSWZwWWZvQldRTDVxeU8rRUxYVzhEbDRJSzQyZGwra0JHUGs4SkFLV2lWSzl6NVZnK1RqYXR1N2RxNnJBTnFJRHJHTElJampNYnJtQkt3TmV0OW5ISlAyR29mN2pTZjd4SU5lZkowRVArckdLQm4wRTFYeUszVUZJdjd4T2ZKZTFoZzBYU0RQWUM4VmlkZUpkTko2M0R2M0tNRmRNcnM1QUV0MVFiWDZnMUlKQVk3WmhjNGpBZzZiZmZjbFp2dkxuMW5qeDlQM3o5dGZmN3g4L3dLMW9BeUxLQXAwRjdTdmowZ0c2Qmxta1FjM29tTkUvQmJQSDUydVl0Tnd2VEZxU2loYklmeE5BZzhGYmRERmxzQzhNRFBuK2pPZ202YUN4SUpnanRwVXROY1I5Rm4yMGNDVThVT0N4ZkRmTU5xQVYvd2FpRHMrSVFPSUFqMVJtY2FiNU9TTHpZc1doVk1PYzROL0NHcGR4amRZYm5nSGZjb1JnSEpBbHNQMER0LzV0Zi83cjdibisyaDh0L3dDQjRDenlnLytGMytCWkY2UUhHd2JzeUxWZ09sSTRVY1VicExEbjBVOGo2U2pMdGJDUWErYktDMHpEQmdIbFlkTWIyc2MyYnJ4VWxGc2NvTDZtaUF0K05uQjc2QUFQeHhhRFRGaFlMT1MrSHlEOTB6b3ZLTTNMRTNzNDBFeU1iTkRIdzBsNTZnZUs2MTdpWTkvenFvL3g4cjM3VDFtdXU1UTc3cXJGRXhndnZQdEVHeUcxa3E0VEU0T1h5UnJZdXhPZFRudUFPejVzekY1VjVvOENsSHBOSENIS1hUTHdwd0NURUlRM3duTkJxQkFVS0hkZlQzQnRCMTZONi9POVZRV3VmQ0x5VE93cnVyazBJNGp5UFdEQUtCUm1tTFhwRVNLTW0wd1JVSUhsdUNWYXNtRGVIeXVFMVFVbFNZQ2VWZUhLRzNmMGpBdUVOd3dhR04xSFpoZHFKTlY4eDJkOEdCUU1iT25Rd2hoaVVka05XQS9qOEs5QmxLWHhiNnVFZFNITk1HbURuRHdqa2cxZGdLYU9iMlljN2FOWjZCV3lnS3hsS01xZTBxR3lzWlBHcGtQU25pSlZJMTFHemxwbmxYNTdXeHRPUUFnSHFtaFFkN09yckNFL3paZWtweGhoUStuaXVzREZjWlRnSy9qS1ZSc1cyRFQzL2FwTG9yeHZGMFhZVEFSTXNxT0NPSXFWMnNid3N6TkEwNjVTM0ZCbHZEclBpbndZUUtoNGlucmpiaXJVcU9ua2hpUUtSSSszaVJNVUE3UDdNc3VDZ1kzekI3d2dlUkdva21ZQThFTW82aFZFUWhEdWxjRlArMkExNE92M2JpUWFVaXdJM3puaTh5Rm5IMXF3OGJudkI1VGhKaCtXSEpOVDhKaFR4SmJCSCtyc1BPcTRqUzBYVmcyOE9TZTBjK0FqakFGRFFuR3ZZR040eHNkRmNlR1l6RlpyaWREUUMxNzhMcGRkMGEwNERmZ1JxMmlBazRWUE9HV2VGdyt3WWg5cjUyV0F3NDFQT3JPR0VKaW94dDVmNEp4dDNDRTBicmhrTDNsVTBFMHlBWUFUTytJWFBud3NXbmlYYjZQYUtUem9mdFhuelRkV2dOS2FWOTI0YjFEWWwydENzY0RNQ084TjhYMWdOaXR4dTdxQk94RHUvRDdnSDVNbmNEVXcyTTF0QUF0RDNXbnhNalEwWmNjUTZvQ3R3RHdsdFJpU2dHRCtJM1Jmc1UxUGh0aSswRWgwYmpYRVJ3ZVFWb3NnalNkZkFTMXlZVGRlZUVpbTk2dDEyNWlDRERLK083Z1Vmd3J2aTBhMFFMSWJDM2hYY3pHRkpmcCswUDdrUlpCZ28vT1BJZTBuSmFBekFzVStCaDB5MGczTzNkS0NSemRsNG43TTlRQkpjVk8vbkJKTG5LOGhHRExrZ1JGcGlCdmFiakR4UkkrQkpWSllFRll2RXBrUVVTSGVENUhGdGNNenRMcFFYNlNxWHhpVWtSQUVWZ0YvTnZQaHIxdG5BTGlJUXJXRkhHSjNzUmNMODhicnc5SVN1QWlmZVpzNGVCYVVhRis2U3p3SDJ3U0hob2EycDBZQVBDSnlnMC95RVhiSUw2enc0NEZmU0NBZlFHSzViMGJ1UWY1Z1JSelpCWHBjYnlxRGI0MEoveDc4UmpXNWhLSG5BcW9FUnhqL3VMR3p3TWNuTCtINC9WdmlweHFnV3l3NCtLaWF0OWVjaVhNTnYyaDBxa2JKYndCeG50dWcyOTExeVlhbDBINVkybTV4UFhsR0pRSGx1TVNNWUtLTjBVUldPaEJxcGkrRWJuWE4xcDFydCthU0dneUpBUGlVWmNOaGNTK01jWWpCakVvR1JGU3daUDhQNFJQbi8yM2RNQWg4TUdPckkvQWhvb1daLzk3WGVQYWRKMCtLOFc4bXBkQ3hKZG15eENlYkpHV2ZEVEZoRmZjRzYwTVBCQ2IvZmpzRThzY0Q0NlU0eG4vZS9jRlRZQ25yTUkya1NKdHJTeWNxQWorQmRNU1RoR0xYYTgreFIvTityWWZ4WHArRkxiVmpQMUhSMjlnMVJPTUYvSmYrejl1ZHUwMkRBRnQ0T3V1QkVEd1EzblBodXdCUEY4SmZFM2dmNElRNXg4c1VBd0o0RDN3cWZ0TjBYNnlZaDErdUQ3TVprczh3Ti9LQlFnQlZ4dERXWFB6YlQ2WXZZaWkzQWZ6dlpBbDkxN01nZnkwSCtKZ0hpVGR3UE9DQ3NXMjQxbFp4MVhEaysyS0h4ejNodXJqenJhaUJOYld2a28xK3RoNDhoZGxyLzRlYlhQKzRTejRTQVVqbTNZRUdhTFVMRDhaRDRUQ1l1THNPVDhubjhWL0EvTXQyQUE3bXlmTGhmQzdnYlJ6WTU0eHZlajRVR292c21zNlRqZjNYQi9qYnR2L0xlOFlhUVA0c0RSd0RJd0Zpd29Wa1RmR0o2V0F5Rzl1Q0RVSFpRWmRHa2JxazVlKzdoL1hDK3ZTeEwwZzZXb2xVeXRBTk9PS1hJMllWTVpic2pDZEM1TkZGN0RHODUzMkJ2K0E1SktkWDVDL3F2a3p4S1pENGJ6WUFJaVNNemxXYU5xdUd6amlDRkJGWUJyb3FYOUVHaFpueFJEanc5eTlRV2lhUThTckN1ZkVIbXFPVkU0cUxQOFVsUTVBYjJnUVZLQytXYlV2L2JwT0Z6MUdDWm1Ka1A5aFJkSGMrMTVCY0dNcUJ3dHp4WHVqbU1NOEsxb1RObWh6QmhKWjNjOUc4endhQVJUNHp0WkFoUDMxalBMQW9sejV2bU9tWStJTjlJaERjajJYR3g2ZjY0VzR4bFNiSndDYU5ad1E1cmNMeXN1S3NuUVZsR2FUUjkyWEFjbjBtYmtydlhBVm5oSWlNZW1kWkZUdUM2WjlvMEsvS1pmTHEyMWdZUGphelFkVTh2a1lTdXI0VHhpUHdYaWJSWCtPZDRQUnBUZGtUUGpOVThvcmVzWlhiSUJ2ODMyalpTUVVhNkNycmFld1BxTFlCOGg4MGZ4ZzlqLzBCRUxHemNuMWNTR2RqSnFaa2pDUFNWVUloaURBU3pjcnZZT1k0aHE4SHZFZzc5WWx3dTBNbFByamxoTHNWVEdRR3BVV3I2ZmdaWkMwcktWUEpSNlp3YTl6K1M1Z1NydjJZemxBNFRUVkhscDFWZ1N4Ymh2Q0xhWUg3dTVVajNEc05nQjU3MkxEeUw4eFJQd2hnQnRVQ0xaNTlSZUFsWkNHRHhPUjRvdDIzSVhsSlpJaHZMU3ZEcTFxWTZRU24yRXlBQyt5RE5UUytYdlBKNUQybTZSdDBMQlhUR2NhbzhTMWNURXR1dllOMFZTeXMrY1BIckVPVllJTmdqdlFrQkozNzQyMnhrZGZ6bUhTb2t4M0FXSXhqVzhQUCs4S2p1T01iNElNTGhJQ0lBSEJSYU1UcnRXZ21mZ0F6QUFudkhQY0ZSdkJicUpOcXcrc2hqTE4zSkI4b0JsK3RlTFp2UWVHV2dZNzhHTEpTLy8rNjQ3bWZ5VXo5MHhEaW95VG9kY1R4NExBZy9qSGhic1ZoaVYyREdDQkgrSFNUZ1BuRlU2TUJqOThQeGt1K2ZqWDhOTjVuQW5YYSs1ajZCQ1dDOFhBMlFxQlFqQUx4TGxDV3VGR2tsdkJYSjlsZEFRK2d5WkdLTU9yaVVZQndBNHVERHQ1WVU4Z0NOR3FEQVNyWU8rWDRZTGFRLzJjTjlxMGNIZ2JLUVluQjRQNjVFSmlKUGZwYmprSGNEMEJLdDRub1g4YnZ4eFVqeXB6TDRZcVlHZmpSQi9RQjVKZ2tTKzhEd1MvWDF3Y1lLaTFlQ1NrQ0lnTFNwN2J6T1lrSHhkbTZvQnVqbXcweWd6a3hqM2xqSncycjhQd1krcm9uaGkza29aNXVNVzB5Vjd0TXUzZkxPVUNDU0dRVXRMZWw3UlJuTHdQMzNTRGhTVXpHWDhyKzB2RkhqT1VIdU0zbHBEUitiUDdCdWU0SEJNYjN2NmFSK3IyTTBveHd2TG9CTVRSZUEvcU8wbGFVSE4rYklTbld2U0NrQmk3eGlUeC9SeXl5dFV5Z0YvQkJyZ0NodWs0Qk5VQ0xyNERoNGE1Sk5OSk1la1JVSlBWWFR6ZU56Z0ZhVTQ5dndpSWlnWVloZVN1VXZjUmtyQTV2UFlCSTBiRGNZeFlSUW54Rjg1UHNUckJLUmNxS2pWWU54aGkraDdSQnJ4S0xzVldjU01YWlltQ3hMamk3Y3E5ZkRLQ1VDd3NDclRnTEZrbytQNFFSZ09nQi9qQy9yQStBQTV1QmFPd0Y4QWNDQXFFbUdqYXdKcGdTN2h0R1hTQi9mS0pKQU5sTlJSUld4NkVaTitEZGJqbENONGpCYnFIWFZvcHh6d1MrRW02TXY3MzRCVStIR0ZtT3R3WXc0c0ZPZk1IYUZUZ2pNbTRsandnSm0rM1dtOUVBcXlRZ0dPNFl1Q2hlaEszZDIwcmZ1QUFGdFI1QjRQZFNIN0g0SCtnUEFHN3FxOFBsQSt4cmpNNGlsY3o2V0R0aTZWRzBYTUQ4MmdjdWhHdyt4Z0EvOXVYQnFQUEtyR1JzUTBXQ2txTmd3TEp5RnEyZTlRUFFtenFmM0lScllSTHExYUFQa0lNYmpXOHc5bkxTb3g5V0VYT0g4K0wzeHA0TWpIOTlUZm52Q21mbGZnS2MyMFRXOGI0alh0RVExWG1YOFA2V01Cc1Z3SFpCM1c1ajdvQXRrQ01vQkVTVWdobEJTQkdjVzUveUd2WitrSjU4SVphM0YwTGVZRHdzVUxvYmo5alI3bmJDRWptZnRCYTBJcUI3Vml4aWt0a0xzYWpKVXZQRk9OZXoySlhMNUhkRGk0ZWxBWWNWUVNCQUVhd0t5RzV1RUlxQVFiTEFYbUFMY0I1SXNNVlNaUm85bVplUlFaWnMrZDdvMVJyc2NEUXl0QXllNlRtMm1RYzRvNlYvb28vN00wc3hBYXQ4MFlvanIvZGRNSVdlcmVFU01vZGFjSW8zMXMyeWh3U0EyUEdZaVJ0bkF4UGNsdjRCQ252ZERVOENXTTBJR0VSdHRnc0xiRVJHZUpoN05RK0ZQcUxuR0xaNlNtMEF5dFdFTkkrWFlzWjZBL0JBaFZDajlWaFNjR1B1TUFpQUNKOEg2R0RXTFpyYlJ3YnFRaGJCVCtVK0pka1pzajF3MmhiSVk3QU53QUQ4ZFVZYkZ5M2d5ZU9hV0llTkIvOHNtUUpVMTJyQVFiMk0zQ0lYZ3pIZklSR3N0ZWw2MWpkWG8vaW13WkRxVkpHNE5pK3pmNXQ3ZjRESzl2Wmk2R3MrSUR1WEdBNkFJTnpCTU9YOWpmR3IzTDkzSEpZVkNzbk52N0pxTDA4VTc3UlByU0VnaDBldHByY00yUXdsUE9DZE1LVUdpM0FjaWJWOW1xVU14Z2kzVmVnQXNYNXFReStzTXBUS3pFMHlDbWxwT2xEQVNnWXhpTGNIWW1SRXorb0I2M2JBUVIwRWpzMTdFM0kxdW5VWEQ3dFdVVDBNTURmTTJ6SXpYdTNSam4yOFBqQ2hXU2RvY0EydjBMUmRZTDNCVHNPVmxjTlZlWGJBTjdxMnQ2VVZKb1lBWTcva1BNU2VhL28rMExkREE1QmtOQmMvaEhETEJMSjU3MkpRTm9ZVHVWMnNrZmdJZVFZbnZCVlFQakpFSkJqRGZEQWd3TE0raDZXVEZlakc2a0lGKzR1QUZ4WVpFemI3QXlEdWI3bjJDeFJGYmdBMlpyUmEzTk0wbXBVdmdCM2pJZCswd0hFYWNEdjUwUlBiVDM1NUxHUXhId1Qyb0d0VEo0MU83b3h0NFhkU3JqVjhlS1lFZThDVzNNZ2lXeFdNVnRZcFNlYUtBbDNRNjdRY05nZnJJUTNNNVhJL2tCWldoRXZpK3BOT2FvZk92ckFYdDRXbDc3REt3Mnp1bER1ZWNIczNqSmhlRU9GbE5lU2ZyRVBWMWticlJmamlMMWVMMFRTQ3pKZWc1Y215YWF0VzFBUjVpNldlKzBNSmNEd2cvd2NFQlMvTzJZdjF0bkdNT00vM1JhK240UktUWkR3UVdBQUQyd3pqcjJneGdMVmpTaVZNakFXODB2cnZIdmJmbjFWVGJURHpWeU1SYmhaQzd3d1Faei9EWlpoTGFUeDFtS2c5R0tDbmtLQiswRFEwS1dDWnBneWw1MU8rMEdSQlZqSk5YQWRtQmtJRkE0L3BDbXdDZ0FoUTllaXF6QkFhMWVhbnZ2L3FxZjQ4L2wvSmpUOHZNUEpsMGZ3NjN3SXBIQnVGbUJSYzZZa0NoY090Y3pzeDNtQ1VFb3FibW9GMUhPRDNFekxqd2ZsNjFYZDBQSXJIa1RFQXlkeDhTc2dLbEh0MTAxMnNncnk5N01NTjExcXYrQldvQi9NR0I3TW9zS09xMllQOFQ0dnFCQ2pWTkhJQy9tSnU4c2xnSitTdlllc0FRQmF2SlBQcWtOTU1HSmxZK0c0Y0IzZGtPMEQ2NlViNGZrcFFmbFZzdWs2QW5SVTFyN3NhVHpYYjhhemI2a0U0TEYrWURTZFo4Z1VCWU45Z1Jld1l6bUhqQW5selhIRUN5R1BaNTB3Z25oTVJXc2FlSDJzNWdSMUFWWmJtdzcreGF2bGIxMGtQUUZ4dXFIVGJKdE9NaFVBc3NIK1B4WkxoRzJKOG5xNzhJeG4xMTZNZDBqUk9VOG1GaEV0dVQyejlzMEpsR1Fsby9yaVAwSG8wdjZJMUh2aE1zem01emlBVlV2ajVzMDQ4NHVxWEZ1akd3VGF3dXVXazFZTGxWWGhOaUF0MkJjNkVId1pTbHB3TkNja09BaTRpVy9CejNoSE9CQWErREE3TWYrVEpYT1h4eTZEOWM1WC84MlYrVU9iRm9ZbHNva2I1UVk0TzI4REVBNkJTTU16eXp2SExWb20vaENtd2hwRlp4VkJ1dThRQzY4MWFuQ1JDQXdGalB0RTF5eDRFSVZDYisxZXZMdWRGc3dMTUJpY2ZzUnd3Q0FBbFFCUDZBdmNNbWxhV2gxZEQ5TEZhc29NcktYUTRuQWRFaUlnaWlqQ0EvUDVhZlY1REVjMUE4bmRhWnJDUzJ0d2hmWWJ5QjFDcFRQd0dGMTVHT0lOTzA0UUZhTjVtay8vcHFvME9wLzFMT05QM1d6WVliSTUySnlUUS9ZaWpiTE5rSnBmU3RKUUJTMk1kdnVXeWVSb2RLbTFqOFZ6c21HNWdXTU5Dc2tEdVBXYVMyeHNQQTMwWGlHckcvWElSL0FPWW9nUE4rdUVXMW9WRDdFTGdCYTluOU9lblljdFdqR0tjZy9VVnJVMDQ5ck9hL252SE8xY3ZPMFVQK0RhY0w1UVhzSG4vOUhPZnBIZy9KRk5qbXVJdjRHMkoySEcxL1RVektUVjNEZkNNWnBQWTViYnVDZWUwMUh4dUNDTzZGcXJkVmNiT2NVQzJKSmczZTFtd1pZa01vQjFFamd1d2pCbWtpZ0JhWUdZMDV6SHJmVVJUdjNFcE9YSFczMTU4M0JVZVBPMkNZVDErWDdQa2hGZEFoSThYeFVqaVFuaDNOZ0x6aUVPMi9tRmVGcjd4cWhYa3I3NDl1TTBPb1FRdnNWN0xORW5aQTlxQ0ZiRWFjejFtSGl4bWZNeStWZVB0N09vRE9ZNDYwUEVmcXdNcXYzK3loR0Z3RWJYOCtRQ1crQXZhd2FqdjM0TFpPSEJOMnpVUnUzdExhUkF2WkJiTWhLbXd6Y3NhSThqRGpqZ1JDMTk3ZldmOFFJL3NLeXhDWHZ2bFltejcrbGp4YWkyTHpTUlpQcHZ4MGJmdExpL09BZS8xOXZzRVZYMnVCM0tOMjJ0WW0xMnRBVUhGMFRzd01KNjJ3dzNSUlY1OFdjOWtqdVpUcG1JcGtDNEJTWVRnUG5pYWlkbUwyQ2pRSzFiaUJHYW14ZDVBTHhOMTJpejhlUW1nSk1OS3BsVHZhbTBBVUxzQVk0Vll0aHhabXdKSSs3Uis0S3p2VDVMbHQ3c1hMdFEwbVo0bnZQQlBsaG1iejJaSStBTXJLVjZEWnJIaEZ2MUkwb0hLbVBRblZ3dGo3dGZpL3oydXpEZmJOb0FjNWcrVHZVYXhPa2haSkFBVENMWWx4WHh5V2ZpVytreklyRVZRQWZyYmpCNWFvSDh0Z0FVZ0lTQmQxZVI5WVhkbkxUd0ZHUFMyNThzVldKWUpnaktMSllvNFh3MFZWaFhBL2hwVmczZGNyOTBYY2dKMHFoaVJoOEUrV2lwVExqT0NTTmVvZHhzTmlkZ2JXOGxxdjhaaW80MFZwV05TeG9Qc1h0azZCSHpkKzJCeWdsSEIrU3ZBUWJhQkc0LzEzVWpUOUFYWmQ1TUlzQm1yZE5rSGJIaGtiOENiejlYTVZHMW94eW0zaHNzK1ZtakRDa0I3b0hNd0dyVGlmbm5CdDUzL2krdzh3SWZDNDViN3k4K28ySlFKWXB0MlBVQnFqb3ZFZmlFcTdDbklOdkNEdkFiOGkzMS9DZ3dNZW12NTB3T2hneXNBdm5KRzBBT1lMZDZYajJ6amo3bE1rTmpIbTFkYlFsbExrVjB4RG40YmRoa25tbytrdG1iZUZJbDVQb3NXUkM2UER2SlRFYzQyY21md0Q0N05xc010UjlTc3haUFh0MFVMRk5qRWZiTmFVTUlMNTlNajh1d0tvN3V5clhRRHkxL3lDQ2dTNWl6ZktRMWZCMGJBUTJiYzkyTXMwdG9STTFSdno2ZTFDZE01Z0ZMUnlxcjdtaWhOQXF3SGlTRDJOT09hMGk4M2FtcmY2a28wNTlsTnltUmVmcFpmZFZjQ2MzMExXd2tsK3VVZ3NVVS9hMU9zOVVrblBXbjlaWVQ5M3ljNkZOLy92TmpmN25udWVFd3R5QlpJYWNFdmxvVmRlTEhSb1QvNCtSZjhCYUtHdFJnTkJNdklDYnNCOTlmb2lJbVJiMDZVTlYvdC8vS08vN3doK0FZVHZpeERyU2E4N2M1RmFhM3dTUkhQZ0RCMnVHY0hmZXlvODdyeEF5Y2E5Vm9oZ2FuVXVvU1RQWkFnblNJQ0VZTmVDUVpaUUhsOU5hNlAvN1Q3ZzBjMjNSbGhzUkRmb1Z0S0VkeHhlajh3VWJOWUpJV2hSeWNCZUJsa2JSRXR2aDlZeDdYYy9JQjFRMjNSVjlRT21kUDZOdDhEMHArUi8yVTdGcCtIcEUxNDVFSmI4WkpjRzhaeHdTV1RLZGhmSzlRR1hxSHBGdjFtU0dEaFNzc01NcFNZeDBCaGgybmJVWERMQlhNd1poNEdjUVVSenpCbmNXQkxzWENubVRwLzBVVThjTGF3RDNpQzhEM1JjdGJiTFBSdG4yTlBhTGloU2pRUnFsV1E3RlFiNzl2U25aR3pkMWx6Y292M09oOWZGVUthdHZWNUNSTm5NTXhrcXNFRzY5Z0NYc1VPVHZ2UHJyc2dIYmErbnh3UC95NHo3TzNEQVdYUVFuMHBXNGJjL09xUnBVNTRsOVBSQ3I4d0F3ZW51TnNWMEF0cnVOS2pKNzRQVG40Vm9iRU1BVUV0OGY0YlJKT2ZKNzBXTDVyQXd5aEFLdXZkclptNWJYaTVMTU14N1dyNDlIUGJySHVaM1M1bGlMaXRvV1lkei8vazgzN0JVaGFrSURaQVZsdDgrRUVxTDcwa3pnR2lqVk5ESG4rVVpaMllDczlYc2wwMXRzVzgyVTZ3Yjk4TDY0NzgzZ2FRUm9tb1ZOaXhBOWduYmdtaWd4ZXZ2QnlHTmR2THRhejdlNUM5TzdZS2xGNkdUU2ZTMUsxdVpHdGhNdWltVlhPM3paR1h3QUF0T0cwNDNTNDRXSWkxNEFhQXkraEpUbWlqZGJVTlZGekhmbzdHU3hhOEdnYUNTL0V5a0pwcVlVZTBUdGdRTldzSGVPa2Qzd3FCdzQzVjlySkpBM0UwaEhvSGJQbTh4UXNadHJwdk9TenZrMmE2VUtNeHVqS0FZMEVpWUpCV0JrUGp6SzJjZXBLVkxLQkI4SzFyUU1wQ01kVS9iQi9iL3VCbnc5WUZ2M2hhdEpuRnNva0F4YzNCZVJmM3I5YnVSbmZ0ZHNhaVBuYmRCUXRSY284OEtMeDJRcE10dE9FUnJsb3dYSmFMNWZJcjRsdFc0Q2Y0S3lzUUJGYlR3dlZ4K200TlAwQWZaaEozUkIvSmZvbnRGNjVwRUN5Q1BBTTd5UTZkdklVSklkdU9yZGhXdUc4YlhvNCtkb2pIaVowbGJESTA2WlVUQUNjYWlDM2JDWVVrQTljQUN2TUdsL0pjR0tiSFFrVCtGeUhaRTJDTGJ6c1hodUdoU0o5MXBza2NCdVFlTVBxOXk0NkJ2WmVGRVR4dzA2SGp2TEIrRTdlUXdwa2ZnM0pHYXhYU3hEODZvY01ucVZDYys3SExPZDBYVEhQcVI1TEpLdllIRHhQMTB5Q0dZWUh6S29hZzVBKzFZUVVlcDhkZ1RCMytZZXZHTjdYSTczZGZ0amxBNmNxSk0wRlU2N0JLcDM1cjFldzYxYzhNdnNoNC9ZQ0JxTWxzQUNEbEVXQUF3dTQwODFWOW0ydzduOTcwcy9lMS9Cclg1bTZHbFJxTGdLMnA1b3dlK00zYVVsTkpGTFNhMVlEZUcrd0crVnZHWTc3SDNtQTBrS2Q1Z0ZWZml2WU1Uc3VFT3lLMW95M2hGUU9hZGZqVzJlM2JKQjZvZHdQSnJtSlZTbU9EYjZYUnZjQm5yYkNiblpYdzNXN1ZrQkdLN053STY3UUZvdEUxSzlBZElPTEhOaFY4L3pUaWpFMThiUEpIeUhsKzhET1NadmtiM3FUWitYQnF0WUJHcHcvOE5aN1AweGtIMlhrL0wxaDNYdzZqRU5OKzA1N0pOblFreFlnNkdCblFBWTQzYTJmYStiMTF4VUYyRFhZQVVOcDUzcExWOTNkZjF0V2VkdDFoVzREeFk3NE5Pc01OSUl3ZmJubldoQldQQmdRUWVBUG9GZFpSbXBqelRTa09uU2xVMU5nc1A1QUE2MWd0SzhiSE5FbHJWcGVsL3pCaFJuZHIwaDU4QU5wbldYbC85L1pLWEhqWWVLb1ZyeFd0SVFpMXNCZWdZNmc3b29BT2dUZGZkTk91Tml6OWFjUTlsQjFVYlR2TEJ6RUFoMkM1WHl2VjU3N2ErQldaRG1zUGRidG14Z1BtdHFBWjdaOEJqMTlWOG10MUNBb2dvOXNGMnJibWE2UTVQMTRJMGJqejA0Q2N2Ryt5cHZnQnRYOEZEV1Q1Z0w0MW9HR1ByL3VZcWJxdFM3N05lZWRXT2c3SWZQbjEvcXJ4aDROYzhEbFBISVlEcG9Ib3ozUURLc0pxdXRSZ2pNZUluVld0cHBRTmt0aGlubm41K1Z3NEJUdVJXQXVjckNKcXRCTjErVTVoRW00Y0IyQVBMTjZtM0tnQ3FMNHFVN2NGNFNpSVVVQnpubGUyNnY3cmpyVndwdE92SWp0dXlmZzdzWGpnZURBR3VHM2g2N0NqdU9SaU5RTzBZdXNaMDM3N2hqZGNXQ0tNMU1RczdSYTRLL2NGYUNHYmdxS1RHTFRVWWJGSTlRYkZma2lWN1BDSm9DTkVyL0ZKZk1LS0Y1YWRsMElKMlV0Y29ETkxPbStLYWk1ejZhY0ZIWHNOOWJlYnhYak10T01DOGNMNTdkSHQ5V1Ezd29VVHNjcGpXZXNJVzJQdlFPb0wwN1ZNY3RwbWpwRzBmQVQrRDdSZldlVXdpWUFYZWh4SzBGS0hKdGNMa05tcWVTcXc5djFBbEY0N3lScXV4a2hOTjR4Z2Vid3NPZWlYK05mRFVxNVRwZXRiUGpaajNlV3lsZWt0UDZsRmdzSEh4aExpOXhPMkJVbi9KckxQQXd3aityb0txQjV1R1JkbW5oUjBoSDNvOTRWZi9MYWp2bmdBMXRlNU5tOUplaUpNdC9sSWEvVlEvZmJXZFR1TUJ2Q1Y5T1dnTUVRdWI5dGcrOGJUc3N2UnhnUXc5T24vS0Z6RGZIMDJ3anpzdHVrbnM1cXNYeEJrOFg3N0FQb053WjRpZzQxRHY5QWZxNHJpWmx0ZkVISkkzRGo1aW1aaldEU2d4Wk5QQjhrN2VVd2I2TjVrODNFQzlKN1lKdGJpVGxjMXhMYmpFY1RYQ29ieUxITjJtelY1T3VUT01sSjlYNXoyR0pRVDI0aEFBV01rQ2JMQ0RvQVp0aW9DSm0wMjRRRnZsMk5xRElhRlU0c01qZ2Nvd21aVjkzVXlOTHhVc0RTam4vQTlnTkVlUno1eC9SOCtNalY0amhWd0JFMjI5UWw2SElUUHVRZ1RtdlNnN1RFdWx1T0VrdW9uQUQ1WUhQL1FyVjV6aEF2Q2JEdytBVHhaN2k3RXdqd0NjREFlZWw1bk5mUU9sNzJXTlZ2TEhuVzIzampqNTVRU1NERG1OcGlLcktlNjZnUjBiTGxCK09FcUJnaDVKSk42RTlNQTZkYXdMV014V0ZjZ0JzWTgzRmErTjF3K1pBYmJOTHVXNmJOQ0xDNThWYlpRcWpxSkI1Y0hBZ0syaHcxZ3R5WWt1Z3Y1c1pxa1Q4RU1PajA2U0F5aTRKZ05pOWJnUXhubEFrbkFOQkNMRzFBdjl1aklHUkR4dXdMaS9xSHp4K1kxYVIwUGorMHpQbFVuMnc2a1FPb1RwdlBESkp0WTBYMXR5eGZnbmNYQ09FenRnSXNBSzlIM2ZxclJDN29oY1IvVituNThiTWFCUUwrZUlGbllwMjhyYXdzNkFHb0xxNDhWQW9kZldscjlFdExIUzFoNitKMUY1Z2J2djRpRGRERGJBUzl6U0hZeXNrN29BQkFIckFBVnhjOXo5UkhOSzRNVnl3dHdHTUVVQjI0ZkszU2Z5VWgvWFZONmJ6RDJ4NVhHTDBMQUNseG4ySkpWYTVoYXMwcnJWM1J6NmwzLzdjdkcwSEw5Ynk1NS9mT2EvNzlkOHZxdkh2UC96U1d2Ly8yYnM3eW9JaVEwWUc1dmErS0UwaWY2a0g0TGVUY2NuM0ZJSno4c2crejMrSS9ML0N5SGN4Qit0c01zcGUyK01QZWdudmZmNElCVHUzejkrOWNOSHYwWEwydWRKT1lGTmQ2SW81MVV1ZjFvMTdMTTlBdVhRMXVnWkpnaWZDZFcwTUxuUGord0lod3BoMm84aEhXd1pPOVRGRzFtMUNUb2gwUVR1RFdyTnN2VjNtMFBLZmJ3WE4rK0tCenhySzRab0daVnNEVWVEN3V3d3B3T3ZVR1o0SkhMa25qMFJraHM2T2I2Yk9iN3JIVUdXNVNCWmRoV2d6Vjc5RHNlYXhzVS9PSXgzVlp5Ykl0aGI5dUJIUDBSc0RtZkk1UXNxMzd0K2Zxd3pSbHNpRU5FeFpKOWF2MTVMSVFBRGZNMVRNb0NobXJQN0NmQUFuNkdtZGpFNmJTRDFDOHNhSWZEdEZRQ2pPMFdqanREN3hqT043ekxyakhML1lKeE5TY2c3UURnZHZyTTUydzE1MDdBQ1pwZ0ZPV0hDc091b2cycmVPelA4SDg1MVpyUkJ1cGttZlZuZGFMZjFDZERPNUZLeUkzSjB1L2t0aTRXQzRNSjd0cXdTQ2lDcHNkd2RMRjB5ZGk1ZnZmT1Bwd3pZWkJBOFFrSXlDNGwyeUFDSGlHWXA1MW45bHEzbFg3Y0VrTXB4YlFxeGdkN1RxT0ZKWHF1ejBrZm1kR0VBZHoxY01ySG1Ed3NITjZQaXVUdTlwOUNXTVR4dzJWRDdTUEdaanl1WlJVR0Z5aXAyYXcxOEFNQnAxS0JSMUJFeGZDYlZpQTQ2ME1PdFV3YWhWT2ZHNXpmeGc0bm96YVc0UzRuN1NRTEd1eFVqK0FzYlBOdFJRcStyRDN3NHl2Y2dvcVN3UWE4VGNUeDV6OFZvM3NzaWRadFlNT3hwSC85emZPNkc5WEpmQUJyS09kNisyazZaWXNGS2pmN0J2QzBWdWl6TXNRYVZZQThrRWFpMUUrdi95ZmEzV0JPbU9TTFR6eTdDRS84ZURVVGRJb2pxTW5tODQ0b2djNWliYUJCNXlYc2FmOEZQZ1hRQjVKdXhyVGdDcEs3NUx5T2poOWErOHJZb3duS2YvS3AxRWdXUHljdUVSMWpneHFuV0V5ekJPZENEV3ZZY1U3ZzlUd2gzUlUrTEZWMlROYUZsQ2MyNjh1SHFZSWRkRitESFoyK0g4QUdqSlljdW1tRkRSNThkaE9pZmhWRDRyZ1pOSW1kWEpkUkloTVFvSmlOWEgydnBEbmV6cjU2NFg0cVYwOGpBNmFkV2NXK3JGU2NGS2dERFk1WG04NDhLdG1vMzlkT0k5ZDJMZyt5REw4UW0yMk1VMWI5VDIydGc1ekEzTnpsRG1YUDArQTJzSm1RS0d1ODlycDBxOUFqT09aR0pleTdHTUFnZUpEQk1yRzNSb0RYV2M2RjNmT2t1azl4Mk5yUG1UbGtyUkZZNW5MU2hma3RMckRoZjFnaVVLdzlybzRjYWxoRzdNOHdDZ1FZU2grNkQyMWgvZXhiQ2ZZSll0Yy9jeUFYYUJMLzBFOVMxcENWTHNGYXRXTE96NVJEd1ZhY1VZZjlPZldkSWc2elFRc0FyN2tyZ0lRU3gzVmluTEZZQ2RydlZjbzlmRzNjdUhNRTZzTVhpZ0d0WkFWQWZENlFyejBzNjdTUGFLakdyOVF6WFRhclpYWUJPOEp0UnNDMmQyZmcyYTE4UTFidGVVZ2RDV2RKNFZDQWJYOFkyeGttcnFaTjJQWTBUMlMveUJtN0J1RjdEeDU2TjRza1E4NmlWTVN1MktqRm4vZG9MTEp4TmlObnA2TXBuNVNNYzBDUWJKaHFzTGJ2a043cWFLVDFhODBGOVpuWVBKTTVXalZkbWlIaG5aOXZNZVBud2xNYmlNYlpjbjJQeXdRZTc0a3ZOblE1MnNuK081M1FjVnRZQXNjRWFwNHNjajJ6dzZ4emN6aUYweGpPYXcvUktkQVA5SjZqeXBOWTUyVTMxUWpsUWNTeFRyRWt5M3B1NzIrRjZQUHNXakQ4VHJsN0xTWnJ0eE4ySHJqdGRhZmpEOHcvcnoyanRWSGR5SEoyaGw5dXpuY0tSVUxmVDVrOWhuangyZnV4YlBpWmhnNmM2d1ZkLy9uWGVpcWJIY1ZoNGVkbi93WDZvYklIKytiTFk1UHhkNHZwSFhoMWhpZ2FIclZjMDBMQ25pOVhKbUVMTS90dWJMTGJPM1Izakk4aW5aNitrbk95N3V4a3BGaDRHMkFKQmlvNGQrcEdxODZPTDltUlBkWTJDWmNQVUFwc3hySWo3TkRLWUVrR1BoZGJiVkhyTklyOXZwRnJwdllhY3NDbFlBWlJaQnplWllrUUpwZGZ4dGp3NmdnWHBEU2JjSXhBR2tHV3lRTWNFMGJXR1VkTGlldDJMeUsvcVpyRFIvaXZIdDlqZDRXRGVWdkNzaHl5ZGhlbkZqcVdZdHNEOENDMkdhWTc3YWVQMGJHTUZpZmFBWXh3d0huMjlTVEZ5akkyUXpTdlJhWld0Rm5aQ1dNNENWVWJIeHpYTi9NTkkzMnpOWHhzMVQ0ekcvakJidW5tbGM0d1A3dVRMYUZ3N2hsTDU1N2ozblNNZkF0RFllVXNpejgveTU5NTlPZU1UMFRScTJsQVRFbTRGbzZuSTF5aE8yL3FQVW1KOVN3c216MkdUa2VDN1V3c05zNGpXcnlhTFVhU3o4Q0Y3REdjSnJYTGQ3R3BBZEVjZkJpY0JTYkFlN0NyOTJtQmdKRGd6c3prRE16WXNsckRNa3pZQ2lKdWxHd1lZYWpXS0Y3UjhaTnZTaE5ZWkRML054Y3lzcW42S0EwbHBGdWRuZlo4bmdHZ2IzUk9TdnVnYXhqak1IckFGa0pGNFd1MzAvdk1pZ0tnSDZqcVowc00ybXdyOEdPc3pDNzFZVkwyc2ZZcjVJN1cyNVhpNEZvTWZKLzlzby9Ib1c3UStmS2NXQUsySG5nRkZIbzZjbXJMUzNSaURBYnZVRFU4OEl2QnNzRFRFR2VWUXUvMlh2OSsxNmM1akxLT3YvYzhmWWJtWS95WGovNzZzdjc2OEh1dDk5ZFcvMTkrMm9vMm0yWWNzMkUyMFhtOHVNOTlPOVJnUkl4TGV4elhZbkVGRmc4bnNqNGhweVhiKzFFTllBc1pmeUJCZk1kVXV2bGpPa3V0bWpVQnlpS214Y0ZmcjlXYVZlTDMzZFZxSmRDUVBQcUhqZUtmUk1GcGRCSENHN2RhemFoc01xQUVORWgvbWltNjRjaHBFZ29keHVVMWg4bzR0Z3RiYXhjTUxtVmJ4QnJBUnN2ZWRXY1VtVmQwMUlkd3pFbHNnRmZuV1h3dmJKZzFTaFp4T25vRFZjZHZPNW5GMnVRSFk1ZjRGNVE0ZEllMTJnem5GSVc3Q2cyUkhUNnpiS2kzY3ZlN09tNmt4dGpkVkV3Rk5sYWh0b1J4Vmt5aXFmZmRINU5od0JVd3kwUUhUdkI0MkMyQnA4cjJaMktQQnR1R0dSeU90SUx5eDlNeVBIL1ZNL3V6RUZLSkY5Q2VOdlAzaHkvdE1NZ3NPOHNhdXBQYS9vemsrWDNpei9lZnZ5N1gvMzQ1bkVMOTE3djU3Yi91ZGYxdTl1OFgrM096WVp2Nys0QTV3Vm80R0lDNWZ0WUpldXUwQUpuUVFjRGJudGVmQldubHY3amQvKzNMT1hYcE1tZ0pLbUdsTFRIRkJtbGZJVWFmVW9WWGhoWENYOTd6OWRhSHhjdERhaGxQL05GUkRUZ3QwL1NuZytZM1hYU1YrMDlsSms5d2lza1EwMU95akhNM0R2NENjRWM2VVQ0SDU1bjNodUZhSHhGUTJwRk8yV2h6dUZKQ21VQjNUbHZxanJMVTRSekFQN3M1cVpNTWdQMCsvVFBiWnJldmRSdzM4RHBldTZYdHVCSHMyRDZyMCtkWmZOdEsvenpzNzFGdDFYUFNiOVJsT2k4UWQvUmFaZmZZZkYydVV1dVlaM3pVdkUrelROb0NNajAyUy9Ic3JrNTd5QU5vQlB5NW5hV0tBWWRBUk4xVy9jZmNFNUQvZno0SnBOc2hLZEVaK2RCdWEzdUxJZG9jMFhWbllCUTBKRm4zWkozS1owOTV1OEJMdnF3ZUlyNGlIaTlvbThuRWJ5WlRKM3pNTmlWOFcwTmc3RVpYZEVhTXpaejN3Tm9Laks1b2JNRkptTmIrUCtBU3A1YzBwMDNNRklVV2svZm9qNjIxMVhHNElRR1FNVEh4VkduZzI0ZTlmV1ZjajhQRUJ0cGxGQmlvYU9VOGZqOWhrbk1GYTRyTnkraHloZDEyY1p6U0ZCa2tFQjdvdEk2WXVnMjBsNjFWM1NZQ1BuMU0xWTBuZXV4bTQ0a2l2aWZ3b25EMTNjTnJ0UGdkQ2JyanF0NE9jVmI2TUV0ZnZZenpucTZ6eHlwSFVLajFuZWNnQ2V4S2tIL2IwZFFCR2RaSlllMTRUSStYZURCM0Q4NFhSODRTTFZURWRBQU95RWpseDRJRGNhM3NjOXpGc24rVEpjTTNkU0hDVmdhMHo4dVNRRzdzU0lSL3pDZkhIVG1rQ2NDNUNqemV3WWZnRjRVa0d2QTNiZUhJamZBNndoaDhVUjF1QnIyMFFMSTFnS1V6WCsxcnR1M00rbDdBbzUxcnhyMlg0UkNibzFhUmtiWHhxNlJ3dHgzZmhDMVpqcmJzTnQwNm1yVFpKaDB1eHgrRWhIOEZJSUlDYjhlNkdNazRKWW94T3hrSE1IVWI0clJyYWN2R3JCaDZQV2pEZ24wVERFajhoWVlYNmJNSldyTVlDM0wxOEdKcERJY0JZYmQvTTZET2ZJL2JRVnBPWWdOUVNIWVY3d2VaMzIxZGRhK1RSbmVrS3B3ak8vZDFPRVlrdm80RGRoaitHY0UwbnJ1eXVHYzJGb0RTc3JFNzlacWxXT1djd3ZEdW5PWTRNeXV6Zy9HQmF3cmk0OXptTTRoa2xGL2hub2JVbUlVc0h3UGd6Q3pUU2hpQ0JkRy8wRWhRSE5UVnVUWkNuTGFQVmV2NnVYUTdadjZNZVYrMnpmSVNvaGRyWVZ0M0lJS2xhckJLUU9qRjdqZHNlL0pKSC9reDkrbmpjZHpjdG9LRjFUZVNid0dVbHNpQUZBcjhtQ09icG9ZN3RBY29FQzZQNG5BNmh1VU1wMVJTWkF1MS9aVk12R2N5N2VucmQ5YVZPZW5sK1EvUVFoN3RwSHM2M2hZNVlkZnMzT2UvWmJNaVVtU2dXQzREL012Um1qaUhYNkQzMzRrcjZWSk1YclpTTEZnSzFpc2lzN2tEajQwN09rd2hPQml1UUh2Q3llTTVvVUxzYW5IYVlndWRGdkJ0M1FKN0trVnZUdGlTK0ZsMnM2NWRtMDAxd1JqUDVIbWR2TXVsM3BNYmcwQTUzVzdBT0RxSVk5b0xFZ3pXTEh2Y3NqSzZNekFBOGJvOHJXR2lYc1ZoUGdrRWJsMHdHSUdmZXUzOUErdEg1MjFpRHI1cDgyTXBnaEVicE8xU2JiYVBhZ0l1UHBIbk5LWGNuUC8rQXZkWlVaT1EyUTU5RkhhaDNFdTM3YnlRVkxjUTBLNEpHTnhmWTZzUXlQYzR4WDhiWFNVN05nQjVCOGRRMkJ6Q2wxOVgzY3dvUHRFQ0IxOCtRVTIzU1piM1FsOHcrc1d1SjI5bkhhd1RtQ3Mrb1RrWTM5Nlphbitla1JoZHExMzlheGd5ZlMzeVAyVk1kN292MFZabDI1MjZaZ3NORXE4WXY2ZmRhVGszeHZPRndxL0JIcVlFN0YvUm1oU25POTlHZmZjSm4xeG85d2VOK0R5eUJXenMzTXR6cHNpWG5taVJxWjBrMlJkeXRONi9KSU50NFBubjlLcnJIK09yVEtEQ0dUTmNaN0c1RGljMC9HWHp0T2MxRkN2MStNNjJKZ1pQd1FaOFR0UnhJalFPTWJXckJBczZZTVBmVi9LMFlRN1NaOVExc3owUTRyVTEzcGJPSWsvd1hTRVFLMnByRXR1S3ltcEUwYXpMbWNlbWltNk1xSFBRVVN4Vzg4WHJZYXUxTmRHT3U1NHpNb0IyczhBc1ZySGJHR3hnLy9oSTR3TTJYL3pFYmdXS2R1YnpXbklML3d3NHJYTEdMNy9Bc0FhWUZacGhvOGF0TU5ZRFpYRzJSbEVRUGxCTHV4eTFaVzh5WkhwYlhWeFEvU0JjeXBhL3MvK3NpaTBzRStWenFJbmlzazlTeldCeGMyZzRPajQzMEs4dHkrbnRHSnlXN3o3T3VMVHpGbEtDSlBkamRyR1k4ajBFRmtwNFc4Z1c3UzB2ZGpWYTd2YU9LNVpzbDUvbGx1WmJMVnBIc09wdnN1RnRkR0hnWXZDZEd2Y1puWmRUbkl5UTYzSTlUd0pOWUFpbi9aTzZPeGxkMFVOVU1SMGE0dWtWUnRxR0V3YWhxb2diemdRRDVLRHc0V3d4NEpLalZEd201REswLzFpdENUTTFodWY4VjR3R3JBVHFZRDdFNmdOKzJxa2dIbjZ3OW01TlAyREdIblZQSXpqTE9WOUdjd1ZheGM0c1h1ak1WaXlDWVhTV2wxL09tNjFPbHd6bU1UM0lZMkp0YnE0TWppcFlhUnM3Vzd5U0pjd1dlM2dTVDdkZHhxNlp6d0pCNTc4NzJEZWx6MktVN3FCb1c1Ujl3QVlROFpBSmpicnp2TDdMN3A3SE1qOXIvZTFZemM1dEJrTHdBamR3eEc1ZTRLVGRjZmZuREJSNzI3TnMxK2trZ0tsbEZ0U29INUxvZEhlWVBVNDBPZXI2WG83ZDRvR3FJNVFNNnpsVnhsWk9BRldRaGo5dnFnNmpBc0tNQVo1QkNLOGNxMVdqeDhyWkxmSXRwL0tpQU9PTWREemFuZnA2MGpUZ21BcVFvempVeXFiRktEY280NFRqcjdnZE5aUkFqc2FoOENHYU5SdjM3UFczMHZhY0t1QUV5Uks1UGU4ZUhndlBEWE8vNGJSNG5ua1dGMGlNYStOYnk0QjB6MTJ0ck5tMm56c2tKWmorQjNJNXU5NmhQOWlNZGlZdUE4R3JVME9BQVRycVdmQnI0Y25HK2ZjNlJYK2FmRmJsekFGd2hEbDY1VVNvOGZvc3phbGMxZW5IajUyY0Uvdm5SdkNDa0pyUFNhTGRDYjIydy9NR3dlZ2g3QmRXL3N5RkZZcVpKV3ZsMXJ3NnRON0RFU0FhcUtiOXBJK1R0dU80VGtsalBpTnRlWFVIRVJTN2tzNEc4TXI5eklYVVRPOVR5K0dFdDJLMytQSzRCWUJJZXZGdjBJUExlcGQ1NnRpc3BrQUZzeGwyRzNpVDRjVjB5cDFCeUtjMXJoZWJHQjlyMkVHK2lBWXdydDVuY1AzRmF0ZzZjc1RaZXEwajBGYk1taWRxdi9USmc1Zi9qWjArRVlwZEJ5YkMzcHUvNmNBRk5NaE9ybkFvTzk4WTlVRnA1Nit0KyszaHN4MzgzeTU1M01XLy9CVDdmdjFUcUo3M0RoV2M4WnRmWXJGci8vZEw1cDlYL3R2UDhQUU94NTdyT20yVCswYWRzUnRTYXRERnVqMW1BU2NkMituelg4dEoxckZnWHUvOHBqUE80SFpHbmJPajJqaHZlUDF6WFg3dktFUDlQZS8vN1BtdWYzM0FmenhmQkF5MVZSeW0rOEFUKzJ0YWJqdkl5bXJoUTF5emhXVDNoSGdIZSs4dkovYWw5MCtiMTlFV0RHaG9XSC9rZStVemdqd0Fkb0tSR25iOUhJanpyQytmU2pxTGFDTlE5SmtYWnFaRkIvc3VvOVZuTXZqNDRFZXh2dG5vZkxVa3NaME9pZ2VOeEZuRFJEeTJCMzY2ZktMNkZ1Ti9PTWh6Um9VejFWeWFOTmc3UU8vZVRwNWo5ZTFYckxhaW53cHBpRVVEbmo4TldqcjBmYzE0Q2N2M1hhdlZYNXYwOTF0SUNhN3pVREl3ZHd0T1FBZ1RrUWQvWVdxRFhadllrOWNSQm1VN1IvZ0k2YzBhbVd0NURFenY0VFN6d0NxaXN0WWJCdE4vTm9FTmNVWFZ2d1duckdFZnYzNDdGTlFSY0o0MDhhNXdkWTg2K3V4ZjhBWE9tRnZMVm5mdytKM3Q2ejZPdkhNMmNYVjJKWllFWUx1YUNuZU9IOWluRmlWZmxxUGFUSHhPZkxOMUlOakNid0lFUGJmMGZ2eDY1K0IybUNzc2tvWFJ4ZXBRNCsxR3c5S1JLL3Y3djl1aEl5ZmJ5czlaQldqZm9xTVpyQzJ3V1QyZlV5YUgxWFZPLy84emF1V2NKWlp4cGUwdDhiSVl4a2s1M1FtNU5pVG96VjRqTTdnQThlOVg4Ni95b1FBcU1xK0Rld0wwSlBzRXVBTmMrbkd3OThVS2VKbHQ3UUUzUDRPeHN6ZCtrNGVzeE40aG1DRjNZenJkb3NqOGNadnhXRnp1WEliang1RDBxenZHY3o4bmlLSHZyVGJtWkxQaWJNd1pNbXF6VGdkYW9uUUJSdW5vM2Y0NlhkSnVtbkRuQ2NWdFRxdCt6VkRKSGVNYWhqeUJkZmFtT0VqUUdDOEtnVm0rMjdiY2FscUNPVTVlRURmYThSUEd3aGNvK0tyMXpJYVlRcVRtQ0lPMnh1TkpLZklLbTVReEl1MWRIdGpscVZGRzBJeS9BL0tjTXhWQ2RneUVzUkdXWVkxaUF5dEs0OFFBNTQyYkM3WUxZTnRKNkltWXpkUFdLamltQzEwZGlyZXNLM0RlM2tuSXdXblptTmRZemF0QnhzRkJHQjE5WlFlM2pYek9Gc1dGaFhQZ21BSFdSekRrbE9wcS80NVRCVjdJU2djZTR6bFhPeHJ5Wi9TdXAydkFkeE9BRU40SWJ1RkMvT2NjRFFkY1BMMm1hdU5wSHBaUVlRWHFQblgrc05nejZXZTlEalRCdkw0T0VKbFEvV1dCOTdaSFBEbnUxUW1tQTlLUDZRQUNkdFlEYWNKcHJRZXBnRUpnOUFLSUpUc1haTThITjdsZ0pVOWU2RFk0eTVZK2JMVEhVRFJQV3pLR0hIakd4M0pwUWZaMndCaWtKanRFYm4zT3piYkg3QTdiNEpQUkZkVFc3cmxselpxSnc1RnN0RHZiOThEWlBCT3c1V2p5MzlOVjJEVTczTGVueGxodzRpR3NPem56RnZBeUVCM256TmdEYzN1Y1hDLy9NZ3Zid3FZeS92VFRycitaczV6Ly9xMS8vYzRabW1uYTBJSFl3K0Y0UzlTUXpubHNGakpWcURrZ3k1NDlKMzRqMS9FYzFWTnRRUnVucmdZaFRzbngwSmJjSUFqYjNyTFZGcDdmOGV6RGFjVDFnajBEOG5KMElNWXBiYkpMNVQwWmlQa2pkdUNrLys3ckczUUtDYXM5Tm9jcHNKbzVRSjZxYzJaYXNNRTZ6M040MFd0SU5XRzduajRkNjNwbVROczBkUXBJbXhYL0FHOGUrWWtYRnN5dzhUWTFmbHNOOWo5OHBtS2ZmQndEbXJXT3EwcTFTcThmRUplZUZ4S0FaUTZ2aFl5ZkI5dkJBWmI3MW9LVkFsOHpjT3RKVFhaNVBFNFp4b1JYNnlLQTBtMUQwVzB5ZDZiU2lTTFhYMlRZczI0TVVuaTRubU1oNDVsWWdwTjBydkNKdStWd3JZVjdtVmJvTzlvUjhaaXY0WVBxcUJjd045dDRuai8vNmEvL0RRYTNtZXNjOTJVbTZNaEx2STY0SEdGaDFmNHBTUEhmdmk1OTNicXFmQkQvYTBHRFZZdFdtc0FFZXJ5SzNTZ0E4d3E3M3NYSTJGcWVCd0J5SEE3L2RDWXpiTTA1ZG5Za3hOcUNuaU9ZK1JlZFA1S2hDSVc0TmRyc1dMSVlmOGsxazNOajJWL0RpY01tdkEvVzBJMktuS21yd1ZybjRyRmNGbEk0SWVXazFhLy9OcS91bVhiMllldTRVWWN6a0FvRzRsUVV5UFAwRUtVQldoU000OFB5SmZDK25VeGdCYmZSUUtlZ3JlbjhTNk9aZzgxM3FMZVZMdnM5YzZzdDA5SmFhRS9EbTlGOVF3WVhNS01EVENRUWNMSnVJSEtoaFBtdVRsVi9uQ051QjJIOW5ML0V6MGhMMGpuQXprSG9BLzU5ZzRiZ0ltd2hPM1huQXVwbzVuMC9SMTZCTlp3VU5rMEdkU25lNjd3TFJKQTEzQVpyc0VpR3J6M2lZSUFzYzRjYzgzT21ML3M5U21HM3BtY3VuQnBtMkxWSUJhdmxxWmlJUEk2U0hmSGNTS2crVHN5bXJWR0h1WWpud29BQmNZemRMTlNnVFV3OHJnL28rUzRMOFo3SHR1bmkzTnhzTyszbm9CZ2I3eXdaYm5mQlRCZWpwL0IrVEthbkFWVHJoTE4xRS9HYzNnT2d2SGZGY0g5R0hFQmlXRldMV0lZRDQ3ZERNTG4xNjdsTnNFN2VCRjJ6dkIrSGV0Y0tCbkcwZVA4OHhJN25OR2NUbjJkWVovZm55Q3VQd29IUy9zN0lhcmdvdytYaEhKeUVxN0gvKzNNTXR1ZXdObWUwNEM3UllLUm9mUlVid1NXZFUrUVl6SEp5TGE5aHlBekJCZTE0RWtPdWwzUEhXN1dYd25sdHJPOEh0a2FlbllrbGxQdHpHaU9nRVE3eDA3eFBMTzNRaG5lZHpqbFk1MFQ3YjRQbU94NmxnZi85YjM3VW9IWTk4WVRxSkpYaWFXZkJUbmRXMU1DNFUrNnNuSmJtZTlBZGd2NzhBTldOdEpuZ0c4bE1DVnpkbXdIdEhBRXp6bEdScTZEWW9ER25ERGY3MHhNd3FZS1FuRFg3SENMdHlOSzdmZFhUT3owNTljeDNRZnJyY0R5RGRyTEFMSUQ3RGxENUtydW14SnlJaVpUSHMxODhaUS9rN2FHNHcrSCt3U0ZMOFQ3Qmxwb2R2Z2VLVjU1UGR2QnJUcXF6VzB4bzRQUzY0Y1JGRDBldFFLSzZQQ1BTOFN4ZHN1eXI4azlBaFZVL3ozMGFBVHlmMDdwNGJFbnY3VHFKRjlDbGcyYVZpZWloNVpDOVIrb3ZCN0RCQW9kUTdIQW8vVlFhMjBKbDFROG0wSEdKU04xYkFLTnJuek1ub0dCQi9adU8vQm92YkJzRGFrMmRaNTNZbG92aVBBR2kxUDNCZGViVm9qT2V3NGJKYTVmcC9qcks0MFFLWE16ekxEM2RFZ2tKMnUzYVQvWXpkclBsWUdnd0ZKL0pmNTEzV3IrVCsyK1hkWUgzT2RySEJLNVJUTXUxcW9uaVlEdExZVjJBTnBESUkySi9CQXhrekZZYmN3ZGwyaUNZRWNoVHNXODk4ZkFvd0hsS1psNm5IamhRRTJWemlwNTl5UkNkZld1dHYyakY3ajZObk5PQmRaQ3Zqb1hzdjRraHk5RVA2VitWd29nL01CYnoyNm9IbW9KYVdjRURuVzE1bFgwWDV6eDdDb01UYjA2eDJZdXVCeVVWTTJ3MHhWTnVZckFGclZycDQ2a3RrQWZyR2YvMmlrZUhqZ1pkOXpsQ3B0V0Q2SXNUS29LWml2dmJ4VGtZY1hoZ0FZVFVIdXZYOGRlNWVBNFpodFVVK3R4V2lHREIzOHVXQkNkdzJXdDc2di96TDBiQzR0WlFmMlhwMzUvM3p2aVArRHBrT0dJYllybmxaZjEwSHEvTGcyWFFWQ2ZRVEg0WXhGcXNUNDIvbmhZYzFmcmZYZnJrcllLRDA2OGwrTFp0QVJpTU9GcmRoam1ZeDQ5NTJOQTU2UTYvRmZSWDVvdXR6RVB6RFZ4Q0FZUHRPaWJBTDMyQ2dTZURqWTZiRHg0UGdHTENjczdad0k3QWZEenBHTFY1MDFoMnVLZmJ3ZmJ2OGEzZ1c2aFdIRmM5N1dhbWRPWHlSVnhjelMzb2Y3Y1ZFY09od0NhMUM0N0trNUE5Wm1NYmdCcWVWWHNMdWt5d09ISVhMQ243aTZiY1BxY3RHdE1zUVVwMFRpbHFKNmQ1bzMybUl4OG5WNWFENWhIK2FJTnptUFA2cFNvRmFzSDB0Tk9mMDJEOWxxTjNuZEViNHpyVG1jMnN0VE9ZelVEZjQ0bW96aDdwSnkzem9tdmpOdDFqcVdKeGlxT2xNL3lZODFNcUsvVUFwUjJtV1crUHV3UkhGRHRjQUJrdGdmZGU1eUszUC9OcWVVaVk4TW5Rek5TZEMyU2UrSDR3QUx1ZVNQR011OGJrTWI3WkRCZU91bnFlRk16UU10MTRDbUcrQ3dkV0grY0N2bVUxc0E5NDJuNWJ6MzUxdk1BNlkxdzgwZXh4TGsvK2RWTThmODdWd3NCdFQ2NEJkRjFXVE51UTcvR0FyTE9UL2ZjNUhPWjh0QjYzUEpvbm9Kd0lUc0M3cjRvcmk1NTYrcDFScXg3TW1PRDlWckZBUUowWGpiZXlpTC84VFMwOS8vdUZCTWZtYVFwT2RUUUVacU9EU2Z1VEVUeERVQzlNakdiWmVRZkl5RzBGblFPbUhIYTBUenF0NU44NVB0WmRGYWVzMjBING4vcC82VUtkby9DZHZQM2ZLODNmWnZBV1JITGFCVDNsN1UzNHhWb2RURkE4M3ZKek9Qbjc0ZyszWi9ud285cEtlSWhkM2s2VUN3NTl5MGlRMjQzOFZLTXIwMk5Wc0g4blR2U2UwN1doQ2oxWURjb25ybmhxVFpEa1UzblRuWjU0c3lMb3dMelBJUU9qMlc2SkVFOG5LTnA4YjNHeUliYnlPRGtaaGdEb1NKY05MdGFQV1RtMmh2UGhrc2ZrZ2FFZmg4TUhKNWEvbmdyNjJnemRuTzJwTDNMNnFSV1lucldCdkVIWDM5Zmp3OFR4ai9OOCtSbW5PK0NJZGFaVzdLN29zTTl1L1E0Tyt3WjJPM3I4T2NWeWVBQjJhRHBNNERvaEZBQzUrR1U2MmdGZzViQWdnOVBSVThLRHh4RTUvTXpEVDZ5dGhPeDc4dDN5YURsSWhBS1M4UDAvMG9LY04xTmlnR1pEMTJFNnJjYUoycStORGluODh0SDJiaWZVOFBXY0tTdXRjZVhKUXVkUTdzdGNkUXdaMW0rZWR6aXVZdDNDUWp2UkRldWJLNUl4enhPVHczMldPVEFsb3g4azBldUlLRE9vMXJNU2tXWGRUSUZJMktsUkRKbitDaXcxVE02ZTN0RkRmSzN4N1JhRWJ1TUwyR0hrZFR2MHNUblo2ODlrMDduUElYTWUwcERGYUd4ckd5ZlZoS2tmMjlsbUVtalBTMHltZjh6ai84NDdBdXI5WnJLOEMxT0JlUzJlK1B4cmdiUW1wSjRobmtYRzZRR04xVm5pRmlaaDRxMW1mbXgxeEtmYmpIUTZXS1pudnp6YndWTzRqVGVlZkIydjNlOXdzdWFPbnJSb3pCU1Z3NXo1VEE3QWh2cjFnOWJ2RHlYRHRYbTJXSmFZcHRrU0Fvem1PdEhmRTZsTjRRR2x6cEZHTnBRQTh5d0xIUjYxM2gyaTNKNXFzNEg5V002cmxXQTg1dDNSeXhoMlFHTU1uajJtRHgzUVVCMmNXeXllbHVadExIZ3pjZUV1TzVEV0pDT3N0bDFud2l3MjIvTy95K2U1Z3ZhTlRKZGx0R001a2NQbjlJMDZDT0oyQm9PRFloVE5aRnZyT1FzbExZOXlhaWY1eDI0YVhiSHhxV25lVHZyT1pKd3pSdFhZN1duQVZreGdFdUgxL3RqZGpWTGpBNnRERkpNQmJxOS9oMk1oRHBIU1hFSGVsdG45bU01Uld4N0F4TDdaRzh1NzJyaWhxelljdWJIMmw5VWVyem56Y3pTOURHZW5NN2FwV2ZOd0NsZFc1elhmOTNSOFk1RndtZG5HRzZPQW52c1R6N0hmRitUTmdqVkxTNXI4SXVPNTd6dzhkQ0pqNlJaYllDVkZFaDA2K1EwM2pYM1RJWGd2K1pCZHA5YXd2eDdlaWcyd0VNYUtKcnc2VDJYMGRGcDMrbVhyWVQ4dUNNSEN1WmZsb1IzWXg5SU5aVDN1Z01uVHkwMVlaOTc5NE1udTVaRVlDSUJ6Um9Ta0ZtK0pvTGZGM0dkbTFobG83UkN3dXM4QjMxYTdsUFpkQVMvWjdNMEtUdHdkOXpuc09WblR0RHpCUWpWOXpDdWJFblM0UFBhZWIyZUh3bGI3MTdPbkZmZStMOXVqSFQrRjNtVlAzK3JPQnZEY1AvdFh3bk1PVlBMUUdBOXhxaWJnMkYvSlp2ZkllZyt0d2F6cGY2NXpMQ3JJNWZyL0FwWVRxQThhSmVyWUFBQUJnMmxEUTFCSlEwTWdjSEp2Wm1sc1pRQUFlSng5a1QxSXcwQWN4VjlUcGFJVkJUdUlPR1NvVGhaRVJSeTFDa1dvRUdxRlZoMU1MdjJDSmcxSmlvdWo0RnB3OEdPeDZ1RGlyS3VEcXlBSWZvQTRPVG9wdWtpSi8wc0tMV0k4T083SHUzdVB1M2VBVUM4enplb1lCelRkTmxPSnVKakpyb3FoVjRRZ29CODlDTWpNTXVZa0tRbmY4WFdQQUYvdllqekwvOXlmbzFmTldRd0lpTVN6ekRCdDRnM2k2VTNiNEx4UEhHRkZXU1UrSng0ejZZTEVqMXhYUEg3alhIQlo0SmtSTTUyYUo0NFFpNFUyVnRxWUZVMk5lSW80cW1vNjVRc1pqMVhPVzV5MWNwVTE3OGxmR003cEs4dGNwem1NQkJheEJBa2lGRlJSUWhrMllyVHFwRmhJMFg3Y3h6L2sraVZ5S2VRcWdaRmpBUlZva0YwLytCLzg3dGJLVDA1NFNlRTQwUG5pT0I4alFHZ1hhTlFjNS92WWNSb25RUEFadU5KYi9rb2RtUGtrdmRiU29rZEEzelp3Y2QzU2xEM2djZ2NZZkRKa1UzYWxJRTBobndmZXoraWJzc0RBTGRDOTV2WFczTWZwQTVDbXJwSTN3TUVoTUZxZzdIV2ZkM2UxOS9idm1XWi9QOGE3Y21MdzBYeHZBQUFBQm1KTFIwUUEvd0QvQVArZ3ZhZVRBQUFBQ1hCSVdYTUFBQXNUQUFBTEV3RUFtcHdZQUFBQUIzUkpUVVVINUFjQ0VDUVNSMS9VTVFBQUlBQkpSRUZVZU5yc1hXZDRGRlhidnMvTTFuVFNFd0pKcUVLb1NxK0NTRkVzS0tKK29vQ2dpSXJTdXdndnZTZ2RRU2tXUkgxRlJJb0ZDL0tDZ0NDQ1FpQzBrSkMydThsdXl2YWRjcjRmT3pOc0dvWVNpdXh6WFhOdHNqdTdNM1Btbkh1ZWVqK0VVb3E3VmY3ODgwK01HREVDZi8vOU44TER3OUd6WjAvRXhzYmlxNisrd3Rtelp3RUFHemR1eE96WnM1R2JtNHRISDMwVUxNc2lKQ1FFYTlhc0FRRHMzYnNYSTBlT3hPblRwNUdZbUloR2pScmh0OTkrdzlpeFkyR3oyYkI1ODJZVUZoYmkwVWNmaGR2dGhzUGh3TkdqUjJFeW1RQUFvYUdoU0U1T2hpaUs0SGtlZ2lBb3I0SWdRQlJGMzcrSktJcnczU2lsOGl1aGxFTGVBSkN5cndCQUtTWFM1U3V2WmQ0ajU4NmRpNG1JaUlqVGFyVXhMTXZHTUF3VHhUQk1CQ0VrSEVBTkFDR0VrQkFBZ1FBQ0FPZ0JhS1NOQmNCSXZ5Y0NFQUI0cE0wSndBSEFUaWt0QVZCQ0tTMmlsRm9vcFdaQkVBbzRqak82WEM1VFhsNWVYdlBtelUzUzcxQ2ZyZHovaEpDeW44bnZBUUFsaEpSNzlka293ekFnaElCaG1MSWJaVmtXTE11Q1lSaklmNnRVS3VWVjN0UnFOZFJxTlRRYURUUWFEYlJhTGJSYUxmUjZQZlI2UFFJREF4RVVGSVRnNEdDRWhZV2hSbzBhaUl5TVJIUjBOT0xqNDNINDhHSFVyWDhQL0hKamhOek53SFk5NHZGNGtKdWJDNFBCZ0lLQ0FsZ3NGaFFWRmFHa3BBUTJtdzEydXgwT2h3TXVsd3N1bHd0dXR4c2Vqd2NlandjY3g0SGpPUEE4WDJxVEFjeDNFMFdSeUFCWEJzeEtBWm44ZnhrQUt3Vll2djl2MjdhdGdWNnZyOGV5YkYyR1llb0FTQ0tFSkFKSUJLQzdUWWJaQmVBU3BUU1RVcG9oaXVKRmp1UFNyVmJyaFFFREJweVh3YzBIMkNvRHZISUFTQWloMG5oUkVISjVZQWdwQlh5K0lPY0xlbVhCemhmMGZNRk9wOWREcDlVaU5EUVVNVEV4Q3NDRmhJUWdMQ3dNNGVIaGlJeU1SRVpHQnRxMjd3UzFXdTFmWEg1Z3UzWVpPWElrVnE1Y2VWdWRVM1IwdEZmTnFRRE1aSzFNQmpiNTNzbWFtaTk0K2Y2L2IvL0I2SmpZMktacXRib3B3ekFwaEpBVUFJMEFhTy93VytnR2NGb1V4Vk9DSUp4eXVaeXA2UmZTVXgvcDI2ZEEwaFI5UVUwc0E0Q2dsQ3IvWDBHam80Umh3QkFHREVQQU1ES1lNWlJoMkFwQmpWV3hVS25VVUt0VVVLblYwS2pWRWhnU3FEVWExRXBJUUVCQUFBSURBeEVjSEl5UWtCRFVxRkVET1RrNWFOcXNKUVJCUUV4TWpCK1ovTUIyODhSb05DSTNOeGNta3drRkJRVW9MQ3hFY1hFeHJGWXJiRFliSEE0SG5FNm5vcVdWMWREK1FVc2pzdGxaQnRCOE5iTlMybGhaVUh1NDd5UHM0bmVXdE5icGRLMFlocm1QRUhJdmdBWjMyVzA2SjRyaW43azVXY2MwR3ZXZlk4ZU0rWFB2M3IyaUQ5aUpsUUNlWk1ZU1NnZ29RRUJJWlNESGdHRVpzQXdEaG1GcEtZQlRzVkN4bDdVMmxXeWlxdFVRUlJIQndVR0lpSWlBWHE5SFFFQ0FZcDRhalViVWIzQVBha2dhWEd4TUxDS2pvdnlMemc5czFTZkZ4Y1hJeWNtQndXQkFmbjUraFdhbjArbUUwK21FMisxV3pFNk80K0R4ZU1EemZDbFE4L0dqRVY4L1dnV2FHYW5BWDZab1ptUEhqbVVmZSt6eFR1R1JVZTJEZ29JN0VFTGFBd2p5M3pIQWJyZkJXbEtNVnZmZGE2T1VIdUo1L3BEVmFqMzg2cXV2SHZyaWl5OTRRa2hab0JNck1tM0xhSEcwbEYrT1VjQU5MTXVBWWIwZ3AySlpzTDdnSmdFY3d6QmdpRmN6TCt0L001dk5xRlU3Q2FHaG9WNFROU0lDMGRIUmlJdUxRM0J3aVArRytvSHR4Z25IY2NqS3lpcWxwUlVWRlNsYVdsWDhhR1cxTkZrNzh3ME95SnFaYkdiNmFHUyt2akpDS1dVc0ZrdmpvS0NnKzFtVzdRS1Fya2VPSEFtTlQ2anR2MWtWU0c3T0piUnAzYnJjYytyUTc0ZjNSVVhIN2k4b3lOL2Z2bTNyTkFuVUt0THF4RXBBN3JJbXh6Q1VJVDRhSE11Q2xiVzRjdHFiQ3FJZ0lqSXlRZ2t3NkhRNjZIUTZGQlVWSVM2dUpnS0RBaEVjSElMUTBGQkZlNHVKaVVWd1VCQ3lzekpodDlzcXZkN0F3Q0FrSmRkRlNHam9MUnZ6a3VKaVpGeThjTXZQVTNXM1RYWktLUXdHQTB3bUUzaEJnTTFxclhBL2k4VUNrOGxVU2p1ejIrMktkdVlMWlA4QVloVkdOV1dIdjJUL2dQajR4OHFDbWNQaDZLYlJhSG93RE5NRFFGUGY4M1E2SFg0RXEwU2NqZ3JISnRUdGN2YlZhclY5YTlaTXdLWHN2Sk04ei85aXM5bCtiWnJTYUQ4aEVIeUJqbEphVHFPVC9ITUFJWlNJSWtSQ3dJZ01CTUpRaGhIQXNneGhHQllzejFLZTVhRlNTUm9jcDRJZzhMRFp0TkJvTklwMjczSzVVRkpTQXExT0Q2ZkxxL2s3SE42NVpyTmFVVkpTZ2lKTEFRSURBeEVXRm5aRkxUWGo0Z1UwYTNIdkxSdnppK25uNFhEWS8xR2JydTd6Vk4xdG9IYnMyREdBTUFnTUNrWmdZQkJpWWt2djQzQTRrSk9URGJlSEI4ZUw0QVFSZ2tnaFVpaXZGS1QwUmxGbW82QVVFQ21GS0ZJaUtwRkw3MWJXeEtTVUVpcVptY1Ficm1PY1RtY2ZqVWJUaHhEU0IwRE5hNzNtSDNmL2dJMGIxc05rTWlJK1BoNGZiL29NNTg2ZHhkQWhnNVI5SmsyWmhvY2Vldmh1eGI4bUtwV3FTVmhZMkJ0Wk9YazVnaUQ4WUxmYmYwcHAxT0FueVZ3VktnVTVTaWtGb2Q0QUt3VWhJa1NSUUJCOEFJNWx3Zk1zV0JWUFZid0tvaWpBNFhDQTR6aW8xV3JGWFdHMzI2RXBMbGFBVG5adE9CeDIyT3cyRkppTUNBME5oYzFtUTJSa0pIUTZYYVdnY1N2bG4wRHRacDNuWFFWc0JvTUJJQXlpWStJcU5sMXljNUdYbXd0VHZnbG1zeGxGaFlXS0g4M2hzTU1wbTUxdU56eHVOenljQjV4SDB0WjRIeDhhTDBBUWVNbmNGQ0dLc3NsSkNhVWlLQVVCS0xsc2Nuci8vKzIzQTkwVEUycy9HaDhmMy9kNndNejNlaGJNbjR1WnMrYWdkZXMyS0NrcEFRRFVxMWNmUC83OEt3RGdwYUdEcTIyOE42eGZoOGNlNzRlSWlJaHk1N1ZzeVRzNGNlSnZhRFFhOUhpd0YwYTgraHBZbGdVQVpHZG5ZOFd5SlRoeDhnUzBXaTE2OXV5Rmw0ZVBVRDZ2RHBHT1dmUEV5Uk12YXJYYUY2ZE1uWll6ZE5qTDM5bnQ5dSthTldtMFh3STRvWXpaV2xhTGs0SVBJa1NSZ1NnU3lnZ0NCSVlGS3pCRVlIbUlva2lkVG1jNVlITTRIRkNyTlY1WGhqUy8zRzQzbkU0WEhBNG5DaTBGRXRBNVlMZmJFUkVSZ2NqSVNMOWE3Z2Myd0dUS1IyQlFjTG4zYlRZYnNyT3p2RGxwK2Ztd0ZGcFFYQ1Q3MFM1SE85MHVGOXdlTnp4dWp4ZlVPQTQ4eDRGVEFJMEhMd2NFRkZBVEpUQ1RFMmk5SUNhYm1hZE9uMjBTRUJqWWoyR1l4KzEyVytQOC9IekV4OGZma09zOWszWWFrWkdSNk5peEV3QW9DNEVRQXExV3EveGRYZkxoeHZYbzNLVkxPV0NiOGZZME5HblNGUCtaUFJmRlJVVVlNL3BOMUV5b2lYNzlub1FvaXBnMFlSeTZkWDhBcytiTWc4bG94TGl4b3hBVkZZMytUdzJvbHZPczVKZzFvNktpaC9WL2FzQ3dyQnpENmM0ZDIrMzR6K3k1Mzc0dzhQOU9BUkFrN1UwZ3hLdVlYOWJrS0tXVVVFSUVTaWtob2loU1FrUUlBa05aMXV1T2NMbGNVS2xVVkE0cXFkVnF1Rnd1cU5SMnlhWGg5ZE82UFI3SlhIV2h1S2hRMGVaY0xoY2NEZ2NjRGdlaW9xS2cxK3V2NmJxLzJ2SWwzbHU5RWovOXNoY0E4TXJMdzJDeFdFcTVZeVpObm9JZUQvWUVBSncrZlFxN2R1NUE4K1l0OEdEUFh0VytYaGZNbjR0dmQrMVU1dWpySTk5RS82Y0dJRDA5SFV2ZVhZeVM0aUlFaDRSaTRxVEpxRlhMNjJjK2VPQTNyRm16K3U0Q05sN2dFUmdZVkVaN3lFRnVUaTZNUmlQTVptOEtoMWRMczE3MnA4blJUby9rVS9QSS9qUXZxQWs4RDU3M0RRZ0l2bWthcEhSQUFNeEhIMytpNjl5bDYxTXN5L1luaFBUeWRhcmFTb3F1K3pxUEh6K0dHZE9uS1pyQTQ0OTZ6Y3grVC9USG9NRkRxdlFiaHc0ZXdNb1Z5MkV3NUNFeE1RbGp4bzVIU3BNbXl1ZDdmOTJERFJ2V0lkOWtRa3hzTElZUEg0RjI3VHNBQUVhKy9pcXlMbVVDQUVhLytRWlVLaFlKQ2JXd2N2VWFpS0tJcmwyNzRlRytmYUhWYWhFZEU0TldyVnZqN0prenltS3FFUjZPNXdZT2hFYWpRVUt0V3VqWXFUUFMwazVYMjd5b3dqRWJFVUlheGNiR1RyaVVuYmZiNlhSc256SDlyUjJmZmJiWkNZQ1hORGNaN0JTUWszMXhoRkJRNmpWVEthWFU3WGFENTNraUJSVW96L053dTkxZ1dSVjROUStPdit5ejVhUmdsTFdrUkFsTWVUeWVVZ0FYR1JsWm9mWW1paUlZaGluM053RGs1ZVhoMjEwN1MrMi81djExeXQ5dWx3dERCcitBanAwNkF3QldyMXFKOVBRTGNOanRxRmV2L3RYWitrMmFLSW5zVnpySHN2OWJ6R2E4dCtZRE5FNUpLZldkV2Y5NUc2TkdqVUh6RmkyeGErY09MRisyRklzV3Z3dVh5NG1GQytaaDVlcTFZTzRtWVBNTkZEaWRUcHc1Y3dabno1N0Z4WXZweU03T1FsNWVIdkx6VFFyQUZSY1h3eXFab2phN0hRNjdBMDZIMHlkWHpRMjMyMDNjYmcveEJoRTg0SG1PU0RscFJCUkZSaFJGaGxMS1VrcloxTk5ubTJkY3lwN2RyZnNESjFRcTFRZStvT2FyUFY2dnRHalJFdHUyNzhLYm84YWdaczBFYk51K0M5dTI3Nm95cUYwNGZ4NXZUWnVDRWErOWpsM2Y3Y1pUQTU3R21ORnZ3R1EwQWdBeU16TXdlOVpNVEp3NEJUdS8vUUdQUFBJWXBrNmRESWZrckYreGNqVzJiZDhGQUZpeWJEbTJiZCtGbGF1OUpXZ013K0M1Z2M4akxLeUcxOWw4TVIwSGZ2c05iZHUyVTdUS0ZTdFhRNmZUS3hQOStMRmphTmFzZWJYTmk2czVKaUdrWjBCQTRNb0ZpOTdaWDc5Qmd5bHIzMS9YakZLcWhUZmhXUU5BRFZBVnBWUWwzM2Q1SGtoemduQWNSenlTTnVaeXVZakw1U0l5V0RtZFRqZ2RUampzRHRqc2R0aHNObGlsd0pYTlprTnhjVEVLQ3d0aE5wdGhNcG1RbDVlSHJLd3NYTHAwQ1c2M1d6blBJMGNPWStya2llQTREaGFMQlM4TkhWSktHMXUwY0Q1R3Z2Rm1wV1B5M1hmZm9sUG5Mb28yT095bGw3SDRuU1dvVTdmdVZZOXZTRWdJTkJwTmhaK3RYL2MrTm4rNkNRQnc0c1RmZVBPTjE4czljTXI2eVI5OTdIRTBiOUVTQUhEUFBZMWdsZ0F6THk4UFlXRTFVTE5temJzdkt1bzFTWTNJenNxV25pTDVLQ3kwNE1LRkM5ajl3L2ZvMXUwQnVGeE9PRjJ1eTZhbnBLVmxabVlnckVZTkVFSWc4RHpoZWErRzV2RzRJUWdDWVZVcThCeEhaSkhOemQ2OSsvUlorOEg2Wi9mOWIrOVRIMjdjQUk3alVMZGVQVXlhUEVWWlRMZVQ3Tnk1SFYzdjc2YVlzTDM3UElUdnZ0dUYzYnQvd01EblgwQkVSQ1EyYlB3WXRXcDcxZjlISG4wTVM1ZThnNnlzUzJqWXNHcjFqcWtuVDJMMHFKRnd1Vnk0djF0M2RPbDZmNFg3clhsdkZmUUJBWGpvNGI0MzdmcXJja3hDU0sySThJaFJRY0hCb3pLemNyZVdsQlIvMVRTbDBTOEE0YVhJcWlCcGJZS3ZCZ2VBOGp4UEdZYVI4eGVwbE5kSUdJWUJMNmdvTC9nbWNYdjl0MDZIbzFRK3BLelJlUlJ6MVd1bVJrbEp2YTFhdGNhdmUzN0J6Qm5Ua1pPVGpTZWZmQXJoRWtocy8yWWI2dGF0aDBhTlV5bzF6YmQrOVNVV0xuNVhlYTh5WUxwZWVmYi9CbUwwcURkZ05oZGczLy8raDFsejVpcWZtUzFtVEo4MkJXYUxHVzNhdE1Yb01lT2cxV3JScjkrVHlqNWZmUDRaZXZidURRQ0lqWTJEeFdKR2VucjYzYVd4aWFLSTlQUUxPSFBtaktTbFhVSmVYaDVNSmhQT3BLV0I1M2xjeXJxa1ZCTEllV3BPaHhOT2x4T1VVbmpjSHVKeHUwdHBhRkxhQnFHaXlCQkNHRW9wMCsrSkoxVVhMbDRhUEc3OGhCKzdkTDEvcTlsc2ZtckY4bVY0ZCtreWZQTHBaMUNyMU5qNjFWZTNiWkFsTHE2MG42OTI3U1FZREhrQWdLQ2dJQnc5K2dlR3Z6d1V6ejM3TkFhL01GQVozNnBLU3BNbTJQM1RIbnp4NVZiazVlVmh3L3AxNWZiNStLTVA4ZnZ2aHpCMzdueW9WRGZuR1Z6VlkxSktZU20wSUNnd0NBekRQQkVXVnVQVFM5bDVXOUxPbm4rMmVmTVdla3FwRHBlSkFYdzFPSVpTeWdpQ3dQQThUM2llSnh6SEVaN241Y0FCY2J2Y3hPVnllVk0vSEU0bFYxSU9IdkE4cjFRc2hJYUdRcS9YdzJReUlUYzNGMWxaV1VoUHZ3QktLWVlQSDRFL2ovNEJyVWFyZ0xUUmFNQ083ZDlnMkxDWEtyMjIzL2J2UTFKeUhjVEd4bDNYV1BidTNSdjkrL2RIZkh3OE9uVG9nUDc5KzZOeDQ4YWw5Z2tLQ3NJcnI3eUtMLy83QmJwMTcxN3F3VGgrL0VTODgrNVNiUHIwYzVqTlpueTlkVXVwNzI3NThyOG9NQmZncWFlZUJnRG85WHBNbmpJTjgrZk52bnMwdHFLaUlwdy9meDZzU2d0VHZoRVdzemMveldyMW1wcFpXWmNRR3hlSDNOd2N4TVRFS0U5Q1ExNGVPSTRESVFTVVVzSnhIZ0FFSE1jUlFlQ0IwaldhWlBOblh3UXZXamgvOEpLbHl3Y1JRcG9lUEhnQTgrWXZCQlZGdkRGcXRHS0NOYnpuSHVUbDVkNldZeFVURXdORFhsNnA5M0p5c3RHeXBUZnZhTi8vOXVMVFRaOWd5ZExsU0toVkN3RFFwVlA3aXJTYWNtRG5kcm53M1hmZm9rK2ZoNkRWNlJBWEY0Y2VQUjdFL3YzN3lrM2FIM2YvZ0dVclZ0MjBoTk9yT2VhUkk0ZWhVYXZSb0dGRDMrdnRHQmdZMkhIN3ptOWZkcnZkbjMzOTlWZi9uVEJ1YkFraFJKQjhjYklXcC9qaUJFR2dsRklxaWlJNGpvTWdpSlJsUlFpaVFBUkJvSUpLZ0NCNFFjODNKOUp1dDBPajBVQ24weW5sV2JMLzdmeTU4eWpJTDhDYU5hc3c4UGxCT0hzbURRdm16OFdreVZPeGM4Y09XRzFXdkRMOEpZQjR5UnlHREhvZWE5WitBSzJVUXZMRjU1L2g1VmRHWFBkNGZ2Lzk5d0NBRGgwNklDTWpBN201NWVmN2hRc1hNSC9lSFB4bjFoeHMzTEFPdFJPVDhQRERmU0VJQWxxMHZGY3hoWHYyN0lYRHYvK3VmTy9YUGIvZ3h4OS93THRMbHBlS2xyZHIzd0h0Mm5lNE96UzI3T3hzbkR4NUVsbFpXY2pLdW9TODNEeVlUSmVEQlVhREFZSWdJaWd3Q0VXRmhZb2ZyU0EvSDRJZ0lDUWtsR2kxT2tJcGhTQUloQmQ0SWdnOFlTUUJ3RHo1WlAvd2k1blpZenQxN25Jb0pqWjJjZHJwVTAyenNpNEJBSktTa2hFVkhZM09uYnNvaTN2SGptL1FvOGVEdHk2UUlwazZBRUFseWlRWmhCN3Urd2oyN3QyRHc0ZC9COC96K09XWG41RjY4Z1I2OXZLNkJNMW1Nd2dCR0paRmNYR1I0aU9oWXVrcWxyajRlQnc2ZUJBY3h5RmZvbW5TYUxYNDVPT1A4UG5ubThGeEhJcUtDckZ2My8vUXJGbXp5NmJ3anUzWTl2VldMRjIrVWpHZnF0MzhydUl4M1c0M2ZqOTBFUFBuemNId1YxNUZZR0JnUllEZVJLZlR6WG4yMmVlK1AzOGhZOFRxOTlaR1NScWNsbEtxb1pTcWZUVTR5US9MZU1rUGVNTHhIUEVHRGR6RTdYWVRsOHNOSDU5Y0tlM05icmZES2lYeHlyNjNyS3hMT0hUb0FPclVxWXR1M1IvQXRPa3pvTkZvWWJGWU1IVFlTL2o4aXkzWStORW5XTE4ySFRRYURUWis5SWtDYXFkUG5ZTGI0NmxXbjZhdkhQM2pDQ1pObm9yN3UzWEh1MHRYNEZUcVNWQktVVnhjakJjRy9wK1NvblQ4K0RFa0pTY3JmMy8wNFViTVg3Q293dkVIL3VYcEhwUlNuRDE3RnBtWm1RckZFS3RTbzZTNEJGYWJGUTY3SFE2bkU5azUyUWdJMEV2T1Y0TGk0bUlRaHNEbGNoTzFXZ1VQNTRFb0NnQkFmT2lCR0FEa2h4OS9DYmwwS1hQWWdmMzdoakVNVXhjQU9uYnNqSU1IRHlJd01CQ2RPM2N0Wnc3UG1UTUxEenpRbzFJZlIzVkwyUVRkQmZQbllzSDh1WGl3WnkrOE5YMEc2dGR2Z0xkbnpzSjdxMWZDa0plSDJvbUpXTGo0WGNSSTJjeDlIbm9ZSjArZXdOQWhMNEJsVlhqczhYNm9YNzhCTElXV1VzY1pNMlljRmk5YTZIMFMxNjZOZFJzK2drYWp3WUtGaTdGeXhUSjg4ZmxuVUt2VjZIcC9Od3dhL0NJQVFCQUVMRnJvTlFPZmZicC9hVE54MCtick5vOHFrcW9lYytScnI0SVFJREV4Q1dQSFRWQjhrRmVRdWxxZGJrYmZSeDU5b2NlRFBULytadHZYbjQwYk83cFkxdDRrLzV1aXhYbFRRNGhJS0FYMVZxaFFRUlFoaWdMaGVSNHN5MUs1Y3FVaWlpdjVZV1V3NUtGR2pYQTBhTkFRWjgrZWdjUGh3Sml4NDZxVTJ2UDVaNS9pcVJ1Y1ZuUGd3SUZLUHh2dzlEUEszeEVSRVJnL1lSSUFJRHc4SEM4T0c0YlhYeDBPU29Ia09uWFE3d212YjIzK3ZEbHd1OTBZNVJOb21QclcyMmpRb0NFS0N5MTRlZGpRZjIrdHFOVnF4Zm56NTNIcDBtVS8ybDkvL1FXR1ZjRnVzOEh1azV1V2xuYjZzdlpDS2RScU5kSHBkTEJhclRMdkZwR0lJQW5ETUlSU3lpUWxKYkY3OXU1L21XWFpsMy82NmNmRysvKzNGelArTXhzQWtKNmVqbmx6WnlNb01CQ0RYeHlLNXMxYktPZTFiT203NERnTzQ4WlByRGdpZVM0TlhidDJyZkoxN3QyNzEwOVFXSWxVTnBhM2NNeE9wNTQ0L3RIeTVjczNmL2ZkZHg0SjRCUVRGZDdhVlBtVnlzWDJETU5RUVJBVS9qZVZTa1ZabG9WYXJZWktwVkxJTFdYVE5DbTVqc1QzSnBGWnhzU2daczBFSkNjbmxVdDN1dEZ5OExmL1ZYbmY5aDI3Vk50NS9DczFOcVBSaUFzWExpQXJ5NXZDNFVzRXliQXFyelBXNllUTDdZTGRiZ2ZQODRpT2lRSFBjWEM2WE1SaHQ0TmhXUkJDaUNBSWNpNmFFdVU4ZmViOE15OE0vTDhSUnFPeGJYeDhQUExLK0E3cTFLa0R1ODJHZ29KOE5HMTYyY1RhL09rbW1NMW16Smc1NjRaZHExNGY0RWV3cXh5Yld6aG1qV3JVQ0orL2MrZk9KMHBLU2o0TUN3dmJRUWpoZkFGTzB1S0lWNHNEcFZTZ29sU3JKL25qSUlvaVVhbFUxSmNWUnRiYUFJS2lvaUx3SEs4ays3cmRicmhkTHJoY1RpUWxKbFVySFZKQVFHQ1Z5cW9DQWdLcmRhRC9kY0NXa1pHQjlQUjBaR2RuSzBtQmN0S3QxV29GdzdCS0tvZkg0NEhGYklGS3BZTGI1U1k4ejROS0JlcmV0QTJHaUNKUEJFRWdBSmcyYmRwMmYyZkowbGNEQXdQN1Rwb3lGVk1uVDBTTjhIQ29WQ3dDeWl5V2pwMDZvNlM0U0VrMk5Cb05XUFBlS2lRazFNS1FRZDRvSXNPdzJQalJKOWQxdlN6THdtNjNWZnVUK0U0VHU4MWFhUWtXYzR2R3pHNnpnbFdwUUFocEV4b2Eya1lRaEw0bWsrbkR1TGk0QTRRUWpsTEtFMEprZ0JOQklJSkNoQ2dxQkptaTZNMGFrYW10UkZGVXpGTUEwT24wc0phVWVGTkNlRGtsaEx0Y3llQnl3K2x5b1pZVTlMblJrbHluM2o4V3dnY0VCQ0s1VHIxcUhldC9qU2xLS2NXWk0yZVFucDZPbkp3Y0dJMUdoV1pJcnZmTXlja0JZUmd2SzBlcHNpaWVlTms0ZUYvNklGbExZMDZrcGpVSURnNStqUkR5eXEweW55b0g4a3lZTFJiRXhNYjcwY3hYYXpma0lqSXlBb20xeTFNNlpXUm13bXkrK1dObU5PUWlJaUlDU1ltbHo0bmp1SFdwcWFrZnQyelo4Z0lBRGdBdlJWSVZINXpNSCtmTENlZERjRW5MbXFVeUpaSWNNWlZUUThMRHd4RVZGWVc0dURqVXJsMjdXa3ZxYnFYOEt6UTJ1WW9nSXlOREFUV3oyWXppNG1LZkluWUgzRzQzUkVwOXk2SUl6L1BnQlI2aVFpdEVDUVVsb0pUMGYyb0F1M0RST3lOWmxuMERRTUxOdUphZzRPQ3Iyajh4c1RieTgwMHdHbklSRkJ4eTEydHVkcHNWTnBzVkFzOVhDR29Ba0pTWUNKUHA1bzJaZkU2aUlKUUROUUJRcTlYRFdyUm8wY2R1dDYvdDNMbnp4OGVPSGZQSUFBZHZrTXEzQUYvaGhwTkJqbUVZSXZuZnFFeFI3dHRjUmdZNFg5WmV1ZWRDdzRZTnI3blc5SGFXTzE1akt5d3N4Tm16WjVVOEdWOE90Ykpra0lXRmhmQldDL0RnS3RmU0dBRGszSVdNbmhxTlpoU0FCMjdtb25TN25Xald0T2xWYTZzWE16SmhzWmdoOENKY3JydVRvMDJ2RHdERHNvaU1pRVJpWXExLzFFWXlNeThodjZBQUFpOVUyNWpwOVFGZ1Zhb0tOYldLUkJURlBTYVRhVjFjWE53K1FvaHZnSUdYTkRkQkFqUlJEaTZ3TEV0OXdJeXExZXBTSkphK1BSYmtCakp5ZDZ5a3BDUTBhTkFBTldyVThBUGJiV051R0kybDBqbGsydTZLR0c3ZGJqZnNkanRjTGhjUktZVWdFVUVxV2hxbEJLRE1rYVBIWXFPaW9rY1JRa2JmN09zeEdmTVFGeHVEdUxnNCtPWHVGby9IcytxWFgzN1oyS2RQSDRNVVlKQTFPTUVuY2lxREc1VzFOVmxUVTZ2VlZHNEJXQmJjZkUzUytQaDRKQ1ltb2tHREJ2K3FKakozckNtYWxaV0ZjK2ZPSVRNelUwbm44RzJ1SXRPNitETGRDb0pBUEI2dUl2WU5CZ0J6NGVLbC9pcVZhalNBKzI3bXRUanNOdGlzSlFDb0g5VDhBZ0RRYURTdjllclZxNFBaYkY0YkVSSHhIU0dFbGNDTjg4bC9JMTRsVHhSOUd2NVFINnA1V2xtdVcyVjFwOVVWVlBBRFd4WGt3b1VMT0gvK3ZBSnFjanBIMmVZcVplaTdDY2R4QUtoODArVXVUOHkyYmR0cWhZU0VqTW04ZVA3Vm0zMHR3Y0hCVUtsVWlJK1BRMnhzckg5RisrV3lPVVZJeS9EdzhEVWVqK2VEMzM3N2JVTzNidDJ5Q1NHTWJKcFNTbmxjWmw0V3ZWNEo2aHNwSmI1UjA3THBJVEo5dlMrMXZjZmpRZDFyWVBEd0E5dDFTbHBhbXBKNEszZU5LaXpGZE9zb0MyckVweGVCNGt1RFZEbmdkcnVmVUt2Vll3RzA4aThsdjl5T29sYXJYK3JhdGV0OStmbjVhNktpb25aTHZqZEdBalZaZTVOVFFjcHFiK1hTUXNwV0x2aDJUcE1CN3A1Nzd1eWtiNUtSa1hGSG5LaGNIaVVIQ1h6OWFXVzFOSjlPVWNSYldPenREQ1duY0ZCS3ljOC8veHlXbUpnNGptR1lDZjZsNDVjN3lQZTJiT3ZXcmVzblRacFVDTURqaytBcnlQNDN1V3BCU2dueDliMVIzd29GdWZWZlVGQlFPYitiSEZTNFU5TkJWSW1KaWJmOVNYSWNoOVRVVk1WM1ZsR0hxQXE2UXZsMlUvY0ZOY1p1dDNmUmFyWGpDU0U5L1V2RkwzZVl2RGwrL1BpVVo1NTVabTFTVXRJUkFDd0FPWHBLVUpySkY2SW9naEFpbDJRUm1mK3RySWJtVzJUdmNEaGd0VnBSVkZTRWxKUVVxTlZxdnlsNm84WGxjaUUxTlZVcGtaS3JDWHhiNGlrOUNTVENQVmxUazAxUENkZ1lTaW5qOFhpR3N5dzdFWUEvbzlVdmQ2UXdETk1qTVRHeGNVbEp5Y3FRa0pBdkFEQ1NlU29ubFpjS0xNZ1dEL1VLa2FzWGZQclk0a3JGOVNrcEtaVjJ4YnB0VGRIYk9kM0RicmVYQXJXeTFRUmxHeGJML2pUSi9KVHJQQmxSRkVsdWJtNThkSFQwQkVMSXEvNmw0WmZiMWQxaU1CaGdOSm9nQ0FKc051cy9mb2ZuK1hWSGpoeFpOM255NUZ4SmM1TXJGNVRpZXArOE4waXBJWElTcjVMekpuZW1MMXVsRUIwZGpabzFheUk1T2ZtMkFMZmc0QkN3TElQbzZPZ3JaaERjdGhxYjFXcXRGTlJraGx1NTk2SnZrRUFHTlovSUoyTzMyenRydGRxSmhKQUgvY3ZITDdjcnFCMDdkZ3dVUkttR3FHSlcyYkQ2RFJ2WDZkcnRnZmM2dEd2ekJ3QUczc2lwVElEcXBVWHkva09sdnJkZWpZMVNMME9TMUFkWDN1UWV1b0pJd1FzVUhDL0M3UkdRbUp5QW9LQmJYOWxpdDl1UW0yZEFibDRlN3J2MzNqc0gySXFMaXhWUWs0dlp6V1p6aFpwYUpaRlB4Wi9tZHJzSHFsU3F5UURxK1plUFgyNVhNUmdNb0NEWFZML0tNRXozV3JWcUo1NU1UVnZSSk9XZUhhQ1VvWUJ2M3cxQm9nc1J2YkVBQ3NrQ0JienJoQ29tcVJ4UmxjeFVVYVFRcVJSTnBTSWFObWlJNEpDUVd6cFdnWUZCQ0F3TWdzbVlCNFBCVUdHYTFHM0ZvUHZERHorZ2ZmdjJpSW1KUWRldVhURmh3Z1RzMmJOSFllaW9LQUxxVnZvUGVIMXEwc2FJb3FpYVAzLytsUFQwOUxYZmYvLzliUU5xUE05ajFLaFJpSTZPUm1ob0tJWVBINjUwZC9MTDNTc21VejZDZ3E4TE1PcUdob1V0UG5mKzR2Q0c5elFLQUtDVHVtZkpQUmRZU3NGU0tuZkxFZ2pQYy9Cd25KZXAxK1dXZWl4NHUyTlpyVllVRnhYQllqSERaRFFoSnljSEdSY3ZJdTFNR2txS2kyK0xNUXNNQ29aUjZweFdWdGdaTTJiY0ZpZTVkKzllOU96WkV5a3BLWGpnZ1FlUWxKU0VvcUlpSEQ1OFdJN3NLSUJXMXZ5VXRUUXZ0YkxBcEtTazFQcjg4OC9uZHUzYWRjeUFBUU9Za0pBUWRPblM1YmE0em9rVEoyTGJ0bTM0OU5OUDhleXp6MkxwMHFVNGRlb1VIbnZzTWYvcXZvc2xOemNYTmNJanJ2cDdwMCtsWXQ1VTdNZUhBQUFnQUVsRVFWU2MyZWpWdXc4QU1DcTF1dU96Ly9kY1NLdldiUzU4dmZVckIrQlYzU296ZnlsQWNMbVJ0MlNPMHN1YnBLa1ZGT1RqME1HREtDd3NSRVJFQklLQ2dxSFQ2ZkQxMTE5aC90eloyTFp0S3dvdGhXZ3BtWWI1K2ZsNGE5b1ViTjY4Q1QvLzlCT2FORzJLME5Bd0FNQmZmeDNIdENtVHNIWHJGaHcvOWlkYXQybDdUVjJ3TkJvTnJOWml4RmZnYTd0dFRORzFhOWVpYWRPbWVPYVpaNUNWbFFWQ0NGcTJiSW1DZ2dLY09YTUdLU2twbFNYZWxnb1NQUGpnZzYwYU5XbzBwVnUzYm4zbG0zZTdpTjF1eDZwVnE3QnAweWFGbXVqOTk5OUh6NTQ5c1dEQmdncWIzdnJsN2hDcnRRVFIxMkNHTnJ5bkVkNmUrWjlTNzZuVjZrRmR1blNKL3VYWC82M3BmbitYRTVSU1JrSTN4VFNWc1UwVUJKR1hmRzJBOTlVTGVCS3dnZUpDK2dWd0hBZVdZUkVSRVlHTWpBeFFBSUdCZ2RpNlpRdldmckFlR28wR1k4ZThpU05IRHFOMTZ6Wll2V29GdW5kL0FIMGZlUlIvL0hFRUMrYk54YXIzMW9MbmVjeWJNd3Z6Rnk1R1VsSXkxbjNnN1N2NjBzdkRyMm5jZkhzRjMzYW1hRkZSRWV4Mk8zSnljbkRod29WU1ByWEV4RVFrSmlZcUtSMTJ1eDM1K2Zta3NMQVFOcHVOdUZ3dUlnZ0NJNG9pazVTVTFQdkhIMzljc256NThyNkVFSFRwMGdWSGp4N0ZXMis5aFhidHZBMTUyN1ZyaDltelo2Tjc5KzRJQ0FoQXZYcjE4UDMzMzJQWnNtVklTRWhBYUdnb2hnMGJwaEQzVVVveGI5NDgxSzlmSHhxTkJoRVJFUmcwYUJEc2RqczhIZythTm0yS0J4KzhISlBZdEdrVHRGb3RqaDgvRGdEbzM3OC9Xclh5RmpVY08zWU1UcWV6Rk45YTU4N2VUdHNIRHg3MHIyNi9sQk96Mll5eFkwWmgwUFAvaDhHREJ1TDNROTU1a3AyZGpRSDluOENUL1I3RnFEZEdsdnJPNUluak1XVFE4MzFtLzJmRzFKU1VsQzRBZEtEVXQ1RU1LNG9pSzY4YnBZa014NkdrcEpnWURIbGVRa3FudDNGelpHUWtHalpvQ0kxV2c1S1NFdVRtNWlBekl3TzdmL2dCS1UyYUlDREFTN0phSTZ3Ry9wTG0vVjkvSFVlWEx0NTVIaG9haXJTMDAvQjRQRERrNVVHbjF5TXB5ZHVZSlR3OEhIOGRQM2JEeCsyV2EyeHlvS0JkdTNiNDl0dHZNWGZ1WE1URnhTRWtKQVI2dlI0eTE3dWMwbUUybXdrQXFGUXFRaW1WdTY0elZxdjFXWTFHTTdWdjM3NzFHelpzaUVXTEZvRmhHTFJ2M3g2UFBQSUlwa3lab2h4ei92ejUyTEZqQnpwMTZvUlhYbmtGL2ZyMXcrT1BQNDdUcDAvajFLbFQ2TmF0R3g1NjZDRTg4Y1FUV0xWcUZSWXVYSWpObXplamNlUEdPSDc4T0FZT0hJam16WnRqekpneCtQREREOUd1WFR0ODhza242TjY5TzBhT0hJbVpNMmVpUllzV0NyQVZGaFlDQUhKeWNoUnc5SG02SWlJaUF0bloyZjVWN0pkeThzWG5tOUdzV1RNTUd2d2kwdFBUc1hyVkNyUnQxeDRKQ1FuNDc1YXRPSGZ1TE9iT0xrMDFQMi9CSW1sdEZiVVovdExROEE4LyttVEY0RUhQZnlzcE1rU3lUamtLQ040SE9CR2xmRjRRUXFoS3BTSnV0OXVyeEFHZ29DQ0VnZHZsZ3MxbVE3NHBId0JCWm1ZR0FnSUNjUEZpT2xZc1d3cEJGQkFzK1FtTGk0dWgxZW13WS9zMytPbW4zZEJvTkxEWmJDZ3BLWUZXcTRYSDQ4SEtGY3VRbTV1TG9xS2lHejV1dDFSajgwM3BFRVVSQXdjT1JOMjZkV0V5bVhEcTFDa2NQWG9VcWFtcEtDa3BnZHZ0UmtsSkNaSEFRSW40TUF6REJBY0h2L0xsbDE4dTBtZzA5ZVdHRnpxZERocU5Cb1FRaFZsVWxuNzkrcUZidDI1UXE5VjQ0b2tuNEhLNU1IZnVYQVFIQjZOdDI3Wm8wcVFKVWxOVEFRQkpTVWxZdjM0OSt2VHBnOFRFUkR6MjJHUG8ycldyOHZsOTk5MkhTWk1tWWZ6NDhSZ3laQWlhTkdtQ0NSTXVWMms5ODh3ekdESEMyNlBSNFhCVW1BdWsxV3FsRGxsKzhVdHBhZDJtTFg3KzZVZDg5T0ZHdUYwdUxINW5TWlcrSjRvaVpzNTRHdy8zZmJUZUF6MGVuSFlxN2R3QUFEcHAwMUJLMWFCVUJZQUZLS09rUndHRVpWWGdPQS94dUQwK3JmN3NjSHM4Y0RnY3NCUmFrSjl2UWxGUkVVNzgvUmZHanh1RDU1NS9BZDI2bGFZdW5EZG5Gakl5TG1MeE8wdWgxV3BMS1ROalJyK0p4bzFUTVBLTk42dGwzRzRac1BrbTM4b3BIUzZYQzNYcTFFR3JWcTNRdUhGalJFUkVRQkFFR0kxRzRuYTdKWFlPN3hOSHpsSGpPRzU4U2tyS2d0VFUxQ3AzcVBBdEk1TUhQQ0Vob1pSVFVnYWF2bjM3b25idDJwZzVjeWFlZSs0NXRHalJBdDk5OTUzVU9NTXIwNmRQUjBSRUJQYnMyWU9QUC81WTZYTlFWdlI2UFR3ZVQ3bjMzVzUzcGYwUi9YS1hBMXZyTmxpNWFnMXFKaVRnNDQ4MjR1MjNwbGJwZSt2WHZRK2RUb2VCejc4QUFGSEJ3Y0Z2ZmYzTjlzRUE5Qks0YVFraGFrS0lpbExLQW1COEFuQ0U0M2g0T0E5UklxWk9KemlQRitoS2lvdGhzVmpnY2J0UlhGS0N4eDkvQWdRRUpkWVNoSWFGS2Vabmg0NmRNUEtOVVZDcjFmQjRQQXB6YjBseE1VYVBHWWZlZlI2Q3krVkNtUFNkT3g3WWZNdWtzck96a1pPVGd5MWJ0dURpeFl0S25ockhjZERyOVFnSkNTR0FsLzdiRjlSV3IxNnQ1amh1T3NNd3MwVlIxSlh0T0g1RisxdFYzZ0t2ck5oM3laSWw2TkNoQXpJek05R2hRd2VzWGJzV3ZYdjNMcldQMFdoRVRrNE9LS1g0OWRkZkt6MXVRa0lDWEM1WEtkV2I0emlZeldiVXJGblR2NHI5VWs3bXpaMk53MGNPbzBlUEJ6RjV5alFjT25RUS96VFg5Ky9maDcyLy9vb3BVOS95bmRlNkprMmFUcm4zM251SE4ydldQQkJTT2dpbFZBMHBIUVFBUzBXUjhEeEhCSUVuUEZjNkhZVG5lYmpkYnNXa1ZHczBjRGdjS0NqSXg4V0w2ZGo5dy9kbzFMZ3hBS0JGaTViS2VmNTU5Q2lTa3BLaDBXZ1FHeGVIMExBd2FDVUxhdisrZldqZW9zV2Q3Mk9UQzlwOWF6K0xpb3FRbjU4UGg4T0I1T1JrMytSYkltczRjdnM3QUdUbnpwMUJQWHYybk1vd3pMaWlvaUtjT25VS1E0Y09yZkI0MTh0T3NHVEpFa3llUEJrelo4NVUzc3ZJeUNnVndYenBwWmZRb2tVTDlPdlhENk5IajBiUG5qMHJCS3FXTFZ0Q3I5ZGozNzU5ZU9TUlI2Ukp1QjhNd3lqQkRiLzR4VmNHRG53Qml4Y3Z3T2ViUDRVb2loZ3pkandZaGtGMmRqYkdqSG9ESE9kQmNYRXhCdlIvQXZmZWR4OG1UWjZLbGN1WHd1Vnk0YlVSTHl1L3MzTDFXZ1FIQjJQQm9uZEd2N05vb2FhZ0lIOWRibTZ1L0lTVm82Vzh0TllFYjJNc3dldGlrL2JoZVlGNk9LODVDZ0lFQndVakxpNGVuMjd5ZHBLLzk5NzdFQndjREo3bjhlcHJJekYvM2h4OCtkOHZvTmZyTVhIeUZFV3BtREwxTGN5YU5RT2NoME90V3JVd1lkS1VPeHZZS0tYbHlxVGs2R2R5Y2pMT256K1AxTlJVQkFRRVFCQUV1TjNlcHdRaFJDYlRJeWtwS1RWbXo1NDlOU0lpNGsyMVdvMHBVNllnSkNRRUF3WlUzTDA2S0NnSUJ3OGV4S0ZEaDY0SlBNTER3M0hnd0FIazV1WkNFQVFzWExnUXAwNmRRbE9wTDhINjlldXhaODhlL1AzMzM2aFhyeDQyYmRxRWwxNTZDZDkrK3kwQTRQUFBQMGRoWVNGR2pCaUJ3TUJBREIwNkZLKy8vanFDZzRPaDBXZ3dmUGh3REJreUJPSGg0ZjVWN0pkeVVxdDJiU3hidnFwQzdmKy9XN1pXK0ozUC8vdFZwYjkzenoyTjhNSDZqYSs1M1c3VjVrODNyWnYrMWxTenRMWjhsQUVHbElxQ0tES2x3QzBvS0FnYXJVYTJua0JBVUNNc0RFbEpTWWlJaUVCTVRDd3lNekxBc2l3YU4wNnAxQi9ZdEdrenJIMS9mYldPMjAwMVJhOVUwQjRVRklUWTJGaUlvaWlibzBRR05aWmxDYVdVWExod0llTDc3NzkvS3k0dTdzMEhIbmdBWGJwMGdWYXJ4Zjc5K3l1MTAxOTc3VFVjT0hBQWd3Y1B2cVp6M3JCaEEwcEtTbEMzYmwyMGE5Y09OcHNOa3laTndwOS8vb21zckN5TUdUTUdVNmRPUllNR0RjQXdERDc0NEFQOCtPT1AyTEJoQXdCZ3k1WXRXTC8rOGsxY3ZIZ3hIbnJvSVR6KytPUG8yN2N2N3IvL2ZpeGJ0c3kvZ3YxeVUwV3IxUTUvYnVEQWwrY3RXQlFGUUN0VkthaTl5bzVTcFVCRTBadDU0RzNBN0Ewb3VOM2VLZ1dIdytFMVN5V2ZtOGxrUW01dUxqSXlNbkQyekpsYmVuMDNqZDBqTFMwTmFXbHBTb3M4dVp1VVhDYmx5M3hiSnZtV1VFckptVE5uYXRTcFUrY3RRc2hJLzdRc0xhSW9JaVB6RWl3V0N3UkJnTXQ1ZDVabzZmUjZzQ3lMOEJvUlNFcXFYV2tRUjdZZXZHTm1CczlYMzVqcDlIcW9WQ3BFaEVjZ0thbHk3c085ZS9laWJ2MmJ6MXJyY2JzLzJMVHBrL2Zmbmo3TkFzQkZDSEZEYVJ4REJFSWdFTUlvRFdQVWFoWFVHZzNWYXFTK3BRRUJDQW9NUkxEVXppOGlJaEt4c2JHb1hiczI2dGFyajNyMXFyZWFzYkkrdkRmRkZKVjdGSlRsVTVNN1NSMC9mdHkzUW9ENDJ2M1NGbFN2WHIycEFQN1ZvR1l3R0s2NlU1RGI3Y0ZmZi8wRlZxVkNjRWlvdjYrbzNZYkNva0lVSE0xSGkrWXRvTkdvS3dTMVAvNDRDb1psRVJSYy9XTm10OXRndGxpUVg1Q1AxcTJ1ekVDZmMvUHpHVi9xMXEyN2VISElpMnMvM0xoQldZZGUwOVJiWWdXSWdpaFNVRzh4UEJVRWtRaThRSlZ1OHo0a2xVNkhBM2E3RFZackNZb0tpMkF1S0NpVmNYQWpwZVlWZnJmYWdTMHJLNnRVajRLeTNka2REZ2RxMTY2dGFHb2VqNmRVN2VmcTFhdlZ3NFlObThvd3pKdndTem5KTStTQlZhbjhuZUFsa1prZmpJWmM1T2JsSXFrQ2h1aUxHWmxnV1BhbWpabnZPV1ZtWGtMaUZmcUwxa3hJdUJYRE5uenF0T2w4UmtiRzJsLzMvRktaaGl0Y0JqdVJVaW9TU3N2M1VlQWxabDZQaDRQTDQ4MkJJNFFnL2laSC9hc1YySXhHbzlJaXIyempsUW82U1pHS3VOUWtVQnZuWDdJVmk3bkFqS0NRVVA5QWxKR2c0QkFVRkpnckJEYUx4WUxnV3pCbVFjRWhLREFYWEJIWWJwa1pyOU85OXY0SDZ6ME42aVd2bDdWYU9hQkF2ZEVGS3BiS015RVVJQVFnY2xkNkVFTEFlTWtzd1RBc1dKYUZpbFZCNVRWZkVSVVZkZWNEbTl5aFhXNlJsNStmWHltZGQxbWZtbS95TGNNd1UvM0x0REkvRVNBSXdsMXZmbGFtSlJVWFdTcjg3RmFOV1dCZ0VJb0xMZGYwM1pMaVltUmN2QUM3M1hiRjMwOUtyb3VRMEdzRGJiMWVQenJ0N0FYWFBRM3FicEx3VEpscVBtTW5YbTdmeDFHTzh4QUorRUFZQXNJd0VyQXhZRmtHS3BYQzFndU5Sb1BRME5BN0Y5aWNUbWNwVUN2THAxWUpTYVRjZklWSXZRbGVZUmhtMnBVWDl0VlRLZDl1VWxXcTQ0cUVFTURwOUhPNVZUb1BLK0c1dTVYQmxXdTlYeGZUejhQaHNGOXhIN3ZkaG95TEY5Q3N4YjNYQWI2Qm8wK2VPdU5vMHJqaFZoOVFvMlUwT0JFQUZVV1I4RHhQblU0SFlWbVdNb1NBRUFZTVF5Q1hObm8zRlZScU5UUnFEUm8xYm54VEtNWnZPTEJSU25IbXpCbWxUWjRjL1N5cnFaV0pmc3FOSlJpSjlmWlpsbVdud1Z2NlVlbHhycEZLK2JhVHFsQWRYNnY4dVBzSGJOeXdIaWFURWZIeDhmaDQwMmM0ZCs0c2hnNFpwT3d6YWNvMFBQVFF3MzRrdkkzbG4wRE5keTVkcjFVYUdocjZ4ckcvVHRwYk5tL3l2YSsyVmdiY0FFQ2tsSHA5YXB5SGVIc3JYQVkyaG1YQk1oS3d5VnFiVm9QR2pWT3F2YTNmRFFjMlgxQXIyOHpZYnJkWFNPZnQ2MWR6T3AyOVZDclZWQUJYTk1pdmgwcjVkalNiL29ucStGb2tOemNYQytiUHhjeFpjOUM2ZFJ1VWxKUUFBT3JWcTQ4ZmYvNFZBUERTME1IVmRsMGIxcS9EWTQvM0s4Vm1JcC9Yc2lYdjRNU0p2NkhSYU5EandWNFk4ZXByWUZrV2dKZVNaOFd5SlRoeDhnUzBXaTE2OXV5Rmw0ZVBVRDZ2RHZtblkzYnAxQjVxdFJxRU1FaEtTc0tRb2NQUXNXT25meXVPUmtWRVJMeTZjdVhLa3RkZmYvMUFHYzJORWtLbzlEK1JtMkR4SE9mbEltY0lKUXdCUXlRL202UzVLY0NtMWtDcjBhSmUvZnJWZWdFM05FRTNJeU1ENmVucGxlYXB5ZFJEVW9zOCtMTGZVa29acTlYYVNxUFJUQVh3ajFkOUE2aVVieitBdXdMVjhUVTlaTkpPSXpJeUVoMDdkb0pHbzFIS3dBZ2hTbWVpNm54eWZyaHhQU3dXYzduM1o3dzlEVFVURXZEMU56dngvZ2NiY09qZ0FXemZ2ZzJBTnlkdjBvUnhhTkR3SG16N1ppZFdyRmlOdmIvdXdkZGJ2NnEyODZ6cU1WZXVYb01kdTc3RGtLSERzSGpoZkd6OWFzc3RtU2VFRU54Nzc3M28yYk1uZXZUb2dmajRxM3U0QzRLQTMzN2JqOGtUeHlNN0s2dXlZOVNMaW9vYXZtREJnc2FTNVNUVGpLdVZ1bEpLNVc3MGhPZDU4SnczOWNOVEt2WERqc0tpUXZ6MDQyNnNYZk1lNXMrZmcvMzc5eUZMT203V3BVc1krZnFyZVBhWnB6Qm0xQnNvS2lwVXp1R2pEemZpdVdlZnhqTlA5OGV1blR0dWpjWm1OQm9WVURNYWpRcW95YmxxdmwzYWZUcTBLNkJtTUJocTZmWDZLUURhVnZYbS9OdWM1b0dCUWJCWnI1OVAvdmp4WTVneGZSbzhFczNNNDQ5NnpjeCtUL1RIb01GRHF2UWJodzRld01vVnkyRXc1Q0V4TVFsanhvNUhTcE1teXVkN2Y5MkREUnZXSWQ5a1FreHNMSVlQSDRGMjdUc0FBRWErL2lxeUxtVUNBRWEvK1FaVUtoWUpDYld3Y3ZVYWlLS0lybDI3NGVHK2ZhSFZhaEVkRTROV3JWc3JtZW9XaXdVMXdzUHgzTUNCMEdnMFNLaFZDeDA3ZFVaYTJ1bHFHL2VyT2FaZXIwZkhqcDJnbmpJTmIwK2ZobDY5Kzl4MFpwYms1R1N3TEl2ZHUzZERxOVdpZS9mdXlNL1BoOFIrVXdxdzVTUmwzNy9mZk9OMTFLeFpFNmRQbjRJZ0NsY0MwTmFKaVluRHhvNGR1K2lkZDk3SmtiVTJINStiSEdBUUJVRUVJYnkzN3BFd2xEQU1DRVBBc0F4eWMzTkJDTUVEUFhvQWxHTEQrbldvVjc4KzlEb2RsaTU1QjQ4OTNnODllanlJTHo3L0RPK3RYb1hKVTZiaHhJbS92WE5zNDBkd3U5MFlQbndZV3Q1N1g1VkIvSVpvYkZhclZXSHE4SzMvbER1M1h5bXRnMUpLWG56eFJUWWlJbUl5SWFSdjFZOVpja01uaTlsc3h1UkpFL0RjczA5ajN0elp0d3pjS3FNNnZocHAwYUlsdG0zZmhUZEhqVUhObWduWXRuMFh0bTNmVldWUXUzRCtQTjZhTmdValhuc2R1NzdiamFjR1BJMHhvOStBU2RJbU16TXpNSHZXVEV5Y09BVTd2LzBCanp6eUdLWk9uYXcwcFZteGNqVzJiZDhGQUZpeWJEbTJiZCtGbGF2WGVDY2N3K0M1Z2M4akxLeUcxeWwrTVIwSGZ2c05iZHQ2NjNnakl5T3hZdVZxNkhSNlpVRWVQM1lNelpvMXI3WXh2NVpqdG03VEZoNk91eVdsUTVHUmtjak56VlY4WGFJb29rYU5HcVgyT1hMa01LWk9uZ2lPNDJDeFdQRFMwQ0d3V0x3UjJYZVhMTVBrS2RPcXBCZ3dETlA3dnZ2dUd4d1RFNk9YdERhZjBpdXdBQmh2UnppUnVGd3U0bks1d0hFZVlqR2JrWmViQzVmVGhlTGlJZ1FHQnFLa3BBUXF0UnFDSUNEMTVBbGtaR1lnTGUyMGN1OGZlcmd2RHY5K0NBQncvdHhadEdqWkVscWREaUdob1dqZnZnUCtPSEw0NXBtaWxOSnlWUVcrbXBwdkJOUzNRN3R2QkhUTm1qV1RDQ0hEYnNSTi8yckxsM2p1MmFmeDdETlBZZjY4T1hDN1hGWDYzdnRyMzhNOTl6VENwNTk5Z2JIakxoTkZtb3hHN055eC9hNXlWTy9jdVIxZDcrK0dqaDA3UWF2Vm9uZWZoM0JQbzBiWXZmc0hBRUJFUkNRMmJQd1lqVk5Td0RBTUhubjBNWEFlRDdLeUxsWDVHS2tuVDZKbmoyNFk5UHh6dUtkUkkzVHBlbitGKzYxNWJ4WDBBUUY0Nk9HK04rMzZxM0pNUWdqQ2E0VERkdjNPK3FzV2pVWURRUkFRRVJHQlRwMDZRUlRGVWtTT0FOQ3FWV3VFUjBSZzVvenBHRHZtVGZSNzRrbUZhT0ZxRzZlb1ZLcm5GeTFhTkJCZUxqZGZrMVFsWVFnalVrb1lob0hINHlFMm13MHVsNHNFQmdiQzdYWkRwVkxCWU1pRDFXcEZkblkyQ2dzdHlNM05SVzVPRGlJam95QW5CUjg2ZUFCV3EzYzg2OVN0aDZOL0hGRW9rbEpUVDhKbXM5MDhZRHQ3OWl3dVhicWtwSFZVcGZkbm1Ram93QnVWcTNiaXhOL1lzdVcvV1B2QmVueTYrUXN3RElQMTY5ZFY2YnZuejUxRGg0NGR5OTM0L1B4ODdOanh6VjBGYkFhREFYRnhwVlgrMnJXVFlERGtBZkN5UEJ3OStnZUd2endVenozN05BYS9NRkRSZEtvcUtVMmFZUGRQZS9ERmwxdVJsNWVIRFJYY3A0OC8raEMvLzM0SWMrZk9yNUJEcnpxa3FzZWtsTUpTYUVIUUxYS0hKQ2NubzFHalJ2anR0OStVb0ZCWjRCMCtmQVQrUFBvSHRCcnRkVDBZOUhvOUJnNGMrS2JCWU9oWmdiL05xN2w1L1cwTUlRU0NJRUN0MWtBUUJPTHh1QkVVRkF4ZUVIRDQ5ME00ZlNvVkRNUEE0WERBWkRLaFY2L2UrT2FicnpGazhQTklUVDJKd0VCdi80VG16VnVnWjY4K0dQbjZDRXlmTmdVQitnRG9BL1EzeDhlV25aMWRMbGZOdDFUS0I5UlFVUVRVYnJkM2xwb1ozNUJaYTdOYUVSUVVqSUNBQURBTWd4ZUh2b1NMRjlPbHhacUhSUXNYSU45a1JHaFlEWXdiUHdHSmlVbjQrZWVmOE44dlBrTm1aaWJtenBrRmpVYUQ1NTU3SGwyNjNvK2xTOTdCc1QrUHdtQXdZUGpMWHI2M3RlK3Z4NGIxNi9Ebm4wZGhOQmlRMHFRSkxxWmZRR1JVTk41NWR5a29wVmk5YWlYKytPTXdPQStIOWgwNjRyWFhSNEpTaW5GalJ1SCtidDN4eUtPUDRjU0p2ekZuOW4rd2J2Mkh0MFYzYlYrSmlZbUJJUyt2MUhzNU9kbG8yZEtiaXJMdmYzdng2YVpQc0dUcGNpVFVxZ1hBR3pXc2FIR1ZCVHUzeTRYdnZ2c1dmZm84QksxT2g3aTRPUFRvOFNEMjc5OVhhcjh0WC80WFArNytBY3RXckxybWhOT3JsYXM1NXBFamg2RlJxOUdnWWNPYmZuK2tza1A4K2VlZkFBQ1daY3RSeTl0c05vd2ZQd1lEbngrRXMyZlNzR0QrWEV5YVBQV2Fna1ZPcHhOYnRteFJBWGpscjcvK01qUnYzdnhQeWRjbVN2NDJKV0pLS1NVTXc4TGpjVk9uMDl1VW5qQU1vcUtpRUJBUUFKMU9oNy8vK2d1aUlNSmlNVU1iRjQvblh4aU1CZzBhNG1KNk9pNWxaaXJIZlc3ZzgzaHU0UE1BZ0plR3ZZaTZkYXRlVUgvTkdsdFJVVkdGdVdxVmdCb3AwNldkeWMzTmpkZHF0Uk54QXp1MHQyN1RGalZyMXNUTHcxN0UxMXUzUUtmVG9YWHJOZ0NBT2JObm9XZlBYdmg0MDJkNC9vVkJtQzVSTEQvd1FBK3NmWDg5YXRhTXg3UzNabUR0KytzVnMyalU2TEdZTUhFS2twS1RzZmI5OWFVNHBOcTJiWWNaTTJjaE16TURIMzJ5R1pjeU0yR3hXR0EwR2hBWUdJQU5Hei9HeDVzMkkvWGtDUncvZmd5RUVFeVpOaDJmYnZvWWFXbW5NWGZPTEV4NzYrMWJCbXJTL2ZCcUg2S29hTklBOEhEZlI3QjM3eDRjUHZ3N2VKN0hMNy84ak5TVEo5Q3pWeS9GSDBrSXdMQXNpb3VMc1BuVFRkTHZsR2FLaVl1UHg2R0RCOEZ4SFBKTkpxODJyTlhpazQ4L3d1ZWZid2JIY1NncUtzUytmZjlEczJiTkxwdkNPN1pqMjlkYnNYVDV5cHZHVTFmVlk3cmRidngrNkNEbXo1dUQ0YSs4ZWtzbzNmUHo4eFdBMG1nMENBc0xLOWNRNWNLRjgramF0UnYrNzdtQm1EWjlCalFhcmVKanV3NnBXNzkrL1JkSGpSb1ZXNUcvalZMS01BekR5RlJqSG84SEhNOFJ6c1BCNDNiRFlYZmd6Smt6aUlxS2d0MWhSMUZSRVFvS0NtQTBHUERISDBld2ZOa1NQUGxVYVY1Rmp1T3c2Wk9QUVlpWHg2MWFOVFpCRUpDZW5xNEVDM3dqb0JXa2RaQXlhUjJFVXNwRVIwZFBJSVE4ZUNOdnVFcWx3b3laczNENlZDcSsvZlpiREg1aElHYk5ub3M2ZGV2aTdKazA5RnpoSmV4cjA2WXQzblc1WURJYUVSMXpiV205V3EwV09yMWVNVnNEQXZUZ09RNnhzWEdJakl6Q3hBbGp3VElzREVhdjM5SHJtNHJBNkRIajhQcXJyK0Q1RndhalNaT210d1RVeWlib0xwZy9Gd3ZtejhXRFBYdmhyZWt6VUw5K0E3dzljeGJlVzcwU2hydzgxRTVNeE1MRjd5SW14cHRmMStlaGgzSHk1QWtNSGZJQ1dGYUZ4eDd2aC9yMUc4QlNwbHhvekpoeFdMeG9JVFp1V0lmYXRXdGozWWFQb05Gb3NHRGhZcXhjc1F4ZmZQNFoxR28xdXQ3ZkRZTUd2NmpNclVVTHZXYmdzMC8zTDIwbWJ0cU0yTmk0R3o0ZVZUM215TmRlQlNGQVltSVN4bzZiY0VQejJJS0RnNnU4YjBaR0JzTEN3dENqUnc4QXdQSGp4OHYxMG1qZXZBV2FOMitockl1eDQ4YmZJUC9yem02dFc3Zk9BTERDUjJzVEFWQkNpRGRHU2tVcTE0dnlQRThad3BBU2F3bmxPUTZob1dHSWpvbUYzVzZIV3FXR1dxM0dybDA3d0RJc25ucjZhYlNYSXV1QWwrSjgrZEozMGJ4NVM4eGZzTGhDYmJPeWNic21ZUE9OZ0ZiR3FWWUcxR1FHQUxsY2FqZ2g1TlViUFVGUG56NEZuVTZIUm8xVDBLaHhDcjc5dGpFKytHQXQ1czFib0poR2lwbkVNS0M0OFZ4MGYvMTFITHQyN3NEU1pTdWcxZWt3WTNycHFqQ0h3d0cxV2cyWHkxbnRBTmFyZHgrNVEzZ3BxVisvQWY2My84cDlURHQyN0ZUcHd0VnF0WmoyMXR1bDNxdW80VzJidHUwcVpIbXRWNzgrbGk1ZldlRnZzeXlMdmZzTzNGU2dyOG94LzJtOGJzUTUyTzIyS2tVcTVhcWI2NVZQUC92aW1yNm5WcXVIYk5pd0llUEZGMS9jQmkvRnJrZ0JLcldTcC9DbWdZaWlLRktCRjhBVERvRUJBVVNyMVZHZFhnZTMyd1dudzF1Tm9GS3IwSzFiZDhUR3hxRldyZHJJeUxpSU9uWHFBZ0E2ZGVxTVRwMDZWM29lZHB1MVVqL29WWnVpZVhsNUNnV1JiMXBIQlJGUStJQ2FvcTNaN2ZZdUxNdE9ySTdKa1hYcEVoYk9ud2U3M1Z0K1lpNG9RR2hvS0xRNkhlbzNhSWdmcGFqZUgzOGNnVnFsUW5UMFAydHJvYUdoS01ndnFMQzdWRVZpTnB1aDErdWgxbWlRbjUvdnpZV1Npb2tOaGp5OHQzb2wzdjlnQXc3Ly9qdU9YRVg0dW5MSGJnRDhjblZqY3l2SHJMSmpSMGRIdzNHRld1ZUFnS3FadkZYZDczcC9LeWdvNkpWMzMzMjNoUkpNa0pyQ3lDYXBuQUlpaWdMaGVRRWN6NFBqUFBCNFpBWmVGeHdPQit3MkcwcUtTN3dNdlBsR0tibS9ha25xZHBzVjBkSFIxdzlzRG9lakZGdkhQd1FMeWlYaHBxV2xoV20xMnZFQXFxVU9xbWV2M3VqY3BTdGVIdllpbm4zbUthU2VQSUhYUjNwcDNLWk9tNDV2ZCszRTRFRURzWEg5T3N6NHo1d3FPVklUYXRWQzIzYnQ4UFNBSi9IQ3dHZGhOQnF1dUgrblRwMmgwK3Z4MUpQOU1IM2FGTlN0Vng5MnV4MDh6MlBtMjlQeDBzdXZvRmJ0MnBqNjF0dFl2SEQrZGZzOTVDZTlYOHBQK3NwS3NKaGJOR1oybXhWc0pScEdYRndjS0JXUmI4eXI4TnlTNjlUN1I5QUtDQWhFY3AwYngxajdEOGVNaTQrUEgvVENDNE5xeU9CR3k0TWJReWtsb2lnUWdlZkJjYnprYi9QQTdYTEI1WFRDN25EQWFyT2l1TGdJRnJNRkpxTVIyVm5aU2wrRnlzYlJaTWdGQ0NvdFA3d3FhdkRVMUZTa3BhVWhNek5UcVFYMU5VVjlLZ3lJckxWSnZqVkdvaUdheFRETWhCc3g2TGVLU3JtNnBUS3E0OHI5TFprd1d5eCtvc2t5WWpUa0lqSXlBb20xeTNPZlpXUm13bXkrK1dObU5PUWlJaUlDU1ZmZ1l6T1pUREFZRE9CNUhsYnI3YzlXNDNLNVZ2WHUzWHM5QUllMHVRQjRDQ0VjQUo0UUloSkNSSWxhWEtZdm9scXRsMW84SUNBQWdZR0JDQTRPUm8wYU5SQVJFWUc0dURqVXJsMGJ5Y25KNVRYRjRHQ29WU3JFeHNaV3FxMWRsWTh0S3lzTDJkblpNSmxNTUp2TkZRWUxQQjVQdWRRT09Wamdjcm1ldUZHZ2RyWE8xanRKcnZhNkVoTnJJei9mQktNaFYyRTV1ZHMxTlp2TkNvSG5Ld1ExQUVoS1RJVEpkUFBHVEQ0blVSQ3VDR3F5U1hxbEJYc2J5bXRaV1ZsbmE5V3F0VmYydHhGQ2xJQUNBSGhaUHdoa2NGT3BWTkJvTk5CcXRkRHI5UXF3aFlhR0lqdzhIRkZSVVlpTGkwT2RPblZRUzBvbnVscXBFckJaclZhbHNxQXNxUG5XZ1BxbWR2aWFvRWFqc1paYXJSNTdxNXl0ZDlLaXZOcEVWRUlJV3JWcWhZc1ptYkJZekNpeVdPQnkzWjBjYlhwOUFCaVdSV1JFSkJJVHI3d2cyclJ1amN6TVM4Z3ZLS2pXTWRQckE4Q3FWUCtvcWQzSlVyTm16Y0ZmZnZubDJhZWVlaW9ibDZPa01yQlJPWkxnSmFma0tjTXdoR1ZaeXJJc1hDNVhLU0pLR2V3Q0FnSVFISnRnME53QUFDQUFTVVJCVkJ5TXNMQ3dhMUppcXJTS1pOUFROd20zZ3NvQ1gxQ1Rxd3NJcFpTSmlJZ1lBNkRWalJ6TTZPaG9HQXpHZngyd3hjZGZmVG9ESVFSMWtwTlFKem5KYjROZXBiYWIrQzhGbTVzcGhKQ1dmZnYySFFCZ0ZRQkI2by9nalpaS3BKVHdFbE5TQ2R6QWNad0NiaXFWcWtKd0N3d01SRWhJQ0pyNGtDOVVWZjR4ZUpDVmxZV2NuQnpGbjFZWnFGM0JCTzFmSGFrZGlyUFZaSURqRG5lZU8rdzJyek1VOUlaeHNmbkZMemRUZERyZGtPenM3TTZReXEwb3BScEl5YnUrZ1FSQkVJZ0ViQXB1eUIydUhCTE5VVW1KTjBxYW41K1BuSndjaGVMb2htbHNkcnU5d253MVg4SklHZGg0bmk5bmdtWm5aOGVxMWVyUjFUV1k5OTU3TDR4R0k0d21FNndseFhja05ianNERTFJcUhtbitWYjg0cGRTRWg4ZlAyamp4bzJwUTRZTUVTUi9tK0JybHBZMVNhWHFCT3BMUkdtMzJ4V3RUUTRzaElTRUlEdzgvS3FxUEs0SWJHWHoxY3BFUG4zejFTb3lRVWxNVE13b0FQZFY1MkRHeE1SY2RTOU92L2pGTDlWaWtyWVlNR0JBL3lGRGhud0FnUGN4U1FYSkpCWGhaZDFWVEZLR1lZaEtwYUl5QzRodllLR29xS2lVcjYxUm8wWlZCN1pNbjZKVFg4blB6OGVaTTJkS01YZVU5YStWRFJ6NGNxeWxwYVgxek1yS0d1Mi8zWDd4eTEwbEwrL1pzK2RvdDI3ZERzdmdCaWxTS2hmTFMwMWc1S3A1aFZQdWN2ZXJ5NlpxV1RPMXFpMzhWSWxoUDVSN2srTUY1Ri9NQW5FYVFOeG1FRTh4Q0djREk5aEJCQmNndUFEQjQ5MUVqbEJSQUNnbDhqYjQvOXF3OWFQM2pQTGZZNy80NWU2VGhHYmkvOVZMcnZIMytZdUZQQUNCQUFKQVpiT1VnUmZOQUNxQ2lqeUY2Q1lRWEJTQ0F4Q3NJSUlPakdBQjRmSkJQTGtnN2t3UTV6bkFsb3I0NUZwUXEvNjU5MFdGd1lPc2JBc01wbUtZTFRZVUZUdGhzN25nY0xyaGRISnd1emw0UER3OEhBK09GOER6SWdTQlFoUXBSRW9KQlpqM2x3MGVDZUFCL3kzMmkxK3VUU2k5YzgrZFpabXVoL2RNZndSeUlNRWJSSkFEQ1N5VmlDbEZrVUlRS0hoZUJNY0w4SEE4UEI0ZWJqY0hwNU9EdyttR3plWkNVYkVUWm9zTkJsTXhMbVZWclZLbm5JK3R1TVNKM0x3aUZCUllVVmprZ05YcWhOM2hCVFdYbTRQYnc4UGpFY0J4QW5oZUpMeEFJUWlVaUpRU1VCREx4WGNic0N6emhuOXErc1V2VnljaUJReVdTSmdLd3lDSUxHeDNkanJpb0FVTEZoeVpPSEhpUmE5SjZnMG1lRTFTaUpJRlNrUkt3QXVFY2dKRFBEeEwzWndLVG84YU5wY2FKVTR0Q20wNkZGajFNQllGSXRzY0JFTnhDUEpLNGxBbmdVTmNlQUVxcTRvc0IydzV1WVV3NVpmQVVtaEhpZFVKbTkwTmg5TURsOHNqZ1JvUGp1UEIrMnByRktBVWhBSk1TSWorTlFBSi9tbnFGNzljSGFnZE8xY2ZZTU1RRkhKbjk4bVZwR1pDN2VUSEowNmN1Qm9BRDIrRVZINFZRUWdGdkczOENDRWdVck5sSXRFZHNTbzFWQ28xVkdvTjFCb3RORm9kdExvQWlDUVF4VFkxOG9vU2tHY093NzMxejFjSWJxVk1VV04rQ1F6R1loUlliQ2dxZHNCcWM4SGg4TURwNHVCeXk2QW15Q1lvNGIwbUtLRWlKWlNDT1BKV2RDZUV2UEp2bW5EanBtM0IvWDNmdVdHL3gvTUNSazMrQXRIMXhpRzA5cHNZUG1vVEhBNlBmMlhmNVdJb2pBVFlNTVRFeHY5cmtzNjFXdTJnUGIvdWE0N0xWT0lhU3VGREplN05uaEJGa1FpQ0FGN2dDYzk1QXdlYzUvL1pPKy93S0tvMml2OW10bVZUSUUxNkNVaEhhVkpFMEFBZlVrUkJCQ3NsSUFncUxZTFNsZDZEOU41RXBTZ0lpaUJLRVpBZWxDSUlVZ1VFRXRKN3NtVm12ajltZDVKTkl5Qk4yZk04KzBCMlpuYnUzcDE1NTIzM0hDc1dxd1dMUThaUDFUNUlKRDR1anVTVUZIUTZBK2dLY3pQZUw5ZHp1eGkyR3hFSlJNY21rNUNnR3JYVU5BdnBHZGJNdkpyRHNObnRzbUMzTzBKUUdVRUJBUkNOUnNQNzdrczBmd3diczVGTlAvek91czk2c1dsMUgzYnUrWk4rZzllNkorWVJSMVI4b2YrY1RpNUF1ZkxsTzJReGJBWlFER1NxVzRuTzFqQkpsZ1hKTG1HMzJ3V2IzWlpaRmJWbVZrVlRVbEpJVEV3a0xqYVd5SnVSZVB2NDN0cXcvWDA5VGcxQjQxSkpURW9qSmRWQ2VwcVZqQXdiRm91aldKRHByU0ZKTXJLc09Bc2NZa2JVdkRjRWdSZmRsMmplU0UyMU1HL3Bic0xHZHlDNGNTV0NHMWRpOGF6T3JGeHprSmhZTi9YUW93cEZBVW5TMzVHbmR1YjBId3dNdmZjcDdUTm5UaE0yYllyR2FlakV4bzNmRU5MbExVSzZkbklSNUltT2ptYmdCd040dTN2WGxpMWF0bXBNRmlweFJWSFVJb0tpaUE3bUh4UkhxNGNrMmJWMkR5ZkZVWWFqNVNNMU5aWGs1Q1NOVWp3eE1RbTdyTSsxMENJQ1dHMTJOUVNOVFNFaEtZM2tGQXRwYVJiU25VYk5XVEN3UzlqdGttQ1haQ1FaeDdJcHhJOC9hcTB6NkhYdlBlZ0w1T25ta3drZDloWEJiY0lJcWpHY0p4cU80ZnV0SjdUdGRadE1aUGpZalZTdFA0cmlsVC9pN1BsSUxCWWJZeVp2cG56TkVYZ1U3VU85cGhQWnNkdFZLTmRtaytnZCtpWGVKZnRUdlBKSGZEcHZlK1lQR0pQTUs1MFg0bGYyQTd4TDl1ZUZWK2R3NFZLVXRyMWoxMFhVYlRJUmdHTy8vMDE2dW8zZ1JwVzA3YzgyVkVYdkQ0WmZkTi9oanlnRUFWTFNoRHM2dG5LVnFvd2FNL2Flam0vK3ZMa3NXN3FFU3hjdmFpU3VBRmV2WG1IRCt2VXNXTFNVWmN0WGN1TEVNWTA4ZGY2OE9UUnI5ajlXZnI2S2Q5OTcvMVVQRDdPSE14ekZVU0VWQkVFSE9JMmJ5dHNtU1VpT1ZVdzJtdzJyTFl1eWZIbzZxU21wSkNhcUlXbDBWQlFKaVZLdU9UWTl3STBiQ1VUSEpCT2ZrRXBTa2xvRlRYTzBkbGlzZHF5YXB5YmhxSUk2dkRVMURQMTQ4RXU5S0tDQys3M0dnbVcvc0hWOVA1bzlWNFhOUC83T0sxMFdjbmpuVUdyWFVCYzd6NWkvazVVTHVtRXk2cWxVb1NnOSszM0JqenYvWU9HbmIxR3BRbEdXZnI2UEYxNmR3L0c5STZsV1JlWHJPbkQ0SXVYS0JuQjB6d2lPbnJoS2ozNmZVOGpIVE0rdWpRa2Q5alVXcTQzd240Y0MwRy93VjRTODl4bjdmMUlabWpxMnEwTjhnbHJldWg2UmdOR29KOEEvODhsc01PZ0k4UGZpMm8wRTl4M3VSZzdFeHNZeWNjSTRZcUtqRUVTUjk5N3JRNE9uRzNMdDJqVUdodmJIWnJQaTYrdkhpcFZmWktZN2huekVqUnZYQVZWMHlkUExpelZyMXdFcXJmK2NXVE5JVEVyRTM4K2Z3VU9IYVZvV0FPdSsvb29ORzlacit3UDBmS2NYUnFPUnNHbFRYTWIyKzRrVFBGVzNMcDZlbnRqdGR2eDgvVGh4L0RqMTZ0WG54SW5qZlBDQlN1amo2K3RicjBTSjRpMHVYYnEwR2JBNVhsb2hRVkVVMlJHU09sWWtTT2gwZHV4Mkd6YWJIcXZOMGFpYm5rNmFTUTFKRXhJU2lJbU53V1RJdmFkTm41NXUxWHJXRXBPeVZFRTFvK2JJcXpseWE0NFExT210Q1NmMmZWeElweE43UFN3WHdsc2Q2OUhzT1pXQThzVldOV2plcENvTGwvL0NvcG1xOW1XYkZrL3lXbnVWYUNRdVBwWFBWaDlnMVpJZXZOUmFWZjJlTnE0amgzNzlpeW16Zm1MbEFsVTV2VmpSUWl5YjB4V1R5VUNsQ2tYNTdmZ1Y1aXplUmMrdWpibDhOWmJTSmYwb1dkd1BUMDhqeStkMjVkcU5lRzA4YjNTb3AvMC9MZDJLaDRjK2x5U3JIb3ZWN3I2TDNjaUJyOWF1cGthTkdvUjBlNXRMbHk0eGY5NGNHanpka0ZLbFN2SDErZzJjUDMrT2llUEh1Und6YWNvMEFCSVRFK2o5VGc4K0dQZ2hvTXIyVFJ3L2x2RVRKMU84ZUhGKytuRXJDK2JQWS9TWXpPTnIxcXFWZzNrNEw0SGx4TVJFUER3OGlJNk9adEtFY1VpeWhJOGpUNWlZbUlqSnc0UHZOMzNIamgzYjhQVDBmTFZIejNmMkxGdTZ4T28wYkk1VkNaSWozNmJJaWlJSWFraXEyQ1c3WUxQckZMMHpKRFdvSVdsNmVqcXBxU2xxU0JvZmowRXZrcDR1WXphN2psRzhFYW1Hb1BHSm1UMXJHZW1aQlFPYnpkbXpsdFZiQTluaHJWV3RYTHduVU8xaHVSRHExSFNsb2FsZXBUaW56dHpRL3E3NGVPWkM4ek5uSTVCbGhZYjF5cnNjMC9qcEN2eHhKbE5YczI3dElFd21nL1ozL2FmSzhlZTVTR1JaWnNpQWxuei80KzhFUGo2UU5xL040YnNmVGxDOVN1N01yR1lQQTFhcmxPTjlpOFdPbDZmUmZSZTdrUVAxNmpkZzU0N3RyUHhzQlphTURNS216eWpRY2JJc00yYjBLTnE4MkpiNjlkVmc2cSsvTG5IdDJ0OE1IenFZN2lGZFdMMzZTNkp1dXVvTFZLcFVtVmM2ZEN6dytFNmYvb09SSTRieVR1OTNhZHJVdFNkLzBvUnhYTDc4RjJIVFoySXltYXAwNjk3amVWd0tDWTRLcVpvU0UxQzlObVJaUXJKTFNIYTdReXRCclpKYUxSWXlMQm1rcDZWcmhZVEV4RVJ1UkNibTlOalVuclVVa2h6ZVdyclRXN000UWxCbno1b2tDNUpkOWRhY3piaG53c2Y0NlhSaXo0ZnBRcEN6YVZ0bVdPem9kSm5GMzZ3R0pMdVZ6L29aV1lWK2pkbmNYYjFlUks4WEVVV1J0aS9VNVBxWktYeS85WGQrM1BrSHc4ZCt5L3hsdXptOFl4aWUyWXhWcVJKK1pHVFlTRWhNdzdld3A1YS9pNDFMcFdSeFgvZGQ3RVpPdzFhdlBuUG5MU1Q4U0RpZnIxeUIwV2hrekxnSnR6eHUyZExGZUhoNDBMbEwxeXk1WWh2RmloVnpDVnYvQ1FvWExreEdSZ1poMDJkU3FGQWhUcHc0VG1GZlgyM2JNNDBhMDZKbEs4MWJMRjY4K0t1OWVyMjdjL0hpaGRZczRhaWRMTHh0TGw2YlhSSjBkcnZpVWlXMVdFalBTQ2MxTlkzazVHVDBPb0dvYUpsaVJRdmg1V25LOU5oaVlwTkpTRXhYZTliU3JWckJ3S1pWUWVWTWIwMVd2VFZuYnUzeGNvKzlEVHorTUYwSTRVY3Z1L3g5Nk1nbGF0ZkluVTIxWXZraTZQVWlCNDljY25uL1FQaEZxbFRLekR1Y3ZlRDZWRHQrOG0rcVZWWUpJWWVNMnNENWkxRjBlZU5wVmkzcHdmNmZQdUxVNlJzY1A1bVRRNnAyamRLWXpRYjJIaml2dmJmdjBBVkVVZURwYkY2akcyNEFUSm80bnZBajRUUnYvanpEaG8vazBLR0RMZy9kM0xCdjMxNzI3TjdOOEJFZnV3Z1dWYWhRZ2FTa0pIN1pzMXZMdDMyLzZUdVhZOCtmUDhmR2pkOFVhR3hQMXFpQjFXckZaREloU1JJSEQreW5aaTFWeTdSV3JkcmFPSS8rOWh0QlFlVXdtVXpsKy9UdDE1eHNSWVRzWHB2czlOclVRa0ptbGRTbUNpOWJNakpJVDFkemJTa3BLY1RFSmhONTA5VnIwOGNucUNzTVVoM2Vtc1ZpVTlkc09VTlFtOE5iazF5OXRkLzJEUGZSNmNTM0g3cWN4SVpmZWFiKzR6emZ0Q29yVmgzZzVPbnJyRnJTSTlkOWZYdzg2TmVyR1lOR3JzZmJ5MFNsQ2tWWi91VitEb1pmWXVyV0Q3WDkvamh6ZzRYTDkvRHUyOEVjL3ZVdjVpemF4ZEk1WFJ4R0w1SStINjVoWHRpYkJQaDdzV0xWQWJ5OVRacGhYUHZORWVJVDBuaXZSekJlWGlaNmRHbE0zOEZyOGZIeHdHalEwenYwUzdwM2VnWi9QeS8zWGV4R0RuVHUzSld3c0Ntc1hiMEtXWllaT09nalJGRjBLUjRrSmlieVdzZFhxUFBVVXd3ZE5vSzVzMmVTa1pGQm4vY3lVOTl6NXkvQ3g4ZUhTVk9tTWZQVDZTeGF1QUJQVDA4R1poTlNQbmIwS0JzMnJLZDkrdzYzSEZ2WnNrRzBiOStCZDN2MVJFSGgyV2VEcVZldlBnRHY5K25INUVrVFdQZjFWNWpOWm9ZTUd3NkFuNzkvaC9hdmROaStjY00zTnNEcU1HNTJWR1VybFcxWGJkcDE1TnBVcnkxcmxkUml0V3E5YlRvUjRoTWtvcUtUS1Y3VVY0dVNoRm1UWCtWR1JEeFJNY25FeDZlU21Ld3VlazlOczJwTHFUSXNrbUMxU3RqdGlpREppcWdvaU5ibytmMTBPakhzWWJvSW5tNCttU2VxbHVEQ1g5RWNPbktKR3RWTE1YMThSNTU5Um0ycHFOdGtJaSszcWNuSWo5cG94MWl0ZGthTy80NHZ2anBFUW1JNnRaNHN6ZmlSYmZsZnNNcjk5T0hJOVp3NUY0R2ZyeWMvLzNJV25VNWthR2dyK3J6VEJGRGJQUVlNL1lydHU4NlFuSkpCblpwbG1EYTJBNDJlVm1YUU9uWmR4T1dyc2Z5NmU3Z2puMllqZE5qWHJQbm1DS0lnMExGZEhXWlBlUU1QRDRQN0xuNkVzZWQ0NWYrazZscHVpSStQbjFqamlhcmZBeWxBS3BBbUNJSUZWZDNLN2xDMWtuVTZuV0l3R2pFWlRZcUhod2tQc3hsUHN5ZGUzbDc0K0JUQ3Q3QXZvZ2dOYTBtVUQzcU1hbFZLVUQ1SXBUVVN4bzk4eVhVWmxjTjdTMDJ6a3A1aEpTUERKbGlzRWphYklraVNMTWd5WXVmWDYray9XOURqTVBEa3cyYllXald2enVpaEx6MnlONGlpZ0NDNHYvdkRNbWNGL2V4SHliQXBpdkpIbzRZTjN2Lzc3NnNwRHVPV0pnaENPbUJCbGV5VFJGR1VSVkZVREFhRG9oSlBlaWdlSGg2WVBjMTRlbnJoN2UxTjRVS0ZFUVNGZWsvS2xDbmxUNlVLeGFqNVJHazhQQXpvVXh5NXRZd01HMVpMMXRVRldSYTVaK3RiV3pxblc1ZUh6YWhsaCtEYisxLzNnMGVlbTBiUklyZS9yRVpTOUZ5T3FreDhraDVKRnNsSWZ6UlZxanpNWm5TaWduOGhPMEZGemlJS2ViZlF5QXBjaWFsTWJJSUpTZGJkc3psVHh5UVRXTmhDMmNmTzN0TElYYjkyN1ZINHFhb3ZXTGo0MlJmYnROcnB5TFhaRkVXeGdXQVhCTFdRSU11eUFDZzJteTNUSU9KWTZTU3JLeFU4VENhMXVUbEZJakVwazlvb3FFd2crdFEwaTRPNUkxdHVUWktSSkZtUUpNV3h5a0I5b1NEcTlib3VELzFUSVdIUkkzRXpwOXY5K1AxQ0tmUUdEM3dLdTNWRlUxTlRpRTlPSWlhaEtqVWZ2NHBKbjVpclVmdjEzQlBvOU9iN01tZXBxU25FSmljUkZmOEU5U3FkeXRlNGxTejFhQkRqRkM5Um9nM3dpOE93V1FHOUlLREgwZGVHcW13bHFNdlFCUVVFUVJRRVJSVFZiZ1JScDhOaXRTS2drSm9ta1p5Y1FYeENHakV4eVpRcTRZZVlubTVUdlRXclN5TXVkcnVFSk1scUpkUkJucVNBa0haemJtdWc4Y000V1lkMkRIM2t3dERJdUNMb0RSNy9LVmFJZndJdkwyK0tGaXVCVHU5QlJGenVpbCtYbzZ1aDA1dnYyNXhsanNuTWxaakt1QUdpS0RiY3MvZEFMVEpKS0EyS291aFFLNlNxTlZNVUZFVldLNlN5TTRyTXVwWlVsU1JJVDdlUmtwcEJZbElhc2ZHcFJFWWxJYVpyUEd0cXo1ck5MbUdYSEViTkdZSm1NbmdJUm9QdVRmZlA4dkFnTnRIMG4yU0YrS2Z3OWlsRVRLSXAxMjF4aWZvSE1tZmVQb1dJU1RDNWZ4d0hTcGN1bmIzMXc4bjhvWE95Zmpob2paQWx0ZjNEN2xna2I3ZlpzRHBhUU5JenJLU21XVWxPeVNBaElZMlkyR1QwRmtzbWVhVFdzNmJtMWdTMWIwMXhXRTZFaENzem54UUU0ZFhiQ2drZjRXVDJ2WjRmU2RZaHlUcTNwNWFIbDVRWUg0c3NneWk2enZlRG1qTjFUSEYzOUpzbkpTWnkrYStMcE9ham9ldmw1VTFRdWNjcFZMandYUm52dlQ2bndXQm91L2FyOVYrOThYckhDMlJTaCtzRlFiQTd3bEZabGV4VEJFbVNGVW1TQk1rdUtVNnZ6VzZ6QVFvV2kweDZ1cFhVVkF1SnllbkV4YVdpVjNOcldaZE55UTV2VFZhTEJnbzQrZGE4dkV5dkYyVEEvekdLNDN2N0ZQY0VuVTZpaUc5Q3ZsVEh1VUVuU285c29hQWdTRTlQZHpGcW9CcVVCemxuNmVscGQvUWcrK3ZTQmRMU1V2UGRKelUxaGN0L1hhUkdyVHAzWmF6MzQ1eTE2OVIrRHJpYUpkZG1kWGh0a3FLb3VUWkZrUlZWeFVweXBUYXkyeEZRc0ZnbE1pdzIxV3RMemlBK01RMTlGdjBDTmE5bXp5d2F5QTVYRUFWaDgxZDlQRVJSZUwwZ1J1MC9SbkY4ejVHYW1rSkVnbCsrVk1kM2l1M2JmbUxGOG1WRVJkMmtSSWtTZlA3bEdzNmZQMGVQN2lIYVBrT0hqK1NGRjlxNGY0aUhHTGN5TUZtdnBYL1RPVDA5dmRxMGU3bjkrdSsrM1dqSnpMVmhFd1JFVUJ5NU5rRlFGQmxKa2hWSmxnUzd1a2dldTkyR0FGaXRNaGtXRytucFZsSlNNMGhLU2tlZm5VRFNuaVczbG5YNVZQT20xVjRGU3Q5cW9Ga3BqdDBvZUlqaTVlWE56VWlabS9GK0ZQT1B2eXVmZStQR0RhWk1uc2lZY1JPb1Y2OCtTVWxKQUZTb1VKSHRPM2NEOEU2UGJ2ZnNleTFmdHBSMkw3Y25JQ0FneDdobXpaak95Wk8vWXpRYWFmNThTOTU3djQvR0tuSHQyalhtekpyQnlWTW5NWmxNdEdqUmtsNjkzOHZCT25FM2NhdHpQdGU0SVFhREFVRVFDUW9Lb251UG5qUnExTmg5OGY1emxCcjU4YWdHMzMyN2NSZmFFaXRGRDBLbVBvSktJYTVremJXcGkrUWxRTUZxazdGWTdHUlliS1NscTdrMjBSbUNTcEtjMmVJaFo3WjQ0R3p4ME9rS3RPVC92MHB4ZkYvQzBueW9qdThFWi84OFEyQmdJSTBhTmNab05CSVlHT2dJeHdSTUpoTW1rOGxsTGVIZHhtY3JsaEVYRjV2ai9kR2pSbEt5VkNrMmZyZVp4VXVXYytqZ0FUWnQrbGIxK0dXWm9ZTS9wRkxsS256NzNXYm16Sm5QbnQyNzJMamhtM3Myem9LZWMrNzhoWHkvWlN2ZGUvUWtiT3BrTm55ei9vRmNKNElnVUtkT0hWcTBhRUh6NXMwcFVlTDJuQWhKa3RpL2Z4L0RobnpFdGIvL3ZpOHBnYkZqUnZIV0c2L1J2VnNYenB3K3JXMzcrK3BWeG84YjA0d3MxVkVjNjBlZERMdVNKQW1TSkFtS0lndVNhdHdFcDdDeUpLa1JwOVZxZC9IYU5NUG0wdDRoT1NtLzFSYVArS3N6bmhBRVd0N3FDL3dUaW1NM1ZNOHRMNnJqMjhIeDQ4ZDR1VzBicGsyZFRHUmtKQyszYmNQTGJkdXc4ck1WQmY2TVF3Y1AwUG10TjJqZUxKZ2UzVVA0NDlRcGwrMTdkdThpcEdzblhtajFQTjI3ZGVIUXdRUGF0bjU5MytmbHRtcG8rOEdBL3J6Y3RnMTkzMzlYTXlMQndVM3BHdElOazhsRWthSkZxVnV2SHVmT25nVWdMaTRPUDM5L09uWHVqTkZvcEZUcDBqUnEvQ3gvL25ubW5zMzc3WnpUYkRiVHFGRmpoZzBmeVpMRkMxMFlaZThYeXBVcmgwNm5ZOXUyYmV6ZHU1ZWFOV3RpTUJoeU5kaTUvWDlBLzc3OHNtYzNaODZjUnBLbDJ6cDM0OGFOMGV2MWVUNGdjdnQ3M2RkZjRlbnB5ZXExWHpONDhGREdqeHVENHJqSVo4Nll6c3Z0WDJtMlp1MjYwbVRUSHdWMGdpQUlvaWc2MTQ0aWF6VUFTWHM1cWRXc0Zqc1pHVGJTMHF5SXpvS0JYU3NZT0xVTXRLS0I0TzNsMGI1Z1Q1STdwemgyUTBWS3F2Q1BjMnkxYXRYbTIwMWJHQkE2a0pJbFMvSHRwaTE4dTJrTElkMjZGK2o0aXhjdThQSEk0YnpYcHk5YnRtN2oxZGRlWitBSC9UWHVyaXRYTGpOKzNCaUdEQm5PNWg5KzRxV1gyakZpeEREUzB0U2svSnk1OC9sMjB4WUFac3lhemJlYnRqQjMva0lBUkZHa1UrY3UrUHFxbnVsZmYxM2l3UDc5Tkdqd05BQ0JnWUhNbVRzZkR3K3pkbk1jUDNhTUdqVnEzck01djVOejFxdmZBS3ZOcGhuays0bkF3RUJ1M0xqaGNDWlVpaTAvUDFkUC84aVJjRVlNRzRMTlppTXVMbzUzZW5RbkxrNFZHLzUweGl5R0RSOTVSdzVJUUVCQW5sNytod05ET1g3c3FHYk1Gc3lmNTdpZXptdS9iOVZxMVRFYWpWeTdwbnFLNTgrZm8wR0RwNmxWdTFhRFhMdzJFUkFWQlVFUUJMWHRROGxhUkhDMGdOaXplbTEyMGpPc2lIWkoxc1JabkdHb3JHU3llS0FvZ2lnS0wvK1RIMkxRd0ZCZTY5aWVrQzV2MGJYem0yelovTDJXMTNpdDR5dTBiL2NpM1VQdTdXS0c3aUZkdUhqeDluUUZ0djMwSTY5MWZJVVhXcmRnOXF5OENmNTI3TmpPNkU5Ry9tZU02K2JObXdodTBwUkdqUnBqTXBsbzFmb0ZxbFN0eWphSGtFZEFRQ0RMVjN4T3RlclZFVVdSbDlxMncyYTE4dmZmVnd0OGpqOU9uYUpGODZhRWRPbEVsYXBWZVM2NFNhNzdMVnd3RDdPbkp5KzB1WDg2UVFVNXB5QUkrUHY1azVKNi8wVjRqRVlqa2lRUkVCQkE0OGFOa1dVWms4bTFQNjV1M1hyNEJ3UXdadlFuREJvNGdQYXZkTURmMzE4Ny9sNmdUOS8rVEp3d25ybHpaclAxaHkxMDZhb1dxQjZ2VUlGZjl1eEJsbVV1WGJyRXpadVJwQ1NyOC9iNDR4WFlzM3NYWGw3ZXpiTVpOWDJtY2N2c2FWTmtSempxMUVlUVZNZE1WWktYc0ZodHBHZllFTE1ZTmExZ0lDdUFvMmlRR2pHM0dYZUJJZmVqSWNOWStjVnE1c3hid09jclYzRHAwaVdOM25ocTJLY1A1UTNlb21VcnZsNi9nVzdkM3VaUlFtUmtKTVdMdStadHlwUUpJakpTWlJYMjl2Ym10OTkrcFhldkhuUjY4M1c2ZGUyY2F5aVNINm8vOFFUYmR1emlxM1ViaUlpSWNGRTRjdUx6bFo5eCtQQWhKazZjbkdmNGM3ZFIwSE1xaWtKY2ZCemVEeWp0VXE1Y09hcFdyY3IrL2Z1MW9sQjJ3OXU3OTNzYy9lMVhURWJUUDNvd21NMW1PbmJzU01lT0hURVlETFJyMTQ2T0hUdnkyR09QdWV6M2VJVUs2ajN6MVJwNjlYNlhRb1hVWFB1cnI3MkJUcStqZTdjdWZQSDVaM2g3ZTJQMlZMM2pRUjhPNXVlZmQvSjI5NjVWdW9hRVZIRVlzNng1TnAyaUtDcFhtM00xZ2l3ak9VTlRXZFk2T2xTdlRjSmlzYUdYc3F3eWNEVGtPc05RQU1GazByZTltejlJNGNLK1BQRmtEUzVlT0UvNThubVRLeVltSmpCNTRnU2lvcUt3MjIxMDZ0eFZZK004YytZME0yZE1KejB0alNKRml6RjAyQWd0TWI1NzE4K3NYdjBsbG93TXFqL3hKQjhNL0ZETFArejZlU2RqUjM5TWVub0dmZnYxMTd5RWI5YXZVNVBYaWtMVFp2K2pXL2NldC93ZVI0NkU4K24wYVlpQ1NNbFNKZkUwZTJyYjhodEQ4MmJCOUIvd0FVdVhMRUpSNFBzdFd4ODZ3MWEwYUZFaUl5SmMzcnQrL1JxMWE2dTlTbnQvMmNPcUw3OWd4c3pabENxdEZzcWZhOXd3MTVzcnU3R3paR1N3ZGVzUHRHNzlBaVlQRDRvWEwwN3o1cyt6Yjk5ZWwvM1dyL3VhN2R0K1l0YWNlWGV0NGZSV3VKMXpIamtTanRGZ29GTGwrNzlFeW1xMVlyZmJPWHBVRGZ0ME9oMFdpOFUxcFpHU3drY2ZEYVJ6bHhET25mMlRLWk1uTW5UWWlEc3FGcVducDdOK3ZWb29hZGV1SFQvODhBTlpGNmRualhCMjcvcVo4Uk1tTVQxc0doTW1UYVpTcGNxWXpXYUdEaHNCUUZKU0VtKzgxb0VTSlVvQ1VLcDBhVDZkTWN0eDMreHErUG5LbFg5a05XeUNJRGpDVVVYSUducXJLVE1KUlJHUUpBVzdYZlhhYkRZN0ZvdGREVVZWYjAwV0hFVURUYWhGVVJSUkVJUzdHZ05FUjBWeDZ1VHZWS2xTTmQvOXpwMDd4LythUDgreUZTdVpNM2NCczJiT3dPcFlHemJxNHhHRWhnN2s4eS9YOE13empWaXlhSUdXcjFtL2ZoMXo1c3huNVJlckFmaGh5MmJ0TTMxOGZGajV4V3BHanhuSC9QbHpBWlZZYi9QbVRTeGN0SlFseXo3anQ5OStZOWZQTy9NZG04VmlZY0s0TVl3YVBaWXZWNitsVE9teTJyWmJqY0Z1dHhNVkZjVzZiNzVsL1RjYkg1anhjblp2QXlpeWpOMXUxNHhRbXhkZllzK2VYWVNISDhadXQvUHp6enY1NDlSSldyUlU2MGV4c2JFSUFvZzZIWW1KQ2F4ZTlhWGpjMXlySHNWTGxPRFF3WVBZYkRhaW8xUkpRcVBKeEJlZnIyVHQydFhZYkRZU0V1TFp1L2NYYXRTb2tSa0tmNytKYnpkdVlPYnN1VnI0ZE0vRDd3S2UwMkt4Y1BqUVFTWlBta0R2ZDkvSHkrditFNFJHUjBkckJzcG9OT0xyNjB0Q2dxdksyY1dMRndnT2JzcGJuVG96OHBQUkdJMG1MY2QycjNEeTVFaytuVEdMNTRLYk1PTGpUL2p0MXlNdTI1T1NrcGcyZFRJdnZ0UTJSemo4OTk5WFdiWjBjYk1zSWFoV1JIQjRiS0lnQ0M3THJGUWFOZGxSSTNETnRlblZTcWlzZVd1eXltRXBBRUo2NUx6V1FNbTc4YVduaDAzRjdHSEdhRExTcDI5L1NwY3BrKy8rTld2V1l2bXlKV3piOWlNNm5SNkxKWVBrNUdRUzR1UHg5dmFoYXJYcUFMelV0aDFObXpVRDRQRGhRMXo3K3lydjluNUhlN0o1ZS90azVoMGM3SjVWcTFYVEV1SGg0WWQ0L3ZtV21NMnFhL3ppaXk4UkhuNllwczMrbCtmWXJsNjl3bU9QRmRHTWM1VnExZGozeTU0Q2pRR2dhMGkzZTVibktBaXlOK2hPbVR5UktaTW44bnlMbG56OHlXZ3FWcXpFcURIaldEQi9McEVSRVpRcFc1YXBZWjlxTW0ydFgyakRxVk1uNmRHOUt6cWRubll2dDZkaXhVckV4YnZlT0FNSGZrall0S21zV0w2VU1tWEtzSFQ1U294R0kxT21oakYzeml5K1dyc0dnOEZBY0pPbWhEakNmVW1TbURaVkRRUGZmTjIxdytqekwxZFRyRmp4dXo0ZkJUMW52ejd2SXdncWMreWdEd2ZmdFQ0MlJRRnZ6NEtYd2k5ZnZveXZyeS9ObXpjSDRQang0MWl0MWh6M3c2MkF1UUFBSUFCSlJFRlVUODJhS2syM1hxOW5VRGFtM0R2RmQ5OTlsM2N1UGNzNWF0V3FUYTFhdGJXL0Z5OWF3TzVkdS9oZjgrZHpGTEg2OTN1ZmxPUVVRcnEvWFdMMDJQRlBObm11MGEvWjhtdzJRYlhrem5CVVVZc0lNb0tnZW15U28xYmd6TFhwSmFmeWxIUDVsSnBiQXhDTVJsM3J1M1h4RFBwd3NFWWJYQkNzWGJNS3U4M09sS25URVFSQmF4OXdoamhPR0F3Ry9QelVKNnpOYWlPNFNWTk5iaXkveEcvV0VDbXJkeTRJZ2xhS3ppKy9rbGV6YUVIR0lHWmY1M09QMExKVmExcTJ5dmtUVnF4WWlWLzJIY3ozMkVhTkd1ZDU0NXBNSmtaK1BNcmx2WGQ2NWVTL3E5L2dhYjVldnlISCt4VXFWbVRtN0xtNWZyWk9wMlBQM2dQMzFkQVg1SnkzbXE5L0FrRUF2YzVPYW1wS2dTcVZpcUp3N05peGYzemVWV3UrdW05ejNLdjNlL1RxbmJ1bSt1dzU4N09tS3VvQng3T0ZvN3BzNGFpZ3lMSWlTWFpFUVVTU0JNZmlna3l2VFpTZFlhaWpHcXFTdVdsaGFHc2VFS0tqWS9EMjhVWVFCSTRmUDBaU1VoS0tvbEM2VEJtU2s1TTVmLzRjQUQvOXVGVVRjcTFYdno0Lzc5eko5ZXVxV096T25UczRlZkwzZk05VHQyNTl0bS9iUm5wNk9qYWJqUisyYkthK296U2RGOHFXRFNJNk9sb3J1VWZjeUpUM3U1TXgvSk1udlRsTGJzK043RWx2enh3OWdROTZ6bkliRTBBUjN3UlNrL01XemZiMExGaklXOUQ5N3VabjNjMXptanc4bW1UejFwdzliYUxqNVNEbFVBUkpraEFFdXlBN284NHNYcHRlQzBNVkJTVkxHSm9XTWJmcDNRcEQ4MEorZ2hTdnZmWTZZOGVNNG9jdFc2aGNwUW9WS2xZa01TR0J3TUJBeG93Yno2ZGgwN0JZTEFRK0ZzaVFvV3Bpc2txVnF2VHJQNENoZ3ovRVpyZFJMcWdjZzRjT3kzY01UOVd0Uzh0V3JYbTNsMW93ZVBhNUpqUnQya3hMaGk1ZHNwaVUxQlJrU1dMZjNyMTA2UnJDUzIzYk1YVDRDRVlNRzRLZnZ6OTZ2VTRySHR6SkdQN0prMTRVNVFJLzZSOGxwS1lrSTRweWpwNUFkYzZrQnpKbnFTbko2RVFwMXo3Rll2NXgzSWoxSnlwU3djdkhMOGZZeXBXdmNNdEY2WjZlWHBRclgrR3VqZmRCbkJNb3NYM0hyc3JQTjI5NmdpdzBSazZqcGhvMkJGbVdIZTRiU0xLZ2RYWTRLNlJDa1VBekZxdGRzRnBsN0pJaXlLcFlpODRXczJDU0tBb2YzTzZvSGlYdTludUJpK2YvSkxoV3dacytMMGRWSmphNWtIdHRiamJjakx4QlFLRWtnaDdMT1plWG95c1RtM1QvNXl5L01UbTl5YWo0UWtUR0IyQ1RqS1Erb3NRdDZlbnBTMXUzYnIwS1ZROGhHVlh3SmQwaCtHSVRCQ1JSRkdTRFhsQ01SaEdUVWErWVBReVl6UVk4elNhOHZFem9uV0dva29YSlExRVVVUlNGNXZjNkNlcEdUbmg3S3JmRjExVW04RHhSOGRXNUdYa0RieDgzTlhocVNqSXBLY25ZYlJtVUNUaVhleW9oOEN4UmNVL2V0emx6amtteXAxTTI4R3krSG5oUi95U0sraWM5NHFJOFNrUGdxeXpocU03QjlxRldSeEVjQytOQmxoVkZWcHQyRlVsUzFBcXBYVUtmclNrWEJZUzR5ek9xY1FkaUxiZWJCSFVqNXcyZzE5bHY2NElXUlptNmxjNXdPYm95Y1lsV0V1TGl5TWg0TkIvMVpyTW5vaWdUVU5oRzJjZk9JUXBLbnRkcHZVb251UkpUbVpnRXl6MmRNN1BaRTUwb0VlQnJvV3pnMlFML3RvOHlPYXNnQ05XM3J1OWJ2SFhIdVg5cHhrMUJoK0RJc1lFZ0tLck5rbVhVYmc1SFpkVDUwcXZVMzluV2hucDdOTG5UUVJYeFRTQWlJY0Z0Mk80QUtTbUpsUFM3ZmNvaVViQlR2c2dmbEMvaWx0KzdIY01SOU5oWmdoNTdPT1QzM0hERlU3V0NxZ0YvT3cyYkFqb2hhd0ZCemJVcGlxTk5UWEtzUm5EMHR5RTZ2RFVCYlcwb2drNFVuN3ZUQVJYemowT1JFb2k2ZVkyMEI3Q083dCtJdE5RVW9pS3VncFJJVWI5L3hzWDJhRC9wSDc0NWN4dTFPNE92cjJjdDFLS0Juc3dDZ2lyMG90R3BLWUtzSURpOE5rR1dsU3dlbTVPZXlPR3hEZXJYVENjSUJQK1RIL0twU3BlNEdlL0R6ZmdBa2hPTnBLUzZmNmk4NE8wRkJwMlZVb0Z4RlBGTmNrK0lHMjRBQnIydVljM3F4UXduL29qVVp3bEhSUVJFQlVVQVFWQVV4eElyeGJHNHdFRzdKc2t5Z3BkWko5anNpaUNwMVZBeCtmcnNwcDVtNDQvdXFYWEREVGNlSkk2ZXVOTG5xZUNKcDhoU0hSVUUwZ0dySUFnMlFVRFdhZFZSSFI0bXZlTGhZY0JzTnFLM1Mxa1d2aXVLY0RNcXNlSDk2b3gzd3cwMzNNalRhelBvS2dOL1pnMUZGUVZSRUZBN09CQ1FaUVc3QklKTlJoRHNhc2dvZ09EaDRTSGE3WFpCVVJSUlVSU2QzVzdmSkFqQzgrNXBkY01OTng0azdIYjdib1BCTURHTHg1WWlDRUlxWUJFRXdTb0lnaVFJZ3F6WDZ4V2owYWc0Nk80VnM5bU02RndYcVNpSzhPcXJyK29FUVdqb25sSTMzSERqUVVPdjE5Y3RXN1pzMXNLQlZrQndPR0t1VkVaWlhvTFJhQlFsU1JJVlJkRWxKaVkyOVBiMjN1V2VVamZjdVA5UUZJWEl5RWh1M294Q2tpUlNVcElmK1RrNWRlcFU3NzU5KzU1MWVteU9WN29nQ0ZiQTd2VGFkRHFkb3Rmck1SZ01pc2xrUXArVnljTER3Nk91Ky9KeXc0MEhZOVNPSFR1R2dxQ3RobkRyOFlLdi8yUGxnUXRrYWZ0d2tFOXFqVFNDSUNBSUFxS29RNi9YbzljYk1oZVdBb0pPcDN2cTMvamxRME5EYWRXcVZZSDJUVWxKUVJBRWR1L2VmY2ZucTFXckZwTW5UNzdUdkFHaG9hRVVLVktFd29VTDA3dDNiMDBFeFkxSEY1R1JrU2dJRkMxV3d0M2NuZ1UrUGo2VnM0V2hvcE40TXF2dFVobUpaTFdmVFpIVkhKdHpuYWdnQ0hYY1UzbHZNV3pZTURadDJzUzZkZXZZdEdrVE8zZnVwRisvZnU2SmVjUVJGUlh0MXVQTkJRYUQ0UWx5NXRpeUdUVUZsVzVOUVZGa0ZGbldkdUQ4K2ZORmdFcnVxYngzU0UxTlpkNjhlWVNGaFJFY0hFeHdjRENMRnk5bTVjcVZ4TVRFdUNmb0VZWWtTWGZrcVowNS9RY0RRL3ZmOC9HZE9YT2FzR2xUMk81UUtuTmk0OFp2Q09ueUZpRmRPN2tJOGtSSFJ6UHdnd0dFZE8xRTN6N3ZjZlhxRlczYmlSUEg2ZmwyTjdxRmRHYjBKeVB6MVdZVkJLSEN5STlIZWVkajNBQUVIQXNObk0yNnpxcW9VS0pFaVNmL0t4ZkpkOTk5Ui8zNjlUR2J6WGg1ZWRHOGVYUE9uWE5sZXZqbGwxK29WcTBhSGg0ZU5HdldqTC8rK2t2Ykpzc3lZOGVPcFhUcDBuaDdlOU9vVVNQMjc5K2Y2N21pbzZONTVaVlg4UFB6dzl2Ym14ZGVlSUVMRnk1bzJ3TURBL253UTVWTjk5aXhZNlNucHhNY25MbXc0OWxubndYZzRNR0Q3cnY3RVVaeThwMnRPcWxjcFNxanhveTlwMk9iUDI4dXk1WXU0ZExGaXk1RzZPclZLMnhZdjU0Rmk1YXliUGxLVHB3NHhwRWo0WTVqNXRDczJmOVkrZmtxdW5WL215bVRKbXFwbUVrVHhqSHlrMUY4dHZKTFNwVXVvMmxtNUlWbm53c3U0VEJrTG9TVGpyQlVXNEdnS2xpcFhwdm0waGtNaHYrRVlmdjk5OTk1NVpWWDZOeTVNMmZPbkdISGpoM2N2SGxUTXk1T3pKbzFpMG1USm5IOCtIRzh2YjFwMXF5WnByd3pjZUpFbGl4WndwSWxTemg1OGlRZE9uU2dlZlBtbk0xRkhEYzBOQlNMeFVKNGVEakhqaDFEbG1WQ1FqTDFCQVlOR3FSeDAxKy9maDJqMFVoQVFFQldWNXVBZ0FDdVhidm12cnZkeUlIWTJGZ0dEUXdscE10YmRBdnB6T0ZENmdQUXFjbmJvWDFiUXZ1N3BqS0dEZmxJOWFLNnZFVzdsMTdnelRkZTFiWmR2SGlSMFA1OTZkNnRDNE0rR01ETm01RXV4Njc3K2l1WC9RRjZ2dE9Mc09rektQLzQ0NjczMm9rVFBGVzNMcDZlS3NtcW42OGZKNDRmMTd5eTU1NVRIK0NGQ3hmbXp6L1BZTFZhaVl5SXdNTnNKaWlvSEFEKy92NmNPSjQvelhueFlzWEtaREZxamp4YjFuQlV5UXhKMVR3YmVsQjcySFE2WGZYL3dvV2dLQXFmZnZvcC9mdXI3bmxRVUJBaElTRXNXTERBWmI5UFB2bUVkdTNhQWJCaXhRcEtsU3JGNXMyYmVlbWxsNWc4ZVRMTGx5L1hDaElEQnc1azE2NWR6Snc1TThmblhMNThtZEtsUzFPeVpFazhQVDFadm55NWk1RWFOaXlUUFRjdExRMFBENDhjWXphWlREbmswOXh3QStDcnRhdXBVYU1HSWQzZTV0S2xTOHlmTjRjR1R6ZlVOSG5QbnovSHhQSGpYSTZaTkdVYW9FcFk5bjZuaDZhL1liVmFtVGgrTE9NblRxWjQ4ZUw4OU9OV0ZzeWZ4K2d4bWNmWHJGVXJoNTVIWHNKRGlZbUplSGg0RUIwZHphUUo0NUJrQ1I5SG5qQXhNUkdUaHdmZmIvcU9IVHUyWVRRYVNVbEpJU2twQ1pQSmhOVnFaZTZjV2R5NGNTT0h3bFoyZUhsN2w4dGkxQndHVGhFZEZBT09YamFjdFFKRlVSUkI3Mnh5RXdUaFAySFlhdGFzU1VCQUFOT25UK2ZNbVRPY1BYdVdYMy85bGFKRlhZdm5qUnRuQ3BVRUJBUlF2bng1L3ZqakQ1NTg4a2xTVTFQcDFxMGJiNytkS1pSc3RWcGRqbkZpeUpBaHZQbm1td1FHQnRLMGFWTmVmUEZGdW5idG11dll6R1p6RGpVaFVDWGRIb1NNbXhzUFArclZiOENjV1RNQWdmcjFHeEEyZlVhQmpwTmxtVEdqUjlIbXhiYlVyOThBVUtVaHIxMzdtK0ZEQjZ2N0tESmUyZlFLS2xXcVRLVktCZGRLUFgzNkQ0Nk5HRXJvQjRNNCsrZWZYTXlTaHBrMFlSd0JnWUdFVFovSnF4MWVkakdJQXo4WXdJc3Z2a1NIanE4eVl0alFmTTloTUJncVpNdXQ1Y3l4b2FDQVZrRFFaMjZnNm4vaFFqaDQ4Q0RObWpXalNaTW1ORzNhbE5kZWU0M3c4SENXTGwyYTcxTklyOWRqTkJvMXJjMDFhOVpRdmJxcnJjL04yMnJidGkzWHIxL24rKysvNThjZmYyVDQ4T0hNbnorZnc0Y1BheTY2RTZWS2xTSWpJNE9FaEFSOGZYMEJzTmxzeE1iR1VySmtTZmRkN0VaT3cxYXZQblBuTFNUOFNEaWZyMXlCMFdoa3pMZ0p0enh1MmRMRmVIaDQwTGxMNWtQV1pyTlJyRmd4VnF6ODRxNk1yWERod21Sa1pCQTJmU2FGQ2hYaXhJbmpGSFpjMTRVTEYrYVpSbzAxa1hOVmh0S2JRb1VLa1pTWXlJU0pVM2o4OGNjNWUvWlA3VjdJQzRJZ1ZNNlNXOHVhWjh0ZVFGQ2NCUVFSRUtLaW9pb0Jwdi9DaGJCZ3dRTHExNi9QMXExYkdUeDRNQzFhdE9ER2pSczVKUFd5NXN1U2s1TzVlUEVpMWF0WHAzejU4cGhNSmk1Y3VFQ0ZDaFcwMTZKRmk5aXlaVXV1SHR2NTgrZnAwcVVMcTFhdFl2LysvWnc2ZFlyampseERWdFN1WFJ1ejJjemV2Wm1xNS92MjdVTVVSWjUrK21uM1hleEdEa3lhT0o3d0krRTBiLzQ4dzRhUDVOQ2hneTdTa2JsaDM3Njk3Tm05bStFalBuYVJxcXhRb1FKSlNVbjhzbWUzbG0vN2ZwT3JUdWo1OCtmWXVQR2JBbzN0eVJvMXNGcXRtRXdtSkVuaTRJSDkxS3lsYXBuV3FsVmJHK2ZSMzM0aktLZ2NScU9SWXNXTFU5alhGNVBEc2RpM2Q2OTJURDR3TFY2NlBEQVhnNmIxc0FGWlBEWkhqczNiMjd2Q2YrVkM4UGYzWitmT25adzhlWktTSlV1eWJ0MDZsaTFiaHArZm44dCs0OGVQcDNIanh2ajYraElhR2tyRmloVnAzYm8xb2lneWNPQkF4bzRkUzdGaXhXallzQ0hyMTY5bnhvd1piTisrUGNmNXpwNDlTNTgrZlpnM2J4NEJBUUdzV0xFQ2IyOXZxbFJSQlcwbVRacEU3ZHExYWRXcUZWNWVYdlRvMFlPK2Zmdmk0K09EMFdpa2QrL2VkTy9lL2I0cG5ydng3MExuemwwSkM1dkMydFdya0dXWmdZTStRaFRGZkJYZTVzNmVTVVpHQm4zZTY2Vjl6dHo1aS9EeDhXSFNsR25NL0hRNml4WXV3TlBUazRIWmhKU1BIVDNLaGczcmFkKyt3eTNIVnJac0VPM2JkK0RkWGoxUlVIajIyV0JOTy9qOVB2MllQR2tDNjc3K0NyUFp6SkJodzdYSWFQaUlqeGszYmpRMnE0M1NwVXN6ZU9qd1c1NnJRb1dLandIWGMvZllIRG9oR3Jla2dpQ0tvb2ZGWWhtZzErc24vMXQvL05EUVVQNzg4MDkrL1BGSFltTmo2ZG16Snp0MzdzUmdNUERVVTAveHhodHYwTE5uVDY1ZXZZcXZyeTgrUGo1TW5UcVZsU3RYOHZmZmY5T29VU01XTGx4SUdZYzZ2YzFtWTh5WU1YejIyV2ZFeE1SUXVYSmxQdm5rRXpwMDZPQjRHdFhpalRmZVlPalFvVVJIUnpOZ3dBQzJiOTlPY25JeWRlclVZZHEwYVRScTFBaFEyejI2ZGV0R1dGaVlsazhMRFExbHpabzFpS0pJeDQ0ZG1UMTdkcTVocmh1UER2YnMyZU5XZDhzSGNiR3hZVFZyVk4rT3VtYlUrVW9UQkNFRFZTbGVFa1ZSWFROcU1DaUNJQWhtdTkwK1V4VEZYdTdwYzhNTnQyRjdHSkdSbnI2NllvVnlYMlF6YktsWkRKdGRFRVJacHhNVnZjR2c2QjNKdWFCLzZ4ZWVOMjhlZmZ2Mi9VLzhlSkdSa1RtcXR3V0JMTXRjdm5LVnVMZzRKRWtpSS8zUlhIdnFZVGFqMCtudzl3c2dLS2dNK1JHbUtvcmltTE5ZN1BaN04yY2Vaak42dlo0QS93Q0Nnc3JlY3YvcjduN0dYQ0ZKVW5FY1RibFpYNHFpcGhIVjdnNFpXUWJKTGlFSWd1QXB5L0pSb0xKNyt2NTlzRmlzbkRoeEFwMWU3OVlWQlZKVFUwaEpUa0tTN05TcVdRdWowWkNyVWZ2MTE5OFFkYnI3b3l2cUdKTXNTOVNyVzlmdHNkMEJGRVU1WDZaVThRL0lKSjEwRWs4NnFjTHRqbkJVMGV2MWlyTzZVTlk5ZGY5T1JFUkdvTlByM2F3UURuaDVlVk8wV0FsME9qMDNJbTdrdXM5Zmw2OGc2blQzYmM2Y1l4SkZIVmV1WEhWZnRIY0FRUkJLWi9QWXN2L2ZwVmxYUEgvK2ZGSEFuYm4rbHlJMkp0Yk5DcEVMdkgwS0VSTVRtK3UydUxpNEJ6Sm4zajZGaUlsMWt4M2NhVlQveWVneDN1VFo4dUhVYlFGUTBBY0VCQlIzejltLzFUMi9jMWFJUjhGelMweUl5M1hiZzVvekx5OXZFdVBqN3VqWXBNUkVMdjkxa2RSOHRIcTl2THdKS3ZjNGhRb1hmbUR6ZmkvSFdiVkt0VUpBVEM3ZW12T093Tm42b1RlWlRFWHYvZzNucGpndUtIeDhDcUhUaVJRcFVvVGl4Vy92R1NNSWtKN3VKcW5NQytsNUVIZyt5T0xLbmY1ZWYxMjZRRnBhL2dLOXFha3BYUDdySWpWcVBUaGF4WHM1enNlS0ZDbVUzVnR6RkE4RTEzQlVVZlE2bmU2dUdqWTN4Zkh0SXpVMWhSc1JrZHlJaU9DcE9uZjNvdHkrN1NkV0xGOUdWTlJOU3BRb3dlZGZydUg4K1hQMDZKN0pRREowK0VoZWVLR04rNGQ0aUhFclk1SDFXdnF2anJOUW9VS0ZzaGd3WjNPdTRGZ01ueldTRWZTaUtENTJONzlZVm9wak53b2Vvbmg1ZVJOMU00TEl5RWlLRlN0MlZ6NzN4bzBiVEprOGtUSGpKbEN2WG4yU2tsVE9yd29WS3JKOTUyNEEzdW5SN1o1OXIrWExsdEx1NWZZdU5FM09jYzJhTVoyVEozL0hhRFRTL1BtV3ZQZCtINDFWNHRxMWE4eVpOWU9UcDA1aU1wbG8wYUlsdlhxL2w0TjE0bTdpVnVkOHJuRkREQVlEZ2lBU0ZCUkU5eDQ5YWRTb3Nmdml2WTh3bXowS3VScTFIQy9IMGtrRlVSVEZnTHQ1Y2pmRjhUOHdjTjQrM0x4NTg2NTkzdGsvenhBWUdFaWpSbzB4R28wRUJnWTZRbGdCaHdhankxckN1NDNQVml3akxpNW5Bbi8wcUpHVUxGV0tqZDl0WnZHUzVSdzZlSUJObTc0RjFKNjhvWU0vcEZMbEtuejczV2JtekpuUG50MjcyTGpobTNzMnpvS2VjKzc4aFh5L1pTdmRlL1FrYk9wa05ueXovb0ZjSjRJZ1VLZE9IVnEwYUVIejVzMHBVZUwybklpREIvWVQwclVUcjcvNkNqTStEZFBXZE5wc05zS21UU0dreTF0MEQrbkN2bjJaYTVyelk4UzlkNUZNS3B1Kys1YWhnMVhhSllQQm1OMWpFM0VVRHJKR2pGYXJGVkVRaEx1NlNOR2R6UDVubnB0ZGt2N3g1eHcvZm95WDI3WmgydFRKUkVaRzhuTGJOcnpjdGcwclAxdFI0TTg0ZFBBQW5kOTZnK2JOZ3VuUlBZUS9UcDF5MmI1bjl5NUN1bmJpaFZiUDA3MWJGdzRkUEtCdDY5ZjNmVjV1cTRhMkh3em96OHR0MjlEMy9YYzFJeEljM0pTdUlkMHdtVXdVS1ZxVXV2WHFjYzVCU2hBWEY0ZWZ2eitkT25mR2FEUlNxblJwR2pWK2xqLy9QSFBQNXYxMnptazJtMm5VcURIRGhvOWt5ZUtGK2RKYTN5dVVLMWNPblU3SHRtM2IyTHQzTHpWcjFzUmdNT1Jxc0xQL1B5TWpuYWxUSmpGeDBsUldyMTFIVEV3TTIzNzZFWUN0UDJ3aEl5T0RsVitzWnZxTVdjeWUrYW5tNWVmRmlGc1FQUEhFRTlwRE5iOHhadjA3SmlhR2QzcDA1OEtGODlydm9OZnJDK1hocVdrdldaYlI2L1dJZ04vZG5QUTdwVGgyUTBWSzhqOHZ0TlNxVlp0dk4yMWhRT2hBU3BZc3hiZWJ0dkR0cGkyRWRPdGVvT012WHJqQXh5T0g4MTZmdm16WnVvMVhYM3VkZ1IvMEo4cmhUVjY1Y3BueDQ4WXdaTWh3TnYvd0V5KzkxSTRSSTRacGFsdHo1czduMjAwcUU4cU1XYlA1ZHRNVzVzNWZDSUFvaW5UcTNBVmZYL1d5Kyt1dlN4ell2NThHRFZSMms4REFRT2JNblkrSGgxbTcwSThmTzBhTkdqWHYyWnpmeVRucjFXK0ExV2JURFBMOVJHQmdJRGR1M05BOEZGbVdjNUE4SERrU3pvaGhRN0RaYk1URnhmRk9qKzdFeGNVUkVSR0JyNjhmSlV1V1JLZlQwYXBWYXc0ZlBnU29yTGZQUHZ1YzQzY1NNQmlNbkR0M1Z0dVdHeU51QVhOamVaSlZMbHU2V0tNR1Azbnlkd2IwVjFjUitmcjY4c1dxTllSMHkrUkUxT2wwdVlXaXVJYWhxa2NyQXZjOGJodzBNSlRYT3JZbnBNdGJkTzM4SmxzMmY2L2xOVjdyK0FydDI3MUk5NUF1OTNRTTNVTzZjUEhpeGRzNlp0dFBQL0pheDFkNG9YVUxacy9LbStCdng0N3RqUDVrNUgvR3VHN2V2SW5nSmsxcDFLZ3hKcE9KVnExZm9FclZxbXh6Q0hrRUJBU3lmTVhuVkt0ZUhWRVVlYWx0TzJ4V0szLy9YZkRtMHo5T25hSkY4NmFFZE9sRWxhcFZlUzY0U2E3N0xWd3dEN09uSnkrMGVmRytmZitDbkZNUUJQejkvRWw1QU1sNm85R0lKRWtFQkFUUXVIRmpaRm5HWkhKbEhhdGJ0eDcrQVFHTUdmMEpnd1lPb1AwckhmRDM5NmRZc2VMRXhjVnk2ZElsN0hZNzRlSGhwS1NvMzhISmlIdnExRW1HRFA0UW84bElZbUtpdHMzSmlEdDN6aXlORWZlZjRzMjNPck5yMTgvTW1UMlRjV05HMDdkZmY2ZDNsaU9uS2dpQ2R6NjVOUmNqcHhjRTRiNGt4RDRhTW94NjllcVRtSmhBcjU1dlU3V2F5bjJXRjczeHc0QVdMVnZSb21VcnZ2NXFMWkdSRVkrTTF4Z1pHVW1GQ2hWZDNpdFRKa2liQTI5dmIzWnMzOGI0OFdOSVNVN0pNNnpJRDlXZmVJSnRPM1lSRVJIQkp4K1BZUG15cGZSOHg1V0g0Zk9WbjNINDhDSG16Sm1QWHErL0w5KzlvT2RVRklXNCtEaThIMURheFJtTzd0Ky9uMXE1OEprSmdrRHYzdS94eHVzZEtWT21yR2FreldZenc0YVBaUEtrOFk3ZnRTeWVack4yM1BmZmJ5STlQWjFKazZjeGUrYW5McCtaRnlOdVhtalZxaFhlM3VyOE9QT0FwMCtmNXZUcDA5byszdDdldlB2dSszd1EybzgzMytwRTVjcDVMeXNUUmRFN256QlVNMm8ybXcwOWNGODVxUXNYOXVXSkoydHc4Y0o1eXBjdm4rZCtpWWtKVEo0NGdhaW9LT3gyRzUwNmQ5WFlPTStjT2MzTUdkTkpUMHVqU05GaURCMDJRb3ZoZCsvNm1kV3J2OFNTa1VIMUo1N2tnNEVmYXZtSFhUL3ZaT3pvajBsUHo2QnZ2LzZhbC9ETituVnE4bHBSYU5yc2YzVHIzdU9XMytQSWtYQStuVDROVVJBcFdhb2tudVpNdHR6OHh0QzhXVEQ5QjN6QTBpV0xVQlQ0ZnN2V2g4NndGUzFhbE1nSVYwTisvZm8xYXRkV1cxSDIvcktIVlY5K3dZeVpzeWxWdWpTZ1ZnMXp1N215R3p0TFJnWmJ0LzVBNjlZdllQTHdvSGp4NGpSdi9yeExvaHBnL2JxdjJiN3RKMmJObVhmZkdrNXY1NXhIam9Sak5CaW9WUG4rTDdHMldxM1k3WGFPSGozcURORnlhR2FrcEtUdzBVY0Q2ZHdsaEhObi8yVEs1SWtNSFRZQ1FSQjR1dUV6UE4zd0dVZnViSzVHUzErNGNHSE1aak5qeDAxQUZFVXNGZ3VGSGZPUUZ5TnVmdmp4UnpWMzk4d3p6M0Q1OG1VdGZIWkplMXk4eU9SSkV4ZzdiZ0lybGkrbFROa2cydVR0S1h0bE0ySlpqSnFRTldSRkJEenY1NDhTSFJYRnFaTy9VNlZLL2t6azU4NmQ0My9ObjJmWmlwWE1tYnVBV1RObllMVmFzVnF0alBwNEJLR2hBL244eXpVODgwd2psaXhhb09WcjFxOWZ4NXc1ODFuNXhXb0FmdGl5V2Z0TUh4OGZWbjZ4bXRGanhqRi8vbHhBSmRiYnZIa1RDeGN0WmNteXovanR0OS9ZOWZQT2ZNZG1zVmlZTUc0TW8wYVA1Y3ZWYXlsVE9uT3A3YTNHWUxmYmlZcUtZdDAzMzdMK200MFB6SGpaN1hhTkJsMlJaZXgydTJhRTJyejRFbnYyN0NJOC9EQjJ1NTJmZjk3Skg2ZE8wcUpsUzBCVlRoSUVFSFU2RWhNVHRCeUpJcnV5RkJjdlVZSkRCdzlpczltSWpvcFN3eWlUaVM4K1g4bmF0YXV4Mld3a0pNU3pkKzh2MUtoUkl6TVUvbjRUMzI3Y3dNelpjKzhiQVdkQnoybXhXRGg4NkNDVEowMmc5N3Z2UHhDdGl1am9hSzJhYlRRYThmWDF6U0dJY3ZIaUJZS0RtL0pXcDg2TS9HUTBScU9KdURqWFZRK0hEeDFreDQ1dFdnOWpyVnExc2R2dGlLSklRa0k4Rnk5ZXBGTEZTdHEyM0JoeC95bCsrL1VJUTRlTm9FblRabnc2Y3c2bi96aVZnKzA2eTRQU25NMm9aWDJHdXBUMzlZRDVmdndZMDhPbVl2WXdZelFaNmRPM1A2VWRwSTU1b1diTldpeGZ0b1J0MjM1RXA5TmpzV1NRbkp4TVFudzgzdDQrVksybTZoRzgxTFlkVFpzMVUzK293NGU0OXZkVjN1MzlUcGFuaWs5bTNzSEI3bG0xV2pVdEVSNGVmb2pubjIrSjJlR092L2ppUzRTSEg2WnBzLy9sT2JhclY2L3cyR05GTk9OY3BWbzE5djJ5cDBCakFPZ2EwdTJ1WEJSM2l1d051bE1tVDJUSzVJazgzNklsSDM4eW1vb1ZLekZxekRnV3pKOUxaRVFFWmNxV1pXcllweFF0cXZiWHRYNmhEYWRPbmFSSDk2N29kSHJhdmR5ZWloVXJFWmR0dWREQWdSOFNObTJxK2lRdVU0YWx5MWRpTkJxWk1qV011WE5tOGRYYU5SZ01Cb0tiTk5XU3hKSWtNVzNxWlBSNlBXKyszdEUxVFB4eU5jV0szZjBWZ0FVOVo3OCs3eU1JS25Qc29BOEgzOVUrTmg4Zm53THZlL255Wlh4OWZUVlp4K1BIaitkSTVOZXNXWXVhTld0cCthcEJXWmh5NCtQajZOV3pCNlZMbDJiaXBDazhWcVNJOXJ1ZU8zZE95M2YzR3hDcWVhNTVNZUlXQkFjT0hNaHoyMnV2djZIOVB5QWdnSThHNXl2czRwRlBHT3F3Y0NLU0pLRUg3c3NkTnVqRHdScHRjRUd3ZHMwcTdEWTdVNlpPUnhBRXJYM0FhWjZkTUJnTStQbXBUMWliMVVad2s2YWEzRmgraWQrc0lWTFdWaTVCRVBKOFltVE5yK1RWTEZxUU1lVEhFM1kzMGJKVmExcTJhcDNqL1lvVksvSEx2dndGbWhzMWFwem5qV3N5bVJqNThTaVg5OTdwMVR2SGZ2VWJQTTNYNnpma2VMOUN4WXJNbkQwMzE4L1c2WFRzMlh2Z3ZocjZncHp6VnZOMU44YVFtcHBTb0ZZcDUrcWVPNFdmbnovcmNva1dzaHZBckFnTURDeXdRdGJkUkVCQWdGWmh6OFZXQ2RudGdTQ0FUcWUyZXp3NDF5RmZkenNHYng5dkJFSGcrUEZqSkNVbG9TZ0twY3VVSVRrNW1mUG5WV1gzbjM3Y1N0aTBLUURVcTErZm4zZnU1UHIxNndEczNMbURreWQvei9jOGRldldaL3UyYmFTbnAyT3oyZmhoeTJicU44aGZXS1ZzMlNDaW82TzFuRUZFbHR6Qm5ZemhuOEJzOXNTTjI1dWJCemxuZVoyN1NKRWlwT1d6cHRyVHMyQWhiMEgzdTFlNHgrTTA1dUtwQ1VvMnI4MFppdW9lMUNUa0owangybXV2TTNiTUtIN1lzb1hLVmFwUW9XSkZFaE1TQ0F3TVpNeTQ4WHdhTmcyTHhVTGdZNEVNR1RwQ0RRbXJWS1ZmL3dFTUhmd2hOcnVOY2tIbEdEeDBXTDVqZUtwdVhWcTJhczI3dmRTQ3diUFBOYUZwVXpXMDNmYlRqeXhkc3BpVTFCUmtTV0xmM3IxMDZSckNTMjNiTVhUNENFWU1HNEtmdno5NnZVNHJIdHpKR083WGsvNVJRbXBLY3A1ZXRmaUE1aXcxSlJsZEhwWFc0c1dMRXhFUlFmVE5DRHk5ZlhLTXJWejVDcmRjWU83cDZVVzU4ZzlXbCtrZWoxT1htOGVXVzk1TlVHNFZkOTBtamg0OVNwRmlibzNNTzBWVTVIWHEzTVpDK011WHJ4QWJGK2RlbTVzTk55TnZFQmdZUU5sY2NybVhyMXdoTnZiK3o5bk55QnNFQkFRUVZEYnYvSEpVVkJTUmtaSFk3WGFTazkyc09OblJwRW1UOWtBYUtvTnVFcG1pTGhZMDdRTkJ2dXZOUVc0UDRwODkwVyszWDZ0czJUSkVSMGNqbnRxUEFBQWdBRWxFUVZSeE0vS0dteHJjTVljcEtjbElkbnV1UmcwZ3FHeFpvcUx1MzV3NXh5UkxVcjVHelJtU0ZuRWs4OTNJRmRuRFRzR1pZeE1FUVYxMUlJcUNvQ2lLaExwRTRhNGdJaUtDeU1pYlBGYlV6Vjk1Kzk3YURVcVVLSDdiN0I2S292RFg1U3ZFeGNVaTJXVXlNaDVOamphejJSTlJweU13SUpDeVpVdmZjb0gvbFN0WGlZNkpRYkpMOTJ6T3pHWlBkSHI5TFQwMU53b0VXUkNFamtCcUZvOHRSUkNFVk1BaUNJSk5FQVM3S0lxS29DaUtGVERjN1hCVUVIVjRlWG5qNmZiY2JvazBoOWdIS0xjVmhycmh4aU1HbXlBSXIyY3hiTWxBc2lBSWFVQkdWc09tQis2NllhdFRwdzQzYjk3a1psUVV5VW1KYmdiZGZPRHQ0NE5CcjZkVXFaTHVFTVFOTi9LSHRhQTdPZzNiWGE4UkZ5MWE5STQwTXQxd3d3MDNDbWpZbEd6L2FoQ0JkUGQ4dWVHR0cvOENaQlRZWTd0eTVZcGJEY1FOTjl4NDZLRW9Tbm91M3BtaU9CUmRuSjFyaXFJbytySmx5NmE2cDh3Tk45eDQyQ0hMY21xVzBGTXpjTm5iUFFSQlFGUVV4VTE1NjRZYmJ2d2JQTGJzekpaS3R2OXJmK3RSZTBIY2NNT05CMy9qdXZWNDg0SGRiay9KWXNDVUxLR29ralVVbFdVWnZhSW9DZmRTcWNnTk45d29tRkZ6Ni9IbUQ0dkZrcHlieCtacXZ6SkQwYmhIYllKS2xTcEZ0V3JWY2hXajhQYjI1clBQUHJ1WFR4MUNRME1wVXFRSWhRc1hwbmZ2M3BvSWlodVBMckxxOGJxWEkrWjU3eVJsODlnVWNtbjFBTld3eFQ2S2szVG16QmttVFpwMDM4ODdiTmd3Tm0zYXhMcDE2OWkwYVJNN2QrNmtYNzkrN3F2MkVZZGJqL2ZXc0ZxdFNWazh0Vnp5YTRMS3JTaUFLRWxTektNNFNlWEtsV1BpeEltY09YUG12cDB6TlRXVmVmUG1FUllXUm5Cd01NSEJ3U3hldkppVksxY1NFeFBqdm5JZllkeXBIdStaMDM4d01MVC9mWEFFVGhNMmJRcmJIVXBsVG16YytBMGhYZDRpcEdzbmxpOWJxcjJmbjhEeWlSUEg2ZmwyTjdxRmRHYjBKeU1Mck0yYW5wNldqR3RUYnJicXFOTmZBOUZtczkxOEZDK2szcjE3VTcxNmRkNTU1NTE4R1hQWHIxL1BrMDgraWRsc3BucjE2aXhkdXRSbCs4OC8vMHlkT25Vd204M1VxMWVQc0xBd0YzSFl3TUJBUHZ4UVpkTTlkdXdZNmVucEJBY0hhOXVmZmZaWkFBNGVQT2krdXg5aDNLa2ViK1VxVlJrMVp1dzlIZHY4ZVhOWnRuUUpseTVlZERGQ1Y2OWVZY1A2OVN4WXRKUmx5MWR5NHNReGpod0pkeHlUdThDeTNXNW4wb1J4alB4a0ZKK3QvSkpTcGN0b21obTNRbEppVWw2aGFJNFZDUHFNakl5b1c2bk4vQmVoMCtsWXNtUUpEUm8wWU5HaVJiejc3cnM1OXRtN2R5K2RPblZpOXV6WnRHclZpcU5IajlLN2QyOTBPaDNkdTNmbjRzV0x0R25UaHI1OSs3Sm16UnArK2VVWCt2ZnY3eUx3TVdqUUlHclhyZzNBOWV2WE1ScU5CQVFFYU5zTkJnTUJBUUZjdTNiTmZYZTdrUU94c2JGTW5EQ09tT2dvQkZIa3ZmZjYwT0RwaGk0a3JiNitmcXhZK1VWbXVtUElSOXk0b1RJNEp5UWs0T25seFpxMTZ3QlZGV3JPckJra0ppWGk3K2ZQNEtIRE5DMExnSFZmZjhXR0RldTEvUUY2dnRNTG85R29NVlU3OGZ1SkV6eFZ0eTZlbnA3WTdYYjhmUDA0Y2Z3NDllclY1OFNKNDN6d3dTREFWV0E1NnVaTlBNeG1nb0xLQWVEdjc4L1BPM2NVYUM1dVJ0MU1Mb0JSVXdEMEVSRVJFWG5Kei8vWDhkUlRUeEVhR3NxUUlVTm8yN2F0cG4zb3hJUUpFK2pSb3dlOWU2dDgvbVhMbHVYU3BVdE1uVHFWN3QyN3MzRGhRcXBVcWNLMGFkUFVwMmZseXB3OGVaTFZxMWU3NU5TY1NFdEx3OFBESThjNFRDWlREdmswTjl3QStHcnRhbXJVcUVGSXQ3ZTVkT2tTOCtmTm9jSFREU2xWcWxTZW1yeVRwcWpYWTJKaUFyM2Y2YUhwYjFpdFZpYU9IOHY0aVpNcFhydzRQLzI0bFFYejV6RjZUT2J4Tld2VnlzRThuSmZ3a0ZOZ09UbzZta2tUeGlISkVqNk9QR0ZXZ2VVZE83WnBBc3RKU1VtWVRDYXNWaXR6NTh6aXhvMGJPUlMyOGc2N1R6dmJQZVM4UERWbk1DcldyRmt6aXR0WWcvVmZ3OWl4WXdrSUNLQnYzNzQ1dHAwNmRZcGx5NWJoN2UydHZVYU9ITW1GQ3hlUUpJbmp4NDlUdjc2clFFMmpSbzN5UEpmWmJNNjFFbXV4V0I2SWpKc2JEei9xMVcvQXpoM2JXZm5aQ2l3WkdRVVdWSkZsbVRHalI5SG14YmJVcjk4QVVLVWhyMTM3bStGREI5TTlwQXVyVjMrcHFiVTVVYWxTWlY3cDBMSEE0enQ5K2c5R2poaktPNzNmcFdsVFYyVzNTUlBHY2ZueVg0Uk5uK21pVkorWW1NakFEd1pRclZwMSt2VWZVTkJUWlV3WVB6WXRuMUJVTTNTQ29EYm9BbHdGS2oyS0Y0Nm5weWNMRnk2a1pjdVdiTnpvcXR4anQ5c1pOR2dRYjcvOWRvN2pSRkZFcjlmbkVBVE9MMTlYcWxRcE1qSXlTRWhJd05mWEYxQlZxMk5qWXlsWjBrMm43a1l1aHExZWZlYk9XMGo0a1hBK1g3a0NvOUhJbUhFVGJubmNzcVdMOGZEd29IT1hydHA3TnB1TllzV0t1WVN0L3dTRkN4Y21JeU9Ec09rektWU29FQ2RPSEtldzQ3ck9TMkM1VUtGQ0pDVW1NbUhpRkI1Ly9ISE9udjFUdXhmeWc2SW9mMmN6WUhJV3p5M24vYWtlbzF4NWxDK2VGaTFhMExselovcjI3YXVKQ0FOVXExYU5reWRQVXFGQ0JlMTE0TUFCWnMyYWhTQUlQUG5ra3h3NWNzVGxzN0wvblJXMWE5ZkdiRGF6ZDIrbTZ2bStmZnNRUlpHbm4zN2FmUmU3a1FPVEpvNG4vRWc0elpzL3o3RGhJemwwNkdDT2gybDI3TnUzbHoyN2R6Tjh4TWN1emFzVktsUWdLU21KWC9iczF2SnQzMi82enVYWTgrZlBzWEhqTndVYTI1TTFhbUMxV2pHWlRFaVN4TUVEKzZsWlM5VXl6VXRndVZqeDRoVDI5Y1hrQ0cvMzdkMnJIWk1mSkVtNm5zMm81ZUd0cWYwZWVrQ1JaZmx5WG9vK2p3cG16SmhCMWFwVlhYSmRRNFlNb1ZXclZvd2VQWnBPblRweCt2UnArdmZ2ejhDQkF3SG8yN2N2czJmUFp1alFvYno5OXRzY09uU0lCUXNXdUlTVmt5Wk5vbmJ0MnJScTFRb3ZMeTk2OU9oQjM3NTk4Zkh4d1dnMDBydDNiN3AzNzM3ZkZNL2QrSGVoYytldWhJVk5ZZTNxVmNpeXpNQkJIeUdLWXI0S2IzTm56eVFqSTRNKzcvWFNQbWZ1L0VYNCtQZ3dhY28wWm40Nm5VVUxGK0RwNmNuQWJEcWl4NDRlWmNPRzliUnYzK0dXWXl0Yk5vajI3VHZ3YnErZUtDZzgrMnl3cGgyY2w4Q3lYcTluK0lpUEdUZHVORGFyamRLbFN6TjQ2SzNGbDIwMlcwUXVIcHVpcXUrNWVtMkNnQ0lJZ3VDZGtaSFJ6MmcwVG54VUxwWlNwVW9SR2hxcXRXRTQ4Y1VYWDlDMWExZFdyRmhCdDI3ZEFGaTFhaFVUSjA3a3dvVUxGQ3RXako0OWV6Snk1RWp0U2ZqVFR6OHhjT0JBTGx5NFFPM2F0V25Rb0FIcjE2L1hkRVVEQXdQcDFxMGJZV0ZoV2o0dE5EU1VOV3ZXSUlvaUhUdDJaUGJzMmJrV0ZkeDRkTEJueng0ZXIxakZQUkY1SUNZbTV0UGFOWi80QlZkYThCUW5MVGdxYmJna2lxS3MwK2xWdzVhVWxOVEcyOXQ3clh2NmJnOG5UNTRrTFMyTkJnMGFhTytOSGoyYUgzNzRnZkR3OFBzMkRsbVd1WHpsS25GeGNVaVNSRWI2bzdsRXk4TnNScWZUNGU4WFFGQlFHVVJSekM5bjQ1aXpXT3oyZXpkbkhtWXplcjJlQVA4QWdvTEt1ZzNiSGVMc24zOE9hLzYvSnFmSktlU1NEamlrOTBSSkZFVlpyOWNwZWtDSmo0Ky8rQ2owc2ttU2ROdnlkdm5kR0JjdVhLQkxseTZzV3JXS1dyVnFjZkxrU2ViTm04Znc0Y1BKalBjTGpzakl5TnVtVTdkWXJKdzRjUUtkWG85UG9jSnUrYjNVRk9JVDRvbjVMWnBhTld0aE5CcHkvZTErL2ZVM1JKME9iNTk3UDJlcHFTbkV4c1VSSFJOTnZicDE4OTMzdXJ1Zk1WY2NPbmdnbHN5Q2dWWTRjREI3T0VKUkJRVUZXWllSQkVId0JJei9aKys3NDV1by8vK2ZkNWRjZHZlbXBZVzJERkdnU0FFL0ZCRi9iQnpnRjBVRlJDeFlrQ0VnSWhRVUZHUXJRZ3NpVTVDbElHSlpBckpreWlwWWhteVEwcWJwYnRPUmNYZS9QMjQwU2ROU29HWG05WGhFYWRaZDNuZjN2TmQ4UGxtVzFRTlF1SmJ3N216cTFLbFl2SGd4MHRMU0VCd2NqTGk0T0l3ZVBicFNiNkU2N1dHSi96N3F4b3NUZXlFc3RMeVhkTzM2RGVUbTVqNFV3V1FmYjIrRU9wSGhjM2xzbGQrL1Eyb0Y5QUV2WTJCMENFVkxBSmdGb1dTR0pFbE9KcE54Sk1xU2NSZGM2M2YzRmg4ZmordlhyOE5rTXVIcTFhc1lNMmJNQXdNMUFNak95bllOVHpzeHJjNE5XVm5PK1IxeWNuSWV5cHBwZFc3SXluYk5CTjlEcXVVaTdGczhiRnM5N0lzSElvT3U2SjJ6TEh2ZXRZU1BsM0hjdlE5UFArbW0wV2pCc2t5RktZbUhzV1lhalJhTWxYRWRuTHMwaThWeUZlWDcxeHo3MkhoMkR3SGNaQVJCY0J6SGNWYXI5WHhGb3hNdWV6U05JSUNTRWhlWFcwVldVZ0hQM2NNc3J0enI4U3JJejhlTjYxZFJWR1NzRkRqRDZvVER6ZDM5b2YyK210aFBvN0h3cGhOQUsrZXhFVVNaN29GTWZLR2twT1NjQzlncTg0NTQybWFEd1FBcnc4QllXRDIwelZxdERqSzVESDYrdmdnTURLejIvZDYxY3dlV0wxc0tneUVEUVVGQldMbHFMUzVmdm9UWS92Mms5NHlObjRDdVhidTVEdklqYk5ldlhVRnhjZVgwUGtWRlJ0eTRmaFdObXpaN292Yno5dTIwMnc2QXhnQmdoYUpCdVljSWJBREFYYnAwNlZ4MGRMVHJES29BMUpLVGt3R0NoRWFyNDJtYkE2cnYrNHVLakVqWFp5QTlQUjNObWxYZlNabVdsb1laMDZmaXk4bGZJenE2QlFvS2VHcWNpSWhJN05xOUR3QXdNUGI5R2x1M1pVdVg0UFh1UGV6WVRNVDltanZuRzZTay9BT2FwdEcrUXljTS9taUlOSHlkbXBxS2hMbHprSEkyQlFxRkFoMDdkc0tIY1lOUmswM2tkOXJtaXpFdlFDNlhneUJJaElXRm9YL3NBTFJ1SGZQQXpzRTdnWVh0dWZRd3JTYjJjLysrdlJrQ2FERWlxTmw3YmJ5WEJsdVZLaEhsV3Jac21RWGdzZ3ZHeXB0ZXJ3Y0lFbjcrZ1RXU205Rm90UER6RHdSQUlEMDl2ZHErOStLL0YrRGo0NFBXcldOQTA3VEVFMGNRQkJRS0JSUUtCV3BTNytMSDVVdVJrMU0rZ1Q5cDRnVFVDZzdHYjc5dndhTEZ5M0QweUdFa0pXMEN3UGZralIwekd2WHFOOENtMzdjZ0lXRUI5dS9iaTk4Mi9scGorMW5WYlNZdVdJak5XN2VqZit3QXpKNDVIUnQvM2VDNk9HcmVxYmd5YzhhMGtzckNVSUlRUWxIWWg2SVEzc0N5TEh1S0pNbEkxM0xhbThHUUNZMVdWK1BiMGVqY1lEQVk3anNrUFgwNkdaTyttQUN6Mll6aTRtSjBmNDBQTTN1ODBSUDkzdTlmcGU4NGV1UXdFaFBtUWE5UFIyaG9HRVo5OGlrYVBmdXM3VjBVeTVZdFFhYkJBUCtBQU1URkRVYXJGLzRIQUJnMjlDUGNFaGhUUjM0OEhESVpoZURnRUNRdVdBaVdaZEcyYlR0MGUrVVZLQlFLK1BuN28zbDBOQzVkdkFpQXIxaDZlbm1oZDU4K29Ha2F3U0VoYUIzVEJ2LytXM05GKzd2WnBrcWxRdXZXTVpESFQ4REVMeWFnVStjdWp4MHpDOE13T0hyMENMWWsvWTRoUTRjak9DUUVBRDhrUC9lN2I1SHl6eG1RSklYWWdSOGlKb1luUXMzTXpNUzBxVk9RblowRm5jNE5ZejRiaTlxMVF5dmREa0VRaUlxS2dvK1BEMWlXeGZuejU1R1dsbGJsL1Z6eDQzTDhzWDNiT1dkaEtBQ1c0emkyTEw5R2NDQUl2a0c5b0VBcUhnQUFaN0ZZa2hVS1JTOFhsTm1ibGJFK2tDcWFScU9Gc1NEdnZyK25hZE1vYkVyYWloMS9iTWZLRlQ5aTlkcWY3K3J6VjY5Y3dlY1Q0akhwcXlsbzNqd2FlL2ZzeHFpUncvSFRxclh3OC9mSHpaczNNR1h5bDVnN2J6NGFOR3lJVGI5dHhQang0N0I1eTNhbzFXb2tKQzZRd3JjNWMrY2hNcktNT0lZa1NmVHUwN2NzSjNQOUdnNGZPb1NQaHZDMFVUNCtQdExuUlcvcWRISXlYdS9lbzhiVy9WNjJHZDJpSmN3V0N5NWR2SWlvWnMwZXlmT1daVm1wOWNqMjN4OFBINHBhdFdyaHdvWHpZR3dxeDl1M2JVVnBhU2xXL0xRR09UazVHUFJoTEJvM2JnSTNOemVKRWZlVlYxL0RpUlBITVdQYVZNei8vb2RLdDErblRoMVFGSVdkTzNkQ29WRGc1WmRmUm1abUppd1dpOU45dFAwN0plVWY3TiszRjdObWYzdmxoVmJSam1Hb2JZNE5Jb0FSQWpocnRWcSszVU5Nd2hVVUZKeHl3Vmg1cTY1Q1FaVzJaVFErOU4rN1pVc1MycjdVRHExYngwQ2hVS0J6bDY1bzBMQWhkZ3A4OTk3ZVBsaTJmQ1dlYWRRSUpFbmkxZGRlaDhWc3hxMWIvMVY1RytmT25rWEg5dTNRcjI5dk5HallFQysyZmNucCt4WitQeDhxdFJwZHU3M3l3SDUvVmJaSkVBUzhQTDFnTERJK2t1ZnM4ZVBITUg3Y1o3QllMTWpKeWNIQTJQN0l5ZUVGNmI2ZE14Zmo0aWVVdTFtZk9YTWFiZHE4S055QUNNamxOQzVkdWlpOTl1S0xQS1c5TFNQdW5XNFlvb2ZHY2Z4RWdLZW5wOTE3bGk1WkpGR0RwNlQ4ZzQrSDh6ZTRLNWN2b1dsVUZHN3hycit0dDhZNENVZkJjWndFY0hJNWJkZWd5NzMxMWx1bndIZjJ1cXdTTzNQbU5EcDFlQmw1ZWJtU2g5T3Y3N3RQVkU0eE1OQytLNzkyN1REbzlYeitUNnZWNHVUSkU0ajdNQmE5MyttRjk5L3JJOTF0cTJxTm5uMFdPLy9jaTUvWGIwUjZlcnFkRUlob0sxZjhpTC8vUG9xcFU2ZFgyeWpjbmF5cTIrUTREam01T2RBK29qMkV6WnRIdzh2YkcxOU8rZ0tmalBvWVBkNzRQNGxCNWs2TXVHZlBwdUN6TWFOQksyams1K2RMcjRtTXVJa0pjeVZHM01xTXBta3dEQU52YjIvRXhNU0FaVms3d2trQWVPZmRQdGk3ZHc4UzVuMkh5VjlPd3RCaHZEQk4zZkFJbkR4eDNEaDB5RWNaRGlHb0hiQUpZRlpPMUlVVWtzY2NBSGIvL3Ywc3gzRkhYZEIxWnlzcEtjR2ExYXVmeU4vbTcrOFB2VU1SNC9idFZJa2IvOEJmKzdGNjFVLzQvUE5KV0wzMjV3cERYWUlneW9HZHFiUVVtMzdiQ0ZNcFQ5b2NHQmlJOXUwNzRNeVowM2J2MjdEK0YremF1UU56dmt0NFlIMVpkN1BONDhlUGdaYkxVYTkrL1lkeWZEcDI3Q2c5SEZtY3hiV1BpeHVNVXlkUFFFRXJxdXp4YnQ2Y2hPWExsbUxhOUZrSWRjaWhWY1NJZTZkd3RHSERoamgwNkpCVWxiYzFyVmFMUVlNK3d2cGZma2E3bDE5Ry9mcjhXRm1USmszUnZrT25rd1pEUm1XZ3hwYk5pWXJCS0svelFNS2VONXkxV3EwdVlLdmlBZHU3WjdmazN0dmF2cjE3OE9IQUQ5Q3Y3N3VZT1dPYWxGTklTMHZEb0E4SG9QYzd2VEJyNW5RcHFmOVE4b1pXcTBTcXliRXNyRmFyQkVMZFhua1YrL2Z2eGJGamY4TnF0V0xQbnQwNGR6WUZIVHQxQXNBTGpCQUVRRklVOHZQenBGQ0NZKzNKVEFPRGduRDB5QkZZTEJaa0dnejhYVnlod0U4clYyRGR1ald3V0N6SXk4dkZnUU4vb1hIanhtV2g4T1lrYlBwdEk3NmJsL2pBZU9xcXVrMlR5WVMvang3QjlHbGZJMjdRUncrbGNKQ1JrWUdkTzNkS0QyZE1Na2FqRVo5K09ncDkrdlpEUUVBQVpreWZXaW03c3hoaWVuaDRZTmJzYitIbDVRV1R5UVIzQWVCRlJ0eGh3MGRBTHBkTGpMaVZtZGxzaHNsa3dzR0RCMkV5bVVCUlZEbHRqNnRYcjJMNnRLL3gxZVN2Y2ZUSVlXemR1a1Y2N2RWWFh6dnJKQXhsYkhOc29tTkdFSVNVcTNOemQ0ZE1LSTl5b2x0WFdGaDR6RVY2ZUdkVHF6WG8yS2tMVnE5YWlTNWR5Z0RxK3ZWcjJMQmhQUklTRmtDaFZHTG1qR25ZdG5VTFh1L2VBM1BuZklQMkhUcWk1NXR2NGRqZlIzSG80SUdIc3UrT0Rib3pway9Gak9sVDBhRmpKM3oreFNSRVJ0YkR4QzhuNC9zRmlkQ25wNk4yYUNobXp2NVc4dGk2ZE8yR3MyZFRFTnYvUFZDVURLOTM3NEhJeUhySXliVUgrVkdqUm1QMnJKbFl2bXdKYXRldWpTWExWb0NtYWN5WU9SdUpDWFB4ODdxMWtNdmxhUHRTTy9SN242ZGZaeGdHczJieVllQTd2ZXk1OTFldVdvT0FnT3B2WXE3cU5vY04rUWdFd1JNc2ZqSjZ6QVB0WTd0YnUzcjFDdHEyYllkM2UvZUIxV3JGM08vbUlDY25wMXhQb2EwMWJScUZjK2ZPZ2lSSjVPWGw0dXJWcTZnbkZINHFZc1N0ekRJek0rM0NYdzhQajNMQ0xTZFBITWZZY2VQeGZQUG1lSzV4RXl4YnVoaGR1M1lEUVJDNGR2M2F0WXBBRFE1VEJ5UkpRU2FqWURZREZFbVZUUjZJSHR0SEgzMTBkTjI2ZGZrQTNGM3dWYm05OFg4OTBhOXZiNGsxRkFEKy92c29VbS85aDBGeEE2VzdsbFpvRlVsSitRY1RCVVdnOElpYTc2cnAxTGtMT25YdVV1NzV5TWg2K090ZzVUcW1yVnZIVkhqaEtoUUtUUGg4b3QxekF6K01LL2UrRmkxYjRaY05HOHM5SHhFWmllL21KVHI5Ym9xaXNQL0E0UWQ2SEt1eXpUdXQxLzJhVGxlOTdVUk5talJGa3lZODViWk1Kc01uRGt5NXpxeEwxMjY0ZE9rUyt2ZmpxOWJEUGg0aGhlUVZNZUpXWmpkdTNJQ0hod2ZhdDI4UEFEaDkrblM1Z3NOYnZkNlcvdTN0N1kxUHg0eVZVbjV4QTJOVG5ZSWFRYkRnT0pZVGpDUkpqaVFKamlCSWFMVmFMamMzaC9mWWJIT2l2L3p5aTNYTm1qVUhTSko4eFFWZEZSdEJFRkNwVkhpalowK3NXbFVtam1FeFc5RDJwWGFTNUpsandya21HMkpkOXZnYVJWRW9LakkrME9GOHg5eG9aUURvNCtOVFpZVXMyL005T1RuNW52Yk5Zckg4cmRmcmJjTlFxd2h1QkE5dW5EUmxRSklnQ0JJa1NZQldLT0R1N3NFWER4eG1ybGlMeFhMUWRhcmRHZGdBb0VmM04zQTc5WmIwZkhTTEZ0aXplN2RFRGI1Nzk1OUlTZmxIY3VlM2I5dktoNE5DR2YxK1RhVlN1dzdHWGE3TncxeXppcmJ0NStlSFltTmhwYW1QcXFaSUhuYUtwanJlbDV1YiswOGxZYWhES0NxTVVwRWtTSUlFU1pKU1ZSUkNJbzRGd0dWa1pMaUFyWXFtVUNyUnAyOVp2cXBCZzRZWU52eGpqQjB6R20vMzZvay9kKzVBY0hBd0FHRDRpRkhZdWVNUDlINm5GN1p0MndxeUdtWWZ4VHU5eSt5dHlGaFk0V3dwK1pEV3JNaFlDS3FDRnBMQXdFQndISXZNakhTbisxYW5ic1Fkd1VDdDFxQk8zWWlIdXU3VnRaK25UcDY0NmdUVXhFZVptQXRCOElVc2tpZ0ROWklFb1ZLcENLdlZTckFzUzNJY1IzRWNKd2VnWUZsMkg0Qm5YWmNJY1BMVUtmZ0gzTC91NTRVTDUxR3JWakRjM055d1o4OXViTisyRmJObWYydjNuZ3o5YlR4L0Y1M3NOMjdjUkhhT2kwSFgwVEwwYWZEeDhVWm83ZkpzdFErTGRaaG45ZlZHbUJNR1hkRU1CZ1AwZWoyc1Zpc0tIMkJqK0tOa0hNZWRiOWV1M1NUd0lpMjI0aTFGQUlvSmdoQTBEZ2lHSUFpV0pFbU9vaWpRTk0zUk5BMkZRZ0VaSDU5S2VSOHBITFZhclh0a01wa0wyQURJcWluL1VWcFNpakdqUi9ISzcxb3RQaHM3cnR3ZFhTNlgzOVYzaG9iV1JtYW1BUm42TkdoMWJpN05BMk1oak1aQ01GYXJVMUFEZ0xEUVVCZ01EMjdOeEgxaUdhWlNVQk5EVWo4L3Y2ZjZHQllXRnY0Tko3azFBSXdZVllxdEhoUkZRUzZYZzZacEtKVktLSlZLcUZRcUVGcXRsckJZTEFURE1BVExzaFRIY1JRQU9pc3I2MlV2TDYrTkxsZ0QwdFBUa2E3UEVCZzRhczRNR2VrSURQQy82eUY0anVOdy9jWk41T1JrZzdHeUtDMTlPc2tuVlNvMVNJcUNqN2NQUWtORDdsaW91WG56UDJSbVpZR3hNalcyWmlxVkdwUk1ka2RQeldWbGR1TEVpVStqbzZPdkFpaEdtY2FCa1NDSUlzR0xNd3Y2Qmd4RlVaeGNMdWNVQ2dXVVNpV25WQ3FoVnF0QnVMbTVFV2F6V1FRMjIzQlV5YkpzTW9CYXJxVUdUcDFLQmtFUTBHaDFVRmZ6SGI2NHlBaGpZUUVBcmxyNTJGem1zc2N3REUwalNYS2tUUmhxQzJ3bEFFb0pnckRSRUtVNG1xWWxZRk9wVkZDcjFYd29TcElreHpDTWJTaktBV0FZaHRsQlVkUUhydVVHbWpXTFFucDZPZ3lHVEJRVzVGWGJzTHBPcDROTUprTlFVQ0FDQWdKY0MrMnlwOXBLU2tyK2duMDExQzRVZGNBb29ZZU54ekNTSkVGUkZDaUtLc3V4aVcwZndsQXBDNEF0TEN6ODA4UER3d1ZzZ2dVR0J0WUlmYmZMWE9ZeTNxNWN1WExHQWRTc0FLd0VRZGkxZWhBT2ZXd2lvSW4vSnNYeXFEaG5aZFBUeG5wNWVmMEo0TFpydVYzbU1wYzlpREMwU1pNbTF3WHdzam84R0Z0UUF3QmI3TElGTjRxaUlHTlpsdU00cm94dDBzWmpBOEJldTNadE8wVlJBMXpMN2pLWHVheUd3OUI5anA2YUE2aUp6TG1jR0YwS1BHOGN3ekJnR0FaV3F4VVdpd1V5aFVJaGtyUkowLzhDMkxFQUdIZDM5KzNlM3Q0dVlIT1p5MXhXbzNiOCtQSGtTc0pRaWRGRDlOQmtNaG5rY2ptRXdnSEV3b0ZXcXdWcEc1dmFjTE5KNGFpdnIrOUJ1RlRpWGVZeWw5VnNHSHF4UllzVy96bUFtZ1VWTUhvNDV0YWtFRlFtNHg4MkZRV0NKRW1PWlZsQ2lHRkZzUVRHYkRadnBtbTZvV3Y1WGVheUdyMjRvZGZya1pGaEFNTXdNQnFmbnNtRHdzTENmYkJ2eXBVZUhNZUpqYmtpd1NSWWx1VXNGZ3RzZEEvQWNad1Vqc3BzUFRiQmErT0lNb1VYRmdDVGxwYTJMU3dzYkl6cjFIT1p5Mm9PMUpLVGs4R0JrS1loL0oraTM3OXp4eDlubkhoclZ2RFRCbmJFa2lSSmNwUk1CbHBPUTZHZ29WQXFvVktxb0ZLcm9GRnJvTlBweWtKUlczQnpERWZyMXExN25tWFpuVS9hWWlZbUprS3BWRDdRYlZxdFZvd1lNUUorZm41d2QzZEhYRndjaW91TFhWZjJVMjU2dlI0Y0NQZ0hCRDExWTNGV3EzVnY3QWZ2WjlzQW04WEJheXNmaG9vRDc3WmhLQ1dEVEM2SFhDNEhLWlBKUkZEamJEdzJ6b2J0Z3dYQUZCY1hKN2xPdi91M2NlUEdJU2twQ2V2WHIwZFNVaEoyNzk2TlljT0d1UmJtS1RlRElSTmFuZHRUK2R2VDA5TU9PUWxCTGFpNGYwMW94aVU1aWhTQVRjYm4xK1F5R2VRMFhSYUtPaFlSQ0lLd2EvMUlTRWpZUEc3Y3VNOEFoTGhPdzN1em9xSWl6SjgvSDZ0V3JVTGJ0cnlVMmFKRmk5Q3hZMGZNbURGRFVtcDMyZE5uRE1QY2s2ZDI0Znc1TEY3MEE3NzlibDZON3QrRkMrZXhkY3RtTkduU0ZCMDZkcEtlLysyM1g3RnA0NjhBUWFCdDIzYjRJSlp2b0toTVlQbk1tZE5JbVBzZHJJd1ZZYUZocWRldVg3L2c0SzFKWWFpSVA0NE51U1JGZ2lRZHZEV2hTa3JUZEpuSFpodUtpdUdvamRmR2pCOC92c1Jpc2Z6NnRKMXdhOWV1UlpNbVRhQlNxVkMzYmwzTW0yZC9BdTNac3dmTm1qV0RTcVZDZEhRMFpzK2ViUWRRUFh2MlJQUG16UUVBeWNuSktDa3BrVUFOQU5xMDRaVzJqeHc1NHJxNm4ySXJMQ3k0cDgvVmI5QVFFNy84cWtiM2JjSDhSQ3hkc2hqWHJsNUZVVkdSOVB4Ly85M0V4ZzBiOFAwUFM3QjAyUXFjT1pPTTQ4ZVBDWi9oQlpaWHJGeU45L3QvZ0JuVHBrcXBtR2xmVDhhRUx5Yml4eFdyNE9QcnQyUHZudDBXWjk0YVFOaFhSRVZ3STBsUXR1TlRBcWpKNUhMSVJkb2lzVHdxdkltaktJcGdHSVlqU1JJTXcwaDVOZ0RXVzdkdWJhcGJ0KzZJcCtWaysrbW5ueEFiRzR0dnZ2a0duVHQzeGw5Ly9ZWGh3NGREclZaandJQUJ1SHIxS3JwMTY0YWhRNGRpN2RxMTB1dTJ5a1U5ZS9aRWJpNnZQM3I3OW0zUU5HMG5xQ0dYeStIdDdZM1UxRlRYMWUyeWNwYWRuWTJwWDA5R1ZxWUJCRWxpOE9BaGFObnFCYVNtcG1MVWlPR3dXTXp3OFBERThoVmw5UFRqUHZzVWFXbjh3RkJlWGg3VUdnM1dybHNQZ0ZlRlNwZzdCL2tGK2ZEeTlNS1lzZU1ra1I0QVdQL0x6OWk0Y1lQMGZnQVlNUEJEMERTTjJiTm0yTzNiUDJmTzRQbm16YUZXcTJHMVd1SHA0WWt6cDA4ak9yb0Z6cHc1alpFalB3RmdMN0JzeU1pQVVxVkNXRmdkMGJNN1ZZRzNaaVVJTWI5R2NBREJFUUJIa0NSSGtTUklrdUtrU0ZNTVF3VnZUYUZRbGdHYlRhN05OaHkxbXgyTmlJaTRZTFZhTjVJaytjYlRjRko5ODgwMytPQ0RENlFjV0dSa0pHN2N1SUhKa3lkandJQUJXTGh3SVJvMGFJQlpzMmJ4ZDgvNjlaR1Nrb0kxYTlaSTMvSDIyMlZpRmNYRnhVNkxGUXFGb3B3c21jdGNCZ0EvcjF1RHhvMGJvOS83SCtEYXRXdFlNRDhCTFZ1OWdPRGdZUHl5WVNNdVg3NkVxVk1tMjMxbTJneitmTXpQejBQY3dGaEpmOE5zTm1QcWxLOHdaZXAwQkFZR1lzY2YyL0g5Z3ZtWTlQak83clFBQUNBQVNVUkJWR1haNTVzMGJWcU9lZmhPQXN1Wm1abVk5dlZrTUN3RG5aQW50QlZZL3ZQUG5aTEFja0ZCQVJRS0JjeG1NK1orOSsyV1pVc1haemw0YXJiZ0pvU2g0UGlIV0RTd1NaL0p5cncxV21qV1ZhbVVJT1Z5T1J5OE5qZ094Z3U5SWd3QUppOHY3NmtKUjgrZlA0OFhYbmpCN3JtWW1Cajg5OTkvTUJxTk9IMzZkRG14MnRhdFcxZjRmU3FWcXB4S0Q4QnJWVDRNZlVxWFBmb1czYUlsZHYrNUN5dCtYQTVUYVdtVkJWVllsc1dYa3lhaTJ5dXZvVVdMbGdCNGFjalUxRnVJSHpzRy9mdjF4Wm8xcTJESXlMRDdYTDE2OWZIRy8vVzhpMnZrSENhTUg0dUJjWVBRcnQzL3N3ZllDZ1NXOC9Qek1Xcmt4L0J3OXpqa3hGdXpMUnBJYkI2MnVUV0tzZ2xEWlJSa01ybU50NmFBU3FXR1RBUTJzV3RYREVjcGl1SUVIVUhiMlZIR3g4ZG5EOE13aHdpQ2FQMmtuMVFxbGNycENTUCtYeWFUbFZNNnIweVVOamc0R0tXbHBjakx5NE9IaHdjQXdHS3hJRHM3RzdWcXVXanZYT1lFMktKYklISCtRaHc3Zmd3clZ5d0hUZFA0Y3ZMWGQvemMwaVdMb0ZRcTBhZnZlOUp6Rm9zRkFRRUJkbUhyL1ppN3V6dEtTMHN4KzV2djRPYm1oak5uVHNOZE9LOUZnZVdPblRwTDNxSldxNFdibXhzSzh2TXhlY3Ewb3krLzFPWjZKZDZhMkx2R2xuVnBDR0VvajFHU1F5YVh5VURMNWFBVkNpaVZLcWcxYXBBMFRVTXU5SDVVRUpMYUZSRUFXSXVLaW41K0drNnFoZzBibGt2cUh6NThHSUdCZ1hCemM4Tnp6ejJINDhlUDI3M3UrTGV0UlVWRlFhVlM0Y0NCTXFIa2d3Y1BnaVJKdEdyVnluVVZ1NnljVFpzNkJjZU9IMFA3OWgwd0xuNENqaDQ5VXU1bTZtZ0hEeDdBL24zN0VELytjenNXNFlpSUNCUVVGT0N2L2Z1a2ZOdm1wTi90UG52NThpWDg5bHZWZ3JMbkdqZUcyV3lHUXFFQXd6QTRjdmdRbWpUbHRVd3JFbGdPQ0F5RXU0Y0g4bkp6ZDFma3JRR0VIWnNIUU5oNGJHVjVOY3JPVzFOQXFWQkNyVlpCcTlGQ0pnSmJSVVVFbTdZUGFSS2hmZnYydng0NWN1UkRnaUFlZTAwRWxtWHh4eDkvbEh1K2JkdTJpSStQUjgrZVBmSHNzOCtpYytmT09IandJT2JNbVlPSkUzbXg0S0ZEaDJMZXZIa1lPM1lzUHZqZ0F4dzllaFRmZi8rOVhWaTVidDA2NU9ibVl2RGd3ZEJvTklpTmpjWFFvVU9oMCtsQTB6VGk0dUxRdjM5L1NUSGJaUzZ6dFQ1OTNzUHMyVE93YnMxcXNDeUxVWjk4Q3BJazdZb0grZm41ZUt2bkcyajIvUE1ZTzI0OEV1ZDloOUxTVWd3Wi9LSDBQWWtMZm9CT3A4TzBHYlB3M2JmZjRJZUYzME90Vm1PVWc0NW84cWxUMkxoeEEzcjArTDg3N2x0b2FCaDY5UGcvRFBwd0FEaHdhTk9tclNRZVhwSEFza3dtdzdqNENlZGZlNlhyMlFxOE5RdEJpS0FtRkEwSThONGFSWUlpS1U0bVZVTGxVdDhhcmFDaFZDbWhGaVlQaUo0OWV5SS9QeCtGaFlVb0tpcENjWEV4U2twS1lES1pDSlBKQkJzOUJKTGpPQmtBT1FCbFVWSFJBSlZLOWZYamZOSWtKaVpXMkJ4Ny9mcDFoSVdGWWRteVpaZ3hZd1p1M0xpQnNMQXdEQjgrSEVPR0RKSGV0MlBIRG93YU5RcFhybHhCVkZRVVdyWnNpUTBiTmtpNm9qMTc5c1NOR3pkdzRzUUpLWjgyWXNRSXJGMjdGaVJKb21mUG5wZzNiOTREbjRCdzJhTmwrL2Z2UjNoa2c2Zml0eG9NaG0rZmoycDhCRHo5dDZocllBUlBCVjVDRUVRcEQzQ0VsVmVob2ppWlRDWXFVSEcyVEI0YXJSWTZuUTZlbnA3dzhmRkZVRkFRd3NMQ1FQVHUzUnQ1ZVhrb0tDaEFVVkVSaW9xS1VGSlNndExTVXBoTUpzSnNOc05xdFpJc3l4STJlZ2owNHNXTHZXSmpZLzhBRVA2MG5vd3BLU2tvTGk1R3k1WXRwZWNtVFpxRWJkdTI0ZGl4WXcvVTY3eHg4ei9rNU9TQVlSaVVsanlkSTFwS2xRb1VSY0hMMHh0aFliVWw4bFJueG5HY3NHYlpzRnByYnMyVUtoVmtNaG04dmJ3UkZoYjYxQU1ieDNIWFBoc3pldUxhTmF1TEFKU2dUTmZBQ0Y1YXJ3U0FXUUEyUnBEV1k0VndrMU1vRmZ4Y3FFb0Z0VVlEblZZTE4zZDNlSGw1dzkvZkQ4SEJJUWlQaUlCTXBWS2hwS1FFTkUzRGJEWkRMcGZEWXJFNFZraHRRMUlHQUROdzRNQ0MzcjE3LzZSVUtpYzlqZ3M4Yk5nd0pDWW0zczhCd3BVclY5QzNiMStzWHIwYVRaczJSVXBLQ3ViUG40LzRlTjd0dnBOS2txUHA5WHI0KzkvZDZMUEpaTWFaTTJkQXlXVFF1Ym03NVBlS2pNak55MFhXeVV3MGJkSVVOQzEzZXV4T25EZ0prcUtnMWRYOG1oVVZHWkdkazRQTXJFeEVDODNhRmRudEo3eWZNVGMzZC9QYU5hdE5OaUdvV1FReUFCWU9ZQWdIYlFQeFB4ekhnV001c0N3TGxtVjVKZytCemNOcXRjQmlzY0JpTnNOc012UEFwbFFxVVZKU1lwZHJrOGxrc0ZxdG9DZ0tETU9BNHppSk5wempPQVlBczNuejVwL2ZmUFBOOXdEVWZkd1dPQ0VoQVFrSkNmZjFIVDE2OUVCOGZEeEdqQmlCdExRMEJBY0g0OU5QUDhYSEgzOHNYVUExYmVuNmRGQXltVXN3V1RDTlJndU5Sb3NNZlJyUzB0TVFGbHJlUzdwKzR5WklpbnBnYTJhN1R6ZHYvb2ZRU21UNGFnVUhQOG5lMnZXbFM1ZWNkQUExbTl4YVdZdEhHYUVrQmJsTTFBM2xxNTZpdDZiVmF1SHU3ZzVQVHkvNCtma2hPRGdZZGNQRDBhalJzeUExR2cyVVNpVVVDZ1ZvbW9adE1VRW9Jb0NpS002bW4wMXMvYkQyNnRVcnQ2U2taT1hUZkNIRng4ZmordlhyTUpsTXVIcjFLc2FNR1ZOcENGVGRscDJWL2RRT1QxZG1XcDBic3JLeW5iNldrNVB6VU5aTXEzTkRWbmJXVTN0TXNyT3preFl2V2xnSys0S0JDRzRTazRkdGl3Y2ZpcEtnWkJSSFVRN051TUtVZ1ZxdGtrRE8yOHNiTXBrTXBGYXJoVnF0aGxLcHhMMjBmcXhidDI0dHgzRXVodDJIY2dlODkrSHBwOEZ6WTFuRzZXc1BhODAwR2kwWUsvT1VucXZjeFc5bXp6cmhCTlRFZkZvNXdSWmU2WjEwbUFmbFI2ZmtBcWlwVkVwb05CcTR1Ym5CdzlNVHZuNitBQUNaVHFlVEtxSkN3Y0F1MXlhVHlUaXIxVW93RE9PMFlUYzJOamIvcmJmZVdxSFJhS1kvNFFjR2VyMGVCb01CVm9hQnNiQjYyRTIxV2gxa2NobjhmSDN2V3RxUElJQ1NFaGVYVzBWV1VnSFAzY01zcnR6cjhTS1NNMEhNVHdhdTVOM2JoaU04d0EySkFoZmxXeTIvb3lBL0h6ZXVYMFZSa2JGU0lBK3JFdzQzZDNka1pHUWtyZnBwUllXNU5UaHB5Q1ZKa3VQNzFtU2NURWJ4b0NhVGc2YkxkQTU0alFNZDc2MTUrMENoNExzTFpCNGVIaWdvS0lEUmFKU3FvV2F6V1FJM3E5VUttVXdHaG1IRXBKM1UweWJrMnF4dnZmWFdtaTFidHJ4QkVFU0xKeFhVa3BPVEFZS0VScXZqMlUyclVkdTRxTWlJZEgwRzB0UFRxMTBKZnRmT0hWaStiQ2tNaGd3RUJRVmg1YXExdUh6NUVtTDc5NVBlTXpaK0FycDI3ZVpDd2tmWkVwT0JxM24zL3ZrcmVTRG1KNE5iMHJGYWR1ZjZ0U3NvTGk2NjQzbDk0L3BWUE51NDZZbDNlcjJaN0FUVW5IcHJrSmh5YllmYzVaREw1SkRUZkRPdVFxR0FVcVdDUnNOUE0zaDY4WGsyMFdTZW5wN0l5OHREWVdHaGJROGJhSnJtcXd3T1hwc042NGVrMXJ4OSszWnpRVUhCais3dTdrOGtzT24xZW9BZzRlZGZNMkxKWW5MWm9FOURlbnA2dFlreXA2V2xZY2IwcWZoeTh0ZUlqbTZCZ2dLZUdpY2lJaEs3ZHU4REFBeU1mYi9HMW0zWjBpVjR2WHNQT3pZVGNiL216dmtHS1NuL2dLWnB0Ty9RQ1lNL0dpSU5YNmVtcGlKaDdoeWtuRTJCUXFGQXg0NmQ4R0hjNEhMRDJkVnBkOXJtaXpFdlFDNlhneUJJaElXRm9YL3NBTFJ1SGZQQXprSGlma0ROQnR5cXkrNEVhcmJnOXQvTm0xdXZYTGxzdllPM3hwVFByVkdRVVJRbmpVN0o1VHdkT0UyWGVXczZMZHc5UE9EdDdXTTNBa242K1BqQXc4TURPcDBPYXJVYUtwVUtDb1ZDeXJVNXk3YzV5N1Y1ZUhoc1psbDIyNU1JYkFaREpqUmFYYzNuWUhSdU1CZ00xZlo5Ri8rOUFCOGZIN1J1SFFPYXBpV2VPSUlnb0ZEd2Q3MjdiVW01Ry90eCtWTGs1SlJQNEUrYU9BRzFnb1B4Mis5YnNHanhNaHc5Y2hoSlNac0E4RDE1WThlTVJyMzZEYkRwOXkxSVNGaUEvZnYyNHJlTk5jZTlVTlZ0Smk1WWlNMWJ0Nk4vN0FETW5qa2RHMy9kNFBJazcyQldxM1ZYbTVnWExqangxQ3IwMWtnN2I2ME0xT1RDUEtoQ3FZUkt4Y3ZzdWJ1NXc4dlRDLzQyM2hvQWtINStmdkR5OG9LYm14dTBXaTNFOWcreFNtb0RiSndOaGJqZGlCWDRhWHlMd1dENDhZazhPSXoxZ1NTYk5Sb3RyRmJyZlgvUDZkUEo2UDVhTjh5YU9SMTZ2UjdkWCt1RzdxOTF3NG9mbDFmNU80NGVPWXcrNzc2TjlpKzNSV3ovZmpoMzlxemQ2L3YzN1VXLzkzcWphK2NPNlA5K1h4dzljbGg2YmRqUWo5RDlOVDYwSGZueGNIUi9yUnVHZmpSSUFwRzJiZHZodlg3dlE2RlF3TS9mSDgyam8zSHA0a1VBZk1YUzA4c0x2ZnYwQVUzVENBNEpRZXVZTnZqMzM1cXJUOTNOTmxVcUZWcTNqc0c0K0FsWXZHaWhIZkhpazJCRlJVVkkrbjBUeG80WlhUMGg2L1hyTzJFL0Uyb0hiQnpIV1RtT1l6aU9ZMW1XNVFoQTh0WktTb3E1VElNQnFiZHVvYUNnb0l5OVE2bUNScU9HUXFIQWd2bUpPSGp3TDZqVWFudGdDd29LZ28rUER6dzlQYUhUNmVEWS9sRkpsYlNjMXhZWUdIallZckVzZWRLQXJib0tCVlhhbHRGNDM5L1J0R2tVTmlWdHhjY2pScUZXcldCc1N0cUtUVWxiMGUvOS9sWDYvTlVyVi9ENWhIZ01IaklVVzdmdnhKdHY5Y0tva2NNbGlwdWJOMjlneXVRdjhkbG44ZGl5YlFkZWZmVjFqQjgvVGhLbFNVaGNnRTFKV3dFQWMrYk93NmFrclVoY3NKQS80VWdTdmZ2MGhZZUhwM0RpWDhQaFE0ZlFzaVZQQXVEajQ0T0V4QVZRS2xVU0VKNU9Ua2JqeGsxcWJNM3ZaWnZSTFZyQ2JMRklnUHc0bWVNUXZmaDNWbFlXQnNiMng1VXJsNnZsUmxKYVdycG0vUGp4dHh4QXplVG9yZkZqVXp5ZU1Bd0RrcVRBc2d3c0Zncy9TUkFlanN5c1RMQWNCNlZDQ1pYUTNuSHMyRkVvbFVxNHVibVgyemFwVXFrUUVCQUFiMjl2dUx1N3c3Yjlvekt2elZsZkcwRVFsblBuenEwRWNOdmxoRCsrdG1WTEV0cSsxQTZ0VzhkQW9WQ2djNWV1YU5Dd0lYYnUzQUVBOFBiMndiTGxLL0ZNbzBZZ1NSS3Z2dlk2TEdZemJ0MzZyOHJiT0hmMkxEcTJiNGQrZlh1alFjT0dlTEh0UzA3ZnQvRDcrVkNwMWVqYTdaVUg5dnVyc2syQ0lPRGw2UVZqa2ZIUk9YQUVvUDBnQ202ai93ZENLWVBxdGZwd0c5Y0doSnZDN20yalI0M0E2ZVJUQUhqRzNPOFh6QWNBZUhoNDRLZlZhOUh2L1ErcVkyOXVIenQyN0s4S1BEWGIzalVXNEhOckZFVnhITWR4RkVXQlpWbE9vVkFJdyswcWVIdDV3V2dzaEVvb0dPVGw1Y0ZzTXVIRkYxK0VYRjUrdW9RRWdLQ2dJUGo2K3NMVDB4TnVibTdRYURSUXE5VlNIc1laQTBoRnViYW9xS2lyeGNYRlB6enBGMy9YemgxUVdQaGtDdHJxOVhvRUJ0cDM1ZGV1SFFhOVBoMEFvTlZxY2ZMa0NjUjlHSXZlNy9UQysrLzFjZW9KVkdhTm5uMFdPLy9jaTUvWGIwUjZlanFXTFMzdjZLOWM4U1ArL3Zzb3BrNmREcGxNOWtCK2UxVzN5WEVjY25Kem9IMkVlZ2lWSFNOQXFPUmdjMHJndmZRMXFOOW9pTkkvcjhMOVUzdnF4Q0ZEaDJQcTExT1FtREFQMjdkdFJkLzMrQXE1ZUcxWGgrWGw1ZjJTbUppWTYrQ3BtWnpuMWppdVRNb1lrTWtvS0pSS21NMW1VQ1NQTmNYRnhTQUlRbkM2RkRoODZCQUdESWlEenMxNW83VU00S2wvQXdJQ2tKT1RVMkhyaDhWaUVXYXlySnpBOXNHeExBdUNJRmlPNHdqYlhGdWJObTFXSGo5K3ZCMUprdTFjL3MvalovNysvdENucDl2ZmZtK25JaXFLYjBVNThOZCtyRjcxRStaOE53L0JJYnhvMllzeEx6ajFhaHpCemxSYWl1M2J0NkZMbDY1UUtKVUlEQXhFKy9ZZGNQRGdBYnYzYlZqL0MzYnQzSUc1Q2ZQaDV1NytRSDczM1d6eitQRmpvT1Z5MUt0Zi80RWZIMFhyRUxqSHZ5ajliZmszRTdtZjdJUzhuamRLdGwyRzlYb3VkTU5iSW5ma0RwaVBwMEg3Z1gwTFVYaEVCRHAyNm95Vks1Wmo1cXh2NE9aMjcxTVljcmtjN2RyWlgrWTdkdXc0TUhyMDZPTlY4TllZQVQ5WXE5WEtDWkVnS0ptTVV5cVVJQWtDTjI1Y2w0WUhlRFlQRFU2ZlRzWkw3VjVHdlFZTjhPL0ZDeFVER3dDRWhJUWdLeXZMYWV1SENHd2l1REVNSXoyRUdWSVdBQ0gydFNVbko1c05Cc09TZ0lDQXB4TFlmdDJ3bnEveWNSemF2ZnovOEg3L1dBQjhtOE5Yazc1QVlXRWhta1pGNGREQkExSXU2a0diYlpHQ1kxbFlyVlpwd3FUYks2OWl5T0E0ZE9qVUNjMmFQWSsvL3RxUGMyZFQ4Tm5ZY1FCNGdSR0NBRWlLUW41K0hyWnUyU0o4ai8xc2JHQlFFSTRlT1lLNmRjT1JsNXNMWHo4LzBBb0ZmbHE1QXZuNWVYaTNkMThVRlJseDRNQmZhTktrTEorMVpYTVNOdjIyRVhNVDVqOHducnFxYnROa011RjA4aW5NbURFTmNZTStlaWlVN3FaRHQyRG90cnI4TWIyZUM3cUpQK1RQK2NGNk13K3FWK3ZCY2prYmNEZ3VPM2Y4Z1gxNzkyREsxOVB3emV4WitIcmFkTlNyZDI4QWJiRllzSE9udlpiNnRXdlh0aFlVRkRoT0Y0Z2VXem1HWElxaVFGSVV3SEZTMzZ4Y0xrZEFZQ0EwR2cxMFdoMysrZWNNL1AwRFFOTUtYTDF5QllVRmhiajQ3d1hrNUdTREpFbkk1WEwwZXZ1ZDhzQW1ocVM1dWJuSXo4K0gwV2hFY1hFeFRDYVRIYmpaZW0wTXczQWN4MEY0U05NSUFDeUJnWUVIVENiVGZKcW1oenhOb0paODZoUzJiRW5Dd2grV2dLSW9mREpxQkVKRHc5RHU1ZitIdVhPK1Fmc09IZEh6emJkdzdPK2pPT1RncFR3b2MyelFuVEY5S21aTW40b09IVHZoOHk4bUlUS3lIaVorT1JuZkwwaUVQajBkdFVORE1YUDJ0NUthVVpldTNYRDJiQXBpKzc4SGlwTGg5ZTQ5RUJsWkR6bTVPWGJiR1RWcU5HYlBtb25seTVhZ2R1M2FXTEpzQldpYXhveVpzNUdZTUJjL3Ixc0x1VnlPdGkrMWsvSTZETU5nMWt3K0RIeW5sejMzL3NwVmF4QVFVUDI5aEZYZDVyQWhINEVnZUlMRlQwYVBlYUI5YkZXeGt1Mlg0ZmJKLzBENXFKSFZleU0wYno0RDk4OWlrRC9qb04zN1VsSlM4TzJjdWZEejk0ZWJ1enRPbmpoK3o4QldiaDlLU240Y04yN2NEUUc4ekk0RkExUXdaUUNBczFnczBHaTFuTnhHdzBBdXA1R1duZ2FaVElZNmRlckN6ODhQMDJmT1JvUDZEZURqNjR1RjM4K0hWcXV6QTdWeXdPYnY3NC9zN0d6azVlVkp3Q2FPV1lrZW00M1g1aGlTMmswakVBUmgzck5uei9KT25UcjlqeUNJcUtjRjJJNGRPNG9PSFRwSnpZS3Z2UElxamgzN0crMWUvbjlJU2ZrSEV3VkZvUENJeUJyZmwwNmR1NkJUNXk3bG5vK01ySWUvRGxhdVk5cTZkVXlGRjY1Q29jQ0V6eWZhUFRmd3c3aHk3MnZSc2hWKzJiQ3gzUE1Sa1pINGJwNXp5aWlLb3JEL3dPRUhlc3lxc3MwN3JkZjltazVYRFgyU0RJZUNtWWVrUDQzTFR3UExUNWQ3MnljMnJMbE5tMGFoYWRQcXVUdzVqanZ6eHg5L09CWU1iSE5yWmltM1JoQ3NRUG5OV2ExV0RnQ1VTaVduMCtta1dkQ2lvbUpjdW5nUi92NEI2TkN4RTgva0lVd1krUGhXUGhwV0xqdGFxMWF0Y3JrMjBXTno4TnFrY0pSbFdWdlZlSWJqT0JLQXRVdVhMdnJzN093ZnZMeThGajVOWHB0dHo2dkFZeWNsbkd1eUlkWmxqNjlSRklXaUl1TWpRV2pnN2UxOVR5bVN0TFMwald2WHJqVTZBVFdUSGFqeG5Hc3NRWUFqU1pLbjlwYnpQV3JpMkpTQ3B1SHQ1WVg2OWVzTHRFUys4UER3Z0krUGo5MWt6cURCemdQQ2N2dzY3dTd1Y05iYlZrSGpMaWZTRzFYVXRPdnQ3YjNkWXJFc2ZscE8wT2JOVzJEWHpwMG9LU21CeFdMQnRxMWIwRUxvMFdyYU5BcmJ0L0Vuek9WTDFkUC9wRktwWGFod2wydnpNTmVzb20zNytmbWgyRmh4bFowTDk3aC9qNm9hdmtNMHRkbyt0MWhhV3JwNjVNaVJGMjFDVUpPRHQyWlhNQ0FJZ2lPRVpuOFpyempGeWVVeVlSYVVsbVpCK1NGM2dTWFgyeHYrL2dHU2R1bGRlV3dBWDBnUXZUWmJxbkJIcjgyMmlDQjZiU3hmQm1PRVNpbEpFQVIxNk5DaFpXM2J0bjJlSUlobVQ5S0ZNK0NEZmlBRTdqVmZYejhrSkM3QTg4MmJvMVBuTGhqMElWOHdhUFBpUzJqWDdtVUF3UEFSby9EbHhNL3g2NGIxQ0krSTRCT21UOUNkL2xHeUltTmhoYTBMNUVOYXN5SmpJYWdLV2tnQ0F3T1JucDZPekl4MHFBV2lCVHNiR2dVdU1mbWVaMGE1Y0E5Z2FQVmxoT3JValpBRzRUbU9PNzFuejU2OVR2SnF0dTBkamtVRGZuUktISnVTeXlDWEM1NGJMWTVObFEyNWUzbDZ3Yy9YcjhveWxVNVhXUzZYSXpnNDJLNklZRXRwSklhaWp1MGZUZ29KVmdDV2R1M2FwV1ptWmk3MDhmRlo5RGhlSkZvbitZOXRmK3lxOFAyOTNuNm5YRElUQVBMeWNqRno5cmR3YzNQRG5qMjdVVkpTVXFWdDNTbHN5TTdKY1FHYmd4bU5oZkR4OVhINm1vK1BON0t6SC95YUdZMkY1UWdCYksxWnMyWXdHQXpRNi9Vb0tzeTM3NVBVQWhoYkcwRHQrOWlEYk9CeWR2VmRGMW9OdEZvTmJ0Njh1ZkhISDM4c3FDZ0VoWU95TzUrVkVaVHZPQTRjeTNJY3k0SmxHVENNbFg5WUxiQmF6TENZUzJFeGw4SlVXZ3hUYVJGdVhyOXk3OEFHQUFFQkFjak56Ylh6MnB6bDJtdzlOb2RDZ3VpMVdRaUNJSDE5ZlhlV2xwYk9WU2dVSHo5dUY0bXNtdTd3cFNXbEdETjZGSy84cnRWSzdSTzJkM1JuWGRTVldXaG9iV1JtR3BDaFQ0Tlc1K2JTUERBV3dtZ3NCR08xSXJTMmN4QUlDdzJGd2ZEZzFremNKNVpoRUJaYU9URDUrZm5aMGU4ODZwYWZuNy9rcFpkZXVsU1J0MFlRaEFoc0RFRVFqTWpjSWFwTzBUUU5sVW9sZUdkbGROOWVYbDd3OVMxVG5XclFvQUVhTm14WTlXdTJzaGRyMTY1ZFlWK2JvOWZHc2l6aFVFZ29WeVZkdFdyVjB2NzkremNpU2JMOTQzU3grUG41SVYyZmNkOFhRRlN6WmxpNHFPSlIycUlpSXdJRDdrN01oU0FJTkcvZUhOZHYzRVJPVGpieWNuSlFXdnAwa2srcVZHcVFGQVVmYngrRWhvWlUrdDRXMGRHNGVmTS9aR1psMWVpYXFWUnFVREladkwyOTd3aHFqNXRacmRaOUV5ZE9QT0FBYXFVaXNBbWdacDlYRTdRTVJDVjN1VnpPeWVWbHhKRzJBT2ZoNFFGdmIyOEVCQVNnZHUyN1d6dmlUb0lqdDI3ZHd2bno1M0h0MmpXa3BxYkNZREFnSnlkSEFqeEhMVklCOUFpV1pRbVdaU21PNHlqd1dxUTB4M0hLR3pkdXRBb05EWjBMNExGU0h6bDFLaGtFUVVDajFVRmR6WGY0NGlJampJVUZBTGhxSjVwMG1jdHF5TklQSFRvME95WW01cFlBWkNYZ05VS0xZSzhQYWl1bHg5aDZhd3FGZ3BQMFFUVzgwTEdIaHdlOHZHekVXZXJXeFRQUFBJT1FrSkM3aTdMdTlJYVFrQkJKVU5reDErYlkraUhLWW5HODJVNGtXSG5uZ2lERHdzS09GeFFVSk9wMHVxbVAwMUZzMWl3SzZlbnBNQmd5VVZpUVZ5MHNIQURmdnlTVHlSQVVGSWlBZ0FEWDVlS3l4OEp1Mzc2OUtpWW1KbFVJTThYUXM5UkpDR29WdkRWcDBGMGlqUlJDVVZ1YWIxRy9RQXhGYTlXcWRkZWdWaVZnNC9NNG9VNXpiVTU2MnNSQ2dtTnZtemh1WlNFSWduSnpjL3ZaWkRMVnBXbDZ3T04wTUFNREE2dU4zZFpsTG50Y3pXZzByZzRPRGo0Ris5WU94eERVWWhPQ3NzNUMwSXBBalZkMTkwRlFVQkJDUTBQdmFSK3JwQk9uMCtrUUVoSmlSMjlreTdnckRxa0svVzEydlcyQzF5YjF0b2tMc1d2WHJpVXN5KzV4blNZdWM5bGpsVmZidjNEaHdqMXdYZ0dWUERZNHpJTUs1SkdjTGFqUkFzVzNHSTdxZEtJb0M1OVhDd2tKdWVlSkRObk5temVyam9JOFZaSFQ3bm54T2ZGMTRTRW04RmloQTU4UXdlMlZWMTVKTzNEZ3dQY2hJU0doQU1KZHA0ekxYUFpvRzhkeDEwNmNPSkgwNmFlZkZqb0pRY3VCbWlDQWJDZW5aNHNmRmVFSUwrUkNnbVZaM0EwKzJRSGIzYmg2dnI2K1RpY1JiQVNXWVVNZlRwaE1KaEFFQVVHNlQyb0JJZmhmUkxacDArWkVUazVPZ3FlbjUreXFoc1V1YzVuTEhvNnpkdTNhdFRWdnZ2bG1xb08zNWl3RXRkcTJkb2pocDhEdktCVU10RnF0WGVncFZqOGpJeVBSdUhGanFOWDNQaUZ5VjJDaVZxc1JHaHBhYm9hMGd2bFJqbVZaUWl3a2NCekhDZ1VGMjZrRTBzdkxhM054Y1hHUVNxVWE0enAzWE9heVI5T3lzN01YaDRlSG43WHgxRXB0SHdSQm1HeEJ6VEVFRlNRR3l1WFZiTUhOVjlEV0RRME52UzlRdTJ0Z0EvZ0Vla1Y4YmM3R3JNVEdYUUhjT0dFcVFjeTNrUURJVnExYUxUbHg0b1MvWEM3djV6cUZYUFlVaDNyUTYvWEl5RENBWVJnWWpZOEdRM05wYWVuUDc3NzdybTIvV3JrUWxPTTRDZFE0am1NRlJseU9aVm1PWVJnN3ZqOHhzaE1mWXBWVW9WQkFyVlpETHBmajBxVkxEeGJZQUNBOFBOenBxSldUYVFSTzZHY1RSNjNFV1ZMYkZoRGluMy8rSWZmczJiTzRmZnYyZmhSRmRYR2Q0aTU3R2tFdE9Ua1pIQWhwR3NML0VkZ3ZpOFh5NTA4clYrekt5Y214T0FFMTJ3a0RmbXlLSUZnQ1lQbDBGSy9lTGpCM2NFb2xMOFNpMFdqNW5qVjNEM2g1ZThIZlB3QzFnb01SWGpkYzB0RzRYN3VuYjZBb0NuWHIxa1Z3Y0REOC9mM2g1ZVZsVnlsVktwVzIrVGRPWkFLeFZiWVMzRlVyK0JZUWMrZk9uVk12WGJxMGtPTzRZNC96Q2JwcDB5WW9sY3FLRXhWV0swYU1HQUUvUHorNHU3c2pMaTVPVW5keTJkTnJlcjBlSEFqNEJ3UTlNbU54TE11ZVBIVHdZTkxFTHlZVU9PVFVTb1JIcVIyb1NYUkVKRmUrQ2xwK3VrRG5wb09IaDZkRVJWUTdOTFJhUU8yZWdRM2dGVzNDd3NJUUZCUUVSMjFTSnlwWG9wcXpYUXVJQTdpWm5ubm1tWlMwdExRRkhNZGRlVkpQNEhIanhpRXBLUW5yMTY5SFVsSVNkdS9laldIRGhybXU3S2ZjRElaTWFIVnVqOHorY0J4MzlkeTVjK3Y3OW5rbnd3SFViQ3VnSmp0UUUzcldTRkthQjRWY0p1Zmt0Q2gwcklCS1pUOWw0T1BqQS8rQUFJU0UxTDR2N1lWcUF6WUFDQTRPUm1ob0tBSURBeVgrdGtyQURiYjliUUovbTBRbExqeE13Y0hCQjNKeWNoSUFaRDVwSjI5UlVSSG16NStQMmJObm8yM2J0bWpidGkwV0xWcUVGU3RXSUNzcnkzVjFQOFhHTU13OWVXb1h6cC9EcUJIRHEzdDNzbTdldkxtNmErY08xMkUvMkc0SGJBUkJXRGlPczRpQ3h4ekhDUnhyRkVlUUpJcUtqTWpOelVHbXdRQUN2TmkwUnEyRzJXVENnYi8rd3BZdG0vSG5ycDN3OXZhdTlzYjMrL2I3NnRXcmg5cTFhMHZnNXVIaFlTZmhad051WWtncTlzT3h6a0pTQUNZZkg1OXQrZm41M3drTFdLUFdxbFVyVEpreUJTKy8vRExVYWpVaUlpTHd4eDkvWU83Y3VRZ09Eb2E3dXpzR0RCZ2dxUzJOR0RFQ25UdDN0dnVPN3QyN1k5Q2dRVTYvdjJmUG5tamV2RGtBSURrNUdTVWxKV2pidHEzMGVwczJiUUFBUjQ0Y2NWM2RUN0VWRmhiYzArZnFOMmlJaVY5K1ZaMjdVcXJYcHk5djA3clZlWlMxZFpUYWhKNjh0MmJYMmtFeUpFbXhITWV4NERqSVpCVE1KaE5VS2pVWEVsSWIvZ0VCU0U5UGgxcXRnVnFqUVVyS1AzamwxVmN4ZXZRWVJFUkU0dkNoUTlXK252ZmRPMFlRQkNJaUlwd3FXam1yam9vdElBQUlBRTZMQ1J6SGtSNGVIdXVOUnFOR285SEUxL1JKTlgzNmRHemV2Qmt4TVRFWU5HZ1FldlRvZ2U3ZHUrUENoUXM0Zi80ODJyVnJoNjVkdStLTk45NjQ2Ky91MmJNbmNuTnpBUUMzYjk4R1RkTjJuRnh5dVJ6ZTN0NUlUVTExWGQwdUsyZloyZG1ZK3ZWa1pHVWFRSkFrQmc4ZWdwYXRYa0JxYWlwR2pSZ09pOFVNRHc5UExGL3hVMW02NDdOUGtaYkdhNWJuNWVWQnJkRmc3YnIxQUlDclY2OGlZZTRjNUJma3c4dlRDMlBHanBORWVnQmcrYktsaTcvNGZQd3BCMUFydFFFM3ZsZ0FXSVJybGlVSXNBUkpjQVJIZ09NNFRpYVRnV1ZaenQzREhRcUZBZ1JCUUorZURyVktEWXFpb0ZhclViOUJRd1RWcW9XaUlpTU9IVHo0TXJuRXdBQUFJQUJKUkVGVTZBRWJ3STljaFllSGx5T2pGTXU4VGdia2JmdmJJSVNrNG1TQ0NHNkVWcXRkV1Z4Y3JGS3BWQ05yOHVUcDBhT0hwSTM0eGh0dllObXlaWmc2ZFNwME9oMWF0bXlKWjU5OUZ1Zk9uYnNuWUh2NzdiZWxmeGNYRnpzdExDZ1VDcGhNSnRkVjdMSnk5dk82TldqY3VESDZ2ZjhCcmwyN2hnWHpFOUN5MVFzSURnN0dMeHMyNHZMbFM1ZzZaYkxkWjZiTm1BVUF5TS9QUTl6QVdJd2NOUm9BWURhYk1YWEtWNWd5ZFRvQ0F3T3g0NC90K0g3QmZFd1NCSWJ5OC9OKytPTHo4WWVkaEo0bGQ4aXJjU3pEaUZSbGtNdHBVWndGUlVWRk1BcHN4a29WSDcxcHRGcjQrdnBpKzdhdHlNL1BSMTVlM3FNSmJBQ3ZjR1ZMSVc3YnRHdmpzZG4ydFVuZ3hwYXA2aklDMEVuZzl0WmJieTFadjM0OXJWUXFhMHpHejNiNlFxRlFTUGxEMFdpYXJoYmdVYWxVTUp2TjVaNDNtVXdQUlovU1pZKytSYmRvaVlTNWN3QVFhTkdpSldaL002ZEtuMk5aRmw5T21vaHVyN3lHRmkxYUFnQ3VYNytHMU5SYmlCL0w5OEt6SEF1Tm9GMVFXRmk0N0oxZWIrMUgrYWtDK3hEVVJrSlBGRHRtR0lhVHkrVWM3NXlBbzJtZUxEWFRZSUJHbzBWMGRBdjgvZmRSdUx0N1FLVlVnbVZaclBoeE9YcjFlaHNObjNrRzQ4ZU5mWFNCRFFEQ3dzTHN0QkVjcHhGc2lDaGg0NjF4QXZpSmt3a1FtdjFBRUFTeFpjc1dZdHEwYVV2aTQrTmxDb1VpcmlaT0hwa1RIdnFLMUtTY1BXK3hXS3EwbmVEZ1lKU1dsaUl2THc4ZUhoN1NaN096czZ2TTVlNnlwd3pZb2xzZ2NmNUNIRHQrREN0WExBZE4wL2h5OHRkMy9OelNKWXVnVkNyUnArOTdkdWRwUUVDQVhkZ0tBRWFqY2NXRStIRjdVMUwrY2V4VGN3UTFPelpjb2RtZW94VUtqcGJMWWJaWVFKRWthSVVDRkVYQjE5Y1hkZXJVaGJ1N0J3aUNRR0JRSUhSYUhSaXJGUk0rbjRqdzhIQmN2UGl2ZEMwOHNzQUdBUFhyMXkrblJlb1lqanA0YlNLOUVXeHlib3lRZ3lNSWdpQysrdXFyUEY5ZjN5VURCdzRrRlFyRndJZDVvdEUwWGM1MXZuVHBVcFU0bzZLaW9xQlNxWERnd0FHOCt1cXJBSUNEQncrQ0pFbTBhdFhLZFJXN3JKeE5tem9GMFMxYW9uMzdEbWdSM1FKdjl1d0JsbVVyN2ZjNmVQQUE5dS9iaDRXTGx0amRpQ01pSWxCUVVJQy85dS9EaTIxZnd0V3JWM0hxNUltZnpwNDl1MmZqeGcwbE5wNWFpY1BEUkJDRVdjaXAyYkhoTWl6THlTZ1paREk1U2twS09IY3ZieWlWU3VoMGJsQW8rUCtYbWtyNUp0eGF3UWdMRFVOQVlDQVVOTTN2NjRFRGFOSzA2YU1QYkFSQm9INzkraEt3T1hwc0RNUFllbXkyNEFaaEZBTkN2czN1TzRjTkc1Wk4wL1NTZnYzNjRXR0NXOHVXTFRGNzltd3NXN1lNelpzM3g2SkZpNUNlbmw3aCs5ZXRXNGZjM0Z3TUhqd1lHbzBHc2JHeEdEcDBLSFE2SFdpYVJseGNIUHIzN3c4dkx5L1hWZXl5Y3RhbnozdVlQWHNHMXExWkRaWmxNZXFUVDBHU3BGM3hJRDgvSDIvMWZBUE5ubjhlWThlTlIrSzg3MUJhV29vaGd6K1V2aWR4d1EvUTZYU1lObU1XdnZ2MkcveXc4SHZRTlAyVGg2Zm5ueHZXLzFJRSswS0JiUU91eVhFT1ZPcFhveWdRRElPU2ttS1VscFp3YXJVYW5sNWVVS2xVcUZlL1BtN2V1SUdqUnc5RHA5T2gxOXZ2SWlRNEJQWHExMGY4K004eGVmSWtXTXdXaElTRVlNelkrRWNmMk1SY1VyMTY5ZXdxcExhNU5pR3ZWcTZZQVB0S3FTMjRFUVJCRUhGeGNaa21rMm5Sd0lFRFdhVlNHZmN3VHJUWFgzOGRJMGVPeE9qUm84R3lMUHIxNjRjQkF3YWd0TlI1WjhxR0RSdHc0OFlOREI0OEdBQXdlL1pzV0sxV2RPL2VIU1JKb21mUG5wZzdkNjdyQ25hWlV3dXBYUnR6NTgxM210YjRaY05HNXpmVFgzNnQ4UHNhTkdpSWhZdVd3R2cwcnBnMWM4YWVaVXNYVndScWtxY21naHBKa2d4UVJob3BveWhPcmxTQ3BoVWMzM3dyaUxLb05YQnpkMGRrWkNUOC9Qd1FGRlFMWVhYcW9HNTRPSlJLSlo1N3JqRitXTFMwUnRmdGpwb0g5Mk1aR1JrNGQrNGNybHk1Z3RUVVZPajFlbVJuWjVmVFN4QkNWMElBUUVKZzRTVTRqaU50TkJQa0hNY3BBQ2duVEpqZ0hoOGZINnRTcVlhNFRuMCtVWHpqNW4vSXlja0J3ekFvTFhrNlI3U1VLaFVvaW9LWHB6ZkN3bXBYR3E1eEhDZXNXVGFzMXBwYk02VktCWmxNQm04dmI0U0ZWVXdSdG4vL2ZvUkhObmdnNjFSWVdMaHNRdnk0dlRiaHB6TlFzMlhzc0lpTUhXSnpQVVhKT0lIZW13YzFwYjI0c2Fkbm1jcFVhRmdZNnRkdkFGOWYzd2QyTHRRb0I1cS92Mys1NFhnbkZWTFk1Tm1rU3Fsd3dkcjJ1SW1GQlV5Wk1nVW5UNTc4WWNPR0RXYTFXbjNQclNBZE9uVEFuMy8rK2NoY21IcTlIdjcrZHpmNmJES1pjZWJNR1ZBeUdYUnU3aTc1dlNJamN2TnlrWFV5RTAyYk5JVllvWE1FdFJNblRvS2tLR2gxTmI5bVJVVkdaT2ZrSURNckU5RkNzM1pGZHJ1Ryt4a0xDZ3ArR0RwazhQNUxseTZhS3dNMWxGVkFKVDFRZ1RoV1VKb2lJSXhPU2F3ZEpDVlFmMU1VNURJWnhQbFF0VklGczhsVTQ3L3RnUUVid0l2QmlPQldXWFZVckpBS3ZXeWMxV29Wd1kyeERVdEZjTnUrZlRzMEdzM1N3c0xDVXExV094S0E4bTczYmRldVhZLzloWnl1VHdjbGs4RS9JQWd1QXpRYUxUUWFMVEwwYVVoTFQwT1lFeUxWNnpkdWdxU29CN1ptdHZ0MDgrWi9DSzFFaHErV1RadFJOVnRwZG5iMjRsWXRteDlHZWZiYmlqdzFLd2lDSVFDR0VEdzFHVVZ4TXBuZ3FZbEQ3V28xdEJvTmRIYWtrWUdvWFRzVWtaR1JDQTBMZStEbndRTmhyUTBQRDY5SVlGa0NPSnU3cVpScnMxcXQ0bXRPd1EwQWREcmRxdHpjM0dJUEQ0L2hBSHlmdGdzNU95c2JXamQzRjZJNW1GYm5ocXlzYktmQWxwT1RBOTFEV0RPdHpnMVoyVm1WQWxzTldaWmVuNzQ4K3Ztb1U3Q2YvWFQwMHV4QkRRS29FU1JIRWlSSGtRSmJCMjFER0NuTWYyb0Z2UUkrQlBWRHJWcDhYdTFoZ05vREF6WSthZG5BS2JBNWVtMmk1eWFDRzhBUENGY0NicHlucCtmR2pJeU1JbDlmMzQ4SWdvaDRXaTVlanJ2MzRlbW53WFBMejh0eCt0ckRXak9OUm92ODNKeDcraXlSbkFsaWZqSnc1ZTY2OURsd1YyK2FzbGEzL20veStUdDRhcVp5b0VZSW9FWVNBZ3V1REhJNXpTbG9rYWxEQURXSkJkY0xiam9kTE9aUzVPVm1Jek5EZzh5TU5LZnJFRlluSEc3dTdvOC9zQUZBbzBhTnlvRmJCY0FtZ1p1WWNLc0kzTVMvL2YzOS83aCsvWHBCN2RxMTQwaVNqSzUrRU9IWlRRMEdBNndNQTJOaDliQ2JhclU2eU9ReStBbTB5SGQxc2hOQVNZbUx5NjBpSzZtQTUrNWhGbGZ1K1hnbEpnTlg3dzdVV0hBbno1WGNXdDg1ZGZaMWxKLzl0Ry9wc011cGxRYzFtUUJxdElMMzFCeUxCYUxJc2RWaWdvZUhSNlhLN1VWRlJ0eTRmaFdObXpaN01vQ05JQWcwYXRTb1FxL05HWmpZZUhCT3dVMzRYbzdqT0s1T25UcUhrNU9UODU1Nzdya0JGRVYxcms1UVMwNU9CZ2dTR3EyT1p6ZXRSbTNqb2lJajB2VVpTRTlQcjNZbCtGMDdkMkQ1c3FVd0dESVFGQlNFbGF2VzR2TGxTNGp0WDhiQ1BqWitBcnAyN2VaQ3drZllpTHNFTlF2SC9IbXc4R0pTbjR5RkdUYmhwK1BzWjRrQWF1WnlPVFU3VUpPREZnU09sUXBCTGsvaldBSDFRMkJRRUl3RmVaV0NtdTA1LzBTRW9xTEo1WElKM083Z3Nkbm0yeW9DTjNId2xoTkQwNmlvcUhNYk4yNmMxYTFidDB5YXB2dFd4ejdyOVhxQUlPSG5Yek5peVdKeTJhQlBRM3A2ZXJWeFU2V2xwV0hHOUtuNGN2TFhpSTV1Z1lJQ25ob25JaUlTdTNidkF3QU1qSDIveG83MXNxVkw4SHIzSG5ac0p1Sit6WjN6RFZKUy9nRk4wMmpmb1JNR2Z6UUVGRVVCQUZKVFU1RXdkdzVTenFaQW9WQ2dZOGRPK0RCdXNQUjZUZGlkdHZsaXpBdVF5K1VnQ0JKaFlXSG9IenNBclZ2SFBKcWVLbXYrZVUzMm9WMWY1UDFXVUFtb2xmUFVCQUxZQ2tDTjcxVlRxa1FSRmwwWnFQbnhiUjFob1dISXo4dXUwZU5VVlNNZnhrYVZTaVVhTldxRThQQndCQWNIUzBMTXRseHVZck9mUXFHUXhGVmxNaGxuSThUTTJISzVDVG1DVWdBbGI3enh4dTBtVFpwOGF6UWF2eEZldnk4ekdES2gwZXBxZkYwME9qY1lESVpxKzc2TC8xNkFqNDhQV3JlT0FVM1Q4UEh4a1R4blFRcXR3cG5ZNnJBZmx5OUZUazUydWVjblRaeUFXc0hCK08zM0xWaTBlQm1PSGptTXBLUk5mUGpFc2hnN1pqVHExVytBVGI5dlFVTENBdXpmdHhlL2JmeTF4dmF6cXR0TVhMQVFtN2R1Ui8vWUFaZzljem8yL3JyaG9WeTAzajkyaDl2by8wbC9leVYwaGJKVEJBQlljNjFGMzdlK052SDNML0oreTNmSXBSVUxqN0xxcHkyb2dXQmdVLzJrS0ZrNVVBTUhaR1ptb3JpNFdNcXArZnI2SWlnd0NLR2hZYWhYdno1b1lWUktQTSthTld1R2poMDdvbjM3OWdnS3Vyc3E5SW9mbDZQM083M3dkcStlMkxwbGM1azNhckZnOXF3WjZOZjNYZlR2MXhjSER4NTQrQjVibVplaVFhTkdqWndOeFpjTFMwV3hWZGhVUzRVVDBuRkNnUlBEMG4vLy9aZlY2WFFyTXpJeTlMNit2b01JZ3JoblVXWXJZMzBneVdhTlJndGp3ZjFUdUp3K25ZeEpYMHlBMld4R2NYRXh1ci9HaDVrOTN1aUpmdS8zcjlKM0hEMXlHSWtKODZEWHB5TTBOQXlqUHZrVWpaNTlWbnA5Lzc2OVdMWnNDVElOQnZnSEJDQXViakJhdmNCZmJNT0dmb1JiLy9GQ3R5TS9IZzZaakVKd2NBZ1NGeXdFeTdKbzI3WWR1cjN5Q2hRS0JmejgvZEU4T2hxWExsNEV3RmNzUGIyODBMdFBIOUEwamVDUUVMU09hWU4vLzcxUVkrdCtOOXRVcVZSbzNUb0c4dmdKbVBqRkJIVHEzT1doTUxObytqU0djVmt5Mkp3U0laemhydjFuemxyendzMnZ6c0plVGNvWm41b1FmaEpXZ0JOQmplVWRCdEttVUNEbmFLRlFrSitmRDBaUW1xSnBHbDVlWlo1YWFCZ1BhbzZTZVhYcTFBRkZVZGk1Y3ljVUNnVmVmdmxsWkdabTJwRkdPTTY5aW4rbnBQekRuMlBMVjhCa01pRXViZ0NpbWoyUG9LQWdiTisyRmFXbHBWangweHJrNU9SZzBJZXhhTnk0aVVRdG5wNmUvbkE4TnRGME9wMVR6ODNUMHhNNm5jNFp4YmlkTUl5b24rRGd1WmtGejYwVVFMRy92LzhmLy83Nzd5U0dZZmJlNjM1V1Y2R2dTdHN5M24vdW9XblRLR3hLMm9xUFI0eENyVnJCMkpTMEZadVN0bFlaMUs1ZXVZTFBKOFJqOEpDaDJMcDlKOTU4cXhkR2pSd09RMFlHQU9EbXpSdVlNdmxMZlBaWlBMWnMyNEZYWDMwZDQ4ZVBrMFJwRWhJWFlGUFNWZ0RBbkxuenNDbHBLeElYTE9SREJKSkU3ejU5NGVIaENZQ24wamw4NkJCYXR1UkpBSHg4ZkpDUXVBQktwVW82MFU4bko2Tng0eVkxdHViM3NzM29GaTFodGxna1FIN1FaamxyZ1BiRDUva2JMMnZkLy9mdGMvTmZ1UGxWQ3V3WmI0c2RIcVVFL3pBRGhBVUV5a0NOeit4d1ZxdUZrOGw1Q3YrOHZIekk1REtvVkNyVUNhdURxR2JQdzh2YkcxcXRGcjYrZnJ5bkZoYUcrdlhxUTZ2Vk9sM1h0TFEweVZsaFdSYWVucDUyNzFtNlpCSFdyRjRGQUVoSitRY2ZEeDhLQUxoeStSS2FSa1ZCb1ZUQ3pkMGRMN3p3UDV3NHp1czhuVGx6R20zYXZDaWNUd1RrY2hxWExwVWRoMWt6cHo5Y1lBTUFkM2QzQ2R4Q1FrTHN3TzBPRk9PMllTa3JoS2JpblVvRXR4SUF4Yzg4ODh5cG4zLytlVXBwYWVseVZ4cjZ6clpsU3hMYXZ0UU9yVnZIUUtGUW9IT1hybWpRc0NGMjd0ekJoMExlUGxpMmZLVWtsZmJxYTYvRFlqYmoxcTMvcXJ5TmMyZlBvbVA3ZHVqWHR6Y2FOR3lJRjl1KzVQUjlDNytmRDVWYWphN2RYbmxndjc4cTJ5UUlBbDZlWGpEV2NCSzh3aHZneWpOUWRZNUFpU2UxT3VIWDVTdTcvVGJtbGdPb09RSmJDUUdVQW9TWmY4QktDS3BTSk1DUklEaWFwam1TSkZGU1hNd1ZGQlRBM2NNZE9xMk9uLzEwYzRPN3V6dFVLaFhjM056NG5Kb0Fhcm9LUkZob21nYkRNUEQyOWtaTVRBeFlscFg0RGtWNzU5MCsyTHQzRHhMbWZZZkpYMDdDMEdHOGZrUGQ4QWljUEhFY1JxTVJCUVVGT0hmdXJIVFR6OC9QaDFLcHhObXpLZmhzekdqUUNocjUrZmtBZ0tUZk55RThQT0xoaGFLMjV1SGhnVWFOR2xWNkVvbi9GeDRjUVJDRXhXTGhiT3NNTE10eXR2MXRZbGdLZ092ZHUzY2FnSVRzN093YlhsNWVnd0RjYzRiK3dvWHppQnNZaTJYTFZ5SWlNaEpMbHl6RzVxUk5rcGRTa1gwMzV4c2NQblFJT1RrNW1EanBTN1I1c2UwakNXeDZ2UjRSRVpGMno5V3VIUWE5bm1jeDBXcTErSFBYVGt5WjhpV01oVWE3TUtLcTF1alpaN0h6ejcxSVQwL0hGNStQeDdLbFN6Qmc0SWQyNzFtNTRrZjgvZmRSSkNRc2NNcVpWeE5XMVcxeUhJZWMzQnhvSDFJUElWZGtTYjg4Zi9lcWhzZUduTUl4aWZ4UkJEWlRCYUduQmVCRXBYYUdBTUVKd0FZS0JDZVh5NkZVcXJpOHZGeFFsQXdCL2dGQzlWTUhOemMzZUhsNVFhL1h3TlBMU3dnL0c5eXhGMDBNUnc4ZE9vU21UdWlKdEZvdEJnMzZDQ05IRE1NNzcvWkcvZnI4dkd5VEprM1JzVk1YREJzNkdPNXU3bENyMUZDcFZkTG5ObTlPUWtsSkNhWk5uNFY1MzMwTEFNakkwR056MHU5SW5QLzlvd0ZzQU9EcDZXa0hiallnVmc3WXhHT0xzaVpld21hdVZBUTZhYTROQUN1UzRubDdlMjg2Zi83OGpYcjE2dldqS09ybGU5MWZ1VnlPdi83YWo0aklTQncrVkRYTzloRWpQOEdJa1o5Z25NQmcrcWlhdjc4LzlBNVVUTGR2cHlJcWltOUZPZkRYZnF4ZTlSUG1mRGNQd1FJUDNZc3hMemk5SVRtQ25hbTBGTnUzYjBPWExsMmhVQ29SR0JpSTl1MDdsRXNBYjFqL0MzYnQzSUc1Q2ZOcnRKSHpYcmQ1L1BneDBISTU2dFd2LzhDUGo1V3g3dnRiZjM1enR4L2pVMUdKT3J0OWdZQ1FldFFBZ2lVSmdpVkFjQ1RIZ2dMSnlVQkNKcE56K2ZsNWNITjNCMk8xd21ESXdMUFBOUzZiS1BEemhWdnFMZmo2K3FGKy9mL2YzcG5IUjFYZSsvL3puR1gyeWI0TXlZU0VrSVJjdHVDbHdyVmlYUzR1VkZzcHVLK1hxcTAvL2ZXNklsYTlyYlYxQlpjcXlxMTZSYXUxZXVVVmJkVWY5ZDY2d3IwZ29sR0pDWVFrSkNRa0lTR1FaU2FabWJNOHZ6L09lVTZlT1psSlFBRkp6UE42SGVaa01wTVp6cHg1bjg5M0wwZnFLTWVJTlp2OTdMUFBBQmp6aU8yZHFCc2FHdkRBL2ZmaW50L2VpN1hQUFl2SmhVVTQyMVRLbDE1Mk9TNjl6RWhzdU9icW4yTHExQkxMeW5PNzNiam50L2RDRUFSRW8xR2twcWJpclRmZlJIK29IOWYrL0pwdjN4UzF3MjNtekprb0tTbEJRVUZCM0ZpLzFOUlV5K2ZtZHJ1WmFVclozRktiV2NyOGJvck5OQjFncHVtcVZhdnVIaGdZZVBMcnZ0ZC9tRDRESDMrOENYVjFPeERnMGpNKzNyd0pLNWJmWXYzODZDT3J2clhvMlloZkRyTzdNUUJRWGJmYVNnSEEyZWY4Q0I5KytENjJiUGtZcXFyaXZmZmV4VmZWMjNER21XY0NNQWFNRUFJSW9vamUzaDdMUjBMMStLRFBwTHc4Yk42MENZcWlvTXVNOWpxY1RyejR4eGZ3eWlzdlExRVU5UFFjd0lZTkgySDI3TmxEcHZDYmY4VWJyMWZpc2NkWEg3VStkUWY3bXRGb0ZCOXYzb1FIN3I4WFA3LzJ1cU1lT09qVEJwKzkvZkhmclQzN2IzZU1abm9PQUlpWTA2UmlBQlJDeklIR0ZMb2dpVlFrQXBVZ1VCa0NIRVNraEFCK2Z3cnk4NE9ZVnY0UGNMbmNjTHZjeU1qSVJFNU9Edkx6OHBHZWxvNjh2THhSb1FZWUVWUW1SQndPQjlMUzBvWTFhZjEwNnllNC9aZDM0cFJUVDhNamp6MkJtcStxNDRLSGlxTGdwUmYvQ0VLQVdiTm1XejVrMVF4azlQUWNRRU5EQThwS3kzRFYxZGZnbFZmWFllMExMeDQ3aW8wM1MyZk9uRGxNc1kyd1VXSXNFRUtzTnVPbWtxTmNncS9PZXJRRDBHKy8vWGI5OXR0di80K1dscGE2L1B6OGZ5R0VISGNvNzlNaHkvQjZQSGp4ank5Z3dVa240YXZxYldQR2gyWlAwSDN3Z2Z2dzRBUDM0ZlF6enNTLy9lcHVsSmFXNGRlLytTM1dQTFVhSGUzdG1GeFlpSWRXUFdKTk0xcjB3N05SWGIwTlZ5MjdBcUlvNGR6RlAwRnBhUm4yMjhxRmJyNzVWcXhhK1pCeEpaNDhHYzgrOXdJY0RnY2VmR2dWVmoveGU3ejZ5cDhoeXpKT1B1VlVYUGt2UHdWZzVDbXVmT2dCU0pLRWl5ODhMOTVNZk9sbEJBS0hQNWZ3WUYvekY5ZGZCMEtBd3NJaTNITHJiVWMxajAwSC9id2hzcmZ5bEpiNzZ0QTRsT0prVTJyV0xjRlFIelVBUmlVQmlBNVFLZ0JVcElSS2dnQlJBMlFpVWlja09OUFNoMm8vZlQ0VUZCUU01YW1aZ1lJZE83WVBpMzRtVzAxTlRVaExTOFBDaFFzQkFKOS8vdm13bVI4WFhEZzA3Q2d6TXhQTGJ4dWFmN0J4NHdZOC90Z2pxS2c0RGc4OHVNcUM1S0lmbm8yNnVqb3N1OUpRYzcrNDRjWmhDbHM2RnI5NHFhbXBtRFZybHRVT2hSQmkzZkw3M0gwVzNBQlFycXBCNTN4d2lVeFRyYUNnNE1QWFhudXQ3cHh6enJuQTVYSXRPOWozcUtncVR2dm5oZmo5WTQvZ2hodHZ4aC9XUEhYTUhjY3p6MXFFTTg5YU5PeiswdEl5ZkxSeDVEbW1KNTY0SU9rWDErbDA0cTUvKzNYY2ZkZjhiSGpmejNuei95bGhNOFNTMGxJODl2anFoSDliRkVWOHVPRi9qK3B4T3BqWEhPMTRmZFBsOXlmUGt4d0loLy8wMnI0UDM3L3JRR1VmWjNyR0VnSE45S2NwQURXZ1JnMmdFUXFkZ0ZJQ1FrVVFLaW9hSkFBeWtReW9RWWJiNHpHU2IvMURQclhzN0J3anBjUE1VL3ZlOXc2K1d0R3EyUG1hYThHQ2s3Qmd3VW5EN3Bja0NiZmN1bnpFNXg2VFlHTWZOQTgzKzVZQWNCYmNGRVdoWm9VQ0gxU2dYS1VDVTI4YUFPMzg4ODl2QmZCa2EydnJsM2w1ZVZjU1FrWnR3cTVyR3I1LzRnSTBOVFhGUlhvRVVZUitCSnQzVHF6eHVVUlJSRGdjaXN1WDFIWDlpMTI3ZGxXZThvTVRkNEJMWnhvQmFqRXlwTkpZZHc2ZEFHYVFnRUFFb1JJRXlCQXNxTGtndytYMHdPUHp3ZWZ6V1Q2MW5CeWpUS3FvTUhHZVdxTGw4WGd4TUJBK3FNY2R5U1VkeXgrMjErdkY3Tm16SVlxaXRZMENPbW9xT0JZeHRRY1ZtRzJxMjAxVEFGb3dHUHhnN2RxMVgxMXd3UVhuZVR5ZW40MzIvakl5TXZCL2YvR3Y2T2Z5M1BMemc5amQzSVJZTEFhcTY2aXRyVUZoWWRFUk8wWnV0MmVDQ29kNGJMN05ZNWJzdFhOeWN0RFJzZGNDV3lqVS8vd2ZYM2orby92dnV6ZGtVMmw4NUpQZmVLaVpaaWQwRTJqVWlId0tWQUtCVEVUcWdBZ0haTGlJRExmREEwOUpEbnhtU2dlREd0LzkxcDZta1d4TktTN0Jyc2I2RWVIbThYZ3hwYmprdXdzMndDaS9zc1BORHJrRUppcFRiL1lxQmNyNTNwaHBxcHVtcVFaQVhiWnNtYlpzMmJKbjZ1cnFQaTB1THI1RUZNVkR5c25JeTh2RHFhZjlNeTYvOUNLa3BLUmFaVXhBZkxySFY5WGI4TVRqdjhmOUR6eUVxU1ZmLzBOT2RLV2ZXRURZSE5LYmFBbmYwakVMaC9vaEpra2htVFJwRXRyYjI5SFIxcnBoZDB2cjIwdVhMRzVLb3RKNG54b0xqTVdHVkpveFJjcTRjQnMxbjRJZ1Vra1NJVWxtaHc2SERBZHJFbW5PS0dEOTFDenpNei9meUZPYlZuNUlxVFlwcWFtb09HN3V0Lzc1SDlHWkI0ZHpVVXF0K1FrdExTM282T2hBVjFjWGVucDYwTnZiaTNBNGpIQTRqRWdrd28vL0k5emdacUxyT3B1bFFBQ3dlUXFpQ1hpWlV1b0E0QURnQk9Bc0tTbHhiZG15NVVlTnUzWmRtUnM0T29NLzkzYnN3ZHhENlBEUjFOU003djM3Snpyb0RqdU9iY2pLeWtSaGdrNFRUYzNONk80KytzZHNiMGNiTWpNelVaU2cwU1NsZEU5blorZC96cGt6NTVPT2pnN0ZCclZvSW9VR0s5cEpWQmlOSVRUVEVxRnNNeXQwSUVrU0pNbWFVd0FuQnphUHh3Ty8zNCswdERSa1pHUWdOemNYK2ZuNW1ESmx5aEd0SmY1T0t6YUx3SVJnNXN5WjdBTWFwdHpzdCtZMkxHSktDS0VtM01Bck4yYWF3cGhHcndGUTYrdnIxWXlNak1vbm4zenlrd1UvT0dWeFJrYm1sVWY2aWk3TDhpRTlwN0J3TXJxNk9yRzNvdzArZjhyRXpJTlFQMEtoZm1pcW1oQnFBRkJVV0lqT3pxTjN6Tmg3MGpVdElkVEM0ZkRMYjd6eHhrZVhYWGJaZ1ZGVVdwUlRhTlpFZGxPbGFTYk1kQTVtUnNkYjJkYksyODMxVWpQYmVXZG5aeU1RQ0dEeTVNa29LU2xCZVhuNW1ENFB4b3hpNDFkRFF3UHE2K3ZSM055TTl2WjI3TnUzRC92MzcwZGZYeDlDb1JEQzRUQUdCd2N0NVdiT1hDRGNHRUJldlFtbWVoTk0wRXNBSkU2OVdRcnUvUTgyVkV3cExsNHFpdUtaUitMLzFibTNIWk1DdVlmY3RvaFNpbDFOemRpL3Z4dWFxaU1TK1c0Mm4zUzdQUkJFRVZtWldTZ3NMQmhWYlRRMzcwYlh2bjNRVk8ySUhUTzMyd05Sa2hJcU5VVlIvcnVxcXVxLzVzK2YzOElCTFJuVUVwbWQ2cERaYVFITm1FMWdBczBvWkk5WGFGNnYxNEphUmtZR3NyS3lNR25TSkJRV0ZxS2twQVJUcDA0ZDgrZkNtQVFiQUxTMHRHRG56cDBXM0RvN08zSGd3QUgwOXZhaXY3OGZBd01EL0dpL1lYRFROSTJZdzV2Wm1EOEdOMmFlc3BGL01nODNBTTVQUHExYWtKT1RlLzdoNnRRN0VBNGgxTjhIZ0I3MlJwTVQ2OWhidXE1dmJXeHNmTHUwdExUV1ZGeDJsUlpMb05LVUJDcE41elpxV2pCSm9lWXgwem44MW55Q2RDUHlhVUt0dExRVUJXWWx5VmhmMGxoOTR3VUZCVENyRG1BV3hWdTNpY3hWMDBTbGdpQVFRUkNnS0FvbGhMQ0dsL2JBZ2hVcEpZUXcwOVE2QVkrZmU5ejdKU1VsbXg1KytPRXpVbEpTemllRWZHM2Q3dmY3SVVrUzh2SW1JUkFJVEh6cngvR2lsTzVvYjIvLzY4S0ZDNnRxYTJzWnpPeSt0SmpOaDhaRHpSWWNzSVlYVTFFVXFTaUsxdmRCbG1YS2V1NHhxSG05WGd0cVJwREFiRHRVV0lpeXNySkRIdjA0QWJZanRISnpjOEdhVVBLQVN3UTF6dmZHWmlFU1JWRkFDR0VKdlpRT0RUWFZiV2toR2dEVm5HK3FtUDQzNWR4enozM3JqanZ1K1BEV1cyODlQUzB0N1h4Q1NQSEUxM2RpSlFEYXJxNnVyci9lZmZmZFc5ZXNXUlBsVE1sWUF0T1QzU3AyczVPZGk4eVh4aWF5MjFXYUxNc1cwT3hRWXdFQ2xzNVJWRlNFc3JLeVllMkV4cndwMnRUVU5PYi9FNUZJQkkyTmpWYTBkTisrZlZhME5CUUtZV0JnQUlPRGc0aEdvNVpaYWhib0VsWXp5Y3hTTG1wS2JLYXBTQ21WbUluSytkOGNBSnkzM0hKTHloVlhYTEV3SlNWbDZUZHBham14eGhYUUd2ZnYzLy9tbWpWclBuMzIyV2NqSEtDVUpFQ0xjYkN6Unp1WlVyT2luY3o4NUt3VXE5czBVMnQ4b0NBMU5SVnBhV25tM004QUNnb0tVRnhjREpmTE5lNk8vWmoxc1NVNGliQmp4dzQwTmpaaXo1NDkyTHQzcndVNEZsUmdnT1A5YnN6M3hnR09tRjE5Q2ZPL0lUNDFSSVFSWEVnRU9NZnk1Y3Y5SzFhc09EVWpJK05jUXNpTWlhLzNkOUtIVnRQWjJmbTNYLzNxVjFYUFBQTk1kQVNnMlRmRk5EdFZEbVlhYjNiYVZacXAxT0tBbGlqeXlZREdVam1LaTRzeGJkcTBNWnZPOFowQkcxdE5UVTFvYkd4RWEydXJwZDRPSERpQXZyNitwRUdGQklHRnVNZ3BVMjhNY2hqS2ZVc0t1T0xpWXNmR2pSdFB5czNOUFZzUWhCTW12dTdqZjJtYXRybWxwZVhkazA0NnFicTF0WlhCaVFlYWNoQkFZOCt6QWdNQUtCL3haQ3FORHhBa0N4S2t4RTFuRHlBWURLSzR1QmhGMzlJZzR3a2YyOWRjUlVWRmZGdWpPQi9jS0FFR2RpVms2bzFxbW1ibHZjSE1kVFBWVzF4d3dlWi9pd0Z3TkRZMk92THk4dDRGOEZGZFhkMmNvcUtpaGJJcy8zamk2ei8rVmpRYWZXdjc5dTMvTTJmT25GMGN6SGlWRmtzQU5TVUowUGpnUUZ5U0xhL1NlTFBUNFhBTTg2Y3gwNU5QNXlnb0tNRFVxVlBIVlpEZ093TTJ3QWdxOEgzYlhDNFgrSlBBRHJkSUpHSUJMaGFMV1pGVEJqY3V1RUQ0a1grc1F3aU1mQ0lWZ0dJcU9BdHdBQnhsWldWYkFIeis3cnZ2dmpwdjNyd2YrSHkrc3dFRUo1QXdwbDBmcmYzOS9lKzgvLzc3bnkxZXZIaGZFcUFsZ3BveWtrSXpMNWg4bEI0c3BzV3RrSlR5QUFBVFRrbEVRVlRtQnVpNlRuVmRoNlpwVmw4eVJWR3NCSFdtNkJ3T0I4TGhNTnh1Ti9yNys5SGIyNHZ0MjdkaisvYnRoK1VZK1AwcEVFWEJTaG1aTUVXUG90K3RycTRPemMzTmFHdHJ3OTY5ZTdGLy8vNFIvVzVtS1ZiQzRBS1gxQXZUTkdXbFdYRkJCcHVKeXB1cU1nREhKWmRjNGxxNWN1WDhuSnljMHlSSk9tMENFMk5ucWFyNmZrdEx5Ly9jZE5OTnRYLzV5MStVZ3dTYWtnQm85cUFBZzVsT2lBQ2p4bE9nZ2loQ0VrV0lyQ1JLa2lFN1pEaGtCNHlwN0taU2M3dmg4WHJoOS9tUmttcU14c3ZLeWtKdUlJQmcwQWdTSEFsL1dwakx3Wng3RE9WZ2ppdXdYWFRSUlhqMTFWZHgvZlhYWS9YcW9YNWZyYTJ0YUdwcXdnMDMzSURQUHZzTTU1NTdMZ29LQ3BMNjNiaUVYcGgrdDVHQ0MyUVV3RWxKQUNjRGtCY3RXbFN3ZHUzYStUazVPUXUvU1Q3Y3hEcWlGOGdkUFQwOUgzejAwVWRmTEY2OHVKdURVaUtnS1VtQXBpWURtdGtqa0RKZkdoRUVpS1l2VFJRTm9FbXlXUm9sT3lBN0hIQ2FVSE83WEhESDVhZ1o2UnhaMmRtbStUbjVxS2lwenIzdHlKc1VPR1p5TWNlZEtTckxNdDU0NHcwODhjUVQxaFVxR0F4Q0VBUlVWMWNETURwMUJnS0JZVDQ0ZXk1Y0xCYURLSXBRRklWbGRaTkU1cW5OQjhjNmhRaW1pU3B4SnFwa252QVcyTmF2WDE4ZkNBU2FBYnkrYmR1MjhxbFRwNTdnZHJ0UEF6QlIxZjd0d3F4dFlHRGdnNXFhbXFwNTgrYnRaajdWZ3dBYXY2bW1RdE1TbVp3TWFJSWdVRXFwbVY5cEpOcUtrZ2hKWkxXZUVtVFpZU2cxRmlSd0dwRlB0eG41OVB1TnlHZEdaZ1p5c25PUmw1ZUh5WVdGMXF6Tkk3MjhQai8yN3QwN0FiWWp0UllzV0lBTkd6Wmd5NVl0bUQ5L3ZuWC94bzBiTVdmT0hHemR1dFVxOWpVbnpjY0JMaHdPVzBHR1NDUmlBYzVVYjlRZVhOQjFuUTh3SkFveWFBQkU4NG90VVVvWjJPeEtUcDQxYTlibkFMNEM4UHoyN2R0bkZSWVdIdTl5dVU2WmdOelJnOW5nNE9CSDlmWDFYMVJVVk96aTFWVVNvS2tIQ2JPRVFPTURBN3F1VTFtV01VeWx4Wm1lVHJqNGRBNnZGN0ZvRkZzLytRUVhYM0lKTWpPemtHT21jeFFWVFlrYlJQeE5WMjF0RGQ1KzYwMVVWTXpCNldjTUw1WDJlbjBJOWZjZU01L2x1QU5iUmtZR1RqMzFWRlJXVnNhQmJkMjZkYmpnZ2d2dzZhZWZJaGdNb3JTMEZCNlBCelUxTmFpc3JFUlBUdy9jYmpmS3k4dVJuNStQd2NGQlNKS0VuVHQzWXQrK2ZkYWdFMG1TYUdabUprUlJKS0ZRQ0pGSWhJcWlDTmF4RndBUlJkRUNuSzdyZ21sMmlHYmtWRFJQZkdhbVNnbEFKNVdYbDI4RjhEbUF0VjkrK2VXMDR1TGlPUjZQNTRTSjNMakR1M1JkcndtSHd4L3YyTEdqNXZqamorZVZtYzZCYVNTZ3FSelE3T1ltaTV6VFpFQmo2UnVxcXNMaGNFQmtIVGtrMDRKZ0tzM2hoTk0xM1BUMHA2Umd6bkhISVJpY2JLUnpGQVNSazNONG81NVBQYmthalkwTkdBaUhoNDFsNU5mUkhDeituUU1iQUN4ZHVoUVBQL3d3SG56d1FRREE0T0FnMXE5Zmo1VXJWMkxGaWhWSVQwL0g3Tm16VVZkWGgrZWZmeDZYWEhJSkNnb0tVRnRiaXpmZmZCT3lMQ01ZREdMMzd0M282dXBDYVdrcFpGbEdUMDhQOXV6Wmc0R0JBYVNtcHRKWUxJWklKRUowWFlmWDY2V2FwaUVTaVVEWGRTcEpFdUdpcU1TOFdnczJNNVd2YU9BM0huVFM3Tm16dnpDVjNLdnIxNitmTkhmdTNPbHBhV2x6WkZrK0FVRHFCSjRPYWZVcWl2SnhkM2YzbDVzMmJXcFlzbVJKTjRZaWtjblVXU0tvOFIwMlZOdHpkVTZoVWI0L0drdXdGU3dmbWhIQkZBU0JPcDB1UGo4TnNzTUJnUkRzM3IwYm1xcENsQ1RNbmpVYitjRWdLQVgrMzl0dlFSQUVwS2RuNEpISEhyZXNrRit1V0k2MnRqMEFnSjZlSG5pOFh2ejVsZGNBR0oxeG52ajlvK2p0NjBWR2VnWnV1LzJYMXBBZUFIanRQMTlGWmVVNjYvRUFjUFUxUDRQRDRjQ3FsUStPbVE5NVhJSnQ4ZUxGdU82NjYxQmRYWTJaTTJkaS9mcjFtRDU5T2dvTEM2M0hlRHdldlB6eXk3ajQ0b3R4N2JYWG9xMnREY0ZnRUtGUUNKczJiVUpGUlFWeWMzUGg5L3VSbFpXRlNDUUNuOCtIZmZ2MlFkTTB1TjF1UkNJUkFLRHA2ZWtRQk1FeVVWVlZoU2lLYk5ZQ2k2SVMwelFWelBzWTRFVHppeUtacWs2MFFZNEhuYlJvMGFKZEFGb0EvTDJpb2tKKzdybm5pb3VMaTZmNWZMN1praVI5RDhCRUsxMmJrRkJWOWRPK3ZyN3F1cnE2eHF1dXVxcTFwcVpHdHlrenpXWTZKZ01hRHpQK09ick4zTFFVbXJraEdkQll6elJDQ0Z3dTF6Q1Z0cWR0RDNLeWN6Qjl4Z3lvcW9ydHRiV1kvMDhuSUQwOUhkLy8vb21JeGFKNDlaVS9ZeG8zMy9UK0IxY2FCTy90d2MrdnVRbzMzWHdyQUdQTzUzMi91d2UvdSs4QlRKbzBDZS84YlQzV1BQVWs3djdOYjYzblZzeVpNNnp6c01QaEdITWYrcmpOWTF1d1lBRmVmLzExekp3NUUrdldyY041NTUwMzdISFYxZFhvNnVwQ1pXVWx1TWxXMEhVZDJkblpjTGxjYUd4c3hNNmRPNjAwRVVWUmtKS1NBcS9YaTRFQm80ZVgzKytIcG1sVVZWVU1EZzRTVlZVaHk3S1ZhMlJHVVZrK0VqWEhhUTFUY0thWmFsVTJjSkJMQkR2cGl5KytrT2JPblZzTllEdUF0d29MQzhXWFgzNTVjbWxwYVhGcWF1bzBXWlpuRWtKS3Zrc1VvNVRXeDJLeHJ3NGNPRkMvZmZ2MjVzc3Z2M3h2YTJ1cmJsTmxpZFJaTXBWbS9ZNGxZOXYrQnU4M2l6TTNlYUJ4Q2VCeFFHTUtqUkFDdDhjOWxNWmhtcDdCWUFGMjF1MUFXMXNiU2twS2NNbGxseUVqM1loNkJnSUJSS05SdU4zdVJDWTJmblAzcjNIMk9UL0d2SG1HUzJiWHJrYTB0cmJnRG5OZ3QwNTFlRzFEVmNyS3BxR3NiTnFZUHcrazhYcUNMMTI2Rkd2WHJzVnR0OTJHdDk5K0cvZmVlKyt3eDZpcWlsdHV1UVUvL2FreDA3Szl2ZDFxT1M2S0l0NTQ0dzI4ODg0N0tDNHV4dVRKa3pGanhneFVWVlZCa2lUNGZENXJpSXZYNjdWU1EyUlpwdEZvRkU2bms1alJVMnFMb0RLd1dWUHNLYVc2ZVIrRG5BQWpSWUF2d0JkdGdCdTIzOXpjTEo1NDRvazdBTlFEZUJlQXVHclZLdC9wcDUrZWw1K2ZQOW5uODAyUlpibEVFSVJwTUhyTGplVVYxWFY5Unl3V2EranY3Mi9ldlh2M25uZmVlV2Z2blhmZU9RZ3VKMndFbUNXRFd0dytwOHppekV5Yk9xTTJvRkcra3d6ZlljWU9OTFlCZ05mampROFFlRHpJeTh2SGpPblRjYUNuQnp2cmRxQzlyUTAzM25RTEp1WGxJUzh2RHp0MzFpVThPUC94N05Od3VWeTQ3UElyclBzVVJVRWdFTURhRjE0Yzl4ZTRjUXUySlV1VzRNWWJiOFRUVHorTnNySXlUSmt5WmRoanBrK2ZqbTNidHFIRUhLWlNVbEtDWjU1NUJoczNic1JWVjEyRlRaczJZZUhDaFZpd1lJR1Y4N1o1ODJhNDNXNzQvWDVycXJYWDY3VWlwdzZIZzVrVmxHOW9xV2thQkVHZ3FxcUNVZ3BSRkNtbGxKaEJCOEtwT0ZaNFR6aklDU2JraEFTUUczSC8xbHR2SFFUUURhQ0dNMzJGeXNyS3JQTHk4dXlzckt4Y244K1hKOHZ5SkZFVWc0U1FBZ0RIU3J1SENLVzBSZE8wUGJGWXJEMFVDclYzZG5aMjFkVFVkRjk0NFlVSE1KU2RyNDhDczJRS0xlRStCelBkRGpNTURRQ2l5ZFJaSXFEWml0YXRqVVhqQWNEbjk4ZVZSdmw4UG16ZCtnbW1UU3ZIcWFlZUJyZmJqWHQvZHcrbWxaZkQ1MHZ1Y2RpNGNRTSsvT0FEL1B2VHo4WWw1WmFVbEtDdnJ3OGZmZmdCZm5EeUtXaG9hRUROVjlYNDBZL1B0UjZ6YzJjZHFxdTM0U2MvV1RvQnRtTnhCWU5CekpzM0QzZmRkUmZ1dlBQT2hJOVpzV0lGempyckxOeDk5OTI0OU5KTFVWTlRnK1hMbCtQbW0yL0dqQmt6a0pXVmhZNk9Edmo5ZmlpS2d2ZmVldytSU0FTeUxDTTlQUjM3OXh1VHozMCtuMVd4d0U1U3Q5c05WVld4Zi85K3FpZ0tmRDRmSkVraWc0T0QwRFNOTWo4R1N4Y1JCSUVWMjRNQmJnVElXWUN5ZFIwWmFlT1RoNFVsUzVZTUFOakQvVjMyR3VUUlJ4LzF6Wm8xS3lVUUNLU2twcWFtZUR5ZUZJZkRrU0pKVW9vb2lpbUNJUGdJSVQ1Q2lKY1E0alpCeUJvQXNOY0NCd1pyYkJ5bGRKQlNHcWFVaG5SZEQybWExcStxYWw4MEd1MGJHQmpvNytucDZXdHZiKy8vOHNzdlE4dVhMeC9nNEVWdElLTWp3RXkzS1N4dHRJMTEwVWdBc2ppWThTQURTNmcxcDZUeDVpYURtYTJ0VUJ6UVdOODBwOU1KU2lsU1UxTnRyYnRUY2RaWlA4Uzc3LzQzR2h2cTRYUTZjZWRkdjRMUDUwTnJheXR1dnZGZm9TZ3g5UGIyNG9Memx1QWY1ODdGN2IrOEU2c2Zmd3lSU0FUWC81K2hDWktybi9vRC9INC83bjl3SlI1NzVHSDg0ZC9Yd09QeDRHYmI0T0dxeno1RFplVzZNUSsyY1ZkNW9Lb3ExcTFiQndCWXVYSWxicnZ0TmpRME5LQzQyT2dCS1VrU1hucnBKVngwMFVVQWdELzk2VSs0Nzc3N1VGOWZqMEFnZ0t1dnZocDMzWFVYQ0NIWXNtVUxycjc2YXRUVzFzTGo4YUM4M0ppdldGVlZoV1hMbHFHMnRoWi8vL3Zmc1hqeFlxdHFZZGV1WFdodWJrWkZSUVVVUmNIT25UdWhxaXFDd1NDYmxvWHU3bTZtMUNBSUFqdXhDZWVMc3lESDloTkFMZzUybkpvVEVvRXN3VDUvbTJqalgyZWtEUWx1N2Z0eGJyQUUrL3p0U0pzZFpQWXRtVW9idHM4Rzl5U0MyR2d3WS91MnhxVUhCVFJlcGZITklIdDZlakJwVWo2OFBpLzhmbk8rcDFuQW5wc2JRSDUrL2lHTndmczJWc1BPN1RqNTVKTW53RFpXVm05dkwvYnMyV1A1Myt6MXBteDRqTDJaSlZkekNtNE1vRlc4ek14VVBzakF5clc0c2kzK2N5STIwTVZCamxJUWdBNkQzU0grbkFoc1BPQ0VCRkFiQ1hBandjME9OQ1NBMkVnS1RVK2kxdXlPL1ZFaFpuczkzbS9HWUdiZGNxYW5mWEEzdFhkdFpqQnp1ZHhJU2ZFUGF3TEpGRnAzZHpjS0poZFp6U0F6TWpPdDRuSy9QMlZNZkUrT0piQ05HMVAwaEJOT3dPYk5tOGRjRUkrL3lLU21wakx6bEhKZEhBalgzWUVRUXVKVUhJeHhnY1R3MDBFQUNFa0VKVW9oMktCSFJnSGFTSEE3Rk9WMktHQTdGTE16R2VDb0RXZ2pBVXkzQXd3Z2xCQlFnSUFRQzJRR3pBUUJBakVoSmdvUStSSW90aG1sVUVOcVRaWWhDQVFDSVZBMURkbXBxY01hUWZyOWZoQkNFQXdHb1drYWZENC9VbE5TNGZWNDBkZmJoNzdldm1QK1JNNFBIbHZOYXNZTjJEWnQyblRVWG9zZjJOemQzUjNYeURJY0RvL1VxZGRTYm9xaVdNck5WRzk4RFNyaEZCemxsQnk3cFRaVGxjRkU1OVVkcjdJSUFVa0VQUTZTbzRGTUdBVm95YUJHRWtCOE5MVkdreWkzWktCamNOSkhlZzV6OXJPWnNwenpQNUVxczhHTVFCRFk3RnFCQ3NMd21ScVNKQmxnazJUSUp0UWNacUt0MCtHQXJ1dVFKQm1abVpsVzVRQnJCQ21LSW9xTHB5TGJuRVV3c1NiQWR0UlhRVUVCY25OejBkYldablhwVFdTZXNvNGhJd0dPM3hqWVJvTWNCemhvbWtZSmt4aERRSXVEalUzaHhVR1BTejBaQmpEVHRCMEpaRWZTeDJiN21TbXBwTUNqaVo3UGRjN0FzSDJEOWdiUUNBRUJBUkVJRlFoQm5Eb3pGQm9WQlFHQ0tFSzBRMDJTSUVuTXAyWkFUWFk0NEpCbE9FeXowNmdxRUNHS0FnS0JnTld1bXpXQ2xHVVowMmZNT09TQjJSTnJBbXlIZFRrY0RoUVZGVm1BNit6c3ROcVE5L1Qwb0wrL2YwVC9XekxBY2Y0M3E0TUlEemt1aWRpQ25HbWVJbjY0UGEvY0NFeEFZUWgySU93N25neFlOcFUzcWorTnNuMDZpbUlqMXMzQlFHNFU0UEUvRytZai8zZHNUbjhBb0xwT0lRZ0NESjRSYXZZL0E3R1VtY0FGQmtUVDdEUnZlWVVtTW9YR0FjMVVhUEhCQVRjOEhzT1hwaW9LSmsrZWJMWHJadE9pb3RIb2lGQWpWVjBnVDFZQjlUMkgvMlF1U1FPOS9qalE0N0lud0RZZUY2VVVIUjBkNk96c2hLcHBoMVRVRzRsRTBOL2ZiNW1qYkV2V3lKSnRsQUk2cFpBa0NVNm5jeVRBUWRNMHZwckJncHdnaUtCVXA3cE9UY0Rwb0JTZ29LejlLZ0JDVFBDWjMzZVlJQndPS2k2Z1JFYTVqZHUzZUhWdy9RenBJU3E1K0RlZjRIZGN6aFpsK2MraUtGclBZZWFscG1rUVJXa1l6QWlmZzJiNHp5eW9qZVpIazAyejArRnd3dUYweEFITjR4bWF2RDRRN2tkcGFTbHljbklRQ0FUZzlYb1A3Z1JiWFFVMDlCeVpFNysrQitUSkt0Qm56NWdBMjNpRVdsVlZGVUFFZUgxK2VMMCs1SDZOMWxLRGc0UG83T3hFZC9jKzlKaVQ2ZnY2K3hBT2hSQU9EMkJ3Y0FDUndRZ2kwUWhpcG5vYkdCeEVKRElJVlZHTXEzcGk4elJ1WTRwTlZWVklrZ1JkcDBTbk9xaXV3d0NjTVJxVlJXVk5BQkRaNFlCdW1ycU1SeXk2TjhRbkFrN05JWWw1eVlNU0IyR0NqZ0kyQWtLU0FpNE9XUEdQNDM0bWhKZWFvSlJhZFpqcy8wZ0lNU3BFSEE3bU82TU1haGJNREQvYWNMTnpSS0N4dGtJdXVOd3V1TjBlZUwwZWVIMCtwSmpwRzJucDZRajM5Nktpb2dJZWorZVF6aXR5cEtER3dXM0NGQjJIcTZPakF5QUNjbksvV2JkUnQ5dU53c0pDQkFLNTZPcnNRdmQrTTdqUTIydVpwd01EWVF3TUdEQ0xScU53ZXp5SXhXTG83K3VGSkVsd3U5MGptcWhNdFdtYUJrVlJJRW55VUpCaENHNUVWWTIvNFhRNnpjZVkvamhSaEs0YjFsczBHaldVaWloaXlKWkZITmdTcEFReGZtQW9ka0VUWGl5U3RhTm1iYURpMVpUMVBHci92Wm5ReklQTkFsZ0NwejhJSWRBMERRNkh3d0lhTXk4QndPbHd4cWRxaUlMMUdWRks0VExyTkYxdUYwUlJ3aGVmVjFsLzErUHhvS1MwRkhtWm1YSHBHM2FGNXZmN2taS2FpdlQwZEdSbVpDSTdKeHV0dTVzT0dXb1Rhd0pzWDN0MWRuYkI2L01mdHIvbmRMb1FMQ2hBWU5Ja00vZXQyMUp3L2YzOUNJVkRHREFWM09CZ0JORm9CQTVaeHVEZ0FIdyszNGcrT0g2THhXS1FaVWRjQWI4Sk9LcUdGQkJDNFBmN1RSVkhDYVU2ZE1wTVZtb01yeUVDWkVuaS9YU2N2MjdJRmNZQnovcUhETzBOZ3g4Yk5HS0RHMVZWMVlJVnBZQ21xU0NFUWhDTWFneEZVVUFFQVpKb3ZDZFZWYURybEJyRjRrTXF6WEw2MnphQkdDYW1wcW5VNVhJTnl6ZGo0T0toRmdxRkVBcUZVRnc4RlM2M0czczdPdERXdGdlelpzMEdVMzNmTzM0ZTB0TFMwTmZYaCtwdFg4THBjR0xHekZsd213ck40L1hBNS9XWkxib05oWmFSa1luczdPekRtbHliK2Z4aUtOV2Q2RnYxdndDQWpDZCtpSUczNmhCNXB6N3BjL3pYSFEvbktVWFlkNEhSanNoNytXeElVOUxSZTgrSEIvMjZveldiUEpiVy93Y2FMdGJ3dU9MVHF3QUFBQUJKUlU1RXJrSmdnZz09Il0sWyJzcmMiLCJkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQVRZQUFBRTJDQVlBQUFEcnZMNnBBQUJLb25wVVdIUlNZWGNnY0hKdlptbHNaU0IwZVhCbElHVjRhV1lBQUhqYXJaMXRsdVc0cldYL2F4UnZDQksvT1J6eFEydjFESHI0dlRjanMxeFZ0bC83clc2WHF5SXo0c2E5RWdrY25BTUMwTFgvOS8vNnJ2LzZyLzk2U212aFNybTIwa3U1K1YvcXFZZVhQN1Q3NTMvOS9QZTUwL252K1Y4c3YvNzAvUFg3MXg4L0NIeU52dkxuQi9YOStmcThmRC8vNHhkK2Y4WXovdnI5cS8zNlNXaS8zdWpYRDNqam55dndrLzN6K3ZORjh2M3c4LzBuL1hxanZuLytVSHFyZjc3VThldU41cThYbmt2NTlXLzY0N0ordnZqMzZ5L2ZxS3pTeW54UURHSEhKOTduditubkN1TFB2eS8vMXZQZndPdWVHUG56RTl0MXZ2eStKUmJrTDdmMysrdDkvM21CL3JMSXYvOTAvWDMxLy9qVDN4WS92TCsrSC8rMmx1WFhHdDNsWC8vZ3lYLzdmdnpqWThKZnpPR1BLd3AvL2NFWTZaOXY1OWUvMzdmYTkrMmZ1M3RUWVVYTEw0czZpLzM4Zmh0ZU9GanllSDZ0OEUvbDM4eWY2L21uODArNzMzdXk1ZXVlOStDZitmUW5zQ3ZmOWFSblBlL3pQZnQ4bmMva0VsUFlvZkkxaEJuaStWNkxOZlF3by91VS9PZjVRbzA5cnRqWXl4bjJ4ZmFsR1A2NGx1ZDhiaitmTjUvR0o2K0hsNGFITjN2NGxYLzd6L1hmL2ZCLzhzLzFmZE1sZXU3MngxcHhYVUc3NWpMY09mL0xxOWlENS91MWIva3M4TzkvZm0zLy9TZjd3VlRad1h5V3VYR0Q3ejErM21MazV4KzJGYzgrUjE2WCtmcXp5YzlWMTY4M1lJbjQ3TXpGUEpFZHVNc1Q4MU9ldTRaUW40ZDFiR3pReTVXSG1NSmdCNTZjdytJaVE0cXhoS3VHRnZ4c2ZxYys1N1VoaHhMOE50akVSdVJZOEszR0RyMXNWa29aKzZtcFlVTnZqam5sbkV1dXVWMjU1N2ZFa2tvdXBkUWl5TDAxMWxSekxiWFdWbnQ5VzJ5cDVWWmFiYTMxOXZiUUl4aVllK20xdDk3Nys0YnI1WU5lM3V2bDlTL2ZHV0hFa1VZZVpkVFJSaC92eEh4bW1ubVdXV2ViZmI0cnJMaUFpVlZXWFczMTllN24yaURGVGp2dnN1dHV1Ky8zdzlhKytLVXZmK1dyWC92NjkvNnhhNzkyOVovKytSL3Mydk5yMThMWktWOVgvOWcxdm52Vit2c3RIdUVrdTJmc1dFZ1BPMTdkQVF3NnVHZDNlMUlLN3B4N2R2ZUFVK1RBUldiMzVscVBPOFlXcHYyRS9EMS83TjAvZHU0LzJyY3J0LzlvMzhML2JlY3V0KzcveDg1ZGJOMC83OXUvMkxWbG5KdG54MzY4MERXOUk5N0hhOTdBRlgxamZmeTF4QysvOGF2ajI3TnVZZElmLytjL3ZWN3ZaNy8xNFY3THUrcFRjMGlsdld4dUhKM0xHSFBpVjJsRUwyVy9pWmVNSGRMWFdueTVnOW5XTSs4OXJqQm5ITHUySis4NmRzeGZlNEc2Yit5MVY1NEplOXFUejFxMTMydnQ4WEFmTSs5M2pUVkNHbnp1RS9vTXFWNVA0NjBtNjRYMTNYdlhtUVkyc0wrWTcvNThOZTM2OExPWTU3M21IcDN0SFBVZVQ4cGpzMk9BLzhzdVlsVFhUS0xWcythcUw4UWt4MDE0ZmRmTE9uUXVNZVRCbmxiV0o0elNBZlkzei9xTlZOL3dMU3hyZmlYT1hFZTZJRFYxM2l4R2JRbUQ3a1NaM3N2M3NNRmYzU3pHSFB4eHhhL05MOXhjbnY0em9DRjE4cTZ4em9CenpxOWRiZVdTMG1LZFpyNUh1VnNQN1BzY1FIN2ZxNFUxUzF4aHJQdEwrVnNZMEZ6NFFZOTVyeHBxM3lXd0VTbVhxN1NIUGZscTV1YU1OK3U3Mll6TzY5ellmN1A1ejh5cmNUSEFRU3o5dTBjcTF6RGd4YnVVdFFhd3dxMkZ1UEVqREJPempXc1V0amx5S1hQRS9QSUpCY2QvQjUrODQ2aGN5b01oOEd2WEdNZXEvanVMK3hjL202V01XUGFZOGNPcDkyemZGZFlxSGZlT2liRE9QV01iS1V4WHBzYzJWL2JXMWo2MytYd3ZZUC9IVzJNdEliQWhSUHIyZkJlaC9RRjlGbUNDS2ZXNXVjZit4bEF4NG1lWHdYS1g4UlUrdlJSODh5UENZeWZ0VGZlYldudDVUOHg5cm5XMTh1VUppSDNIbk1xTERYNy83Z3ErZnZQaXI2VnZ4L3B4UVRHTnNIdGJvT1Axc29QOHRYd1JBTUs3dVVwUUROOEhGVWJuZm5NQVNjU3MyT1YrNzdOZWJDQWFCUmYvNW85WDc2OWVVaDR1dnRmdmlkL1ovZjAxWGVGbGkyckJXcDVaZ3VheTg0UW1ManhrOC82c0lleUdUMzIwbFRCaHRZU1R4SWNrUFF4My9QYWo1dzVzYW9SbnZQVmpLN2orWTVjZE1LNndCUmN1MWRiZkwvTGhrWGU0TmxHNmw4YU9EdWdTSzFSVCtiQmpYamxITDl0N1h2eWhGWmFraDZmbkcyc0xLUWZ1YjcvUzc3ckc4MTZwakZXZWQvSXBnR3R2QkFLd0dPTi84bHZ6UitCdk5VN3dsTC9PQXo0enB4YjN5Q3pNZmpUSGNXTlUxOE05Vlp6MFRiTTI3aDVIZTlkUkVKbTlsSTdKTGxMQTEwSmdKZitkeVY3L3VUMC9YdkhLR3c0WEpqZk5CUmNpVCtjZXBEVTN4amVCVlR4YU9scER3VEtKcnZVdEd5TnM3eTdsRzREQWdwRGxjdmZSaW12RVJZOUtHSjFWVEhvSFY5UXpWREVCcHBPUXBya1dQclR6eWcxdXdubjQvL2V5aXJFTllKS1Y0cDc1MHpEMkFUdnN5VnBQdjhyVGRMV09OUUpRZk9KdThkQ3V6WHM5aEM4Q1lreEV4TWRQR1J1OGlnUlZJbWtwc2QwRTgvUmlHdnRhL1U0NWdvK2RuY2VuUzhaUUhseXJQeDE4WUo5bTVCY3FjY1pQV3FXK1h3dDdFakluL3NSdmJhSUFjWTM3d0ZyWjVrQkVYdW5leE0vK2VOc3ZpQU95RGRCMTE0Sm9Xa0REQTBaaE1jL2dkMGRxL1Q3c0crKzVRT0ZrVkRXQ0RZSmMyMVhreGVpK0F0QmtRaFBvY0UrQy9pRCtzN0oxUGp2aTl4M0dVeDlEenE0NVhXUGk3dkVSRU1EWXhrSnkrYnpsbkFUK0dybWlNY3F6dU0zYjBBUVJnT2g4SFN3Qi9pTitHU0FyZk85SzhTVThjaHY4elNVaHFtQzY1ZGtwRzhiQUpWWi84NHRuOFRHTGxnRnJJeFcvanprbHNBRXlFYStSRm9zT1FJNmF4U2hvRlB6cS9uYUFPc0MyOW9kWUxRTkNSZFFqWG96QUhjQWlxbWphMGlJY2Y1TjF1elpPQ0laRlNFZXF3RDZVWTBsQ3B0WURqRVNRc1M4aVRTRzRWakNzTkw2dXZaOEpmTVNWaGJFdnZWZEphOThnd2Q3M093aTNrV0NlWHV3OEF5OGY3L0h0WXBDNVUyRWxaOXZJY2JBSy8wT0xzVnZZUHB6dEprQytoT1FidXBkMHB6dXZ0VUltM0w3YkN3VUVTc3o1V2JsQkhPdUhTM1BSbU1LUUpSTFNZREVmc0JIWU5SeStsd3gyOWxzdEJiemlRUnZDeHdkamZkQVVHQ0F4elVXRnhMVmFDT0ZqQTlEZ3hRZmRaSE1KSlJlNnpIQ0N6UlBlSVVoNE41ZlA0c1AxUUhaNDNzME5zVTRwRGFJbWRuQUxCaktuL3Y1Y0ZqZVc0eFV5UkdpK0hYTFlDLzZDeEFLVmNDUDhqaHNocU9abnZ3VG1zRGIybkVEVmlUTVFaZS9haVg3WUtKYXY4SXZReGc1bTk0RjVZRXozQjlvQUc0bUw3OEF3OUtqZzNVUjBkaDRFWjNYZW5OaFozcGRieFdUNzNudGRTMzVCTkFvWWQ0c0R6M2c3THZwSkEvQWFLQlp2Z0JNWDFuMjlXTW1jSGRiUmN5Q1lKU1ZXNVhkNnZmU2VSUGdJOEJ0QzlhdWR3Q2dtdUE2bEkvN2NCL1dRQmtBTVpCSytXTjVRRndxaDVBRlkxYzZQQm0vVWVQbHMzblhpVnJFTXBDQjBubzFEWDFlTUl4dm5lb05KZ2dFTHJjNmFha3ViVlhsWWw0VmxCUUlrYk1yTlRjMGRSY2VrR2dob3FJYzJjK3Q1NG9rVE13QXVRQWJlZTJFMWkxRDZRTVlDMGdDcVNlUllGOHl1bVNLQWpxVXFIaUJJY0E1NUdWRXdzWlJRcERrL3NHK3hJWVNyQ1UwQlIzWnVXZ3BzWWNMNTJEWEF2R3ZLUFVhaUF3SUtsd1dwV0hQelBmd1dWQk5ranRBd1RDT1diNzVnbm90K1FoV3Z3S25ydm5yQ205WUwyelR5UFRVQXpJUUw0REprZEFXWERncWhQa0NZWjIzZUhSd0JhTnhxMkdycU9BZzdlb1BaZ0FoNER1SnFodmRUalROcXNCY0pYVERDUkhSOTJNRHBad2dPTjJFN3c4cEJhd0l6RGxvRDRIb0I1SENMVURKNDlRYldDeFRpQW9uK3lBRHM1ZTJ3aS9LQ25FUVhqSGIzNXg0dkN4ekJVb0lJVzhyV29VV2dyMFFiL0hPTnBCV2ovMWl4VHVCaFkrK3B6NTUwR3lzRVJ0eHc1cDRqSVRkT0loaVVBRUo4QS9JUXJTZVZoWVlKUWpzNzdIMitjQ1F1R1g4a0hCQzhPemlGSEcwaEpjSXNZVk1nTHRndlVxVVNQOVVNRjBRcGp1OHVIeDZzV2duell6SG5VOEhrcitjS3h6ZFNBVHlURFlJa1RyYnVnVEFDUEJXVGZyR3JoRzllTDlIS1A2Sk0rUk5HVUpQKzZ5OTFiaVhCczdpcWdKNEMxQUhMaFFSQ1ZnU2tUSlE5WWtlNExOUVB2SWtkSm9DT2lsSTVDZEdBazlVWGhJNEVLWng4KzhHR090OFhxcGJDeTlvRFU1Q0t5cEsxV2NlRmZVd1l3cmNDb2hYOTNSTXNFSDdvVHNBRTBibThBUWd3Q01QcFViUkIwSTNGTDZRRW5JYTBzTXQzdmRBazMwTmNoS0d5cEhEYUpqWGV0MUliVStxVGk5azlaSmdmOVBnMWpIa0ZFektHRzZLRjc3SWgxd25oUjZqMXBpRGpqU1VPb05JTkZCcHJCeUdPLytCWmVzejc1YzUxRlJUbEJvbGJJT0psL0JwSngxWmVFY3YrakkxM2w0K0N3b0VZOUxLMlJFVmV5SjB2VE4wUFlWTUpGeDJUYXdneGdqT1lpeEh0UElDa0M3Y1lIKzhQNG4xTFRmNjlOMDZ2amdUbVVtaGdDNHVIdzRFbzJDVE9BZ0VENmlCVm53dUdSWS84dGdzMGpIc2hCRkFGRGZjTm1FcjJjeEM0aStCdUtxeXo2Z01uN3FBUXdaTm9qbmMxMVN2bUdIQzh1SEVSWGdrTWJzeEc1TWlHWVFKUWZrdDZBTmUzNFZITm5XY05EaUQxazFaRTQzNXZPekZwNGpBRDRjZkhzaE1Kd2hKZzVUQUZGdWY4MWdBOE1DZURVZHNHSWx5NUp4T2FJeExwMkRzd0Nza01uc0I4THZnQWZKeDlOd29Nd1ByZUlOM052UkM3TWVNWE80YkVUcFlmUHY2Q2cxd0ZpOFYxMXdUcmVnc2IyVWE0WkhNZ0Jhb2VpV3N1RmFOSlhMTThwQk5wOXRFb21NZUlDZUQ3eXJHemw2VmxLVEUvU0RaTFA1RVFnQXZrK05YamwyWkc0QWRsb0JPNFNDVlN3ZmlJSVpWL3YvNUVNelJJaVVlWEptcE9CZDRJWTh6clRSZzhrUVcrelc5WFdHRTBSVEpDeDRlSkVnQWdkd0h5ZkZONVh6RVMxUDQ5c0Z6bzMzb0VRWllsWHkwWjdER0w3M083SWpJY1RjbDZ2UUNJdjA0VWJrWm9maVdQRDFJK0V5U0JlQVBIYmZ6cFhRaUpIUzUwWU02OXcydkwvZ3k2QlBQS1Z2eGFEaGNqRVloUWliMmJnSUFDc3poUFRUZFJxYkcxeUFRV3RKbGhmL2c3M3hYSFM1QUJiS1FzMW82akNPeUFVaW96ZWJBd0VUVmZSZzM2U1lnM0tHM0pOK3ZORzdYd0JOamQ0SEtJK01BUGNha2k1SEJ0RERlais5Z3pqR0tZTU1USmlBQWcyNTM0WEZrTi9DR0doY1ZkRTB2ZUNrNms1YmNpSy8xelFkeUVseVRxSGE3MjVVR1lRREh6dVpHL29ucEI1WXJTZlJDVGZVTkdZZFkzbkhQSVpBSnlELzZjWHp3S0JvblBjU1BBRlpyUGU0WlZ3ampHcU1Yd1NpUjhOUERPeFNDenF2RDVyNWFWZ0lpWVJKTVVSRDVlOGI0cVB2aTdGNU1DNzBjb2J4Z3BrcEFyWWkraEtZcUxwZDhrNGlOMyttSHBrS3FnWnNtNWhIUGtsWGx2K0lxWnRJNDc4VDJFMjI5YnYvN3BRaEFlS1BCekpnRlNZWTZzQ094dEMzaDNBS0lEMmlqZVJsbFcrOEdMSGlSUnV2aGw2VHVjSytKVmhJWU1PVUlPUURVS1pQOExMTWhtbFhCc3lEQWh5andtbDcvK3Rqdlg5M3R6Y05FY0Rqdm1EYmloblJPT2ovYVJma2MyQ3laMUNHRGJrREZ3RnhTcUs1dlVLMTlaRi9ybUlSd1Q3THhUajJaTUZXZjJGaWIvblpNNUtCd29RSGpkM0JaczlnOXovY2RGc1ViYks2OFpLL2x1MUU4d3pPK2g2MEs4d0d6OERWblJlSzhscjBWYXhOZE1MNlJ1OTNHYjZVV1dobmpCZkZPcnBzclVaVmtVaFhHRW9mMzlxMDhtWmtpMVRYK2lMWUFhK1hKOXVLSjltOXRtSFdDRkhicXlaaURXb0xpQWZDekhDQWRZWXFjbXZCRlFEMytEdDJic0dWNEhxUWFuMEtrUTlnNFRPNG1udHlKQ1MvdzNWOElLNkxHeHdqaFlyRkFOZjhwK0lCVXllUkd1Q3RFVE1ZVkE4SlIybTBNQzg5K0IvWDUxdythZ0tPd09rZmRPNXVLV3ltaWdCQXZxTEdnSzRNNTFuOHY0L3V5b2Y3NlFqejBHOXBDeGpTaEM3TVlQd1IrMkZPV05yL0QrY1BUU0c4Q0dWbWdOSlh0L0JWWUcrMFBCaHlHaEJOdzdJVFBCQUdXcVJQL3hxbzBnYVpnWTRQN2VBRGlrQVRWeTdlZUZ0dTZ0OW9KT1FjNEZGUmc2dlByRjFNR2VYVC9FK2dQcXBnOVdodFNGZ0FPK0NJZVozSXNLeEY3UVFlTDFjendqbTNzc0NNaUNlcXZUbzVTNGh6a24yUTF2bkRvNkhWb2tocXNMd1RUQzJBTUQyUmVobnNXQU0zU28yd2Z0aHlaQUdENTM1WG41VmFRbHdtdW13bTBRTk1DRkNEM0NOY3Z1N0RNZURVeU9aajc3aHg3aDVOZ0hVdElzMkRzVmlJYktiQ29hVnAwQ01haDVqQVAzZlRGTGVCVUVsaWdIUCtTU0wvVXVsa3pRM3JmSEhKQU9VQ3g2cXR5MGN5a0FlcnNYa3lVVEdCei9HZ2F2aEdRK1p3UGxIRmluUS8vUU8vREJWL2tIY0dXY0FkblU0RzRnYXpEQjRRN3lsakJicmpkci8zZy9QMkwxa1c5bXNCQVNZNkZZUk0xMzRaS1lEZnNpQVFFdkNTZDhDanNQaTNpZjIwVElSR3M4eE96cmcvM2locUJQV0RDTFlXb01BTnZiN0JiTFNweUcxU0NRUUY1QUdDZHBHeWYvekxtaVRKLzJGYyt1SUZxL1R2bUZyTWRmT0JueHlXVkxXN0R2aVZFaWtlRHhBMVdGbTBYc2hIVUI1ekJyTk1HRTFtNlBNd2hSWUUwenc1blFIZXphRFZqZUlCTnZqQTc1N3A4VTc0UmpMUE9rM1BkNGJ1K2ZVQlJaK3ZUR2N2a2x2OG85SW5CUDZBK3BKSnZoa1ZoQkl4ZFRoQkRlZ3hCUU5xSmtJQWlHai8vNG5UdGo4TisrVkxyZkNiWHc1NFZCQlJPaUd3R0E1Yk5sdkMxdlRPQUpDaXpUY1laMG9nSUI3bU5sWlE0dnRnOGVnWVZRUVM0K2VLYk5kaURjQ2FzREJ6Uk41QkY5TitXSlZ1bUhQUlYrQk1pOFQvbU1mcWw1MUMwYmVmZEVXdyt3cm5rd0F5MEU0d2xoRHh3VGkwRFRXSDR3M0V2b1l6L1pIUlYyQjd3emVJMndOK25yWWtUUHI1UTlpcFhGL3pLTWluai9JZ01CWFd4cHRpWnY3M2pqby9HQ2FXeWZLUUsyYzBFVEw2d05JYm9udXhYREFKbU8zR2tSaHRGdWRyaWFha1ZKODVZMzhTU1pSTWRBdUZ6SVUzeHVYbStlWjE0bWFqMEJ3eWFBL2N3T3NzcW1HRUJTTG4xZ0diZUhtaE5HVVFNQkNVYkt5N2lVMFJWak5jQ1NBbmdFYXJrc21kWEFJVmozQ25TakRKSS94Y3lCVEdRek82SThCNTJSMWR5NGFHZ3VLM2RBR2tCby9XTFZva2Vxc3M5c0R1NHhWeUFQVUdXK3o4UitzZlIxa3NFcG4zT3VBRkEwejRWMGpxbVNlK0tCRWFpLzBvVjFZRmZMQitlRFBZTVd3NlFGd1FkMmpJd2JnTnMzNEZxTEhZTUlSWVR2czhka2orSFIxNEtOTmMyZFQ5Q2JIOHdvOUM5NDZOV00vK0lzc2NuckpLQkRMd0xYTkY1b25tZUJoSVpCbk9ySnpPaVJ6Z002UmhEazM5aUpjMGhvRXgxM3JIQzhXYWJXdlcrY0JUaDdNaVRaL0RyeElSSEtzRGRDZG9OWThSVkhLQUR6OXF6UVE4ZTVJQU5RL08rY2xqNEJHWUI4Ukk0OEFSVG5nckVuM0J5RmpSRGJyUGlGcStVWUFXaENUWmxkb3d3djhjak0yRjQzbDJDKytiNkp0ODFjWHM5bmZZZ29BMFlJUGdMc1FOcDNlZnp0U1RCRzBIQ2I2YUhXTEo1d3VraTdScys4QWJTSFQ0V0RaVVRwalNjWGVZZnBYVDRjV0N4Z05wdTFjNFFWRXNDNWE2eWRwUnY2Z2pvd1BtcThrbkVKTkFEZzRubkJCMU9RcWQwSW5JRFlBc20vcTU4ak1OZzBQZ2pId084OHFuampTK3dKVUI4d0NnN1hJSlZRSFVEUU9MbDJuaDBIbmF6Zm5IeGE3ZFpFK0tYaVpNRWNqd29jM3Y2a0trWDZDQkFMRmgxTXNXV3VBbFQ5OW92cDRBYmZJcFJnMjFnWDZIdmwwbk5KcllzQkgveCttVWd6NW54b0hvZ1ZMR3NTM3lzd2hneGhkU3FMRkU1MGNrc3NCWUJBMStmaU0vT1RRQ1RJRFBqSmRYc1lpRDI5dGIxY1VvUWJ3dVhQZ2NPUXk2QUZjeXRtZzdjbnVSOVdoNnlMdkJHOENSYWJNcEg5UFd3SE81ZFFCNVQ5RjVJNkttTWRreGcvQ1p1WUhhdkpIc043MExZWW82eG1zbXZmbmMwOFlLRnBIQVQ0SUxFc0ZSL1ZOdkVQcy85a2g2d0tPck9oUHpQVzRXRkRmV0R4ZURuc2s4Vk83Z1g2RXdhU1lXYStjVWxSOFh0elNXQXFYQTRYR2NyZXdkM2s3bkVRNzdtUTBPOTNrdCtzL2NWR0pwT2trZjgwMWhhVlFjU0lyaUVPeko1aXhDdzh6R01jZGM3bUJ2VlN2TkVMajlpTkMvTXAyTkVEUzlrZllRTFcwSUVtTkllcUhsNFVqS1Ric3liUXROMTRXSXRZSzM4RHFUSWJXR2NrSm1Rek9KYzZlbnE0YlYxVHpQTGlEalJYN053RVRVb0pPMExhY2htSWZGbkZrcUZHa3dPdHN4ejNTb1VQdmdqZGNBc1V5a2xEd1RBd055N1ZFMCtXTUo2REcrSnNndzF6ZjZzUXVzZk45ZCtmbXNManpHektmbHpLUWNCL1F5OWdKN0QzaWVhMmtxWUFjdUZRQWh6M3k2WW9QMDljZjMwbDNEZUxUMjdZeWd0M3ZvSk9uWTM5aEk1VWdwOEZ5dVFQNnM3N3dpTHhCNEgrZVZqb2dvL2liM3cxaUp1UEJMT0pWMWgyOEl2NVd5STdzYWl1VHJBK0JSaXl2bWF4Z1d5V1dJYlFSZ2doemw1cDVWZ1FtbVhVQXNzaGlWZlBwcG90WnlqejNtYjVaR0t5Z1FRL0FoTUFYMFNlL3M1dFRFSUhFV2NyTldHUE9XMDBIMEhyQzlmSHZXWWlEQmJ2TVhEN3pWbHVTNDkrY1JZczhKQVd2LzZtTFloNS9JemwvMzRZME9WNUVKZHJqVXpHVnEyQXlJcVFMbkZxUDVVUjZCemUzK05wZkFVNCsvV0RYOTltTy9qQlpkVnFPblVHZUVvM2tmZXd5Y1NqWUtrU3pySlkwWURkQU9sdzVLVlQreFBqblp3NlFRdVEzT2tDUWFMUms5am1BY05NMlhJbHdqWVV0djJjU1NTREF6SDhzVzRsK2RvSm5udnVGc3Q0VDZWSnd0ZWdUU1ZzWHEyY1l5OE5SZHdmRjIvUVFnZG1lQWd1bU0zOFJlakpoS1ZIVndsNys5UXBpQWJ6a0lzWUlPaEVNL3RhRXVvR1NNRTlWT3NlcDdMTmc0MElyQVhoWG00RmtySHFucXh0REJ6OUJCNmg5RUhDNEFuSVVqMTlvU1ZqR3dvQ3ZHaWV0ZDBWcnVqQjdDczMwRS9lZkZzVGdUNUNUOFl4UFlMdVVHYmM0Z1Z5SStvdi9OYjZaaXdKWjRNWVdjdzR4V2FWSWp3aG4rTndaQ2ZpbDBqYlFYWW85N3BZZmdnazVBblVKdUtCRG9WWUFIc1lhZ3Jlc0hsK0RpeVc1c2NCZUZod1NhalRHM09GRTh3UDB2YXVhOTFjRWlJZGNLNHB2cWFNTVlMQ0JzQ1gyUDNqSkFETDEwWG1BTWdSQ0RjUmdzdUhmU1dKMmdJdUxndDdJS2UzQ0pDZTEzd2p2amJVQ3A0YXFoTFp5anBaSmpZZVFOTWV1MGtFendxNGR6WjFMZXdJNFpWYmdvUllWUWJQM2RDVzVmMmdRUmNYa3p5SUF3ZHhWL1FNMU1sVVRBZ25TbmJQa3ZCc0REeGN4SWx1THVnRy9rQ05lSkxqcnlJL0lCcFp2VmNqbU0zZ2dreVRJNFZ6TnZteWJjQ0ZyS1N5SU5mUmQ2ZzFCRkVxVDBxWXg0Q2RtM2oxdkJ5dVpDR0psSGF0RHdHUmNGSHJiS1pWWE5HalFleUp2MThlRk03aWdmWExDeXVZZzU1ODJGd1FGdmtMdVRXQmcwSXBhWEovY0l0OHZ4NjBiajB6RzlPN1NkZnJ1U1hqYUU5TTcrM3FjR1Q5bm50Q0xLelZZZzhDYnhQdVVhZmxZMDByR3BpQUZSZ0VCVGdnYWk3QWo1VENWaVgyNTJZOURTWEU3SVp5NUJmaEo1NHhFbTJJMU5FMWg3RUhhd0x3akFJcCtGN3ZCdjVhTGphUHhTZEFoNmN2ck1ManVWODZ6MlJwUmV0OGVnOE1EUGE4Q0hDc0FSalNJQTR4bnRTaFpTUHpNdGV3UGxRQ1dEZ3RsVWlzTWs2VndaTlFMQytCdVJQQThwczhJd0NOUjNyaTF1ZzlvMEp5ZTJ5ZCsyWFZtalU3MENQSVFtRmhrWGd4d20xYUxPN0lQT1ZSckR1OEZESzRnckMwNWI3OGc2UzIvcUwxNzFJWm5tejZ0S3BrZWtxSENzRWlzUGJhTWlDRDd4S1Q4V29ZMEloR0dTTCtZelVSVElSVmc0dncyOWZEajZBczBORUd1MStvOUtQOVB6eVl5M3lRYzMxN3dLV2ROZVFUc29kSUlpTVIxM0YvbENvV3ZLOHlkcnZOYVlFR1hQQ0cwelpQWDk2QXAyWXIxbUJtV0JwMHlsTW1naVp1aXNRR0t3WjMrcDFqUG13Y3FJV0FGQ0JuNERTU0sxSFpQSVduYVEraDRVUHlXVTdTTVAvazBXZ2xkT0h1T3hkVUIzdC9kMGpBdkU0MkIvZlM0TURaTHhENEpscHllN0tOOFdZZ2s0aUFtdjN3S0lncjhkd0NDZllpVzUxMUV6b2VOTnBGOEo0UFNKSFEwRjVreEtQakI4NWpEQW5SQXVONTljSkRZMTVQZ1hCOWhNczg1WUhFR2VCQzJMaHFoZm1EK096N1Nxd3ZoSlQxakUrYzM3MDhXdWU5RWFkVnM0SmhHZEMrVTFoMWt0eTlLT3gxOUN0Q01WWEVwWnRpeWRZMkE0Z20xdnNBN1ZvYXQvazI0ZXpUeGh2RzNpeFh3aThKWk1OeU8yNG5YRVFmMUw1SEwrWWZlVnQ4SW5ETkNPZlFCdXVaVlNqZDAraVp3azhkMnd4QXlNTzJ6NGdmSFpIYk1FaENDdHpIWWdGWVRGdk4wbU5DMVNMMlZpVU91TElIak5yaWt3MWZTd1ppWXQwSDBDb3d1WUhBclJHd1lCRUJzbnZ5bGVhTXVaekhjbDlXZWxxSVN4eXR3MHpIbnJuLzhKWmthWUZISU9NN29zakRUSEQ1WkVSWTBmTVZEdjJOTUhJQTJZaU1oMjE4WUQ1czYyUm5XTW56dGNKMzRLMlE1R0RSSGVybzV3MmFxdnB3RVdJRkhDVDgvT0RQMzRaWEV3d2VaRGkwTENORkUydzdBZWczV0VhQUJQSUkrK2lYQjlDS3lZTktYTnlUU2NpSnVSVEVaeGp3Tll2WmlaWkViaUJabzBrL25DbXozNTk1U0ZQWFZ1QVRZcUVERU1nVzhROHp3NUQvdUFpWTRaWDVWaWdUZG1vUlQ4QWlwYjJnS3k0NFp1YUtDSE1BNml1ZkgwYjdZdkVPWWU5RnoyR2NnTUhPbGlud1RsQ0E1ZDRRYzlOWDNsMDkrUVZEQ1FEeGFyQ29oazVBN3ZLR2ozbUtsdy9IZU9EbG5rOStmZ3B5NXowVjNIQzlZZ1VNdUJXaExWa2pCQmdHZUlRTlY5QURVV1BKaVE1SCtNeFEwVVdjL3p3bmhQZGdIY1hjUlJFQ2lURnJZWGdFcVJ1SFJ2UGl4bGNObmpLaEJJZ3pzbUlRR0p3d2M1Z3N2SHpoaU1QMWdMdjRaWjM4QlRjMzkvWkk1eVJIYWx3VzZHWk1IT1d3TW5hREdPSnRsZnlZSmg2STU0bHJubTlrczFjMUhjazdYMTVpRWFISjNMNFRGMzZ4bXl4a3kzQjdBSld3VjRDUURNK3pCbzhRb0ZLS0NDSVA4cjlkejJrNm9paFpNb3RWZ0ZHdlJSbnpRcDY0NzVtd2xqYU1vaWduUGR2NHV2S2RMUUhLU3VjalVyZjIwdnFTR3kzQWxRSGpBNXBrenVaTk9HMzBIVitQZWpldVQzVHl5Sjd3MDBNRm8yQWNqVTFtLy9rS0FwdjNCZFM5S1N0VkxKZjBpQ0JmK1NsV1Z1TFZaaEN4NTNtRHFBVS9nWWRiUVc3ZWZwdE9XTlhhTTZ2MlRmNHMrU3JVQ21xUWlJRHpncFdXYlhFWnNTeUtEcDJWRzN3R1lUNlpqSzlmNWRWZGhXTnR3Y1pNMFNLb2JuaHZEQjRBd3BGRHZONUgvNEdlSSt6RlNVOTBYV2dnVFNwSGVBa2g1QnQwNWgxZG9tcWxGdHdVaUVQM1lhSHFqVlF2MHlMRm5wVXZlb1pyd2hodisxREI4T3dNc0M1N2l4RHEzMnVsZGl0NGJKRm9LVEZteThTSU9VMnllT3BjTFJ4SjFveXFXWkRWcWtaVzYzM01vaXJkY09CczBjQmp3a09yVDUvdERueUc5U29RalhoSmFWKzJXZThDbUZzM3RYQmJWMmR4aHBXUGVMRjVTbk1ZMVZESGRxTk1zYUY5SjZRSkVoZVhiTmpSU2JobU9OdFU3cko1c0RyaTQvUWtaWGxZaGZoQTVRSDl3M3dWb3ZTekZTRjhDdmxxUW9kWGpNczZsVFpPcTQ4eDFDby9VTTZLR3FUd3ViL05sWE9EeTJhZ1JCaHY1aTNSL2phTlFGYk9yNWQySFVqcjFzVDA5L0VNL3hSWG5hUWpNUkQrMVFqZ1hIQUxudS9Cc09SdHVCRHJWNnltenljZmNwZEwveURHNFpWSmJjL1ZFQ3A5TzI0MlpRRmc1SWRkc1I2TU9JZUVZWWt3NkJpeXJvUXZoSk1ydWp5d3hnQzJ4NlpGYVpVczlrVitjdUdiV0JIYjl5YlB1b3E4VzhlNzdaUkJFY0pFVk9vQ0FKZDAzZDlKOUFLRUhsd1BQMVZGQ0tTK1pnSmhhVUhMdDNqNHNXMXYyd0JSb0pIWVA0dUJ1WG0rL3F6TEUxbDNpazBCdkRwbzEwek5RRFJ2NnljYVBOYVR3R0xoSGdTWVNNYS9DRE5vMlhyVmNZWDNnbmRkRFo1UElNU0lYdXZoZXo1SDB1aG5xSENzeXp3dkp2d04zclBld1NLZjIxUkVWa2Zla0g2Qy8zb0F1d3N6MmdWQ1lTOEVpd3JxWUFMWkpQTzR0MWsyY0JNMWlId0hZUzFYOXpUMTIxQlkxbVkvSGtTdEZ5ZStibXpodGxwMnZlVldiNGIyZVVMd0pRdFE5b3NVakI5ZUNHVUtjQUNiSDlnUWM0YW5JTW5jYUpTbFhBdDIrTHpRR21FeVd2RUhrUFlPVnlFU1k0OUpOZnhwQnpBVzVUQjZoTHZDVVY4RW5Ha3NwR1NEUkp5NEQ3OUZCSlU1VWVJc3R2bWJFdUFlamIydnB1cE5NbHFUWEMxM3h6cWdVNFFGbHZRSjFkT1N1Uy9MSkluRm1FVWxESUFVY0h2VFczVnd6N24zV2xnc2tOZ2lENklmRWV5OVR4VXpxcXRZM3lZdmhBWmNpTitmYmt3cmVURW44UUV3ZUhzd28zTWJnTGNGbWhtaVRnUkFzWUJuQTE1azlBRmZYR253WlZsV2JVa1o4ZnRVOHlLNENXa0txU2V0VTNlQUlJSFFzakFUMVEySjhLZ2pEZlJNaDUyNzUyd3lDQWpQUHRYMGxpVGVOeG9kYWRWM3R0aWhEbzNOSXVtMGpESzVTbmVGSlZ1ODJ0OHlUZGQvbm1yNnQ1a21JREtuQytQSXA0cCszaDdZV2M1YTJvMC9QMmh1di9IYWoveC8vM3I5SnkvRVFMK210QTY3Ym9zM1FkeDQ5aGVzY3VObWV5L3VENldQQ2xEMGdSSTVxcTRKWmNHTXdla0hHcXZVbzFaUHFScEVRK2gzajFIdnRxZnM1ME5Dd0p5elo5K1F6V2w1L3BRMDIrRlFvUmtWL1BNd2JzM1h1a0VMT09wOWloeU4xN0NRNUZudWE4ZldaU21lYUZ0dEFTbFRrWVlyUlE4MUtzSWh2eFdxQVNFQWcxQmRIZ2lpa3BEV3VHRERYd2xoZnJ1bmkvaGI5eWxRdEdUV2c3bnlVMWFSMEt3Wk5nMHJUczAybXZzaFVCREZUTzVhOEUrNEdxajV0TzI3YVZmZjJnb3N0N0hwUzRLMVFaNkE1eHhiaDhwQ1BzeC9WR0xQVDBaNWVraHlqcmI1VUpRbVNpYy9Wd2Fsckx0NnJGSURBUDVoUE5qVUwvT0JtbWxBZnRXQU5KLzhWd0h4cE92ZkNBaDhiMkhmNEF1SVpHRkVCbER1Y3p4dWt5QStyRUo4Z2tTbVRValJ4WTVpNXg1OTI3a1FaQ1d0ZXlqWElFVUd6bXJZR3paL2VmQitFMnFJdy8zZTdHdTBIK1d6eWFMZTE0Wnp4ZUdoMnB1aDV6Z3M0ZDNERDVzN29oMWdGamExSWlHQzBzTDR0Z2JJVXNJVUlWaUp1d2R3N2N5MHpoRG1tS3hsSnd4b1JlalZJeElNVHkva2lJVjh2bUw4QWs2Z1Z3YUZkSCtJWmhEcjUvU1hYY3ZhRHNETGlvSTc5b0ZIR1p0Wkh5dHhOaHdFaklKQzh0YmJWdU8xTEgzOGlvTUtDT0xwTmFWKzNlZDArZzc0UXJUTzlBVW91V1VUR1BhQ1RJdE5pOGVHcTRHSDJLYmNGUEhvWWVoTGtMREpHMVZhTDh1OHh2c2huVHo1Tm1jYTMzZGFTNVROaXNyZ3A4MUxyTU93MkMxNlY0UUxuRGQ1L3RlbkRSbHBYajhWNTZsbXk3clk3aDFnQ2NOT0VINFgvNFFVUGZPd0phOE05dmphSE9SWlNOVk83WlBPL0hoZC9JZHR2S3NubnVGV0FWYjd2NExudERKY1hCbXc5dWhWQ1JjOHJiZlhnTUF5OGVVTXNGdXFrUE1GdFZYekFyQ3NkN0UwSXdSNzFCSVh4TW9qUVNlL3h0dmk0ekRMNTYwUWlJNXJOdTBoeGc5OXdFYU5xMmNDdWNLNW40NkIvbXBBbzJXUEttN2RLVTU3NWxGQkg0VC9GVmMyRnUwcGVid0pvcWVXRlpwRDdFZXRRQnJTWTQzeDZmK0ltQ0NLRnA1dlRZMG81WGtYdkxvcHFnRHp6MnBLWWpNcVMxVUgyTXVQaExwdm5Qd290OUh3b3Bvd0R1TGFPWjE0QlNDMkZLS0JheXhnRFM0TUg4TStKV0ZRb0tkQjV1QkhaWjV5U21KOFptTThYbUxGWUx4c09wb2p0UEhlZk1jUURrTGNMZzlBY1h1bUltU28yYzVYU3oybmFZRmxVU01CQmFPVFFXZXIwRUdLMFdYbnFYK2IrOTVvd3pzRTlia2xKRE1haDh4YzR5YlhTZ1JsN2czZWJEa2FabFRhNXc1UHUwN1pPS1Q2ZDV2d1hhYU5NRDNDT1lyZlBqVStQZFNmdkZIeFhNUVNHc25wdGxMSFhNMnJYbjR0bkVSNm9HbWU2bkV4R3RPYVlEU054VmJ2dW9uWFlOd2dTQUl5MTJONVZMUmFQeE4xWUd4UUxTd3J2S2I0UFFjMXRJMTRUcWZrNzJBR0xtOURIMjZDcVZpWTVwbkNsYy9Gd3NwdjAwZUJOVWZjTi9qelkrRHhYSkxiUW1UWTl4VFBlZGZja0duclR0R1pZRHgwejJrQ2w2bWRuUTZQTU50ZFQ0S25tT0Y1ZjUxTS9TWEQ4NTNXUWZINUI1MFBOanNMNVBwVGZ1Y0h0WC95T3ovWm5aUGJzVFRuait6Tzd4d09RZWVQMUk3aUQ2ZVZldUlVaXhWNWJabi9VTGpJN1owbHFGOExsbUVaaWROdForWXRzd2tTU01XN1p3TEVQSkhoUXVoN0JJQTJ0d1NBblFzbXczTUZ5bTBraVd6VEdzQkdQTjNGTWwzN2tpcnhMUk1JOGJUYVBXMGNGNVFMWHFYWVJ6Qit0cC9jTXRENTN0WTZ2VGF2SXBKWlB4UW9Rb1lyZ0NsN09wV0c0aUtBcmEwQzhkZDBaVTk5aUYwNHQ1b1Vyd2hXTHRoc0FEWVFFb3FkTFEyWmMwL3pSVnRUV3F6YThPQXBZTG9RZGhRdUpOYWdnZ3lHbzRTSWlKNmpIV21CVVBQVXN5VEJSQ1FoWkVHMVY4UkQ4ZnJjVDgrL2ZUTDFnc21pWXZDM2srTWlpbVdQZGowSUZWQ3Q5YTBiMzM4TGdzUHVyV0hSSENyVkUrL2JPSEFUaXI4WGNkeE5UcktpcUlhQVp4ZTQ5bGRxQnBHNWVKajFNbjlJckd1djlTUGRjb0EycmZDeVJXVW9YYjZSRzZMR1BsTFA4MGFCNlZ0RGxpd3YrT2J0TVJxK3ZPUkNhT0ZUSXptOVpYNktyTWRhb0tFMjFCR2hRcjdRclVSaU9EaExFazl1R0NRWWQ3WVJGQ1RCWWZrUUMvUTlzN01TaUFWOThWYTRHTGFFVVd6YnFFdTkyR0hvVTdjWUhDSCtKcWpFQXFxRVpweVl6NUFzKzJud2VqUGFiM0RLUWlKWVJ6MHhQdVZVYnJGclg3YWREU25XTFVmRk5ZajJwZHNsQUpzd2hyTVJIbXMwZWN5Sy9MRDVTUzM5MnBEbmxYUkZ0MzloazhtU0htU01LUU03Wksxb2JUYnAzZ2RKOC8zSFY2ejZCcDBoTC9qZUhaNW9EY0c4TDNZY2k5ejM5NHkxSVQ1clE3Rnd0aU9DV1V5VEpKV0wrQ3lOZUg0VDRtQ2JTRHQwZUthdjhXYW9JL2ozcDBES0pzak1aU1E0aGIyK21DcEFzQ3d6Nm05VloyNURWSnZXWFpzVnJlRXpiTVVQTG5EWjE3MzZHQ1lJaHF6TlhtWVlMM3paeXRMa29ZS3RGWHFTTGMzRUNBREhtbHhya2pDM3VMbEpGcnVzWlU5TExCa1Z1b3NGQjdqU3lXNkNqZlp6ZG9oVndlL3RCT3I3SHRhd294SWVLN1pDTG1ZUGlPNFhxMk11MUtSSzlqaUdUY1BDaDBtZmFvYlpUblhMMTZFTnhmUTkvQW9EWVhmaE5YZzBHL1ZZOGRpdTAvcVMyVHY3cDlFcVdCQzNaWGRWdUFmRXhXS3RHTjdxK1pMdWE5ZWJkUytJYk1pa1djcWtwb1dON09VcGFEU2hTNHo3cWZnYjBOT0QyS1BHTzNRcDQyTjNwb1k5MmRGcENqUUNzTW1aUzhEK3dQdC91aVRadTA2MFlDOVllVnVINFBPKzJ0QkRsQVBXUTdlbm9FTzNoaVg3RzU1cmRGSDFFMWI3WlFVYzNnV3U0b2dmOE5YdHRRbjJDT0duWUljamE5Z1JaUHV3OVJWN3dGQWtsaWVLejljR2VSaWgzYXZJYUhTMDRhaENNaGJhTjMwZVM4Y0JWVHA1TEh2T2lCZ0lMV0xHd0JwcXEvQ2NpWTl3a1o4V2Y5bVhoN01EYjhxTGhCb0haanhWdEQwWkFuamJCbzBDWnhzQXhtRTVDbFNIQmJ1ZisvVG9SR000bHAxaHVCcU44MEZLdHVFQURkUis4bytMcUlxb1FGbmNsa3gzeFY1MlJzckNQdGlqVTZac1VlNVQ5bVVoWlRSeGl5blhFK3kzOHJ6MjArS0VXME5qMlZqQ1l2WThzdjM1TDhOaFdaK0NhNlhMR2trTERnUEIxSnplYlNWMGhSdHZEekdzRWJUdlBpZVBGay9ONHVuUnQrM0pVc3N4d3k0MiswQkdvY292c1k4b0JMQ2RzL0liem1FbnlOTDJ1RWVXMDhraGhWVTNFZnR1QUFUL3RrZXZ5dUVRd2QweXh1SEJJdllSN3R3cUJNQ1QrZmdrRzNyczVnLzhQeEZZSFAwd2pWZVlTekxwOVVYQUF1amo5M0R5QzVyT2o5d0FTRlA3d0M5aVJNSTR0K1ZYSFhPMzhPSjk3alFWaXZLd2MrQ0RMVGxPQVdNMHk5SFdoU0xISmducG5wdDlOdDIyRXU3WEkyZy8yS0pwMnhVZ0pYYXFDcVNQUjdwMk9DZG9LVktqZ2FYaHVleUd4NWt4bnVlcmc3ajN2ZCtwb3cwVFViQjRqeVNCSk9TalpaK2JBTHZNUFVIb2dLSjVpdlB4NzRid2UvTnlIbGg3ZmlaNWdLckE2WUF3blRhZGFwVTJ2NFZmUHhqdVJKVEEzKzBwT3QyMzdOeHA0QnJqOGlJZEpXRWRRbjZJWWc0bXdSNzV0SURXQTY3aStvemNJTnhJN0NKWElBbTBzdkJVb1RtOEFLTzZUbFVRSC9DNlN0MzBwYk5pR2l5RGhaWC9lbFpoSlRHTHg0SUU1eDZ3VTU1VzVSdjVaK21hSnlLWGZUQUJMT3RXY01HUzBaM29Qa1NneFJpd1RsUTNEREU2MWFNTjk2aDZoQVBnajJkcFFEWTFRZGpDRlp1Tlk4bDQ1a2duR2FDREpyVGNSd1ZhVDY0UjQ4Y2tyU3o3ODEvazNHQXpwdkxXQ3dJOVpSdFFaRHpnQmtBZ3I3WVhZNk5Xd1V2bU16cEcxcCtUMVEvTkE3SEhSb0RhUkpMR25nV0gzL0ZEYTN1SkhzUktMVEJDbjVOZHNzOFAzcXpibmd5TDRJSVZBWVh3THNPeHB3bWVPZzBjL2J2K2pJNVFTR1RKemJKTUEycXlOQUR1OUowZ2w4NVFnNXV0dWdXMmZXYUFDRld2UndIOVVoUjhBV0Z6R3IvNkdnUmRDOW1pWjMyWWRTSzhZVURJVTNQRkpwV3dRYnVCVG5FeGZ0UTJUbUJmOXNPcU9CcmtDTU0vcmFPSlJyUEdPSnBGMWcrMDNlSmVHRlN4emhJejhLd2V2NnhIa1RsS0pVbGtVZlZuSjRkNTNna3p0MTdIQXp4RXNIMlF4VjVjYXdxM0tOSWVSOGw0b0FKRkErZ2ZNTHUrWjRvSTRHTEhiTFJwMXJpWVBWRzZrNDVzaWYrZmNiSHg0Mlh6cjRITlFtVHM4YnNpVUptc2wrSDZvWWpkZFBTUmNwWWQzd0xuUG9UY1psSkxtOTMvWllWcnFuek95MVhhWWJiamhiREcrc1luZ09wSm5uK3JpdGYzVEJNcnNJM0hEbm1FWUJxblpVaUNONUVoajdLbndLQjdobXBlcktBelpqN0xXdUFjeDVCZW0wbXNqMEFXbFFwVzIyR1Rta0l5VFp0bFRwRTBETnVLWG10UFUzTmtJV3lneVZOMW8zcWEwN0lDZ0t0c1o1NE9RSUVrS0Zicm1LdDBsazB3ako0cFFoRm90VDIrWGl6RytPeGJyUXRVWURYc3JWbXdobURiTzl1MWdhVnFqU25mWEJHR1V4M1kwckZSOEc4NUVNVlZUUkN0YVNyeWpPcXhoT05NQWJxamZjb2Y5TDNaUmJPa3lZMUxnL1RCcVc3TGZ4OWwxeW5ZdTYwT09ydG05U3Fhbkh1MWRyVlZtMktSWG9udklUdG1iNmNmRkpiU05yR0lxM2pYVDY2d251RWhwcER1ZFlId2Q3WUI5a1c5dHBvSk85dTVRc1BYZUw2dUJJSTlJSW9lak55aEFNc1VFT3diRnNIVzN2ZEpaZUVpUDZNVnVHdFVpQVhNZHpJbkFtZHA0NGRheDcvWFAveXI4b2ZyZjF6L0FINWFPR1FaTzNiTnhSSWRINXVYblJBVFBVaTlaVFhiaW1yekRjc1RkL1RkQWhKcXNsRVhNL0ZrRG5jaGRqdkt4c2s1WVo1RTAvMWM1L1RhT3N6dVNKTFFHc1E3R3kvNTNHM2xhbkR3VGJXSEFsY2ZpdTlSRnBiWmk1MHQzU2lCV0E1WHNFbGFpWlEvSEx5YUVpY1FJL0JPU01iVmI0OVJrdFZRTnNlY21UNUlaTStKYW0rWUp5TFdaTmlGZ1dCaXUzNGQ0N0pjblRBU1RkM09CVlhEVnp4Q2hwSlZodzBzUHFObkM4blBJRkQ5K0g1c25yTjdGU1QyWW9aVk90Z3pyM0pZeHkwbGRCcEI5OERaUVRoSHZZUDFFVjNMZFJNWitLa2R5ZG1FU0xtS3N6WGdudWd4akRvSGxGK0hlRUdRdWhNdWNMc1Z6L1REMTBXcVQ3SlBNSHVBd0d1czVqd2RpUytXalVxcjR5SGFGUFNEMVJCMjRYeTFJZ2tzY0pxMlh6dThJSnlZK2swLzFjbC8wcDZSY2VRNjJBVkx6d0YreTd5SmNud2lXOUJ0V1B5UURPcStBQ0V5dFczOTFqUUxqWm1Bdml4aWtsOU5XN1pzSDBDdVY4ZC9uZUZVTDFqK1RSdTkyTndQN28wOW1SMDdkYmIzWjR0MlJCU2pNNHZWWmMrWlJRRGxSZnpOY2RtUkJiV3hsZk8wUXJXZndYTklXZDRndnV4Y0VqRyszRVU5SkoxQzJ1enB0SEpLUWhTVkNNM2hkNkROdUtYWFp1ZkFBVnN1QUJUZTN4NU9JZ25zZGxxeGx4emdZRjFCMWJXSnhPYVZJYlUyQUYxbDhPbGNtMlVhSlhKOWRiUC83SDdDMEx1cEM5TU5UN2NzaE5XcDlzODlGZzRvS3IvS2ZnQ29oUGlyRVhtYkxkNG11clZpR3llZGFUbzlSYkpQZmZ3YTdtSDdEUEFNUzdGRVhyRTB6c2dSMU5NNE0xbGd5cmVqUkNwNFI2Z3dHV0hWeHZUOHVoTFdxaTJINkJ0SDNqaUpZbHJuaVpzY29wZ3l3WUtZTTYvMzdyemFnUWhPYXVHdXNQZXE4czltdys2MVNpVW8xUVFkdDN2ZW5HTWh5R0tVNFpFbWhBZmsrM0s0Ymt2ZExJalowYWJLOTV5THoyN3dOWXNMKy9QWURrRkRPUE8wOGxTZ2NrZlZSWWQ1UnV2ZFVLU1hjNGxRbEl1QWczaUFONXhDY1NzK1grZ0FTaGpMNmw0SktBcGZmNVRYd1lLakttM3kzRjloTmVQRk84Tzc3R3gweW9WRE5NdkhoVUFBOFBYRng4RW5wMk9BeW50UzhDd2lhMmRGcnlQdkhOWEczMUFUNE5GVzRrRHpsN3lPRURYVWsrYWtBdTV6NmhFOU1Oc3F5WTI2c3lpMXY5MGpWL3piN2hkRTJrM3NQKzJ1TmRtejIyNHo1T0tNS1FHUDZpMFFLa1E0TzBLU3M4S1NVeXpEWitIRU16ZEUxYXBTL2VWYWV6bXZBQ09Id3J5WkMyNFFFSEFBMTEyVzZlSmRrSEE3V2VQNEdYbGtTWHl3NnJiYUFFdVlRenNML2s0SkdTdHN4elFndm14UHNGdDYyV0J1M1M1MFZjaXFHeXJ3Z1diTjJYNndnZDR0Y1dCWDRCYjdKbVFEc0RkeDhTeHNzMTRkVS9DdU1GdVluSzNlanZjc3paWXZRTE56ZzQ3VU8zVWgraHdxRTdlRFo3OW5kcW1kL2JBdnErbXhISlFYL28vTG9wbzduMkZuZkpsSXMxak9YTFBJV3c1Mk5tK2gwcGxlbDFQZ0xJaCtvWUp3QXhUeCtJZ2NMd3AxT2F2RmdHRnl4aFBtWXBSNGkzWEphYU5seDVrMFo0Rlh5cGMrbWV4YWFTZFh4MzVVczNaYXA2TXlXOVdobG1uS2RVWUlXdEQ3aE0rSVlTUDQ4dEFjNVFIUHRuOG1CaWVEZkI2Nm5vNk1XdzZKcUYzT25IM3N4YzI0REI1cDNReU1aellIWGtBOVV3SVI3Wi90RjlxMm01NG1hbitDbG5xMk9tdm1sTTUwQndRa1N3Tk5SVzVIWGpvRXhnaEpSTEpSSFJvL2xhUTRMU1F4ZXJwUjdKREVlKzZHZkhxSks4RWFXT2szTjNSN0taOTlScWZrdnBqZnhEYlE1Tnk4UXordUZ1L3RkSmRLR0Z6TzdKQ2tUWWZ1ZVlScDI4YzBMWHlmWXR5SzJPR05xL09Nbks0UVBUNjR0ekhnc2dFU0lac3dZdFRnYTYvNXM3bVp5bkkwUy9VZEhRc1lzQkhUdVRTZEtCUk12endxRFhDVWlPSTBwR3RHdTBZdE9CMjNnOExFSFlkUnNNVU9RYW04dS9sS0xPekdnWkZLRUtCb0Nna0ttS0VoZXFIb2NGazhiTCtVZmdOTG05dEdNa2RMd1I2eGs5NnpoY05lUjdmdTB4NDVaeGpsSkRzQnUwUitzMmpYLzFOTHpvWml6MWZ6Y2hKejlBRGp6T09ESFNFRDBpTXZ6V001R2NCMlFQZ3UyNGNtNmhrYkNlZEllZGxsazA4K3RTY2tyWE8wVmhOQmJYOTcwVTdKWnY5eldvOGF2SlhOQmFBQ1g5N1hZaVNyZG8xVnRzVkFkWHJOWitvQUxrSkljbTdIZXJJdDdjWFpRZXhOaSt1endSQVBGbVd5bEE1Y2w5ZVpzTEFTZXdEZmdKazdiVGZFdFZuMU90NW8rbEl5dHZkalVmM0tZSUIxNVJDRmJuY0xoS2ZwMlVvRWk1blNpelZsdDZKeC93RmdLOUJuaTlqNE83SEtvKy9IVXZWNmNxM0JvY3IyY3hTMFBJamk0QmF1Nkp3cW9BR2l1ZE83bThxemhwMGRkdzRlRE14QlkzZzhrWStZMXEzT01qdG4vcWZIYzJGZVJETjk3L0RJTVRNaDBQbDR0ZmJyY2ZJSThxemVWZ0RjUVBISjg1ZzVLSTREYWY5aE1jdjErdzk4Tkc3MlJhVU9YQkRUN3k5YWg4dmJyUGVZYUhQSEI1VXpWVE9ZMzNRalVDL3d4TTlCQWFkZS91NE9oU1ZzT2hIcjZVNjdPK2RXMkFvQjFTUEhJeVN5d3dDeXJqZi9paTBKNW0vYkNRUmludkdvZTZ4cGV0S3FlWlRXWjh1djlBWEZEbE5BSEhZMWlEM3M5cHVBQ2ZsTTVRWHg2dVhzOGVkTU1ZQ1BEWUpSc2UrMW5sRTNkczF1YUY2QWtpVG5OY0JpYmlkeHZhNy9HYk5pbHNmeDErMUNONFRUQVdEblViUTljL3ZCWGd4Ym83ME5KMSt6U084dzdXTHhiSGV3WGlldW51b2FYQmYvMWZ1M3ZZZ09vYjJuazRzYzZmWFp4SXc5Z09kRmVFTXhES0NzT1JqcU0xTGIvUDBSOXdoNmpyN3plTFdqOHNxcEt0NzJvaUNXbkxsY0ZZTmdWbWdlVEZoYXRNNkFyaGJ3cCtic1EydUtBSURQQ3Z6KzFNdm84anFIcFhvVjFxSVVELzF3TmNjZGpYYkt5MkVPQ1lXeFBmTW1KaUFycHNXTm52VGZaN3ozR3k2bzJEK2hVUnBLMEw5cXloY3pRNzZKMEt6aEdXN1dYWW9KTzMzeDVzdUZjd1RWNC9nak8ybUtReVQzR1ZudC9Ebkl2UFV6VXVaaHZhUGRPWksvZ2JkT3EvOFFSUTV5UVdVVGw5MmtaZThFYTJuQ0RTOThBWExIMkVYVnBTMTA0VFF2VFJPYk1DTUgrVTVGcDNsNHEyYXV0d0hKQVd0SURyUkdGaGFiUkFoNnpsb3RIbHR1aHdFNHdvYVZjemoyN3pxVnc4WEJEbFByQldCRGRBMUZhUFNReWZxUUdSeU1MUTVYdU4xWURqRXVqb1NkaWRkdDFwUGRXNlp4UDVEVzUzYVk1cjZjRTF5Y2grYzBSTXpaMDVwd0wyQVNvcy9scERNdmRZR2Y2UnlSOGdjNCtUSU56bWRtUzdrSjdjOWp5YkNlYUhjcksycWdzcmtQaWNqMmVOdjhCSm9CQndubTlqd3VjSlpvNmRHUkhpd2dsTVVzdUk4RldaYW5lSTU2MHJ1WmJ6ZzFCRHEwQ3JSdFdwandPcGpOL0M0WEhkd1NJT1d4TkdRNjQ4R3BWK3VLRURJZnJZRzJRcEJPT3gybUpUdk5VWUxXdTZPM3krZVBjTUNpdUwyZCtDQWtMK2lDclVNRURKaC8rZGdrRHhDY3pTQmIzUGxSdkZzREFYME85WlNpT3NuNjdiY25xdG54VkIydVBaSmptMEdiWlkzeGhkcy9zMlFrcTdNV0ljVDNHZU5iWWNoNWIvbVBPMlcxbG1lUFZnN2REaE1KMDJFYmM4cDZJUmJsdVR3MmkwN0E0ejRyZk5SQmprR1ZINTB6YjlZZ3JRN3ZNVlVNdDBKRUo4aEY2aUNiOVNLSVRXaGJTZW1hTHAxSGRwQUt6emlxaCtqQkk4eHp5YnhOVWc0N1RNMlRkV3hFUGh5TXZnN1pjZFpuWEpDOWl3WE1HWDcwZHNmdG9UNlJNdWhhaDdvNUUrQUk0dGN5V0JhRG41bUpQS1YxRG5xNVh5c0VpcjBGblN1S1o3NEhSTFBZclUwZzlzaFhkK08zKzR5Q3c3SWtKd2NmNDJFcTB0TDBQVEUvYk9IQjJlWit5M1ZzYnVPMGh6SHg0YTNacnV4Z09FemJUbTNyNW0rVmRiS3MzK0lsNXpUQ1ZJN3NhTTdObm5uWndmSjVFalJPOWlLQVF3VFpEV0t4V2w5M3d1RmcvNGFkNWNGYXhETzQ3TU9PaWhWd3pzZzhSKy96cWkzcmp6Yk5nZFhSbW1SRWFZMm42UHVGU2pnblNmWVF6b21PZmVRWUJLTE1ydmt6d2M3Tzhad3Z4KzZmNGZZUEJFbmNlQ1ZLejZsV1RhWXJtMjJwY1RSTEVkQTkycXYxTWM1VXVDSGEyLzcyQlJzQkxCMTJBSzZJSHBQTHhyUGY1QnhjQjBiWmVCZnRGTEdVekVjQjVGZGV0VTMyRUJmTDNSeGZpYnE2QVBJVXJiNjAyVDZmS2RLUXZBQXZZNDN0NXEwMnljVjVZT3FnWFB1c0REV3I5VnFOOEpYOTYva2kySVRWVUQ3VVlWb1hvUFRuc2ozL2J0eUNqY1lqK1JxY3l6cU54eTRiZWVYam1NbmJVMldnNHhxS3Ivc2RVWDJLUUswMnJzZXlwN3gzRUorUkNDOWZRS3ZsNUIrUmZHM0huZHNSbEFhM1F3QUltVjFMSnZlZTR1aGxRa1JHNzd3SktaZnNDSzN6T1FmVVp5cW13ekJ0VjBxRU5MWVd1RnltZHFxalZzRHN3ajNzRXV3dE5sbm90Q0t6L1U2ZkhVc21ET2RlR29menMzZWQ1czZuZGFyN0RHUU1XMGxlaUxRK2djTlIyRnhvSFIwQWUrQ0lRTDZIU2NWNER4K1lFMlpla2FrWUZySUtzaWNMYkFaZ0c3VHRSTDdPSTRHc0JIcFEwUTZpOWpFUm5VaDRTbWxOOWNWZnFmeDdRdWVRRGk5NFZRbkVEb29NVHRYYWxTRERHdG5QQ29JUTBWbHNBN1dILzI4OVk1d3M0ZmxyNkhZaWMydFNBS0R3VEh6R0E5QXMxeW51dENvcCtjd0lUL0JSYmF1ZTJSWk9Jbko4cVZ4UG1udE9XSnhKamlKYkJWVnhqdUNRWnFpQ3k3WnJaMCtZUFlsT1JBeUVZUFNpRkx0MS9nVmN4emwzc1RYa09aTzRuTTFvbC9RWkxzUnJQTSsvd25RdUhJRENJanB1Ymx1SnZ6NUJ6Sjc2aEI2M05OS1pYTzhyNnh2ZFk0SmtkNUJKcStLMGJydDh0bU0vU25Xb3NNT0RNREUwNEhKaTNGMkR4NFhQOHJFdjV1OGR3dU5RTHRNUDNUNituS3hiQ2NOK2hpdkRiL09aaWV1QUdRZE9aUDJJRzk1bzFmYzhwTXFTcHRQYnJmSlRnQVlmS0dOdmt2T1J0MDhibVJmMjBXMXRzTFR5UytFcDQzYWM0bk82L1oxSXZ1MHdSN1hFNnV4aS9ObVNhZjYrN1FKODdHaVM1VGl1R0pia2RVckhpcE8yVGpzaFB0dXp0VTFXUEVWa3htc2prR2NzSHRhNHpLOWpwR0F4WERraFpWOVdoTFBRWTBoQ1FmWlluQzl2bnNsR0QwZW5UbzhOTFNzMmVMQ2d0aUVMRmtRUE1CcS9RazNFZEtFU0I5eDNPR0cvT3F6YmMrVTdPL1FNRW1wOW96TTVZWFZPN1FrZTNUa3NJVTRmSXdNOVpJTlptekhxWlJkZXQzNFpXVVUwSFFTYXgwSUFFQjFPakhrUjhmRkMzT1huK1ZVek5iT3dUbk0yZ2VUSTdHWGRuTlBQVnRXajkrMUo4bm1FVHNzZTFUVHNPWW1pcVp6Snp0MUNVamJFa2xsSWdoSnJubWRiRlFkTFhlTThtQ0crQ0NzNzNWczhQVTU5bnRIMEhpd0ZqKzNudVQwck42MkVocWNpcU9lcHNiRlhRV1JDaXd5dTNjZFZDSVBnekhTS2w5VVZoQS83alh5YUZWeDNueUZ4enVjYkV1RkRhZ2VDRWZScHhyS0xHTWNHMWVjOHhvVkZDNVpNcnMvMlVnTEhaOE5kUHNPYXNzWS9IT0FTcEp5OXJRY2RoYy9zY3hoNVdSVDlBbGtlakJJQzJGckhyZkxTUmNDUDh1VVd3bWtuVEU2T1YyRFk0Rk9ySmc2ZHJhZEg4Q3ZYcXhjL25nd0F6TTJuWFpTTnBHdnloR2FDZ3FCS1dKNHdsQk1VWHJNYVBsZkRNOTA1TGJua1hVdTVIQnkxYnQ0aG9JbWNRL1FZbWp4ckg1QWRic1daeUNVNUJzNEhqSG1VTzgvTWhtenJ1TWhrdytBTGh3VG1wSnJyZzFUcEgxODFUTE55TVNBZklZVHJObitFSllSMENvbzl3Zkt3YTV4blJ0Z3dOQ3FSRm5yaTBIbUhQVXJoY3YyaVUxb2NnMi9lSHZObiszQmNaektrZFVveFhnTGtaaGNNbE9xNE0wejc0azFaTTBjcVA3YUdtdlpYZ0htNHBFTngwMzA1VGVGaytlNHpzdlI5YlNnYlUvVk9qRWRzRnd3eTN6YTJuMmVaZk9ZWjRKdXc1bUJ2RzA3eFJNZjdRMnhZUkNkZVdwd1JnOGZqenVEMEVTTFRtOE1qcndvdGRFaGxRK1pEZjE1UFU2RWh6WW5oMzJtRWRjS01GZDNiaWdVbnA4WDcxTkYvdGpsTE0xZ09wQ2pVYUp1SFJIUi9aMDQzR0dDM3lMZjluOG5RblJFTUhpNmZLdjc5eVVCQXRNVUYzMWE0cWJxTGJJUzFVN3R2SE1YaVYrZE9PdExENmhOWUtpRS96dURKT0QrTk1qWjdQczlQMTgvSUgyd0VzTCtjWWdTWHh0aWpFOEVzTlR3dHpxOEEvZDIyeHovMkNIVDJ4T1lXSkNPT0MyRUdYKzQvcXNhSi9RN2JRUUJab1ljR3VsV3UxZW92ck5FNXJ0c3BSNThZYk1VTDdLQTZvL2g5NjVscnVUQk0xTllHTjg4QW5EdGdBOEZobVFqTjZTRG9CZVpEQkxPUEJiRGpIRUx3VmQ1Y2ZZbU90cFVDVFFJMzJtZEkwaGZTWlcxdlA0L1ZJYXBEeGw2azludkxBQk9vS2ZLdU03TkNQTENqdnlLMVJPcWViYzk0N0FoU3Q5L1h1TzJVS0krak1ud0NEalF1Mis0RVBLbFdOTnRuMnZJeXZFa3ZkcVRFcFE0Tmp0MDd6LzBnNE5qQ01CMitEblREbUJEMFRzZjNJVkJ6Z052UTYvWXpobE5hcnJwU2xVR2Z1K2ZGelFIRjg4VWc0OGV0b1dBOGdCL3dNenZNYXpuVi9BN3d0cXJxN3M2VWVuMTJoRjFRdk5kdElWcFlCTWtKMDZzRlRNUlJMN080bGhVMnM4Q0IySFlHYUVxd1QvWGZhWUh5Y01peCtYMzRrTUpNSkIwT25MYzQwclpyS2ZnSHJYRldnNmpOKy91NEhNVDY0Yy9iS0dlV0RRSmpnWXhqNzUwNmhpZ2tERm5GQTRLbWVWdDVERmU4UUJZWTlMZDZJTTVqOEhiVHpQRU4wNjBkOHdVb1BYRWtmaTBQUFFpa1FQN3kyUWpzMm16Qi9GVktsbnZZSHg1OE9BOXMzUHlTVFVmdVI4RjFMQTgzQ1ZpNE13ZEwzMjl3WXBXeDNqNituL0tvYXVNcHJOYnVzbjZlOFRNT2hYWFdQUXI5Y1FkOUxCRHMwVWtuWnBudDA0QTlxS3pZNG9uYThhelRxZGQ1WHM0dzdhYXpRSXdrRkNEcEFCQWd3cm1jcitjMHNwWFp6SXRtdUJzUm9Eb1VHZzJmNURhbllkRTVFZEZJTG1xZGd2Qm1kdER4ZE1OWDFXb052UTl1RVFpYmRhREpQRFhyTFVMYi9EREhlU2JhZXcxand1TmtmdmppeWF0YnRHZEJ1UFhEeU5IN1Y1bmM1OEQrNFBTMUJ3TDVGb2poNlpFNVU3UXh5TjVmSDVQblU4aFlUWjhRVWN5ckJ5S29UZFlCSCs0UHdZbkk1cU1lc3pPeWJTYTFOc3BzTVNDVG5NZCtmVUt5VTM1OElwVVBFd2lPa3JXYi9OVGhZMUNGTlZiazJGUHNPQjNFckpWcWIvSTVLcGFQeUt6TE5aMUFjMG9kbzQ5dXlFRlF4cThRQ2ZlWnhEekVUWjlUMXFXekRuVGNIcUM5ZkZyMGpNMG42T3dGclFsT0RTdjJmU1ZyVnNycGhNVkpVTmRueUNDWTBJRk1JOTZRMkp3ODNqN1BsNHlPV0s4UmVmTEJJWi93dFh4U0hqYVVtK29aNzdRckpXUHhYRTV4VUN3SzFBZXNiS09TVGZHT2ZQbTZSUG5NZ2NWNnpOVVN3eTJzSGlYYU8wdjRTSU1YT2VRU05EeEowdFA0ZmFiYXFmRmgvc3RhSkFJQ3YvWTRienQ5VnhZcEpGVU8wWFl1VHpxNS9ua291L0hBMU94SkxIdjJ2SjIwQjdOcDVyUEdNdkJ4WHl4Rmd2b3REdzZYQitqRmJzKzUrbGlPM0xmUUdlZElwK3Jlc3JHNTd1amMzWTNkT3NZZlkrL2V2b08xNzh2QkVuZXlCQU1pNUVBUjliUWRMdU8wY3ltOXp6UlZyTXNEVHNOZ1JtNTZHL2JRbUNZN1FZUEZ2c0VXM3B3TEpGWjlUa2FXSWpoenhFZnZRV0tjQXUvNGNYalZOZ2ZwV1NGclhXZEJxdnVBaDRvZFh3ZzZmb1hQNVBxd0creG9Cbno2dElSYnRNOGlPYVNwT2ZmVXNibU9rbmpPdUl6cHVmQWN5cmZ3NXNzUnJ5eUpGUFBYczlNd3lHa1R1TTg2ZVRFK1lOYjV6NDRyOGZ6RWRvVFB5QXY0UFU1WG1WMTI0T2dDcTNkdU1MbkliQ3RvKzhnRkFGbWsxRHF6bzN6b25PeUxCZVJtcGFNZVJjRkp6VDZaUHZ1ZWk0MW5TNTdYR3UzZ3VZL1RXUUJUS0hlRlY2OXRUWmtIK3lvbWkyek9jOXVlSGNiblF4LzBGeCtPMWEvM1RDaTExTWhPWUdJSHJMSDdiQ2Vyc3RsREo4ZFZKK2g2ek9mRFBSd2xrcDA1V253S1RQSXVUZDFjSTdwWHp6UHlUNWxlTmx3NjNnSkc5cUFDbEhDUExmdFFpczhDaGpQUHp3NDU2MUVjVnBsZlIvWmUySWxQbllPYXhBd244M2daY0lnb0MvVDZ5VnFEYlBZOG5LZGR6V292K3NOS0I0UFI4L2o0Rnk1cytyQTdhM0Y4b3BaMW15bWNaRngwM051cHQvWXhkUDB0VUg2ODlSMW5XcFdITnV3S011V1IwUU1ITTY3cnUwLzVLZnZ6aFZWT1Z0L1NONURVSjhRNVVNYUhQVmhxTk15VW1PcUx4QitXbVkvM3NZY0llN2EyWFk2WGRpaWliVzQ0Njg5TVpHd2F4dnU1SnlPY3h6anNiZi9UWTE3Zk1qdWJDeUNiem1tNG13ZWI0enFSTy9sWVllaHc4ckdQeTlRdzZINnlzZHNIMStLMjU3QTIyaFpqaDJVeGt4TjloQThNRVplRStEcHJiTmtza2FNUDgrMCtvYzdSVEVmd0xpdkdpSVJlQ25iaDhZSHN2cHN5Y0p3NkVJc2Y1VlBTYzVsV1VMZjZZTHZuSEdEWWVLMGNSSjdBVHg3aUJjeUwzZlJKSXc0L3hQTTlLWDBqN2xyc3V6bG5sbGREeXZpSWhlYzJMWTI1ZlBGTUJ3L0xUcjVnZllSVGh3ZzJ4QWUyMnRrRTZPemhVK0hXY3Y2NG8rUzBvMjFmb01tamU1eEhlNWJ3Rkorck1RRmx4MWg5QkhNZit1UmpIR0JSK2Rlb3FmdjB6NWlZUFg0TUhqa1N4L05xV0dVajJHMkhERHJnS3hOcFlYSGRacFhpUTVrTHVPQ2NzbWJ3UlFJYVh5eXo4RFBmZW9tODZWYy9JNHJxdGF3Zk5lcTBVajJBM2JJNHgwYzMyYm5zUTE3c1FEMVB1YktwR05zdzd6ejJSYlNZUmhXSG5wcXQ0cmN0bXlaOEVvQW14QmI1WnJxYmtCM25NeDViYVh3aXhPY2pIaDNEaVRwc0Z1Z0NsczJ1TXF0d3k3YlNCRG4zc0RoOEVnQlB3UGhUVHlQQjVQbmRuVjdGRXZ2L2VaOHhoN045N0NLTVNDR2ZBRnNKUmg2Ym5UR3JZenE1UFh1c1daMkNHSWZQZDRJRi9yM2hrZFc0dGtkNFBuN3JrWUdicUhMZUVPOEM2ekJZRTJuRVRaT2dUanFSUVRzSUNYZUsyZWVjZHNjNnhMYXVVMVdyQlppdE9nUGJhMjFPRi9TUXp2RzNWa3hndVh0R0I1V2RweUNkVnY3dVdDWVRVeUxzZUM0SFdGcnRZTXRLZEFvUWlzUUo2K056TkN2WThaeVJLdzQ4eFFDaGE4Z2NPS05aVkN1S2drOHBmWHpDUUZYS3J2QTVFOThPMmV6akI2cjl2aWpmNVROL0crSzFPUHk2TzhvU1VuSTdHcjlEZTdGOEkrRHNubVdqOVQyZnh5aUxISW9GZ0krNjhKNTc0U3hPM2E3TDU4UDRiSjBBL3IzcXBTUkIvRXp4ZVJxUmZRSVR4aGlBVnBqRm1EN2NiSnUyOVJqZmt2MHpYdTA3QngrTzV4QytmVzVXUHVtb2Vjb1lFVjhBY3J2MlBMTk1rR2Z3UlZaS1VNU0pzeVVpSGpIRzE5bUhxVHUrM3g2VEhzNnpTYUJld0lmRCtvb1BkQTcySEgrVnV4eldKOW1wQnV0M2pIeTJyUXpGM0Nyb2FtSWZtTHVkTU1DTlFkUnNyL0E1ajVzM3cyQlp2ZXZNQVNrL2xjU09MRHpQSjRXT0wrTkdzUmx5cUR6ZlhqMjFEejQyT1B0b2ljZmtjYlNtZkoyejI0dWxIbzdFNWlxUWloYWtUeCtUR3BEZU16MUMyVWY0ekIzMTVmTlVMUFc1MitOanFaOTcrMXluWUJIVTR5TlRuVFJ6cGplaWMzMEdlZmw1eHRncEgvTEpBc1FZNDd3ZGFXQ1ZBNEpXY1RUcE1OM3R3VFQvc211N1Y4KysrQzdyQURrRTl4N0hDVmh3OG5wZzdJT3BIWFhvOHhwOXFPT3AvdkZoeUw0WWszN1UwOUZCa3piSDNhZ0RCeHV3V3QyNE9rYTFOV0FsajBJUkZENnphWllHZVhyaU9jQzNXSi9scmN1V3UrODg3YXlmcnY3aGdhWGo4cHkrVnlKbWQ1cUhJYzJXcmpqTXdHTWdLQmZVMk5vOHkrVXIwdmkrY1Z1ajgrVjRnMjBCazArc3c0WTlodVhDaVVHT1RYZnFHcFRaSnlGNStqNWZuN3NtZjl2MjR6cXgvd1Q0dXVwVlBISWdOSGdXNUlGdVA3TTl2b1JMZmRWSEQ4R0lBZmZzQWNSOUd2ZVREMEFiQmRtSEEzeVBlZUVYTGRLR200cjdvWjdQWEVmOEJDSnZuUnF2OXhtTlZ0MUdIdzU5aHAxakg4UnRLMVVzaTJGbGZZYnRERDZwd256N3poSEVjNXp6cnFkUURCYW1oU3pwNTVuelphc1p2b0U1TGduazRaVEpzMXZsZkVuVGNYenoxSmRFSDV4bWtpRHlCcTlEOHJGamJJY0ZsL0xPeGZXYmc3UW15aEhXRHdTQno3NC9icWY3bkdPczZ2VTB3UWZOQVJuT3JMdHQ1YWhxNi9jOHhXUGVPVG5jczBXVGZGWXIzUjRwcHpPZ1R6ekJSeTZybkFDRTg4eTBKdzJXTXFtbmkrTjliYWF4ZThDR2ZvbUN3d0xzL2ZnOGV6WVZQQnhrcml0YWdXQ1RnZ2N6UFR2RTM0ZHVKcCtpbUIyd2RTcjFYMnZaQUVGajZEZUF6V0theElkd1B6QTN1M2tpb3VNNkQxSktYNXFlZk10S3VkSmxocHRvMkgxTXdadWR0d1hFcXVlYzh3dWFzV09BckdPKzY3UWppK1crUGg5Lys0cVRGaVcwRGlCNlNOS0cwL2h0ejBXNHdwaTJSbDgzdTVFUzhnUGR5Zlo0UG9pUkdidmlaYm1sTllhV2ZGb2xpTEJZcGpTZmQzWUY0T3VFRkx1WDF2REp1MnZZOHUyRDZ0LzJaV1FJZHU1VW0zd1ZDOVc2YytSdDhiUFpHbWRqeVozWXhCcmJNWHRiN3ZuNkNIRWN3L1A1NnBBVmJLbFpPa0M0NUlyUzlWa01kZHVVWjhORXNOM2t0a04zd2JibDdHQnBRd0MybmcwK25vbzQ4amlhcjhkajdMVUFadjhQUFBmdkVhNk8wVTRBQUFHRGFVTkRVRWxEUXlCd2NtOW1hV3hsQUFCNG5IMlJQVWpEUUJ6RlgxT2xvaFVGTzRnNFpLaE9Ga1JGSExVS1JhZ1Fhb1ZXSFV3dS9ZSW1EVW1LaTZQZ1duRHdZN0hxNE9Lc3E0T3JJQWgrZ0RnNU9pbTZTSW4vU3dvdFlqdzQ3c2U3ZTQrN2Q0QlFMelBONmhnSE5OMDJVNG00bU1tdWlxRlhoQ0NnSHowSXlNd3k1aVFwQ2QveGRZOEFYKzlpUE12LzNKK2pWODFaREFpSXhMUE1NRzNpRGVMcFRkdmd2RThjWVVWWkpUNG5IalBwZ3NTUFhGYzhmdU5jY0ZuZ21SRXpuWm9uamhDTGhUWlcycGdWVFkxNGlqaXFhanJsQ3htUFZjNWJuTFZ5bFRYdnlWOFl6dWtyeTF5bk9Zd0VGckVFQ1NJVVZGRkNHVFppdE9xa1dFalJmdHpIUCtUNkpYSXA1Q3FCa1dNQkZXaVFYVC80SC96dTFzcFBUbmhKNFRqUStlSTRIeU5BYUJkbzFCem4rOWh4R2lkQThCbTQwbHYrU2gyWStTUzkxdEtpUjBEZk5uQngzZEtVUGVCeUJ4aDhNbVJUZHFVZ1RTR2ZCOTdQNkp1eXdNQXQwTDNtOWRiY3gra0RrS2F1a2pmQXdTRXdXcURzZFo5M2Q3WDM5dStaWm44L3hydHlZdkRSZkc4QUFBQUdZa3RIUkFEL0FQOEEvNkM5cDVNQUFBQUpjRWhaY3dBQUN4TUFBQXNUQVFDYW5CZ0FBQUFIZEVsTlJRZmtCd0lSRWcrc2gwT3FBQUFnQUVsRVFWUjQydXhkZDNnVTFkNSt6MnpMcHZlRWtJUmlDQzBRYWdoZE9xR0lOQkZFVWNFR0lvSUZwUGhodVY3QWNtM1gzcEFyS2dvaUlDQWl2WWwwRWtwQ1NPOWxlNXZabWZQOXNUdkQ3R1pEVVVUVS9UM1BQSnZkemN5ZU9YUG1uZmZYQ2FVVWZ2bnRZclBaVUY1ZWp1cnFhdFRXMWtLbjAwR3YxOE5rTXNGc05zTmlzY0JtczhGbXM4SGhjTURoY0lCbFdiQXNDNDdqd0hFY25FNm50UEU4TDczeVBBOUJFT1IvRTBFUUlOOG9wZUlyb1pSQzNBQVE3MWNBb0pRUzk5Q2xWNi9QU0Y1ZVhseFVWRlFUalVZVHAxQW80aGlHaVdFWUpvb1FFZ2tnQWtBb0lTUVVRQkNBUUFCYUFHcjNwZ0RBdUk4bkFPQUJzTzdOQnNBS3dFSXBOUUl3VWtyMWxOSjZTbWtkei9PMUhNZFYyZTMyNm9xS2lvcjA5UFJxOTNHb2JHdnduaERpL1ozNEdRQlFRa2lEVjlsR0dZWUJJUVFNdzBBUUJLalZhakFNQTRaaHFFS2hnRUtoQU1Nd0VQOVdLcFhTcTdpcFZDcW9WQ3FvMVdxbzFXcG9OQnBvTkJwb3RWcG90Vm9FQlFVaE9EZ1lJU0VoQ0E4UFIwUkVCS0tqb3hFYkc0dUVoQVJvdFZyL3pYUWRoZmlCN2JjSnk3SW9MeTlIWldVbGFtdHJVVjlmRDcxZUQ2UFJLQUdhMVdxRjNXNkgzVzYvSXFESndVeStDWUpBUklEekFqTVBJQlBCU1E1UzNvQWxmMTlkWFowYUhCeWNvbFFxYjJFWXBpVWhwRGtocEJtQVpnQUNicEpwdGdNb3BwUVdDWUpReVBOOEFjdXlGM1U2WFg2elpzMHVlQUVjdlF6Z05RQkFFZmdJSVI1QVJ5bWxTcVZTQkRZSjBPUWdKOS9rNE5ZWXdBVUVCQ0FnSUFDQmdZRVN3SVdHaGlJOFBCeVJrWkdJam81R2ZIdzhFaElTb0Zhci9UZVhIOWorSENrcEtVRmxaU1ZxYW1wUVYxY0huVTRIbzlFSWs4a2tBWnJOWnJzc29IRWNKN0V6YjFEekJXWWlLeE9CVGJ4MlBvRE00MzFlWGw1c1FrSkNCNVZLMVVHaFVMUW5oTFFIMEJhQTVxOThEWGllZDVTV2xwNU5Ta282NDNRNno5aHN0cHpjM055Y0hqMTYxTHFab2h6VUJEZFQ4MkIyM2tBbkFwc0lhQ0tidXhMSWlhQ21VQ2lnVXFrdUMzQmFyVllDdUpDUUVJU0doaUlpSWdKUlVWR0lpWWxCZkh3OGtwS1MvRGVaSDlodW5GUlZWVFZRT3cwR2c2UjJ5Z0hOYnJjM0FMUXJzRFFpcXAxZWdFWWFZV1VOR05uRWlSTVZIMzMwVWZlQWdJQnVDb1dpS3lHa0M0RFV2K08xNEhrZXBhV2xhTmFzbWZkWGVZSWdIT000N3JqUmFEeDJ4eDEzSE51OWU3Y2dBenNKOUFnaGdqZWpjd05iQXpiSE1BejFBWEJRS0JUMFN1eE5EbkFpZXhNQlRsUlB3OExDR3FpbmNYRngvcHZPRDJ4L25CZ01CcFNWbFVrc3paZmE2Y3VPeG5FY1dKYVZHSm9QT3hxUjI5RjhNRFBpdzE0bXNqTHl4Qk5QS0pZdVhkcEhvOUgwWkJpbUZ5R2tKNERnZjhJMXVReXdlWXVaVW5ySTZYUWVNcGxNaDJmT25IbG96Wm8xVGg5QUo0akE1Z1kwNnNNdVI3MlpuSmY5alhyYjMwUUdwMWFySllCcnpQNG1WMDlGOXRhMGFWT0VoWVg1YjBJL3NGMC80VGdPSlNVbEhpeE5yOWRMTE8xcTdHaStuQU1pb0hteE0wbk5sREV5eUlHTVVzclUxOWUzQ3c0T3ZsV2hVUFFqaFBRSDhJOWM5ZGNBYkEyZVU0SWc3T1U0Ymw5VlZkVys1czJibjNPRFd3TldKMmQwSW51VC95MDZHT1FnSndKY1k4NkZLOW5mUlBZV0hoN3V3ZDZTa3BLZ1VxbjhONlVmMkg2ZlZGWldvclMwdElGelFLNTJpcUFtc2pTUm9WMHRvUEU4TDZtWmdpQTBDbVpXcTNXQVdxMGV6RERNWUFBZC9GZm5kd0didDJRN25jNGRScU54VjNSMDlENjR2TGlDTjlnMUJuSU13MGpxcWtLaG9GY0xjQ0tERTltYkNHNXk5ZFRidVpDWW1JajQrSGoveGZjRDI3V0x4V0pCY1hFeHlzdkxVVlZWNWRNNWNLWHdEUysxazNnNUJ1VHNqSGd4TkFuTWJEWmJsbHF0emlLRVpBRm82cjh5TjBUS2VKNy8wV1F5YlkrTWpOeU9TK0VxUGtGT3BxNTZxS29paS9QeW9OSXJzVGR2OWRUYnVSQVhGNGVFaEFRa0p5Y2pLQ2pJZjdYOHdIWjFVbEpTSXJFMHVTM05sM1BBMjVibWc2VmRDZENJdDgzTVlyRU0xR2cwdHhGQ1J2bkI3S1lBdVMxNnZYNUxURXlNeU9ROFFNNmJ4Y2xBcmxHQVV5cVZqUUtjbkwxNU94ZThiVytKaVlsKzc2a2YyQzR2SnBNSlJVVkZLQ3NyODJCcEJvUEJJeWJ0S29Oc2lhaDZpdXJtWmRnWm85UHAwb0tEZzhjeURITTdnSGIrSlhsVHlsbVdaVGVXbDVkdmJ0bXk1Umtad1BFeWtLT3kxd1lzVHE2bXVvR05YazF3cnp6MlRmU2NpdXl0YWRPbWFOYXNHVUpDUXZ4WHlBOXNEVm1hNkNEdzVmRnNMSVJEQkRWZmdDYlBFcEFEbWdobW16WnRDaGc4ZVBCRXBWSTVnUkF5ekw4TS96b2lDTUkycTlXNjRhMjMzdHE0YU5FaUd3Q25IT1JrTEU0UTJadTNtdW9WSHVJQmNDSzROUllhNHUwNUZSMExmdmJtQnpZQWdOVnFSVUZCUVFNSGdjalN6R2F6WkV1N1F1YkE1UUNOeU5WT3ZWN2ZJU2dvYUJMRE1KTUErRmZpWC95WnlISGMycEtTa3ZVcEtTbG5aYW9xNzYycXlnQ09YZ25nTGhmWXE5VnFFUndjTExFM2I4ZENpeFl0RUJnWTZMOHkvMVJncTZpb2tGVFA2dXBxRHdmQmxlTFNaS0RtRTlEY0FiVWU2cWJWYXMxU3E5V1RDU0VUL1V2dWI4bmkxdW4xK3JYUjBkRTczQXhPRG5DOEx6VlZGaWJTQU9BYXM3MzVpbnNUVmRQWTJGaEpOVzNTcE1rLy9wb28vMGtueS9NODh2UHpVVnhjaklxS0Npa2x5bUF3d0dnMHdtS3hTS0RtY0RoOFpnK0lnQ2F6by9rRXRMdnZ2bHZ4MFVjZjNhMVVLdThHME1kLysvOTloV0dZY1pHUmtlTjRudDl2c1ZpK0hqeDQ4TnJEaHc4N1JaQ2psRGJ3clBJOFR3VkJvR0xpUGMvelZCNFM1SFE2cVR4Y1NOUVE1S1lROGFFclBvU3RWaXZNWmpOTUpoTnV1ZVVXS0JTS2Z5NWpLeXdzL0VlY3FORm85QWxvb3NkVHJuWmV6bzdtUytXVXNUT3lhZE9ta05hdFc5K3JVQ2ltd1I5ejlvOFVTbW0ydytINGN2MzY5V3NXTEZoZ0pJVHdNaWJYUUUyVnNUaHZCbmRaKzV0Y1BSVURlOFdjMHlaTm1pQTVPUm1ob2FIL1RHRGpPTzV2ZjVKbFpXVW9MQ3lVdko1aUJvRzM2dGxZR0llWDJrbGthVThTcUdWblowZTBiTm55Zm9WQ2NUK0FGUC90N1JjQStYYTdmZFdtVFp1K25qSmxpczVMVGVYZDRDYXBxTzVnWHlyUFFmVldUMzJGaFhpblpFVkhSMHRlMCtiTm02TnAwMzllNU5EZjJzWkdLVVZ1Ymk2S2lvcWtnTnZMZVQyOU13Z2FVVHRGTHlkREtTVW5UNTRNYmR1MjdReUZRakVEd0MzK2U5a3ZQdVNpeldiNy9LdXZ2dnB5K3ZUcGhzc0JIQ0ZFa0NYZGUyY3VVRjhaQzQxNVRjV0EzbWJObWlFMU5SWHViQWsvc1AyVnhXUXk0Y0tGQzVMNldWMWRMWGs5NVFHM2plUjVFdTlVS0M5UEo3Tmt5UkxGa2lWTEhsUW9GQS9DSDMvbWw2dDcwSjYxV3Ewcjc3ampqdFZidG14aDNRQW5xYWd5Z0JQa0hsVHYxQ3lWU2tVYnl6ZVZxNldSa1pHSWpZMlYxTktVbEpSL1RNemIzeExZcXFxcWtKK2ZqNUtTRWxSVVZEUUk1ZkJPWHBmYjFMeFltb2RqUVBSeTJ1MzJPMVVxMVNNQWV2aHZWNy84Qm9BN2JEUWFQd3NQRDk5SUNPRzhBTTZid1ZGWkJnUDFabS9lTVcvZUFiMWlTRWlUSmsyUWxKU0VXMjY1NVI5UkR1bHY1eFV0TEN6RXhZc1hQZUxUNUxtZTNzbnJjcFltT2dya2FxZk1qc1pZcmRhQmFyVjZwanZseVM5KytXMXNncENNc0xDd0RKN25SMVZYVjMvV3BFbVRBM0tBOC9haThqeFAzUUpCRUtoWC9UN3hQWHg1VVVXeml1aEZ0ZHZ0c05sc2FONjh1Ui9ZL2lyMnRQUG56K1BpeFl1Tk9na3VVOW0yQVV1VHE1MzE5Zldwb2FHaHN3Z2hEL3R2Uzc5Y0wyRVlaa1I4ZlB3SWxtVS95c25KK2J4ejU4NzVNb0FUdzBSNEFJSWdDSUxZMzhLOVVURmxUdzV1M2lBbk9zQzh3YzF1dDZOMTY5Wi9XN3ZiM3dMWWJEWWJ6cDgvNytINWxNZW5OV1pQODJacGJsQ1QxTTU3NzcxWDhjRUhIOHhXS0JTUEFVajAzNHArK1NORXBWTE42TlNwVTViRllubS9iOSsrbng4L2Zwd0Z3TG5aR3lPcXFKUlNxUmltMjRGRjVjVVZ2QUhPdTFxekhPQllscFhBN2UvWVNPWXZEMnc2blE2NXVia29MQ3lVQ2tMS3EzTDRLZ2JwZm9ySlFVM08waGhLS2JGYXJVTTFHczNqQUFiNWJ6Mi8zQUJwR2hnWStQeXZ2LzQ2b0xxNitxTW1UWnJzSllTd012V1U0RkpPS3ZWaWNIS0FvNzZhQXZsU1Q4VW9nTlRVVkVSRVJQaUI3V2FScXFvcWozQU9NWW5kVjRWYldTZ0hFZW01RDViR2xKYVd4c2ZGeFQzT01NeGMvNzNtbHo5QlBSMFFIeDgvd09Gdy9IZkhqaDJmWm1WbFZiclZVMFlPY0s0SzVrUm9qTDNKVkZWY2pacWFtcHI2dDNJcS9HV0JyYVNrQkhsNWVTZ3FLcExDT2VUTlZYdzVDYnhaR3MvemNwYkcyTzMyQ1NxVmFtN3B1ZU5keXgzQnlFaHZkVk9kYzMxOVBXcHJhNUdhbXVwSGdMK0E2UFY2Yk5xMENRNkhBNE1HRGJvbWc3MWFyWjQxYk5pd1huVjFkZTlIUlVWdElZUW8zT29wSjdPOUVibjlUUTV3TE1zU2xtVWJ0YjE1TXppTzQxQlRVd096MlF3QWlJaUlRT3ZXclg4MzhTZ29LQUFBeE1mSFg5WDVWMVJVd09Gdy9HN25CdFBvTjZ3ZW55eDdFb1A3WnlJanN6OGVXZmdxeWt6T20yTEI1T2ZuNDh5Wk14NXhhcGZZbWc3RkY4N2cySkZma1oyZGc4TGlNbGhzZGpnY0RpSUNuRHMxaWdpQ3dBaUNvS2lzckV4Mk9wMnZxVlNxL3dIb3V2by84M0RQd25kdWluTTlmUGd3M256elRRREFrU05IOE5wcnIvMnBZL2c5d25FY0JnMGFCSVBCOEtlTjRYckwyYk5uWWJmYkczenVkRG94WWNJRUZCVVZJVEF3RUZhcjlacVBUUWpwSEJrWitSN0xzb3QzN05qUmpGSWFRQ25Wd05XWVdrVXBWVkJLRllJZ0tBUkJZSGllSjA2bms3QXNDNVBKQktQUlNFUXZxRHlQMUdBd1NGV2lLeW9xVUZ4Y2pBc1hMdURRb1VQNCtPT1A4ZmJiYi8vbWRmYlNTeS9oekprekFJQ0NnZ0tzVzdjT0sxYXN3S3BWcTY1cVBXelpzc1huLzE3cmVtZ1UyTFo5OEN4ZS9IUTNSazUvQnYrMzREN2svZmdlSG5uMjR6OTlJWjA3ZHc1bno1NUZmbjYrUjdWYmthMVZGZWNodjZRYXlzQlFoQVFIZ0hOWVNIMjlub2lNemMzVWlDQUlJa3NiRngwZC9SVWhaT2JOK05ULzhzc3ZrWkdSOGJjWWcwcWx3dnZ2di8rYnVpN2REUFBnUytiTW1ZT1NrcElHbitmbDVZRVFna1dMRm1IeTVNbG8xNjdkNzVtM0IvcjM3Ly9mRXlkT1pBSFF3dFVUVmdRM3BSdmdHQkhjM0wwMENNL3pjRGdjeEc2M0V4SGNMQllMVENZVFRwNDhpWUtDQXRUVTFFajlQVmlXeFpneFl6Qnc0TURmTkU2VHlZVGR1M2VqVFpzMkFJRE16RXlzV0xFQ3Q5MTIydzFmRDQwQ1cxRlJFVWhFYzB5Wk5BWWpiNzhYTHk5L0VjTzZKTUVKUUY5NEZIUHZuNENNakV6Y052a2g3RDVYQlFBNHV2NU5ETSs2RjNrMkFIRGl4WWRHNHVGbFg3cDUrVWxNeWhxT2Q3L1lpRGxUYjBkbVJpWSsyMTNvWW1DSHZzYzk0NFlpSTZNWHBqNzZIUEpySFNKdHhOWlBsbVAwZ0w3STdEc1lEeTU0QmFmUG5VTkJRWUhVRHMrN0owRkZlVFhBcUJBYUZBQ1ZXa3ZVU2dXbzRIU0JtdE5KT0k1bG5FNG4wMlBBaVBCdjlwMTVRYVZTZlFtZzI0RnYzOFR3L3IwdzhMWjdjTGpRSktldUhtTlk4dVlhT0c3UVRXTTBHbkgyN0ZuMDZIRXBEdGhxdFdMZXZIbm8zNzgvSG52c01UZ2NydEdZeldZODlkUlRHRHAwS0NaTm1vUlRwMDRCQUw3NDRnczgrdWlqR0RCZ0FCWXZYb3lzckN6TW1UTkhiTHFNdFd2WFl1VElrY2pLeXZMNXBQUTFobDkrK1FYang0L0g0TUdEc1dUSkV0aHNOZ0JBYlcwdFpzK2VqYXlzTEl3ZlB4Ni8vUEtMdE05ZGQ5MkZrU05IWXM2Y09aSzZJOHFubjM2SzRjT0hZOGlRSVhqdHRkZmdIVFR1UFlidnYvOGU3NzMzbnZUOTZkT25zV0RCQW9rcExWdTJERU9IRHNYWXNXT2xNWmpOWmt5ZlBoMnZ2LzQ2aGd3Wmdva1RKNktvcUFnQThNSUxMMkQvL3YzUzhWYXZYbzNWcTFkZjl0cnMyYk1IRXlkT1JHbHBLZWJPbll1SkV5ZGk1ODZkQUlDbm4zNGFUenp4QkNvcUtqQng0a1JNbkRnUkxNdGU5bmpyMTYvSDY2Ky9qaGt6WnFCZnYzNVl1blNwTkEvTGx5L0hxRkdqdWl4Y3VQQ0RYYnQyemYzd3d3K2pLYVVCQURTVVVoV2xWQ2tJZ3BMbmVVYmMzTUJHREFZRGFtdHJVVnRiUzJ3MkczUTZIVTZmUGcyajBZanM3R3hzMjdZTlI0OGVSV1ZsSmNyS3lsQlFVSURpNG1MVTFkVkp2NStYbDRkNzdya0hnaUJJNDUwOWV6YU9IajNxY1E0Yk4yN0VxRkdqd0RETUZkZjI1ZGJEdVhQbk1HSENCQXdZTUFDZmZ2cnBGZGZraFFzWE1IUG1URHp3d0FQbzM3OC9GaTVjQ01oMGM0K3Q4c1FHOUdqZkVwMTZqY0RTVjk3QjZSTFhpVkxlZ0Zrak9xSDdzR240YnRONnpKcHdLMXIzbUlBU084V3VqK2VqUllzK09HbW1vSlRESTROU01HejJXNjc5cXZlaFM0c1dhTm02RTJiTW00OUY4NS9FM253eitMb1RHTkMrSlVZLytDdzJyRitOMFQzYll0VGpybjFPckZ1R2xpM2FZdjdySy9ISzBqbElpb2xDMXJRbk1ILytmRXlmUGgzanhvM0Q0TUdEa1ptWmlZNGRPeUkxTlJVYUJRRlJhaEFaR1VsQ1FrS0lWcXNsYXJXYVVTcVZDdUt5S2FwWHJmbDI4T0lIUi8vWXNtMGZlcXlhbzQ3U1BiUnJTZ3M2OXFHbGRQM2FsWFJZdDFUYWVzVGpsRkpLVDZ4YlJsdTJhRXVmZWV0L2RPMm5LMmlubEpaMDJib1Q5RWJJcWxXcjZOdHZ2eTI5Ly9ISEgybi8vdjFwV1ZrWmRUZ2M5TTQ3NzZScjE2NmxsRks2WU1FQytxOS8vWXNLZ2tBUEh6NU1lL1RvUWExV0szMzk5ZGZwZi83ekgzcmt5QkU2ZE9oUXluRWM3ZFdyRjYydHJhVUhEeDZrSTBlT3BIcTludXIxZWpwa3lCQjY4dVRKeTQ1QnA5UFI3dDI3MDdObnoxS2U1K25DaFF2cEcyKzhRU21sOVAzMzM2ZWZmLzQ1cFpUU1E0Y08wWDc5K2tuN21Vd21halFhYVhwNk9qVVlETkxuNTgrZnAvMzY5YU1XaTRWYUxCWTZaY29VK3NzdnYxeDJERFUxTmJSMzc5NlU0emhLS2FWTGxpeWhhOWFzb1pSUyt2cnJyOU9ubjM2YU9wMU9ldmJzV1pxWm1VbnRkanMxR0F3MEpTV0ZIamh3Z0ZKSzZZb1ZLK2o4K2ZPbGVaMDVjNlowL0dIRGh0SEN3c0tydWtaRGhneWh1Ym01RFQ0L2NlSUVIVE5tekZWZjY0OC8vcGplZHR0dFZLZlRVYlBaVEFjT0hFaC8vZlZYU2ltbE5wdU5HbzFHT21QR0RQcmpqejlTbnVkL0tpd3NuQUNnR1lBbUFLSUlJV0VBZ2dBRU1BeWpKb1FvQ1NFS3RWck5CQVFFRUFBa0xDeU14TWZISXprNUdRRUJBV2pldkRreU16TXhlUEJnakJzM0R0T25UOGY4K2ZQeDBFTVBZZURBZ1RoKy9EaFlsZ1dsRkdQR2pNSE9uVHRCS2NXNWMrZlF0MjlmT0oxT0Q4d1lOMjRjeXNyS0dtREpwNTkraXVlZmY5N2pNN2VxalBUMGRCZ01CdW56anovK0dHUEhqb1haYkVaVlZSVXlNek9SblozdHNlK3FWYXZ3OXR0dlMrOVBuejZOcmwyN29xeXNERmFyRlJNblRteWNzY1dsajhiV3JkL2p2cXdPMlAvTmV4Z3plQWcrK2ZrY2hPSUQySEhXZ0lrUFBvM2JSNDdCb3NmdUJsdDlGTHRQMWw4VkN4bjE2SEo4K09veXZManNaZlJwR1lTQ2c3dFJhR1V3ZmQ0aWpCNHpHVys4K3dGbWp1dnRlZ0o4dndFMExBRjh4Um5rRkJrUW9IRGk5UEdqVXB5YWQwaUh6V1lEVHlrZzhCNnFwOHZyS1RBVVVNeCtZOVBEVXllT1g3bGsvc3loR25zWnR1M09SZjZ2aDFEUEIrREJwNS9CbUhIM1lHQ1hoRXRQb2U4M0FCRkpTRkFZVVdVTFJHSUVnMTI3OXQwUXhyWjI3VnFNR3pmTzQ3TTJiZG9nSVNFQmFyVWEzYnAxUTBWRkJRRGdwNTkrd293Wk0wQUlRZmZ1M1pHY25Jd1RKMDRBZ0ZSMU5TUWtCRXFsRWtGQlFXQlpGcHMzYjBhVEprMndkdTFhckYyN0ZqRXhNVGgwNk5CbHgzRDQ4R0YwNk5BQmJkcTBBY013V0xCZ0FjYVBIdzhBbURGakJoSVNFdkQyMjI5ajU4NmRxS21wa2ZZVGY5ODdJRFF1TGc0cWxRcnZ2UE1PVHA4K2paVXJWelpRTWJ6SEVCMGRqUzVkdW1EMzd0MWdXUmE3ZCsvR3FGR3VaSkFmZnZnQklTRWhXTGx5SlE0Y09BQ0dZWEQrL0hscEREMTc5Z1FBWkdSa29LcktwV2tNSERnUU9UazUwT2wwT0hueUpPTGk0cTVIVzc5cmx1N2R1eU04UEJ4QlFVRm8zNzQ5S2lzckFRQUJBUUVJQ1FtUjZxc3hERE80V2JObWIzenp6VGRUM0twcGdLaWFBaEJWVXdVQVJzeWVJWVFRdHlPTjJPMTI4RHdQdTkwdXFhWjZ2UjUxZFhWU1lMdlJhRVJ1Ymk1T25Ub0Z1OTJPR1RObTRQUFBQd2NBZlBYVlY3am5ubnM4NnIzbDV1WWlKQ1FFQ1FrSlYzV3VqYTBIQU9qY3VUT0Nnb0lRR3h1TFljT0dlVEQveHU2TGhJUUVKQ1FrUUt2VjRxNjc3bW9NMkZoc1cvTVo5aGN5ZUd6eE1temJzeGUzZDFEaGxkZmVkM2xRQUdnMHJ1YXRLdmNyeC9MdWZRWElHR3NEQ2ZlS2wyRTVCd0FGTkJxWGc3WkZlaDlrOWUzc29wd21DemliSGhmT24wZGhZUkhDazFJUkc2SzhYUGNvUWtFSXBZSlVPODNwNUJpZTU1bml2TzBKdlZxMmVDMUFxMzBEUUlKU280WUNBTWV6NEp3OEFBV1Vtb1pPWXJ2TkRpWERvNmF5RXBXVjFlaVNOUVZadmRyZUVGdGllSGo0WmF1aE1nd2pxUXNjeDBHajBVamZhYlhhSzZvL05wc05vYUdoMGpaMjdGajA2ZFBuc21QZ09BNXF0VnA2SHhJU0lwWEZlZTIxMTdCKy9YcjA2TkVESTBhTXVLcnpEQXNMdzhhTkc1R2Ftb3B2dnZrR3c0WU5RMWxaMlJYblllclVxZmoyMjIreGZmdDJEQm8wU0FveXRkbHNpSXlNbE01cDd0eTVQdWRRUG5kS3BSTGp4NC9IOTk5L2ozWHIxdUd1dSs3NjAyMTNoQkJjSVk4N1FhMVd2elJ6NXN4bk5tM2FsQ2lxcGpLQUU0R05jZHVUSVFnQ2NRZm5Fa0VRaU1QaDhIQXE2UFY2NmQ2eVdDd29MQ3lVd0sxdjM3N0l6OC9IdVhQbnNHM2JOa3lhTk1sak1HdldyTUdFQ1JPdSt6eW8xV3JJUzZ0ZHpYMmgwV2dhQXpZbGp2KzRFazh0ZUJaN1QrYWp0TFFRZXJQcnhsRTA2NFpPMFNyOHNPNS9LQ3d0eGFvdk53Q0JMZEd0WXd6aVkrSUExT0dYZzluSVA3a0xGNnI1S3c2OFpaY01SQ2hZYlBwbUhjcEs4L0RZdUV6MHZ1ZEZtRXdtTkVtK0JaYmFLZ2loOGJpbFpUTW9CUllCYWtXRE9EVjN4VnZDc2l3VWhBQlVJQ3pMRVo3bkdVR2d6TkNIWCszWHBIbm1oNW50dzJidTJyZ0c1NHBMOGUycU5iQ1FFR1IwYVkxYk9uUkVJQ3hZdjJvZENzOGZ4Y204T21sOGZYcjNCS2ZYSVNFakN3OC9PQTF0NHNPUWtkSGxEMS9ZYTlhc3dSMTMzSEZOVC9zdFc3WklidmJzN0d4MDdOanhpdnRVVlZWaHpKZ3htREJoQWxxM2J1MnhZSHlOb1hQbnpqaDY5Q2owZXIzMFB4OTg4SUhrdFIwM2JoeTZkKzh1cVFsWGtsMjdkdUhsbDEvR2JiZmRobGRlZVFWdDI3YkZ3WU1IcnpnUDNidDNSMmxwS1ZhdVhJa3BVNlpJbjJka1pJQmxXVXlZTUFFVEpreEFRa0xDVlFXZlRwbzBDZXZYcjhmaHc0ZXZ5WGl1MVdwUlYxZjNwNEJmZW5vNmpoNDlPcU5YcjE0dkZSUVU5UERsV0FDZ0VBU0JFZG1iNkRXbGxNS1hVMEhNMXJIWmJGSUovYnk4UEp3OWV4YVRKazNDbzQ4K2lxRkRoM3BVQ2VFNERydDI3Y0xRb1VPdnkza1ZGaGFDVWdxSHc0SGR1M2VqUTRjT1Yxd1BPcDFPc2pmLzlOTlBqY1d4TVhqc3BmK2lldjVDekJnM0JCd0ZvbHQyd2Nzdno0RlMzUVRMWHY4WFpzNTdIZ1A3cllJNnJDbWVlZVZ0cElVQ0dId25ScVY5ZytVUDM0WlBXL2REaTZaaDBGM2hKRFRKL2ZISzBnZngxTDhYb2UrN0hLS2FkOFZ6QzZmZzlPblRpTzQ0QkptdEwrRG5iejRENitTaDBJYWdaWW9HQVhCNng2aEo4V2xRS0FnUkJDSUlQQUhBREo3NHlOVFgvKy9oWndqUnBpeDQrWFVVUFRJUEkyN3RCMFZBQk81YitocUd0dElDR0lvbER3N0JzeDgralI5WHg2Sjl3cVdxbzhNZmZoNlBsejZCTng2Zmd1VThFTjJxSzE3cVB4bEErQisyWUZtV3haNDlleVNEK05YSTBxVkxNWFBtVEd6YXRBa1ZGUlZZdW5UcEZXL284ZVBINC9UcDA4akt5a0pNVEF3VUNvWGs1bTlzREFrSkNaZzdkeTRtVEppQW1KZ1lBSkRjN2pObXpNRENoUXNSR3h1TDJOaFlDSUlBZzhHQXNMQXdqQnMzRHZYMTlUQ1pUQmcxYWhTQ2dvS3daY3NXWkdabVl2WHExUmd6Wm96cndhbFFTRGZJNWVhQkVJS0pFeWRpeTVZdFNFbTVWTmR6NGNLRmVQVFJSekYyN0ZoUVN0R3VYYnVyOHFhS1BRTmF0V29GcGZMcXd6dW5UcDJLT1hQbW9Ibno1aGc5ZWpTbVRwMTYzZGZEODg4L2p4MDdkcUMydGhiSGpoM0RTeSs5aFBmZmZ4K3RXN2ZHMUtsVE1XclVxSUZ4Y1hITkJnMGE5TmJQUC8rODBaMkdKYS9zekFNQXBWUnd4MjVDb1ZCUWtSQ28xV29hSHg4UHM5bU1VNmRPU1d6eHM4OCtRL3YyN1RGeTVFZ0lnb0EyYmRxZ3VMZ1k5OTU3cjhmNGZ2NzVaL1R0MjllRHlRUEFoZzBiOE5wcnI4RmtNc0hwZE9Mbm4zL0dJNDg4Z2ttVEpqVzZIa1NRR2p0MkxQUjZQWHIwNkNHWkR5NjNIaHdPQis2OTkxNHA5T2FLWll0WWh3bFdCNFB3VUsvTzA0SVQ5WG85Z2tPam9mWllCd0wwOVhvRVIwWmVVL1N2d0RxZ045c0FCamliaytNUnpsRmRYWVhhZWgwY05sdURiQUk1cU1teUNKaHBkMDlWTFAvUGZ4ZEVod2N0Z2tjZ3NtdDgydUJ3YU5TZWhOVm0wUVBLWUdoOXFLU3N3d0tqalVkMCtCOWZhdm5zMmJNNGN1UUk3cjc3N212ZXQ2NnVEcUdob1ZDcFZGZTlqODFtQThkeEhtV2tyelFHbG1WaE5wc1JHUm5aWUlIWmJEYUVoMThiOEp0TUpoQkNFQndjZkYzbXdXQXdRSzFXLys0OHlDbFRwalNJdTl1NGNhT0g1MC9za1hHbGN5NHVMc1lqanp6aThWbjc5dTJ4WXNXSzN6eSttcG9hYUxWYU1BeURmLzNyWDg3RXhNVFgzbnZ2dlk5T25UcGxkNmRrY2U3TjZhN3pKaEJDQkhraFM0MUdBN1ZhVGNYYWJtTHBJN0ZvWlZSVUZPTGo0MkUwR3BHVGs0TlZxMVo1bk91bm4zNktQbjM2b0ZXcjZ4ZlFMaWJyeTBOQkdsc1AyZG5aV0xod0liNzc3anZvOVhwRVJrYmVYUFhZZERvZGNyeEFUWFFTaU0xVzVOMmpaS0FtcjV2R1ZGWldKa1ZGUlQxRENKa0J2L2psYnlxQ0lPRHV1KzlHMDZaTndmTThUcDA2aGErLy9ob2hJU0VyZCt6WThlSHc0Y05MQ1NFT0wzRGp2Y0JOS2owdWdwdThHNVlJYnFXbHBTZ29LTUNzV2JQUXAwOGZ0Ry9mL3FiSkx4V0JiY09HRFpjWS9jMENiSEpRS3lrcDhRQTFlY01WSDB4TlRJMWlCRUVnSnBPcG0xYXJYZWl2bWVhWGZ3cTRaV2RudzI2M0l6MDlYWElnOFR5L0pUYzM5NzEyN2RxZEJ1Q1FzVGVudTdrTTcrNnhJSWlOWStUZ0pwWWNGNzJYWWcyM2xKUVVxV0RselFKdVZxc1ZoWVdGSGtIUU53V3c2ZlY2Wkdkbit3UTFrYW41VWo5bFdRU0VVc3JZYkxaaGFyVjZFZnlWYmYzaUYxQktENWVYbDcrVG1KaTQxd3ZjT0RlNFNjek5YWDY4QWJnMXBwYUs0SmFXbG5iTlpvY2JJWDk2RXJ6QllQREoxSFE2bmM4S0hWN3Fwd1JxRG9kanNsS3BYQVNnbFg5Sis4VXZya3E5Q1FrSmtiVzF0VzlGUjBkdnBwUXl4QlU0UmlpbEhOeE9CY0dkVXVBbU9SUXV4d1AxQVpTK2ZnTnBhV20vS1MzcWJ3dHNKcFBKcDAxTkJMWExxWjl5VUdOWjltR0ZRckVZUUl4L09mdkZMeDdBa3hJVkZiVllyOWNIaDRlSGYrTXVkMFM4TlRZUjNKeE9wMDl3YXd6VXhLMURodzQzVmFNWTVzLzZZWXZGMHFpandFY1RZOUlZcUhFYzk1UkNvVmp1QjdYRzdRLzMzWGZmVFRXbXdzSkMvTlVhZGIvODhzdm8zYnMzaGc0ZEtxVWFUWjQ4R1pNblQ4YjA2ZE92KysvdDM3OGYxN0huYjB4WVdOZ1NzOWw4THdDdExKaFhucWtnWlNrNG5VN2lybHBKUjdRQUFDQUFTVVJCVk5NbUpkRGJiRGFQUU42NnVqb3BlVDQvUHg4NU9UbXdXQ3c0ZS9hc2xOSGhMZlBtelpNeVpScVR6WnMzU3hrT2Z6bGdzOXZ0MStMOUpQSU9VcUpON1oxMzNsRnhIUGNzd3pBdndwVlM0aGNmd3ZNOGpodzVjbE9ONmNVWFg0VEZZcEhlRzQxR3pKNDkrNmFkdzVNblQyTExsaTM0NmFlZnNHM2JOblRxMUFrcWxRcXZ2UElLbm52dXVlcyt2elUxTlZpK2ZQazFoZXo0a29zWEwrSzU1NTRUM3dZRUJRVXRyS3lzZkVpajBRUTFBbTRLUVJDSXZQeVJ1OUt1Qkc0V2l3VkdvN0ZSY0h2aWlTYzhVcTNrY3VyVUthbGdRbU5TV1ZrcEZTZjRTNm1pSE1kZDF2dnBaVk1UY3o0aEw4Znk2NisvQnFlbnB5OWlHT2JKdnlNWUhUaHdBTTJiTjhmKy9mc1JGeGVIdm4zN1NqbDFack1aMjdkdkJ3RDA2ZE1IMGRIUjBuN2w1ZVhZdDI4ZklpTWpNWERnUUo5VkZrNmRPZ1dOUmlNVkVheXRyY1hPblRzUkdCaUlmdjM2ZWFnVFI0OGVSVjVlSHZyMTY0ZmMzRnpjZXV1dGtscXliOTgrbEplWG8zZnYza2hNdk5RT29xeXNEUHYyN1VOb2FDZ0dEaHpva2VZRnVMSWlhbXBxMEw1OWV3QkFUazRPenA0OWk5MjdkMlBidG0wQWdMNTkrMHJ4WjhYRnhUaHc0QUNTazVQUnExY3ZDUWd2WExpQW1KZ1k3TisvSDZtcHFlalNwY3NWeDBBcHhkNjllMUZSVVlHZVBYc2lPVGxaMmljbkp3ZFJVVkVvTFMxRmJtNHUrdlhyaDhURVJPemN1Uk9IRGgxQ1JFUUU5dTNiQjdWYUxjMUQwNlpOWVRLWkdtV2xodzRkUWxKU0VucjE2Z1ZDQ0k0ZE80YlUxRlJvdFZxY1BuMGFuVHAxd29VTEZ4QVlHT2lSWTdsdTNUcU1IVHYyaXV2aCtQSGppSTZPUmxKU0VnQkllYk9EQmczQ3NXUEhjUFRvVWV6YXRVc0tjQjA2ZENpMFd1M2MxcTFicTRjTkcvYlJ5eSsvckhlcmxLTGR6ZWxXUzNsM3FYeFJMWVhENFNDQ0lGQ2owUWkxV28zRXhFUVBWWlFRZ3BLU0VnUUVCSGpZMjhUcjU2dHMwOUdqUjVHYm00dHUzYnA1eE1BSmdvQWRPM2FndnI0ZVE0WU1rWTczeXkrL29FMmJOZ2dMQ3dQTHNqaHc0SUIwTGV4Mk83WnQyd2FsVW9sMjdkcmRXTVpHS2ZVQU5Ya3l1M2ZURlZsdkFnOVFPMy8rZkVSNmV2clN2eXVvQWE0eU9rOCsrU1QwZWoyV0xWc21aUVBvZERyY2R0dHRLQzB0UldGaElVYVBIbzNhMmxvQXdMRmp4ekJwMGlUbzlYcHMzcndaRHp6d1FJUGpidG15QmZQbno1ZkE4T0xGaTVnNGNhS1UvRDFtekJncExXWDE2dFY0K3VtbllUUWFzV2pSSWp6OTlOTWVLc1hYWDM4Tm85R0lLVk9tSURzN0d3QncvUGh4M0gzMzNUQWFqZGk1Y3ljbVQ1N2N3RGF6YnQwNmp3VG11cm82bEphV1FoQUVGQlVWb2Fpb1NMVHpZT2ZPblhqZ2dRZGdzVmp3N3J2dlNvR3NwYVdsbURWckZ0NTQ0dzNvZERyTW1qVUx1M2J0dXVJWTVzMmJoeSsrK0FJbWt3bFRwMDcxS0ZXMGF0VXFUSjgrSGUrKyt5NXljbkpRWCs4cTZsQlNVb0thbWhyWWJEWVVGUldodExUMGl0ZHY1ODZkdU8rKysyQ3hXUERSUng5SmtmSWJOMjdFa1NOSGNQYnNXVHp4eEJNQWdQZmZmOTlEUGFPVTR2dnZ2OGZ0dDk5K3hmVncvdng1UFAvODg5TC9iZDI2Rlo5Ly9qa1loa0YxZFRVcUt5dkJzcXcwcjZLd0xEdkw0WEE4UEhIaXhHZ0FZdkZLRlFDbElBZ0tBQXBLS1dGWlZsSkxUU1lUcXFxcVlMRllVRjFkalp5Y0hBL21WbFZWaGExYnQ2SmR1M2JJeWNrQnBSUm56cHpCK1BIalVWMWRqYzgvLzl4ajdsNTk5Vlc4L3ZycnNGcXRlT1NSUjdCanh3N3B1dzBiTnVEbzBhTTRmZm8weG93Wkk3SDc1Y3VYU3lZTW85R0lKNTk4VWdMQ3FWT25ZdnYyN2NqUHozZGxmelJXdHVpUDJFNmZQbzF2di8wV3I3enlDdWJObTRlcFU2ZGk1TWlSNk5PbkR6cDE2b1RVMUZRa0pTVWhOallXNGVIaEpDZ29pR2cwR2thcFZDb1VDb1d5c0xBd1RoQ0VOK25mWElZUEgwNnpzN01wcFpTV2xwYlNEaDA2VUtmVFNXdHJhK214WThjb3BaU3lMRXRuekpoQnQyelpRaW1sOVA3Nzc2ZWJObTJpN3RMUWRNR0NCVlNuMDFHajBValQwdExvMXExYjZaQWhRMmhWVlpYME82V2xwZlRNbVRQUzhiS3lzdWpwMDZjcHBaVDI3OTlmR2tONWVUbnQzcjA3cFpUUzQ4ZVAweEVqUmxCWE5XcEt2L25tRy9yVVUwOVJTaW45OU5OUDZlelpzNm5UNlpTK3M5bHMwdThKZ2tDenNyS29UcWZ6T04rYW1ocmF0V3ZYQnZNd2VQQmdhUXdXaTRWMjd0eVpPaHdPbXBPVFF3Y05HaVNONFlNUFBxRFBQZmZjWmNkdzVzd1pldXV0dDFLeFIrZXVYYnZvK1BIanBkK2FQMzgrZmY3NTUzMWVqMDJiTnRGWnMyYjUvTTVvTk5LT0hUdDZmRFpxMUNoNitQQmhTaW1sSE1mUm5qMTcwdno4ZlByZGQ5L1JkOTU1aDM3NDRZZDA0TUNCdExLeWtvNGRPOVpqam80Y09kTGd0eHBiRDNhN25XWmtaTkRpNG1KS0thVjMzbmtuM2JGangyVkxKeGtNQnBxV2xrWTVqcU4ydS8yRDJiTm5ad0JvQVNBQlFCU0FVRUpJSU1Nd0dnQktobUVVR28yR0lZU1F3TUJBRWhzYmk0U0VCREFNZzA2ZE9xRlBuejRZT1hJa0prK2VqQ1pObW1ERmloWDQ5dHR2cFJwNUgzLzhzWFQvOSt2WEQvbjUrYWlzckVSR1JnYnNkanNvcFRoNDhDQ21USmtpbFMxYXRHaVJ0TS8wNmRPeGZ2MTZVRW94ZHV4WW5EaHhBcFJTMU5UVW9HdlhycUNVNHBkZmZzR0lFU01nc3N5MzNucnJ4cW1pNTg2ZHc0VUxGeVQxVTk3elU3U3BOUko4S3pHMTVPVGtSWVNRMmZnSGlKaXYyTFJwVXpBTUE2UFJpSkNRRUt4ZXZSb3JWcXhBYUdnb2NuSnlwQ2Q3WVdHaHBGNFNRdkR2Zi84YmdNdnpiTGZiTVgvK2ZQVHMyVlBLN3dSY3BXT2VmLzU1V0N3V0JBY0hvNktpQWp6UFM2eElQSjdjMXBPYm00dnk4bktNSERsU3N1RzFiZXVxZGpKNThtUVVGUlZoMkxCaFNFMU54YlJwMHhBUWNNbjhlZVRJRWFTa3BGeFYzQlBQODhqUHo4ZFRUejNsTVY0eCtWNnIxVXJxZVhCd3NGVEpwTEV4RkJRVW9HM2J0cEo2bnBhV0p0WGpGK1Y2cFFSZHZIaFJVcjJVU2lYYXRHbUR3c0pDcEtlblkrZk9uWEE0SEpnNmRTb09IandJcFZMcE1VZU5KWG43V2c4UkVSR1lPblVxVnExYWhjbVRKNk9tcGdiOSsvZS9xclhsM2g0WU4yNGM5dTdkKzhHSkV5ZnF2YlVyZDg0b0wrYVg4anhQV1pZVisvL0Jack5KYW1oNWVUbGlZMk5SWFYwTmxVb0ZoVUtCMDZkUFMrV2s1T3NvUHo4ZkZvdEZVcmNGUWZCWUUzTHpSV3BxcWtlMUYxOVNWbGJtMFNOVnBWTGRHR0RMejgvM0NXbytLblEwQ09rUWJXb3RXclQ0eDRDYVhJeEdJMWlXUlhCd01MWnMyUUtqMFlndnYzUlZKWDcwMFVlbC80dU9qa1pGUllXVUVMNW56eDUwNzk3ZDVTRmlHR3pldkJuejVzM0RlKys5SitVcnJsNjlHb21KaVZpeVpBa0FlS2cvVVZGUnFLeXM5TENmaVorM2JkdldaNFhadkx3OFBQend3M2oyMldkeDdOZ3hQUGpnZzlpMmJSdWlvcUl1ZTlNU1FqeXFzd0tBUXFGQWVIZzRQdnp3UTZrc2tpaWkrdTFMR2h0RFRFeU14dzFTVmxibUFmTFhVOFRmRXB2dWlML1Z2SGx6VkZaV0lqQXdFSU1HRGNMaXhZczlHcVpZTEJZY1AzNWNlaWhkYVQwQXJpVDhFU05HZ0dWWlRKczJ6Y091Nm10ZXZVV3RWai9RcGswYllkU29VZSsvK09LTGFBemNSQURpT0U0S0JSRWRBWVFRWEx4NEVkMjZkVU50YlMwVUNvWGtRRGgrL0RoNjkrNHRIVTljUTlIUjBkaTBhWk5QTzdCOHpPWGw1Y2pNekJUSEtqM0E1Q2FPcUtpb0J1cjhIdzVzSlNVbFV0TVZiNmJtYlZQekZkTHh6anZ2cU55T2dqbi9KRUI3NzczM01HWEtGS3hhdFFvalJveUFTcVZDYkd5c1ZBK3J1TGdZdTNidGtrcnNUSnMyVGZLazVlYm00cXV2dnNLR0RSdmdkRHFoVnF1UmtKQ0F0OTU2QzdmZmZqdmF0V3VIL3YzN0l5WW1CbHUzYnNXT0hUdHc0c1FKbkRselJyS3hUWm8wQ1FzWExzU0REejdva1lQWHQyOWZ2UExLSzNqMTFWZlJ2MzkvN05peEEwbEpTWmc4ZVRLT0hEbUNiZHUyWWM2Y09haXBxWUZhclphZXZtTGxpT1hMbHpjNDEvRHdjQ2dVQ256MTFWZG8wcVFKMnJWcmg1aVlHRHp3d0FPWU0yY081czJiaDdxNk9telpzZ1gvL2U5L0x6dHZqWTJoYTlldUlJVGc3YmZmUmtaR0JwWXRXOWFnU3NXMWVwczNiTmdBdTkwT2p1UHczWGZmSVRRMEZJTUdEY0w5OTkrUEpVdVc0TWtubjhUZXZYc1JGaGFHdExRMEVFSVFHQmlJdG0zYklqazVHVVZGUlI3OUFEWnYzb3podzRmN3ZObDlyUWZBMVUxcThPREIyTEJoQS9idDh5eUFtcFNVaE9MaVl2end3dzhJQ0FpUUFNSmJGQXJGUXdzWExuVHUzTG56L2YzNzl6ZXdpN3JmODRJZ1NIRnVsRkxpY0Rnb0lRVHlwdU42dlY0Q3R0VFVWTHo3N3J1SWpJeUV6V1pEY1hFeEFLQjE2OVpvM3J3NUZpMWFoUEhqeCtQWXNXT3dXQ3lZTzNldU5BOWR1M2FGeVdUQy92MzdzWGp4WWdCQTE2NWQ4Zm5ubjhQaGNPQ0hIMzZReHBlWm1Zbi8rNy8vdzN2dnZZZFdyVnBoelpvMVVDeGR1dlFQdXptcnFxcHd6dDJqUU43TTJHQXdlQlNJOUU2VGtzZXByVisvZmduRE1BditTYUQyeFJkZllOU29VVGg0OENCYXRHaUJ1WFBuUXFWU0lURXhFWW1KaWRpM2J4L2k0K014WWNJRVJFVkZJVEV4RWExYXRVSlNVcEtyRnBWU2lSZGVlRUh5TENvVUNuVHYzaDFCUVVIbzBhTUh6cHc1Zy9UMGRMUnIxdzRxbFFxSERoMUNXbG9haGc0ZGlvU0VCTVRFeENBakl3TldxeFZIamh4QnQyN2RjT2pRSVV5ZlBoMEtoUUszM1hZYmNuTnpjZlRvVWJScDB3WVRKa3dBd3pCSVQwOUhVRkFRZHU3Y0NZdkZnc1dMRjB1OUt0ZXRXNGNtVFpwSUhqcTVNQXlEWHIxNllkKytmYWlvcUVCYVdocENRME9saXJLaStqWnIxaXlKcVFRR0JuclU2WXFMaTBPTEZpMGFIUU1oQkNOSGprUjJkamFPSHorT2lSTW5ObWd5MHJKbFM4VEd4dnE4SmxGUlVRMDhkOXUzYjRmRllrRzNidDFnTUJqZ2REcVJucDZPOVBSMGhJV0ZZZnYyN1lpS2lzS3p6ejRybGZTSmlJaEF0MjdkRUIwZGplam9hR1JtWmtxZTZPZWVldzZQUC81NGd5ait4dGFEbkR3MGI5NGNBd1lNOE5oUHE5V2lZOGVPMkxObkQzUTZIVHAxNm9TQWdBQW9GQXAwNjlaTityK3dzREIwN05neDQ4NDc3N1MvOU5KTHAyV01UMFE0S3FxY2NpYW5WQ3FKNk5RS0NRbnhxS1JDS1VWa1pDU2FOV3VHRXlkT0lDa3BDVmxaV1VoTFM0TldxMFZXVmhhcXE2dHg4T0JCeE1iRzR0NTc3NVhVN2Q2OWV5TXZMdzlsWldWNDl0bG5KZTkxdDI3ZFVGcGFpcHljSEV5YU5BbXhzYkhvMnJXclZPcnE4T0hEcUtxcVFueDgvQitYSzZyVDZaQ2RuZTNCMXJ5ekNyekRPdVJ4YW1Md3JUdE96UzkrOGNzTkVMUFovTytRa0pEL0FiQzVLNE00QUxDUVZRWVJjMHVWU2lWMU0ySXE1cFdLU2ZNUkVSRlNYbWx5Y2pKU1VsS1FscFoydzVMbS94QlYxR2F6U1IzYUt5b3FVRnRiSzdYSHU1ejZ5Zk04NUdsU0RNTXM5aTgxdi9qbHhrbHdjUEJjblU1bmpZaUlXT2RtWmhUdVdEYXY5Q3ZxSW5YRWxhTkZDR1VZQmd6RGdCQUM4VytsVWdtVlNnVjNjajA2ZHV6NHUydmsvU25BUmluRitmUG5VVmhZNktGK3lxdDBpQjVRZVpGSXQ2dVdFUlBhM2JtZi9vd0N2L2pseGtwQWVIajRZMVZWVlphNHVMaXQxSlZjNm0xdmc3dUhndUIwT2tYUHFBaHlJSVJJZGpaeEV3Rk9vOUVnUFQzZFp4T1hteHJZNUtBbU5qSVdIUVVXaStXeWxXOEZRU0EybTIyWXUwcUhQL2ZUTDM3NWN5UW1KaVptWmtGQmdiRkZpeFlIdkpnYkJVQUZRUkM5bzVBQkdoR0xWNHFiSE5SRVlBc0lDSkNhS3Y4bGdLMndzQkFYTDE1RVdWbVpUMGVCM1c1dmtGVWdDK3RnVENaVE4zYzlOWC9wSWIvNDVVOFVRa2hLY25MeVE4ZVBIOWQzN3R3NXg4M2NSSldVaW4wUlJOWUdtWU5CVkVXOUdadFNxWVJhcllaYXJaYjZtdjVSY3QyOG9sVlZWWkpkcmJ5ODNLZGRyVEVQcUZqT096UTA5Q1ZDeUVEL3N2S0xYMjRLY0dzYUV4TVQycWxUcDlOZmYvMjF6UTFla3FjVVBtcTJ1VlZTOFcvNTU1Qi9Mb2EreUh0Y1hGZkdkajFhaDVuTlpwdzVjMFppYTJJT3FIZWJQSWZENGVFQkZkWFB1KzY2U3dIZ21mcjZlbjg1YjcvNDVlYVM0Wm1abVRVcEtTbXZYYmh3UVpBeE4xZjZnZnNQOXl2Y1dRbkVuVUlIK2VaME9xWE5YVFVFN2RxMSswUEFUZmw3YXo1UlNwR1hsNGZpNG1KVVZGUTBzS3MxVnRMYjdTd2dBSmhseTVZdGNEcWRONlR4aXRoVDBidnF4SlhFWXJFZ01GQUxRaGovVXZmTFA0MjUzYjExNjlicWxKU1V6OTA0SnNqQmpSQVJ4d1RLOHp6aE9JNjZhUnNWdmFSeUZkWGJZOXErZmZ2cjdreFFCZ1VGL3E0RDVPWGxvYmEyQm5xOURtYXpDVmFyQlhhN3BITEtLblE0aVJpZExQZUExdGJXVEhVN0MyNkk4THdUVGllUGF6MXZuVTZIeU1pSWErbzVhVEFZRVJJUzdET1MzQzkrK1N0SlVGRGduUHo4QzVXMzNKS3lGWUEzYzVQK1R4QUVnZWQ1T0oxT0tCUU00VGlPS3BWS2lhSFo3VFpZclJhWXpTYm85VHJVMXRhZ29xTDh1cmJ1QXdEbHRkeW8zbEpXVm9heXNuSlVWOWVndmw0SGc4RUlzN2xCandJM3VFbWdSdHhVbGRUVzF2UU5DQWg0Qmpld0xwenJhU0hndDV5M2FBQzlXakdaVEFnTEM4WHZtV08vK09VbUVXVjhmUHpEeDQ4ZnEremN1Y3N4WDh6Ti9Vb0VRUURQODVUam5HQVl6aXZrUXdtVlNnMjFXZ09OSmdCYWJTQ0Nnb0lSR2hyYUlDZjRkdzIyc1dxWFY1TFM4akljUDNzQk9uZEdnY0Z3S2FuZGJMYkFiTFdCdlFSc0lsc2p2Q0FRS2xDbXFLZ3dJU2dvYUQ2QWxCdExxeGtwenVaYVZXNTVjdThmdGM4ZklmcGplNkJvbG9HUXFCc1RGa2c1TzZ3MTlWQkdSRU9qVlFOd29tTHJKdGhzZ0twcEd5Umx0UGxiM09tL1pWNXRoVG13T0tNUm5STFh5SC9ZVWJYak1DSnY3UWVWbStnN2lrNmo3RmcrQUlMWVFhTVJITXBjeDdGRUlEb2w0V3AzdWFWMTY5YjNyMXk1c256YXRHbmxBTVJBWGZHVmlwNVNudWZoRHRxVlFrQXVlVWN0N3RBUHRic1RsaGJCd1VFSUN3dTdwcVl3anRKY0dJeGF4TFpMYW5CT3ltdFdrNmdWaXo3WWdQMUY1U0IxWlNpdnFVZlh1RkFvTFNaWUxHYVVsSldoc040TXd0a2dDRUNJRXNUcGRNTEpjY1RCT1FuZ3NxdTlrMnQrK3JuNCtDSGtUMWlRbE9LYTFVTXhQKzVhOXZzdCsvd1JVdkxaY2dSTi9neGh2UVAvOE4rcTIvWVpEci95SmJUTldzQlJWb0JtODk5Rm0vNUpVQVVFd25KdU44NGRxRUt6ekhaL0MyRDdMZk5xT0xBR2hmcE14S2FPYkl6bkkvKzl6eERZOTFabzNFU2ZVV3FnMWdiZzR0c3ZRSjErRzBMREc2Nm55dTgvUnNEdGFRaUw4UnhMd2Z0TG9lejFHSkk2UkRiWXgzemlSNVFZT2lNMk5mRmFOSjRCNDhhTks1dzJiZHBiTXRZbXlGZ2I0SXB6ZzFzbHBRekRFSVdDb1E2SEFrcWxheE16RWRUcVMyMytnb09EMGJGang2c21BcGJqbTNIMlZCTGkwNW8xT0tkckJ6YW8wVCt0RFhvMkRVRmhvUnE3OXBaaTd6a2R1a2NvWU5iWDRXS2RGWW5oZ2VEdFFFVjFIVEVybFZBSVRuQ3VDR1ZDQU1aU20vL1F5RGYyek16SlRFWEhHNXhiSURkaS9qWTFsdm5EOXdFRVVEQnc2cXZBMFNBRVJuaDZqU2dFRURDd1Y1VkQwSVFqTVB6U1lyWlhsWUpYaGlJb0t2VFNEcXdkb0FUT3Vtb0ltakFFaEhxbXREaU5kYkFaSFFpTWk0ZEM5ZHRCbUMvZGovMkxWNkxIMm8xbzBpd1VFR3l3MW5OZ0dBM2lCNDlBTUZPTy9BS0xqL2tRNEtpcEJzY3BFSlFRQTgrSEhROWJlUVVFVlRBQ1k4STl2cU1PSzh4VnRkREV4RUd0MWR4azgrcjduQWpQZ1RvRlVLc0JOZ3NRSEJmaE9UNW5EUHArK3htSWJJNjB6ZHFoZWJOMnFQeG9DWUNHYTVjNm5Xai8zQ29RWmNQUHphY09RTmxxT29nUURpaVYwamlvNEVUODdmTVFML3ZzMGpBNFdNc3J3RVRHSVNCUTAyQ3Q4RWJIZlZVVlZZVnhUZUxXQStCOXFhU0NJQWlFRU9wME9zRXdCQTRiSllRdzFLWEJLS0ZVdXRPc1ZHb0VhTlJRT096UUtMVUlEUTExMjlzRTJDckxRUU1pUGE0REFIQzZLbkFrQkVUZ1FKMjhOQi95YzFKZXF6ZWlvcUllYWs2UGtxcEsxTlhWZ3poWjJNd21XTlFxMU9vTUlBb0NjQzY3bW9KUWNDd0xFRXFvcTZZNnNWaE0vWlRPdXZrY28wU3drb0NRR3cxc2x3RHV0ekMyYTludnQrd0RBT2VmbVlpTHVTb3dTaVdFeW9zSXZuMGgrczRkN2ZyU2VBcnIreitEcEdFeHFEeWxBOHc2ZFBqa0p6UkxNdUxRZlZOUlo0MkV5bElHa2pZUnQ3NDZHeW9Hb0U0T2hXL05SN0ZLQVh2QkdjVFBYb0h1ZDdrS0VoYis1MUdjM0ZTQ2lPWVJNRjZvUThaM0d4RXJQZHdkT1Bua1RLZ0d6VUs3ckM1WFhocy9ySWVxK3dRa05IZXJFNHBBQk1ueVJ3Z1ZRSG5CYXo2cXNYUHdlTEF4TGFHQ0FTWmRLUHF1K2hDUk1ScFFVekgyVExrSDl0Q1dVQXM2MkJNR0l1dFZWMGsrL2U3UHNXZlJKd2h0bXdKNzJVVWtQL2tKMmcxc2ZwUE1hK1BuQklHSDhlZVYySDdnS3pDV01xRDFuUmo0eGl3b0FWU3UreTlPZmJrRCttTjFHSHhtTHlLOXNacmpBSGl1Si9ic3o5ajkvRWV3blA0VnJkL2VpN2EzTm5HcFpNYzJZZC9MWDhDU1V3SlM4RGpLUDlFaStyWkgwR1Z5UHdCbE9IRDNrOUFWbmtmSTdZdlIvK2xMcGRydHVYdXdlOVlpTUUxYWdTM01ROXhETDZIYlhYMTlyWldIOXh6SUx1elhLKzJZU3hXbEFrQUVVUzExdTBrSno3SncydTFVU1Fqc09oMzQ2SGczYTFPQzZNdFE5UXVMV3BVZFFlcGdYRlQraUlCM1hrQ2d2UXg1enp3SE5yUVpVSDhSUVVQbm92Y1Q0MEFnNE9KcnMzRnlZeUhDWWpYZ1dBZEltMlQzZkhpZTB6VUJtOVZxbGNJNmFtdnJvSyt2eHZIQ1NzUUZoN3JhYzluc0lMd0FvOUVHbzhOSkZGUUFGU2loREFoRHdJeGJzaUw4a1RVN242cXYweWRNR3o4WUtlby9ReEg5YmNEMmUvYTdaaERsblhCRzlzS29qNTRDTWVYaGg4enhxSmd5QWdueFNoZXZxRGtGUmVabWpGN2VFUUFMSjZ0RTFlbzNVYVhxaDlIZkw0YkNhY0N1SVpuSTJ6TU83UWNrQXJ3VHlyUXNESDV1Q3B6bGU3Rit3QkswR3JjTEVZSFZPUGZoWG1Uc09vV0VPQUJPRmp3amY5Z0lNSnc2QWsxNzNWV2RnK1hDQlFRbjkyejhmNmtBd2VrTmJCSG9zMzQ3MUNFdTZwNzN3aFNjK1dvWCtqNDJIT1lEUDZBK2JEREdmZUVxaE1tenJMUnY0ZWNmb05uVG42SFRHTmZUbldlRks0N3h4czFyNCtjRWdZY1Z5Umo3N1p0UUN3YnNITlFEK1lmdVFKdWVzV2d5Zmc2YWpCK0xIOXBNZEFPWTEvaWRIRUE5Z1UzVGJqQ0dmalVZUis3cUJ0QkxheTJ3NjJnTS9XbzBqdC9mRzZvNzMwTGEwR1Raa1JMUjU4dXZVUEwrWTdoUVQyWEhzK0xJWTNPUXNPQnJkQnpjR29McFBEYjNtNEg2TVhzUkZkSmdyVFJocVdMYXNtWC9MbGl3NEJtQlVnaUVRSURNWTBvcEJRVUV5aWlnMEFaUUZaekVYRnREdGFHQlVDanNJQ3dMYzEwMTRvZVBSR3lMSm9pTGprWnRSUVYyL3VkVkpJMThHY05uRFFHY091d1lPQUNsNDBhaXFmb1FqbjV5QVVNUGIwVjRrSURqOXc5R2xmU2c5RHluYTNMWEZSUVVvS3pNblFOYVg0dURKODdBb1FwQ3NzSUpzOWtPam1VaGNBS2MxRW5BUzRudEVBUVFRYURFa2R6cHlibjkydzJ0THptUFY3Y2Z3YWpVRVloVHdTOCtKTFpQTDFlbm5aQldpR2tEMUo4cVIwSzhlM0ZxVW5ITDZJNlNhVUNwQm5RblR5S3U3NU5RQUlBeURFMTZ0MGYxNmRQQUFKZjlKSzZucTRxcE1xRTdvcUl1b2o3ZmpvZ09rWWp2R1lrakQweEQ4cEIraUI4NERQSHQ1ZmFXUVBULzZkUWZmS1lNcWplK2c5enZkc05tdG9PdExrS0l1NXkwTnEwcm1GTVBZTmNjQitKNmRFWHl5SkVJVXJ2bnAyY3ZIUGpYTERqUGprQlVqMzVJSHREbEpwcFhaYVBuQkFCUm1abFFNd0NZTU1SM2I0WDY3R3lnNTAyUWNHUEtSWFcyRFlxOTMrRFlRWmRxVHAybDBCV1lFZFhSNTFvWk9IdjI3TE1MRmp6enNWc2w1YjNzYmRTTjBKVG5lVEFxQmdMbGljM09VcVZDQWNKeWNBWkdRQ0FzakVZak5Cb05BbXNLVUgrc0NIejBlaHlyL3dVVUFPdDBvRDZuQkJHcU0yRFNNaEFleEFCZ0VOKzNKNnJPTkxhcXJ0WlFXbEtDMHRKU1ZGZFhvYTZ1RmtkT25FS0pRNEhXb1VyWTdhNXFIVlFRSUhBY0dNSkF6UUM4UUFsQUNhV1VtZmI4ZjhaTkhkcm42WjdONHpHeWJ4KzA1d3F4cmREaVI3Qkc2Y1ZsdnRNRzRmZEZrQkQzbFZlaTgwZmIwSHZ4ZEtpNU11d2ZQeFI1eDJwKzgxR0RXamFIc2FENG12YXg3RitKZ3grZlJKZDMvb2VSVzdhZzYvMUQzZG9Nb0V6c2dkSDdma0xLa0U0dzdWcUZIMjUvREt4N3Y2WVB2SXdSWDY1QWVId0F6aSs1QjRmZTMzSFR6T3ZsenNublhvemlKbGwwQXFDS1JQeklMQ1JtWlNFcGF5UjZmTG9XVFc4SmJIU3RCQVlHemlvcEtlNExWeVVlRGFWVURWZjRsb0pTcXFBQVF5bDE5eXJsd1FQZ09CWXM2NENENDhCQmdOVnFoY2xraHNGZ1FGMmREanFPQWR1cUxVaVhMa2pLeWtLWE4vK0hsRjdYRmdweVZjQm1NcGs4ZW9BZVAzVUtaNHdVSGFJMFlPMDIyTzBPc0t3RENnQ1V0Uk9ISzJhTlVFRWdCR0NxcWtxVFFnUFZUeFRWR2x6VFp6ZWd4RUlRSE9DUDcycE1xdmNlY0JrclRIbW9PUWRFZHJ5OFN6NGl2UU9xOXU0RkR3Qk9BeXIyNXlCU1ZtRzI2dUJlbC9HMy9GZlU2Vm9ocWtVQUFCYXNSVUJVUmorMG0vY3NVdnJGd3BndjcrTE5JdWYvNXVEODlxdGpiUW1qeDRIZHR4cGxCUWIzZldLRnBkcHd5ZTBVR1FtMnJBUk91WG1qckF3QnJkSVJIaE1FQ0JhVS9iVDNrcUhhWkFMQzRwQTRhZ0s2djdZVXlwSnpzTHAzWmswbUJOM1NDU24zUG94T0Q5NEcvYm04bTJaZUwzZE9BRkMzYng5WXdYVzh5bC96RU5raDdlcmNkcEhoc0Z5aHNZbTNLRFVhc0hYMVYvZlBJVzBRMjlvR3F5a2NzZDI2SXJaYlY0UzNhQVpWRUhQWnRkSzBhZE43di9sbVRSTVIzQUJJNEFhQW9RTFB1T3h0VHNJVFFIQnloR1U1T0RnbldONEptODBWdE9zQ053NjBhU0RLQ3F0Z2l3aUh0blVxb2xxM2hEb2tFRUdkT29CbUg0YmVJZ0J3b25MdndjYlArMnJPMTVYWTdpNFlXVk9LQTNtVnNMRjI3Q20zUVdEdGdBQWtCQ25ocENBcXh0VzlCdFJWaDQ0Qm1LaW91SG1QanV6ZDdZRnZOK0xva1Jqb2EydVIwTEVuUmlScC9BaldpQ2hxRCtMSGNYZUFyNzZJMEFkZWNOdUJHcGY0T3g5SDNBOTM0b2NSSjZDeWxZR2tQWVJXL1MrcGxkekpMZGc2Y1N2c0JXZlFmTWxiY0RtYXFyRnIyRmp3Q2FsUW94NUc4eTBZT0ZRZWlzR2hjdE0zVU1jT1IrdkJIYTk4RXlYM1I3OS9UOEgrMi9zam9FVUxPRXFMa2ZMU2FuUVk2bkltYUxxTVJLdVdYMkY5dDk3UWRCbUIwUjhzUXRTZ1NWQy9kUmUyVER3S3hsYVBnT2hMRGdEendmL2g1OFZmSXFKTkM5Z0t6aUI2NWdLRXVhZmg3SUk3VUhSUmk1QVlOWFM1MWVqNndlTTN6YndLbHprbkFOQXF5dkhUN2VQQVdNckFkSG9NdDJSRUFkRGg0RDMzUUZmdmdORlFnUVBqUmtPcGJZRSthOStFMk1LNjlkejUyUG5ZT09RdkRVYjdsNzlFcTU0SnlGdnhLUEwyRk1HYVd3MVNjaThLWDlNaWJmbi9rTnpldFZmeVBROWoxeE9Qb095VEVNVGQrVFF5cGc5RzdkWjNjUGp0TFdBckw4TEIvWXJOKzFlaXlYMEwwWGw4VDNSNzgzWHNmbkFxU3Q1TGdaS3JoZG1TZ0NFL2ZZemd5NndWUWtqblVhTkczUUhndnpLVmxIZlRWSUZTU2dXbmt3b0FWU3JWY0RxZDREaU9FSjZubkVCaHQ5dmR3YnV1eWg5UlBRZWdhdnRLYkR6K0UzSWlBeEJsRFVQZkRkOGpKcjR2dWo3UUVqOFBHNDdRV0MxNHFuRTFEQVFhbkJPaEdtblFnUUFBSUFCSlJFRlVWTGlpQ2lvbXVKZVdpdVdJNnFEWEczeDFtWEludUhPRTV3VkNLVlhZN2JhSktwWHFmeTdyTDRkeWd3VWFiUkNpdEgrT2NhMjZ1aG9zeXlFeDhkcW9iWFoyRGxKVFcwbTE2LytvZlFEZzdGT2pZZW02RkIySEpZSVRnaEVVRlhUVis3ckNFa0lRRk5VdzBORlJVd2toSUJUYWtFQVA5Y05XV1FrblZBaU9qOEgxY09kUTFncHplVFZVTWZFSUNMcUtlQjZCaGFtNEV1cllCR2dDbFY1ZldXR3BySVVxSWhZQklaN0hZZzAxY0JnY0NFeEloRUo1azgzclpjNEpBSGhMSFd4bXhpdmM0MllSQWRieVVsQk5CSUtpUXE1NnJaU1ZsYzFOVEV6YUJjQUNWMmx4TzZXVUpZUnc3ckxpdkhkSmNhMVdpOERBUUFRRkJTRWtKQVRoNFdHSWpJeENiR3dzNGtJRDBheFpLM1R1bllua3BFdEJ1R3hkRlp4TU1BSWpnbjRiWTdOWUxDZ3RMVVZWbGJ5Mm1na1dpOVdkRDNvcGJjcnBsSEpCaVNDNGduQkxTMHZpVlNyVjNFdVBTeFVTSXNQaGw2c1RkVVFjMU5lNFQwQmM0OEdXbXBoNG45WUliWHpDZFIwM1VRY2k1RnBxYlRGcWhEUlBidVNyUUlRaysvNU9IUllEZGRoTk9xK1hPU2NBVUFSRklUam9abDE1REFJVGtxOTVyU1FrSkV6NzlOTlBjdTY3NzM0ZUFBOVFpYlhoVW9GS01aZVVLaFFLd3JJc2RXVWtLS0JTS1dHeHFLVE9Zb0dCV2hoWU84cEtTeEVWR1ltZ0lOZUVxYU91ZlAwdWEyTzcxSVRGMVRKUEJEVlhhVzhISEE0V0hPZVJNaVY2UWdtbGxNVEZ4VDBPb0tzZm9xNU5FbWY4SDFKdVRmRlBoSDllLzFKQ0NPbDB4eDEzVEhEYjJUU1VRdTVJWUVSY2tHVWxnT000d25Fc0hBNFdkcnNETnBzTkZvdlZuU1N2UjExZExTb3JLNlhXZlZkdFcyenNpOHJLU3EveTNpYVl6UmJZYkZZUHBzYXluRlJqU1JCNEtjSGRhclVNWlJobXJ2OXlYN3VFdE83bW53VC92UDRsSlRBdzhNSGMzUE5IVTFOYkh3YmdkREczUzdZMlNJbnlVcm9WV0phRFFzRktTZkkybXdwbXM5cWRKSzlEVUZBUVFrTkRFUkVSZ2ZqNCtLdmtuRDZFNHpndkZkUlZXODFtczhKbXM3dTlvS3hVMmx1dWdsSkt5YjMzVGxOb05KckgvWmZaTDM3NTUwbkxsaTJucEtTa3lEMmtLamVKWXVBTy94QUVNUVRFQ2FmVFNUaU9BOHVLck0wT204MEtzOWtNZzhHSSt2cDZWRlZWbzdTMEZGZGJQOUluc0xsQ082cmNLcWhCcXJObXM4bVpHdXZSQWRwZEpaTlFTcGtQUHZoZ05vQkIva3Q4N1ZKZFhZM1MwakwvUlBqbEx5c0toYUwvNGNPL2pCYUJqVktxRXNHTlVxcWdsREx1NnRseWxWVENGWWZEQlc2WDZyWVozQ3BwRlVwS1NuNGJzQmtNQm5mUGdocm9kSHFZVEs2MmVXTExQSG1OTmFlVEkvSXVVd0JJZlgxZHFrS2hlTXgvZWYzaWwzK3VSRVJFVE51eVpYT1NGN2hKc1cwQWlDQUlNdGJHU2F6TlZaRFM3cmEzV1dBeUdhSFQ2VkZiVzRQeThuSVlESVpyQnpaNWh5bFhlVzhMckZaUFVET2F6S2cxbUtBM1dXRmpPYm5EZ0JFNDY2eFhOdXhNWExCdUwvYVdtUzRkMkduSE43c1A0Y212dHVIZjIwK2d5a0g5Vi85Nmk5T0MydFBuWGNHa3YxTXFWcitBZys5dnUrNUQ1Q3QveFk3Wnl6M0dhRGkySGNlV3pNZSsyWE54NXJ1OUVIN1hMd2pRbjlpUDh4KytneE52cm9URDY3dXFyU3R4Y1Baak9QTEtoN0JhWmIva3JNTysrMmZDNUUrR3VWN1M5TlpiYjczZERXd2VRYnNpYS9OMEpIZ3lOeEhjckZZYnpHWUxqRWFYU2xwZFhZMnlxd2hTOWdDMnFxb3FWRlpXdWhMYzNYRnFMaFhVSnJPcjJXR3cyTUVRVnlFUHdja1JweXRtalZnTkZRTm5mN1Q1WVM0cUVTTmIvajk3NXgwZlJaMy8vK2ZNbHV4bTAzczJnU1FraEJZZ0dDRFNJUUtDb0tBQ0l2WUduT1c4czVldkRVODV6M0xxejNZcWxrTlJFYkZncEJjRlJLUUhDTTJRaFBSZU45dG5mbi9zSnRrVVVoQ1UwM2s5ekFPem01bjV6T2Z6bWZlODYrdHQ0S0gzdnVGQXZXc0xmNVQrSFV0emJNd2RNWWpnMmp4dVh2SHpyOXpBQ3RyQWRKUXQxejdhVkhiMHEyUmtYUm1XeXRxelBzVGpMejFMOE1VejhTd2l5dmxpSlg3REp4RTNZeFRaajk5TTVvYmNYM0dGS2pKZmVJWHF6RDFrdnZweGk3bW8zdncyUC96amE2TG5Yb2UyYUNPYjdudTF1Y0pLSFV6TUVDMEgzdCtnN0tPekJKMU9kME5tNXVIQkhscmI2YUtrTGJTMmx2NjJ4cW9FbDBsYVhsNUJjWEV4SlNVbFhSZHNoWVd1Wml6VjFWVXRURkNyMVlMTjFpallKUHk5dFlKV0ZCQUZCTkZWeGk4QVl1SEo3TnVQK01WeS82amVqRW0rZ0xrSkFpdjJGd0JXTmh3dTRkWkpJeGthRThIOFMwZGdQM2FVZzJabDhjOFdpcjVld3Y2WGx1S29POG5oeFl2WnYvaDVxaXFrUmlsRjFwdlBzLzJPTzluNzhoSWFURjE3cGNoMWhSeGEvQWpiNzMyRWdveDhUNmxIOWpzdnNmMk9POW45MHBLV21rK0hndmNJeDlhYWlKdmNwOFhIZzU5NWc0UkxKeEkxY1JhUktTR1lDb3QveFV3RU0vS2o1YVRlZncydEt6Q3pQMXhLN01LSDZURnFHSU1XUFlMOXUvOVM1bUhWUk0yK25PTC92b2RGZWVPZU5mVHUzZnRLV2dZUk5CN21hS1BXMWxSTDZobElzTm1zV0swdFRkTHE2aXJLeXNvb0xDenFtbURMeTNNVnVMZU9ncnBNVUZkYWgwZVQ0K2FBZ1N1Q0sxb3M1cm5IeTZ1bkowU0U0R3lvNTFTZGxjU0lFSTZYVmdGT0hKS01TdVcrbktoQ0pkZHpzdEt1clB4WmdrLy9ZWVNOR1lLb0NTSTRMWTJ3dExIbzlhNzVQdkgwQW83dXFTSCt0bHZSNUc5a3c5MHZka2xiTGxyeEdlb2gwNGlkRU1kUDE4eWp2TUlCT01pNGV3Ni9aRHBKV0RBZkgvTWVOdjcxMzEwNlg5M1BQMkJQSEk3ZmFiSXJpNWN0SWp1M0YvMHVIM1lPWnNoQzlkRTgvUHNuVW4veUdEYXZSQUlpeXFuSmFuYVhpQkVwQklsN0tjNnlLUnZxTEVHdFZsK2NuNTgzbXFiY3RtWmZXMk5ESnplZGVJdEFna3U0MmQzNWJXMmpwS1dsSmVUbDVYY3MyR3cyRzhYRnJsclE2dXBxdHduYTRCRUZQVTE2aDlNcHlMSXMzdkhRWXlxTlJ2TVhzODJHdDFiRnAydldjczJLbjlGcjFOVGFIWUEzSStLRFdQRmpCbVgxRGF6ZHVvOWNXY0JzZFNncmY1YmcyM3NReG1GSmlMb0FJbEpUTWFhbW92TUdLQ043NVM2U0huMmNpT1JrQmk1NkZPZTY1VlRVZDM1Ty82bFgwM2ZLS0tLbTMwYjhNQ2M1R3pLUVMzZHliS09Ub1l2dUlpZ3hrZmk3NzBYY3NZS3FoczdQVjN2eUpJYm9tSGEvYTlpL2dtMnY3U050K1JMOGZNOEZsYm9KbXhsVW1tSzJUQnhMWm5vdWFqMDQ2anpOQmg5OFluVFVaUlVyRytvc0lqSXljblpLU29yT3JiVzFDU1EwbXFYdHBYKzR0TGJHS0dtRDJ5U3Rwcnk4bk9MaUlteTI5bDlDYXBjSld0aWlINmlyL3JPeFpLcFpXMnRNeEhVNm5hNDJkcElrQ0lJZ3ZQTDBZL09CVkwxV1M0UEp5ZFhUTCtFU3A1cTlPemZqcDNIVmhONDJZd3JXdFR1NWZXa1dBL3YwWlh6UUNid1Zkbzl6RDBjMURkWCtlSWU1MVNUdlVQUysxVmdxZ1U3NjFCbzhraUgxeGtqS0s4dHhsTlppdFJTeTY5WWJtOVI5cjZRa3NBS2RVUC9MT09BME5Pa1ZPN1lSY1BGc0FrUFBGVmU4QWEwZW5QWUlKdjYwRDAxSUVEOHNCcld2dnUyZlNrNWwzNXhGaUtJNGJNMmExWk5EUThPK0JlenVId2R1V25GY3lidUN1eTlwazV6eDFOcTBXbGNnUWFkekJSS3FxaHBOMGtKaTJ5bmZVNXZONWlZNklsY3RxR2VUNDViNWFnNkhRM0FMTmNIdWNBbTFqQVA3L1ZRcTFYeUFQcUdCL0hLMEhORnJNS0hBOGVKeUVudTRPaEpwdlFPNDUvS0wzUzZhUWlaczgrWCtVSVZsOHV6dUlBRkJjcmFrSEZNSDRCMVFRME9wRGVLMFlDckRYQmVBTHFnTE9vNkhyOHRjV0lSK3NCRjFtQUV2UXp5alB2NFVRemZmUzM2UmNaZzN0SitIRkRUeFdwSnNvZTBia1NYNTRCMkl6dmZYRkZmcUNPamZnNXJNNCtndUdBYW1ES3BMd3hnUTc5dkNYRzBvTUJFU0Y5bnVHSnhhZnd5QnZzbytPeFBQWjNEdzdFY2VlZmo3WjU5ZGJBUHNzaXc3M0F3Z1RyZlcxcUoxbjlzeWxKdWpwRFozbExUUkpLMmhvc0lWU0FnUEQwZXZiL21DRXB2b2lLb2EyK2MxZURROHRqYldndUp3TlByV0hOaHNEZ0FCV1JiMjFEaHVYYmIvVkgrQTJMNTk2VmVidy9QYlQ3QmwvMTQrelpLWmxld3FIaTQ2bGN0SGUwNnc2ZUF4L3I1MEU0TXVIRXFjSXRmT3NsSVNnNi82QkpudmZVSGV1ZzAwbUFCQ2liMWlHSWVlK1FmRkdmczUrTVJpVkpQbkVPelQrZWxxMW43TTBUWGJLZmoySGJMMmFPZzVzVDlDV0NwOXh0cjQ4ZjduS2Q2ZlFkSG1OZXgrNFowdXBaajRqUmlKa0xrSFV6c2VpTXBOS3ptK2VuZTd4KzI1ZGd6YlgxN1Y1V2tvK3ZZampueStBYWU5bkt3UFBpUjcreUVBZWwxM1BUbHZQRVB1OXpzNThQaXphS1pjUzZoSEViMWNjWUJ5MHlBaStyVFZHZy9jTVlYdm4xMnU3TEV6aENBSWZlKzc3NzVKdEsxRzhFejlhSkcwNjNBMCt2V2JUVktMeFZWTFdsZFg1ODV0SzI4M2tLQ2FPL2Nxajl5MUttcHJhOXBwZW16RlpuT0ZZcDBPUnhON3h5UHZmUmFrOXZaNTNTcHFnNFpIQjRIS2k3UitVUnc0bGtWR3BaTTdMeDNITUhlZlE2Zk53cGJNYlBhWDFESXdhVEFQaklubjkrQU5OWmxNT0owU2ZuNSszVHF1dExTTTRPRGdidlVJUFpOanpuUjhydDNqalhIU2haaU9IcVFodndqRHdBdlJld3NFajVtTXF1d3dlYXUzSU1hT1pQaWpmMEhiaFg0VHdlTW1ZenV3bHBMTVdnWSsvVS9DWS93QWtmQXBNMUNWSGlRdmZRTzFoYlVFanh4TFVGeGs1NVJIK2tqc0dVdXA4UjVEV0srV0xDK1N0UUYxV0N4QmNhMXFBYVZpRHYvemJZeDNQRVpZYk5lWVlhcCsya1JEclphdzRRTVJHa3lJZmowSjdtMUVGM2NCSVZGMlRxV3ZSWWdlemZCSEZxRFZOSSs2NkpQWGFPZzFuY1F4N2ZjOTlSc3dqT0Q0U0VWS25hbk9yTk1aTlJyTitrMmJObHR4MTQ4MlVva0xndERZV2Q2ems1d2dpZ0tpcUhML2lPNE9WeXAzbHlzMVdxMFduYzZMb0tDZ0Z2Umd3amZmZk8zUnk2QlJ1Tlc2bVR3YWszUE5XQ3lOWEdzT3dlbDBpcklzaXphYjlWNlZTdlhzLzlMa251OThiR2M2dnY4VjJMSTJzUEhwSDVuOHdlTmRlckhaTXI1ZzFTTS9NK1BiNXppbkhsbEhCVnV1K0F1RFB2eVVvRUJSa1VMbkNOWFYxZjhLREF4YWlZdXp6U1FJUWdNdUQ2MU5FQVNISUFpU1NxV1NHam5iZERvdldhZlQ0KzJ0eDJBdzRPUGppNStmSDBGQmdZU0doaEVWWlNRdUxvNysvZnNUSHgvZmJJcDZCZ3pjWkpHTkdocDJ1OE90RWpvRmQzcEhVK25VbmoyN2ZWVXExYzNLVWlub0RyVHhFNW5hUmFFR29FMmF3ZVZmTGVhY2g1blV3WXovWnJraTFNNHgvUDM5cjd6Ly92dDhHODFSZDRSVVJhdFNLM2R1R3c2SEsxSnF0enV3MmV3ZVVWSlhibHRqSUtHMHRKU0dodWJRdk9ncWNxOXZaWHEyekZscmoydHQ0TUNCTndJS3VaV0Njd3RSamFoV2hNMGZ5TmNXLytDREQwNXdDN1oyZlcydE9kdmE1clpaV3dRU1hFWHlya0JDMDdacFdlVGVtSy9XTmhMcVp1NFFBT0c2NjY1VnFWU3FHNVJsVXFCQVFYY1JGQlEwbzFldlh0cDJ0RGF4VVd2enFFaG95cDF0RGlSNGtsSTJGc203MGo4c0ZvdExzTG0wTlZlUmV6dEN6Wk9TcUVsYmUvZmRkNjhEQmlwTGRQYmhJdFZUVWdvVS9LRzF0Z0hidG0wZDB4V3RyWDNOclZHNE5SYkp0MHovQUJCYnBuZllXdmpXbkU2bk8yK3RtUmtYRU5WcTlYWEs4cHhMd2VhblRJU0NQelRDdzhPbnVRVmFvOWFtcGhVWlpXTTFRcU1NYXVscnM1MDIvY1BoY0tCdXJBZHQxTllhODlXYUt3eWFmR3U0S2IrbkFxT3JhMnVwc1l0RUJmclFyZ3ZFS1lPcWRRS0FrNktxT2d6ZXZ2aDVxWlRWUFEySzEzeEtWYkVWTWFRWGZhYVA2ZEl4dHZ3amxCU0s5QmplcDh2WHNSY2ZJV3ZOejhoQStNUXJDSW8rVjVxaWhPUm9XMDBxcU5WbnBTdFdpeXRWNVpLenQ0eGVGM1dEQnJ3dW4yTmZiRVFDQWxNdkpxSmYxK2luSzdhdlJ0VjdIQUZoM2wyK1ZNWFdkRXF6eWhGMDRjVFBuY0xaU3VXVUdpb28zTElOcTBuRWVPbWw2SFcvM1g0MW5kaE5yVFdVeUtTWUxoOGppdUtJNDhlUEpic3B4QnVMNCsxdXJjM3BOa2ViTkRhbjA0bkRac0ZVWDQ5VHE0WFFTTHk4dkZyVmtUWnFiU1dJcm5yUTVnb0REMjJ0VGNBQUVMUnEyOVYzL2ZzRHByMzFMWC8vNkV0R3Y3U0NIYVdOOVhaV252dm9DMFkrOVI4U0ZpM0hzMFRWWWFwZy9pc2ZzZUN6elZ6NjRsSStQRkttU0xEVFFPVmxRRk56aEQydmZON2xZeHIyZmN1K0Q5WjJ6eVFRdFdnTUJvby9lWkhDektwemRqK24zbm1ZbFNsRFdabVN6Q2ZHYUQ0Zk1wU1ZLVVBaK01SSHYrcTgrUjh0NXNpR0l5MWZuUVc3K1BtRi8zYnZSS0lhamNGQTVkb1B5Zjd4ZUpjUHkzN2pNWW9PVlhmdlVsNTZ0R0k1KzU5Nm5iTlhhdTlnOTAyWDhNdW1RNWhMeTVIT0lUdkp3WWR1b3JpdzVRVXFOeTBqODh0ZDNUNVhiR3pzUkU5enRGV0VWR2dkU0hBNm5UaGxHV3QxT2ZXVmRVMFZDV2F6cFVscmM5V1JsaUUyVWhKNUNqVjNoWUZuMEFCWmxvWHE2cXFCZ2tvN2U4R3NtV3gvWUI0cjc3Nk9CL3A3c1dqRHdjWkhrc2tqTDJUVmdvdG8vZTVmOWYyUDFQVWN6RGNMTDJmbE5VTjUrYXZ0VkNqME1PMGlkTUtsOUpxY2VucnRyS0tFMmxPRk9OdVpQMmRESFhYNXBiU2g4WlJzbUU3bFlERlptajVTaDhVVFAzc1dnVkcrcDMxZ0d2SlBVVjlTeGEraEJlMTUyM1BNT3JDZldRZFc0ZWNkeUtpMSs1bDFZRDhUbjdxMmVYaVNTN016NWVkZ3FiTjBycUU0SEppT0hhRG1WRG1TdzBHYjlyaVNqYnFjZk95Mkxtd3lRd1M5WnM4aU5QRTByZVVjTmt6NXB6QlZ0TS9jYWkwdHhGelQwTTVhMUZDYms5OWluUUtIcHhFLzR5Sk9GK2lWTENacWMzS3dObGk2T0xzU2tpT2ZzbjBpU1lzZXBQOWZic0Rnb1VBNlRSWFVuY3B2ZDYvSUFBNExkVGs1WFpvbnllR2dldWNXVERVMjE1eTN1L2VLMnpLOXVLOWhiV2haY3FMUmFDN2J1SEZERHcrTnJja2NiZksxdVZzT3VJUWFndWp0RFJvVkRtZGpMYWtIdFZGZERhVTUyUlJrNTZIMlRPOW9McHR5NG5SS1NGS3p4Z2FJQm9QaEtoQko4dWpnM2pQSUgwdEpJLzJRbWlHOW9xQW1wODBVN3NncVpNSkZZd0VJanVsRlA4ZFdkcFk1dVNSY01VbTdZOUx0dkdvTVplWkl2SDBscXJNYlNIMy9FNkw2dUpydTJnNnZaczJzeldpb3dxd2Z4dVJQbmtldkJmT3hMV3llL3lBcVl5TFc3T05FM1A0dmhsOC9yc01yeVhXNWJKazlENHR2TDdSeUpaYW9TVXg3cGJrL2orM1lKcll1V2s3eUcyOFI3UDhyNytySWwzeXk0SE1TZXRWUlZpUmhyM0l3ZXYxYUxKL2V3L0hTa1Z6MDJDekF3cGFSL1lqOU9Kc1l6UTdXL2UxRkxDY09ZamM4UmZWM0FSaEd6bUhNUFhQYzVtZ1dXNjZiaTYydW51cFNQeWF2WDBIQUdWclo5aFByV0hYTjAvajI3b1ZVZmhJcGJqb1h2ZkVnV3JkZ3luNzFYbkxWS2l6Wm1VVGUvUktwMTQ4SEpFNjk4d2k3bC95RWYzd1F0ZmwyUm42NGpQRFlqZ2RSdmZsOU5qLzRMdjc5RTdBVVpCSHo0RklHVEl6cjhKaTg5LzdCNFc5MlVsdFR4RS96NXFKV0J6RHMvWGNKOW9XQzl4N2hwemUyRVJBWFNIV3VqWkdmZlVwa25HdXhDcGZjejRHZEVtTDJMcHdhYjZ6K3ljejg1SituZFEyVXIvc1B1OTllUTEyMm1ZcDdydWFFUVVYUCtVL1JmL0lBQU16N3ZtYjl0Y3VSS241QjZuTU5VOSsrRnhWUXZXTTVQOXo5UFBvK2ZURWQrNFhFZjd6YmRBekE4T0dwWTRGVGJxM05CcklOQkJXeTdKUmxXWlJBbHB4T1dSQkUxR3FYNWlaTE1vTGtiSkgrMFZCeWdyMHJ2aVF5MmtpSjFJQzZ0Vyt0MFZIbjB0YWFUZEJ2di8xV0o0cmlWUzFYM2NSN083T1lNK215VHJhSGxiSTZCeVA4UkY3NGJBM0pvMGNSN2dObHRXWUk5MUhrVmRjTkdZYTh1d2F0citzQktmcmdFZmEvczVLb0YyNXh6WEsxbHFscnY4U2dzN0g3cWxGa2ZyMmZsTm1KN1A3cjM0aCs3RE1HVGV5RFZIZU03OGJlU3NYbFd3bnU0RG1yMzU1T1ZmQVVydmo0TWRmYnVCVTlqTE91bElyOWg3Q2RKWHRLUHJHSHdCYzJrenJjQ0E0TFRxRHdkQ1owOUFpbXJGakI4U2ZtVWhYM2QxSnZiS25kU3FVbUJxNzltbUIva2QxWERTZjMreXdDcHNlZm1Wc2dhZ1NYL3JnVmpSckF3cllwUThuZFBaL2V3MTB2RSszZ1M1bjQxRHdjaFZ2NWFzSmpKTTdhZ2lGN05Udi9jNHlMTjIvQ3oxZWsvSXRuK09uNTk1bitlc2V0UUxLWHZrdmNveCtRUEtNM0lPSHNnaGJWNCtiSDZYSHpLVllsWE0zSUZTc0lhc3hrcnN2ZzUzK3VaL1MycllTSDZjaC8vVzUyUGZjK2w3M1YvSEtxMm5tVVN6YXRJeUJZaDlOaTZkRGZHVEo1QVZNbUwyRHJoRGlpWHYyY1hyMWJwa3liSFVZdS8rWTUxSlljMGdmTnBLemlYaUo4QzlsNSt6OUlXcnFSWGttaDJQTzM4UFhNcDRpYnVCdzNWU0ErUG9acDgrYk5XN0ZzMlRLcnl4eEZJeURiWlJBRndmV2Y0Q0t6eGVsMHlrNm5VNUFsV1JhY1RuY3RxYnNCakNHQTZObXppSXdNcDJmUENOU05VVkNYR2VyMFNQRm82VitiT0hIaWJLQzV6N3hrNS9VdlZ1TklTR1ZCLytDdWVuVXc2RFJvUkVHUlVXZUltdTByeWZ3NEhWTkZIWTdxRXFTK3plOGFROHBvRERvQUxTR2poM0g4d0NHWUFxV0h6S2kyZnM3ZUhTQWpJVHZ5cWNxdUozalE2VjhxK3FRVXhJemIySEszbGZEVUZIcE9tNGJCb3pKTVAzUXVjdzdQUFhzM0ZuTWh2WWE3VFVHMTdsZlZFYXZpa3dqMmR6MTRodWhJNnFxcnovaGNnbWpoeFBPTHlkOTVBTHRWd3B4VFMwQmxEZUFTYk9FalJybUdiQnhHY1BCSktyTXNtSGR2QTIvSWV2bFpWd3YweWhQVUhQVEM2WFllblE1aEkwYnk0ek4zNERoeUNjR3BZK2s1NFlJekhyZmxXQWJtcUdHRWhibWlDQkZqeDFDN05MM0ZHQUltemlUQVhjdXQwdjI2YUVOZ3loQlhkWWd1QWtOSURaWmFjSmJ2b3J6R2k2QXYvMFBWbDRCa1Jxek1wS1lLOU0waUkvcjU1LytWdW16WnNzMk41cWpzTWtjZElEdGxHY25sYjVOa1NYSzIwdGc4b3FSYU04VS9iYUdpcHA0Q2pSZnFSaFBVNld5ZDR0R2NrQXVJYXJWcWxzZjdtazlYcldhckdNZjdVL3ZUZVY2NEY2Rythc3BxbmZ4bHhrVkFBLytwaDFBL3ZTS3BPclk4VzdvcVRxNWp5NlBMR2ZmRmZ3bUxEYWJxNnhmWjhyV3RJd1hQZFJKTkVCSFRwdUxqWHFnZVU2ZmpHKy9kNGJYVTBhbGN1bTA5UlZ1M1V2ak5VdExmV2N2TXpXK2hQVWUzS3VvTnA5bEhVdE8vY2hkNVNRVlBCMVkzaWhiYWU5MW12L29RK2FVcGpGMjJDSjIzbXQzWERVT1c1Tk9md1gwOXI1akJSRTJkNnY1OEtqMXZEbWd4bFBhdUZYWGI4MXlTdHAraXJUOXg3TEhyS2J6dVZVWXRTRHRuMjB2cmMvYWVQMEU4VGRHYmZ6UlJVNmMybGNUMW1EYTdqVnNnTEN3c0Rkald5dGZtbWJBcnUxSS9YSlJHc293Z09KMnlTMmE1M0doVm1ZZEJOakpnU2hyUlBZeUl6Y200enZiU081QmxXYWlxcWt3U0JPSGlSbjlaK3ZyMWZGWVh3anRYcGpTcGxKMXRtWkdKUmpZZnl3YWdMUGNreDlTUnBJWXEvclhUemxod0NPcnlYRXdlY3N0ZVZJUWMyWmZnMkdEQVFkNjZsbzFIVEh1MlVXOEJzRkcrYlJlaEF3ZUJiMS9DK3BocHFBc2diR2dLWVVOVENJaUxRV05vWGpodFNBaDFCUzBicURqcTZzQS9uT2pwc3hqMjBwT284NDdpNmZ1MVpXMWwrMTBQVTFsMzd1YkFLeUNBQmpmOXM3UDRBSlZGTGNtUjFIb0Rsc3J5cy9lZ2h3UmhPdFdTYnJxaG9JQ0FJY1BRZWF0eFZoeWlhR2RMUHJtU0hWdGQ4MVc0aTRxcTNnVEg2UWdaUGhwcjFpSDAvWVkwemJtZk1haFptQm1DMERxS01WVzFsTlMydWpvTThja2szTGlRNVBtWFVYMzB4Qm5maTY3UElIUUZ1eWd0ZFFVaGluL1lpbC95NEYvTnFLUDIxV01yNzFvRVhSVTNqQkJ5c1JwaW0rY2hMZ3BOcTdlaldxMU8yN0JoZlk5MkJKdXEwV0pzbEVlU0pDRUw0TERaV25TMnNwZ2FFUHo5Y1RpdDFKZG5vMjRVYW0yckRKcUNCb0tQajgvbHpjWjBDWXMzWlZHcjh5THRXVmVvWFJ2YWkrMExKd0R3NU5zZmtGNXNvODdtWU1haUpTUU5Ic2FITXdZeGZjd292bnRuRlplOWVaS2FxbHIrZXZrMGdwVVN3Tk1MdHJBUkRKNFp3dW9MUjZBMnBuRHh0NitoSHpZVkkyK3pldVpjdEdJZHVxQ1Fsb0lnU09LSFdaZWpwZ3FMNzBRbXp4Z0V3TkJYWCtiNytkZVM5MVlDYW5zNTlTWWprOVl2YVNMUTdYWExQV3k2K1Q2K2ZGOUgzTjlmSlBuS29kVHYrSWlOLy9jSmdYM2pNR2RuRW5MN1EvaDd2SlNkNVNmSi91d3JZaDViVE5BNVNuOExtWGdGcW4vT0lYM21UNmo5SXRHRnROUnpqSmRmejlGYkgrS2IxYS9nTi81NnhqOTY3YSs2WHZUY0JaeVlONStWSTk4azRwcUhHSG5ITkdLdXVaWDFDeFpRdDNFZ2xoclFKN1JrWGJFZldNMmEyV3V3WkdjUys5ai9JOEFiR0RDVjFKdTJzMzdDT1B6N3hXSStsWTNmcFE4eTlwNUwzVTl5VHdiZmR4RmIwMGFnOG90aFRQb0tnbjNneUVOenlEMnB4emRVUzlYeFVsTGUvdHVaMzR6dklGSWZ1cGp0MHlmakh4OU1kYmFOa1o4cyt0VnJrbkRUUXJiZlBaTmYvQTNFM1AwU0E2Y25kZkNtTUpMNjJrTnN2V2tLSitMN0lsZVZZZ2xLWmZwSGk5b0kyR0hEaHFVQ3VaN0NUWlpsdStDS2tFcXlJTGpOVVFtVmx3N0pWRTlWdGdsYllEamFXQzkwNFVacU0zOGlxL3drSllJT1lmRGd3ZTcrQm1Zc0ZvdGd0VnF4MisyQ3crRVEzQ0ZYbGRQcDJBUDBQeHUyVlVsVlBYcURBVC90NzZPdG5lKzBSWjNEZ1NrL0gxVmdCRHBEVzcrSXM2R09obG9MUGhHaHJjd2RpWWJDZkdTdlFBekJYWk5Fa3EwQlUzRTVtc0F3ZEw2NjMyVzlaSnVKK3RJNnZLTWorTDMwZTJkREhRMmxOWGozakViVnpzdllXbGFNcFBORDc5dlN2SmNzSmt5bEZYaUZSYURWZFcwUDJHcktzTlpZOFRaR296b0xsQ1pPVXcwTkZTYThvNDN0anYyM2NhbllNT1VYSS9xSG9QZHZQNWxabHVXam9xaTZBNmdINm5CUkdwa0JpNEJnRjBUQktRaUNwRmFyWlkxR0kzdDVlYUhUNldTOVhvKzN0N2VMMHNoYmo0OVdUMWpQSGk2TnJka0ViZGJZY0NmSW1VejFhV2RIcUxrY0h1R0JTcm5RcnpRR01FVEhubDc5OS9iRjE5dTMzYm4zTnZiczNtcHB2Zkh0MmZOM3ZWdEJhOEEzMnZDN2prSGw3WXR2QjZrYVhxSHRWeXFJT2dPK1BiczNkcTEvS0ZyL3N6aDJneisrQnYvZmQ4dUtXZ3lkN0NOQkVQb2VQSmpSZCtEQVFmdmJtS1BJVG1Ra1FSQUVTWEpwYlkyQkJNODZVb2NrSTJuVjJHd1d4TWJHTE0yNWE4MytOVUR3OHZLNlRCRW1DaFFvT05lSWo0OGYwY29VYmMzNFFYT1psWVNuN1BJc2tMZGFiWWdldmpYQjdWc1RQQngyb2lBSTA1VXBWNkJBd2JtR1hxOVBvN242b04wV2ZhNGZxWkg1UTJpaHNUVjF0ckkxYW15U1IrREFwYWtCZ3RuY01CV0lVcVpjZ1FJRnZ3R01SNDhlR1VqTDhxcEd4by9HMUROQmtqeUw0NXMxdDJiMmp5YkIxb1p6emVYZTBHcW5LblA5MjZLMHRKVDgvQUpsSWhUOEtSRVRFelBNVTJOem02T3FWdVpvQzY0Mk40VjRDNjFOZEFjTW1sUzd4Z1BkWnFnaTJCUW9VUENiUWFmVGpXK2xyYlZuanJhUVdhMERDWGE3SGRIVERHM1UxQUNob2NFMG9YMHoxTW5Pak1Nc1dybUJlNWR2NHYxOXVSNzBLekpaZVhsOHVtMHZyMjQ4U0szSFVRNXJBOXNQSGVldGpUdFpubG1pck9CdmlFUDNYY0dKSGNYbndVZ2tmcjU2UEFXbi9qaHpheXZOcGJxdzRneVBybVBiWlpkUzJxWERiVlJsSE1KK0ZoaHhhamN0WWNNVFM4L3FQTWdORlpRZXlXM3orYy9YakNVdnAzdm1hRWJHZ1Q0ZVdsc0xqYTN4cHlWWG05UW1RaXEyTWtQeE1FTW50cjgzNi9ocWJ5RjlZbVBFelJLVUFBQWdBRWxFUVZTNHRIOFVtN2RzNFBFZkdrZHU1cjMxZXpoU1hNSmJQMlMyRUd5bGVUbThzemViL2IrYzVJdGppbUQ3TFdFdEs4QnV0cDBISXhHSm5yY1F2OEEva090ZzFTdnNXYkwrakFXOXBTQVBSMWU2VFZQT2pxdHZwTnA4Rmw0dnBrb2F5czR1LzU2VSt3TWIvdnBpbTg4dGVUblk3ZDA3VjY5ZXZaSTlOYmJUbUtONG1xUE5XcHRMc0ttYm82R05UQjR1TTFRVXhmWUZteGpBNGhzbk5mMGEyRkRHWC9ibnd0aFl3SnRuYnA0Sk5UbDhjM0JuU3pHYzBKOFBFdnF6ZXNOYVBxaFRoTTI1UVBYT2RJNHZYNDhkRFFFanA5SC95dkZOU2JxMjRxUHNlK3gxekZaZkV2OTJOeUZHWDhCQzNySVBLZHA3Rkx0ZFRmQ2tXZlNabm9vQVZQK2NUbVdsbm9ZOUc2aXBGSW03NlhhTVNlNGlkVWNkMmUrL1ErSCtrM2pGRDZIL3dwdnc5dTQ0KzlOMDZBZE9yTm9Pb2hidjBhMEViODUrTXBkOGdxbmFpaUYrQ0gwWDN0QTVBK3hweGxDemR3MUZCZjcwdlhRRUFKV2JWMUlsSlJKL1VSSmw2ejZpUVcya2NuTTZGbWN3Q2ZQdklEVFczLzJ3bDNMOG5YZW95Q3JCa0RTTy9yZGNpVmJkcUkyVWNlSzk5eWc3VW9CWHowUVNicjZWQVA5cU1sOStuNG85R2RUVmxiRi9jVFpDWkRLRGIzUjVieHdWV1J4OTkwT3FUOVVTTVBZeStsMlY1azR3dHBIMzBWdWMybm1TZ0xUSmRFVUJxL3grQmFkKzNJL1pWTU9KRnhaVG9CV0puTE9ROEhoLzEvbVd2VXZlanFPb2pRa2t6cC9mVk5qZXNRSll6YkdYbjZVOHU0cklXVGZUYTB5L0pzMHcvL1AzeVB2aEVLcUlSQklYZG42KzNBOWVvdkxnQWVUaVhQWXRYb3lnQ3FUMzN4WTJrU2FZVCt4azk1SlBzWHRGTStDZXUvRnpsN0JZOGpNNDlzN0gxRlU3Q0oxNk5ZbFRoaUlBM3Q3ZUk0RFBXcHVpamVZb3NpUTVKVW5BNlVRbHFtVkpJd2xPdTAwMjFkUmdreVFzWHQ2SWpWTFBuWlNMTENOVVZsYjBwNHZOV282VWxwTm9ERkdreXU4TXVXUW5HK1kvVCtqTWErbHozUlVJNXBvV1JJQW5sMzlGNkNXekNkQmxzL1hCMTVwTW9acENPMUd6cjZQM3ZHa1V2WEk3QjljY0E2Qm1Wem83L3ZZNG1wUnB4RTdvd1k2cnI2V2lSZ0ljWk53OWgxOHluU1FzbUkrUGVROGIvL3J2VGg5UVRYZ3NZV21qS2ZuOFZWcVNiVFN3ODZicmNmYTVtSDd6YjhRL1FNTGE2UnYrOUdQdzZ6dUl3bGZ2NDhUdVFteFpXL25oNGY4UU1DalJwZk9zLzR3ZEQ3eEk0T1M1R0JNZGJMbjJUc3dPbHhXeTg1b1psSm1NSk41Mkl4ejlqQitlVzk1a29mdzA3eklLQ24xSnVHMCs0YjI4cU0ydEJMVXZ3ZVBTOEU4TVF4dmRsN0MwTk1LSEpycHZLWjh0TStmUUVKQkNuOXV1b3Y3Ylo5bjl3WFlBU2o1OWxsMmZIaUx1cGhzUjlxMmtyTER6eW41ZDNFREMwbEpSYTcwSUhKTkdXTnA0ZklKY25JaUZTeDVqMTZjWnhONTBNd0dhazZ5ZmQxK1htSG5yMW4xS1ErU0Z4TThjeWFFN3J5TDNtSXRFTSt1NXY3Qi9WVGF4Tjgwbk5LeVV6ZGM5MU9uNS9JZU9JV1JZRXZoR0VKNldSdGk0QzkwMFR5NnROR2RGT3NaWjE2QXQzY0pQenk5ek9iUks5N051NWkySWd5YlJaOTVsRkw1OEI0YzNaUUd1WmkrclYzOFgyWjZmRFZrV1pBUkJFQVFRYUhLak9lMVduS2pSQkllaURkQ2hibDN3anFzMmRIeFhIcVpUMllkNTg3Q1RkKzdzcDBpVzM5dDcxVkNKdzZGSGI0d21KRDZDa09TVzM4ZGRkeWZSSS9valIxMU54dVVmWUFPMGhETGdyemRSc1g4LzVtb2Ivb1BpS045MUVLYTQraVlFWEh3TmZhYU1Ba1lSdCtJOWNqY2RJMmhVSmNjMk9wbTQ2eTU4TkJCMDk3MWtwVjVMVmNPOUJIZEEvYThON1lreDFNZ3ZiVFE3SzVacUs0RmhZUVFNU0NKNFlITG5RcngwWndkak1ETHl0YWRaYzh0dDVCaHE2ZnZpUndTSE5wY3pSYzViU095WVlUQm1NTGtmRGlCL2J4VXg2dlhrbHZkbjVyMVhvd0w4NzcyRHIyZThqdVhST1lnWnE4a3BUdURLcjI1M01ac2tOOWRHaHFlbVlqOFVRWmsySG1OcU15ZGMxZnJsMUJpbk1mNkdpNUdCUVhmZHlLcEZYekg4eGxIa3JsaEo3NzkramZHQ09Jejk3K2ZFaDFkMmVyL2VQZnZnM2RNWGpVWkhTR29xb1labW9aRzk0aXY2UHZROXhnc2lJUGtKY3BaZVFNR0psNGpyM1hFSmwrYUNTMG0rS2cwQjZEZnpNM0xUZnlBbWZqQkhsdXdpZWRPUGhJYW9rZnZmVCs3U0VSUmxPWWlKUDMxOVYwRFNNSHhWK1FpR3ZCYnowSWplQy8rT2NXZ2dnZlpackhyT1ZWOWU4UG43YU1iZlN2OXBvNUdBZ1F1dVlNZnlkSkxTWEp4MUtTa3AvWUU4VDNOVUVJU21aRjBFUVVBVVpVSEVwWmhwdk5ENWFuQTZyTmdGTDVkZ2ErenUzdmlqVXFuR2RqYlpGVVU1TFBqOEFJdHVtRW1pUWFsbS83MmhpcHZFeUhzeU9IRG5ISDRvdEdLODV1K01lR0J1VTMybFY2Q3I1RjNRYVJFY0RpVEFXWkhKK2hrMzRUMXlLZ0VSL2pRVTF5UDVORk5TZTBlRU52Mi9QalNNNnNvU0hLWGxXQzJGN0xyMXhpWWFIcStrSkxBQzNtY3k4a0NHdmZvTSsxOTltQy8rbmtmQWlNc1k4Y3FUK0hhd3B4eWxSUjJPUWRkblBER0ppemhlTUpqeG8xbzJHTkdITmRKL2EvRTIrdE5RWGsyRHN4aXBNb050Tjl6WVBLb0I4VWlBdGJBWVRVeDh0K2lhNm92emNSemR3NVlibXZzbkJQY2U3anBmYVIxQllXRnVWU3dDUThDdktRZzFZeWsxbzJ1MG1NUkFER0ZncTZ3RE91WkkxSVZGTmJrcGRNWXdHcklxb2FZSXM5bk1vWHR2NVdqVG0rQUNWTmpjOHVYTS9LcnFRSmRUVmFWUklWdXRycEVYRldENjhRaWJiOWpjclBrTmFTNXlDZ2dJU0FZMmVHaHJyaDhCQVZrV0pLZFRrSjJTSUlvcUpFbVNIZVphb2FiVUtodDhmQkYwdHJZYTI3MzMzcXNTQktGRDN1aTZpZ0p1K1dnYmQ4NmR3YmdJYjBXcW5CZFEwL09XQitsNXk0UFlTak5ZTjI0MlJkZk9KZHJZZ2U5bTR3b2NRMjVnN0w5dUIrRDRFN3Z3REZnMkZEYzMzREdYbGVKMVlUanFNQTFlaG5oR2Zmd3BCdlhaR1huZ21GbE1HRE1MMlZMRmpubVRPZjdkSEZKbW41NDFRaDBXMmVFWXlyOTVrZHpxSWNUMytvV2ZYOS9BcUR1YTNjWG00a0lnQmJEUlVGaERaRWdBM3VvSVZKR3BUUGpzdFRhRjlqWmpCUGJjbmRna211akFQU0VJQW5LcjdpaytFZEY0cFlReThiMkgyd3FVY0gvTXBhVkFIRmlLTVZVN3Vpd2dCRkZxMWR0Qmp5NU1qNld3SEhwSGdGU0ZxUlNNWGFCYnNaUVdJTHMxR1V0aEtkNFJRZUFmanQ3Z3o1QzNsaEVlZUFiS1NqZTZ5T2dqamZoZk5KT0pUN2ZQeUtMUmFFWU1IanhJYytCQVJxdEVYVUYwNld1Q2dDemh0TnVSTkdyc1pnc2FuMkIwWWY3b3ZIV29XNW1oUFBua0U2T0IwMWZOMnV1NGMwazZ1dWcrVkJaa3M3UUFWUG9BNWlXN2lseTNIempFeVlweXJFNHpYMjdQSUNFOGtxa0pvVWdOMVN3N2NJckRwNm9wdFVrczNRR3BBL3FSNktkUlpOSlpnQzFyTzBjMkZSQ1pPZ0JIL2s2czJoaDhBam94Y2FKNjBMQW5uWUtmUjBQeExvNTl1dy85OUV1YWd4RnJQK2JvbWtINE9nNlJ2Yy9BK0ZmNklQaEw5QmxyNDhmN24yZmdEUmNqVnhWU3NDZVBJZmZkMWlIN1J0WCs3WmhxckpoTlRpcDNia0Y5eW91UTFCSG9kS1VjL05jS2dzYU5SRXNsOWFWMmdtUENPeHkzRUpaNjJqRTRzN2F6N1I5ckdmWE5ONFQ0bHJGK3loeE9wUGFuOTFDWGhDLzY1QzF5VWlNUXNsZFRZcitRWVJjRW9oV24wdFAzUmJZL3M0UStsNmJpS0R4R1NiYkVCWCtaalhiUVZHSWovczMyLzN1RC9uUEhZc3ZkZ3h3NWlaN3U4L25IOTZYcWc2ODRPVFFJYldnTTBjUDdFVGhwRGo0dlhNN1BiL1ltYmt3aTVxeERWSnZDR0RSdklqRnpybVRicTRzSkNibWRxcS9lb0w2cmNvMFFBbUljbkhoektkYmtTQUpTUnVNYnJDTnUxa3grZXZGSkFud1hVci9wQTJvaXBoSFZ1M1A5MHI0M25YMmZYWXd4b29ZajN4eGx5S2ZqUWUxTHY1c0dzdnV1aDBuKzZ6V283ZVVVYmRwUHdtUDMwQm1CdnlvNkhuM0JNeHo2ZkJYK2dVR0VqeC9WRkh4cEQxR3piK1RBSlg4aG8zOEl4Z0ZHNm83dXhPcVRUTjlMaGpVcGNPKzk5MzZ2bEpTVVF6UTNVMVlKSUNJSUlyTHNSRVpBRkdSWmxwRkZBYnU1Rmx1REJybTZGSlZhclJiY2lia0NJRDcxMUpOWGk2SjRlaCtiWktmS3BpTGNvTUZpZDJDeE83Q0tXb1pIQndGd0xQY1VlVlkxUTJORHdlRkFyZmNsS2N3WDJWclBEOWxsK1BnSDBpL0lHNHZkUVZSNEJCSDYzNWFNeG1ReTRYUkszVzVLWEZwYVJuQndNQ3FWNnB3ZWM2YmpRM0JTdFdzN1JaczJVMXNxTUhEUjA0UWFtN2VqYjc4VURJSHU2SlltZ0xCaC9kSDFIRXhnVUEwNVg2N0JqSkdrMjJaajZKRkFZRnc0TmJ2U2NjUmNoTDVzRDZYSFRReDgrcCtFOS9BRlJNS256RUJWZXBDODlBM1VGdFlTUEhJc1FYR1JIWExtbDYxYlNkWHhmSHlUVWhGckN6RVhsT0J6UVNvNkx6Qm5IYUJvMHliS0R1Y1FmY3VqSkk1TjZGUjdPZDBZeXJkdkpuVDJYekQyalVEUUJoQTVvaThWaHdzSkdSaFB4ZnBQMEkyZGkrUGdKcXJLZkVoNTdnbjhBM1FnZUJGMTJVeHNSN2VUdis0SFRGVjJ3c2FPd3o4cUJBUXZvaStiZ1QxN0ovbHJmOERxQ01BNFlRUTZiOWRUcTQwZFRFaVVRUFhoNDFqTVdzSUg5d2FOSHoxblRLTHU1ODBVYk55SzFhb2xZdXg0Zk1OOE1Rd1lnYStZVCs0M205R05tVTFNU2krQ2hnN0ZxMU5aSkJJK01RMWIxaUhxOC9MUnhTVmpDTkxoZThFNGZPUUNUcTFhajgwUXovQm5IOGJnM2JrcWJSaWVobGZCeitUL25FWEMvYzhTayt3UzFFR2pMOEZIS0NJdi9UdXFzb3J4dTJBMFlmMWlPaWNpOWdvbmFtdy9haklPWVM2c3huOTRDbzNNWkFISkk5QzdEVHZCTjV5d1FRbUloa2hpTHhsQnhaWTFGRzc3Q1ljWVRPVFkwWGdITkZ1QWVyMCs2N25ubmp1QnE5K29IWEFJNEFDY2dDUUlvaXlxUkZrUUJOUmVPbFNTRTZuQmd0b1FpS0RUNlVSUDdqV0h3LzZOSUFpVC9xaWF6Zm5PeDNhbTR6dmJ5SDM5ZHJKTmt4ai93T1YvbUxVL2N2K2xtRktlWk9qY0ZFWEYveCtBdytIWW90Rm9uNldabzYxZUVBUVRZQlVFd1NZSXpSeHRXcTJMbzgzTHk4WFJKcm9UY3BGbFdaZzllNVpLRUlRUnlwUXE4QXFMd3ljaTZBOTFUN3FZUkx4REZKL3cvNHpYV0swZUdoTVQweHc0YU5sTVdXeDBuN21TZFJ0L1hLbHJhczgwajNmZmZYY1lvUFREVTBERTdQdUorSVBkVTl5ZEx5b0wrNzhGbjJYTFB1NDVhdFRvWXpUM1FCRGRhUitOcFZXQ0xNdXlMRGZuNDBxUzFLeXhBZWgwdXFIS1hDcFFvT0I4UWUvZXZYdTVoWnBuMmtlallLT2x4dWJxMVNKSlVzdkNVcFZLcFRnZnppTEN3a0s3RlRnQU1CZ00rUG41S3BPblFBSGc3Ky9mcDVVWktucjhDSzAwTnlUSlJXbWs5cUFwRWdSQnVFQ1p5ck1wMk1LNmZZekJZRkFtVG9FQ056UWFUUkp0Zld5TnhmQk5UQitlUmZHZUdoc25UaHdQQXhLN2VzSHEybHB5SytweG5DNG56OW0ycWF6VmJDYTNyQnF6UTFaV1RJRUNCWjFDRUlTRUYxNTQzcWVsWUpOYm02TkNjejZ1Ui9BQUVJeEdZNWVLM3BIcXVldVZGZXkxYXduM2NsSm8xL1B2NjZjeElrd1BXSG51bzIvNU9xdWNVaW1BTFU5ZFJiVDdzUGRYcnVTTnpEcDZCSGlSVjJWaHdSV1hjT3VBTUdYbEZDaFEwQ0VtVFpwa0JDcG9JcHhFRkFRRUQ4ME5hUGExeWJLRXV0RkcxV2kwWFJOc29vWUZzMmFTMU1PVjFyN3l1MVVzMm5DUTFmT0dBeW9tajd5UVd5ZWFtZmptbmhhSGpVZ2R4VFV6dzlHS2tIOTBENU5YYk9QeUFWZDBVdEdtUUlHQ1B6dWlvcUo2QXBsdVFkYWljeFZ0b3FNdTRTYTZKWjJnVW9rRHVuWVpyeWFoQnRBenlCOUxFOCtNbWlHOW9nald0ODE4N2hzVjNsUnJGeGJraCtpd1k1V1VSVk9nUUVISDhQSHhpZk1RYXExYjhqWGxzcms3V0NGSmt0Q1U1Q1lJd29CdVg5RnU0cjJkV2N4SjdkdU5nMlErMlhxUVljT1RNQ3FrSUFvVUtPZ0VHbzBtb1pWUWErRmphNTJvSzh0eVV3bVlBSFNQVkUyeTgvb1hxM0VrcExLZ2Y5Y055aDAvYitYamNqOWVtRFJBV1RFRkNoUjBDbEVVKzNob2FXNmhKbnVhbzlBaWdPQVNiRUpwYVVraTROWDFTem41ZE5WcXRvcHh2REsxUDExVnZBNW03T0t4bjJwNTU3bzBnaFZTRHdVS0ZIUU5YaXRYZmhIaUlkeEVXYVlGaDZSbkFFR1dYZWtlK1BqNEpIVDlHakxwNjlmeldWMEk3MXlaZ3I2TFV1M0U4UVA4ZlZNQmI5dzBoUmh2eFFaVm9FQkIxOUczYjk5UVRwK2s2eUhVWE9hb0doRFVhblY4bDY5Z0xtSHhwaXhxZFY2a1BldWkrZFdHOW1MN3dna0FQUG4yQjZRWDI2aXpPWml4YUFsSmc0Zng0WXhCL0dmMURnb3JSYTU1K1VQM2lmUzhkOTg4QnVxVlJWT2dRRUhIQ0FrSkRXOXJqcmJVMmp6TVVWa1FCRUh2Y05oZkZrVngvcDloZ241TDJpSUZDaFNjSFpqTjVtWGUzb2FsdU9pTDZvQTZONFdSQmJBTGd1QVFSVkZTcTlXeVJxT1JSUUJCRUdLVnFWT2dRTUg1Q28xR0Urbld5c1RUbUtLZWFSK3VMd1JCaUZHbVRvRUNCZWNyVkNwVjlHbUVtdUFoM0FBWmtKdStWQVNiQWdVS3psc0lndENqbGNZbXlIS0xsQTkzOVFISU1vZ25UaHdQQjNUSzFDbFFvT0E4aHU3Zi8zN0pwM09OeldXT2lzSEJ3WkhLbkNsUW9PQjh4OENCQS8wOEJGbWJkSTlHb1NiTE1xS1hsMWY0bVZ6a1RHaUx6T1lHY3N1cU1kbi9QTFJGc3VSQWtwU2lXQVVLZmkwaUlpTDhUcU90dGY1QnJWS3B1aWZZenBDMjZLT3ZWdkxtVVJQaHZsb0tLMDNjT0dNcUN3Zjl3WlhGd2swc1Q3NGVWWThJWkpzRnI3Z1Voci80RWhHOU95OUJNKzMrbWdQYkhZeTgrMHBsUnl0UUFQajcrN2ZXMkU1bmlncWlLSXFoM1RxN203Wm8rd1B6V0huM2RUelEzNHRGR3c2NnYzVFJGcTFhY0JHdHlhMnZuRHFkN1E5ZHg4bzdyaUw5MnFHOCtzMVBWUHdaVnNNcmtVbDdkalA3NEg3NkRuR3cvZi8rWDR1dkhUVmwxT1lYSTdYUThpVHN4ZG1VSHZ3RnlkRTlqVThHY0Zpb3k4bkJicFBjNTNOOVo2c3FvNzYwcHMweDlwb3lhay9sNDdBcG1xV0M4eGZlM3Q1K3B4RnFUZFRnamFWVmFsRVV1MG1KNWtWU2orYXkwcDVCL2xoS1d0SVdVWlBUNWlpOVIwZFlDVkNyeEE0YjdQN3hvQ1pzZUFxV3pVZHhBaW9zSEh2NkxnNXZ5TU0vVkVOdGJSQVRQbHRDUUdBZE8rZmZSc1dKSEJyS0pOYk4zUVVCU1V4NDkwbThLQ1M5NytVTTM3T1RVQU5VZnJtWWJlc05YUGJHWDEwSzRwTDdPYkJUUXN6ZWhWUGpqZFUvbVptZi9KTzlONlZTSVF4Qk5sWFRjRFNEaUwrL3hZaWJ4d0tRL2NKQzluMlRTMkJzRUxXL2xIUGhxcldFTiswSUMvdi92Z0QxUlhlUk5GM3A4NlBnOTRWV3ErM1V4K2JXMkZBTGduRG16U01iYVlzbVhkYWxQLzlwenk1ZTNaWE55UW9yLzVoM0tVRi9odFdRNmlqZXRJbHFTdzBuWC91Q3VHdWVRZ1ZVYjNpUGpCMGFMdHU0Qmk4MTVMdzhuMzF2cldMQ3c1ZHo0YnNycVBudVZUWi9aV1hLMi9kMzYzSlZPNDl5eWFaMUJBVHJjRm9zVGF1dTZUZVZDUS9Pd0xKN0dWL2V1NHpoTjQ5RlJTbEhsL3pJaFZzeU1JWUREaHZPRm1XOEVqV0g5dUUxc0ZwNXFoVDgvcXFCV3UxSHg3NDFvVm1OZ01BemUyQzdUMXMwc0c4L25qTEdzRzN2SGw3ZHVKK0xiaDNQSDc1MWlWUkw4YXB2RVlxUFVhTWZ5dVFiSmdKUXRuMDdHa1F5Ri84RENiRG5WbEJsUFFUOHVzN3JBUk5uRWhEc3l0NVI2WnF6ZU1LR0RBWkFGeDJOV0YyTkhWQVJSTVNJSUhiZmRnTTlKNDBsSXUxaUlnWkVleXIvakZ1Zm9UeFJDczRMcUZTcWRreFJHUkNFMWc0WkVmRHIvaVhPakxiSVlQQ2hkMlFZTjAwZGpWLytNWDZzK0JORVI5VlJKUC83SmNaK3NvcGVrY2ZZOVZwNjArUWJrbEtKbWpxVkhsT24wbXZoSTR4OS9KYXVTTXJtNVdzbkpLMzFhWjlWUUZDN1YwbHNhUjRQZVhjZG8vN3ZGclQyQXJaZk9aa1RlOHVVSjBqQmVRbFJGSDFvNDF0cldWTGwvZ3hSRUlSdUNyWXpvUzJTS0tveE5mMVdXVnBNbmtOTG9PN1A1R1VUNlhmL0E1UXNlWjdLR29uUVVTT3B6VHlCZjNJS1lVTlRDQnM2R0VOUXMvNnE5alZnTDYra3Blanl4Y3VubG9iQ0JwZld0Mi9Qcnh5VERadEpJbmo0V1ByZjh6Z0pZOE9velNwcDhmM2hKKzdtMkFaRmExUHcrME1RQko5T1ROQW1YalkxZE5NYVBDUGFvcjY4c3ZRenRwdDFoT3BGY2l0TXpKbCtFVVAvWkMwME5iRnA5QjMvTEFmL2s4NjRCMjRsYWNmZHBJK2JURUN2Y09wemZ5SHFqcGRJdVdxRVM3dE5tVW9rTi9MTnVJbG9JbEs0NkxQbjBPRkw3K3Nuc1hQV3hSeExDRWRyRU1IbjE0eW9sQzBYWDQ3VG1JaVdTbXJyNDBtYjNOL2plenZGMzM2T05td0tmU1lPVXA0c0JiKzNZRFBRc3VpOVRhb0h6UkpPS2dDTXY4WEFUQTBtS2l3T2duMTlNV2grSDdMSjg0MjJ5R21xb2FHcURuMllFYlcyYTNOaUxTdEUwZ1dnOS9VK0N5T1FNQmNYNDBDRFQwVG9ueXhTcmVCL0RFV0NJQzRFVExpb2kycUJla0VReklCVkVBUzdJQWhPbFVvbHFZSGZqT3JSNEczQTRLMnNqaWRVQm45OERmN2RPc1lyOUd5K2gwVDBFVVpsSVJUOEwwRFhnUm5xQVZmd1FHRk9WS0JBd2Y4Q1dzdXFkZzBNV1VZUmJBb1VLUGlmRW14dEFnYnRDYmhHL25BRkNoUW9PTitoNmtCamF5SGdHc3NTRkp3REhEcDBHSnZOcGt5RUFnVm5CMkpIV2xwN2Y2aEFnUUlGL3dzUU92a2RRQkNCYmxJNk9ObVpjWmhGS3pkdzcvSk52TDh2bDJhZFJDWXJMNDlQdCszbDFZMEhxVzN2Y0llSjl6Zit4SmI4R21XSk9vS3RtTXgvUFVmZS90emY1ZksvdkhRdjZaZE1Zdm5vRzJnNGg5Y3haNlNUUHYxU1ZnNGZ3dEh2ODMvWEthL1BPb1NwUnRHd3oyTjBXVmFKZ0xON3A2N2pxNzJGOUltTjRkTCtVV3plc29ISGY4aHAzS2E4dDM0UFI0cExlT3VIekhZRjIrb3QyM2pqeHd5MkZOUXF5OVRSUTdiamF3NTlzSlQ5Ly9ueWQ3bCt3ajB2TXUyVDV4R3JLam1YaFcvNlFkT1k5dTBxZWlWNTQ3RCt2clJKSjU1WlFQYk9ZbVh6bmI5d25rWmJhNk8xcVFFYm9PbTZsUnZBNGhzbk5mMGEyRkRHWC9ibnd0aFl3SnRuYnA0Sk5UbDhjM0JubTBOcmk3TlpXdWpEN0RqdmM2b0YvQkdRdDNvTnZlNTlpUHlYUDZiVzlqZjh1aEM3cnQ2Wnp2SGw2N0dqSVdEa05QcGZPYjVweFUxSHRuTnMyVGVZYXlYOEw3eVlmbGRQUklXRnZHVWZVclQzS0hhN211QkpzK2d6UGJYVEpOMks3YXZJWGYwakRWVW1mQWVOb2U5TnMybGtwYXJidTQ3U21pQzhTbjRpZCt0UmZNWmN3ZUM1YVdmOGdpNzViaW5aYTNlQlh4VHh0eXdrTk5iRjJTQTNsSExzN1hjb1AxR0VsekdHWGpjc0pEamF4UUpvT3J5Vkl4K3V4R29WOFJrMGx2NjN6T2h3ZzF1emRuRmsrUVpLanBZanJuZ1R4eDQvL0VaZVNxOXhTVTNuTzdiMEs4eFdMVkZ6YnlRMnRROEFoVis4ZzJ3Y1JPbDNLekZiZlVuODI5MkVHRjFqc09ic0ozUEpKNWlxclJqaWg5QjM0UTNvZFIzZkU1ZzQ4ZnBTZ3FkY1NNNlNwVmdjdnZUNTIvMEVHdzNLQStHMlk3cWpzZjBxM2Z0SWFUbUp4cEF1N0ZFYnozMzdNd3VuRHNOTFNXL3ZaSzZxeUYrWFRlVFVPVVFtbHBEZkJTMUNMdG5KaHZuUEV6cnpXdnBjZHdXQ3VhWkowN0ljV2NPYWVRK2dHM294Zlc2YWcxRFc2RDZvbzZiUVR0VHM2K2c5YnhwRnI5ek93VFhIT3IxV1hWWXVnV25UNmJmZ2VxVE16L2pobVkrYXY5dTNucjMzL1kxVDJWN0V6SnFKMmxGM3hocGY5WWJYK09IWmRLTG0za3hrbkpNdFY5NUVyZnVOZVB6WjI4a3ZES1R2N1F1SkhOSURTNlc3RnRtV3ovZno3c0k3YlE1OWI1bUxIaE9PVHE2akRvb2lMQzBON3hBRFBvTXVKQ3d0RGYrNDhPYTV1LzVSZk5KbWtuREZPSTdlZXozWkI2c0FLUHJtWFhZKzl5R2hsOHdtUUpmTjFnZGZjNSt4Z1owM1hZK3p6OFgwbTM4ai9nRVNWbnZuOXdRbXNsNTdrUi8vN3pYOFIxNU05S2dFTE9WMXl2Tndlc0VtdC9xM2hjWm01Z3lwaTA1bEgrYk53MDdldWJOZnAzLzc4ODRkVlBkSVpteW9sbjNLQW5VSWE4WkdLbnhTQ1RkcWtkTEdjR1R0T3ZxUHViNWpXZGhRaWNPaFIyK01KaVErZ3BEazV1OXlscjVONkhXUDBuK0dTM01LU1I3bS9pYVVBWCs5aVlyOSt6RlgyL0FmRkVmNXJvTXdwVStIMTRxOS9uWnFqK3lucHFBYy82VCtIRnV4RzdpMitXMlprTWFJaDI5REFLSi94VHprTGw5QjNPMHYwV1BFQlRCaUVJVXJCNUgzVXo0RDBxS3hWbGFpaVFyQkw3NHZJUU9TUExaK05WYVRDbDFZQklHRDRnZ2VOS3pUNjZnQ2pSaFRqWlNFNk5Fa0RzR1kyck41N3Q3L0QyRTNQRVN2c2NPUWdiNnpoNUdkdm9tNGdTN0s5cmpyN2lSNlJIL2txS3ZKdVB3RGJJQVdLNVpxSzRGaFlRUU1TQ0o0WUhLWDdzbjFpSnJwYys5enhBOE5WaDZFdHJCMFIyTTdJNnV3b2lpSEJaOGZZTkVOVTBrMGRCSmN0Vlh3ajQzWkRBM1hzUDd3U1U3VzJNa3JMT1pRaFVsWnFuWlF1RzR0R3IyRm82Ky9RVVZlUFdWcjEyTHQ3T0dNbThUSWU4Wno0TTQ1TEIrY3lyWi9mZHJra0RBWEZPQWJGOWZXWVZHUnlkcTBOREpYcktmcTBHRWFpdXVSSEozdEhSTzdyNytZSDUvOUw1WDdEMU9YWFlSa3M3VjRaZm9tSnA2Vm1sTkxaU1hlSVdGTjcyQjlXQ0NXMG5JQStqendBbDU1WC9QdGlLRjhmY20xRko1d0U4MzdKREh5cGRzNCtlUUN2aGlRektZSFhzYnFPUE14bUVvS0tmdnlUYmJjY0NQZjMzQWp2L3hZams5d3Mybm9GZWhpSVJCMFdnU0h3KzNkRG1UWXE4OVEvczdEZkRFd21YVzNQazZkU2VyMG5sd0xHWUp2dkNMVTJyVktaTm5jam5iV25rRWdxM0VWbEhZTGRSVUYzUExSTnU2Y080TnhFVjBvL3BSVmpCMFlTMjd1S1hLQjdIbzc5dEpTVGxURWtoU3MrQTlhdjVUeTEyOGxmTUs5cUF4YVZIMUhFcEQrVHdvT211ZzFzS081VXRQemxnZnBlY3VEMkVveldEZHVOa1hYemlYYUNQb2VVWlJrWndNdE5ldktqU3R3RExtQnNmKzYzV1hlUGJHTFV5MmtwUnJSNFdpeGMrVDhIWnc0SE1HVmUxNUdDOVJ1ZUkzRE96SmRkSCtOYjhzelNDSVMxQ3FrUm52TkRWMVFFQTNscFVCUHdJRzV0QXEvTUpmYnd5djJBaTU4NjJQQVFkYmlCZXgvYXdYR0Z4Y0FFSDdaUXNJdlc0aXpKcDh0MHlhUXMvY0crZ3p2b2xIU3FyK0VJZHhJNU9oN0dISGJtRzdkVCtDWVdVd1lNd3ZaVXNXT2VaTTUvdDBjVW1ZbmRYaFBkREovdG9waWJBNHZmTUlELzZ5Q3plUWh6RHIwY0tobFdhNFZoRzY4WCsxMTNMa2tIVjEwSHlvTHNsbGFBQ3A5QVBPU1hlcjc5Z09IT0ZsUmp0VnA1c3Z0R1NTRVJ6STFJWlQ3Wm94dk9zVXJIK2RUbFRDSXl4TkRGVG5XV292Sy81SEMzSjVNdmUrMnBvQ0JmUEFyOGpkK1Q2K0JsNXhlS2M3YXpwRk5CVVNtRHNDUnZ4T3JOZ2FmQUxmcGVNMThEczk3aXN4NEhXRnh2cFIrbjBIY1hiZmdIZFdEaGozcEZQdzhHb3AzY2V6YmZlaW5lMXpEcHhlaHNVVmt2UEFoRVgwaU1VNmRqQzR3Q20velFiTFc3U0RZWU9Md3F4OERRMzcxZlllbWptRFhmMS9CSUUzQ2Y5Q0ZoTVNGMG5QT0xEWTgrUy9DNHg1Q3lscE5mbEVpRjEvb010bXlQM2dWb2Vjd2ZFTTFWSnpJd3lmNUtyY3BjWWdEeS9ZUVBpWUZvU2FMQnBNdnZsMTB2dnYxVHVEWThuZnhGY2ZpSFRlSTBONFJ4TjZ3Z01NM1BNYlJ5Q2NKNmVsSDlkNGZFSHZQb05lb3VBN09WTXJCZjYwZ2FOeEl0RlJTWDJvbk9NYmxzK3ZvbmpyREwwOWVUMmJwU0daOTl1U2ZWYkRWZDZDdHRSQjJxaWVlZU9KS1FSQVN1KzdZdGxObFV4RnUwR0N4TzdEWUhWaEZMY09qWFIwTWp1V2VJcytxWm1oc0tEZ2NxUFcrSklYNXRqbE5kSGc0c1g2L2ZiY09zaXdBQUNBQVNVUkJWQU42azhtRTB5bmg1OWM5ZnMzUzBqS0NnNE5ScVZUbjlCaHJRU0hxdnNQb2tSemY5SmwzbEJHSFhVTm8vOWdPVkI0blZidTJVN1JwTTdXbEFnTVhQVTJvMFdVbXFVTVRpQmszZ0pLMTZSVC9mQWg5NzFUQ0I4YmkxWE13Z1VFMTVIeTVCak5Ha202YmphRkhBb0Z4alIwWk5SZ3Z2Z2p6c1F3c0pTWUNocWFnOVE3Rm1CcEx3Y292cWNxdHA5OWR0K01kSGtWNGNyUDU2UlVaNzNHT3JzRTNlUXcrWWlXMVdibW9JeFB4and4QTMyc1lvVVlicDc1S3gxUWZ4SkIvTGlJbzFFVkc0NmpNcFdqVFprcCszSWNoZFM3SnQxNkNTZ1JFZ2RxTUhSUnQyVVRsTDFVa1B2ZzAwZjNDdXFabERSdVAybHhNM1M4NTRCZEZZR3dZNnJBRVlzYjBwbmhOT3NVL0hVRHdqY1U0YmpnNmd5dk82dHN2QlVPZ2V4OXJBZ2diNW1LVU5tY2RvR2pUSnNvTzV4Qjl5Nk1ramswQTZQQ2VYTkFTTkd3b1duWDdZelQwSGt6b2dOZy81MHZmNlR6NjlOUC8rQmxYRU1IYStLOGdDSGJBS1FpQ0pBaUNMQWlDTERpZGpvOUVVYnptenpJNXZ5VWYyN25pY0ZPZzRFL3BwTEZZVnVyMTN2K2xMUjliQXkzNDJFUkpsR1c1VXBreUJRb1VuTzl3T0J5MUhpYW5mSHBmbTRBb3kzS0ZNbVVLRkNnNDMyRzFXaHZMbFZvTHREWUNUblE2bmVYS2xDbFFvT0I4UjBORFF4MHRrM0pQR3gwVjdYWjdpVEpsNXdaaFlhSGRDaHdvVUtEZzlLaXVyajZkS2RvazdCb3pQRVNMeFZLcVRObTVFbXhoaW1CVG9PQXNvYWlvcUs2MVFCT0VObVZWc2lDQVdGUlVYSFJHMHJPMmx0eUtlaHluSTJSd3R0SVFKUm1IVTJyNmthUS96NExJN254MHVSM1dsZE1WdXpWOUxpa2JXb0VDZ0l5TWcvWHVSME5xRm01Q080K1BnSHJ3NE1HbGt1UzA0T29BMHpta2V1NTZaUVY3N1ZyQ3Zad1Uydlg4Ky9wcGpBalRBMWFlKytoYnZzNHFwMVFLWU10VFZ6WFZDbjYxYmpXUDdDakF6NTJnTS9yQ1VieHdVZUlmZnpWcTl2REZwSDh6NCtjblNCOXdENU1PcjJwcTVKcS81QjVPMWt5Z24vRUFCNDRrTVBHcHVVMkhiWnVRUk95SGh5aThKNW5RcDNmVHE1OWEyZGtLL3N5dzNILy8vUTBkbUtJdC9HMk5UOHNwb0d0U1J0U3dZTlpNa25xNDB0cFhmcmVLUlJzT3NucmVjRURGNUpFWGN1dEVNeFBmYk51bGZPcUVDYnc0UGtGWklnVUtGSFRQNnBIbHZGWUNUUExRM0ZycWE0S0E2RHBHN2daTnExZVRVQVBvR2VTUHBhbkdUODJRWGxFRTY5dlhMcXFycTloeUpJZnNHc3VmYUVrRVZGNWVnSWhLcjI5UkhDNnF2QkMxSW9KS2hkZ3ExVnpVNjBFRTBVdnY0amxXb09CUERLZlRXZEJLcUxYeXRRbXlJQWcwQmcvVWdDeEpVczRaT2JudEp0N2JtY1djU1pkMStxY2FMeDMya25KVzdTOWg2L0oxVEo4eWhjYzk2R0grc1BDL2dNdTNMZ0hnMHQzTFczeGx2SEV4cmxiRjAyaE54VGpxdTEwQTlQaDRoN0tyRmZ6cFliUFppdHJSMk5wTitSQUVRVllEc3RQcHpPNjJZSlBzdlA3RmFod0pxU3pvM3puTnlyUUphVXh6LzM5WjdtRW12TGVkNnkvb1NheEdXVFFGQ2hSMGpQcjYrcUoyVE5EVCt0bEV0elE4MlUzRmtFOVhyV2FyR01jclUvdDN1OVZWYUhRa1VZNTZUaW44NEFvVUtPZ0NTa3RMeStpQ2o2M1JIQlVCdWFxcUtxdnJsNUJKWDcrZXorcENlT2ZLRlBSaTE0NnByRGMzL1paMTdDUUYrbUQ2K0NnTHBrQ0JnczZSbVpsWjRTSE1wRTVNVVFSQkVMd0JyU1E1aXdHdlRxOWdMbWIwb2krbzFYbmhKYm9jZGRyUVhteGZPQUdBSjkvK2dQUmlHMVZtQi81Nkw1SUdEK1BER1FPNDc1WDMyT1B3d1Y4amtWY3JjZS9zcWN6ckUvS2JUOUJ2eWU2aFFJR0Nzd0tySUlqWDRtcGpVSStMMmFOT0VJUjY5MmMyUVJBY29pZzZWU3BSMW1nMHNpQUlndDR0MkxZQXllZHViQkpsMWZXWW5RTGhBYjU0L1U0SitZcGdVNkRnZnd1U0pHV29WT3BGdU5vWWVBbzJrNGRnYzdvRW0wcldhRFJ5WTQ2QkxFbFNwaWlLNTFDd2lZUUcrQ21ycEVDQmdtN0Jack5sMGRhMzF0ckhKbnVZb1lpQ0lNaUE3SEE0TXBVcFZLQkF3Zm1HdXJxNjNIWUVXcE9QclhVT1cxUHdBSkROWnZOaFpRb1ZLRkJ3dnVIVXFWTUZyUVNhazlPa2ZEUUt1Y2FZcG56OCtBbEZzQ2xRb09DOHc5cTE2MHJjZ3N2cElkVGFwSHMwbWFHaTRLbzhBT1RVMU5SeVNYS2VBSHIvcHFOKzU5SzJuMTF3TmFUTVUxWlVnWUkvT1dSWi91WFJSeDgxdHpaRDNYUkZucHFhaDNBVG00cmdaVUNTSkdtdktJcTkvOGdUNVZXVGk2b3dFNnE3MTVUVzc5UXBSTWNKVUNzc0d3b1UvRmF3MmV5SDJ6VkRaU1FFV1FKQlJwWkJsbVJCbGhHY0lEamtwdUFCZ0d5MzIvZDFmaWtuT3pNT3MyamxCdTVkdm9uMzkrVmlhNWF2Wk9YbDhlbTJ2Ynk2OFNDMXJXM2wvQnllK1hJajk2M1l6RWVaaFgrNlJTcmZ0NW14VDM3UjRyT1QyNzVsK3I5V3QvdjNQNjVad2VqWmQ1RTg2MUgyMW5oODRhaGwwWlAvWU5BVmQzUEo0cTkvMjV0d0ZIUHRUUTh5OUlxRlhQdkdEMTArN0lVbkgrRy9lNnU2ZGFsbC8zbU4wZGYrbmVnWnozQTIyVkMvVzduNi83TjM3bkZSMWZuL2Y1NHpGNFpoR0VZWVlFUkVSRkpEUkVNeUpEVXpjODNNekZ4enk4eTFzakl6MTNWYmExMXovZlUxdDIyN21HdG1kak16MTh6TVRNMVlORFBYeXN6SUM1a1JJUklpSWd6RE1NemxmSDUveklVWndHdnFWczc3OFRnUFpjN2w4em5uOHptdjg3NitQcHpSN0hNM3NLdW9sQk10S0wvbWpaZkpHVFdabm1QL1NjVjVlL0F1OXUwcnh0YU1uMi81aTA4ejUvMXZ6K2hLQmF0ZnArKzQ2YVJkK3dkMjI4NWhGeFViVTZiUDQ0RDkzSkVJSHJmYURyWmloZ2I3MlBDVFRVcEl5RkpUOEFCL1pOUnF0ZTQ2ZGVmcldMT3JuQzZwSGJnaG94MmJ0K1F6YTJ1SmIyY0RMMy80QmZzcmpyQm82NzRRWUR0V2RvRGZ2YnFOdUxiSkRMMDBHZG5sdnVpQXplMm9wL1JvYmNodnNSMHl1T3ZxTHEwY1hjZnNmNzdIelBsUHNudlYvNUVkMDdTbi9MT1BlTGs4Z1oycm4yWDl3emRlMkp0UVcxajJ5dCtaTnlxTEdydnI5RUg5YUNWV3UrZU1tcnIxbnNsc1d6Z0pkYTJWYy9HcTVHOTRqNElEeHppdzV4dXE3Y2VaLy9KYXFrNW5HdHBMR1g3L2k3UzZPSWk5bUJrdkZySnM2YlBzWHZaSExPZnR3ZGN5Y2RKY2lwb1I0MWlycTZtdWQ1M1JsUWFPdkoxdFMvOUNrcXZ1eEVTeFp5R0ZtOWF5cjAwV25mWG5qbzZtcVBqUUQ4MjB0V0FmbTVEODRPWURPRitxaDFBSDI2bWpSOSt5YS9QbUFodHc0bUluMmNUajQ2OE4vTm5HZnBUN2R2OEEvVk1CUGY4M1lRVFVsckQyNjA5RFRsdXhiUmZYRFJySXZibEpGeFdZT1k0ZjVzbVgxMUpjcjJWb3VpN2tDN3pzdGJjb3NpcFlMczBPT1dmbnR2K3c1c3R2S2F4MnMzcmxXMnhUUlRMeHpwdEppYlN6K01WMzJMWDNheHhWR3VZOHQ0ellqcGxNRzlielozR3ZibHNWaTFmbXMvdjdDaFNka2RFMzNjRGdqUGdtY0N2OWhvZm12SWxObjhDVTM0K2dhNXkzME1WKy9EQUwzdGpJbm5JYm1ibDltVHE4RjZkS2cxWWFqck5vMlh2cytQNFlzWW1KakJzOWd1eTIrcE8vMEFPdVl0bkt0Yno4U1NGYmxEVThOUDRtektmd0xHejVZQjM1WHhkanJUL01FODh0UTQvTXJXUEhrTkZHSnYvOXRXelo4eTFsamdhV3ZQUW1lbDBjMCs2K0R0TUpyOWJJMHRjM1VlT3E1cUE5Z2FHZG5LejgvQmpUNzcrZGpEZ051ei8vaEJVRmV5aXZiU0R0MGg1TUhYTTFKZzNzL3U5bVZ1MDhTSW05bmtXTGxtSFJTQXk5NFVieVVnMCtCYjZLSnhjc1pzOVJHRC8ydHd5NHBNMVBHRVE3eTk5WXc2WnZqbURwZUFsVGJ4dUt4UWRVWlVXN2VXTEZSeWh0a2htZUZZOGp0aFBEZTdRTFdIS0wvbDNBclZPZkRySHVWaXgvQjJOcWU5Wi84Q202eEk1TS8vMzFXSHgxbURVbCsxbTIzMG1POWtlV0ZCU1IwT2t5Wms2NENqMHUxcTlleDZvdmZyQzlzV0hya1daYW1nZFFoTmNjOVZPREMwbENLSXFDcmNHRHJkR0Y3SE82Q1VENTZLT1BGQ0hFampONUR2c3JxK2ljZEtyU0tCZDdTMnVKRWNlNWMvRmIzUHJ5Qmo3NG9lWmk4QkF3ZmRwc0R1cTdNR2xZTnN2ZXlRL2FweUk3cHlkZHRVZFpsQithUXBqY0laMGh1ZDB3U1JIazVXWXpKSzg3UmcyQWh0NjlMeU92Y3lJR2N5SkRyc3ltLzZWdFQ5b0Q2NkVEekZuNEpyT2JiWnVLenYzaVpNN3FDcXExQ1l5L1pUaGorN1ZuNm9OL1lYdFZrNWEyNUowdERCbzJoTnlvSHhueWh4ZXhBelFjWmRTRVI2bU02Y3lVVzYraFpOUHJUUC8zMTZkc2E4bjhwMWgvSkpwcGQ5ekkwTzRKVkIxdk9PVTV4UWYycytPSE9wTGo0MG5RT2RqMHlkNFdwbDF6U2V0OEtVTjZYNEpPWTZUL2xka011YkluQ1Rydmk1bmU5VktHNUtTajB4a1llR1UyZ3kvdmNnb2E2a2FXdnZJbWNxZmVsSCs4bk1WN3RlUkdWZkRFdTE4QmNMQ2tncHdyODVnMmRnZ2NLR0RNczVzQVNFcnB4SkFyTTRqVmFPamRPNXNoVjE1R1Nwc202Ris5N2orazllckxzSTZDMFg5NWlaL0NMZkhxTS85Z2ZtRTlFMjhkUnV5UFh6RDRyeXU4NmxGdE1VTW56U2NsOXlwRzl6SXliZlo4Vm44WlpORGJmbURUZ1NqNmRvc09BYlpWYjc3QmpOYy9aL1NvNjRpdDNNbkl1VTJ1azVvZmlwajk1SFBNLzI4Tkk2Ky9pbFNkQ3dkUXNId0pVOTc5anR0K2U5MFhUcGRiYWNVRTladWhpaUtFa1BEcWJZMXVCWTFHVFl4ZUc0aUtCc0RON1hidjBHZzBnMDduSVpSK3Y1Zm45M3A0Y2ZLbHAvb01ZSE81V2IvdkNNK1BHWWF0L0JzbXZMYVJqZytONGRkTURxNVU3R1hGOTNIc2VXa2dGaGxtM3Q2ZlVSLzRIN2RNUnJjTTVNTzc0ZnZROHl6dE8yQnByOE9naVNDblZ3YVpBYTFDUTg4ZUdhakxkMk1vajZSdmRzYXBMVWVObGdSelN4M0NvRG4zTlczNmxFeW1ENi9oczIrK3d5Wk1aQ1E0K0d4Zk5Ybjl2VnJibU50K3krRHNTNkRuSGJ6ODFuMXNPVHlKMUQwRjdFdnN3NnJSVnlBRGMrNGNTc2JUVzVsL1MvZVR0bFY5M0lxK2JReHBxUjNvMlNYdHRQb25HeXpNZStRK1hwNzdERU5tM0FkRjN5T2Z3bXBLNmRpSmxEZzNXbTAwdWRrWklhWm1hcWRMU0kycFJ4Y1JUVjUyeGtrMHRlRHZXUnlEK2wySzgwTXpTcS9MR09DcFo5VVgzby9NcU4vZVJQRzNCeW42c1piTXJxbk1YMWNFRENhaFhRb0o3ZlFZTlZxeXN6UElhYWFZRGh4MkhTUDdaTURsMGN4ZU9JOFNKMlNjVmVWZkRVdlg3MmZtcTQrUWw2SWhyOU00bHZTZnkyNzdyY2dmZjRJemF4RFRoM2l0Z3dsWGYwaGg4RWZ0eHpMS1l4SklhVVVEdnZmTzIrbmZQWWIrS1dOWStKdEZGRDk2RTJtKzQreUdOQmJQSE9NMUVmTUFGSmEvL3pGVEpqL0haUjIxZTFveFF6MkFSNUs4NENZRUlDSDhhN3FvMVNvaUl5SlErM0kvaFBjZ0llcnE2ajZMalkwOTVTTTQ5bU1KOTd6MUZYUHVHRUhucUZQWjFHcjBhaGpXK3pKU2paRmd6S0pQMUhZK0s3Zi9xb0hOV1ZPTHJVMGNadC9qc2NUR0F4ZDJVVEE1UW9mRjNOSTBNZXBQSGQxZHR1QkpacTdkRDhnODhkeDhSbmVKUFBtSGJsYytnMmEreTVCQnVTUkVxYW15ZTNDNG0vdy95V2JmdkpLTldHSmNWTlkwSWxkV1lqMzREU01mUEJRNHJuZkhTMUhncEhSWTk5NS9QNDhzV0VIMnNOZlJKWFprL3YrYnlzQ09KeS9aUyt2VUFZQWhOMTlQRW1EczJ2SENUd3BaalZvR1dhVkMxa2hvWlJYMlJnRTBNSFBLSTJ3UnFRenFab0hqVlRoY3B4ZUJOeHQ5OTYxV29WYTVVY1JaK3hLb3JEVmdNZnRJRWlOTkpFVFpLRDhPNnRvYUxPWW1XditFMkpqUWp6aWd5RklyRjlXUWxPQTdOaWFXQk1sS3BSM1NmRjFPN3BqY3pPL1ZTRVZWSTBNc0pnNlVmRnZjR3FnRis5ZUVkME9TWmJRbzFEYzRzTmtkaFBqWUFHWFNwUHQzckZqeFppMFFjNkw3cnp0Mm1EdVhiV1B5bUJ1NXlxSS9qU2Vtb1dPaUFaZmJIWGdNTG8rRVJ2NTFMMDJualRWaFBINk1LZ1VzTWxSVUhiM3dQcjdxbzZ6LzZQTVd6dmNocGxReTIwYWY5Tnl4azZjemR2SUo3azJqeHVFS0RRYXNlbWNESXlkT1pkNklUb0NMNGkzdmhPd3ZxNm9DektCWXFhalZrUlFiUVhKQ0FnbFpiZGo0MU5nVGFEZ3FaTGVuUlVUUzFMNHpDLzgrQy9Dd2JQNC9tUG5hRnJiUEhuNWF6NlJydHpQTWFKSWxaTVZ6eGdFTW03V1dLcnNnMVhJYXV0eVBlMW40VFJ5bEh6eUlBU2paK2paUGZGRVNkSUFFMHBtdjd1YTAyeWcvM2toeXV6aWFrYytqVlNrNGdvZFFiU0FoeGtaRmxRdFNOR0N2b2JMZVFGSWIwSm5qS2QzV0ZQTXRxemdLUWQ4UlhXSThDY2NyS1ZjZ1RRNTFRNVVmcVlFVUU5UldVeW1NbUlNZ1EyNEJoaEZZekJHVVY5VFVQdnI0TTJVbkFMV1FraXJaVjNFUXFkUFNScXNSR2hWZWpTMUl4TXFWSzkzTGw3L3hzU3pMdzFwM2w5VXgrYVgzMFNWM29mcnc5N3grR0ZTUkptN3Q2YVg1L3VTclBSUWZxNkxSMDhBN254U1NudGlXNjlManViSFhwVXdvK0M4OWpUblVIejdJNTRxRnY3YUwrRlVEbTV5UXdhM3B4NWp4d21ZbVgySGlzVGMraG9TK1hzQ3BQY3EyZlljcFBmQWo5cU5xOHYrN0cxTkNNam1kemkyVms2bFRkeGIvcmZzNXY3ZXM3dDBvZXVrZEZtZnBTVTF1eitBZUhVaHBsOENLclZ2WTJWVmkzOGNmc1A0SEpVUWpYL25HMnd4T0dVM2xwKzlUMnFFZmZkdks2RTBEU1Z2MENOTmVhOGV0VjZSUVdWTE1IbnNiSGhxWjR6MHBxaDBEMmg5anp2TWJHTnpKek1DQmx4T3JobFgvWGdYdHV0TFpyR2JuOTVXa1oxNXovZ1pTbjBoWGRSa0xWbnhFWHRzb2NpN1A0WFMrNXh2ZlhNeVlkUjVzNzgwNDlSSndNZkVrT2I1ajJVZDd5TkU3ZU9LbER3bk5sVGVSMGQ3RG9xVWJxZXhtSmlNcmk3UTJwN1kzQ3plOXplVnpkL0h0Wjg4U3VveFNEUDE3UlBIVW9yY3A3MlVoTDY4UHlRWVQ0NFpleW1QUExpWDJ6djVzVy9VbXV2NVgwMU1QY3I5K0pEODlrK212cFpBVFdjV3JueDRoTjNod2pla003bGpMOXFJRzBqSkNOZnRGTHk4ak0vSTNiRisxa3VSckI1QitVa1ZVNXRiciszSFA4eTk5ZXZENzhtQXoxQjBDYnNLL01xV3ZSbFFvMURzRWtpU2p1RDJvMUdxMUpJUkFDQ0g1dEgvNTRZY2ZUbENyMWEzNzJSUVh4NTBxRXFNME9GeHVIQzQzamJLVzNzbGVNK09iSDBvNTFLZ21KelVlM0c3VWtkRmtKa1JqVGt5aW03NlJEVi8vd0k5S0pBK1A2RWNuZ3hwMnZkbXlqYmJkSWFuN2VabWpqZFhsS0hWSDBldjFaM1JlYlcwdDBkSFJ5UEtaaExKVkRPaVh3NEdkTy9qUC9xUGNjY3N3T3FVa2tkdTVMYmFLVWxaK3ZJZGFLWm9lU1ZHVVZsUmgxNXJJN2hnTWJGcDZaWGNtcWhVTlB5b3VnWnlPOGY4ejBOYVpVeGpjMWNnWFJUOVFyNG1oZDNvaUdkMnpFT1VIZVB1alFxSXU3Y3U5MTNTa1UzcG5VdHA0UDJEWFgzVTUvOG5mekE5U0lnditmQ3VKa1NyUVJISHpiM0w0OXNzdldmZkpWMVE2dFF6c2N4a2Q0dlFCTjhZMUEzTDQ4YnRpU3FvYTZOR2pDOUZxc05kVXNPbVRYZVIvZm9DVTdFSE12aldYaVBPMTZJMms0emY5dTFGOHNKamk4bU9rWFpwQmZHVFFvR2dNOU9tUlNtdnZhM3hTQ2xkMVR3a3lxMVgwNkpGT0pKRGNzUlBKTVZwMEpqT1hYWkxHNE95MnJGdS9sYy9MR25oZ3drMjBpemZUcjF2N3dBdC9WYjllVkpWOHo3ZUhqNUtZa2s1N2s5WVhYT2hJZWtKVVlNN2taSGNoMkRzVWJiWXdvTmNsTkhjazlMM3lDdW9yU3ZqdXgrT2tkZTFHZ2w2aVorL2U2S3UvWitYbUw5RWtaN0xnd1p1STFraWdNWExUb0N3T2ZGMUVqYjQ5L1JJYU9HcnF5ZzNkTFlIN1NoQlZMTmhWejIxOU9nYUNCeXZmZkk4YnhvNWllLzRXR2kwWkxIamdCcUkwVGM5T1p6TFRPejAwU2Faajk1N0VXZys5ODg3MnZZY0FKOUFJT0h6L09nR1hIK0JVc3F5b0pBbTFTa1pSQkU2M0I2Y2lJZWwwT3RudGRrdENDRmtJb1JKQ2FFdEt2dStla3BLeTdZSzhJUmU0cEtyMjI4OXhsKzhqTHU3TUtnOUtTMHRKU2twQ0hhNDhDTXRGS2tYZmZFZHFwMDZvbmNlNTlmZC9aT2pNcHhuZlBjaGo1YXBoMUwxUE1lZloyV1FZWk1ESnFCdkdNWGJSQ2thY0dmMGg3K1J2djIva0grYjlTQ2dIbTAyQ2VpUWFKU1NuSk9HUkpFbFJ5N0xRcW1VUm9kVVFFYUVSL3VDQmtDUUo0WTB2Q0VCSlRlMVlwQ2llUFVCbWVEakRFcGF3QUd6OWNBM2pacGVoU0pFTUhUV0pjZDJidWVFMUpsYTlOQ2ZFTDVqZXRST3haN2hna3lMRXZwRi9tSGVzbVJucXBtVzZoNUI4T1d6K0lLaEtrbEhKRW1wWmxnbnlzd1hBemUxMkY2alY2bDhkc0xralluREhwa0ZDNHBtWnNMVVNJajRWTk9GbHRjSnljY3JFT1M4dzhRelBtZmZhVzJmY1RyMnQvdE1UK05ZOFNKSVgxQ1FFa29Ra3k5NElzMXFMU3FORjFrWWdSK2dDNlI1SWt1UlQycnlJYUxWYXQ4VEd4azc1dFEyT1IyZkNKVWRCd3BucHhvMlZMa1Q4SlJDbUJnOUxXTTZyZkZPNmN4K2hhUjV1d0NOSlVwREdKaUZKc3BCa0ZaSktnNnpSSW10MXlGb2RxZ2g5azhiV0xKOU5NWnZqdHltSzV6RFE3cnplUmZidld2N1d0bnQ0ZE1NU2xvdFFoQkRsbDEvZXU3U1pDZXJmZkpSRlVnaHJyaXpMZ1UybFVxRlN5VjVnazJWWmVEeWVZRk5VQUI2UHgvT0JTcVdhY0Y3dkpNeTdGcGF3aE1VbkRRME5XMXZUMW9LMkVCNDJXWmFGSDhPOHdPWUZON2sxamMyUGpIVjF0dndUZGFER2F1V0hZN1lUc3dONFF0T2ZGVVhCN1FuZGZ2V2lWTE5pOFNJV0xWckUrdTFGdjdyYnF5a3ZZdldLNVN4ZmR5RUM2RTRLVnErbTBuRVJ2TjJPS3RibmYzYkMzZnUyYjZTd05MVFdldmVXTlJSVnRGNGxXbG04azVYTGw3Tm15KzVtODlQSlp3VnJXYjVzR1h0S203aDRQaXRZVDVYemYzUHJCdzkrOTFVelVITUQ3bEF6bEJZYVd4T2dxVkNwMUY1ZzgyOFE0RFlTZ0JJYkc1c1BIQTU5R0RZZWVQcFZybCswamo4c2U0ZStUNjNpdjVYK0F1UkcvcjdzYmZMKzlnTHBjMVpTRmpqSnhVUC9lbzIrODN6YjR5OXo2Y3dsZkZRamZ1VXpWTVpnTUZDNjR5MFdydDd4aSt1OXUyUUxvNmM4MWZwTzZ4NEc1VjNQOXFKU0tpcHJPUCtmS1R0UFRIdVF3b3VBTzJIZE0zOWkvYjRURTZXdFgvd3dxN2VYaGZ5MjhkVy9zKzJBdFNWR2xtd2lkK0NkN0NrdG83STY5T0d0bmZkNzduM2liY3JLSzdEYW0ybzdiSHZXTUgzK3h2K0pHZHFqUjQvdmZlRFYzQXh0S3FYeUZZWUdZNWNYMEpvMnRVK0ZrNXJTUGlRaFNTaENDQVZRUEI3UEJwVktkVmZUdTZyaG5sRWp5R3p2TFJOWnZmNDk1dVIvellaYmV3TXFCdWZsY3RlZ0JnWTkvMFZRbHpVOCtjRHZBMytWRjMzSnlNM0g2V09TZnVXNFptTFlyV1BSVlg5Q1lja0p0SjdLTW1yY09sS2JNNlFvVHNwS3k1QU5acExNb1RXUURsczE1WlZXekVuSkdIWG5LYTlPVVhEVWxMRnArMWU0M1c2UVpkVCs1R1JGd1ZxMGsxTExRT2JObk82ZFdLZDFUVUFHZTAwVmxUWklUVzY2WjZlOWhySUtLMG1wS2VpQ0wrWjJVRkpXaVNYbERKZHVWSnlVbFphamo3VVFhOVNGZEtLeXJCUzMza3hTck9IMEFONWhwY29HbGxndFplVTFKQ1ZibXU3WE4wNDZjeEptUTJnNzFSWGxXSjFxVWxJc0ljOUhBUlM3bGJJcUc4a3BTYUdKdmM1UzVpM1p5Y0xkcjdSNGVCV2xwZWpNTFdtL0ZMZWI2VXMrUWEyV1cxaEp4VHQyWU9oN0M3T21UME9XMVNIbmJOKytnM0ZUTmpONWNHaCs1c0R4azVpYS9RQ2xVNGFRY2dGalpYYTdmY3NKZkd1ZUlQK2FBZ0VmVzhBTTlXdHNhclVLdFZxTjJtZVBFbXlTK3NLakN1Q3BxYW5aRUJjWDF3UnNSSkRadnFrVUtpVTJCc2NSZjZHem1zdlMya0Z0eVVsdjRLMmRleG1XZlRVWGRYeFJzVEYzL0EyOFd1Z21SVjlEbGJFdkc5YzhqMFVIOXJJZERCNTBHN3IwbnNqV01tTDczOCtLeDhZQnNPWFZ2M0RYM1BWa1phWlFldUFBTTVaL3pxZ3N3em52M2hPVGg3Tis1dy9ZRGxReGFNaDFxT1ZFRnE1ZFJtY2RQRFp4Q0p0Mkg2Ym1ZQTFEaGx5SG5Ib1ZHNWZNUENXNGplL2RCbjMvQ1d6TjM0SmVkakJneWdzOE1TR1B0Zk1mWk9xQ3JXUjBObE5VMnNpcmE5YlROODJJclhRN2d3YjlEbDE2Rmc2SGk1b2dqcUV4bVpHTVd0WEFxSzVRczJNSlhhZCtTc1dPRndFNHNHMHBvOGMvU216bkxKeFZKUXlhOWlLengvVEdYcjZMMFNOdW84cVlCbFVIU0IzeEtNdG5qejFwdjhmbnRxWEVuRXZwcnMvb210c0hhL0ZYOUp6NEFnc21EYVJ5M3laR2pya2ZiWElHTlFmM01YRDY4enc1Y1JCUXdjaXNmbFFtZENhV0dnNVVtMWk1L20yeUxEbzJQbk0zajZ3OVFpeU4yS3VLY0diY3lmWVZzd0x2UWtuQktpbzYvNGFRSVhWV01XbkVOV3lyU2NDRUE3ZXRtc0ZEZklwejRUcEdUbnVXb2wzYm1MNzhXNllPU2ZhZFZNV1VrYjlqNThFREZGZktEQm15R1V2dUxTeDc3QzVLdHk1andwelhLTnBWanFIOE50WTlwV1BjckpjWTE5OWJGb214SjRNN2w3T3FvSVJwUTFJdjJDdXhiOS8rTDA5aWhnWnBiS0ZCZ3lZVDFHdUdxdFZxU0U1dWg5bHNscUtqbzZXSWlBaFpyVmFyWkZuV1NKSVVLVW1TVVpLa0JDR1VmVUlvb3NYbXJCUDNQZk9TV0xqM2FPanZOY1dpNTZ3M3hhRld6bkhWSFJKNXM1ZUsvUTJ0WE84Q2JFZU9WSWhEaHc2ZDhYbGZmLzIxYUd4MG5GV2JIejUzajdqK2p5K0gvUGJEQjArS3hGNjNpRnFQSW9Sd2lEOWZmNmw0OElXdFFnaEYvT2U1ZThYVjl5ME1IQnZjN3UyWHhZaFhQcmY2L25hS3hrYjNTZHYrOGFQRm9tMWlva2hzdGozNnhtZW43SGZEMTh0RlRLODdXdDEzL1BPWFJlS1Y5NXpSYzdpalY1UzQ1cjVuaE10Ly9RYTdxUDFxbFVqc2VKWDR2dFo3SDErOE1VUDB1bjJ1RUVJUno5N2RSL3p1OFRYZTlyNWFJV0trUlBIaGo5NXpiK2tXSWQ3YTcrdkxmeGVMeEN2dTlMYmpLaE5YdDQ4WHIzeFM1bXZYTFk0ZHJ4RkNLT0xSbTd1TCs1N045eDFYSlc3czFrNjgrNjM5NUgyK0lsRzh0ZDhwWHZ2ejllTDJmNndSeHo1WklycjlkbzRRd2laK2Yzazc4ZmYzOXdnaEZPR3AzU042dGI5VWZGSHJIYy9hMnFicnZ2Ym42OFR0dnZ2WThQU2RvbDIvZTBXOVVJUm9PQ2g2dFdrblBqN2ExTjViZjd0RjNQancwcEErZlBIR0k2TGp0ZE5FbzFDRWFDZ1ZWN1hUaUVmZkxBdzU1cy9YZHhCUGJ5aHRPVy9mbkNWNjNQRkVxL2YyeCtzN2lPYytMRzkxMytzUDN5aHUrZHZLQy9aZUtvcW5DQmdOREFjR0FibEFOeUJWa3FRRUh4WkZ5cktzVWF2VnFvaUlDRGs2T2xveW04MVNjbkk3MHRQVHljcktvaytmUGx4NzdiV29aVm5sanloSXNpd0xSVkVrWHhEQmI0NTZuRTduZTFxdE5wUjBUWEh4cjdjMzRFNi9nbnN5VHI4OGFkdk8vY1JmMnBXdU9pNXEyYlhyUzNyMnZRYWpES0Jsd09BOEh0dTVHeWIySmJOM2Z3N00vVFBqNVJMNjV2Vmw1TWloK0lta0JnenN5NXk3YnVUQXlPdm9PMmdvUS9OT3pzbG02WDhYNVJWMy9XeDhqbVBHamcyWVhqcWRqaDNiQ2xEMHNHanVYN3pUcXFxSVBidDBLTUN1blhzWU9NRkxHbURLR2tCUDg2a05YdmVCYlh3bTkyWnRYbEtnelZpVEVhaGt5OVlEeEpvL1lNYU1ENzA2amR0QjRlNFNocWQzUGNrVjlSZ01hakFhTUJsaU1SanRPT3cyc081ankyNDdjc0hyek5qcU5SV2Q3bEwySExTUm5hMWp4Nm9uV2JqaVF5cXREbW9xaWtrZGRYUGdpajF6ZTZNSDBDV1JtbEJEVlEzZ3M4cXJxNnN3SllleWdSVHUvcEs4QVhkNnRUcGRNZ1B6MHMrN1Q5TVlhNktxN01JNU5HdHFhcmJRc3RJZ3hCU1ZKRWtKckJzYUVnME44cTJwMVdnMEdxOHAyc3djRlZKVHRxNENlTXJMeTllbnBxWStGQlR5Wk1WN0cvaFk3c2dyMTJWdzJyWEhTaU1yc1NPZ3BBQUFJQUJKUkVGVWR4WXpjc1JGbHVKeGhzWFpDYjNIc0c5M0xnWDVCYXhlK2plZVhMR2RmV3ZuSWdNVG5ueVgvcnUyVWJCbEN6Tkc5V1Azd285NFpNU0pDMFFxZDZ4ZzhMMS9iL0g3dU5tdk1XMUUxb1VQcGhoYmtnOVkwbm94WW9SLzdZWWJHVFU1OXBTUHpPdlY4enJ0RkVWcGV0RlBlcUtlQVVOdm9uZUM5NjhSSTI0a0pUM2w5QVpQOWpGRWVPRVRVRkMwWmdhUHZKRVUyWCs5bTBucnJLZDh5MEltTFBpQy9JM3YwVFhCd1BvbjcyUitSUk1VcWVVVCswV05KaE5XcTdXVlBzZ1hkS1JzVmlzbWsrR0N0YmQxNjhkZk5RTTExMG5NVUJHYXR4YmtXL01CbSt4UGFHc0N0d0JWZUNBNm1wYldhWitpS0p0OHNRdmUvL0JEL2wxbjVzV2JleEY1QnMvNzZBL2Y4SW1uTGNQVERCY1ZybGxpMjFKY1hCTHlsYzNPdm96ZDIvNkRWUUZ3c21YamRuSnllZ1ltbFNFaGxSRzNUbUR4TXc5VHNXOC9EcDhEMldxMWs1N2RuNG5UWnZIUStMN3MyWGZncEcyYnM0ZXpmdDE3ckd1MjNUVzQ2eW43cmRYcFVLcXJmSDA4ZlZuMnhCVG1MczAvcldPeit3Nms2c0JYSkdYMkpqYzNsOXpjWE5KOFFZWHMzSjRVYlBHbWt0UVVibUZYVlZOSFlzMUdTa3U4a2NFZDIzWUVucTA2dlMrOWxjOVlHWWdhS3RUVVdJRUVCdlJQNW1DRk05Qk9ka2JuVm9IMjlGU2FUQVptMkNtMXhnYXVsNW1laHRFZ1UxRmFTa0xYWG5STk1JQmlZKzI2Z3RPK2JFNTJML2J0Q2FXS3o4NitqTzFidG5sWGczT1VrYi85NFBuM2QrM1pRMDUyemdWNVA5eHU5K1lSSTBZY0N3STJGeTBUYzROeTE2U1FaTnhnMzVwR28wYXIxYUpXcTlWK1VQTkZSMlVoU1FyTnpWRzd2V0d0d1JBMW1JWWpQRjd3SFZaZEJBUG43dmUrQVBGcGZITHYxUURNWHZ3cTcxYzRxWE82dVhIT1MyVDJ1SnpYYnZScUJtOS92cDkrdlhwaGtpOHFYQ056K0FUeVhyNkp0SzV2a1RYMFB0WStOWm1VUVJPWnN2eDljbkt1SWtWbnBjclluNDNqdkdiWFp5dG1NL0daajhuc21rSng0VzRtUGZLUzEzVEJ6VVBEZTdCTFNTZko2S1N3eE0zeTlZTlBybTlvOVNRbG45M0xLNmNQWkdydlo4bkp1Z3lqSVpWbEJlL1E5VFF1dFdQVFcreE83OHdqNDA3Tk1HL01Hc0hpU1I4eHNHZDN1bWFtVTFWeWtLNmovc2FyTTBkeDF5Ti9aK1hnM3pGZzY0czQzWUxrSUZOMDNNUzdHVHErSDV1eU9tTXhSeURqVy90Qm5jU2k1ZjlnekxpcldOWTFFNldxakVGVG4yZm1tTjQ4TlA4VnhvNjhuWnpseVZpMERnNldDWlp0M1VyT1dWSGc2Wm4zNmt1TUhuTURhNS9xaXM1WlNhbXRIZms3VjVFMWREem1KNjRuYi9DbjZPeFZXQkxTVHZ1cTZZTkdZcGgyR3djZGorRmYreWRyMUJTR0xiMkduTHhyaVpWZEtPWW1tcCtYWjkvQnkvbkZGTytyUUZkeUU2c2Uwek5qNFhzTXl6S2UvWVMxSDJEakxpUExsNlZma1BmajBLR3lUMW94UVYwbk1FT0ZKTWxCMGRBbVRjMnZyV20xV3FSdTNiclIwTkJBUTBNRGpZME9xYkhSaWN2bGtoUkZrUlZGa1lVUWFrRDdmLy8zbVBIaGh4L2VCclQvSllOTVpXVWxUcWVMNU9RenF4VGJzMmN2blR0Zmd2WWMxNHJXVkpWamRXdEpzWVMrWFU2N2xmTEtha3dKU1pqMDJoYm5WTnRsVXBJdHFIOGxId20zdzBaNVJSV3hsaVFNT20zd0Rzcktxa2hJU1ViYjdGNXRWZVhZTUdJeHQySUJLRTdLUzh2Um1TM0VHa0lkdXRVVnBUalFZN0dZejRHQnAxQlJWZ3E2V0N6QmFUbUtrOUtTY295V1pFejZNMHZKV1Q3ckpuYW5QY3dUNDN1SHRGTlpYb2JPbEhSYXRPNC9SYll2bWNhVHBUbXNublArWFVaQ2lMS2JiaHI1NTNmZmZkZE9FMFdSRGJCSmttVEh5OFBtbENUSkxjdXlJc3V5b3RGb1JFU0Vsb2dJbllpTWpDUXlNcEtvcUNnTUJnTkdvNUhZMkRaSVBYcjB3RzYzMDlEUWdNUGhrQm9iRzNHNVhGSXpqallOb0d0c2REeWkwV2ltaG9FdExHRTVmNkpZaTFtNnFZVHhvd2IrVDlvdldQVXE2VVBHa1dJNC8xOU5xOVg2VWt5TTZRT2dnVkR1dFhwSmtob0FoeVJKTGttU3ZOeHJhclh3QWxzRU9wMFgyUFI2UFZGUlVVUkhSeE1URTBOY1hKelhGUFdib3lxVlNxaFVLc25qOFFoWmx2QjRSSENKbGZ2UW9VTnIwdExTcG9hblhsaCtQVkxEOG9YTHFHN0ZqMmpKN011b0FSZCt6VmJabU1iNFVXbi9zeWN5Y05UNEM5Ylc1czFiZGpYenJaM1F2eWJMa3ZDYm56NnNDakpCMVdpMUdpSWlJb2lNMVBtQlRVV1FyODBYSFpYeHhrWUQwVkVsUGYyUy9XNjNhN1VzeXlQREwwUllmaDFpNHRaSms4T1A0WDhnalkyTjYwYU1HRkZGcUYvTkJiZ2tTUXF3ZVJDb0RRMWw4UWpHTHI5dlRhZUxJREpTajZ6UmFBSUhCS2QrQkJmRyt3SUpIcnlWQ0crSGh5UXNZUW5MVDVXaW9xSlBXdEhXZ29NR250WW9pcHFuZUdnMEdqUWFMVnB0QkRxZERyMCtBR3lhUUxqVXIrS3BWTEpvenZZQmVNem0rQUloeENmaFlRbExXTUp5dHVMeGVIYjA3SG5aOXlmUjFscXdlYWhVVFdhb0Y2dlVxTldhRnRxYXdSQ0ZXcXZWb05GNGJWU1h5NHVBYnJjYmIwV0NncUlvQVhBVFFuZ0FkMzE5L2IvZGluSmxyVXVtWFJ0RDY1RTVqd0JWc3lKM2o0dnk0ellpb2d6RVJZWXB0bi9Pc3J0Z05Uc09WSUl1aVhIamg2TVBQNUt3bkVNNWRLanNQeWZSMXZ5TXVVcG9zYnNxeEFUMTQ1YmZ0NmJUZVFNSjBkSFJxTFZhTFJxTkZyWGEyVW9Rd2QxaW9SZkFrOUlsOCsxTEg1dzdNVEhDazFudWl1VHBjZGZUSnlFU0wyM1JPdDc5cm9wS3hjU1d2OTJDdnlUM3lLRnZHYjlzRzNHV2VJNVhIcVZ6cnp6K09hZ0xjbmlNVzVYeUhUQm5DeXlhOGI5cFg2dlRZNUFybWZyUUc0d0lBMXRZenFFb2lyS3ZYNysrZTFyWDFwcVcxc08vWUlza0NWbVdXZ2thK0UxUXY3WVdpY0VRaGRFWWd4d1JFWUZYYS9PYXBFM2dKdnUwTmptUXJPc0h0dVBsaDl6NUQ5ejA1dW9IYitlaGpBam01SC90NjdLWHR1aTllNjZoK1JyamIyejlsTzU1VjdQczk4TjRkOUsxZkxwNU8xODFYQ1FENmZ2WFdnVmxGYUg3N0RWUVhFcklTdWVLQXJaeTJMb0wzRzdPYVBWdkJlOVVLU2tHdXpPMEE3YnFsdTJmU0RMeWhqQjI5RkJPRlBGM082d1VGNWRnZDdic1hFMWxHU1dsNWJUWXBUZ3BLeW1tdk1vYWZyc3ZZcW1zck54WVZuWTRHTlNjQk5ZTERTbWhVa0sxdGVDZ2dkOEViZExXb3FLODJscWJOaWJraUFnZFdtMkV6d0hYbE1HclVxbUZ2eUloS0lqZ2F3ejM4dVhMVndMZnBjVEc0R2dNcFMyS2kyeVpRR2pRYWxINWw3T1hBTFVXZytyaUdNaitGcGoyRU9RTmd1R0RZSUd2T0czeERPalpINlpNZ0t6ZVVGVHQvZjJoTVRCdU5wUnNnU0ZEWVBBWThPMGkxd3k3ZlJ5RWUxWkF6N0ZON2VRdmdBRmpZSEF1akJrTDJTTzh2ODhZQ1VOR3dzZ3hNQ2diSmkvOGlXYnEybWZvM0xrWFU2YmNROWZPUFZpNW85UzN4OG1URTYraDc5QTdtRHI1OS9UTUhvNS9qNzFzQjMwekxtWDg1RDh4ZnVTMWpKbTVOUHlHWDRRaWhDaWVOZXZSTDA5aGhyWW9lUGVWVFluZzlBNHZzRVVRRWFIekpla2FpSW1KSVRZMkZuVmtwSTZHQnE4NjUzUnFmTDQyZGZNSWFiQko2Z0U4ZDk4OTBYcmI2Qkd2di96cGQ3TkhYenY4bERkMHgzWFg4TURybXhoZnZKZnFZelhjT2ZwYUxybFljbDNkRXRXeGdqMCtabWFIQTRyWHcyTmJvWEFYbU5Td2RpNDg5QlNzZlF5ZVhBa0gxOENJRlpDLzRzeWEycmtOZHU2R0RMTzNuY0NISlJOV3pZR2FIWkE2RVo2WkJHZVZ2NjVVTW0zUy8rT3h0ZDl3YTdhWmZhdG1NMkRhWTR6WXZoaHR6VzRXckxLeXBlcHpVbVZRbk02QXRycGp6VkswQS85RS9zSjd2UkRvZEliZjhvdFFqaDQ5K3Q2TEw3N1kySnEySmttUzN3ejEwRVQvTFVJam9jR2dwaVVpUWt0a3BDNmdyWmxNSnN6bWVPVElTRDA2blk0bVg1dUdaa203d2VrZklha2ZrMmIrMzcvZDZWY1VudzV0MFpiUHZxQlNaK0dlcTdLNTUvSjJ2Skcva3dBLzVhOWRaTUd0UVpxVlRnZmJ0b0FlbURjVFpzeUFqYnRoVCtGUGI2cm5VQytvK2R2eFM1NnZPc2VVQXJwcWIzcjMyVG4vQ2ltMFp6QWcyOXRJeHNCQnFBcy9wMVFCak9uMFQ2OWt6TERibWZ2TUluYVgxUVRBTTdOM2Z3NnNlWnp4azJld1pQazZiSW82L0paZmZOcmE5MDg4OGNRWHpVQXRXR05ya2VMUkhOUmErdFowdmtpb0lWQjFZTEVrSWtkRjZkSHBJdkg2MnJUNG82U2hxUjhxRVFScUN0NElxZnVWK2M4Yy8vdUFqa3RQSFFCb1pPV24zM0xidFZmU0o5WEM5ZjM2MHMxVndxYVMrb3NFMk1Cb2FPa0xTK2tKSTBaNHQzSFRZTlVUWithelU5d3Q5eGxQVVBzY1lNbzVuOUVhT1phbDIvY3lmL290eUJXN0dOejdjcmFXZVRVekx4WFRSNHpJNjh6V3BYOGpiL1JNbFBDN2ZyRnBhMnYvK2MrbkhNMU1VTCsyMXVvU2U4RVlGRnhoNE5YV1d2cld6R1l6YXJVYTJXQXdvTmRIaG1odFRVbTc2dGE0Mm56Z0pua2tTWEt2V2JuaVRTSEUvcFBma2dwRGhJb2ZxbXE5TDZTamxrUDFFZ2JkeGZ2VjdqOEFEaFJDMXh6SXpmVnVLVUYxOEhvaldDdERnd29BQ1VZbzlUSHliUHZzUEhmU1lNYnNMcWVrT3FnWFNabGs2ZmV4WlZjVkFQc0s4bkZuWGU3bEpIUGFzTHIxOUI0NGpCbnpGaklpM1VGUmlWYzNQREVWVTFndUVtM3RtOW16Wis5c0JkU2NRZjYxVmxJOFpFSkxwMEsxTmIwK3NwbTI1bVUrVVVkSEc2bXJxNk8rM283RDBVQmpZeU5PcHhhTnhvWEw1VUt0Vmd1MzJ5MTVQQjZoZU1OelFnZ1VFSW9RZUNaTXVMUDJoVHJMYXp1bVhEY1BUa3hiTlBuNks3bDcxWHQ4c1RPZW1xb3Frckw2TURSbzdZU0xUZEtHd3N5dGtKTUZtZW5lS09iUWgyRHVPQjkrNUhyNWtiT3l3SkFFR3pkQ0xEQmhJdHcxQ0JaMkJiTUJPSi9VZHVwVVpzOGF5cTA5dTZBM3BiRmkrNGRrR2l3OHRlQ3ZqQnJaaCtXWmFlelpWODZDNWU5NzJWMXJDdW1mZXp2bWpDemttaEpxRXNZd0w5ZkwvWHRpS3Fhd1hBenk0NDgvcm4zKytVV244cTJGSk9RRzBSS0pscUFXRVdEMWlJNDIwcVpORytMajQ5SDUvQy9TYTYrOVNtbHBLUlVWUnpoMnJJcWFtbHJxNnF6WWJQV255L3FodmU2NklmcDE2OWF0a2lTcDkwbnZ6dU9pdkxhZWlNaW8vMW1DN3MrTjNjTmhnNHBxc0ZoQWQ1cVhycTRFUlFkbTQvOXVvcm9kTnNvcXFraElUa0VmbktHdE9LbW9LTWV0TnBHY0VFcHhmVElxcHJEOGVrVlJsSjJabWQyZjJyOS92d012aTBlOWI3TUJkaCtMaC9OMFdEd01CaStRbVV3eHhNV1pzVmdTU1VsSjRaSkxMcUZIang3bzlkN1BwYnBObXpiVTFOUlFWMmZEYnErbm9jRkJZMk1FV3EwVGw4c2JJVldyVmNMdDlyTit5SGc4bmtDeUx1RFpzR0dqMDJxMXZob1RFM055WUZOcFNJbzFoVWM2U0hRR1NEMURyU3MyNFgvZmI3WE9RR3BySFplMVdKSlNXejFIcXplU21tb01EL3BGSnNYRnhlL3YzNy9memFram9RcE5xN3Y3dExYbXZyVUlINE9IVjFzekdyM2FXa0pDUWdEVUFHU3oyWXpKWkNJNjJvQmVIMFZrcEk2SWlBaWZyNjExZjF0UTFDS3crSUxKMU9ZOVJWSFdoNGN4TEdFSmkxOWNMdGVIbDF6U2VYOHpRRHVsYjYwMXY1cEdvdzNRRXVuMVVVUkhHM3pwSGVhQWJ5MEFiQWtKaWNUR3htSTB4bUF3Uk9GTi80aWdxU0pCN1VzQlVZa2dDdkdRRWl1OGl5NjRLaXNyWHcwUFpWakNFaGEvZlBubGw1c0lUY1lOQUp0UFczT2ZYRnZUTktzSGJTcDBOeHE5eWJnSkNZbEVSVVdGQWx0U1Vsdk1aak50MnBpSWpvNG1OUDBqSWtocjB6U1BrcmJRMnRxMlRkcnVjcm1XaEljekxHRUpTMzE5L2ZJcnJzZzkxQXpVR2sra3JmbVgxR3RlTnVXbkpEcFJla2RTVXR1V0hwSEl5RWdzRmd0eGNYSEV4TVRnVGYvd0p1MUdSR2g5S1NBQmN6UzR6RW8wcXlGMVM1TGsycnQzNzFMZzhLOXRrREl6dTRWcHdjTVNsdE1VSWNUaE5XdldiRzFOVTJ1bXJZWGtyYlVXQ2ZWWEdQaTUxcHFuZDBSR1JyWUVOb0NrcENUaTQrTnAwNllOUnFPUnFLZ285UHBJSWlMODRLWTViVi9iWlpkbGYyZTMyMThJRDIxWXduTHhTbVZsNWNxeFkyOC8za3hUTzZHMmRpTGZtdGNFMVJJUjRjMVpDdzRZeE1mSGs1U1UxR3I3M25YSXRWb3NGcTlKNmcwa1JLUFg2d09CQkswMklsaHpFNG9RT04wZTRmWW9RaEZOckI5K2RPN2FJM3Zwcy9tRm0wTTRITndPM3Zwb0I5TlhiT0x4L04wY2FSVGgwUTlMV0g2RjRuYTdQODdKeWZuOE5MUzFrS1gxL05VRnZsUVBYeVZVUkZEQUlMZ2UxSXpGMHZhRVZsUWdBYWw5KzJUOGdZU1lHQ01HZ3dGL0hXbXcxcWJSeURqZG9OWm9VS2xVSUFUQzF6ay91QjA2ZU1DNVlNTi9sd1FEMjdMMzEvTjZpWk14ZmJLSXN4NWl3cXJQd2lVMVlRbkxyMUFLQ3d2Zkx5czczTHk2b0JGb0RBWTFINkFwd1RXaFRWSFFKbTB0dEI3VUdBZ1l0RytmZk1JK2hGUU9KaVcxSlQ0K0hwT3BEZEhSWHBNME10SnJralpwYlRyTWJhS0ZJVEtDQ0sxR3FGU3k4R0pid0ZiMlNCS3VnMC9kOTdITDZmeVg5OHFONU84OXdsM1g1cEhUd2NMRUcvcmcrcWFJcnh2Q2t5QXNZZmsxaWRWcWZiVlhyNXdTSDNnRmcxcHd3WHR6dmpXZnRxWnFSVnZUaFZRWW1FeCtFN1R0U2ZzUkFteUppWWxZTEJiTTVqaE1waGlmU2VvRk41MnV5UnoxUlVtRldxMUdCTkdMTkdsdGtodHdiaXNvZUVVSThTVjRjQ3NDbGNyWG5LeENKV3dVVjd2Q015RXNZZm1WaUtJb1h6My8vS0xtQVlPQWIwMlNKQ2V0KzlaOEpxaEdOR2xyVFdWVDNweTFhRXltR014bWI4QWdNVEh4OUlFTm9GMjdkaVFrSlBoeTI0d1lERkdCSW5rL0E0Zy9rQUFLaXBCUWE5UUJJa3BKQ2pCZ3VzZGRON1RpK1BIakw0Q2VQcDFpV2JXOWtLTTJPeDk4L0NVL0NJbUdSbmQ0Tm9RbExMOFMrZmJiYjFmUG1ESEQxZ3FvK1RVMlA2Z0ZwM2kwV0hHcWlibkRYK1FlNVZ2aFBaYUVoQVRhdFR0MU9XUUxZSXVKaVNFcEtRbXpPZDZYMjlaa2tnYURteXhCbmNNamRIbzlHcFdxMWFSZEpNa1ZGMmZlNEhLNVhyejd4aUYwNXdpVFh0L0FwMG9pQTJKVjZIVmhUcTZ3aE9YWElEYWI3WTJ1WFMvOUpzZ0ViV3ltcmJXYWpOc1VNTkNJNXFBV1d1VHVKWkJNU2tvaUppYm1sUDFwRlZuYXQyOVBkWFUxVnF1Vit2cDZYeUY4STA2bkU2ZlRpYU9obmlPMlJvd21FMnFQazhaR0JVVlJFRUlJUlZFVUVCNGhrQ1NRa1NUVko1OTg4dkpWVjEzVmE5cE52OGtHY05lVmMvVzJhUDRVSDE2cEtpeGgrUldZb0x0ZmZmVzF6YTM0MVlKQnpZVTMxOVVqU1pMU1BCbTN5YThXV2pabE1FU0hGTHkzYjkvK3RQclVLckJwTkJxU2s1T3ByYTNGWnF2RGJyZmpjRGk4bEVhT0JyNCtWb2NxTWdwSnVHaHdLOEl0SkVtdFVna2hGRHdlOE5JYVNZb0FOd0xYMVZjUExOdno1YTVGWDNxaUZpZHBGZDc1K0hPeWNudlRNWXhyWVFuTEwxNisrZWFiMVE4ODhJRDFKQ1pvaTJSYzM2cFRMZEk3dkg2MWxsSFF4TVFFa3BPVDBXaE9EelJPeUtkcXNWaWFKZTVHKzJwSkk0bVBpOE9randDVkNrbWxRcEs5cUN2TEtpRkpDQ1JKU0pLdllsL0NKVW1TTS9PeTdFMmxKU1hQcmk4NlRNL2VmWGw2Y09md2pBaExXSDdoVWx0YnV5UWpvOXVCazJocmZtRHorOVo4QVlNQXFJVXc0bnFqb1A1YTBPaVFSTnptaGU1bnJMSDVKU1VsSllqU3lPNmpOR29rUFZueGNiYlpxSyszMDlEUUlCb2JHeVdQeDROYXJjYmo4UWlmQkJaWmxpVEptVmhUK3RLTTMvKytteXpMZzhKVElpeGgrV1dMMiszZTh1aWpzejl1Qm1vT21uTFdUaFFGRFRaQmhaZTFJNWc4VXU4elFVMCtFOVJDU2tyS0dmWHRwQXo0VVZGUkpDY25rNWlZNEV2Y2pjRmdDQzZVYndvbStHdTdWTDVBZ24rcFBvSXFFdTY2Nis3amh3NGRlZ0VvRDArTHNJVGxGeTAvZnZycHArODkrK3l6RGEyWW9JNW1KbWlnd2lBMFlLQVd3WnFhdjhEZFlJZ09MS1BuTjBHYnMzZjhKSTBOdklHRTJ0cmFnTllXOExVNXZkVGhicmNidDl1TngrTkJVWnFDQ0VJSWZPQW1DU0hjZ0NSSmtweWEydkZ6cTdWMlFYUjA5Tnp3M0FoTFdINlpjdmp3NFdWOSsvWXI4NEZYTUtBMU4wSGRRUlVHb21sUmx0QmdnYi9BUFNySzRFdnRhRU44dkRlMTQzUURCbWNFYkFBZE9uVHdSVWh0TkRRMDBOam93T2wwNG5JNWc0RE5qY2ZqRVI2UFJ3b0dPSi9XSnZsTVVwY2tTU3FqTWViZmpZMk9OSzFXZTFkNGlvUWxMTDhzc2Rsc2J5UW50OTlGYUdwSGN4UFVSV2paVkFzVHRJbGpMYmpBUFpTT3FFT0hEbWZWeDlOYWpDMDZPcHIyN2RzSDBSc0ZNKzc2cXhMOHhKU2FVNW1rVHFEeHd3L3pseWlLVWhDZUptRUp5eTlIM0c3M1I0c1dMU3FnOVFob1FHTTdtUW5xQnpXdE5xSlpkWUdCbUJoVGdJNm9mZnYyUkVkSG4xVS9UenREdG4zNzlsaXQza1ZlUXJVMnYwbnF3ZVB4NFBFb0p6SkpFVUpJUHRYVU9XellzUEx2dnk5K3ZrT0gxQTZTUktmd2xBbExXSDdlSW9RbzNyRmp4OW8vL2VtaHVsWk1VQWN0QXdhZUU1dWdYazFOcDR2MHNYYjRPZGE4QmU3SnljbG5aWUtla2NibWw0NGRPOUt1blRjRkpGS3ZCMDBrK3Fpb0lHTEtDTFFhRFpJc0MxbmxYMGsraEcwM0VFaVFKTW5ac1dQYXpwcWE0OC9SY3ZuTWkwYXNwWHRZdjIzUHVibk85bjBYcE0vYjFxK2d6UHB6ZjY2d2Z0dUZhczFPd2ZvdE9IOGhjNlc1ZkxacE5RZXJUL2tLdW91Ly91L3lmdjM2bHpYVDFsb3pRZjJKdUw3cUFxOEpxdEg0dGJWUXY1cS9aS3BObTFqaTQrTnAxeTZKamgwNy9xUjdPaU5nMCt2MWRHaHY1cTFQQ25uOTZ6SStLU2xuMDNlVk5LaTlOcklrUEpSVTEzSE03c1JxZHdpN1MwRldxWVVzUzBLU1VCVGhYWXRVQ09FV0FwY2tTWTJ4c1hIdk5UUTBQUFZMQWFJbkpvOWtXOW01STF5cUxNem5tWlZiZnZKMXlqOWJ4U01MMTE2UWUxcncwTy9aOFRPUGExY1d3ak1yVzNsT08rRGVlZWUycmQwcjUvRmt3VUcwdjVDNTBseGVubk0vMjRwdEp6M20yTEhxRjlONzlOMFRwS2s1Z2pkSmtocURRYTNKQkpXRVNxVVdYbERUaWhQVmdYcnoxY3kwYlp0RWh3NGRRbGFjT3UvQUJ0QzJYWHNtL200TTgzOC9nZ2R1R0VTLzlrYjIxemlKaW9yQ0VCM0RwUjNha1o2VVFMdUVOa2hDd1lXTUxLdUVJb1FBU2NpeXYxQmUrQmtBR25OemM1ZTRYSzdYZmdIK0JRcTNmVWhaalRkb0VpcmVDYXc0YkJRWGwrSU1tcy8ybWlxS2kwdXd1ME1udWFJb3BBNlp6TWI1azF1OWxzTldSVWxaMVpuMTBXNmxwTFNpQmRlZHcxWkRTWEV4Tm9mN0RPN0p1MzVvY1VrcGRtZkxGN1Ntc295SzZwWXZoSzJxdk5WK0swNDdKY1hGVkZudDUzV2NGQVZTaDhERytTMS90NVhEMWwzZ2Rudi9EdG52Z0pJU3NMZWllaWw0N1lxUzR1Yjc3VHo1eEJ0TW1qVDJselZYRkNkbEpTWFlXaG5YNXVOa3Q5di8zYU5IaitCOHRSWW1LQzFUTzN4ckdLaEVNTWRhODJDQm56aXlLVit0UFczYnR2M0pjK0FzcXRBanVLNXZUNzc2U3NadWJ5REZrc0NudGRVWURCRzRYRzRrV2NhR2dzZmpRYTFSQzZGU1Myb1pISTFPWkZrU0VpS1FBaUs4S1NCU1llSFhja0ZCd1l1REJnMUtVS2xVMS8wY1FhMXczVE5NZStaZENnL2EyWDNYZFN3eHFCazI5WjlNSFpZRndLdlRiMlIxVFVjcVA5c01laDNxekp2WnRtUUdUOXc3aUdXN1BhUWw2Tml6cjV6WnI3N04yTDdwZ0lOWjQ0YVRYL2dOdHF6NzJMTnNScUN0UlZOL3c0b0RjYWpkeDZrNldFaks2TWRaTzIvOEtmdFlYYmlHQVlNK1FFYzFGZm84Q3RZL1Q0SVdWczRaeTZ6VjM5TTUxY3pCd2lMR1AvRUdENDNLT2VVOXJWdndJRk9lMmtSR1ZtZktEeFF6ZCswWERFbjM2aVhMWm85bnNmVUkrM2J1WXVyTC8ySDZzRXh3VnpOci9NMnNMRkpJMGR1cE52Um0zZXAvWWRGQnhlNDFEQjcxSjFJeXM3QlZIaVI3L05NOE5YSGdlUm1yV2VNZ3Z4QnNXYkJuV2RQdkQ0MkJiVVZRVWdGRGhnQ3hzSElseEFJN1ZzTDRHWkNlQ1FlS1lPNXlHSlhqUFM5L0FjelpCcnFEWU5WQ2pRbUtmQXROT29yV1UyRFBZVW1hN2hjelY1U2FnNHdjL0JzcURGMlI3UTA0S3Azayt2WTFINmVldC84alA4MzF6WWVIRHg5dUxWZXROUk5VQ2E0RjlhOEpxdEZvUmRNcVU1R0JPdENZR0JPeHNYRWtKaWFTbk55ZVRwM09rYnRkQ09Xc3R1UEhxL2w0OHdmMHYyVTgxOTMvWis2Nzd6NUdqLzR0MTE1N0xWZGNjUVdkTDBsRE5zUVJiMmxMYkd3YlNWS3BKSlZXSzB1eXJKSmtXUU5TQkpJVUpVbVNTWktrUktERHZuMTdoeXVLNTFOdndjTDUyWTRjcVJDSERoMDY2L052NnhFbDN0cnZiUEg3SzMrOFhzVDNHaVYrckhjTElSVFIwR2dYUWlpaXRyWW1jTXozRzU0UUhhNmRHbkxlL3JkbWlXNjN6UTM1N2ZrSHJ4R1gzelpYZUlRaVhJY0xSTHU0SzhRUG5wUDNhLzliczRTbVhUOXhxRUVSUWpqRUgzL1RVZnpwOWM5YTlLRnUveW9SMytWbVVYK0tlNnI5YXBXSWEzZUYySC9NNGYydHNVWWNyL1BlMnkzZFZPS1BMMjRYUWloaTc3OW5pazQzekJCQ0tHTHpjNU5FajVzZkVZMUNFVUs0eGROM1h5bis4T0kyMy9PNVR0enhqN1dCNnpjMk9rNytyQnYzaVQ1dEUwVmlZdWgyOWYzelQydWM5citsaUc2M3RmejkyM2NVMGUyVzBOODhSeFhSS1ZFUm54ejIvdjNqeDRwbzMwOFJMdC8rRDU5VFJHUTdSZXc5NnYyN29hSHAzQzllbXlHNi8rNnhYOVJjZWZmeHNhTGZmZDduMkhoa2grZ1VxUkt2ZkY3ZFlwemNidGZPOTk1Yit3RHdXMkE0TUJqb0MyUURYWUVPa2lRbCt0N2hLRm1XSTFRcWxVYXRWcXNpSWlKa2c4RWd4Y2JHU20zYnRpVXRyU09abWQyNDRvb3J1UGJhYXhrOStyZmNkOTk5L08xdmYrT1ZWMTdtNDQrM2N2eDQ5Vm5qVWZQdHJIbURUTVlvdHV3dEpySmJINGJGZXlndksvTkZTTjAwTnRSenlPckNraGlIeHQySVEzaUVKTW1TSkt1RTEveVdGQ0Y1a0x4cXF3VElraVJKR1JuZHZpNHJPN1F3S1NrcFZwS2s5RjlhSUdEd3lGdXg2TDNXdlU3ci9ZSlhGRzVpOGpPdmNLQ3NHc1ZSUmFXY2Qxclg2cDJYaXd6SVNjbVluZFZZbllEdTVPZWs1VjVOc2c1QVMvK0JlU3pjdVJ2RzVtQXIzY21NZWY5aTk4RUtGTGVObW1valZ1QmtYb3hkT3dwSUhYUXpYV045bmlPdEVaTzJTZEh2bmVkVlo1SlNVckJWZlE3QTFxMmIwZHB6bURYallRQktTK3hVNkFxQlBMTDc5bVBHNUQ5eGI4Vm41UFh0ejZoaGd6anBtbC9hcm13di8vR0NqRnZKZGloWHc1cjVzTlpycFdIZDR5MlA4UmZ5OUJ3S0dXYnYvM1ZCNDFCWmZSU2pNZmtYTlZkMjdmeWMvaU9uZXg5elFnNTlNNHlCZlUzajlPbDNTVzJUM25wMCt2MUhtZ1VLZ2lPZ0ovQ3J5YUpwQlhkL3NLQXBBbW93R0h4cmdyWWhJY0ZiQjVxYW1vckpaRHBuWTNxV3dPWmh4WHNiMkJPZnd4UFhHUGorbXlLY1Bsb2poNzJPVDQ3VzBTNDVtV2pGUVgyOTF6OGdxMVJJc3BvSUxjTGpjZU5VRUVpU2dpOXgxdzl3eWNudFA2NnFPdnBjWEZ6Y1RDRCtsd1JzT3FPaG1iT3BrRkdqL3NwajZ6OWdXSFlLOWwxTHNVejQ4UFFHUmc0YUd2bk1IZENLMzRPcVZERmgrRzBNVy9RaDh3ZG5vcTdjZ2lYenJ6ODVEQzBIOTgvbnJGS0FyTnhyR1RIWTkwMGFjU1BHaEZRQXNrYk1ZRi9PY1BJM0ZiQjAzbjBzM2ZKSDhwKzU5OFFOT0E4eUl1KzNsRFRyYU5kaEQ3RGlzUW5uZk95TXlUQnlSTlBmSThkQVF2QitZK3ZubVkzUjJPeldYOVpja2VYVzNIVEI0MVMxN1BWL3YvSGc5UHUvSjdTdzNkR0tYeTFBOVIyMDJMSHdyd25hTWdKcThBVUx2UHhxYmR0NmszQ1RrNVBQNlhpZUJiQUozdi93US81ZFoyYnA3M3Boa0FXZWhnWWNEZ2NPV3czcmRod2pPUzJkUkJ4WXJiVjR6Rk5pQUFBZ0FFbEVRVlNCVXF1b3lBaFI1eFNTVHExRzhiZ0JTWkVsQ1FGNEN4UzgvalloaEdRMng2K3ZxVGx1aUltSitldXA5WlFMS3dham5xckthdWlhY09xRHE4c29WYWZRdDJjS01yQit6YnU0MForM3ZoWHYyRXlaWXpiSk9pZmJDcmFUTS9ZUDRLNmt0TnBJLzd4TTFNQ085ZXVwVkU1OVQ5bTVBeW1aOHcrS3FxZDR0VGEzbFJxSEFaUGh4UEdtQWYydjVxRnR4V1RQR3V2VnhoUTdWVFhleHF4V0s3SEpHWXlla0VGdWtwdThKNHRPZmpQYVZCYXZmWS9tdm0ydDRhZDkxZlZHc0ZaNnZkeit5WithQjRaU2tGT2h0NDlBb3FycTlDWmUxNTQ1bEQrekFTZTAwRUIvcm5PbGQwNHY1bTNiQ3JkbTRhemN5YllpS3dOOCs2eFdxNk5CaW5ubHdVZiszejZhMGpvY1FFTUlxTFZNN2ZCcmE4SDhhcUsxeWdML0tsTnQyMXBJU2VsQTU4N25udW5ueklHdDRRaVBGM3lIVlJmQndMbjdBVkJGVzNpcWR3cUZoVjl5U05HaVBuS01iK3gxdUJ2c3RORkhZdGJwaURYRlVIL2ttS2h2OUVqQ0k0UktyWlprL01TVUlmV2traEJDTnBuYXZHV3oxVVZGUlVVOThuTUN0Z21UL3NENENWZXgyR1JnekNNdjh0REluaWMrT0dVQVUvSWVwVy91TmFRWkZNd3A4WUVIWHJGakJhT20vd3Q3VlFrbFZUSjkrNzVQNXJBSFdUUmoxRm4zeldMMk1IYlExYWlwcHRJNGxJTFIyYUJWbUQ2aE04TnorOUF6V1krYzFCNkxmT3A3TW1hTjROVVpIekUwcHdjWldaMnBMQzNsc1pXZk1qajl4QWJrZ0lsekdQclpiOG5zZVNVWktTWU9IaWhtOG9MM3VYZVFnYlZQak9leDlVZkpTSTFsWDJFUmp5eDgrNVJUTXlIcHpML2lGVHRnMUhTd1YwRkpGZlR0QzVuRFlKSFAzNTZVQzRPQXJDd3dKTUhHalJCcmh1WHpZVnllTjNoZ0xRZHREdVF2T28wUFhjK2g1TGtmWld1WmswSEoybC9FWEJseTd5eGVIblE5dVFNL1FPMTBZYlEwcWFQTEh2M2RpL2MvczJGWE0xQnpCSUdidHhhMGxXQ0JmNkZqWHdSVXRCWUJqWWt4QmRZdGFOOCtoZlQwVGtpU2RNN2ZVMG1JYzVObmMrVElFZmJ1M2N0MzMzM0hvVU5sSERsU3diRmp4emgrdklhNnVqcnE2NzFGOVBYMURUaGRUc250TGFDWFBCNlBKSVNRZlpzYTBBQmFJWVFPaUxUYjYrK0pqSXo4dzdtNjRjcktTcHhPRjhuSjdTNFlHRmFWbGVEV203SEVHczU3VzI2N2xmSWFCOGxKQ1NHNVBEV1ZaZGd3a0p4d1pocVAyMkdqdkxJYXN5VVp2ZmIwc29Qc05aVlVXZDFZa3BMUUJuMDZIYllxeXF0c1dKSlNUdnRhRjFJVU41U1ZnZEVNcGpNWXFtMUxwaksvTkkrVmMwYi9ndWFLbS9MU2NreEI0M3I4K1BFWFltUGp0alF6UFJzQXUrOWZyOVltU1U3Sm0yUWZBRFpmWllFL1QwMDBBWnFCNkdodi9XZGNYQnlKaVJiYXQwK21VNmRPZE92VzdaU0xzcHl0cUdiUGZ2VGNtR2dHQTVJazBkam9MWTUzdWZ5RjhRcUs0dkdYV1BsRHNUNVFGVTIvQmR1NlhzME5nSysrK21yUHlKRWpVYXZWdmM5RlArdnI2L0Y0Rkl3bmNwcWNCOUViVFJnaXRSZWtMVmtUUVV4MEZNMi9nYm9vSThhb003ZnFaYldXbUpnWU5LclQvNnBxZEZIRXhFU2phb1pkYXEyZU5pYlRHVjNyUW9va1E0d0pkR2M0VkNsWnZXazhYRWJYSGwxUi9XTG1pa3gwMExqVzF0YStQR2pRdFp0Ly9QRkhWeXZBNWszQzlmblZKRWx5TlRNL0E2RG1Oei85Q3h6N0F3VnQyclR4clZuUU5tQitubXUvMmprSUhyUXVxYW1wT0J3T25FNHZyWkhiN1FwUUduazhpZy9rdkdEV0JHcENlSk1sRmNWWFY0b1F3dVYzdXExYjk3NzArT1B6bGp6eXlNUHFpSWlJZTdob3BZYmxDNWRSM1lxQ2JjbnN5NmdCUFFuTC8walVzWXdkTitJWE8xZnE2dXBlZStDQkJ6Wi84Y1VYemZQVW1rRE5Hd0gxYTJvZWZNd2QvZ2hvOHdSY2J3VFVuNnNXdXNKVVdscEhVbE5UeisrUW5Pc0xkdW5TeGNmWjVneWtmL2dMNVAyYW0yOFRRZ2lmMWtaQWxWTVU0V2NDa2Z3K3R6bHo1dFRFeDV1WDNIMzMzWEpFUk1UZEYrZmJZK0xXU1pQRElCS1djenBYNnVycVh2L3JYMmNWdlA3NnNvWWdUYTBoZVBPREdrR1ZCYklzdFJZQkRmalZ2SVNSaHFBYTBJUkFXa2VYTGwzTy83Zm1uS3Z6a2tTWExsMW9iR3owOGJXNS9GeHRBYzJ0S1pGT0JJR2JRQWloK0loQVFreFVTWko0NElFcHg3UmE3Wkk3N3JpRGl4ZmN3aEtXY3lkMWRYV3ZQL2JZWS9uUFB2dHNQYUdCZ29BL3pRZHFJZmxxWGhOVWJoRUI5Uy9FMHB3dzBwdXIxcGJVMUE1MDZkTGx2QVFMemp1d0FVUkdSdEs1YytjZ1dpT3YxdGJrYS9PYXBENnoxQTl1d3FlbCtTT2x3ZUFtU1JMU1BmZmNlN1N4c1hIeDNYZmZyZWgwdW52Q1V6TXNZVGxyVUh2dHIzK2RWWEFTVUd1UXBOQlZwb0tYemd1T2dEWnBhazBKdURFeHhvQmZ6WityMXJselp5SWpJeS9JL1oyejRFRnI0S2JUNlFMZzV2SDR3UzNBMWViWDJ2eWJEOGJGaVhSQkFiQmh3MGFYSkVsNyt2VEpkV3MwbWpNT0tQd3ZnZ2RoQ2N2UFNXcHJhMStlUEhseXdlTEZMOXBQcEtuUlNtVkJFNmpKd3IvQWNXZ0VOS3JGZWdWK245cWxsMTZLMld5K1lQZDRGakYzRDU4VzdtWE82bnordUxLQVY3NzhJY0JEMVZoZncvS1BQbVhHdnpmeGwzZTNVNnhFY2NrbGw5Q2hRd29KNWpnYUZJbkQ5WTNVQ0sxdmRYbDk4T3J5UXFQUm9GS3BoVCtEMlI5MW9ZbkRyUkZ3UFBiWVk3VTMzenpxQmJ2ZC92U0ZuaFFIOSt5azJuR3VydVprMzY3ZDJFNDc0NmFja1RtLzRjQlpFSDlaSzRvcE9nUDJoMjJ2UHNLOVQ2NzUyYitrSTNPZ3FKWG5zVzhYWi9CY1QwZmN6THRyTkZ0SzdEL3B1WjVLS2t1TEtLazhTN0k3OXdHR1pOOU0yVWtPT1g3OCtBdlhYRE1vUDhpbjFrSlRveVVOVVhCaHU0K0dLTlNuRmhtcDl5WGdlcXNLNHVQTldDeHQ2ZEFoaFVzdXVlUzhwWFdjTzJCVDZsaXpxNXd1cVIyNElhTWRtN2ZrTTJ0ckNRQS9GSmZ3ZGIzTTFkMDdreDByZU9DRlZSdzJ0Q1U5UFoxb25ZYjlEUkwxSHNFUlJZUFJhTVJnYUVaUzZRTTN0ZG9QYmw1eXltWUVsWTJBWThPR0RmVlJVWWFYYkRiYjQ3NkJ1U0F5ZSt6VkZKU2NLMTdNU2lZT3ZZa2krK2tEWVdsSk1jNnplR0UvVy9VNDB4ZXNPKzNqYlRWSHFhaXkvZXlCYmZ4VVNHaGxGazhjeWhrODE5TVlxUjFMV1ZHUlJ2OVUvVTk2cnFlU3RmUC93THpWaFdmOW9Td3BMajFSdVp6ajZOR2p6OFhHeG0wSmluNmVEcWg1SkVueStJTUZMYzNQVUxKSWYxV0JuNElvUFQzOUp6SGhYamdmbTJ6aThmSFhCdjVzWXovS2ZidC9nUDZwZE83ZWs4ZTcrL2Vrc212dmZyWitYODFEMloxd3VRWVNrOUNPOXpldXA2RGNnY2trK2N6U1FOb0hQcjlhd05mbWR1TXJKY1VUN0hQenBZUUFFQjF0WEhiOGVMWGRaREpONFR6V2xwYnVYTWZpTlo5U1dPSEUvc3lqN0RiTDVJNjRtMkU1M2pMcGZWdFhzMlQ1KzFqbE5veTVkeXFEc3J3NU9vcTlna1hQekdkSDBXRmlrenN5N3Q1cFpLY1kyWjIvakZWYnZxREVWc09pT1gvRm9wVVpPdTRQNUhVK2RRTHQ3dnlsekYrMW1ZU01xNWcrYlR3bU5iaXRaU3hlOURLN2k3NUgwY2N4ZXNJVUJtZW5nTE9jcCtZK3o4NGR1emhncldUbXpPL1FKZmRpNXIwakFpOUQvc3JGck5yNEJSamFNbXJDSkFiMTlQWGRXYzJTdVkrdzdlQXhoazM0STZQNitrcGYzRlpXTEp6UHBwM2ZrdEQ1Y3FaT214UW82TjYzYlRXTGxyNlBEUzBaQTI1aTJxMkRPUitwdVB1Mnd2Sk4zaG5jTytUWndLb3RVR0tEUlhQQW9vV2g0eURQMS9YdDYyRFpHbkFiWU9JMHlQRlh1VHZnbVFVd1pEQXNYZ0IyQTB5ZkJlbSs0WGgxOFFzTUgvdEMwNzJjNHJuYXF3NndZUDRpOXBUVWtqbm90MHdkTnlSUWNyVmozV0plWGYxZm5Gb2plY052NDY2aHZWR3E5akhuLzdkMzd1RlJsSGY3Lzh6c0ladk5ack1zSVFraGlURmdpQ0hFaUJnUkl5SWlJbEpFUktTSWlKUWlXcXJXb3JWS0tTOHZQenpVSXFVVXJWSThJWjRRRVJFcElxVVVFUkFSTVVhTU1jUVFZZ2doaEdXejJlek96dlA3WS9Zd3V6bUFlS0srUHRjMTE1NW5abWVldWVmK251N3Z3aGZZdGJXQ3VySy9NNnZtbjZRVlhzYU1jWU5Qd05KY0xGLzRDQnRMdjJMSTJJdWprYXloZ29VTEYxTldmYXdoUGpuOXFTY2VmV0EzMGJXZkxUSG1aN3VncG10d0hBVnFrVUJCQW9tSjBhQ21WUlgwK3Zaa2lMNXpZSXNabjlZM2tKdCtWdHNQL01lcE9LSXlLVFVKZ0x5OFBCUkZvVnRLTitKYldraE9SdWRyQ3kxaDhBcURHMEFnMENtNGlTNWRuS3NPSGFwcjd0YXQyMjNmbFNxSVBTT1A0Y050YkgveEVZcEtMbU5JdHBtMERNMVBWNzVoRVNQdWZJMEZpLzZYWkY4bE04WmN4ZEp0NzFPY1ltYnBmVGV5em5jRjgyYU9vNzV5RHcwTmJzaXlrOTZySDhNdEZ0WXNlWkhpSVZlUWI0T3M1Sk5Jb0ZXcVdQWnlPUS9lL2t0V3pMK0R5WE1zcko0M0hsOTlKWTJXYkNiZk5ncGYvVzV1RzMwWlMzZCt5c0FVTzRPR1hvSHMyazFsYlIrR0Q3OEMyUmFwWFh4eDlnM00yMlpud1p4YnNYcHFLTjFYSGdhMkxTOC94OGdsRHpFaGV4c1R4dDVNY2ZXN1pKa1ZIcHh5Qlp2TlZ6RDd6anNvWGZrb0l5WTNzdXZsMlZDM2xSSGovNGNGei82ZGJMdVBiWHMwd2N2T2dHM2Jxc2Zac1BkUXpNMnpLN2ZkTjRPVVRtWm5TallNSHdwVFI4UFlleUV0K04zMFhqRGNBbXVXUVBFUWdzYzFhRjR2ZzZsUHdNSkhnRm9ZUHd3MjdZRXNTeERZNXNHNjdUQjFNdEFJalM3QUFkREErdldWM0RldlVIZmxkSEpjUGRXTUhYd0YrVk1mNHZaUmFTeWIreHRtZWl3c21qNll4dDByR0QzekZaWXZld2lIMnNEdWFzMk1sYTBwREIxK0JkN3lEY2haRnpCOGVCSFc1Qk0zQ1Y0NTd4Y3MzSnZCb3Z0K3licEY5MUtobzJ0enB2Nk0rbk5uZlhIOTliYm5SNDY4SmxUN0dadW5GbHBhT3dLMVVGVkJaK2FuQm1wZFNVdExKVE16azE2OWVwS1hsL2VETWZsdkJHelYrei9oc1U4Q1BEbmo3Rmg3bFdmV2JDVHU3QUZjMlNNdS9HNmZQbjFJVDA4bm9ibVoxRlE2Q2lJRXZ5MEVoS09sSFlJYmFJQ1ltcHEyZnYvKy9hNnNyTXhiWkZrKy85cytVSTYwWHBTazlTTE5aaVMvL3lCSzhpS0hidW5DdnpCeDFxdU1LTWtEK2pOMTZGT3NXTHVMNGlrRGFXeG93SnFWUWs1dUFVV0ZSYm9MTTUrVWJEdDJzNFYrSlNYMFA5a0tHdG5LekxsektNNDJVamozdDZTTWVnN1h2UEhZZXcxaTVxUThkdTdaalZ0TklUL2R6YzdkdFF3Y2tVWC9raEthOXFTVGJNNmxwS1JFQjVMVkxGeThsUVY3dm1SWWxoa29SdmNwQlNOdlpOcW9JVUF4QTJmOW1iMjFrR1haeXNKMUNwc3I3eVhiRFAzdSt3TlA5UG9acFo3WjVMc2JhRktzcEdSazBTODNuWDc5VC94M0xBNG5hV2x0YmlNWVQwRHprck9nSkF0c3hyYUFsNUlOZGpQMEt5SHF1QzU2Qk81NkFnWUg5MnYwMDdCNks5dytOUGdGRDl5N0NJYWt4MnlzcVpKS1Z3YloraUpiMmRiaGNTMWIreXhsR2Rld2N2b29aR0R1dmJlU2Y4K0xMSm8rR0c5REk2ckZSbHBHTmdWWi9RZ2ZJbXN5SlNVbDdGdnRwS2xYVWZSNTZ0aGh3UElWRzVtNTZpQURDNndVejcySEphdi9FUEduSFdyOG9MWFYvY3JJa1RmdXAyM3RwNjVFaWxhSmprSE5ZREFJazhtSTJXelNnVnBzVVh1b1hDb3pYQzcxUTQ1VEJyWWpYMVZ4eXlzZk1mZW0wZVFtNkdlaFlNT21qYng2UEpubE54WkczYTBsU1NJN081dkU0eTR5TTlDWm9tcVVPUnBhajhiZ29EUG1CaElhZVJQaXpEUFAzUGJoaDd1Yit2YnRPOVZnTUF6L3ZnNWlkWFV0Wll0L3g2NW5RLy9Xd3RDaDJ2UHBjLy9PZmJQK2wzNjU5MlBKS0dMUnNxY1lrdmNOb2tOeUN1bEJlbUpKeThEYVZFOGowTFQxYVlaT2ZwVGhZNjRteFc2bXdhM2c5WjBneXVDdXBVWk5JeWVyL1JLZVpJY3pQRTNNWmdWRkFXOWREWTNlR3U0Y2QwM2tlMFhub0hyQjJHc2t6ODdhemF4SlYxQmU0Mlg0MVB0NWZNN2tUblhYYkk0VTB0Sml2aUhidjVNOHBPcGFXRG9MVnVtSWNULzloaHlRbTk2dVl4bEYvanJiK1JKWDZYYkdqUDQwL0Y1eDNrV29RUHJReVR3NHBvS3BJeStrMmlVeitkNUhtVC85VktkcUUzWDFGaktTTmIrZk1TME5aM0EvL1g3L3hvc24zcjdteGhtM0h0S1puM3BRQ3hlMVM3UlI2b2dCTlJNbVUzc05XRUt5M20xQjdmdklWZnZXZ2UzNGtZUDhZdmxXWm95L21rdlNvcDJwNzc3M0gvN3l1Y3pUTjErTXZaM0NPWVBSZ0NPckp6M1BGS2hxSUlhdGhZQXM1RzhMbTZRZGdWc0lEVVhJTkQzMzNINmZyRnIxNnArdXV1cXF3MmF6K2NadlBkb2l5Nmd4WXZsWldSa1VUZjhiOTQ1cWF3VTdjb3Bac3VJTlFHSDVySjh6YThGeXRqMXhaeVIySTZ0dHRQYzdEOTdVVTFPclVKUmp4RnRYZzhlWlFUTHd4TExIR0RQcktSNmMzQS93VWJubXo3RTczbWEvc2FXVElkZFJXZTJqVjliSjFTZWEwekp3Mm5KNWV1MWJZZk5QUDUxR3paakxxQmx6Y2RYdFpuRGg1V3llT3BsaG5aUUVWdTdad3RwdEIySldrMHJ4aUNIWXY0bHpUbTdiMHlBckhjWXNodkVGWHpPVTVzZ214MUpIVFFQMFN1R0V4elVyL1F4U0JxU3lmdVc4ZHJaaFk4cnNoVXladlpENjBsVVVEZnc5VTZZT3A1ZFJQNzlPTmpqbElEM0ZTMDJEQjlLc0tIVjFOS3JRNHZHODlOalNmN3g5eHgxL2NIVUNhbDZkV2FxUGZyWUJ0VWhoZTd4T3FjT3U2MVdRR2l4c3o2RlBuejZZVEtZZlBLajA5WUhOZjV3Wi8zZ1RTMFp2R2cvdTU3bURZSWgzTUtFb2kvM2xlN2oxcmMrNTRkTCtyUDlBaSt5Y2RjYVpERWhQUlBVMHNlS2phajZwYnVLSVQyVnZOeWYydERQSVVLTjliRHBRQ3owWElhdlQ3OWR5MmJSRVh3SVJiQXN6UWlHRUVHUEdYSHN3THk5dndmdnY3NnkzMld4MzhDMG1JdWZuOTJMNW9nVllSaGJRcTNBZytWbE9wdDc1SzRiZjlVdHl6UDlEcmxObTU2YjE1SXk2aDZINWRsWSsvaUJrRHlRM3pjeXVmVlgwNm4rVDNuQWlQMGZoOFFWUFVOOC9nL3dCUThnNWtaOU45Ykpneml4U2JoL0ZpdmwvWnRqRTMyRURzckt6ZVhIdDgrd3FraWxiOXdUcnloWDBLbGU1dVgzWXZlUWxYaHlRakNNdGgrRURDOENZeFowelNyaHJ5cTBzbVBNcnJONGE5alhhbURxdTQxNEVjbG9KZHc3MU1YbjZIR1pOSHdXTk5hemRYc1hjMmJmaks5L01vdlhWRENrcHdsTzlsUVp6RHRuT3p2L084Q216R1g0S3VwRzd0MEtqRjF3KzJMNEpHcXd3Y0hCRUZUZy9CeDVmQVBYOUlYOEE1Q1REN1ROaDZqU3d6ZGVDQ2x2V3crRGJvZDhKQ1hRYXc0YW1zRzE3T1lOSFJXdUh0WGRjODBkT0ltZnVwZHkxSUk4SlEvS3BMOTlEcVR1TmU2YU1vR3JyYWxhVit4amNMNWZhM2J1UXM3SkoxczNPdkY1bjgrQ0tmN0FxdzAxeVJoNkQrblhtTXJZeGNlSnc1cysrbjZ6N2ZzNmFSWThveHhYbHlVdHplLy9uVU5zZUJYcEE4MHFTMUNxRWFDZjVWaDhvTUlhWW1vaVBENW1mTmgxVDAwQXQxS3VnVDU4K1dDeW5oM3ppMTAvUVZmMGM5UmxJVFREaDlTdDQvUXF0c3BuaURDZkhtMXV3Mm15WVJDRDhXVklYSnpsSkZrU3JteTM3RDJOTDZzTFpUaXQrSVpHWG00dlRLRkFVQlZXTmxGdXBxZ2lib2pxeVJvaTlDZEhoM2dsSmtnUWdHaG9hMUFjZWVLRHN0dHR1cmJKYXJUMGxTWExDTjAvUUxSNXlGVXJkUGo2dHFDSytSeDl5MDVQbzJuTUFWeFk1V2ZQcXEyeCt2d3hyWmwrR1hud2VpWEV5bm9aS05xei9KeHMzN3lTclpESnpmajJhT0RsQ0VTNFpjU1VONVh2NGZIODFxYjM2azluMUJCTkRUbVhVc0RONC9wbVZPTS85T1EvZE01NDRHZkl2dUJSUnRZTlgzL2dYQ2YzR01YMTBFVDM3RkpPVnJHVjZKK1djeDRBc2lkMGZmVXFkeDh6RjUybU8zWUpMUjVOcnFlTzFWMS9uNDJvWEZ3KzlndXhVN2RnNE0zcFRrSk1hM3RjK3hSZVRIQzlUTW1vY2xyb1BXYlZxSFo4ZU9FYS9TNFp5VHE4ZUdDU0ZEN2Y5aXczci8wblpWeEovV1BBbzUvVkkvRTRtN3RzdncwY1YwUGRDY0IrRTZobzRkMUFFMkM0WkFRM2w4UGwrU08wRm1WMGg2MXdZMEIxV3ZRbzdQb0swUGpENGZMQVlJcmY1QVFNaHJwM3RaWGZ4TStmcFhVd2JlMG5VKyswZVYxTVMxNDY3aXMvZlhjL2F0elpSM3hySGtLSERPQ1BWam95WDdadmY1cTMxNy9CbFN6Y2Uvc3NEbkprVU9lZVo1NVp3VnR3eFBpd3J4MjEwY241K2RxZkg0ZXlMaG1FLzlBRXZyWHUzTW1mdzFZK3RYL25ZKzgzSGozZWswS0ZQNS9BRkFTMHErVFkyK2hscXZoSWZyNW1maVluMm9BU1JKaFNwQlFwNlVWQlFnTTFtNDNRWjM1b2UyNm1PWThlT1VWcGFTa1hGRnh3NGNJQzZ1anFPSEdtSTBYRnJDUmJXdCtMeitTUi9VTXROVlZWSlZkV1FucHNCTUtEcHVabUVFSEZvYzlSU1Z2WkpVVzV1N2hTRHdYRHBENkhIOXRQNE1Rd1BkNDYra2xFTDMySkl0dlcwMmpORlVmNjlmZnYyTlIwME0rNmdUNEh3QzBGQWx1V0FQdm0yL1R5MVdGMjFVRXFIRnYwc0tDZ2dLU25wdERvbXhoOTZCNUtTa3VqVHA0L085TlNibGxGRVRVL2ZKTkI2SjJpbWFkakpJVUordDZCWnFnSnFmbjZmM2M4Ly8zenRtREhYVkFFM244NlhUL211VGV5dGFtem5FenZEeGc3anAwS3dIMnBZV2JqNjM5LzdWbjBORmF6WnZLZmR6M0tMaDVMak5ENy8rT09QYjdyNzdudU9FNTNPRVd0KzZ0dmsrU1ZKRGtnU3FyNU1xcjJLZ2xDZVdxejVxUmVMUE4xQTdiUUFOZ0NIdzBHZlB2azZVSk4wajFJWTRDUkpDaTFDa2lSSjg3a3BRVUJUVVZVaDlQbHRJWjhiSUc2NDRZWmE0SytmZmJhdnltcE5tQTUwUHgwdm40eThJdXdaN1VRelpTTzJuOURsLzl3dzJqTVlWR0tqSGJ2cUs3Zkh0VHd4TVRzazQ2MVA1OUNib08zMS9nd0FBYTJ5UjFiYkwyalhmR29oOHpNVS9kU1lXZ2pVOHIvVnpsSS9PbUFENk5LbFN6RDNKUUptR29pRm1Kc1VmaDNMM0FCSlZTVVZWQ2tFYW5ybUJxaEI5aVo2OTg1YnZXblRwcXJ1M2ROdU1oZ01RMDYzRTJLMU9iSCtoR0EvamREOXpHd2hKU2JSVDFHVXpUdDI3SGlqcE9UaUdqcnZ6cTZ2SkdpdjhVcW9xWEdiaW9KSThtMUhnWUo4dW5UcGN0b2V0KzlNM2VOVVJueDhQSGE3UGF6ZkZnb2toTEk2T2dnYWRKWXdFN1p0UXdBSGlHZWVlZWF3MVdyZDBhOWZQOCtwS0lUOE5INGFQOVE0ZHV6WTB2dnV1Ly8xVzI2NTVZak85RHhSeldkVU1idE9laWdtbmNPaTY5SWVLejJrOTZuMU9hMUJEVTZENEVFSEo0OVBQaW5qaXkrK29LYm1BSFYxaHpoeTVBaE5UVTI0WEM2YW01dnhlRHp0QkJUOEtFb2dGRkNJRFNvWTBZSUtab0pCQlNEdXdJSHFpM3YwNkRGWmtxUnpmN3BzZmhxbjYxQlZkYzlubjMyMktqKy9UM21RcGNYNjAvUW1hSlRwaVNiakhXSnFJdExRMkVUSFBqVzkrUm5OMUU1SG45cTN4dGlhWEM0T3VmM1lMR1prUFdjS0tOUTJIc01qRE5qTTBSbTZyUzB0SEd4cUppNHVEcFBjTWRHeVdDdzRISTV3VDlMbzZnVEN2cmlZNUdhcEkvSVdTZ0hSbTZhaDE0OCsrdWpCZ29LQ25UazVPWDZqMGZpOWc1dXJ1cFIzUHE3bnJLeVVrLytSdDU1VmEzYlE4K3d6djNIemtOTnBWTzNkeXZwM3RsRFhZaUduUjlmdmZ3ZE84Ymh1WGZzaWNsb0I5cmp2WnJmY2J2ZnpUejY1OU9XUkkwY2VwUDE2VDMwcWgxZnF1T2VucnU3VEhHWnFRdkZodGptdzJ4SklUSXcwWHdsSkQ0VlVPZ29LQ2s2WUpyVjN5em9DWFh1U2FKWkFiZVRGSjU5aDYvdTdxUGZaT0N2eis5TmorL28rTnRYTnIvK3lrdDErTTZseEFXcjk4VHc2NlNvdVRJbW41dk9QdWU3RkhUZ2Rkanl1NDNUdjFaZS9YMWRNb2d4UHJWckZrckxqWkRyaU9IRFV5eTFqUmpDMVQ4Y1hjMkppSW4zNzlrV1daV1RaRUh5TUxKS2tmNHdFRkNSSnd1LzNpMkNGZ2hCQ29LcHFWQ0FocVBHbUNrMDZKSERkZGVOcWdML1YxQnpZbTU2ZWZwTWtTZDlxWjVUMVMyZFJrVDZlR1NQYXByelg3OTNJd2cxR1JwUVVuUHdLUFZYTWYzQXBnOGNNd2ZsZkJGeDcxaXhrVldNQmN5Y1BiZk5aM2JZbkdEVGxPYVpQL2hseWl1YzcyNGZhN1M4emQ3UEM0L2RPK0phT3E4cUtCWDlrVk1GNE1yN2xrTFdxcWg5OS92bm5xL0x5enY1TXg5SmlPN04zeE5JVVNaSUNRcWlxRUFqWmFGSU5CZ09SRXFsSW5scmRvYTh3ZGMwaU1URXhxcUE5VWlhbFZSUWtKQ1IwZmwrbzNzemtXUyt3WmN1SWtJY1FtODNHM2cxUHNiYkN3b2lCZWFjeHNNa21iaGs3bW9KTUxScXlhdDBiek4zNE1XOU5LTWJxVE9mVjM5NUV1dFdFNm5NemFjRnp2RlNleDlROE94ZGVjQkUzakU3RkxFUE52ZzhZdG5JcjEvUVpRMmYzNVlTRUJBb0xDOUZPaUl6QjBCN0lTVUZna3dtS1U2SkZUUDNCb0VLNElUTmhwTk1ZbXlwSlVpaW9vQUtCakl6TXpVODl0ZXlUY2VQR2piVmFyZFBhdlhzMjFWUHZVc25LU29zNmVDcmdkVFhRNEpiSlNvOWNGcXFpVUYzMkFhVytJU2lLZ2l3YmtlWHd4Q1Y3K0F6V2p6UzJ1VmpjamZYNHpFN3Nzb2M2dDB4R2lqMzhtV0x2eDg3dHk5dFVBQ2xLZENtT2JEUkd2cVA2cUttdXdaS2NUckxORXJzNWtGWHFxcXN3T3RKSnRuLzcyZU9xcWxCWDlRbTdhK3p0SEFlRm5kdmVvOS9ZVzdobjVuaGs0OGxOUzhYcm9ycTJrYlNNbUQ2bEtxaUttK3JhSnRLek04SzFxcXFxNHE2dFlNdHViM0FmdERsMHd1UHFkZEhnaGpTbm1acmFKdEl6MHNMZlVSU1ZSUnMvYlZPMHJ5cEtWQ1JULzM4Qm11cXFjY3YyTm4xZVEyb29CL2VYUC8yM2Y2elk4c0QvbStzbUVpRFFsMGZwemREMlRFOVZraVJWVlRYclJLZ2l1cG14Mll3c3k4UlpySmhraUUrdzRYQTR0TVZ1dzJLT0l5MDlnOXpjczRJVkJXWnREdFUya3A2UlJrTk5OYmEwTEt5NlUvWHN3Z2NZTXZsL0l0RjcyY0hJQ1JPeE5MN0wzcW9PN3RGTjlkUzVGTEt5MHFPdUo4WHJwcWEySGtkS09vN1krUm84U28yMTFmZ3N6cWdlcktyUFEzVnR3NmxFUmVNb3lJeHc3aXhuRXQ1RGZnQ2NYU013SlpzVHlMQWJhZkZwRjF0ZWo0aUNab3JUanF6NGFUMlJwazNRTEkyQW15RUc1Q0tzTGNMZ3BCREFTWklraWRERkhnUTRnUzVpMmc1N1UyNitlVXJnNXB1blBGbGUvdGtIT1RrNUV3d0d3eVdoZy9uMGZUOW56dXBLOHROa0t0enByRjczRXZuSlpsWS9jaU96Vmg3QWJqUWdlNm94RjkzQ3VtVXpNVmR2WWZpVS82Vm0zMjZhYkhkVHNjcEo5dUFiV1RwckV1Qmw5cVJSYk56N0dlN0NXeWxkSG14Vjd0NURidGJQS0NqSlkvdmVSZ2IwUzJQZjdyM01XLzB4WTRvY3JIeDRCbyt2MmNHMktpZlZOVzhUSnZjMVd4ZysvdjZnNHFhUDBwMjdXYlQ5S0pQNjJhZ3YyOENZOGIvQ25KRlBVMFVaUTJZK3hpUFROTmFrbEw2SVkveHpUTWs5eHRZYWdhZFJZY1d1SGZUN05xUDQzbkltamZvVlpkV2xWTG0zTVh6M0MxaHlMMmZ0a252QVZjcjRzYitodkx5VUduVVh3N2MvUTlIb08zaGt4c2dUc3I4eE0vNUdmbUVPZTB0cmVlVEZOeGszSUl1bTdVdkptZkFZUlNsV0xGYUZmZTUwTm14OGhWNzJKdTRaZnoxYjkxVlFWYWN5ZlBoNzREeUhsMTkrQkNkMGVGd25EK2hPVmZJQXFuZnZKRy9BaGJncVA2Sm8ydDlaZk5zUXlqY3M1YmFIbjJmdjl1MHMyZFBDMkhEMWs0dTd4djJNWGZVYXRGWHYyODNRV2ErdzdNNFJxSzVLcG8yOWp0MGVKdzVmUFJUOG5MWEw3ZzFXVE5SeGNXci8vNXd4ck9qTkY1YXZxK3FBcGVuTlVGOFEwSHg2bGhhNmFVdGF1RTNFbVF5aVZWRUo5ZjAwRytHcjJvT29CaFB5VjRlUVZMQW1KdEcxcTVORG4veUgxeXM5WkdlazhQd3p5N25sMFpjNTd6d0x5Kys5aHZtYm1yRzdQOFdZY1I0MjVTdnFrNjlrOTh0emdwZEhQUysrWE1ic2UwODJGdWZsaWFjeHZEZ0FBQ0FBU1VSQlZIdHY0cEYxVmVTbW1LbDBKYk5xL1N2a09ZMXNmZm8rSmovOE5nVzU2VlNYbFRMd3RyK3crTTdnZkdqYVJYYmVyWXdkbmNxR1hVZFFYSTNNVy9NUithV1BNUHpoTFJRNUJFMU50ZWc2UnAzQzRqdk9yUXYvd1pKUERyZjVySFovS1JmT2Y0VUQzdGpmQlhoNjVVb212L254MTlxV3FnYjQrT085dlBiYWF5eGE5QmZ1dSsvMy9QS1h2K1M2NjhZeWJOZ3dMcnBvSUVWRlJlVGw5U1k3TzV2dTNidVRuSndzSlNVbFNRa0pWaWt1TGs0Mm1Vd0dnOEZnbEdYWkpNdHluQ1JKVmttU0VpVko2aUpKVWdxUUFlUUFad05GdlhyMUd0RFllT1QvQ2FIV05IKzZTblR0ZnFIWTM2d0lJVlR4dDE5ZEtuNTIvN05DQ0ZXODlxY2JSTGNMSm92akFWVUlmNE80K3V5dTRxa2REVUlJVlFpaGlpZC9lNFc0NWJFdDRkZjY1ZE5YWm9zK044eVB2SGQ4dHppcng2WGlzRkRFemVjbGlYKzgzeWhlZStBRzhhdkhOa1crYzNpTDZOSGpNbkc0bmZVSm9ZcDNIdnUxT1BmcWU3VDlFVzV4OC9rOXhFTnZsZ29oVkJFNFZpck95enhiZkhCTSs2Ny80eFhDWU9naW5ucTNXdnU5M3lOYS9PMnZWd2hWdlAvOGJKR2FtaHF6ZEJkUC9ydTJ3OStFbHJmLytndHgxZStXdGZ2WkczKzZTVno3eHhVblhJY1FxaENCT25GcGp5N2krUS9xaFJDcStPU1YyYUxiaFZORnExREYwZmVlRUliNHM4U093MTRoaENyKy9NdUx4RTBQclEzLzl2UFg1b3MrMTg5dWY3M3RITmViTGtnVnIzenFFOC84N2lweDQ1OVdpeVB2TGhWOXJwdXIrNTFQWE44blFienllZnY3K3RXSEswWHYzaVhpNCtEK1BIWDNWZUxLM3k0VEFhRUtJVHppanN2T0VuOTVwMXFvYXFDbXJ1NmpCVEw4SEJnTGpBYXVBaTRIQmdFWEFFWEJ1WmtEWkVpU2xCS2N1NG1TSkZsbFdZNlRaZGxrTUJpTUpwUEpZSlFsR2NrZ0pka1RKQWtrWjJwM3NyT3pTWFBFWWJLbmN0RkZBN2w4OFBtWUpDaTVlaEwzM2ZkN0hucm9JVjU3N1RVKy9uZ3ZycktWZE90OUxjMUM1Ym5mWGMydkg5dk1sMjg4VE9aVmR5SUM1Wng3NWlVY0NsNmZnZjNyNk5MOU1nNjNjKzIrL2RkYnVPcTN5NkxlKytMTmg4bTg4QWFPK3JYWHIvKy82L25aL1NzUVF1WDRzU1lDb2ZVZTNVN1BydWZ5UmZCNzR1aE96akNaK01NTHU0THI4dExTcXZEcEszT0pQL01LRHJXcUNPSDZCbmxzcXArL3Zmb1dTcThMdUNVLzJxQnNidnlLVzEvK2dQdHZ1SWFNR0lmcWV6di93L01OZGw2WTh2WDBtaVJKb3FDZ0FLUFJpTkZvd0dBd2hobWNMT3NmbzN4eFliK2JKQ2toOWlaVVZRMHJodWpZVzZpL1FpREUzaW9xS2hTbnMrdXF0OTVhOTM3WFE5dEhPd2RjZWxOMlVDbDI2SkRMbVAvRWJtQWlBTVdEQm1HVEFkbko0SUU1N05wZHl1VGlRYWQwYUdXYkhSc3lkb2NkdTgyT3pXYkQ3VGs1bWU3cXJjOHlmVWs1NjdhOG9lMlBxNHpOZXp6SW01N2ozaTBhOC9RcDFaUld1T25YVDZQdzVweEJqQjhZbE9Bd1d1ak1FTzAvWVE1MUUrYjhzTTY2MnIzczllUXpPRmk5bmo5a0tNYkp2NlphaFdUQW5qK1Evc21hQVRwazBNVXNXN3NMR0hHS0c3TmlzeG5CYnNOaGMyS3plL0NlNUxud05aUXlidno5ekgveFh4UUU5MmZUeGkzNGNyTzU3OTdQQUNoM3F4eDVmL3VLRlYrMWJwazQ4Y2FqSjJCcG9ScFBueVJKb1RTT2dDN3FLZlQ1YVQ3Rkw0eHg4WmpqNGpFYlBLTFZEOWF1OGJnUCtlbmFvenQyZXhKMmV5S1o5dDBrZFUyaFI0OE1Fa1FqTHk1ZFRIV0RCMVZ4MDlSb0o5U0J3VzZ6WTNYWWNkbzhJTnV3NGlia0RmVTJOZUN4T3poWm9yOTE4Nyt4SXZQZ3JOOXJITGV5bmxMdkhtQThhbE1GOTk3MUozYVdWZUZWZmRTN0cyaHdRMDVvNVpaOEpvM3RGM3hoeGhMME5XVDN2NEFVTTREdFZJRXR3SXR2dk1WLzVETjU2c3I4S0d1eTFkM0FyYzl1NU5xcnIrS3FyT2hNMDQvM3ZzOGZ0cnY0eDlRUmREMUZaWk84dkR4Q3prK2owWWpCRUFLNmlKbXErZUhDdmpnUmZKU0NFVkdkYnpZSzRGUWR3SVhBTFFBb1YxNDVZait3SlBPeVg3NnJLTXExUnFQeGlwTnhLbi96SVVkczlaTlluYWQyTitPbi9KbkZxLzlGTDRjeHZCK3FPWmxoWTY0bUs3aXEwYU92SlNjM1V1OG9XMjJjckZkdDcrb0ZUSnJ6WEp2MzczMzhuNHdma01LUGI4amhoOGpaT0FsWklhV0oyOGZmUU1uc3B4aFRsQmExdmdGRHIyWmtnUTFGVWQ0KzY2eGVHNlpPSFhkZ2VRVFFPZ0kxdlk5TjBabWVZWDl4cUUrSUxCdUUwU0R3ZWtIMXVrVkRhek1JZ2IvRlMwS0NqVGl6ak1scW8wdVhMamlkRHF4R2lhNnBQZWg1WmxmbS9mSVBYTDlzTTlPSEZXQ3MzMHhhd1I5aS9tMzdjOUppdDJQMnVIQkIyOENMM043Vm9aSlZkREdqUjRkdS9sY3oxWkVCS053MzZXb3NrNTVud3hPRE1GTk5jZkpGMGRQZmFzUFNEbkxKc3ZFYkJBOFF2UG4yMjd4MFBKbG5mMzRlOGJxZFZqeE4zUEhVZWdZT0djYU52YVAvM3VmbEgvR2JUUWRaOG91Um5HSDlaaXI0UFh2MnhHdzJCd0hPaU1rVURYRHRNTGNnd0VsU1JIbzhVcUVRWW0yNndFSkF4OTdDaytqQU8wOStZREk5V1ZwVGMrQ2R0OS9aZUYyLy9sUENTcjA3dDJ6QnJVN0dwamF5ZVZzbG82WkdaS1J0Vmh1TkRmWGY3ZlhucTJmNnVCc1krZUFMRE12WEhYdDdBVVB5UFZTN25Jd2Jya251dUJzYU1OcE83UnprRFp2SzJ2N2oyc3hjWjhxSlEvbFdheUlORFYrM281T1BoVE9uWXgweWcya2pnbmZwOUFJS3JXVnMzdDNBaEg3SmxHM2FpRko0UGxreWVBQlgyVloyTmZnb1RqYXphY3QvNk4vdjk1RjlzTnR3MWRlZzhGMlczYWdzdmVkR2FncC96WklKQTZJK0dUSjBJSzkrWHJOcjB1Q1NOM3VmbGZ0cDZPWVpBMmp0Z0pya2x5UU4rSVFRQVFFQk9aeHdpeG9JcUFMSmdObG9Fa2FqRWVGckJ0a2t6c2hNMXdRaVpTOGZmL1lWUmxzaTZhbGQrZHp0b1Z1M2JqaXRYZzQyQlJpZWNTYjV1UWtjY1hkbDBNQUNqTUQyZGV1b1A4bjdzNXpkajBMZlBzb2FvU1FHMmRLYzNhbmNXQlVsRXo5bzhDWE1tZjhaZWYzdlFic0hLNXBzUGg2cXF6MU1MU25HTEVQVnBqWHNQWVdtWFYvLzNMWWM0b0ZOWCtDeXhERmt2cVlRYXU2V3c3dlRMMlhIaHgreXNlNDRIN3krbG4rOEhtUUhsdy9qL2dHWi9QMnQ5Nmh0bExsaDRUUEJGY1d6Yk9ZRStwNWkvOVRNekV3ZHVKblFrZzJOWVZNMW1zV0ZnVTRJSWFSZ1BhbVFwRUFvVnk0Y1dOQ3h0eERBUmJFM3dKK1JrZmt2MlJqLzNyOC9lWDZZRU9JNklDK0ZTb1lOdWd5anB3Wnp5ZStaTUNCeWRvZU52NFZGNDM1RjBlb0h5QnQyQ3kvT24wcmQ5aGNaTy9OdmVCcXFxR3FRS1NsNWs0S1JkL0Q0akU3MHQ3ejdHRGZzbDlSNlhkVFhWek95NUdMTUdSZXo4Y1g1MUc5NmtlVzc2aWljZXpNdno5Vk83YndWLzJaa3ZwVUhuLzRINDhiL2pEVUw4ckQ0NnFsMjkyRGpycFZrbk1KeE4xdnRaRmhQTGEraC84aEpPQjYvZ1lLaUYzQVdYY09XcDJlZEZMQnRYUGtjanJSUkVXQ1QwMWl3K0ErTUhYTWhLd3B5S0MyclpmR0tOekdqQVpzOXpjNjlJeS9GYWxVcGQyZXc3cEdJUW0zNmdORU01Um9LQzgvRGxuNEI2OWN2d2RuSmNlMXN6SjkyQmV2SzNPeXI5TEIzM0VVc3REcFlzT3BOaXUxN2VYRFJlc2lyb1YvUjM3VHJZT1pmK2VNTkYzMDI5QmV6MXZ5cTc2QVBlejhjVUhSUlRMMGloeStXb1VuZ0Y1b2FoeEtjaDJvRTFDUWh5d2hGRVVKSU1tYXpkajBjYjNZSmMwSnl1QXVjMWVyRVlUeUFxeldPNHVLTGFGajNUMTUrelVXODBZU3phendaUGZQSTdObUxtVk55R1RYZ1Fvb3lyTWpwbWFTZDdQMVB6bURjNkN6V2I5aER5ZmpvYkttQ1VWTVl1T3dhY3ZKZW9YREVyYXhaTUlPY0ViY3phOHZOOUM4OG40SmU2VlJWN21QRVBVOHlmOUlnYnJ0ekFsT0hYOEN5Z2pTODF1NzBPb1djcHRPeTh1RHJqS05IajFKZVhrNVYxWmZVMXRaU1gxOVBZK01SbXBxT0JXV1BRbFVLTFhpOXJYZzhIbncrSDBLb2t0YWhYaUVRQ0VqQi9ndWhhZ1VwV0xFZ0I4SGZDQmlEVlFzbXRNb0ZNeEIzMzMyL1R6emJVbkg1NnNBMTF5Mi9hMmhPZzFjbUkrVjB6QzVUcWF1cEJvdVR0T1FmaDBhSTRuVlRVOWRBU2tZVzFtQytSZFAycGVUTi9JRGFUWCtpcHM1RmVrd2F3UTh4aEJEN0R4OCt2R2JPbkRtN0hudnM4VmFkS2VscngvUU1QZnFERVU5RnQ2aDZYMXF3aW9CZzBtM1FlakdIR2hsanNVUzZzMnVxdDRrNEhFazRuVjFKU2VsS3ZObkMyZWVjUjJHZjNsRWxVazMxTmJpeHRVbEZPZEZ3bGEyaFpNWUdkbTVhZk5LdURhKzdpYnBHRjJscEdWaDBLVHZ1eG5vYVBESlpHY21uMU9YTStOOCt1YnQwNlVKaFlTRnhjWEhFeFpuRGR5dzlnOU16TnlIVVVJY3JJY3QrU1pabEZFVVJnVUFVZXdzVjE4c3g1bWtBVUlMbXFSL3d6NS8vZ0E5NHJmY2c4WTczenVGRGUzUkx1aGJvZWZvZEtabTBqT3dmbGZmTGFMR1JuZDIrWW9Cc3RwR1ZaZnVoQWEzeThPSERieno4OE1NZi9QblBDN3c2Z1BKM0FHaXhnUUZGSHh3SUFwc0lsVVVGNWJ2UlFFM0xUd3RXRTBTQm1zMm1xZDRtSllYNkUyZ2Qyck96cytuZHV6Zng4ZEZta3lNbGcxUEo5ckhuajJMaGpFWnFYU281SjZucmJyRTV5TGExM1pyTm1ZTHRHL0NELzNyR3BwdEVmUGJaWjFSVzd1Zmd3Um9PSFRwRVEwT2t2dFR0ZHVQeGVIQzVqdEhTNGtVSWdjL253K2Z6NGZmN0pVVnBsNzFKUWdnNUNIQ2htbE5Ea0wyWmd1ek5yRi91dm50bTR1OSs5N3RMblU3bjFaSWs5ZUduOGYyeXVLWWF0dXh6TVdSQS9nL0hqVlcxckw2K2Z2M3MyWC84OE1rbm4yenRCTkJpRjM4UTFOb0RORDJvaFZsYUVOUkVNRWNOdmVSUUJOVDAxUVNwOU9pUlFVN09tZlR1M2ZzSGI3cnlYWTBmRGJDRlJsVlZGWldWbGRUVTFGQlhWMGREZzZiRzYzSWQ0L2h4TjBlUEhzWGo4U0NFQ0JiUXQrTHorZkg3L1NGbDNpQnpDMGlxS3FSZzVEUmtvb1pDUWdhZGVkb3V3T1hrNUppM2J2M1B4YW1wcVZmSnNuemhUNUR6NHgrQlFHRDdnUU0xNzF4OGNVbHBUYzFCSlNZdzBCbW82UUV0N0VjTFZROEFRaC94Yk12U1RKak5jZWdWYjYxV2ZkMW5wSkZ4UmtZR09UazVaR2RuLzZqUGhmSEg5b2V5czdPSmo0L0hZb2tuTGk0T3N6a3VIR1F3R2swRUFnRmtXVVlJb1ROVFd6RVlES0U3WVlpOWlVQWdnQ1JKb2J5M2tFa3F4d1lYWXN4VEgyQ3VyS3cwcDZmM2VBZllVbDcrV1ZGMmR2WlFrOGswNnFmTC84YzNXbHRiMSs3YnQrL2RvcUp6OSt2QVRNL1NmTzJBbXI4RFFBdm9VampDWm1jc1N6TWFqVUdXcG9HYXhXTFJ5WGduWUxNbGtwU1VoTk9wbHgzS29tZlBIRkpUVTMvMDU4VDRZL3hUcWFtcFdLM1dJTUJac0ZnaUFCY0lLQmdNQmxSVkRVZFF2VjVqQ056dytYeENsdVgyZkc4aWFKckdSazhEQkp0aW9JWGlUWHFBQTh5NXViMTNBbnZlZVdmalM4WEZGd3l5MlJLdWdsTUtTdjQwVGgvWFI4M3g0OGYvK2E5L2JkNDlldlRvaGc0QXJUMVE4M2ZHMERveU8wTXNyWDNUVSs5UDA0SUVTVWtPbkU0bktTbmQ2TjY5TzFsWlo5Q3JWMDhTRXhQL1Q1eWZVelpGbTF3dWp2bGxlblN4UlJjQUJ4UnFqeDdIRUo5QWFrSjByOHFXRmcvMWJoL0pqaVFTVE5MM01ma29MeS9ueXkrMWlPbWhRL1VjT0ZETjBhTk5xR29BdDF1TG1MYTBSSnJGdExhMjR2Zjc4UG0waGpFNjN4dWg1akZxUUtBS0lRZmJtWVpNMUk1OGNIcFQxUVNZSjB5WVlQblRueDYrSUNVbFpZalJhT3hReFhmUDV0Vlk4b2FSbDNieXpVTnF5NkJDZ1VHRlA2NkpXckZyTTJwR01ibmhZNkdpS0ZGbDVoaURFL0ZFSmNpcXF1cUszNk0rb2I2bUdwL1owU1lpMk5SUVE2TWIwdExUL25Yb1lNMjd2L25OYno1OS9mWFgvU2NKYVA1MkFDM1FBVU5UUS9YT3dRQUIwU3d0MHJ5NHJUOHRvcVBtZEdwQmd2VDBkTTQ0NHd4eWMzTy9VMzlhOWQ2dHVKMkY1R2VjSGhIMzcwMjJhUG5xVlR5MnI1blVSRE8xamMxTXZ2cEtwaGQrdDIwSEpFbWlkKy9lSkNRa0JQTjU0dkg3L1JnTVJsUTFFSlhrRzVrOEJscGJqUmdNUHZ4K3Y5QytMMHVLRWtCUkZLSDRBdmhWRlVrU0t0cE5OYXdXRXN4M013UW5hNGpCR1lPQTVnc0IyNG9WSzFwWHJGanhMMkRyeG8xdlAzWCsrZWRma0ppWU9GU1NwQ2hkbC9WUFAwVHlsQUZmQzlqMmJZTVZqVDh1WUZOZDVVeVordjlZc2YzdDhIc3I1OTNBeEFmWGsreXdoYWZ5STJzK1lueS9Sb3B0NXpKdjMxR0d0OGVKUFdVVXBCVmdIL2N3MjVmT2pBQlh4VmJHamJ1SldtTUdkcVVldWZCbXRqNTlEM2pydVdQQzFaK3Qvc1MzT2NuUS9OSEhuNVlmMFlGU2U0RG03d0RRbEJNQW1tZy9PR0RBYURUcHBZYUlpek1URjJkcHg1K21LZDUyN1pwTWFtb3FHUms5MlAzNll0elhMYUwzZHh3a3NDazFqSm0rbG0xckg4UjhHc3laVTJCc3JaUWVhSW1TTFhxeUtaVzNKaFRUZU9RSTNuaDdsR3pSNE5FM01EWFBUa3Vyai9nNDdTOGYyYitYaTUvL2d2L011b2J2UzA2d3FhbUp5c3I5bEphV1VsdGJTeUFRNE9qUlJvNGQwNElLc2ZsdVFXWGVVTlFVUlZIdysvMVM4L0VBZmpWQW5Ea1NQZFVGR0NRaGtLRk5rTUVRWkhER0dCWVh0WHo4OGQ2OG5qMTdYaGdmSHo5RVZaUjBWY2RDVGdvQVZHMlJqVEdNSlVoaDNJM1E1SU9NdEs4SkxGNm9xb1dNYkFpbEdubWFvSzRSMHJLSWtxNEoxYjVVVjBGeUJsaDFzMXp4UW5VZFpHVzN2YU9xd2M5UzBxTi9BN0J4MFRTV05BNWgxWnp4T21EN09RdHJMbUhyNDlOajFsUkYvMDZBclhMdGc0eGRzZ3QzMldFMlYveWJkQ09BbTBuOWUyS2Y5aEtMcHcwR29MUzByUGJNTTgvWS9OSkQwejc4eGYrK1VCM3lxWjRFb09rWEpjalFBdTJabkFTRlQ0TTF6VEZtcHdHRHdSUnNXbXlpSTlNeklTR0JSSnNWQ1lrdUtlbjB6TTRJQmdreXljN0s0cEZwQTNGTWZwTzdSbVJIeTFnQjdvWmFHcnhtc2pPUzI4eVZ4cnBxRkdzeUtmYm9tNnFucVo2NkppOXA2Um5SVWxING1ENm9ENE1YdnMvNGZqOThnNWZ2VGJZb0JHcWhZMmMweUh5ZmdXYUh3OEU1NXhUUzJ1ckZaREtoS0g3aTR5M0V4Vm1DUVFiTkVkdmNiTUpvYk1Ga011TDFhaXpPNS9QaGIvVnpwTUVnVkZXUkJBcXRyWXFRNUFCbWs0cUVKSHgrRGR4Q1FRWlpEZ2NaQWtKZ0FDbFl4U05DekswTnlQWHRXN2dIK0FSNCtvSUxMdWg3NExNUHo3LzdoWXJCZHc3UFNEK1ovemh0T095cWdPd0pzSHBlNVAxN3g4QWVHVlEzVkpmQzBGbXcrTFlUcisvcG1iQ3FDZXAzQWxZd0ZzRFdwZkR3ZEZpK0IzSlNvTFFNNWp3TkUwdkF2UWN5eHNISVhHaHd3WjQ2MkxJTGN1MVF0eHNHajRhTVF1MmY3OXNNVzVzZ0c5aitNa3krRjNvVlFQayttTDhDeHZZUHd5RXJWcnpPNkVVUGZ5dnpZTTNxTnhnNjRWRzhTNjlsN2ZZR3BwVWs0OTIzbnRVVldleVpmRW10eCtQWlVsSHh4VWZubkhQT2ZqMjc2Z0RRMmdPMjBIdWhvRklnREdpeXJFcENxR3FvSXpnSUpFa1lUU1lNc2lTOExTM0k1Z1M2V09QQTN5d2FqcnJJek03R2F0RkF6V0tKSXk0dTFKSGRTa0tDalhqSnplc3Z2NEUxN1F6aTFHWlNpc2Z4NUp6QjJCcDJNMjc4UFpUdnFjRlllUk1iRmxrWk1QNXU1azBkQmtvanN5ZGZ5OHY3VkxLc0hocHR4YXhkOVRmU0xEQnpSQ2JiUEgweG1nM1VWKzVsOU96bmVIQ3lWcy81NHR3Ym1QVnlKWVc5a3FuWVY4OFRXM2N3SUl5SlprYU51b2dsTDY5bGZMK0pVY2ZjVmJhZUNmYzh5N3psS3loeW5MYkFwaHYrWnBidCtJSnhsN2NOOW4xVjlTbGJqamw1V1ZjenV2MkQ5MW4wL240cWo3UXliOExQdm5mMVY0UEJRTStlUFltTHMrRHp0V0sxSmdTN1hNY0NuSm1XRmkySzJ0cmFxb0diMGNjWjJVYU8xdm1GSytDblM2SVdQUTBFQXFMRkZ3Qkp4V3hTaGFxcWtxSWdDUkNTRkk2aUJnQ0RKS0dBWkFoT2ZHUFFURFcyQTNUR0hUdDI3QUwyL09iS3JLY3UyL3RSNzV5Y25DS3IxWHBoWjdseFN6ZkEyb1d3dEoxeVRGc0JySndMVGRzaGV4b3N2TzNrVHY3MlBiQjNPNlJad1J2c0NqajlZYmduNkVxcFdnK0Q1OExFRGNFN2VoM2NzeE1LSFhEUGNGaTVFZTRiQXcvZkMyTWZoSGtUb0dvRDlOb1l2TWsxd01UYjRkbmRNREFkNnJaQzhWMHdla3R3LzN3VjdDeTFjM3RCMnl1aWRNMWZHRnJ4cWphUjB5NWc5Zko1bldlOHEzV3NYbGZGN0huOThkUmV5dU5yMXpGMTRNU3k4Zy8zN2ZCMGNaVDFqRFBvbVptcVkxbWRBWnFpWTJlS0hzd2tDVFhZbEZob2lndVNrR1JKR01PUlRvbFdiNnRRaFlGNHM0bjRPQk9OVGNkRmdIaU9IV3NoTGFzblNiYjRzRDh0MGozS2lzMm1xZDBlK2JTVWhMNGplZml1Y2ZUb2tVNTZlanBaV1ZtUW04dkdqZU9aTmZac0hKT2ZaK2JJN1BCaDJQejRiTlo0TDJMdnJubVlVVms0N1JJZVhyNk5CVk1IYWlSZ3dEaldQVHdaVC9WR2V2Vy9tMm5qZDVCanFXUEo0czBzMjN1UVFXbUE0c01YWTB3VUZoYXg2OEdkaEJSdndyY21WeDI3ZG4yRTIvZmZFQlU5QmRtaXZubG44ei9wWjdCMTl3Y3NlbWNQbDAwZFRNSVBRRk5UVWxKd09ydVFtSmlJelpZUUZVSFZKdEZ4M0c0elpyT0hsaGFONFlWTTAyYXpEOGx2eEdMeGkyQmdRZks0RlF5bWdEQWF0UjROQVVVVklDUlpEa2RSSlEza2hCeThVT1RnaFdBSW1xbmhzcTEyZ001WVdIak9SMEVtOTlKYmI2M3JmdDU1NStVN0hJNGlrOGwwSVhCU25UVUdCdlgvSEZsZ2FRUTNuRlIyK2JBeEdxZ0JZWG1ZdXIwd1l5R1UxMmdtWkwxdWd0dHlOVkFEeU1xQ1VPL25YYnRoOXRQYTgreUJrQkdjZVZYYm9OWUlxeGZCR2tEMWdhc1Vhb0VzQUZjRGpVWUhqbmJjakRtRHJtUHhuS0RFdDlGMlF0OU80L2ExbEtaZGR1ejhyb0VkOVVYRmU5K1o5TmN2REE5TlBoTHlrWGJDenRvRE5iM0NSclR2REZTQnBHcVBRb1I3YnNpeU1NZ0k3WWFvQ0pDMDNwRUdJeGFMUlJpTlJ0S05jUEJ3QS9iVWJMcDN0ZXNDQkJxbzJXelJxUnpaQ2VmeitxS1ZQTHN5Z1dIRFJwQmJkR0toeHkxYi9vWFowNS9aOS80KzZEYndVR2ZaQzJqQU5tU1FabzVic3daU2JDOWxkNVZDVGw0eVF3WWxjOXU0YXhnMzhqSUdEeC9Gb01Lc3FQWGFuUTVjOVVmYWJNODVZREoxZFpOUGQxTVVUbFcyS0NIQnhsa0pOczY2c29RMzVxNWcyNUZMdUx6ckQ1UDViTFZhNmRPbkQzYTduY1RFUkJJU2JGaXRWcXpXZUpxYU5BYm5kc2RoTmpkak5wdndlazE0dmEwY054c3g0Q2MrUHV4N0U3TGtCMk5BTXBzMUJ1ZHIxUmljTEdzcElvR0FRSktGaEFnSDdLVGdYVjNXbUp6azEvbmlEREVncHdjNlkxQkM2UUN3OFp4ekNrM0xsajJWazVOelptK2J6VlpvTkJyN1EvdDlsY09LTGwrejhNNFNHK1J5dzlpeE1HOGRqT3dIbnQyUU5xV2Q3Y1JzU3phQ1Q0bjRJdlNlWFhzR2pCa2RlVDFtUElRRmtPd083SW9MbHhkaTZaalZrVTVlM2dsMTlOMktvbnpnY3JsS0Z6NytWT1dSRDdmWDJNM0wxUmhtRm9qeGczVUVhSG93MC85RzFmdlBnbzBpVlVsQ0lCQ3lMS01HQWtLV0RhaEtxeERJSk5vVE1adU1IR3RzRUFhVEJsNG1rd21QNXdpeUxPSHpLOWdTYmNUSHRNUUw5ZmwwT3AxMDY1WkNldnFGYkx2bUpqNzc5Rk5XUGZzZ2YzM3RBOHJXek84OEtnd1VEcmljMGNPQ2dndWpyOGFla24xQ3FKano4dnVNM3I2RkxaczJNSEhJK1R5NDdtTW1GRWVrcXR4Tkxtek8wNk9EMWZja1c2VHkxYkVXdWlkcC9LeXh2bzREaXBrdWxoKytuQ016TXhPSHd4RUVPRnNZNE9MamoyS3hhT0NtQlJaYU1KbGFPQlpud29DUGhBUmpxR0tCaERnakxzVXZMSFlEaWk4Z3VZV0N5UndRSmxrbEVBamc5NnRJa2lEWVR5YlU2Rmx2cHNyQkM4UWdTVkk0NktBRHVmYkF6dmpSUjN1TjU1MTNYaW13RDFoN3hobG5HRzRiOVh4V3ErR3NISjh2cWJmSlpDcVFKS25YdDNyQUdxSGFDQ1ZGMms2dlczMVM2bVFNR2dqcjE4Q0lHYkIzUGRRb0VmWm1xd1k1RzRxRFFZMkdCaDJHbVhNcHptMmt0TnhEWWVHSm84TkNpQXBWaUUrT0hxNnIyRnl4NzhzYmI1eDBxS2FtUm8xaFplMnhzNDVZV3Zpem9OS0xFck1PVlFpaFQ5VVFXZ3RjU2NneUJGU0V3V0JBOGZ1RndXUkNWVm94bU9LeHhzY0pneFJBVVNFK1RtdDdKN3hOZkhWTTBLKzRtT3E5SDFEanl1YjhOSWN1NFZZZjlkVHFQYnQyNlVKcWp4NmNmZTZGRE8vbklHM2tjM2cxbDZqR29DMFc2aHNhMEx5WjJoZzg2Rkx1MlZwSnY5a1ROWmFyZW1ob2l0eHFObTNaeE15UlUvQlViMk9ucTRBRjJVYkFoOHV0VWpSd0tFVURoOUpVK2svS3ltdEJCMnhsWmFVVTlXL0xHTjNsbTVneGZ4WDNMRnBNdnYxMEJiWlRraTFLNVMvUHZjUzdMUmE2eGN0OGVhU1pjU012bzMvQ2FRSHVKQ1ltVWxCUVFGSlNrZzdnRXJCYUcybHFpc2RpY1JFWDEweGNuSm5HT0RNbTRjVm1NOUhhcWtWTjB6Sjl1Q3RNMUIvMkkxUkZ5R1lGZTRJaUJaTjdoZGNiUURhcVFoYXFGRXIwMVFVYVFpQVhabkhCdkRnLzBibHhoaGlBYS9QOHl5Ky9OUHp1cnlXZkFSVnhDM2tITUR6eXlKOXNoNW91VDA5MDljaHFiYldkYVJLbVhpRDNSbE1vK2ZvakMyNGZDQ1VESU1jR3lWa25ONG51ZWhoR2o0SDhwWkRiSDdLQ2RxT2NEQ3NXd2FTQld2REFWUXZtL3JEeDhZaFRldXk0SzFpNWZpTVRDa2RGaCtlRitNenI5WDV4L1BqeEw2dXJxdy8rODU4YkR0MS8vLzB0Z0RxaFg3bzRBWmgxQkdxeHo4Tkw4UHhFSmRJRzJ3TUppS1JySUVsQ1FzSm9rSVJmMGRLS2ZLMWVUT1k0TENaVkhHbHljN2pCQzBMQ2FKQXd4OFZqTmNQZWZYWGs5UnRJYWxjN0dZTXY0dTJOTy9Iazl5YTdXM0pRbGNOSnQyN2R3cVZSbVptWmJIcmlMaTVlK0I4SzhyS28zTHVIMis3N0IzcjRIenZ0TjR5ZGRnTWJGOXNaUFBsL1dEQmpCSU9ueldYRXp1c29LTHFJL0N3SEZlV1Z6Rmo4SnRPSGFtUy9hZnZMREJyNkdrMlZwVXg2K0JseUxBQjFqQysrRkhkR1BuWWFxSFRuc21aa1lSUjVXYnZtMzR5ZE82L051ZmZXbC9QTU15OHg4Y0h2RDlpKzExclJaazh6Ujd3S1hSTVRTVERKUHdpSTFkZlg0L1A1eWNqbzBmNCtOamRUWFYwZFR1ZzljaVJVYStvS3l5QzF0R2krdDlaV2J4RGN0SHBUajl1SEloU0VxcVdIaElycVE5VUx3UnBVZ2pXb29VSjdRa0NuZTVTRGdDZnJscENNa2lHbUNmU0puaHRpZncvSXExYTltcHlYbDljdE9ibGJxczJXa0c0eW1ib2JESVlNU1pJeTJ4cDliVWRERFNoV1NQdWFFU0JGQVdvaGF5aVVsMGZzWmxXQm1ocXdKNE1qWWt4N2hSQUhXZy92T2podytCKy9XckgyNmErVXh2ckRaV1ZsUjY2L2Z2eFJJaHA2NGZTSkRzQ3NJNGJXNGZOUThibnU5L29ValhDcVJ1Z3gyQjB0cU5Zc2laQmN2VzZKbEVNWlpBUVMxZ1FibHJoUW5hYzVHUEVNQlFnU3d1M3d1blJ4aEZsYUtEaWdiNFhuODdpb3JXL1VPanBaVHo2THpOTlVUNE5MSVMwOUhYUHc3alJ6ZUNiWk05OW5ZcUdDWW5HU0hKWHVvZEpRVzRzYk0xbnBLZEdwSS92V01uRDZPclp2WG9MMU5DQXJQN29pK0c4S2JLRlJWMWNYTEtRL1JFUERZUm9iRzRNYWJ4R2xFSStuL1lvRnY5OGZ6bjNUVnk0RUFvb1VDS2hoZ0FzRTFDQ3dxYWhxR05UUU1icU9RQzRNVURHcUk1MHRjc3h6T2VaNWFCdlNvNDh1c1BYdDI5ZWVscFptVDBwS3NsdXRWcnZaYkxZYmpVYTd3V0N3eTdKc2t5VEpKa2xTZ2lSSjhVRWdEQWtBR0hUZXRSQXcrR3EzNHB2NUxONXplNHVXYmErSjV2UUp3cjN3bDZvN0VBZ2NWeFRGMWRyYTZ2SjRQTWVibXBwY1gzMzExZkc5ZXo5MjMzMzMzUjRkZUlrWUlCT2RiKy8wNGdBQUM4eEpSRUZVZ0prZXlEcGlhbEZMSjBBV0JXWjZJQU9FTEV0QzY1SW1ZVERJT2tDVE1SaU1zWlVEUkJKdDIxWVFhTW0ybWlKSFlxSTl6TktTazdzRnU3RnJPV3JmNVFnQjI0eWhYMjg3KzdhdHBjRlJURW4rNlNFUC94T3dkWmJONHZkejRNQ0JzSUNsSm9OMGxHUEhYTzJLV0VhcmhmaUNBS2UwQTNBUkZxZDF1bGRENVZyRU1Mak9RSzVkc05PVmQ4bWRnSnFoSFdDTFhmVGI2V3lobmNmWTU5Rk8ycmJQOVkrZExiRkFGcnQweE5MYVBBOUp2N2NIWWljQ3M5RHptSjRhVWV5c0xhQ0ZRQzFTdU42UkdHUlNraDJIb3d2SnlWMXh5RzcrODJFbERtY1hESWJvL3ZUOWg0MW5RTzYzMjExOTM4N05XSHNOSk10cC9xKyt6bjhDdHBNWXg0NGQ0K0RCZzlUVjFYSDRjSWk5UlhUZW1wczlPdk0wVXJVUXFUbU5CcmhBSUJCbHBxcXFHZ1Z5UXFpU3FvcXdxVXE0NGJPUWhFQVBkRkk3SUJRRFVNS2dWVU8weTlJNkF6ZXBBNENUMndHMXpnQ3VNM0NMQlRUYUFiSE9HSnJhQVZ1TGVxMmwya2lkZ2xqUVo2YnJOeHYybTRYQUxHaHFTa2hTTkpqcFFFMkVGR1AwZ0dZMFJoaGFxSHFnYlY2YVRWZm5HZkdsOWVqUmc2U2tKSDRhMzNsVTlQL2VTRXBLSWlrcGlhNWR1MUpiK3hYMTlZZUNPbTlIZ3lWWng0TUY5YzNoZ25xdlYxK1NwV2R2SVJNMUVLVWVvZ2xjQmdnRVZGUlZGVUdRRTZxcVNxRitNMXEvQnFKWVhQQUNqbVYwT2dDU1pFbUtCVHdoQndHeU0wRHJETnkrRG5QN09zRDJkY3pPZGdFdUNFaWRBaGpSemJMRG9LcjNtWVZlQndFdGlwbHBRQ2FIMmo2S0VGT0wxSFlhd3dyT0VaWVdxUjZ3UktWd2hGaGFFbDI2ZENFNU9abVVsRlRTMDd2L241QVgrZ25ZZnVCUld2b0p1Ymxua1pxYXlvRUROZFRWZmNYaHc0YzVjdVJJbEpCbHlEelZLNGJvYTA0MTM1c0djb0ZBR09BSUJBSjZpU1JKeCtCRWhNbUowS09JTVZYUlIxY2piRnhJN1lOU0ZOaEZmYWNkYzFjNkFiQjF4TnBpVFZMUkNiaDFCbkJxZTY5MW9OUVpzNHY5VGRTMjlLOWpURTFrV2RZRkEvU0FaaUNrdWhFTGFBWkRoS0hwaTliYlU3YlZ6TTZJRUdUWHJsMkRMSzA3bVprL0tWcjlCR3cvd01qTXpDQTFOWVhhMnRxd1NtOWI4N1E1R0Z3SUFad3Z4di9XaHIyRkFneEJrRk03QkRrZHdCSE41cVJZb0l0NjNzNW5iWlpnSGwwbnpFeEwwdm91Zld4QjFuV3lUSzZqQmIxcEdmT2E5bGhaaUpucC9HZnRnRm5JN0RSMnd0TDBmalJ6VUJNd1hpY0NHVzEyaHRSdDA5UFRNWnZOUDExZ1B3eXdCZGl4ZHgvL3JQaUtZNHBNd1ZrOXVlSGNNekFEcmMxTnZMcnJNL2JXSGNOZ3NUR3l1SkFMdThja3dpdk5QUFh2anptejk5a016dmp2OVIyWXpXYXlzN05KVFUxdFk1N3FvNmZSL2pjOWcrczRlcW9vQ29xL2xaWldDWXRaaWdJNVhiQ2hQWkFqMGlZVktmWlJWVlVrV1E0SjJNUXlPenBoWXJyWFVralc2MlRCckNQR0Z2V2VDTm5aSjJaeWRBSmdrZDhMSVZSVllERElVVXhNNXk5RG42SVJDMmFod0lDK0NiZWVvWFVXN1l6NDBTeHQvR2loYUdlczJSbmJVS1c5VWJZYnNvcWczWmF3N2lvbVRIbVl4U3VXNE5SZDFiWGxlNUJUOGtsemZEdUE2U2xkeGVDNXU5ajU4dnh2dko0aGMzZXgvUlRXY3pMLzZSVDAySTZ6ZW5jdFJZWFpwSnBWbHIyOWtjK09YOGFEZzdMNXNyS0tqNXRsTHUyYmkvdklRWDc5OTVVc3ZtTWlBN3BFTnZQVzVxMHNlZTlMcnJMMStLOEd0dENJajQrblo4OGMwdEpTcWF1cm83NitQbWllNnYxdklZQnJhU09MMUZIMHRMbmhDSWNiQStSa3BlalRRNFRPRDZjSE9ZUlFoUlp3VUFrR0hzSkFGN3pPSlNFRWtpd2pSY0NPazJGZE1jQjNJakE3MlhLU05xYXBpUHc0Q3FSQzdLb2p0cWNIeFBEejRQOHpHQXpoOTJNQ0FFUWVZOEZNMWpYZDFnT2FJZGlZdTZOb1o3U3NVRWlGUXdPMGlCK3RhOWV1cEtTa2tKYVdGcFdQZHFJeGJRUXNySUQrN1JUTnJWc3lCOHVBOFZHZ0JyRGt2cC9qbVB4V1ZCSDhON3FocHhWdys2UnZudEtoZWwxVVZSOCtwZCtlekgvNitzQW1PM2hnOHVYaGwxMDhoN2wxejVjd0tKdmN2a1U4MERmMFNUYTdQL21VTGZzYkdkQkZPeEN1dXYwOFYydmp1ak90ZUg1azFEY2hJWUdlUFh2U3ZYdjNjUFQwU08xblBQL0NlL1F0S2NIZWNoelhvVW8yZlZKTG43NjVKTFMyQkFNTXNRek95NEV2dnFEcFdETkNFUnc1Y2dSVk1wR1M0b1JBZ0lEaTVmRGhZMWl0Um5ITTVVSkZ4cDdrUUJaK3llVnlvd1MwV0lJcHppSk1SZ2toQkFHL0QxOGdJQUJKbGcxSWhNQk9SVkZVTFNDaHFxQ3hscE1GclRZQXBrWFlwVENsQzZia0k3Y3ZjaWpDM3dtQnJQYjlZRjRZWVlBSzdtMlExV21BSklRcWhOQitMMGt5UnFNaEJIQUVGRDhDQ1VuV29wUWhVQXY0VzFFeG9yUzJFaEJndGRteHhwbkNRTlpPMmdadGZXbnRNelN0NzBBUTBJeng3UG5FU3U0Wk5uWnNTNlJiVGhJM1QzU1EzazN6b3gydFNPTnZ6MWh4eVRCK09nd3RCRHl3Y0JHTXZ4UFNMT0NyaDBlZWhSbDNRZVVtV0xrWnF0encrRnhJTThPSVNUQXdONFFTalN4KzRsL2N0V1ZwK09BMmxXOWp3Yk52c2JtMEhzdnlQOU8wM1VIZTRHdVpPRlJyWmx5NWN5MWJtNUpKcWQzQ2k1cytJWGZJejdsdjhuQXFkMjNnNlpmZm9ycXVrZlRjODVseCszVFM3UnBNckh2NlliWlZITWVhZFg3VWlhell2b2J0alhhOGV6YXd0ZUlJSTZmOGxyRWx1U2R4MWZoWXZXUWVhM1orU2IrUk4zSGIyQkprb0w1OE8wdWZYVVY1OVNHY0diMlpkdnVkNUtWWlQvaWZWRTg5eXhZdlptdnBBYjV4K3YrbjlRM2twcmVUUytNL1RzVVJsVDZwUVZhbStuaG83VTZtWDNrK2NUL09qbCtBVmx5Zms1UERPZWVjUS82NWd6akgwY0NMRzh2SXlrcGp4L28xbURQeXlVeExwVnUzRkpLVHUrSjBPbkU0dW9UTHVleDJPNms5TWtoMUppS1pFMGhMU3lNMXBTc0pWbzBCeE1jWmNMdVBjK1JvTTNhSEU3dk5ndEVjUjV4UkZySXBUdGlUa29RdHdTeDhMYzFnREdhMFd6UTVKczAyTXdtVHlTU01ScU13R2lRUkJBaGhNQnFFMXNSQlVnMEdneXJMY250TElMaUVHdmRHTDVyMm5DSkprcVo0b2RWVmhsL0hMQUZKa2dLeUpBVmtXUTRteVdycmxtVXB2QjJCVUlXR3VtcHd1NnJCWUZBbHRJNU5KcE5SU0toQ0NhakNhRFFLazhra1RDYXpNRWlJZ0NwMGlyTnhpSUJDYTJ1cnNDUWtDcHRaRm03WGNSRVhyL20vSW83OTRIR09qOGRxdFhDOHNaNURodzVSVjFkSGJXMHRCdzhlNUtzamJ1eDJPMGxKU1RnY1hZSitzcTUwNjVaQ2Ftb3E2Y25kZVc5TEJtVU5aM0Q5OVRrMDdzeGxXMTArNTV4ekRrcEZEdGZlYWFWa0xFd2FDWGVOZ1ozMWdCV3lmRERwSGswc2ROWVVjS1dCWFliMFhqQjhPRGpOVUR4RWU1Nmx1K1I4NVp2WUtmZG5RSHFFcDFpU3N4ZzIvQXF5VTJ6MDZuY3h3NGRmUWI5ZUVWbS95cDF2Y3QrMFg3Q3F3c0w0aWRkalZWd0FWRldVMDJ2QWxkeCsxeDFrdWQ5ajJKUjVZY0dDdkg2REtja0s4TWl5TjJLQTdRMW1UTHNidFdBb0U0Wm1NbjNzelZTZmhFUlIwKzVWYkhEMVl2cVVhMWs5K3pvV2I2Z0VvS2F5QW1kK0NYZmVkUWNsS1ljWU51SVczQ2Y2VDZxTEdTTXVabDJObldtMzMvSE5nZ2ZWK3ovaHNVOENQRG5qN0ZpaXlUTnJOaEozOWdDdTdLR1ZKTzdjOFI1Tm1VVU02bWJtdy84RHprdUx4VUoyZGpaM0xYNkJUeSsvbU1jZi80REd6SkhNdnU0UzNDNTlpb2luVFltVzFacUlUYmlvOVBqSnlNaUk5c0VadFJtVGNWWnZFZzFDWjZaYU1NZjdhUFkwbzBqeHlIS0xVREVRRjJkR21NM0VXVlFhV3IyUzBSeEhuQ0Zvc2lxQ0ZxOWZ4TnNTa0lXZ3RhVVpSWURSYUNSaXdZcVFPUm9tV2tFaUZrVytoTkRpcHFvL0FMS01qQ29GQW1Bd0dKR2xqazFRaldSSitIMCtKSU1Sb3h4NUQwQUVBZ1FrQ1lzbExzcWtOSnROQkJTRmdDb3dHbVI4QVVGY25CbEprcEVzRnBRV0Y2NVdTVmdzY1dGenMvVzRoQ2t1aVdTbkExbEs0SGhsTFhKY1BQRkdmYldBUGpBZzAycExKSTVvYzlTYzJBV25zMHVVRDAzZlNNVk9FaWFqZzkvKzBVbS8xRzUwUDU3S3F6VkdMQlpZdWhBbXpvSVJKZHIvbnpvVVZxeUY0aWt3Wmhac0hnRmp4a0JUQ213TXlwcWxaR3VMM1F6OVN0cWFvclhsVlZoeU1xTmtYU3pPREVwS01saWZZc1dSWDB4SlNWdXp6WmgzQlkvUHV4MFpHQjU4YjhqNEdkUlc3R1Z2ZVEwWmhlZFM5K3hiTkFMSlFFNWhNU20rTXVSbGJVM0lncEUzTW0zVUVLQ1lnYlArek41YVRTbTUwNUUraVBuM2pzY0J6SnB4RGZlOHVJcmJoODJrMy9DSlpGZnZZMDlaSmVaZTUyQ3BmSUFLTnhSMThwOWN1MWV6dkRhUG1zMHpzUVAvSDNpeGdKbVFPNXd1QUFBQUFFbEZUa1N1UW1DQyJdLFsxLCJkZXRhaWxzLWNvbnRhaW5lciJdLFsxLCJkZXRhaWxzIl0sWyJocmVmIiwiaHR0cHM6Ly93d3cudGVuc29yZmxvdy5vcmcvYXBpX2RvY3MvcHl0aG9uL3RmL2RlYnVnZ2luZy9leHBlcmltZW50YWwvZW5hYmxlX2R1bXBfZGVidWdfaW5mbyIsInRhcmdldCIsImJsYW5rIiwicmVsIiwibm9yZWZlcnJlciBub29wZW5lciJdLFsiaHJlZiIsImh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2FwaV9kb2NzL3B5dGhvbi90Zi9kZWJ1Z2dpbmciLCJ0YXJnZXQiLCJibGFuayIsInJlbCIsIm5vcmVmZXJyZXIgbm9vcGVuZXIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCkoMSwiZGl2IiwxKSxBKDIsIkRlYnVnZ2VyIFYyIGlzIGluYWN0aXZlIGJlY2F1c2Ugbm8gZGF0YSBpcyBhdmFpbGFibGUuIiksdigpLF8oMywiZGl2IiksQSg0LCJUbyB1c2UgdGhlIGRlYnVnZ2VyLCIpLHYoKSxfKDUsImRpdiIpKDYsIm9sIikoNywibGkiKSxBKDgsIiBBZGQgdGhlIGZvbGxvd2luZyBsaW5lIHRvIHRoZSBiZWdpbm5pbmcgb2YgeW91ciBwcm9ncmFtOiAiKSxfKDksImRpdiIsMikoMTAsInNwYW4iKSxBKDExLCJ0Zi5kZWJ1Z2dpbmcuZXhwZXJpbWVudGFsLmVuYWJsZV9kdW1wX2RlYnVnX2luZm8oIiksdigpLF8oMTIsInNwYW4iLDMpLEEoMTMsImxvZ2RpciIpLHYoKSxBKDE0LCIsICIpLF8oMTUsInNwYW4iLDMpLEEoMTYsJ3RlbnNvcl9kZWJ1Z19tb2RlPSJGVUxMX0hFQUxUSCInKSx2KCksQSgxNywiLCAiKSxfKDE4LCJzcGFuIiwzKSxBKDE5LCJjaXJjdWxhcl9idWZmZXJfc2l6ZT0tMSIpLHYoKSxfKDIwLCJzcGFuIiksQSgyMSwiKSIpLHYoKSgpKCksXygyMiwibGkiKSxBKDIzLCJSZS1ydW4gdGhlIHByb2dyYW0uIiksdigpKCkoKSxfKDI0LCJkaXYiLDQpKDI1LCJkaXYiLDUpKDI2LCJkaXYiLDYpLE8oMjcsImltZyIsNyksdigpLF8oMjgsImRpdiIsOCksQSgyOSwiQXV0by1hbGVydHMgZm9yIHByb2JsZW1zIGZvdW5kIiksdigpKCksXygzMCwiZGl2Iiw1KSgzMSwiZGl2Iiw2KSxPKDMyLCJpbWciLDkpLHYoKSxfKDMzLCJkaXYiLDgpLEEoMzQsIiBJbnRlZ3JhdGVkIGRlYnVnZ2luZyB0byB0cmFjZSBwcm9ibGVtcyB0byB0aGVpciBjYXVzZXMgIiksdigpKCksXygzNSwiZGl2Iiw1KSgzNiwiZGl2Iiw2KSxPKDM3LCJpbWciLDEwKSx2KCksXygzOCwiZGl2Iiw4KSxBKDM5LCJMaW5rIGxvZyB0byBjb2RlIiksdigpKCkoKSxfKDQwLCJkaXYiLDExKSg0MSwiZGl2IiwxMiksQSg0MiwiIFRoZSBsb2cgZGlyZWN0b3J5IG11c3QgY29udGFpbiBUZW5zb3JGbG93IERlYnVnZ2VyIChWMikgZGF0YS4gdGYuZGVidWdnaW5nLmV4cGVyaW1lbnRhbC5lbmFibGVfZHVtcF9kZWJ1Z19pbmZvKCkgd2lsbCBjb2xsZWN0IHRlbnNvciBkYXRhLCBncmFwaCBzdHJ1Y3R1cmVzLCB0aGUgYXNzb2NpYXRlZCBzdGFjayB0cmFjZXMsIGFuZCBzb3VyY2UgY29kZSB0byB0aGUgc3BlY2lmaWNlZCBkaXJlY3RvcnkgbG9nZGlyIGFzIHRoZSBpbnN0cnVtZW50ZWQgVGVuc29yRmxvdyBwcm9ncmFtIGV4ZWN1dGVzLiAiKSx2KCksXyg0MywiZGl2IiwxMikoNDQsImRpdiIpLEEoNDUsIiBTZWUgIiksXyg0NiwiYSIsMTMpLEEoNDcsIiBkb2N1bWVudGF0aW9uICIpLHYoKSxBKDQ4LCIgb2YgdGhlIFB5dGhvbiBBUEkgb2YgRGVidWdnZXIgVjIuICIpLHYoKSxfKDQ5LCJkaXYiKSxBKDUwLCIgU2VlICIpLF8oNTEsImEiLDE0KSxBKDUyLCIgaGVyZSAiKSx2KCksQSg1MywiIGZvciBvdGhlciBUZW5zb3JGbG93IGRlYnVnZ2luZyBBUElzLiAiKSx2KCkoKSgpKCkpfSxzdHlsZXM6WyIuYXJnW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIGNvbG9yOiBsaWdodGJsdWU7XG4gIGZvbnQtc3R5bGU6IGl0YWxpYztcbiAgbWFyZ2luOiAycHg7XG59XG5cbi5jb2RlW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIGZvbnQtZmFtaWx5OiAnUm9ib3RvIE1vbm8nLCBtb25vc3BhY2U7XG4gIG1hcmdpbjogMTBweDtcbn1cblxuLmNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV0ge1xuICBoZWlnaHQ6IDEwMCU7XG4gIGZvbnQtZmFtaWx5OiBSb2JvdG87XG4gIGZvbnQtc2l6ZTogMTVweDtcbiAgb3ZlcmZsb3cteTogYXV0bztcbiAgcGFkZGluZzogNTBweDtcbn1cblxuLmRldGFpbHMtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIGRpc3BsYXk6IGlubGluZS1mbGV4O1xuICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICB3aWR0aDogMTAwJTtcbn1cblxuLmRldGFpbHNbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBtYXJnaW46IDEwcHggNjBweDtcbiAgd2lkdGg6IDUwJTtcbn1cblxuLmV4aGliaXQtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XG4gIHdpZHRoOiAxMDAlO1xufVxuXG4uZXhoaWJpdFtfbmdjb250ZW50LSVDT01QJV0ge1xuICBhbGlnbi1jb250ZW50OiBjZW50ZXI7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgbWFyZ2luOiAxMHB4IDYwcHg7XG4gIHZlcnRpY2FsLWFsaWduOiB0b3A7XG4gIHdpZHRoOiAzMTBweDtcbn1cblxuLmV4aGliaXRbX25nY29udGVudC0lQ09NUCVdICAgLmRlc2NyaXB0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIGZvbnQtd2VpZ2h0OiBib2xkO1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIHdpZHRoOiAzMTBweDtcbn1cblxuLmV4aGliaXRbX25nY29udGVudC0lQ09NUCVdICAgLnNjcmVlbnNob3RbX25nY29udGVudC0lQ09NUCVdICAgY2FudmFzW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIGhlaWdodDogMjAwcHg7XG4gIHdpZHRoOiAxMDAlO1xufVxuXG4udGl0bGVbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZm9udC1zaXplOiAxMzUlO1xuICBmb250LXdlaWdodDogYm9sZDtcbiAgbWFyZ2luLWJvdHRvbTogMjVweDtcbn0iXX0pLG59KSgpLFZyZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInRmLWRlYnVnZ2VyLXYyLWluYWN0aXZlIl1dLGRlY2xzOjEsdmFyczowLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiZPKDAsImluYWN0aXZlLWNvbXBvbmVudCIpfSxkZXBlbmRlbmNpZXM6W0JyZV0sZW5jYXBzdWxhdGlvbjoyfSksbn0pKCksVXJlX2dldFdpbmRvdz1mdW5jdGlvbigpe3JldHVybiB3aW5kb3d9O2Z1bmN0aW9uIEhyZShuKXtsZXQgdD1VcmVfZ2V0V2luZG93KCkucmVxdWlyZTtyZXR1cm4gbmV3IFByb21pc2UoZT0+e3QobixlKX0pfXZhciBnVV9sb2FkTW9uYWNvPWFzeW5jIGZ1bmN0aW9uKCl7bGV0IG49VXJlX2dldFdpbmRvdygpO2lmKHZvaWQgMD09PW4ubW9uYWNvKXtpZighbi5yZXF1aXJlKXRocm93IG5ldyBFcnJvcigibG9hZE1vbmFjbygpIGZhaWxlZCBiZWNhdXNlIGZ1bmN0aW9uIHJlcXVpcmUoKSBpcyB1bmF2YWlsYWJsZSIpO24ucmVxdWlyZS5jb25maWcoe3BhdGhzOnt2czoiL3RmLWltcG9ydHMvdnMifX0pLGF3YWl0IEhyZShbInZzL2VkaXRvci9lZGl0b3IubWFpbiJdKSxhd2FpdCBIcmUoWyJ2cy9weXRob24vcHl0aG9uLmNvbnRyaWJ1dGlvbiJdKX19O2Z1bmN0aW9uIFBQKG4pe3JldHVybiBuPyJ2cy1kYXJrIjoidnMifXZhciBoZz0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMucmVzaXplRXZlbnREZWJvdW5jZVBlcmlvZEluTXM9MTAwLHRoaXMub25SZXNpemU9bmV3IEcsdGhpcy5uZ1Vuc3Vic2NyaWJlJD1uZXcga2UsdGhpcy5vblJlc2l6ZSQ9bmV3IGtlO2xldCBpPW5ldyBSZXNpemVPYnNlcnZlcigoKT0+e3RoaXMub25SZXNpemUkLm5leHQoKX0pO2kub2JzZXJ2ZShlLm5hdGl2ZUVsZW1lbnQpLHRoaXMubmdVbnN1YnNjcmliZSQuc3Vic2NyaWJlKCgpPT57aS51bm9ic2VydmUoZS5uYXRpdmVFbGVtZW50KX0pfW5nT25Jbml0KCl7dGhpcy5vblJlc2l6ZSQucGlwZShaYSgxKSxIcih0aGlzLnJlc2l6ZUV2ZW50RGVib3VuY2VQZXJpb2RJbk1zKSxzdCh0aGlzLm5nVW5zdWJzY3JpYmUkKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5vblJlc2l6ZS5lbWl0KCl9KX1uZ09uRGVzdHJveSgpe3RoaXMubmdVbnN1YnNjcmliZSQubmV4dCgpLHRoaXMubmdVbnN1YnNjcmliZSQuY29tcGxldGUoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShSZSkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJkZXRlY3RSZXNpemUiLCIiXV0saW5wdXRzOntyZXNpemVFdmVudERlYm91bmNlUGVyaW9kSW5NczoicmVzaXplRXZlbnREZWJvdW5jZVBlcmlvZEluTXMifSxvdXRwdXRzOntvblJlc2l6ZToib25SZXNpemUifX0pLG59KSgpLE9CZT1bImNvZGVWaWV3ZXJDb250YWluZXIiXSxXcmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMubGluZXM9bnVsbCx0aGlzLmZvY3VzZWRMaW5lbm89bnVsbCx0aGlzLm1vbmFjbz1udWxsLHRoaXMuZWRpdG9yPW51bGwsdGhpcy5kZWNvcmF0aW9ucz1bXSx0aGlzLlJFU0laRV9ERUJPVU5DRV9JTlRFUlZBTF9NUz01MH1vblJlc2l6ZSgpe3RoaXMuZWRpdG9yJiZ0aGlzLmVkaXRvci5sYXlvdXQoKX1uZ09uQ2hhbmdlcyhlKXtpZihudWxsPT09dGhpcy5tb25hY28pcmV0dXJuO2xldCBpPWUubW9uYWNvJiZudWxsPT09dGhpcy5lZGl0b3I7bnVsbD09PXRoaXMuZWRpdG9yJiYodGhpcy5lZGl0b3I9dGhpcy5tb25hY28uZWRpdG9yLmNyZWF0ZSh0aGlzLmNvZGVWaWV3ZXJDb250YWluZXIubmF0aXZlRWxlbWVudCx7dmFsdWU6KHRoaXMubGluZXM/P1tdKS5qb2luKCJcbiIpLGxhbmd1YWdlOiJweXRob24iLHJlYWRPbmx5OiEwLGZvbnRTaXplOjEwLG1pbmltYXA6e2VuYWJsZWQ6ITB9LHRoZW1lOlBQKHRoaXMudXNlRGFya01vZGUpfSkpLGUubGluZXMmJnRoaXMubGluZXMmJnRoaXMuZWRpdG9yLnNldFZhbHVlKHRoaXMubGluZXMuam9pbigiXG4iKSk7bGV0IHI9aXx8ZS5mb2N1c2VkTGluZW5vP3RoaXMuZm9jdXNlZExpbmVubzpudWxsO2lmKHImJnRoaXMubGluZXMpe3RoaXMuZWRpdG9yLnJldmVhbExpbmVJbkNlbnRlcihyLHRoaXMubW9uYWNvLmVkaXRvci5TY3JvbGxUeXBlLlNtb290aCk7bGV0IG89dGhpcy5saW5lc1tyLTFdLmxlbmd0aDt0aGlzLmRlY29yYXRpb25zPXRoaXMuZWRpdG9yLmRlbHRhRGVjb3JhdGlvbnModGhpcy5kZWNvcmF0aW9ucyxbe3JhbmdlOm5ldyB0aGlzLm1vbmFjby5SYW5nZShyLDEsciwxKSxvcHRpb25zOntpc1dob2xlTGluZTohMCxsaW5lc0RlY29yYXRpb25zQ2xhc3NOYW1lOiJoaWdobGlnaHQtZ3V0dGVyIn19LHtyYW5nZTpuZXcgdGhpcy5tb25hY28uUmFuZ2UociwxLHIsbysxKSxvcHRpb25zOntpbmxpbmVDbGFzc05hbWU6ImhpZ2hsaWdodC1saW5lIn19XSl9ZS51c2VEYXJrTW9kZSYmdGhpcy5tb25hY28uZWRpdG9yLnNldFRoZW1lKFBQKHRoaXMudXNlRGFya01vZGUpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sic291cmNlLWNvZGUtY29tcG9uZW50Il1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmb3QoT0JlLDcsUmUpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkuY29kZVZpZXdlckNvbnRhaW5lcj1yLmZpcnN0KX19LGlucHV0czp7bGluZXM6ImxpbmVzIixmb2N1c2VkTGluZW5vOiJmb2N1c2VkTGluZW5vIixtb25hY286Im1vbmFjbyIsdXNlRGFya01vZGU6InVzZURhcmtNb2RlIn0sZmVhdHVyZXM6W0Z0XSxkZWNsczoyLHZhcnM6MSxjb25zdHM6W1siZGV0ZWN0UmVzaXplIiwiIiwxLCJjb2RlLXZpZXdlci1jb250YWluZXIiLDMsInJlc2l6ZUV2ZW50RGVib3VuY2VQZXJpb2RJbk1zIiwib25SZXNpemUiXSxbImNvZGVWaWV3ZXJDb250YWluZXIiLCIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCwxKSxQKCJvblJlc2l6ZSIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblJlc2l6ZSgpfSksdigpKSwyJmUmJnkoInJlc2l6ZUV2ZW50RGVib3VuY2VQZXJpb2RJbk1zIixpLlJFU0laRV9ERUJPVU5DRV9JTlRFUlZBTF9NUyl9LGRlcGVuZGVuY2llczpbaGddLHN0eWxlczpbIi5jb2RlLXZpZXdlci1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdIHtcbiAgaGVpZ2h0OiAxMDAlO1xufVxuXG5bX25naG9zdC0lQ09NUCVdICAgICAuaGlnaGxpZ2h0LWd1dHRlciB7XG4gIGJhY2tncm91bmQ6IHJnYmEoMjU1LCAxMTEsIDAsIDAuNyk7XG4gIHdpZHRoOiA1cHggIWltcG9ydGFudDtcbn1cblxuW19uZ2hvc3QtJUNPTVAlXSAgICAgLmhpZ2hsaWdodC1saW5lIHtcbiAgYmFja2dyb3VuZDogcmdiYSgyNTUsIDExMSwgMCwgMC4zKTtcbn0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLHFyZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5saW5lcz1udWxsLHRoaXMuZm9jdXNlZExpbmVubz1udWxsLHRoaXMudXNlRGFya01vZGU9ITEsdGhpcy5tb25hY28kPW51bGx9bmdPbkluaXQoKXt0aGlzLm1vbmFjbyQ9RW8oZ1VfbG9hZE1vbmFjbygpKS5waXBlKEwoKCk9PndpbmRvdy5tb25hY28pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sic291cmNlLWNvZGUiXV0saW5wdXRzOntsaW5lczoibGluZXMiLGZvY3VzZWRMaW5lbm86ImZvY3VzZWRMaW5lbm8iLHVzZURhcmtNb2RlOiJ1c2VEYXJrTW9kZSJ9LGRlY2xzOjIsdmFyczo2LGNvbnN0czpbWzMsImxpbmVzIiwiZm9jdXNlZExpbmVubyIsIm1vbmFjbyIsInVzZURhcmtNb2RlIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJzb3VyY2UtY29kZS1jb21wb25lbnQiLDApLEIoMSwiYXN5bmMiKSksMiZlJiZ5KCJsaW5lcyIsaS5saW5lcykoImZvY3VzZWRMaW5lbm8iLGkuZm9jdXNlZExpbmVubykoIm1vbmFjbyIsVSgxLDQsaS5tb25hY28kKSkoInVzZURhcmtNb2RlIixpLnVzZURhcmtNb2RlKX0sZGVwZW5kZW5jaWVzOltXcmUsR2VdLGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpO2Z1bmN0aW9uIE5CZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2Iiw2KSxBKDEpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygxKSxqZSgiICIsZS5mb2N1c2VkU291cmNlTGluZVNwZWMuZmlsZV9wYXRoLCIgIil9fWZ1bmN0aW9uIExCZShuLHQpezEmbiYmKF8oMCwiZGl2Iiw3KSxBKDEsIiBObyBmaWxlIHNlbGVjdGVkLiBDbGljayBhIGxpbmUgbnVtYmVyIGluIHRoZSBTdGFjayBUcmFjZSBzZWN0aW9uIHRvIHNob3cgdGhlIHNvdXJjZSBjb2RlLiAiKSx2KCkpfWZ1bmN0aW9uIEJCZShuLHQpe2lmKDEmbiYmTygwLCJzb3VyY2UtY29kZSIsOCksMiZuKXtsZXQgZT1TKCk7eSgibGluZXMiLGUuZm9jdXNlZFNvdXJjZUZpbGVDb250ZW50LmxpbmVzKSgiZm9jdXNlZExpbmVubyIsZS5mb2N1c2VkU291cmNlTGluZVNwZWMubGluZW5vKSgidXNlRGFya01vZGUiLGUudXNlRGFya01vZGUpfX12YXIgWXJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLmZvY3VzZWRTb3VyY2VGaWxlQ29udGVudD1udWxsLHRoaXMuZm9jdXNlZFNvdXJjZUxpbmVTcGVjPW51bGx9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInNvdXJjZS1maWxlcy1jb21wb25lbnQiXV0saW5wdXRzOntmb2N1c2VkU291cmNlRmlsZUNvbnRlbnQ6ImZvY3VzZWRTb3VyY2VGaWxlQ29udGVudCIsZm9jdXNlZFNvdXJjZUxpbmVTcGVjOiJmb2N1c2VkU291cmNlTGluZVNwZWMiLHVzZURhcmtNb2RlOiJ1c2VEYXJrTW9kZSJ9LGRlY2xzOjgsdmFyczozLGNvbnN0czpbWzEsInNvdXJjZS1maWxlcy1jb250YWluZXIiXSxbMSwiaGVhZGVyLXNlY3Rpb24iXSxbMSwidGl0bGUtdGFnIl0sWyJjbGFzcyIsImZpbGUtbGFiZWwiLDQsIm5nSWYiLCJuZ0lmRWxzZSJdLFsibm9GaWxlU2VsZWN0ZWQiLCIiXSxbMywibGluZXMiLCJmb2N1c2VkTGluZW5vIiwidXNlRGFya01vZGUiLDQsIm5nSWYiXSxbMSwiZmlsZS1sYWJlbCJdLFsxLCJuby1maWxlLXNlbGVjdGVkIl0sWzMsImxpbmVzIiwiZm9jdXNlZExpbmVubyIsInVzZURhcmtNb2RlIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoXygwLCJkaXYiLDApKDEsImRpdiIsMSkoMiwiZGl2IiwyKSxBKDMsIlNvdXJjZSBDb2RlIiksdigpLEUoNCxOQmUsMiwxLCJkaXYiLDMpLEUoNSxMQmUsMiwwLCJuZy10ZW1wbGF0ZSIsbnVsbCw0LHF0KSx2KCksRSg3LEJCZSwxLDMsInNvdXJjZS1jb2RlIiw1KSx2KCkpLDImZSl7bGV0IHI9JGUoNik7Qyg0KSx5KCJuZ0lmIixudWxsIT09aS5mb2N1c2VkU291cmNlTGluZVNwZWMpKCJuZ0lmRWxzZSIsciksQygzKSx5KCJuZ0lmIixudWxsIT09aS5mb2N1c2VkU291cmNlRmlsZUNvbnRlbnQmJm51bGwhPT1pLmZvY3VzZWRTb3VyY2VMaW5lU3BlYyYmbnVsbCE9PWkuZm9jdXNlZFNvdXJjZUZpbGVDb250ZW50LmxpbmVzKX19LGRlcGVuZGVuY2llczpbQmUscXJlXSxzdHlsZXM6WycuaGVhZGVyLXNlY3Rpb25bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7ZGlzcGxheTpmbGV4O2hlaWdodDoyNHB4O3BhZGRpbmctYm90dG9tOjZweDt2ZXJ0aWNhbC1hbGlnbjptaWRkbGU7d2hpdGUtc3BhY2U6bm93cmFwO3dpZHRoOjEwMCV9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmhlYWRlci1zZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmhlYWRlci1zZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjNTU1fS5maWxlLWxhYmVsW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9jaztmb250LXdlaWdodDpub3JtYWw7d2hpdGUtc3BhY2U6bm9ybWFsO292ZXJmbG93LXdyYXA6YW55d2hlcmU7b3ZlcmZsb3cteTphdXRvO3BhZGRpbmc6MCAyMHB4fS5uby1maWxlLXNlbGVjdGVkW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9jaztjb2xvcjojNjY2O3BhZGRpbmc6MCAyMHB4O3doaXRlLXNwYWNlOm5vcm1hbH0uc291cmNlLWZpbGVzLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpmbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjtmb250LWZhbWlseToiUm9ib3RvIE1vbm8iLG1vbm9zcGFjZTtmb250LXNpemU6MTBweDtoZWlnaHQ6MTAwJX0udGl0bGUtdGFnW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9jaztmb250LXdlaWdodDpib2xkO2hlaWdodDoxMDAlO3BhZGRpbmctbGVmdDo2cHg7dmVydGljYWwtYWxpZ246dG9wfXNvdXJjZS1jb2RlW19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4LWdyb3c6MTt3aWR0aDoxMDAlfSddfSksbn0pKCksWHJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuZm9jdXNlZFNvdXJjZUZpbGVDb250ZW50JD10aGlzLnN0b3JlLnNlbGVjdChUUCksdGhpcy5mb2N1c2VkU291cmNlTGluZVNwZWMkPXRoaXMuc3RvcmUuc2VsZWN0KERQKSx0aGlzLnVzZURhcmtNb2RlJD10aGlzLnN0b3JlLnNlbGVjdChRdSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ0Zi1kZWJ1Z2dlci12Mi1zb3VyY2UtZmlsZXMiXV0sZGVjbHM6NCx2YXJzOjksY29uc3RzOltbMywiZm9jdXNlZFNvdXJjZUZpbGVDb250ZW50IiwiZm9jdXNlZFNvdXJjZUxpbmVTcGVjIiwidXNlRGFya01vZGUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihPKDAsInNvdXJjZS1maWxlcy1jb21wb25lbnQiLDApLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpKSwyJmUmJnkoImZvY3VzZWRTb3VyY2VGaWxlQ29udGVudCIsVSgxLDMsaS5mb2N1c2VkU291cmNlRmlsZUNvbnRlbnQkKSkoImZvY3VzZWRTb3VyY2VMaW5lU3BlYyIsVSgyLDUsaS5mb2N1c2VkU291cmNlTGluZVNwZWMkKSkoInVzZURhcmtNb2RlIixVKDMsNyxpLnVzZURhcmtNb2RlJCkpfSxkZXBlbmRlbmNpZXM6W1lyZSxHZV0sZW5jYXBzdWxhdGlvbjoyfSksbn0pKCksVUJlPVsic3RhY2tGcmFtZUFycmF5Il07ZnVuY3Rpb24gekJlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiwxMyksQSgxKSx2KCkpLDImbil7bGV0IGU9UygzKTtDKDEpLGplKCIgIyIsZS5leGVjdXRpb25JbmRleCwiOiAiKX19ZnVuY3Rpb24gakJlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiwxNCksQSgxKSx2KCkpLDImbil7bGV0IGU9UygzKTtDKDEpLGplKCIgIixlLm9wVHlwZSwiICIpfX1mdW5jdGlvbiBHQmUobix0KXtpZigxJm4mJihfKDAsImRpdiIpLEEoMSwiIEVhZ2VyIGV4ZWN1dGlvbiAiKSxFKDIsekJlLDIsMSwic3BhbiIsMTEpLEUoMyxqQmUsMiwxLCJzcGFuIiwxMiksdigpKSwyJm4pe2xldCBlPVMoMik7QygyKSx5KCJuZ0lmIixudWxsIT09ZS5vcFR5cGUpLEMoMSkseSgibmdJZiIsbnVsbCE9PWUub3BUeXBlKX19ZnVuY3Rpb24gV0JlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiwxNiksQSgxKSx2KCkpLDImbil7bGV0IGU9UygzKTtDKDEpLGplKCcgIicsZS5vcE5hbWUsJyIgJyl9fWZ1bmN0aW9uIHFCZShuLHQpe2lmKDEmbiYmKF8oMCwic3BhbiIsMTQpLEEoMSksdigpKSwyJm4pe2xldCBlPVMoMyk7QygxKSxqZSgiICIsZS5vcFR5cGUsIiAiKX19ZnVuY3Rpb24gWUJlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiKSxBKDEsIiBDcmVhdGlvbiBvZiBncmFwaCBvcCAiKSxFKDIsV0JlLDIsMSwic3BhbiIsMTUpLEUoMyxxQmUsMiwxLCJzcGFuIiwxMiksdigpKSwyJm4pe2xldCBlPVMoMik7QygyKSx5KCJuZ0lmIixudWxsIT09ZS5vcE5hbWUpLEMoMSkseSgibmdJZiIsbnVsbCE9PWUub3BUeXBlKX19ZnVuY3Rpb24gWEJlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiwxNyksQSgxKSx2KCkpLDImbil7bGV0IGU9UygyKTtDKDEpLGplKCIgKEhvc3QgbmFtZTogIixlLnN0YWNrRnJhbWVzRm9yRGlzcGxheVswXS5ob3N0X25hbWUsIikgIil9fWZ1bmN0aW9uIFFCZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2Iiw3KSgxLCJzcGFuIikoMiwic3BhbiIsOCksRSgzLEdCZSw0LDIsImRpdiIsOSksRSg0LFlCZSw0LDIsImRpdiIsOSksdigpKCksXyg1LCJkaXYiKSxFKDYsWEJlLDIsMSwic3BhbiIsMTApLHYoKSgpKSwyJm4pe2xldCBlPVMoKTtDKDIpLHkoIm5nU3dpdGNoIixlLmNvZGVMb2NhdGlvblR5cGUpLEMoMSkseSgibmdTd2l0Y2hDYXNlIixlLkNvZGVMb2NhdGlvblR5cGUuRVhFQ1VUSU9OKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsZS5Db2RlTG9jYXRpb25UeXBlLkdSQVBIX09QX0NSRUFUSU9OKSxDKDIpLHkoIm5nSWYiLG51bGwhPT1lLnN0YWNrRnJhbWVzRm9yRGlzcGxheSYmZS5zdGFja0ZyYW1lc0ZvckRpc3BsYXkubGVuZ3RoPjApfX1mdW5jdGlvbiBLQmUobix0KXsxJm4mJihfKDAsImRpdiIsMTgpLEEoMSwiIENsaWNrIGFuIGVhZ2VyIGV4ZWN1dGlvbiBvciBncmFwaCBvcCB0byBzaG93IGl0cyBvcmlnaW5hbCBzdGFjayB0cmFjZS4gIiksdigpKX1mdW5jdGlvbiBaQmUobix0KXsxJm4mJihfKDAsImRpdiIsMjgpLEEoMSwiIFx1MjkxMyAiKSx2KCkpfXZhciBKQmU9ZnVuY3Rpb24obix0KXtyZXR1cm5bbix0XX07ZnVuY3Rpb24gJEJlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IiwyMikoMSwiZGl2IiwyMyksQSgyKSx2KCksXygzLCJkaXYiLDI0KSxFKDQsWkJlLDIsMCwiZGl2IiwyNSksXyg1LCJkaXYiLDI2KSxQKCJjbGljayIsZnVuY3Rpb24oKXtsZXQgbz1vZShlKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoMikub25Tb3VyY2VMaW5lQ2xpY2tlZC5lbWl0KG8pKX0pLEEoNiksdigpLF8oNywiZGl2IiwyNyksQSg4KSx2KCkoKSgpfWlmKDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKDIpO3koIm5nQ2xhc3MiLFFyKDYsSkJlLGUuYmVsb25nc1RvRm9jdXNlZEZpbGU/ImZvY3VzZWQtZmlsZSI6IiIsZS5mb2N1c2VkPyJmb2N1c2VkLXN0YWNrLWZyYW1lIjoiIikpLEMoMSksWmkoInRpdGxlIixlLmZpbGVfcGF0aCksQygxKSxqZSgiICIsZS5jb25jaXNlX2ZpbGVfcGF0aCwiICIpLEMoMikseSgibmdJZiIsaS5zdGlja1RvQm90dG9tbW9zdEZyYW1lSW5Gb2N1c2VkRmlsZSYmZS5mb2N1c2VkKSxDKDIpLGplKCIgTGluZSAiLGUubGluZW5vLCIgIiksQygyKSxqZSgiICIsZS5mdW5jdGlvbl9uYW1lLCIgIil9fWZ1bmN0aW9uIGVWZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwxOSwyMCksRSgyLCRCZSw5LDksImRpdiIsMjEpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygyKSx5KCJuZ0Zvck9mIixlLnN0YWNrRnJhbWVzRm9yRGlzcGxheSl9fWZ1bmN0aW9uIHRWZShuLHQpe312YXIgUXJlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLnN0YWNrRnJhbWVzRm9yRGlzcGxheT1udWxsLHRoaXMub25Tb3VyY2VMaW5lQ2xpY2tlZD1uZXcgRyx0aGlzLkNvZGVMb2NhdGlvblR5cGU9eHN9bmdBZnRlclZpZXdDaGVja2VkKCl7aWYodm9pZCAwPT09dGhpcy5zdGFja0ZyYW1lQXJyYXkpcmV0dXJuO2xldCBlPXRoaXMuc3RhY2tGcmFtZUFycmF5Lm5hdGl2ZUVsZW1lbnQsaT1lLnF1ZXJ5U2VsZWN0b3IoIi5mb2N1c2VkLXN0YWNrLWZyYW1lIik7aWYobnVsbCE9PWkpcmV0dXJuIHZvaWQgdGhpcy5zY3JvbGxUb0VsZW1lbnQoZSxpKTtsZXQgcj1lLnF1ZXJ5U2VsZWN0b3IoIi5zdGFjay1mcmFtZS1jb250YWluZXI6bGFzdC1jaGlsZCIpO251bGwhPT1yJiZ0aGlzLnNjcm9sbFRvRWxlbWVudChlLHIpfXNjcm9sbFRvRWxlbWVudChlLGkpe2Uuc2Nyb2xsVG9wPWkub2Zmc2V0VG9wfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJzdGFjay10cmFjZS1jb21wb25lbnQiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiZvdChVQmUsNSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5zdGFja0ZyYW1lQXJyYXk9ci5maXJzdCl9fSxpbnB1dHM6e2NvZGVMb2NhdGlvblR5cGU6ImNvZGVMb2NhdGlvblR5cGUiLG9wVHlwZToib3BUeXBlIixvcE5hbWU6Im9wTmFtZSIsZXhlY3V0aW9uSW5kZXg6ImV4ZWN1dGlvbkluZGV4IixzdGlja1RvQm90dG9tbW9zdEZyYW1lSW5Gb2N1c2VkRmlsZToic3RpY2tUb0JvdHRvbW1vc3RGcmFtZUluRm9jdXNlZEZpbGUiLHN0YWNrRnJhbWVzRm9yRGlzcGxheToic3RhY2tGcmFtZXNGb3JEaXNwbGF5In0sb3V0cHV0czp7b25Tb3VyY2VMaW5lQ2xpY2tlZDoib25Tb3VyY2VMaW5lQ2xpY2tlZCJ9LGRlY2xzOjEwLHZhcnM6NCxjb25zdHM6W1sxLCJzdGFjay10cmFjZS1jb250YWluZXIiXSxbMSwic3RhY2stdHJhY2UtaGVhZGVyIl0sWzEsInN0YWNrLXRyYWNlLXRpdGxlIl0sWyJjbGFzcyIsInN0YWNrLXRyYWNlLWF1eC1pbmZvIiw0LCJuZ0lmIiwibmdJZkVsc2UiXSxbIm5vU3RhY2tUcmFjZSIsIiJdLFsiY2xhc3MiLCJzdGFjay1mcmFtZS1hcnJheSIsNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJsb2FkaW5nU2VjdGlvbiIsIiJdLFsxLCJzdGFjay10cmFjZS1hdXgtaW5mbyJdLFsxLCJjb2RlLWxvY2F0aW9uLW9yaWdpbiIsMywibmdTd2l0Y2giXSxbNCwibmdTd2l0Y2hDYXNlIl0sWyJjbGFzcyIsInN0YWNrLXRyYWNlLWhvc3QtbmFtZSIsNCwibmdJZiJdLFsiY2xhc3MiLCJlYWdlci1leGVjdXRpb24taW5kZXgiLDQsIm5nSWYiXSxbImNsYXNzIiwib3AtdHlwZSIsNCwibmdJZiJdLFsxLCJlYWdlci1leGVjdXRpb24taW5kZXgiXSxbMSwib3AtdHlwZSJdLFsiY2xhc3MiLCJvcC1uYW1lIiw0LCJuZ0lmIl0sWzEsIm9wLW5hbWUiXSxbMSwic3RhY2stdHJhY2UtaG9zdC1uYW1lIl0sWzEsInN0YWNrLXRyYWNlLWF1eC1pbmZvIiwibm8tc3RhY2stdHJhY2UiXSxbMSwic3RhY2stZnJhbWUtYXJyYXkiXSxbInN0YWNrRnJhbWVBcnJheSIsIiJdLFsiY2xhc3MiLCJzdGFjay1mcmFtZS1jb250YWluZXIiLDMsIm5nQ2xhc3MiLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsxLCJzdGFjay1mcmFtZS1jb250YWluZXIiLDMsIm5nQ2xhc3MiXSxbMSwic3RhY2stZnJhbWUtZmlsZS1wYXRoIiwzLCJ0aXRsZSJdLFsxLCJzdGFjay1mcmFtZS1saW5lbm8tZnVuY3Rpb24iXSxbImNsYXNzIiwic3RpY2stdG8tYm90dG9tbW9zdC1pbmRpY2F0b3IiLCJ0aXRsZSIsIlN0aWNraW5nIHRvIHRoZSBib3R0b21tb3N0IGZyYW1lIGluIHRoZSBjdXJyZW50IHNvdXJjZSBmaWxlIHdoZW4gbmF2aWdhdGluZyBleGVjdXRpb25zIGFuZCBncmFwaCBvcHMuIFRvIHJlbW92ZSB0aGlzIHN0aWNraW5nLCBjbGljayBhbnkgbm9uLWJvdHRvbW1vc3Qgc3RhY2sgZnJhbWUuIiw0LCJuZ0lmIl0sWzEsInN0YWNrLWZyYW1lLWxpbmVubyIsMywiY2xpY2siXSxbMSwic3RhY2stZnJhbWUtZnVuY3Rpb24iXSxbInRpdGxlIiwiU3RpY2tpbmcgdG8gdGhlIGJvdHRvbW1vc3QgZnJhbWUgaW4gdGhlIGN1cnJlbnQgc291cmNlIGZpbGUgd2hlbiBuYXZpZ2F0aW5nIGV4ZWN1dGlvbnMgYW5kIGdyYXBoIG9wcy4gVG8gcmVtb3ZlIHRoaXMgc3RpY2tpbmcsIGNsaWNrIGFueSBub24tYm90dG9tbW9zdCBzdGFjayBmcmFtZS4iLDEsInN0aWNrLXRvLWJvdHRvbW1vc3QtaW5kaWNhdG9yIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoXygwLCJkaXYiLDApKDEsImRpdiIsMSkoMiwic3BhbiIsMiksQSgzLCIgU3RhY2sgVHJhY2UgIiksdigpLEUoNCxRQmUsNyw0LCJkaXYiLDMpLEUoNSxLQmUsMiwwLCJuZy10ZW1wbGF0ZSIsbnVsbCw0LHF0KSx2KCksRSg3LGVWZSwzLDEsImRpdiIsNSksRSg4LHRWZSwwLDAsIm5nLXRlbXBsYXRlIixudWxsLDYscXQpLHYoKSksMiZlKXtsZXQgcj0kZSg2KSxvPSRlKDkpO0MoNCkseSgibmdJZiIsbnVsbCE9PWkuY29kZUxvY2F0aW9uVHlwZSkoIm5nSWZFbHNlIixyKSxDKDMpLHkoIm5nSWYiLG51bGwhPT1pLnN0YWNrRnJhbWVzRm9yRGlzcGxheSkoIm5nSWZFbHNlIixvKX19LGRlcGVuZGVuY2llczpbRm4sZG4sQmUsQ3IsVXJdLHN0eWxlczpbJy5mb2N1c2VkLWZpbGVbX25nY29udGVudC0lQ09NUCVde2ZvbnQtd2VpZ2h0OmJvbGR9LmZvY3VzZWQtc3RhY2stZnJhbWVbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMTExLDAsLjMpfS5uby1zdGFjay10cmFjZVtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6Z3JheX0ub3AtbmFtZVtfbmdjb250ZW50LSVDT01QJV17d29yZC13cmFwOmFueXdoZXJlfS5vcC10eXBlW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNlY2VmZjE7Ym9yZGVyOjFweCBzb2xpZCAjZWJlYmViO2JvcmRlci1yYWRpdXM6NHB4O2ZvbnQtZmFtaWx5OiJSb2JvdG8gTW9ubyIsbW9ub3NwYWNlO2ZvbnQtc2l6ZToxMHB4O2hlaWdodDoxNHB4O2xpbmUtaGVpZ2h0OjE0cHg7cGFkZGluZzoxcHggM3B4O3dpZHRoOm1heC1jb250ZW50fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5vcC10eXBlW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLm9wLXR5cGVbX25nY29udGVudC0lQ09NUCVde2JvcmRlcjoxcHggc29saWQgIzU1NX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAub3AtdHlwZVtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5vcC10eXBlW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiM0NTVhNjR9LnN0YWNrLWZyYW1lLWFycmF5W19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdy14OmhpZGRlbjtvdmVyZmxvdy15OmF1dG87d2lkdGg6Y2FsYygxMDAlIC0gOHB4KX0uc3RhY2stZnJhbWUtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjYTBhMGEwfS5zdGFjay1mcmFtZS1maWxlLXBhdGhbX25nY29udGVudC0lQ09NUCVde21heC13aWR0aDoxODBweDt3aWR0aDoxODBweH0uc3RhY2stZnJhbWUtbGluZW5vLWZ1bmN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXt0ZXh0LWFsaWduOnJpZ2h0O3doaXRlLXNwYWNlOm5vd3JhcH0uc3RhY2stZnJhbWUtZnVuY3Rpb25bX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6aW5saW5lLWJsb2NrO21heC13aWR0aDoyMDBweDtwYWRkaW5nLWxlZnQ6MTBweDt0ZXh0LWFsaWduOmxlZnQ7d2hpdGUtc3BhY2U6bm9ybWFsO3dpZHRoOjIwMHB4O3dvcmQtd3JhcDphbnl3aGVyZX0uc3RhY2stZnJhbWUtbGluZW5vW19uZ2NvbnRlbnQtJUNPTVAlXXtjdXJzb3I6cG9pbnRlcjtkaXNwbGF5OmlubGluZS1ibG9jazttYXgtd2lkdGg6ODBweDt0ZXh0LWFsaWduOmxlZnQ7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZTt3aWR0aDo4MHB4fS5zdGFjay10cmFjZS1hdXgtaW5mb1tfbmdjb250ZW50LSVDT01QJV17bWFyZ2luLXRvcDoxNXB4O3BhZGRpbmctbGVmdDoyNHB4fS5zdGFjay10cmFjZS1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjFweCBzb2xpZCAjZWJlYmViO2JveC1zaXppbmc6Ym9yZGVyLWJveDtkaXNwbGF5OmZsZXg7ZmxleC1mbG93OmNvbHVtbjtmb250LXNpemU6MTBweDtmb250LWZhbWlseToiUm9ib3RvIE1vbm8iLG1vbm9zcGFjZTtoZWlnaHQ6MTAwJTttYXJnaW4tbGVmdDo4cHg7bWF4LWhlaWdodDozNjBweDtvdmVyZmxvdy14OmhpZGRlbjtvdmVyZmxvdy15OmhpZGRlbjtwYWRkaW5nLWxlZnQ6OHB4O3dpZHRoOjEwMCV9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnN0YWNrLXRyYWNlLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5zdGFjay10cmFjZS1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjFweCBzb2xpZCAjNTU1fS5zdGFjay10cmFjZS1oZWFkZXJbX25nY29udGVudC0lQ09NUCVde2JveC1zaGFkb3c6MCA1cHggM3B4IC0zcHggI2NjYztwYWRkaW5nLWJvdHRvbTozcHh9LnN0YWNrLXRyYWNlLWhvc3QtbmFtZVtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6Z3JheX0uc3RhY2stdHJhY2UtdGl0bGVbX25nY29udGVudC0lQ09NUCVde2ZvbnQtd2VpZ2h0OmJvbGR9LnN0aWNrLXRvLWJvdHRvbW1vc3QtaW5kaWNhdG9yW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9jaztmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMnB4O3BhZGRpbmctcmlnaHQ6M3B4fSddfSksbn0pKCksS3JlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuY29kZUxvY2F0aW9uVHlwZSQ9dGhpcy5zdG9yZS5waXBlKHZ0KEooUHcsaT0+bnVsbD09PWk/bnVsbDppLmNvZGVMb2NhdGlvblR5cGUpKSksdGhpcy5vcFR5cGUkPXRoaXMuc3RvcmUucGlwZSh2dChKKFB3LGk9Pm51bGw9PT1pP251bGw6aS5vcFR5cGUpKSksdGhpcy5vcE5hbWUkPXRoaXMuc3RvcmUucGlwZSh2dChKKFB3LGk9Pm51bGw9PT1pfHxpLmNvZGVMb2NhdGlvblR5cGUhPT14cy5HUkFQSF9PUF9DUkVBVElPTj9udWxsOmkub3BOYW1lKSkpLHRoaXMuZXhlY3V0aW9uSW5kZXgkPXRoaXMuc3RvcmUucGlwZSh2dChKKFB3LGk9Pm51bGw9PT1pfHxpLmNvZGVMb2NhdGlvblR5cGUhPT14cy5FWEVDVVRJT04/bnVsbDppLmV4ZWN1dGlvbkluZGV4KSkpLHRoaXMuc3RpY2tUb0JvdHRvbW1vc3RGcmFtZUluRm9jdXNlZEZpbGUkPXRoaXMuc3RvcmUucGlwZSh2dChEcmUpKSx0aGlzLnN0YWNrRnJhbWVzRm9yRGlzcGxheSQ9dGhpcy5zdG9yZS5waXBlKHZ0KEooRXJlLERQLChpLHIpPT57aWYobnVsbD09PWkpcmV0dXJuIG51bGw7bGV0IG89W107Zm9yKGxldCBzIG9mIGkpe2xldHtob3N0X25hbWU6YSxmaWxlX3BhdGg6bCxsaW5lbm86YyxmdW5jdGlvbl9uYW1lOnV9PXMsZD1sLnNwbGl0KCIvIiksaD1udWxsIT09ciYmYT09PXIuaG9zdF9uYW1lJiZsPT09ci5maWxlX3BhdGg7by5wdXNoKHtob3N0X25hbWU6YSxmaWxlX3BhdGg6bCxjb25jaXNlX2ZpbGVfcGF0aDpkW2QubGVuZ3RoLTFdLGxpbmVubzpjLGZ1bmN0aW9uX25hbWU6dSxiZWxvbmdzVG9Gb2N1c2VkRmlsZTpoLGZvY3VzZWQ6aCYmYz09PXIubGluZW5vfSl9cmV0dXJuIG99KSkpfW9uU291cmNlTGluZUNsaWNrZWQoZSl7bGV0e2hvc3RfbmFtZTppLGZpbGVfcGF0aDpyLGxpbmVubzpvLGZ1bmN0aW9uX25hbWU6c309ZTt0aGlzLnN0b3JlLmRpc3BhdGNoKEp2KHtzdGFja0ZyYW1lOntob3N0X25hbWU6aSxmaWxlX3BhdGg6cixsaW5lbm86byxmdW5jdGlvbl9uYW1lOnN9fSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGYtZGVidWdnZXItdjItc3RhY2stdHJhY2UiXV0sZGVjbHM6Nyx2YXJzOjE4LGNvbnN0czpbWzMsImNvZGVMb2NhdGlvblR5cGUiLCJvcFR5cGUiLCJvcE5hbWUiLCJleGVjdXRpb25JbmRleCIsInN0aWNrVG9Cb3R0b21tb3N0RnJhbWVJbkZvY3VzZWRGaWxlIiwic3RhY2tGcmFtZXNGb3JEaXNwbGF5Iiwib25Tb3VyY2VMaW5lQ2xpY2tlZCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwic3RhY2stdHJhY2UtY29tcG9uZW50IiwwKSxQKCJvblNvdXJjZUxpbmVDbGlja2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblNvdXJjZUxpbmVDbGlja2VkKG8pfSksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIiksQig0LCJhc3luYyIpLEIoNSwiYXN5bmMiKSxCKDYsImFzeW5jIiksdigpKSwyJmUmJnkoImNvZGVMb2NhdGlvblR5cGUiLFUoMSw2LGkuY29kZUxvY2F0aW9uVHlwZSQpKSgib3BUeXBlIixVKDIsOCxpLm9wVHlwZSQpKSgib3BOYW1lIixVKDMsMTAsaS5vcE5hbWUkKSkoImV4ZWN1dGlvbkluZGV4IixVKDQsMTIsaS5leGVjdXRpb25JbmRleCQpKSgic3RpY2tUb0JvdHRvbW1vc3RGcmFtZUluRm9jdXNlZEZpbGUiLFUoNSwxNCxpLnN0aWNrVG9Cb3R0b21tb3N0RnJhbWVJbkZvY3VzZWRGaWxlJCkpKCJzdGFja0ZyYW1lc0ZvckRpc3BsYXkiLFUoNiwxNixpLnN0YWNrRnJhbWVzRm9yRGlzcGxheSQpKX0sZGVwZW5kZW5jaWVzOltRcmUsR2VdLGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpLHJWZT1mdW5jdGlvbihuLHQpe3JldHVybnt0ZW5zb3JEZWJ1Z01vZGU6bixhcnJheTp0fX07ZnVuY3Rpb24gb1ZlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDEyKSgxLCJkaXYiLDEzKSxBKDIpLHYoKSxfKDMsImRpdiIsMTQpLE8oNCwiZGVidWctdGVuc29yLXZhbHVlIiwxNSksdigpKCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT10LmluZGV4LHI9UygzKTtDKDIpLGplKCJPdXRwdXQgc2xvdCAiLGksIjoiKSxDKDIpLHkoImRlYnVnVGVuc29yVmFsdWUiLHIucGFyc2VEZWJ1Z1RlbnNvclZhbHVlKFFyKDIsclZlLHIudGVuc29yRGVidWdNb2RlLGUpKSl9fWZ1bmN0aW9uIHNWZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwxMCksRSgxLG9WZSw1LDUsImRpdiIsMTEpLHYoKSksMiZuKXtsZXQgZT1TKDIpO0MoMSkseSgibmdGb3JPZiIsZS5kZWJ1Z1RlbnNvclZhbHVlcyl9fWZ1bmN0aW9uIGFWZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IikoMSwiZGl2IikoMiwiZGl2IiwzKSgzLCJzcGFuIiw0KSxBKDQsIiBPcDogIiksdigpLF8oNSwic3BhbiIsNSksQSg2KSx2KCkoKSxfKDcsImRpdiIsMykoOCwic3BhbiIsNCksQSg5LCIgIyBvZiBpbnB1dCB0ZW5zb3JzOiAiKSx2KCksXygxMCwic3BhbiIsNiksQSgxMSksdigpKCksXygxMiwiZGl2IiwzKSgxMywic3BhbiIsNCksQSgxNCwiICMgb2Ygb3V0cHV0IHRlbnNvcnM6ICIpLHYoKSxfKDE1LCJzcGFuIiw3KSxBKDE2KSx2KCksXygxNywic3BhbiIsOCksQSgxOCksdigpKCksRSgxOSxzVmUsMiwxLCJkaXYiLDkpLHYoKSgpKSwyJm4pe2xldCBlPVMoKTtDKDYpLGplKCIgIixlLmZvY3VzZWRFeGVjdXRpb25EYXRhLm9wX3R5cGUsIiAiKSxDKDUpLGplKCIgIixudWxsPT1lLmZvY3VzZWRFeGVjdXRpb25EYXRhLmlucHV0X3RlbnNvcl9pZHM/MDplLmZvY3VzZWRFeGVjdXRpb25EYXRhLmlucHV0X3RlbnNvcl9pZHMubGVuZ3RoLCIgIiksQyg1KSxqZSgiICIsbnVsbD09ZS5mb2N1c2VkRXhlY3V0aW9uRGF0YS5vdXRwdXRfdGVuc29yX2lkcz8wOmUuZm9jdXNlZEV4ZWN1dGlvbkRhdGEub3V0cHV0X3RlbnNvcl9pZHMubGVuZ3RoLCIgIiksQygyKSxqZSgiIChkZWJ1ZyBtb2RlOiAiLGUuVGVuc29yRGVidWdNb2RlW2UudGVuc29yRGVidWdNb2RlXSwiKSAiKSxDKDEpLHkoIm5nSWYiLGUuaGFzRGVidWdUZW5zb3JWYWx1ZXMpfX1mdW5jdGlvbiBsVmUobix0KXt9dmFyIFpyZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy50ZW5zb3JEZWJ1Z01vZGU9YXMuVU5TUEVDSUZJRUQsdGhpcy5oYXNEZWJ1Z1RlbnNvclZhbHVlcz0hMSx0aGlzLmRlYnVnVGVuc29yVmFsdWVzPW51bGwsdGhpcy5kZWJ1Z1RlbnNvckR0eXBlcz1udWxsLHRoaXMuVGVuc29yRGVidWdNb2RlPWFzLHRoaXMucGFyc2VEZWJ1Z1RlbnNvclZhbHVlPUFQfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJleGVjdXRpb24tZGF0YS1jb21wb25lbnQiXV0saW5wdXRzOntmb2N1c2VkRXhlY3V0aW9uSW5kZXg6ImZvY3VzZWRFeGVjdXRpb25JbmRleCIsZm9jdXNlZEV4ZWN1dGlvbkRhdGE6ImZvY3VzZWRFeGVjdXRpb25EYXRhIix0ZW5zb3JEZWJ1Z01vZGU6InRlbnNvckRlYnVnTW9kZSIsaGFzRGVidWdUZW5zb3JWYWx1ZXM6Imhhc0RlYnVnVGVuc29yVmFsdWVzIixkZWJ1Z1RlbnNvclZhbHVlczoiZGVidWdUZW5zb3JWYWx1ZXMiLGRlYnVnVGVuc29yRHR5cGVzOiJkZWJ1Z1RlbnNvckR0eXBlcyJ9LGRlY2xzOjcsdmFyczozLGNvbnN0czpbWzEsImZvY3VzLWV4ZWN1dGlvbi1jb250YWluZXIiXSxbNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJsb2FkaW5nX3NlY3Rpb24iLCIiXSxbMSwiZXhlY3V0aW9uLWRhdGEtZmllbGQiXSxbMSwiZXhlY3V0aW9uLWRhdGEta2V5Il0sWzEsImV4ZWN1dGlvbi1kYXRhLXZhbHVlIiwib3AtdHlwZSJdLFsxLCJleGVjdXRpb24tZGF0YS12YWx1ZSIsImlucHV0LXRlbnNvcnMiXSxbMSwiZXhlY3V0aW9uLWRhdGEtdmFsdWUiLCJvdXRwdXQtdGVuc29ycyJdLFsxLCJleGVjdXRpb24tZGF0YS12YWx1ZSJdLFsiY2xhc3MiLCJvdXRwdXQtc2xvdHMiLDQsIm5nSWYiXSxbMSwib3V0cHV0LXNsb3RzIl0sWyJjbGFzcyIsIm91dHB1dC1zbG90LWNvbnRhaW5lciIsNCwibmdGb3IiLCJuZ0Zvck9mIl0sWzEsIm91dHB1dC1zbG90LWNvbnRhaW5lciJdLFsxLCJvdXRwdXQtc2xvdC1udW1iZXIiXSxbMSwib3V0cHV0LXNsb3QtZGVidWctdGVuc29yLXZhbHVlIl0sWzMsImRlYnVnVGVuc29yVmFsdWUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJihfKDAsImRpdiIsMCkoMSwiZGl2IikoMiwic3BhbiIpLEEoMyksdigpKCksRSg0LGFWZSwyMCw1LCJkaXYiLDEpLEUoNSxsVmUsMCwwLCJuZy10ZW1wbGF0ZSIsbnVsbCwyLHF0KSx2KCkpLDImZSl7bGV0IHI9JGUoNik7QygzKSxqZSgiIFB5dGhvbiBFeGVjdXRpb24gIyIsaS5mb2N1c2VkRXhlY3V0aW9uSW5kZXgsIiAiKSxDKDEpLHkoIm5nSWYiLG51bGwhPT1pLmZvY3VzZWRFeGVjdXRpb25EYXRhKSgibmdJZkVsc2UiLHIpfX0sZGVwZW5kZW5jaWVzOltkbixCZSxJUF0sc3R5bGVzOlsnLmRlYnVnLXRlbnNvci12YWx1ZXMtdGFibGVbX25nY29udGVudC0lQ09NUCVde3dpZHRoOjEwMCV9LmRlYnVnLXRlbnNvci12YWx1ZXMtdGFibGVbX25nY29udGVudC0lQ09NUCVdICAgdGRbX25nY29udGVudC0lQ09NUCVde2JvcmRlci10b3A6MXB4IHNvbGlkICMwMDA7dGV4dC1hbGlnbjpsZWZ0fS5kZWJ1Zy10ZW5zb3ItdmFsdWVzLXRhYmxlW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHRoW19uZ2NvbnRlbnQtJUNPTVAlXXt0ZXh0LWFsaWduOmxlZnR9LmV4ZWN1dGlvbi1kYXRhLWZpZWxkW19uZ2NvbnRlbnQtJUNPTVAlXXt3aGl0ZS1zcGFjZTpub3dyYXB9LmV4ZWN1dGlvbi1kYXRhLWtleVtfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTppbmxpbmUtYmxvY2s7bWF4LXdpZHRoOjEyMHB4O3RleHQtYWxpZ246cmlnaHQ7d2lkdGg6MTIwcHh9LmV4ZWN1dGlvbi1kYXRhLXZhbHVlW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9jazttYXJnaW4tbGVmdDoxMHB4fS5mb2N1cy1leGVjdXRpb24tY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNmZmNjODA7Ym9yZGVyLXJhZGl1czo0cHg7Zm9udC1zaXplOjEycHg7aGVpZ2h0OjEyMHB4O3BhZGRpbmc6NXB4O3dpZHRoOjM2MHB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5mb2N1cy1leGVjdXRpb24tY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmZvY3VzLWV4ZWN1dGlvbi1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2U2NTEwMH0ub3V0cHV0LXNsb3RzW19uZ2NvbnRlbnQtJUNPTVAlXXtoZWlnaHQ6NjBweDtvdmVyZmxvdy14OmF1dG87b3ZlcmZsb3cteTphdXRvfS5vdXRwdXQtc2xvdC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci10b3A6MXB4IHNvbGlkICNlYmViZWI7bWFyZ2luLXRvcDo1cHg7cGFkZGluZzoycHggMDt2ZXJ0aWNhbC1hbGlnbjp0b3B9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLm91dHB1dC1zbG90LWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5vdXRwdXQtc2xvdC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci10b3A6MXB4IHNvbGlkICM1NTV9Lm91dHB1dC1zbG90LW51bWJlcltfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpibG9jaztmb250LWZhbWlseToiUm9ib3RvIE1vbm8iLG1vbm9zcGFjZX0ub3V0cHV0LXNsb3QtZGVidWctdGVuc29yLXZhbHVlW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmJsb2NrO21hcmdpbjozcHggMCAzcHggMzBweH0ub3V0cHV0LXRlbnNvcnNbX25nY29udGVudC0lQ09NUCVde21hcmdpbi10b3A6NXB4fSddfSksbn0pKCksSnJlPSJVbmtub3duIGR0eXBlIiwkcmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5mb2N1c2VkRXhlY3V0aW9uRGF0YSQ9dGhpcy5zdG9yZS5waXBlKHZ0KHBnKSksdGhpcy50ZW5zb3JEZWJ1Z01vZGUkPXRoaXMuc3RvcmUucGlwZSh2dChKKHBnLGk9Pm51bGw9PT1pP2FzLlVOU1BFQ0lGSUVEOmkudGVuc29yX2RlYnVnX21vZGUpKSksdGhpcy5oYXNEZWJ1Z1RlbnNvclZhbHVlcyQ9dGhpcy5zdG9yZS5waXBlKHZ0KEoocGcsaT0+e2lmKG51bGw9PT1pfHxudWxsPT09aS5kZWJ1Z190ZW5zb3JfdmFsdWVzKXJldHVybiExO2ZvcihsZXQgciBvZiBpLmRlYnVnX3RlbnNvcl92YWx1ZXMpaWYobnVsbCE9PXImJnIubGVuZ3RoPjApcmV0dXJuITA7cmV0dXJuITF9KSkpLHRoaXMuZGVidWdUZW5zb3JWYWx1ZXMkPXRoaXMuc3RvcmUucGlwZSh2dChKKHBnLGk9Pm51bGw9PT1pP251bGw6aS5kZWJ1Z190ZW5zb3JfdmFsdWVzKSkpLHRoaXMuZGVidWdUZW5zb3JEdHlwZXMkPXRoaXMuc3RvcmUucGlwZSh2dChKKHBnLGk9PntpZihudWxsPT09aXx8bnVsbD09PWkuZGVidWdfdGVuc29yX3ZhbHVlc3x8aS50ZW5zb3JfZGVidWdfbW9kZSE9PWFzLkZVTExfSEVBTFRIJiZpLnRlbnNvcl9kZWJ1Z19tb2RlIT09YXMuU0hBUEUpcmV0dXJuIG51bGw7bGV0IHI9W107Zm9yKGxldCBvIG9mIGkuZGVidWdfdGVuc29yX3ZhbHVlcylpZihudWxsPT09bylyLnB1c2goSnJlKTtlbHNle2xldCBzPVN0cmluZyhpLnRlbnNvcl9kZWJ1Z19tb2RlPT09YXMuRlVMTF9IRUFMVEg/b1syXTpvWzFdKTtyLnB1c2goUndbc118fEpyZSl9cmV0dXJuIHJ9KSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGYtZGVidWdnZXItdjItZXhlY3V0aW9uLWRhdGEiXV0saW5wdXRzOntmb2N1c2VkRXhlY3V0aW9uSW5kZXg6ImZvY3VzZWRFeGVjdXRpb25JbmRleCJ9LGRlY2xzOjYsdmFyczoxNixjb25zdHM6W1szLCJmb2N1c2VkRXhlY3V0aW9uSW5kZXgiLCJmb2N1c2VkRXhlY3V0aW9uRGF0YSIsInRlbnNvckRlYnVnTW9kZSIsImhhc0RlYnVnVGVuc29yVmFsdWVzIiwiZGVidWdUZW5zb3JWYWx1ZXMiLCJkZWJ1Z1RlbnNvckR0eXBlcyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKE8oMCwiZXhlY3V0aW9uLWRhdGEtY29tcG9uZW50IiwwKSxCKDEsImFzeW5jIiksQigyLCJhc3luYyIpLEIoMywiYXN5bmMiKSxCKDQsImFzeW5jIiksQig1LCJhc3luYyIpKSwyJmUmJnkoImZvY3VzZWRFeGVjdXRpb25JbmRleCIsaS5mb2N1c2VkRXhlY3V0aW9uSW5kZXgpKCJmb2N1c2VkRXhlY3V0aW9uRGF0YSIsVSgxLDYsaS5mb2N1c2VkRXhlY3V0aW9uRGF0YSQpKSgidGVuc29yRGVidWdNb2RlIixVKDIsOCxpLnRlbnNvckRlYnVnTW9kZSQpKSgiaGFzRGVidWdUZW5zb3JWYWx1ZXMiLFUoMywxMCxpLmhhc0RlYnVnVGVuc29yVmFsdWVzJCkpKCJkZWJ1Z1RlbnNvclZhbHVlcyIsVSg0LDEyLGkuZGVidWdUZW5zb3JWYWx1ZXMkKSkoImRlYnVnVGVuc29yRHR5cGVzIixVKDUsMTQsaS5kZWJ1Z1RlbnNvckR0eXBlcyQpKX0sZGVwZW5kZW5jaWVzOltacmUsR2VdLGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpLGhWZT1bInNsaWRlcldyYXBwZXIiXSxjYz1sYSh7cGFzc2l2ZTohMX0pLHZWZT17cHJvdmlkZTpObyx1c2VFeGlzdGluZzpKbigoKT0+dXApLG11bHRpOiEwfSx5VmU9b2Moa28oc28oY2xhc3N7Y29uc3RydWN0b3Iobil7dGhpcy5fZWxlbWVudFJlZj1ufX0pLCJhY2NlbnQiKSksdXA9KCgpPT57Y2xhc3MgbiBleHRlbmRzIHlWZXtjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsLGMpe3N1cGVyKGUpLHRoaXMuX2ZvY3VzTW9uaXRvcj1pLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmPXIsdGhpcy5fZGlyPW8sdGhpcy5fbmdab25lPWEsdGhpcy5fYW5pbWF0aW9uTW9kZT1jLHRoaXMuX2ludmVydD0hMSx0aGlzLl9tYXg9MTAwLHRoaXMuX21pbj0wLHRoaXMuX3N0ZXA9MSx0aGlzLl90aHVtYkxhYmVsPSExLHRoaXMuX3RpY2tJbnRlcnZhbD0wLHRoaXMuX3ZhbHVlPW51bGwsdGhpcy5fdmVydGljYWw9ITEsdGhpcy5jaGFuZ2U9bmV3IEcsdGhpcy5pbnB1dD1uZXcgRyx0aGlzLnZhbHVlQ2hhbmdlPW5ldyBHLHRoaXMub25Ub3VjaGVkPSgpPT57fSx0aGlzLl9wZXJjZW50PTAsdGhpcy5faXNTbGlkaW5nPW51bGwsdGhpcy5faXNBY3RpdmU9ITEsdGhpcy5fdGlja0ludGVydmFsUGVyY2VudD0wLHRoaXMuX3NsaWRlckRpbWVuc2lvbnM9bnVsbCx0aGlzLl9jb250cm9sVmFsdWVBY2Nlc3NvckNoYW5nZUZuPSgpPT57fSx0aGlzLl9kaXJDaGFuZ2VTdWJzY3JpcHRpb249U24uRU1QVFksdGhpcy5fcG9pbnRlckRvd249dT0+e3RoaXMuZGlzYWJsZWR8fHRoaXMuX2lzU2xpZGluZ3x8IU93KHUpJiYwIT09dS5idXR0b258fHRoaXMuX25nWm9uZS5ydW4oKCk9Pnt0aGlzLl90b3VjaElkPU93KHUpP2Z1bmN0aW9uKG4sdCl7Zm9yKGxldCBlPTA7ZTxuLnRvdWNoZXMubGVuZ3RoO2UrKyl7bGV0IGk9bi50b3VjaGVzW2VdLnRhcmdldDtpZih0PT09aXx8dC5jb250YWlucyhpKSlyZXR1cm4gbi50b3VjaGVzW2VdLmlkZW50aWZpZXJ9fSh1LHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudCk6dm9pZCAwO2xldCBkPXRvZSh1LHRoaXMuX3RvdWNoSWQpO2lmKGQpe2xldCBwPXRoaXMudmFsdWU7dGhpcy5faXNTbGlkaW5nPSJwb2ludGVyIix0aGlzLl9sYXN0UG9pbnRlckV2ZW50PXUsdGhpcy5fZm9jdXNIb3N0RWxlbWVudCgpLHRoaXMuX29uTW91c2VlbnRlcigpLHRoaXMuX2JpbmRHbG9iYWxFdmVudHModSksdGhpcy5fZm9jdXNIb3N0RWxlbWVudCgpLHRoaXMuX3VwZGF0ZVZhbHVlRnJvbVBvc2l0aW9uKGQpLHRoaXMuX3ZhbHVlT25TbGlkZVN0YXJ0PXAsdS5jYW5jZWxhYmxlJiZ1LnByZXZlbnREZWZhdWx0KCkscCE9dGhpcy52YWx1ZSYmdGhpcy5fZW1pdElucHV0RXZlbnQoKX19KX0sdGhpcy5fcG9pbnRlck1vdmU9dT0+e2lmKCJwb2ludGVyIj09PXRoaXMuX2lzU2xpZGluZyl7bGV0IGQ9dG9lKHUsdGhpcy5fdG91Y2hJZCk7aWYoZCl7dS5jYW5jZWxhYmxlJiZ1LnByZXZlbnREZWZhdWx0KCk7bGV0IHA9dGhpcy52YWx1ZTt0aGlzLl9sYXN0UG9pbnRlckV2ZW50PXUsdGhpcy5fdXBkYXRlVmFsdWVGcm9tUG9zaXRpb24oZCkscCE9dGhpcy52YWx1ZSYmdGhpcy5fZW1pdElucHV0RXZlbnQoKX19fSx0aGlzLl9wb2ludGVyVXA9dT0+eyJwb2ludGVyIj09PXRoaXMuX2lzU2xpZGluZyYmKCFPdyh1KXx8Im51bWJlciIhPXR5cGVvZiB0aGlzLl90b3VjaElkfHx2VSh1LmNoYW5nZWRUb3VjaGVzLHRoaXMuX3RvdWNoSWQpKSYmKHUuY2FuY2VsYWJsZSYmdS5wcmV2ZW50RGVmYXVsdCgpLHRoaXMuX3JlbW92ZUdsb2JhbEV2ZW50cygpLHRoaXMuX2lzU2xpZGluZz1udWxsLHRoaXMuX3RvdWNoSWQ9dm9pZCAwLHRoaXMuX3ZhbHVlT25TbGlkZVN0YXJ0IT10aGlzLnZhbHVlJiYhdGhpcy5kaXNhYmxlZCYmdGhpcy5fZW1pdENoYW5nZUV2ZW50KCksdGhpcy5fdmFsdWVPblNsaWRlU3RhcnQ9dGhpcy5fbGFzdFBvaW50ZXJFdmVudD1udWxsKX0sdGhpcy5fd2luZG93Qmx1cj0oKT0+e3RoaXMuX2xhc3RQb2ludGVyRXZlbnQmJnRoaXMuX3BvaW50ZXJVcCh0aGlzLl9sYXN0UG9pbnRlckV2ZW50KX0sdGhpcy5fZG9jdW1lbnQ9bCx0aGlzLnRhYkluZGV4PXBhcnNlSW50KHMpfHwwLGEucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntsZXQgdT1lLm5hdGl2ZUVsZW1lbnQ7dS5hZGRFdmVudExpc3RlbmVyKCJtb3VzZWRvd24iLHRoaXMuX3BvaW50ZXJEb3duLGNjKSx1LmFkZEV2ZW50TGlzdGVuZXIoInRvdWNoc3RhcnQiLHRoaXMuX3BvaW50ZXJEb3duLGNjKX0pfWdldCBpbnZlcnQoKXtyZXR1cm4gdGhpcy5faW52ZXJ0fXNldCBpbnZlcnQoZSl7dGhpcy5faW52ZXJ0PVJ0KGUpfWdldCBtYXgoKXtyZXR1cm4gdGhpcy5fbWF4fXNldCBtYXgoZSl7dGhpcy5fbWF4PUJpKGUsdGhpcy5fbWF4KSx0aGlzLl9wZXJjZW50PXRoaXMuX2NhbGN1bGF0ZVBlcmNlbnRhZ2UodGhpcy5fdmFsdWUpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpfWdldCBtaW4oKXtyZXR1cm4gdGhpcy5fbWlufXNldCBtaW4oZSl7dGhpcy5fbWluPUJpKGUsdGhpcy5fbWluKSx0aGlzLl9wZXJjZW50PXRoaXMuX2NhbGN1bGF0ZVBlcmNlbnRhZ2UodGhpcy5fdmFsdWUpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpfWdldCBzdGVwKCl7cmV0dXJuIHRoaXMuX3N0ZXB9c2V0IHN0ZXAoZSl7dGhpcy5fc3RlcD1CaShlLHRoaXMuX3N0ZXApLHRoaXMuX3N0ZXAlMSE9MCYmKHRoaXMuX3JvdW5kVG9EZWNpbWFsPXRoaXMuX3N0ZXAudG9TdHJpbmcoKS5zcGxpdCgiLiIpLnBvcCgpLmxlbmd0aCksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCl9Z2V0IHRodW1iTGFiZWwoKXtyZXR1cm4gdGhpcy5fdGh1bWJMYWJlbH1zZXQgdGh1bWJMYWJlbChlKXt0aGlzLl90aHVtYkxhYmVsPVJ0KGUpfWdldCB0aWNrSW50ZXJ2YWwoKXtyZXR1cm4gdGhpcy5fdGlja0ludGVydmFsfXNldCB0aWNrSW50ZXJ2YWwoZSl7dGhpcy5fdGlja0ludGVydmFsPSJhdXRvIj09PWU/ImF1dG8iOiJudW1iZXIiPT10eXBlb2YgZXx8InN0cmluZyI9PXR5cGVvZiBlP0JpKGUsdGhpcy5fdGlja0ludGVydmFsKTowfWdldCB2YWx1ZSgpe3JldHVybiBudWxsPT09dGhpcy5fdmFsdWUmJih0aGlzLnZhbHVlPXRoaXMuX21pbiksdGhpcy5fdmFsdWV9c2V0IHZhbHVlKGUpe2lmKGUhPT10aGlzLl92YWx1ZSl7bGV0IGk9QmkoZSwwKTt0aGlzLl9yb3VuZFRvRGVjaW1hbCYmaSE9PXRoaXMubWluJiZpIT09dGhpcy5tYXgmJihpPXBhcnNlRmxvYXQoaS50b0ZpeGVkKHRoaXMuX3JvdW5kVG9EZWNpbWFsKSkpLHRoaXMuX3ZhbHVlPWksdGhpcy5fcGVyY2VudD10aGlzLl9jYWxjdWxhdGVQZXJjZW50YWdlKHRoaXMuX3ZhbHVlKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKX19Z2V0IHZlcnRpY2FsKCl7cmV0dXJuIHRoaXMuX3ZlcnRpY2FsfXNldCB2ZXJ0aWNhbChlKXt0aGlzLl92ZXJ0aWNhbD1SdChlKX1nZXQgZGlzcGxheVZhbHVlKCl7cmV0dXJuIHRoaXMuZGlzcGxheVdpdGg/dGhpcy5kaXNwbGF5V2l0aCh0aGlzLnZhbHVlKTp0aGlzLl9yb3VuZFRvRGVjaW1hbCYmdGhpcy52YWx1ZSYmdGhpcy52YWx1ZSUxIT0wP3RoaXMudmFsdWUudG9GaXhlZCh0aGlzLl9yb3VuZFRvRGVjaW1hbCk6dGhpcy52YWx1ZXx8MH1mb2N1cyhlKXt0aGlzLl9mb2N1c0hvc3RFbGVtZW50KGUpfWJsdXIoKXt0aGlzLl9ibHVySG9zdEVsZW1lbnQoKX1nZXQgcGVyY2VudCgpe3JldHVybiB0aGlzLl9jbGFtcCh0aGlzLl9wZXJjZW50KX1fc2hvdWxkSW52ZXJ0QXhpcygpe3JldHVybiB0aGlzLnZlcnRpY2FsPyF0aGlzLmludmVydDp0aGlzLmludmVydH1faXNNaW5WYWx1ZSgpe3JldHVybiAwPT09dGhpcy5wZXJjZW50fV9nZXRUaHVtYkdhcCgpe3JldHVybiB0aGlzLmRpc2FibGVkPzc6dGhpcy5faXNNaW5WYWx1ZSgpJiYhdGhpcy50aHVtYkxhYmVsP3RoaXMuX2lzQWN0aXZlPzEwOjc6MH1fZ2V0VHJhY2tCYWNrZ3JvdW5kU3R5bGVzKCl7bGV0IGk9dGhpcy52ZXJ0aWNhbD9gMSwgJHsxLXRoaXMucGVyY2VudH0sIDFgOjEtdGhpcy5wZXJjZW50KyIsIDEsIDEiO3JldHVybnt0cmFuc2Zvcm06YHRyYW5zbGF0ZSR7dGhpcy52ZXJ0aWNhbD8iWSI6IlgifSgke3RoaXMuX3Nob3VsZEludmVydE1vdXNlQ29vcmRzKCk/Ii0iOiIifSR7dGhpcy5fZ2V0VGh1bWJHYXAoKX1weCkgc2NhbGUzZCgke2l9KWB9fV9nZXRUcmFja0ZpbGxTdHlsZXMoKXtsZXQgZT10aGlzLnBlcmNlbnQscj10aGlzLnZlcnRpY2FsP2AxLCAke2V9LCAxYDpgJHtlfSwgMSwgMWA7cmV0dXJue3RyYW5zZm9ybTpgdHJhbnNsYXRlJHt0aGlzLnZlcnRpY2FsPyJZIjoiWCJ9KCR7dGhpcy5fc2hvdWxkSW52ZXJ0TW91c2VDb29yZHMoKT8iIjoiLSJ9JHt0aGlzLl9nZXRUaHVtYkdhcCgpfXB4KSBzY2FsZTNkKCR7cn0pYCxkaXNwbGF5OjA9PT1lPyJub25lIjoiIn19X2dldFRpY2tzQ29udGFpbmVyU3R5bGVzKCl7cmV0dXJue3RyYW5zZm9ybTpgdHJhbnNsYXRlJHt0aGlzLnZlcnRpY2FsPyJZIjoiWCJ9KCR7dGhpcy52ZXJ0aWNhbHx8InJ0bCIhPXRoaXMuX2dldERpcmVjdGlvbigpPyItIjoiIn0ke3RoaXMuX3RpY2tJbnRlcnZhbFBlcmNlbnQvMioxMDB9JSlgfX1fZ2V0VGlja3NTdHlsZXMoKXtsZXQgZT0xMDAqdGhpcy5fdGlja0ludGVydmFsUGVyY2VudCxhPXtiYWNrZ3JvdW5kU2l6ZTp0aGlzLnZlcnRpY2FsP2AycHggJHtlfSVgOmAke2V9JSAycHhgLHRyYW5zZm9ybTpgdHJhbnNsYXRlWigwKSB0cmFuc2xhdGUke3RoaXMudmVydGljYWw/IlkiOiJYIn0oJHt0aGlzLnZlcnRpY2FsfHwicnRsIiE9dGhpcy5fZ2V0RGlyZWN0aW9uKCk/IiI6Ii0ifSR7ZS8yfSUpJHt0aGlzLnZlcnRpY2FsfHwicnRsIiE9dGhpcy5fZ2V0RGlyZWN0aW9uKCk/IiI6IiByb3RhdGUoMTgwZGVnKSJ9YH07aWYodGhpcy5faXNNaW5WYWx1ZSgpJiZ0aGlzLl9nZXRUaHVtYkdhcCgpKXtsZXQgYyxsPXRoaXMuX3Nob3VsZEludmVydEF4aXMoKTtjPXRoaXMudmVydGljYWw/bD8iQm90dG9tIjoiVG9wIjpsPyJSaWdodCI6IkxlZnQiLGFbYHBhZGRpbmcke2N9YF09YCR7dGhpcy5fZ2V0VGh1bWJHYXAoKX1weGB9cmV0dXJuIGF9X2dldFRodW1iQ29udGFpbmVyU3R5bGVzKCl7bGV0IGU9dGhpcy5fc2hvdWxkSW52ZXJ0QXhpcygpO3JldHVybnt0cmFuc2Zvcm06YHRyYW5zbGF0ZSR7dGhpcy52ZXJ0aWNhbD8iWSI6IlgifSgtJHsxMDAqKCgicnRsIiE9dGhpcy5fZ2V0RGlyZWN0aW9uKCl8fHRoaXMudmVydGljYWw/ZTohZSk/dGhpcy5wZXJjZW50OjEtdGhpcy5wZXJjZW50KX0lKWB9fV9zaG91bGRJbnZlcnRNb3VzZUNvb3Jkcygpe2xldCBlPXRoaXMuX3Nob3VsZEludmVydEF4aXMoKTtyZXR1cm4icnRsIiE9dGhpcy5fZ2V0RGlyZWN0aW9uKCl8fHRoaXMudmVydGljYWw/ZTohZX1fZ2V0RGlyZWN0aW9uKCl7cmV0dXJuIHRoaXMuX2RpciYmInJ0bCI9PXRoaXMuX2Rpci52YWx1ZT8icnRsIjoibHRyIn1uZ0FmdGVyVmlld0luaXQoKXt0aGlzLl9mb2N1c01vbml0b3IubW9uaXRvcih0aGlzLl9lbGVtZW50UmVmLCEwKS5zdWJzY3JpYmUoZT0+e3RoaXMuX2lzQWN0aXZlPSEhZSYmImtleWJvYXJkIiE9PWUsdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYuZGV0ZWN0Q2hhbmdlcygpfSksdGhpcy5fZGlyJiYodGhpcy5fZGlyQ2hhbmdlU3Vic2NyaXB0aW9uPXRoaXMuX2Rpci5jaGFuZ2Uuc3Vic2NyaWJlKCgpPT57dGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCl9KSl9bmdPbkRlc3Ryb3koKXtsZXQgZT10aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQ7ZS5yZW1vdmVFdmVudExpc3RlbmVyKCJtb3VzZWRvd24iLHRoaXMuX3BvaW50ZXJEb3duLGNjKSxlLnJlbW92ZUV2ZW50TGlzdGVuZXIoInRvdWNoc3RhcnQiLHRoaXMuX3BvaW50ZXJEb3duLGNjKSx0aGlzLl9sYXN0UG9pbnRlckV2ZW50PW51bGwsdGhpcy5fcmVtb3ZlR2xvYmFsRXZlbnRzKCksdGhpcy5fZm9jdXNNb25pdG9yLnN0b3BNb25pdG9yaW5nKHRoaXMuX2VsZW1lbnRSZWYpLHRoaXMuX2RpckNoYW5nZVN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpfV9vbk1vdXNlZW50ZXIoKXt0aGlzLmRpc2FibGVkfHwodGhpcy5fc2xpZGVyRGltZW5zaW9ucz10aGlzLl9nZXRTbGlkZXJEaW1lbnNpb25zKCksdGhpcy5fdXBkYXRlVGlja0ludGVydmFsUGVyY2VudCgpKX1fb25Gb2N1cygpe3RoaXMuX3NsaWRlckRpbWVuc2lvbnM9dGhpcy5fZ2V0U2xpZGVyRGltZW5zaW9ucygpLHRoaXMuX3VwZGF0ZVRpY2tJbnRlcnZhbFBlcmNlbnQoKX1fb25CbHVyKCl7dGhpcy5vblRvdWNoZWQoKX1fb25LZXlkb3duKGUpe2lmKHRoaXMuZGlzYWJsZWR8fGtyKGUpfHx0aGlzLl9pc1NsaWRpbmcmJiJrZXlib2FyZCIhPT10aGlzLl9pc1NsaWRpbmcpcmV0dXJuO2xldCBpPXRoaXMudmFsdWU7c3dpdGNoKGUua2V5Q29kZSl7Y2FzZSAzMzp0aGlzLl9pbmNyZW1lbnQoMTApO2JyZWFrO2Nhc2UgMzQ6dGhpcy5faW5jcmVtZW50KC0xMCk7YnJlYWs7Y2FzZSAzNTp0aGlzLnZhbHVlPXRoaXMubWF4O2JyZWFrO2Nhc2UgMzY6dGhpcy52YWx1ZT10aGlzLm1pbjticmVhaztjYXNlIDM3OnRoaXMuX2luY3JlbWVudCgicnRsIj09dGhpcy5fZ2V0RGlyZWN0aW9uKCk/MTotMSk7YnJlYWs7Y2FzZSAzODp0aGlzLl9pbmNyZW1lbnQoMSk7YnJlYWs7Y2FzZSAzOTp0aGlzLl9pbmNyZW1lbnQoInJ0bCI9PXRoaXMuX2dldERpcmVjdGlvbigpPy0xOjEpO2JyZWFrO2Nhc2UgNDA6dGhpcy5faW5jcmVtZW50KC0xKTticmVhaztkZWZhdWx0OnJldHVybn1pIT10aGlzLnZhbHVlJiYodGhpcy5fZW1pdElucHV0RXZlbnQoKSx0aGlzLl9lbWl0Q2hhbmdlRXZlbnQoKSksdGhpcy5faXNTbGlkaW5nPSJrZXlib2FyZCIsZS5wcmV2ZW50RGVmYXVsdCgpfV9vbktleXVwKCl7ImtleWJvYXJkIj09PXRoaXMuX2lzU2xpZGluZyYmKHRoaXMuX2lzU2xpZGluZz1udWxsKX1fZ2V0V2luZG93KCl7cmV0dXJuIHRoaXMuX2RvY3VtZW50LmRlZmF1bHRWaWV3fHx3aW5kb3d9X2JpbmRHbG9iYWxFdmVudHMoZSl7bGV0IGk9dGhpcy5fZG9jdW1lbnQscj1PdyhlKSxzPXI/InRvdWNoZW5kIjoibW91c2V1cCI7aS5hZGRFdmVudExpc3RlbmVyKHI/InRvdWNobW92ZSI6Im1vdXNlbW92ZSIsdGhpcy5fcG9pbnRlck1vdmUsY2MpLGkuYWRkRXZlbnRMaXN0ZW5lcihzLHRoaXMuX3BvaW50ZXJVcCxjYyksciYmaS5hZGRFdmVudExpc3RlbmVyKCJ0b3VjaGNhbmNlbCIsdGhpcy5fcG9pbnRlclVwLGNjKTtsZXQgYT10aGlzLl9nZXRXaW5kb3coKTt0eXBlb2YgYTwidSImJmEmJmEuYWRkRXZlbnRMaXN0ZW5lcigiYmx1ciIsdGhpcy5fd2luZG93Qmx1cil9X3JlbW92ZUdsb2JhbEV2ZW50cygpe2xldCBlPXRoaXMuX2RvY3VtZW50O2UucmVtb3ZlRXZlbnRMaXN0ZW5lcigibW91c2Vtb3ZlIix0aGlzLl9wb2ludGVyTW92ZSxjYyksZS5yZW1vdmVFdmVudExpc3RlbmVyKCJtb3VzZXVwIix0aGlzLl9wb2ludGVyVXAsY2MpLGUucmVtb3ZlRXZlbnRMaXN0ZW5lcigidG91Y2htb3ZlIix0aGlzLl9wb2ludGVyTW92ZSxjYyksZS5yZW1vdmVFdmVudExpc3RlbmVyKCJ0b3VjaGVuZCIsdGhpcy5fcG9pbnRlclVwLGNjKSxlLnJlbW92ZUV2ZW50TGlzdGVuZXIoInRvdWNoY2FuY2VsIix0aGlzLl9wb2ludGVyVXAsY2MpO2xldCBpPXRoaXMuX2dldFdpbmRvdygpO3R5cGVvZiBpPCJ1IiYmaSYmaS5yZW1vdmVFdmVudExpc3RlbmVyKCJibHVyIix0aGlzLl93aW5kb3dCbHVyKX1faW5jcmVtZW50KGUpe2xldCBpPXRoaXMuX2NsYW1wKHRoaXMudmFsdWV8fDAsdGhpcy5taW4sdGhpcy5tYXgpO3RoaXMudmFsdWU9dGhpcy5fY2xhbXAoaSt0aGlzLnN0ZXAqZSx0aGlzLm1pbix0aGlzLm1heCl9X3VwZGF0ZVZhbHVlRnJvbVBvc2l0aW9uKGUpe2lmKCF0aGlzLl9zbGlkZXJEaW1lbnNpb25zKXJldHVybjtsZXQgcz10aGlzLl9jbGFtcCgoKHRoaXMudmVydGljYWw/ZS55OmUueCktKHRoaXMudmVydGljYWw/dGhpcy5fc2xpZGVyRGltZW5zaW9ucy50b3A6dGhpcy5fc2xpZGVyRGltZW5zaW9ucy5sZWZ0KSkvKHRoaXMudmVydGljYWw/dGhpcy5fc2xpZGVyRGltZW5zaW9ucy5oZWlnaHQ6dGhpcy5fc2xpZGVyRGltZW5zaW9ucy53aWR0aCkpO2lmKHRoaXMuX3Nob3VsZEludmVydE1vdXNlQ29vcmRzKCkmJihzPTEtcyksMD09PXMpdGhpcy52YWx1ZT10aGlzLm1pbjtlbHNlIGlmKDE9PT1zKXRoaXMudmFsdWU9dGhpcy5tYXg7ZWxzZXtsZXQgYT10aGlzLl9jYWxjdWxhdGVWYWx1ZShzKSxsPU1hdGgucm91bmQoKGEtdGhpcy5taW4pL3RoaXMuc3RlcCkqdGhpcy5zdGVwK3RoaXMubWluO3RoaXMudmFsdWU9dGhpcy5fY2xhbXAobCx0aGlzLm1pbix0aGlzLm1heCl9fV9lbWl0Q2hhbmdlRXZlbnQoKXt0aGlzLl9jb250cm9sVmFsdWVBY2Nlc3NvckNoYW5nZUZuKHRoaXMudmFsdWUpLHRoaXMudmFsdWVDaGFuZ2UuZW1pdCh0aGlzLnZhbHVlKSx0aGlzLmNoYW5nZS5lbWl0KHRoaXMuX2NyZWF0ZUNoYW5nZUV2ZW50KCkpfV9lbWl0SW5wdXRFdmVudCgpe3RoaXMuaW5wdXQuZW1pdCh0aGlzLl9jcmVhdGVDaGFuZ2VFdmVudCgpKX1fdXBkYXRlVGlja0ludGVydmFsUGVyY2VudCgpe2lmKCF0aGlzLnRpY2tJbnRlcnZhbHx8IXRoaXMuX3NsaWRlckRpbWVuc2lvbnMpcmV0dXJuO2xldCBlO2lmKCJhdXRvIj09dGhpcy50aWNrSW50ZXJ2YWwpe2xldCBpPXRoaXMudmVydGljYWw/dGhpcy5fc2xpZGVyRGltZW5zaW9ucy5oZWlnaHQ6dGhpcy5fc2xpZGVyRGltZW5zaW9ucy53aWR0aDtlPU1hdGguY2VpbCgzMC8oaSp0aGlzLnN0ZXAvKHRoaXMubWF4LXRoaXMubWluKSkpKnRoaXMuc3RlcC9pfWVsc2UgZT10aGlzLnRpY2tJbnRlcnZhbCp0aGlzLnN0ZXAvKHRoaXMubWF4LXRoaXMubWluKTt0aGlzLl90aWNrSW50ZXJ2YWxQZXJjZW50PWVvZShlKT9lOjB9X2NyZWF0ZUNoYW5nZUV2ZW50KGU9dGhpcy52YWx1ZSl7bGV0IGk9bmV3IGNsYXNze307cmV0dXJuIGkuc291cmNlPXRoaXMsaS52YWx1ZT1lLGl9X2NhbGN1bGF0ZVBlcmNlbnRhZ2UoZSl7bGV0IGk9KChlfHwwKS10aGlzLm1pbikvKHRoaXMubWF4LXRoaXMubWluKTtyZXR1cm4gZW9lKGkpP2k6MH1fY2FsY3VsYXRlVmFsdWUoZSl7cmV0dXJuIHRoaXMubWluK2UqKHRoaXMubWF4LXRoaXMubWluKX1fY2xhbXAoZSxpPTAscj0xKXtyZXR1cm4gTWF0aC5tYXgoaSxNYXRoLm1pbihlLHIpKX1fZ2V0U2xpZGVyRGltZW5zaW9ucygpe3JldHVybiB0aGlzLl9zbGlkZXJXcmFwcGVyP3RoaXMuX3NsaWRlcldyYXBwZXIubmF0aXZlRWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTpudWxsfV9mb2N1c0hvc3RFbGVtZW50KGUpe3RoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5mb2N1cyhlKX1fYmx1ckhvc3RFbGVtZW50KCl7dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LmJsdXIoKX13cml0ZVZhbHVlKGUpe3RoaXMudmFsdWU9ZX1yZWdpc3Rlck9uQ2hhbmdlKGUpe3RoaXMuX2NvbnRyb2xWYWx1ZUFjY2Vzc29yQ2hhbmdlRm49ZX1yZWdpc3Rlck9uVG91Y2hlZChlKXt0aGlzLm9uVG91Y2hlZD1lfXNldERpc2FibGVkU3RhdGUoZSl7dGhpcy5kaXNhYmxlZD1lfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKEZyKSxNKG5uKSxNKCRpLDgpLHZvKCJ0YWJpbmRleCIpLE0oX3QpLE0oSHQpLE0oUGksOCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1zbGlkZXIiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiZvdChoVmUsNSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5fc2xpZGVyV3JhcHBlcj1yLmZpcnN0KX19LGhvc3RBdHRyczpbInJvbGUiLCJzbGlkZXIiLDEsIm1hdC1zbGlkZXIiLCJtYXQtZm9jdXMtaW5kaWNhdG9yIl0saG9zdFZhcnM6MjksaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MSZlJiZQKCJmb2N1cyIsZnVuY3Rpb24oKXtyZXR1cm4gaS5fb25Gb2N1cygpfSkoImJsdXIiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuX29uQmx1cigpfSkoImtleWRvd24iLGZ1bmN0aW9uKG8pe3JldHVybiBpLl9vbktleWRvd24obyl9KSgia2V5dXAiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuX29uS2V5dXAoKX0pKCJtb3VzZWVudGVyIixmdW5jdGlvbigpe3JldHVybiBpLl9vbk1vdXNlZW50ZXIoKX0pKCJzZWxlY3RzdGFydCIsZnVuY3Rpb24obyl7cmV0dXJuIG8ucHJldmVudERlZmF1bHQoKX0pLDImZSYmKF9zKCJ0YWJJbmRleCIsaS50YWJJbmRleCksemUoImFyaWEtZGlzYWJsZWQiLGkuZGlzYWJsZWQpKCJhcmlhLXZhbHVlbWF4IixpLm1heCkoImFyaWEtdmFsdWVtaW4iLGkubWluKSgiYXJpYS12YWx1ZW5vdyIsaS52YWx1ZSkoImFyaWEtdmFsdWV0ZXh0IixudWxsPT1pLnZhbHVlVGV4dD9pLmRpc3BsYXlWYWx1ZTppLnZhbHVlVGV4dCkoImFyaWEtb3JpZW50YXRpb24iLGkudmVydGljYWw/InZlcnRpY2FsIjoiaG9yaXpvbnRhbCIpLGV0KCJtYXQtc2xpZGVyLWRpc2FibGVkIixpLmRpc2FibGVkKSgibWF0LXNsaWRlci1oYXMtdGlja3MiLGkudGlja0ludGVydmFsKSgibWF0LXNsaWRlci1ob3Jpem9udGFsIiwhaS52ZXJ0aWNhbCkoIm1hdC1zbGlkZXItYXhpcy1pbnZlcnRlZCIsaS5fc2hvdWxkSW52ZXJ0QXhpcygpKSgibWF0LXNsaWRlci1pbnZlcnQtbW91c2UtY29vcmRzIixpLl9zaG91bGRJbnZlcnRNb3VzZUNvb3JkcygpKSgibWF0LXNsaWRlci1zbGlkaW5nIixpLl9pc1NsaWRpbmcpKCJtYXQtc2xpZGVyLXRodW1iLWxhYmVsLXNob3dpbmciLGkudGh1bWJMYWJlbCkoIm1hdC1zbGlkZXItdmVydGljYWwiLGkudmVydGljYWwpKCJtYXQtc2xpZGVyLW1pbi12YWx1ZSIsaS5faXNNaW5WYWx1ZSgpKSgibWF0LXNsaWRlci1oaWRlLWxhc3QtdGljayIsaS5kaXNhYmxlZHx8aS5faXNNaW5WYWx1ZSgpJiZpLl9nZXRUaHVtYkdhcCgpJiZpLl9zaG91bGRJbnZlcnRBeGlzKCkpKCJfbWF0LWFuaW1hdGlvbi1ub29wYWJsZSIsIk5vb3BBbmltYXRpb25zIj09PWkuX2FuaW1hdGlvbk1vZGUpKX0saW5wdXRzOntkaXNhYmxlZDoiZGlzYWJsZWQiLGNvbG9yOiJjb2xvciIsdGFiSW5kZXg6InRhYkluZGV4IixpbnZlcnQ6ImludmVydCIsbWF4OiJtYXgiLG1pbjoibWluIixzdGVwOiJzdGVwIix0aHVtYkxhYmVsOiJ0aHVtYkxhYmVsIix0aWNrSW50ZXJ2YWw6InRpY2tJbnRlcnZhbCIsdmFsdWU6InZhbHVlIixkaXNwbGF5V2l0aDoiZGlzcGxheVdpdGgiLHZhbHVlVGV4dDoidmFsdWVUZXh0Iix2ZXJ0aWNhbDoidmVydGljYWwifSxvdXRwdXRzOntjaGFuZ2U6ImNoYW5nZSIsaW5wdXQ6ImlucHV0Iix2YWx1ZUNoYW5nZToidmFsdWVDaGFuZ2UifSxleHBvcnRBczpbIm1hdFNsaWRlciJdLGZlYXR1cmVzOlskdChbdlZlXSksdHRdLGRlY2xzOjEzLHZhcnM6Nixjb25zdHM6W1sxLCJtYXQtc2xpZGVyLXdyYXBwZXIiXSxbInNsaWRlcldyYXBwZXIiLCIiXSxbMSwibWF0LXNsaWRlci10cmFjay13cmFwcGVyIl0sWzEsIm1hdC1zbGlkZXItdHJhY2stYmFja2dyb3VuZCIsMywibmdTdHlsZSJdLFsxLCJtYXQtc2xpZGVyLXRyYWNrLWZpbGwiLDMsIm5nU3R5bGUiXSxbMSwibWF0LXNsaWRlci10aWNrcy1jb250YWluZXIiLDMsIm5nU3R5bGUiXSxbMSwibWF0LXNsaWRlci10aWNrcyIsMywibmdTdHlsZSJdLFsxLCJtYXQtc2xpZGVyLXRodW1iLWNvbnRhaW5lciIsMywibmdTdHlsZSJdLFsxLCJtYXQtc2xpZGVyLWZvY3VzLXJpbmciXSxbMSwibWF0LXNsaWRlci10aHVtYiJdLFsxLCJtYXQtc2xpZGVyLXRodW1iLWxhYmVsIl0sWzEsIm1hdC1zbGlkZXItdGh1bWItbGFiZWwtdGV4dCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IiwwLDEpKDIsImRpdiIsMiksTygzLCJkaXYiLDMpKDQsImRpdiIsNCksdigpLF8oNSwiZGl2Iiw1KSxPKDYsImRpdiIsNiksdigpLF8oNywiZGl2Iiw3KSxPKDgsImRpdiIsOCkoOSwiZGl2Iiw5KSxfKDEwLCJkaXYiLDEwKSgxMSwic3BhbiIsMTEpLEEoMTIpLHYoKSgpKCkoKSksMiZlJiYoQygzKSx5KCJuZ1N0eWxlIixpLl9nZXRUcmFja0JhY2tncm91bmRTdHlsZXMoKSksQygxKSx5KCJuZ1N0eWxlIixpLl9nZXRUcmFja0ZpbGxTdHlsZXMoKSksQygxKSx5KCJuZ1N0eWxlIixpLl9nZXRUaWNrc0NvbnRhaW5lclN0eWxlcygpKSxDKDEpLHkoIm5nU3R5bGUiLGkuX2dldFRpY2tzU3R5bGVzKCkpLEMoMSkseSgibmdTdHlsZSIsaS5fZ2V0VGh1bWJDb250YWluZXJTdHlsZXMoKSksQyg1KSx5dChpLmRpc3BsYXlWYWx1ZSkpfSxkZXBlbmRlbmNpZXM6W3p1XSxzdHlsZXM6WycubWF0LXNsaWRlcntkaXNwbGF5OmlubGluZS1ibG9jaztwb3NpdGlvbjpyZWxhdGl2ZTtib3gtc2l6aW5nOmJvcmRlci1ib3g7cGFkZGluZzo4cHg7b3V0bGluZTpub25lO3ZlcnRpY2FsLWFsaWduOm1pZGRsZX0ubWF0LXNsaWRlcjpub3QoLm1hdC1zbGlkZXItZGlzYWJsZWQpOmFjdGl2ZSwubWF0LXNsaWRlci5tYXQtc2xpZGVyLXNsaWRpbmc6bm90KC5tYXQtc2xpZGVyLWRpc2FibGVkKXtjdXJzb3I6Z3JhYmJpbmd9Lm1hdC1zbGlkZXItd3JhcHBlcnstd2Via2l0LXByaW50LWNvbG9yLWFkanVzdDpleGFjdDtjb2xvci1hZGp1c3Q6ZXhhY3Q7cG9zaXRpb246YWJzb2x1dGV9Lm1hdC1zbGlkZXItdHJhY2std3JhcHBlcntwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MDtsZWZ0OjA7b3ZlcmZsb3c6aGlkZGVufS5tYXQtc2xpZGVyLXRyYWNrLWZpbGx7cG9zaXRpb246YWJzb2x1dGU7dHJhbnNmb3JtLW9yaWdpbjowIDA7dHJhbnNpdGlvbjp0cmFuc2Zvcm0gNDAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSksYmFja2dyb3VuZC1jb2xvciA0MDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKX0ubWF0LXNsaWRlci10cmFjay1iYWNrZ3JvdW5ke3Bvc2l0aW9uOmFic29sdXRlO3RyYW5zZm9ybS1vcmlnaW46MTAwJSAxMDAlO3RyYW5zaXRpb246dHJhbnNmb3JtIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpLGJhY2tncm91bmQtY29sb3IgNDAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSl9Lm1hdC1zbGlkZXItdGlja3MtY29udGFpbmVye3Bvc2l0aW9uOmFic29sdXRlO2xlZnQ6MDt0b3A6MDtvdmVyZmxvdzpoaWRkZW59Lm1hdC1zbGlkZXItdGlja3N7LXdlYmtpdC1iYWNrZ3JvdW5kLWNsaXA6Y29udGVudC1ib3g7YmFja2dyb3VuZC1jbGlwOmNvbnRlbnQtYm94O2JhY2tncm91bmQtcmVwZWF0OnJlcGVhdDtib3gtc2l6aW5nOmJvcmRlci1ib3g7b3BhY2l0eTowO3RyYW5zaXRpb246b3BhY2l0eSA0MDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKX0ubWF0LXNsaWRlci10aHVtYi1jb250YWluZXJ7cG9zaXRpb246YWJzb2x1dGU7ei1pbmRleDoxO3RyYW5zaXRpb246dHJhbnNmb3JtIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpfS5tYXQtc2xpZGVyLWZvY3VzLXJpbmd7cG9zaXRpb246YWJzb2x1dGU7d2lkdGg6MzBweDtoZWlnaHQ6MzBweDtib3JkZXItcmFkaXVzOjUwJTt0cmFuc2Zvcm06c2NhbGUoMCk7b3BhY2l0eTowO3RyYW5zaXRpb246dHJhbnNmb3JtIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpLGJhY2tncm91bmQtY29sb3IgNDAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSksb3BhY2l0eSA0MDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKX0ubWF0LXNsaWRlci5jZGsta2V5Ym9hcmQtZm9jdXNlZCAubWF0LXNsaWRlci1mb2N1cy1yaW5nLC5tYXQtc2xpZGVyLmNkay1wcm9ncmFtLWZvY3VzZWQgLm1hdC1zbGlkZXItZm9jdXMtcmluZ3t0cmFuc2Zvcm06c2NhbGUoMSk7b3BhY2l0eToxfS5tYXQtc2xpZGVyOm5vdCgubWF0LXNsaWRlci1kaXNhYmxlZCk6bm90KC5tYXQtc2xpZGVyLXNsaWRpbmcpIC5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLC5tYXQtc2xpZGVyOm5vdCgubWF0LXNsaWRlci1kaXNhYmxlZCk6bm90KC5tYXQtc2xpZGVyLXNsaWRpbmcpIC5tYXQtc2xpZGVyLXRodW1ie2N1cnNvcjpncmFifS5tYXQtc2xpZGVyLXRodW1ie3Bvc2l0aW9uOmFic29sdXRlO3JpZ2h0Oi0xMHB4O2JvdHRvbTotMTBweDtib3gtc2l6aW5nOmJvcmRlci1ib3g7d2lkdGg6MjBweDtoZWlnaHQ6MjBweDtib3JkZXI6M3B4IHNvbGlkIHJnYmEoMCwwLDAsMCk7Ym9yZGVyLXJhZGl1czo1MCU7dHJhbnNmb3JtOnNjYWxlKDAuNyk7dHJhbnNpdGlvbjp0cmFuc2Zvcm0gNDAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSksYmFja2dyb3VuZC1jb2xvciA0MDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKSxib3JkZXItY29sb3IgNDAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSl9Lm1hdC1zbGlkZXItdGh1bWItbGFiZWx7ZGlzcGxheTpub25lO2FsaWduLWl0ZW1zOmNlbnRlcjtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyO3Bvc2l0aW9uOmFic29sdXRlO3dpZHRoOjI4cHg7aGVpZ2h0OjI4cHg7Ym9yZGVyLXJhZGl1czo1MCU7dHJhbnNpdGlvbjp0cmFuc2Zvcm0gNDAwbXMgY3ViaWMtYmV6aWVyKDAuMjUsIDAuOCwgMC4yNSwgMSksYm9yZGVyLXJhZGl1cyA0MDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKSxiYWNrZ3JvdW5kLWNvbG9yIDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1zbGlkZXItdGh1bWItbGFiZWx7b3V0bGluZTpzb2xpZCAxcHh9Lm1hdC1zbGlkZXItdGh1bWItbGFiZWwtdGV4dHt6LWluZGV4OjE7b3BhY2l0eTowO3RyYW5zaXRpb246b3BhY2l0eSA0MDBtcyBjdWJpYy1iZXppZXIoMC4yNSwgMC44LCAwLjI1LCAxKX0ubWF0LXNsaWRlci1zbGlkaW5nIC5tYXQtc2xpZGVyLXRyYWNrLWZpbGwsLm1hdC1zbGlkZXItc2xpZGluZyAubWF0LXNsaWRlci10cmFjay1iYWNrZ3JvdW5kLC5tYXQtc2xpZGVyLXNsaWRpbmcgLm1hdC1zbGlkZXItdGh1bWItY29udGFpbmVye3RyYW5zaXRpb24tZHVyYXRpb246MG1zfS5tYXQtc2xpZGVyLWhhcy10aWNrcyAubWF0LXNsaWRlci13cmFwcGVyOjphZnRlcntjb250ZW50OiIiO3Bvc2l0aW9uOmFic29sdXRlO2JvcmRlci13aWR0aDowO2JvcmRlci1zdHlsZTpzb2xpZDtvcGFjaXR5OjA7dHJhbnNpdGlvbjpvcGFjaXR5IDQwMG1zIGN1YmljLWJlemllcigwLjI1LCAwLjgsIDAuMjUsIDEpfS5tYXQtc2xpZGVyLWhhcy10aWNrcy5jZGstZm9jdXNlZDpub3QoLm1hdC1zbGlkZXItaGlkZS1sYXN0LXRpY2spIC5tYXQtc2xpZGVyLXdyYXBwZXI6OmFmdGVyLC5tYXQtc2xpZGVyLWhhcy10aWNrczpob3Zlcjpub3QoLm1hdC1zbGlkZXItaGlkZS1sYXN0LXRpY2spIC5tYXQtc2xpZGVyLXdyYXBwZXI6OmFmdGVye29wYWNpdHk6MX0ubWF0LXNsaWRlci1oYXMtdGlja3MuY2RrLWZvY3VzZWQ6bm90KC5tYXQtc2xpZGVyLWRpc2FibGVkKSAubWF0LXNsaWRlci10aWNrcywubWF0LXNsaWRlci1oYXMtdGlja3M6aG92ZXI6bm90KC5tYXQtc2xpZGVyLWRpc2FibGVkKSAubWF0LXNsaWRlci10aWNrc3tvcGFjaXR5OjF9Lm1hdC1zbGlkZXItdGh1bWItbGFiZWwtc2hvd2luZyAubWF0LXNsaWRlci1mb2N1cy1yaW5ne2Rpc3BsYXk6bm9uZX0ubWF0LXNsaWRlci10aHVtYi1sYWJlbC1zaG93aW5nIC5tYXQtc2xpZGVyLXRodW1iLWxhYmVse2Rpc3BsYXk6ZmxleH0ubWF0LXNsaWRlci1heGlzLWludmVydGVkIC5tYXQtc2xpZGVyLXRyYWNrLWZpbGx7dHJhbnNmb3JtLW9yaWdpbjoxMDAlIDEwMCV9Lm1hdC1zbGlkZXItYXhpcy1pbnZlcnRlZCAubWF0LXNsaWRlci10cmFjay1iYWNrZ3JvdW5ke3RyYW5zZm9ybS1vcmlnaW46MCAwfS5tYXQtc2xpZGVyOm5vdCgubWF0LXNsaWRlci1kaXNhYmxlZCkuY2RrLWZvY3VzZWQubWF0LXNsaWRlci10aHVtYi1sYWJlbC1zaG93aW5nIC5tYXQtc2xpZGVyLXRodW1ie3RyYW5zZm9ybTpzY2FsZSgwKX0ubWF0LXNsaWRlcjpub3QoLm1hdC1zbGlkZXItZGlzYWJsZWQpLmNkay1mb2N1c2VkIC5tYXQtc2xpZGVyLXRodW1iLWxhYmVse2JvcmRlci1yYWRpdXM6NTAlIDUwJSAwfS5tYXQtc2xpZGVyOm5vdCgubWF0LXNsaWRlci1kaXNhYmxlZCkuY2RrLWZvY3VzZWQgLm1hdC1zbGlkZXItdGh1bWItbGFiZWwtdGV4dHtvcGFjaXR5OjF9Lm1hdC1zbGlkZXI6bm90KC5tYXQtc2xpZGVyLWRpc2FibGVkKS5jZGstbW91c2UtZm9jdXNlZCAubWF0LXNsaWRlci10aHVtYiwubWF0LXNsaWRlcjpub3QoLm1hdC1zbGlkZXItZGlzYWJsZWQpLmNkay10b3VjaC1mb2N1c2VkIC5tYXQtc2xpZGVyLXRodW1iLC5tYXQtc2xpZGVyOm5vdCgubWF0LXNsaWRlci1kaXNhYmxlZCkuY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LXNsaWRlci10aHVtYntib3JkZXItd2lkdGg6MnB4O3RyYW5zZm9ybTpzY2FsZSgxKX0ubWF0LXNsaWRlci1kaXNhYmxlZCAubWF0LXNsaWRlci1mb2N1cy1yaW5ne3RyYW5zZm9ybTpzY2FsZSgwKTtvcGFjaXR5OjB9Lm1hdC1zbGlkZXItZGlzYWJsZWQgLm1hdC1zbGlkZXItdGh1bWJ7Ym9yZGVyLXdpZHRoOjRweDt0cmFuc2Zvcm06c2NhbGUoMC41KX0ubWF0LXNsaWRlci1kaXNhYmxlZCAubWF0LXNsaWRlci10aHVtYi1sYWJlbHtkaXNwbGF5Om5vbmV9Lm1hdC1zbGlkZXItaG9yaXpvbnRhbHtoZWlnaHQ6NDhweDttaW4td2lkdGg6MTI4cHh9Lm1hdC1zbGlkZXItaG9yaXpvbnRhbCAubWF0LXNsaWRlci13cmFwcGVye2hlaWdodDoycHg7dG9wOjIzcHg7bGVmdDo4cHg7cmlnaHQ6OHB4fS5tYXQtc2xpZGVyLWhvcml6b250YWwgLm1hdC1zbGlkZXItd3JhcHBlcjo6YWZ0ZXJ7aGVpZ2h0OjJweDtib3JkZXItbGVmdC13aWR0aDoycHg7cmlnaHQ6MDt0b3A6MH0ubWF0LXNsaWRlci1ob3Jpem9udGFsIC5tYXQtc2xpZGVyLXRyYWNrLXdyYXBwZXJ7aGVpZ2h0OjJweDt3aWR0aDoxMDAlfS5tYXQtc2xpZGVyLWhvcml6b250YWwgLm1hdC1zbGlkZXItdHJhY2stZmlsbHtoZWlnaHQ6MnB4O3dpZHRoOjEwMCU7dHJhbnNmb3JtOnNjYWxlWCgwKX0ubWF0LXNsaWRlci1ob3Jpem9udGFsIC5tYXQtc2xpZGVyLXRyYWNrLWJhY2tncm91bmR7aGVpZ2h0OjJweDt3aWR0aDoxMDAlO3RyYW5zZm9ybTpzY2FsZVgoMSl9Lm1hdC1zbGlkZXItaG9yaXpvbnRhbCAubWF0LXNsaWRlci10aWNrcy1jb250YWluZXJ7aGVpZ2h0OjJweDt3aWR0aDoxMDAlfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1zbGlkZXItaG9yaXpvbnRhbCAubWF0LXNsaWRlci10aWNrcy1jb250YWluZXJ7aGVpZ2h0OjA7b3V0bGluZTpzb2xpZCAycHg7dG9wOjFweH0ubWF0LXNsaWRlci1ob3Jpem9udGFsIC5tYXQtc2xpZGVyLXRpY2tze2hlaWdodDoycHg7d2lkdGg6MTAwJX0ubWF0LXNsaWRlci1ob3Jpem9udGFsIC5tYXQtc2xpZGVyLXRodW1iLWNvbnRhaW5lcnt3aWR0aDoxMDAlO2hlaWdodDowO3RvcDo1MCV9Lm1hdC1zbGlkZXItaG9yaXpvbnRhbCAubWF0LXNsaWRlci1mb2N1cy1yaW5ne3RvcDotMTVweDtyaWdodDotMTVweH0ubWF0LXNsaWRlci1ob3Jpem9udGFsIC5tYXQtc2xpZGVyLXRodW1iLWxhYmVse3JpZ2h0Oi0xNHB4O3RvcDotNDBweDt0cmFuc2Zvcm06dHJhbnNsYXRlWSgyNnB4KSBzY2FsZSgwLjAxKSByb3RhdGUoNDVkZWcpfS5tYXQtc2xpZGVyLWhvcml6b250YWwgLm1hdC1zbGlkZXItdGh1bWItbGFiZWwtdGV4dHt0cmFuc2Zvcm06cm90YXRlKC00NWRlZyl9Lm1hdC1zbGlkZXItaG9yaXpvbnRhbC5jZGstZm9jdXNlZCAubWF0LXNsaWRlci10aHVtYi1sYWJlbHt0cmFuc2Zvcm06cm90YXRlKDQ1ZGVnKX0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtc2xpZGVyLWhvcml6b250YWwuY2RrLWZvY3VzZWQgLm1hdC1zbGlkZXItdGh1bWItbGFiZWwsLmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LXNsaWRlci1ob3Jpem9udGFsLmNkay1mb2N1c2VkIC5tYXQtc2xpZGVyLXRodW1iLWxhYmVsLXRleHR7dHJhbnNmb3JtOm5vbmV9Lm1hdC1zbGlkZXItdmVydGljYWx7d2lkdGg6NDhweDttaW4taGVpZ2h0OjEyOHB4fS5tYXQtc2xpZGVyLXZlcnRpY2FsIC5tYXQtc2xpZGVyLXdyYXBwZXJ7d2lkdGg6MnB4O3RvcDo4cHg7Ym90dG9tOjhweDtsZWZ0OjIzcHh9Lm1hdC1zbGlkZXItdmVydGljYWwgLm1hdC1zbGlkZXItd3JhcHBlcjo6YWZ0ZXJ7d2lkdGg6MnB4O2JvcmRlci10b3Atd2lkdGg6MnB4O2JvdHRvbTowO2xlZnQ6MH0ubWF0LXNsaWRlci12ZXJ0aWNhbCAubWF0LXNsaWRlci10cmFjay13cmFwcGVye2hlaWdodDoxMDAlO3dpZHRoOjJweH0ubWF0LXNsaWRlci12ZXJ0aWNhbCAubWF0LXNsaWRlci10cmFjay1maWxse2hlaWdodDoxMDAlO3dpZHRoOjJweDt0cmFuc2Zvcm06c2NhbGVZKDApfS5tYXQtc2xpZGVyLXZlcnRpY2FsIC5tYXQtc2xpZGVyLXRyYWNrLWJhY2tncm91bmR7aGVpZ2h0OjEwMCU7d2lkdGg6MnB4O3RyYW5zZm9ybTpzY2FsZVkoMSl9Lm1hdC1zbGlkZXItdmVydGljYWwgLm1hdC1zbGlkZXItdGlja3MtY29udGFpbmVye3dpZHRoOjJweDtoZWlnaHQ6MTAwJX0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtc2xpZGVyLXZlcnRpY2FsIC5tYXQtc2xpZGVyLXRpY2tzLWNvbnRhaW5lcnt3aWR0aDowO291dGxpbmU6c29saWQgMnB4O2xlZnQ6MXB4fS5tYXQtc2xpZGVyLXZlcnRpY2FsIC5tYXQtc2xpZGVyLWZvY3VzLXJpbmd7Ym90dG9tOi0xNXB4O2xlZnQ6LTE1cHh9Lm1hdC1zbGlkZXItdmVydGljYWwgLm1hdC1zbGlkZXItdGlja3N7d2lkdGg6MnB4O2hlaWdodDoxMDAlfS5tYXQtc2xpZGVyLXZlcnRpY2FsIC5tYXQtc2xpZGVyLXRodW1iLWNvbnRhaW5lcntoZWlnaHQ6MTAwJTt3aWR0aDowO2xlZnQ6NTAlfS5tYXQtc2xpZGVyLXZlcnRpY2FsIC5tYXQtc2xpZGVyLXRodW1iey13ZWJraXQtYmFja2ZhY2UtdmlzaWJpbGl0eTpoaWRkZW47YmFja2ZhY2UtdmlzaWJpbGl0eTpoaWRkZW59Lm1hdC1zbGlkZXItdmVydGljYWwgLm1hdC1zbGlkZXItdGh1bWItbGFiZWx7Ym90dG9tOi0xNHB4O2xlZnQ6LTQwcHg7dHJhbnNmb3JtOnRyYW5zbGF0ZVgoMjZweCkgc2NhbGUoMC4wMSkgcm90YXRlKC00NWRlZyl9Lm1hdC1zbGlkZXItdmVydGljYWwgLm1hdC1zbGlkZXItdGh1bWItbGFiZWwtdGV4dHt0cmFuc2Zvcm06cm90YXRlKDQ1ZGVnKX0ubWF0LXNsaWRlci12ZXJ0aWNhbC5jZGstZm9jdXNlZCAubWF0LXNsaWRlci10aHVtYi1sYWJlbHt0cmFuc2Zvcm06cm90YXRlKC00NWRlZyl9W2Rpcj1ydGxdIC5tYXQtc2xpZGVyLXdyYXBwZXI6OmFmdGVye2xlZnQ6MDtyaWdodDphdXRvfVtkaXI9cnRsXSAubWF0LXNsaWRlci1ob3Jpem9udGFsIC5tYXQtc2xpZGVyLXRyYWNrLWZpbGx7dHJhbnNmb3JtLW9yaWdpbjoxMDAlIDEwMCV9W2Rpcj1ydGxdIC5tYXQtc2xpZGVyLWhvcml6b250YWwgLm1hdC1zbGlkZXItdHJhY2stYmFja2dyb3VuZHt0cmFuc2Zvcm0tb3JpZ2luOjAgMH1bZGlyPXJ0bF0gLm1hdC1zbGlkZXItaG9yaXpvbnRhbC5tYXQtc2xpZGVyLWF4aXMtaW52ZXJ0ZWQgLm1hdC1zbGlkZXItdHJhY2stZmlsbHt0cmFuc2Zvcm0tb3JpZ2luOjAgMH1bZGlyPXJ0bF0gLm1hdC1zbGlkZXItaG9yaXpvbnRhbC5tYXQtc2xpZGVyLWF4aXMtaW52ZXJ0ZWQgLm1hdC1zbGlkZXItdHJhY2stYmFja2dyb3VuZHt0cmFuc2Zvcm0tb3JpZ2luOjEwMCUgMTAwJX0ubWF0LXNsaWRlci5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LXNsaWRlci10cmFjay1maWxsLC5tYXQtc2xpZGVyLl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIC5tYXQtc2xpZGVyLXRyYWNrLWJhY2tncm91bmQsLm1hdC1zbGlkZXIuX21hdC1hbmltYXRpb24tbm9vcGFibGUgLm1hdC1zbGlkZXItdGlja3MsLm1hdC1zbGlkZXIuX21hdC1hbmltYXRpb24tbm9vcGFibGUgLm1hdC1zbGlkZXItdGh1bWItY29udGFpbmVyLC5tYXQtc2xpZGVyLl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIC5tYXQtc2xpZGVyLWZvY3VzLXJpbmcsLm1hdC1zbGlkZXIuX21hdC1hbmltYXRpb24tbm9vcGFibGUgLm1hdC1zbGlkZXItdGh1bWIsLm1hdC1zbGlkZXIuX21hdC1hbmltYXRpb24tbm9vcGFibGUgLm1hdC1zbGlkZXItdGh1bWItbGFiZWwsLm1hdC1zbGlkZXIuX21hdC1hbmltYXRpb24tbm9vcGFibGUgLm1hdC1zbGlkZXItdGh1bWItbGFiZWwtdGV4dCwubWF0LXNsaWRlci5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LXNsaWRlci1oYXMtdGlja3MgLm1hdC1zbGlkZXItd3JhcHBlcjo6YWZ0ZXJ7dHJhbnNpdGlvbjpub25lfSddLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIGVvZShuKXtyZXR1cm4haXNOYU4obikmJmlzRmluaXRlKG4pfWZ1bmN0aW9uIE93KG4pe3JldHVybiJ0Ij09PW4udHlwZVswXX1mdW5jdGlvbiB0b2Uobix0KXtsZXQgZTtyZXR1cm4gZT1PdyhuKT8ibnVtYmVyIj09dHlwZW9mIHQ/dlUobi50b3VjaGVzLHQpfHx2VShuLmNoYW5nZWRUb3VjaGVzLHQpOm4udG91Y2hlc1swXXx8bi5jaGFuZ2VkVG91Y2hlc1swXTpuLGU/e3g6ZS5jbGllbnRYLHk6ZS5jbGllbnRZfTp2b2lkIDB9ZnVuY3Rpb24gdlUobix0KXtmb3IobGV0IGU9MDtlPG4ubGVuZ3RoO2UrKylpZihuW2VdLmlkZW50aWZpZXI9PT10KXJldHVybiBuW2VdfXZhciBXaD0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsbG4sbG5dfSksbn0pKCk7ZnVuY3Rpb24geFZlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwibWF0LXNsaWRlciIsMTEpLFAoImlucHV0IixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygyKS5vblNsaWRlckNoYW5nZS5lbWl0KHIudmFsdWUpKX0pLHYoKX1pZigyJm4pe2xldCBlPVMoMik7eSgibWluIiwwKSgibWF4IixlLnNjcm9sbEJlZ2luSW5kZXhVcHBlckxpbWl0KSgidmFsdWUiLGUuc2Nyb2xsQmVnaW5JbmRleCl9fWZ1bmN0aW9uIENWZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsNikoMSwiYnV0dG9uIiw3KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uTmF2aWdhdGVMZWZ0LmVtaXQoKSl9KSxBKDIsIiA8ICIpLHYoKSxfKDMsImRpdiIsOCksQSg0KSx2KCksXyg1LCJidXR0b24iLDkpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkub25OYXZpZ2F0ZVJpZ2h0LmVtaXQoKSl9KSxBKDYsIiA+ICIpLHYoKSxFKDcseFZlLDEsMywibWF0LXNsaWRlciIsMTApLHYoKX1pZigyJm4pe2xldCBlPVMoKTtDKDQpLFRUKCIgIixlLnNjcm9sbEJlZ2luSW5kZXgsIiB+ICIsZS5zY3JvbGxCZWdpbkluZGV4K2UuZGlzcGxheUNvdW50LTEsIiBvZiAiLGUubnVtRXhlY3V0aW9ucywiICIpLEMoMykseSgibmdJZiIsZS5zY3JvbGxCZWdpbkluZGV4VXBwZXJMaW1pdD4wKX19dmFyIE1WZT1mdW5jdGlvbihuLHQsZSl7cmV0dXJuW24sdCxlXX07ZnVuY3Rpb24gd1ZlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IiwxNCksUCgiY2xpY2siLGZ1bmN0aW9uKCl7bGV0IG89b2UoZSkuaW5kZXg7cmV0dXJuIHNlKFMoMikub25FeGVjdXRpb25EaWdlc3RDbGlja2VkLmVtaXQobykpfSksXygxLCJkaXYiLDE1KSxBKDIpLHYoKSgpfWlmKDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT10LmluZGV4LHI9UygyKTtDKDEpLFppKCJ0aXRsZSIsZS5vcF90eXBlKSx5KCJuZ0NsYXNzIixaeCgzLE1WZSxlLmlzX2dyYXBoPyJmdW5jLWdyYXBoLWV4ZWN1dGlvbiI6IiIsaT09PXIuZm9jdXNlZEV4ZWN1dGlvbkRpc3BsYXlJbmRleD8iZm9jdXNlZCI6IiIsci5kaXNwbGF5Rm9jdXNlZEFsZXJ0VHlwZXNbaV18fCIiKSksQygxKSxqZSgiICIsZS5zaG9ydF9vcF90eXBlLCIgIil9fWZ1bmN0aW9uIFNWZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwxMiksRSgxLHdWZSwzLDcsImRpdiIsMTMpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygxKSx5KCJuZ0Zvck9mIixlLmRpc3BsYXlFeGVjdXRpb25EaWdlc3RzKX19ZnVuY3Rpb24gRVZlKG4sdCl7aWYoMSZuJiYoc24oMCksTygxLCJ0Zi1kZWJ1Z2dlci12Mi1leGVjdXRpb24tZGF0YSIsMTYpLGFuKCkpLDImbil7bGV0IGU9UygpO0MoMSkseSgiZm9jdXNlZEV4ZWN1dGlvbkluZGV4IixlLmZvY3VzZWRFeGVjdXRpb25JbmRleCl9fXZhciBub2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuYWN0aXZlUnVuSWQ9bnVsbCx0aGlzLmxvYWRpbmdOdW1FeGVjdXRpb25zPSExLHRoaXMubnVtRXhlY3V0aW9ucz0wLHRoaXMuc2Nyb2xsQmVnaW5JbmRleD0wLHRoaXMuc2Nyb2xsQmVnaW5JbmRleFVwcGVyTGltaXQ9MCx0aGlzLnBhZ2VTaXplPTAsdGhpcy5kaXNwbGF5Q291bnQ9MCx0aGlzLmRpc3BsYXlFeGVjdXRpb25EaWdlc3RzPVtdLHRoaXMuZGlzcGxheUZvY3VzZWRBbGVydFR5cGVzPVtdLHRoaXMuZm9jdXNlZEV4ZWN1dGlvbkluZGV4PW51bGwsdGhpcy5mb2N1c2VkRXhlY3V0aW9uRGlzcGxheUluZGV4PW51bGwsdGhpcy5mb2N1c2VkRXhlY3V0aW9uRGF0YT1udWxsLHRoaXMub25OYXZpZ2F0ZUxlZnQ9bmV3IEcsdGhpcy5vbk5hdmlnYXRlUmlnaHQ9bmV3IEcsdGhpcy5vbkV4ZWN1dGlvbkRpZ2VzdENsaWNrZWQ9bmV3IEcsdGhpcy5vblNsaWRlckNoYW5nZT1uZXcgR319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGltZWxpbmUtY29tcG9uZW50Il1dLGlucHV0czp7YWN0aXZlUnVuSWQ6ImFjdGl2ZVJ1bklkIixsb2FkaW5nTnVtRXhlY3V0aW9uczoibG9hZGluZ051bUV4ZWN1dGlvbnMiLG51bUV4ZWN1dGlvbnM6Im51bUV4ZWN1dGlvbnMiLHNjcm9sbEJlZ2luSW5kZXg6InNjcm9sbEJlZ2luSW5kZXgiLHNjcm9sbEJlZ2luSW5kZXhVcHBlckxpbWl0OiJzY3JvbGxCZWdpbkluZGV4VXBwZXJMaW1pdCIscGFnZVNpemU6InBhZ2VTaXplIixkaXNwbGF5Q291bnQ6ImRpc3BsYXlDb3VudCIsZGlzcGxheUV4ZWN1dGlvbkRpZ2VzdHM6ImRpc3BsYXlFeGVjdXRpb25EaWdlc3RzIixkaXNwbGF5Rm9jdXNlZEFsZXJ0VHlwZXM6ImRpc3BsYXlGb2N1c2VkQWxlcnRUeXBlcyIsZm9jdXNlZEV4ZWN1dGlvbkluZGV4OiJmb2N1c2VkRXhlY3V0aW9uSW5kZXgiLGZvY3VzZWRFeGVjdXRpb25EaXNwbGF5SW5kZXg6ImZvY3VzZWRFeGVjdXRpb25EaXNwbGF5SW5kZXgiLGZvY3VzZWRFeGVjdXRpb25EYXRhOiJmb2N1c2VkRXhlY3V0aW9uRGF0YSJ9LG91dHB1dHM6e29uTmF2aWdhdGVMZWZ0OiJvbk5hdmlnYXRlTGVmdCIsb25OYXZpZ2F0ZVJpZ2h0OiJvbk5hdmlnYXRlUmlnaHQiLG9uRXhlY3V0aW9uRGlnZXN0Q2xpY2tlZDoib25FeGVjdXRpb25EaWdlc3RDbGlja2VkIixvblNsaWRlckNoYW5nZToib25TbGlkZXJDaGFuZ2UifSxkZWNsczo5LHZhcnM6NCxjb25zdHM6W1sxLCJ0aW1lbGluZS10aXRsZSJdLFsxLCJleGVjdXRpb24tY291bnQiXSxbMSwidG9wLWxldmVsLWV4ZWN1dGlvbnMiXSxbImNsYXNzIiwibmF2aWdhdGlvbi1zZWN0aW9uIiw0LCJuZ0lmIl0sWyJjbGFzcyIsImV4ZWN1dGlvbi10aW1lbGluZSIsNCwibmdJZiJdLFs0LCJuZ0lmIl0sWzEsIm5hdmlnYXRpb24tc2VjdGlvbiJdLFsibWF0LWJ1dHRvbiIsIiIsMSwibmF2aWdhdGlvbi1idXR0b24tbGVmdCIsMywiY2xpY2siXSxbMSwibmF2aWdhdGlvbi1wb3NpdGlvbi1pbmZvIl0sWyJtYXQtYnV0dG9uIiwiIiwxLCJuYXZpZ2F0aW9uLWJ1dHRvbi1yaWdodCIsMywiY2xpY2siXSxbImNsYXNzIiwidGltZWxpbmUtc2xpZGVyIiwic3RlcCIsIjEiLDMsIm1pbiIsIm1heCIsInZhbHVlIiwiaW5wdXQiLDQsIm5nSWYiXSxbInN0ZXAiLCIxIiwxLCJ0aW1lbGluZS1zbGlkZXIiLDMsIm1pbiIsIm1heCIsInZhbHVlIiwiaW5wdXQiXSxbMSwiZXhlY3V0aW9uLXRpbWVsaW5lIl0sWzMsImNsaWNrIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbMywiY2xpY2siXSxbMSwiZXhlY3V0aW9uLWRpZ2VzdCIsMywibmdDbGFzcyIsInRpdGxlIl0sWzMsImZvY3VzZWRFeGVjdXRpb25JbmRleCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IikoMSwiZGl2IiwwKSxBKDIsIiBQeXRob24gRXhlY3V0aW9uIFRpbWVsaW5lICIpLF8oMywic3BhbiIsMSksQSg0KSx2KCkoKSxfKDUsImRpdiIsMiksRSg2LENWZSw4LDQsImRpdiIsMyksRSg3LFNWZSwyLDEsImRpdiIsNCksRSg4LEVWZSwyLDEsIm5nLWNvbnRhaW5lciIsNSksdigpKCkpLDImZSYmKEMoNCksamUoIiAoIixpLm51bUV4ZWN1dGlvbnMsIikgIiksQygyKSx5KCJuZ0lmIixpLm51bUV4ZWN1dGlvbnMpLEMoMSkseSgibmdJZiIsaS5udW1FeGVjdXRpb25zKSxDKDEpLHkoIm5nSWYiLG51bGwhPT1pLmFjdGl2ZVJ1bklkJiZudWxsIT09aS5mb2N1c2VkRXhlY3V0aW9uSW5kZXgpKX0sZGVwZW5kZW5jaWVzOltGbixkbixCZSwkcmUsX24sdXBdLHN0eWxlczpbIi5leGVjdXRpb24tZGlnZXN0W19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIGJhY2tncm91bmQtY29sb3I6ICNlM2U1ZTg7XG4gIGJvcmRlcjogMXB4IHNvbGlkICNjMGMwYzA7XG4gIGNvbG9yOiAjNDI1MDY2O1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gIGZvbnQtc2l6ZTogMTBweDtcbiAgaGVpZ2h0OiAxNXB4O1xuICBwYWRkaW5nOiAxcHg7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTtcbiAgd2lkdGg6IDEycHg7XG59XG5cbi5leGVjdXRpb24tZGlnZXN0LmZ1bmMtZ3JhcGgtZXhlY3V0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gIGJhY2tncm91bmQtY29sb3I6ICNjN2RiZjU7XG4gIGNvbG9yOiAjNGU1NjY0O1xuICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTtcbn1cblxuLmV4ZWN1dGlvbi1kaWdlc3QuZm9jdXNlZFtfbmdjb250ZW50LSVDT01QJV0ge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmZkNGIzO1xuICBib3JkZXI6IDFweCBzb2xpZCAjMDAwO1xuICBmb250LXdlaWdodDogYm9sZDtcbn1cblxuLmV4ZWN1dGlvbi1kaWdlc3QuSW5mTmFuQWxlcnRbX25nY29udGVudC0lQ09NUCVdIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2U1MjU5MjtcbiAgY29sb3I6ICNmZmY7XG59XG5cblxuLmV4ZWN1dGlvbi1kaWdlc3RbX25nY29udGVudC0lQ09NUCVdOmhvdmVyIHtcbiAgYm9yZGVyOiAxcHggc29saWQgIzAwMDtcbiAgZm9udC13ZWlnaHQ6IGJvbGQ7XG59XG5cbi5leGVjdXRpb24tdGltZWxpbmVbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZGlzcGxheTogZmxleDtcbiAgb3ZlcmZsb3cteDogaGlkZGVuO1xuICB3aGl0ZS1zcGFjZTogbm93cmFwO1xuICB3aWR0aDogMTAwJTtcbiAgbWFyZ2luLXRvcDogNXB4O1xuICBtYXJnaW4tYm90dG9tOiA1cHg7XG59XG5cbi50aW1lbGluZS1zbGlkZXJbX25nY29udGVudC0lQ09NUCVdIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBoZWlnaHQ6IDQ4cHg7XG4gIGxlZnQ6IDM0MHB4OyBcbiAgcGFkZGluZzogMDtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICByaWdodDogNDBweDtcbn1cblxuICAudGltZWxpbmUtc2xpZGVyIC5tYXQtc2xpZGVyLXRodW1iIHtcbiAgYm9yZGVyLXJhZGl1czogNXB4O1xuICByaWdodDogLTQwcHg7XG4gIHdpZHRoOiA4MHB4O1xufVxuXG5cbi5uYXZpZ2F0aW9uLXBvc2l0aW9uLWluZm9bX25nY29udGVudC0lQ09NUCVdIHtcbiAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gIGZvbnQtc2l6ZTogMTRweDtcbiAgbGluZS1oZWlnaHQ6IG5vcm1hbDtcbiAgbWF4LXdpZHRoOiAyMDBweDtcbiAgcGFkZGluZy1sZWZ0OiAxMHB4O1xuICBwYWRkaW5nLXJpZ2h0OiAxMHB4O1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG59XG5cbi5uYXZpZ2F0aW9uLXNlY3Rpb25bX25nY29udGVudC0lQ09NUCVdIHtcbiAgaGVpZ2h0OiA0OHB4O1xuICBsaW5lLWhlaWdodDogNDhweDtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO1xuICB3aWR0aDogMTAwJTtcbn0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLERWZT1bIl9fZm9yd2FyZF8iLCJfX2JhY2t3YXJkXyIsIl9faW5mZXJlbmNlXyJdLGlvZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLmFjdGl2ZVJ1bklkJD10aGlzLnN0b3JlLnBpcGUodnQoVnMpKSx0aGlzLmxvYWRpbmdOdW1FeGVjdXRpb25zJD10aGlzLnN0b3JlLnBpcGUodnQoSih3UCxpPT5pLnN0YXRlPT1PZS5MT0FESU5HKSkpLHRoaXMuc2Nyb2xsQmVnaW5JbmRleCQ9dGhpcy5zdG9yZS5waXBlKHZ0KEF3KSksdGhpcy5zY3JvbGxCZWdpbkluZGV4VXBwZXJMaW1pdCQ9dGhpcy5zdG9yZS5waXBlKHZ0KEooamgsbnksKGkscik9Pk1hdGgubWF4KDAsaS1yKSkpKSx0aGlzLnBhZ2VTaXplJD10aGlzLnN0b3JlLnBpcGUodnQodHkpKSx0aGlzLmRpc3BsYXlDb3VudCQ9dGhpcy5zdG9yZS5waXBlKHZ0KG55KSksdGhpcy5kaXNwbGF5RXhlY3V0aW9uRGlnZXN0cyQ9dGhpcy5zdG9yZS5waXBlKHZ0KEooZnJlLGk9PmkubWFwKHI9PmZ1bmN0aW9uKG4sdD0xKXtpZighbilyZXR1cm57b3BfdHlwZToiKE4vQSkiLHNob3J0X29wX3R5cGU6Ii4uIixpc19ncmFwaDohMX07bGV0IGU9RFZlLmZpbHRlcihpPT5uLm9wX3R5cGUuc3RhcnRzV2l0aChpKSk7aWYoZS5sZW5ndGgpe2xldCBpPW4ub3BfdHlwZS5zbGljZShlWzBdLmxlbmd0aCk7cmV0dXJue29wX3R5cGU6bi5vcF90eXBlLHNob3J0X29wX3R5cGU6aS5zbGljZSgwLHQpLGlzX2dyYXBoOiEwfX1yZXR1cm57b3BfdHlwZTpuLm9wX3R5cGUsc2hvcnRfb3BfdHlwZTpuLm9wX3R5cGUuc2xpY2UoMCx0KSxpc19ncmFwaDohMX19KHIpKSkpKSx0aGlzLmRpc3BsYXlGb2N1c2VkQWxlcnRUeXBlcyQ9dGhpcy5zdG9yZS5waXBlKHZ0KE1yZSkpLHRoaXMuZm9jdXNlZEV4ZWN1dGlvbkluZGV4JD10aGlzLnN0b3JlLnBpcGUodnQodVUpKSx0aGlzLmZvY3VzZWRFeGVjdXRpb25EaXNwbGF5SW5kZXgkPXRoaXMuc3RvcmUucGlwZSh2dCh3cmUpKSx0aGlzLm51bUV4ZWN1dGlvbnMkPXRoaXMuc3RvcmUucGlwZSh2dChqaCkpfW9uTmF2aWdhdGVMZWZ0KCl7dGhpcy5zdG9yZS5kaXNwYXRjaChXdigpKX1vbk5hdmlnYXRlUmlnaHQoKXt0aGlzLnN0b3JlLmRpc3BhdGNoKHF2KCkpfW9uRXhlY3V0aW9uRGlnZXN0Q2xpY2tlZChlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKFh2KHtkaXNwbGF5SW5kZXg6ZX0pKX1vblNsaWRlckNoYW5nZShlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKFl2KHtpbmRleDplfSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGYtZGVidWdnZXItdjItdGltZWxpbmUiXV0sZGVjbHM6MTIsdmFyczozMyxjb25zdHM6W1szLCJhY3RpdmVSdW5JZCIsImxvYWRpbmdOdW1FeGVjdXRpb25zIiwibnVtRXhlY3V0aW9ucyIsInNjcm9sbEJlZ2luSW5kZXgiLCJzY3JvbGxCZWdpbkluZGV4VXBwZXJMaW1pdCIsInBhZ2VTaXplIiwiZGlzcGxheUNvdW50IiwiZGlzcGxheUV4ZWN1dGlvbkRpZ2VzdHMiLCJkaXNwbGF5Rm9jdXNlZEFsZXJ0VHlwZXMiLCJmb2N1c2VkRXhlY3V0aW9uSW5kZXgiLCJmb2N1c2VkRXhlY3V0aW9uRGlzcGxheUluZGV4Iiwib25OYXZpZ2F0ZUxlZnQiLCJvbk5hdmlnYXRlUmlnaHQiLCJvbkV4ZWN1dGlvbkRpZ2VzdENsaWNrZWQiLCJvblNsaWRlckNoYW5nZSJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwidGltZWxpbmUtY29tcG9uZW50IiwwKSxQKCJvbk5hdmlnYXRlTGVmdCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbk5hdmlnYXRlTGVmdCgpfSkoIm9uTmF2aWdhdGVSaWdodCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbk5hdmlnYXRlUmlnaHQoKX0pKCJvbkV4ZWN1dGlvbkRpZ2VzdENsaWNrZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uRXhlY3V0aW9uRGlnZXN0Q2xpY2tlZChvKX0pKCJvblNsaWRlckNoYW5nZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25TbGlkZXJDaGFuZ2Uobyl9KSxCKDEsImFzeW5jIiksQigyLCJhc3luYyIpLEIoMywiYXN5bmMiKSxCKDQsImFzeW5jIiksQig1LCJhc3luYyIpLEIoNiwiYXN5bmMiKSxCKDcsImFzeW5jIiksQig4LCJhc3luYyIpLEIoOSwiYXN5bmMiKSxCKDEwLCJhc3luYyIpLEIoMTEsImFzeW5jIiksdigpKSwyJmUmJnkoImFjdGl2ZVJ1bklkIixVKDEsMTEsaS5hY3RpdmVSdW5JZCQpKSgibG9hZGluZ051bUV4ZWN1dGlvbnMiLFUoMiwxMyxpLmxvYWRpbmdOdW1FeGVjdXRpb25zJCkpKCJudW1FeGVjdXRpb25zIixVKDMsMTUsaS5udW1FeGVjdXRpb25zJCkpKCJzY3JvbGxCZWdpbkluZGV4IixVKDQsMTcsaS5zY3JvbGxCZWdpbkluZGV4JCkpKCJzY3JvbGxCZWdpbkluZGV4VXBwZXJMaW1pdCIsVSg1LDE5LGkuc2Nyb2xsQmVnaW5JbmRleFVwcGVyTGltaXQkKSkoInBhZ2VTaXplIixVKDYsMjEsaS5wYWdlU2l6ZSQpKSgiZGlzcGxheUNvdW50IixVKDcsMjMsaS5kaXNwbGF5Q291bnQkKSkoImRpc3BsYXlFeGVjdXRpb25EaWdlc3RzIixVKDgsMjUsaS5kaXNwbGF5RXhlY3V0aW9uRGlnZXN0cyQpKSgiZGlzcGxheUZvY3VzZWRBbGVydFR5cGVzIixVKDksMjcsaS5kaXNwbGF5Rm9jdXNlZEFsZXJ0VHlwZXMkKSkoImZvY3VzZWRFeGVjdXRpb25JbmRleCIsVSgxMCwyOSxpLmZvY3VzZWRFeGVjdXRpb25JbmRleCQpKSgiZm9jdXNlZEV4ZWN1dGlvbkRpc3BsYXlJbmRleCIsVSgxMSwzMSxpLmZvY3VzZWRFeGVjdXRpb25EaXNwbGF5SW5kZXgkKSl9LGRlcGVuZGVuY2llczpbbm9lLEdlXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiBQVmUobix0KXsxJm4mJk8oMCwidGYtZGVidWdnZXItdjItaW5hY3RpdmUiKX1mdW5jdGlvbiBSVmUobix0KXsxJm4mJihfKDAsImRpdiIsMyksTygxLCJ0Zi1kZWJ1Z2dlci12Mi1hbGVydHMiKSxfKDIsImRpdiIsNCksTygzLCJ0Zi1kZWJ1Z2dlci12Mi10aW1lbGluZSIpKDQsInRmLWRlYnVnZ2VyLXYyLWdyYXBoIiksdigpLE8oNSwidGYtZGVidWdnZXItdjItZ3JhcGgtZXhlY3V0aW9ucyIpLHYoKSxfKDYsImRpdiIsNSksTyg3LCJ0Zi1kZWJ1Z2dlci12Mi1zb3VyY2UtZmlsZXMiKSg4LCJ0Zi1kZWJ1Z2dlci12Mi1zdGFjay10cmFjZSIpLHYoKSl9dmFyIHJvZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5ydW5zPXt9LHRoaXMucnVuSWRzPVtdLHRoaXMuYWN0aXZlUnVuSWQ9bnVsbH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siZGVidWdnZXItY29tcG9uZW50Il1dLGlucHV0czp7cnVuczoicnVucyIscnVuSWRzOiJydW5JZHMiLGFjdGl2ZVJ1bklkOiJhY3RpdmVSdW5JZCJ9LGRlY2xzOjQsdmFyczoyLGNvbnN0czpbWzEsImRlYnVnZ2VyLWNvbnRhaW5lciJdLFs0LCJuZ0lmIiwibmdJZkVsc2UiXSxbImRhdGFBdmFpbGFibGUiLCIiXSxbMSwidG9wLXNlY3Rpb24iXSxbMSwidG9wLWNlbnRlci1zZWN0aW9uIl0sWzEsImJvdHRvbS1zZWN0aW9uIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoXygwLCJkaXYiLDApLEUoMSxQVmUsMSwwLCJ0Zi1kZWJ1Z2dlci12Mi1pbmFjdGl2ZSIsMSksRSgyLFJWZSw5LDAsIm5nLXRlbXBsYXRlIixudWxsLDIscXQpLHYoKSksMiZlKXtsZXQgcj0kZSgzKTtDKDEpLHkoIm5nSWYiLDA9PT1pLnJ1bklkcy5sZW5ndGgpKCJuZ0lmRWxzZSIscil9fSxkZXBlbmRlbmNpZXM6W1ByZSxCZSxrcmUsTHJlLFZyZSxYcmUsS3JlLGlvZV0sc3R5bGVzOlsiLmJvdHRvbS1zZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXtib3gtc2l6aW5nOmJvcmRlci1ib3g7Ym9yZGVyLXRvcDoxcHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmZsZXg7ZmxleC1ncm93OjE7aGVpZ2h0OjM0JTtwYWRkaW5nLXRvcDo2cHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmJvdHRvbS1zZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmJvdHRvbS1zZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItdG9wOjFweCBzb2xpZCAjNTU1fS5kZWJ1Z2dlci1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2JveC1zaXppbmc6Ym9yZGVyLWJveDtoZWlnaHQ6MTAwJTtvdmVyZmxvdzpoaWRkZW59LnRvcC1zZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXtib3gtc2l6aW5nOmJvcmRlci1ib3g7ZGlzcGxheTpmbGV4O2ZsZXgtZ3JvdzoxO2hlaWdodDo2NiU7cGFkZGluZzo2cHggMH10Zi1kZWJ1Z2dlci12Mi1hbGVydHNbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1yaWdodDoxcHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmlubGluZS1ibG9jazttYXJnaW4tcmlnaHQ6MTBweDttaW4td2lkdGg6MTYwcHg7d2lkdGg6Y2FsYygxNSUgLSAxMXB4KX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICB0Zi1kZWJ1Z2dlci12Mi1hbGVydHNbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICB0Zi1kZWJ1Z2dlci12Mi1hbGVydHNbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1yaWdodDoxcHggc29saWQgIzU1NX10Zi1kZWJ1Z2dlci12Mi1ncmFwaC1leGVjdXRpb25zW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9jaztmbGV4LWdyb3c6MTttaW4td2lkdGg6NTQwcHg7d2lkdGg6NTQwcHh9dGYtZGVidWdnZXItdjItc291cmNlLWZpbGVzW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6MTAwJTt3aWR0aDo3MCV9dGYtZGVidWdnZXItdjItc3RhY2stdHJhY2VbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6aW5saW5lLWJsb2NrO2ZsZXgtZ3JvdzoxO2hlaWdodDoxMDAlO21pbi13aWR0aDo1NDBweDt3aWR0aDo1NDBweH0udG9wLWNlbnRlci1zZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9jaztvdmVyZmxvdzphdXRvO3dpZHRoOjU1JX10Zi1kZWJ1Z2dlci12Mi10aW1lbGluZVtfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpibG9ja310Zi1kZWJ1Z2dlci12Mi1ncmFwaFtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLXRvcDoxcHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmJsb2NrO21hcmdpbi10b3A6NXB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIHRmLWRlYnVnZ2VyLXYyLWdyYXBoW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgdGYtZGVidWdnZXItdjItZ3JhcGhbX25nY29udGVudC0lQ09NUCVde2JvcmRlci10b3A6MXB4IHNvbGlkICM1NTV9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxvb2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5ydW5zJD10aGlzLnN0b3JlLnBpcGUodnQodWcpKSx0aGlzLnJ1bnNJZHMkPXRoaXMuc3RvcmUucGlwZSh2dChKKHVnLGk9Pk9iamVjdC5rZXlzKGkpKSkpLHRoaXMuYWN0aXZlUnVuSWQkPXRoaXMuc3RvcmUucGlwZSh2dChWcykpfW5nT25Jbml0KCl7dGhpcy5zdG9yZS5kaXNwYXRjaCh0UCgpKX1uZ09uRGVzdHJveSgpe3RoaXMuc3RvcmUuZGlzcGF0Y2goblAoKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ0Zi1kZWJ1Z2dlci12MiJdXSxkZWNsczo0LHZhcnM6OSxjb25zdHM6W1szLCJydW5zIiwicnVuSWRzIiwiYWN0aXZlUnVuSWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihPKDAsImRlYnVnZ2VyLWNvbXBvbmVudCIsMCksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIikpLDImZSYmeSgicnVucyIsVSgxLDMsaS5ydW5zJCkpKCJydW5JZHMiLFUoMiw1LGkucnVuc0lkcyQpKSgiYWN0aXZlUnVuSWQiLFUoMyw3LGkuYWN0aXZlUnVuSWQkKSl9LGRlcGVuZGVuY2llczpbcm9lLEdlXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVdIHtcbiAgICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICAgIGhlaWdodDogMTAwJTtcbiAgICAgIH0iXX0pLG59KSgpLGtQPSJkZWJ1Z2dlci12MiI7ZnVuY3Rpb24geVUobix0LGUsaSxyKXtpZihlPD0wfHwhTnVtYmVyLmlzSW50ZWdlcihlKSl0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgcGFnZVNpemU6ICR7ZX1gKTtpZih0PmkpdGhyb3cgbmV3IEVycm9yKGBlbmQgaW5kZXggKCR7dH0pIGV4Y2VlZHMgdG90YWwgbnVtYmVyIG9mIGl0ZW1zICgke2l9KWApO2lmKHQtbj5lKXRocm93IG5ldyBFcnJvcigiYmVnaW4tZW5kIHNwYW4gZXhjZWVkcyBwYWdlIHNpemUsIHdoaWNoIGlzIG5vdCBhbGxvd2VkIik7bGV0IG89W10scz1NYXRoLmZsb29yKG4vZSk7KCEocyBpbiByKXx8cltzXTxlJiZzKmUrcltzXTxpKSYmby5wdXNoKHMpO2xldCBhPU1hdGguZmxvb3IoKHQtMSkvZSk7cmV0dXJuIGEhPT1zJiYoIShhIGluIHIpfHxhKmUrclthXTx0JiZ0PGkpJiZvLnB1c2goYSksb312YXIgbG9lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIpe3RoaXMuYWN0aW9ucyQ9ZSx0aGlzLnN0b3JlPWksdGhpcy5kYXRhU291cmNlPXIsdGhpcy5sb2FkRGF0YSQ9Y3IoKCk9PntsZXQgbz10aGlzLmxvYWREZWJ1Z2dlclJ1bnMoSnQodGhpcy5vbkRlYnVnZ2VyRGF0YVBvbGwoKSx0aGlzLm9uQ29yZVJlbG9hZCgpKSkucGlwZShUcygpKSxzPXRoaXMubG9hZFNvdXJjZUZpbGVMaXN0KG8pLGE9dGhpcy5jcmVhdGVOdW1FeGVjdXRpb25Mb2FkZXIobyksbD10aGlzLmNyZWF0ZU51bUFsZXJ0c0FuZEJyZWFrZG93bkxvYWRlcihvKSxjPXRoaXMub25BbGVydFR5cGVGb2N1c2VkKCksdT10aGlzLmZldGNoRXhlY3V0aW9uRGlnZXN0c0ZvckFsZXJ0VHlwZUZvY3VzKGMpLGQ9dGhpcy5jcmVhdGVJbml0aWFsRXhlY3V0aW9uRGV0ZWN0b3IoYSkucGlwZShUcygpKSxwPXRoaXMuY3JlYXRlRXhlY3V0aW9uRGlnZXN0TG9hZGVyKEp0KHRoaXMub25FeGVjdXRpb25TY3JvbGwoKSx0aGlzLmNyZWF0ZUluaXRpYWxFeGVjdXRpb25EaWdlc3QoZCksdSkpLGg9dGhpcy5jcmVhdGVFeGVjdXRpb25EYXRhQW5kU3RhY2tGcmFtZXNMb2FkZXIoSnQodGhpcy5vbkV4ZWN1dGlvbkRpZ2VzdEZvY3VzZWQoKSxkLnBpcGUoV3QodGhpcy5zdG9yZS5zZWxlY3QoVnMpLHRoaXMuc3RvcmUuc2VsZWN0KGRVKSksTCgoWyxiLERdKT0+KHthY3RpdmVSdW5JZDpiLGxvYWRlZEV4ZWN1dGlvbkRhdGE6RCxmb2N1c0luZGV4OjB9KSkpKSk7cmV0dXJuIEp0KGwscCxoLHRoaXMuY3JlYXRlTnVtR3JhcGhFeGVjdXRpb25Mb2FkZXIobykscyx0aGlzLm9uU291cmNlRmlsZUZvY3VzZWQoKSx0aGlzLmxvYWRHcmFwaEV4ZWN1dGlvblBhZ2VzKHRoaXMub25HcmFwaEV4ZWN1dGlvblNjcm9sbCgpKSx0aGlzLmxvYWRHcmFwaE9wU3RhY2tGcmFtZXModGhpcy5sb2FkR3JhcGhPcEluZm8oKSkpLnBpcGUoTCgoKT0+KHt9KSkpfSx7ZGlzcGF0Y2g6ITF9KX1vbkRlYnVnZ2VyRGF0YVBvbGwoKXtyZXR1cm4gdGhpcy5hY3Rpb25zJC5waXBlKGlpKHRQKSx1aShlPT5mdW5jdGlvbihuLHQsZSl7cmV0dXJuIG4ucGlwZShmdW5jdGlvbihuKXtyZXR1cm4gZW4oKHQsZSk9PntsZXQgaSxvLHI9ITEscz0hMSxhPSExLGw9KCk9PmEmJnMmJihlLmNvbXBsZXRlKCksITApLHU9KCk9PnthPSExLGk9dC5zdWJzY3JpYmUoanQoZSx2b2lkIDAsKCk9PnthPSEwLCFsKCkmJihvfHwobz1uZXcga2UsbihvKS5zdWJzY3JpYmUoanQoZSwoKT0+e2k/dSgpOnI9ITB9LCgpPT57cz0hMCxsKCl9KSkpLG8pLm5leHQoKX0pKSxyJiYoaS51bnN1YnNjcmliZSgpLGk9bnVsbCxyPSExLHUoKSl9O3UoKX0pfShpPT5pLnBpcGUoV3QodCksdjAoKFsscl0pPT5LYShyKSkpKSxzdChlKSxMKCgpPT57fSkpfShYdChlKSx0aGlzLnN0b3JlLnNlbGVjdChjcmUpLnBpcGUoTChpPT5mdW5jdGlvbihuKXtyZXR1cm4gbj42ZTQ/NmU0Om4+NGUzP246MmUzfShpKSkpLHRoaXMuYWN0aW9ucyQucGlwZShpaShuUCkpKSksa3QoKCk9PnRoaXMuc3RvcmUuZGlzcGF0Y2goTXcoKSkpLEwoKCk9Pnt9KSl9b25Db3JlUmVsb2FkKCl7cmV0dXJuIEp0KHRoaXMuYWN0aW9ucyQucGlwZShpaShGYSxhYSkpLHRoaXMuYWN0aW9ucyQucGlwZShpaShadSkpLnBpcGUoV3QodGhpcy5zdG9yZS5zZWxlY3QoaVUpKSxZZSgoWyxlXSk9PmUuc3RhdGU9PT1PZS5OT1RfTE9BREVEfHxlLnN0YXRlPT09T2UuRkFJTEVEJiZudWxsPT09ZS5sYXN0TG9hZGVkVGltZUluTXMpKSkucGlwZShXdCh0aGlzLnN0b3JlLnNlbGVjdChScykpLFllKChbLGVdKT0+ZT09PWtQKSxrdCgoKT0+dGhpcy5zdG9yZS5kaXNwYXRjaChNdygpKSksTCgoKT0+e30pKX1sb2FkRGVidWdnZXJSdW5zKGUpe3JldHVybiBlLnBpcGUoV3QodGhpcy5zdG9yZS5zZWxlY3QoaVUpKSxZZSgoWyx7c3RhdGU6aX1dKT0+aSE9PU9lLkxPQURJTkcpLGt0KCgpPT50aGlzLnN0b3JlLmRpc3BhdGNoKGlQKCkpKSx4bigoKT0+dGhpcy5kYXRhU291cmNlLmZldGNoUnVucygpLnBpcGUoa3QoaT0+e3RoaXMuc3RvcmUuZGlzcGF0Y2goclAoe3J1bnM6aX0pKX0pLEwoKCk9Pnt9KSkpKX1jcmVhdGVOdW1FeGVjdXRpb25Mb2FkZXIoZSl7cmV0dXJuIGUucGlwZShXdCh0aGlzLnN0b3JlLnNlbGVjdCh1ZyksdGhpcy5zdG9yZS5zZWxlY3Qod1ApKSxZZSgoWyxpLHJdKT0+T2JqZWN0LmtleXMoaSkubGVuZ3RoPjAmJnIuc3RhdGUhPT1PZS5MT0FESU5HKSxrdCgoKT0+dGhpcy5zdG9yZS5kaXNwYXRjaChhUCgpKSkseG4oKFssaV0pPT57bGV0IHI9T2JqZWN0LmtleXMoaSlbMF07cmV0dXJuIHRoaXMuZGF0YVNvdXJjZS5mZXRjaEV4ZWN1dGlvbkRpZ2VzdHMociwwLDApLnBpcGUoa3QoYT0+e3RoaXMuc3RvcmUuZGlzcGF0Y2gobFAoe251bUV4ZWN1dGlvbnM6YS5udW1fZGlnZXN0c30pKX0pLEwoKCk9Pnt9KSl9KSl9Y3JlYXRlTnVtR3JhcGhFeGVjdXRpb25Mb2FkZXIoZSl7cmV0dXJuIGUucGlwZShXdCh0aGlzLnN0b3JlLnNlbGVjdCh1ZyksdGhpcy5zdG9yZS5zZWxlY3QobXJlKSksWWUoKFssaSxyXSk9Pk9iamVjdC5rZXlzKGkpLmxlbmd0aD4wJiZyLnN0YXRlIT09T2UuTE9BRElORyksa3QoKCk9PnRoaXMuc3RvcmUuZGlzcGF0Y2gocFAoKSkpLHhuKChbLGldKT0+e2xldCByPU9iamVjdC5rZXlzKGkpWzBdO3JldHVybiB0aGlzLmRhdGFTb3VyY2UuZmV0Y2hHcmFwaEV4ZWN1dGlvbkRpZ2VzdHMociwwLDApLnBpcGUoa3QoYT0+e3RoaXMuc3RvcmUuZGlzcGF0Y2goaFAoe251bUdyYXBoRXhlY3V0aW9uczphLm51bV9kaWdlc3RzfSkpfSksTCgoKT0+e30pKX0pKX1jcmVhdGVOdW1BbGVydHNBbmRCcmVha2Rvd25Mb2FkZXIoZSl7cmV0dXJuIGUucGlwZShXdCh0aGlzLnN0b3JlLnNlbGVjdCh1ZyksdGhpcy5zdG9yZS5zZWxlY3QoclUpKSxZZSgoWyxpLHJdKT0+T2JqZWN0LmtleXMoaSkubGVuZ3RoPjAmJnIuc3RhdGUhPT1PZS5MT0FESU5HKSxrdCgoKT0+dGhpcy5zdG9yZS5kaXNwYXRjaCh3dygpKSkseG4oKFssaV0pPT57bGV0IHI9T2JqZWN0LmtleXMoaSlbMF07cmV0dXJuIHRoaXMuZGF0YVNvdXJjZS5mZXRjaEFsZXJ0cyhyLDAsMCkucGlwZShrdChhPT57dGhpcy5zdG9yZS5kaXNwYXRjaChvUCh7bnVtQWxlcnRzOmEubnVtX2FsZXJ0cyxhbGVydHNCcmVha2Rvd246YS5hbGVydHNfYnJlYWtkb3dufSkpfSksTCgoKT0+e30pKX0pKX1jcmVhdGVJbml0aWFsRXhlY3V0aW9uRGV0ZWN0b3IoZSl7cmV0dXJuIGUucGlwZShXdCh0aGlzLnN0b3JlLnNlbGVjdChqaCksdGhpcy5zdG9yZS5zZWxlY3QoRHcpKSxZZSgoWyxpLHJdKT0+aT4wJiYwPT09T2JqZWN0LmtleXMoci5wYWdlTG9hZGVkU2l6ZXMpLmxlbmd0aCksTCgoKT0+e30pKX1jcmVhdGVJbml0aWFsRXhlY3V0aW9uRGlnZXN0KGUpe3JldHVybiBlLnBpcGUoV3QodGhpcy5zdG9yZS5zZWxlY3QoamgpLHRoaXMuc3RvcmUuc2VsZWN0KFZzKSx0aGlzLnN0b3JlLnNlbGVjdCh0eSkpLFllKChbLCxpXSk9Pm51bGwhPT1pKSxMKChbLGkscixvXSk9Pih7YmVnaW46MCxlbmQ6TWF0aC5taW4oaSxvKSxydW5JZDpyfSkpKX1vbkV4ZWN1dGlvblNjcm9sbCgpe3JldHVybiB0aGlzLmFjdGlvbnMkLnBpcGUoaWkoV3YscXYsWXYpLFd0KHRoaXMuc3RvcmUuc2VsZWN0KFZzKSx0aGlzLnN0b3JlLnNlbGVjdChBdyksdGhpcy5zdG9yZS5zZWxlY3QoamgpLHRoaXMuc3RvcmUuc2VsZWN0KG55KSx0aGlzLnN0b3JlLnNlbGVjdCh0eSkpLFllKChbZV0pPT5udWxsIT09ZSksTCgoWyxlLGkscixvLHNdKT0+KHtydW5JZDplLGJlZ2luOmksZW5kOk1hdGgubWluKHIsaStvKSxwYWdlU2l6ZTpzfSkpLFd0KHRoaXMuc3RvcmUuc2VsZWN0KER3KSksTCgoW2UsaV0pPT4oe3Byb3BzOmUsbG9hZGVkOmksbWlzc2luZ1BhZ2VzOnlVKGUuYmVnaW4sZS5lbmQsZS5wYWdlU2l6ZSxpLm51bUV4ZWN1dGlvbnMsaS5wYWdlTG9hZGVkU2l6ZXMpfSkpLFllKCh7bWlzc2luZ1BhZ2VzOmV9KT0+ZS5sZW5ndGg+MCksTCgoe3Byb3BzOmUsbG9hZGVkOmksbWlzc2luZ1BhZ2VzOnJ9KT0+e2xldHtydW5JZDpvLHBhZ2VTaXplOnN9PWU7cmV0dXJue2JlZ2luOnJbMF0qcyxlbmQ6TWF0aC5taW4oaS5udW1FeGVjdXRpb25zLChyW3IubGVuZ3RoLTFdKzEpKnMpLHJ1bklkOm99fSkpfWNyZWF0ZUV4ZWN1dGlvbkRpZ2VzdExvYWRlcihlKXtyZXR1cm4gZS5waXBlKFd0KHRoaXMuc3RvcmUuc2VsZWN0KER3KSksWWUoKFt7YmVnaW46aSxlbmQ6cn0sb10pPT5yPmkmJiFmdW5jdGlvbihuLHQsZSl7aWYodD49ZSl0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGJlZ2luIHRvIGJlIGxlc3MgdGhhbiBlbmQsIGJ1dCBnb3QgYmVnaW49JHt0fSwgZW5kPSR7ZX1gKTtyZXR1cm4tMSE9PW4uZmluZEluZGV4KGk9PmkuYmVnaW4+PXQmJmkuZW5kPD1lKX0oby5sb2FkaW5nUmFuZ2VzLGkscikpLGt0KChbe2JlZ2luOmksZW5kOnJ9XSk9Pnt0aGlzLnN0b3JlLmRpc3BhdGNoKGNQKHtiZWdpbjppLGVuZDpyfSkpfSkseG4oKFt7cnVuSWQ6aSxiZWdpbjpyLGVuZDpvfV0pPT50aGlzLmRhdGFTb3VyY2UuZmV0Y2hFeGVjdXRpb25EaWdlc3RzKGkscixvKS5waXBlKGt0KHM9Pnt0aGlzLnN0b3JlLmRpc3BhdGNoKHVQKHMpKX0pLEwoKCk9Pnt9KSkpKX1vbkV4ZWN1dGlvbkRpZ2VzdEZvY3VzZWQoKXtyZXR1cm4gdGhpcy5hY3Rpb25zJC5waXBlKGlpKFh2KSxXdCh0aGlzLnN0b3JlLnNlbGVjdChWcyksdGhpcy5zdG9yZS5zZWxlY3QoZFUpLHRoaXMuc3RvcmUuc2VsZWN0KEF3KSksTCgoW2UsaSxyLG9dKT0+KHthY3RpdmVSdW5JZDppLGxvYWRlZEV4ZWN1dGlvbkRhdGE6cixmb2N1c0luZGV4Om8rZS5kaXNwbGF5SW5kZXh9KSkpfWNyZWF0ZUV4ZWN1dGlvbkRhdGFBbmRTdGFja0ZyYW1lc0xvYWRlcihlKXtyZXR1cm4gZS5waXBlKFllKCh7YWN0aXZlUnVuSWQ6aSxsb2FkZWRFeGVjdXRpb25EYXRhOnIsZm9jdXNJbmRleDpvfSk9Pm51bGwhPT1pJiZudWxsIT09byYmdm9pZCAwPT09cltvXSkseG4oKHthY3RpdmVSdW5JZDppLGZvY3VzSW5kZXg6cn0pPT57bGV0IG89cixzPW8rMTtyZXR1cm4gdGhpcy5kYXRhU291cmNlLmZldGNoRXhlY3V0aW9uRGF0YShpLG8scykucGlwZShrdChhPT57dGhpcy5zdG9yZS5kaXNwYXRjaChkUChhKSl9KSxMKGE9Pih7ZXhlY3V0aW9uRGF0YTphLGJlZ2luOm8sZW5kOnN9KSkpfSksTCgoe2V4ZWN1dGlvbkRhdGE6aX0pPT5pLmV4ZWN1dGlvbnNbMF0pLFd0KHRoaXMuc3RvcmUuc2VsZWN0KFZzKSx0aGlzLnN0b3JlLnNlbGVjdChwVSkpLFllKChbaSxyLG9dKT0+e2lmKG51bGw9PT1yKXJldHVybiExO2ZvcihsZXQgcyBvZiBpLnN0YWNrX2ZyYW1lX2lkcylpZih2b2lkIDA9PT1vW3NdKXJldHVybiEwO3JldHVybiExfSkseG4oKFtpLHJdKT0+e2xldCBvPWkuc3RhY2tfZnJhbWVfaWRzO3JldHVybiB0aGlzLmRhdGFTb3VyY2UuZmV0Y2hTdGFja0ZyYW1lcyhyLG8pLnBpcGUoa3Qocz0+e2xldCBhPXt9O2ZvcihsZXQgbD0wO2w8by5sZW5ndGg7KytsKWFbb1tsXV09cy5zdGFja19mcmFtZXNbbF07dGhpcy5zdG9yZS5kaXNwYXRjaChTdyh7c3RhY2tGcmFtZXM6YX0pKX0pLEwoKCk9Pnt9KSl9KSl9b25HcmFwaEV4ZWN1dGlvblNjcm9sbCgpe3JldHVybiB0aGlzLmFjdGlvbnMkLnBpcGUoaWkoUXYpLEhyKDEwMCksV3QodGhpcy5zdG9yZS5zZWxlY3QoVnMpLHRoaXMuc3RvcmUuc2VsZWN0KEl3KSx0aGlzLnN0b3JlLnNlbGVjdChncmUpKSxZZSgoWyxlLGldKT0+bnVsbCE9PWUmJmk+MCksTCgoWyxlLGkscl0pPT4oe3J1bklkOmUsbnVtR3JhcGhFeGVjdXRpb25zOmksc2Nyb2xsQmVnaW5JbmRleDpyfSkpLFd0KHRoaXMuc3RvcmUuc2VsZWN0KHZyZSksdGhpcy5zdG9yZS5zZWxlY3QoX3JlKSx0aGlzLnN0b3JlLnNlbGVjdCh5cmUpLHRoaXMuc3RvcmUuc2VsZWN0KGJyZSkpLEwoKFt7cnVuSWQ6ZSxudW1HcmFwaEV4ZWN1dGlvbnM6aSxzY3JvbGxCZWdpbkluZGV4OnJ9LG8scyxhLGxdKT0+e2xldCBjPXlVKHIsTWF0aC5taW4ocitzLGkpLG8saSxsKTtyZXR1cm4gYz1jLmZpbHRlcih1PT4tMT09PWEuaW5kZXhPZih1KSkse3J1bklkOmUsbWlzc2luZ1BhZ2VzOmMscGFnZVNpemU6byxudW1HcmFwaEV4ZWN1dGlvbnM6aX19KSl9bG9hZEdyYXBoRXhlY3V0aW9uUGFnZXMoZSl7cmV0dXJuIGUucGlwZShZZSgoe21pc3NpbmdQYWdlczppfSk9PmkubGVuZ3RoPjApLGt0KCh7bWlzc2luZ1BhZ2VzOml9KT0+e2kuZm9yRWFjaChyPT57dGhpcy5zdG9yZS5kaXNwYXRjaChmUCh7cGFnZUluZGV4OnJ9KSl9KX0pLHhuKCh7cnVuSWQ6aSxtaXNzaW5nUGFnZXM6cixwYWdlU2l6ZTpvLG51bUdyYXBoRXhlY3V0aW9uczpzfSk9PntsZXQgYT1yWzBdKm8sbD1NYXRoLm1pbigocltyLmxlbmd0aC0xXSsxKSpvLHMpO3JldHVybiB0aGlzLmRhdGFTb3VyY2UuZmV0Y2hHcmFwaEV4ZWN1dGlvbkRhdGEoaSxhLGwpLnBpcGUoa3QoYz0+e3RoaXMuc3RvcmUuZGlzcGF0Y2gobVAoYykpfSksTCgoKT0+e30pKX0pKX1sb2FkR3JhcGhPcEluZm8oKXtyZXR1cm4gdGhpcy5hY3Rpb25zJC5waXBlKGlpKFp2LEt2KSxXdCh0aGlzLnN0b3JlLnNlbGVjdChWcyksdGhpcy5zdG9yZS5zZWxlY3QoU3JlKSksWWUoKFtlLGkscl0pPT57bGV0e2dyYXBoX2lkOm8sb3BfbmFtZTpzfT1lO3JldHVybiEobnVsbD09PWl8fHZvaWQgMCE9PXJbb10mJnJbb10uaGFzKHMpJiYocltvXS5nZXQocyk9PT1PZS5MT0FESU5HfHxyW29dLmdldChzKT09PU9lLkxPQURFRCkpfSksa3QoKFt7Z3JhcGhfaWQ6ZSxvcF9uYW1lOml9XSk9PnRoaXMuc3RvcmUuZGlzcGF0Y2goZ1Aoe2dyYXBoX2lkOmUsb3BfbmFtZTppfSkpKSx4bigoW2UsaV0pPT57bGV0e2dyYXBoX2lkOnIsb3BfbmFtZTpvfT1lO3JldHVybiB0aGlzLmRhdGFTb3VyY2UuZmV0Y2hHcmFwaE9wSW5mbyhpLHIsbykucGlwZShrdChzPT50aGlzLnN0b3JlLmRpc3BhdGNoKF9QKHtncmFwaE9wSW5mb1Jlc3BvbnNlOnN9KSkpLEwocz0+KHtydW5JZDppLHN0YWNrRnJhbWVJZHM6cy5zdGFja19mcmFtZV9pZHN9KSkpfSkpfWxvYWRHcmFwaE9wU3RhY2tGcmFtZXMoZSl7cmV0dXJuIGUucGlwZShXdCh0aGlzLnN0b3JlLnNlbGVjdChwVSkpLEwoKFt7cnVuSWQ6aSxzdGFja0ZyYW1lSWRzOnJ9LG9dKT0+KHtydW5JZDppLG1pc3NpbmdTdGFja0ZyYW1lSWRzOnIuZmlsdGVyKGE9PnZvaWQgMD09PW9bYV0pfSkpLFllKCh7cnVuSWQ6aSxtaXNzaW5nU3RhY2tGcmFtZUlkczpyfSk9Pm51bGwhPT1pJiZyLmxlbmd0aD4wKSx4bigoe3J1bklkOmksbWlzc2luZ1N0YWNrRnJhbWVJZHM6cn0pPT50aGlzLmRhdGFTb3VyY2UuZmV0Y2hTdGFja0ZyYW1lcyhpLHIpLnBpcGUoa3Qobz0+e2xldCBzPXt9O2ZvcihsZXQgYT0wO2E8ci5sZW5ndGg7KythKXNbclthXV09by5zdGFja19mcmFtZXNbYV07dGhpcy5zdG9yZS5kaXNwYXRjaChTdyh7c3RhY2tGcmFtZXM6c30pKX0pLEwoKCk9Pnt9KSkpKX1vbkFsZXJ0VHlwZUZvY3VzZWQoKXtyZXR1cm4gdGhpcy5hY3Rpb25zJC5waXBlKGlpKEd2KSxXdCh0aGlzLnN0b3JlLnNlbGVjdChWcyksdGhpcy5zdG9yZS5zZWxlY3QoTVApLHRoaXMuc3RvcmUuc2VsZWN0KGRyZSksdGhpcy5zdG9yZS5zZWxlY3QocHJlKSx0aGlzLnN0b3JlLnNlbGVjdChyVSkpLFllKChbLGUsaSxyLG8sc10pPT5udWxsIT09ZSYmbnVsbCE9PWkmJnI+MCYmKG51bGw9PT1vfHxPYmplY3Qua2V5cyhvKS5sZW5ndGg8cikmJnMuc3RhdGUhPT1PZS5MT0FESU5HKSxrdCgoKT0+dGhpcy5zdG9yZS5kaXNwYXRjaCh3dygpKSkseG4oKFssZSxpXSk9PnRoaXMuZGF0YVNvdXJjZS5mZXRjaEFsZXJ0cyhlLDAsLTEsaSkpLGt0KCh7bnVtX2FsZXJ0czplLGFsZXJ0c19icmVha2Rvd246aSxhbGVydF90eXBlOnIsYmVnaW46byxlbmQ6cyxhbGVydHM6YX0pPT57dGhpcy5zdG9yZS5kaXNwYXRjaChzUCh7bnVtQWxlcnRzOmUsYWxlcnRzQnJlYWtkb3duOmksYWxlcnRUeXBlOnIsYmVnaW46byxlbmQ6cyxhbGVydHM6YX0pKX0pKX1mZXRjaEV4ZWN1dGlvbkRpZ2VzdHNGb3JBbGVydFR5cGVGb2N1cyhlKXtyZXR1cm4gZS5waXBlKFd0KHRoaXMuc3RvcmUuc2VsZWN0KHR5KSx0aGlzLnN0b3JlLnNlbGVjdChueSksdGhpcy5zdG9yZS5zZWxlY3QoamgpLHRoaXMuc3RvcmUuc2VsZWN0KER3KSx0aGlzLnN0b3JlLnNlbGVjdChWcykpLEwoKFtpLHIsbyxzLGEsbF0pPT57bGV0IHU9aS5hbGVydHNbMF0uZXhlY3V0aW9uX2luZGV4LGQ9eVUoTWF0aC5tYXgoMCx1LU1hdGguZmxvb3Ioby8yKSksTWF0aC5taW4odStNYXRoLmZsb29yKG8vMikscykscixzLGEucGFnZUxvYWRlZFNpemVzKTtyZXR1cm4gMD09PWQubGVuZ3RoP3tydW5JZDpsLGJlZ2luOjAsZW5kOjB9OntydW5JZDpsLGJlZ2luOmRbMF0qcixlbmQ6TWF0aC5taW4oYS5udW1FeGVjdXRpb25zLChkW2QubGVuZ3RoLTFdKzEpKnIpfX0pKX1sb2FkU291cmNlRmlsZUxpc3QoZSl7cmV0dXJuIGUucGlwZShXdCh0aGlzLnN0b3JlLnNlbGVjdChWcyksdGhpcy5zdG9yZS5zZWxlY3QoVHJlKSksWWUoKFssaSxyXSk9Pm51bGwhPT1pJiZyLnN0YXRlIT09T2UuTE9BRElORyksa3QoKCk9PnRoaXMuc3RvcmUuZGlzcGF0Y2godlAoKSkpLHhuKChbLGldKT0+dGhpcy5kYXRhU291cmNlLmZldGNoU291cmNlRmlsZUxpc3QoaSkucGlwZShrdChyPT57bGV0IG89W107ci5mb3JFYWNoKChbcyxhXSk9PntvLnB1c2goe2hvc3RfbmFtZTpzLGZpbGVfcGF0aDphfSl9KSx0aGlzLnN0b3JlLmRpc3BhdGNoKHlQKHtzb3VyY2VGaWxlczpvfSkpfSksTCgoKT0+e30pKSkpfW9uU291cmNlRmlsZUZvY3VzZWQoKXtyZXR1cm4gdGhpcy5hY3Rpb25zJC5waXBlKGlpKEp2KSxXdCh0aGlzLnN0b3JlLnNlbGVjdChWcyksdGhpcy5zdG9yZS5zZWxlY3QoaFUpLHRoaXMuc3RvcmUuc2VsZWN0KFRQKSksTCgoW2UsaSxyLG9dKT0+KHtydW5JZDppLHN0YWNrRnJhbWU6ZS5zdGFja0ZyYW1lLGZpbGVJbmRleDpyLGZpbGVDb250ZW50Om99KSksWWUoKHtydW5JZDplLGZpbGVDb250ZW50Oml9KT0+bnVsbCE9PWUmJm51bGwhPT1pJiZpLmxvYWRTdGF0ZT09PU9lLk5PVF9MT0FERUQpLGt0KCh7c3RhY2tGcmFtZTplfSk9PnRoaXMuc3RvcmUuZGlzcGF0Y2goYlAoe2hvc3RfbmFtZTplLmhvc3RfbmFtZSxmaWxlX3BhdGg6ZS5maWxlX3BhdGh9KSkpLHhuKCh7ZmlsZUluZGV4OmUscnVuSWQ6aX0pPT50aGlzLmRhdGFTb3VyY2UuZmV0Y2hTb3VyY2VGaWxlKGksZSkucGlwZShrdChyPT57dGhpcy5zdG9yZS5kaXNwYXRjaCh4UChyKSl9KSxMKCgpPT57fSkpKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooUG8pLGooQ2UpLGooZVApKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxjb2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lXX0pLG59KSgpLHVvZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWVdfSksbn0pKCksRlA9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lXX0pLG59KSgpLGRvZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsRlAsWmNdfSksbn0pKCkscG9lPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHt9KSxufSkoKSxxaD0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksTlA9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLHFoXX0pLG59KSgpLGhvZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsTlBdfSksbn0pKCksZm9lPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxOUF19KSxufSkoKSxtb2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLEZQXX0pLG59KSgpLGdvZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsbW9lLFBuLFdoXX0pLG59KSgpLF9vZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbY29lLE1lLGVjLGRvZSx1b2UscG9lLGhvZSxmb2UsaXJlLGdvZSx3ci5mb3JGZWF0dXJlKEV3LGxyZSkscm8uZm9yRmVhdHVyZShbbG9lXSksQnMuZm9yUGx1Z2luKGtQLG9vZSldfSksbn0pKCksTFA9YmUoIltNZXRyaWNzXSBNZXRyaWNzIFNldHRpbmdzIFBhbmUgQ2xvc2VkIiksQlA9YmUoIltNZXRyaWNzXSBNZXRyaWNzIFNldHRpbmdzIFBhbmUgVG9nZ2xlZCIpLFZQPWJlKCJbTWV0cmljc10gU2xpZGUgb3V0IHNldHRpbmdzIG1lbnUgdG9nZ2xlZCIpLEhQPWJlKCJbTWV0cmljc10gTWV0cmljcyBUYWcgTWV0YWRhdGEgUmVxdWVzdGVkIiksVVA9YmUoIltNZXRyaWNzXSBNZXRyaWNzIFRhZyBNZXRhZGF0YSBMb2FkZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSx6UD1iZSgiW01ldHJpY3NdIE1ldHJpY3MgVGFnIE1ldGFkYXRhIEZhaWxlZCIpLGpQPWJlKCJbTWV0cmljc10gTWV0cmljcyBTZXR0aW5ncyBDaGFuZ2UgVG9vbHRpcCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLEdQPShiZSgiW01ldHJpY3NdIE1ldHJpY3MgU2V0dGluZ3MgVG9nZ2xlIFNob3cgRGF0YSBEb3dubG9hZCIpLGJlKCJbTWV0cmljc10gTWV0cmljcyBTZXR0aW5nIFRvZ2dsZSBJZ25vcmUgT3V0bGllciIpKSxXUD1iZSgiW01ldHJpY3NdIE1ldHJpY3MgU2V0dGluZyBDaGFuZ2UgWCBBeGlzIFR5cGUiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxxUD1iZSgiW01ldHJpY3NdIE1ldHJpY3MgU2V0dGluZyBDaGFuZ2UgQ2FyZCBXaWR0aCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFlQPWJlKCJbTWV0cmljc10gTWV0cmljcyBTZXR0aW5nIFJlc2V0IENhcmQgV2lkdGgiKSxYUD1iZSgiW01ldHJpY3NdIE1ldHJpY3MgU2V0dGluZyBDaGFuZ2UgU2NhbGFyIFNtb290aGluZyIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFFQPWJlKCJbTWV0cmljc10gTWV0cmljcyBTZXR0aW5nIFBhcnRpdGlvbiBOb24gTW9ub3RvbmljIFggVG9nZ2xlZCIpLEtQPWJlKCJbTWV0cmljc10gTWV0cmljcyBTZXR0aW5nIENoYW5nZSBJbWFnZSBCcmlnaHRuZXNzIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksWlA9YmUoIltNZXRyaWNzXSBNZXRyaWNzIFNldHRpbmcgQ2hhbmdlIEltYWdlIENvbnRyYXN0Iix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksSlA9YmUoIltNZXRyaWNzXSBJbWFnZSBCcmlnaHRuZXNzIFNldHRpbmcgUmVzZXQiKSwkUD1iZSgiW01ldHJpY3NdIEltYWdlIENvbnRyYXN0IFNldHRpbmcgUmVzZXQiKSxlUj1iZSgiW01ldHJpY3NdIE1ldHJpY3MgU2V0dGluZyBUb2dnbGUgSW1hZ2UgU2hvdyBBY3R1YWwgU2l6ZSIpLHRSPWJlKCJbTWV0cmljc10gTWV0cmljcyBTZXR0aW5nIENoYW5nZSBIaXN0b2dyYW0gTW9kZSIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLG5SPWJlKCJbTWV0cmljc10gTXVsdGlwbGUgVGltZSBTZXJpZXMgUmVxdWVzdGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksaVI9YmUoIltNZXRyaWNzXSBGZXRjaCBUaW1lIFNlcmllcyBSZXF1ZXN0IEZhaWxlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLHJSPWJlKCJbTWV0cmljc10gRmV0Y2ggVGltZSBTZXJpZXMgUmVzcG9uc2UgTG9hZGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksaXk9YmUoIltNZXRyaWNzXSBDYXJkIFZpc2liaWxpdHkgQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLG9SPWJlKCJbTWV0cmljc10gQ2FyZCBTdGVwIFNsaWRlciBDaGFuZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksc1I9YmUoIltNZXRyaWNzXSBUYWcgRmlsdGVyIENoYW5nZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxhUj1iZSgiW01ldHJpY3NdIE1ldHJpY3MgVGFnIEdyb3VwIEV4cGFuc2lvbiBDaGFuZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSkscnk9YmUoIltNZXRyaWNzXSBDYXJkIFBpbiBTdGF0ZSBUb2dnbGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksbFI9YmUoIltNZXRyaWNzXSBUb2dnbGUgVmlzaWJsZSBQbHVnaW4iLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxjUj1iZSgiW01ldHJpY3NdIFRvZ2dsZSBTaG93IEFsbCBQbHVnaW5zIiksWWg9YmUoIltNZXRyaWNzXSBUaW1lIFNlbGVjdGlvbiBDaGFuZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksdm9lPWJlKCJbTWV0cmljc10gTGlua2VkIFRpbWUgU2VsZWN0aW9uIENsZWFyZWQiKSx1Uj1iZSgiW01ldHJpY3NdIExpbmtlZCBUaW1lIEVuYWJsZSBUb2dnbGUiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSx5b2U9YmUoIltNZXRyaWNzXSBTb3J0aW5nIERhdGEgVGFibGUgQnkgSGVhZGVyIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksZFI9YmUoIltNZXRyaWNzXSBEYXRhIHRhYmxlIGNvbHVtbiBkcmFnZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksYm9lPWJlKCJbTWV0cmljc10gRGF0YSB0YWJsZSBjb2x1bW5zIGVkaXRlZCBpbiBlZGl0IG1lbnUiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxwUj1iZSgiW01ldHJpY3NdIERhdGEgdGFibGUgY29sdW1uIHRvZ2dsZWQgaW4gZWRpdCBtZW51Iix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksWGg9YmUoIltNZXRyaWNzXSBUaW1lIFNlbGVjdG9yIEVuYWJsZSBUb2dnbGUiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxoUj1iZSgiW01ldHJpY3NdIFJhbmdlIFNlbGVjdGlvbiBUb2dnbGVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSk7ZnVuY3Rpb24ga3cobix0KXtsZXQgZT17fTtmb3IobGV0IGkgb2YgT2JqZWN0LmtleXMobikpZVtpXT10KG5baV0saSk7cmV0dXJuIGV9dmFyIGNzPSgoKT0+KGZ1bmN0aW9uKG4pe24uTk9ORT0ibm8gYWZmb3JkYW5jZSIsbi5FWFRFTkRFRF9MSU5FPSJleHRlbmRlZExpbmUiLG4uRk9CPSJmb2IiLG4uRk9CX1JFTU9WRUQ9ImZvYlJlbW92ZWQiLG4uRk9CX1RFWFQ9ImZvYlRleHQiLG4uU0VUVElOR1NfVEVYVD0ic2V0dGluZ3NUZXh0IixuLlNFVFRJTkdTX1NMSURFUj0ic2V0dGluZ3NTbGlkZXIiLG4uQ0hBTkdFX1RPX1NJTkdMRT0iY2hhbmdlVG9TaW5nbGUiLG4uSElTVE9HUkFNX0NMSUNLX1RPX1JBTkdFPSJoaXN0b2dyYW1DbGlja1RvUmFuZ2UiLG4uRk9CX0FEREVEPSJmb2JBZGRlZCJ9KGNzfHwoY3M9e30pKSxjcykpKCksYmw9KCgpPT4oZnVuY3Rpb24obil7bi5OT05FPSJubyB0b2dnbGUgYWZmb3JkYW5jZSIsbi5GT0JfREVTRUxFQ1Q9ImZvYkRlc2VsZWN0IixuLkNIRUNLX0JPWD0iY2hlY2tCb3gifShibHx8KGJsPXt9KSksYmwpKSgpLHBhPSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5IT1JJWk9OVEFMPTBdPSJIT1JJWk9OVEFMIixuW24uVkVSVElDQUw9MV09IlZFUlRJQ0FMIn0ocGF8fChwYT17fSkpLHBhKSkoKTtmdW5jdGlvbiBmUihuKXtsZXQgdD1uZXcgTWFwLGU9bi5zbGljZSgpLnNvcnQoKGkscik9PkZ3KGkudGFnLHIudGFnKSk7Zm9yKGxldCBpIG9mIGUpe2xldCByPUJWZShpLnRhZyk7dC5oYXMocil8fHQuc2V0KHIse2dyb3VwTmFtZTpyLGl0ZW1zOltdfSksdC5nZXQocikuaXRlbXMucHVzaChpKX1yZXR1cm5bLi4udC52YWx1ZXMoKV19ZnVuY3Rpb24gQlZlKG4pe3JldHVybiBuLnNwbGl0KCIvIiwxKVswXX1mdW5jdGlvbiBGdyhuLHQpe2xldCBlPTAsaT0wO2Zvcig7Oyl7aWYoZT09PW4ubGVuZ3RoKXJldHVybiBpPT09dC5sZW5ndGg/MDotMTtpZihpPT09dC5sZW5ndGgpcmV0dXJuIDE7aWYoZmcobltlXSkmJmZnKHRbaV0pKXtsZXQgcj1lLG89aTtlPXhvZShuLGUrMSksaT14b2UodCxpKzEpO2xldCBzPU51bWJlcihuLnNsaWNlKHIsZSkpLGE9TnVtYmVyKHQuc2xpY2UobyxpKSk7aWYoczxhKXJldHVybi0xO2lmKHM+YSlyZXR1cm4gMX1lbHNle2lmKGJVKG5bZV0pKXtpZighYlUodFtpXSkpcmV0dXJuLTF9ZWxzZXtpZihiVSh0W2ldKSlyZXR1cm4gMTtpZihuW2VdPHRbaV0pcmV0dXJuLTE7aWYobltlXT50W2ldKXJldHVybiAxfWUrKyxpKyt9fX1mdW5jdGlvbiB4b2Uobix0KXtsZXQgZTt2YXIgbzsobz1lfHwoZT17fSkpW28uTkFUVVJBTD0wXT0iTkFUVVJBTCIsb1tvLlJFQUw9MV09IlJFQUwiLG9bby5FWFBPTkVOVF9TSUdOPTJdPSJFWFBPTkVOVF9TSUdOIixvW28uRVhQT05FTlQ9M109IkVYUE9ORU5UIjtsZXQgaT1lLk5BVFVSQUwscj10O2Zvcig7cjxuLmxlbmd0aDtyKyspaWYoaT09PWUuTkFUVVJBTCl7aWYoIi4iPT09bltyXSlpPWUuUkVBTDtlbHNlIGlmKCJlIj09PW5bcl18fCJFIj09PW5bcl0paT1lLkVYUE9ORU5UX1NJR047ZWxzZSBpZighZmcobltyXSkpYnJlYWt9ZWxzZSBpZihpPT09ZS5SRUFMKXtpZigiZSI9PT1uW3JdfHwiRSI9PT1uW3JdKWk9ZS5FWFBPTkVOVF9TSUdOO2Vsc2UgaWYoIWZnKG5bcl0pKWJyZWFrfWVsc2UgaWYoaT09PWUuRVhQT05FTlRfU0lHTil7aWYoIWZnKG5bcl0pJiYiKyIhPT1uW3JdJiYiLSIhPT1uW3JdKWJyZWFrO2k9ZS5FWFBPTkVOVH1lbHNlIGlmKGk9PT1lLkVYUE9ORU5UJiYhZmcobltyXSkpYnJlYWs7cmV0dXJuIHJ9ZnVuY3Rpb24gZmcobil7cmV0dXJuIjAiPD1uJiZuPD0iOSJ9ZnVuY3Rpb24gYlUobil7cmV0dXJuIi8iPT09bnx8Zmcobil9dmFyIHN5PSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5PUklHSU5BTD0wXT0iT1JJR0lOQUwiLG5bbi5ERVJJVkVEPTFdPSJERVJJVkVEIn0oc3l8fChzeT17fSkpLHN5KSkoKSxLdD0oKCk9PihmdW5jdGlvbihuKXtuLkNPTE9SPSJDT0xPUiIsbi5SRUxBVElWRV9USU1FPSJSRUxBVElWRV9USU1FIixuLlJVTj0iUlVOIixuLlNURVA9IlNURVAiLG4uVElNRT0iVElNRSIsbi5WQUxVRT0iVkFMVUUiLG4uU01PT1RIRUQ9IlNNT09USEVEIixuLlZBTFVFX0NIQU5HRT0iVkFMVUVfQ0hBTkdFIixuLlNUQVJUX1NURVA9IlNUQVJUX1NURVAiLG4uRU5EX1NURVA9IkVORF9TVEVQIixuLlNUQVJUX1ZBTFVFPSJTVEFSVF9WQUxVRSIsbi5FTkRfVkFMVUU9IkVORF9WQUxVRSIsbi5NSU5fVkFMVUU9Ik1JTl9WQUxVRSIsbi5NQVhfVkFMVUU9Ik1BWF9WQUxVRSIsbi5QRVJDRU5UQUdFX0NIQU5HRT0iUEVSQ0VOVEFHRV9DSEFOR0UifShLdHx8KEt0PXt9KSksS3QpKSgpLGdkPSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5TSU5HTEU9MF09IlNJTkdMRSIsbltuLlJBTkdFPTFdPSJSQU5HRSJ9KGdkfHwoZ2Q9e30pKSxnZCkpKCkseGw9KCgpPT4oZnVuY3Rpb24obil7bltuLkFTQ0VORElORz0wXT0iQVNDRU5ESU5HIixuW24uREVTQ0VORElORz0xXT0iREVTQ0VORElORyJ9KHhsfHwoeGw9e30pKSx4bCkpKCk7ZnVuY3Rpb24geFUobix0LGUpe2xldHtwbHVnaW46aSx0YWc6cixydW5JZDpvLHNhbXBsZTpzfT10W25dLGE9cnAoZSxpLHIscyk7aWYoYSl7aWYobnVsbCE9PW8mJmEucnVuVG9TZXJpZXMuaGFzT3duUHJvcGVydHkobykpe2xldCBjPWEucnVuVG9TZXJpZXNbb10ubGVuZ3RoO3JldHVybiBjPjA/Yy0xOm51bGx9bGV0IGw9T2JqZWN0LnZhbHVlcyhhLnJ1blRvU2VyaWVzKS5tYXAoYz0+Yy5sZW5ndGgpO2lmKGwubGVuZ3RoKXJldHVybiBNYXRoLm1heCguLi5sKS0xfXJldHVybiBudWxsfWZ1bmN0aW9uIEhWZShuLHQsZSxpKXtsZXQgcj17Li4udH07Zm9yKGxldCBvIGluIG4pe2lmKCFuLmhhc093blByb3BlcnR5KG8pKWNvbnRpbnVlO2xldCBzPXhVKG8sbixlKTtpZihudWxsPT09cyl7dC5oYXNPd25Qcm9wZXJ0eShvKSYmKHJbb109bnVsbCk7Y29udGludWV9bGV0IGE9dC5oYXNPd25Qcm9wZXJ0eShvKT90W29dLmluZGV4Om51bGwsbD14VShvLG4saSksYz1udWxsIT09YSYmYT09PWw7KG51bGwhPT1hJiZhPnN8fG51bGw9PT1hfHxjKSYmKHJbb109e2luZGV4OnMsaXNDbG9zZXN0OiExfSl9cmV0dXJuIHJ9ZnVuY3Rpb24gQ29lKG4pe2xldCB0PWt3KG4ucnVuVG9Mb2FkU3RhdGUsZT0+ZT09PU9lLkxPQURJTkc/T2UuTE9BRElORzpPZS5OT1RfTE9BREVEKTtyZXR1cm57Li4ubixydW5Ub0xvYWRTdGF0ZTp0fX1mdW5jdGlvbiBNb2Uobix0LGUsaSl7cmV0dXJuIEpTT04uc3RyaW5naWZ5KFtuLHQsZXx8IiIsaV0pfXZhcntpbml0aWFsU3RhdGU6U29lLHJlZHVjZXJzOlVWZX09em0oe3RhZ01ldGFkYXRhTG9hZFN0YXRlOntzdGF0ZTpPZS5OT1RfTE9BREVELGxhc3RMb2FkZWRUaW1lSW5NczpudWxsfSx0YWdNZXRhZGF0YTp7c2NhbGFyczp7dGFnRGVzY3JpcHRpb25zOnt9LHRhZ1RvUnVuczp7fX0saGlzdG9ncmFtczp7dGFnRGVzY3JpcHRpb25zOnt9LHRhZ1RvUnVuczp7fX0saW1hZ2VzOnt0YWdEZXNjcmlwdGlvbnM6e30sdGFnUnVuU2FtcGxlZEluZm86e319fSxjYXJkTGlzdDpbXSxjYXJkVG9QaW5uZWRDb3B5Om5ldyBNYXAsY2FyZFRvUGlubmVkQ29weUNhY2hlOm5ldyBNYXAscGlubmVkQ2FyZFRvT3JpZ2luYWw6bmV3IE1hcCx1bnJlc29sdmVkSW1wb3J0ZWRQaW5uZWRDYXJkczpbXSxjYXJkTWV0YWRhdGFNYXA6e30sY2FyZFN0ZXBJbmRleDp7fSx0YWdGaWx0ZXI6IiIsdGFnR3JvdXBFeHBhbmRlZDpuZXcgTWFwLGxpbmtlZFRpbWVTZWxlY3Rpb246bnVsbCxsaW5rZWRUaW1lRW5hYmxlZDohMSxzdGVwU2VsZWN0b3JFbmFibGVkOiExLHJhbmdlU2VsZWN0aW9uRW5hYmxlZDohMSxzaW5nbGVTZWxlY3Rpb25IZWFkZXJzOlt7dHlwZTpLdC5SVU4sZW5hYmxlZDohMH0se3R5cGU6S3QuU01PT1RIRUQsZW5hYmxlZDohMH0se3R5cGU6S3QuVkFMVUUsZW5hYmxlZDohMH0se3R5cGU6S3QuU1RFUCxlbmFibGVkOiEwfSx7dHlwZTpLdC5SRUxBVElWRV9USU1FLGVuYWJsZWQ6ITB9XSxyYW5nZVNlbGVjdGlvbkhlYWRlcnM6W3t0eXBlOkt0LlJVTixlbmFibGVkOiEwfSx7dHlwZTpLdC5NSU5fVkFMVUUsZW5hYmxlZDohMH0se3R5cGU6S3QuTUFYX1ZBTFVFLGVuYWJsZWQ6ITB9LHt0eXBlOkt0LlNUQVJUX1ZBTFVFLGVuYWJsZWQ6ITB9LHt0eXBlOkt0LkVORF9WQUxVRSxlbmFibGVkOiEwfSx7dHlwZTpLdC5WQUxVRV9DSEFOR0UsZW5hYmxlZDohMH0se3R5cGU6S3QuUEVSQ0VOVEFHRV9DSEFOR0UsZW5hYmxlZDohMH0se3R5cGU6S3QuU1RBUlRfU1RFUCxlbmFibGVkOiEwfSx7dHlwZTpLdC5FTkRfU1RFUCxlbmFibGVkOiEwfV0sZmlsdGVyZWRQbHVnaW5UeXBlczpuZXcgU2V0LHN0ZXBNaW5NYXg6e21pbjoxLzAsbWF4Oi0xLzB9fSx7aXNTZXR0aW5nc1BhbmVPcGVuOiEwLGlzU2xpZGVvdXRNZW51T3BlbjohMSx0aW1lU2VyaWVzRGF0YTp7c2NhbGFyczp7fSxoaXN0b2dyYW1zOnt9LGltYWdlczp7fX0sc2V0dGluZ3M6SUksc2V0dGluZ092ZXJyaWRlczp7fSx2aXNpYmxlQ2FyZE1hcDpuZXcgTWFwfSwobix0LGUpPT5Qcyh0LGUpP246ey4uLm4sdGFnTWV0YWRhdGFMb2FkU3RhdGU6e3N0YXRlOk9lLk5PVF9MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOm51bGx9LHRhZ01ldGFkYXRhOntzY2FsYXJzOnt0YWdEZXNjcmlwdGlvbnM6e30sdGFnVG9SdW5zOnt9fSxoaXN0b2dyYW1zOnt0YWdEZXNjcmlwdGlvbnM6e30sdGFnVG9SdW5zOnt9fSxpbWFnZXM6e3RhZ0Rlc2NyaXB0aW9uczp7fSx0YWdSdW5TYW1wbGVkSW5mbzp7fX19LGNhcmRMaXN0OltdLGNhcmRNZXRhZGF0YU1hcDp7fSx2aXNpYmxlQ2FyZE1hcDpuZXcgTWFwfSksQ1U9U29lLHpWZT12cihTb2UsU2UoS18sKG4se3JvdXRlS2luZDp0LHBhcnRpYWxTdGF0ZTplfSk9PntpZih0IT09aGkuRVhQRVJJTUVOVCYmdCE9PWhpLkNPTVBBUkVfRVhQRVJJTUVOVClyZXR1cm4gbjtsZXQgaT1uZXcgU2V0O2ZvcihsZXQgdSBvZiBuLnBpbm5lZENhcmRUb09yaWdpbmFsLmtleXMoKSl7bGV0e3BsdWdpbjpkLHRhZzpwLHJ1bklkOmgsc2FtcGxlOmZ9PW4uY2FyZE1ldGFkYXRhTWFwW3VdO2kuYWRkKE1vZShkLHAsaCxmKSl9bGV0IHI9ZSxvPVtdO2ZvcihsZXQgdSBvZlsuLi5uLnVucmVzb2x2ZWRJbXBvcnRlZFBpbm5lZENhcmRzLC4uLnIubWV0cmljcy5waW5uZWRDYXJkc10pe2xldCBkPU1vZSh1LnBsdWdpbix1LnRhZyx1LnJ1bklkLHUuc2FtcGxlKTtpLmhhcyhkKXx8KGkuYWRkKGQpLG8ucHVzaCh1KSl9bGV0IHM9SzQobyxuLmNhcmRMaXN0LG4uY2FyZE1ldGFkYXRhTWFwLG4uY2FyZFRvUGlubmVkQ29weSxuLmNhcmRUb1Bpbm5lZENvcHlDYWNoZSxuLnBpbm5lZENhcmRUb09yaWdpbmFsLG4uY2FyZFN0ZXBJbmRleCksYT1yLm1ldHJpY3Muc21vb3RoaW5nLGw9bi5zZXR0aW5nT3ZlcnJpZGVzO2lmKE51bWJlci5pc0Zpbml0ZShhKSYmbnVsbCE9PWEpe2xldCB1PU1hdGgubWF4KDAsTWF0aC5taW4oLjk5OSxOdW1iZXIoYS50b1ByZWNpc2lvbigzKSkpKTtsPXsuLi5uLnNldHRpbmdPdmVycmlkZXMsc2NhbGFyU21vb3RoaW5nOnV9fWxldCBjPXsuLi5uLC4uLnMsc2V0dGluZ092ZXJyaWRlczpsfTtyZXR1cm4gbnVsbCE9PXIubWV0cmljcy50YWdGaWx0ZXImJihjLnRhZ0ZpbHRlcj1yLm1ldHJpY3MudGFnRmlsdGVyKSxjfSksU2UoWWMsKG4se3BhcnRpYWxTZXR0aW5nczp0fSk9PntsZXQgZT17fTt0LnRvb2x0aXBTb3J0JiZPYmplY3QudmFsdWVzKE9vKS5pbmNsdWRlcyh0LnRvb2x0aXBTb3J0KSYmKGUudG9vbHRpcFNvcnQ9dC50b29sdGlwU29ydCksIm51bWJlciI9PXR5cGVvZiB0LnRpbWVTZXJpZXNDYXJkTWluV2lkdGgmJihlLmNhcmRNaW5XaWR0aD10LnRpbWVTZXJpZXNDYXJkTWluV2lkdGgpLCJib29sZWFuIj09dHlwZW9mIHQuaWdub3JlT3V0bGllcnMmJihlLmlnbm9yZU91dGxpZXJzPXQuaWdub3JlT3V0bGllcnMpLCJudW1iZXIiPT10eXBlb2YgdC5zY2FsYXJTbW9vdGhpbmcmJihlLnNjYWxhclNtb290aGluZz10LnNjYWxhclNtb290aGluZyk7bGV0IGk9dC50aW1lU2VyaWVzU2V0dGluZ3NQYW5lT3BlbmVkPz9uLmlzU2V0dGluZ3NQYW5lT3BlbixyPXQuc3RlcFNlbGVjdG9yRW5hYmxlZD8/bi5zdGVwU2VsZWN0b3JFbmFibGVkLG89dC5yYW5nZVNlbGVjdGlvbkVuYWJsZWQ/P24ucmFuZ2VTZWxlY3Rpb25FbmFibGVkLHM9dC5saW5rZWRUaW1lRW5hYmxlZD8/bi5saW5rZWRUaW1lRW5hYmxlZDtyZXR1cm57Li4ubixpc1NldHRpbmdzUGFuZU9wZW46aSxzdGVwU2VsZWN0b3JFbmFibGVkOnIscmFuZ2VTZWxlY3Rpb25FbmFibGVkOm8sbGlua2VkVGltZUVuYWJsZWQ6cyxzZXR0aW5nczp7Li4ubi5zZXR0aW5ncywuLi5lfX19KSxTZShhYSxGYSxuPT57bGV0IHQ9bi50YWdNZXRhZGF0YUxvYWRTdGF0ZS5zdGF0ZT09PU9lLkxPQURJTkc/T2UuTE9BRElORzpPZS5OT1RfTE9BREVELGU9a3cobi50aW1lU2VyaWVzRGF0YSwoaSxyKT0+a3coaSxvPT5mbChyKT9rdyhvLHM9PkNvZShzKSk6Q29lKG8pKSk7cmV0dXJuey4uLm4sdGFnTWV0YWRhdGFMb2FkU3RhdGU6ey4uLm4udGFnTWV0YWRhdGFMb2FkU3RhdGUsc3RhdGU6dH0sdGltZVNlcmllc0RhdGE6ZX19KSxTZShIUCxuPT4oey4uLm4sdGFnTWV0YWRhdGFMb2FkU3RhdGU6ey4uLm4udGFnTWV0YWRhdGFMb2FkU3RhdGUsc3RhdGU6T2UuTE9BRElOR319KSksU2UoelAsbj0+KHsuLi5uLHRhZ01ldGFkYXRhTG9hZFN0YXRlOnsuLi5uLnRhZ01ldGFkYXRhTG9hZFN0YXRlLHN0YXRlOk9lLkZBSUxFRH19KSksU2UoVVAsKG4se3RhZ01ldGFkYXRhOnR9KT0+e2xldCBlPXtzY2FsYXJzOndvZSh0LHJpLlNDQUxBUlMpLGhpc3RvZ3JhbXM6d29lKHQscmkuSElTVE9HUkFNUyksaW1hZ2VzOnRbcmkuSU1BR0VTXX0saT17fSxyPWZ1bmN0aW9uKG4pe2xldCB0PVtdO2ZvcihsZXQgZSBvZiBPYmplY3Qua2V5cyhuKSl7bGV0IHIsaT1lO2lmKGZsKGkpKXtpZighbWwoaSkpdGhyb3cgbmV3IEVycm9yKCJNdWx0aS1ydW4sIHNhbXBsZWQgcGx1Z2luIHN1cHBvcnQgbm90IHlldCBpbXBsZW1lbnRlZCIpO3tsZXQgbz1uW2ldLnRhZ1J1blNhbXBsZWRJbmZvO2ZvcihsZXQgcyBvZiBPYmplY3Qua2V5cyhvKSlmb3IobGV0IGEgb2YgT2JqZWN0LmtleXMob1tzXSkpe2xldHttYXhTYW1wbGVzUGVyU3RlcDpsfT1vW3NdW2FdO2ZvcihsZXQgYz0wO2M8bDtjKyspdC5wdXNoKHtwbHVnaW46aSx0YWc6cyxydW5JZDphLHNhbXBsZTpjLG51bVNhbXBsZTpsfSl9fX1lbHNlIGlmKG1sKGkpKXtyPW5baV0udGFnVG9SdW5zO2ZvcihsZXQgbyBvZiBPYmplY3Qua2V5cyhyKSlmb3IobGV0IHMgb2YgcltvXSl0LnB1c2goe3BsdWdpbjppLHRhZzpvLHJ1bklkOnN9KX1lbHNle3I9bltpXS50YWdUb1J1bnM7Zm9yKGxldCBvIG9mIE9iamVjdC5rZXlzKHIpKXQucHVzaCh7cGx1Z2luOmksdGFnOm8scnVuSWQ6bnVsbH0pfX1yZXR1cm4gdH0oZSksbz1bXTtmb3IobGV0IGggb2Ygcil7bGV0IGY9cGVlKGgpO2lbZl09aCxvLnB1c2goZil9bGV0IHM9bi50YWdHcm91cEV4cGFuZGVkO2lmKDA9PT1uLnRhZ0dyb3VwRXhwYW5kZWQuc2l6ZSl7bGV0IGY9ZlIoby5tYXAobT0+KHsuLi5pW21dLGNhcmRJZDptfSkpLmZpbHRlcihCb29sZWFuKSk7cz1uZXcgTWFwKG4udGFnR3JvdXBFeHBhbmRlZCk7Zm9yKGxldCBtIG9mIGYuc2xpY2UoMCwyKSlzLnNldChtLmdyb3VwTmFtZSwhMCl9bGV0e25leHRDYXJkVG9QaW5uZWRDb3B5OmEsbmV4dFBpbm5lZENhcmRUb09yaWdpbmFsOmwscGlubmVkQ2FyZE1ldGFkYXRhTWFwOmN9PWZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1uZXcgTWFwLHI9bmV3IE1hcCxvPXt9O3JldHVybiBuLmZvckVhY2goKHMsYSk9PnstMSE9PWUuaW5kZXhPZihhKSYmKGkuc2V0KGEscyksci5zZXQocyxhKSxvW3NdPXRbYV0pfSkse25leHRDYXJkVG9QaW5uZWRDb3B5OmksbmV4dFBpbm5lZENhcmRUb09yaWdpbmFsOnIscGlubmVkQ2FyZE1ldGFkYXRhTWFwOm99fShuLmNhcmRUb1Bpbm5lZENvcHlDYWNoZSxpLG8pLHU9ey4uLmksLi4uY30sZD1mdW5jdGlvbihuLHQpe2xldCBlPXt9O3JldHVybiBPYmplY3QuZW50cmllcyhuKS5mb3JFYWNoKChbaSxyXSk9Pnt0W2ldJiYoZVtpXT1yKX0pLGV9KG4uY2FyZFN0ZXBJbmRleCx1KSxwPUs0KG4udW5yZXNvbHZlZEltcG9ydGVkUGlubmVkQ2FyZHMsbyx1LGEsbi5jYXJkVG9QaW5uZWRDb3B5Q2FjaGUsbCxkKTtyZXR1cm57Li4ubiwuLi5wLHRhZ0dyb3VwRXhwYW5kZWQ6cyx0YWdNZXRhZGF0YUxvYWRTdGF0ZTp7c3RhdGU6T2UuTE9BREVELGxhc3RMb2FkZWRUaW1lSW5NczpEYXRlLm5vdygpfSx0YWdNZXRhZGF0YTplLGNhcmRMaXN0Om99fSksU2Uoc1IsKG4se3RhZ0ZpbHRlcjp0fSk9Pih7Li4ubix0YWdGaWx0ZXI6dH0pKSxTZShqUCwobix7c29ydDp0fSk9Pih7Li4ubixzZXR0aW5nT3ZlcnJpZGVzOnsuLi5uLnNldHRpbmdPdmVycmlkZXMsdG9vbHRpcFNvcnQ6dH19KSksU2UoR1Asbj0+e2xldCB0PSEobi5zZXR0aW5nT3ZlcnJpZGVzLmlnbm9yZU91dGxpZXJzPz9uLnNldHRpbmdzLmlnbm9yZU91dGxpZXJzKTtyZXR1cm57Li4ubixzZXR0aW5nT3ZlcnJpZGVzOnsuLi5uLnNldHRpbmdPdmVycmlkZXMsaWdub3JlT3V0bGllcnM6dH19fSksU2UoV1AsKG4se3hBeGlzVHlwZTp0fSk9Pih7Li4ubixzZXR0aW5nT3ZlcnJpZGVzOnsuLi5uLnNldHRpbmdPdmVycmlkZXMseEF4aXNUeXBlOnR9fSkpLFNlKFhQLChuLHtzbW9vdGhpbmc6dH0pPT4oey4uLm4sc2V0dGluZ092ZXJyaWRlczp7Li4ubi5zZXR0aW5nT3ZlcnJpZGVzLHNjYWxhclNtb290aGluZzp0fX0pKSxTZShRUCxuPT57bGV0IHQ9IShuLnNldHRpbmdPdmVycmlkZXMuc2NhbGFyUGFydGl0aW9uTm9uTW9ub3RvbmljWD8/bi5zZXR0aW5ncy5zY2FsYXJQYXJ0aXRpb25Ob25Nb25vdG9uaWNYKTtyZXR1cm57Li4ubixzZXR0aW5nT3ZlcnJpZGVzOnsuLi5uLnNldHRpbmdPdmVycmlkZXMsc2NhbGFyUGFydGl0aW9uTm9uTW9ub3RvbmljWDp0fX19KSxTZShLUCwobix7YnJpZ2h0bmVzc0luTWlsbGk6dH0pPT4oey4uLm4sc2V0dGluZ092ZXJyaWRlczp7Li4ubi5zZXR0aW5nT3ZlcnJpZGVzLGltYWdlQnJpZ2h0bmVzc0luTWlsbGk6dH19KSksU2UoWlAsKG4se2NvbnRyYXN0SW5NaWxsaTp0fSk9Pih7Li4ubixzZXR0aW5nT3ZlcnJpZGVzOnsuLi5uLnNldHRpbmdPdmVycmlkZXMsaW1hZ2VDb250cmFzdEluTWlsbGk6dH19KSksU2UoSlAsbj0+e2xldHtpbWFnZUJyaWdodG5lc3NJbk1pbGxpOnQsLi4uZX09bi5zZXR0aW5nT3ZlcnJpZGVzO3JldHVybnsuLi5uLHNldHRpbmdPdmVycmlkZXM6ZX19KSxTZSgkUCxuPT57bGV0e2ltYWdlQ29udHJhc3RJbk1pbGxpOnQsLi4uZX09bi5zZXR0aW5nT3ZlcnJpZGVzO3JldHVybnsuLi5uLHNldHRpbmdPdmVycmlkZXM6ZX19KSxTZShlUixuPT57bGV0IHQ9IShuLnNldHRpbmdPdmVycmlkZXMuaW1hZ2VTaG93QWN0dWFsU2l6ZT8/bi5zZXR0aW5ncy5pbWFnZVNob3dBY3R1YWxTaXplKTtyZXR1cm57Li4ubixzZXR0aW5nT3ZlcnJpZGVzOnsuLi5uLnNldHRpbmdPdmVycmlkZXMsaW1hZ2VTaG93QWN0dWFsU2l6ZTp0fX19KSxTZSh0Uiwobix7aGlzdG9ncmFtTW9kZTp0fSk9Pih7Li4ubixzZXR0aW5nT3ZlcnJpZGVzOnsuLi5uLnNldHRpbmdPdmVycmlkZXMsaGlzdG9ncmFtTW9kZTp0fX0pKSxTZShxUCwobix7Y2FyZE1pbldpZHRoOnR9KT0+KHsuLi5uLHNldHRpbmdPdmVycmlkZXM6ey4uLm4uc2V0dGluZ092ZXJyaWRlcyxjYXJkTWluV2lkdGg6dH19KSksU2UoWVAsbj0+KHsuLi5uLHNldHRpbmdPdmVycmlkZXM6ey4uLm4uc2V0dGluZ092ZXJyaWRlcyxjYXJkTWluV2lkdGg6bnVsbH19KSksU2UoblIsKG4se3JlcXVlc3RzOnR9KT0+e2lmKCF0Lmxlbmd0aClyZXR1cm4gbjtsZXQgZT17Li4ubi50aW1lU2VyaWVzRGF0YX07Zm9yKGxldCBpIG9mIHQpe2xldHtwbHVnaW46cix0YWc6byxzYW1wbGU6c309aTtlW3JdPUVJKGUscixvLHMpO2xldCBhPXJwKGUscixvLHMpLGw9ejQoaSk/W2kucnVuSWRdOnV2KG4udGFnTWV0YWRhdGEscixvLHMpO2EucnVuVG9Mb2FkU3RhdGU9VEkoT2UuTE9BRElORyxsLGEucnVuVG9Mb2FkU3RhdGUpfXJldHVybnsuLi5uLHRpbWVTZXJpZXNEYXRhOmV9fSksU2UoaVIsKG4se3JlcXVlc3Q6dH0pPT57bGV0IGU9ey4uLm4udGltZVNlcmllc0RhdGF9LHtwbHVnaW46aSx0YWc6cixzYW1wbGU6b309dDtlW2ldPUVJKGUsaSxyLG8pO2xldCBzPXJwKGUsaSxyLG8pLGE9ejQodCk/W3QucnVuSWRdOnV2KG4udGFnTWV0YWRhdGEsaSxyLG8pO3JldHVybiBzLnJ1blRvTG9hZFN0YXRlPVRJKE9lLkZBSUxFRCxhLHMucnVuVG9Mb2FkU3RhdGUpLHsuLi5uLHRpbWVTZXJpZXNEYXRhOmV9fSksU2UoclIsKG4se3Jlc3BvbnNlOnR9KT0+e2xldCBlPXsuLi5uLnN0ZXBNaW5NYXh9LGk9ey4uLm4udGltZVNlcmllc0RhdGF9LHtwbHVnaW46cix0YWc6byxydW5JZDpzLHNhbXBsZTphfT10O2lbcl09RUkoaSxyLG8sYSk7bGV0IGw9cnAoaSxyLG8sYSk7aWYoYkkodCkpe2xldCB1PXM/W3NdOnV2KG4udGFnTWV0YWRhdGEscixvLGEpO2wucnVuVG9Mb2FkU3RhdGU9VEkoT2UuRkFJTEVELHUsbC5ydW5Ub0xvYWRTdGF0ZSl9ZWxzZXtsZXQgdT10LnJ1blRvU2VyaWVzO2wucnVuVG9TZXJpZXM9ey4uLmwucnVuVG9TZXJpZXN9LGwucnVuVG9Mb2FkU3RhdGU9ey4uLmwucnVuVG9Mb2FkU3RhdGV9O2ZvcihsZXQgZCBpbiB1KWlmKHUuaGFzT3duUHJvcGVydHkoZCkpe2wucnVuVG9TZXJpZXNbZF09dVtkXSxsLnJ1blRvTG9hZFN0YXRlW2RdPU9lLkxPQURFRDtmb3IobGV0IHAgb2YgdVtkXSllLm1pbj1NYXRoLm1pbihlLm1pbixwLnN0ZXApLGUubWF4PU1hdGgubWF4KGUubWF4LHAuc3RlcCl9fXJldHVybnsuLi5uLHRpbWVTZXJpZXNEYXRhOmksY2FyZFN0ZXBJbmRleDpIVmUobi5jYXJkTWV0YWRhdGFNYXAsbi5jYXJkU3RlcEluZGV4LGksbi50aW1lU2VyaWVzRGF0YSksc3RlcE1pbk1heDplfX0pLFNlKG9SLChuLHtjYXJkSWQ6dCxzdGVwSW5kZXg6ZX0pPT57bGV0IGk9eFUodCxuLmNhcmRNZXRhZGF0YU1hcCxuLnRpbWVTZXJpZXNEYXRhKSxyPWU7cmV0dXJuIG51bGw9PT1pP3I9bnVsbDplPmkmJihyPWkpLHsuLi5uLGNhcmRTdGVwSW5kZXg6ey4uLm4uY2FyZFN0ZXBJbmRleCxbdF06e2luZGV4OnIsaXNDbG9zZXN0OiExfX19fSksU2UoYVIsKG4se3RhZ0dyb3VwOnR9KT0+e2xldCBlPW5ldyBNYXAobi50YWdHcm91cEV4cGFuZGVkKTtyZXR1cm4gZS5zZXQodCwhZS5nZXQodCkpLHsuLi5uLHRhZ0dyb3VwRXhwYW5kZWQ6ZX19KSxTZShpeSwobix7ZW50ZXJlZENhcmRzOnQsZXhpdGVkQ2FyZHM6ZX0pPT57aWYoIXQubGVuZ3RoJiYhZS5sZW5ndGgpcmV0dXJuIG47bGV0IGk9bmV3IE1hcChuLnZpc2libGVDYXJkTWFwKTtyZXR1cm4gdC5mb3JFYWNoKCh7ZWxlbWVudElkOnIsY2FyZElkOm99KT0+e2xldCBzPWkuZ2V0KHIpPz9udWxsO2lmKG51bGwhPT1zJiZzIT09byl0aHJvdyBuZXcgRXJyb3IoIkEgRE9NIGVsZW1lbnQgY2Fubm90IGJlIHJldXNlZCBmb3IgbW9yZSB0aGFuIDEgdW5pcXVlIGNhcmQgbWV0YWRhdGEiKTtpLnNldChyLG8pfSksZS5mb3JFYWNoKCh7ZWxlbWVudElkOnJ9KT0+e2kuZGVsZXRlKHIpfSksey4uLm4sdmlzaWJsZUNhcmRNYXA6aX19KSxTZShyeSwobix7Y2FyZElkOnR9KT0+e2xldCBlPW4ucGlubmVkQ2FyZFRvT3JpZ2luYWwuaGFzKHQpLGk9IWUmJiFuLmNhcmRUb1Bpbm5lZENvcHkuaGFzKHQpO2lmKGkmJiFESShuKSlyZXR1cm4gbjtsZXQgcj1uZXcgTWFwKG4uY2FyZFRvUGlubmVkQ29weSksbz1uZXcgTWFwKG4uY2FyZFRvUGlubmVkQ29weUNhY2hlKSxzPW5ldyBNYXAobi5waW5uZWRDYXJkVG9PcmlnaW5hbCksYT17Li4ubi5jYXJkTWV0YWRhdGFNYXB9LGw9ey4uLm4uY2FyZFN0ZXBJbmRleH07aWYoZSl7bGV0IGM9bi5waW5uZWRDYXJkVG9PcmlnaW5hbC5nZXQodCk7ci5kZWxldGUoYyksby5kZWxldGUoYykscy5kZWxldGUodCksZGVsZXRlIGFbdF0sZGVsZXRlIGxbdF19ZWxzZSBpZihpKXtsZXQgYz1aNCh0LHIsbyxzLGwsYSk7cj1jLmNhcmRUb1Bpbm5lZENvcHksbz1jLmNhcmRUb1Bpbm5lZENvcHlDYWNoZSxzPWMucGlubmVkQ2FyZFRvT3JpZ2luYWwsYT1jLmNhcmRNZXRhZGF0YU1hcCxsPWMuY2FyZFN0ZXBJbmRleH1lbHNle2xldCBjPW4uY2FyZFRvUGlubmVkQ29weS5nZXQodCk7ci5kZWxldGUodCksby5kZWxldGUodCkscy5kZWxldGUoYyksZGVsZXRlIGFbY10sZGVsZXRlIGxbY119cmV0dXJuey4uLm4sY2FyZE1ldGFkYXRhTWFwOmEsY2FyZFN0ZXBJbmRleDpsLGNhcmRUb1Bpbm5lZENvcHk6cixjYXJkVG9QaW5uZWRDb3B5Q2FjaGU6byxwaW5uZWRDYXJkVG9PcmlnaW5hbDpzfX0pLFNlKHVSLG49PntsZXQgdD0hbi5saW5rZWRUaW1lRW5hYmxlZCxlPXsuLi5uLmNhcmRTdGVwSW5kZXh9LGk9bi5saW5rZWRUaW1lU2VsZWN0aW9uLHI9bi5zdGVwU2VsZWN0b3JFbmFibGVkO2lmKHQpe2xldHttaW46b309bi5zdGVwTWluTWF4LHM9bz09PTEvMD8wOm87aT1uLmxpbmtlZFRpbWVTZWxlY3Rpb24/P3tzdGFydDp7c3RlcDpzfSxlbmQ6bnVsbH0sZT1KNChuLmNhcmRTdGVwSW5kZXgsbi5jYXJkTWV0YWRhdGFNYXAsbi50aW1lU2VyaWVzRGF0YSxpKSxyPXR9cmV0dXJuey4uLm4sY2FyZFN0ZXBJbmRleDplLGxpbmtlZFRpbWVFbmFibGVkOnQsbGlua2VkVGltZVNlbGVjdGlvbjppLHN0ZXBTZWxlY3RvckVuYWJsZWQ6cn19KSxTZShoUixuPT57bGV0IHQ9IW4ucmFuZ2VTZWxlY3Rpb25FbmFibGVkLGU9bi5zdGVwU2VsZWN0b3JFbmFibGVkLGk9bi5saW5rZWRUaW1lU2VsZWN0aW9uO3JldHVybiB0PyhlPXQsaXx8KGk9e3N0YXJ0OntzdGVwOm4uc3RlcE1pbk1heC5taW59LGVuZDp7c3RlcDpuLnN0ZXBNaW5NYXgubWF4fX0pLGkuZW5kfHwoaT17Li4uaSxlbmQ6e3N0ZXA6bi5zdGVwTWluTWF4Lm1heH19KSk6aSYmKGk9ey4uLmksZW5kOm51bGx9KSx7Li4ubixzdGVwU2VsZWN0b3JFbmFibGVkOmUscmFuZ2VTZWxlY3Rpb25FbmFibGVkOnQsbGlua2VkVGltZVNlbGVjdGlvbjppfX0pLFNlKFloLChuLHQpPT57bGV0e3RpbWVTZWxlY3Rpb246ZX09dCxpPWUuc3RhcnQuc3RlcCxyPWUuZW5kPy5zdGVwLHM9bi5yYW5nZVNlbGVjdGlvbkVuYWJsZWQ7bi5saW5rZWRUaW1lRW5hYmxlZCYmKHM9dm9pZCAwIT09cik7bGV0IGE9e3N0YXJ0OntzdGVwOml9LGVuZDp2b2lkIDA9PT1yP251bGw6e3N0ZXA6aT5yP2k6cn19LGw9SjQobi5jYXJkU3RlcEluZGV4LG4uY2FyZE1ldGFkYXRhTWFwLG4udGltZVNlcmllc0RhdGEsYSk7cmV0dXJuey4uLm4sbGlua2VkVGltZVNlbGVjdGlvbjphLGNhcmRTdGVwSW5kZXg6bCxyYW5nZVNlbGVjdGlvbkVuYWJsZWQ6c319KSxTZShYaCwobix7YWZmb3JkYW5jZTp0fSk9PntpZighbi5saW5rZWRUaW1lRW5hYmxlZCYmdCE9PWJsLkNIRUNLX0JPWClyZXR1cm57Li4ubn07bGV0IGU9IW4uc3RlcFNlbGVjdG9yRW5hYmxlZCxpPWUmJm4ubGlua2VkVGltZUVuYWJsZWQscj1lJiZuLnJhbmdlU2VsZWN0aW9uRW5hYmxlZDtyZXR1cm57Li4ubixsaW5rZWRUaW1lRW5hYmxlZDppLHN0ZXBTZWxlY3RvckVuYWJsZWQ6ZSxyYW5nZVNlbGVjdGlvbkVuYWJsZWQ6cn19KSxTZSh2b2Usbj0+KHsuLi5uLGxpbmtlZFRpbWVTZWxlY3Rpb246bnVsbH0pKSxTZShkUiwobix7bmV3T3JkZXI6dH0pPT5uLnJhbmdlU2VsZWN0aW9uRW5hYmxlZD97Li4ubixyYW5nZVNlbGVjdGlvbkhlYWRlcnM6dH06ey4uLm4sc2luZ2xlU2VsZWN0aW9uSGVhZGVyczp0fSksU2UoYm9lLChuLHtkYXRhVGFibGVNb2RlOnQsaGVhZGVyczplfSk9PntsZXQgaT1bXSxyPVtdO3JldHVybiBlLmZvckVhY2gobz0+e28uZW5hYmxlZD9pLnB1c2gobyk6ci5wdXNoKG8pfSksdD09PWdkLlJBTkdFP3suLi5uLHJhbmdlU2VsZWN0aW9uSGVhZGVyczppLmNvbmNhdChyKX06ey4uLm4sc2luZ2xlU2VsZWN0aW9uSGVhZGVyczppLmNvbmNhdChyKX19KSxTZShwUiwobix7ZGF0YVRhYmxlTW9kZTp0LGhlYWRlclR5cGU6ZX0pPT57bGV0IGk9dD09PWdkLlJBTkdFP24ucmFuZ2VTZWxlY3Rpb25IZWFkZXJzOm4uc2luZ2xlU2VsZWN0aW9uSGVhZGVycyxyPWkuZmluZEluZGV4KGE9PmEudHlwZT09PWUpLG89ZnVuY3Rpb24obil7bGV0IHQ9MDtyZXR1cm4gbi5mb3JFYWNoKGU9PntlLmVuYWJsZWQmJnQrK30pLHR9KGkpO2lbcl0uZW5hYmxlZCYmby0tO2xldCBzPWZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1bLi4uZV07cmV0dXJuIGkuc3BsaWNlKG4sMSksaS5zcGxpY2UodCwwLGVbbl0pLGl9KHIsbyxpKTtyZXR1cm4gc1tvXT17dHlwZTpzW29dLnR5cGUsZW5hYmxlZDohc1tvXS5lbmFibGVkfSx0PT09Z2QuUkFOR0U/ey4uLm4scmFuZ2VTZWxlY3Rpb25IZWFkZXJzOnN9OnsuLi5uLHNpbmdsZVNlbGVjdGlvbkhlYWRlcnM6c319KSxTZShsUiwobix7cGx1Z2luOnR9KT0+e2xldCBlPW5ldyBTZXQobi5maWx0ZXJlZFBsdWdpblR5cGVzKTtyZXR1cm4gZS5oYXModCk/ZS5kZWxldGUodCk6ZS5hZGQodCksT2JqZWN0LnZhbHVlcyhyaSkuZXZlcnkoaT0+ZS5oYXMoaSkpJiYoZT1uZXcgU2V0KSx7Li4ubixmaWx0ZXJlZFBsdWdpblR5cGVzOmV9fSksU2UoY1Isbj0+KHsuLi5uLGZpbHRlcmVkUGx1Z2luVHlwZXM6bmV3IFNldH0pKSxTZShCUCxuPT4oey4uLm4saXNTZXR0aW5nc1BhbmVPcGVuOiFuLmlzU2V0dGluZ3NQYW5lT3Blbn0pKSxTZShMUCxuPT4oey4uLm4saXNTZXR0aW5nc1BhbmVPcGVuOiExfSkpLFNlKFZQLG49Pih7Li4ubixpc1NsaWRlb3V0TWVudU9wZW46IW4uaXNTbGlkZW91dE1lbnVPcGVufSkpKTtmdW5jdGlvbiBFb2Uobix0KXtyZXR1cm4gam0oelZlLFVWZSkobix0KX1mdW5jdGlvbiB3b2Uobix0KXtyZXR1cm57dGFnRGVzY3JpcHRpb25zOm5bdF0udGFnRGVzY3JpcHRpb25zLHRhZ1RvUnVuczpqVmUoblt0XS5ydW5UYWdJbmZvKX19ZnVuY3Rpb24galZlKG4pe2xldCB0PXt9O2ZvcihsZXQgZSBpbiBuKWZvcihsZXQgaSBvZiBuW2VdKXRbaV09Wy4uLnRbaV18fFtdLGVdO3JldHVybiB0fXZhciBxVmU9SihiaCx0Yywobix0LGUpPT50P3suLi50LGxvYWRTdGF0ZTpuLGlkOmV9Om51bGwpLFRvZT1iZSgiW01ldHJpY3MgRWZmZWN0c10gSW5pdCIpLERvZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLmFjdGlvbnMkPWUsdGhpcy5zdG9yZT1pLHRoaXMuZGF0YVNvdXJjZT1yLHRoaXMuZGFzaGJvYXJkU2hvd25XaXRob3V0RGF0YSQ9dGhpcy5hY3Rpb25zJC5waXBlKGlpKFRvZSxadSxVbSxKbCksV3QodGhpcy5zdG9yZS5zZWxlY3QoUnMpLHRoaXMuc3RvcmUuc2VsZWN0KFVNKSksWWUoKFssbyxzXSk9Pm89PT1CTSYmcy5zdGF0ZT09PU9lLk5PVF9MT0FERUQpKSx0aGlzLnJlbG9hZFJlcXVlc3RlZFdoaWxlU2hvd24kPXRoaXMuYWN0aW9ucyQucGlwZShpaShhYSxGYSksV3QodGhpcy5zdG9yZS5zZWxlY3QoUnMpKSxZZSgoWyxvXSk9Pm89PT1CTSkpLHRoaXMubG9hZFRhZ01ldGFkYXRhJD1KdCh0aGlzLmRhc2hib2FyZFNob3duV2l0aG91dERhdGEkLHRoaXMucmVsb2FkUmVxdWVzdGVkV2hpbGVTaG93biQpLnBpcGUoV3QodGhpcy5zdG9yZS5zZWxlY3QoVU0pLHRoaXMuc3RvcmUuc2VsZWN0KFdvKSksWWUoKFssbyxzXSk9Pm8uc3RhdGUhPT1PZS5MT0FESU5HJiZudWxsIT09cyksa3QoKCk9Pnt0aGlzLnN0b3JlLmRpc3BhdGNoKEhQKCkpfSksdWkoKFssLG9dKT0+dGhpcy5kYXRhU291cmNlLmZldGNoVGFnTWV0YWRhdGEobykucGlwZShrdChzPT57dGhpcy5zdG9yZS5kaXNwYXRjaChVUCh7dGFnTWV0YWRhdGE6c30pKX0pLGZvKCgpPT4odGhpcy5zdG9yZS5kaXNwYXRjaCh6UCgpKSxYdChudWxsKSkpKSkpLHRoaXMudmlzaWJsZUNhcmRzV2l0aG91dERhdGFDaGFuZ2VkJD10aGlzLmFjdGlvbnMkLnBpcGUoaWkoaXkpLFd0KHRoaXMuZ2V0VmlzaWJsZUNhcmRGZXRjaEluZm9zKCkpLEwoKFssb10pPT5vLmZpbHRlcihzPT5zLmxvYWRTdGF0ZT09PU9lLk5PVF9MT0FERUQpKSksdGhpcy52aXNpYmxlQ2FyZHNSZWxvYWRlZCQ9dGhpcy5yZWxvYWRSZXF1ZXN0ZWRXaGlsZVNob3duJC5waXBlKFd0KHRoaXMuZ2V0VmlzaWJsZUNhcmRGZXRjaEluZm9zKCkpLEwoKFssb10pPT5vLmZpbHRlcihzPT5zLmxvYWRTdGF0ZSE9PU9lLkxPQURJTkcpKSksdGhpcy5sb2FkVGltZVNlcmllcyQ9SnQodGhpcy52aXNpYmxlQ2FyZHNXaXRob3V0RGF0YUNoYW5nZWQkLHRoaXMudmlzaWJsZUNhcmRzUmVsb2FkZWQkKS5waXBlKFllKG89Pm8ubGVuZ3RoPjApLFd0KHRoaXMuc3RvcmUuc2VsZWN0KFdvKS5waXBlKFllKG89Pm51bGwhPT1vKSkpLHhuKChbbyxzXSk9PnRoaXMuZmV0Y2hUaW1lU2VyaWVzRm9yQ2FyZHMobyxzKSkpLHRoaXMuZGF0YUVmZmVjdHMkPWNyKCgpPT5KdCh0aGlzLmxvYWRUYWdNZXRhZGF0YSQsdGhpcy5sb2FkVGltZVNlcmllcyQpLHtkaXNwYXRjaDohMX0pfW5ncnhPbkluaXRFZmZlY3RzKCl7cmV0dXJuIFRvZSgpfWdldFZpc2libGVDYXJkRmV0Y2hJbmZvcygpe3JldHVybiB0aGlzLnN0b3JlLnNlbGVjdChtZWUpLnBpcGUodWkoaT0+aS5zaXplP2xyKFsuLi5pXS5tYXAobz0+dGhpcy5zdG9yZS5zZWxlY3QocVZlLG8pLnBpcGUoUXQoMSkpKSk6WHQoW10pKSxMKGk9PmkuZmlsdGVyKEJvb2xlYW4pKSl9ZmV0Y2hUaW1lU2VyaWVzKGUpe3JldHVybiB0aGlzLmRhdGFTb3VyY2UuZmV0Y2hUaW1lU2VyaWVzKFtlXSkucGlwZShrdChpPT57bGV0IHI9aS5maWx0ZXIoYkkpO3IubGVuZ3RoJiZjb25zb2xlLmVycm9yKCJUaW1lIHNlcmllcyByZXNwb25zZSBjb250YWluZWQgZXJyb3JzOiIsciksdGhpcy5zdG9yZS5kaXNwYXRjaChyUih7cmVzcG9uc2U6aVswXX0pKX0pLGZvKCgpPT4odGhpcy5zdG9yZS5kaXNwYXRjaChpUih7cmVxdWVzdDplfSkpLFh0KG51bGwpKSkpfWZldGNoVGltZVNlcmllc0ZvckNhcmRzKGUsaSl7cmV0dXJuIFh0KGUubWFwKG89PntsZXR7cGx1Z2luOnMsdGFnOmEscnVuSWQ6bCxzYW1wbGU6Y309byx1PW1sKHMpP3twbHVnaW46cyx0YWc6YSxydW5JZDpsfTp7cGx1Z2luOnMsdGFnOmEsZXhwZXJpbWVudElkczppfTtyZXR1cm4gdm9pZCAwIT09YyYmKHUuc2FtcGxlPWMpLHV9KSkucGlwZShrdChvPT57dGhpcy5zdG9yZS5kaXNwYXRjaChuUih7cmVxdWVzdHM6b30pKX0pLHhuKG89Pkp0KC4uLm8ubWFwKGE9PnRoaXMuZmV0Y2hUaW1lU2VyaWVzKGEpKSkpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoaihQbyksaihDZSksaigkdSkpfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLE1VPW5ldyBwZSgiTWV0cmljcyBTdG9yZSBDb25maWciKSx3VT1uZXcgcGUoIk1ldHJpY3MgSW5pdGlhbCBTZXR0aW5ncyBDb25maWciKTtmdW5jdGlvbiBBb2Uobil7cmV0dXJuIG4/e2luaXRpYWxTdGF0ZTp7Li4uQ1Usc2V0dGluZ3M6bn19Ontpbml0aWFsU3RhdGU6Q1V9fXZhciBDbD0oKCk9PihmdW5jdGlvbihuKXtuW24uTEVGVD0xXT0iTEVGVCIsbltuLlJJR0hUPTJdPSJSSUdIVCIsbltuLk1JRERMRT00XT0iTUlERExFIixuW24uRk9VUlRIPThdPSJGT1VSVEgiLG5bbi5GSUZUSD0zMl09IkZJRlRIIn0oQ2x8fChDbD17fSkpLENsKSkoKSxJb2U9MDtmdW5jdGlvbiBZVmUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJidXR0b24iLDMpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkuZXhwYW5kU2lkZWJhcigpKX0pLE8oMSwibWF0LWljb24iLDQpLHYoKX19ZnVuY3Rpb24gWFZlKG4sdCl7aWYoMSZuJiYoXygwLCJuYXYiLDUpLEIoMSwiYXN5bmMiKSxWbigyLDEpLHYoKSksMiZuKXtsZXQgZT1TKCk7UHQoIndpZHRoIixVKDEsNCxlLndpZHRoJCksIiUiKSgibWluLXdpZHRoIixlLk1JTklNVU1fU0lERUJBUl9XSURUSF9JTl9QWCwicHgiKX19ZnVuY3Rpb24gUVZlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2Iiw2KSxQKCJtb3VzZWRvd24iLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5yZXNpemVHcmFiYmVkKCkpfSksdigpfX12YXIgS1ZlPVtbWyIiLCJtYWluIiwiIl1dLFtbIiIsInNpZGViYXIiLCIiXV1dLFpWZT1bIlttYWluXSIsIltzaWRlYmFyXSJdLFJvZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSl7dGhpcy5zdG9yZT1lLHRoaXMud2lkdGgkPXRoaXMuc3RvcmUuc2VsZWN0KGlJKSx0aGlzLm5nVW5zdWJzY3JpYmU9bmV3IGtlLHRoaXMucmVzaXppbmc9ITEsdGhpcy5NSU5JTVVNX1NJREVCQVJfV0lEVEhfSU5fUFg9NzUsX2koaS5uYXRpdmVFbGVtZW50LCJtb3VzZW1vdmUiKS5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSksWWUoKCk9PnRoaXMucmVzaXppbmcpKS5zdWJzY3JpYmUocj0+e2lmKChyLmJ1dHRvbnMmQ2wuTEVGVCkhPT1DbC5MRUZUKXJldHVybiB2b2lkKHRoaXMucmVzaXppbmc9ITEpO3IucHJldmVudERlZmF1bHQoKTtsZXR7d2lkdGg6b309aS5uYXRpdmVFbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO3RoaXMuc3RvcmUuZGlzcGF0Y2gobnYoe3dpZHRoSW5QZXJjZW50OnIuY2xpZW50WDw9dGhpcy5NSU5JTVVNX1NJREVCQVJfV0lEVEhfSU5fUFg/MDpyLmNsaWVudFgvbyoxMDB9KSl9KSxfaShpLm5hdGl2ZUVsZW1lbnQsIm1vdXNldXAiLHtwYXNzaXZlOiEwfSkucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLnJlc2l6aW5nPSExfSl9bmdPbkRlc3Ryb3koKXt0aGlzLm5nVW5zdWJzY3JpYmUubmV4dCgpLHRoaXMubmdVbnN1YnNjcmliZS5jb21wbGV0ZSgpfXJlc2l6ZUdyYWJiZWQoKXt0aGlzLnJlc2l6aW5nPSEwfWV4cGFuZFNpZGViYXIoKXt0aGlzLnN0b3JlLmRpc3BhdGNoKG52KHt3aWR0aEluUGVyY2VudDoyMH0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSksTShSZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInRiLWRhc2hib2FyZC1sYXlvdXQiXV0sbmdDb250ZW50U2VsZWN0b3JzOlpWZSxkZWNsczo3LHZhcnM6OSxjb25zdHM6W1siY2xhc3MiLCJleHBhbmQiLDMsImNsaWNrIiw0LCJuZ0lmIl0sWyJjbGFzcyIsInNpZGViYXIiLDMsIndpZHRoIiwibWluV2lkdGgiLDQsIm5nSWYiXSxbImNsYXNzIiwicmVzaXplciIsMywibW91c2Vkb3duIiw0LCJuZ0lmIl0sWzEsImV4cGFuZCIsMywiY2xpY2siXSxbInN2Z0ljb24iLCJleHBhbmRfbW9yZV8yNHB4Il0sWzEsInNpZGViYXIiXSxbMSwicmVzaXplciIsMywibW91c2Vkb3duIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoeGkoS1ZlKSxFKDAsWVZlLDIsMCwiYnV0dG9uIiwwKSxCKDEsImFzeW5jIiksRSgyLFhWZSwzLDYsIm5hdiIsMSksQigzLCJhc3luYyIpLEUoNCxRVmUsMSwwLCJkaXYiLDIpLEIoNSwiYXN5bmMiKSxWbig2KSksMiZlJiYoeSgibmdJZiIsMD09PVUoMSwzLGkud2lkdGgkKSksQygyKSx5KCJuZ0lmIixVKDMsNSxpLndpZHRoJCk+MCksQygyKSx5KCJuZ0lmIixVKDUsNyxpLndpZHRoJCk+MCkpfSxkZXBlbmRlbmNpZXM6W0JlLEd0LEdlXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpyb3c7aGVpZ2h0OjEwMCU7d2lkdGg6MTAwJTtwb3NpdGlvbjpyZWxhdGl2ZX0uc2lkZWJhcltfbmdjb250ZW50LSVDT01QJV17bWF4LXdpZHRoOjgwdnd9LnJlc2l6ZXJbX25nY29udGVudC0lQ09NUCVdLCAuZXhwYW5kW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItY29sb3I6I2ViZWJlYjtib3gtc2l6aW5nOmJvcmRlci1ib3g7ZmxleDowIDA7anVzdGlmeS1zZWxmOnN0cmV0Y2h9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnJlc2l6ZXJbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAucmVzaXplcltfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWNvbG9yOiM1NTV9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmV4cGFuZFtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5leHBhbmRbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1jb2xvcjojNTU1fS5leHBhbmRbX25nY29udGVudC0lQ09NUCVde3dpZHRoOjIwcHh9LnJlc2l6ZXJbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtib3JkZXItc3R5bGU6c29saWQ7Ym9yZGVyLXdpZHRoOjAgMnB4O2N1cnNvcjpldy1yZXNpemU7Y29udGFpbjpzdHJpY3Q7ZGlzcGxheTpmbGV4O2p1c3RpZnktc2VsZjpzdHJldGNofS5yZXNpemVyW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5tYXQtaWNvbltfbmdjb250ZW50LSVDT01QJV17d2lkdGg6MTAwJX0ucmVzaXplcltfbmdjb250ZW50LSVDT01QJV06aG92ZXJ7Ym9yZGVyLWNvbG9yOiNjY2M7b3V0bGluZTozcHggc29saWQgI2NjYzt6LWluZGV4OjF9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnJlc2l6ZXJbX25nY29udGVudC0lQ09NUCVdOmhvdmVyLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAucmVzaXplcltfbmdjb250ZW50LSVDT01QJV06aG92ZXJ7b3V0bGluZS1jb2xvcjojNzc3O2JvcmRlci1jb2xvcjojNzc3fS5leHBhbmRbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtiYWNrZ3JvdW5kOnJnYmEoMCwwLDAsMCk7Ym9yZGVyLXN0eWxlOnNvbGlkO2JvcmRlci13aWR0aDowIDFweCAwIDA7Y29sb3I6aW5oZXJpdDtjb250YWluOmNvbnRlbnQ7Y3Vyc29yOnBvaW50ZXI7ZGlzcGxheTpmbGV4O2p1c3RpZnktc2VsZjpzdHJldGNoO3BhZGRpbmc6MH0uZXhwYW5kW19uZ2NvbnRlbnQtJUNPTVAlXSAgIG1hdC1pY29uW19uZ2NvbnRlbnQtJUNPTVAlXXt0cmFuc2Zvcm06cm90YXRlKC05MGRlZyk7dHJhbnNmb3JtLW9yaWdpbjpjZW50ZXJ9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxTVT1uZXcgV2Vha01hcCxOdz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMucm9vdD10LHRoaXMuYnVmZmVyPWUsdGhpcy5kZXN0cm95ZWRUYXJnZXRzPW5ldyBXZWFrU2V0fWluaXRpYWxpemUodCl7aWYodGhpcy5pbnRlcnNlY3Rpb25PYnNlcnZlcilyZXR1cm47dGhpcy5pbnRlcnNlY3Rpb25DYWxsYmFjaz10O2xldCBlPXt0aHJlc2hvbGQ6MCxyb290OnRoaXMucm9vdD8/bnVsbH07dGhpcy5idWZmZXImJihlLnJvb3RNYXJnaW49dGhpcy5idWZmZXIpLHRoaXMuaW50ZXJzZWN0aW9uT2JzZXJ2ZXI9bmV3IEludGVyc2VjdGlvbk9ic2VydmVyKHRoaXMub25DYXJkSW50ZXJzZWN0aW9uLmJpbmQodGhpcyksZSl9YWRkKHQpe3RoaXMuZW5zdXJlSW5pdGlhbGl6ZWQoKSYmdGhpcy5pbnRlcnNlY3Rpb25PYnNlcnZlci5vYnNlcnZlKHQpfXdpbGxEZXN0cm95KHQpe3RoaXMuZW5zdXJlSW5pdGlhbGl6ZWQoKSYmdGhpcy5kZXN0cm95ZWRUYXJnZXRzLmFkZCh0KX1lbnN1cmVJbml0aWFsaXplZCgpe2lmKCF0aGlzLmludGVyc2VjdGlvbk9ic2VydmVyKXRocm93IG5ldyBFcnJvcigiQ2FyZE9ic2VydmVyIG11c3QgYmUgaW5pdGlhbGl6ZWQgYmVmb3JlIHVzZSIpO3JldHVybiEwfW9uQ2FyZEludGVyc2VjdGlvbih0KXt0LnNvcnQoKHIsbyk9PnIudGltZS1vLnRpbWUpO2xldCBlPW5ldyBTZXQsaT1uZXcgU2V0O2ZvcihsZXR7aXNJbnRlcnNlY3Rpbmc6cix0YXJnZXQ6b31vZiB0KXI/KGUuYWRkKG8pLGkuZGVsZXRlKG8pKTooZS5kZWxldGUobyksaS5hZGQobykpLHRoaXMuZGVzdHJveWVkVGFyZ2V0cy5oYXMobykmJiFyJiYodGhpcy5kZXN0cm95ZWRUYXJnZXRzLmRlbGV0ZShvKSx0aGlzLmludGVyc2VjdGlvbk9ic2VydmVyLnVub2JzZXJ2ZShvKSk7dGhpcy5pbnRlcnNlY3Rpb25DYWxsYmFjayhlLGkpfW9uQ2FyZEludGVyc2VjdGlvbkZvclRlc3QodCl7dGhpcy5vbkNhcmRJbnRlcnNlY3Rpb24odCl9fSxPb2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuaG9zdD1lLHRoaXMuc3RvcmU9aX1vbkNhcmRJbnRlcnNlY3Rpb24oZSxpKXtsZXQgcj1bLi4uZV0ubWFwKHM9PntsZXQgYT1TVS5nZXQocyk7aWYoIWEpdGhyb3cgbmV3IEVycm9yKCJBIENhcmRPYnNlcnZlciBlbGVtZW50IG11c3QgaGF2ZSBhbiBhc3NvY2lhdGVkIGVsZW1lbnQgaWQgYW5kIGNhcmQgaWQuIik7cmV0dXJue2VsZW1lbnRJZDphLmVsZW1lbnRJZCxjYXJkSWQ6YS5jYXJkSWR9fSksbz1bLi4uaV0ubWFwKHM9PntsZXQgYT1TVS5nZXQocyk7aWYoIWEpdGhyb3cgbmV3IEVycm9yKCJBIENhcmRPYnNlcnZlciBlbGVtZW50IG11c3QgaGF2ZSBhbiBhc3NvY2lhdGVkIGVsZW1lbnQgaWQgYW5kIGNhcmQgaWQuIik7cmV0dXJue2VsZW1lbnRJZDphLmVsZW1lbnRJZCxjYXJkSWQ6YS5jYXJkSWR9fSk7dGhpcy5zdG9yZS5kaXNwYXRjaChpeSh7ZW50ZXJlZENhcmRzOnIsZXhpdGVkQ2FyZHM6b30pKX1uZ09uSW5pdCgpe2xldCBlPXRoaXMuaG9zdC5uYXRpdmVFbGVtZW50O1NVLnNldChlLHtlbGVtZW50SWQ6KElvZSsrLFN5bWJvbChJb2UpKSxjYXJkSWQ6dGhpcy5jYXJkSWR9KSx0aGlzLmNhcmRPYnNlcnZlcnx8KHRoaXMuY2FyZE9ic2VydmVyPW5ldyBOdyksdGhpcy5jYXJkT2JzZXJ2ZXIuaW5pdGlhbGl6ZSh0aGlzLm9uQ2FyZEludGVyc2VjdGlvbi5iaW5kKHRoaXMpKSx0aGlzLmNhcmRPYnNlcnZlci5hZGQoZSl9bmdPbkRlc3Ryb3koKXt0aGlzLmNhcmRPYnNlcnZlciYmdGhpcy5jYXJkT2JzZXJ2ZXIud2lsbERlc3Ryb3kodGhpcy5ob3N0Lm5hdGl2ZUVsZW1lbnQpfWhvc3RGb3JUZXN0KCl7cmV0dXJuIHRoaXMuaG9zdH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShSZSksTShDZSkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJjYXJkTGF6eUxvYWRlciIsIiJdXSxpbnB1dHM6e2NhcmRJZDpbImNhcmRMYXp5TG9hZGVyIiwiY2FyZElkIl0sY2FyZE9ic2VydmVyOiJjYXJkT2JzZXJ2ZXIifX0pLG59KSgpLGU1ZT1bImJ1dHRvbiJdLHQ1ZT1bIioiXSxrb2U9bmV3IHBlKCJNQVRfQlVUVE9OX1RPR0dMRV9ERUZBVUxUX09QVElPTlMiKSxGb2U9bmV3IHBlKCJNYXRCdXR0b25Ub2dnbGVHcm91cCIpLG41ZT17cHJvdmlkZTpObyx1c2VFeGlzdGluZzpKbigoKT0+RVUpLG11bHRpOiEwfSxOb2U9MCxtUj1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMuc291cmNlPXQsdGhpcy52YWx1ZT1lfX0sRVU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuX2NoYW5nZURldGVjdG9yPWUsdGhpcy5fdmVydGljYWw9ITEsdGhpcy5fbXVsdGlwbGU9ITEsdGhpcy5fZGlzYWJsZWQ9ITEsdGhpcy5fY29udHJvbFZhbHVlQWNjZXNzb3JDaGFuZ2VGbj0oKT0+e30sdGhpcy5fb25Ub3VjaGVkPSgpPT57fSx0aGlzLl9uYW1lPSJtYXQtYnV0dG9uLXRvZ2dsZS1ncm91cC0iK05vZSsrLHRoaXMudmFsdWVDaGFuZ2U9bmV3IEcsdGhpcy5jaGFuZ2U9bmV3IEcsdGhpcy5hcHBlYXJhbmNlPWkmJmkuYXBwZWFyYW5jZT9pLmFwcGVhcmFuY2U6InN0YW5kYXJkIn1nZXQgbmFtZSgpe3JldHVybiB0aGlzLl9uYW1lfXNldCBuYW1lKGUpe3RoaXMuX25hbWU9ZSx0aGlzLl9tYXJrQnV0dG9uc0ZvckNoZWNrKCl9Z2V0IHZlcnRpY2FsKCl7cmV0dXJuIHRoaXMuX3ZlcnRpY2FsfXNldCB2ZXJ0aWNhbChlKXt0aGlzLl92ZXJ0aWNhbD1SdChlKX1nZXQgdmFsdWUoKXtsZXQgZT10aGlzLl9zZWxlY3Rpb25Nb2RlbD90aGlzLl9zZWxlY3Rpb25Nb2RlbC5zZWxlY3RlZDpbXTtyZXR1cm4gdGhpcy5tdWx0aXBsZT9lLm1hcChpPT5pLnZhbHVlKTplWzBdP2VbMF0udmFsdWU6dm9pZCAwfXNldCB2YWx1ZShlKXt0aGlzLl9zZXRTZWxlY3Rpb25CeVZhbHVlKGUpLHRoaXMudmFsdWVDaGFuZ2UuZW1pdCh0aGlzLnZhbHVlKX1nZXQgc2VsZWN0ZWQoKXtsZXQgZT10aGlzLl9zZWxlY3Rpb25Nb2RlbD90aGlzLl9zZWxlY3Rpb25Nb2RlbC5zZWxlY3RlZDpbXTtyZXR1cm4gdGhpcy5tdWx0aXBsZT9lOmVbMF18fG51bGx9Z2V0IG11bHRpcGxlKCl7cmV0dXJuIHRoaXMuX211bHRpcGxlfXNldCBtdWx0aXBsZShlKXt0aGlzLl9tdWx0aXBsZT1SdChlKSx0aGlzLl9tYXJrQnV0dG9uc0ZvckNoZWNrKCl9Z2V0IGRpc2FibGVkKCl7cmV0dXJuIHRoaXMuX2Rpc2FibGVkfXNldCBkaXNhYmxlZChlKXt0aGlzLl9kaXNhYmxlZD1SdChlKSx0aGlzLl9tYXJrQnV0dG9uc0ZvckNoZWNrKCl9bmdPbkluaXQoKXt0aGlzLl9zZWxlY3Rpb25Nb2RlbD1uZXcgQWgodGhpcy5tdWx0aXBsZSx2b2lkIDAsITEpfW5nQWZ0ZXJDb250ZW50SW5pdCgpe3RoaXMuX3NlbGVjdGlvbk1vZGVsLnNlbGVjdCguLi50aGlzLl9idXR0b25Ub2dnbGVzLmZpbHRlcihlPT5lLmNoZWNrZWQpKX13cml0ZVZhbHVlKGUpe3RoaXMudmFsdWU9ZSx0aGlzLl9jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKX1yZWdpc3Rlck9uQ2hhbmdlKGUpe3RoaXMuX2NvbnRyb2xWYWx1ZUFjY2Vzc29yQ2hhbmdlRm49ZX1yZWdpc3Rlck9uVG91Y2hlZChlKXt0aGlzLl9vblRvdWNoZWQ9ZX1zZXREaXNhYmxlZFN0YXRlKGUpe3RoaXMuZGlzYWJsZWQ9ZX1fZW1pdENoYW5nZUV2ZW50KGUpe2xldCBpPW5ldyBtUihlLHRoaXMudmFsdWUpO3RoaXMuX2NvbnRyb2xWYWx1ZUFjY2Vzc29yQ2hhbmdlRm4oaS52YWx1ZSksdGhpcy5jaGFuZ2UuZW1pdChpKX1fc3luY0J1dHRvblRvZ2dsZShlLGkscj0hMSxvPSExKXshdGhpcy5tdWx0aXBsZSYmdGhpcy5zZWxlY3RlZCYmIWUuY2hlY2tlZCYmKHRoaXMuc2VsZWN0ZWQuY2hlY2tlZD0hMSksdGhpcy5fc2VsZWN0aW9uTW9kZWw/aT90aGlzLl9zZWxlY3Rpb25Nb2RlbC5zZWxlY3QoZSk6dGhpcy5fc2VsZWN0aW9uTW9kZWwuZGVzZWxlY3QoZSk6bz0hMCxvP1Byb21pc2UucmVzb2x2ZSgpLnRoZW4oKCk9PnRoaXMuX3VwZGF0ZU1vZGVsVmFsdWUoZSxyKSk6dGhpcy5fdXBkYXRlTW9kZWxWYWx1ZShlLHIpfV9pc1NlbGVjdGVkKGUpe3JldHVybiB0aGlzLl9zZWxlY3Rpb25Nb2RlbCYmdGhpcy5fc2VsZWN0aW9uTW9kZWwuaXNTZWxlY3RlZChlKX1faXNQcmVjaGVja2VkKGUpe3JldHVybiEodHlwZW9mIHRoaXMuX3Jhd1ZhbHVlPiJ1IikmJih0aGlzLm11bHRpcGxlJiZBcnJheS5pc0FycmF5KHRoaXMuX3Jhd1ZhbHVlKT90aGlzLl9yYXdWYWx1ZS5zb21lKGk9Pm51bGwhPWUudmFsdWUmJmk9PT1lLnZhbHVlKTplLnZhbHVlPT09dGhpcy5fcmF3VmFsdWUpfV9zZXRTZWxlY3Rpb25CeVZhbHVlKGUpe3RoaXMuX3Jhd1ZhbHVlPWUsdGhpcy5fYnV0dG9uVG9nZ2xlcyYmKHRoaXMubXVsdGlwbGUmJmU/KEFycmF5LmlzQXJyYXkoZSksdGhpcy5fY2xlYXJTZWxlY3Rpb24oKSxlLmZvckVhY2goaT0+dGhpcy5fc2VsZWN0VmFsdWUoaSkpKToodGhpcy5fY2xlYXJTZWxlY3Rpb24oKSx0aGlzLl9zZWxlY3RWYWx1ZShlKSkpfV9jbGVhclNlbGVjdGlvbigpe3RoaXMuX3NlbGVjdGlvbk1vZGVsLmNsZWFyKCksdGhpcy5fYnV0dG9uVG9nZ2xlcy5mb3JFYWNoKGU9PmUuY2hlY2tlZD0hMSl9X3NlbGVjdFZhbHVlKGUpe2xldCBpPXRoaXMuX2J1dHRvblRvZ2dsZXMuZmluZChyPT5udWxsIT1yLnZhbHVlJiZyLnZhbHVlPT09ZSk7aSYmKGkuY2hlY2tlZD0hMCx0aGlzLl9zZWxlY3Rpb25Nb2RlbC5zZWxlY3QoaSkpfV91cGRhdGVNb2RlbFZhbHVlKGUsaSl7aSYmdGhpcy5fZW1pdENoYW5nZUV2ZW50KGUpLHRoaXMudmFsdWVDaGFuZ2UuZW1pdCh0aGlzLnZhbHVlKX1fbWFya0J1dHRvbnNGb3JDaGVjaygpe3RoaXMuX2J1dHRvblRvZ2dsZXM/LmZvckVhY2goZT0+ZS5fbWFya0ZvckNoZWNrKCkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKG5uKSxNKGtvZSw4KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1idXR0b24tdG9nZ2xlLWdyb3VwIl1dLGNvbnRlbnRRdWVyaWVzOmZ1bmN0aW9uKGUsaSxyKXtpZigxJmUmJkVpKHIscjVlLDUpLDImZSl7bGV0IG87TmUobz1MZSgpKSYmKGkuX2J1dHRvblRvZ2dsZXM9byl9fSxob3N0QXR0cnM6WyJyb2xlIiwiZ3JvdXAiLDEsIm1hdC1idXR0b24tdG9nZ2xlLWdyb3VwIl0saG9zdFZhcnM6NSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJih6ZSgiYXJpYS1kaXNhYmxlZCIsaS5kaXNhYmxlZCksZXQoIm1hdC1idXR0b24tdG9nZ2xlLXZlcnRpY2FsIixpLnZlcnRpY2FsKSgibWF0LWJ1dHRvbi10b2dnbGUtZ3JvdXAtYXBwZWFyYW5jZS1zdGFuZGFyZCIsInN0YW5kYXJkIj09PWkuYXBwZWFyYW5jZSkpfSxpbnB1dHM6e2FwcGVhcmFuY2U6ImFwcGVhcmFuY2UiLG5hbWU6Im5hbWUiLHZlcnRpY2FsOiJ2ZXJ0aWNhbCIsdmFsdWU6InZhbHVlIixtdWx0aXBsZToibXVsdGlwbGUiLGRpc2FibGVkOiJkaXNhYmxlZCJ9LG91dHB1dHM6e3ZhbHVlQ2hhbmdlOiJ2YWx1ZUNoYW5nZSIsY2hhbmdlOiJjaGFuZ2UifSxleHBvcnRBczpbIm1hdEJ1dHRvblRvZ2dsZUdyb3VwIl0sZmVhdHVyZXM6WyR0KFtuNWUse3Byb3ZpZGU6Rm9lLHVzZUV4aXN0aW5nOm59XSldfSksbn0pKCksaTVlPXFvKGNsYXNze30pLHI1ZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgaTVle2NvbnN0cnVjdG9yKGUsaSxyLG8scyxhKXtzdXBlcigpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmPWksdGhpcy5fZWxlbWVudFJlZj1yLHRoaXMuX2ZvY3VzTW9uaXRvcj1vLHRoaXMuX2NoZWNrZWQ9ITEsdGhpcy5hcmlhTGFiZWxsZWRieT1udWxsLHRoaXMuX2Rpc2FibGVkPSExLHRoaXMuY2hhbmdlPW5ldyBHO2xldCBsPU51bWJlcihzKTt0aGlzLnRhYkluZGV4PWx8fDA9PT1sP2w6bnVsbCx0aGlzLmJ1dHRvblRvZ2dsZUdyb3VwPWUsdGhpcy5hcHBlYXJhbmNlPWEmJmEuYXBwZWFyYW5jZT9hLmFwcGVhcmFuY2U6InN0YW5kYXJkIn1nZXQgYnV0dG9uSWQoKXtyZXR1cm5gJHt0aGlzLmlkfS1idXR0b25gfWdldCBhcHBlYXJhbmNlKCl7cmV0dXJuIHRoaXMuYnV0dG9uVG9nZ2xlR3JvdXA/dGhpcy5idXR0b25Ub2dnbGVHcm91cC5hcHBlYXJhbmNlOnRoaXMuX2FwcGVhcmFuY2V9c2V0IGFwcGVhcmFuY2UoZSl7dGhpcy5fYXBwZWFyYW5jZT1lfWdldCBjaGVja2VkKCl7cmV0dXJuIHRoaXMuYnV0dG9uVG9nZ2xlR3JvdXA/dGhpcy5idXR0b25Ub2dnbGVHcm91cC5faXNTZWxlY3RlZCh0aGlzKTp0aGlzLl9jaGVja2VkfXNldCBjaGVja2VkKGUpe2xldCBpPVJ0KGUpO2khPT10aGlzLl9jaGVja2VkJiYodGhpcy5fY2hlY2tlZD1pLHRoaXMuYnV0dG9uVG9nZ2xlR3JvdXAmJnRoaXMuYnV0dG9uVG9nZ2xlR3JvdXAuX3N5bmNCdXR0b25Ub2dnbGUodGhpcyx0aGlzLl9jaGVja2VkKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSl9Z2V0IGRpc2FibGVkKCl7cmV0dXJuIHRoaXMuX2Rpc2FibGVkfHx0aGlzLmJ1dHRvblRvZ2dsZUdyb3VwJiZ0aGlzLmJ1dHRvblRvZ2dsZUdyb3VwLmRpc2FibGVkfXNldCBkaXNhYmxlZChlKXt0aGlzLl9kaXNhYmxlZD1SdChlKX1uZ09uSW5pdCgpe2xldCBlPXRoaXMuYnV0dG9uVG9nZ2xlR3JvdXA7dGhpcy5pZD10aGlzLmlkfHwibWF0LWJ1dHRvbi10b2dnbGUtIitOb2UrKyxlJiYoZS5faXNQcmVjaGVja2VkKHRoaXMpP3RoaXMuY2hlY2tlZD0hMDplLl9pc1NlbGVjdGVkKHRoaXMpIT09dGhpcy5fY2hlY2tlZCYmZS5fc3luY0J1dHRvblRvZ2dsZSh0aGlzLHRoaXMuX2NoZWNrZWQpKX1uZ0FmdGVyVmlld0luaXQoKXt0aGlzLl9mb2N1c01vbml0b3IubW9uaXRvcih0aGlzLl9lbGVtZW50UmVmLCEwKX1uZ09uRGVzdHJveSgpe2xldCBlPXRoaXMuYnV0dG9uVG9nZ2xlR3JvdXA7dGhpcy5fZm9jdXNNb25pdG9yLnN0b3BNb25pdG9yaW5nKHRoaXMuX2VsZW1lbnRSZWYpLGUmJmUuX2lzU2VsZWN0ZWQodGhpcykmJmUuX3N5bmNCdXR0b25Ub2dnbGUodGhpcywhMSwhMSwhMCl9Zm9jdXMoZSl7dGhpcy5fYnV0dG9uRWxlbWVudC5uYXRpdmVFbGVtZW50LmZvY3VzKGUpfV9vbkJ1dHRvbkNsaWNrKCl7bGV0IGU9ISF0aGlzLl9pc1NpbmdsZVNlbGVjdG9yKCl8fCF0aGlzLl9jaGVja2VkO2UhPT10aGlzLl9jaGVja2VkJiYodGhpcy5fY2hlY2tlZD1lLHRoaXMuYnV0dG9uVG9nZ2xlR3JvdXAmJih0aGlzLmJ1dHRvblRvZ2dsZUdyb3VwLl9zeW5jQnV0dG9uVG9nZ2xlKHRoaXMsdGhpcy5fY2hlY2tlZCwhMCksdGhpcy5idXR0b25Ub2dnbGVHcm91cC5fb25Ub3VjaGVkKCkpKSx0aGlzLmNoYW5nZS5lbWl0KG5ldyBtUih0aGlzLHRoaXMudmFsdWUpKX1fbWFya0ZvckNoZWNrKCl7dGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCl9X2dldEJ1dHRvbk5hbWUoKXtyZXR1cm4gdGhpcy5faXNTaW5nbGVTZWxlY3RvcigpP3RoaXMuYnV0dG9uVG9nZ2xlR3JvdXAubmFtZTp0aGlzLm5hbWV8fG51bGx9X2lzU2luZ2xlU2VsZWN0b3IoKXtyZXR1cm4gdGhpcy5idXR0b25Ub2dnbGVHcm91cCYmIXRoaXMuYnV0dG9uVG9nZ2xlR3JvdXAubXVsdGlwbGV9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oRm9lLDgpLE0obm4pLE0oUmUpLE0oRnIpLHZvKCJ0YWJpbmRleCIpLE0oa29lLDgpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtYXQtYnV0dG9uLXRvZ2dsZSJdXSx2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJm90KGU1ZSw1KSwyJmUpe2xldCByO05lKHI9TGUoKSkmJihpLl9idXR0b25FbGVtZW50PXIuZmlyc3QpfX0saG9zdEF0dHJzOlsicm9sZSIsInByZXNlbnRhdGlvbiIsMSwibWF0LWJ1dHRvbi10b2dnbGUiXSxob3N0VmFyczoxMixob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJlAoImZvY3VzIixmdW5jdGlvbigpe3JldHVybiBpLmZvY3VzKCl9KSwyJmUmJih6ZSgiYXJpYS1sYWJlbCIsbnVsbCkoImFyaWEtbGFiZWxsZWRieSIsbnVsbCkoImlkIixpLmlkKSgibmFtZSIsbnVsbCksZXQoIm1hdC1idXR0b24tdG9nZ2xlLXN0YW5kYWxvbmUiLCFpLmJ1dHRvblRvZ2dsZUdyb3VwKSgibWF0LWJ1dHRvbi10b2dnbGUtY2hlY2tlZCIsaS5jaGVja2VkKSgibWF0LWJ1dHRvbi10b2dnbGUtZGlzYWJsZWQiLGkuZGlzYWJsZWQpKCJtYXQtYnV0dG9uLXRvZ2dsZS1hcHBlYXJhbmNlLXN0YW5kYXJkIiwic3RhbmRhcmQiPT09aS5hcHBlYXJhbmNlKSl9LGlucHV0czp7ZGlzYWJsZVJpcHBsZToiZGlzYWJsZVJpcHBsZSIsYXJpYUxhYmVsOlsiYXJpYS1sYWJlbCIsImFyaWFMYWJlbCJdLGFyaWFMYWJlbGxlZGJ5OlsiYXJpYS1sYWJlbGxlZGJ5IiwiYXJpYUxhYmVsbGVkYnkiXSxpZDoiaWQiLG5hbWU6Im5hbWUiLHZhbHVlOiJ2YWx1ZSIsdGFiSW5kZXg6InRhYkluZGV4IixhcHBlYXJhbmNlOiJhcHBlYXJhbmNlIixjaGVja2VkOiJjaGVja2VkIixkaXNhYmxlZDoiZGlzYWJsZWQifSxvdXRwdXRzOntjaGFuZ2U6ImNoYW5nZSJ9LGV4cG9ydEFzOlsibWF0QnV0dG9uVG9nZ2xlIl0sZmVhdHVyZXM6W3R0XSxuZ0NvbnRlbnRTZWxlY3RvcnM6dDVlLGRlY2xzOjYsdmFyczo5LGNvbnN0czpbWyJ0eXBlIiwiYnV0dG9uIiwxLCJtYXQtYnV0dG9uLXRvZ2dsZS1idXR0b24iLCJtYXQtZm9jdXMtaW5kaWNhdG9yIiwzLCJpZCIsImRpc2FibGVkIiwiY2xpY2siXSxbImJ1dHRvbiIsIiJdLFsxLCJtYXQtYnV0dG9uLXRvZ2dsZS1sYWJlbC1jb250ZW50Il0sWzEsIm1hdC1idXR0b24tdG9nZ2xlLWZvY3VzLW92ZXJsYXkiXSxbIm1hdFJpcHBsZSIsIiIsMSwibWF0LWJ1dHRvbi10b2dnbGUtcmlwcGxlIiwzLCJtYXRSaXBwbGVUcmlnZ2VyIiwibWF0UmlwcGxlRGlzYWJsZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJih4aSgpLF8oMCwiYnV0dG9uIiwwLDEpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLl9vbkJ1dHRvbkNsaWNrKCl9KSxfKDIsInNwYW4iLDIpLFZuKDMpLHYoKSgpLE8oNCwic3BhbiIsMykoNSwic3BhbiIsNCkpLDImZSl7bGV0IHI9JGUoMSk7eSgiaWQiLGkuYnV0dG9uSWQpKCJkaXNhYmxlZCIsaS5kaXNhYmxlZHx8bnVsbCksemUoInRhYmluZGV4IixpLmRpc2FibGVkPy0xOmkudGFiSW5kZXgpKCJhcmlhLXByZXNzZWQiLGkuY2hlY2tlZCkoIm5hbWUiLGkuX2dldEJ1dHRvbk5hbWUoKSkoImFyaWEtbGFiZWwiLGkuYXJpYUxhYmVsKSgiYXJpYS1sYWJlbGxlZGJ5IixpLmFyaWFMYWJlbGxlZGJ5KSxDKDUpLHkoIm1hdFJpcHBsZVRyaWdnZXIiLHIpKCJtYXRSaXBwbGVEaXNhYmxlZCIsaS5kaXNhYmxlUmlwcGxlfHxpLmRpc2FibGVkKX19LGRlcGVuZGVuY2llczpbWW9dLHN0eWxlczpbIi5tYXQtYnV0dG9uLXRvZ2dsZS1zdGFuZGFsb25lLC5tYXQtYnV0dG9uLXRvZ2dsZS1ncm91cHtwb3NpdGlvbjpyZWxhdGl2ZTtkaXNwbGF5OmlubGluZS1mbGV4O2ZsZXgtZGlyZWN0aW9uOnJvdzt3aGl0ZS1zcGFjZTpub3dyYXA7b3ZlcmZsb3c6aGlkZGVuO2JvcmRlci1yYWRpdXM6MnB4Oy13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjpyZ2JhKDAsMCwwLDApO3RyYW5zZm9ybTp0cmFuc2xhdGVaKDApfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1idXR0b24tdG9nZ2xlLXN0YW5kYWxvbmUsLmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWJ1dHRvbi10b2dnbGUtZ3JvdXB7b3V0bGluZTpzb2xpZCAxcHh9Lm1hdC1idXR0b24tdG9nZ2xlLXN0YW5kYWxvbmUubWF0LWJ1dHRvbi10b2dnbGUtYXBwZWFyYW5jZS1zdGFuZGFyZCwubWF0LWJ1dHRvbi10b2dnbGUtZ3JvdXAtYXBwZWFyYW5jZS1zdGFuZGFyZHtib3JkZXItcmFkaXVzOjRweH0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtYnV0dG9uLXRvZ2dsZS1zdGFuZGFsb25lLm1hdC1idXR0b24tdG9nZ2xlLWFwcGVhcmFuY2Utc3RhbmRhcmQsLmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LWJ1dHRvbi10b2dnbGUtZ3JvdXAtYXBwZWFyYW5jZS1zdGFuZGFyZHtvdXRsaW5lOjB9Lm1hdC1idXR0b24tdG9nZ2xlLXZlcnRpY2Fse2ZsZXgtZGlyZWN0aW9uOmNvbHVtbn0ubWF0LWJ1dHRvbi10b2dnbGUtdmVydGljYWwgLm1hdC1idXR0b24tdG9nZ2xlLWxhYmVsLWNvbnRlbnR7ZGlzcGxheTpibG9ja30ubWF0LWJ1dHRvbi10b2dnbGV7d2hpdGUtc3BhY2U6bm93cmFwO3Bvc2l0aW9uOnJlbGF0aXZlfS5tYXQtYnV0dG9uLXRvZ2dsZSAubWF0LWljb24gc3Zne3ZlcnRpY2FsLWFsaWduOnRvcH0ubWF0LWJ1dHRvbi10b2dnbGUuY2RrLWtleWJvYXJkLWZvY3VzZWQgLm1hdC1idXR0b24tdG9nZ2xlLWZvY3VzLW92ZXJsYXl7b3BhY2l0eToxfS5tYXQtYnV0dG9uLXRvZ2dsZS1hcHBlYXJhbmNlLXN0YW5kYXJkOm5vdCgubWF0LWJ1dHRvbi10b2dnbGUtZGlzYWJsZWQpOmhvdmVyIC5tYXQtYnV0dG9uLXRvZ2dsZS1mb2N1cy1vdmVybGF5e29wYWNpdHk6LjA0fS5tYXQtYnV0dG9uLXRvZ2dsZS1hcHBlYXJhbmNlLXN0YW5kYXJkLmNkay1rZXlib2FyZC1mb2N1c2VkOm5vdCgubWF0LWJ1dHRvbi10b2dnbGUtZGlzYWJsZWQpIC5tYXQtYnV0dG9uLXRvZ2dsZS1mb2N1cy1vdmVybGF5e29wYWNpdHk6LjEyfUBtZWRpYShob3Zlcjogbm9uZSl7Lm1hdC1idXR0b24tdG9nZ2xlLWFwcGVhcmFuY2Utc3RhbmRhcmQ6bm90KC5tYXQtYnV0dG9uLXRvZ2dsZS1kaXNhYmxlZCk6aG92ZXIgLm1hdC1idXR0b24tdG9nZ2xlLWZvY3VzLW92ZXJsYXl7ZGlzcGxheTpub25lfX0ubWF0LWJ1dHRvbi10b2dnbGUtbGFiZWwtY29udGVudHstd2Via2l0LXVzZXItc2VsZWN0Om5vbmU7dXNlci1zZWxlY3Q6bm9uZTtkaXNwbGF5OmlubGluZS1ibG9jaztsaW5lLWhlaWdodDozNnB4O3BhZGRpbmc6MCAxNnB4O3Bvc2l0aW9uOnJlbGF0aXZlfS5tYXQtYnV0dG9uLXRvZ2dsZS1hcHBlYXJhbmNlLXN0YW5kYXJkIC5tYXQtYnV0dG9uLXRvZ2dsZS1sYWJlbC1jb250ZW50e3BhZGRpbmc6MCAxMnB4fS5tYXQtYnV0dG9uLXRvZ2dsZS1sYWJlbC1jb250ZW50Pip7dmVydGljYWwtYWxpZ246bWlkZGxlfS5tYXQtYnV0dG9uLXRvZ2dsZS1mb2N1cy1vdmVybGF5e3RvcDowO2xlZnQ6MDtyaWdodDowO2JvdHRvbTowO3Bvc2l0aW9uOmFic29sdXRlO2JvcmRlci1yYWRpdXM6aW5oZXJpdDtwb2ludGVyLWV2ZW50czpub25lO29wYWNpdHk6MH0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtYnV0dG9uLXRvZ2dsZS1jaGVja2VkIC5tYXQtYnV0dG9uLXRvZ2dsZS1mb2N1cy1vdmVybGF5e2JvcmRlci1ib3R0b206c29saWQgMzZweDtvcGFjaXR5Oi41O2hlaWdodDowfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1idXR0b24tdG9nZ2xlLWNoZWNrZWQ6aG92ZXIgLm1hdC1idXR0b24tdG9nZ2xlLWZvY3VzLW92ZXJsYXl7b3BhY2l0eTouNn0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtYnV0dG9uLXRvZ2dsZS1jaGVja2VkLm1hdC1idXR0b24tdG9nZ2xlLWFwcGVhcmFuY2Utc3RhbmRhcmQgLm1hdC1idXR0b24tdG9nZ2xlLWZvY3VzLW92ZXJsYXl7Ym9yZGVyLWJvdHRvbTpzb2xpZCA1MDBweH0ubWF0LWJ1dHRvbi10b2dnbGUgLm1hdC1idXR0b24tdG9nZ2xlLXJpcHBsZXt0b3A6MDtsZWZ0OjA7cmlnaHQ6MDtib3R0b206MDtwb3NpdGlvbjphYnNvbHV0ZTtwb2ludGVyLWV2ZW50czpub25lfS5tYXQtYnV0dG9uLXRvZ2dsZS1idXR0b257Ym9yZGVyOjA7YmFja2dyb3VuZDpub25lO2NvbG9yOmluaGVyaXQ7cGFkZGluZzowO21hcmdpbjowO2ZvbnQ6aW5oZXJpdDtvdXRsaW5lOm5vbmU7d2lkdGg6MTAwJTtjdXJzb3I6cG9pbnRlcn0ubWF0LWJ1dHRvbi10b2dnbGUtZGlzYWJsZWQgLm1hdC1idXR0b24tdG9nZ2xlLWJ1dHRvbntjdXJzb3I6ZGVmYXVsdH0ubWF0LWJ1dHRvbi10b2dnbGUtYnV0dG9uOjotbW96LWZvY3VzLWlubmVye2JvcmRlcjowfSJdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLGdSPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltsbixfbCxsbl19KSxufSkoKTtmdW5jdGlvbiBzNWUobix0KXtpZigxJm4mJihJbigpLE8oMCwiY2lyY2xlIiw0KSksMiZuKXtsZXQgZT1TKCksaT0kZSgxKTtQdCgiYW5pbWF0aW9uLW5hbWUiLCJtYXQtcHJvZ3Jlc3Mtc3Bpbm5lci1zdHJva2Utcm90YXRlLSIrZS5fc3Bpbm5lckFuaW1hdGlvbkxhYmVsKSgic3Ryb2tlLWRhc2hvZmZzZXQiLGUuX2dldFN0cm9rZURhc2hPZmZzZXQoKSwicHgiKSgic3Ryb2tlLWRhc2hhcnJheSIsZS5fZ2V0U3Ryb2tlQ2lyY3VtZmVyZW5jZSgpLCJweCIpKCJzdHJva2Utd2lkdGgiLGUuX2dldENpcmNsZVN0cm9rZVdpZHRoKCksIiUiKSgidHJhbnNmb3JtLW9yaWdpbiIsZS5fZ2V0Q2lyY2xlVHJhbnNmb3JtT3JpZ2luKGkpKSx6ZSgiciIsZS5fZ2V0Q2lyY2xlUmFkaXVzKCkpfX1mdW5jdGlvbiBhNWUobix0KXtpZigxJm4mJihJbigpLE8oMCwiY2lyY2xlIiw0KSksMiZuKXtsZXQgZT1TKCksaT0kZSgxKTtQdCgic3Ryb2tlLWRhc2hvZmZzZXQiLGUuX2dldFN0cm9rZURhc2hPZmZzZXQoKSwicHgiKSgic3Ryb2tlLWRhc2hhcnJheSIsZS5fZ2V0U3Ryb2tlQ2lyY3VtZmVyZW5jZSgpLCJweCIpKCJzdHJva2Utd2lkdGgiLGUuX2dldENpcmNsZVN0cm9rZVdpZHRoKCksIiUiKSgidHJhbnNmb3JtLW9yaWdpbiIsZS5fZ2V0Q2lyY2xlVHJhbnNmb3JtT3JpZ2luKGkpKSx6ZSgiciIsZS5fZ2V0Q2lyY2xlUmFkaXVzKCkpfX12YXIgYzVlPWtvKGNsYXNze2NvbnN0cnVjdG9yKG4pe3RoaXMuX2VsZW1lbnRSZWY9bn19LCJwcmltYXJ5IiksdTVlPW5ldyBwZSgibWF0LXByb2dyZXNzLXNwaW5uZXItZGVmYXVsdC1vcHRpb25zIix7cHJvdmlkZWRJbjoicm9vdCIsZmFjdG9yeTpmdW5jdGlvbigpe3JldHVybntkaWFtZXRlcjoxMDB9fX0pLEJvPWNsYXNzIGV4dGVuZHMgYzVle2NvbnN0cnVjdG9yKHQsZSxpLHIsbyxzLGEsbCl7c3VwZXIodCksdGhpcy5fZG9jdW1lbnQ9aSx0aGlzLl9kaWFtZXRlcj0xMDAsdGhpcy5fdmFsdWU9MCx0aGlzLl9yZXNpemVTdWJzY3JpcHRpb249U24uRU1QVFksdGhpcy5tb2RlPSJkZXRlcm1pbmF0ZSI7bGV0IGM9Qm8uX2RpYW1ldGVyczt0aGlzLl9zcGlubmVyQW5pbWF0aW9uTGFiZWw9dGhpcy5fZ2V0U3Bpbm5lckFuaW1hdGlvbkxhYmVsKCksYy5oYXMoaS5oZWFkKXx8Yy5zZXQoaS5oZWFkLG5ldyBTZXQoWzEwMF0pKSx0aGlzLl9ub29wQW5pbWF0aW9ucz0iTm9vcEFuaW1hdGlvbnMiPT09ciYmISFvJiYhby5fZm9yY2VBbmltYXRpb25zLCJtYXQtc3Bpbm5lciI9PT10Lm5hdGl2ZUVsZW1lbnQubm9kZU5hbWUudG9Mb3dlckNhc2UoKSYmKHRoaXMubW9kZT0iaW5kZXRlcm1pbmF0ZSIpLG8mJihvLmNvbG9yJiYodGhpcy5jb2xvcj10aGlzLmRlZmF1bHRDb2xvcj1vLmNvbG9yKSxvLmRpYW1ldGVyJiYodGhpcy5kaWFtZXRlcj1vLmRpYW1ldGVyKSxvLnN0cm9rZVdpZHRoJiYodGhpcy5zdHJva2VXaWR0aD1vLnN0cm9rZVdpZHRoKSksZS5pc0Jyb3dzZXImJmUuU0FGQVJJJiZhJiZzJiZsJiYodGhpcy5fcmVzaXplU3Vic2NyaXB0aW9uPWEuY2hhbmdlKDE1MCkuc3Vic2NyaWJlKCgpPT57ImluZGV0ZXJtaW5hdGUiPT09dGhpcy5tb2RlJiZsLnJ1bigoKT0+cy5tYXJrRm9yQ2hlY2soKSl9KSl9Z2V0IGRpYW1ldGVyKCl7cmV0dXJuIHRoaXMuX2RpYW1ldGVyfXNldCBkaWFtZXRlcih0KXt0aGlzLl9kaWFtZXRlcj1CaSh0KSx0aGlzLl9zcGlubmVyQW5pbWF0aW9uTGFiZWw9dGhpcy5fZ2V0U3Bpbm5lckFuaW1hdGlvbkxhYmVsKCksdGhpcy5fc3R5bGVSb290JiZ0aGlzLl9hdHRhY2hTdHlsZU5vZGUoKX1nZXQgc3Ryb2tlV2lkdGgoKXtyZXR1cm4gdGhpcy5fc3Ryb2tlV2lkdGh8fHRoaXMuZGlhbWV0ZXIvMTB9c2V0IHN0cm9rZVdpZHRoKHQpe3RoaXMuX3N0cm9rZVdpZHRoPUJpKHQpfWdldCB2YWx1ZSgpe3JldHVybiJkZXRlcm1pbmF0ZSI9PT10aGlzLm1vZGU/dGhpcy5fdmFsdWU6MH1zZXQgdmFsdWUodCl7dGhpcy5fdmFsdWU9TWF0aC5tYXgoMCxNYXRoLm1pbigxMDAsQmkodCkpKX1uZ09uSW5pdCgpe2xldCB0PXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudDt0aGlzLl9zdHlsZVJvb3Q9YTIodCl8fHRoaXMuX2RvY3VtZW50LmhlYWQsdGhpcy5fYXR0YWNoU3R5bGVOb2RlKCksdC5jbGFzc0xpc3QuYWRkKCJtYXQtcHJvZ3Jlc3Mtc3Bpbm5lci1pbmRldGVybWluYXRlLWFuaW1hdGlvbiIpfW5nT25EZXN0cm95KCl7dGhpcy5fcmVzaXplU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCl9X2dldENpcmNsZVJhZGl1cygpe3JldHVybih0aGlzLmRpYW1ldGVyLTEwKS8yfV9nZXRWaWV3Qm94KCl7bGV0IHQ9Mip0aGlzLl9nZXRDaXJjbGVSYWRpdXMoKSt0aGlzLnN0cm9rZVdpZHRoO3JldHVybmAwIDAgJHt0fSAke3R9YH1fZ2V0U3Ryb2tlQ2lyY3VtZmVyZW5jZSgpe3JldHVybiAyKk1hdGguUEkqdGhpcy5fZ2V0Q2lyY2xlUmFkaXVzKCl9X2dldFN0cm9rZURhc2hPZmZzZXQoKXtyZXR1cm4iZGV0ZXJtaW5hdGUiPT09dGhpcy5tb2RlP3RoaXMuX2dldFN0cm9rZUNpcmN1bWZlcmVuY2UoKSooMTAwLXRoaXMuX3ZhbHVlKS8xMDA6bnVsbH1fZ2V0Q2lyY2xlU3Ryb2tlV2lkdGgoKXtyZXR1cm4gdGhpcy5zdHJva2VXaWR0aC90aGlzLmRpYW1ldGVyKjEwMH1fZ2V0Q2lyY2xlVHJhbnNmb3JtT3JpZ2luKHQpe2xldCBlPTUwKih0LmN1cnJlbnRTY2FsZT8/MSk7cmV0dXJuYCR7ZX0lICR7ZX0lYH1fYXR0YWNoU3R5bGVOb2RlKCl7bGV0IHQ9dGhpcy5fc3R5bGVSb290LGU9dGhpcy5fZGlhbWV0ZXIsaT1Cby5fZGlhbWV0ZXJzLHI9aS5nZXQodCk7aWYoIXJ8fCFyLmhhcyhlKSl7bGV0IG89dGhpcy5fZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3R5bGUiKTtvLnNldEF0dHJpYnV0ZSgibWF0LXNwaW5uZXItYW5pbWF0aW9uIix0aGlzLl9zcGlubmVyQW5pbWF0aW9uTGFiZWwpLG8udGV4dENvbnRlbnQ9dGhpcy5fZ2V0QW5pbWF0aW9uVGV4dCgpLHQuYXBwZW5kQ2hpbGQobykscnx8KHI9bmV3IFNldCxpLnNldCh0LHIpKSxyLmFkZChlKX19X2dldEFuaW1hdGlvblRleHQoKXtsZXQgdD10aGlzLl9nZXRTdHJva2VDaXJjdW1mZXJlbmNlKCk7cmV0dXJuIlxuIEBrZXlmcmFtZXMgbWF0LXByb2dyZXNzLXNwaW5uZXItc3Ryb2tlLXJvdGF0ZS1ESUFNRVRFUiB7XG4gICAgMCUgICAgICB7IHN0cm9rZS1kYXNob2Zmc2V0OiBTVEFSVF9WQUxVRTsgIHRyYW5zZm9ybTogcm90YXRlKDApOyB9XG4gICAgMTIuNSUgICB7IHN0cm9rZS1kYXNob2Zmc2V0OiBFTkRfVkFMVUU7ICAgIHRyYW5zZm9ybTogcm90YXRlKDApOyB9XG4gICAgMTIuNTAwMSUgIHsgc3Ryb2tlLWRhc2hvZmZzZXQ6IEVORF9WQUxVRTsgICAgdHJhbnNmb3JtOiByb3RhdGVYKDE4MGRlZykgcm90YXRlKDcyLjVkZWcpOyB9XG4gICAgMjUlICAgICB7IHN0cm9rZS1kYXNob2Zmc2V0OiBTVEFSVF9WQUxVRTsgIHRyYW5zZm9ybTogcm90YXRlWCgxODBkZWcpIHJvdGF0ZSg3Mi41ZGVnKTsgfVxuXG4gICAgMjUuMDAwMSUgICB7IHN0cm9rZS1kYXNob2Zmc2V0OiBTVEFSVF9WQUxVRTsgIHRyYW5zZm9ybTogcm90YXRlKDI3MGRlZyk7IH1cbiAgICAzNy41JSAgIHsgc3Ryb2tlLWRhc2hvZmZzZXQ6IEVORF9WQUxVRTsgICAgdHJhbnNmb3JtOiByb3RhdGUoMjcwZGVnKTsgfVxuICAgIDM3LjUwMDElICB7IHN0cm9rZS1kYXNob2Zmc2V0OiBFTkRfVkFMVUU7ICAgIHRyYW5zZm9ybTogcm90YXRlWCgxODBkZWcpIHJvdGF0ZSgxNjEuNWRlZyk7IH1cbiAgICA1MCUgICAgIHsgc3Ryb2tlLWRhc2hvZmZzZXQ6IFNUQVJUX1ZBTFVFOyAgdHJhbnNmb3JtOiByb3RhdGVYKDE4MGRlZykgcm90YXRlKDE2MS41ZGVnKTsgfVxuXG4gICAgNTAuMDAwMSUgIHsgc3Ryb2tlLWRhc2hvZmZzZXQ6IFNUQVJUX1ZBTFVFOyAgdHJhbnNmb3JtOiByb3RhdGUoMTgwZGVnKTsgfVxuICAgIDYyLjUlICAgeyBzdHJva2UtZGFzaG9mZnNldDogRU5EX1ZBTFVFOyAgICB0cmFuc2Zvcm06IHJvdGF0ZSgxODBkZWcpOyB9XG4gICAgNjIuNTAwMSUgIHsgc3Ryb2tlLWRhc2hvZmZzZXQ6IEVORF9WQUxVRTsgICAgdHJhbnNmb3JtOiByb3RhdGVYKDE4MGRlZykgcm90YXRlKDI1MS41ZGVnKTsgfVxuICAgIDc1JSAgICAgeyBzdHJva2UtZGFzaG9mZnNldDogU1RBUlRfVkFMVUU7ICB0cmFuc2Zvcm06IHJvdGF0ZVgoMTgwZGVnKSByb3RhdGUoMjUxLjVkZWcpOyB9XG5cbiAgICA3NS4wMDAxJSAgeyBzdHJva2UtZGFzaG9mZnNldDogU1RBUlRfVkFMVUU7ICB0cmFuc2Zvcm06IHJvdGF0ZSg5MGRlZyk7IH1cbiAgICA4Ny41JSAgIHsgc3Ryb2tlLWRhc2hvZmZzZXQ6IEVORF9WQUxVRTsgICAgdHJhbnNmb3JtOiByb3RhdGUoOTBkZWcpOyB9XG4gICAgODcuNTAwMSUgIHsgc3Ryb2tlLWRhc2hvZmZzZXQ6IEVORF9WQUxVRTsgICAgdHJhbnNmb3JtOiByb3RhdGVYKDE4MGRlZykgcm90YXRlKDM0MS41ZGVnKTsgfVxuICAgIDEwMCUgICAgeyBzdHJva2UtZGFzaG9mZnNldDogU1RBUlRfVkFMVUU7ICB0cmFuc2Zvcm06IHJvdGF0ZVgoMTgwZGVnKSByb3RhdGUoMzQxLjVkZWcpOyB9XG4gIH1cbiIucmVwbGFjZSgvU1RBUlRfVkFMVUUvZywiIisuOTUqdCkucmVwbGFjZSgvRU5EX1ZBTFVFL2csIiIrLjIqdCkucmVwbGFjZSgvRElBTUVURVIvZyxgJHt0aGlzLl9zcGlubmVyQW5pbWF0aW9uTGFiZWx9YCl9X2dldFNwaW5uZXJBbmltYXRpb25MYWJlbCgpe3JldHVybiB0aGlzLmRpYW1ldGVyLnRvU3RyaW5nKCkucmVwbGFjZSgiLiIsIl8iKX19O0JvLl9kaWFtZXRlcnM9bmV3IFdlYWtNYXAsQm8uXHUwMjc1ZmFjPWZ1bmN0aW9uKHQpe3JldHVybiBuZXcodHx8Qm8pKE0oUmUpLE0ob2kpLE0oSHQsOCksTShQaSw4KSxNKHU1ZSksTShubiksTShWYSksTShfdCkpfSxCby5cdTAyNzVjbXA9Uih7dHlwZTpCbyxzZWxlY3RvcnM6W1sibWF0LXByb2dyZXNzLXNwaW5uZXIiXSxbIm1hdC1zcGlubmVyIl1dLGhvc3RBdHRyczpbInJvbGUiLCJwcm9ncmVzc2JhciIsInRhYmluZGV4IiwiLTEiLDEsIm1hdC1wcm9ncmVzcy1zcGlubmVyIiwibWF0LXNwaW5uZXIiXSxob3N0VmFyczoxMCxob3N0QmluZGluZ3M6ZnVuY3Rpb24odCxlKXsyJnQmJih6ZSgiYXJpYS12YWx1ZW1pbiIsImRldGVybWluYXRlIj09PWUubW9kZT8wOm51bGwpKCJhcmlhLXZhbHVlbWF4IiwiZGV0ZXJtaW5hdGUiPT09ZS5tb2RlPzEwMDpudWxsKSgiYXJpYS12YWx1ZW5vdyIsImRldGVybWluYXRlIj09PWUubW9kZT9lLnZhbHVlOm51bGwpKCJtb2RlIixlLm1vZGUpLFB0KCJ3aWR0aCIsZS5kaWFtZXRlciwicHgiKSgiaGVpZ2h0IixlLmRpYW1ldGVyLCJweCIpLGV0KCJfbWF0LWFuaW1hdGlvbi1ub29wYWJsZSIsZS5fbm9vcEFuaW1hdGlvbnMpKX0saW5wdXRzOntjb2xvcjoiY29sb3IiLGRpYW1ldGVyOiJkaWFtZXRlciIsc3Ryb2tlV2lkdGg6InN0cm9rZVdpZHRoIixtb2RlOiJtb2RlIix2YWx1ZToidmFsdWUifSxleHBvcnRBczpbIm1hdFByb2dyZXNzU3Bpbm5lciJdLGZlYXR1cmVzOlt0dF0sZGVjbHM6NCx2YXJzOjgsY29uc3RzOltbInByZXNlcnZlQXNwZWN0UmF0aW8iLCJ4TWlkWU1pZCBtZWV0IiwiZm9jdXNhYmxlIiwiZmFsc2UiLCJhcmlhLWhpZGRlbiIsInRydWUiLDMsIm5nU3dpdGNoIl0sWyJzdmciLCIiXSxbImN4IiwiNTAlIiwiY3kiLCI1MCUiLDMsImFuaW1hdGlvbi1uYW1lIiwic3Ryb2tlLWRhc2hvZmZzZXQiLCJzdHJva2UtZGFzaGFycmF5Iiwic3Ryb2tlLXdpZHRoIiwidHJhbnNmb3JtLW9yaWdpbiIsNCwibmdTd2l0Y2hDYXNlIl0sWyJjeCIsIjUwJSIsImN5IiwiNTAlIiwzLCJzdHJva2UtZGFzaG9mZnNldCIsInN0cm9rZS1kYXNoYXJyYXkiLCJzdHJva2Utd2lkdGgiLCJ0cmFuc2Zvcm0tb3JpZ2luIiw0LCJuZ1N3aXRjaENhc2UiXSxbImN4IiwiNTAlIiwiY3kiLCI1MCUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24odCxlKXsxJnQmJihJbigpLF8oMCwic3ZnIiwwLDEpLEUoMixzNWUsMSwxMSwiY2lyY2xlIiwyKSxFKDMsYTVlLDEsOSwiY2lyY2xlIiwzKSx2KCkpLDImdCYmKFB0KCJ3aWR0aCIsZS5kaWFtZXRlciwicHgiKSgiaGVpZ2h0IixlLmRpYW1ldGVyLCJweCIpLHkoIm5nU3dpdGNoIiwiaW5kZXRlcm1pbmF0ZSI9PT1lLm1vZGUpLHplKCJ2aWV3Qm94IixlLl9nZXRWaWV3Qm94KCkpLEMoMikseSgibmdTd2l0Y2hDYXNlIiwhMCksQygxKSx5KCJuZ1N3aXRjaENhc2UiLCExKSl9LGRlcGVuZGVuY2llczpbQ3IsVXJdLHN0eWxlczpbIi5tYXQtcHJvZ3Jlc3Mtc3Bpbm5lcntkaXNwbGF5OmJsb2NrO3Bvc2l0aW9uOnJlbGF0aXZlO292ZXJmbG93OmhpZGRlbn0ubWF0LXByb2dyZXNzLXNwaW5uZXIgc3Zne3Bvc2l0aW9uOmFic29sdXRlO3RyYW5zZm9ybTpyb3RhdGUoLTkwZGVnKTt0b3A6MDtsZWZ0OjA7dHJhbnNmb3JtLW9yaWdpbjpjZW50ZXI7b3ZlcmZsb3c6dmlzaWJsZX0ubWF0LXByb2dyZXNzLXNwaW5uZXIgY2lyY2xle2ZpbGw6cmdiYSgwLDAsMCwwKTt0cmFuc2l0aW9uOnN0cm9rZS1kYXNob2Zmc2V0IDIyNW1zIGxpbmVhcn0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtcHJvZ3Jlc3Mtc3Bpbm5lciBjaXJjbGV7c3Ryb2tlOkNhbnZhc1RleHR9Lm1hdC1wcm9ncmVzcy1zcGlubmVyW21vZGU9aW5kZXRlcm1pbmF0ZV0gc3Zne2FuaW1hdGlvbjptYXQtcHJvZ3Jlc3Mtc3Bpbm5lci1saW5lYXItcm90YXRlIDIwMDBtcyBsaW5lYXIgaW5maW5pdGV9Lm1hdC1wcm9ncmVzcy1zcGlubmVyW21vZGU9aW5kZXRlcm1pbmF0ZV0gY2lyY2xle3RyYW5zaXRpb24tcHJvcGVydHk6c3Ryb2tlO2FuaW1hdGlvbi1kdXJhdGlvbjo0MDAwbXM7YW5pbWF0aW9uLXRpbWluZy1mdW5jdGlvbjpjdWJpYy1iZXppZXIoMC4zNSwgMCwgMC4yNSwgMSk7YW5pbWF0aW9uLWl0ZXJhdGlvbi1jb3VudDppbmZpbml0ZX0ubWF0LXByb2dyZXNzLXNwaW5uZXIuX21hdC1hbmltYXRpb24tbm9vcGFibGUgc3ZnLC5tYXQtcHJvZ3Jlc3Mtc3Bpbm5lci5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSBjaXJjbGV7YW5pbWF0aW9uOm5vbmU7dHJhbnNpdGlvbjpub25lfUBrZXlmcmFtZXMgbWF0LXByb2dyZXNzLXNwaW5uZXItbGluZWFyLXJvdGF0ZXswJXt0cmFuc2Zvcm06cm90YXRlKDBkZWcpfTEwMCV7dHJhbnNmb3JtOnJvdGF0ZSgzNjBkZWcpfX1Aa2V5ZnJhbWVzIG1hdC1wcm9ncmVzcy1zcGlubmVyLXN0cm9rZS1yb3RhdGUtMTAwezAle3N0cm9rZS1kYXNob2Zmc2V0OjI2OC42MDYxNzE1NzVweDt0cmFuc2Zvcm06cm90YXRlKDApfTEyLjUle3N0cm9rZS1kYXNob2Zmc2V0OjU2LjU0ODY2NzdweDt0cmFuc2Zvcm06cm90YXRlKDApfTEyLjUwMDEle3N0cm9rZS1kYXNob2Zmc2V0OjU2LjU0ODY2NzdweDt0cmFuc2Zvcm06cm90YXRlWCgxODBkZWcpIHJvdGF0ZSg3Mi41ZGVnKX0yNSV7c3Ryb2tlLWRhc2hvZmZzZXQ6MjY4LjYwNjE3MTU3NXB4O3RyYW5zZm9ybTpyb3RhdGVYKDE4MGRlZykgcm90YXRlKDcyLjVkZWcpfTI1LjAwMDEle3N0cm9rZS1kYXNob2Zmc2V0OjI2OC42MDYxNzE1NzVweDt0cmFuc2Zvcm06cm90YXRlKDI3MGRlZyl9MzcuNSV7c3Ryb2tlLWRhc2hvZmZzZXQ6NTYuNTQ4NjY3N3B4O3RyYW5zZm9ybTpyb3RhdGUoMjcwZGVnKX0zNy41MDAxJXtzdHJva2UtZGFzaG9mZnNldDo1Ni41NDg2Njc3cHg7dHJhbnNmb3JtOnJvdGF0ZVgoMTgwZGVnKSByb3RhdGUoMTYxLjVkZWcpfTUwJXtzdHJva2UtZGFzaG9mZnNldDoyNjguNjA2MTcxNTc1cHg7dHJhbnNmb3JtOnJvdGF0ZVgoMTgwZGVnKSByb3RhdGUoMTYxLjVkZWcpfTUwLjAwMDEle3N0cm9rZS1kYXNob2Zmc2V0OjI2OC42MDYxNzE1NzVweDt0cmFuc2Zvcm06cm90YXRlKDE4MGRlZyl9NjIuNSV7c3Ryb2tlLWRhc2hvZmZzZXQ6NTYuNTQ4NjY3N3B4O3RyYW5zZm9ybTpyb3RhdGUoMTgwZGVnKX02Mi41MDAxJXtzdHJva2UtZGFzaG9mZnNldDo1Ni41NDg2Njc3cHg7dHJhbnNmb3JtOnJvdGF0ZVgoMTgwZGVnKSByb3RhdGUoMjUxLjVkZWcpfTc1JXtzdHJva2UtZGFzaG9mZnNldDoyNjguNjA2MTcxNTc1cHg7dHJhbnNmb3JtOnJvdGF0ZVgoMTgwZGVnKSByb3RhdGUoMjUxLjVkZWcpfTc1LjAwMDEle3N0cm9rZS1kYXNob2Zmc2V0OjI2OC42MDYxNzE1NzVweDt0cmFuc2Zvcm06cm90YXRlKDkwZGVnKX04Ny41JXtzdHJva2UtZGFzaG9mZnNldDo1Ni41NDg2Njc3cHg7dHJhbnNmb3JtOnJvdGF0ZSg5MGRlZyl9ODcuNTAwMSV7c3Ryb2tlLWRhc2hvZmZzZXQ6NTYuNTQ4NjY3N3B4O3RyYW5zZm9ybTpyb3RhdGVYKDE4MGRlZykgcm90YXRlKDM0MS41ZGVnKX0xMDAle3N0cm9rZS1kYXNob2Zmc2V0OjI2OC42MDYxNzE1NzVweDt0cmFuc2Zvcm06cm90YXRlWCgxODBkZWcpIHJvdGF0ZSgzNDEuNWRlZyl9fSJdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pO3ZhciBfZD0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbbG4sTWUsbG5dfSksbn0pKCk7ZnVuY3Rpb24gaDVlKG4sdCl7aWYoMSZuJiYoXygwLCJiIiksQSgxKSx2KCkpLDImbil7bGV0IGU9UygpLiRpbXBsaWNpdDtDKDEpLGplKCIiLGUuZGlzcGxheUFsaWFzLCI6Iil9fWZ1bmN0aW9uIGY1ZShuLHQpe2lmKDEmbiYmKF8oMCwibWF0LW9wdGlvbiIsMikoMSwic3BhbiIsMyksRSgyLGg1ZSwyLDEsImIiLDQpLEEoMyksdigpKCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQ7eSgidmFsdWUiLGUudmFsdWUpKCJkaXNhYmxlZCIsZS5kaXNhYmxlZCksQygxKSxFVCgidGl0bGUiLCIiLGUuZGlzcGxheUFsaWFzLCI6ICIsZS5kaXNwbGF5VGV4dCwiIiksQygxKSx5KCJuZ0lmIixlLmRpc3BsYXlBbGlhcyksQygxKSxqZSgiICIsZS5kaXNwbGF5VGV4dCwiICIpfX12YXIgTG9lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLnZhbHVlPSIiLHRoaXMub3B0aW9ucz1bXSx0aGlzLnNlbGVjdGlvbkNoYW5nZT1uZXcgR319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGItZHJvcGRvd24iXV0saW5wdXRzOnt2YWx1ZToidmFsdWUiLG9wdGlvbnM6Im9wdGlvbnMifSxvdXRwdXRzOntzZWxlY3Rpb25DaGFuZ2U6InNlbGVjdGlvbkNoYW5nZSJ9LGRlY2xzOjIsdmFyczoyLGNvbnN0czpbWzMsInZhbHVlIiwic2VsZWN0aW9uQ2hhbmdlIl0sWzMsInZhbHVlIiwiZGlzYWJsZWQiLDQsIm5nRm9yIiwibmdGb3JPZiJdLFszLCJ2YWx1ZSIsImRpc2FibGVkIl0sWzEsIm9wdGlvbi1jb250ZW50IiwzLCJ0aXRsZSJdLFs0LCJuZ0lmIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJtYXQtc2VsZWN0IiwwKSxQKCJzZWxlY3Rpb25DaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLnNlbGVjdGlvbkNoYW5nZS5lbWl0KG8udmFsdWUpfSksRSgxLGY1ZSw0LDYsIm1hdC1vcHRpb24iLDEpLHYoKSksMiZlJiYoeSgidmFsdWUiLGkudmFsdWUpLEMoMSkseSgibmdGb3JPZiIsaS5vcHRpb25zKSl9LGRlcGVuZGVuY2llczpbZG4sQmUsSGgsT3NdLHN0eWxlczpbIm1hdC1zZWxlY3RbX25nY29udGVudC0lQ09NUCVde2JvcmRlcjoxcHggc29saWQgIzhlOThhMztib3JkZXItcmFkaXVzOjNweDtib3gtc2l6aW5nOmJvcmRlci1ib3g7cGFkZGluZzo2cHh9bWF0LXNlbGVjdFtfbmdjb250ZW50LSVDT01QJV06Zm9jdXN7b3V0bGluZS1jb2xvcjotd2Via2l0LWZvY3VzLXJpbmctY29sb3I7b3V0bGluZS1zdHlsZTphdXRvfSAgLm1hdC1zZWxlY3QtcGFuZWx7bWF4LXdpZHRoOjcwdnd9ICBtYXQtb3B0aW9uLm1hdC1vcHRpb257aGVpZ2h0OmF1dG99ICAubWF0LW9wdGlvbi10ZXh0e3doaXRlLXNwYWNlOm5vcm1hbDt3b3JkLWJyZWFrOmJyZWFrLWFsbH0ub3B0aW9uLWNvbnRlbnRbX25nY29udGVudC0lQ09NUCVde3doaXRlLXNwYWNlOm5vd3JhcH0iXX0pLG59KSgpO2Z1bmN0aW9uIGc1ZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMzIpKDEsIm1hdC1jaGVja2JveCIsMjcpLFAoImNoYW5nZSIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygyKS5yYW5nZVNlbGVjdGlvblRvZ2dsZWQuZW1pdCgpKX0pLEEoMiwiRW5hYmxlIFJhbmdlIFNlbGVjdGlvbiAiKSx2KCkoKX1pZigyJm4pe2xldCBlPVMoMik7QygxKSx5KCJjaGVja2VkIixlLmlzU2NhbGFyU3RlcFNlbGVjdG9yUmFuZ2VFbmFibGVkKSgiZGlzYWJsZWQiLCFlLmlzQXhpc1R5cGVTdGVwKCkpfX1mdW5jdGlvbiBfNWUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDMzKSgxLCJtYXQtY2hlY2tib3giLDI3KSxQKCJjaGFuZ2UiLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoMikubGlua2VkVGltZVRvZ2dsZWQuZW1pdCgpKX0pLEEoMiksdigpKCl9aWYoMiZuKXtsZXQgZT1TKDIpO0MoMSkseSgiY2hlY2tlZCIsZS5pc0xpbmtlZFRpbWVFbmFibGVkKSgiZGlzYWJsZWQiLCFlLmlzQXhpc1R5cGVTdGVwKCkpLEMoMSksamUoIkxpbmsgYnkgc3RlcCAiLGUuZ2V0TGlua2VkVGltZVNlbGVjdGlvblN0YXJ0U3RlcCgpLCIgIil9fWZ1bmN0aW9uIHY1ZShuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsMzcpfWZ1bmN0aW9uIHk1ZShuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsMzgpfWZ1bmN0aW9uIGI1ZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMzQpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKDIpLm9uU2xpZGVPdXRUb2dnbGVkLmVtaXQoKSl9KSxFKDEsdjVlLDEsMCwibWF0LWljb24iLDM1KSxFKDIseTVlLDEsMCwibWF0LWljb24iLDM2KSxBKDMsIiBPcGVuIENvbHVtbiBFZGl0IENvbnRyb2wgIiksdigpfWlmKDImbil7bGV0IGU9UygyKTtDKDEpLHkoIm5nSWYiLCFlLmlzU2xpZGVPdXRNZW51T3BlbiksQygxKSx5KCJuZ0lmIixlLmlzU2xpZGVPdXRNZW51T3Blbil9fWZ1bmN0aW9uIHg1ZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMjYpKDEsIm1hdC1jaGVja2JveCIsMjcpLFAoImNoYW5nZSIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLnN0ZXBTZWxlY3RvclRvZ2dsZWQuZW1pdCgpKX0pLEEoMiwiRW5hYmxlIHN0ZXAgc2VsZWN0aW9uIGFuZCBkYXRhIHRhYmxlICIpLHYoKSxfKDMsInNwYW4iLDI4KSxBKDQsIihTY2FsYXJzIG9ubHkpIiksdigpLEUoNSxnNWUsMywyLCJkaXYiLDI5KSxFKDYsXzVlLDMsMywiZGl2IiwzMCksRSg3LGI1ZSw0LDIsImRpdiIsMzEpLHYoKX1pZigyJm4pe2xldCBlPVMoKTt5KCJ0aXRsZSIsZS5pc0F4aXNUeXBlU3RlcCgpPyIiOiJPbmx5IGF2YWlsYWJsZSB3aGVuIEhvcml6b250YWwgQXhpcyBpcyBzZXQgdG8gc3RlcCIpLEMoMSkseSgiY2hlY2tlZCIsZS5pc1NjYWxhclN0ZXBTZWxlY3RvckVuYWJsZWQpKCJkaXNhYmxlZCIsIWUuaXNBeGlzVHlwZVN0ZXAoKSksQyg0KSx5KCJuZ0lmIixlLmlzUmFuZ2VTZWxlY3Rpb25BbGxvd2VkKSxDKDEpLHkoIm5nSWYiLGUuaXNMaW5rZWRUaW1lRmVhdHVyZUVuYWJsZWQpLEMoMSkseSgibmdJZiIsZS5pc1NjYWxhckNvbHVtbkN1c3RvbWl6YXRpb25FbmFibGVkKX19ZnVuY3Rpb24gQzVlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwic2VjdGlvbiIsMzkpKDEsImgzIiwxKSxBKDIsIkltYWdlcyIpLHYoKSxfKDMsImRpdiIsNDApKDQsImRpdiIsNDEpLEEoNSwiQnJpZ2h0bmVzcyIpLHYoKSxfKDYsImRpdiIsOCkoNywibWF0LXNsaWRlciIsNDIpLFAoImlucHV0IixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLmltYWdlQnJpZ2h0bmVzc1NsaWRlckNoYW5nZWQkLmVtaXQoci52YWx1ZSkpfSksdigpLF8oOCwiYnV0dG9uIiw0MyksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5pbWFnZUJyaWdodG5lc3NSZXNldC5lbWl0KCkpfSksTyg5LCJtYXQtaWNvbiIsMTEpLHYoKSgpKCksXygxMCwiZGl2Iiw0NCkoMTEsImRpdiIsNDUpLEEoMTIsIkNvbnRyYXN0IiksdigpLF8oMTMsImRpdiIsOCkoMTQsIm1hdC1zbGlkZXIiLDQ2KSxQKCJpbnB1dCIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5pbWFnZUNvbnRyYXN0U2xpZGVyQ2hhbmdlZCQuZW1pdChyLnZhbHVlKSl9KSx2KCksXygxNSwiYnV0dG9uIiw0NyksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5pbWFnZUNvbnRyYXN0UmVzZXQuZW1pdCgpKX0pLE8oMTYsIm1hdC1pY29uIiwxMSksdigpKCkoKSxfKDE3LCJkaXYiLDQ4KSgxOCwibWF0LWNoZWNrYm94IiwyMCksUCgiY2hhbmdlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLmltYWdlU2hvd0FjdHVhbFNpemVDaGFuZ2VkLmVtaXQoci5jaGVja2VkKSl9KSxBKDE5LCJTaG93IGFjdHVhbCBpbWFnZSBzaXplIiksdigpKCkoKX1pZigyJm4pe2xldCBlPVMoKTtDKDcpLHkoIm1heCIsMmUzKSgibWluIiwwKSgic3RlcCIsMTApKCJ2YWx1ZSIsZS5pbWFnZUJyaWdodG5lc3NJbk1pbGxpKSgidGh1bWJMYWJlbCIsITApKCJkaXNwbGF5V2l0aCIsZS5mb3JtYXRNaWxsaVRvWmVyb3RoKSxDKDcpLHkoIm1heCIsNWUzKSgibWluIiwwKSgic3RlcCIsMTApKCJ2YWx1ZSIsZS5pbWFnZUNvbnRyYXN0SW5NaWxsaSkoInRodW1iTGFiZWwiLCEwKSgiZGlzcGxheVdpdGgiLGUuZm9ybWF0TWlsbGlUb1plcm90aCksQyg0KSx5KCJjaGVja2VkIixlLmltYWdlU2hvd0FjdHVhbFNpemUpfX12YXIgVm9lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5sb2NhbGU9ZSx0aGlzLmxpbmtlZFRpbWVUb2dnbGVkPW5ldyBHLHRoaXMubGlua2VkVGltZVNlbGVjdGlvbkNoYW5nZWQ9bmV3IEcsdGhpcy5zdGVwU2VsZWN0b3JUb2dnbGVkPW5ldyBHLHRoaXMucmFuZ2VTZWxlY3Rpb25Ub2dnbGVkPW5ldyBHLHRoaXMub25TbGlkZU91dFRvZ2dsZWQ9bmV3IEcsdGhpcy5Ub29sdGlwU29ydERyb3Bkb3duT3B0aW9ucz1be3ZhbHVlOk9vLkFMUEhBQkVUSUNBTCxkaXNwbGF5VGV4dDoiQWxwaGFiZXRpY2FsIn0se3ZhbHVlOk9vLkFTQ0VORElORyxkaXNwbGF5VGV4dDoiQXNjZW5kaW5nIn0se3ZhbHVlOk9vLkRFU0NFTkRJTkcsZGlzcGxheVRleHQ6IkRlc2NlbmRpbmcifSx7dmFsdWU6T28uTkVBUkVTVCxkaXNwbGF5VGV4dDoiTmVhcmVzdCBQaXhlbCJ9LHt2YWx1ZTpPby5ORUFSRVNUX1ksZGlzcGxheVRleHQ6Ik5lYXJlc3QgWSJ9XSx0aGlzLnRvb2x0aXBTb3J0Q2hhbmdlZD1uZXcgRyx0aGlzLmlnbm9yZU91dGxpZXJzQ2hhbmdlZD1uZXcgRyx0aGlzLlhBeGlzVHlwZT1KaSx0aGlzLlhBeGlzVHlwZURyb3Bkb3duT3B0aW9ucz1be3ZhbHVlOkppLlNURVAsZGlzcGxheVRleHQ6IlN0ZXAifSx7dmFsdWU6SmkuUkVMQVRJVkUsZGlzcGxheVRleHQ6IlJlbGF0aXZlIn0se3ZhbHVlOkppLldBTExfVElNRSxkaXNwbGF5VGV4dDoiV2FsbCJ9XSx0aGlzLnhBeGlzVHlwZUNoYW5nZWQ9bmV3IEcsdGhpcy5NQVhfQ0FSRF9XSURUSF9TTElERVJfVkFMVUU9NzM1LHRoaXMuTUlOX0NBUkRfV0lEVEhfU0xJREVSX1ZBTFVFPTMzNSx0aGlzLmNhcmRXaWR0aFNsaWRlckNoYW5nZWQkPW5ldyBHLHRoaXMuY2FyZFdpZHRoQ2hhbmdlZD10aGlzLmNhcmRXaWR0aFNsaWRlckNoYW5nZWQkLnBpcGUoYnUoMjUwKSksdGhpcy5jYXJkV2lkdGhSZXNldD1uZXcgRyx0aGlzLkhpc3RvZ3JhbU1vZGVEcm9wZG93bk9wdGlvbnM9W3t2YWx1ZTp6ci5PRkZTRVQsZGlzcGxheVRleHQ6Ik9mZnNldCJ9LHt2YWx1ZTp6ci5PVkVSTEFZLGRpc3BsYXlUZXh0OiJPdmVybGF5In1dLHRoaXMuaGlzdG9ncmFtTW9kZUNoYW5nZWQ9bmV3IEcsdGhpcy5NQVhfU01PT1RISU5HX1ZBTFVFPS45OTksdGhpcy5NQVhfU01PT1RISU5HX1NMSURFUl9WQUxVRT0uOTksdGhpcy5zY2FsYXJTbW9vdGhpbmdDb250cm9sQ2hhbmdlZCQ9bmV3IEcsdGhpcy5zY2FsYXJTbW9vdGhpbmdDaGFuZ2VkPXRoaXMuc2NhbGFyU21vb3RoaW5nQ29udHJvbENoYW5nZWQkLnBpcGUoYnUoMjUwKSksdGhpcy5zY2FsYXJQYXJ0aXRpb25YVG9nZ2xlZD1uZXcgRyx0aGlzLmltYWdlQnJpZ2h0bmVzc1NsaWRlckNoYW5nZWQkPW5ldyBHLHRoaXMuaW1hZ2VCcmlnaHRuZXNzSW5NaWxsaUNoYW5nZWQ9dGhpcy5pbWFnZUJyaWdodG5lc3NTbGlkZXJDaGFuZ2VkJC5waXBlKGJ1KDI1MCkpLHRoaXMuaW1hZ2VCcmlnaHRuZXNzUmVzZXQ9bmV3IEcsdGhpcy5pbWFnZUNvbnRyYXN0U2xpZGVyQ2hhbmdlZCQ9bmV3IEcsdGhpcy5pbWFnZUNvbnRyYXN0SW5NaWxsaUNoYW5nZWQ9dGhpcy5pbWFnZUNvbnRyYXN0U2xpZGVyQ2hhbmdlZCQucGlwZShidSgyNTApKSx0aGlzLmltYWdlQ29udHJhc3RSZXNldD1uZXcgRyx0aGlzLmltYWdlU2hvd0FjdHVhbFNpemVDaGFuZ2VkPW5ldyBHfW9uU2NhbGFyU21vb3RoaW5nSW5wdXQoZSl7bGV0IGk9ZS50YXJnZXQ7aWYoIWkudmFsdWUpcmV0dXJuO2xldCByPU1hdGgubWluKE1hdGgubWF4KDAscGFyc2VGbG9hdChpLnZhbHVlKSksLjk5OSk7ciE9PXBhcnNlRmxvYXQoaS52YWx1ZSkmJihpLnZhbHVlPVN0cmluZyhyKSksdGhpcy5zY2FsYXJTbW9vdGhpbmdDb250cm9sQ2hhbmdlZCQuZW1pdChyKX1mb3JtYXRNaWxsaVRvWmVyb3RoKGUpe3JldHVybiB1NShlLzFlMyx0aGlzLmxvY2FsZXx8ImVuLVVTIiwiMS4wLTIiKX1nZXRMaW5rZWRUaW1lU2VsZWN0aW9uU3RhcnRTdGVwKCl7cmV0dXJuIHRoaXMuaXNMaW5rZWRUaW1lRW5hYmxlZHx8bnVsbD09PXRoaXMubGlua2VkVGltZVNlbGVjdGlvbnx8bnVsbCE9PXRoaXMubGlua2VkVGltZVNlbGVjdGlvbi5lbmQ/IiI6dGhpcy5saW5rZWRUaW1lU2VsZWN0aW9uLnN0YXJ0LnN0ZXB9aXNBeGlzVHlwZVN0ZXAoKXtyZXR1cm4gdGhpcy54QXhpc1R5cGU9PT1KaS5TVEVQfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFdkKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1kYXNoYm9hcmQtc2V0dGluZ3MtY29tcG9uZW50Il1dLGlucHV0czp7aXNMaW5rZWRUaW1lRmVhdHVyZUVuYWJsZWQ6ImlzTGlua2VkVGltZUZlYXR1cmVFbmFibGVkIixpc1JhbmdlU2VsZWN0aW9uQWxsb3dlZDoiaXNSYW5nZVNlbGVjdGlvbkFsbG93ZWQiLGlzTGlua2VkVGltZUVuYWJsZWQ6ImlzTGlua2VkVGltZUVuYWJsZWQiLGlzU2NhbGFyU3RlcFNlbGVjdG9yRmVhdHVyZUVuYWJsZWQ6ImlzU2NhbGFyU3RlcFNlbGVjdG9yRmVhdHVyZUVuYWJsZWQiLGlzU2NhbGFyU3RlcFNlbGVjdG9yRW5hYmxlZDoiaXNTY2FsYXJTdGVwU2VsZWN0b3JFbmFibGVkIixpc1NjYWxhclN0ZXBTZWxlY3RvclJhbmdlRW5hYmxlZDoiaXNTY2FsYXJTdGVwU2VsZWN0b3JSYW5nZUVuYWJsZWQiLGlzU2NhbGFyQ29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQ6ImlzU2NhbGFyQ29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQiLGxpbmtlZFRpbWVTZWxlY3Rpb246ImxpbmtlZFRpbWVTZWxlY3Rpb24iLHN0ZXBNaW5NYXg6InN0ZXBNaW5NYXgiLGlzU2xpZGVPdXRNZW51T3BlbjoiaXNTbGlkZU91dE1lbnVPcGVuIixpc0ltYWdlU3VwcG9ydEVuYWJsZWQ6ImlzSW1hZ2VTdXBwb3J0RW5hYmxlZCIsdG9vbHRpcFNvcnQ6InRvb2x0aXBTb3J0IixpZ25vcmVPdXRsaWVyczoiaWdub3JlT3V0bGllcnMiLHhBeGlzVHlwZToieEF4aXNUeXBlIixjYXJkTWluV2lkdGg6ImNhcmRNaW5XaWR0aCIsaGlzdG9ncmFtTW9kZToiaGlzdG9ncmFtTW9kZSIsc2NhbGFyU21vb3RoaW5nOiJzY2FsYXJTbW9vdGhpbmciLHNjYWxhclBhcnRpdGlvblg6InNjYWxhclBhcnRpdGlvblgiLGltYWdlQnJpZ2h0bmVzc0luTWlsbGk6ImltYWdlQnJpZ2h0bmVzc0luTWlsbGkiLGltYWdlQ29udHJhc3RJbk1pbGxpOiJpbWFnZUNvbnRyYXN0SW5NaWxsaSIsaW1hZ2VTaG93QWN0dWFsU2l6ZToiaW1hZ2VTaG93QWN0dWFsU2l6ZSJ9LG91dHB1dHM6e2xpbmtlZFRpbWVUb2dnbGVkOiJsaW5rZWRUaW1lVG9nZ2xlZCIsbGlua2VkVGltZVNlbGVjdGlvbkNoYW5nZWQ6ImxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkIixzdGVwU2VsZWN0b3JUb2dnbGVkOiJzdGVwU2VsZWN0b3JUb2dnbGVkIixyYW5nZVNlbGVjdGlvblRvZ2dsZWQ6InJhbmdlU2VsZWN0aW9uVG9nZ2xlZCIsb25TbGlkZU91dFRvZ2dsZWQ6Im9uU2xpZGVPdXRUb2dnbGVkIix0b29sdGlwU29ydENoYW5nZWQ6InRvb2x0aXBTb3J0Q2hhbmdlZCIsaWdub3JlT3V0bGllcnNDaGFuZ2VkOiJpZ25vcmVPdXRsaWVyc0NoYW5nZWQiLHhBeGlzVHlwZUNoYW5nZWQ6InhBeGlzVHlwZUNoYW5nZWQiLGNhcmRXaWR0aENoYW5nZWQ6ImNhcmRXaWR0aENoYW5nZWQiLGNhcmRXaWR0aFJlc2V0OiJjYXJkV2lkdGhSZXNldCIsaGlzdG9ncmFtTW9kZUNoYW5nZWQ6Imhpc3RvZ3JhbU1vZGVDaGFuZ2VkIixzY2FsYXJTbW9vdGhpbmdDaGFuZ2VkOiJzY2FsYXJTbW9vdGhpbmdDaGFuZ2VkIixzY2FsYXJQYXJ0aXRpb25YVG9nZ2xlZDoic2NhbGFyUGFydGl0aW9uWFRvZ2dsZWQiLGltYWdlQnJpZ2h0bmVzc0luTWlsbGlDaGFuZ2VkOiJpbWFnZUJyaWdodG5lc3NJbk1pbGxpQ2hhbmdlZCIsaW1hZ2VCcmlnaHRuZXNzUmVzZXQ6ImltYWdlQnJpZ2h0bmVzc1Jlc2V0IixpbWFnZUNvbnRyYXN0SW5NaWxsaUNoYW5nZWQ6ImltYWdlQ29udHJhc3RJbk1pbGxpQ2hhbmdlZCIsaW1hZ2VDb250cmFzdFJlc2V0OiJpbWFnZUNvbnRyYXN0UmVzZXQiLGltYWdlU2hvd0FjdHVhbFNpemVDaGFuZ2VkOiJpbWFnZVNob3dBY3R1YWxTaXplQ2hhbmdlZCJ9LGRlY2xzOjQzLHZhcnM6MjIsY29uc3RzOmZ1bmN0aW9uKCl7bGV0IHQsZSxpO3JldHVybiB0PSRsb2NhbGl6ZWA6QSBidXR0b24gdG8gcmVzZXQgdGhlIGNhcmQgd2lkdGggc2V0dGluZ+KQn2NjZGM5NmIwMDNmYmJhOTBkYjdhNjk1OWI1YjI2ZTNjYzU4ZjdkODDikJ81MjIzMTExMDQ3OTY4MTAyNDY2OlJlc2V0IGNhcmQgd2lkdGhgLGU9JGxvY2FsaXplYDpBIGJ1dHRvbiB0byByZXNldCB0aGUgaW1hZ2UgYnJpZ2h0bmVzcyBzZXR0aW5n4pCfYzQ4MmIzYTQ3ZWEwOTc1ZmE4YmUwMWFmYjNmYmVjOWI3NjYyOGJkN+KQnzExODkxNjE4NTcyNDAzNzgzOTU6UmVzZXQgYnJpZ2h0bmVzc2AsaT0kbG9jYWxpemVgOkEgYnV0dG9uIHRvIHJlc2V0IHRoZSBpbWFnZSBjb250cmFzdCBzZXR0aW5n4pCfZWQ3MTJhOGI5MjcwNDFiZTE1MjUyYjI5ZWI1MjFlYmIxMzc0YmFkOOKQnzUzNzA3MDMzNDI5MjM2MTE5NTU6UmVzZXQgY29udHJhc3RgLFtbMSwiZ2VuZXJhbCJdLFsxLCJzZWN0aW9uLXRpdGxlIl0sWzEsImNvbnRyb2wtcm93IiwieC1heGlzLXR5cGUiXSxbImlkIiwieC1heGlzLXR5cGUtbGFiZWwiLDEsImNvbnRyb2wtbmFtZSJdLFszLCJ2YWx1ZSIsIm9wdGlvbnMiLCJzZWxlY3Rpb25DaGFuZ2UiXSxbImNsYXNzIiwiY29udHJvbC1yb3cgc2NhbGFycy1zdGVwLXNlbGVjdG9yIiwzLCJ0aXRsZSIsNCwibmdJZiJdLFsxLCJjb250cm9sLXJvdyIsImNhcmQtd2lkdGgiXSxbImlkIiwiY2FyZC13aWR0aC1sYWJlbCIsMSwiY29udHJvbC1uYW1lIl0sWzEsInNsaWRlci1yb3ciXSxbImFyaWEtbGFiZWxsZWRieSIsImNhcmQtd2lkdGgtbGFiZWwiLCJjb2xvciIsInByaW1hcnkiLDMsIm1heCIsIm1pbiIsInN0ZXAiLCJ2YWx1ZSIsInRodW1iTGFiZWwiLCJpbnB1dCJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsdCwidGl0bGUiLCJSZXNldCBjYXJkIHdpZHRoIiwxLCJyZXNldC1idXR0b24iLDMsImNsaWNrIl0sWyJzdmdJY29uIiwic2V0dGluZ3NfYmFja3VwX3Jlc3RvcmVfMjRweCJdLFsxLCJzY2FsYXJzIl0sWzEsImNvbnRyb2wtcm93Iiwic2NhbGFycy1zbW9vdGhpbmciXSxbImlkIiwic2NhbGFycy1zbW9vdGhpbmctbGFiZWwiLDEsImNvbnRyb2wtbmFtZSJdLFsiYXJpYS1sYWJlbGxlZGJ5Iiwic2NhbGFycy1zbW9vdGhpbmctbGFiZWwiLCJjb2xvciIsInByaW1hcnkiLDMsIm1heCIsIm1pbiIsInN0ZXAiLCJ2YWx1ZSIsInRodW1iTGFiZWwiLCJpbnB1dCJdLFsiYXJpYS1sYWJlbGxlZGJ5Iiwic2NhbGFycy1zbW9vdGhpbmctbGFiZWwiLCJ0eXBlIiwibnVtYmVyIiwibWluIiwiMCIsInN0ZXAiLCIwLjAwMSIsMSwic2xpZGVyLWlucHV0IiwzLCJtYXgiLCJ2YWx1ZSIsImlucHV0Il0sWzEsImNvbnRyb2wtcm93IiwidG9vbHRpcC1zb3J0Il0sWzEsImNvbnRyb2wtbmFtZSJdLFsxLCJjb250cm9sLXJvdyIsInNjYWxhcnMtaWdub3JlLW91dGxpZXJzIl0sWzMsImNoZWNrZWQiLCJjaGFuZ2UiXSxbMSwiY29udHJvbC1yb3ciLCJzY2FsYXJzLXBhcnRpdGlvbi14Il0sWyJzdmdJY29uIiwiaGVscF9vdXRsaW5lXzI0cHgiLCJ0aXRsZSIsIk5vbi1tb25vdG9uaWMgc3RlcHMgY2FuIG9jY3VyIHdoZW4gcmV1c2luZyBhIGxvZ2RpciB3aXRoIG11bHRpcGxlIHN1bW1hcnkgd3JpdGVycyBhbmQgb3ZlcmxhcHBpbmcgc3RlcHMuIExpbmUgY2hhcnRzLCB3aXRob3V0IHRoaXMgb3B0aW9uIGVuYWJsZWQsIGNhbiBhcHBlYXIgemlnIHphZ2dlZC4gVGhpcyBpcyBjb21tb24gd2hlbiByZXN0YXJ0aW5nIGZyb20gYSBjaGVja3BvaW50LlxuXG5XaGVuIGVuYWJsZWQsIGEgbm9uLW1vbm90b25pYyB0aW1lIHNlcmllcyBjb21wb3NlZCBvZiBOIG1vbm90b25pYyBwaWVjZXMgd2lsbCBiZSBzaG93biBhcyBOIG1vbm90b25pYyBsaW5lcy4iLDEsImluZm8iXSxbMSwiSGlzdG9ncmFtcyJdLFsxLCJjb250cm9sLXJvdyIsImhpc3RvZ3JhbS1tb2RlIl0sWyJjbGFzcyIsImltYWdlIiw0LCJuZ0lmIl0sWzEsImNvbnRyb2wtcm93Iiwic2NhbGFycy1zdGVwLXNlbGVjdG9yIiwzLCJ0aXRsZSJdLFszLCJjaGVja2VkIiwiZGlzYWJsZWQiLCJjaGFuZ2UiXSxbMSwiaW5kZW50Il0sWyJjbGFzcyIsImluZGVudCByYW5nZS1zZWxlY3Rpb24iLDQsIm5nSWYiXSxbImNsYXNzIiwiY29udHJvbC1yb3cgbGlua2VkLXRpbWUgaW5kZW50Iiw0LCJuZ0lmIl0sWyJjbGFzcyIsImNvbHVtbi1lZGl0LW1lbnUtdG9nZ2xlIiwzLCJjbGljayIsNCwibmdJZiJdLFsxLCJpbmRlbnQiLCJyYW5nZS1zZWxlY3Rpb24iXSxbMSwiY29udHJvbC1yb3ciLCJsaW5rZWQtdGltZSIsImluZGVudCJdLFsxLCJjb2x1bW4tZWRpdC1tZW51LXRvZ2dsZSIsMywiY2xpY2siXSxbInN2Z0ljb24iLCJjaGV2cm9uX2xlZnRfMjRweCIsNCwibmdJZiJdLFsic3ZnSWNvbiIsImNoZXZyb25fcmlnaHRfMjRweCIsNCwibmdJZiJdLFsic3ZnSWNvbiIsImNoZXZyb25fbGVmdF8yNHB4Il0sWyJzdmdJY29uIiwiY2hldnJvbl9yaWdodF8yNHB4Il0sWzEsImltYWdlIl0sWzEsImNvbnRyb2wtcm93IiwiaW1hZ2UtYnJpZ2h0bmVzcyJdLFsiaWQiLCJpbWFnZS1icmlnaHRuZXNzLWxhYmVsIiwxLCJjb250cm9sLW5hbWUiXSxbImFyaWEtbGFiZWxsZWRieSIsImltYWdlLWJyaWdodG5lc3MtbGFiZWwiLCJjb2xvciIsInByaW1hcnkiLDMsIm1heCIsIm1pbiIsInN0ZXAiLCJ2YWx1ZSIsInRodW1iTGFiZWwiLCJkaXNwbGF5V2l0aCIsImlucHV0Il0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJhcmlhLWxhYmVsIixlLCJ0aXRsZSIsIlJlc2V0IGJyaWdodG5lc3MiLDEsInJlc2V0LWJ1dHRvbiIsMywiY2xpY2siXSxbMSwiY29udHJvbC1yb3ciLCJpbWFnZS1jb250cmFzdCJdLFsiaWQiLCJpbWFnZS1jb25zdHJhc3QtbGFiZWwiLDEsImNvbnRyb2wtbmFtZSJdLFsiYXJpYS1sYWJlbGxlZGJ5IiwiaW1hZ2UtY29uc3RyYXN0LWxhYmVsIiwiY29sb3IiLCJwcmltYXJ5IiwzLCJtYXgiLCJtaW4iLCJzdGVwIiwidmFsdWUiLCJ0aHVtYkxhYmVsIiwiZGlzcGxheVdpdGgiLCJpbnB1dCJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsaSwidGl0bGUiLCJSZXNldCBjb250cmFzdCIsMSwicmVzZXQtYnV0dG9uIiwzLCJjbGljayJdLFsxLCJjb250cm9sLXJvdyIsImltYWdlLXNob3ctYWN0dWFsLXNpemUiXV19LHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJzZWN0aW9uIiwwKSgxLCJoMyIsMSksQSgyLCJHZW5lcmFsIiksdigpLF8oMywiZGl2IiwyKSg0LCJkaXYiLDMpLEEoNSwiSG9yaXpvbnRhbCBBeGlzIiksdigpLF8oNiwidGItZHJvcGRvd24iLDQpLFAoInNlbGVjdGlvbkNoYW5nZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkueEF4aXNUeXBlQ2hhbmdlZC5lbWl0KG8pfSksdigpKCksRSg3LHg1ZSw4LDYsImRpdiIsNSksXyg4LCJkaXYiLDYpKDksImRpdiIsNyksQSgxMCwiQ2FyZCBXaWR0aCIpLHYoKSxfKDExLCJkaXYiLDgpKDEyLCJtYXQtc2xpZGVyIiw5KSxQKCJpbnB1dCIsZnVuY3Rpb24obyl7cmV0dXJuIGkuY2FyZFdpZHRoU2xpZGVyQ2hhbmdlZCQuZW1pdChvLnZhbHVlKX0pLHYoKSxfKDEzLCJidXR0b24iLDEwKSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5jYXJkV2lkdGhSZXNldC5lbWl0KCl9KSxPKDE0LCJtYXQtaWNvbiIsMTEpLHYoKSgpKCkoKSxfKDE1LCJzZWN0aW9uIiwxMikoMTYsImgzIiwxKSxBKDE3LCJTY2FsYXJzIiksdigpLF8oMTgsImRpdiIsMTMpKDE5LCJkaXYiLDE0KSxBKDIwLCJTbW9vdGhpbmciKSx2KCksXygyMSwiZGl2Iiw4KSgyMiwibWF0LXNsaWRlciIsMTUpLFAoImlucHV0IixmdW5jdGlvbihvKXtyZXR1cm4gaS5zY2FsYXJTbW9vdGhpbmdDb250cm9sQ2hhbmdlZCQuZW1pdChvLnZhbHVlKX0pLHYoKSxfKDIzLCJpbnB1dCIsMTYpLFAoImlucHV0IixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblNjYWxhclNtb290aGluZ0lucHV0KG8pfSksdigpKCkoKSxfKDI0LCJkaXYiLDE3KSgyNSwiZGl2IiwxOCksQSgyNiwiVG9vbHRpcCBzb3J0aW5nIG1ldGhvZCIpLHYoKSxfKDI3LCJ0Yi1kcm9wZG93biIsNCksUCgic2VsZWN0aW9uQ2hhbmdlIixmdW5jdGlvbihvKXtyZXR1cm4gaS50b29sdGlwU29ydENoYW5nZWQuZW1pdChvKX0pLHYoKSgpLF8oMjgsImRpdiIsMTkpKDI5LCJtYXQtY2hlY2tib3giLDIwKSxQKCJjaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLmlnbm9yZU91dGxpZXJzQ2hhbmdlZC5lbWl0KG8uY2hlY2tlZCl9KSxBKDMwLCJJZ25vcmUgb3V0bGllcnMgaW4gY2hhcnQgc2NhbGluZyIpLHYoKSgpLF8oMzEsImRpdiIsMjEpKDMyLCJtYXQtY2hlY2tib3giLDIwKSxQKCJjaGFuZ2UiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuc2NhbGFyUGFydGl0aW9uWFRvZ2dsZWQuZW1pdCgpfSksQSgzMywiUGFydGl0aW9uIG5vbi1tb25vdG9uaWMgWCBheGlzIiksdigpLE8oMzQsIm1hdC1pY29uIiwyMiksdigpKCksXygzNSwic2VjdGlvbiIsMjMpKDM2LCJoMyIsMSksQSgzNywiSGlzdG9ncmFtcyIpLHYoKSxfKDM4LCJkaXYiLDI0KSgzOSwiZGl2IiwxOCksQSg0MCwiTW9kZSIpLHYoKSxfKDQxLCJ0Yi1kcm9wZG93biIsNCksUCgic2VsZWN0aW9uQ2hhbmdlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5oaXN0b2dyYW1Nb2RlQ2hhbmdlZC5lbWl0KG8pfSksdigpKCkoKSxFKDQyLEM1ZSwyMCwxMywic2VjdGlvbiIsMjUpKSwyJmUmJihDKDYpLHkoInZhbHVlIixpLnhBeGlzVHlwZSkoIm9wdGlvbnMiLGkuWEF4aXNUeXBlRHJvcGRvd25PcHRpb25zKSxDKDEpLHkoIm5nSWYiLGkuaXNTY2FsYXJTdGVwU2VsZWN0b3JGZWF0dXJlRW5hYmxlZCksQyg1KSx5KCJtYXgiLGkuTUFYX0NBUkRfV0lEVEhfU0xJREVSX1ZBTFVFKSgibWluIixpLk1JTl9DQVJEX1dJRFRIX1NMSURFUl9WQUxVRSkoInN0ZXAiLDUwKSgidmFsdWUiLGkuY2FyZE1pbldpZHRoKSgidGh1bWJMYWJlbCIsITEpLEMoMTApLHkoIm1heCIsaS5NQVhfU01PT1RISU5HX1NMSURFUl9WQUxVRSkoIm1pbiIsMCkoInN0ZXAiLC4wMSkoInZhbHVlIixpLnNjYWxhclNtb290aGluZykoInRodW1iTGFiZWwiLCEwKSxDKDEpLHkoIm1heCIsaS5NQVhfU01PT1RISU5HX1ZBTFVFKSgidmFsdWUiLGkuc2NhbGFyU21vb3RoaW5nKSxDKDQpLHkoInZhbHVlIixpLnRvb2x0aXBTb3J0KSgib3B0aW9ucyIsaS5Ub29sdGlwU29ydERyb3Bkb3duT3B0aW9ucyksQygyKSx5KCJjaGVja2VkIixpLmlnbm9yZU91dGxpZXJzKSxDKDMpLHkoImNoZWNrZWQiLGkuc2NhbGFyUGFydGl0aW9uWCksQyg5KSx5KCJ2YWx1ZSIsaS5oaXN0b2dyYW1Nb2RlKSgib3B0aW9ucyIsaS5IaXN0b2dyYW1Nb2RlRHJvcGRvd25PcHRpb25zKSxDKDEpLHkoIm5nSWYiLGkuaXNJbWFnZVN1cHBvcnRFbmFibGVkKSl9LGRlcGVuZGVuY2llczpbQmUsTG9lLF9uLHlsLEd0LHVwXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2NvbG9yOiM2MTYxNjE7Zm9udC1zaXplOjEycHh9Ym9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfXNlY3Rpb25bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7cGFkZGluZzoxNnB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIHNlY3Rpb25bX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICBzZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjNTU1fS5zZWN0aW9uLXRpdGxlW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjojMjEyMTIxO3RleHQtdHJhbnNmb3JtOnVwcGVyY2FzZTtmb250LXdlaWdodDo1MDA7Zm9udC1zaXplOjEzcHg7bGluZS1oZWlnaHQ6bm9ybWFsO21hcmdpbjowIDAgMTJweCAwfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5zZWN0aW9uLXRpdGxlW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnNlY3Rpb24tdGl0bGVbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiNmZmZ9c2VjdGlvbltfbmdjb250ZW50LSVDT01QJV0gICAuY29udHJvbC1yb3dbX25nY29udGVudC0lQ09NUCVdOm5vdCg6bGFzdC1jaGlsZCl7bWFyZ2luLWJvdHRvbToxMnB4fS5jb250cm9sLW5hbWVbX25nY29udGVudC0lQ09NUCVde21hcmdpbi1ib3R0b206OHB4fS5zbGlkZXItcm93W19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmZsZXg7YWxpZ24taXRlbXM6Y2VudGVyO2hlaWdodDoyOHB4fS5zbGlkZXItcm93W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5yZXNldC1idXR0b25bX25nY29udGVudC0lQ09NUCVde21hcmdpbi1sZWZ0OjZweH0uc2xpZGVyLXJvd1tfbmdjb250ZW50LSVDT01QJV0gICAuc2xpZGVyLWlucHV0W19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOmluaGVyaXQ7Ym9yZGVyOjFweCBzb2xpZCAjOGU5OGEzO2JvcmRlci1yYWRpdXM6MnB4O2JveC1zaXppbmc6Ym9yZGVyLWJveDtjb2xvcjppbmhlcml0O2hlaWdodDoxMDAlO21hcmdpbi1sZWZ0OjEycHg7cGFkZGluZzowIDRweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuc2xpZGVyLXJvd1tfbmdjb250ZW50LSVDT01QJV0gICAuc2xpZGVyLWlucHV0W19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnNsaWRlci1yb3dbX25nY29udGVudC0lQ09NUCVdICAgLnNsaWRlci1pbnB1dFtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWNvbG9yOiM0MjUwNjZ9LnNjYWxhcnMtc21vb3RoaW5nW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5zbGlkZXItaW5wdXRbX25nY29udGVudC0lQ09NUCVde2ZsZXg6bm9uZTt3aWR0aDo1ZW19LnNjYWxhcnMtcGFydGl0aW9uLXhbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtkaXNwbGF5OmZsZXh9LnNjYWxhcnMtcGFydGl0aW9uLXhbX25nY29udGVudC0lQ09NUCVdICAgLmluZm9bX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxNXB4O21hcmdpbi1sZWZ0OjVweDt3aWR0aDoxNXB4fW1hdC1zbGlkZXJbX25nY29udGVudC0lQ09NUCVde2ZsZXg6MTttYXJnaW4tbGVmdDotOHB4O21hcmdpbi1yaWdodDotOHB4fS5jb2x1bW4tZWRpdC1tZW51LXRvZ2dsZVtfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleDtjdXJzb3I6cG9pbnRlcn0uY29sdW1uLWVkaXQtbWVudS10b2dnbGVbX25nY29udGVudC0lQ09NUCVdICAgbWF0LWljb25bX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxNXB4O3dpZHRoOjE1cHh9dGItZHJvcGRvd25bX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6YmxvY2t9LmxpbmtlZC10aW1lW19uZ2NvbnRlbnQtJUNPTVAlXXtwYWRkaW5nOjVweCAwfS5jb250cm9sLXJvd1tfbmdjb250ZW50LSVDT01QJV0gICAuaW5kZW50W19uZ2NvbnRlbnQtJUNPTVAlXXttYXJnaW4tbGVmdDoyNXB4fSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksVDVlPU9iamVjdC5mcmVlemUoe1NMSURFUjpjcy5TRVRUSU5HU19TTElERVIsVEVYVDpjcy5TRVRUSU5HU19URVhULFRFWFRfREVMRVRFRDpjcy5DSEFOR0VfVE9fU0lOR0xFfSksSG9lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuaXNMaW5rZWRUaW1lRmVhdHVyZUVuYWJsZWQkPXRoaXMuc3RvcmUuc2VsZWN0KE0kKSx0aGlzLmlzUmFuZ2VTZWxlY3Rpb25BbGxvd2VkJD10aGlzLnN0b3JlLnNlbGVjdChUJCksdGhpcy5pc1NjYWxhclN0ZXBTZWxlY3RvckZlYXR1cmVFbmFibGVkJD10aGlzLnN0b3JlLnNlbGVjdChTJCksdGhpcy5pc1NjYWxhclN0ZXBTZWxlY3RvckVuYWJsZWQkPXRoaXMuc3RvcmUuc2VsZWN0KGZ2KSx0aGlzLmlzU2NhbGFyU3RlcFNlbGVjdG9yUmFuZ2VFbmFibGVkJD10aGlzLnN0b3JlLnNlbGVjdChtdiksdGhpcy5pc0xpbmtlZFRpbWVFbmFibGVkJD10aGlzLnN0b3JlLnNlbGVjdChZbSksdGhpcy5pc1NjYWxhckNvbHVtbkN1c3RvbWl6YXRpb25FbmFibGVkJD10aGlzLnN0b3JlLnNlbGVjdChxQSksdGhpcy5saW5rZWRUaW1lU2VsZWN0aW9uJD10aGlzLnN0b3JlLnNlbGVjdChpSCksdGhpcy5zdGVwTWluTWF4JD10aGlzLnN0b3JlLnNlbGVjdChuSCksdGhpcy5pc1NsaWRlT3V0TWVudU9wZW4kPXRoaXMuc3RvcmUuc2VsZWN0KFVJKSx0aGlzLmlzSW1hZ2VTdXBwb3J0RW5hYmxlZCQ9dGhpcy5zdG9yZS5zZWxlY3QoZ2gpLnBpcGUoWWUoQm9vbGVhbiksUXQoMSksV3QodGhpcy5zdG9yZS5zZWxlY3QoV0EpKSxMKChbLGldKT0+aSkpLHRoaXMudG9vbHRpcFNvcnQkPXRoaXMuc3RvcmUuc2VsZWN0KHB2KSx0aGlzLmlnbm9yZU91dGxpZXJzJD10aGlzLnN0b3JlLnNlbGVjdChodiksdGhpcy54QXhpc1R5cGUkPXRoaXMuc3RvcmUuc2VsZWN0KHRkKSx0aGlzLmNhcmRNaW5XaWR0aCQ9dGhpcy5zdG9yZS5zZWxlY3QoZHYpLHRoaXMuaGlzdG9ncmFtTW9kZSQ9dGhpcy5zdG9yZS5zZWxlY3QoUkkpLHRoaXMuc2NhbGFyU21vb3RoaW5nJD10aGlzLnN0b3JlLnNlbGVjdChvcCksdGhpcy5zY2FsYXJQYXJ0aXRpb25YJD10aGlzLnN0b3JlLnNlbGVjdChPSSksdGhpcy5pbWFnZUJyaWdodG5lc3NJbk1pbGxpJD10aGlzLnN0b3JlLnNlbGVjdChrSSksdGhpcy5pbWFnZUNvbnRyYXN0SW5NaWxsaSQ9dGhpcy5zdG9yZS5zZWxlY3QoRkkpLHRoaXMuaW1hZ2VTaG93QWN0dWFsU2l6ZSQ9dGhpcy5zdG9yZS5zZWxlY3QoTkkpfW9uVG9vbHRpcFNvcnRDaGFuZ2VkKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goalAoe3NvcnQ6ZX0pKX1vbklnbm9yZU91dGxpZXJzQ2hhbmdlZCgpe3RoaXMuc3RvcmUuZGlzcGF0Y2goR1AoKSl9b25YQXhpc1R5cGVDaGFuZ2VkKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goV1Aoe3hBeGlzVHlwZTplfSkpfW9uQ2FyZFdpZHRoQ2hhbmdlZChlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKHFQKHtjYXJkTWluV2lkdGg6ZX0pKX1vbkNhcmRXaWR0aFJlc2V0KCl7dGhpcy5zdG9yZS5kaXNwYXRjaChZUCgpKX1vbkhpc3RvZ3JhbU1vZGVDaGFuZ2VkKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2godFIoe2hpc3RvZ3JhbU1vZGU6ZX0pKX1vblNjYWxhclNtb290aGluZ0NoYW5nZWQoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChYUCh7c21vb3RoaW5nOmV9KSl9b25TY2FsYXJQYXJ0aXRpb25YVG9nZ2xlZCgpe3RoaXMuc3RvcmUuZGlzcGF0Y2goUVAoKSl9b25JbWFnZUJyaWdodG5lc3NJbk1pbGxpQ2hhbmdlZChlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKEtQKHticmlnaHRuZXNzSW5NaWxsaTplfSkpfW9uSW1hZ2VCcmlnaHRuZXNzUmVzZXQoKXt0aGlzLnN0b3JlLmRpc3BhdGNoKEpQKCkpfW9uSW1hZ2VDb250cmFzdFJlc2V0KCl7dGhpcy5zdG9yZS5kaXNwYXRjaCgkUCgpKX1vbkltYWdlQ29udHJhc3RJbk1pbGxpQ2hhbmdlZChlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKFpQKHtjb250cmFzdEluTWlsbGk6ZX0pKX1vbkltYWdlU2hvd0FjdHVhbFNpemVDaGFuZ2VkKCl7dGhpcy5zdG9yZS5kaXNwYXRjaChlUigpKX1vbkxpbmtlZFRpbWVUb2dnbGVkKCl7dGhpcy5zdG9yZS5kaXNwYXRjaCh1Uih7YWZmb3JkYW5jZTpibC5DSEVDS19CT1h9KSl9b25TdGVwU2VsZWN0b3JUb2dnbGVkKCl7dGhpcy5zdG9yZS5kaXNwYXRjaChYaCh7YWZmb3JkYW5jZTpibC5DSEVDS19CT1h9KSl9b25SYW5nZVNlbGVjdGlvblRvZ2dsZWQoKXt0aGlzLnN0b3JlLmRpc3BhdGNoKGhSKHthZmZvcmRhbmNlOmJsLkNIRUNLX0JPWH0pKX1vbkxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkKHt0aW1lU2VsZWN0aW9uOmUsc291cmNlOml9KXt0aGlzLnN0b3JlLmRpc3BhdGNoKFloKHt0aW1lU2VsZWN0aW9uOmUsYWZmb3JkYW5jZTpUNWVbaV19KSl9b25TbGlkZU91dFRvZ2dsZWQoKXt0aGlzLnN0b3JlLmRpc3BhdGNoKFZQKCkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1kYXNoYm9hcmQtc2V0dGluZ3MiXV0sZGVjbHM6MjIsdmFyczo2Myxjb25zdHM6W1szLCJpc0ltYWdlU3VwcG9ydEVuYWJsZWQiLCJ0b29sdGlwU29ydCIsImlnbm9yZU91dGxpZXJzIiwieEF4aXNUeXBlIiwiY2FyZE1pbldpZHRoIiwiaGlzdG9ncmFtTW9kZSIsInNjYWxhclNtb290aGluZyIsInNjYWxhclBhcnRpdGlvblgiLCJpbWFnZUJyaWdodG5lc3NJbk1pbGxpIiwiaW1hZ2VDb250cmFzdEluTWlsbGkiLCJpbWFnZVNob3dBY3R1YWxTaXplIiwiaXNMaW5rZWRUaW1lRmVhdHVyZUVuYWJsZWQiLCJpc1JhbmdlU2VsZWN0aW9uQWxsb3dlZCIsImlzU2NhbGFyU3RlcFNlbGVjdG9yRmVhdHVyZUVuYWJsZWQiLCJpc1NjYWxhclN0ZXBTZWxlY3RvckVuYWJsZWQiLCJpc1NjYWxhclN0ZXBTZWxlY3RvclJhbmdlRW5hYmxlZCIsImlzTGlua2VkVGltZUVuYWJsZWQiLCJpc1NjYWxhckNvbHVtbkN1c3RvbWl6YXRpb25FbmFibGVkIiwibGlua2VkVGltZVNlbGVjdGlvbiIsInN0ZXBNaW5NYXgiLCJpc1NsaWRlT3V0TWVudU9wZW4iLCJ0b29sdGlwU29ydENoYW5nZWQiLCJpZ25vcmVPdXRsaWVyc0NoYW5nZWQiLCJ4QXhpc1R5cGVDaGFuZ2VkIiwiY2FyZFdpZHRoQ2hhbmdlZCIsImNhcmRXaWR0aFJlc2V0IiwiaGlzdG9ncmFtTW9kZUNoYW5nZWQiLCJzY2FsYXJTbW9vdGhpbmdDaGFuZ2VkIiwic2NhbGFyUGFydGl0aW9uWFRvZ2dsZWQiLCJpbWFnZUJyaWdodG5lc3NJbk1pbGxpQ2hhbmdlZCIsImltYWdlQnJpZ2h0bmVzc1Jlc2V0IiwiaW1hZ2VDb250cmFzdEluTWlsbGlDaGFuZ2VkIiwiaW1hZ2VDb250cmFzdFJlc2V0IiwiaW1hZ2VTaG93QWN0dWFsU2l6ZUNoYW5nZWQiLCJsaW5rZWRUaW1lVG9nZ2xlZCIsImxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkIiwic3RlcFNlbGVjdG9yVG9nZ2xlZCIsInJhbmdlU2VsZWN0aW9uVG9nZ2xlZCIsIm9uU2xpZGVPdXRUb2dnbGVkIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJtZXRyaWNzLWRhc2hib2FyZC1zZXR0aW5ncy1jb21wb25lbnQiLDApLFAoInRvb2x0aXBTb3J0Q2hhbmdlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25Ub29sdGlwU29ydENoYW5nZWQobyl9KSgiaWdub3JlT3V0bGllcnNDaGFuZ2VkIixmdW5jdGlvbigpe3JldHVybiBpLm9uSWdub3JlT3V0bGllcnNDaGFuZ2VkKCl9KSgieEF4aXNUeXBlQ2hhbmdlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25YQXhpc1R5cGVDaGFuZ2VkKG8pfSkoImNhcmRXaWR0aENoYW5nZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uQ2FyZFdpZHRoQ2hhbmdlZChvKX0pKCJjYXJkV2lkdGhSZXNldCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkNhcmRXaWR0aFJlc2V0KCl9KSgiaGlzdG9ncmFtTW9kZUNoYW5nZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uSGlzdG9ncmFtTW9kZUNoYW5nZWQobyl9KSgic2NhbGFyU21vb3RoaW5nQ2hhbmdlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25TY2FsYXJTbW9vdGhpbmdDaGFuZ2VkKG8pfSkoInNjYWxhclBhcnRpdGlvblhUb2dnbGVkIixmdW5jdGlvbigpe3JldHVybiBpLm9uU2NhbGFyUGFydGl0aW9uWFRvZ2dsZWQoKX0pKCJpbWFnZUJyaWdodG5lc3NJbk1pbGxpQ2hhbmdlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25JbWFnZUJyaWdodG5lc3NJbk1pbGxpQ2hhbmdlZChvKX0pKCJpbWFnZUJyaWdodG5lc3NSZXNldCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkltYWdlQnJpZ2h0bmVzc1Jlc2V0KCl9KSgiaW1hZ2VDb250cmFzdEluTWlsbGlDaGFuZ2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vbkltYWdlQ29udHJhc3RJbk1pbGxpQ2hhbmdlZChvKX0pKCJpbWFnZUNvbnRyYXN0UmVzZXQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25JbWFnZUNvbnRyYXN0UmVzZXQoKX0pKCJpbWFnZVNob3dBY3R1YWxTaXplQ2hhbmdlZCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkltYWdlU2hvd0FjdHVhbFNpemVDaGFuZ2VkKCl9KSgibGlua2VkVGltZVRvZ2dsZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25MaW5rZWRUaW1lVG9nZ2xlZCgpfSkoImxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vbkxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkKG8pfSkoInN0ZXBTZWxlY3RvclRvZ2dsZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25TdGVwU2VsZWN0b3JUb2dnbGVkKCl9KSgicmFuZ2VTZWxlY3Rpb25Ub2dnbGVkIixmdW5jdGlvbigpe3JldHVybiBpLm9uUmFuZ2VTZWxlY3Rpb25Ub2dnbGVkKCl9KSgib25TbGlkZU91dFRvZ2dsZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25TbGlkZU91dFRvZ2dsZWQoKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLEIoNCwiYXN5bmMiKSxCKDUsImFzeW5jIiksQig2LCJhc3luYyIpLEIoNywiYXN5bmMiKSxCKDgsImFzeW5jIiksQig5LCJhc3luYyIpLEIoMTAsImFzeW5jIiksQigxMSwiYXN5bmMiKSxCKDEyLCJhc3luYyIpLEIoMTMsImFzeW5jIiksQigxNCwiYXN5bmMiKSxCKDE1LCJhc3luYyIpLEIoMTYsImFzeW5jIiksQigxNywiYXN5bmMiKSxCKDE4LCJhc3luYyIpLEIoMTksImFzeW5jIiksQigyMCwiYXN5bmMiKSxCKDIxLCJhc3luYyIpLHYoKSksMiZlJiZ5KCJpc0ltYWdlU3VwcG9ydEVuYWJsZWQiLFUoMSwyMSxpLmlzSW1hZ2VTdXBwb3J0RW5hYmxlZCQpKSgidG9vbHRpcFNvcnQiLFUoMiwyMyxpLnRvb2x0aXBTb3J0JCkpKCJpZ25vcmVPdXRsaWVycyIsVSgzLDI1LGkuaWdub3JlT3V0bGllcnMkKSkoInhBeGlzVHlwZSIsVSg0LDI3LGkueEF4aXNUeXBlJCkpKCJjYXJkTWluV2lkdGgiLFUoNSwyOSxpLmNhcmRNaW5XaWR0aCQpKSgiaGlzdG9ncmFtTW9kZSIsVSg2LDMxLGkuaGlzdG9ncmFtTW9kZSQpKSgic2NhbGFyU21vb3RoaW5nIixVKDcsMzMsaS5zY2FsYXJTbW9vdGhpbmckKSkoInNjYWxhclBhcnRpdGlvblgiLFUoOCwzNSxpLnNjYWxhclBhcnRpdGlvblgkKSkoImltYWdlQnJpZ2h0bmVzc0luTWlsbGkiLFUoOSwzNyxpLmltYWdlQnJpZ2h0bmVzc0luTWlsbGkkKSkoImltYWdlQ29udHJhc3RJbk1pbGxpIixVKDEwLDM5LGkuaW1hZ2VDb250cmFzdEluTWlsbGkkKSkoImltYWdlU2hvd0FjdHVhbFNpemUiLFUoMTEsNDEsaS5pbWFnZVNob3dBY3R1YWxTaXplJCkpKCJpc0xpbmtlZFRpbWVGZWF0dXJlRW5hYmxlZCIsVSgxMiw0MyxpLmlzTGlua2VkVGltZUZlYXR1cmVFbmFibGVkJCkpKCJpc1JhbmdlU2VsZWN0aW9uQWxsb3dlZCIsVSgxMyw0NSxpLmlzUmFuZ2VTZWxlY3Rpb25BbGxvd2VkJCkpKCJpc1NjYWxhclN0ZXBTZWxlY3RvckZlYXR1cmVFbmFibGVkIixVKDE0LDQ3LGkuaXNTY2FsYXJTdGVwU2VsZWN0b3JGZWF0dXJlRW5hYmxlZCQpKSgiaXNTY2FsYXJTdGVwU2VsZWN0b3JFbmFibGVkIixVKDE1LDQ5LGkuaXNTY2FsYXJTdGVwU2VsZWN0b3JFbmFibGVkJCkpKCJpc1NjYWxhclN0ZXBTZWxlY3RvclJhbmdlRW5hYmxlZCIsVSgxNiw1MSxpLmlzU2NhbGFyU3RlcFNlbGVjdG9yUmFuZ2VFbmFibGVkJCkpKCJpc0xpbmtlZFRpbWVFbmFibGVkIixVKDE3LDUzLGkuaXNMaW5rZWRUaW1lRW5hYmxlZCQpKSgiaXNTY2FsYXJDb2x1bW5DdXN0b21pemF0aW9uRW5hYmxlZCIsVSgxOCw1NSxpLmlzU2NhbGFyQ29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQkKSkoImxpbmtlZFRpbWVTZWxlY3Rpb24iLFUoMTksNTcsaS5saW5rZWRUaW1lU2VsZWN0aW9uJCkpKCJzdGVwTWluTWF4IixVKDIwLDU5LGkuc3RlcE1pbk1heCQpKSgiaXNTbGlkZU91dE1lbnVPcGVuIixVKDIxLDYxLGkuaXNTbGlkZU91dE1lbnVPcGVuJCkpfSxkZXBlbmRlbmNpZXM6W1ZvZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksVW9lPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1ldHJpY3MtZGFzaGJvYXJkLXJpZ2h0LXBhbmUiXV0sZGVjbHM6MSx2YXJzOjAsdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJk8oMCwibWV0cmljcy1kYXNoYm9hcmQtc2V0dGluZ3MiKX0sZGVwZW5kZW5jaWVzOltIb2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIEk1ZShuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsMyl9ZnVuY3Rpb24gUDVlKG4sdCl7MSZuJiZPKDAsIm1hdC1pY29uIiwzKX1mdW5jdGlvbiBSNWUobix0KXsxJm4mJk8oMCwiZGl2Iiw0KX12YXIgdlI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuQ29sdW1uSGVhZGVyVHlwZT1LdH1nZXRIZWFkZXJUZXh0Q29sdW1uKGUpe3N3aXRjaChlKXtjYXNlIEt0LlJVTjpyZXR1cm4iUnVuIjtjYXNlIEt0LlZBTFVFOnJldHVybiJWYWx1ZSI7Y2FzZSBLdC5TVEVQOnJldHVybiJTdGVwIjtjYXNlIEt0LlRJTUU6cmV0dXJuIlRpbWUiO2Nhc2UgS3QuUkVMQVRJVkVfVElNRTpyZXR1cm4iUmVsYXRpdmUiO2Nhc2UgS3QuU01PT1RIRUQ6cmV0dXJuIlNtb290aGVkIjtjYXNlIEt0LlZBTFVFX0NIQU5HRTpyZXR1cm4iVmFsdWUiO2Nhc2UgS3QuU1RBUlRfU1RFUDpyZXR1cm4iU3RhcnQgU3RlcCI7Y2FzZSBLdC5FTkRfU1RFUDpyZXR1cm4iRW5kIFN0ZXAiO2Nhc2UgS3QuU1RBUlRfVkFMVUU6cmV0dXJuIlN0YXJ0IFZhbHVlIjtjYXNlIEt0LkVORF9WQUxVRTpyZXR1cm4iRW5kIFZhbHVlIjtjYXNlIEt0Lk1JTl9WQUxVRTpyZXR1cm4iTWluIjtjYXNlIEt0Lk1BWF9WQUxVRTpyZXR1cm4iTWF4IjtjYXNlIEt0LlBFUkNFTlRBR0VfQ0hBTkdFOnJldHVybiIlIjtkZWZhdWx0OnJldHVybiIifX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGItZGF0YS10YWJsZS1oZWFkZXIiXV0saW5wdXRzOntoZWFkZXI6ImhlYWRlciJ9LGRlY2xzOjYsdmFyczo0LGNvbnN0czpbWzEsImhlYWRlci1jb250YWluZXIiLDMsIm5nU3dpdGNoIl0sWyJzdmdJY29uIiwiY2hhbmdlX2hpc3RvcnlfMjRweCIsNCwibmdTd2l0Y2hDYXNlIl0sWyJjbGFzcyIsImV4dHJhLXJpZ2h0LXBhZGRpbmciLDQsIm5nU3dpdGNoRGVmYXVsdCJdLFsic3ZnSWNvbiIsImNoYW5nZV9oaXN0b3J5XzI0cHgiXSxbMSwiZXh0cmEtcmlnaHQtcGFkZGluZyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IiwwKSxFKDEsSTVlLDEsMCwibWF0LWljb24iLDEpLEUoMixQNWUsMSwwLCJtYXQtaWNvbiIsMSksRSgzLFI1ZSwxLDAsImRpdiIsMiksXyg0LCJzcGFuIiksQSg1KSx2KCkoKSksMiZlJiYoeSgibmdTd2l0Y2giLGkuaGVhZGVyLnR5cGUpLEMoMSkseSgibmdTd2l0Y2hDYXNlIixpLkNvbHVtbkhlYWRlclR5cGUuVkFMVUVfQ0hBTkdFKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsaS5Db2x1bW5IZWFkZXJUeXBlLlBFUkNFTlRBR0VfQ0hBTkdFKSxDKDMpLHl0KGkuZ2V0SGVhZGVyVGV4dENvbHVtbihpLmhlYWRlci50eXBlKSkpfSxkZXBlbmRlbmNpZXM6W0NyLFVyLGNoLEd0XSxzdHlsZXM6WyIuaGVhZGVyLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleH0uZXh0cmEtcmlnaHQtcGFkZGluZ1tfbmdjb250ZW50LSVDT01QJV17cGFkZGluZy1yaWdodDoxcHh9bWF0LWljb25bX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxMnB4O3dpZHRoOjEycHh9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiBPNWUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDYpKDEsIm1hdC1jaGVja2JveCIsNyksUCgiY2hhbmdlIixmdW5jdGlvbigpe2xldCBvPW9lKGUpLiRpbXBsaWNpdCxzPVMoKS5kYXRhVGFibGVNb2RlO3JldHVybiBzZShTKCkudG9nZ2xlSGVhZGVyKG8scykpfSksTygyLCJ0Yi1kYXRhLXRhYmxlLWhlYWRlciIsOCksdigpKCl9aWYoMiZuKXtsZXQgZT10LiRpbXBsaWNpdDtDKDEpLHkoImNoZWNrZWQiLGUuZW5hYmxlZCksQygxKSx5KCJoZWFkZXIiLGUpfX1mdW5jdGlvbiBrNWUobix0KXtpZigxJm4mJihfKDAsImRpdiIsNCksRSgxLE81ZSwzLDIsImRpdiIsNSksdigpKSwyJm4pe2xldCBlPXQuaGVhZGVycztDKDEpLHkoIm5nRm9yT2YiLGUpfX12YXIgam9lPWZ1bmN0aW9uKG4sdCl7cmV0dXJue2hlYWRlcnM6bixkYXRhVGFibGVNb2RlOnR9fSxHb2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuRGF0YVRhYmxlTW9kZT1nZCx0aGlzLnNlbGVjdGVkVGFiPWdkLlNJTkdMRSx0aGlzLm9uU2NhbGFyVGFibGVDb2x1bW5Ub2dnbGVkPW5ldyBHfXRvZ2dsZUhlYWRlcihlLGkpe3RoaXMub25TY2FsYXJUYWJsZUNvbHVtblRvZ2dsZWQuZW1pdCh7ZGF0YVRhYmxlTW9kZTppLGhlYWRlclR5cGU6ZS50eXBlfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1ldHJpY3Mtc2NhbGFyLWNvbHVtbi1lZGl0b3ItY29tcG9uZW50Il1dLGlucHV0czp7cmFuZ2VIZWFkZXJzOiJyYW5nZUhlYWRlcnMiLHNpbmdsZUhlYWRlcnM6InNpbmdsZUhlYWRlcnMifSxvdXRwdXRzOntvblNjYWxhclRhYmxlQ29sdW1uVG9nZ2xlZDoib25TY2FsYXJUYWJsZUNvbHVtblRvZ2dsZWQifSxkZWNsczo4LHZhcnM6MTIsY29uc3RzOltbMSwidGFiLWdyb3VwIl0sWzMsImxhYmVsIl0sWzMsIm5nVGVtcGxhdGVPdXRsZXQiLCJuZ1RlbXBsYXRlT3V0bGV0Q29udGV4dCJdLFsiaGVhZGVyTGlzdCIsIiJdLFsxLCJoZWFkZXItbGlzdCJdLFsiY2xhc3MiLCJoZWFkZXItbGlzdC1pdGVtIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbMSwiaGVhZGVyLWxpc3QtaXRlbSJdLFszLCJjaGVja2VkIiwiY2hhbmdlIl0sWzMsImhlYWRlciJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKF8oMCwiZGl2IikoMSwibWF0LXRhYi1ncm91cCIsMCkoMiwibWF0LXRhYiIsMSksTygzLCJuZ0NvbnRleHQiLDIpLHYoKSxfKDQsIm1hdC10YWIiLDEpLE8oNSwibmdDb250ZXh0IiwyKSx2KCkoKSgpLEUoNixrNWUsMiwxLCJuZy10ZW1wbGF0ZSIsbnVsbCwzLHF0KSksMiZlKXtsZXQgcj0kZSg3KTtDKDIpLHkoImxhYmVsIiwiU2luZ2xlIiksQygxKSx5KCJuZ1RlbXBsYXRlT3V0bGV0IixyKSgibmdUZW1wbGF0ZU91dGxldENvbnRleHQiLFFyKDYsam9lLGkuc2luZ2xlSGVhZGVycyxpLkRhdGFUYWJsZU1vZGUuU0lOR0xFKSksQygxKSx5KCJsYWJlbCIsIlJhbmdlIiksQygxKSx5KCJuZ1RlbXBsYXRlT3V0bGV0IixyKSgibmdUZW1wbGF0ZU91dGxldENvbnRleHQiLFFyKDksam9lLGkucmFuZ2VIZWFkZXJzLGkuRGF0YVRhYmxlTW9kZS5SQU5HRSkpfX0sZGVwZW5kZW5jaWVzOltkbixvcyx2Uix5bCxaMix4d10sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXSAgICAgLm1hdC10YWItbGFiZWx7bWluLXdpZHRoOjB9LnRhYi1ncm91cFtfbmdjb250ZW50LSVDT01QJV17cG9zaXRpb246cmVsYXRpdmU7ei1pbmRleDowfS5oZWFkZXItbGlzdFtfbmdjb250ZW50LSVDT01QJV17bWFyZ2luLXRvcDo1JTttYXJnaW4tbGVmdDo1JX0uaGVhZGVyLWxpc3QtaXRlbVtfbmdjb250ZW50LSVDT01QJV17cGFkZGluZzozcHh9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxXb2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5zaW5nbGVIZWFkZXJzJD10aGlzLnN0b3JlLnNlbGVjdChCSSksdGhpcy5yYW5nZUhlYWRlcnMkPXRoaXMuc3RvcmUuc2VsZWN0KFZJKX1vblNjYWxhclRhYmxlQ29sdW1uVG9nZ2xlZCh7ZGF0YVRhYmxlTW9kZTplLGhlYWRlclR5cGU6aX0pe3RoaXMuc3RvcmUuZGlzcGF0Y2gocFIoe2RhdGFUYWJsZU1vZGU6ZSxoZWFkZXJUeXBlOml9KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtZXRyaWNzLXNjYWxhci1jb2x1bW4tZWRpdG9yIl1dLGRlY2xzOjMsdmFyczo2LGNvbnN0czpbWzMsInNpbmdsZUhlYWRlcnMiLCJyYW5nZUhlYWRlcnMiLCJvblNjYWxhclRhYmxlQ29sdW1uVG9nZ2xlZCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwibWV0cmljcy1zY2FsYXItY29sdW1uLWVkaXRvci1jb21wb25lbnQiLDApLFAoIm9uU2NhbGFyVGFibGVDb2x1bW5Ub2dnbGVkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblNjYWxhclRhYmxlQ29sdW1uVG9nZ2xlZChvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksdigpKSwyJmUmJnkoInNpbmdsZUhlYWRlcnMiLFUoMSwyLGkuc2luZ2xlSGVhZGVycyQpKSgicmFuZ2VIZWFkZXJzIixVKDIsNCxpLnJhbmdlSGVhZGVycyQpKX0sZGVwZW5kZW5jaWVzOltHb2UsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLEw1ZT1KKFBJLG9vLChuLHQpPT5uLmZpbHRlcihlPT4hbWwoZS5wbHVnaW4pfHxCb29sZWFuKHQmJnQuZ2V0KGUucnVuSWQpKSkpLG1nPUooTDVlLG49Pm4uc29ydCgodCxlKT0+RncodC50YWcsZS50YWcpKSksYXk9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMucmVmPWUsdGhpcy5jZGtTY3JvbGxhYmxlPWksdGhpcy5vblZpc2liaWxpdHlDaGFuZ2U9bmV3IEcsdGhpcy5uZ1Vuc3Vic2NyaWJlJD1uZXcga2UsdGhpcy5vbkV2ZW50JD1uZXcga2V9bmdPbkluaXQoKXtsZXQgZT17cm9vdDp0aGlzLmNka1Njcm9sbGFibGU/dGhpcy5jZGtTY3JvbGxhYmxlLmdldEVsZW1lbnRSZWYoKS5uYXRpdmVFbGVtZW50Om51bGx9O3RoaXMuaW50ZXJzZWN0aW9uT2JzZXJ2ZXJNYXJnaW4mJihlLnJvb3RNYXJnaW49dGhpcy5pbnRlcnNlY3Rpb25PYnNlcnZlck1hcmdpbik7bGV0IGk9bmV3IEludGVyc2VjdGlvbk9ic2VydmVyKHI9Pnt0aGlzLm9uRXZlbnQkLm5leHQocil9LGUpO2kub2JzZXJ2ZSh0aGlzLnJlZi5uYXRpdmVFbGVtZW50KSx0aGlzLm5nVW5zdWJzY3JpYmUkLnN1YnNjcmliZSgoKT0+e2kudW5vYnNlcnZlKHRoaXMucmVmLm5hdGl2ZUVsZW1lbnQpfSksdGhpcy5vbkV2ZW50JC5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSQpKS5zdWJzY3JpYmUocj0+e2xldCBvPXIuc2xpY2UoLTEpWzBdO3RoaXMub25WaXNpYmlsaXR5Q2hhbmdlLmVtaXQoe3Zpc2libGU6by5pc0ludGVyc2VjdGluZ30pfSl9bmdPbkRlc3Ryb3koKXt0aGlzLm5nVW5zdWJzY3JpYmUkLm5leHQoKSx0aGlzLm5nVW5zdWJzY3JpYmUkLmNvbXBsZXRlKCl9d2FpdEZvckV2ZW50Rm9yVGVzdE9ubHkoKXtyZXR1cm4gbmV3IFByb21pc2UoZT0+dGhpcy5vbkV2ZW50JC5waXBlKFF0KDEpKS5zdWJzY3JpYmUoKCk9PntlKCl9KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0oSWgsOCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJvYnNlcnZlSW50ZXJzZWN0aW9uIiwiIl1dLGlucHV0czp7aW50ZXJzZWN0aW9uT2JzZXJ2ZXJNYXJnaW46ImludGVyc2VjdGlvbk9ic2VydmVyTWFyZ2luIn0sb3V0cHV0czp7b25WaXNpYmlsaXR5Q2hhbmdlOiJvblZpc2liaWxpdHlDaGFuZ2UifX0pLG59KSgpLHFvZT0iL3NjYWxhcl9zdW1tYXJ5IjtmdW5jdGlvbiBseShuLHQpe2xldCBlPW47cmV0dXJuIHQmJm4uc3RhcnRzV2l0aCh0KyIvIikmJihlPW4uc2xpY2UodC5sZW5ndGgrMSkpLGUuZW5kc1dpdGgocW9lKSYmKGU9ZS5zbGljZSgwLC1xb2UubGVuZ3RoKSksZXx8bn1mdW5jdGlvbiBZb2Uobix0LGUpe3JldHVybiBuPHQ/dDpuPmU/ZTpufWZ1bmN0aW9uIFFoKG4sdCxlKXtsZXQgaT1Zb2Uobi5zdGFydC5zdGVwLHQsZSkscj1uLmVuZD9Zb2Uobi5lbmQuc3RlcCx0LGUpOm51bGw7cmV0dXJue3N0YXJ0U3RlcDppLGVuZFN0ZXA6cixjbGlwcGVkOmkhPT1uLnN0YXJ0LnN0ZXB8fHIhPT0obi5lbmQ/LnN0ZXA/P251bGwpfX12YXIgVjVlPVsibWVhc3VyZXIiXSxINWU9WyJpbnB1dCJdLEpvZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuY2hhbmdlRGV0ZWN0b3I9ZSx0aGlzLnBsYWNlaG9sZGVyPSIiLHRoaXMuc3R5bGU9ImRlZmF1bHQiLHRoaXMucGF0dGVyblJlZ2V4PW5ldyBSZWdFeHAoIi4qIiksdGhpcy5pc1ZhbGlkPSEwLHRoaXMub25WYWx1ZUNoYW5nZT1uZXcgRyx0aGlzLmJsdXI9bmV3IEcsdGhpcy5mb2N1cz1uZXcgRyx0aGlzLmtleWRvd249bmV3IEcsdGhpcy5rZXl1cD1uZXcgRyx0aGlzLmludGVybmFsVmFsdWU9IiIsdGhpcy5mb250Q2hhbmdlTGlzdGVuZXI9dGhpcy51cGRhdGVJbnB1dFdpZHRoLmJpbmQodGhpcyl9bmdPbkluaXQoKXtkb2N1bWVudC5mb250cyYmZG9jdW1lbnQuZm9udHMuYWRkRXZlbnRMaXN0ZW5lcigibG9hZGluZ2RvbmUiLHRoaXMuZm9udENoYW5nZUxpc3RlbmVyKX1uZ09uRGVzdHJveSgpe2RvY3VtZW50LmZvbnRzJiZkb2N1bWVudC5mb250cy5yZW1vdmVFdmVudExpc3RlbmVyKCJsb2FkaW5nZG9uZSIsdGhpcy5mb250Q2hhbmdlTGlzdGVuZXIpfW5nT25DaGFuZ2VzKGUpe2UucGF0dGVybiYmKHRoaXMucGF0dGVyblJlZ2V4PW5ldyBSZWdFeHAodGhpcy5wYXR0ZXJuPz8iIikpLGUudmFsdWUmJih0aGlzLmludGVybmFsVmFsdWU9dGhpcy52YWx1ZSksdGhpcy5pc1ZhbGlkPXRoaXMucGF0dGVyblJlZ2V4LnRlc3QodGhpcy5pbnRlcm5hbFZhbHVlKX1uZ0FmdGVyVmlld0NoZWNrZWQoKXt0aGlzLnVwZGF0ZUlucHV0V2lkdGgoKX1vbklucHV0KGUpe2xldCBpPXRoaXMuaW50ZXJuYWxWYWx1ZTt0aGlzLmludGVybmFsVmFsdWU9dGhpcy5pbnB1dEVsUmVmLm5hdGl2ZUVsZW1lbnQudmFsdWUsdGhpcy5pbnRlcm5hbFZhbHVlIT09aSYmKHRoaXMuaXNWYWxpZD10aGlzLnBhdHRlcm5SZWdleC50ZXN0KHRoaXMuaW50ZXJuYWxWYWx1ZSksdGhpcy5jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKSksdGhpcy5vblZhbHVlQ2hhbmdlLmVtaXQoe3ZhbHVlOnRoaXMuaW50ZXJuYWxWYWx1ZX0pfXVwZGF0ZUlucHV0V2lkdGgoKXtsZXR7d2lkdGg6ZX09dGhpcy5tZWFzdXJlckVsUmVmLm5hdGl2ZUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7dGhpcy5pbnB1dEVsUmVmLm5hdGl2ZUVsZW1lbnQuc3R5bGUud2lkdGg9YCR7ZX1weGB9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0obm4pKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJjb250ZW50LXdyYXBwaW5nLWlucHV0Il1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKG90KFY1ZSw3LFJlKSxvdChINWUsNyxSZSkpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkubWVhc3VyZXJFbFJlZj1yLmZpcnN0KSxOZShyPUxlKCkpJiYoaS5pbnB1dEVsUmVmPXIuZmlyc3QpfX0saG9zdFZhcnM6Mixob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJkRhKGkuc3R5bGUpfSxpbnB1dHM6e3ZhbHVlOiJ2YWx1ZSIscGxhY2Vob2xkZXI6InBsYWNlaG9sZGVyIixzdHlsZToic3R5bGUiLHBhdHRlcm46InBhdHRlcm4ifSxvdXRwdXRzOntvblZhbHVlQ2hhbmdlOiJvblZhbHVlQ2hhbmdlIixibHVyOiJibHVyIixmb2N1czoiZm9jdXMiLGtleWRvd246ImtleWRvd24iLGtleXVwOiJrZXl1cCJ9LGZlYXR1cmVzOltGdF0sZGVjbHM6Nix2YXJzOjcsY29uc3RzOltbImFyaWEtaGlkZGVuIiwidHJ1ZSIsMSwibWVhc3VyZXIiXSxbIm1lYXN1cmVyIiwiIl0sWyJhdXRvY29tcGxldGUiLCJvZmYiLCJzcGVsbGNoZWNrIiwiZmFsc2UiLCJ0eXBlIiwidGV4dCIsMywidmFsdWUiLCJwbGFjZWhvbGRlciIsImJsdXIiLCJmb2N1cyIsImlucHV0Iiwia2V5ZG93biIsImtleXVwIl0sWyJpbnB1dCIsIiJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwic3BhbiIpKDEsInNwYW4iLDAsMSksQSgzKSx2KCksXyg0LCJpbnB1dCIsMiwzKSxQKCJibHVyIixmdW5jdGlvbihvKXtyZXR1cm4gaS5ibHVyLmVtaXQobyl9KSgiZm9jdXMiLGZ1bmN0aW9uKG8pe3JldHVybiBpLmZvY3VzLmVtaXQobyl9KSgiaW5wdXQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uSW5wdXQobyl9KSgia2V5ZG93biIsZnVuY3Rpb24obyl7cmV0dXJuIGkua2V5ZG93bi5lbWl0KG8pfSkoImtleXVwIixmdW5jdGlvbihvKXtyZXR1cm4gaS5rZXl1cC5lbWl0KG8pfSksdigpKCkpLDImZSYmKGV0KCJjb250YWluZXIiLCEwKSgiaXMtdmFsaWQiLGkuaXNWYWxpZCksQygzKSx5dChpLmludGVybmFsVmFsdWV8fGkucGxhY2Vob2xkZXIpLEMoMSkseSgidmFsdWUiLGkudmFsdWUpKCJwbGFjZWhvbGRlciIsaS5wbGFjZWhvbGRlcikpfSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2Rpc3BsYXk6aW5saW5lLWZsZXg7d2lkdGg6bWF4LWNvbnRlbnR9W19uZ2hvc3QtJUNPTVAlXTpmb2N1cy13aXRoaW4gICAuY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItY29sb3I6I2Y1N2MwMH0uZGVmYXVsdFtfbmdob3N0LSVDT01QJV06aG92ZXIgICAuY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItY29sb3I6I2ViZWJlYn0uZXJyb3JbX25naG9zdC0lQ09NUCVdICAgLmNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV0sIFtfbmdob3N0LSVDT01QJV0gICAuY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXTpub3QoLmlzLXZhbGlkKXtib3JkZXItY29sb3I6I2VmOWE5YX0uZXJyb3JbX25naG9zdC0lQ09NUCVdICAgLmNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV06aG92ZXIsIC5lcnJvcltfbmdob3N0LSVDT01QJV0gICAuY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXTpmb2N1cy13aXRoaW4sIFtfbmdob3N0LSVDT01QJV0gICAuY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXTpub3QoLmlzLXZhbGlkKTpob3ZlciwgW19uZ2hvc3QtJUNPTVAlXSAgIC5jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdOm5vdCguaXMtdmFsaWQpOmZvY3VzLXdpdGhpbntib3JkZXItY29sb3I6I2VmOWE5YX0uaGlnaC1jb250cmFzdFtfbmdob3N0LSVDT01QJV0gICAuY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItY29sb3I6I2JkYmRiZH0uaGlnaC1jb250cmFzdFtfbmdob3N0LSVDT01QJV0gICAuY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXTpob3Zlcntib3JkZXItY29sb3I6Izc1NzU3NX0uY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItcmFkaXVzOjRweDtib3JkZXI6MnB4IHNvbGlkIHJnYmEoMCwwLDAsMCk7cGFkZGluZzoxcHggMnB4O3Bvc2l0aW9uOnJlbGF0aXZlfS5tZWFzdXJlcltfbmdjb250ZW50LSVDT01QJV17cG9pbnRlci1ldmVudHM6bm9uZTtwb3NpdGlvbjphYnNvbHV0ZTt2aXNpYmlsaXR5OmhpZGRlbn0ubWVhc3VyZXJbX25nY29udGVudC0lQ09NUCVdLCBpbnB1dFtfbmdjb250ZW50LSVDT01QJV17Zm9udC1mYW1pbHk6aW5oZXJpdDtmb250LXNpemU6aW5oZXJpdDtsaW5lLWhlaWdodDoxLjQ7cGFkZGluZzowO3doaXRlLXNwYWNlOnByZX0ubWVhc3VyZXJbX25nY29udGVudC0lQ09NUCVdOmVtcHR5LCBpbnB1dFtfbmdjb250ZW50LSVDT01QJV06ZW1wdHl7d2lkdGg6MmNofWlucHV0W19uZ2NvbnRlbnQtJUNPTVAlXXthcHBlYXJhbmNlOm5vbmU7YmFja2dyb3VuZC1jb2xvcjppbmhlcml0O2JvcmRlcjowO2NvbG9yOmluaGVyaXQ7ZGlzcGxheTppbmxpbmUtYmxvY2s7Zm9udC1mYW1pbHk6aW5oZXJpdDtvdXRsaW5lOjB9aW5wdXRbX25nY29udGVudC0lQ09NUCVdOmZvY3Vze3BhZGRpbmctcmlnaHQ6MWNofSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gejVlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiY29udGVudC13cmFwcGluZy1pbnB1dCIsMyksUCgib25WYWx1ZUNoYW5nZSIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5hbGlhc0NoYW5nZWQuZW1pdChyKSl9KSx2KCl9aWYoMiZuKXtsZXQgZT1TKCk7amwoZS5pc0FsaWFzTmFtZUxlZ2FsPyJoaWdoLWNvbnRyYXN0IjoiZXJyb3IiKSx5KCJ2YWx1ZSIsZS5hbGlhcy5hbGlhc1RleHQpfX1mdW5jdGlvbiBqNWUobix0KXtpZigxJm4mJihfKDAsInNwYW4iLDQpLEEoMSksdigpKSwyJm4pe2xldCBlPVMoKTtldCgiaWxsZWdhbCIsIWUuaXNBbGlhc05hbWVMZWdhbCkseSgidGl0bGUiLGUudGl0bGUpLEMoMSkseXQoZS5hbGlhcy5hbGlhc1RleHQpfX12YXIgY3k9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuaXNBbGlhc05hbWVMZWdhbD0hMCx0aGlzLmFsaWFzQ2hhbmdlZD1uZXcgR319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGItZXhwZXJpbWVudC1hbGlhcyJdXSxpbnB1dHM6e2FsaWFzOiJhbGlhcyIsYWxpYXNFZGl0YWJsZToiYWxpYXNFZGl0YWJsZSIsdGl0bGU6InRpdGxlIixpc0FsaWFzTmFtZUxlZ2FsOiJpc0FsaWFzTmFtZUxlZ2FsIn0sb3V0cHV0czp7YWxpYXNDaGFuZ2VkOiJhbGlhc0NoYW5nZWQifSxkZWNsczo1LHZhcnM6Myxjb25zdHM6W1sxLCJhbGlhcy1udW1iZXIiXSxbInBsYWNlaG9sZGVyIiwiQWxpYXMgZm9yIGV4cGVyaW1lbnQiLDMsInN0eWxlIiwidmFsdWUiLCJvblZhbHVlQ2hhbmdlIiw0LCJuZ0lmIiwibmdJZkVsc2UiXSxbIm5vRWRpdEFsaWFzTmFtZSIsIiJdLFsicGxhY2Vob2xkZXIiLCJBbGlhcyBmb3IgZXhwZXJpbWVudCIsMywidmFsdWUiLCJvblZhbHVlQ2hhbmdlIl0sWzMsInRpdGxlIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoXygwLCJzcGFuIiwwKSxBKDEpLHYoKSxFKDIsejVlLDEsMywiY29udGVudC13cmFwcGluZy1pbnB1dCIsMSksRSgzLGo1ZSwyLDQsIm5nLXRlbXBsYXRlIixudWxsLDIscXQpKSwyJmUpe2xldCByPSRlKDQpO0MoMSkseXQoaS5hbGlhcy5hbGlhc051bWJlciksQygxKSx5KCJuZ0lmIixpLmFsaWFzRWRpdGFibGUpKCJuZ0lmRWxzZSIscil9fSxkZXBlbmRlbmNpZXM6W0JlLEpvZV0sc3R5bGVzOlsiLmFsaWFzLW51bWJlcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZTBlMGUwO2JvcmRlcjoxcHggc29saWQgI2ViZWJlYjtjb2xvcjojMjEyMTIxO2JvcmRlci1yYWRpdXM6MnB4O21hcmdpbi1yaWdodDoycHg7cGFkZGluZzowIDJweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuYWxpYXMtbnVtYmVyW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmFsaWFzLW51bWJlcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojNjE2MTYxfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5hbGlhcy1udW1iZXJbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuYWxpYXMtbnVtYmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXI6MXB4IHNvbGlkICM1NTV9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmFsaWFzLW51bWJlcltfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5hbGlhcy1udW1iZXJbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiNmZmZ9W19uZ2hvc3QtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1mbGV4O2FsaWduLWl0ZW1zOmJhc2VsaW5lfSJdfSksbn0pKCk7ZnVuY3Rpb24gRzVlKG4sdCl7MSZuJiZPKDAsInRiLWV4cGVyaW1lbnQtYWxpYXMiLDIpLDImbiYmeSgiYWxpYXMiLFMoKS5leHBlcmltZW50QWxpYXMpfWZ1bmN0aW9uIFc1ZShuLHQpezEmbiYmKF8oMCwic3BhbiIpLEEoMSwiLyIpLHYoKSl9dmFyICRvZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJjYXJkLXJ1bi1uYW1lLWNvbXBvbmVudCJdXSxpbnB1dHM6e25hbWU6Im5hbWUiLGV4cGVyaW1lbnRBbGlhczoiZXhwZXJpbWVudEFsaWFzIn0sZGVjbHM6NCx2YXJzOjMsY29uc3RzOltbMywiYWxpYXMiLDQsIm5nSWYiXSxbNCwibmdJZiJdLFszLCJhbGlhcyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKEUoMCxHNWUsMSwxLCJ0Yi1leHBlcmltZW50LWFsaWFzIiwwKSxFKDEsVzVlLDIsMCwic3BhbiIsMSksXygyLCJzcGFuIiksQSgzKSx2KCkpLDImZSYmKHkoIm5nSWYiLG51bGwhPWkuZXhwZXJpbWVudEFsaWFzKSxDKDEpLHkoIm5nSWYiLG51bGwhPWkuZXhwZXJpbWVudEFsaWFzKSxDKDIpLHl0KGkubmFtZSkpfSxkZXBlbmRlbmNpZXM6W0JlLGN5XSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2NvbG9yOiM2MTYxNjF9Ym9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCkseVI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWV9bmdPbkluaXQoKXt0aGlzLm5hbWUkPUx0KFt0aGlzLnN0b3JlLnNlbGVjdChXSSx7cnVuSWQ6dGhpcy5ydW5JZH0pXSkucGlwZShMKChbZV0pPT5mdW5jdGlvbihuLHQsZSl7aWYoIXQpcmV0dXJuIG47bGV0IGk9dD8ubmFtZT8/Ii4uLiI7cmV0dXJuIGl9KHRoaXMucnVuSWQsZSkpKSx0aGlzLmV4cGVyaW1lbnRBbGlhcyQ9THQoW3RoaXMuc3RvcmUuc2VsZWN0KEdJLHtydW5JZDp0aGlzLnJ1bklkfSksdGhpcy5zdG9yZS5zZWxlY3QoWXUpXSkucGlwZShMKChbZSxpXSk9PmU/aVtlXTpudWxsKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJjYXJkLXJ1bi1uYW1lIl1dLGlucHV0czp7cnVuSWQ6InJ1bklkIn0sZGVjbHM6NCx2YXJzOjksY29uc3RzOltbMywibmFtZSIsImV4cGVyaW1lbnRBbGlhcyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKE8oMCwiY2FyZC1ydW4tbmFtZS1jb21wb25lbnQiLDApLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpKSwyJmUmJih5KCJuYW1lIixVKDEsMyxpLm5hbWUkKSkoImV4cGVyaW1lbnRBbGlhcyIsVSgzLDcsaS5leHBlcmltZW50QWxpYXMkKSksemUoInRpdGxlIixVKDIsNSxpLm5hbWUkKSkpfSxkZXBlbmRlbmNpZXM6WyRvZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gWTVlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiwyKSxBKDEpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygxKSx5dChlLmZpcnN0VGV4dFBhcnQoKSl9fXZhciB1eT0oKCk9PntjbGFzcyBue3BhcnNlVmFsdWUoKXtsZXQgZT10aGlzLnZhbHVlLmxhc3RJbmRleE9mKCIvIik7cmV0dXJuLTE9PT1lP3tmaXJzdDoiIixzZWNvbmQ6dGhpcy52YWx1ZX06e2ZpcnN0OnRoaXMudmFsdWUuc2xpY2UoMCxlKSxzZWNvbmQ6dGhpcy52YWx1ZS5zbGljZShlKX19Zmlyc3RUZXh0UGFydCgpe3JldHVybiB0aGlzLnBhcnNlVmFsdWUoKS5maXJzdH1zZWNvbmRUZXh0UGFydCgpe3JldHVybiB0aGlzLnBhcnNlVmFsdWUoKS5zZWNvbmR9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInRiLXRydW5jYXRlZC1wYXRoIl1dLGlucHV0czp7dmFsdWU6InZhbHVlIn0sZGVjbHM6Myx2YXJzOjIsY29uc3RzOltbImNsYXNzIiwiZmlyc3QtdGV4dC1wYXJ0Iiw0LCJuZ0lmIl0sWzEsInNlY29uZC10ZXh0LXBhcnQiXSxbMSwiZmlyc3QtdGV4dC1wYXJ0Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoRSgwLFk1ZSwyLDEsInNwYW4iLDApLF8oMSwic3BhbiIsMSksQSgyKSx2KCkpLDImZSYmKHkoIm5nSWYiLGkuZmlyc3RUZXh0UGFydCgpLmxlbmd0aD4wKSxDKDIpLHl0KGkuc2Vjb25kVGV4dFBhcnQoKSkpfSxkZXBlbmRlbmNpZXM6W0JlXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2Rpc3BsYXk6aW5saW5lLWZsZXg7d2hpdGUtc3BhY2U6bm93cmFwfS5maXJzdC10ZXh0LXBhcnRbX25nY29udGVudC0lQ09NUCVde2ZsZXg6MSAxIDRjaDttYXgtd2lkdGg6bWF4LWNvbnRlbnR9LmZpcnN0LXRleHQtcGFydFtfbmdjb250ZW50LSVDT01QJV0sIC5zZWNvbmQtdGV4dC1wYXJ0W19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpc30iXX0pLG59KSgpO2Z1bmN0aW9uIFg1ZShuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsMil9ZnVuY3Rpb24gUTVlKG4sdCl7MSZuJiZPKDAsIm1hdC1pY29uIiwzKX12YXIgZHk9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuaXNDbGlwcGVkPSExLHRoaXMuaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkPSExfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ2aXMtbGlua2VkLXRpbWUtc2VsZWN0aW9uLXdhcm5pbmciXV0saW5wdXRzOntpc0NsaXBwZWQ6ImlzQ2xpcHBlZCIsaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkOiJpc0Nsb3Nlc3RTdGVwSGlnaGxpZ2h0ZWQifSxkZWNsczoyLHZhcnM6Mixjb25zdHM6W1siZGF0YS12YWx1ZSIsImNsaXBwZWQiLCJzdmdJY29uIiwiaW5mb19vdXRsaW5lXzI0cHgiLCJ0aXRsZSIsIkxpbmtlZCBzdGVwIGlzIG5vdCBmb3VuZCBpbiB0aGlzIHZpc3VhbGl6YXRpb24uIFdlIGhpZ2hsaWdodGVkIHRoZSBjbG9zZXN0IHN0ZXAgZm9yIHlvdS4iLDQsIm5nSWYiXSxbImRhdGEtdmFsdWUiLCJjbG9zZXN0U3RlcEhpZ2hsaWdodGVkIiwic3ZnSWNvbiIsImluZm9fb3V0bGluZV8yNHB4IiwidGl0bGUiLCJEYXRhIGlzIG5vdCBmb3VuZCBvbiBzZWxlY3RlZCBzdGVwLiBXZSBoaWdobGlnaHRlZCB0aGUgY2xvc2VzdCBzdGVwIGZvciB5b3UuIiw0LCJuZ0lmIl0sWyJkYXRhLXZhbHVlIiwiY2xpcHBlZCIsInN2Z0ljb24iLCJpbmZvX291dGxpbmVfMjRweCIsInRpdGxlIiwiTGlua2VkIHN0ZXAgaXMgbm90IGZvdW5kIGluIHRoaXMgdmlzdWFsaXphdGlvbi4gV2UgaGlnaGxpZ2h0ZWQgdGhlIGNsb3Nlc3Qgc3RlcCBmb3IgeW91LiJdLFsiZGF0YS12YWx1ZSIsImNsb3Nlc3RTdGVwSGlnaGxpZ2h0ZWQiLCJzdmdJY29uIiwiaW5mb19vdXRsaW5lXzI0cHgiLCJ0aXRsZSIsIkRhdGEgaXMgbm90IGZvdW5kIG9uIHNlbGVjdGVkIHN0ZXAuIFdlIGhpZ2hsaWdodGVkIHRoZSBjbG9zZXN0IHN0ZXAgZm9yIHlvdS4iXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihFKDAsWDVlLDEsMCwibWF0LWljb24iLDApLEUoMSxRNWUsMSwwLCJtYXQtaWNvbiIsMSkpLDImZSYmKHkoIm5nSWYiLGkuaXNDbGlwcGVkKSxDKDEpLHkoIm5nSWYiLGkuaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkKSl9LGRlcGVuZGVuY2llczpbQmUsR3RdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17Y29sb3I6I2QzMmYyZjtoZWlnaHQ6MWVtO2xpbmUtaGVpZ2h0OjA7ZGlzcGxheTppbmxpbmUtZmxleH1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV17Y29sb3I6I2QzMmYyZn1bX25naG9zdC0lQ09NUCVdICAgbWF0LWljb25bX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxMDAlO3dpZHRoOjEwMCV9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiBLNWUobix0KXtpZigxJm4mJihfKDAsInNwYW4iLDE5KSxBKDEpLEIoMiwibnVtYmVyIiksdigpKSwyJm4pe2xldCBlPVMoKTtDKDEpLGplKCJTdGVwICIsVSgyLDEsZS5zdGVwc1tlLnN0ZXBJbmRleF0pLCIiKX19ZnVuY3Rpb24gWjVlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiwyMCksQSgxKSxCKDIsIm51bWJlciIpLEIoMywibnVtYmVyIiksdigpKSwyJm4pe2xldCBlPVMoKTtDKDEpLFhwKCJTYW1wbGUgIixVKDIsMixlLnNhbXBsZSsxKSwiLyIsVSgzLDQsZS5udW1TYW1wbGUpLCIiKX19ZnVuY3Rpb24gSjVlKG4sdCl7MSZuJiZPKDAsIm1hdC1zcGlubmVyIiwyMSl9ZnVuY3Rpb24gJDVlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiksTygxLCJzcGFuIiwzMCkoMiwic3BhbiIsMzEpLHYoKSksMiZuKXtsZXQgZT1TKDMpO0MoMiksUHQoImxlZnQiLGUuc2xpZGVyU3RhcnRQb3NpdGlvbikoIndpZHRoIixlLnNsaWRlclRyYWNrV2lkdGgpfX1mdW5jdGlvbiBlNGUobix0KXtpZigxJm4mJk8oMCwiZGl2IiwzMiksMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPVMoMyk7UHQoImxlZnQiLGkuZ2V0TGlua2VkVGltZVRpY2tMZWZ0U3R5bGUoZSkpKCJtYXJnaW4tbGVmdCIsaS5nZXRMaW5rZWRUaW1lVGlja01hcmdpbkxlZnRTdHlsZShlKSl9fWZ1bmN0aW9uIHQ0ZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwyNyksRSgxLCQ1ZSwzLDQsInNwYW4iLDI4KSxFKDIsZTRlLDEsNCwiZGl2IiwyOSksdigpKSwyJm4pe2xldCBlPVMoMik7QygxKSx5KCJuZ0lmIixudWxsIT09ZS5saW5rZWRUaW1lU2VsZWN0aW9uLmVuZFN0ZXApLEMoMSkseSgibmdGb3JPZiIsZS5zZWxlY3RlZFN0ZXBzKX19dmFyIG40ZT1mdW5jdGlvbihuKXtyZXR1cm5bbl19LGk0ZT1mdW5jdGlvbihuKXtyZXR1cm57ZmlsdGVyOm59fTtmdW5jdGlvbiByNGUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7c24oMCksXygxLCJkaXYiLDIyKSgyLCJtYXQtc2xpZGVyIiwyMyksUCgiaW5wdXQiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25TbGlkZXJJbnB1dChyKSl9KSx2KCksRSgzLHQ0ZSwzLDIsImRpdiIsMjQpLHYoKSxfKDQsImRpdiIsMjUpLE8oNSwiaW1nIiwyNiksdigpLGFuKCl9aWYoMiZuKXtsZXQgZT1TKCk7QygyKSx5KCJuZ0NsYXNzIixPbigxMSxuNGUsZS5saW5rZWRUaW1lU2VsZWN0aW9uJiZudWxsIT09ZS5saW5rZWRUaW1lU2VsZWN0aW9uLmVuZFN0ZXA/ImhpZGUtc2xpZGVyIjoiIikpKCJkaXNhYmxlZCIsZS5zdGVwcy5sZW5ndGg8PTEpKCJtaW4iLDApKCJtYXgiLGUuc3RlcHMubGVuZ3RoLTEpKCJzdGVwIiwxKSgidGlja0ludGVydmFsIiwxKSgidmFsdWUiLGUuc3RlcEluZGV4KSxDKDEpLHkoIm5nSWYiLGUubGlua2VkVGltZVNlbGVjdGlvbiksQygyKSxYeCgiYWx0IiwiSW1hZ2UgYXQgc3RlcCAiLGUuc3RlcHNbZS5zdGVwSW5kZXhdLCIiKSxaaSgic3JjIixlLmltYWdlVXJsLHpsKSx5KCJuZ1N0eWxlIixPbigxMyxpNGUsZS5jc3NGaWx0ZXIoKSkpfX1mdW5jdGlvbiBvNGUobix0KXsxJm4mJihfKDAsImRpdiIsMzQpLEEoMSwiIERhdGEgZmFpbGVkIHRvIGxvYWQuICIpLHYoKSl9ZnVuY3Rpb24gczRlKG4sdCl7aWYoMSZuJiZFKDAsbzRlLDIsMCwiZGl2IiwzMyksMiZuKXtsZXQgZT1TKCk7eSgibmdJZiIsZS5sb2FkU3RhdGU9PT1lLkRhdGFMb2FkU3RhdGUuRkFJTEVEKX19dmFyIGE0ZT1mdW5jdGlvbihuKXtyZXR1cm57YmFja2dyb3VuZENvbG9yOm59fSx0c2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuRGF0YUxvYWRTdGF0ZT1PZSx0aGlzLnNsaWRlclN0YXJ0UG9zaXRpb249IiIsdGhpcy5zbGlkZXJUcmFja1dpZHRoPSIiLHRoaXMubGlua2VkVGltZVNlbGVjdGlvbj1udWxsLHRoaXMuaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkPSExLHRoaXMub25BY3R1YWxTaXplVG9nZ2xlPW5ldyBHLHRoaXMuc3RlcEluZGV4Q2hhbmdlPW5ldyBHLHRoaXMub25QaW5DbGlja2VkPW5ldyBHfWNzc0ZpbHRlcigpe3JldHVybmBjb250cmFzdCgke3RoaXMuY29udHJhc3RJbk1pbGxpLzEwfSUpIGJyaWdodG5lc3MoJHt0aGlzLmJyaWdodG5lc3NJbk1pbGxpLzFlM30pYH1vblNsaWRlcklucHV0KGUpe3RoaXMuc3RlcEluZGV4Q2hhbmdlLmVtaXQoZS52YWx1ZSl9Y2hhbmdlRGlzdGluY3QoZSl7cmV0dXJuIGUuY3VycmVudFZhbHVlIT09ZS5wcmV2aW91c1ZhbHVlfW5nT25DaGFuZ2VzKGUpeyhlLnNlbGVjdGVkU3RlcHMmJnRoaXMuY2hhbmdlRGlzdGluY3QoZS5zZWxlY3RlZFN0ZXBzKXx8ZS5saW5rZWRUaW1lU2VsZWN0aW9uJiZ0aGlzLmNoYW5nZURpc3RpbmN0KGUubGlua2VkVGltZVNlbGVjdGlvbikpJiZ0aGlzLnJlbmRlclJhbmdlU2xpZGVyKCl9cmVuZGVyUmFuZ2VTbGlkZXIoKXtpZighdGhpcy5saW5rZWRUaW1lU2VsZWN0aW9ufHwhdGhpcy5saW5rZWRUaW1lU2VsZWN0aW9uLmVuZFN0ZXApcmV0dXJuO2xldCBlPXRoaXMuc3RlcHMubGVuZ3RoLTEsaT10aGlzLmxpbmtlZFRpbWVTZWxlY3Rpb24uc3RhcnRTdGVwPHRoaXMuc3RlcHNbMF0/dGhpcy5zdGVwc1swXTp0aGlzLmxpbmtlZFRpbWVTZWxlY3Rpb24uc3RhcnRTdGVwLHI9dGhpcy5saW5rZWRUaW1lU2VsZWN0aW9uLmVuZFN0ZXA+dGhpcy5zdGVwc1tlXT90aGlzLnN0ZXBzW2VdOnRoaXMubGlua2VkVGltZVNlbGVjdGlvbi5lbmRTdGVwLHtzdGFydFBvc2l0aW9uOm8sd2lkdGg6c309dGhpcy5nZXRUcmFja1N0YXJ0UG9zaXRpb25BbmRXaWR0aChpLHIsZSk7dGhpcy5zbGlkZXJTdGFydFBvc2l0aW9uPTEwMCpvKyIlIix0aGlzLnNsaWRlclRyYWNrV2lkdGg9MTAwKnMrIiUifWdldFRyYWNrU3RhcnRQb3NpdGlvbkFuZFdpZHRoKGUsaSxyKXtsZXQgbz0xL3Iscz0wLGE9MCxsPTA7Zm9yKDtsPHRoaXMuc3RlcHMubGVuZ3RoLTE7bCsrKXtsZXQgYz10aGlzLnN0ZXBzW2xdLHU9dGhpcy5zdGVwc1tsKzFdO2lmKGM8PWUmJmU8PXUpe3MrPShlLWMpLyh1LWMpO2JyZWFrfX1mb3Iocz0ocytsKSpvO2w8dGhpcy5zdGVwcy5sZW5ndGgtMTtsKyspe2xldCBjPXRoaXMuc3RlcHNbbF0sdT10aGlzLnN0ZXBzW2wrMV07aWYoZT49YyYmaTw9dSl7YT0oaS1lKS8odS1jKTticmVha31pZihlPj1jJiZpPj11KWErPSh1LWUpLyh1LWMpO2Vsc2V7aWYoIShpPj11KSl7YSs9KGktYykvKHUtYyk7YnJlYWt9YSs9MX19cmV0dXJuIGEqPW8sKHM+MXx8czwwKSYmKHM9MCkse3N0YXJ0UG9zaXRpb246cyx3aWR0aDphfX1nZXRMaW5rZWRUaW1lVGlja0xlZnRTdHlsZShlKXtpZigtMT09dGhpcy5zdGVwcy5pbmRleE9mKGUpKXRocm93IG5ldyBFcnJvcigiSW52YWxpZCBzdGVwSW5kZXg6IHN0ZXBJbmRleCB2YWx1ZSBpcyBub3QgaW5jbHVkZWQgaW4gc3RlcHMiKTtyZXR1cm4gdGhpcy5zdGVwcy5pbmRleE9mKGUpLyh0aGlzLnN0ZXBzLmxlbmd0aC0xKSoxMDArIiUifWdldExpbmtlZFRpbWVUaWNrTWFyZ2luTGVmdFN0eWxlKGUpe2lmKC0xPT10aGlzLnN0ZXBzLmluZGV4T2YoZSkpdGhyb3cgbmV3IEVycm9yKCJJbnZhbGlkIHN0ZXBJbmRleDogc3RlcEluZGV4IHZhbHVlIGlzIG5vdCBpbmNsdWRlZCBpbiBzdGVwcyIpO3JldHVybmAtJHt0aGlzLnN0ZXBzLmluZGV4T2YoZSkvKHRoaXMuc3RlcHMubGVuZ3RoLTEpKjE0fXB4YH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siaW1hZ2UtY2FyZC1jb21wb25lbnQiXV0saG9zdFZhcnM6Mixob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJmV0KCJhY3R1YWwtc2l6ZSIsaS5zaG93QWN0dWFsU2l6ZSl9LGlucHV0czp7bG9hZFN0YXRlOiJsb2FkU3RhdGUiLHRpdGxlOiJ0aXRsZSIsdGFnOiJ0YWciLHJ1bklkOiJydW5JZCIsc2FtcGxlOiJzYW1wbGUiLG51bVNhbXBsZToibnVtU2FtcGxlIixpbWFnZVVybDoiaW1hZ2VVcmwiLHN0ZXBJbmRleDoic3RlcEluZGV4IixzdGVwczoic3RlcHMiLGJyaWdodG5lc3NJbk1pbGxpOiJicmlnaHRuZXNzSW5NaWxsaSIsY29udHJhc3RJbk1pbGxpOiJjb250cmFzdEluTWlsbGkiLHNob3dBY3R1YWxTaXplOiJzaG93QWN0dWFsU2l6ZSIscnVuQ29sb3JTY2FsZToicnVuQ29sb3JTY2FsZSIsYWxsb3dUb2dnbGVBY3R1YWxTaXplOiJhbGxvd1RvZ2dsZUFjdHVhbFNpemUiLGlzUGlubmVkOiJpc1Bpbm5lZCIsc2VsZWN0ZWRTdGVwczoic2VsZWN0ZWRTdGVwcyIsbGlua2VkVGltZVNlbGVjdGlvbjoibGlua2VkVGltZVNlbGVjdGlvbiIsaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkOiJpc0Nsb3Nlc3RTdGVwSGlnaGxpZ2h0ZWQifSxvdXRwdXRzOntvbkFjdHVhbFNpemVUb2dnbGU6Im9uQWN0dWFsU2l6ZVRvZ2dsZSIsc3RlcEluZGV4Q2hhbmdlOiJzdGVwSW5kZXhDaGFuZ2UiLG9uUGluQ2xpY2tlZDoib25QaW5DbGlja2VkIn0sZmVhdHVyZXM6W0Z0XSxkZWNsczoyMSx2YXJzOjE2LGNvbnN0czpmdW5jdGlvbigpe2xldCB0LGU7cmV0dXJuIHQ9JGxvY2FsaXplYDpBIGJ1dHRvbiB0byBwaW4gYSBjYXJkLuKQn2U2NjVkYzcxMmJkNWYxOGQ0ZGZhM2EyOWUxMjVkNTY1Y2M1MWUyZjbikJ83Mjg0NjA2NDI2MjM0Mzc1MzQ0OlBpbiBjYXJkYCxlPSRsb2NhbGl6ZWA6QSBidXR0b24gb24gYW4gaW1hZ2UgY2FyZCB0aGF0IHRvZ2dsZXMgYWN0dWFsIGltYWdlIHNpemUu4pCfM2NhMDVlZjNhNmUzYTM3MDY1ZjVlMGY2OWM1ZDVhMjE3OGQ5MDc5MeKQnzc2MzUxMDE5MzY2NjQ3ODkxNDA6VG9nZ2xlIGFjdHVhbCBpbWFnZSBzaXplYCxbWzEsImhlYWRpbmciXSxbMSwibGluZSJdLFsxLCJ0YWciXSxbMSwidGFnLXBhdGgiLDMsInRpdGxlIiwidmFsdWUiXSxbMywiaXNDbGlwcGVkIiwiaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkIl0sWzEsImNvbnRyb2xzIl0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJhcmlhLWxhYmVsIix0LDEsInBpbi1idXR0b24iLDMsImNsaWNrIl0sWzMsInN2Z0ljb24iXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsImFyaWEtbGFiZWwiLGUsInRpdGxlIiwiVG9nZ2xlIGFjdHVhbCBpbWFnZSBzaXplIiwzLCJkaXNhYmxlZCIsImNsaWNrIl0sWyJzdmdJY29uIiwiaW1hZ2Vfc2VhcmNoXzI0cHgiXSxbMSwicnVuIl0sWzEsImRvdCIsMywibmdTdHlsZSJdLFsxLCJydW4tdGV4dCIsMywicnVuSWQiXSxbMSwibWV0YWRhdGEiXSxbImNsYXNzIiwic3RlcCIsNCwibmdJZiJdLFsiY2xhc3MiLCJzYW1wbGUiLDQsIm5nSWYiXSxbImNsYXNzIiwibG9hZGluZyIsImRpYW1ldGVyIiwiMTgiLDQsIm5nSWYiXSxbNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJub0ltYWdlRGF0YSIsIiJdLFsxLCJzdGVwIl0sWzEsInNhbXBsZSJdLFsiZGlhbWV0ZXIiLCIxOCIsMSwibG9hZGluZyJdLFsxLCJzbGlkZXItcm93Il0sWyJjb2xvciIsInByaW1hcnkiLDEsInN0ZXAtc2xpZGVyIiwzLCJuZ0NsYXNzIiwiZGlzYWJsZWQiLCJtaW4iLCJtYXgiLCJzdGVwIiwidGlja0ludGVydmFsIiwidmFsdWUiLCJpbnB1dCJdLFsiY2xhc3MiLCJsaW5rZWQtdGltZS13cmFwcGVyIiw0LCJuZ0lmIl0sWzEsImltZy1jb250YWluZXIiXSxbMywiYWx0Iiwic3JjIiwibmdTdHlsZSJdLFsxLCJsaW5rZWQtdGltZS13cmFwcGVyIl0sWzQsIm5nSWYiXSxbImNsYXNzIiwibGlua2VkLXRpbWUtdGljayIsMywibGVmdCIsIm1hcmdpbi1sZWZ0Iiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbMSwic2xpZGVyLXRyYWNrIl0sWzEsInNsaWRlci10cmFjay1maWxsIl0sWzEsImxpbmtlZC10aW1lLXRpY2siXSxbImNsYXNzIiwiZW1wdHktbWVzc2FnZSIsNCwibmdJZiJdLFsxLCJlbXB0eS1tZXNzYWdlIl1dfSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKF8oMCwiZGl2IiwwKSgxLCJkaXYiLDEpKDIsInNwYW4iLDIpLE8oMywidGItdHJ1bmNhdGVkLXBhdGgiLDMpKDQsInZpcy1saW5rZWQtdGltZS1zZWxlY3Rpb24td2FybmluZyIsNCksdigpLF8oNSwic3BhbiIsNSkoNiwiYnV0dG9uIiw2KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblBpbkNsaWNrZWQuZW1pdCghaS5pc1Bpbm5lZCl9KSxPKDcsIm1hdC1pY29uIiw3KSx2KCksXyg4LCJidXR0b24iLDgpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uQWN0dWFsU2l6ZVRvZ2dsZS5lbWl0KCl9KSxPKDksIm1hdC1pY29uIiw5KSx2KCkoKSgpLF8oMTAsImRpdiIsMSkoMTEsInNwYW4iLDEwKSxPKDEyLCJzcGFuIiwxMSkoMTMsImNhcmQtcnVuLW5hbWUiLDEyKSx2KCksXygxNCwiZGl2IiwxMyksRSgxNSxLNWUsMywzLCJzcGFuIiwxNCksRSgxNixaNWUsNCw2LCJzcGFuIiwxNSksRSgxNyxKNWUsMSwwLCJtYXQtc3Bpbm5lciIsMTYpLHYoKSgpKCksRSgxOCxyNGUsNiwxNSwibmctY29udGFpbmVyIiwxNyksRSgxOSxzNGUsMSwxLCJuZy10ZW1wbGF0ZSIsbnVsbCwxOCxxdCkpLDImZSl7bGV0IHI9JGUoMjApO0MoMyksWmkoInRpdGxlIixpLnRhZyksWmkoInZhbHVlIixpLnRpdGxlKSxDKDEpLHkoImlzQ2xpcHBlZCIsaS5saW5rZWRUaW1lU2VsZWN0aW9uJiZpLmxpbmtlZFRpbWVTZWxlY3Rpb24uY2xpcHBlZCkoImlzQ2xvc2VzdFN0ZXBIaWdobGlnaHRlZCIsaS5pc0Nsb3Nlc3RTdGVwSGlnaGxpZ2h0ZWQpLEMoMiksemUoInRpdGxlIixpLmlzUGlubmVkPyJVbnBpbiBjYXJkIjoiUGluIGNhcmQiKSxDKDEpLHkoInN2Z0ljb24iLGkuaXNQaW5uZWQ/ImtlZXBfMjRweCI6ImtlZXBfb3V0bGluZV8yNHB4IiksQygxKSx5KCJkaXNhYmxlZCIsIWkuYWxsb3dUb2dnbGVBY3R1YWxTaXplKSxDKDQpLHkoIm5nU3R5bGUiLE9uKDE0LGE0ZSxpLnJ1bkNvbG9yU2NhbGUoaS5ydW5JZCkpKSxDKDEpLHkoInJ1bklkIixpLnJ1bklkKSxDKDIpLHkoIm5nSWYiLG51bGwhPT1pLnN0ZXBJbmRleCYmaS5zdGVwSW5kZXg8aS5zdGVwcy5sZW5ndGgpLEMoMSkseSgibmdJZiIsaS5udW1TYW1wbGU+MSksQygxKSx5KCJuZ0lmIixpLmxvYWRTdGF0ZT09PWkuRGF0YUxvYWRTdGF0ZS5MT0FESU5HKSxDKDEpLHkoIm5nSWYiLG51bGwhPT1pLnN0ZXBJbmRleCYmaS5zdGVwSW5kZXg8aS5zdGVwcy5sZW5ndGgpKCJuZ0lmRWxzZSIscil9fSxkZXBlbmRlbmNpZXM6W0ZuLGRuLEJlLHp1LF9uLEd0LEJvLHVwLHlSLHV5LGR5LFFsXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2JveC1zaXppbmc6Ym9yZGVyLWJveDtkaXNwbGF5OmZsZXg7ZmxleC1iYXNpczozMThweDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47ZmxleC1ncm93OjE7aGVpZ2h0OjEwMCU7b3ZlcmZsb3c6YXV0bztwYWRkaW5nOjE2cHg7cGFkZGluZy10b3A6NHB4fS5hY3R1YWwtc2l6ZVtfbmdob3N0LSVDT01QJV17aGVpZ2h0OmF1dG99LmhlYWRpbmdbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtmb250LXNpemU6MTRweDttYXJnaW4tYm90dG9tOjRweDtwb3NpdGlvbjpyZWxhdGl2ZX0ubGluZVtfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6Z3JpZDtncmlkLXRlbXBsYXRlLWNvbHVtbnM6MWZyIG1heC1jb250ZW50fS50YWdbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtkaXNwbGF5OmZsZXg7Z2FwOjVweH0ubWV0YWRhdGFbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6ZmxleDtmbGV4LXdyYXA6d3JhcDtnYXA6NXB4O2p1c3RpZnktY29udGVudDpmbGV4LWVuZDttYXgtd2lkdGg6MTc1cHg7dGV4dC1hbGlnbjplbmR9LnRhZy1wYXRoW19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdzpoaWRkZW59LnBpbi1idXR0b25bX25nY29udGVudC0lQ09NUCVdICAgbWF0LWljb25bX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxOHB4fS5ydW5bX25nY29udGVudC0lQ09NUCVde2FsaWduLXNlbGY6YmFzZWxpbmU7ZGlzcGxheTpmbGV4O292ZXJmbG93OmhpZGRlbjt3aGl0ZS1zcGFjZTpub3dyYXB9LnJ1bltfbmdjb250ZW50LSVDT01QJV0gICAuZG90W19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4Om5vbmU7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2lkdGg6MTNweDtoZWlnaHQ6MTNweDtib3JkZXItcmFkaXVzOjUwJTttYXJnaW4tcmlnaHQ6NHB4fS5ydW5bX25nY29udGVudC0lQ09NUCVdICAgLnJ1bi10ZXh0W19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpczttYXgtd2lkdGg6MTIwcHh9LnJ1bltfbmdjb250ZW50LSVDT01QJV0sIC5zYW1wbGVbX25nY29udGVudC0lQ09NUCVdLCAuc3RlcFtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTtmb250LXNpemU6MTNweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAucnVuW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnJ1bltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnNhbXBsZVtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5zYW1wbGVbX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5zdGVwW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnN0ZXBbX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfS5jb250cm9sc1tfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTt3aGl0ZS1zcGFjZTpub3dyYXA7anVzdGlmeS1zZWxmOmZsZXgtZW5kO2ZsZXgtc2hyaW5rOjA7bWFyZ2luLXJpZ2h0Oi0xMnB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5jb250cm9sc1tfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5jb250cm9sc1tfbmdjb250ZW50LSVDT01QJV17Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9LmltZy1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2ZsZXgtZ3JvdzoxO292ZXJmbG93LXk6YXV0bztwb3NpdGlvbjpyZWxhdGl2ZX0uaW1nLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV0gICBpbWdbX25nY29udGVudC0lQ09NUCVde2ltYWdlLXJlbmRlcmluZzotbW96LWNyaXNwLWVkZ2VzO2ltYWdlLXJlbmRlcmluZzpwaXhlbGF0ZWR9LmFjdHVhbC1zaXplW19uZ2hvc3QtJUNPTVAlXSAgIC5pbWctY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdzphdXRvO2ZsZXg6bm9uZX1bX25naG9zdC0lQ09NUCVdOm5vdCguYWN0dWFsLXNpemUpICAgaW1nW19uZ2NvbnRlbnQtJUNPTVAlXXtwb3NpdGlvbjphYnNvbHV0ZTttYXgtaGVpZ2h0OjEwMCU7bWF4LXdpZHRoOjEwMCU7d2lkdGg6YXV0bztoZWlnaHQ6MTAwJTtvYmplY3QtZml0OmNvbnRhaW59LnNsaWRlci1yb3dbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6ZmxleDthbGlnbi1pdGVtczpjZW50ZXI7aGVpZ2h0OjI0cHg7cG9zaXRpb246cmVsYXRpdmV9LnN0ZXAtc2xpZGVyW19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4OjF9W19uZ2hvc3QtJUNPTVAlXSAgICAgLm1hdC1zbGlkZXItbWluLXZhbHVlIC5tYXQtc2xpZGVyLXRodW1ie2JhY2tncm91bmQtY29sb3I6I2Y1N2MwMH1bX25naG9zdC0lQ09NUCVdICAgICAuaGlkZS1zbGlkZXIubWF0LXNsaWRlci1ob3Jpem9udGFsIC5tYXQtc2xpZGVyLXRyYWNrLXdyYXBwZXJ7aGVpZ2h0OjB9LmVtcHR5LW1lc3NhZ2VbX25nY29udGVudC0lQ09NUCVde21hcmdpbi10b3A6MWVtO2ZvbnQtc2l6ZToxM3B4fS5saW5rZWQtdGltZS13cmFwcGVyW19uZ2NvbnRlbnQtJUNPTVAlXXtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6NXB4O3dpZHRoOjEwMCV9LmxpbmtlZC10aW1lLXRpY2tbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2UwZTBlMDtib3JkZXItcmFkaXVzOjUwJTtoZWlnaHQ6MTRweDtwb3NpdGlvbjphYnNvbHV0ZTt3aWR0aDoxNHB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5saW5rZWQtdGltZS10aWNrW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmxpbmtlZC10aW1lLXRpY2tbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6IzIxMjEyMX0uc2xpZGVyLXRyYWNrW19uZ2NvbnRlbnQtJUNPTVAlXSwgLnNsaWRlci10cmFjay1maWxsW19uZ2NvbnRlbnQtJUNPTVAlXXtoZWlnaHQ6MnB4O3RvcDo2cHg7cG9zaXRpb246YWJzb2x1dGV9LnNsaWRlci10cmFja1tfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZDpyZ2JhKDAsMCwwLC4yNik7bGVmdDo3cHg7d2lkdGg6Y2FsYygxMDAlIC0gMTRweCl9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnNsaWRlci10cmFja1tfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5zbGlkZXItdHJhY2tbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQ6cmdiYSgyNTUsMjU1LDI1NSwuMyl9LnNsaWRlci10cmFjay1maWxsW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kOiNmNTdjMDB9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnNsaWRlci10cmFjay1maWxsW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnNsaWRlci10cmFjay1maWxsW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kOiNlZjZjMDB9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxuc2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuc3RvcmU9ZSx0aGlzLmRhdGFTb3VyY2U9aSx0aGlzLmZ1bGxXaWR0aENoYW5nZWQ9bmV3IEcsdGhpcy5waW5TdGF0ZUNoYW5nZWQ9bmV3IEcsdGhpcy5icmlnaHRuZXNzSW5NaWxsaSQ9dGhpcy5zdG9yZS5zZWxlY3Qoa0kpLHRoaXMuY29udHJhc3RJbk1pbGxpJD10aGlzLnN0b3JlLnNlbGVjdChGSSksdGhpcy5hY3R1YWxTaXplR2xvYmFsU2V0dGluZyQ9dGhpcy5zdG9yZS5zZWxlY3QoTkkpLHRoaXMuc2hvd0FjdHVhbFNpemU9ITEsdGhpcy5hY3R1YWxTaXplVWlUb2dnbGVkPSExLHRoaXMuYWN0dWFsU2l6ZVVpVG9nZ2xlU3ViamVjdD1uZXcgaHIodGhpcy5hY3R1YWxTaXplVWlUb2dnbGVkKSx0aGlzLm5nVW5zdWJzY3JpYmU9bmV3IGtlfW9uU3RlcEluZGV4Q2hhbmdlZChlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKG9SKHtjYXJkSWQ6dGhpcy5jYXJkSWQsc3RlcEluZGV4OmV9KSl9aXNJbWFnZUNhcmRNZXRhZGF0YShlKXtsZXR7cGx1Z2luOml9PWU7cmV0dXJuIGk9PT1yaS5JTUFHRVN9b25BY3R1YWxTaXplVG9nZ2xlKCl7dGhpcy5hY3R1YWxTaXplVWlUb2dnbGVkPSF0aGlzLmFjdHVhbFNpemVVaVRvZ2dsZWQsdGhpcy5hY3R1YWxTaXplVWlUb2dnbGVTdWJqZWN0Lm5leHQodGhpcy5hY3R1YWxTaXplVWlUb2dnbGVkKX1uZ09uSW5pdCgpe0x0KFt0aGlzLmFjdHVhbFNpemVHbG9iYWxTZXR0aW5nJCx0aGlzLmFjdHVhbFNpemVVaVRvZ2dsZVN1YmplY3RdKS5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSksa3QoKFtsLGNdKT0+e3RoaXMuc2hvd0FjdHVhbFNpemU9bHx8Yyx0aGlzLmZ1bGxXaWR0aENoYW5nZWQuZW1pdCh0aGlzLnNob3dBY3R1YWxTaXplKX0pKS5zdWJzY3JpYmUoKCk9Pnt9KTtsZXQgaT10aGlzLnN0b3JlLnNlbGVjdCh0Yyx0aGlzLmNhcmRJZCkucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpLFllKGw9PiEhbCYmdGhpcy5pc0ltYWdlQ2FyZE1ldGFkYXRhKGwpKSxMKGw9PmwpLE1hKDEpKSxvPUx0KFtpLHRoaXMuc3RvcmUuc2VsZWN0KHhoLHRoaXMuY2FyZElkKV0pLnBpcGUoc3QodGhpcy5uZ1Vuc3Vic2NyaWJlKSxMKChbbCxjXSk9PntsZXQgdT1sLnJ1bklkO3JldHVybiBjJiZjLmhhc093blByb3BlcnR5KHUpP2NbdV06W119KSx5aSgobCxjKT0+bC5sZW5ndGg9PT1jLmxlbmd0aCYmMD09PWwubGVuZ3RofHxsPT09YyksTWEoMSkpO3RoaXMuc3RlcEluZGV4JD10aGlzLnN0b3JlLnNlbGVjdCh0SCx0aGlzLmNhcmRJZCkucGlwZShMKGw9Pmw/bC5pbmRleDpudWxsKSksdGhpcy5pc0Nsb3Nlc3RTdGVwSGlnaGxpZ2h0ZWQkPXRoaXMuc3RvcmUuc2VsZWN0KHRILHRoaXMuY2FyZElkKS5waXBlKEwobD0+ISFsJiZsLmlzQ2xvc2VzdCkpLHRoaXMubG9hZFN0YXRlJD10aGlzLnN0b3JlLnNlbGVjdChiaCx0aGlzLmNhcmRJZCksdGhpcy50YWckPWkucGlwZShMKGw9PmwudGFnKSksdGhpcy50aXRsZSQ9dGhpcy50YWckLnBpcGUoTChsPT5seShsLHRoaXMuZ3JvdXBOYW1lKSkpLHRoaXMucnVuSWQkPWkucGlwZShMKGw9PmwucnVuSWQpKSx0aGlzLnNhbXBsZSQ9aS5waXBlKEwobD0+bC5zYW1wbGUpKSx0aGlzLm51bVNhbXBsZSQ9aS5waXBlKEwobD0+bC5udW1TYW1wbGUpKSx0aGlzLnN0ZXBzJD10aGlzLnN0b3JlLnNlbGVjdChnZWUsdGhpcy5jYXJkSWQpLHRoaXMuaXNQaW5uZWQkPXRoaXMuc3RvcmUuc2VsZWN0KENoLHRoaXMuY2FyZElkKSx0aGlzLmxpbmtlZFRpbWVTZWxlY3Rpb24kPXRoaXMuc3RvcmUuc2VsZWN0KFhtKS5waXBlKGZyKHRoaXMuc3RlcHMkKSxMKChbbCxjXSk9Pmw/UWgobCxNYXRoLm1pbiguLi5jKSxNYXRoLm1heCguLi5jKSk6bnVsbCkpLHRoaXMuc2VsZWN0ZWRTdGVwcyQ9dGhpcy5saW5rZWRUaW1lU2VsZWN0aW9uJC5waXBlKGZyKHRoaXMuc3RlcHMkKSxMKChbbCxjXSk9Pmw/bnVsbD09PWwuZW5kU3RlcD8tMSE9PWMuaW5kZXhPZihsLnN0YXJ0U3RlcCk/W2wuc3RhcnRTdGVwXTpbXTpjLmZpbHRlcih1PT51Pj1sLnN0YXJ0U3RlcCYmdTw9bC5lbmRTdGVwKTpbXSkpO2xldCBhPUx0KFtvLHRoaXMuc3RlcEluZGV4JF0pLnBpcGUoTCgoW2wsY10pPT5udWxsIT09YyYmbFtjXT9sW2NdOm51bGwpKTt0aGlzLmltYWdlVXJsJD1hLnBpcGUoTChsPT5sP3RoaXMuZGF0YVNvdXJjZS5pbWFnZVVybChsLmltYWdlSWQpOm51bGwpKX1uZ09uRGVzdHJveSgpe3RoaXMubmdVbnN1YnNjcmliZS5uZXh0KCksdGhpcy5uZ1Vuc3Vic2NyaWJlLmNvbXBsZXRlKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpLE0oJHUpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJpbWFnZS1jYXJkIl1dLGlucHV0czp7Y2FyZElkOiJjYXJkSWQiLGdyb3VwTmFtZToiZ3JvdXBOYW1lIixydW5Db2xvclNjYWxlOiJydW5Db2xvclNjYWxlIn0sb3V0cHV0czp7ZnVsbFdpZHRoQ2hhbmdlZDoiZnVsbFdpZHRoQ2hhbmdlZCIscGluU3RhdGVDaGFuZ2VkOiJwaW5TdGF0ZUNoYW5nZWQifSxkZWNsczoxNyx2YXJzOjUwLGNvbnN0czpbWzMsImxvYWRTdGF0ZSIsInRpdGxlIiwidGFnIiwicnVuSWQiLCJzYW1wbGUiLCJudW1TYW1wbGUiLCJpbWFnZVVybCIsInN0ZXBJbmRleCIsInN0ZXBzIiwiaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkIiwiYnJpZ2h0bmVzc0luTWlsbGkiLCJjb250cmFzdEluTWlsbGkiLCJydW5Db2xvclNjYWxlIiwic2hvd0FjdHVhbFNpemUiLCJhbGxvd1RvZ2dsZUFjdHVhbFNpemUiLCJpc1Bpbm5lZCIsImxpbmtlZFRpbWVTZWxlY3Rpb24iLCJzZWxlY3RlZFN0ZXBzIiwic3RlcEluZGV4Q2hhbmdlIiwib25BY3R1YWxTaXplVG9nZ2xlIiwib25QaW5DbGlja2VkIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJpbWFnZS1jYXJkLWNvbXBvbmVudCIsMCksUCgic3RlcEluZGV4Q2hhbmdlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblN0ZXBJbmRleENoYW5nZWQobyl9KSgib25BY3R1YWxTaXplVG9nZ2xlIixmdW5jdGlvbigpe3JldHVybiBpLm9uQWN0dWFsU2l6ZVRvZ2dsZSgpfSkoIm9uUGluQ2xpY2tlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkucGluU3RhdGVDaGFuZ2VkLmVtaXQobyl9KSxCKDEsImFzeW5jIiksQigyLCJhc3luYyIpLEIoMywiYXN5bmMiKSxCKDQsImFzeW5jIiksQig1LCJhc3luYyIpLEIoNiwiYXN5bmMiKSxCKDcsImFzeW5jIiksQig4LCJhc3luYyIpLEIoOSwiYXN5bmMiKSxCKDEwLCJhc3luYyIpLEIoMTEsImFzeW5jIiksQigxMiwiYXN5bmMiKSxCKDEzLCJhc3luYyIpLEIoMTQsImFzeW5jIiksQigxNSwiYXN5bmMiKSxCKDE2LCJhc3luYyIpLHYoKSksMiZlJiZ5KCJsb2FkU3RhdGUiLFUoMSwxOCxpLmxvYWRTdGF0ZSQpKSgidGl0bGUiLFUoMiwyMCxpLnRpdGxlJCkpKCJ0YWciLFUoMywyMixpLnRhZyQpKSgicnVuSWQiLFUoNCwyNCxpLnJ1bklkJCkpKCJzYW1wbGUiLFUoNSwyNixpLnNhbXBsZSQpKSgibnVtU2FtcGxlIixVKDYsMjgsaS5udW1TYW1wbGUkKSkoImltYWdlVXJsIixVKDcsMzAsaS5pbWFnZVVybCQpKSgic3RlcEluZGV4IixVKDgsMzIsaS5zdGVwSW5kZXgkKSkoInN0ZXBzIixVKDksMzQsaS5zdGVwcyQpKSgiaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkIixVKDEwLDM2LGkuaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkJCkpKCJicmlnaHRuZXNzSW5NaWxsaSIsVSgxMSwzOCxpLmJyaWdodG5lc3NJbk1pbGxpJCkpKCJjb250cmFzdEluTWlsbGkiLFUoMTIsNDAsaS5jb250cmFzdEluTWlsbGkkKSkoInJ1bkNvbG9yU2NhbGUiLGkucnVuQ29sb3JTY2FsZSkoInNob3dBY3R1YWxTaXplIixpLnNob3dBY3R1YWxTaXplKSgiYWxsb3dUb2dnbGVBY3R1YWxTaXplIiwhMT09PVUoMTMsNDIsaS5hY3R1YWxTaXplR2xvYmFsU2V0dGluZyQpKSgiaXNQaW5uZWQiLFUoMTQsNDQsaS5pc1Bpbm5lZCQpKSgibGlua2VkVGltZVNlbGVjdGlvbiIsVSgxNSw0NixpLmxpbmtlZFRpbWVTZWxlY3Rpb24kKSkoInNlbGVjdGVkU3RlcHMiLFUoMTYsNDgsaS5zZWxlY3RlZFN0ZXBzJCkpfSxkZXBlbmRlbmNpZXM6W3RzZSxHZV0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXSB7XG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG4gICAgICAgIGhlaWdodDogMTAwJTtcbiAgICAgIH0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLGRyPSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5TVkc9MF09IlNWRyIsbltuLldFQkdMPTFdPSJXRUJHTCJ9KGRyfHwoZHI9e30pKSxkcikpKCksTnI9KCgpPT4oZnVuY3Rpb24obil7bltuLkxJTkVBUj0wXT0iTElORUFSIixuW24uTE9HMTA9MV09IkxPRzEwIixuW24uVElNRT0yXT0iVElNRSJ9KE5yfHwoTnI9e30pKSxOcikpKCksR3I9KCgpPT4oZnVuY3Rpb24obil7bi5OT05FPSJOT05FIixuLkRSQUdfWk9PTUlORz0iRFJBR19aT09NSU5HIixuLlNDUk9MTF9aT09NSU5HPSJTQ1JPTExfWk9PTUlORyIsbi5QQU5OSU5HPSJQQU5OSU5HIn0oR3J8fChHcj17fSkpLEdyKSkoKTtmdW5jdGlvbiBkNGUobix0KXsxJm4mJihfKDAsInNwYW4iKSxBKDEsInNjYWxhciIpLHYoKSl9ZnVuY3Rpb24gcDRlKG4sdCl7MSZuJiYoXygwLCJzcGFuIiksQSgxLCJoaXN0b2dyYW0iKSx2KCkpfWZ1bmN0aW9uIGg0ZShuLHQpezEmbiYmKF8oMCwic3BhbiIpLEEoMSwidW5rbm93biIpLHYoKSl9ZnVuY3Rpb24gZjRlKG4sdCl7aWYoMSZuJiYoc24oMCwxMyksRSgxLGQ0ZSwyLDAsInNwYW4iLDE0KSxFKDIscDRlLDIsMCwic3BhbiIsMTQpLEUoMyxoNGUsMiwwLCJzcGFuIiwxNSksYW4oKSksMiZuKXtsZXQgZT1TKDIpO3koIm5nU3dpdGNoIixlLmNhcmRNZXRhZGF0YS5wbHVnaW4pLEMoMSkseSgibmdTd2l0Y2hDYXNlIixlLlBsdWdpblR5cGUuU0NBTEFSUyksQygxKSx5KCJuZ1N3aXRjaENhc2UiLGUuUGx1Z2luVHlwZS5ISVNUT0dSQU1TKX19ZnVuY3Rpb24gbTRlKG4sdCl7MSZuJiZOaSgwKX1mdW5jdGlvbiBnNGUobix0KXtpZigxJm4mJihfKDAsIm9wdGlvbiIsMTYpLEEoMSksdigpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0O3koInZhbHVlIixlLmlkKSxDKDEpLHl0KGUubmFtZSl9fWZ1bmN0aW9uIF80ZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtzbigwKSxfKDEsImgyIiksRSgyLGY0ZSw0LDMsIm5nLXRlbXBsYXRlIixudWxsLDIscXQpLF8oNCwic3BhbiIpLEEoNSwiRG93bmxvYWRceGEwIiksdigpLEUoNixtNGUsMSwwLCJuZy1jb250YWluZXIiLDMpLF8oNywic3BhbiIpLEEoOCwiXHhhMGRhdGEgZm9yXHhhMCIpLHYoKSxfKDksImNvZGUiLDQpLEEoMTApLHYoKSgpLF8oMTEsIm1hdC1kaWFsb2ctY29udGVudCIpKDEyLCJtYXQtZm9ybS1maWVsZCIsNSkoMTMsIm1hdC1sYWJlbCIpLEEoMTQsIlNlbGVjdCBhIHJ1biB0byBkb3dubG9hZCBhIGRhdGEgZm9yIGEgc2VyaWVzIiksdigpLF8oMTUsInNlbGVjdCIsNiksUCgiY2hhbmdlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLnJ1blNlbGVjdGVkLmVtaXQoci50YXJnZXQudmFsdWUpKX0pLF8oMTYsIm9wdGlvbiIsNyksQSgxNywiLSIpLHYoKSxFKDE4LGc0ZSwyLDIsIm9wdGlvbiIsOCksdigpKCksXygxOSwiZGl2Iiw5KSgyMCwic3BhbiIpLEEoMjEsIkRvd25sb2FkIGFzXHUyMDI2IiksdigpLEEoMjIsIlx4YTAiKSxfKDIzLCJhIiwxMCksQSgyNCwiSlNPTiIpLHYoKSxfKDI1LCJhIiwxMCksQSgyNiwiQ1NWIiksdigpKCkoKSxfKDI3LCJtYXQtZGlhbG9nLWFjdGlvbnMiLDExKSgyOCwiYnV0dG9uIiwxMiksQSgyOSwiQ2xvc2UiKSx2KCkoKSxhbigpfWlmKDImbil7bGV0IGU9JGUoMyksaT1TKCk7Qyg2KSx5KCJuZ1RlbXBsYXRlT3V0bGV0IixlKSxDKDMpLHkoInRpdGxlIixpLmNhcmRNZXRhZGF0YS50YWcpLEMoMSkseXQoaS5jYXJkTWV0YWRhdGEudGFnKSxDKDUpLHkoInZhbHVlIixpLnNlbGVjdGVkUnVuSWR8fCIiKSxDKDEpLHkoInZhbHVlIiwiIiksQygyKSx5KCJuZ0Zvck9mIixpLnJ1bnMpLEMoNSkseSgiZGlzYWJsZWQiLCFpLmRvd25sb2FkVXJsSnNvbikoImRvd25sb2FkIixpLmdldERvd25sb2FkTmFtZSgianNvbiIpKSx6ZSgiaHJlZiIsaS5kb3dubG9hZFVybEpzb24semwpLEMoMikseSgiZGlzYWJsZWQiLCFpLmRvd25sb2FkVXJsQ3N2KSgiZG93bmxvYWQiLGkuZ2V0RG93bmxvYWROYW1lKCJjc3YiKSksemUoImhyZWYiLGkuZG93bmxvYWRVcmxDc3YsemwpfX1mdW5jdGlvbiB2NGUobix0KXsxJm4mJkEoMCwiTG9hZGluZy4uLiIpfXZhciByc2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMucnVuU2VsZWN0ZWQ9bmV3IEcsdGhpcy5QbHVnaW5UeXBlPXJpfWdldERvd25sb2FkTmFtZShlKXtsZXQgaT10aGlzLnJ1bnMuZmluZChyPT5yLmlkPT09dGhpcy5zZWxlY3RlZFJ1bklkKTtyZXR1cm4gaT9gJHtpLm5hbWV9LiR7ZX1gOiIifX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJkYXRhX2Rvd25sb2FkX2RpYWxvZ19jb21wb25lbnQiXV0saW5wdXRzOntjYXJkTWV0YWRhdGE6ImNhcmRNZXRhZGF0YSIscnVuczoicnVucyIsc2VsZWN0ZWRSdW5JZDoic2VsZWN0ZWRSdW5JZCIsZG93bmxvYWRVcmxDc3Y6ImRvd25sb2FkVXJsQ3N2Iixkb3dubG9hZFVybEpzb246ImRvd25sb2FkVXJsSnNvbiJ9LG91dHB1dHM6e3J1blNlbGVjdGVkOiJydW5TZWxlY3RlZCJ9LGRlY2xzOjMsdmFyczoyLGNvbnN0czpbWzQsIm5nSWYiLCJuZ0lmRWxzZSJdLFsibm9DYXJkTWV0YWRhdGEiLCIiXSxbImRhdGFOYW1lIiwiIl0sWzQsIm5nVGVtcGxhdGVPdXRsZXQiXSxbMSwidGFnLW5hbWUiLDMsInRpdGxlIl0sWyJhcHBlYXJhbmNlIiwiZmlsbCIsMSwicnVuLXNlbGVjdG9yIl0sWyJtYXROYXRpdmVDb250cm9sIiwiIiwibmFtZSIsInJ1biIsImNka0ZvY3VzSW5pdGlhbCIsIiIsInJlcXVpcmVkIiwiIiwzLCJ2YWx1ZSIsImNoYW5nZSJdLFsic2VsZWN0ZWQiLCIiLDMsInZhbHVlIl0sWzMsInZhbHVlIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbMSwiZG93bmxvYWQtY29udHJvbHMiXSxbIm1hdC1zdHJva2VkLWJ1dHRvbiIsIiIsMywiZGlzYWJsZWQiLCJkb3dubG9hZCJdLFsiYWxpZ24iLCJlbmQiXSxbIm1hdC1idXR0b24iLCIiLCJtYXQtZGlhbG9nLWNsb3NlIiwiIl0sWzMsIm5nU3dpdGNoIl0sWzQsIm5nU3dpdGNoQ2FzZSJdLFs0LCJOZ1N3aXRjaERlZmF1bHQiXSxbMywidmFsdWUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJihFKDAsXzRlLDMwLDEyLCJuZy1jb250YWluZXIiLDApLEUoMSx2NGUsMSwwLCJuZy10ZW1wbGF0ZSIsbnVsbCwxLHF0KSksMiZlKXtsZXQgcj0kZSgyKTt5KCJuZ0lmIixpLmNhcmRNZXRhZGF0YSkoIm5nSWZFbHNlIixyKX19LGRlcGVuZGVuY2llczpbZG4sQmUsb3MsQ3IsVXIsRG5lLEluZSxfbixJdixUMixEMixBMixwZCxOdixVaF0sc3R5bGVzOlsiaDJbX25nY29udGVudC0lQ09NUCVde2ZvbnQtc2l6ZToxLjI1ZW07b3ZlcmZsb3ctd3JhcDpicmVhay13b3JkfS5ydW4tc2VsZWN0b3JbX25nY29udGVudC0lQ09NUCVde2ZvbnQtc2l6ZTouOWVtO3dpZHRoOjEwMCV9LmRvd25sb2FkLWNvbnRyb2xzW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXNpemU6LjllbX0uZG93bmxvYWQtY29udHJvbHNbX25nY29udGVudC0lQ09NUCVdICAgYVtfbmdjb250ZW50LSVDT01QJV17bWFyZ2luOjNweCAxMHB4IDNweCAwfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksb3NlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIpe3RoaXMuc2VsZWN0ZWRSdW5JZCQ9bmV3IGhyKG51bGwpLHRoaXMuY2FyZE1ldGFkYXRhJD1lLnNlbGVjdCh0YyxyLmNhcmRJZCkucGlwZShZZShvPT5Cb29sZWFuKG8pKSksdGhpcy5kb3dubG9hZFVybENzdiQ9THQoW2Uuc2VsZWN0KHRjLHIuY2FyZElkKSx0aGlzLnNlbGVjdGVkUnVuSWQkXSkucGlwZShMKChbbyxzXSk9Pm8mJnM/aS5kb3dubG9hZFVybChvLnBsdWdpbixvLnRhZyxzLCJjc3YiKTpudWxsKSx6bihudWxsKSksdGhpcy5kb3dubG9hZFVybEpzb24kPUx0KFtlLnNlbGVjdCh0YyxyLmNhcmRJZCksdGhpcy5zZWxlY3RlZFJ1bklkJF0pLnBpcGUoTCgoW28sc10pPT5vJiZzP2kuZG93bmxvYWRVcmwoby5wbHVnaW4sby50YWcscywianNvbiIpOm51bGwpLHpuKG51bGwpKSx0aGlzLnJ1bnMkPUx0KFtlLnNlbGVjdChxSSksZS5zZWxlY3QoeGgsci5jYXJkSWQpXSkucGlwZShMKChbbyxzXSk9PnM/T2JqZWN0LmtleXMocykubWFwKGE9Pm8uZ2V0KGEpKS5maWx0ZXIoQm9vbGVhbik6W10pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSksTSgkdSksTShjdykpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImRhdGFfZG93bmxvYWRfZGlhbG9nIl1dLGRlY2xzOjYsdmFyczoxNSxjb25zdHM6W1szLCJjYXJkTWV0YWRhdGEiLCJydW5zIiwic2VsZWN0ZWRSdW5JZCIsImRvd25sb2FkVXJsQ3N2IiwiZG93bmxvYWRVcmxKc29uIiwicnVuU2VsZWN0ZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRhdGFfZG93bmxvYWRfZGlhbG9nX2NvbXBvbmVudCIsMCksUCgicnVuU2VsZWN0ZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLnNlbGVjdGVkUnVuSWQkLm5leHQobyl9KSxCKDEsImFzeW5jIiksQigyLCJhc3luYyIpLEIoMywiYXN5bmMiKSxCKDQsImFzeW5jIiksQig1LCJhc3luYyIpLHYoKSksMiZlJiZ5KCJjYXJkTWV0YWRhdGEiLFUoMSw1LGkuY2FyZE1ldGFkYXRhJCkpKCJydW5zIixVKDIsNyxpLnJ1bnMkKSkoInNlbGVjdGVkUnVuSWQiLFUoMyw5LGkuc2VsZWN0ZWRSdW5JZCQpKSgiZG93bmxvYWRVcmxDc3YiLFUoNCwxMSxpLmRvd25sb2FkVXJsQ3N2JCkpKCJkb3dubG9hZFVybEpzb24iLFUoNSwxMyxpLmRvd25sb2FkVXJsSnNvbiQpKX0sZGVwZW5kZW5jaWVzOltyc2UsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIHVjKG4sdCl7cmV0dXJuIG48dD8tMTpuPnQ/MTpuPj10PzA6TmFOfWZ1bmN0aW9uIEJ3KG4pe3JldHVybiAxPT09bi5sZW5ndGgmJihuPWZ1bmN0aW9uKG4pe3JldHVybiBmdW5jdGlvbih0LGUpe3JldHVybiB1YyhuKHQpLGUpfX0obikpLHtsZWZ0OmZ1bmN0aW9uKHQsZSxpLHIpe2ZvcihudWxsPT1pJiYoaT0wKSxudWxsPT1yJiYocj10Lmxlbmd0aCk7aTxyOyl7dmFyIG89aStyPj4+MTtuKHRbb10sZSk8MD9pPW8rMTpyPW99cmV0dXJuIGl9LHJpZ2h0OmZ1bmN0aW9uKHQsZSxpLHIpe2ZvcihudWxsPT1pJiYoaT0wKSxudWxsPT1yJiYocj10Lmxlbmd0aCk7aTxyOyl7dmFyIG89aStyPj4+MTtuKHRbb10sZSk+MD9yPW86aT1vKzF9cmV0dXJuIGl9fX12YXIgaXU9QncodWMpLnJpZ2h0O2Z1bmN0aW9uIHhSKG4sdCl7dmFyIHIsbyxzLGU9bi5sZW5ndGgsaT0tMTtpZihudWxsPT10KXtmb3IoOysraTxlOylpZihudWxsIT0ocj1uW2ldKSYmcj49cilmb3Iobz1zPXI7KytpPGU7KW51bGwhPShyPW5baV0pJiYobz5yJiYobz1yKSxzPHImJihzPXIpKX1lbHNlIGZvcig7KytpPGU7KWlmKG51bGwhPShyPXQobltpXSxpLG4pKSYmcj49cilmb3Iobz1zPXI7KytpPGU7KW51bGwhPShyPXQobltpXSxpLG4pKSYmKG8+ciYmKG89ciksczxyJiYocz1yKSk7cmV0dXJuW28sc119dmFyIGNzZT1BcnJheS5wcm90b3R5cGUuc2xpY2U7ZnVuY3Rpb24gVncobil7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIG59fWZ1bmN0aW9uIHVzZShuKXtyZXR1cm4gbn1mdW5jdGlvbiBLaChuLHQsZSl7bj0rbix0PSt0LGU9KHI9YXJndW1lbnRzLmxlbmd0aCk8Mj8odD1uLG49MCwxKTpyPDM/MTorZTtmb3IodmFyIGk9LTEscj0wfE1hdGgubWF4KDAsTWF0aC5jZWlsKCh0LW4pL2UpKSxvPW5ldyBBcnJheShyKTsrK2k8cjspb1tpXT1uK2kqZTtyZXR1cm4gb312YXIgUlU9TWF0aC5zcXJ0KDUwKSxPVT1NYXRoLnNxcnQoMTApLGtVPU1hdGguc3FydCgyKTtmdW5jdGlvbiBIdyhuLHQsZSl7dmFyIGksbyxzLGEscj0tMTtpZihlPStlLChuPStuKT09KHQ9K3QpJiZlPjApcmV0dXJuW25dO2lmKChpPXQ8bikmJihvPW4sbj10LHQ9byksMD09PShhPXB5KG4sdCxlKSl8fCFpc0Zpbml0ZShhKSlyZXR1cm5bXTtpZihhPjApZm9yKG49TWF0aC5jZWlsKG4vYSksdD1NYXRoLmZsb29yKHQvYSkscz1uZXcgQXJyYXkobz1NYXRoLmNlaWwodC1uKzEpKTsrK3I8bzspc1tyXT0obityKSphO2Vsc2UgZm9yKG49TWF0aC5mbG9vcihuKmEpLHQ9TWF0aC5jZWlsKHQqYSkscz1uZXcgQXJyYXkobz1NYXRoLmNlaWwobi10KzEpKTsrK3I8bzspc1tyXT0obi1yKS9hO3JldHVybiBpJiZzLnJldmVyc2UoKSxzfWZ1bmN0aW9uIHB5KG4sdCxlKXt2YXIgaT0odC1uKS9NYXRoLm1heCgwLGUpLHI9TWF0aC5mbG9vcihNYXRoLmxvZyhpKS9NYXRoLkxOMTApLG89aS9NYXRoLnBvdygxMCxyKTtyZXR1cm4gcj49MD8obz49UlU/MTA6bz49T1U/NTpvPj1rVT8yOjEpKk1hdGgucG93KDEwLHIpOi1NYXRoLnBvdygxMCwtcikvKG8+PVJVPzEwOm8+PU9VPzU6bz49a1U/MjoxKX1mdW5jdGlvbiB2ZChuLHQsZSl7dmFyIGk9TWF0aC5hYnModC1uKS9NYXRoLm1heCgwLGUpLHI9TWF0aC5wb3coMTAsTWF0aC5mbG9vcihNYXRoLmxvZyhpKS9NYXRoLkxOMTApKSxvPWkvcjtyZXR1cm4gbz49UlU/cio9MTA6bz49T1U/cio9NTpvPj1rVSYmKHIqPTIpLHQ8bj8tcjpyfWZ1bmN0aW9uIENSKG4pe3JldHVybiBNYXRoLmNlaWwoTWF0aC5sb2cobi5sZW5ndGgpL01hdGguTE4yKSsxfWZ1bmN0aW9uIE1SKCl7dmFyIG49dXNlLHQ9eFIsZT1DUjtmdW5jdGlvbiBpKHIpe3ZhciBvLGEscz1yLmxlbmd0aCxsPW5ldyBBcnJheShzKTtmb3Iobz0wO288czsrK28pbFtvXT1uKHJbb10sbyxyKTt2YXIgYz10KGwpLHU9Y1swXSxkPWNbMV0scD1lKGwsdSxkKTtBcnJheS5pc0FycmF5KHApfHwocD12ZCh1LGQscCkscD1LaChNYXRoLmNlaWwodS9wKSpwLGQscCkpO2Zvcih2YXIgaD1wLmxlbmd0aDtwWzBdPD11OylwLnNoaWZ0KCksLS1oO2Zvcig7cFtoLTFdPmQ7KXAucG9wKCksLS1oO3ZhciBtLGY9bmV3IEFycmF5KGgrMSk7Zm9yKG89MDtvPD1oOysrbykobT1mW29dPVtdKS54MD1vPjA/cFtvLTFdOnUsbS54MT1vPGg/cFtvXTpkO2ZvcihvPTA7bzxzOysrbyl1PD0oYT1sW29dKSYmYTw9ZCYmZltpdShwLGEsMCxoKV0ucHVzaChyW29dKTtyZXR1cm4gZn1yZXR1cm4gaS52YWx1ZT1mdW5jdGlvbihyKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj0iZnVuY3Rpb24iPT10eXBlb2Ygcj9yOlZ3KHIpLGkpOm59LGkuZG9tYWluPWZ1bmN0aW9uKHIpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PSJmdW5jdGlvbiI9PXR5cGVvZiByP3I6VncoW3JbMF0sclsxXV0pLGkpOnR9LGkudGhyZXNob2xkcz1mdW5jdGlvbihyKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT0iZnVuY3Rpb24iPT10eXBlb2Ygcj9yOkFycmF5LmlzQXJyYXkocik/VncoY3NlLmNhbGwocikpOlZ3KHIpLGkpOmV9LGl9dmFyIHdSPUFycmF5LnByb3RvdHlwZS5zbGljZTtmdW5jdGlvbiBkc2Uobil7cmV0dXJuIG59ZnVuY3Rpb24gRDRlKG4pe3JldHVybiJ0cmFuc2xhdGUoIisobisuNSkrIiwwKSJ9ZnVuY3Rpb24gQTRlKG4pe3JldHVybiJ0cmFuc2xhdGUoMCwiKyhuKy41KSsiKSJ9ZnVuY3Rpb24gSTRlKG4pe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4rbih0KX19ZnVuY3Rpb24gUDRlKG4pe3ZhciB0PU1hdGgubWF4KDAsbi5iYW5kd2lkdGgoKS0xKS8yO3JldHVybiBuLnJvdW5kKCkmJih0PU1hdGgucm91bmQodCkpLGZ1bmN0aW9uKGUpe3JldHVybituKGUpK3R9fWZ1bmN0aW9uIFI0ZSgpe3JldHVybiF0aGlzLl9fYXhpc31mdW5jdGlvbiBCVShuLHQpe3ZhciBlPVtdLGk9bnVsbCxyPW51bGwsbz02LHM9NixhPTMsbD0xPT09bnx8ND09PW4/LTE6MSxjPTQ9PT1ufHwyPT09bj8ieCI6InkiLHU9MT09PW58fDM9PT1uP0Q0ZTpBNGU7ZnVuY3Rpb24gZChwKXt2YXIgaD1pPz8odC50aWNrcz90LnRpY2tzLmFwcGx5KHQsZSk6dC5kb21haW4oKSksZj1yPz8odC50aWNrRm9ybWF0P3QudGlja0Zvcm1hdC5hcHBseSh0LGUpOmRzZSksbT1NYXRoLm1heChvLDApK2EseD10LnJhbmdlKCksZz0reFswXSsuNSxiPSt4W3gubGVuZ3RoLTFdKy41LEQ9KHQuYmFuZHdpZHRoP1A0ZTpJNGUpKHQuY29weSgpKSxUPXAuc2VsZWN0aW9uP3Auc2VsZWN0aW9uKCk6cCxrPVQuc2VsZWN0QWxsKCIuZG9tYWluIikuZGF0YShbbnVsbF0pLFo9VC5zZWxlY3RBbGwoIi50aWNrIikuZGF0YShoLHQpLm9yZGVyKCksej1aLmV4aXQoKSxmZT1aLmVudGVyKCkuYXBwZW5kKCJnIikuYXR0cigiY2xhc3MiLCJ0aWNrIiksdWU9Wi5zZWxlY3QoImxpbmUiKSxoZT1aLnNlbGVjdCgidGV4dCIpO2s9ay5tZXJnZShrLmVudGVyKCkuaW5zZXJ0KCJwYXRoIiwiLnRpY2siKS5hdHRyKCJjbGFzcyIsImRvbWFpbiIpLmF0dHIoInN0cm9rZSIsImN1cnJlbnRDb2xvciIpKSxaPVoubWVyZ2UoZmUpLHVlPXVlLm1lcmdlKGZlLmFwcGVuZCgibGluZSIpLmF0dHIoInN0cm9rZSIsImN1cnJlbnRDb2xvciIpLmF0dHIoYysiMiIsbCpvKSksaGU9aGUubWVyZ2UoZmUuYXBwZW5kKCJ0ZXh0IikuYXR0cigiZmlsbCIsImN1cnJlbnRDb2xvciIpLmF0dHIoYyxsKm0pLmF0dHIoImR5IiwxPT09bj8iMGVtIjozPT09bj8iMC43MWVtIjoiMC4zMmVtIikpLHAhPT1UJiYoaz1rLnRyYW5zaXRpb24ocCksWj1aLnRyYW5zaXRpb24ocCksdWU9dWUudHJhbnNpdGlvbihwKSxoZT1oZS50cmFuc2l0aW9uKHApLHo9ei50cmFuc2l0aW9uKHApLmF0dHIoIm9wYWNpdHkiLDFlLTYpLmF0dHIoInRyYW5zZm9ybSIsZnVuY3Rpb24odyl7cmV0dXJuIGlzRmluaXRlKHc9RCh3KSk/dSh3KTp0aGlzLmdldEF0dHJpYnV0ZSgidHJhbnNmb3JtIil9KSxmZS5hdHRyKCJvcGFjaXR5IiwxZS02KS5hdHRyKCJ0cmFuc2Zvcm0iLGZ1bmN0aW9uKHcpe3ZhciBGPXRoaXMucGFyZW50Tm9kZS5fX2F4aXM7cmV0dXJuIHUoRiYmaXNGaW5pdGUoRj1GKHcpKT9GOkQodykpfSkpLHoucmVtb3ZlKCksay5hdHRyKCJkIiw0PT09bnx8Mj09bj9zPyJNIitsKnMrIiwiK2crIkgwLjVWIitiKyJIIitsKnM6Ik0wLjUsIitnKyJWIitiOnM/Ik0iK2crIiwiK2wqcysiVjAuNUgiK2IrIlYiK2wqczoiTSIrZysiLDAuNUgiK2IpLFouYXR0cigib3BhY2l0eSIsMSkuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbih3KXtyZXR1cm4gdShEKHcpKX0pLHVlLmF0dHIoYysiMiIsbCpvKSxoZS5hdHRyKGMsbCptKS50ZXh0KGYpLFQuZmlsdGVyKFI0ZSkuYXR0cigiZmlsbCIsIm5vbmUiKS5hdHRyKCJmb250LXNpemUiLDEwKS5hdHRyKCJmb250LWZhbWlseSIsInNhbnMtc2VyaWYiKS5hdHRyKCJ0ZXh0LWFuY2hvciIsMj09PW4/InN0YXJ0Ijo0PT09bj8iZW5kIjoibWlkZGxlIiksVC5lYWNoKGZ1bmN0aW9uKCl7dGhpcy5fX2F4aXM9RH0pfXJldHVybiBkLnNjYWxlPWZ1bmN0aW9uKHApe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXAsZCk6dH0sZC50aWNrcz1mdW5jdGlvbigpe3JldHVybiBlPXdSLmNhbGwoYXJndW1lbnRzKSxkfSxkLnRpY2tBcmd1bWVudHM9ZnVuY3Rpb24ocCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGU9bnVsbD09cD9bXTp3Ui5jYWxsKHApLGQpOmUuc2xpY2UoKX0sZC50aWNrVmFsdWVzPWZ1bmN0aW9uKHApe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPW51bGw9PXA/bnVsbDp3Ui5jYWxsKHApLGQpOmkmJmkuc2xpY2UoKX0sZC50aWNrRm9ybWF0PWZ1bmN0aW9uKHApe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPXAsZCk6cn0sZC50aWNrU2l6ZT1mdW5jdGlvbihwKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz1zPStwLGQpOm99LGQudGlja1NpemVJbm5lcj1mdW5jdGlvbihwKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obz0rcCxkKTpvfSxkLnRpY2tTaXplT3V0ZXI9ZnVuY3Rpb24ocCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHM9K3AsZCk6c30sZC50aWNrUGFkZGluZz1mdW5jdGlvbihwKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYT0rcCxkKTphfSxkfWZ1bmN0aW9uIHp3KG4pe3JldHVybiBCVSgyLG4pfWZ1bmN0aW9uIGp3KG4pe3JldHVybiBCVSgzLG4pfXZhciBPNGU9e3ZhbHVlOmZ1bmN0aW9uKCl7fX07ZnVuY3Rpb24gZnNlKCl7Zm9yKHZhciBpLG49MCx0PWFyZ3VtZW50cy5sZW5ndGgsZT17fTtuPHQ7KytuKXtpZighKGk9YXJndW1lbnRzW25dKyIiKXx8aSBpbiBlfHwvW1xzLl0vLnRlc3QoaSkpdGhyb3cgbmV3IEVycm9yKCJpbGxlZ2FsIHR5cGU6ICIraSk7ZVtpXT1bXX1yZXR1cm4gbmV3IEVSKGUpfWZ1bmN0aW9uIEVSKG4pe3RoaXMuXz1ufWZ1bmN0aW9uIGs0ZShuLHQpe3JldHVybiBuLnRyaW0oKS5zcGxpdCgvXnxccysvKS5tYXAoZnVuY3Rpb24oZSl7dmFyIGk9IiIscj1lLmluZGV4T2YoIi4iKTtpZihyPj0wJiYoaT1lLnNsaWNlKHIrMSksZT1lLnNsaWNlKDAscikpLGUmJiF0Lmhhc093blByb3BlcnR5KGUpKXRocm93IG5ldyBFcnJvcigidW5rbm93biB0eXBlOiAiK2UpO3JldHVybnt0eXBlOmUsbmFtZTppfX0pfWZ1bmN0aW9uIEY0ZShuLHQpe2Zvcih2YXIgcixlPTAsaT1uLmxlbmd0aDtlPGk7KytlKWlmKChyPW5bZV0pLm5hbWU9PT10KXJldHVybiByLnZhbHVlfWZ1bmN0aW9uIGhzZShuLHQsZSl7Zm9yKHZhciBpPTAscj1uLmxlbmd0aDtpPHI7KytpKWlmKG5baV0ubmFtZT09PXQpe25baV09TzRlLG49bi5zbGljZSgwLGkpLmNvbmNhdChuLnNsaWNlKGkrMSkpO2JyZWFrfXJldHVybiBudWxsIT1lJiZuLnB1c2goe25hbWU6dCx2YWx1ZTplfSksbn1FUi5wcm90b3R5cGU9ZnNlLnByb3RvdHlwZT17Y29uc3RydWN0b3I6RVIsb246ZnVuY3Rpb24obix0KXt2YXIgcixlPXRoaXMuXyxpPWs0ZShuKyIiLGUpLG89LTEscz1pLmxlbmd0aDtpZighKGFyZ3VtZW50cy5sZW5ndGg8Mikpe2lmKG51bGwhPXQmJiJmdW5jdGlvbiIhPXR5cGVvZiB0KXRocm93IG5ldyBFcnJvcigiaW52YWxpZCBjYWxsYmFjazogIit0KTtmb3IoOysrbzxzOylpZihyPShuPWlbb10pLnR5cGUpZVtyXT1oc2UoZVtyXSxuLm5hbWUsdCk7ZWxzZSBpZihudWxsPT10KWZvcihyIGluIGUpZVtyXT1oc2UoZVtyXSxuLm5hbWUsbnVsbCk7cmV0dXJuIHRoaXN9Zm9yKDsrK288czspaWYoKHI9KG49aVtvXSkudHlwZSkmJihyPUY0ZShlW3JdLG4ubmFtZSkpKXJldHVybiByfSxjb3B5OmZ1bmN0aW9uKCl7dmFyIG49e30sdD10aGlzLl87Zm9yKHZhciBlIGluIHQpbltlXT10W2VdLnNsaWNlKCk7cmV0dXJuIG5ldyBFUihuKX0sY2FsbDpmdW5jdGlvbihuLHQpe2lmKChyPWFyZ3VtZW50cy5sZW5ndGgtMik+MClmb3IodmFyIHIsbyxlPW5ldyBBcnJheShyKSxpPTA7aTxyOysraSllW2ldPWFyZ3VtZW50c1tpKzJdO2lmKCF0aGlzLl8uaGFzT3duUHJvcGVydHkobikpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHR5cGU6ICIrbik7Zm9yKGk9MCxyPShvPXRoaXMuX1tuXSkubGVuZ3RoO2k8cjsrK2kpb1tpXS52YWx1ZS5hcHBseSh0LGUpfSxhcHBseTpmdW5jdGlvbihuLHQsZSl7aWYoIXRoaXMuXy5oYXNPd25Qcm9wZXJ0eShuKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gdHlwZTogIituKTtmb3IodmFyIGk9dGhpcy5fW25dLHI9MCxvPWkubGVuZ3RoO3I8bzsrK3IpaVtyXS52YWx1ZS5hcHBseSh0LGUpfX07dmFyIEd3PWZzZSxUUj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIsSFU9e3N2ZzoiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLHhodG1sOlRSLHhsaW5rOiJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIix4bWw6Imh0dHA6Ly93d3cudzMub3JnL1hNTC8xOTk4L25hbWVzcGFjZSIseG1sbnM6Imh0dHA6Ly93d3cudzMub3JnLzIwMDAveG1sbnMvIn07ZnVuY3Rpb24gZHAobil7dmFyIHQ9bis9IiIsZT10LmluZGV4T2YoIjoiKTtyZXR1cm4gZT49MCYmInhtbG5zIiE9PSh0PW4uc2xpY2UoMCxlKSkmJihuPW4uc2xpY2UoZSsxKSksSFUuaGFzT3duUHJvcGVydHkodCk/e3NwYWNlOkhVW3RdLGxvY2FsOm59Om59ZnVuY3Rpb24gTjRlKG4pe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PXRoaXMub3duZXJEb2N1bWVudCxlPXRoaXMubmFtZXNwYWNlVVJJO3JldHVybiBlPT09VFImJnQuZG9jdW1lbnRFbGVtZW50Lm5hbWVzcGFjZVVSST09PVRSP3QuY3JlYXRlRWxlbWVudChuKTp0LmNyZWF0ZUVsZW1lbnROUyhlLG4pfX1mdW5jdGlvbiBMNGUobil7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMub3duZXJEb2N1bWVudC5jcmVhdGVFbGVtZW50TlMobi5zcGFjZSxuLmxvY2FsKX19ZnVuY3Rpb24gRFIobil7dmFyIHQ9ZHAobik7cmV0dXJuKHQubG9jYWw/TDRlOk40ZSkodCl9ZnVuY3Rpb24gQjRlKCl7fWZ1bmN0aW9uIGdnKG4pe3JldHVybiBudWxsPT1uP0I0ZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLnF1ZXJ5U2VsZWN0b3Iobil9fWZ1bmN0aW9uIFY0ZSgpe3JldHVybltdfWZ1bmN0aW9uIFd3KG4pe3JldHVybiBudWxsPT1uP1Y0ZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLnF1ZXJ5U2VsZWN0b3JBbGwobil9fWZ1bmN0aW9uIHF3KG4pe3JldHVybiBmdW5jdGlvbigpe3JldHVybiB0aGlzLm1hdGNoZXMobil9fWZ1bmN0aW9uIEFSKG4pe3JldHVybiBuZXcgQXJyYXkobi5sZW5ndGgpfWZ1bmN0aW9uIFl3KG4sdCl7dGhpcy5vd25lckRvY3VtZW50PW4ub3duZXJEb2N1bWVudCx0aGlzLm5hbWVzcGFjZVVSST1uLm5hbWVzcGFjZVVSSSx0aGlzLl9uZXh0PW51bGwsdGhpcy5fcGFyZW50PW4sdGhpcy5fX2RhdGFfXz10fWZ1bmN0aW9uIEg0ZShuLHQsZSxpLHIsbyl7Zm9yKHZhciBhLHM9MCxsPXQubGVuZ3RoLGM9by5sZW5ndGg7czxjOysrcykoYT10W3NdKT8oYS5fX2RhdGFfXz1vW3NdLGlbc109YSk6ZVtzXT1uZXcgWXcobixvW3NdKTtmb3IoO3M8bDsrK3MpKGE9dFtzXSkmJihyW3NdPWEpfWZ1bmN0aW9uIFU0ZShuLHQsZSxpLHIsbyxzKXt2YXIgYSxsLGgsYz17fSx1PXQubGVuZ3RoLGQ9by5sZW5ndGgscD1uZXcgQXJyYXkodSk7Zm9yKGE9MDthPHU7KythKShsPXRbYV0pJiYocFthXT1oPSIkIitzLmNhbGwobCxsLl9fZGF0YV9fLGEsdCksaCBpbiBjP3JbYV09bDpjW2hdPWwpO2ZvcihhPTA7YTxkOysrYSkobD1jW2g9IiQiK3MuY2FsbChuLG9bYV0sYSxvKV0pPyhpW2FdPWwsbC5fX2RhdGFfXz1vW2FdLGNbaF09bnVsbCk6ZVthXT1uZXcgWXcobixvW2FdKTtmb3IoYT0wO2E8dTsrK2EpKGw9dFthXSkmJmNbcFthXV09PT1sJiYoclthXT1sKX1mdW5jdGlvbiB6NGUobix0KXtyZXR1cm4gbjx0Py0xOm4+dD8xOm4+PXQ/MDpOYU59ZnVuY3Rpb24gajRlKG4pe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlKG4pfX1mdW5jdGlvbiBHNGUobil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGVOUyhuLnNwYWNlLG4ubG9jYWwpfX1mdW5jdGlvbiBXNGUobix0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZShuLHQpfX1mdW5jdGlvbiBxNGUobix0KXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnNldEF0dHJpYnV0ZU5TKG4uc3BhY2Usbi5sb2NhbCx0KX19ZnVuY3Rpb24gWTRlKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGU9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7bnVsbD09ZT90aGlzLnJlbW92ZUF0dHJpYnV0ZShuKTp0aGlzLnNldEF0dHJpYnV0ZShuLGUpfX1mdW5jdGlvbiBYNGUobix0KXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgZT10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtudWxsPT1lP3RoaXMucmVtb3ZlQXR0cmlidXRlTlMobi5zcGFjZSxuLmxvY2FsKTp0aGlzLnNldEF0dHJpYnV0ZU5TKG4uc3BhY2Usbi5sb2NhbCxlKX19ZnVuY3Rpb24gSVIobil7cmV0dXJuIG4ub3duZXJEb2N1bWVudCYmbi5vd25lckRvY3VtZW50LmRlZmF1bHRWaWV3fHxuLmRvY3VtZW50JiZufHxuLmRlZmF1bHRWaWV3fWZ1bmN0aW9uIFE0ZShuKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnN0eWxlLnJlbW92ZVByb3BlcnR5KG4pfX1mdW5jdGlvbiBLNGUobix0LGUpe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc3R5bGUuc2V0UHJvcGVydHkobix0LGUpfX1mdW5jdGlvbiBaNGUobix0LGUpe3JldHVybiBmdW5jdGlvbigpe3ZhciBpPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO251bGw9PWk/dGhpcy5zdHlsZS5yZW1vdmVQcm9wZXJ0eShuKTp0aGlzLnN0eWxlLnNldFByb3BlcnR5KG4saSxlKX19ZnVuY3Rpb24gWmgobix0KXtyZXR1cm4gbi5zdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKHQpfHxJUihuKS5nZXRDb21wdXRlZFN0eWxlKG4sbnVsbCkuZ2V0UHJvcGVydHlWYWx1ZSh0KX1mdW5jdGlvbiBKNGUobil7cmV0dXJuIGZ1bmN0aW9uKCl7ZGVsZXRlIHRoaXNbbl19fWZ1bmN0aW9uICQ0ZShuLHQpe3JldHVybiBmdW5jdGlvbigpe3RoaXNbbl09dH19ZnVuY3Rpb24gZUhlKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGU9dC5hcHBseSh0aGlzLGFyZ3VtZW50cyk7bnVsbD09ZT9kZWxldGUgdGhpc1tuXTp0aGlzW25dPWV9fWZ1bmN0aW9uIE5zZShuKXtyZXR1cm4gbi50cmltKCkuc3BsaXQoL158XHMrLyl9ZnVuY3Rpb24gVVUobil7cmV0dXJuIG4uY2xhc3NMaXN0fHxuZXcgTHNlKG4pfWZ1bmN0aW9uIExzZShuKXt0aGlzLl9ub2RlPW4sdGhpcy5fbmFtZXM9TnNlKG4uZ2V0QXR0cmlidXRlKCJjbGFzcyIpfHwiIil9ZnVuY3Rpb24gQnNlKG4sdCl7Zm9yKHZhciBlPVVVKG4pLGk9LTEscj10Lmxlbmd0aDsrK2k8cjspZS5hZGQodFtpXSl9ZnVuY3Rpb24gVnNlKG4sdCl7Zm9yKHZhciBlPVVVKG4pLGk9LTEscj10Lmxlbmd0aDsrK2k8cjspZS5yZW1vdmUodFtpXSl9ZnVuY3Rpb24gdEhlKG4pe3JldHVybiBmdW5jdGlvbigpe0JzZSh0aGlzLG4pfX1mdW5jdGlvbiBuSGUobil7cmV0dXJuIGZ1bmN0aW9uKCl7VnNlKHRoaXMsbil9fWZ1bmN0aW9uIGlIZShuLHQpe3JldHVybiBmdW5jdGlvbigpeyh0LmFwcGx5KHRoaXMsYXJndW1lbnRzKT9Cc2U6VnNlKSh0aGlzLG4pfX1mdW5jdGlvbiBySGUoKXt0aGlzLnRleHRDb250ZW50PSIifWZ1bmN0aW9uIG9IZShuKXtyZXR1cm4gZnVuY3Rpb24oKXt0aGlzLnRleHRDb250ZW50PW59fWZ1bmN0aW9uIHNIZShuKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD1uLmFwcGx5KHRoaXMsYXJndW1lbnRzKTt0aGlzLnRleHRDb250ZW50PXQ/PyIifX1mdW5jdGlvbiBhSGUoKXt0aGlzLmlubmVySFRNTD0iIn1mdW5jdGlvbiBsSGUobil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5pbm5lckhUTUw9bn19ZnVuY3Rpb24gY0hlKG4pe3JldHVybiBmdW5jdGlvbigpe3ZhciB0PW4uYXBwbHkodGhpcyxhcmd1bWVudHMpO3RoaXMuaW5uZXJIVE1MPXQ/PyIifX1mdW5jdGlvbiB1SGUoKXt0aGlzLm5leHRTaWJsaW5nJiZ0aGlzLnBhcmVudE5vZGUuYXBwZW5kQ2hpbGQodGhpcyl9ZnVuY3Rpb24gZEhlKCl7dGhpcy5wcmV2aW91c1NpYmxpbmcmJnRoaXMucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUodGhpcyx0aGlzLnBhcmVudE5vZGUuZmlyc3RDaGlsZCl9ZnVuY3Rpb24gcEhlKCl7cmV0dXJuIG51bGx9ZnVuY3Rpb24gaEhlKCl7dmFyIG49dGhpcy5wYXJlbnROb2RlO24mJm4ucmVtb3ZlQ2hpbGQodGhpcyl9ZnVuY3Rpb24gZkhlKCl7dmFyIG49dGhpcy5jbG9uZU5vZGUoITEpLHQ9dGhpcy5wYXJlbnROb2RlO3JldHVybiB0P3QuaW5zZXJ0QmVmb3JlKG4sdGhpcy5uZXh0U2libGluZyk6bn1mdW5jdGlvbiBtSGUoKXt2YXIgbj10aGlzLmNsb25lTm9kZSghMCksdD10aGlzLnBhcmVudE5vZGU7cmV0dXJuIHQ/dC5pbnNlcnRCZWZvcmUobix0aGlzLm5leHRTaWJsaW5nKTpufVl3LnByb3RvdHlwZT17Y29uc3RydWN0b3I6WXcsYXBwZW5kQ2hpbGQ6ZnVuY3Rpb24obil7cmV0dXJuIHRoaXMuX3BhcmVudC5pbnNlcnRCZWZvcmUobix0aGlzLl9uZXh0KX0saW5zZXJ0QmVmb3JlOmZ1bmN0aW9uKG4sdCl7cmV0dXJuIHRoaXMuX3BhcmVudC5pbnNlcnRCZWZvcmUobix0KX0scXVlcnlTZWxlY3RvcjpmdW5jdGlvbihuKXtyZXR1cm4gdGhpcy5fcGFyZW50LnF1ZXJ5U2VsZWN0b3Iobil9LHF1ZXJ5U2VsZWN0b3JBbGw6ZnVuY3Rpb24obil7cmV0dXJuIHRoaXMuX3BhcmVudC5xdWVyeVNlbGVjdG9yQWxsKG4pfX0sTHNlLnByb3RvdHlwZT17YWRkOmZ1bmN0aW9uKG4pe3RoaXMuX25hbWVzLmluZGV4T2Yobik8MCYmKHRoaXMuX25hbWVzLnB1c2gobiksdGhpcy5fbm9kZS5zZXRBdHRyaWJ1dGUoImNsYXNzIix0aGlzLl9uYW1lcy5qb2luKCIgIikpKX0scmVtb3ZlOmZ1bmN0aW9uKG4pe3ZhciB0PXRoaXMuX25hbWVzLmluZGV4T2Yobik7dD49MCYmKHRoaXMuX25hbWVzLnNwbGljZSh0LDEpLHRoaXMuX25vZGUuc2V0QXR0cmlidXRlKCJjbGFzcyIsdGhpcy5fbmFtZXMuam9pbigiICIpKSl9LGNvbnRhaW5zOmZ1bmN0aW9uKG4pe3JldHVybiB0aGlzLl9uYW1lcy5pbmRleE9mKG4pPj0wfX07dmFyIFpzZT17fSxzaT1udWxsO2Z1bmN0aW9uIGdIZShuLHQsZSl7cmV0dXJuIG49SnNlKG4sdCxlKSxmdW5jdGlvbihpKXt2YXIgcj1pLnJlbGF0ZWRUYXJnZXQ7KCFyfHxyIT09dGhpcyYmISg4JnIuY29tcGFyZURvY3VtZW50UG9zaXRpb24odGhpcykpKSYmbi5jYWxsKHRoaXMsaSl9fWZ1bmN0aW9uIEpzZShuLHQsZSl7cmV0dXJuIGZ1bmN0aW9uKGkpe3ZhciByPXNpO3NpPWk7dHJ5e24uY2FsbCh0aGlzLHRoaXMuX19kYXRhX18sdCxlKX1maW5hbGx5e3NpPXJ9fX1mdW5jdGlvbiBfSGUobil7cmV0dXJuIG4udHJpbSgpLnNwbGl0KC9efFxzKy8pLm1hcChmdW5jdGlvbih0KXt2YXIgZT0iIixpPXQuaW5kZXhPZigiLiIpO3JldHVybiBpPj0wJiYoZT10LnNsaWNlKGkrMSksdD10LnNsaWNlKDAsaSkpLHt0eXBlOnQsbmFtZTplfX0pfWZ1bmN0aW9uIHZIZShuKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD10aGlzLl9fb247aWYodCl7Zm9yKHZhciBvLGU9MCxpPS0xLHI9dC5sZW5ndGg7ZTxyOysrZSlvPXRbZV0sbi50eXBlJiZvLnR5cGUhPT1uLnR5cGV8fG8ubmFtZSE9PW4ubmFtZT90WysraV09bzp0aGlzLnJlbW92ZUV2ZW50TGlzdGVuZXIoby50eXBlLG8ubGlzdGVuZXIsby5jYXB0dXJlKTsrK2k/dC5sZW5ndGg9aTpkZWxldGUgdGhpcy5fX29ufX19ZnVuY3Rpb24geUhlKG4sdCxlKXt2YXIgaT1ac2UuaGFzT3duUHJvcGVydHkobi50eXBlKT9nSGU6SnNlO3JldHVybiBmdW5jdGlvbihyLG8scyl7dmFyIGwsYT10aGlzLl9fb24sYz1pKHQsbyxzKTtpZihhKWZvcih2YXIgdT0wLGQ9YS5sZW5ndGg7dTxkOysrdSlpZigobD1hW3VdKS50eXBlPT09bi50eXBlJiZsLm5hbWU9PT1uLm5hbWUpcmV0dXJuIHRoaXMucmVtb3ZlRXZlbnRMaXN0ZW5lcihsLnR5cGUsbC5saXN0ZW5lcixsLmNhcHR1cmUpLHRoaXMuYWRkRXZlbnRMaXN0ZW5lcihsLnR5cGUsbC5saXN0ZW5lcj1jLGwuY2FwdHVyZT1lKSx2b2lkKGwudmFsdWU9dCk7dGhpcy5hZGRFdmVudExpc3RlbmVyKG4udHlwZSxjLGUpLGw9e3R5cGU6bi50eXBlLG5hbWU6bi5uYW1lLHZhbHVlOnQsbGlzdGVuZXI6YyxjYXB0dXJlOmV9LGE/YS5wdXNoKGwpOnRoaXMuX19vbj1bbF19fWZ1bmN0aW9uIGVhZShuLHQsZSl7dmFyIGk9SVIobikscj1pLkN1c3RvbUV2ZW50OyJmdW5jdGlvbiI9PXR5cGVvZiByP3I9bmV3IHIodCxlKToocj1pLmRvY3VtZW50LmNyZWF0ZUV2ZW50KCJFdmVudCIpLGU/KHIuaW5pdEV2ZW50KHQsZS5idWJibGVzLGUuY2FuY2VsYWJsZSksci5kZXRhaWw9ZS5kZXRhaWwpOnIuaW5pdEV2ZW50KHQsITEsITEpKSxuLmRpc3BhdGNoRXZlbnQocil9ZnVuY3Rpb24gYkhlKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIGVhZSh0aGlzLG4sdCl9fWZ1bmN0aW9uIHhIZShuLHQpe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBlYWUodGhpcyxuLHQuYXBwbHkodGhpcyxhcmd1bWVudHMpKX19dHlwZW9mIGRvY3VtZW50PCJ1IiYmKCJvbm1vdXNlZW50ZXIiaW4gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50fHwoWnNlPXttb3VzZWVudGVyOiJtb3VzZW92ZXIiLG1vdXNlbGVhdmU6Im1vdXNlb3V0In0pKTt2YXIgalU9W251bGxdO2Z1bmN0aW9uIGFvKG4sdCl7dGhpcy5fZ3JvdXBzPW4sdGhpcy5fcGFyZW50cz10fWZ1bmN0aW9uIG5hZSgpe3JldHVybiBuZXcgYW8oW1tkb2N1bWVudC5kb2N1bWVudEVsZW1lbnRdXSxqVSl9YW8ucHJvdG90eXBlPW5hZS5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOmFvLHNlbGVjdDpmdW5jdGlvbihuKXsiZnVuY3Rpb24iIT10eXBlb2YgbiYmKG49Z2cobikpO2Zvcih2YXIgdD10aGlzLl9ncm91cHMsZT10Lmxlbmd0aCxpPW5ldyBBcnJheShlKSxyPTA7cjxlOysrcilmb3IodmFyIGwsYyxvPXRbcl0scz1vLmxlbmd0aCxhPWlbcl09bmV3IEFycmF5KHMpLHU9MDt1PHM7Kyt1KShsPW9bdV0pJiYoYz1uLmNhbGwobCxsLl9fZGF0YV9fLHUsbykpJiYoIl9fZGF0YV9fImluIGwmJihjLl9fZGF0YV9fPWwuX19kYXRhX18pLGFbdV09Yyk7cmV0dXJuIG5ldyBhbyhpLHRoaXMuX3BhcmVudHMpfSxzZWxlY3RBbGw6ZnVuY3Rpb24obil7ImZ1bmN0aW9uIiE9dHlwZW9mIG4mJihuPVd3KG4pKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLGU9dC5sZW5ndGgsaT1bXSxyPVtdLG89MDtvPGU7KytvKWZvcih2YXIgbCxzPXRbb10sYT1zLmxlbmd0aCxjPTA7YzxhOysrYykobD1zW2NdKSYmKGkucHVzaChuLmNhbGwobCxsLl9fZGF0YV9fLGMscykpLHIucHVzaChsKSk7cmV0dXJuIG5ldyBhbyhpLHIpfSxmaWx0ZXI6ZnVuY3Rpb24obil7ImZ1bmN0aW9uIiE9dHlwZW9mIG4mJihuPXF3KG4pKTtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLGU9dC5sZW5ndGgsaT1uZXcgQXJyYXkoZSkscj0wO3I8ZTsrK3IpZm9yKHZhciBsLG89dFtyXSxzPW8ubGVuZ3RoLGE9aVtyXT1bXSxjPTA7YzxzOysrYykobD1vW2NdKSYmbi5jYWxsKGwsbC5fX2RhdGFfXyxjLG8pJiZhLnB1c2gobCk7cmV0dXJuIG5ldyBhbyhpLHRoaXMuX3BhcmVudHMpfSxkYXRhOmZ1bmN0aW9uKG4sdCl7aWYoIW4pcmV0dXJuIGg9bmV3IEFycmF5KHRoaXMuc2l6ZSgpKSxjPS0xLHRoaXMuZWFjaChmdW5jdGlvbihaKXtoWysrY109Wn0pLGg7dmFyIGU9dD9VNGU6SDRlLGk9dGhpcy5fcGFyZW50cyxyPXRoaXMuX2dyb3VwczsiZnVuY3Rpb24iIT10eXBlb2YgbiYmKG49ZnVuY3Rpb24obil7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIG59fShuKSk7Zm9yKHZhciBvPXIubGVuZ3RoLHM9bmV3IEFycmF5KG8pLGE9bmV3IEFycmF5KG8pLGw9bmV3IEFycmF5KG8pLGM9MDtjPG87KytjKXt2YXIgdT1pW2NdLGQ9cltjXSxwPWQubGVuZ3RoLGg9bi5jYWxsKHUsdSYmdS5fX2RhdGFfXyxjLGkpLGY9aC5sZW5ndGgsbT1hW2NdPW5ldyBBcnJheShmKSx4PXNbY109bmV3IEFycmF5KGYpO2UodSxkLG0seCxsW2NdPW5ldyBBcnJheShwKSxoLHQpO2Zvcih2YXIgVCxrLGI9MCxEPTA7YjxmOysrYilpZihUPW1bYl0pe2ZvcihiPj1EJiYoRD1iKzEpOyEoaz14W0RdKSYmKytEPGY7KTtULl9uZXh0PWt8fG51bGx9fXJldHVybihzPW5ldyBhbyhzLGkpKS5fZW50ZXI9YSxzLl9leGl0PWwsc30sZW50ZXI6ZnVuY3Rpb24oKXtyZXR1cm4gbmV3IGFvKHRoaXMuX2VudGVyfHx0aGlzLl9ncm91cHMubWFwKEFSKSx0aGlzLl9wYXJlbnRzKX0sZXhpdDpmdW5jdGlvbigpe3JldHVybiBuZXcgYW8odGhpcy5fZXhpdHx8dGhpcy5fZ3JvdXBzLm1hcChBUiksdGhpcy5fcGFyZW50cyl9LGpvaW46ZnVuY3Rpb24obix0LGUpe3ZhciBpPXRoaXMuZW50ZXIoKSxyPXRoaXMsbz10aGlzLmV4aXQoKTtyZXR1cm4gaT0iZnVuY3Rpb24iPT10eXBlb2Ygbj9uKGkpOmkuYXBwZW5kKG4rIiIpLG51bGwhPXQmJihyPXQocikpLG51bGw9PWU/by5yZW1vdmUoKTplKG8pLGkmJnI/aS5tZXJnZShyKS5vcmRlcigpOnJ9LG1lcmdlOmZ1bmN0aW9uKG4pe2Zvcih2YXIgdD10aGlzLl9ncm91cHMsZT1uLl9ncm91cHMsaT10Lmxlbmd0aCxvPU1hdGgubWluKGksZS5sZW5ndGgpLHM9bmV3IEFycmF5KGkpLGE9MDthPG87KythKWZvcih2YXIgcCxsPXRbYV0sYz1lW2FdLHU9bC5sZW5ndGgsZD1zW2FdPW5ldyBBcnJheSh1KSxoPTA7aDx1OysraCkocD1sW2hdfHxjW2hdKSYmKGRbaF09cCk7Zm9yKDthPGk7KythKXNbYV09dFthXTtyZXR1cm4gbmV3IGFvKHMsdGhpcy5fcGFyZW50cyl9LG9yZGVyOmZ1bmN0aW9uKCl7Zm9yKHZhciBuPXRoaXMuX2dyb3Vwcyx0PS0xLGU9bi5sZW5ndGg7Kyt0PGU7KWZvcih2YXIgcyxpPW5bdF0scj1pLmxlbmd0aC0xLG89aVtyXTstLXI+PTA7KShzPWlbcl0pJiYobyYmNF5zLmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKG8pJiZvLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKHMsbyksbz1zKTtyZXR1cm4gdGhpc30sc29ydDpmdW5jdGlvbihuKXtmdW5jdGlvbiB0KGQscCl7cmV0dXJuIGQmJnA/bihkLl9fZGF0YV9fLHAuX19kYXRhX18pOiFkLSFwfW58fChuPXo0ZSk7Zm9yKHZhciBlPXRoaXMuX2dyb3VwcyxpPWUubGVuZ3RoLHI9bmV3IEFycmF5KGkpLG89MDtvPGk7KytvKXtmb3IodmFyIGMscz1lW29dLGE9cy5sZW5ndGgsbD1yW29dPW5ldyBBcnJheShhKSx1PTA7dTxhOysrdSkoYz1zW3VdKSYmKGxbdV09Yyk7bC5zb3J0KHQpfXJldHVybiBuZXcgYW8ocix0aGlzLl9wYXJlbnRzKS5vcmRlcigpfSxjYWxsOmZ1bmN0aW9uKCl7dmFyIG49YXJndW1lbnRzWzBdO3JldHVybiBhcmd1bWVudHNbMF09dGhpcyxuLmFwcGx5KG51bGwsYXJndW1lbnRzKSx0aGlzfSxub2RlczpmdW5jdGlvbigpe3ZhciBuPW5ldyBBcnJheSh0aGlzLnNpemUoKSksdD0tMTtyZXR1cm4gdGhpcy5lYWNoKGZ1bmN0aW9uKCl7blsrK3RdPXRoaXN9KSxufSxub2RlOmZ1bmN0aW9uKCl7Zm9yKHZhciBuPXRoaXMuX2dyb3Vwcyx0PTAsZT1uLmxlbmd0aDt0PGU7Kyt0KWZvcih2YXIgaT1uW3RdLHI9MCxvPWkubGVuZ3RoO3I8bzsrK3Ipe3ZhciBzPWlbcl07aWYocylyZXR1cm4gc31yZXR1cm4gbnVsbH0sc2l6ZTpmdW5jdGlvbigpe3ZhciBuPTA7cmV0dXJuIHRoaXMuZWFjaChmdW5jdGlvbigpeysrbn0pLG59LGVtcHR5OmZ1bmN0aW9uKCl7cmV0dXJuIXRoaXMubm9kZSgpfSxlYWNoOmZ1bmN0aW9uKG4pe2Zvcih2YXIgdD10aGlzLl9ncm91cHMsZT0wLGk9dC5sZW5ndGg7ZTxpOysrZSlmb3IodmFyIGEscj10W2VdLG89MCxzPXIubGVuZ3RoO288czsrK28pKGE9cltvXSkmJm4uY2FsbChhLGEuX19kYXRhX18sbyxyKTtyZXR1cm4gdGhpc30sYXR0cjpmdW5jdGlvbihuLHQpe3ZhciBlPWRwKG4pO2lmKGFyZ3VtZW50cy5sZW5ndGg8Mil7dmFyIGk9dGhpcy5ub2RlKCk7cmV0dXJuIGUubG9jYWw/aS5nZXRBdHRyaWJ1dGVOUyhlLnNwYWNlLGUubG9jYWwpOmkuZ2V0QXR0cmlidXRlKGUpfXJldHVybiB0aGlzLmVhY2goKG51bGw9PXQ/ZS5sb2NhbD9HNGU6ajRlOiJmdW5jdGlvbiI9PXR5cGVvZiB0P2UubG9jYWw/WDRlOlk0ZTplLmxvY2FsP3E0ZTpXNGUpKGUsdCkpfSxzdHlsZTpmdW5jdGlvbihuLHQsZSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg+MT90aGlzLmVhY2goKG51bGw9PXQ/UTRlOiJmdW5jdGlvbiI9PXR5cGVvZiB0P1o0ZTpLNGUpKG4sdCxlPz8iIikpOlpoKHRoaXMubm9kZSgpLG4pfSxwcm9wZXJ0eTpmdW5jdGlvbihuLHQpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPjE/dGhpcy5lYWNoKChudWxsPT10P0o0ZToiZnVuY3Rpb24iPT10eXBlb2YgdD9lSGU6JDRlKShuLHQpKTp0aGlzLm5vZGUoKVtuXX0sY2xhc3NlZDpmdW5jdGlvbihuLHQpe3ZhciBlPU5zZShuKyIiKTtpZihhcmd1bWVudHMubGVuZ3RoPDIpe2Zvcih2YXIgaT1VVSh0aGlzLm5vZGUoKSkscj0tMSxvPWUubGVuZ3RoOysrcjxvOylpZighaS5jb250YWlucyhlW3JdKSlyZXR1cm4hMTtyZXR1cm4hMH1yZXR1cm4gdGhpcy5lYWNoKCgiZnVuY3Rpb24iPT10eXBlb2YgdD9pSGU6dD90SGU6bkhlKShlLHQpKX0sdGV4dDpmdW5jdGlvbihuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2gobnVsbD09bj9ySGU6KCJmdW5jdGlvbiI9PXR5cGVvZiBuP3NIZTpvSGUpKG4pKTp0aGlzLm5vZGUoKS50ZXh0Q29udGVudH0saHRtbDpmdW5jdGlvbihuKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2gobnVsbD09bj9hSGU6KCJmdW5jdGlvbiI9PXR5cGVvZiBuP2NIZTpsSGUpKG4pKTp0aGlzLm5vZGUoKS5pbm5lckhUTUx9LHJhaXNlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZWFjaCh1SGUpfSxsb3dlcjpmdW5jdGlvbigpe3JldHVybiB0aGlzLmVhY2goZEhlKX0sYXBwZW5kOmZ1bmN0aW9uKG4pe3ZhciB0PSJmdW5jdGlvbiI9PXR5cGVvZiBuP246RFIobik7cmV0dXJuIHRoaXMuc2VsZWN0KGZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuYXBwZW5kQ2hpbGQodC5hcHBseSh0aGlzLGFyZ3VtZW50cykpfSl9LGluc2VydDpmdW5jdGlvbihuLHQpe3ZhciBlPSJmdW5jdGlvbiI9PXR5cGVvZiBuP246RFIobiksaT1udWxsPT10P3BIZToiZnVuY3Rpb24iPT10eXBlb2YgdD90OmdnKHQpO3JldHVybiB0aGlzLnNlbGVjdChmdW5jdGlvbigpe3JldHVybiB0aGlzLmluc2VydEJlZm9yZShlLmFwcGx5KHRoaXMsYXJndW1lbnRzKSxpLmFwcGx5KHRoaXMsYXJndW1lbnRzKXx8bnVsbCl9KX0scmVtb3ZlOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZWFjaChoSGUpfSxjbG9uZTpmdW5jdGlvbihuKXtyZXR1cm4gdGhpcy5zZWxlY3Qobj9tSGU6ZkhlKX0sZGF0dW06ZnVuY3Rpb24obil7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5wcm9wZXJ0eSgiX19kYXRhX18iLG4pOnRoaXMubm9kZSgpLl9fZGF0YV9ffSxvbjpmdW5jdGlvbihuLHQsZSl7dmFyIHIscyxpPV9IZShuKyIiKSxvPWkubGVuZ3RoO2lmKCEoYXJndW1lbnRzLmxlbmd0aDwyKSl7Zm9yKGE9dD95SGU6dkhlLG51bGw9PWUmJihlPSExKSxyPTA7cjxvOysrcil0aGlzLmVhY2goYShpW3JdLHQsZSkpO3JldHVybiB0aGlzfXZhciBhPXRoaXMubm9kZSgpLl9fb247aWYoYSlmb3IodmFyIHUsbD0wLGM9YS5sZW5ndGg7bDxjOysrbClmb3Iocj0wLHU9YVtsXTtyPG87KytyKWlmKChzPWlbcl0pLnR5cGU9PT11LnR5cGUmJnMubmFtZT09PXUubmFtZSlyZXR1cm4gdS52YWx1ZX0sZGlzcGF0Y2g6ZnVuY3Rpb24obix0KXtyZXR1cm4gdGhpcy5lYWNoKCgiZnVuY3Rpb24iPT10eXBlb2YgdD94SGU6YkhlKShuLHQpKX19O3ZhciBwcD1uYWU7ZnVuY3Rpb24gYm8obil7cmV0dXJuInN0cmluZyI9PXR5cGVvZiBuP25ldyBhbyhbW2RvY3VtZW50LnF1ZXJ5U2VsZWN0b3IobildXSxbZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50XSk6bmV3IGFvKFtbbl1dLGpVKX1mdW5jdGlvbiBQUigpe2Zvcih2YXIgdCxuPXNpO3Q9bi5zb3VyY2VFdmVudDspbj10O3JldHVybiBufWZ1bmN0aW9uIFJSKG4sdCl7dmFyIGU9bi5vd25lclNWR0VsZW1lbnR8fG47aWYoZS5jcmVhdGVTVkdQb2ludCl7dmFyIGk9ZS5jcmVhdGVTVkdQb2ludCgpO3JldHVybiBpLng9dC5jbGllbnRYLGkueT10LmNsaWVudFksWyhpPWkubWF0cml4VHJhbnNmb3JtKG4uZ2V0U2NyZWVuQ1RNKCkuaW52ZXJzZSgpKSkueCxpLnldfXZhciByPW4uZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7cmV0dXJuW3QuY2xpZW50WC1yLmxlZnQtbi5jbGllbnRMZWZ0LHQuY2xpZW50WS1yLnRvcC1uLmNsaWVudFRvcF19ZnVuY3Rpb24gR1Uobil7dmFyIHQ9UFIoKTtyZXR1cm4gdC5jaGFuZ2VkVG91Y2hlcyYmKHQ9dC5jaGFuZ2VkVG91Y2hlc1swXSksUlIobix0KX1mdW5jdGlvbiBPUigpe3NpLnByZXZlbnREZWZhdWx0KCksc2kuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCl9ZnVuY3Rpb24gcVUobil7dmFyIHQ9bi5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsZT1ibyhuKS5vbigiZHJhZ3N0YXJ0LmRyYWciLE9SLCEwKTsib25zZWxlY3RzdGFydCJpbiB0P2Uub24oInNlbGVjdHN0YXJ0LmRyYWciLE9SLCEwKToodC5fX25vc2VsZWN0PXQuc3R5bGUuTW96VXNlclNlbGVjdCx0LnN0eWxlLk1velVzZXJTZWxlY3Q9Im5vbmUiKX1mdW5jdGlvbiBZVShuLHQpe3ZhciBlPW4uZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LGk9Ym8obikub24oImRyYWdzdGFydC5kcmFnIixudWxsKTt0JiYoaS5vbigiY2xpY2suZHJhZyIsT1IsITApLHNldFRpbWVvdXQoZnVuY3Rpb24oKXtpLm9uKCJjbGljay5kcmFnIixudWxsKX0sMCkpLCJvbnNlbGVjdHN0YXJ0ImluIGU/aS5vbigic2VsZWN0c3RhcnQuZHJhZyIsbnVsbCk6KGUuc3R5bGUuTW96VXNlclNlbGVjdD1lLl9fbm9zZWxlY3QsZGVsZXRlIGUuX19ub3NlbGVjdCl9ZnVuY3Rpb24gX2cobix0LGUpe24ucHJvdG90eXBlPXQucHJvdG90eXBlPWUsZS5jb25zdHJ1Y3Rvcj1ufWZ1bmN0aW9uIGh5KG4sdCl7dmFyIGU9T2JqZWN0LmNyZWF0ZShuLnByb3RvdHlwZSk7Zm9yKHZhciBpIGluIHQpZVtpXT10W2ldO3JldHVybiBlfWZ1bmN0aW9uIEpoKCl7fXZhciBGUj0xLy43LGZ5PSJcXHMqKFsrLV0/XFxkKylcXHMqIixRdz0iXFxzKihbKy1dP1xcZCpcXC4/XFxkKyg/OltlRV1bKy1dP1xcZCspPylcXHMqIixiZD0iXFxzKihbKy1dP1xcZCpcXC4/XFxkKyg/OltlRV1bKy1dP1xcZCspPyklXFxzKiIsQ0hlPS9eIyhbMC05YS1mXXszLDh9KSQvLE1IZT1uZXcgUmVnRXhwKCJecmdiXFwoIitbZnksZnksZnldKyJcXCkkIiksd0hlPW5ldyBSZWdFeHAoIl5yZ2JcXCgiK1tiZCxiZCxiZF0rIlxcKSQiKSxTSGU9bmV3IFJlZ0V4cCgiXnJnYmFcXCgiK1tmeSxmeSxmeSxRd10rIlxcKSQiKSxFSGU9bmV3IFJlZ0V4cCgiXnJnYmFcXCgiK1tiZCxiZCxiZCxRd10rIlxcKSQiKSxUSGU9bmV3IFJlZ0V4cCgiXmhzbFxcKCIrW1F3LGJkLGJkXSsiXFwpJCIpLERIZT1uZXcgUmVnRXhwKCJeaHNsYVxcKCIrW1F3LGJkLGJkLFF3XSsiXFwpJCIpLGlhZT17YWxpY2VibHVlOjE1NzkyMzgzLGFudGlxdWV3aGl0ZToxNjQ0NDM3NSxhcXVhOjY1NTM1LGFxdWFtYXJpbmU6ODM4ODU2NCxhenVyZToxNTc5NDE3NSxiZWlnZToxNjExOTI2MCxiaXNxdWU6MTY3NzAyNDQsYmxhY2s6MCxibGFuY2hlZGFsbW9uZDoxNjc3MjA0NSxibHVlOjI1NSxibHVldmlvbGV0OjkwNTUyMDIsYnJvd246MTA4MjQyMzQsYnVybHl3b29kOjE0NTk2MjMxLGNhZGV0Ymx1ZTo2MjY2NTI4LGNoYXJ0cmV1c2U6ODM4ODM1MixjaG9jb2xhdGU6MTM3ODk0NzAsY29yYWw6MTY3NDQyNzIsY29ybmZsb3dlcmJsdWU6NjU5MTk4MSxjb3Juc2lsazoxNjc3NTM4OCxjcmltc29uOjE0NDIzMTAwLGN5YW46NjU1MzUsZGFya2JsdWU6MTM5LGRhcmtjeWFuOjM1NzIzLGRhcmtnb2xkZW5yb2Q6MTIwOTI5MzksZGFya2dyYXk6MTExMTkwMTcsZGFya2dyZWVuOjI1NjAwLGRhcmtncmV5OjExMTE5MDE3LGRhcmtraGFraToxMjQzMzI1OSxkYXJrbWFnZW50YTo5MTA5NjQzLGRhcmtvbGl2ZWdyZWVuOjU1OTc5OTksZGFya29yYW5nZToxNjc0NzUyMCxkYXJrb3JjaGlkOjEwMDQwMDEyLGRhcmtyZWQ6OTEwOTUwNCxkYXJrc2FsbW9uOjE1MzA4NDEwLGRhcmtzZWFncmVlbjo5NDE5OTE5LGRhcmtzbGF0ZWJsdWU6NDczNDM0NyxkYXJrc2xhdGVncmF5OjMxMDA0OTUsZGFya3NsYXRlZ3JleTozMTAwNDk1LGRhcmt0dXJxdW9pc2U6NTI5NDUsZGFya3Zpb2xldDo5Njk5NTM5LGRlZXBwaW5rOjE2NzE2OTQ3LGRlZXBza3libHVlOjQ5MTUxLGRpbWdyYXk6NjkwODI2NSxkaW1ncmV5OjY5MDgyNjUsZG9kZ2VyYmx1ZToyMDAzMTk5LGZpcmVicmljazoxMTY3NDE0NixmbG9yYWx3aGl0ZToxNjc3NTkyMCxmb3Jlc3RncmVlbjoyMjYzODQyLGZ1Y2hzaWE6MTY3MTE5MzUsZ2FpbnNib3JvOjE0NDc0NDYwLGdob3N0d2hpdGU6MTYzMTY2NzEsZ29sZDoxNjc2NjcyMCxnb2xkZW5yb2Q6MTQzMjkxMjAsZ3JheTo4NDIxNTA0LGdyZWVuOjMyNzY4LGdyZWVueWVsbG93OjExNDAzMDU1LGdyZXk6ODQyMTUwNCxob25leWRldzoxNTc5NDE2MCxob3RwaW5rOjE2NzM4NzQwLGluZGlhbnJlZDoxMzQ1ODUyNCxpbmRpZ286NDkxNTMzMCxpdm9yeToxNjc3NzIwMCxraGFraToxNTc4NzY2MCxsYXZlbmRlcjoxNTEzMjQxMCxsYXZlbmRlcmJsdXNoOjE2NzczMzY1LGxhd25ncmVlbjo4MTkwOTc2LGxlbW9uY2hpZmZvbjoxNjc3NTg4NSxsaWdodGJsdWU6MTEzOTMyNTQsbGlnaHRjb3JhbDoxNTc2MTUzNixsaWdodGN5YW46MTQ3NDU1OTksbGlnaHRnb2xkZW5yb2R5ZWxsb3c6MTY0NDgyMTAsbGlnaHRncmF5OjEzODgyMzIzLGxpZ2h0Z3JlZW46OTQ5ODI1NixsaWdodGdyZXk6MTM4ODIzMjMsbGlnaHRwaW5rOjE2NzU4NDY1LGxpZ2h0c2FsbW9uOjE2NzUyNzYyLGxpZ2h0c2VhZ3JlZW46MjE0Mjg5MCxsaWdodHNreWJsdWU6ODkwMDM0NixsaWdodHNsYXRlZ3JheTo3ODMzNzUzLGxpZ2h0c2xhdGVncmV5Ojc4MzM3NTMsbGlnaHRzdGVlbGJsdWU6MTE1ODQ3MzQsbGlnaHR5ZWxsb3c6MTY3NzcxODQsbGltZTo2NTI4MCxsaW1lZ3JlZW46MzMyOTMzMCxsaW5lbjoxNjQ0NTY3MCxtYWdlbnRhOjE2NzExOTM1LG1hcm9vbjo4Mzg4NjA4LG1lZGl1bWFxdWFtYXJpbmU6NjczNzMyMixtZWRpdW1ibHVlOjIwNSxtZWRpdW1vcmNoaWQ6MTIyMTE2NjcsbWVkaXVtcHVycGxlOjk2NjI2ODMsbWVkaXVtc2VhZ3JlZW46Mzk3ODA5NyxtZWRpdW1zbGF0ZWJsdWU6ODA4Nzc5MCxtZWRpdW1zcHJpbmdncmVlbjo2NDE1NCxtZWRpdW10dXJxdW9pc2U6NDc3MjMwMCxtZWRpdW12aW9sZXRyZWQ6MTMwNDcxNzMsbWlkbmlnaHRibHVlOjE2NDQ5MTIsbWludGNyZWFtOjE2MTIxODUwLG1pc3R5cm9zZToxNjc3MDI3Myxtb2NjYXNpbjoxNjc3MDIyOSxuYXZham93aGl0ZToxNjc2ODY4NSxuYXZ5OjEyOCxvbGRsYWNlOjE2NjQzNTU4LG9saXZlOjg0MjEzNzYsb2xpdmVkcmFiOjcwNDg3Mzksb3JhbmdlOjE2NzUzOTIwLG9yYW5nZXJlZDoxNjcyOTM0NCxvcmNoaWQ6MTQzMTU3MzQscGFsZWdvbGRlbnJvZDoxNTY1NzEzMCxwYWxlZ3JlZW46MTAwMjU4ODAscGFsZXR1cnF1b2lzZToxMTUyOTk2NixwYWxldmlvbGV0cmVkOjE0MzgxMjAzLHBhcGF5YXdoaXA6MTY3NzMwNzcscGVhY2hwdWZmOjE2NzY3NjczLHBlcnU6MTM0Njg5OTEscGluazoxNjc2MTAzNSxwbHVtOjE0NTI0NjM3LHBvd2RlcmJsdWU6MTE1OTE5MTAscHVycGxlOjgzODg3MzYscmViZWNjYXB1cnBsZTo2Njk3ODgxLHJlZDoxNjcxMTY4MCxyb3N5YnJvd246MTIzNTc1MTkscm95YWxibHVlOjQyODY5NDUsc2FkZGxlYnJvd246OTEyNzE4NyxzYWxtb246MTY0MTY4ODIsc2FuZHlicm93bjoxNjAzMjg2NCxzZWFncmVlbjozMDUwMzI3LHNlYXNoZWxsOjE2Nzc0NjM4LHNpZW5uYToxMDUwNjc5NyxzaWx2ZXI6MTI2MzIyNTYsc2t5Ymx1ZTo4OTAwMzMxLHNsYXRlYmx1ZTo2OTcwMDYxLHNsYXRlZ3JheTo3MzcyOTQ0LHNsYXRlZ3JleTo3MzcyOTQ0LHNub3c6MTY3NzU5MzAsc3ByaW5nZ3JlZW46NjU0MDcsc3RlZWxibHVlOjQ2MjA5ODAsdGFuOjEzODA4NzgwLHRlYWw6MzI4OTYsdGhpc3RsZToxNDIwNDg4OCx0b21hdG86MTY3MzcwOTUsdHVycXVvaXNlOjQyNTE4NTYsdmlvbGV0OjE1NjMxMDg2LHdoZWF0OjE2MTEzMzMxLHdoaXRlOjE2Nzc3MjE1LHdoaXRlc21va2U6MTYxMTkyODUseWVsbG93OjE2Nzc2OTYwLHllbGxvd2dyZWVuOjEwMTQ1MDc0fTtmdW5jdGlvbiByYWUoKXtyZXR1cm4gdGhpcy5yZ2IoKS5mb3JtYXRIZXgoKX1mdW5jdGlvbiBvYWUoKXtyZXR1cm4gdGhpcy5yZ2IoKS5mb3JtYXRSZ2IoKX1mdW5jdGlvbiBydShuKXt2YXIgdCxlO3JldHVybiBuPShuKyIiKS50cmltKCkudG9Mb3dlckNhc2UoKSwodD1DSGUuZXhlYyhuKSk/KGU9dFsxXS5sZW5ndGgsdD1wYXJzZUludCh0WzFdLDE2KSw2PT09ZT9zYWUodCk6Mz09PWU/bmV3IEhzKHQ+PjgmMTV8dD4+NCYyNDAsdD4+NCYxNXwyNDAmdCwoMTUmdCk8PDR8MTUmdCwxKTo4PT09ZT9rUih0Pj4yNCYyNTUsdD4+MTYmMjU1LHQ+PjgmMjU1LCgyNTUmdCkvMjU1KTo0PT09ZT9rUih0Pj4xMiYxNXx0Pj44JjI0MCx0Pj44JjE1fHQ+PjQmMjQwLHQ+PjQmMTV8MjQwJnQsKCgxNSZ0KTw8NHwxNSZ0KS8yNTUpOm51bGwpOih0PU1IZS5leGVjKG4pKT9uZXcgSHModFsxXSx0WzJdLHRbM10sMSk6KHQ9d0hlLmV4ZWMobikpP25ldyBIcygyNTUqdFsxXS8xMDAsMjU1KnRbMl0vMTAwLDI1NSp0WzNdLzEwMCwxKToodD1TSGUuZXhlYyhuKSk/a1IodFsxXSx0WzJdLHRbM10sdFs0XSk6KHQ9RUhlLmV4ZWMobikpP2tSKDI1NSp0WzFdLzEwMCwyNTUqdFsyXS8xMDAsMjU1KnRbM10vMTAwLHRbNF0pOih0PVRIZS5leGVjKG4pKT9jYWUodFsxXSx0WzJdLzEwMCx0WzNdLzEwMCwxKToodD1ESGUuZXhlYyhuKSk/Y2FlKHRbMV0sdFsyXS8xMDAsdFszXS8xMDAsdFs0XSk6aWFlLmhhc093blByb3BlcnR5KG4pP3NhZShpYWVbbl0pOiJ0cmFuc3BhcmVudCI9PT1uP25ldyBIcyhOYU4sTmFOLE5hTiwwKTpudWxsfWZ1bmN0aW9uIHNhZShuKXtyZXR1cm4gbmV3IEhzKG4+PjE2JjI1NSxuPj44JjI1NSwyNTUmbiwxKX1mdW5jdGlvbiBrUihuLHQsZSxpKXtyZXR1cm4gaTw9MCYmKG49dD1lPU5hTiksbmV3IEhzKG4sdCxlLGkpfWZ1bmN0aW9uIEtVKG4pe3JldHVybiBuIGluc3RhbmNlb2YgSmh8fChuPXJ1KG4pKSxuP25ldyBIcygobj1uLnJnYigpKS5yLG4uZyxuLmIsbi5vcGFjaXR5KTpuZXcgSHN9ZnVuY3Rpb24gbXkobix0LGUsaSl7cmV0dXJuIDE9PT1hcmd1bWVudHMubGVuZ3RoP0tVKG4pOm5ldyBIcyhuLHQsZSxpPz8xKX1mdW5jdGlvbiBIcyhuLHQsZSxpKXt0aGlzLnI9K24sdGhpcy5nPSt0LHRoaXMuYj0rZSx0aGlzLm9wYWNpdHk9K2l9ZnVuY3Rpb24gYWFlKCl7cmV0dXJuIiMiK1hVKHRoaXMucikrWFUodGhpcy5nKStYVSh0aGlzLmIpfWZ1bmN0aW9uIGxhZSgpe3ZhciBuPXRoaXMub3BhY2l0eTtyZXR1cm4oMT09PShuPWlzTmFOKG4pPzE6TWF0aC5tYXgoMCxNYXRoLm1pbigxLG4pKSk/InJnYigiOiJyZ2JhKCIpK01hdGgubWF4KDAsTWF0aC5taW4oMjU1LE1hdGgucm91bmQodGhpcy5yKXx8MCkpKyIsICIrTWF0aC5tYXgoMCxNYXRoLm1pbigyNTUsTWF0aC5yb3VuZCh0aGlzLmcpfHwwKSkrIiwgIitNYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKHRoaXMuYil8fDApKSsoMT09PW4/IikiOiIsICIrbisiKSIpfWZ1bmN0aW9uIFhVKG4pe3JldHVybigobj1NYXRoLm1heCgwLE1hdGgubWluKDI1NSxNYXRoLnJvdW5kKG4pfHwwKSkpPDE2PyIwIjoiIikrbi50b1N0cmluZygxNil9ZnVuY3Rpb24gY2FlKG4sdCxlLGkpe3JldHVybiBpPD0wP249dD1lPU5hTjplPD0wfHxlPj0xP249dD1OYU46dDw9MCYmKG49TmFOKSxuZXcgeWQobix0LGUsaSl9ZnVuY3Rpb24gdWFlKG4pe2lmKG4gaW5zdGFuY2VvZiB5ZClyZXR1cm4gbmV3IHlkKG4uaCxuLnMsbi5sLG4ub3BhY2l0eSk7aWYobiBpbnN0YW5jZW9mIEpofHwobj1ydShuKSksIW4pcmV0dXJuIG5ldyB5ZDtpZihuIGluc3RhbmNlb2YgeWQpcmV0dXJuIG47dmFyIHQ9KG49bi5yZ2IoKSkuci8yNTUsZT1uLmcvMjU1LGk9bi5iLzI1NSxyPU1hdGgubWluKHQsZSxpKSxvPU1hdGgubWF4KHQsZSxpKSxzPU5hTixhPW8tcixsPShvK3IpLzI7cmV0dXJuIGE/KHM9dD09PW8/KGUtaSkvYSs2KihlPGkpOmU9PT1vPyhpLXQpL2ErMjoodC1lKS9hKzQsYS89bDwuNT9vK3I6Mi1vLXIscyo9NjApOmE9bD4wJiZsPDE/MDpzLG5ldyB5ZChzLGEsbCxuLm9wYWNpdHkpfWZ1bmN0aW9uIHZnKG4sdCxlLGkpe3JldHVybiAxPT09YXJndW1lbnRzLmxlbmd0aD91YWUobik6bmV3IHlkKG4sdCxlLGk/PzEpfWZ1bmN0aW9uIHlkKG4sdCxlLGkpe3RoaXMuaD0rbix0aGlzLnM9K3QsdGhpcy5sPStlLHRoaXMub3BhY2l0eT0raX1mdW5jdGlvbiBRVShuLHQsZSl7cmV0dXJuIDI1NSoobjw2MD90KyhlLXQpKm4vNjA6bjwxODA/ZTpuPDI0MD90KyhlLXQpKigyNDAtbikvNjA6dCl9X2coSmgscnUse2NvcHk6ZnVuY3Rpb24obil7cmV0dXJuIE9iamVjdC5hc3NpZ24obmV3IHRoaXMuY29uc3RydWN0b3IsdGhpcyxuKX0sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5yZ2IoKS5kaXNwbGF5YWJsZSgpfSxoZXg6cmFlLGZvcm1hdEhleDpyYWUsZm9ybWF0SHNsOmZ1bmN0aW9uKCl7cmV0dXJuIHVhZSh0aGlzKS5mb3JtYXRIc2woKX0sZm9ybWF0UmdiOm9hZSx0b1N0cmluZzpvYWV9KSxfZyhIcyxteSxoeShKaCx7YnJpZ2h0ZXI6ZnVuY3Rpb24obil7cmV0dXJuIG49bnVsbD09bj9GUjpNYXRoLnBvdyhGUixuKSxuZXcgSHModGhpcy5yKm4sdGhpcy5nKm4sdGhpcy5iKm4sdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKG4pe3JldHVybiBuPW51bGw9PW4/Ljc6TWF0aC5wb3coLjcsbiksbmV3IEhzKHRoaXMucipuLHRoaXMuZypuLHRoaXMuYipuLHRoaXMub3BhY2l0eSl9LHJnYjpmdW5jdGlvbigpe3JldHVybiB0aGlzfSxkaXNwbGF5YWJsZTpmdW5jdGlvbigpe3JldHVybi0uNTw9dGhpcy5yJiZ0aGlzLnI8MjU1LjUmJi0uNTw9dGhpcy5nJiZ0aGlzLmc8MjU1LjUmJi0uNTw9dGhpcy5iJiZ0aGlzLmI8MjU1LjUmJjA8PXRoaXMub3BhY2l0eSYmdGhpcy5vcGFjaXR5PD0xfSxoZXg6YWFlLGZvcm1hdEhleDphYWUsZm9ybWF0UmdiOmxhZSx0b1N0cmluZzpsYWV9KSksX2coeWQsdmcsaHkoSmgse2JyaWdodGVyOmZ1bmN0aW9uKG4pe3JldHVybiBuPW51bGw9PW4/RlI6TWF0aC5wb3coRlIsbiksbmV3IHlkKHRoaXMuaCx0aGlzLnMsdGhpcy5sKm4sdGhpcy5vcGFjaXR5KX0sZGFya2VyOmZ1bmN0aW9uKG4pe3JldHVybiBuPW51bGw9PW4/Ljc6TWF0aC5wb3coLjcsbiksbmV3IHlkKHRoaXMuaCx0aGlzLnMsdGhpcy5sKm4sdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7dmFyIG49dGhpcy5oJTM2MCszNjAqKHRoaXMuaDwwKSx0PWlzTmFOKG4pfHxpc05hTih0aGlzLnMpPzA6dGhpcy5zLGU9dGhpcy5sLGk9ZSsoZTwuNT9lOjEtZSkqdCxyPTIqZS1pO3JldHVybiBuZXcgSHMoUVUobj49MjQwP24tMjQwOm4rMTIwLHIsaSksUVUobixyLGkpLFFVKG48MTIwP24rMjQwOm4tMTIwLHIsaSksdGhpcy5vcGFjaXR5KX0sZGlzcGxheWFibGU6ZnVuY3Rpb24oKXtyZXR1cm4oMDw9dGhpcy5zJiZ0aGlzLnM8PTF8fGlzTmFOKHRoaXMucykpJiYwPD10aGlzLmwmJnRoaXMubDw9MSYmMDw9dGhpcy5vcGFjaXR5JiZ0aGlzLm9wYWNpdHk8PTF9LGZvcm1hdEhzbDpmdW5jdGlvbigpe3ZhciBuPXRoaXMub3BhY2l0eTtyZXR1cm4oMT09PShuPWlzTmFOKG4pPzE6TWF0aC5tYXgoMCxNYXRoLm1pbigxLG4pKSk/ImhzbCgiOiJoc2xhKCIpKyh0aGlzLmh8fDApKyIsICIrMTAwKih0aGlzLnN8fDApKyIlLCAiKzEwMCoodGhpcy5sfHwwKSsiJSIrKDE9PT1uPyIpIjoiLCAiK24rIikiKX19KSk7dmFyIGRhZT1NYXRoLlBJLzE4MCxwYWU9MTgwL01hdGguUEksZ2FlPTQvMjksZ3k9Ni8yOSxfYWU9MypneSpneTtmdW5jdGlvbiB2YWUobil7aWYobiBpbnN0YW5jZW9mIHhkKXJldHVybiBuZXcgeGQobi5sLG4uYSxuLmIsbi5vcGFjaXR5KTtpZihuIGluc3RhbmNlb2YgaHApcmV0dXJuIHlhZShuKTtuIGluc3RhbmNlb2YgSHN8fChuPUtVKG4pKTt2YXIgbyxzLHQ9ZXoobi5yKSxlPWV6KG4uZyksaT1leihuLmIpLHI9WlUoKC4yMjI1MDQ1KnQrLjcxNjg3ODYqZSsuMDYwNjE2OSppKS8xKTtyZXR1cm4gdD09PWUmJmU9PT1pP289cz1yOihvPVpVKCguNDM2MDc0Nyp0Ky4zODUwNjQ5KmUrLjE0MzA4MDQqaSkvLjk2NDIyKSxzPVpVKCguMDEzOTMyMip0Ky4wOTcxMDQ1KmUrLjcxNDE3MzMqaSkvLjgyNTIxKSksbmV3IHhkKDExNipyLTE2LDUwMCooby1yKSwyMDAqKHItcyksbi5vcGFjaXR5KX1mdW5jdGlvbiB4ZChuLHQsZSxpKXt0aGlzLmw9K24sdGhpcy5hPSt0LHRoaXMuYj0rZSx0aGlzLm9wYWNpdHk9K2l9ZnVuY3Rpb24gWlUobil7cmV0dXJuIG4+LjAwODg1NjQ1MTY3OTAzNTYzMT9NYXRoLnBvdyhuLDEvMyk6bi9fYWUrZ2FlfWZ1bmN0aW9uIEpVKG4pe3JldHVybiBuPmd5P24qbipuOl9hZSoobi1nYWUpfWZ1bmN0aW9uICRVKG4pe3JldHVybiAyNTUqKG48PS4wMDMxMzA4PzEyLjkyKm46MS4wNTUqTWF0aC5wb3cobiwxLzIuNCktLjA1NSl9ZnVuY3Rpb24gZXoobil7cmV0dXJuKG4vPTI1NSk8PS4wNDA0NT9uLzEyLjkyOk1hdGgucG93KChuKy4wNTUpLzEuMDU1LDIuNCl9ZnVuY3Rpb24gUEhlKG4pe2lmKG4gaW5zdGFuY2VvZiBocClyZXR1cm4gbmV3IGhwKG4uaCxuLmMsbi5sLG4ub3BhY2l0eSk7aWYobiBpbnN0YW5jZW9mIHhkfHwobj12YWUobikpLDA9PT1uLmEmJjA9PT1uLmIpcmV0dXJuIG5ldyBocChOYU4sMDxuLmwmJm4ubDwxMDA/MDpOYU4sbi5sLG4ub3BhY2l0eSk7dmFyIHQ9TWF0aC5hdGFuMihuLmIsbi5hKSpwYWU7cmV0dXJuIG5ldyBocCh0PDA/dCszNjA6dCxNYXRoLnNxcnQobi5hKm4uYStuLmIqbi5iKSxuLmwsbi5vcGFjaXR5KX1mdW5jdGlvbiB5ZyhuLHQsZSxpKXtyZXR1cm4gMT09PWFyZ3VtZW50cy5sZW5ndGg/UEhlKG4pOm5ldyBocChuLHQsZSxpPz8xKX1mdW5jdGlvbiBocChuLHQsZSxpKXt0aGlzLmg9K24sdGhpcy5jPSt0LHRoaXMubD0rZSx0aGlzLm9wYWNpdHk9K2l9ZnVuY3Rpb24geWFlKG4pe2lmKGlzTmFOKG4uaCkpcmV0dXJuIG5ldyB4ZChuLmwsMCwwLG4ub3BhY2l0eSk7dmFyIHQ9bi5oKmRhZTtyZXR1cm4gbmV3IHhkKG4ubCxNYXRoLmNvcyh0KSpuLmMsTWF0aC5zaW4odCkqbi5jLG4ub3BhY2l0eSl9ZnVuY3Rpb24gbnoobix0LGUsaSxyKXt2YXIgbz1uKm4scz1vKm47cmV0dXJuKCgxLTMqbiszKm8tcykqdCsoNC02Km8rMypzKSplKygxKzMqbiszKm8tMypzKSppK3MqcikvNn1mdW5jdGlvbiBfeShuKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gbn19ZnVuY3Rpb24gQ2FlKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKGUpe3JldHVybiBuK2UqdH19ZnVuY3Rpb24gTFIobix0KXt2YXIgZT10LW47cmV0dXJuIGU/Q2FlKG4sZT4xODB8fGU8LTE4MD9lLTM2MCpNYXRoLnJvdW5kKGUvMzYwKTplKTpfeShpc05hTihuKT90Om4pfWZ1bmN0aW9uIHphKG4sdCl7dmFyIGU9dC1uO3JldHVybiBlP0NhZShuLGUpOl95KGlzTmFOKG4pP3Q6bil9X2coeGQsZnVuY3Rpb24obix0LGUsaSl7cmV0dXJuIDE9PT1hcmd1bWVudHMubGVuZ3RoP3ZhZShuKTpuZXcgeGQobix0LGUsaT8/MSl9LGh5KEpoLHticmlnaHRlcjpmdW5jdGlvbihuKXtyZXR1cm4gbmV3IHhkKHRoaXMubCsxOCoobj8/MSksdGhpcy5hLHRoaXMuYix0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24obil7cmV0dXJuIG5ldyB4ZCh0aGlzLmwtMTgqKG4/PzEpLHRoaXMuYSx0aGlzLmIsdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7dmFyIG49KHRoaXMubCsxNikvMTE2LHQ9aXNOYU4odGhpcy5hKT9uOm4rdGhpcy5hLzUwMCxlPWlzTmFOKHRoaXMuYik/bjpuLXRoaXMuYi8yMDA7cmV0dXJuIG5ldyBIcygkVSgzLjEzMzg1NjEqKHQ9Ljk2NDIyKkpVKHQpKS0xLjYxNjg2NjcqKG49MSpKVShuKSktLjQ5MDYxNDYqKGU9LjgyNTIxKkpVKGUpKSksJFUoLS45Nzg3Njg0KnQrMS45MTYxNDE1Km4rLjAzMzQ1NCplKSwkVSguMDcxOTQ1Myp0LS4yMjg5OTE0Km4rMS40MDUyNDI3KmUpLHRoaXMub3BhY2l0eSl9fSkpLF9nKGhwLHlnLGh5KEpoLHticmlnaHRlcjpmdW5jdGlvbihuKXtyZXR1cm4gbmV3IGhwKHRoaXMuaCx0aGlzLmMsdGhpcy5sKzE4KihuPz8xKSx0aGlzLm9wYWNpdHkpfSxkYXJrZXI6ZnVuY3Rpb24obil7cmV0dXJuIG5ldyBocCh0aGlzLmgsdGhpcy5jLHRoaXMubC0xOCoobj8/MSksdGhpcy5vcGFjaXR5KX0scmdiOmZ1bmN0aW9uKCl7cmV0dXJuIHlhZSh0aGlzKS5yZ2IoKX19KSk7dmFyIGJnPWZ1bmN0aW9uIG4odCl7dmFyIGU9ZnVuY3Rpb24obil7cmV0dXJuIDE9PShuPStuKT96YTpmdW5jdGlvbih0LGUpe3JldHVybiBlLXQ/ZnVuY3Rpb24obix0LGUpe3JldHVybiBuPU1hdGgucG93KG4sZSksdD1NYXRoLnBvdyh0LGUpLW4sZT0xL2UsZnVuY3Rpb24oaSl7cmV0dXJuIE1hdGgucG93KG4raSp0LGUpfX0odCxlLG4pOl95KGlzTmFOKHQpP2U6dCl9fSh0KTtmdW5jdGlvbiBpKHIsbyl7dmFyIHM9ZSgocj1teShyKSkuciwobz1teShvKSkuciksYT1lKHIuZyxvLmcpLGw9ZShyLmIsby5iKSxjPXphKHIub3BhY2l0eSxvLm9wYWNpdHkpO3JldHVybiBmdW5jdGlvbih1KXtyZXR1cm4gci5yPXModSksci5nPWEodSksci5iPWwodSksci5vcGFjaXR5PWModSkscisiIn19cmV0dXJuIGkuZ2FtbWE9bixpfSgxKTtmdW5jdGlvbiB3YWUobil7cmV0dXJuIGZ1bmN0aW9uKHQpe3ZhciBzLGEsZT10Lmxlbmd0aCxpPW5ldyBBcnJheShlKSxyPW5ldyBBcnJheShlKSxvPW5ldyBBcnJheShlKTtmb3Iocz0wO3M8ZTsrK3MpYT1teSh0W3NdKSxpW3NdPWEucnx8MCxyW3NdPWEuZ3x8MCxvW3NdPWEuYnx8MDtyZXR1cm4gaT1uKGkpLHI9bihyKSxvPW4obyksYS5vcGFjaXR5PTEsZnVuY3Rpb24obCl7cmV0dXJuIGEucj1pKGwpLGEuZz1yKGwpLGEuYj1vKGwpLGErIiJ9fX12YXIgaXo9d2FlKGZ1bmN0aW9uKG4pe3ZhciB0PW4ubGVuZ3RoLTE7cmV0dXJuIGZ1bmN0aW9uKGUpe3ZhciBpPWU8PTA/ZT0wOmU+PTE/KGU9MSx0LTEpOk1hdGguZmxvb3IoZSp0KSxyPW5baV0sbz1uW2krMV07cmV0dXJuIG56KChlLWkvdCkqdCxpPjA/bltpLTFdOjIqci1vLHIsbyxpPHQtMT9uW2krMl06MipvLXIpfX0pO2Z1bmN0aW9uIFNhZShuLHQpe3R8fCh0PVtdKTt2YXIgcixlPW4/TWF0aC5taW4odC5sZW5ndGgsbi5sZW5ndGgpOjAsaT10LnNsaWNlKCk7cmV0dXJuIGZ1bmN0aW9uKG8pe2ZvcihyPTA7cjxlOysrcilpW3JdPW5bcl0qKDEtbykrdFtyXSpvO3JldHVybiBpfX1mdW5jdGlvbiBUYWUobix0KXt2YXIgcyxlPXQ/dC5sZW5ndGg6MCxpPW4/TWF0aC5taW4oZSxuLmxlbmd0aCk6MCxyPW5ldyBBcnJheShpKSxvPW5ldyBBcnJheShlKTtmb3Iocz0wO3M8aTsrK3MpcltzXT1mcChuW3NdLHRbc10pO2Zvcig7czxlOysrcylvW3NdPXRbc107cmV0dXJuIGZ1bmN0aW9uKGEpe2ZvcihzPTA7czxpOysrcylvW3NdPXJbc10oYSk7cmV0dXJuIG99fWZ1bmN0aW9uIERhZShuLHQpe3ZhciBlPW5ldyBEYXRlO3JldHVybiBuPStuLHQ9K3QsZnVuY3Rpb24oaSl7cmV0dXJuIGUuc2V0VGltZShuKigxLWkpK3QqaSksZX19ZnVuY3Rpb24gQ3Mobix0KXtyZXR1cm4gbj0rbix0PSt0LGZ1bmN0aW9uKGUpe3JldHVybiBuKigxLWUpK3QqZX19ZnVuY3Rpb24gQWFlKG4sdCl7dmFyIHIsZT17fSxpPXt9O2ZvcihyIGluKG51bGw9PT1ufHwib2JqZWN0IiE9dHlwZW9mIG4pJiYobj17fSksKG51bGw9PT10fHwib2JqZWN0IiE9dHlwZW9mIHQpJiYodD17fSksdClyIGluIG4/ZVtyXT1mcChuW3JdLHRbcl0pOmlbcl09dFtyXTtyZXR1cm4gZnVuY3Rpb24obyl7Zm9yKHIgaW4gZSlpW3JdPWVbcl0obyk7cmV0dXJuIGl9fXdhZShmdW5jdGlvbihuKXt2YXIgdD1uLmxlbmd0aDtyZXR1cm4gZnVuY3Rpb24oZSl7dmFyIGk9TWF0aC5mbG9vcigoKGUlPTEpPDA/KytlOmUpKnQpO3JldHVybiBueigoZS1pL3QpKnQsblsoaSt0LTEpJXRdLG5baSV0XSxuWyhpKzEpJXRdLG5bKGkrMikldF0pfX0pO3ZhciBvej0vWy0rXT8oPzpcZCtcLj9cZCp8XC4/XGQrKSg/OltlRV1bLStdP1xkKyk/L2cscno9bmV3IFJlZ0V4cChvei5zb3VyY2UsImciKTtmdW5jdGlvbiBLdyhuLHQpe3ZhciBpLHIsbyxlPW96Lmxhc3RJbmRleD1yei5sYXN0SW5kZXg9MCxzPS0xLGE9W10sbD1bXTtmb3Iobis9IiIsdCs9IiI7KGk9b3ouZXhlYyhuKSkmJihyPXJ6LmV4ZWModCkpOykobz1yLmluZGV4KT5lJiYobz10LnNsaWNlKGUsbyksYVtzXT9hW3NdKz1vOmFbKytzXT1vKSwoaT1pWzBdKT09PShyPXJbMF0pP2Fbc10/YVtzXSs9cjphWysrc109cjooYVsrK3NdPW51bGwsbC5wdXNoKHtpOnMseDpDcyhpLHIpfSkpLGU9cnoubGFzdEluZGV4O3JldHVybiBlPHQubGVuZ3RoJiYobz10LnNsaWNlKGUpLGFbc10/YVtzXSs9bzphWysrc109byksYS5sZW5ndGg8Mj9sWzBdP2Z1bmN0aW9uKG4pe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4gbih0KSsiIn19KGxbMF0ueCk6ZnVuY3Rpb24obil7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIG59fSh0KToodD1sLmxlbmd0aCxmdW5jdGlvbihjKXtmb3IodmFyIGQsdT0wO3U8dDsrK3UpYVsoZD1sW3VdKS5pXT1kLngoYyk7cmV0dXJuIGEuam9pbigiIil9KX1mdW5jdGlvbiBmcChuLHQpe3ZhciBpLGU9dHlwZW9mIHQ7cmV0dXJuIG51bGw9PXR8fCJib29sZWFuIj09PWU/X3kodCk6KCJudW1iZXIiPT09ZT9Dczoic3RyaW5nIj09PWU/KGk9cnUodCkpPyh0PWksYmcpOkt3OnQgaW5zdGFuY2VvZiBydT9iZzp0IGluc3RhbmNlb2YgRGF0ZT9EYWU6ZnVuY3Rpb24obil7cmV0dXJuIEFycmF5QnVmZmVyLmlzVmlldyhuKSYmIShuIGluc3RhbmNlb2YgRGF0YVZpZXcpfSh0KT9TYWU6QXJyYXkuaXNBcnJheSh0KT9UYWU6ImZ1bmN0aW9uIiE9dHlwZW9mIHQudmFsdWVPZiYmImZ1bmN0aW9uIiE9dHlwZW9mIHQudG9TdHJpbmd8fGlzTmFOKHQpP0FhZTpDcykobix0KX1mdW5jdGlvbiBzeihuLHQpe3JldHVybiBuPStuLHQ9K3QsZnVuY3Rpb24oZSl7cmV0dXJuIE1hdGgucm91bmQobiooMS1lKSt0KmUpfX12YXIgWncsbHosUGFlLFZSLElhZT0xODAvTWF0aC5QSSxCUj17dHJhbnNsYXRlWDowLHRyYW5zbGF0ZVk6MCxyb3RhdGU6MCxza2V3WDowLHNjYWxlWDoxLHNjYWxlWToxfTtmdW5jdGlvbiBheihuLHQsZSxpLHIsbyl7dmFyIHMsYSxsO3JldHVybihzPU1hdGguc3FydChuKm4rdCp0KSkmJihuLz1zLHQvPXMpLChsPW4qZSt0KmkpJiYoZS09bipsLGktPXQqbCksKGE9TWF0aC5zcXJ0KGUqZStpKmkpKSYmKGUvPWEsaS89YSxsLz1hKSxuKmk8dCplJiYobj0tbix0PS10LGw9LWwscz0tcykse3RyYW5zbGF0ZVg6cix0cmFuc2xhdGVZOm8scm90YXRlOk1hdGguYXRhbjIodCxuKSpJYWUsc2tld1g6TWF0aC5hdGFuKGwpKklhZSxzY2FsZVg6cyxzY2FsZVk6YX19ZnVuY3Rpb24ga2FlKG4sdCxlLGkpe2Z1bmN0aW9uIHIoYyl7cmV0dXJuIGMubGVuZ3RoP2MucG9wKCkrIiAiOiIifXJldHVybiBmdW5jdGlvbihjLHUpe3ZhciBkPVtdLHA9W107cmV0dXJuIGM9bihjKSx1PW4odSksZnVuY3Rpb24oYyx1LGQscCxoLGYpe2lmKGMhPT1kfHx1IT09cCl7dmFyIG09aC5wdXNoKCJ0cmFuc2xhdGUoIixudWxsLHQsbnVsbCxlKTtmLnB1c2goe2k6bS00LHg6Q3MoYyxkKX0se2k6bS0yLHg6Q3ModSxwKX0pfWVsc2UoZHx8cCkmJmgucHVzaCgidHJhbnNsYXRlKCIrZCt0K3ArZSl9KGMudHJhbnNsYXRlWCxjLnRyYW5zbGF0ZVksdS50cmFuc2xhdGVYLHUudHJhbnNsYXRlWSxkLHApLGZ1bmN0aW9uKGMsdSxkLHApe2MhPT11PyhjLXU+MTgwP3UrPTM2MDp1LWM+MTgwJiYoYys9MzYwKSxwLnB1c2goe2k6ZC5wdXNoKHIoZCkrInJvdGF0ZSgiLG51bGwsaSktMix4OkNzKGMsdSl9KSk6dSYmZC5wdXNoKHIoZCkrInJvdGF0ZSgiK3UraSl9KGMucm90YXRlLHUucm90YXRlLGQscCksZnVuY3Rpb24oYyx1LGQscCl7YyE9PXU/cC5wdXNoKHtpOmQucHVzaChyKGQpKyJza2V3WCgiLG51bGwsaSktMix4OkNzKGMsdSl9KTp1JiZkLnB1c2gocihkKSsic2tld1goIit1K2kpfShjLnNrZXdYLHUuc2tld1gsZCxwKSxmdW5jdGlvbihjLHUsZCxwLGgsZil7aWYoYyE9PWR8fHUhPT1wKXt2YXIgbT1oLnB1c2gocihoKSsic2NhbGUoIixudWxsLCIsIixudWxsLCIpIik7Zi5wdXNoKHtpOm0tNCx4OkNzKGMsZCl9LHtpOm0tMix4OkNzKHUscCl9KX1lbHNlKDEhPT1kfHwxIT09cCkmJmgucHVzaChyKGgpKyJzY2FsZSgiK2QrIiwiK3ArIikiKX0oYy5zY2FsZVgsYy5zY2FsZVksdS5zY2FsZVgsdS5zY2FsZVksZCxwKSxjPXU9bnVsbCxmdW5jdGlvbihoKXtmb3IodmFyIHgsZj0tMSxtPXAubGVuZ3RoOysrZjxtOylkWyh4PXBbZl0pLmldPXgueChoKTtyZXR1cm4gZC5qb2luKCIiKX19fXZhciBjej1rYWUoZnVuY3Rpb24obil7cmV0dXJuIm5vbmUiPT09bj9CUjooWnd8fChadz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJESVYiKSxsej1kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsUGFlPWRvY3VtZW50LmRlZmF1bHRWaWV3KSxady5zdHlsZS50cmFuc2Zvcm09bixuPVBhZS5nZXRDb21wdXRlZFN0eWxlKGx6LmFwcGVuZENoaWxkKFp3KSxudWxsKS5nZXRQcm9wZXJ0eVZhbHVlKCJ0cmFuc2Zvcm0iKSxsei5yZW1vdmVDaGlsZChadyksYXooKyhuPW4uc2xpY2UoNywtMSkuc3BsaXQoIiwiKSlbMF0sK25bMV0sK25bMl0sK25bM10sK25bNF0sK25bNV0pKX0sInB4LCAiLCJweCkiLCJkZWcpIiksdXo9a2FlKGZ1bmN0aW9uKG4pe3JldHVybiBudWxsPT1uP0JSOihWUnx8KFZSPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUygiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLCJnIikpLFZSLnNldEF0dHJpYnV0ZSgidHJhbnNmb3JtIixuKSwobj1WUi50cmFuc2Zvcm0uYmFzZVZhbC5jb25zb2xpZGF0ZSgpKT9heigobj1uLm1hdHJpeCkuYSxuLmIsbi5jLG4uZCxuLmUsbi5mKTpCUil9LCIsICIsIikiLCIpIik7ZnVuY3Rpb24gRmFlKG4pe3JldHVybiBmdW5jdGlvbih0LGUpe3ZhciBpPW4oKHQ9dmcodCkpLmgsKGU9dmcoZSkpLmgpLHI9emEodC5zLGUucyksbz16YSh0LmwsZS5sKSxzPXphKHQub3BhY2l0eSxlLm9wYWNpdHkpO3JldHVybiBmdW5jdGlvbihhKXtyZXR1cm4gdC5oPWkoYSksdC5zPXIoYSksdC5sPW8oYSksdC5vcGFjaXR5PXMoYSksdCsiIn19fXZhciBkej1GYWUoTFIpO2Z1bmN0aW9uIE5hZShuKXtyZXR1cm4gZnVuY3Rpb24odCxlKXt2YXIgaT1uKCh0PXlnKHQpKS5oLChlPXlnKGUpKS5oKSxyPXphKHQuYyxlLmMpLG89emEodC5sLGUubCkscz16YSh0Lm9wYWNpdHksZS5vcGFjaXR5KTtyZXR1cm4gZnVuY3Rpb24oYSl7cmV0dXJuIHQuaD1pKGEpLHQuYz1yKGEpLHQubD1vKGEpLHQub3BhY2l0eT1zKGEpLHQrIiJ9fX1GYWUoemEpO3ZhciBIUixlUyxwej1OYWUoTFIpLHZ5PShOYWUoemEpLDApLCR3PTAsSnc9MCxVUj0wLHhnPTAselI9MCx0Uz0ib2JqZWN0Ij09dHlwZW9mIHBlcmZvcm1hbmNlJiZwZXJmb3JtYW5jZS5ub3c/cGVyZm9ybWFuY2U6RGF0ZSxWYWU9Im9iamVjdCI9PXR5cGVvZiB3aW5kb3cmJndpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWU/d2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZS5iaW5kKHdpbmRvdyk6ZnVuY3Rpb24obil7c2V0VGltZW91dChuLDE3KX07ZnVuY3Rpb24geXkoKXtyZXR1cm4geGd8fChWYWUoQkhlKSx4Zz10Uy5ub3coKSt6Uil9ZnVuY3Rpb24gQkhlKCl7eGc9MH1mdW5jdGlvbiBuUygpe3RoaXMuX2NhbGw9dGhpcy5fdGltZT10aGlzLl9uZXh0PW51bGx9ZnVuY3Rpb24galIobix0LGUpe3ZhciBpPW5ldyBuUztyZXR1cm4gaS5yZXN0YXJ0KG4sdCxlKSxpfWZ1bmN0aW9uIExhZSgpe3hnPShVUj10Uy5ub3coKSkrelIsdnk9JHc9MDt0cnl7IWZ1bmN0aW9uKCl7eXkoKSwrK3Z5O2Zvcih2YXIgdCxuPUhSO247KSh0PXhnLW4uX3RpbWUpPj0wJiZuLl9jYWxsLmNhbGwobnVsbCx0KSxuPW4uX25leHQ7LS12eX0oKX1maW5hbGx5e3Z5PTAsZnVuY3Rpb24oKXtmb3IodmFyIG4sZSx0PUhSLGk9MS8wO3Q7KXQuX2NhbGw/KGk+dC5fdGltZSYmKGk9dC5fdGltZSksbj10LHQ9dC5fbmV4dCk6KGU9dC5fbmV4dCx0Ll9uZXh0PW51bGwsdD1uP24uX25leHQ9ZTpIUj1lKTtlUz1uLGh6KGkpfSgpLHhnPTB9fWZ1bmN0aW9uIFZIZSgpe3ZhciBuPXRTLm5vdygpLHQ9bi1VUjt0PjFlMyYmKHpSLT10LFVSPW4pfWZ1bmN0aW9uIGh6KG4pe3Z5fHwoJHcmJigkdz1jbGVhclRpbWVvdXQoJHcpKSxuLXhnPjI0PyhuPDEvMCYmKCR3PXNldFRpbWVvdXQoTGFlLG4tdFMubm93KCktelIpKSxKdyYmKEp3PWNsZWFySW50ZXJ2YWwoSncpKSk6KEp3fHwoVVI9dFMubm93KCksSnc9c2V0SW50ZXJ2YWwoVkhlLDFlMykpLHZ5PTEsVmFlKExhZSkpKX1mdW5jdGlvbiBHUihuLHQsZSl7dmFyIGk9bmV3IG5TO3JldHVybiBpLnJlc3RhcnQoZnVuY3Rpb24ocil7aS5zdG9wKCksbihyK3QpfSx0PW51bGw9PXQ/MDordCxlKSxpfW5TLnByb3RvdHlwZT1qUi5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOm5TLHJlc3RhcnQ6ZnVuY3Rpb24obix0LGUpe2lmKCJmdW5jdGlvbiIhPXR5cGVvZiBuKXRocm93IG5ldyBUeXBlRXJyb3IoImNhbGxiYWNrIGlzIG5vdCBhIGZ1bmN0aW9uIik7ZT0obnVsbD09ZT95eSgpOitlKSsobnVsbD09dD8wOit0KSwhdGhpcy5fbmV4dCYmZVMhPT10aGlzJiYoZVM/ZVMuX25leHQ9dGhpczpIUj10aGlzLGVTPXRoaXMpLHRoaXMuX2NhbGw9bix0aGlzLl90aW1lPWUsaHooKX0sc3RvcDpmdW5jdGlvbigpe3RoaXMuX2NhbGwmJih0aGlzLl9jYWxsPW51bGwsdGhpcy5fdGltZT0xLzAsaHooKSl9fTt2YXIgVUhlPUd3KCJzdGFydCIsImVuZCIsImNhbmNlbCIsImludGVycnVwdCIpLHpIZT1bXTtmdW5jdGlvbiAkaChuLHQsZSxpLHIsbyl7dmFyIHM9bi5fX3RyYW5zaXRpb247aWYocyl7aWYoZSBpbiBzKXJldHVybn1lbHNlIG4uX190cmFuc2l0aW9uPXt9OyFmdW5jdGlvbihuLHQsZSl7dmFyIHIsaT1uLl9fdHJhbnNpdGlvbjtmdW5jdGlvbiBzKGMpe3ZhciB1LGQscCxoO2lmKDEhPT1lLnN0YXRlKXJldHVybiBsKCk7Zm9yKHUgaW4gaSlpZigoaD1pW3VdKS5uYW1lPT09ZS5uYW1lKXtpZigzPT09aC5zdGF0ZSlyZXR1cm4gR1Iocyk7ND09PWguc3RhdGU/KGguc3RhdGU9NixoLnRpbWVyLnN0b3AoKSxoLm9uLmNhbGwoImludGVycnVwdCIsbixuLl9fZGF0YV9fLGguaW5kZXgsaC5ncm91cCksZGVsZXRlIGlbdV0pOit1PHQmJihoLnN0YXRlPTYsaC50aW1lci5zdG9wKCksaC5vbi5jYWxsKCJjYW5jZWwiLG4sbi5fX2RhdGFfXyxoLmluZGV4LGguZ3JvdXApLGRlbGV0ZSBpW3VdKX1pZihHUihmdW5jdGlvbigpezM9PT1lLnN0YXRlJiYoZS5zdGF0ZT00LGUudGltZXIucmVzdGFydChhLGUuZGVsYXksZS50aW1lKSxhKGMpKX0pLGUuc3RhdGU9MixlLm9uLmNhbGwoInN0YXJ0IixuLG4uX19kYXRhX18sZS5pbmRleCxlLmdyb3VwKSwyPT09ZS5zdGF0ZSl7Zm9yKGUuc3RhdGU9MyxyPW5ldyBBcnJheShwPWUudHdlZW4ubGVuZ3RoKSx1PTAsZD0tMTt1PHA7Kyt1KShoPWUudHdlZW5bdV0udmFsdWUuY2FsbChuLG4uX19kYXRhX18sZS5pbmRleCxlLmdyb3VwKSkmJihyWysrZF09aCk7ci5sZW5ndGg9ZCsxfX1mdW5jdGlvbiBhKGMpe2Zvcih2YXIgdT1jPGUuZHVyYXRpb24/ZS5lYXNlLmNhbGwobnVsbCxjL2UuZHVyYXRpb24pOihlLnRpbWVyLnJlc3RhcnQobCksZS5zdGF0ZT01LDEpLGQ9LTEscD1yLmxlbmd0aDsrK2Q8cDspcltkXS5jYWxsKG4sdSk7NT09PWUuc3RhdGUmJihlLm9uLmNhbGwoImVuZCIsbixuLl9fZGF0YV9fLGUuaW5kZXgsZS5ncm91cCksbCgpKX1mdW5jdGlvbiBsKCl7Zm9yKHZhciBjIGluIGUuc3RhdGU9NixlLnRpbWVyLnN0b3AoKSxkZWxldGUgaVt0XSxpKXJldHVybjtkZWxldGUgbi5fX3RyYW5zaXRpb259aVt0XT1lLGUudGltZXI9alIoZnVuY3Rpb24oYyl7ZS5zdGF0ZT0xLGUudGltZXIucmVzdGFydChzLGUuZGVsYXksZS50aW1lKSxlLmRlbGF5PD1jJiZzKGMtZS5kZWxheSl9LDAsZS50aW1lKX0obixlLHtuYW1lOnQsaW5kZXg6aSxncm91cDpyLG9uOlVIZSx0d2Vlbjp6SGUsdGltZTpvLnRpbWUsZGVsYXk6by5kZWxheSxkdXJhdGlvbjpvLmR1cmF0aW9uLGVhc2U6by5lYXNlLHRpbWVyOm51bGwsc3RhdGU6MH0pfWZ1bmN0aW9uIHJTKG4sdCl7dmFyIGU9WG8obix0KTtpZihlLnN0YXRlPjApdGhyb3cgbmV3IEVycm9yKCJ0b28gbGF0ZTsgYWxyZWFkeSBzY2hlZHVsZWQiKTtyZXR1cm4gZX1mdW5jdGlvbiBoYShuLHQpe3ZhciBlPVhvKG4sdCk7aWYoZS5zdGF0ZT4zKXRocm93IG5ldyBFcnJvcigidG9vIGxhdGU7IGFscmVhZHkgcnVubmluZyIpO3JldHVybiBlfWZ1bmN0aW9uIFhvKG4sdCl7dmFyIGU9bi5fX3RyYW5zaXRpb247aWYoIWV8fCEoZT1lW3RdKSl0aHJvdyBuZXcgRXJyb3IoInRyYW5zaXRpb24gbm90IGZvdW5kIik7cmV0dXJuIGV9ZnVuY3Rpb24gQ2cobix0KXt2YXIgaSxyLHMsZT1uLl9fdHJhbnNpdGlvbixvPSEwO2lmKGUpe2ZvcihzIGluIHQ9bnVsbD09dD9udWxsOnQrIiIsZSkoaT1lW3NdKS5uYW1lPT09dD8ocj1pLnN0YXRlPjImJmkuc3RhdGU8NSxpLnN0YXRlPTYsaS50aW1lci5zdG9wKCksaS5vbi5jYWxsKHI/ImludGVycnVwdCI6ImNhbmNlbCIsbixuLl9fZGF0YV9fLGkuaW5kZXgsaS5ncm91cCksZGVsZXRlIGVbc10pOm89ITE7byYmZGVsZXRlIG4uX190cmFuc2l0aW9ufX1mdW5jdGlvbiBHSGUobix0KXt2YXIgZSxpO3JldHVybiBmdW5jdGlvbigpe3ZhciByPWhhKHRoaXMsbiksbz1yLnR3ZWVuO2lmKG8hPT1lKWZvcih2YXIgcz0wLGE9KGk9ZT1vKS5sZW5ndGg7czxhOysrcylpZihpW3NdLm5hbWU9PT10KXsoaT1pLnNsaWNlKCkpLnNwbGljZShzLDEpO2JyZWFrfXIudHdlZW49aX19ZnVuY3Rpb24gV0hlKG4sdCxlKXt2YXIgaSxyO2lmKCJmdW5jdGlvbiIhPXR5cGVvZiBlKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbz1oYSh0aGlzLG4pLHM9by50d2VlbjtpZihzIT09aSl7cj0oaT1zKS5zbGljZSgpO2Zvcih2YXIgYT17bmFtZTp0LHZhbHVlOmV9LGw9MCxjPXIubGVuZ3RoO2w8YzsrK2wpaWYocltsXS5uYW1lPT09dCl7cltsXT1hO2JyZWFrfWw9PT1jJiZyLnB1c2goYSl9by50d2Vlbj1yfX1mdW5jdGlvbiBieShuLHQsZSl7dmFyIGk9bi5faWQ7cmV0dXJuIG4uZWFjaChmdW5jdGlvbigpe3ZhciByPWhhKHRoaXMsaSk7KHIudmFsdWV8fChyLnZhbHVlPXt9KSlbdF09ZS5hcHBseSh0aGlzLGFyZ3VtZW50cyl9KSxmdW5jdGlvbihyKXtyZXR1cm4gWG8ocixpKS52YWx1ZVt0XX19ZnVuY3Rpb24gWFIobix0KXt2YXIgZTtyZXR1cm4oIm51bWJlciI9PXR5cGVvZiB0P0NzOnQgaW5zdGFuY2VvZiBydT9iZzooZT1ydSh0KSk/KHQ9ZSxiZyk6S3cpKG4sdCl9ZnVuY3Rpb24gcUhlKG4pe3JldHVybiBmdW5jdGlvbigpe3RoaXMucmVtb3ZlQXR0cmlidXRlKG4pfX1mdW5jdGlvbiBZSGUobil7cmV0dXJuIGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmVBdHRyaWJ1dGVOUyhuLnNwYWNlLG4ubG9jYWwpfX1mdW5jdGlvbiBYSGUobix0LGUpe3ZhciBpLG8scj1lKyIiO3JldHVybiBmdW5jdGlvbigpe3ZhciBzPXRoaXMuZ2V0QXR0cmlidXRlKG4pO3JldHVybiBzPT09cj9udWxsOnM9PT1pP286bz10KGk9cyxlKX19ZnVuY3Rpb24gUUhlKG4sdCxlKXt2YXIgaSxvLHI9ZSsiIjtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgcz10aGlzLmdldEF0dHJpYnV0ZU5TKG4uc3BhY2Usbi5sb2NhbCk7cmV0dXJuIHM9PT1yP251bGw6cz09PWk/bzpvPXQoaT1zLGUpfX1mdW5jdGlvbiBLSGUobix0LGUpe3ZhciBpLHIsbztyZXR1cm4gZnVuY3Rpb24oKXt2YXIgcyxsLGE9ZSh0aGlzKTtyZXR1cm4gbnVsbD09YT92b2lkIHRoaXMucmVtb3ZlQXR0cmlidXRlKG4pOihzPXRoaXMuZ2V0QXR0cmlidXRlKG4pKT09PShsPWErIiIpP251bGw6cz09PWkmJmw9PT1yP286KHI9bCxvPXQoaT1zLGEpKX19ZnVuY3Rpb24gWkhlKG4sdCxlKXt2YXIgaSxyLG87cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHMsbCxhPWUodGhpcyk7cmV0dXJuIG51bGw9PWE/dm9pZCB0aGlzLnJlbW92ZUF0dHJpYnV0ZU5TKG4uc3BhY2Usbi5sb2NhbCk6KHM9dGhpcy5nZXRBdHRyaWJ1dGVOUyhuLnNwYWNlLG4ubG9jYWwpKT09PShsPWErIiIpP251bGw6cz09PWkmJmw9PT1yP286KHI9bCxvPXQoaT1zLGEpKX19ZnVuY3Rpb24gSkhlKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKGUpe3RoaXMuc2V0QXR0cmlidXRlKG4sdC5jYWxsKHRoaXMsZSkpfX1mdW5jdGlvbiAkSGUobix0KXtyZXR1cm4gZnVuY3Rpb24oZSl7dGhpcy5zZXRBdHRyaWJ1dGVOUyhuLnNwYWNlLG4ubG9jYWwsdC5jYWxsKHRoaXMsZSkpfX1mdW5jdGlvbiBlVWUobix0KXt2YXIgZSxpO2Z1bmN0aW9uIHIoKXt2YXIgbz10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyZXR1cm4gbyE9PWkmJihlPShpPW8pJiYkSGUobixvKSksZX1yZXR1cm4gci5fdmFsdWU9dCxyfWZ1bmN0aW9uIHRVZShuLHQpe3ZhciBlLGk7ZnVuY3Rpb24gcigpe3ZhciBvPXQuYXBwbHkodGhpcyxhcmd1bWVudHMpO3JldHVybiBvIT09aSYmKGU9KGk9bykmJkpIZShuLG8pKSxlfXJldHVybiByLl92YWx1ZT10LHJ9ZnVuY3Rpb24gblVlKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKCl7clModGhpcyxuKS5kZWxheT0rdC5hcHBseSh0aGlzLGFyZ3VtZW50cyl9fWZ1bmN0aW9uIGlVZShuLHQpe3JldHVybiB0PSt0LGZ1bmN0aW9uKCl7clModGhpcyxuKS5kZWxheT10fX1mdW5jdGlvbiByVWUobix0KXtyZXR1cm4gZnVuY3Rpb24oKXtoYSh0aGlzLG4pLmR1cmF0aW9uPSt0LmFwcGx5KHRoaXMsYXJndW1lbnRzKX19ZnVuY3Rpb24gb1VlKG4sdCl7cmV0dXJuIHQ9K3QsZnVuY3Rpb24oKXtoYSh0aGlzLG4pLmR1cmF0aW9uPXR9fWZ1bmN0aW9uIHNVZShuLHQpe2lmKCJmdW5jdGlvbiIhPXR5cGVvZiB0KXRocm93IG5ldyBFcnJvcjtyZXR1cm4gZnVuY3Rpb24oKXtoYSh0aGlzLG4pLmVhc2U9dH19ZnVuY3Rpb24gbFVlKG4sdCxlKXt2YXIgaSxyLG89ZnVuY3Rpb24obil7cmV0dXJuKG4rIiIpLnRyaW0oKS5zcGxpdCgvXnxccysvKS5ldmVyeShmdW5jdGlvbih0KXt2YXIgZT10LmluZGV4T2YoIi4iKTtyZXR1cm4gZT49MCYmKHQ9dC5zbGljZSgwLGUpKSwhdHx8InN0YXJ0Ij09PXR9KX0odCk/clM6aGE7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHM9byh0aGlzLG4pLGE9cy5vbjthIT09aSYmKHI9KGk9YSkuY29weSgpKS5vbih0LGUpLHMub249cn19dmFyIHVVZT1wcC5wcm90b3R5cGUuY29uc3RydWN0b3I7ZnVuY3Rpb24gaWxlKG4pe3JldHVybiBmdW5jdGlvbigpe3RoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkobil9fWZ1bmN0aW9uIG1VZShuLHQsZSl7cmV0dXJuIGZ1bmN0aW9uKGkpe3RoaXMuc3R5bGUuc2V0UHJvcGVydHkobix0LmNhbGwodGhpcyxpKSxlKX19ZnVuY3Rpb24gZ1VlKG4sdCxlKXt2YXIgaSxyO2Z1bmN0aW9uIG8oKXt2YXIgcz10LmFwcGx5KHRoaXMsYXJndW1lbnRzKTtyZXR1cm4gcyE9PXImJihpPShyPXMpJiZtVWUobixzLGUpKSxpfXJldHVybiBvLl92YWx1ZT10LG99ZnVuY3Rpb24geVVlKG4pe3JldHVybiBmdW5jdGlvbih0KXt0aGlzLnRleHRDb250ZW50PW4uY2FsbCh0aGlzLHQpfX1mdW5jdGlvbiBiVWUobil7dmFyIHQsZTtmdW5jdGlvbiBpKCl7dmFyIHI9bi5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cmV0dXJuIHIhPT1lJiYodD0oZT1yKSYmeVVlKHIpKSx0fXJldHVybiBpLl92YWx1ZT1uLGl9dmFyIHhVZT0wO2Z1bmN0aW9uIFVzKG4sdCxlLGkpe3RoaXMuX2dyb3Vwcz1uLHRoaXMuX3BhcmVudHM9dCx0aGlzLl9uYW1lPWUsdGhpcy5faWQ9aX1mdW5jdGlvbiBRUigpe3JldHVybisreFVlfXZhciB4eT1wcC5wcm90b3R5cGU7VXMucHJvdG90eXBlPWZ1bmN0aW9uKG4pe3JldHVybiBwcCgpLnRyYW5zaXRpb24obil9LnByb3RvdHlwZT17Y29uc3RydWN0b3I6VXMsc2VsZWN0OmZ1bmN0aW9uKG4pe3ZhciB0PXRoaXMuX25hbWUsZT10aGlzLl9pZDsiZnVuY3Rpb24iIT10eXBlb2YgbiYmKG49Z2cobikpO2Zvcih2YXIgaT10aGlzLl9ncm91cHMscj1pLmxlbmd0aCxvPW5ldyBBcnJheShyKSxzPTA7czxyOysrcylmb3IodmFyIHUsZCxhPWlbc10sbD1hLmxlbmd0aCxjPW9bc109bmV3IEFycmF5KGwpLHA9MDtwPGw7KytwKSh1PWFbcF0pJiYoZD1uLmNhbGwodSx1Ll9fZGF0YV9fLHAsYSkpJiYoIl9fZGF0YV9fImluIHUmJihkLl9fZGF0YV9fPXUuX19kYXRhX18pLGNbcF09ZCwkaChjW3BdLHQsZSxwLGMsWG8odSxlKSkpO3JldHVybiBuZXcgVXMobyx0aGlzLl9wYXJlbnRzLHQsZSl9LHNlbGVjdEFsbDpmdW5jdGlvbihuKXt2YXIgdD10aGlzLl9uYW1lLGU9dGhpcy5faWQ7ImZ1bmN0aW9uIiE9dHlwZW9mIG4mJihuPVd3KG4pKTtmb3IodmFyIGk9dGhpcy5fZ3JvdXBzLHI9aS5sZW5ndGgsbz1bXSxzPVtdLGE9MDthPHI7KythKWZvcih2YXIgdSxsPWlbYV0sYz1sLmxlbmd0aCxkPTA7ZDxjOysrZClpZih1PWxbZF0pe2Zvcih2YXIgaCxwPW4uY2FsbCh1LHUuX19kYXRhX18sZCxsKSxmPVhvKHUsZSksbT0wLHg9cC5sZW5ndGg7bTx4OysrbSkoaD1wW21dKSYmJGgoaCx0LGUsbSxwLGYpO28ucHVzaChwKSxzLnB1c2godSl9cmV0dXJuIG5ldyBVcyhvLHMsdCxlKX0sZmlsdGVyOmZ1bmN0aW9uKG4peyJmdW5jdGlvbiIhPXR5cGVvZiBuJiYobj1xdyhuKSk7Zm9yKHZhciB0PXRoaXMuX2dyb3VwcyxlPXQubGVuZ3RoLGk9bmV3IEFycmF5KGUpLHI9MDtyPGU7KytyKWZvcih2YXIgbCxvPXRbcl0scz1vLmxlbmd0aCxhPWlbcl09W10sYz0wO2M8czsrK2MpKGw9b1tjXSkmJm4uY2FsbChsLGwuX19kYXRhX18sYyxvKSYmYS5wdXNoKGwpO3JldHVybiBuZXcgVXMoaSx0aGlzLl9wYXJlbnRzLHRoaXMuX25hbWUsdGhpcy5faWQpfSxtZXJnZTpmdW5jdGlvbihuKXtpZihuLl9pZCE9PXRoaXMuX2lkKXRocm93IG5ldyBFcnJvcjtmb3IodmFyIHQ9dGhpcy5fZ3JvdXBzLGU9bi5fZ3JvdXBzLGk9dC5sZW5ndGgsbz1NYXRoLm1pbihpLGUubGVuZ3RoKSxzPW5ldyBBcnJheShpKSxhPTA7YTxvOysrYSlmb3IodmFyIHAsbD10W2FdLGM9ZVthXSx1PWwubGVuZ3RoLGQ9c1thXT1uZXcgQXJyYXkodSksaD0wO2g8dTsrK2gpKHA9bFtoXXx8Y1toXSkmJihkW2hdPXApO2Zvcig7YTxpOysrYSlzW2FdPXRbYV07cmV0dXJuIG5ldyBVcyhzLHRoaXMuX3BhcmVudHMsdGhpcy5fbmFtZSx0aGlzLl9pZCl9LHNlbGVjdGlvbjpmdW5jdGlvbigpe3JldHVybiBuZXcgdVVlKHRoaXMuX2dyb3Vwcyx0aGlzLl9wYXJlbnRzKX0sdHJhbnNpdGlvbjpmdW5jdGlvbigpe2Zvcih2YXIgbj10aGlzLl9uYW1lLHQ9dGhpcy5faWQsZT1RUigpLGk9dGhpcy5fZ3JvdXBzLHI9aS5sZW5ndGgsbz0wO288cjsrK28pZm9yKHZhciBsLHM9aVtvXSxhPXMubGVuZ3RoLGM9MDtjPGE7KytjKWlmKGw9c1tjXSl7dmFyIHU9WG8obCx0KTskaChsLG4sZSxjLHMse3RpbWU6dS50aW1lK3UuZGVsYXkrdS5kdXJhdGlvbixkZWxheTowLGR1cmF0aW9uOnUuZHVyYXRpb24sZWFzZTp1LmVhc2V9KX1yZXR1cm4gbmV3IFVzKGksdGhpcy5fcGFyZW50cyxuLGUpfSxjYWxsOnh5LmNhbGwsbm9kZXM6eHkubm9kZXMsbm9kZTp4eS5ub2RlLHNpemU6eHkuc2l6ZSxlbXB0eTp4eS5lbXB0eSxlYWNoOnh5LmVhY2gsb246ZnVuY3Rpb24obix0KXt2YXIgZT10aGlzLl9pZDtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aDwyP1hvKHRoaXMubm9kZSgpLGUpLm9uLm9uKG4pOnRoaXMuZWFjaChsVWUoZSxuLHQpKX0sYXR0cjpmdW5jdGlvbihuLHQpe3ZhciBlPWRwKG4pLGk9InRyYW5zZm9ybSI9PT1lP3V6OlhSO3JldHVybiB0aGlzLmF0dHJUd2VlbihuLCJmdW5jdGlvbiI9PXR5cGVvZiB0PyhlLmxvY2FsP1pIZTpLSGUpKGUsaSxieSh0aGlzLCJhdHRyLiIrbix0KSk6bnVsbD09dD8oZS5sb2NhbD9ZSGU6cUhlKShlKTooZS5sb2NhbD9RSGU6WEhlKShlLGksdCkpfSxhdHRyVHdlZW46ZnVuY3Rpb24obix0KXt2YXIgZT0iYXR0ci4iK247aWYoYXJndW1lbnRzLmxlbmd0aDwyKXJldHVybihlPXRoaXMudHdlZW4oZSkpJiZlLl92YWx1ZTtpZihudWxsPT10KXJldHVybiB0aGlzLnR3ZWVuKGUsbnVsbCk7aWYoImZ1bmN0aW9uIiE9dHlwZW9mIHQpdGhyb3cgbmV3IEVycm9yO3ZhciBpPWRwKG4pO3JldHVybiB0aGlzLnR3ZWVuKGUsKGkubG9jYWw/ZVVlOnRVZSkoaSx0KSl9LHN0eWxlOmZ1bmN0aW9uKG4sdCxlKXt2YXIgaT0idHJhbnNmb3JtIj09KG4rPSIiKT9jejpYUjtyZXR1cm4gbnVsbD09dD90aGlzLnN0eWxlVHdlZW4obixmdW5jdGlvbihuLHQpe3ZhciBlLGkscjtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgbz1aaCh0aGlzLG4pLHM9KHRoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkobiksWmgodGhpcyxuKSk7cmV0dXJuIG89PT1zP251bGw6bz09PWUmJnM9PT1pP3I6cj10KGU9byxpPXMpfX0obixpKSkub24oImVuZC5zdHlsZS4iK24saWxlKG4pKToiZnVuY3Rpb24iPT10eXBlb2YgdD90aGlzLnN0eWxlVHdlZW4obixmdW5jdGlvbihuLHQsZSl7dmFyIGkscixvO3JldHVybiBmdW5jdGlvbigpe3ZhciBzPVpoKHRoaXMsbiksYT1lKHRoaXMpLGw9YSsiIjtyZXR1cm4gbnVsbD09YSYmKHRoaXMuc3R5bGUucmVtb3ZlUHJvcGVydHkobiksbD1hPVpoKHRoaXMsbikpLHM9PT1sP251bGw6cz09PWkmJmw9PT1yP286KHI9bCxvPXQoaT1zLGEpKX19KG4saSxieSh0aGlzLCJzdHlsZS4iK24sdCkpKS5lYWNoKGZ1bmN0aW9uKG4sdCl7dmFyIGUsaSxyLGEsbz0ic3R5bGUuIit0LHM9ImVuZC4iK287cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIGw9aGEodGhpcyxuKSxjPWwub24sdT1udWxsPT1sLnZhbHVlW29dP2F8fChhPWlsZSh0KSk6dm9pZCAwOyhjIT09ZXx8ciE9PXUpJiYoaT0oZT1jKS5jb3B5KCkpLm9uKHMscj11KSxsLm9uPWl9fSh0aGlzLl9pZCxuKSk6dGhpcy5zdHlsZVR3ZWVuKG4sZnVuY3Rpb24obix0LGUpe3ZhciBpLG8scj1lKyIiO3JldHVybiBmdW5jdGlvbigpe3ZhciBzPVpoKHRoaXMsbik7cmV0dXJuIHM9PT1yP251bGw6cz09PWk/bzpvPXQoaT1zLGUpfX0obixpLHQpLGUpLm9uKCJlbmQuc3R5bGUuIituLG51bGwpfSxzdHlsZVR3ZWVuOmZ1bmN0aW9uKG4sdCxlKXt2YXIgaT0ic3R5bGUuIisobis9IiIpO2lmKGFyZ3VtZW50cy5sZW5ndGg8MilyZXR1cm4oaT10aGlzLnR3ZWVuKGkpKSYmaS5fdmFsdWU7aWYobnVsbD09dClyZXR1cm4gdGhpcy50d2VlbihpLG51bGwpO2lmKCJmdW5jdGlvbiIhPXR5cGVvZiB0KXRocm93IG5ldyBFcnJvcjtyZXR1cm4gdGhpcy50d2VlbihpLGdVZShuLHQsZT8/IiIpKX0sdGV4dDpmdW5jdGlvbihuKXtyZXR1cm4gdGhpcy50d2VlbigidGV4dCIsImZ1bmN0aW9uIj09dHlwZW9mIG4/ZnVuY3Rpb24obil7cmV0dXJuIGZ1bmN0aW9uKCl7dmFyIHQ9bih0aGlzKTt0aGlzLnRleHRDb250ZW50PXQ/PyIifX0oYnkodGhpcywidGV4dCIsbikpOmZ1bmN0aW9uKG4pe3JldHVybiBmdW5jdGlvbigpe3RoaXMudGV4dENvbnRlbnQ9bn19KG51bGw9PW4/IiI6bisiIikpfSx0ZXh0VHdlZW46ZnVuY3Rpb24obil7dmFyIHQ9InRleHQiO2lmKGFyZ3VtZW50cy5sZW5ndGg8MSlyZXR1cm4odD10aGlzLnR3ZWVuKHQpKSYmdC5fdmFsdWU7aWYobnVsbD09bilyZXR1cm4gdGhpcy50d2Vlbih0LG51bGwpO2lmKCJmdW5jdGlvbiIhPXR5cGVvZiBuKXRocm93IG5ldyBFcnJvcjtyZXR1cm4gdGhpcy50d2Vlbih0LGJVZShuKSl9LHJlbW92ZTpmdW5jdGlvbigpe3JldHVybiB0aGlzLm9uKCJlbmQucmVtb3ZlIixmdW5jdGlvbihuKXtyZXR1cm4gZnVuY3Rpb24oKXt2YXIgdD10aGlzLnBhcmVudE5vZGU7Zm9yKHZhciBlIGluIHRoaXMuX190cmFuc2l0aW9uKWlmKCtlIT09bilyZXR1cm47dCYmdC5yZW1vdmVDaGlsZCh0aGlzKX19KHRoaXMuX2lkKSl9LHR3ZWVuOmZ1bmN0aW9uKG4sdCl7dmFyIGU9dGhpcy5faWQ7aWYobis9IiIsYXJndW1lbnRzLmxlbmd0aDwyKXtmb3IodmFyIHMsaT1Ybyh0aGlzLm5vZGUoKSxlKS50d2VlbixyPTAsbz1pLmxlbmd0aDtyPG87KytyKWlmKChzPWlbcl0pLm5hbWU9PT1uKXJldHVybiBzLnZhbHVlO3JldHVybiBudWxsfXJldHVybiB0aGlzLmVhY2goKG51bGw9PXQ/R0hlOldIZSkoZSxuLHQpKX0sZGVsYXk6ZnVuY3Rpb24obil7dmFyIHQ9dGhpcy5faWQ7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5lYWNoKCgiZnVuY3Rpb24iPT10eXBlb2Ygbj9uVWU6aVVlKSh0LG4pKTpYbyh0aGlzLm5vZGUoKSx0KS5kZWxheX0sZHVyYXRpb246ZnVuY3Rpb24obil7dmFyIHQ9dGhpcy5faWQ7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/dGhpcy5lYWNoKCgiZnVuY3Rpb24iPT10eXBlb2Ygbj9yVWU6b1VlKSh0LG4pKTpYbyh0aGlzLm5vZGUoKSx0KS5kdXJhdGlvbn0sZWFzZTpmdW5jdGlvbihuKXt2YXIgdD10aGlzLl9pZDtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD90aGlzLmVhY2goc1VlKHQsbikpOlhvKHRoaXMubm9kZSgpLHQpLmVhc2V9LGVuZDpmdW5jdGlvbigpe3ZhciBuLHQsZT10aGlzLGk9ZS5faWQscj1lLnNpemUoKTtyZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24obyxzKXt2YXIgYT17dmFsdWU6c30sbD17dmFsdWU6ZnVuY3Rpb24oKXswPT0tLXImJm8oKX19O2UuZWFjaChmdW5jdGlvbigpe3ZhciBjPWhhKHRoaXMsaSksdT1jLm9uO3UhPT1uJiYoKHQ9KG49dSkuY29weSgpKS5fLmNhbmNlbC5wdXNoKGEpLHQuXy5pbnRlcnJ1cHQucHVzaChhKSx0Ll8uZW5kLnB1c2gobCkpLGMub249dH0pfSl9fTt2YXIgZ3o9e3RpbWU6bnVsbCxkZWxheTowLGR1cmF0aW9uOjI1MCxlYXNlOmZ1bmN0aW9uKG4pe3JldHVybigobio9Mik8PTE/bipuKm46KG4tPTIpKm4qbisyKS8yfX07ZnVuY3Rpb24gQ1VlKG4sdCl7Zm9yKHZhciBlOyEoZT1uLl9fdHJhbnNpdGlvbil8fCEoZT1lW3RdKTspaWYoIShuPW4ucGFyZW50Tm9kZSkpcmV0dXJuIGd6LnRpbWU9eXkoKSxnejtyZXR1cm4gZX1mdW5jdGlvbiBaUihuKXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gbn19ZnVuY3Rpb24gZGxlKG4sdCxlKXt0aGlzLnRhcmdldD1uLHRoaXMudHlwZT10LHRoaXMuc2VsZWN0aW9uPWV9ZnVuY3Rpb24gX3ooKXtzaS5zdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24oKX1mdW5jdGlvbiBKUigpe3NpLnByZXZlbnREZWZhdWx0KCksc2kuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCl9cHAucHJvdG90eXBlLmludGVycnVwdD1mdW5jdGlvbihuKXtyZXR1cm4gdGhpcy5lYWNoKGZ1bmN0aW9uKCl7Q2codGhpcyxuKX0pfSxwcC5wcm90b3R5cGUudHJhbnNpdGlvbj1mdW5jdGlvbihuKXt2YXIgdCxlO24gaW5zdGFuY2VvZiBVcz8odD1uLl9pZCxuPW4uX25hbWUpOih0PVFSKCksKGU9Z3opLnRpbWU9eXkoKSxuPW51bGw9PW4/bnVsbDpuKyIiKTtmb3IodmFyIGk9dGhpcy5fZ3JvdXBzLHI9aS5sZW5ndGgsbz0wO288cjsrK28pZm9yKHZhciBsLHM9aVtvXSxhPXMubGVuZ3RoLGM9MDtjPGE7KytjKShsPXNbY10pJiYkaChsLG4sdCxjLHMsZXx8Q1VlKGwsdCkpO3JldHVybiBuZXcgVXMoaSx0aGlzLl9wYXJlbnRzLG4sdCl9O3ZhciBwbGU9e25hbWU6ImRyYWcifSx2ej17bmFtZToic3BhY2UifSxDeT17bmFtZToiaGFuZGxlIn0sTXk9e25hbWU6ImNlbnRlciJ9O2Z1bmN0aW9uIGhsZShuKXtyZXR1cm5bK25bMF0sK25bMV1dfWZ1bmN0aW9uIHh6KG4pe3JldHVybltobGUoblswXSksaGxlKG5bMV0pXX1mdW5jdGlvbiBNVWUobil7cmV0dXJuIGZ1bmN0aW9uKHQpe3JldHVybiBmdW5jdGlvbihuLHQsZSl7YXJndW1lbnRzLmxlbmd0aDwzJiYoZT10LHQ9UFIoKS5jaGFuZ2VkVG91Y2hlcyk7Zm9yKHZhciBvLGk9MCxyPXQ/dC5sZW5ndGg6MDtpPHI7KytpKWlmKChvPXRbaV0pLmlkZW50aWZpZXI9PT1lKXJldHVybiBSUihuLG8pO3JldHVybiBudWxsfSh0LHNpLnRvdWNoZXMsbil9fXZhciB5ej17bmFtZToieCIsaGFuZGxlczpbInciLCJlIl0ubWFwKG9TKSxpbnB1dDpmdW5jdGlvbihuLHQpe3JldHVybiBudWxsPT1uP251bGw6W1srblswXSx0WzBdWzFdXSxbK25bMV0sdFsxXVsxXV1dfSxvdXRwdXQ6ZnVuY3Rpb24obil7cmV0dXJuIG4mJltuWzBdWzBdLG5bMV1bMF1dfX0sJFI9e25hbWU6InkiLGhhbmRsZXM6WyJuIiwicyJdLm1hcChvUyksaW5wdXQ6ZnVuY3Rpb24obix0KXtyZXR1cm4gbnVsbD09bj9udWxsOltbdFswXVswXSwrblswXV0sW3RbMV1bMF0sK25bMV1dXX0sb3V0cHV0OmZ1bmN0aW9uKG4pe3JldHVybiBuJiZbblswXVsxXSxuWzFdWzFdXX19LG1wPShbIm4iLCJ3IiwiZSIsInMiLCJudyIsIm5lIiwic3ciLCJzZSJdLm1hcChvUykse292ZXJsYXk6ImNyb3NzaGFpciIsc2VsZWN0aW9uOiJtb3ZlIixuOiJucy1yZXNpemUiLGU6ImV3LXJlc2l6ZSIsczoibnMtcmVzaXplIix3OiJldy1yZXNpemUiLG53OiJud3NlLXJlc2l6ZSIsbmU6Im5lc3ctcmVzaXplIixzZToibndzZS1yZXNpemUiLHN3OiJuZXN3LXJlc2l6ZSJ9KSxmbGU9e2U6InciLHc6ImUiLG53OiJuZSIsbmU6Im53IixzZToic3ciLHN3OiJzZSJ9LG1sZT17bjoicyIsczoibiIsbnc6InN3IixuZToic2UiLHNlOiJuZSIsc3c6Im53In0sd1VlPXtvdmVybGF5OjEsc2VsZWN0aW9uOjEsbjpudWxsLGU6MSxzOm51bGwsdzotMSxudzotMSxuZToxLHNlOjEsc3c6LTF9LFNVZT17b3ZlcmxheToxLHNlbGVjdGlvbjoxLG46LTEsZTpudWxsLHM6MSx3Om51bGwsbnc6LTEsbmU6LTEsc2U6MSxzdzoxfTtmdW5jdGlvbiBvUyhuKXtyZXR1cm57dHlwZTpufX1mdW5jdGlvbiBFVWUoKXtyZXR1cm4hc2kuY3RybEtleSYmIXNpLmJ1dHRvbn1mdW5jdGlvbiBUVWUoKXt2YXIgbj10aGlzLm93bmVyU1ZHRWxlbWVudHx8dGhpcztyZXR1cm4gbi5oYXNBdHRyaWJ1dGUoInZpZXdCb3giKT9bWyhuPW4udmlld0JveC5iYXNlVmFsKS54LG4ueV0sW24ueCtuLndpZHRoLG4ueStuLmhlaWdodF1dOltbMCwwXSxbbi53aWR0aC5iYXNlVmFsLnZhbHVlLG4uaGVpZ2h0LmJhc2VWYWwudmFsdWVdXX1mdW5jdGlvbiBEVWUoKXtyZXR1cm4gbmF2aWdhdG9yLm1heFRvdWNoUG9pbnRzfHwib250b3VjaHN0YXJ0ImluIHRoaXN9ZnVuY3Rpb24gYnoobil7Zm9yKDshbi5fX2JydXNoOylpZighKG49bi5wYXJlbnROb2RlKSlyZXR1cm47cmV0dXJuIG4uX19icnVzaH1mdW5jdGlvbiBBVWUobil7cmV0dXJuIG5bMF1bMF09PT1uWzFdWzBdfHxuWzBdWzFdPT09blsxXVsxXX1NYXRoO3ZhciBNej1NYXRoLlBJLHd6PTIqTXosTWc9MWUtNixrVWU9d3otTWc7ZnVuY3Rpb24gU3ooKXt0aGlzLl94MD10aGlzLl95MD10aGlzLl94MT10aGlzLl95MT1udWxsLHRoaXMuXz0iIn1mdW5jdGlvbiBfbGUoKXtyZXR1cm4gbmV3IFN6fVN6LnByb3RvdHlwZT1fbGUucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjpTeixtb3ZlVG86ZnVuY3Rpb24obix0KXt0aGlzLl8rPSJNIisodGhpcy5feDA9dGhpcy5feDE9K24pKyIsIisodGhpcy5feTA9dGhpcy5feTE9K3QpfSxjbG9zZVBhdGg6ZnVuY3Rpb24oKXtudWxsIT09dGhpcy5feDEmJih0aGlzLl94MT10aGlzLl94MCx0aGlzLl95MT10aGlzLl95MCx0aGlzLl8rPSJaIil9LGxpbmVUbzpmdW5jdGlvbihuLHQpe3RoaXMuXys9IkwiKyh0aGlzLl94MT0rbikrIiwiKyh0aGlzLl95MT0rdCl9LHF1YWRyYXRpY0N1cnZlVG86ZnVuY3Rpb24obix0LGUsaSl7dGhpcy5fKz0iUSIrICtuKyIsIisgK3QrIiwiKyh0aGlzLl94MT0rZSkrIiwiKyh0aGlzLl95MT0raSl9LGJlemllckN1cnZlVG86ZnVuY3Rpb24obix0LGUsaSxyLG8pe3RoaXMuXys9IkMiKyArbisiLCIrICt0KyIsIisgK2UrIiwiKyAraSsiLCIrKHRoaXMuX3gxPStyKSsiLCIrKHRoaXMuX3kxPStvKX0sYXJjVG86ZnVuY3Rpb24obix0LGUsaSxyKXt2YXIgbz10aGlzLl94MSxzPXRoaXMuX3kxLGE9KGU9K2UpLShuPStuKSxsPShpPStpKS0odD0rdCksYz1vLW4sdT1zLXQsZD1jKmMrdSp1O2lmKChyPStyKTwwKXRocm93IG5ldyBFcnJvcigibmVnYXRpdmUgcmFkaXVzOiAiK3IpO2lmKG51bGw9PT10aGlzLl94MSl0aGlzLl8rPSJNIisodGhpcy5feDE9bikrIiwiKyh0aGlzLl95MT10KTtlbHNlIGlmKGQ+TWcpaWYoTWF0aC5hYnModSphLWwqYyk+TWcmJnIpe3ZhciBwPWUtbyxoPWktcyxmPWEqYStsKmwsbT1wKnAraCpoLHg9TWF0aC5zcXJ0KGYpLGc9TWF0aC5zcXJ0KGQpLGI9cipNYXRoLnRhbigoTXotTWF0aC5hY29zKChmK2QtbSkvKDIqeCpnKSkpLzIpLEQ9Yi9nLFQ9Yi94O01hdGguYWJzKEQtMSk+TWcmJih0aGlzLl8rPSJMIisobitEKmMpKyIsIisodCtEKnUpKSx0aGlzLl8rPSJBIityKyIsIityKyIsMCwwLCIrICsodSpwPmMqaCkrIiwiKyh0aGlzLl94MT1uK1QqYSkrIiwiKyh0aGlzLl95MT10K1QqbCl9ZWxzZSB0aGlzLl8rPSJMIisodGhpcy5feDE9bikrIiwiKyh0aGlzLl95MT10KX0sYXJjOmZ1bmN0aW9uKG4sdCxlLGkscixvKXtuPStuLHQ9K3Qsbz0hIW87dmFyIHM9KGU9K2UpKk1hdGguY29zKGkpLGE9ZSpNYXRoLnNpbihpKSxsPW4rcyxjPXQrYSx1PTFebyxkPW8/aS1yOnItaTtpZihlPDApdGhyb3cgbmV3IEVycm9yKCJuZWdhdGl2ZSByYWRpdXM6ICIrZSk7bnVsbD09PXRoaXMuX3gxP3RoaXMuXys9Ik0iK2wrIiwiK2M6KE1hdGguYWJzKHRoaXMuX3gxLWwpPk1nfHxNYXRoLmFicyh0aGlzLl95MS1jKT5NZykmJih0aGlzLl8rPSJMIitsKyIsIitjKSxlJiYoZDwwJiYoZD1kJXd6K3d6KSxkPmtVZT90aGlzLl8rPSJBIitlKyIsIitlKyIsMCwxLCIrdSsiLCIrKG4tcykrIiwiKyh0LWEpKyJBIitlKyIsIitlKyIsMCwxLCIrdSsiLCIrKHRoaXMuX3gxPWwpKyIsIisodGhpcy5feTE9Yyk6ZD5NZyYmKHRoaXMuXys9IkEiK2UrIiwiK2UrIiwwLCIrICsoZD49TXopKyIsIit1KyIsIisodGhpcy5feDE9bitlKk1hdGguY29zKHIpKSsiLCIrKHRoaXMuX3kxPXQrZSpNYXRoLnNpbihyKSkpKX0scmVjdDpmdW5jdGlvbihuLHQsZSxpKXt0aGlzLl8rPSJNIisodGhpcy5feDA9dGhpcy5feDE9K24pKyIsIisodGhpcy5feTA9dGhpcy5feTE9K3QpKyJoIisgK2UrInYiKyAraSsiaCIrLWUrIloifSx0b1N0cmluZzpmdW5jdGlvbigpe3JldHVybiB0aGlzLl99fTt2YXIgc1M9X2xlLE1sPSIkIjtmdW5jdGlvbiBlTygpe31mdW5jdGlvbiB2bGUobix0KXt2YXIgZT1uZXcgZU87aWYobiBpbnN0YW5jZW9mIGVPKW4uZWFjaChmdW5jdGlvbihhLGwpe2Uuc2V0KGwsYSl9KTtlbHNlIGlmKEFycmF5LmlzQXJyYXkobikpe3ZhciBvLGk9LTEscj1uLmxlbmd0aDtpZihudWxsPT10KWZvcig7KytpPHI7KWUuc2V0KGksbltpXSk7ZWxzZSBmb3IoOysraTxyOyllLnNldCh0KG89bltpXSxpLG4pLG8pfWVsc2UgaWYobilmb3IodmFyIHMgaW4gbillLnNldChzLG5bc10pO3JldHVybiBlfWVPLnByb3RvdHlwZT12bGUucHJvdG90eXBlPXtjb25zdHJ1Y3RvcjplTyxoYXM6ZnVuY3Rpb24obil7cmV0dXJuIE1sK24gaW4gdGhpc30sZ2V0OmZ1bmN0aW9uKG4pe3JldHVybiB0aGlzW01sK25dfSxzZXQ6ZnVuY3Rpb24obix0KXtyZXR1cm4gdGhpc1tNbCtuXT10LHRoaXN9LHJlbW92ZTpmdW5jdGlvbihuKXt2YXIgdD1NbCtuO3JldHVybiB0IGluIHRoaXMmJmRlbGV0ZSB0aGlzW3RdfSxjbGVhcjpmdW5jdGlvbigpe2Zvcih2YXIgbiBpbiB0aGlzKW5bMF09PT1NbCYmZGVsZXRlIHRoaXNbbl19LGtleXM6ZnVuY3Rpb24oKXt2YXIgbj1bXTtmb3IodmFyIHQgaW4gdGhpcyl0WzBdPT09TWwmJm4ucHVzaCh0LnNsaWNlKDEpKTtyZXR1cm4gbn0sdmFsdWVzOmZ1bmN0aW9uKCl7dmFyIG49W107Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PU1sJiZuLnB1c2godGhpc1t0XSk7cmV0dXJuIG59LGVudHJpZXM6ZnVuY3Rpb24oKXt2YXIgbj1bXTtmb3IodmFyIHQgaW4gdGhpcyl0WzBdPT09TWwmJm4ucHVzaCh7a2V5OnQuc2xpY2UoMSksdmFsdWU6dGhpc1t0XX0pO3JldHVybiBufSxzaXplOmZ1bmN0aW9uKCl7dmFyIG49MDtmb3IodmFyIHQgaW4gdGhpcyl0WzBdPT09TWwmJisrbjtyZXR1cm4gbn0sZW1wdHk6ZnVuY3Rpb24oKXtmb3IodmFyIG4gaW4gdGhpcylpZihuWzBdPT09TWwpcmV0dXJuITE7cmV0dXJuITB9LGVhY2g6ZnVuY3Rpb24obil7Zm9yKHZhciB0IGluIHRoaXMpdFswXT09PU1sJiZuKHRoaXNbdF0sdC5zbGljZSgxKSx0aGlzKX19O3ZhciBncD12bGU7ZnVuY3Rpb24gdE8oKXt9dmFyIHdnPWdwLnByb3RvdHlwZTtmdW5jdGlvbiBTZyhuLHQpe2lmKChlPShuPXQ/bi50b0V4cG9uZW50aWFsKHQtMSk6bi50b0V4cG9uZW50aWFsKCkpLmluZGV4T2YoImUiKSk8MClyZXR1cm4gbnVsbDt2YXIgZSxpPW4uc2xpY2UoMCxlKTtyZXR1cm5baS5sZW5ndGg+MT9pWzBdK2kuc2xpY2UoMik6aSwrbi5zbGljZShlKzEpXX1mdW5jdGlvbiBDZChuKXtyZXR1cm4obj1TZyhNYXRoLmFicyhuKSkpP25bMV06TmFOfXRPLnByb3RvdHlwZT1mdW5jdGlvbihuLHQpe3ZhciBlPW5ldyB0TztpZihuIGluc3RhbmNlb2YgdE8pbi5lYWNoKGZ1bmN0aW9uKG8pe2UuYWRkKG8pfSk7ZWxzZSBpZihuKXt2YXIgaT0tMSxyPW4ubGVuZ3RoO2lmKG51bGw9PXQpZm9yKDsrK2k8cjspZS5hZGQobltpXSk7ZWxzZSBmb3IoOysraTxyOyllLmFkZCh0KG5baV0saSxuKSl9cmV0dXJuIGV9LnByb3RvdHlwZT17Y29uc3RydWN0b3I6dE8saGFzOndnLmhhcyxhZGQ6ZnVuY3Rpb24obil7cmV0dXJuIHRoaXNbTWwrKG4rPSIiKV09bix0aGlzfSxyZW1vdmU6d2cucmVtb3ZlLGNsZWFyOndnLmNsZWFyLHZhbHVlczp3Zy5rZXlzLHNpemU6d2cuc2l6ZSxlbXB0eTp3Zy5lbXB0eSxlYWNoOndnLmVhY2h9LE1hdGgsTWF0aC5zcXJ0KDUpO3ZhciBFeixWVWU9L14oPzooLik/KFs8Pj1eXSkpPyhbK1wtKCBdKT8oWyQjXSk/KDApPyhcZCspPygsKT8oXC5cZCspPyh+KT8oW2EteiVdKT8kL2k7ZnVuY3Rpb24gZWYobil7aWYoISh0PVZVZS5leGVjKG4pKSl0aHJvdyBuZXcgRXJyb3IoImludmFsaWQgZm9ybWF0OiAiK24pO3ZhciB0O3JldHVybiBuZXcgbk8oe2ZpbGw6dFsxXSxhbGlnbjp0WzJdLHNpZ246dFszXSxzeW1ib2w6dFs0XSx6ZXJvOnRbNV0sd2lkdGg6dFs2XSxjb21tYTp0WzddLHByZWNpc2lvbjp0WzhdJiZ0WzhdLnNsaWNlKDEpLHRyaW06dFs5XSx0eXBlOnRbMTBdfSl9ZnVuY3Rpb24gbk8obil7dGhpcy5maWxsPXZvaWQgMD09PW4uZmlsbD8iICI6bi5maWxsKyIiLHRoaXMuYWxpZ249dm9pZCAwPT09bi5hbGlnbj8iPiI6bi5hbGlnbisiIix0aGlzLnNpZ249dm9pZCAwPT09bi5zaWduPyItIjpuLnNpZ24rIiIsdGhpcy5zeW1ib2w9dm9pZCAwPT09bi5zeW1ib2w/IiI6bi5zeW1ib2wrIiIsdGhpcy56ZXJvPSEhbi56ZXJvLHRoaXMud2lkdGg9dm9pZCAwPT09bi53aWR0aD92b2lkIDA6K24ud2lkdGgsdGhpcy5jb21tYT0hIW4uY29tbWEsdGhpcy5wcmVjaXNpb249dm9pZCAwPT09bi5wcmVjaXNpb24/dm9pZCAwOituLnByZWNpc2lvbix0aGlzLnRyaW09ISFuLnRyaW0sdGhpcy50eXBlPXZvaWQgMD09PW4udHlwZT8iIjpuLnR5cGUrIiJ9ZnVuY3Rpb24gVHoobix0KXt2YXIgZT1TZyhuLHQpO2lmKCFlKXJldHVybiBuKyIiO3ZhciBpPWVbMF0scj1lWzFdO3JldHVybiByPDA/IjAuIituZXcgQXJyYXkoLXIpLmpvaW4oIjAiKStpOmkubGVuZ3RoPnIrMT9pLnNsaWNlKDAscisxKSsiLiIraS5zbGljZShyKzEpOmkrbmV3IEFycmF5KHItaS5sZW5ndGgrMikuam9pbigiMCIpfWVmLnByb3RvdHlwZT1uTy5wcm90b3R5cGUsbk8ucHJvdG90eXBlLnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZmlsbCt0aGlzLmFsaWduK3RoaXMuc2lnbit0aGlzLnN5bWJvbCsodGhpcy56ZXJvPyIwIjoiIikrKHZvaWQgMD09PXRoaXMud2lkdGg/IiI6TWF0aC5tYXgoMSwwfHRoaXMud2lkdGgpKSsodGhpcy5jb21tYT8iLCI6IiIpKyh2b2lkIDA9PT10aGlzLnByZWNpc2lvbj8iIjoiLiIrTWF0aC5tYXgoMCwwfHRoaXMucHJlY2lzaW9uKSkrKHRoaXMudHJpbT8ifiI6IiIpK3RoaXMudHlwZX07dmFyIER6PXsiJSI6ZnVuY3Rpb24obix0KXtyZXR1cm4oMTAwKm4pLnRvRml4ZWQodCl9LGI6ZnVuY3Rpb24obil7cmV0dXJuIE1hdGgucm91bmQobikudG9TdHJpbmcoMil9LGM6ZnVuY3Rpb24obil7cmV0dXJuIG4rIiJ9LGQ6ZnVuY3Rpb24obil7cmV0dXJuIE1hdGguYWJzKG49TWF0aC5yb3VuZChuKSk+PTFlMjE/bi50b0xvY2FsZVN0cmluZygiZW4iKS5yZXBsYWNlKC8sL2csIiIpOm4udG9TdHJpbmcoMTApfSxlOmZ1bmN0aW9uKG4sdCl7cmV0dXJuIG4udG9FeHBvbmVudGlhbCh0KX0sZjpmdW5jdGlvbihuLHQpe3JldHVybiBuLnRvRml4ZWQodCl9LGc6ZnVuY3Rpb24obix0KXtyZXR1cm4gbi50b1ByZWNpc2lvbih0KX0sbzpmdW5jdGlvbihuKXtyZXR1cm4gTWF0aC5yb3VuZChuKS50b1N0cmluZyg4KX0scDpmdW5jdGlvbihuLHQpe3JldHVybiBUeigxMDAqbix0KX0scjpUeixzOmZ1bmN0aW9uKG4sdCl7dmFyIGU9U2cobix0KTtpZighZSlyZXR1cm4gbisiIjt2YXIgaT1lWzBdLHI9ZVsxXSxvPXItKEV6PTMqTWF0aC5tYXgoLTgsTWF0aC5taW4oOCxNYXRoLmZsb29yKHIvMykpKSkrMSxzPWkubGVuZ3RoO3JldHVybiBvPT09cz9pOm8+cz9pK25ldyBBcnJheShvLXMrMSkuam9pbigiMCIpOm8+MD9pLnNsaWNlKDAsbykrIi4iK2kuc2xpY2Uobyk6IjAuIituZXcgQXJyYXkoMS1vKS5qb2luKCIwIikrU2cobixNYXRoLm1heCgwLHQrby0xKSlbMF19LFg6ZnVuY3Rpb24obil7cmV0dXJuIE1hdGgucm91bmQobikudG9TdHJpbmcoMTYpLnRvVXBwZXJDYXNlKCl9LHg6ZnVuY3Rpb24obil7cmV0dXJuIE1hdGgucm91bmQobikudG9TdHJpbmcoMTYpfX07ZnVuY3Rpb24gQXoobil7cmV0dXJuIG59dmFyIGlPLHhvLHJPLEVsZT1BcnJheS5wcm90b3R5cGUubWFwLFRsZT1bInkiLCJ6IiwiYSIsImYiLCJwIiwibiIsIlx4YjUiLCJtIiwiIiwiayIsIk0iLCJHIiwiVCIsIlAiLCJFIiwiWiIsIlkiXTtmdW5jdGlvbiBkYygpe3JldHVybiBNYXRoLnJhbmRvbSgpfWlPPWZ1bmN0aW9uKG4pe3ZhciB0PXZvaWQgMD09PW4uZ3JvdXBpbmd8fHZvaWQgMD09PW4udGhvdXNhbmRzP0F6OmZ1bmN0aW9uKG4sdCl7cmV0dXJuIGZ1bmN0aW9uKGUsaSl7Zm9yKHZhciByPWUubGVuZ3RoLG89W10scz0wLGE9blswXSxsPTA7cj4wJiZhPjAmJihsK2ErMT5pJiYoYT1NYXRoLm1heCgxLGktbCkpLG8ucHVzaChlLnN1YnN0cmluZyhyLT1hLHIrYSkpLCEoKGwrPWErMSk+aSkpOylhPW5bcz0ocysxKSVuLmxlbmd0aF07cmV0dXJuIG8ucmV2ZXJzZSgpLmpvaW4odCl9fShFbGUuY2FsbChuLmdyb3VwaW5nLE51bWJlciksbi50aG91c2FuZHMrIiIpLGU9dm9pZCAwPT09bi5jdXJyZW5jeT8iIjpuLmN1cnJlbmN5WzBdKyIiLGk9dm9pZCAwPT09bi5jdXJyZW5jeT8iIjpuLmN1cnJlbmN5WzFdKyIiLHI9dm9pZCAwPT09bi5kZWNpbWFsPyIuIjpuLmRlY2ltYWwrIiIsbz12b2lkIDA9PT1uLm51bWVyYWxzP0F6OmZ1bmN0aW9uKG4pe3JldHVybiBmdW5jdGlvbih0KXtyZXR1cm4gdC5yZXBsYWNlKC9bMC05XS9nLGZ1bmN0aW9uKGUpe3JldHVybiBuWytlXX0pfX0oRWxlLmNhbGwobi5udW1lcmFscyxTdHJpbmcpKSxzPXZvaWQgMD09PW4ucGVyY2VudD8iJSI6bi5wZXJjZW50KyIiLGE9dm9pZCAwPT09bi5taW51cz8iLSI6bi5taW51cysiIixsPXZvaWQgMD09PW4ubmFuPyJOYU4iOm4ubmFuKyIiO2Z1bmN0aW9uIGMoZCl7dmFyIHA9KGQ9ZWYoZCkpLmZpbGwsaD1kLmFsaWduLGY9ZC5zaWduLG09ZC5zeW1ib2wseD1kLnplcm8sZz1kLndpZHRoLGI9ZC5jb21tYSxEPWQucHJlY2lzaW9uLFQ9ZC50cmltLGs9ZC50eXBlOyJuIj09PWs/KGI9ITAsaz0iZyIpOkR6W2tdfHwodm9pZCAwPT09RCYmKEQ9MTIpLFQ9ITAsaz0iZyIpLCh4fHwiMCI9PT1wJiYiPSI9PT1oKSYmKHg9ITAscD0iMCIsaD0iPSIpO3ZhciBaPSIkIj09PW0/ZToiIyI9PT1tJiYvW2JveFhdLy50ZXN0KGspPyIwIitrLnRvTG93ZXJDYXNlKCk6IiIsej0iJCI9PT1tP2k6L1slcF0vLnRlc3Qoayk/czoiIixmZT1EeltrXSx1ZT0vW2RlZmdwcnMlXS8udGVzdChrKTtmdW5jdGlvbiBoZSh3KXt2YXIgSyxkZSxZLEY9WixxPXo7aWYoImMiPT09aylxPWZlKHcpK3Esdz0iIjtlbHNle3ZhciBhZT0odz0rdyk8MHx8MS93PDA7aWYodz1pc05hTih3KT9sOmZlKE1hdGguYWJzKHcpLEQpLFQmJih3PWZ1bmN0aW9uKG4pe2U6Zm9yKHZhciByLHQ9bi5sZW5ndGgsZT0xLGk9LTE7ZTx0OysrZSlzd2l0Y2gobltlXSl7Y2FzZSIuIjppPXI9ZTticmVhaztjYXNlIjAiOjA9PT1pJiYoaT1lKSxyPWU7YnJlYWs7ZGVmYXVsdDppZighK25bZV0pYnJlYWsgZTtpPjAmJihpPTApfXJldHVybiBpPjA/bi5zbGljZSgwLGkpK24uc2xpY2UocisxKTpufSh3KSksYWUmJjA9PSt3JiYiKyIhPT1mJiYoYWU9ITEpLEY9KGFlPyIoIj09PWY/ZjphOiItIj09PWZ8fCIoIj09PWY/IiI6ZikrRixxPSgicyI9PT1rP1RsZVs4K0V6LzNdOiIiKStxKyhhZSYmIigiPT09Zj8iKSI6IiIpLHVlKWZvcihLPS0xLGRlPXcubGVuZ3RoOysrSzxkZTspaWYoNDg+KFk9dy5jaGFyQ29kZUF0KEspKXx8WT41Nyl7cT0oNDY9PT1ZP3Irdy5zbGljZShLKzEpOncuc2xpY2UoSykpK3Esdz13LnNsaWNlKDAsSyk7YnJlYWt9fWImJiF4JiYodz10KHcsMS8wKSk7dmFyIGxlPUYubGVuZ3RoK3cubGVuZ3RoK3EubGVuZ3RoLEllPWxlPGc/bmV3IEFycmF5KGctbGUrMSkuam9pbihwKToiIjtzd2l0Y2goYiYmeCYmKHc9dChJZSt3LEllLmxlbmd0aD9nLXEubGVuZ3RoOjEvMCksSWU9IiIpLGgpe2Nhc2UiPCI6dz1GK3crcStJZTticmVhaztjYXNlIj0iOnc9RitJZSt3K3E7YnJlYWs7Y2FzZSJeIjp3PUllLnNsaWNlKDAsbGU9SWUubGVuZ3RoPj4xKStGK3crcStJZS5zbGljZShsZSk7YnJlYWs7ZGVmYXVsdDp3PUllK0YrdytxfXJldHVybiBvKHcpfXJldHVybiBEPXZvaWQgMD09PUQ/NjovW2dwcnNdLy50ZXN0KGspP01hdGgubWF4KDEsTWF0aC5taW4oMjEsRCkpOk1hdGgubWF4KDAsTWF0aC5taW4oMjAsRCkpLGhlLnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIGQrIiJ9LGhlfXJldHVybntmb3JtYXQ6Yyxmb3JtYXRQcmVmaXg6ZnVuY3Rpb24oZCxwKXt2YXIgaD1jKCgoZD1lZihkKSkudHlwZT0iZiIsZCkpLGY9MypNYXRoLm1heCgtOCxNYXRoLm1pbig4LE1hdGguZmxvb3IoQ2QocCkvMykpKSxtPU1hdGgucG93KDEwLC1mKSx4PVRsZVs4K2YvM107cmV0dXJuIGZ1bmN0aW9uKGcpe3JldHVybiBoKG0qZykreH19fX0oe2RlY2ltYWw6Ii4iLHRob3VzYW5kczoiLCIsZ3JvdXBpbmc6WzNdLGN1cnJlbmN5OlsiJCIsIiJdLG1pbnVzOiItIn0pLHhvPWlPLmZvcm1hdCxyTz1pTy5mb3JtYXRQcmVmaXgsZnVuY3Rpb24gbih0KXtmdW5jdGlvbiBlKGkscil7cmV0dXJuIGk9bnVsbD09aT8wOitpLHI9bnVsbD09cj8xOityLDE9PT1hcmd1bWVudHMubGVuZ3RoPyhyPWksaT0wKTpyLT1pLGZ1bmN0aW9uKCl7cmV0dXJuIHQoKSpyK2l9fXJldHVybiBlLnNvdXJjZT1uLGV9KGRjKTt2YXIga3o9ZnVuY3Rpb24gbih0KXtmdW5jdGlvbiBlKGkscil7dmFyIG8scztyZXR1cm4gaT1udWxsPT1pPzA6K2kscj1udWxsPT1yPzE6K3IsZnVuY3Rpb24oKXt2YXIgYTtpZihudWxsIT1vKWE9byxvPW51bGw7ZWxzZSBkb3tvPTIqdCgpLTEsYT0yKnQoKS0xLHM9bypvK2EqYX13aGlsZSghc3x8cz4xKTtyZXR1cm4gaStyKmEqTWF0aC5zcXJ0KC0yKk1hdGgubG9nKHMpL3MpfX1yZXR1cm4gZS5zb3VyY2U9bixlfShkYyksRno9KGZ1bmN0aW9uIG4odCl7ZnVuY3Rpb24gZSgpe3ZhciBpPWt6LnNvdXJjZSh0KS5hcHBseSh0aGlzLGFyZ3VtZW50cyk7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIE1hdGguZXhwKGkoKSl9fXJldHVybiBlLnNvdXJjZT1uLGV9KGRjKSxmdW5jdGlvbiBuKHQpe2Z1bmN0aW9uIGUoaSl7cmV0dXJuIGZ1bmN0aW9uKCl7Zm9yKHZhciByPTAsbz0wO288aTsrK28pcis9dCgpO3JldHVybiByfX1yZXR1cm4gZS5zb3VyY2U9bixlfShkYykpO2Z1bmN0aW9uIHpzKG4sdCl7c3dpdGNoKGFyZ3VtZW50cy5sZW5ndGgpe2Nhc2UgMDpicmVhaztjYXNlIDE6dGhpcy5yYW5nZShuKTticmVhaztkZWZhdWx0OnRoaXMucmFuZ2UodCkuZG9tYWluKG4pfXJldHVybiB0aGlzfShmdW5jdGlvbiBuKHQpe2Z1bmN0aW9uIGUoaSl7dmFyIHI9Rnouc291cmNlKHQpKGkpO3JldHVybiBmdW5jdGlvbigpe3JldHVybiByKCkvaX19cmV0dXJuIGUuc291cmNlPW4sZX0pKGRjKSxmdW5jdGlvbiBuKHQpe2Z1bmN0aW9uIGUoaSl7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuLU1hdGgubG9nKDEtdCgpKS9pfX1yZXR1cm4gZS5zb3VyY2U9bixlfShkYyk7dmFyIEFsZT1BcnJheS5wcm90b3R5cGUsbFM9QWxlLm1hcCx0Zj1BbGUuc2xpY2UsTno9e25hbWU6ImltcGxpY2l0In07ZnVuY3Rpb24gY1MoKXt2YXIgbj1ncCgpLHQ9W10sZT1bXSxpPU56O2Z1bmN0aW9uIHIobyl7dmFyIHM9bysiIixhPW4uZ2V0KHMpO2lmKCFhKXtpZihpIT09TnopcmV0dXJuIGk7bi5zZXQocyxhPXQucHVzaChvKSl9cmV0dXJuIGVbKGEtMSklZS5sZW5ndGhdfXJldHVybiByLmRvbWFpbj1mdW5jdGlvbihvKXtpZighYXJndW1lbnRzLmxlbmd0aClyZXR1cm4gdC5zbGljZSgpO3Q9W10sbj1ncCgpO2Zvcih2YXIgbCxjLHM9LTEsYT1vLmxlbmd0aDsrK3M8YTspbi5oYXMoYz0obD1vW3NdKSsiIil8fG4uc2V0KGMsdC5wdXNoKGwpKTtyZXR1cm4gcn0sci5yYW5nZT1mdW5jdGlvbihvKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT10Zi5jYWxsKG8pLHIpOmUuc2xpY2UoKX0sci51bmtub3duPWZ1bmN0aW9uKG8pe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPW8scik6aX0sci5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIGNTKHQsZSkudW5rbm93bihpKX0senMuYXBwbHkocixhcmd1bWVudHMpLHJ9ZnVuY3Rpb24gd3koKXt2YXIgcixvLG49Y1MoKS51bmtub3duKHZvaWQgMCksdD1uLmRvbWFpbixlPW4ucmFuZ2UsaT1bMCwxXSxzPSExLGE9MCxsPTAsYz0uNTtmdW5jdGlvbiB1KCl7dmFyIGQ9dCgpLmxlbmd0aCxwPWlbMV08aVswXSxoPWlbcC0wXSxmPWlbMS1wXTtyPShmLWgpL01hdGgubWF4KDEsZC1hKzIqbCkscyYmKHI9TWF0aC5mbG9vcihyKSksaCs9KGYtaC1yKihkLWEpKSpjLG89ciooMS1hKSxzJiYoaD1NYXRoLnJvdW5kKGgpLG89TWF0aC5yb3VuZChvKSk7dmFyIG09S2goZCkubWFwKGZ1bmN0aW9uKHgpe3JldHVybiBoK3IqeH0pO3JldHVybiBlKHA/bS5yZXZlcnNlKCk6bSl9cmV0dXJuIGRlbGV0ZSBuLnVua25vd24sbi5kb21haW49ZnVuY3Rpb24oZCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQoZCksdSgpKTp0KCl9LG4ucmFuZ2U9ZnVuY3Rpb24oZCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9WytkWzBdLCtkWzFdXSx1KCkpOmkuc2xpY2UoKX0sbi5yYW5nZVJvdW5kPWZ1bmN0aW9uKGQpe3JldHVybiBpPVsrZFswXSwrZFsxXV0scz0hMCx1KCl9LG4uYmFuZHdpZHRoPWZ1bmN0aW9uKCl7cmV0dXJuIG99LG4uc3RlcD1mdW5jdGlvbigpe3JldHVybiByfSxuLnJvdW5kPWZ1bmN0aW9uKGQpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhzPSEhZCx1KCkpOnN9LG4ucGFkZGluZz1mdW5jdGlvbihkKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYT1NYXRoLm1pbigxLGw9K2QpLHUoKSk6YX0sbi5wYWRkaW5nSW5uZXI9ZnVuY3Rpb24oZCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGE9TWF0aC5taW4oMSxkKSx1KCkpOmF9LG4ucGFkZGluZ091dGVyPWZ1bmN0aW9uKGQpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhsPStkLHUoKSk6bH0sbi5hbGlnbj1mdW5jdGlvbihkKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oYz1NYXRoLm1heCgwLE1hdGgubWluKDEsZCkpLHUoKSk6Y30sbi5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIHd5KHQoKSxpKS5yb3VuZChzKS5wYWRkaW5nSW5uZXIoYSkucGFkZGluZ091dGVyKGwpLmFsaWduKGMpfSx6cy5hcHBseSh1KCksYXJndW1lbnRzKX1mdW5jdGlvbiBJbGUobil7dmFyIHQ9bi5jb3B5O3JldHVybiBuLnBhZGRpbmc9bi5wYWRkaW5nT3V0ZXIsZGVsZXRlIG4ucGFkZGluZ0lubmVyLGRlbGV0ZSBuLnBhZGRpbmdPdXRlcixuLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gSWxlKHQoKSl9LG59ZnVuY3Rpb24gU3koKXtyZXR1cm4gSWxlKHd5LmFwcGx5KG51bGwsYXJndW1lbnRzKS5wYWRkaW5nSW5uZXIoMSkpfWZ1bmN0aW9uIEx6KG4pe3JldHVybitufXZhciBSbGU9WzAsMV07ZnVuY3Rpb24gamEobil7cmV0dXJuIG59ZnVuY3Rpb24gQnoobix0KXtyZXR1cm4odC09bj0rbik/ZnVuY3Rpb24oZSl7cmV0dXJuKGUtbikvdH06ZnVuY3Rpb24obil7cmV0dXJuIGZ1bmN0aW9uKCl7cmV0dXJuIG59fShpc05hTih0KT9OYU46LjUpfWZ1bmN0aW9uIE9sZShuKXt2YXIgaSx0PW5bMF0sZT1uW24ubGVuZ3RoLTFdO3JldHVybiB0PmUmJihpPXQsdD1lLGU9aSksZnVuY3Rpb24ocil7cmV0dXJuIE1hdGgubWF4KHQsTWF0aC5taW4oZSxyKSl9fWZ1bmN0aW9uIEdVZShuLHQsZSl7dmFyIGk9blswXSxyPW5bMV0sbz10WzBdLHM9dFsxXTtyZXR1cm4gcjxpPyhpPUJ6KHIsaSksbz1lKHMsbykpOihpPUJ6KGksciksbz1lKG8scykpLGZ1bmN0aW9uKGEpe3JldHVybiBvKGkoYSkpfX1mdW5jdGlvbiBXVWUobix0LGUpe3ZhciBpPU1hdGgubWluKG4ubGVuZ3RoLHQubGVuZ3RoKS0xLHI9bmV3IEFycmF5KGkpLG89bmV3IEFycmF5KGkpLHM9LTE7Zm9yKG5baV08blswXSYmKG49bi5zbGljZSgpLnJldmVyc2UoKSx0PXQuc2xpY2UoKS5yZXZlcnNlKCkpOysrczxpOylyW3NdPUJ6KG5bc10sbltzKzFdKSxvW3NdPWUodFtzXSx0W3MrMV0pO3JldHVybiBmdW5jdGlvbihhKXt2YXIgbD1pdShuLGEsMSxpKS0xO3JldHVybiBvW2xdKHJbbF0oYSkpfX1mdW5jdGlvbiBuZihuLHQpe3JldHVybiB0LmRvbWFpbihuLmRvbWFpbigpKS5yYW5nZShuLnJhbmdlKCkpLmludGVycG9sYXRlKG4uaW50ZXJwb2xhdGUoKSkuY2xhbXAobi5jbGFtcCgpKS51bmtub3duKG4udW5rbm93bigpKX1mdW5jdGlvbiB1Uygpe3ZhciBpLHIsbyxhLGwsYyxuPVJsZSx0PVJsZSxlPWZwLHM9amE7ZnVuY3Rpb24gdSgpe3JldHVybiBhPU1hdGgubWluKG4ubGVuZ3RoLHQubGVuZ3RoKT4yP1dVZTpHVWUsbD1jPW51bGwsZH1mdW5jdGlvbiBkKHApe3JldHVybiBpc05hTihwPStwKT9vOihsfHwobD1hKG4ubWFwKGkpLHQsZSkpKShpKHMocCkpKX1yZXR1cm4gZC5pbnZlcnQ9ZnVuY3Rpb24ocCl7cmV0dXJuIHMocigoY3x8KGM9YSh0LG4ubWFwKGkpLENzKSkpKHApKSl9LGQuZG9tYWluPWZ1bmN0aW9uKHApe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPWxTLmNhbGwocCxMeikscz09PWphfHwocz1PbGUobikpLHUoKSk6bi5zbGljZSgpfSxkLnJhbmdlPWZ1bmN0aW9uKHApe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyh0PXRmLmNhbGwocCksdSgpKTp0LnNsaWNlKCl9LGQucmFuZ2VSb3VuZD1mdW5jdGlvbihwKXtyZXR1cm4gdD10Zi5jYWxsKHApLGU9c3osdSgpfSxkLmNsYW1wPWZ1bmN0aW9uKHApe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhzPXA/T2xlKG4pOmphLGQpOnMhPT1qYX0sZC5pbnRlcnBvbGF0ZT1mdW5jdGlvbihwKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT1wLHUoKSk6ZX0sZC51bmtub3duPWZ1bmN0aW9uKHApe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhvPXAsZCk6b30sZnVuY3Rpb24ocCxoKXtyZXR1cm4gaT1wLHI9aCx1KCl9fWZ1bmN0aW9uIGRTKG4sdCl7cmV0dXJuIHVTKCkobix0KX1mdW5jdGlvbiBFZyhuKXt2YXIgdD1uLmRvbWFpbjtyZXR1cm4gbi50aWNrcz1mdW5jdGlvbihlKXt2YXIgaT10KCk7cmV0dXJuIEh3KGlbMF0saVtpLmxlbmd0aC0xXSxlPz8xMCl9LG4udGlja0Zvcm1hdD1mdW5jdGlvbihlLGkpe3ZhciByPXQoKTtyZXR1cm4gZnVuY3Rpb24obix0LGUsaSl7dmFyIG8scj12ZChuLHQsZSk7c3dpdGNoKChpPWVmKGk/PyIsZiIpKS50eXBlKXtjYXNlInMiOnZhciBzPU1hdGgubWF4KE1hdGguYWJzKG4pLE1hdGguYWJzKHQpKTtyZXR1cm4gbnVsbD09aS5wcmVjaXNpb24mJiFpc05hTihvPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIE1hdGgubWF4KDAsMypNYXRoLm1heCgtOCxNYXRoLm1pbig4LE1hdGguZmxvb3IoQ2QodCkvMykpKS1DZChNYXRoLmFicyhuKSkpfShyLHMpKSYmKGkucHJlY2lzaW9uPW8pLHJPKGkscyk7Y2FzZSIiOmNhc2UiZSI6Y2FzZSJnIjpjYXNlInAiOmNhc2UiciI6bnVsbD09aS5wcmVjaXNpb24mJiFpc05hTihvPWZ1bmN0aW9uKG4sdCl7cmV0dXJuIG49TWF0aC5hYnMobiksdD1NYXRoLmFicyh0KS1uLE1hdGgubWF4KDAsQ2QodCktQ2QobikpKzF9KHIsTWF0aC5tYXgoTWF0aC5hYnMobiksTWF0aC5hYnModCkpKSkmJihpLnByZWNpc2lvbj1vLSgiZSI9PT1pLnR5cGUpKTticmVhaztjYXNlImYiOmNhc2UiJSI6bnVsbD09aS5wcmVjaXNpb24mJiFpc05hTihvPWZ1bmN0aW9uKG4pe3JldHVybiBNYXRoLm1heCgwLC1DZChNYXRoLmFicyhuKSkpfShyKSkmJihpLnByZWNpc2lvbj1vLTIqKCIlIj09PWkudHlwZSkpfXJldHVybiB4byhpKX0oclswXSxyW3IubGVuZ3RoLTFdLGU/PzEwLGkpfSxuLm5pY2U9ZnVuY3Rpb24oZSl7bnVsbD09ZSYmKGU9MTApO3ZhciBsLGk9dCgpLHI9MCxvPWkubGVuZ3RoLTEscz1pW3JdLGE9aVtvXTtyZXR1cm4gYTxzJiYobD1zLHM9YSxhPWwsbD1yLHI9byxvPWwpLChsPXB5KHMsYSxlKSk+MD9sPXB5KHM9TWF0aC5mbG9vcihzL2wpKmwsYT1NYXRoLmNlaWwoYS9sKSpsLGUpOmw8MCYmKGw9cHkocz1NYXRoLmNlaWwocypsKS9sLGE9TWF0aC5mbG9vcihhKmwpL2wsZSkpLGw+MD8oaVtyXT1NYXRoLmZsb29yKHMvbCkqbCxpW29dPU1hdGguY2VpbChhL2wpKmwsdChpKSk6bDwwJiYoaVtyXT1NYXRoLmNlaWwocypsKS9sLGlbb109TWF0aC5mbG9vcihhKmwpL2wsdChpKSksbn0sbn1mdW5jdGlvbiBRbygpe3ZhciBuPWRTKGphLGphKTtyZXR1cm4gbi5jb3B5PWZ1bmN0aW9uKCl7cmV0dXJuIG5mKG4sUW8oKSl9LHpzLmFwcGx5KG4sYXJndW1lbnRzKSxFZyhuKX1mdW5jdGlvbiBvTyhuLHQpe3ZhciBzLGU9MCxpPShuPW4uc2xpY2UoKSkubGVuZ3RoLTEscj1uW2VdLG89bltpXTtyZXR1cm4gbzxyJiYocz1lLGU9aSxpPXMscz1yLHI9byxvPXMpLG5bZV09dC5mbG9vcihyKSxuW2ldPXQuY2VpbChvKSxufWZ1bmN0aW9uIGtsZShuKXtyZXR1cm4gTWF0aC5sb2cobil9ZnVuY3Rpb24gRmxlKG4pe3JldHVybiBNYXRoLmV4cChuKX1mdW5jdGlvbiBxVWUobil7cmV0dXJuLU1hdGgubG9nKC1uKX1mdW5jdGlvbiBZVWUobil7cmV0dXJuLU1hdGguZXhwKC1uKX1mdW5jdGlvbiBYVWUobil7cmV0dXJuIGlzRmluaXRlKG4pPysoIjFlIituKTpuPDA/MDpufWZ1bmN0aW9uIE5sZShuKXtyZXR1cm4gZnVuY3Rpb24odCl7cmV0dXJuLW4oLXQpfX1mdW5jdGlvbiBIeihuKXt2YXIgcixvLHQ9bihrbGUsRmxlKSxlPXQuZG9tYWluLGk9MTA7ZnVuY3Rpb24gcygpe3JldHVybiByPWZ1bmN0aW9uKG4pe3JldHVybiBuPT09TWF0aC5FP01hdGgubG9nOjEwPT09biYmTWF0aC5sb2cxMHx8Mj09PW4mJk1hdGgubG9nMnx8KG49TWF0aC5sb2cobiksZnVuY3Rpb24odCl7cmV0dXJuIE1hdGgubG9nKHQpL259KX0oaSksbz1mdW5jdGlvbihuKXtyZXR1cm4gMTA9PT1uP1hVZTpuPT09TWF0aC5FP01hdGguZXhwOmZ1bmN0aW9uKHQpe3JldHVybiBNYXRoLnBvdyhuLHQpfX0oaSksZSgpWzBdPDA/KHI9TmxlKHIpLG89TmxlKG8pLG4ocVVlLFlVZSkpOm4oa2xlLEZsZSksdH1yZXR1cm4gdC5iYXNlPWZ1bmN0aW9uKGEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPSthLHMoKSk6aX0sdC5kb21haW49ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGUoYSkscygpKTplKCl9LHQudGlja3M9ZnVuY3Rpb24oYSl7dmFyIGQsbD1lKCksYz1sWzBdLHU9bFtsLmxlbmd0aC0xXTsoZD11PGMpJiYocD1jLGM9dSx1PXApO3ZhciBmLG0seCxwPXIoYyksaD1yKHUpLGc9bnVsbD09YT8xMDorYSxiPVtdO2lmKCEoaSUxKSYmaC1wPGcpe2lmKHA9TWF0aC5yb3VuZChwKS0xLGg9TWF0aC5yb3VuZChoKSsxLGM+MCl7Zm9yKDtwPGg7KytwKWZvcihtPTEsZj1vKHApO208aTsrK20paWYoISgoeD1mKm0pPGMpKXtpZih4PnUpYnJlYWs7Yi5wdXNoKHgpfX1lbHNlIGZvcig7cDxoOysrcClmb3IobT1pLTEsZj1vKHApO20+PTE7LS1tKWlmKCEoKHg9ZiptKTxjKSl7aWYoeD51KWJyZWFrO2IucHVzaCh4KX19ZWxzZSBiPUh3KHAsaCxNYXRoLm1pbihoLXAsZykpLm1hcChvKTtyZXR1cm4gZD9iLnJldmVyc2UoKTpifSx0LnRpY2tGb3JtYXQ9ZnVuY3Rpb24oYSxsKXtpZihudWxsPT1sJiYobD0xMD09PWk/Ii4wZSI6IiwiKSwiZnVuY3Rpb24iIT10eXBlb2YgbCYmKGw9eG8obCkpLGE9PT0xLzApcmV0dXJuIGw7bnVsbD09YSYmKGE9MTApO3ZhciBjPU1hdGgubWF4KDEsaSphL3QudGlja3MoKS5sZW5ndGgpO3JldHVybiBmdW5jdGlvbih1KXt2YXIgZD11L28oTWF0aC5yb3VuZChyKHUpKSk7cmV0dXJuIGQqaTxpLS41JiYoZCo9aSksZDw9Yz9sKHUpOiIifX0sdC5uaWNlPWZ1bmN0aW9uKCl7cmV0dXJuIGUob08oZSgpLHtmbG9vcjpmdW5jdGlvbihhKXtyZXR1cm4gbyhNYXRoLmZsb29yKHIoYSkpKX0sY2VpbDpmdW5jdGlvbihhKXtyZXR1cm4gbyhNYXRoLmNlaWwocihhKSkpfX0pKX0sdH1mdW5jdGlvbiBwUygpe3ZhciBuPUh6KHVTKCkpLmRvbWFpbihbMSwxMF0pO3JldHVybiBuLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gbmYobixwUygpKS5iYXNlKG4uYmFzZSgpKX0senMuYXBwbHkobixhcmd1bWVudHMpLG59dmFyIFV6PW5ldyBEYXRlLHp6PW5ldyBEYXRlO2Z1bmN0aW9uIHlyKG4sdCxlLGkpe2Z1bmN0aW9uIHIobyl7cmV0dXJuIG4obz0wPT09YXJndW1lbnRzLmxlbmd0aD9uZXcgRGF0ZTpuZXcgRGF0ZSgrbykpLG99cmV0dXJuIHIuZmxvb3I9ZnVuY3Rpb24obyl7cmV0dXJuIG4obz1uZXcgRGF0ZSgrbykpLG99LHIuY2VpbD1mdW5jdGlvbihvKXtyZXR1cm4gbihvPW5ldyBEYXRlKG8tMSkpLHQobywxKSxuKG8pLG99LHIucm91bmQ9ZnVuY3Rpb24obyl7dmFyIHM9cihvKSxhPXIuY2VpbChvKTtyZXR1cm4gby1zPGEtbz9zOmF9LHIub2Zmc2V0PWZ1bmN0aW9uKG8scyl7cmV0dXJuIHQobz1uZXcgRGF0ZSgrbyksbnVsbD09cz8xOk1hdGguZmxvb3IocykpLG99LHIucmFuZ2U9ZnVuY3Rpb24obyxzLGEpe3ZhciBjLGw9W107aWYobz1yLmNlaWwobyksYT1udWxsPT1hPzE6TWF0aC5mbG9vcihhKSwhKG88cyYmYT4wKSlyZXR1cm4gbDtkb3tsLnB1c2goYz1uZXcgRGF0ZSgrbykpLHQobyxhKSxuKG8pfXdoaWxlKGM8byYmbzxzKTtyZXR1cm4gbH0sci5maWx0ZXI9ZnVuY3Rpb24obyl7cmV0dXJuIHlyKGZ1bmN0aW9uKHMpe2lmKHM+PXMpZm9yKDtuKHMpLCFvKHMpOylzLnNldFRpbWUocy0xKX0sZnVuY3Rpb24ocyxhKXtpZihzPj1zKWlmKGE8MClmb3IoOysrYTw9MDspZm9yKDt0KHMsLTEpLCFvKHMpOyk7ZWxzZSBmb3IoOy0tYT49MDspZm9yKDt0KHMsMSksIW8ocyk7KTt9KX0sZSYmKHIuY291bnQ9ZnVuY3Rpb24obyxzKXtyZXR1cm4gVXouc2V0VGltZSgrbyksenouc2V0VGltZSgrcyksbihVeiksbih6eiksTWF0aC5mbG9vcihlKFV6LHp6KSl9LHIuZXZlcnk9ZnVuY3Rpb24obyl7cmV0dXJuIG89TWF0aC5mbG9vcihvKSxpc0Zpbml0ZShvKSYmbz4wP28+MT9yLmZpbHRlcihpP2Z1bmN0aW9uKHMpe3JldHVybiBpKHMpJW89PTB9OmZ1bmN0aW9uKHMpe3JldHVybiByLmNvdW50KDAscyklbz09MH0pOnI6bnVsbH0pLHJ9dmFyIHNPPXlyKGZ1bmN0aW9uKCl7fSxmdW5jdGlvbihuLHQpe24uc2V0VGltZSgrbit0KX0sZnVuY3Rpb24obix0KXtyZXR1cm4gdC1ufSk7c08uZXZlcnk9ZnVuY3Rpb24obil7cmV0dXJuIG49TWF0aC5mbG9vcihuKSxpc0Zpbml0ZShuKSYmbj4wP24+MT95cihmdW5jdGlvbih0KXt0LnNldFRpbWUoTWF0aC5mbG9vcih0L24pKm4pfSxmdW5jdGlvbih0LGUpe3Quc2V0VGltZSgrdCtlKm4pfSxmdW5jdGlvbih0LGUpe3JldHVybihlLXQpL259KTpzTzpudWxsfTt2YXIgYU89c08sX3A9NmU0LGNPPTYwNDhlNSxCbGU9eXIoZnVuY3Rpb24obil7bi5zZXRUaW1lKG4tbi5nZXRNaWxsaXNlY29uZHMoKSl9LGZ1bmN0aW9uKG4sdCl7bi5zZXRUaW1lKCtuKzFlMyp0KX0sZnVuY3Rpb24obix0KXtyZXR1cm4odC1uKS8xZTN9LGZ1bmN0aW9uKG4pe3JldHVybiBuLmdldFVUQ1NlY29uZHMoKX0pLHVPPUJsZSxIbGU9eXIoZnVuY3Rpb24obil7bi5zZXRUaW1lKG4tbi5nZXRNaWxsaXNlY29uZHMoKS0xZTMqbi5nZXRTZWNvbmRzKCkpfSxmdW5jdGlvbihuLHQpe24uc2V0VGltZSgrbit0Kl9wKX0sZnVuY3Rpb24obix0KXtyZXR1cm4odC1uKS9fcH0sZnVuY3Rpb24obil7cmV0dXJuIG4uZ2V0TWludXRlcygpfSksR3o9SGxlLFVsZT15cihmdW5jdGlvbihuKXtuLnNldFRpbWUobi1uLmdldE1pbGxpc2Vjb25kcygpLTFlMypuLmdldFNlY29uZHMoKS1uLmdldE1pbnV0ZXMoKSpfcCl9LGZ1bmN0aW9uKG4sdCl7bi5zZXRUaW1lKCtuKzM2ZTUqdCl9LGZ1bmN0aW9uKG4sdCl7cmV0dXJuKHQtbikvMzZlNX0sZnVuY3Rpb24obil7cmV0dXJuIG4uZ2V0SG91cnMoKX0pLFd6PVVsZSx6bGU9eXIoZnVuY3Rpb24obil7bi5zZXRIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24obix0KXtuLnNldERhdGUobi5nZXREYXRlKCkrdCl9LGZ1bmN0aW9uKG4sdCl7cmV0dXJuKHQtbi0odC5nZXRUaW1lem9uZU9mZnNldCgpLW4uZ2V0VGltZXpvbmVPZmZzZXQoKSkqX3ApLzg2NGU1fSxmdW5jdGlvbihuKXtyZXR1cm4gbi5nZXREYXRlKCktMX0pLEV5PXpsZTtmdW5jdGlvbiBEZyhuKXtyZXR1cm4geXIoZnVuY3Rpb24odCl7dC5zZXREYXRlKHQuZ2V0RGF0ZSgpLSh0LmdldERheSgpKzctbiklNyksdC5zZXRIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24odCxlKXt0LnNldERhdGUodC5nZXREYXRlKCkrNyplKX0sZnVuY3Rpb24odCxlKXtyZXR1cm4oZS10LShlLmdldFRpbWV6b25lT2Zmc2V0KCktdC5nZXRUaW1lem9uZU9mZnNldCgpKSpfcCkvY099KX12YXIgQWc9RGcoMCksVHk9RGcoMSkscmY9KERnKDIpLERnKDMpLERnKDQpKSxYbGU9KERnKDUpLERnKDYpLHlyKGZ1bmN0aW9uKG4pe24uc2V0RGF0ZSgxKSxuLnNldEhvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihuLHQpe24uc2V0TW9udGgobi5nZXRNb250aCgpK3QpfSxmdW5jdGlvbihuLHQpe3JldHVybiB0LmdldE1vbnRoKCktbi5nZXRNb250aCgpKzEyKih0LmdldEZ1bGxZZWFyKCktbi5nZXRGdWxsWWVhcigpKX0sZnVuY3Rpb24obil7cmV0dXJuIG4uZ2V0TW9udGgoKX0pKSxxej1YbGUsWXo9eXIoZnVuY3Rpb24obil7bi5zZXRNb250aCgwLDEpLG4uc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKG4sdCl7bi5zZXRGdWxsWWVhcihuLmdldEZ1bGxZZWFyKCkrdCl9LGZ1bmN0aW9uKG4sdCl7cmV0dXJuIHQuZ2V0RnVsbFllYXIoKS1uLmdldEZ1bGxZZWFyKCl9LGZ1bmN0aW9uKG4pe3JldHVybiBuLmdldEZ1bGxZZWFyKCl9KTtZei5ldmVyeT1mdW5jdGlvbihuKXtyZXR1cm4gaXNGaW5pdGUobj1NYXRoLmZsb29yKG4pKSYmbj4wP3lyKGZ1bmN0aW9uKHQpe3Quc2V0RnVsbFllYXIoTWF0aC5mbG9vcih0LmdldEZ1bGxZZWFyKCkvbikqbiksdC5zZXRNb250aCgwLDEpLHQuc2V0SG91cnMoMCwwLDAsMCl9LGZ1bmN0aW9uKHQsZSl7dC5zZXRGdWxsWWVhcih0LmdldEZ1bGxZZWFyKCkrZSpuKX0pOm51bGx9O3ZhciB2cD1ZeixRbGU9eXIoZnVuY3Rpb24obil7bi5zZXRVVENIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24obix0KXtuLnNldFVUQ0RhdGUobi5nZXRVVENEYXRlKCkrdCl9LGZ1bmN0aW9uKG4sdCl7cmV0dXJuKHQtbikvODY0ZTV9LGZ1bmN0aW9uKG4pe3JldHVybiBuLmdldFVUQ0RhdGUoKS0xfSksZE89UWxlO2Z1bmN0aW9uIElnKG4pe3JldHVybiB5cihmdW5jdGlvbih0KXt0LnNldFVUQ0RhdGUodC5nZXRVVENEYXRlKCktKHQuZ2V0VVRDRGF5KCkrNy1uKSU3KSx0LnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbih0LGUpe3Quc2V0VVRDRGF0ZSh0LmdldFVUQ0RhdGUoKSs3KmUpfSxmdW5jdGlvbih0LGUpe3JldHVybihlLXQpL2NPfSl9dmFyIGhTPUlnKDApLER5PUlnKDEpLG9mPShJZygyKSxJZygzKSxJZyg0KSksWHo9KElnKDUpLElnKDYpLHlyKGZ1bmN0aW9uKG4pe24uc2V0VVRDTW9udGgoMCwxKSxuLnNldFVUQ0hvdXJzKDAsMCwwLDApfSxmdW5jdGlvbihuLHQpe24uc2V0VVRDRnVsbFllYXIobi5nZXRVVENGdWxsWWVhcigpK3QpfSxmdW5jdGlvbihuLHQpe3JldHVybiB0LmdldFVUQ0Z1bGxZZWFyKCktbi5nZXRVVENGdWxsWWVhcigpfSxmdW5jdGlvbihuKXtyZXR1cm4gbi5nZXRVVENGdWxsWWVhcigpfSkpO1h6LmV2ZXJ5PWZ1bmN0aW9uKG4pe3JldHVybiBpc0Zpbml0ZShuPU1hdGguZmxvb3IobikpJiZuPjA/eXIoZnVuY3Rpb24odCl7dC5zZXRVVENGdWxsWWVhcihNYXRoLmZsb29yKHQuZ2V0VVRDRnVsbFllYXIoKS9uKSpuKSx0LnNldFVUQ01vbnRoKDAsMSksdC5zZXRVVENIb3VycygwLDAsMCwwKX0sZnVuY3Rpb24odCxlKXt0LnNldFVUQ0Z1bGxZZWFyKHQuZ2V0VVRDRnVsbFllYXIoKStlKm4pfSk6bnVsbH07dmFyIFBnPVh6O2Z1bmN0aW9uIFF6KG4pe2lmKDA8PW4ueSYmbi55PDEwMCl7dmFyIHQ9bmV3IERhdGUoLTEsbi5tLG4uZCxuLkgsbi5NLG4uUyxuLkwpO3JldHVybiB0LnNldEZ1bGxZZWFyKG4ueSksdH1yZXR1cm4gbmV3IERhdGUobi55LG4ubSxuLmQsbi5ILG4uTSxuLlMsbi5MKX1mdW5jdGlvbiBLeihuKXtpZigwPD1uLnkmJm4ueTwxMDApe3ZhciB0PW5ldyBEYXRlKERhdGUuVVRDKC0xLG4ubSxuLmQsbi5ILG4uTSxuLlMsbi5MKSk7cmV0dXJuIHQuc2V0VVRDRnVsbFllYXIobi55KSx0fXJldHVybiBuZXcgRGF0ZShEYXRlLlVUQyhuLnksbi5tLG4uZCxuLkgsbi5NLG4uUyxuLkwpKX1mdW5jdGlvbiBmUyhuLHQsZSl7cmV0dXJue3k6bixtOnQsZDplLEg6MCxNOjAsUzowLEw6MH19dmFyIEF5LEl5LHRjZT17Ii0iOiIiLF86IiAiLDA6IjAifSx1cz0vXlxzKlxkKy8sZ3plPS9eJS8sX3plPS9bXFxeJCorP3xbXF0oKS57fV0vZztmdW5jdGlvbiBHaShuLHQsZSl7dmFyIGk9bjwwPyItIjoiIixyPShpPy1uOm4pKyIiLG89ci5sZW5ndGg7cmV0dXJuIGkrKG88ZT9uZXcgQXJyYXkoZS1vKzEpLmpvaW4odCkrcjpyKX1mdW5jdGlvbiB2emUobil7cmV0dXJuIG4ucmVwbGFjZShfemUsIlxcJCYiKX1mdW5jdGlvbiBtUyhuKXtyZXR1cm4gbmV3IFJlZ0V4cCgiXig/OiIrbi5tYXAodnplKS5qb2luKCJ8IikrIikiLCJpIil9ZnVuY3Rpb24gZ1Mobil7Zm9yKHZhciB0PXt9LGU9LTEsaT1uLmxlbmd0aDsrK2U8aTspdFtuW2VdLnRvTG93ZXJDYXNlKCldPWU7cmV0dXJuIHR9ZnVuY3Rpb24geXplKG4sdCxlKXt2YXIgaT11cy5leGVjKHQuc2xpY2UoZSxlKzEpKTtyZXR1cm4gaT8obi53PStpWzBdLGUraVswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIGJ6ZShuLHQsZSl7dmFyIGk9dXMuZXhlYyh0LnNsaWNlKGUsZSsxKSk7cmV0dXJuIGk/KG4udT0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiB4emUobix0LGUpe3ZhciBpPXVzLmV4ZWModC5zbGljZShlLGUrMikpO3JldHVybiBpPyhuLlU9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gQ3plKG4sdCxlKXt2YXIgaT11cy5leGVjKHQuc2xpY2UoZSxlKzIpKTtyZXR1cm4gaT8obi5WPStpWzBdLGUraVswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIE16ZShuLHQsZSl7dmFyIGk9dXMuZXhlYyh0LnNsaWNlKGUsZSsyKSk7cmV0dXJuIGk/KG4uVz0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBuY2Uobix0LGUpe3ZhciBpPXVzLmV4ZWModC5zbGljZShlLGUrNCkpO3JldHVybiBpPyhuLnk9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gaWNlKG4sdCxlKXt2YXIgaT11cy5leGVjKHQuc2xpY2UoZSxlKzIpKTtyZXR1cm4gaT8obi55PStpWzBdKygraVswXT42OD8xOTAwOjJlMyksZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gd3plKG4sdCxlKXt2YXIgaT0vXihaKXwoWystXVxkXGQpKD86Oj8oXGRcZCkpPy8uZXhlYyh0LnNsaWNlKGUsZSs2KSk7cmV0dXJuIGk/KG4uWj1pWzFdPzA6LShpWzJdKyhpWzNdfHwiMDAiKSksZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gU3plKG4sdCxlKXt2YXIgaT11cy5leGVjKHQuc2xpY2UoZSxlKzEpKTtyZXR1cm4gaT8obi5xPTMqaVswXS0zLGUraVswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIEV6ZShuLHQsZSl7dmFyIGk9dXMuZXhlYyh0LnNsaWNlKGUsZSsyKSk7cmV0dXJuIGk/KG4ubT1pWzBdLTEsZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gcmNlKG4sdCxlKXt2YXIgaT11cy5leGVjKHQuc2xpY2UoZSxlKzIpKTtyZXR1cm4gaT8obi5kPStpWzBdLGUraVswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIFR6ZShuLHQsZSl7dmFyIGk9dXMuZXhlYyh0LnNsaWNlKGUsZSszKSk7cmV0dXJuIGk/KG4ubT0wLG4uZD0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBvY2Uobix0LGUpe3ZhciBpPXVzLmV4ZWModC5zbGljZShlLGUrMikpO3JldHVybiBpPyhuLkg9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gRHplKG4sdCxlKXt2YXIgaT11cy5leGVjKHQuc2xpY2UoZSxlKzIpKTtyZXR1cm4gaT8obi5NPStpWzBdLGUraVswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIEF6ZShuLHQsZSl7dmFyIGk9dXMuZXhlYyh0LnNsaWNlKGUsZSsyKSk7cmV0dXJuIGk/KG4uUz0raVswXSxlK2lbMF0ubGVuZ3RoKTotMX1mdW5jdGlvbiBJemUobix0LGUpe3ZhciBpPXVzLmV4ZWModC5zbGljZShlLGUrMykpO3JldHVybiBpPyhuLkw9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gUHplKG4sdCxlKXt2YXIgaT11cy5leGVjKHQuc2xpY2UoZSxlKzYpKTtyZXR1cm4gaT8obi5MPU1hdGguZmxvb3IoaVswXS8xZTMpLGUraVswXS5sZW5ndGgpOi0xfWZ1bmN0aW9uIFJ6ZShuLHQsZSl7dmFyIGk9Z3plLmV4ZWModC5zbGljZShlLGUrMSkpO3JldHVybiBpP2UraVswXS5sZW5ndGg6LTF9ZnVuY3Rpb24gT3plKG4sdCxlKXt2YXIgaT11cy5leGVjKHQuc2xpY2UoZSkpO3JldHVybiBpPyhuLlE9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24ga3plKG4sdCxlKXt2YXIgaT11cy5leGVjKHQuc2xpY2UoZSkpO3JldHVybiBpPyhuLnM9K2lbMF0sZStpWzBdLmxlbmd0aCk6LTF9ZnVuY3Rpb24gc2NlKG4sdCl7cmV0dXJuIEdpKG4uZ2V0RGF0ZSgpLHQsMil9ZnVuY3Rpb24gRnplKG4sdCl7cmV0dXJuIEdpKG4uZ2V0SG91cnMoKSx0LDIpfWZ1bmN0aW9uIE56ZShuLHQpe3JldHVybiBHaShuLmdldEhvdXJzKCklMTJ8fDEyLHQsMil9ZnVuY3Rpb24gTHplKG4sdCl7cmV0dXJuIEdpKDErRXkuY291bnQodnAobiksbiksdCwzKX1mdW5jdGlvbiBkY2Uobix0KXtyZXR1cm4gR2kobi5nZXRNaWxsaXNlY29uZHMoKSx0LDMpfWZ1bmN0aW9uIEJ6ZShuLHQpe3JldHVybiBkY2Uobix0KSsiMDAwIn1mdW5jdGlvbiBWemUobix0KXtyZXR1cm4gR2kobi5nZXRNb250aCgpKzEsdCwyKX1mdW5jdGlvbiBIemUobix0KXtyZXR1cm4gR2kobi5nZXRNaW51dGVzKCksdCwyKX1mdW5jdGlvbiBVemUobix0KXtyZXR1cm4gR2kobi5nZXRTZWNvbmRzKCksdCwyKX1mdW5jdGlvbiB6emUobil7dmFyIHQ9bi5nZXREYXkoKTtyZXR1cm4gMD09PXQ/Nzp0fWZ1bmN0aW9uIGp6ZShuLHQpe3JldHVybiBHaShBZy5jb3VudCh2cChuKS0xLG4pLHQsMil9ZnVuY3Rpb24gcGNlKG4pe3ZhciB0PW4uZ2V0RGF5KCk7cmV0dXJuIHQ+PTR8fDA9PT10P3JmKG4pOnJmLmNlaWwobil9ZnVuY3Rpb24gR3plKG4sdCl7cmV0dXJuIG49cGNlKG4pLEdpKHJmLmNvdW50KHZwKG4pLG4pKyg0PT09dnAobikuZ2V0RGF5KCkpLHQsMil9ZnVuY3Rpb24gV3plKG4pe3JldHVybiBuLmdldERheSgpfWZ1bmN0aW9uIHF6ZShuLHQpe3JldHVybiBHaShUeS5jb3VudCh2cChuKS0xLG4pLHQsMil9ZnVuY3Rpb24gWXplKG4sdCl7cmV0dXJuIEdpKG4uZ2V0RnVsbFllYXIoKSUxMDAsdCwyKX1mdW5jdGlvbiBYemUobix0KXtyZXR1cm4gR2koKG49cGNlKG4pKS5nZXRGdWxsWWVhcigpJTEwMCx0LDIpfWZ1bmN0aW9uIFF6ZShuLHQpe3JldHVybiBHaShuLmdldEZ1bGxZZWFyKCklMWU0LHQsNCl9ZnVuY3Rpb24gS3plKG4sdCl7dmFyIGU9bi5nZXREYXkoKTtyZXR1cm4gR2koKG49ZT49NHx8MD09PWU/cmYobik6cmYuY2VpbChuKSkuZ2V0RnVsbFllYXIoKSUxZTQsdCw0KX1mdW5jdGlvbiBaemUobil7dmFyIHQ9bi5nZXRUaW1lem9uZU9mZnNldCgpO3JldHVybih0PjA/Ii0iOih0Kj0tMSwiKyIpKStHaSh0LzYwfDAsIjAiLDIpK0dpKHQlNjAsIjAiLDIpfWZ1bmN0aW9uIGFjZShuLHQpe3JldHVybiBHaShuLmdldFVUQ0RhdGUoKSx0LDIpfWZ1bmN0aW9uIEp6ZShuLHQpe3JldHVybiBHaShuLmdldFVUQ0hvdXJzKCksdCwyKX1mdW5jdGlvbiAkemUobix0KXtyZXR1cm4gR2kobi5nZXRVVENIb3VycygpJTEyfHwxMix0LDIpfWZ1bmN0aW9uIGVqZShuLHQpe3JldHVybiBHaSgxK2RPLmNvdW50KFBnKG4pLG4pLHQsMyl9ZnVuY3Rpb24gaGNlKG4sdCl7cmV0dXJuIEdpKG4uZ2V0VVRDTWlsbGlzZWNvbmRzKCksdCwzKX1mdW5jdGlvbiB0amUobix0KXtyZXR1cm4gaGNlKG4sdCkrIjAwMCJ9ZnVuY3Rpb24gbmplKG4sdCl7cmV0dXJuIEdpKG4uZ2V0VVRDTW9udGgoKSsxLHQsMil9ZnVuY3Rpb24gaWplKG4sdCl7cmV0dXJuIEdpKG4uZ2V0VVRDTWludXRlcygpLHQsMil9ZnVuY3Rpb24gcmplKG4sdCl7cmV0dXJuIEdpKG4uZ2V0VVRDU2Vjb25kcygpLHQsMil9ZnVuY3Rpb24gb2plKG4pe3ZhciB0PW4uZ2V0VVRDRGF5KCk7cmV0dXJuIDA9PT10Pzc6dH1mdW5jdGlvbiBzamUobix0KXtyZXR1cm4gR2koaFMuY291bnQoUGcobiktMSxuKSx0LDIpfWZ1bmN0aW9uIGZjZShuKXt2YXIgdD1uLmdldFVUQ0RheSgpO3JldHVybiB0Pj00fHwwPT09dD9vZihuKTpvZi5jZWlsKG4pfWZ1bmN0aW9uIGFqZShuLHQpe3JldHVybiBuPWZjZShuKSxHaShvZi5jb3VudChQZyhuKSxuKSsoND09PVBnKG4pLmdldFVUQ0RheSgpKSx0LDIpfWZ1bmN0aW9uIGxqZShuKXtyZXR1cm4gbi5nZXRVVENEYXkoKX1mdW5jdGlvbiBjamUobix0KXtyZXR1cm4gR2koRHkuY291bnQoUGcobiktMSxuKSx0LDIpfWZ1bmN0aW9uIHVqZShuLHQpe3JldHVybiBHaShuLmdldFVUQ0Z1bGxZZWFyKCklMTAwLHQsMil9ZnVuY3Rpb24gZGplKG4sdCl7cmV0dXJuIEdpKChuPWZjZShuKSkuZ2V0VVRDRnVsbFllYXIoKSUxMDAsdCwyKX1mdW5jdGlvbiBwamUobix0KXtyZXR1cm4gR2kobi5nZXRVVENGdWxsWWVhcigpJTFlNCx0LDQpfWZ1bmN0aW9uIGhqZShuLHQpe3ZhciBlPW4uZ2V0VVRDRGF5KCk7cmV0dXJuIEdpKChuPWU+PTR8fDA9PT1lP29mKG4pOm9mLmNlaWwobikpLmdldFVUQ0Z1bGxZZWFyKCklMWU0LHQsNCl9ZnVuY3Rpb24gZmplKCl7cmV0dXJuIiswMDAwIn1mdW5jdGlvbiBsY2UoKXtyZXR1cm4iJSJ9ZnVuY3Rpb24gY2NlKG4pe3JldHVybitufWZ1bmN0aW9uIHVjZShuKXtyZXR1cm4gTWF0aC5mbG9vcigrbi8xZTMpfUF5PWZ1bmN0aW9uKG4pe3ZhciB0PW4uZGF0ZVRpbWUsZT1uLmRhdGUsaT1uLnRpbWUscj1uLnBlcmlvZHMsbz1uLmRheXMscz1uLnNob3J0RGF5cyxhPW4ubW9udGhzLGw9bi5zaG9ydE1vbnRocyxjPW1TKHIpLHU9Z1MociksZD1tUyhvKSxwPWdTKG8pLGg9bVMocyksZj1nUyhzKSxtPW1TKGEpLHg9Z1MoYSksZz1tUyhsKSxiPWdTKGwpLEQ9e2E6ZnVuY3Rpb24oVGUpe3JldHVybiBzW1RlLmdldERheSgpXX0sQTpmdW5jdGlvbihUZSl7cmV0dXJuIG9bVGUuZ2V0RGF5KCldfSxiOmZ1bmN0aW9uKFRlKXtyZXR1cm4gbFtUZS5nZXRNb250aCgpXX0sQjpmdW5jdGlvbihUZSl7cmV0dXJuIGFbVGUuZ2V0TW9udGgoKV19LGM6bnVsbCxkOnNjZSxlOnNjZSxmOkJ6ZSxnOlh6ZSxHOkt6ZSxIOkZ6ZSxJOk56ZSxqOkx6ZSxMOmRjZSxtOlZ6ZSxNOkh6ZSxwOmZ1bmN0aW9uKFRlKXtyZXR1cm4gclsrKFRlLmdldEhvdXJzKCk+PTEyKV19LHE6ZnVuY3Rpb24oVGUpe3JldHVybiAxK35+KFRlLmdldE1vbnRoKCkvMyl9LFE6Y2NlLHM6dWNlLFM6VXplLHU6enplLFU6anplLFY6R3plLHc6V3plLFc6cXplLHg6bnVsbCxYOm51bGwseTpZemUsWTpRemUsWjpaemUsIiUiOmxjZX0sVD17YTpmdW5jdGlvbihUZSl7cmV0dXJuIHNbVGUuZ2V0VVRDRGF5KCldfSxBOmZ1bmN0aW9uKFRlKXtyZXR1cm4gb1tUZS5nZXRVVENEYXkoKV19LGI6ZnVuY3Rpb24oVGUpe3JldHVybiBsW1RlLmdldFVUQ01vbnRoKCldfSxCOmZ1bmN0aW9uKFRlKXtyZXR1cm4gYVtUZS5nZXRVVENNb250aCgpXX0sYzpudWxsLGQ6YWNlLGU6YWNlLGY6dGplLGc6ZGplLEc6aGplLEg6SnplLEk6JHplLGo6ZWplLEw6aGNlLG06bmplLE06aWplLHA6ZnVuY3Rpb24oVGUpe3JldHVybiByWysoVGUuZ2V0VVRDSG91cnMoKT49MTIpXX0scTpmdW5jdGlvbihUZSl7cmV0dXJuIDErfn4oVGUuZ2V0VVRDTW9udGgoKS8zKX0sUTpjY2Usczp1Y2UsUzpyamUsdTpvamUsVTpzamUsVjphamUsdzpsamUsVzpjamUseDpudWxsLFg6bnVsbCx5OnVqZSxZOnBqZSxaOmZqZSwiJSI6bGNlfSxrPXthOmZ1bmN0aW9uKFRlLHh0LG10KXt2YXIgY2U9aC5leGVjKHh0LnNsaWNlKG10KSk7cmV0dXJuIGNlPyhUZS53PWZbY2VbMF0udG9Mb3dlckNhc2UoKV0sbXQrY2VbMF0ubGVuZ3RoKTotMX0sQTpmdW5jdGlvbihUZSx4dCxtdCl7dmFyIGNlPWQuZXhlYyh4dC5zbGljZShtdCkpO3JldHVybiBjZT8oVGUudz1wW2NlWzBdLnRvTG93ZXJDYXNlKCldLG10K2NlWzBdLmxlbmd0aCk6LTF9LGI6ZnVuY3Rpb24oVGUseHQsbXQpe3ZhciBjZT1nLmV4ZWMoeHQuc2xpY2UobXQpKTtyZXR1cm4gY2U/KFRlLm09YltjZVswXS50b0xvd2VyQ2FzZSgpXSxtdCtjZVswXS5sZW5ndGgpOi0xfSxCOmZ1bmN0aW9uKFRlLHh0LG10KXt2YXIgY2U9bS5leGVjKHh0LnNsaWNlKG10KSk7cmV0dXJuIGNlPyhUZS5tPXhbY2VbMF0udG9Mb3dlckNhc2UoKV0sbXQrY2VbMF0ubGVuZ3RoKTotMX0sYzpmdW5jdGlvbihUZSx4dCxtdCl7cmV0dXJuIGZlKFRlLHQseHQsbXQpfSxkOnJjZSxlOnJjZSxmOlB6ZSxnOmljZSxHOm5jZSxIOm9jZSxJOm9jZSxqOlR6ZSxMOkl6ZSxtOkV6ZSxNOkR6ZSxwOmZ1bmN0aW9uKFRlLHh0LG10KXt2YXIgY2U9Yy5leGVjKHh0LnNsaWNlKG10KSk7cmV0dXJuIGNlPyhUZS5wPXVbY2VbMF0udG9Mb3dlckNhc2UoKV0sbXQrY2VbMF0ubGVuZ3RoKTotMX0scTpTemUsUTpPemUsczpremUsUzpBemUsdTpiemUsVTp4emUsVjpDemUsdzp5emUsVzpNemUseDpmdW5jdGlvbihUZSx4dCxtdCl7cmV0dXJuIGZlKFRlLGUseHQsbXQpfSxYOmZ1bmN0aW9uKFRlLHh0LG10KXtyZXR1cm4gZmUoVGUsaSx4dCxtdCl9LHk6aWNlLFk6bmNlLFo6d3plLCIlIjpSemV9O2Z1bmN0aW9uIFooVGUseHQpe3JldHVybiBmdW5jdGlvbihtdCl7dmFyIGJ0LGhuLG9uLGNlPVtdLGR0PS0xLFdlPTAsTXQ9VGUubGVuZ3RoO2ZvcihtdCBpbnN0YW5jZW9mIERhdGV8fChtdD1uZXcgRGF0ZSgrbXQpKTsrK2R0PE10OykzNz09PVRlLmNoYXJDb2RlQXQoZHQpJiYoY2UucHVzaChUZS5zbGljZShXZSxkdCkpLG51bGwhPShobj10Y2VbYnQ9VGUuY2hhckF0KCsrZHQpXSk/YnQ9VGUuY2hhckF0KCsrZHQpOmhuPSJlIj09PWJ0PyIgIjoiMCIsKG9uPXh0W2J0XSkmJihidD1vbihtdCxobikpLGNlLnB1c2goYnQpLFdlPWR0KzEpO3JldHVybiBjZS5wdXNoKFRlLnNsaWNlKFdlLGR0KSksY2Uuam9pbigiIil9fWZ1bmN0aW9uIHooVGUseHQpe3JldHVybiBmdW5jdGlvbihtdCl7dmFyIFdlLE10LGNlPWZTKDE5MDAsdm9pZCAwLDEpO2lmKGZlKGNlLFRlLG10Kz0iIiwwKSE9bXQubGVuZ3RoKXJldHVybiBudWxsO2lmKCJRImluIGNlKXJldHVybiBuZXcgRGF0ZShjZS5RKTtpZigicyJpbiBjZSlyZXR1cm4gbmV3IERhdGUoMWUzKmNlLnMrKCJMImluIGNlP2NlLkw6MCkpO2lmKHh0JiYhKCJaImluIGNlKSYmKGNlLlo9MCksInAiaW4gY2UmJihjZS5IPWNlLkglMTIrMTIqY2UucCksdm9pZCAwPT09Y2UubSYmKGNlLm09InEiaW4gY2U/Y2UucTowKSwiViJpbiBjZSl7aWYoY2UuVjwxfHxjZS5WPjUzKXJldHVybiBudWxsOyJ3ImluIGNlfHwoY2Uudz0xKSwiWiJpbiBjZT8oTXQ9KFdlPUt6KGZTKGNlLnksMCwxKSkpLmdldFVUQ0RheSgpLFdlPU10PjR8fDA9PT1NdD9EeS5jZWlsKFdlKTpEeShXZSksV2U9ZE8ub2Zmc2V0KFdlLDcqKGNlLlYtMSkpLGNlLnk9V2UuZ2V0VVRDRnVsbFllYXIoKSxjZS5tPVdlLmdldFVUQ01vbnRoKCksY2UuZD1XZS5nZXRVVENEYXRlKCkrKGNlLncrNiklNyk6KE10PShXZT1ReihmUyhjZS55LDAsMSkpKS5nZXREYXkoKSxXZT1NdD40fHwwPT09TXQ/VHkuY2VpbChXZSk6VHkoV2UpLFdlPUV5Lm9mZnNldChXZSw3KihjZS5WLTEpKSxjZS55PVdlLmdldEZ1bGxZZWFyKCksY2UubT1XZS5nZXRNb250aCgpLGNlLmQ9V2UuZ2V0RGF0ZSgpKyhjZS53KzYpJTcpfWVsc2UoIlciaW4gY2V8fCJVImluIGNlKSYmKCJ3ImluIGNlfHwoY2Uudz0idSJpbiBjZT9jZS51JTc6IlciaW4gY2U/MTowKSxNdD0iWiJpbiBjZT9LeihmUyhjZS55LDAsMSkpLmdldFVUQ0RheSgpOlF6KGZTKGNlLnksMCwxKSkuZ2V0RGF5KCksY2UubT0wLGNlLmQ9IlciaW4gY2U/KGNlLncrNiklNys3KmNlLlctKE10KzUpJTc6Y2Uudys3KmNlLlUtKE10KzYpJTcpO3JldHVybiJaImluIGNlPyhjZS5IKz1jZS5aLzEwMHwwLGNlLk0rPWNlLlolMTAwLEt6KGNlKSk6UXooY2UpfX1mdW5jdGlvbiBmZShUZSx4dCxtdCxjZSl7Zm9yKHZhciBidCxobixkdD0wLFdlPXh0Lmxlbmd0aCxNdD1tdC5sZW5ndGg7ZHQ8V2U7KXtpZihjZT49TXQpcmV0dXJuLTE7aWYoMzc9PT0oYnQ9eHQuY2hhckNvZGVBdChkdCsrKSkpe2lmKGJ0PXh0LmNoYXJBdChkdCsrKSwhKGhuPWtbYnQgaW4gdGNlP3h0LmNoYXJBdChkdCsrKTpidF0pfHwoY2U9aG4oVGUsbXQsY2UpKTwwKXJldHVybi0xfWVsc2UgaWYoYnQhPW10LmNoYXJDb2RlQXQoY2UrKykpcmV0dXJuLTF9cmV0dXJuIGNlfXJldHVybiBELng9WihlLEQpLEQuWD1aKGksRCksRC5jPVoodCxEKSxULng9WihlLFQpLFQuWD1aKGksVCksVC5jPVoodCxUKSx7Zm9ybWF0OmZ1bmN0aW9uKFRlKXt2YXIgeHQ9WihUZSs9IiIsRCk7cmV0dXJuIHh0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIFRlfSx4dH0scGFyc2U6ZnVuY3Rpb24oVGUpe3ZhciB4dD16KFRlKz0iIiwhMSk7cmV0dXJuIHh0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIFRlfSx4dH0sdXRjRm9ybWF0OmZ1bmN0aW9uKFRlKXt2YXIgeHQ9WihUZSs9IiIsVCk7cmV0dXJuIHh0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIFRlfSx4dH0sdXRjUGFyc2U6ZnVuY3Rpb24oVGUpe3ZhciB4dD16KFRlKz0iIiwhMCk7cmV0dXJuIHh0LnRvU3RyaW5nPWZ1bmN0aW9uKCl7cmV0dXJuIFRlfSx4dH19fSh7ZGF0ZVRpbWU6IiV4LCAlWCIsZGF0ZToiJS1tLyUtZC8lWSIsdGltZToiJS1JOiVNOiVTICVwIixwZXJpb2RzOlsiQU0iLCJQTSJdLGRheXM6WyJTdW5kYXkiLCJNb25kYXkiLCJUdWVzZGF5IiwiV2VkbmVzZGF5IiwiVGh1cnNkYXkiLCJGcmlkYXkiLCJTYXR1cmRheSJdLHNob3J0RGF5czpbIlN1biIsIk1vbiIsIlR1ZSIsIldlZCIsIlRodSIsIkZyaSIsIlNhdCJdLG1vbnRoczpbIkphbnVhcnkiLCJGZWJydWFyeSIsIk1hcmNoIiwiQXByaWwiLCJNYXkiLCJKdW5lIiwiSnVseSIsIkF1Z3VzdCIsIlNlcHRlbWJlciIsIk9jdG9iZXIiLCJOb3ZlbWJlciIsIkRlY2VtYmVyIl0sc2hvcnRNb250aHM6WyJKYW4iLCJGZWIiLCJNYXIiLCJBcHIiLCJNYXkiLCJKdW4iLCJKdWwiLCJBdWciLCJTZXAiLCJPY3QiLCJOb3YiLCJEZWMiXX0pLEl5PUF5LmZvcm1hdDt2YXIgdlM9NmU0LHlTPTYwKnZTLGJTPTI0KnlTLCR6PTM2NSpiUztmdW5jdGlvbiBnamUobil7cmV0dXJuIG5ldyBEYXRlKG4pfWZ1bmN0aW9uIF9qZShuKXtyZXR1cm4gbiBpbnN0YW5jZW9mIERhdGU/K246K25ldyBEYXRlKCtuKX1mdW5jdGlvbiBlaihuLHQsZSxpLHIsbyxzLGEsbCl7dmFyIGM9ZFMoamEsamEpLHU9Yy5pbnZlcnQsZD1jLmRvbWFpbixwPWwoIi4lTCIpLGg9bCgiOiVTIiksZj1sKCIlSTolTSIpLG09bCgiJUkgJXAiKSx4PWwoIiVhICVkIiksZz1sKCIlYiAlZCIpLGI9bCgiJUIiKSxEPWwoIiVZIiksVD1bW3MsMSwxZTNdLFtzLDUsNWUzXSxbcywxNSwxNWUzXSxbcywzMCwzZTRdLFtvLDEsdlNdLFtvLDUsNSp2U10sW28sMTUsMTUqdlNdLFtvLDMwLDMwKnZTXSxbciwxLHlTXSxbciwzLDMqeVNdLFtyLDYsNip5U10sW3IsMTIsMTIqeVNdLFtpLDEsYlNdLFtpLDIsMipiU10sW2UsMSw2MDQ4ZTVdLFt0LDEsMjU5MmU2XSxbdCwzLDc3NzZlNl0sW24sMSwkel1dO2Z1bmN0aW9uIGsoeil7cmV0dXJuKHMoeik8ej9wOm8oeik8ej9oOnIoeik8ej9mOmkoeik8ej9tOnQoeik8ej9lKHopPHo/eDpnOm4oeik8ej9iOkQpKHopfWZ1bmN0aW9uIFooeixmZSx1ZSxoZSl7aWYobnVsbD09eiYmKHo9MTApLCJudW1iZXIiPT10eXBlb2Ygeil7dmFyIHc9TWF0aC5hYnModWUtZmUpL3osRj1CdyhmdW5jdGlvbihxKXtyZXR1cm4gcVsyXX0pLnJpZ2h0KFQsdyk7Rj09PVQubGVuZ3RoPyhoZT12ZChmZS8keix1ZS8keix6KSx6PW4pOkY/KGhlPShGPVRbdy9UW0YtMV1bMl08VFtGXVsyXS93P0YtMTpGXSlbMV0sej1GWzBdKTooaGU9TWF0aC5tYXgodmQoZmUsdWUseiksMSksej1hKX1yZXR1cm4gbnVsbD09aGU/ejp6LmV2ZXJ5KGhlKX1yZXR1cm4gYy5pbnZlcnQ9ZnVuY3Rpb24oeil7cmV0dXJuIG5ldyBEYXRlKHUoeikpfSxjLmRvbWFpbj1mdW5jdGlvbih6KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD9kKGxTLmNhbGwoeixfamUpKTpkKCkubWFwKGdqZSl9LGMudGlja3M9ZnVuY3Rpb24oeixmZSl7dmFyIHEsdWU9ZCgpLGhlPXVlWzBdLHc9dWVbdWUubGVuZ3RoLTFdLEY9dzxoZTtyZXR1cm4gRiYmKHE9aGUsaGU9dyx3PXEpLHE9KHE9Wih6LGhlLHcsZmUpKT9xLnJhbmdlKGhlLHcrMSk6W10sRj9xLnJldmVyc2UoKTpxfSxjLnRpY2tGb3JtYXQ9ZnVuY3Rpb24oeixmZSl7cmV0dXJuIG51bGw9PWZlP2s6bChmZSl9LGMubmljZT1mdW5jdGlvbih6LGZlKXt2YXIgdWU9ZCgpO3JldHVybih6PVooeix1ZVswXSx1ZVt1ZS5sZW5ndGgtMV0sZmUpKT9kKG9PKHVlLHopKTpjfSxjLmNvcHk9ZnVuY3Rpb24oKXtyZXR1cm4gbmYoYyxlaihuLHQsZSxpLHIsbyxzLGEsbCkpfSxjfWZ1bmN0aW9uIFJnKCl7cmV0dXJuIHpzLmFwcGx5KGVqKHZwLHF6LEFnLEV5LFd6LEd6LHVPLGFPLEl5KS5kb21haW4oW25ldyBEYXRlKDJlMywwLDEpLG5ldyBEYXRlKDJlMywwLDIpXSksYXJndW1lbnRzKX1mdW5jdGlvbiBQeShuKXtmb3IodmFyIHQ9bi5sZW5ndGgvNnwwLGU9bmV3IEFycmF5KHQpLGk9MDtpPHQ7KWVbaV09IiMiK24uc2xpY2UoNippLDYqKytpKTtyZXR1cm4gZX1mdW5jdGlvbiBSeShuKXtyZXR1cm4gaXoobltuLmxlbmd0aC0xXSl9dmFyIHRqPVJ5KG5ldyBBcnJheSgzKS5jb25jYXQoImRlZWJmNzllY2FlMTMxODJiZCIsImVmZjNmZmJkZDdlNzZiYWVkNjIxNzFiNSIsImVmZjNmZmJkZDdlNzZiYWVkNjMxODJiZDA4NTE5YyIsImVmZjNmZmM2ZGJlZjllY2FlMTZiYWVkNjMxODJiZDA4NTE5YyIsImVmZjNmZmM2ZGJlZjllY2FlMTZiYWVkNjQyOTJjNjIxNzFiNTA4NDU5NCIsImY3ZmJmZmRlZWJmN2M2ZGJlZjllY2FlMTZiYWVkNjQyOTJjNjIxNzFiNTA4NDU5NCIsImY3ZmJmZmRlZWJmN2M2ZGJlZjllY2FlMTZiYWVkNjQyOTJjNjIxNzFiNTA4NTE5YzA4MzA2YiIpLm1hcChQeSkpLG5qPVJ5KG5ldyBBcnJheSgzKS5jb25jYXQoImYwZjBmMGJkYmRiZDYzNjM2MyIsImY3ZjdmN2NjY2NjYzk2OTY5NjUyNTI1MiIsImY3ZjdmN2NjY2NjYzk2OTY5NjYzNjM2MzI1MjUyNSIsImY3ZjdmN2Q5ZDlkOWJkYmRiZDk2OTY5NjYzNjM2MzI1MjUyNSIsImY3ZjdmN2Q5ZDlkOWJkYmRiZDk2OTY5NjczNzM3MzUyNTI1MjI1MjUyNSIsImZmZmZmZmYwZjBmMGQ5ZDlkOWJkYmRiZDk2OTY5NjczNzM3MzUyNTI1MjI1MjUyNSIsImZmZmZmZmYwZjBmMGQ5ZDlkOWJkYmRiZDk2OTY5NjczNzM3MzUyNTI1MjI1MjUyNTAwMDAwMCIpLm1hcChQeSkpLGlqPVJ5KG5ldyBBcnJheSgzKS5jb25jYXQoImZlZTBkMmZjOTI3MmRlMmQyNiIsImZlZTVkOWZjYWU5MWZiNmE0YWNiMTgxZCIsImZlZTVkOWZjYWU5MWZiNmE0YWRlMmQyNmE1MGYxNSIsImZlZTVkOWZjYmJhMWZjOTI3MmZiNmE0YWRlMmQyNmE1MGYxNSIsImZlZTVkOWZjYmJhMWZjOTI3MmZiNmE0YWVmM2IyY2NiMTgxZDk5MDAwZCIsImZmZjVmMGZlZTBkMmZjYmJhMWZjOTI3MmZiNmE0YWVmM2IyY2NiMTgxZDk5MDAwZCIsImZmZjVmMGZlZTBkMmZjYmJhMWZjOTI3MmZiNmE0YWVmM2IyY2NiMTgxZGE1MGYxNTY3MDAwZCIpLm1hcChQeSkpO2Z1bmN0aW9uIGZhKG4pe3JldHVybiBmdW5jdGlvbigpe3JldHVybiBufX1mdW5jdGlvbiB3Y2Uobil7dGhpcy5fY29udGV4dD1ufWZ1bmN0aW9uIHBPKG4pe3JldHVybiBuZXcgd2NlKG4pfWZ1bmN0aW9uIGhPKG4pe3JldHVybiBuWzBdfWZ1bmN0aW9uIGZPKG4pe3JldHVybiBuWzFdfWZ1bmN0aW9uIHhTKCl7dmFyIG49aE8sdD1mTyxlPWZhKCEwKSxpPW51bGwscj1wTyxvPW51bGw7ZnVuY3Rpb24gcyhhKXt2YXIgbCx1LHAsYz1hLmxlbmd0aCxkPSExO2ZvcihudWxsPT1pJiYobz1yKHA9c1MoKSkpLGw9MDtsPD1jOysrbCkhKGw8YyYmZSh1PWFbbF0sbCxhKSk9PT1kJiYoKGQ9IWQpP28ubGluZVN0YXJ0KCk6by5saW5lRW5kKCkpLGQmJm8ucG9pbnQoK24odSxsLGEpLCt0KHUsbCxhKSk7aWYocClyZXR1cm4gbz1udWxsLHArIiJ8fG51bGx9cmV0dXJuIHMueD1mdW5jdGlvbihhKXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8obj0iZnVuY3Rpb24iPT10eXBlb2YgYT9hOmZhKCthKSxzKTpufSxzLnk9ZnVuY3Rpb24oYSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9ImZ1bmN0aW9uIj09dHlwZW9mIGE/YTpmYSgrYSkscyk6dH0scy5kZWZpbmVkPWZ1bmN0aW9uKGEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhlPSJmdW5jdGlvbiI9PXR5cGVvZiBhP2E6ZmEoISFhKSxzKTplfSxzLmN1cnZlPWZ1bmN0aW9uKGEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhyPWEsbnVsbCE9aSYmKG89cihpKSkscyk6cn0scy5jb250ZXh0PWZ1bmN0aW9uKGEpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhudWxsPT1hP2k9bz1udWxsOm89cihpPWEpLHMpOml9LHN9ZnVuY3Rpb24gU2NlKG4sdCxlKXtuLl9jb250ZXh0LmJlemllckN1cnZlVG8obi5feDErbi5fayoobi5feDItbi5feDApLG4uX3kxK24uX2sqKG4uX3kyLW4uX3kwKSxuLl94MituLl9rKihuLl94MS10KSxuLl95MituLl9rKihuLl95MS1lKSxuLl94MixuLl95Mil9ZnVuY3Rpb24gbU8obix0KXt0aGlzLl9jb250ZXh0PW4sdGhpcy5faz0oMS10KS82fWZ1bmN0aW9uIEVjZShuLHQpe3RoaXMuX2NvbnRleHQ9bix0aGlzLl9hbHBoYT10fU1hdGgsd2NlLnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3BvaW50PTB9LGxpbmVFbmQ6ZnVuY3Rpb24oKXsodGhpcy5fbGluZXx8MCE9PXRoaXMuX2xpbmUmJjE9PT10aGlzLl9wb2ludCkmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCksdGhpcy5fbGluZT0xLXRoaXMuX2xpbmV9LHBvaW50OmZ1bmN0aW9uKG4sdCl7c3dpdGNoKG49K24sdD0rdCx0aGlzLl9wb2ludCl7Y2FzZSAwOnRoaXMuX3BvaW50PTEsdGhpcy5fbGluZT90aGlzLl9jb250ZXh0LmxpbmVUbyhuLHQpOnRoaXMuX2NvbnRleHQubW92ZVRvKG4sdCk7YnJlYWs7Y2FzZSAxOnRoaXMuX3BvaW50PTI7ZGVmYXVsdDp0aGlzLl9jb250ZXh0LmxpbmVUbyhuLHQpfX19LG1PLnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3gwPXRoaXMuX3gxPXRoaXMuX3gyPXRoaXMuX3kwPXRoaXMuX3kxPXRoaXMuX3kyPU5hTix0aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7c3dpdGNoKHRoaXMuX3BvaW50KXtjYXNlIDI6dGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feDIsdGhpcy5feTIpO2JyZWFrO2Nhc2UgMzpTY2UodGhpcyx0aGlzLl94MSx0aGlzLl95MSl9KHRoaXMuX2xpbmV8fDAhPT10aGlzLl9saW5lJiYxPT09dGhpcy5fcG9pbnQpJiZ0aGlzLl9jb250ZXh0LmNsb3NlUGF0aCgpLHRoaXMuX2xpbmU9MS10aGlzLl9saW5lfSxwb2ludDpmdW5jdGlvbihuLHQpe3N3aXRjaChuPStuLHQ9K3QsdGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xLHRoaXMuX2xpbmU/dGhpcy5fY29udGV4dC5saW5lVG8obix0KTp0aGlzLl9jb250ZXh0Lm1vdmVUbyhuLHQpO2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yLHRoaXMuX3gxPW4sdGhpcy5feTE9dDticmVhaztjYXNlIDI6dGhpcy5fcG9pbnQ9MztkZWZhdWx0OlNjZSh0aGlzLG4sdCl9dGhpcy5feDA9dGhpcy5feDEsdGhpcy5feDE9dGhpcy5feDIsdGhpcy5feDI9bix0aGlzLl95MD10aGlzLl95MSx0aGlzLl95MT10aGlzLl95Mix0aGlzLl95Mj10fX0sZnVuY3Rpb24gbih0KXtmdW5jdGlvbiBlKGkpe3JldHVybiBuZXcgbU8oaSx0KX1yZXR1cm4gZS50ZW5zaW9uPWZ1bmN0aW9uKGkpe3JldHVybiBuKCtpKX0sZX0oMCksRWNlLnByb3RvdHlwZT17YXJlYVN0YXJ0OmZ1bmN0aW9uKCl7dGhpcy5fbGluZT0wfSxhcmVhRW5kOmZ1bmN0aW9uKCl7dGhpcy5fbGluZT1OYU59LGxpbmVTdGFydDpmdW5jdGlvbigpe3RoaXMuX3gwPXRoaXMuX3gxPXRoaXMuX3gyPXRoaXMuX3kwPXRoaXMuX3kxPXRoaXMuX3kyPU5hTix0aGlzLl9sMDFfYT10aGlzLl9sMTJfYT10aGlzLl9sMjNfYT10aGlzLl9sMDFfMmE9dGhpcy5fbDEyXzJhPXRoaXMuX2wyM18yYT10aGlzLl9wb2ludD0wfSxsaW5lRW5kOmZ1bmN0aW9uKCl7c3dpdGNoKHRoaXMuX3BvaW50KXtjYXNlIDI6dGhpcy5fY29udGV4dC5saW5lVG8odGhpcy5feDIsdGhpcy5feTIpO2JyZWFrO2Nhc2UgMzp0aGlzLnBvaW50KHRoaXMuX3gyLHRoaXMuX3kyKX0odGhpcy5fbGluZXx8MCE9PXRoaXMuX2xpbmUmJjE9PT10aGlzLl9wb2ludCkmJnRoaXMuX2NvbnRleHQuY2xvc2VQYXRoKCksdGhpcy5fbGluZT0xLXRoaXMuX2xpbmV9LHBvaW50OmZ1bmN0aW9uKG4sdCl7aWYobj0rbix0PSt0LHRoaXMuX3BvaW50KXt2YXIgZT10aGlzLl94Mi1uLGk9dGhpcy5feTItdDt0aGlzLl9sMjNfYT1NYXRoLnNxcnQodGhpcy5fbDIzXzJhPU1hdGgucG93KGUqZStpKmksdGhpcy5fYWxwaGEpKX1zd2l0Y2godGhpcy5fcG9pbnQpe2Nhc2UgMDp0aGlzLl9wb2ludD0xLHRoaXMuX2xpbmU/dGhpcy5fY29udGV4dC5saW5lVG8obix0KTp0aGlzLl9jb250ZXh0Lm1vdmVUbyhuLHQpO2JyZWFrO2Nhc2UgMTp0aGlzLl9wb2ludD0yO2JyZWFrO2Nhc2UgMjp0aGlzLl9wb2ludD0zO2RlZmF1bHQ6IWZ1bmN0aW9uKG4sdCxlKXt2YXIgaT1uLl94MSxyPW4uX3kxLG89bi5feDIscz1uLl95MjtpZihuLl9sMDFfYT4xZS0xMil7dmFyIGE9MipuLl9sMDFfMmErMypuLl9sMDFfYSpuLl9sMTJfYStuLl9sMTJfMmEsbD0zKm4uX2wwMV9hKihuLl9sMDFfYStuLl9sMTJfYSk7aT0oaSphLW4uX3gwKm4uX2wxMl8yYStuLl94MipuLl9sMDFfMmEpL2wscj0ociphLW4uX3kwKm4uX2wxMl8yYStuLl95MipuLl9sMDFfMmEpL2x9aWYobi5fbDIzX2E+MWUtMTIpe3ZhciBjPTIqbi5fbDIzXzJhKzMqbi5fbDIzX2Eqbi5fbDEyX2Erbi5fbDEyXzJhLHU9MypuLl9sMjNfYSoobi5fbDIzX2Erbi5fbDEyX2EpO289KG8qYytuLl94MSpuLl9sMjNfMmEtdCpuLl9sMTJfMmEpL3Uscz0ocypjK24uX3kxKm4uX2wyM18yYS1lKm4uX2wxMl8yYSkvdX1uLl9jb250ZXh0LmJlemllckN1cnZlVG8oaSxyLG8scyxuLl94MixuLl95Mil9KHRoaXMsbix0KX10aGlzLl9sMDFfYT10aGlzLl9sMTJfYSx0aGlzLl9sMTJfYT10aGlzLl9sMjNfYSx0aGlzLl9sMDFfMmE9dGhpcy5fbDEyXzJhLHRoaXMuX2wxMl8yYT10aGlzLl9sMjNfMmEsdGhpcy5feDA9dGhpcy5feDEsdGhpcy5feDE9dGhpcy5feDIsdGhpcy5feDI9bix0aGlzLl95MD10aGlzLl95MSx0aGlzLl95MT10aGlzLl95Mix0aGlzLl95Mj10fX07dmFyIHNqPWZ1bmN0aW9uIG4odCl7ZnVuY3Rpb24gZShpKXtyZXR1cm4gdD9uZXcgRWNlKGksdCk6bmV3IG1PKGksMCl9cmV0dXJuIGUuYWxwaGE9ZnVuY3Rpb24oaSl7cmV0dXJuIG4oK2kpfSxlfSguNSk7ZnVuY3Rpb24gYWooKXt0aGlzLl89bnVsbH1mdW5jdGlvbiBPeShuKXtuLlU9bi5DPW4uTD1uLlI9bi5QPW4uTj1udWxsfWZ1bmN0aW9uIENTKG4sdCl7dmFyIGU9dCxpPXQuUixyPWUuVTtyP3IuTD09PWU/ci5MPWk6ci5SPWk6bi5fPWksaS5VPXIsZS5VPWksZS5SPWkuTCxlLlImJihlLlIuVT1lKSxpLkw9ZX1mdW5jdGlvbiBNUyhuLHQpe3ZhciBlPXQsaT10Lkwscj1lLlU7cj9yLkw9PT1lP3IuTD1pOnIuUj1pOm4uXz1pLGkuVT1yLGUuVT1pLGUuTD1pLlIsZS5MJiYoZS5MLlU9ZSksaS5SPWV9ZnVuY3Rpb24gVGNlKG4pe2Zvcig7bi5MOyluPW4uTDtyZXR1cm4gbn1hai5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOmFqLGluc2VydDpmdW5jdGlvbihuLHQpe3ZhciBlLGkscjtpZihuKXtpZih0LlA9bix0Lk49bi5OLG4uTiYmKG4uTi5QPXQpLG4uTj10LG4uUil7Zm9yKG49bi5SO24uTDspbj1uLkw7bi5MPXR9ZWxzZSBuLlI9dDtlPW59ZWxzZSB0aGlzLl8/KG49VGNlKHRoaXMuXyksdC5QPW51bGwsdC5OPW4sbi5QPW4uTD10LGU9bik6KHQuUD10Lk49bnVsbCx0aGlzLl89dCxlPW51bGwpO2Zvcih0Lkw9dC5SPW51bGwsdC5VPWUsdC5DPSEwLG49dDtlJiZlLkM7KWU9PT0oaT1lLlUpLkw/KHI9aS5SKSYmci5DPyhlLkM9ci5DPSExLGkuQz0hMCxuPWkpOihuPT09ZS5SJiYoQ1ModGhpcyxlKSxlPShuPWUpLlUpLGUuQz0hMSxpLkM9ITAsTVModGhpcyxpKSk6KHI9aS5MKSYmci5DPyhlLkM9ci5DPSExLGkuQz0hMCxuPWkpOihuPT09ZS5MJiYoTVModGhpcyxlKSxlPShuPWUpLlUpLGUuQz0hMSxpLkM9ITAsQ1ModGhpcyxpKSksZT1uLlU7dGhpcy5fLkM9ITF9LHJlbW92ZTpmdW5jdGlvbihuKXtuLk4mJihuLk4uUD1uLlApLG4uUCYmKG4uUC5OPW4uTiksbi5OPW4uUD1udWxsO3ZhciBlLG8scyx0PW4uVSxpPW4uTCxyPW4uUjtpZihvPWk/cj9UY2Uocik6aTpyLHQ/dC5MPT09bj90Lkw9bzp0LlI9bzp0aGlzLl89byxpJiZyPyhzPW8uQyxvLkM9bi5DLG8uTD1pLGkuVT1vLG8hPT1yPyh0PW8uVSxvLlU9bi5VLHQuTD1uPW8uUixvLlI9cixyLlU9byk6KG8uVT10LHQ9byxuPW8uUikpOihzPW4uQyxuPW8pLG4mJihuLlU9dCksIXMpe2lmKG4mJm4uQylyZXR1cm4gdm9pZChuLkM9ITEpO2Rve2lmKG49PT10aGlzLl8pYnJlYWs7aWYobj09PXQuTCl7aWYoKGU9dC5SKS5DJiYoZS5DPSExLHQuQz0hMCxDUyh0aGlzLHQpLGU9dC5SKSxlLkwmJmUuTC5DfHxlLlImJmUuUi5DKXsoIWUuUnx8IWUuUi5DKSYmKGUuTC5DPSExLGUuQz0hMCxNUyh0aGlzLGUpLGU9dC5SKSxlLkM9dC5DLHQuQz1lLlIuQz0hMSxDUyh0aGlzLHQpLG49dGhpcy5fO2JyZWFrfX1lbHNlIGlmKChlPXQuTCkuQyYmKGUuQz0hMSx0LkM9ITAsTVModGhpcyx0KSxlPXQuTCksZS5MJiZlLkwuQ3x8ZS5SJiZlLlIuQyl7KCFlLkx8fCFlLkwuQykmJihlLlIuQz0hMSxlLkM9ITAsQ1ModGhpcyxlKSxlPXQuTCksZS5DPXQuQyx0LkM9ZS5MLkM9ITEsTVModGhpcyx0KSxuPXRoaXMuXzticmVha31lLkM9ITAsbj10LHQ9dC5VfXdoaWxlKCFuLkMpO24mJihuLkM9ITEpfX19O3ZhciBsaj1hajtmdW5jdGlvbiBreShuLHQsZSxpKXt2YXIgcj1bbnVsbCxudWxsXSxvPWRzLnB1c2gociktMTtyZXR1cm4gci5sZWZ0PW4sci5yaWdodD10LGUmJndTKHIsbix0LGUpLGkmJndTKHIsdCxuLGkpLG1hW24uaW5kZXhdLmhhbGZlZGdlcy5wdXNoKG8pLG1hW3QuaW5kZXhdLmhhbGZlZGdlcy5wdXNoKG8pLHJ9ZnVuY3Rpb24gRnkobix0LGUpe3ZhciBpPVt0LGVdO3JldHVybiBpLmxlZnQ9bixpfWZ1bmN0aW9uIHdTKG4sdCxlLGkpe25bMF18fG5bMV0/bi5sZWZ0PT09ZT9uWzFdPWk6blswXT1pOihuWzBdPWksbi5sZWZ0PXQsbi5yaWdodD1lKX1mdW5jdGlvbiB4amUobix0LGUsaSxyKXt2YXIgbSxvPW5bMF0scz1uWzFdLGE9b1swXSxsPW9bMV0sZD0wLHA9MSxoPXNbMF0tYSxmPXNbMV0tbDtpZihtPXQtYSxofHwhKG0+MCkpe2lmKG0vPWgsaDwwKXtpZihtPGQpcmV0dXJuO208cCYmKHA9bSl9ZWxzZSBpZihoPjApe2lmKG0+cClyZXR1cm47bT5kJiYoZD1tKX1pZihtPWktYSxofHwhKG08MCkpe2lmKG0vPWgsaDwwKXtpZihtPnApcmV0dXJuO20+ZCYmKGQ9bSl9ZWxzZSBpZihoPjApe2lmKG08ZClyZXR1cm47bTxwJiYocD1tKX1pZihtPWUtbCxmfHwhKG0+MCkpe2lmKG0vPWYsZjwwKXtpZihtPGQpcmV0dXJuO208cCYmKHA9bSl9ZWxzZSBpZihmPjApe2lmKG0+cClyZXR1cm47bT5kJiYoZD1tKX1pZihtPXItbCxmfHwhKG08MCkpe2lmKG0vPWYsZjwwKXtpZihtPnApcmV0dXJuO20+ZCYmKGQ9bSl9ZWxzZSBpZihmPjApe2lmKG08ZClyZXR1cm47bTxwJiYocD1tKX1yZXR1cm4hKGQ+MCkmJiEocDwxKXx8KGQ+MCYmKG5bMF09W2ErZCpoLGwrZCpmXSkscDwxJiYoblsxXT1bYStwKmgsbCtwKmZdKSksITB9fX19fWZ1bmN0aW9uIENqZShuLHQsZSxpLHIpe3ZhciBvPW5bMV07aWYobylyZXR1cm4hMDt2YXIgbSx4LHM9blswXSxhPW4ubGVmdCxsPW4ucmlnaHQsYz1hWzBdLHU9YVsxXSxkPWxbMF0scD1sWzFdLGg9KGMrZCkvMjtpZihwPT09dSl7aWYoaDx0fHxoPj1pKXJldHVybjtpZihjPmQpe2lmKHMpe2lmKHNbMV0+PXIpcmV0dXJufWVsc2Ugcz1baCxlXTtvPVtoLHJdfWVsc2V7aWYocyl7aWYoc1sxXTxlKXJldHVybn1lbHNlIHM9W2gscl07bz1baCxlXX19ZWxzZSBpZih4PSh1K3ApLzItKG09KGMtZCkvKHAtdSkpKmgsbTwtMXx8bT4xKWlmKGM+ZCl7aWYocyl7aWYoc1sxXT49cilyZXR1cm59ZWxzZSBzPVsoZS14KS9tLGVdO289WyhyLXgpL20scl19ZWxzZXtpZihzKXtpZihzWzFdPGUpcmV0dXJufWVsc2Ugcz1bKHIteCkvbSxyXTtvPVsoZS14KS9tLGVdfWVsc2UgaWYodTxwKXtpZihzKXtpZihzWzBdPj1pKXJldHVybn1lbHNlIHM9W3QsbSp0K3hdO289W2ksbSppK3hdfWVsc2V7aWYocyl7aWYoc1swXTx0KXJldHVybn1lbHNlIHM9W2ksbSppK3hdO289W3QsbSp0K3hdfXJldHVybiBuWzBdPXMsblsxXT1vLCEwfWZ1bmN0aW9uIE1qZShuLHQpe3ZhciBlPW4uc2l0ZSxpPXQubGVmdCxyPXQucmlnaHQ7cmV0dXJuIGU9PT1yJiYocj1pLGk9ZSkscj9NYXRoLmF0YW4yKHJbMV0taVsxXSxyWzBdLWlbMF0pOihlPT09aT8oaT10WzFdLHI9dFswXSk6KGk9dFswXSxyPXRbMV0pLE1hdGguYXRhbjIoaVswXS1yWzBdLHJbMV0taVsxXSkpfWZ1bmN0aW9uIGNqKG4sdCl7cmV0dXJuIHRbKyh0LmxlZnQhPT1uLnNpdGUpXX1mdW5jdGlvbiB3amUobix0KXtyZXR1cm4gdFsrKHQubGVmdD09PW4uc2l0ZSldfXZhciBnTyxSY2U9W107ZnVuY3Rpb24gU2plKCl7T3kodGhpcyksdGhpcy54PXRoaXMueT10aGlzLmFyYz10aGlzLnNpdGU9dGhpcy5jeT1udWxsfWZ1bmN0aW9uIE9nKG4pe3ZhciB0PW4uUCxlPW4uTjtpZih0JiZlKXt2YXIgaT10LnNpdGUscj1uLnNpdGUsbz1lLnNpdGU7aWYoaSE9PW8pe3ZhciBzPXJbMF0sYT1yWzFdLGw9aVswXS1zLGM9aVsxXS1hLHU9b1swXS1zLGQ9b1sxXS1hLHA9MioobCpkLWMqdSk7aWYoIShwPj0tT2NlKSl7dmFyIGg9bCpsK2MqYyxmPXUqdStkKmQsbT0oZCpoLWMqZikvcCx4PShsKmYtdSpoKS9wLGc9UmNlLnBvcCgpfHxuZXcgU2plO2cuYXJjPW4sZy5zaXRlPXIsZy54PW0rcyxnLnk9KGcuY3k9eCthKStNYXRoLnNxcnQobSptK3gqeCksbi5jaXJjbGU9Zztmb3IodmFyIGI9bnVsbCxEPU55Ll87RDspaWYoZy55PEQueXx8Zy55PT09RC55JiZnLng8PUQueCl7aWYoIUQuTCl7Yj1ELlA7YnJlYWt9RD1ELkx9ZWxzZXtpZighRC5SKXtiPUQ7YnJlYWt9RD1ELlJ9TnkuaW5zZXJ0KGIsZyksYnx8KGdPPWcpfX19fWZ1bmN0aW9uIGtnKG4pe3ZhciB0PW4uY2lyY2xlO3QmJih0LlB8fChnTz10Lk4pLE55LnJlbW92ZSh0KSxSY2UucHVzaCh0KSxPeSh0KSxuLmNpcmNsZT1udWxsKX12YXIgRmNlPVtdO2Z1bmN0aW9uIEVqZSgpe095KHRoaXMpLHRoaXMuZWRnZT10aGlzLnNpdGU9dGhpcy5jaXJjbGU9bnVsbH1mdW5jdGlvbiBrY2Uobil7dmFyIHQ9RmNlLnBvcCgpfHxuZXcgRWplO3JldHVybiB0LnNpdGU9bix0fWZ1bmN0aW9uIHVqKG4pe2tnKG4pLEZnLnJlbW92ZShuKSxGY2UucHVzaChuKSxPeShuKX1mdW5jdGlvbiBOY2Uobil7dmFyIHQ9bi5jaXJjbGUsZT10LngsaT10LmN5LHI9W2UsaV0sbz1uLlAscz1uLk4sYT1bbl07dWoobik7Zm9yKHZhciBsPW87bC5jaXJjbGUmJk1hdGguYWJzKGUtbC5jaXJjbGUueCk8cHImJk1hdGguYWJzKGktbC5jaXJjbGUuY3kpPHByOylvPWwuUCxhLnVuc2hpZnQobCksdWoobCksbD1vO2EudW5zaGlmdChsKSxrZyhsKTtmb3IodmFyIGM9cztjLmNpcmNsZSYmTWF0aC5hYnMoZS1jLmNpcmNsZS54KTxwciYmTWF0aC5hYnMoaS1jLmNpcmNsZS5jeSk8cHI7KXM9Yy5OLGEucHVzaChjKSx1aihjKSxjPXM7YS5wdXNoKGMpLGtnKGMpO3ZhciBkLHU9YS5sZW5ndGg7Zm9yKGQ9MTtkPHU7KytkKXdTKChjPWFbZF0pLmVkZ2UsKGw9YVtkLTFdKS5zaXRlLGMuc2l0ZSxyKTsoYz1hW3UtMV0pLmVkZ2U9a3koKGw9YVswXSkuc2l0ZSxjLnNpdGUsbnVsbCxyKSxPZyhsKSxPZyhjKX1mdW5jdGlvbiBMY2Uobil7Zm9yKHZhciBpLHIsbyxzLHQ9blswXSxlPW5bMV0sYT1GZy5fO2E7KWlmKChvPUJjZShhLGUpLXQpPnByKWE9YS5MO2Vsc2V7aWYoISgocz10LVRqZShhLGUpKT5wcikpe28+LXByPyhpPWEuUCxyPWEpOnM+LXByPyhpPWEscj1hLk4pOmk9cj1hO2JyZWFrfWlmKCFhLlIpe2k9YTticmVha31hPWEuUn0hZnVuY3Rpb24obil7bWFbbi5pbmRleF09e3NpdGU6bixoYWxmZWRnZXM6W119fShuKTt2YXIgbD1rY2Uobik7aWYoRmcuaW5zZXJ0KGksbCksaXx8cil7aWYoaT09PXIpcmV0dXJuIGtnKGkpLHI9a2NlKGkuc2l0ZSksRmcuaW5zZXJ0KGwsciksbC5lZGdlPXIuZWRnZT1reShpLnNpdGUsbC5zaXRlKSxPZyhpKSx2b2lkIE9nKHIpO2lmKCFyKXJldHVybiB2b2lkKGwuZWRnZT1reShpLnNpdGUsbC5zaXRlKSk7a2coaSksa2cocik7dmFyIGM9aS5zaXRlLHU9Y1swXSxkPWNbMV0scD1uWzBdLXUsaD1uWzFdLWQsZj1yLnNpdGUsbT1mWzBdLXUseD1mWzFdLWQsZz0yKihwKngtaCptKSxiPXAqcCtoKmgsRD1tKm0reCp4LFQ9Wyh4KmItaCpEKS9nK3UsKHAqRC1tKmIpL2crZF07d1Moci5lZGdlLGMsZixUKSxsLmVkZ2U9a3koYyxuLG51bGwsVCksci5lZGdlPWt5KG4sZixudWxsLFQpLE9nKGkpLE9nKHIpfX1mdW5jdGlvbiBCY2Uobix0KXt2YXIgZT1uLnNpdGUsaT1lWzBdLHI9ZVsxXSxvPXItdDtpZighbylyZXR1cm4gaTt2YXIgcz1uLlA7aWYoIXMpcmV0dXJuLTEvMDt2YXIgYT0oZT1zLnNpdGUpWzBdLGw9ZVsxXSxjPWwtdDtpZighYylyZXR1cm4gYTt2YXIgdT1hLWksZD0xL28tMS9jLHA9dS9jO3JldHVybiBkPygtcCtNYXRoLnNxcnQocCpwLTIqZCoodSp1LygtMipjKS1sK2MvMityLW8vMikpKS9kK2k6KGkrYSkvMn1mdW5jdGlvbiBUamUobix0KXt2YXIgZT1uLk47aWYoZSlyZXR1cm4gQmNlKGUsdCk7dmFyIGk9bi5zaXRlO3JldHVybiBpWzFdPT09dD9pWzBdOjEvMH12YXIgRmcsbWEsTnksZHMscHI9MWUtNixPY2U9MWUtMTI7ZnVuY3Rpb24gRGplKG4sdCxlKXtyZXR1cm4oblswXS1lWzBdKSoodFsxXS1uWzFdKS0oblswXS10WzBdKSooZVsxXS1uWzFdKX1mdW5jdGlvbiBBamUobix0KXtyZXR1cm4gdFsxXS1uWzFdfHx0WzBdLW5bMF19ZnVuY3Rpb24gX08obix0KXt2YXIgaSxyLG8sZT1uLnNvcnQoQWplKS5wb3AoKTtmb3IoZHM9W10sbWE9bmV3IEFycmF5KG4ubGVuZ3RoKSxGZz1uZXcgbGosTnk9bmV3IGxqOzspaWYobz1nTyxlJiYoIW98fGVbMV08by55fHxlWzFdPT09by55JiZlWzBdPG8ueCkpKGVbMF0hPT1pfHxlWzFdIT09cikmJihMY2UoZSksaT1lWzBdLHI9ZVsxXSksZT1uLnBvcCgpO2Vsc2V7aWYoIW8pYnJlYWs7TmNlKG8uYXJjKX1pZihmdW5jdGlvbigpe2Zvcih2YXIgZSxpLHIsbyxuPTAsdD1tYS5sZW5ndGg7bjx0OysrbilpZigoZT1tYVtuXSkmJihvPShpPWUuaGFsZmVkZ2VzKS5sZW5ndGgpKXt2YXIgcz1uZXcgQXJyYXkobyksYT1uZXcgQXJyYXkobyk7Zm9yKHI9MDtyPG87KytyKXNbcl09cixhW3JdPU1qZShlLGRzW2lbcl1dKTtmb3Iocy5zb3J0KGZ1bmN0aW9uKGwsYyl7cmV0dXJuIGFbY10tYVtsXX0pLHI9MDtyPG87KytyKWFbcl09aVtzW3JdXTtmb3Iocj0wO3I8bzsrK3IpaVtyXT1hW3JdfX0oKSx0KXt2YXIgcz0rdFswXVswXSxhPSt0WzBdWzFdLGw9K3RbMV1bMF0sYz0rdFsxXVsxXTsoZnVuY3Rpb24obix0LGUsaSl7Zm9yKHZhciBvLHI9ZHMubGVuZ3RoO3ItLTspQ2plKG89ZHNbcl0sbix0LGUsaSkmJnhqZShvLG4sdCxlLGkpJiYoTWF0aC5hYnMob1swXVswXS1vWzFdWzBdKT5wcnx8TWF0aC5hYnMob1swXVsxXS1vWzFdWzFdKT5wcil8fGRlbGV0ZSBkc1tyXX0pKHMsYSxsLGMpLGZ1bmN0aW9uKG4sdCxlLGkpe3ZhciBvLHMsYSxsLGMsdSxkLHAsaCxmLG0seCxyPW1hLmxlbmd0aCxnPSEwO2ZvcihvPTA7bzxyOysrbylpZihzPW1hW29dKXtmb3IoYT1zLnNpdGUsbD0oYz1zLmhhbGZlZGdlcykubGVuZ3RoO2wtLTspZHNbY1tsXV18fGMuc3BsaWNlKGwsMSk7Zm9yKGw9MCx1PWMubGVuZ3RoO2w8dTspbT0oZj13amUocyxkc1tjW2xdXSkpWzBdLHg9ZlsxXSxwPShkPWNqKHMsZHNbY1srK2wldV1dKSlbMF0saD1kWzFdLChNYXRoLmFicyhtLXApPnByfHxNYXRoLmFicyh4LWgpPnByKSYmKGMuc3BsaWNlKGwsMCxkcy5wdXNoKEZ5KGEsZixNYXRoLmFicyhtLW4pPHByJiZpLXg+cHI/W24sTWF0aC5hYnMocC1uKTxwcj9oOmldOk1hdGguYWJzKHgtaSk8cHImJmUtbT5wcj9bTWF0aC5hYnMoaC1pKTxwcj9wOmUsaV06TWF0aC5hYnMobS1lKTxwciYmeC10PnByP1tlLE1hdGguYWJzKHAtZSk8cHI/aDp0XTpNYXRoLmFicyh4LXQpPHByJiZtLW4+cHI/W01hdGguYWJzKGgtdCk8cHI/cDpuLHRdOm51bGwpKS0xKSwrK3UpO3UmJihnPSExKX1pZihnKXt2YXIgYixELFQsaz0xLzA7Zm9yKG89MCxnPW51bGw7bzxyOysrbykocz1tYVtvXSkmJihUPShiPShhPXMuc2l0ZSlbMF0tbikqYisoRD1hWzFdLXQpKkQpPGsmJihrPVQsZz1zKTtpZihnKXt2YXIgWj1bbix0XSx6PVtuLGldLGZlPVtlLGldLHVlPVtlLHRdO2cuaGFsZmVkZ2VzLnB1c2goZHMucHVzaChGeShhPWcuc2l0ZSxaLHopKS0xLGRzLnB1c2goRnkoYSx6LGZlKSktMSxkcy5wdXNoKEZ5KGEsZmUsdWUpKS0xLGRzLnB1c2goRnkoYSx1ZSxaKSktMSl9fWZvcihvPTA7bzxyOysrbykocz1tYVtvXSkmJihzLmhhbGZlZGdlcy5sZW5ndGh8fGRlbGV0ZSBtYVtvXSl9KHMsYSxsLGMpfXRoaXMuZWRnZXM9ZHMsdGhpcy5jZWxscz1tYSxGZz1OeT1kcz1tYT1udWxsfWZ1bmN0aW9uIE5nKG4sdCxlKXt0aGlzLms9bix0aGlzLng9dCx0aGlzLnk9ZX1fTy5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOl9PLHBvbHlnb25zOmZ1bmN0aW9uKCl7dmFyIG49dGhpcy5lZGdlcztyZXR1cm4gdGhpcy5jZWxscy5tYXAoZnVuY3Rpb24odCl7dmFyIGU9dC5oYWxmZWRnZXMubWFwKGZ1bmN0aW9uKGkpe3JldHVybiBjaih0LG5baV0pfSk7cmV0dXJuIGUuZGF0YT10LnNpdGUuZGF0YSxlfSl9LHRyaWFuZ2xlczpmdW5jdGlvbigpe3ZhciBuPVtdLHQ9dGhpcy5lZGdlcztyZXR1cm4gdGhpcy5jZWxscy5mb3JFYWNoKGZ1bmN0aW9uKGUsaSl7aWYoYT0obz1lLmhhbGZlZGdlcykubGVuZ3RoKWZvcih2YXIgbyxhLGwscj1lLnNpdGUscz0tMSxjPXRbb1thLTFdXSx1PWMubGVmdD09PXI/Yy5yaWdodDpjLmxlZnQ7KytzPGE7KWw9dSx1PShjPXRbb1tzXV0pLmxlZnQ9PT1yP2MucmlnaHQ6Yy5sZWZ0LGwmJnUmJmk8bC5pbmRleCYmaTx1LmluZGV4JiZEamUocixsLHUpPDAmJm4ucHVzaChbci5kYXRhLGwuZGF0YSx1LmRhdGFdKX0pLG59LGxpbmtzOmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZWRnZXMuZmlsdGVyKGZ1bmN0aW9uKG4pe3JldHVybiBuLnJpZ2h0fSkubWFwKGZ1bmN0aW9uKG4pe3JldHVybntzb3VyY2U6bi5sZWZ0LmRhdGEsdGFyZ2V0Om4ucmlnaHQuZGF0YX19KX0sZmluZDpmdW5jdGlvbihuLHQsZSl7Zm9yKHZhciByLGEsaT10aGlzLG89aS5fZm91bmR8fDAscz1pLmNlbGxzLmxlbmd0aDshKGE9aS5jZWxsc1tvXSk7KWlmKCsrbz49cylyZXR1cm4gbnVsbDt2YXIgbD1uLWEuc2l0ZVswXSxjPXQtYS5zaXRlWzFdLHU9bCpsK2MqYztkb3thPWkuY2VsbHNbcj1vXSxvPW51bGwsYS5oYWxmZWRnZXMuZm9yRWFjaChmdW5jdGlvbihkKXt2YXIgcD1pLmVkZ2VzW2RdLGg9cC5sZWZ0O2lmKGghPT1hLnNpdGUmJmh8fChoPXAucmlnaHQpKXt2YXIgZj1uLWhbMF0sbT10LWhbMV0seD1mKmYrbSptO3g8dSYmKHU9eCxvPWguaW5kZXgpfX0pfXdoaWxlKG51bGwhPT1vKTtyZXR1cm4gaS5fZm91bmQ9cixudWxsPT1lfHx1PD1lKmU/YS5zaXRlOm51bGx9fSxOZy5wcm90b3R5cGU9e2NvbnN0cnVjdG9yOk5nLHNjYWxlOmZ1bmN0aW9uKG4pe3JldHVybiAxPT09bj90aGlzOm5ldyBOZyh0aGlzLmsqbix0aGlzLngsdGhpcy55KX0sdHJhbnNsYXRlOmZ1bmN0aW9uKG4sdCl7cmV0dXJuIDA9PT1uJjA9PT10P3RoaXM6bmV3IE5nKHRoaXMuayx0aGlzLngrdGhpcy5rKm4sdGhpcy55K3RoaXMuayp0KX0sYXBwbHk6ZnVuY3Rpb24obil7cmV0dXJuW25bMF0qdGhpcy5rK3RoaXMueCxuWzFdKnRoaXMuayt0aGlzLnldfSxhcHBseVg6ZnVuY3Rpb24obil7cmV0dXJuIG4qdGhpcy5rK3RoaXMueH0sYXBwbHlZOmZ1bmN0aW9uKG4pe3JldHVybiBuKnRoaXMuayt0aGlzLnl9LGludmVydDpmdW5jdGlvbihuKXtyZXR1cm5bKG5bMF0tdGhpcy54KS90aGlzLmssKG5bMV0tdGhpcy55KS90aGlzLmtdfSxpbnZlcnRYOmZ1bmN0aW9uKG4pe3JldHVybihuLXRoaXMueCkvdGhpcy5rfSxpbnZlcnRZOmZ1bmN0aW9uKG4pe3JldHVybihuLXRoaXMueSkvdGhpcy5rfSxyZXNjYWxlWDpmdW5jdGlvbihuKXtyZXR1cm4gbi5jb3B5KCkuZG9tYWluKG4ucmFuZ2UoKS5tYXAodGhpcy5pbnZlcnRYLHRoaXMpLm1hcChuLmludmVydCxuKSl9LHJlc2NhbGVZOmZ1bmN0aW9uKG4pe3JldHVybiBuLmNvcHkoKS5kb21haW4obi5yYW5nZSgpLm1hcCh0aGlzLmludmVydFksdGhpcykubWFwKG4uaW52ZXJ0LG4pKX0sdG9TdHJpbmc6ZnVuY3Rpb24oKXtyZXR1cm4idHJhbnNsYXRlKCIrdGhpcy54KyIsIit0aGlzLnkrIikgc2NhbGUoIit0aGlzLmsrIikifX0sbmV3IE5nKDEsMCwwKTt2YXIgemNlPXhvKCIuMn5lIiksSWplPXhvKCIuNH5yIiksVmNlPXhvKCIsfiIpO2Z1bmN0aW9uIEhjZShuKXtpZigwPT09bilyZXR1cm4iMCI7bGV0IHQ9TWF0aC5hYnMobik7cmV0dXJuIHQ+PTFlNHx8dDwuMDAxP3pjZShuKTpJamUobil9dmFyIHlwPXtmb3JtYXRUaWNrOkhjZSxmb3JtYXRTaG9ydDpIY2UsZm9ybWF0UmVhZGFibGUobil7bGV0IHQ9TWF0aC5hYnMobik7cmV0dXJuIHQ+PTFlNHx8dDwuMDAxP3pjZShuKTpWY2Uobil9LGZvcm1hdExvbmc6VmNlfSxQamU9bmV3IEludGwuTnVtYmVyRm9ybWF0KHZvaWQgMCx7bWF4aW11bUZyYWN0aW9uRGlnaXRzOjN9KTtmdW5jdGlvbiB2TyhuKXtyZXR1cm4gUGplLmZvcm1hdChuKX12YXIgTWQ9e2Zvcm1hdFRpY2s6dk8sZm9ybWF0U2hvcnQ6dk8sZm9ybWF0UmVhZGFibGU6dk8sZm9ybWF0TG9uZzp2T30sUmplPXhvKCIwLjN+cyIpLE9qZT14bygiLC4zfmYiKTtmdW5jdGlvbiB5TyhuKXtsZXQgdD1NYXRoLmFicyhuKTtyZXR1cm4gdD49MWU0fHx0PC4wMDE/UmplKG4pOk9qZShuKX12YXIgamNlPXtmb3JtYXRUaWNrOnlPLGZvcm1hdFNob3J0OnlPLGZvcm1hdFJlYWRhYmxlOnlPLGZvcm1hdExvbmc6eU99LGdqPTM2ZTUsTHk9eG8oIi40fiIpO2Z1bmN0aW9uIGJPKG4pe2lmKDA9PT1uKXJldHVybiIwIjtsZXQgdD1NYXRoLnNpZ24obik+MD8iIjoiLSIsZT1NYXRoLmFicyhuKTtyZXR1cm4gdCs9ZTwxZTM/YCR7THkoZSl9IG1zYDplPDZlND9gJHtMeShlLzFlMyl9IHNlY2A6ZTxnaj9gJHtMeShlLzZlNCl9IG1pbmA6ZTw4NjRlNT9gJHtMeShlL2dqKX0gaHJgOmU8MzE1MzZlNj9gJHtMeShlLzg2NGU1KX0gZGF5YDpgJHtMeShlLzMxNTM2ZTYpfSB5cmAsdH12YXIgaGosU1M9e2Zvcm1hdFRpY2s6Yk8sZm9ybWF0U2hvcnQ6Yk8sZm9ybWF0UmVhZGFibGU6Yk8sZm9ybWF0TG9uZzpiT30sa2plPVJnKCkudGlja0Zvcm1hdCgpLEdjZT17Zm9ybWF0VGljazpuPT5ramUobmV3IERhdGUobikpLGZvcm1hdFNob3J0Om49Pm5ldyBEYXRlKG4pLnRvTG9jYWxlU3RyaW5nKGhqLHt5ZWFyOiJudW1lcmljIixtb250aDoic2hvcnQiLGRheToibnVtZXJpYyIsaG91cjoibnVtZXJpYyIsbWludXRlOiJudW1lcmljIixzZWNvbmQ6Im51bWVyaWMifSksZm9ybWF0UmVhZGFibGU6bj0+bmV3IERhdGUobikudG9Mb2NhbGVTdHJpbmcoaGose3llYXI6Im51bWVyaWMiLG1vbnRoOiJzaG9ydCIsZGF5OiJudW1lcmljIixob3VyOiJudW1lcmljIixtaW51dGU6Im51bWVyaWMiLHNlY29uZDoibnVtZXJpYyIsdGltZVpvbmVOYW1lOiJzaG9ydCJ9KSxmb3JtYXRMb25nOm49Pm5ldyBEYXRlKG4pLnRvTG9jYWxlU3RyaW5nKGhqLHt5ZWFyOiJudW1lcmljIixtb250aDoibG9uZyIsZGF5OiJudW1lcmljIixob3VyOiJudW1lcmljIixtaW51dGU6Im51bWVyaWMiLHNlY29uZDoibnVtZXJpYyIsdGltZVpvbmVOYW1lOiJzaG9ydCIsZnJhY3Rpb25hbFNlY29uZERpZ2l0czozfSl9O2Z1bmN0aW9uIG91KG4pe3N3aXRjaChuKXtjYXNlIE5yLkxJTkVBUjpyZXR1cm4gbmV3IEVTO2Nhc2UgTnIuTE9HMTA6cmV0dXJuIG5ldyBiajtjYXNlIE5yLlRJTUU6cmV0dXJuIG5ldyBUUztkZWZhdWx0OnRocm93IG5ldyBSYW5nZUVycm9yKGBTY2FsZVR5cGUgJHtufSBub3Qgc3VwcG9ydGVkLmApfX12YXIgRVM9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmRlZmF1bHRGb3JtYXR0ZXI9eXB9dHJhbnNmb3JtKHQsZSxpKXtsZXRbcixvXT10LHM9by1yLFthLGxdPWU7cmV0dXJuIDA9PT1zP2E6KGwtYSkvcyooaS1yKSthfWZvcndhcmQodCxlLGkpe3JldHVybiB0aGlzLnRyYW5zZm9ybSh0LGUsaSl9cmV2ZXJzZSh0LGUsaSl7cmV0dXJuIHRoaXMudHJhbnNmb3JtKGUsdCxpKX1uaWNlRG9tYWluKHQpe2xldFtlLGldPXQ7aWYoaTxlKXRocm93IG5ldyBFcnJvcigiVW5leHBlY3RlZCBpbnB1dDogbWluIGlzIGxhcmdlciB0aGFuIG1heCIpO2lmKGk9PT1lKXJldHVybiAwPT09ZT9bLTEsMV06ZTwwP1syKmUsMF06WzAsMiplXTtsZXQgcj1RbygpLG89LjA1KihpLWUrTnVtYmVyLkVQU0lMT04pLFtzLGFdPXIuZG9tYWluKFtlLW8saStvXSkubmljZSgpLmRvbWFpbigpO3JldHVybltzLGFdfXRpY2tzKHQsZSl7cmV0dXJuIFFvKCkuZG9tYWluKHQpLnRpY2tzKGUpfWlzU2FmZU51bWJlcih0KXtyZXR1cm4gTnVtYmVyLmlzRmluaXRlKHQpfX0sYmo9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmRlZmF1bHRGb3JtYXR0ZXI9eXB9dHJhbnNmb3JtKHQpe3JldHVybiBNYXRoLmxvZzEwKHQ+MD90Ok51bWJlci5NSU5fVkFMVUUpfXVudHJhbnNmb3JtKHQpe3JldHVybiBNYXRoLmV4cCh0L01hdGguTE9HMTBFKX1mb3J3YXJkKHQsZSxpKXtpZihpPD0wKXJldHVybiBlWzBdO2xldFtyLG9dPXQsW3MsYV09ZSxsPXRoaXMudHJhbnNmb3JtKHIpLHU9dGhpcy50cmFuc2Zvcm0obyktbCxkPWEtcztyZXR1cm4gaT10aGlzLnRyYW5zZm9ybShpKSxkLyh1K051bWJlci5FUFNJTE9OKSooaS1sKStzfXJldmVyc2UodCxlLGkpe2xldFtyLG9dPXQsW3MsYV09ZSxsPXRoaXMudHJhbnNmb3JtKHIpLHU9dGhpcy50cmFuc2Zvcm0obyktbDtyZXR1cm4gdGhpcy51bnRyYW5zZm9ybSh1LyhhLXMrTnVtYmVyLkVQU0lMT04pKihpLXMpK2wpfW5pY2VEb21haW4odCl7bGV0W2UsaV09dDtpZihlPmkpdGhyb3cgbmV3IEVycm9yKCJVbmV4cGVjdGVkIGlucHV0OiBtaW4gaXMgbGFyZ2VyIHRoYW4gbWF4Iik7bGV0IHI9TWF0aC5tYXgoZSxOdW1iZXIuTUlOX1ZBTFVFKSxvPU1hdGgubWF4KGksTnVtYmVyLk1JTl9WQUxVRSk7cmV0dXJuIGk8PTA/W051bWJlci5NSU5fVkFMVUUsMV06W01hdGgubWF4KE51bWJlci5NSU5fVkFMVUUsLjUqciksMipvXX10aWNrcyh0LGUpe2xldCBpPXRbMF08PTA/TnVtYmVyLk1JTl9WQUxVRTp0WzBdLHI9dFsxXTw9MD9OdW1iZXIuTUlOX1ZBTFVFOnRbMV0sbz1wUygpLmRvbWFpbihbaSxyXSkudGlja3MoZSk7cmV0dXJuIG8ubGVuZ3RoP286dH1pc1NhZmVOdW1iZXIodCl7cmV0dXJuIE51bWJlci5pc0Zpbml0ZSh0KSYmdD4wfX0sVFM9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnNjYWxlPVJnKCksdGhpcy5kZWZhdWx0Rm9ybWF0dGVyPUdjZX1mb3J3YXJkKHQsZSxpKXtyZXR1cm4gdGhpcy5zY2FsZS5kb21haW4odCkucmFuZ2UoZSkoaSl9cmV2ZXJzZSh0LGUsaSl7cmV0dXJuIHRoaXMuc2NhbGUuZG9tYWluKHQpLnJhbmdlKGUpLmludmVydChpKS5nZXRUaW1lKCl9bmljZURvbWFpbih0KXtsZXRbZSxpXT10aGlzLnNjYWxlLmRvbWFpbih0KS5uaWNlKCkuZG9tYWluKCk7cmV0dXJuW2UuZ2V0VGltZSgpLGkuZ2V0VGltZSgpXX10aWNrcyh0LGUpe3JldHVybiB0aGlzLnNjYWxlLmRvbWFpbih0KS50aWNrcyhlKS5tYXAoaT0+aS5nZXRUaW1lKCkpfWlzU2FmZU51bWJlcih0KXtyZXR1cm4gTnVtYmVyLmlzRmluaXRlKHQpfX0seGo9ITE7aWYoc2VsZi5oYXNPd25Qcm9wZXJ0eSgiV2ViR0wyUmVuZGVyaW5nQ29udGV4dCIpJiZzZWxmLmhhc093blByb3BlcnR5KCJkb2N1bWVudCIpKXtsZXQgbj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKTtuLmFkZEV2ZW50TGlzdGVuZXIoIndlYmdsY29udGV4dGNyZWF0aW9uZXJyb3IiLCgpPT57eGo9ITF9KTtsZXQgdD1uLmdldENvbnRleHQoIndlYmdsMiIpO3hqPUJvb2xlYW4odCl9dmFyIHN1X2NvbnZlcnRSZWN0VG9FeHRlbnQ9ZnVuY3Rpb24obil7cmV0dXJue3g6W24ueCxuLngrbi53aWR0aF0seTpbbi55LG4ueStuLmhlaWdodF19fSxzdV9pc1dlYkdsMlN1cHBvcnRlZD1mdW5jdGlvbigpe3JldHVybiB4an0sc3VfaXNPZmZzY3JlZW5DYW52YXNTdXBwb3J0ZWQ9ZnVuY3Rpb24oKXtyZXR1cm4gc2VsZi5oYXNPd25Qcm9wZXJ0eSgiT2Zmc2NyZWVuQ2FudmFzIil9LHN1X2FyZVBvbHlsaW5lc0VxdWFsPWZ1bmN0aW9uKG4sdCl7aWYobi5sZW5ndGghPT10Lmxlbmd0aClyZXR1cm4hMTtmb3IobGV0IGU9MDtlPG4ubGVuZ3RoO2UrKylpZihuW2VdIT09dFtlXSlyZXR1cm4hMTtyZXR1cm4hMH0sQnk9Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnhTY2FsZT1vdShOci5MSU5FQVIpLHRoaXMueVNjYWxlPW91KE5yLkxJTkVBUiksdGhpcy5kb21Db250YWluZXJSZWN0PXt4OjAsd2lkdGg6MSx5OjAsaGVpZ2h0OjF9LHRoaXMubGFzdFVwZGF0ZWQ9MCx0aGlzLmN1cnJlbnRWaWV3Qm94UmVjdD17eDowLHdpZHRoOjEseTowLGhlaWdodDoxfX1nZXRVcGRhdGVJZGVudGlmaWVyKCl7cmV0dXJuIHRoaXMubGFzdFVwZGF0ZWR9dXBkYXRlSWRlbnRpZmllcigpe3RoaXMubGFzdFVwZGF0ZWQrK31pc1lBeGlzUG9pbnRlZERvd24oKXtyZXR1cm4hMH1zZXRYU2NhbGUodCl7dGhpcy54U2NhbGU9dCx0aGlzLnVwZGF0ZUlkZW50aWZpZXIoKX1zZXRZU2NhbGUodCl7dGhpcy55U2NhbGU9dCx0aGlzLnVwZGF0ZUlkZW50aWZpZXIoKX1nZXRDdXJyZW50Vmlld0JveFJlY3QoKXtyZXR1cm4gdGhpcy5jdXJyZW50Vmlld0JveFJlY3R9c2V0Vmlld0JveFJlY3QodCl7dGhpcy5jdXJyZW50Vmlld0JveFJlY3Q9dCx0aGlzLnVwZGF0ZUlkZW50aWZpZXIoKX1zZXREb21Db250YWluZXJSZWN0KHQpe3RoaXMuZG9tQ29udGFpbmVyUmVjdD10LHRoaXMudXBkYXRlSWRlbnRpZmllcigpfXRyYW5zZm9ybURhdGFUb1VpQ29vcmQodCxlKXtsZXQgaT10LHI9c3VfY29udmVydFJlY3RUb0V4dGVudCh0aGlzLmN1cnJlbnRWaWV3Qm94UmVjdCk7cmV0dXJuW3RoaXMueFNjYWxlLmZvcndhcmQoci54LFtpLngsaS54K2kud2lkdGhdLGVbMF0pLHRoaXMueVNjYWxlLmZvcndhcmQoci55LHRoaXMuaXNZQXhpc1BvaW50ZWREb3duKCk/W2kueStpLmhlaWdodCxpLnldOltpLnksaS55K2kuaGVpZ2h0XSxlWzFdKV19fTtmdW5jdGlvbiB4TyhuLHQsZSxpKXtsZXR7Y29sb3I6cix2aXNpYmxlOm8sb3BhY2l0eTpzfT1pLGE9bjtyZXR1cm4gYXx8bz8oYT1hPz90KCksYT1lKGEpLGEuc3R5bGUuZGlzcGxheT1vPyIiOiJub25lIixhLnN0eWxlLnN0cm9rZT1yLGEuc3R5bGUub3BhY2l0eT1TdHJpbmcocz8/MSksYSk6bnVsbH12YXIgRWw9MTAwMSxabz0xMDAzLEdzPTEwMDYsX2Y9MTAwOSxXUz0xMDEyLFVnPTEwMTUsbGI9MTAxNixjYj0xMDIwLGdhPTEwMjMsamc9MTAyNixoYj0xMDI3LG9iPTI0MDAsc2I9MjQwMSxiZj0zZTMsV3I9MzAwMSxxUz0zNTA0NCxvaz0zNTA0OCxFcD1jbGFzc3thZGRFdmVudExpc3RlbmVyKHQsZSl7dm9pZCAwPT09dGhpcy5fbGlzdGVuZXJzJiYodGhpcy5fbGlzdGVuZXJzPXt9KTtsZXQgaT10aGlzLl9saXN0ZW5lcnM7dm9pZCAwPT09aVt0XSYmKGlbdF09W10pLC0xPT09aVt0XS5pbmRleE9mKGUpJiZpW3RdLnB1c2goZSl9aGFzRXZlbnRMaXN0ZW5lcih0LGUpe2lmKHZvaWQgMD09PXRoaXMuX2xpc3RlbmVycylyZXR1cm4hMTtsZXQgaT10aGlzLl9saXN0ZW5lcnM7cmV0dXJuIHZvaWQgMCE9PWlbdF0mJi0xIT09aVt0XS5pbmRleE9mKGUpfXJlbW92ZUV2ZW50TGlzdGVuZXIodCxlKXtpZih2b2lkIDA9PT10aGlzLl9saXN0ZW5lcnMpcmV0dXJuO2xldCByPXRoaXMuX2xpc3RlbmVyc1t0XTtpZih2b2lkIDAhPT1yKXtsZXQgbz1yLmluZGV4T2YoZSk7LTEhPT1vJiZyLnNwbGljZShvLDEpfX1kaXNwYXRjaEV2ZW50KHQpe2lmKHZvaWQgMD09PXRoaXMuX2xpc3RlbmVycylyZXR1cm47bGV0IGk9dGhpcy5fbGlzdGVuZXJzW3QudHlwZV07aWYodm9pZCAwIT09aSl7dC50YXJnZXQ9dGhpcztsZXQgcj1pLnNsaWNlKDApO2ZvcihsZXQgbz0wLHM9ci5sZW5ndGg7bzxzO28rKylyW29dLmNhbGwodGhpcyx0KTt0LnRhcmdldD1udWxsfX19LE1zPVtdO2ZvcihsZXQgbj0wO248MjU2O24rKylNc1tuXT0objwxNj8iMCI6IiIpK24udG9TdHJpbmcoMTYpO3ZhciBEaj1NYXRoLlBJLzE4MCxoOD0xODAvTWF0aC5QSTtmdW5jdGlvbiBkdSgpe2xldCBuPTQyOTQ5NjcyOTUqTWF0aC5yYW5kb20oKXwwLHQ9NDI5NDk2NzI5NSpNYXRoLnJhbmRvbSgpfDAsZT00Mjk0OTY3Mjk1Kk1hdGgucmFuZG9tKCl8MCxpPTQyOTQ5NjcyOTUqTWF0aC5yYW5kb20oKXwwO3JldHVybihNc1syNTUmbl0rTXNbbj4+OCYyNTVdK01zW24+PjE2JjI1NV0rTXNbbj4+MjQmMjU1XSsiLSIrTXNbMjU1JnRdK01zW3Q+PjgmMjU1XSsiLSIrTXNbdD4+MTYmMTV8NjRdK01zW3Q+PjI0JjI1NV0rIi0iK01zWzYzJmV8MTI4XStNc1tlPj44JjI1NV0rIi0iK01zW2U+PjE2JjI1NV0rTXNbZT4+MjQmMjU1XStNc1syNTUmaV0rTXNbaT4+OCYyNTVdK01zW2k+PjE2JjI1NV0rTXNbaT4+MjQmMjU1XSkudG9VcHBlckNhc2UoKX1mdW5jdGlvbiBHYShuLHQsZSl7cmV0dXJuIE1hdGgubWF4KHQsTWF0aC5taW4oZSxuKSl9ZnVuY3Rpb24gQWoobix0LGUpe3JldHVybigxLWUpKm4rZSp0fWZ1bmN0aW9uIHh1ZShuKXtyZXR1cm4gMD09KG4mbi0xKSYmMCE9PW59ZnVuY3Rpb24gSDhlKG4pe3JldHVybiBNYXRoLnBvdygyLE1hdGguZmxvb3IoTWF0aC5sb2cobikvTWF0aC5MTjIpKX12YXIgYXQ9Y2xhc3N7Y29uc3RydWN0b3IodD0wLGU9MCl7dGhpcy54PXQsdGhpcy55PWV9Z2V0IHdpZHRoKCl7cmV0dXJuIHRoaXMueH1zZXQgd2lkdGgodCl7dGhpcy54PXR9Z2V0IGhlaWdodCgpe3JldHVybiB0aGlzLnl9c2V0IGhlaWdodCh0KXt0aGlzLnk9dH1zZXQodCxlKXtyZXR1cm4gdGhpcy54PXQsdGhpcy55PWUsdGhpc31zZXRTY2FsYXIodCl7cmV0dXJuIHRoaXMueD10LHRoaXMueT10LHRoaXN9c2V0WCh0KXtyZXR1cm4gdGhpcy54PXQsdGhpc31zZXRZKHQpe3JldHVybiB0aGlzLnk9dCx0aGlzfXNldENvbXBvbmVudCh0LGUpe3N3aXRjaCh0KXtjYXNlIDA6dGhpcy54PWU7YnJlYWs7Y2FzZSAxOnRoaXMueT1lO2JyZWFrO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKCJpbmRleCBpcyBvdXQgb2YgcmFuZ2U6ICIrdCl9cmV0dXJuIHRoaXN9Z2V0Q29tcG9uZW50KHQpe3N3aXRjaCh0KXtjYXNlIDA6cmV0dXJuIHRoaXMueDtjYXNlIDE6cmV0dXJuIHRoaXMueTtkZWZhdWx0OnRocm93IG5ldyBFcnJvcigiaW5kZXggaXMgb3V0IG9mIHJhbmdlOiAiK3QpfX1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3Rvcih0aGlzLngsdGhpcy55KX1jb3B5KHQpe3JldHVybiB0aGlzLng9dC54LHRoaXMueT10LnksdGhpc31hZGQodCxlKXtyZXR1cm4gdm9pZCAwIT09ZT8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IyOiAuYWRkKCkgbm93IG9ubHkgYWNjZXB0cyBvbmUgYXJndW1lbnQuIFVzZSAuYWRkVmVjdG9ycyggYSwgYiApIGluc3RlYWQuIiksdGhpcy5hZGRWZWN0b3JzKHQsZSkpOih0aGlzLngrPXQueCx0aGlzLnkrPXQueSx0aGlzKX1hZGRTY2FsYXIodCl7cmV0dXJuIHRoaXMueCs9dCx0aGlzLnkrPXQsdGhpc31hZGRWZWN0b3JzKHQsZSl7cmV0dXJuIHRoaXMueD10LngrZS54LHRoaXMueT10LnkrZS55LHRoaXN9YWRkU2NhbGVkVmVjdG9yKHQsZSl7cmV0dXJuIHRoaXMueCs9dC54KmUsdGhpcy55Kz10LnkqZSx0aGlzfXN1Yih0LGUpe3JldHVybiB2b2lkIDAhPT1lPyhjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjI6IC5zdWIoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5zdWJWZWN0b3JzKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLnN1YlZlY3RvcnModCxlKSk6KHRoaXMueC09dC54LHRoaXMueS09dC55LHRoaXMpfXN1YlNjYWxhcih0KXtyZXR1cm4gdGhpcy54LT10LHRoaXMueS09dCx0aGlzfXN1YlZlY3RvcnModCxlKXtyZXR1cm4gdGhpcy54PXQueC1lLngsdGhpcy55PXQueS1lLnksdGhpc31tdWx0aXBseSh0KXtyZXR1cm4gdGhpcy54Kj10LngsdGhpcy55Kj10LnksdGhpc31tdWx0aXBseVNjYWxhcih0KXtyZXR1cm4gdGhpcy54Kj10LHRoaXMueSo9dCx0aGlzfWRpdmlkZSh0KXtyZXR1cm4gdGhpcy54Lz10LngsdGhpcy55Lz10LnksdGhpc31kaXZpZGVTY2FsYXIodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlTY2FsYXIoMS90KX1hcHBseU1hdHJpeDModCl7bGV0IGU9dGhpcy54LGk9dGhpcy55LHI9dC5lbGVtZW50cztyZXR1cm4gdGhpcy54PXJbMF0qZStyWzNdKmkrcls2XSx0aGlzLnk9clsxXSplK3JbNF0qaStyWzddLHRoaXN9bWluKHQpe3JldHVybiB0aGlzLng9TWF0aC5taW4odGhpcy54LHQueCksdGhpcy55PU1hdGgubWluKHRoaXMueSx0LnkpLHRoaXN9bWF4KHQpe3JldHVybiB0aGlzLng9TWF0aC5tYXgodGhpcy54LHQueCksdGhpcy55PU1hdGgubWF4KHRoaXMueSx0LnkpLHRoaXN9Y2xhbXAodCxlKXtyZXR1cm4gdGhpcy54PU1hdGgubWF4KHQueCxNYXRoLm1pbihlLngsdGhpcy54KSksdGhpcy55PU1hdGgubWF4KHQueSxNYXRoLm1pbihlLnksdGhpcy55KSksdGhpc31jbGFtcFNjYWxhcih0LGUpe3JldHVybiB0aGlzLng9TWF0aC5tYXgodCxNYXRoLm1pbihlLHRoaXMueCkpLHRoaXMueT1NYXRoLm1heCh0LE1hdGgubWluKGUsdGhpcy55KSksdGhpc31jbGFtcExlbmd0aCh0LGUpe2xldCBpPXRoaXMubGVuZ3RoKCk7cmV0dXJuIHRoaXMuZGl2aWRlU2NhbGFyKGl8fDEpLm11bHRpcGx5U2NhbGFyKE1hdGgubWF4KHQsTWF0aC5taW4oZSxpKSkpfWZsb29yKCl7cmV0dXJuIHRoaXMueD1NYXRoLmZsb29yKHRoaXMueCksdGhpcy55PU1hdGguZmxvb3IodGhpcy55KSx0aGlzfWNlaWwoKXtyZXR1cm4gdGhpcy54PU1hdGguY2VpbCh0aGlzLngpLHRoaXMueT1NYXRoLmNlaWwodGhpcy55KSx0aGlzfXJvdW5kKCl7cmV0dXJuIHRoaXMueD1NYXRoLnJvdW5kKHRoaXMueCksdGhpcy55PU1hdGgucm91bmQodGhpcy55KSx0aGlzfXJvdW5kVG9aZXJvKCl7cmV0dXJuIHRoaXMueD10aGlzLng8MD9NYXRoLmNlaWwodGhpcy54KTpNYXRoLmZsb29yKHRoaXMueCksdGhpcy55PXRoaXMueTwwP01hdGguY2VpbCh0aGlzLnkpOk1hdGguZmxvb3IodGhpcy55KSx0aGlzfW5lZ2F0ZSgpe3JldHVybiB0aGlzLng9LXRoaXMueCx0aGlzLnk9LXRoaXMueSx0aGlzfWRvdCh0KXtyZXR1cm4gdGhpcy54KnQueCt0aGlzLnkqdC55fWNyb3NzKHQpe3JldHVybiB0aGlzLngqdC55LXRoaXMueSp0Lnh9bGVuZ3RoU3EoKXtyZXR1cm4gdGhpcy54KnRoaXMueCt0aGlzLnkqdGhpcy55fWxlbmd0aCgpe3JldHVybiBNYXRoLnNxcnQodGhpcy54KnRoaXMueCt0aGlzLnkqdGhpcy55KX1tYW5oYXR0YW5MZW5ndGgoKXtyZXR1cm4gTWF0aC5hYnModGhpcy54KStNYXRoLmFicyh0aGlzLnkpfW5vcm1hbGl6ZSgpe3JldHVybiB0aGlzLmRpdmlkZVNjYWxhcih0aGlzLmxlbmd0aCgpfHwxKX1hbmdsZSgpe3JldHVybiBNYXRoLmF0YW4yKC10aGlzLnksLXRoaXMueCkrTWF0aC5QSX1kaXN0YW5jZVRvKHQpe3JldHVybiBNYXRoLnNxcnQodGhpcy5kaXN0YW5jZVRvU3F1YXJlZCh0KSl9ZGlzdGFuY2VUb1NxdWFyZWQodCl7bGV0IGU9dGhpcy54LXQueCxpPXRoaXMueS10Lnk7cmV0dXJuIGUqZStpKml9bWFuaGF0dGFuRGlzdGFuY2VUbyh0KXtyZXR1cm4gTWF0aC5hYnModGhpcy54LXQueCkrTWF0aC5hYnModGhpcy55LXQueSl9c2V0TGVuZ3RoKHQpe3JldHVybiB0aGlzLm5vcm1hbGl6ZSgpLm11bHRpcGx5U2NhbGFyKHQpfWxlcnAodCxlKXtyZXR1cm4gdGhpcy54Kz0odC54LXRoaXMueCkqZSx0aGlzLnkrPSh0LnktdGhpcy55KSplLHRoaXN9bGVycFZlY3RvcnModCxlLGkpe3JldHVybiB0aGlzLng9dC54KyhlLngtdC54KSppLHRoaXMueT10LnkrKGUueS10LnkpKmksdGhpc31lcXVhbHModCl7cmV0dXJuIHQueD09PXRoaXMueCYmdC55PT09dGhpcy55fWZyb21BcnJheSh0LGU9MCl7cmV0dXJuIHRoaXMueD10W2VdLHRoaXMueT10W2UrMV0sdGhpc310b0FycmF5KHQ9W10sZT0wKXtyZXR1cm4gdFtlXT10aGlzLngsdFtlKzFdPXRoaXMueSx0fWZyb21CdWZmZXJBdHRyaWJ1dGUodCxlLGkpe3JldHVybiB2b2lkIDAhPT1pJiZjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjI6IG9mZnNldCBoYXMgYmVlbiByZW1vdmVkIGZyb20gLmZyb21CdWZmZXJBdHRyaWJ1dGUoKS4iKSx0aGlzLng9dC5nZXRYKGUpLHRoaXMueT10LmdldFkoZSksdGhpc31yb3RhdGVBcm91bmQodCxlKXtsZXQgaT1NYXRoLmNvcyhlKSxyPU1hdGguc2luKGUpLG89dGhpcy54LXQueCxzPXRoaXMueS10Lnk7cmV0dXJuIHRoaXMueD1vKmktcypyK3QueCx0aGlzLnk9bypyK3MqaSt0LnksdGhpc31yYW5kb20oKXtyZXR1cm4gdGhpcy54PU1hdGgucmFuZG9tKCksdGhpcy55PU1hdGgucmFuZG9tKCksdGhpc30qW1N5bWJvbC5pdGVyYXRvcl0oKXt5aWVsZCB0aGlzLngseWllbGQgdGhpcy55fX07YXQucHJvdG90eXBlLmlzVmVjdG9yMj0hMDt2YXIgSm89Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmVsZW1lbnRzPVsxLDAsMCwwLDEsMCwwLDAsMV0sYXJndW1lbnRzLmxlbmd0aD4wJiZjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXgzOiB0aGUgY29uc3RydWN0b3Igbm8gbG9uZ2VyIHJlYWRzIGFyZ3VtZW50cy4gdXNlIC5zZXQoKSBpbnN0ZWFkLiIpfXNldCh0LGUsaSxyLG8scyxhLGwsYyl7bGV0IHU9dGhpcy5lbGVtZW50cztyZXR1cm4gdVswXT10LHVbMV09cix1WzJdPWEsdVszXT1lLHVbNF09byx1WzVdPWwsdVs2XT1pLHVbN109cyx1WzhdPWMsdGhpc31pZGVudGl0eSgpe3JldHVybiB0aGlzLnNldCgxLDAsMCwwLDEsMCwwLDAsMSksdGhpc31jb3B5KHQpe2xldCBlPXRoaXMuZWxlbWVudHMsaT10LmVsZW1lbnRzO3JldHVybiBlWzBdPWlbMF0sZVsxXT1pWzFdLGVbMl09aVsyXSxlWzNdPWlbM10sZVs0XT1pWzRdLGVbNV09aVs1XSxlWzZdPWlbNl0sZVs3XT1pWzddLGVbOF09aVs4XSx0aGlzfWV4dHJhY3RCYXNpcyh0LGUsaSl7cmV0dXJuIHQuc2V0RnJvbU1hdHJpeDNDb2x1bW4odGhpcywwKSxlLnNldEZyb21NYXRyaXgzQ29sdW1uKHRoaXMsMSksaS5zZXRGcm9tTWF0cml4M0NvbHVtbih0aGlzLDIpLHRoaXN9c2V0RnJvbU1hdHJpeDQodCl7bGV0IGU9dC5lbGVtZW50cztyZXR1cm4gdGhpcy5zZXQoZVswXSxlWzRdLGVbOF0sZVsxXSxlWzVdLGVbOV0sZVsyXSxlWzZdLGVbMTBdKSx0aGlzfW11bHRpcGx5KHQpe3JldHVybiB0aGlzLm11bHRpcGx5TWF0cmljZXModGhpcyx0KX1wcmVtdWx0aXBseSh0KXtyZXR1cm4gdGhpcy5tdWx0aXBseU1hdHJpY2VzKHQsdGhpcyl9bXVsdGlwbHlNYXRyaWNlcyh0LGUpe2xldCBpPXQuZWxlbWVudHMscj1lLmVsZW1lbnRzLG89dGhpcy5lbGVtZW50cyxzPWlbMF0sYT1pWzNdLGw9aVs2XSxjPWlbMV0sdT1pWzRdLGQ9aVs3XSxwPWlbMl0saD1pWzVdLGY9aVs4XSxtPXJbMF0seD1yWzNdLGc9cls2XSxiPXJbMV0sRD1yWzRdLFQ9cls3XSxrPXJbMl0sWj1yWzVdLHo9cls4XTtyZXR1cm4gb1swXT1zKm0rYSpiK2wqayxvWzNdPXMqeCthKkQrbCpaLG9bNl09cypnK2EqVCtsKnosb1sxXT1jKm0rdSpiK2QqayxvWzRdPWMqeCt1KkQrZCpaLG9bN109YypnK3UqVCtkKnosb1syXT1wKm0raCpiK2YqayxvWzVdPXAqeCtoKkQrZipaLG9bOF09cCpnK2gqVCtmKnosdGhpc31tdWx0aXBseVNjYWxhcih0KXtsZXQgZT10aGlzLmVsZW1lbnRzO3JldHVybiBlWzBdKj10LGVbM10qPXQsZVs2XSo9dCxlWzFdKj10LGVbNF0qPXQsZVs3XSo9dCxlWzJdKj10LGVbNV0qPXQsZVs4XSo9dCx0aGlzfWRldGVybWluYW50KCl7bGV0IHQ9dGhpcy5lbGVtZW50cyxlPXRbMF0saT10WzFdLHI9dFsyXSxvPXRbM10scz10WzRdLGE9dFs1XSxsPXRbNl0sYz10WzddLHU9dFs4XTtyZXR1cm4gZSpzKnUtZSphKmMtaSpvKnUraSphKmwrcipvKmMtcipzKmx9aW52ZXJ0KCl7bGV0IHQ9dGhpcy5lbGVtZW50cyxlPXRbMF0saT10WzFdLHI9dFsyXSxvPXRbM10scz10WzRdLGE9dFs1XSxsPXRbNl0sYz10WzddLHU9dFs4XSxkPXUqcy1hKmMscD1hKmwtdSpvLGg9YypvLXMqbCxmPWUqZCtpKnArcipoO2lmKDA9PT1mKXJldHVybiB0aGlzLnNldCgwLDAsMCwwLDAsMCwwLDAsMCk7bGV0IG09MS9mO3JldHVybiB0WzBdPWQqbSx0WzFdPShyKmMtdSppKSptLHRbMl09KGEqaS1yKnMpKm0sdFszXT1wKm0sdFs0XT0odSplLXIqbCkqbSx0WzVdPShyKm8tYSplKSptLHRbNl09aCptLHRbN109KGkqbC1jKmUpKm0sdFs4XT0ocyplLWkqbykqbSx0aGlzfXRyYW5zcG9zZSgpe2xldCB0LGU9dGhpcy5lbGVtZW50cztyZXR1cm4gdD1lWzFdLGVbMV09ZVszXSxlWzNdPXQsdD1lWzJdLGVbMl09ZVs2XSxlWzZdPXQsdD1lWzVdLGVbNV09ZVs3XSxlWzddPXQsdGhpc31nZXROb3JtYWxNYXRyaXgodCl7cmV0dXJuIHRoaXMuc2V0RnJvbU1hdHJpeDQodCkuaW52ZXJ0KCkudHJhbnNwb3NlKCl9dHJhbnNwb3NlSW50b0FycmF5KHQpe2xldCBlPXRoaXMuZWxlbWVudHM7cmV0dXJuIHRbMF09ZVswXSx0WzFdPWVbM10sdFsyXT1lWzZdLHRbM109ZVsxXSx0WzRdPWVbNF0sdFs1XT1lWzddLHRbNl09ZVsyXSx0WzddPWVbNV0sdFs4XT1lWzhdLHRoaXN9c2V0VXZUcmFuc2Zvcm0odCxlLGkscixvLHMsYSl7bGV0IGw9TWF0aC5jb3MobyksYz1NYXRoLnNpbihvKTtyZXR1cm4gdGhpcy5zZXQoaSpsLGkqYywtaSoobCpzK2MqYSkrcyt0LC1yKmMscipsLC1yKigtYypzK2wqYSkrYStlLDAsMCwxKSx0aGlzfXNjYWxlKHQsZSl7bGV0IGk9dGhpcy5lbGVtZW50cztyZXR1cm4gaVswXSo9dCxpWzNdKj10LGlbNl0qPXQsaVsxXSo9ZSxpWzRdKj1lLGlbN10qPWUsdGhpc31yb3RhdGUodCl7bGV0IGU9TWF0aC5jb3ModCksaT1NYXRoLnNpbih0KSxyPXRoaXMuZWxlbWVudHMsbz1yWzBdLHM9clszXSxhPXJbNl0sbD1yWzFdLGM9cls0XSx1PXJbN107cmV0dXJuIHJbMF09ZSpvK2kqbCxyWzNdPWUqcytpKmMscls2XT1lKmEraSp1LHJbMV09LWkqbytlKmwscls0XT0taSpzK2UqYyxyWzddPS1pKmErZSp1LHRoaXN9dHJhbnNsYXRlKHQsZSl7bGV0IGk9dGhpcy5lbGVtZW50cztyZXR1cm4gaVswXSs9dCppWzJdLGlbM10rPXQqaVs1XSxpWzZdKz10KmlbOF0saVsxXSs9ZSppWzJdLGlbNF0rPWUqaVs1XSxpWzddKz1lKmlbOF0sdGhpc31lcXVhbHModCl7bGV0IGU9dGhpcy5lbGVtZW50cyxpPXQuZWxlbWVudHM7Zm9yKGxldCByPTA7cjw5O3IrKylpZihlW3JdIT09aVtyXSlyZXR1cm4hMTtyZXR1cm4hMH1mcm9tQXJyYXkodCxlPTApe2ZvcihsZXQgaT0wO2k8OTtpKyspdGhpcy5lbGVtZW50c1tpXT10W2krZV07cmV0dXJuIHRoaXN9dG9BcnJheSh0PVtdLGU9MCl7bGV0IGk9dGhpcy5lbGVtZW50cztyZXR1cm4gdFtlXT1pWzBdLHRbZSsxXT1pWzFdLHRbZSsyXT1pWzJdLHRbZSszXT1pWzNdLHRbZSs0XT1pWzRdLHRbZSs1XT1pWzVdLHRbZSs2XT1pWzZdLHRbZSs3XT1pWzddLHRbZSs4XT1pWzhdLHR9Y2xvbmUoKXtyZXR1cm4obmV3IHRoaXMuY29uc3RydWN0b3IpLmZyb21BcnJheSh0aGlzLmVsZW1lbnRzKX19O2Z1bmN0aW9uIE9kZShuKXtmb3IobGV0IHQ9bi5sZW5ndGgtMTt0Pj0wOy0tdClpZihuW3RdPjY1NTM1KXJldHVybiEwO3JldHVybiExfWZ1bmN0aW9uIFlTKG4pe3JldHVybiBkb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiLG4pfUpvLnByb3RvdHlwZS5pc01hdHJpeDM9ITA7dmFyIEN1ZT17YWxpY2VibHVlOjE1NzkyMzgzLGFudGlxdWV3aGl0ZToxNjQ0NDM3NSxhcXVhOjY1NTM1LGFxdWFtYXJpbmU6ODM4ODU2NCxhenVyZToxNTc5NDE3NSxiZWlnZToxNjExOTI2MCxiaXNxdWU6MTY3NzAyNDQsYmxhY2s6MCxibGFuY2hlZGFsbW9uZDoxNjc3MjA0NSxibHVlOjI1NSxibHVldmlvbGV0OjkwNTUyMDIsYnJvd246MTA4MjQyMzQsYnVybHl3b29kOjE0NTk2MjMxLGNhZGV0Ymx1ZTo2MjY2NTI4LGNoYXJ0cmV1c2U6ODM4ODM1MixjaG9jb2xhdGU6MTM3ODk0NzAsY29yYWw6MTY3NDQyNzIsY29ybmZsb3dlcmJsdWU6NjU5MTk4MSxjb3Juc2lsazoxNjc3NTM4OCxjcmltc29uOjE0NDIzMTAwLGN5YW46NjU1MzUsZGFya2JsdWU6MTM5LGRhcmtjeWFuOjM1NzIzLGRhcmtnb2xkZW5yb2Q6MTIwOTI5MzksZGFya2dyYXk6MTExMTkwMTcsZGFya2dyZWVuOjI1NjAwLGRhcmtncmV5OjExMTE5MDE3LGRhcmtraGFraToxMjQzMzI1OSxkYXJrbWFnZW50YTo5MTA5NjQzLGRhcmtvbGl2ZWdyZWVuOjU1OTc5OTksZGFya29yYW5nZToxNjc0NzUyMCxkYXJrb3JjaGlkOjEwMDQwMDEyLGRhcmtyZWQ6OTEwOTUwNCxkYXJrc2FsbW9uOjE1MzA4NDEwLGRhcmtzZWFncmVlbjo5NDE5OTE5LGRhcmtzbGF0ZWJsdWU6NDczNDM0NyxkYXJrc2xhdGVncmF5OjMxMDA0OTUsZGFya3NsYXRlZ3JleTozMTAwNDk1LGRhcmt0dXJxdW9pc2U6NTI5NDUsZGFya3Zpb2xldDo5Njk5NTM5LGRlZXBwaW5rOjE2NzE2OTQ3LGRlZXBza3libHVlOjQ5MTUxLGRpbWdyYXk6NjkwODI2NSxkaW1ncmV5OjY5MDgyNjUsZG9kZ2VyYmx1ZToyMDAzMTk5LGZpcmVicmljazoxMTY3NDE0NixmbG9yYWx3aGl0ZToxNjc3NTkyMCxmb3Jlc3RncmVlbjoyMjYzODQyLGZ1Y2hzaWE6MTY3MTE5MzUsZ2FpbnNib3JvOjE0NDc0NDYwLGdob3N0d2hpdGU6MTYzMTY2NzEsZ29sZDoxNjc2NjcyMCxnb2xkZW5yb2Q6MTQzMjkxMjAsZ3JheTo4NDIxNTA0LGdyZWVuOjMyNzY4LGdyZWVueWVsbG93OjExNDAzMDU1LGdyZXk6ODQyMTUwNCxob25leWRldzoxNTc5NDE2MCxob3RwaW5rOjE2NzM4NzQwLGluZGlhbnJlZDoxMzQ1ODUyNCxpbmRpZ286NDkxNTMzMCxpdm9yeToxNjc3NzIwMCxraGFraToxNTc4NzY2MCxsYXZlbmRlcjoxNTEzMjQxMCxsYXZlbmRlcmJsdXNoOjE2NzczMzY1LGxhd25ncmVlbjo4MTkwOTc2LGxlbW9uY2hpZmZvbjoxNjc3NTg4NSxsaWdodGJsdWU6MTEzOTMyNTQsbGlnaHRjb3JhbDoxNTc2MTUzNixsaWdodGN5YW46MTQ3NDU1OTksbGlnaHRnb2xkZW5yb2R5ZWxsb3c6MTY0NDgyMTAsbGlnaHRncmF5OjEzODgyMzIzLGxpZ2h0Z3JlZW46OTQ5ODI1NixsaWdodGdyZXk6MTM4ODIzMjMsbGlnaHRwaW5rOjE2NzU4NDY1LGxpZ2h0c2FsbW9uOjE2NzUyNzYyLGxpZ2h0c2VhZ3JlZW46MjE0Mjg5MCxsaWdodHNreWJsdWU6ODkwMDM0NixsaWdodHNsYXRlZ3JheTo3ODMzNzUzLGxpZ2h0c2xhdGVncmV5Ojc4MzM3NTMsbGlnaHRzdGVlbGJsdWU6MTE1ODQ3MzQsbGlnaHR5ZWxsb3c6MTY3NzcxODQsbGltZTo2NTI4MCxsaW1lZ3JlZW46MzMyOTMzMCxsaW5lbjoxNjQ0NTY3MCxtYWdlbnRhOjE2NzExOTM1LG1hcm9vbjo4Mzg4NjA4LG1lZGl1bWFxdWFtYXJpbmU6NjczNzMyMixtZWRpdW1ibHVlOjIwNSxtZWRpdW1vcmNoaWQ6MTIyMTE2NjcsbWVkaXVtcHVycGxlOjk2NjI2ODMsbWVkaXVtc2VhZ3JlZW46Mzk3ODA5NyxtZWRpdW1zbGF0ZWJsdWU6ODA4Nzc5MCxtZWRpdW1zcHJpbmdncmVlbjo2NDE1NCxtZWRpdW10dXJxdW9pc2U6NDc3MjMwMCxtZWRpdW12aW9sZXRyZWQ6MTMwNDcxNzMsbWlkbmlnaHRibHVlOjE2NDQ5MTIsbWludGNyZWFtOjE2MTIxODUwLG1pc3R5cm9zZToxNjc3MDI3Myxtb2NjYXNpbjoxNjc3MDIyOSxuYXZham93aGl0ZToxNjc2ODY4NSxuYXZ5OjEyOCxvbGRsYWNlOjE2NjQzNTU4LG9saXZlOjg0MjEzNzYsb2xpdmVkcmFiOjcwNDg3Mzksb3JhbmdlOjE2NzUzOTIwLG9yYW5nZXJlZDoxNjcyOTM0NCxvcmNoaWQ6MTQzMTU3MzQscGFsZWdvbGRlbnJvZDoxNTY1NzEzMCxwYWxlZ3JlZW46MTAwMjU4ODAscGFsZXR1cnF1b2lzZToxMTUyOTk2NixwYWxldmlvbGV0cmVkOjE0MzgxMjAzLHBhcGF5YXdoaXA6MTY3NzMwNzcscGVhY2hwdWZmOjE2NzY3NjczLHBlcnU6MTM0Njg5OTEscGluazoxNjc2MTAzNSxwbHVtOjE0NTI0NjM3LHBvd2RlcmJsdWU6MTE1OTE5MTAscHVycGxlOjgzODg3MzYscmViZWNjYXB1cnBsZTo2Njk3ODgxLHJlZDoxNjcxMTY4MCxyb3N5YnJvd246MTIzNTc1MTkscm95YWxibHVlOjQyODY5NDUsc2FkZGxlYnJvd246OTEyNzE4NyxzYWxtb246MTY0MTY4ODIsc2FuZHlicm93bjoxNjAzMjg2NCxzZWFncmVlbjozMDUwMzI3LHNlYXNoZWxsOjE2Nzc0NjM4LHNpZW5uYToxMDUwNjc5NyxzaWx2ZXI6MTI2MzIyNTYsc2t5Ymx1ZTo4OTAwMzMxLHNsYXRlYmx1ZTo2OTcwMDYxLHNsYXRlZ3JheTo3MzcyOTQ0LHNsYXRlZ3JleTo3MzcyOTQ0LHNub3c6MTY3NzU5MzAsc3ByaW5nZ3JlZW46NjU0MDcsc3RlZWxibHVlOjQ2MjA5ODAsdGFuOjEzODA4NzgwLHRlYWw6MzI4OTYsdGhpc3RsZToxNDIwNDg4OCx0b21hdG86MTY3MzcwOTUsdHVycXVvaXNlOjQyNTE4NTYsdmlvbGV0OjE1NjMxMDg2LHdoZWF0OjE2MTEzMzMxLHdoaXRlOjE2Nzc3MjE1LHdoaXRlc21va2U6MTYxMTkyODUseWVsbG93OjE2Nzc2OTYwLHllbGxvd2dyZWVuOjEwMTQ1MDc0fSxhdT17aDowLHM6MCxsOjB9LENPPXtoOjAsczowLGw6MH07ZnVuY3Rpb24gSWoobix0LGUpe3JldHVybiBlPDAmJihlKz0xKSxlPjEmJihlLT0xKSxlPDEvNj9uKzYqKHQtbikqZTplPC41P3Q6ZTwyLzM/bis2Kih0LW4pKigyLzMtZSk6bn1mdW5jdGlvbiB1YihuKXtyZXR1cm4gbjwuMDQwNDU/LjA3NzM5OTM4MDgqbjpNYXRoLnBvdyguOTQ3ODY3Mjk4NipuKy4wNTIxMzI3MDE0LDIuNCl9ZnVuY3Rpb24gUGoobil7cmV0dXJuIG48LjAwMzEzMDg/MTIuOTIqbjoxLjA1NSpNYXRoLnBvdyhuLC40MTY2NiktLjA1NX12YXIgdm49KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7cmV0dXJuIHZvaWQgMD09PWkmJnZvaWQgMD09PXI/dGhpcy5zZXQoZSk6dGhpcy5zZXRSR0IoZSxpLHIpfXNldChlKXtyZXR1cm4gZSYmZS5pc0NvbG9yP3RoaXMuY29weShlKToibnVtYmVyIj09dHlwZW9mIGU/dGhpcy5zZXRIZXgoZSk6InN0cmluZyI9PXR5cGVvZiBlJiZ0aGlzLnNldFN0eWxlKGUpLHRoaXN9c2V0U2NhbGFyKGUpe3JldHVybiB0aGlzLnI9ZSx0aGlzLmc9ZSx0aGlzLmI9ZSx0aGlzfXNldEhleChlKXtyZXR1cm4gZT1NYXRoLmZsb29yKGUpLHRoaXMucj0oZT4+MTYmMjU1KS8yNTUsdGhpcy5nPShlPj44JjI1NSkvMjU1LHRoaXMuYj0oMjU1JmUpLzI1NSx0aGlzfXNldFJHQihlLGkscil7cmV0dXJuIHRoaXMucj1lLHRoaXMuZz1pLHRoaXMuYj1yLHRoaXN9c2V0SFNMKGUsaSxyKXtpZihlPWZ1bmN0aW9uKG4sdCl7cmV0dXJuKG4lMSsxKSUxfShlKSxpPUdhKGksMCwxKSxyPUdhKHIsMCwxKSwwPT09aSl0aGlzLnI9dGhpcy5nPXRoaXMuYj1yO2Vsc2V7bGV0IG89cjw9LjU/ciooMStpKTpyK2ktcippLHM9MipyLW87dGhpcy5yPUlqKHMsbyxlKzEvMyksdGhpcy5nPUlqKHMsbyxlKSx0aGlzLmI9SWoocyxvLGUtMS8zKX1yZXR1cm4gdGhpc31zZXRTdHlsZShlKXtmdW5jdGlvbiBpKG8pe3ZvaWQgMCE9PW8mJnBhcnNlRmxvYXQobyk8MSYmY29uc29sZS53YXJuKCJUSFJFRS5Db2xvcjogQWxwaGEgY29tcG9uZW50IG9mICIrZSsiIHdpbGwgYmUgaWdub3JlZC4iKX1sZXQgcjtpZihyPS9eKCg/OnJnYnxoc2wpYT8pXCgoW15cKV0qKVwpLy5leGVjKGUpKXtsZXQgbyxhPXJbMl07c3dpdGNoKHJbMV0pe2Nhc2UicmdiIjpjYXNlInJnYmEiOmlmKG89L15ccyooXGQrKVxzKixccyooXGQrKVxzKixccyooXGQrKVxzKig/OixccyooXGQqXC4/XGQrKVxzKik/JC8uZXhlYyhhKSlyZXR1cm4gdGhpcy5yPU1hdGgubWluKDI1NSxwYXJzZUludChvWzFdLDEwKSkvMjU1LHRoaXMuZz1NYXRoLm1pbigyNTUscGFyc2VJbnQob1syXSwxMCkpLzI1NSx0aGlzLmI9TWF0aC5taW4oMjU1LHBhcnNlSW50KG9bM10sMTApKS8yNTUsaShvWzRdKSx0aGlzO2lmKG89L15ccyooXGQrKVwlXHMqLFxzKihcZCspXCVccyosXHMqKFxkKylcJVxzKig/OixccyooXGQqXC4/XGQrKVxzKik/JC8uZXhlYyhhKSlyZXR1cm4gdGhpcy5yPU1hdGgubWluKDEwMCxwYXJzZUludChvWzFdLDEwKSkvMTAwLHRoaXMuZz1NYXRoLm1pbigxMDAscGFyc2VJbnQob1syXSwxMCkpLzEwMCx0aGlzLmI9TWF0aC5taW4oMTAwLHBhcnNlSW50KG9bM10sMTApKS8xMDAsaShvWzRdKSx0aGlzO2JyZWFrO2Nhc2UiaHNsIjpjYXNlImhzbGEiOmlmKG89L15ccyooXGQqXC4/XGQrKVxzKixccyooXGQrKVwlXHMqLFxzKihcZCspXCVccyooPzosXHMqKFxkKlwuP1xkKylccyopPyQvLmV4ZWMoYSkpe2xldCBsPXBhcnNlRmxvYXQob1sxXSkvMzYwLGM9cGFyc2VJbnQob1syXSwxMCkvMTAwLHU9cGFyc2VJbnQob1szXSwxMCkvMTAwO3JldHVybiBpKG9bNF0pLHRoaXMuc2V0SFNMKGwsYyx1KX19fWVsc2UgaWYocj0vXlwjKFtBLUZhLWZcZF0rKSQvLmV4ZWMoZSkpe2xldCBvPXJbMV0scz1vLmxlbmd0aDtpZigzPT09cylyZXR1cm4gdGhpcy5yPXBhcnNlSW50KG8uY2hhckF0KDApK28uY2hhckF0KDApLDE2KS8yNTUsdGhpcy5nPXBhcnNlSW50KG8uY2hhckF0KDEpK28uY2hhckF0KDEpLDE2KS8yNTUsdGhpcy5iPXBhcnNlSW50KG8uY2hhckF0KDIpK28uY2hhckF0KDIpLDE2KS8yNTUsdGhpcztpZig2PT09cylyZXR1cm4gdGhpcy5yPXBhcnNlSW50KG8uY2hhckF0KDApK28uY2hhckF0KDEpLDE2KS8yNTUsdGhpcy5nPXBhcnNlSW50KG8uY2hhckF0KDIpK28uY2hhckF0KDMpLDE2KS8yNTUsdGhpcy5iPXBhcnNlSW50KG8uY2hhckF0KDQpK28uY2hhckF0KDUpLDE2KS8yNTUsdGhpc31yZXR1cm4gZSYmZS5sZW5ndGg+MD90aGlzLnNldENvbG9yTmFtZShlKTp0aGlzfXNldENvbG9yTmFtZShlKXtsZXQgaT1DdWVbZS50b0xvd2VyQ2FzZSgpXTtyZXR1cm4gdm9pZCAwIT09aT90aGlzLnNldEhleChpKTpjb25zb2xlLndhcm4oIlRIUkVFLkNvbG9yOiBVbmtub3duIGNvbG9yICIrZSksdGhpc31jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3Rvcih0aGlzLnIsdGhpcy5nLHRoaXMuYil9Y29weShlKXtyZXR1cm4gdGhpcy5yPWUucix0aGlzLmc9ZS5nLHRoaXMuYj1lLmIsdGhpc31jb3B5U1JHQlRvTGluZWFyKGUpe3JldHVybiB0aGlzLnI9dWIoZS5yKSx0aGlzLmc9dWIoZS5nKSx0aGlzLmI9dWIoZS5iKSx0aGlzfWNvcHlMaW5lYXJUb1NSR0IoZSl7cmV0dXJuIHRoaXMucj1QaihlLnIpLHRoaXMuZz1QaihlLmcpLHRoaXMuYj1QaihlLmIpLHRoaXN9Y29udmVydFNSR0JUb0xpbmVhcigpe3JldHVybiB0aGlzLmNvcHlTUkdCVG9MaW5lYXIodGhpcyksdGhpc31jb252ZXJ0TGluZWFyVG9TUkdCKCl7cmV0dXJuIHRoaXMuY29weUxpbmVhclRvU1JHQih0aGlzKSx0aGlzfWdldEhleCgpe3JldHVybiAyNTUqdGhpcy5yPDwxNl4yNTUqdGhpcy5nPDw4XjI1NSp0aGlzLmI8PDB9Z2V0SGV4U3RyaW5nKCl7cmV0dXJuKCIwMDAwMDAiK3RoaXMuZ2V0SGV4KCkudG9TdHJpbmcoMTYpKS5zbGljZSgtNil9Z2V0SFNMKGUpe2xldCBsLGMsaT10aGlzLnIscj10aGlzLmcsbz10aGlzLmIscz1NYXRoLm1heChpLHIsbyksYT1NYXRoLm1pbihpLHIsbyksdT0oYStzKS8yO2lmKGE9PT1zKWw9MCxjPTA7ZWxzZXtsZXQgZD1zLWE7c3dpdGNoKGM9dTw9LjU/ZC8ocythKTpkLygyLXMtYSkscyl7Y2FzZSBpOmw9KHItbykvZCsocjxvPzY6MCk7YnJlYWs7Y2FzZSByOmw9KG8taSkvZCsyO2JyZWFrO2Nhc2UgbzpsPShpLXIpL2QrNH1sLz02fXJldHVybiBlLmg9bCxlLnM9YyxlLmw9dSxlfWdldFN0eWxlKCl7cmV0dXJuInJnYigiKygyNTUqdGhpcy5yfDApKyIsIisoMjU1KnRoaXMuZ3wwKSsiLCIrKDI1NSp0aGlzLmJ8MCkrIikifW9mZnNldEhTTChlLGkscil7cmV0dXJuIHRoaXMuZ2V0SFNMKGF1KSxhdS5oKz1lLGF1LnMrPWksYXUubCs9cix0aGlzLnNldEhTTChhdS5oLGF1LnMsYXUubCksdGhpc31hZGQoZSl7cmV0dXJuIHRoaXMucis9ZS5yLHRoaXMuZys9ZS5nLHRoaXMuYis9ZS5iLHRoaXN9YWRkQ29sb3JzKGUsaSl7cmV0dXJuIHRoaXMucj1lLnIraS5yLHRoaXMuZz1lLmcraS5nLHRoaXMuYj1lLmIraS5iLHRoaXN9YWRkU2NhbGFyKGUpe3JldHVybiB0aGlzLnIrPWUsdGhpcy5nKz1lLHRoaXMuYis9ZSx0aGlzfXN1YihlKXtyZXR1cm4gdGhpcy5yPU1hdGgubWF4KDAsdGhpcy5yLWUuciksdGhpcy5nPU1hdGgubWF4KDAsdGhpcy5nLWUuZyksdGhpcy5iPU1hdGgubWF4KDAsdGhpcy5iLWUuYiksdGhpc31tdWx0aXBseShlKXtyZXR1cm4gdGhpcy5yKj1lLnIsdGhpcy5nKj1lLmcsdGhpcy5iKj1lLmIsdGhpc31tdWx0aXBseVNjYWxhcihlKXtyZXR1cm4gdGhpcy5yKj1lLHRoaXMuZyo9ZSx0aGlzLmIqPWUsdGhpc31sZXJwKGUsaSl7cmV0dXJuIHRoaXMucis9KGUuci10aGlzLnIpKmksdGhpcy5nKz0oZS5nLXRoaXMuZykqaSx0aGlzLmIrPShlLmItdGhpcy5iKSppLHRoaXN9bGVycENvbG9ycyhlLGkscil7cmV0dXJuIHRoaXMucj1lLnIrKGkuci1lLnIpKnIsdGhpcy5nPWUuZysoaS5nLWUuZykqcix0aGlzLmI9ZS5iKyhpLmItZS5iKSpyLHRoaXN9bGVycEhTTChlLGkpe3RoaXMuZ2V0SFNMKGF1KSxlLmdldEhTTChDTyk7bGV0IHI9QWooYXUuaCxDTy5oLGkpLG89QWooYXUucyxDTy5zLGkpLHM9QWooYXUubCxDTy5sLGkpO3JldHVybiB0aGlzLnNldEhTTChyLG8scyksdGhpc31lcXVhbHMoZSl7cmV0dXJuIGUucj09PXRoaXMuciYmZS5nPT09dGhpcy5nJiZlLmI9PT10aGlzLmJ9ZnJvbUFycmF5KGUsaT0wKXtyZXR1cm4gdGhpcy5yPWVbaV0sdGhpcy5nPWVbaSsxXSx0aGlzLmI9ZVtpKzJdLHRoaXN9dG9BcnJheShlPVtdLGk9MCl7cmV0dXJuIGVbaV09dGhpcy5yLGVbaSsxXT10aGlzLmcsZVtpKzJdPXRoaXMuYixlfWZyb21CdWZmZXJBdHRyaWJ1dGUoZSxpKXtyZXR1cm4gdGhpcy5yPWUuZ2V0WChpKSx0aGlzLmc9ZS5nZXRZKGkpLHRoaXMuYj1lLmdldFooaSksITA9PT1lLm5vcm1hbGl6ZWQmJih0aGlzLnIvPTI1NSx0aGlzLmcvPTI1NSx0aGlzLmIvPTI1NSksdGhpc310b0pTT04oKXtyZXR1cm4gdGhpcy5nZXRIZXgoKX19cmV0dXJuIG4uTkFNRVM9Q3VlLG59KSgpO3ZuLnByb3RvdHlwZS5pc0NvbG9yPSEwLHZuLnByb3RvdHlwZS5yPTEsdm4ucHJvdG90eXBlLmc9MSx2bi5wcm90b3R5cGUuYj0xO3ZhciBWeSxUcD1jbGFzc3tzdGF0aWMgZ2V0RGF0YVVSTCh0KXtpZigvXmRhdGE6L2kudGVzdCh0LnNyYyl8fHR5cGVvZiBIVE1MQ2FudmFzRWxlbWVudD4idSIpcmV0dXJuIHQuc3JjO2xldCBlO2lmKHQgaW5zdGFuY2VvZiBIVE1MQ2FudmFzRWxlbWVudCllPXQ7ZWxzZXt2b2lkIDA9PT1WeSYmKFZ5PVlTKCJjYW52YXMiKSksVnkud2lkdGg9dC53aWR0aCxWeS5oZWlnaHQ9dC5oZWlnaHQ7bGV0IGk9VnkuZ2V0Q29udGV4dCgiMmQiKTt0IGluc3RhbmNlb2YgSW1hZ2VEYXRhP2kucHV0SW1hZ2VEYXRhKHQsMCwwKTppLmRyYXdJbWFnZSh0LDAsMCx0LndpZHRoLHQuaGVpZ2h0KSxlPVZ5fXJldHVybiBlLndpZHRoPjIwNDh8fGUuaGVpZ2h0PjIwNDg/KGNvbnNvbGUud2FybigiVEhSRUUuSW1hZ2VVdGlscy5nZXREYXRhVVJMOiBJbWFnZSBjb252ZXJ0ZWQgdG8ganBnIGZvciBwZXJmb3JtYW5jZSByZWFzb25zIix0KSxlLnRvRGF0YVVSTCgiaW1hZ2UvanBlZyIsLjYpKTplLnRvRGF0YVVSTCgiaW1hZ2UvcG5nIil9c3RhdGljIHNSR0JUb0xpbmVhcih0KXtpZih0eXBlb2YgSFRNTEltYWdlRWxlbWVudDwidSImJnQgaW5zdGFuY2VvZiBIVE1MSW1hZ2VFbGVtZW50fHx0eXBlb2YgSFRNTENhbnZhc0VsZW1lbnQ8InUiJiZ0IGluc3RhbmNlb2YgSFRNTENhbnZhc0VsZW1lbnR8fHR5cGVvZiBJbWFnZUJpdG1hcDwidSImJnQgaW5zdGFuY2VvZiBJbWFnZUJpdG1hcCl7bGV0IGU9WVMoImNhbnZhcyIpO2Uud2lkdGg9dC53aWR0aCxlLmhlaWdodD10LmhlaWdodDtsZXQgaT1lLmdldENvbnRleHQoIjJkIik7aS5kcmF3SW1hZ2UodCwwLDAsdC53aWR0aCx0LmhlaWdodCk7bGV0IHI9aS5nZXRJbWFnZURhdGEoMCwwLHQud2lkdGgsdC5oZWlnaHQpLG89ci5kYXRhO2ZvcihsZXQgcz0wO3M8by5sZW5ndGg7cysrKW9bc109MjU1KnViKG9bc10vMjU1KTtyZXR1cm4gaS5wdXRJbWFnZURhdGEociwwLDApLGV9aWYodC5kYXRhKXtsZXQgZT10LmRhdGEuc2xpY2UoMCk7Zm9yKGxldCBpPTA7aTxlLmxlbmd0aDtpKyspZVtpXT1lIGluc3RhbmNlb2YgVWludDhBcnJheXx8ZSBpbnN0YW5jZW9mIFVpbnQ4Q2xhbXBlZEFycmF5P01hdGguZmxvb3IoMjU1KnViKGVbaV0vMjU1KSk6dWIoZVtpXSk7cmV0dXJue2RhdGE6ZSx3aWR0aDp0LndpZHRoLGhlaWdodDp0LmhlaWdodH19cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuSW1hZ2VVdGlscy5zUkdCVG9MaW5lYXIoKTogVW5zdXBwb3J0ZWQgaW1hZ2UgdHlwZS4gTm8gY29sb3Igc3BhY2UgY29udmVyc2lvbiBhcHBsaWVkLiIpLHR9fSxVOGU9MCxIbz1jbGFzcyBleHRlbmRzIEVwe2NvbnN0cnVjdG9yKHQ9SG8uREVGQVVMVF9JTUFHRSxlPUhvLkRFRkFVTFRfTUFQUElORyxpPUVsLHI9RWwsbz1HcyxzPTEwMDgsYT1nYSxsPV9mLGM9MSx1PWJmKXtzdXBlcigpLE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCJpZCIse3ZhbHVlOlU4ZSsrfSksdGhpcy51dWlkPWR1KCksdGhpcy5uYW1lPSIiLHRoaXMuaW1hZ2U9dCx0aGlzLm1pcG1hcHM9W10sdGhpcy5tYXBwaW5nPWUsdGhpcy53cmFwUz1pLHRoaXMud3JhcFQ9cix0aGlzLm1hZ0ZpbHRlcj1vLHRoaXMubWluRmlsdGVyPXMsdGhpcy5hbmlzb3Ryb3B5PWMsdGhpcy5mb3JtYXQ9YSx0aGlzLmludGVybmFsRm9ybWF0PW51bGwsdGhpcy50eXBlPWwsdGhpcy5vZmZzZXQ9bmV3IGF0KDAsMCksdGhpcy5yZXBlYXQ9bmV3IGF0KDEsMSksdGhpcy5jZW50ZXI9bmV3IGF0KDAsMCksdGhpcy5yb3RhdGlvbj0wLHRoaXMubWF0cml4QXV0b1VwZGF0ZT0hMCx0aGlzLm1hdHJpeD1uZXcgSm8sdGhpcy5nZW5lcmF0ZU1pcG1hcHM9ITAsdGhpcy5wcmVtdWx0aXBseUFscGhhPSExLHRoaXMuZmxpcFk9ITAsdGhpcy51bnBhY2tBbGlnbm1lbnQ9NCx0aGlzLmVuY29kaW5nPXUsdGhpcy51c2VyRGF0YT17fSx0aGlzLnZlcnNpb249MCx0aGlzLm9uVXBkYXRlPW51bGwsdGhpcy5pc1JlbmRlclRhcmdldFRleHR1cmU9ITEsdGhpcy5uZWVkc1BNUkVNVXBkYXRlPSExfXVwZGF0ZU1hdHJpeCgpe3RoaXMubWF0cml4LnNldFV2VHJhbnNmb3JtKHRoaXMub2Zmc2V0LngsdGhpcy5vZmZzZXQueSx0aGlzLnJlcGVhdC54LHRoaXMucmVwZWF0LnksdGhpcy5yb3RhdGlvbix0aGlzLmNlbnRlci54LHRoaXMuY2VudGVyLnkpfWNsb25lKCl7cmV0dXJuKG5ldyB0aGlzLmNvbnN0cnVjdG9yKS5jb3B5KHRoaXMpfWNvcHkodCl7cmV0dXJuIHRoaXMubmFtZT10Lm5hbWUsdGhpcy5pbWFnZT10LmltYWdlLHRoaXMubWlwbWFwcz10Lm1pcG1hcHMuc2xpY2UoMCksdGhpcy5tYXBwaW5nPXQubWFwcGluZyx0aGlzLndyYXBTPXQud3JhcFMsdGhpcy53cmFwVD10LndyYXBULHRoaXMubWFnRmlsdGVyPXQubWFnRmlsdGVyLHRoaXMubWluRmlsdGVyPXQubWluRmlsdGVyLHRoaXMuYW5pc290cm9weT10LmFuaXNvdHJvcHksdGhpcy5mb3JtYXQ9dC5mb3JtYXQsdGhpcy5pbnRlcm5hbEZvcm1hdD10LmludGVybmFsRm9ybWF0LHRoaXMudHlwZT10LnR5cGUsdGhpcy5vZmZzZXQuY29weSh0Lm9mZnNldCksdGhpcy5yZXBlYXQuY29weSh0LnJlcGVhdCksdGhpcy5jZW50ZXIuY29weSh0LmNlbnRlciksdGhpcy5yb3RhdGlvbj10LnJvdGF0aW9uLHRoaXMubWF0cml4QXV0b1VwZGF0ZT10Lm1hdHJpeEF1dG9VcGRhdGUsdGhpcy5tYXRyaXguY29weSh0Lm1hdHJpeCksdGhpcy5nZW5lcmF0ZU1pcG1hcHM9dC5nZW5lcmF0ZU1pcG1hcHMsdGhpcy5wcmVtdWx0aXBseUFscGhhPXQucHJlbXVsdGlwbHlBbHBoYSx0aGlzLmZsaXBZPXQuZmxpcFksdGhpcy51bnBhY2tBbGlnbm1lbnQ9dC51bnBhY2tBbGlnbm1lbnQsdGhpcy5lbmNvZGluZz10LmVuY29kaW5nLHRoaXMudXNlckRhdGE9SlNPTi5wYXJzZShKU09OLnN0cmluZ2lmeSh0LnVzZXJEYXRhKSksdGhpc310b0pTT04odCl7bGV0IGU9dm9pZCAwPT09dHx8InN0cmluZyI9PXR5cGVvZiB0O2lmKCFlJiZ2b2lkIDAhPT10LnRleHR1cmVzW3RoaXMudXVpZF0pcmV0dXJuIHQudGV4dHVyZXNbdGhpcy51dWlkXTtsZXQgaT17bWV0YWRhdGE6e3ZlcnNpb246NC41LHR5cGU6IlRleHR1cmUiLGdlbmVyYXRvcjoiVGV4dHVyZS50b0pTT04ifSx1dWlkOnRoaXMudXVpZCxuYW1lOnRoaXMubmFtZSxtYXBwaW5nOnRoaXMubWFwcGluZyxyZXBlYXQ6W3RoaXMucmVwZWF0LngsdGhpcy5yZXBlYXQueV0sb2Zmc2V0Olt0aGlzLm9mZnNldC54LHRoaXMub2Zmc2V0LnldLGNlbnRlcjpbdGhpcy5jZW50ZXIueCx0aGlzLmNlbnRlci55XSxyb3RhdGlvbjp0aGlzLnJvdGF0aW9uLHdyYXA6W3RoaXMud3JhcFMsdGhpcy53cmFwVF0sZm9ybWF0OnRoaXMuZm9ybWF0LHR5cGU6dGhpcy50eXBlLGVuY29kaW5nOnRoaXMuZW5jb2RpbmcsbWluRmlsdGVyOnRoaXMubWluRmlsdGVyLG1hZ0ZpbHRlcjp0aGlzLm1hZ0ZpbHRlcixhbmlzb3Ryb3B5OnRoaXMuYW5pc290cm9weSxmbGlwWTp0aGlzLmZsaXBZLHByZW11bHRpcGx5QWxwaGE6dGhpcy5wcmVtdWx0aXBseUFscGhhLHVucGFja0FsaWdubWVudDp0aGlzLnVucGFja0FsaWdubWVudH07aWYodm9pZCAwIT09dGhpcy5pbWFnZSl7bGV0IHI9dGhpcy5pbWFnZTtpZih2b2lkIDA9PT1yLnV1aWQmJihyLnV1aWQ9ZHUoKSksIWUmJnZvaWQgMD09PXQuaW1hZ2VzW3IudXVpZF0pe2xldCBvO2lmKEFycmF5LmlzQXJyYXkocikpe289W107Zm9yKGxldCBzPTAsYT1yLmxlbmd0aDtzPGE7cysrKW8ucHVzaChSaihyW3NdLmlzRGF0YVRleHR1cmU/cltzXS5pbWFnZTpyW3NdKSl9ZWxzZSBvPVJqKHIpO3QuaW1hZ2VzW3IudXVpZF09e3V1aWQ6ci51dWlkLHVybDpvfX1pLmltYWdlPXIudXVpZH1yZXR1cm4ie30iIT09SlNPTi5zdHJpbmdpZnkodGhpcy51c2VyRGF0YSkmJihpLnVzZXJEYXRhPXRoaXMudXNlckRhdGEpLGV8fCh0LnRleHR1cmVzW3RoaXMudXVpZF09aSksaX1kaXNwb3NlKCl7dGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJkaXNwb3NlIn0pfXRyYW5zZm9ybVV2KHQpe2lmKDMwMCE9PXRoaXMubWFwcGluZylyZXR1cm4gdDtpZih0LmFwcGx5TWF0cml4Myh0aGlzLm1hdHJpeCksdC54PDB8fHQueD4xKXN3aXRjaCh0aGlzLndyYXBTKXtjYXNlIDFlMzp0Lng9dC54LU1hdGguZmxvb3IodC54KTticmVhaztjYXNlIEVsOnQueD10Lng8MD8wOjE7YnJlYWs7Y2FzZSAxMDAyOnQueD0xPT09TWF0aC5hYnMoTWF0aC5mbG9vcih0LngpJTIpP01hdGguY2VpbCh0LngpLXQueDp0LngtTWF0aC5mbG9vcih0LngpfWlmKHQueTwwfHx0Lnk+MSlzd2l0Y2godGhpcy53cmFwVCl7Y2FzZSAxZTM6dC55PXQueS1NYXRoLmZsb29yKHQueSk7YnJlYWs7Y2FzZSBFbDp0Lnk9dC55PDA/MDoxO2JyZWFrO2Nhc2UgMTAwMjp0Lnk9MT09PU1hdGguYWJzKE1hdGguZmxvb3IodC55KSUyKT9NYXRoLmNlaWwodC55KS10Lnk6dC55LU1hdGguZmxvb3IodC55KX1yZXR1cm4gdGhpcy5mbGlwWSYmKHQueT0xLXQueSksdH1zZXQgbmVlZHNVcGRhdGUodCl7ITA9PT10JiZ0aGlzLnZlcnNpb24rK319O2Z1bmN0aW9uIFJqKG4pe3JldHVybiB0eXBlb2YgSFRNTEltYWdlRWxlbWVudDwidSImJm4gaW5zdGFuY2VvZiBIVE1MSW1hZ2VFbGVtZW50fHx0eXBlb2YgSFRNTENhbnZhc0VsZW1lbnQ8InUiJiZuIGluc3RhbmNlb2YgSFRNTENhbnZhc0VsZW1lbnR8fHR5cGVvZiBJbWFnZUJpdG1hcDwidSImJm4gaW5zdGFuY2VvZiBJbWFnZUJpdG1hcD9UcC5nZXREYXRhVVJMKG4pOm4uZGF0YT97ZGF0YTpBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChuLmRhdGEpLHdpZHRoOm4ud2lkdGgsaGVpZ2h0Om4uaGVpZ2h0LHR5cGU6bi5kYXRhLmNvbnN0cnVjdG9yLm5hbWV9Oihjb25zb2xlLndhcm4oIlRIUkVFLlRleHR1cmU6IFVuYWJsZSB0byBzZXJpYWxpemUgVGV4dHVyZS4iKSx7fSl9SG8uREVGQVVMVF9JTUFHRT12b2lkIDAsSG8uREVGQVVMVF9NQVBQSU5HPTMwMCxIby5wcm90b3R5cGUuaXNUZXh0dXJlPSEwO3ZhciBhcj1jbGFzc3tjb25zdHJ1Y3Rvcih0PTAsZT0wLGk9MCxyPTEpe3RoaXMueD10LHRoaXMueT1lLHRoaXMuej1pLHRoaXMudz1yfWdldCB3aWR0aCgpe3JldHVybiB0aGlzLnp9c2V0IHdpZHRoKHQpe3RoaXMuej10fWdldCBoZWlnaHQoKXtyZXR1cm4gdGhpcy53fXNldCBoZWlnaHQodCl7dGhpcy53PXR9c2V0KHQsZSxpLHIpe3JldHVybiB0aGlzLng9dCx0aGlzLnk9ZSx0aGlzLno9aSx0aGlzLnc9cix0aGlzfXNldFNjYWxhcih0KXtyZXR1cm4gdGhpcy54PXQsdGhpcy55PXQsdGhpcy56PXQsdGhpcy53PXQsdGhpc31zZXRYKHQpe3JldHVybiB0aGlzLng9dCx0aGlzfXNldFkodCl7cmV0dXJuIHRoaXMueT10LHRoaXN9c2V0Wih0KXtyZXR1cm4gdGhpcy56PXQsdGhpc31zZXRXKHQpe3JldHVybiB0aGlzLnc9dCx0aGlzfXNldENvbXBvbmVudCh0LGUpe3N3aXRjaCh0KXtjYXNlIDA6dGhpcy54PWU7YnJlYWs7Y2FzZSAxOnRoaXMueT1lO2JyZWFrO2Nhc2UgMjp0aGlzLno9ZTticmVhaztjYXNlIDM6dGhpcy53PWU7YnJlYWs7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoImluZGV4IGlzIG91dCBvZiByYW5nZTogIit0KX1yZXR1cm4gdGhpc31nZXRDb21wb25lbnQodCl7c3dpdGNoKHQpe2Nhc2UgMDpyZXR1cm4gdGhpcy54O2Nhc2UgMTpyZXR1cm4gdGhpcy55O2Nhc2UgMjpyZXR1cm4gdGhpcy56O2Nhc2UgMzpyZXR1cm4gdGhpcy53O2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKCJpbmRleCBpcyBvdXQgb2YgcmFuZ2U6ICIrdCl9fWNsb25lKCl7cmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKHRoaXMueCx0aGlzLnksdGhpcy56LHRoaXMudyl9Y29weSh0KXtyZXR1cm4gdGhpcy54PXQueCx0aGlzLnk9dC55LHRoaXMuej10LnosdGhpcy53PXZvaWQgMCE9PXQudz90Lnc6MSx0aGlzfWFkZCh0LGUpe3JldHVybiB2b2lkIDAhPT1lPyhjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjQ6IC5hZGQoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5hZGRWZWN0b3JzKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLmFkZFZlY3RvcnModCxlKSk6KHRoaXMueCs9dC54LHRoaXMueSs9dC55LHRoaXMueis9dC56LHRoaXMudys9dC53LHRoaXMpfWFkZFNjYWxhcih0KXtyZXR1cm4gdGhpcy54Kz10LHRoaXMueSs9dCx0aGlzLnorPXQsdGhpcy53Kz10LHRoaXN9YWRkVmVjdG9ycyh0LGUpe3JldHVybiB0aGlzLng9dC54K2UueCx0aGlzLnk9dC55K2UueSx0aGlzLno9dC56K2Uueix0aGlzLnc9dC53K2Uudyx0aGlzfWFkZFNjYWxlZFZlY3Rvcih0LGUpe3JldHVybiB0aGlzLngrPXQueCplLHRoaXMueSs9dC55KmUsdGhpcy56Kz10LnoqZSx0aGlzLncrPXQudyplLHRoaXN9c3ViKHQsZSl7cmV0dXJuIHZvaWQgMCE9PWU/KGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yNDogLnN1YigpIG5vdyBvbmx5IGFjY2VwdHMgb25lIGFyZ3VtZW50LiBVc2UgLnN1YlZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMuc3ViVmVjdG9ycyh0LGUpKToodGhpcy54LT10LngsdGhpcy55LT10LnksdGhpcy56LT10LnosdGhpcy53LT10LncsdGhpcyl9c3ViU2NhbGFyKHQpe3JldHVybiB0aGlzLngtPXQsdGhpcy55LT10LHRoaXMuei09dCx0aGlzLnctPXQsdGhpc31zdWJWZWN0b3JzKHQsZSl7cmV0dXJuIHRoaXMueD10LngtZS54LHRoaXMueT10LnktZS55LHRoaXMuej10LnotZS56LHRoaXMudz10LnctZS53LHRoaXN9bXVsdGlwbHkodCl7cmV0dXJuIHRoaXMueCo9dC54LHRoaXMueSo9dC55LHRoaXMueio9dC56LHRoaXMudyo9dC53LHRoaXN9bXVsdGlwbHlTY2FsYXIodCl7cmV0dXJuIHRoaXMueCo9dCx0aGlzLnkqPXQsdGhpcy56Kj10LHRoaXMudyo9dCx0aGlzfWFwcGx5TWF0cml4NCh0KXtsZXQgZT10aGlzLngsaT10aGlzLnkscj10aGlzLnosbz10aGlzLncscz10LmVsZW1lbnRzO3JldHVybiB0aGlzLng9c1swXSplK3NbNF0qaStzWzhdKnIrc1sxMl0qbyx0aGlzLnk9c1sxXSplK3NbNV0qaStzWzldKnIrc1sxM10qbyx0aGlzLno9c1syXSplK3NbNl0qaStzWzEwXSpyK3NbMTRdKm8sdGhpcy53PXNbM10qZStzWzddKmkrc1sxMV0qcitzWzE1XSpvLHRoaXN9ZGl2aWRlU2NhbGFyKHQpe3JldHVybiB0aGlzLm11bHRpcGx5U2NhbGFyKDEvdCl9c2V0QXhpc0FuZ2xlRnJvbVF1YXRlcm5pb24odCl7dGhpcy53PTIqTWF0aC5hY29zKHQudyk7bGV0IGU9TWF0aC5zcXJ0KDEtdC53KnQudyk7cmV0dXJuIGU8MWUtND8odGhpcy54PTEsdGhpcy55PTAsdGhpcy56PTApOih0aGlzLng9dC54L2UsdGhpcy55PXQueS9lLHRoaXMuej10LnovZSksdGhpc31zZXRBeGlzQW5nbGVGcm9tUm90YXRpb25NYXRyaXgodCl7bGV0IGUsaSxyLG8sbD10LmVsZW1lbnRzLGM9bFswXSx1PWxbNF0sZD1sWzhdLHA9bFsxXSxoPWxbNV0sZj1sWzldLG09bFsyXSx4PWxbNl0sZz1sWzEwXTtpZihNYXRoLmFicyh1LXApPC4wMSYmTWF0aC5hYnMoZC1tKTwuMDEmJk1hdGguYWJzKGYteCk8LjAxKXtpZihNYXRoLmFicyh1K3ApPC4xJiZNYXRoLmFicyhkK20pPC4xJiZNYXRoLmFicyhmK3gpPC4xJiZNYXRoLmFicyhjK2grZy0zKTwuMSlyZXR1cm4gdGhpcy5zZXQoMSwwLDAsMCksdGhpcztlPU1hdGguUEk7bGV0IEQ9KGMrMSkvMixUPShoKzEpLzIsaz0oZysxKS8yLFo9KHUrcCkvNCx6PShkK20pLzQsZmU9KGYreCkvNDtyZXR1cm4gRD5UJiZEPms/RDwuMDE/KGk9MCxyPS43MDcxMDY3ODEsbz0uNzA3MTA2NzgxKTooaT1NYXRoLnNxcnQoRCkscj1aL2ksbz16L2kpOlQ+az9UPC4wMT8oaT0uNzA3MTA2NzgxLHI9MCxvPS43MDcxMDY3ODEpOihyPU1hdGguc3FydChUKSxpPVovcixvPWZlL3IpOms8LjAxPyhpPS43MDcxMDY3ODEscj0uNzA3MTA2NzgxLG89MCk6KG89TWF0aC5zcXJ0KGspLGk9ei9vLHI9ZmUvbyksdGhpcy5zZXQoaSxyLG8sZSksdGhpc31sZXQgYj1NYXRoLnNxcnQoKHgtZikqKHgtZikrKGQtbSkqKGQtbSkrKHAtdSkqKHAtdSkpO3JldHVybiBNYXRoLmFicyhiKTwuMDAxJiYoYj0xKSx0aGlzLng9KHgtZikvYix0aGlzLnk9KGQtbSkvYix0aGlzLno9KHAtdSkvYix0aGlzLnc9TWF0aC5hY29zKChjK2grZy0xKS8yKSx0aGlzfW1pbih0KXtyZXR1cm4gdGhpcy54PU1hdGgubWluKHRoaXMueCx0LngpLHRoaXMueT1NYXRoLm1pbih0aGlzLnksdC55KSx0aGlzLno9TWF0aC5taW4odGhpcy56LHQueiksdGhpcy53PU1hdGgubWluKHRoaXMudyx0LncpLHRoaXN9bWF4KHQpe3JldHVybiB0aGlzLng9TWF0aC5tYXgodGhpcy54LHQueCksdGhpcy55PU1hdGgubWF4KHRoaXMueSx0LnkpLHRoaXMuej1NYXRoLm1heCh0aGlzLnosdC56KSx0aGlzLnc9TWF0aC5tYXgodGhpcy53LHQudyksdGhpc31jbGFtcCh0LGUpe3JldHVybiB0aGlzLng9TWF0aC5tYXgodC54LE1hdGgubWluKGUueCx0aGlzLngpKSx0aGlzLnk9TWF0aC5tYXgodC55LE1hdGgubWluKGUueSx0aGlzLnkpKSx0aGlzLno9TWF0aC5tYXgodC56LE1hdGgubWluKGUueix0aGlzLnopKSx0aGlzLnc9TWF0aC5tYXgodC53LE1hdGgubWluKGUudyx0aGlzLncpKSx0aGlzfWNsYW1wU2NhbGFyKHQsZSl7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0LE1hdGgubWluKGUsdGhpcy54KSksdGhpcy55PU1hdGgubWF4KHQsTWF0aC5taW4oZSx0aGlzLnkpKSx0aGlzLno9TWF0aC5tYXgodCxNYXRoLm1pbihlLHRoaXMueikpLHRoaXMudz1NYXRoLm1heCh0LE1hdGgubWluKGUsdGhpcy53KSksdGhpc31jbGFtcExlbmd0aCh0LGUpe2xldCBpPXRoaXMubGVuZ3RoKCk7cmV0dXJuIHRoaXMuZGl2aWRlU2NhbGFyKGl8fDEpLm11bHRpcGx5U2NhbGFyKE1hdGgubWF4KHQsTWF0aC5taW4oZSxpKSkpfWZsb29yKCl7cmV0dXJuIHRoaXMueD1NYXRoLmZsb29yKHRoaXMueCksdGhpcy55PU1hdGguZmxvb3IodGhpcy55KSx0aGlzLno9TWF0aC5mbG9vcih0aGlzLnopLHRoaXMudz1NYXRoLmZsb29yKHRoaXMudyksdGhpc31jZWlsKCl7cmV0dXJuIHRoaXMueD1NYXRoLmNlaWwodGhpcy54KSx0aGlzLnk9TWF0aC5jZWlsKHRoaXMueSksdGhpcy56PU1hdGguY2VpbCh0aGlzLnopLHRoaXMudz1NYXRoLmNlaWwodGhpcy53KSx0aGlzfXJvdW5kKCl7cmV0dXJuIHRoaXMueD1NYXRoLnJvdW5kKHRoaXMueCksdGhpcy55PU1hdGgucm91bmQodGhpcy55KSx0aGlzLno9TWF0aC5yb3VuZCh0aGlzLnopLHRoaXMudz1NYXRoLnJvdW5kKHRoaXMudyksdGhpc31yb3VuZFRvWmVybygpe3JldHVybiB0aGlzLng9dGhpcy54PDA/TWF0aC5jZWlsKHRoaXMueCk6TWF0aC5mbG9vcih0aGlzLngpLHRoaXMueT10aGlzLnk8MD9NYXRoLmNlaWwodGhpcy55KTpNYXRoLmZsb29yKHRoaXMueSksdGhpcy56PXRoaXMuejwwP01hdGguY2VpbCh0aGlzLnopOk1hdGguZmxvb3IodGhpcy56KSx0aGlzLnc9dGhpcy53PDA/TWF0aC5jZWlsKHRoaXMudyk6TWF0aC5mbG9vcih0aGlzLncpLHRoaXN9bmVnYXRlKCl7cmV0dXJuIHRoaXMueD0tdGhpcy54LHRoaXMueT0tdGhpcy55LHRoaXMuej0tdGhpcy56LHRoaXMudz0tdGhpcy53LHRoaXN9ZG90KHQpe3JldHVybiB0aGlzLngqdC54K3RoaXMueSp0LnkrdGhpcy56KnQueit0aGlzLncqdC53fWxlbmd0aFNxKCl7cmV0dXJuIHRoaXMueCp0aGlzLngrdGhpcy55KnRoaXMueSt0aGlzLnoqdGhpcy56K3RoaXMudyp0aGlzLnd9bGVuZ3RoKCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLngqdGhpcy54K3RoaXMueSp0aGlzLnkrdGhpcy56KnRoaXMueit0aGlzLncqdGhpcy53KX1tYW5oYXR0YW5MZW5ndGgoKXtyZXR1cm4gTWF0aC5hYnModGhpcy54KStNYXRoLmFicyh0aGlzLnkpK01hdGguYWJzKHRoaXMueikrTWF0aC5hYnModGhpcy53KX1ub3JtYWxpemUoKXtyZXR1cm4gdGhpcy5kaXZpZGVTY2FsYXIodGhpcy5sZW5ndGgoKXx8MSl9c2V0TGVuZ3RoKHQpe3JldHVybiB0aGlzLm5vcm1hbGl6ZSgpLm11bHRpcGx5U2NhbGFyKHQpfWxlcnAodCxlKXtyZXR1cm4gdGhpcy54Kz0odC54LXRoaXMueCkqZSx0aGlzLnkrPSh0LnktdGhpcy55KSplLHRoaXMueis9KHQuei10aGlzLnopKmUsdGhpcy53Kz0odC53LXRoaXMudykqZSx0aGlzfWxlcnBWZWN0b3JzKHQsZSxpKXtyZXR1cm4gdGhpcy54PXQueCsoZS54LXQueCkqaSx0aGlzLnk9dC55KyhlLnktdC55KSppLHRoaXMuej10LnorKGUuei10LnopKmksdGhpcy53PXQudysoZS53LXQudykqaSx0aGlzfWVxdWFscyh0KXtyZXR1cm4gdC54PT09dGhpcy54JiZ0Lnk9PT10aGlzLnkmJnQuej09PXRoaXMueiYmdC53PT09dGhpcy53fWZyb21BcnJheSh0LGU9MCl7cmV0dXJuIHRoaXMueD10W2VdLHRoaXMueT10W2UrMV0sdGhpcy56PXRbZSsyXSx0aGlzLnc9dFtlKzNdLHRoaXN9dG9BcnJheSh0PVtdLGU9MCl7cmV0dXJuIHRbZV09dGhpcy54LHRbZSsxXT10aGlzLnksdFtlKzJdPXRoaXMueix0W2UrM109dGhpcy53LHR9ZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LGUsaSl7cmV0dXJuIHZvaWQgMCE9PWkmJmNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yNDogb2Zmc2V0IGhhcyBiZWVuIHJlbW92ZWQgZnJvbSAuZnJvbUJ1ZmZlckF0dHJpYnV0ZSgpLiIpLHRoaXMueD10LmdldFgoZSksdGhpcy55PXQuZ2V0WShlKSx0aGlzLno9dC5nZXRaKGUpLHRoaXMudz10LmdldFcoZSksdGhpc31yYW5kb20oKXtyZXR1cm4gdGhpcy54PU1hdGgucmFuZG9tKCksdGhpcy55PU1hdGgucmFuZG9tKCksdGhpcy56PU1hdGgucmFuZG9tKCksdGhpcy53PU1hdGgucmFuZG9tKCksdGhpc30qW1N5bWJvbC5pdGVyYXRvcl0oKXt5aWVsZCB0aGlzLngseWllbGQgdGhpcy55LHlpZWxkIHRoaXMueix5aWVsZCB0aGlzLnd9fTthci5wcm90b3R5cGUuaXNWZWN0b3I0PSEwO3ZhciBXYT1jbGFzcyBleHRlbmRzIEVwe2NvbnN0cnVjdG9yKHQsZSxpPXt9KXtzdXBlcigpLHRoaXMud2lkdGg9dCx0aGlzLmhlaWdodD1lLHRoaXMuZGVwdGg9MSx0aGlzLnNjaXNzb3I9bmV3IGFyKDAsMCx0LGUpLHRoaXMuc2Npc3NvclRlc3Q9ITEsdGhpcy52aWV3cG9ydD1uZXcgYXIoMCwwLHQsZSksdGhpcy50ZXh0dXJlPW5ldyBIbyh2b2lkIDAsaS5tYXBwaW5nLGkud3JhcFMsaS53cmFwVCxpLm1hZ0ZpbHRlcixpLm1pbkZpbHRlcixpLmZvcm1hdCxpLnR5cGUsaS5hbmlzb3Ryb3B5LGkuZW5jb2RpbmcpLHRoaXMudGV4dHVyZS5pc1JlbmRlclRhcmdldFRleHR1cmU9ITAsdGhpcy50ZXh0dXJlLmltYWdlPXt3aWR0aDp0LGhlaWdodDplLGRlcHRoOjF9LHRoaXMudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM9dm9pZCAwIT09aS5nZW5lcmF0ZU1pcG1hcHMmJmkuZ2VuZXJhdGVNaXBtYXBzLHRoaXMudGV4dHVyZS5pbnRlcm5hbEZvcm1hdD12b2lkIDAhPT1pLmludGVybmFsRm9ybWF0P2kuaW50ZXJuYWxGb3JtYXQ6bnVsbCx0aGlzLnRleHR1cmUubWluRmlsdGVyPXZvaWQgMCE9PWkubWluRmlsdGVyP2kubWluRmlsdGVyOkdzLHRoaXMuZGVwdGhCdWZmZXI9dm9pZCAwPT09aS5kZXB0aEJ1ZmZlcnx8aS5kZXB0aEJ1ZmZlcix0aGlzLnN0ZW5jaWxCdWZmZXI9dm9pZCAwIT09aS5zdGVuY2lsQnVmZmVyJiZpLnN0ZW5jaWxCdWZmZXIsdGhpcy5kZXB0aFRleHR1cmU9dm9pZCAwIT09aS5kZXB0aFRleHR1cmU/aS5kZXB0aFRleHR1cmU6bnVsbH1zZXRUZXh0dXJlKHQpe3QuaW1hZ2U9e3dpZHRoOnRoaXMud2lkdGgsaGVpZ2h0OnRoaXMuaGVpZ2h0LGRlcHRoOnRoaXMuZGVwdGh9LHRoaXMudGV4dHVyZT10fXNldFNpemUodCxlLGk9MSl7KHRoaXMud2lkdGghPT10fHx0aGlzLmhlaWdodCE9PWV8fHRoaXMuZGVwdGghPT1pKSYmKHRoaXMud2lkdGg9dCx0aGlzLmhlaWdodD1lLHRoaXMuZGVwdGg9aSx0aGlzLnRleHR1cmUuaW1hZ2Uud2lkdGg9dCx0aGlzLnRleHR1cmUuaW1hZ2UuaGVpZ2h0PWUsdGhpcy50ZXh0dXJlLmltYWdlLmRlcHRoPWksdGhpcy5kaXNwb3NlKCkpLHRoaXMudmlld3BvcnQuc2V0KDAsMCx0LGUpLHRoaXMuc2Npc3Nvci5zZXQoMCwwLHQsZSl9Y2xvbmUoKXtyZXR1cm4obmV3IHRoaXMuY29uc3RydWN0b3IpLmNvcHkodGhpcyl9Y29weSh0KXtyZXR1cm4gdGhpcy53aWR0aD10LndpZHRoLHRoaXMuaGVpZ2h0PXQuaGVpZ2h0LHRoaXMuZGVwdGg9dC5kZXB0aCx0aGlzLnZpZXdwb3J0LmNvcHkodC52aWV3cG9ydCksdGhpcy50ZXh0dXJlPXQudGV4dHVyZS5jbG9uZSgpLHRoaXMudGV4dHVyZS5pbWFnZT1PYmplY3QuYXNzaWduKHt9LHQudGV4dHVyZS5pbWFnZSksdGhpcy5kZXB0aEJ1ZmZlcj10LmRlcHRoQnVmZmVyLHRoaXMuc3RlbmNpbEJ1ZmZlcj10LnN0ZW5jaWxCdWZmZXIsdGhpcy5kZXB0aFRleHR1cmU9dC5kZXB0aFRleHR1cmUsdGhpc31kaXNwb3NlKCl7dGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJkaXNwb3NlIn0pfX07V2EucHJvdG90eXBlLmlzV2ViR0xSZW5kZXJUYXJnZXQ9ITAsY2xhc3MgZXh0ZW5kcyBXYXtjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIodCxlKTtsZXQgcj10aGlzLnRleHR1cmU7dGhpcy50ZXh0dXJlPVtdO2ZvcihsZXQgbz0wO288aTtvKyspdGhpcy50ZXh0dXJlW29dPXIuY2xvbmUoKX1zZXRTaXplKHQsZSxpPTEpe2lmKHRoaXMud2lkdGghPT10fHx0aGlzLmhlaWdodCE9PWV8fHRoaXMuZGVwdGghPT1pKXt0aGlzLndpZHRoPXQsdGhpcy5oZWlnaHQ9ZSx0aGlzLmRlcHRoPWk7Zm9yKGxldCByPTAsbz10aGlzLnRleHR1cmUubGVuZ3RoO3I8bztyKyspdGhpcy50ZXh0dXJlW3JdLmltYWdlLndpZHRoPXQsdGhpcy50ZXh0dXJlW3JdLmltYWdlLmhlaWdodD1lLHRoaXMudGV4dHVyZVtyXS5pbWFnZS5kZXB0aD1pO3RoaXMuZGlzcG9zZSgpfXJldHVybiB0aGlzLnZpZXdwb3J0LnNldCgwLDAsdCxlKSx0aGlzLnNjaXNzb3Iuc2V0KDAsMCx0LGUpLHRoaXN9Y29weSh0KXt0aGlzLmRpc3Bvc2UoKSx0aGlzLndpZHRoPXQud2lkdGgsdGhpcy5oZWlnaHQ9dC5oZWlnaHQsdGhpcy5kZXB0aD10LmRlcHRoLHRoaXMudmlld3BvcnQuc2V0KDAsMCx0aGlzLndpZHRoLHRoaXMuaGVpZ2h0KSx0aGlzLnNjaXNzb3Iuc2V0KDAsMCx0aGlzLndpZHRoLHRoaXMuaGVpZ2h0KSx0aGlzLmRlcHRoQnVmZmVyPXQuZGVwdGhCdWZmZXIsdGhpcy5zdGVuY2lsQnVmZmVyPXQuc3RlbmNpbEJ1ZmZlcix0aGlzLmRlcHRoVGV4dHVyZT10LmRlcHRoVGV4dHVyZSx0aGlzLnRleHR1cmUubGVuZ3RoPTA7Zm9yKGxldCBlPTAsaT10LnRleHR1cmUubGVuZ3RoO2U8aTtlKyspdGhpcy50ZXh0dXJlW2VdPXQudGV4dHVyZVtlXS5jbG9uZSgpO3JldHVybiB0aGlzfX0ucHJvdG90eXBlLmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHM9ITA7dmFyIFhTPWNsYXNzIGV4dGVuZHMgV2F7Y29uc3RydWN0b3IodCxlLGk9e30pe3N1cGVyKHQsZSxpKSx0aGlzLnNhbXBsZXM9NCx0aGlzLmlnbm9yZURlcHRoRm9yTXVsdGlzYW1wbGVDb3B5PXZvaWQgMD09PWkuaWdub3JlRGVwdGh8fGkuaWdub3JlRGVwdGgsdGhpcy51c2VSZW5kZXJUb1RleHR1cmU9dm9pZCAwIT09aS51c2VSZW5kZXJUb1RleHR1cmUmJmkudXNlUmVuZGVyVG9UZXh0dXJlLHRoaXMudXNlUmVuZGVyYnVmZmVyPSExPT09dGhpcy51c2VSZW5kZXJUb1RleHR1cmV9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weS5jYWxsKHRoaXMsdCksdGhpcy5zYW1wbGVzPXQuc2FtcGxlcyx0aGlzLnVzZVJlbmRlclRvVGV4dHVyZT10LnVzZVJlbmRlclRvVGV4dHVyZSx0aGlzLnVzZVJlbmRlcmJ1ZmZlcj10LnVzZVJlbmRlcmJ1ZmZlcix0aGlzfX07WFMucHJvdG90eXBlLmlzV2ViR0xNdWx0aXNhbXBsZVJlbmRlclRhcmdldD0hMDt2YXIgcXM9Y2xhc3N7Y29uc3RydWN0b3IodD0wLGU9MCxpPTAscj0xKXt0aGlzLl94PXQsdGhpcy5feT1lLHRoaXMuX3o9aSx0aGlzLl93PXJ9c3RhdGljIHNsZXJwKHQsZSxpLHIpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlF1YXRlcm5pb246IFN0YXRpYyAuc2xlcnAoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgcW0uc2xlcnBRdWF0ZXJuaW9ucyggcWEsIHFiLCB0ICkgaW5zdGVhZC4iKSxpLnNsZXJwUXVhdGVybmlvbnModCxlLHIpfXN0YXRpYyBzbGVycEZsYXQodCxlLGkscixvLHMsYSl7bGV0IGw9aVtyKzBdLGM9aVtyKzFdLHU9aVtyKzJdLGQ9aVtyKzNdLHA9b1tzKzBdLGg9b1tzKzFdLGY9b1tzKzJdLG09b1tzKzNdO2lmKDA9PT1hKXJldHVybiB0W2UrMF09bCx0W2UrMV09Yyx0W2UrMl09dSx2b2lkKHRbZSszXT1kKTtpZigxPT09YSlyZXR1cm4gdFtlKzBdPXAsdFtlKzFdPWgsdFtlKzJdPWYsdm9pZCh0W2UrM109bSk7aWYoZCE9PW18fGwhPT1wfHxjIT09aHx8dSE9PWYpe2xldCB4PTEtYSxnPWwqcCtjKmgrdSpmK2QqbSxiPWc+PTA/MTotMSxEPTEtZypnO2lmKEQ+TnVtYmVyLkVQU0lMT04pe2xldCBrPU1hdGguc3FydChEKSxaPU1hdGguYXRhbjIoayxnKmIpO3g9TWF0aC5zaW4oeCpaKS9rLGE9TWF0aC5zaW4oYSpaKS9rfWxldCBUPWEqYjtpZihsPWwqeCtwKlQsYz1jKngraCpULHU9dSp4K2YqVCxkPWQqeCttKlQseD09PTEtYSl7bGV0IGs9MS9NYXRoLnNxcnQobCpsK2MqYyt1KnUrZCpkKTtsKj1rLGMqPWssdSo9ayxkKj1rfX10W2VdPWwsdFtlKzFdPWMsdFtlKzJdPXUsdFtlKzNdPWR9c3RhdGljIG11bHRpcGx5UXVhdGVybmlvbnNGbGF0KHQsZSxpLHIsbyxzKXtsZXQgYT1pW3JdLGw9aVtyKzFdLGM9aVtyKzJdLHU9aVtyKzNdLGQ9b1tzXSxwPW9bcysxXSxoPW9bcysyXSxmPW9bcyszXTtyZXR1cm4gdFtlXT1hKmYrdSpkK2wqaC1jKnAsdFtlKzFdPWwqZit1KnArYypkLWEqaCx0W2UrMl09YypmK3UqaCthKnAtbCpkLHRbZSszXT11KmYtYSpkLWwqcC1jKmgsdH1nZXQgeCgpe3JldHVybiB0aGlzLl94fXNldCB4KHQpe3RoaXMuX3g9dCx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCl9Z2V0IHkoKXtyZXR1cm4gdGhpcy5feX1zZXQgeSh0KXt0aGlzLl95PXQsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpfWdldCB6KCl7cmV0dXJuIHRoaXMuX3p9c2V0IHoodCl7dGhpcy5fej10LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKX1nZXQgdygpe3JldHVybiB0aGlzLl93fXNldCB3KHQpe3RoaXMuX3c9dCx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCl9c2V0KHQsZSxpLHIpe3JldHVybiB0aGlzLl94PXQsdGhpcy5feT1lLHRoaXMuX3o9aSx0aGlzLl93PXIsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5feCx0aGlzLl95LHRoaXMuX3osdGhpcy5fdyl9Y29weSh0KXtyZXR1cm4gdGhpcy5feD10LngsdGhpcy5feT10LnksdGhpcy5fej10LnosdGhpcy5fdz10LncsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9c2V0RnJvbUV1bGVyKHQsZSl7aWYoIXR8fCF0LmlzRXVsZXIpdGhyb3cgbmV3IEVycm9yKCJUSFJFRS5RdWF0ZXJuaW9uOiAuc2V0RnJvbUV1bGVyKCkgbm93IGV4cGVjdHMgYW4gRXVsZXIgcm90YXRpb24gcmF0aGVyIHRoYW4gYSBWZWN0b3IzIGFuZCBvcmRlci4iKTtsZXQgaT10Ll94LHI9dC5feSxvPXQuX3oscz10Ll9vcmRlcixhPU1hdGguY29zLGw9TWF0aC5zaW4sYz1hKGkvMiksdT1hKHIvMiksZD1hKG8vMikscD1sKGkvMiksaD1sKHIvMiksZj1sKG8vMik7c3dpdGNoKHMpe2Nhc2UiWFlaIjp0aGlzLl94PXAqdSpkK2MqaCpmLHRoaXMuX3k9YypoKmQtcCp1KmYsdGhpcy5fej1jKnUqZitwKmgqZCx0aGlzLl93PWMqdSpkLXAqaCpmO2JyZWFrO2Nhc2UiWVhaIjp0aGlzLl94PXAqdSpkK2MqaCpmLHRoaXMuX3k9YypoKmQtcCp1KmYsdGhpcy5fej1jKnUqZi1wKmgqZCx0aGlzLl93PWMqdSpkK3AqaCpmO2JyZWFrO2Nhc2UiWlhZIjp0aGlzLl94PXAqdSpkLWMqaCpmLHRoaXMuX3k9YypoKmQrcCp1KmYsdGhpcy5fej1jKnUqZitwKmgqZCx0aGlzLl93PWMqdSpkLXAqaCpmO2JyZWFrO2Nhc2UiWllYIjp0aGlzLl94PXAqdSpkLWMqaCpmLHRoaXMuX3k9YypoKmQrcCp1KmYsdGhpcy5fej1jKnUqZi1wKmgqZCx0aGlzLl93PWMqdSpkK3AqaCpmO2JyZWFrO2Nhc2UiWVpYIjp0aGlzLl94PXAqdSpkK2MqaCpmLHRoaXMuX3k9YypoKmQrcCp1KmYsdGhpcy5fej1jKnUqZi1wKmgqZCx0aGlzLl93PWMqdSpkLXAqaCpmO2JyZWFrO2Nhc2UiWFpZIjp0aGlzLl94PXAqdSpkLWMqaCpmLHRoaXMuX3k9YypoKmQtcCp1KmYsdGhpcy5fej1jKnUqZitwKmgqZCx0aGlzLl93PWMqdSpkK3AqaCpmO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS53YXJuKCJUSFJFRS5RdWF0ZXJuaW9uOiAuc2V0RnJvbUV1bGVyKCkgZW5jb3VudGVyZWQgYW4gdW5rbm93biBvcmRlcjogIitzKX1yZXR1cm4hMSE9PWUmJnRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21BeGlzQW5nbGUodCxlKXtsZXQgaT1lLzIscj1NYXRoLnNpbihpKTtyZXR1cm4gdGhpcy5feD10Lngqcix0aGlzLl95PXQueSpyLHRoaXMuX3o9dC56KnIsdGhpcy5fdz1NYXRoLmNvcyhpKSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31zZXRGcm9tUm90YXRpb25NYXRyaXgodCl7bGV0IGU9dC5lbGVtZW50cyxpPWVbMF0scj1lWzRdLG89ZVs4XSxzPWVbMV0sYT1lWzVdLGw9ZVs5XSxjPWVbMl0sdT1lWzZdLGQ9ZVsxMF0scD1pK2ErZDtpZihwPjApe2xldCBoPS41L01hdGguc3FydChwKzEpO3RoaXMuX3c9LjI1L2gsdGhpcy5feD0odS1sKSpoLHRoaXMuX3k9KG8tYykqaCx0aGlzLl96PShzLXIpKmh9ZWxzZSBpZihpPmEmJmk+ZCl7bGV0IGg9MipNYXRoLnNxcnQoMStpLWEtZCk7dGhpcy5fdz0odS1sKS9oLHRoaXMuX3g9LjI1KmgsdGhpcy5feT0ocitzKS9oLHRoaXMuX3o9KG8rYykvaH1lbHNlIGlmKGE+ZCl7bGV0IGg9MipNYXRoLnNxcnQoMSthLWktZCk7dGhpcy5fdz0oby1jKS9oLHRoaXMuX3g9KHIrcykvaCx0aGlzLl95PS4yNSpoLHRoaXMuX3o9KGwrdSkvaH1lbHNle2xldCBoPTIqTWF0aC5zcXJ0KDErZC1pLWEpO3RoaXMuX3c9KHMtcikvaCx0aGlzLl94PShvK2MpL2gsdGhpcy5feT0obCt1KS9oLHRoaXMuX3o9LjI1Kmh9cmV0dXJuIHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21Vbml0VmVjdG9ycyh0LGUpe2xldCBpPXQuZG90KGUpKzE7cmV0dXJuIGk8TnVtYmVyLkVQU0lMT04/KGk9MCxNYXRoLmFicyh0LngpPk1hdGguYWJzKHQueik/KHRoaXMuX3g9LXQueSx0aGlzLl95PXQueCx0aGlzLl96PTAsdGhpcy5fdz1pKToodGhpcy5feD0wLHRoaXMuX3k9LXQueix0aGlzLl96PXQueSx0aGlzLl93PWkpKToodGhpcy5feD10LnkqZS56LXQueiplLnksdGhpcy5feT10LnoqZS54LXQueCplLnosdGhpcy5fej10LngqZS55LXQueSplLngsdGhpcy5fdz1pKSx0aGlzLm5vcm1hbGl6ZSgpfWFuZ2xlVG8odCl7cmV0dXJuIDIqTWF0aC5hY29zKE1hdGguYWJzKEdhKHRoaXMuZG90KHQpLC0xLDEpKSl9cm90YXRlVG93YXJkcyh0LGUpe2xldCBpPXRoaXMuYW5nbGVUbyh0KTtpZigwPT09aSlyZXR1cm4gdGhpcztsZXQgcj1NYXRoLm1pbigxLGUvaSk7cmV0dXJuIHRoaXMuc2xlcnAodCxyKSx0aGlzfWlkZW50aXR5KCl7cmV0dXJuIHRoaXMuc2V0KDAsMCwwLDEpfWludmVydCgpe3JldHVybiB0aGlzLmNvbmp1Z2F0ZSgpfWNvbmp1Z2F0ZSgpe3JldHVybiB0aGlzLl94Kj0tMSx0aGlzLl95Kj0tMSx0aGlzLl96Kj0tMSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31kb3QodCl7cmV0dXJuIHRoaXMuX3gqdC5feCt0aGlzLl95KnQuX3krdGhpcy5feip0Ll96K3RoaXMuX3cqdC5fd31sZW5ndGhTcSgpe3JldHVybiB0aGlzLl94KnRoaXMuX3grdGhpcy5feSp0aGlzLl95K3RoaXMuX3oqdGhpcy5feit0aGlzLl93KnRoaXMuX3d9bGVuZ3RoKCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLl94KnRoaXMuX3grdGhpcy5feSp0aGlzLl95K3RoaXMuX3oqdGhpcy5feit0aGlzLl93KnRoaXMuX3cpfW5vcm1hbGl6ZSgpe2xldCB0PXRoaXMubGVuZ3RoKCk7cmV0dXJuIDA9PT10Pyh0aGlzLl94PTAsdGhpcy5feT0wLHRoaXMuX3o9MCx0aGlzLl93PTEpOih0PTEvdCx0aGlzLl94PXRoaXMuX3gqdCx0aGlzLl95PXRoaXMuX3kqdCx0aGlzLl96PXRoaXMuX3oqdCx0aGlzLl93PXRoaXMuX3cqdCksdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9bXVsdGlwbHkodCxlKXtyZXR1cm4gdm9pZCAwIT09ZT8oY29uc29sZS53YXJuKCJUSFJFRS5RdWF0ZXJuaW9uOiAubXVsdGlwbHkoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5tdWx0aXBseVF1YXRlcm5pb25zKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLm11bHRpcGx5UXVhdGVybmlvbnModCxlKSk6dGhpcy5tdWx0aXBseVF1YXRlcm5pb25zKHRoaXMsdCl9cHJlbXVsdGlwbHkodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlRdWF0ZXJuaW9ucyh0LHRoaXMpfW11bHRpcGx5UXVhdGVybmlvbnModCxlKXtsZXQgaT10Ll94LHI9dC5feSxvPXQuX3oscz10Ll93LGE9ZS5feCxsPWUuX3ksYz1lLl96LHU9ZS5fdztyZXR1cm4gdGhpcy5feD1pKnUrcyphK3IqYy1vKmwsdGhpcy5feT1yKnUrcypsK28qYS1pKmMsdGhpcy5fej1vKnUrcypjK2kqbC1yKmEsdGhpcy5fdz1zKnUtaSphLXIqbC1vKmMsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9c2xlcnAodCxlKXtpZigwPT09ZSlyZXR1cm4gdGhpcztpZigxPT09ZSlyZXR1cm4gdGhpcy5jb3B5KHQpO2xldCBpPXRoaXMuX3gscj10aGlzLl95LG89dGhpcy5feixzPXRoaXMuX3csYT1zKnQuX3craSp0Ll94K3IqdC5feStvKnQuX3o7aWYoYTwwPyh0aGlzLl93PS10Ll93LHRoaXMuX3g9LXQuX3gsdGhpcy5feT0tdC5feSx0aGlzLl96PS10Ll96LGE9LWEpOnRoaXMuY29weSh0KSxhPj0xKXJldHVybiB0aGlzLl93PXMsdGhpcy5feD1pLHRoaXMuX3k9cix0aGlzLl96PW8sdGhpcztsZXQgbD0xLWEqYTtpZihsPD1OdW1iZXIuRVBTSUxPTil7bGV0IGg9MS1lO3JldHVybiB0aGlzLl93PWgqcytlKnRoaXMuX3csdGhpcy5feD1oKmkrZSp0aGlzLl94LHRoaXMuX3k9aCpyK2UqdGhpcy5feSx0aGlzLl96PWgqbytlKnRoaXMuX3osdGhpcy5ub3JtYWxpemUoKSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31sZXQgYz1NYXRoLnNxcnQobCksdT1NYXRoLmF0YW4yKGMsYSksZD1NYXRoLnNpbigoMS1lKSp1KS9jLHA9TWF0aC5zaW4oZSp1KS9jO3JldHVybiB0aGlzLl93PXMqZCt0aGlzLl93KnAsdGhpcy5feD1pKmQrdGhpcy5feCpwLHRoaXMuX3k9cipkK3RoaXMuX3kqcCx0aGlzLl96PW8qZCt0aGlzLl96KnAsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9c2xlcnBRdWF0ZXJuaW9ucyh0LGUsaSl7cmV0dXJuIHRoaXMuY29weSh0KS5zbGVycChlLGkpfXJhbmRvbSgpe2xldCB0PU1hdGgucmFuZG9tKCksZT1NYXRoLnNxcnQoMS10KSxpPU1hdGguc3FydCh0KSxyPTIqTWF0aC5QSSpNYXRoLnJhbmRvbSgpLG89MipNYXRoLlBJKk1hdGgucmFuZG9tKCk7cmV0dXJuIHRoaXMuc2V0KGUqTWF0aC5jb3MociksaSpNYXRoLnNpbihvKSxpKk1hdGguY29zKG8pLGUqTWF0aC5zaW4ocikpfWVxdWFscyh0KXtyZXR1cm4gdC5feD09PXRoaXMuX3gmJnQuX3k9PT10aGlzLl95JiZ0Ll96PT09dGhpcy5feiYmdC5fdz09PXRoaXMuX3d9ZnJvbUFycmF5KHQsZT0wKXtyZXR1cm4gdGhpcy5feD10W2VdLHRoaXMuX3k9dFtlKzFdLHRoaXMuX3o9dFtlKzJdLHRoaXMuX3c9dFtlKzNdLHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXRvQXJyYXkodD1bXSxlPTApe3JldHVybiB0W2VdPXRoaXMuX3gsdFtlKzFdPXRoaXMuX3ksdFtlKzJdPXRoaXMuX3osdFtlKzNdPXRoaXMuX3csdH1mcm9tQnVmZmVyQXR0cmlidXRlKHQsZSl7cmV0dXJuIHRoaXMuX3g9dC5nZXRYKGUpLHRoaXMuX3k9dC5nZXRZKGUpLHRoaXMuX3o9dC5nZXRaKGUpLHRoaXMuX3c9dC5nZXRXKGUpLHRoaXN9X29uQ2hhbmdlKHQpe3JldHVybiB0aGlzLl9vbkNoYW5nZUNhbGxiYWNrPXQsdGhpc31fb25DaGFuZ2VDYWxsYmFjaygpe319O3FzLnByb3RvdHlwZS5pc1F1YXRlcm5pb249ITA7dmFyIGllPWNsYXNze2NvbnN0cnVjdG9yKHQ9MCxlPTAsaT0wKXt0aGlzLng9dCx0aGlzLnk9ZSx0aGlzLno9aX1zZXQodCxlLGkpe3JldHVybiB2b2lkIDA9PT1pJiYoaT10aGlzLnopLHRoaXMueD10LHRoaXMueT1lLHRoaXMuej1pLHRoaXN9c2V0U2NhbGFyKHQpe3JldHVybiB0aGlzLng9dCx0aGlzLnk9dCx0aGlzLno9dCx0aGlzfXNldFgodCl7cmV0dXJuIHRoaXMueD10LHRoaXN9c2V0WSh0KXtyZXR1cm4gdGhpcy55PXQsdGhpc31zZXRaKHQpe3JldHVybiB0aGlzLno9dCx0aGlzfXNldENvbXBvbmVudCh0LGUpe3N3aXRjaCh0KXtjYXNlIDA6dGhpcy54PWU7YnJlYWs7Y2FzZSAxOnRoaXMueT1lO2JyZWFrO2Nhc2UgMjp0aGlzLno9ZTticmVhaztkZWZhdWx0OnRocm93IG5ldyBFcnJvcigiaW5kZXggaXMgb3V0IG9mIHJhbmdlOiAiK3QpfXJldHVybiB0aGlzfWdldENvbXBvbmVudCh0KXtzd2l0Y2godCl7Y2FzZSAwOnJldHVybiB0aGlzLng7Y2FzZSAxOnJldHVybiB0aGlzLnk7Y2FzZSAyOnJldHVybiB0aGlzLno7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoImluZGV4IGlzIG91dCBvZiByYW5nZTogIit0KX19Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy54LHRoaXMueSx0aGlzLnopfWNvcHkodCl7cmV0dXJuIHRoaXMueD10LngsdGhpcy55PXQueSx0aGlzLno9dC56LHRoaXN9YWRkKHQsZSl7cmV0dXJuIHZvaWQgMCE9PWU/KGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogLmFkZCgpIG5vdyBvbmx5IGFjY2VwdHMgb25lIGFyZ3VtZW50LiBVc2UgLmFkZFZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMuYWRkVmVjdG9ycyh0LGUpKToodGhpcy54Kz10LngsdGhpcy55Kz10LnksdGhpcy56Kz10LnosdGhpcyl9YWRkU2NhbGFyKHQpe3JldHVybiB0aGlzLngrPXQsdGhpcy55Kz10LHRoaXMueis9dCx0aGlzfWFkZFZlY3RvcnModCxlKXtyZXR1cm4gdGhpcy54PXQueCtlLngsdGhpcy55PXQueStlLnksdGhpcy56PXQueitlLnosdGhpc31hZGRTY2FsZWRWZWN0b3IodCxlKXtyZXR1cm4gdGhpcy54Kz10LngqZSx0aGlzLnkrPXQueSplLHRoaXMueis9dC56KmUsdGhpc31zdWIodCxlKXtyZXR1cm4gdm9pZCAwIT09ZT8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuc3ViKCkgbm93IG9ubHkgYWNjZXB0cyBvbmUgYXJndW1lbnQuIFVzZSAuc3ViVmVjdG9ycyggYSwgYiApIGluc3RlYWQuIiksdGhpcy5zdWJWZWN0b3JzKHQsZSkpOih0aGlzLngtPXQueCx0aGlzLnktPXQueSx0aGlzLnotPXQueix0aGlzKX1zdWJTY2FsYXIodCl7cmV0dXJuIHRoaXMueC09dCx0aGlzLnktPXQsdGhpcy56LT10LHRoaXN9c3ViVmVjdG9ycyh0LGUpe3JldHVybiB0aGlzLng9dC54LWUueCx0aGlzLnk9dC55LWUueSx0aGlzLno9dC56LWUueix0aGlzfW11bHRpcGx5KHQsZSl7cmV0dXJuIHZvaWQgMCE9PWU/KGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogLm11bHRpcGx5KCkgbm93IG9ubHkgYWNjZXB0cyBvbmUgYXJndW1lbnQuIFVzZSAubXVsdGlwbHlWZWN0b3JzKCBhLCBiICkgaW5zdGVhZC4iKSx0aGlzLm11bHRpcGx5VmVjdG9ycyh0LGUpKToodGhpcy54Kj10LngsdGhpcy55Kj10LnksdGhpcy56Kj10LnosdGhpcyl9bXVsdGlwbHlTY2FsYXIodCl7cmV0dXJuIHRoaXMueCo9dCx0aGlzLnkqPXQsdGhpcy56Kj10LHRoaXN9bXVsdGlwbHlWZWN0b3JzKHQsZSl7cmV0dXJuIHRoaXMueD10LngqZS54LHRoaXMueT10LnkqZS55LHRoaXMuej10LnoqZS56LHRoaXN9YXBwbHlFdWxlcih0KXtyZXR1cm4gdCYmdC5pc0V1bGVyfHxjb25zb2xlLmVycm9yKCJUSFJFRS5WZWN0b3IzOiAuYXBwbHlFdWxlcigpIG5vdyBleHBlY3RzIGFuIEV1bGVyIHJvdGF0aW9uIHJhdGhlciB0aGFuIGEgVmVjdG9yMyBhbmQgb3JkZXIuIiksdGhpcy5hcHBseVF1YXRlcm5pb24oTXVlLnNldEZyb21FdWxlcih0KSl9YXBwbHlBeGlzQW5nbGUodCxlKXtyZXR1cm4gdGhpcy5hcHBseVF1YXRlcm5pb24oTXVlLnNldEZyb21BeGlzQW5nbGUodCxlKSl9YXBwbHlNYXRyaXgzKHQpe2xldCBlPXRoaXMueCxpPXRoaXMueSxyPXRoaXMueixvPXQuZWxlbWVudHM7cmV0dXJuIHRoaXMueD1vWzBdKmUrb1szXSppK29bNl0qcix0aGlzLnk9b1sxXSplK29bNF0qaStvWzddKnIsdGhpcy56PW9bMl0qZStvWzVdKmkrb1s4XSpyLHRoaXN9YXBwbHlOb3JtYWxNYXRyaXgodCl7cmV0dXJuIHRoaXMuYXBwbHlNYXRyaXgzKHQpLm5vcm1hbGl6ZSgpfWFwcGx5TWF0cml4NCh0KXtsZXQgZT10aGlzLngsaT10aGlzLnkscj10aGlzLnosbz10LmVsZW1lbnRzLHM9MS8ob1szXSplK29bN10qaStvWzExXSpyK29bMTVdKTtyZXR1cm4gdGhpcy54PShvWzBdKmUrb1s0XSppK29bOF0qcitvWzEyXSkqcyx0aGlzLnk9KG9bMV0qZStvWzVdKmkrb1s5XSpyK29bMTNdKSpzLHRoaXMuej0ob1syXSplK29bNl0qaStvWzEwXSpyK29bMTRdKSpzLHRoaXN9YXBwbHlRdWF0ZXJuaW9uKHQpe2xldCBlPXRoaXMueCxpPXRoaXMueSxyPXRoaXMueixvPXQueCxzPXQueSxhPXQueixsPXQudyxjPWwqZStzKnItYSppLHU9bCppK2EqZS1vKnIsZD1sKnIrbyppLXMqZSxwPS1vKmUtcyppLWEqcjtyZXR1cm4gdGhpcy54PWMqbCtwKi1vK3UqLWEtZCotcyx0aGlzLnk9dSpsK3AqLXMrZCotby1jKi1hLHRoaXMuej1kKmwrcCotYStjKi1zLXUqLW8sdGhpc31wcm9qZWN0KHQpe3JldHVybiB0aGlzLmFwcGx5TWF0cml4NCh0Lm1hdHJpeFdvcmxkSW52ZXJzZSkuYXBwbHlNYXRyaXg0KHQucHJvamVjdGlvbk1hdHJpeCl9dW5wcm9qZWN0KHQpe3JldHVybiB0aGlzLmFwcGx5TWF0cml4NCh0LnByb2plY3Rpb25NYXRyaXhJbnZlcnNlKS5hcHBseU1hdHJpeDQodC5tYXRyaXhXb3JsZCl9dHJhbnNmb3JtRGlyZWN0aW9uKHQpe2xldCBlPXRoaXMueCxpPXRoaXMueSxyPXRoaXMueixvPXQuZWxlbWVudHM7cmV0dXJuIHRoaXMueD1vWzBdKmUrb1s0XSppK29bOF0qcix0aGlzLnk9b1sxXSplK29bNV0qaStvWzldKnIsdGhpcy56PW9bMl0qZStvWzZdKmkrb1sxMF0qcix0aGlzLm5vcm1hbGl6ZSgpfWRpdmlkZSh0KXtyZXR1cm4gdGhpcy54Lz10LngsdGhpcy55Lz10LnksdGhpcy56Lz10LnosdGhpc31kaXZpZGVTY2FsYXIodCl7cmV0dXJuIHRoaXMubXVsdGlwbHlTY2FsYXIoMS90KX1taW4odCl7cmV0dXJuIHRoaXMueD1NYXRoLm1pbih0aGlzLngsdC54KSx0aGlzLnk9TWF0aC5taW4odGhpcy55LHQueSksdGhpcy56PU1hdGgubWluKHRoaXMueix0LnopLHRoaXN9bWF4KHQpe3JldHVybiB0aGlzLng9TWF0aC5tYXgodGhpcy54LHQueCksdGhpcy55PU1hdGgubWF4KHRoaXMueSx0LnkpLHRoaXMuej1NYXRoLm1heCh0aGlzLnosdC56KSx0aGlzfWNsYW1wKHQsZSl7cmV0dXJuIHRoaXMueD1NYXRoLm1heCh0LngsTWF0aC5taW4oZS54LHRoaXMueCkpLHRoaXMueT1NYXRoLm1heCh0LnksTWF0aC5taW4oZS55LHRoaXMueSkpLHRoaXMuej1NYXRoLm1heCh0LnosTWF0aC5taW4oZS56LHRoaXMueikpLHRoaXN9Y2xhbXBTY2FsYXIodCxlKXtyZXR1cm4gdGhpcy54PU1hdGgubWF4KHQsTWF0aC5taW4oZSx0aGlzLngpKSx0aGlzLnk9TWF0aC5tYXgodCxNYXRoLm1pbihlLHRoaXMueSkpLHRoaXMuej1NYXRoLm1heCh0LE1hdGgubWluKGUsdGhpcy56KSksdGhpc31jbGFtcExlbmd0aCh0LGUpe2xldCBpPXRoaXMubGVuZ3RoKCk7cmV0dXJuIHRoaXMuZGl2aWRlU2NhbGFyKGl8fDEpLm11bHRpcGx5U2NhbGFyKE1hdGgubWF4KHQsTWF0aC5taW4oZSxpKSkpfWZsb29yKCl7cmV0dXJuIHRoaXMueD1NYXRoLmZsb29yKHRoaXMueCksdGhpcy55PU1hdGguZmxvb3IodGhpcy55KSx0aGlzLno9TWF0aC5mbG9vcih0aGlzLnopLHRoaXN9Y2VpbCgpe3JldHVybiB0aGlzLng9TWF0aC5jZWlsKHRoaXMueCksdGhpcy55PU1hdGguY2VpbCh0aGlzLnkpLHRoaXMuej1NYXRoLmNlaWwodGhpcy56KSx0aGlzfXJvdW5kKCl7cmV0dXJuIHRoaXMueD1NYXRoLnJvdW5kKHRoaXMueCksdGhpcy55PU1hdGgucm91bmQodGhpcy55KSx0aGlzLno9TWF0aC5yb3VuZCh0aGlzLnopLHRoaXN9cm91bmRUb1plcm8oKXtyZXR1cm4gdGhpcy54PXRoaXMueDwwP01hdGguY2VpbCh0aGlzLngpOk1hdGguZmxvb3IodGhpcy54KSx0aGlzLnk9dGhpcy55PDA/TWF0aC5jZWlsKHRoaXMueSk6TWF0aC5mbG9vcih0aGlzLnkpLHRoaXMuej10aGlzLno8MD9NYXRoLmNlaWwodGhpcy56KTpNYXRoLmZsb29yKHRoaXMueiksdGhpc31uZWdhdGUoKXtyZXR1cm4gdGhpcy54PS10aGlzLngsdGhpcy55PS10aGlzLnksdGhpcy56PS10aGlzLnosdGhpc31kb3QodCl7cmV0dXJuIHRoaXMueCp0LngrdGhpcy55KnQueSt0aGlzLnoqdC56fWxlbmd0aFNxKCl7cmV0dXJuIHRoaXMueCp0aGlzLngrdGhpcy55KnRoaXMueSt0aGlzLnoqdGhpcy56fWxlbmd0aCgpe3JldHVybiBNYXRoLnNxcnQodGhpcy54KnRoaXMueCt0aGlzLnkqdGhpcy55K3RoaXMueip0aGlzLnopfW1hbmhhdHRhbkxlbmd0aCgpe3JldHVybiBNYXRoLmFicyh0aGlzLngpK01hdGguYWJzKHRoaXMueSkrTWF0aC5hYnModGhpcy56KX1ub3JtYWxpemUoKXtyZXR1cm4gdGhpcy5kaXZpZGVTY2FsYXIodGhpcy5sZW5ndGgoKXx8MSl9c2V0TGVuZ3RoKHQpe3JldHVybiB0aGlzLm5vcm1hbGl6ZSgpLm11bHRpcGx5U2NhbGFyKHQpfWxlcnAodCxlKXtyZXR1cm4gdGhpcy54Kz0odC54LXRoaXMueCkqZSx0aGlzLnkrPSh0LnktdGhpcy55KSplLHRoaXMueis9KHQuei10aGlzLnopKmUsdGhpc31sZXJwVmVjdG9ycyh0LGUsaSl7cmV0dXJuIHRoaXMueD10LngrKGUueC10LngpKmksdGhpcy55PXQueSsoZS55LXQueSkqaSx0aGlzLno9dC56KyhlLnotdC56KSppLHRoaXN9Y3Jvc3ModCxlKXtyZXR1cm4gdm9pZCAwIT09ZT8oY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuY3Jvc3MoKSBub3cgb25seSBhY2NlcHRzIG9uZSBhcmd1bWVudC4gVXNlIC5jcm9zc1ZlY3RvcnMoIGEsIGIgKSBpbnN0ZWFkLiIpLHRoaXMuY3Jvc3NWZWN0b3JzKHQsZSkpOnRoaXMuY3Jvc3NWZWN0b3JzKHRoaXMsdCl9Y3Jvc3NWZWN0b3JzKHQsZSl7bGV0IGk9dC54LHI9dC55LG89dC56LHM9ZS54LGE9ZS55LGw9ZS56O3JldHVybiB0aGlzLng9cipsLW8qYSx0aGlzLnk9bypzLWkqbCx0aGlzLno9aSphLXIqcyx0aGlzfXByb2plY3RPblZlY3Rvcih0KXtsZXQgZT10Lmxlbmd0aFNxKCk7aWYoMD09PWUpcmV0dXJuIHRoaXMuc2V0KDAsMCwwKTtsZXQgaT10LmRvdCh0aGlzKS9lO3JldHVybiB0aGlzLmNvcHkodCkubXVsdGlwbHlTY2FsYXIoaSl9cHJvamVjdE9uUGxhbmUodCl7cmV0dXJuIE9qLmNvcHkodGhpcykucHJvamVjdE9uVmVjdG9yKHQpLHRoaXMuc3ViKE9qKX1yZWZsZWN0KHQpe3JldHVybiB0aGlzLnN1YihPai5jb3B5KHQpLm11bHRpcGx5U2NhbGFyKDIqdGhpcy5kb3QodCkpKX1hbmdsZVRvKHQpe2xldCBlPU1hdGguc3FydCh0aGlzLmxlbmd0aFNxKCkqdC5sZW5ndGhTcSgpKTtpZigwPT09ZSlyZXR1cm4gTWF0aC5QSS8yO2xldCBpPXRoaXMuZG90KHQpL2U7cmV0dXJuIE1hdGguYWNvcyhHYShpLC0xLDEpKX1kaXN0YW5jZVRvKHQpe3JldHVybiBNYXRoLnNxcnQodGhpcy5kaXN0YW5jZVRvU3F1YXJlZCh0KSl9ZGlzdGFuY2VUb1NxdWFyZWQodCl7bGV0IGU9dGhpcy54LXQueCxpPXRoaXMueS10Lnkscj10aGlzLnotdC56O3JldHVybiBlKmUraSppK3Iqcn1tYW5oYXR0YW5EaXN0YW5jZVRvKHQpe3JldHVybiBNYXRoLmFicyh0aGlzLngtdC54KStNYXRoLmFicyh0aGlzLnktdC55KStNYXRoLmFicyh0aGlzLnotdC56KX1zZXRGcm9tU3BoZXJpY2FsKHQpe3JldHVybiB0aGlzLnNldEZyb21TcGhlcmljYWxDb29yZHModC5yYWRpdXMsdC5waGksdC50aGV0YSl9c2V0RnJvbVNwaGVyaWNhbENvb3Jkcyh0LGUsaSl7bGV0IHI9TWF0aC5zaW4oZSkqdDtyZXR1cm4gdGhpcy54PXIqTWF0aC5zaW4oaSksdGhpcy55PU1hdGguY29zKGUpKnQsdGhpcy56PXIqTWF0aC5jb3MoaSksdGhpc31zZXRGcm9tQ3lsaW5kcmljYWwodCl7cmV0dXJuIHRoaXMuc2V0RnJvbUN5bGluZHJpY2FsQ29vcmRzKHQucmFkaXVzLHQudGhldGEsdC55KX1zZXRGcm9tQ3lsaW5kcmljYWxDb29yZHModCxlLGkpe3JldHVybiB0aGlzLng9dCpNYXRoLnNpbihlKSx0aGlzLnk9aSx0aGlzLno9dCpNYXRoLmNvcyhlKSx0aGlzfXNldEZyb21NYXRyaXhQb3NpdGlvbih0KXtsZXQgZT10LmVsZW1lbnRzO3JldHVybiB0aGlzLng9ZVsxMl0sdGhpcy55PWVbMTNdLHRoaXMuej1lWzE0XSx0aGlzfXNldEZyb21NYXRyaXhTY2FsZSh0KXtsZXQgZT10aGlzLnNldEZyb21NYXRyaXhDb2x1bW4odCwwKS5sZW5ndGgoKSxpPXRoaXMuc2V0RnJvbU1hdHJpeENvbHVtbih0LDEpLmxlbmd0aCgpLHI9dGhpcy5zZXRGcm9tTWF0cml4Q29sdW1uKHQsMikubGVuZ3RoKCk7cmV0dXJuIHRoaXMueD1lLHRoaXMueT1pLHRoaXMuej1yLHRoaXN9c2V0RnJvbU1hdHJpeENvbHVtbih0LGUpe3JldHVybiB0aGlzLmZyb21BcnJheSh0LmVsZW1lbnRzLDQqZSl9c2V0RnJvbU1hdHJpeDNDb2x1bW4odCxlKXtyZXR1cm4gdGhpcy5mcm9tQXJyYXkodC5lbGVtZW50cywzKmUpfWVxdWFscyh0KXtyZXR1cm4gdC54PT09dGhpcy54JiZ0Lnk9PT10aGlzLnkmJnQuej09PXRoaXMuen1mcm9tQXJyYXkodCxlPTApe3JldHVybiB0aGlzLng9dFtlXSx0aGlzLnk9dFtlKzFdLHRoaXMuej10W2UrMl0sdGhpc310b0FycmF5KHQ9W10sZT0wKXtyZXR1cm4gdFtlXT10aGlzLngsdFtlKzFdPXRoaXMueSx0W2UrMl09dGhpcy56LHR9ZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LGUsaSl7cmV0dXJuIHZvaWQgMCE9PWkmJmNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogb2Zmc2V0IGhhcyBiZWVuIHJlbW92ZWQgZnJvbSAuZnJvbUJ1ZmZlckF0dHJpYnV0ZSgpLiIpLHRoaXMueD10LmdldFgoZSksdGhpcy55PXQuZ2V0WShlKSx0aGlzLno9dC5nZXRaKGUpLHRoaXN9cmFuZG9tKCl7cmV0dXJuIHRoaXMueD1NYXRoLnJhbmRvbSgpLHRoaXMueT1NYXRoLnJhbmRvbSgpLHRoaXMuej1NYXRoLnJhbmRvbSgpLHRoaXN9cmFuZG9tRGlyZWN0aW9uKCl7bGV0IHQ9MiooTWF0aC5yYW5kb20oKS0uNSksZT1NYXRoLnJhbmRvbSgpKk1hdGguUEkqMixpPU1hdGguc3FydCgxLXQqKjIpO3JldHVybiB0aGlzLng9aSpNYXRoLmNvcyhlKSx0aGlzLnk9aSpNYXRoLnNpbihlKSx0aGlzLno9dCx0aGlzfSpbU3ltYm9sLml0ZXJhdG9yXSgpe3lpZWxkIHRoaXMueCx5aWVsZCB0aGlzLnkseWllbGQgdGhpcy56fX07aWUucHJvdG90eXBlLmlzVmVjdG9yMz0hMDt2YXIgT2o9bmV3IGllLE11ZT1uZXcgcXMsVGw9Y2xhc3N7Y29uc3RydWN0b3IodD1uZXcgaWUoMS8wLDEvMCwxLzApLGU9bmV3IGllKC0xLzAsLTEvMCwtMS8wKSl7dGhpcy5taW49dCx0aGlzLm1heD1lfXNldCh0LGUpe3JldHVybiB0aGlzLm1pbi5jb3B5KHQpLHRoaXMubWF4LmNvcHkoZSksdGhpc31zZXRGcm9tQXJyYXkodCl7bGV0IGU9MS8wLGk9MS8wLHI9MS8wLG89LTEvMCxzPS0xLzAsYT0tMS8wO2ZvcihsZXQgbD0wLGM9dC5sZW5ndGg7bDxjO2wrPTMpe2xldCB1PXRbbF0sZD10W2wrMV0scD10W2wrMl07dTxlJiYoZT11KSxkPGkmJihpPWQpLHA8ciYmKHI9cCksdT5vJiYobz11KSxkPnMmJihzPWQpLHA+YSYmKGE9cCl9cmV0dXJuIHRoaXMubWluLnNldChlLGksciksdGhpcy5tYXguc2V0KG8scyxhKSx0aGlzfXNldEZyb21CdWZmZXJBdHRyaWJ1dGUodCl7bGV0IGU9MS8wLGk9MS8wLHI9MS8wLG89LTEvMCxzPS0xLzAsYT0tMS8wO2ZvcihsZXQgbD0wLGM9dC5jb3VudDtsPGM7bCsrKXtsZXQgdT10LmdldFgobCksZD10LmdldFkobCkscD10LmdldFoobCk7dTxlJiYoZT11KSxkPGkmJihpPWQpLHA8ciYmKHI9cCksdT5vJiYobz11KSxkPnMmJihzPWQpLHA+YSYmKGE9cCl9cmV0dXJuIHRoaXMubWluLnNldChlLGksciksdGhpcy5tYXguc2V0KG8scyxhKSx0aGlzfXNldEZyb21Qb2ludHModCl7dGhpcy5tYWtlRW1wdHkoKTtmb3IobGV0IGU9MCxpPXQubGVuZ3RoO2U8aTtlKyspdGhpcy5leHBhbmRCeVBvaW50KHRbZV0pO3JldHVybiB0aGlzfXNldEZyb21DZW50ZXJBbmRTaXplKHQsZSl7bGV0IGk9TGcuY29weShlKS5tdWx0aXBseVNjYWxhciguNSk7cmV0dXJuIHRoaXMubWluLmNvcHkodCkuc3ViKGkpLHRoaXMubWF4LmNvcHkodCkuYWRkKGkpLHRoaXN9c2V0RnJvbU9iamVjdCh0LGU9ITEpe3JldHVybiB0aGlzLm1ha2VFbXB0eSgpLHRoaXMuZXhwYW5kQnlPYmplY3QodCxlKX1jbG9uZSgpe3JldHVybihuZXcgdGhpcy5jb25zdHJ1Y3RvcikuY29weSh0aGlzKX1jb3B5KHQpe3JldHVybiB0aGlzLm1pbi5jb3B5KHQubWluKSx0aGlzLm1heC5jb3B5KHQubWF4KSx0aGlzfW1ha2VFbXB0eSgpe3JldHVybiB0aGlzLm1pbi54PXRoaXMubWluLnk9dGhpcy5taW4uej0xLzAsdGhpcy5tYXgueD10aGlzLm1heC55PXRoaXMubWF4Lno9LTEvMCx0aGlzfWlzRW1wdHkoKXtyZXR1cm4gdGhpcy5tYXgueDx0aGlzLm1pbi54fHx0aGlzLm1heC55PHRoaXMubWluLnl8fHRoaXMubWF4Lno8dGhpcy5taW4uen1nZXRDZW50ZXIodCl7cmV0dXJuIHRoaXMuaXNFbXB0eSgpP3Quc2V0KDAsMCwwKTp0LmFkZFZlY3RvcnModGhpcy5taW4sdGhpcy5tYXgpLm11bHRpcGx5U2NhbGFyKC41KX1nZXRTaXplKHQpe3JldHVybiB0aGlzLmlzRW1wdHkoKT90LnNldCgwLDAsMCk6dC5zdWJWZWN0b3JzKHRoaXMubWF4LHRoaXMubWluKX1leHBhbmRCeVBvaW50KHQpe3JldHVybiB0aGlzLm1pbi5taW4odCksdGhpcy5tYXgubWF4KHQpLHRoaXN9ZXhwYW5kQnlWZWN0b3IodCl7cmV0dXJuIHRoaXMubWluLnN1Yih0KSx0aGlzLm1heC5hZGQodCksdGhpc31leHBhbmRCeVNjYWxhcih0KXtyZXR1cm4gdGhpcy5taW4uYWRkU2NhbGFyKC10KSx0aGlzLm1heC5hZGRTY2FsYXIodCksdGhpc31leHBhbmRCeU9iamVjdCh0LGU9ITEpe3QudXBkYXRlV29ybGRNYXRyaXgoITEsITEpO2xldCBpPXQuZ2VvbWV0cnk7aWYodm9pZCAwIT09aSlpZihlJiZudWxsIT1pLmF0dHJpYnV0ZXMmJnZvaWQgMCE9PWkuYXR0cmlidXRlcy5wb3NpdGlvbil7bGV0IG89aS5hdHRyaWJ1dGVzLnBvc2l0aW9uO2ZvcihsZXQgcz0wLGE9by5jb3VudDtzPGE7cysrKUxnLmZyb21CdWZmZXJBdHRyaWJ1dGUobyxzKS5hcHBseU1hdHJpeDQodC5tYXRyaXhXb3JsZCksdGhpcy5leHBhbmRCeVBvaW50KExnKX1lbHNlIG51bGw9PT1pLmJvdW5kaW5nQm94JiZpLmNvbXB1dGVCb3VuZGluZ0JveCgpLGtqLmNvcHkoaS5ib3VuZGluZ0JveCksa2ouYXBwbHlNYXRyaXg0KHQubWF0cml4V29ybGQpLHRoaXMudW5pb24oa2opO2xldCByPXQuY2hpbGRyZW47Zm9yKGxldCBvPTAscz1yLmxlbmd0aDtvPHM7bysrKXRoaXMuZXhwYW5kQnlPYmplY3QocltvXSxlKTtyZXR1cm4gdGhpc31jb250YWluc1BvaW50KHQpe3JldHVybiEodC54PHRoaXMubWluLnh8fHQueD50aGlzLm1heC54fHx0Lnk8dGhpcy5taW4ueXx8dC55PnRoaXMubWF4Lnl8fHQuejx0aGlzLm1pbi56fHx0Lno+dGhpcy5tYXgueil9Y29udGFpbnNCb3godCl7cmV0dXJuIHRoaXMubWluLng8PXQubWluLngmJnQubWF4Lng8PXRoaXMubWF4LngmJnRoaXMubWluLnk8PXQubWluLnkmJnQubWF4Lnk8PXRoaXMubWF4LnkmJnRoaXMubWluLno8PXQubWluLnomJnQubWF4Lno8PXRoaXMubWF4Lnp9Z2V0UGFyYW1ldGVyKHQsZSl7cmV0dXJuIGUuc2V0KCh0LngtdGhpcy5taW4ueCkvKHRoaXMubWF4LngtdGhpcy5taW4ueCksKHQueS10aGlzLm1pbi55KS8odGhpcy5tYXgueS10aGlzLm1pbi55KSwodC56LXRoaXMubWluLnopLyh0aGlzLm1heC56LXRoaXMubWluLnopKX1pbnRlcnNlY3RzQm94KHQpe3JldHVybiEodC5tYXgueDx0aGlzLm1pbi54fHx0Lm1pbi54PnRoaXMubWF4Lnh8fHQubWF4Lnk8dGhpcy5taW4ueXx8dC5taW4ueT50aGlzLm1heC55fHx0Lm1heC56PHRoaXMubWluLnp8fHQubWluLno+dGhpcy5tYXgueil9aW50ZXJzZWN0c1NwaGVyZSh0KXtyZXR1cm4gdGhpcy5jbGFtcFBvaW50KHQuY2VudGVyLExnKSxMZy5kaXN0YW5jZVRvU3F1YXJlZCh0LmNlbnRlcik8PXQucmFkaXVzKnQucmFkaXVzfWludGVyc2VjdHNQbGFuZSh0KXtsZXQgZSxpO3JldHVybiB0Lm5vcm1hbC54PjA/KGU9dC5ub3JtYWwueCp0aGlzLm1pbi54LGk9dC5ub3JtYWwueCp0aGlzLm1heC54KTooZT10Lm5vcm1hbC54KnRoaXMubWF4LngsaT10Lm5vcm1hbC54KnRoaXMubWluLngpLHQubm9ybWFsLnk+MD8oZSs9dC5ub3JtYWwueSp0aGlzLm1pbi55LGkrPXQubm9ybWFsLnkqdGhpcy5tYXgueSk6KGUrPXQubm9ybWFsLnkqdGhpcy5tYXgueSxpKz10Lm5vcm1hbC55KnRoaXMubWluLnkpLHQubm9ybWFsLno+MD8oZSs9dC5ub3JtYWwueip0aGlzLm1pbi56LGkrPXQubm9ybWFsLnoqdGhpcy5tYXgueik6KGUrPXQubm9ybWFsLnoqdGhpcy5tYXgueixpKz10Lm5vcm1hbC56KnRoaXMubWluLnopLGU8PS10LmNvbnN0YW50JiZpPj0tdC5jb25zdGFudH1pbnRlcnNlY3RzVHJpYW5nbGUodCl7aWYodGhpcy5pc0VtcHR5KCkpcmV0dXJuITE7dGhpcy5nZXRDZW50ZXIoQVMpLE1PLnN1YlZlY3RvcnModGhpcy5tYXgsQVMpLEh5LnN1YlZlY3RvcnModC5hLEFTKSxVeS5zdWJWZWN0b3JzKHQuYixBUyksenkuc3ViVmVjdG9ycyh0LmMsQVMpLGFmLnN1YlZlY3RvcnMoVXksSHkpLGxmLnN1YlZlY3RvcnMoenksVXkpLEJnLnN1YlZlY3RvcnMoSHksenkpO2xldCBlPVswLC1hZi56LGFmLnksMCwtbGYueixsZi55LDAsLUJnLnosQmcueSxhZi56LDAsLWFmLngsbGYueiwwLC1sZi54LEJnLnosMCwtQmcueCwtYWYueSxhZi54LDAsLWxmLnksbGYueCwwLC1CZy55LEJnLngsMF07cmV0dXJuISghRmooZSxIeSxVeSx6eSxNTyl8fChlPVsxLDAsMCwwLDEsMCwwLDAsMV0sIUZqKGUsSHksVXksenksTU8pKSkmJih3Ty5jcm9zc1ZlY3RvcnMoYWYsbGYpLGU9W3dPLngsd08ueSx3Ty56XSxGaihlLEh5LFV5LHp5LE1PKSl9Y2xhbXBQb2ludCh0LGUpe3JldHVybiBlLmNvcHkodCkuY2xhbXAodGhpcy5taW4sdGhpcy5tYXgpfWRpc3RhbmNlVG9Qb2ludCh0KXtyZXR1cm4gTGcuY29weSh0KS5jbGFtcCh0aGlzLm1pbix0aGlzLm1heCkuc3ViKHQpLmxlbmd0aCgpfWdldEJvdW5kaW5nU3BoZXJlKHQpe3JldHVybiB0aGlzLmdldENlbnRlcih0LmNlbnRlciksdC5yYWRpdXM9LjUqdGhpcy5nZXRTaXplKExnKS5sZW5ndGgoKSx0fWludGVyc2VjdCh0KXtyZXR1cm4gdGhpcy5taW4ubWF4KHQubWluKSx0aGlzLm1heC5taW4odC5tYXgpLHRoaXMuaXNFbXB0eSgpJiZ0aGlzLm1ha2VFbXB0eSgpLHRoaXN9dW5pb24odCl7cmV0dXJuIHRoaXMubWluLm1pbih0Lm1pbiksdGhpcy5tYXgubWF4KHQubWF4KSx0aGlzfWFwcGx5TWF0cml4NCh0KXtyZXR1cm4gdGhpcy5pc0VtcHR5KCl8fChicFswXS5zZXQodGhpcy5taW4ueCx0aGlzLm1pbi55LHRoaXMubWluLnopLmFwcGx5TWF0cml4NCh0KSxicFsxXS5zZXQodGhpcy5taW4ueCx0aGlzLm1pbi55LHRoaXMubWF4LnopLmFwcGx5TWF0cml4NCh0KSxicFsyXS5zZXQodGhpcy5taW4ueCx0aGlzLm1heC55LHRoaXMubWluLnopLmFwcGx5TWF0cml4NCh0KSxicFszXS5zZXQodGhpcy5taW4ueCx0aGlzLm1heC55LHRoaXMubWF4LnopLmFwcGx5TWF0cml4NCh0KSxicFs0XS5zZXQodGhpcy5tYXgueCx0aGlzLm1pbi55LHRoaXMubWluLnopLmFwcGx5TWF0cml4NCh0KSxicFs1XS5zZXQodGhpcy5tYXgueCx0aGlzLm1pbi55LHRoaXMubWF4LnopLmFwcGx5TWF0cml4NCh0KSxicFs2XS5zZXQodGhpcy5tYXgueCx0aGlzLm1heC55LHRoaXMubWluLnopLmFwcGx5TWF0cml4NCh0KSxicFs3XS5zZXQodGhpcy5tYXgueCx0aGlzLm1heC55LHRoaXMubWF4LnopLmFwcGx5TWF0cml4NCh0KSx0aGlzLnNldEZyb21Qb2ludHMoYnApKSx0aGlzfXRyYW5zbGF0ZSh0KXtyZXR1cm4gdGhpcy5taW4uYWRkKHQpLHRoaXMubWF4LmFkZCh0KSx0aGlzfWVxdWFscyh0KXtyZXR1cm4gdC5taW4uZXF1YWxzKHRoaXMubWluKSYmdC5tYXguZXF1YWxzKHRoaXMubWF4KX19O1RsLnByb3RvdHlwZS5pc0JveDM9ITA7dmFyIGJwPVtuZXcgaWUsbmV3IGllLG5ldyBpZSxuZXcgaWUsbmV3IGllLG5ldyBpZSxuZXcgaWUsbmV3IGllXSxMZz1uZXcgaWUsa2o9bmV3IFRsLEh5PW5ldyBpZSxVeT1uZXcgaWUsenk9bmV3IGllLGFmPW5ldyBpZSxsZj1uZXcgaWUsQmc9bmV3IGllLEFTPW5ldyBpZSxNTz1uZXcgaWUsd089bmV3IGllLFZnPW5ldyBpZTtmdW5jdGlvbiBGaihuLHQsZSxpLHIpe2ZvcihsZXQgbz0wLHM9bi5sZW5ndGgtMztvPD1zO28rPTMpe1ZnLmZyb21BcnJheShuLG8pO2xldCBhPXIueCpNYXRoLmFicyhWZy54KStyLnkqTWF0aC5hYnMoVmcueSkrci56Kk1hdGguYWJzKFZnLnopLGw9dC5kb3QoVmcpLGM9ZS5kb3QoVmcpLHU9aS5kb3QoVmcpO2lmKE1hdGgubWF4KC1NYXRoLm1heChsLGMsdSksTWF0aC5taW4obCxjLHUpKT5hKXJldHVybiExfXJldHVybiEwfXZhciB6OGU9bmV3IFRsLHd1ZT1uZXcgaWUsU089bmV3IGllLE5qPW5ldyBpZSx4Zj1jbGFzc3tjb25zdHJ1Y3Rvcih0PW5ldyBpZSxlPS0xKXt0aGlzLmNlbnRlcj10LHRoaXMucmFkaXVzPWV9c2V0KHQsZSl7cmV0dXJuIHRoaXMuY2VudGVyLmNvcHkodCksdGhpcy5yYWRpdXM9ZSx0aGlzfXNldEZyb21Qb2ludHModCxlKXtsZXQgaT10aGlzLmNlbnRlcjt2b2lkIDAhPT1lP2kuY29weShlKTp6OGUuc2V0RnJvbVBvaW50cyh0KS5nZXRDZW50ZXIoaSk7bGV0IHI9MDtmb3IobGV0IG89MCxzPXQubGVuZ3RoO288cztvKyspcj1NYXRoLm1heChyLGkuZGlzdGFuY2VUb1NxdWFyZWQodFtvXSkpO3JldHVybiB0aGlzLnJhZGl1cz1NYXRoLnNxcnQociksdGhpc31jb3B5KHQpe3JldHVybiB0aGlzLmNlbnRlci5jb3B5KHQuY2VudGVyKSx0aGlzLnJhZGl1cz10LnJhZGl1cyx0aGlzfWlzRW1wdHkoKXtyZXR1cm4gdGhpcy5yYWRpdXM8MH1tYWtlRW1wdHkoKXtyZXR1cm4gdGhpcy5jZW50ZXIuc2V0KDAsMCwwKSx0aGlzLnJhZGl1cz0tMSx0aGlzfWNvbnRhaW5zUG9pbnQodCl7cmV0dXJuIHQuZGlzdGFuY2VUb1NxdWFyZWQodGhpcy5jZW50ZXIpPD10aGlzLnJhZGl1cyp0aGlzLnJhZGl1c31kaXN0YW5jZVRvUG9pbnQodCl7cmV0dXJuIHQuZGlzdGFuY2VUbyh0aGlzLmNlbnRlciktdGhpcy5yYWRpdXN9aW50ZXJzZWN0c1NwaGVyZSh0KXtsZXQgZT10aGlzLnJhZGl1cyt0LnJhZGl1cztyZXR1cm4gdC5jZW50ZXIuZGlzdGFuY2VUb1NxdWFyZWQodGhpcy5jZW50ZXIpPD1lKmV9aW50ZXJzZWN0c0JveCh0KXtyZXR1cm4gdC5pbnRlcnNlY3RzU3BoZXJlKHRoaXMpfWludGVyc2VjdHNQbGFuZSh0KXtyZXR1cm4gTWF0aC5hYnModC5kaXN0YW5jZVRvUG9pbnQodGhpcy5jZW50ZXIpKTw9dGhpcy5yYWRpdXN9Y2xhbXBQb2ludCh0LGUpe2xldCBpPXRoaXMuY2VudGVyLmRpc3RhbmNlVG9TcXVhcmVkKHQpO3JldHVybiBlLmNvcHkodCksaT50aGlzLnJhZGl1cyp0aGlzLnJhZGl1cyYmKGUuc3ViKHRoaXMuY2VudGVyKS5ub3JtYWxpemUoKSxlLm11bHRpcGx5U2NhbGFyKHRoaXMucmFkaXVzKS5hZGQodGhpcy5jZW50ZXIpKSxlfWdldEJvdW5kaW5nQm94KHQpe3JldHVybiB0aGlzLmlzRW1wdHkoKT8odC5tYWtlRW1wdHkoKSx0KToodC5zZXQodGhpcy5jZW50ZXIsdGhpcy5jZW50ZXIpLHQuZXhwYW5kQnlTY2FsYXIodGhpcy5yYWRpdXMpLHQpfWFwcGx5TWF0cml4NCh0KXtyZXR1cm4gdGhpcy5jZW50ZXIuYXBwbHlNYXRyaXg0KHQpLHRoaXMucmFkaXVzPXRoaXMucmFkaXVzKnQuZ2V0TWF4U2NhbGVPbkF4aXMoKSx0aGlzfXRyYW5zbGF0ZSh0KXtyZXR1cm4gdGhpcy5jZW50ZXIuYWRkKHQpLHRoaXN9ZXhwYW5kQnlQb2ludCh0KXtOai5zdWJWZWN0b3JzKHQsdGhpcy5jZW50ZXIpO2xldCBlPU5qLmxlbmd0aFNxKCk7aWYoZT50aGlzLnJhZGl1cyp0aGlzLnJhZGl1cyl7bGV0IGk9TWF0aC5zcXJ0KGUpLHI9LjUqKGktdGhpcy5yYWRpdXMpO3RoaXMuY2VudGVyLmFkZChOai5tdWx0aXBseVNjYWxhcihyL2kpKSx0aGlzLnJhZGl1cys9cn1yZXR1cm4gdGhpc311bmlvbih0KXtyZXR1cm4hMD09PXRoaXMuY2VudGVyLmVxdWFscyh0LmNlbnRlcik/U08uc2V0KDAsMCwxKS5tdWx0aXBseVNjYWxhcih0LnJhZGl1cyk6U08uc3ViVmVjdG9ycyh0LmNlbnRlcix0aGlzLmNlbnRlcikubm9ybWFsaXplKCkubXVsdGlwbHlTY2FsYXIodC5yYWRpdXMpLHRoaXMuZXhwYW5kQnlQb2ludCh3dWUuY29weSh0LmNlbnRlcikuYWRkKFNPKSksdGhpcy5leHBhbmRCeVBvaW50KHd1ZS5jb3B5KHQuY2VudGVyKS5zdWIoU08pKSx0aGlzfWVxdWFscyh0KXtyZXR1cm4gdC5jZW50ZXIuZXF1YWxzKHRoaXMuY2VudGVyKSYmdC5yYWRpdXM9PT10aGlzLnJhZGl1c31jbG9uZSgpe3JldHVybihuZXcgdGhpcy5jb25zdHJ1Y3RvcikuY29weSh0aGlzKX19LHhwPW5ldyBpZSxMaj1uZXcgaWUsRU89bmV3IGllLGNmPW5ldyBpZSxCaj1uZXcgaWUsVE89bmV3IGllLFZqPW5ldyBpZSxDZj1jbGFzc3tjb25zdHJ1Y3Rvcih0PW5ldyBpZSxlPW5ldyBpZSgwLDAsLTEpKXt0aGlzLm9yaWdpbj10LHRoaXMuZGlyZWN0aW9uPWV9c2V0KHQsZSl7cmV0dXJuIHRoaXMub3JpZ2luLmNvcHkodCksdGhpcy5kaXJlY3Rpb24uY29weShlKSx0aGlzfWNvcHkodCl7cmV0dXJuIHRoaXMub3JpZ2luLmNvcHkodC5vcmlnaW4pLHRoaXMuZGlyZWN0aW9uLmNvcHkodC5kaXJlY3Rpb24pLHRoaXN9YXQodCxlKXtyZXR1cm4gZS5jb3B5KHRoaXMuZGlyZWN0aW9uKS5tdWx0aXBseVNjYWxhcih0KS5hZGQodGhpcy5vcmlnaW4pfWxvb2tBdCh0KXtyZXR1cm4gdGhpcy5kaXJlY3Rpb24uY29weSh0KS5zdWIodGhpcy5vcmlnaW4pLm5vcm1hbGl6ZSgpLHRoaXN9cmVjYXN0KHQpe3JldHVybiB0aGlzLm9yaWdpbi5jb3B5KHRoaXMuYXQodCx4cCkpLHRoaXN9Y2xvc2VzdFBvaW50VG9Qb2ludCh0LGUpe2Uuc3ViVmVjdG9ycyh0LHRoaXMub3JpZ2luKTtsZXQgaT1lLmRvdCh0aGlzLmRpcmVjdGlvbik7cmV0dXJuIGk8MD9lLmNvcHkodGhpcy5vcmlnaW4pOmUuY29weSh0aGlzLmRpcmVjdGlvbikubXVsdGlwbHlTY2FsYXIoaSkuYWRkKHRoaXMub3JpZ2luKX1kaXN0YW5jZVRvUG9pbnQodCl7cmV0dXJuIE1hdGguc3FydCh0aGlzLmRpc3RhbmNlU3FUb1BvaW50KHQpKX1kaXN0YW5jZVNxVG9Qb2ludCh0KXtsZXQgZT14cC5zdWJWZWN0b3JzKHQsdGhpcy5vcmlnaW4pLmRvdCh0aGlzLmRpcmVjdGlvbik7cmV0dXJuIGU8MD90aGlzLm9yaWdpbi5kaXN0YW5jZVRvU3F1YXJlZCh0KTooeHAuY29weSh0aGlzLmRpcmVjdGlvbikubXVsdGlwbHlTY2FsYXIoZSkuYWRkKHRoaXMub3JpZ2luKSx4cC5kaXN0YW5jZVRvU3F1YXJlZCh0KSl9ZGlzdGFuY2VTcVRvU2VnbWVudCh0LGUsaSxyKXtMai5jb3B5KHQpLmFkZChlKS5tdWx0aXBseVNjYWxhciguNSksRU8uY29weShlKS5zdWIodCkubm9ybWFsaXplKCksY2YuY29weSh0aGlzLm9yaWdpbikuc3ViKExqKTtsZXQgZCxwLGgsZixvPS41KnQuZGlzdGFuY2VUbyhlKSxzPS10aGlzLmRpcmVjdGlvbi5kb3QoRU8pLGE9Y2YuZG90KHRoaXMuZGlyZWN0aW9uKSxsPS1jZi5kb3QoRU8pLGM9Y2YubGVuZ3RoU3EoKSx1PU1hdGguYWJzKDEtcypzKTtpZih1PjApaWYoZD1zKmwtYSxwPXMqYS1sLGY9byp1LGQ+PTApaWYocD49LWYpaWYocDw9Zil7bGV0IG09MS91O2QqPW0scCo9bSxoPWQqKGQrcypwKzIqYSkrcCoocypkK3ArMipsKStjfWVsc2UgcD1vLGQ9TWF0aC5tYXgoMCwtKHMqcCthKSksaD0tZCpkK3AqKHArMipsKStjO2Vsc2UgcD0tbyxkPU1hdGgubWF4KDAsLShzKnArYSkpLGg9LWQqZCtwKihwKzIqbCkrYztlbHNlIHA8PS1mPyhkPU1hdGgubWF4KDAsLSgtcypvK2EpKSxwPWQ+MD8tbzpNYXRoLm1pbihNYXRoLm1heCgtbywtbCksbyksaD0tZCpkK3AqKHArMipsKStjKTpwPD1mPyhkPTAscD1NYXRoLm1pbihNYXRoLm1heCgtbywtbCksbyksaD1wKihwKzIqbCkrYyk6KGQ9TWF0aC5tYXgoMCwtKHMqbythKSkscD1kPjA/bzpNYXRoLm1pbihNYXRoLm1heCgtbywtbCksbyksaD0tZCpkK3AqKHArMipsKStjKTtlbHNlIHA9cz4wPy1vOm8sZD1NYXRoLm1heCgwLC0ocypwK2EpKSxoPS1kKmQrcCoocCsyKmwpK2M7cmV0dXJuIGkmJmkuY29weSh0aGlzLmRpcmVjdGlvbikubXVsdGlwbHlTY2FsYXIoZCkuYWRkKHRoaXMub3JpZ2luKSxyJiZyLmNvcHkoRU8pLm11bHRpcGx5U2NhbGFyKHApLmFkZChMaiksaH1pbnRlcnNlY3RTcGhlcmUodCxlKXt4cC5zdWJWZWN0b3JzKHQuY2VudGVyLHRoaXMub3JpZ2luKTtsZXQgaT14cC5kb3QodGhpcy5kaXJlY3Rpb24pLHI9eHAuZG90KHhwKS1pKmksbz10LnJhZGl1cyp0LnJhZGl1cztpZihyPm8pcmV0dXJuIG51bGw7bGV0IHM9TWF0aC5zcXJ0KG8tciksYT1pLXMsbD1pK3M7cmV0dXJuIGE8MCYmbDwwP251bGw6dGhpcy5hdChhPDA/bDphLGUpfWludGVyc2VjdHNTcGhlcmUodCl7cmV0dXJuIHRoaXMuZGlzdGFuY2VTcVRvUG9pbnQodC5jZW50ZXIpPD10LnJhZGl1cyp0LnJhZGl1c31kaXN0YW5jZVRvUGxhbmUodCl7bGV0IGU9dC5ub3JtYWwuZG90KHRoaXMuZGlyZWN0aW9uKTtpZigwPT09ZSlyZXR1cm4gMD09PXQuZGlzdGFuY2VUb1BvaW50KHRoaXMub3JpZ2luKT8wOm51bGw7bGV0IGk9LSh0aGlzLm9yaWdpbi5kb3QodC5ub3JtYWwpK3QuY29uc3RhbnQpL2U7cmV0dXJuIGk+PTA/aTpudWxsfWludGVyc2VjdFBsYW5lKHQsZSl7bGV0IGk9dGhpcy5kaXN0YW5jZVRvUGxhbmUodCk7cmV0dXJuIG51bGw9PT1pP251bGw6dGhpcy5hdChpLGUpfWludGVyc2VjdHNQbGFuZSh0KXtsZXQgZT10LmRpc3RhbmNlVG9Qb2ludCh0aGlzLm9yaWdpbik7cmV0dXJuIDA9PT1lfHx0Lm5vcm1hbC5kb3QodGhpcy5kaXJlY3Rpb24pKmU8MH1pbnRlcnNlY3RCb3godCxlKXtsZXQgaSxyLG8scyxhLGwsYz0xL3RoaXMuZGlyZWN0aW9uLngsdT0xL3RoaXMuZGlyZWN0aW9uLnksZD0xL3RoaXMuZGlyZWN0aW9uLnoscD10aGlzLm9yaWdpbjtyZXR1cm4gYz49MD8oaT0odC5taW4ueC1wLngpKmMscj0odC5tYXgueC1wLngpKmMpOihpPSh0Lm1heC54LXAueCkqYyxyPSh0Lm1pbi54LXAueCkqYyksdT49MD8obz0odC5taW4ueS1wLnkpKnUscz0odC5tYXgueS1wLnkpKnUpOihvPSh0Lm1heC55LXAueSkqdSxzPSh0Lm1pbi55LXAueSkqdSksaT5zfHxvPnJ8fCgobz5pfHxpIT1pKSYmKGk9byksKHM8cnx8ciE9cikmJihyPXMpLGQ+PTA/KGE9KHQubWluLnotcC56KSpkLGw9KHQubWF4LnotcC56KSpkKTooYT0odC5tYXguei1wLnopKmQsbD0odC5taW4uei1wLnopKmQpLGk+bHx8YT5yKXx8KChhPml8fGkhPWkpJiYoaT1hKSwobDxyfHxyIT1yKSYmKHI9bCkscjwwKT9udWxsOnRoaXMuYXQoaT49MD9pOnIsZSl9aW50ZXJzZWN0c0JveCh0KXtyZXR1cm4gbnVsbCE9PXRoaXMuaW50ZXJzZWN0Qm94KHQseHApfWludGVyc2VjdFRyaWFuZ2xlKHQsZSxpLHIsbyl7Qmouc3ViVmVjdG9ycyhlLHQpLFRPLnN1YlZlY3RvcnMoaSx0KSxWai5jcm9zc1ZlY3RvcnMoQmosVE8pO2xldCBhLHM9dGhpcy5kaXJlY3Rpb24uZG90KFZqKTtpZihzPjApe2lmKHIpcmV0dXJuIG51bGw7YT0xfWVsc2V7aWYoIShzPDApKXJldHVybiBudWxsO2E9LTEscz0tc31jZi5zdWJWZWN0b3JzKHRoaXMub3JpZ2luLHQpO2xldCBsPWEqdGhpcy5kaXJlY3Rpb24uZG90KFRPLmNyb3NzVmVjdG9ycyhjZixUTykpO2lmKGw8MClyZXR1cm4gbnVsbDtsZXQgYz1hKnRoaXMuZGlyZWN0aW9uLmRvdChCai5jcm9zcyhjZikpO2lmKGM8MHx8bCtjPnMpcmV0dXJuIG51bGw7bGV0IHU9LWEqY2YuZG90KFZqKTtyZXR1cm4gdTwwP251bGw6dGhpcy5hdCh1L3Msbyl9YXBwbHlNYXRyaXg0KHQpe3JldHVybiB0aGlzLm9yaWdpbi5hcHBseU1hdHJpeDQodCksdGhpcy5kaXJlY3Rpb24udHJhbnNmb3JtRGlyZWN0aW9uKHQpLHRoaXN9ZXF1YWxzKHQpe3JldHVybiB0Lm9yaWdpbi5lcXVhbHModGhpcy5vcmlnaW4pJiZ0LmRpcmVjdGlvbi5lcXVhbHModGhpcy5kaXJlY3Rpb24pfWNsb25lKCl7cmV0dXJuKG5ldyB0aGlzLmNvbnN0cnVjdG9yKS5jb3B5KHRoaXMpfX0sUm49Y2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmVsZW1lbnRzPVsxLDAsMCwwLDAsMSwwLDAsMCwwLDEsMCwwLDAsMCwxXSxhcmd1bWVudHMubGVuZ3RoPjAmJmNvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IHRoZSBjb25zdHJ1Y3RvciBubyBsb25nZXIgcmVhZHMgYXJndW1lbnRzLiB1c2UgLnNldCgpIGluc3RlYWQuIil9c2V0KHQsZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtLHgpe2xldCBnPXRoaXMuZWxlbWVudHM7cmV0dXJuIGdbMF09dCxnWzRdPWUsZ1s4XT1pLGdbMTJdPXIsZ1sxXT1vLGdbNV09cyxnWzldPWEsZ1sxM109bCxnWzJdPWMsZ1s2XT11LGdbMTBdPWQsZ1sxNF09cCxnWzNdPWgsZ1s3XT1mLGdbMTFdPW0sZ1sxNV09eCx0aGlzfWlkZW50aXR5KCl7cmV0dXJuIHRoaXMuc2V0KDEsMCwwLDAsMCwxLDAsMCwwLDAsMSwwLDAsMCwwLDEpLHRoaXN9Y2xvbmUoKXtyZXR1cm4obmV3IFJuKS5mcm9tQXJyYXkodGhpcy5lbGVtZW50cyl9Y29weSh0KXtsZXQgZT10aGlzLmVsZW1lbnRzLGk9dC5lbGVtZW50cztyZXR1cm4gZVswXT1pWzBdLGVbMV09aVsxXSxlWzJdPWlbMl0sZVszXT1pWzNdLGVbNF09aVs0XSxlWzVdPWlbNV0sZVs2XT1pWzZdLGVbN109aVs3XSxlWzhdPWlbOF0sZVs5XT1pWzldLGVbMTBdPWlbMTBdLGVbMTFdPWlbMTFdLGVbMTJdPWlbMTJdLGVbMTNdPWlbMTNdLGVbMTRdPWlbMTRdLGVbMTVdPWlbMTVdLHRoaXN9Y29weVBvc2l0aW9uKHQpe2xldCBlPXRoaXMuZWxlbWVudHMsaT10LmVsZW1lbnRzO3JldHVybiBlWzEyXT1pWzEyXSxlWzEzXT1pWzEzXSxlWzE0XT1pWzE0XSx0aGlzfXNldEZyb21NYXRyaXgzKHQpe2xldCBlPXQuZWxlbWVudHM7cmV0dXJuIHRoaXMuc2V0KGVbMF0sZVszXSxlWzZdLDAsZVsxXSxlWzRdLGVbN10sMCxlWzJdLGVbNV0sZVs4XSwwLDAsMCwwLDEpLHRoaXN9ZXh0cmFjdEJhc2lzKHQsZSxpKXtyZXR1cm4gdC5zZXRGcm9tTWF0cml4Q29sdW1uKHRoaXMsMCksZS5zZXRGcm9tTWF0cml4Q29sdW1uKHRoaXMsMSksaS5zZXRGcm9tTWF0cml4Q29sdW1uKHRoaXMsMiksdGhpc31tYWtlQmFzaXModCxlLGkpe3JldHVybiB0aGlzLnNldCh0LngsZS54LGkueCwwLHQueSxlLnksaS55LDAsdC56LGUueixpLnosMCwwLDAsMCwxKSx0aGlzfWV4dHJhY3RSb3RhdGlvbih0KXtsZXQgZT10aGlzLmVsZW1lbnRzLGk9dC5lbGVtZW50cyxyPTEvankuc2V0RnJvbU1hdHJpeENvbHVtbih0LDApLmxlbmd0aCgpLG89MS9qeS5zZXRGcm9tTWF0cml4Q29sdW1uKHQsMSkubGVuZ3RoKCkscz0xL2p5LnNldEZyb21NYXRyaXhDb2x1bW4odCwyKS5sZW5ndGgoKTtyZXR1cm4gZVswXT1pWzBdKnIsZVsxXT1pWzFdKnIsZVsyXT1pWzJdKnIsZVszXT0wLGVbNF09aVs0XSpvLGVbNV09aVs1XSpvLGVbNl09aVs2XSpvLGVbN109MCxlWzhdPWlbOF0qcyxlWzldPWlbOV0qcyxlWzEwXT1pWzEwXSpzLGVbMTFdPTAsZVsxMl09MCxlWzEzXT0wLGVbMTRdPTAsZVsxNV09MSx0aGlzfW1ha2VSb3RhdGlvbkZyb21FdWxlcih0KXt0JiZ0LmlzRXVsZXJ8fGNvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IC5tYWtlUm90YXRpb25Gcm9tRXVsZXIoKSBub3cgZXhwZWN0cyBhIEV1bGVyIHJvdGF0aW9uIHJhdGhlciB0aGFuIGEgVmVjdG9yMyBhbmQgb3JkZXIuIik7bGV0IGU9dGhpcy5lbGVtZW50cyxpPXQueCxyPXQueSxvPXQueixzPU1hdGguY29zKGkpLGE9TWF0aC5zaW4oaSksbD1NYXRoLmNvcyhyKSxjPU1hdGguc2luKHIpLHU9TWF0aC5jb3MobyksZD1NYXRoLnNpbihvKTtpZigiWFlaIj09PXQub3JkZXIpe2xldCBwPXMqdSxoPXMqZCxmPWEqdSxtPWEqZDtlWzBdPWwqdSxlWzRdPS1sKmQsZVs4XT1jLGVbMV09aCtmKmMsZVs1XT1wLW0qYyxlWzldPS1hKmwsZVsyXT1tLXAqYyxlWzZdPWYraCpjLGVbMTBdPXMqbH1lbHNlIGlmKCJZWFoiPT09dC5vcmRlcil7bGV0IHA9bCp1LGg9bCpkLGY9Yyp1LG09YypkO2VbMF09cCttKmEsZVs0XT1mKmEtaCxlWzhdPXMqYyxlWzFdPXMqZCxlWzVdPXMqdSxlWzldPS1hLGVbMl09aCphLWYsZVs2XT1tK3AqYSxlWzEwXT1zKmx9ZWxzZSBpZigiWlhZIj09PXQub3JkZXIpe2xldCBwPWwqdSxoPWwqZCxmPWMqdSxtPWMqZDtlWzBdPXAtbSphLGVbNF09LXMqZCxlWzhdPWYraCphLGVbMV09aCtmKmEsZVs1XT1zKnUsZVs5XT1tLXAqYSxlWzJdPS1zKmMsZVs2XT1hLGVbMTBdPXMqbH1lbHNlIGlmKCJaWVgiPT09dC5vcmRlcil7bGV0IHA9cyp1LGg9cypkLGY9YSp1LG09YSpkO2VbMF09bCp1LGVbNF09ZipjLWgsZVs4XT1wKmMrbSxlWzFdPWwqZCxlWzVdPW0qYytwLGVbOV09aCpjLWYsZVsyXT0tYyxlWzZdPWEqbCxlWzEwXT1zKmx9ZWxzZSBpZigiWVpYIj09PXQub3JkZXIpe2xldCBwPXMqbCxoPXMqYyxmPWEqbCxtPWEqYztlWzBdPWwqdSxlWzRdPW0tcCpkLGVbOF09ZipkK2gsZVsxXT1kLGVbNV09cyp1LGVbOV09LWEqdSxlWzJdPS1jKnUsZVs2XT1oKmQrZixlWzEwXT1wLW0qZH1lbHNlIGlmKCJYWlkiPT09dC5vcmRlcil7bGV0IHA9cypsLGg9cypjLGY9YSpsLG09YSpjO2VbMF09bCp1LGVbNF09LWQsZVs4XT1jKnUsZVsxXT1wKmQrbSxlWzVdPXMqdSxlWzldPWgqZC1mLGVbMl09ZipkLWgsZVs2XT1hKnUsZVsxMF09bSpkK3B9cmV0dXJuIGVbM109MCxlWzddPTAsZVsxMV09MCxlWzEyXT0wLGVbMTNdPTAsZVsxNF09MCxlWzE1XT0xLHRoaXN9bWFrZVJvdGF0aW9uRnJvbVF1YXRlcm5pb24odCl7cmV0dXJuIHRoaXMuY29tcG9zZShqOGUsdCxHOGUpfWxvb2tBdCh0LGUsaSl7bGV0IHI9dGhpcy5lbGVtZW50cztyZXR1cm4gd2wuc3ViVmVjdG9ycyh0LGUpLDA9PT13bC5sZW5ndGhTcSgpJiYod2wuej0xKSx3bC5ub3JtYWxpemUoKSx1Zi5jcm9zc1ZlY3RvcnMoaSx3bCksMD09PXVmLmxlbmd0aFNxKCkmJigxPT09TWF0aC5hYnMoaS56KT93bC54Kz0xZS00OndsLnorPTFlLTQsd2wubm9ybWFsaXplKCksdWYuY3Jvc3NWZWN0b3JzKGksd2wpKSx1Zi5ub3JtYWxpemUoKSxETy5jcm9zc1ZlY3RvcnMod2wsdWYpLHJbMF09dWYueCxyWzRdPURPLngscls4XT13bC54LHJbMV09dWYueSxyWzVdPURPLnkscls5XT13bC55LHJbMl09dWYueixyWzZdPURPLnosclsxMF09d2wueix0aGlzfW11bHRpcGx5KHQsZSl7cmV0dXJuIHZvaWQgMCE9PWU/KGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLm11bHRpcGx5KCkgbm93IG9ubHkgYWNjZXB0cyBvbmUgYXJndW1lbnQuIFVzZSAubXVsdGlwbHlNYXRyaWNlcyggYSwgYiApIGluc3RlYWQuIiksdGhpcy5tdWx0aXBseU1hdHJpY2VzKHQsZSkpOnRoaXMubXVsdGlwbHlNYXRyaWNlcyh0aGlzLHQpfXByZW11bHRpcGx5KHQpe3JldHVybiB0aGlzLm11bHRpcGx5TWF0cmljZXModCx0aGlzKX1tdWx0aXBseU1hdHJpY2VzKHQsZSl7bGV0IGk9dC5lbGVtZW50cyxyPWUuZWxlbWVudHMsbz10aGlzLmVsZW1lbnRzLHM9aVswXSxhPWlbNF0sbD1pWzhdLGM9aVsxMl0sdT1pWzFdLGQ9aVs1XSxwPWlbOV0saD1pWzEzXSxmPWlbMl0sbT1pWzZdLHg9aVsxMF0sZz1pWzE0XSxiPWlbM10sRD1pWzddLFQ9aVsxMV0saz1pWzE1XSxaPXJbMF0sej1yWzRdLGZlPXJbOF0sdWU9clsxMl0saGU9clsxXSx3PXJbNV0sRj1yWzldLHE9clsxM10sSz1yWzJdLGRlPXJbNl0sWT1yWzEwXSxhZT1yWzE0XSxsZT1yWzNdLEllPXJbN10sdmU9clsxMV0sRGU9clsxNV07cmV0dXJuIG9bMF09cypaK2EqaGUrbCpLK2MqbGUsb1s0XT1zKnorYSp3K2wqZGUrYypJZSxvWzhdPXMqZmUrYSpGK2wqWStjKnZlLG9bMTJdPXMqdWUrYSpxK2wqYWUrYypEZSxvWzFdPXUqWitkKmhlK3AqSytoKmxlLG9bNV09dSp6K2QqdytwKmRlK2gqSWUsb1s5XT11KmZlK2QqRitwKlkraCp2ZSxvWzEzXT11KnVlK2QqcStwKmFlK2gqRGUsb1syXT1mKlorbSpoZSt4KksrZypsZSxvWzZdPWYqeittKncreCpkZStnKkllLG9bMTBdPWYqZmUrbSpGK3gqWStnKnZlLG9bMTRdPWYqdWUrbSpxK3gqYWUrZypEZSxvWzNdPWIqWitEKmhlK1QqSytrKmxlLG9bN109Yip6K0QqdytUKmRlK2sqSWUsb1sxMV09YipmZStEKkYrVCpZK2sqdmUsb1sxNV09Yip1ZStEKnErVCphZStrKkRlLHRoaXN9bXVsdGlwbHlTY2FsYXIodCl7bGV0IGU9dGhpcy5lbGVtZW50cztyZXR1cm4gZVswXSo9dCxlWzRdKj10LGVbOF0qPXQsZVsxMl0qPXQsZVsxXSo9dCxlWzVdKj10LGVbOV0qPXQsZVsxM10qPXQsZVsyXSo9dCxlWzZdKj10LGVbMTBdKj10LGVbMTRdKj10LGVbM10qPXQsZVs3XSo9dCxlWzExXSo9dCxlWzE1XSo9dCx0aGlzfWRldGVybWluYW50KCl7bGV0IHQ9dGhpcy5lbGVtZW50cyxlPXRbMF0saT10WzRdLHI9dFs4XSxvPXRbMTJdLHM9dFsxXSxhPXRbNV0sbD10WzldLGM9dFsxM10sdT10WzJdLGQ9dFs2XSxwPXRbMTBdLGg9dFsxNF07cmV0dXJuIHRbM10qKCtvKmwqZC1yKmMqZC1vKmEqcCtpKmMqcCtyKmEqaC1pKmwqaCkrdFs3XSooK2UqbCpoLWUqYypwK28qcypwLXIqcypoK3IqYyp1LW8qbCp1KSt0WzExXSooK2UqYypkLWUqYSpoLW8qcypkK2kqcypoK28qYSp1LWkqYyp1KSt0WzE1XSooLXIqYSp1LWUqbCpkK2UqYSpwK3IqcypkLWkqcypwK2kqbCp1KX10cmFuc3Bvc2UoKXtsZXQgZSx0PXRoaXMuZWxlbWVudHM7cmV0dXJuIGU9dFsxXSx0WzFdPXRbNF0sdFs0XT1lLGU9dFsyXSx0WzJdPXRbOF0sdFs4XT1lLGU9dFs2XSx0WzZdPXRbOV0sdFs5XT1lLGU9dFszXSx0WzNdPXRbMTJdLHRbMTJdPWUsZT10WzddLHRbN109dFsxM10sdFsxM109ZSxlPXRbMTFdLHRbMTFdPXRbMTRdLHRbMTRdPWUsdGhpc31zZXRQb3NpdGlvbih0LGUsaSl7bGV0IHI9dGhpcy5lbGVtZW50cztyZXR1cm4gdC5pc1ZlY3RvcjM/KHJbMTJdPXQueCxyWzEzXT10LnksclsxNF09dC56KTooclsxMl09dCxyWzEzXT1lLHJbMTRdPWkpLHRoaXN9aW52ZXJ0KCl7bGV0IHQ9dGhpcy5lbGVtZW50cyxlPXRbMF0saT10WzFdLHI9dFsyXSxvPXRbM10scz10WzRdLGE9dFs1XSxsPXRbNl0sYz10WzddLHU9dFs4XSxkPXRbOV0scD10WzEwXSxoPXRbMTFdLGY9dFsxMl0sbT10WzEzXSx4PXRbMTRdLGc9dFsxNV0sYj1kKngqYy1tKnAqYyttKmwqaC1hKngqaC1kKmwqZythKnAqZyxEPWYqcCpjLXUqeCpjLWYqbCpoK3MqeCpoK3UqbCpnLXMqcCpnLFQ9dSptKmMtZipkKmMrZiphKmgtcyptKmgtdSphKmcrcypkKmcsaz1mKmQqbC11Km0qbC1mKmEqcCtzKm0qcCt1KmEqeC1zKmQqeCxaPWUqYitpKkQrcipUK28qaztpZigwPT09WilyZXR1cm4gdGhpcy5zZXQoMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCk7bGV0IHo9MS9aO3JldHVybiB0WzBdPWIqeix0WzFdPShtKnAqby1kKngqby1tKnIqaCtpKngqaCtkKnIqZy1pKnAqZykqeix0WzJdPShhKngqby1tKmwqbyttKnIqYy1pKngqYy1hKnIqZytpKmwqZykqeix0WzNdPShkKmwqby1hKnAqby1kKnIqYytpKnAqYythKnIqaC1pKmwqaCkqeix0WzRdPUQqeix0WzVdPSh1Kngqby1mKnAqbytmKnIqaC1lKngqaC11KnIqZytlKnAqZykqeix0WzZdPShmKmwqby1zKngqby1mKnIqYytlKngqYytzKnIqZy1lKmwqZykqeix0WzddPShzKnAqby11Kmwqbyt1KnIqYy1lKnAqYy1zKnIqaCtlKmwqaCkqeix0WzhdPVQqeix0WzldPShmKmQqby11Km0qby1mKmkqaCtlKm0qaCt1KmkqZy1lKmQqZykqeix0WzEwXT0ocyptKm8tZiphKm8rZippKmMtZSptKmMtcyppKmcrZSphKmcpKnosdFsxMV09KHUqYSpvLXMqZCpvLXUqaSpjK2UqZCpjK3MqaSpoLWUqYSpoKSp6LHRbMTJdPWsqeix0WzEzXT0odSptKnItZipkKnIrZippKnAtZSptKnAtdSppKngrZSpkKngpKnosdFsxNF09KGYqYSpyLXMqbSpyLWYqaSpsK2UqbSpsK3MqaSp4LWUqYSp4KSp6LHRbMTVdPShzKmQqci11KmEqcit1KmkqbC1lKmQqbC1zKmkqcCtlKmEqcCkqeix0aGlzfXNjYWxlKHQpe2xldCBlPXRoaXMuZWxlbWVudHMsaT10Lngscj10Lnksbz10Lno7cmV0dXJuIGVbMF0qPWksZVs0XSo9cixlWzhdKj1vLGVbMV0qPWksZVs1XSo9cixlWzldKj1vLGVbMl0qPWksZVs2XSo9cixlWzEwXSo9byxlWzNdKj1pLGVbN10qPXIsZVsxMV0qPW8sdGhpc31nZXRNYXhTY2FsZU9uQXhpcygpe2xldCB0PXRoaXMuZWxlbWVudHM7cmV0dXJuIE1hdGguc3FydChNYXRoLm1heCh0WzBdKnRbMF0rdFsxXSp0WzFdK3RbMl0qdFsyXSx0WzRdKnRbNF0rdFs1XSp0WzVdK3RbNl0qdFs2XSx0WzhdKnRbOF0rdFs5XSp0WzldK3RbMTBdKnRbMTBdKSl9bWFrZVRyYW5zbGF0aW9uKHQsZSxpKXtyZXR1cm4gdGhpcy5zZXQoMSwwLDAsdCwwLDEsMCxlLDAsMCwxLGksMCwwLDAsMSksdGhpc31tYWtlUm90YXRpb25YKHQpe2xldCBlPU1hdGguY29zKHQpLGk9TWF0aC5zaW4odCk7cmV0dXJuIHRoaXMuc2V0KDEsMCwwLDAsMCxlLC1pLDAsMCxpLGUsMCwwLDAsMCwxKSx0aGlzfW1ha2VSb3RhdGlvblkodCl7bGV0IGU9TWF0aC5jb3ModCksaT1NYXRoLnNpbih0KTtyZXR1cm4gdGhpcy5zZXQoZSwwLGksMCwwLDEsMCwwLC1pLDAsZSwwLDAsMCwwLDEpLHRoaXN9bWFrZVJvdGF0aW9uWih0KXtsZXQgZT1NYXRoLmNvcyh0KSxpPU1hdGguc2luKHQpO3JldHVybiB0aGlzLnNldChlLC1pLDAsMCxpLGUsMCwwLDAsMCwxLDAsMCwwLDAsMSksdGhpc31tYWtlUm90YXRpb25BeGlzKHQsZSl7bGV0IGk9TWF0aC5jb3MoZSkscj1NYXRoLnNpbihlKSxvPTEtaSxzPXQueCxhPXQueSxsPXQueixjPW8qcyx1PW8qYTtyZXR1cm4gdGhpcy5zZXQoYypzK2ksYyphLXIqbCxjKmwrciphLDAsYyphK3IqbCx1KmEraSx1KmwtcipzLDAsYypsLXIqYSx1KmwrcipzLG8qbCpsK2ksMCwwLDAsMCwxKSx0aGlzfW1ha2VTY2FsZSh0LGUsaSl7cmV0dXJuIHRoaXMuc2V0KHQsMCwwLDAsMCxlLDAsMCwwLDAsaSwwLDAsMCwwLDEpLHRoaXN9bWFrZVNoZWFyKHQsZSxpLHIsbyxzKXtyZXR1cm4gdGhpcy5zZXQoMSxpLG8sMCx0LDEscywwLGUsciwxLDAsMCwwLDAsMSksdGhpc31jb21wb3NlKHQsZSxpKXtsZXQgcj10aGlzLmVsZW1lbnRzLG89ZS5feCxzPWUuX3ksYT1lLl96LGw9ZS5fdyxjPW8rbyx1PXMrcyxkPWErYSxwPW8qYyxoPW8qdSxmPW8qZCxtPXMqdSx4PXMqZCxnPWEqZCxiPWwqYyxEPWwqdSxUPWwqZCxrPWkueCxaPWkueSx6PWkuejtyZXR1cm4gclswXT0oMS0obStnKSkqayxyWzFdPShoK1QpKmssclsyXT0oZi1EKSprLHJbM109MCxyWzRdPShoLVQpKloscls1XT0oMS0ocCtnKSkqWixyWzZdPSh4K2IpKloscls3XT0wLHJbOF09KGYrRCkqeixyWzldPSh4LWIpKnosclsxMF09KDEtKHArbSkpKnosclsxMV09MCxyWzEyXT10LngsclsxM109dC55LHJbMTRdPXQueixyWzE1XT0xLHRoaXN9ZGVjb21wb3NlKHQsZSxpKXtsZXQgcj10aGlzLmVsZW1lbnRzLG89ankuc2V0KHJbMF0sclsxXSxyWzJdKS5sZW5ndGgoKSxzPWp5LnNldChyWzRdLHJbNV0scls2XSkubGVuZ3RoKCksYT1qeS5zZXQocls4XSxyWzldLHJbMTBdKS5sZW5ndGgoKTt0aGlzLmRldGVybWluYW50KCk8MCYmKG89LW8pLHQueD1yWzEyXSx0Lnk9clsxM10sdC56PXJbMTRdLGx1LmNvcHkodGhpcyk7bGV0IGM9MS9vLHU9MS9zLGQ9MS9hO3JldHVybiBsdS5lbGVtZW50c1swXSo9YyxsdS5lbGVtZW50c1sxXSo9YyxsdS5lbGVtZW50c1syXSo9YyxsdS5lbGVtZW50c1s0XSo9dSxsdS5lbGVtZW50c1s1XSo9dSxsdS5lbGVtZW50c1s2XSo9dSxsdS5lbGVtZW50c1s4XSo9ZCxsdS5lbGVtZW50c1s5XSo9ZCxsdS5lbGVtZW50c1sxMF0qPWQsZS5zZXRGcm9tUm90YXRpb25NYXRyaXgobHUpLGkueD1vLGkueT1zLGkuej1hLHRoaXN9bWFrZVBlcnNwZWN0aXZlKHQsZSxpLHIsbyxzKXt2b2lkIDA9PT1zJiZjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5tYWtlUGVyc3BlY3RpdmUoKSBoYXMgYmVlbiByZWRlZmluZWQgYW5kIGhhcyBhIG5ldyBzaWduYXR1cmUuIFBsZWFzZSBjaGVjayB0aGUgZG9jcy4iKTtsZXQgYT10aGlzLmVsZW1lbnRzLGM9MipvLyhpLXIpLHU9KGUrdCkvKGUtdCksZD0oaStyKS8oaS1yKSxwPS0ocytvKS8ocy1vKSxoPS0yKnMqby8ocy1vKTtyZXR1cm4gYVswXT0yKm8vKGUtdCksYVs0XT0wLGFbOF09dSxhWzEyXT0wLGFbMV09MCxhWzVdPWMsYVs5XT1kLGFbMTNdPTAsYVsyXT0wLGFbNl09MCxhWzEwXT1wLGFbMTRdPWgsYVszXT0wLGFbN109MCxhWzExXT0tMSxhWzE1XT0wLHRoaXN9bWFrZU9ydGhvZ3JhcGhpYyh0LGUsaSxyLG8scyl7bGV0IGE9dGhpcy5lbGVtZW50cyxsPTEvKGUtdCksYz0xLyhpLXIpLHU9MS8ocy1vKSxkPShlK3QpKmwscD0oaStyKSpjLGg9KHMrbykqdTtyZXR1cm4gYVswXT0yKmwsYVs0XT0wLGFbOF09MCxhWzEyXT0tZCxhWzFdPTAsYVs1XT0yKmMsYVs5XT0wLGFbMTNdPS1wLGFbMl09MCxhWzZdPTAsYVsxMF09LTIqdSxhWzE0XT0taCxhWzNdPTAsYVs3XT0wLGFbMTFdPTAsYVsxNV09MSx0aGlzfWVxdWFscyh0KXtsZXQgZT10aGlzLmVsZW1lbnRzLGk9dC5lbGVtZW50cztmb3IobGV0IHI9MDtyPDE2O3IrKylpZihlW3JdIT09aVtyXSlyZXR1cm4hMTtyZXR1cm4hMH1mcm9tQXJyYXkodCxlPTApe2ZvcihsZXQgaT0wO2k8MTY7aSsrKXRoaXMuZWxlbWVudHNbaV09dFtpK2VdO3JldHVybiB0aGlzfXRvQXJyYXkodD1bXSxlPTApe2xldCBpPXRoaXMuZWxlbWVudHM7cmV0dXJuIHRbZV09aVswXSx0W2UrMV09aVsxXSx0W2UrMl09aVsyXSx0W2UrM109aVszXSx0W2UrNF09aVs0XSx0W2UrNV09aVs1XSx0W2UrNl09aVs2XSx0W2UrN109aVs3XSx0W2UrOF09aVs4XSx0W2UrOV09aVs5XSx0W2UrMTBdPWlbMTBdLHRbZSsxMV09aVsxMV0sdFtlKzEyXT1pWzEyXSx0W2UrMTNdPWlbMTNdLHRbZSsxNF09aVsxNF0sdFtlKzE1XT1pWzE1XSx0fX07Um4ucHJvdG90eXBlLmlzTWF0cml4ND0hMDt2YXIgank9bmV3IGllLGx1PW5ldyBSbixqOGU9bmV3IGllKDAsMCwwKSxHOGU9bmV3IGllKDEsMSwxKSx1Zj1uZXcgaWUsRE89bmV3IGllLHdsPW5ldyBpZSxTdWU9bmV3IFJuLEV1ZT1uZXcgcXMsTWY9Y2xhc3N7Y29uc3RydWN0b3IodD0wLGU9MCxpPTAscj1NZi5EZWZhdWx0T3JkZXIpe3RoaXMuX3g9dCx0aGlzLl95PWUsdGhpcy5fej1pLHRoaXMuX29yZGVyPXJ9Z2V0IHgoKXtyZXR1cm4gdGhpcy5feH1zZXQgeCh0KXt0aGlzLl94PXQsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpfWdldCB5KCl7cmV0dXJuIHRoaXMuX3l9c2V0IHkodCl7dGhpcy5feT10LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKX1nZXQgeigpe3JldHVybiB0aGlzLl96fXNldCB6KHQpe3RoaXMuX3o9dCx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCl9Z2V0IG9yZGVyKCl7cmV0dXJuIHRoaXMuX29yZGVyfXNldCBvcmRlcih0KXt0aGlzLl9vcmRlcj10LHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKX1zZXQodCxlLGkscj10aGlzLl9vcmRlcil7cmV0dXJuIHRoaXMuX3g9dCx0aGlzLl95PWUsdGhpcy5fej1pLHRoaXMuX29yZGVyPXIsdGhpcy5fb25DaGFuZ2VDYWxsYmFjaygpLHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5feCx0aGlzLl95LHRoaXMuX3osdGhpcy5fb3JkZXIpfWNvcHkodCl7cmV0dXJuIHRoaXMuX3g9dC5feCx0aGlzLl95PXQuX3ksdGhpcy5fej10Ll96LHRoaXMuX29yZGVyPXQuX29yZGVyLHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2soKSx0aGlzfXNldEZyb21Sb3RhdGlvbk1hdHJpeCh0LGU9dGhpcy5fb3JkZXIsaT0hMCl7bGV0IHI9dC5lbGVtZW50cyxvPXJbMF0scz1yWzRdLGE9cls4XSxsPXJbMV0sYz1yWzVdLHU9cls5XSxkPXJbMl0scD1yWzZdLGg9clsxMF07c3dpdGNoKGUpe2Nhc2UiWFlaIjp0aGlzLl95PU1hdGguYXNpbihHYShhLC0xLDEpKSxNYXRoLmFicyhhKTwuOTk5OTk5OT8odGhpcy5feD1NYXRoLmF0YW4yKC11LGgpLHRoaXMuX3o9TWF0aC5hdGFuMigtcyxvKSk6KHRoaXMuX3g9TWF0aC5hdGFuMihwLGMpLHRoaXMuX3o9MCk7YnJlYWs7Y2FzZSJZWFoiOnRoaXMuX3g9TWF0aC5hc2luKC1HYSh1LC0xLDEpKSxNYXRoLmFicyh1KTwuOTk5OTk5OT8odGhpcy5feT1NYXRoLmF0YW4yKGEsaCksdGhpcy5fej1NYXRoLmF0YW4yKGwsYykpOih0aGlzLl95PU1hdGguYXRhbjIoLWQsbyksdGhpcy5fej0wKTticmVhaztjYXNlIlpYWSI6dGhpcy5feD1NYXRoLmFzaW4oR2EocCwtMSwxKSksTWF0aC5hYnMocCk8Ljk5OTk5OTk/KHRoaXMuX3k9TWF0aC5hdGFuMigtZCxoKSx0aGlzLl96PU1hdGguYXRhbjIoLXMsYykpOih0aGlzLl95PTAsdGhpcy5fej1NYXRoLmF0YW4yKGwsbykpO2JyZWFrO2Nhc2UiWllYIjp0aGlzLl95PU1hdGguYXNpbigtR2EoZCwtMSwxKSksTWF0aC5hYnMoZCk8Ljk5OTk5OTk/KHRoaXMuX3g9TWF0aC5hdGFuMihwLGgpLHRoaXMuX3o9TWF0aC5hdGFuMihsLG8pKToodGhpcy5feD0wLHRoaXMuX3o9TWF0aC5hdGFuMigtcyxjKSk7YnJlYWs7Y2FzZSJZWlgiOnRoaXMuX3o9TWF0aC5hc2luKEdhKGwsLTEsMSkpLE1hdGguYWJzKGwpPC45OTk5OTk5Pyh0aGlzLl94PU1hdGguYXRhbjIoLXUsYyksdGhpcy5feT1NYXRoLmF0YW4yKC1kLG8pKToodGhpcy5feD0wLHRoaXMuX3k9TWF0aC5hdGFuMihhLGgpKTticmVhaztjYXNlIlhaWSI6dGhpcy5fej1NYXRoLmFzaW4oLUdhKHMsLTEsMSkpLE1hdGguYWJzKHMpPC45OTk5OTk5Pyh0aGlzLl94PU1hdGguYXRhbjIocCxjKSx0aGlzLl95PU1hdGguYXRhbjIoYSxvKSk6KHRoaXMuX3g9TWF0aC5hdGFuMigtdSxoKSx0aGlzLl95PTApO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS53YXJuKCJUSFJFRS5FdWxlcjogLnNldEZyb21Sb3RhdGlvbk1hdHJpeCgpIGVuY291bnRlcmVkIGFuIHVua25vd24gb3JkZXI6ICIrZSl9cmV0dXJuIHRoaXMuX29yZGVyPWUsITA9PT1pJiZ0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc31zZXRGcm9tUXVhdGVybmlvbih0LGUsaSl7cmV0dXJuIFN1ZS5tYWtlUm90YXRpb25Gcm9tUXVhdGVybmlvbih0KSx0aGlzLnNldEZyb21Sb3RhdGlvbk1hdHJpeChTdWUsZSxpKX1zZXRGcm9tVmVjdG9yMyh0LGU9dGhpcy5fb3JkZXIpe3JldHVybiB0aGlzLnNldCh0LngsdC55LHQueixlKX1yZW9yZGVyKHQpe3JldHVybiBFdWUuc2V0RnJvbUV1bGVyKHRoaXMpLHRoaXMuc2V0RnJvbVF1YXRlcm5pb24oRXVlLHQpfWVxdWFscyh0KXtyZXR1cm4gdC5feD09PXRoaXMuX3gmJnQuX3k9PT10aGlzLl95JiZ0Ll96PT09dGhpcy5feiYmdC5fb3JkZXI9PT10aGlzLl9vcmRlcn1mcm9tQXJyYXkodCl7cmV0dXJuIHRoaXMuX3g9dFswXSx0aGlzLl95PXRbMV0sdGhpcy5fej10WzJdLHZvaWQgMCE9PXRbM10mJih0aGlzLl9vcmRlcj10WzNdKSx0aGlzLl9vbkNoYW5nZUNhbGxiYWNrKCksdGhpc310b0FycmF5KHQ9W10sZT0wKXtyZXR1cm4gdFtlXT10aGlzLl94LHRbZSsxXT10aGlzLl95LHRbZSsyXT10aGlzLl96LHRbZSszXT10aGlzLl9vcmRlcix0fXRvVmVjdG9yMyh0KXtyZXR1cm4gdD90LnNldCh0aGlzLl94LHRoaXMuX3ksdGhpcy5feik6bmV3IGllKHRoaXMuX3gsdGhpcy5feSx0aGlzLl96KX1fb25DaGFuZ2UodCl7cmV0dXJuIHRoaXMuX29uQ2hhbmdlQ2FsbGJhY2s9dCx0aGlzfV9vbkNoYW5nZUNhbGxiYWNrKCl7fX07TWYucHJvdG90eXBlLmlzRXVsZXI9ITAsTWYuRGVmYXVsdE9yZGVyPSJYWVoiLE1mLlJvdGF0aW9uT3JkZXJzPVsiWFlaIiwiWVpYIiwiWlhZIiwiWFpZIiwiWVhaIiwiWllYIl07dmFyIHNrPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5tYXNrPTF9c2V0KHQpe3RoaXMubWFzaz0oMTw8dHwwKT4+PjB9ZW5hYmxlKHQpe3RoaXMubWFza3w9MTw8dHwwfWVuYWJsZUFsbCgpe3RoaXMubWFzaz0tMX10b2dnbGUodCl7dGhpcy5tYXNrXj0xPDx0fDB9ZGlzYWJsZSh0KXt0aGlzLm1hc2smPX4oMTw8dHwwKX1kaXNhYmxlQWxsKCl7dGhpcy5tYXNrPTB9dGVzdCh0KXtyZXR1cm4gMCE9KHRoaXMubWFzayZ0Lm1hc2spfWlzRW5hYmxlZCh0KXtyZXR1cm4gMCE9KHRoaXMubWFzayYoMTw8dHwwKSl9fSxXOGU9MCxUdWU9bmV3IGllLEd5PW5ldyBxcyxDcD1uZXcgUm4sQU89bmV3IGllLElTPW5ldyBpZSxxOGU9bmV3IGllLFk4ZT1uZXcgcXMsRHVlPW5ldyBpZSgxLDAsMCksQXVlPW5ldyBpZSgwLDEsMCksSXVlPW5ldyBpZSgwLDAsMSksWDhlPXt0eXBlOiJhZGRlZCJ9LFB1ZT17dHlwZToicmVtb3ZlZCJ9LFhpPWNsYXNzIGV4dGVuZHMgRXB7Y29uc3RydWN0b3IoKXtzdXBlcigpLE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCJpZCIse3ZhbHVlOlc4ZSsrfSksdGhpcy51dWlkPWR1KCksdGhpcy5uYW1lPSIiLHRoaXMudHlwZT0iT2JqZWN0M0QiLHRoaXMucGFyZW50PW51bGwsdGhpcy5jaGlsZHJlbj1bXSx0aGlzLnVwPVhpLkRlZmF1bHRVcC5jbG9uZSgpO2xldCB0PW5ldyBpZSxlPW5ldyBNZixpPW5ldyBxcyxyPW5ldyBpZSgxLDEsMSk7ZS5fb25DaGFuZ2UoZnVuY3Rpb24oKXtpLnNldEZyb21FdWxlcihlLCExKX0pLGkuX29uQ2hhbmdlKGZ1bmN0aW9uKCl7ZS5zZXRGcm9tUXVhdGVybmlvbihpLHZvaWQgMCwhMSl9KSxPYmplY3QuZGVmaW5lUHJvcGVydGllcyh0aGlzLHtwb3NpdGlvbjp7Y29uZmlndXJhYmxlOiEwLGVudW1lcmFibGU6ITAsdmFsdWU6dH0scm90YXRpb246e2NvbmZpZ3VyYWJsZTohMCxlbnVtZXJhYmxlOiEwLHZhbHVlOmV9LHF1YXRlcm5pb246e2NvbmZpZ3VyYWJsZTohMCxlbnVtZXJhYmxlOiEwLHZhbHVlOml9LHNjYWxlOntjb25maWd1cmFibGU6ITAsZW51bWVyYWJsZTohMCx2YWx1ZTpyfSxtb2RlbFZpZXdNYXRyaXg6e3ZhbHVlOm5ldyBSbn0sbm9ybWFsTWF0cml4Ont2YWx1ZTpuZXcgSm99fSksdGhpcy5tYXRyaXg9bmV3IFJuLHRoaXMubWF0cml4V29ybGQ9bmV3IFJuLHRoaXMubWF0cml4QXV0b1VwZGF0ZT1YaS5EZWZhdWx0TWF0cml4QXV0b1VwZGF0ZSx0aGlzLm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9ITEsdGhpcy5sYXllcnM9bmV3IHNrLHRoaXMudmlzaWJsZT0hMCx0aGlzLmNhc3RTaGFkb3c9ITEsdGhpcy5yZWNlaXZlU2hhZG93PSExLHRoaXMuZnJ1c3R1bUN1bGxlZD0hMCx0aGlzLnJlbmRlck9yZGVyPTAsdGhpcy5hbmltYXRpb25zPVtdLHRoaXMudXNlckRhdGE9e319b25CZWZvcmVSZW5kZXIoKXt9b25BZnRlclJlbmRlcigpe31hcHBseU1hdHJpeDQodCl7dGhpcy5tYXRyaXhBdXRvVXBkYXRlJiZ0aGlzLnVwZGF0ZU1hdHJpeCgpLHRoaXMubWF0cml4LnByZW11bHRpcGx5KHQpLHRoaXMubWF0cml4LmRlY29tcG9zZSh0aGlzLnBvc2l0aW9uLHRoaXMucXVhdGVybmlvbix0aGlzLnNjYWxlKX1hcHBseVF1YXRlcm5pb24odCl7cmV0dXJuIHRoaXMucXVhdGVybmlvbi5wcmVtdWx0aXBseSh0KSx0aGlzfXNldFJvdGF0aW9uRnJvbUF4aXNBbmdsZSh0LGUpe3RoaXMucXVhdGVybmlvbi5zZXRGcm9tQXhpc0FuZ2xlKHQsZSl9c2V0Um90YXRpb25Gcm9tRXVsZXIodCl7dGhpcy5xdWF0ZXJuaW9uLnNldEZyb21FdWxlcih0LCEwKX1zZXRSb3RhdGlvbkZyb21NYXRyaXgodCl7dGhpcy5xdWF0ZXJuaW9uLnNldEZyb21Sb3RhdGlvbk1hdHJpeCh0KX1zZXRSb3RhdGlvbkZyb21RdWF0ZXJuaW9uKHQpe3RoaXMucXVhdGVybmlvbi5jb3B5KHQpfXJvdGF0ZU9uQXhpcyh0LGUpe3JldHVybiBHeS5zZXRGcm9tQXhpc0FuZ2xlKHQsZSksdGhpcy5xdWF0ZXJuaW9uLm11bHRpcGx5KEd5KSx0aGlzfXJvdGF0ZU9uV29ybGRBeGlzKHQsZSl7cmV0dXJuIEd5LnNldEZyb21BeGlzQW5nbGUodCxlKSx0aGlzLnF1YXRlcm5pb24ucHJlbXVsdGlwbHkoR3kpLHRoaXN9cm90YXRlWCh0KXtyZXR1cm4gdGhpcy5yb3RhdGVPbkF4aXMoRHVlLHQpfXJvdGF0ZVkodCl7cmV0dXJuIHRoaXMucm90YXRlT25BeGlzKEF1ZSx0KX1yb3RhdGVaKHQpe3JldHVybiB0aGlzLnJvdGF0ZU9uQXhpcyhJdWUsdCl9dHJhbnNsYXRlT25BeGlzKHQsZSl7cmV0dXJuIFR1ZS5jb3B5KHQpLmFwcGx5UXVhdGVybmlvbih0aGlzLnF1YXRlcm5pb24pLHRoaXMucG9zaXRpb24uYWRkKFR1ZS5tdWx0aXBseVNjYWxhcihlKSksdGhpc310cmFuc2xhdGVYKHQpe3JldHVybiB0aGlzLnRyYW5zbGF0ZU9uQXhpcyhEdWUsdCl9dHJhbnNsYXRlWSh0KXtyZXR1cm4gdGhpcy50cmFuc2xhdGVPbkF4aXMoQXVlLHQpfXRyYW5zbGF0ZVoodCl7cmV0dXJuIHRoaXMudHJhbnNsYXRlT25BeGlzKEl1ZSx0KX1sb2NhbFRvV29ybGQodCl7cmV0dXJuIHQuYXBwbHlNYXRyaXg0KHRoaXMubWF0cml4V29ybGQpfXdvcmxkVG9Mb2NhbCh0KXtyZXR1cm4gdC5hcHBseU1hdHJpeDQoQ3AuY29weSh0aGlzLm1hdHJpeFdvcmxkKS5pbnZlcnQoKSl9bG9va0F0KHQsZSxpKXt0LmlzVmVjdG9yMz9BTy5jb3B5KHQpOkFPLnNldCh0LGUsaSk7bGV0IHI9dGhpcy5wYXJlbnQ7dGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSksSVMuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKHRoaXMubWF0cml4V29ybGQpLHRoaXMuaXNDYW1lcmF8fHRoaXMuaXNMaWdodD9DcC5sb29rQXQoSVMsQU8sdGhpcy51cCk6Q3AubG9va0F0KEFPLElTLHRoaXMudXApLHRoaXMucXVhdGVybmlvbi5zZXRGcm9tUm90YXRpb25NYXRyaXgoQ3ApLHImJihDcC5leHRyYWN0Um90YXRpb24oci5tYXRyaXhXb3JsZCksR3kuc2V0RnJvbVJvdGF0aW9uTWF0cml4KENwKSx0aGlzLnF1YXRlcm5pb24ucHJlbXVsdGlwbHkoR3kuaW52ZXJ0KCkpKX1hZGQodCl7aWYoYXJndW1lbnRzLmxlbmd0aD4xKXtmb3IobGV0IGU9MDtlPGFyZ3VtZW50cy5sZW5ndGg7ZSsrKXRoaXMuYWRkKGFyZ3VtZW50c1tlXSk7cmV0dXJuIHRoaXN9cmV0dXJuIHQ9PT10aGlzPyhjb25zb2xlLmVycm9yKCJUSFJFRS5PYmplY3QzRC5hZGQ6IG9iamVjdCBjYW4ndCBiZSBhZGRlZCBhcyBhIGNoaWxkIG9mIGl0c2VsZi4iLHQpLHRoaXMpOih0JiZ0LmlzT2JqZWN0M0Q/KG51bGwhPT10LnBhcmVudCYmdC5wYXJlbnQucmVtb3ZlKHQpLHQucGFyZW50PXRoaXMsdGhpcy5jaGlsZHJlbi5wdXNoKHQpLHQuZGlzcGF0Y2hFdmVudChYOGUpKTpjb25zb2xlLmVycm9yKCJUSFJFRS5PYmplY3QzRC5hZGQ6IG9iamVjdCBub3QgYW4gaW5zdGFuY2Ugb2YgVEhSRUUuT2JqZWN0M0QuIix0KSx0aGlzKX1yZW1vdmUodCl7aWYoYXJndW1lbnRzLmxlbmd0aD4xKXtmb3IobGV0IGk9MDtpPGFyZ3VtZW50cy5sZW5ndGg7aSsrKXRoaXMucmVtb3ZlKGFyZ3VtZW50c1tpXSk7cmV0dXJuIHRoaXN9bGV0IGU9dGhpcy5jaGlsZHJlbi5pbmRleE9mKHQpO3JldHVybi0xIT09ZSYmKHQucGFyZW50PW51bGwsdGhpcy5jaGlsZHJlbi5zcGxpY2UoZSwxKSx0LmRpc3BhdGNoRXZlbnQoUHVlKSksdGhpc31yZW1vdmVGcm9tUGFyZW50KCl7bGV0IHQ9dGhpcy5wYXJlbnQ7cmV0dXJuIG51bGwhPT10JiZ0LnJlbW92ZSh0aGlzKSx0aGlzfWNsZWFyKCl7Zm9yKGxldCB0PTA7dDx0aGlzLmNoaWxkcmVuLmxlbmd0aDt0Kyspe2xldCBlPXRoaXMuY2hpbGRyZW5bdF07ZS5wYXJlbnQ9bnVsbCxlLmRpc3BhdGNoRXZlbnQoUHVlKX1yZXR1cm4gdGhpcy5jaGlsZHJlbi5sZW5ndGg9MCx0aGlzfWF0dGFjaCh0KXtyZXR1cm4gdGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSksQ3AuY29weSh0aGlzLm1hdHJpeFdvcmxkKS5pbnZlcnQoKSxudWxsIT09dC5wYXJlbnQmJih0LnBhcmVudC51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSksQ3AubXVsdGlwbHkodC5wYXJlbnQubWF0cml4V29ybGQpKSx0LmFwcGx5TWF0cml4NChDcCksdGhpcy5hZGQodCksdC51cGRhdGVXb3JsZE1hdHJpeCghMSwhMCksdGhpc31nZXRPYmplY3RCeUlkKHQpe3JldHVybiB0aGlzLmdldE9iamVjdEJ5UHJvcGVydHkoImlkIix0KX1nZXRPYmplY3RCeU5hbWUodCl7cmV0dXJuIHRoaXMuZ2V0T2JqZWN0QnlQcm9wZXJ0eSgibmFtZSIsdCl9Z2V0T2JqZWN0QnlQcm9wZXJ0eSh0LGUpe2lmKHRoaXNbdF09PT1lKXJldHVybiB0aGlzO2ZvcihsZXQgaT0wLHI9dGhpcy5jaGlsZHJlbi5sZW5ndGg7aTxyO2krKyl7bGV0IHM9dGhpcy5jaGlsZHJlbltpXS5nZXRPYmplY3RCeVByb3BlcnR5KHQsZSk7aWYodm9pZCAwIT09cylyZXR1cm4gc319Z2V0V29ybGRQb3NpdGlvbih0KXtyZXR1cm4gdGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSksdC5zZXRGcm9tTWF0cml4UG9zaXRpb24odGhpcy5tYXRyaXhXb3JsZCl9Z2V0V29ybGRRdWF0ZXJuaW9uKHQpe3JldHVybiB0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKSx0aGlzLm1hdHJpeFdvcmxkLmRlY29tcG9zZShJUyx0LHE4ZSksdH1nZXRXb3JsZFNjYWxlKHQpe3JldHVybiB0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKSx0aGlzLm1hdHJpeFdvcmxkLmRlY29tcG9zZShJUyxZOGUsdCksdH1nZXRXb3JsZERpcmVjdGlvbih0KXt0aGlzLnVwZGF0ZVdvcmxkTWF0cml4KCEwLCExKTtsZXQgZT10aGlzLm1hdHJpeFdvcmxkLmVsZW1lbnRzO3JldHVybiB0LnNldChlWzhdLGVbOV0sZVsxMF0pLm5vcm1hbGl6ZSgpfXJheWNhc3QoKXt9dHJhdmVyc2UodCl7dCh0aGlzKTtsZXQgZT10aGlzLmNoaWxkcmVuO2ZvcihsZXQgaT0wLHI9ZS5sZW5ndGg7aTxyO2krKyllW2ldLnRyYXZlcnNlKHQpfXRyYXZlcnNlVmlzaWJsZSh0KXtpZighMT09PXRoaXMudmlzaWJsZSlyZXR1cm47dCh0aGlzKTtsZXQgZT10aGlzLmNoaWxkcmVuO2ZvcihsZXQgaT0wLHI9ZS5sZW5ndGg7aTxyO2krKyllW2ldLnRyYXZlcnNlVmlzaWJsZSh0KX10cmF2ZXJzZUFuY2VzdG9ycyh0KXtsZXQgZT10aGlzLnBhcmVudDtudWxsIT09ZSYmKHQoZSksZS50cmF2ZXJzZUFuY2VzdG9ycyh0KSl9dXBkYXRlTWF0cml4KCl7dGhpcy5tYXRyaXguY29tcG9zZSh0aGlzLnBvc2l0aW9uLHRoaXMucXVhdGVybmlvbix0aGlzLnNjYWxlKSx0aGlzLm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9ITB9dXBkYXRlTWF0cml4V29ybGQodCl7dGhpcy5tYXRyaXhBdXRvVXBkYXRlJiZ0aGlzLnVwZGF0ZU1hdHJpeCgpLCh0aGlzLm1hdHJpeFdvcmxkTmVlZHNVcGRhdGV8fHQpJiYobnVsbD09PXRoaXMucGFyZW50P3RoaXMubWF0cml4V29ybGQuY29weSh0aGlzLm1hdHJpeCk6dGhpcy5tYXRyaXhXb3JsZC5tdWx0aXBseU1hdHJpY2VzKHRoaXMucGFyZW50Lm1hdHJpeFdvcmxkLHRoaXMubWF0cml4KSx0aGlzLm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9ITEsdD0hMCk7bGV0IGU9dGhpcy5jaGlsZHJlbjtmb3IobGV0IGk9MCxyPWUubGVuZ3RoO2k8cjtpKyspZVtpXS51cGRhdGVNYXRyaXhXb3JsZCh0KX11cGRhdGVXb3JsZE1hdHJpeCh0LGUpe2xldCBpPXRoaXMucGFyZW50O2lmKCEwPT09dCYmbnVsbCE9PWkmJmkudXBkYXRlV29ybGRNYXRyaXgoITAsITEpLHRoaXMubWF0cml4QXV0b1VwZGF0ZSYmdGhpcy51cGRhdGVNYXRyaXgoKSxudWxsPT09dGhpcy5wYXJlbnQ/dGhpcy5tYXRyaXhXb3JsZC5jb3B5KHRoaXMubWF0cml4KTp0aGlzLm1hdHJpeFdvcmxkLm11bHRpcGx5TWF0cmljZXModGhpcy5wYXJlbnQubWF0cml4V29ybGQsdGhpcy5tYXRyaXgpLCEwPT09ZSl7bGV0IHI9dGhpcy5jaGlsZHJlbjtmb3IobGV0IG89MCxzPXIubGVuZ3RoO288cztvKyspcltvXS51cGRhdGVXb3JsZE1hdHJpeCghMSwhMCl9fXRvSlNPTih0KXtsZXQgZT12b2lkIDA9PT10fHwic3RyaW5nIj09dHlwZW9mIHQsaT17fTtlJiYodD17Z2VvbWV0cmllczp7fSxtYXRlcmlhbHM6e30sdGV4dHVyZXM6e30saW1hZ2VzOnt9LHNoYXBlczp7fSxza2VsZXRvbnM6e30sYW5pbWF0aW9uczp7fX0saS5tZXRhZGF0YT17dmVyc2lvbjo0LjUsdHlwZToiT2JqZWN0IixnZW5lcmF0b3I6Ik9iamVjdDNELnRvSlNPTiJ9KTtsZXQgcj17fTtmdW5jdGlvbiBvKGEsbCl7cmV0dXJuIHZvaWQgMD09PWFbbC51dWlkXSYmKGFbbC51dWlkXT1sLnRvSlNPTih0KSksbC51dWlkfWlmKHIudXVpZD10aGlzLnV1aWQsci50eXBlPXRoaXMudHlwZSwiIiE9PXRoaXMubmFtZSYmKHIubmFtZT10aGlzLm5hbWUpLCEwPT09dGhpcy5jYXN0U2hhZG93JiYoci5jYXN0U2hhZG93PSEwKSwhMD09PXRoaXMucmVjZWl2ZVNoYWRvdyYmKHIucmVjZWl2ZVNoYWRvdz0hMCksITE9PT10aGlzLnZpc2libGUmJihyLnZpc2libGU9ITEpLCExPT09dGhpcy5mcnVzdHVtQ3VsbGVkJiYoci5mcnVzdHVtQ3VsbGVkPSExKSwwIT09dGhpcy5yZW5kZXJPcmRlciYmKHIucmVuZGVyT3JkZXI9dGhpcy5yZW5kZXJPcmRlciksInt9IiE9PUpTT04uc3RyaW5naWZ5KHRoaXMudXNlckRhdGEpJiYoci51c2VyRGF0YT10aGlzLnVzZXJEYXRhKSxyLmxheWVycz10aGlzLmxheWVycy5tYXNrLHIubWF0cml4PXRoaXMubWF0cml4LnRvQXJyYXkoKSwhMT09PXRoaXMubWF0cml4QXV0b1VwZGF0ZSYmKHIubWF0cml4QXV0b1VwZGF0ZT0hMSksdGhpcy5pc0luc3RhbmNlZE1lc2gmJihyLnR5cGU9Ikluc3RhbmNlZE1lc2giLHIuY291bnQ9dGhpcy5jb3VudCxyLmluc3RhbmNlTWF0cml4PXRoaXMuaW5zdGFuY2VNYXRyaXgudG9KU09OKCksbnVsbCE9PXRoaXMuaW5zdGFuY2VDb2xvciYmKHIuaW5zdGFuY2VDb2xvcj10aGlzLmluc3RhbmNlQ29sb3IudG9KU09OKCkpKSx0aGlzLmlzU2NlbmUpdGhpcy5iYWNrZ3JvdW5kJiYodGhpcy5iYWNrZ3JvdW5kLmlzQ29sb3I/ci5iYWNrZ3JvdW5kPXRoaXMuYmFja2dyb3VuZC50b0pTT04oKTp0aGlzLmJhY2tncm91bmQuaXNUZXh0dXJlJiYoci5iYWNrZ3JvdW5kPXRoaXMuYmFja2dyb3VuZC50b0pTT04odCkudXVpZCkpLHRoaXMuZW52aXJvbm1lbnQmJnRoaXMuZW52aXJvbm1lbnQuaXNUZXh0dXJlJiYoci5lbnZpcm9ubWVudD10aGlzLmVudmlyb25tZW50LnRvSlNPTih0KS51dWlkKTtlbHNlIGlmKHRoaXMuaXNNZXNofHx0aGlzLmlzTGluZXx8dGhpcy5pc1BvaW50cyl7ci5nZW9tZXRyeT1vKHQuZ2VvbWV0cmllcyx0aGlzLmdlb21ldHJ5KTtsZXQgYT10aGlzLmdlb21ldHJ5LnBhcmFtZXRlcnM7aWYodm9pZCAwIT09YSYmdm9pZCAwIT09YS5zaGFwZXMpe2xldCBsPWEuc2hhcGVzO2lmKEFycmF5LmlzQXJyYXkobCkpZm9yKGxldCBjPTAsdT1sLmxlbmd0aDtjPHU7YysrKW8odC5zaGFwZXMsbFtjXSk7ZWxzZSBvKHQuc2hhcGVzLGwpfX1pZih0aGlzLmlzU2tpbm5lZE1lc2gmJihyLmJpbmRNb2RlPXRoaXMuYmluZE1vZGUsci5iaW5kTWF0cml4PXRoaXMuYmluZE1hdHJpeC50b0FycmF5KCksdm9pZCAwIT09dGhpcy5za2VsZXRvbiYmKG8odC5za2VsZXRvbnMsdGhpcy5za2VsZXRvbiksci5za2VsZXRvbj10aGlzLnNrZWxldG9uLnV1aWQpKSx2b2lkIDAhPT10aGlzLm1hdGVyaWFsKWlmKEFycmF5LmlzQXJyYXkodGhpcy5tYXRlcmlhbCkpe2xldCBhPVtdO2ZvcihsZXQgbD0wLGM9dGhpcy5tYXRlcmlhbC5sZW5ndGg7bDxjO2wrKylhLnB1c2gobyh0Lm1hdGVyaWFscyx0aGlzLm1hdGVyaWFsW2xdKSk7ci5tYXRlcmlhbD1hfWVsc2Ugci5tYXRlcmlhbD1vKHQubWF0ZXJpYWxzLHRoaXMubWF0ZXJpYWwpO2lmKHRoaXMuY2hpbGRyZW4ubGVuZ3RoPjApe3IuY2hpbGRyZW49W107Zm9yKGxldCBhPTA7YTx0aGlzLmNoaWxkcmVuLmxlbmd0aDthKyspci5jaGlsZHJlbi5wdXNoKHRoaXMuY2hpbGRyZW5bYV0udG9KU09OKHQpLm9iamVjdCl9aWYodGhpcy5hbmltYXRpb25zLmxlbmd0aD4wKXtyLmFuaW1hdGlvbnM9W107Zm9yKGxldCBhPTA7YTx0aGlzLmFuaW1hdGlvbnMubGVuZ3RoO2ErKylyLmFuaW1hdGlvbnMucHVzaChvKHQuYW5pbWF0aW9ucyx0aGlzLmFuaW1hdGlvbnNbYV0pKX1pZihlKXtsZXQgYT1zKHQuZ2VvbWV0cmllcyksbD1zKHQubWF0ZXJpYWxzKSxjPXModC50ZXh0dXJlcyksdT1zKHQuaW1hZ2VzKSxkPXModC5zaGFwZXMpLHA9cyh0LnNrZWxldG9ucyksaD1zKHQuYW5pbWF0aW9ucyk7YS5sZW5ndGg+MCYmKGkuZ2VvbWV0cmllcz1hKSxsLmxlbmd0aD4wJiYoaS5tYXRlcmlhbHM9bCksYy5sZW5ndGg+MCYmKGkudGV4dHVyZXM9YyksdS5sZW5ndGg+MCYmKGkuaW1hZ2VzPXUpLGQubGVuZ3RoPjAmJihpLnNoYXBlcz1kKSxwLmxlbmd0aD4wJiYoaS5za2VsZXRvbnM9cCksaC5sZW5ndGg+MCYmKGkuYW5pbWF0aW9ucz1oKX1yZXR1cm4gaS5vYmplY3Q9cixpO2Z1bmN0aW9uIHMoYSl7bGV0IGw9W107Zm9yKGxldCBjIGluIGEpe2xldCB1PWFbY107ZGVsZXRlIHUubWV0YWRhdGEsbC5wdXNoKHUpfXJldHVybiBsfX1jbG9uZSh0KXtyZXR1cm4obmV3IHRoaXMuY29uc3RydWN0b3IpLmNvcHkodGhpcyx0KX1jb3B5KHQsZT0hMCl7aWYodGhpcy5uYW1lPXQubmFtZSx0aGlzLnVwLmNvcHkodC51cCksdGhpcy5wb3NpdGlvbi5jb3B5KHQucG9zaXRpb24pLHRoaXMucm90YXRpb24ub3JkZXI9dC5yb3RhdGlvbi5vcmRlcix0aGlzLnF1YXRlcm5pb24uY29weSh0LnF1YXRlcm5pb24pLHRoaXMuc2NhbGUuY29weSh0LnNjYWxlKSx0aGlzLm1hdHJpeC5jb3B5KHQubWF0cml4KSx0aGlzLm1hdHJpeFdvcmxkLmNvcHkodC5tYXRyaXhXb3JsZCksdGhpcy5tYXRyaXhBdXRvVXBkYXRlPXQubWF0cml4QXV0b1VwZGF0ZSx0aGlzLm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9dC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlLHRoaXMubGF5ZXJzLm1hc2s9dC5sYXllcnMubWFzayx0aGlzLnZpc2libGU9dC52aXNpYmxlLHRoaXMuY2FzdFNoYWRvdz10LmNhc3RTaGFkb3csdGhpcy5yZWNlaXZlU2hhZG93PXQucmVjZWl2ZVNoYWRvdyx0aGlzLmZydXN0dW1DdWxsZWQ9dC5mcnVzdHVtQ3VsbGVkLHRoaXMucmVuZGVyT3JkZXI9dC5yZW5kZXJPcmRlcix0aGlzLnVzZXJEYXRhPUpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkodC51c2VyRGF0YSkpLCEwPT09ZSlmb3IobGV0IGk9MDtpPHQuY2hpbGRyZW4ubGVuZ3RoO2krKyl0aGlzLmFkZCh0LmNoaWxkcmVuW2ldLmNsb25lKCkpO3JldHVybiB0aGlzfX07WGkuRGVmYXVsdFVwPW5ldyBpZSgwLDEsMCksWGkuRGVmYXVsdE1hdHJpeEF1dG9VcGRhdGU9ITAsWGkucHJvdG90eXBlLmlzT2JqZWN0M0Q9ITA7dmFyIGN1PW5ldyBpZSxNcD1uZXcgaWUsSGo9bmV3IGllLHdwPW5ldyBpZSxXeT1uZXcgaWUscXk9bmV3IGllLFJ1ZT1uZXcgaWUsVWo9bmV3IGllLHpqPW5ldyBpZSxqaj1uZXcgaWUsbG89Y2xhc3N7Y29uc3RydWN0b3IodD1uZXcgaWUsZT1uZXcgaWUsaT1uZXcgaWUpe3RoaXMuYT10LHRoaXMuYj1lLHRoaXMuYz1pfXN0YXRpYyBnZXROb3JtYWwodCxlLGkscil7ci5zdWJWZWN0b3JzKGksZSksY3Uuc3ViVmVjdG9ycyh0LGUpLHIuY3Jvc3MoY3UpO2xldCBvPXIubGVuZ3RoU3EoKTtyZXR1cm4gbz4wP3IubXVsdGlwbHlTY2FsYXIoMS9NYXRoLnNxcnQobykpOnIuc2V0KDAsMCwwKX1zdGF0aWMgZ2V0QmFyeWNvb3JkKHQsZSxpLHIsbyl7Y3Uuc3ViVmVjdG9ycyhyLGUpLE1wLnN1YlZlY3RvcnMoaSxlKSxIai5zdWJWZWN0b3JzKHQsZSk7bGV0IHM9Y3UuZG90KGN1KSxhPWN1LmRvdChNcCksbD1jdS5kb3QoSGopLGM9TXAuZG90KE1wKSx1PU1wLmRvdChIaiksZD1zKmMtYSphO2lmKDA9PT1kKXJldHVybiBvLnNldCgtMiwtMSwtMSk7bGV0IHA9MS9kLGg9KGMqbC1hKnUpKnAsZj0ocyp1LWEqbCkqcDtyZXR1cm4gby5zZXQoMS1oLWYsZixoKX1zdGF0aWMgY29udGFpbnNQb2ludCh0LGUsaSxyKXtyZXR1cm4gdGhpcy5nZXRCYXJ5Y29vcmQodCxlLGkscix3cCksd3AueD49MCYmd3AueT49MCYmd3AueCt3cC55PD0xfXN0YXRpYyBnZXRVVih0LGUsaSxyLG8scyxhLGwpe3JldHVybiB0aGlzLmdldEJhcnljb29yZCh0LGUsaSxyLHdwKSxsLnNldCgwLDApLGwuYWRkU2NhbGVkVmVjdG9yKG8sd3AueCksbC5hZGRTY2FsZWRWZWN0b3Iocyx3cC55KSxsLmFkZFNjYWxlZFZlY3RvcihhLHdwLnopLGx9c3RhdGljIGlzRnJvbnRGYWNpbmcodCxlLGkscil7cmV0dXJuIGN1LnN1YlZlY3RvcnMoaSxlKSxNcC5zdWJWZWN0b3JzKHQsZSksY3UuY3Jvc3MoTXApLmRvdChyKTwwfXNldCh0LGUsaSl7cmV0dXJuIHRoaXMuYS5jb3B5KHQpLHRoaXMuYi5jb3B5KGUpLHRoaXMuYy5jb3B5KGkpLHRoaXN9c2V0RnJvbVBvaW50c0FuZEluZGljZXModCxlLGkscil7cmV0dXJuIHRoaXMuYS5jb3B5KHRbZV0pLHRoaXMuYi5jb3B5KHRbaV0pLHRoaXMuYy5jb3B5KHRbcl0pLHRoaXN9c2V0RnJvbUF0dHJpYnV0ZUFuZEluZGljZXModCxlLGkscil7cmV0dXJuIHRoaXMuYS5mcm9tQnVmZmVyQXR0cmlidXRlKHQsZSksdGhpcy5iLmZyb21CdWZmZXJBdHRyaWJ1dGUodCxpKSx0aGlzLmMuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LHIpLHRoaXN9Y2xvbmUoKXtyZXR1cm4obmV3IHRoaXMuY29uc3RydWN0b3IpLmNvcHkodGhpcyl9Y29weSh0KXtyZXR1cm4gdGhpcy5hLmNvcHkodC5hKSx0aGlzLmIuY29weSh0LmIpLHRoaXMuYy5jb3B5KHQuYyksdGhpc31nZXRBcmVhKCl7cmV0dXJuIGN1LnN1YlZlY3RvcnModGhpcy5jLHRoaXMuYiksTXAuc3ViVmVjdG9ycyh0aGlzLmEsdGhpcy5iKSwuNSpjdS5jcm9zcyhNcCkubGVuZ3RoKCl9Z2V0TWlkcG9pbnQodCl7cmV0dXJuIHQuYWRkVmVjdG9ycyh0aGlzLmEsdGhpcy5iKS5hZGQodGhpcy5jKS5tdWx0aXBseVNjYWxhcigxLzMpfWdldE5vcm1hbCh0KXtyZXR1cm4gbG8uZ2V0Tm9ybWFsKHRoaXMuYSx0aGlzLmIsdGhpcy5jLHQpfWdldFBsYW5lKHQpe3JldHVybiB0LnNldEZyb21Db3BsYW5hclBvaW50cyh0aGlzLmEsdGhpcy5iLHRoaXMuYyl9Z2V0QmFyeWNvb3JkKHQsZSl7cmV0dXJuIGxvLmdldEJhcnljb29yZCh0LHRoaXMuYSx0aGlzLmIsdGhpcy5jLGUpfWdldFVWKHQsZSxpLHIsbyl7cmV0dXJuIGxvLmdldFVWKHQsdGhpcy5hLHRoaXMuYix0aGlzLmMsZSxpLHIsbyl9Y29udGFpbnNQb2ludCh0KXtyZXR1cm4gbG8uY29udGFpbnNQb2ludCh0LHRoaXMuYSx0aGlzLmIsdGhpcy5jKX1pc0Zyb250RmFjaW5nKHQpe3JldHVybiBsby5pc0Zyb250RmFjaW5nKHRoaXMuYSx0aGlzLmIsdGhpcy5jLHQpfWludGVyc2VjdHNCb3godCl7cmV0dXJuIHQuaW50ZXJzZWN0c1RyaWFuZ2xlKHRoaXMpfWNsb3Nlc3RQb2ludFRvUG9pbnQodCxlKXtsZXQgcyxhLGk9dGhpcy5hLHI9dGhpcy5iLG89dGhpcy5jO1d5LnN1YlZlY3RvcnMocixpKSxxeS5zdWJWZWN0b3JzKG8saSksVWouc3ViVmVjdG9ycyh0LGkpO2xldCBsPVd5LmRvdChVaiksYz1xeS5kb3QoVWopO2lmKGw8PTAmJmM8PTApcmV0dXJuIGUuY29weShpKTt6ai5zdWJWZWN0b3JzKHQscik7bGV0IHU9V3kuZG90KHpqKSxkPXF5LmRvdCh6aik7aWYodT49MCYmZDw9dSlyZXR1cm4gZS5jb3B5KHIpO2xldCBwPWwqZC11KmM7aWYocDw9MCYmbD49MCYmdTw9MClyZXR1cm4gcz1sLyhsLXUpLGUuY29weShpKS5hZGRTY2FsZWRWZWN0b3IoV3kscyk7amouc3ViVmVjdG9ycyh0LG8pO2xldCBoPVd5LmRvdChqaiksZj1xeS5kb3QoamopO2lmKGY+PTAmJmg8PWYpcmV0dXJuIGUuY29weShvKTtsZXQgbT1oKmMtbCpmO2lmKG08PTAmJmM+PTAmJmY8PTApcmV0dXJuIGE9Yy8oYy1mKSxlLmNvcHkoaSkuYWRkU2NhbGVkVmVjdG9yKHF5LGEpO2xldCB4PXUqZi1oKmQ7aWYoeDw9MCYmZC11Pj0wJiZoLWY+PTApcmV0dXJuIFJ1ZS5zdWJWZWN0b3JzKG8sciksYT0oZC11KS8oZC11KyhoLWYpKSxlLmNvcHkocikuYWRkU2NhbGVkVmVjdG9yKFJ1ZSxhKTtsZXQgZz0xLyh4K20rcCk7cmV0dXJuIHM9bSpnLGE9cCpnLGUuY29weShpKS5hZGRTY2FsZWRWZWN0b3IoV3kscykuYWRkU2NhbGVkVmVjdG9yKHF5LGEpfWVxdWFscyh0KXtyZXR1cm4gdC5hLmVxdWFscyh0aGlzLmEpJiZ0LmIuZXF1YWxzKHRoaXMuYikmJnQuYy5lcXVhbHModGhpcy5jKX19LFE4ZT0wLGhzPWNsYXNzIGV4dGVuZHMgRXB7Y29uc3RydWN0b3IoKXtzdXBlcigpLE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCJpZCIse3ZhbHVlOlE4ZSsrfSksdGhpcy51dWlkPWR1KCksdGhpcy5uYW1lPSIiLHRoaXMudHlwZT0iTWF0ZXJpYWwiLHRoaXMuZm9nPSEwLHRoaXMuYmxlbmRpbmc9MSx0aGlzLnNpZGU9MCx0aGlzLnZlcnRleENvbG9ycz0hMSx0aGlzLm9wYWNpdHk9MSx0aGlzLnRyYW5zcGFyZW50PSExLHRoaXMuYmxlbmRTcmM9MjA0LHRoaXMuYmxlbmREc3Q9MjA1LHRoaXMuYmxlbmRFcXVhdGlvbj0xMDAsdGhpcy5ibGVuZFNyY0FscGhhPW51bGwsdGhpcy5ibGVuZERzdEFscGhhPW51bGwsdGhpcy5ibGVuZEVxdWF0aW9uQWxwaGE9bnVsbCx0aGlzLmRlcHRoRnVuYz0zLHRoaXMuZGVwdGhUZXN0PSEwLHRoaXMuZGVwdGhXcml0ZT0hMCx0aGlzLnN0ZW5jaWxXcml0ZU1hc2s9MjU1LHRoaXMuc3RlbmNpbEZ1bmM9NTE5LHRoaXMuc3RlbmNpbFJlZj0wLHRoaXMuc3RlbmNpbEZ1bmNNYXNrPTI1NSx0aGlzLnN0ZW5jaWxGYWlsPTc2ODAsdGhpcy5zdGVuY2lsWkZhaWw9NzY4MCx0aGlzLnN0ZW5jaWxaUGFzcz03NjgwLHRoaXMuc3RlbmNpbFdyaXRlPSExLHRoaXMuY2xpcHBpbmdQbGFuZXM9bnVsbCx0aGlzLmNsaXBJbnRlcnNlY3Rpb249ITEsdGhpcy5jbGlwU2hhZG93cz0hMSx0aGlzLnNoYWRvd1NpZGU9bnVsbCx0aGlzLmNvbG9yV3JpdGU9ITAsdGhpcy5hbHBoYVdyaXRlPSEwLHRoaXMucHJlY2lzaW9uPW51bGwsdGhpcy5wb2x5Z29uT2Zmc2V0PSExLHRoaXMucG9seWdvbk9mZnNldEZhY3Rvcj0wLHRoaXMucG9seWdvbk9mZnNldFVuaXRzPTAsdGhpcy5kaXRoZXJpbmc9ITEsdGhpcy5hbHBoYVRvQ292ZXJhZ2U9ITEsdGhpcy5wcmVtdWx0aXBsaWVkQWxwaGE9ITEsdGhpcy52aXNpYmxlPSEwLHRoaXMudG9uZU1hcHBlZD0hMCx0aGlzLnVzZXJEYXRhPXt9LHRoaXMudmVyc2lvbj0wLHRoaXMuX2FscGhhVGVzdD0wfWdldCBhbHBoYVRlc3QoKXtyZXR1cm4gdGhpcy5fYWxwaGFUZXN0fXNldCBhbHBoYVRlc3QodCl7dGhpcy5fYWxwaGFUZXN0PjAhPXQ+MCYmdGhpcy52ZXJzaW9uKyssdGhpcy5fYWxwaGFUZXN0PXR9b25CdWlsZCgpe31vbkJlZm9yZVJlbmRlcigpe31vbkJlZm9yZUNvbXBpbGUoKXt9Y3VzdG9tUHJvZ3JhbUNhY2hlS2V5KCl7cmV0dXJuIHRoaXMub25CZWZvcmVDb21waWxlLnRvU3RyaW5nKCl9c2V0VmFsdWVzKHQpe2lmKHZvaWQgMCE9PXQpZm9yKGxldCBlIGluIHQpe2xldCBpPXRbZV07aWYodm9pZCAwPT09aSl7Y29uc29sZS53YXJuKCJUSFJFRS5NYXRlcmlhbDogJyIrZSsiJyBwYXJhbWV0ZXIgaXMgdW5kZWZpbmVkLiIpO2NvbnRpbnVlfWlmKCJzaGFkaW5nIj09PWUpe2NvbnNvbGUud2FybigiVEhSRUUuIit0aGlzLnR5cGUrIjogLnNoYWRpbmcgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIHRoZSBib29sZWFuIC5mbGF0U2hhZGluZyBpbnN0ZWFkLiIpLHRoaXMuZmxhdFNoYWRpbmc9MT09PWk7Y29udGludWV9bGV0IHI9dGhpc1tlXTt2b2lkIDAhPT1yP3ImJnIuaXNDb2xvcj9yLnNldChpKTpyJiZyLmlzVmVjdG9yMyYmaSYmaS5pc1ZlY3RvcjM/ci5jb3B5KGkpOnRoaXNbZV09aTpjb25zb2xlLndhcm4oIlRIUkVFLiIrdGhpcy50eXBlKyI6ICciK2UrIicgaXMgbm90IGEgcHJvcGVydHkgb2YgdGhpcyBtYXRlcmlhbC4iKX19dG9KU09OKHQpe2xldCBlPXZvaWQgMD09PXR8fCJzdHJpbmciPT10eXBlb2YgdDtlJiYodD17dGV4dHVyZXM6e30saW1hZ2VzOnt9fSk7bGV0IGk9e21ldGFkYXRhOnt2ZXJzaW9uOjQuNSx0eXBlOiJNYXRlcmlhbCIsZ2VuZXJhdG9yOiJNYXRlcmlhbC50b0pTT04ifX07ZnVuY3Rpb24gcihvKXtsZXQgcz1bXTtmb3IobGV0IGEgaW4gbyl7bGV0IGw9b1thXTtkZWxldGUgbC5tZXRhZGF0YSxzLnB1c2gobCl9cmV0dXJuIHN9aWYoaS51dWlkPXRoaXMudXVpZCxpLnR5cGU9dGhpcy50eXBlLCIiIT09dGhpcy5uYW1lJiYoaS5uYW1lPXRoaXMubmFtZSksdGhpcy5jb2xvciYmdGhpcy5jb2xvci5pc0NvbG9yJiYoaS5jb2xvcj10aGlzLmNvbG9yLmdldEhleCgpKSx2b2lkIDAhPT10aGlzLnJvdWdobmVzcyYmKGkucm91Z2huZXNzPXRoaXMucm91Z2huZXNzKSx2b2lkIDAhPT10aGlzLm1ldGFsbmVzcyYmKGkubWV0YWxuZXNzPXRoaXMubWV0YWxuZXNzKSx2b2lkIDAhPT10aGlzLnNoZWVuJiYoaS5zaGVlbj10aGlzLnNoZWVuKSx0aGlzLnNoZWVuQ29sb3ImJnRoaXMuc2hlZW5Db2xvci5pc0NvbG9yJiYoaS5zaGVlbkNvbG9yPXRoaXMuc2hlZW5Db2xvci5nZXRIZXgoKSksdm9pZCAwIT09dGhpcy5zaGVlblJvdWdobmVzcyYmKGkuc2hlZW5Sb3VnaG5lc3M9dGhpcy5zaGVlblJvdWdobmVzcyksdGhpcy5lbWlzc2l2ZSYmdGhpcy5lbWlzc2l2ZS5pc0NvbG9yJiYoaS5lbWlzc2l2ZT10aGlzLmVtaXNzaXZlLmdldEhleCgpKSx0aGlzLmVtaXNzaXZlSW50ZW5zaXR5JiYxIT09dGhpcy5lbWlzc2l2ZUludGVuc2l0eSYmKGkuZW1pc3NpdmVJbnRlbnNpdHk9dGhpcy5lbWlzc2l2ZUludGVuc2l0eSksdGhpcy5zcGVjdWxhciYmdGhpcy5zcGVjdWxhci5pc0NvbG9yJiYoaS5zcGVjdWxhcj10aGlzLnNwZWN1bGFyLmdldEhleCgpKSx2b2lkIDAhPT10aGlzLnNwZWN1bGFySW50ZW5zaXR5JiYoaS5zcGVjdWxhckludGVuc2l0eT10aGlzLnNwZWN1bGFySW50ZW5zaXR5KSx0aGlzLnNwZWN1bGFyQ29sb3ImJnRoaXMuc3BlY3VsYXJDb2xvci5pc0NvbG9yJiYoaS5zcGVjdWxhckNvbG9yPXRoaXMuc3BlY3VsYXJDb2xvci5nZXRIZXgoKSksdm9pZCAwIT09dGhpcy5zaGluaW5lc3MmJihpLnNoaW5pbmVzcz10aGlzLnNoaW5pbmVzcyksdm9pZCAwIT09dGhpcy5jbGVhcmNvYXQmJihpLmNsZWFyY29hdD10aGlzLmNsZWFyY29hdCksdm9pZCAwIT09dGhpcy5jbGVhcmNvYXRSb3VnaG5lc3MmJihpLmNsZWFyY29hdFJvdWdobmVzcz10aGlzLmNsZWFyY29hdFJvdWdobmVzcyksdGhpcy5jbGVhcmNvYXRNYXAmJnRoaXMuY2xlYXJjb2F0TWFwLmlzVGV4dHVyZSYmKGkuY2xlYXJjb2F0TWFwPXRoaXMuY2xlYXJjb2F0TWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLmNsZWFyY29hdFJvdWdobmVzc01hcCYmdGhpcy5jbGVhcmNvYXRSb3VnaG5lc3NNYXAuaXNUZXh0dXJlJiYoaS5jbGVhcmNvYXRSb3VnaG5lc3NNYXA9dGhpcy5jbGVhcmNvYXRSb3VnaG5lc3NNYXAudG9KU09OKHQpLnV1aWQpLHRoaXMuY2xlYXJjb2F0Tm9ybWFsTWFwJiZ0aGlzLmNsZWFyY29hdE5vcm1hbE1hcC5pc1RleHR1cmUmJihpLmNsZWFyY29hdE5vcm1hbE1hcD10aGlzLmNsZWFyY29hdE5vcm1hbE1hcC50b0pTT04odCkudXVpZCxpLmNsZWFyY29hdE5vcm1hbFNjYWxlPXRoaXMuY2xlYXJjb2F0Tm9ybWFsU2NhbGUudG9BcnJheSgpKSx0aGlzLm1hcCYmdGhpcy5tYXAuaXNUZXh0dXJlJiYoaS5tYXA9dGhpcy5tYXAudG9KU09OKHQpLnV1aWQpLHRoaXMubWF0Y2FwJiZ0aGlzLm1hdGNhcC5pc1RleHR1cmUmJihpLm1hdGNhcD10aGlzLm1hdGNhcC50b0pTT04odCkudXVpZCksdGhpcy5hbHBoYU1hcCYmdGhpcy5hbHBoYU1hcC5pc1RleHR1cmUmJihpLmFscGhhTWFwPXRoaXMuYWxwaGFNYXAudG9KU09OKHQpLnV1aWQpLHRoaXMubGlnaHRNYXAmJnRoaXMubGlnaHRNYXAuaXNUZXh0dXJlJiYoaS5saWdodE1hcD10aGlzLmxpZ2h0TWFwLnRvSlNPTih0KS51dWlkLGkubGlnaHRNYXBJbnRlbnNpdHk9dGhpcy5saWdodE1hcEludGVuc2l0eSksdGhpcy5hb01hcCYmdGhpcy5hb01hcC5pc1RleHR1cmUmJihpLmFvTWFwPXRoaXMuYW9NYXAudG9KU09OKHQpLnV1aWQsaS5hb01hcEludGVuc2l0eT10aGlzLmFvTWFwSW50ZW5zaXR5KSx0aGlzLmJ1bXBNYXAmJnRoaXMuYnVtcE1hcC5pc1RleHR1cmUmJihpLmJ1bXBNYXA9dGhpcy5idW1wTWFwLnRvSlNPTih0KS51dWlkLGkuYnVtcFNjYWxlPXRoaXMuYnVtcFNjYWxlKSx0aGlzLm5vcm1hbE1hcCYmdGhpcy5ub3JtYWxNYXAuaXNUZXh0dXJlJiYoaS5ub3JtYWxNYXA9dGhpcy5ub3JtYWxNYXAudG9KU09OKHQpLnV1aWQsaS5ub3JtYWxNYXBUeXBlPXRoaXMubm9ybWFsTWFwVHlwZSxpLm5vcm1hbFNjYWxlPXRoaXMubm9ybWFsU2NhbGUudG9BcnJheSgpKSx0aGlzLmRpc3BsYWNlbWVudE1hcCYmdGhpcy5kaXNwbGFjZW1lbnRNYXAuaXNUZXh0dXJlJiYoaS5kaXNwbGFjZW1lbnRNYXA9dGhpcy5kaXNwbGFjZW1lbnRNYXAudG9KU09OKHQpLnV1aWQsaS5kaXNwbGFjZW1lbnRTY2FsZT10aGlzLmRpc3BsYWNlbWVudFNjYWxlLGkuZGlzcGxhY2VtZW50Qmlhcz10aGlzLmRpc3BsYWNlbWVudEJpYXMpLHRoaXMucm91Z2huZXNzTWFwJiZ0aGlzLnJvdWdobmVzc01hcC5pc1RleHR1cmUmJihpLnJvdWdobmVzc01hcD10aGlzLnJvdWdobmVzc01hcC50b0pTT04odCkudXVpZCksdGhpcy5tZXRhbG5lc3NNYXAmJnRoaXMubWV0YWxuZXNzTWFwLmlzVGV4dHVyZSYmKGkubWV0YWxuZXNzTWFwPXRoaXMubWV0YWxuZXNzTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLmVtaXNzaXZlTWFwJiZ0aGlzLmVtaXNzaXZlTWFwLmlzVGV4dHVyZSYmKGkuZW1pc3NpdmVNYXA9dGhpcy5lbWlzc2l2ZU1hcC50b0pTT04odCkudXVpZCksdGhpcy5zcGVjdWxhck1hcCYmdGhpcy5zcGVjdWxhck1hcC5pc1RleHR1cmUmJihpLnNwZWN1bGFyTWFwPXRoaXMuc3BlY3VsYXJNYXAudG9KU09OKHQpLnV1aWQpLHRoaXMuc3BlY3VsYXJJbnRlbnNpdHlNYXAmJnRoaXMuc3BlY3VsYXJJbnRlbnNpdHlNYXAuaXNUZXh0dXJlJiYoaS5zcGVjdWxhckludGVuc2l0eU1hcD10aGlzLnNwZWN1bGFySW50ZW5zaXR5TWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLnNwZWN1bGFyQ29sb3JNYXAmJnRoaXMuc3BlY3VsYXJDb2xvck1hcC5pc1RleHR1cmUmJihpLnNwZWN1bGFyQ29sb3JNYXA9dGhpcy5zcGVjdWxhckNvbG9yTWFwLnRvSlNPTih0KS51dWlkKSx0aGlzLmVudk1hcCYmdGhpcy5lbnZNYXAuaXNUZXh0dXJlJiYoaS5lbnZNYXA9dGhpcy5lbnZNYXAudG9KU09OKHQpLnV1aWQsdm9pZCAwIT09dGhpcy5jb21iaW5lJiYoaS5jb21iaW5lPXRoaXMuY29tYmluZSkpLHZvaWQgMCE9PXRoaXMuZW52TWFwSW50ZW5zaXR5JiYoaS5lbnZNYXBJbnRlbnNpdHk9dGhpcy5lbnZNYXBJbnRlbnNpdHkpLHZvaWQgMCE9PXRoaXMucmVmbGVjdGl2aXR5JiYoaS5yZWZsZWN0aXZpdHk9dGhpcy5yZWZsZWN0aXZpdHkpLHZvaWQgMCE9PXRoaXMucmVmcmFjdGlvblJhdGlvJiYoaS5yZWZyYWN0aW9uUmF0aW89dGhpcy5yZWZyYWN0aW9uUmF0aW8pLHRoaXMuZ3JhZGllbnRNYXAmJnRoaXMuZ3JhZGllbnRNYXAuaXNUZXh0dXJlJiYoaS5ncmFkaWVudE1hcD10aGlzLmdyYWRpZW50TWFwLnRvSlNPTih0KS51dWlkKSx2b2lkIDAhPT10aGlzLnRyYW5zbWlzc2lvbiYmKGkudHJhbnNtaXNzaW9uPXRoaXMudHJhbnNtaXNzaW9uKSx0aGlzLnRyYW5zbWlzc2lvbk1hcCYmdGhpcy50cmFuc21pc3Npb25NYXAuaXNUZXh0dXJlJiYoaS50cmFuc21pc3Npb25NYXA9dGhpcy50cmFuc21pc3Npb25NYXAudG9KU09OKHQpLnV1aWQpLHZvaWQgMCE9PXRoaXMudGhpY2tuZXNzJiYoaS50aGlja25lc3M9dGhpcy50aGlja25lc3MpLHRoaXMudGhpY2tuZXNzTWFwJiZ0aGlzLnRoaWNrbmVzc01hcC5pc1RleHR1cmUmJihpLnRoaWNrbmVzc01hcD10aGlzLnRoaWNrbmVzc01hcC50b0pTT04odCkudXVpZCksdm9pZCAwIT09dGhpcy5hdHRlbnVhdGlvbkRpc3RhbmNlJiYoaS5hdHRlbnVhdGlvbkRpc3RhbmNlPXRoaXMuYXR0ZW51YXRpb25EaXN0YW5jZSksdm9pZCAwIT09dGhpcy5hdHRlbnVhdGlvbkNvbG9yJiYoaS5hdHRlbnVhdGlvbkNvbG9yPXRoaXMuYXR0ZW51YXRpb25Db2xvci5nZXRIZXgoKSksdm9pZCAwIT09dGhpcy5zaXplJiYoaS5zaXplPXRoaXMuc2l6ZSksbnVsbCE9PXRoaXMuc2hhZG93U2lkZSYmKGkuc2hhZG93U2lkZT10aGlzLnNoYWRvd1NpZGUpLHZvaWQgMCE9PXRoaXMuc2l6ZUF0dGVudWF0aW9uJiYoaS5zaXplQXR0ZW51YXRpb249dGhpcy5zaXplQXR0ZW51YXRpb24pLDEhPT10aGlzLmJsZW5kaW5nJiYoaS5ibGVuZGluZz10aGlzLmJsZW5kaW5nKSwwIT09dGhpcy5zaWRlJiYoaS5zaWRlPXRoaXMuc2lkZSksdGhpcy52ZXJ0ZXhDb2xvcnMmJihpLnZlcnRleENvbG9ycz0hMCksdGhpcy5vcGFjaXR5PDEmJihpLm9wYWNpdHk9dGhpcy5vcGFjaXR5KSwhMD09PXRoaXMudHJhbnNwYXJlbnQmJihpLnRyYW5zcGFyZW50PXRoaXMudHJhbnNwYXJlbnQpLGkuZGVwdGhGdW5jPXRoaXMuZGVwdGhGdW5jLGkuZGVwdGhUZXN0PXRoaXMuZGVwdGhUZXN0LGkuZGVwdGhXcml0ZT10aGlzLmRlcHRoV3JpdGUsaS5jb2xvcldyaXRlPXRoaXMuY29sb3JXcml0ZSxpLmFscGhhV3JpdGU9dGhpcy5hbHBoYVdyaXRlLGkuc3RlbmNpbFdyaXRlPXRoaXMuc3RlbmNpbFdyaXRlLGkuc3RlbmNpbFdyaXRlTWFzaz10aGlzLnN0ZW5jaWxXcml0ZU1hc2ssaS5zdGVuY2lsRnVuYz10aGlzLnN0ZW5jaWxGdW5jLGkuc3RlbmNpbFJlZj10aGlzLnN0ZW5jaWxSZWYsaS5zdGVuY2lsRnVuY01hc2s9dGhpcy5zdGVuY2lsRnVuY01hc2ssaS5zdGVuY2lsRmFpbD10aGlzLnN0ZW5jaWxGYWlsLGkuc3RlbmNpbFpGYWlsPXRoaXMuc3RlbmNpbFpGYWlsLGkuc3RlbmNpbFpQYXNzPXRoaXMuc3RlbmNpbFpQYXNzLHRoaXMucm90YXRpb24mJjAhPT10aGlzLnJvdGF0aW9uJiYoaS5yb3RhdGlvbj10aGlzLnJvdGF0aW9uKSwhMD09PXRoaXMucG9seWdvbk9mZnNldCYmKGkucG9seWdvbk9mZnNldD0hMCksMCE9PXRoaXMucG9seWdvbk9mZnNldEZhY3RvciYmKGkucG9seWdvbk9mZnNldEZhY3Rvcj10aGlzLnBvbHlnb25PZmZzZXRGYWN0b3IpLDAhPT10aGlzLnBvbHlnb25PZmZzZXRVbml0cyYmKGkucG9seWdvbk9mZnNldFVuaXRzPXRoaXMucG9seWdvbk9mZnNldFVuaXRzKSx0aGlzLmxpbmV3aWR0aCYmMSE9PXRoaXMubGluZXdpZHRoJiYoaS5saW5ld2lkdGg9dGhpcy5saW5ld2lkdGgpLHZvaWQgMCE9PXRoaXMuZGFzaFNpemUmJihpLmRhc2hTaXplPXRoaXMuZGFzaFNpemUpLHZvaWQgMCE9PXRoaXMuZ2FwU2l6ZSYmKGkuZ2FwU2l6ZT10aGlzLmdhcFNpemUpLHZvaWQgMCE9PXRoaXMuc2NhbGUmJihpLnNjYWxlPXRoaXMuc2NhbGUpLCEwPT09dGhpcy5kaXRoZXJpbmcmJihpLmRpdGhlcmluZz0hMCksdGhpcy5hbHBoYVRlc3Q+MCYmKGkuYWxwaGFUZXN0PXRoaXMuYWxwaGFUZXN0KSwhMD09PXRoaXMuYWxwaGFUb0NvdmVyYWdlJiYoaS5hbHBoYVRvQ292ZXJhZ2U9dGhpcy5hbHBoYVRvQ292ZXJhZ2UpLCEwPT09dGhpcy5wcmVtdWx0aXBsaWVkQWxwaGEmJihpLnByZW11bHRpcGxpZWRBbHBoYT10aGlzLnByZW11bHRpcGxpZWRBbHBoYSksITA9PT10aGlzLndpcmVmcmFtZSYmKGkud2lyZWZyYW1lPXRoaXMud2lyZWZyYW1lKSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD4xJiYoaS53aXJlZnJhbWVMaW5ld2lkdGg9dGhpcy53aXJlZnJhbWVMaW5ld2lkdGgpLCJyb3VuZCIhPT10aGlzLndpcmVmcmFtZUxpbmVjYXAmJihpLndpcmVmcmFtZUxpbmVjYXA9dGhpcy53aXJlZnJhbWVMaW5lY2FwKSwicm91bmQiIT09dGhpcy53aXJlZnJhbWVMaW5lam9pbiYmKGkud2lyZWZyYW1lTGluZWpvaW49dGhpcy53aXJlZnJhbWVMaW5lam9pbiksITA9PT10aGlzLmZsYXRTaGFkaW5nJiYoaS5mbGF0U2hhZGluZz10aGlzLmZsYXRTaGFkaW5nKSwhMT09PXRoaXMudmlzaWJsZSYmKGkudmlzaWJsZT0hMSksITE9PT10aGlzLnRvbmVNYXBwZWQmJihpLnRvbmVNYXBwZWQ9ITEpLCJ7fSIhPT1KU09OLnN0cmluZ2lmeSh0aGlzLnVzZXJEYXRhKSYmKGkudXNlckRhdGE9dGhpcy51c2VyRGF0YSksZSl7bGV0IG89cih0LnRleHR1cmVzKSxzPXIodC5pbWFnZXMpO28ubGVuZ3RoPjAmJihpLnRleHR1cmVzPW8pLHMubGVuZ3RoPjAmJihpLmltYWdlcz1zKX1yZXR1cm4gaX1jbG9uZSgpe3JldHVybihuZXcgdGhpcy5jb25zdHJ1Y3RvcikuY29weSh0aGlzKX1jb3B5KHQpe3RoaXMubmFtZT10Lm5hbWUsdGhpcy5mb2c9dC5mb2csdGhpcy5ibGVuZGluZz10LmJsZW5kaW5nLHRoaXMuc2lkZT10LnNpZGUsdGhpcy52ZXJ0ZXhDb2xvcnM9dC52ZXJ0ZXhDb2xvcnMsdGhpcy5vcGFjaXR5PXQub3BhY2l0eSx0aGlzLnRyYW5zcGFyZW50PXQudHJhbnNwYXJlbnQsdGhpcy5ibGVuZFNyYz10LmJsZW5kU3JjLHRoaXMuYmxlbmREc3Q9dC5ibGVuZERzdCx0aGlzLmJsZW5kRXF1YXRpb249dC5ibGVuZEVxdWF0aW9uLHRoaXMuYmxlbmRTcmNBbHBoYT10LmJsZW5kU3JjQWxwaGEsdGhpcy5ibGVuZERzdEFscGhhPXQuYmxlbmREc3RBbHBoYSx0aGlzLmJsZW5kRXF1YXRpb25BbHBoYT10LmJsZW5kRXF1YXRpb25BbHBoYSx0aGlzLmRlcHRoRnVuYz10LmRlcHRoRnVuYyx0aGlzLmRlcHRoVGVzdD10LmRlcHRoVGVzdCx0aGlzLmRlcHRoV3JpdGU9dC5kZXB0aFdyaXRlLHRoaXMuc3RlbmNpbFdyaXRlTWFzaz10LnN0ZW5jaWxXcml0ZU1hc2ssdGhpcy5zdGVuY2lsRnVuYz10LnN0ZW5jaWxGdW5jLHRoaXMuc3RlbmNpbFJlZj10LnN0ZW5jaWxSZWYsdGhpcy5zdGVuY2lsRnVuY01hc2s9dC5zdGVuY2lsRnVuY01hc2ssdGhpcy5zdGVuY2lsRmFpbD10LnN0ZW5jaWxGYWlsLHRoaXMuc3RlbmNpbFpGYWlsPXQuc3RlbmNpbFpGYWlsLHRoaXMuc3RlbmNpbFpQYXNzPXQuc3RlbmNpbFpQYXNzLHRoaXMuc3RlbmNpbFdyaXRlPXQuc3RlbmNpbFdyaXRlO2xldCBlPXQuY2xpcHBpbmdQbGFuZXMsaT1udWxsO2lmKG51bGwhPT1lKXtsZXQgcj1lLmxlbmd0aDtpPW5ldyBBcnJheShyKTtmb3IobGV0IG89MDtvIT09cjsrK28paVtvXT1lW29dLmNsb25lKCl9cmV0dXJuIHRoaXMuY2xpcHBpbmdQbGFuZXM9aSx0aGlzLmNsaXBJbnRlcnNlY3Rpb249dC5jbGlwSW50ZXJzZWN0aW9uLHRoaXMuY2xpcFNoYWRvd3M9dC5jbGlwU2hhZG93cyx0aGlzLnNoYWRvd1NpZGU9dC5zaGFkb3dTaWRlLHRoaXMuY29sb3JXcml0ZT10LmNvbG9yV3JpdGUsdGhpcy5hbHBoYVdyaXRlPXQuYWxwaGFXcml0ZSx0aGlzLnByZWNpc2lvbj10LnByZWNpc2lvbix0aGlzLnBvbHlnb25PZmZzZXQ9dC5wb2x5Z29uT2Zmc2V0LHRoaXMucG9seWdvbk9mZnNldEZhY3Rvcj10LnBvbHlnb25PZmZzZXRGYWN0b3IsdGhpcy5wb2x5Z29uT2Zmc2V0VW5pdHM9dC5wb2x5Z29uT2Zmc2V0VW5pdHMsdGhpcy5kaXRoZXJpbmc9dC5kaXRoZXJpbmcsdGhpcy5hbHBoYVRlc3Q9dC5hbHBoYVRlc3QsdGhpcy5hbHBoYVRvQ292ZXJhZ2U9dC5hbHBoYVRvQ292ZXJhZ2UsdGhpcy5wcmVtdWx0aXBsaWVkQWxwaGE9dC5wcmVtdWx0aXBsaWVkQWxwaGEsdGhpcy52aXNpYmxlPXQudmlzaWJsZSx0aGlzLnRvbmVNYXBwZWQ9dC50b25lTWFwcGVkLHRoaXMudXNlckRhdGE9SlNPTi5wYXJzZShKU09OLnN0cmluZ2lmeSh0LnVzZXJEYXRhKSksdGhpc31kaXNwb3NlKCl7dGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJkaXNwb3NlIn0pfXNldCBuZWVkc1VwZGF0ZSh0KXshMD09PXQmJnRoaXMudmVyc2lvbisrfX07aHMucHJvdG90eXBlLmlzTWF0ZXJpYWw9ITA7dmFyIEdnPWNsYXNzIGV4dGVuZHMgaHN7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9Ik1lc2hCYXNpY01hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyB2bigxNjc3NzIxNSksdGhpcy5tYXA9bnVsbCx0aGlzLmxpZ2h0TWFwPW51bGwsdGhpcy5saWdodE1hcEludGVuc2l0eT0xLHRoaXMuYW9NYXA9bnVsbCx0aGlzLmFvTWFwSW50ZW5zaXR5PTEsdGhpcy5zcGVjdWxhck1hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLmVudk1hcD1udWxsLHRoaXMuY29tYmluZT0wLHRoaXMucmVmbGVjdGl2aXR5PTEsdGhpcy5yZWZyYWN0aW9uUmF0aW89Ljk4LHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy53aXJlZnJhbWVMaW5lY2FwPSJyb3VuZCIsdGhpcy53aXJlZnJhbWVMaW5lam9pbj0icm91bmQiLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMubWFwPXQubWFwLHRoaXMubGlnaHRNYXA9dC5saWdodE1hcCx0aGlzLmxpZ2h0TWFwSW50ZW5zaXR5PXQubGlnaHRNYXBJbnRlbnNpdHksdGhpcy5hb01hcD10LmFvTWFwLHRoaXMuYW9NYXBJbnRlbnNpdHk9dC5hb01hcEludGVuc2l0eSx0aGlzLnNwZWN1bGFyTWFwPXQuc3BlY3VsYXJNYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZW52TWFwPXQuZW52TWFwLHRoaXMuY29tYmluZT10LmNvbWJpbmUsdGhpcy5yZWZsZWN0aXZpdHk9dC5yZWZsZWN0aXZpdHksdGhpcy5yZWZyYWN0aW9uUmF0aW89dC5yZWZyYWN0aW9uUmF0aW8sdGhpcy53aXJlZnJhbWU9dC53aXJlZnJhbWUsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9dC53aXJlZnJhbWVMaW5ld2lkdGgsdGhpcy53aXJlZnJhbWVMaW5lY2FwPXQud2lyZWZyYW1lTGluZWNhcCx0aGlzLndpcmVmcmFtZUxpbmVqb2luPXQud2lyZWZyYW1lTGluZWpvaW4sdGhpc319O0dnLnByb3RvdHlwZS5pc01lc2hCYXNpY01hdGVyaWFsPSEwO3ZhciBFcj1uZXcgaWUsSU89bmV3IGF0LFlyPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXtpZihBcnJheS5pc0FycmF5KHQpKXRocm93IG5ldyBUeXBlRXJyb3IoIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZTogYXJyYXkgc2hvdWxkIGJlIGEgVHlwZWQgQXJyYXkuIik7dGhpcy5uYW1lPSIiLHRoaXMuYXJyYXk9dCx0aGlzLml0ZW1TaXplPWUsdGhpcy5jb3VudD12b2lkIDAhPT10P3QubGVuZ3RoL2U6MCx0aGlzLm5vcm1hbGl6ZWQ9ITA9PT1pLHRoaXMudXNhZ2U9cVMsdGhpcy51cGRhdGVSYW5nZT17b2Zmc2V0OjAsY291bnQ6LTF9LHRoaXMudmVyc2lvbj0wfW9uVXBsb2FkQ2FsbGJhY2soKXt9c2V0IG5lZWRzVXBkYXRlKHQpeyEwPT09dCYmdGhpcy52ZXJzaW9uKyt9c2V0VXNhZ2UodCl7cmV0dXJuIHRoaXMudXNhZ2U9dCx0aGlzfWNvcHkodCl7cmV0dXJuIHRoaXMubmFtZT10Lm5hbWUsdGhpcy5hcnJheT1uZXcgdC5hcnJheS5jb25zdHJ1Y3Rvcih0LmFycmF5KSx0aGlzLml0ZW1TaXplPXQuaXRlbVNpemUsdGhpcy5jb3VudD10LmNvdW50LHRoaXMubm9ybWFsaXplZD10Lm5vcm1hbGl6ZWQsdGhpcy51c2FnZT10LnVzYWdlLHRoaXN9Y29weUF0KHQsZSxpKXt0Kj10aGlzLml0ZW1TaXplLGkqPWUuaXRlbVNpemU7Zm9yKGxldCByPTAsbz10aGlzLml0ZW1TaXplO3I8bztyKyspdGhpcy5hcnJheVt0K3JdPWUuYXJyYXlbaStyXTtyZXR1cm4gdGhpc31jb3B5QXJyYXkodCl7cmV0dXJuIHRoaXMuYXJyYXkuc2V0KHQpLHRoaXN9Y29weUNvbG9yc0FycmF5KHQpe2xldCBlPXRoaXMuYXJyYXksaT0wO2ZvcihsZXQgcj0wLG89dC5sZW5ndGg7cjxvO3IrKyl7bGV0IHM9dFtyXTt2b2lkIDA9PT1zJiYoY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGUuY29weUNvbG9yc0FycmF5KCk6IGNvbG9yIGlzIHVuZGVmaW5lZCIscikscz1uZXcgdm4pLGVbaSsrXT1zLnIsZVtpKytdPXMuZyxlW2krK109cy5ifXJldHVybiB0aGlzfWNvcHlWZWN0b3Iyc0FycmF5KHQpe2xldCBlPXRoaXMuYXJyYXksaT0wO2ZvcihsZXQgcj0wLG89dC5sZW5ndGg7cjxvO3IrKyl7bGV0IHM9dFtyXTt2b2lkIDA9PT1zJiYoY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGUuY29weVZlY3RvcjJzQXJyYXkoKTogdmVjdG9yIGlzIHVuZGVmaW5lZCIscikscz1uZXcgYXQpLGVbaSsrXT1zLngsZVtpKytdPXMueX1yZXR1cm4gdGhpc31jb3B5VmVjdG9yM3NBcnJheSh0KXtsZXQgZT10aGlzLmFycmF5LGk9MDtmb3IobGV0IHI9MCxvPXQubGVuZ3RoO3I8bztyKyspe2xldCBzPXRbcl07dm9pZCAwPT09cyYmKGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyQXR0cmlidXRlLmNvcHlWZWN0b3Izc0FycmF5KCk6IHZlY3RvciBpcyB1bmRlZmluZWQiLHIpLHM9bmV3IGllKSxlW2krK109cy54LGVbaSsrXT1zLnksZVtpKytdPXMuen1yZXR1cm4gdGhpc31jb3B5VmVjdG9yNHNBcnJheSh0KXtsZXQgZT10aGlzLmFycmF5LGk9MDtmb3IobGV0IHI9MCxvPXQubGVuZ3RoO3I8bztyKyspe2xldCBzPXRbcl07dm9pZCAwPT09cyYmKGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyQXR0cmlidXRlLmNvcHlWZWN0b3I0c0FycmF5KCk6IHZlY3RvciBpcyB1bmRlZmluZWQiLHIpLHM9bmV3IGFyKSxlW2krK109cy54LGVbaSsrXT1zLnksZVtpKytdPXMueixlW2krK109cy53fXJldHVybiB0aGlzfWFwcGx5TWF0cml4Myh0KXtpZigyPT09dGhpcy5pdGVtU2l6ZSlmb3IobGV0IGU9MCxpPXRoaXMuY291bnQ7ZTxpO2UrKylJTy5mcm9tQnVmZmVyQXR0cmlidXRlKHRoaXMsZSksSU8uYXBwbHlNYXRyaXgzKHQpLHRoaXMuc2V0WFkoZSxJTy54LElPLnkpO2Vsc2UgaWYoMz09PXRoaXMuaXRlbVNpemUpZm9yKGxldCBlPTAsaT10aGlzLmNvdW50O2U8aTtlKyspRXIuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0aGlzLGUpLEVyLmFwcGx5TWF0cml4Myh0KSx0aGlzLnNldFhZWihlLEVyLngsRXIueSxFci56KTtyZXR1cm4gdGhpc31hcHBseU1hdHJpeDQodCl7Zm9yKGxldCBlPTAsaT10aGlzLmNvdW50O2U8aTtlKyspRXIueD10aGlzLmdldFgoZSksRXIueT10aGlzLmdldFkoZSksRXIuej10aGlzLmdldFooZSksRXIuYXBwbHlNYXRyaXg0KHQpLHRoaXMuc2V0WFlaKGUsRXIueCxFci55LEVyLnopO3JldHVybiB0aGlzfWFwcGx5Tm9ybWFsTWF0cml4KHQpe2ZvcihsZXQgZT0wLGk9dGhpcy5jb3VudDtlPGk7ZSsrKUVyLng9dGhpcy5nZXRYKGUpLEVyLnk9dGhpcy5nZXRZKGUpLEVyLno9dGhpcy5nZXRaKGUpLEVyLmFwcGx5Tm9ybWFsTWF0cml4KHQpLHRoaXMuc2V0WFlaKGUsRXIueCxFci55LEVyLnopO3JldHVybiB0aGlzfXRyYW5zZm9ybURpcmVjdGlvbih0KXtmb3IobGV0IGU9MCxpPXRoaXMuY291bnQ7ZTxpO2UrKylFci54PXRoaXMuZ2V0WChlKSxFci55PXRoaXMuZ2V0WShlKSxFci56PXRoaXMuZ2V0WihlKSxFci50cmFuc2Zvcm1EaXJlY3Rpb24odCksdGhpcy5zZXRYWVooZSxFci54LEVyLnksRXIueik7cmV0dXJuIHRoaXN9c2V0KHQsZT0wKXtyZXR1cm4gdGhpcy5hcnJheS5zZXQodCxlKSx0aGlzfWdldFgodCl7cmV0dXJuIHRoaXMuYXJyYXlbdCp0aGlzLml0ZW1TaXplXX1zZXRYKHQsZSl7cmV0dXJuIHRoaXMuYXJyYXlbdCp0aGlzLml0ZW1TaXplXT1lLHRoaXN9Z2V0WSh0KXtyZXR1cm4gdGhpcy5hcnJheVt0KnRoaXMuaXRlbVNpemUrMV19c2V0WSh0LGUpe3JldHVybiB0aGlzLmFycmF5W3QqdGhpcy5pdGVtU2l6ZSsxXT1lLHRoaXN9Z2V0Wih0KXtyZXR1cm4gdGhpcy5hcnJheVt0KnRoaXMuaXRlbVNpemUrMl19c2V0Wih0LGUpe3JldHVybiB0aGlzLmFycmF5W3QqdGhpcy5pdGVtU2l6ZSsyXT1lLHRoaXN9Z2V0Vyh0KXtyZXR1cm4gdGhpcy5hcnJheVt0KnRoaXMuaXRlbVNpemUrM119c2V0Vyh0LGUpe3JldHVybiB0aGlzLmFycmF5W3QqdGhpcy5pdGVtU2l6ZSszXT1lLHRoaXN9c2V0WFkodCxlLGkpe3JldHVybiB0aGlzLmFycmF5WzArKHQqPXRoaXMuaXRlbVNpemUpXT1lLHRoaXMuYXJyYXlbdCsxXT1pLHRoaXN9c2V0WFlaKHQsZSxpLHIpe3JldHVybiB0aGlzLmFycmF5WzArKHQqPXRoaXMuaXRlbVNpemUpXT1lLHRoaXMuYXJyYXlbdCsxXT1pLHRoaXMuYXJyYXlbdCsyXT1yLHRoaXN9c2V0WFlaVyh0LGUsaSxyLG8pe3JldHVybiB0aGlzLmFycmF5WzArKHQqPXRoaXMuaXRlbVNpemUpXT1lLHRoaXMuYXJyYXlbdCsxXT1pLHRoaXMuYXJyYXlbdCsyXT1yLHRoaXMuYXJyYXlbdCszXT1vLHRoaXN9b25VcGxvYWQodCl7cmV0dXJuIHRoaXMub25VcGxvYWRDYWxsYmFjaz10LHRoaXN9Y2xvbmUoKXtyZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5hcnJheSx0aGlzLml0ZW1TaXplKS5jb3B5KHRoaXMpfXRvSlNPTigpe2xldCB0PXtpdGVtU2l6ZTp0aGlzLml0ZW1TaXplLHR5cGU6dGhpcy5hcnJheS5jb25zdHJ1Y3Rvci5uYW1lLGFycmF5OkFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKHRoaXMuYXJyYXkpLG5vcm1hbGl6ZWQ6dGhpcy5ub3JtYWxpemVkfTtyZXR1cm4iIiE9PXRoaXMubmFtZSYmKHQubmFtZT10aGlzLm5hbWUpLHRoaXMudXNhZ2UhPT1xUyYmKHQudXNhZ2U9dGhpcy51c2FnZSksKDAhPT10aGlzLnVwZGF0ZVJhbmdlLm9mZnNldHx8LTEhPT10aGlzLnVwZGF0ZVJhbmdlLmNvdW50KSYmKHQudXBkYXRlUmFuZ2U9dGhpcy51cGRhdGVSYW5nZSksdH19O1lyLnByb3RvdHlwZS5pc0J1ZmZlckF0dHJpYnV0ZT0hMDt2YXIgYWs9Y2xhc3MgZXh0ZW5kcyBZcntjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIobmV3IFVpbnQxNkFycmF5KHQpLGUsaSl9fSxsaz1jbGFzcyBleHRlbmRzIFlye2NvbnN0cnVjdG9yKHQsZSxpKXtzdXBlcihuZXcgVWludDMyQXJyYXkodCksZSxpKX19OyhjbGFzcyBleHRlbmRzIFlye2NvbnN0cnVjdG9yKHQsZSxpKXtzdXBlcihuZXcgVWludDE2QXJyYXkodCksZSxpKX19KS5wcm90b3R5cGUuaXNGbG9hdDE2QnVmZmVyQXR0cmlidXRlPSEwO3ZhciBKcj1jbGFzcyBleHRlbmRzIFlye2NvbnN0cnVjdG9yKHQsZSxpKXtzdXBlcihuZXcgRmxvYXQzMkFycmF5KHQpLGUsaSl9fSxLOGU9MCxwYz1uZXcgUm4sR2o9bmV3IFhpLFl5PW5ldyBpZSxTbD1uZXcgVGwsUFM9bmV3IFRsLHBzPW5ldyBpZSxucj1jbGFzcyBleHRlbmRzIEVwe2NvbnN0cnVjdG9yKCl7c3VwZXIoKSxPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywiaWQiLHt2YWx1ZTpLOGUrK30pLHRoaXMudXVpZD1kdSgpLHRoaXMubmFtZT0iIix0aGlzLnR5cGU9IkJ1ZmZlckdlb21ldHJ5Iix0aGlzLmluZGV4PW51bGwsdGhpcy5hdHRyaWJ1dGVzPXt9LHRoaXMubW9ycGhBdHRyaWJ1dGVzPXt9LHRoaXMubW9ycGhUYXJnZXRzUmVsYXRpdmU9ITEsdGhpcy5ncm91cHM9W10sdGhpcy5ib3VuZGluZ0JveD1udWxsLHRoaXMuYm91bmRpbmdTcGhlcmU9bnVsbCx0aGlzLmRyYXdSYW5nZT17c3RhcnQ6MCxjb3VudDoxLzB9LHRoaXMudXNlckRhdGE9e319Z2V0SW5kZXgoKXtyZXR1cm4gdGhpcy5pbmRleH1zZXRJbmRleCh0KXtyZXR1cm4gdGhpcy5pbmRleD1BcnJheS5pc0FycmF5KHQpP25ldyhPZGUodCk/bGs6YWspKHQsMSk6dCx0aGlzfWdldEF0dHJpYnV0ZSh0KXtyZXR1cm4gdGhpcy5hdHRyaWJ1dGVzW3RdfXNldEF0dHJpYnV0ZSh0LGUpe3JldHVybiB0aGlzLmF0dHJpYnV0ZXNbdF09ZSx0aGlzfWRlbGV0ZUF0dHJpYnV0ZSh0KXtyZXR1cm4gZGVsZXRlIHRoaXMuYXR0cmlidXRlc1t0XSx0aGlzfWhhc0F0dHJpYnV0ZSh0KXtyZXR1cm4gdm9pZCAwIT09dGhpcy5hdHRyaWJ1dGVzW3RdfWFkZEdyb3VwKHQsZSxpPTApe3RoaXMuZ3JvdXBzLnB1c2goe3N0YXJ0OnQsY291bnQ6ZSxtYXRlcmlhbEluZGV4Oml9KX1jbGVhckdyb3Vwcygpe3RoaXMuZ3JvdXBzPVtdfXNldERyYXdSYW5nZSh0LGUpe3RoaXMuZHJhd1JhbmdlLnN0YXJ0PXQsdGhpcy5kcmF3UmFuZ2UuY291bnQ9ZX1hcHBseU1hdHJpeDQodCl7bGV0IGU9dGhpcy5hdHRyaWJ1dGVzLnBvc2l0aW9uO3ZvaWQgMCE9PWUmJihlLmFwcGx5TWF0cml4NCh0KSxlLm5lZWRzVXBkYXRlPSEwKTtsZXQgaT10aGlzLmF0dHJpYnV0ZXMubm9ybWFsO2lmKHZvaWQgMCE9PWkpe2xldCBvPShuZXcgSm8pLmdldE5vcm1hbE1hdHJpeCh0KTtpLmFwcGx5Tm9ybWFsTWF0cml4KG8pLGkubmVlZHNVcGRhdGU9ITB9bGV0IHI9dGhpcy5hdHRyaWJ1dGVzLnRhbmdlbnQ7cmV0dXJuIHZvaWQgMCE9PXImJihyLnRyYW5zZm9ybURpcmVjdGlvbih0KSxyLm5lZWRzVXBkYXRlPSEwKSxudWxsIT09dGhpcy5ib3VuZGluZ0JveCYmdGhpcy5jb21wdXRlQm91bmRpbmdCb3goKSxudWxsIT09dGhpcy5ib3VuZGluZ1NwaGVyZSYmdGhpcy5jb21wdXRlQm91bmRpbmdTcGhlcmUoKSx0aGlzfWFwcGx5UXVhdGVybmlvbih0KXtyZXR1cm4gcGMubWFrZVJvdGF0aW9uRnJvbVF1YXRlcm5pb24odCksdGhpcy5hcHBseU1hdHJpeDQocGMpLHRoaXN9cm90YXRlWCh0KXtyZXR1cm4gcGMubWFrZVJvdGF0aW9uWCh0KSx0aGlzLmFwcGx5TWF0cml4NChwYyksdGhpc31yb3RhdGVZKHQpe3JldHVybiBwYy5tYWtlUm90YXRpb25ZKHQpLHRoaXMuYXBwbHlNYXRyaXg0KHBjKSx0aGlzfXJvdGF0ZVoodCl7cmV0dXJuIHBjLm1ha2VSb3RhdGlvbloodCksdGhpcy5hcHBseU1hdHJpeDQocGMpLHRoaXN9dHJhbnNsYXRlKHQsZSxpKXtyZXR1cm4gcGMubWFrZVRyYW5zbGF0aW9uKHQsZSxpKSx0aGlzLmFwcGx5TWF0cml4NChwYyksdGhpc31zY2FsZSh0LGUsaSl7cmV0dXJuIHBjLm1ha2VTY2FsZSh0LGUsaSksdGhpcy5hcHBseU1hdHJpeDQocGMpLHRoaXN9bG9va0F0KHQpe3JldHVybiBHai5sb29rQXQodCksR2oudXBkYXRlTWF0cml4KCksdGhpcy5hcHBseU1hdHJpeDQoR2oubWF0cml4KSx0aGlzfWNlbnRlcigpe3JldHVybiB0aGlzLmNvbXB1dGVCb3VuZGluZ0JveCgpLHRoaXMuYm91bmRpbmdCb3guZ2V0Q2VudGVyKFl5KS5uZWdhdGUoKSx0aGlzLnRyYW5zbGF0ZShZeS54LFl5LnksWXkueiksdGhpc31zZXRGcm9tUG9pbnRzKHQpe2xldCBlPVtdO2ZvcihsZXQgaT0wLHI9dC5sZW5ndGg7aTxyO2krKyl7bGV0IG89dFtpXTtlLnB1c2goby54LG8ueSxvLnp8fDApfXJldHVybiB0aGlzLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyBKcihlLDMpKSx0aGlzfWNvbXB1dGVCb3VuZGluZ0JveCgpe251bGw9PT10aGlzLmJvdW5kaW5nQm94JiYodGhpcy5ib3VuZGluZ0JveD1uZXcgVGwpO2xldCB0PXRoaXMuYXR0cmlidXRlcy5wb3NpdGlvbixlPXRoaXMubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uO2lmKHQmJnQuaXNHTEJ1ZmZlckF0dHJpYnV0ZSlyZXR1cm4gY29uc29sZS5lcnJvcignVEhSRUUuQnVmZmVyR2VvbWV0cnkuY29tcHV0ZUJvdW5kaW5nQm94KCk6IEdMQnVmZmVyQXR0cmlidXRlIHJlcXVpcmVzIGEgbWFudWFsIGJvdW5kaW5nIGJveC4gQWx0ZXJuYXRpdmVseSBzZXQgIm1lc2guZnJ1c3R1bUN1bGxlZCIgdG8gImZhbHNlIi4nLHRoaXMpLHZvaWQgdGhpcy5ib3VuZGluZ0JveC5zZXQobmV3IGllKC0xLzAsLTEvMCwtMS8wKSxuZXcgaWUoMS8wLDEvMCwxLzApKTtpZih2b2lkIDAhPT10KXtpZih0aGlzLmJvdW5kaW5nQm94LnNldEZyb21CdWZmZXJBdHRyaWJ1dGUodCksZSlmb3IobGV0IGk9MCxyPWUubGVuZ3RoO2k8cjtpKyspU2wuc2V0RnJvbUJ1ZmZlckF0dHJpYnV0ZShlW2ldKSx0aGlzLm1vcnBoVGFyZ2V0c1JlbGF0aXZlPyhwcy5hZGRWZWN0b3JzKHRoaXMuYm91bmRpbmdCb3gubWluLFNsLm1pbiksdGhpcy5ib3VuZGluZ0JveC5leHBhbmRCeVBvaW50KHBzKSxwcy5hZGRWZWN0b3JzKHRoaXMuYm91bmRpbmdCb3gubWF4LFNsLm1heCksdGhpcy5ib3VuZGluZ0JveC5leHBhbmRCeVBvaW50KHBzKSk6KHRoaXMuYm91bmRpbmdCb3guZXhwYW5kQnlQb2ludChTbC5taW4pLHRoaXMuYm91bmRpbmdCb3guZXhwYW5kQnlQb2ludChTbC5tYXgpKX1lbHNlIHRoaXMuYm91bmRpbmdCb3gubWFrZUVtcHR5KCk7KGlzTmFOKHRoaXMuYm91bmRpbmdCb3gubWluLngpfHxpc05hTih0aGlzLmJvdW5kaW5nQm94Lm1pbi55KXx8aXNOYU4odGhpcy5ib3VuZGluZ0JveC5taW4ueikpJiZjb25zb2xlLmVycm9yKCdUSFJFRS5CdWZmZXJHZW9tZXRyeS5jb21wdXRlQm91bmRpbmdCb3goKTogQ29tcHV0ZWQgbWluL21heCBoYXZlIE5hTiB2YWx1ZXMuIFRoZSAicG9zaXRpb24iIGF0dHJpYnV0ZSBpcyBsaWtlbHkgdG8gaGF2ZSBOYU4gdmFsdWVzLicsdGhpcyl9Y29tcHV0ZUJvdW5kaW5nU3BoZXJlKCl7bnVsbD09PXRoaXMuYm91bmRpbmdTcGhlcmUmJih0aGlzLmJvdW5kaW5nU3BoZXJlPW5ldyB4Zik7bGV0IHQ9dGhpcy5hdHRyaWJ1dGVzLnBvc2l0aW9uLGU9dGhpcy5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb247aWYodCYmdC5pc0dMQnVmZmVyQXR0cmlidXRlKXJldHVybiBjb25zb2xlLmVycm9yKCdUSFJFRS5CdWZmZXJHZW9tZXRyeS5jb21wdXRlQm91bmRpbmdTcGhlcmUoKTogR0xCdWZmZXJBdHRyaWJ1dGUgcmVxdWlyZXMgYSBtYW51YWwgYm91bmRpbmcgc3BoZXJlLiBBbHRlcm5hdGl2ZWx5IHNldCAibWVzaC5mcnVzdHVtQ3VsbGVkIiB0byAiZmFsc2UiLicsdGhpcyksdm9pZCB0aGlzLmJvdW5kaW5nU3BoZXJlLnNldChuZXcgaWUsMS8wKTtpZih0KXtsZXQgaT10aGlzLmJvdW5kaW5nU3BoZXJlLmNlbnRlcjtpZihTbC5zZXRGcm9tQnVmZmVyQXR0cmlidXRlKHQpLGUpZm9yKGxldCBvPTAscz1lLmxlbmd0aDtvPHM7bysrKVBTLnNldEZyb21CdWZmZXJBdHRyaWJ1dGUoZVtvXSksdGhpcy5tb3JwaFRhcmdldHNSZWxhdGl2ZT8ocHMuYWRkVmVjdG9ycyhTbC5taW4sUFMubWluKSxTbC5leHBhbmRCeVBvaW50KHBzKSxwcy5hZGRWZWN0b3JzKFNsLm1heCxQUy5tYXgpLFNsLmV4cGFuZEJ5UG9pbnQocHMpKTooU2wuZXhwYW5kQnlQb2ludChQUy5taW4pLFNsLmV4cGFuZEJ5UG9pbnQoUFMubWF4KSk7U2wuZ2V0Q2VudGVyKGkpO2xldCByPTA7Zm9yKGxldCBvPTAscz10LmNvdW50O288cztvKyspcHMuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LG8pLHI9TWF0aC5tYXgocixpLmRpc3RhbmNlVG9TcXVhcmVkKHBzKSk7aWYoZSlmb3IobGV0IG89MCxzPWUubGVuZ3RoO288cztvKyspe2xldCBhPWVbb10sbD10aGlzLm1vcnBoVGFyZ2V0c1JlbGF0aXZlO2ZvcihsZXQgYz0wLHU9YS5jb3VudDtjPHU7YysrKXBzLmZyb21CdWZmZXJBdHRyaWJ1dGUoYSxjKSxsJiYoWXkuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LGMpLHBzLmFkZChZeSkpLHI9TWF0aC5tYXgocixpLmRpc3RhbmNlVG9TcXVhcmVkKHBzKSl9dGhpcy5ib3VuZGluZ1NwaGVyZS5yYWRpdXM9TWF0aC5zcXJ0KHIpLGlzTmFOKHRoaXMuYm91bmRpbmdTcGhlcmUucmFkaXVzKSYmY29uc29sZS5lcnJvcignVEhSRUUuQnVmZmVyR2VvbWV0cnkuY29tcHV0ZUJvdW5kaW5nU3BoZXJlKCk6IENvbXB1dGVkIHJhZGl1cyBpcyBOYU4uIFRoZSAicG9zaXRpb24iIGF0dHJpYnV0ZSBpcyBsaWtlbHkgdG8gaGF2ZSBOYU4gdmFsdWVzLicsdGhpcyl9fWNvbXB1dGVUYW5nZW50cygpe2xldCB0PXRoaXMuaW5kZXgsZT10aGlzLmF0dHJpYnV0ZXM7aWYobnVsbD09PXR8fHZvaWQgMD09PWUucG9zaXRpb258fHZvaWQgMD09PWUubm9ybWFsfHx2b2lkIDA9PT1lLnV2KXJldHVybiB2b2lkIGNvbnNvbGUuZXJyb3IoIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuY29tcHV0ZVRhbmdlbnRzKCkgZmFpbGVkLiBNaXNzaW5nIHJlcXVpcmVkIGF0dHJpYnV0ZXMgKGluZGV4LCBwb3NpdGlvbiwgbm9ybWFsIG9yIHV2KSIpO2xldCBpPXQuYXJyYXkscj1lLnBvc2l0aW9uLmFycmF5LG89ZS5ub3JtYWwuYXJyYXkscz1lLnV2LmFycmF5LGE9ci5sZW5ndGgvMzt2b2lkIDA9PT1lLnRhbmdlbnQmJnRoaXMuc2V0QXR0cmlidXRlKCJ0YW5nZW50IixuZXcgWXIobmV3IEZsb2F0MzJBcnJheSg0KmEpLDQpKTtsZXQgbD1lLnRhbmdlbnQuYXJyYXksYz1bXSx1PVtdO2ZvcihsZXQgaGU9MDtoZTxhO2hlKyspY1toZV09bmV3IGllLHVbaGVdPW5ldyBpZTtsZXQgZD1uZXcgaWUscD1uZXcgaWUsaD1uZXcgaWUsZj1uZXcgYXQsbT1uZXcgYXQseD1uZXcgYXQsZz1uZXcgaWUsYj1uZXcgaWU7ZnVuY3Rpb24gRChoZSx3LEYpe2QuZnJvbUFycmF5KHIsMypoZSkscC5mcm9tQXJyYXkociwzKncpLGguZnJvbUFycmF5KHIsMypGKSxmLmZyb21BcnJheShzLDIqaGUpLG0uZnJvbUFycmF5KHMsMip3KSx4LmZyb21BcnJheShzLDIqRikscC5zdWIoZCksaC5zdWIoZCksbS5zdWIoZikseC5zdWIoZik7bGV0IHE9MS8obS54KngueS14LngqbS55KTshaXNGaW5pdGUocSl8fChnLmNvcHkocCkubXVsdGlwbHlTY2FsYXIoeC55KS5hZGRTY2FsZWRWZWN0b3IoaCwtbS55KS5tdWx0aXBseVNjYWxhcihxKSxiLmNvcHkoaCkubXVsdGlwbHlTY2FsYXIobS54KS5hZGRTY2FsZWRWZWN0b3IocCwteC54KS5tdWx0aXBseVNjYWxhcihxKSxjW2hlXS5hZGQoZyksY1t3XS5hZGQoZyksY1tGXS5hZGQoZyksdVtoZV0uYWRkKGIpLHVbd10uYWRkKGIpLHVbRl0uYWRkKGIpKX1sZXQgVD10aGlzLmdyb3VwczswPT09VC5sZW5ndGgmJihUPVt7c3RhcnQ6MCxjb3VudDppLmxlbmd0aH1dKTtmb3IobGV0IGhlPTAsdz1ULmxlbmd0aDtoZTx3OysraGUpe2xldCBGPVRbaGVdLHE9Ri5zdGFydDtmb3IobGV0IGRlPXEsWT1xK0YuY291bnQ7ZGU8WTtkZSs9MylEKGlbZGUrMF0saVtkZSsxXSxpW2RlKzJdKX1sZXQgaz1uZXcgaWUsWj1uZXcgaWUsej1uZXcgaWUsZmU9bmV3IGllO2Z1bmN0aW9uIHVlKGhlKXt6LmZyb21BcnJheShvLDMqaGUpLGZlLmNvcHkoeik7bGV0IHc9Y1toZV07ay5jb3B5KHcpLGsuc3ViKHoubXVsdGlwbHlTY2FsYXIoei5kb3QodykpKS5ub3JtYWxpemUoKSxaLmNyb3NzVmVjdG9ycyhmZSx3KTtsZXQgcT1aLmRvdCh1W2hlXSk8MD8tMToxO2xbNCpoZV09ay54LGxbNCpoZSsxXT1rLnksbFs0KmhlKzJdPWsueixsWzQqaGUrM109cX1mb3IobGV0IGhlPTAsdz1ULmxlbmd0aDtoZTx3OysraGUpe2xldCBGPVRbaGVdLHE9Ri5zdGFydDtmb3IobGV0IGRlPXEsWT1xK0YuY291bnQ7ZGU8WTtkZSs9Myl1ZShpW2RlKzBdKSx1ZShpW2RlKzFdKSx1ZShpW2RlKzJdKX19Y29tcHV0ZVZlcnRleE5vcm1hbHMoKXtsZXQgdD10aGlzLmluZGV4LGU9dGhpcy5nZXRBdHRyaWJ1dGUoInBvc2l0aW9uIik7aWYodm9pZCAwIT09ZSl7bGV0IGk9dGhpcy5nZXRBdHRyaWJ1dGUoIm5vcm1hbCIpO2lmKHZvaWQgMD09PWkpaT1uZXcgWXIobmV3IEZsb2F0MzJBcnJheSgzKmUuY291bnQpLDMpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLGkpO2Vsc2UgZm9yKGxldCBwPTAsaD1pLmNvdW50O3A8aDtwKyspaS5zZXRYWVoocCwwLDAsMCk7bGV0IHI9bmV3IGllLG89bmV3IGllLHM9bmV3IGllLGE9bmV3IGllLGw9bmV3IGllLGM9bmV3IGllLHU9bmV3IGllLGQ9bmV3IGllO2lmKHQpZm9yKGxldCBwPTAsaD10LmNvdW50O3A8aDtwKz0zKXtsZXQgZj10LmdldFgocCswKSxtPXQuZ2V0WChwKzEpLHg9dC5nZXRYKHArMik7ci5mcm9tQnVmZmVyQXR0cmlidXRlKGUsZiksby5mcm9tQnVmZmVyQXR0cmlidXRlKGUsbSkscy5mcm9tQnVmZmVyQXR0cmlidXRlKGUseCksdS5zdWJWZWN0b3JzKHMsbyksZC5zdWJWZWN0b3JzKHIsbyksdS5jcm9zcyhkKSxhLmZyb21CdWZmZXJBdHRyaWJ1dGUoaSxmKSxsLmZyb21CdWZmZXJBdHRyaWJ1dGUoaSxtKSxjLmZyb21CdWZmZXJBdHRyaWJ1dGUoaSx4KSxhLmFkZCh1KSxsLmFkZCh1KSxjLmFkZCh1KSxpLnNldFhZWihmLGEueCxhLnksYS56KSxpLnNldFhZWihtLGwueCxsLnksbC56KSxpLnNldFhZWih4LGMueCxjLnksYy56KX1lbHNlIGZvcihsZXQgcD0wLGg9ZS5jb3VudDtwPGg7cCs9MylyLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSxwKzApLG8uZnJvbUJ1ZmZlckF0dHJpYnV0ZShlLHArMSkscy5mcm9tQnVmZmVyQXR0cmlidXRlKGUscCsyKSx1LnN1YlZlY3RvcnMocyxvKSxkLnN1YlZlY3RvcnMocixvKSx1LmNyb3NzKGQpLGkuc2V0WFlaKHArMCx1LngsdS55LHUueiksaS5zZXRYWVoocCsxLHUueCx1LnksdS56KSxpLnNldFhZWihwKzIsdS54LHUueSx1LnopO3RoaXMubm9ybWFsaXplTm9ybWFscygpLGkubmVlZHNVcGRhdGU9ITB9fW1lcmdlKHQsZSl7aWYoIXR8fCF0LmlzQnVmZmVyR2VvbWV0cnkpcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiVEhSRUUuQnVmZmVyR2VvbWV0cnkubWVyZ2UoKTogZ2VvbWV0cnkgbm90IGFuIGluc3RhbmNlIG9mIFRIUkVFLkJ1ZmZlckdlb21ldHJ5LiIsdCk7dm9pZCAwPT09ZSYmKGU9MCxjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5Lm1lcmdlKCk6IE92ZXJ3cml0aW5nIG9yaWdpbmFsIGdlb21ldHJ5LCBzdGFydGluZyBhdCBvZmZzZXQ9MC4gVXNlIEJ1ZmZlckdlb21ldHJ5VXRpbHMubWVyZ2VCdWZmZXJHZW9tZXRyaWVzKCkgZm9yIGxvc3NsZXNzIG1lcmdlLiIpKTtsZXQgaT10aGlzLmF0dHJpYnV0ZXM7Zm9yKGxldCByIGluIGkpe2lmKHZvaWQgMD09PXQuYXR0cmlidXRlc1tyXSljb250aW51ZTtsZXQgcz1pW3JdLmFycmF5LGE9dC5hdHRyaWJ1dGVzW3JdLGw9YS5hcnJheSxjPWEuaXRlbVNpemUqZSx1PU1hdGgubWluKGwubGVuZ3RoLHMubGVuZ3RoLWMpO2ZvcihsZXQgZD0wLHA9YztkPHU7ZCsrLHArKylzW3BdPWxbZF19cmV0dXJuIHRoaXN9bm9ybWFsaXplTm9ybWFscygpe2xldCB0PXRoaXMuYXR0cmlidXRlcy5ub3JtYWw7Zm9yKGxldCBlPTAsaT10LmNvdW50O2U8aTtlKyspcHMuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh0LGUpLHBzLm5vcm1hbGl6ZSgpLHQuc2V0WFlaKGUscHMueCxwcy55LHBzLnopfXRvTm9uSW5kZXhlZCgpe2Z1bmN0aW9uIHQoYSxsKXtsZXQgYz1hLmFycmF5LHU9YS5pdGVtU2l6ZSxkPWEubm9ybWFsaXplZCxwPW5ldyBjLmNvbnN0cnVjdG9yKGwubGVuZ3RoKnUpLGg9MCxmPTA7Zm9yKGxldCBtPTAseD1sLmxlbmd0aDttPHg7bSsrKXtoPWEuaXNJbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZT9sW21dKmEuZGF0YS5zdHJpZGUrYS5vZmZzZXQ6bFttXSp1O2ZvcihsZXQgZz0wO2c8dTtnKyspcFtmKytdPWNbaCsrXX1yZXR1cm4gbmV3IFlyKHAsdSxkKX1pZihudWxsPT09dGhpcy5pbmRleClyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeS50b05vbkluZGV4ZWQoKTogQnVmZmVyR2VvbWV0cnkgaXMgYWxyZWFkeSBub24taW5kZXhlZC4iKSx0aGlzO2xldCBlPW5ldyBucixpPXRoaXMuaW5kZXguYXJyYXkscj10aGlzLmF0dHJpYnV0ZXM7Zm9yKGxldCBhIGluIHIpe2xldCBjPXQoclthXSxpKTtlLnNldEF0dHJpYnV0ZShhLGMpfWxldCBvPXRoaXMubW9ycGhBdHRyaWJ1dGVzO2ZvcihsZXQgYSBpbiBvKXtsZXQgbD1bXSxjPW9bYV07Zm9yKGxldCB1PTAsZD1jLmxlbmd0aDt1PGQ7dSsrKXtsZXQgaD10KGNbdV0saSk7bC5wdXNoKGgpfWUubW9ycGhBdHRyaWJ1dGVzW2FdPWx9ZS5tb3JwaFRhcmdldHNSZWxhdGl2ZT10aGlzLm1vcnBoVGFyZ2V0c1JlbGF0aXZlO2xldCBzPXRoaXMuZ3JvdXBzO2ZvcihsZXQgYT0wLGw9cy5sZW5ndGg7YTxsO2ErKyl7bGV0IGM9c1thXTtlLmFkZEdyb3VwKGMuc3RhcnQsYy5jb3VudCxjLm1hdGVyaWFsSW5kZXgpfXJldHVybiBlfXRvSlNPTigpe2xldCB0PXttZXRhZGF0YTp7dmVyc2lvbjo0LjUsdHlwZToiQnVmZmVyR2VvbWV0cnkiLGdlbmVyYXRvcjoiQnVmZmVyR2VvbWV0cnkudG9KU09OIn19O2lmKHQudXVpZD10aGlzLnV1aWQsdC50eXBlPXRoaXMudHlwZSwiIiE9PXRoaXMubmFtZSYmKHQubmFtZT10aGlzLm5hbWUpLE9iamVjdC5rZXlzKHRoaXMudXNlckRhdGEpLmxlbmd0aD4wJiYodC51c2VyRGF0YT10aGlzLnVzZXJEYXRhKSx2b2lkIDAhPT10aGlzLnBhcmFtZXRlcnMpe2xldCBsPXRoaXMucGFyYW1ldGVycztmb3IobGV0IGMgaW4gbCl2b2lkIDAhPT1sW2NdJiYodFtjXT1sW2NdKTtyZXR1cm4gdH10LmRhdGE9e2F0dHJpYnV0ZXM6e319O2xldCBlPXRoaXMuaW5kZXg7bnVsbCE9PWUmJih0LmRhdGEuaW5kZXg9e3R5cGU6ZS5hcnJheS5jb25zdHJ1Y3Rvci5uYW1lLGFycmF5OkFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGUuYXJyYXkpfSk7bGV0IGk9dGhpcy5hdHRyaWJ1dGVzO2ZvcihsZXQgbCBpbiBpKXQuZGF0YS5hdHRyaWJ1dGVzW2xdPWlbbF0udG9KU09OKHQuZGF0YSk7bGV0IHI9e30sbz0hMTtmb3IobGV0IGwgaW4gdGhpcy5tb3JwaEF0dHJpYnV0ZXMpe2xldCBjPXRoaXMubW9ycGhBdHRyaWJ1dGVzW2xdLHU9W107Zm9yKGxldCBkPTAscD1jLmxlbmd0aDtkPHA7ZCsrKXUucHVzaChjW2RdLnRvSlNPTih0LmRhdGEpKTt1Lmxlbmd0aD4wJiYocltsXT11LG89ITApfW8mJih0LmRhdGEubW9ycGhBdHRyaWJ1dGVzPXIsdC5kYXRhLm1vcnBoVGFyZ2V0c1JlbGF0aXZlPXRoaXMubW9ycGhUYXJnZXRzUmVsYXRpdmUpO2xldCBzPXRoaXMuZ3JvdXBzO3MubGVuZ3RoPjAmJih0LmRhdGEuZ3JvdXBzPUpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkocykpKTtsZXQgYT10aGlzLmJvdW5kaW5nU3BoZXJlO3JldHVybiBudWxsIT09YSYmKHQuZGF0YS5ib3VuZGluZ1NwaGVyZT17Y2VudGVyOmEuY2VudGVyLnRvQXJyYXkoKSxyYWRpdXM6YS5yYWRpdXN9KSx0fWNsb25lKCl7cmV0dXJuKG5ldyB0aGlzLmNvbnN0cnVjdG9yKS5jb3B5KHRoaXMpfWNvcHkodCl7dGhpcy5pbmRleD1udWxsLHRoaXMuYXR0cmlidXRlcz17fSx0aGlzLm1vcnBoQXR0cmlidXRlcz17fSx0aGlzLmdyb3Vwcz1bXSx0aGlzLmJvdW5kaW5nQm94PW51bGwsdGhpcy5ib3VuZGluZ1NwaGVyZT1udWxsO2xldCBlPXt9O3RoaXMubmFtZT10Lm5hbWU7bGV0IGk9dC5pbmRleDtudWxsIT09aSYmdGhpcy5zZXRJbmRleChpLmNsb25lKGUpKTtsZXQgcj10LmF0dHJpYnV0ZXM7Zm9yKGxldCBjIGluIHIpdGhpcy5zZXRBdHRyaWJ1dGUoYyxyW2NdLmNsb25lKGUpKTtsZXQgbz10Lm1vcnBoQXR0cmlidXRlcztmb3IobGV0IGMgaW4gbyl7bGV0IHU9W10sZD1vW2NdO2ZvcihsZXQgcD0wLGg9ZC5sZW5ndGg7cDxoO3ArKyl1LnB1c2goZFtwXS5jbG9uZShlKSk7dGhpcy5tb3JwaEF0dHJpYnV0ZXNbY109dX10aGlzLm1vcnBoVGFyZ2V0c1JlbGF0aXZlPXQubW9ycGhUYXJnZXRzUmVsYXRpdmU7bGV0IHM9dC5ncm91cHM7Zm9yKGxldCBjPTAsdT1zLmxlbmd0aDtjPHU7YysrKXtsZXQgZD1zW2NdO3RoaXMuYWRkR3JvdXAoZC5zdGFydCxkLmNvdW50LGQubWF0ZXJpYWxJbmRleCl9bGV0IGE9dC5ib3VuZGluZ0JveDtudWxsIT09YSYmKHRoaXMuYm91bmRpbmdCb3g9YS5jbG9uZSgpKTtsZXQgbD10LmJvdW5kaW5nU3BoZXJlO3JldHVybiBudWxsIT09bCYmKHRoaXMuYm91bmRpbmdTcGhlcmU9bC5jbG9uZSgpKSx0aGlzLmRyYXdSYW5nZS5zdGFydD10LmRyYXdSYW5nZS5zdGFydCx0aGlzLmRyYXdSYW5nZS5jb3VudD10LmRyYXdSYW5nZS5jb3VudCx0aGlzLnVzZXJEYXRhPXQudXNlckRhdGEsdm9pZCAwIT09dC5wYXJhbWV0ZXJzJiYodGhpcy5wYXJhbWV0ZXJzPU9iamVjdC5hc3NpZ24oe30sdC5wYXJhbWV0ZXJzKSksdGhpc31kaXNwb3NlKCl7dGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJkaXNwb3NlIn0pfX07bnIucHJvdG90eXBlLmlzQnVmZmVyR2VvbWV0cnk9ITA7dmFyIE91ZT1uZXcgUm4sWHk9bmV3IENmLFdqPW5ldyB4ZixkZj1uZXcgaWUscGY9bmV3IGllLGhmPW5ldyBpZSxxaj1uZXcgaWUsWWo9bmV3IGllLFhqPW5ldyBpZSxQTz1uZXcgaWUsUk89bmV3IGllLE9PPW5ldyBpZSxrTz1uZXcgYXQsRk89bmV3IGF0LE5PPW5ldyBhdCxRaj1uZXcgaWUsTE89bmV3IGllLFZvPWNsYXNzIGV4dGVuZHMgWGl7Y29uc3RydWN0b3IodD1uZXcgbnIsZT1uZXcgR2cpe3N1cGVyKCksdGhpcy50eXBlPSJNZXNoIix0aGlzLmdlb21ldHJ5PXQsdGhpcy5tYXRlcmlhbD1lLHRoaXMudXBkYXRlTW9ycGhUYXJnZXRzKCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx2b2lkIDAhPT10Lm1vcnBoVGFyZ2V0SW5mbHVlbmNlcyYmKHRoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzPXQubW9ycGhUYXJnZXRJbmZsdWVuY2VzLnNsaWNlKCkpLHZvaWQgMCE9PXQubW9ycGhUYXJnZXREaWN0aW9uYXJ5JiYodGhpcy5tb3JwaFRhcmdldERpY3Rpb25hcnk9T2JqZWN0LmFzc2lnbih7fSx0Lm1vcnBoVGFyZ2V0RGljdGlvbmFyeSkpLHRoaXMubWF0ZXJpYWw9dC5tYXRlcmlhbCx0aGlzLmdlb21ldHJ5PXQuZ2VvbWV0cnksdGhpc311cGRhdGVNb3JwaFRhcmdldHMoKXtsZXQgdD10aGlzLmdlb21ldHJ5O2lmKHQuaXNCdWZmZXJHZW9tZXRyeSl7bGV0IGU9dC5tb3JwaEF0dHJpYnV0ZXMsaT1PYmplY3Qua2V5cyhlKTtpZihpLmxlbmd0aD4wKXtsZXQgcj1lW2lbMF1dO2lmKHZvaWQgMCE9PXIpe3RoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzPVtdLHRoaXMubW9ycGhUYXJnZXREaWN0aW9uYXJ5PXt9O2ZvcihsZXQgbz0wLHM9ci5sZW5ndGg7bzxzO28rKyl7bGV0IGE9cltvXS5uYW1lfHxTdHJpbmcobyk7dGhpcy5tb3JwaFRhcmdldEluZmx1ZW5jZXMucHVzaCgwKSx0aGlzLm1vcnBoVGFyZ2V0RGljdGlvbmFyeVthXT1vfX19fWVsc2V7bGV0IGU9dC5tb3JwaFRhcmdldHM7dm9pZCAwIT09ZSYmZS5sZW5ndGg+MCYmY29uc29sZS5lcnJvcigiVEhSRUUuTWVzaC51cGRhdGVNb3JwaFRhcmdldHMoKSBubyBsb25nZXIgc3VwcG9ydHMgVEhSRUUuR2VvbWV0cnkuIFVzZSBUSFJFRS5CdWZmZXJHZW9tZXRyeSBpbnN0ZWFkLiIpfX1yYXljYXN0KHQsZSl7bGV0IHMsaT10aGlzLmdlb21ldHJ5LHI9dGhpcy5tYXRlcmlhbCxvPXRoaXMubWF0cml4V29ybGQ7aWYodm9pZCAwIT09ciYmKG51bGw9PT1pLmJvdW5kaW5nU3BoZXJlJiZpLmNvbXB1dGVCb3VuZGluZ1NwaGVyZSgpLFdqLmNvcHkoaS5ib3VuZGluZ1NwaGVyZSksV2ouYXBwbHlNYXRyaXg0KG8pLCExIT09dC5yYXkuaW50ZXJzZWN0c1NwaGVyZShXaikpJiYoT3VlLmNvcHkobykuaW52ZXJ0KCksWHkuY29weSh0LnJheSkuYXBwbHlNYXRyaXg0KE91ZSksbnVsbD09PWkuYm91bmRpbmdCb3h8fCExIT09WHkuaW50ZXJzZWN0c0JveChpLmJvdW5kaW5nQm94KSkpaWYoaS5pc0J1ZmZlckdlb21ldHJ5KXtsZXQgYT1pLmluZGV4LGw9aS5hdHRyaWJ1dGVzLnBvc2l0aW9uLGM9aS5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24sdT1pLm1vcnBoVGFyZ2V0c1JlbGF0aXZlLGQ9aS5hdHRyaWJ1dGVzLnV2LHA9aS5hdHRyaWJ1dGVzLnV2MixoPWkuZ3JvdXBzLGY9aS5kcmF3UmFuZ2U7aWYobnVsbCE9PWEpaWYoQXJyYXkuaXNBcnJheShyKSlmb3IobGV0IG09MCx4PWgubGVuZ3RoO208eDttKyspe2xldCBnPWhbbV0sYj1yW2cubWF0ZXJpYWxJbmRleF07Zm9yKGxldCBrPU1hdGgubWF4KGcuc3RhcnQsZi5zdGFydCksWj1NYXRoLm1pbihhLmNvdW50LE1hdGgubWluKGcuc3RhcnQrZy5jb3VudCxmLnN0YXJ0K2YuY291bnQpKTtrPFo7ays9Myl7bGV0IHo9YS5nZXRYKGspLGZlPWEuZ2V0WChrKzEpLHVlPWEuZ2V0WChrKzIpO3M9Qk8odGhpcyxiLHQsWHksbCxjLHUsZCxwLHosZmUsdWUpLHMmJihzLmZhY2VJbmRleD1NYXRoLmZsb29yKGsvMykscy5mYWNlLm1hdGVyaWFsSW5kZXg9Zy5tYXRlcmlhbEluZGV4LGUucHVzaChzKSl9fWVsc2UgZm9yKGxldCBnPU1hdGgubWF4KDAsZi5zdGFydCksYj1NYXRoLm1pbihhLmNvdW50LGYuc3RhcnQrZi5jb3VudCk7ZzxiO2crPTMpe2xldCBEPWEuZ2V0WChnKSxUPWEuZ2V0WChnKzEpLGs9YS5nZXRYKGcrMik7cz1CTyh0aGlzLHIsdCxYeSxsLGMsdSxkLHAsRCxULGspLHMmJihzLmZhY2VJbmRleD1NYXRoLmZsb29yKGcvMyksZS5wdXNoKHMpKX1lbHNlIGlmKHZvaWQgMCE9PWwpaWYoQXJyYXkuaXNBcnJheShyKSlmb3IobGV0IG09MCx4PWgubGVuZ3RoO208eDttKyspe2xldCBnPWhbbV0sYj1yW2cubWF0ZXJpYWxJbmRleF07Zm9yKGxldCBrPU1hdGgubWF4KGcuc3RhcnQsZi5zdGFydCksWj1NYXRoLm1pbihsLmNvdW50LE1hdGgubWluKGcuc3RhcnQrZy5jb3VudCxmLnN0YXJ0K2YuY291bnQpKTtrPFo7ays9MylzPUJPKHRoaXMsYix0LFh5LGwsYyx1LGQscCxrLGsrMSxrKzIpLHMmJihzLmZhY2VJbmRleD1NYXRoLmZsb29yKGsvMykscy5mYWNlLm1hdGVyaWFsSW5kZXg9Zy5tYXRlcmlhbEluZGV4LGUucHVzaChzKSl9ZWxzZSBmb3IobGV0IGc9TWF0aC5tYXgoMCxmLnN0YXJ0KSxiPU1hdGgubWluKGwuY291bnQsZi5zdGFydCtmLmNvdW50KTtnPGI7Zys9MylzPUJPKHRoaXMscix0LFh5LGwsYyx1LGQscCxnLGcrMSxnKzIpLHMmJihzLmZhY2VJbmRleD1NYXRoLmZsb29yKGcvMyksZS5wdXNoKHMpKX1lbHNlIGkuaXNHZW9tZXRyeSYmY29uc29sZS5lcnJvcigiVEhSRUUuTWVzaC5yYXljYXN0KCkgbm8gbG9uZ2VyIHN1cHBvcnRzIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX19O2Z1bmN0aW9uIEJPKG4sdCxlLGkscixvLHMsYSxsLGMsdSxkKXtkZi5mcm9tQnVmZmVyQXR0cmlidXRlKHIsYykscGYuZnJvbUJ1ZmZlckF0dHJpYnV0ZShyLHUpLGhmLmZyb21CdWZmZXJBdHRyaWJ1dGUocixkKTtsZXQgcD1uLm1vcnBoVGFyZ2V0SW5mbHVlbmNlcztpZihvJiZwKXtQTy5zZXQoMCwwLDApLFJPLnNldCgwLDAsMCksT08uc2V0KDAsMCwwKTtmb3IobGV0IGY9MCxtPW8ubGVuZ3RoO2Y8bTtmKyspe2xldCB4PXBbZl0sZz1vW2ZdOzAhPT14JiYocWouZnJvbUJ1ZmZlckF0dHJpYnV0ZShnLGMpLFlqLmZyb21CdWZmZXJBdHRyaWJ1dGUoZyx1KSxYai5mcm9tQnVmZmVyQXR0cmlidXRlKGcsZCkscz8oUE8uYWRkU2NhbGVkVmVjdG9yKHFqLHgpLFJPLmFkZFNjYWxlZFZlY3RvcihZaix4KSxPTy5hZGRTY2FsZWRWZWN0b3IoWGoseCkpOihQTy5hZGRTY2FsZWRWZWN0b3IocWouc3ViKGRmKSx4KSxSTy5hZGRTY2FsZWRWZWN0b3IoWWouc3ViKHBmKSx4KSxPTy5hZGRTY2FsZWRWZWN0b3IoWGouc3ViKGhmKSx4KSkpfWRmLmFkZChQTykscGYuYWRkKFJPKSxoZi5hZGQoT08pfW4uaXNTa2lubmVkTWVzaCYmKG4uYm9uZVRyYW5zZm9ybShjLGRmKSxuLmJvbmVUcmFuc2Zvcm0odSxwZiksbi5ib25lVHJhbnNmb3JtKGQsaGYpKTtsZXQgaD1mdW5jdGlvbihuLHQsZSxpLHIsbyxzLGEpe2xldCBsO2lmKGw9MT09PXQuc2lkZT9pLmludGVyc2VjdFRyaWFuZ2xlKHMsbyxyLCEwLGEpOmkuaW50ZXJzZWN0VHJpYW5nbGUocixvLHMsMiE9PXQuc2lkZSxhKSxudWxsPT09bClyZXR1cm4gbnVsbDtMTy5jb3B5KGEpLExPLmFwcGx5TWF0cml4NChuLm1hdHJpeFdvcmxkKTtsZXQgYz1lLnJheS5vcmlnaW4uZGlzdGFuY2VUbyhMTyk7cmV0dXJuIGM8ZS5uZWFyfHxjPmUuZmFyP251bGw6e2Rpc3RhbmNlOmMscG9pbnQ6TE8uY2xvbmUoKSxvYmplY3Q6bn19KG4sdCxlLGksZGYscGYsaGYsUWopO2lmKGgpe2EmJihrTy5mcm9tQnVmZmVyQXR0cmlidXRlKGEsYyksRk8uZnJvbUJ1ZmZlckF0dHJpYnV0ZShhLHUpLE5PLmZyb21CdWZmZXJBdHRyaWJ1dGUoYSxkKSxoLnV2PWxvLmdldFVWKFFqLGRmLHBmLGhmLGtPLEZPLE5PLG5ldyBhdCkpLGwmJihrTy5mcm9tQnVmZmVyQXR0cmlidXRlKGwsYyksRk8uZnJvbUJ1ZmZlckF0dHJpYnV0ZShsLHUpLE5PLmZyb21CdWZmZXJBdHRyaWJ1dGUobCxkKSxoLnV2Mj1sby5nZXRVVihRaixkZixwZixoZixrTyxGTyxOTyxuZXcgYXQpKTtsZXQgZj17YTpjLGI6dSxjOmQsbm9ybWFsOm5ldyBpZSxtYXRlcmlhbEluZGV4OjB9O2xvLmdldE5vcm1hbChkZixwZixoZixmLm5vcm1hbCksaC5mYWNlPWZ9cmV0dXJuIGh9Vm8ucHJvdG90eXBlLmlzTWVzaD0hMDt2YXIgV2c9Y2xhc3MgZXh0ZW5kcyBucntjb25zdHJ1Y3Rvcih0PTEsZT0xLGk9MSxyPTEsbz0xLHM9MSl7c3VwZXIoKSx0aGlzLnR5cGU9IkJveEdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3dpZHRoOnQsaGVpZ2h0OmUsZGVwdGg6aSx3aWR0aFNlZ21lbnRzOnIsaGVpZ2h0U2VnbWVudHM6byxkZXB0aFNlZ21lbnRzOnN9O2xldCBhPXRoaXM7cj1NYXRoLmZsb29yKHIpLG89TWF0aC5mbG9vcihvKSxzPU1hdGguZmxvb3Iocyk7bGV0IGw9W10sYz1bXSx1PVtdLGQ9W10scD0wLGg9MDtmdW5jdGlvbiBmKG0seCxnLGIsRCxULGssWix6LGZlLHVlKXtsZXQgaGU9VC96LHc9ay9mZSxGPVQvMixxPWsvMixLPVovMixkZT16KzEsWT1mZSsxLGFlPTAsbGU9MCxJZT1uZXcgaWU7Zm9yKGxldCB2ZT0wO3ZlPFk7dmUrKyl7bGV0IERlPXZlKnctcTtmb3IobGV0IG50PTA7bnQ8ZGU7bnQrKylJZVttXT0obnQqaGUtRikqYixJZVt4XT1EZSpELEllW2ddPUssYy5wdXNoKEllLngsSWUueSxJZS56KSxJZVttXT0wLEllW3hdPTAsSWVbZ109Wj4wPzE6LTEsdS5wdXNoKEllLngsSWUueSxJZS56KSxkLnB1c2gobnQveiksZC5wdXNoKDEtdmUvZmUpLGFlKz0xfWZvcihsZXQgdmU9MDt2ZTxmZTt2ZSsrKWZvcihsZXQgRGU9MDtEZTx6O0RlKyspe2xldCBndD1wK0RlK2RlKih2ZSsxKSxVZT1wKyhEZSsxKStkZSoodmUrMSksQWU9cCsoRGUrMSkrZGUqdmU7bC5wdXNoKHArRGUrZGUqdmUsZ3QsQWUpLGwucHVzaChndCxVZSxBZSksbGUrPTZ9YS5hZGRHcm91cChoLGxlLHVlKSxoKz1sZSxwKz1hZX1mKCJ6IiwieSIsIngiLC0xLC0xLGksZSx0LHMsbywwKSxmKCJ6IiwieSIsIngiLDEsLTEsaSxlLC10LHMsbywxKSxmKCJ4IiwieiIsInkiLDEsMSx0LGksZSxyLHMsMiksZigieCIsInoiLCJ5IiwxLC0xLHQsaSwtZSxyLHMsMyksZigieCIsInkiLCJ6IiwxLC0xLHQsZSxpLHIsbyw0KSxmKCJ4IiwieSIsInoiLC0xLC0xLHQsZSwtaSxyLG8sNSksdGhpcy5zZXRJbmRleChsKSx0aGlzLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyBKcihjLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgibm9ybWFsIixuZXcgSnIodSwzKSksdGhpcy5zZXRBdHRyaWJ1dGUoInV2IixuZXcgSnIoZCwyKSl9c3RhdGljIGZyb21KU09OKHQpe3JldHVybiBuZXcgV2codC53aWR0aCx0LmhlaWdodCx0LmRlcHRoLHQud2lkdGhTZWdtZW50cyx0LmhlaWdodFNlZ21lbnRzLHQuZGVwdGhTZWdtZW50cyl9fTtmdW5jdGlvbiBmYihuKXtsZXQgdD17fTtmb3IobGV0IGUgaW4gbil7dFtlXT17fTtmb3IobGV0IGkgaW4gbltlXSl7bGV0IHI9bltlXVtpXTt0W2VdW2ldPXImJihyLmlzQ29sb3J8fHIuaXNNYXRyaXgzfHxyLmlzTWF0cml4NHx8ci5pc1ZlY3RvcjJ8fHIuaXNWZWN0b3IzfHxyLmlzVmVjdG9yNHx8ci5pc1RleHR1cmV8fHIuaXNRdWF0ZXJuaW9uKT9yLmNsb25lKCk6QXJyYXkuaXNBcnJheShyKT9yLnNsaWNlKCk6cn19cmV0dXJuIHR9ZnVuY3Rpb24ganMobil7bGV0IHQ9e307Zm9yKGxldCBlPTA7ZTxuLmxlbmd0aDtlKyspe2xldCBpPWZiKG5bZV0pO2ZvcihsZXQgciBpbiBpKXRbcl09aVtyXX1yZXR1cm4gdH12YXIgSjhlPXtjbG9uZTpmYixtZXJnZTpqc30sRHA9Y2xhc3MgZXh0ZW5kcyBoc3tjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iU2hhZGVyTWF0ZXJpYWwiLHRoaXMuZGVmaW5lcz17fSx0aGlzLnVuaWZvcm1zPXt9LHRoaXMudmVydGV4U2hhZGVyPSJ2b2lkIG1haW4oKSB7XG5cdGdsX1Bvc2l0aW9uID0gcHJvamVjdGlvbk1hdHJpeCAqIG1vZGVsVmlld01hdHJpeCAqIHZlYzQoIHBvc2l0aW9uLCAxLjAgKTtcbn0iLHRoaXMuZnJhZ21lbnRTaGFkZXI9InZvaWQgbWFpbigpIHtcblx0Z2xfRnJhZ0NvbG9yID0gdmVjNCggMS4wLCAwLjAsIDAuMCwgMS4wICk7XG59Iix0aGlzLmxpbmV3aWR0aD0xLHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy5mb2c9ITEsdGhpcy5saWdodHM9ITEsdGhpcy5jbGlwcGluZz0hMSx0aGlzLmV4dGVuc2lvbnM9e2Rlcml2YXRpdmVzOiExLGZyYWdEZXB0aDohMSxkcmF3QnVmZmVyczohMSxzaGFkZXJUZXh0dXJlTE9EOiExfSx0aGlzLmRlZmF1bHRBdHRyaWJ1dGVWYWx1ZXM9e2NvbG9yOlsxLDEsMV0sdXY6WzAsMF0sdXYyOlswLDBdfSx0aGlzLmluZGV4MEF0dHJpYnV0ZU5hbWU9dm9pZCAwLHRoaXMudW5pZm9ybXNOZWVkVXBkYXRlPSExLHRoaXMuZ2xzbFZlcnNpb249bnVsbCx2b2lkIDAhPT10JiYodm9pZCAwIT09dC5hdHRyaWJ1dGVzJiZjb25zb2xlLmVycm9yKCJUSFJFRS5TaGFkZXJNYXRlcmlhbDogYXR0cmlidXRlcyBzaG91bGQgbm93IGJlIGRlZmluZWQgaW4gVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKSx0aGlzLnNldFZhbHVlcyh0KSl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmZyYWdtZW50U2hhZGVyPXQuZnJhZ21lbnRTaGFkZXIsdGhpcy52ZXJ0ZXhTaGFkZXI9dC52ZXJ0ZXhTaGFkZXIsdGhpcy51bmlmb3Jtcz1mYih0LnVuaWZvcm1zKSx0aGlzLmRlZmluZXM9T2JqZWN0LmFzc2lnbih7fSx0LmRlZmluZXMpLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXMubGlnaHRzPXQubGlnaHRzLHRoaXMuY2xpcHBpbmc9dC5jbGlwcGluZyx0aGlzLmV4dGVuc2lvbnM9T2JqZWN0LmFzc2lnbih7fSx0LmV4dGVuc2lvbnMpLHRoaXMuZ2xzbFZlcnNpb249dC5nbHNsVmVyc2lvbix0aGlzfXRvSlNPTih0KXtsZXQgZT1zdXBlci50b0pTT04odCk7ZS5nbHNsVmVyc2lvbj10aGlzLmdsc2xWZXJzaW9uLGUudW5pZm9ybXM9e307Zm9yKGxldCByIGluIHRoaXMudW5pZm9ybXMpe2xldCBzPXRoaXMudW5pZm9ybXNbcl0udmFsdWU7ZS51bmlmb3Jtc1tyXT1zJiZzLmlzVGV4dHVyZT97dHlwZToidCIsdmFsdWU6cy50b0pTT04odCkudXVpZH06cyYmcy5pc0NvbG9yP3t0eXBlOiJjIix2YWx1ZTpzLmdldEhleCgpfTpzJiZzLmlzVmVjdG9yMj97dHlwZToidjIiLHZhbHVlOnMudG9BcnJheSgpfTpzJiZzLmlzVmVjdG9yMz97dHlwZToidjMiLHZhbHVlOnMudG9BcnJheSgpfTpzJiZzLmlzVmVjdG9yND97dHlwZToidjQiLHZhbHVlOnMudG9BcnJheSgpfTpzJiZzLmlzTWF0cml4Mz97dHlwZToibTMiLHZhbHVlOnMudG9BcnJheSgpfTpzJiZzLmlzTWF0cml4ND97dHlwZToibTQiLHZhbHVlOnMudG9BcnJheSgpfTp7dmFsdWU6c319T2JqZWN0LmtleXModGhpcy5kZWZpbmVzKS5sZW5ndGg+MCYmKGUuZGVmaW5lcz10aGlzLmRlZmluZXMpLGUudmVydGV4U2hhZGVyPXRoaXMudmVydGV4U2hhZGVyLGUuZnJhZ21lbnRTaGFkZXI9dGhpcy5mcmFnbWVudFNoYWRlcjtsZXQgaT17fTtmb3IobGV0IHIgaW4gdGhpcy5leHRlbnNpb25zKSEwPT09dGhpcy5leHRlbnNpb25zW3JdJiYoaVtyXT0hMCk7cmV0dXJuIE9iamVjdC5rZXlzKGkpLmxlbmd0aD4wJiYoZS5leHRlbnNpb25zPWkpLGV9fTtEcC5wcm90b3R5cGUuaXNTaGFkZXJNYXRlcmlhbD0hMDt2YXIgUVM9Y2xhc3MgZXh0ZW5kcyBYaXtjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy50eXBlPSJDYW1lcmEiLHRoaXMubWF0cml4V29ybGRJbnZlcnNlPW5ldyBSbix0aGlzLnByb2plY3Rpb25NYXRyaXg9bmV3IFJuLHRoaXMucHJvamVjdGlvbk1hdHJpeEludmVyc2U9bmV3IFJufWNvcHkodCxlKXtyZXR1cm4gc3VwZXIuY29weSh0LGUpLHRoaXMubWF0cml4V29ybGRJbnZlcnNlLmNvcHkodC5tYXRyaXhXb3JsZEludmVyc2UpLHRoaXMucHJvamVjdGlvbk1hdHJpeC5jb3B5KHQucHJvamVjdGlvbk1hdHJpeCksdGhpcy5wcm9qZWN0aW9uTWF0cml4SW52ZXJzZS5jb3B5KHQucHJvamVjdGlvbk1hdHJpeEludmVyc2UpLHRoaXN9Z2V0V29ybGREaXJlY3Rpb24odCl7dGhpcy51cGRhdGVXb3JsZE1hdHJpeCghMCwhMSk7bGV0IGU9dGhpcy5tYXRyaXhXb3JsZC5lbGVtZW50cztyZXR1cm4gdC5zZXQoLWVbOF0sLWVbOV0sLWVbMTBdKS5ub3JtYWxpemUoKX11cGRhdGVNYXRyaXhXb3JsZCh0KXtzdXBlci51cGRhdGVNYXRyaXhXb3JsZCh0KSx0aGlzLm1hdHJpeFdvcmxkSW52ZXJzZS5jb3B5KHRoaXMubWF0cml4V29ybGQpLmludmVydCgpfXVwZGF0ZVdvcmxkTWF0cml4KHQsZSl7c3VwZXIudXBkYXRlV29ybGRNYXRyaXgodCxlKSx0aGlzLm1hdHJpeFdvcmxkSW52ZXJzZS5jb3B5KHRoaXMubWF0cml4V29ybGQpLmludmVydCgpfWNsb25lKCl7cmV0dXJuKG5ldyB0aGlzLmNvbnN0cnVjdG9yKS5jb3B5KHRoaXMpfX07UVMucHJvdG90eXBlLmlzQ2FtZXJhPSEwO3ZhciBXcz1jbGFzcyBleHRlbmRzIFFTe2NvbnN0cnVjdG9yKHQ9NTAsZT0xLGk9LjEscj0yZTMpe3N1cGVyKCksdGhpcy50eXBlPSJQZXJzcGVjdGl2ZUNhbWVyYSIsdGhpcy5mb3Y9dCx0aGlzLnpvb209MSx0aGlzLm5lYXI9aSx0aGlzLmZhcj1yLHRoaXMuZm9jdXM9MTAsdGhpcy5hc3BlY3Q9ZSx0aGlzLnZpZXc9bnVsbCx0aGlzLmZpbG1HYXVnZT0zNSx0aGlzLmZpbG1PZmZzZXQ9MCx0aGlzLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX1jb3B5KHQsZSl7cmV0dXJuIHN1cGVyLmNvcHkodCxlKSx0aGlzLmZvdj10LmZvdix0aGlzLnpvb209dC56b29tLHRoaXMubmVhcj10Lm5lYXIsdGhpcy5mYXI9dC5mYXIsdGhpcy5mb2N1cz10LmZvY3VzLHRoaXMuYXNwZWN0PXQuYXNwZWN0LHRoaXMudmlldz1udWxsPT09dC52aWV3P251bGw6T2JqZWN0LmFzc2lnbih7fSx0LnZpZXcpLHRoaXMuZmlsbUdhdWdlPXQuZmlsbUdhdWdlLHRoaXMuZmlsbU9mZnNldD10LmZpbG1PZmZzZXQsdGhpc31zZXRGb2NhbExlbmd0aCh0KXtsZXQgZT0uNSp0aGlzLmdldEZpbG1IZWlnaHQoKS90O3RoaXMuZm92PTIqaDgqTWF0aC5hdGFuKGUpLHRoaXMudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpfWdldEZvY2FsTGVuZ3RoKCl7bGV0IHQ9TWF0aC50YW4oLjUqRGoqdGhpcy5mb3YpO3JldHVybi41KnRoaXMuZ2V0RmlsbUhlaWdodCgpL3R9Z2V0RWZmZWN0aXZlRk9WKCl7cmV0dXJuIDIqaDgqTWF0aC5hdGFuKE1hdGgudGFuKC41KkRqKnRoaXMuZm92KS90aGlzLnpvb20pfWdldEZpbG1XaWR0aCgpe3JldHVybiB0aGlzLmZpbG1HYXVnZSpNYXRoLm1pbih0aGlzLmFzcGVjdCwxKX1nZXRGaWxtSGVpZ2h0KCl7cmV0dXJuIHRoaXMuZmlsbUdhdWdlL01hdGgubWF4KHRoaXMuYXNwZWN0LDEpfXNldFZpZXdPZmZzZXQodCxlLGkscixvLHMpe3RoaXMuYXNwZWN0PXQvZSxudWxsPT09dGhpcy52aWV3JiYodGhpcy52aWV3PXtlbmFibGVkOiEwLGZ1bGxXaWR0aDoxLGZ1bGxIZWlnaHQ6MSxvZmZzZXRYOjAsb2Zmc2V0WTowLHdpZHRoOjEsaGVpZ2h0OjF9KSx0aGlzLnZpZXcuZW5hYmxlZD0hMCx0aGlzLnZpZXcuZnVsbFdpZHRoPXQsdGhpcy52aWV3LmZ1bGxIZWlnaHQ9ZSx0aGlzLnZpZXcub2Zmc2V0WD1pLHRoaXMudmlldy5vZmZzZXRZPXIsdGhpcy52aWV3LndpZHRoPW8sdGhpcy52aWV3LmhlaWdodD1zLHRoaXMudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpfWNsZWFyVmlld09mZnNldCgpe251bGwhPT10aGlzLnZpZXcmJih0aGlzLnZpZXcuZW5hYmxlZD0hMSksdGhpcy51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl9dXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpe2xldCB0PXRoaXMubmVhcixlPXQqTWF0aC50YW4oLjUqRGoqdGhpcy5mb3YpL3RoaXMuem9vbSxpPTIqZSxyPXRoaXMuYXNwZWN0Kmksbz0tLjUqcixzPXRoaXMudmlldztpZihudWxsIT09dGhpcy52aWV3JiZ0aGlzLnZpZXcuZW5hYmxlZCl7bGV0IGw9cy5mdWxsV2lkdGgsYz1zLmZ1bGxIZWlnaHQ7bys9cy5vZmZzZXRYKnIvbCxlLT1zLm9mZnNldFkqaS9jLHIqPXMud2lkdGgvbCxpKj1zLmhlaWdodC9jfWxldCBhPXRoaXMuZmlsbU9mZnNldDswIT09YSYmKG8rPXQqYS90aGlzLmdldEZpbG1XaWR0aCgpKSx0aGlzLnByb2plY3Rpb25NYXRyaXgubWFrZVBlcnNwZWN0aXZlKG8sbytyLGUsZS1pLHQsdGhpcy5mYXIpLHRoaXMucHJvamVjdGlvbk1hdHJpeEludmVyc2UuY29weSh0aGlzLnByb2plY3Rpb25NYXRyaXgpLmludmVydCgpfXRvSlNPTih0KXtsZXQgZT1zdXBlci50b0pTT04odCk7cmV0dXJuIGUub2JqZWN0LmZvdj10aGlzLmZvdixlLm9iamVjdC56b29tPXRoaXMuem9vbSxlLm9iamVjdC5uZWFyPXRoaXMubmVhcixlLm9iamVjdC5mYXI9dGhpcy5mYXIsZS5vYmplY3QuZm9jdXM9dGhpcy5mb2N1cyxlLm9iamVjdC5hc3BlY3Q9dGhpcy5hc3BlY3QsbnVsbCE9PXRoaXMudmlldyYmKGUub2JqZWN0LnZpZXc9T2JqZWN0LmFzc2lnbih7fSx0aGlzLnZpZXcpKSxlLm9iamVjdC5maWxtR2F1Z2U9dGhpcy5maWxtR2F1Z2UsZS5vYmplY3QuZmlsbU9mZnNldD10aGlzLmZpbG1PZmZzZXQsZX19O1dzLnByb3RvdHlwZS5pc1BlcnNwZWN0aXZlQ2FtZXJhPSEwO3ZhciBLUz1jbGFzcyBleHRlbmRzIFhpe2NvbnN0cnVjdG9yKHQsZSxpKXtpZihzdXBlcigpLHRoaXMudHlwZT0iQ3ViZUNhbWVyYSIsITAhPT1pLmlzV2ViR0xDdWJlUmVuZGVyVGFyZ2V0KXJldHVybiB2b2lkIGNvbnNvbGUuZXJyb3IoIlRIUkVFLkN1YmVDYW1lcmE6IFRoZSBjb25zdHJ1Y3RvciBub3cgZXhwZWN0cyBhbiBpbnN0YW5jZSBvZiBXZWJHTEN1YmVSZW5kZXJUYXJnZXQgYXMgdGhpcmQgcGFyYW1ldGVyLiIpO3RoaXMucmVuZGVyVGFyZ2V0PWk7bGV0IHI9bmV3IFdzKDkwLDEsdCxlKTtyLmxheWVycz10aGlzLmxheWVycyxyLnVwLnNldCgwLC0xLDApLHIubG9va0F0KG5ldyBpZSgxLDAsMCkpLHRoaXMuYWRkKHIpO2xldCBvPW5ldyBXcyg5MCwxLHQsZSk7by5sYXllcnM9dGhpcy5sYXllcnMsby51cC5zZXQoMCwtMSwwKSxvLmxvb2tBdChuZXcgaWUoLTEsMCwwKSksdGhpcy5hZGQobyk7bGV0IHM9bmV3IFdzKDkwLDEsdCxlKTtzLmxheWVycz10aGlzLmxheWVycyxzLnVwLnNldCgwLDAsMSkscy5sb29rQXQobmV3IGllKDAsMSwwKSksdGhpcy5hZGQocyk7bGV0IGE9bmV3IFdzKDkwLDEsdCxlKTthLmxheWVycz10aGlzLmxheWVycyxhLnVwLnNldCgwLDAsLTEpLGEubG9va0F0KG5ldyBpZSgwLC0xLDApKSx0aGlzLmFkZChhKTtsZXQgbD1uZXcgV3MoOTAsMSx0LGUpO2wubGF5ZXJzPXRoaXMubGF5ZXJzLGwudXAuc2V0KDAsLTEsMCksbC5sb29rQXQobmV3IGllKDAsMCwxKSksdGhpcy5hZGQobCk7bGV0IGM9bmV3IFdzKDkwLDEsdCxlKTtjLmxheWVycz10aGlzLmxheWVycyxjLnVwLnNldCgwLC0xLDApLGMubG9va0F0KG5ldyBpZSgwLDAsLTEpKSx0aGlzLmFkZChjKX11cGRhdGUodCxlKXtudWxsPT09dGhpcy5wYXJlbnQmJnRoaXMudXBkYXRlTWF0cml4V29ybGQoKTtsZXQgaT10aGlzLnJlbmRlclRhcmdldCxbcixvLHMsYSxsLGNdPXRoaXMuY2hpbGRyZW4sdT10LnhyLmVuYWJsZWQsZD10LmdldFJlbmRlclRhcmdldCgpO3QueHIuZW5hYmxlZD0hMTtsZXQgcD1pLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzO2kudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM9ITEsdC5zZXRSZW5kZXJUYXJnZXQoaSwwKSx0LnJlbmRlcihlLHIpLHQuc2V0UmVuZGVyVGFyZ2V0KGksMSksdC5yZW5kZXIoZSxvKSx0LnNldFJlbmRlclRhcmdldChpLDIpLHQucmVuZGVyKGUscyksdC5zZXRSZW5kZXJUYXJnZXQoaSwzKSx0LnJlbmRlcihlLGEpLHQuc2V0UmVuZGVyVGFyZ2V0KGksNCksdC5yZW5kZXIoZSxsKSxpLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzPXAsdC5zZXRSZW5kZXJUYXJnZXQoaSw1KSx0LnJlbmRlcihlLGMpLHQuc2V0UmVuZGVyVGFyZ2V0KGQpLHQueHIuZW5hYmxlZD11LGkudGV4dHVyZS5uZWVkc1BNUkVNVXBkYXRlPSEwfX0sbWI9Y2xhc3MgZXh0ZW5kcyBIb3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyxhLGwsYyx1KXtzdXBlcih0PXZvaWQgMCE9PXQ/dDpbXSxlPXZvaWQgMCE9PWU/ZTozMDEsaSxyLG8scyxhLGwsYyx1KSx0aGlzLmZsaXBZPSExfWdldCBpbWFnZXMoKXtyZXR1cm4gdGhpcy5pbWFnZX1zZXQgaW1hZ2VzKHQpe3RoaXMuaW1hZ2U9dH19O21iLnByb3RvdHlwZS5pc0N1YmVUZXh0dXJlPSEwO3ZhciBjaz1jbGFzcyBleHRlbmRzIFdhe2NvbnN0cnVjdG9yKHQsZSxpKXtOdW1iZXIuaXNJbnRlZ2VyKGUpJiYoY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTEN1YmVSZW5kZXJUYXJnZXQ6IGNvbnN0cnVjdG9yIHNpZ25hdHVyZSBpcyBub3cgV2ViR0xDdWJlUmVuZGVyVGFyZ2V0KCBzaXplLCBvcHRpb25zICkiKSxlPWkpLHN1cGVyKHQsdCxlKSx0aGlzLnRleHR1cmU9bmV3IG1iKHZvaWQgMCwoZT1lfHx7fSkubWFwcGluZyxlLndyYXBTLGUud3JhcFQsZS5tYWdGaWx0ZXIsZS5taW5GaWx0ZXIsZS5mb3JtYXQsZS50eXBlLGUuYW5pc290cm9weSxlLmVuY29kaW5nKSx0aGlzLnRleHR1cmUuaXNSZW5kZXJUYXJnZXRUZXh0dXJlPSEwLHRoaXMudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM9dm9pZCAwIT09ZS5nZW5lcmF0ZU1pcG1hcHMmJmUuZ2VuZXJhdGVNaXBtYXBzLHRoaXMudGV4dHVyZS5taW5GaWx0ZXI9dm9pZCAwIT09ZS5taW5GaWx0ZXI/ZS5taW5GaWx0ZXI6R3N9ZnJvbUVxdWlyZWN0YW5ndWxhclRleHR1cmUodCxlKXt0aGlzLnRleHR1cmUudHlwZT1lLnR5cGUsdGhpcy50ZXh0dXJlLmZvcm1hdD1nYSx0aGlzLnRleHR1cmUuZW5jb2Rpbmc9ZS5lbmNvZGluZyx0aGlzLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzPWUuZ2VuZXJhdGVNaXBtYXBzLHRoaXMudGV4dHVyZS5taW5GaWx0ZXI9ZS5taW5GaWx0ZXIsdGhpcy50ZXh0dXJlLm1hZ0ZpbHRlcj1lLm1hZ0ZpbHRlcjtsZXQgaV91bmlmb3Jtcz17dEVxdWlyZWN0Ont2YWx1ZTpudWxsfX0saV92ZXJ0ZXhTaGFkZXI9IlxuXG5cdFx0XHRcdHZhcnlpbmcgdmVjMyB2V29ybGREaXJlY3Rpb247XG5cblx0XHRcdFx0dmVjMyB0cmFuc2Zvcm1EaXJlY3Rpb24oIGluIHZlYzMgZGlyLCBpbiBtYXQ0IG1hdHJpeCApIHtcblxuXHRcdFx0XHRcdHJldHVybiBub3JtYWxpemUoICggbWF0cml4ICogdmVjNCggZGlyLCAwLjAgKSApLnh5eiApO1xuXG5cdFx0XHRcdH1cblxuXHRcdFx0XHR2b2lkIG1haW4oKSB7XG5cblx0XHRcdFx0XHR2V29ybGREaXJlY3Rpb24gPSB0cmFuc2Zvcm1EaXJlY3Rpb24oIHBvc2l0aW9uLCBtb2RlbE1hdHJpeCApO1xuXG5cdFx0XHRcdFx0I2luY2x1ZGUgPGJlZ2luX3ZlcnRleD5cblx0XHRcdFx0XHQjaW5jbHVkZSA8cHJvamVjdF92ZXJ0ZXg+XG5cblx0XHRcdFx0fVxuXHRcdFx0IixpX2ZyYWdtZW50U2hhZGVyPSJcblxuXHRcdFx0XHR1bmlmb3JtIHNhbXBsZXIyRCB0RXF1aXJlY3Q7XG5cblx0XHRcdFx0dmFyeWluZyB2ZWMzIHZXb3JsZERpcmVjdGlvbjtcblxuXHRcdFx0XHQjaW5jbHVkZSA8Y29tbW9uPlxuXG5cdFx0XHRcdHZvaWQgbWFpbigpIHtcblxuXHRcdFx0XHRcdHZlYzMgZGlyZWN0aW9uID0gbm9ybWFsaXplKCB2V29ybGREaXJlY3Rpb24gKTtcblxuXHRcdFx0XHRcdHZlYzIgc2FtcGxlVVYgPSBlcXVpcmVjdFV2KCBkaXJlY3Rpb24gKTtcblxuXHRcdFx0XHRcdGdsX0ZyYWdDb2xvciA9IHRleHR1cmUyRCggdEVxdWlyZWN0LCBzYW1wbGVVViApO1xuXG5cdFx0XHRcdH1cblx0XHRcdCIscj1uZXcgV2coNSw1LDUpLG89bmV3IERwKHtuYW1lOiJDdWJlbWFwRnJvbUVxdWlyZWN0Iix1bmlmb3JtczpmYihpX3VuaWZvcm1zKSx2ZXJ0ZXhTaGFkZXI6aV92ZXJ0ZXhTaGFkZXIsZnJhZ21lbnRTaGFkZXI6aV9mcmFnbWVudFNoYWRlcixzaWRlOjEsYmxlbmRpbmc6MH0pO28udW5pZm9ybXMudEVxdWlyZWN0LnZhbHVlPWU7bGV0IHM9bmV3IFZvKHIsbyksYT1lLm1pbkZpbHRlcjtyZXR1cm4gMTAwOD09PWUubWluRmlsdGVyJiYoZS5taW5GaWx0ZXI9R3MpLG5ldyBLUygxLDEwLHRoaXMpLnVwZGF0ZSh0LHMpLGUubWluRmlsdGVyPWEscy5nZW9tZXRyeS5kaXNwb3NlKCkscy5tYXRlcmlhbC5kaXNwb3NlKCksdGhpc31jbGVhcih0LGUsaSxyKXtsZXQgbz10LmdldFJlbmRlclRhcmdldCgpO2ZvcihsZXQgcz0wO3M8NjtzKyspdC5zZXRSZW5kZXJUYXJnZXQodGhpcyxzKSx0LmNsZWFyKGUsaSxyKTt0LnNldFJlbmRlclRhcmdldChvKX19O2NrLnByb3RvdHlwZS5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldD0hMDt2YXIgS2o9bmV3IGllLHRHZT1uZXcgaWUsbkdlPW5ldyBKbyx1dT1jbGFzc3tjb25zdHJ1Y3Rvcih0PW5ldyBpZSgxLDAsMCksZT0wKXt0aGlzLm5vcm1hbD10LHRoaXMuY29uc3RhbnQ9ZX1zZXQodCxlKXtyZXR1cm4gdGhpcy5ub3JtYWwuY29weSh0KSx0aGlzLmNvbnN0YW50PWUsdGhpc31zZXRDb21wb25lbnRzKHQsZSxpLHIpe3JldHVybiB0aGlzLm5vcm1hbC5zZXQodCxlLGkpLHRoaXMuY29uc3RhbnQ9cix0aGlzfXNldEZyb21Ob3JtYWxBbmRDb3BsYW5hclBvaW50KHQsZSl7cmV0dXJuIHRoaXMubm9ybWFsLmNvcHkodCksdGhpcy5jb25zdGFudD0tZS5kb3QodGhpcy5ub3JtYWwpLHRoaXN9c2V0RnJvbUNvcGxhbmFyUG9pbnRzKHQsZSxpKXtsZXQgcj1Lai5zdWJWZWN0b3JzKGksZSkuY3Jvc3ModEdlLnN1YlZlY3RvcnModCxlKSkubm9ybWFsaXplKCk7cmV0dXJuIHRoaXMuc2V0RnJvbU5vcm1hbEFuZENvcGxhbmFyUG9pbnQocix0KSx0aGlzfWNvcHkodCl7cmV0dXJuIHRoaXMubm9ybWFsLmNvcHkodC5ub3JtYWwpLHRoaXMuY29uc3RhbnQ9dC5jb25zdGFudCx0aGlzfW5vcm1hbGl6ZSgpe2xldCB0PTEvdGhpcy5ub3JtYWwubGVuZ3RoKCk7cmV0dXJuIHRoaXMubm9ybWFsLm11bHRpcGx5U2NhbGFyKHQpLHRoaXMuY29uc3RhbnQqPXQsdGhpc31uZWdhdGUoKXtyZXR1cm4gdGhpcy5jb25zdGFudCo9LTEsdGhpcy5ub3JtYWwubmVnYXRlKCksdGhpc31kaXN0YW5jZVRvUG9pbnQodCl7cmV0dXJuIHRoaXMubm9ybWFsLmRvdCh0KSt0aGlzLmNvbnN0YW50fWRpc3RhbmNlVG9TcGhlcmUodCl7cmV0dXJuIHRoaXMuZGlzdGFuY2VUb1BvaW50KHQuY2VudGVyKS10LnJhZGl1c31wcm9qZWN0UG9pbnQodCxlKXtyZXR1cm4gZS5jb3B5KHRoaXMubm9ybWFsKS5tdWx0aXBseVNjYWxhcigtdGhpcy5kaXN0YW5jZVRvUG9pbnQodCkpLmFkZCh0KX1pbnRlcnNlY3RMaW5lKHQsZSl7bGV0IGk9dC5kZWx0YShLaikscj10aGlzLm5vcm1hbC5kb3QoaSk7aWYoMD09PXIpcmV0dXJuIDA9PT10aGlzLmRpc3RhbmNlVG9Qb2ludCh0LnN0YXJ0KT9lLmNvcHkodC5zdGFydCk6bnVsbDtsZXQgbz0tKHQuc3RhcnQuZG90KHRoaXMubm9ybWFsKSt0aGlzLmNvbnN0YW50KS9yO3JldHVybiBvPDB8fG8+MT9udWxsOmUuY29weShpKS5tdWx0aXBseVNjYWxhcihvKS5hZGQodC5zdGFydCl9aW50ZXJzZWN0c0xpbmUodCl7bGV0IGU9dGhpcy5kaXN0YW5jZVRvUG9pbnQodC5zdGFydCksaT10aGlzLmRpc3RhbmNlVG9Qb2ludCh0LmVuZCk7cmV0dXJuIGU8MCYmaT4wfHxpPDAmJmU+MH1pbnRlcnNlY3RzQm94KHQpe3JldHVybiB0LmludGVyc2VjdHNQbGFuZSh0aGlzKX1pbnRlcnNlY3RzU3BoZXJlKHQpe3JldHVybiB0LmludGVyc2VjdHNQbGFuZSh0aGlzKX1jb3BsYW5hclBvaW50KHQpe3JldHVybiB0LmNvcHkodGhpcy5ub3JtYWwpLm11bHRpcGx5U2NhbGFyKC10aGlzLmNvbnN0YW50KX1hcHBseU1hdHJpeDQodCxlKXtsZXQgaT1lfHxuR2UuZ2V0Tm9ybWFsTWF0cml4KHQpLHI9dGhpcy5jb3BsYW5hclBvaW50KEtqKS5hcHBseU1hdHJpeDQodCksbz10aGlzLm5vcm1hbC5hcHBseU1hdHJpeDMoaSkubm9ybWFsaXplKCk7cmV0dXJuIHRoaXMuY29uc3RhbnQ9LXIuZG90KG8pLHRoaXN9dHJhbnNsYXRlKHQpe3JldHVybiB0aGlzLmNvbnN0YW50LT10LmRvdCh0aGlzLm5vcm1hbCksdGhpc31lcXVhbHModCl7cmV0dXJuIHQubm9ybWFsLmVxdWFscyh0aGlzLm5vcm1hbCkmJnQuY29uc3RhbnQ9PT10aGlzLmNvbnN0YW50fWNsb25lKCl7cmV0dXJuKG5ldyB0aGlzLmNvbnN0cnVjdG9yKS5jb3B5KHRoaXMpfX07dXUucHJvdG90eXBlLmlzUGxhbmU9ITA7dmFyIFp5PW5ldyB4ZixWTz1uZXcgaWUsZ2I9Y2xhc3N7Y29uc3RydWN0b3IodD1uZXcgdXUsZT1uZXcgdXUsaT1uZXcgdXUscj1uZXcgdXUsbz1uZXcgdXUscz1uZXcgdXUpe3RoaXMucGxhbmVzPVt0LGUsaSxyLG8sc119c2V0KHQsZSxpLHIsbyxzKXtsZXQgYT10aGlzLnBsYW5lcztyZXR1cm4gYVswXS5jb3B5KHQpLGFbMV0uY29weShlKSxhWzJdLmNvcHkoaSksYVszXS5jb3B5KHIpLGFbNF0uY29weShvKSxhWzVdLmNvcHkocyksdGhpc31jb3B5KHQpe2xldCBlPXRoaXMucGxhbmVzO2ZvcihsZXQgaT0wO2k8NjtpKyspZVtpXS5jb3B5KHQucGxhbmVzW2ldKTtyZXR1cm4gdGhpc31zZXRGcm9tUHJvamVjdGlvbk1hdHJpeCh0KXtsZXQgZT10aGlzLnBsYW5lcyxpPXQuZWxlbWVudHMscj1pWzBdLG89aVsxXSxzPWlbMl0sYT1pWzNdLGw9aVs0XSxjPWlbNV0sdT1pWzZdLGQ9aVs3XSxwPWlbOF0saD1pWzldLGY9aVsxMF0sbT1pWzExXSx4PWlbMTJdLGc9aVsxM10sYj1pWzE0XSxEPWlbMTVdO3JldHVybiBlWzBdLnNldENvbXBvbmVudHMoYS1yLGQtbCxtLXAsRC14KS5ub3JtYWxpemUoKSxlWzFdLnNldENvbXBvbmVudHMoYStyLGQrbCxtK3AsRCt4KS5ub3JtYWxpemUoKSxlWzJdLnNldENvbXBvbmVudHMoYStvLGQrYyxtK2gsRCtnKS5ub3JtYWxpemUoKSxlWzNdLnNldENvbXBvbmVudHMoYS1vLGQtYyxtLWgsRC1nKS5ub3JtYWxpemUoKSxlWzRdLnNldENvbXBvbmVudHMoYS1zLGQtdSxtLWYsRC1iKS5ub3JtYWxpemUoKSxlWzVdLnNldENvbXBvbmVudHMoYStzLGQrdSxtK2YsRCtiKS5ub3JtYWxpemUoKSx0aGlzfWludGVyc2VjdHNPYmplY3QodCl7bGV0IGU9dC5nZW9tZXRyeTtyZXR1cm4gbnVsbD09PWUuYm91bmRpbmdTcGhlcmUmJmUuY29tcHV0ZUJvdW5kaW5nU3BoZXJlKCksWnkuY29weShlLmJvdW5kaW5nU3BoZXJlKS5hcHBseU1hdHJpeDQodC5tYXRyaXhXb3JsZCksdGhpcy5pbnRlcnNlY3RzU3BoZXJlKFp5KX1pbnRlcnNlY3RzU3ByaXRlKHQpe3JldHVybiBaeS5jZW50ZXIuc2V0KDAsMCwwKSxaeS5yYWRpdXM9LjcwNzEwNjc4MTE4NjU0NzYsWnkuYXBwbHlNYXRyaXg0KHQubWF0cml4V29ybGQpLHRoaXMuaW50ZXJzZWN0c1NwaGVyZShaeSl9aW50ZXJzZWN0c1NwaGVyZSh0KXtsZXQgZT10aGlzLnBsYW5lcyxpPXQuY2VudGVyLHI9LXQucmFkaXVzO2ZvcihsZXQgbz0wO288NjtvKyspaWYoZVtvXS5kaXN0YW5jZVRvUG9pbnQoaSk8cilyZXR1cm4hMTtyZXR1cm4hMH1pbnRlcnNlY3RzQm94KHQpe2xldCBlPXRoaXMucGxhbmVzO2ZvcihsZXQgaT0wO2k8NjtpKyspe2xldCByPWVbaV07aWYoVk8ueD1yLm5vcm1hbC54PjA/dC5tYXgueDp0Lm1pbi54LFZPLnk9ci5ub3JtYWwueT4wP3QubWF4Lnk6dC5taW4ueSxWTy56PXIubm9ybWFsLno+MD90Lm1heC56OnQubWluLnosci5kaXN0YW5jZVRvUG9pbnQoVk8pPDApcmV0dXJuITF9cmV0dXJuITB9Y29udGFpbnNQb2ludCh0KXtsZXQgZT10aGlzLnBsYW5lcztmb3IobGV0IGk9MDtpPDY7aSsrKWlmKGVbaV0uZGlzdGFuY2VUb1BvaW50KHQpPDApcmV0dXJuITE7cmV0dXJuITB9Y2xvbmUoKXtyZXR1cm4obmV3IHRoaXMuY29uc3RydWN0b3IpLmNvcHkodGhpcyl9fTtmdW5jdGlvbiBrZGUoKXtsZXQgbj1udWxsLHQ9ITEsZT1udWxsLGk9bnVsbDtmdW5jdGlvbiByKG8scyl7ZShvLHMpLGk9bi5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUocil9cmV0dXJue3N0YXJ0OmZ1bmN0aW9uKCl7ITAhPT10JiZudWxsIT09ZSYmKGk9bi5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUociksdD0hMCl9LHN0b3A6ZnVuY3Rpb24oKXtuLmNhbmNlbEFuaW1hdGlvbkZyYW1lKGkpLHQ9ITF9LHNldEFuaW1hdGlvbkxvb3A6ZnVuY3Rpb24obyl7ZT1vfSxzZXRDb250ZXh0OmZ1bmN0aW9uKG8pe249b319fWZ1bmN0aW9uIGlHZShuLHQpe2xldCBlPXQuaXNXZWJHTDIsaT1uZXcgV2Vha01hcDtyZXR1cm57Z2V0OmZ1bmN0aW9uKGMpe3JldHVybiBjLmlzSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGUmJihjPWMuZGF0YSksaS5nZXQoYyl9LHJlbW92ZTpmdW5jdGlvbihjKXtjLmlzSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGUmJihjPWMuZGF0YSk7bGV0IHU9aS5nZXQoYyk7dSYmKG4uZGVsZXRlQnVmZmVyKHUuYnVmZmVyKSxpLmRlbGV0ZShjKSl9LHVwZGF0ZTpmdW5jdGlvbihjLHUpe2lmKGMuaXNHTEJ1ZmZlckF0dHJpYnV0ZSl7bGV0IHA9aS5nZXQoYyk7cmV0dXJuIHZvaWQoKCFwfHxwLnZlcnNpb248Yy52ZXJzaW9uKSYmaS5zZXQoYyx7YnVmZmVyOmMuYnVmZmVyLHR5cGU6Yy50eXBlLGJ5dGVzUGVyRWxlbWVudDpjLmVsZW1lbnRTaXplLHZlcnNpb246Yy52ZXJzaW9ufSkpfWMuaXNJbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZSYmKGM9Yy5kYXRhKTtsZXQgZD1pLmdldChjKTt2b2lkIDA9PT1kP2kuc2V0KGMsZnVuY3Rpb24oYyx1KXtsZXQgZD1jLmFycmF5LHA9Yy51c2FnZSxoPW4uY3JlYXRlQnVmZmVyKCk7bi5iaW5kQnVmZmVyKHUsaCksbi5idWZmZXJEYXRhKHUsZCxwKSxjLm9uVXBsb2FkQ2FsbGJhY2soKTtsZXQgZj01MTI2O3JldHVybiBkIGluc3RhbmNlb2YgRmxvYXQzMkFycmF5P2Y9NTEyNjpkIGluc3RhbmNlb2YgRmxvYXQ2NEFycmF5P2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xBdHRyaWJ1dGVzOiBVbnN1cHBvcnRlZCBkYXRhIGJ1ZmZlciBmb3JtYXQ6IEZsb2F0NjRBcnJheS4iKTpkIGluc3RhbmNlb2YgVWludDE2QXJyYXk/Yy5pc0Zsb2F0MTZCdWZmZXJBdHRyaWJ1dGU/ZT9mPTUxMzE6Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTEF0dHJpYnV0ZXM6IFVzYWdlIG9mIEZsb2F0MTZCdWZmZXJBdHRyaWJ1dGUgcmVxdWlyZXMgV2ViR0wyLiIpOmY9NTEyMzpkIGluc3RhbmNlb2YgSW50MTZBcnJheT9mPTUxMjI6ZCBpbnN0YW5jZW9mIFVpbnQzMkFycmF5P2Y9NTEyNTpkIGluc3RhbmNlb2YgSW50MzJBcnJheT9mPTUxMjQ6ZCBpbnN0YW5jZW9mIEludDhBcnJheT9mPTUxMjA6KGQgaW5zdGFuY2VvZiBVaW50OEFycmF5fHxkIGluc3RhbmNlb2YgVWludDhDbGFtcGVkQXJyYXkpJiYoZj01MTIxKSx7YnVmZmVyOmgsdHlwZTpmLGJ5dGVzUGVyRWxlbWVudDpkLkJZVEVTX1BFUl9FTEVNRU5ULHZlcnNpb246Yy52ZXJzaW9ufX0oYyx1KSk6ZC52ZXJzaW9uPGMudmVyc2lvbiYmKGZ1bmN0aW9uKGMsdSxkKXtsZXQgcD11LmFycmF5LGg9dS51cGRhdGVSYW5nZTtuLmJpbmRCdWZmZXIoZCxjKSwtMT09PWguY291bnQ/bi5idWZmZXJTdWJEYXRhKGQsMCxwKTooZT9uLmJ1ZmZlclN1YkRhdGEoZCxoLm9mZnNldCpwLkJZVEVTX1BFUl9FTEVNRU5ULHAsaC5vZmZzZXQsaC5jb3VudCk6bi5idWZmZXJTdWJEYXRhKGQsaC5vZmZzZXQqcC5CWVRFU19QRVJfRUxFTUVOVCxwLnN1YmFycmF5KGgub2Zmc2V0LGgub2Zmc2V0K2guY291bnQpKSxoLmNvdW50PS0xKX0oZC5idWZmZXIsYyx1KSxkLnZlcnNpb249Yy52ZXJzaW9uKX19fXZhciBaUz1jbGFzcyBleHRlbmRzIG5ye2NvbnN0cnVjdG9yKHQ9MSxlPTEsaT0xLHI9MSl7c3VwZXIoKSx0aGlzLnR5cGU9IlBsYW5lR2VvbWV0cnkiLHRoaXMucGFyYW1ldGVycz17d2lkdGg6dCxoZWlnaHQ6ZSx3aWR0aFNlZ21lbnRzOmksaGVpZ2h0U2VnbWVudHM6cn07bGV0IG89dC8yLHM9ZS8yLGE9TWF0aC5mbG9vcihpKSxsPU1hdGguZmxvb3IociksYz1hKzEsdT1sKzEsZD10L2EscD1lL2wsaD1bXSxmPVtdLG09W10seD1bXTtmb3IobGV0IGc9MDtnPHU7ZysrKXtsZXQgYj1nKnAtcztmb3IobGV0IEQ9MDtEPGM7RCsrKWYucHVzaChEKmQtbywtYiwwKSxtLnB1c2goMCwwLDEpLHgucHVzaChEL2EpLHgucHVzaCgxLWcvbCl9Zm9yKGxldCBnPTA7ZzxsO2crKylmb3IobGV0IGI9MDtiPGE7YisrKXtsZXQgVD1iK2MqKGcrMSksaz1iKzErYyooZysxKSxaPWIrMStjKmc7aC5wdXNoKGIrYypnLFQsWiksaC5wdXNoKFQsayxaKX10aGlzLnNldEluZGV4KGgpLHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IEpyKGYsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG5ldyBKcihtLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyBKcih4LDIpKX1zdGF0aWMgZnJvbUpTT04odCl7cmV0dXJuIG5ldyBaUyh0LndpZHRoLHQuaGVpZ2h0LHQud2lkdGhTZWdtZW50cyx0LmhlaWdodFNlZ21lbnRzKX19LERpPXthbHBoYW1hcF9mcmFnbWVudDoiI2lmZGVmIFVTRV9BTFBIQU1BUFxuXHRkaWZmdXNlQ29sb3IuYSAqPSB0ZXh0dXJlMkQoIGFscGhhTWFwLCB2VXYgKS5nO1xuI2VuZGlmIixhbHBoYW1hcF9wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX0FMUEhBTUFQXG5cdHVuaWZvcm0gc2FtcGxlcjJEIGFscGhhTWFwO1xuI2VuZGlmIixhbHBoYXRlc3RfZnJhZ21lbnQ6IiNpZmRlZiBVU0VfQUxQSEFURVNUXG5cdGlmICggZGlmZnVzZUNvbG9yLmEgPCBhbHBoYVRlc3QgKSBkaXNjYXJkO1xuI2VuZGlmIixhbHBoYXRlc3RfcGFyc19mcmFnbWVudDoiI2lmZGVmIFVTRV9BTFBIQVRFU1Rcblx0dW5pZm9ybSBmbG9hdCBhbHBoYVRlc3Q7XG4jZW5kaWYiLGFvbWFwX2ZyYWdtZW50OiIjaWZkZWYgVVNFX0FPTUFQXG5cdGZsb2F0IGFtYmllbnRPY2NsdXNpb24gPSAoIHRleHR1cmUyRCggYW9NYXAsIHZVdjIgKS5yIC0gMS4wICkgKiBhb01hcEludGVuc2l0eSArIDEuMDtcblx0cmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICo9IGFtYmllbnRPY2NsdXNpb247XG5cdCNpZiBkZWZpbmVkKCBVU0VfRU5WTUFQICkgJiYgZGVmaW5lZCggU1RBTkRBUkQgKVxuXHRcdGZsb2F0IGRvdE5WID0gc2F0dXJhdGUoIGRvdCggZ2VvbWV0cnkubm9ybWFsLCBnZW9tZXRyeS52aWV3RGlyICkgKTtcblx0XHRyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdFNwZWN1bGFyICo9IGNvbXB1dGVTcGVjdWxhck9jY2x1c2lvbiggZG90TlYsIGFtYmllbnRPY2NsdXNpb24sIG1hdGVyaWFsLnJvdWdobmVzcyApO1xuXHQjZW5kaWZcbiNlbmRpZiIsYW9tYXBfcGFyc19mcmFnbWVudDoiI2lmZGVmIFVTRV9BT01BUFxuXHR1bmlmb3JtIHNhbXBsZXIyRCBhb01hcDtcblx0dW5pZm9ybSBmbG9hdCBhb01hcEludGVuc2l0eTtcbiNlbmRpZiIsYmVnaW5fdmVydGV4OiJ2ZWMzIHRyYW5zZm9ybWVkID0gdmVjMyggcG9zaXRpb24gKTsiLGJlZ2lubm9ybWFsX3ZlcnRleDoidmVjMyBvYmplY3ROb3JtYWwgPSB2ZWMzKCBub3JtYWwgKTtcbiNpZmRlZiBVU0VfVEFOR0VOVFxuXHR2ZWMzIG9iamVjdFRhbmdlbnQgPSB2ZWMzKCB0YW5nZW50Lnh5eiApO1xuI2VuZGlmIixic2RmczoidmVjMyBCUkRGX0xhbWJlcnQoIGNvbnN0IGluIHZlYzMgZGlmZnVzZUNvbG9yICkge1xuXHRyZXR1cm4gUkVDSVBST0NBTF9QSSAqIGRpZmZ1c2VDb2xvcjtcbn1cbnZlYzMgRl9TY2hsaWNrKCBjb25zdCBpbiB2ZWMzIGYwLCBjb25zdCBpbiBmbG9hdCBmOTAsIGNvbnN0IGluIGZsb2F0IGRvdFZIICkge1xuXHRmbG9hdCBmcmVzbmVsID0gZXhwMiggKCAtIDUuNTU0NzMgKiBkb3RWSCAtIDYuOTgzMTYgKSAqIGRvdFZIICk7XG5cdHJldHVybiBmMCAqICggMS4wIC0gZnJlc25lbCApICsgKCBmOTAgKiBmcmVzbmVsICk7XG59XG5mbG9hdCBWX0dHWF9TbWl0aENvcnJlbGF0ZWQoIGNvbnN0IGluIGZsb2F0IGFscGhhLCBjb25zdCBpbiBmbG9hdCBkb3ROTCwgY29uc3QgaW4gZmxvYXQgZG90TlYgKSB7XG5cdGZsb2F0IGEyID0gcG93MiggYWxwaGEgKTtcblx0ZmxvYXQgZ3YgPSBkb3ROTCAqIHNxcnQoIGEyICsgKCAxLjAgLSBhMiApICogcG93MiggZG90TlYgKSApO1xuXHRmbG9hdCBnbCA9IGRvdE5WICogc3FydCggYTIgKyAoIDEuMCAtIGEyICkgKiBwb3cyKCBkb3ROTCApICk7XG5cdHJldHVybiAwLjUgLyBtYXgoIGd2ICsgZ2wsIEVQU0lMT04gKTtcbn1cbmZsb2F0IERfR0dYKCBjb25zdCBpbiBmbG9hdCBhbHBoYSwgY29uc3QgaW4gZmxvYXQgZG90TkggKSB7XG5cdGZsb2F0IGEyID0gcG93MiggYWxwaGEgKTtcblx0ZmxvYXQgZGVub20gPSBwb3cyKCBkb3ROSCApICogKCBhMiAtIDEuMCApICsgMS4wO1xuXHRyZXR1cm4gUkVDSVBST0NBTF9QSSAqIGEyIC8gcG93MiggZGVub20gKTtcbn1cbnZlYzMgQlJERl9HR1goIGNvbnN0IGluIHZlYzMgbGlnaHREaXIsIGNvbnN0IGluIHZlYzMgdmlld0RpciwgY29uc3QgaW4gdmVjMyBub3JtYWwsIGNvbnN0IGluIHZlYzMgZjAsIGNvbnN0IGluIGZsb2F0IGY5MCwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzICkge1xuXHRmbG9hdCBhbHBoYSA9IHBvdzIoIHJvdWdobmVzcyApO1xuXHR2ZWMzIGhhbGZEaXIgPSBub3JtYWxpemUoIGxpZ2h0RGlyICsgdmlld0RpciApO1xuXHRmbG9hdCBkb3ROTCA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgbGlnaHREaXIgKSApO1xuXHRmbG9hdCBkb3ROViA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgdmlld0RpciApICk7XG5cdGZsb2F0IGRvdE5IID0gc2F0dXJhdGUoIGRvdCggbm9ybWFsLCBoYWxmRGlyICkgKTtcblx0ZmxvYXQgZG90VkggPSBzYXR1cmF0ZSggZG90KCB2aWV3RGlyLCBoYWxmRGlyICkgKTtcblx0dmVjMyBGID0gRl9TY2hsaWNrKCBmMCwgZjkwLCBkb3RWSCApO1xuXHRmbG9hdCBWID0gVl9HR1hfU21pdGhDb3JyZWxhdGVkKCBhbHBoYSwgZG90TkwsIGRvdE5WICk7XG5cdGZsb2F0IEQgPSBEX0dHWCggYWxwaGEsIGRvdE5IICk7XG5cdHJldHVybiBGICogKCBWICogRCApO1xufVxudmVjMiBMVENfVXYoIGNvbnN0IGluIHZlYzMgTiwgY29uc3QgaW4gdmVjMyBWLCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MgKSB7XG5cdGNvbnN0IGZsb2F0IExVVF9TSVpFID0gNjQuMDtcblx0Y29uc3QgZmxvYXQgTFVUX1NDQUxFID0gKCBMVVRfU0laRSAtIDEuMCApIC8gTFVUX1NJWkU7XG5cdGNvbnN0IGZsb2F0IExVVF9CSUFTID0gMC41IC8gTFVUX1NJWkU7XG5cdGZsb2F0IGRvdE5WID0gc2F0dXJhdGUoIGRvdCggTiwgViApICk7XG5cdHZlYzIgdXYgPSB2ZWMyKCByb3VnaG5lc3MsIHNxcnQoIDEuMCAtIGRvdE5WICkgKTtcblx0dXYgPSB1diAqIExVVF9TQ0FMRSArIExVVF9CSUFTO1xuXHRyZXR1cm4gdXY7XG59XG5mbG9hdCBMVENfQ2xpcHBlZFNwaGVyZUZvcm1GYWN0b3IoIGNvbnN0IGluIHZlYzMgZiApIHtcblx0ZmxvYXQgbCA9IGxlbmd0aCggZiApO1xuXHRyZXR1cm4gbWF4KCAoIGwgKiBsICsgZi56ICkgLyAoIGwgKyAxLjAgKSwgMC4wICk7XG59XG52ZWMzIExUQ19FZGdlVmVjdG9yRm9ybUZhY3RvciggY29uc3QgaW4gdmVjMyB2MSwgY29uc3QgaW4gdmVjMyB2MiApIHtcblx0ZmxvYXQgeCA9IGRvdCggdjEsIHYyICk7XG5cdGZsb2F0IHkgPSBhYnMoIHggKTtcblx0ZmxvYXQgYSA9IDAuODU0Mzk4NSArICggMC40OTY1MTU1ICsgMC4wMTQ1MjA2ICogeSApICogeTtcblx0ZmxvYXQgYiA9IDMuNDE3NTk0MCArICggNC4xNjE2NzI0ICsgeSApICogeTtcblx0ZmxvYXQgdiA9IGEgLyBiO1xuXHRmbG9hdCB0aGV0YV9zaW50aGV0YSA9ICggeCA+IDAuMCApID8gdiA6IDAuNSAqIGludmVyc2VzcXJ0KCBtYXgoIDEuMCAtIHggKiB4LCAxZS03ICkgKSAtIHY7XG5cdHJldHVybiBjcm9zcyggdjEsIHYyICkgKiB0aGV0YV9zaW50aGV0YTtcbn1cbnZlYzMgTFRDX0V2YWx1YXRlKCBjb25zdCBpbiB2ZWMzIE4sIGNvbnN0IGluIHZlYzMgViwgY29uc3QgaW4gdmVjMyBQLCBjb25zdCBpbiBtYXQzIG1JbnYsIGNvbnN0IGluIHZlYzMgcmVjdENvb3Jkc1sgNCBdICkge1xuXHR2ZWMzIHYxID0gcmVjdENvb3Jkc1sgMSBdIC0gcmVjdENvb3Jkc1sgMCBdO1xuXHR2ZWMzIHYyID0gcmVjdENvb3Jkc1sgMyBdIC0gcmVjdENvb3Jkc1sgMCBdO1xuXHR2ZWMzIGxpZ2h0Tm9ybWFsID0gY3Jvc3MoIHYxLCB2MiApO1xuXHRpZiggZG90KCBsaWdodE5vcm1hbCwgUCAtIHJlY3RDb29yZHNbIDAgXSApIDwgMC4wICkgcmV0dXJuIHZlYzMoIDAuMCApO1xuXHR2ZWMzIFQxLCBUMjtcblx0VDEgPSBub3JtYWxpemUoIFYgLSBOICogZG90KCBWLCBOICkgKTtcblx0VDIgPSAtIGNyb3NzKCBOLCBUMSApO1xuXHRtYXQzIG1hdCA9IG1JbnYgKiB0cmFuc3Bvc2VNYXQzKCBtYXQzKCBUMSwgVDIsIE4gKSApO1xuXHR2ZWMzIGNvb3Jkc1sgNCBdO1xuXHRjb29yZHNbIDAgXSA9IG1hdCAqICggcmVjdENvb3Jkc1sgMCBdIC0gUCApO1xuXHRjb29yZHNbIDEgXSA9IG1hdCAqICggcmVjdENvb3Jkc1sgMSBdIC0gUCApO1xuXHRjb29yZHNbIDIgXSA9IG1hdCAqICggcmVjdENvb3Jkc1sgMiBdIC0gUCApO1xuXHRjb29yZHNbIDMgXSA9IG1hdCAqICggcmVjdENvb3Jkc1sgMyBdIC0gUCApO1xuXHRjb29yZHNbIDAgXSA9IG5vcm1hbGl6ZSggY29vcmRzWyAwIF0gKTtcblx0Y29vcmRzWyAxIF0gPSBub3JtYWxpemUoIGNvb3Jkc1sgMSBdICk7XG5cdGNvb3Jkc1sgMiBdID0gbm9ybWFsaXplKCBjb29yZHNbIDIgXSApO1xuXHRjb29yZHNbIDMgXSA9IG5vcm1hbGl6ZSggY29vcmRzWyAzIF0gKTtcblx0dmVjMyB2ZWN0b3JGb3JtRmFjdG9yID0gdmVjMyggMC4wICk7XG5cdHZlY3RvckZvcm1GYWN0b3IgKz0gTFRDX0VkZ2VWZWN0b3JGb3JtRmFjdG9yKCBjb29yZHNbIDAgXSwgY29vcmRzWyAxIF0gKTtcblx0dmVjdG9yRm9ybUZhY3RvciArPSBMVENfRWRnZVZlY3RvckZvcm1GYWN0b3IoIGNvb3Jkc1sgMSBdLCBjb29yZHNbIDIgXSApO1xuXHR2ZWN0b3JGb3JtRmFjdG9yICs9IExUQ19FZGdlVmVjdG9yRm9ybUZhY3RvciggY29vcmRzWyAyIF0sIGNvb3Jkc1sgMyBdICk7XG5cdHZlY3RvckZvcm1GYWN0b3IgKz0gTFRDX0VkZ2VWZWN0b3JGb3JtRmFjdG9yKCBjb29yZHNbIDMgXSwgY29vcmRzWyAwIF0gKTtcblx0ZmxvYXQgcmVzdWx0ID0gTFRDX0NsaXBwZWRTcGhlcmVGb3JtRmFjdG9yKCB2ZWN0b3JGb3JtRmFjdG9yICk7XG5cdHJldHVybiB2ZWMzKCByZXN1bHQgKTtcbn1cbmZsb2F0IEdfQmxpbm5QaG9uZ19JbXBsaWNpdCggKSB7XG5cdHJldHVybiAwLjI1O1xufVxuZmxvYXQgRF9CbGlublBob25nKCBjb25zdCBpbiBmbG9hdCBzaGluaW5lc3MsIGNvbnN0IGluIGZsb2F0IGRvdE5IICkge1xuXHRyZXR1cm4gUkVDSVBST0NBTF9QSSAqICggc2hpbmluZXNzICogMC41ICsgMS4wICkgKiBwb3coIGRvdE5ILCBzaGluaW5lc3MgKTtcbn1cbnZlYzMgQlJERl9CbGlublBob25nKCBjb25zdCBpbiB2ZWMzIGxpZ2h0RGlyLCBjb25zdCBpbiB2ZWMzIHZpZXdEaXIsIGNvbnN0IGluIHZlYzMgbm9ybWFsLCBjb25zdCBpbiB2ZWMzIHNwZWN1bGFyQ29sb3IsIGNvbnN0IGluIGZsb2F0IHNoaW5pbmVzcyApIHtcblx0dmVjMyBoYWxmRGlyID0gbm9ybWFsaXplKCBsaWdodERpciArIHZpZXdEaXIgKTtcblx0ZmxvYXQgZG90TkggPSBzYXR1cmF0ZSggZG90KCBub3JtYWwsIGhhbGZEaXIgKSApO1xuXHRmbG9hdCBkb3RWSCA9IHNhdHVyYXRlKCBkb3QoIHZpZXdEaXIsIGhhbGZEaXIgKSApO1xuXHR2ZWMzIEYgPSBGX1NjaGxpY2soIHNwZWN1bGFyQ29sb3IsIDEuMCwgZG90VkggKTtcblx0ZmxvYXQgRyA9IEdfQmxpbm5QaG9uZ19JbXBsaWNpdCggKTtcblx0ZmxvYXQgRCA9IERfQmxpbm5QaG9uZyggc2hpbmluZXNzLCBkb3ROSCApO1xuXHRyZXR1cm4gRiAqICggRyAqIEQgKTtcbn1cbiNpZiBkZWZpbmVkKCBVU0VfU0hFRU4gKVxuZmxvYXQgRF9DaGFybGllKCBmbG9hdCByb3VnaG5lc3MsIGZsb2F0IGRvdE5IICkge1xuXHRmbG9hdCBhbHBoYSA9IHBvdzIoIHJvdWdobmVzcyApO1xuXHRmbG9hdCBpbnZBbHBoYSA9IDEuMCAvIGFscGhhO1xuXHRmbG9hdCBjb3MyaCA9IGRvdE5IICogZG90Tkg7XG5cdGZsb2F0IHNpbjJoID0gbWF4KCAxLjAgLSBjb3MyaCwgMC4wMDc4MTI1ICk7XG5cdHJldHVybiAoIDIuMCArIGludkFscGhhICkgKiBwb3coIHNpbjJoLCBpbnZBbHBoYSAqIDAuNSApIC8gKCAyLjAgKiBQSSApO1xufVxuZmxvYXQgVl9OZXViZWx0KCBmbG9hdCBkb3ROViwgZmxvYXQgZG90TkwgKSB7XG5cdHJldHVybiBzYXR1cmF0ZSggMS4wIC8gKCA0LjAgKiAoIGRvdE5MICsgZG90TlYgLSBkb3ROTCAqIGRvdE5WICkgKSApO1xufVxudmVjMyBCUkRGX1NoZWVuKCBjb25zdCBpbiB2ZWMzIGxpZ2h0RGlyLCBjb25zdCBpbiB2ZWMzIHZpZXdEaXIsIGNvbnN0IGluIHZlYzMgbm9ybWFsLCB2ZWMzIHNoZWVuQ29sb3IsIGNvbnN0IGluIGZsb2F0IHNoZWVuUm91Z2huZXNzICkge1xuXHR2ZWMzIGhhbGZEaXIgPSBub3JtYWxpemUoIGxpZ2h0RGlyICsgdmlld0RpciApO1xuXHRmbG9hdCBkb3ROTCA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgbGlnaHREaXIgKSApO1xuXHRmbG9hdCBkb3ROViA9IHNhdHVyYXRlKCBkb3QoIG5vcm1hbCwgdmlld0RpciApICk7XG5cdGZsb2F0IGRvdE5IID0gc2F0dXJhdGUoIGRvdCggbm9ybWFsLCBoYWxmRGlyICkgKTtcblx0ZmxvYXQgRCA9IERfQ2hhcmxpZSggc2hlZW5Sb3VnaG5lc3MsIGRvdE5IICk7XG5cdGZsb2F0IFYgPSBWX05ldWJlbHQoIGRvdE5WLCBkb3ROTCApO1xuXHRyZXR1cm4gc2hlZW5Db2xvciAqICggRCAqIFYgKTtcbn1cbiNlbmRpZiIsYnVtcG1hcF9wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX0JVTVBNQVBcblx0dW5pZm9ybSBzYW1wbGVyMkQgYnVtcE1hcDtcblx0dW5pZm9ybSBmbG9hdCBidW1wU2NhbGU7XG5cdHZlYzIgZEhkeHlfZndkKCkge1xuXHRcdHZlYzIgZFNUZHggPSBkRmR4KCB2VXYgKTtcblx0XHR2ZWMyIGRTVGR5ID0gZEZkeSggdlV2ICk7XG5cdFx0ZmxvYXQgSGxsID0gYnVtcFNjYWxlICogdGV4dHVyZTJEKCBidW1wTWFwLCB2VXYgKS54O1xuXHRcdGZsb2F0IGRCeCA9IGJ1bXBTY2FsZSAqIHRleHR1cmUyRCggYnVtcE1hcCwgdlV2ICsgZFNUZHggKS54IC0gSGxsO1xuXHRcdGZsb2F0IGRCeSA9IGJ1bXBTY2FsZSAqIHRleHR1cmUyRCggYnVtcE1hcCwgdlV2ICsgZFNUZHkgKS54IC0gSGxsO1xuXHRcdHJldHVybiB2ZWMyKCBkQngsIGRCeSApO1xuXHR9XG5cdHZlYzMgcGVydHVyYk5vcm1hbEFyYiggdmVjMyBzdXJmX3BvcywgdmVjMyBzdXJmX25vcm0sIHZlYzIgZEhkeHksIGZsb2F0IGZhY2VEaXJlY3Rpb24gKSB7XG5cdFx0dmVjMyB2U2lnbWFYID0gdmVjMyggZEZkeCggc3VyZl9wb3MueCApLCBkRmR4KCBzdXJmX3Bvcy55ICksIGRGZHgoIHN1cmZfcG9zLnogKSApO1xuXHRcdHZlYzMgdlNpZ21hWSA9IHZlYzMoIGRGZHkoIHN1cmZfcG9zLnggKSwgZEZkeSggc3VyZl9wb3MueSApLCBkRmR5KCBzdXJmX3Bvcy56ICkgKTtcblx0XHR2ZWMzIHZOID0gc3VyZl9ub3JtO1xuXHRcdHZlYzMgUjEgPSBjcm9zcyggdlNpZ21hWSwgdk4gKTtcblx0XHR2ZWMzIFIyID0gY3Jvc3MoIHZOLCB2U2lnbWFYICk7XG5cdFx0ZmxvYXQgZkRldCA9IGRvdCggdlNpZ21hWCwgUjEgKSAqIGZhY2VEaXJlY3Rpb247XG5cdFx0dmVjMyB2R3JhZCA9IHNpZ24oIGZEZXQgKSAqICggZEhkeHkueCAqIFIxICsgZEhkeHkueSAqIFIyICk7XG5cdFx0cmV0dXJuIG5vcm1hbGl6ZSggYWJzKCBmRGV0ICkgKiBzdXJmX25vcm0gLSB2R3JhZCApO1xuXHR9XG4jZW5kaWYiLGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudDoiI2lmIE5VTV9DTElQUElOR19QTEFORVMgPiAwXG5cdHZlYzQgcGxhbmU7XG5cdCNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnRcblx0Zm9yICggaW50IGkgPSAwOyBpIDwgVU5JT05fQ0xJUFBJTkdfUExBTkVTOyBpICsrICkge1xuXHRcdHBsYW5lID0gY2xpcHBpbmdQbGFuZXNbIGkgXTtcblx0XHRpZiAoIGRvdCggdkNsaXBQb3NpdGlvbiwgcGxhbmUueHl6ICkgPiBwbGFuZS53ICkgZGlzY2FyZDtcblx0fVxuXHQjcHJhZ21hIHVucm9sbF9sb29wX2VuZFxuXHQjaWYgVU5JT05fQ0xJUFBJTkdfUExBTkVTIDwgTlVNX0NMSVBQSU5HX1BMQU5FU1xuXHRcdGJvb2wgY2xpcHBlZCA9IHRydWU7XG5cdFx0I3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydFxuXHRcdGZvciAoIGludCBpID0gVU5JT05fQ0xJUFBJTkdfUExBTkVTOyBpIDwgTlVNX0NMSVBQSU5HX1BMQU5FUzsgaSArKyApIHtcblx0XHRcdHBsYW5lID0gY2xpcHBpbmdQbGFuZXNbIGkgXTtcblx0XHRcdGNsaXBwZWQgPSAoIGRvdCggdkNsaXBQb3NpdGlvbiwgcGxhbmUueHl6ICkgPiBwbGFuZS53ICkgJiYgY2xpcHBlZDtcblx0XHR9XG5cdFx0I3ByYWdtYSB1bnJvbGxfbG9vcF9lbmRcblx0XHRpZiAoIGNsaXBwZWQgKSBkaXNjYXJkO1xuXHQjZW5kaWZcbiNlbmRpZiIsY2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ6IiNpZiBOVU1fQ0xJUFBJTkdfUExBTkVTID4gMFxuXHR2YXJ5aW5nIHZlYzMgdkNsaXBQb3NpdGlvbjtcblx0dW5pZm9ybSB2ZWM0IGNsaXBwaW5nUGxhbmVzWyBOVU1fQ0xJUFBJTkdfUExBTkVTIF07XG4jZW5kaWYiLGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleDoiI2lmIE5VTV9DTElQUElOR19QTEFORVMgPiAwXG5cdHZhcnlpbmcgdmVjMyB2Q2xpcFBvc2l0aW9uO1xuI2VuZGlmIixjbGlwcGluZ19wbGFuZXNfdmVydGV4OiIjaWYgTlVNX0NMSVBQSU5HX1BMQU5FUyA+IDBcblx0dkNsaXBQb3NpdGlvbiA9IC0gbXZQb3NpdGlvbi54eXo7XG4jZW5kaWYiLGNvbG9yX2ZyYWdtZW50OiIjaWYgZGVmaW5lZCggVVNFX0NPTE9SX0FMUEhBIClcblx0ZGlmZnVzZUNvbG9yICo9IHZDb2xvcjtcbiNlbGlmIGRlZmluZWQoIFVTRV9DT0xPUiApXG5cdGRpZmZ1c2VDb2xvci5yZ2IgKj0gdkNvbG9yO1xuI2VuZGlmIixjb2xvcl9wYXJzX2ZyYWdtZW50OiIjaWYgZGVmaW5lZCggVVNFX0NPTE9SX0FMUEhBIClcblx0dmFyeWluZyB2ZWM0IHZDb2xvcjtcbiNlbGlmIGRlZmluZWQoIFVTRV9DT0xPUiApXG5cdHZhcnlpbmcgdmVjMyB2Q29sb3I7XG4jZW5kaWYiLGNvbG9yX3BhcnNfdmVydGV4OiIjaWYgZGVmaW5lZCggVVNFX0NPTE9SX0FMUEhBIClcblx0dmFyeWluZyB2ZWM0IHZDb2xvcjtcbiNlbGlmIGRlZmluZWQoIFVTRV9DT0xPUiApIHx8IGRlZmluZWQoIFVTRV9JTlNUQU5DSU5HX0NPTE9SIClcblx0dmFyeWluZyB2ZWMzIHZDb2xvcjtcbiNlbmRpZiIsY29sb3JfdmVydGV4OiIjaWYgZGVmaW5lZCggVVNFX0NPTE9SX0FMUEhBIClcblx0dkNvbG9yID0gdmVjNCggMS4wICk7XG4jZWxpZiBkZWZpbmVkKCBVU0VfQ09MT1IgKSB8fCBkZWZpbmVkKCBVU0VfSU5TVEFOQ0lOR19DT0xPUiApXG5cdHZDb2xvciA9IHZlYzMoIDEuMCApO1xuI2VuZGlmXG4jaWZkZWYgVVNFX0NPTE9SXG5cdHZDb2xvciAqPSBjb2xvcjtcbiNlbmRpZlxuI2lmZGVmIFVTRV9JTlNUQU5DSU5HX0NPTE9SXG5cdHZDb2xvci54eXogKj0gaW5zdGFuY2VDb2xvci54eXo7XG4jZW5kaWYiLGNvbW1vbjoiI2RlZmluZSBQSSAzLjE0MTU5MjY1MzU4OTc5M1xuI2RlZmluZSBQSTIgNi4yODMxODUzMDcxNzk1ODZcbiNkZWZpbmUgUElfSEFMRiAxLjU3MDc5NjMyNjc5NDg5NjZcbiNkZWZpbmUgUkVDSVBST0NBTF9QSSAwLjMxODMwOTg4NjE4Mzc5MDdcbiNkZWZpbmUgUkVDSVBST0NBTF9QSTIgMC4xNTkxNTQ5NDMwOTE4OTUzNVxuI2RlZmluZSBFUFNJTE9OIDFlLTZcbiNpZm5kZWYgc2F0dXJhdGVcbiNkZWZpbmUgc2F0dXJhdGUoIGEgKSBjbGFtcCggYSwgMC4wLCAxLjAgKVxuI2VuZGlmXG4jZGVmaW5lIHdoaXRlQ29tcGxlbWVudCggYSApICggMS4wIC0gc2F0dXJhdGUoIGEgKSApXG5mbG9hdCBwb3cyKCBjb25zdCBpbiBmbG9hdCB4ICkgeyByZXR1cm4geCp4OyB9XG5mbG9hdCBwb3czKCBjb25zdCBpbiBmbG9hdCB4ICkgeyByZXR1cm4geCp4Kng7IH1cbmZsb2F0IHBvdzQoIGNvbnN0IGluIGZsb2F0IHggKSB7IGZsb2F0IHgyID0geCp4OyByZXR1cm4geDIqeDI7IH1cbmZsb2F0IG1heDMoIGNvbnN0IGluIHZlYzMgdiApIHsgcmV0dXJuIG1heCggbWF4KCB2LngsIHYueSApLCB2LnogKTsgfVxuZmxvYXQgYXZlcmFnZSggY29uc3QgaW4gdmVjMyBjb2xvciApIHsgcmV0dXJuIGRvdCggY29sb3IsIHZlYzMoIDAuMzMzMyApICk7IH1cbmhpZ2hwIGZsb2F0IHJhbmQoIGNvbnN0IGluIHZlYzIgdXYgKSB7XG5cdGNvbnN0IGhpZ2hwIGZsb2F0IGEgPSAxMi45ODk4LCBiID0gNzguMjMzLCBjID0gNDM3NTguNTQ1Mztcblx0aGlnaHAgZmxvYXQgZHQgPSBkb3QoIHV2Lnh5LCB2ZWMyKCBhLGIgKSApLCBzbiA9IG1vZCggZHQsIFBJICk7XG5cdHJldHVybiBmcmFjdCggc2luKCBzbiApICogYyApO1xufVxuI2lmZGVmIEhJR0hfUFJFQ0lTSU9OXG5cdGZsb2F0IHByZWNpc2lvblNhZmVMZW5ndGgoIHZlYzMgdiApIHsgcmV0dXJuIGxlbmd0aCggdiApOyB9XG4jZWxzZVxuXHRmbG9hdCBwcmVjaXNpb25TYWZlTGVuZ3RoKCB2ZWMzIHYgKSB7XG5cdFx0ZmxvYXQgbWF4Q29tcG9uZW50ID0gbWF4MyggYWJzKCB2ICkgKTtcblx0XHRyZXR1cm4gbGVuZ3RoKCB2IC8gbWF4Q29tcG9uZW50ICkgKiBtYXhDb21wb25lbnQ7XG5cdH1cbiNlbmRpZlxuc3RydWN0IEluY2lkZW50TGlnaHQge1xuXHR2ZWMzIGNvbG9yO1xuXHR2ZWMzIGRpcmVjdGlvbjtcblx0Ym9vbCB2aXNpYmxlO1xufTtcbnN0cnVjdCBSZWZsZWN0ZWRMaWdodCB7XG5cdHZlYzMgZGlyZWN0RGlmZnVzZTtcblx0dmVjMyBkaXJlY3RTcGVjdWxhcjtcblx0dmVjMyBpbmRpcmVjdERpZmZ1c2U7XG5cdHZlYzMgaW5kaXJlY3RTcGVjdWxhcjtcbn07XG5zdHJ1Y3QgR2VvbWV0cmljQ29udGV4dCB7XG5cdHZlYzMgcG9zaXRpb247XG5cdHZlYzMgbm9ybWFsO1xuXHR2ZWMzIHZpZXdEaXI7XG4jaWZkZWYgVVNFX0NMRUFSQ09BVFxuXHR2ZWMzIGNsZWFyY29hdE5vcm1hbDtcbiNlbmRpZlxufTtcbnZlYzMgdHJhbnNmb3JtRGlyZWN0aW9uKCBpbiB2ZWMzIGRpciwgaW4gbWF0NCBtYXRyaXggKSB7XG5cdHJldHVybiBub3JtYWxpemUoICggbWF0cml4ICogdmVjNCggZGlyLCAwLjAgKSApLnh5eiApO1xufVxudmVjMyBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCBpbiB2ZWMzIGRpciwgaW4gbWF0NCBtYXRyaXggKSB7XG5cdHJldHVybiBub3JtYWxpemUoICggdmVjNCggZGlyLCAwLjAgKSAqIG1hdHJpeCApLnh5eiApO1xufVxubWF0MyB0cmFuc3Bvc2VNYXQzKCBjb25zdCBpbiBtYXQzIG0gKSB7XG5cdG1hdDMgdG1wO1xuXHR0bXBbIDAgXSA9IHZlYzMoIG1bIDAgXS54LCBtWyAxIF0ueCwgbVsgMiBdLnggKTtcblx0dG1wWyAxIF0gPSB2ZWMzKCBtWyAwIF0ueSwgbVsgMSBdLnksIG1bIDIgXS55ICk7XG5cdHRtcFsgMiBdID0gdmVjMyggbVsgMCBdLnosIG1bIDEgXS56LCBtWyAyIF0ueiApO1xuXHRyZXR1cm4gdG1wO1xufVxuZmxvYXQgbGluZWFyVG9SZWxhdGl2ZUx1bWluYW5jZSggY29uc3QgaW4gdmVjMyBjb2xvciApIHtcblx0dmVjMyB3ZWlnaHRzID0gdmVjMyggMC4yMTI2LCAwLjcxNTIsIDAuMDcyMiApO1xuXHRyZXR1cm4gZG90KCB3ZWlnaHRzLCBjb2xvci5yZ2IgKTtcbn1cbmJvb2wgaXNQZXJzcGVjdGl2ZU1hdHJpeCggbWF0NCBtICkge1xuXHRyZXR1cm4gbVsgMiBdWyAzIF0gPT0gLSAxLjA7XG59XG52ZWMyIGVxdWlyZWN0VXYoIGluIHZlYzMgZGlyICkge1xuXHRmbG9hdCB1ID0gYXRhbiggZGlyLnosIGRpci54ICkgKiBSRUNJUFJPQ0FMX1BJMiArIDAuNTtcblx0ZmxvYXQgdiA9IGFzaW4oIGNsYW1wKCBkaXIueSwgLSAxLjAsIDEuMCApICkgKiBSRUNJUFJPQ0FMX1BJICsgMC41O1xuXHRyZXR1cm4gdmVjMiggdSwgdiApO1xufSIsY3ViZV91dl9yZWZsZWN0aW9uX2ZyYWdtZW50OiIjaWZkZWYgRU5WTUFQX1RZUEVfQ1VCRV9VVlxuXHQjZGVmaW5lIGN1YmVVVl9tYXhNaXBMZXZlbCA4LjBcblx0I2RlZmluZSBjdWJlVVZfbWluTWlwTGV2ZWwgNC4wXG5cdCNkZWZpbmUgY3ViZVVWX21heFRpbGVTaXplIDI1Ni4wXG5cdCNkZWZpbmUgY3ViZVVWX21pblRpbGVTaXplIDE2LjBcblx0ZmxvYXQgZ2V0RmFjZSggdmVjMyBkaXJlY3Rpb24gKSB7XG5cdFx0dmVjMyBhYnNEaXJlY3Rpb24gPSBhYnMoIGRpcmVjdGlvbiApO1xuXHRcdGZsb2F0IGZhY2UgPSAtIDEuMDtcblx0XHRpZiAoIGFic0RpcmVjdGlvbi54ID4gYWJzRGlyZWN0aW9uLnogKSB7XG5cdFx0XHRpZiAoIGFic0RpcmVjdGlvbi54ID4gYWJzRGlyZWN0aW9uLnkgKVxuXHRcdFx0XHRmYWNlID0gZGlyZWN0aW9uLnggPiAwLjAgPyAwLjAgOiAzLjA7XG5cdFx0XHRlbHNlXG5cdFx0XHRcdGZhY2UgPSBkaXJlY3Rpb24ueSA+IDAuMCA/IDEuMCA6IDQuMDtcblx0XHR9IGVsc2Uge1xuXHRcdFx0aWYgKCBhYnNEaXJlY3Rpb24ueiA+IGFic0RpcmVjdGlvbi55IClcblx0XHRcdFx0ZmFjZSA9IGRpcmVjdGlvbi56ID4gMC4wID8gMi4wIDogNS4wO1xuXHRcdFx0ZWxzZVxuXHRcdFx0XHRmYWNlID0gZGlyZWN0aW9uLnkgPiAwLjAgPyAxLjAgOiA0LjA7XG5cdFx0fVxuXHRcdHJldHVybiBmYWNlO1xuXHR9XG5cdHZlYzIgZ2V0VVYoIHZlYzMgZGlyZWN0aW9uLCBmbG9hdCBmYWNlICkge1xuXHRcdHZlYzIgdXY7XG5cdFx0aWYgKCBmYWNlID09IDAuMCApIHtcblx0XHRcdHV2ID0gdmVjMiggZGlyZWN0aW9uLnosIGRpcmVjdGlvbi55ICkgLyBhYnMoIGRpcmVjdGlvbi54ICk7XG5cdFx0fSBlbHNlIGlmICggZmFjZSA9PSAxLjAgKSB7XG5cdFx0XHR1diA9IHZlYzIoIC0gZGlyZWN0aW9uLngsIC0gZGlyZWN0aW9uLnogKSAvIGFicyggZGlyZWN0aW9uLnkgKTtcblx0XHR9IGVsc2UgaWYgKCBmYWNlID09IDIuMCApIHtcblx0XHRcdHV2ID0gdmVjMiggLSBkaXJlY3Rpb24ueCwgZGlyZWN0aW9uLnkgKSAvIGFicyggZGlyZWN0aW9uLnogKTtcblx0XHR9IGVsc2UgaWYgKCBmYWNlID09IDMuMCApIHtcblx0XHRcdHV2ID0gdmVjMiggLSBkaXJlY3Rpb24ueiwgZGlyZWN0aW9uLnkgKSAvIGFicyggZGlyZWN0aW9uLnggKTtcblx0XHR9IGVsc2UgaWYgKCBmYWNlID09IDQuMCApIHtcblx0XHRcdHV2ID0gdmVjMiggLSBkaXJlY3Rpb24ueCwgZGlyZWN0aW9uLnogKSAvIGFicyggZGlyZWN0aW9uLnkgKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0dXYgPSB2ZWMyKCBkaXJlY3Rpb24ueCwgZGlyZWN0aW9uLnkgKSAvIGFicyggZGlyZWN0aW9uLnogKTtcblx0XHR9XG5cdFx0cmV0dXJuIDAuNSAqICggdXYgKyAxLjAgKTtcblx0fVxuXHR2ZWMzIGJpbGluZWFyQ3ViZVVWKCBzYW1wbGVyMkQgZW52TWFwLCB2ZWMzIGRpcmVjdGlvbiwgZmxvYXQgbWlwSW50ICkge1xuXHRcdGZsb2F0IGZhY2UgPSBnZXRGYWNlKCBkaXJlY3Rpb24gKTtcblx0XHRmbG9hdCBmaWx0ZXJJbnQgPSBtYXgoIGN1YmVVVl9taW5NaXBMZXZlbCAtIG1pcEludCwgMC4wICk7XG5cdFx0bWlwSW50ID0gbWF4KCBtaXBJbnQsIGN1YmVVVl9taW5NaXBMZXZlbCApO1xuXHRcdGZsb2F0IGZhY2VTaXplID0gZXhwMiggbWlwSW50ICk7XG5cdFx0ZmxvYXQgdGV4ZWxTaXplID0gMS4wIC8gKCAzLjAgKiBjdWJlVVZfbWF4VGlsZVNpemUgKTtcblx0XHR2ZWMyIHV2ID0gZ2V0VVYoIGRpcmVjdGlvbiwgZmFjZSApICogKCBmYWNlU2l6ZSAtIDEuMCApICsgMC41O1xuXHRcdGlmICggZmFjZSA+IDIuMCApIHtcblx0XHRcdHV2LnkgKz0gZmFjZVNpemU7XG5cdFx0XHRmYWNlIC09IDMuMDtcblx0XHR9XG5cdFx0dXYueCArPSBmYWNlICogZmFjZVNpemU7XG5cdFx0aWYgKCBtaXBJbnQgPCBjdWJlVVZfbWF4TWlwTGV2ZWwgKSB7XG5cdFx0XHR1di55ICs9IDIuMCAqIGN1YmVVVl9tYXhUaWxlU2l6ZTtcblx0XHR9XG5cdFx0dXYueSArPSBmaWx0ZXJJbnQgKiAyLjAgKiBjdWJlVVZfbWluVGlsZVNpemU7XG5cdFx0dXYueCArPSAzLjAgKiBtYXgoIDAuMCwgY3ViZVVWX21heFRpbGVTaXplIC0gMi4wICogZmFjZVNpemUgKTtcblx0XHR1diAqPSB0ZXhlbFNpemU7XG5cdFx0cmV0dXJuIHRleHR1cmUyRCggZW52TWFwLCB1diApLnJnYjtcblx0fVxuXHQjZGVmaW5lIHIwIDEuMFxuXHQjZGVmaW5lIHYwIDAuMzM5XG5cdCNkZWZpbmUgbTAgLSAyLjBcblx0I2RlZmluZSByMSAwLjhcblx0I2RlZmluZSB2MSAwLjI3NlxuXHQjZGVmaW5lIG0xIC0gMS4wXG5cdCNkZWZpbmUgcjQgMC40XG5cdCNkZWZpbmUgdjQgMC4wNDZcblx0I2RlZmluZSBtNCAyLjBcblx0I2RlZmluZSByNSAwLjMwNVxuXHQjZGVmaW5lIHY1IDAuMDE2XG5cdCNkZWZpbmUgbTUgMy4wXG5cdCNkZWZpbmUgcjYgMC4yMVxuXHQjZGVmaW5lIHY2IDAuMDAzOFxuXHQjZGVmaW5lIG02IDQuMFxuXHRmbG9hdCByb3VnaG5lc3NUb01pcCggZmxvYXQgcm91Z2huZXNzICkge1xuXHRcdGZsb2F0IG1pcCA9IDAuMDtcblx0XHRpZiAoIHJvdWdobmVzcyA+PSByMSApIHtcblx0XHRcdG1pcCA9ICggcjAgLSByb3VnaG5lc3MgKSAqICggbTEgLSBtMCApIC8gKCByMCAtIHIxICkgKyBtMDtcblx0XHR9IGVsc2UgaWYgKCByb3VnaG5lc3MgPj0gcjQgKSB7XG5cdFx0XHRtaXAgPSAoIHIxIC0gcm91Z2huZXNzICkgKiAoIG00IC0gbTEgKSAvICggcjEgLSByNCApICsgbTE7XG5cdFx0fSBlbHNlIGlmICggcm91Z2huZXNzID49IHI1ICkge1xuXHRcdFx0bWlwID0gKCByNCAtIHJvdWdobmVzcyApICogKCBtNSAtIG00ICkgLyAoIHI0IC0gcjUgKSArIG00O1xuXHRcdH0gZWxzZSBpZiAoIHJvdWdobmVzcyA+PSByNiApIHtcblx0XHRcdG1pcCA9ICggcjUgLSByb3VnaG5lc3MgKSAqICggbTYgLSBtNSApIC8gKCByNSAtIHI2ICkgKyBtNTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0bWlwID0gLSAyLjAgKiBsb2cyKCAxLjE2ICogcm91Z2huZXNzICk7XHRcdH1cblx0XHRyZXR1cm4gbWlwO1xuXHR9XG5cdHZlYzQgdGV4dHVyZUN1YmVVViggc2FtcGxlcjJEIGVudk1hcCwgdmVjMyBzYW1wbGVEaXIsIGZsb2F0IHJvdWdobmVzcyApIHtcblx0XHRmbG9hdCBtaXAgPSBjbGFtcCggcm91Z2huZXNzVG9NaXAoIHJvdWdobmVzcyApLCBtMCwgY3ViZVVWX21heE1pcExldmVsICk7XG5cdFx0ZmxvYXQgbWlwRiA9IGZyYWN0KCBtaXAgKTtcblx0XHRmbG9hdCBtaXBJbnQgPSBmbG9vciggbWlwICk7XG5cdFx0dmVjMyBjb2xvcjAgPSBiaWxpbmVhckN1YmVVViggZW52TWFwLCBzYW1wbGVEaXIsIG1pcEludCApO1xuXHRcdGlmICggbWlwRiA9PSAwLjAgKSB7XG5cdFx0XHRyZXR1cm4gdmVjNCggY29sb3IwLCAxLjAgKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0dmVjMyBjb2xvcjEgPSBiaWxpbmVhckN1YmVVViggZW52TWFwLCBzYW1wbGVEaXIsIG1pcEludCArIDEuMCApO1xuXHRcdFx0cmV0dXJuIHZlYzQoIG1peCggY29sb3IwLCBjb2xvcjEsIG1pcEYgKSwgMS4wICk7XG5cdFx0fVxuXHR9XG4jZW5kaWYiLGRlZmF1bHRub3JtYWxfdmVydGV4OiJ2ZWMzIHRyYW5zZm9ybWVkTm9ybWFsID0gb2JqZWN0Tm9ybWFsO1xuI2lmZGVmIFVTRV9JTlNUQU5DSU5HXG5cdG1hdDMgbSA9IG1hdDMoIGluc3RhbmNlTWF0cml4ICk7XG5cdHRyYW5zZm9ybWVkTm9ybWFsIC89IHZlYzMoIGRvdCggbVsgMCBdLCBtWyAwIF0gKSwgZG90KCBtWyAxIF0sIG1bIDEgXSApLCBkb3QoIG1bIDIgXSwgbVsgMiBdICkgKTtcblx0dHJhbnNmb3JtZWROb3JtYWwgPSBtICogdHJhbnNmb3JtZWROb3JtYWw7XG4jZW5kaWZcbnRyYW5zZm9ybWVkTm9ybWFsID0gbm9ybWFsTWF0cml4ICogdHJhbnNmb3JtZWROb3JtYWw7XG4jaWZkZWYgRkxJUF9TSURFRFxuXHR0cmFuc2Zvcm1lZE5vcm1hbCA9IC0gdHJhbnNmb3JtZWROb3JtYWw7XG4jZW5kaWZcbiNpZmRlZiBVU0VfVEFOR0VOVFxuXHR2ZWMzIHRyYW5zZm9ybWVkVGFuZ2VudCA9ICggbW9kZWxWaWV3TWF0cml4ICogdmVjNCggb2JqZWN0VGFuZ2VudCwgMC4wICkgKS54eXo7XG5cdCNpZmRlZiBGTElQX1NJREVEXG5cdFx0dHJhbnNmb3JtZWRUYW5nZW50ID0gLSB0cmFuc2Zvcm1lZFRhbmdlbnQ7XG5cdCNlbmRpZlxuI2VuZGlmIixkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg6IiNpZmRlZiBVU0VfRElTUExBQ0VNRU5UTUFQXG5cdHVuaWZvcm0gc2FtcGxlcjJEIGRpc3BsYWNlbWVudE1hcDtcblx0dW5pZm9ybSBmbG9hdCBkaXNwbGFjZW1lbnRTY2FsZTtcblx0dW5pZm9ybSBmbG9hdCBkaXNwbGFjZW1lbnRCaWFzO1xuI2VuZGlmIixkaXNwbGFjZW1lbnRtYXBfdmVydGV4OiIjaWZkZWYgVVNFX0RJU1BMQUNFTUVOVE1BUFxuXHR0cmFuc2Zvcm1lZCArPSBub3JtYWxpemUoIG9iamVjdE5vcm1hbCApICogKCB0ZXh0dXJlMkQoIGRpc3BsYWNlbWVudE1hcCwgdlV2ICkueCAqIGRpc3BsYWNlbWVudFNjYWxlICsgZGlzcGxhY2VtZW50QmlhcyApO1xuI2VuZGlmIixlbWlzc2l2ZW1hcF9mcmFnbWVudDoiI2lmZGVmIFVTRV9FTUlTU0lWRU1BUFxuXHR2ZWM0IGVtaXNzaXZlQ29sb3IgPSB0ZXh0dXJlMkQoIGVtaXNzaXZlTWFwLCB2VXYgKTtcblx0dG90YWxFbWlzc2l2ZVJhZGlhbmNlICo9IGVtaXNzaXZlQ29sb3IucmdiO1xuI2VuZGlmIixlbWlzc2l2ZW1hcF9wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX0VNSVNTSVZFTUFQXG5cdHVuaWZvcm0gc2FtcGxlcjJEIGVtaXNzaXZlTWFwO1xuI2VuZGlmIixlbmNvZGluZ3NfZnJhZ21lbnQ6ImdsX0ZyYWdDb2xvciA9IGxpbmVhclRvT3V0cHV0VGV4ZWwoIGdsX0ZyYWdDb2xvciApOyIsZW5jb2RpbmdzX3BhcnNfZnJhZ21lbnQ6InZlYzQgTGluZWFyVG9MaW5lYXIoIGluIHZlYzQgdmFsdWUgKSB7XG5cdHJldHVybiB2YWx1ZTtcbn1cbnZlYzQgTGluZWFyVG9zUkdCKCBpbiB2ZWM0IHZhbHVlICkge1xuXHRyZXR1cm4gdmVjNCggbWl4KCBwb3coIHZhbHVlLnJnYiwgdmVjMyggMC40MTY2NiApICkgKiAxLjA1NSAtIHZlYzMoIDAuMDU1ICksIHZhbHVlLnJnYiAqIDEyLjkyLCB2ZWMzKCBsZXNzVGhhbkVxdWFsKCB2YWx1ZS5yZ2IsIHZlYzMoIDAuMDAzMTMwOCApICkgKSApLCB2YWx1ZS5hICk7XG59IixlbnZtYXBfZnJhZ21lbnQ6IiNpZmRlZiBVU0VfRU5WTUFQXG5cdCNpZmRlZiBFTlZfV09STERQT1Ncblx0XHR2ZWMzIGNhbWVyYVRvRnJhZztcblx0XHRpZiAoIGlzT3J0aG9ncmFwaGljICkge1xuXHRcdFx0Y2FtZXJhVG9GcmFnID0gbm9ybWFsaXplKCB2ZWMzKCAtIHZpZXdNYXRyaXhbIDAgXVsgMiBdLCAtIHZpZXdNYXRyaXhbIDEgXVsgMiBdLCAtIHZpZXdNYXRyaXhbIDIgXVsgMiBdICkgKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0Y2FtZXJhVG9GcmFnID0gbm9ybWFsaXplKCB2V29ybGRQb3NpdGlvbiAtIGNhbWVyYVBvc2l0aW9uICk7XG5cdFx0fVxuXHRcdHZlYzMgd29ybGROb3JtYWwgPSBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCBub3JtYWwsIHZpZXdNYXRyaXggKTtcblx0XHQjaWZkZWYgRU5WTUFQX01PREVfUkVGTEVDVElPTlxuXHRcdFx0dmVjMyByZWZsZWN0VmVjID0gcmVmbGVjdCggY2FtZXJhVG9GcmFnLCB3b3JsZE5vcm1hbCApO1xuXHRcdCNlbHNlXG5cdFx0XHR2ZWMzIHJlZmxlY3RWZWMgPSByZWZyYWN0KCBjYW1lcmFUb0ZyYWcsIHdvcmxkTm9ybWFsLCByZWZyYWN0aW9uUmF0aW8gKTtcblx0XHQjZW5kaWZcblx0I2Vsc2Vcblx0XHR2ZWMzIHJlZmxlY3RWZWMgPSB2UmVmbGVjdDtcblx0I2VuZGlmXG5cdCNpZmRlZiBFTlZNQVBfVFlQRV9DVUJFXG5cdFx0dmVjNCBlbnZDb2xvciA9IHRleHR1cmVDdWJlKCBlbnZNYXAsIHZlYzMoIGZsaXBFbnZNYXAgKiByZWZsZWN0VmVjLngsIHJlZmxlY3RWZWMueXogKSApO1xuXHQjZWxpZiBkZWZpbmVkKCBFTlZNQVBfVFlQRV9DVUJFX1VWIClcblx0XHR2ZWM0IGVudkNvbG9yID0gdGV4dHVyZUN1YmVVViggZW52TWFwLCByZWZsZWN0VmVjLCAwLjAgKTtcblx0I2Vsc2Vcblx0XHR2ZWM0IGVudkNvbG9yID0gdmVjNCggMC4wICk7XG5cdCNlbmRpZlxuXHQjaWZkZWYgRU5WTUFQX0JMRU5ESU5HX01VTFRJUExZXG5cdFx0b3V0Z29pbmdMaWdodCA9IG1peCggb3V0Z29pbmdMaWdodCwgb3V0Z29pbmdMaWdodCAqIGVudkNvbG9yLnh5eiwgc3BlY3VsYXJTdHJlbmd0aCAqIHJlZmxlY3Rpdml0eSApO1xuXHQjZWxpZiBkZWZpbmVkKCBFTlZNQVBfQkxFTkRJTkdfTUlYIClcblx0XHRvdXRnb2luZ0xpZ2h0ID0gbWl4KCBvdXRnb2luZ0xpZ2h0LCBlbnZDb2xvci54eXosIHNwZWN1bGFyU3RyZW5ndGggKiByZWZsZWN0aXZpdHkgKTtcblx0I2VsaWYgZGVmaW5lZCggRU5WTUFQX0JMRU5ESU5HX0FERCApXG5cdFx0b3V0Z29pbmdMaWdodCArPSBlbnZDb2xvci54eXogKiBzcGVjdWxhclN0cmVuZ3RoICogcmVmbGVjdGl2aXR5O1xuXHQjZW5kaWZcbiNlbmRpZiIsZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX0VOVk1BUFxuXHR1bmlmb3JtIGZsb2F0IGVudk1hcEludGVuc2l0eTtcblx0dW5pZm9ybSBmbG9hdCBmbGlwRW52TWFwO1xuXHQjaWZkZWYgRU5WTUFQX1RZUEVfQ1VCRVxuXHRcdHVuaWZvcm0gc2FtcGxlckN1YmUgZW52TWFwO1xuXHQjZWxzZVxuXHRcdHVuaWZvcm0gc2FtcGxlcjJEIGVudk1hcDtcblx0I2VuZGlmXG5cdFxuI2VuZGlmIixlbnZtYXBfcGFyc19mcmFnbWVudDoiI2lmZGVmIFVTRV9FTlZNQVBcblx0dW5pZm9ybSBmbG9hdCByZWZsZWN0aXZpdHk7XG5cdCNpZiBkZWZpbmVkKCBVU0VfQlVNUE1BUCApIHx8IGRlZmluZWQoIFVTRV9OT1JNQUxNQVAgKSB8fCBkZWZpbmVkKCBQSE9ORyApXG5cdFx0I2RlZmluZSBFTlZfV09STERQT1Ncblx0I2VuZGlmXG5cdCNpZmRlZiBFTlZfV09STERQT1Ncblx0XHR2YXJ5aW5nIHZlYzMgdldvcmxkUG9zaXRpb247XG5cdFx0dW5pZm9ybSBmbG9hdCByZWZyYWN0aW9uUmF0aW87XG5cdCNlbHNlXG5cdFx0dmFyeWluZyB2ZWMzIHZSZWZsZWN0O1xuXHQjZW5kaWZcbiNlbmRpZiIsZW52bWFwX3BhcnNfdmVydGV4OiIjaWZkZWYgVVNFX0VOVk1BUFxuXHQjaWYgZGVmaW5lZCggVVNFX0JVTVBNQVAgKSB8fCBkZWZpbmVkKCBVU0VfTk9STUFMTUFQICkgfHxkZWZpbmVkKCBQSE9ORyApXG5cdFx0I2RlZmluZSBFTlZfV09STERQT1Ncblx0I2VuZGlmXG5cdCNpZmRlZiBFTlZfV09STERQT1Ncblx0XHRcblx0XHR2YXJ5aW5nIHZlYzMgdldvcmxkUG9zaXRpb247XG5cdCNlbHNlXG5cdFx0dmFyeWluZyB2ZWMzIHZSZWZsZWN0O1xuXHRcdHVuaWZvcm0gZmxvYXQgcmVmcmFjdGlvblJhdGlvO1xuXHQjZW5kaWZcbiNlbmRpZiIsZW52bWFwX3BoeXNpY2FsX3BhcnNfZnJhZ21lbnQ6IiNpZiBkZWZpbmVkKCBVU0VfRU5WTUFQIClcblx0I2lmZGVmIEVOVk1BUF9NT0RFX1JFRlJBQ1RJT05cblx0XHR1bmlmb3JtIGZsb2F0IHJlZnJhY3Rpb25SYXRpbztcblx0I2VuZGlmXG5cdHZlYzMgZ2V0SUJMSXJyYWRpYW5jZSggY29uc3QgaW4gdmVjMyBub3JtYWwgKSB7XG5cdFx0I2lmIGRlZmluZWQoIEVOVk1BUF9UWVBFX0NVQkVfVVYgKVxuXHRcdFx0dmVjMyB3b3JsZE5vcm1hbCA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIG5vcm1hbCwgdmlld01hdHJpeCApO1xuXHRcdFx0dmVjNCBlbnZNYXBDb2xvciA9IHRleHR1cmVDdWJlVVYoIGVudk1hcCwgd29ybGROb3JtYWwsIDEuMCApO1xuXHRcdFx0cmV0dXJuIFBJICogZW52TWFwQ29sb3IucmdiICogZW52TWFwSW50ZW5zaXR5O1xuXHRcdCNlbHNlXG5cdFx0XHRyZXR1cm4gdmVjMyggMC4wICk7XG5cdFx0I2VuZGlmXG5cdH1cblx0dmVjMyBnZXRJQkxSYWRpYW5jZSggY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzICkge1xuXHRcdCNpZiBkZWZpbmVkKCBFTlZNQVBfVFlQRV9DVUJFX1VWIClcblx0XHRcdHZlYzMgcmVmbGVjdFZlYztcblx0XHRcdCNpZmRlZiBFTlZNQVBfTU9ERV9SRUZMRUNUSU9OXG5cdFx0XHRcdHJlZmxlY3RWZWMgPSByZWZsZWN0KCAtIHZpZXdEaXIsIG5vcm1hbCApO1xuXHRcdFx0XHRyZWZsZWN0VmVjID0gbm9ybWFsaXplKCBtaXgoIHJlZmxlY3RWZWMsIG5vcm1hbCwgcm91Z2huZXNzICogcm91Z2huZXNzKSApO1xuXHRcdFx0I2Vsc2Vcblx0XHRcdFx0cmVmbGVjdFZlYyA9IHJlZnJhY3QoIC0gdmlld0Rpciwgbm9ybWFsLCByZWZyYWN0aW9uUmF0aW8gKTtcblx0XHRcdCNlbmRpZlxuXHRcdFx0cmVmbGVjdFZlYyA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIHJlZmxlY3RWZWMsIHZpZXdNYXRyaXggKTtcblx0XHRcdHZlYzQgZW52TWFwQ29sb3IgPSB0ZXh0dXJlQ3ViZVVWKCBlbnZNYXAsIHJlZmxlY3RWZWMsIHJvdWdobmVzcyApO1xuXHRcdFx0cmV0dXJuIGVudk1hcENvbG9yLnJnYiAqIGVudk1hcEludGVuc2l0eTtcblx0XHQjZWxzZVxuXHRcdFx0cmV0dXJuIHZlYzMoIDAuMCApO1xuXHRcdCNlbmRpZlxuXHR9XG4jZW5kaWYiLGVudm1hcF92ZXJ0ZXg6IiNpZmRlZiBVU0VfRU5WTUFQXG5cdCNpZmRlZiBFTlZfV09STERQT1Ncblx0XHR2V29ybGRQb3NpdGlvbiA9IHdvcmxkUG9zaXRpb24ueHl6O1xuXHQjZWxzZVxuXHRcdHZlYzMgY2FtZXJhVG9WZXJ0ZXg7XG5cdFx0aWYgKCBpc09ydGhvZ3JhcGhpYyApIHtcblx0XHRcdGNhbWVyYVRvVmVydGV4ID0gbm9ybWFsaXplKCB2ZWMzKCAtIHZpZXdNYXRyaXhbIDAgXVsgMiBdLCAtIHZpZXdNYXRyaXhbIDEgXVsgMiBdLCAtIHZpZXdNYXRyaXhbIDIgXVsgMiBdICkgKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0Y2FtZXJhVG9WZXJ0ZXggPSBub3JtYWxpemUoIHdvcmxkUG9zaXRpb24ueHl6IC0gY2FtZXJhUG9zaXRpb24gKTtcblx0XHR9XG5cdFx0dmVjMyB3b3JsZE5vcm1hbCA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIHRyYW5zZm9ybWVkTm9ybWFsLCB2aWV3TWF0cml4ICk7XG5cdFx0I2lmZGVmIEVOVk1BUF9NT0RFX1JFRkxFQ1RJT05cblx0XHRcdHZSZWZsZWN0ID0gcmVmbGVjdCggY2FtZXJhVG9WZXJ0ZXgsIHdvcmxkTm9ybWFsICk7XG5cdFx0I2Vsc2Vcblx0XHRcdHZSZWZsZWN0ID0gcmVmcmFjdCggY2FtZXJhVG9WZXJ0ZXgsIHdvcmxkTm9ybWFsLCByZWZyYWN0aW9uUmF0aW8gKTtcblx0XHQjZW5kaWZcblx0I2VuZGlmXG4jZW5kaWYiLGZvZ192ZXJ0ZXg6IiNpZmRlZiBVU0VfRk9HXG5cdHZGb2dEZXB0aCA9IC0gbXZQb3NpdGlvbi56O1xuI2VuZGlmIixmb2dfcGFyc192ZXJ0ZXg6IiNpZmRlZiBVU0VfRk9HXG5cdHZhcnlpbmcgZmxvYXQgdkZvZ0RlcHRoO1xuI2VuZGlmIixmb2dfZnJhZ21lbnQ6IiNpZmRlZiBVU0VfRk9HXG5cdCNpZmRlZiBGT0dfRVhQMlxuXHRcdGZsb2F0IGZvZ0ZhY3RvciA9IDEuMCAtIGV4cCggLSBmb2dEZW5zaXR5ICogZm9nRGVuc2l0eSAqIHZGb2dEZXB0aCAqIHZGb2dEZXB0aCApO1xuXHQjZWxzZVxuXHRcdGZsb2F0IGZvZ0ZhY3RvciA9IHNtb290aHN0ZXAoIGZvZ05lYXIsIGZvZ0ZhciwgdkZvZ0RlcHRoICk7XG5cdCNlbmRpZlxuXHRnbF9GcmFnQ29sb3IucmdiID0gbWl4KCBnbF9GcmFnQ29sb3IucmdiLCBmb2dDb2xvciwgZm9nRmFjdG9yICk7XG4jZW5kaWYiLGZvZ19wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX0ZPR1xuXHR1bmlmb3JtIHZlYzMgZm9nQ29sb3I7XG5cdHZhcnlpbmcgZmxvYXQgdkZvZ0RlcHRoO1xuXHQjaWZkZWYgRk9HX0VYUDJcblx0XHR1bmlmb3JtIGZsb2F0IGZvZ0RlbnNpdHk7XG5cdCNlbHNlXG5cdFx0dW5pZm9ybSBmbG9hdCBmb2dOZWFyO1xuXHRcdHVuaWZvcm0gZmxvYXQgZm9nRmFyO1xuXHQjZW5kaWZcbiNlbmRpZiIsZ3JhZGllbnRtYXBfcGFyc19mcmFnbWVudDoiI2lmZGVmIFVTRV9HUkFESUVOVE1BUFxuXHR1bmlmb3JtIHNhbXBsZXIyRCBncmFkaWVudE1hcDtcbiNlbmRpZlxudmVjMyBnZXRHcmFkaWVudElycmFkaWFuY2UoIHZlYzMgbm9ybWFsLCB2ZWMzIGxpZ2h0RGlyZWN0aW9uICkge1xuXHRmbG9hdCBkb3ROTCA9IGRvdCggbm9ybWFsLCBsaWdodERpcmVjdGlvbiApO1xuXHR2ZWMyIGNvb3JkID0gdmVjMiggZG90TkwgKiAwLjUgKyAwLjUsIDAuMCApO1xuXHQjaWZkZWYgVVNFX0dSQURJRU5UTUFQXG5cdFx0cmV0dXJuIHZlYzMoIHRleHR1cmUyRCggZ3JhZGllbnRNYXAsIGNvb3JkICkuciApO1xuXHQjZWxzZVxuXHRcdHJldHVybiAoIGNvb3JkLnggPCAwLjcgKSA/IHZlYzMoIDAuNyApIDogdmVjMyggMS4wICk7XG5cdCNlbmRpZlxufSIsbGlnaHRtYXBfZnJhZ21lbnQ6IiNpZmRlZiBVU0VfTElHSFRNQVBcblx0dmVjNCBsaWdodE1hcFRleGVsID0gdGV4dHVyZTJEKCBsaWdodE1hcCwgdlV2MiApO1xuXHR2ZWMzIGxpZ2h0TWFwSXJyYWRpYW5jZSA9IGxpZ2h0TWFwVGV4ZWwucmdiICogbGlnaHRNYXBJbnRlbnNpdHk7XG5cdCNpZm5kZWYgUEhZU0lDQUxMWV9DT1JSRUNUX0xJR0hUU1xuXHRcdGxpZ2h0TWFwSXJyYWRpYW5jZSAqPSBQSTtcblx0I2VuZGlmXG5cdHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSBsaWdodE1hcElycmFkaWFuY2U7XG4jZW5kaWYiLGxpZ2h0bWFwX3BhcnNfZnJhZ21lbnQ6IiNpZmRlZiBVU0VfTElHSFRNQVBcblx0dW5pZm9ybSBzYW1wbGVyMkQgbGlnaHRNYXA7XG5cdHVuaWZvcm0gZmxvYXQgbGlnaHRNYXBJbnRlbnNpdHk7XG4jZW5kaWYiLGxpZ2h0c19sYW1iZXJ0X3ZlcnRleDoidmVjMyBkaWZmdXNlID0gdmVjMyggMS4wICk7XG5HZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5O1xuZ2VvbWV0cnkucG9zaXRpb24gPSBtdlBvc2l0aW9uLnh5ejtcbmdlb21ldHJ5Lm5vcm1hbCA9IG5vcm1hbGl6ZSggdHJhbnNmb3JtZWROb3JtYWwgKTtcbmdlb21ldHJ5LnZpZXdEaXIgPSAoIGlzT3J0aG9ncmFwaGljICkgPyB2ZWMzKCAwLCAwLCAxICkgOiBub3JtYWxpemUoIC1tdlBvc2l0aW9uLnh5eiApO1xuR2VvbWV0cmljQ29udGV4dCBiYWNrR2VvbWV0cnk7XG5iYWNrR2VvbWV0cnkucG9zaXRpb24gPSBnZW9tZXRyeS5wb3NpdGlvbjtcbmJhY2tHZW9tZXRyeS5ub3JtYWwgPSAtZ2VvbWV0cnkubm9ybWFsO1xuYmFja0dlb21ldHJ5LnZpZXdEaXIgPSBnZW9tZXRyeS52aWV3RGlyO1xudkxpZ2h0RnJvbnQgPSB2ZWMzKCAwLjAgKTtcbnZJbmRpcmVjdEZyb250ID0gdmVjMyggMC4wICk7XG4jaWZkZWYgRE9VQkxFX1NJREVEXG5cdHZMaWdodEJhY2sgPSB2ZWMzKCAwLjAgKTtcblx0dkluZGlyZWN0QmFjayA9IHZlYzMoIDAuMCApO1xuI2VuZGlmXG5JbmNpZGVudExpZ2h0IGRpcmVjdExpZ2h0O1xuZmxvYXQgZG90Tkw7XG52ZWMzIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTtcbnZJbmRpcmVjdEZyb250ICs9IGdldEFtYmllbnRMaWdodElycmFkaWFuY2UoIGFtYmllbnRMaWdodENvbG9yICk7XG52SW5kaXJlY3RGcm9udCArPSBnZXRMaWdodFByb2JlSXJyYWRpYW5jZSggbGlnaHRQcm9iZSwgZ2VvbWV0cnkubm9ybWFsICk7XG4jaWZkZWYgRE9VQkxFX1NJREVEXG5cdHZJbmRpcmVjdEJhY2sgKz0gZ2V0QW1iaWVudExpZ2h0SXJyYWRpYW5jZSggYW1iaWVudExpZ2h0Q29sb3IgKTtcblx0dkluZGlyZWN0QmFjayArPSBnZXRMaWdodFByb2JlSXJyYWRpYW5jZSggbGlnaHRQcm9iZSwgYmFja0dlb21ldHJ5Lm5vcm1hbCApO1xuI2VuZGlmXG4jaWYgTlVNX1BPSU5UX0xJR0hUUyA+IDBcblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydFxuXHRmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fUE9JTlRfTElHSFRTOyBpICsrICkge1xuXHRcdGdldFBvaW50TGlnaHRJbmZvKCBwb2ludExpZ2h0c1sgaSBdLCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTtcblx0XHRkb3ROTCA9IGRvdCggZ2VvbWV0cnkubm9ybWFsLCBkaXJlY3RMaWdodC5kaXJlY3Rpb24gKTtcblx0XHRkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2UgPSBkaXJlY3RMaWdodC5jb2xvcjtcblx0XHR2TGlnaHRGcm9udCArPSBzYXR1cmF0ZSggZG90TkwgKSAqIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTtcblx0XHQjaWZkZWYgRE9VQkxFX1NJREVEXG5cdFx0XHR2TGlnaHRCYWNrICs9IHNhdHVyYXRlKCAtIGRvdE5MICkgKiBkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2U7XG5cdFx0I2VuZGlmXG5cdH1cblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9lbmRcbiNlbmRpZlxuI2lmIE5VTV9TUE9UX0xJR0hUUyA+IDBcblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydFxuXHRmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fU1BPVF9MSUdIVFM7IGkgKysgKSB7XG5cdFx0Z2V0U3BvdExpZ2h0SW5mbyggc3BvdExpZ2h0c1sgaSBdLCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTtcblx0XHRkb3ROTCA9IGRvdCggZ2VvbWV0cnkubm9ybWFsLCBkaXJlY3RMaWdodC5kaXJlY3Rpb24gKTtcblx0XHRkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2UgPSBkaXJlY3RMaWdodC5jb2xvcjtcblx0XHR2TGlnaHRGcm9udCArPSBzYXR1cmF0ZSggZG90TkwgKSAqIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTtcblx0XHQjaWZkZWYgRE9VQkxFX1NJREVEXG5cdFx0XHR2TGlnaHRCYWNrICs9IHNhdHVyYXRlKCAtIGRvdE5MICkgKiBkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2U7XG5cdFx0I2VuZGlmXG5cdH1cblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9lbmRcbiNlbmRpZlxuI2lmIE5VTV9ESVJfTElHSFRTID4gMFxuXHQjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0XG5cdGZvciAoIGludCBpID0gMDsgaSA8IE5VTV9ESVJfTElHSFRTOyBpICsrICkge1xuXHRcdGdldERpcmVjdGlvbmFsTGlnaHRJbmZvKCBkaXJlY3Rpb25hbExpZ2h0c1sgaSBdLCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTtcblx0XHRkb3ROTCA9IGRvdCggZ2VvbWV0cnkubm9ybWFsLCBkaXJlY3RMaWdodC5kaXJlY3Rpb24gKTtcblx0XHRkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2UgPSBkaXJlY3RMaWdodC5jb2xvcjtcblx0XHR2TGlnaHRGcm9udCArPSBzYXR1cmF0ZSggZG90TkwgKSAqIGRpcmVjdExpZ2h0Q29sb3JfRGlmZnVzZTtcblx0XHQjaWZkZWYgRE9VQkxFX1NJREVEXG5cdFx0XHR2TGlnaHRCYWNrICs9IHNhdHVyYXRlKCAtIGRvdE5MICkgKiBkaXJlY3RMaWdodENvbG9yX0RpZmZ1c2U7XG5cdFx0I2VuZGlmXG5cdH1cblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9lbmRcbiNlbmRpZlxuI2lmIE5VTV9IRU1JX0xJR0hUUyA+IDBcblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydFxuXHRmb3IgKCBpbnQgaSA9IDA7IGkgPCBOVU1fSEVNSV9MSUdIVFM7IGkgKysgKSB7XG5cdFx0dkluZGlyZWN0RnJvbnQgKz0gZ2V0SGVtaXNwaGVyZUxpZ2h0SXJyYWRpYW5jZSggaGVtaXNwaGVyZUxpZ2h0c1sgaSBdLCBnZW9tZXRyeS5ub3JtYWwgKTtcblx0XHQjaWZkZWYgRE9VQkxFX1NJREVEXG5cdFx0XHR2SW5kaXJlY3RCYWNrICs9IGdldEhlbWlzcGhlcmVMaWdodElycmFkaWFuY2UoIGhlbWlzcGhlcmVMaWdodHNbIGkgXSwgYmFja0dlb21ldHJ5Lm5vcm1hbCApO1xuXHRcdCNlbmRpZlxuXHR9XG5cdCNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kXG4jZW5kaWYiLGxpZ2h0c19wYXJzX2JlZ2luOiJ1bmlmb3JtIGJvb2wgcmVjZWl2ZVNoYWRvdztcbnVuaWZvcm0gdmVjMyBhbWJpZW50TGlnaHRDb2xvcjtcbnVuaWZvcm0gdmVjMyBsaWdodFByb2JlWyA5IF07XG52ZWMzIHNoR2V0SXJyYWRpYW5jZUF0KCBpbiB2ZWMzIG5vcm1hbCwgaW4gdmVjMyBzaENvZWZmaWNpZW50c1sgOSBdICkge1xuXHRmbG9hdCB4ID0gbm9ybWFsLngsIHkgPSBub3JtYWwueSwgeiA9IG5vcm1hbC56O1xuXHR2ZWMzIHJlc3VsdCA9IHNoQ29lZmZpY2llbnRzWyAwIF0gKiAwLjg4NjIyNztcblx0cmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyAxIF0gKiAyLjAgKiAwLjUxMTY2NCAqIHk7XG5cdHJlc3VsdCArPSBzaENvZWZmaWNpZW50c1sgMiBdICogMi4wICogMC41MTE2NjQgKiB6O1xuXHRyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDMgXSAqIDIuMCAqIDAuNTExNjY0ICogeDtcblx0cmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyA0IF0gKiAyLjAgKiAwLjQyOTA0MyAqIHggKiB5O1xuXHRyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDUgXSAqIDIuMCAqIDAuNDI5MDQzICogeSAqIHo7XG5cdHJlc3VsdCArPSBzaENvZWZmaWNpZW50c1sgNiBdICogKCAwLjc0MzEyNSAqIHogKiB6IC0gMC4yNDc3MDggKTtcblx0cmVzdWx0ICs9IHNoQ29lZmZpY2llbnRzWyA3IF0gKiAyLjAgKiAwLjQyOTA0MyAqIHggKiB6O1xuXHRyZXN1bHQgKz0gc2hDb2VmZmljaWVudHNbIDggXSAqIDAuNDI5MDQzICogKCB4ICogeCAtIHkgKiB5ICk7XG5cdHJldHVybiByZXN1bHQ7XG59XG52ZWMzIGdldExpZ2h0UHJvYmVJcnJhZGlhbmNlKCBjb25zdCBpbiB2ZWMzIGxpZ2h0UHJvYmVbIDkgXSwgY29uc3QgaW4gdmVjMyBub3JtYWwgKSB7XG5cdHZlYzMgd29ybGROb3JtYWwgPSBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCBub3JtYWwsIHZpZXdNYXRyaXggKTtcblx0dmVjMyBpcnJhZGlhbmNlID0gc2hHZXRJcnJhZGlhbmNlQXQoIHdvcmxkTm9ybWFsLCBsaWdodFByb2JlICk7XG5cdHJldHVybiBpcnJhZGlhbmNlO1xufVxudmVjMyBnZXRBbWJpZW50TGlnaHRJcnJhZGlhbmNlKCBjb25zdCBpbiB2ZWMzIGFtYmllbnRMaWdodENvbG9yICkge1xuXHR2ZWMzIGlycmFkaWFuY2UgPSBhbWJpZW50TGlnaHRDb2xvcjtcblx0cmV0dXJuIGlycmFkaWFuY2U7XG59XG5mbG9hdCBnZXREaXN0YW5jZUF0dGVudWF0aW9uKCBjb25zdCBpbiBmbG9hdCBsaWdodERpc3RhbmNlLCBjb25zdCBpbiBmbG9hdCBjdXRvZmZEaXN0YW5jZSwgY29uc3QgaW4gZmxvYXQgZGVjYXlFeHBvbmVudCApIHtcblx0I2lmIGRlZmluZWQgKCBQSFlTSUNBTExZX0NPUlJFQ1RfTElHSFRTIClcblx0XHRmbG9hdCBkaXN0YW5jZUZhbGxvZmYgPSAxLjAgLyBtYXgoIHBvdyggbGlnaHREaXN0YW5jZSwgZGVjYXlFeHBvbmVudCApLCAwLjAxICk7XG5cdFx0aWYgKCBjdXRvZmZEaXN0YW5jZSA+IDAuMCApIHtcblx0XHRcdGRpc3RhbmNlRmFsbG9mZiAqPSBwb3cyKCBzYXR1cmF0ZSggMS4wIC0gcG93NCggbGlnaHREaXN0YW5jZSAvIGN1dG9mZkRpc3RhbmNlICkgKSApO1xuXHRcdH1cblx0XHRyZXR1cm4gZGlzdGFuY2VGYWxsb2ZmO1xuXHQjZWxzZVxuXHRcdGlmICggY3V0b2ZmRGlzdGFuY2UgPiAwLjAgJiYgZGVjYXlFeHBvbmVudCA+IDAuMCApIHtcblx0XHRcdHJldHVybiBwb3coIHNhdHVyYXRlKCAtIGxpZ2h0RGlzdGFuY2UgLyBjdXRvZmZEaXN0YW5jZSArIDEuMCApLCBkZWNheUV4cG9uZW50ICk7XG5cdFx0fVxuXHRcdHJldHVybiAxLjA7XG5cdCNlbmRpZlxufVxuZmxvYXQgZ2V0U3BvdEF0dGVudWF0aW9uKCBjb25zdCBpbiBmbG9hdCBjb25lQ29zaW5lLCBjb25zdCBpbiBmbG9hdCBwZW51bWJyYUNvc2luZSwgY29uc3QgaW4gZmxvYXQgYW5nbGVDb3NpbmUgKSB7XG5cdHJldHVybiBzbW9vdGhzdGVwKCBjb25lQ29zaW5lLCBwZW51bWJyYUNvc2luZSwgYW5nbGVDb3NpbmUgKTtcbn1cbiNpZiBOVU1fRElSX0xJR0hUUyA+IDBcblx0c3RydWN0IERpcmVjdGlvbmFsTGlnaHQge1xuXHRcdHZlYzMgZGlyZWN0aW9uO1xuXHRcdHZlYzMgY29sb3I7XG5cdH07XG5cdHVuaWZvcm0gRGlyZWN0aW9uYWxMaWdodCBkaXJlY3Rpb25hbExpZ2h0c1sgTlVNX0RJUl9MSUdIVFMgXTtcblx0dm9pZCBnZXREaXJlY3Rpb25hbExpZ2h0SW5mbyggY29uc3QgaW4gRGlyZWN0aW9uYWxMaWdodCBkaXJlY3Rpb25hbExpZ2h0LCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBvdXQgSW5jaWRlbnRMaWdodCBsaWdodCApIHtcblx0XHRsaWdodC5jb2xvciA9IGRpcmVjdGlvbmFsTGlnaHQuY29sb3I7XG5cdFx0bGlnaHQuZGlyZWN0aW9uID0gZGlyZWN0aW9uYWxMaWdodC5kaXJlY3Rpb247XG5cdFx0bGlnaHQudmlzaWJsZSA9IHRydWU7XG5cdH1cbiNlbmRpZlxuI2lmIE5VTV9QT0lOVF9MSUdIVFMgPiAwXG5cdHN0cnVjdCBQb2ludExpZ2h0IHtcblx0XHR2ZWMzIHBvc2l0aW9uO1xuXHRcdHZlYzMgY29sb3I7XG5cdFx0ZmxvYXQgZGlzdGFuY2U7XG5cdFx0ZmxvYXQgZGVjYXk7XG5cdH07XG5cdHVuaWZvcm0gUG9pbnRMaWdodCBwb2ludExpZ2h0c1sgTlVNX1BPSU5UX0xJR0hUUyBdO1xuXHR2b2lkIGdldFBvaW50TGlnaHRJbmZvKCBjb25zdCBpbiBQb2ludExpZ2h0IHBvaW50TGlnaHQsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIG91dCBJbmNpZGVudExpZ2h0IGxpZ2h0ICkge1xuXHRcdHZlYzMgbFZlY3RvciA9IHBvaW50TGlnaHQucG9zaXRpb24gLSBnZW9tZXRyeS5wb3NpdGlvbjtcblx0XHRsaWdodC5kaXJlY3Rpb24gPSBub3JtYWxpemUoIGxWZWN0b3IgKTtcblx0XHRmbG9hdCBsaWdodERpc3RhbmNlID0gbGVuZ3RoKCBsVmVjdG9yICk7XG5cdFx0bGlnaHQuY29sb3IgPSBwb2ludExpZ2h0LmNvbG9yO1xuXHRcdGxpZ2h0LmNvbG9yICo9IGdldERpc3RhbmNlQXR0ZW51YXRpb24oIGxpZ2h0RGlzdGFuY2UsIHBvaW50TGlnaHQuZGlzdGFuY2UsIHBvaW50TGlnaHQuZGVjYXkgKTtcblx0XHRsaWdodC52aXNpYmxlID0gKCBsaWdodC5jb2xvciAhPSB2ZWMzKCAwLjAgKSApO1xuXHR9XG4jZW5kaWZcbiNpZiBOVU1fU1BPVF9MSUdIVFMgPiAwXG5cdHN0cnVjdCBTcG90TGlnaHQge1xuXHRcdHZlYzMgcG9zaXRpb247XG5cdFx0dmVjMyBkaXJlY3Rpb247XG5cdFx0dmVjMyBjb2xvcjtcblx0XHRmbG9hdCBkaXN0YW5jZTtcblx0XHRmbG9hdCBkZWNheTtcblx0XHRmbG9hdCBjb25lQ29zO1xuXHRcdGZsb2F0IHBlbnVtYnJhQ29zO1xuXHR9O1xuXHR1bmlmb3JtIFNwb3RMaWdodCBzcG90TGlnaHRzWyBOVU1fU1BPVF9MSUdIVFMgXTtcblx0dm9pZCBnZXRTcG90TGlnaHRJbmZvKCBjb25zdCBpbiBTcG90TGlnaHQgc3BvdExpZ2h0LCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBvdXQgSW5jaWRlbnRMaWdodCBsaWdodCApIHtcblx0XHR2ZWMzIGxWZWN0b3IgPSBzcG90TGlnaHQucG9zaXRpb24gLSBnZW9tZXRyeS5wb3NpdGlvbjtcblx0XHRsaWdodC5kaXJlY3Rpb24gPSBub3JtYWxpemUoIGxWZWN0b3IgKTtcblx0XHRmbG9hdCBhbmdsZUNvcyA9IGRvdCggbGlnaHQuZGlyZWN0aW9uLCBzcG90TGlnaHQuZGlyZWN0aW9uICk7XG5cdFx0ZmxvYXQgc3BvdEF0dGVudWF0aW9uID0gZ2V0U3BvdEF0dGVudWF0aW9uKCBzcG90TGlnaHQuY29uZUNvcywgc3BvdExpZ2h0LnBlbnVtYnJhQ29zLCBhbmdsZUNvcyApO1xuXHRcdGlmICggc3BvdEF0dGVudWF0aW9uID4gMC4wICkge1xuXHRcdFx0ZmxvYXQgbGlnaHREaXN0YW5jZSA9IGxlbmd0aCggbFZlY3RvciApO1xuXHRcdFx0bGlnaHQuY29sb3IgPSBzcG90TGlnaHQuY29sb3IgKiBzcG90QXR0ZW51YXRpb247XG5cdFx0XHRsaWdodC5jb2xvciAqPSBnZXREaXN0YW5jZUF0dGVudWF0aW9uKCBsaWdodERpc3RhbmNlLCBzcG90TGlnaHQuZGlzdGFuY2UsIHNwb3RMaWdodC5kZWNheSApO1xuXHRcdFx0bGlnaHQudmlzaWJsZSA9ICggbGlnaHQuY29sb3IgIT0gdmVjMyggMC4wICkgKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0bGlnaHQuY29sb3IgPSB2ZWMzKCAwLjAgKTtcblx0XHRcdGxpZ2h0LnZpc2libGUgPSBmYWxzZTtcblx0XHR9XG5cdH1cbiNlbmRpZlxuI2lmIE5VTV9SRUNUX0FSRUFfTElHSFRTID4gMFxuXHRzdHJ1Y3QgUmVjdEFyZWFMaWdodCB7XG5cdFx0dmVjMyBjb2xvcjtcblx0XHR2ZWMzIHBvc2l0aW9uO1xuXHRcdHZlYzMgaGFsZldpZHRoO1xuXHRcdHZlYzMgaGFsZkhlaWdodDtcblx0fTtcblx0dW5pZm9ybSBzYW1wbGVyMkQgbHRjXzE7XHR1bmlmb3JtIHNhbXBsZXIyRCBsdGNfMjtcblx0dW5pZm9ybSBSZWN0QXJlYUxpZ2h0IHJlY3RBcmVhTGlnaHRzWyBOVU1fUkVDVF9BUkVBX0xJR0hUUyBdO1xuI2VuZGlmXG4jaWYgTlVNX0hFTUlfTElHSFRTID4gMFxuXHRzdHJ1Y3QgSGVtaXNwaGVyZUxpZ2h0IHtcblx0XHR2ZWMzIGRpcmVjdGlvbjtcblx0XHR2ZWMzIHNreUNvbG9yO1xuXHRcdHZlYzMgZ3JvdW5kQ29sb3I7XG5cdH07XG5cdHVuaWZvcm0gSGVtaXNwaGVyZUxpZ2h0IGhlbWlzcGhlcmVMaWdodHNbIE5VTV9IRU1JX0xJR0hUUyBdO1xuXHR2ZWMzIGdldEhlbWlzcGhlcmVMaWdodElycmFkaWFuY2UoIGNvbnN0IGluIEhlbWlzcGhlcmVMaWdodCBoZW1pTGlnaHQsIGNvbnN0IGluIHZlYzMgbm9ybWFsICkge1xuXHRcdGZsb2F0IGRvdE5MID0gZG90KCBub3JtYWwsIGhlbWlMaWdodC5kaXJlY3Rpb24gKTtcblx0XHRmbG9hdCBoZW1pRGlmZnVzZVdlaWdodCA9IDAuNSAqIGRvdE5MICsgMC41O1xuXHRcdHZlYzMgaXJyYWRpYW5jZSA9IG1peCggaGVtaUxpZ2h0Lmdyb3VuZENvbG9yLCBoZW1pTGlnaHQuc2t5Q29sb3IsIGhlbWlEaWZmdXNlV2VpZ2h0ICk7XG5cdFx0cmV0dXJuIGlycmFkaWFuY2U7XG5cdH1cbiNlbmRpZiIsbGlnaHRzX3Rvb25fZnJhZ21lbnQ6IlRvb25NYXRlcmlhbCBtYXRlcmlhbDtcbm1hdGVyaWFsLmRpZmZ1c2VDb2xvciA9IGRpZmZ1c2VDb2xvci5yZ2I7IixsaWdodHNfdG9vbl9wYXJzX2ZyYWdtZW50OiJ2YXJ5aW5nIHZlYzMgdlZpZXdQb3NpdGlvbjtcbnN0cnVjdCBUb29uTWF0ZXJpYWwge1xuXHR2ZWMzIGRpZmZ1c2VDb2xvcjtcbn07XG52b2lkIFJFX0RpcmVjdF9Ub29uKCBjb25zdCBpbiBJbmNpZGVudExpZ2h0IGRpcmVjdExpZ2h0LCBjb25zdCBpbiBHZW9tZXRyaWNDb250ZXh0IGdlb21ldHJ5LCBjb25zdCBpbiBUb29uTWF0ZXJpYWwgbWF0ZXJpYWwsIGlub3V0IFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ICkge1xuXHR2ZWMzIGlycmFkaWFuY2UgPSBnZXRHcmFkaWVudElycmFkaWFuY2UoIGdlb21ldHJ5Lm5vcm1hbCwgZGlyZWN0TGlnaHQuZGlyZWN0aW9uICkgKiBkaXJlY3RMaWdodC5jb2xvcjtcblx0cmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArPSBpcnJhZGlhbmNlICogQlJERl9MYW1iZXJ0KCBtYXRlcmlhbC5kaWZmdXNlQ29sb3IgKTtcbn1cbnZvaWQgUkVfSW5kaXJlY3REaWZmdXNlX1Rvb24oIGNvbnN0IGluIHZlYzMgaXJyYWRpYW5jZSwgY29uc3QgaW4gR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeSwgY29uc3QgaW4gVG9vbk1hdGVyaWFsIG1hdGVyaWFsLCBpbm91dCBSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCApIHtcblx0cmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICs9IGlycmFkaWFuY2UgKiBCUkRGX0xhbWJlcnQoIG1hdGVyaWFsLmRpZmZ1c2VDb2xvciApO1xufVxuI2RlZmluZSBSRV9EaXJlY3RcdFx0XHRcdFJFX0RpcmVjdF9Ub29uXG4jZGVmaW5lIFJFX0luZGlyZWN0RGlmZnVzZVx0XHRSRV9JbmRpcmVjdERpZmZ1c2VfVG9vblxuI2RlZmluZSBNYXRlcmlhbF9MaWdodFByb2JlTE9EKCBtYXRlcmlhbCApXHQoMCkiLGxpZ2h0c19waG9uZ19mcmFnbWVudDoiQmxpbm5QaG9uZ01hdGVyaWFsIG1hdGVyaWFsO1xubWF0ZXJpYWwuZGlmZnVzZUNvbG9yID0gZGlmZnVzZUNvbG9yLnJnYjtcbm1hdGVyaWFsLnNwZWN1bGFyQ29sb3IgPSBzcGVjdWxhcjtcbm1hdGVyaWFsLnNwZWN1bGFyU2hpbmluZXNzID0gc2hpbmluZXNzO1xubWF0ZXJpYWwuc3BlY3VsYXJTdHJlbmd0aCA9IHNwZWN1bGFyU3RyZW5ndGg7IixsaWdodHNfcGhvbmdfcGFyc19mcmFnbWVudDoidmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247XG5zdHJ1Y3QgQmxpbm5QaG9uZ01hdGVyaWFsIHtcblx0dmVjMyBkaWZmdXNlQ29sb3I7XG5cdHZlYzMgc3BlY3VsYXJDb2xvcjtcblx0ZmxvYXQgc3BlY3VsYXJTaGluaW5lc3M7XG5cdGZsb2F0IHNwZWN1bGFyU3RyZW5ndGg7XG59O1xudm9pZCBSRV9EaXJlY3RfQmxpbm5QaG9uZyggY29uc3QgaW4gSW5jaWRlbnRMaWdodCBkaXJlY3RMaWdodCwgY29uc3QgaW4gR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeSwgY29uc3QgaW4gQmxpbm5QaG9uZ01hdGVyaWFsIG1hdGVyaWFsLCBpbm91dCBSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCApIHtcblx0ZmxvYXQgZG90TkwgPSBzYXR1cmF0ZSggZG90KCBnZW9tZXRyeS5ub3JtYWwsIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiApICk7XG5cdHZlYzMgaXJyYWRpYW5jZSA9IGRvdE5MICogZGlyZWN0TGlnaHQuY29sb3I7XG5cdHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKz0gaXJyYWRpYW5jZSAqIEJSREZfTGFtYmVydCggbWF0ZXJpYWwuZGlmZnVzZUNvbG9yICk7XG5cdHJlZmxlY3RlZExpZ2h0LmRpcmVjdFNwZWN1bGFyICs9IGlycmFkaWFuY2UgKiBCUkRGX0JsaW5uUGhvbmcoIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiwgZ2VvbWV0cnkudmlld0RpciwgZ2VvbWV0cnkubm9ybWFsLCBtYXRlcmlhbC5zcGVjdWxhckNvbG9yLCBtYXRlcmlhbC5zcGVjdWxhclNoaW5pbmVzcyApICogbWF0ZXJpYWwuc3BlY3VsYXJTdHJlbmd0aDtcbn1cbnZvaWQgUkVfSW5kaXJlY3REaWZmdXNlX0JsaW5uUGhvbmcoIGNvbnN0IGluIHZlYzMgaXJyYWRpYW5jZSwgY29uc3QgaW4gR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeSwgY29uc3QgaW4gQmxpbm5QaG9uZ01hdGVyaWFsIG1hdGVyaWFsLCBpbm91dCBSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCApIHtcblx0cmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICs9IGlycmFkaWFuY2UgKiBCUkRGX0xhbWJlcnQoIG1hdGVyaWFsLmRpZmZ1c2VDb2xvciApO1xufVxuI2RlZmluZSBSRV9EaXJlY3RcdFx0XHRcdFJFX0RpcmVjdF9CbGlublBob25nXG4jZGVmaW5lIFJFX0luZGlyZWN0RGlmZnVzZVx0XHRSRV9JbmRpcmVjdERpZmZ1c2VfQmxpbm5QaG9uZ1xuI2RlZmluZSBNYXRlcmlhbF9MaWdodFByb2JlTE9EKCBtYXRlcmlhbCApXHQoMCkiLGxpZ2h0c19waHlzaWNhbF9mcmFnbWVudDoiUGh5c2ljYWxNYXRlcmlhbCBtYXRlcmlhbDtcbm1hdGVyaWFsLmRpZmZ1c2VDb2xvciA9IGRpZmZ1c2VDb2xvci5yZ2IgKiAoIDEuMCAtIG1ldGFsbmVzc0ZhY3RvciApO1xudmVjMyBkeHkgPSBtYXgoIGFicyggZEZkeCggZ2VvbWV0cnlOb3JtYWwgKSApLCBhYnMoIGRGZHkoIGdlb21ldHJ5Tm9ybWFsICkgKSApO1xuZmxvYXQgZ2VvbWV0cnlSb3VnaG5lc3MgPSBtYXgoIG1heCggZHh5LngsIGR4eS55ICksIGR4eS56ICk7XG5tYXRlcmlhbC5yb3VnaG5lc3MgPSBtYXgoIHJvdWdobmVzc0ZhY3RvciwgMC4wNTI1ICk7bWF0ZXJpYWwucm91Z2huZXNzICs9IGdlb21ldHJ5Um91Z2huZXNzO1xubWF0ZXJpYWwucm91Z2huZXNzID0gbWluKCBtYXRlcmlhbC5yb3VnaG5lc3MsIDEuMCApO1xuI2lmZGVmIElPUlxuXHQjaWZkZWYgU1BFQ1VMQVJcblx0XHRmbG9hdCBzcGVjdWxhckludGVuc2l0eUZhY3RvciA9IHNwZWN1bGFySW50ZW5zaXR5O1xuXHRcdHZlYzMgc3BlY3VsYXJDb2xvckZhY3RvciA9IHNwZWN1bGFyQ29sb3I7XG5cdFx0I2lmZGVmIFVTRV9TUEVDVUxBUklOVEVOU0lUWU1BUFxuXHRcdFx0c3BlY3VsYXJJbnRlbnNpdHlGYWN0b3IgKj0gdGV4dHVyZTJEKCBzcGVjdWxhckludGVuc2l0eU1hcCwgdlV2ICkuYTtcblx0XHQjZW5kaWZcblx0XHQjaWZkZWYgVVNFX1NQRUNVTEFSQ09MT1JNQVBcblx0XHRcdHNwZWN1bGFyQ29sb3JGYWN0b3IgKj0gdGV4dHVyZTJEKCBzcGVjdWxhckNvbG9yTWFwLCB2VXYgKS5yZ2I7XG5cdFx0I2VuZGlmXG5cdFx0bWF0ZXJpYWwuc3BlY3VsYXJGOTAgPSBtaXgoIHNwZWN1bGFySW50ZW5zaXR5RmFjdG9yLCAxLjAsIG1ldGFsbmVzc0ZhY3RvciApO1xuXHQjZWxzZVxuXHRcdGZsb2F0IHNwZWN1bGFySW50ZW5zaXR5RmFjdG9yID0gMS4wO1xuXHRcdHZlYzMgc3BlY3VsYXJDb2xvckZhY3RvciA9IHZlYzMoIDEuMCApO1xuXHRcdG1hdGVyaWFsLnNwZWN1bGFyRjkwID0gMS4wO1xuXHQjZW5kaWZcblx0bWF0ZXJpYWwuc3BlY3VsYXJDb2xvciA9IG1peCggbWluKCBwb3cyKCAoIGlvciAtIDEuMCApIC8gKCBpb3IgKyAxLjAgKSApICogc3BlY3VsYXJDb2xvckZhY3RvciwgdmVjMyggMS4wICkgKSAqIHNwZWN1bGFySW50ZW5zaXR5RmFjdG9yLCBkaWZmdXNlQ29sb3IucmdiLCBtZXRhbG5lc3NGYWN0b3IgKTtcbiNlbHNlXG5cdG1hdGVyaWFsLnNwZWN1bGFyQ29sb3IgPSBtaXgoIHZlYzMoIDAuMDQgKSwgZGlmZnVzZUNvbG9yLnJnYiwgbWV0YWxuZXNzRmFjdG9yICk7XG5cdG1hdGVyaWFsLnNwZWN1bGFyRjkwID0gMS4wO1xuI2VuZGlmXG4jaWZkZWYgVVNFX0NMRUFSQ09BVFxuXHRtYXRlcmlhbC5jbGVhcmNvYXQgPSBjbGVhcmNvYXQ7XG5cdG1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcyA9IGNsZWFyY29hdFJvdWdobmVzcztcblx0bWF0ZXJpYWwuY2xlYXJjb2F0RjAgPSB2ZWMzKCAwLjA0ICk7XG5cdG1hdGVyaWFsLmNsZWFyY29hdEY5MCA9IDEuMDtcblx0I2lmZGVmIFVTRV9DTEVBUkNPQVRNQVBcblx0XHRtYXRlcmlhbC5jbGVhcmNvYXQgKj0gdGV4dHVyZTJEKCBjbGVhcmNvYXRNYXAsIHZVdiApLng7XG5cdCNlbmRpZlxuXHQjaWZkZWYgVVNFX0NMRUFSQ09BVF9ST1VHSE5FU1NNQVBcblx0XHRtYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgKj0gdGV4dHVyZTJEKCBjbGVhcmNvYXRSb3VnaG5lc3NNYXAsIHZVdiApLnk7XG5cdCNlbmRpZlxuXHRtYXRlcmlhbC5jbGVhcmNvYXQgPSBzYXR1cmF0ZSggbWF0ZXJpYWwuY2xlYXJjb2F0ICk7XHRtYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgPSBtYXgoIG1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcywgMC4wNTI1ICk7XG5cdG1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcyArPSBnZW9tZXRyeVJvdWdobmVzcztcblx0bWF0ZXJpYWwuY2xlYXJjb2F0Um91Z2huZXNzID0gbWluKCBtYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MsIDEuMCApO1xuI2VuZGlmXG4jaWZkZWYgVVNFX1NIRUVOXG5cdG1hdGVyaWFsLnNoZWVuQ29sb3IgPSBzaGVlbkNvbG9yO1xuXHQjaWZkZWYgVVNFX1NIRUVOQ09MT1JNQVBcblx0XHRtYXRlcmlhbC5zaGVlbkNvbG9yICo9IHRleHR1cmUyRCggc2hlZW5Db2xvck1hcCwgdlV2ICkucmdiO1xuXHQjZW5kaWZcblx0bWF0ZXJpYWwuc2hlZW5Sb3VnaG5lc3MgPSBjbGFtcCggc2hlZW5Sb3VnaG5lc3MsIDAuMDcsIDEuMCApO1xuXHQjaWZkZWYgVVNFX1NIRUVOUk9VR0hORVNTTUFQXG5cdFx0bWF0ZXJpYWwuc2hlZW5Sb3VnaG5lc3MgKj0gdGV4dHVyZTJEKCBzaGVlblJvdWdobmVzc01hcCwgdlV2ICkuYTtcblx0I2VuZGlmXG4jZW5kaWYiLGxpZ2h0c19waHlzaWNhbF9wYXJzX2ZyYWdtZW50OiJzdHJ1Y3QgUGh5c2ljYWxNYXRlcmlhbCB7XG5cdHZlYzMgZGlmZnVzZUNvbG9yO1xuXHRmbG9hdCByb3VnaG5lc3M7XG5cdHZlYzMgc3BlY3VsYXJDb2xvcjtcblx0ZmxvYXQgc3BlY3VsYXJGOTA7XG5cdCNpZmRlZiBVU0VfQ0xFQVJDT0FUXG5cdFx0ZmxvYXQgY2xlYXJjb2F0O1xuXHRcdGZsb2F0IGNsZWFyY29hdFJvdWdobmVzcztcblx0XHR2ZWMzIGNsZWFyY29hdEYwO1xuXHRcdGZsb2F0IGNsZWFyY29hdEY5MDtcblx0I2VuZGlmXG5cdCNpZmRlZiBVU0VfU0hFRU5cblx0XHR2ZWMzIHNoZWVuQ29sb3I7XG5cdFx0ZmxvYXQgc2hlZW5Sb3VnaG5lc3M7XG5cdCNlbmRpZlxufTtcbnZlYzMgY2xlYXJjb2F0U3BlY3VsYXIgPSB2ZWMzKCAwLjAgKTtcbnZlYzMgc2hlZW5TcGVjdWxhciA9IHZlYzMoIDAuMCApO1xuZmxvYXQgSUJMU2hlZW5CUkRGKCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MpIHtcblx0ZmxvYXQgZG90TlYgPSBzYXR1cmF0ZSggZG90KCBub3JtYWwsIHZpZXdEaXIgKSApO1xuXHRmbG9hdCByMiA9IHJvdWdobmVzcyAqIHJvdWdobmVzcztcblx0ZmxvYXQgYSA9IHJvdWdobmVzcyA8IDAuMjUgPyAtMzM5LjIgKiByMiArIDE2MS40ICogcm91Z2huZXNzIC0gMjUuOSA6IC04LjQ4ICogcjIgKyAxNC4zICogcm91Z2huZXNzIC0gOS45NTtcblx0ZmxvYXQgYiA9IHJvdWdobmVzcyA8IDAuMjUgPyA0NC4wICogcjIgLSAyMy43ICogcm91Z2huZXNzICsgMy4yNiA6IDEuOTcgKiByMiAtIDMuMjcgKiByb3VnaG5lc3MgKyAwLjcyO1xuXHRmbG9hdCBERyA9IGV4cCggYSAqIGRvdE5WICsgYiApICsgKCByb3VnaG5lc3MgPCAwLjI1ID8gMC4wIDogMC4xICogKCByb3VnaG5lc3MgLSAwLjI1ICkgKTtcblx0cmV0dXJuIHNhdHVyYXRlKCBERyAqIFJFQ0lQUk9DQUxfUEkgKTtcbn1cbnZlYzIgREZHQXBwcm94KCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MgKSB7XG5cdGZsb2F0IGRvdE5WID0gc2F0dXJhdGUoIGRvdCggbm9ybWFsLCB2aWV3RGlyICkgKTtcblx0Y29uc3QgdmVjNCBjMCA9IHZlYzQoIC0gMSwgLSAwLjAyNzUsIC0gMC41NzIsIDAuMDIyICk7XG5cdGNvbnN0IHZlYzQgYzEgPSB2ZWM0KCAxLCAwLjA0MjUsIDEuMDQsIC0gMC4wNCApO1xuXHR2ZWM0IHIgPSByb3VnaG5lc3MgKiBjMCArIGMxO1xuXHRmbG9hdCBhMDA0ID0gbWluKCByLnggKiByLngsIGV4cDIoIC0gOS4yOCAqIGRvdE5WICkgKSAqIHIueCArIHIueTtcblx0dmVjMiBmYWIgPSB2ZWMyKCAtIDEuMDQsIDEuMDQgKSAqIGEwMDQgKyByLnp3O1xuXHRyZXR1cm4gZmFiO1xufVxudmVjMyBFbnZpcm9ubWVudEJSREYoIGNvbnN0IGluIHZlYzMgbm9ybWFsLCBjb25zdCBpbiB2ZWMzIHZpZXdEaXIsIGNvbnN0IGluIHZlYzMgc3BlY3VsYXJDb2xvciwgY29uc3QgaW4gZmxvYXQgc3BlY3VsYXJGOTAsIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcyApIHtcblx0dmVjMiBmYWIgPSBERkdBcHByb3goIG5vcm1hbCwgdmlld0Rpciwgcm91Z2huZXNzICk7XG5cdHJldHVybiBzcGVjdWxhckNvbG9yICogZmFiLnggKyBzcGVjdWxhckY5MCAqIGZhYi55O1xufVxudm9pZCBjb21wdXRlTXVsdGlzY2F0dGVyaW5nKCBjb25zdCBpbiB2ZWMzIG5vcm1hbCwgY29uc3QgaW4gdmVjMyB2aWV3RGlyLCBjb25zdCBpbiB2ZWMzIHNwZWN1bGFyQ29sb3IsIGNvbnN0IGluIGZsb2F0IHNwZWN1bGFyRjkwLCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MsIGlub3V0IHZlYzMgc2luZ2xlU2NhdHRlciwgaW5vdXQgdmVjMyBtdWx0aVNjYXR0ZXIgKSB7XG5cdHZlYzIgZmFiID0gREZHQXBwcm94KCBub3JtYWwsIHZpZXdEaXIsIHJvdWdobmVzcyApO1xuXHR2ZWMzIEZzc0VzcyA9IHNwZWN1bGFyQ29sb3IgKiBmYWIueCArIHNwZWN1bGFyRjkwICogZmFiLnk7XG5cdGZsb2F0IEVzcyA9IGZhYi54ICsgZmFiLnk7XG5cdGZsb2F0IEVtcyA9IDEuMCAtIEVzcztcblx0dmVjMyBGYXZnID0gc3BlY3VsYXJDb2xvciArICggMS4wIC0gc3BlY3VsYXJDb2xvciApICogMC4wNDc2MTk7XHR2ZWMzIEZtcyA9IEZzc0VzcyAqIEZhdmcgLyAoIDEuMCAtIEVtcyAqIEZhdmcgKTtcblx0c2luZ2xlU2NhdHRlciArPSBGc3NFc3M7XG5cdG11bHRpU2NhdHRlciArPSBGbXMgKiBFbXM7XG59XG4jaWYgTlVNX1JFQ1RfQVJFQV9MSUdIVFMgPiAwXG5cdHZvaWQgUkVfRGlyZWN0X1JlY3RBcmVhX1BoeXNpY2FsKCBjb25zdCBpbiBSZWN0QXJlYUxpZ2h0IHJlY3RBcmVhTGlnaHQsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIGNvbnN0IGluIFBoeXNpY2FsTWF0ZXJpYWwgbWF0ZXJpYWwsIGlub3V0IFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ICkge1xuXHRcdHZlYzMgbm9ybWFsID0gZ2VvbWV0cnkubm9ybWFsO1xuXHRcdHZlYzMgdmlld0RpciA9IGdlb21ldHJ5LnZpZXdEaXI7XG5cdFx0dmVjMyBwb3NpdGlvbiA9IGdlb21ldHJ5LnBvc2l0aW9uO1xuXHRcdHZlYzMgbGlnaHRQb3MgPSByZWN0QXJlYUxpZ2h0LnBvc2l0aW9uO1xuXHRcdHZlYzMgaGFsZldpZHRoID0gcmVjdEFyZWFMaWdodC5oYWxmV2lkdGg7XG5cdFx0dmVjMyBoYWxmSGVpZ2h0ID0gcmVjdEFyZWFMaWdodC5oYWxmSGVpZ2h0O1xuXHRcdHZlYzMgbGlnaHRDb2xvciA9IHJlY3RBcmVhTGlnaHQuY29sb3I7XG5cdFx0ZmxvYXQgcm91Z2huZXNzID0gbWF0ZXJpYWwucm91Z2huZXNzO1xuXHRcdHZlYzMgcmVjdENvb3Jkc1sgNCBdO1xuXHRcdHJlY3RDb29yZHNbIDAgXSA9IGxpZ2h0UG9zICsgaGFsZldpZHRoIC0gaGFsZkhlaWdodDtcdFx0cmVjdENvb3Jkc1sgMSBdID0gbGlnaHRQb3MgLSBoYWxmV2lkdGggLSBoYWxmSGVpZ2h0O1xuXHRcdHJlY3RDb29yZHNbIDIgXSA9IGxpZ2h0UG9zIC0gaGFsZldpZHRoICsgaGFsZkhlaWdodDtcblx0XHRyZWN0Q29vcmRzWyAzIF0gPSBsaWdodFBvcyArIGhhbGZXaWR0aCArIGhhbGZIZWlnaHQ7XG5cdFx0dmVjMiB1diA9IExUQ19Vdiggbm9ybWFsLCB2aWV3RGlyLCByb3VnaG5lc3MgKTtcblx0XHR2ZWM0IHQxID0gdGV4dHVyZTJEKCBsdGNfMSwgdXYgKTtcblx0XHR2ZWM0IHQyID0gdGV4dHVyZTJEKCBsdGNfMiwgdXYgKTtcblx0XHRtYXQzIG1JbnYgPSBtYXQzKFxuXHRcdFx0dmVjMyggdDEueCwgMCwgdDEueSApLFxuXHRcdFx0dmVjMyggICAgMCwgMSwgICAgMCApLFxuXHRcdFx0dmVjMyggdDEueiwgMCwgdDEudyApXG5cdFx0KTtcblx0XHR2ZWMzIGZyZXNuZWwgPSAoIG1hdGVyaWFsLnNwZWN1bGFyQ29sb3IgKiB0Mi54ICsgKCB2ZWMzKCAxLjAgKSAtIG1hdGVyaWFsLnNwZWN1bGFyQ29sb3IgKSAqIHQyLnkgKTtcblx0XHRyZWZsZWN0ZWRMaWdodC5kaXJlY3RTcGVjdWxhciArPSBsaWdodENvbG9yICogZnJlc25lbCAqIExUQ19FdmFsdWF0ZSggbm9ybWFsLCB2aWV3RGlyLCBwb3NpdGlvbiwgbUludiwgcmVjdENvb3JkcyApO1xuXHRcdHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKz0gbGlnaHRDb2xvciAqIG1hdGVyaWFsLmRpZmZ1c2VDb2xvciAqIExUQ19FdmFsdWF0ZSggbm9ybWFsLCB2aWV3RGlyLCBwb3NpdGlvbiwgbWF0MyggMS4wICksIHJlY3RDb29yZHMgKTtcblx0fVxuI2VuZGlmXG52b2lkIFJFX0RpcmVjdF9QaHlzaWNhbCggY29uc3QgaW4gSW5jaWRlbnRMaWdodCBkaXJlY3RMaWdodCwgY29uc3QgaW4gR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeSwgY29uc3QgaW4gUGh5c2ljYWxNYXRlcmlhbCBtYXRlcmlhbCwgaW5vdXQgUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgKSB7XG5cdGZsb2F0IGRvdE5MID0gc2F0dXJhdGUoIGRvdCggZ2VvbWV0cnkubm9ybWFsLCBkaXJlY3RMaWdodC5kaXJlY3Rpb24gKSApO1xuXHR2ZWMzIGlycmFkaWFuY2UgPSBkb3ROTCAqIGRpcmVjdExpZ2h0LmNvbG9yO1xuXHQjaWZkZWYgVVNFX0NMRUFSQ09BVFxuXHRcdGZsb2F0IGRvdE5MY2MgPSBzYXR1cmF0ZSggZG90KCBnZW9tZXRyeS5jbGVhcmNvYXROb3JtYWwsIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiApICk7XG5cdFx0dmVjMyBjY0lycmFkaWFuY2UgPSBkb3ROTGNjICogZGlyZWN0TGlnaHQuY29sb3I7XG5cdFx0Y2xlYXJjb2F0U3BlY3VsYXIgKz0gY2NJcnJhZGlhbmNlICogQlJERl9HR1goIGRpcmVjdExpZ2h0LmRpcmVjdGlvbiwgZ2VvbWV0cnkudmlld0RpciwgZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsLCBtYXRlcmlhbC5jbGVhcmNvYXRGMCwgbWF0ZXJpYWwuY2xlYXJjb2F0RjkwLCBtYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgKTtcblx0I2VuZGlmXG5cdCNpZmRlZiBVU0VfU0hFRU5cblx0XHRzaGVlblNwZWN1bGFyICs9IGlycmFkaWFuY2UgKiBCUkRGX1NoZWVuKCBkaXJlY3RMaWdodC5kaXJlY3Rpb24sIGdlb21ldHJ5LnZpZXdEaXIsIGdlb21ldHJ5Lm5vcm1hbCwgbWF0ZXJpYWwuc2hlZW5Db2xvciwgbWF0ZXJpYWwuc2hlZW5Sb3VnaG5lc3MgKTtcblx0I2VuZGlmXG5cdHJlZmxlY3RlZExpZ2h0LmRpcmVjdFNwZWN1bGFyICs9IGlycmFkaWFuY2UgKiBCUkRGX0dHWCggZGlyZWN0TGlnaHQuZGlyZWN0aW9uLCBnZW9tZXRyeS52aWV3RGlyLCBnZW9tZXRyeS5ub3JtYWwsIG1hdGVyaWFsLnNwZWN1bGFyQ29sb3IsIG1hdGVyaWFsLnNwZWN1bGFyRjkwLCBtYXRlcmlhbC5yb3VnaG5lc3MgKTtcblx0cmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArPSBpcnJhZGlhbmNlICogQlJERl9MYW1iZXJ0KCBtYXRlcmlhbC5kaWZmdXNlQ29sb3IgKTtcbn1cbnZvaWQgUkVfSW5kaXJlY3REaWZmdXNlX1BoeXNpY2FsKCBjb25zdCBpbiB2ZWMzIGlycmFkaWFuY2UsIGNvbnN0IGluIEdlb21ldHJpY0NvbnRleHQgZ2VvbWV0cnksIGNvbnN0IGluIFBoeXNpY2FsTWF0ZXJpYWwgbWF0ZXJpYWwsIGlub3V0IFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ICkge1xuXHRyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKz0gaXJyYWRpYW5jZSAqIEJSREZfTGFtYmVydCggbWF0ZXJpYWwuZGlmZnVzZUNvbG9yICk7XG59XG52b2lkIFJFX0luZGlyZWN0U3BlY3VsYXJfUGh5c2ljYWwoIGNvbnN0IGluIHZlYzMgcmFkaWFuY2UsIGNvbnN0IGluIHZlYzMgaXJyYWRpYW5jZSwgY29uc3QgaW4gdmVjMyBjbGVhcmNvYXRSYWRpYW5jZSwgY29uc3QgaW4gR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeSwgY29uc3QgaW4gUGh5c2ljYWxNYXRlcmlhbCBtYXRlcmlhbCwgaW5vdXQgUmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQpIHtcblx0I2lmZGVmIFVTRV9DTEVBUkNPQVRcblx0XHRjbGVhcmNvYXRTcGVjdWxhciArPSBjbGVhcmNvYXRSYWRpYW5jZSAqIEVudmlyb25tZW50QlJERiggZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsLCBnZW9tZXRyeS52aWV3RGlyLCBtYXRlcmlhbC5jbGVhcmNvYXRGMCwgbWF0ZXJpYWwuY2xlYXJjb2F0RjkwLCBtYXRlcmlhbC5jbGVhcmNvYXRSb3VnaG5lc3MgKTtcblx0I2VuZGlmXG5cdCNpZmRlZiBVU0VfU0hFRU5cblx0XHRzaGVlblNwZWN1bGFyICs9IGlycmFkaWFuY2UgKiBtYXRlcmlhbC5zaGVlbkNvbG9yICogSUJMU2hlZW5CUkRGKCBnZW9tZXRyeS5ub3JtYWwsIGdlb21ldHJ5LnZpZXdEaXIsIG1hdGVyaWFsLnNoZWVuUm91Z2huZXNzICk7XG5cdCNlbmRpZlxuXHR2ZWMzIHNpbmdsZVNjYXR0ZXJpbmcgPSB2ZWMzKCAwLjAgKTtcblx0dmVjMyBtdWx0aVNjYXR0ZXJpbmcgPSB2ZWMzKCAwLjAgKTtcblx0dmVjMyBjb3NpbmVXZWlnaHRlZElycmFkaWFuY2UgPSBpcnJhZGlhbmNlICogUkVDSVBST0NBTF9QSTtcblx0Y29tcHV0ZU11bHRpc2NhdHRlcmluZyggZ2VvbWV0cnkubm9ybWFsLCBnZW9tZXRyeS52aWV3RGlyLCBtYXRlcmlhbC5zcGVjdWxhckNvbG9yLCBtYXRlcmlhbC5zcGVjdWxhckY5MCwgbWF0ZXJpYWwucm91Z2huZXNzLCBzaW5nbGVTY2F0dGVyaW5nLCBtdWx0aVNjYXR0ZXJpbmcgKTtcblx0dmVjMyBkaWZmdXNlID0gbWF0ZXJpYWwuZGlmZnVzZUNvbG9yICogKCAxLjAgLSAoIHNpbmdsZVNjYXR0ZXJpbmcgKyBtdWx0aVNjYXR0ZXJpbmcgKSApO1xuXHRyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdFNwZWN1bGFyICs9IHJhZGlhbmNlICogc2luZ2xlU2NhdHRlcmluZztcblx0cmVmbGVjdGVkTGlnaHQuaW5kaXJlY3RTcGVjdWxhciArPSBtdWx0aVNjYXR0ZXJpbmcgKiBjb3NpbmVXZWlnaHRlZElycmFkaWFuY2U7XG5cdHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSBkaWZmdXNlICogY29zaW5lV2VpZ2h0ZWRJcnJhZGlhbmNlO1xufVxuI2RlZmluZSBSRV9EaXJlY3RcdFx0XHRcdFJFX0RpcmVjdF9QaHlzaWNhbFxuI2RlZmluZSBSRV9EaXJlY3RfUmVjdEFyZWFcdFx0UkVfRGlyZWN0X1JlY3RBcmVhX1BoeXNpY2FsXG4jZGVmaW5lIFJFX0luZGlyZWN0RGlmZnVzZVx0XHRSRV9JbmRpcmVjdERpZmZ1c2VfUGh5c2ljYWxcbiNkZWZpbmUgUkVfSW5kaXJlY3RTcGVjdWxhclx0XHRSRV9JbmRpcmVjdFNwZWN1bGFyX1BoeXNpY2FsXG5mbG9hdCBjb21wdXRlU3BlY3VsYXJPY2NsdXNpb24oIGNvbnN0IGluIGZsb2F0IGRvdE5WLCBjb25zdCBpbiBmbG9hdCBhbWJpZW50T2NjbHVzaW9uLCBjb25zdCBpbiBmbG9hdCByb3VnaG5lc3MgKSB7XG5cdHJldHVybiBzYXR1cmF0ZSggcG93KCBkb3ROViArIGFtYmllbnRPY2NsdXNpb24sIGV4cDIoIC0gMTYuMCAqIHJvdWdobmVzcyAtIDEuMCApICkgLSAxLjAgKyBhbWJpZW50T2NjbHVzaW9uICk7XG59IixsaWdodHNfZnJhZ21lbnRfYmVnaW46IlxuR2VvbWV0cmljQ29udGV4dCBnZW9tZXRyeTtcbmdlb21ldHJ5LnBvc2l0aW9uID0gLSB2Vmlld1Bvc2l0aW9uO1xuZ2VvbWV0cnkubm9ybWFsID0gbm9ybWFsO1xuZ2VvbWV0cnkudmlld0RpciA9ICggaXNPcnRob2dyYXBoaWMgKSA/IHZlYzMoIDAsIDAsIDEgKSA6IG5vcm1hbGl6ZSggdlZpZXdQb3NpdGlvbiApO1xuI2lmZGVmIFVTRV9DTEVBUkNPQVRcblx0Z2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsID0gY2xlYXJjb2F0Tm9ybWFsO1xuI2VuZGlmXG5JbmNpZGVudExpZ2h0IGRpcmVjdExpZ2h0O1xuI2lmICggTlVNX1BPSU5UX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3QgKVxuXHRQb2ludExpZ2h0IHBvaW50TGlnaHQ7XG5cdCNpZiBkZWZpbmVkKCBVU0VfU0hBRE9XTUFQICkgJiYgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgPiAwXG5cdFBvaW50TGlnaHRTaGFkb3cgcG9pbnRMaWdodFNoYWRvdztcblx0I2VuZGlmXG5cdCNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnRcblx0Zm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1BPSU5UX0xJR0hUUzsgaSArKyApIHtcblx0XHRwb2ludExpZ2h0ID0gcG9pbnRMaWdodHNbIGkgXTtcblx0XHRnZXRQb2ludExpZ2h0SW5mbyggcG9pbnRMaWdodCwgZ2VvbWV0cnksIGRpcmVjdExpZ2h0ICk7XG5cdFx0I2lmIGRlZmluZWQoIFVTRV9TSEFET1dNQVAgKSAmJiAoIFVOUk9MTEVEX0xPT1BfSU5ERVggPCBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyApXG5cdFx0cG9pbnRMaWdodFNoYWRvdyA9IHBvaW50TGlnaHRTaGFkb3dzWyBpIF07XG5cdFx0ZGlyZWN0TGlnaHQuY29sb3IgKj0gYWxsKCBidmVjMiggZGlyZWN0TGlnaHQudmlzaWJsZSwgcmVjZWl2ZVNoYWRvdyApICkgPyBnZXRQb2ludFNoYWRvdyggcG9pbnRTaGFkb3dNYXBbIGkgXSwgcG9pbnRMaWdodFNoYWRvdy5zaGFkb3dNYXBTaXplLCBwb2ludExpZ2h0U2hhZG93LnNoYWRvd0JpYXMsIHBvaW50TGlnaHRTaGFkb3cuc2hhZG93UmFkaXVzLCB2UG9pbnRTaGFkb3dDb29yZFsgaSBdLCBwb2ludExpZ2h0U2hhZG93LnNoYWRvd0NhbWVyYU5lYXIsIHBvaW50TGlnaHRTaGFkb3cuc2hhZG93Q2FtZXJhRmFyICkgOiAxLjA7XG5cdFx0I2VuZGlmXG5cdFx0UkVfRGlyZWN0KCBkaXJlY3RMaWdodCwgZ2VvbWV0cnksIG1hdGVyaWFsLCByZWZsZWN0ZWRMaWdodCApO1xuXHR9XG5cdCNwcmFnbWEgdW5yb2xsX2xvb3BfZW5kXG4jZW5kaWZcbiNpZiAoIE5VTV9TUE9UX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3QgKVxuXHRTcG90TGlnaHQgc3BvdExpZ2h0O1xuXHQjaWYgZGVmaW5lZCggVVNFX1NIQURPV01BUCApICYmIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwXG5cdFNwb3RMaWdodFNoYWRvdyBzcG90TGlnaHRTaGFkb3c7XG5cdCNlbmRpZlxuXHQjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0XG5cdGZvciAoIGludCBpID0gMDsgaSA8IE5VTV9TUE9UX0xJR0hUUzsgaSArKyApIHtcblx0XHRzcG90TGlnaHQgPSBzcG90TGlnaHRzWyBpIF07XG5cdFx0Z2V0U3BvdExpZ2h0SW5mbyggc3BvdExpZ2h0LCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTtcblx0XHQjaWYgZGVmaW5lZCggVVNFX1NIQURPV01BUCApICYmICggVU5ST0xMRURfTE9PUF9JTkRFWCA8IE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgKVxuXHRcdHNwb3RMaWdodFNoYWRvdyA9IHNwb3RMaWdodFNoYWRvd3NbIGkgXTtcblx0XHRkaXJlY3RMaWdodC5jb2xvciAqPSBhbGwoIGJ2ZWMyKCBkaXJlY3RMaWdodC52aXNpYmxlLCByZWNlaXZlU2hhZG93ICkgKSA/IGdldFNoYWRvdyggc3BvdFNoYWRvd01hcFsgaSBdLCBzcG90TGlnaHRTaGFkb3cuc2hhZG93TWFwU2l6ZSwgc3BvdExpZ2h0U2hhZG93LnNoYWRvd0JpYXMsIHNwb3RMaWdodFNoYWRvdy5zaGFkb3dSYWRpdXMsIHZTcG90U2hhZG93Q29vcmRbIGkgXSApIDogMS4wO1xuXHRcdCNlbmRpZlxuXHRcdFJFX0RpcmVjdCggZGlyZWN0TGlnaHQsIGdlb21ldHJ5LCBtYXRlcmlhbCwgcmVmbGVjdGVkTGlnaHQgKTtcblx0fVxuXHQjcHJhZ21hIHVucm9sbF9sb29wX2VuZFxuI2VuZGlmXG4jaWYgKCBOVU1fRElSX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3QgKVxuXHREaXJlY3Rpb25hbExpZ2h0IGRpcmVjdGlvbmFsTGlnaHQ7XG5cdCNpZiBkZWZpbmVkKCBVU0VfU0hBRE9XTUFQICkgJiYgTlVNX0RJUl9MSUdIVF9TSEFET1dTID4gMFxuXHREaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3c7XG5cdCNlbmRpZlxuXHQjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0XG5cdGZvciAoIGludCBpID0gMDsgaSA8IE5VTV9ESVJfTElHSFRTOyBpICsrICkge1xuXHRcdGRpcmVjdGlvbmFsTGlnaHQgPSBkaXJlY3Rpb25hbExpZ2h0c1sgaSBdO1xuXHRcdGdldERpcmVjdGlvbmFsTGlnaHRJbmZvKCBkaXJlY3Rpb25hbExpZ2h0LCBnZW9tZXRyeSwgZGlyZWN0TGlnaHQgKTtcblx0XHQjaWYgZGVmaW5lZCggVVNFX1NIQURPV01BUCApICYmICggVU5ST0xMRURfTE9PUF9JTkRFWCA8IE5VTV9ESVJfTElHSFRfU0hBRE9XUyApXG5cdFx0ZGlyZWN0aW9uYWxMaWdodFNoYWRvdyA9IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzWyBpIF07XG5cdFx0ZGlyZWN0TGlnaHQuY29sb3IgKj0gYWxsKCBidmVjMiggZGlyZWN0TGlnaHQudmlzaWJsZSwgcmVjZWl2ZVNoYWRvdyApICkgPyBnZXRTaGFkb3coIGRpcmVjdGlvbmFsU2hhZG93TWFwWyBpIF0sIGRpcmVjdGlvbmFsTGlnaHRTaGFkb3cuc2hhZG93TWFwU2l6ZSwgZGlyZWN0aW9uYWxMaWdodFNoYWRvdy5zaGFkb3dCaWFzLCBkaXJlY3Rpb25hbExpZ2h0U2hhZG93LnNoYWRvd1JhZGl1cywgdkRpcmVjdGlvbmFsU2hhZG93Q29vcmRbIGkgXSApIDogMS4wO1xuXHRcdCNlbmRpZlxuXHRcdFJFX0RpcmVjdCggZGlyZWN0TGlnaHQsIGdlb21ldHJ5LCBtYXRlcmlhbCwgcmVmbGVjdGVkTGlnaHQgKTtcblx0fVxuXHQjcHJhZ21hIHVucm9sbF9sb29wX2VuZFxuI2VuZGlmXG4jaWYgKCBOVU1fUkVDVF9BUkVBX0xJR0hUUyA+IDAgKSAmJiBkZWZpbmVkKCBSRV9EaXJlY3RfUmVjdEFyZWEgKVxuXHRSZWN0QXJlYUxpZ2h0IHJlY3RBcmVhTGlnaHQ7XG5cdCNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnRcblx0Zm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1JFQ1RfQVJFQV9MSUdIVFM7IGkgKysgKSB7XG5cdFx0cmVjdEFyZWFMaWdodCA9IHJlY3RBcmVhTGlnaHRzWyBpIF07XG5cdFx0UkVfRGlyZWN0X1JlY3RBcmVhKCByZWN0QXJlYUxpZ2h0LCBnZW9tZXRyeSwgbWF0ZXJpYWwsIHJlZmxlY3RlZExpZ2h0ICk7XG5cdH1cblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9lbmRcbiNlbmRpZlxuI2lmIGRlZmluZWQoIFJFX0luZGlyZWN0RGlmZnVzZSApXG5cdHZlYzMgaWJsSXJyYWRpYW5jZSA9IHZlYzMoIDAuMCApO1xuXHR2ZWMzIGlycmFkaWFuY2UgPSBnZXRBbWJpZW50TGlnaHRJcnJhZGlhbmNlKCBhbWJpZW50TGlnaHRDb2xvciApO1xuXHRpcnJhZGlhbmNlICs9IGdldExpZ2h0UHJvYmVJcnJhZGlhbmNlKCBsaWdodFByb2JlLCBnZW9tZXRyeS5ub3JtYWwgKTtcblx0I2lmICggTlVNX0hFTUlfTElHSFRTID4gMCApXG5cdFx0I3ByYWdtYSB1bnJvbGxfbG9vcF9zdGFydFxuXHRcdGZvciAoIGludCBpID0gMDsgaSA8IE5VTV9IRU1JX0xJR0hUUzsgaSArKyApIHtcblx0XHRcdGlycmFkaWFuY2UgKz0gZ2V0SGVtaXNwaGVyZUxpZ2h0SXJyYWRpYW5jZSggaGVtaXNwaGVyZUxpZ2h0c1sgaSBdLCBnZW9tZXRyeS5ub3JtYWwgKTtcblx0XHR9XG5cdFx0I3ByYWdtYSB1bnJvbGxfbG9vcF9lbmRcblx0I2VuZGlmXG4jZW5kaWZcbiNpZiBkZWZpbmVkKCBSRV9JbmRpcmVjdFNwZWN1bGFyIClcblx0dmVjMyByYWRpYW5jZSA9IHZlYzMoIDAuMCApO1xuXHR2ZWMzIGNsZWFyY29hdFJhZGlhbmNlID0gdmVjMyggMC4wICk7XG4jZW5kaWYiLGxpZ2h0c19mcmFnbWVudF9tYXBzOiIjaWYgZGVmaW5lZCggUkVfSW5kaXJlY3REaWZmdXNlIClcblx0I2lmZGVmIFVTRV9MSUdIVE1BUFxuXHRcdHZlYzQgbGlnaHRNYXBUZXhlbCA9IHRleHR1cmUyRCggbGlnaHRNYXAsIHZVdjIgKTtcblx0XHR2ZWMzIGxpZ2h0TWFwSXJyYWRpYW5jZSA9IGxpZ2h0TWFwVGV4ZWwucmdiICogbGlnaHRNYXBJbnRlbnNpdHk7XG5cdFx0I2lmbmRlZiBQSFlTSUNBTExZX0NPUlJFQ1RfTElHSFRTXG5cdFx0XHRsaWdodE1hcElycmFkaWFuY2UgKj0gUEk7XG5cdFx0I2VuZGlmXG5cdFx0aXJyYWRpYW5jZSArPSBsaWdodE1hcElycmFkaWFuY2U7XG5cdCNlbmRpZlxuXHQjaWYgZGVmaW5lZCggVVNFX0VOVk1BUCApICYmIGRlZmluZWQoIFNUQU5EQVJEICkgJiYgZGVmaW5lZCggRU5WTUFQX1RZUEVfQ1VCRV9VViApXG5cdFx0aWJsSXJyYWRpYW5jZSArPSBnZXRJQkxJcnJhZGlhbmNlKCBnZW9tZXRyeS5ub3JtYWwgKTtcblx0I2VuZGlmXG4jZW5kaWZcbiNpZiBkZWZpbmVkKCBVU0VfRU5WTUFQICkgJiYgZGVmaW5lZCggUkVfSW5kaXJlY3RTcGVjdWxhciApXG5cdHJhZGlhbmNlICs9IGdldElCTFJhZGlhbmNlKCBnZW9tZXRyeS52aWV3RGlyLCBnZW9tZXRyeS5ub3JtYWwsIG1hdGVyaWFsLnJvdWdobmVzcyApO1xuXHQjaWZkZWYgVVNFX0NMRUFSQ09BVFxuXHRcdGNsZWFyY29hdFJhZGlhbmNlICs9IGdldElCTFJhZGlhbmNlKCBnZW9tZXRyeS52aWV3RGlyLCBnZW9tZXRyeS5jbGVhcmNvYXROb3JtYWwsIG1hdGVyaWFsLmNsZWFyY29hdFJvdWdobmVzcyApO1xuXHQjZW5kaWZcbiNlbmRpZiIsbGlnaHRzX2ZyYWdtZW50X2VuZDoiI2lmIGRlZmluZWQoIFJFX0luZGlyZWN0RGlmZnVzZSApXG5cdFJFX0luZGlyZWN0RGlmZnVzZSggaXJyYWRpYW5jZSwgZ2VvbWV0cnksIG1hdGVyaWFsLCByZWZsZWN0ZWRMaWdodCApO1xuI2VuZGlmXG4jaWYgZGVmaW5lZCggUkVfSW5kaXJlY3RTcGVjdWxhciApXG5cdFJFX0luZGlyZWN0U3BlY3VsYXIoIHJhZGlhbmNlLCBpYmxJcnJhZGlhbmNlLCBjbGVhcmNvYXRSYWRpYW5jZSwgZ2VvbWV0cnksIG1hdGVyaWFsLCByZWZsZWN0ZWRMaWdodCApO1xuI2VuZGlmIixsb2dkZXB0aGJ1Zl9mcmFnbWVudDoiI2lmIGRlZmluZWQoIFVTRV9MT0dERVBUSEJVRiApICYmIGRlZmluZWQoIFVTRV9MT0dERVBUSEJVRl9FWFQgKVxuXHRnbF9GcmFnRGVwdGhFWFQgPSB2SXNQZXJzcGVjdGl2ZSA9PSAwLjAgPyBnbF9GcmFnQ29vcmQueiA6IGxvZzIoIHZGcmFnRGVwdGggKSAqIGxvZ0RlcHRoQnVmRkMgKiAwLjU7XG4jZW5kaWYiLGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ6IiNpZiBkZWZpbmVkKCBVU0VfTE9HREVQVEhCVUYgKSAmJiBkZWZpbmVkKCBVU0VfTE9HREVQVEhCVUZfRVhUIClcblx0dW5pZm9ybSBmbG9hdCBsb2dEZXB0aEJ1ZkZDO1xuXHR2YXJ5aW5nIGZsb2F0IHZGcmFnRGVwdGg7XG5cdHZhcnlpbmcgZmxvYXQgdklzUGVyc3BlY3RpdmU7XG4jZW5kaWYiLGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4OiIjaWZkZWYgVVNFX0xPR0RFUFRIQlVGXG5cdCNpZmRlZiBVU0VfTE9HREVQVEhCVUZfRVhUXG5cdFx0dmFyeWluZyBmbG9hdCB2RnJhZ0RlcHRoO1xuXHRcdHZhcnlpbmcgZmxvYXQgdklzUGVyc3BlY3RpdmU7XG5cdCNlbHNlXG5cdFx0dW5pZm9ybSBmbG9hdCBsb2dEZXB0aEJ1ZkZDO1xuXHQjZW5kaWZcbiNlbmRpZiIsbG9nZGVwdGhidWZfdmVydGV4OiIjaWZkZWYgVVNFX0xPR0RFUFRIQlVGXG5cdCNpZmRlZiBVU0VfTE9HREVQVEhCVUZfRVhUXG5cdFx0dkZyYWdEZXB0aCA9IDEuMCArIGdsX1Bvc2l0aW9uLnc7XG5cdFx0dklzUGVyc3BlY3RpdmUgPSBmbG9hdCggaXNQZXJzcGVjdGl2ZU1hdHJpeCggcHJvamVjdGlvbk1hdHJpeCApICk7XG5cdCNlbHNlXG5cdFx0aWYgKCBpc1BlcnNwZWN0aXZlTWF0cml4KCBwcm9qZWN0aW9uTWF0cml4ICkgKSB7XG5cdFx0XHRnbF9Qb3NpdGlvbi56ID0gbG9nMiggbWF4KCBFUFNJTE9OLCBnbF9Qb3NpdGlvbi53ICsgMS4wICkgKSAqIGxvZ0RlcHRoQnVmRkMgLSAxLjA7XG5cdFx0XHRnbF9Qb3NpdGlvbi56ICo9IGdsX1Bvc2l0aW9uLnc7XG5cdFx0fVxuXHQjZW5kaWZcbiNlbmRpZiIsbWFwX2ZyYWdtZW50OiIjaWZkZWYgVVNFX01BUFxuXHR2ZWM0IHNhbXBsZWREaWZmdXNlQ29sb3IgPSB0ZXh0dXJlMkQoIG1hcCwgdlV2ICk7XG5cdCNpZmRlZiBERUNPREVfVklERU9fVEVYVFVSRVxuXHRcdHNhbXBsZWREaWZmdXNlQ29sb3IgPSB2ZWM0KCBtaXgoIHBvdyggc2FtcGxlZERpZmZ1c2VDb2xvci5yZ2IgKiAwLjk0Nzg2NzI5ODYgKyB2ZWMzKCAwLjA1MjEzMjcwMTQgKSwgdmVjMyggMi40ICkgKSwgc2FtcGxlZERpZmZ1c2VDb2xvci5yZ2IgKiAwLjA3NzM5OTM4MDgsIHZlYzMoIGxlc3NUaGFuRXF1YWwoIHNhbXBsZWREaWZmdXNlQ29sb3IucmdiLCB2ZWMzKCAwLjA0MDQ1ICkgKSApICksIHNhbXBsZWREaWZmdXNlQ29sb3IudyApO1xuXHQjZW5kaWZcblx0ZGlmZnVzZUNvbG9yICo9IHNhbXBsZWREaWZmdXNlQ29sb3I7XG4jZW5kaWYiLG1hcF9wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX01BUFxuXHR1bmlmb3JtIHNhbXBsZXIyRCBtYXA7XG4jZW5kaWYiLG1hcF9wYXJ0aWNsZV9mcmFnbWVudDoiI2lmIGRlZmluZWQoIFVTRV9NQVAgKSB8fCBkZWZpbmVkKCBVU0VfQUxQSEFNQVAgKVxuXHR2ZWMyIHV2ID0gKCB1dlRyYW5zZm9ybSAqIHZlYzMoIGdsX1BvaW50Q29vcmQueCwgMS4wIC0gZ2xfUG9pbnRDb29yZC55LCAxICkgKS54eTtcbiNlbmRpZlxuI2lmZGVmIFVTRV9NQVBcblx0ZGlmZnVzZUNvbG9yICo9IHRleHR1cmUyRCggbWFwLCB1diApO1xuI2VuZGlmXG4jaWZkZWYgVVNFX0FMUEhBTUFQXG5cdGRpZmZ1c2VDb2xvci5hICo9IHRleHR1cmUyRCggYWxwaGFNYXAsIHV2ICkuZztcbiNlbmRpZiIsbWFwX3BhcnRpY2xlX3BhcnNfZnJhZ21lbnQ6IiNpZiBkZWZpbmVkKCBVU0VfTUFQICkgfHwgZGVmaW5lZCggVVNFX0FMUEhBTUFQIClcblx0dW5pZm9ybSBtYXQzIHV2VHJhbnNmb3JtO1xuI2VuZGlmXG4jaWZkZWYgVVNFX01BUFxuXHR1bmlmb3JtIHNhbXBsZXIyRCBtYXA7XG4jZW5kaWZcbiNpZmRlZiBVU0VfQUxQSEFNQVBcblx0dW5pZm9ybSBzYW1wbGVyMkQgYWxwaGFNYXA7XG4jZW5kaWYiLG1ldGFsbmVzc21hcF9mcmFnbWVudDoiZmxvYXQgbWV0YWxuZXNzRmFjdG9yID0gbWV0YWxuZXNzO1xuI2lmZGVmIFVTRV9NRVRBTE5FU1NNQVBcblx0dmVjNCB0ZXhlbE1ldGFsbmVzcyA9IHRleHR1cmUyRCggbWV0YWxuZXNzTWFwLCB2VXYgKTtcblx0bWV0YWxuZXNzRmFjdG9yICo9IHRleGVsTWV0YWxuZXNzLmI7XG4jZW5kaWYiLG1ldGFsbmVzc21hcF9wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX01FVEFMTkVTU01BUFxuXHR1bmlmb3JtIHNhbXBsZXIyRCBtZXRhbG5lc3NNYXA7XG4jZW5kaWYiLG1vcnBobm9ybWFsX3ZlcnRleDoiI2lmZGVmIFVTRV9NT1JQSE5PUk1BTFNcblx0b2JqZWN0Tm9ybWFsICo9IG1vcnBoVGFyZ2V0QmFzZUluZmx1ZW5jZTtcblx0I2lmZGVmIE1PUlBIVEFSR0VUU19URVhUVVJFXG5cdFx0Zm9yICggaW50IGkgPSAwOyBpIDwgTU9SUEhUQVJHRVRTX0NPVU5UOyBpICsrICkge1xuXHRcdFx0aWYgKCBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIGkgXSAhPSAwLjAgKSBvYmplY3ROb3JtYWwgKz0gZ2V0TW9ycGgoIGdsX1ZlcnRleElELCBpLCAxLCAyICkgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIGkgXTtcblx0XHR9XG5cdCNlbHNlXG5cdFx0b2JqZWN0Tm9ybWFsICs9IG1vcnBoTm9ybWFsMCAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgMCBdO1xuXHRcdG9iamVjdE5vcm1hbCArPSBtb3JwaE5vcm1hbDEgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDEgXTtcblx0XHRvYmplY3ROb3JtYWwgKz0gbW9ycGhOb3JtYWwyICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyAyIF07XG5cdFx0b2JqZWN0Tm9ybWFsICs9IG1vcnBoTm9ybWFsMyAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgMyBdO1xuXHQjZW5kaWZcbiNlbmRpZiIsbW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg6IiNpZmRlZiBVU0VfTU9SUEhUQVJHRVRTXG5cdHVuaWZvcm0gZmxvYXQgbW9ycGhUYXJnZXRCYXNlSW5mbHVlbmNlO1xuXHQjaWZkZWYgTU9SUEhUQVJHRVRTX1RFWFRVUkVcblx0XHR1bmlmb3JtIGZsb2F0IG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgTU9SUEhUQVJHRVRTX0NPVU5UIF07XG5cdFx0dW5pZm9ybSBzYW1wbGVyMkRBcnJheSBtb3JwaFRhcmdldHNUZXh0dXJlO1xuXHRcdHVuaWZvcm0gdmVjMiBtb3JwaFRhcmdldHNUZXh0dXJlU2l6ZTtcblx0XHR2ZWMzIGdldE1vcnBoKCBjb25zdCBpbiBpbnQgdmVydGV4SW5kZXgsIGNvbnN0IGluIGludCBtb3JwaFRhcmdldEluZGV4LCBjb25zdCBpbiBpbnQgb2Zmc2V0LCBjb25zdCBpbiBpbnQgc3RyaWRlICkge1xuXHRcdFx0ZmxvYXQgdGV4ZWxJbmRleCA9IGZsb2F0KCB2ZXJ0ZXhJbmRleCAqIHN0cmlkZSArIG9mZnNldCApO1xuXHRcdFx0ZmxvYXQgeSA9IGZsb29yKCB0ZXhlbEluZGV4IC8gbW9ycGhUYXJnZXRzVGV4dHVyZVNpemUueCApO1xuXHRcdFx0ZmxvYXQgeCA9IHRleGVsSW5kZXggLSB5ICogbW9ycGhUYXJnZXRzVGV4dHVyZVNpemUueDtcblx0XHRcdHZlYzMgbW9ycGhVViA9IHZlYzMoICggeCArIDAuNSApIC8gbW9ycGhUYXJnZXRzVGV4dHVyZVNpemUueCwgeSAvIG1vcnBoVGFyZ2V0c1RleHR1cmVTaXplLnksIG1vcnBoVGFyZ2V0SW5kZXggKTtcblx0XHRcdHJldHVybiB0ZXh0dXJlKCBtb3JwaFRhcmdldHNUZXh0dXJlLCBtb3JwaFVWICkueHl6O1xuXHRcdH1cblx0I2Vsc2Vcblx0XHQjaWZuZGVmIFVTRV9NT1JQSE5PUk1BTFNcblx0XHRcdHVuaWZvcm0gZmxvYXQgbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyA4IF07XG5cdFx0I2Vsc2Vcblx0XHRcdHVuaWZvcm0gZmxvYXQgbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyA0IF07XG5cdFx0I2VuZGlmXG5cdCNlbmRpZlxuI2VuZGlmIixtb3JwaHRhcmdldF92ZXJ0ZXg6IiNpZmRlZiBVU0VfTU9SUEhUQVJHRVRTXG5cdHRyYW5zZm9ybWVkICo9IG1vcnBoVGFyZ2V0QmFzZUluZmx1ZW5jZTtcblx0I2lmZGVmIE1PUlBIVEFSR0VUU19URVhUVVJFXG5cdFx0Zm9yICggaW50IGkgPSAwOyBpIDwgTU9SUEhUQVJHRVRTX0NPVU5UOyBpICsrICkge1xuXHRcdFx0I2lmbmRlZiBVU0VfTU9SUEhOT1JNQUxTXG5cdFx0XHRcdGlmICggbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyBpIF0gIT0gMC4wICkgdHJhbnNmb3JtZWQgKz0gZ2V0TW9ycGgoIGdsX1ZlcnRleElELCBpLCAwLCAxICkgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIGkgXTtcblx0XHRcdCNlbHNlXG5cdFx0XHRcdGlmICggbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyBpIF0gIT0gMC4wICkgdHJhbnNmb3JtZWQgKz0gZ2V0TW9ycGgoIGdsX1ZlcnRleElELCBpLCAwLCAyICkgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIGkgXTtcblx0XHRcdCNlbmRpZlxuXHRcdH1cblx0I2Vsc2Vcblx0XHR0cmFuc2Zvcm1lZCArPSBtb3JwaFRhcmdldDAgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDAgXTtcblx0XHR0cmFuc2Zvcm1lZCArPSBtb3JwaFRhcmdldDEgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDEgXTtcblx0XHR0cmFuc2Zvcm1lZCArPSBtb3JwaFRhcmdldDIgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDIgXTtcblx0XHR0cmFuc2Zvcm1lZCArPSBtb3JwaFRhcmdldDMgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDMgXTtcblx0XHQjaWZuZGVmIFVTRV9NT1JQSE5PUk1BTFNcblx0XHRcdHRyYW5zZm9ybWVkICs9IG1vcnBoVGFyZ2V0NCAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgNCBdO1xuXHRcdFx0dHJhbnNmb3JtZWQgKz0gbW9ycGhUYXJnZXQ1ICogbW9ycGhUYXJnZXRJbmZsdWVuY2VzWyA1IF07XG5cdFx0XHR0cmFuc2Zvcm1lZCArPSBtb3JwaFRhcmdldDYgKiBtb3JwaFRhcmdldEluZmx1ZW5jZXNbIDYgXTtcblx0XHRcdHRyYW5zZm9ybWVkICs9IG1vcnBoVGFyZ2V0NyAqIG1vcnBoVGFyZ2V0SW5mbHVlbmNlc1sgNyBdO1xuXHRcdCNlbmRpZlxuXHQjZW5kaWZcbiNlbmRpZiIsbm9ybWFsX2ZyYWdtZW50X2JlZ2luOiJmbG9hdCBmYWNlRGlyZWN0aW9uID0gZ2xfRnJvbnRGYWNpbmcgPyAxLjAgOiAtIDEuMDtcbiNpZmRlZiBGTEFUX1NIQURFRFxuXHR2ZWMzIGZkeCA9IHZlYzMoIGRGZHgoIHZWaWV3UG9zaXRpb24ueCApLCBkRmR4KCB2Vmlld1Bvc2l0aW9uLnkgKSwgZEZkeCggdlZpZXdQb3NpdGlvbi56ICkgKTtcblx0dmVjMyBmZHkgPSB2ZWMzKCBkRmR5KCB2Vmlld1Bvc2l0aW9uLnggKSwgZEZkeSggdlZpZXdQb3NpdGlvbi55ICksIGRGZHkoIHZWaWV3UG9zaXRpb24ueiApICk7XG5cdHZlYzMgbm9ybWFsID0gbm9ybWFsaXplKCBjcm9zcyggZmR4LCBmZHkgKSApO1xuI2Vsc2Vcblx0dmVjMyBub3JtYWwgPSBub3JtYWxpemUoIHZOb3JtYWwgKTtcblx0I2lmZGVmIERPVUJMRV9TSURFRFxuXHRcdG5vcm1hbCA9IG5vcm1hbCAqIGZhY2VEaXJlY3Rpb247XG5cdCNlbmRpZlxuXHQjaWZkZWYgVVNFX1RBTkdFTlRcblx0XHR2ZWMzIHRhbmdlbnQgPSBub3JtYWxpemUoIHZUYW5nZW50ICk7XG5cdFx0dmVjMyBiaXRhbmdlbnQgPSBub3JtYWxpemUoIHZCaXRhbmdlbnQgKTtcblx0XHQjaWZkZWYgRE9VQkxFX1NJREVEXG5cdFx0XHR0YW5nZW50ID0gdGFuZ2VudCAqIGZhY2VEaXJlY3Rpb247XG5cdFx0XHRiaXRhbmdlbnQgPSBiaXRhbmdlbnQgKiBmYWNlRGlyZWN0aW9uO1xuXHRcdCNlbmRpZlxuXHRcdCNpZiBkZWZpbmVkKCBUQU5HRU5UU1BBQ0VfTk9STUFMTUFQICkgfHwgZGVmaW5lZCggVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVAgKVxuXHRcdFx0bWF0MyB2VEJOID0gbWF0MyggdGFuZ2VudCwgYml0YW5nZW50LCBub3JtYWwgKTtcblx0XHQjZW5kaWZcblx0I2VuZGlmXG4jZW5kaWZcbnZlYzMgZ2VvbWV0cnlOb3JtYWwgPSBub3JtYWw7Iixub3JtYWxfZnJhZ21lbnRfbWFwczoiI2lmZGVmIE9CSkVDVFNQQUNFX05PUk1BTE1BUFxuXHRub3JtYWwgPSB0ZXh0dXJlMkQoIG5vcm1hbE1hcCwgdlV2ICkueHl6ICogMi4wIC0gMS4wO1xuXHQjaWZkZWYgRkxJUF9TSURFRFxuXHRcdG5vcm1hbCA9IC0gbm9ybWFsO1xuXHQjZW5kaWZcblx0I2lmZGVmIERPVUJMRV9TSURFRFxuXHRcdG5vcm1hbCA9IG5vcm1hbCAqIGZhY2VEaXJlY3Rpb247XG5cdCNlbmRpZlxuXHRub3JtYWwgPSBub3JtYWxpemUoIG5vcm1hbE1hdHJpeCAqIG5vcm1hbCApO1xuI2VsaWYgZGVmaW5lZCggVEFOR0VOVFNQQUNFX05PUk1BTE1BUCApXG5cdHZlYzMgbWFwTiA9IHRleHR1cmUyRCggbm9ybWFsTWFwLCB2VXYgKS54eXogKiAyLjAgLSAxLjA7XG5cdG1hcE4ueHkgKj0gbm9ybWFsU2NhbGU7XG5cdCNpZmRlZiBVU0VfVEFOR0VOVFxuXHRcdG5vcm1hbCA9IG5vcm1hbGl6ZSggdlRCTiAqIG1hcE4gKTtcblx0I2Vsc2Vcblx0XHRub3JtYWwgPSBwZXJ0dXJiTm9ybWFsMkFyYiggLSB2Vmlld1Bvc2l0aW9uLCBub3JtYWwsIG1hcE4sIGZhY2VEaXJlY3Rpb24gKTtcblx0I2VuZGlmXG4jZWxpZiBkZWZpbmVkKCBVU0VfQlVNUE1BUCApXG5cdG5vcm1hbCA9IHBlcnR1cmJOb3JtYWxBcmIoIC0gdlZpZXdQb3NpdGlvbiwgbm9ybWFsLCBkSGR4eV9md2QoKSwgZmFjZURpcmVjdGlvbiApO1xuI2VuZGlmIixub3JtYWxfcGFyc19mcmFnbWVudDoiI2lmbmRlZiBGTEFUX1NIQURFRFxuXHR2YXJ5aW5nIHZlYzMgdk5vcm1hbDtcblx0I2lmZGVmIFVTRV9UQU5HRU5UXG5cdFx0dmFyeWluZyB2ZWMzIHZUYW5nZW50O1xuXHRcdHZhcnlpbmcgdmVjMyB2Qml0YW5nZW50O1xuXHQjZW5kaWZcbiNlbmRpZiIsbm9ybWFsX3BhcnNfdmVydGV4OiIjaWZuZGVmIEZMQVRfU0hBREVEXG5cdHZhcnlpbmcgdmVjMyB2Tm9ybWFsO1xuXHQjaWZkZWYgVVNFX1RBTkdFTlRcblx0XHR2YXJ5aW5nIHZlYzMgdlRhbmdlbnQ7XG5cdFx0dmFyeWluZyB2ZWMzIHZCaXRhbmdlbnQ7XG5cdCNlbmRpZlxuI2VuZGlmIixub3JtYWxfdmVydGV4OiIjaWZuZGVmIEZMQVRfU0hBREVEXG5cdHZOb3JtYWwgPSBub3JtYWxpemUoIHRyYW5zZm9ybWVkTm9ybWFsICk7XG5cdCNpZmRlZiBVU0VfVEFOR0VOVFxuXHRcdHZUYW5nZW50ID0gbm9ybWFsaXplKCB0cmFuc2Zvcm1lZFRhbmdlbnQgKTtcblx0XHR2Qml0YW5nZW50ID0gbm9ybWFsaXplKCBjcm9zcyggdk5vcm1hbCwgdlRhbmdlbnQgKSAqIHRhbmdlbnQudyApO1xuXHQjZW5kaWZcbiNlbmRpZiIsbm9ybWFsbWFwX3BhcnNfZnJhZ21lbnQ6IiNpZmRlZiBVU0VfTk9STUFMTUFQXG5cdHVuaWZvcm0gc2FtcGxlcjJEIG5vcm1hbE1hcDtcblx0dW5pZm9ybSB2ZWMyIG5vcm1hbFNjYWxlO1xuI2VuZGlmXG4jaWZkZWYgT0JKRUNUU1BBQ0VfTk9STUFMTUFQXG5cdHVuaWZvcm0gbWF0MyBub3JtYWxNYXRyaXg7XG4jZW5kaWZcbiNpZiAhIGRlZmluZWQgKCBVU0VfVEFOR0VOVCApICYmICggZGVmaW5lZCAoIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAgKSB8fCBkZWZpbmVkICggVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVAgKSApXG5cdHZlYzMgcGVydHVyYk5vcm1hbDJBcmIoIHZlYzMgZXllX3BvcywgdmVjMyBzdXJmX25vcm0sIHZlYzMgbWFwTiwgZmxvYXQgZmFjZURpcmVjdGlvbiApIHtcblx0XHR2ZWMzIHEwID0gdmVjMyggZEZkeCggZXllX3Bvcy54ICksIGRGZHgoIGV5ZV9wb3MueSApLCBkRmR4KCBleWVfcG9zLnogKSApO1xuXHRcdHZlYzMgcTEgPSB2ZWMzKCBkRmR5KCBleWVfcG9zLnggKSwgZEZkeSggZXllX3Bvcy55ICksIGRGZHkoIGV5ZV9wb3MueiApICk7XG5cdFx0dmVjMiBzdDAgPSBkRmR4KCB2VXYuc3QgKTtcblx0XHR2ZWMyIHN0MSA9IGRGZHkoIHZVdi5zdCApO1xuXHRcdHZlYzMgTiA9IHN1cmZfbm9ybTtcblx0XHR2ZWMzIHExcGVycCA9IGNyb3NzKCBxMSwgTiApO1xuXHRcdHZlYzMgcTBwZXJwID0gY3Jvc3MoIE4sIHEwICk7XG5cdFx0dmVjMyBUID0gcTFwZXJwICogc3QwLnggKyBxMHBlcnAgKiBzdDEueDtcblx0XHR2ZWMzIEIgPSBxMXBlcnAgKiBzdDAueSArIHEwcGVycCAqIHN0MS55O1xuXHRcdGZsb2F0IGRldCA9IG1heCggZG90KCBULCBUICksIGRvdCggQiwgQiApICk7XG5cdFx0ZmxvYXQgc2NhbGUgPSAoIGRldCA9PSAwLjAgKSA/IDAuMCA6IGZhY2VEaXJlY3Rpb24gKiBpbnZlcnNlc3FydCggZGV0ICk7XG5cdFx0cmV0dXJuIG5vcm1hbGl6ZSggVCAqICggbWFwTi54ICogc2NhbGUgKSArIEIgKiAoIG1hcE4ueSAqIHNjYWxlICkgKyBOICogbWFwTi56ICk7XG5cdH1cbiNlbmRpZiIsY2xlYXJjb2F0X25vcm1hbF9mcmFnbWVudF9iZWdpbjoiI2lmZGVmIFVTRV9DTEVBUkNPQVRcblx0dmVjMyBjbGVhcmNvYXROb3JtYWwgPSBnZW9tZXRyeU5vcm1hbDtcbiNlbmRpZiIsY2xlYXJjb2F0X25vcm1hbF9mcmFnbWVudF9tYXBzOiIjaWZkZWYgVVNFX0NMRUFSQ09BVF9OT1JNQUxNQVBcblx0dmVjMyBjbGVhcmNvYXRNYXBOID0gdGV4dHVyZTJEKCBjbGVhcmNvYXROb3JtYWxNYXAsIHZVdiApLnh5eiAqIDIuMCAtIDEuMDtcblx0Y2xlYXJjb2F0TWFwTi54eSAqPSBjbGVhcmNvYXROb3JtYWxTY2FsZTtcblx0I2lmZGVmIFVTRV9UQU5HRU5UXG5cdFx0Y2xlYXJjb2F0Tm9ybWFsID0gbm9ybWFsaXplKCB2VEJOICogY2xlYXJjb2F0TWFwTiApO1xuXHQjZWxzZVxuXHRcdGNsZWFyY29hdE5vcm1hbCA9IHBlcnR1cmJOb3JtYWwyQXJiKCAtIHZWaWV3UG9zaXRpb24sIGNsZWFyY29hdE5vcm1hbCwgY2xlYXJjb2F0TWFwTiwgZmFjZURpcmVjdGlvbiApO1xuXHQjZW5kaWZcbiNlbmRpZiIsY2xlYXJjb2F0X3BhcnNfZnJhZ21lbnQ6IiNpZmRlZiBVU0VfQ0xFQVJDT0FUTUFQXG5cdHVuaWZvcm0gc2FtcGxlcjJEIGNsZWFyY29hdE1hcDtcbiNlbmRpZlxuI2lmZGVmIFVTRV9DTEVBUkNPQVRfUk9VR0hORVNTTUFQXG5cdHVuaWZvcm0gc2FtcGxlcjJEIGNsZWFyY29hdFJvdWdobmVzc01hcDtcbiNlbmRpZlxuI2lmZGVmIFVTRV9DTEVBUkNPQVRfTk9STUFMTUFQXG5cdHVuaWZvcm0gc2FtcGxlcjJEIGNsZWFyY29hdE5vcm1hbE1hcDtcblx0dW5pZm9ybSB2ZWMyIGNsZWFyY29hdE5vcm1hbFNjYWxlO1xuI2VuZGlmIixvdXRwdXRfZnJhZ21lbnQ6IiNpZmRlZiBPUEFRVUVcbmRpZmZ1c2VDb2xvci5hID0gMS4wO1xuI2VuZGlmXG4jaWZkZWYgVVNFX1RSQU5TTUlTU0lPTlxuZGlmZnVzZUNvbG9yLmEgKj0gdHJhbnNtaXNzaW9uQWxwaGEgKyAwLjE7XG4jZW5kaWZcbmdsX0ZyYWdDb2xvciA9IHZlYzQoIG91dGdvaW5nTGlnaHQsIGRpZmZ1c2VDb2xvci5hICk7IixwYWNraW5nOiJ2ZWMzIHBhY2tOb3JtYWxUb1JHQiggY29uc3QgaW4gdmVjMyBub3JtYWwgKSB7XG5cdHJldHVybiBub3JtYWxpemUoIG5vcm1hbCApICogMC41ICsgMC41O1xufVxudmVjMyB1bnBhY2tSR0JUb05vcm1hbCggY29uc3QgaW4gdmVjMyByZ2IgKSB7XG5cdHJldHVybiAyLjAgKiByZ2IueHl6IC0gMS4wO1xufVxuY29uc3QgZmxvYXQgUGFja1Vwc2NhbGUgPSAyNTYuIC8gMjU1Ljtjb25zdCBmbG9hdCBVbnBhY2tEb3duc2NhbGUgPSAyNTUuIC8gMjU2LjtcbmNvbnN0IHZlYzMgUGFja0ZhY3RvcnMgPSB2ZWMzKCAyNTYuICogMjU2LiAqIDI1Ni4sIDI1Ni4gKiAyNTYuLCAyNTYuICk7XG5jb25zdCB2ZWM0IFVucGFja0ZhY3RvcnMgPSBVbnBhY2tEb3duc2NhbGUgLyB2ZWM0KCBQYWNrRmFjdG9ycywgMS4gKTtcbmNvbnN0IGZsb2F0IFNoaWZ0UmlnaHQ4ID0gMS4gLyAyNTYuO1xudmVjNCBwYWNrRGVwdGhUb1JHQkEoIGNvbnN0IGluIGZsb2F0IHYgKSB7XG5cdHZlYzQgciA9IHZlYzQoIGZyYWN0KCB2ICogUGFja0ZhY3RvcnMgKSwgdiApO1xuXHRyLnl6dyAtPSByLnh5eiAqIFNoaWZ0UmlnaHQ4O1x0cmV0dXJuIHIgKiBQYWNrVXBzY2FsZTtcbn1cbmZsb2F0IHVucGFja1JHQkFUb0RlcHRoKCBjb25zdCBpbiB2ZWM0IHYgKSB7XG5cdHJldHVybiBkb3QoIHYsIFVucGFja0ZhY3RvcnMgKTtcbn1cbnZlYzQgcGFjazJIYWxmVG9SR0JBKCB2ZWMyIHYgKSB7XG5cdHZlYzQgciA9IHZlYzQoIHYueCwgZnJhY3QoIHYueCAqIDI1NS4wICksIHYueSwgZnJhY3QoIHYueSAqIDI1NS4wICkgKTtcblx0cmV0dXJuIHZlYzQoIHIueCAtIHIueSAvIDI1NS4wLCByLnksIHIueiAtIHIudyAvIDI1NS4wLCByLncgKTtcbn1cbnZlYzIgdW5wYWNrUkdCQVRvMkhhbGYoIHZlYzQgdiApIHtcblx0cmV0dXJuIHZlYzIoIHYueCArICggdi55IC8gMjU1LjAgKSwgdi56ICsgKCB2LncgLyAyNTUuMCApICk7XG59XG5mbG9hdCB2aWV3WlRvT3J0aG9ncmFwaGljRGVwdGgoIGNvbnN0IGluIGZsb2F0IHZpZXdaLCBjb25zdCBpbiBmbG9hdCBuZWFyLCBjb25zdCBpbiBmbG9hdCBmYXIgKSB7XG5cdHJldHVybiAoIHZpZXdaICsgbmVhciApIC8gKCBuZWFyIC0gZmFyICk7XG59XG5mbG9hdCBvcnRob2dyYXBoaWNEZXB0aFRvVmlld1ooIGNvbnN0IGluIGZsb2F0IGxpbmVhckNsaXBaLCBjb25zdCBpbiBmbG9hdCBuZWFyLCBjb25zdCBpbiBmbG9hdCBmYXIgKSB7XG5cdHJldHVybiBsaW5lYXJDbGlwWiAqICggbmVhciAtIGZhciApIC0gbmVhcjtcbn1cbmZsb2F0IHZpZXdaVG9QZXJzcGVjdGl2ZURlcHRoKCBjb25zdCBpbiBmbG9hdCB2aWV3WiwgY29uc3QgaW4gZmxvYXQgbmVhciwgY29uc3QgaW4gZmxvYXQgZmFyICkge1xuXHRyZXR1cm4gKCAoIG5lYXIgKyB2aWV3WiApICogZmFyICkgLyAoICggZmFyIC0gbmVhciApICogdmlld1ogKTtcbn1cbmZsb2F0IHBlcnNwZWN0aXZlRGVwdGhUb1ZpZXdaKCBjb25zdCBpbiBmbG9hdCBpbnZDbGlwWiwgY29uc3QgaW4gZmxvYXQgbmVhciwgY29uc3QgaW4gZmxvYXQgZmFyICkge1xuXHRyZXR1cm4gKCBuZWFyICogZmFyICkgLyAoICggZmFyIC0gbmVhciApICogaW52Q2xpcFogLSBmYXIgKTtcbn0iLHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ6IiNpZmRlZiBQUkVNVUxUSVBMSUVEX0FMUEhBXG5cdGdsX0ZyYWdDb2xvci5yZ2IgKj0gZ2xfRnJhZ0NvbG9yLmE7XG4jZW5kaWYiLHByb2plY3RfdmVydGV4OiJ2ZWM0IG12UG9zaXRpb24gPSB2ZWM0KCB0cmFuc2Zvcm1lZCwgMS4wICk7XG4jaWZkZWYgVVNFX0lOU1RBTkNJTkdcblx0bXZQb3NpdGlvbiA9IGluc3RhbmNlTWF0cml4ICogbXZQb3NpdGlvbjtcbiNlbmRpZlxubXZQb3NpdGlvbiA9IG1vZGVsVmlld01hdHJpeCAqIG12UG9zaXRpb247XG5nbF9Qb3NpdGlvbiA9IHByb2plY3Rpb25NYXRyaXggKiBtdlBvc2l0aW9uOyIsZGl0aGVyaW5nX2ZyYWdtZW50OiIjaWZkZWYgRElUSEVSSU5HXG5cdGdsX0ZyYWdDb2xvci5yZ2IgPSBkaXRoZXJpbmcoIGdsX0ZyYWdDb2xvci5yZ2IgKTtcbiNlbmRpZiIsZGl0aGVyaW5nX3BhcnNfZnJhZ21lbnQ6IiNpZmRlZiBESVRIRVJJTkdcblx0dmVjMyBkaXRoZXJpbmcoIHZlYzMgY29sb3IgKSB7XG5cdFx0ZmxvYXQgZ3JpZF9wb3NpdGlvbiA9IHJhbmQoIGdsX0ZyYWdDb29yZC54eSApO1xuXHRcdHZlYzMgZGl0aGVyX3NoaWZ0X1JHQiA9IHZlYzMoIDAuMjUgLyAyNTUuMCwgLTAuMjUgLyAyNTUuMCwgMC4yNSAvIDI1NS4wICk7XG5cdFx0ZGl0aGVyX3NoaWZ0X1JHQiA9IG1peCggMi4wICogZGl0aGVyX3NoaWZ0X1JHQiwgLTIuMCAqIGRpdGhlcl9zaGlmdF9SR0IsIGdyaWRfcG9zaXRpb24gKTtcblx0XHRyZXR1cm4gY29sb3IgKyBkaXRoZXJfc2hpZnRfUkdCO1xuXHR9XG4jZW5kaWYiLHJvdWdobmVzc21hcF9mcmFnbWVudDoiZmxvYXQgcm91Z2huZXNzRmFjdG9yID0gcm91Z2huZXNzO1xuI2lmZGVmIFVTRV9ST1VHSE5FU1NNQVBcblx0dmVjNCB0ZXhlbFJvdWdobmVzcyA9IHRleHR1cmUyRCggcm91Z2huZXNzTWFwLCB2VXYgKTtcblx0cm91Z2huZXNzRmFjdG9yICo9IHRleGVsUm91Z2huZXNzLmc7XG4jZW5kaWYiLHJvdWdobmVzc21hcF9wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX1JPVUdITkVTU01BUFxuXHR1bmlmb3JtIHNhbXBsZXIyRCByb3VnaG5lc3NNYXA7XG4jZW5kaWYiLHNoYWRvd21hcF9wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX1NIQURPV01BUFxuXHQjaWYgTlVNX0RJUl9MSUdIVF9TSEFET1dTID4gMFxuXHRcdHVuaWZvcm0gc2FtcGxlcjJEIGRpcmVjdGlvbmFsU2hhZG93TWFwWyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTtcblx0XHR2YXJ5aW5nIHZlYzQgdkRpcmVjdGlvbmFsU2hhZG93Q29vcmRbIE5VTV9ESVJfTElHSFRfU0hBRE9XUyBdO1xuXHRcdHN0cnVjdCBEaXJlY3Rpb25hbExpZ2h0U2hhZG93IHtcblx0XHRcdGZsb2F0IHNoYWRvd0JpYXM7XG5cdFx0XHRmbG9hdCBzaGFkb3dOb3JtYWxCaWFzO1xuXHRcdFx0ZmxvYXQgc2hhZG93UmFkaXVzO1xuXHRcdFx0dmVjMiBzaGFkb3dNYXBTaXplO1xuXHRcdH07XG5cdFx0dW5pZm9ybSBEaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHRTaGFkb3dzWyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTtcblx0I2VuZGlmXG5cdCNpZiBOVU1fU1BPVF9MSUdIVF9TSEFET1dTID4gMFxuXHRcdHVuaWZvcm0gc2FtcGxlcjJEIHNwb3RTaGFkb3dNYXBbIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgXTtcblx0XHR2YXJ5aW5nIHZlYzQgdlNwb3RTaGFkb3dDb29yZFsgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyBdO1xuXHRcdHN0cnVjdCBTcG90TGlnaHRTaGFkb3cge1xuXHRcdFx0ZmxvYXQgc2hhZG93Qmlhcztcblx0XHRcdGZsb2F0IHNoYWRvd05vcm1hbEJpYXM7XG5cdFx0XHRmbG9hdCBzaGFkb3dSYWRpdXM7XG5cdFx0XHR2ZWMyIHNoYWRvd01hcFNpemU7XG5cdFx0fTtcblx0XHR1bmlmb3JtIFNwb3RMaWdodFNoYWRvdyBzcG90TGlnaHRTaGFkb3dzWyBOVU1fU1BPVF9MSUdIVF9TSEFET1dTIF07XG5cdCNlbmRpZlxuXHQjaWYgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgPiAwXG5cdFx0dW5pZm9ybSBzYW1wbGVyMkQgcG9pbnRTaGFkb3dNYXBbIE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTIF07XG5cdFx0dmFyeWluZyB2ZWM0IHZQb2ludFNoYWRvd0Nvb3JkWyBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyBdO1xuXHRcdHN0cnVjdCBQb2ludExpZ2h0U2hhZG93IHtcblx0XHRcdGZsb2F0IHNoYWRvd0JpYXM7XG5cdFx0XHRmbG9hdCBzaGFkb3dOb3JtYWxCaWFzO1xuXHRcdFx0ZmxvYXQgc2hhZG93UmFkaXVzO1xuXHRcdFx0dmVjMiBzaGFkb3dNYXBTaXplO1xuXHRcdFx0ZmxvYXQgc2hhZG93Q2FtZXJhTmVhcjtcblx0XHRcdGZsb2F0IHNoYWRvd0NhbWVyYUZhcjtcblx0XHR9O1xuXHRcdHVuaWZvcm0gUG9pbnRMaWdodFNoYWRvdyBwb2ludExpZ2h0U2hhZG93c1sgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgXTtcblx0I2VuZGlmXG5cdGZsb2F0IHRleHR1cmUyRENvbXBhcmUoIHNhbXBsZXIyRCBkZXB0aHMsIHZlYzIgdXYsIGZsb2F0IGNvbXBhcmUgKSB7XG5cdFx0cmV0dXJuIHN0ZXAoIGNvbXBhcmUsIHVucGFja1JHQkFUb0RlcHRoKCB0ZXh0dXJlMkQoIGRlcHRocywgdXYgKSApICk7XG5cdH1cblx0dmVjMiB0ZXh0dXJlMkREaXN0cmlidXRpb24oIHNhbXBsZXIyRCBzaGFkb3csIHZlYzIgdXYgKSB7XG5cdFx0cmV0dXJuIHVucGFja1JHQkFUbzJIYWxmKCB0ZXh0dXJlMkQoIHNoYWRvdywgdXYgKSApO1xuXHR9XG5cdGZsb2F0IFZTTVNoYWRvdyAoc2FtcGxlcjJEIHNoYWRvdywgdmVjMiB1diwgZmxvYXQgY29tcGFyZSApe1xuXHRcdGZsb2F0IG9jY2x1c2lvbiA9IDEuMDtcblx0XHR2ZWMyIGRpc3RyaWJ1dGlvbiA9IHRleHR1cmUyRERpc3RyaWJ1dGlvbiggc2hhZG93LCB1diApO1xuXHRcdGZsb2F0IGhhcmRfc2hhZG93ID0gc3RlcCggY29tcGFyZSAsIGRpc3RyaWJ1dGlvbi54ICk7XG5cdFx0aWYgKGhhcmRfc2hhZG93ICE9IDEuMCApIHtcblx0XHRcdGZsb2F0IGRpc3RhbmNlID0gY29tcGFyZSAtIGRpc3RyaWJ1dGlvbi54IDtcblx0XHRcdGZsb2F0IHZhcmlhbmNlID0gbWF4KCAwLjAwMDAwLCBkaXN0cmlidXRpb24ueSAqIGRpc3RyaWJ1dGlvbi55ICk7XG5cdFx0XHRmbG9hdCBzb2Z0bmVzc19wcm9iYWJpbGl0eSA9IHZhcmlhbmNlIC8gKHZhcmlhbmNlICsgZGlzdGFuY2UgKiBkaXN0YW5jZSApO1x0XHRcdHNvZnRuZXNzX3Byb2JhYmlsaXR5ID0gY2xhbXAoICggc29mdG5lc3NfcHJvYmFiaWxpdHkgLSAwLjMgKSAvICggMC45NSAtIDAuMyApLCAwLjAsIDEuMCApO1x0XHRcdG9jY2x1c2lvbiA9IGNsYW1wKCBtYXgoIGhhcmRfc2hhZG93LCBzb2Z0bmVzc19wcm9iYWJpbGl0eSApLCAwLjAsIDEuMCApO1xuXHRcdH1cblx0XHRyZXR1cm4gb2NjbHVzaW9uO1xuXHR9XG5cdGZsb2F0IGdldFNoYWRvdyggc2FtcGxlcjJEIHNoYWRvd01hcCwgdmVjMiBzaGFkb3dNYXBTaXplLCBmbG9hdCBzaGFkb3dCaWFzLCBmbG9hdCBzaGFkb3dSYWRpdXMsIHZlYzQgc2hhZG93Q29vcmQgKSB7XG5cdFx0ZmxvYXQgc2hhZG93ID0gMS4wO1xuXHRcdHNoYWRvd0Nvb3JkLnh5eiAvPSBzaGFkb3dDb29yZC53O1xuXHRcdHNoYWRvd0Nvb3JkLnogKz0gc2hhZG93Qmlhcztcblx0XHRidmVjNCBpbkZydXN0dW1WZWMgPSBidmVjNCAoIHNoYWRvd0Nvb3JkLnggPj0gMC4wLCBzaGFkb3dDb29yZC54IDw9IDEuMCwgc2hhZG93Q29vcmQueSA+PSAwLjAsIHNoYWRvd0Nvb3JkLnkgPD0gMS4wICk7XG5cdFx0Ym9vbCBpbkZydXN0dW0gPSBhbGwoIGluRnJ1c3R1bVZlYyApO1xuXHRcdGJ2ZWMyIGZydXN0dW1UZXN0VmVjID0gYnZlYzIoIGluRnJ1c3R1bSwgc2hhZG93Q29vcmQueiA8PSAxLjAgKTtcblx0XHRib29sIGZydXN0dW1UZXN0ID0gYWxsKCBmcnVzdHVtVGVzdFZlYyApO1xuXHRcdGlmICggZnJ1c3R1bVRlc3QgKSB7XG5cdFx0I2lmIGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1BDRiApXG5cdFx0XHR2ZWMyIHRleGVsU2l6ZSA9IHZlYzIoIDEuMCApIC8gc2hhZG93TWFwU2l6ZTtcblx0XHRcdGZsb2F0IGR4MCA9IC0gdGV4ZWxTaXplLnggKiBzaGFkb3dSYWRpdXM7XG5cdFx0XHRmbG9hdCBkeTAgPSAtIHRleGVsU2l6ZS55ICogc2hhZG93UmFkaXVzO1xuXHRcdFx0ZmxvYXQgZHgxID0gKyB0ZXhlbFNpemUueCAqIHNoYWRvd1JhZGl1cztcblx0XHRcdGZsb2F0IGR5MSA9ICsgdGV4ZWxTaXplLnkgKiBzaGFkb3dSYWRpdXM7XG5cdFx0XHRmbG9hdCBkeDIgPSBkeDAgLyAyLjA7XG5cdFx0XHRmbG9hdCBkeTIgPSBkeTAgLyAyLjA7XG5cdFx0XHRmbG9hdCBkeDMgPSBkeDEgLyAyLjA7XG5cdFx0XHRmbG9hdCBkeTMgPSBkeTEgLyAyLjA7XG5cdFx0XHRzaGFkb3cgPSAoXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDAsIGR5MCApLCBzaGFkb3dDb29yZC56ICkgK1xuXHRcdFx0XHR0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTAgKSwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MSwgZHkwICksIHNoYWRvd0Nvb3JkLnogKSArXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDIsIGR5MiApLCBzaGFkb3dDb29yZC56ICkgK1xuXHRcdFx0XHR0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggMC4wLCBkeTIgKSwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MywgZHkyICksIHNoYWRvd0Nvb3JkLnogKSArXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDAsIDAuMCApLCBzaGFkb3dDb29yZC56ICkgK1xuXHRcdFx0XHR0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggZHgyLCAwLjAgKSwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIGR4MywgMC4wICksIHNoYWRvd0Nvb3JkLnogKSArXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDEsIDAuMCApLCBzaGFkb3dDb29yZC56ICkgK1xuXHRcdFx0XHR0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggZHgyLCBkeTMgKSwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIDAuMCwgZHkzICksIHNoYWRvd0Nvb3JkLnogKSArXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDMsIGR5MyApLCBzaGFkb3dDb29yZC56ICkgK1xuXHRcdFx0XHR0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHNoYWRvd0Nvb3JkLnh5ICsgdmVjMiggZHgwLCBkeTEgKSwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSArIHZlYzIoIDAuMCwgZHkxICksIHNoYWRvd0Nvb3JkLnogKSArXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHkgKyB2ZWMyKCBkeDEsIGR5MSApLCBzaGFkb3dDb29yZC56IClcblx0XHRcdCkgKiAoIDEuMCAvIDE3LjAgKTtcblx0XHQjZWxpZiBkZWZpbmVkKCBTSEFET1dNQVBfVFlQRV9QQ0ZfU09GVCApXG5cdFx0XHR2ZWMyIHRleGVsU2l6ZSA9IHZlYzIoIDEuMCApIC8gc2hhZG93TWFwU2l6ZTtcblx0XHRcdGZsb2F0IGR4ID0gdGV4ZWxTaXplLng7XG5cdFx0XHRmbG9hdCBkeSA9IHRleGVsU2l6ZS55O1xuXHRcdFx0dmVjMiB1diA9IHNoYWRvd0Nvb3JkLnh5O1xuXHRcdFx0dmVjMiBmID0gZnJhY3QoIHV2ICogc2hhZG93TWFwU2l6ZSArIDAuNSApO1xuXHRcdFx0dXYgLT0gZiAqIHRleGVsU2l6ZTtcblx0XHRcdHNoYWRvdyA9IChcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIGR4LCAwLjAgKSwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIDAuMCwgZHkgKSwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHRleGVsU2l6ZSwgc2hhZG93Q29vcmQueiApICtcblx0XHRcdFx0bWl4KCB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggLWR4LCAwLjAgKSwgc2hhZG93Q29vcmQueiApLCBcblx0XHRcdFx0XHQgdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIDIuMCAqIGR4LCAwLjAgKSwgc2hhZG93Q29vcmQueiApLFxuXHRcdFx0XHRcdCBmLnggKSArXG5cdFx0XHRcdG1peCggdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIC1keCwgZHkgKSwgc2hhZG93Q29vcmQueiApLCBcblx0XHRcdFx0XHQgdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIDIuMCAqIGR4LCBkeSApLCBzaGFkb3dDb29yZC56ICksXG5cdFx0XHRcdFx0IGYueCApICtcblx0XHRcdFx0bWl4KCB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggMC4wLCAtZHkgKSwgc2hhZG93Q29vcmQueiApLCBcblx0XHRcdFx0XHQgdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIDAuMCwgMi4wICogZHkgKSwgc2hhZG93Q29vcmQueiApLFxuXHRcdFx0XHRcdCBmLnkgKSArXG5cdFx0XHRcdG1peCggdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIGR4LCAtZHkgKSwgc2hhZG93Q29vcmQueiApLCBcblx0XHRcdFx0XHQgdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCB1diArIHZlYzIoIGR4LCAyLjAgKiBkeSApLCBzaGFkb3dDb29yZC56ICksXG5cdFx0XHRcdFx0IGYueSApICtcblx0XHRcdFx0bWl4KCBtaXgoIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAtZHgsIC1keSApLCBzaGFkb3dDb29yZC56ICksIFxuXHRcdFx0XHRcdFx0ICB0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIHV2ICsgdmVjMiggMi4wICogZHgsIC1keSApLCBzaGFkb3dDb29yZC56ICksXG5cdFx0XHRcdFx0XHQgIGYueCApLFxuXHRcdFx0XHRcdCBtaXgoIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAtZHgsIDIuMCAqIGR5ICksIHNoYWRvd0Nvb3JkLnogKSwgXG5cdFx0XHRcdFx0XHQgIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgdXYgKyB2ZWMyKCAyLjAgKiBkeCwgMi4wICogZHkgKSwgc2hhZG93Q29vcmQueiApLFxuXHRcdFx0XHRcdFx0ICBmLnggKSxcblx0XHRcdFx0XHQgZi55IClcblx0XHRcdCkgKiAoIDEuMCAvIDkuMCApO1xuXHRcdCNlbGlmIGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1ZTTSApXG5cdFx0XHRzaGFkb3cgPSBWU01TaGFkb3coIHNoYWRvd01hcCwgc2hhZG93Q29vcmQueHksIHNoYWRvd0Nvb3JkLnogKTtcblx0XHQjZWxzZVxuXHRcdFx0c2hhZG93ID0gdGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBzaGFkb3dDb29yZC54eSwgc2hhZG93Q29vcmQueiApO1xuXHRcdCNlbmRpZlxuXHRcdH1cblx0XHRyZXR1cm4gc2hhZG93O1xuXHR9XG5cdHZlYzIgY3ViZVRvVVYoIHZlYzMgdiwgZmxvYXQgdGV4ZWxTaXplWSApIHtcblx0XHR2ZWMzIGFic1YgPSBhYnMoIHYgKTtcblx0XHRmbG9hdCBzY2FsZVRvQ3ViZSA9IDEuMCAvIG1heCggYWJzVi54LCBtYXgoIGFic1YueSwgYWJzVi56ICkgKTtcblx0XHRhYnNWICo9IHNjYWxlVG9DdWJlO1xuXHRcdHYgKj0gc2NhbGVUb0N1YmUgKiAoIDEuMCAtIDIuMCAqIHRleGVsU2l6ZVkgKTtcblx0XHR2ZWMyIHBsYW5hciA9IHYueHk7XG5cdFx0ZmxvYXQgYWxtb3N0QVRleGVsID0gMS41ICogdGV4ZWxTaXplWTtcblx0XHRmbG9hdCBhbG1vc3RPbmUgPSAxLjAgLSBhbG1vc3RBVGV4ZWw7XG5cdFx0aWYgKCBhYnNWLnogPj0gYWxtb3N0T25lICkge1xuXHRcdFx0aWYgKCB2LnogPiAwLjAgKVxuXHRcdFx0XHRwbGFuYXIueCA9IDQuMCAtIHYueDtcblx0XHR9IGVsc2UgaWYgKCBhYnNWLnggPj0gYWxtb3N0T25lICkge1xuXHRcdFx0ZmxvYXQgc2lnblggPSBzaWduKCB2LnggKTtcblx0XHRcdHBsYW5hci54ID0gdi56ICogc2lnblggKyAyLjAgKiBzaWduWDtcblx0XHR9IGVsc2UgaWYgKCBhYnNWLnkgPj0gYWxtb3N0T25lICkge1xuXHRcdFx0ZmxvYXQgc2lnblkgPSBzaWduKCB2LnkgKTtcblx0XHRcdHBsYW5hci54ID0gdi54ICsgMi4wICogc2lnblkgKyAyLjA7XG5cdFx0XHRwbGFuYXIueSA9IHYueiAqIHNpZ25ZIC0gMi4wO1xuXHRcdH1cblx0XHRyZXR1cm4gdmVjMiggMC4xMjUsIDAuMjUgKSAqIHBsYW5hciArIHZlYzIoIDAuMzc1LCAwLjc1ICk7XG5cdH1cblx0ZmxvYXQgZ2V0UG9pbnRTaGFkb3coIHNhbXBsZXIyRCBzaGFkb3dNYXAsIHZlYzIgc2hhZG93TWFwU2l6ZSwgZmxvYXQgc2hhZG93QmlhcywgZmxvYXQgc2hhZG93UmFkaXVzLCB2ZWM0IHNoYWRvd0Nvb3JkLCBmbG9hdCBzaGFkb3dDYW1lcmFOZWFyLCBmbG9hdCBzaGFkb3dDYW1lcmFGYXIgKSB7XG5cdFx0dmVjMiB0ZXhlbFNpemUgPSB2ZWMyKCAxLjAgKSAvICggc2hhZG93TWFwU2l6ZSAqIHZlYzIoIDQuMCwgMi4wICkgKTtcblx0XHR2ZWMzIGxpZ2h0VG9Qb3NpdGlvbiA9IHNoYWRvd0Nvb3JkLnh5ejtcblx0XHRmbG9hdCBkcCA9ICggbGVuZ3RoKCBsaWdodFRvUG9zaXRpb24gKSAtIHNoYWRvd0NhbWVyYU5lYXIgKSAvICggc2hhZG93Q2FtZXJhRmFyIC0gc2hhZG93Q2FtZXJhTmVhciApO1x0XHRkcCArPSBzaGFkb3dCaWFzO1xuXHRcdHZlYzMgYmQzRCA9IG5vcm1hbGl6ZSggbGlnaHRUb1Bvc2l0aW9uICk7XG5cdFx0I2lmIGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1BDRiApIHx8IGRlZmluZWQoIFNIQURPV01BUF9UWVBFX1BDRl9TT0ZUICkgfHwgZGVmaW5lZCggU0hBRE9XTUFQX1RZUEVfVlNNIClcblx0XHRcdHZlYzIgb2Zmc2V0ID0gdmVjMiggLSAxLCAxICkgKiBzaGFkb3dSYWRpdXMgKiB0ZXhlbFNpemUueTtcblx0XHRcdHJldHVybiAoXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgY3ViZVRvVVYoIGJkM0QgKyBvZmZzZXQueHl5LCB0ZXhlbFNpemUueSApLCBkcCApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC55eXksIHRleGVsU2l6ZS55ICksIGRwICkgK1xuXHRcdFx0XHR0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnh5eCwgdGV4ZWxTaXplLnkgKSwgZHAgKSArXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgY3ViZVRvVVYoIGJkM0QgKyBvZmZzZXQueXl4LCB0ZXhlbFNpemUueSApLCBkcCApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCwgdGV4ZWxTaXplLnkgKSwgZHAgKSArXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgY3ViZVRvVVYoIGJkM0QgKyBvZmZzZXQueHh5LCB0ZXhlbFNpemUueSApLCBkcCApICtcblx0XHRcdFx0dGV4dHVyZTJEQ29tcGFyZSggc2hhZG93TWFwLCBjdWJlVG9VViggYmQzRCArIG9mZnNldC55eHksIHRleGVsU2l6ZS55ICksIGRwICkgK1xuXHRcdFx0XHR0ZXh0dXJlMkRDb21wYXJlKCBzaGFkb3dNYXAsIGN1YmVUb1VWKCBiZDNEICsgb2Zmc2V0Lnh4eCwgdGV4ZWxTaXplLnkgKSwgZHAgKSArXG5cdFx0XHRcdHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgY3ViZVRvVVYoIGJkM0QgKyBvZmZzZXQueXh4LCB0ZXhlbFNpemUueSApLCBkcCApXG5cdFx0XHQpICogKCAxLjAgLyA5LjAgKTtcblx0XHQjZWxzZVxuXHRcdFx0cmV0dXJuIHRleHR1cmUyRENvbXBhcmUoIHNoYWRvd01hcCwgY3ViZVRvVVYoIGJkM0QsIHRleGVsU2l6ZS55ICksIGRwICk7XG5cdFx0I2VuZGlmXG5cdH1cbiNlbmRpZiIsc2hhZG93bWFwX3BhcnNfdmVydGV4OiIjaWZkZWYgVVNFX1NIQURPV01BUFxuXHQjaWYgTlVNX0RJUl9MSUdIVF9TSEFET1dTID4gMFxuXHRcdHVuaWZvcm0gbWF0NCBkaXJlY3Rpb25hbFNoYWRvd01hdHJpeFsgTlVNX0RJUl9MSUdIVF9TSEFET1dTIF07XG5cdFx0dmFyeWluZyB2ZWM0IHZEaXJlY3Rpb25hbFNoYWRvd0Nvb3JkWyBOVU1fRElSX0xJR0hUX1NIQURPV1MgXTtcblx0XHRzdHJ1Y3QgRGlyZWN0aW9uYWxMaWdodFNoYWRvdyB7XG5cdFx0XHRmbG9hdCBzaGFkb3dCaWFzO1xuXHRcdFx0ZmxvYXQgc2hhZG93Tm9ybWFsQmlhcztcblx0XHRcdGZsb2F0IHNoYWRvd1JhZGl1cztcblx0XHRcdHZlYzIgc2hhZG93TWFwU2l6ZTtcblx0XHR9O1xuXHRcdHVuaWZvcm0gRGlyZWN0aW9uYWxMaWdodFNoYWRvdyBkaXJlY3Rpb25hbExpZ2h0U2hhZG93c1sgTlVNX0RJUl9MSUdIVF9TSEFET1dTIF07XG5cdCNlbmRpZlxuXHQjaWYgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyA+IDBcblx0XHR1bmlmb3JtIG1hdDQgc3BvdFNoYWRvd01hdHJpeFsgTlVNX1NQT1RfTElHSFRfU0hBRE9XUyBdO1xuXHRcdHZhcnlpbmcgdmVjNCB2U3BvdFNoYWRvd0Nvb3JkWyBOVU1fU1BPVF9MSUdIVF9TSEFET1dTIF07XG5cdFx0c3RydWN0IFNwb3RMaWdodFNoYWRvdyB7XG5cdFx0XHRmbG9hdCBzaGFkb3dCaWFzO1xuXHRcdFx0ZmxvYXQgc2hhZG93Tm9ybWFsQmlhcztcblx0XHRcdGZsb2F0IHNoYWRvd1JhZGl1cztcblx0XHRcdHZlYzIgc2hhZG93TWFwU2l6ZTtcblx0XHR9O1xuXHRcdHVuaWZvcm0gU3BvdExpZ2h0U2hhZG93IHNwb3RMaWdodFNoYWRvd3NbIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgXTtcblx0I2VuZGlmXG5cdCNpZiBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyA+IDBcblx0XHR1bmlmb3JtIG1hdDQgcG9pbnRTaGFkb3dNYXRyaXhbIE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTIF07XG5cdFx0dmFyeWluZyB2ZWM0IHZQb2ludFNoYWRvd0Nvb3JkWyBOVU1fUE9JTlRfTElHSFRfU0hBRE9XUyBdO1xuXHRcdHN0cnVjdCBQb2ludExpZ2h0U2hhZG93IHtcblx0XHRcdGZsb2F0IHNoYWRvd0JpYXM7XG5cdFx0XHRmbG9hdCBzaGFkb3dOb3JtYWxCaWFzO1xuXHRcdFx0ZmxvYXQgc2hhZG93UmFkaXVzO1xuXHRcdFx0dmVjMiBzaGFkb3dNYXBTaXplO1xuXHRcdFx0ZmxvYXQgc2hhZG93Q2FtZXJhTmVhcjtcblx0XHRcdGZsb2F0IHNoYWRvd0NhbWVyYUZhcjtcblx0XHR9O1xuXHRcdHVuaWZvcm0gUG9pbnRMaWdodFNoYWRvdyBwb2ludExpZ2h0U2hhZG93c1sgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1MgXTtcblx0I2VuZGlmXG4jZW5kaWYiLHNoYWRvd21hcF92ZXJ0ZXg6IiNpZmRlZiBVU0VfU0hBRE9XTUFQXG5cdCNpZiBOVU1fRElSX0xJR0hUX1NIQURPV1MgPiAwIHx8IE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwIHx8IE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTID4gMFxuXHRcdHZlYzMgc2hhZG93V29ybGROb3JtYWwgPSBpbnZlcnNlVHJhbnNmb3JtRGlyZWN0aW9uKCB0cmFuc2Zvcm1lZE5vcm1hbCwgdmlld01hdHJpeCApO1xuXHRcdHZlYzQgc2hhZG93V29ybGRQb3NpdGlvbjtcblx0I2VuZGlmXG5cdCNpZiBOVU1fRElSX0xJR0hUX1NIQURPV1MgPiAwXG5cdCNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnRcblx0Zm9yICggaW50IGkgPSAwOyBpIDwgTlVNX0RJUl9MSUdIVF9TSEFET1dTOyBpICsrICkge1xuXHRcdHNoYWRvd1dvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uICsgdmVjNCggc2hhZG93V29ybGROb3JtYWwgKiBkaXJlY3Rpb25hbExpZ2h0U2hhZG93c1sgaSBdLnNoYWRvd05vcm1hbEJpYXMsIDAgKTtcblx0XHR2RGlyZWN0aW9uYWxTaGFkb3dDb29yZFsgaSBdID0gZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXhbIGkgXSAqIHNoYWRvd1dvcmxkUG9zaXRpb247XG5cdH1cblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9lbmRcblx0I2VuZGlmXG5cdCNpZiBOVU1fU1BPVF9MSUdIVF9TSEFET1dTID4gMFxuXHQjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0XG5cdGZvciAoIGludCBpID0gMDsgaSA8IE5VTV9TUE9UX0xJR0hUX1NIQURPV1M7IGkgKysgKSB7XG5cdFx0c2hhZG93V29ybGRQb3NpdGlvbiA9IHdvcmxkUG9zaXRpb24gKyB2ZWM0KCBzaGFkb3dXb3JsZE5vcm1hbCAqIHNwb3RMaWdodFNoYWRvd3NbIGkgXS5zaGFkb3dOb3JtYWxCaWFzLCAwICk7XG5cdFx0dlNwb3RTaGFkb3dDb29yZFsgaSBdID0gc3BvdFNoYWRvd01hdHJpeFsgaSBdICogc2hhZG93V29ybGRQb3NpdGlvbjtcblx0fVxuXHQjcHJhZ21hIHVucm9sbF9sb29wX2VuZFxuXHQjZW5kaWZcblx0I2lmIE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTID4gMFxuXHQjcHJhZ21hIHVucm9sbF9sb29wX3N0YXJ0XG5cdGZvciAoIGludCBpID0gMDsgaSA8IE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTOyBpICsrICkge1xuXHRcdHNoYWRvd1dvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uICsgdmVjNCggc2hhZG93V29ybGROb3JtYWwgKiBwb2ludExpZ2h0U2hhZG93c1sgaSBdLnNoYWRvd05vcm1hbEJpYXMsIDAgKTtcblx0XHR2UG9pbnRTaGFkb3dDb29yZFsgaSBdID0gcG9pbnRTaGFkb3dNYXRyaXhbIGkgXSAqIHNoYWRvd1dvcmxkUG9zaXRpb247XG5cdH1cblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9lbmRcblx0I2VuZGlmXG4jZW5kaWYiLHNoYWRvd21hc2tfcGFyc19mcmFnbWVudDoiZmxvYXQgZ2V0U2hhZG93TWFzaygpIHtcblx0ZmxvYXQgc2hhZG93ID0gMS4wO1xuXHQjaWZkZWYgVVNFX1NIQURPV01BUFxuXHQjaWYgTlVNX0RJUl9MSUdIVF9TSEFET1dTID4gMFxuXHREaXJlY3Rpb25hbExpZ2h0U2hhZG93IGRpcmVjdGlvbmFsTGlnaHQ7XG5cdCNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnRcblx0Zm9yICggaW50IGkgPSAwOyBpIDwgTlVNX0RJUl9MSUdIVF9TSEFET1dTOyBpICsrICkge1xuXHRcdGRpcmVjdGlvbmFsTGlnaHQgPSBkaXJlY3Rpb25hbExpZ2h0U2hhZG93c1sgaSBdO1xuXHRcdHNoYWRvdyAqPSByZWNlaXZlU2hhZG93ID8gZ2V0U2hhZG93KCBkaXJlY3Rpb25hbFNoYWRvd01hcFsgaSBdLCBkaXJlY3Rpb25hbExpZ2h0LnNoYWRvd01hcFNpemUsIGRpcmVjdGlvbmFsTGlnaHQuc2hhZG93QmlhcywgZGlyZWN0aW9uYWxMaWdodC5zaGFkb3dSYWRpdXMsIHZEaXJlY3Rpb25hbFNoYWRvd0Nvb3JkWyBpIF0gKSA6IDEuMDtcblx0fVxuXHQjcHJhZ21hIHVucm9sbF9sb29wX2VuZFxuXHQjZW5kaWZcblx0I2lmIE5VTV9TUE9UX0xJR0hUX1NIQURPV1MgPiAwXG5cdFNwb3RMaWdodFNoYWRvdyBzcG90TGlnaHQ7XG5cdCNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnRcblx0Zm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1NQT1RfTElHSFRfU0hBRE9XUzsgaSArKyApIHtcblx0XHRzcG90TGlnaHQgPSBzcG90TGlnaHRTaGFkb3dzWyBpIF07XG5cdFx0c2hhZG93ICo9IHJlY2VpdmVTaGFkb3cgPyBnZXRTaGFkb3coIHNwb3RTaGFkb3dNYXBbIGkgXSwgc3BvdExpZ2h0LnNoYWRvd01hcFNpemUsIHNwb3RMaWdodC5zaGFkb3dCaWFzLCBzcG90TGlnaHQuc2hhZG93UmFkaXVzLCB2U3BvdFNoYWRvd0Nvb3JkWyBpIF0gKSA6IDEuMDtcblx0fVxuXHQjcHJhZ21hIHVucm9sbF9sb29wX2VuZFxuXHQjZW5kaWZcblx0I2lmIE5VTV9QT0lOVF9MSUdIVF9TSEFET1dTID4gMFxuXHRQb2ludExpZ2h0U2hhZG93IHBvaW50TGlnaHQ7XG5cdCNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnRcblx0Zm9yICggaW50IGkgPSAwOyBpIDwgTlVNX1BPSU5UX0xJR0hUX1NIQURPV1M7IGkgKysgKSB7XG5cdFx0cG9pbnRMaWdodCA9IHBvaW50TGlnaHRTaGFkb3dzWyBpIF07XG5cdFx0c2hhZG93ICo9IHJlY2VpdmVTaGFkb3cgPyBnZXRQb2ludFNoYWRvdyggcG9pbnRTaGFkb3dNYXBbIGkgXSwgcG9pbnRMaWdodC5zaGFkb3dNYXBTaXplLCBwb2ludExpZ2h0LnNoYWRvd0JpYXMsIHBvaW50TGlnaHQuc2hhZG93UmFkaXVzLCB2UG9pbnRTaGFkb3dDb29yZFsgaSBdLCBwb2ludExpZ2h0LnNoYWRvd0NhbWVyYU5lYXIsIHBvaW50TGlnaHQuc2hhZG93Q2FtZXJhRmFyICkgOiAxLjA7XG5cdH1cblx0I3ByYWdtYSB1bnJvbGxfbG9vcF9lbmRcblx0I2VuZGlmXG5cdCNlbmRpZlxuXHRyZXR1cm4gc2hhZG93O1xufSIsc2tpbmJhc2VfdmVydGV4OiIjaWZkZWYgVVNFX1NLSU5OSU5HXG5cdG1hdDQgYm9uZU1hdFggPSBnZXRCb25lTWF0cml4KCBza2luSW5kZXgueCApO1xuXHRtYXQ0IGJvbmVNYXRZID0gZ2V0Qm9uZU1hdHJpeCggc2tpbkluZGV4LnkgKTtcblx0bWF0NCBib25lTWF0WiA9IGdldEJvbmVNYXRyaXgoIHNraW5JbmRleC56ICk7XG5cdG1hdDQgYm9uZU1hdFcgPSBnZXRCb25lTWF0cml4KCBza2luSW5kZXgudyApO1xuI2VuZGlmIixza2lubmluZ19wYXJzX3ZlcnRleDoiI2lmZGVmIFVTRV9TS0lOTklOR1xuXHR1bmlmb3JtIG1hdDQgYmluZE1hdHJpeDtcblx0dW5pZm9ybSBtYXQ0IGJpbmRNYXRyaXhJbnZlcnNlO1xuXHQjaWZkZWYgQk9ORV9URVhUVVJFXG5cdFx0dW5pZm9ybSBoaWdocCBzYW1wbGVyMkQgYm9uZVRleHR1cmU7XG5cdFx0dW5pZm9ybSBpbnQgYm9uZVRleHR1cmVTaXplO1xuXHRcdG1hdDQgZ2V0Qm9uZU1hdHJpeCggY29uc3QgaW4gZmxvYXQgaSApIHtcblx0XHRcdGZsb2F0IGogPSBpICogNC4wO1xuXHRcdFx0ZmxvYXQgeCA9IG1vZCggaiwgZmxvYXQoIGJvbmVUZXh0dXJlU2l6ZSApICk7XG5cdFx0XHRmbG9hdCB5ID0gZmxvb3IoIGogLyBmbG9hdCggYm9uZVRleHR1cmVTaXplICkgKTtcblx0XHRcdGZsb2F0IGR4ID0gMS4wIC8gZmxvYXQoIGJvbmVUZXh0dXJlU2l6ZSApO1xuXHRcdFx0ZmxvYXQgZHkgPSAxLjAgLyBmbG9hdCggYm9uZVRleHR1cmVTaXplICk7XG5cdFx0XHR5ID0gZHkgKiAoIHkgKyAwLjUgKTtcblx0XHRcdHZlYzQgdjEgPSB0ZXh0dXJlMkQoIGJvbmVUZXh0dXJlLCB2ZWMyKCBkeCAqICggeCArIDAuNSApLCB5ICkgKTtcblx0XHRcdHZlYzQgdjIgPSB0ZXh0dXJlMkQoIGJvbmVUZXh0dXJlLCB2ZWMyKCBkeCAqICggeCArIDEuNSApLCB5ICkgKTtcblx0XHRcdHZlYzQgdjMgPSB0ZXh0dXJlMkQoIGJvbmVUZXh0dXJlLCB2ZWMyKCBkeCAqICggeCArIDIuNSApLCB5ICkgKTtcblx0XHRcdHZlYzQgdjQgPSB0ZXh0dXJlMkQoIGJvbmVUZXh0dXJlLCB2ZWMyKCBkeCAqICggeCArIDMuNSApLCB5ICkgKTtcblx0XHRcdG1hdDQgYm9uZSA9IG1hdDQoIHYxLCB2MiwgdjMsIHY0ICk7XG5cdFx0XHRyZXR1cm4gYm9uZTtcblx0XHR9XG5cdCNlbHNlXG5cdFx0dW5pZm9ybSBtYXQ0IGJvbmVNYXRyaWNlc1sgTUFYX0JPTkVTIF07XG5cdFx0bWF0NCBnZXRCb25lTWF0cml4KCBjb25zdCBpbiBmbG9hdCBpICkge1xuXHRcdFx0bWF0NCBib25lID0gYm9uZU1hdHJpY2VzWyBpbnQoaSkgXTtcblx0XHRcdHJldHVybiBib25lO1xuXHRcdH1cblx0I2VuZGlmXG4jZW5kaWYiLHNraW5uaW5nX3ZlcnRleDoiI2lmZGVmIFVTRV9TS0lOTklOR1xuXHR2ZWM0IHNraW5WZXJ0ZXggPSBiaW5kTWF0cml4ICogdmVjNCggdHJhbnNmb3JtZWQsIDEuMCApO1xuXHR2ZWM0IHNraW5uZWQgPSB2ZWM0KCAwLjAgKTtcblx0c2tpbm5lZCArPSBib25lTWF0WCAqIHNraW5WZXJ0ZXggKiBza2luV2VpZ2h0Lng7XG5cdHNraW5uZWQgKz0gYm9uZU1hdFkgKiBza2luVmVydGV4ICogc2tpbldlaWdodC55O1xuXHRza2lubmVkICs9IGJvbmVNYXRaICogc2tpblZlcnRleCAqIHNraW5XZWlnaHQuejtcblx0c2tpbm5lZCArPSBib25lTWF0VyAqIHNraW5WZXJ0ZXggKiBza2luV2VpZ2h0Lnc7XG5cdHRyYW5zZm9ybWVkID0gKCBiaW5kTWF0cml4SW52ZXJzZSAqIHNraW5uZWQgKS54eXo7XG4jZW5kaWYiLHNraW5ub3JtYWxfdmVydGV4OiIjaWZkZWYgVVNFX1NLSU5OSU5HXG5cdG1hdDQgc2tpbk1hdHJpeCA9IG1hdDQoIDAuMCApO1xuXHRza2luTWF0cml4ICs9IHNraW5XZWlnaHQueCAqIGJvbmVNYXRYO1xuXHRza2luTWF0cml4ICs9IHNraW5XZWlnaHQueSAqIGJvbmVNYXRZO1xuXHRza2luTWF0cml4ICs9IHNraW5XZWlnaHQueiAqIGJvbmVNYXRaO1xuXHRza2luTWF0cml4ICs9IHNraW5XZWlnaHQudyAqIGJvbmVNYXRXO1xuXHRza2luTWF0cml4ID0gYmluZE1hdHJpeEludmVyc2UgKiBza2luTWF0cml4ICogYmluZE1hdHJpeDtcblx0b2JqZWN0Tm9ybWFsID0gdmVjNCggc2tpbk1hdHJpeCAqIHZlYzQoIG9iamVjdE5vcm1hbCwgMC4wICkgKS54eXo7XG5cdCNpZmRlZiBVU0VfVEFOR0VOVFxuXHRcdG9iamVjdFRhbmdlbnQgPSB2ZWM0KCBza2luTWF0cml4ICogdmVjNCggb2JqZWN0VGFuZ2VudCwgMC4wICkgKS54eXo7XG5cdCNlbmRpZlxuI2VuZGlmIixzcGVjdWxhcm1hcF9mcmFnbWVudDoiZmxvYXQgc3BlY3VsYXJTdHJlbmd0aDtcbiNpZmRlZiBVU0VfU1BFQ1VMQVJNQVBcblx0dmVjNCB0ZXhlbFNwZWN1bGFyID0gdGV4dHVyZTJEKCBzcGVjdWxhck1hcCwgdlV2ICk7XG5cdHNwZWN1bGFyU3RyZW5ndGggPSB0ZXhlbFNwZWN1bGFyLnI7XG4jZWxzZVxuXHRzcGVjdWxhclN0cmVuZ3RoID0gMS4wO1xuI2VuZGlmIixzcGVjdWxhcm1hcF9wYXJzX2ZyYWdtZW50OiIjaWZkZWYgVVNFX1NQRUNVTEFSTUFQXG5cdHVuaWZvcm0gc2FtcGxlcjJEIHNwZWN1bGFyTWFwO1xuI2VuZGlmIix0b25lbWFwcGluZ19mcmFnbWVudDoiI2lmIGRlZmluZWQoIFRPTkVfTUFQUElORyApXG5cdGdsX0ZyYWdDb2xvci5yZ2IgPSB0b25lTWFwcGluZyggZ2xfRnJhZ0NvbG9yLnJnYiApO1xuI2VuZGlmIix0b25lbWFwcGluZ19wYXJzX2ZyYWdtZW50OiIjaWZuZGVmIHNhdHVyYXRlXG4jZGVmaW5lIHNhdHVyYXRlKCBhICkgY2xhbXAoIGEsIDAuMCwgMS4wIClcbiNlbmRpZlxudW5pZm9ybSBmbG9hdCB0b25lTWFwcGluZ0V4cG9zdXJlO1xudmVjMyBMaW5lYXJUb25lTWFwcGluZyggdmVjMyBjb2xvciApIHtcblx0cmV0dXJuIHRvbmVNYXBwaW5nRXhwb3N1cmUgKiBjb2xvcjtcbn1cbnZlYzMgUmVpbmhhcmRUb25lTWFwcGluZyggdmVjMyBjb2xvciApIHtcblx0Y29sb3IgKj0gdG9uZU1hcHBpbmdFeHBvc3VyZTtcblx0cmV0dXJuIHNhdHVyYXRlKCBjb2xvciAvICggdmVjMyggMS4wICkgKyBjb2xvciApICk7XG59XG52ZWMzIE9wdGltaXplZENpbmVvblRvbmVNYXBwaW5nKCB2ZWMzIGNvbG9yICkge1xuXHRjb2xvciAqPSB0b25lTWFwcGluZ0V4cG9zdXJlO1xuXHRjb2xvciA9IG1heCggdmVjMyggMC4wICksIGNvbG9yIC0gMC4wMDQgKTtcblx0cmV0dXJuIHBvdyggKCBjb2xvciAqICggNi4yICogY29sb3IgKyAwLjUgKSApIC8gKCBjb2xvciAqICggNi4yICogY29sb3IgKyAxLjcgKSArIDAuMDYgKSwgdmVjMyggMi4yICkgKTtcbn1cbnZlYzMgUlJUQW5kT0RURml0KCB2ZWMzIHYgKSB7XG5cdHZlYzMgYSA9IHYgKiAoIHYgKyAwLjAyNDU3ODYgKSAtIDAuMDAwMDkwNTM3O1xuXHR2ZWMzIGIgPSB2ICogKCAwLjk4MzcyOSAqIHYgKyAwLjQzMjk1MTAgKSArIDAuMjM4MDgxO1xuXHRyZXR1cm4gYSAvIGI7XG59XG52ZWMzIEFDRVNGaWxtaWNUb25lTWFwcGluZyggdmVjMyBjb2xvciApIHtcblx0Y29uc3QgbWF0MyBBQ0VTSW5wdXRNYXQgPSBtYXQzKFxuXHRcdHZlYzMoIDAuNTk3MTksIDAuMDc2MDAsIDAuMDI4NDAgKSxcdFx0dmVjMyggMC4zNTQ1OCwgMC45MDgzNCwgMC4xMzM4MyApLFxuXHRcdHZlYzMoIDAuMDQ4MjMsIDAuMDE1NjYsIDAuODM3NzcgKVxuXHQpO1xuXHRjb25zdCBtYXQzIEFDRVNPdXRwdXRNYXQgPSBtYXQzKFxuXHRcdHZlYzMoICAxLjYwNDc1LCAtMC4xMDIwOCwgLTAuMDAzMjcgKSxcdFx0dmVjMyggLTAuNTMxMDgsICAxLjEwODEzLCAtMC4wNzI3NiApLFxuXHRcdHZlYzMoIC0wLjA3MzY3LCAtMC4wMDYwNSwgIDEuMDc2MDIgKVxuXHQpO1xuXHRjb2xvciAqPSB0b25lTWFwcGluZ0V4cG9zdXJlIC8gMC42O1xuXHRjb2xvciA9IEFDRVNJbnB1dE1hdCAqIGNvbG9yO1xuXHRjb2xvciA9IFJSVEFuZE9EVEZpdCggY29sb3IgKTtcblx0Y29sb3IgPSBBQ0VTT3V0cHV0TWF0ICogY29sb3I7XG5cdHJldHVybiBzYXR1cmF0ZSggY29sb3IgKTtcbn1cbnZlYzMgQ3VzdG9tVG9uZU1hcHBpbmcoIHZlYzMgY29sb3IgKSB7IHJldHVybiBjb2xvcjsgfSIsdHJhbnNtaXNzaW9uX2ZyYWdtZW50OiIjaWZkZWYgVVNFX1RSQU5TTUlTU0lPTlxuXHRmbG9hdCB0cmFuc21pc3Npb25BbHBoYSA9IDEuMDtcblx0ZmxvYXQgdHJhbnNtaXNzaW9uRmFjdG9yID0gdHJhbnNtaXNzaW9uO1xuXHRmbG9hdCB0aGlja25lc3NGYWN0b3IgPSB0aGlja25lc3M7XG5cdCNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OTUFQXG5cdFx0dHJhbnNtaXNzaW9uRmFjdG9yICo9IHRleHR1cmUyRCggdHJhbnNtaXNzaW9uTWFwLCB2VXYgKS5yO1xuXHQjZW5kaWZcblx0I2lmZGVmIFVTRV9USElDS05FU1NNQVBcblx0XHR0aGlja25lc3NGYWN0b3IgKj0gdGV4dHVyZTJEKCB0aGlja25lc3NNYXAsIHZVdiApLmc7XG5cdCNlbmRpZlxuXHR2ZWMzIHBvcyA9IHZXb3JsZFBvc2l0aW9uO1xuXHR2ZWMzIHYgPSBub3JtYWxpemUoIGNhbWVyYVBvc2l0aW9uIC0gcG9zICk7XG5cdHZlYzMgbiA9IGludmVyc2VUcmFuc2Zvcm1EaXJlY3Rpb24oIG5vcm1hbCwgdmlld01hdHJpeCApO1xuXHR2ZWM0IHRyYW5zbWlzc2lvbiA9IGdldElCTFZvbHVtZVJlZnJhY3Rpb24oXG5cdFx0biwgdiwgcm91Z2huZXNzRmFjdG9yLCBtYXRlcmlhbC5kaWZmdXNlQ29sb3IsIG1hdGVyaWFsLnNwZWN1bGFyQ29sb3IsIG1hdGVyaWFsLnNwZWN1bGFyRjkwLFxuXHRcdHBvcywgbW9kZWxNYXRyaXgsIHZpZXdNYXRyaXgsIHByb2plY3Rpb25NYXRyaXgsIGlvciwgdGhpY2tuZXNzRmFjdG9yLFxuXHRcdGF0dGVudWF0aW9uQ29sb3IsIGF0dGVudWF0aW9uRGlzdGFuY2UgKTtcblx0dG90YWxEaWZmdXNlID0gbWl4KCB0b3RhbERpZmZ1c2UsIHRyYW5zbWlzc2lvbi5yZ2IsIHRyYW5zbWlzc2lvbkZhY3RvciApO1xuXHR0cmFuc21pc3Npb25BbHBoYSA9IG1peCggdHJhbnNtaXNzaW9uQWxwaGEsIHRyYW5zbWlzc2lvbi5hLCB0cmFuc21pc3Npb25GYWN0b3IgKTtcbiNlbmRpZiIsdHJhbnNtaXNzaW9uX3BhcnNfZnJhZ21lbnQ6IiNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OXG5cdHVuaWZvcm0gZmxvYXQgdHJhbnNtaXNzaW9uO1xuXHR1bmlmb3JtIGZsb2F0IHRoaWNrbmVzcztcblx0dW5pZm9ybSBmbG9hdCBhdHRlbnVhdGlvbkRpc3RhbmNlO1xuXHR1bmlmb3JtIHZlYzMgYXR0ZW51YXRpb25Db2xvcjtcblx0I2lmZGVmIFVTRV9UUkFOU01JU1NJT05NQVBcblx0XHR1bmlmb3JtIHNhbXBsZXIyRCB0cmFuc21pc3Npb25NYXA7XG5cdCNlbmRpZlxuXHQjaWZkZWYgVVNFX1RISUNLTkVTU01BUFxuXHRcdHVuaWZvcm0gc2FtcGxlcjJEIHRoaWNrbmVzc01hcDtcblx0I2VuZGlmXG5cdHVuaWZvcm0gdmVjMiB0cmFuc21pc3Npb25TYW1wbGVyU2l6ZTtcblx0dW5pZm9ybSBzYW1wbGVyMkQgdHJhbnNtaXNzaW9uU2FtcGxlck1hcDtcblx0dW5pZm9ybSBtYXQ0IG1vZGVsTWF0cml4O1xuXHR1bmlmb3JtIG1hdDQgcHJvamVjdGlvbk1hdHJpeDtcblx0dmFyeWluZyB2ZWMzIHZXb3JsZFBvc2l0aW9uO1xuXHR2ZWMzIGdldFZvbHVtZVRyYW5zbWlzc2lvblJheSggY29uc3QgaW4gdmVjMyBuLCBjb25zdCBpbiB2ZWMzIHYsIGNvbnN0IGluIGZsb2F0IHRoaWNrbmVzcywgY29uc3QgaW4gZmxvYXQgaW9yLCBjb25zdCBpbiBtYXQ0IG1vZGVsTWF0cml4ICkge1xuXHRcdHZlYzMgcmVmcmFjdGlvblZlY3RvciA9IHJlZnJhY3QoIC0gdiwgbm9ybWFsaXplKCBuICksIDEuMCAvIGlvciApO1xuXHRcdHZlYzMgbW9kZWxTY2FsZTtcblx0XHRtb2RlbFNjYWxlLnggPSBsZW5ndGgoIHZlYzMoIG1vZGVsTWF0cml4WyAwIF0ueHl6ICkgKTtcblx0XHRtb2RlbFNjYWxlLnkgPSBsZW5ndGgoIHZlYzMoIG1vZGVsTWF0cml4WyAxIF0ueHl6ICkgKTtcblx0XHRtb2RlbFNjYWxlLnogPSBsZW5ndGgoIHZlYzMoIG1vZGVsTWF0cml4WyAyIF0ueHl6ICkgKTtcblx0XHRyZXR1cm4gbm9ybWFsaXplKCByZWZyYWN0aW9uVmVjdG9yICkgKiB0aGlja25lc3MgKiBtb2RlbFNjYWxlO1xuXHR9XG5cdGZsb2F0IGFwcGx5SW9yVG9Sb3VnaG5lc3MoIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcywgY29uc3QgaW4gZmxvYXQgaW9yICkge1xuXHRcdHJldHVybiByb3VnaG5lc3MgKiBjbGFtcCggaW9yICogMi4wIC0gMi4wLCAwLjAsIDEuMCApO1xuXHR9XG5cdHZlYzQgZ2V0VHJhbnNtaXNzaW9uU2FtcGxlKCBjb25zdCBpbiB2ZWMyIGZyYWdDb29yZCwgY29uc3QgaW4gZmxvYXQgcm91Z2huZXNzLCBjb25zdCBpbiBmbG9hdCBpb3IgKSB7XG5cdFx0ZmxvYXQgZnJhbWVidWZmZXJMb2QgPSBsb2cyKCB0cmFuc21pc3Npb25TYW1wbGVyU2l6ZS54ICkgKiBhcHBseUlvclRvUm91Z2huZXNzKCByb3VnaG5lc3MsIGlvciApO1xuXHRcdCNpZmRlZiBURVhUVVJFX0xPRF9FWFRcblx0XHRcdHJldHVybiB0ZXh0dXJlMkRMb2RFWFQoIHRyYW5zbWlzc2lvblNhbXBsZXJNYXAsIGZyYWdDb29yZC54eSwgZnJhbWVidWZmZXJMb2QgKTtcblx0XHQjZWxzZVxuXHRcdFx0cmV0dXJuIHRleHR1cmUyRCggdHJhbnNtaXNzaW9uU2FtcGxlck1hcCwgZnJhZ0Nvb3JkLnh5LCBmcmFtZWJ1ZmZlckxvZCApO1xuXHRcdCNlbmRpZlxuXHR9XG5cdHZlYzMgYXBwbHlWb2x1bWVBdHRlbnVhdGlvbiggY29uc3QgaW4gdmVjMyByYWRpYW5jZSwgY29uc3QgaW4gZmxvYXQgdHJhbnNtaXNzaW9uRGlzdGFuY2UsIGNvbnN0IGluIHZlYzMgYXR0ZW51YXRpb25Db2xvciwgY29uc3QgaW4gZmxvYXQgYXR0ZW51YXRpb25EaXN0YW5jZSApIHtcblx0XHRpZiAoIGF0dGVudWF0aW9uRGlzdGFuY2UgPT0gMC4wICkge1xuXHRcdFx0cmV0dXJuIHJhZGlhbmNlO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHR2ZWMzIGF0dGVudWF0aW9uQ29lZmZpY2llbnQgPSAtbG9nKCBhdHRlbnVhdGlvbkNvbG9yICkgLyBhdHRlbnVhdGlvbkRpc3RhbmNlO1xuXHRcdFx0dmVjMyB0cmFuc21pdHRhbmNlID0gZXhwKCAtIGF0dGVudWF0aW9uQ29lZmZpY2llbnQgKiB0cmFuc21pc3Npb25EaXN0YW5jZSApO1x0XHRcdHJldHVybiB0cmFuc21pdHRhbmNlICogcmFkaWFuY2U7XG5cdFx0fVxuXHR9XG5cdHZlYzQgZ2V0SUJMVm9sdW1lUmVmcmFjdGlvbiggY29uc3QgaW4gdmVjMyBuLCBjb25zdCBpbiB2ZWMzIHYsIGNvbnN0IGluIGZsb2F0IHJvdWdobmVzcywgY29uc3QgaW4gdmVjMyBkaWZmdXNlQ29sb3IsXG5cdFx0Y29uc3QgaW4gdmVjMyBzcGVjdWxhckNvbG9yLCBjb25zdCBpbiBmbG9hdCBzcGVjdWxhckY5MCwgY29uc3QgaW4gdmVjMyBwb3NpdGlvbiwgY29uc3QgaW4gbWF0NCBtb2RlbE1hdHJpeCxcblx0XHRjb25zdCBpbiBtYXQ0IHZpZXdNYXRyaXgsIGNvbnN0IGluIG1hdDQgcHJvak1hdHJpeCwgY29uc3QgaW4gZmxvYXQgaW9yLCBjb25zdCBpbiBmbG9hdCB0aGlja25lc3MsXG5cdFx0Y29uc3QgaW4gdmVjMyBhdHRlbnVhdGlvbkNvbG9yLCBjb25zdCBpbiBmbG9hdCBhdHRlbnVhdGlvbkRpc3RhbmNlICkge1xuXHRcdHZlYzMgdHJhbnNtaXNzaW9uUmF5ID0gZ2V0Vm9sdW1lVHJhbnNtaXNzaW9uUmF5KCBuLCB2LCB0aGlja25lc3MsIGlvciwgbW9kZWxNYXRyaXggKTtcblx0XHR2ZWMzIHJlZnJhY3RlZFJheUV4aXQgPSBwb3NpdGlvbiArIHRyYW5zbWlzc2lvblJheTtcblx0XHR2ZWM0IG5kY1BvcyA9IHByb2pNYXRyaXggKiB2aWV3TWF0cml4ICogdmVjNCggcmVmcmFjdGVkUmF5RXhpdCwgMS4wICk7XG5cdFx0dmVjMiByZWZyYWN0aW9uQ29vcmRzID0gbmRjUG9zLnh5IC8gbmRjUG9zLnc7XG5cdFx0cmVmcmFjdGlvbkNvb3JkcyArPSAxLjA7XG5cdFx0cmVmcmFjdGlvbkNvb3JkcyAvPSAyLjA7XG5cdFx0dmVjNCB0cmFuc21pdHRlZExpZ2h0ID0gZ2V0VHJhbnNtaXNzaW9uU2FtcGxlKCByZWZyYWN0aW9uQ29vcmRzLCByb3VnaG5lc3MsIGlvciApO1xuXHRcdHZlYzMgYXR0ZW51YXRlZENvbG9yID0gYXBwbHlWb2x1bWVBdHRlbnVhdGlvbiggdHJhbnNtaXR0ZWRMaWdodC5yZ2IsIGxlbmd0aCggdHJhbnNtaXNzaW9uUmF5ICksIGF0dGVudWF0aW9uQ29sb3IsIGF0dGVudWF0aW9uRGlzdGFuY2UgKTtcblx0XHR2ZWMzIEYgPSBFbnZpcm9ubWVudEJSREYoIG4sIHYsIHNwZWN1bGFyQ29sb3IsIHNwZWN1bGFyRjkwLCByb3VnaG5lc3MgKTtcblx0XHRyZXR1cm4gdmVjNCggKCAxLjAgLSBGICkgKiBhdHRlbnVhdGVkQ29sb3IgKiBkaWZmdXNlQ29sb3IsIHRyYW5zbWl0dGVkTGlnaHQuYSApO1xuXHR9XG4jZW5kaWYiLHV2X3BhcnNfZnJhZ21lbnQ6IiNpZiAoIGRlZmluZWQoIFVTRV9VViApICYmICEgZGVmaW5lZCggVVZTX1ZFUlRFWF9PTkxZICkgKVxuXHR2YXJ5aW5nIHZlYzIgdlV2O1xuI2VuZGlmIix1dl9wYXJzX3ZlcnRleDoiI2lmZGVmIFVTRV9VVlxuXHQjaWZkZWYgVVZTX1ZFUlRFWF9PTkxZXG5cdFx0dmVjMiB2VXY7XG5cdCNlbHNlXG5cdFx0dmFyeWluZyB2ZWMyIHZVdjtcblx0I2VuZGlmXG5cdHVuaWZvcm0gbWF0MyB1dlRyYW5zZm9ybTtcbiNlbmRpZiIsdXZfdmVydGV4OiIjaWZkZWYgVVNFX1VWXG5cdHZVdiA9ICggdXZUcmFuc2Zvcm0gKiB2ZWMzKCB1diwgMSApICkueHk7XG4jZW5kaWYiLHV2Ml9wYXJzX2ZyYWdtZW50OiIjaWYgZGVmaW5lZCggVVNFX0xJR0hUTUFQICkgfHwgZGVmaW5lZCggVVNFX0FPTUFQIClcblx0dmFyeWluZyB2ZWMyIHZVdjI7XG4jZW5kaWYiLHV2Ml9wYXJzX3ZlcnRleDoiI2lmIGRlZmluZWQoIFVTRV9MSUdIVE1BUCApIHx8IGRlZmluZWQoIFVTRV9BT01BUCApXG5cdGF0dHJpYnV0ZSB2ZWMyIHV2Mjtcblx0dmFyeWluZyB2ZWMyIHZVdjI7XG5cdHVuaWZvcm0gbWF0MyB1djJUcmFuc2Zvcm07XG4jZW5kaWYiLHV2Ml92ZXJ0ZXg6IiNpZiBkZWZpbmVkKCBVU0VfTElHSFRNQVAgKSB8fCBkZWZpbmVkKCBVU0VfQU9NQVAgKVxuXHR2VXYyID0gKCB1djJUcmFuc2Zvcm0gKiB2ZWMzKCB1djIsIDEgKSApLnh5O1xuI2VuZGlmIix3b3JsZHBvc192ZXJ0ZXg6IiNpZiBkZWZpbmVkKCBVU0VfRU5WTUFQICkgfHwgZGVmaW5lZCggRElTVEFOQ0UgKSB8fCBkZWZpbmVkICggVVNFX1NIQURPV01BUCApIHx8IGRlZmluZWQgKCBVU0VfVFJBTlNNSVNTSU9OIClcblx0dmVjNCB3b3JsZFBvc2l0aW9uID0gdmVjNCggdHJhbnNmb3JtZWQsIDEuMCApO1xuXHQjaWZkZWYgVVNFX0lOU1RBTkNJTkdcblx0XHR3b3JsZFBvc2l0aW9uID0gaW5zdGFuY2VNYXRyaXggKiB3b3JsZFBvc2l0aW9uO1xuXHQjZW5kaWZcblx0d29ybGRQb3NpdGlvbiA9IG1vZGVsTWF0cml4ICogd29ybGRQb3NpdGlvbjtcbiNlbmRpZiIsYmFja2dyb3VuZF92ZXJ0OiJ2YXJ5aW5nIHZlYzIgdlV2O1xudW5pZm9ybSBtYXQzIHV2VHJhbnNmb3JtO1xudm9pZCBtYWluKCkge1xuXHR2VXYgPSAoIHV2VHJhbnNmb3JtICogdmVjMyggdXYsIDEgKSApLnh5O1xuXHRnbF9Qb3NpdGlvbiA9IHZlYzQoIHBvc2l0aW9uLnh5LCAxLjAsIDEuMCApO1xufSIsYmFja2dyb3VuZF9mcmFnOiJ1bmlmb3JtIHNhbXBsZXIyRCB0MkQ7XG52YXJ5aW5nIHZlYzIgdlV2O1xudm9pZCBtYWluKCkge1xuXHRnbF9GcmFnQ29sb3IgPSB0ZXh0dXJlMkQoIHQyRCwgdlV2ICk7XG5cdCNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD5cblx0I2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD5cbn0iLGN1YmVfdmVydDoidmFyeWluZyB2ZWMzIHZXb3JsZERpcmVjdGlvbjtcbiNpbmNsdWRlIDxjb21tb24+XG52b2lkIG1haW4oKSB7XG5cdHZXb3JsZERpcmVjdGlvbiA9IHRyYW5zZm9ybURpcmVjdGlvbiggcG9zaXRpb24sIG1vZGVsTWF0cml4ICk7XG5cdCNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD5cblx0Z2xfUG9zaXRpb24ueiA9IGdsX1Bvc2l0aW9uLnc7XG59IixjdWJlX2ZyYWc6IiNpbmNsdWRlIDxlbnZtYXBfY29tbW9uX3BhcnNfZnJhZ21lbnQ+XG51bmlmb3JtIGZsb2F0IG9wYWNpdHk7XG52YXJ5aW5nIHZlYzMgdldvcmxkRGlyZWN0aW9uO1xuI2luY2x1ZGUgPGN1YmVfdXZfcmVmbGVjdGlvbl9mcmFnbWVudD5cbnZvaWQgbWFpbigpIHtcblx0dmVjMyB2UmVmbGVjdCA9IHZXb3JsZERpcmVjdGlvbjtcblx0I2luY2x1ZGUgPGVudm1hcF9mcmFnbWVudD5cblx0Z2xfRnJhZ0NvbG9yID0gZW52Q29sb3I7XG5cdGdsX0ZyYWdDb2xvci5hICo9IG9wYWNpdHk7XG5cdCNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD5cblx0I2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD5cbn0iLGRlcHRoX3ZlcnQ6IiNpbmNsdWRlIDxjb21tb24+XG4jaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPHNraW5uaW5nX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD5cbnZhcnlpbmcgdmVjMiB2SGlnaFByZWNpc2lvblpXO1xudm9pZCBtYWluKCkge1xuXHQjaW5jbHVkZSA8dXZfdmVydGV4PlxuXHQjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PlxuXHQjaWZkZWYgVVNFX0RJU1BMQUNFTUVOVE1BUFxuXHRcdCNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+XG5cdFx0I2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD5cblx0XHQjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+XG5cdCNlbmRpZlxuXHQjaW5jbHVkZSA8YmVnaW5fdmVydGV4PlxuXHQjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PlxuXHQjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PlxuXHQjaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3ZlcnRleD5cblx0I2luY2x1ZGUgPHByb2plY3RfdmVydGV4PlxuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PlxuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD5cblx0dkhpZ2hQcmVjaXNpb25aVyA9IGdsX1Bvc2l0aW9uLnp3O1xufSIsZGVwdGhfZnJhZzoiI2lmIERFUFRIX1BBQ0tJTkcgPT0gMzIwMFxuXHR1bmlmb3JtIGZsb2F0IG9wYWNpdHk7XG4jZW5kaWZcbiNpbmNsdWRlIDxjb21tb24+XG4jaW5jbHVkZSA8cGFja2luZz5cbiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGFscGhhbWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8YWxwaGF0ZXN0X3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc19mcmFnbWVudD5cbnZhcnlpbmcgdmVjMiB2SGlnaFByZWNpc2lvblpXO1xudm9pZCBtYWluKCkge1xuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PlxuXHR2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIDEuMCApO1xuXHQjaWYgREVQVEhfUEFDS0lORyA9PSAzMjAwXG5cdFx0ZGlmZnVzZUNvbG9yLmEgPSBvcGFjaXR5O1xuXHQjZW5kaWZcblx0I2luY2x1ZGUgPG1hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfZnJhZ21lbnQ+XG5cdGZsb2F0IGZyYWdDb29yZFogPSAwLjUgKiB2SGlnaFByZWNpc2lvblpXWzBdIC8gdkhpZ2hQcmVjaXNpb25aV1sxXSArIDAuNTtcblx0I2lmIERFUFRIX1BBQ0tJTkcgPT0gMzIwMFxuXHRcdGdsX0ZyYWdDb2xvciA9IHZlYzQoIHZlYzMoIDEuMCAtIGZyYWdDb29yZFogKSwgb3BhY2l0eSApO1xuXHQjZWxpZiBERVBUSF9QQUNLSU5HID09IDMyMDFcblx0XHRnbF9GcmFnQ29sb3IgPSBwYWNrRGVwdGhUb1JHQkEoIGZyYWdDb29yZFogKTtcblx0I2VuZGlmXG59IixkaXN0YW5jZVJHQkFfdmVydDoiI2RlZmluZSBESVNUQU5DRVxudmFyeWluZyB2ZWMzIHZXb3JsZFBvc2l0aW9uO1xuI2luY2x1ZGUgPGNvbW1vbj5cbiNpbmNsdWRlIDx1dl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8c2tpbm5pbmdfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfdmVydGV4Plxudm9pZCBtYWluKCkge1xuXHQjaW5jbHVkZSA8dXZfdmVydGV4PlxuXHQjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PlxuXHQjaWZkZWYgVVNFX0RJU1BMQUNFTUVOVE1BUFxuXHRcdCNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+XG5cdFx0I2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD5cblx0XHQjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+XG5cdCNlbmRpZlxuXHQjaW5jbHVkZSA8YmVnaW5fdmVydGV4PlxuXHQjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PlxuXHQjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PlxuXHQjaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3ZlcnRleD5cblx0I2luY2x1ZGUgPHByb2plY3RfdmVydGV4PlxuXHQjaW5jbHVkZSA8d29ybGRwb3NfdmVydGV4PlxuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD5cblx0dldvcmxkUG9zaXRpb24gPSB3b3JsZFBvc2l0aW9uLnh5ejtcbn0iLGRpc3RhbmNlUkdCQV9mcmFnOiIjZGVmaW5lIERJU1RBTkNFXG51bmlmb3JtIHZlYzMgcmVmZXJlbmNlUG9zaXRpb247XG51bmlmb3JtIGZsb2F0IG5lYXJEaXN0YW5jZTtcbnVuaWZvcm0gZmxvYXQgZmFyRGlzdGFuY2U7XG52YXJ5aW5nIHZlYzMgdldvcmxkUG9zaXRpb247XG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPHBhY2tpbmc+XG4jaW5jbHVkZSA8dXZfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxhbHBoYW1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Plxudm9pZCBtYWluICgpIHtcblx0I2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19mcmFnbWVudD5cblx0dmVjNCBkaWZmdXNlQ29sb3IgPSB2ZWM0KCAxLjAgKTtcblx0I2luY2x1ZGUgPG1hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PlxuXHRmbG9hdCBkaXN0ID0gbGVuZ3RoKCB2V29ybGRQb3NpdGlvbiAtIHJlZmVyZW5jZVBvc2l0aW9uICk7XG5cdGRpc3QgPSAoIGRpc3QgLSBuZWFyRGlzdGFuY2UgKSAvICggZmFyRGlzdGFuY2UgLSBuZWFyRGlzdGFuY2UgKTtcblx0ZGlzdCA9IHNhdHVyYXRlKCBkaXN0ICk7XG5cdGdsX0ZyYWdDb2xvciA9IHBhY2tEZXB0aFRvUkdCQSggZGlzdCApO1xufSIsZXF1aXJlY3RfdmVydDoidmFyeWluZyB2ZWMzIHZXb3JsZERpcmVjdGlvbjtcbiNpbmNsdWRlIDxjb21tb24+XG52b2lkIG1haW4oKSB7XG5cdHZXb3JsZERpcmVjdGlvbiA9IHRyYW5zZm9ybURpcmVjdGlvbiggcG9zaXRpb24sIG1vZGVsTWF0cml4ICk7XG5cdCNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD5cbn0iLGVxdWlyZWN0X2ZyYWc6InVuaWZvcm0gc2FtcGxlcjJEIHRFcXVpcmVjdDtcbnZhcnlpbmcgdmVjMyB2V29ybGREaXJlY3Rpb247XG4jaW5jbHVkZSA8Y29tbW9uPlxudm9pZCBtYWluKCkge1xuXHR2ZWMzIGRpcmVjdGlvbiA9IG5vcm1hbGl6ZSggdldvcmxkRGlyZWN0aW9uICk7XG5cdHZlYzIgc2FtcGxlVVYgPSBlcXVpcmVjdFV2KCBkaXJlY3Rpb24gKTtcblx0Z2xfRnJhZ0NvbG9yID0gdGV4dHVyZTJEKCB0RXF1aXJlY3QsIHNhbXBsZVVWICk7XG5cdCNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD5cblx0I2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD5cbn0iLGxpbmVkYXNoZWRfdmVydDoidW5pZm9ybSBmbG9hdCBzY2FsZTtcbmF0dHJpYnV0ZSBmbG9hdCBsaW5lRGlzdGFuY2U7XG52YXJ5aW5nIGZsb2F0IHZMaW5lRGlzdGFuY2U7XG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+XG52b2lkIG1haW4oKSB7XG5cdHZMaW5lRGlzdGFuY2UgPSBzY2FsZSAqIGxpbmVEaXN0YW5jZTtcblx0I2luY2x1ZGUgPGNvbG9yX3ZlcnRleD5cblx0I2luY2x1ZGUgPGJlZ2luX3ZlcnRleD5cblx0I2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD5cblx0I2luY2x1ZGUgPHByb2plY3RfdmVydGV4PlxuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PlxuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD5cblx0I2luY2x1ZGUgPGZvZ192ZXJ0ZXg+XG59IixsaW5lZGFzaGVkX2ZyYWc6InVuaWZvcm0gdmVjMyBkaWZmdXNlO1xudW5pZm9ybSBmbG9hdCBvcGFjaXR5O1xudW5pZm9ybSBmbG9hdCBkYXNoU2l6ZTtcbnVuaWZvcm0gZmxvYXQgdG90YWxTaXplO1xudmFyeWluZyBmbG9hdCB2TGluZURpc3RhbmNlO1xuI2luY2x1ZGUgPGNvbW1vbj5cbiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+XG5cdGlmICggbW9kKCB2TGluZURpc3RhbmNlLCB0b3RhbFNpemUgKSA+IGRhc2hTaXplICkge1xuXHRcdGRpc2NhcmQ7XG5cdH1cblx0dmVjMyBvdXRnb2luZ0xpZ2h0ID0gdmVjMyggMC4wICk7XG5cdHZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggZGlmZnVzZSwgb3BhY2l0eSApO1xuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxjb2xvcl9mcmFnbWVudD5cblx0b3V0Z29pbmdMaWdodCA9IGRpZmZ1c2VDb2xvci5yZ2I7XG5cdCNpbmNsdWRlIDxvdXRwdXRfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD5cblx0I2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD5cblx0I2luY2x1ZGUgPGZvZ19mcmFnbWVudD5cblx0I2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+XG59IixtZXNoYmFzaWNfdmVydDoiI2luY2x1ZGUgPGNvbW1vbj5cbiNpbmNsdWRlIDx1dl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDx1djJfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8ZW52bWFwX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDx1dl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDx1djJfdmVydGV4PlxuXHQjaW5jbHVkZSA8Y29sb3JfdmVydGV4PlxuXHQjaWYgZGVmaW5lZCAoIFVTRV9FTlZNQVAgKSB8fCBkZWZpbmVkICggVVNFX1NLSU5OSU5HIClcblx0XHQjaW5jbHVkZSA8YmVnaW5ub3JtYWxfdmVydGV4PlxuXHRcdCNpbmNsdWRlIDxtb3JwaG5vcm1hbF92ZXJ0ZXg+XG5cdFx0I2luY2x1ZGUgPHNraW5iYXNlX3ZlcnRleD5cblx0XHQjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+XG5cdFx0I2luY2x1ZGUgPGRlZmF1bHRub3JtYWxfdmVydGV4PlxuXHQjZW5kaWZcblx0I2luY2x1ZGUgPGJlZ2luX3ZlcnRleD5cblx0I2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD5cblx0I2luY2x1ZGUgPHByb2plY3RfdmVydGV4PlxuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PlxuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD5cblx0I2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD5cblx0I2luY2x1ZGUgPGVudm1hcF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxmb2dfdmVydGV4PlxufSIsbWVzaGJhc2ljX2ZyYWc6InVuaWZvcm0gdmVjMyBkaWZmdXNlO1xudW5pZm9ybSBmbG9hdCBvcGFjaXR5O1xuI2lmbmRlZiBGTEFUX1NIQURFRFxuXHR2YXJ5aW5nIHZlYzMgdk5vcm1hbDtcbiNlbmRpZlxuI2luY2x1ZGUgPGNvbW1vbj5cbiNpbmNsdWRlIDxkaXRoZXJpbmdfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8dXYyX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8bWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxhb21hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxpZ2h0bWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8ZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGVudm1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGN1YmVfdXZfcmVmbGVjdGlvbl9mcmFnbWVudD5cbiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxzcGVjdWxhcm1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+XG5cdHZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggZGlmZnVzZSwgb3BhY2l0eSApO1xuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxtYXBfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxjb2xvcl9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8c3BlY3VsYXJtYXBfZnJhZ21lbnQ+XG5cdFJlZmxlY3RlZExpZ2h0IHJlZmxlY3RlZExpZ2h0ID0gUmVmbGVjdGVkTGlnaHQoIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApICk7XG5cdCNpZmRlZiBVU0VfTElHSFRNQVBcblx0XHR2ZWM0IGxpZ2h0TWFwVGV4ZWw9IHRleHR1cmUyRCggbGlnaHRNYXAsIHZVdjIgKTtcblx0XHRyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKz0gbGlnaHRNYXBUZXhlbC5yZ2IgKiBsaWdodE1hcEludGVuc2l0eTtcblx0I2Vsc2Vcblx0XHRyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKz0gdmVjMyggMS4wICk7XG5cdCNlbmRpZlxuXHQjaW5jbHVkZSA8YW9tYXBfZnJhZ21lbnQ+XG5cdHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSAqPSBkaWZmdXNlQ29sb3IucmdiO1xuXHR2ZWMzIG91dGdvaW5nTGlnaHQgPSByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2U7XG5cdCNpbmNsdWRlIDxlbnZtYXBfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxvdXRwdXRfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDx0b25lbWFwcGluZ19mcmFnbWVudD5cblx0I2luY2x1ZGUgPGVuY29kaW5nc19mcmFnbWVudD5cblx0I2luY2x1ZGUgPGZvZ19mcmFnbWVudD5cblx0I2luY2x1ZGUgPHByZW11bHRpcGxpZWRfYWxwaGFfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxkaXRoZXJpbmdfZnJhZ21lbnQ+XG59IixtZXNobGFtYmVydF92ZXJ0OiIjZGVmaW5lIExBTUJFUlRcbnZhcnlpbmcgdmVjMyB2TGlnaHRGcm9udDtcbnZhcnlpbmcgdmVjMyB2SW5kaXJlY3RGcm9udDtcbiNpZmRlZiBET1VCTEVfU0lERURcblx0dmFyeWluZyB2ZWMzIHZMaWdodEJhY2s7XG5cdHZhcnlpbmcgdmVjMyB2SW5kaXJlY3RCYWNrO1xuI2VuZGlmXG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPHV2Ml9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxlbnZtYXBfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8YnNkZnM+XG4jaW5jbHVkZSA8bGlnaHRzX3BhcnNfYmVnaW4+XG4jaW5jbHVkZSA8Y29sb3JfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPHNraW5uaW5nX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDx1dl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDx1djJfdmVydGV4PlxuXHQjaW5jbHVkZSA8Y29sb3JfdmVydGV4PlxuXHQjaW5jbHVkZSA8YmVnaW5ub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8bW9ycGhub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8c2tpbmJhc2VfdmVydGV4PlxuXHQjaW5jbHVkZSA8c2tpbm5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxkZWZhdWx0bm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPGJlZ2luX3ZlcnRleD5cblx0I2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD5cblx0I2luY2x1ZGUgPHByb2plY3RfdmVydGV4PlxuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PlxuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD5cblx0I2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD5cblx0I2luY2x1ZGUgPGVudm1hcF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxsaWdodHNfbGFtYmVydF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxzaGFkb3dtYXBfdmVydGV4PlxuXHQjaW5jbHVkZSA8Zm9nX3ZlcnRleD5cbn0iLG1lc2hsYW1iZXJ0X2ZyYWc6InVuaWZvcm0gdmVjMyBkaWZmdXNlO1xudW5pZm9ybSB2ZWMzIGVtaXNzaXZlO1xudW5pZm9ybSBmbG9hdCBvcGFjaXR5O1xudmFyeWluZyB2ZWMzIHZMaWdodEZyb250O1xudmFyeWluZyB2ZWMzIHZJbmRpcmVjdEZyb250O1xuI2lmZGVmIERPVUJMRV9TSURFRFxuXHR2YXJ5aW5nIHZlYzMgdkxpZ2h0QmFjaztcblx0dmFyeWluZyB2ZWMzIHZJbmRpcmVjdEJhY2s7XG4jZW5kaWZcbiNpbmNsdWRlIDxjb21tb24+XG4jaW5jbHVkZSA8cGFja2luZz5cbiNpbmNsdWRlIDxkaXRoZXJpbmdfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8dXYyX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8bWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxhb21hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxpZ2h0bWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8ZW1pc3NpdmVtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxlbnZtYXBfY29tbW9uX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8ZW52bWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Y3ViZV91dl9yZWZsZWN0aW9uX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGJzZGZzPlxuI2luY2x1ZGUgPGxpZ2h0c19wYXJzX2JlZ2luPlxuI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHNoYWRvd21hc2tfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxzcGVjdWxhcm1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+XG5cdHZlYzQgZGlmZnVzZUNvbG9yID0gdmVjNCggZGlmZnVzZSwgb3BhY2l0eSApO1xuXHRSZWZsZWN0ZWRMaWdodCByZWZsZWN0ZWRMaWdodCA9IFJlZmxlY3RlZExpZ2h0KCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSApO1xuXHR2ZWMzIHRvdGFsRW1pc3NpdmVSYWRpYW5jZSA9IGVtaXNzaXZlO1xuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxtYXBfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxjb2xvcl9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGFscGhhbWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8YWxwaGF0ZXN0X2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8c3BlY3VsYXJtYXBfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxlbWlzc2l2ZW1hcF9mcmFnbWVudD5cblx0I2lmZGVmIERPVUJMRV9TSURFRFxuXHRcdHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArPSAoIGdsX0Zyb250RmFjaW5nICkgPyB2SW5kaXJlY3RGcm9udCA6IHZJbmRpcmVjdEJhY2s7XG5cdCNlbHNlXG5cdFx0cmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICs9IHZJbmRpcmVjdEZyb250O1xuXHQjZW5kaWZcblx0I2luY2x1ZGUgPGxpZ2h0bWFwX2ZyYWdtZW50PlxuXHRyZWZsZWN0ZWRMaWdodC5pbmRpcmVjdERpZmZ1c2UgKj0gQlJERl9MYW1iZXJ0KCBkaWZmdXNlQ29sb3IucmdiICk7XG5cdCNpZmRlZiBET1VCTEVfU0lERURcblx0XHRyZWZsZWN0ZWRMaWdodC5kaXJlY3REaWZmdXNlID0gKCBnbF9Gcm9udEZhY2luZyApID8gdkxpZ2h0RnJvbnQgOiB2TGlnaHRCYWNrO1xuXHQjZWxzZVxuXHRcdHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgPSB2TGlnaHRGcm9udDtcblx0I2VuZGlmXG5cdHJlZmxlY3RlZExpZ2h0LmRpcmVjdERpZmZ1c2UgKj0gQlJERl9MYW1iZXJ0KCBkaWZmdXNlQ29sb3IucmdiICkgKiBnZXRTaGFkb3dNYXNrKCk7XG5cdCNpbmNsdWRlIDxhb21hcF9mcmFnbWVudD5cblx0dmVjMyBvdXRnb2luZ0xpZ2h0ID0gcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArIHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZSArIHRvdGFsRW1pc3NpdmVSYWRpYW5jZTtcblx0I2luY2x1ZGUgPGVudm1hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8ZW5jb2RpbmdzX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8cHJlbXVsdGlwbGllZF9hbHBoYV9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGRpdGhlcmluZ19mcmFnbWVudD5cbn0iLG1lc2htYXRjYXBfdmVydDoiI2RlZmluZSBNQVRDQVBcbnZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uO1xuI2luY2x1ZGUgPGNvbW1vbj5cbiNpbmNsdWRlIDx1dl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxjb2xvcl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPG5vcm1hbF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDx1dl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxjb2xvcl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxtb3JwaG5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPGRlZmF1bHRub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8bm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPGJlZ2luX3ZlcnRleD5cblx0I2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD5cblx0I2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD5cblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD5cblx0I2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxmb2dfdmVydGV4PlxuXHR2Vmlld1Bvc2l0aW9uID0gLSBtdlBvc2l0aW9uLnh5ejtcbn0iLG1lc2htYXRjYXBfZnJhZzoiI2RlZmluZSBNQVRDQVBcbnVuaWZvcm0gdmVjMyBkaWZmdXNlO1xudW5pZm9ybSBmbG9hdCBvcGFjaXR5O1xudW5pZm9ybSBzYW1wbGVyMkQgbWF0Y2FwO1xudmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247XG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPGRpdGhlcmluZ19wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGNvbG9yX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8dXZfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxhbHBoYW1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPG5vcm1hbF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGJ1bXBtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxub3JtYWxtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Plxudm9pZCBtYWluKCkge1xuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PlxuXHR2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTtcblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9iZWdpbj5cblx0I2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9tYXBzPlxuXHR2ZWMzIHZpZXdEaXIgPSBub3JtYWxpemUoIHZWaWV3UG9zaXRpb24gKTtcblx0dmVjMyB4ID0gbm9ybWFsaXplKCB2ZWMzKCB2aWV3RGlyLnosIDAuMCwgLSB2aWV3RGlyLnggKSApO1xuXHR2ZWMzIHkgPSBjcm9zcyggdmlld0RpciwgeCApO1xuXHR2ZWMyIHV2ID0gdmVjMiggZG90KCB4LCBub3JtYWwgKSwgZG90KCB5LCBub3JtYWwgKSApICogMC40OTUgKyAwLjU7XG5cdCNpZmRlZiBVU0VfTUFUQ0FQXG5cdFx0dmVjNCBtYXRjYXBDb2xvciA9IHRleHR1cmUyRCggbWF0Y2FwLCB1diApO1xuXHQjZWxzZVxuXHRcdHZlYzQgbWF0Y2FwQ29sb3IgPSB2ZWM0KCB2ZWMzKCBtaXgoIDAuMiwgMC44LCB1di55ICkgKSwgMS4wICk7XG5cdCNlbmRpZlxuXHR2ZWMzIG91dGdvaW5nTGlnaHQgPSBkaWZmdXNlQ29sb3IucmdiICogbWF0Y2FwQ29sb3IucmdiO1xuXHQjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxwcmVtdWx0aXBsaWVkX2FscGhhX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50PlxufSIsbWVzaG5vcm1hbF92ZXJ0OiIjZGVmaW5lIE5PUk1BTFxuI2lmIGRlZmluZWQoIEZMQVRfU0hBREVEICkgfHwgZGVmaW5lZCggVVNFX0JVTVBNQVAgKSB8fCBkZWZpbmVkKCBUQU5HRU5UU1BBQ0VfTk9STUFMTUFQIClcblx0dmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247XG4jZW5kaWZcbiNpbmNsdWRlIDxjb21tb24+XG4jaW5jbHVkZSA8dXZfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPG5vcm1hbF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDx1dl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxtb3JwaG5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPGRlZmF1bHRub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8bm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPGJlZ2luX3ZlcnRleD5cblx0I2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD5cblx0I2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD5cblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD5cblx0I2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+XG4jaWYgZGVmaW5lZCggRkxBVF9TSEFERUQgKSB8fCBkZWZpbmVkKCBVU0VfQlVNUE1BUCApIHx8IGRlZmluZWQoIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAgKVxuXHR2Vmlld1Bvc2l0aW9uID0gLSBtdlBvc2l0aW9uLnh5ejtcbiNlbmRpZlxufSIsbWVzaG5vcm1hbF9mcmFnOiIjZGVmaW5lIE5PUk1BTFxudW5pZm9ybSBmbG9hdCBvcGFjaXR5O1xuI2lmIGRlZmluZWQoIEZMQVRfU0hBREVEICkgfHwgZGVmaW5lZCggVVNFX0JVTVBNQVAgKSB8fCBkZWZpbmVkKCBUQU5HRU5UU1BBQ0VfTk9STUFMTUFQIClcblx0dmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247XG4jZW5kaWZcbiNpbmNsdWRlIDxwYWNraW5nPlxuI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8bm9ybWFsX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8YnVtcG1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPG5vcm1hbG1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9mcmFnbWVudD5cblx0I2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9iZWdpbj5cblx0I2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9tYXBzPlxuXHRnbF9GcmFnQ29sb3IgPSB2ZWM0KCBwYWNrTm9ybWFsVG9SR0IoIG5vcm1hbCApLCBvcGFjaXR5ICk7XG59IixtZXNocGhvbmdfdmVydDoiI2RlZmluZSBQSE9OR1xudmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247XG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPHV2Ml9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8ZW52bWFwX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxub3JtYWxfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8bW9ycGh0YXJnZXRfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8c2tpbm5pbmdfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8c2hhZG93bWFwX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX3ZlcnRleD5cbnZvaWQgbWFpbigpIHtcblx0I2luY2x1ZGUgPHV2X3ZlcnRleD5cblx0I2luY2x1ZGUgPHV2Ml92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxjb2xvcl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxiZWdpbm5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxtb3JwaG5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxza2luYmFzZV92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxza2lubm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPGRlZmF1bHRub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8bm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPGJlZ2luX3ZlcnRleD5cblx0I2luY2x1ZGUgPG1vcnBodGFyZ2V0X3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5uaW5nX3ZlcnRleD5cblx0I2luY2x1ZGUgPGRpc3BsYWNlbWVudG1hcF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD5cblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD5cblx0I2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+XG5cdHZWaWV3UG9zaXRpb24gPSAtIG12UG9zaXRpb24ueHl6O1xuXHQjaW5jbHVkZSA8d29ybGRwb3NfdmVydGV4PlxuXHQjaW5jbHVkZSA8ZW52bWFwX3ZlcnRleD5cblx0I2luY2x1ZGUgPHNoYWRvd21hcF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxmb2dfdmVydGV4PlxufSIsbWVzaHBob25nX2ZyYWc6IiNkZWZpbmUgUEhPTkdcbnVuaWZvcm0gdmVjMyBkaWZmdXNlO1xudW5pZm9ybSB2ZWMzIGVtaXNzaXZlO1xudW5pZm9ybSB2ZWMzIHNwZWN1bGFyO1xudW5pZm9ybSBmbG9hdCBzaGluaW5lc3M7XG51bmlmb3JtIGZsb2F0IG9wYWNpdHk7XG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPHBhY2tpbmc+XG4jaW5jbHVkZSA8ZGl0aGVyaW5nX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Y29sb3JfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHV2Ml9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGFscGhhbWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8YWxwaGF0ZXN0X3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8YW9tYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxsaWdodG1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGVtaXNzaXZlbWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8ZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGVudm1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGN1YmVfdXZfcmVmbGVjdGlvbl9mcmFnbWVudD5cbiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxic2Rmcz5cbiNpbmNsdWRlIDxsaWdodHNfcGFyc19iZWdpbj5cbiNpbmNsdWRlIDxub3JtYWxfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxsaWdodHNfcGhvbmdfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxzaGFkb3dtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxidW1wbWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8bm9ybWFsbWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8c3BlY3VsYXJtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Plxudm9pZCBtYWluKCkge1xuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PlxuXHR2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTtcblx0UmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgPSBSZWZsZWN0ZWRMaWdodCggdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICkgKTtcblx0dmVjMyB0b3RhbEVtaXNzaXZlUmFkaWFuY2UgPSBlbWlzc2l2ZTtcblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPHNwZWN1bGFybWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bm9ybWFsX2ZyYWdtZW50X2JlZ2luPlxuXHQjaW5jbHVkZSA8bm9ybWFsX2ZyYWdtZW50X21hcHM+XG5cdCNpbmNsdWRlIDxlbWlzc2l2ZW1hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGxpZ2h0c19waG9uZ19mcmFnbWVudD5cblx0I2luY2x1ZGUgPGxpZ2h0c19mcmFnbWVudF9iZWdpbj5cblx0I2luY2x1ZGUgPGxpZ2h0c19mcmFnbWVudF9tYXBzPlxuXHQjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X2VuZD5cblx0I2luY2x1ZGUgPGFvbWFwX2ZyYWdtZW50PlxuXHR2ZWMzIG91dGdvaW5nTGlnaHQgPSByZWZsZWN0ZWRMaWdodC5kaXJlY3REaWZmdXNlICsgcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICsgcmVmbGVjdGVkTGlnaHQuZGlyZWN0U3BlY3VsYXIgKyByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdFNwZWN1bGFyICsgdG90YWxFbWlzc2l2ZVJhZGlhbmNlO1xuXHQjaW5jbHVkZSA8ZW52bWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxwcmVtdWx0aXBsaWVkX2FscGhhX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50PlxufSIsbWVzaHBoeXNpY2FsX3ZlcnQ6IiNkZWZpbmUgU1RBTkRBUkRcbnZhcnlpbmcgdmVjMyB2Vmlld1Bvc2l0aW9uO1xuI2lmZGVmIFVTRV9UUkFOU01JU1NJT05cblx0dmFyeWluZyB2ZWMzIHZXb3JsZFBvc2l0aW9uO1xuI2VuZGlmXG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPHV2Ml9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8Y29sb3JfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPG5vcm1hbF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxzaGFkb3dtYXBfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfdmVydGV4Plxudm9pZCBtYWluKCkge1xuXHQjaW5jbHVkZSA8dXZfdmVydGV4PlxuXHQjaW5jbHVkZSA8dXYyX3ZlcnRleD5cblx0I2luY2x1ZGUgPGNvbG9yX3ZlcnRleD5cblx0I2luY2x1ZGUgPGJlZ2lubm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5iYXNlX3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5ub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8YmVnaW5fdmVydGV4PlxuXHQjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PlxuXHQjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PlxuXHQjaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3ZlcnRleD5cblx0I2luY2x1ZGUgPHByb2plY3RfdmVydGV4PlxuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PlxuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD5cblx0dlZpZXdQb3NpdGlvbiA9IC0gbXZQb3NpdGlvbi54eXo7XG5cdCNpbmNsdWRlIDx3b3JsZHBvc192ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxzaGFkb3dtYXBfdmVydGV4PlxuXHQjaW5jbHVkZSA8Zm9nX3ZlcnRleD5cbiNpZmRlZiBVU0VfVFJBTlNNSVNTSU9OXG5cdHZXb3JsZFBvc2l0aW9uID0gd29ybGRQb3NpdGlvbi54eXo7XG4jZW5kaWZcbn0iLG1lc2hwaHlzaWNhbF9mcmFnOiIjZGVmaW5lIFNUQU5EQVJEXG4jaWZkZWYgUEhZU0lDQUxcblx0I2RlZmluZSBJT1Jcblx0I2RlZmluZSBTUEVDVUxBUlxuI2VuZGlmXG51bmlmb3JtIHZlYzMgZGlmZnVzZTtcbnVuaWZvcm0gdmVjMyBlbWlzc2l2ZTtcbnVuaWZvcm0gZmxvYXQgcm91Z2huZXNzO1xudW5pZm9ybSBmbG9hdCBtZXRhbG5lc3M7XG51bmlmb3JtIGZsb2F0IG9wYWNpdHk7XG4jaWZkZWYgSU9SXG5cdHVuaWZvcm0gZmxvYXQgaW9yO1xuI2VuZGlmXG4jaWZkZWYgU1BFQ1VMQVJcblx0dW5pZm9ybSBmbG9hdCBzcGVjdWxhckludGVuc2l0eTtcblx0dW5pZm9ybSB2ZWMzIHNwZWN1bGFyQ29sb3I7XG5cdCNpZmRlZiBVU0VfU1BFQ1VMQVJJTlRFTlNJVFlNQVBcblx0XHR1bmlmb3JtIHNhbXBsZXIyRCBzcGVjdWxhckludGVuc2l0eU1hcDtcblx0I2VuZGlmXG5cdCNpZmRlZiBVU0VfU1BFQ1VMQVJDT0xPUk1BUFxuXHRcdHVuaWZvcm0gc2FtcGxlcjJEIHNwZWN1bGFyQ29sb3JNYXA7XG5cdCNlbmRpZlxuI2VuZGlmXG4jaWZkZWYgVVNFX0NMRUFSQ09BVFxuXHR1bmlmb3JtIGZsb2F0IGNsZWFyY29hdDtcblx0dW5pZm9ybSBmbG9hdCBjbGVhcmNvYXRSb3VnaG5lc3M7XG4jZW5kaWZcbiNpZmRlZiBVU0VfU0hFRU5cblx0dW5pZm9ybSB2ZWMzIHNoZWVuQ29sb3I7XG5cdHVuaWZvcm0gZmxvYXQgc2hlZW5Sb3VnaG5lc3M7XG5cdCNpZmRlZiBVU0VfU0hFRU5DT0xPUk1BUFxuXHRcdHVuaWZvcm0gc2FtcGxlcjJEIHNoZWVuQ29sb3JNYXA7XG5cdCNlbmRpZlxuXHQjaWZkZWYgVVNFX1NIRUVOUk9VR0hORVNTTUFQXG5cdFx0dW5pZm9ybSBzYW1wbGVyMkQgc2hlZW5Sb3VnaG5lc3NNYXA7XG5cdCNlbmRpZlxuI2VuZGlmXG52YXJ5aW5nIHZlYzMgdlZpZXdQb3NpdGlvbjtcbiNpbmNsdWRlIDxjb21tb24+XG4jaW5jbHVkZSA8cGFja2luZz5cbiNpbmNsdWRlIDxkaXRoZXJpbmdfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHV2X3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8dXYyX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8bWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8YWxwaGFtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxhbHBoYXRlc3RfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxhb21hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxpZ2h0bWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8ZW1pc3NpdmVtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxic2Rmcz5cbiNpbmNsdWRlIDxjdWJlX3V2X3JlZmxlY3Rpb25fZnJhZ21lbnQ+XG4jaW5jbHVkZSA8ZW52bWFwX2NvbW1vbl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGVudm1hcF9waHlzaWNhbF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxpZ2h0c19wYXJzX2JlZ2luPlxuI2luY2x1ZGUgPG5vcm1hbF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxpZ2h0c19waHlzaWNhbF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHRyYW5zbWlzc2lvbl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGJ1bXBtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxub3JtYWxtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxjbGVhcmNvYXRfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxyb3VnaG5lc3NtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxtZXRhbG5lc3NtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Plxudm9pZCBtYWluKCkge1xuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PlxuXHR2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTtcblx0UmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgPSBSZWZsZWN0ZWRMaWdodCggdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICkgKTtcblx0dmVjMyB0b3RhbEVtaXNzaXZlUmFkaWFuY2UgPSBlbWlzc2l2ZTtcblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPHJvdWdobmVzc21hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPG1ldGFsbmVzc21hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9iZWdpbj5cblx0I2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9tYXBzPlxuXHQjaW5jbHVkZSA8Y2xlYXJjb2F0X25vcm1hbF9mcmFnbWVudF9iZWdpbj5cblx0I2luY2x1ZGUgPGNsZWFyY29hdF9ub3JtYWxfZnJhZ21lbnRfbWFwcz5cblx0I2luY2x1ZGUgPGVtaXNzaXZlbWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bGlnaHRzX3BoeXNpY2FsX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X2JlZ2luPlxuXHQjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X21hcHM+XG5cdCNpbmNsdWRlIDxsaWdodHNfZnJhZ21lbnRfZW5kPlxuXHQjaW5jbHVkZSA8YW9tYXBfZnJhZ21lbnQ+XG5cdHZlYzMgdG90YWxEaWZmdXNlID0gcmVmbGVjdGVkTGlnaHQuZGlyZWN0RGlmZnVzZSArIHJlZmxlY3RlZExpZ2h0LmluZGlyZWN0RGlmZnVzZTtcblx0dmVjMyB0b3RhbFNwZWN1bGFyID0gcmVmbGVjdGVkTGlnaHQuZGlyZWN0U3BlY3VsYXIgKyByZWZsZWN0ZWRMaWdodC5pbmRpcmVjdFNwZWN1bGFyO1xuXHQjaW5jbHVkZSA8dHJhbnNtaXNzaW9uX2ZyYWdtZW50PlxuXHR2ZWMzIG91dGdvaW5nTGlnaHQgPSB0b3RhbERpZmZ1c2UgKyB0b3RhbFNwZWN1bGFyICsgdG90YWxFbWlzc2l2ZVJhZGlhbmNlO1xuXHQjaWZkZWYgVVNFX1NIRUVOXG5cdFx0ZmxvYXQgc2hlZW5FbmVyZ3lDb21wID0gMS4wIC0gMC4xNTcgKiBtYXgzKCBtYXRlcmlhbC5zaGVlbkNvbG9yICk7XG5cdFx0b3V0Z29pbmdMaWdodCA9IG91dGdvaW5nTGlnaHQgKiBzaGVlbkVuZXJneUNvbXAgKyBzaGVlblNwZWN1bGFyO1xuXHQjZW5kaWZcblx0I2lmZGVmIFVTRV9DTEVBUkNPQVRcblx0XHRmbG9hdCBkb3ROVmNjID0gc2F0dXJhdGUoIGRvdCggZ2VvbWV0cnkuY2xlYXJjb2F0Tm9ybWFsLCBnZW9tZXRyeS52aWV3RGlyICkgKTtcblx0XHR2ZWMzIEZjYyA9IEZfU2NobGljayggbWF0ZXJpYWwuY2xlYXJjb2F0RjAsIG1hdGVyaWFsLmNsZWFyY29hdEY5MCwgZG90TlZjYyApO1xuXHRcdG91dGdvaW5nTGlnaHQgPSBvdXRnb2luZ0xpZ2h0ICogKCAxLjAgLSBtYXRlcmlhbC5jbGVhcmNvYXQgKiBGY2MgKSArIGNsZWFyY29hdFNwZWN1bGFyICogbWF0ZXJpYWwuY2xlYXJjb2F0O1xuXHQjZW5kaWZcblx0I2luY2x1ZGUgPG91dHB1dF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPHRvbmVtYXBwaW5nX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8ZW5jb2RpbmdzX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8Zm9nX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8cHJlbXVsdGlwbGllZF9hbHBoYV9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGRpdGhlcmluZ19mcmFnbWVudD5cbn0iLG1lc2h0b29uX3ZlcnQ6IiNkZWZpbmUgVE9PTlxudmFyeWluZyB2ZWMzIHZWaWV3UG9zaXRpb247XG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPHV2Ml9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxkaXNwbGFjZW1lbnRtYXBfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8Y29sb3JfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPG5vcm1hbF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxza2lubmluZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxzaGFkb3dtYXBfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8bG9nZGVwdGhidWZfcGFyc192ZXJ0ZXg+XG4jaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfdmVydGV4Plxudm9pZCBtYWluKCkge1xuXHQjaW5jbHVkZSA8dXZfdmVydGV4PlxuXHQjaW5jbHVkZSA8dXYyX3ZlcnRleD5cblx0I2luY2x1ZGUgPGNvbG9yX3ZlcnRleD5cblx0I2luY2x1ZGUgPGJlZ2lubm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5iYXNlX3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5ub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8YmVnaW5fdmVydGV4PlxuXHQjaW5jbHVkZSA8bW9ycGh0YXJnZXRfdmVydGV4PlxuXHQjaW5jbHVkZSA8c2tpbm5pbmdfdmVydGV4PlxuXHQjaW5jbHVkZSA8ZGlzcGxhY2VtZW50bWFwX3ZlcnRleD5cblx0I2luY2x1ZGUgPHByb2plY3RfdmVydGV4PlxuXHQjaW5jbHVkZSA8bG9nZGVwdGhidWZfdmVydGV4PlxuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3ZlcnRleD5cblx0dlZpZXdQb3NpdGlvbiA9IC0gbXZQb3NpdGlvbi54eXo7XG5cdCNpbmNsdWRlIDx3b3JsZHBvc192ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxzaGFkb3dtYXBfdmVydGV4PlxuXHQjaW5jbHVkZSA8Zm9nX3ZlcnRleD5cbn0iLG1lc2h0b29uX2ZyYWc6IiNkZWZpbmUgVE9PTlxudW5pZm9ybSB2ZWMzIGRpZmZ1c2U7XG51bmlmb3JtIHZlYzMgZW1pc3NpdmU7XG51bmlmb3JtIGZsb2F0IG9wYWNpdHk7XG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPHBhY2tpbmc+XG4jaW5jbHVkZSA8ZGl0aGVyaW5nX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Y29sb3JfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDx1dl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHV2Ml9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPG1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGFscGhhbWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8YWxwaGF0ZXN0X3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8YW9tYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxsaWdodG1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGVtaXNzaXZlbWFwX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Z3JhZGllbnRtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxic2Rmcz5cbiNpbmNsdWRlIDxsaWdodHNfcGFyc19iZWdpbj5cbiNpbmNsdWRlIDxub3JtYWxfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxsaWdodHNfdG9vbl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGJ1bXBtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxub3JtYWxtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc19wYXJzX2ZyYWdtZW50Plxudm9pZCBtYWluKCkge1xuXHQjaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX2ZyYWdtZW50PlxuXHR2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTtcblx0UmVmbGVjdGVkTGlnaHQgcmVmbGVjdGVkTGlnaHQgPSBSZWZsZWN0ZWRMaWdodCggdmVjMyggMC4wICksIHZlYzMoIDAuMCApLCB2ZWMzKCAwLjAgKSwgdmVjMyggMC4wICkgKTtcblx0dmVjMyB0b3RhbEVtaXNzaXZlUmFkaWFuY2UgPSBlbWlzc2l2ZTtcblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxhbHBoYW1hcF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGFscGhhdGVzdF9mcmFnbWVudD5cblx0I2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9iZWdpbj5cblx0I2luY2x1ZGUgPG5vcm1hbF9mcmFnbWVudF9tYXBzPlxuXHQjaW5jbHVkZSA8ZW1pc3NpdmVtYXBfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxsaWdodHNfdG9vbl9mcmFnbWVudD5cblx0I2luY2x1ZGUgPGxpZ2h0c19mcmFnbWVudF9iZWdpbj5cblx0I2luY2x1ZGUgPGxpZ2h0c19mcmFnbWVudF9tYXBzPlxuXHQjaW5jbHVkZSA8bGlnaHRzX2ZyYWdtZW50X2VuZD5cblx0I2luY2x1ZGUgPGFvbWFwX2ZyYWdtZW50PlxuXHR2ZWMzIG91dGdvaW5nTGlnaHQgPSByZWZsZWN0ZWRMaWdodC5kaXJlY3REaWZmdXNlICsgcmVmbGVjdGVkTGlnaHQuaW5kaXJlY3REaWZmdXNlICsgdG90YWxFbWlzc2l2ZVJhZGlhbmNlO1xuXHQjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxwcmVtdWx0aXBsaWVkX2FscGhhX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8ZGl0aGVyaW5nX2ZyYWdtZW50PlxufSIscG9pbnRzX3ZlcnQ6InVuaWZvcm0gZmxvYXQgc2l6ZTtcbnVuaWZvcm0gZmxvYXQgc2NhbGU7XG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPGNvbG9yX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxtb3JwaHRhcmdldF9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDxjb2xvcl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD5cblx0Z2xfUG9pbnRTaXplID0gc2l6ZTtcblx0I2lmZGVmIFVTRV9TSVpFQVRURU5VQVRJT05cblx0XHRib29sIGlzUGVyc3BlY3RpdmUgPSBpc1BlcnNwZWN0aXZlTWF0cml4KCBwcm9qZWN0aW9uTWF0cml4ICk7XG5cdFx0aWYgKCBpc1BlcnNwZWN0aXZlICkgZ2xfUG9pbnRTaXplICo9ICggc2NhbGUgLyAtIG12UG9zaXRpb24ueiApO1xuXHQjZW5kaWZcblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3ZlcnRleD5cblx0I2luY2x1ZGUgPGNsaXBwaW5nX3BsYW5lc192ZXJ0ZXg+XG5cdCNpbmNsdWRlIDx3b3JsZHBvc192ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxmb2dfdmVydGV4PlxufSIscG9pbnRzX2ZyYWc6InVuaWZvcm0gdmVjMyBkaWZmdXNlO1xudW5pZm9ybSBmbG9hdCBvcGFjaXR5O1xuI2luY2x1ZGUgPGNvbW1vbj5cbiNpbmNsdWRlIDxjb2xvcl9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPG1hcF9wYXJ0aWNsZV9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+XG5cdHZlYzMgb3V0Z29pbmdMaWdodCA9IHZlYzMoIDAuMCApO1xuXHR2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTtcblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bWFwX3BhcnRpY2xlX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8Y29sb3JfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxhbHBoYXRlc3RfZnJhZ21lbnQ+XG5cdG91dGdvaW5nTGlnaHQgPSBkaWZmdXNlQ29sb3IucmdiO1xuXHQjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxwcmVtdWx0aXBsaWVkX2FscGhhX2ZyYWdtZW50PlxufSIsc2hhZG93X3ZlcnQ6IiNpbmNsdWRlIDxjb21tb24+XG4jaW5jbHVkZSA8Zm9nX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPG1vcnBodGFyZ2V0X3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPHNraW5uaW5nX3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPHNoYWRvd21hcF9wYXJzX3ZlcnRleD5cbnZvaWQgbWFpbigpIHtcblx0I2luY2x1ZGUgPGJlZ2lubm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPG1vcnBobm9ybWFsX3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5iYXNlX3ZlcnRleD5cblx0I2luY2x1ZGUgPHNraW5ub3JtYWxfdmVydGV4PlxuXHQjaW5jbHVkZSA8ZGVmYXVsdG5vcm1hbF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxiZWdpbl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxtb3JwaHRhcmdldF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxza2lubmluZ192ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxwcm9qZWN0X3ZlcnRleD5cblx0I2luY2x1ZGUgPHdvcmxkcG9zX3ZlcnRleD5cblx0I2luY2x1ZGUgPHNoYWRvd21hcF92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxmb2dfdmVydGV4PlxufSIsc2hhZG93X2ZyYWc6InVuaWZvcm0gdmVjMyBjb2xvcjtcbnVuaWZvcm0gZmxvYXQgb3BhY2l0eTtcbiNpbmNsdWRlIDxjb21tb24+XG4jaW5jbHVkZSA8cGFja2luZz5cbiNpbmNsdWRlIDxmb2dfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxic2Rmcz5cbiNpbmNsdWRlIDxsaWdodHNfcGFyc19iZWdpbj5cbiNpbmNsdWRlIDxzaGFkb3dtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxzaGFkb3dtYXNrX3BhcnNfZnJhZ21lbnQ+XG52b2lkIG1haW4oKSB7XG5cdGdsX0ZyYWdDb2xvciA9IHZlYzQoIGNvbG9yLCBvcGFjaXR5ICogKCAxLjAgLSBnZXRTaGFkb3dNYXNrKCkgKSApO1xuXHQjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+XG59IixzcHJpdGVfdmVydDoidW5pZm9ybSBmbG9hdCByb3RhdGlvbjtcbnVuaWZvcm0gdmVjMiBjZW50ZXI7XG4jaW5jbHVkZSA8Y29tbW9uPlxuI2luY2x1ZGUgPHV2X3BhcnNfdmVydGV4PlxuI2luY2x1ZGUgPGZvZ19wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleD5cbiNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfcGFyc192ZXJ0ZXg+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDx1dl92ZXJ0ZXg+XG5cdHZlYzQgbXZQb3NpdGlvbiA9IG1vZGVsVmlld01hdHJpeCAqIHZlYzQoIDAuMCwgMC4wLCAwLjAsIDEuMCApO1xuXHR2ZWMyIHNjYWxlO1xuXHRzY2FsZS54ID0gbGVuZ3RoKCB2ZWMzKCBtb2RlbE1hdHJpeFsgMCBdLngsIG1vZGVsTWF0cml4WyAwIF0ueSwgbW9kZWxNYXRyaXhbIDAgXS56ICkgKTtcblx0c2NhbGUueSA9IGxlbmd0aCggdmVjMyggbW9kZWxNYXRyaXhbIDEgXS54LCBtb2RlbE1hdHJpeFsgMSBdLnksIG1vZGVsTWF0cml4WyAxIF0ueiApICk7XG5cdCNpZm5kZWYgVVNFX1NJWkVBVFRFTlVBVElPTlxuXHRcdGJvb2wgaXNQZXJzcGVjdGl2ZSA9IGlzUGVyc3BlY3RpdmVNYXRyaXgoIHByb2plY3Rpb25NYXRyaXggKTtcblx0XHRpZiAoIGlzUGVyc3BlY3RpdmUgKSBzY2FsZSAqPSAtIG12UG9zaXRpb24uejtcblx0I2VuZGlmXG5cdHZlYzIgYWxpZ25lZFBvc2l0aW9uID0gKCBwb3NpdGlvbi54eSAtICggY2VudGVyIC0gdmVjMiggMC41ICkgKSApICogc2NhbGU7XG5cdHZlYzIgcm90YXRlZFBvc2l0aW9uO1xuXHRyb3RhdGVkUG9zaXRpb24ueCA9IGNvcyggcm90YXRpb24gKSAqIGFsaWduZWRQb3NpdGlvbi54IC0gc2luKCByb3RhdGlvbiApICogYWxpZ25lZFBvc2l0aW9uLnk7XG5cdHJvdGF0ZWRQb3NpdGlvbi55ID0gc2luKCByb3RhdGlvbiApICogYWxpZ25lZFBvc2l0aW9uLnggKyBjb3MoIHJvdGF0aW9uICkgKiBhbGlnbmVkUG9zaXRpb24ueTtcblx0bXZQb3NpdGlvbi54eSArPSByb3RhdGVkUG9zaXRpb247XG5cdGdsX1Bvc2l0aW9uID0gcHJvamVjdGlvbk1hdHJpeCAqIG12UG9zaXRpb247XG5cdCNpbmNsdWRlIDxsb2dkZXB0aGJ1Zl92ZXJ0ZXg+XG5cdCNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfdmVydGV4PlxuXHQjaW5jbHVkZSA8Zm9nX3ZlcnRleD5cbn0iLHNwcml0ZV9mcmFnOiJ1bmlmb3JtIHZlYzMgZGlmZnVzZTtcbnVuaWZvcm0gZmxvYXQgb3BhY2l0eTtcbiNpbmNsdWRlIDxjb21tb24+XG4jaW5jbHVkZSA8dXZfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxtYXBfcGFyc19mcmFnbWVudD5cbiNpbmNsdWRlIDxhbHBoYW1hcF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGFscGhhdGVzdF9wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGZvZ19wYXJzX2ZyYWdtZW50PlxuI2luY2x1ZGUgPGxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQ+XG4jaW5jbHVkZSA8Y2xpcHBpbmdfcGxhbmVzX3BhcnNfZnJhZ21lbnQ+XG52b2lkIG1haW4oKSB7XG5cdCNpbmNsdWRlIDxjbGlwcGluZ19wbGFuZXNfZnJhZ21lbnQ+XG5cdHZlYzMgb3V0Z29pbmdMaWdodCA9IHZlYzMoIDAuMCApO1xuXHR2ZWM0IGRpZmZ1c2VDb2xvciA9IHZlYzQoIGRpZmZ1c2UsIG9wYWNpdHkgKTtcblx0I2luY2x1ZGUgPGxvZ2RlcHRoYnVmX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8bWFwX2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8YWxwaGFtYXBfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxhbHBoYXRlc3RfZnJhZ21lbnQ+XG5cdG91dGdvaW5nTGlnaHQgPSBkaWZmdXNlQ29sb3IucmdiO1xuXHQjaW5jbHVkZSA8b3V0cHV0X2ZyYWdtZW50PlxuXHQjaW5jbHVkZSA8dG9uZW1hcHBpbmdfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxlbmNvZGluZ3NfZnJhZ21lbnQ+XG5cdCNpbmNsdWRlIDxmb2dfZnJhZ21lbnQ+XG59In0sQnQ9e2NvbW1vbjp7ZGlmZnVzZTp7dmFsdWU6bmV3IHZuKDE2Nzc3MjE1KX0sb3BhY2l0eTp7dmFsdWU6MX0sbWFwOnt2YWx1ZTpudWxsfSx1dlRyYW5zZm9ybTp7dmFsdWU6bmV3IEpvfSx1djJUcmFuc2Zvcm06e3ZhbHVlOm5ldyBKb30sYWxwaGFNYXA6e3ZhbHVlOm51bGx9LGFscGhhVGVzdDp7dmFsdWU6MH19LHNwZWN1bGFybWFwOntzcGVjdWxhck1hcDp7dmFsdWU6bnVsbH19LGVudm1hcDp7ZW52TWFwOnt2YWx1ZTpudWxsfSxmbGlwRW52TWFwOnt2YWx1ZTotMX0scmVmbGVjdGl2aXR5Ont2YWx1ZToxfSxpb3I6e3ZhbHVlOjEuNX0scmVmcmFjdGlvblJhdGlvOnt2YWx1ZTouOTh9fSxhb21hcDp7YW9NYXA6e3ZhbHVlOm51bGx9LGFvTWFwSW50ZW5zaXR5Ont2YWx1ZToxfX0sbGlnaHRtYXA6e2xpZ2h0TWFwOnt2YWx1ZTpudWxsfSxsaWdodE1hcEludGVuc2l0eTp7dmFsdWU6MX19LGVtaXNzaXZlbWFwOntlbWlzc2l2ZU1hcDp7dmFsdWU6bnVsbH19LGJ1bXBtYXA6e2J1bXBNYXA6e3ZhbHVlOm51bGx9LGJ1bXBTY2FsZTp7dmFsdWU6MX19LG5vcm1hbG1hcDp7bm9ybWFsTWFwOnt2YWx1ZTpudWxsfSxub3JtYWxTY2FsZTp7dmFsdWU6bmV3IGF0KDEsMSl9fSxkaXNwbGFjZW1lbnRtYXA6e2Rpc3BsYWNlbWVudE1hcDp7dmFsdWU6bnVsbH0sZGlzcGxhY2VtZW50U2NhbGU6e3ZhbHVlOjF9LGRpc3BsYWNlbWVudEJpYXM6e3ZhbHVlOjB9fSxyb3VnaG5lc3NtYXA6e3JvdWdobmVzc01hcDp7dmFsdWU6bnVsbH19LG1ldGFsbmVzc21hcDp7bWV0YWxuZXNzTWFwOnt2YWx1ZTpudWxsfX0sZ3JhZGllbnRtYXA6e2dyYWRpZW50TWFwOnt2YWx1ZTpudWxsfX0sZm9nOntmb2dEZW5zaXR5Ont2YWx1ZToyNWUtNX0sZm9nTmVhcjp7dmFsdWU6MX0sZm9nRmFyOnt2YWx1ZToyZTN9LGZvZ0NvbG9yOnt2YWx1ZTpuZXcgdm4oMTY3NzcyMTUpfX0sbGlnaHRzOnthbWJpZW50TGlnaHRDb2xvcjp7dmFsdWU6W119LGxpZ2h0UHJvYmU6e3ZhbHVlOltdfSxkaXJlY3Rpb25hbExpZ2h0czp7dmFsdWU6W10scHJvcGVydGllczp7ZGlyZWN0aW9uOnt9LGNvbG9yOnt9fX0sZGlyZWN0aW9uYWxMaWdodFNoYWRvd3M6e3ZhbHVlOltdLHByb3BlcnRpZXM6e3NoYWRvd0JpYXM6e30sc2hhZG93Tm9ybWFsQmlhczp7fSxzaGFkb3dSYWRpdXM6e30sc2hhZG93TWFwU2l6ZTp7fX19LGRpcmVjdGlvbmFsU2hhZG93TWFwOnt2YWx1ZTpbXX0sZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXg6e3ZhbHVlOltdfSxzcG90TGlnaHRzOnt2YWx1ZTpbXSxwcm9wZXJ0aWVzOntjb2xvcjp7fSxwb3NpdGlvbjp7fSxkaXJlY3Rpb246e30sZGlzdGFuY2U6e30sY29uZUNvczp7fSxwZW51bWJyYUNvczp7fSxkZWNheTp7fX19LHNwb3RMaWdodFNoYWRvd3M6e3ZhbHVlOltdLHByb3BlcnRpZXM6e3NoYWRvd0JpYXM6e30sc2hhZG93Tm9ybWFsQmlhczp7fSxzaGFkb3dSYWRpdXM6e30sc2hhZG93TWFwU2l6ZTp7fX19LHNwb3RTaGFkb3dNYXA6e3ZhbHVlOltdfSxzcG90U2hhZG93TWF0cml4Ont2YWx1ZTpbXX0scG9pbnRMaWdodHM6e3ZhbHVlOltdLHByb3BlcnRpZXM6e2NvbG9yOnt9LHBvc2l0aW9uOnt9LGRlY2F5Ont9LGRpc3RhbmNlOnt9fX0scG9pbnRMaWdodFNoYWRvd3M6e3ZhbHVlOltdLHByb3BlcnRpZXM6e3NoYWRvd0JpYXM6e30sc2hhZG93Tm9ybWFsQmlhczp7fSxzaGFkb3dSYWRpdXM6e30sc2hhZG93TWFwU2l6ZTp7fSxzaGFkb3dDYW1lcmFOZWFyOnt9LHNoYWRvd0NhbWVyYUZhcjp7fX19LHBvaW50U2hhZG93TWFwOnt2YWx1ZTpbXX0scG9pbnRTaGFkb3dNYXRyaXg6e3ZhbHVlOltdfSxoZW1pc3BoZXJlTGlnaHRzOnt2YWx1ZTpbXSxwcm9wZXJ0aWVzOntkaXJlY3Rpb246e30sc2t5Q29sb3I6e30sZ3JvdW5kQ29sb3I6e319fSxyZWN0QXJlYUxpZ2h0czp7dmFsdWU6W10scHJvcGVydGllczp7Y29sb3I6e30scG9zaXRpb246e30sd2lkdGg6e30saGVpZ2h0Ont9fX0sbHRjXzE6e3ZhbHVlOm51bGx9LGx0Y18yOnt2YWx1ZTpudWxsfX0scG9pbnRzOntkaWZmdXNlOnt2YWx1ZTpuZXcgdm4oMTY3NzcyMTUpfSxvcGFjaXR5Ont2YWx1ZToxfSxzaXplOnt2YWx1ZToxfSxzY2FsZTp7dmFsdWU6MX0sbWFwOnt2YWx1ZTpudWxsfSxhbHBoYU1hcDp7dmFsdWU6bnVsbH0sYWxwaGFUZXN0Ont2YWx1ZTowfSx1dlRyYW5zZm9ybTp7dmFsdWU6bmV3IEpvfX0sc3ByaXRlOntkaWZmdXNlOnt2YWx1ZTpuZXcgdm4oMTY3NzcyMTUpfSxvcGFjaXR5Ont2YWx1ZToxfSxjZW50ZXI6e3ZhbHVlOm5ldyBhdCguNSwuNSl9LHJvdGF0aW9uOnt2YWx1ZTowfSxtYXA6e3ZhbHVlOm51bGx9LGFscGhhTWFwOnt2YWx1ZTpudWxsfSxhbHBoYVRlc3Q6e3ZhbHVlOjB9LHV2VHJhbnNmb3JtOnt2YWx1ZTpuZXcgSm99fX0sU2Q9e2Jhc2ljOnt1bmlmb3JtczpqcyhbQnQuY29tbW9uLEJ0LnNwZWN1bGFybWFwLEJ0LmVudm1hcCxCdC5hb21hcCxCdC5saWdodG1hcCxCdC5mb2ddKSx2ZXJ0ZXhTaGFkZXI6RGkubWVzaGJhc2ljX3ZlcnQsZnJhZ21lbnRTaGFkZXI6RGkubWVzaGJhc2ljX2ZyYWd9LGxhbWJlcnQ6e3VuaWZvcm1zOmpzKFtCdC5jb21tb24sQnQuc3BlY3VsYXJtYXAsQnQuZW52bWFwLEJ0LmFvbWFwLEJ0LmxpZ2h0bWFwLEJ0LmVtaXNzaXZlbWFwLEJ0LmZvZyxCdC5saWdodHMse2VtaXNzaXZlOnt2YWx1ZTpuZXcgdm4oMCl9fV0pLHZlcnRleFNoYWRlcjpEaS5tZXNobGFtYmVydF92ZXJ0LGZyYWdtZW50U2hhZGVyOkRpLm1lc2hsYW1iZXJ0X2ZyYWd9LHBob25nOnt1bmlmb3JtczpqcyhbQnQuY29tbW9uLEJ0LnNwZWN1bGFybWFwLEJ0LmVudm1hcCxCdC5hb21hcCxCdC5saWdodG1hcCxCdC5lbWlzc2l2ZW1hcCxCdC5idW1wbWFwLEJ0Lm5vcm1hbG1hcCxCdC5kaXNwbGFjZW1lbnRtYXAsQnQuZm9nLEJ0LmxpZ2h0cyx7ZW1pc3NpdmU6e3ZhbHVlOm5ldyB2bigwKX0sc3BlY3VsYXI6e3ZhbHVlOm5ldyB2bigxMTE4NDgxKX0sc2hpbmluZXNzOnt2YWx1ZTozMH19XSksdmVydGV4U2hhZGVyOkRpLm1lc2hwaG9uZ192ZXJ0LGZyYWdtZW50U2hhZGVyOkRpLm1lc2hwaG9uZ19mcmFnfSxzdGFuZGFyZDp7dW5pZm9ybXM6anMoW0J0LmNvbW1vbixCdC5lbnZtYXAsQnQuYW9tYXAsQnQubGlnaHRtYXAsQnQuZW1pc3NpdmVtYXAsQnQuYnVtcG1hcCxCdC5ub3JtYWxtYXAsQnQuZGlzcGxhY2VtZW50bWFwLEJ0LnJvdWdobmVzc21hcCxCdC5tZXRhbG5lc3NtYXAsQnQuZm9nLEJ0LmxpZ2h0cyx7ZW1pc3NpdmU6e3ZhbHVlOm5ldyB2bigwKX0scm91Z2huZXNzOnt2YWx1ZToxfSxtZXRhbG5lc3M6e3ZhbHVlOjB9LGVudk1hcEludGVuc2l0eTp7dmFsdWU6MX19XSksdmVydGV4U2hhZGVyOkRpLm1lc2hwaHlzaWNhbF92ZXJ0LGZyYWdtZW50U2hhZGVyOkRpLm1lc2hwaHlzaWNhbF9mcmFnfSx0b29uOnt1bmlmb3JtczpqcyhbQnQuY29tbW9uLEJ0LmFvbWFwLEJ0LmxpZ2h0bWFwLEJ0LmVtaXNzaXZlbWFwLEJ0LmJ1bXBtYXAsQnQubm9ybWFsbWFwLEJ0LmRpc3BsYWNlbWVudG1hcCxCdC5ncmFkaWVudG1hcCxCdC5mb2csQnQubGlnaHRzLHtlbWlzc2l2ZTp7dmFsdWU6bmV3IHZuKDApfX1dKSx2ZXJ0ZXhTaGFkZXI6RGkubWVzaHRvb25fdmVydCxmcmFnbWVudFNoYWRlcjpEaS5tZXNodG9vbl9mcmFnfSxtYXRjYXA6e3VuaWZvcm1zOmpzKFtCdC5jb21tb24sQnQuYnVtcG1hcCxCdC5ub3JtYWxtYXAsQnQuZGlzcGxhY2VtZW50bWFwLEJ0LmZvZyx7bWF0Y2FwOnt2YWx1ZTpudWxsfX1dKSx2ZXJ0ZXhTaGFkZXI6RGkubWVzaG1hdGNhcF92ZXJ0LGZyYWdtZW50U2hhZGVyOkRpLm1lc2htYXRjYXBfZnJhZ30scG9pbnRzOnt1bmlmb3JtczpqcyhbQnQucG9pbnRzLEJ0LmZvZ10pLHZlcnRleFNoYWRlcjpEaS5wb2ludHNfdmVydCxmcmFnbWVudFNoYWRlcjpEaS5wb2ludHNfZnJhZ30sZGFzaGVkOnt1bmlmb3JtczpqcyhbQnQuY29tbW9uLEJ0LmZvZyx7c2NhbGU6e3ZhbHVlOjF9LGRhc2hTaXplOnt2YWx1ZToxfSx0b3RhbFNpemU6e3ZhbHVlOjJ9fV0pLHZlcnRleFNoYWRlcjpEaS5saW5lZGFzaGVkX3ZlcnQsZnJhZ21lbnRTaGFkZXI6RGkubGluZWRhc2hlZF9mcmFnfSxkZXB0aDp7dW5pZm9ybXM6anMoW0J0LmNvbW1vbixCdC5kaXNwbGFjZW1lbnRtYXBdKSx2ZXJ0ZXhTaGFkZXI6RGkuZGVwdGhfdmVydCxmcmFnbWVudFNoYWRlcjpEaS5kZXB0aF9mcmFnfSxub3JtYWw6e3VuaWZvcm1zOmpzKFtCdC5jb21tb24sQnQuYnVtcG1hcCxCdC5ub3JtYWxtYXAsQnQuZGlzcGxhY2VtZW50bWFwLHtvcGFjaXR5Ont2YWx1ZToxfX1dKSx2ZXJ0ZXhTaGFkZXI6RGkubWVzaG5vcm1hbF92ZXJ0LGZyYWdtZW50U2hhZGVyOkRpLm1lc2hub3JtYWxfZnJhZ30sc3ByaXRlOnt1bmlmb3JtczpqcyhbQnQuc3ByaXRlLEJ0LmZvZ10pLHZlcnRleFNoYWRlcjpEaS5zcHJpdGVfdmVydCxmcmFnbWVudFNoYWRlcjpEaS5zcHJpdGVfZnJhZ30sYmFja2dyb3VuZDp7dW5pZm9ybXM6e3V2VHJhbnNmb3JtOnt2YWx1ZTpuZXcgSm99LHQyRDp7dmFsdWU6bnVsbH19LHZlcnRleFNoYWRlcjpEaS5iYWNrZ3JvdW5kX3ZlcnQsZnJhZ21lbnRTaGFkZXI6RGkuYmFja2dyb3VuZF9mcmFnfSxjdWJlOnt1bmlmb3JtczpqcyhbQnQuZW52bWFwLHtvcGFjaXR5Ont2YWx1ZToxfX1dKSx2ZXJ0ZXhTaGFkZXI6RGkuY3ViZV92ZXJ0LGZyYWdtZW50U2hhZGVyOkRpLmN1YmVfZnJhZ30sZXF1aXJlY3Q6e3VuaWZvcm1zOnt0RXF1aXJlY3Q6e3ZhbHVlOm51bGx9fSx2ZXJ0ZXhTaGFkZXI6RGkuZXF1aXJlY3RfdmVydCxmcmFnbWVudFNoYWRlcjpEaS5lcXVpcmVjdF9mcmFnfSxkaXN0YW5jZVJHQkE6e3VuaWZvcm1zOmpzKFtCdC5jb21tb24sQnQuZGlzcGxhY2VtZW50bWFwLHtyZWZlcmVuY2VQb3NpdGlvbjp7dmFsdWU6bmV3IGllfSxuZWFyRGlzdGFuY2U6e3ZhbHVlOjF9LGZhckRpc3RhbmNlOnt2YWx1ZToxZTN9fV0pLHZlcnRleFNoYWRlcjpEaS5kaXN0YW5jZVJHQkFfdmVydCxmcmFnbWVudFNoYWRlcjpEaS5kaXN0YW5jZVJHQkFfZnJhZ30sc2hhZG93Ont1bmlmb3JtczpqcyhbQnQubGlnaHRzLEJ0LmZvZyx7Y29sb3I6e3ZhbHVlOm5ldyB2bigwKX0sb3BhY2l0eTp7dmFsdWU6MX19XSksdmVydGV4U2hhZGVyOkRpLnNoYWRvd192ZXJ0LGZyYWdtZW50U2hhZGVyOkRpLnNoYWRvd19mcmFnfX07ZnVuY3Rpb24gSVdlKG4sdCxlLGkscixvKXtsZXQgbCxjLHM9bmV3IHZuKDApLGE9ITA9PT1yPzA6MSx1PW51bGwsZD0wLHA9bnVsbDtmdW5jdGlvbiBmKG0seCl7ZS5idWZmZXJzLmNvbG9yLnNldENsZWFyKG0ucixtLmcsbS5iLHgsbyl9cmV0dXJue2dldENsZWFyQ29sb3I6ZnVuY3Rpb24oKXtyZXR1cm4gc30sc2V0Q2xlYXJDb2xvcjpmdW5jdGlvbihtLHg9MSl7cy5zZXQobSksYT14LGYocyxhKX0sZ2V0Q2xlYXJBbHBoYTpmdW5jdGlvbigpe3JldHVybiBhfSxzZXRDbGVhckFscGhhOmZ1bmN0aW9uKG0pe2E9bSxmKHMsYSl9LHJlbmRlcjpmdW5jdGlvbihtLHgpe2xldCBnPSExLGI9ITA9PT14LmlzU2NlbmU/eC5iYWNrZ3JvdW5kOm51bGw7YiYmYi5pc1RleHR1cmUmJihiPXQuZ2V0KGIpKTtsZXQgRD1uLnhyLFQ9RC5nZXRTZXNzaW9uJiZELmdldFNlc3Npb24oKTtUJiYiYWRkaXRpdmUiPT09VC5lbnZpcm9ubWVudEJsZW5kTW9kZSYmKGI9bnVsbCksbnVsbD09PWI/ZihzLGEpOmImJmIuaXNDb2xvciYmKGYoYiwxKSxnPSEwKSwobi5hdXRvQ2xlYXJ8fGcpJiZuLmNsZWFyKG4uYXV0b0NsZWFyQ29sb3Isbi5hdXRvQ2xlYXJEZXB0aCxuLmF1dG9DbGVhclN0ZW5jaWwpLGImJihiLmlzQ3ViZVRleHR1cmV8fDMwNj09PWIubWFwcGluZyk/KHZvaWQgMD09PWMmJihjPW5ldyBWbyhuZXcgV2coMSwxLDEpLG5ldyBEcCh7bmFtZToiQmFja2dyb3VuZEN1YmVNYXRlcmlhbCIsdW5pZm9ybXM6ZmIoU2QuY3ViZS51bmlmb3JtcyksdmVydGV4U2hhZGVyOlNkLmN1YmUudmVydGV4U2hhZGVyLGZyYWdtZW50U2hhZGVyOlNkLmN1YmUuZnJhZ21lbnRTaGFkZXIsc2lkZToxLGRlcHRoVGVzdDohMSxkZXB0aFdyaXRlOiExLGZvZzohMX0pKSxjLmdlb21ldHJ5LmRlbGV0ZUF0dHJpYnV0ZSgibm9ybWFsIiksYy5nZW9tZXRyeS5kZWxldGVBdHRyaWJ1dGUoInV2IiksYy5vbkJlZm9yZVJlbmRlcj1mdW5jdGlvbihrLFoseil7dGhpcy5tYXRyaXhXb3JsZC5jb3B5UG9zaXRpb24oei5tYXRyaXhXb3JsZCl9LE9iamVjdC5kZWZpbmVQcm9wZXJ0eShjLm1hdGVyaWFsLCJlbnZNYXAiLHtnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy51bmlmb3Jtcy5lbnZNYXAudmFsdWV9fSksaS51cGRhdGUoYykpLGMubWF0ZXJpYWwudW5pZm9ybXMuZW52TWFwLnZhbHVlPWIsYy5tYXRlcmlhbC51bmlmb3Jtcy5mbGlwRW52TWFwLnZhbHVlPWIuaXNDdWJlVGV4dHVyZSYmITE9PT1iLmlzUmVuZGVyVGFyZ2V0VGV4dHVyZT8tMToxLCh1IT09Ynx8ZCE9PWIudmVyc2lvbnx8cCE9PW4udG9uZU1hcHBpbmcpJiYoYy5tYXRlcmlhbC5uZWVkc1VwZGF0ZT0hMCx1PWIsZD1iLnZlcnNpb24scD1uLnRvbmVNYXBwaW5nKSxtLnVuc2hpZnQoYyxjLmdlb21ldHJ5LGMubWF0ZXJpYWwsMCwwLG51bGwpKTpiJiZiLmlzVGV4dHVyZSYmKHZvaWQgMD09PWwmJihsPW5ldyBWbyhuZXcgWlMoMiwyKSxuZXcgRHAoe25hbWU6IkJhY2tncm91bmRNYXRlcmlhbCIsdW5pZm9ybXM6ZmIoU2QuYmFja2dyb3VuZC51bmlmb3JtcyksdmVydGV4U2hhZGVyOlNkLmJhY2tncm91bmQudmVydGV4U2hhZGVyLGZyYWdtZW50U2hhZGVyOlNkLmJhY2tncm91bmQuZnJhZ21lbnRTaGFkZXIsc2lkZTowLGRlcHRoVGVzdDohMSxkZXB0aFdyaXRlOiExLGZvZzohMX0pKSxsLmdlb21ldHJ5LmRlbGV0ZUF0dHJpYnV0ZSgibm9ybWFsIiksT2JqZWN0LmRlZmluZVByb3BlcnR5KGwubWF0ZXJpYWwsIm1hcCIse2dldDpmdW5jdGlvbigpe3JldHVybiB0aGlzLnVuaWZvcm1zLnQyRC52YWx1ZX19KSxpLnVwZGF0ZShsKSksbC5tYXRlcmlhbC51bmlmb3Jtcy50MkQudmFsdWU9YiwhMD09PWIubWF0cml4QXV0b1VwZGF0ZSYmYi51cGRhdGVNYXRyaXgoKSxsLm1hdGVyaWFsLnVuaWZvcm1zLnV2VHJhbnNmb3JtLnZhbHVlLmNvcHkoYi5tYXRyaXgpLCh1IT09Ynx8ZCE9PWIudmVyc2lvbnx8cCE9PW4udG9uZU1hcHBpbmcpJiYobC5tYXRlcmlhbC5uZWVkc1VwZGF0ZT0hMCx1PWIsZD1iLnZlcnNpb24scD1uLnRvbmVNYXBwaW5nKSxtLnVuc2hpZnQobCxsLmdlb21ldHJ5LGwubWF0ZXJpYWwsMCwwLG51bGwpKX19fWZ1bmN0aW9uIFBXZShuLHQsZSxpKXtsZXQgcj1uLmdldFBhcmFtZXRlcigzNDkyMSksbz1pLmlzV2ViR0wyP251bGw6dC5nZXQoIk9FU192ZXJ0ZXhfYXJyYXlfb2JqZWN0Iikscz1pLmlzV2ViR0wyfHxudWxsIT09byxhPXt9LGw9bShudWxsKSxjPWw7ZnVuY3Rpb24gcChxKXtyZXR1cm4gaS5pc1dlYkdMMj9uLmJpbmRWZXJ0ZXhBcnJheShxKTpvLmJpbmRWZXJ0ZXhBcnJheU9FUyhxKX1mdW5jdGlvbiBoKHEpe3JldHVybiBpLmlzV2ViR0wyP24uZGVsZXRlVmVydGV4QXJyYXkocSk6by5kZWxldGVWZXJ0ZXhBcnJheU9FUyhxKX1mdW5jdGlvbiBtKHEpe2xldCBLPVtdLGRlPVtdLFk9W107Zm9yKGxldCBhZT0wO2FlPHI7YWUrKylLW2FlXT0wLGRlW2FlXT0wLFlbYWVdPTA7cmV0dXJue2dlb21ldHJ5Om51bGwscHJvZ3JhbTpudWxsLHdpcmVmcmFtZTohMSxuZXdBdHRyaWJ1dGVzOkssZW5hYmxlZEF0dHJpYnV0ZXM6ZGUsYXR0cmlidXRlRGl2aXNvcnM6WSxvYmplY3Q6cSxhdHRyaWJ1dGVzOnt9LGluZGV4Om51bGx9fWZ1bmN0aW9uIGIoKXtsZXQgcT1jLm5ld0F0dHJpYnV0ZXM7Zm9yKGxldCBLPTAsZGU9cS5sZW5ndGg7SzxkZTtLKyspcVtLXT0wfWZ1bmN0aW9uIEQocSl7VChxLDApfWZ1bmN0aW9uIFQocSxLKXtsZXQgWT1jLmVuYWJsZWRBdHRyaWJ1dGVzLGFlPWMuYXR0cmlidXRlRGl2aXNvcnM7Yy5uZXdBdHRyaWJ1dGVzW3FdPTEsMD09PVlbcV0mJihuLmVuYWJsZVZlcnRleEF0dHJpYkFycmF5KHEpLFlbcV09MSksYWVbcV0hPT1LJiYoKGkuaXNXZWJHTDI/bjp0LmdldCgiQU5HTEVfaW5zdGFuY2VkX2FycmF5cyIpKVtpLmlzV2ViR0wyPyJ2ZXJ0ZXhBdHRyaWJEaXZpc29yIjoidmVydGV4QXR0cmliRGl2aXNvckFOR0xFIl0ocSxLKSxhZVtxXT1LKX1mdW5jdGlvbiBrKCl7bGV0IHE9Yy5uZXdBdHRyaWJ1dGVzLEs9Yy5lbmFibGVkQXR0cmlidXRlcztmb3IobGV0IGRlPTAsWT1LLmxlbmd0aDtkZTxZO2RlKyspS1tkZV0hPT1xW2RlXSYmKG4uZGlzYWJsZVZlcnRleEF0dHJpYkFycmF5KGRlKSxLW2RlXT0wKX1mdW5jdGlvbiBaKHEsSyxkZSxZLGFlLGxlKXshMCE9PWkuaXNXZWJHTDJ8fDUxMjQhPT1kZSYmNTEyNSE9PWRlP24udmVydGV4QXR0cmliUG9pbnRlcihxLEssZGUsWSxhZSxsZSk6bi52ZXJ0ZXhBdHRyaWJJUG9pbnRlcihxLEssZGUsYWUsbGUpfWZ1bmN0aW9uIHcoKXtGKCksYyE9PWwmJihjPWwscChjLm9iamVjdCkpfWZ1bmN0aW9uIEYoKXtsLmdlb21ldHJ5PW51bGwsbC5wcm9ncmFtPW51bGwsbC53aXJlZnJhbWU9ITF9cmV0dXJue3NldHVwOmZ1bmN0aW9uKHEsSyxkZSxZLGFlKXtsZXQgbGU9ITE7aWYocyl7bGV0IEllPWZ1bmN0aW9uKHEsSyxkZSl7bGV0IFk9ITA9PT1kZS53aXJlZnJhbWUsYWU9YVtxLmlkXTt2b2lkIDA9PT1hZSYmKGFlPXt9LGFbcS5pZF09YWUpO2xldCBsZT1hZVtLLmlkXTt2b2lkIDA9PT1sZSYmKGxlPXt9LGFlW0suaWRdPWxlKTtsZXQgSWU9bGVbWV07cmV0dXJuIHZvaWQgMD09PUllJiYoSWU9bShpLmlzV2ViR0wyP24uY3JlYXRlVmVydGV4QXJyYXkoKTpvLmNyZWF0ZVZlcnRleEFycmF5T0VTKCkpLGxlW1ldPUllKSxJZX0oWSxkZSxLKTtjIT09SWUmJihjPUllLHAoYy5vYmplY3QpKSxsZT1mdW5jdGlvbihxLEspe2xldCBkZT1jLmF0dHJpYnV0ZXMsWT1xLmF0dHJpYnV0ZXMsYWU9MDtmb3IobGV0IGxlIGluIFkpe2xldCBJZT1kZVtsZV0sdmU9WVtsZV07aWYodm9pZCAwPT09SWV8fEllLmF0dHJpYnV0ZSE9PXZlfHxJZS5kYXRhIT09dmUuZGF0YSlyZXR1cm4hMDthZSsrfXJldHVybiBjLmF0dHJpYnV0ZXNOdW0hPT1hZXx8Yy5pbmRleCE9PUt9KFksYWUpLGxlJiZmdW5jdGlvbihxLEspe2xldCBkZT17fSxZPXEuYXR0cmlidXRlcyxhZT0wO2ZvcihsZXQgbGUgaW4gWSl7bGV0IEllPVlbbGVdLHZlPXt9O3ZlLmF0dHJpYnV0ZT1JZSxJZS5kYXRhJiYodmUuZGF0YT1JZS5kYXRhKSxkZVtsZV09dmUsYWUrK31jLmF0dHJpYnV0ZXM9ZGUsYy5hdHRyaWJ1dGVzTnVtPWFlLGMuaW5kZXg9S30oWSxhZSl9ZWxzZXtsZXQgSWU9ITA9PT1LLndpcmVmcmFtZTsoYy5nZW9tZXRyeSE9PVkuaWR8fGMucHJvZ3JhbSE9PWRlLmlkfHxjLndpcmVmcmFtZSE9PUllKSYmKGMuZ2VvbWV0cnk9WS5pZCxjLnByb2dyYW09ZGUuaWQsYy53aXJlZnJhbWU9SWUsbGU9ITApfSEwPT09cS5pc0luc3RhbmNlZE1lc2gmJihsZT0hMCksbnVsbCE9PWFlJiZlLnVwZGF0ZShhZSwzNDk2MyksbGUmJihmdW5jdGlvbihxLEssZGUsWSl7aWYoITE9PT1pLmlzV2ViR0wyJiYocS5pc0luc3RhbmNlZE1lc2h8fFkuaXNJbnN0YW5jZWRCdWZmZXJHZW9tZXRyeSkmJm51bGw9PT10LmdldCgiQU5HTEVfaW5zdGFuY2VkX2FycmF5cyIpKXJldHVybjtiKCk7bGV0IGFlPVkuYXR0cmlidXRlcyxsZT1kZS5nZXRBdHRyaWJ1dGVzKCksSWU9Sy5kZWZhdWx0QXR0cmlidXRlVmFsdWVzO2ZvcihsZXQgdmUgaW4gbGUpe2xldCBEZT1sZVt2ZV07aWYoRGUubG9jYXRpb24+PTApe2xldCBudD1hZVt2ZV07aWYodm9pZCAwPT09bnQmJigiaW5zdGFuY2VNYXRyaXgiPT09dmUmJnEuaW5zdGFuY2VNYXRyaXgmJihudD1xLmluc3RhbmNlTWF0cml4KSwiaW5zdGFuY2VDb2xvciI9PT12ZSYmcS5pbnN0YW5jZUNvbG9yJiYobnQ9cS5pbnN0YW5jZUNvbG9yKSksdm9pZCAwIT09bnQpe2xldCBndD1udC5ub3JtYWxpemVkLFVlPW50Lml0ZW1TaXplLEFlPWUuZ2V0KG50KTtpZih2b2lkIDA9PT1BZSljb250aW51ZTtsZXQgdG49QWUuYnVmZmVyLHB0PUFlLnR5cGUsd3Q9QWUuYnl0ZXNQZXJFbGVtZW50O2lmKG50LmlzSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGUpe2xldCBUZT1udC5kYXRhLHh0PVRlLnN0cmlkZSxtdD1udC5vZmZzZXQ7aWYoVGUmJlRlLmlzSW5zdGFuY2VkSW50ZXJsZWF2ZWRCdWZmZXIpe2ZvcihsZXQgY2U9MDtjZTxEZS5sb2NhdGlvblNpemU7Y2UrKylUKERlLmxvY2F0aW9uK2NlLFRlLm1lc2hQZXJBdHRyaWJ1dGUpOyEwIT09cS5pc0luc3RhbmNlZE1lc2gmJnZvaWQgMD09PVkuX21heEluc3RhbmNlQ291bnQmJihZLl9tYXhJbnN0YW5jZUNvdW50PVRlLm1lc2hQZXJBdHRyaWJ1dGUqVGUuY291bnQpfWVsc2UgZm9yKGxldCBjZT0wO2NlPERlLmxvY2F0aW9uU2l6ZTtjZSsrKUQoRGUubG9jYXRpb24rY2UpO24uYmluZEJ1ZmZlcigzNDk2Mix0bik7Zm9yKGxldCBjZT0wO2NlPERlLmxvY2F0aW9uU2l6ZTtjZSsrKVooRGUubG9jYXRpb24rY2UsVWUvRGUubG9jYXRpb25TaXplLHB0LGd0LHh0Knd0LChtdCtVZS9EZS5sb2NhdGlvblNpemUqY2UpKnd0KX1lbHNle2lmKG50LmlzSW5zdGFuY2VkQnVmZmVyQXR0cmlidXRlKXtmb3IobGV0IFRlPTA7VGU8RGUubG9jYXRpb25TaXplO1RlKyspVChEZS5sb2NhdGlvbitUZSxudC5tZXNoUGVyQXR0cmlidXRlKTshMCE9PXEuaXNJbnN0YW5jZWRNZXNoJiZ2b2lkIDA9PT1ZLl9tYXhJbnN0YW5jZUNvdW50JiYoWS5fbWF4SW5zdGFuY2VDb3VudD1udC5tZXNoUGVyQXR0cmlidXRlKm50LmNvdW50KX1lbHNlIGZvcihsZXQgVGU9MDtUZTxEZS5sb2NhdGlvblNpemU7VGUrKylEKERlLmxvY2F0aW9uK1RlKTtuLmJpbmRCdWZmZXIoMzQ5NjIsdG4pO2ZvcihsZXQgVGU9MDtUZTxEZS5sb2NhdGlvblNpemU7VGUrKylaKERlLmxvY2F0aW9uK1RlLFVlL0RlLmxvY2F0aW9uU2l6ZSxwdCxndCxVZSp3dCxVZS9EZS5sb2NhdGlvblNpemUqVGUqd3QpfX1lbHNlIGlmKHZvaWQgMCE9PUllKXtsZXQgZ3Q9SWVbdmVdO2lmKHZvaWQgMCE9PWd0KXN3aXRjaChndC5sZW5ndGgpe2Nhc2UgMjpuLnZlcnRleEF0dHJpYjJmdihEZS5sb2NhdGlvbixndCk7YnJlYWs7Y2FzZSAzOm4udmVydGV4QXR0cmliM2Z2KERlLmxvY2F0aW9uLGd0KTticmVhaztjYXNlIDQ6bi52ZXJ0ZXhBdHRyaWI0ZnYoRGUubG9jYXRpb24sZ3QpO2JyZWFrO2RlZmF1bHQ6bi52ZXJ0ZXhBdHRyaWIxZnYoRGUubG9jYXRpb24sZ3QpfX19fWsoKX0ocSxLLGRlLFkpLG51bGwhPT1hZSYmbi5iaW5kQnVmZmVyKDM0OTYzLGUuZ2V0KGFlKS5idWZmZXIpKX0scmVzZXQ6dyxyZXNldERlZmF1bHRTdGF0ZTpGLGRpc3Bvc2U6ZnVuY3Rpb24oKXt3KCk7Zm9yKGxldCBxIGluIGEpe2xldCBLPWFbcV07Zm9yKGxldCBkZSBpbiBLKXtsZXQgWT1LW2RlXTtmb3IobGV0IGFlIGluIFkpaChZW2FlXS5vYmplY3QpLGRlbGV0ZSBZW2FlXTtkZWxldGUgS1tkZV19ZGVsZXRlIGFbcV19fSxyZWxlYXNlU3RhdGVzT2ZHZW9tZXRyeTpmdW5jdGlvbihxKXtpZih2b2lkIDA9PT1hW3EuaWRdKXJldHVybjtsZXQgSz1hW3EuaWRdO2ZvcihsZXQgZGUgaW4gSyl7bGV0IFk9S1tkZV07Zm9yKGxldCBhZSBpbiBZKWgoWVthZV0ub2JqZWN0KSxkZWxldGUgWVthZV07ZGVsZXRlIEtbZGVdfWRlbGV0ZSBhW3EuaWRdfSxyZWxlYXNlU3RhdGVzT2ZQcm9ncmFtOmZ1bmN0aW9uKHEpe2ZvcihsZXQgSyBpbiBhKXtsZXQgZGU9YVtLXTtpZih2b2lkIDA9PT1kZVtxLmlkXSljb250aW51ZTtsZXQgWT1kZVtxLmlkXTtmb3IobGV0IGFlIGluIFkpaChZW2FlXS5vYmplY3QpLGRlbGV0ZSBZW2FlXTtkZWxldGUgZGVbcS5pZF19fSxpbml0QXR0cmlidXRlczpiLGVuYWJsZUF0dHJpYnV0ZTpELGRpc2FibGVVbnVzZWRBdHRyaWJ1dGVzOmt9fWZ1bmN0aW9uIFJXZShuLHQsZSxpKXtsZXQgbyxyPWkuaXNXZWJHTDI7dGhpcy5zZXRNb2RlPWZ1bmN0aW9uKGMpe289Y30sdGhpcy5yZW5kZXI9ZnVuY3Rpb24oYyx1KXtuLmRyYXdBcnJheXMobyxjLHUpLGUudXBkYXRlKHUsbywxKX0sdGhpcy5yZW5kZXJJbnN0YW5jZXM9ZnVuY3Rpb24oYyx1LGQpe2lmKDA9PT1kKXJldHVybjtsZXQgcCxoO2lmKHIpcD1uLGg9ImRyYXdBcnJheXNJbnN0YW5jZWQiO2Vsc2UgaWYocD10LmdldCgiQU5HTEVfaW5zdGFuY2VkX2FycmF5cyIpLGg9ImRyYXdBcnJheXNJbnN0YW5jZWRBTkdMRSIsbnVsbD09PXApcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xCdWZmZXJSZW5kZXJlcjogdXNpbmcgVEhSRUUuSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnkgYnV0IGhhcmR3YXJlIGRvZXMgbm90IHN1cHBvcnQgZXh0ZW5zaW9uIEFOR0xFX2luc3RhbmNlZF9hcnJheXMuIik7cFtoXShvLGMsdSxkKSxlLnVwZGF0ZSh1LG8sZCl9fWZ1bmN0aW9uIE9XZShuLHQsZSl7bGV0IGk7ZnVuY3Rpb24gbyh6KXtpZigiaGlnaHAiPT09eil7aWYobi5nZXRTaGFkZXJQcmVjaXNpb25Gb3JtYXQoMzU2MzMsMzYzMzgpLnByZWNpc2lvbj4wJiZuLmdldFNoYWRlclByZWNpc2lvbkZvcm1hdCgzNTYzMiwzNjMzOCkucHJlY2lzaW9uPjApcmV0dXJuImhpZ2hwIjt6PSJtZWRpdW1wIn1yZXR1cm4ibWVkaXVtcCI9PT16JiZuLmdldFNoYWRlclByZWNpc2lvbkZvcm1hdCgzNTYzMywzNjMzNykucHJlY2lzaW9uPjAmJm4uZ2V0U2hhZGVyUHJlY2lzaW9uRm9ybWF0KDM1NjMyLDM2MzM3KS5wcmVjaXNpb24+MD8ibWVkaXVtcCI6Imxvd3AifWxldCBzPXR5cGVvZiBXZWJHTDJSZW5kZXJpbmdDb250ZXh0PCJ1IiYmbiBpbnN0YW5jZW9mIFdlYkdMMlJlbmRlcmluZ0NvbnRleHR8fHR5cGVvZiBXZWJHTDJDb21wdXRlUmVuZGVyaW5nQ29udGV4dDwidSImJm4gaW5zdGFuY2VvZiBXZWJHTDJDb21wdXRlUmVuZGVyaW5nQ29udGV4dCxhPXZvaWQgMCE9PWUucHJlY2lzaW9uP2UucHJlY2lzaW9uOiJoaWdocCIsbD1vKGEpO2whPT1hJiYoY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiIsYSwibm90IHN1cHBvcnRlZCwgdXNpbmciLGwsImluc3RlYWQuIiksYT1sKTtsZXQgYz1zfHx0LmhhcygiV0VCR0xfZHJhd19idWZmZXJzIiksdT0hMD09PWUubG9nYXJpdGhtaWNEZXB0aEJ1ZmZlcixkPW4uZ2V0UGFyYW1ldGVyKDM0OTMwKSxwPW4uZ2V0UGFyYW1ldGVyKDM1NjYwKSxoPW4uZ2V0UGFyYW1ldGVyKDMzNzkpLGY9bi5nZXRQYXJhbWV0ZXIoMzQwNzYpLG09bi5nZXRQYXJhbWV0ZXIoMzQ5MjEpLHg9bi5nZXRQYXJhbWV0ZXIoMzYzNDcpLGc9bi5nZXRQYXJhbWV0ZXIoMzYzNDgpLGI9bi5nZXRQYXJhbWV0ZXIoMzYzNDkpLEQ9cD4wLFQ9c3x8dC5oYXMoIk9FU190ZXh0dXJlX2Zsb2F0Iik7cmV0dXJue2lzV2ViR0wyOnMsZHJhd0J1ZmZlcnM6YyxnZXRNYXhBbmlzb3Ryb3B5OmZ1bmN0aW9uKCl7aWYodm9pZCAwIT09aSlyZXR1cm4gaTtpZighMD09PXQuaGFzKCJFWFRfdGV4dHVyZV9maWx0ZXJfYW5pc290cm9waWMiKSl7bGV0IHo9dC5nZXQoIkVYVF90ZXh0dXJlX2ZpbHRlcl9hbmlzb3Ryb3BpYyIpO2k9bi5nZXRQYXJhbWV0ZXIoei5NQVhfVEVYVFVSRV9NQVhfQU5JU09UUk9QWV9FWFQpfWVsc2UgaT0wO3JldHVybiBpfSxnZXRNYXhQcmVjaXNpb246byxwcmVjaXNpb246YSxsb2dhcml0aG1pY0RlcHRoQnVmZmVyOnUsbWF4VGV4dHVyZXM6ZCxtYXhWZXJ0ZXhUZXh0dXJlczpwLG1heFRleHR1cmVTaXplOmgsbWF4Q3ViZW1hcFNpemU6ZixtYXhBdHRyaWJ1dGVzOm0sbWF4VmVydGV4VW5pZm9ybXM6eCxtYXhWYXJ5aW5nczpnLG1heEZyYWdtZW50VW5pZm9ybXM6Yix2ZXJ0ZXhUZXh0dXJlczpELGZsb2F0RnJhZ21lbnRUZXh0dXJlczpULGZsb2F0VmVydGV4VGV4dHVyZXM6RCYmVCxtYXhTYW1wbGVzOnM/bi5nZXRQYXJhbWV0ZXIoMzYxODMpOjB9fWZ1bmN0aW9uIGtXZShuKXtsZXQgdD10aGlzLGU9bnVsbCxpPTAscj0hMSxvPSExLHM9bmV3IHV1LGE9bmV3IEpvLGw9e3ZhbHVlOm51bGwsbmVlZHNVcGRhdGU6ITF9O2Z1bmN0aW9uIGMoKXtsLnZhbHVlIT09ZSYmKGwudmFsdWU9ZSxsLm5lZWRzVXBkYXRlPWk+MCksdC5udW1QbGFuZXM9aSx0Lm51bUludGVyc2VjdGlvbj0wfWZ1bmN0aW9uIHUoZCxwLGgsZil7bGV0IG09bnVsbCE9PWQ/ZC5sZW5ndGg6MCx4PW51bGw7aWYoMCE9PW0pe2lmKHg9bC52YWx1ZSwhMCE9PWZ8fG51bGw9PT14KXtsZXQgZz1oKzQqbSxiPXAubWF0cml4V29ybGRJbnZlcnNlO2EuZ2V0Tm9ybWFsTWF0cml4KGIpLChudWxsPT09eHx8eC5sZW5ndGg8ZykmJih4PW5ldyBGbG9hdDMyQXJyYXkoZykpO2ZvcihsZXQgRD0wLFQ9aDtEIT09bTsrK0QsVCs9NClzLmNvcHkoZFtEXSkuYXBwbHlNYXRyaXg0KGIsYSkscy5ub3JtYWwudG9BcnJheSh4LFQpLHhbVCszXT1zLmNvbnN0YW50fWwudmFsdWU9eCxsLm5lZWRzVXBkYXRlPSEwfXJldHVybiB0Lm51bVBsYW5lcz1tLHQubnVtSW50ZXJzZWN0aW9uPTAseH10aGlzLnVuaWZvcm09bCx0aGlzLm51bVBsYW5lcz0wLHRoaXMubnVtSW50ZXJzZWN0aW9uPTAsdGhpcy5pbml0PWZ1bmN0aW9uKGQscCxoKXtsZXQgZj0wIT09ZC5sZW5ndGh8fHB8fDAhPT1pfHxyO3JldHVybiByPXAsZT11KGQsaCwwKSxpPWQubGVuZ3RoLGZ9LHRoaXMuYmVnaW5TaGFkb3dzPWZ1bmN0aW9uKCl7bz0hMCx1KG51bGwpfSx0aGlzLmVuZFNoYWRvd3M9ZnVuY3Rpb24oKXtvPSExLGMoKX0sdGhpcy5zZXRTdGF0ZT1mdW5jdGlvbihkLHAsaCl7bGV0IGY9ZC5jbGlwcGluZ1BsYW5lcyxtPWQuY2xpcEludGVyc2VjdGlvbix4PWQuY2xpcFNoYWRvd3MsZz1uLmdldChkKTtpZighcnx8bnVsbD09PWZ8fDA9PT1mLmxlbmd0aHx8byYmIXgpbz91KG51bGwpOmMoKTtlbHNle2xldCBiPW8/MDppLEQ9NCpiLFQ9Zy5jbGlwcGluZ1N0YXRlfHxudWxsO2wudmFsdWU9VCxUPXUoZixwLEQsaCk7Zm9yKGxldCBrPTA7ayE9PUQ7KytrKVRba109ZVtrXTtnLmNsaXBwaW5nU3RhdGU9VCx0aGlzLm51bUludGVyc2VjdGlvbj1tP3RoaXMubnVtUGxhbmVzOjAsdGhpcy5udW1QbGFuZXMrPWJ9fX1mdW5jdGlvbiBGV2Uobil7bGV0IHQ9bmV3IFdlYWtNYXA7ZnVuY3Rpb24gZShzLGEpe3JldHVybiAzMDM9PT1hP3MubWFwcGluZz0zMDE6MzA0PT09YSYmKHMubWFwcGluZz0zMDIpLHN9ZnVuY3Rpb24gcihzKXtsZXQgYT1zLnRhcmdldDthLnJlbW92ZUV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLHIpO2xldCBsPXQuZ2V0KGEpO3ZvaWQgMCE9PWwmJih0LmRlbGV0ZShhKSxsLmRpc3Bvc2UoKSl9cmV0dXJue2dldDpmdW5jdGlvbihzKXtpZihzJiZzLmlzVGV4dHVyZSYmITE9PT1zLmlzUmVuZGVyVGFyZ2V0VGV4dHVyZSl7bGV0IGE9cy5tYXBwaW5nO2lmKDMwMz09PWF8fDMwND09PWEpe2lmKHQuaGFzKHMpKXJldHVybiBlKHQuZ2V0KHMpLnRleHR1cmUscy5tYXBwaW5nKTt7bGV0IGw9cy5pbWFnZTtpZihsJiZsLmhlaWdodD4wKXtsZXQgYz1uZXcgY2sobC5oZWlnaHQvMik7cmV0dXJuIGMuZnJvbUVxdWlyZWN0YW5ndWxhclRleHR1cmUobixzKSx0LnNldChzLGMpLHMuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsciksZShjLnRleHR1cmUscy5tYXBwaW5nKX1yZXR1cm4gbnVsbH19fXJldHVybiBzfSxkaXNwb3NlOmZ1bmN0aW9uKCl7dD1uZXcgV2Vha01hcH19fVNkLnBoeXNpY2FsPXt1bmlmb3JtczpqcyhbU2Quc3RhbmRhcmQudW5pZm9ybXMse2NsZWFyY29hdDp7dmFsdWU6MH0sY2xlYXJjb2F0TWFwOnt2YWx1ZTpudWxsfSxjbGVhcmNvYXRSb3VnaG5lc3M6e3ZhbHVlOjB9LGNsZWFyY29hdFJvdWdobmVzc01hcDp7dmFsdWU6bnVsbH0sY2xlYXJjb2F0Tm9ybWFsU2NhbGU6e3ZhbHVlOm5ldyBhdCgxLDEpfSxjbGVhcmNvYXROb3JtYWxNYXA6e3ZhbHVlOm51bGx9LHNoZWVuOnt2YWx1ZTowfSxzaGVlbkNvbG9yOnt2YWx1ZTpuZXcgdm4oMCl9LHNoZWVuQ29sb3JNYXA6e3ZhbHVlOm51bGx9LHNoZWVuUm91Z2huZXNzOnt2YWx1ZToxfSxzaGVlblJvdWdobmVzc01hcDp7dmFsdWU6bnVsbH0sdHJhbnNtaXNzaW9uOnt2YWx1ZTowfSx0cmFuc21pc3Npb25NYXA6e3ZhbHVlOm51bGx9LHRyYW5zbWlzc2lvblNhbXBsZXJTaXplOnt2YWx1ZTpuZXcgYXR9LHRyYW5zbWlzc2lvblNhbXBsZXJNYXA6e3ZhbHVlOm51bGx9LHRoaWNrbmVzczp7dmFsdWU6MH0sdGhpY2tuZXNzTWFwOnt2YWx1ZTpudWxsfSxhdHRlbnVhdGlvbkRpc3RhbmNlOnt2YWx1ZTowfSxhdHRlbnVhdGlvbkNvbG9yOnt2YWx1ZTpuZXcgdm4oMCl9LHNwZWN1bGFySW50ZW5zaXR5Ont2YWx1ZToxfSxzcGVjdWxhckludGVuc2l0eU1hcDp7dmFsdWU6bnVsbH0sc3BlY3VsYXJDb2xvcjp7dmFsdWU6bmV3IHZuKDEsMSwxKX0sc3BlY3VsYXJDb2xvck1hcDp7dmFsdWU6bnVsbH19XSksdmVydGV4U2hhZGVyOkRpLm1lc2hwaHlzaWNhbF92ZXJ0LGZyYWdtZW50U2hhZGVyOkRpLm1lc2hwaHlzaWNhbF9mcmFnfTt2YXIgcWc9Y2xhc3MgZXh0ZW5kcyBRU3tjb25zdHJ1Y3Rvcih0PS0xLGU9MSxpPTEscj0tMSxvPS4xLHM9MmUzKXtzdXBlcigpLHRoaXMudHlwZT0iT3J0aG9ncmFwaGljQ2FtZXJhIix0aGlzLnpvb209MSx0aGlzLnZpZXc9bnVsbCx0aGlzLmxlZnQ9dCx0aGlzLnJpZ2h0PWUsdGhpcy50b3A9aSx0aGlzLmJvdHRvbT1yLHRoaXMubmVhcj1vLHRoaXMuZmFyPXMsdGhpcy51cGRhdGVQcm9qZWN0aW9uTWF0cml4KCl9Y29weSh0LGUpe3JldHVybiBzdXBlci5jb3B5KHQsZSksdGhpcy5sZWZ0PXQubGVmdCx0aGlzLnJpZ2h0PXQucmlnaHQsdGhpcy50b3A9dC50b3AsdGhpcy5ib3R0b209dC5ib3R0b20sdGhpcy5uZWFyPXQubmVhcix0aGlzLmZhcj10LmZhcix0aGlzLnpvb209dC56b29tLHRoaXMudmlldz1udWxsPT09dC52aWV3P251bGw6T2JqZWN0LmFzc2lnbih7fSx0LnZpZXcpLHRoaXN9c2V0Vmlld09mZnNldCh0LGUsaSxyLG8scyl7bnVsbD09PXRoaXMudmlldyYmKHRoaXMudmlldz17ZW5hYmxlZDohMCxmdWxsV2lkdGg6MSxmdWxsSGVpZ2h0OjEsb2Zmc2V0WDowLG9mZnNldFk6MCx3aWR0aDoxLGhlaWdodDoxfSksdGhpcy52aWV3LmVuYWJsZWQ9ITAsdGhpcy52aWV3LmZ1bGxXaWR0aD10LHRoaXMudmlldy5mdWxsSGVpZ2h0PWUsdGhpcy52aWV3Lm9mZnNldFg9aSx0aGlzLnZpZXcub2Zmc2V0WT1yLHRoaXMudmlldy53aWR0aD1vLHRoaXMudmlldy5oZWlnaHQ9cyx0aGlzLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX1jbGVhclZpZXdPZmZzZXQoKXtudWxsIT09dGhpcy52aWV3JiYodGhpcy52aWV3LmVuYWJsZWQ9ITEpLHRoaXMudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpfXVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKXtsZXQgdD0odGhpcy5yaWdodC10aGlzLmxlZnQpLygyKnRoaXMuem9vbSksZT0odGhpcy50b3AtdGhpcy5ib3R0b20pLygyKnRoaXMuem9vbSksaT0odGhpcy5yaWdodCt0aGlzLmxlZnQpLzIscj0odGhpcy50b3ArdGhpcy5ib3R0b20pLzIsbz1pLXQscz1pK3QsYT1yK2UsbD1yLWU7aWYobnVsbCE9PXRoaXMudmlldyYmdGhpcy52aWV3LmVuYWJsZWQpe2xldCBjPSh0aGlzLnJpZ2h0LXRoaXMubGVmdCkvdGhpcy52aWV3LmZ1bGxXaWR0aC90aGlzLnpvb20sdT0odGhpcy50b3AtdGhpcy5ib3R0b20pL3RoaXMudmlldy5mdWxsSGVpZ2h0L3RoaXMuem9vbTtvKz1jKnRoaXMudmlldy5vZmZzZXRYLHM9bytjKnRoaXMudmlldy53aWR0aCxhLT11KnRoaXMudmlldy5vZmZzZXRZLGw9YS11KnRoaXMudmlldy5oZWlnaHR9dGhpcy5wcm9qZWN0aW9uTWF0cml4Lm1ha2VPcnRob2dyYXBoaWMobyxzLGEsbCx0aGlzLm5lYXIsdGhpcy5mYXIpLHRoaXMucHJvamVjdGlvbk1hdHJpeEludmVyc2UuY29weSh0aGlzLnByb2plY3Rpb25NYXRyaXgpLmludmVydCgpfXRvSlNPTih0KXtsZXQgZT1zdXBlci50b0pTT04odCk7cmV0dXJuIGUub2JqZWN0Lnpvb209dGhpcy56b29tLGUub2JqZWN0LmxlZnQ9dGhpcy5sZWZ0LGUub2JqZWN0LnJpZ2h0PXRoaXMucmlnaHQsZS5vYmplY3QudG9wPXRoaXMudG9wLGUub2JqZWN0LmJvdHRvbT10aGlzLmJvdHRvbSxlLm9iamVjdC5uZWFyPXRoaXMubmVhcixlLm9iamVjdC5mYXI9dGhpcy5mYXIsbnVsbCE9PXRoaXMudmlldyYmKGUub2JqZWN0LnZpZXc9T2JqZWN0LmFzc2lnbih7fSx0aGlzLnZpZXcpKSxlfX07cWcucHJvdG90eXBlLmlzT3J0aG9ncmFwaGljQ2FtZXJhPSEwO3ZhciBfYj1jbGFzcyBleHRlbmRzIERwe2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpLHRoaXMudHlwZT0iUmF3U2hhZGVyTWF0ZXJpYWwifX07X2IucHJvdG90eXBlLmlzUmF3U2hhZGVyTWF0ZXJpYWw9ITA7dmFyIHdkPU1hdGgucG93KDIsOCksRmRlPVsuMTI1LC4yMTUsLjM1LC40NDYsLjUyNiwuNTgyXSxOZGU9NStGZGUubGVuZ3RoLFpqPW5ldyBxZyx7X2xvZFBsYW5lczpSUyxfc2l6ZUxvZHM6a3VlLF9zaWdtYXM6SE99PU5XZSgpLEZ1ZT1uZXcgdm4sSmo9bnVsbCxIZz0oMStNYXRoLnNxcnQoNSkpLzIsJHk9MS9IZyxOdWU9W25ldyBpZSgxLDEsMSksbmV3IGllKC0xLDEsMSksbmV3IGllKDEsMSwtMSksbmV3IGllKC0xLDEsLTEpLG5ldyBpZSgwLEhnLCR5KSxuZXcgaWUoMCxIZywtJHkpLG5ldyBpZSgkeSwwLEhnKSxuZXcgaWUoLSR5LDAsSGcpLG5ldyBpZShIZywkeSwwKSxuZXcgaWUoLUhnLCR5LDApXSx1az1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLl9yZW5kZXJlcj10LHRoaXMuX3BpbmdQb25nUmVuZGVyVGFyZ2V0PW51bGwsdGhpcy5fYmx1ck1hdGVyaWFsPWZ1bmN0aW9uKG4pe2xldCB0PW5ldyBGbG9hdDMyQXJyYXkoMjApLGU9bmV3IGllKDAsMSwwKTtyZXR1cm4gbmV3IF9iKHtuYW1lOiJTcGhlcmljYWxHYXVzc2lhbkJsdXIiLGRlZmluZXM6e246MjB9LHVuaWZvcm1zOntlbnZNYXA6e3ZhbHVlOm51bGx9LHNhbXBsZXM6e3ZhbHVlOjF9LHdlaWdodHM6e3ZhbHVlOnR9LGxhdGl0dWRpbmFsOnt2YWx1ZTohMX0sZFRoZXRhOnt2YWx1ZTowfSxtaXBJbnQ6e3ZhbHVlOjB9LHBvbGVBeGlzOnt2YWx1ZTplfX0sdmVydGV4U2hhZGVyOiJcblxuXHRcdHByZWNpc2lvbiBtZWRpdW1wIGZsb2F0O1xuXHRcdHByZWNpc2lvbiBtZWRpdW1wIGludDtcblxuXHRcdGF0dHJpYnV0ZSB2ZWMzIHBvc2l0aW9uO1xuXHRcdGF0dHJpYnV0ZSB2ZWMyIHV2O1xuXHRcdGF0dHJpYnV0ZSBmbG9hdCBmYWNlSW5kZXg7XG5cblx0XHR2YXJ5aW5nIHZlYzMgdk91dHB1dERpcmVjdGlvbjtcblxuXHRcdC8vIFJIIGNvb3JkaW5hdGUgc3lzdGVtOyBQTVJFTSBmYWNlLWluZGV4aW5nIGNvbnZlbnRpb25cblx0XHR2ZWMzIGdldERpcmVjdGlvbiggdmVjMiB1diwgZmxvYXQgZmFjZSApIHtcblxuXHRcdFx0dXYgPSAyLjAgKiB1diAtIDEuMDtcblxuXHRcdFx0dmVjMyBkaXJlY3Rpb24gPSB2ZWMzKCB1diwgMS4wICk7XG5cblx0XHRcdGlmICggZmFjZSA9PSAwLjAgKSB7XG5cblx0XHRcdFx0ZGlyZWN0aW9uID0gZGlyZWN0aW9uLnp5eDsgLy8gKCAxLCB2LCB1ICkgcG9zIHhcblxuXHRcdFx0fSBlbHNlIGlmICggZmFjZSA9PSAxLjAgKSB7XG5cblx0XHRcdFx0ZGlyZWN0aW9uID0gZGlyZWN0aW9uLnh6eTtcblx0XHRcdFx0ZGlyZWN0aW9uLnh6ICo9IC0xLjA7IC8vICggLXUsIDEsIC12ICkgcG9zIHlcblxuXHRcdFx0fSBlbHNlIGlmICggZmFjZSA9PSAyLjAgKSB7XG5cblx0XHRcdFx0ZGlyZWN0aW9uLnggKj0gLTEuMDsgLy8gKCAtdSwgdiwgMSApIHBvcyB6XG5cblx0XHRcdH0gZWxzZSBpZiAoIGZhY2UgPT0gMy4wICkge1xuXG5cdFx0XHRcdGRpcmVjdGlvbiA9IGRpcmVjdGlvbi56eXg7XG5cdFx0XHRcdGRpcmVjdGlvbi54eiAqPSAtMS4wOyAvLyAoIC0xLCB2LCAtdSApIG5lZyB4XG5cblx0XHRcdH0gZWxzZSBpZiAoIGZhY2UgPT0gNC4wICkge1xuXG5cdFx0XHRcdGRpcmVjdGlvbiA9IGRpcmVjdGlvbi54enk7XG5cdFx0XHRcdGRpcmVjdGlvbi54eSAqPSAtMS4wOyAvLyAoIC11LCAtMSwgdiApIG5lZyB5XG5cblx0XHRcdH0gZWxzZSBpZiAoIGZhY2UgPT0gNS4wICkge1xuXG5cdFx0XHRcdGRpcmVjdGlvbi56ICo9IC0xLjA7IC8vICggdSwgdiwgLTEgKSBuZWcgelxuXG5cdFx0XHR9XG5cblx0XHRcdHJldHVybiBkaXJlY3Rpb247XG5cblx0XHR9XG5cblx0XHR2b2lkIG1haW4oKSB7XG5cblx0XHRcdHZPdXRwdXREaXJlY3Rpb24gPSBnZXREaXJlY3Rpb24oIHV2LCBmYWNlSW5kZXggKTtcblx0XHRcdGdsX1Bvc2l0aW9uID0gdmVjNCggcG9zaXRpb24sIDEuMCApO1xuXG5cdFx0fVxuXHQiLGZyYWdtZW50U2hhZGVyOiJcblxuXHRcdFx0cHJlY2lzaW9uIG1lZGl1bXAgZmxvYXQ7XG5cdFx0XHRwcmVjaXNpb24gbWVkaXVtcCBpbnQ7XG5cblx0XHRcdHZhcnlpbmcgdmVjMyB2T3V0cHV0RGlyZWN0aW9uO1xuXG5cdFx0XHR1bmlmb3JtIHNhbXBsZXIyRCBlbnZNYXA7XG5cdFx0XHR1bmlmb3JtIGludCBzYW1wbGVzO1xuXHRcdFx0dW5pZm9ybSBmbG9hdCB3ZWlnaHRzWyBuIF07XG5cdFx0XHR1bmlmb3JtIGJvb2wgbGF0aXR1ZGluYWw7XG5cdFx0XHR1bmlmb3JtIGZsb2F0IGRUaGV0YTtcblx0XHRcdHVuaWZvcm0gZmxvYXQgbWlwSW50O1xuXHRcdFx0dW5pZm9ybSB2ZWMzIHBvbGVBeGlzO1xuXG5cdFx0XHQjZGVmaW5lIEVOVk1BUF9UWVBFX0NVQkVfVVZcblx0XHRcdCNpbmNsdWRlIDxjdWJlX3V2X3JlZmxlY3Rpb25fZnJhZ21lbnQ+XG5cblx0XHRcdHZlYzMgZ2V0U2FtcGxlKCBmbG9hdCB0aGV0YSwgdmVjMyBheGlzICkge1xuXG5cdFx0XHRcdGZsb2F0IGNvc1RoZXRhID0gY29zKCB0aGV0YSApO1xuXHRcdFx0XHQvLyBSb2RyaWd1ZXMnIGF4aXMtYW5nbGUgcm90YXRpb25cblx0XHRcdFx0dmVjMyBzYW1wbGVEaXJlY3Rpb24gPSB2T3V0cHV0RGlyZWN0aW9uICogY29zVGhldGFcblx0XHRcdFx0XHQrIGNyb3NzKCBheGlzLCB2T3V0cHV0RGlyZWN0aW9uICkgKiBzaW4oIHRoZXRhIClcblx0XHRcdFx0XHQrIGF4aXMgKiBkb3QoIGF4aXMsIHZPdXRwdXREaXJlY3Rpb24gKSAqICggMS4wIC0gY29zVGhldGEgKTtcblxuXHRcdFx0XHRyZXR1cm4gYmlsaW5lYXJDdWJlVVYoIGVudk1hcCwgc2FtcGxlRGlyZWN0aW9uLCBtaXBJbnQgKTtcblxuXHRcdFx0fVxuXG5cdFx0XHR2b2lkIG1haW4oKSB7XG5cblx0XHRcdFx0dmVjMyBheGlzID0gbGF0aXR1ZGluYWwgPyBwb2xlQXhpcyA6IGNyb3NzKCBwb2xlQXhpcywgdk91dHB1dERpcmVjdGlvbiApO1xuXG5cdFx0XHRcdGlmICggYWxsKCBlcXVhbCggYXhpcywgdmVjMyggMC4wICkgKSApICkge1xuXG5cdFx0XHRcdFx0YXhpcyA9IHZlYzMoIHZPdXRwdXREaXJlY3Rpb24ueiwgMC4wLCAtIHZPdXRwdXREaXJlY3Rpb24ueCApO1xuXG5cdFx0XHRcdH1cblxuXHRcdFx0XHRheGlzID0gbm9ybWFsaXplKCBheGlzICk7XG5cblx0XHRcdFx0Z2xfRnJhZ0NvbG9yID0gdmVjNCggMC4wLCAwLjAsIDAuMCwgMS4wICk7XG5cdFx0XHRcdGdsX0ZyYWdDb2xvci5yZ2IgKz0gd2VpZ2h0c1sgMCBdICogZ2V0U2FtcGxlKCAwLjAsIGF4aXMgKTtcblxuXHRcdFx0XHRmb3IgKCBpbnQgaSA9IDE7IGkgPCBuOyBpKysgKSB7XG5cblx0XHRcdFx0XHRpZiAoIGkgPj0gc2FtcGxlcyApIHtcblxuXHRcdFx0XHRcdFx0YnJlYWs7XG5cblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHRmbG9hdCB0aGV0YSA9IGRUaGV0YSAqIGZsb2F0KCBpICk7XG5cdFx0XHRcdFx0Z2xfRnJhZ0NvbG9yLnJnYiArPSB3ZWlnaHRzWyBpIF0gKiBnZXRTYW1wbGUoIC0xLjAgKiB0aGV0YSwgYXhpcyApO1xuXHRcdFx0XHRcdGdsX0ZyYWdDb2xvci5yZ2IgKz0gd2VpZ2h0c1sgaSBdICogZ2V0U2FtcGxlKCB0aGV0YSwgYXhpcyApO1xuXG5cdFx0XHRcdH1cblxuXHRcdFx0fVxuXHRcdCIsYmxlbmRpbmc6MCxkZXB0aFRlc3Q6ITEsZGVwdGhXcml0ZTohMX0pfSgpLHRoaXMuX2VxdWlyZWN0U2hhZGVyPW51bGwsdGhpcy5fY3ViZW1hcFNoYWRlcj1udWxsLHRoaXMuX2NvbXBpbGVNYXRlcmlhbCh0aGlzLl9ibHVyTWF0ZXJpYWwpfWZyb21TY2VuZSh0LGU9MCxpPS4xLHI9MTAwKXtKaj10aGlzLl9yZW5kZXJlci5nZXRSZW5kZXJUYXJnZXQoKTtsZXQgbz10aGlzLl9hbGxvY2F0ZVRhcmdldHMoKTtyZXR1cm4gdGhpcy5fc2NlbmVUb0N1YmVVVih0LGkscixvKSxlPjAmJnRoaXMuX2JsdXIobywwLDAsZSksdGhpcy5fYXBwbHlQTVJFTShvKSx0aGlzLl9jbGVhbnVwKG8pLG99ZnJvbUVxdWlyZWN0YW5ndWxhcih0LGU9bnVsbCl7cmV0dXJuIHRoaXMuX2Zyb21UZXh0dXJlKHQsZSl9ZnJvbUN1YmVtYXAodCxlPW51bGwpe3JldHVybiB0aGlzLl9mcm9tVGV4dHVyZSh0LGUpfWNvbXBpbGVDdWJlbWFwU2hhZGVyKCl7bnVsbD09PXRoaXMuX2N1YmVtYXBTaGFkZXImJih0aGlzLl9jdWJlbWFwU2hhZGVyPVZ1ZSgpLHRoaXMuX2NvbXBpbGVNYXRlcmlhbCh0aGlzLl9jdWJlbWFwU2hhZGVyKSl9Y29tcGlsZUVxdWlyZWN0YW5ndWxhclNoYWRlcigpe251bGw9PT10aGlzLl9lcXVpcmVjdFNoYWRlciYmKHRoaXMuX2VxdWlyZWN0U2hhZGVyPUJ1ZSgpLHRoaXMuX2NvbXBpbGVNYXRlcmlhbCh0aGlzLl9lcXVpcmVjdFNoYWRlcikpfWRpc3Bvc2UoKXt0aGlzLl9ibHVyTWF0ZXJpYWwuZGlzcG9zZSgpLG51bGwhPT10aGlzLl9waW5nUG9uZ1JlbmRlclRhcmdldCYmdGhpcy5fcGluZ1BvbmdSZW5kZXJUYXJnZXQuZGlzcG9zZSgpLG51bGwhPT10aGlzLl9jdWJlbWFwU2hhZGVyJiZ0aGlzLl9jdWJlbWFwU2hhZGVyLmRpc3Bvc2UoKSxudWxsIT09dGhpcy5fZXF1aXJlY3RTaGFkZXImJnRoaXMuX2VxdWlyZWN0U2hhZGVyLmRpc3Bvc2UoKTtmb3IobGV0IHQ9MDt0PFJTLmxlbmd0aDt0KyspUlNbdF0uZGlzcG9zZSgpfV9jbGVhbnVwKHQpe3RoaXMuX3JlbmRlcmVyLnNldFJlbmRlclRhcmdldChKaiksdC5zY2lzc29yVGVzdD0hMSxVTyh0LDAsMCx0LndpZHRoLHQuaGVpZ2h0KX1fZnJvbVRleHR1cmUodCxlKXtKaj10aGlzLl9yZW5kZXJlci5nZXRSZW5kZXJUYXJnZXQoKTtsZXQgaT1lfHx0aGlzLl9hbGxvY2F0ZVRhcmdldHModCk7cmV0dXJuIHRoaXMuX3RleHR1cmVUb0N1YmVVVih0LGkpLHRoaXMuX2FwcGx5UE1SRU0oaSksdGhpcy5fY2xlYW51cChpKSxpfV9hbGxvY2F0ZVRhcmdldHModCl7bGV0IGU9e21hZ0ZpbHRlcjpHcyxtaW5GaWx0ZXI6R3MsZ2VuZXJhdGVNaXBtYXBzOiExLHR5cGU6bGIsZm9ybWF0OmdhLGVuY29kaW5nOmJmLGRlcHRoQnVmZmVyOiExfSxpPUx1ZShlKTtyZXR1cm4gaS5kZXB0aEJ1ZmZlcj0hdCxudWxsPT09dGhpcy5fcGluZ1BvbmdSZW5kZXJUYXJnZXQmJih0aGlzLl9waW5nUG9uZ1JlbmRlclRhcmdldD1MdWUoZSkpLGl9X2NvbXBpbGVNYXRlcmlhbCh0KXtsZXQgZT1uZXcgVm8oUlNbMF0sdCk7dGhpcy5fcmVuZGVyZXIuY29tcGlsZShlLFpqKX1fc2NlbmVUb0N1YmVVVih0LGUsaSxyKXtsZXQgYT1uZXcgV3MoOTAsMSxlLGkpLGw9WzEsLTEsMSwxLDEsMV0sYz1bMSwxLDEsLTEsLTEsLTFdLHU9dGhpcy5fcmVuZGVyZXIsZD11LmF1dG9DbGVhcixwPXUudG9uZU1hcHBpbmc7dS5nZXRDbGVhckNvbG9yKEZ1ZSksdS50b25lTWFwcGluZz0wLHUuYXV0b0NsZWFyPSExO2xldCBoPW5ldyBHZyh7bmFtZToiUE1SRU0uQmFja2dyb3VuZCIsc2lkZToxLGRlcHRoV3JpdGU6ITEsZGVwdGhUZXN0OiExfSksZj1uZXcgVm8obmV3IFdnLGgpLG09ITEseD10LmJhY2tncm91bmQ7eD94LmlzQ29sb3ImJihoLmNvbG9yLmNvcHkoeCksdC5iYWNrZ3JvdW5kPW51bGwsbT0hMCk6KGguY29sb3IuY29weShGdWUpLG09ITApO2ZvcihsZXQgZz0wO2c8NjtnKyspe2xldCBiPWclMzswPT09Yj8oYS51cC5zZXQoMCxsW2ddLDApLGEubG9va0F0KGNbZ10sMCwwKSk6MT09PWI/KGEudXAuc2V0KDAsMCxsW2ddKSxhLmxvb2tBdCgwLGNbZ10sMCkpOihhLnVwLnNldCgwLGxbZ10sMCksYS5sb29rQXQoMCwwLGNbZ10pKSxVTyhyLGIqd2QsZz4yP3dkOjAsd2Qsd2QpLHUuc2V0UmVuZGVyVGFyZ2V0KHIpLG0mJnUucmVuZGVyKGYsYSksdS5yZW5kZXIodCxhKX1mLmdlb21ldHJ5LmRpc3Bvc2UoKSxmLm1hdGVyaWFsLmRpc3Bvc2UoKSx1LnRvbmVNYXBwaW5nPXAsdS5hdXRvQ2xlYXI9ZCx0LmJhY2tncm91bmQ9eH1fdGV4dHVyZVRvQ3ViZVVWKHQsZSl7bGV0IGk9dGhpcy5fcmVuZGVyZXIscj0zMDE9PT10Lm1hcHBpbmd8fDMwMj09PXQubWFwcGluZztyPyhudWxsPT09dGhpcy5fY3ViZW1hcFNoYWRlciYmKHRoaXMuX2N1YmVtYXBTaGFkZXI9VnVlKCkpLHRoaXMuX2N1YmVtYXBTaGFkZXIudW5pZm9ybXMuZmxpcEVudk1hcC52YWx1ZT0hMT09PXQuaXNSZW5kZXJUYXJnZXRUZXh0dXJlPy0xOjEpOm51bGw9PT10aGlzLl9lcXVpcmVjdFNoYWRlciYmKHRoaXMuX2VxdWlyZWN0U2hhZGVyPUJ1ZSgpKTtsZXQgbz1yP3RoaXMuX2N1YmVtYXBTaGFkZXI6dGhpcy5fZXF1aXJlY3RTaGFkZXIscz1uZXcgVm8oUlNbMF0sbyksYT1vLnVuaWZvcm1zO2EuZW52TWFwLnZhbHVlPXQscnx8YS50ZXhlbFNpemUudmFsdWUuc2V0KDEvdC5pbWFnZS53aWR0aCwxL3QuaW1hZ2UuaGVpZ2h0KSxVTyhlLDAsMCwzKndkLDIqd2QpLGkuc2V0UmVuZGVyVGFyZ2V0KGUpLGkucmVuZGVyKHMsWmopfV9hcHBseVBNUkVNKHQpe2xldCBlPXRoaXMuX3JlbmRlcmVyLGk9ZS5hdXRvQ2xlYXI7ZS5hdXRvQ2xlYXI9ITE7Zm9yKGxldCByPTE7cjxOZGU7cisrKXtsZXQgbz1NYXRoLnNxcnQoSE9bcl0qSE9bcl0tSE9bci0xXSpIT1tyLTFdKTt0aGlzLl9ibHVyKHQsci0xLHIsbyxOdWVbKHItMSklTnVlLmxlbmd0aF0pfWUuYXV0b0NsZWFyPWl9X2JsdXIodCxlLGkscixvKXtsZXQgcz10aGlzLl9waW5nUG9uZ1JlbmRlclRhcmdldDt0aGlzLl9oYWxmQmx1cih0LHMsZSxpLHIsImxhdGl0dWRpbmFsIixvKSx0aGlzLl9oYWxmQmx1cihzLHQsaSxpLHIsImxvbmdpdHVkaW5hbCIsbyl9X2hhbGZCbHVyKHQsZSxpLHIsbyxzLGEpe2xldCBsPXRoaXMuX3JlbmRlcmVyLGM9dGhpcy5fYmx1ck1hdGVyaWFsOyJsYXRpdHVkaW5hbCIhPT1zJiYibG9uZ2l0dWRpbmFsIiE9PXMmJmNvbnNvbGUuZXJyb3IoImJsdXIgZGlyZWN0aW9uIG11c3QgYmUgZWl0aGVyIGxhdGl0dWRpbmFsIG9yIGxvbmdpdHVkaW5hbCEiKTtsZXQgZD1uZXcgVm8oUlNbcl0sYykscD1jLnVuaWZvcm1zLGg9a3VlW2ldLTEsZj1pc0Zpbml0ZShvKT9NYXRoLlBJLygyKmgpOjIqTWF0aC5QSS8zOSxtPW8vZix4PWlzRmluaXRlKG8pPzErTWF0aC5mbG9vcigzKm0pOjIwO3g+MjAmJmNvbnNvbGUud2Fybihgc2lnbWFSYWRpYW5zLCAke299LCBpcyB0b28gbGFyZ2UgYW5kIHdpbGwgY2xpcCwgYXMgaXQgcmVxdWVzdGVkICR7eH0gc2FtcGxlcyB3aGVuIHRoZSBtYXhpbXVtIGlzIHNldCB0byAyMGApO2xldCBnPVtdLGI9MDtmb3IobGV0IFo9MDtaPDIwOysrWil7bGV0IHo9Wi9tLGZlPU1hdGguZXhwKC16KnovMik7Zy5wdXNoKGZlKSwwPT09Wj9iKz1mZTpaPHgmJihiKz0yKmZlKX1mb3IobGV0IFo9MDtaPGcubGVuZ3RoO1orKylnW1pdPWdbWl0vYjtwLmVudk1hcC52YWx1ZT10LnRleHR1cmUscC5zYW1wbGVzLnZhbHVlPXgscC53ZWlnaHRzLnZhbHVlPWcscC5sYXRpdHVkaW5hbC52YWx1ZT0ibGF0aXR1ZGluYWwiPT09cyxhJiYocC5wb2xlQXhpcy52YWx1ZT1hKSxwLmRUaGV0YS52YWx1ZT1mLHAubWlwSW50LnZhbHVlPTgtaTtsZXQgRD1rdWVbcl07VU8oZSwzKk1hdGgubWF4KDAsd2QtMipEKSwoMD09PXI/MDoyKndkKSsyKkQqKHI+ND9yLTgrNDowKSwzKkQsMipEKSxsLnNldFJlbmRlclRhcmdldChlKSxsLnJlbmRlcihkLFpqKX19O2Z1bmN0aW9uIE5XZSgpe2xldCBuPVtdLHQ9W10sZT1bXSxpPTg7Zm9yKGxldCByPTA7cjxOZGU7cisrKXtsZXQgbz1NYXRoLnBvdygyLGkpO3QucHVzaChvKTtsZXQgcz0xL287cj40P3M9RmRlW3ItOCs0LTFdOjA9PT1yJiYocz0wKSxlLnB1c2gocyk7bGV0IGE9MS8oby0xKSxsPS1hLzIsYz0xK2EvMix1PVtsLGwsYyxsLGMsYyxsLGwsYyxjLGwsY10sZD02LHA9NixoPTMsZj0yLG09MSx4PW5ldyBGbG9hdDMyQXJyYXkoaCpwKmQpLGc9bmV3IEZsb2F0MzJBcnJheShmKnAqZCksYj1uZXcgRmxvYXQzMkFycmF5KG0qcCpkKTtmb3IobGV0IFQ9MDtUPGQ7VCsrKXtsZXQgaz1UJTMqMi8zLTEsWj1UPjI/MDotMTt4LnNldChbayxaLDAsaysyLzMsWiwwLGsrMi8zLForMSwwLGssWiwwLGsrMi8zLForMSwwLGssWisxLDBdLGgqcCpUKSxnLnNldCh1LGYqcCpUKSxiLnNldChbVCxULFQsVCxULFRdLG0qcCpUKX1sZXQgRD1uZXcgbnI7RC5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgWXIoeCxoKSksRC5zZXRBdHRyaWJ1dGUoInV2IixuZXcgWXIoZyxmKSksRC5zZXRBdHRyaWJ1dGUoImZhY2VJbmRleCIsbmV3IFlyKGIsbSkpLG4ucHVzaChEKSxpPjQmJmktLX1yZXR1cm57X2xvZFBsYW5lczpuLF9zaXplTG9kczp0LF9zaWdtYXM6ZX19ZnVuY3Rpb24gTHVlKG4pe2xldCB0PW5ldyBXYSgzKndkLDMqd2Qsbik7cmV0dXJuIHQudGV4dHVyZS5tYXBwaW5nPTMwNix0LnRleHR1cmUubmFtZT0iUE1SRU0uY3ViZVV2Iix0LnNjaXNzb3JUZXN0PSEwLHR9ZnVuY3Rpb24gVU8obix0LGUsaSxyKXtuLnZpZXdwb3J0LnNldCh0LGUsaSxyKSxuLnNjaXNzb3Iuc2V0KHQsZSxpLHIpfWZ1bmN0aW9uIEJ1ZSgpe2xldCBuPW5ldyBhdCgxLDEpO3JldHVybiBuZXcgX2Ioe25hbWU6IkVxdWlyZWN0YW5ndWxhclRvQ3ViZVVWIix1bmlmb3Jtczp7ZW52TWFwOnt2YWx1ZTpudWxsfSx0ZXhlbFNpemU6e3ZhbHVlOm59fSx2ZXJ0ZXhTaGFkZXI6IlxuXG5cdFx0cHJlY2lzaW9uIG1lZGl1bXAgZmxvYXQ7XG5cdFx0cHJlY2lzaW9uIG1lZGl1bXAgaW50O1xuXG5cdFx0YXR0cmlidXRlIHZlYzMgcG9zaXRpb247XG5cdFx0YXR0cmlidXRlIHZlYzIgdXY7XG5cdFx0YXR0cmlidXRlIGZsb2F0IGZhY2VJbmRleDtcblxuXHRcdHZhcnlpbmcgdmVjMyB2T3V0cHV0RGlyZWN0aW9uO1xuXG5cdFx0Ly8gUkggY29vcmRpbmF0ZSBzeXN0ZW07IFBNUkVNIGZhY2UtaW5kZXhpbmcgY29udmVudGlvblxuXHRcdHZlYzMgZ2V0RGlyZWN0aW9uKCB2ZWMyIHV2LCBmbG9hdCBmYWNlICkge1xuXG5cdFx0XHR1diA9IDIuMCAqIHV2IC0gMS4wO1xuXG5cdFx0XHR2ZWMzIGRpcmVjdGlvbiA9IHZlYzMoIHV2LCAxLjAgKTtcblxuXHRcdFx0aWYgKCBmYWNlID09IDAuMCApIHtcblxuXHRcdFx0XHRkaXJlY3Rpb24gPSBkaXJlY3Rpb24uenl4OyAvLyAoIDEsIHYsIHUgKSBwb3MgeFxuXG5cdFx0XHR9IGVsc2UgaWYgKCBmYWNlID09IDEuMCApIHtcblxuXHRcdFx0XHRkaXJlY3Rpb24gPSBkaXJlY3Rpb24ueHp5O1xuXHRcdFx0XHRkaXJlY3Rpb24ueHogKj0gLTEuMDsgLy8gKCAtdSwgMSwgLXYgKSBwb3MgeVxuXG5cdFx0XHR9IGVsc2UgaWYgKCBmYWNlID09IDIuMCApIHtcblxuXHRcdFx0XHRkaXJlY3Rpb24ueCAqPSAtMS4wOyAvLyAoIC11LCB2LCAxICkgcG9zIHpcblxuXHRcdFx0fSBlbHNlIGlmICggZmFjZSA9PSAzLjAgKSB7XG5cblx0XHRcdFx0ZGlyZWN0aW9uID0gZGlyZWN0aW9uLnp5eDtcblx0XHRcdFx0ZGlyZWN0aW9uLnh6ICo9IC0xLjA7IC8vICggLTEsIHYsIC11ICkgbmVnIHhcblxuXHRcdFx0fSBlbHNlIGlmICggZmFjZSA9PSA0LjAgKSB7XG5cblx0XHRcdFx0ZGlyZWN0aW9uID0gZGlyZWN0aW9uLnh6eTtcblx0XHRcdFx0ZGlyZWN0aW9uLnh5ICo9IC0xLjA7IC8vICggLXUsIC0xLCB2ICkgbmVnIHlcblxuXHRcdFx0fSBlbHNlIGlmICggZmFjZSA9PSA1LjAgKSB7XG5cblx0XHRcdFx0ZGlyZWN0aW9uLnogKj0gLTEuMDsgLy8gKCB1LCB2LCAtMSApIG5lZyB6XG5cblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuIGRpcmVjdGlvbjtcblxuXHRcdH1cblxuXHRcdHZvaWQgbWFpbigpIHtcblxuXHRcdFx0dk91dHB1dERpcmVjdGlvbiA9IGdldERpcmVjdGlvbiggdXYsIGZhY2VJbmRleCApO1xuXHRcdFx0Z2xfUG9zaXRpb24gPSB2ZWM0KCBwb3NpdGlvbiwgMS4wICk7XG5cblx0XHR9XG5cdCIsZnJhZ21lbnRTaGFkZXI6IlxuXG5cdFx0XHRwcmVjaXNpb24gbWVkaXVtcCBmbG9hdDtcblx0XHRcdHByZWNpc2lvbiBtZWRpdW1wIGludDtcblxuXHRcdFx0dmFyeWluZyB2ZWMzIHZPdXRwdXREaXJlY3Rpb247XG5cblx0XHRcdHVuaWZvcm0gc2FtcGxlcjJEIGVudk1hcDtcblx0XHRcdHVuaWZvcm0gdmVjMiB0ZXhlbFNpemU7XG5cblx0XHRcdCNpbmNsdWRlIDxjb21tb24+XG5cblx0XHRcdHZvaWQgbWFpbigpIHtcblxuXHRcdFx0XHRnbF9GcmFnQ29sb3IgPSB2ZWM0KCAwLjAsIDAuMCwgMC4wLCAxLjAgKTtcblxuXHRcdFx0XHR2ZWMzIG91dHB1dERpcmVjdGlvbiA9IG5vcm1hbGl6ZSggdk91dHB1dERpcmVjdGlvbiApO1xuXHRcdFx0XHR2ZWMyIHV2ID0gZXF1aXJlY3RVdiggb3V0cHV0RGlyZWN0aW9uICk7XG5cblx0XHRcdFx0dmVjMiBmID0gZnJhY3QoIHV2IC8gdGV4ZWxTaXplIC0gMC41ICk7XG5cdFx0XHRcdHV2IC09IGYgKiB0ZXhlbFNpemU7XG5cdFx0XHRcdHZlYzMgdGwgPSB0ZXh0dXJlMkQgKCBlbnZNYXAsIHV2ICkucmdiO1xuXHRcdFx0XHR1di54ICs9IHRleGVsU2l6ZS54O1xuXHRcdFx0XHR2ZWMzIHRyID0gdGV4dHVyZTJEICggZW52TWFwLCB1diApLnJnYjtcblx0XHRcdFx0dXYueSArPSB0ZXhlbFNpemUueTtcblx0XHRcdFx0dmVjMyBiciA9IHRleHR1cmUyRCAoIGVudk1hcCwgdXYgKS5yZ2I7XG5cdFx0XHRcdHV2LnggLT0gdGV4ZWxTaXplLng7XG5cdFx0XHRcdHZlYzMgYmwgPSB0ZXh0dXJlMkQgKCBlbnZNYXAsIHV2ICkucmdiO1xuXG5cdFx0XHRcdHZlYzMgdG0gPSBtaXgoIHRsLCB0ciwgZi54ICk7XG5cdFx0XHRcdHZlYzMgYm0gPSBtaXgoIGJsLCBiciwgZi54ICk7XG5cdFx0XHRcdGdsX0ZyYWdDb2xvci5yZ2IgPSBtaXgoIHRtLCBibSwgZi55ICk7XG5cblx0XHRcdH1cblx0XHQiLGJsZW5kaW5nOjAsZGVwdGhUZXN0OiExLGRlcHRoV3JpdGU6ITF9KX1mdW5jdGlvbiBWdWUoKXtyZXR1cm4gbmV3IF9iKHtuYW1lOiJDdWJlbWFwVG9DdWJlVVYiLHVuaWZvcm1zOntlbnZNYXA6e3ZhbHVlOm51bGx9LGZsaXBFbnZNYXA6e3ZhbHVlOi0xfX0sdmVydGV4U2hhZGVyOiJcblxuXHRcdHByZWNpc2lvbiBtZWRpdW1wIGZsb2F0O1xuXHRcdHByZWNpc2lvbiBtZWRpdW1wIGludDtcblxuXHRcdGF0dHJpYnV0ZSB2ZWMzIHBvc2l0aW9uO1xuXHRcdGF0dHJpYnV0ZSB2ZWMyIHV2O1xuXHRcdGF0dHJpYnV0ZSBmbG9hdCBmYWNlSW5kZXg7XG5cblx0XHR2YXJ5aW5nIHZlYzMgdk91dHB1dERpcmVjdGlvbjtcblxuXHRcdC8vIFJIIGNvb3JkaW5hdGUgc3lzdGVtOyBQTVJFTSBmYWNlLWluZGV4aW5nIGNvbnZlbnRpb25cblx0XHR2ZWMzIGdldERpcmVjdGlvbiggdmVjMiB1diwgZmxvYXQgZmFjZSApIHtcblxuXHRcdFx0dXYgPSAyLjAgKiB1diAtIDEuMDtcblxuXHRcdFx0dmVjMyBkaXJlY3Rpb24gPSB2ZWMzKCB1diwgMS4wICk7XG5cblx0XHRcdGlmICggZmFjZSA9PSAwLjAgKSB7XG5cblx0XHRcdFx0ZGlyZWN0aW9uID0gZGlyZWN0aW9uLnp5eDsgLy8gKCAxLCB2LCB1ICkgcG9zIHhcblxuXHRcdFx0fSBlbHNlIGlmICggZmFjZSA9PSAxLjAgKSB7XG5cblx0XHRcdFx0ZGlyZWN0aW9uID0gZGlyZWN0aW9uLnh6eTtcblx0XHRcdFx0ZGlyZWN0aW9uLnh6ICo9IC0xLjA7IC8vICggLXUsIDEsIC12ICkgcG9zIHlcblxuXHRcdFx0fSBlbHNlIGlmICggZmFjZSA9PSAyLjAgKSB7XG5cblx0XHRcdFx0ZGlyZWN0aW9uLnggKj0gLTEuMDsgLy8gKCAtdSwgdiwgMSApIHBvcyB6XG5cblx0XHRcdH0gZWxzZSBpZiAoIGZhY2UgPT0gMy4wICkge1xuXG5cdFx0XHRcdGRpcmVjdGlvbiA9IGRpcmVjdGlvbi56eXg7XG5cdFx0XHRcdGRpcmVjdGlvbi54eiAqPSAtMS4wOyAvLyAoIC0xLCB2LCAtdSApIG5lZyB4XG5cblx0XHRcdH0gZWxzZSBpZiAoIGZhY2UgPT0gNC4wICkge1xuXG5cdFx0XHRcdGRpcmVjdGlvbiA9IGRpcmVjdGlvbi54enk7XG5cdFx0XHRcdGRpcmVjdGlvbi54eSAqPSAtMS4wOyAvLyAoIC11LCAtMSwgdiApIG5lZyB5XG5cblx0XHRcdH0gZWxzZSBpZiAoIGZhY2UgPT0gNS4wICkge1xuXG5cdFx0XHRcdGRpcmVjdGlvbi56ICo9IC0xLjA7IC8vICggdSwgdiwgLTEgKSBuZWcgelxuXG5cdFx0XHR9XG5cblx0XHRcdHJldHVybiBkaXJlY3Rpb247XG5cblx0XHR9XG5cblx0XHR2b2lkIG1haW4oKSB7XG5cblx0XHRcdHZPdXRwdXREaXJlY3Rpb24gPSBnZXREaXJlY3Rpb24oIHV2LCBmYWNlSW5kZXggKTtcblx0XHRcdGdsX1Bvc2l0aW9uID0gdmVjNCggcG9zaXRpb24sIDEuMCApO1xuXG5cdFx0fVxuXHQiLGZyYWdtZW50U2hhZGVyOiJcblxuXHRcdFx0cHJlY2lzaW9uIG1lZGl1bXAgZmxvYXQ7XG5cdFx0XHRwcmVjaXNpb24gbWVkaXVtcCBpbnQ7XG5cblx0XHRcdHVuaWZvcm0gZmxvYXQgZmxpcEVudk1hcDtcblxuXHRcdFx0dmFyeWluZyB2ZWMzIHZPdXRwdXREaXJlY3Rpb247XG5cblx0XHRcdHVuaWZvcm0gc2FtcGxlckN1YmUgZW52TWFwO1xuXG5cdFx0XHR2b2lkIG1haW4oKSB7XG5cblx0XHRcdFx0Z2xfRnJhZ0NvbG9yID0gdGV4dHVyZUN1YmUoIGVudk1hcCwgdmVjMyggZmxpcEVudk1hcCAqIHZPdXRwdXREaXJlY3Rpb24ueCwgdk91dHB1dERpcmVjdGlvbi55eiApICk7XG5cblx0XHRcdH1cblx0XHQiLGJsZW5kaW5nOjAsZGVwdGhUZXN0OiExLGRlcHRoV3JpdGU6ITF9KX1mdW5jdGlvbiBCV2Uobil7bGV0IHQ9bmV3IFdlYWtNYXAsZT1udWxsO2Z1bmN0aW9uIG8oYSl7bGV0IGw9YS50YXJnZXQ7bC5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixvKTtsZXQgYz10LmdldChsKTt2b2lkIDAhPT1jJiYodC5kZWxldGUobCksYy5kaXNwb3NlKCkpfXJldHVybntnZXQ6ZnVuY3Rpb24oYSl7aWYoYSYmYS5pc1RleHR1cmUpe2xldCBsPWEubWFwcGluZyxjPTMwMz09PWx8fDMwND09PWwsdT0zMDE9PT1sfHwzMDI9PT1sO2lmKGN8fHUpe2lmKGEuaXNSZW5kZXJUYXJnZXRUZXh0dXJlJiYhMD09PWEubmVlZHNQTVJFTVVwZGF0ZSl7YS5uZWVkc1BNUkVNVXBkYXRlPSExO2xldCBkPXQuZ2V0KGEpO3JldHVybiBudWxsPT09ZSYmKGU9bmV3IHVrKG4pKSxkPWM/ZS5mcm9tRXF1aXJlY3Rhbmd1bGFyKGEsZCk6ZS5mcm9tQ3ViZW1hcChhLGQpLHQuc2V0KGEsZCksZC50ZXh0dXJlfWlmKHQuaGFzKGEpKXJldHVybiB0LmdldChhKS50ZXh0dXJlO3tsZXQgZD1hLmltYWdlO2lmKGMmJmQmJmQuaGVpZ2h0PjB8fHUmJmQmJmZ1bmN0aW9uKGEpe2xldCBsPTA7Zm9yKGxldCB1PTA7dTw2O3UrKyl2b2lkIDAhPT1hW3VdJiZsKys7cmV0dXJuIDY9PT1sfShkKSl7bnVsbD09PWUmJihlPW5ldyB1ayhuKSk7bGV0IHA9Yz9lLmZyb21FcXVpcmVjdGFuZ3VsYXIoYSk6ZS5mcm9tQ3ViZW1hcChhKTtyZXR1cm4gdC5zZXQoYSxwKSxhLmFkZEV2ZW50TGlzdGVuZXIoImRpc3Bvc2UiLG8pLHAudGV4dHVyZX1yZXR1cm4gbnVsbH19fXJldHVybiBhfSxkaXNwb3NlOmZ1bmN0aW9uKCl7dD1uZXcgV2Vha01hcCxudWxsIT09ZSYmKGUuZGlzcG9zZSgpLGU9bnVsbCl9fX1mdW5jdGlvbiBWV2Uobil7bGV0IHQ9e307ZnVuY3Rpb24gZShpKXtpZih2b2lkIDAhPT10W2ldKXJldHVybiB0W2ldO2xldCByO3N3aXRjaChpKXtjYXNlIldFQkdMX2RlcHRoX3RleHR1cmUiOnI9bi5nZXRFeHRlbnNpb24oIldFQkdMX2RlcHRoX3RleHR1cmUiKXx8bi5nZXRFeHRlbnNpb24oIk1PWl9XRUJHTF9kZXB0aF90ZXh0dXJlIil8fG4uZ2V0RXh0ZW5zaW9uKCJXRUJLSVRfV0VCR0xfZGVwdGhfdGV4dHVyZSIpO2JyZWFrO2Nhc2UiRVhUX3RleHR1cmVfZmlsdGVyX2FuaXNvdHJvcGljIjpyPW4uZ2V0RXh0ZW5zaW9uKCJFWFRfdGV4dHVyZV9maWx0ZXJfYW5pc290cm9waWMiKXx8bi5nZXRFeHRlbnNpb24oIk1PWl9FWFRfdGV4dHVyZV9maWx0ZXJfYW5pc290cm9waWMiKXx8bi5nZXRFeHRlbnNpb24oIldFQktJVF9FWFRfdGV4dHVyZV9maWx0ZXJfYW5pc290cm9waWMiKTticmVhaztjYXNlIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjIjpyPW4uZ2V0RXh0ZW5zaW9uKCJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfczN0YyIpfHxuLmdldEV4dGVuc2lvbigiTU9aX1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjIil8fG4uZ2V0RXh0ZW5zaW9uKCJXRUJLSVRfV0VCR0xfY29tcHJlc3NlZF90ZXh0dXJlX3MzdGMiKTticmVhaztjYXNlIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9wdnJ0YyI6cj1uLmdldEV4dGVuc2lvbigiV0VCR0xfY29tcHJlc3NlZF90ZXh0dXJlX3B2cnRjIil8fG4uZ2V0RXh0ZW5zaW9uKCJXRUJLSVRfV0VCR0xfY29tcHJlc3NlZF90ZXh0dXJlX3B2cnRjIik7YnJlYWs7ZGVmYXVsdDpyPW4uZ2V0RXh0ZW5zaW9uKGkpfXJldHVybiB0W2ldPXIscn1yZXR1cm57aGFzOmZ1bmN0aW9uKGkpe3JldHVybiBudWxsIT09ZShpKX0saW5pdDpmdW5jdGlvbihpKXtpLmlzV2ViR0wyP2UoIkVYVF9jb2xvcl9idWZmZXJfZmxvYXQiKTooZSgiV0VCR0xfZGVwdGhfdGV4dHVyZSIpLGUoIk9FU190ZXh0dXJlX2Zsb2F0IiksZSgiT0VTX3RleHR1cmVfaGFsZl9mbG9hdCIpLGUoIk9FU190ZXh0dXJlX2hhbGZfZmxvYXRfbGluZWFyIiksZSgiT0VTX3N0YW5kYXJkX2Rlcml2YXRpdmVzIiksZSgiT0VTX2VsZW1lbnRfaW5kZXhfdWludCIpLGUoIk9FU192ZXJ0ZXhfYXJyYXlfb2JqZWN0IiksZSgiQU5HTEVfaW5zdGFuY2VkX2FycmF5cyIpKSxlKCJPRVNfdGV4dHVyZV9mbG9hdF9saW5lYXIiKSxlKCJFWFRfY29sb3JfYnVmZmVyX2hhbGZfZmxvYXQiKSxlKCJXRUJHTF9tdWx0aXNhbXBsZWRfcmVuZGVyX3RvX3RleHR1cmUiKX0sZ2V0OmZ1bmN0aW9uKGkpe2xldCByPWUoaSk7cmV0dXJuIG51bGw9PT1yJiZjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6ICIraSsiIGV4dGVuc2lvbiBub3Qgc3VwcG9ydGVkLiIpLHJ9fX1mdW5jdGlvbiBIV2Uobix0LGUsaSl7bGV0IHI9e30sbz1uZXcgV2Vha01hcDtmdW5jdGlvbiBzKGQpe2xldCBwPWQudGFyZ2V0O251bGwhPT1wLmluZGV4JiZ0LnJlbW92ZShwLmluZGV4KTtmb3IobGV0IGYgaW4gcC5hdHRyaWJ1dGVzKXQucmVtb3ZlKHAuYXR0cmlidXRlc1tmXSk7cC5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixzKSxkZWxldGUgcltwLmlkXTtsZXQgaD1vLmdldChwKTtoJiYodC5yZW1vdmUoaCksby5kZWxldGUocCkpLGkucmVsZWFzZVN0YXRlc09mR2VvbWV0cnkocCksITA9PT1wLmlzSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnkmJmRlbGV0ZSBwLl9tYXhJbnN0YW5jZUNvdW50LGUubWVtb3J5Lmdlb21ldHJpZXMtLX1mdW5jdGlvbiBjKGQpe2xldCBwPVtdLGg9ZC5pbmRleCxmPWQuYXR0cmlidXRlcy5wb3NpdGlvbixtPTA7aWYobnVsbCE9PWgpe2xldCBiPWguYXJyYXk7bT1oLnZlcnNpb247Zm9yKGxldCBEPTAsVD1iLmxlbmd0aDtEPFQ7RCs9Myl7bGV0IGs9YltEKzBdLFo9YltEKzFdLHo9YltEKzJdO3AucHVzaChrLFosWix6LHosayl9fWVsc2V7bT1mLnZlcnNpb247Zm9yKGxldCBEPTAsVD1mLmFycmF5Lmxlbmd0aC8zLTE7RDxUO0QrPTMpe2xldCBrPUQrMCxaPUQrMSx6PUQrMjtwLnB1c2goayxaLFoseix6LGspfX1sZXQgeD1uZXcoT2RlKHApP2xrOmFrKShwLDEpO3gudmVyc2lvbj1tO2xldCBnPW8uZ2V0KGQpO2cmJnQucmVtb3ZlKGcpLG8uc2V0KGQseCl9cmV0dXJue2dldDpmdW5jdGlvbihkLHApe3JldHVybiEwPT09cltwLmlkXXx8KHAuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIscykscltwLmlkXT0hMCxlLm1lbW9yeS5nZW9tZXRyaWVzKyspLHB9LHVwZGF0ZTpmdW5jdGlvbihkKXtsZXQgcD1kLmF0dHJpYnV0ZXM7Zm9yKGxldCBmIGluIHApdC51cGRhdGUocFtmXSwzNDk2Mik7bGV0IGg9ZC5tb3JwaEF0dHJpYnV0ZXM7Zm9yKGxldCBmIGluIGgpe2xldCBtPWhbZl07Zm9yKGxldCB4PTAsZz1tLmxlbmd0aDt4PGc7eCsrKXQudXBkYXRlKG1beF0sMzQ5NjIpfX0sZ2V0V2lyZWZyYW1lQXR0cmlidXRlOmZ1bmN0aW9uKGQpe2xldCBwPW8uZ2V0KGQpO2lmKHApe2xldCBoPWQuaW5kZXg7bnVsbCE9PWgmJnAudmVyc2lvbjxoLnZlcnNpb24mJmMoZCl9ZWxzZSBjKGQpO3JldHVybiBvLmdldChkKX19fWZ1bmN0aW9uIFVXZShuLHQsZSxpKXtsZXQgbyxhLGwscj1pLmlzV2ViR0wyO3RoaXMuc2V0TW9kZT1mdW5jdGlvbihwKXtvPXB9LHRoaXMuc2V0SW5kZXg9ZnVuY3Rpb24ocCl7YT1wLnR5cGUsbD1wLmJ5dGVzUGVyRWxlbWVudH0sdGhpcy5yZW5kZXI9ZnVuY3Rpb24ocCxoKXtuLmRyYXdFbGVtZW50cyhvLGgsYSxwKmwpLGUudXBkYXRlKGgsbywxKX0sdGhpcy5yZW5kZXJJbnN0YW5jZXM9ZnVuY3Rpb24ocCxoLGYpe2lmKDA9PT1mKXJldHVybjtsZXQgbSx4O2lmKHIpbT1uLHg9ImRyYXdFbGVtZW50c0luc3RhbmNlZCI7ZWxzZSBpZihtPXQuZ2V0KCJBTkdMRV9pbnN0YW5jZWRfYXJyYXlzIikseD0iZHJhd0VsZW1lbnRzSW5zdGFuY2VkQU5HTEUiLG51bGw9PT1tKXJldHVybiB2b2lkIGNvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMSW5kZXhlZEJ1ZmZlclJlbmRlcmVyOiB1c2luZyBUSFJFRS5JbnN0YW5jZWRCdWZmZXJHZW9tZXRyeSBidXQgaGFyZHdhcmUgZG9lcyBub3Qgc3VwcG9ydCBleHRlbnNpb24gQU5HTEVfaW5zdGFuY2VkX2FycmF5cy4iKTttW3hdKG8saCxhLHAqbCxmKSxlLnVwZGF0ZShoLG8sZil9fWZ1bmN0aW9uIHpXZShuKXtsZXQgZT17ZnJhbWU6MCxjYWxsczowLHRyaWFuZ2xlczowLHBvaW50czowLGxpbmVzOjB9O3JldHVybnttZW1vcnk6e2dlb21ldHJpZXM6MCx0ZXh0dXJlczowfSxyZW5kZXI6ZSxwcm9ncmFtczpudWxsLGF1dG9SZXNldDohMCxyZXNldDpmdW5jdGlvbigpe2UuZnJhbWUrKyxlLmNhbGxzPTAsZS50cmlhbmdsZXM9MCxlLnBvaW50cz0wLGUubGluZXM9MH0sdXBkYXRlOmZ1bmN0aW9uKG8scyxhKXtzd2l0Y2goZS5jYWxscysrLHMpe2Nhc2UgNDplLnRyaWFuZ2xlcys9YSooby8zKTticmVhaztjYXNlIDE6ZS5saW5lcys9YSooby8yKTticmVhaztjYXNlIDM6ZS5saW5lcys9YSooby0xKTticmVhaztjYXNlIDI6ZS5saW5lcys9YSpvO2JyZWFrO2Nhc2UgMDplLnBvaW50cys9YSpvO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xJbmZvOiBVbmtub3duIGRyYXcgbW9kZToiLHMpfX19fXZhciBKUz1jbGFzcyBleHRlbmRzIEhve2NvbnN0cnVjdG9yKHQ9bnVsbCxlPTEsaT0xLHI9MSl7c3VwZXIobnVsbCksdGhpcy5pbWFnZT17ZGF0YTp0LHdpZHRoOmUsaGVpZ2h0OmksZGVwdGg6cn0sdGhpcy5tYWdGaWx0ZXI9Wm8sdGhpcy5taW5GaWx0ZXI9Wm8sdGhpcy53cmFwUj1FbCx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMSx0aGlzLmZsaXBZPSExLHRoaXMudW5wYWNrQWxpZ25tZW50PTF9fTtmdW5jdGlvbiBqV2Uobix0KXtyZXR1cm4gblswXS10WzBdfWZ1bmN0aW9uIEdXZShuLHQpe3JldHVybiBNYXRoLmFicyh0WzFdKS1NYXRoLmFicyhuWzFdKX1mdW5jdGlvbiBIdWUobix0KXtsZXQgZT0xLGk9dC5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlP3QuZGF0YS5hcnJheTp0LmFycmF5O2kgaW5zdGFuY2VvZiBJbnQ4QXJyYXk/ZT0xMjc6aSBpbnN0YW5jZW9mIEludDE2QXJyYXk/ZT0zMjc2NzppIGluc3RhbmNlb2YgSW50MzJBcnJheT9lPTIxNDc0ODM2NDc6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xNb3JwaHRhcmdldHM6IFVuc3VwcG9ydGVkIG1vcnBoIGF0dHJpYnV0ZSBkYXRhIHR5cGU6ICIsaSksbi5kaXZpZGVTY2FsYXIoZSl9ZnVuY3Rpb24gV1dlKG4sdCxlKXtsZXQgaT17fSxyPW5ldyBGbG9hdDMyQXJyYXkoOCksbz1uZXcgV2Vha01hcCxzPW5ldyBpZSxhPVtdO2ZvcihsZXQgYz0wO2M8ODtjKyspYVtjXT1bYywwXTtyZXR1cm57dXBkYXRlOmZ1bmN0aW9uKGMsdSxkLHApe2xldCBoPWMubW9ycGhUYXJnZXRJbmZsdWVuY2VzO2lmKCEwPT09dC5pc1dlYkdMMil7bGV0IGY9dS5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24ubGVuZ3RoLG09by5nZXQodSk7aWYodm9pZCAwPT09bXx8bS5jb3VudCE9PWYpe2xldCBGPWZ1bmN0aW9uKCl7aGUuZGlzcG9zZSgpLG8uZGVsZXRlKHUpLHUucmVtb3ZlRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsRil9O3ZvaWQgMCE9PW0mJm0udGV4dHVyZS5kaXNwb3NlKCk7bGV0IGI9dm9pZCAwIT09dS5tb3JwaEF0dHJpYnV0ZXMubm9ybWFsLEQ9dS5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24sVD11Lm1vcnBoQXR0cmlidXRlcy5ub3JtYWx8fFtdLFo9ITA9PT1iPzI6MSx6PXUuYXR0cmlidXRlcy5wb3NpdGlvbi5jb3VudCpaLGZlPTE7ej50Lm1heFRleHR1cmVTaXplJiYoZmU9TWF0aC5jZWlsKHovdC5tYXhUZXh0dXJlU2l6ZSksej10Lm1heFRleHR1cmVTaXplKTtsZXQgdWU9bmV3IEZsb2F0MzJBcnJheSh6KmZlKjQqZiksaGU9bmV3IEpTKHVlLHosZmUsZik7aGUuZm9ybWF0PWdhLGhlLnR5cGU9VWcsaGUubmVlZHNVcGRhdGU9ITA7bGV0IHc9NCpaO2ZvcihsZXQgcT0wO3E8ZjtxKyspe2xldCBLPURbcV0sZGU9VFtxXSxZPXoqZmUqNCpxO2ZvcihsZXQgYWU9MDthZTxLLmNvdW50O2FlKyspe3MuZnJvbUJ1ZmZlckF0dHJpYnV0ZShLLGFlKSwhMD09PUsubm9ybWFsaXplZCYmSHVlKHMsSyk7bGV0IGxlPWFlKnc7dWVbWStsZSswXT1zLngsdWVbWStsZSsxXT1zLnksdWVbWStsZSsyXT1zLnosdWVbWStsZSszXT0wLCEwPT09YiYmKHMuZnJvbUJ1ZmZlckF0dHJpYnV0ZShkZSxhZSksITA9PT1kZS5ub3JtYWxpemVkJiZIdWUocyxkZSksdWVbWStsZSs0XT1zLngsdWVbWStsZSs1XT1zLnksdWVbWStsZSs2XT1zLnosdWVbWStsZSs3XT0wKX19bT17Y291bnQ6Zix0ZXh0dXJlOmhlLHNpemU6bmV3IGF0KHosZmUpfSxvLnNldCh1LG0pLHUuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsRil9bGV0IHg9MDtmb3IobGV0IGI9MDtiPGgubGVuZ3RoO2IrKyl4Kz1oW2JdO2xldCBnPXUubW9ycGhUYXJnZXRzUmVsYXRpdmU/MToxLXg7cC5nZXRVbmlmb3JtcygpLnNldFZhbHVlKG4sIm1vcnBoVGFyZ2V0QmFzZUluZmx1ZW5jZSIsZykscC5nZXRVbmlmb3JtcygpLnNldFZhbHVlKG4sIm1vcnBoVGFyZ2V0SW5mbHVlbmNlcyIsaCkscC5nZXRVbmlmb3JtcygpLnNldFZhbHVlKG4sIm1vcnBoVGFyZ2V0c1RleHR1cmUiLG0udGV4dHVyZSxlKSxwLmdldFVuaWZvcm1zKCkuc2V0VmFsdWUobiwibW9ycGhUYXJnZXRzVGV4dHVyZVNpemUiLG0uc2l6ZSl9ZWxzZXtsZXQgZj12b2lkIDA9PT1oPzA6aC5sZW5ndGgsbT1pW3UuaWRdO2lmKHZvaWQgMD09PW18fG0ubGVuZ3RoIT09Zil7bT1bXTtmb3IobGV0IFQ9MDtUPGY7VCsrKW1bVF09W1QsMF07aVt1LmlkXT1tfWZvcihsZXQgVD0wO1Q8ZjtUKyspe2xldCBrPW1bVF07a1swXT1ULGtbMV09aFtUXX1tLnNvcnQoR1dlKTtmb3IobGV0IFQ9MDtUPDg7VCsrKVQ8ZiYmbVtUXVsxXT8oYVtUXVswXT1tW1RdWzBdLGFbVF1bMV09bVtUXVsxXSk6KGFbVF1bMF09TnVtYmVyLk1BWF9TQUZFX0lOVEVHRVIsYVtUXVsxXT0wKTthLnNvcnQoaldlKTtsZXQgeD11Lm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbixnPXUubW9ycGhBdHRyaWJ1dGVzLm5vcm1hbCxiPTA7Zm9yKGxldCBUPTA7VDw4O1QrKyl7bGV0IGs9YVtUXSxaPWtbMF0sej1rWzFdO1ohPT1OdW1iZXIuTUFYX1NBRkVfSU5URUdFUiYmej8oeCYmdS5nZXRBdHRyaWJ1dGUoIm1vcnBoVGFyZ2V0IitUKSE9PXhbWl0mJnUuc2V0QXR0cmlidXRlKCJtb3JwaFRhcmdldCIrVCx4W1pdKSxnJiZ1LmdldEF0dHJpYnV0ZSgibW9ycGhOb3JtYWwiK1QpIT09Z1taXSYmdS5zZXRBdHRyaWJ1dGUoIm1vcnBoTm9ybWFsIitULGdbWl0pLHJbVF09eixiKz16KTooeCYmITA9PT11Lmhhc0F0dHJpYnV0ZSgibW9ycGhUYXJnZXQiK1QpJiZ1LmRlbGV0ZUF0dHJpYnV0ZSgibW9ycGhUYXJnZXQiK1QpLGcmJiEwPT09dS5oYXNBdHRyaWJ1dGUoIm1vcnBoTm9ybWFsIitUKSYmdS5kZWxldGVBdHRyaWJ1dGUoIm1vcnBoTm9ybWFsIitUKSxyW1RdPTApfWxldCBEPXUubW9ycGhUYXJnZXRzUmVsYXRpdmU/MToxLWI7cC5nZXRVbmlmb3JtcygpLnNldFZhbHVlKG4sIm1vcnBoVGFyZ2V0QmFzZUluZmx1ZW5jZSIsRCkscC5nZXRVbmlmb3JtcygpLnNldFZhbHVlKG4sIm1vcnBoVGFyZ2V0SW5mbHVlbmNlcyIscil9fX19ZnVuY3Rpb24gcVdlKG4sdCxlLGkpe2xldCByPW5ldyBXZWFrTWFwO2Z1bmN0aW9uIGEobCl7bGV0IGM9bC50YXJnZXQ7Yy5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixhKSxlLnJlbW92ZShjLmluc3RhbmNlTWF0cml4KSxudWxsIT09Yy5pbnN0YW5jZUNvbG9yJiZlLnJlbW92ZShjLmluc3RhbmNlQ29sb3IpfXJldHVybnt1cGRhdGU6ZnVuY3Rpb24obCl7bGV0IGM9aS5yZW5kZXIuZnJhbWUsZD10LmdldChsLGwuZ2VvbWV0cnkpO3JldHVybiByLmdldChkKSE9PWMmJih0LnVwZGF0ZShkKSxyLnNldChkLGMpKSxsLmlzSW5zdGFuY2VkTWVzaCYmKCExPT09bC5oYXNFdmVudExpc3RlbmVyKCJkaXNwb3NlIixhKSYmbC5hZGRFdmVudExpc3RlbmVyKCJkaXNwb3NlIixhKSxlLnVwZGF0ZShsLmluc3RhbmNlTWF0cml4LDM0OTYyKSxudWxsIT09bC5pbnN0YW5jZUNvbG9yJiZlLnVwZGF0ZShsLmluc3RhbmNlQ29sb3IsMzQ5NjIpKSxkfSxkaXNwb3NlOmZ1bmN0aW9uKCl7cj1uZXcgV2Vha01hcH19fUpTLnByb3RvdHlwZS5pc0RhdGFUZXh0dXJlMkRBcnJheT0hMDt2YXIgZGs9Y2xhc3MgZXh0ZW5kcyBIb3tjb25zdHJ1Y3Rvcih0PW51bGwsZT0xLGk9MSxyPTEpe3N1cGVyKG51bGwpLHRoaXMuaW1hZ2U9e2RhdGE6dCx3aWR0aDplLGhlaWdodDppLGRlcHRoOnJ9LHRoaXMubWFnRmlsdGVyPVpvLHRoaXMubWluRmlsdGVyPVpvLHRoaXMud3JhcFI9RWwsdGhpcy5nZW5lcmF0ZU1pcG1hcHM9ITEsdGhpcy5mbGlwWT0hMSx0aGlzLnVucGFja0FsaWdubWVudD0xfX07ZGsucHJvdG90eXBlLmlzRGF0YVRleHR1cmUzRD0hMDt2YXIgTGRlPW5ldyBIbyxCZGU9bmV3IEpTLFZkZT1uZXcgZGssSGRlPW5ldyBtYixVdWU9W10senVlPVtdLGp1ZT1uZXcgRmxvYXQzMkFycmF5KDE2KSxHdWU9bmV3IEZsb2F0MzJBcnJheSg5KSxXdWU9bmV3IEZsb2F0MzJBcnJheSg0KTtmdW5jdGlvbiBFYihuLHQsZSl7bGV0IGk9blswXTtpZihpPD0wfHxpPjApcmV0dXJuIG47bGV0IHI9dCplLG89VXVlW3JdO2lmKHZvaWQgMD09PW8mJihvPW5ldyBGbG9hdDMyQXJyYXkociksVXVlW3JdPW8pLDAhPT10KXtpLnRvQXJyYXkobywwKTtmb3IobGV0IHM9MSxhPTA7cyE9PXQ7KytzKWErPWUsbltzXS50b0FycmF5KG8sYSl9cmV0dXJuIG99ZnVuY3Rpb24gX2Eobix0KXtpZihuLmxlbmd0aCE9PXQubGVuZ3RoKXJldHVybiExO2ZvcihsZXQgZT0wLGk9bi5sZW5ndGg7ZTxpO2UrKylpZihuW2VdIT09dFtlXSlyZXR1cm4hMTtyZXR1cm4hMH1mdW5jdGlvbiBZcyhuLHQpe2ZvcihsZXQgZT0wLGk9dC5sZW5ndGg7ZTxpO2UrKyluW2VdPXRbZV19ZnVuY3Rpb24ga2sobix0KXtsZXQgZT16dWVbdF07dm9pZCAwPT09ZSYmKGU9bmV3IEludDMyQXJyYXkodCksenVlW3RdPWUpO2ZvcihsZXQgaT0wO2khPT10OysraSllW2ldPW4uYWxsb2NhdGVUZXh0dXJlVW5pdCgpO3JldHVybiBlfWZ1bmN0aW9uIFlXZShuLHQpe2xldCBlPXRoaXMuY2FjaGU7ZVswXSE9PXQmJihuLnVuaWZvcm0xZih0aGlzLmFkZHIsdCksZVswXT10KX1mdW5jdGlvbiBYV2Uobix0KXtsZXQgZT10aGlzLmNhY2hlO2lmKHZvaWQgMCE9PXQueCkoZVswXSE9PXQueHx8ZVsxXSE9PXQueSkmJihuLnVuaWZvcm0yZih0aGlzLmFkZHIsdC54LHQueSksZVswXT10LngsZVsxXT10LnkpO2Vsc2V7aWYoX2EoZSx0KSlyZXR1cm47bi51bmlmb3JtMmZ2KHRoaXMuYWRkcix0KSxZcyhlLHQpfX1mdW5jdGlvbiBRV2Uobix0KXtsZXQgZT10aGlzLmNhY2hlO2lmKHZvaWQgMCE9PXQueCkoZVswXSE9PXQueHx8ZVsxXSE9PXQueXx8ZVsyXSE9PXQueikmJihuLnVuaWZvcm0zZih0aGlzLmFkZHIsdC54LHQueSx0LnopLGVbMF09dC54LGVbMV09dC55LGVbMl09dC56KTtlbHNlIGlmKHZvaWQgMCE9PXQucikoZVswXSE9PXQucnx8ZVsxXSE9PXQuZ3x8ZVsyXSE9PXQuYikmJihuLnVuaWZvcm0zZih0aGlzLmFkZHIsdC5yLHQuZyx0LmIpLGVbMF09dC5yLGVbMV09dC5nLGVbMl09dC5iKTtlbHNle2lmKF9hKGUsdCkpcmV0dXJuO24udW5pZm9ybTNmdih0aGlzLmFkZHIsdCksWXMoZSx0KX19ZnVuY3Rpb24gS1dlKG4sdCl7bGV0IGU9dGhpcy5jYWNoZTtpZih2b2lkIDAhPT10LngpKGVbMF0hPT10Lnh8fGVbMV0hPT10Lnl8fGVbMl0hPT10Lnp8fGVbM10hPT10LncpJiYobi51bmlmb3JtNGYodGhpcy5hZGRyLHQueCx0LnksdC56LHQudyksZVswXT10LngsZVsxXT10LnksZVsyXT10LnosZVszXT10LncpO2Vsc2V7aWYoX2EoZSx0KSlyZXR1cm47bi51bmlmb3JtNGZ2KHRoaXMuYWRkcix0KSxZcyhlLHQpfX1mdW5jdGlvbiBaV2Uobix0KXtsZXQgZT10aGlzLmNhY2hlLGk9dC5lbGVtZW50cztpZih2b2lkIDA9PT1pKXtpZihfYShlLHQpKXJldHVybjtuLnVuaWZvcm1NYXRyaXgyZnYodGhpcy5hZGRyLCExLHQpLFlzKGUsdCl9ZWxzZXtpZihfYShlLGkpKXJldHVybjtXdWUuc2V0KGkpLG4udW5pZm9ybU1hdHJpeDJmdih0aGlzLmFkZHIsITEsV3VlKSxZcyhlLGkpfX1mdW5jdGlvbiBKV2Uobix0KXtsZXQgZT10aGlzLmNhY2hlLGk9dC5lbGVtZW50cztpZih2b2lkIDA9PT1pKXtpZihfYShlLHQpKXJldHVybjtuLnVuaWZvcm1NYXRyaXgzZnYodGhpcy5hZGRyLCExLHQpLFlzKGUsdCl9ZWxzZXtpZihfYShlLGkpKXJldHVybjtHdWUuc2V0KGkpLG4udW5pZm9ybU1hdHJpeDNmdih0aGlzLmFkZHIsITEsR3VlKSxZcyhlLGkpfX1mdW5jdGlvbiAkV2Uobix0KXtsZXQgZT10aGlzLmNhY2hlLGk9dC5lbGVtZW50cztpZih2b2lkIDA9PT1pKXtpZihfYShlLHQpKXJldHVybjtuLnVuaWZvcm1NYXRyaXg0ZnYodGhpcy5hZGRyLCExLHQpLFlzKGUsdCl9ZWxzZXtpZihfYShlLGkpKXJldHVybjtqdWUuc2V0KGkpLG4udW5pZm9ybU1hdHJpeDRmdih0aGlzLmFkZHIsITEsanVlKSxZcyhlLGkpfX1mdW5jdGlvbiBlN2Uobix0KXtsZXQgZT10aGlzLmNhY2hlO2VbMF0hPT10JiYobi51bmlmb3JtMWkodGhpcy5hZGRyLHQpLGVbMF09dCl9ZnVuY3Rpb24gdDdlKG4sdCl7bGV0IGU9dGhpcy5jYWNoZTtfYShlLHQpfHwobi51bmlmb3JtMml2KHRoaXMuYWRkcix0KSxZcyhlLHQpKX1mdW5jdGlvbiBuN2Uobix0KXtsZXQgZT10aGlzLmNhY2hlO19hKGUsdCl8fChuLnVuaWZvcm0zaXYodGhpcy5hZGRyLHQpLFlzKGUsdCkpfWZ1bmN0aW9uIGk3ZShuLHQpe2xldCBlPXRoaXMuY2FjaGU7X2EoZSx0KXx8KG4udW5pZm9ybTRpdih0aGlzLmFkZHIsdCksWXMoZSx0KSl9ZnVuY3Rpb24gcjdlKG4sdCl7bGV0IGU9dGhpcy5jYWNoZTtlWzBdIT09dCYmKG4udW5pZm9ybTF1aSh0aGlzLmFkZHIsdCksZVswXT10KX1mdW5jdGlvbiBvN2Uobix0KXtsZXQgZT10aGlzLmNhY2hlO19hKGUsdCl8fChuLnVuaWZvcm0ydWl2KHRoaXMuYWRkcix0KSxZcyhlLHQpKX1mdW5jdGlvbiBzN2Uobix0KXtsZXQgZT10aGlzLmNhY2hlO19hKGUsdCl8fChuLnVuaWZvcm0zdWl2KHRoaXMuYWRkcix0KSxZcyhlLHQpKX1mdW5jdGlvbiBhN2Uobix0KXtsZXQgZT10aGlzLmNhY2hlO19hKGUsdCl8fChuLnVuaWZvcm00dWl2KHRoaXMuYWRkcix0KSxZcyhlLHQpKX1mdW5jdGlvbiBsN2Uobix0LGUpe2xldCBpPXRoaXMuY2FjaGUscj1lLmFsbG9jYXRlVGV4dHVyZVVuaXQoKTtpWzBdIT09ciYmKG4udW5pZm9ybTFpKHRoaXMuYWRkcixyKSxpWzBdPXIpLGUuc2FmZVNldFRleHR1cmUyRCh0fHxMZGUscil9ZnVuY3Rpb24gYzdlKG4sdCxlKXtsZXQgaT10aGlzLmNhY2hlLHI9ZS5hbGxvY2F0ZVRleHR1cmVVbml0KCk7aVswXSE9PXImJihuLnVuaWZvcm0xaSh0aGlzLmFkZHIsciksaVswXT1yKSxlLnNldFRleHR1cmUzRCh0fHxWZGUscil9ZnVuY3Rpb24gdTdlKG4sdCxlKXtsZXQgaT10aGlzLmNhY2hlLHI9ZS5hbGxvY2F0ZVRleHR1cmVVbml0KCk7aVswXSE9PXImJihuLnVuaWZvcm0xaSh0aGlzLmFkZHIsciksaVswXT1yKSxlLnNhZmVTZXRUZXh0dXJlQ3ViZSh0fHxIZGUscil9ZnVuY3Rpb24gZDdlKG4sdCxlKXtsZXQgaT10aGlzLmNhY2hlLHI9ZS5hbGxvY2F0ZVRleHR1cmVVbml0KCk7aVswXSE9PXImJihuLnVuaWZvcm0xaSh0aGlzLmFkZHIsciksaVswXT1yKSxlLnNldFRleHR1cmUyREFycmF5KHR8fEJkZSxyKX1mdW5jdGlvbiBoN2Uobix0KXtuLnVuaWZvcm0xZnYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIGY3ZShuLHQpe2xldCBlPUViKHQsdGhpcy5zaXplLDIpO24udW5pZm9ybTJmdih0aGlzLmFkZHIsZSl9ZnVuY3Rpb24gbTdlKG4sdCl7bGV0IGU9RWIodCx0aGlzLnNpemUsMyk7bi51bmlmb3JtM2Z2KHRoaXMuYWRkcixlKX1mdW5jdGlvbiBnN2Uobix0KXtsZXQgZT1FYih0LHRoaXMuc2l6ZSw0KTtuLnVuaWZvcm00ZnYodGhpcy5hZGRyLGUpfWZ1bmN0aW9uIF83ZShuLHQpe2xldCBlPUViKHQsdGhpcy5zaXplLDQpO24udW5pZm9ybU1hdHJpeDJmdih0aGlzLmFkZHIsITEsZSl9ZnVuY3Rpb24gdjdlKG4sdCl7bGV0IGU9RWIodCx0aGlzLnNpemUsOSk7bi51bmlmb3JtTWF0cml4M2Z2KHRoaXMuYWRkciwhMSxlKX1mdW5jdGlvbiB5N2Uobix0KXtsZXQgZT1FYih0LHRoaXMuc2l6ZSwxNik7bi51bmlmb3JtTWF0cml4NGZ2KHRoaXMuYWRkciwhMSxlKX1mdW5jdGlvbiBiN2Uobix0KXtuLnVuaWZvcm0xaXYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIHg3ZShuLHQpe24udW5pZm9ybTJpdih0aGlzLmFkZHIsdCl9ZnVuY3Rpb24gQzdlKG4sdCl7bi51bmlmb3JtM2l2KHRoaXMuYWRkcix0KX1mdW5jdGlvbiBNN2Uobix0KXtuLnVuaWZvcm00aXYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIHc3ZShuLHQpe24udW5pZm9ybTF1aXYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIFM3ZShuLHQpe24udW5pZm9ybTJ1aXYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIEU3ZShuLHQpe24udW5pZm9ybTN1aXYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIFQ3ZShuLHQpe24udW5pZm9ybTR1aXYodGhpcy5hZGRyLHQpfWZ1bmN0aW9uIEQ3ZShuLHQsZSl7bGV0IGk9dC5sZW5ndGgscj1rayhlLGkpO24udW5pZm9ybTFpdih0aGlzLmFkZHIscik7Zm9yKGxldCBvPTA7byE9PWk7KytvKWUuc2FmZVNldFRleHR1cmUyRCh0W29dfHxMZGUscltvXSl9ZnVuY3Rpb24gQTdlKG4sdCxlKXtsZXQgaT10Lmxlbmd0aCxyPWtrKGUsaSk7bi51bmlmb3JtMWl2KHRoaXMuYWRkcixyKTtmb3IobGV0IG89MDtvIT09aTsrK28pZS5zZXRUZXh0dXJlM0QodFtvXXx8VmRlLHJbb10pfWZ1bmN0aW9uIEk3ZShuLHQsZSl7bGV0IGk9dC5sZW5ndGgscj1rayhlLGkpO24udW5pZm9ybTFpdih0aGlzLmFkZHIscik7Zm9yKGxldCBvPTA7byE9PWk7KytvKWUuc2FmZVNldFRleHR1cmVDdWJlKHRbb118fEhkZSxyW29dKX1mdW5jdGlvbiBQN2Uobix0LGUpe2xldCBpPXQubGVuZ3RoLHI9a2soZSxpKTtuLnVuaWZvcm0xaXYodGhpcy5hZGRyLHIpO2ZvcihsZXQgbz0wO28hPT1pOysrbyllLnNldFRleHR1cmUyREFycmF5KHRbb118fEJkZSxyW29dKX1mdW5jdGlvbiBPN2Uobix0LGUpe3RoaXMuaWQ9bix0aGlzLmFkZHI9ZSx0aGlzLmNhY2hlPVtdLHRoaXMuc2V0VmFsdWU9ZnVuY3Rpb24obil7c3dpdGNoKG4pe2Nhc2UgNTEyNjpyZXR1cm4gWVdlO2Nhc2UgMzU2NjQ6cmV0dXJuIFhXZTtjYXNlIDM1NjY1OnJldHVybiBRV2U7Y2FzZSAzNTY2NjpyZXR1cm4gS1dlO2Nhc2UgMzU2NzQ6cmV0dXJuIFpXZTtjYXNlIDM1Njc1OnJldHVybiBKV2U7Y2FzZSAzNTY3NjpyZXR1cm4gJFdlO2Nhc2UgNTEyNDpjYXNlIDM1NjcwOnJldHVybiBlN2U7Y2FzZSAzNTY2NzpjYXNlIDM1NjcxOnJldHVybiB0N2U7Y2FzZSAzNTY2ODpjYXNlIDM1NjcyOnJldHVybiBuN2U7Y2FzZSAzNTY2OTpjYXNlIDM1NjczOnJldHVybiBpN2U7Y2FzZSA1MTI1OnJldHVybiByN2U7Y2FzZSAzNjI5NDpyZXR1cm4gbzdlO2Nhc2UgMzYyOTU6cmV0dXJuIHM3ZTtjYXNlIDM2Mjk2OnJldHVybiBhN2U7Y2FzZSAzNTY3ODpjYXNlIDM2MTk4OmNhc2UgMzYyOTg6Y2FzZSAzNjMwNjpjYXNlIDM1NjgyOnJldHVybiBsN2U7Y2FzZSAzNTY3OTpjYXNlIDM2Mjk5OmNhc2UgMzYzMDc6cmV0dXJuIGM3ZTtjYXNlIDM1NjgwOmNhc2UgMzYzMDA6Y2FzZSAzNjMwODpjYXNlIDM2MjkzOnJldHVybiB1N2U7Y2FzZSAzNjI4OTpjYXNlIDM2MzAzOmNhc2UgMzYzMTE6Y2FzZSAzNjI5MjpyZXR1cm4gZDdlfX0odC50eXBlKX1mdW5jdGlvbiBVZGUobix0LGUpe3RoaXMuaWQ9bix0aGlzLmFkZHI9ZSx0aGlzLmNhY2hlPVtdLHRoaXMuc2l6ZT10LnNpemUsdGhpcy5zZXRWYWx1ZT1mdW5jdGlvbihuKXtzd2l0Y2gobil7Y2FzZSA1MTI2OnJldHVybiBoN2U7Y2FzZSAzNTY2NDpyZXR1cm4gZjdlO2Nhc2UgMzU2NjU6cmV0dXJuIG03ZTtjYXNlIDM1NjY2OnJldHVybiBnN2U7Y2FzZSAzNTY3NDpyZXR1cm4gXzdlO2Nhc2UgMzU2NzU6cmV0dXJuIHY3ZTtjYXNlIDM1Njc2OnJldHVybiB5N2U7Y2FzZSA1MTI0OmNhc2UgMzU2NzA6cmV0dXJuIGI3ZTtjYXNlIDM1NjY3OmNhc2UgMzU2NzE6cmV0dXJuIHg3ZTtjYXNlIDM1NjY4OmNhc2UgMzU2NzI6cmV0dXJuIEM3ZTtjYXNlIDM1NjY5OmNhc2UgMzU2NzM6cmV0dXJuIE03ZTtjYXNlIDUxMjU6cmV0dXJuIHc3ZTtjYXNlIDM2Mjk0OnJldHVybiBTN2U7Y2FzZSAzNjI5NTpyZXR1cm4gRTdlO2Nhc2UgMzYyOTY6cmV0dXJuIFQ3ZTtjYXNlIDM1Njc4OmNhc2UgMzYxOTg6Y2FzZSAzNjI5ODpjYXNlIDM2MzA2OmNhc2UgMzU2ODI6cmV0dXJuIEQ3ZTtjYXNlIDM1Njc5OmNhc2UgMzYyOTk6Y2FzZSAzNjMwNzpyZXR1cm4gQTdlO2Nhc2UgMzU2ODA6Y2FzZSAzNjMwMDpjYXNlIDM2MzA4OmNhc2UgMzYyOTM6cmV0dXJuIEk3ZTtjYXNlIDM2Mjg5OmNhc2UgMzYzMDM6Y2FzZSAzNjMxMTpjYXNlIDM2MjkyOnJldHVybiBQN2V9fSh0LnR5cGUpfWZ1bmN0aW9uIHpkZShuKXt0aGlzLmlkPW4sdGhpcy5zZXE9W10sdGhpcy5tYXA9e319VWRlLnByb3RvdHlwZS51cGRhdGVDYWNoZT1mdW5jdGlvbihuKXtsZXQgdD10aGlzLmNhY2hlO24gaW5zdGFuY2VvZiBGbG9hdDMyQXJyYXkmJnQubGVuZ3RoIT09bi5sZW5ndGgmJih0aGlzLmNhY2hlPW5ldyBGbG9hdDMyQXJyYXkobi5sZW5ndGgpKSxZcyh0LG4pfSx6ZGUucHJvdG90eXBlLnNldFZhbHVlPWZ1bmN0aW9uKG4sdCxlKXtsZXQgaT10aGlzLnNlcTtmb3IobGV0IHI9MCxvPWkubGVuZ3RoO3IhPT1vOysrcil7bGV0IHM9aVtyXTtzLnNldFZhbHVlKG4sdFtzLmlkXSxlKX19O3ZhciAkaj0vKFx3KykoXF0pPyhcW3xcLik/L2c7ZnVuY3Rpb24gcXVlKG4sdCl7bi5zZXEucHVzaCh0KSxuLm1hcFt0LmlkXT10fWZ1bmN0aW9uIGs3ZShuLHQsZSl7bGV0IGk9bi5uYW1lLHI9aS5sZW5ndGg7Zm9yKCRqLmxhc3RJbmRleD0wOzspe2xldCBvPSRqLmV4ZWMoaSkscz0kai5sYXN0SW5kZXgsYT1vWzFdLGM9b1szXTtpZigiXSI9PT1vWzJdJiYoYXw9MCksdm9pZCAwPT09Y3x8IlsiPT09YyYmcysyPT09cil7cXVlKGUsdm9pZCAwPT09Yz9uZXcgTzdlKGEsbix0KTpuZXcgVWRlKGEsbix0KSk7YnJlYWt9e2xldCBkPWUubWFwW2FdO3ZvaWQgMD09PWQmJihkPW5ldyB6ZGUoYSkscXVlKGUsZCkpLGU9ZH19fWZ1bmN0aW9uIHlmKG4sdCl7dGhpcy5zZXE9W10sdGhpcy5tYXA9e307bGV0IGU9bi5nZXRQcm9ncmFtUGFyYW1ldGVyKHQsMzU3MTgpO2ZvcihsZXQgaT0wO2k8ZTsrK2kpe2xldCByPW4uZ2V0QWN0aXZlVW5pZm9ybSh0LGkpO2s3ZShyLG4uZ2V0VW5pZm9ybUxvY2F0aW9uKHQsci5uYW1lKSx0aGlzKX19ZnVuY3Rpb24gWXVlKG4sdCxlKXtsZXQgaT1uLmNyZWF0ZVNoYWRlcih0KTtyZXR1cm4gbi5zaGFkZXJTb3VyY2UoaSxlKSxuLmNvbXBpbGVTaGFkZXIoaSksaX15Zi5wcm90b3R5cGUuc2V0VmFsdWU9ZnVuY3Rpb24obix0LGUsaSl7bGV0IHI9dGhpcy5tYXBbdF07dm9pZCAwIT09ciYmci5zZXRWYWx1ZShuLGUsaSl9LHlmLnByb3RvdHlwZS5zZXRPcHRpb25hbD1mdW5jdGlvbihuLHQsZSl7bGV0IGk9dFtlXTt2b2lkIDAhPT1pJiZ0aGlzLnNldFZhbHVlKG4sZSxpKX0seWYudXBsb2FkPWZ1bmN0aW9uKG4sdCxlLGkpe2ZvcihsZXQgcj0wLG89dC5sZW5ndGg7ciE9PW87KytyKXtsZXQgcz10W3JdLGE9ZVtzLmlkXTshMSE9PWEubmVlZHNVcGRhdGUmJnMuc2V0VmFsdWUobixhLnZhbHVlLGkpfX0seWYuc2VxV2l0aFZhbHVlPWZ1bmN0aW9uKG4sdCl7bGV0IGU9W107Zm9yKGxldCBpPTAscj1uLmxlbmd0aDtpIT09cjsrK2kpe2xldCBvPW5baV07by5pZCBpbiB0JiZlLnB1c2gobyl9cmV0dXJuIGV9O3ZhciBGN2U9MDtmdW5jdGlvbiBYdWUobix0LGUpe2xldCBpPW4uZ2V0U2hhZGVyUGFyYW1ldGVyKHQsMzU3MTMpLHI9bi5nZXRTaGFkZXJJbmZvTG9nKHQpLnRyaW0oKTtyZXR1cm4gaSYmIiI9PT1yPyIiOmUudG9VcHBlckNhc2UoKSsiXG5cbiIrcisiXG5cbiIrZnVuY3Rpb24obil7bGV0IHQ9bi5zcGxpdCgiXG4iKTtmb3IobGV0IGU9MDtlPHQubGVuZ3RoO2UrKyl0W2VdPWUrMSsiOiAiK3RbZV07cmV0dXJuIHQuam9pbigiXG4iKX0obi5nZXRTaGFkZXJTb3VyY2UodCkpfWZ1bmN0aW9uIEI3ZShuLHQpe2xldCBlPWZ1bmN0aW9uKG4pe3N3aXRjaChuKXtjYXNlIGJmOnJldHVyblsiTGluZWFyIiwiKCB2YWx1ZSApIl07Y2FzZSBXcjpyZXR1cm5bInNSR0IiLCIoIHZhbHVlICkiXTtkZWZhdWx0OnJldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUHJvZ3JhbTogVW5zdXBwb3J0ZWQgZW5jb2Rpbmc6IixuKSxbIkxpbmVhciIsIiggdmFsdWUgKSJdfX0odCk7cmV0dXJuInZlYzQgIituKyIoIHZlYzQgdmFsdWUgKSB7IHJldHVybiBMaW5lYXJUbyIrZVswXStlWzFdKyI7IH0ifWZ1bmN0aW9uIFY3ZShuLHQpe2xldCBlO3N3aXRjaCh0KXtjYXNlIDE6ZT0iTGluZWFyIjticmVhaztjYXNlIDI6ZT0iUmVpbmhhcmQiO2JyZWFrO2Nhc2UgMzplPSJPcHRpbWl6ZWRDaW5lb24iO2JyZWFrO2Nhc2UgNDplPSJBQ0VTRmlsbWljIjticmVhaztjYXNlIDU6ZT0iQ3VzdG9tIjticmVhaztkZWZhdWx0OmNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xQcm9ncmFtOiBVbnN1cHBvcnRlZCB0b25lTWFwcGluZzoiLHQpLGU9IkxpbmVhciJ9cmV0dXJuInZlYzMgIituKyIoIHZlYzMgY29sb3IgKSB7IHJldHVybiAiK2UrIlRvbmVNYXBwaW5nKCBjb2xvciApOyB9In1mdW5jdGlvbiBWUyhuKXtyZXR1cm4iIiE9PW59ZnVuY3Rpb24gUXVlKG4sdCl7cmV0dXJuIG4ucmVwbGFjZSgvTlVNX0RJUl9MSUdIVFMvZyx0Lm51bURpckxpZ2h0cykucmVwbGFjZSgvTlVNX1NQT1RfTElHSFRTL2csdC5udW1TcG90TGlnaHRzKS5yZXBsYWNlKC9OVU1fUkVDVF9BUkVBX0xJR0hUUy9nLHQubnVtUmVjdEFyZWFMaWdodHMpLnJlcGxhY2UoL05VTV9QT0lOVF9MSUdIVFMvZyx0Lm51bVBvaW50TGlnaHRzKS5yZXBsYWNlKC9OVU1fSEVNSV9MSUdIVFMvZyx0Lm51bUhlbWlMaWdodHMpLnJlcGxhY2UoL05VTV9ESVJfTElHSFRfU0hBRE9XUy9nLHQubnVtRGlyTGlnaHRTaGFkb3dzKS5yZXBsYWNlKC9OVU1fU1BPVF9MSUdIVF9TSEFET1dTL2csdC5udW1TcG90TGlnaHRTaGFkb3dzKS5yZXBsYWNlKC9OVU1fUE9JTlRfTElHSFRfU0hBRE9XUy9nLHQubnVtUG9pbnRMaWdodFNoYWRvd3MpfWZ1bmN0aW9uIEt1ZShuLHQpe3JldHVybiBuLnJlcGxhY2UoL05VTV9DTElQUElOR19QTEFORVMvZyx0Lm51bUNsaXBwaW5nUGxhbmVzKS5yZXBsYWNlKC9VTklPTl9DTElQUElOR19QTEFORVMvZyx0Lm51bUNsaXBwaW5nUGxhbmVzLXQubnVtQ2xpcEludGVyc2VjdGlvbil9dmFyIGo3ZT0vXlsgXHRdKiNpbmNsdWRlICs8KFtcd1xkLi9dKyk+L2dtO2Z1bmN0aW9uIGc4KG4pe3JldHVybiBuLnJlcGxhY2UoajdlLEc3ZSl9ZnVuY3Rpb24gRzdlKG4sdCl7bGV0IGU9RGlbdF07aWYodm9pZCAwPT09ZSl0aHJvdyBuZXcgRXJyb3IoIkNhbiBub3QgcmVzb2x2ZSAjaW5jbHVkZSA8Iit0KyI+Iik7cmV0dXJuIGc4KGUpfXZhciBXN2U9LyNwcmFnbWEgdW5yb2xsX2xvb3BbXHNdKz9mb3IgXCggaW50IGkgXD0gKFxkKylcOyBpIDwgKFxkKylcOyBpIFwrXCsgXCkgXHsoW1xzXFNdKz8pKD89XH0pXH0vZyxxN2U9LyNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnRccytmb3JccypcKFxzKmludFxzK2lccyo9XHMqKFxkKylccyo7XHMqaVxzKjxccyooXGQrKVxzKjtccyppXHMqXCtcK1xzKlwpXHMqeyhbXHNcU10rPyl9XHMrI3ByYWdtYSB1bnJvbGxfbG9vcF9lbmQvZztmdW5jdGlvbiBadWUobil7cmV0dXJuIG4ucmVwbGFjZShxN2UsamRlKS5yZXBsYWNlKFc3ZSxZN2UpfWZ1bmN0aW9uIFk3ZShuLHQsZSxpKXtyZXR1cm4gY29uc29sZS53YXJuKCJXZWJHTFByb2dyYW06ICNwcmFnbWEgdW5yb2xsX2xvb3Agc2hhZGVyIHN5bnRheCBpcyBkZXByZWNhdGVkLiBQbGVhc2UgdXNlICNwcmFnbWEgdW5yb2xsX2xvb3Bfc3RhcnQgc3ludGF4IGluc3RlYWQuIiksamRlKDAsdCxlLGkpfWZ1bmN0aW9uIGpkZShuLHQsZSxpKXtsZXQgcj0iIjtmb3IobGV0IG89cGFyc2VJbnQodCk7bzxwYXJzZUludChlKTtvKyspcis9aS5yZXBsYWNlKC9cW1xzKmlccypcXS9nLCJbICIrbysiIF0iKS5yZXBsYWNlKC9VTlJPTExFRF9MT09QX0lOREVYL2csbyk7cmV0dXJuIHJ9ZnVuY3Rpb24gSnVlKG4pe2xldCB0PSJwcmVjaXNpb24gIituLnByZWNpc2lvbisiIGZsb2F0O1xucHJlY2lzaW9uICIrbi5wcmVjaXNpb24rIiBpbnQ7IjtyZXR1cm4iaGlnaHAiPT09bi5wcmVjaXNpb24/dCs9IlxuI2RlZmluZSBISUdIX1BSRUNJU0lPTiI6Im1lZGl1bXAiPT09bi5wcmVjaXNpb24/dCs9IlxuI2RlZmluZSBNRURJVU1fUFJFQ0lTSU9OIjoibG93cCI9PT1uLnByZWNpc2lvbiYmKHQrPSJcbiNkZWZpbmUgTE9XX1BSRUNJU0lPTiIpLHR9ZnVuY3Rpb24gSjdlKG4sdCxlLGkpe2xldCBtLHgscj1uLmdldENvbnRleHQoKSxvPWUuZGVmaW5lcyxzPWUudmVydGV4U2hhZGVyLGE9ZS5mcmFnbWVudFNoYWRlcixsPWZ1bmN0aW9uKG4pe2xldCB0PSJTSEFET1dNQVBfVFlQRV9CQVNJQyI7cmV0dXJuIDE9PT1uLnNoYWRvd01hcFR5cGU/dD0iU0hBRE9XTUFQX1RZUEVfUENGIjoyPT09bi5zaGFkb3dNYXBUeXBlP3Q9IlNIQURPV01BUF9UWVBFX1BDRl9TT0ZUIjozPT09bi5zaGFkb3dNYXBUeXBlJiYodD0iU0hBRE9XTUFQX1RZUEVfVlNNIiksdH0oZSksYz1mdW5jdGlvbihuKXtsZXQgdD0iRU5WTUFQX1RZUEVfQ1VCRSI7aWYobi5lbnZNYXApc3dpdGNoKG4uZW52TWFwTW9kZSl7Y2FzZSAzMDE6Y2FzZSAzMDI6dD0iRU5WTUFQX1RZUEVfQ1VCRSI7YnJlYWs7Y2FzZSAzMDY6Y2FzZSAzMDc6dD0iRU5WTUFQX1RZUEVfQ1VCRV9VViJ9cmV0dXJuIHR9KGUpLHU9ZnVuY3Rpb24obil7bGV0IHQ9IkVOVk1BUF9NT0RFX1JFRkxFQ1RJT04iO2lmKG4uZW52TWFwKXN3aXRjaChuLmVudk1hcE1vZGUpe2Nhc2UgMzAyOmNhc2UgMzA3OnQ9IkVOVk1BUF9NT0RFX1JFRlJBQ1RJT04ifXJldHVybiB0fShlKSxkPWZ1bmN0aW9uKG4pe2xldCB0PSJFTlZNQVBfQkxFTkRJTkdfTk9ORSI7aWYobi5lbnZNYXApc3dpdGNoKG4uY29tYmluZSl7Y2FzZSAwOnQ9IkVOVk1BUF9CTEVORElOR19NVUxUSVBMWSI7YnJlYWs7Y2FzZSAxOnQ9IkVOVk1BUF9CTEVORElOR19NSVgiO2JyZWFrO2Nhc2UgMjp0PSJFTlZNQVBfQkxFTkRJTkdfQUREIn1yZXR1cm4gdH0oZSkscD1lLmlzV2ViR0wyPyIiOmZ1bmN0aW9uKG4pe3JldHVybltuLmV4dGVuc2lvbkRlcml2YXRpdmVzfHxuLmVudk1hcEN1YmVVVnx8bi5idW1wTWFwfHxuLnRhbmdlbnRTcGFjZU5vcm1hbE1hcHx8bi5jbGVhcmNvYXROb3JtYWxNYXB8fG4uZmxhdFNoYWRpbmd8fCJwaHlzaWNhbCI9PT1uLnNoYWRlcklEPyIjZXh0ZW5zaW9uIEdMX09FU19zdGFuZGFyZF9kZXJpdmF0aXZlcyA6IGVuYWJsZSI6IiIsKG4uZXh0ZW5zaW9uRnJhZ0RlcHRofHxuLmxvZ2FyaXRobWljRGVwdGhCdWZmZXIpJiZuLnJlbmRlcmVyRXh0ZW5zaW9uRnJhZ0RlcHRoPyIjZXh0ZW5zaW9uIEdMX0VYVF9mcmFnX2RlcHRoIDogZW5hYmxlIjoiIixuLmV4dGVuc2lvbkRyYXdCdWZmZXJzJiZuLnJlbmRlcmVyRXh0ZW5zaW9uRHJhd0J1ZmZlcnM/IiNleHRlbnNpb24gR0xfRVhUX2RyYXdfYnVmZmVycyA6IHJlcXVpcmUiOiIiLChuLmV4dGVuc2lvblNoYWRlclRleHR1cmVMT0R8fG4uZW52TWFwfHxuLnRyYW5zbWlzc2lvbikmJm4ucmVuZGVyZXJFeHRlbnNpb25TaGFkZXJUZXh0dXJlTG9kPyIjZXh0ZW5zaW9uIEdMX0VYVF9zaGFkZXJfdGV4dHVyZV9sb2QgOiBlbmFibGUiOiIiXS5maWx0ZXIoVlMpLmpvaW4oIlxuIil9KGUpLGg9ZnVuY3Rpb24obil7bGV0IHQ9W107Zm9yKGxldCBlIGluIG4pe2xldCBpPW5bZV07ITEhPT1pJiZ0LnB1c2goIiNkZWZpbmUgIitlKyIgIitpKX1yZXR1cm4gdC5qb2luKCJcbiIpfShvKSxmPXIuY3JlYXRlUHJvZ3JhbSgpLGc9ZS5nbHNsVmVyc2lvbj8iI3ZlcnNpb24gIitlLmdsc2xWZXJzaW9uKyJcbiI6IiI7ZS5pc1Jhd1NoYWRlck1hdGVyaWFsPyhtPVtoXS5maWx0ZXIoVlMpLmpvaW4oIlxuIiksbS5sZW5ndGg+MCYmKG0rPSJcbiIpLHg9W3AsaF0uZmlsdGVyKFZTKS5qb2luKCJcbiIpLHgubGVuZ3RoPjAmJih4Kz0iXG4iKSk6KG09W0p1ZShlKSwiI2RlZmluZSBTSEFERVJfTkFNRSAiK2Uuc2hhZGVyTmFtZSxoLGUuaW5zdGFuY2luZz8iI2RlZmluZSBVU0VfSU5TVEFOQ0lORyI6IiIsZS5pbnN0YW5jaW5nQ29sb3I/IiNkZWZpbmUgVVNFX0lOU1RBTkNJTkdfQ09MT1IiOiIiLGUuc3VwcG9ydHNWZXJ0ZXhUZXh0dXJlcz8iI2RlZmluZSBWRVJURVhfVEVYVFVSRVMiOiIiLCIjZGVmaW5lIE1BWF9CT05FUyAiK2UubWF4Qm9uZXMsZS51c2VGb2cmJmUuZm9nPyIjZGVmaW5lIFVTRV9GT0ciOiIiLGUudXNlRm9nJiZlLmZvZ0V4cDI/IiNkZWZpbmUgRk9HX0VYUDIiOiIiLGUubWFwPyIjZGVmaW5lIFVTRV9NQVAiOiIiLGUuZW52TWFwPyIjZGVmaW5lIFVTRV9FTlZNQVAiOiIiLGUuZW52TWFwPyIjZGVmaW5lICIrdToiIixlLmxpZ2h0TWFwPyIjZGVmaW5lIFVTRV9MSUdIVE1BUCI6IiIsZS5hb01hcD8iI2RlZmluZSBVU0VfQU9NQVAiOiIiLGUuZW1pc3NpdmVNYXA/IiNkZWZpbmUgVVNFX0VNSVNTSVZFTUFQIjoiIixlLmJ1bXBNYXA/IiNkZWZpbmUgVVNFX0JVTVBNQVAiOiIiLGUubm9ybWFsTWFwPyIjZGVmaW5lIFVTRV9OT1JNQUxNQVAiOiIiLGUubm9ybWFsTWFwJiZlLm9iamVjdFNwYWNlTm9ybWFsTWFwPyIjZGVmaW5lIE9CSkVDVFNQQUNFX05PUk1BTE1BUCI6IiIsZS5ub3JtYWxNYXAmJmUudGFuZ2VudFNwYWNlTm9ybWFsTWFwPyIjZGVmaW5lIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAiOiIiLGUuY2xlYXJjb2F0TWFwPyIjZGVmaW5lIFVTRV9DTEVBUkNPQVRNQVAiOiIiLGUuY2xlYXJjb2F0Um91Z2huZXNzTWFwPyIjZGVmaW5lIFVTRV9DTEVBUkNPQVRfUk9VR0hORVNTTUFQIjoiIixlLmNsZWFyY29hdE5vcm1hbE1hcD8iI2RlZmluZSBVU0VfQ0xFQVJDT0FUX05PUk1BTE1BUCI6IiIsZS5kaXNwbGFjZW1lbnRNYXAmJmUuc3VwcG9ydHNWZXJ0ZXhUZXh0dXJlcz8iI2RlZmluZSBVU0VfRElTUExBQ0VNRU5UTUFQIjoiIixlLnNwZWN1bGFyTWFwPyIjZGVmaW5lIFVTRV9TUEVDVUxBUk1BUCI6IiIsZS5zcGVjdWxhckludGVuc2l0eU1hcD8iI2RlZmluZSBVU0VfU1BFQ1VMQVJJTlRFTlNJVFlNQVAiOiIiLGUuc3BlY3VsYXJDb2xvck1hcD8iI2RlZmluZSBVU0VfU1BFQ1VMQVJDT0xPUk1BUCI6IiIsZS5yb3VnaG5lc3NNYXA/IiNkZWZpbmUgVVNFX1JPVUdITkVTU01BUCI6IiIsZS5tZXRhbG5lc3NNYXA/IiNkZWZpbmUgVVNFX01FVEFMTkVTU01BUCI6IiIsZS5hbHBoYU1hcD8iI2RlZmluZSBVU0VfQUxQSEFNQVAiOiIiLGUudHJhbnNtaXNzaW9uPyIjZGVmaW5lIFVTRV9UUkFOU01JU1NJT04iOiIiLGUudHJhbnNtaXNzaW9uTWFwPyIjZGVmaW5lIFVTRV9UUkFOU01JU1NJT05NQVAiOiIiLGUudGhpY2tuZXNzTWFwPyIjZGVmaW5lIFVTRV9USElDS05FU1NNQVAiOiIiLGUuc2hlZW5Db2xvck1hcD8iI2RlZmluZSBVU0VfU0hFRU5DT0xPUk1BUCI6IiIsZS5zaGVlblJvdWdobmVzc01hcD8iI2RlZmluZSBVU0VfU0hFRU5ST1VHSE5FU1NNQVAiOiIiLGUudmVydGV4VGFuZ2VudHM/IiNkZWZpbmUgVVNFX1RBTkdFTlQiOiIiLGUudmVydGV4Q29sb3JzPyIjZGVmaW5lIFVTRV9DT0xPUiI6IiIsZS52ZXJ0ZXhBbHBoYXM/IiNkZWZpbmUgVVNFX0NPTE9SX0FMUEhBIjoiIixlLnZlcnRleFV2cz8iI2RlZmluZSBVU0VfVVYiOiIiLGUudXZzVmVydGV4T25seT8iI2RlZmluZSBVVlNfVkVSVEVYX09OTFkiOiIiLGUuZmxhdFNoYWRpbmc/IiNkZWZpbmUgRkxBVF9TSEFERUQiOiIiLGUuc2tpbm5pbmc/IiNkZWZpbmUgVVNFX1NLSU5OSU5HIjoiIixlLnVzZVZlcnRleFRleHR1cmU/IiNkZWZpbmUgQk9ORV9URVhUVVJFIjoiIixlLm1vcnBoVGFyZ2V0cz8iI2RlZmluZSBVU0VfTU9SUEhUQVJHRVRTIjoiIixlLm1vcnBoTm9ybWFscyYmITE9PT1lLmZsYXRTaGFkaW5nPyIjZGVmaW5lIFVTRV9NT1JQSE5PUk1BTFMiOiIiLGUubW9ycGhUYXJnZXRzJiZlLmlzV2ViR0wyPyIjZGVmaW5lIE1PUlBIVEFSR0VUU19URVhUVVJFIjoiIixlLm1vcnBoVGFyZ2V0cyYmZS5pc1dlYkdMMj8iI2RlZmluZSBNT1JQSFRBUkdFVFNfQ09VTlQgIitlLm1vcnBoVGFyZ2V0c0NvdW50OiIiLGUuZG91YmxlU2lkZWQ/IiNkZWZpbmUgRE9VQkxFX1NJREVEIjoiIixlLmZsaXBTaWRlZD8iI2RlZmluZSBGTElQX1NJREVEIjoiIixlLnNoYWRvd01hcEVuYWJsZWQ/IiNkZWZpbmUgVVNFX1NIQURPV01BUCI6IiIsZS5zaGFkb3dNYXBFbmFibGVkPyIjZGVmaW5lICIrbDoiIixlLnNpemVBdHRlbnVhdGlvbj8iI2RlZmluZSBVU0VfU0laRUFUVEVOVUFUSU9OIjoiIixlLmxvZ2FyaXRobWljRGVwdGhCdWZmZXI/IiNkZWZpbmUgVVNFX0xPR0RFUFRIQlVGIjoiIixlLmxvZ2FyaXRobWljRGVwdGhCdWZmZXImJmUucmVuZGVyZXJFeHRlbnNpb25GcmFnRGVwdGg/IiNkZWZpbmUgVVNFX0xPR0RFUFRIQlVGX0VYVCI6IiIsInVuaWZvcm0gbWF0NCBtb2RlbE1hdHJpeDsiLCJ1bmlmb3JtIG1hdDQgbW9kZWxWaWV3TWF0cml4OyIsInVuaWZvcm0gbWF0NCBwcm9qZWN0aW9uTWF0cml4OyIsInVuaWZvcm0gbWF0NCB2aWV3TWF0cml4OyIsInVuaWZvcm0gbWF0MyBub3JtYWxNYXRyaXg7IiwidW5pZm9ybSB2ZWMzIGNhbWVyYVBvc2l0aW9uOyIsInVuaWZvcm0gYm9vbCBpc09ydGhvZ3JhcGhpYzsiLCIjaWZkZWYgVVNFX0lOU1RBTkNJTkciLCJcdGF0dHJpYnV0ZSBtYXQ0IGluc3RhbmNlTWF0cml4OyIsIiNlbmRpZiIsIiNpZmRlZiBVU0VfSU5TVEFOQ0lOR19DT0xPUiIsIlx0YXR0cmlidXRlIHZlYzMgaW5zdGFuY2VDb2xvcjsiLCIjZW5kaWYiLCJhdHRyaWJ1dGUgdmVjMyBwb3NpdGlvbjsiLCJhdHRyaWJ1dGUgdmVjMyBub3JtYWw7IiwiYXR0cmlidXRlIHZlYzIgdXY7IiwiI2lmZGVmIFVTRV9UQU5HRU5UIiwiXHRhdHRyaWJ1dGUgdmVjNCB0YW5nZW50OyIsIiNlbmRpZiIsIiNpZiBkZWZpbmVkKCBVU0VfQ09MT1JfQUxQSEEgKSIsIlx0YXR0cmlidXRlIHZlYzQgY29sb3I7IiwiI2VsaWYgZGVmaW5lZCggVVNFX0NPTE9SICkiLCJcdGF0dHJpYnV0ZSB2ZWMzIGNvbG9yOyIsIiNlbmRpZiIsIiNpZiAoIGRlZmluZWQoIFVTRV9NT1JQSFRBUkdFVFMgKSAmJiAhIGRlZmluZWQoIE1PUlBIVEFSR0VUU19URVhUVVJFICkgKSIsIlx0YXR0cmlidXRlIHZlYzMgbW9ycGhUYXJnZXQwOyIsIlx0YXR0cmlidXRlIHZlYzMgbW9ycGhUYXJnZXQxOyIsIlx0YXR0cmlidXRlIHZlYzMgbW9ycGhUYXJnZXQyOyIsIlx0YXR0cmlidXRlIHZlYzMgbW9ycGhUYXJnZXQzOyIsIlx0I2lmZGVmIFVTRV9NT1JQSE5PUk1BTFMiLCJcdFx0YXR0cmlidXRlIHZlYzMgbW9ycGhOb3JtYWwwOyIsIlx0XHRhdHRyaWJ1dGUgdmVjMyBtb3JwaE5vcm1hbDE7IiwiXHRcdGF0dHJpYnV0ZSB2ZWMzIG1vcnBoTm9ybWFsMjsiLCJcdFx0YXR0cmlidXRlIHZlYzMgbW9ycGhOb3JtYWwzOyIsIlx0I2Vsc2UiLCJcdFx0YXR0cmlidXRlIHZlYzMgbW9ycGhUYXJnZXQ0OyIsIlx0XHRhdHRyaWJ1dGUgdmVjMyBtb3JwaFRhcmdldDU7IiwiXHRcdGF0dHJpYnV0ZSB2ZWMzIG1vcnBoVGFyZ2V0NjsiLCJcdFx0YXR0cmlidXRlIHZlYzMgbW9ycGhUYXJnZXQ3OyIsIlx0I2VuZGlmIiwiI2VuZGlmIiwiI2lmZGVmIFVTRV9TS0lOTklORyIsIlx0YXR0cmlidXRlIHZlYzQgc2tpbkluZGV4OyIsIlx0YXR0cmlidXRlIHZlYzQgc2tpbldlaWdodDsiLCIjZW5kaWYiLCJcbiJdLmZpbHRlcihWUykuam9pbigiXG4iKSx4PVtwLEp1ZShlKSwiI2RlZmluZSBTSEFERVJfTkFNRSAiK2Uuc2hhZGVyTmFtZSxoLGUudXNlRm9nJiZlLmZvZz8iI2RlZmluZSBVU0VfRk9HIjoiIixlLnVzZUZvZyYmZS5mb2dFeHAyPyIjZGVmaW5lIEZPR19FWFAyIjoiIixlLm1hcD8iI2RlZmluZSBVU0VfTUFQIjoiIixlLm1hdGNhcD8iI2RlZmluZSBVU0VfTUFUQ0FQIjoiIixlLmVudk1hcD8iI2RlZmluZSBVU0VfRU5WTUFQIjoiIixlLmVudk1hcD8iI2RlZmluZSAiK2M6IiIsZS5lbnZNYXA/IiNkZWZpbmUgIit1OiIiLGUuZW52TWFwPyIjZGVmaW5lICIrZDoiIixlLmxpZ2h0TWFwPyIjZGVmaW5lIFVTRV9MSUdIVE1BUCI6IiIsZS5hb01hcD8iI2RlZmluZSBVU0VfQU9NQVAiOiIiLGUuZW1pc3NpdmVNYXA/IiNkZWZpbmUgVVNFX0VNSVNTSVZFTUFQIjoiIixlLmJ1bXBNYXA/IiNkZWZpbmUgVVNFX0JVTVBNQVAiOiIiLGUubm9ybWFsTWFwPyIjZGVmaW5lIFVTRV9OT1JNQUxNQVAiOiIiLGUubm9ybWFsTWFwJiZlLm9iamVjdFNwYWNlTm9ybWFsTWFwPyIjZGVmaW5lIE9CSkVDVFNQQUNFX05PUk1BTE1BUCI6IiIsZS5ub3JtYWxNYXAmJmUudGFuZ2VudFNwYWNlTm9ybWFsTWFwPyIjZGVmaW5lIFRBTkdFTlRTUEFDRV9OT1JNQUxNQVAiOiIiLGUuY2xlYXJjb2F0PyIjZGVmaW5lIFVTRV9DTEVBUkNPQVQiOiIiLGUuY2xlYXJjb2F0TWFwPyIjZGVmaW5lIFVTRV9DTEVBUkNPQVRNQVAiOiIiLGUuY2xlYXJjb2F0Um91Z2huZXNzTWFwPyIjZGVmaW5lIFVTRV9DTEVBUkNPQVRfUk9VR0hORVNTTUFQIjoiIixlLmNsZWFyY29hdE5vcm1hbE1hcD8iI2RlZmluZSBVU0VfQ0xFQVJDT0FUX05PUk1BTE1BUCI6IiIsZS5zcGVjdWxhck1hcD8iI2RlZmluZSBVU0VfU1BFQ1VMQVJNQVAiOiIiLGUuc3BlY3VsYXJJbnRlbnNpdHlNYXA/IiNkZWZpbmUgVVNFX1NQRUNVTEFSSU5URU5TSVRZTUFQIjoiIixlLnNwZWN1bGFyQ29sb3JNYXA/IiNkZWZpbmUgVVNFX1NQRUNVTEFSQ09MT1JNQVAiOiIiLGUucm91Z2huZXNzTWFwPyIjZGVmaW5lIFVTRV9ST1VHSE5FU1NNQVAiOiIiLGUubWV0YWxuZXNzTWFwPyIjZGVmaW5lIFVTRV9NRVRBTE5FU1NNQVAiOiIiLGUuYWxwaGFNYXA/IiNkZWZpbmUgVVNFX0FMUEhBTUFQIjoiIixlLmFscGhhVGVzdD8iI2RlZmluZSBVU0VfQUxQSEFURVNUIjoiIixlLnNoZWVuPyIjZGVmaW5lIFVTRV9TSEVFTiI6IiIsZS5zaGVlbkNvbG9yTWFwPyIjZGVmaW5lIFVTRV9TSEVFTkNPTE9STUFQIjoiIixlLnNoZWVuUm91Z2huZXNzTWFwPyIjZGVmaW5lIFVTRV9TSEVFTlJPVUdITkVTU01BUCI6IiIsZS50cmFuc21pc3Npb24/IiNkZWZpbmUgVVNFX1RSQU5TTUlTU0lPTiI6IiIsZS50cmFuc21pc3Npb25NYXA/IiNkZWZpbmUgVVNFX1RSQU5TTUlTU0lPTk1BUCI6IiIsZS50aGlja25lc3NNYXA/IiNkZWZpbmUgVVNFX1RISUNLTkVTU01BUCI6IiIsZS5kZWNvZGVWaWRlb1RleHR1cmU/IiNkZWZpbmUgREVDT0RFX1ZJREVPX1RFWFRVUkUiOiIiLGUudmVydGV4VGFuZ2VudHM/IiNkZWZpbmUgVVNFX1RBTkdFTlQiOiIiLGUudmVydGV4Q29sb3JzfHxlLmluc3RhbmNpbmdDb2xvcj8iI2RlZmluZSBVU0VfQ09MT1IiOiIiLGUudmVydGV4QWxwaGFzPyIjZGVmaW5lIFVTRV9DT0xPUl9BTFBIQSI6IiIsZS52ZXJ0ZXhVdnM/IiNkZWZpbmUgVVNFX1VWIjoiIixlLnV2c1ZlcnRleE9ubHk/IiNkZWZpbmUgVVZTX1ZFUlRFWF9PTkxZIjoiIixlLmdyYWRpZW50TWFwPyIjZGVmaW5lIFVTRV9HUkFESUVOVE1BUCI6IiIsZS5mbGF0U2hhZGluZz8iI2RlZmluZSBGTEFUX1NIQURFRCI6IiIsZS5kb3VibGVTaWRlZD8iI2RlZmluZSBET1VCTEVfU0lERUQiOiIiLGUuZmxpcFNpZGVkPyIjZGVmaW5lIEZMSVBfU0lERUQiOiIiLGUuc2hhZG93TWFwRW5hYmxlZD8iI2RlZmluZSBVU0VfU0hBRE9XTUFQIjoiIixlLnNoYWRvd01hcEVuYWJsZWQ/IiNkZWZpbmUgIitsOiIiLGUucHJlbXVsdGlwbGllZEFscGhhPyIjZGVmaW5lIFBSRU1VTFRJUExJRURfQUxQSEEiOiIiLGUucGh5c2ljYWxseUNvcnJlY3RMaWdodHM/IiNkZWZpbmUgUEhZU0lDQUxMWV9DT1JSRUNUX0xJR0hUUyI6IiIsZS5sb2dhcml0aG1pY0RlcHRoQnVmZmVyPyIjZGVmaW5lIFVTRV9MT0dERVBUSEJVRiI6IiIsZS5sb2dhcml0aG1pY0RlcHRoQnVmZmVyJiZlLnJlbmRlcmVyRXh0ZW5zaW9uRnJhZ0RlcHRoPyIjZGVmaW5lIFVTRV9MT0dERVBUSEJVRl9FWFQiOiIiLChlLmV4dGVuc2lvblNoYWRlclRleHR1cmVMT0R8fGUuZW52TWFwKSYmZS5yZW5kZXJlckV4dGVuc2lvblNoYWRlclRleHR1cmVMb2Q/IiNkZWZpbmUgVEVYVFVSRV9MT0RfRVhUIjoiIiwidW5pZm9ybSBtYXQ0IHZpZXdNYXRyaXg7IiwidW5pZm9ybSB2ZWMzIGNhbWVyYVBvc2l0aW9uOyIsInVuaWZvcm0gYm9vbCBpc09ydGhvZ3JhcGhpYzsiLDAhPT1lLnRvbmVNYXBwaW5nPyIjZGVmaW5lIFRPTkVfTUFQUElORyI6IiIsMCE9PWUudG9uZU1hcHBpbmc/RGkudG9uZW1hcHBpbmdfcGFyc19mcmFnbWVudDoiIiwwIT09ZS50b25lTWFwcGluZz9WN2UoInRvbmVNYXBwaW5nIixlLnRvbmVNYXBwaW5nKToiIixlLmRpdGhlcmluZz8iI2RlZmluZSBESVRIRVJJTkciOiIiLGUuYWxwaGFXcml0ZT8iIjoiI2RlZmluZSBPUEFRVUUiLERpLmVuY29kaW5nc19wYXJzX2ZyYWdtZW50LEI3ZSgibGluZWFyVG9PdXRwdXRUZXhlbCIsZS5vdXRwdXRFbmNvZGluZyksZS5kZXB0aFBhY2tpbmc/IiNkZWZpbmUgREVQVEhfUEFDS0lORyAiK2UuZGVwdGhQYWNraW5nOiIiLCJcbiJdLmZpbHRlcihWUykuam9pbigiXG4iKSkscz1nOChzKSxzPVF1ZShzLGUpLHM9S3VlKHMsZSksYT1nOChhKSxhPVF1ZShhLGUpLGE9S3VlKGEsZSkscz1adWUocyksYT1adWUoYSksZS5pc1dlYkdMMiYmITAhPT1lLmlzUmF3U2hhZGVyTWF0ZXJpYWwmJihnPSIjdmVyc2lvbiAzMDAgZXNcbiIsbT1bInByZWNpc2lvbiBtZWRpdW1wIHNhbXBsZXIyREFycmF5OyIsIiNkZWZpbmUgYXR0cmlidXRlIGluIiwiI2RlZmluZSB2YXJ5aW5nIG91dCIsIiNkZWZpbmUgdGV4dHVyZTJEIHRleHR1cmUiXS5qb2luKCJcbiIpKyJcbiIrbSx4PVsiI2RlZmluZSB2YXJ5aW5nIGluIiwiMzAwIGVzIj09PWUuZ2xzbFZlcnNpb24/IiI6ImxheW91dChsb2NhdGlvbiA9IDApIG91dCBoaWdocCB2ZWM0IHBjX2ZyYWdDb2xvcjsiLCIzMDAgZXMiPT09ZS5nbHNsVmVyc2lvbj8iIjoiI2RlZmluZSBnbF9GcmFnQ29sb3IgcGNfZnJhZ0NvbG9yIiwiI2RlZmluZSBnbF9GcmFnRGVwdGhFWFQgZ2xfRnJhZ0RlcHRoIiwiI2RlZmluZSB0ZXh0dXJlMkQgdGV4dHVyZSIsIiNkZWZpbmUgdGV4dHVyZUN1YmUgdGV4dHVyZSIsIiNkZWZpbmUgdGV4dHVyZTJEUHJvaiB0ZXh0dXJlUHJvaiIsIiNkZWZpbmUgdGV4dHVyZTJETG9kRVhUIHRleHR1cmVMb2QiLCIjZGVmaW5lIHRleHR1cmUyRFByb2pMb2RFWFQgdGV4dHVyZVByb2pMb2QiLCIjZGVmaW5lIHRleHR1cmVDdWJlTG9kRVhUIHRleHR1cmVMb2QiLCIjZGVmaW5lIHRleHR1cmUyREdyYWRFWFQgdGV4dHVyZUdyYWQiLCIjZGVmaW5lIHRleHR1cmUyRFByb2pHcmFkRVhUIHRleHR1cmVQcm9qR3JhZCIsIiNkZWZpbmUgdGV4dHVyZUN1YmVHcmFkRVhUIHRleHR1cmVHcmFkIl0uam9pbigiXG4iKSsiXG4iK3gpO2xldCBaLHosRD1nK3grYSxUPVl1ZShyLDM1NjMzLGcrbStzKSxrPVl1ZShyLDM1NjMyLEQpO2lmKHIuYXR0YWNoU2hhZGVyKGYsVCksci5hdHRhY2hTaGFkZXIoZixrKSx2b2lkIDAhPT1lLmluZGV4MEF0dHJpYnV0ZU5hbWU/ci5iaW5kQXR0cmliTG9jYXRpb24oZiwwLGUuaW5kZXgwQXR0cmlidXRlTmFtZSk6ITA9PT1lLm1vcnBoVGFyZ2V0cyYmci5iaW5kQXR0cmliTG9jYXRpb24oZiwwLCJwb3NpdGlvbiIpLHIubGlua1Byb2dyYW0oZiksbi5kZWJ1Zy5jaGVja1NoYWRlckVycm9ycyl7bGV0IGZlPXIuZ2V0UHJvZ3JhbUluZm9Mb2coZikudHJpbSgpLHVlPXIuZ2V0U2hhZGVySW5mb0xvZyhUKS50cmltKCksaGU9ci5nZXRTaGFkZXJJbmZvTG9nKGspLnRyaW0oKSx3PSEwLEY9ITA7aWYoITE9PT1yLmdldFByb2dyYW1QYXJhbWV0ZXIoZiwzNTcxNCkpe3c9ITE7bGV0IHE9WHVlKHIsVCwidmVydGV4IiksSz1YdWUocixrLCJmcmFnbWVudCIpO2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMUHJvZ3JhbTogU2hhZGVyIEVycm9yICIrci5nZXRFcnJvcigpKyIgLSBWQUxJREFURV9TVEFUVVMgIityLmdldFByb2dyYW1QYXJhbWV0ZXIoZiwzNTcxNSkrIlxuXG5Qcm9ncmFtIEluZm8gTG9nOiAiK2ZlKyJcbiIrcSsiXG4iK0spfWVsc2UiIiE9PWZlP2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xQcm9ncmFtOiBQcm9ncmFtIEluZm8gTG9nOiIsZmUpOigiIj09PXVlfHwiIj09PWhlKSYmKEY9ITEpO0YmJih0aGlzLmRpYWdub3N0aWNzPXtydW5uYWJsZTp3LHByb2dyYW1Mb2c6ZmUsdmVydGV4U2hhZGVyOntsb2c6dWUscHJlZml4Om19LGZyYWdtZW50U2hhZGVyOntsb2c6aGUscHJlZml4Onh9fSl9cmV0dXJuIHIuZGVsZXRlU2hhZGVyKFQpLHIuZGVsZXRlU2hhZGVyKGspLHRoaXMuZ2V0VW5pZm9ybXM9ZnVuY3Rpb24oKXtyZXR1cm4gdm9pZCAwPT09WiYmKFo9bmV3IHlmKHIsZikpLFp9LHRoaXMuZ2V0QXR0cmlidXRlcz1mdW5jdGlvbigpe3JldHVybiB2b2lkIDA9PT16JiYoej1mdW5jdGlvbihuLHQpe2xldCBlPXt9LGk9bi5nZXRQcm9ncmFtUGFyYW1ldGVyKHQsMzU3MjEpO2ZvcihsZXQgcj0wO3I8aTtyKyspe2xldCBvPW4uZ2V0QWN0aXZlQXR0cmliKHQscikscz1vLm5hbWUsYT0xOzM1Njc0PT09by50eXBlJiYoYT0yKSwzNTY3NT09PW8udHlwZSYmKGE9MyksMzU2NzY9PT1vLnR5cGUmJihhPTQpLGVbc109e3R5cGU6by50eXBlLGxvY2F0aW9uOm4uZ2V0QXR0cmliTG9jYXRpb24odCxzKSxsb2NhdGlvblNpemU6YX19cmV0dXJuIGV9KHIsZikpLHp9LHRoaXMuZGVzdHJveT1mdW5jdGlvbigpe2kucmVsZWFzZVN0YXRlc09mUHJvZ3JhbSh0aGlzKSxyLmRlbGV0ZVByb2dyYW0oZiksdGhpcy5wcm9ncmFtPXZvaWQgMH0sdGhpcy5uYW1lPWUuc2hhZGVyTmFtZSx0aGlzLmlkPUY3ZSsrLHRoaXMuY2FjaGVLZXk9dCx0aGlzLnVzZWRUaW1lcz0xLHRoaXMucHJvZ3JhbT1mLHRoaXMudmVydGV4U2hhZGVyPVQsdGhpcy5mcmFnbWVudFNoYWRlcj1rLHRoaXN9dmFyICQ3ZT0wO2Z1bmN0aW9uIGU5ZShuLHQsZSxpLHIsbyxzKXtsZXQgYT1uZXcgc2ssbD1uZXcgY2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnNoYWRlckNhY2hlPW5ldyBNYXAsdGhpcy5tYXRlcmlhbENhY2hlPW5ldyBNYXB9dXBkYXRlKHQpe2xldCBpPXQuZnJhZ21lbnRTaGFkZXIscj10aGlzLl9nZXRTaGFkZXJTdGFnZSh0LnZlcnRleFNoYWRlciksbz10aGlzLl9nZXRTaGFkZXJTdGFnZShpKSxzPXRoaXMuX2dldFNoYWRlckNhY2hlRm9yTWF0ZXJpYWwodCk7cmV0dXJuITE9PT1zLmhhcyhyKSYmKHMuYWRkKHIpLHIudXNlZFRpbWVzKyspLCExPT09cy5oYXMobykmJihzLmFkZChvKSxvLnVzZWRUaW1lcysrKSx0aGlzfXJlbW92ZSh0KXtsZXQgZT10aGlzLm1hdGVyaWFsQ2FjaGUuZ2V0KHQpO2ZvcihsZXQgaSBvZiBlKWkudXNlZFRpbWVzLS0sMD09PWkudXNlZFRpbWVzJiZ0aGlzLnNoYWRlckNhY2hlLmRlbGV0ZShpKTtyZXR1cm4gdGhpcy5tYXRlcmlhbENhY2hlLmRlbGV0ZSh0KSx0aGlzfWdldFZlcnRleFNoYWRlcklEKHQpe3JldHVybiB0aGlzLl9nZXRTaGFkZXJTdGFnZSh0LnZlcnRleFNoYWRlcikuaWR9Z2V0RnJhZ21lbnRTaGFkZXJJRCh0KXtyZXR1cm4gdGhpcy5fZ2V0U2hhZGVyU3RhZ2UodC5mcmFnbWVudFNoYWRlcikuaWR9ZGlzcG9zZSgpe3RoaXMuc2hhZGVyQ2FjaGUuY2xlYXIoKSx0aGlzLm1hdGVyaWFsQ2FjaGUuY2xlYXIoKX1fZ2V0U2hhZGVyQ2FjaGVGb3JNYXRlcmlhbCh0KXtsZXQgZT10aGlzLm1hdGVyaWFsQ2FjaGU7cmV0dXJuITE9PT1lLmhhcyh0KSYmZS5zZXQodCxuZXcgU2V0KSxlLmdldCh0KX1fZ2V0U2hhZGVyU3RhZ2UodCl7bGV0IGU9dGhpcy5zaGFkZXJDYWNoZTtpZighMT09PWUuaGFzKHQpKXtsZXQgaT1uZXcgY2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLmlkPSQ3ZSsrLHRoaXMudXNlZFRpbWVzPTB9fTtlLnNldCh0LGkpfXJldHVybiBlLmdldCh0KX19LGM9W10sdT1yLmlzV2ViR0wyLGQ9ci5sb2dhcml0aG1pY0RlcHRoQnVmZmVyLHA9ci5mbG9hdFZlcnRleFRleHR1cmVzLGg9ci5tYXhWZXJ0ZXhVbmlmb3JtcyxmPXIudmVydGV4VGV4dHVyZXMsbT1yLnByZWNpc2lvbix4PXtNZXNoRGVwdGhNYXRlcmlhbDoiZGVwdGgiLE1lc2hEaXN0YW5jZU1hdGVyaWFsOiJkaXN0YW5jZVJHQkEiLE1lc2hOb3JtYWxNYXRlcmlhbDoibm9ybWFsIixNZXNoQmFzaWNNYXRlcmlhbDoiYmFzaWMiLE1lc2hMYW1iZXJ0TWF0ZXJpYWw6ImxhbWJlcnQiLE1lc2hQaG9uZ01hdGVyaWFsOiJwaG9uZyIsTWVzaFRvb25NYXRlcmlhbDoidG9vbiIsTWVzaFN0YW5kYXJkTWF0ZXJpYWw6InBoeXNpY2FsIixNZXNoUGh5c2ljYWxNYXRlcmlhbDoicGh5c2ljYWwiLE1lc2hNYXRjYXBNYXRlcmlhbDoibWF0Y2FwIixMaW5lQmFzaWNNYXRlcmlhbDoiYmFzaWMiLExpbmVEYXNoZWRNYXRlcmlhbDoiZGFzaGVkIixQb2ludHNNYXRlcmlhbDoicG9pbnRzIixTaGFkb3dNYXRlcmlhbDoic2hhZG93IixTcHJpdGVNYXRlcmlhbDoic3ByaXRlIn07cmV0dXJue2dldFBhcmFtZXRlcnM6ZnVuY3Rpb24odyxGLHEsSyxkZSl7bGV0IERlLG50LGd0LFVlLFk9Sy5mb2csbGU9KHcuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbD9lOnQpLmdldCh3LmVudk1hcHx8KHcuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbD9LLmVudmlyb25tZW50Om51bGwpKSxJZT14W3cudHlwZV0sdmU9ZGUuaXNTa2lubmVkTWVzaD9mdW5jdGlvbih3KXtsZXQgcT13LnNrZWxldG9uLmJvbmVzO2lmKHApcmV0dXJuIDEwMjQ7e2xldCBkZT1NYXRoLmZsb29yKChoLTIwKS80KSxZPU1hdGgubWluKGRlLHEubGVuZ3RoKTtyZXR1cm4gWTxxLmxlbmd0aD8oY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBTa2VsZXRvbiBoYXMgIitxLmxlbmd0aCsiIGJvbmVzLiBUaGlzIEdQVSBzdXBwb3J0cyAiK1krIi4iKSwwKTpZfX0oZGUpOjA7aWYobnVsbCE9PXcucHJlY2lzaW9uJiYobT1yLmdldE1heFByZWNpc2lvbih3LnByZWNpc2lvbiksbSE9PXcucHJlY2lzaW9uJiZjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUHJvZ3JhbS5nZXRQYXJhbWV0ZXJzOiIsdy5wcmVjaXNpb24sIm5vdCBzdXBwb3J0ZWQsIHVzaW5nIixtLCJpbnN0ZWFkLiIpKSxJZSl7bGV0IFRlPVNkW0llXTtEZT1UZS52ZXJ0ZXhTaGFkZXIsbnQ9VGUuZnJhZ21lbnRTaGFkZXJ9ZWxzZSBEZT13LnZlcnRleFNoYWRlcixudD13LmZyYWdtZW50U2hhZGVyLGwudXBkYXRlKHcpLGd0PWwuZ2V0VmVydGV4U2hhZGVySUQodyksVWU9bC5nZXRGcmFnbWVudFNoYWRlcklEKHcpO2xldCBBZT1uLmdldFJlbmRlclRhcmdldCgpLHB0PXcuY2xlYXJjb2F0PjA7cmV0dXJue2lzV2ViR0wyOnUsc2hhZGVySUQ6SWUsc2hhZGVyTmFtZTp3LnR5cGUsdmVydGV4U2hhZGVyOkRlLGZyYWdtZW50U2hhZGVyOm50LGRlZmluZXM6dy5kZWZpbmVzLGN1c3RvbVZlcnRleFNoYWRlcklEOmd0LGN1c3RvbUZyYWdtZW50U2hhZGVySUQ6VWUsaXNSYXdTaGFkZXJNYXRlcmlhbDohMD09PXcuaXNSYXdTaGFkZXJNYXRlcmlhbCxnbHNsVmVyc2lvbjp3Lmdsc2xWZXJzaW9uLHByZWNpc2lvbjptLGluc3RhbmNpbmc6ITA9PT1kZS5pc0luc3RhbmNlZE1lc2gsaW5zdGFuY2luZ0NvbG9yOiEwPT09ZGUuaXNJbnN0YW5jZWRNZXNoJiZudWxsIT09ZGUuaW5zdGFuY2VDb2xvcixzdXBwb3J0c1ZlcnRleFRleHR1cmVzOmYsb3V0cHV0RW5jb2Rpbmc6bnVsbD09PUFlP24ub3V0cHV0RW5jb2Rpbmc6ITA9PT1BZS5pc1hSUmVuZGVyVGFyZ2V0P0FlLnRleHR1cmUuZW5jb2Rpbmc6YmYsbWFwOiEhdy5tYXAsbWF0Y2FwOiEhdy5tYXRjYXAsZW52TWFwOiEhbGUsZW52TWFwTW9kZTpsZSYmbGUubWFwcGluZyxlbnZNYXBDdWJlVVY6ISFsZSYmKDMwNj09PWxlLm1hcHBpbmd8fDMwNz09PWxlLm1hcHBpbmcpLGxpZ2h0TWFwOiEhdy5saWdodE1hcCxhb01hcDohIXcuYW9NYXAsZW1pc3NpdmVNYXA6ISF3LmVtaXNzaXZlTWFwLGJ1bXBNYXA6ISF3LmJ1bXBNYXAsbm9ybWFsTWFwOiEhdy5ub3JtYWxNYXAsb2JqZWN0U3BhY2VOb3JtYWxNYXA6MT09PXcubm9ybWFsTWFwVHlwZSx0YW5nZW50U3BhY2VOb3JtYWxNYXA6MD09PXcubm9ybWFsTWFwVHlwZSxkZWNvZGVWaWRlb1RleHR1cmU6ISF3Lm1hcCYmITA9PT13Lm1hcC5pc1ZpZGVvVGV4dHVyZSYmdy5tYXAuZW5jb2Rpbmc9PT1XcixjbGVhcmNvYXQ6cHQsY2xlYXJjb2F0TWFwOnB0JiYhIXcuY2xlYXJjb2F0TWFwLGNsZWFyY29hdFJvdWdobmVzc01hcDpwdCYmISF3LmNsZWFyY29hdFJvdWdobmVzc01hcCxjbGVhcmNvYXROb3JtYWxNYXA6cHQmJiEhdy5jbGVhcmNvYXROb3JtYWxNYXAsZGlzcGxhY2VtZW50TWFwOiEhdy5kaXNwbGFjZW1lbnRNYXAscm91Z2huZXNzTWFwOiEhdy5yb3VnaG5lc3NNYXAsbWV0YWxuZXNzTWFwOiEhdy5tZXRhbG5lc3NNYXAsc3BlY3VsYXJNYXA6ISF3LnNwZWN1bGFyTWFwLHNwZWN1bGFySW50ZW5zaXR5TWFwOiEhdy5zcGVjdWxhckludGVuc2l0eU1hcCxzcGVjdWxhckNvbG9yTWFwOiEhdy5zcGVjdWxhckNvbG9yTWFwLGFscGhhTWFwOiEhdy5hbHBoYU1hcCxhbHBoYVRlc3Q6dy5hbHBoYVRlc3Q+MCxhbHBoYVdyaXRlOncuYWxwaGFXcml0ZXx8dy50cmFuc3BhcmVudCxncmFkaWVudE1hcDohIXcuZ3JhZGllbnRNYXAsc2hlZW46dy5zaGVlbj4wLHNoZWVuQ29sb3JNYXA6ISF3LnNoZWVuQ29sb3JNYXAsc2hlZW5Sb3VnaG5lc3NNYXA6ISF3LnNoZWVuUm91Z2huZXNzTWFwLHRyYW5zbWlzc2lvbjp3LnRyYW5zbWlzc2lvbj4wLHRyYW5zbWlzc2lvbk1hcDohIXcudHJhbnNtaXNzaW9uTWFwLHRoaWNrbmVzc01hcDohIXcudGhpY2tuZXNzTWFwLGNvbWJpbmU6dy5jb21iaW5lLHZlcnRleFRhbmdlbnRzOiEhdy5ub3JtYWxNYXAmJiEhZGUuZ2VvbWV0cnkmJiEhZGUuZ2VvbWV0cnkuYXR0cmlidXRlcy50YW5nZW50LHZlcnRleENvbG9yczp3LnZlcnRleENvbG9ycyx2ZXJ0ZXhBbHBoYXM6ITA9PT13LnZlcnRleENvbG9ycyYmISFkZS5nZW9tZXRyeSYmISFkZS5nZW9tZXRyeS5hdHRyaWJ1dGVzLmNvbG9yJiY0PT09ZGUuZ2VvbWV0cnkuYXR0cmlidXRlcy5jb2xvci5pdGVtU2l6ZSx2ZXJ0ZXhVdnM6ISEody5tYXB8fHcuYnVtcE1hcHx8dy5ub3JtYWxNYXB8fHcuc3BlY3VsYXJNYXB8fHcuYWxwaGFNYXB8fHcuZW1pc3NpdmVNYXB8fHcucm91Z2huZXNzTWFwfHx3Lm1ldGFsbmVzc01hcHx8dy5jbGVhcmNvYXRNYXB8fHcuY2xlYXJjb2F0Um91Z2huZXNzTWFwfHx3LmNsZWFyY29hdE5vcm1hbE1hcHx8dy5kaXNwbGFjZW1lbnRNYXB8fHcudHJhbnNtaXNzaW9uTWFwfHx3LnRoaWNrbmVzc01hcHx8dy5zcGVjdWxhckludGVuc2l0eU1hcHx8dy5zcGVjdWxhckNvbG9yTWFwfHx3LnNoZWVuQ29sb3JNYXB8fHcuc2hlZW5Sb3VnaG5lc3NNYXApLHV2c1ZlcnRleE9ubHk6ISh3Lm1hcHx8dy5idW1wTWFwfHx3Lm5vcm1hbE1hcHx8dy5zcGVjdWxhck1hcHx8dy5hbHBoYU1hcHx8dy5lbWlzc2l2ZU1hcHx8dy5yb3VnaG5lc3NNYXB8fHcubWV0YWxuZXNzTWFwfHx3LmNsZWFyY29hdE5vcm1hbE1hcHx8dy50cmFuc21pc3Npb24+MHx8dy50cmFuc21pc3Npb25NYXB8fHcudGhpY2tuZXNzTWFwfHx3LnNwZWN1bGFySW50ZW5zaXR5TWFwfHx3LnNwZWN1bGFyQ29sb3JNYXB8fHcuc2hlZW4+MHx8dy5zaGVlbkNvbG9yTWFwfHx3LnNoZWVuUm91Z2huZXNzTWFwfHwhdy5kaXNwbGFjZW1lbnRNYXApLGZvZzohIVksdXNlRm9nOncuZm9nLGZvZ0V4cDI6WSYmWS5pc0ZvZ0V4cDIsZmxhdFNoYWRpbmc6ISF3LmZsYXRTaGFkaW5nLHNpemVBdHRlbnVhdGlvbjp3LnNpemVBdHRlbnVhdGlvbixsb2dhcml0aG1pY0RlcHRoQnVmZmVyOmQsc2tpbm5pbmc6ITA9PT1kZS5pc1NraW5uZWRNZXNoJiZ2ZT4wLG1heEJvbmVzOnZlLHVzZVZlcnRleFRleHR1cmU6cCxtb3JwaFRhcmdldHM6ISFkZS5nZW9tZXRyeSYmISFkZS5nZW9tZXRyeS5tb3JwaEF0dHJpYnV0ZXMucG9zaXRpb24sbW9ycGhOb3JtYWxzOiEhZGUuZ2VvbWV0cnkmJiEhZGUuZ2VvbWV0cnkubW9ycGhBdHRyaWJ1dGVzLm5vcm1hbCxtb3JwaFRhcmdldHNDb3VudDpkZS5nZW9tZXRyeSYmZGUuZ2VvbWV0cnkubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uP2RlLmdlb21ldHJ5Lm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbi5sZW5ndGg6MCxudW1EaXJMaWdodHM6Ri5kaXJlY3Rpb25hbC5sZW5ndGgsbnVtUG9pbnRMaWdodHM6Ri5wb2ludC5sZW5ndGgsbnVtU3BvdExpZ2h0czpGLnNwb3QubGVuZ3RoLG51bVJlY3RBcmVhTGlnaHRzOkYucmVjdEFyZWEubGVuZ3RoLG51bUhlbWlMaWdodHM6Ri5oZW1pLmxlbmd0aCxudW1EaXJMaWdodFNoYWRvd3M6Ri5kaXJlY3Rpb25hbFNoYWRvd01hcC5sZW5ndGgsbnVtUG9pbnRMaWdodFNoYWRvd3M6Ri5wb2ludFNoYWRvd01hcC5sZW5ndGgsbnVtU3BvdExpZ2h0U2hhZG93czpGLnNwb3RTaGFkb3dNYXAubGVuZ3RoLG51bUNsaXBwaW5nUGxhbmVzOnMubnVtUGxhbmVzLG51bUNsaXBJbnRlcnNlY3Rpb246cy5udW1JbnRlcnNlY3Rpb24sZGl0aGVyaW5nOncuZGl0aGVyaW5nLHNoYWRvd01hcEVuYWJsZWQ6bi5zaGFkb3dNYXAuZW5hYmxlZCYmcS5sZW5ndGg+MCxzaGFkb3dNYXBUeXBlOm4uc2hhZG93TWFwLnR5cGUsdG9uZU1hcHBpbmc6dy50b25lTWFwcGVkP24udG9uZU1hcHBpbmc6MCxwaHlzaWNhbGx5Q29ycmVjdExpZ2h0czpuLnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzLHByZW11bHRpcGxpZWRBbHBoYTp3LnByZW11bHRpcGxpZWRBbHBoYSxkb3VibGVTaWRlZDoyPT09dy5zaWRlLGZsaXBTaWRlZDoxPT09dy5zaWRlLGRlcHRoUGFja2luZzp2b2lkIDAhPT13LmRlcHRoUGFja2luZyYmdy5kZXB0aFBhY2tpbmcsaW5kZXgwQXR0cmlidXRlTmFtZTp3LmluZGV4MEF0dHJpYnV0ZU5hbWUsZXh0ZW5zaW9uRGVyaXZhdGl2ZXM6dy5leHRlbnNpb25zJiZ3LmV4dGVuc2lvbnMuZGVyaXZhdGl2ZXMsZXh0ZW5zaW9uRnJhZ0RlcHRoOncuZXh0ZW5zaW9ucyYmdy5leHRlbnNpb25zLmZyYWdEZXB0aCxleHRlbnNpb25EcmF3QnVmZmVyczp3LmV4dGVuc2lvbnMmJncuZXh0ZW5zaW9ucy5kcmF3QnVmZmVycyxleHRlbnNpb25TaGFkZXJUZXh0dXJlTE9EOncuZXh0ZW5zaW9ucyYmdy5leHRlbnNpb25zLnNoYWRlclRleHR1cmVMT0QscmVuZGVyZXJFeHRlbnNpb25GcmFnRGVwdGg6dXx8aS5oYXMoIkVYVF9mcmFnX2RlcHRoIikscmVuZGVyZXJFeHRlbnNpb25EcmF3QnVmZmVyczp1fHxpLmhhcygiV0VCR0xfZHJhd19idWZmZXJzIikscmVuZGVyZXJFeHRlbnNpb25TaGFkZXJUZXh0dXJlTG9kOnV8fGkuaGFzKCJFWFRfc2hhZGVyX3RleHR1cmVfbG9kIiksY3VzdG9tUHJvZ3JhbUNhY2hlS2V5OncuY3VzdG9tUHJvZ3JhbUNhY2hlS2V5KCl9fSxnZXRQcm9ncmFtQ2FjaGVLZXk6ZnVuY3Rpb24odyl7bGV0IEY9W107aWYody5zaGFkZXJJRD9GLnB1c2gody5zaGFkZXJJRCk6KEYucHVzaCh3LmN1c3RvbVZlcnRleFNoYWRlcklEKSxGLnB1c2gody5jdXN0b21GcmFnbWVudFNoYWRlcklEKSksdm9pZCAwIT09dy5kZWZpbmVzKWZvcihsZXQgcSBpbiB3LmRlZmluZXMpRi5wdXNoKHEpLEYucHVzaCh3LmRlZmluZXNbcV0pO3JldHVybiExPT09dy5pc1Jhd1NoYWRlck1hdGVyaWFsJiYoZnVuY3Rpb24odyxGKXt3LnB1c2goRi5wcmVjaXNpb24pLHcucHVzaChGLm91dHB1dEVuY29kaW5nKSx3LnB1c2goRi5lbnZNYXBNb2RlKSx3LnB1c2goRi5jb21iaW5lKSx3LnB1c2goRi52ZXJ0ZXhVdnMpLHcucHVzaChGLmZvZ0V4cDIpLHcucHVzaChGLnNpemVBdHRlbnVhdGlvbiksdy5wdXNoKEYubWF4Qm9uZXMpLHcucHVzaChGLm1vcnBoVGFyZ2V0c0NvdW50KSx3LnB1c2goRi5udW1EaXJMaWdodHMpLHcucHVzaChGLm51bVBvaW50TGlnaHRzKSx3LnB1c2goRi5udW1TcG90TGlnaHRzKSx3LnB1c2goRi5udW1IZW1pTGlnaHRzKSx3LnB1c2goRi5udW1SZWN0QXJlYUxpZ2h0cyksdy5wdXNoKEYubnVtRGlyTGlnaHRTaGFkb3dzKSx3LnB1c2goRi5udW1Qb2ludExpZ2h0U2hhZG93cyksdy5wdXNoKEYubnVtU3BvdExpZ2h0U2hhZG93cyksdy5wdXNoKEYuc2hhZG93TWFwVHlwZSksdy5wdXNoKEYudG9uZU1hcHBpbmcpLHcucHVzaChGLm51bUNsaXBwaW5nUGxhbmVzKSx3LnB1c2goRi5udW1DbGlwSW50ZXJzZWN0aW9uKSx3LnB1c2goRi5hbHBoYVdyaXRlKX0oRix3KSxmdW5jdGlvbih3LEYpe2EuZGlzYWJsZUFsbCgpLEYuaXNXZWJHTDImJmEuZW5hYmxlKDApLEYuc3VwcG9ydHNWZXJ0ZXhUZXh0dXJlcyYmYS5lbmFibGUoMSksRi5pbnN0YW5jaW5nJiZhLmVuYWJsZSgyKSxGLmluc3RhbmNpbmdDb2xvciYmYS5lbmFibGUoMyksRi5tYXAmJmEuZW5hYmxlKDQpLEYubWF0Y2FwJiZhLmVuYWJsZSg1KSxGLmVudk1hcCYmYS5lbmFibGUoNiksRi5lbnZNYXBDdWJlVVYmJmEuZW5hYmxlKDcpLEYubGlnaHRNYXAmJmEuZW5hYmxlKDgpLEYuYW9NYXAmJmEuZW5hYmxlKDkpLEYuZW1pc3NpdmVNYXAmJmEuZW5hYmxlKDEwKSxGLmJ1bXBNYXAmJmEuZW5hYmxlKDExKSxGLm5vcm1hbE1hcCYmYS5lbmFibGUoMTIpLEYub2JqZWN0U3BhY2VOb3JtYWxNYXAmJmEuZW5hYmxlKDEzKSxGLnRhbmdlbnRTcGFjZU5vcm1hbE1hcCYmYS5lbmFibGUoMTQpLEYuY2xlYXJjb2F0JiZhLmVuYWJsZSgxNSksRi5jbGVhcmNvYXRNYXAmJmEuZW5hYmxlKDE2KSxGLmNsZWFyY29hdFJvdWdobmVzc01hcCYmYS5lbmFibGUoMTcpLEYuY2xlYXJjb2F0Tm9ybWFsTWFwJiZhLmVuYWJsZSgxOCksRi5kaXNwbGFjZW1lbnRNYXAmJmEuZW5hYmxlKDE5KSxGLnNwZWN1bGFyTWFwJiZhLmVuYWJsZSgyMCksRi5yb3VnaG5lc3NNYXAmJmEuZW5hYmxlKDIxKSxGLm1ldGFsbmVzc01hcCYmYS5lbmFibGUoMjIpLEYuZ3JhZGllbnRNYXAmJmEuZW5hYmxlKDIzKSxGLmFscGhhTWFwJiZhLmVuYWJsZSgyNCksRi5hbHBoYVRlc3QmJmEuZW5hYmxlKDI1KSxGLnZlcnRleENvbG9ycyYmYS5lbmFibGUoMjYpLEYudmVydGV4QWxwaGFzJiZhLmVuYWJsZSgyNyksRi52ZXJ0ZXhVdnMmJmEuZW5hYmxlKDI4KSxGLnZlcnRleFRhbmdlbnRzJiZhLmVuYWJsZSgyOSksRi51dnNWZXJ0ZXhPbmx5JiZhLmVuYWJsZSgzMCksRi5mb2cmJmEuZW5hYmxlKDMxKSx3LnB1c2goYS5tYXNrKSxhLmRpc2FibGVBbGwoKSxGLnVzZUZvZyYmYS5lbmFibGUoMCksRi5mbGF0U2hhZGluZyYmYS5lbmFibGUoMSksRi5sb2dhcml0aG1pY0RlcHRoQnVmZmVyJiZhLmVuYWJsZSgyKSxGLnNraW5uaW5nJiZhLmVuYWJsZSgzKSxGLnVzZVZlcnRleFRleHR1cmUmJmEuZW5hYmxlKDQpLEYubW9ycGhUYXJnZXRzJiZhLmVuYWJsZSg1KSxGLm1vcnBoTm9ybWFscyYmYS5lbmFibGUoNiksRi5wcmVtdWx0aXBsaWVkQWxwaGEmJmEuZW5hYmxlKDcpLEYuc2hhZG93TWFwRW5hYmxlZCYmYS5lbmFibGUoOCksRi5waHlzaWNhbGx5Q29ycmVjdExpZ2h0cyYmYS5lbmFibGUoOSksRi5kb3VibGVTaWRlZCYmYS5lbmFibGUoMTApLEYuZmxpcFNpZGVkJiZhLmVuYWJsZSgxMSksRi5kZXB0aFBhY2tpbmcmJmEuZW5hYmxlKDEyKSxGLmRpdGhlcmluZyYmYS5lbmFibGUoMTMpLEYuc3BlY3VsYXJJbnRlbnNpdHlNYXAmJmEuZW5hYmxlKDE0KSxGLnNwZWN1bGFyQ29sb3JNYXAmJmEuZW5hYmxlKDE1KSxGLnRyYW5zbWlzc2lvbiYmYS5lbmFibGUoMTYpLEYudHJhbnNtaXNzaW9uTWFwJiZhLmVuYWJsZSgxNyksRi50aGlja25lc3NNYXAmJmEuZW5hYmxlKDE4KSxGLnNoZWVuJiZhLmVuYWJsZSgxOSksRi5zaGVlbkNvbG9yTWFwJiZhLmVuYWJsZSgyMCksRi5zaGVlblJvdWdobmVzc01hcCYmYS5lbmFibGUoMjEpLEYuZGVjb2RlVmlkZW9UZXh0dXJlJiZhLmVuYWJsZSgyMiksdy5wdXNoKGEubWFzayl9KEYsdyksRi5wdXNoKG4ub3V0cHV0RW5jb2RpbmcpKSxGLnB1c2gody5jdXN0b21Qcm9ncmFtQ2FjaGVLZXkpLEYuam9pbigpfSxnZXRVbmlmb3JtczpmdW5jdGlvbih3KXtsZXQgcSxGPXhbdy50eXBlXTtyZXR1cm4gcT1GP0o4ZS5jbG9uZShTZFtGXS51bmlmb3Jtcyk6dy51bmlmb3JtcyxxfSxhY3F1aXJlUHJvZ3JhbTpmdW5jdGlvbih3LEYpe2xldCBxO2ZvcihsZXQgSz0wLGRlPWMubGVuZ3RoO0s8ZGU7SysrKXtsZXQgWT1jW0tdO2lmKFkuY2FjaGVLZXk9PT1GKXtxPVksKytxLnVzZWRUaW1lczticmVha319cmV0dXJuIHZvaWQgMD09PXEmJihxPW5ldyBKN2UobixGLHcsbyksYy5wdXNoKHEpKSxxfSxyZWxlYXNlUHJvZ3JhbTpmdW5jdGlvbih3KXtpZigwPT0tLXcudXNlZFRpbWVzKXtsZXQgRj1jLmluZGV4T2Yodyk7Y1tGXT1jW2MubGVuZ3RoLTFdLGMucG9wKCksdy5kZXN0cm95KCl9fSxyZWxlYXNlU2hhZGVyQ2FjaGU6ZnVuY3Rpb24odyl7bC5yZW1vdmUodyl9LHByb2dyYW1zOmMsZGlzcG9zZTpmdW5jdGlvbigpe2wuZGlzcG9zZSgpfX19ZnVuY3Rpb24gdDllKCl7bGV0IG49bmV3IFdlYWtNYXA7cmV0dXJue2dldDpmdW5jdGlvbihvKXtsZXQgcz1uLmdldChvKTtyZXR1cm4gdm9pZCAwPT09cyYmKHM9e30sbi5zZXQobyxzKSksc30scmVtb3ZlOmZ1bmN0aW9uKG8pe24uZGVsZXRlKG8pfSx1cGRhdGU6ZnVuY3Rpb24obyxzLGEpe24uZ2V0KG8pW3NdPWF9LGRpc3Bvc2U6ZnVuY3Rpb24oKXtuPW5ldyBXZWFrTWFwfX19ZnVuY3Rpb24gbjllKG4sdCl7cmV0dXJuIG4uZ3JvdXBPcmRlciE9PXQuZ3JvdXBPcmRlcj9uLmdyb3VwT3JkZXItdC5ncm91cE9yZGVyOm4ucmVuZGVyT3JkZXIhPT10LnJlbmRlck9yZGVyP24ucmVuZGVyT3JkZXItdC5yZW5kZXJPcmRlcjpuLm1hdGVyaWFsLmlkIT09dC5tYXRlcmlhbC5pZD9uLm1hdGVyaWFsLmlkLXQubWF0ZXJpYWwuaWQ6bi56IT09dC56P24uei10Lno6bi5pZC10LmlkfWZ1bmN0aW9uICR1ZShuLHQpe3JldHVybiBuLmdyb3VwT3JkZXIhPT10Lmdyb3VwT3JkZXI/bi5ncm91cE9yZGVyLXQuZ3JvdXBPcmRlcjpuLnJlbmRlck9yZGVyIT09dC5yZW5kZXJPcmRlcj9uLnJlbmRlck9yZGVyLXQucmVuZGVyT3JkZXI6bi56IT09dC56P3Quei1uLno6bi5pZC10LmlkfWZ1bmN0aW9uIGVkZSgpe2xldCBuPVtdLHQ9MCxlPVtdLGk9W10scj1bXTtmdW5jdGlvbiBzKGQscCxoLGYsbSx4KXtsZXQgZz1uW3RdO3JldHVybiB2b2lkIDA9PT1nPyhnPXtpZDpkLmlkLG9iamVjdDpkLGdlb21ldHJ5OnAsbWF0ZXJpYWw6aCxncm91cE9yZGVyOmYscmVuZGVyT3JkZXI6ZC5yZW5kZXJPcmRlcix6Om0sZ3JvdXA6eH0sblt0XT1nKTooZy5pZD1kLmlkLGcub2JqZWN0PWQsZy5nZW9tZXRyeT1wLGcubWF0ZXJpYWw9aCxnLmdyb3VwT3JkZXI9ZixnLnJlbmRlck9yZGVyPWQucmVuZGVyT3JkZXIsZy56PW0sZy5ncm91cD14KSx0KyssZ31yZXR1cm57b3BhcXVlOmUsdHJhbnNtaXNzaXZlOmksdHJhbnNwYXJlbnQ6cixpbml0OmZ1bmN0aW9uKCl7dD0wLGUubGVuZ3RoPTAsaS5sZW5ndGg9MCxyLmxlbmd0aD0wfSxwdXNoOmZ1bmN0aW9uKGQscCxoLGYsbSx4KXtsZXQgZz1zKGQscCxoLGYsbSx4KTtoLnRyYW5zbWlzc2lvbj4wP2kucHVzaChnKTohMD09PWgudHJhbnNwYXJlbnQ/ci5wdXNoKGcpOmUucHVzaChnKX0sdW5zaGlmdDpmdW5jdGlvbihkLHAsaCxmLG0seCl7bGV0IGc9cyhkLHAsaCxmLG0seCk7aC50cmFuc21pc3Npb24+MD9pLnVuc2hpZnQoZyk6ITA9PT1oLnRyYW5zcGFyZW50P3IudW5zaGlmdChnKTplLnVuc2hpZnQoZyl9LGZpbmlzaDpmdW5jdGlvbigpe2ZvcihsZXQgZD10LHA9bi5sZW5ndGg7ZDxwO2QrKyl7bGV0IGg9bltkXTtpZihudWxsPT09aC5pZClicmVhaztoLmlkPW51bGwsaC5vYmplY3Q9bnVsbCxoLmdlb21ldHJ5PW51bGwsaC5tYXRlcmlhbD1udWxsLGguZ3JvdXA9bnVsbH19LHNvcnQ6ZnVuY3Rpb24oZCxwKXtlLmxlbmd0aD4xJiZlLnNvcnQoZHx8bjllKSxpLmxlbmd0aD4xJiZpLnNvcnQocHx8JHVlKSxyLmxlbmd0aD4xJiZyLnNvcnQocHx8JHVlKX19fWZ1bmN0aW9uIGk5ZSgpe2xldCBuPW5ldyBXZWFrTWFwO3JldHVybntnZXQ6ZnVuY3Rpb24oaSxyKXtsZXQgbztyZXR1cm4hMT09PW4uaGFzKGkpPyhvPW5ldyBlZGUsbi5zZXQoaSxbb10pKTpyPj1uLmdldChpKS5sZW5ndGg/KG89bmV3IGVkZSxuLmdldChpKS5wdXNoKG8pKTpvPW4uZ2V0KGkpW3JdLG99LGRpc3Bvc2U6ZnVuY3Rpb24oKXtuPW5ldyBXZWFrTWFwfX19ZnVuY3Rpb24gcjllKCl7bGV0IG49e307cmV0dXJue2dldDpmdW5jdGlvbih0KXtpZih2b2lkIDAhPT1uW3QuaWRdKXJldHVybiBuW3QuaWRdO2xldCBlO3N3aXRjaCh0LnR5cGUpe2Nhc2UiRGlyZWN0aW9uYWxMaWdodCI6ZT17ZGlyZWN0aW9uOm5ldyBpZSxjb2xvcjpuZXcgdm59O2JyZWFrO2Nhc2UiU3BvdExpZ2h0IjplPXtwb3NpdGlvbjpuZXcgaWUsZGlyZWN0aW9uOm5ldyBpZSxjb2xvcjpuZXcgdm4sZGlzdGFuY2U6MCxjb25lQ29zOjAscGVudW1icmFDb3M6MCxkZWNheTowfTticmVhaztjYXNlIlBvaW50TGlnaHQiOmU9e3Bvc2l0aW9uOm5ldyBpZSxjb2xvcjpuZXcgdm4sZGlzdGFuY2U6MCxkZWNheTowfTticmVhaztjYXNlIkhlbWlzcGhlcmVMaWdodCI6ZT17ZGlyZWN0aW9uOm5ldyBpZSxza3lDb2xvcjpuZXcgdm4sZ3JvdW5kQ29sb3I6bmV3IHZufTticmVhaztjYXNlIlJlY3RBcmVhTGlnaHQiOmU9e2NvbG9yOm5ldyB2bixwb3NpdGlvbjpuZXcgaWUsaGFsZldpZHRoOm5ldyBpZSxoYWxmSGVpZ2h0Om5ldyBpZX19cmV0dXJuIG5bdC5pZF09ZSxlfX19dmFyIHM5ZT0wO2Z1bmN0aW9uIGE5ZShuLHQpe3JldHVybih0LmNhc3RTaGFkb3c/MTowKS0obi5jYXN0U2hhZG93PzE6MCl9ZnVuY3Rpb24gbDllKG4sdCl7bGV0IGU9bmV3IHI5ZSxpPWZ1bmN0aW9uKCl7bGV0IG49e307cmV0dXJue2dldDpmdW5jdGlvbih0KXtpZih2b2lkIDAhPT1uW3QuaWRdKXJldHVybiBuW3QuaWRdO2xldCBlO3N3aXRjaCh0LnR5cGUpe2Nhc2UiRGlyZWN0aW9uYWxMaWdodCI6Y2FzZSJTcG90TGlnaHQiOmU9e3NoYWRvd0JpYXM6MCxzaGFkb3dOb3JtYWxCaWFzOjAsc2hhZG93UmFkaXVzOjEsc2hhZG93TWFwU2l6ZTpuZXcgYXR9O2JyZWFrO2Nhc2UiUG9pbnRMaWdodCI6ZT17c2hhZG93QmlhczowLHNoYWRvd05vcm1hbEJpYXM6MCxzaGFkb3dSYWRpdXM6MSxzaGFkb3dNYXBTaXplOm5ldyBhdCxzaGFkb3dDYW1lcmFOZWFyOjEsc2hhZG93Q2FtZXJhRmFyOjFlM319cmV0dXJuIG5bdC5pZF09ZSxlfX19KCkscj17dmVyc2lvbjowLGhhc2g6e2RpcmVjdGlvbmFsTGVuZ3RoOi0xLHBvaW50TGVuZ3RoOi0xLHNwb3RMZW5ndGg6LTEscmVjdEFyZWFMZW5ndGg6LTEsaGVtaUxlbmd0aDotMSxudW1EaXJlY3Rpb25hbFNoYWRvd3M6LTEsbnVtUG9pbnRTaGFkb3dzOi0xLG51bVNwb3RTaGFkb3dzOi0xfSxhbWJpZW50OlswLDAsMF0scHJvYmU6W10sZGlyZWN0aW9uYWw6W10sZGlyZWN0aW9uYWxTaGFkb3c6W10sZGlyZWN0aW9uYWxTaGFkb3dNYXA6W10sZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXg6W10sc3BvdDpbXSxzcG90U2hhZG93OltdLHNwb3RTaGFkb3dNYXA6W10sc3BvdFNoYWRvd01hdHJpeDpbXSxyZWN0QXJlYTpbXSxyZWN0QXJlYUxUQzE6bnVsbCxyZWN0QXJlYUxUQzI6bnVsbCxwb2ludDpbXSxwb2ludFNoYWRvdzpbXSxwb2ludFNoYWRvd01hcDpbXSxwb2ludFNoYWRvd01hdHJpeDpbXSxoZW1pOltdfTtmb3IobGV0IHU9MDt1PDk7dSsrKXIucHJvYmUucHVzaChuZXcgaWUpO2xldCBvPW5ldyBpZSxzPW5ldyBSbixhPW5ldyBSbjtyZXR1cm57c2V0dXA6ZnVuY3Rpb24odSxkKXtsZXQgcD0wLGg9MCxmPTA7Zm9yKGxldCB1ZT0wO3VlPDk7dWUrKylyLnByb2JlW3VlXS5zZXQoMCwwLDApO2xldCBtPTAseD0wLGc9MCxiPTAsRD0wLFQ9MCxrPTAsWj0wO3Uuc29ydChhOWUpO2xldCB6PSEwIT09ZD9NYXRoLlBJOjE7Zm9yKGxldCB1ZT0wLGhlPXUubGVuZ3RoO3VlPGhlO3VlKyspe2xldCB3PXVbdWVdLEY9dy5jb2xvcixxPXcuaW50ZW5zaXR5LEs9dy5kaXN0YW5jZSxkZT13LnNoYWRvdyYmdy5zaGFkb3cubWFwP3cuc2hhZG93Lm1hcC50ZXh0dXJlOm51bGw7aWYody5pc0FtYmllbnRMaWdodClwKz1GLnIqcSp6LGgrPUYuZypxKnosZis9Ri5iKnEqejtlbHNlIGlmKHcuaXNMaWdodFByb2JlKWZvcihsZXQgWT0wO1k8OTtZKyspci5wcm9iZVtZXS5hZGRTY2FsZWRWZWN0b3Iody5zaC5jb2VmZmljaWVudHNbWV0scSk7ZWxzZSBpZih3LmlzRGlyZWN0aW9uYWxMaWdodCl7bGV0IFk9ZS5nZXQodyk7aWYoWS5jb2xvci5jb3B5KHcuY29sb3IpLm11bHRpcGx5U2NhbGFyKHcuaW50ZW5zaXR5KnopLHcuY2FzdFNoYWRvdyl7bGV0IGFlPXcuc2hhZG93LGxlPWkuZ2V0KHcpO2xlLnNoYWRvd0JpYXM9YWUuYmlhcyxsZS5zaGFkb3dOb3JtYWxCaWFzPWFlLm5vcm1hbEJpYXMsbGUuc2hhZG93UmFkaXVzPWFlLnJhZGl1cyxsZS5zaGFkb3dNYXBTaXplPWFlLm1hcFNpemUsci5kaXJlY3Rpb25hbFNoYWRvd1ttXT1sZSxyLmRpcmVjdGlvbmFsU2hhZG93TWFwW21dPWRlLHIuZGlyZWN0aW9uYWxTaGFkb3dNYXRyaXhbbV09dy5zaGFkb3cubWF0cml4LFQrK31yLmRpcmVjdGlvbmFsW21dPVksbSsrfWVsc2UgaWYody5pc1Nwb3RMaWdodCl7bGV0IFk9ZS5nZXQodyk7aWYoWS5wb3NpdGlvbi5zZXRGcm9tTWF0cml4UG9zaXRpb24ody5tYXRyaXhXb3JsZCksWS5jb2xvci5jb3B5KEYpLm11bHRpcGx5U2NhbGFyKHEqeiksWS5kaXN0YW5jZT1LLFkuY29uZUNvcz1NYXRoLmNvcyh3LmFuZ2xlKSxZLnBlbnVtYnJhQ29zPU1hdGguY29zKHcuYW5nbGUqKDEtdy5wZW51bWJyYSkpLFkuZGVjYXk9dy5kZWNheSx3LmNhc3RTaGFkb3cpe2xldCBhZT13LnNoYWRvdyxsZT1pLmdldCh3KTtsZS5zaGFkb3dCaWFzPWFlLmJpYXMsbGUuc2hhZG93Tm9ybWFsQmlhcz1hZS5ub3JtYWxCaWFzLGxlLnNoYWRvd1JhZGl1cz1hZS5yYWRpdXMsbGUuc2hhZG93TWFwU2l6ZT1hZS5tYXBTaXplLHIuc3BvdFNoYWRvd1tnXT1sZSxyLnNwb3RTaGFkb3dNYXBbZ109ZGUsci5zcG90U2hhZG93TWF0cml4W2ddPXcuc2hhZG93Lm1hdHJpeCxaKyt9ci5zcG90W2ddPVksZysrfWVsc2UgaWYody5pc1JlY3RBcmVhTGlnaHQpe2xldCBZPWUuZ2V0KHcpO1kuY29sb3IuY29weShGKS5tdWx0aXBseVNjYWxhcihxKSxZLmhhbGZXaWR0aC5zZXQoLjUqdy53aWR0aCwwLDApLFkuaGFsZkhlaWdodC5zZXQoMCwuNSp3LmhlaWdodCwwKSxyLnJlY3RBcmVhW2JdPVksYisrfWVsc2UgaWYody5pc1BvaW50TGlnaHQpe2xldCBZPWUuZ2V0KHcpO2lmKFkuY29sb3IuY29weSh3LmNvbG9yKS5tdWx0aXBseVNjYWxhcih3LmludGVuc2l0eSp6KSxZLmRpc3RhbmNlPXcuZGlzdGFuY2UsWS5kZWNheT13LmRlY2F5LHcuY2FzdFNoYWRvdyl7bGV0IGFlPXcuc2hhZG93LGxlPWkuZ2V0KHcpO2xlLnNoYWRvd0JpYXM9YWUuYmlhcyxsZS5zaGFkb3dOb3JtYWxCaWFzPWFlLm5vcm1hbEJpYXMsbGUuc2hhZG93UmFkaXVzPWFlLnJhZGl1cyxsZS5zaGFkb3dNYXBTaXplPWFlLm1hcFNpemUsbGUuc2hhZG93Q2FtZXJhTmVhcj1hZS5jYW1lcmEubmVhcixsZS5zaGFkb3dDYW1lcmFGYXI9YWUuY2FtZXJhLmZhcixyLnBvaW50U2hhZG93W3hdPWxlLHIucG9pbnRTaGFkb3dNYXBbeF09ZGUsci5wb2ludFNoYWRvd01hdHJpeFt4XT13LnNoYWRvdy5tYXRyaXgsaysrfXIucG9pbnRbeF09WSx4Kyt9ZWxzZSBpZih3LmlzSGVtaXNwaGVyZUxpZ2h0KXtsZXQgWT1lLmdldCh3KTtZLnNreUNvbG9yLmNvcHkody5jb2xvcikubXVsdGlwbHlTY2FsYXIocSp6KSxZLmdyb3VuZENvbG9yLmNvcHkody5ncm91bmRDb2xvcikubXVsdGlwbHlTY2FsYXIocSp6KSxyLmhlbWlbRF09WSxEKyt9fWI+MCYmKHQuaXNXZWJHTDJ8fCEwPT09bi5oYXMoIk9FU190ZXh0dXJlX2Zsb2F0X2xpbmVhciIpPyhyLnJlY3RBcmVhTFRDMT1CdC5MVENfRkxPQVRfMSxyLnJlY3RBcmVhTFRDMj1CdC5MVENfRkxPQVRfMik6ITA9PT1uLmhhcygiT0VTX3RleHR1cmVfaGFsZl9mbG9hdF9saW5lYXIiKT8oci5yZWN0QXJlYUxUQzE9QnQuTFRDX0hBTEZfMSxyLnJlY3RBcmVhTFRDMj1CdC5MVENfSEFMRl8yKTpjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBVbmFibGUgdG8gdXNlIFJlY3RBcmVhTGlnaHQuIE1pc3NpbmcgV2ViR0wgZXh0ZW5zaW9ucy4iKSksci5hbWJpZW50WzBdPXAsci5hbWJpZW50WzFdPWgsci5hbWJpZW50WzJdPWY7bGV0IGZlPXIuaGFzaDsoZmUuZGlyZWN0aW9uYWxMZW5ndGghPT1tfHxmZS5wb2ludExlbmd0aCE9PXh8fGZlLnNwb3RMZW5ndGghPT1nfHxmZS5yZWN0QXJlYUxlbmd0aCE9PWJ8fGZlLmhlbWlMZW5ndGghPT1EfHxmZS5udW1EaXJlY3Rpb25hbFNoYWRvd3MhPT1UfHxmZS5udW1Qb2ludFNoYWRvd3MhPT1rfHxmZS5udW1TcG90U2hhZG93cyE9PVopJiYoci5kaXJlY3Rpb25hbC5sZW5ndGg9bSxyLnNwb3QubGVuZ3RoPWcsci5yZWN0QXJlYS5sZW5ndGg9YixyLnBvaW50Lmxlbmd0aD14LHIuaGVtaS5sZW5ndGg9RCxyLmRpcmVjdGlvbmFsU2hhZG93Lmxlbmd0aD1ULHIuZGlyZWN0aW9uYWxTaGFkb3dNYXAubGVuZ3RoPVQsci5wb2ludFNoYWRvdy5sZW5ndGg9ayxyLnBvaW50U2hhZG93TWFwLmxlbmd0aD1rLHIuc3BvdFNoYWRvdy5sZW5ndGg9WixyLnNwb3RTaGFkb3dNYXAubGVuZ3RoPVosci5kaXJlY3Rpb25hbFNoYWRvd01hdHJpeC5sZW5ndGg9VCxyLnBvaW50U2hhZG93TWF0cml4Lmxlbmd0aD1rLHIuc3BvdFNoYWRvd01hdHJpeC5sZW5ndGg9WixmZS5kaXJlY3Rpb25hbExlbmd0aD1tLGZlLnBvaW50TGVuZ3RoPXgsZmUuc3BvdExlbmd0aD1nLGZlLnJlY3RBcmVhTGVuZ3RoPWIsZmUuaGVtaUxlbmd0aD1ELGZlLm51bURpcmVjdGlvbmFsU2hhZG93cz1ULGZlLm51bVBvaW50U2hhZG93cz1rLGZlLm51bVNwb3RTaGFkb3dzPVosci52ZXJzaW9uPXM5ZSsrKX0sc2V0dXBWaWV3OmZ1bmN0aW9uKHUsZCl7bGV0IHA9MCxoPTAsZj0wLG09MCx4PTAsZz1kLm1hdHJpeFdvcmxkSW52ZXJzZTtmb3IobGV0IGI9MCxEPXUubGVuZ3RoO2I8RDtiKyspe2xldCBUPXVbYl07aWYoVC5pc0RpcmVjdGlvbmFsTGlnaHQpe2xldCBrPXIuZGlyZWN0aW9uYWxbcF07ay5kaXJlY3Rpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKFQubWF0cml4V29ybGQpLG8uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKFQudGFyZ2V0Lm1hdHJpeFdvcmxkKSxrLmRpcmVjdGlvbi5zdWIobyksay5kaXJlY3Rpb24udHJhbnNmb3JtRGlyZWN0aW9uKGcpLHArK31lbHNlIGlmKFQuaXNTcG90TGlnaHQpe2xldCBrPXIuc3BvdFtmXTtrLnBvc2l0aW9uLnNldEZyb21NYXRyaXhQb3NpdGlvbihULm1hdHJpeFdvcmxkKSxrLnBvc2l0aW9uLmFwcGx5TWF0cml4NChnKSxrLmRpcmVjdGlvbi5zZXRGcm9tTWF0cml4UG9zaXRpb24oVC5tYXRyaXhXb3JsZCksby5zZXRGcm9tTWF0cml4UG9zaXRpb24oVC50YXJnZXQubWF0cml4V29ybGQpLGsuZGlyZWN0aW9uLnN1YihvKSxrLmRpcmVjdGlvbi50cmFuc2Zvcm1EaXJlY3Rpb24oZyksZisrfWVsc2UgaWYoVC5pc1JlY3RBcmVhTGlnaHQpe2xldCBrPXIucmVjdEFyZWFbbV07ay5wb3NpdGlvbi5zZXRGcm9tTWF0cml4UG9zaXRpb24oVC5tYXRyaXhXb3JsZCksay5wb3NpdGlvbi5hcHBseU1hdHJpeDQoZyksYS5pZGVudGl0eSgpLHMuY29weShULm1hdHJpeFdvcmxkKSxzLnByZW11bHRpcGx5KGcpLGEuZXh0cmFjdFJvdGF0aW9uKHMpLGsuaGFsZldpZHRoLnNldCguNSpULndpZHRoLDAsMCksay5oYWxmSGVpZ2h0LnNldCgwLC41KlQuaGVpZ2h0LDApLGsuaGFsZldpZHRoLmFwcGx5TWF0cml4NChhKSxrLmhhbGZIZWlnaHQuYXBwbHlNYXRyaXg0KGEpLG0rK31lbHNlIGlmKFQuaXNQb2ludExpZ2h0KXtsZXQgaz1yLnBvaW50W2hdO2sucG9zaXRpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKFQubWF0cml4V29ybGQpLGsucG9zaXRpb24uYXBwbHlNYXRyaXg0KGcpLGgrK31lbHNlIGlmKFQuaXNIZW1pc3BoZXJlTGlnaHQpe2xldCBrPXIuaGVtaVt4XTtrLmRpcmVjdGlvbi5zZXRGcm9tTWF0cml4UG9zaXRpb24oVC5tYXRyaXhXb3JsZCksay5kaXJlY3Rpb24udHJhbnNmb3JtRGlyZWN0aW9uKGcpLGsuZGlyZWN0aW9uLm5vcm1hbGl6ZSgpLHgrK319fSxzdGF0ZTpyfX1mdW5jdGlvbiB0ZGUobix0KXtsZXQgZT1uZXcgbDllKG4sdCksaT1bXSxyPVtdO3JldHVybntpbml0OmZ1bmN0aW9uKCl7aS5sZW5ndGg9MCxyLmxlbmd0aD0wfSxzdGF0ZTp7bGlnaHRzQXJyYXk6aSxzaGFkb3dzQXJyYXk6cixsaWdodHM6ZX0sc2V0dXBMaWdodHM6ZnVuY3Rpb24oZCl7ZS5zZXR1cChpLGQpfSxzZXR1cExpZ2h0c1ZpZXc6ZnVuY3Rpb24oZCl7ZS5zZXR1cFZpZXcoaSxkKX0scHVzaExpZ2h0OmZ1bmN0aW9uKGQpe2kucHVzaChkKX0scHVzaFNoYWRvdzpmdW5jdGlvbihkKXtyLnB1c2goZCl9fX1mdW5jdGlvbiBjOWUobix0KXtsZXQgZT1uZXcgV2Vha01hcDtyZXR1cm57Z2V0OmZ1bmN0aW9uKG8scz0wKXtsZXQgYTtyZXR1cm4hMT09PWUuaGFzKG8pPyhhPW5ldyB0ZGUobix0KSxlLnNldChvLFthXSkpOnM+PWUuZ2V0KG8pLmxlbmd0aD8oYT1uZXcgdGRlKG4sdCksZS5nZXQobykucHVzaChhKSk6YT1lLmdldChvKVtzXSxhfSxkaXNwb3NlOmZ1bmN0aW9uKCl7ZT1uZXcgV2Vha01hcH19fXZhciBwaz1jbGFzcyBleHRlbmRzIGhze2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJNZXNoRGVwdGhNYXRlcmlhbCIsdGhpcy5kZXB0aFBhY2tpbmc9MzIwMCx0aGlzLm1hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLndpcmVmcmFtZT0hMSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD0xLHRoaXMuZm9nPSExLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5kZXB0aFBhY2tpbmc9dC5kZXB0aFBhY2tpbmcsdGhpcy5tYXA9dC5tYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZGlzcGxhY2VtZW50TWFwPXQuZGlzcGxhY2VtZW50TWFwLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9dC5kaXNwbGFjZW1lbnRTY2FsZSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9dC5kaXNwbGFjZW1lbnRCaWFzLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXN9fTtway5wcm90b3R5cGUuaXNNZXNoRGVwdGhNYXRlcmlhbD0hMDt2YXIgaGs9Y2xhc3MgZXh0ZW5kcyBoc3tjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaERpc3RhbmNlTWF0ZXJpYWwiLHRoaXMucmVmZXJlbmNlUG9zaXRpb249bmV3IGllLHRoaXMubmVhckRpc3RhbmNlPTEsdGhpcy5mYXJEaXN0YW5jZT0xZTMsdGhpcy5tYXA9bnVsbCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy5kaXNwbGFjZW1lbnRNYXA9bnVsbCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPTEsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPTAsdGhpcy5mb2c9ITEsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLnJlZmVyZW5jZVBvc2l0aW9uLmNvcHkodC5yZWZlcmVuY2VQb3NpdGlvbiksdGhpcy5uZWFyRGlzdGFuY2U9dC5uZWFyRGlzdGFuY2UsdGhpcy5mYXJEaXN0YW5jZT10LmZhckRpc3RhbmNlLHRoaXMubWFwPXQubWFwLHRoaXMuYWxwaGFNYXA9dC5hbHBoYU1hcCx0aGlzLmRpc3BsYWNlbWVudE1hcD10LmRpc3BsYWNlbWVudE1hcCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPXQuZGlzcGxhY2VtZW50U2NhbGUsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPXQuZGlzcGxhY2VtZW50Qmlhcyx0aGlzfX07ZnVuY3Rpb24gR2RlKG4sdCxlKXtsZXQgaT1uZXcgZ2Iscj1uZXcgYXQsbz1uZXcgYXQscz1uZXcgYXIsYT1uZXcgcGsoe2RlcHRoUGFja2luZzozMjAxfSksbD1uZXcgaGssYz17fSx1PWUubWF4VGV4dHVyZVNpemUsZD17MDoxLDE6MCwyOjJ9LHA9bmV3IERwKHtkZWZpbmVzOntWU01fU0FNUExFUzo4fSx1bmlmb3Jtczp7c2hhZG93X3Bhc3M6e3ZhbHVlOm51bGx9LHJlc29sdXRpb246e3ZhbHVlOm5ldyBhdH0scmFkaXVzOnt2YWx1ZTo0fX0sdmVydGV4U2hhZGVyOiJ2b2lkIG1haW4oKSB7XG5cdGdsX1Bvc2l0aW9uID0gdmVjNCggcG9zaXRpb24sIDEuMCApO1xufSIsZnJhZ21lbnRTaGFkZXI6InVuaWZvcm0gc2FtcGxlcjJEIHNoYWRvd19wYXNzO1xudW5pZm9ybSB2ZWMyIHJlc29sdXRpb247XG51bmlmb3JtIGZsb2F0IHJhZGl1cztcbiNpbmNsdWRlIDxwYWNraW5nPlxudm9pZCBtYWluKCkge1xuXHRjb25zdCBmbG9hdCBzYW1wbGVzID0gZmxvYXQoIFZTTV9TQU1QTEVTICk7XG5cdGZsb2F0IG1lYW4gPSAwLjA7XG5cdGZsb2F0IHNxdWFyZWRfbWVhbiA9IDAuMDtcblx0ZmxvYXQgdXZTdHJpZGUgPSBzYW1wbGVzIDw9IDEuMCA/IDAuMCA6IDIuMCAvICggc2FtcGxlcyAtIDEuMCApO1xuXHRmbG9hdCB1dlN0YXJ0ID0gc2FtcGxlcyA8PSAxLjAgPyAwLjAgOiAtIDEuMDtcblx0Zm9yICggZmxvYXQgaSA9IDAuMDsgaSA8IHNhbXBsZXM7IGkgKysgKSB7XG5cdFx0ZmxvYXQgdXZPZmZzZXQgPSB1dlN0YXJ0ICsgaSAqIHV2U3RyaWRlO1xuXHRcdCNpZmRlZiBIT1JJWk9OVEFMX1BBU1Ncblx0XHRcdHZlYzIgZGlzdHJpYnV0aW9uID0gdW5wYWNrUkdCQVRvMkhhbGYoIHRleHR1cmUyRCggc2hhZG93X3Bhc3MsICggZ2xfRnJhZ0Nvb3JkLnh5ICsgdmVjMiggdXZPZmZzZXQsIDAuMCApICogcmFkaXVzICkgLyByZXNvbHV0aW9uICkgKTtcblx0XHRcdG1lYW4gKz0gZGlzdHJpYnV0aW9uLng7XG5cdFx0XHRzcXVhcmVkX21lYW4gKz0gZGlzdHJpYnV0aW9uLnkgKiBkaXN0cmlidXRpb24ueSArIGRpc3RyaWJ1dGlvbi54ICogZGlzdHJpYnV0aW9uLng7XG5cdFx0I2Vsc2Vcblx0XHRcdGZsb2F0IGRlcHRoID0gdW5wYWNrUkdCQVRvRGVwdGgoIHRleHR1cmUyRCggc2hhZG93X3Bhc3MsICggZ2xfRnJhZ0Nvb3JkLnh5ICsgdmVjMiggMC4wLCB1dk9mZnNldCApICogcmFkaXVzICkgLyByZXNvbHV0aW9uICkgKTtcblx0XHRcdG1lYW4gKz0gZGVwdGg7XG5cdFx0XHRzcXVhcmVkX21lYW4gKz0gZGVwdGggKiBkZXB0aDtcblx0XHQjZW5kaWZcblx0fVxuXHRtZWFuID0gbWVhbiAvIHNhbXBsZXM7XG5cdHNxdWFyZWRfbWVhbiA9IHNxdWFyZWRfbWVhbiAvIHNhbXBsZXM7XG5cdGZsb2F0IHN0ZF9kZXYgPSBzcXJ0KCBzcXVhcmVkX21lYW4gLSBtZWFuICogbWVhbiApO1xuXHRnbF9GcmFnQ29sb3IgPSBwYWNrMkhhbGZUb1JHQkEoIHZlYzIoIG1lYW4sIHN0ZF9kZXYgKSApO1xufSJ9KSxoPXAuY2xvbmUoKTtoLmRlZmluZXMuSE9SSVpPTlRBTF9QQVNTPTE7bGV0IGY9bmV3IG5yO2Yuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IFlyKG5ldyBGbG9hdDMyQXJyYXkoWy0xLC0xLC41LDMsLTEsLjUsLTEsMywuNV0pLDMpKTtsZXQgbT1uZXcgVm8oZixwKSx4PXRoaXM7ZnVuY3Rpb24gZyhULGspe2xldCBaPXQudXBkYXRlKG0pO3AuZGVmaW5lcy5WU01fU0FNUExFUyE9PVQuYmx1clNhbXBsZXMmJihwLmRlZmluZXMuVlNNX1NBTVBMRVM9VC5ibHVyU2FtcGxlcyxoLmRlZmluZXMuVlNNX1NBTVBMRVM9VC5ibHVyU2FtcGxlcyxwLm5lZWRzVXBkYXRlPSEwLGgubmVlZHNVcGRhdGU9ITApLHAudW5pZm9ybXMuc2hhZG93X3Bhc3MudmFsdWU9VC5tYXAudGV4dHVyZSxwLnVuaWZvcm1zLnJlc29sdXRpb24udmFsdWU9VC5tYXBTaXplLHAudW5pZm9ybXMucmFkaXVzLnZhbHVlPVQucmFkaXVzLG4uc2V0UmVuZGVyVGFyZ2V0KFQubWFwUGFzcyksbi5jbGVhcigpLG4ucmVuZGVyQnVmZmVyRGlyZWN0KGssbnVsbCxaLHAsbSxudWxsKSxoLnVuaWZvcm1zLnNoYWRvd19wYXNzLnZhbHVlPVQubWFwUGFzcy50ZXh0dXJlLGgudW5pZm9ybXMucmVzb2x1dGlvbi52YWx1ZT1ULm1hcFNpemUsaC51bmlmb3Jtcy5yYWRpdXMudmFsdWU9VC5yYWRpdXMsbi5zZXRSZW5kZXJUYXJnZXQoVC5tYXApLG4uY2xlYXIoKSxuLnJlbmRlckJ1ZmZlckRpcmVjdChrLG51bGwsWixoLG0sbnVsbCl9ZnVuY3Rpb24gYihULGssWix6LGZlLHVlLGhlKXtsZXQgdz1udWxsLEY9ITA9PT16LmlzUG9pbnRMaWdodD9ULmN1c3RvbURpc3RhbmNlTWF0ZXJpYWw6VC5jdXN0b21EZXB0aE1hdGVyaWFsO2lmKHc9dm9pZCAwIT09Rj9GOiEwPT09ei5pc1BvaW50TGlnaHQ/bDphLG4ubG9jYWxDbGlwcGluZ0VuYWJsZWQmJiEwPT09Wi5jbGlwU2hhZG93cyYmMCE9PVouY2xpcHBpbmdQbGFuZXMubGVuZ3RofHxaLmRpc3BsYWNlbWVudE1hcCYmMCE9PVouZGlzcGxhY2VtZW50U2NhbGV8fFouYWxwaGFNYXAmJlouYWxwaGFUZXN0PjApe2xldCBxPXcudXVpZCxLPVoudXVpZCxkZT1jW3FdO3ZvaWQgMD09PWRlJiYoZGU9e30sY1txXT1kZSk7bGV0IFk9ZGVbS107dm9pZCAwPT09WSYmKFk9dy5jbG9uZSgpLGRlW0tdPVkpLHc9WX1yZXR1cm4gdy52aXNpYmxlPVoudmlzaWJsZSx3LndpcmVmcmFtZT1aLndpcmVmcmFtZSx3LnNpZGU9Mz09PWhlP251bGwhPT1aLnNoYWRvd1NpZGU/Wi5zaGFkb3dTaWRlOlouc2lkZTpudWxsIT09Wi5zaGFkb3dTaWRlP1ouc2hhZG93U2lkZTpkW1ouc2lkZV0sdy5hbHBoYU1hcD1aLmFscGhhTWFwLHcuYWxwaGFUZXN0PVouYWxwaGFUZXN0LHcuY2xpcFNoYWRvd3M9Wi5jbGlwU2hhZG93cyx3LmNsaXBwaW5nUGxhbmVzPVouY2xpcHBpbmdQbGFuZXMsdy5jbGlwSW50ZXJzZWN0aW9uPVouY2xpcEludGVyc2VjdGlvbix3LmRpc3BsYWNlbWVudE1hcD1aLmRpc3BsYWNlbWVudE1hcCx3LmRpc3BsYWNlbWVudFNjYWxlPVouZGlzcGxhY2VtZW50U2NhbGUsdy5kaXNwbGFjZW1lbnRCaWFzPVouZGlzcGxhY2VtZW50Qmlhcyx3LndpcmVmcmFtZUxpbmV3aWR0aD1aLndpcmVmcmFtZUxpbmV3aWR0aCx3LmxpbmV3aWR0aD1aLmxpbmV3aWR0aCwhMD09PXouaXNQb2ludExpZ2h0JiYhMD09PXcuaXNNZXNoRGlzdGFuY2VNYXRlcmlhbCYmKHcucmVmZXJlbmNlUG9zaXRpb24uc2V0RnJvbU1hdHJpeFBvc2l0aW9uKHoubWF0cml4V29ybGQpLHcubmVhckRpc3RhbmNlPWZlLHcuZmFyRGlzdGFuY2U9dWUpLHd9ZnVuY3Rpb24gRChULGssWix6LGZlKXtpZighMT09PVQudmlzaWJsZSlyZXR1cm47aWYoVC5sYXllcnMudGVzdChrLmxheWVycykmJihULmlzTWVzaHx8VC5pc0xpbmV8fFQuaXNQb2ludHMpJiYoVC5jYXN0U2hhZG93fHxULnJlY2VpdmVTaGFkb3cmJjM9PT1mZSkmJighVC5mcnVzdHVtQ3VsbGVkfHxpLmludGVyc2VjdHNPYmplY3QoVCkpKXtULm1vZGVsVmlld01hdHJpeC5tdWx0aXBseU1hdHJpY2VzKFoubWF0cml4V29ybGRJbnZlcnNlLFQubWF0cml4V29ybGQpO2xldCB3PXQudXBkYXRlKFQpLEY9VC5tYXRlcmlhbDtpZihBcnJheS5pc0FycmF5KEYpKXtsZXQgcT13Lmdyb3Vwcztmb3IobGV0IEs9MCxkZT1xLmxlbmd0aDtLPGRlO0srKyl7bGV0IFk9cVtLXSxhZT1GW1kubWF0ZXJpYWxJbmRleF07aWYoYWUmJmFlLnZpc2libGUpe2xldCBsZT1iKFQsMCxhZSx6LFoubmVhcixaLmZhcixmZSk7bi5yZW5kZXJCdWZmZXJEaXJlY3QoWixudWxsLHcsbGUsVCxZKX19fWVsc2UgaWYoRi52aXNpYmxlKXtsZXQgcT1iKFQsMCxGLHosWi5uZWFyLFouZmFyLGZlKTtuLnJlbmRlckJ1ZmZlckRpcmVjdChaLG51bGwsdyxxLFQsbnVsbCl9fWxldCBoZT1ULmNoaWxkcmVuO2ZvcihsZXQgdz0wLEY9aGUubGVuZ3RoO3c8Rjt3KyspRChoZVt3XSxrLFoseixmZSl9dGhpcy5lbmFibGVkPSExLHRoaXMuYXV0b1VwZGF0ZT0hMCx0aGlzLm5lZWRzVXBkYXRlPSExLHRoaXMudHlwZT0xLHRoaXMucmVuZGVyPWZ1bmN0aW9uKFQsayxaKXtpZighMT09PXguZW5hYmxlZHx8ITE9PT14LmF1dG9VcGRhdGUmJiExPT09eC5uZWVkc1VwZGF0ZXx8MD09PVQubGVuZ3RoKXJldHVybjtsZXQgej1uLmdldFJlbmRlclRhcmdldCgpLGZlPW4uZ2V0QWN0aXZlQ3ViZUZhY2UoKSx1ZT1uLmdldEFjdGl2ZU1pcG1hcExldmVsKCksaGU9bi5zdGF0ZTtoZS5zZXRCbGVuZGluZygwKSxoZS5idWZmZXJzLmNvbG9yLnNldENsZWFyKDEsMSwxLDEpLGhlLmJ1ZmZlcnMuZGVwdGguc2V0VGVzdCghMCksaGUuc2V0U2Npc3NvclRlc3QoITEpO2ZvcihsZXQgdz0wLEY9VC5sZW5ndGg7dzxGO3crKyl7bGV0IHE9VFt3XSxLPXEuc2hhZG93O2lmKHZvaWQgMD09PUspe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xTaGFkb3dNYXA6IixxLCJoYXMgbm8gc2hhZG93LiIpO2NvbnRpbnVlfWlmKCExPT09Sy5hdXRvVXBkYXRlJiYhMT09PUsubmVlZHNVcGRhdGUpY29udGludWU7ci5jb3B5KEsubWFwU2l6ZSk7bGV0IGRlPUsuZ2V0RnJhbWVFeHRlbnRzKCk7aWYoci5tdWx0aXBseShkZSksby5jb3B5KEsubWFwU2l6ZSksKHIueD51fHxyLnk+dSkmJihyLng+dSYmKG8ueD1NYXRoLmZsb29yKHUvZGUueCksci54PW8ueCpkZS54LEsubWFwU2l6ZS54PW8ueCksci55PnUmJihvLnk9TWF0aC5mbG9vcih1L2RlLnkpLHIueT1vLnkqZGUueSxLLm1hcFNpemUueT1vLnkpKSxudWxsPT09Sy5tYXAmJiFLLmlzUG9pbnRMaWdodFNoYWRvdyYmMz09PXRoaXMudHlwZSl7bGV0IGFlPXttaW5GaWx0ZXI6R3MsbWFnRmlsdGVyOkdzLGZvcm1hdDpnYX07Sy5tYXA9bmV3IFdhKHIueCxyLnksYWUpLEsubWFwLnRleHR1cmUubmFtZT1xLm5hbWUrIi5zaGFkb3dNYXAiLEsubWFwUGFzcz1uZXcgV2Eoci54LHIueSxhZSksSy5jYW1lcmEudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpfW51bGw9PT1LLm1hcCYmKEsubWFwPW5ldyBXYShyLngsci55LHttaW5GaWx0ZXI6Wm8sbWFnRmlsdGVyOlpvLGZvcm1hdDpnYX0pLEsubWFwLnRleHR1cmUubmFtZT1xLm5hbWUrIi5zaGFkb3dNYXAiLEsuY2FtZXJhLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKSksbi5zZXRSZW5kZXJUYXJnZXQoSy5tYXApLG4uY2xlYXIoKTtsZXQgWT1LLmdldFZpZXdwb3J0Q291bnQoKTtmb3IobGV0IGFlPTA7YWU8WTthZSsrKXtsZXQgbGU9Sy5nZXRWaWV3cG9ydChhZSk7cy5zZXQoby54KmxlLngsby55KmxlLnksby54KmxlLnosby55KmxlLncpLGhlLnZpZXdwb3J0KHMpLEsudXBkYXRlTWF0cmljZXMocSxhZSksaT1LLmdldEZydXN0dW0oKSxEKGssWixLLmNhbWVyYSxxLHRoaXMudHlwZSl9IUsuaXNQb2ludExpZ2h0U2hhZG93JiYzPT09dGhpcy50eXBlJiZnKEssWiksSy5uZWVkc1VwZGF0ZT0hMX14Lm5lZWRzVXBkYXRlPSExLG4uc2V0UmVuZGVyVGFyZ2V0KHosZmUsdWUpfX1mdW5jdGlvbiBwOWUobix0LGUpe2xldCBpPWUuaXNXZWJHTDIsYT1uZXcgZnVuY3Rpb24oKXtsZXQgZ2U9ITEsZm49bmV3IGFyLFp0PW51bGwsTm49bmV3IGFyKDAsMCwwLDApO3JldHVybntzZXRNYXNrOmZ1bmN0aW9uKFplKXtadCE9PVplJiYhZ2UmJihuLmNvbG9yTWFzayhaZSxaZSxaZSxaZSksWnQ9WmUpfSxzZXRMb2NrZWQ6ZnVuY3Rpb24oWmUpe2dlPVplfSxzZXRDbGVhcjpmdW5jdGlvbihaZSxEbixNaSxUcixFcyl7ITA9PT1FcyYmKFplKj1UcixEbio9VHIsTWkqPVRyKSxmbi5zZXQoWmUsRG4sTWksVHIpLCExPT09Tm4uZXF1YWxzKGZuKSYmKG4uY2xlYXJDb2xvcihaZSxEbixNaSxUciksTm4uY29weShmbikpfSxyZXNldDpmdW5jdGlvbigpe2dlPSExLFp0PW51bGwsTm4uc2V0KC0xLDAsMCwwKX19fSxsPW5ldyBmdW5jdGlvbigpe2xldCBnZT0hMSxmbj1udWxsLFp0PW51bGwsTm49bnVsbDtyZXR1cm57c2V0VGVzdDpmdW5jdGlvbihaZSl7WmU/QWUoMjkyOSk6dG4oMjkyOSl9LHNldE1hc2s6ZnVuY3Rpb24oWmUpe2ZuIT09WmUmJiFnZSYmKG4uZGVwdGhNYXNrKFplKSxmbj1aZSl9LHNldEZ1bmM6ZnVuY3Rpb24oWmUpe2lmKFp0IT09WmUpe2lmKFplKXN3aXRjaChaZSl7Y2FzZSAwOm4uZGVwdGhGdW5jKDUxMik7YnJlYWs7Y2FzZSAxOm4uZGVwdGhGdW5jKDUxOSk7YnJlYWs7Y2FzZSAyOm4uZGVwdGhGdW5jKDUxMyk7YnJlYWs7Y2FzZSAzOmRlZmF1bHQ6bi5kZXB0aEZ1bmMoNTE1KTticmVhaztjYXNlIDQ6bi5kZXB0aEZ1bmMoNTE0KTticmVhaztjYXNlIDU6bi5kZXB0aEZ1bmMoNTE4KTticmVhaztjYXNlIDY6bi5kZXB0aEZ1bmMoNTE2KTticmVhaztjYXNlIDc6bi5kZXB0aEZ1bmMoNTE3KX1lbHNlIG4uZGVwdGhGdW5jKDUxNSk7WnQ9WmV9fSxzZXRMb2NrZWQ6ZnVuY3Rpb24oWmUpe2dlPVplfSxzZXRDbGVhcjpmdW5jdGlvbihaZSl7Tm4hPT1aZSYmKG4uY2xlYXJEZXB0aChaZSksTm49WmUpfSxyZXNldDpmdW5jdGlvbigpe2dlPSExLGZuPW51bGwsWnQ9bnVsbCxObj1udWxsfX19LGM9bmV3IGZ1bmN0aW9uKCl7bGV0IGdlPSExLGZuPW51bGwsWnQ9bnVsbCxObj1udWxsLFplPW51bGwsRG49bnVsbCxNaT1udWxsLFRyPW51bGwsRXM9bnVsbDtyZXR1cm57c2V0VGVzdDpmdW5jdGlvbihCcil7Z2V8fChCcj9BZSgyOTYwKTp0bigyOTYwKSl9LHNldE1hc2s6ZnVuY3Rpb24oQnIpe2ZuIT09QnImJiFnZSYmKG4uc3RlbmNpbE1hc2soQnIpLGZuPUJyKX0sc2V0RnVuYzpmdW5jdGlvbihCcixQbCxiYSl7KFp0IT09QnJ8fE5uIT09UGx8fFplIT09YmEpJiYobi5zdGVuY2lsRnVuYyhCcixQbCxiYSksWnQ9QnIsTm49UGwsWmU9YmEpfSxzZXRPcDpmdW5jdGlvbihCcixQbCxiYSl7KERuIT09QnJ8fE1pIT09UGx8fFRyIT09YmEpJiYobi5zdGVuY2lsT3AoQnIsUGwsYmEpLERuPUJyLE1pPVBsLFRyPWJhKX0sc2V0TG9ja2VkOmZ1bmN0aW9uKEJyKXtnZT1Ccn0sc2V0Q2xlYXI6ZnVuY3Rpb24oQnIpe0VzIT09QnImJihuLmNsZWFyU3RlbmNpbChCciksRXM9QnIpfSxyZXNldDpmdW5jdGlvbigpe2dlPSExLGZuPW51bGwsWnQ9bnVsbCxObj1udWxsLFplPW51bGwsRG49bnVsbCxNaT1udWxsLFRyPW51bGwsRXM9bnVsbH19fSx1PXt9LGQ9e30scD1uZXcgV2Vha01hcCxoPVtdLGY9bnVsbCxtPSExLHg9bnVsbCxnPW51bGwsYj1udWxsLEQ9bnVsbCxUPW51bGwsaz1udWxsLFo9bnVsbCx6PSExLGZlPW51bGwsdWU9bnVsbCxoZT1udWxsLHc9bnVsbCxGPW51bGwscT1uLmdldFBhcmFtZXRlcigzNTY2MSksSz0hMSxkZT0wLFk9bi5nZXRQYXJhbWV0ZXIoNzkzOCk7LTEhPT1ZLmluZGV4T2YoIldlYkdMIik/KGRlPXBhcnNlRmxvYXQoL15XZWJHTCAoXGQpLy5leGVjKFkpWzFdKSxLPWRlPj0xKTotMSE9PVkuaW5kZXhPZigiT3BlbkdMIEVTIikmJihkZT1wYXJzZUZsb2F0KC9eT3BlbkdMIEVTIChcZCkvLmV4ZWMoWSlbMV0pLEs9ZGU+PTIpO2xldCBhZT1udWxsLGxlPXt9LEllPW4uZ2V0UGFyYW1ldGVyKDMwODgpLHZlPW4uZ2V0UGFyYW1ldGVyKDI5NzgpLERlPShuZXcgYXIpLmZyb21BcnJheShJZSksbnQ9KG5ldyBhcikuZnJvbUFycmF5KHZlKTtmdW5jdGlvbiBndChnZSxmbixadCl7bGV0IE5uPW5ldyBVaW50OEFycmF5KDQpLFplPW4uY3JlYXRlVGV4dHVyZSgpO24uYmluZFRleHR1cmUoZ2UsWmUpLG4udGV4UGFyYW1ldGVyaShnZSwxMDI0MSw5NzI4KSxuLnRleFBhcmFtZXRlcmkoZ2UsMTAyNDAsOTcyOCk7Zm9yKGxldCBEbj0wO0RuPFp0O0RuKyspbi50ZXhJbWFnZTJEKGZuK0RuLDAsNjQwOCwxLDEsMCw2NDA4LDUxMjEsTm4pO3JldHVybiBaZX1sZXQgVWU9e307ZnVuY3Rpb24gQWUoZ2UpeyEwIT09dVtnZV0mJihuLmVuYWJsZShnZSksdVtnZV09ITApfWZ1bmN0aW9uIHRuKGdlKXshMSE9PXVbZ2VdJiYobi5kaXNhYmxlKGdlKSx1W2dlXT0hMSl9VWVbMzU1M109Z3QoMzU1MywzNTUzLDEpLFVlWzM0MDY3XT1ndCgzNDA2NywzNDA2OSw2KSxhLnNldENsZWFyKDAsMCwwLDEpLGwuc2V0Q2xlYXIoMSksYy5zZXRDbGVhcigwKSxBZSgyOTI5KSxsLnNldEZ1bmMoMyksV2UoITEpLE10KDEpLEFlKDI4ODQpLGNlKDApO2xldCB4dD17MTAwOjMyNzc0LDEwMTozMjc3OCwxMDI6MzI3Nzl9O2lmKGkpeHRbMTAzXT0zMjc3NSx4dFsxMDRdPTMyNzc2O2Vsc2V7bGV0IGdlPXQuZ2V0KCJFWFRfYmxlbmRfbWlubWF4Iik7bnVsbCE9PWdlJiYoeHRbMTAzXT1nZS5NSU5fRVhULHh0WzEwNF09Z2UuTUFYX0VYVCl9bGV0IG10PXsyMDA6MCwyMDE6MSwyMDI6NzY4LDIwNDo3NzAsMjEwOjc3NiwyMDg6Nzc0LDIwNjo3NzIsMjAzOjc2OSwyMDU6NzcxLDIwOTo3NzUsMjA3Ojc3M307ZnVuY3Rpb24gY2UoZ2UsZm4sWnQsTm4sWmUsRG4sTWksVHIpe2lmKDAhPT1nZSl7aWYoITE9PT1tJiYoQWUoMzA0MiksbT0hMCksNT09PWdlKVplPVplfHxmbixEbj1Ebnx8WnQsTWk9TWl8fE5uLChmbiE9PWd8fFplIT09VCkmJihuLmJsZW5kRXF1YXRpb25TZXBhcmF0ZSh4dFtmbl0seHRbWmVdKSxnPWZuLFQ9WmUpLChadCE9PWJ8fE5uIT09RHx8RG4hPT1rfHxNaSE9PVopJiYobi5ibGVuZEZ1bmNTZXBhcmF0ZShtdFtadF0sbXRbTm5dLG10W0RuXSxtdFtNaV0pLGI9WnQsRD1ObixrPURuLFo9TWkpLHg9Z2Usej1udWxsO2Vsc2UgaWYoZ2UhPT14fHxUciE9PXope2lmKCgxMDAhPT1nfHwxMDAhPT1UKSYmKG4uYmxlbmRFcXVhdGlvbigzMjc3NCksZz0xMDAsVD0xMDApLFRyKXN3aXRjaChnZSl7Y2FzZSAxOm4uYmxlbmRGdW5jU2VwYXJhdGUoMSw3NzEsMSw3NzEpO2JyZWFrO2Nhc2UgMjpuLmJsZW5kRnVuYygxLDEpO2JyZWFrO2Nhc2UgMzpuLmJsZW5kRnVuY1NlcGFyYXRlKDAsNzY5LDAsMSk7YnJlYWs7Y2FzZSA0Om4uYmxlbmRGdW5jU2VwYXJhdGUoMCw3NjgsMCw3NzApO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZTogSW52YWxpZCBibGVuZGluZzogIixnZSl9ZWxzZSBzd2l0Y2goZ2Upe2Nhc2UgMTpuLmJsZW5kRnVuY1NlcGFyYXRlKDc3MCw3NzEsMSw3NzEpO2JyZWFrO2Nhc2UgMjpuLmJsZW5kRnVuYyg3NzAsMSk7YnJlYWs7Y2FzZSAzOm4uYmxlbmRGdW5jU2VwYXJhdGUoMCw3NjksMCwxKTticmVhaztjYXNlIDQ6bi5ibGVuZEZ1bmMoMCw3NjgpO2JyZWFrO2RlZmF1bHQ6Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZTogSW52YWxpZCBibGVuZGluZzogIixnZSl9Yj1udWxsLEQ9bnVsbCxrPW51bGwsWj1udWxsLHg9Z2Usej1Ucn19ZWxzZSEwPT09bSYmKHRuKDMwNDIpLG09ITEpfWZ1bmN0aW9uIFdlKGdlKXtmZSE9PWdlJiYobi5mcm9udEZhY2UoZ2U/MjMwNDoyMzA1KSxmZT1nZSl9ZnVuY3Rpb24gTXQoZ2UpezAhPT1nZT8oQWUoMjg4NCksZ2UhPT11ZSYmbi5jdWxsRmFjZSgxPT09Z2U/MTAyOToyPT09Z2U/MTAyODoxMDMyKSk6dG4oMjg4NCksdWU9Z2V9ZnVuY3Rpb24gaG4oZ2UsZm4sWnQpe2dlPyhBZSgzMjgyMyksKHchPT1mbnx8RiE9PVp0KSYmKG4ucG9seWdvbk9mZnNldChmbixadCksdz1mbixGPVp0KSk6dG4oMzI4MjMpfWZ1bmN0aW9uIGZpKGdlKXt2b2lkIDA9PT1nZSYmKGdlPTMzOTg0K3EtMSksYWUhPT1nZSYmKG4uYWN0aXZlVGV4dHVyZShnZSksYWU9Z2UpfXJldHVybntidWZmZXJzOntjb2xvcjphLGRlcHRoOmwsc3RlbmNpbDpjfSxlbmFibGU6QWUsZGlzYWJsZTp0bixiaW5kRnJhbWVidWZmZXI6ZnVuY3Rpb24oZ2UsZm4pe3JldHVybiBkW2dlXSE9PWZuJiYobi5iaW5kRnJhbWVidWZmZXIoZ2UsZm4pLGRbZ2VdPWZuLGkmJigzNjAwOT09PWdlJiYoZFszNjE2MF09Zm4pLDM2MTYwPT09Z2UmJihkWzM2MDA5XT1mbikpLCEwKX0sZHJhd0J1ZmZlcnM6ZnVuY3Rpb24oZ2UsZm4pe2xldCBadD1oLE5uPSExO2lmKGdlKWlmKFp0PXAuZ2V0KGZuKSx2b2lkIDA9PT1adCYmKFp0PVtdLHAuc2V0KGZuLFp0KSksZ2UuaXNXZWJHTE11bHRpcGxlUmVuZGVyVGFyZ2V0cyl7bGV0IFplPWdlLnRleHR1cmU7aWYoWnQubGVuZ3RoIT09WmUubGVuZ3RofHwzNjA2NCE9PVp0WzBdKXtmb3IobGV0IERuPTAsTWk9WmUubGVuZ3RoO0RuPE1pO0RuKyspWnRbRG5dPTM2MDY0K0RuO1p0Lmxlbmd0aD1aZS5sZW5ndGgsTm49ITB9fWVsc2UgMzYwNjQhPT1adFswXSYmKFp0WzBdPTM2MDY0LE5uPSEwKTtlbHNlIDEwMjkhPT1adFswXSYmKFp0WzBdPTEwMjksTm49ITApO05uJiYoZS5pc1dlYkdMMj9uLmRyYXdCdWZmZXJzKFp0KTp0LmdldCgiV0VCR0xfZHJhd19idWZmZXJzIikuZHJhd0J1ZmZlcnNXRUJHTChadCkpfSx1c2VQcm9ncmFtOmZ1bmN0aW9uKGdlKXtyZXR1cm4gZiE9PWdlJiYobi51c2VQcm9ncmFtKGdlKSxmPWdlLCEwKX0sc2V0QmxlbmRpbmc6Y2Usc2V0TWF0ZXJpYWw6ZnVuY3Rpb24oZ2UsZm4pezI9PT1nZS5zaWRlP3RuKDI4ODQpOkFlKDI4ODQpO2xldCBadD0xPT09Z2Uuc2lkZTtmbiYmKFp0PSFadCksV2UoWnQpLDE9PT1nZS5ibGVuZGluZyYmITE9PT1nZS50cmFuc3BhcmVudD9jZSgwKTpjZShnZS5ibGVuZGluZyxnZS5ibGVuZEVxdWF0aW9uLGdlLmJsZW5kU3JjLGdlLmJsZW5kRHN0LGdlLmJsZW5kRXF1YXRpb25BbHBoYSxnZS5ibGVuZFNyY0FscGhhLGdlLmJsZW5kRHN0QWxwaGEsZ2UucHJlbXVsdGlwbGllZEFscGhhKSxsLnNldEZ1bmMoZ2UuZGVwdGhGdW5jKSxsLnNldFRlc3QoZ2UuZGVwdGhUZXN0KSxsLnNldE1hc2soZ2UuZGVwdGhXcml0ZSksYS5zZXRNYXNrKGdlLmNvbG9yV3JpdGUpO2xldCBObj1nZS5zdGVuY2lsV3JpdGU7Yy5zZXRUZXN0KE5uKSxObiYmKGMuc2V0TWFzayhnZS5zdGVuY2lsV3JpdGVNYXNrKSxjLnNldEZ1bmMoZ2Uuc3RlbmNpbEZ1bmMsZ2Uuc3RlbmNpbFJlZixnZS5zdGVuY2lsRnVuY01hc2spLGMuc2V0T3AoZ2Uuc3RlbmNpbEZhaWwsZ2Uuc3RlbmNpbFpGYWlsLGdlLnN0ZW5jaWxaUGFzcykpLGhuKGdlLnBvbHlnb25PZmZzZXQsZ2UucG9seWdvbk9mZnNldEZhY3RvcixnZS5wb2x5Z29uT2Zmc2V0VW5pdHMpLCEwPT09Z2UuYWxwaGFUb0NvdmVyYWdlP0FlKDMyOTI2KTp0bigzMjkyNil9LHNldEZsaXBTaWRlZDpXZSxzZXRDdWxsRmFjZTpNdCxzZXRMaW5lV2lkdGg6ZnVuY3Rpb24oZ2Upe2dlIT09aGUmJihLJiZuLmxpbmVXaWR0aChnZSksaGU9Z2UpfSxzZXRQb2x5Z29uT2Zmc2V0OmhuLHNldFNjaXNzb3JUZXN0OmZ1bmN0aW9uKGdlKXtnZT9BZSgzMDg5KTp0bigzMDg5KX0sYWN0aXZlVGV4dHVyZTpmaSxiaW5kVGV4dHVyZTpmdW5jdGlvbihnZSxmbil7bnVsbD09PWFlJiZmaSgpO2xldCBadD1sZVthZV07dm9pZCAwPT09WnQmJihadD17dHlwZTp2b2lkIDAsdGV4dHVyZTp2b2lkIDB9LGxlW2FlXT1adCksKFp0LnR5cGUhPT1nZXx8WnQudGV4dHVyZSE9PWZuKSYmKG4uYmluZFRleHR1cmUoZ2UsZm58fFVlW2dlXSksWnQudHlwZT1nZSxadC50ZXh0dXJlPWZuKX0sdW5iaW5kVGV4dHVyZTpmdW5jdGlvbigpe2xldCBnZT1sZVthZV07dm9pZCAwIT09Z2UmJnZvaWQgMCE9PWdlLnR5cGUmJihuLmJpbmRUZXh0dXJlKGdlLnR5cGUsbnVsbCksZ2UudHlwZT12b2lkIDAsZ2UudGV4dHVyZT12b2lkIDApfSxjb21wcmVzc2VkVGV4SW1hZ2UyRDpmdW5jdGlvbigpe3RyeXtuLmNvbXByZXNzZWRUZXhJbWFnZTJELmFwcGx5KG4sYXJndW1lbnRzKX1jYXRjaChnZSl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZToiLGdlKX19LHRleEltYWdlMkQ6ZnVuY3Rpb24oKXt0cnl7bi50ZXhJbWFnZTJELmFwcGx5KG4sYXJndW1lbnRzKX1jYXRjaChnZSl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZToiLGdlKX19LHRleEltYWdlM0Q6ZnVuY3Rpb24oKXt0cnl7bi50ZXhJbWFnZTNELmFwcGx5KG4sYXJndW1lbnRzKX1jYXRjaChnZSl7Y29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xTdGF0ZToiLGdlKX19LHRleFN0b3JhZ2UyRDpmdW5jdGlvbigpe3RyeXtuLnRleFN0b3JhZ2UyRC5hcHBseShuLGFyZ3VtZW50cyl9Y2F0Y2goZ2Upe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IixnZSl9fSx0ZXhTdG9yYWdlM0Q6ZnVuY3Rpb24oKXt0cnl7bi50ZXhTdG9yYWdlM0QuYXBwbHkobixhcmd1bWVudHMpfWNhdGNoKGdlKXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFN0YXRlOiIsZ2UpfX0sdGV4U3ViSW1hZ2UyRDpmdW5jdGlvbigpe3RyeXtuLnRleFN1YkltYWdlMkQuYXBwbHkobixhcmd1bWVudHMpfWNhdGNoKGdlKXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFN0YXRlOiIsZ2UpfX0sdGV4U3ViSW1hZ2UzRDpmdW5jdGlvbigpe3RyeXtuLnRleFN1YkltYWdlM0QuYXBwbHkobixhcmd1bWVudHMpfWNhdGNoKGdlKXtjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFN0YXRlOiIsZ2UpfX0sY29tcHJlc3NlZFRleFN1YkltYWdlMkQ6ZnVuY3Rpb24oKXt0cnl7bi5jb21wcmVzc2VkVGV4U3ViSW1hZ2UyRC5hcHBseShuLGFyZ3VtZW50cyl9Y2F0Y2goZ2Upe2NvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMU3RhdGU6IixnZSl9fSxzY2lzc29yOmZ1bmN0aW9uKGdlKXshMT09PURlLmVxdWFscyhnZSkmJihuLnNjaXNzb3IoZ2UueCxnZS55LGdlLnosZ2UudyksRGUuY29weShnZSkpfSx2aWV3cG9ydDpmdW5jdGlvbihnZSl7ITE9PT1udC5lcXVhbHMoZ2UpJiYobi52aWV3cG9ydChnZS54LGdlLnksZ2UueixnZS53KSxudC5jb3B5KGdlKSl9LHJlc2V0OmZ1bmN0aW9uKCl7bi5kaXNhYmxlKDMwNDIpLG4uZGlzYWJsZSgyODg0KSxuLmRpc2FibGUoMjkyOSksbi5kaXNhYmxlKDMyODIzKSxuLmRpc2FibGUoMzA4OSksbi5kaXNhYmxlKDI5NjApLG4uZGlzYWJsZSgzMjkyNiksbi5ibGVuZEVxdWF0aW9uKDMyNzc0KSxuLmJsZW5kRnVuYygxLDApLG4uYmxlbmRGdW5jU2VwYXJhdGUoMSwwLDEsMCksbi5jb2xvck1hc2soITAsITAsITAsITApLG4uY2xlYXJDb2xvcigwLDAsMCwwKSxuLmRlcHRoTWFzayghMCksbi5kZXB0aEZ1bmMoNTEzKSxuLmNsZWFyRGVwdGgoMSksbi5zdGVuY2lsTWFzayg0Mjk0OTY3Mjk1KSxuLnN0ZW5jaWxGdW5jKDUxOSwwLDQyOTQ5NjcyOTUpLG4uc3RlbmNpbE9wKDc2ODAsNzY4MCw3NjgwKSxuLmNsZWFyU3RlbmNpbCgwKSxuLmN1bGxGYWNlKDEwMjkpLG4uZnJvbnRGYWNlKDIzMDUpLG4ucG9seWdvbk9mZnNldCgwLDApLG4uYWN0aXZlVGV4dHVyZSgzMzk4NCksbi5iaW5kRnJhbWVidWZmZXIoMzYxNjAsbnVsbCksITA9PT1pJiYobi5iaW5kRnJhbWVidWZmZXIoMzYwMDksbnVsbCksbi5iaW5kRnJhbWVidWZmZXIoMzYwMDgsbnVsbCkpLG4udXNlUHJvZ3JhbShudWxsKSxuLmxpbmVXaWR0aCgxKSxuLnNjaXNzb3IoMCwwLG4uY2FudmFzLndpZHRoLG4uY2FudmFzLmhlaWdodCksbi52aWV3cG9ydCgwLDAsbi5jYW52YXMud2lkdGgsbi5jYW52YXMuaGVpZ2h0KSx1PXt9LGFlPW51bGwsbGU9e30sZD17fSxwPW5ldyBXZWFrTWFwLGg9W10sZj1udWxsLG09ITEseD1udWxsLGc9bnVsbCxiPW51bGwsRD1udWxsLFQ9bnVsbCxrPW51bGwsWj1udWxsLHo9ITEsZmU9bnVsbCx1ZT1udWxsLGhlPW51bGwsdz1udWxsLEY9bnVsbCxEZS5zZXQoMCwwLG4uY2FudmFzLndpZHRoLG4uY2FudmFzLmhlaWdodCksbnQuc2V0KDAsMCxuLmNhbnZhcy53aWR0aCxuLmNhbnZhcy5oZWlnaHQpLGEucmVzZXQoKSxsLnJlc2V0KCksYy5yZXNldCgpfX19ZnVuY3Rpb24gaDllKG4sdCxlLGkscixvLHMpe2xldCBtLGE9ci5pc1dlYkdMMixsPXIubWF4VGV4dHVyZXMsYz1yLm1heEN1YmVtYXBTaXplLHU9ci5tYXhUZXh0dXJlU2l6ZSxkPXIubWF4U2FtcGxlcyxoPXQuaGFzKCJXRUJHTF9tdWx0aXNhbXBsZWRfcmVuZGVyX3RvX3RleHR1cmUiKT90LmdldCgiV0VCR0xfbXVsdGlzYW1wbGVkX3JlbmRlcl90b190ZXh0dXJlIik6dm9pZCAwLGY9bmV3IFdlYWtNYXAseD0hMTt0cnl7eD10eXBlb2YgT2Zmc2NyZWVuQ2FudmFzPCJ1IiYmbnVsbCE9PW5ldyBPZmZzY3JlZW5DYW52YXMoMSwxKS5nZXRDb250ZXh0KCIyZCIpfWNhdGNoe31mdW5jdGlvbiBnKGVlLFcpe3JldHVybiB4P25ldyBPZmZzY3JlZW5DYW52YXMoZWUsVyk6WVMoImNhbnZhcyIpfWZ1bmN0aW9uIGIoZWUsVyxYZSxUdCl7bGV0IG1uPTE7aWYoKGVlLndpZHRoPlR0fHxlZS5oZWlnaHQ+VHQpJiYobW49VHQvTWF0aC5tYXgoZWUud2lkdGgsZWUuaGVpZ2h0KSksbW48MXx8ITA9PT1XKXtpZih0eXBlb2YgSFRNTEltYWdlRWxlbWVudDwidSImJmVlIGluc3RhbmNlb2YgSFRNTEltYWdlRWxlbWVudHx8dHlwZW9mIEhUTUxDYW52YXNFbGVtZW50PCJ1IiYmZWUgaW5zdGFuY2VvZiBIVE1MQ2FudmFzRWxlbWVudHx8dHlwZW9mIEltYWdlQml0bWFwPCJ1IiYmZWUgaW5zdGFuY2VvZiBJbWFnZUJpdG1hcCl7bGV0IHFlPVc/SDhlOk1hdGguZmxvb3Isd249cWUobW4qZWUud2lkdGgpLHluPXFlKG1uKmVlLmhlaWdodCk7dm9pZCAwPT09bSYmKG09Zyh3bix5bikpO2xldCB6dD1YZT9nKHduLHluKTptO3JldHVybiB6dC53aWR0aD13bix6dC5oZWlnaHQ9eW4senQuZ2V0Q29udGV4dCgiMmQiKS5kcmF3SW1hZ2UoZWUsMCwwLHduLHluKSxjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IFRleHR1cmUgaGFzIGJlZW4gcmVzaXplZCBmcm9tICgiK2VlLndpZHRoKyJ4IitlZS5oZWlnaHQrIikgdG8gKCIrd24rIngiK3luKyIpLiIpLHp0fXJldHVybiJkYXRhImluIGVlJiZjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IEltYWdlIGluIERhdGFUZXh0dXJlIGlzIHRvbyBiaWcgKCIrZWUud2lkdGgrIngiK2VlLmhlaWdodCsiKS4iKSxlZX1yZXR1cm4gZWV9ZnVuY3Rpb24gRChlZSl7cmV0dXJuIHh1ZShlZS53aWR0aCkmJnh1ZShlZS5oZWlnaHQpfWZ1bmN0aW9uIGsoZWUsVyl7cmV0dXJuIGVlLmdlbmVyYXRlTWlwbWFwcyYmVyYmZWUubWluRmlsdGVyIT09Wm8mJmVlLm1pbkZpbHRlciE9PUdzfWZ1bmN0aW9uIFooZWUpe24uZ2VuZXJhdGVNaXBtYXAoZWUpfWZ1bmN0aW9uIHooZWUsVyxYZSxUdCxtbj0hMSl7aWYoITE9PT1hKXJldHVybiBXO2lmKG51bGwhPT1lZSl7aWYodm9pZCAwIT09bltlZV0pcmV0dXJuIG5bZWVdO2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogQXR0ZW1wdCB0byB1c2Ugbm9uLWV4aXN0aW5nIFdlYkdMIGludGVybmFsIGZvcm1hdCAnIitlZSsiJyIpfWxldCBxZT1XO3JldHVybiA2NDAzPT09VyYmKDUxMjY9PT1YZSYmKHFlPTMzMzI2KSw1MTMxPT09WGUmJihxZT0zMzMyNSksNTEyMT09PVhlJiYocWU9MzMzMjEpKSwzMzMxOT09PVcmJig1MTI2PT09WGUmJihxZT0zMzMyOCksNTEzMT09PVhlJiYocWU9MzMzMjcpLDUxMjE9PT1YZSYmKHFlPTMzMzIzKSksNjQwOD09PVcmJig1MTI2PT09WGUmJihxZT0zNDgzNiksNTEzMT09PVhlJiYocWU9MzQ4NDIpLDUxMjE9PT1YZSYmKHFlPVR0PT09V3ImJiExPT09bW4/MzU5MDc6MzI4NTYpLDMyODE5PT09WGUmJihxZT0zMjg1NCksMzI4MjA9PT1YZSYmKHFlPTMyODU1KSksKDMzMzI1PT09cWV8fDMzMzI2PT09cWV8fDMzMzI3PT09cWV8fDMzMzI4PT09cWV8fDM0ODQyPT09cWV8fDM0ODM2PT09cWUpJiZ0LmdldCgiRVhUX2NvbG9yX2J1ZmZlcl9mbG9hdCIpLHFlfWZ1bmN0aW9uIGZlKGVlLFcsWGUpe3JldHVybiEwPT09ayhlZSxYZSl8fGVlLmlzRnJhbWVidWZmZXJUZXh0dXJlJiZlZS5taW5GaWx0ZXIhPT1abyYmZWUubWluRmlsdGVyIT09R3M/TWF0aC5sb2cyKE1hdGgubWF4KFcud2lkdGgsVy5oZWlnaHQpKSsxOnZvaWQgMCE9PWVlLm1pcG1hcHMmJmVlLm1pcG1hcHMubGVuZ3RoPjA/ZWUubWlwbWFwcy5sZW5ndGg6ZWUuaXNDb21wcmVzc2VkVGV4dHVyZSYmQXJyYXkuaXNBcnJheShlZS5pbWFnZSk/Vy5taXBtYXBzLmxlbmd0aDoxfWZ1bmN0aW9uIHVlKGVlKXtyZXR1cm4gZWU9PT1ab3x8MTAwND09PWVlfHwxMDA1PT09ZWU/OTcyODo5NzI5fWZ1bmN0aW9uIGhlKGVlKXtsZXQgVz1lZS50YXJnZXQ7Vy5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixoZSksZnVuY3Rpb24oZWUpe2xldCBXPWkuZ2V0KGVlKTt2b2lkIDAhPT1XLl9fd2ViZ2xJbml0JiYobi5kZWxldGVUZXh0dXJlKFcuX193ZWJnbFRleHR1cmUpLGkucmVtb3ZlKGVlKSl9KFcpLFcuaXNWaWRlb1RleHR1cmUmJmYuZGVsZXRlKFcpLHMubWVtb3J5LnRleHR1cmVzLS19ZnVuY3Rpb24gdyhlZSl7bGV0IFc9ZWUudGFyZ2V0O1cucmVtb3ZlRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsdyksZnVuY3Rpb24oZWUpe2xldCBXPWVlLnRleHR1cmUsWGU9aS5nZXQoZWUpLFR0PWkuZ2V0KFcpO2lmKGVlKXtpZih2b2lkIDAhPT1UdC5fX3dlYmdsVGV4dHVyZSYmKG4uZGVsZXRlVGV4dHVyZShUdC5fX3dlYmdsVGV4dHVyZSkscy5tZW1vcnkudGV4dHVyZXMtLSksZWUuZGVwdGhUZXh0dXJlJiZlZS5kZXB0aFRleHR1cmUuZGlzcG9zZSgpLGVlLmlzV2ViR0xDdWJlUmVuZGVyVGFyZ2V0KWZvcihsZXQgbW49MDttbjw2O21uKyspbi5kZWxldGVGcmFtZWJ1ZmZlcihYZS5fX3dlYmdsRnJhbWVidWZmZXJbbW5dKSxYZS5fX3dlYmdsRGVwdGhidWZmZXImJm4uZGVsZXRlUmVuZGVyYnVmZmVyKFhlLl9fd2ViZ2xEZXB0aGJ1ZmZlclttbl0pO2Vsc2Ugbi5kZWxldGVGcmFtZWJ1ZmZlcihYZS5fX3dlYmdsRnJhbWVidWZmZXIpLFhlLl9fd2ViZ2xEZXB0aGJ1ZmZlciYmbi5kZWxldGVSZW5kZXJidWZmZXIoWGUuX193ZWJnbERlcHRoYnVmZmVyKSxYZS5fX3dlYmdsTXVsdGlzYW1wbGVkRnJhbWVidWZmZXImJm4uZGVsZXRlRnJhbWVidWZmZXIoWGUuX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyKSxYZS5fX3dlYmdsQ29sb3JSZW5kZXJidWZmZXImJm4uZGVsZXRlUmVuZGVyYnVmZmVyKFhlLl9fd2ViZ2xDb2xvclJlbmRlcmJ1ZmZlciksWGUuX193ZWJnbERlcHRoUmVuZGVyYnVmZmVyJiZuLmRlbGV0ZVJlbmRlcmJ1ZmZlcihYZS5fX3dlYmdsRGVwdGhSZW5kZXJidWZmZXIpO2lmKGVlLmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHMpZm9yKGxldCBtbj0wLHFlPVcubGVuZ3RoO21uPHFlO21uKyspe2xldCB3bj1pLmdldChXW21uXSk7d24uX193ZWJnbFRleHR1cmUmJihuLmRlbGV0ZVRleHR1cmUod24uX193ZWJnbFRleHR1cmUpLHMubWVtb3J5LnRleHR1cmVzLS0pLGkucmVtb3ZlKFdbbW5dKX1pLnJlbW92ZShXKSxpLnJlbW92ZShlZSl9fShXKX1sZXQgSz0wO2Z1bmN0aW9uIGFlKGVlLFcpe2xldCBYZT1pLmdldChlZSk7aWYoZWUuaXNWaWRlb1RleHR1cmUmJmZ1bmN0aW9uKGVlKXtsZXQgVz1zLnJlbmRlci5mcmFtZTtmLmdldChlZSkhPT1XJiYoZi5zZXQoZWUsVyksZWUudXBkYXRlKCkpfShlZSksZWUudmVyc2lvbj4wJiZYZS5fX3ZlcnNpb24hPT1lZS52ZXJzaW9uKXtsZXQgVHQ9ZWUuaW1hZ2U7aWYodm9pZCAwPT09VHQpY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIG1hcmtlZCBmb3IgdXBkYXRlIGJ1dCBpbWFnZSBpcyB1bmRlZmluZWQiKTtlbHNle2lmKCExIT09VHQuY29tcGxldGUpcmV0dXJuIHZvaWQgQWUoWGUsZWUsVyk7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIG1hcmtlZCBmb3IgdXBkYXRlIGJ1dCBpbWFnZSBpcyBpbmNvbXBsZXRlIil9fWUuYWN0aXZlVGV4dHVyZSgzMzk4NCtXKSxlLmJpbmRUZXh0dXJlKDM1NTMsWGUuX193ZWJnbFRleHR1cmUpfWZ1bmN0aW9uIHZlKGVlLFcpe2xldCBYZT1pLmdldChlZSk7ZWUudmVyc2lvbj4wJiZYZS5fX3ZlcnNpb24hPT1lZS52ZXJzaW9uP2Z1bmN0aW9uKGVlLFcsWGUpe2lmKDYhPT1XLmltYWdlLmxlbmd0aClyZXR1cm47VWUoZWUsVyksZS5hY3RpdmVUZXh0dXJlKDMzOTg0K1hlKSxlLmJpbmRUZXh0dXJlKDM0MDY3LGVlLl9fd2ViZ2xUZXh0dXJlKSxuLnBpeGVsU3RvcmVpKDM3NDQwLFcuZmxpcFkpLG4ucGl4ZWxTdG9yZWkoMzc0NDEsVy5wcmVtdWx0aXBseUFscGhhKSxuLnBpeGVsU3RvcmVpKDMzMTcsVy51bnBhY2tBbGlnbm1lbnQpLG4ucGl4ZWxTdG9yZWkoMzc0NDMsMCk7bGV0IFR0PVcmJihXLmlzQ29tcHJlc3NlZFRleHR1cmV8fFcuaW1hZ2VbMF0uaXNDb21wcmVzc2VkVGV4dHVyZSksbW49Vy5pbWFnZVswXSYmVy5pbWFnZVswXS5pc0RhdGFUZXh0dXJlLHFlPVtdO2ZvcihsZXQgWmU9MDtaZTw2O1plKyspcWVbWmVdPVR0fHxtbj9tbj9XLmltYWdlW1plXS5pbWFnZTpXLmltYWdlW1plXTpiKFcuaW1hZ2VbWmVdLCExLCEwLGMpLHFlW1plXT1obihXLHFlW1plXSk7bGV0IE5uLHduPXFlWzBdLHluPUQod24pfHxhLHp0PW8uY29udmVydChXLmZvcm1hdCxXLmVuY29kaW5nKSxVdD1vLmNvbnZlcnQoVy50eXBlKSxXbj16KFcuaW50ZXJuYWxGb3JtYXQsenQsVXQsVy5lbmNvZGluZyksZ2U9YSYmITAhPT1XLmlzVmlkZW9UZXh0dXJlLGZuPXZvaWQgMD09PWVlLl9fdmVyc2lvbixadD1mZShXLHduLHluKTtpZihndCgzNDA2NyxXLHluKSxUdCl7Z2UmJmZuJiZlLnRleFN0b3JhZ2UyRCgzNDA2NyxadCxXbix3bi53aWR0aCx3bi5oZWlnaHQpO2ZvcihsZXQgWmU9MDtaZTw2O1plKyspe05uPXFlW1plXS5taXBtYXBzO2ZvcihsZXQgRG49MDtEbjxObi5sZW5ndGg7RG4rKyl7bGV0IE1pPU5uW0RuXTtXLmZvcm1hdCE9PWdhP251bGwhPT16dD9nZT9lLmNvbXByZXNzZWRUZXhTdWJJbWFnZTJEKDM0MDY5K1plLERuLDAsMCxNaS53aWR0aCxNaS5oZWlnaHQsenQsTWkuZGF0YSk6ZS5jb21wcmVzc2VkVGV4SW1hZ2UyRCgzNDA2OStaZSxEbixXbixNaS53aWR0aCxNaS5oZWlnaHQsMCxNaS5kYXRhKTpjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IEF0dGVtcHQgdG8gbG9hZCB1bnN1cHBvcnRlZCBjb21wcmVzc2VkIHRleHR1cmUgZm9ybWF0IGluIC5zZXRUZXh0dXJlQ3ViZSgpIik6Z2U/ZS50ZXhTdWJJbWFnZTJEKDM0MDY5K1plLERuLDAsMCxNaS53aWR0aCxNaS5oZWlnaHQsenQsVXQsTWkuZGF0YSk6ZS50ZXhJbWFnZTJEKDM0MDY5K1plLERuLFduLE1pLndpZHRoLE1pLmhlaWdodCwwLHp0LFV0LE1pLmRhdGEpfX19ZWxzZXtObj1XLm1pcG1hcHMsZ2UmJmZuJiYoTm4ubGVuZ3RoPjAmJlp0KyssZS50ZXhTdG9yYWdlMkQoMzQwNjcsWnQsV24scWVbMF0ud2lkdGgscWVbMF0uaGVpZ2h0KSk7Zm9yKGxldCBaZT0wO1plPDY7WmUrKylpZihtbil7Z2U/ZS50ZXhTdWJJbWFnZTJEKDM0MDY5K1plLDAsMCwwLHFlW1plXS53aWR0aCxxZVtaZV0uaGVpZ2h0LHp0LFV0LHFlW1plXS5kYXRhKTplLnRleEltYWdlMkQoMzQwNjkrWmUsMCxXbixxZVtaZV0ud2lkdGgscWVbWmVdLmhlaWdodCwwLHp0LFV0LHFlW1plXS5kYXRhKTtmb3IobGV0IERuPTA7RG48Tm4ubGVuZ3RoO0RuKyspe2xldCBUcj1ObltEbl0uaW1hZ2VbWmVdLmltYWdlO2dlP2UudGV4U3ViSW1hZ2UyRCgzNDA2OStaZSxEbisxLDAsMCxUci53aWR0aCxUci5oZWlnaHQsenQsVXQsVHIuZGF0YSk6ZS50ZXhJbWFnZTJEKDM0MDY5K1plLERuKzEsV24sVHIud2lkdGgsVHIuaGVpZ2h0LDAsenQsVXQsVHIuZGF0YSl9fWVsc2V7Z2U/ZS50ZXhTdWJJbWFnZTJEKDM0MDY5K1plLDAsMCwwLHp0LFV0LHFlW1plXSk6ZS50ZXhJbWFnZTJEKDM0MDY5K1plLDAsV24senQsVXQscWVbWmVdKTtmb3IobGV0IERuPTA7RG48Tm4ubGVuZ3RoO0RuKyspe2xldCBNaT1ObltEbl07Z2U/ZS50ZXhTdWJJbWFnZTJEKDM0MDY5K1plLERuKzEsMCwwLHp0LFV0LE1pLmltYWdlW1plXSk6ZS50ZXhJbWFnZTJEKDM0MDY5K1plLERuKzEsV24senQsVXQsTWkuaW1hZ2VbWmVdKX19fWsoVyx5bikmJlooMzQwNjcpLGVlLl9fdmVyc2lvbj1XLnZlcnNpb24sVy5vblVwZGF0ZSYmVy5vblVwZGF0ZShXKX0oWGUsZWUsVyk6KGUuYWN0aXZlVGV4dHVyZSgzMzk4NCtXKSxlLmJpbmRUZXh0dXJlKDM0MDY3LFhlLl9fd2ViZ2xUZXh0dXJlKSl9bGV0IERlPXsxZTM6MTA0OTcsW0VsXTozMzA3MSwxMDAyOjMzNjQ4fSxudD17W1pvXTo5NzI4LDEwMDQ6OTk4NCwxMDA1Ojk5ODYsW0dzXTo5NzI5LDEwMDc6OTk4NSwxMDA4Ojk5ODd9O2Z1bmN0aW9uIGd0KGVlLFcsWGUpe2lmKFhlPyhuLnRleFBhcmFtZXRlcmkoZWUsMTAyNDIsRGVbVy53cmFwU10pLG4udGV4UGFyYW1ldGVyaShlZSwxMDI0MyxEZVtXLndyYXBUXSksKDMyODc5PT09ZWV8fDM1ODY2PT09ZWUpJiZuLnRleFBhcmFtZXRlcmkoZWUsMzI4ODIsRGVbVy53cmFwUl0pLG4udGV4UGFyYW1ldGVyaShlZSwxMDI0MCxudFtXLm1hZ0ZpbHRlcl0pLG4udGV4UGFyYW1ldGVyaShlZSwxMDI0MSxudFtXLm1pbkZpbHRlcl0pKToobi50ZXhQYXJhbWV0ZXJpKGVlLDEwMjQyLDMzMDcxKSxuLnRleFBhcmFtZXRlcmkoZWUsMTAyNDMsMzMwNzEpLCgzMjg3OT09PWVlfHwzNTg2Nj09PWVlKSYmbi50ZXhQYXJhbWV0ZXJpKGVlLDMyODgyLDMzMDcxKSwoVy53cmFwUyE9PUVsfHxXLndyYXBUIT09RWwpJiZjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IFRleHR1cmUgaXMgbm90IHBvd2VyIG9mIHR3by4gVGV4dHVyZS53cmFwUyBhbmQgVGV4dHVyZS53cmFwVCBzaG91bGQgYmUgc2V0IHRvIFRIUkVFLkNsYW1wVG9FZGdlV3JhcHBpbmcuIiksbi50ZXhQYXJhbWV0ZXJpKGVlLDEwMjQwLHVlKFcubWFnRmlsdGVyKSksbi50ZXhQYXJhbWV0ZXJpKGVlLDEwMjQxLHVlKFcubWluRmlsdGVyKSksVy5taW5GaWx0ZXIhPT1abyYmVy5taW5GaWx0ZXIhPT1HcyYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBUZXh0dXJlIGlzIG5vdCBwb3dlciBvZiB0d28uIFRleHR1cmUubWluRmlsdGVyIHNob3VsZCBiZSBzZXQgdG8gVEhSRUUuTmVhcmVzdEZpbHRlciBvciBUSFJFRS5MaW5lYXJGaWx0ZXIuIikpLCEwPT09dC5oYXMoIkVYVF90ZXh0dXJlX2ZpbHRlcl9hbmlzb3Ryb3BpYyIpKXtsZXQgVHQ9dC5nZXQoIkVYVF90ZXh0dXJlX2ZpbHRlcl9hbmlzb3Ryb3BpYyIpO2lmKFcudHlwZT09PVVnJiYhMT09PXQuaGFzKCJPRVNfdGV4dHVyZV9mbG9hdF9saW5lYXIiKXx8ITE9PT1hJiZXLnR5cGU9PT1sYiYmITE9PT10LmhhcygiT0VTX3RleHR1cmVfaGFsZl9mbG9hdF9saW5lYXIiKSlyZXR1cm47KFcuYW5pc290cm9weT4xfHxpLmdldChXKS5fX2N1cnJlbnRBbmlzb3Ryb3B5KSYmKG4udGV4UGFyYW1ldGVyZihlZSxUdC5URVhUVVJFX01BWF9BTklTT1RST1BZX0VYVCxNYXRoLm1pbihXLmFuaXNvdHJvcHksci5nZXRNYXhBbmlzb3Ryb3B5KCkpKSxpLmdldChXKS5fX2N1cnJlbnRBbmlzb3Ryb3B5PVcuYW5pc290cm9weSl9fWZ1bmN0aW9uIFVlKGVlLFcpe3ZvaWQgMD09PWVlLl9fd2ViZ2xJbml0JiYoZWUuX193ZWJnbEluaXQ9ITAsVy5hZGRFdmVudExpc3RlbmVyKCJkaXNwb3NlIixoZSksZWUuX193ZWJnbFRleHR1cmU9bi5jcmVhdGVUZXh0dXJlKCkscy5tZW1vcnkudGV4dHVyZXMrKyl9ZnVuY3Rpb24gQWUoZWUsVyxYZSl7bGV0IFR0PTM1NTM7Vy5pc0RhdGFUZXh0dXJlMkRBcnJheSYmKFR0PTM1ODY2KSxXLmlzRGF0YVRleHR1cmUzRCYmKFR0PTMyODc5KSxVZShlZSxXKSxlLmFjdGl2ZVRleHR1cmUoMzM5ODQrWGUpLGUuYmluZFRleHR1cmUoVHQsZWUuX193ZWJnbFRleHR1cmUpLG4ucGl4ZWxTdG9yZWkoMzc0NDAsVy5mbGlwWSksbi5waXhlbFN0b3JlaSgzNzQ0MSxXLnByZW11bHRpcGx5QWxwaGEpLG4ucGl4ZWxTdG9yZWkoMzMxNyxXLnVucGFja0FsaWdubWVudCksbi5waXhlbFN0b3JlaSgzNzQ0MywwKTtsZXQgbW49ZnVuY3Rpb24oZWUpe3JldHVybiFhJiYoZWUud3JhcFMhPT1FbHx8ZWUud3JhcFQhPT1FbHx8ZWUubWluRmlsdGVyIT09Wm8mJmVlLm1pbkZpbHRlciE9PUdzKX0oVykmJiExPT09RChXLmltYWdlKSxxZT1iKFcuaW1hZ2UsbW4sITEsdSk7cWU9aG4oVyxxZSk7bGV0IHduPUQocWUpfHxhLHluPW8uY29udmVydChXLmZvcm1hdCxXLmVuY29kaW5nKSx6dD1vLmNvbnZlcnQoVy50eXBlKSxVdD16KFcuaW50ZXJuYWxGb3JtYXQseW4senQsVy5lbmNvZGluZyxXLmlzVmlkZW9UZXh0dXJlKTtndChUdCxXLHduKTtsZXQgV24sZ2U9Vy5taXBtYXBzLGZuPWEmJiEwIT09Vy5pc1ZpZGVvVGV4dHVyZSxadD12b2lkIDA9PT1lZS5fX3ZlcnNpb24sTm49ZmUoVyxxZSx3bik7aWYoVy5pc0RlcHRoVGV4dHVyZSlVdD02NDAyLGE/VXQ9Vy50eXBlPT09VWc/MzYwMTI6MTAxND09PVcudHlwZT8zMzE5MDpXLnR5cGU9PT1jYj8zNTA1NjozMzE4OTpXLnR5cGU9PT1VZyYmY29uc29sZS5lcnJvcigiV2ViR0xSZW5kZXJlcjogRmxvYXRpbmcgcG9pbnQgZGVwdGggdGV4dHVyZSByZXF1aXJlcyBXZWJHTDIuIiksVy5mb3JtYXQ9PT1qZyYmNjQwMj09PVV0JiZXLnR5cGUhPT1XUyYmMTAxNCE9PVcudHlwZSYmKGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogVXNlIFVuc2lnbmVkU2hvcnRUeXBlIG9yIFVuc2lnbmVkSW50VHlwZSBmb3IgRGVwdGhGb3JtYXQgRGVwdGhUZXh0dXJlLiIpLFcudHlwZT1XUyx6dD1vLmNvbnZlcnQoVy50eXBlKSksVy5mb3JtYXQ9PT1oYiYmNjQwMj09PVV0JiYoVXQ9MzQwNDEsVy50eXBlIT09Y2ImJihjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IFVzZSBVbnNpZ25lZEludDI0OFR5cGUgZm9yIERlcHRoU3RlbmNpbEZvcm1hdCBEZXB0aFRleHR1cmUuIiksVy50eXBlPWNiLHp0PW8uY29udmVydChXLnR5cGUpKSksZm4mJlp0P2UudGV4U3RvcmFnZTJEKDM1NTMsMSxVdCxxZS53aWR0aCxxZS5oZWlnaHQpOmUudGV4SW1hZ2UyRCgzNTUzLDAsVXQscWUud2lkdGgscWUuaGVpZ2h0LDAseW4senQsbnVsbCk7ZWxzZSBpZihXLmlzRGF0YVRleHR1cmUpaWYoZ2UubGVuZ3RoPjAmJnduKXtmbiYmWnQmJmUudGV4U3RvcmFnZTJEKDM1NTMsTm4sVXQsZ2VbMF0ud2lkdGgsZ2VbMF0uaGVpZ2h0KTtmb3IobGV0IFplPTAsRG49Z2UubGVuZ3RoO1plPERuO1plKyspV249Z2VbWmVdLGZuP2UudGV4U3ViSW1hZ2UyRCgzNTUzLDAsMCwwLFduLndpZHRoLFduLmhlaWdodCx5bix6dCxXbi5kYXRhKTplLnRleEltYWdlMkQoMzU1MyxaZSxVdCxXbi53aWR0aCxXbi5oZWlnaHQsMCx5bix6dCxXbi5kYXRhKTtXLmdlbmVyYXRlTWlwbWFwcz0hMX1lbHNlIGZuPyhadCYmZS50ZXhTdG9yYWdlMkQoMzU1MyxObixVdCxxZS53aWR0aCxxZS5oZWlnaHQpLGUudGV4U3ViSW1hZ2UyRCgzNTUzLDAsMCwwLHFlLndpZHRoLHFlLmhlaWdodCx5bix6dCxxZS5kYXRhKSk6ZS50ZXhJbWFnZTJEKDM1NTMsMCxVdCxxZS53aWR0aCxxZS5oZWlnaHQsMCx5bix6dCxxZS5kYXRhKTtlbHNlIGlmKFcuaXNDb21wcmVzc2VkVGV4dHVyZSl7Zm4mJlp0JiZlLnRleFN0b3JhZ2UyRCgzNTUzLE5uLFV0LGdlWzBdLndpZHRoLGdlWzBdLmhlaWdodCk7Zm9yKGxldCBaZT0wLERuPWdlLmxlbmd0aDtaZTxEbjtaZSsrKVduPWdlW1plXSxXLmZvcm1hdCE9PWdhP251bGwhPT15bj9mbj9lLmNvbXByZXNzZWRUZXhTdWJJbWFnZTJEKDM1NTMsWmUsMCwwLFduLndpZHRoLFduLmhlaWdodCx5bixXbi5kYXRhKTplLmNvbXByZXNzZWRUZXhJbWFnZTJEKDM1NTMsWmUsVXQsV24ud2lkdGgsV24uaGVpZ2h0LDAsV24uZGF0YSk6Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBBdHRlbXB0IHRvIGxvYWQgdW5zdXBwb3J0ZWQgY29tcHJlc3NlZCB0ZXh0dXJlIGZvcm1hdCBpbiAudXBsb2FkVGV4dHVyZSgpIik6Zm4/ZS50ZXhTdWJJbWFnZTJEKDM1NTMsWmUsMCwwLFduLndpZHRoLFduLmhlaWdodCx5bix6dCxXbi5kYXRhKTplLnRleEltYWdlMkQoMzU1MyxaZSxVdCxXbi53aWR0aCxXbi5oZWlnaHQsMCx5bix6dCxXbi5kYXRhKX1lbHNlIGlmKFcuaXNEYXRhVGV4dHVyZTJEQXJyYXkpZm4/KFp0JiZlLnRleFN0b3JhZ2UzRCgzNTg2NixObixVdCxxZS53aWR0aCxxZS5oZWlnaHQscWUuZGVwdGgpLGUudGV4U3ViSW1hZ2UzRCgzNTg2NiwwLDAsMCwwLHFlLndpZHRoLHFlLmhlaWdodCxxZS5kZXB0aCx5bix6dCxxZS5kYXRhKSk6ZS50ZXhJbWFnZTNEKDM1ODY2LDAsVXQscWUud2lkdGgscWUuaGVpZ2h0LHFlLmRlcHRoLDAseW4senQscWUuZGF0YSk7ZWxzZSBpZihXLmlzRGF0YVRleHR1cmUzRClmbj8oWnQmJmUudGV4U3RvcmFnZTNEKDMyODc5LE5uLFV0LHFlLndpZHRoLHFlLmhlaWdodCxxZS5kZXB0aCksZS50ZXhTdWJJbWFnZTNEKDMyODc5LDAsMCwwLDAscWUud2lkdGgscWUuaGVpZ2h0LHFlLmRlcHRoLHluLHp0LHFlLmRhdGEpKTplLnRleEltYWdlM0QoMzI4NzksMCxVdCxxZS53aWR0aCxxZS5oZWlnaHQscWUuZGVwdGgsMCx5bix6dCxxZS5kYXRhKTtlbHNlIGlmKFcuaXNGcmFtZWJ1ZmZlclRleHR1cmUpZm4mJlp0P2UudGV4U3RvcmFnZTJEKDM1NTMsTm4sVXQscWUud2lkdGgscWUuaGVpZ2h0KTplLnRleEltYWdlMkQoMzU1MywwLFV0LHFlLndpZHRoLHFlLmhlaWdodCwwLHluLHp0LG51bGwpO2Vsc2UgaWYoZ2UubGVuZ3RoPjAmJnduKXtmbiYmWnQmJmUudGV4U3RvcmFnZTJEKDM1NTMsTm4sVXQsZ2VbMF0ud2lkdGgsZ2VbMF0uaGVpZ2h0KTtmb3IobGV0IFplPTAsRG49Z2UubGVuZ3RoO1plPERuO1plKyspV249Z2VbWmVdLGZuP2UudGV4U3ViSW1hZ2UyRCgzNTUzLFplLDAsMCx5bix6dCxXbik6ZS50ZXhJbWFnZTJEKDM1NTMsWmUsVXQseW4senQsV24pO1cuZ2VuZXJhdGVNaXBtYXBzPSExfWVsc2UgZm4/KFp0JiZlLnRleFN0b3JhZ2UyRCgzNTUzLE5uLFV0LHFlLndpZHRoLHFlLmhlaWdodCksZS50ZXhTdWJJbWFnZTJEKDM1NTMsMCwwLDAseW4senQscWUpKTplLnRleEltYWdlMkQoMzU1MywwLFV0LHluLHp0LHFlKTtrKFcsd24pJiZaKFR0KSxlZS5fX3ZlcnNpb249Vy52ZXJzaW9uLFcub25VcGRhdGUmJlcub25VcGRhdGUoVyl9ZnVuY3Rpb24gcHQoZWUsVyxYZSxUdCxtbil7bGV0IHFlPW8uY29udmVydChYZS5mb3JtYXQsWGUuZW5jb2RpbmcpLHduPW8uY29udmVydChYZS50eXBlKSx5bj16KFhlLmludGVybmFsRm9ybWF0LHFlLHduLFhlLmVuY29kaW5nKTtpLmdldChXKS5fX2hhc0V4dGVybmFsVGV4dHVyZXN8fCgzMjg3OT09PW1ufHwzNTg2Nj09PW1uP2UudGV4SW1hZ2UzRChtbiwwLHluLFcud2lkdGgsVy5oZWlnaHQsVy5kZXB0aCwwLHFlLHduLG51bGwpOmUudGV4SW1hZ2UyRChtbiwwLHluLFcud2lkdGgsVy5oZWlnaHQsMCxxZSx3bixudWxsKSksZS5iaW5kRnJhbWVidWZmZXIoMzYxNjAsZWUpLFcudXNlUmVuZGVyVG9UZXh0dXJlP2guZnJhbWVidWZmZXJUZXh0dXJlMkRNdWx0aXNhbXBsZUVYVCgzNjE2MCxUdCxtbixpLmdldChYZSkuX193ZWJnbFRleHR1cmUsMCxNdChXKSk6bi5mcmFtZWJ1ZmZlclRleHR1cmUyRCgzNjE2MCxUdCxtbixpLmdldChYZSkuX193ZWJnbFRleHR1cmUsMCksZS5iaW5kRnJhbWVidWZmZXIoMzYxNjAsbnVsbCl9ZnVuY3Rpb24gd3QoZWUsVyxYZSl7aWYobi5iaW5kUmVuZGVyYnVmZmVyKDM2MTYxLGVlKSxXLmRlcHRoQnVmZmVyJiYhVy5zdGVuY2lsQnVmZmVyKXtsZXQgVHQ9MzMxODk7aWYoWGV8fFcudXNlUmVuZGVyVG9UZXh0dXJlKXtsZXQgbW49Vy5kZXB0aFRleHR1cmU7bW4mJm1uLmlzRGVwdGhUZXh0dXJlJiYobW4udHlwZT09PVVnP1R0PTM2MDEyOjEwMTQ9PT1tbi50eXBlJiYoVHQ9MzMxOTApKTtsZXQgcWU9TXQoVyk7Vy51c2VSZW5kZXJUb1RleHR1cmU/aC5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGVFWFQoMzYxNjEscWUsVHQsVy53aWR0aCxXLmhlaWdodCk6bi5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGUoMzYxNjEscWUsVHQsVy53aWR0aCxXLmhlaWdodCl9ZWxzZSBuLnJlbmRlcmJ1ZmZlclN0b3JhZ2UoMzYxNjEsVHQsVy53aWR0aCxXLmhlaWdodCk7bi5mcmFtZWJ1ZmZlclJlbmRlcmJ1ZmZlcigzNjE2MCwzNjA5NiwzNjE2MSxlZSl9ZWxzZSBpZihXLmRlcHRoQnVmZmVyJiZXLnN0ZW5jaWxCdWZmZXIpe2xldCBUdD1NdChXKTtYZSYmVy51c2VSZW5kZXJidWZmZXI/bi5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGUoMzYxNjEsVHQsMzUwNTYsVy53aWR0aCxXLmhlaWdodCk6Vy51c2VSZW5kZXJUb1RleHR1cmU/aC5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGVFWFQoMzYxNjEsVHQsMzUwNTYsVy53aWR0aCxXLmhlaWdodCk6bi5yZW5kZXJidWZmZXJTdG9yYWdlKDM2MTYxLDM0MDQxLFcud2lkdGgsVy5oZWlnaHQpLG4uZnJhbWVidWZmZXJSZW5kZXJidWZmZXIoMzYxNjAsMzMzMDYsMzYxNjEsZWUpfWVsc2V7bGV0IFR0PSEwPT09Vy5pc1dlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzP1cudGV4dHVyZVswXTpXLnRleHR1cmUsbW49by5jb252ZXJ0KFR0LmZvcm1hdCxUdC5lbmNvZGluZykscWU9by5jb252ZXJ0KFR0LnR5cGUpLHduPXooVHQuaW50ZXJuYWxGb3JtYXQsbW4scWUsVHQuZW5jb2RpbmcpLHluPU10KFcpO1hlJiZXLnVzZVJlbmRlcmJ1ZmZlcj9uLnJlbmRlcmJ1ZmZlclN0b3JhZ2VNdWx0aXNhbXBsZSgzNjE2MSx5bix3bixXLndpZHRoLFcuaGVpZ2h0KTpXLnVzZVJlbmRlclRvVGV4dHVyZT9oLnJlbmRlcmJ1ZmZlclN0b3JhZ2VNdWx0aXNhbXBsZUVYVCgzNjE2MSx5bix3bixXLndpZHRoLFcuaGVpZ2h0KTpuLnJlbmRlcmJ1ZmZlclN0b3JhZ2UoMzYxNjEsd24sVy53aWR0aCxXLmhlaWdodCl9bi5iaW5kUmVuZGVyYnVmZmVyKDM2MTYxLG51bGwpfWZ1bmN0aW9uIHh0KGVlKXtsZXQgVz1pLmdldChlZSksWGU9ITA9PT1lZS5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldDtpZihlZS5kZXB0aFRleHR1cmUmJiFXLl9fYXV0b0FsbG9jYXRlRGVwdGhCdWZmZXIpe2lmKFhlKXRocm93IG5ldyBFcnJvcigidGFyZ2V0LmRlcHRoVGV4dHVyZSBub3Qgc3VwcG9ydGVkIGluIEN1YmUgcmVuZGVyIHRhcmdldHMiKTshZnVuY3Rpb24oZWUsVyl7aWYoVyYmVy5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldCl0aHJvdyBuZXcgRXJyb3IoIkRlcHRoIFRleHR1cmUgd2l0aCBjdWJlIHJlbmRlciB0YXJnZXRzIGlzIG5vdCBzdXBwb3J0ZWQiKTtpZihlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxlZSksIVcuZGVwdGhUZXh0dXJlfHwhVy5kZXB0aFRleHR1cmUuaXNEZXB0aFRleHR1cmUpdGhyb3cgbmV3IEVycm9yKCJyZW5kZXJUYXJnZXQuZGVwdGhUZXh0dXJlIG11c3QgYmUgYW4gaW5zdGFuY2Ugb2YgVEhSRUUuRGVwdGhUZXh0dXJlIik7KCFpLmdldChXLmRlcHRoVGV4dHVyZSkuX193ZWJnbFRleHR1cmV8fFcuZGVwdGhUZXh0dXJlLmltYWdlLndpZHRoIT09Vy53aWR0aHx8Vy5kZXB0aFRleHR1cmUuaW1hZ2UuaGVpZ2h0IT09Vy5oZWlnaHQpJiYoVy5kZXB0aFRleHR1cmUuaW1hZ2Uud2lkdGg9Vy53aWR0aCxXLmRlcHRoVGV4dHVyZS5pbWFnZS5oZWlnaHQ9Vy5oZWlnaHQsVy5kZXB0aFRleHR1cmUubmVlZHNVcGRhdGU9ITApLGFlKFcuZGVwdGhUZXh0dXJlLDApO2xldCBUdD1pLmdldChXLmRlcHRoVGV4dHVyZSkuX193ZWJnbFRleHR1cmUsbW49TXQoVyk7aWYoVy5kZXB0aFRleHR1cmUuZm9ybWF0PT09amcpVy51c2VSZW5kZXJUb1RleHR1cmU/aC5mcmFtZWJ1ZmZlclRleHR1cmUyRE11bHRpc2FtcGxlRVhUKDM2MTYwLDM2MDk2LDM1NTMsVHQsMCxtbik6bi5mcmFtZWJ1ZmZlclRleHR1cmUyRCgzNjE2MCwzNjA5NiwzNTUzLFR0LDApO2Vsc2V7aWYoVy5kZXB0aFRleHR1cmUuZm9ybWF0IT09aGIpdGhyb3cgbmV3IEVycm9yKCJVbmtub3duIGRlcHRoVGV4dHVyZSBmb3JtYXQiKTtXLnVzZVJlbmRlclRvVGV4dHVyZT9oLmZyYW1lYnVmZmVyVGV4dHVyZTJETXVsdGlzYW1wbGVFWFQoMzYxNjAsMzMzMDYsMzU1MyxUdCwwLG1uKTpuLmZyYW1lYnVmZmVyVGV4dHVyZTJEKDM2MTYwLDMzMzA2LDM1NTMsVHQsMCl9fShXLl9fd2ViZ2xGcmFtZWJ1ZmZlcixlZSl9ZWxzZSBpZihYZSl7Vy5fX3dlYmdsRGVwdGhidWZmZXI9W107Zm9yKGxldCBUdD0wO1R0PDY7VHQrKyllLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxXLl9fd2ViZ2xGcmFtZWJ1ZmZlcltUdF0pLFcuX193ZWJnbERlcHRoYnVmZmVyW1R0XT1uLmNyZWF0ZVJlbmRlcmJ1ZmZlcigpLHd0KFcuX193ZWJnbERlcHRoYnVmZmVyW1R0XSxlZSwhMSl9ZWxzZSBlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxXLl9fd2ViZ2xGcmFtZWJ1ZmZlciksVy5fX3dlYmdsRGVwdGhidWZmZXI9bi5jcmVhdGVSZW5kZXJidWZmZXIoKSx3dChXLl9fd2ViZ2xEZXB0aGJ1ZmZlcixlZSwhMSk7ZS5iaW5kRnJhbWVidWZmZXIoMzYxNjAsbnVsbCl9ZnVuY3Rpb24gTXQoZWUpe3JldHVybiBhJiYoZWUudXNlUmVuZGVyYnVmZmVyfHxlZS51c2VSZW5kZXJUb1RleHR1cmUpP01hdGgubWluKGQsZWUuc2FtcGxlcyk6MH1mdW5jdGlvbiBobihlZSxXKXtsZXQgWGU9ZWUuZW5jb2RpbmcsVHQ9ZWUuZm9ybWF0LG1uPWVlLnR5cGU7cmV0dXJuITA9PT1lZS5pc0NvbXByZXNzZWRUZXh0dXJlfHwhMD09PWVlLmlzVmlkZW9UZXh0dXJlfHwxMDM1PT09ZWUuZm9ybWF0fHxYZSE9PWJmJiYoWGU9PT1Xcj8hMT09PWE/ITA9PT10LmhhcygiRVhUX3NSR0IiKSYmVHQ9PT1nYT8oZWUuZm9ybWF0PTEwMzUsZWUubWluRmlsdGVyPUdzLGVlLmdlbmVyYXRlTWlwbWFwcz0hMSk6Vz1UcC5zUkdCVG9MaW5lYXIoVyk6KFR0IT09Z2F8fG1uIT09X2YpJiZjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMVGV4dHVyZXM6IHNSR0IgZW5jb2RlZCB0ZXh0dXJlcyBoYXZlIHRvIHVzZSBSR0JBRm9ybWF0IGFuZCBVbnNpZ25lZEJ5dGVUeXBlLiIpOmNvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMVGV4dHVyZXM6IFVuc3VwcG9ydGVkIHRleHR1cmUgZW5jb2Rpbmc6IixYZSkpLFd9bGV0IG9uPSExLGZpPSExO3RoaXMuYWxsb2NhdGVUZXh0dXJlVW5pdD1mdW5jdGlvbigpe2xldCBlZT1LO3JldHVybiBlZT49bCYmY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFRleHR1cmVzOiBUcnlpbmcgdG8gdXNlICIrZWUrIiB0ZXh0dXJlIHVuaXRzIHdoaWxlIHRoaXMgR1BVIHN1cHBvcnRzIG9ubHkgIitsKSxLKz0xLGVlfSx0aGlzLnJlc2V0VGV4dHVyZVVuaXRzPWZ1bmN0aW9uKCl7Sz0wfSx0aGlzLnNldFRleHR1cmUyRD1hZSx0aGlzLnNldFRleHR1cmUyREFycmF5PWZ1bmN0aW9uKGVlLFcpe2xldCBYZT1pLmdldChlZSk7ZWUudmVyc2lvbj4wJiZYZS5fX3ZlcnNpb24hPT1lZS52ZXJzaW9uP0FlKFhlLGVlLFcpOihlLmFjdGl2ZVRleHR1cmUoMzM5ODQrVyksZS5iaW5kVGV4dHVyZSgzNTg2NixYZS5fX3dlYmdsVGV4dHVyZSkpfSx0aGlzLnNldFRleHR1cmUzRD1mdW5jdGlvbihlZSxXKXtsZXQgWGU9aS5nZXQoZWUpO2VlLnZlcnNpb24+MCYmWGUuX192ZXJzaW9uIT09ZWUudmVyc2lvbj9BZShYZSxlZSxXKTooZS5hY3RpdmVUZXh0dXJlKDMzOTg0K1cpLGUuYmluZFRleHR1cmUoMzI4NzksWGUuX193ZWJnbFRleHR1cmUpKX0sdGhpcy5zZXRUZXh0dXJlQ3ViZT12ZSx0aGlzLnJlYmluZFRleHR1cmVzPWZ1bmN0aW9uKGVlLFcsWGUpe2xldCBUdD1pLmdldChlZSk7dm9pZCAwIT09VyYmcHQoVHQuX193ZWJnbEZyYW1lYnVmZmVyLGVlLGVlLnRleHR1cmUsMzYwNjQsMzU1Myksdm9pZCAwIT09WGUmJnh0KGVlKX0sdGhpcy5zZXR1cFJlbmRlclRhcmdldD1mdW5jdGlvbihlZSl7bGV0IFc9ZWUudGV4dHVyZSxYZT1pLmdldChlZSksVHQ9aS5nZXQoVyk7ZWUuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsdyksITAhPT1lZS5pc1dlYkdMTXVsdGlwbGVSZW5kZXJUYXJnZXRzJiYodm9pZCAwPT09VHQuX193ZWJnbFRleHR1cmUmJihUdC5fX3dlYmdsVGV4dHVyZT1uLmNyZWF0ZVRleHR1cmUoKSksVHQuX192ZXJzaW9uPVcudmVyc2lvbixzLm1lbW9yeS50ZXh0dXJlcysrKTtsZXQgbW49ITA9PT1lZS5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldCxxZT0hMD09PWVlLmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHMsd249Vy5pc0RhdGFUZXh0dXJlM0R8fFcuaXNEYXRhVGV4dHVyZTJEQXJyYXkseW49RChlZSl8fGE7aWYobW4pe1hlLl9fd2ViZ2xGcmFtZWJ1ZmZlcj1bXTtmb3IobGV0IHp0PTA7enQ8Njt6dCsrKVhlLl9fd2ViZ2xGcmFtZWJ1ZmZlclt6dF09bi5jcmVhdGVGcmFtZWJ1ZmZlcigpfWVsc2UgaWYoWGUuX193ZWJnbEZyYW1lYnVmZmVyPW4uY3JlYXRlRnJhbWVidWZmZXIoKSxxZSlpZihyLmRyYXdCdWZmZXJzKXtsZXQgenQ9ZWUudGV4dHVyZTtmb3IobGV0IFV0PTAsV249enQubGVuZ3RoO1V0PFduO1V0Kyspe2xldCBnZT1pLmdldCh6dFtVdF0pO3ZvaWQgMD09PWdlLl9fd2ViZ2xUZXh0dXJlJiYoZ2UuX193ZWJnbFRleHR1cmU9bi5jcmVhdGVUZXh0dXJlKCkscy5tZW1vcnkudGV4dHVyZXMrKyl9fWVsc2UgY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBXZWJHTE11bHRpcGxlUmVuZGVyVGFyZ2V0cyBjYW4gb25seSBiZSB1c2VkIHdpdGggV2ViR0wyIG9yIFdFQkdMX2RyYXdfYnVmZmVycyBleHRlbnNpb24uIik7ZWxzZSBpZihlZS51c2VSZW5kZXJidWZmZXIpaWYoYSl7WGUuX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyPW4uY3JlYXRlRnJhbWVidWZmZXIoKSxYZS5fX3dlYmdsQ29sb3JSZW5kZXJidWZmZXI9bi5jcmVhdGVSZW5kZXJidWZmZXIoKSxuLmJpbmRSZW5kZXJidWZmZXIoMzYxNjEsWGUuX193ZWJnbENvbG9yUmVuZGVyYnVmZmVyKTtsZXQgenQ9by5jb252ZXJ0KFcuZm9ybWF0LFcuZW5jb2RpbmcpLFV0PW8uY29udmVydChXLnR5cGUpLFduPXooVy5pbnRlcm5hbEZvcm1hdCx6dCxVdCxXLmVuY29kaW5nKSxnZT1NdChlZSk7bi5yZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGUoMzYxNjEsZ2UsV24sZWUud2lkdGgsZWUuaGVpZ2h0KSxlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxYZS5fX3dlYmdsTXVsdGlzYW1wbGVkRnJhbWVidWZmZXIpLG4uZnJhbWVidWZmZXJSZW5kZXJidWZmZXIoMzYxNjAsMzYwNjQsMzYxNjEsWGUuX193ZWJnbENvbG9yUmVuZGVyYnVmZmVyKSxuLmJpbmRSZW5kZXJidWZmZXIoMzYxNjEsbnVsbCksZWUuZGVwdGhCdWZmZXImJihYZS5fX3dlYmdsRGVwdGhSZW5kZXJidWZmZXI9bi5jcmVhdGVSZW5kZXJidWZmZXIoKSx3dChYZS5fX3dlYmdsRGVwdGhSZW5kZXJidWZmZXIsZWUsITApKSxlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxudWxsKX1lbHNlIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogV2ViR0xNdWx0aXNhbXBsZVJlbmRlclRhcmdldCBjYW4gb25seSBiZSB1c2VkIHdpdGggV2ViR0wyLiIpO2lmKG1uKXtlLmJpbmRUZXh0dXJlKDM0MDY3LFR0Ll9fd2ViZ2xUZXh0dXJlKSxndCgzNDA2NyxXLHluKTtmb3IobGV0IHp0PTA7enQ8Njt6dCsrKXB0KFhlLl9fd2ViZ2xGcmFtZWJ1ZmZlclt6dF0sZWUsVywzNjA2NCwzNDA2OSt6dCk7ayhXLHluKSYmWigzNDA2NyksZS51bmJpbmRUZXh0dXJlKCl9ZWxzZSBpZihxZSl7bGV0IHp0PWVlLnRleHR1cmU7Zm9yKGxldCBVdD0wLFduPXp0Lmxlbmd0aDtVdDxXbjtVdCsrKXtsZXQgZ2U9enRbVXRdLGZuPWkuZ2V0KGdlKTtlLmJpbmRUZXh0dXJlKDM1NTMsZm4uX193ZWJnbFRleHR1cmUpLGd0KDM1NTMsZ2UseW4pLHB0KFhlLl9fd2ViZ2xGcmFtZWJ1ZmZlcixlZSxnZSwzNjA2NCtVdCwzNTUzKSxrKGdlLHluKSYmWigzNTUzKX1lLnVuYmluZFRleHR1cmUoKX1lbHNle2xldCB6dD0zNTUzO3duJiYoYT96dD1XLmlzRGF0YVRleHR1cmUzRD8zMjg3OTozNTg2Njpjb25zb2xlLndhcm4oIlRIUkVFLkRhdGFUZXh0dXJlM0QgYW5kIFRIUkVFLkRhdGFUZXh0dXJlMkRBcnJheSBvbmx5IHN1cHBvcnRlZCB3aXRoIFdlYkdMMi4iKSksZS5iaW5kVGV4dHVyZSh6dCxUdC5fX3dlYmdsVGV4dHVyZSksZ3QoenQsVyx5bikscHQoWGUuX193ZWJnbEZyYW1lYnVmZmVyLGVlLFcsMzYwNjQsenQpLGsoVyx5bikmJlooenQpLGUudW5iaW5kVGV4dHVyZSgpfWVlLmRlcHRoQnVmZmVyJiZ4dChlZSl9LHRoaXMudXBkYXRlUmVuZGVyVGFyZ2V0TWlwbWFwPWZ1bmN0aW9uKGVlKXtsZXQgVz1EKGVlKXx8YSxYZT0hMD09PWVlLmlzV2ViR0xNdWx0aXBsZVJlbmRlclRhcmdldHM/ZWUudGV4dHVyZTpbZWUudGV4dHVyZV07Zm9yKGxldCBUdD0wLG1uPVhlLmxlbmd0aDtUdDxtbjtUdCsrKXtsZXQgcWU9WGVbVHRdO2lmKGsocWUsVykpe2xldCB3bj1lZS5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldD8zNDA2NzozNTUzLHluPWkuZ2V0KHFlKS5fX3dlYmdsVGV4dHVyZTtlLmJpbmRUZXh0dXJlKHduLHluKSxaKHduKSxlLnVuYmluZFRleHR1cmUoKX19fSx0aGlzLnVwZGF0ZU11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0PWZ1bmN0aW9uKGVlKXtpZihlZS51c2VSZW5kZXJidWZmZXIpaWYoYSl7bGV0IFc9ZWUud2lkdGgsWGU9ZWUuaGVpZ2h0LFR0PTE2Mzg0LG1uPVszNjA2NF0scWU9ZWUuc3RlbmNpbEJ1ZmZlcj8zMzMwNjozNjA5NjtlZS5kZXB0aEJ1ZmZlciYmbW4ucHVzaChxZSksZWUuaWdub3JlRGVwdGhGb3JNdWx0aXNhbXBsZUNvcHl8fChlZS5kZXB0aEJ1ZmZlciYmKFR0fD0yNTYpLGVlLnN0ZW5jaWxCdWZmZXImJihUdHw9MTAyNCkpO2xldCB3bj1pLmdldChlZSk7ZS5iaW5kRnJhbWVidWZmZXIoMzYwMDgsd24uX193ZWJnbE11bHRpc2FtcGxlZEZyYW1lYnVmZmVyKSxlLmJpbmRGcmFtZWJ1ZmZlcigzNjAwOSx3bi5fX3dlYmdsRnJhbWVidWZmZXIpLGVlLmlnbm9yZURlcHRoRm9yTXVsdGlzYW1wbGVDb3B5JiYobi5pbnZhbGlkYXRlRnJhbWVidWZmZXIoMzYwMDgsW3FlXSksbi5pbnZhbGlkYXRlRnJhbWVidWZmZXIoMzYwMDksW3FlXSkpLG4uYmxpdEZyYW1lYnVmZmVyKDAsMCxXLFhlLDAsMCxXLFhlLFR0LDk3MjgpLG4uaW52YWxpZGF0ZUZyYW1lYnVmZmVyKDM2MDA4LG1uKSxlLmJpbmRGcmFtZWJ1ZmZlcigzNjAwOCxudWxsKSxlLmJpbmRGcmFtZWJ1ZmZlcigzNjAwOSx3bi5fX3dlYmdsTXVsdGlzYW1wbGVkRnJhbWVidWZmZXIpfWVsc2UgY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBXZWJHTE11bHRpc2FtcGxlUmVuZGVyVGFyZ2V0IGNhbiBvbmx5IGJlIHVzZWQgd2l0aCBXZWJHTDIuIil9LHRoaXMuc2V0dXBEZXB0aFJlbmRlcmJ1ZmZlcj14dCx0aGlzLnNldHVwRnJhbWVCdWZmZXJUZXh0dXJlPXB0LHRoaXMuc2FmZVNldFRleHR1cmUyRD1mdW5jdGlvbihlZSxXKXtlZSYmZWUuaXNXZWJHTFJlbmRlclRhcmdldCYmKCExPT09b24mJihjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMVGV4dHVyZXMuc2FmZVNldFRleHR1cmUyRDogZG9uJ3QgdXNlIHJlbmRlciB0YXJnZXRzIGFzIHRleHR1cmVzLiBVc2UgdGhlaXIgLnRleHR1cmUgcHJvcGVydHkgaW5zdGVhZC4iKSxvbj0hMCksZWU9ZWUudGV4dHVyZSksYWUoZWUsVyl9LHRoaXMuc2FmZVNldFRleHR1cmVDdWJlPWZ1bmN0aW9uKGVlLFcpe2VlJiZlZS5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldCYmKCExPT09ZmkmJihjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMVGV4dHVyZXMuc2FmZVNldFRleHR1cmVDdWJlOiBkb24ndCB1c2UgY3ViZSByZW5kZXIgdGFyZ2V0cyBhcyB0ZXh0dXJlcy4gVXNlIHRoZWlyIC50ZXh0dXJlIHByb3BlcnR5IGluc3RlYWQuIiksZmk9ITApLGVlPWVlLnRleHR1cmUpLHZlKGVlLFcpfX1mdW5jdGlvbiBmOWUobix0LGUpe2xldCBpPWUuaXNXZWJHTDI7cmV0dXJue2NvbnZlcnQ6ZnVuY3Rpb24obyxzPW51bGwpe2xldCBhO2lmKG89PT1fZilyZXR1cm4gNTEyMTtpZigxMDE3PT09bylyZXR1cm4gMzI4MTk7aWYoMTAxOD09PW8pcmV0dXJuIDMyODIwO2lmKDEwMTA9PT1vKXJldHVybiA1MTIwO2lmKDEwMTE9PT1vKXJldHVybiA1MTIyO2lmKG89PT1XUylyZXR1cm4gNTEyMztpZigxMDEzPT09bylyZXR1cm4gNTEyNDtpZigxMDE0PT09bylyZXR1cm4gNTEyNTtpZihvPT09VWcpcmV0dXJuIDUxMjY7aWYobz09PWxiKXJldHVybiBpPzUxMzE6KGE9dC5nZXQoIk9FU190ZXh0dXJlX2hhbGZfZmxvYXQiKSxudWxsIT09YT9hLkhBTEZfRkxPQVRfT0VTOm51bGwpO2lmKDEwMjE9PT1vKXJldHVybiA2NDA2O2lmKG89PT1nYSlyZXR1cm4gNjQwODtpZigxMDI0PT09bylyZXR1cm4gNjQwOTtpZigxMDI1PT09bylyZXR1cm4gNjQxMDtpZihvPT09amcpcmV0dXJuIDY0MDI7aWYobz09PWhiKXJldHVybiAzNDA0MTtpZigxMDI4PT09bylyZXR1cm4gNjQwMztpZigxMDM1PT09bylyZXR1cm4gYT10LmdldCgiRVhUX3NSR0IiKSxudWxsIT09YT9hLlNSR0JfQUxQSEFfRVhUOm51bGw7aWYoMTAyOT09PW8pcmV0dXJuIDM2MjQ0O2lmKDEwMzA9PT1vKXJldHVybiAzMzMxOTtpZigxMDMxPT09bylyZXR1cm4gMzMzMjA7aWYoMTAzMz09PW8pcmV0dXJuIDM2MjQ5O2lmKDMzNzc2PT09b3x8MzM3Nzc9PT1vfHwzMzc3OD09PW98fDMzNzc5PT09bylpZihzPT09V3Ipe2lmKGE9dC5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjX3NyZ2IiKSxudWxsPT09YSlyZXR1cm4gbnVsbDtpZigzMzc3Nj09PW8pcmV0dXJuIGEuQ09NUFJFU1NFRF9TUkdCX1MzVENfRFhUMV9FWFQ7aWYoMzM3Nzc9PT1vKXJldHVybiBhLkNPTVBSRVNTRURfU1JHQl9BTFBIQV9TM1RDX0RYVDFfRVhUO2lmKDMzNzc4PT09bylyZXR1cm4gYS5DT01QUkVTU0VEX1NSR0JfQUxQSEFfUzNUQ19EWFQzX0VYVDtpZigzMzc3OT09PW8pcmV0dXJuIGEuQ09NUFJFU1NFRF9TUkdCX0FMUEhBX1MzVENfRFhUNV9FWFR9ZWxzZXtpZihhPXQuZ2V0KCJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfczN0YyIpLG51bGw9PT1hKXJldHVybiBudWxsO2lmKDMzNzc2PT09bylyZXR1cm4gYS5DT01QUkVTU0VEX1JHQl9TM1RDX0RYVDFfRVhUO2lmKDMzNzc3PT09bylyZXR1cm4gYS5DT01QUkVTU0VEX1JHQkFfUzNUQ19EWFQxX0VYVDtpZigzMzc3OD09PW8pcmV0dXJuIGEuQ09NUFJFU1NFRF9SR0JBX1MzVENfRFhUM19FWFQ7aWYoMzM3Nzk9PT1vKXJldHVybiBhLkNPTVBSRVNTRURfUkdCQV9TM1RDX0RYVDVfRVhUfWlmKDM1ODQwPT09b3x8MzU4NDE9PT1vfHwzNTg0Mj09PW98fDM1ODQzPT09byl7aWYoYT10LmdldCgiV0VCR0xfY29tcHJlc3NlZF90ZXh0dXJlX3B2cnRjIiksbnVsbD09PWEpcmV0dXJuIG51bGw7aWYoMzU4NDA9PT1vKXJldHVybiBhLkNPTVBSRVNTRURfUkdCX1BWUlRDXzRCUFBWMV9JTUc7aWYoMzU4NDE9PT1vKXJldHVybiBhLkNPTVBSRVNTRURfUkdCX1BWUlRDXzJCUFBWMV9JTUc7aWYoMzU4NDI9PT1vKXJldHVybiBhLkNPTVBSRVNTRURfUkdCQV9QVlJUQ180QlBQVjFfSU1HO2lmKDM1ODQzPT09bylyZXR1cm4gYS5DT01QUkVTU0VEX1JHQkFfUFZSVENfMkJQUFYxX0lNR31pZigzNjE5Nj09PW8pcmV0dXJuIGE9dC5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9ldGMxIiksbnVsbCE9PWE/YS5DT01QUkVTU0VEX1JHQl9FVEMxX1dFQkdMOm51bGw7aWYoMzc0OTI9PT1vfHwzNzQ5Nj09PW8pe2lmKGE9dC5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9ldGMiKSxudWxsPT09YSlyZXR1cm4gbnVsbDtpZigzNzQ5Mj09PW8pcmV0dXJuIHM9PT1Xcj9hLkNPTVBSRVNTRURfU1JHQjhfRVRDMjphLkNPTVBSRVNTRURfUkdCOF9FVEMyO2lmKDM3NDk2PT09bylyZXR1cm4gcz09PVdyP2EuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfRVRDMl9FQUM6YS5DT01QUkVTU0VEX1JHQkE4X0VUQzJfRUFDfWlmKDM3ODA4PT09b3x8Mzc4MDk9PT1vfHwzNzgxMD09PW98fDM3ODExPT09b3x8Mzc4MTI9PT1vfHwzNzgxMz09PW98fDM3ODE0PT09b3x8Mzc4MTU9PT1vfHwzNzgxNj09PW98fDM3ODE3PT09b3x8Mzc4MTg9PT1vfHwzNzgxOT09PW98fDM3ODIwPT09b3x8Mzc4MjE9PT1vKXtpZihhPXQuZ2V0KCJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfYXN0YyIpLG51bGw9PT1hKXJldHVybiBudWxsO2lmKDM3ODA4PT09bylyZXR1cm4gcz09PVdyP2EuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ180eDRfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfNHg0X0tIUjtpZigzNzgwOT09PW8pcmV0dXJuIHM9PT1Xcj9hLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfNXg0X0tIUjphLkNPTVBSRVNTRURfUkdCQV9BU1RDXzV4NF9LSFI7aWYoMzc4MTA9PT1vKXJldHVybiBzPT09V3I/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzV4NV9LSFI6YS5DT01QUkVTU0VEX1JHQkFfQVNUQ181eDVfS0hSO2lmKDM3ODExPT09bylyZXR1cm4gcz09PVdyP2EuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ182eDVfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfNng1X0tIUjtpZigzNzgxMj09PW8pcmV0dXJuIHM9PT1Xcj9hLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfNng2X0tIUjphLkNPTVBSRVNTRURfUkdCQV9BU1RDXzZ4Nl9LSFI7aWYoMzc4MTM9PT1vKXJldHVybiBzPT09V3I/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzh4NV9LSFI6YS5DT01QUkVTU0VEX1JHQkFfQVNUQ184eDVfS0hSO2lmKDM3ODE0PT09bylyZXR1cm4gcz09PVdyP2EuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ184eDZfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfOHg2X0tIUjtpZigzNzgxNT09PW8pcmV0dXJuIHM9PT1Xcj9hLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfOHg4X0tIUjphLkNPTVBSRVNTRURfUkdCQV9BU1RDXzh4OF9LSFI7aWYoMzc4MTY9PT1vKXJldHVybiBzPT09V3I/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDVfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfMTB4NV9LSFI7aWYoMzc4MTc9PT1vKXJldHVybiBzPT09V3I/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDZfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfMTB4Nl9LSFI7aWYoMzc4MTg9PT1vKXJldHVybiBzPT09V3I/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDhfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfMTB4OF9LSFI7aWYoMzc4MTk9PT1vKXJldHVybiBzPT09V3I/YS5DT01QUkVTU0VEX1NSR0I4X0FMUEhBOF9BU1RDXzEweDEwX0tIUjphLkNPTVBSRVNTRURfUkdCQV9BU1RDXzEweDEwX0tIUjtpZigzNzgyMD09PW8pcmV0dXJuIHM9PT1Xcj9hLkNPTVBSRVNTRURfU1JHQjhfQUxQSEE4X0FTVENfMTJ4MTBfS0hSOmEuQ09NUFJFU1NFRF9SR0JBX0FTVENfMTJ4MTBfS0hSO2lmKDM3ODIxPT09bylyZXR1cm4gcz09PVdyP2EuQ09NUFJFU1NFRF9TUkdCOF9BTFBIQThfQVNUQ18xMngxMl9LSFI6YS5DT01QUkVTU0VEX1JHQkFfQVNUQ18xMngxMl9LSFJ9aWYoMzY0OTI9PT1vKXtpZihhPXQuZ2V0KCJFWFRfdGV4dHVyZV9jb21wcmVzc2lvbl9icHRjIiksbnVsbD09PWEpcmV0dXJuIG51bGw7aWYoMzY0OTI9PT1vKXJldHVybiBzPT09V3I/YS5DT01QUkVTU0VEX1NSR0JfQUxQSEFfQlBUQ19VTk9STV9FWFQ6YS5DT01QUkVTU0VEX1JHQkFfQlBUQ19VTk9STV9FWFR9cmV0dXJuIG89PT1jYj9pPzM0MDQyOihhPXQuZ2V0KCJXRUJHTF9kZXB0aF90ZXh0dXJlIiksbnVsbCE9PWE/YS5VTlNJR05FRF9JTlRfMjRfOF9XRUJHTDpudWxsKTp2b2lkIDB9fX1oay5wcm90b3R5cGUuaXNNZXNoRGlzdGFuY2VNYXRlcmlhbD0hMDt2YXIgZms9Y2xhc3MgZXh0ZW5kcyBXc3tjb25zdHJ1Y3Rvcih0PVtdKXtzdXBlcigpLHRoaXMuY2FtZXJhcz10fX07ZmsucHJvdG90eXBlLmlzQXJyYXlDYW1lcmE9ITA7dmFyIHpnPWNsYXNzIGV4dGVuZHMgWGl7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMudHlwZT0iR3JvdXAifX07emcucHJvdG90eXBlLmlzR3JvdXA9ITA7dmFyIG05ZT17dHlwZToibW92ZSJ9LFVTPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5fdGFyZ2V0UmF5PW51bGwsdGhpcy5fZ3JpcD1udWxsLHRoaXMuX2hhbmQ9bnVsbH1nZXRIYW5kU3BhY2UoKXtyZXR1cm4gbnVsbD09PXRoaXMuX2hhbmQmJih0aGlzLl9oYW5kPW5ldyB6Zyx0aGlzLl9oYW5kLm1hdHJpeEF1dG9VcGRhdGU9ITEsdGhpcy5faGFuZC52aXNpYmxlPSExLHRoaXMuX2hhbmQuam9pbnRzPXt9LHRoaXMuX2hhbmQuaW5wdXRTdGF0ZT17cGluY2hpbmc6ITF9KSx0aGlzLl9oYW5kfWdldFRhcmdldFJheVNwYWNlKCl7cmV0dXJuIG51bGw9PT10aGlzLl90YXJnZXRSYXkmJih0aGlzLl90YXJnZXRSYXk9bmV3IHpnLHRoaXMuX3RhcmdldFJheS5tYXRyaXhBdXRvVXBkYXRlPSExLHRoaXMuX3RhcmdldFJheS52aXNpYmxlPSExLHRoaXMuX3RhcmdldFJheS5oYXNMaW5lYXJWZWxvY2l0eT0hMSx0aGlzLl90YXJnZXRSYXkubGluZWFyVmVsb2NpdHk9bmV3IGllLHRoaXMuX3RhcmdldFJheS5oYXNBbmd1bGFyVmVsb2NpdHk9ITEsdGhpcy5fdGFyZ2V0UmF5LmFuZ3VsYXJWZWxvY2l0eT1uZXcgaWUpLHRoaXMuX3RhcmdldFJheX1nZXRHcmlwU3BhY2UoKXtyZXR1cm4gbnVsbD09PXRoaXMuX2dyaXAmJih0aGlzLl9ncmlwPW5ldyB6Zyx0aGlzLl9ncmlwLm1hdHJpeEF1dG9VcGRhdGU9ITEsdGhpcy5fZ3JpcC52aXNpYmxlPSExLHRoaXMuX2dyaXAuaGFzTGluZWFyVmVsb2NpdHk9ITEsdGhpcy5fZ3JpcC5saW5lYXJWZWxvY2l0eT1uZXcgaWUsdGhpcy5fZ3JpcC5oYXNBbmd1bGFyVmVsb2NpdHk9ITEsdGhpcy5fZ3JpcC5hbmd1bGFyVmVsb2NpdHk9bmV3IGllKSx0aGlzLl9ncmlwfWRpc3BhdGNoRXZlbnQodCl7cmV0dXJuIG51bGwhPT10aGlzLl90YXJnZXRSYXkmJnRoaXMuX3RhcmdldFJheS5kaXNwYXRjaEV2ZW50KHQpLG51bGwhPT10aGlzLl9ncmlwJiZ0aGlzLl9ncmlwLmRpc3BhdGNoRXZlbnQodCksbnVsbCE9PXRoaXMuX2hhbmQmJnRoaXMuX2hhbmQuZGlzcGF0Y2hFdmVudCh0KSx0aGlzfWRpc2Nvbm5lY3QodCl7cmV0dXJuIHRoaXMuZGlzcGF0Y2hFdmVudCh7dHlwZToiZGlzY29ubmVjdGVkIixkYXRhOnR9KSxudWxsIT09dGhpcy5fdGFyZ2V0UmF5JiYodGhpcy5fdGFyZ2V0UmF5LnZpc2libGU9ITEpLG51bGwhPT10aGlzLl9ncmlwJiYodGhpcy5fZ3JpcC52aXNpYmxlPSExKSxudWxsIT09dGhpcy5faGFuZCYmKHRoaXMuX2hhbmQudmlzaWJsZT0hMSksdGhpc311cGRhdGUodCxlLGkpe2xldCByPW51bGwsbz1udWxsLHM9bnVsbCxhPXRoaXMuX3RhcmdldFJheSxsPXRoaXMuX2dyaXAsYz10aGlzLl9oYW5kO2lmKHQmJiJ2aXNpYmxlLWJsdXJyZWQiIT09ZS5zZXNzaW9uLnZpc2liaWxpdHlTdGF0ZSlpZihudWxsIT09YSYmKHI9ZS5nZXRQb3NlKHQudGFyZ2V0UmF5U3BhY2UsaSksbnVsbCE9PXImJihhLm1hdHJpeC5mcm9tQXJyYXkoci50cmFuc2Zvcm0ubWF0cml4KSxhLm1hdHJpeC5kZWNvbXBvc2UoYS5wb3NpdGlvbixhLnJvdGF0aW9uLGEuc2NhbGUpLHIubGluZWFyVmVsb2NpdHk/KGEuaGFzTGluZWFyVmVsb2NpdHk9ITAsYS5saW5lYXJWZWxvY2l0eS5jb3B5KHIubGluZWFyVmVsb2NpdHkpKTphLmhhc0xpbmVhclZlbG9jaXR5PSExLHIuYW5ndWxhclZlbG9jaXR5PyhhLmhhc0FuZ3VsYXJWZWxvY2l0eT0hMCxhLmFuZ3VsYXJWZWxvY2l0eS5jb3B5KHIuYW5ndWxhclZlbG9jaXR5KSk6YS5oYXNBbmd1bGFyVmVsb2NpdHk9ITEsdGhpcy5kaXNwYXRjaEV2ZW50KG05ZSkpKSxjJiZ0LmhhbmQpe3M9ITA7Zm9yKGxldCBtIG9mIHQuaGFuZC52YWx1ZXMoKSl7bGV0IHg9ZS5nZXRKb2ludFBvc2UobSxpKTtpZih2b2lkIDA9PT1jLmpvaW50c1ttLmpvaW50TmFtZV0pe2xldCBiPW5ldyB6ZztiLm1hdHJpeEF1dG9VcGRhdGU9ITEsYi52aXNpYmxlPSExLGMuam9pbnRzW20uam9pbnROYW1lXT1iLGMuYWRkKGIpfWxldCBnPWMuam9pbnRzW20uam9pbnROYW1lXTtudWxsIT09eCYmKGcubWF0cml4LmZyb21BcnJheSh4LnRyYW5zZm9ybS5tYXRyaXgpLGcubWF0cml4LmRlY29tcG9zZShnLnBvc2l0aW9uLGcucm90YXRpb24sZy5zY2FsZSksZy5qb2ludFJhZGl1cz14LnJhZGl1cyksZy52aXNpYmxlPW51bGwhPT14fWxldCBwPWMuam9pbnRzWyJpbmRleC1maW5nZXItdGlwIl0ucG9zaXRpb24uZGlzdGFuY2VUbyhjLmpvaW50c1sidGh1bWItdGlwIl0ucG9zaXRpb24pLGg9LjAyLGY9LjAwNTtjLmlucHV0U3RhdGUucGluY2hpbmcmJnA+aCtmPyhjLmlucHV0U3RhdGUucGluY2hpbmc9ITEsdGhpcy5kaXNwYXRjaEV2ZW50KHt0eXBlOiJwaW5jaGVuZCIsaGFuZGVkbmVzczp0LmhhbmRlZG5lc3MsdGFyZ2V0OnRoaXN9KSk6IWMuaW5wdXRTdGF0ZS5waW5jaGluZyYmcDw9aC1mJiYoYy5pbnB1dFN0YXRlLnBpbmNoaW5nPSEwLHRoaXMuZGlzcGF0Y2hFdmVudCh7dHlwZToicGluY2hzdGFydCIsaGFuZGVkbmVzczp0LmhhbmRlZG5lc3MsdGFyZ2V0OnRoaXN9KSl9ZWxzZSBudWxsIT09bCYmdC5ncmlwU3BhY2UmJihvPWUuZ2V0UG9zZSh0LmdyaXBTcGFjZSxpKSxudWxsIT09byYmKGwubWF0cml4LmZyb21BcnJheShvLnRyYW5zZm9ybS5tYXRyaXgpLGwubWF0cml4LmRlY29tcG9zZShsLnBvc2l0aW9uLGwucm90YXRpb24sbC5zY2FsZSksby5saW5lYXJWZWxvY2l0eT8obC5oYXNMaW5lYXJWZWxvY2l0eT0hMCxsLmxpbmVhclZlbG9jaXR5LmNvcHkoby5saW5lYXJWZWxvY2l0eSkpOmwuaGFzTGluZWFyVmVsb2NpdHk9ITEsby5hbmd1bGFyVmVsb2NpdHk/KGwuaGFzQW5ndWxhclZlbG9jaXR5PSEwLGwuYW5ndWxhclZlbG9jaXR5LmNvcHkoby5hbmd1bGFyVmVsb2NpdHkpKTpsLmhhc0FuZ3VsYXJWZWxvY2l0eT0hMSkpO3JldHVybiBudWxsIT09YSYmKGEudmlzaWJsZT1udWxsIT09ciksbnVsbCE9PWwmJihsLnZpc2libGU9bnVsbCE9PW8pLG51bGwhPT1jJiYoYy52aXNpYmxlPW51bGwhPT1zKSx0aGlzfX0sJFM9Y2xhc3MgZXh0ZW5kcyBIb3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyxhLGwsYyx1KXtpZigodT12b2lkIDAhPT11P3U6amcpIT09amcmJnUhPT1oYil0aHJvdyBuZXcgRXJyb3IoIkRlcHRoVGV4dHVyZSBmb3JtYXQgbXVzdCBiZSBlaXRoZXIgVEhSRUUuRGVwdGhGb3JtYXQgb3IgVEhSRUUuRGVwdGhTdGVuY2lsRm9ybWF0Iik7dm9pZCAwPT09aSYmdT09PWpnJiYoaT1XUyksdm9pZCAwPT09aSYmdT09PWhiJiYoaT1jYiksc3VwZXIobnVsbCxyLG8scyxhLGwsdSxpLGMpLHRoaXMuaW1hZ2U9e3dpZHRoOnQsaGVpZ2h0OmV9LHRoaXMubWFnRmlsdGVyPXZvaWQgMCE9PWE/YTpabyx0aGlzLm1pbkZpbHRlcj12b2lkIDAhPT1sP2w6Wm8sdGhpcy5mbGlwWT0hMSx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMX19OyRTLnByb3RvdHlwZS5pc0RlcHRoVGV4dHVyZT0hMDt2YXIgeTg9Y2xhc3MgZXh0ZW5kcyBFcHtjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKCk7bGV0IGk9dGhpcyxyPW51bGwsbz0xLHM9bnVsbCxhPSJsb2NhbC1mbG9vciIsbD10LmV4dGVuc2lvbnMuaGFzKCJXRUJHTF9tdWx0aXNhbXBsZWRfcmVuZGVyX3RvX3RleHR1cmUiKSxjPW51bGwsdT1udWxsLGQ9bnVsbCxwPW51bGwsaD0hMSxmPW51bGwsbT1lLmdldENvbnRleHRBdHRyaWJ1dGVzKCkseD1udWxsLGc9bnVsbCxiPVtdLEQ9bmV3IE1hcCxUPW5ldyBXcztULmxheWVycy5lbmFibGUoMSksVC52aWV3cG9ydD1uZXcgYXI7bGV0IGs9bmV3IFdzO2subGF5ZXJzLmVuYWJsZSgyKSxrLnZpZXdwb3J0PW5ldyBhcjtsZXQgWj1bVCxrXSx6PW5ldyBmazt6LmxheWVycy5lbmFibGUoMSksei5sYXllcnMuZW5hYmxlKDIpO2xldCBmZT1udWxsLHVlPW51bGw7ZnVuY3Rpb24gaGUodmUpe2xldCBEZT1ELmdldCh2ZS5pbnB1dFNvdXJjZSk7RGUmJkRlLmRpc3BhdGNoRXZlbnQoe3R5cGU6dmUudHlwZSxkYXRhOnZlLmlucHV0U291cmNlfSl9ZnVuY3Rpb24gdygpe0QuZm9yRWFjaChmdW5jdGlvbih2ZSxEZSl7dmUuZGlzY29ubmVjdChEZSl9KSxELmNsZWFyKCksZmU9bnVsbCx1ZT1udWxsLHQuc2V0UmVuZGVyVGFyZ2V0KHgpLHA9bnVsbCxkPW51bGwsdT1udWxsLHI9bnVsbCxnPW51bGwsSWUuc3RvcCgpLGkuaXNQcmVzZW50aW5nPSExLGkuZGlzcGF0Y2hFdmVudCh7dHlwZToic2Vzc2lvbmVuZCJ9KX1mdW5jdGlvbiBGKHZlKXtsZXQgRGU9ci5pbnB1dFNvdXJjZXM7Zm9yKGxldCBudD0wO250PGIubGVuZ3RoO250KyspRC5zZXQoRGVbbnRdLGJbbnRdKTtmb3IobGV0IG50PTA7bnQ8dmUucmVtb3ZlZC5sZW5ndGg7bnQrKyl7bGV0IGd0PXZlLnJlbW92ZWRbbnRdLFVlPUQuZ2V0KGd0KTtVZSYmKFVlLmRpc3BhdGNoRXZlbnQoe3R5cGU6ImRpc2Nvbm5lY3RlZCIsZGF0YTpndH0pLEQuZGVsZXRlKGd0KSl9Zm9yKGxldCBudD0wO250PHZlLmFkZGVkLmxlbmd0aDtudCsrKXtsZXQgZ3Q9dmUuYWRkZWRbbnRdLFVlPUQuZ2V0KGd0KTtVZSYmVWUuZGlzcGF0Y2hFdmVudCh7dHlwZToiY29ubmVjdGVkIixkYXRhOmd0fSl9fXRoaXMuY2FtZXJhQXV0b1VwZGF0ZT0hMCx0aGlzLmVuYWJsZWQ9ITEsdGhpcy5pc1ByZXNlbnRpbmc9ITEsdGhpcy5nZXRDb250cm9sbGVyPWZ1bmN0aW9uKHZlKXtsZXQgRGU9Ylt2ZV07cmV0dXJuIHZvaWQgMD09PURlJiYoRGU9bmV3IFVTLGJbdmVdPURlKSxEZS5nZXRUYXJnZXRSYXlTcGFjZSgpfSx0aGlzLmdldENvbnRyb2xsZXJHcmlwPWZ1bmN0aW9uKHZlKXtsZXQgRGU9Ylt2ZV07cmV0dXJuIHZvaWQgMD09PURlJiYoRGU9bmV3IFVTLGJbdmVdPURlKSxEZS5nZXRHcmlwU3BhY2UoKX0sdGhpcy5nZXRIYW5kPWZ1bmN0aW9uKHZlKXtsZXQgRGU9Ylt2ZV07cmV0dXJuIHZvaWQgMD09PURlJiYoRGU9bmV3IFVTLGJbdmVdPURlKSxEZS5nZXRIYW5kU3BhY2UoKX0sdGhpcy5zZXRGcmFtZWJ1ZmZlclNjYWxlRmFjdG9yPWZ1bmN0aW9uKHZlKXtvPXZlLCEwPT09aS5pc1ByZXNlbnRpbmcmJmNvbnNvbGUud2FybigiVEhSRUUuV2ViWFJNYW5hZ2VyOiBDYW5ub3QgY2hhbmdlIGZyYW1lYnVmZmVyIHNjYWxlIHdoaWxlIHByZXNlbnRpbmcuIil9LHRoaXMuc2V0UmVmZXJlbmNlU3BhY2VUeXBlPWZ1bmN0aW9uKHZlKXthPXZlLCEwPT09aS5pc1ByZXNlbnRpbmcmJmNvbnNvbGUud2FybigiVEhSRUUuV2ViWFJNYW5hZ2VyOiBDYW5ub3QgY2hhbmdlIHJlZmVyZW5jZSBzcGFjZSB0eXBlIHdoaWxlIHByZXNlbnRpbmcuIil9LHRoaXMuZ2V0UmVmZXJlbmNlU3BhY2U9ZnVuY3Rpb24oKXtyZXR1cm4gc30sdGhpcy5nZXRCYXNlTGF5ZXI9ZnVuY3Rpb24oKXtyZXR1cm4gbnVsbCE9PWQ/ZDpwfSx0aGlzLmdldEJpbmRpbmc9ZnVuY3Rpb24oKXtyZXR1cm4gdX0sdGhpcy5nZXRGcmFtZT1mdW5jdGlvbigpe3JldHVybiBmfSx0aGlzLmdldFNlc3Npb249ZnVuY3Rpb24oKXtyZXR1cm4gcn0sdGhpcy5zZXRTZXNzaW9uPWFzeW5jIGZ1bmN0aW9uKHZlKXtpZihyPXZlLG51bGwhPT1yKXtpZih4PXQuZ2V0UmVuZGVyVGFyZ2V0KCksci5hZGRFdmVudExpc3RlbmVyKCJzZWxlY3QiLGhlKSxyLmFkZEV2ZW50TGlzdGVuZXIoInNlbGVjdHN0YXJ0IixoZSksci5hZGRFdmVudExpc3RlbmVyKCJzZWxlY3RlbmQiLGhlKSxyLmFkZEV2ZW50TGlzdGVuZXIoInNxdWVlemUiLGhlKSxyLmFkZEV2ZW50TGlzdGVuZXIoInNxdWVlemVzdGFydCIsaGUpLHIuYWRkRXZlbnRMaXN0ZW5lcigic3F1ZWV6ZWVuZCIsaGUpLHIuYWRkRXZlbnRMaXN0ZW5lcigiZW5kIix3KSxyLmFkZEV2ZW50TGlzdGVuZXIoImlucHV0c291cmNlc2NoYW5nZSIsRiksITAhPT1tLnhyQ29tcGF0aWJsZSYmYXdhaXQgZS5tYWtlWFJDb21wYXRpYmxlKCksdm9pZCAwPT09ci5yZW5kZXJTdGF0ZS5sYXllcnN8fCExPT09dC5jYXBhYmlsaXRpZXMuaXNXZWJHTDIpcD1uZXcgWFJXZWJHTExheWVyKHIsZSx7YW50aWFsaWFzOnZvaWQgMCE9PXIucmVuZGVyU3RhdGUubGF5ZXJzfHxtLmFudGlhbGlhcyxhbHBoYTptLmFscGhhLGRlcHRoOm0uZGVwdGgsc3RlbmNpbDptLnN0ZW5jaWwsZnJhbWVidWZmZXJTY2FsZUZhY3RvcjpvfSksci51cGRhdGVSZW5kZXJTdGF0ZSh7YmFzZUxheWVyOnB9KSxnPW5ldyBXYShwLmZyYW1lYnVmZmVyV2lkdGgscC5mcmFtZWJ1ZmZlckhlaWdodCx7Zm9ybWF0OmdhLHR5cGU6X2YsZW5jb2Rpbmc6dC5vdXRwdXRFbmNvZGluZ30pO2Vsc2V7aD1tLmFudGlhbGlhcztsZXQgRGU9bnVsbCxudD1udWxsLGd0PW51bGw7bS5kZXB0aCYmKGd0PW0uc3RlbmNpbD8zNTA1NjozMzE5MCxEZT1tLnN0ZW5jaWw/aGI6amcsbnQ9bS5zdGVuY2lsP2NiOldTKTtsZXQgVWU9e2NvbG9yRm9ybWF0OnQub3V0cHV0RW5jb2Rpbmc9PT1Xcj8zNTkwNzozMjg1NixkZXB0aEZvcm1hdDpndCxzY2FsZUZhY3RvcjpvfTt1PW5ldyBYUldlYkdMQmluZGluZyhyLGUpLGQ9dS5jcmVhdGVQcm9qZWN0aW9uTGF5ZXIoVWUpLHIudXBkYXRlUmVuZGVyU3RhdGUoe2xheWVyczpbZF19KSxnPWg/bmV3IFhTKGQudGV4dHVyZVdpZHRoLGQudGV4dHVyZUhlaWdodCx7Zm9ybWF0OmdhLHR5cGU6X2YsZGVwdGhUZXh0dXJlOm5ldyAkUyhkLnRleHR1cmVXaWR0aCxkLnRleHR1cmVIZWlnaHQsbnQsdm9pZCAwLHZvaWQgMCx2b2lkIDAsdm9pZCAwLHZvaWQgMCx2b2lkIDAsRGUpLHN0ZW5jaWxCdWZmZXI6bS5zdGVuY2lsLGlnbm9yZURlcHRoOmQuaWdub3JlRGVwdGhWYWx1ZXMsdXNlUmVuZGVyVG9UZXh0dXJlOmwsZW5jb2Rpbmc6dC5vdXRwdXRFbmNvZGluZ30pOm5ldyBXYShkLnRleHR1cmVXaWR0aCxkLnRleHR1cmVIZWlnaHQse2Zvcm1hdDpnYSx0eXBlOl9mLGRlcHRoVGV4dHVyZTpuZXcgJFMoZC50ZXh0dXJlV2lkdGgsZC50ZXh0dXJlSGVpZ2h0LG50LHZvaWQgMCx2b2lkIDAsdm9pZCAwLHZvaWQgMCx2b2lkIDAsdm9pZCAwLERlKSxzdGVuY2lsQnVmZmVyOm0uc3RlbmNpbCxpZ25vcmVEZXB0aDpkLmlnbm9yZURlcHRoVmFsdWVzLGVuY29kaW5nOnQub3V0cHV0RW5jb2Rpbmd9KX1nLmlzWFJSZW5kZXJUYXJnZXQ9ITAsdGhpcy5zZXRGb3ZlYXRpb24oMSkscz1hd2FpdCByLnJlcXVlc3RSZWZlcmVuY2VTcGFjZShhKSxJZS5zZXRDb250ZXh0KHIpLEllLnN0YXJ0KCksaS5pc1ByZXNlbnRpbmc9ITAsaS5kaXNwYXRjaEV2ZW50KHt0eXBlOiJzZXNzaW9uc3RhcnQifSl9fTtsZXQgcT1uZXcgaWUsSz1uZXcgaWU7ZnVuY3Rpb24gWSh2ZSxEZSl7bnVsbD09PURlP3ZlLm1hdHJpeFdvcmxkLmNvcHkodmUubWF0cml4KTp2ZS5tYXRyaXhXb3JsZC5tdWx0aXBseU1hdHJpY2VzKERlLm1hdHJpeFdvcmxkLHZlLm1hdHJpeCksdmUubWF0cml4V29ybGRJbnZlcnNlLmNvcHkodmUubWF0cml4V29ybGQpLmludmVydCgpfXRoaXMudXBkYXRlQ2FtZXJhPWZ1bmN0aW9uKHZlKXtpZihudWxsPT09cilyZXR1cm47ei5uZWFyPWsubmVhcj1ULm5lYXI9dmUubmVhcix6LmZhcj1rLmZhcj1ULmZhcj12ZS5mYXIsKGZlIT09ei5uZWFyfHx1ZSE9PXouZmFyKSYmKHIudXBkYXRlUmVuZGVyU3RhdGUoe2RlcHRoTmVhcjp6Lm5lYXIsZGVwdGhGYXI6ei5mYXJ9KSxmZT16Lm5lYXIsdWU9ei5mYXIpO2xldCBEZT12ZS5wYXJlbnQsbnQ9ei5jYW1lcmFzO1koeixEZSk7Zm9yKGxldCBVZT0wO1VlPG50Lmxlbmd0aDtVZSsrKVkobnRbVWVdLERlKTt6Lm1hdHJpeFdvcmxkLmRlY29tcG9zZSh6LnBvc2l0aW9uLHoucXVhdGVybmlvbix6LnNjYWxlKSx2ZS5wb3NpdGlvbi5jb3B5KHoucG9zaXRpb24pLHZlLnF1YXRlcm5pb24uY29weSh6LnF1YXRlcm5pb24pLHZlLnNjYWxlLmNvcHkoei5zY2FsZSksdmUubWF0cml4LmNvcHkoei5tYXRyaXgpLHZlLm1hdHJpeFdvcmxkLmNvcHkoei5tYXRyaXhXb3JsZCk7bGV0IGd0PXZlLmNoaWxkcmVuO2ZvcihsZXQgVWU9MCxBZT1ndC5sZW5ndGg7VWU8QWU7VWUrKylndFtVZV0udXBkYXRlTWF0cml4V29ybGQoITApOzI9PT1udC5sZW5ndGg/ZnVuY3Rpb24odmUsRGUsbnQpe3Euc2V0RnJvbU1hdHJpeFBvc2l0aW9uKERlLm1hdHJpeFdvcmxkKSxLLnNldEZyb21NYXRyaXhQb3NpdGlvbihudC5tYXRyaXhXb3JsZCk7bGV0IGd0PXEuZGlzdGFuY2VUbyhLKSxVZT1EZS5wcm9qZWN0aW9uTWF0cml4LmVsZW1lbnRzLEFlPW50LnByb2plY3Rpb25NYXRyaXguZWxlbWVudHMsdG49VWVbMTRdLyhVZVsxMF0tMSkscHQ9VWVbMTRdLyhVZVsxMF0rMSksd3Q9KFVlWzldKzEpL1VlWzVdLFRlPShVZVs5XS0xKS9VZVs1XSx4dD0oVWVbOF0tMSkvVWVbMF0sbXQ9KEFlWzhdKzEpL0FlWzBdLGNlPXRuKnh0LGR0PXRuKm10LFdlPWd0LygteHQrbXQpLE10PVdlKi14dDtEZS5tYXRyaXhXb3JsZC5kZWNvbXBvc2UodmUucG9zaXRpb24sdmUucXVhdGVybmlvbix2ZS5zY2FsZSksdmUudHJhbnNsYXRlWChNdCksdmUudHJhbnNsYXRlWihXZSksdmUubWF0cml4V29ybGQuY29tcG9zZSh2ZS5wb3NpdGlvbix2ZS5xdWF0ZXJuaW9uLHZlLnNjYWxlKSx2ZS5tYXRyaXhXb3JsZEludmVyc2UuY29weSh2ZS5tYXRyaXhXb3JsZCkuaW52ZXJ0KCk7bGV0IGJ0PXRuK1dlLGhuPXB0K1dlO3ZlLnByb2plY3Rpb25NYXRyaXgubWFrZVBlcnNwZWN0aXZlKGNlLU10LGR0KyhndC1NdCksd3QqcHQvaG4qYnQsVGUqcHQvaG4qYnQsYnQsaG4pfSh6LFQsayk6ei5wcm9qZWN0aW9uTWF0cml4LmNvcHkoVC5wcm9qZWN0aW9uTWF0cml4KX0sdGhpcy5nZXRDYW1lcmE9ZnVuY3Rpb24oKXtyZXR1cm4gen0sdGhpcy5nZXRGb3ZlYXRpb249ZnVuY3Rpb24oKXtyZXR1cm4gbnVsbCE9PWQ/ZC5maXhlZEZvdmVhdGlvbjpudWxsIT09cD9wLmZpeGVkRm92ZWF0aW9uOnZvaWQgMH0sdGhpcy5zZXRGb3ZlYXRpb249ZnVuY3Rpb24odmUpe251bGwhPT1kJiYoZC5maXhlZEZvdmVhdGlvbj12ZSksbnVsbCE9PXAmJnZvaWQgMCE9PXAuZml4ZWRGb3ZlYXRpb24mJihwLmZpeGVkRm92ZWF0aW9uPXZlKX07bGV0IGFlPW51bGwsSWU9bmV3IGtkZTtJZS5zZXRBbmltYXRpb25Mb29wKGZ1bmN0aW9uKHZlLERlKXtpZihjPURlLmdldFZpZXdlclBvc2UocyksZj1EZSxudWxsIT09Yyl7bGV0IGd0PWMudmlld3M7bnVsbCE9PXAmJih0LnNldFJlbmRlclRhcmdldEZyYW1lYnVmZmVyKGcscC5mcmFtZWJ1ZmZlciksdC5zZXRSZW5kZXJUYXJnZXQoZykpO2xldCBVZT0hMTtndC5sZW5ndGghPT16LmNhbWVyYXMubGVuZ3RoJiYoei5jYW1lcmFzLmxlbmd0aD0wLFVlPSEwKTtmb3IobGV0IEFlPTA7QWU8Z3QubGVuZ3RoO0FlKyspe2xldCB0bj1ndFtBZV0scHQ9bnVsbDtpZihudWxsIT09cClwdD1wLmdldFZpZXdwb3J0KHRuKTtlbHNle2xldCBUZT11LmdldFZpZXdTdWJJbWFnZShkLHRuKTtwdD1UZS52aWV3cG9ydCwwPT09QWUmJih0LnNldFJlbmRlclRhcmdldFRleHR1cmVzKGcsVGUuY29sb3JUZXh0dXJlLGQuaWdub3JlRGVwdGhWYWx1ZXM/dm9pZCAwOlRlLmRlcHRoU3RlbmNpbFRleHR1cmUpLHQuc2V0UmVuZGVyVGFyZ2V0KGcpKX1sZXQgd3Q9WltBZV07d3QubWF0cml4LmZyb21BcnJheSh0bi50cmFuc2Zvcm0ubWF0cml4KSx3dC5wcm9qZWN0aW9uTWF0cml4LmZyb21BcnJheSh0bi5wcm9qZWN0aW9uTWF0cml4KSx3dC52aWV3cG9ydC5zZXQocHQueCxwdC55LHB0LndpZHRoLHB0LmhlaWdodCksMD09PUFlJiZ6Lm1hdHJpeC5jb3B5KHd0Lm1hdHJpeCksITA9PT1VZSYmei5jYW1lcmFzLnB1c2god3QpfX1sZXQgbnQ9ci5pbnB1dFNvdXJjZXM7Zm9yKGxldCBndD0wO2d0PGIubGVuZ3RoO2d0KyspYltndF0udXBkYXRlKG50W2d0XSxEZSxzKTthZSYmYWUodmUsRGUpLGY9bnVsbH0pLHRoaXMuc2V0QW5pbWF0aW9uTG9vcD1mdW5jdGlvbih2ZSl7YWU9dmV9LHRoaXMuZGlzcG9zZT1mdW5jdGlvbigpe319fTtmdW5jdGlvbiBnOWUobil7ZnVuY3Rpb24gaShnLGIpe2cub3BhY2l0eS52YWx1ZT1iLm9wYWNpdHksYi5jb2xvciYmZy5kaWZmdXNlLnZhbHVlLmNvcHkoYi5jb2xvciksYi5lbWlzc2l2ZSYmZy5lbWlzc2l2ZS52YWx1ZS5jb3B5KGIuZW1pc3NpdmUpLm11bHRpcGx5U2NhbGFyKGIuZW1pc3NpdmVJbnRlbnNpdHkpLGIubWFwJiYoZy5tYXAudmFsdWU9Yi5tYXApLGIuYWxwaGFNYXAmJihnLmFscGhhTWFwLnZhbHVlPWIuYWxwaGFNYXApLGIuc3BlY3VsYXJNYXAmJihnLnNwZWN1bGFyTWFwLnZhbHVlPWIuc3BlY3VsYXJNYXApLGIuYWxwaGFUZXN0PjAmJihnLmFscGhhVGVzdC52YWx1ZT1iLmFscGhhVGVzdCk7bGV0IFQsayxEPW4uZ2V0KGIpLmVudk1hcDtEJiYoZy5lbnZNYXAudmFsdWU9RCxnLmZsaXBFbnZNYXAudmFsdWU9RC5pc0N1YmVUZXh0dXJlJiYhMT09PUQuaXNSZW5kZXJUYXJnZXRUZXh0dXJlPy0xOjEsZy5yZWZsZWN0aXZpdHkudmFsdWU9Yi5yZWZsZWN0aXZpdHksZy5pb3IudmFsdWU9Yi5pb3IsZy5yZWZyYWN0aW9uUmF0aW8udmFsdWU9Yi5yZWZyYWN0aW9uUmF0aW8pLGIubGlnaHRNYXAmJihnLmxpZ2h0TWFwLnZhbHVlPWIubGlnaHRNYXAsZy5saWdodE1hcEludGVuc2l0eS52YWx1ZT1iLmxpZ2h0TWFwSW50ZW5zaXR5KSxiLmFvTWFwJiYoZy5hb01hcC52YWx1ZT1iLmFvTWFwLGcuYW9NYXBJbnRlbnNpdHkudmFsdWU9Yi5hb01hcEludGVuc2l0eSksYi5tYXA/VD1iLm1hcDpiLnNwZWN1bGFyTWFwP1Q9Yi5zcGVjdWxhck1hcDpiLmRpc3BsYWNlbWVudE1hcD9UPWIuZGlzcGxhY2VtZW50TWFwOmIubm9ybWFsTWFwP1Q9Yi5ub3JtYWxNYXA6Yi5idW1wTWFwP1Q9Yi5idW1wTWFwOmIucm91Z2huZXNzTWFwP1Q9Yi5yb3VnaG5lc3NNYXA6Yi5tZXRhbG5lc3NNYXA/VD1iLm1ldGFsbmVzc01hcDpiLmFscGhhTWFwP1Q9Yi5hbHBoYU1hcDpiLmVtaXNzaXZlTWFwP1Q9Yi5lbWlzc2l2ZU1hcDpiLmNsZWFyY29hdE1hcD9UPWIuY2xlYXJjb2F0TWFwOmIuY2xlYXJjb2F0Tm9ybWFsTWFwP1Q9Yi5jbGVhcmNvYXROb3JtYWxNYXA6Yi5jbGVhcmNvYXRSb3VnaG5lc3NNYXA/VD1iLmNsZWFyY29hdFJvdWdobmVzc01hcDpiLnNwZWN1bGFySW50ZW5zaXR5TWFwP1Q9Yi5zcGVjdWxhckludGVuc2l0eU1hcDpiLnNwZWN1bGFyQ29sb3JNYXA/VD1iLnNwZWN1bGFyQ29sb3JNYXA6Yi50cmFuc21pc3Npb25NYXA/VD1iLnRyYW5zbWlzc2lvbk1hcDpiLnRoaWNrbmVzc01hcD9UPWIudGhpY2tuZXNzTWFwOmIuc2hlZW5Db2xvck1hcD9UPWIuc2hlZW5Db2xvck1hcDpiLnNoZWVuUm91Z2huZXNzTWFwJiYoVD1iLnNoZWVuUm91Z2huZXNzTWFwKSx2b2lkIDAhPT1UJiYoVC5pc1dlYkdMUmVuZGVyVGFyZ2V0JiYoVD1ULnRleHR1cmUpLCEwPT09VC5tYXRyaXhBdXRvVXBkYXRlJiZULnVwZGF0ZU1hdHJpeCgpLGcudXZUcmFuc2Zvcm0udmFsdWUuY29weShULm1hdHJpeCkpLGIuYW9NYXA/az1iLmFvTWFwOmIubGlnaHRNYXAmJihrPWIubGlnaHRNYXApLHZvaWQgMCE9PWsmJihrLmlzV2ViR0xSZW5kZXJUYXJnZXQmJihrPWsudGV4dHVyZSksITA9PT1rLm1hdHJpeEF1dG9VcGRhdGUmJmsudXBkYXRlTWF0cml4KCksZy51djJUcmFuc2Zvcm0udmFsdWUuY29weShrLm1hdHJpeCkpfWZ1bmN0aW9uIGQoZyxiKXtnLnJvdWdobmVzcy52YWx1ZT1iLnJvdWdobmVzcyxnLm1ldGFsbmVzcy52YWx1ZT1iLm1ldGFsbmVzcyxiLnJvdWdobmVzc01hcCYmKGcucm91Z2huZXNzTWFwLnZhbHVlPWIucm91Z2huZXNzTWFwKSxiLm1ldGFsbmVzc01hcCYmKGcubWV0YWxuZXNzTWFwLnZhbHVlPWIubWV0YWxuZXNzTWFwKSxiLmVtaXNzaXZlTWFwJiYoZy5lbWlzc2l2ZU1hcC52YWx1ZT1iLmVtaXNzaXZlTWFwKSxiLmJ1bXBNYXAmJihnLmJ1bXBNYXAudmFsdWU9Yi5idW1wTWFwLGcuYnVtcFNjYWxlLnZhbHVlPWIuYnVtcFNjYWxlLDE9PT1iLnNpZGUmJihnLmJ1bXBTY2FsZS52YWx1ZSo9LTEpKSxiLm5vcm1hbE1hcCYmKGcubm9ybWFsTWFwLnZhbHVlPWIubm9ybWFsTWFwLGcubm9ybWFsU2NhbGUudmFsdWUuY29weShiLm5vcm1hbFNjYWxlKSwxPT09Yi5zaWRlJiZnLm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSxiLmRpc3BsYWNlbWVudE1hcCYmKGcuZGlzcGxhY2VtZW50TWFwLnZhbHVlPWIuZGlzcGxhY2VtZW50TWFwLGcuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9Yi5kaXNwbGFjZW1lbnRTY2FsZSxnLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9Yi5kaXNwbGFjZW1lbnRCaWFzKSxuLmdldChiKS5lbnZNYXAmJihnLmVudk1hcEludGVuc2l0eS52YWx1ZT1iLmVudk1hcEludGVuc2l0eSl9cmV0dXJue3JlZnJlc2hGb2dVbmlmb3JtczpmdW5jdGlvbihnLGIpe2cuZm9nQ29sb3IudmFsdWUuY29weShiLmNvbG9yKSxiLmlzRm9nPyhnLmZvZ05lYXIudmFsdWU9Yi5uZWFyLGcuZm9nRmFyLnZhbHVlPWIuZmFyKTpiLmlzRm9nRXhwMiYmKGcuZm9nRGVuc2l0eS52YWx1ZT1iLmRlbnNpdHkpfSxyZWZyZXNoTWF0ZXJpYWxVbmlmb3JtczpmdW5jdGlvbihnLGIsRCxULGspe2IuaXNNZXNoQmFzaWNNYXRlcmlhbD9pKGcsYik6Yi5pc01lc2hMYW1iZXJ0TWF0ZXJpYWw/KGkoZyxiKSxmdW5jdGlvbihnLGIpe2IuZW1pc3NpdmVNYXAmJihnLmVtaXNzaXZlTWFwLnZhbHVlPWIuZW1pc3NpdmVNYXApfShnLGIpKTpiLmlzTWVzaFRvb25NYXRlcmlhbD8oaShnLGIpLGZ1bmN0aW9uKGcsYil7Yi5ncmFkaWVudE1hcCYmKGcuZ3JhZGllbnRNYXAudmFsdWU9Yi5ncmFkaWVudE1hcCksYi5lbWlzc2l2ZU1hcCYmKGcuZW1pc3NpdmVNYXAudmFsdWU9Yi5lbWlzc2l2ZU1hcCksYi5idW1wTWFwJiYoZy5idW1wTWFwLnZhbHVlPWIuYnVtcE1hcCxnLmJ1bXBTY2FsZS52YWx1ZT1iLmJ1bXBTY2FsZSwxPT09Yi5zaWRlJiYoZy5idW1wU2NhbGUudmFsdWUqPS0xKSksYi5ub3JtYWxNYXAmJihnLm5vcm1hbE1hcC52YWx1ZT1iLm5vcm1hbE1hcCxnLm5vcm1hbFNjYWxlLnZhbHVlLmNvcHkoYi5ub3JtYWxTY2FsZSksMT09PWIuc2lkZSYmZy5ub3JtYWxTY2FsZS52YWx1ZS5uZWdhdGUoKSksYi5kaXNwbGFjZW1lbnRNYXAmJihnLmRpc3BsYWNlbWVudE1hcC52YWx1ZT1iLmRpc3BsYWNlbWVudE1hcCxnLmRpc3BsYWNlbWVudFNjYWxlLnZhbHVlPWIuZGlzcGxhY2VtZW50U2NhbGUsZy5kaXNwbGFjZW1lbnRCaWFzLnZhbHVlPWIuZGlzcGxhY2VtZW50Qmlhcyl9KGcsYikpOmIuaXNNZXNoUGhvbmdNYXRlcmlhbD8oaShnLGIpLGZ1bmN0aW9uKGcsYil7Zy5zcGVjdWxhci52YWx1ZS5jb3B5KGIuc3BlY3VsYXIpLGcuc2hpbmluZXNzLnZhbHVlPU1hdGgubWF4KGIuc2hpbmluZXNzLDFlLTQpLGIuZW1pc3NpdmVNYXAmJihnLmVtaXNzaXZlTWFwLnZhbHVlPWIuZW1pc3NpdmVNYXApLGIuYnVtcE1hcCYmKGcuYnVtcE1hcC52YWx1ZT1iLmJ1bXBNYXAsZy5idW1wU2NhbGUudmFsdWU9Yi5idW1wU2NhbGUsMT09PWIuc2lkZSYmKGcuYnVtcFNjYWxlLnZhbHVlKj0tMSkpLGIubm9ybWFsTWFwJiYoZy5ub3JtYWxNYXAudmFsdWU9Yi5ub3JtYWxNYXAsZy5ub3JtYWxTY2FsZS52YWx1ZS5jb3B5KGIubm9ybWFsU2NhbGUpLDE9PT1iLnNpZGUmJmcubm9ybWFsU2NhbGUudmFsdWUubmVnYXRlKCkpLGIuZGlzcGxhY2VtZW50TWFwJiYoZy5kaXNwbGFjZW1lbnRNYXAudmFsdWU9Yi5kaXNwbGFjZW1lbnRNYXAsZy5kaXNwbGFjZW1lbnRTY2FsZS52YWx1ZT1iLmRpc3BsYWNlbWVudFNjYWxlLGcuZGlzcGxhY2VtZW50Qmlhcy52YWx1ZT1iLmRpc3BsYWNlbWVudEJpYXMpfShnLGIpKTpiLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWw/KGkoZyxiKSxiLmlzTWVzaFBoeXNpY2FsTWF0ZXJpYWw/ZnVuY3Rpb24oZyxiLEQpe2QoZyxiKSxnLmlvci52YWx1ZT1iLmlvcixiLnNoZWVuPjAmJihnLnNoZWVuQ29sb3IudmFsdWUuY29weShiLnNoZWVuQ29sb3IpLm11bHRpcGx5U2NhbGFyKGIuc2hlZW4pLGcuc2hlZW5Sb3VnaG5lc3MudmFsdWU9Yi5zaGVlblJvdWdobmVzcyxiLnNoZWVuQ29sb3JNYXAmJihnLnNoZWVuQ29sb3JNYXAudmFsdWU9Yi5zaGVlbkNvbG9yTWFwKSxiLnNoZWVuUm91Z2huZXNzTWFwJiYoZy5zaGVlblJvdWdobmVzc01hcC52YWx1ZT1iLnNoZWVuUm91Z2huZXNzTWFwKSksYi5jbGVhcmNvYXQ+MCYmKGcuY2xlYXJjb2F0LnZhbHVlPWIuY2xlYXJjb2F0LGcuY2xlYXJjb2F0Um91Z2huZXNzLnZhbHVlPWIuY2xlYXJjb2F0Um91Z2huZXNzLGIuY2xlYXJjb2F0TWFwJiYoZy5jbGVhcmNvYXRNYXAudmFsdWU9Yi5jbGVhcmNvYXRNYXApLGIuY2xlYXJjb2F0Um91Z2huZXNzTWFwJiYoZy5jbGVhcmNvYXRSb3VnaG5lc3NNYXAudmFsdWU9Yi5jbGVhcmNvYXRSb3VnaG5lc3NNYXApLGIuY2xlYXJjb2F0Tm9ybWFsTWFwJiYoZy5jbGVhcmNvYXROb3JtYWxTY2FsZS52YWx1ZS5jb3B5KGIuY2xlYXJjb2F0Tm9ybWFsU2NhbGUpLGcuY2xlYXJjb2F0Tm9ybWFsTWFwLnZhbHVlPWIuY2xlYXJjb2F0Tm9ybWFsTWFwLDE9PT1iLnNpZGUmJmcuY2xlYXJjb2F0Tm9ybWFsU2NhbGUudmFsdWUubmVnYXRlKCkpKSxiLnRyYW5zbWlzc2lvbj4wJiYoZy50cmFuc21pc3Npb24udmFsdWU9Yi50cmFuc21pc3Npb24sZy50cmFuc21pc3Npb25TYW1wbGVyTWFwLnZhbHVlPUQudGV4dHVyZSxnLnRyYW5zbWlzc2lvblNhbXBsZXJTaXplLnZhbHVlLnNldChELndpZHRoLEQuaGVpZ2h0KSxiLnRyYW5zbWlzc2lvbk1hcCYmKGcudHJhbnNtaXNzaW9uTWFwLnZhbHVlPWIudHJhbnNtaXNzaW9uTWFwKSxnLnRoaWNrbmVzcy52YWx1ZT1iLnRoaWNrbmVzcyxiLnRoaWNrbmVzc01hcCYmKGcudGhpY2tuZXNzTWFwLnZhbHVlPWIudGhpY2tuZXNzTWFwKSxnLmF0dGVudWF0aW9uRGlzdGFuY2UudmFsdWU9Yi5hdHRlbnVhdGlvbkRpc3RhbmNlLGcuYXR0ZW51YXRpb25Db2xvci52YWx1ZS5jb3B5KGIuYXR0ZW51YXRpb25Db2xvcikpLGcuc3BlY3VsYXJJbnRlbnNpdHkudmFsdWU9Yi5zcGVjdWxhckludGVuc2l0eSxnLnNwZWN1bGFyQ29sb3IudmFsdWUuY29weShiLnNwZWN1bGFyQ29sb3IpLGIuc3BlY3VsYXJJbnRlbnNpdHlNYXAmJihnLnNwZWN1bGFySW50ZW5zaXR5TWFwLnZhbHVlPWIuc3BlY3VsYXJJbnRlbnNpdHlNYXApLGIuc3BlY3VsYXJDb2xvck1hcCYmKGcuc3BlY3VsYXJDb2xvck1hcC52YWx1ZT1iLnNwZWN1bGFyQ29sb3JNYXApfShnLGIsayk6ZChnLGIpKTpiLmlzTWVzaE1hdGNhcE1hdGVyaWFsPyhpKGcsYiksZnVuY3Rpb24oZyxiKXtiLm1hdGNhcCYmKGcubWF0Y2FwLnZhbHVlPWIubWF0Y2FwKSxiLmJ1bXBNYXAmJihnLmJ1bXBNYXAudmFsdWU9Yi5idW1wTWFwLGcuYnVtcFNjYWxlLnZhbHVlPWIuYnVtcFNjYWxlLDE9PT1iLnNpZGUmJihnLmJ1bXBTY2FsZS52YWx1ZSo9LTEpKSxiLm5vcm1hbE1hcCYmKGcubm9ybWFsTWFwLnZhbHVlPWIubm9ybWFsTWFwLGcubm9ybWFsU2NhbGUudmFsdWUuY29weShiLm5vcm1hbFNjYWxlKSwxPT09Yi5zaWRlJiZnLm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSxiLmRpc3BsYWNlbWVudE1hcCYmKGcuZGlzcGxhY2VtZW50TWFwLnZhbHVlPWIuZGlzcGxhY2VtZW50TWFwLGcuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9Yi5kaXNwbGFjZW1lbnRTY2FsZSxnLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9Yi5kaXNwbGFjZW1lbnRCaWFzKX0oZyxiKSk6Yi5pc01lc2hEZXB0aE1hdGVyaWFsPyhpKGcsYiksZnVuY3Rpb24oZyxiKXtiLmRpc3BsYWNlbWVudE1hcCYmKGcuZGlzcGxhY2VtZW50TWFwLnZhbHVlPWIuZGlzcGxhY2VtZW50TWFwLGcuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9Yi5kaXNwbGFjZW1lbnRTY2FsZSxnLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9Yi5kaXNwbGFjZW1lbnRCaWFzKX0oZyxiKSk6Yi5pc01lc2hEaXN0YW5jZU1hdGVyaWFsPyhpKGcsYiksZnVuY3Rpb24oZyxiKXtiLmRpc3BsYWNlbWVudE1hcCYmKGcuZGlzcGxhY2VtZW50TWFwLnZhbHVlPWIuZGlzcGxhY2VtZW50TWFwLGcuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9Yi5kaXNwbGFjZW1lbnRTY2FsZSxnLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9Yi5kaXNwbGFjZW1lbnRCaWFzKSxnLnJlZmVyZW5jZVBvc2l0aW9uLnZhbHVlLmNvcHkoYi5yZWZlcmVuY2VQb3NpdGlvbiksZy5uZWFyRGlzdGFuY2UudmFsdWU9Yi5uZWFyRGlzdGFuY2UsZy5mYXJEaXN0YW5jZS52YWx1ZT1iLmZhckRpc3RhbmNlfShnLGIpKTpiLmlzTWVzaE5vcm1hbE1hdGVyaWFsPyhpKGcsYiksZnVuY3Rpb24oZyxiKXtiLmJ1bXBNYXAmJihnLmJ1bXBNYXAudmFsdWU9Yi5idW1wTWFwLGcuYnVtcFNjYWxlLnZhbHVlPWIuYnVtcFNjYWxlLDE9PT1iLnNpZGUmJihnLmJ1bXBTY2FsZS52YWx1ZSo9LTEpKSxiLm5vcm1hbE1hcCYmKGcubm9ybWFsTWFwLnZhbHVlPWIubm9ybWFsTWFwLGcubm9ybWFsU2NhbGUudmFsdWUuY29weShiLm5vcm1hbFNjYWxlKSwxPT09Yi5zaWRlJiZnLm5vcm1hbFNjYWxlLnZhbHVlLm5lZ2F0ZSgpKSxiLmRpc3BsYWNlbWVudE1hcCYmKGcuZGlzcGxhY2VtZW50TWFwLnZhbHVlPWIuZGlzcGxhY2VtZW50TWFwLGcuZGlzcGxhY2VtZW50U2NhbGUudmFsdWU9Yi5kaXNwbGFjZW1lbnRTY2FsZSxnLmRpc3BsYWNlbWVudEJpYXMudmFsdWU9Yi5kaXNwbGFjZW1lbnRCaWFzKX0oZyxiKSk6Yi5pc0xpbmVCYXNpY01hdGVyaWFsPyhmdW5jdGlvbihnLGIpe2cuZGlmZnVzZS52YWx1ZS5jb3B5KGIuY29sb3IpLGcub3BhY2l0eS52YWx1ZT1iLm9wYWNpdHl9KGcsYiksYi5pc0xpbmVEYXNoZWRNYXRlcmlhbCYmZnVuY3Rpb24oZyxiKXtnLmRhc2hTaXplLnZhbHVlPWIuZGFzaFNpemUsZy50b3RhbFNpemUudmFsdWU9Yi5kYXNoU2l6ZStiLmdhcFNpemUsZy5zY2FsZS52YWx1ZT1iLnNjYWxlfShnLGIpKTpiLmlzUG9pbnRzTWF0ZXJpYWw/ZnVuY3Rpb24oZyxiLEQsVCl7bGV0IGs7Zy5kaWZmdXNlLnZhbHVlLmNvcHkoYi5jb2xvciksZy5vcGFjaXR5LnZhbHVlPWIub3BhY2l0eSxnLnNpemUudmFsdWU9Yi5zaXplKkQsZy5zY2FsZS52YWx1ZT0uNSpULGIubWFwJiYoZy5tYXAudmFsdWU9Yi5tYXApLGIuYWxwaGFNYXAmJihnLmFscGhhTWFwLnZhbHVlPWIuYWxwaGFNYXApLGIuYWxwaGFUZXN0PjAmJihnLmFscGhhVGVzdC52YWx1ZT1iLmFscGhhVGVzdCksYi5tYXA/az1iLm1hcDpiLmFscGhhTWFwJiYoaz1iLmFscGhhTWFwKSx2b2lkIDAhPT1rJiYoITA9PT1rLm1hdHJpeEF1dG9VcGRhdGUmJmsudXBkYXRlTWF0cml4KCksZy51dlRyYW5zZm9ybS52YWx1ZS5jb3B5KGsubWF0cml4KSl9KGcsYixELFQpOmIuaXNTcHJpdGVNYXRlcmlhbD9mdW5jdGlvbihnLGIpe2xldCBEO2cuZGlmZnVzZS52YWx1ZS5jb3B5KGIuY29sb3IpLGcub3BhY2l0eS52YWx1ZT1iLm9wYWNpdHksZy5yb3RhdGlvbi52YWx1ZT1iLnJvdGF0aW9uLGIubWFwJiYoZy5tYXAudmFsdWU9Yi5tYXApLGIuYWxwaGFNYXAmJihnLmFscGhhTWFwLnZhbHVlPWIuYWxwaGFNYXApLGIuYWxwaGFUZXN0PjAmJihnLmFscGhhVGVzdC52YWx1ZT1iLmFscGhhVGVzdCksYi5tYXA/RD1iLm1hcDpiLmFscGhhTWFwJiYoRD1iLmFscGhhTWFwKSx2b2lkIDAhPT1EJiYoITA9PT1ELm1hdHJpeEF1dG9VcGRhdGUmJkQudXBkYXRlTWF0cml4KCksZy51dlRyYW5zZm9ybS52YWx1ZS5jb3B5KEQubWF0cml4KSl9KGcsYik6Yi5pc1NoYWRvd01hdGVyaWFsPyhnLmNvbG9yLnZhbHVlLmNvcHkoYi5jb2xvciksZy5vcGFjaXR5LnZhbHVlPWIub3BhY2l0eSk6Yi5pc1NoYWRlck1hdGVyaWFsJiYoYi51bmlmb3Jtc05lZWRVcGRhdGU9ITEpfX19ZnVuY3Rpb24gaXIobj17fSl7bGV0IHQ9dm9pZCAwIT09bi5jYW52YXM/bi5jYW52YXM6ZnVuY3Rpb24oKXtsZXQgbj1ZUygiY2FudmFzIik7cmV0dXJuIG4uc3R5bGUuZGlzcGxheT0iYmxvY2siLG59KCksZT12b2lkIDAhPT1uLmNvbnRleHQ/bi5jb250ZXh0Om51bGwsaT12b2lkIDAhPT1uLmFscGhhJiZuLmFscGhhLHI9dm9pZCAwPT09bi5kZXB0aHx8bi5kZXB0aCxvPXZvaWQgMD09PW4uc3RlbmNpbHx8bi5zdGVuY2lsLHM9dm9pZCAwIT09bi5hbnRpYWxpYXMmJm4uYW50aWFsaWFzLGE9dm9pZCAwPT09bi5wcmVtdWx0aXBsaWVkQWxwaGF8fG4ucHJlbXVsdGlwbGllZEFscGhhLGw9dm9pZCAwIT09bi5wcmVzZXJ2ZURyYXdpbmdCdWZmZXImJm4ucHJlc2VydmVEcmF3aW5nQnVmZmVyLGM9dm9pZCAwIT09bi5wb3dlclByZWZlcmVuY2U/bi5wb3dlclByZWZlcmVuY2U6ImRlZmF1bHQiLHU9dm9pZCAwIT09bi5mYWlsSWZNYWpvclBlcmZvcm1hbmNlQ2F2ZWF0JiZuLmZhaWxJZk1ham9yUGVyZm9ybWFuY2VDYXZlYXQsZD1udWxsLHA9bnVsbCxoPVtdLGY9W107dGhpcy5kb21FbGVtZW50PXQsdGhpcy5kZWJ1Zz17Y2hlY2tTaGFkZXJFcnJvcnM6ITB9LHRoaXMuYXV0b0NsZWFyPSEwLHRoaXMuYXV0b0NsZWFyQ29sb3I9ITAsdGhpcy5hdXRvQ2xlYXJEZXB0aD0hMCx0aGlzLmF1dG9DbGVhclN0ZW5jaWw9ITAsdGhpcy5zb3J0T2JqZWN0cz0hMCx0aGlzLmNsaXBwaW5nUGxhbmVzPVtdLHRoaXMubG9jYWxDbGlwcGluZ0VuYWJsZWQ9ITEsdGhpcy5vdXRwdXRFbmNvZGluZz1iZix0aGlzLnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzPSExLHRoaXMudG9uZU1hcHBpbmc9MCx0aGlzLnRvbmVNYXBwaW5nRXhwb3N1cmU9MTtsZXQgbT10aGlzLHg9ITEsZz0wLGI9MCxEPW51bGwsVD0tMSxrPW51bGwsWj1uZXcgYXIsej1uZXcgYXIsZmU9bnVsbCx1ZT10LndpZHRoLGhlPXQuaGVpZ2h0LHc9MSxGPW51bGwscT1udWxsLEs9bmV3IGFyKDAsMCx1ZSxoZSksZGU9bmV3IGFyKDAsMCx1ZSxoZSksWT0hMSxhZT1uZXcgZ2IsbGU9ITEsSWU9ITEsdmU9bnVsbCxEZT1uZXcgUm4sbnQ9bmV3IGllLGd0PXtiYWNrZ3JvdW5kOm51bGwsZm9nOm51bGwsZW52aXJvbm1lbnQ6bnVsbCxvdmVycmlkZU1hdGVyaWFsOm51bGwsaXNTY2VuZTohMH07ZnVuY3Rpb24gVWUoKXtyZXR1cm4gbnVsbD09PUQ/dzoxfWxldCBwdCx3dCxUZSx4dCxtdCxjZSxkdCxXZSxNdCxidCxobixvbixmaSxXaSxxaSxlZSxXLFhlLFR0LG1uLHFlLHduLHluLEFlPWU7ZnVuY3Rpb24gdG4oSSxYKXtmb3IobGV0ICQ9MDskPEkubGVuZ3RoOyQrKyl7bGV0IG1lPXQuZ2V0Q29udGV4dChJWyRdLFgpO2lmKG51bGwhPT1tZSlyZXR1cm4gbWV9cmV0dXJuIG51bGx9dHJ5e2xldCBJPXthbHBoYTohMCxkZXB0aDpyLHN0ZW5jaWw6byxhbnRpYWxpYXM6cyxwcmVtdWx0aXBsaWVkQWxwaGE6YSxwcmVzZXJ2ZURyYXdpbmdCdWZmZXI6bCxwb3dlclByZWZlcmVuY2U6YyxmYWlsSWZNYWpvclBlcmZvcm1hbmNlQ2F2ZWF0OnV9O2lmKCJzZXRBdHRyaWJ1dGUiaW4gdCYmdC5zZXRBdHRyaWJ1dGUoImRhdGEtZW5naW5lIiwidGhyZWUuanMgcjEzNyIpLHQuYWRkRXZlbnRMaXN0ZW5lcigid2ViZ2xjb250ZXh0bG9zdCIsV24sITEpLHQuYWRkRXZlbnRMaXN0ZW5lcigid2ViZ2xjb250ZXh0cmVzdG9yZWQiLGdlLCExKSxudWxsPT09QWUpe2xldCBYPVsid2ViZ2wyIiwid2ViZ2wiLCJleHBlcmltZW50YWwtd2ViZ2wiXTtpZighMD09PW0uaXNXZWJHTDFSZW5kZXJlciYmWC5zaGlmdCgpLEFlPXRuKFgsSSksbnVsbD09PUFlKXRocm93IHRuKFgpP25ldyBFcnJvcigiRXJyb3IgY3JlYXRpbmcgV2ViR0wgY29udGV4dCB3aXRoIHlvdXIgc2VsZWN0ZWQgYXR0cmlidXRlcy4iKTpuZXcgRXJyb3IoIkVycm9yIGNyZWF0aW5nIFdlYkdMIGNvbnRleHQuIil9dm9pZCAwPT09QWUuZ2V0U2hhZGVyUHJlY2lzaW9uRm9ybWF0JiYoQWUuZ2V0U2hhZGVyUHJlY2lzaW9uRm9ybWF0PWZ1bmN0aW9uKCl7cmV0dXJue3JhbmdlTWluOjEscmFuZ2VNYXg6MSxwcmVjaXNpb246MX19KX1jYXRjaChJKXt0aHJvdyBjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAiK0kubWVzc2FnZSksSX1mdW5jdGlvbiB6dCgpe3B0PW5ldyBWV2UoQWUpLHd0PW5ldyBPV2UoQWUscHQsbikscHQuaW5pdCh3dCksd249bmV3IGY5ZShBZSxwdCx3dCksVGU9bmV3IHA5ZShBZSxwdCx3dCkseHQ9bmV3IHpXZShBZSksbXQ9bmV3IHQ5ZSxjZT1uZXcgaDllKEFlLHB0LFRlLG10LHd0LHduLHh0KSxkdD1uZXcgRldlKG0pLFdlPW5ldyBCV2UobSksTXQ9bmV3IGlHZShBZSx3dCkseW49bmV3IFBXZShBZSxwdCxNdCx3dCksYnQ9bmV3IEhXZShBZSxNdCx4dCx5biksaG49bmV3IHFXZShBZSxidCxNdCx4dCksVHQ9bmV3IFdXZShBZSx3dCxjZSksZWU9bmV3IGtXZShtdCksb249bmV3IGU5ZShtLGR0LFdlLHB0LHd0LHluLGVlKSxmaT1uZXcgZzllKG10KSxXaT1uZXcgaTllLHFpPW5ldyBjOWUocHQsd3QpLFhlPW5ldyBJV2UobSxkdCxUZSxobixpLGEpLFc9bmV3IEdkZShtLGhuLHd0KSxtbj1uZXcgUldlKEFlLHB0LHh0LHd0KSxxZT1uZXcgVVdlKEFlLHB0LHh0LHd0KSx4dC5wcm9ncmFtcz1vbi5wcm9ncmFtcyxtLmNhcGFiaWxpdGllcz13dCxtLmV4dGVuc2lvbnM9cHQsbS5wcm9wZXJ0aWVzPW10LG0ucmVuZGVyTGlzdHM9V2ksbS5zaGFkb3dNYXA9VyxtLnN0YXRlPVRlLG0uaW5mbz14dH16dCgpO2xldCBVdD1uZXcgeTgobSxBZSk7ZnVuY3Rpb24gV24oSSl7SS5wcmV2ZW50RGVmYXVsdCgpLGNvbnNvbGUubG9nKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBDb250ZXh0IExvc3QuIikseD0hMH1mdW5jdGlvbiBnZSgpe2NvbnNvbGUubG9nKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiBDb250ZXh0IFJlc3RvcmVkLiIpLHg9ITE7bGV0IEk9eHQuYXV0b1Jlc2V0LFg9Vy5lbmFibGVkLCQ9Vy5hdXRvVXBkYXRlLG5lPVcubmVlZHNVcGRhdGUsbWU9Vy50eXBlO3p0KCkseHQuYXV0b1Jlc2V0PUksVy5lbmFibGVkPVgsVy5hdXRvVXBkYXRlPSQsVy5uZWVkc1VwZGF0ZT1uZSxXLnR5cGU9bWV9ZnVuY3Rpb24gZm4oSSl7bGV0IFg9SS50YXJnZXQ7WC5yZW1vdmVFdmVudExpc3RlbmVyKCJkaXNwb3NlIixmbiksZnVuY3Rpb24oSSl7KGZ1bmN0aW9uKEkpe2xldCBYPW10LmdldChJKS5wcm9ncmFtczt2b2lkIDAhPT1YJiYoWC5mb3JFYWNoKGZ1bmN0aW9uKCQpe29uLnJlbGVhc2VQcm9ncmFtKCQpfSksSS5pc1NoYWRlck1hdGVyaWFsJiZvbi5yZWxlYXNlU2hhZGVyQ2FjaGUoSSkpfSkoSSksbXQucmVtb3ZlKEkpfShYKX10aGlzLnhyPVV0LHRoaXMuZ2V0Q29udGV4dD1mdW5jdGlvbigpe3JldHVybiBBZX0sdGhpcy5nZXRDb250ZXh0QXR0cmlidXRlcz1mdW5jdGlvbigpe3JldHVybiBBZS5nZXRDb250ZXh0QXR0cmlidXRlcygpfSx0aGlzLmZvcmNlQ29udGV4dExvc3M9ZnVuY3Rpb24oKXtsZXQgST1wdC5nZXQoIldFQkdMX2xvc2VfY29udGV4dCIpO0kmJkkubG9zZUNvbnRleHQoKX0sdGhpcy5mb3JjZUNvbnRleHRSZXN0b3JlPWZ1bmN0aW9uKCl7bGV0IEk9cHQuZ2V0KCJXRUJHTF9sb3NlX2NvbnRleHQiKTtJJiZJLnJlc3RvcmVDb250ZXh0KCl9LHRoaXMuZ2V0UGl4ZWxSYXRpbz1mdW5jdGlvbigpe3JldHVybiB3fSx0aGlzLnNldFBpeGVsUmF0aW89ZnVuY3Rpb24oSSl7dm9pZCAwIT09SSYmKHc9SSx0aGlzLnNldFNpemUodWUsaGUsITEpKX0sdGhpcy5nZXRTaXplPWZ1bmN0aW9uKEkpe3JldHVybiBJLnNldCh1ZSxoZSl9LHRoaXMuc2V0U2l6ZT1mdW5jdGlvbihJLFgsJCl7VXQuaXNQcmVzZW50aW5nP2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogQ2FuJ3QgY2hhbmdlIHNpemUgd2hpbGUgVlIgZGV2aWNlIGlzIHByZXNlbnRpbmcuIik6KHVlPUksaGU9WCx0LndpZHRoPU1hdGguZmxvb3IoSSp3KSx0LmhlaWdodD1NYXRoLmZsb29yKFgqdyksITEhPT0kJiYodC5zdHlsZS53aWR0aD1JKyJweCIsdC5zdHlsZS5oZWlnaHQ9WCsicHgiKSx0aGlzLnNldFZpZXdwb3J0KDAsMCxJLFgpKX0sdGhpcy5nZXREcmF3aW5nQnVmZmVyU2l6ZT1mdW5jdGlvbihJKXtyZXR1cm4gSS5zZXQodWUqdyxoZSp3KS5mbG9vcigpfSx0aGlzLnNldERyYXdpbmdCdWZmZXJTaXplPWZ1bmN0aW9uKEksWCwkKXt1ZT1JLGhlPVgsdz0kLHQud2lkdGg9TWF0aC5mbG9vcihJKiQpLHQuaGVpZ2h0PU1hdGguZmxvb3IoWCokKSx0aGlzLnNldFZpZXdwb3J0KDAsMCxJLFgpfSx0aGlzLmdldEN1cnJlbnRWaWV3cG9ydD1mdW5jdGlvbihJKXtyZXR1cm4gSS5jb3B5KFopfSx0aGlzLmdldFZpZXdwb3J0PWZ1bmN0aW9uKEkpe3JldHVybiBJLmNvcHkoSyl9LHRoaXMuc2V0Vmlld3BvcnQ9ZnVuY3Rpb24oSSxYLCQsbmUpe0kuaXNWZWN0b3I0P0suc2V0KEkueCxJLnksSS56LEkudyk6Sy5zZXQoSSxYLCQsbmUpLFRlLnZpZXdwb3J0KFouY29weShLKS5tdWx0aXBseVNjYWxhcih3KS5mbG9vcigpKX0sdGhpcy5nZXRTY2lzc29yPWZ1bmN0aW9uKEkpe3JldHVybiBJLmNvcHkoZGUpfSx0aGlzLnNldFNjaXNzb3I9ZnVuY3Rpb24oSSxYLCQsbmUpe0kuaXNWZWN0b3I0P2RlLnNldChJLngsSS55LEkueixJLncpOmRlLnNldChJLFgsJCxuZSksVGUuc2Npc3Nvcih6LmNvcHkoZGUpLm11bHRpcGx5U2NhbGFyKHcpLmZsb29yKCkpfSx0aGlzLmdldFNjaXNzb3JUZXN0PWZ1bmN0aW9uKCl7cmV0dXJuIFl9LHRoaXMuc2V0U2Npc3NvclRlc3Q9ZnVuY3Rpb24oSSl7VGUuc2V0U2Npc3NvclRlc3QoWT1JKX0sdGhpcy5zZXRPcGFxdWVTb3J0PWZ1bmN0aW9uKEkpe0Y9SX0sdGhpcy5zZXRUcmFuc3BhcmVudFNvcnQ9ZnVuY3Rpb24oSSl7cT1JfSx0aGlzLmdldENsZWFyQ29sb3I9ZnVuY3Rpb24oSSl7cmV0dXJuIEkuY29weShYZS5nZXRDbGVhckNvbG9yKCkpfSx0aGlzLnNldENsZWFyQ29sb3I9ZnVuY3Rpb24oKXtYZS5zZXRDbGVhckNvbG9yLmFwcGx5KFhlLGFyZ3VtZW50cyl9LHRoaXMuZ2V0Q2xlYXJBbHBoYT1mdW5jdGlvbigpe3JldHVybiBYZS5nZXRDbGVhckFscGhhKCl9LHRoaXMuc2V0Q2xlYXJBbHBoYT1mdW5jdGlvbigpe1hlLnNldENsZWFyQWxwaGEuYXBwbHkoWGUsYXJndW1lbnRzKX0sdGhpcy5jbGVhcj1mdW5jdGlvbihJLFgsJCl7bGV0IG5lPTA7KHZvaWQgMD09PUl8fEkpJiYobmV8PTE2Mzg0KSwodm9pZCAwPT09WHx8WCkmJihuZXw9MjU2KSwodm9pZCAwPT09JHx8JCkmJihuZXw9MTAyNCksQWUuY2xlYXIobmUpfSx0aGlzLmNsZWFyQ29sb3I9ZnVuY3Rpb24oKXt0aGlzLmNsZWFyKCEwLCExLCExKX0sdGhpcy5jbGVhckRlcHRoPWZ1bmN0aW9uKCl7dGhpcy5jbGVhcighMSwhMCwhMSl9LHRoaXMuY2xlYXJTdGVuY2lsPWZ1bmN0aW9uKCl7dGhpcy5jbGVhcighMSwhMSwhMCl9LHRoaXMuZGlzcG9zZT1mdW5jdGlvbigpe3QucmVtb3ZlRXZlbnRMaXN0ZW5lcigid2ViZ2xjb250ZXh0bG9zdCIsV24sITEpLHQucmVtb3ZlRXZlbnRMaXN0ZW5lcigid2ViZ2xjb250ZXh0cmVzdG9yZWQiLGdlLCExKSxXaS5kaXNwb3NlKCkscWkuZGlzcG9zZSgpLG10LmRpc3Bvc2UoKSxkdC5kaXNwb3NlKCksV2UuZGlzcG9zZSgpLGhuLmRpc3Bvc2UoKSx5bi5kaXNwb3NlKCksb24uZGlzcG9zZSgpLFV0LmRpc3Bvc2UoKSxVdC5yZW1vdmVFdmVudExpc3RlbmVyKCJzZXNzaW9uc3RhcnQiLE1pKSxVdC5yZW1vdmVFdmVudExpc3RlbmVyKCJzZXNzaW9uZW5kIixUciksdmUmJih2ZS5kaXNwb3NlKCksdmU9bnVsbCksRXMuc3RvcCgpfSx0aGlzLnJlbmRlckJ1ZmZlckRpcmVjdD1mdW5jdGlvbihJLFgsJCxuZSxtZSxLZSl7bnVsbD09PVgmJihYPWd0KTtsZXQgbHQ9bWUuaXNNZXNoJiZtZS5tYXRyaXhXb3JsZC5kZXRlcm1pbmFudCgpPDAsSmU9ZnVuY3Rpb24oSSxYLCQsbmUsbWUpeyEwIT09WC5pc1NjZW5lJiYoWD1ndCksY2UucmVzZXRUZXh0dXJlVW5pdHMoKTtsZXQgS2U9WC5mb2csSmU9bnVsbD09PUQ/bS5vdXRwdXRFbmNvZGluZzohMD09PUQuaXNYUlJlbmRlclRhcmdldD9ELnRleHR1cmUuZW5jb2Rpbmc6YmYsZnQ9KG5lLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWw/V2U6ZHQpLmdldChuZS5lbnZNYXB8fChuZS5pc01lc2hTdGFuZGFyZE1hdGVyaWFsP1guZW52aXJvbm1lbnQ6bnVsbCkpLEN0PSEwPT09bmUudmVydGV4Q29sb3JzJiYhISQuYXR0cmlidXRlcy5jb2xvciYmND09PSQuYXR0cmlidXRlcy5jb2xvci5pdGVtU2l6ZSxJdD0hIW5lLm5vcm1hbE1hcCYmISEkLmF0dHJpYnV0ZXMudGFuZ2VudCxOdD0hISQubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uLGJuPSEhJC5tb3JwaEF0dHJpYnV0ZXMubm9ybWFsLHJyPSQubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uPyQubW9ycGhBdHRyaWJ1dGVzLnBvc2l0aW9uLmxlbmd0aDowLEFpPW5lLnRvbmVNYXBwZWQ/bS50b25lTWFwcGluZzowLE1uPW10LmdldChuZSksSG49cC5zdGF0ZS5saWdodHM7ITAhPT1sZXx8ITAhPT1JZSYmST09PWt8fGVlLnNldFN0YXRlKG5lLEksST09PWsmJm5lLmlkPT09VCk7bGV0IE90PSExO25lLnZlcnNpb249PT1Nbi5fX3ZlcnNpb24/KE1uLm5lZWRzTGlnaHRzJiZNbi5saWdodHNTdGF0ZVZlcnNpb24hPT1Ibi5zdGF0ZS52ZXJzaW9ufHxNbi5vdXRwdXRFbmNvZGluZyE9PUplfHxtZS5pc0luc3RhbmNlZE1lc2gmJiExPT09TW4uaW5zdGFuY2luZ3x8IW1lLmlzSW5zdGFuY2VkTWVzaCYmITA9PT1Nbi5pbnN0YW5jaW5nfHxtZS5pc1NraW5uZWRNZXNoJiYhMT09PU1uLnNraW5uaW5nfHwhbWUuaXNTa2lubmVkTWVzaCYmITA9PT1Nbi5za2lubmluZ3x8TW4uZW52TWFwIT09ZnR8fG5lLmZvZyYmTW4uZm9nIT09S2V8fHZvaWQgMCE9PU1uLm51bUNsaXBwaW5nUGxhbmVzJiYoTW4ubnVtQ2xpcHBpbmdQbGFuZXMhPT1lZS5udW1QbGFuZXN8fE1uLm51bUludGVyc2VjdGlvbiE9PWVlLm51bUludGVyc2VjdGlvbil8fE1uLnZlcnRleEFscGhhcyE9PUN0fHxNbi52ZXJ0ZXhUYW5nZW50cyE9PUl0fHxNbi5tb3JwaFRhcmdldHMhPT1OdHx8TW4ubW9ycGhOb3JtYWxzIT09Ym58fE1uLnRvbmVNYXBwaW5nIT09QWl8fCEwPT09d3QuaXNXZWJHTDImJk1uLm1vcnBoVGFyZ2V0c0NvdW50IT09cnIpJiYoT3Q9ITApOihPdD0hMCxNbi5fX3ZlcnNpb249bmUudmVyc2lvbik7bGV0IHdpPU1uLmN1cnJlbnRQcm9ncmFtOyEwPT09T3QmJih3aT1leChuZSxYLG1lKSk7bGV0IGFpPSExLFluPSExLCRuPSExLFl0PXdpLmdldFVuaWZvcm1zKCksWWk9TW4udW5pZm9ybXM7aWYoVGUudXNlUHJvZ3JhbSh3aS5wcm9ncmFtKSYmKGFpPSEwLFluPSEwLCRuPSEwKSxuZS5pZCE9PVQmJihUPW5lLmlkLFluPSEwKSxhaXx8ayE9PUkpe2lmKFl0LnNldFZhbHVlKEFlLCJwcm9qZWN0aW9uTWF0cml4IixJLnByb2plY3Rpb25NYXRyaXgpLHd0LmxvZ2FyaXRobWljRGVwdGhCdWZmZXImJll0LnNldFZhbHVlKEFlLCJsb2dEZXB0aEJ1ZkZDIiwyLyhNYXRoLmxvZyhJLmZhcisxKS9NYXRoLkxOMikpLGshPT1JJiYoaz1JLFluPSEwLCRuPSEwKSxuZS5pc1NoYWRlck1hdGVyaWFsfHxuZS5pc01lc2hQaG9uZ01hdGVyaWFsfHxuZS5pc01lc2hUb29uTWF0ZXJpYWx8fG5lLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWx8fG5lLmVudk1hcCl7bGV0IEFuPVl0Lm1hcC5jYW1lcmFQb3NpdGlvbjt2b2lkIDAhPT1BbiYmQW4uc2V0VmFsdWUoQWUsbnQuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKEkubWF0cml4V29ybGQpKX0obmUuaXNNZXNoUGhvbmdNYXRlcmlhbHx8bmUuaXNNZXNoVG9vbk1hdGVyaWFsfHxuZS5pc01lc2hMYW1iZXJ0TWF0ZXJpYWx8fG5lLmlzTWVzaEJhc2ljTWF0ZXJpYWx8fG5lLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWx8fG5lLmlzU2hhZGVyTWF0ZXJpYWwpJiZZdC5zZXRWYWx1ZShBZSwiaXNPcnRob2dyYXBoaWMiLCEwPT09SS5pc09ydGhvZ3JhcGhpY0NhbWVyYSksKG5lLmlzTWVzaFBob25nTWF0ZXJpYWx8fG5lLmlzTWVzaFRvb25NYXRlcmlhbHx8bmUuaXNNZXNoTGFtYmVydE1hdGVyaWFsfHxuZS5pc01lc2hCYXNpY01hdGVyaWFsfHxuZS5pc01lc2hTdGFuZGFyZE1hdGVyaWFsfHxuZS5pc1NoYWRlck1hdGVyaWFsfHxuZS5pc1NoYWRvd01hdGVyaWFsfHxtZS5pc1NraW5uZWRNZXNoKSYmWXQuc2V0VmFsdWUoQWUsInZpZXdNYXRyaXgiLEkubWF0cml4V29ybGRJbnZlcnNlKX1pZihtZS5pc1NraW5uZWRNZXNoKXtZdC5zZXRPcHRpb25hbChBZSxtZSwiYmluZE1hdHJpeCIpLFl0LnNldE9wdGlvbmFsKEFlLG1lLCJiaW5kTWF0cml4SW52ZXJzZSIpO2xldCBBbj1tZS5za2VsZXRvbjtBbiYmKHd0LmZsb2F0VmVydGV4VGV4dHVyZXM/KG51bGw9PT1Bbi5ib25lVGV4dHVyZSYmQW4uY29tcHV0ZUJvbmVUZXh0dXJlKCksWXQuc2V0VmFsdWUoQWUsImJvbmVUZXh0dXJlIixBbi5ib25lVGV4dHVyZSxjZSksWXQuc2V0VmFsdWUoQWUsImJvbmVUZXh0dXJlU2l6ZSIsQW4uYm9uZVRleHR1cmVTaXplKSk6WXQuc2V0T3B0aW9uYWwoQWUsQW4sImJvbmVNYXRyaWNlcyIpKX1yZXR1cm4hISQmJih2b2lkIDAhPT0kLm1vcnBoQXR0cmlidXRlcy5wb3NpdGlvbnx8dm9pZCAwIT09JC5tb3JwaEF0dHJpYnV0ZXMubm9ybWFsKSYmVHQudXBkYXRlKG1lLCQsbmUsd2kpLChZbnx8TW4ucmVjZWl2ZVNoYWRvdyE9PW1lLnJlY2VpdmVTaGFkb3cpJiYoTW4ucmVjZWl2ZVNoYWRvdz1tZS5yZWNlaXZlU2hhZG93LFl0LnNldFZhbHVlKEFlLCJyZWNlaXZlU2hhZG93IixtZS5yZWNlaXZlU2hhZG93KSksWW4mJihZdC5zZXRWYWx1ZShBZSwidG9uZU1hcHBpbmdFeHBvc3VyZSIsbS50b25lTWFwcGluZ0V4cG9zdXJlKSxNbi5uZWVkc0xpZ2h0cyYmZnVuY3Rpb24oSSxYKXtJLmFtYmllbnRMaWdodENvbG9yLm5lZWRzVXBkYXRlPVgsSS5saWdodFByb2JlLm5lZWRzVXBkYXRlPVgsSS5kaXJlY3Rpb25hbExpZ2h0cy5uZWVkc1VwZGF0ZT1YLEkuZGlyZWN0aW9uYWxMaWdodFNoYWRvd3MubmVlZHNVcGRhdGU9WCxJLnBvaW50TGlnaHRzLm5lZWRzVXBkYXRlPVgsSS5wb2ludExpZ2h0U2hhZG93cy5uZWVkc1VwZGF0ZT1YLEkuc3BvdExpZ2h0cy5uZWVkc1VwZGF0ZT1YLEkuc3BvdExpZ2h0U2hhZG93cy5uZWVkc1VwZGF0ZT1YLEkucmVjdEFyZWFMaWdodHMubmVlZHNVcGRhdGU9WCxJLmhlbWlzcGhlcmVMaWdodHMubmVlZHNVcGRhdGU9WH0oWWksJG4pLEtlJiZuZS5mb2cmJmZpLnJlZnJlc2hGb2dVbmlmb3JtcyhZaSxLZSksZmkucmVmcmVzaE1hdGVyaWFsVW5pZm9ybXMoWWksbmUsdyxoZSx2ZSkseWYudXBsb2FkKEFlLE1uLnVuaWZvcm1zTGlzdCxZaSxjZSkpLG5lLmlzU2hhZGVyTWF0ZXJpYWwmJiEwPT09bmUudW5pZm9ybXNOZWVkVXBkYXRlJiYoeWYudXBsb2FkKEFlLE1uLnVuaWZvcm1zTGlzdCxZaSxjZSksbmUudW5pZm9ybXNOZWVkVXBkYXRlPSExKSxuZS5pc1Nwcml0ZU1hdGVyaWFsJiZZdC5zZXRWYWx1ZShBZSwiY2VudGVyIixtZS5jZW50ZXIpLFl0LnNldFZhbHVlKEFlLCJtb2RlbFZpZXdNYXRyaXgiLG1lLm1vZGVsVmlld01hdHJpeCksWXQuc2V0VmFsdWUoQWUsIm5vcm1hbE1hdHJpeCIsbWUubm9ybWFsTWF0cml4KSxZdC5zZXRWYWx1ZShBZSwibW9kZWxNYXRyaXgiLG1lLm1hdHJpeFdvcmxkKSx3aX0oSSxYLCQsbmUsbWUpO1RlLnNldE1hdGVyaWFsKG5lLGx0KTtsZXQgZnQ9JC5pbmRleCxDdD0kLmF0dHJpYnV0ZXMucG9zaXRpb247aWYobnVsbD09PWZ0KXtpZih2b2lkIDA9PT1DdHx8MD09PUN0LmNvdW50KXJldHVybn1lbHNlIGlmKDA9PT1mdC5jb3VudClyZXR1cm47bGV0IEl0PTE7ITA9PT1uZS53aXJlZnJhbWUmJihmdD1idC5nZXRXaXJlZnJhbWVBdHRyaWJ1dGUoJCksSXQ9MikseW4uc2V0dXAobWUsbmUsSmUsJCxmdCk7bGV0IE50LGJuPW1uO251bGwhPT1mdCYmKE50PU10LmdldChmdCksYm49cWUsYm4uc2V0SW5kZXgoTnQpKTtsZXQgcnI9bnVsbCE9PWZ0P2Z0LmNvdW50OkN0LmNvdW50LEFpPSQuZHJhd1JhbmdlLnN0YXJ0Kkl0LE1uPSQuZHJhd1JhbmdlLmNvdW50Kkl0LEhuPW51bGwhPT1LZT9LZS5zdGFydCpJdDowLE90PW51bGwhPT1LZT9LZS5jb3VudCpJdDoxLzAsd2k9TWF0aC5tYXgoQWksSG4pLGFpPU1hdGgubWluKHJyLEFpK01uLEhuK090KS0xLFluPU1hdGgubWF4KDAsYWktd2krMSk7aWYoMCE9PVluKXtpZihtZS5pc01lc2gpITA9PT1uZS53aXJlZnJhbWU/KFRlLnNldExpbmVXaWR0aChuZS53aXJlZnJhbWVMaW5ld2lkdGgqVWUoKSksYm4uc2V0TW9kZSgxKSk6Ym4uc2V0TW9kZSg0KTtlbHNlIGlmKG1lLmlzTGluZSl7bGV0ICRuPW5lLmxpbmV3aWR0aDt2b2lkIDA9PT0kbiYmKCRuPTEpLFRlLnNldExpbmVXaWR0aCgkbipVZSgpKSxibi5zZXRNb2RlKG1lLmlzTGluZVNlZ21lbnRzPzE6bWUuaXNMaW5lTG9vcD8yOjMpfWVsc2UgbWUuaXNQb2ludHM/Ym4uc2V0TW9kZSgwKTptZS5pc1Nwcml0ZSYmYm4uc2V0TW9kZSg0KTtpZihtZS5pc0luc3RhbmNlZE1lc2gpYm4ucmVuZGVySW5zdGFuY2VzKHdpLFluLG1lLmNvdW50KTtlbHNlIGlmKCQuaXNJbnN0YW5jZWRCdWZmZXJHZW9tZXRyeSl7bGV0ICRuPU1hdGgubWluKCQuaW5zdGFuY2VDb3VudCwkLl9tYXhJbnN0YW5jZUNvdW50KTtibi5yZW5kZXJJbnN0YW5jZXMod2ksWW4sJG4pfWVsc2UgYm4ucmVuZGVyKHdpLFluKX19LHRoaXMuY29tcGlsZT1mdW5jdGlvbihJLFgpe3A9cWkuZ2V0KEkpLHAuaW5pdCgpLGYucHVzaChwKSxJLnRyYXZlcnNlVmlzaWJsZShmdW5jdGlvbigkKXskLmlzTGlnaHQmJiQubGF5ZXJzLnRlc3QoWC5sYXllcnMpJiYocC5wdXNoTGlnaHQoJCksJC5jYXN0U2hhZG93JiZwLnB1c2hTaGFkb3coJCkpfSkscC5zZXR1cExpZ2h0cyhtLnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzKSxJLnRyYXZlcnNlKGZ1bmN0aW9uKCQpe2xldCBuZT0kLm1hdGVyaWFsO2lmKG5lKWlmKEFycmF5LmlzQXJyYXkobmUpKWZvcihsZXQgbWU9MDttZTxuZS5sZW5ndGg7bWUrKylleChuZVttZV0sSSwkKTtlbHNlIGV4KG5lLEksJCl9KSxmLnBvcCgpLHA9bnVsbH07bGV0IFplPW51bGw7ZnVuY3Rpb24gTWkoKXtFcy5zdG9wKCl9ZnVuY3Rpb24gVHIoKXtFcy5zdGFydCgpfWxldCBFcz1uZXcga2RlO2Z1bmN0aW9uIEJyKEksWCwkLG5lKXtpZighMT09PUkudmlzaWJsZSlyZXR1cm47aWYoSS5sYXllcnMudGVzdChYLmxheWVycykpaWYoSS5pc0dyb3VwKSQ9SS5yZW5kZXJPcmRlcjtlbHNlIGlmKEkuaXNMT0QpITA9PT1JLmF1dG9VcGRhdGUmJkkudXBkYXRlKFgpO2Vsc2UgaWYoSS5pc0xpZ2h0KXAucHVzaExpZ2h0KEkpLEkuY2FzdFNoYWRvdyYmcC5wdXNoU2hhZG93KEkpO2Vsc2UgaWYoSS5pc1Nwcml0ZSl7aWYoIUkuZnJ1c3R1bUN1bGxlZHx8YWUuaW50ZXJzZWN0c1Nwcml0ZShJKSl7bmUmJm50LnNldEZyb21NYXRyaXhQb3NpdGlvbihJLm1hdHJpeFdvcmxkKS5hcHBseU1hdHJpeDQoRGUpO2xldCBsdD1obi51cGRhdGUoSSksSmU9SS5tYXRlcmlhbDtKZS52aXNpYmxlJiZkLnB1c2goSSxsdCxKZSwkLG50LnosbnVsbCl9fWVsc2UgaWYoKEkuaXNNZXNofHxJLmlzTGluZXx8SS5pc1BvaW50cykmJihJLmlzU2tpbm5lZE1lc2gmJkkuc2tlbGV0b24uZnJhbWUhPT14dC5yZW5kZXIuZnJhbWUmJihJLnNrZWxldG9uLnVwZGF0ZSgpLEkuc2tlbGV0b24uZnJhbWU9eHQucmVuZGVyLmZyYW1lKSwhSS5mcnVzdHVtQ3VsbGVkfHxhZS5pbnRlcnNlY3RzT2JqZWN0KEkpKSl7bmUmJm50LnNldEZyb21NYXRyaXhQb3NpdGlvbihJLm1hdHJpeFdvcmxkKS5hcHBseU1hdHJpeDQoRGUpO2xldCBsdD1obi51cGRhdGUoSSksSmU9SS5tYXRlcmlhbDtpZihBcnJheS5pc0FycmF5KEplKSl7bGV0IGZ0PWx0Lmdyb3Vwcztmb3IobGV0IEN0PTAsSXQ9ZnQubGVuZ3RoO0N0PEl0O0N0Kyspe2xldCBOdD1mdFtDdF0sYm49SmVbTnQubWF0ZXJpYWxJbmRleF07Ym4mJmJuLnZpc2libGUmJmQucHVzaChJLGx0LGJuLCQsbnQueixOdCl9fWVsc2UgSmUudmlzaWJsZSYmZC5wdXNoKEksbHQsSmUsJCxudC56LG51bGwpfWxldCBLZT1JLmNoaWxkcmVuO2ZvcihsZXQgbHQ9MCxKZT1LZS5sZW5ndGg7bHQ8SmU7bHQrKylCcihLZVtsdF0sWCwkLG5lKX1mdW5jdGlvbiBQbChJLFgsJCxuZSl7bGV0IG1lPUkub3BhcXVlLEtlPUkudHJhbnNtaXNzaXZlLGx0PUkudHJhbnNwYXJlbnQ7cC5zZXR1cExpZ2h0c1ZpZXcoJCksS2UubGVuZ3RoPjAmJmZ1bmN0aW9uKEksWCwkKXtudWxsPT09dmUmJih2ZT1uZXcoITA9PT1zJiYhMD09PXd0LmlzV2ViR0wyP1hTOldhKSgxMDI0LDEwMjQse2dlbmVyYXRlTWlwbWFwczohMCx0eXBlOm51bGwhPT13bi5jb252ZXJ0KGxiKT9sYjpfZixtaW5GaWx0ZXI6MTAwOCxtYWdGaWx0ZXI6Wm8sd3JhcFM6RWwsd3JhcFQ6RWwsdXNlUmVuZGVyVG9UZXh0dXJlOnB0LmhhcygiV0VCR0xfbXVsdGlzYW1wbGVkX3JlbmRlcl90b190ZXh0dXJlIil9KSk7bGV0IG5lPW0uZ2V0UmVuZGVyVGFyZ2V0KCk7bS5zZXRSZW5kZXJUYXJnZXQodmUpLG0uY2xlYXIoKTtsZXQgbWU9bS50b25lTWFwcGluZzttLnRvbmVNYXBwaW5nPTAsYmMoSSxYLCQpLG0udG9uZU1hcHBpbmc9bWUsY2UudXBkYXRlTXVsdGlzYW1wbGVSZW5kZXJUYXJnZXQodmUpLGNlLnVwZGF0ZVJlbmRlclRhcmdldE1pcG1hcCh2ZSksbS5zZXRSZW5kZXJUYXJnZXQobmUpfShtZSxYLCQpLG5lJiZUZS52aWV3cG9ydChaLmNvcHkobmUpKSxtZS5sZW5ndGg+MCYmYmMobWUsWCwkKSxLZS5sZW5ndGg+MCYmYmMoS2UsWCwkKSxsdC5sZW5ndGg+MCYmYmMobHQsWCwkKX1mdW5jdGlvbiBiYyhJLFgsJCl7bGV0IG5lPSEwPT09WC5pc1NjZW5lP1gub3ZlcnJpZGVNYXRlcmlhbDpudWxsO2ZvcihsZXQgbWU9MCxLZT1JLmxlbmd0aDttZTxLZTttZSsrKXtsZXQgbHQ9SVttZV0sSmU9bHQub2JqZWN0LGZ0PWx0Lmdlb21ldHJ5LEN0PW51bGw9PT1uZT9sdC5tYXRlcmlhbDpuZSxJdD1sdC5ncm91cDtKZS5sYXllcnMudGVzdCgkLmxheWVycykmJmlOKEplLFgsJCxmdCxDdCxJdCl9fWZ1bmN0aW9uIGlOKEksWCwkLG5lLG1lLEtlKXtJLm9uQmVmb3JlUmVuZGVyKG0sWCwkLG5lLG1lLEtlKSxJLm1vZGVsVmlld01hdHJpeC5tdWx0aXBseU1hdHJpY2VzKCQubWF0cml4V29ybGRJbnZlcnNlLEkubWF0cml4V29ybGQpLEkubm9ybWFsTWF0cml4LmdldE5vcm1hbE1hdHJpeChJLm1vZGVsVmlld01hdHJpeCksbWUub25CZWZvcmVSZW5kZXIobSxYLCQsbmUsSSxLZSksITA9PT1tZS50cmFuc3BhcmVudCYmMj09PW1lLnNpZGU/KG1lLnNpZGU9MSxtZS5uZWVkc1VwZGF0ZT0hMCxtLnJlbmRlckJ1ZmZlckRpcmVjdCgkLFgsbmUsbWUsSSxLZSksbWUuc2lkZT0wLG1lLm5lZWRzVXBkYXRlPSEwLG0ucmVuZGVyQnVmZmVyRGlyZWN0KCQsWCxuZSxtZSxJLEtlKSxtZS5zaWRlPTIpOm0ucmVuZGVyQnVmZmVyRGlyZWN0KCQsWCxuZSxtZSxJLEtlKSxJLm9uQWZ0ZXJSZW5kZXIobSxYLCQsbmUsbWUsS2UpfWZ1bmN0aW9uIGV4KEksWCwkKXshMCE9PVguaXNTY2VuZSYmKFg9Z3QpO2xldCBuZT1tdC5nZXQoSSksbWU9cC5zdGF0ZS5saWdodHMsbHQ9bWUuc3RhdGUudmVyc2lvbixKZT1vbi5nZXRQYXJhbWV0ZXJzKEksbWUuc3RhdGUscC5zdGF0ZS5zaGFkb3dzQXJyYXksWCwkKSxmdD1vbi5nZXRQcm9ncmFtQ2FjaGVLZXkoSmUpLEN0PW5lLnByb2dyYW1zO25lLmVudmlyb25tZW50PUkuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbD9YLmVudmlyb25tZW50Om51bGwsbmUuZm9nPVguZm9nLG5lLmVudk1hcD0oSS5pc01lc2hTdGFuZGFyZE1hdGVyaWFsP1dlOmR0KS5nZXQoSS5lbnZNYXB8fG5lLmVudmlyb25tZW50KSx2b2lkIDA9PT1DdCYmKEkuYWRkRXZlbnRMaXN0ZW5lcigiZGlzcG9zZSIsZm4pLEN0PW5ldyBNYXAsbmUucHJvZ3JhbXM9Q3QpO2xldCBJdD1DdC5nZXQoZnQpO2lmKHZvaWQgMCE9PUl0KXtpZihuZS5jdXJyZW50UHJvZ3JhbT09PUl0JiZuZS5saWdodHNTdGF0ZVZlcnNpb249PT1sdClyZXR1cm4gRkUoSSxKZSksSXR9ZWxzZSBKZS51bmlmb3Jtcz1vbi5nZXRVbmlmb3JtcyhJKSxJLm9uQnVpbGQoJCxKZSxtKSxJLm9uQmVmb3JlQ29tcGlsZShKZSxtKSxJdD1vbi5hY3F1aXJlUHJvZ3JhbShKZSxmdCksQ3Quc2V0KGZ0LEl0KSxuZS51bmlmb3Jtcz1KZS51bmlmb3JtcztsZXQgTnQ9bmUudW5pZm9ybXM7KCFJLmlzU2hhZGVyTWF0ZXJpYWwmJiFJLmlzUmF3U2hhZGVyTWF0ZXJpYWx8fCEwPT09SS5jbGlwcGluZykmJihOdC5jbGlwcGluZ1BsYW5lcz1lZS51bmlmb3JtKSxGRShJLEplKSxuZS5uZWVkc0xpZ2h0cz1mdW5jdGlvbihJKXtyZXR1cm4gSS5pc01lc2hMYW1iZXJ0TWF0ZXJpYWx8fEkuaXNNZXNoVG9vbk1hdGVyaWFsfHxJLmlzTWVzaFBob25nTWF0ZXJpYWx8fEkuaXNNZXNoU3RhbmRhcmRNYXRlcmlhbHx8SS5pc1NoYWRvd01hdGVyaWFsfHxJLmlzU2hhZGVyTWF0ZXJpYWwmJiEwPT09SS5saWdodHN9KEkpLG5lLmxpZ2h0c1N0YXRlVmVyc2lvbj1sdCxuZS5uZWVkc0xpZ2h0cyYmKE50LmFtYmllbnRMaWdodENvbG9yLnZhbHVlPW1lLnN0YXRlLmFtYmllbnQsTnQubGlnaHRQcm9iZS52YWx1ZT1tZS5zdGF0ZS5wcm9iZSxOdC5kaXJlY3Rpb25hbExpZ2h0cy52YWx1ZT1tZS5zdGF0ZS5kaXJlY3Rpb25hbCxOdC5kaXJlY3Rpb25hbExpZ2h0U2hhZG93cy52YWx1ZT1tZS5zdGF0ZS5kaXJlY3Rpb25hbFNoYWRvdyxOdC5zcG90TGlnaHRzLnZhbHVlPW1lLnN0YXRlLnNwb3QsTnQuc3BvdExpZ2h0U2hhZG93cy52YWx1ZT1tZS5zdGF0ZS5zcG90U2hhZG93LE50LnJlY3RBcmVhTGlnaHRzLnZhbHVlPW1lLnN0YXRlLnJlY3RBcmVhLE50Lmx0Y18xLnZhbHVlPW1lLnN0YXRlLnJlY3RBcmVhTFRDMSxOdC5sdGNfMi52YWx1ZT1tZS5zdGF0ZS5yZWN0QXJlYUxUQzIsTnQucG9pbnRMaWdodHMudmFsdWU9bWUuc3RhdGUucG9pbnQsTnQucG9pbnRMaWdodFNoYWRvd3MudmFsdWU9bWUuc3RhdGUucG9pbnRTaGFkb3csTnQuaGVtaXNwaGVyZUxpZ2h0cy52YWx1ZT1tZS5zdGF0ZS5oZW1pLE50LmRpcmVjdGlvbmFsU2hhZG93TWFwLnZhbHVlPW1lLnN0YXRlLmRpcmVjdGlvbmFsU2hhZG93TWFwLE50LmRpcmVjdGlvbmFsU2hhZG93TWF0cml4LnZhbHVlPW1lLnN0YXRlLmRpcmVjdGlvbmFsU2hhZG93TWF0cml4LE50LnNwb3RTaGFkb3dNYXAudmFsdWU9bWUuc3RhdGUuc3BvdFNoYWRvd01hcCxOdC5zcG90U2hhZG93TWF0cml4LnZhbHVlPW1lLnN0YXRlLnNwb3RTaGFkb3dNYXRyaXgsTnQucG9pbnRTaGFkb3dNYXAudmFsdWU9bWUuc3RhdGUucG9pbnRTaGFkb3dNYXAsTnQucG9pbnRTaGFkb3dNYXRyaXgudmFsdWU9bWUuc3RhdGUucG9pbnRTaGFkb3dNYXRyaXgpO2xldCBibj1JdC5nZXRVbmlmb3JtcygpLHJyPXlmLnNlcVdpdGhWYWx1ZShibi5zZXEsTnQpO3JldHVybiBuZS5jdXJyZW50UHJvZ3JhbT1JdCxuZS51bmlmb3Jtc0xpc3Q9cnIsSXR9ZnVuY3Rpb24gRkUoSSxYKXtsZXQgJD1tdC5nZXQoSSk7JC5vdXRwdXRFbmNvZGluZz1YLm91dHB1dEVuY29kaW5nLCQuaW5zdGFuY2luZz1YLmluc3RhbmNpbmcsJC5za2lubmluZz1YLnNraW5uaW5nLCQubW9ycGhUYXJnZXRzPVgubW9ycGhUYXJnZXRzLCQubW9ycGhOb3JtYWxzPVgubW9ycGhOb3JtYWxzLCQubW9ycGhUYXJnZXRzQ291bnQ9WC5tb3JwaFRhcmdldHNDb3VudCwkLm51bUNsaXBwaW5nUGxhbmVzPVgubnVtQ2xpcHBpbmdQbGFuZXMsJC5udW1JbnRlcnNlY3Rpb249WC5udW1DbGlwSW50ZXJzZWN0aW9uLCQudmVydGV4QWxwaGFzPVgudmVydGV4QWxwaGFzLCQudmVydGV4VGFuZ2VudHM9WC52ZXJ0ZXhUYW5nZW50cywkLnRvbmVNYXBwaW5nPVgudG9uZU1hcHBpbmd9RXMuc2V0QW5pbWF0aW9uTG9vcChmdW5jdGlvbihJKXtaZSYmWmUoSSl9KSx0eXBlb2Ygd2luZG93PCJ1IiYmRXMuc2V0Q29udGV4dCh3aW5kb3cpLHRoaXMuc2V0QW5pbWF0aW9uTG9vcD1mdW5jdGlvbihJKXtaZT1JLFV0LnNldEFuaW1hdGlvbkxvb3AoSSksbnVsbD09PUk/RXMuc3RvcCgpOkVzLnN0YXJ0KCl9LFV0LmFkZEV2ZW50TGlzdGVuZXIoInNlc3Npb25zdGFydCIsTWkpLFV0LmFkZEV2ZW50TGlzdGVuZXIoInNlc3Npb25lbmQiLFRyKSx0aGlzLnJlbmRlcj1mdW5jdGlvbihJLFgpe2lmKHZvaWQgMD09PVh8fCEwPT09WC5pc0NhbWVyYSl7aWYoITAhPT14KXtpZighMD09PUkuYXV0b1VwZGF0ZSYmSS51cGRhdGVNYXRyaXhXb3JsZCgpLG51bGw9PT1YLnBhcmVudCYmWC51cGRhdGVNYXRyaXhXb3JsZCgpLCEwPT09VXQuZW5hYmxlZCYmITA9PT1VdC5pc1ByZXNlbnRpbmcmJighMD09PVV0LmNhbWVyYUF1dG9VcGRhdGUmJlV0LnVwZGF0ZUNhbWVyYShYKSxYPVV0LmdldENhbWVyYSgpKSwhMD09PUkuaXNTY2VuZSYmSS5vbkJlZm9yZVJlbmRlcihtLEksWCxEKSxwPXFpLmdldChJLGYubGVuZ3RoKSxwLmluaXQoKSxmLnB1c2gocCksRGUubXVsdGlwbHlNYXRyaWNlcyhYLnByb2plY3Rpb25NYXRyaXgsWC5tYXRyaXhXb3JsZEludmVyc2UpLGFlLnNldEZyb21Qcm9qZWN0aW9uTWF0cml4KERlKSxJZT10aGlzLmxvY2FsQ2xpcHBpbmdFbmFibGVkLGxlPWVlLmluaXQodGhpcy5jbGlwcGluZ1BsYW5lcyxJZSxYKSxkPVdpLmdldChJLGgubGVuZ3RoKSxkLmluaXQoKSxoLnB1c2goZCksQnIoSSxYLDAsbS5zb3J0T2JqZWN0cyksZC5maW5pc2goKSwhMD09PW0uc29ydE9iamVjdHMmJmQuc29ydChGLHEpLCEwPT09bGUmJmVlLmJlZ2luU2hhZG93cygpLFcucmVuZGVyKHAuc3RhdGUuc2hhZG93c0FycmF5LEksWCksITA9PT1sZSYmZWUuZW5kU2hhZG93cygpLCEwPT09dGhpcy5pbmZvLmF1dG9SZXNldCYmdGhpcy5pbmZvLnJlc2V0KCksWGUucmVuZGVyKGQsSSkscC5zZXR1cExpZ2h0cyhtLnBoeXNpY2FsbHlDb3JyZWN0TGlnaHRzKSxYLmlzQXJyYXlDYW1lcmEpe2xldCBuZT1YLmNhbWVyYXM7Zm9yKGxldCBtZT0wLEtlPW5lLmxlbmd0aDttZTxLZTttZSsrKXtsZXQgbHQ9bmVbbWVdO1BsKGQsSSxsdCxsdC52aWV3cG9ydCl9fWVsc2UgUGwoZCxJLFgpO251bGwhPT1EJiYoY2UudXBkYXRlTXVsdGlzYW1wbGVSZW5kZXJUYXJnZXQoRCksY2UudXBkYXRlUmVuZGVyVGFyZ2V0TWlwbWFwKEQpKSwhMD09PUkuaXNTY2VuZSYmSS5vbkFmdGVyUmVuZGVyKG0sSSxYKSxUZS5idWZmZXJzLmRlcHRoLnNldFRlc3QoITApLFRlLmJ1ZmZlcnMuZGVwdGguc2V0TWFzayghMCksVGUuYnVmZmVycy5jb2xvci5zZXRNYXNrKCEwKSxUZS5zZXRQb2x5Z29uT2Zmc2V0KCExKSx5bi5yZXNldERlZmF1bHRTdGF0ZSgpLFQ9LTEsaz1udWxsLGYucG9wKCkscD1mLmxlbmd0aD4wP2ZbZi5sZW5ndGgtMV06bnVsbCxoLnBvcCgpLGQ9aC5sZW5ndGg+MD9oW2gubGVuZ3RoLTFdOm51bGx9fWVsc2UgY29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlci5yZW5kZXI6IGNhbWVyYSBpcyBub3QgYW4gaW5zdGFuY2Ugb2YgVEhSRUUuQ2FtZXJhLiIpfSx0aGlzLmdldEFjdGl2ZUN1YmVGYWNlPWZ1bmN0aW9uKCl7cmV0dXJuIGd9LHRoaXMuZ2V0QWN0aXZlTWlwbWFwTGV2ZWw9ZnVuY3Rpb24oKXtyZXR1cm4gYn0sdGhpcy5nZXRSZW5kZXJUYXJnZXQ9ZnVuY3Rpb24oKXtyZXR1cm4gRH0sdGhpcy5zZXRSZW5kZXJUYXJnZXRUZXh0dXJlcz1mdW5jdGlvbihJLFgsJCl7bXQuZ2V0KEkudGV4dHVyZSkuX193ZWJnbFRleHR1cmU9WCxtdC5nZXQoSS5kZXB0aFRleHR1cmUpLl9fd2ViZ2xUZXh0dXJlPSQ7bGV0IG5lPW10LmdldChJKTtuZS5fX2hhc0V4dGVybmFsVGV4dHVyZXM9ITAsbmUuX19oYXNFeHRlcm5hbFRleHR1cmVzJiYobmUuX19hdXRvQWxsb2NhdGVEZXB0aEJ1ZmZlcj12b2lkIDA9PT0kLG5lLl9fYXV0b0FsbG9jYXRlRGVwdGhCdWZmZXJ8fEkudXNlUmVuZGVyVG9UZXh0dXJlJiYoY29uc29sZS53YXJuKCJyZW5kZXItdG8tdGV4dHVyZSBleHRlbnNpb24gd2FzIGRpc2FibGVkIGJlY2F1c2UgYW4gZXh0ZXJuYWwgdGV4dHVyZSB3YXMgcHJvdmlkZWQiKSxJLnVzZVJlbmRlclRvVGV4dHVyZT0hMSxJLnVzZVJlbmRlcmJ1ZmZlcj0hMCkpfSx0aGlzLnNldFJlbmRlclRhcmdldEZyYW1lYnVmZmVyPWZ1bmN0aW9uKEksWCl7bGV0ICQ9bXQuZ2V0KEkpOyQuX193ZWJnbEZyYW1lYnVmZmVyPVgsJC5fX3VzZURlZmF1bHRGcmFtZWJ1ZmZlcj12b2lkIDA9PT1YfSx0aGlzLnNldFJlbmRlclRhcmdldD1mdW5jdGlvbihJLFg9MCwkPTApe0Q9SSxnPVgsYj0kO2xldCBuZT0hMDtpZihJKXtsZXQgZnQ9bXQuZ2V0KEkpO3ZvaWQgMCE9PWZ0Ll9fdXNlRGVmYXVsdEZyYW1lYnVmZmVyPyhUZS5iaW5kRnJhbWVidWZmZXIoMzYxNjAsbnVsbCksbmU9ITEpOnZvaWQgMD09PWZ0Ll9fd2ViZ2xGcmFtZWJ1ZmZlcj9jZS5zZXR1cFJlbmRlclRhcmdldChJKTpmdC5fX2hhc0V4dGVybmFsVGV4dHVyZXMmJmNlLnJlYmluZFRleHR1cmVzKEksbXQuZ2V0KEkudGV4dHVyZSkuX193ZWJnbFRleHR1cmUsbXQuZ2V0KEkuZGVwdGhUZXh0dXJlKS5fX3dlYmdsVGV4dHVyZSl9bGV0IG1lPW51bGwsS2U9ITEsbHQ9ITE7aWYoSSl7bGV0IGZ0PUkudGV4dHVyZTsoZnQuaXNEYXRhVGV4dHVyZTNEfHxmdC5pc0RhdGFUZXh0dXJlMkRBcnJheSkmJihsdD0hMCk7bGV0IEN0PW10LmdldChJKS5fX3dlYmdsRnJhbWVidWZmZXI7SS5pc1dlYkdMQ3ViZVJlbmRlclRhcmdldD8obWU9Q3RbWF0sS2U9ITApOm1lPUkudXNlUmVuZGVyYnVmZmVyP210LmdldChJKS5fX3dlYmdsTXVsdGlzYW1wbGVkRnJhbWVidWZmZXI6Q3QsWi5jb3B5KEkudmlld3BvcnQpLHouY29weShJLnNjaXNzb3IpLGZlPUkuc2Npc3NvclRlc3R9ZWxzZSBaLmNvcHkoSykubXVsdGlwbHlTY2FsYXIodykuZmxvb3IoKSx6LmNvcHkoZGUpLm11bHRpcGx5U2NhbGFyKHcpLmZsb29yKCksZmU9WTtpZihUZS5iaW5kRnJhbWVidWZmZXIoMzYxNjAsbWUpJiZ3dC5kcmF3QnVmZmVycyYmbmUmJlRlLmRyYXdCdWZmZXJzKEksbWUpLFRlLnZpZXdwb3J0KFopLFRlLnNjaXNzb3IoeiksVGUuc2V0U2Npc3NvclRlc3QoZmUpLEtlKXtsZXQgZnQ9bXQuZ2V0KEkudGV4dHVyZSk7QWUuZnJhbWVidWZmZXJUZXh0dXJlMkQoMzYxNjAsMzYwNjQsMzQwNjkrWCxmdC5fX3dlYmdsVGV4dHVyZSwkKX1lbHNlIGlmKGx0KXtsZXQgZnQ9bXQuZ2V0KEkudGV4dHVyZSk7QWUuZnJhbWVidWZmZXJUZXh0dXJlTGF5ZXIoMzYxNjAsMzYwNjQsZnQuX193ZWJnbFRleHR1cmUsJHx8MCxYfHwwKX1UPS0xfSx0aGlzLnJlYWRSZW5kZXJUYXJnZXRQaXhlbHM9ZnVuY3Rpb24oSSxYLCQsbmUsbWUsS2UsbHQpe2lmKCFJfHwhSS5pc1dlYkdMUmVuZGVyVGFyZ2V0KXJldHVybiB2b2lkIGNvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMUmVuZGVyZXIucmVhZFJlbmRlclRhcmdldFBpeGVsczogcmVuZGVyVGFyZ2V0IGlzIG5vdCBUSFJFRS5XZWJHTFJlbmRlclRhcmdldC4iKTtsZXQgSmU9bXQuZ2V0KEkpLl9fd2ViZ2xGcmFtZWJ1ZmZlcjtpZihJLmlzV2ViR0xDdWJlUmVuZGVyVGFyZ2V0JiZ2b2lkIDAhPT1sdCYmKEplPUplW2x0XSksSmUpe1RlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxKZSk7dHJ5e2xldCBmdD1JLnRleHR1cmUsQ3Q9ZnQuZm9ybWF0LEl0PWZ0LnR5cGU7aWYoQ3QhPT1nYSYmd24uY29udmVydChDdCkhPT1BZS5nZXRQYXJhbWV0ZXIoMzU3MzkpKXJldHVybiB2b2lkIGNvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMUmVuZGVyZXIucmVhZFJlbmRlclRhcmdldFBpeGVsczogcmVuZGVyVGFyZ2V0IGlzIG5vdCBpbiBSR0JBIG9yIGltcGxlbWVudGF0aW9uIGRlZmluZWQgZm9ybWF0LiIpO2xldCBOdD1JdD09PWxiJiYocHQuaGFzKCJFWFRfY29sb3JfYnVmZmVyX2hhbGZfZmxvYXQiKXx8d3QuaXNXZWJHTDImJnB0LmhhcygiRVhUX2NvbG9yX2J1ZmZlcl9mbG9hdCIpKTtpZighKEl0PT09X2Z8fHduLmNvbnZlcnQoSXQpPT09QWUuZ2V0UGFyYW1ldGVyKDM1NzM4KXx8SXQ9PT1VZyYmKHd0LmlzV2ViR0wyfHxwdC5oYXMoIk9FU190ZXh0dXJlX2Zsb2F0Iil8fHB0LmhhcygiV0VCR0xfY29sb3JfYnVmZmVyX2Zsb2F0IikpfHxOdCkpcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiVEhSRUUuV2ViR0xSZW5kZXJlci5yZWFkUmVuZGVyVGFyZ2V0UGl4ZWxzOiByZW5kZXJUYXJnZXQgaXMgbm90IGluIFVuc2lnbmVkQnl0ZVR5cGUgb3IgaW1wbGVtZW50YXRpb24gZGVmaW5lZCB0eXBlLiIpOzM2MDUzPT09QWUuY2hlY2tGcmFtZWJ1ZmZlclN0YXR1cygzNjE2MCk/WD49MCYmWDw9SS53aWR0aC1uZSYmJD49MCYmJDw9SS5oZWlnaHQtbWUmJkFlLnJlYWRQaXhlbHMoWCwkLG5lLG1lLHduLmNvbnZlcnQoQ3QpLHduLmNvbnZlcnQoSXQpLEtlKTpjb25zb2xlLmVycm9yKCJUSFJFRS5XZWJHTFJlbmRlcmVyLnJlYWRSZW5kZXJUYXJnZXRQaXhlbHM6IHJlYWRQaXhlbHMgZnJvbSByZW5kZXJUYXJnZXQgZmFpbGVkLiBGcmFtZWJ1ZmZlciBub3QgY29tcGxldGUuIil9ZmluYWxseXtsZXQgZnQ9bnVsbCE9PUQ/bXQuZ2V0KEQpLl9fd2ViZ2xGcmFtZWJ1ZmZlcjpudWxsO1RlLmJpbmRGcmFtZWJ1ZmZlcigzNjE2MCxmdCl9fX0sdGhpcy5jb3B5RnJhbWVidWZmZXJUb1RleHR1cmU9ZnVuY3Rpb24oSSxYLCQ9MCl7aWYoITAhPT1YLmlzRnJhbWVidWZmZXJUZXh0dXJlKXJldHVybiB2b2lkIGNvbnNvbGUuZXJyb3IoIlRIUkVFLldlYkdMUmVuZGVyZXI6IGNvcHlGcmFtZWJ1ZmZlclRvVGV4dHVyZSgpIGNhbiBvbmx5IGJlIHVzZWQgd2l0aCBGcmFtZWJ1ZmZlclRleHR1cmUuIik7bGV0IG5lPU1hdGgucG93KDIsLSQpLG1lPU1hdGguZmxvb3IoWC5pbWFnZS53aWR0aCpuZSksS2U9TWF0aC5mbG9vcihYLmltYWdlLmhlaWdodCpuZSk7Y2Uuc2V0VGV4dHVyZTJEKFgsMCksQWUuY29weVRleFN1YkltYWdlMkQoMzU1MywkLDAsMCxJLngsSS55LG1lLEtlKSxUZS51bmJpbmRUZXh0dXJlKCl9LHRoaXMuY29weVRleHR1cmVUb1RleHR1cmU9ZnVuY3Rpb24oSSxYLCQsbmU9MCl7bGV0IG1lPVguaW1hZ2Uud2lkdGgsS2U9WC5pbWFnZS5oZWlnaHQsbHQ9d24uY29udmVydCgkLmZvcm1hdCksSmU9d24uY29udmVydCgkLnR5cGUpO2NlLnNldFRleHR1cmUyRCgkLDApLEFlLnBpeGVsU3RvcmVpKDM3NDQwLCQuZmxpcFkpLEFlLnBpeGVsU3RvcmVpKDM3NDQxLCQucHJlbXVsdGlwbHlBbHBoYSksQWUucGl4ZWxTdG9yZWkoMzMxNywkLnVucGFja0FsaWdubWVudCksWC5pc0RhdGFUZXh0dXJlP0FlLnRleFN1YkltYWdlMkQoMzU1MyxuZSxJLngsSS55LG1lLEtlLGx0LEplLFguaW1hZ2UuZGF0YSk6WC5pc0NvbXByZXNzZWRUZXh0dXJlP0FlLmNvbXByZXNzZWRUZXhTdWJJbWFnZTJEKDM1NTMsbmUsSS54LEkueSxYLm1pcG1hcHNbMF0ud2lkdGgsWC5taXBtYXBzWzBdLmhlaWdodCxsdCxYLm1pcG1hcHNbMF0uZGF0YSk6QWUudGV4U3ViSW1hZ2UyRCgzNTUzLG5lLEkueCxJLnksbHQsSmUsWC5pbWFnZSksMD09PW5lJiYkLmdlbmVyYXRlTWlwbWFwcyYmQWUuZ2VuZXJhdGVNaXBtYXAoMzU1MyksVGUudW5iaW5kVGV4dHVyZSgpfSx0aGlzLmNvcHlUZXh0dXJlVG9UZXh0dXJlM0Q9ZnVuY3Rpb24oSSxYLCQsbmUsbWU9MCl7aWYobS5pc1dlYkdMMVJlbmRlcmVyKXJldHVybiB2b2lkIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlci5jb3B5VGV4dHVyZVRvVGV4dHVyZTNEOiBjYW4gb25seSBiZSB1c2VkIHdpdGggV2ViR0wyLiIpO2xldCBJdCxLZT1JLm1heC54LUkubWluLngrMSxsdD1JLm1heC55LUkubWluLnkrMSxKZT1JLm1heC56LUkubWluLnorMSxmdD13bi5jb252ZXJ0KG5lLmZvcm1hdCksQ3Q9d24uY29udmVydChuZS50eXBlKTtpZihuZS5pc0RhdGFUZXh0dXJlM0QpY2Uuc2V0VGV4dHVyZTNEKG5lLDApLEl0PTMyODc5O2Vsc2V7aWYoIW5lLmlzRGF0YVRleHR1cmUyREFycmF5KXJldHVybiB2b2lkIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlci5jb3B5VGV4dHVyZVRvVGV4dHVyZTNEOiBvbmx5IHN1cHBvcnRzIFRIUkVFLkRhdGFUZXh0dXJlM0QgYW5kIFRIUkVFLkRhdGFUZXh0dXJlMkRBcnJheS4iKTtjZS5zZXRUZXh0dXJlMkRBcnJheShuZSwwKSxJdD0zNTg2Nn1BZS5waXhlbFN0b3JlaSgzNzQ0MCxuZS5mbGlwWSksQWUucGl4ZWxTdG9yZWkoMzc0NDEsbmUucHJlbXVsdGlwbHlBbHBoYSksQWUucGl4ZWxTdG9yZWkoMzMxNyxuZS51bnBhY2tBbGlnbm1lbnQpO2xldCBOdD1BZS5nZXRQYXJhbWV0ZXIoMzMxNCksYm49QWUuZ2V0UGFyYW1ldGVyKDMyODc4KSxycj1BZS5nZXRQYXJhbWV0ZXIoMzMxNiksQWk9QWUuZ2V0UGFyYW1ldGVyKDMzMTUpLE1uPUFlLmdldFBhcmFtZXRlcigzMjg3NyksSG49JC5pc0NvbXByZXNzZWRUZXh0dXJlPyQubWlwbWFwc1swXTokLmltYWdlO0FlLnBpeGVsU3RvcmVpKDMzMTQsSG4ud2lkdGgpLEFlLnBpeGVsU3RvcmVpKDMyODc4LEhuLmhlaWdodCksQWUucGl4ZWxTdG9yZWkoMzMxNixJLm1pbi54KSxBZS5waXhlbFN0b3JlaSgzMzE1LEkubWluLnkpLEFlLnBpeGVsU3RvcmVpKDMyODc3LEkubWluLnopLCQuaXNEYXRhVGV4dHVyZXx8JC5pc0RhdGFUZXh0dXJlM0Q/QWUudGV4U3ViSW1hZ2UzRChJdCxtZSxYLngsWC55LFgueixLZSxsdCxKZSxmdCxDdCxIbi5kYXRhKTokLmlzQ29tcHJlc3NlZFRleHR1cmU/KGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlci5jb3B5VGV4dHVyZVRvVGV4dHVyZTNEOiB1bnRlc3RlZCBzdXBwb3J0IGZvciBjb21wcmVzc2VkIHNyY1RleHR1cmUuIiksQWUuY29tcHJlc3NlZFRleFN1YkltYWdlM0QoSXQsbWUsWC54LFgueSxYLnosS2UsbHQsSmUsZnQsSG4uZGF0YSkpOkFlLnRleFN1YkltYWdlM0QoSXQsbWUsWC54LFgueSxYLnosS2UsbHQsSmUsZnQsQ3QsSG4pLEFlLnBpeGVsU3RvcmVpKDMzMTQsTnQpLEFlLnBpeGVsU3RvcmVpKDMyODc4LGJuKSxBZS5waXhlbFN0b3JlaSgzMzE2LHJyKSxBZS5waXhlbFN0b3JlaSgzMzE1LEFpKSxBZS5waXhlbFN0b3JlaSgzMjg3NyxNbiksMD09PW1lJiZuZS5nZW5lcmF0ZU1pcG1hcHMmJkFlLmdlbmVyYXRlTWlwbWFwKEl0KSxUZS51bmJpbmRUZXh0dXJlKCl9LHRoaXMuaW5pdFRleHR1cmU9ZnVuY3Rpb24oSSl7Y2Uuc2V0VGV4dHVyZTJEKEksMCksVGUudW5iaW5kVGV4dHVyZSgpfSx0aGlzLnJlc2V0U3RhdGU9ZnVuY3Rpb24oKXtnPTAsYj0wLEQ9bnVsbCxUZS5yZXNldCgpLHluLnJlc2V0KCl9LHR5cGVvZiBfX1RIUkVFX0RFVlRPT0xTX188InUiJiZfX1RIUkVFX0RFVlRPT0xTX18uZGlzcGF0Y2hFdmVudChuZXcgQ3VzdG9tRXZlbnQoIm9ic2VydmUiLHtkZXRhaWw6dGhpc30pKX1pci5wcm90b3R5cGUuaXNXZWJHTFJlbmRlcmVyPSEwLGNsYXNzIGV4dGVuZHMgaXJ7fS5wcm90b3R5cGUuaXNXZWJHTDFSZW5kZXJlcj0hMDt2YXIgZUU9Y2xhc3N7Y29uc3RydWN0b3IodCxlPTI1ZS01KXt0aGlzLm5hbWU9IiIsdGhpcy5jb2xvcj1uZXcgdm4odCksdGhpcy5kZW5zaXR5PWV9Y2xvbmUoKXtyZXR1cm4gbmV3IGVFKHRoaXMuY29sb3IsdGhpcy5kZW5zaXR5KX10b0pTT04oKXtyZXR1cm57dHlwZToiRm9nRXhwMiIsY29sb3I6dGhpcy5jb2xvci5nZXRIZXgoKSxkZW5zaXR5OnRoaXMuZGVuc2l0eX19fTtlRS5wcm90b3R5cGUuaXNGb2dFeHAyPSEwO3ZhciB0RT1jbGFzc3tjb25zdHJ1Y3Rvcih0LGU9MSxpPTFlMyl7dGhpcy5uYW1lPSIiLHRoaXMuY29sb3I9bmV3IHZuKHQpLHRoaXMubmVhcj1lLHRoaXMuZmFyPWl9Y2xvbmUoKXtyZXR1cm4gbmV3IHRFKHRoaXMuY29sb3IsdGhpcy5uZWFyLHRoaXMuZmFyKX10b0pTT04oKXtyZXR1cm57dHlwZToiRm9nIixjb2xvcjp0aGlzLmNvbG9yLmdldEhleCgpLG5lYXI6dGhpcy5uZWFyLGZhcjp0aGlzLmZhcn19fTt0RS5wcm90b3R5cGUuaXNGb2c9ITA7dmFyIHZiPWNsYXNzIGV4dGVuZHMgWGl7Y29uc3RydWN0b3IoKXtzdXBlcigpLHRoaXMudHlwZT0iU2NlbmUiLHRoaXMuYmFja2dyb3VuZD1udWxsLHRoaXMuZW52aXJvbm1lbnQ9bnVsbCx0aGlzLmZvZz1udWxsLHRoaXMub3ZlcnJpZGVNYXRlcmlhbD1udWxsLHRoaXMuYXV0b1VwZGF0ZT0hMCx0eXBlb2YgX19USFJFRV9ERVZUT09MU19fPCJ1IiYmX19USFJFRV9ERVZUT09MU19fLmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCJvYnNlcnZlIix7ZGV0YWlsOnRoaXN9KSl9Y29weSh0LGUpe3JldHVybiBzdXBlci5jb3B5KHQsZSksbnVsbCE9PXQuYmFja2dyb3VuZCYmKHRoaXMuYmFja2dyb3VuZD10LmJhY2tncm91bmQuY2xvbmUoKSksbnVsbCE9PXQuZW52aXJvbm1lbnQmJih0aGlzLmVudmlyb25tZW50PXQuZW52aXJvbm1lbnQuY2xvbmUoKSksbnVsbCE9PXQuZm9nJiYodGhpcy5mb2c9dC5mb2cuY2xvbmUoKSksbnVsbCE9PXQub3ZlcnJpZGVNYXRlcmlhbCYmKHRoaXMub3ZlcnJpZGVNYXRlcmlhbD10Lm92ZXJyaWRlTWF0ZXJpYWwuY2xvbmUoKSksdGhpcy5hdXRvVXBkYXRlPXQuYXV0b1VwZGF0ZSx0aGlzLm1hdHJpeEF1dG9VcGRhdGU9dC5tYXRyaXhBdXRvVXBkYXRlLHRoaXN9dG9KU09OKHQpe2xldCBlPXN1cGVyLnRvSlNPTih0KTtyZXR1cm4gbnVsbCE9PXRoaXMuZm9nJiYoZS5vYmplY3QuZm9nPXRoaXMuZm9nLnRvSlNPTigpKSxlfX07dmIucHJvdG90eXBlLmlzU2NlbmU9ITA7dmFyIFlnPWNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5hcnJheT10LHRoaXMuc3RyaWRlPWUsdGhpcy5jb3VudD12b2lkIDAhPT10P3QubGVuZ3RoL2U6MCx0aGlzLnVzYWdlPXFTLHRoaXMudXBkYXRlUmFuZ2U9e29mZnNldDowLGNvdW50Oi0xfSx0aGlzLnZlcnNpb249MCx0aGlzLnV1aWQ9ZHUoKX1vblVwbG9hZENhbGxiYWNrKCl7fXNldCBuZWVkc1VwZGF0ZSh0KXshMD09PXQmJnRoaXMudmVyc2lvbisrfXNldFVzYWdlKHQpe3JldHVybiB0aGlzLnVzYWdlPXQsdGhpc31jb3B5KHQpe3JldHVybiB0aGlzLmFycmF5PW5ldyB0LmFycmF5LmNvbnN0cnVjdG9yKHQuYXJyYXkpLHRoaXMuY291bnQ9dC5jb3VudCx0aGlzLnN0cmlkZT10LnN0cmlkZSx0aGlzLnVzYWdlPXQudXNhZ2UsdGhpc31jb3B5QXQodCxlLGkpe3QqPXRoaXMuc3RyaWRlLGkqPWUuc3RyaWRlO2ZvcihsZXQgcj0wLG89dGhpcy5zdHJpZGU7cjxvO3IrKyl0aGlzLmFycmF5W3Qrcl09ZS5hcnJheVtpK3JdO3JldHVybiB0aGlzfXNldCh0LGU9MCl7cmV0dXJuIHRoaXMuYXJyYXkuc2V0KHQsZSksdGhpc31jbG9uZSh0KXt2b2lkIDA9PT10LmFycmF5QnVmZmVycyYmKHQuYXJyYXlCdWZmZXJzPXt9KSx2b2lkIDA9PT10aGlzLmFycmF5LmJ1ZmZlci5fdXVpZCYmKHRoaXMuYXJyYXkuYnVmZmVyLl91dWlkPWR1KCkpLHZvaWQgMD09PXQuYXJyYXlCdWZmZXJzW3RoaXMuYXJyYXkuYnVmZmVyLl91dWlkXSYmKHQuYXJyYXlCdWZmZXJzW3RoaXMuYXJyYXkuYnVmZmVyLl91dWlkXT10aGlzLmFycmF5LnNsaWNlKDApLmJ1ZmZlcik7bGV0IGU9bmV3IHRoaXMuYXJyYXkuY29uc3RydWN0b3IodC5hcnJheUJ1ZmZlcnNbdGhpcy5hcnJheS5idWZmZXIuX3V1aWRdKSxpPW5ldyB0aGlzLmNvbnN0cnVjdG9yKGUsdGhpcy5zdHJpZGUpO3JldHVybiBpLnNldFVzYWdlKHRoaXMudXNhZ2UpLGl9b25VcGxvYWQodCl7cmV0dXJuIHRoaXMub25VcGxvYWRDYWxsYmFjaz10LHRoaXN9dG9KU09OKHQpe3JldHVybiB2b2lkIDA9PT10LmFycmF5QnVmZmVycyYmKHQuYXJyYXlCdWZmZXJzPXt9KSx2b2lkIDA9PT10aGlzLmFycmF5LmJ1ZmZlci5fdXVpZCYmKHRoaXMuYXJyYXkuYnVmZmVyLl91dWlkPWR1KCkpLHZvaWQgMD09PXQuYXJyYXlCdWZmZXJzW3RoaXMuYXJyYXkuYnVmZmVyLl91dWlkXSYmKHQuYXJyYXlCdWZmZXJzW3RoaXMuYXJyYXkuYnVmZmVyLl91dWlkXT1BcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChuZXcgVWludDMyQXJyYXkodGhpcy5hcnJheS5idWZmZXIpKSkse3V1aWQ6dGhpcy51dWlkLGJ1ZmZlcjp0aGlzLmFycmF5LmJ1ZmZlci5fdXVpZCx0eXBlOnRoaXMuYXJyYXkuY29uc3RydWN0b3IubmFtZSxzdHJpZGU6dGhpcy5zdHJpZGV9fX07WWcucHJvdG90eXBlLmlzSW50ZXJsZWF2ZWRCdWZmZXI9ITA7dmFyIENvPW5ldyBpZSxYZz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyPSExKXt0aGlzLm5hbWU9IiIsdGhpcy5kYXRhPXQsdGhpcy5pdGVtU2l6ZT1lLHRoaXMub2Zmc2V0PWksdGhpcy5ub3JtYWxpemVkPSEwPT09cn1nZXQgY291bnQoKXtyZXR1cm4gdGhpcy5kYXRhLmNvdW50fWdldCBhcnJheSgpe3JldHVybiB0aGlzLmRhdGEuYXJyYXl9c2V0IG5lZWRzVXBkYXRlKHQpe3RoaXMuZGF0YS5uZWVkc1VwZGF0ZT10fWFwcGx5TWF0cml4NCh0KXtmb3IobGV0IGU9MCxpPXRoaXMuZGF0YS5jb3VudDtlPGk7ZSsrKUNvLng9dGhpcy5nZXRYKGUpLENvLnk9dGhpcy5nZXRZKGUpLENvLno9dGhpcy5nZXRaKGUpLENvLmFwcGx5TWF0cml4NCh0KSx0aGlzLnNldFhZWihlLENvLngsQ28ueSxDby56KTtyZXR1cm4gdGhpc31hcHBseU5vcm1hbE1hdHJpeCh0KXtmb3IobGV0IGU9MCxpPXRoaXMuY291bnQ7ZTxpO2UrKylDby54PXRoaXMuZ2V0WChlKSxDby55PXRoaXMuZ2V0WShlKSxDby56PXRoaXMuZ2V0WihlKSxDby5hcHBseU5vcm1hbE1hdHJpeCh0KSx0aGlzLnNldFhZWihlLENvLngsQ28ueSxDby56KTtyZXR1cm4gdGhpc310cmFuc2Zvcm1EaXJlY3Rpb24odCl7Zm9yKGxldCBlPTAsaT10aGlzLmNvdW50O2U8aTtlKyspQ28ueD10aGlzLmdldFgoZSksQ28ueT10aGlzLmdldFkoZSksQ28uej10aGlzLmdldFooZSksQ28udHJhbnNmb3JtRGlyZWN0aW9uKHQpLHRoaXMuc2V0WFlaKGUsQ28ueCxDby55LENvLnopO3JldHVybiB0aGlzfXNldFgodCxlKXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5W3QqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldF09ZSx0aGlzfXNldFkodCxlKXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5W3QqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldCsxXT1lLHRoaXN9c2V0Wih0LGUpe3JldHVybiB0aGlzLmRhdGEuYXJyYXlbdCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0KzJdPWUsdGhpc31zZXRXKHQsZSl7cmV0dXJuIHRoaXMuZGF0YS5hcnJheVt0KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQrM109ZSx0aGlzfWdldFgodCl7cmV0dXJuIHRoaXMuZGF0YS5hcnJheVt0KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXRdfWdldFkodCl7cmV0dXJuIHRoaXMuZGF0YS5hcnJheVt0KnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQrMV19Z2V0Wih0KXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5W3QqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldCsyXX1nZXRXKHQpe3JldHVybiB0aGlzLmRhdGEuYXJyYXlbdCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0KzNdfXNldFhZKHQsZSxpKXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5Wyh0PXQqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldCkrMF09ZSx0aGlzLmRhdGEuYXJyYXlbdCsxXT1pLHRoaXN9c2V0WFlaKHQsZSxpLHIpe3JldHVybiB0aGlzLmRhdGEuYXJyYXlbKHQ9dCp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0KSswXT1lLHRoaXMuZGF0YS5hcnJheVt0KzFdPWksdGhpcy5kYXRhLmFycmF5W3QrMl09cix0aGlzfXNldFhZWlcodCxlLGkscixvKXtyZXR1cm4gdGhpcy5kYXRhLmFycmF5Wyh0PXQqdGhpcy5kYXRhLnN0cmlkZSt0aGlzLm9mZnNldCkrMF09ZSx0aGlzLmRhdGEuYXJyYXlbdCsxXT1pLHRoaXMuZGF0YS5hcnJheVt0KzJdPXIsdGhpcy5kYXRhLmFycmF5W3QrM109byx0aGlzfWNsb25lKHQpe2lmKHZvaWQgMD09PXQpe2NvbnNvbGUubG9nKCJUSFJFRS5JbnRlcmxlYXZlZEJ1ZmZlckF0dHJpYnV0ZS5jbG9uZSgpOiBDbG9uaW5nIGFuIGludGVybGF2ZWQgYnVmZmVyIGF0dHJpYnV0ZSB3aWxsIGRlaW50ZXJsZWF2ZSBidWZmZXIgZGF0YS4iKTtsZXQgZT1bXTtmb3IobGV0IGk9MDtpPHRoaXMuY291bnQ7aSsrKXtsZXQgcj1pKnRoaXMuZGF0YS5zdHJpZGUrdGhpcy5vZmZzZXQ7Zm9yKGxldCBvPTA7bzx0aGlzLml0ZW1TaXplO28rKyllLnB1c2godGhpcy5kYXRhLmFycmF5W3Irb10pfXJldHVybiBuZXcgWXIobmV3IHRoaXMuYXJyYXkuY29uc3RydWN0b3IoZSksdGhpcy5pdGVtU2l6ZSx0aGlzLm5vcm1hbGl6ZWQpfXJldHVybiB2b2lkIDA9PT10LmludGVybGVhdmVkQnVmZmVycyYmKHQuaW50ZXJsZWF2ZWRCdWZmZXJzPXt9KSx2b2lkIDA9PT10LmludGVybGVhdmVkQnVmZmVyc1t0aGlzLmRhdGEudXVpZF0mJih0LmludGVybGVhdmVkQnVmZmVyc1t0aGlzLmRhdGEudXVpZF09dGhpcy5kYXRhLmNsb25lKHQpKSxuZXcgWGcodC5pbnRlcmxlYXZlZEJ1ZmZlcnNbdGhpcy5kYXRhLnV1aWRdLHRoaXMuaXRlbVNpemUsdGhpcy5vZmZzZXQsdGhpcy5ub3JtYWxpemVkKX10b0pTT04odCl7aWYodm9pZCAwPT09dCl7Y29uc29sZS5sb2coIlRIUkVFLkludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlLnRvSlNPTigpOiBTZXJpYWxpemluZyBhbiBpbnRlcmxhdmVkIGJ1ZmZlciBhdHRyaWJ1dGUgd2lsbCBkZWludGVybGVhdmUgYnVmZmVyIGRhdGEuIik7bGV0IGU9W107Zm9yKGxldCBpPTA7aTx0aGlzLmNvdW50O2krKyl7bGV0IHI9aSp0aGlzLmRhdGEuc3RyaWRlK3RoaXMub2Zmc2V0O2ZvcihsZXQgbz0wO288dGhpcy5pdGVtU2l6ZTtvKyspZS5wdXNoKHRoaXMuZGF0YS5hcnJheVtyK29dKX1yZXR1cm57aXRlbVNpemU6dGhpcy5pdGVtU2l6ZSx0eXBlOnRoaXMuYXJyYXkuY29uc3RydWN0b3IubmFtZSxhcnJheTplLG5vcm1hbGl6ZWQ6dGhpcy5ub3JtYWxpemVkfX1yZXR1cm4gdm9pZCAwPT09dC5pbnRlcmxlYXZlZEJ1ZmZlcnMmJih0LmludGVybGVhdmVkQnVmZmVycz17fSksdm9pZCAwPT09dC5pbnRlcmxlYXZlZEJ1ZmZlcnNbdGhpcy5kYXRhLnV1aWRdJiYodC5pbnRlcmxlYXZlZEJ1ZmZlcnNbdGhpcy5kYXRhLnV1aWRdPXRoaXMuZGF0YS50b0pTT04odCkpLHtpc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlOiEwLGl0ZW1TaXplOnRoaXMuaXRlbVNpemUsZGF0YTp0aGlzLmRhdGEudXVpZCxvZmZzZXQ6dGhpcy5vZmZzZXQsbm9ybWFsaXplZDp0aGlzLm5vcm1hbGl6ZWR9fX07WGcucHJvdG90eXBlLmlzSW50ZXJsZWF2ZWRCdWZmZXJBdHRyaWJ1dGU9ITA7dmFyIG1rPWNsYXNzIGV4dGVuZHMgaHN7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9IlNwcml0ZU1hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyB2bigxNjc3NzIxNSksdGhpcy5tYXA9bnVsbCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy5yb3RhdGlvbj0wLHRoaXMuc2l6ZUF0dGVudWF0aW9uPSEwLHRoaXMudHJhbnNwYXJlbnQ9ITAsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5tYXA9dC5tYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMucm90YXRpb249dC5yb3RhdGlvbix0aGlzLnNpemVBdHRlbnVhdGlvbj10LnNpemVBdHRlbnVhdGlvbix0aGlzfX07bWsucHJvdG90eXBlLmlzU3ByaXRlTWF0ZXJpYWw9ITA7dmFyIGViLE9TPW5ldyBpZSx0Yj1uZXcgaWUsbmI9bmV3IGllLGliPW5ldyBhdCxrUz1uZXcgYXQsV2RlPW5ldyBSbix6Tz1uZXcgaWUsRlM9bmV3IGllLGpPPW5ldyBpZSxuZGU9bmV3IGF0LGU4PW5ldyBhdCxpZGU9bmV3IGF0O2Z1bmN0aW9uIEdPKG4sdCxlLGkscixvKXtpYi5zdWJWZWN0b3JzKG4sZSkuYWRkU2NhbGFyKC41KS5tdWx0aXBseShpKSx2b2lkIDAhPT1yPyhrUy54PW8qaWIueC1yKmliLnksa1MueT1yKmliLngrbyppYi55KTprUy5jb3B5KGliKSxuLmNvcHkodCksbi54Kz1rUy54LG4ueSs9a1MueSxuLmFwcGx5TWF0cml4NChXZGUpfShjbGFzcyBleHRlbmRzIFhpe2NvbnN0cnVjdG9yKHQpe2lmKHN1cGVyKCksdGhpcy50eXBlPSJTcHJpdGUiLHZvaWQgMD09PWViKXtlYj1uZXcgbnI7bGV0IGU9bmV3IEZsb2F0MzJBcnJheShbLS41LC0uNSwwLDAsMCwuNSwtLjUsMCwxLDAsLjUsLjUsMCwxLDEsLS41LC41LDAsMCwxXSksaT1uZXcgWWcoZSw1KTtlYi5zZXRJbmRleChbMCwxLDIsMCwyLDNdKSxlYi5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixuZXcgWGcoaSwzLDAsITEpKSxlYi5zZXRBdHRyaWJ1dGUoInV2IixuZXcgWGcoaSwyLDMsITEpKX10aGlzLmdlb21ldHJ5PWViLHRoaXMubWF0ZXJpYWw9dm9pZCAwIT09dD90Om5ldyBtayx0aGlzLmNlbnRlcj1uZXcgYXQoLjUsLjUpfXJheWNhc3QodCxlKXtudWxsPT09dC5jYW1lcmEmJmNvbnNvbGUuZXJyb3IoJ1RIUkVFLlNwcml0ZTogIlJheWNhc3Rlci5jYW1lcmEiIG5lZWRzIHRvIGJlIHNldCBpbiBvcmRlciB0byByYXljYXN0IGFnYWluc3Qgc3ByaXRlcy4nKSx0Yi5zZXRGcm9tTWF0cml4U2NhbGUodGhpcy5tYXRyaXhXb3JsZCksV2RlLmNvcHkodC5jYW1lcmEubWF0cml4V29ybGQpLHRoaXMubW9kZWxWaWV3TWF0cml4Lm11bHRpcGx5TWF0cmljZXModC5jYW1lcmEubWF0cml4V29ybGRJbnZlcnNlLHRoaXMubWF0cml4V29ybGQpLG5iLnNldEZyb21NYXRyaXhQb3NpdGlvbih0aGlzLm1vZGVsVmlld01hdHJpeCksdC5jYW1lcmEuaXNQZXJzcGVjdGl2ZUNhbWVyYSYmITE9PT10aGlzLm1hdGVyaWFsLnNpemVBdHRlbnVhdGlvbiYmdGIubXVsdGlwbHlTY2FsYXIoLW5iLnopO2xldCByLG8saT10aGlzLm1hdGVyaWFsLnJvdGF0aW9uOzAhPT1pJiYobz1NYXRoLmNvcyhpKSxyPU1hdGguc2luKGkpKTtsZXQgcz10aGlzLmNlbnRlcjtHTyh6Ty5zZXQoLS41LC0uNSwwKSxuYixzLHRiLHIsbyksR08oRlMuc2V0KC41LC0uNSwwKSxuYixzLHRiLHIsbyksR08oak8uc2V0KC41LC41LDApLG5iLHMsdGIscixvKSxuZGUuc2V0KDAsMCksZTguc2V0KDEsMCksaWRlLnNldCgxLDEpO2xldCBhPXQucmF5LmludGVyc2VjdFRyaWFuZ2xlKHpPLEZTLGpPLCExLE9TKTtpZihudWxsPT09YSYmKEdPKEZTLnNldCgtLjUsLjUsMCksbmIscyx0YixyLG8pLGU4LnNldCgwLDEpLGE9dC5yYXkuaW50ZXJzZWN0VHJpYW5nbGUoek8sak8sRlMsITEsT1MpLG51bGw9PT1hKSlyZXR1cm47bGV0IGw9dC5yYXkub3JpZ2luLmRpc3RhbmNlVG8oT1MpO2w8dC5uZWFyfHxsPnQuZmFyfHxlLnB1c2goe2Rpc3RhbmNlOmwscG9pbnQ6T1MuY2xvbmUoKSx1djpsby5nZXRVVihPUyx6TyxGUyxqTyxuZGUsZTgsaWRlLG5ldyBhdCksZmFjZTpudWxsLG9iamVjdDp0aGlzfSl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx2b2lkIDAhPT10LmNlbnRlciYmdGhpcy5jZW50ZXIuY29weSh0LmNlbnRlciksdGhpcy5tYXRlcmlhbD10Lm1hdGVyaWFsLHRoaXN9fSkucHJvdG90eXBlLmlzU3ByaXRlPSEwO3ZhciByZGU9bmV3IGllLG9kZT1uZXcgYXIsc2RlPW5ldyBhcix2OWU9bmV3IGllLGFkZT1uZXcgUm4sZ2s9Y2xhc3MgZXh0ZW5kcyBWb3tjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKHQsZSksdGhpcy50eXBlPSJTa2lubmVkTWVzaCIsdGhpcy5iaW5kTW9kZT0iYXR0YWNoZWQiLHRoaXMuYmluZE1hdHJpeD1uZXcgUm4sdGhpcy5iaW5kTWF0cml4SW52ZXJzZT1uZXcgUm59Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmJpbmRNb2RlPXQuYmluZE1vZGUsdGhpcy5iaW5kTWF0cml4LmNvcHkodC5iaW5kTWF0cml4KSx0aGlzLmJpbmRNYXRyaXhJbnZlcnNlLmNvcHkodC5iaW5kTWF0cml4SW52ZXJzZSksdGhpcy5za2VsZXRvbj10LnNrZWxldG9uLHRoaXN9YmluZCh0LGUpe3RoaXMuc2tlbGV0b249dCx2b2lkIDA9PT1lJiYodGhpcy51cGRhdGVNYXRyaXhXb3JsZCghMCksdGhpcy5za2VsZXRvbi5jYWxjdWxhdGVJbnZlcnNlcygpLGU9dGhpcy5tYXRyaXhXb3JsZCksdGhpcy5iaW5kTWF0cml4LmNvcHkoZSksdGhpcy5iaW5kTWF0cml4SW52ZXJzZS5jb3B5KGUpLmludmVydCgpfXBvc2UoKXt0aGlzLnNrZWxldG9uLnBvc2UoKX1ub3JtYWxpemVTa2luV2VpZ2h0cygpe2xldCB0PW5ldyBhcixlPXRoaXMuZ2VvbWV0cnkuYXR0cmlidXRlcy5za2luV2VpZ2h0O2ZvcihsZXQgaT0wLHI9ZS5jb3VudDtpPHI7aSsrKXt0Lng9ZS5nZXRYKGkpLHQueT1lLmdldFkoaSksdC56PWUuZ2V0WihpKSx0Lnc9ZS5nZXRXKGkpO2xldCBvPTEvdC5tYW5oYXR0YW5MZW5ndGgoKTtvIT09MS8wP3QubXVsdGlwbHlTY2FsYXIobyk6dC5zZXQoMSwwLDAsMCksZS5zZXRYWVpXKGksdC54LHQueSx0LnosdC53KX19dXBkYXRlTWF0cml4V29ybGQodCl7c3VwZXIudXBkYXRlTWF0cml4V29ybGQodCksImF0dGFjaGVkIj09PXRoaXMuYmluZE1vZGU/dGhpcy5iaW5kTWF0cml4SW52ZXJzZS5jb3B5KHRoaXMubWF0cml4V29ybGQpLmludmVydCgpOiJkZXRhY2hlZCI9PT10aGlzLmJpbmRNb2RlP3RoaXMuYmluZE1hdHJpeEludmVyc2UuY29weSh0aGlzLmJpbmRNYXRyaXgpLmludmVydCgpOmNvbnNvbGUud2FybigiVEhSRUUuU2tpbm5lZE1lc2g6IFVucmVjb2duaXplZCBiaW5kTW9kZTogIit0aGlzLmJpbmRNb2RlKX1ib25lVHJhbnNmb3JtKHQsZSl7bGV0IGk9dGhpcy5za2VsZXRvbixyPXRoaXMuZ2VvbWV0cnk7b2RlLmZyb21CdWZmZXJBdHRyaWJ1dGUoci5hdHRyaWJ1dGVzLnNraW5JbmRleCx0KSxzZGUuZnJvbUJ1ZmZlckF0dHJpYnV0ZShyLmF0dHJpYnV0ZXMuc2tpbldlaWdodCx0KSxyZGUuY29weShlKS5hcHBseU1hdHJpeDQodGhpcy5iaW5kTWF0cml4KSxlLnNldCgwLDAsMCk7Zm9yKGxldCBvPTA7bzw0O28rKyl7bGV0IHM9c2RlLmdldENvbXBvbmVudChvKTtpZigwIT09cyl7bGV0IGE9b2RlLmdldENvbXBvbmVudChvKTthZGUubXVsdGlwbHlNYXRyaWNlcyhpLmJvbmVzW2FdLm1hdHJpeFdvcmxkLGkuYm9uZUludmVyc2VzW2FdKSxlLmFkZFNjYWxlZFZlY3Rvcih2OWUuY29weShyZGUpLmFwcGx5TWF0cml4NChhZGUpLHMpfX1yZXR1cm4gZS5hcHBseU1hdHJpeDQodGhpcy5iaW5kTWF0cml4SW52ZXJzZSl9fTtnay5wcm90b3R5cGUuaXNTa2lubmVkTWVzaD0hMCxjbGFzcyBleHRlbmRzIFhpe2NvbnN0cnVjdG9yKCl7c3VwZXIoKSx0aGlzLnR5cGU9IkJvbmUifX0ucHJvdG90eXBlLmlzQm9uZT0hMCxjbGFzcyBleHRlbmRzIEhve2NvbnN0cnVjdG9yKHQ9bnVsbCxlPTEsaT0xLHIsbyxzLGEsbCxjPVpvLHU9Wm8sZCxwKXtzdXBlcihudWxsLHMsYSxsLGMsdSxyLG8sZCxwKSx0aGlzLmltYWdlPXtkYXRhOnQsd2lkdGg6ZSxoZWlnaHQ6aX0sdGhpcy5tYWdGaWx0ZXI9Yyx0aGlzLm1pbkZpbHRlcj11LHRoaXMuZ2VuZXJhdGVNaXBtYXBzPSExLHRoaXMuZmxpcFk9ITEsdGhpcy51bnBhY2tBbGlnbm1lbnQ9MX19LnByb3RvdHlwZS5pc0RhdGFUZXh0dXJlPSEwO3ZhciBuRT1jbGFzcyBleHRlbmRzIFlye2NvbnN0cnVjdG9yKHQsZSxpLHI9MSl7Im51bWJlciI9PXR5cGVvZiBpJiYocj1pLGk9ITEsY29uc29sZS5lcnJvcigiVEhSRUUuSW5zdGFuY2VkQnVmZmVyQXR0cmlidXRlOiBUaGUgY29uc3RydWN0b3Igbm93IGV4cGVjdHMgbm9ybWFsaXplZCBhcyB0aGUgdGhpcmQgYXJndW1lbnQuIikpLHN1cGVyKHQsZSxpKSx0aGlzLm1lc2hQZXJBdHRyaWJ1dGU9cn1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMubWVzaFBlckF0dHJpYnV0ZT10Lm1lc2hQZXJBdHRyaWJ1dGUsdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC5tZXNoUGVyQXR0cmlidXRlPXRoaXMubWVzaFBlckF0dHJpYnV0ZSx0LmlzSW5zdGFuY2VkQnVmZmVyQXR0cmlidXRlPSEwLHR9fTtuRS5wcm90b3R5cGUuaXNJbnN0YW5jZWRCdWZmZXJBdHRyaWJ1dGU9ITA7dmFyIGxkZT1uZXcgUm4sY2RlPW5ldyBSbixXTz1bXSxOUz1uZXcgVm87KGNsYXNzIGV4dGVuZHMgVm97Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKHQsZSksdGhpcy5pbnN0YW5jZU1hdHJpeD1uZXcgbkUobmV3IEZsb2F0MzJBcnJheSgxNippKSwxNiksdGhpcy5pbnN0YW5jZUNvbG9yPW51bGwsdGhpcy5jb3VudD1pLHRoaXMuZnJ1c3R1bUN1bGxlZD0hMX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuaW5zdGFuY2VNYXRyaXguY29weSh0Lmluc3RhbmNlTWF0cml4KSxudWxsIT09dC5pbnN0YW5jZUNvbG9yJiYodGhpcy5pbnN0YW5jZUNvbG9yPXQuaW5zdGFuY2VDb2xvci5jbG9uZSgpKSx0aGlzLmNvdW50PXQuY291bnQsdGhpc31nZXRDb2xvckF0KHQsZSl7ZS5mcm9tQXJyYXkodGhpcy5pbnN0YW5jZUNvbG9yLmFycmF5LDMqdCl9Z2V0TWF0cml4QXQodCxlKXtlLmZyb21BcnJheSh0aGlzLmluc3RhbmNlTWF0cml4LmFycmF5LDE2KnQpfXJheWNhc3QodCxlKXtsZXQgaT10aGlzLm1hdHJpeFdvcmxkLHI9dGhpcy5jb3VudDtpZihOUy5nZW9tZXRyeT10aGlzLmdlb21ldHJ5LE5TLm1hdGVyaWFsPXRoaXMubWF0ZXJpYWwsdm9pZCAwIT09TlMubWF0ZXJpYWwpZm9yKGxldCBvPTA7bzxyO28rKyl7dGhpcy5nZXRNYXRyaXhBdChvLGxkZSksY2RlLm11bHRpcGx5TWF0cmljZXMoaSxsZGUpLE5TLm1hdHJpeFdvcmxkPWNkZSxOUy5yYXljYXN0KHQsV08pO2ZvcihsZXQgcz0wLGE9V08ubGVuZ3RoO3M8YTtzKyspe2xldCBsPVdPW3NdO2wuaW5zdGFuY2VJZD1vLGwub2JqZWN0PXRoaXMsZS5wdXNoKGwpfVdPLmxlbmd0aD0wfX1zZXRDb2xvckF0KHQsZSl7bnVsbD09PXRoaXMuaW5zdGFuY2VDb2xvciYmKHRoaXMuaW5zdGFuY2VDb2xvcj1uZXcgbkUobmV3IEZsb2F0MzJBcnJheSgzKnRoaXMuaW5zdGFuY2VNYXRyaXguY291bnQpLDMpKSxlLnRvQXJyYXkodGhpcy5pbnN0YW5jZUNvbG9yLmFycmF5LDMqdCl9c2V0TWF0cml4QXQodCxlKXtlLnRvQXJyYXkodGhpcy5pbnN0YW5jZU1hdHJpeC5hcnJheSwxNip0KX11cGRhdGVNb3JwaFRhcmdldHMoKXt9ZGlzcG9zZSgpe3RoaXMuZGlzcGF0Y2hFdmVudCh7dHlwZToiZGlzcG9zZSJ9KX19KS5wcm90b3R5cGUuaXNJbnN0YW5jZWRNZXNoPSEwO3ZhciBBcD1jbGFzcyBleHRlbmRzIGhze2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJMaW5lQmFzaWNNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgdm4oMTY3NzcyMTUpLHRoaXMubGluZXdpZHRoPTEsdGhpcy5saW5lY2FwPSJyb3VuZCIsdGhpcy5saW5lam9pbj0icm91bmQiLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMubGluZXdpZHRoPXQubGluZXdpZHRoLHRoaXMubGluZWNhcD10LmxpbmVjYXAsdGhpcy5saW5lam9pbj10LmxpbmVqb2luLHRoaXN9fTtBcC5wcm90b3R5cGUuaXNMaW5lQmFzaWNNYXRlcmlhbD0hMDt2YXIgdWRlPW5ldyBpZSxkZGU9bmV3IGllLHBkZT1uZXcgUm4sdDg9bmV3IENmLHFPPW5ldyB4ZixpRT1jbGFzcyBleHRlbmRzIFhpe2NvbnN0cnVjdG9yKHQ9bmV3IG5yLGU9bmV3IEFwKXtzdXBlcigpLHRoaXMudHlwZT0iTGluZSIsdGhpcy5nZW9tZXRyeT10LHRoaXMubWF0ZXJpYWw9ZSx0aGlzLnVwZGF0ZU1vcnBoVGFyZ2V0cygpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5tYXRlcmlhbD10Lm1hdGVyaWFsLHRoaXMuZ2VvbWV0cnk9dC5nZW9tZXRyeSx0aGlzfWNvbXB1dGVMaW5lRGlzdGFuY2VzKCl7bGV0IHQ9dGhpcy5nZW9tZXRyeTtpZih0LmlzQnVmZmVyR2VvbWV0cnkpaWYobnVsbD09PXQuaW5kZXgpe2xldCBlPXQuYXR0cmlidXRlcy5wb3NpdGlvbixpPVswXTtmb3IobGV0IHI9MSxvPWUuY291bnQ7cjxvO3IrKyl1ZGUuZnJvbUJ1ZmZlckF0dHJpYnV0ZShlLHItMSksZGRlLmZyb21CdWZmZXJBdHRyaWJ1dGUoZSxyKSxpW3JdPWlbci0xXSxpW3JdKz11ZGUuZGlzdGFuY2VUbyhkZGUpO3Quc2V0QXR0cmlidXRlKCJsaW5lRGlzdGFuY2UiLG5ldyBKcihpLDEpKX1lbHNlIGNvbnNvbGUud2FybigiVEhSRUUuTGluZS5jb21wdXRlTGluZURpc3RhbmNlcygpOiBDb21wdXRhdGlvbiBvbmx5IHBvc3NpYmxlIHdpdGggbm9uLWluZGV4ZWQgQnVmZmVyR2VvbWV0cnkuIik7ZWxzZSB0LmlzR2VvbWV0cnkmJmNvbnNvbGUuZXJyb3IoIlRIUkVFLkxpbmUuY29tcHV0ZUxpbmVEaXN0YW5jZXMoKSBubyBsb25nZXIgc3VwcG9ydHMgVEhSRUUuR2VvbWV0cnkuIFVzZSBUSFJFRS5CdWZmZXJHZW9tZXRyeSBpbnN0ZWFkLiIpO3JldHVybiB0aGlzfXJheWNhc3QodCxlKXtsZXQgaT10aGlzLmdlb21ldHJ5LHI9dGhpcy5tYXRyaXhXb3JsZCxvPXQucGFyYW1zLkxpbmUudGhyZXNob2xkLHM9aS5kcmF3UmFuZ2U7aWYobnVsbD09PWkuYm91bmRpbmdTcGhlcmUmJmkuY29tcHV0ZUJvdW5kaW5nU3BoZXJlKCkscU8uY29weShpLmJvdW5kaW5nU3BoZXJlKSxxTy5hcHBseU1hdHJpeDQocikscU8ucmFkaXVzKz1vLCExPT09dC5yYXkuaW50ZXJzZWN0c1NwaGVyZShxTykpcmV0dXJuO3BkZS5jb3B5KHIpLmludmVydCgpLHQ4LmNvcHkodC5yYXkpLmFwcGx5TWF0cml4NChwZGUpO2xldCBhPW8vKCh0aGlzLnNjYWxlLngrdGhpcy5zY2FsZS55K3RoaXMuc2NhbGUueikvMyksbD1hKmEsYz1uZXcgaWUsdT1uZXcgaWUsZD1uZXcgaWUscD1uZXcgaWUsaD10aGlzLmlzTGluZVNlZ21lbnRzPzI6MTtpZihpLmlzQnVmZmVyR2VvbWV0cnkpe2xldCBmPWkuaW5kZXgseD1pLmF0dHJpYnV0ZXMucG9zaXRpb247aWYobnVsbCE9PWYpZm9yKGxldCBEPU1hdGgubWF4KDAscy5zdGFydCksVD1NYXRoLm1pbihmLmNvdW50LHMuc3RhcnQrcy5jb3VudCktMTtEPFQ7RCs9aCl7bGV0IGs9Zi5nZXRYKEQpLFo9Zi5nZXRYKEQrMSk7aWYoYy5mcm9tQnVmZmVyQXR0cmlidXRlKHgsayksdS5mcm9tQnVmZmVyQXR0cmlidXRlKHgsWiksdDguZGlzdGFuY2VTcVRvU2VnbWVudChjLHUscCxkKT5sKWNvbnRpbnVlO3AuYXBwbHlNYXRyaXg0KHRoaXMubWF0cml4V29ybGQpO2xldCBmZT10LnJheS5vcmlnaW4uZGlzdGFuY2VUbyhwKTtmZTx0Lm5lYXJ8fGZlPnQuZmFyfHxlLnB1c2goe2Rpc3RhbmNlOmZlLHBvaW50OmQuY2xvbmUoKS5hcHBseU1hdHJpeDQodGhpcy5tYXRyaXhXb3JsZCksaW5kZXg6RCxmYWNlOm51bGwsZmFjZUluZGV4Om51bGwsb2JqZWN0OnRoaXN9KX1lbHNlIGZvcihsZXQgRD1NYXRoLm1heCgwLHMuc3RhcnQpLFQ9TWF0aC5taW4oeC5jb3VudCxzLnN0YXJ0K3MuY291bnQpLTE7RDxUO0QrPWgpe2lmKGMuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh4LEQpLHUuZnJvbUJ1ZmZlckF0dHJpYnV0ZSh4LEQrMSksdDguZGlzdGFuY2VTcVRvU2VnbWVudChjLHUscCxkKT5sKWNvbnRpbnVlO3AuYXBwbHlNYXRyaXg0KHRoaXMubWF0cml4V29ybGQpO2xldCBaPXQucmF5Lm9yaWdpbi5kaXN0YW5jZVRvKHApO1o8dC5uZWFyfHxaPnQuZmFyfHxlLnB1c2goe2Rpc3RhbmNlOloscG9pbnQ6ZC5jbG9uZSgpLmFwcGx5TWF0cml4NCh0aGlzLm1hdHJpeFdvcmxkKSxpbmRleDpELGZhY2U6bnVsbCxmYWNlSW5kZXg6bnVsbCxvYmplY3Q6dGhpc30pfX1lbHNlIGkuaXNHZW9tZXRyeSYmY29uc29sZS5lcnJvcigiVEhSRUUuTGluZS5yYXljYXN0KCkgbm8gbG9uZ2VyIHN1cHBvcnRzIFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX11cGRhdGVNb3JwaFRhcmdldHMoKXtsZXQgdD10aGlzLmdlb21ldHJ5O2lmKHQuaXNCdWZmZXJHZW9tZXRyeSl7bGV0IGU9dC5tb3JwaEF0dHJpYnV0ZXMsaT1PYmplY3Qua2V5cyhlKTtpZihpLmxlbmd0aD4wKXtsZXQgcj1lW2lbMF1dO2lmKHZvaWQgMCE9PXIpe3RoaXMubW9ycGhUYXJnZXRJbmZsdWVuY2VzPVtdLHRoaXMubW9ycGhUYXJnZXREaWN0aW9uYXJ5PXt9O2ZvcihsZXQgbz0wLHM9ci5sZW5ndGg7bzxzO28rKyl7bGV0IGE9cltvXS5uYW1lfHxTdHJpbmcobyk7dGhpcy5tb3JwaFRhcmdldEluZmx1ZW5jZXMucHVzaCgwKSx0aGlzLm1vcnBoVGFyZ2V0RGljdGlvbmFyeVthXT1vfX19fWVsc2V7bGV0IGU9dC5tb3JwaFRhcmdldHM7dm9pZCAwIT09ZSYmZS5sZW5ndGg+MCYmY29uc29sZS5lcnJvcigiVEhSRUUuTGluZS51cGRhdGVNb3JwaFRhcmdldHMoKSBkb2VzIG5vdCBzdXBwb3J0IFRIUkVFLkdlb21ldHJ5LiBVc2UgVEhSRUUuQnVmZmVyR2VvbWV0cnkgaW5zdGVhZC4iKX19fTtpRS5wcm90b3R5cGUuaXNMaW5lPSEwO3ZhciBoZGU9bmV3IGllLGZkZT1uZXcgaWUsckU9Y2xhc3MgZXh0ZW5kcyBpRXtjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKHQsZSksdGhpcy50eXBlPSJMaW5lU2VnbWVudHMifWNvbXB1dGVMaW5lRGlzdGFuY2VzKCl7bGV0IHQ9dGhpcy5nZW9tZXRyeTtpZih0LmlzQnVmZmVyR2VvbWV0cnkpaWYobnVsbD09PXQuaW5kZXgpe2xldCBlPXQuYXR0cmlidXRlcy5wb3NpdGlvbixpPVtdO2ZvcihsZXQgcj0wLG89ZS5jb3VudDtyPG87cis9MiloZGUuZnJvbUJ1ZmZlckF0dHJpYnV0ZShlLHIpLGZkZS5mcm9tQnVmZmVyQXR0cmlidXRlKGUscisxKSxpW3JdPTA9PT1yPzA6aVtyLTFdLGlbcisxXT1pW3JdK2hkZS5kaXN0YW5jZVRvKGZkZSk7dC5zZXRBdHRyaWJ1dGUoImxpbmVEaXN0YW5jZSIsbmV3IEpyKGksMSkpfWVsc2UgY29uc29sZS53YXJuKCJUSFJFRS5MaW5lU2VnbWVudHMuY29tcHV0ZUxpbmVEaXN0YW5jZXMoKTogQ29tcHV0YXRpb24gb25seSBwb3NzaWJsZSB3aXRoIG5vbi1pbmRleGVkIEJ1ZmZlckdlb21ldHJ5LiIpO2Vsc2UgdC5pc0dlb21ldHJ5JiZjb25zb2xlLmVycm9yKCJUSFJFRS5MaW5lU2VnbWVudHMuY29tcHV0ZUxpbmVEaXN0YW5jZXMoKSBubyBsb25nZXIgc3VwcG9ydHMgVEhSRUUuR2VvbWV0cnkuIFVzZSBUSFJFRS5CdWZmZXJHZW9tZXRyeSBpbnN0ZWFkLiIpO3JldHVybiB0aGlzfX07ckUucHJvdG90eXBlLmlzTGluZVNlZ21lbnRzPSEwLGNsYXNzIGV4dGVuZHMgaUV7Y29uc3RydWN0b3IodCxlKXtzdXBlcih0LGUpLHRoaXMudHlwZT0iTGluZUxvb3AifX0ucHJvdG90eXBlLmlzTGluZUxvb3A9ITA7dmFyIF9rPWNsYXNzIGV4dGVuZHMgaHN7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9IlBvaW50c01hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyB2bigxNjc3NzIxNSksdGhpcy5tYXA9bnVsbCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy5zaXplPTEsdGhpcy5zaXplQXR0ZW51YXRpb249ITAsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5tYXA9dC5tYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuc2l6ZT10LnNpemUsdGhpcy5zaXplQXR0ZW51YXRpb249dC5zaXplQXR0ZW51YXRpb24sdGhpc319O19rLnByb3RvdHlwZS5pc1BvaW50c01hdGVyaWFsPSEwO3ZhciBtZGU9bmV3IFJuLEU4PW5ldyBDZixZTz1uZXcgeGYsWE89bmV3IGllO2Z1bmN0aW9uIGdkZShuLHQsZSxpLHIsbyxzKXtsZXQgYT1FOC5kaXN0YW5jZVNxVG9Qb2ludChuKTtpZihhPGUpe2xldCBsPW5ldyBpZTtFOC5jbG9zZXN0UG9pbnRUb1BvaW50KG4sbCksbC5hcHBseU1hdHJpeDQoaSk7bGV0IGM9ci5yYXkub3JpZ2luLmRpc3RhbmNlVG8obCk7aWYoYzxyLm5lYXJ8fGM+ci5mYXIpcmV0dXJuO28ucHVzaCh7ZGlzdGFuY2U6YyxkaXN0YW5jZVRvUmF5Ok1hdGguc3FydChhKSxwb2ludDpsLGluZGV4OnQsZmFjZTpudWxsLG9iamVjdDpzfSl9fShjbGFzcyBleHRlbmRzIFhpe2NvbnN0cnVjdG9yKHQ9bmV3IG5yLGU9bmV3IF9rKXtzdXBlcigpLHRoaXMudHlwZT0iUG9pbnRzIix0aGlzLmdlb21ldHJ5PXQsdGhpcy5tYXRlcmlhbD1lLHRoaXMudXBkYXRlTW9ycGhUYXJnZXRzKCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLm1hdGVyaWFsPXQubWF0ZXJpYWwsdGhpcy5nZW9tZXRyeT10Lmdlb21ldHJ5LHRoaXN9cmF5Y2FzdCh0LGUpe2xldCBpPXRoaXMuZ2VvbWV0cnkscj10aGlzLm1hdHJpeFdvcmxkLG89dC5wYXJhbXMuUG9pbnRzLnRocmVzaG9sZCxzPWkuZHJhd1JhbmdlO2lmKG51bGw9PT1pLmJvdW5kaW5nU3BoZXJlJiZpLmNvbXB1dGVCb3VuZGluZ1NwaGVyZSgpLFlPLmNvcHkoaS5ib3VuZGluZ1NwaGVyZSksWU8uYXBwbHlNYXRyaXg0KHIpLFlPLnJhZGl1cys9bywhMT09PXQucmF5LmludGVyc2VjdHNTcGhlcmUoWU8pKXJldHVybjttZGUuY29weShyKS5pbnZlcnQoKSxFOC5jb3B5KHQucmF5KS5hcHBseU1hdHJpeDQobWRlKTtsZXQgYT1vLygodGhpcy5zY2FsZS54K3RoaXMuc2NhbGUueSt0aGlzLnNjYWxlLnopLzMpLGw9YSphO2lmKGkuaXNCdWZmZXJHZW9tZXRyeSl7bGV0IGM9aS5pbmRleCxkPWkuYXR0cmlidXRlcy5wb3NpdGlvbjtpZihudWxsIT09Yylmb3IobGV0IGY9TWF0aC5tYXgoMCxzLnN0YXJ0KSxtPU1hdGgubWluKGMuY291bnQscy5zdGFydCtzLmNvdW50KTtmPG07ZisrKXtsZXQgeD1jLmdldFgoZik7WE8uZnJvbUJ1ZmZlckF0dHJpYnV0ZShkLHgpLGdkZShYTyx4LGwscix0LGUsdGhpcyl9ZWxzZSBmb3IobGV0IGY9TWF0aC5tYXgoMCxzLnN0YXJ0KSxtPU1hdGgubWluKGQuY291bnQscy5zdGFydCtzLmNvdW50KTtmPG07ZisrKVhPLmZyb21CdWZmZXJBdHRyaWJ1dGUoZCxmKSxnZGUoWE8sZixsLHIsdCxlLHRoaXMpfWVsc2UgY29uc29sZS5lcnJvcigiVEhSRUUuUG9pbnRzLnJheWNhc3QoKSBubyBsb25nZXIgc3VwcG9ydHMgVEhSRUUuR2VvbWV0cnkuIFVzZSBUSFJFRS5CdWZmZXJHZW9tZXRyeSBpbnN0ZWFkLiIpfXVwZGF0ZU1vcnBoVGFyZ2V0cygpe2xldCB0PXRoaXMuZ2VvbWV0cnk7aWYodC5pc0J1ZmZlckdlb21ldHJ5KXtsZXQgZT10Lm1vcnBoQXR0cmlidXRlcyxpPU9iamVjdC5rZXlzKGUpO2lmKGkubGVuZ3RoPjApe2xldCByPWVbaVswXV07aWYodm9pZCAwIT09cil7dGhpcy5tb3JwaFRhcmdldEluZmx1ZW5jZXM9W10sdGhpcy5tb3JwaFRhcmdldERpY3Rpb25hcnk9e307Zm9yKGxldCBvPTAscz1yLmxlbmd0aDtvPHM7bysrKXtsZXQgYT1yW29dLm5hbWV8fFN0cmluZyhvKTt0aGlzLm1vcnBoVGFyZ2V0SW5mbHVlbmNlcy5wdXNoKDApLHRoaXMubW9ycGhUYXJnZXREaWN0aW9uYXJ5W2FdPW99fX19ZWxzZXtsZXQgZT10Lm1vcnBoVGFyZ2V0czt2b2lkIDAhPT1lJiZlLmxlbmd0aD4wJiZjb25zb2xlLmVycm9yKCJUSFJFRS5Qb2ludHMudXBkYXRlTW9ycGhUYXJnZXRzKCkgZG9lcyBub3Qgc3VwcG9ydCBUSFJFRS5HZW9tZXRyeS4gVXNlIFRIUkVFLkJ1ZmZlckdlb21ldHJ5IGluc3RlYWQuIil9fX0pLnByb3RvdHlwZS5pc1BvaW50cz0hMCxjbGFzcyBleHRlbmRzIEhve2NvbnN0cnVjdG9yKHQsZSxpLHIsbyxzLGEsbCxjKXtzdXBlcih0LGUsaSxyLG8scyxhLGwsYyksdGhpcy5taW5GaWx0ZXI9dm9pZCAwIT09cz9zOkdzLHRoaXMubWFnRmlsdGVyPXZvaWQgMCE9PW8/bzpHcyx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMTtsZXQgdT10aGlzOyJyZXF1ZXN0VmlkZW9GcmFtZUNhbGxiYWNrImluIHQmJnQucmVxdWVzdFZpZGVvRnJhbWVDYWxsYmFjayhmdW5jdGlvbiBkKCl7dS5uZWVkc1VwZGF0ZT0hMCx0LnJlcXVlc3RWaWRlb0ZyYW1lQ2FsbGJhY2soZCl9KX1jbG9uZSgpe3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3Rvcih0aGlzLmltYWdlKS5jb3B5KHRoaXMpfXVwZGF0ZSgpe2xldCB0PXRoaXMuaW1hZ2U7InJlcXVlc3RWaWRlb0ZyYW1lQ2FsbGJhY2siaW4gdD09MCYmdC5yZWFkeVN0YXRlPj10LkhBVkVfQ1VSUkVOVF9EQVRBJiYodGhpcy5uZWVkc1VwZGF0ZT0hMCl9fS5wcm90b3R5cGUuaXNWaWRlb1RleHR1cmU9ITAsY2xhc3MgZXh0ZW5kcyBIb3tjb25zdHJ1Y3Rvcih0LGUsaSl7c3VwZXIoe3dpZHRoOnQsaGVpZ2h0OmV9KSx0aGlzLmZvcm1hdD1pLHRoaXMubWFnRmlsdGVyPVpvLHRoaXMubWluRmlsdGVyPVpvLHRoaXMuZ2VuZXJhdGVNaXBtYXBzPSExLHRoaXMubmVlZHNVcGRhdGU9ITB9fS5wcm90b3R5cGUuaXNGcmFtZWJ1ZmZlclRleHR1cmU9ITAsY2xhc3MgZXh0ZW5kcyBIb3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyxhLGwsYyx1LGQscCl7c3VwZXIobnVsbCxzLGEsbCxjLHUscixvLGQscCksdGhpcy5pbWFnZT17d2lkdGg6ZSxoZWlnaHQ6aX0sdGhpcy5taXBtYXBzPXQsdGhpcy5mbGlwWT0hMSx0aGlzLmdlbmVyYXRlTWlwbWFwcz0hMX19LnByb3RvdHlwZS5pc0NvbXByZXNzZWRUZXh0dXJlPSEwLGNsYXNzIGV4dGVuZHMgSG97Y29uc3RydWN0b3IodCxlLGkscixvLHMsYSxsLGMpe3N1cGVyKHQsZSxpLHIsbyxzLGEsbCxjKSx0aGlzLm5lZWRzVXBkYXRlPSEwfX0ucHJvdG90eXBlLmlzQ2FudmFzVGV4dHVyZT0hMDt2YXIgeWI9Y2xhc3MgZXh0ZW5kcyBucntjb25zdHJ1Y3Rvcih0PTEsZT04LGk9MCxyPTIqTWF0aC5QSSl7c3VwZXIoKSx0aGlzLnR5cGU9IkNpcmNsZUdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3JhZGl1czp0LHNlZ21lbnRzOmUsdGhldGFTdGFydDppLHRoZXRhTGVuZ3RoOnJ9LGU9TWF0aC5tYXgoMyxlKTtsZXQgbz1bXSxzPVtdLGE9W10sbD1bXSxjPW5ldyBpZSx1PW5ldyBhdDtzLnB1c2goMCwwLDApLGEucHVzaCgwLDAsMSksbC5wdXNoKC41LC41KTtmb3IobGV0IGQ9MCxwPTM7ZDw9ZTtkKysscCs9Myl7bGV0IGg9aStkL2UqcjtjLng9dCpNYXRoLmNvcyhoKSxjLnk9dCpNYXRoLnNpbihoKSxzLnB1c2goYy54LGMueSxjLnopLGEucHVzaCgwLDAsMSksdS54PShzW3BdL3QrMSkvMix1Lnk9KHNbcCsxXS90KzEpLzIsbC5wdXNoKHUueCx1LnkpfWZvcihsZXQgZD0xO2Q8PWU7ZCsrKW8ucHVzaChkLGQrMSwwKTt0aGlzLnNldEluZGV4KG8pLHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IEpyKHMsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG5ldyBKcihhLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyBKcihsLDIpKX1zdGF0aWMgZnJvbUpTT04odCl7cmV0dXJuIG5ldyB5Yih0LnJhZGl1cyx0LnNlZ21lbnRzLHQudGhldGFTdGFydCx0LnRoZXRhTGVuZ3RoKX19LHFhPShuZXcgaWUsbmV3IGllLG5ldyBpZSxuZXcgbG8sY2xhc3N7Y29uc3RydWN0b3IoKXt0aGlzLnR5cGU9IkN1cnZlIix0aGlzLmFyY0xlbmd0aERpdmlzaW9ucz0yMDB9Z2V0UG9pbnQoKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5DdXJ2ZTogLmdldFBvaW50KCkgbm90IGltcGxlbWVudGVkLiIpLG51bGx9Z2V0UG9pbnRBdCh0LGUpe2xldCBpPXRoaXMuZ2V0VXRvVG1hcHBpbmcodCk7cmV0dXJuIHRoaXMuZ2V0UG9pbnQoaSxlKX1nZXRQb2ludHModD01KXtsZXQgZT1bXTtmb3IobGV0IGk9MDtpPD10O2krKyllLnB1c2godGhpcy5nZXRQb2ludChpL3QpKTtyZXR1cm4gZX1nZXRTcGFjZWRQb2ludHModD01KXtsZXQgZT1bXTtmb3IobGV0IGk9MDtpPD10O2krKyllLnB1c2godGhpcy5nZXRQb2ludEF0KGkvdCkpO3JldHVybiBlfWdldExlbmd0aCgpe2xldCB0PXRoaXMuZ2V0TGVuZ3RocygpO3JldHVybiB0W3QubGVuZ3RoLTFdfWdldExlbmd0aHModD10aGlzLmFyY0xlbmd0aERpdmlzaW9ucyl7aWYodGhpcy5jYWNoZUFyY0xlbmd0aHMmJnRoaXMuY2FjaGVBcmNMZW5ndGhzLmxlbmd0aD09PXQrMSYmIXRoaXMubmVlZHNVcGRhdGUpcmV0dXJuIHRoaXMuY2FjaGVBcmNMZW5ndGhzO3RoaXMubmVlZHNVcGRhdGU9ITE7bGV0IGksZT1bXSxyPXRoaXMuZ2V0UG9pbnQoMCksbz0wO2UucHVzaCgwKTtmb3IobGV0IHM9MTtzPD10O3MrKylpPXRoaXMuZ2V0UG9pbnQocy90KSxvKz1pLmRpc3RhbmNlVG8ociksZS5wdXNoKG8pLHI9aTtyZXR1cm4gdGhpcy5jYWNoZUFyY0xlbmd0aHM9ZSxlfXVwZGF0ZUFyY0xlbmd0aHMoKXt0aGlzLm5lZWRzVXBkYXRlPSEwLHRoaXMuZ2V0TGVuZ3RocygpfWdldFV0b1RtYXBwaW5nKHQsZSl7bGV0IHMsaT10aGlzLmdldExlbmd0aHMoKSxyPTAsbz1pLmxlbmd0aDtzPWV8fHQqaVtvLTFdO2xldCBjLGE9MCxsPW8tMTtmb3IoO2E8PWw7KWlmKHI9TWF0aC5mbG9vcihhKyhsLWEpLzIpLGM9aVtyXS1zLGM8MClhPXIrMTtlbHNle2lmKCEoYz4wKSl7bD1yO2JyZWFrfWw9ci0xfWlmKHI9bCxpW3JdPT09cylyZXR1cm4gci8oby0xKTtsZXQgdT1pW3JdO3JldHVybihyKyhzLXUpLyhpW3IrMV0tdSkpLyhvLTEpfWdldFRhbmdlbnQodCxlKXtsZXQgcj10LTFlLTQsbz10KzFlLTQ7cjwwJiYocj0wKSxvPjEmJihvPTEpO2xldCBzPXRoaXMuZ2V0UG9pbnQociksYT10aGlzLmdldFBvaW50KG8pLGw9ZXx8KHMuaXNWZWN0b3IyP25ldyBhdDpuZXcgaWUpO3JldHVybiBsLmNvcHkoYSkuc3ViKHMpLm5vcm1hbGl6ZSgpLGx9Z2V0VGFuZ2VudEF0KHQsZSl7bGV0IGk9dGhpcy5nZXRVdG9UbWFwcGluZyh0KTtyZXR1cm4gdGhpcy5nZXRUYW5nZW50KGksZSl9Y29tcHV0ZUZyZW5ldEZyYW1lcyh0LGUpe2xldCBpPW5ldyBpZSxyPVtdLG89W10scz1bXSxhPW5ldyBpZSxsPW5ldyBSbjtmb3IobGV0IGg9MDtoPD10O2grKylyW2hdPXRoaXMuZ2V0VGFuZ2VudEF0KGgvdCxuZXcgaWUpO29bMF09bmV3IGllLHNbMF09bmV3IGllO2xldCBjPU51bWJlci5NQVhfVkFMVUUsdT1NYXRoLmFicyhyWzBdLngpLGQ9TWF0aC5hYnMoclswXS55KSxwPU1hdGguYWJzKHJbMF0ueik7dTw9YyYmKGM9dSxpLnNldCgxLDAsMCkpLGQ8PWMmJihjPWQsaS5zZXQoMCwxLDApKSxwPD1jJiZpLnNldCgwLDAsMSksYS5jcm9zc1ZlY3RvcnMoclswXSxpKS5ub3JtYWxpemUoKSxvWzBdLmNyb3NzVmVjdG9ycyhyWzBdLGEpLHNbMF0uY3Jvc3NWZWN0b3JzKHJbMF0sb1swXSk7Zm9yKGxldCBoPTE7aDw9dDtoKyspe2lmKG9baF09b1toLTFdLmNsb25lKCksc1toXT1zW2gtMV0uY2xvbmUoKSxhLmNyb3NzVmVjdG9ycyhyW2gtMV0scltoXSksYS5sZW5ndGgoKT5OdW1iZXIuRVBTSUxPTil7YS5ub3JtYWxpemUoKTtsZXQgZj1NYXRoLmFjb3MoR2EocltoLTFdLmRvdChyW2hdKSwtMSwxKSk7b1toXS5hcHBseU1hdHJpeDQobC5tYWtlUm90YXRpb25BeGlzKGEsZikpfXNbaF0uY3Jvc3NWZWN0b3JzKHJbaF0sb1toXSl9aWYoITA9PT1lKXtsZXQgaD1NYXRoLmFjb3MoR2Eob1swXS5kb3Qob1t0XSksLTEsMSkpO2gvPXQsclswXS5kb3QoYS5jcm9zc1ZlY3RvcnMob1swXSxvW3RdKSk+MCYmKGg9LWgpO2ZvcihsZXQgZj0xO2Y8PXQ7ZisrKW9bZl0uYXBwbHlNYXRyaXg0KGwubWFrZVJvdGF0aW9uQXhpcyhyW2ZdLGgqZikpLHNbZl0uY3Jvc3NWZWN0b3JzKHJbZl0sb1tmXSl9cmV0dXJue3RhbmdlbnRzOnIsbm9ybWFsczpvLGJpbm9ybWFsczpzfX1jbG9uZSgpe3JldHVybihuZXcgdGhpcy5jb25zdHJ1Y3RvcikuY29weSh0aGlzKX1jb3B5KHQpe3JldHVybiB0aGlzLmFyY0xlbmd0aERpdmlzaW9ucz10LmFyY0xlbmd0aERpdmlzaW9ucyx0aGlzfXRvSlNPTigpe2xldCB0PXttZXRhZGF0YTp7dmVyc2lvbjo0LjUsdHlwZToiQ3VydmUiLGdlbmVyYXRvcjoiQ3VydmUudG9KU09OIn19O3JldHVybiB0LmFyY0xlbmd0aERpdmlzaW9ucz10aGlzLmFyY0xlbmd0aERpdmlzaW9ucyx0LnR5cGU9dGhpcy50eXBlLHR9ZnJvbUpTT04odCl7cmV0dXJuIHRoaXMuYXJjTGVuZ3RoRGl2aXNpb25zPXQuYXJjTGVuZ3RoRGl2aXNpb25zLHRoaXN9fSksYmI9Y2xhc3MgZXh0ZW5kcyBxYXtjb25zdHJ1Y3Rvcih0PTAsZT0wLGk9MSxyPTEsbz0wLHM9MipNYXRoLlBJLGE9ITEsbD0wKXtzdXBlcigpLHRoaXMudHlwZT0iRWxsaXBzZUN1cnZlIix0aGlzLmFYPXQsdGhpcy5hWT1lLHRoaXMueFJhZGl1cz1pLHRoaXMueVJhZGl1cz1yLHRoaXMuYVN0YXJ0QW5nbGU9byx0aGlzLmFFbmRBbmdsZT1zLHRoaXMuYUNsb2Nrd2lzZT1hLHRoaXMuYVJvdGF0aW9uPWx9Z2V0UG9pbnQodCxlKXtsZXQgaT1lfHxuZXcgYXQscj0yKk1hdGguUEksbz10aGlzLmFFbmRBbmdsZS10aGlzLmFTdGFydEFuZ2xlLHM9TWF0aC5hYnMobyk8TnVtYmVyLkVQU0lMT047Zm9yKDtvPDA7KW8rPXI7Zm9yKDtvPnI7KW8tPXI7bzxOdW1iZXIuRVBTSUxPTiYmKG89cz8wOnIpLCEwPT09dGhpcy5hQ2xvY2t3aXNlJiYhcyYmKG89PT1yP289LXI6by09cik7bGV0IGE9dGhpcy5hU3RhcnRBbmdsZSt0Km8sbD10aGlzLmFYK3RoaXMueFJhZGl1cypNYXRoLmNvcyhhKSxjPXRoaXMuYVkrdGhpcy55UmFkaXVzKk1hdGguc2luKGEpO2lmKDAhPT10aGlzLmFSb3RhdGlvbil7bGV0IHU9TWF0aC5jb3ModGhpcy5hUm90YXRpb24pLGQ9TWF0aC5zaW4odGhpcy5hUm90YXRpb24pLHA9bC10aGlzLmFYLGg9Yy10aGlzLmFZO2w9cCp1LWgqZCt0aGlzLmFYLGM9cCpkK2gqdSt0aGlzLmFZfXJldHVybiBpLnNldChsLGMpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5hWD10LmFYLHRoaXMuYVk9dC5hWSx0aGlzLnhSYWRpdXM9dC54UmFkaXVzLHRoaXMueVJhZGl1cz10LnlSYWRpdXMsdGhpcy5hU3RhcnRBbmdsZT10LmFTdGFydEFuZ2xlLHRoaXMuYUVuZEFuZ2xlPXQuYUVuZEFuZ2xlLHRoaXMuYUNsb2Nrd2lzZT10LmFDbG9ja3dpc2UsdGhpcy5hUm90YXRpb249dC5hUm90YXRpb24sdGhpc310b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gdC5hWD10aGlzLmFYLHQuYVk9dGhpcy5hWSx0LnhSYWRpdXM9dGhpcy54UmFkaXVzLHQueVJhZGl1cz10aGlzLnlSYWRpdXMsdC5hU3RhcnRBbmdsZT10aGlzLmFTdGFydEFuZ2xlLHQuYUVuZEFuZ2xlPXRoaXMuYUVuZEFuZ2xlLHQuYUNsb2Nrd2lzZT10aGlzLmFDbG9ja3dpc2UsdC5hUm90YXRpb249dGhpcy5hUm90YXRpb24sdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy5hWD10LmFYLHRoaXMuYVk9dC5hWSx0aGlzLnhSYWRpdXM9dC54UmFkaXVzLHRoaXMueVJhZGl1cz10LnlSYWRpdXMsdGhpcy5hU3RhcnRBbmdsZT10LmFTdGFydEFuZ2xlLHRoaXMuYUVuZEFuZ2xlPXQuYUVuZEFuZ2xlLHRoaXMuYUNsb2Nrd2lzZT10LmFDbG9ja3dpc2UsdGhpcy5hUm90YXRpb249dC5hUm90YXRpb24sdGhpc319O2JiLnByb3RvdHlwZS5pc0VsbGlwc2VDdXJ2ZT0hMDt2YXIgdms9Y2xhc3MgZXh0ZW5kcyBiYntjb25zdHJ1Y3Rvcih0LGUsaSxyLG8scyl7c3VwZXIodCxlLGksaSxyLG8scyksdGhpcy50eXBlPSJBcmNDdXJ2ZSJ9fTtmdW5jdGlvbiBTRygpe2xldCBuPTAsdD0wLGU9MCxpPTA7ZnVuY3Rpb24gcihvLHMsYSxsKXtuPW8sdD1hLGU9LTMqbyszKnMtMiphLWwsaT0yKm8tMipzK2ErbH1yZXR1cm57aW5pdENhdG11bGxSb206ZnVuY3Rpb24obyxzLGEsbCxjKXtyKHMsYSxjKihhLW8pLGMqKGwtcykpfSxpbml0Tm9udW5pZm9ybUNhdG11bGxSb206ZnVuY3Rpb24obyxzLGEsbCxjLHUsZCl7bGV0IHA9KHMtbykvYy0oYS1vKS8oYyt1KSsoYS1zKS91LGg9KGEtcykvdS0obC1zKS8odStkKSsobC1hKS9kO3AqPXUsaCo9dSxyKHMsYSxwLGgpfSxjYWxjOmZ1bmN0aW9uKG8pe2xldCBzPW8qbztyZXR1cm4gbit0Km8rZSpzK2kqKHMqbyl9fX12ay5wcm90b3R5cGUuaXNBcmNDdXJ2ZT0hMDt2YXIgUU89bmV3IGllLG44PW5ldyBTRyxpOD1uZXcgU0cscjg9bmV3IFNHLHlrPWNsYXNzIGV4dGVuZHMgcWF7Y29uc3RydWN0b3IodD1bXSxlPSExLGk9ImNlbnRyaXBldGFsIixyPS41KXtzdXBlcigpLHRoaXMudHlwZT0iQ2F0bXVsbFJvbUN1cnZlMyIsdGhpcy5wb2ludHM9dCx0aGlzLmNsb3NlZD1lLHRoaXMuY3VydmVUeXBlPWksdGhpcy50ZW5zaW9uPXJ9Z2V0UG9pbnQodCxlPW5ldyBpZSl7bGV0IGMsdSxpPWUscj10aGlzLnBvaW50cyxvPXIubGVuZ3RoLHM9KG8tKHRoaXMuY2xvc2VkPzA6MSkpKnQsYT1NYXRoLmZsb29yKHMpLGw9cy1hO3RoaXMuY2xvc2VkP2ErPWE+MD8wOihNYXRoLmZsb29yKE1hdGguYWJzKGEpL28pKzEpKm86MD09PWwmJmE9PT1vLTEmJihhPW8tMixsPTEpLHRoaXMuY2xvc2VkfHxhPjA/Yz1yWyhhLTEpJW9dOihRTy5zdWJWZWN0b3JzKHJbMF0sclsxXSkuYWRkKHJbMF0pLGM9UU8pO2xldCBkPXJbYSVvXSxwPXJbKGErMSklb107aWYodGhpcy5jbG9zZWR8fGErMjxvP3U9clsoYSsyKSVvXTooUU8uc3ViVmVjdG9ycyhyW28tMV0scltvLTJdKS5hZGQocltvLTFdKSx1PVFPKSwiY2VudHJpcGV0YWwiPT09dGhpcy5jdXJ2ZVR5cGV8fCJjaG9yZGFsIj09PXRoaXMuY3VydmVUeXBlKXtsZXQgaD0iY2hvcmRhbCI9PT10aGlzLmN1cnZlVHlwZT8uNTouMjUsZj1NYXRoLnBvdyhjLmRpc3RhbmNlVG9TcXVhcmVkKGQpLGgpLG09TWF0aC5wb3coZC5kaXN0YW5jZVRvU3F1YXJlZChwKSxoKSx4PU1hdGgucG93KHAuZGlzdGFuY2VUb1NxdWFyZWQodSksaCk7bTwxZS00JiYobT0xKSxmPDFlLTQmJihmPW0pLHg8MWUtNCYmKHg9bSksbjguaW5pdE5vbnVuaWZvcm1DYXRtdWxsUm9tKGMueCxkLngscC54LHUueCxmLG0seCksaTguaW5pdE5vbnVuaWZvcm1DYXRtdWxsUm9tKGMueSxkLnkscC55LHUueSxmLG0seCkscjguaW5pdE5vbnVuaWZvcm1DYXRtdWxsUm9tKGMueixkLnoscC56LHUueixmLG0seCl9ZWxzZSJjYXRtdWxscm9tIj09PXRoaXMuY3VydmVUeXBlJiYobjguaW5pdENhdG11bGxSb20oYy54LGQueCxwLngsdS54LHRoaXMudGVuc2lvbiksaTguaW5pdENhdG11bGxSb20oYy55LGQueSxwLnksdS55LHRoaXMudGVuc2lvbikscjguaW5pdENhdG11bGxSb20oYy56LGQueixwLnosdS56LHRoaXMudGVuc2lvbikpO3JldHVybiBpLnNldChuOC5jYWxjKGwpLGk4LmNhbGMobCkscjguY2FsYyhsKSksaX1jb3B5KHQpe3N1cGVyLmNvcHkodCksdGhpcy5wb2ludHM9W107Zm9yKGxldCBlPTAsaT10LnBvaW50cy5sZW5ndGg7ZTxpO2UrKyl0aGlzLnBvaW50cy5wdXNoKHQucG9pbnRzW2VdLmNsb25lKCkpO3JldHVybiB0aGlzLmNsb3NlZD10LmNsb3NlZCx0aGlzLmN1cnZlVHlwZT10LmN1cnZlVHlwZSx0aGlzLnRlbnNpb249dC50ZW5zaW9uLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7dC5wb2ludHM9W107Zm9yKGxldCBlPTAsaT10aGlzLnBvaW50cy5sZW5ndGg7ZTxpO2UrKyl0LnBvaW50cy5wdXNoKHRoaXMucG9pbnRzW2VdLnRvQXJyYXkoKSk7cmV0dXJuIHQuY2xvc2VkPXRoaXMuY2xvc2VkLHQuY3VydmVUeXBlPXRoaXMuY3VydmVUeXBlLHQudGVuc2lvbj10aGlzLnRlbnNpb24sdH1mcm9tSlNPTih0KXtzdXBlci5mcm9tSlNPTih0KSx0aGlzLnBvaW50cz1bXTtmb3IobGV0IGU9MCxpPXQucG9pbnRzLmxlbmd0aDtlPGk7ZSsrKXtsZXQgcj10LnBvaW50c1tlXTt0aGlzLnBvaW50cy5wdXNoKChuZXcgaWUpLmZyb21BcnJheShyKSl9cmV0dXJuIHRoaXMuY2xvc2VkPXQuY2xvc2VkLHRoaXMuY3VydmVUeXBlPXQuY3VydmVUeXBlLHRoaXMudGVuc2lvbj10LnRlbnNpb24sdGhpc319O2Z1bmN0aW9uIF9kZShuLHQsZSxpLHIpe2xldCBvPS41KihpLXQpLHM9LjUqKHItZSksYT1uKm47cmV0dXJuKDIqZS0yKmkrbytzKSoobiphKSsoLTMqZSszKmktMipvLXMpKmErbypuK2V9ZnVuY3Rpb24gelMobix0LGUsaSl7cmV0dXJuIGZ1bmN0aW9uKG4sdCl7bGV0IGU9MS1uO3JldHVybiBlKmUqdH0obix0KStmdW5jdGlvbihuLHQpe3JldHVybiAyKigxLW4pKm4qdH0obixlKStmdW5jdGlvbihuLHQpe3JldHVybiBuKm4qdH0obixpKX1mdW5jdGlvbiBqUyhuLHQsZSxpLHIpe3JldHVybiBmdW5jdGlvbihuLHQpe2xldCBlPTEtbjtyZXR1cm4gZSplKmUqdH0obix0KStmdW5jdGlvbihuLHQpe2xldCBlPTEtbjtyZXR1cm4gMyplKmUqbip0fShuLGUpK2Z1bmN0aW9uKG4sdCl7cmV0dXJuIDMqKDEtbikqbipuKnR9KG4saSkrZnVuY3Rpb24obix0KXtyZXR1cm4gbipuKm4qdH0obixyKX15ay5wcm90b3R5cGUuaXNDYXRtdWxsUm9tQ3VydmUzPSEwO3ZhciBvRT1jbGFzcyBleHRlbmRzIHFhe2NvbnN0cnVjdG9yKHQ9bmV3IGF0LGU9bmV3IGF0LGk9bmV3IGF0LHI9bmV3IGF0KXtzdXBlcigpLHRoaXMudHlwZT0iQ3ViaWNCZXppZXJDdXJ2ZSIsdGhpcy52MD10LHRoaXMudjE9ZSx0aGlzLnYyPWksdGhpcy52Mz1yfWdldFBvaW50KHQsZT1uZXcgYXQpe2xldCBpPWUscj10aGlzLnYwLG89dGhpcy52MSxzPXRoaXMudjIsYT10aGlzLnYzO3JldHVybiBpLnNldChqUyh0LHIueCxvLngscy54LGEueCksalModCxyLnksby55LHMueSxhLnkpKSxpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy52MC5jb3B5KHQudjApLHRoaXMudjEuY29weSh0LnYxKSx0aGlzLnYyLmNvcHkodC52MiksdGhpcy52My5jb3B5KHQudjMpLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIHQudjA9dGhpcy52MC50b0FycmF5KCksdC52MT10aGlzLnYxLnRvQXJyYXkoKSx0LnYyPXRoaXMudjIudG9BcnJheSgpLHQudjM9dGhpcy52My50b0FycmF5KCksdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy52MC5mcm9tQXJyYXkodC52MCksdGhpcy52MS5mcm9tQXJyYXkodC52MSksdGhpcy52Mi5mcm9tQXJyYXkodC52MiksdGhpcy52My5mcm9tQXJyYXkodC52MyksdGhpc319O29FLnByb3RvdHlwZS5pc0N1YmljQmV6aWVyQ3VydmU9ITA7dmFyIGJrPWNsYXNzIGV4dGVuZHMgcWF7Y29uc3RydWN0b3IodD1uZXcgaWUsZT1uZXcgaWUsaT1uZXcgaWUscj1uZXcgaWUpe3N1cGVyKCksdGhpcy50eXBlPSJDdWJpY0JlemllckN1cnZlMyIsdGhpcy52MD10LHRoaXMudjE9ZSx0aGlzLnYyPWksdGhpcy52Mz1yfWdldFBvaW50KHQsZT1uZXcgaWUpe2xldCBpPWUscj10aGlzLnYwLG89dGhpcy52MSxzPXRoaXMudjIsYT10aGlzLnYzO3JldHVybiBpLnNldChqUyh0LHIueCxvLngscy54LGEueCksalModCxyLnksby55LHMueSxhLnkpLGpTKHQsci56LG8ueixzLnosYS56KSksaX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMudjAuY29weSh0LnYwKSx0aGlzLnYxLmNvcHkodC52MSksdGhpcy52Mi5jb3B5KHQudjIpLHRoaXMudjMuY29weSh0LnYzKSx0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3JldHVybiB0LnYwPXRoaXMudjAudG9BcnJheSgpLHQudjE9dGhpcy52MS50b0FycmF5KCksdC52Mj10aGlzLnYyLnRvQXJyYXkoKSx0LnYzPXRoaXMudjMudG9BcnJheSgpLHR9ZnJvbUpTT04odCl7cmV0dXJuIHN1cGVyLmZyb21KU09OKHQpLHRoaXMudjAuZnJvbUFycmF5KHQudjApLHRoaXMudjEuZnJvbUFycmF5KHQudjEpLHRoaXMudjIuZnJvbUFycmF5KHQudjIpLHRoaXMudjMuZnJvbUFycmF5KHQudjMpLHRoaXN9fTtiay5wcm90b3R5cGUuaXNDdWJpY0JlemllckN1cnZlMz0hMDt2YXIgeGI9Y2xhc3MgZXh0ZW5kcyBxYXtjb25zdHJ1Y3Rvcih0PW5ldyBhdCxlPW5ldyBhdCl7c3VwZXIoKSx0aGlzLnR5cGU9IkxpbmVDdXJ2ZSIsdGhpcy52MT10LHRoaXMudjI9ZX1nZXRQb2ludCh0LGU9bmV3IGF0KXtsZXQgaT1lO3JldHVybiAxPT09dD9pLmNvcHkodGhpcy52Mik6KGkuY29weSh0aGlzLnYyKS5zdWIodGhpcy52MSksaS5tdWx0aXBseVNjYWxhcih0KS5hZGQodGhpcy52MSkpLGl9Z2V0UG9pbnRBdCh0LGUpe3JldHVybiB0aGlzLmdldFBvaW50KHQsZSl9Z2V0VGFuZ2VudCh0LGUpe2xldCBpPWV8fG5ldyBhdDtyZXR1cm4gaS5jb3B5KHRoaXMudjIpLnN1Yih0aGlzLnYxKS5ub3JtYWxpemUoKSxpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy52MS5jb3B5KHQudjEpLHRoaXMudjIuY29weSh0LnYyKSx0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3JldHVybiB0LnYxPXRoaXMudjEudG9BcnJheSgpLHQudjI9dGhpcy52Mi50b0FycmF5KCksdH1mcm9tSlNPTih0KXtyZXR1cm4gc3VwZXIuZnJvbUpTT04odCksdGhpcy52MS5mcm9tQXJyYXkodC52MSksdGhpcy52Mi5mcm9tQXJyYXkodC52MiksdGhpc319O3hiLnByb3RvdHlwZS5pc0xpbmVDdXJ2ZT0hMDt2YXIgc0U9Y2xhc3MgZXh0ZW5kcyBxYXtjb25zdHJ1Y3Rvcih0PW5ldyBhdCxlPW5ldyBhdCxpPW5ldyBhdCl7c3VwZXIoKSx0aGlzLnR5cGU9IlF1YWRyYXRpY0JlemllckN1cnZlIix0aGlzLnYwPXQsdGhpcy52MT1lLHRoaXMudjI9aX1nZXRQb2ludCh0LGU9bmV3IGF0KXtsZXQgaT1lLHI9dGhpcy52MCxvPXRoaXMudjEscz10aGlzLnYyO3JldHVybiBpLnNldCh6Uyh0LHIueCxvLngscy54KSx6Uyh0LHIueSxvLnkscy55KSksaX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMudjAuY29weSh0LnYwKSx0aGlzLnYxLmNvcHkodC52MSksdGhpcy52Mi5jb3B5KHQudjIpLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIHQudjA9dGhpcy52MC50b0FycmF5KCksdC52MT10aGlzLnYxLnRvQXJyYXkoKSx0LnYyPXRoaXMudjIudG9BcnJheSgpLHR9ZnJvbUpTT04odCl7cmV0dXJuIHN1cGVyLmZyb21KU09OKHQpLHRoaXMudjAuZnJvbUFycmF5KHQudjApLHRoaXMudjEuZnJvbUFycmF5KHQudjEpLHRoaXMudjIuZnJvbUFycmF5KHQudjIpLHRoaXN9fTtzRS5wcm90b3R5cGUuaXNRdWFkcmF0aWNCZXppZXJDdXJ2ZT0hMDt2YXIgeGs9Y2xhc3MgZXh0ZW5kcyBxYXtjb25zdHJ1Y3Rvcih0PW5ldyBpZSxlPW5ldyBpZSxpPW5ldyBpZSl7c3VwZXIoKSx0aGlzLnR5cGU9IlF1YWRyYXRpY0JlemllckN1cnZlMyIsdGhpcy52MD10LHRoaXMudjE9ZSx0aGlzLnYyPWl9Z2V0UG9pbnQodCxlPW5ldyBpZSl7bGV0IGk9ZSxyPXRoaXMudjAsbz10aGlzLnYxLHM9dGhpcy52MjtyZXR1cm4gaS5zZXQoelModCxyLngsby54LHMueCkselModCxyLnksby55LHMueSkselModCxyLnosby56LHMueikpLGl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLnYwLmNvcHkodC52MCksdGhpcy52MS5jb3B5KHQudjEpLHRoaXMudjIuY29weSh0LnYyKSx0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3JldHVybiB0LnYwPXRoaXMudjAudG9BcnJheSgpLHQudjE9dGhpcy52MS50b0FycmF5KCksdC52Mj10aGlzLnYyLnRvQXJyYXkoKSx0fWZyb21KU09OKHQpe3JldHVybiBzdXBlci5mcm9tSlNPTih0KSx0aGlzLnYwLmZyb21BcnJheSh0LnYwKSx0aGlzLnYxLmZyb21BcnJheSh0LnYxKSx0aGlzLnYyLmZyb21BcnJheSh0LnYyKSx0aGlzfX07eGsucHJvdG90eXBlLmlzUXVhZHJhdGljQmV6aWVyQ3VydmUzPSEwO3ZhciBhRT1jbGFzcyBleHRlbmRzIHFhe2NvbnN0cnVjdG9yKHQ9W10pe3N1cGVyKCksdGhpcy50eXBlPSJTcGxpbmVDdXJ2ZSIsdGhpcy5wb2ludHM9dH1nZXRQb2ludCh0LGU9bmV3IGF0KXtsZXQgaT1lLHI9dGhpcy5wb2ludHMsbz0oci5sZW5ndGgtMSkqdCxzPU1hdGguZmxvb3IobyksYT1vLXMsbD1yWzA9PT1zP3M6cy0xXSxjPXJbc10sdT1yW3M+ci5sZW5ndGgtMj9yLmxlbmd0aC0xOnMrMV0sZD1yW3M+ci5sZW5ndGgtMz9yLmxlbmd0aC0xOnMrMl07cmV0dXJuIGkuc2V0KF9kZShhLGwueCxjLngsdS54LGQueCksX2RlKGEsbC55LGMueSx1LnksZC55KSksaX1jb3B5KHQpe3N1cGVyLmNvcHkodCksdGhpcy5wb2ludHM9W107Zm9yKGxldCBlPTAsaT10LnBvaW50cy5sZW5ndGg7ZTxpO2UrKyl0aGlzLnBvaW50cy5wdXNoKHQucG9pbnRzW2VdLmNsb25lKCkpO3JldHVybiB0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3QucG9pbnRzPVtdO2ZvcihsZXQgZT0wLGk9dGhpcy5wb2ludHMubGVuZ3RoO2U8aTtlKyspdC5wb2ludHMucHVzaCh0aGlzLnBvaW50c1tlXS50b0FycmF5KCkpO3JldHVybiB0fWZyb21KU09OKHQpe3N1cGVyLmZyb21KU09OKHQpLHRoaXMucG9pbnRzPVtdO2ZvcihsZXQgZT0wLGk9dC5wb2ludHMubGVuZ3RoO2U8aTtlKyspe2xldCByPXQucG9pbnRzW2VdO3RoaXMucG9pbnRzLnB1c2goKG5ldyBhdCkuZnJvbUFycmF5KHIpKX1yZXR1cm4gdGhpc319O2FFLnByb3RvdHlwZS5pc1NwbGluZUN1cnZlPSEwO3ZhciBxZGU9T2JqZWN0LmZyZWV6ZSh7X19wcm90b19fOm51bGwsQXJjQ3VydmU6dmssQ2F0bXVsbFJvbUN1cnZlMzp5ayxDdWJpY0JlemllckN1cnZlOm9FLEN1YmljQmV6aWVyQ3VydmUzOmJrLEVsbGlwc2VDdXJ2ZTpiYixMaW5lQ3VydmU6eGIsTGluZUN1cnZlMzpjbGFzcyBleHRlbmRzIHFhe2NvbnN0cnVjdG9yKHQ9bmV3IGllLGU9bmV3IGllKXtzdXBlcigpLHRoaXMudHlwZT0iTGluZUN1cnZlMyIsdGhpcy5pc0xpbmVDdXJ2ZTM9ITAsdGhpcy52MT10LHRoaXMudjI9ZX1nZXRQb2ludCh0LGU9bmV3IGllKXtsZXQgaT1lO3JldHVybiAxPT09dD9pLmNvcHkodGhpcy52Mik6KGkuY29weSh0aGlzLnYyKS5zdWIodGhpcy52MSksaS5tdWx0aXBseVNjYWxhcih0KS5hZGQodGhpcy52MSkpLGl9Z2V0UG9pbnRBdCh0LGUpe3JldHVybiB0aGlzLmdldFBvaW50KHQsZSl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLnYxLmNvcHkodC52MSksdGhpcy52Mi5jb3B5KHQudjIpLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIHQudjE9dGhpcy52MS50b0FycmF5KCksdC52Mj10aGlzLnYyLnRvQXJyYXkoKSx0fWZyb21KU09OKHQpe3JldHVybiBzdXBlci5mcm9tSlNPTih0KSx0aGlzLnYxLmZyb21BcnJheSh0LnYxKSx0aGlzLnYyLmZyb21BcnJheSh0LnYyKSx0aGlzfX0sUXVhZHJhdGljQmV6aWVyQ3VydmU6c0UsUXVhZHJhdGljQmV6aWVyQ3VydmUzOnhrLFNwbGluZUN1cnZlOmFFfSksTzg9Y2xhc3MgZXh0ZW5kcyBxYXtjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy50eXBlPSJDdXJ2ZVBhdGgiLHRoaXMuY3VydmVzPVtdLHRoaXMuYXV0b0Nsb3NlPSExfWFkZCh0KXt0aGlzLmN1cnZlcy5wdXNoKHQpfWNsb3NlUGF0aCgpe2xldCB0PXRoaXMuY3VydmVzWzBdLmdldFBvaW50KDApLGU9dGhpcy5jdXJ2ZXNbdGhpcy5jdXJ2ZXMubGVuZ3RoLTFdLmdldFBvaW50KDEpO3QuZXF1YWxzKGUpfHx0aGlzLmN1cnZlcy5wdXNoKG5ldyB4YihlLHQpKX1nZXRQb2ludCh0LGUpe2xldCBpPXQqdGhpcy5nZXRMZW5ndGgoKSxyPXRoaXMuZ2V0Q3VydmVMZW5ndGhzKCksbz0wO2Zvcig7bzxyLmxlbmd0aDspe2lmKHJbb10+PWkpe2xldCBzPXJbb10taSxhPXRoaXMuY3VydmVzW29dLGw9YS5nZXRMZW5ndGgoKTtyZXR1cm4gYS5nZXRQb2ludEF0KDA9PT1sPzA6MS1zL2wsZSl9bysrfXJldHVybiBudWxsfWdldExlbmd0aCgpe2xldCB0PXRoaXMuZ2V0Q3VydmVMZW5ndGhzKCk7cmV0dXJuIHRbdC5sZW5ndGgtMV19dXBkYXRlQXJjTGVuZ3Rocygpe3RoaXMubmVlZHNVcGRhdGU9ITAsdGhpcy5jYWNoZUxlbmd0aHM9bnVsbCx0aGlzLmdldEN1cnZlTGVuZ3RocygpfWdldEN1cnZlTGVuZ3Rocygpe2lmKHRoaXMuY2FjaGVMZW5ndGhzJiZ0aGlzLmNhY2hlTGVuZ3Rocy5sZW5ndGg9PT10aGlzLmN1cnZlcy5sZW5ndGgpcmV0dXJuIHRoaXMuY2FjaGVMZW5ndGhzO2xldCB0PVtdLGU9MDtmb3IobGV0IGk9MCxyPXRoaXMuY3VydmVzLmxlbmd0aDtpPHI7aSsrKWUrPXRoaXMuY3VydmVzW2ldLmdldExlbmd0aCgpLHQucHVzaChlKTtyZXR1cm4gdGhpcy5jYWNoZUxlbmd0aHM9dCx0fWdldFNwYWNlZFBvaW50cyh0PTQwKXtsZXQgZT1bXTtmb3IobGV0IGk9MDtpPD10O2krKyllLnB1c2godGhpcy5nZXRQb2ludChpL3QpKTtyZXR1cm4gdGhpcy5hdXRvQ2xvc2UmJmUucHVzaChlWzBdKSxlfWdldFBvaW50cyh0PTEyKXtsZXQgaSxlPVtdO2ZvcihsZXQgcj0wLG89dGhpcy5jdXJ2ZXM7cjxvLmxlbmd0aDtyKyspe2xldCBzPW9bcl0sbD1zLmdldFBvaW50cyhzJiZzLmlzRWxsaXBzZUN1cnZlPzIqdDpzJiYocy5pc0xpbmVDdXJ2ZXx8cy5pc0xpbmVDdXJ2ZTMpPzE6cyYmcy5pc1NwbGluZUN1cnZlP3Qqcy5wb2ludHMubGVuZ3RoOnQpO2ZvcihsZXQgYz0wO2M8bC5sZW5ndGg7YysrKXtsZXQgdT1sW2NdO2kmJmkuZXF1YWxzKHUpfHwoZS5wdXNoKHUpLGk9dSl9fXJldHVybiB0aGlzLmF1dG9DbG9zZSYmZS5sZW5ndGg+MSYmIWVbZS5sZW5ndGgtMV0uZXF1YWxzKGVbMF0pJiZlLnB1c2goZVswXSksZX1jb3B5KHQpe3N1cGVyLmNvcHkodCksdGhpcy5jdXJ2ZXM9W107Zm9yKGxldCBlPTAsaT10LmN1cnZlcy5sZW5ndGg7ZTxpO2UrKyl0aGlzLmN1cnZlcy5wdXNoKHQuY3VydmVzW2VdLmNsb25lKCkpO3JldHVybiB0aGlzLmF1dG9DbG9zZT10LmF1dG9DbG9zZSx0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3QuYXV0b0Nsb3NlPXRoaXMuYXV0b0Nsb3NlLHQuY3VydmVzPVtdO2ZvcihsZXQgZT0wLGk9dGhpcy5jdXJ2ZXMubGVuZ3RoO2U8aTtlKyspdC5jdXJ2ZXMucHVzaCh0aGlzLmN1cnZlc1tlXS50b0pTT04oKSk7cmV0dXJuIHR9ZnJvbUpTT04odCl7c3VwZXIuZnJvbUpTT04odCksdGhpcy5hdXRvQ2xvc2U9dC5hdXRvQ2xvc2UsdGhpcy5jdXJ2ZXM9W107Zm9yKGxldCBlPTAsaT10LmN1cnZlcy5sZW5ndGg7ZTxpO2UrKyl7bGV0IHI9dC5jdXJ2ZXNbZV07dGhpcy5jdXJ2ZXMucHVzaCgobmV3IHFkZVtyLnR5cGVdKS5mcm9tSlNPTihyKSl9cmV0dXJuIHRoaXN9fSxsRT1jbGFzcyBleHRlbmRzIE84e2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJQYXRoIix0aGlzLmN1cnJlbnRQb2ludD1uZXcgYXQsdCYmdGhpcy5zZXRGcm9tUG9pbnRzKHQpfXNldEZyb21Qb2ludHModCl7dGhpcy5tb3ZlVG8odFswXS54LHRbMF0ueSk7Zm9yKGxldCBlPTEsaT10Lmxlbmd0aDtlPGk7ZSsrKXRoaXMubGluZVRvKHRbZV0ueCx0W2VdLnkpO3JldHVybiB0aGlzfW1vdmVUbyh0LGUpe3JldHVybiB0aGlzLmN1cnJlbnRQb2ludC5zZXQodCxlKSx0aGlzfWxpbmVUbyh0LGUpe2xldCBpPW5ldyB4Yih0aGlzLmN1cnJlbnRQb2ludC5jbG9uZSgpLG5ldyBhdCh0LGUpKTtyZXR1cm4gdGhpcy5jdXJ2ZXMucHVzaChpKSx0aGlzLmN1cnJlbnRQb2ludC5zZXQodCxlKSx0aGlzfXF1YWRyYXRpY0N1cnZlVG8odCxlLGkscil7bGV0IG89bmV3IHNFKHRoaXMuY3VycmVudFBvaW50LmNsb25lKCksbmV3IGF0KHQsZSksbmV3IGF0KGkscikpO3JldHVybiB0aGlzLmN1cnZlcy5wdXNoKG8pLHRoaXMuY3VycmVudFBvaW50LnNldChpLHIpLHRoaXN9YmV6aWVyQ3VydmVUbyh0LGUsaSxyLG8scyl7bGV0IGE9bmV3IG9FKHRoaXMuY3VycmVudFBvaW50LmNsb25lKCksbmV3IGF0KHQsZSksbmV3IGF0KGksciksbmV3IGF0KG8scykpO3JldHVybiB0aGlzLmN1cnZlcy5wdXNoKGEpLHRoaXMuY3VycmVudFBvaW50LnNldChvLHMpLHRoaXN9c3BsaW5lVGhydSh0KXtsZXQgZT1bdGhpcy5jdXJyZW50UG9pbnQuY2xvbmUoKV0uY29uY2F0KHQpLGk9bmV3IGFFKGUpO3JldHVybiB0aGlzLmN1cnZlcy5wdXNoKGkpLHRoaXMuY3VycmVudFBvaW50LmNvcHkodFt0Lmxlbmd0aC0xXSksdGhpc31hcmModCxlLGkscixvLHMpe3JldHVybiB0aGlzLmFic2FyYyh0K3RoaXMuY3VycmVudFBvaW50LngsZSt0aGlzLmN1cnJlbnRQb2ludC55LGkscixvLHMpLHRoaXN9YWJzYXJjKHQsZSxpLHIsbyxzKXtyZXR1cm4gdGhpcy5hYnNlbGxpcHNlKHQsZSxpLGkscixvLHMpLHRoaXN9ZWxsaXBzZSh0LGUsaSxyLG8scyxhLGwpe3JldHVybiB0aGlzLmFic2VsbGlwc2UodCt0aGlzLmN1cnJlbnRQb2ludC54LGUrdGhpcy5jdXJyZW50UG9pbnQueSxpLHIsbyxzLGEsbCksdGhpc31hYnNlbGxpcHNlKHQsZSxpLHIsbyxzLGEsbCl7bGV0IGM9bmV3IGJiKHQsZSxpLHIsbyxzLGEsbCk7aWYodGhpcy5jdXJ2ZXMubGVuZ3RoPjApe2xldCBkPWMuZ2V0UG9pbnQoMCk7ZC5lcXVhbHModGhpcy5jdXJyZW50UG9pbnQpfHx0aGlzLmxpbmVUbyhkLngsZC55KX10aGlzLmN1cnZlcy5wdXNoKGMpO2xldCB1PWMuZ2V0UG9pbnQoMSk7cmV0dXJuIHRoaXMuY3VycmVudFBvaW50LmNvcHkodSksdGhpc31jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuY3VycmVudFBvaW50LmNvcHkodC5jdXJyZW50UG9pbnQpLHRoaXN9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIHQuY3VycmVudFBvaW50PXRoaXMuY3VycmVudFBvaW50LnRvQXJyYXkoKSx0fWZyb21KU09OKHQpe3JldHVybiBzdXBlci5mcm9tSlNPTih0KSx0aGlzLmN1cnJlbnRQb2ludC5mcm9tQXJyYXkodC5jdXJyZW50UG9pbnQpLHRoaXN9fSxJcD1jbGFzcyBleHRlbmRzIGxFe2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpLHRoaXMudXVpZD1kdSgpLHRoaXMudHlwZT0iU2hhcGUiLHRoaXMuaG9sZXM9W119Z2V0UG9pbnRzSG9sZXModCl7bGV0IGU9W107Zm9yKGxldCBpPTAscj10aGlzLmhvbGVzLmxlbmd0aDtpPHI7aSsrKWVbaV09dGhpcy5ob2xlc1tpXS5nZXRQb2ludHModCk7cmV0dXJuIGV9ZXh0cmFjdFBvaW50cyh0KXtyZXR1cm57c2hhcGU6dGhpcy5nZXRQb2ludHModCksaG9sZXM6dGhpcy5nZXRQb2ludHNIb2xlcyh0KX19Y29weSh0KXtzdXBlci5jb3B5KHQpLHRoaXMuaG9sZXM9W107Zm9yKGxldCBlPTAsaT10LmhvbGVzLmxlbmd0aDtlPGk7ZSsrKXRoaXMuaG9sZXMucHVzaCh0LmhvbGVzW2VdLmNsb25lKCkpO3JldHVybiB0aGlzfXRvSlNPTigpe2xldCB0PXN1cGVyLnRvSlNPTigpO3QudXVpZD10aGlzLnV1aWQsdC5ob2xlcz1bXTtmb3IobGV0IGU9MCxpPXRoaXMuaG9sZXMubGVuZ3RoO2U8aTtlKyspdC5ob2xlcy5wdXNoKHRoaXMuaG9sZXNbZV0udG9KU09OKCkpO3JldHVybiB0fWZyb21KU09OKHQpe3N1cGVyLmZyb21KU09OKHQpLHRoaXMudXVpZD10LnV1aWQsdGhpcy5ob2xlcz1bXTtmb3IobGV0IGU9MCxpPXQuaG9sZXMubGVuZ3RoO2U8aTtlKyspe2xldCByPXQuaG9sZXNbZV07dGhpcy5ob2xlcy5wdXNoKChuZXcgbEUpLmZyb21KU09OKHIpKX1yZXR1cm4gdGhpc319O2Z1bmN0aW9uIFlkZShuLHQsZSxpLHIpe2xldCBvLHM7aWYocj09PWZ1bmN0aW9uKG4sdCxlLGkpe2xldCByPTA7Zm9yKGxldCBvPXQscz1lLWk7bzxlO28rPWkpcis9KG5bc10tbltvXSkqKG5bbysxXStuW3MrMV0pLHM9bztyZXR1cm4gcn0obix0LGUsaSk+MClmb3Iobz10O288ZTtvKz1pKXM9dmRlKG8sbltvXSxuW28rMV0scyk7ZWxzZSBmb3Iobz1lLWk7bz49dDtvLT1pKXM9dmRlKG8sbltvXSxuW28rMV0scyk7cmV0dXJuIHMmJkZrKHMscy5uZXh0KSYmKGRFKHMpLHM9cy5uZXh0KSxzfWZ1bmN0aW9uIHdmKG4sdCl7aWYoIW4pcmV0dXJuIG47dHx8KHQ9bik7bGV0IGksZT1uO2Rve2lmKGk9ITEsZS5zdGVpbmVyfHwhRmsoZSxlLm5leHQpJiYwIT09WnIoZS5wcmV2LGUsZS5uZXh0KSllPWUubmV4dDtlbHNle2lmKGRFKGUpLGU9dD1lLnByZXYsZT09PWUubmV4dClicmVhaztpPSEwfX13aGlsZShpfHxlIT09dCk7cmV0dXJuIHR9ZnVuY3Rpb24gY0Uobix0LGUsaSxyLG8scyl7aWYoIW4pcmV0dXJuOyFzJiZvJiZmdW5jdGlvbihuLHQsZSxpKXtsZXQgcj1uO2Rve251bGw9PT1yLnomJihyLno9azgoci54LHIueSx0LGUsaSkpLHIucHJldlo9ci5wcmV2LHIubmV4dFo9ci5uZXh0LHI9ci5uZXh0fXdoaWxlKHIhPT1uKTtyLnByZXZaLm5leHRaPW51bGwsci5wcmV2Wj1udWxsLGZ1bmN0aW9uKG4pe2xldCB0LGUsaSxyLG8scyxhLGwsYz0xO2Rve2ZvcihlPW4sbj1udWxsLG89bnVsbCxzPTA7ZTspe2ZvcihzKyssaT1lLGE9MCx0PTA7dDxjJiYoYSsrLGk9aS5uZXh0WixpKTt0KyspO2ZvcihsPWM7YT4wfHxsPjAmJmk7KTAhPT1hJiYoMD09PWx8fCFpfHxlLno8PWkueik/KHI9ZSxlPWUubmV4dFosYS0tKToocj1pLGk9aS5uZXh0WixsLS0pLG8/by5uZXh0Wj1yOm49cixyLnByZXZaPW8sbz1yO2U9aX1vLm5leHRaPW51bGwsYyo9Mn13aGlsZShzPjEpfShyKX0obixpLHIsbyk7bGV0IGwsYyxhPW47Zm9yKDtuLnByZXYhPT1uLm5leHQ7KWlmKGw9bi5wcmV2LGM9bi5uZXh0LG8/RDllKG4saSxyLG8pOlQ5ZShuKSl0LnB1c2gobC5pL2UpLHQucHVzaChuLmkvZSksdC5wdXNoKGMuaS9lKSxkRShuKSxuPWMubmV4dCxhPWMubmV4dDtlbHNlIGlmKChuPWMpPT09YSl7cz8xPT09cz9jRShuPUE5ZSh3ZihuKSx0LGUpLHQsZSxpLHIsbywyKToyPT09cyYmSTllKG4sdCxlLGkscixvKTpjRSh3ZihuKSx0LGUsaSxyLG8sMSk7YnJlYWt9fWZ1bmN0aW9uIFQ5ZShuKXtsZXQgdD1uLnByZXYsZT1uLGk9bi5uZXh0O2lmKFpyKHQsZSxpKT49MClyZXR1cm4hMTtsZXQgcj1uLm5leHQubmV4dDtmb3IoO3IhPT1uLnByZXY7KXtpZihhYih0LngsdC55LGUueCxlLnksaS54LGkueSxyLngsci55KSYmWnIoci5wcmV2LHIsci5uZXh0KT49MClyZXR1cm4hMTtyPXIubmV4dH1yZXR1cm4hMH1mdW5jdGlvbiBEOWUobix0LGUsaSl7bGV0IHI9bi5wcmV2LG89bixzPW4ubmV4dDtpZihacihyLG8scyk+PTApcmV0dXJuITE7bGV0IGM9ci54Pm8ueD9yLng+cy54P3IueDpzLng6by54PnMueD9vLng6cy54LHU9ci55Pm8ueT9yLnk+cy55P3IueTpzLnk6by55PnMueT9vLnk6cy55LGQ9azgoci54PG8ueD9yLng8cy54P3IueDpzLng6by54PHMueD9vLng6cy54LHIueTxvLnk/ci55PHMueT9yLnk6cy55Om8ueTxzLnk/by55OnMueSx0LGUsaSkscD1rOChjLHUsdCxlLGkpLGg9bi5wcmV2WixmPW4ubmV4dFo7Zm9yKDtoJiZoLno+PWQmJmYmJmYuejw9cDspe2lmKGghPT1uLnByZXYmJmghPT1uLm5leHQmJmFiKHIueCxyLnksby54LG8ueSxzLngscy55LGgueCxoLnkpJiZacihoLnByZXYsaCxoLm5leHQpPj0wfHwoaD1oLnByZXZaLGYhPT1uLnByZXYmJmYhPT1uLm5leHQmJmFiKHIueCxyLnksby54LG8ueSxzLngscy55LGYueCxmLnkpJiZacihmLnByZXYsZixmLm5leHQpPj0wKSlyZXR1cm4hMTtmPWYubmV4dFp9Zm9yKDtoJiZoLno+PWQ7KXtpZihoIT09bi5wcmV2JiZoIT09bi5uZXh0JiZhYihyLngsci55LG8ueCxvLnkscy54LHMueSxoLngsaC55KSYmWnIoaC5wcmV2LGgsaC5uZXh0KT49MClyZXR1cm4hMTtoPWgucHJldlp9Zm9yKDtmJiZmLno8PXA7KXtpZihmIT09bi5wcmV2JiZmIT09bi5uZXh0JiZhYihyLngsci55LG8ueCxvLnkscy54LHMueSxmLngsZi55KSYmWnIoZi5wcmV2LGYsZi5uZXh0KT49MClyZXR1cm4hMTtmPWYubmV4dFp9cmV0dXJuITB9ZnVuY3Rpb24gQTllKG4sdCxlKXtsZXQgaT1uO2Rve2xldCByPWkucHJldixvPWkubmV4dC5uZXh0OyFGayhyLG8pJiZYZGUocixpLGkubmV4dCxvKSYmdUUocixvKSYmdUUobyxyKSYmKHQucHVzaChyLmkvZSksdC5wdXNoKGkuaS9lKSx0LnB1c2goby5pL2UpLGRFKGkpLGRFKGkubmV4dCksaT1uPW8pLGk9aS5uZXh0fXdoaWxlKGkhPT1uKTtyZXR1cm4gd2YoaSl9ZnVuY3Rpb24gSTllKG4sdCxlLGkscixvKXtsZXQgcz1uO2Rve2xldCBhPXMubmV4dC5uZXh0O2Zvcig7YSE9PXMucHJldjspe2lmKHMuaSE9PWEuaSYmVjllKHMsYSkpe2xldCBsPVFkZShzLGEpO3JldHVybiBzPXdmKHMscy5uZXh0KSxsPXdmKGwsbC5uZXh0KSxjRShzLHQsZSxpLHIsbyksdm9pZCBjRShsLHQsZSxpLHIsbyl9YT1hLm5leHR9cz1zLm5leHR9d2hpbGUocyE9PW4pfWZ1bmN0aW9uIFI5ZShuLHQpe3JldHVybiBuLngtdC54fWZ1bmN0aW9uIE85ZShuLHQpe2lmKHQ9ZnVuY3Rpb24obix0KXtsZXQgcyxlPXQsaT1uLngscj1uLnksbz0tMS8wO2Rve2lmKHI8PWUueSYmcj49ZS5uZXh0LnkmJmUubmV4dC55IT09ZS55KXtsZXQgcD1lLngrKHItZS55KSooZS5uZXh0LngtZS54KS8oZS5uZXh0LnktZS55KTtpZihwPD1pJiZwPm8pe2lmKG89cCxwPT09aSl7aWYocj09PWUueSlyZXR1cm4gZTtpZihyPT09ZS5uZXh0LnkpcmV0dXJuIGUubmV4dH1zPWUueDxlLm5leHQueD9lOmUubmV4dH19ZT1lLm5leHR9d2hpbGUoZSE9PXQpO2lmKCFzKXJldHVybiBudWxsO2lmKGk9PT1vKXJldHVybiBzO2xldCBkLGE9cyxsPXMueCxjPXMueSx1PTEvMDtlPXM7ZG97aT49ZS54JiZlLng+PWwmJmkhPT1lLngmJmFiKHI8Yz9pOm8scixsLGMscjxjP286aSxyLGUueCxlLnkpJiYoZD1NYXRoLmFicyhyLWUueSkvKGktZS54KSx1RShlLG4pJiYoZDx1fHxkPT09dSYmKGUueD5zLnh8fGUueD09PXMueCYmRjllKHMsZSkpKSYmKHM9ZSx1PWQpKSxlPWUubmV4dH13aGlsZShlIT09YSk7cmV0dXJuIHN9KG4sdCksdCl7bGV0IGU9UWRlKHQsbik7d2YodCx0Lm5leHQpLHdmKGUsZS5uZXh0KX19ZnVuY3Rpb24gRjllKG4sdCl7cmV0dXJuIFpyKG4ucHJldixuLHQucHJldik8MCYmWnIodC5uZXh0LG4sbi5uZXh0KTwwfWZ1bmN0aW9uIGs4KG4sdCxlLGkscil7cmV0dXJuKG49MTQzMTY1NTc2NSYoKG49ODU4OTkzNDU5Jigobj0yNTI2NDUxMzUmKChuPTE2NzExOTM1Jigobj0zMjc2Nyoobi1lKSpyKXxuPDw4KSl8bjw8NCkpfG48PDIpKXxuPDwxKSl8KHQ9MTQzMTY1NTc2NSYoKHQ9ODU4OTkzNDU5JigodD0yNTI2NDUxMzUmKCh0PTE2NzExOTM1JigodD0zMjc2NyoodC1pKSpyKXx0PDw4KSl8dDw8NCkpfHQ8PDIpKXx0PDwxKSk8PDF9ZnVuY3Rpb24gQjllKG4pe2xldCB0PW4sZT1uO2Rveyh0Lng8ZS54fHx0Lng9PT1lLngmJnQueTxlLnkpJiYoZT10KSx0PXQubmV4dH13aGlsZSh0IT09bik7cmV0dXJuIGV9ZnVuY3Rpb24gYWIobix0LGUsaSxyLG8scyxhKXtyZXR1cm4oci1zKSoodC1hKS0obi1zKSooby1hKT49MCYmKG4tcykqKGktYSktKGUtcykqKHQtYSk+PTAmJihlLXMpKihvLWEpLShyLXMpKihpLWEpPj0wfWZ1bmN0aW9uIFY5ZShuLHQpe3JldHVybiBuLm5leHQuaSE9PXQuaSYmbi5wcmV2LmkhPT10LmkmJiFmdW5jdGlvbihuLHQpe2xldCBlPW47ZG97aWYoZS5pIT09bi5pJiZlLm5leHQuaSE9PW4uaSYmZS5pIT09dC5pJiZlLm5leHQuaSE9PXQuaSYmWGRlKGUsZS5uZXh0LG4sdCkpcmV0dXJuITA7ZT1lLm5leHR9d2hpbGUoZSE9PW4pO3JldHVybiExfShuLHQpJiYodUUobix0KSYmdUUodCxuKSYmZnVuY3Rpb24obix0KXtsZXQgZT1uLGk9ITEscj0obi54K3QueCkvMixvPShuLnkrdC55KS8yO2Rve2UueT5vIT1lLm5leHQueT5vJiZlLm5leHQueSE9PWUueSYmcjwoZS5uZXh0LngtZS54KSooby1lLnkpLyhlLm5leHQueS1lLnkpK2UueCYmKGk9IWkpLGU9ZS5uZXh0fXdoaWxlKGUhPT1uKTtyZXR1cm4gaX0obix0KSYmKFpyKG4ucHJldixuLHQucHJldil8fFpyKG4sdC5wcmV2LHQpKXx8Rmsobix0KSYmWnIobi5wcmV2LG4sbi5uZXh0KT4wJiZacih0LnByZXYsdCx0Lm5leHQpPjApfWZ1bmN0aW9uIFpyKG4sdCxlKXtyZXR1cm4odC55LW4ueSkqKGUueC10LngpLSh0Lngtbi54KSooZS55LXQueSl9ZnVuY3Rpb24gRmsobix0KXtyZXR1cm4gbi54PT09dC54JiZuLnk9PT10Lnl9ZnVuY3Rpb24gWGRlKG4sdCxlLGkpe2xldCByPVpPKFpyKG4sdCxlKSksbz1aTyhacihuLHQsaSkpLHM9Wk8oWnIoZSxpLG4pKSxhPVpPKFpyKGUsaSx0KSk7cmV0dXJuISEociE9PW8mJnMhPT1hfHwwPT09ciYmS08obixlLHQpfHwwPT09byYmS08obixpLHQpfHwwPT09cyYmS08oZSxuLGkpfHwwPT09YSYmS08oZSx0LGkpKX1mdW5jdGlvbiBLTyhuLHQsZSl7cmV0dXJuIHQueDw9TWF0aC5tYXgobi54LGUueCkmJnQueD49TWF0aC5taW4obi54LGUueCkmJnQueTw9TWF0aC5tYXgobi55LGUueSkmJnQueT49TWF0aC5taW4obi55LGUueSl9ZnVuY3Rpb24gWk8obil7cmV0dXJuIG4+MD8xOm48MD8tMTowfWZ1bmN0aW9uIHVFKG4sdCl7cmV0dXJuIFpyKG4ucHJldixuLG4ubmV4dCk8MD9acihuLHQsbi5uZXh0KT49MCYmWnIobixuLnByZXYsdCk+PTA6WnIobix0LG4ucHJldik8MHx8WnIobixuLm5leHQsdCk8MH1mdW5jdGlvbiBRZGUobix0KXtsZXQgZT1uZXcgRjgobi5pLG4ueCxuLnkpLGk9bmV3IEY4KHQuaSx0LngsdC55KSxyPW4ubmV4dCxvPXQucHJldjtyZXR1cm4gbi5uZXh0PXQsdC5wcmV2PW4sZS5uZXh0PXIsci5wcmV2PWUsaS5uZXh0PWUsZS5wcmV2PWksby5uZXh0PWksaS5wcmV2PW8saX1mdW5jdGlvbiB2ZGUobix0LGUsaSl7bGV0IHI9bmV3IEY4KG4sdCxlKTtyZXR1cm4gaT8oci5uZXh0PWkubmV4dCxyLnByZXY9aSxpLm5leHQucHJldj1yLGkubmV4dD1yKTooci5wcmV2PXIsci5uZXh0PXIpLHJ9ZnVuY3Rpb24gZEUobil7bi5uZXh0LnByZXY9bi5wcmV2LG4ucHJldi5uZXh0PW4ubmV4dCxuLnByZXZaJiYobi5wcmV2Wi5uZXh0Wj1uLm5leHRaKSxuLm5leHRaJiYobi5uZXh0Wi5wcmV2Wj1uLnByZXZaKX1mdW5jdGlvbiBGOChuLHQsZSl7dGhpcy5pPW4sdGhpcy54PXQsdGhpcy55PWUsdGhpcy5wcmV2PW51bGwsdGhpcy5uZXh0PW51bGwsdGhpcy56PW51bGwsdGhpcy5wcmV2Wj1udWxsLHRoaXMubmV4dFo9bnVsbCx0aGlzLnN0ZWluZXI9ITF9dmFyIEVkPWNsYXNze3N0YXRpYyBhcmVhKHQpe2xldCBlPXQubGVuZ3RoLGk9MDtmb3IobGV0IHI9ZS0xLG89MDtvPGU7cj1vKyspaSs9dFtyXS54KnRbb10ueS10W29dLngqdFtyXS55O3JldHVybi41Kml9c3RhdGljIGlzQ2xvY2tXaXNlKHQpe3JldHVybiBFZC5hcmVhKHQpPDB9c3RhdGljIHRyaWFuZ3VsYXRlU2hhcGUodCxlKXtsZXQgaT1bXSxyPVtdLG89W107eWRlKHQpLGJkZShpLHQpO2xldCBzPXQubGVuZ3RoO2UuZm9yRWFjaCh5ZGUpO2ZvcihsZXQgbD0wO2w8ZS5sZW5ndGg7bCsrKXIucHVzaChzKSxzKz1lW2xdLmxlbmd0aCxiZGUoaSxlW2xdKTtsZXQgYT1mdW5jdGlvbihuLHQsZT0yKXtsZXQgYSxsLGMsdSxkLHAsaCxpPXQmJnQubGVuZ3RoLHI9aT90WzBdKmU6bi5sZW5ndGgsbz1ZZGUobiwwLHIsZSwhMCkscz1bXTtpZighb3x8by5uZXh0PT09by5wcmV2KXJldHVybiBzO2lmKGkmJihvPWZ1bmN0aW9uKG4sdCxlLGkpe2xldCBvLHMsYSxsLGMscj1bXTtmb3Iobz0wLHM9dC5sZW5ndGg7bzxzO28rKylhPXRbb10qaSxsPW88cy0xP3RbbysxXSppOm4ubGVuZ3RoLGM9WWRlKG4sYSxsLGksITEpLGM9PT1jLm5leHQmJihjLnN0ZWluZXI9ITApLHIucHVzaChCOWUoYykpO2ZvcihyLnNvcnQoUjllKSxvPTA7bzxyLmxlbmd0aDtvKyspTzllKHJbb10sZSksZT13ZihlLGUubmV4dCk7cmV0dXJuIGV9KG4sdCxvLGUpKSxuLmxlbmd0aD44MCplKXthPWM9blswXSxsPXU9blsxXTtmb3IobGV0IGY9ZTtmPHI7Zis9ZSlkPW5bZl0scD1uW2YrMV0sZDxhJiYoYT1kKSxwPGwmJihsPXApLGQ+YyYmKGM9ZCkscD51JiYodT1wKTtoPU1hdGgubWF4KGMtYSx1LWwpLGg9MCE9PWg/MS9oOjB9cmV0dXJuIGNFKG8scyxlLGEsbCxoKSxzfShpLHIpO2ZvcihsZXQgbD0wO2w8YS5sZW5ndGg7bCs9MylvLnB1c2goYS5zbGljZShsLGwrMykpO3JldHVybiBvfX07ZnVuY3Rpb24geWRlKG4pe2xldCB0PW4ubGVuZ3RoO3Q+MiYmblt0LTFdLmVxdWFscyhuWzBdKSYmbi5wb3AoKX1mdW5jdGlvbiBiZGUobix0KXtmb3IobGV0IGU9MDtlPHQubGVuZ3RoO2UrKyluLnB1c2godFtlXS54KSxuLnB1c2godFtlXS55KX12YXIgU2Y9Y2xhc3MgZXh0ZW5kcyBucntjb25zdHJ1Y3Rvcih0PW5ldyBJcChbbmV3IGF0KC41LC41KSxuZXcgYXQoLS41LC41KSxuZXcgYXQoLS41LC0uNSksbmV3IGF0KC41LC0uNSldKSxlPXt9KXtzdXBlcigpLHRoaXMudHlwZT0iRXh0cnVkZUdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3NoYXBlczp0LG9wdGlvbnM6ZX0sdD1BcnJheS5pc0FycmF5KHQpP3Q6W3RdO2xldCBpPXRoaXMscj1bXSxvPVtdO2ZvcihsZXQgYT0wLGw9dC5sZW5ndGg7YTxsO2ErKylzKHRbYV0pO2Z1bmN0aW9uIHMoYSl7bGV0IGw9W10sYz12b2lkIDAhPT1lLmN1cnZlU2VnbWVudHM/ZS5jdXJ2ZVNlZ21lbnRzOjEyLHU9dm9pZCAwIT09ZS5zdGVwcz9lLnN0ZXBzOjEsZD12b2lkIDAhPT1lLmRlcHRoP2UuZGVwdGg6MSxwPXZvaWQgMD09PWUuYmV2ZWxFbmFibGVkfHxlLmJldmVsRW5hYmxlZCxoPXZvaWQgMCE9PWUuYmV2ZWxUaGlja25lc3M/ZS5iZXZlbFRoaWNrbmVzczouMixmPXZvaWQgMCE9PWUuYmV2ZWxTaXplP2UuYmV2ZWxTaXplOmgtLjEsbT12b2lkIDAhPT1lLmJldmVsT2Zmc2V0P2UuYmV2ZWxPZmZzZXQ6MCx4PXZvaWQgMCE9PWUuYmV2ZWxTZWdtZW50cz9lLmJldmVsU2VnbWVudHM6MyxnPWUuZXh0cnVkZVBhdGgsYj12b2lkIDAhPT1lLlVWR2VuZXJhdG9yP2UuVVZHZW5lcmF0b3I6ajllO3ZvaWQgMCE9PWUuYW1vdW50JiYoY29uc29sZS53YXJuKCJUSFJFRS5FeHRydWRlQnVmZmVyR2VvbWV0cnk6IGFtb3VudCBoYXMgYmVlbiByZW5hbWVkIHRvIGRlcHRoLiIpLGQ9ZS5hbW91bnQpO2xldCBELGssWix6LGZlLFQ9ITE7ZyYmKEQ9Zy5nZXRTcGFjZWRQb2ludHModSksVD0hMCxwPSExLGs9Zy5jb21wdXRlRnJlbmV0RnJhbWVzKHUsITEpLFo9bmV3IGllLHo9bmV3IGllLGZlPW5ldyBpZSkscHx8KHg9MCxoPTAsZj0wLG09MCk7bGV0IHVlPWEuZXh0cmFjdFBvaW50cyhjKSxoZT11ZS5zaGFwZSx3PXVlLmhvbGVzO2lmKCFFZC5pc0Nsb2NrV2lzZShoZSkpe2hlPWhlLnJldmVyc2UoKTtmb3IobGV0IGNlPTAsZHQ9dy5sZW5ndGg7Y2U8ZHQ7Y2UrKyl7bGV0IFdlPXdbY2VdO0VkLmlzQ2xvY2tXaXNlKFdlKSYmKHdbY2VdPVdlLnJldmVyc2UoKSl9fWxldCBxPUVkLnRyaWFuZ3VsYXRlU2hhcGUoaGUsdyksSz1oZTtmb3IobGV0IGNlPTAsZHQ9dy5sZW5ndGg7Y2U8ZHQ7Y2UrKyloZT1oZS5jb25jYXQod1tjZV0pO2Z1bmN0aW9uIGRlKGNlLGR0LFdlKXtyZXR1cm4gZHR8fGNvbnNvbGUuZXJyb3IoIlRIUkVFLkV4dHJ1ZGVHZW9tZXRyeTogdmVjIGRvZXMgbm90IGV4aXN0IiksZHQuY2xvbmUoKS5tdWx0aXBseVNjYWxhcihXZSkuYWRkKGNlKX1sZXQgWT1oZS5sZW5ndGgsYWU9cS5sZW5ndGg7ZnVuY3Rpb24gbGUoY2UsZHQsV2Upe2xldCBNdCxidCxobixvbj1jZS54LWR0LngsZmk9Y2UueS1kdC55LFdpPVdlLngtY2UueCxxaT1XZS55LWNlLnksZWU9b24qb24rZmkqZmk7aWYoTWF0aC5hYnMob24qcWktZmkqV2kpPk51bWJlci5FUFNJTE9OKXtsZXQgWGU9TWF0aC5zcXJ0KGVlKSxUdD1NYXRoLnNxcnQoV2kqV2krcWkqcWkpLG1uPWR0LngtZmkvWGUscWU9ZHQueStvbi9YZSx6dD0oKFdlLngtcWkvVHQtbW4pKnFpLShXZS55K1dpL1R0LXFlKSpXaSkvKG9uKnFpLWZpKldpKTtNdD1tbitvbip6dC1jZS54LGJ0PXFlK2ZpKnp0LWNlLnk7bGV0IFV0PU10Kk10K2J0KmJ0O2lmKFV0PD0yKXJldHVybiBuZXcgYXQoTXQsYnQpO2huPU1hdGguc3FydChVdC8yKX1lbHNle2xldCBYZT0hMTtvbj5OdW1iZXIuRVBTSUxPTj9XaT5OdW1iZXIuRVBTSUxPTiYmKFhlPSEwKTpvbjwtTnVtYmVyLkVQU0lMT04/V2k8LU51bWJlci5FUFNJTE9OJiYoWGU9ITApOk1hdGguc2lnbihmaSk9PT1NYXRoLnNpZ24ocWkpJiYoWGU9ITApLFhlPyhNdD0tZmksYnQ9b24saG49TWF0aC5zcXJ0KGVlKSk6KE10PW9uLGJ0PWZpLGhuPU1hdGguc3FydChlZS8yKSl9cmV0dXJuIG5ldyBhdChNdC9obixidC9obil9bGV0IEllPVtdO2ZvcihsZXQgY2U9MCxkdD1LLmxlbmd0aCxXZT1kdC0xLE10PWNlKzE7Y2U8ZHQ7Y2UrKyxXZSsrLE10KyspV2U9PT1kdCYmKFdlPTApLE10PT09ZHQmJihNdD0wKSxJZVtjZV09bGUoS1tjZV0sS1tXZV0sS1tNdF0pO2xldCBEZSx2ZT1bXSxudD1JZS5jb25jYXQoKTtmb3IobGV0IGNlPTAsZHQ9dy5sZW5ndGg7Y2U8ZHQ7Y2UrKyl7bGV0IFdlPXdbY2VdO0RlPVtdO2ZvcihsZXQgTXQ9MCxidD1XZS5sZW5ndGgsaG49YnQtMSxvbj1NdCsxO010PGJ0O010KyssaG4rKyxvbisrKWhuPT09YnQmJihobj0wKSxvbj09PWJ0JiYob249MCksRGVbTXRdPWxlKFdlW010XSxXZVtobl0sV2Vbb25dKTt2ZS5wdXNoKERlKSxudD1udC5jb25jYXQoRGUpfWZvcihsZXQgY2U9MDtjZTx4O2NlKyspe2xldCBkdD1jZS94LFdlPWgqTWF0aC5jb3MoZHQqTWF0aC5QSS8yKSxNdD1mKk1hdGguc2luKGR0Kk1hdGguUEkvMikrbTtmb3IobGV0IGJ0PTAsaG49Sy5sZW5ndGg7YnQ8aG47YnQrKyl7bGV0IG9uPWRlKEtbYnRdLEllW2J0XSxNdCk7cHQob24ueCxvbi55LC1XZSl9Zm9yKGxldCBidD0wLGhuPXcubGVuZ3RoO2J0PGhuO2J0Kyspe2xldCBvbj13W2J0XTtEZT12ZVtidF07Zm9yKGxldCBmaT0wLFdpPW9uLmxlbmd0aDtmaTxXaTtmaSsrKXtsZXQgcWk9ZGUob25bZmldLERlW2ZpXSxNdCk7cHQocWkueCxxaS55LC1XZSl9fX1sZXQgZ3Q9ZittO2ZvcihsZXQgY2U9MDtjZTxZO2NlKyspe2xldCBkdD1wP2RlKGhlW2NlXSxudFtjZV0sZ3QpOmhlW2NlXTtUPyh6LmNvcHkoay5ub3JtYWxzWzBdKS5tdWx0aXBseVNjYWxhcihkdC54KSxaLmNvcHkoay5iaW5vcm1hbHNbMF0pLm11bHRpcGx5U2NhbGFyKGR0LnkpLGZlLmNvcHkoRFswXSkuYWRkKHopLmFkZChaKSxwdChmZS54LGZlLnksZmUueikpOnB0KGR0LngsZHQueSwwKX1mb3IobGV0IGNlPTE7Y2U8PXU7Y2UrKylmb3IobGV0IGR0PTA7ZHQ8WTtkdCsrKXtsZXQgV2U9cD9kZShoZVtkdF0sbnRbZHRdLGd0KTpoZVtkdF07VD8oei5jb3B5KGsubm9ybWFsc1tjZV0pLm11bHRpcGx5U2NhbGFyKFdlLngpLFouY29weShrLmJpbm9ybWFsc1tjZV0pLm11bHRpcGx5U2NhbGFyKFdlLnkpLGZlLmNvcHkoRFtjZV0pLmFkZCh6KS5hZGQoWikscHQoZmUueCxmZS55LGZlLnopKTpwdChXZS54LFdlLnksZC91KmNlKX1mb3IobGV0IGNlPXgtMTtjZT49MDtjZS0tKXtsZXQgZHQ9Y2UveCxXZT1oKk1hdGguY29zKGR0Kk1hdGguUEkvMiksTXQ9ZipNYXRoLnNpbihkdCpNYXRoLlBJLzIpK207Zm9yKGxldCBidD0wLGhuPUsubGVuZ3RoO2J0PGhuO2J0Kyspe2xldCBvbj1kZShLW2J0XSxJZVtidF0sTXQpO3B0KG9uLngsb24ueSxkK1dlKX1mb3IobGV0IGJ0PTAsaG49dy5sZW5ndGg7YnQ8aG47YnQrKyl7bGV0IG9uPXdbYnRdO0RlPXZlW2J0XTtmb3IobGV0IGZpPTAsV2k9b24ubGVuZ3RoO2ZpPFdpO2ZpKyspe2xldCBxaT1kZShvbltmaV0sRGVbZmldLE10KTtUP3B0KHFpLngscWkueStEW3UtMV0ueSxEW3UtMV0ueCtXZSk6cHQocWkueCxxaS55LGQrV2UpfX19ZnVuY3Rpb24gdG4oY2UsZHQpe2xldCBXZT1jZS5sZW5ndGg7Zm9yKDstLVdlPj0wOyl7bGV0IE10PVdlLGJ0PVdlLTE7YnQ8MCYmKGJ0PWNlLmxlbmd0aC0xKTtmb3IobGV0IGhuPTAsb249dSsyKng7aG48b247aG4rKyl7bGV0IGZpPVkqaG4sV2k9WSooaG4rMSk7VGUoZHQrTXQrZmksZHQrYnQrZmksZHQrYnQrV2ksZHQrTXQrV2kpfX19ZnVuY3Rpb24gcHQoY2UsZHQsV2Upe2wucHVzaChjZSksbC5wdXNoKGR0KSxsLnB1c2goV2UpfWZ1bmN0aW9uIHd0KGNlLGR0LFdlKXt4dChjZSkseHQoZHQpLHh0KFdlKTtsZXQgTXQ9ci5sZW5ndGgvMyxidD1iLmdlbmVyYXRlVG9wVVYoaSxyLE10LTMsTXQtMixNdC0xKTttdChidFswXSksbXQoYnRbMV0pLG10KGJ0WzJdKX1mdW5jdGlvbiBUZShjZSxkdCxXZSxNdCl7eHQoY2UpLHh0KGR0KSx4dChNdCkseHQoZHQpLHh0KFdlKSx4dChNdCk7bGV0IGJ0PXIubGVuZ3RoLzMsaG49Yi5nZW5lcmF0ZVNpZGVXYWxsVVYoaSxyLGJ0LTYsYnQtMyxidC0yLGJ0LTEpO210KGhuWzBdKSxtdChoblsxXSksbXQoaG5bM10pLG10KGhuWzFdKSxtdChoblsyXSksbXQoaG5bM10pfWZ1bmN0aW9uIHh0KGNlKXtyLnB1c2gobFszKmNlKzBdKSxyLnB1c2gobFszKmNlKzFdKSxyLnB1c2gobFszKmNlKzJdKX1mdW5jdGlvbiBtdChjZSl7by5wdXNoKGNlLngpLG8ucHVzaChjZS55KX0oZnVuY3Rpb24oKXtsZXQgY2U9ci5sZW5ndGgvMztpZihwKXtsZXQgZHQ9MCxXZT1ZKmR0O2ZvcihsZXQgTXQ9MDtNdDxhZTtNdCsrKXtsZXQgYnQ9cVtNdF07d3QoYnRbMl0rV2UsYnRbMV0rV2UsYnRbMF0rV2UpfWR0PXUrMip4LFdlPVkqZHQ7Zm9yKGxldCBNdD0wO010PGFlO010Kyspe2xldCBidD1xW010XTt3dChidFswXStXZSxidFsxXStXZSxidFsyXStXZSl9fWVsc2V7Zm9yKGxldCBkdD0wO2R0PGFlO2R0Kyspe2xldCBXZT1xW2R0XTt3dChXZVsyXSxXZVsxXSxXZVswXSl9Zm9yKGxldCBkdD0wO2R0PGFlO2R0Kyspe2xldCBXZT1xW2R0XTt3dChXZVswXStZKnUsV2VbMV0rWSp1LFdlWzJdK1kqdSl9fWkuYWRkR3JvdXAoY2Usci5sZW5ndGgvMy1jZSwwKX0pKCksZnVuY3Rpb24oKXtsZXQgY2U9ci5sZW5ndGgvMyxkdD0wO3RuKEssZHQpLGR0Kz1LLmxlbmd0aDtmb3IobGV0IFdlPTAsTXQ9dy5sZW5ndGg7V2U8TXQ7V2UrKyl7bGV0IGJ0PXdbV2VdO3RuKGJ0LGR0KSxkdCs9YnQubGVuZ3RofWkuYWRkR3JvdXAoY2Usci5sZW5ndGgvMy1jZSwxKX0oKX10aGlzLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyBKcihyLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyBKcihvLDIpKSx0aGlzLmNvbXB1dGVWZXJ0ZXhOb3JtYWxzKCl9dG9KU09OKCl7bGV0IHQ9c3VwZXIudG9KU09OKCk7cmV0dXJuIGZ1bmN0aW9uKG4sdCxlKXtpZihlLnNoYXBlcz1bXSxBcnJheS5pc0FycmF5KG4pKWZvcihsZXQgaT0wLHI9bi5sZW5ndGg7aTxyO2krKyllLnNoYXBlcy5wdXNoKG5baV0udXVpZCk7ZWxzZSBlLnNoYXBlcy5wdXNoKG4udXVpZCk7cmV0dXJuIHZvaWQgMCE9PXQuZXh0cnVkZVBhdGgmJihlLm9wdGlvbnMuZXh0cnVkZVBhdGg9dC5leHRydWRlUGF0aC50b0pTT04oKSksZX0odGhpcy5wYXJhbWV0ZXJzLnNoYXBlcyx0aGlzLnBhcmFtZXRlcnMub3B0aW9ucyx0KX1zdGF0aWMgZnJvbUpTT04odCxlKXtsZXQgaT1bXTtmb3IobGV0IG89MCxzPXQuc2hhcGVzLmxlbmd0aDtvPHM7bysrKWkucHVzaChlW3Quc2hhcGVzW29dXSk7bGV0IHI9dC5vcHRpb25zLmV4dHJ1ZGVQYXRoO3JldHVybiB2b2lkIDAhPT1yJiYodC5vcHRpb25zLmV4dHJ1ZGVQYXRoPShuZXcgcWRlW3IudHlwZV0pLmZyb21KU09OKHIpKSxuZXcgU2YoaSx0Lm9wdGlvbnMpfX0sajllPXtnZW5lcmF0ZVRvcFVWOmZ1bmN0aW9uKG4sdCxlLGkscil7bGV0IGE9dFszKmldLGw9dFszKmkrMV0sYz10WzMqcl0sdT10WzMqcisxXTtyZXR1cm5bbmV3IGF0KHRbMyplXSx0WzMqZSsxXSksbmV3IGF0KGEsbCksbmV3IGF0KGMsdSldfSxnZW5lcmF0ZVNpZGVXYWxsVVY6ZnVuY3Rpb24obix0LGUsaSxyLG8pe2xldCBzPXRbMyplXSxhPXRbMyplKzFdLGw9dFszKmUrMl0sYz10WzMqaV0sdT10WzMqaSsxXSxkPXRbMyppKzJdLHA9dFszKnJdLGg9dFszKnIrMV0sZj10WzMqcisyXSxtPXRbMypvXSx4PXRbMypvKzFdLGc9dFszKm8rMl07cmV0dXJuIE1hdGguYWJzKGEtdSk8TWF0aC5hYnMocy1jKT9bbmV3IGF0KHMsMS1sKSxuZXcgYXQoYywxLWQpLG5ldyBhdChwLDEtZiksbmV3IGF0KG0sMS1nKV06W25ldyBhdChhLDEtbCksbmV3IGF0KHUsMS1kKSxuZXcgYXQoaCwxLWYpLG5ldyBhdCh4LDEtZyldfX0sUWc9Y2xhc3MgZXh0ZW5kcyBucntjb25zdHJ1Y3Rvcih0PW5ldyBJcChbbmV3IGF0KDAsLjUpLG5ldyBhdCgtLjUsLS41KSxuZXcgYXQoLjUsLS41KV0pLGU9MTIpe3N1cGVyKCksdGhpcy50eXBlPSJTaGFwZUdlb21ldHJ5Iix0aGlzLnBhcmFtZXRlcnM9e3NoYXBlczp0LGN1cnZlU2VnbWVudHM6ZX07bGV0IGk9W10scj1bXSxvPVtdLHM9W10sYT0wLGw9MDtpZighMT09PUFycmF5LmlzQXJyYXkodCkpYyh0KTtlbHNlIGZvcihsZXQgdT0wO3U8dC5sZW5ndGg7dSsrKWModFt1XSksdGhpcy5hZGRHcm91cChhLGwsdSksYSs9bCxsPTA7ZnVuY3Rpb24gYyh1KXtsZXQgZD1yLmxlbmd0aC8zLHA9dS5leHRyYWN0UG9pbnRzKGUpLGg9cC5zaGFwZSxmPXAuaG9sZXM7ITE9PT1FZC5pc0Nsb2NrV2lzZShoKSYmKGg9aC5yZXZlcnNlKCkpO2ZvcihsZXQgeD0wLGc9Zi5sZW5ndGg7eDxnO3grKyl7bGV0IGI9Zlt4XTshMD09PUVkLmlzQ2xvY2tXaXNlKGIpJiYoZlt4XT1iLnJldmVyc2UoKSl9bGV0IG09RWQudHJpYW5ndWxhdGVTaGFwZShoLGYpO2ZvcihsZXQgeD0wLGc9Zi5sZW5ndGg7eDxnO3grKyloPWguY29uY2F0KGZbeF0pO2ZvcihsZXQgeD0wLGc9aC5sZW5ndGg7eDxnO3grKyl7bGV0IGI9aFt4XTtyLnB1c2goYi54LGIueSwwKSxvLnB1c2goMCwwLDEpLHMucHVzaChiLngsYi55KX1mb3IobGV0IHg9MCxnPW0ubGVuZ3RoO3g8Zzt4Kyspe2xldCBiPW1beF07aS5wdXNoKGJbMF0rZCxiWzFdK2QsYlsyXStkKSxsKz0zfX10aGlzLnNldEluZGV4KGkpLHRoaXMuc2V0QXR0cmlidXRlKCJwb3NpdGlvbiIsbmV3IEpyKHIsMykpLHRoaXMuc2V0QXR0cmlidXRlKCJub3JtYWwiLG5ldyBKcihvLDMpKSx0aGlzLnNldEF0dHJpYnV0ZSgidXYiLG5ldyBKcihzLDIpKX10b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04oKTtyZXR1cm4gZnVuY3Rpb24obix0KXtpZih0LnNoYXBlcz1bXSxBcnJheS5pc0FycmF5KG4pKWZvcihsZXQgZT0wLGk9bi5sZW5ndGg7ZTxpO2UrKyl0LnNoYXBlcy5wdXNoKG5bZV0udXVpZCk7ZWxzZSB0LnNoYXBlcy5wdXNoKG4udXVpZCk7cmV0dXJuIHR9KHRoaXMucGFyYW1ldGVycy5zaGFwZXMsdCl9c3RhdGljIGZyb21KU09OKHQsZSl7bGV0IGk9W107Zm9yKGxldCByPTAsbz10LnNoYXBlcy5sZW5ndGg7cjxvO3IrKylpLnB1c2goZVt0LnNoYXBlc1tyXV0pO3JldHVybiBuZXcgUWcoaSx0LmN1cnZlU2VnbWVudHMpfX07KGNsYXNzIGV4dGVuZHMgaHN7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLnR5cGU9IlNoYWRvd01hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyB2bigwKSx0aGlzLnRyYW5zcGFyZW50PSEwLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXN9fSkucHJvdG90eXBlLmlzU2hhZG93TWF0ZXJpYWw9ITA7dmFyIENrPWNsYXNzIGV4dGVuZHMgaHN7Y29uc3RydWN0b3IodCl7c3VwZXIoKSx0aGlzLmRlZmluZXM9e1NUQU5EQVJEOiIifSx0aGlzLnR5cGU9Ik1lc2hTdGFuZGFyZE1hdGVyaWFsIix0aGlzLmNvbG9yPW5ldyB2bigxNjc3NzIxNSksdGhpcy5yb3VnaG5lc3M9MSx0aGlzLm1ldGFsbmVzcz0wLHRoaXMubWFwPW51bGwsdGhpcy5saWdodE1hcD1udWxsLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9MSx0aGlzLmFvTWFwPW51bGwsdGhpcy5hb01hcEludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmU9bmV3IHZuKDApLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9MSx0aGlzLmVtaXNzaXZlTWFwPW51bGwsdGhpcy5idW1wTWFwPW51bGwsdGhpcy5idW1wU2NhbGU9MSx0aGlzLm5vcm1hbE1hcD1udWxsLHRoaXMubm9ybWFsTWFwVHlwZT0wLHRoaXMubm9ybWFsU2NhbGU9bmV3IGF0KDEsMSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9bnVsbCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPTEsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPTAsdGhpcy5yb3VnaG5lc3NNYXA9bnVsbCx0aGlzLm1ldGFsbmVzc01hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLmVudk1hcD1udWxsLHRoaXMuZW52TWFwSW50ZW5zaXR5PTEsdGhpcy5yZWZyYWN0aW9uUmF0aW89Ljk4LHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy53aXJlZnJhbWVMaW5lY2FwPSJyb3VuZCIsdGhpcy53aXJlZnJhbWVMaW5lam9pbj0icm91bmQiLHRoaXMuZmxhdFNoYWRpbmc9ITEsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmRlZmluZXM9e1NUQU5EQVJEOiIifSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5yb3VnaG5lc3M9dC5yb3VnaG5lc3MsdGhpcy5tZXRhbG5lc3M9dC5tZXRhbG5lc3MsdGhpcy5tYXA9dC5tYXAsdGhpcy5saWdodE1hcD10LmxpZ2h0TWFwLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9dC5saWdodE1hcEludGVuc2l0eSx0aGlzLmFvTWFwPXQuYW9NYXAsdGhpcy5hb01hcEludGVuc2l0eT10LmFvTWFwSW50ZW5zaXR5LHRoaXMuZW1pc3NpdmUuY29weSh0LmVtaXNzaXZlKSx0aGlzLmVtaXNzaXZlTWFwPXQuZW1pc3NpdmVNYXAsdGhpcy5lbWlzc2l2ZUludGVuc2l0eT10LmVtaXNzaXZlSW50ZW5zaXR5LHRoaXMuYnVtcE1hcD10LmJ1bXBNYXAsdGhpcy5idW1wU2NhbGU9dC5idW1wU2NhbGUsdGhpcy5ub3JtYWxNYXA9dC5ub3JtYWxNYXAsdGhpcy5ub3JtYWxNYXBUeXBlPXQubm9ybWFsTWFwVHlwZSx0aGlzLm5vcm1hbFNjYWxlLmNvcHkodC5ub3JtYWxTY2FsZSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9dC5kaXNwbGFjZW1lbnRNYXAsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT10LmRpc3BsYWNlbWVudFNjYWxlLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz10LmRpc3BsYWNlbWVudEJpYXMsdGhpcy5yb3VnaG5lc3NNYXA9dC5yb3VnaG5lc3NNYXAsdGhpcy5tZXRhbG5lc3NNYXA9dC5tZXRhbG5lc3NNYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZW52TWFwPXQuZW52TWFwLHRoaXMuZW52TWFwSW50ZW5zaXR5PXQuZW52TWFwSW50ZW5zaXR5LHRoaXMucmVmcmFjdGlvblJhdGlvPXQucmVmcmFjdGlvblJhdGlvLHRoaXMud2lyZWZyYW1lPXQud2lyZWZyYW1lLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPXQud2lyZWZyYW1lTGluZXdpZHRoLHRoaXMud2lyZWZyYW1lTGluZWNhcD10LndpcmVmcmFtZUxpbmVjYXAsdGhpcy53aXJlZnJhbWVMaW5lam9pbj10LndpcmVmcmFtZUxpbmVqb2luLHRoaXMuZmxhdFNoYWRpbmc9dC5mbGF0U2hhZGluZyx0aGlzfX07Q2sucHJvdG90eXBlLmlzTWVzaFN0YW5kYXJkTWF0ZXJpYWw9ITAsY2xhc3MgZXh0ZW5kcyBDa3tjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMuZGVmaW5lcz17U1RBTkRBUkQ6IiIsUEhZU0lDQUw6IiJ9LHRoaXMudHlwZT0iTWVzaFBoeXNpY2FsTWF0ZXJpYWwiLHRoaXMuY2xlYXJjb2F0TWFwPW51bGwsdGhpcy5jbGVhcmNvYXRSb3VnaG5lc3M9MCx0aGlzLmNsZWFyY29hdFJvdWdobmVzc01hcD1udWxsLHRoaXMuY2xlYXJjb2F0Tm9ybWFsU2NhbGU9bmV3IGF0KDEsMSksdGhpcy5jbGVhcmNvYXROb3JtYWxNYXA9bnVsbCx0aGlzLmlvcj0xLjUsT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsInJlZmxlY3Rpdml0eSIse2dldDpmdW5jdGlvbigpe3JldHVybiBHYSgyLjUqKHRoaXMuaW9yLTEpLyh0aGlzLmlvcisxKSwwLDEpfSxzZXQ6ZnVuY3Rpb24oZSl7dGhpcy5pb3I9KDErLjQqZSkvKDEtLjQqZSl9fSksdGhpcy5zaGVlbkNvbG9yPW5ldyB2bigwKSx0aGlzLnNoZWVuQ29sb3JNYXA9bnVsbCx0aGlzLnNoZWVuUm91Z2huZXNzPTEsdGhpcy5zaGVlblJvdWdobmVzc01hcD1udWxsLHRoaXMudHJhbnNtaXNzaW9uTWFwPW51bGwsdGhpcy50aGlja25lc3M9MCx0aGlzLnRoaWNrbmVzc01hcD1udWxsLHRoaXMuYXR0ZW51YXRpb25EaXN0YW5jZT0wLHRoaXMuYXR0ZW51YXRpb25Db2xvcj1uZXcgdm4oMSwxLDEpLHRoaXMuc3BlY3VsYXJJbnRlbnNpdHk9MSx0aGlzLnNwZWN1bGFySW50ZW5zaXR5TWFwPW51bGwsdGhpcy5zcGVjdWxhckNvbG9yPW5ldyB2bigxLDEsMSksdGhpcy5zcGVjdWxhckNvbG9yTWFwPW51bGwsdGhpcy5fc2hlZW49MCx0aGlzLl9jbGVhcmNvYXQ9MCx0aGlzLl90cmFuc21pc3Npb249MCx0aGlzLnNldFZhbHVlcyh0KX1nZXQgc2hlZW4oKXtyZXR1cm4gdGhpcy5fc2hlZW59c2V0IHNoZWVuKHQpe3RoaXMuX3NoZWVuPjAhPXQ+MCYmdGhpcy52ZXJzaW9uKyssdGhpcy5fc2hlZW49dH1nZXQgY2xlYXJjb2F0KCl7cmV0dXJuIHRoaXMuX2NsZWFyY29hdH1zZXQgY2xlYXJjb2F0KHQpe3RoaXMuX2NsZWFyY29hdD4wIT10PjAmJnRoaXMudmVyc2lvbisrLHRoaXMuX2NsZWFyY29hdD10fWdldCB0cmFuc21pc3Npb24oKXtyZXR1cm4gdGhpcy5fdHJhbnNtaXNzaW9ufXNldCB0cmFuc21pc3Npb24odCl7dGhpcy5fdHJhbnNtaXNzaW9uPjAhPXQ+MCYmdGhpcy52ZXJzaW9uKyssdGhpcy5fdHJhbnNtaXNzaW9uPXR9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmRlZmluZXM9e1NUQU5EQVJEOiIiLFBIWVNJQ0FMOiIifSx0aGlzLmNsZWFyY29hdD10LmNsZWFyY29hdCx0aGlzLmNsZWFyY29hdE1hcD10LmNsZWFyY29hdE1hcCx0aGlzLmNsZWFyY29hdFJvdWdobmVzcz10LmNsZWFyY29hdFJvdWdobmVzcyx0aGlzLmNsZWFyY29hdFJvdWdobmVzc01hcD10LmNsZWFyY29hdFJvdWdobmVzc01hcCx0aGlzLmNsZWFyY29hdE5vcm1hbE1hcD10LmNsZWFyY29hdE5vcm1hbE1hcCx0aGlzLmNsZWFyY29hdE5vcm1hbFNjYWxlLmNvcHkodC5jbGVhcmNvYXROb3JtYWxTY2FsZSksdGhpcy5pb3I9dC5pb3IsdGhpcy5zaGVlbj10LnNoZWVuLHRoaXMuc2hlZW5Db2xvci5jb3B5KHQuc2hlZW5Db2xvciksdGhpcy5zaGVlbkNvbG9yTWFwPXQuc2hlZW5Db2xvck1hcCx0aGlzLnNoZWVuUm91Z2huZXNzPXQuc2hlZW5Sb3VnaG5lc3MsdGhpcy5zaGVlblJvdWdobmVzc01hcD10LnNoZWVuUm91Z2huZXNzTWFwLHRoaXMudHJhbnNtaXNzaW9uPXQudHJhbnNtaXNzaW9uLHRoaXMudHJhbnNtaXNzaW9uTWFwPXQudHJhbnNtaXNzaW9uTWFwLHRoaXMudGhpY2tuZXNzPXQudGhpY2tuZXNzLHRoaXMudGhpY2tuZXNzTWFwPXQudGhpY2tuZXNzTWFwLHRoaXMuYXR0ZW51YXRpb25EaXN0YW5jZT10LmF0dGVudWF0aW9uRGlzdGFuY2UsdGhpcy5hdHRlbnVhdGlvbkNvbG9yLmNvcHkodC5hdHRlbnVhdGlvbkNvbG9yKSx0aGlzLnNwZWN1bGFySW50ZW5zaXR5PXQuc3BlY3VsYXJJbnRlbnNpdHksdGhpcy5zcGVjdWxhckludGVuc2l0eU1hcD10LnNwZWN1bGFySW50ZW5zaXR5TWFwLHRoaXMuc3BlY3VsYXJDb2xvci5jb3B5KHQuc3BlY3VsYXJDb2xvciksdGhpcy5zcGVjdWxhckNvbG9yTWFwPXQuc3BlY3VsYXJDb2xvck1hcCx0aGlzfX0ucHJvdG90eXBlLmlzTWVzaFBoeXNpY2FsTWF0ZXJpYWw9ITAsY2xhc3MgZXh0ZW5kcyBoc3tjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaFBob25nTWF0ZXJpYWwiLHRoaXMuY29sb3I9bmV3IHZuKDE2Nzc3MjE1KSx0aGlzLnNwZWN1bGFyPW5ldyB2bigxMTE4NDgxKSx0aGlzLnNoaW5pbmVzcz0zMCx0aGlzLm1hcD1udWxsLHRoaXMubGlnaHRNYXA9bnVsbCx0aGlzLmxpZ2h0TWFwSW50ZW5zaXR5PTEsdGhpcy5hb01hcD1udWxsLHRoaXMuYW9NYXBJbnRlbnNpdHk9MSx0aGlzLmVtaXNzaXZlPW5ldyB2bigwKSx0aGlzLmVtaXNzaXZlSW50ZW5zaXR5PTEsdGhpcy5lbWlzc2l2ZU1hcD1udWxsLHRoaXMuYnVtcE1hcD1udWxsLHRoaXMuYnVtcFNjYWxlPTEsdGhpcy5ub3JtYWxNYXA9bnVsbCx0aGlzLm5vcm1hbE1hcFR5cGU9MCx0aGlzLm5vcm1hbFNjYWxlPW5ldyBhdCgxLDEpLHRoaXMuZGlzcGxhY2VtZW50TWFwPW51bGwsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT0xLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz0wLHRoaXMuc3BlY3VsYXJNYXA9bnVsbCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy5lbnZNYXA9bnVsbCx0aGlzLmNvbWJpbmU9MCx0aGlzLnJlZmxlY3Rpdml0eT0xLHRoaXMucmVmcmFjdGlvblJhdGlvPS45OCx0aGlzLndpcmVmcmFtZT0hMSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD0xLHRoaXMud2lyZWZyYW1lTGluZWNhcD0icm91bmQiLHRoaXMud2lyZWZyYW1lTGluZWpvaW49InJvdW5kIix0aGlzLmZsYXRTaGFkaW5nPSExLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMuc3BlY3VsYXIuY29weSh0LnNwZWN1bGFyKSx0aGlzLnNoaW5pbmVzcz10LnNoaW5pbmVzcyx0aGlzLm1hcD10Lm1hcCx0aGlzLmxpZ2h0TWFwPXQubGlnaHRNYXAsdGhpcy5saWdodE1hcEludGVuc2l0eT10LmxpZ2h0TWFwSW50ZW5zaXR5LHRoaXMuYW9NYXA9dC5hb01hcCx0aGlzLmFvTWFwSW50ZW5zaXR5PXQuYW9NYXBJbnRlbnNpdHksdGhpcy5lbWlzc2l2ZS5jb3B5KHQuZW1pc3NpdmUpLHRoaXMuZW1pc3NpdmVNYXA9dC5lbWlzc2l2ZU1hcCx0aGlzLmVtaXNzaXZlSW50ZW5zaXR5PXQuZW1pc3NpdmVJbnRlbnNpdHksdGhpcy5idW1wTWFwPXQuYnVtcE1hcCx0aGlzLmJ1bXBTY2FsZT10LmJ1bXBTY2FsZSx0aGlzLm5vcm1hbE1hcD10Lm5vcm1hbE1hcCx0aGlzLm5vcm1hbE1hcFR5cGU9dC5ub3JtYWxNYXBUeXBlLHRoaXMubm9ybWFsU2NhbGUuY29weSh0Lm5vcm1hbFNjYWxlKSx0aGlzLmRpc3BsYWNlbWVudE1hcD10LmRpc3BsYWNlbWVudE1hcCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPXQuZGlzcGxhY2VtZW50U2NhbGUsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPXQuZGlzcGxhY2VtZW50Qmlhcyx0aGlzLnNwZWN1bGFyTWFwPXQuc3BlY3VsYXJNYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZW52TWFwPXQuZW52TWFwLHRoaXMuY29tYmluZT10LmNvbWJpbmUsdGhpcy5yZWZsZWN0aXZpdHk9dC5yZWZsZWN0aXZpdHksdGhpcy5yZWZyYWN0aW9uUmF0aW89dC5yZWZyYWN0aW9uUmF0aW8sdGhpcy53aXJlZnJhbWU9dC53aXJlZnJhbWUsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9dC53aXJlZnJhbWVMaW5ld2lkdGgsdGhpcy53aXJlZnJhbWVMaW5lY2FwPXQud2lyZWZyYW1lTGluZWNhcCx0aGlzLndpcmVmcmFtZUxpbmVqb2luPXQud2lyZWZyYW1lTGluZWpvaW4sdGhpcy5mbGF0U2hhZGluZz10LmZsYXRTaGFkaW5nLHRoaXN9fS5wcm90b3R5cGUuaXNNZXNoUGhvbmdNYXRlcmlhbD0hMCxjbGFzcyBleHRlbmRzIGhze2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy5kZWZpbmVzPXtUT09OOiIifSx0aGlzLnR5cGU9Ik1lc2hUb29uTWF0ZXJpYWwiLHRoaXMuY29sb3I9bmV3IHZuKDE2Nzc3MjE1KSx0aGlzLm1hcD1udWxsLHRoaXMuZ3JhZGllbnRNYXA9bnVsbCx0aGlzLmxpZ2h0TWFwPW51bGwsdGhpcy5saWdodE1hcEludGVuc2l0eT0xLHRoaXMuYW9NYXA9bnVsbCx0aGlzLmFvTWFwSW50ZW5zaXR5PTEsdGhpcy5lbWlzc2l2ZT1uZXcgdm4oMCksdGhpcy5lbWlzc2l2ZUludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmVNYXA9bnVsbCx0aGlzLmJ1bXBNYXA9bnVsbCx0aGlzLmJ1bXBTY2FsZT0xLHRoaXMubm9ybWFsTWFwPW51bGwsdGhpcy5ub3JtYWxNYXBUeXBlPTAsdGhpcy5ub3JtYWxTY2FsZT1uZXcgYXQoMSwxKSx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy53aXJlZnJhbWU9ITEsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9MSx0aGlzLndpcmVmcmFtZUxpbmVjYXA9InJvdW5kIix0aGlzLndpcmVmcmFtZUxpbmVqb2luPSJyb3VuZCIsdGhpcy5zZXRWYWx1ZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5tYXA9dC5tYXAsdGhpcy5ncmFkaWVudE1hcD10LmdyYWRpZW50TWFwLHRoaXMubGlnaHRNYXA9dC5saWdodE1hcCx0aGlzLmxpZ2h0TWFwSW50ZW5zaXR5PXQubGlnaHRNYXBJbnRlbnNpdHksdGhpcy5hb01hcD10LmFvTWFwLHRoaXMuYW9NYXBJbnRlbnNpdHk9dC5hb01hcEludGVuc2l0eSx0aGlzLmVtaXNzaXZlLmNvcHkodC5lbWlzc2l2ZSksdGhpcy5lbWlzc2l2ZU1hcD10LmVtaXNzaXZlTWFwLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9dC5lbWlzc2l2ZUludGVuc2l0eSx0aGlzLmJ1bXBNYXA9dC5idW1wTWFwLHRoaXMuYnVtcFNjYWxlPXQuYnVtcFNjYWxlLHRoaXMubm9ybWFsTWFwPXQubm9ybWFsTWFwLHRoaXMubm9ybWFsTWFwVHlwZT10Lm5vcm1hbE1hcFR5cGUsdGhpcy5ub3JtYWxTY2FsZS5jb3B5KHQubm9ybWFsU2NhbGUpLHRoaXMuZGlzcGxhY2VtZW50TWFwPXQuZGlzcGxhY2VtZW50TWFwLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9dC5kaXNwbGFjZW1lbnRTY2FsZSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9dC5kaXNwbGFjZW1lbnRCaWFzLHRoaXMuYWxwaGFNYXA9dC5hbHBoYU1hcCx0aGlzLndpcmVmcmFtZT10LndpcmVmcmFtZSx0aGlzLndpcmVmcmFtZUxpbmV3aWR0aD10LndpcmVmcmFtZUxpbmV3aWR0aCx0aGlzLndpcmVmcmFtZUxpbmVjYXA9dC53aXJlZnJhbWVMaW5lY2FwLHRoaXMud2lyZWZyYW1lTGluZWpvaW49dC53aXJlZnJhbWVMaW5lam9pbix0aGlzfX0ucHJvdG90eXBlLmlzTWVzaFRvb25NYXRlcmlhbD0hMCxjbGFzcyBleHRlbmRzIGhze2NvbnN0cnVjdG9yKHQpe3N1cGVyKCksdGhpcy50eXBlPSJNZXNoTm9ybWFsTWF0ZXJpYWwiLHRoaXMuYnVtcE1hcD1udWxsLHRoaXMuYnVtcFNjYWxlPTEsdGhpcy5ub3JtYWxNYXA9bnVsbCx0aGlzLm5vcm1hbE1hcFR5cGU9MCx0aGlzLm5vcm1hbFNjYWxlPW5ldyBhdCgxLDEpLHRoaXMuZGlzcGxhY2VtZW50TWFwPW51bGwsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT0xLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz0wLHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy5mb2c9ITEsdGhpcy5mbGF0U2hhZGluZz0hMSx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuYnVtcE1hcD10LmJ1bXBNYXAsdGhpcy5idW1wU2NhbGU9dC5idW1wU2NhbGUsdGhpcy5ub3JtYWxNYXA9dC5ub3JtYWxNYXAsdGhpcy5ub3JtYWxNYXBUeXBlPXQubm9ybWFsTWFwVHlwZSx0aGlzLm5vcm1hbFNjYWxlLmNvcHkodC5ub3JtYWxTY2FsZSksdGhpcy5kaXNwbGFjZW1lbnRNYXA9dC5kaXNwbGFjZW1lbnRNYXAsdGhpcy5kaXNwbGFjZW1lbnRTY2FsZT10LmRpc3BsYWNlbWVudFNjYWxlLHRoaXMuZGlzcGxhY2VtZW50Qmlhcz10LmRpc3BsYWNlbWVudEJpYXMsdGhpcy53aXJlZnJhbWU9dC53aXJlZnJhbWUsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9dC53aXJlZnJhbWVMaW5ld2lkdGgsdGhpcy5mbGF0U2hhZGluZz10LmZsYXRTaGFkaW5nLHRoaXN9fS5wcm90b3R5cGUuaXNNZXNoTm9ybWFsTWF0ZXJpYWw9ITAsY2xhc3MgZXh0ZW5kcyBoc3tjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTWVzaExhbWJlcnRNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgdm4oMTY3NzcyMTUpLHRoaXMubWFwPW51bGwsdGhpcy5saWdodE1hcD1udWxsLHRoaXMubGlnaHRNYXBJbnRlbnNpdHk9MSx0aGlzLmFvTWFwPW51bGwsdGhpcy5hb01hcEludGVuc2l0eT0xLHRoaXMuZW1pc3NpdmU9bmV3IHZuKDApLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9MSx0aGlzLmVtaXNzaXZlTWFwPW51bGwsdGhpcy5zcGVjdWxhck1hcD1udWxsLHRoaXMuYWxwaGFNYXA9bnVsbCx0aGlzLmVudk1hcD1udWxsLHRoaXMuY29tYmluZT0wLHRoaXMucmVmbGVjdGl2aXR5PTEsdGhpcy5yZWZyYWN0aW9uUmF0aW89Ljk4LHRoaXMud2lyZWZyYW1lPSExLHRoaXMud2lyZWZyYW1lTGluZXdpZHRoPTEsdGhpcy53aXJlZnJhbWVMaW5lY2FwPSJyb3VuZCIsdGhpcy53aXJlZnJhbWVMaW5lam9pbj0icm91bmQiLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMubWFwPXQubWFwLHRoaXMubGlnaHRNYXA9dC5saWdodE1hcCx0aGlzLmxpZ2h0TWFwSW50ZW5zaXR5PXQubGlnaHRNYXBJbnRlbnNpdHksdGhpcy5hb01hcD10LmFvTWFwLHRoaXMuYW9NYXBJbnRlbnNpdHk9dC5hb01hcEludGVuc2l0eSx0aGlzLmVtaXNzaXZlLmNvcHkodC5lbWlzc2l2ZSksdGhpcy5lbWlzc2l2ZU1hcD10LmVtaXNzaXZlTWFwLHRoaXMuZW1pc3NpdmVJbnRlbnNpdHk9dC5lbWlzc2l2ZUludGVuc2l0eSx0aGlzLnNwZWN1bGFyTWFwPXQuc3BlY3VsYXJNYXAsdGhpcy5hbHBoYU1hcD10LmFscGhhTWFwLHRoaXMuZW52TWFwPXQuZW52TWFwLHRoaXMuY29tYmluZT10LmNvbWJpbmUsdGhpcy5yZWZsZWN0aXZpdHk9dC5yZWZsZWN0aXZpdHksdGhpcy5yZWZyYWN0aW9uUmF0aW89dC5yZWZyYWN0aW9uUmF0aW8sdGhpcy53aXJlZnJhbWU9dC53aXJlZnJhbWUsdGhpcy53aXJlZnJhbWVMaW5ld2lkdGg9dC53aXJlZnJhbWVMaW5ld2lkdGgsdGhpcy53aXJlZnJhbWVMaW5lY2FwPXQud2lyZWZyYW1lTGluZWNhcCx0aGlzLndpcmVmcmFtZUxpbmVqb2luPXQud2lyZWZyYW1lTGluZWpvaW4sdGhpc319LnByb3RvdHlwZS5pc01lc2hMYW1iZXJ0TWF0ZXJpYWw9ITAsY2xhc3MgZXh0ZW5kcyBoc3tjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMuZGVmaW5lcz17TUFUQ0FQOiIifSx0aGlzLnR5cGU9Ik1lc2hNYXRjYXBNYXRlcmlhbCIsdGhpcy5jb2xvcj1uZXcgdm4oMTY3NzcyMTUpLHRoaXMubWF0Y2FwPW51bGwsdGhpcy5tYXA9bnVsbCx0aGlzLmJ1bXBNYXA9bnVsbCx0aGlzLmJ1bXBTY2FsZT0xLHRoaXMubm9ybWFsTWFwPW51bGwsdGhpcy5ub3JtYWxNYXBUeXBlPTAsdGhpcy5ub3JtYWxTY2FsZT1uZXcgYXQoMSwxKSx0aGlzLmRpc3BsYWNlbWVudE1hcD1udWxsLHRoaXMuZGlzcGxhY2VtZW50U2NhbGU9MSx0aGlzLmRpc3BsYWNlbWVudEJpYXM9MCx0aGlzLmFscGhhTWFwPW51bGwsdGhpcy5mbGF0U2hhZGluZz0hMSx0aGlzLnNldFZhbHVlcyh0KX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuZGVmaW5lcz17TUFUQ0FQOiIifSx0aGlzLmNvbG9yLmNvcHkodC5jb2xvciksdGhpcy5tYXRjYXA9dC5tYXRjYXAsdGhpcy5tYXA9dC5tYXAsdGhpcy5idW1wTWFwPXQuYnVtcE1hcCx0aGlzLmJ1bXBTY2FsZT10LmJ1bXBTY2FsZSx0aGlzLm5vcm1hbE1hcD10Lm5vcm1hbE1hcCx0aGlzLm5vcm1hbE1hcFR5cGU9dC5ub3JtYWxNYXBUeXBlLHRoaXMubm9ybWFsU2NhbGUuY29weSh0Lm5vcm1hbFNjYWxlKSx0aGlzLmRpc3BsYWNlbWVudE1hcD10LmRpc3BsYWNlbWVudE1hcCx0aGlzLmRpc3BsYWNlbWVudFNjYWxlPXQuZGlzcGxhY2VtZW50U2NhbGUsdGhpcy5kaXNwbGFjZW1lbnRCaWFzPXQuZGlzcGxhY2VtZW50Qmlhcyx0aGlzLmFscGhhTWFwPXQuYWxwaGFNYXAsdGhpcy5mbGF0U2hhZGluZz10LmZsYXRTaGFkaW5nLHRoaXN9fS5wcm90b3R5cGUuaXNNZXNoTWF0Y2FwTWF0ZXJpYWw9ITAsY2xhc3MgZXh0ZW5kcyBBcHtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iTGluZURhc2hlZE1hdGVyaWFsIix0aGlzLnNjYWxlPTEsdGhpcy5kYXNoU2l6ZT0zLHRoaXMuZ2FwU2l6ZT0xLHRoaXMuc2V0VmFsdWVzKHQpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5zY2FsZT10LnNjYWxlLHRoaXMuZGFzaFNpemU9dC5kYXNoU2l6ZSx0aGlzLmdhcFNpemU9dC5nYXBTaXplLHRoaXN9fS5wcm90b3R5cGUuaXNMaW5lRGFzaGVkTWF0ZXJpYWw9ITA7dmFyIHFyPXthcnJheVNsaWNlOmZ1bmN0aW9uKG4sdCxlKXtyZXR1cm4gcXIuaXNUeXBlZEFycmF5KG4pP25ldyBuLmNvbnN0cnVjdG9yKG4uc3ViYXJyYXkodCx2b2lkIDAhPT1lP2U6bi5sZW5ndGgpKTpuLnNsaWNlKHQsZSl9LGNvbnZlcnRBcnJheTpmdW5jdGlvbihuLHQsZSl7cmV0dXJuIW58fCFlJiZuLmNvbnN0cnVjdG9yPT09dD9uOiJudW1iZXIiPT10eXBlb2YgdC5CWVRFU19QRVJfRUxFTUVOVD9uZXcgdChuKTpBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChuKX0saXNUeXBlZEFycmF5OmZ1bmN0aW9uKG4pe3JldHVybiBBcnJheUJ1ZmZlci5pc1ZpZXcobikmJiEobiBpbnN0YW5jZW9mIERhdGFWaWV3KX0sZ2V0S2V5ZnJhbWVPcmRlcjpmdW5jdGlvbihuKXtsZXQgZT1uLmxlbmd0aCxpPW5ldyBBcnJheShlKTtmb3IobGV0IHI9MDtyIT09ZTsrK3IpaVtyXT1yO3JldHVybiBpLnNvcnQoZnVuY3Rpb24ocixvKXtyZXR1cm4gbltyXS1uW29dfSksaX0sc29ydGVkQXJyYXk6ZnVuY3Rpb24obix0LGUpe2xldCBpPW4ubGVuZ3RoLHI9bmV3IG4uY29uc3RydWN0b3IoaSk7Zm9yKGxldCBvPTAscz0wO3MhPT1pOysrbyl7bGV0IGE9ZVtvXSp0O2ZvcihsZXQgbD0wO2whPT10OysrbClyW3MrK109blthK2xdfXJldHVybiByfSxmbGF0dGVuSlNPTjpmdW5jdGlvbihuLHQsZSxpKXtsZXQgcj0xLG89blswXTtmb3IoO3ZvaWQgMCE9PW8mJnZvaWQgMD09PW9baV07KW89bltyKytdO2lmKHZvaWQgMD09PW8pcmV0dXJuO2xldCBzPW9baV07aWYodm9pZCAwIT09cylpZihBcnJheS5pc0FycmF5KHMpKWRve3M9b1tpXSx2b2lkIDAhPT1zJiYodC5wdXNoKG8udGltZSksZS5wdXNoLmFwcGx5KGUscykpLG89bltyKytdfXdoaWxlKHZvaWQgMCE9PW8pO2Vsc2UgaWYodm9pZCAwIT09cy50b0FycmF5KWRve3M9b1tpXSx2b2lkIDAhPT1zJiYodC5wdXNoKG8udGltZSkscy50b0FycmF5KGUsZS5sZW5ndGgpKSxvPW5bcisrXX13aGlsZSh2b2lkIDAhPT1vKTtlbHNlIGRve3M9b1tpXSx2b2lkIDAhPT1zJiYodC5wdXNoKG8udGltZSksZS5wdXNoKHMpKSxvPW5bcisrXX13aGlsZSh2b2lkIDAhPT1vKX0sc3ViY2xpcDpmdW5jdGlvbihuLHQsZSxpLHI9MzApe2xldCBvPW4uY2xvbmUoKTtvLm5hbWU9dDtsZXQgcz1bXTtmb3IobGV0IGw9MDtsPG8udHJhY2tzLmxlbmd0aDsrK2wpe2xldCBjPW8udHJhY2tzW2xdLHU9Yy5nZXRWYWx1ZVNpemUoKSxkPVtdLHA9W107Zm9yKGxldCBoPTA7aDxjLnRpbWVzLmxlbmd0aDsrK2gpe2xldCBmPWMudGltZXNbaF0qcjtpZighKGY8ZXx8Zj49aSkpe2QucHVzaChjLnRpbWVzW2hdKTtmb3IobGV0IG09MDttPHU7KyttKXAucHVzaChjLnZhbHVlc1toKnUrbV0pfX0wIT09ZC5sZW5ndGgmJihjLnRpbWVzPXFyLmNvbnZlcnRBcnJheShkLGMudGltZXMuY29uc3RydWN0b3IpLGMudmFsdWVzPXFyLmNvbnZlcnRBcnJheShwLGMudmFsdWVzLmNvbnN0cnVjdG9yKSxzLnB1c2goYykpfW8udHJhY2tzPXM7bGV0IGE9MS8wO2ZvcihsZXQgbD0wO2w8by50cmFja3MubGVuZ3RoOysrbClhPm8udHJhY2tzW2xdLnRpbWVzWzBdJiYoYT1vLnRyYWNrc1tsXS50aW1lc1swXSk7Zm9yKGxldCBsPTA7bDxvLnRyYWNrcy5sZW5ndGg7KytsKW8udHJhY2tzW2xdLnNoaWZ0KC0xKmEpO3JldHVybiBvLnJlc2V0RHVyYXRpb24oKSxvfSxtYWtlQ2xpcEFkZGl0aXZlOmZ1bmN0aW9uKG4sdD0wLGU9bixpPTMwKXtpPD0wJiYoaT0zMCk7bGV0IHI9ZS50cmFja3MubGVuZ3RoLG89dC9pO2ZvcihsZXQgcz0wO3M8cjsrK3Mpe2xldCBhPWUudHJhY2tzW3NdLGw9YS5WYWx1ZVR5cGVOYW1lO2lmKCJib29sIj09PWx8fCJzdHJpbmciPT09bCljb250aW51ZTtsZXQgYz1uLnRyYWNrcy5maW5kKGZ1bmN0aW9uKGcpe3JldHVybiBnLm5hbWU9PT1hLm5hbWUmJmcuVmFsdWVUeXBlTmFtZT09PWx9KTtpZih2b2lkIDA9PT1jKWNvbnRpbnVlO2xldCB1PTAsZD1hLmdldFZhbHVlU2l6ZSgpO2EuY3JlYXRlSW50ZXJwb2xhbnQuaXNJbnRlcnBvbGFudEZhY3RvcnlNZXRob2RHTFRGQ3ViaWNTcGxpbmUmJih1PWQvMyk7bGV0IHA9MCxoPWMuZ2V0VmFsdWVTaXplKCk7Yy5jcmVhdGVJbnRlcnBvbGFudC5pc0ludGVycG9sYW50RmFjdG9yeU1ldGhvZEdMVEZDdWJpY1NwbGluZSYmKHA9aC8zKTtsZXQgbSxmPWEudGltZXMubGVuZ3RoLTE7aWYobzw9YS50aW1lc1swXSltPXFyLmFycmF5U2xpY2UoYS52YWx1ZXMsdSxkLXUpO2Vsc2UgaWYobz49YS50aW1lc1tmXSl7bGV0IGc9ZipkK3U7bT1xci5hcnJheVNsaWNlKGEudmFsdWVzLGcsZytkLXUpfWVsc2V7bGV0IGc9YS5jcmVhdGVJbnRlcnBvbGFudCgpLGI9dSxEPWQtdTtnLmV2YWx1YXRlKG8pLG09cXIuYXJyYXlTbGljZShnLnJlc3VsdEJ1ZmZlcixiLEQpfSJxdWF0ZXJuaW9uIj09PWwmJihuZXcgcXMpLmZyb21BcnJheShtKS5ub3JtYWxpemUoKS5jb25qdWdhdGUoKS50b0FycmF5KG0pO2xldCB4PWMudGltZXMubGVuZ3RoO2ZvcihsZXQgZz0wO2c8eDsrK2cpe2xldCBiPWcqaCtwO2lmKCJxdWF0ZXJuaW9uIj09PWwpcXMubXVsdGlwbHlRdWF0ZXJuaW9uc0ZsYXQoYy52YWx1ZXMsYixtLDAsYy52YWx1ZXMsYik7ZWxzZXtsZXQgRD1oLTIqcDtmb3IobGV0IFQ9MDtUPEQ7KytUKWMudmFsdWVzW2IrVF0tPW1bVF19fX1yZXR1cm4gbi5ibGVuZE1vZGU9MjUwMSxufX0sVGQ9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscil7dGhpcy5wYXJhbWV0ZXJQb3NpdGlvbnM9dCx0aGlzLl9jYWNoZWRJbmRleD0wLHRoaXMucmVzdWx0QnVmZmVyPXZvaWQgMCE9PXI/cjpuZXcgZS5jb25zdHJ1Y3RvcihpKSx0aGlzLnNhbXBsZVZhbHVlcz1lLHRoaXMudmFsdWVTaXplPWksdGhpcy5zZXR0aW5ncz1udWxsLHRoaXMuRGVmYXVsdFNldHRpbmdzXz17fX1ldmFsdWF0ZSh0KXtsZXQgZT10aGlzLnBhcmFtZXRlclBvc2l0aW9ucyxpPXRoaXMuX2NhY2hlZEluZGV4LHI9ZVtpXSxvPWVbaS0xXTtlOnt0OntsZXQgcztuOntpOmlmKCEodDxyKSl7Zm9yKGxldCBhPWkrMjs7KXtpZih2b2lkIDA9PT1yKXtpZih0PG8pYnJlYWsgaTtyZXR1cm4gaT1lLmxlbmd0aCx0aGlzLl9jYWNoZWRJbmRleD1pLHRoaXMuYWZ0ZXJFbmRfKGktMSx0LG8pfWlmKGk9PT1hKWJyZWFrO2lmKG89cixyPWVbKytpXSx0PHIpYnJlYWsgdH1zPWUubGVuZ3RoO2JyZWFrIG59aWYodD49bylicmVhayBlO3tsZXQgYT1lWzFdO3Q8YSYmKGk9MixvPWEpO2ZvcihsZXQgbD1pLTI7Oyl7aWYodm9pZCAwPT09bylyZXR1cm4gdGhpcy5fY2FjaGVkSW5kZXg9MCx0aGlzLmJlZm9yZVN0YXJ0XygwLHQscik7aWYoaT09PWwpYnJlYWs7aWYocj1vLG89ZVstLWktMV0sdD49bylicmVhayB0fXM9aSxpPTB9fWZvcig7aTxzOyl7bGV0IGE9aStzPj4+MTt0PGVbYV0/cz1hOmk9YSsxfWlmKHI9ZVtpXSxvPWVbaS0xXSx2b2lkIDA9PT1vKXJldHVybiB0aGlzLl9jYWNoZWRJbmRleD0wLHRoaXMuYmVmb3JlU3RhcnRfKDAsdCxyKTtpZih2b2lkIDA9PT1yKXJldHVybiBpPWUubGVuZ3RoLHRoaXMuX2NhY2hlZEluZGV4PWksdGhpcy5hZnRlckVuZF8oaS0xLG8sdCl9dGhpcy5fY2FjaGVkSW5kZXg9aSx0aGlzLmludGVydmFsQ2hhbmdlZF8oaSxvLHIpfXJldHVybiB0aGlzLmludGVycG9sYXRlXyhpLG8sdCxyKX1nZXRTZXR0aW5nc18oKXtyZXR1cm4gdGhpcy5zZXR0aW5nc3x8dGhpcy5EZWZhdWx0U2V0dGluZ3NffWNvcHlTYW1wbGVWYWx1ZV8odCl7bGV0IGU9dGhpcy5yZXN1bHRCdWZmZXIsaT10aGlzLnNhbXBsZVZhbHVlcyxyPXRoaXMudmFsdWVTaXplLG89dCpyO2ZvcihsZXQgcz0wO3MhPT1yOysrcyllW3NdPWlbbytzXTtyZXR1cm4gZX1pbnRlcnBvbGF0ZV8oKXt0aHJvdyBuZXcgRXJyb3IoImNhbGwgdG8gYWJzdHJhY3QgbWV0aG9kIil9aW50ZXJ2YWxDaGFuZ2VkXygpe319O1RkLnByb3RvdHlwZS5iZWZvcmVTdGFydF89VGQucHJvdG90eXBlLmNvcHlTYW1wbGVWYWx1ZV8sVGQucHJvdG90eXBlLmFmdGVyRW5kXz1UZC5wcm90b3R5cGUuY29weVNhbXBsZVZhbHVlXzt2YXIgRzg9Y2xhc3MgZXh0ZW5kcyBUZHtjb25zdHJ1Y3Rvcih0LGUsaSxyKXtzdXBlcih0LGUsaSxyKSx0aGlzLl93ZWlnaHRQcmV2PS0wLHRoaXMuX29mZnNldFByZXY9LTAsdGhpcy5fd2VpZ2h0TmV4dD0tMCx0aGlzLl9vZmZzZXROZXh0PS0wLHRoaXMuRGVmYXVsdFNldHRpbmdzXz17ZW5kaW5nU3RhcnQ6b2IsZW5kaW5nRW5kOm9ifX1pbnRlcnZhbENoYW5nZWRfKHQsZSxpKXtsZXQgcj10aGlzLnBhcmFtZXRlclBvc2l0aW9ucyxvPXQtMixzPXQrMSxhPXJbb10sbD1yW3NdO2lmKHZvaWQgMD09PWEpc3dpdGNoKHRoaXMuZ2V0U2V0dGluZ3NfKCkuZW5kaW5nU3RhcnQpe2Nhc2Ugc2I6bz10LGE9MiplLWk7YnJlYWs7Y2FzZSAyNDAyOm89ci5sZW5ndGgtMixhPWUrcltvXS1yW28rMV07YnJlYWs7ZGVmYXVsdDpvPXQsYT1pfWlmKHZvaWQgMD09PWwpc3dpdGNoKHRoaXMuZ2V0U2V0dGluZ3NfKCkuZW5kaW5nRW5kKXtjYXNlIHNiOnM9dCxsPTIqaS1lO2JyZWFrO2Nhc2UgMjQwMjpzPTEsbD1pK3JbMV0tclswXTticmVhaztkZWZhdWx0OnM9dC0xLGw9ZX1sZXQgYz0uNSooaS1lKSx1PXRoaXMudmFsdWVTaXplO3RoaXMuX3dlaWdodFByZXY9Yy8oZS1hKSx0aGlzLl93ZWlnaHROZXh0PWMvKGwtaSksdGhpcy5fb2Zmc2V0UHJldj1vKnUsdGhpcy5fb2Zmc2V0TmV4dD1zKnV9aW50ZXJwb2xhdGVfKHQsZSxpLHIpe2xldCBvPXRoaXMucmVzdWx0QnVmZmVyLHM9dGhpcy5zYW1wbGVWYWx1ZXMsYT10aGlzLnZhbHVlU2l6ZSxsPXQqYSxjPWwtYSx1PXRoaXMuX29mZnNldFByZXYsZD10aGlzLl9vZmZzZXROZXh0LHA9dGhpcy5fd2VpZ2h0UHJldixoPXRoaXMuX3dlaWdodE5leHQsZj0oaS1lKS8oci1lKSxtPWYqZix4PW0qZixnPS1wKngrMipwKm0tcCpmLGI9KDErcCkqeCsoLTEuNS0yKnApKm0rKC0uNStwKSpmKzEsRD0oLTEtaCkqeCsoMS41K2gpKm0rLjUqZixUPWgqeC1oKm07Zm9yKGxldCBrPTA7ayE9PWE7KytrKW9ba109ZypzW3Ura10rYipzW2Mra10rRCpzW2wra10rVCpzW2Qra107cmV0dXJuIG99fSxNaz1jbGFzcyBleHRlbmRzIFRke2NvbnN0cnVjdG9yKHQsZSxpLHIpe3N1cGVyKHQsZSxpLHIpfWludGVycG9sYXRlXyh0LGUsaSxyKXtsZXQgbz10aGlzLnJlc3VsdEJ1ZmZlcixzPXRoaXMuc2FtcGxlVmFsdWVzLGE9dGhpcy52YWx1ZVNpemUsbD10KmEsYz1sLWEsdT0oaS1lKS8oci1lKSxkPTEtdTtmb3IobGV0IHA9MDtwIT09YTsrK3Apb1twXT1zW2MrcF0qZCtzW2wrcF0qdTtyZXR1cm4gb319LFc4PWNsYXNzIGV4dGVuZHMgVGR7Y29uc3RydWN0b3IodCxlLGkscil7c3VwZXIodCxlLGkscil9aW50ZXJwb2xhdGVfKHQpe3JldHVybiB0aGlzLmNvcHlTYW1wbGVWYWx1ZV8odC0xKX19LGhjPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe2lmKHZvaWQgMD09PXQpdGhyb3cgbmV3IEVycm9yKCJUSFJFRS5LZXlmcmFtZVRyYWNrOiB0cmFjayBuYW1lIGlzIHVuZGVmaW5lZCIpO2lmKHZvaWQgMD09PWV8fDA9PT1lLmxlbmd0aCl0aHJvdyBuZXcgRXJyb3IoIlRIUkVFLktleWZyYW1lVHJhY2s6IG5vIGtleWZyYW1lcyBpbiB0cmFjayBuYW1lZCAiK3QpO3RoaXMubmFtZT10LHRoaXMudGltZXM9cXIuY29udmVydEFycmF5KGUsdGhpcy5UaW1lQnVmZmVyVHlwZSksdGhpcy52YWx1ZXM9cXIuY29udmVydEFycmF5KGksdGhpcy5WYWx1ZUJ1ZmZlclR5cGUpLHRoaXMuc2V0SW50ZXJwb2xhdGlvbihyfHx0aGlzLkRlZmF1bHRJbnRlcnBvbGF0aW9uKX1zdGF0aWMgdG9KU09OKHQpe2xldCBpLGU9dC5jb25zdHJ1Y3RvcjtpZihlLnRvSlNPTiE9PXRoaXMudG9KU09OKWk9ZS50b0pTT04odCk7ZWxzZXtpPXtuYW1lOnQubmFtZSx0aW1lczpxci5jb252ZXJ0QXJyYXkodC50aW1lcyxBcnJheSksdmFsdWVzOnFyLmNvbnZlcnRBcnJheSh0LnZhbHVlcyxBcnJheSl9O2xldCByPXQuZ2V0SW50ZXJwb2xhdGlvbigpO3IhPT10LkRlZmF1bHRJbnRlcnBvbGF0aW9uJiYoaS5pbnRlcnBvbGF0aW9uPXIpfXJldHVybiBpLnR5cGU9dC5WYWx1ZVR5cGVOYW1lLGl9SW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kRGlzY3JldGUodCl7cmV0dXJuIG5ldyBXOCh0aGlzLnRpbWVzLHRoaXMudmFsdWVzLHRoaXMuZ2V0VmFsdWVTaXplKCksdCl9SW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kTGluZWFyKHQpe3JldHVybiBuZXcgTWsodGhpcy50aW1lcyx0aGlzLnZhbHVlcyx0aGlzLmdldFZhbHVlU2l6ZSgpLHQpfUludGVycG9sYW50RmFjdG9yeU1ldGhvZFNtb290aCh0KXtyZXR1cm4gbmV3IEc4KHRoaXMudGltZXMsdGhpcy52YWx1ZXMsdGhpcy5nZXRWYWx1ZVNpemUoKSx0KX1zZXRJbnRlcnBvbGF0aW9uKHQpe2xldCBlO3N3aXRjaCh0KXtjYXNlIDIzMDA6ZT10aGlzLkludGVycG9sYW50RmFjdG9yeU1ldGhvZERpc2NyZXRlO2JyZWFrO2Nhc2UgMjMwMTplPXRoaXMuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kTGluZWFyO2JyZWFrO2Nhc2UgMjMwMjplPXRoaXMuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kU21vb3RofWlmKHZvaWQgMD09PWUpe2xldCBpPSJ1bnN1cHBvcnRlZCBpbnRlcnBvbGF0aW9uIGZvciAiK3RoaXMuVmFsdWVUeXBlTmFtZSsiIGtleWZyYW1lIHRyYWNrIG5hbWVkICIrdGhpcy5uYW1lO2lmKHZvaWQgMD09PXRoaXMuY3JlYXRlSW50ZXJwb2xhbnQpe2lmKHQ9PT10aGlzLkRlZmF1bHRJbnRlcnBvbGF0aW9uKXRocm93IG5ldyBFcnJvcihpKTt0aGlzLnNldEludGVycG9sYXRpb24odGhpcy5EZWZhdWx0SW50ZXJwb2xhdGlvbil9cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuS2V5ZnJhbWVUcmFjazoiLGkpLHRoaXN9cmV0dXJuIHRoaXMuY3JlYXRlSW50ZXJwb2xhbnQ9ZSx0aGlzfWdldEludGVycG9sYXRpb24oKXtzd2l0Y2godGhpcy5jcmVhdGVJbnRlcnBvbGFudCl7Y2FzZSB0aGlzLkludGVycG9sYW50RmFjdG9yeU1ldGhvZERpc2NyZXRlOnJldHVybiAyMzAwO2Nhc2UgdGhpcy5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2RMaW5lYXI6cmV0dXJuIDIzMDE7Y2FzZSB0aGlzLkludGVycG9sYW50RmFjdG9yeU1ldGhvZFNtb290aDpyZXR1cm4gMjMwMn19Z2V0VmFsdWVTaXplKCl7cmV0dXJuIHRoaXMudmFsdWVzLmxlbmd0aC90aGlzLnRpbWVzLmxlbmd0aH1zaGlmdCh0KXtpZigwIT09dCl7bGV0IGU9dGhpcy50aW1lcztmb3IobGV0IGk9MCxyPWUubGVuZ3RoO2khPT1yOysraSllW2ldKz10fXJldHVybiB0aGlzfXNjYWxlKHQpe2lmKDEhPT10KXtsZXQgZT10aGlzLnRpbWVzO2ZvcihsZXQgaT0wLHI9ZS5sZW5ndGg7aSE9PXI7KytpKWVbaV0qPXR9cmV0dXJuIHRoaXN9dHJpbSh0LGUpe2xldCBpPXRoaXMudGltZXMscj1pLmxlbmd0aCxvPTAscz1yLTE7Zm9yKDtvIT09ciYmaVtvXTx0OykrK287Zm9yKDstMSE9PXMmJmlbc10+ZTspLS1zO2lmKCsrcywwIT09b3x8cyE9PXIpe28+PXMmJihzPU1hdGgubWF4KHMsMSksbz1zLTEpO2xldCBhPXRoaXMuZ2V0VmFsdWVTaXplKCk7dGhpcy50aW1lcz1xci5hcnJheVNsaWNlKGksbyxzKSx0aGlzLnZhbHVlcz1xci5hcnJheVNsaWNlKHRoaXMudmFsdWVzLG8qYSxzKmEpfXJldHVybiB0aGlzfXZhbGlkYXRlKCl7bGV0IHQ9ITAsZT10aGlzLmdldFZhbHVlU2l6ZSgpO2UtTWF0aC5mbG9vcihlKSE9MCYmKGNvbnNvbGUuZXJyb3IoIlRIUkVFLktleWZyYW1lVHJhY2s6IEludmFsaWQgdmFsdWUgc2l6ZSBpbiB0cmFjay4iLHRoaXMpLHQ9ITEpO2xldCBpPXRoaXMudGltZXMscj10aGlzLnZhbHVlcyxvPWkubGVuZ3RoOzA9PT1vJiYoY29uc29sZS5lcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogVHJhY2sgaXMgZW1wdHkuIix0aGlzKSx0PSExKTtsZXQgcz1udWxsO2ZvcihsZXQgYT0wO2EhPT1vO2ErKyl7bGV0IGw9aVthXTtpZigibnVtYmVyIj09dHlwZW9mIGwmJmlzTmFOKGwpKXtjb25zb2xlLmVycm9yKCJUSFJFRS5LZXlmcmFtZVRyYWNrOiBUaW1lIGlzIG5vdCBhIHZhbGlkIG51bWJlci4iLHRoaXMsYSxsKSx0PSExO2JyZWFrfWlmKG51bGwhPT1zJiZzPmwpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLktleWZyYW1lVHJhY2s6IE91dCBvZiBvcmRlciBrZXlzLiIsdGhpcyxhLGwscyksdD0hMTticmVha31zPWx9aWYodm9pZCAwIT09ciYmcXIuaXNUeXBlZEFycmF5KHIpKWZvcihsZXQgYT0wLGw9ci5sZW5ndGg7YSE9PWw7KythKXtsZXQgYz1yW2FdO2lmKGlzTmFOKGMpKXtjb25zb2xlLmVycm9yKCJUSFJFRS5LZXlmcmFtZVRyYWNrOiBWYWx1ZSBpcyBub3QgYSB2YWxpZCBudW1iZXIuIix0aGlzLGEsYyksdD0hMTticmVha319cmV0dXJuIHR9b3B0aW1pemUoKXtsZXQgdD1xci5hcnJheVNsaWNlKHRoaXMudGltZXMpLGU9cXIuYXJyYXlTbGljZSh0aGlzLnZhbHVlcyksaT10aGlzLmdldFZhbHVlU2l6ZSgpLHI9MjMwMj09PXRoaXMuZ2V0SW50ZXJwb2xhdGlvbigpLG89dC5sZW5ndGgtMSxzPTE7Zm9yKGxldCBhPTE7YTxvOysrYSl7bGV0IGw9ITEsYz10W2FdO2lmKGMhPT10W2ErMV0mJigxIT09YXx8YyE9PXRbMF0pKWlmKHIpbD0hMDtlbHNle2xldCBkPWEqaSxwPWQtaSxoPWQraTtmb3IobGV0IGY9MDtmIT09aTsrK2Ype2xldCBtPWVbZCtmXTtpZihtIT09ZVtwK2ZdfHxtIT09ZVtoK2ZdKXtsPSEwO2JyZWFrfX19aWYobCl7aWYoYSE9PXMpe3Rbc109dFthXTtsZXQgZD1hKmkscD1zKmk7Zm9yKGxldCBoPTA7aCE9PWk7KytoKWVbcCtoXT1lW2QraF19KytzfX1pZihvPjApe3Rbc109dFtvXTtmb3IobGV0IGE9byppLGw9cyppLGM9MDtjIT09aTsrK2MpZVtsK2NdPWVbYStjXTsrK3N9cmV0dXJuIHMhPT10Lmxlbmd0aD8odGhpcy50aW1lcz1xci5hcnJheVNsaWNlKHQsMCxzKSx0aGlzLnZhbHVlcz1xci5hcnJheVNsaWNlKGUsMCxzKmkpKToodGhpcy50aW1lcz10LHRoaXMudmFsdWVzPWUpLHRoaXN9Y2xvbmUoKXtsZXQgdD1xci5hcnJheVNsaWNlKHRoaXMudGltZXMsMCksZT1xci5hcnJheVNsaWNlKHRoaXMudmFsdWVzLDApLHI9bmV3KDAsdGhpcy5jb25zdHJ1Y3RvcikodGhpcy5uYW1lLHQsZSk7cmV0dXJuIHIuY3JlYXRlSW50ZXJwb2xhbnQ9dGhpcy5jcmVhdGVJbnRlcnBvbGFudCxyfX07aGMucHJvdG90eXBlLlRpbWVCdWZmZXJUeXBlPUZsb2F0MzJBcnJheSxoYy5wcm90b3R5cGUuVmFsdWVCdWZmZXJUeXBlPUZsb2F0MzJBcnJheSxoYy5wcm90b3R5cGUuRGVmYXVsdEludGVycG9sYXRpb249MjMwMTt2YXIgRWY9Y2xhc3MgZXh0ZW5kcyBoY3t9O0VmLnByb3RvdHlwZS5WYWx1ZVR5cGVOYW1lPSJib29sIixFZi5wcm90b3R5cGUuVmFsdWVCdWZmZXJUeXBlPUFycmF5LEVmLnByb3RvdHlwZS5EZWZhdWx0SW50ZXJwb2xhdGlvbj0yMzAwLEVmLnByb3RvdHlwZS5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2RMaW5lYXI9dm9pZCAwLEVmLnByb3RvdHlwZS5JbnRlcnBvbGFudEZhY3RvcnlNZXRob2RTbW9vdGg9dm9pZCAwO3ZhciB3az1jbGFzcyBleHRlbmRzIGhje307d2sucHJvdG90eXBlLlZhbHVlVHlwZU5hbWU9ImNvbG9yIjt2YXIgQ2I9Y2xhc3MgZXh0ZW5kcyBoY3t9O0NiLnByb3RvdHlwZS5WYWx1ZVR5cGVOYW1lPSJudW1iZXIiO3ZhciBxOD1jbGFzcyBleHRlbmRzIFRke2NvbnN0cnVjdG9yKHQsZSxpLHIpe3N1cGVyKHQsZSxpLHIpfWludGVycG9sYXRlXyh0LGUsaSxyKXtsZXQgbz10aGlzLnJlc3VsdEJ1ZmZlcixzPXRoaXMuc2FtcGxlVmFsdWVzLGE9dGhpcy52YWx1ZVNpemUsbD0oaS1lKS8oci1lKSxjPXQqYTtmb3IobGV0IHU9YythO2MhPT11O2MrPTQpcXMuc2xlcnBGbGF0KG8sMCxzLGMtYSxzLGMsbCk7cmV0dXJuIG99fSxLZz1jbGFzcyBleHRlbmRzIGhje0ludGVycG9sYW50RmFjdG9yeU1ldGhvZExpbmVhcih0KXtyZXR1cm4gbmV3IHE4KHRoaXMudGltZXMsdGhpcy52YWx1ZXMsdGhpcy5nZXRWYWx1ZVNpemUoKSx0KX19O0tnLnByb3RvdHlwZS5WYWx1ZVR5cGVOYW1lPSJxdWF0ZXJuaW9uIixLZy5wcm90b3R5cGUuRGVmYXVsdEludGVycG9sYXRpb249MjMwMSxLZy5wcm90b3R5cGUuSW50ZXJwb2xhbnRGYWN0b3J5TWV0aG9kU21vb3RoPXZvaWQgMDt2YXIgVGY9Y2xhc3MgZXh0ZW5kcyBoY3t9O1RmLnByb3RvdHlwZS5WYWx1ZVR5cGVOYW1lPSJzdHJpbmciLFRmLnByb3RvdHlwZS5WYWx1ZUJ1ZmZlclR5cGU9QXJyYXksVGYucHJvdG90eXBlLkRlZmF1bHRJbnRlcnBvbGF0aW9uPTIzMDAsVGYucHJvdG90eXBlLkludGVycG9sYW50RmFjdG9yeU1ldGhvZExpbmVhcj12b2lkIDAsVGYucHJvdG90eXBlLkludGVycG9sYW50RmFjdG9yeU1ldGhvZFNtb290aD12b2lkIDA7dmFyIE1iPWNsYXNzIGV4dGVuZHMgaGN7fTtNYi5wcm90b3R5cGUuVmFsdWVUeXBlTmFtZT0idmVjdG9yIjt2YXIgU2s9Y2xhc3N7Y29uc3RydWN0b3IodCxlPS0xLGkscj0yNTAwKXt0aGlzLm5hbWU9dCx0aGlzLnRyYWNrcz1pLHRoaXMuZHVyYXRpb249ZSx0aGlzLmJsZW5kTW9kZT1yLHRoaXMudXVpZD1kdSgpLHRoaXMuZHVyYXRpb248MCYmdGhpcy5yZXNldER1cmF0aW9uKCl9c3RhdGljIHBhcnNlKHQpe2xldCBlPVtdLGk9dC50cmFja3Mscj0xLyh0LmZwc3x8MSk7Zm9yKGxldCBzPTAsYT1pLmxlbmd0aDtzIT09YTsrK3MpZS5wdXNoKFk5ZShpW3NdKS5zY2FsZShyKSk7bGV0IG89bmV3IHRoaXModC5uYW1lLHQuZHVyYXRpb24sZSx0LmJsZW5kTW9kZSk7cmV0dXJuIG8udXVpZD10LnV1aWQsb31zdGF0aWMgdG9KU09OKHQpe2xldCBlPVtdLGk9dC50cmFja3Mscj17bmFtZTp0Lm5hbWUsZHVyYXRpb246dC5kdXJhdGlvbix0cmFja3M6ZSx1dWlkOnQudXVpZCxibGVuZE1vZGU6dC5ibGVuZE1vZGV9O2ZvcihsZXQgbz0wLHM9aS5sZW5ndGg7byE9PXM7KytvKWUucHVzaChoYy50b0pTT04oaVtvXSkpO3JldHVybiByfXN0YXRpYyBDcmVhdGVGcm9tTW9ycGhUYXJnZXRTZXF1ZW5jZSh0LGUsaSxyKXtsZXQgbz1lLmxlbmd0aCxzPVtdO2ZvcihsZXQgYT0wO2E8bzthKyspe2xldCBsPVtdLGM9W107bC5wdXNoKChhK28tMSklbyxhLChhKzEpJW8pLGMucHVzaCgwLDEsMCk7bGV0IHU9cXIuZ2V0S2V5ZnJhbWVPcmRlcihsKTtsPXFyLnNvcnRlZEFycmF5KGwsMSx1KSxjPXFyLnNvcnRlZEFycmF5KGMsMSx1KSwhciYmMD09PWxbMF0mJihsLnB1c2gobyksYy5wdXNoKGNbMF0pKSxzLnB1c2gobmV3IENiKCIubW9ycGhUYXJnZXRJbmZsdWVuY2VzWyIrZVthXS5uYW1lKyJdIixsLGMpLnNjYWxlKDEvaSkpfXJldHVybiBuZXcgdGhpcyh0LC0xLHMpfXN0YXRpYyBmaW5kQnlOYW1lKHQsZSl7bGV0IGk9dDtpZighQXJyYXkuaXNBcnJheSh0KSl7bGV0IHI9dDtpPXIuZ2VvbWV0cnkmJnIuZ2VvbWV0cnkuYW5pbWF0aW9uc3x8ci5hbmltYXRpb25zfWZvcihsZXQgcj0wO3I8aS5sZW5ndGg7cisrKWlmKGlbcl0ubmFtZT09PWUpcmV0dXJuIGlbcl07cmV0dXJuIG51bGx9c3RhdGljIENyZWF0ZUNsaXBzRnJvbU1vcnBoVGFyZ2V0U2VxdWVuY2VzKHQsZSxpKXtsZXQgcj17fSxvPS9eKFtcdy1dKj8pKFtcZF0rKSQvO2ZvcihsZXQgYT0wLGw9dC5sZW5ndGg7YTxsO2ErKyl7bGV0IGM9dFthXSx1PWMubmFtZS5tYXRjaChvKTtpZih1JiZ1Lmxlbmd0aD4xKXtsZXQgZD11WzFdLHA9cltkXTtwfHwocltkXT1wPVtdKSxwLnB1c2goYyl9fWxldCBzPVtdO2ZvcihsZXQgYSBpbiByKXMucHVzaCh0aGlzLkNyZWF0ZUZyb21Nb3JwaFRhcmdldFNlcXVlbmNlKGEsclthXSxlLGkpKTtyZXR1cm4gc31zdGF0aWMgcGFyc2VBbmltYXRpb24odCxlKXtpZighdClyZXR1cm4gY29uc29sZS5lcnJvcigiVEhSRUUuQW5pbWF0aW9uQ2xpcDogTm8gYW5pbWF0aW9uIGluIEpTT05Mb2FkZXIgZGF0YS4iKSxudWxsO2xldCBpPWZ1bmN0aW9uKGQscCxoLGYsbSl7aWYoMCE9PWgubGVuZ3RoKXtsZXQgeD1bXSxnPVtdO3FyLmZsYXR0ZW5KU09OKGgseCxnLGYpLDAhPT14Lmxlbmd0aCYmbS5wdXNoKG5ldyBkKHAseCxnKSl9fSxyPVtdLG89dC5uYW1lfHwiZGVmYXVsdCIscz10LmZwc3x8MzAsYT10LmJsZW5kTW9kZSxsPXQubGVuZ3RofHwtMSxjPXQuaGllcmFyY2h5fHxbXTtmb3IobGV0IGQ9MDtkPGMubGVuZ3RoO2QrKyl7bGV0IHA9Y1tkXS5rZXlzO2lmKHAmJjAhPT1wLmxlbmd0aClpZihwWzBdLm1vcnBoVGFyZ2V0cyl7bGV0IGYsaD17fTtmb3IoZj0wO2Y8cC5sZW5ndGg7ZisrKWlmKHBbZl0ubW9ycGhUYXJnZXRzKWZvcihsZXQgbT0wO208cFtmXS5tb3JwaFRhcmdldHMubGVuZ3RoO20rKyloW3BbZl0ubW9ycGhUYXJnZXRzW21dXT0tMTtmb3IobGV0IG0gaW4gaCl7bGV0IHg9W10sZz1bXTtmb3IobGV0IGI9MDtiIT09cFtmXS5tb3JwaFRhcmdldHMubGVuZ3RoOysrYil7bGV0IEQ9cFtmXTt4LnB1c2goRC50aW1lKSxnLnB1c2goRC5tb3JwaFRhcmdldD09PW0/MTowKX1yLnB1c2gobmV3IENiKCIubW9ycGhUYXJnZXRJbmZsdWVuY2VbIittKyJdIix4LGcpKX1sPWgubGVuZ3RoKihzfHwxKX1lbHNle2xldCBoPSIuYm9uZXNbIitlW2RdLm5hbWUrIl0iO2koTWIsaCsiLnBvc2l0aW9uIixwLCJwb3MiLHIpLGkoS2csaCsiLnF1YXRlcm5pb24iLHAsInJvdCIsciksaShNYixoKyIuc2NhbGUiLHAsInNjbCIscil9fXJldHVybiAwPT09ci5sZW5ndGg/bnVsbDpuZXcgdGhpcyhvLGwscixhKX1yZXNldER1cmF0aW9uKCl7bGV0IGU9MDtmb3IobGV0IGk9MCxyPXRoaXMudHJhY2tzLmxlbmd0aDtpIT09cjsrK2kpe2xldCBvPXRoaXMudHJhY2tzW2ldO2U9TWF0aC5tYXgoZSxvLnRpbWVzW28udGltZXMubGVuZ3RoLTFdKX1yZXR1cm4gdGhpcy5kdXJhdGlvbj1lLHRoaXN9dHJpbSgpe2ZvcihsZXQgdD0wO3Q8dGhpcy50cmFja3MubGVuZ3RoO3QrKyl0aGlzLnRyYWNrc1t0XS50cmltKDAsdGhpcy5kdXJhdGlvbik7cmV0dXJuIHRoaXN9dmFsaWRhdGUoKXtsZXQgdD0hMDtmb3IobGV0IGU9MDtlPHRoaXMudHJhY2tzLmxlbmd0aDtlKyspdD10JiZ0aGlzLnRyYWNrc1tlXS52YWxpZGF0ZSgpO3JldHVybiB0fW9wdGltaXplKCl7Zm9yKGxldCB0PTA7dDx0aGlzLnRyYWNrcy5sZW5ndGg7dCsrKXRoaXMudHJhY2tzW3RdLm9wdGltaXplKCk7cmV0dXJuIHRoaXN9Y2xvbmUoKXtsZXQgdD1bXTtmb3IobGV0IGU9MDtlPHRoaXMudHJhY2tzLmxlbmd0aDtlKyspdC5wdXNoKHRoaXMudHJhY2tzW2VdLmNsb25lKCkpO3JldHVybiBuZXcgdGhpcy5jb25zdHJ1Y3Rvcih0aGlzLm5hbWUsdGhpcy5kdXJhdGlvbix0LHRoaXMuYmxlbmRNb2RlKX10b0pTT04oKXtyZXR1cm4gdGhpcy5jb25zdHJ1Y3Rvci50b0pTT04odGhpcyl9fTtmdW5jdGlvbiBZOWUobil7aWYodm9pZCAwPT09bi50eXBlKXRocm93IG5ldyBFcnJvcigiVEhSRUUuS2V5ZnJhbWVUcmFjazogdHJhY2sgdHlwZSB1bmRlZmluZWQsIGNhbiBub3QgcGFyc2UiKTtsZXQgdD1mdW5jdGlvbihuKXtzd2l0Y2gobi50b0xvd2VyQ2FzZSgpKXtjYXNlInNjYWxhciI6Y2FzZSJkb3VibGUiOmNhc2UiZmxvYXQiOmNhc2UibnVtYmVyIjpjYXNlImludGVnZXIiOnJldHVybiBDYjtjYXNlInZlY3RvciI6Y2FzZSJ2ZWN0b3IyIjpjYXNlInZlY3RvcjMiOmNhc2UidmVjdG9yNCI6cmV0dXJuIE1iO2Nhc2UiY29sb3IiOnJldHVybiB3aztjYXNlInF1YXRlcm5pb24iOnJldHVybiBLZztjYXNlImJvb2wiOmNhc2UiYm9vbGVhbiI6cmV0dXJuIEVmO2Nhc2Uic3RyaW5nIjpyZXR1cm4gVGZ9dGhyb3cgbmV3IEVycm9yKCJUSFJFRS5LZXlmcmFtZVRyYWNrOiBVbnN1cHBvcnRlZCB0eXBlTmFtZTogIituKX0obi50eXBlKTtpZih2b2lkIDA9PT1uLnRpbWVzKXtsZXQgZT1bXSxpPVtdO3FyLmZsYXR0ZW5KU09OKG4ua2V5cyxlLGksInZhbHVlIiksbi50aW1lcz1lLG4udmFsdWVzPWl9cmV0dXJuIHZvaWQgMCE9PXQucGFyc2U/dC5wYXJzZShuKTpuZXcgdChuLm5hbWUsbi50aW1lcyxuLnZhbHVlcyxuLmludGVycG9sYXRpb24pfXZhciB3Yj17ZW5hYmxlZDohMSxmaWxlczp7fSxhZGQ6ZnVuY3Rpb24obix0KXshMSE9PXRoaXMuZW5hYmxlZCYmKHRoaXMuZmlsZXNbbl09dCl9LGdldDpmdW5jdGlvbihuKXtpZighMSE9PXRoaXMuZW5hYmxlZClyZXR1cm4gdGhpcy5maWxlc1tuXX0scmVtb3ZlOmZ1bmN0aW9uKG4pe2RlbGV0ZSB0aGlzLmZpbGVzW25dfSxjbGVhcjpmdW5jdGlvbigpe3RoaXMuZmlsZXM9e319fSxYOWU9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpKXtsZXQgbCxyPXRoaXMsbz0hMSxzPTAsYT0wLGM9W107dGhpcy5vblN0YXJ0PXZvaWQgMCx0aGlzLm9uTG9hZD10LHRoaXMub25Qcm9ncmVzcz1lLHRoaXMub25FcnJvcj1pLHRoaXMuaXRlbVN0YXJ0PWZ1bmN0aW9uKHUpe2ErKywhMT09PW8mJnZvaWQgMCE9PXIub25TdGFydCYmci5vblN0YXJ0KHUscyxhKSxvPSEwfSx0aGlzLml0ZW1FbmQ9ZnVuY3Rpb24odSl7cysrLHZvaWQgMCE9PXIub25Qcm9ncmVzcyYmci5vblByb2dyZXNzKHUscyxhKSxzPT09YSYmKG89ITEsdm9pZCAwIT09ci5vbkxvYWQmJnIub25Mb2FkKCkpfSx0aGlzLml0ZW1FcnJvcj1mdW5jdGlvbih1KXt2b2lkIDAhPT1yLm9uRXJyb3ImJnIub25FcnJvcih1KX0sdGhpcy5yZXNvbHZlVVJMPWZ1bmN0aW9uKHUpe3JldHVybiBsP2wodSk6dX0sdGhpcy5zZXRVUkxNb2RpZmllcj1mdW5jdGlvbih1KXtyZXR1cm4gbD11LHRoaXN9LHRoaXMuYWRkSGFuZGxlcj1mdW5jdGlvbih1LGQpe3JldHVybiBjLnB1c2godSxkKSx0aGlzfSx0aGlzLnJlbW92ZUhhbmRsZXI9ZnVuY3Rpb24odSl7bGV0IGQ9Yy5pbmRleE9mKHUpO3JldHVybi0xIT09ZCYmYy5zcGxpY2UoZCwyKSx0aGlzfSx0aGlzLmdldEhhbmRsZXI9ZnVuY3Rpb24odSl7Zm9yKGxldCBkPTAscD1jLmxlbmd0aDtkPHA7ZCs9Mil7bGV0IGg9Y1tkXSxmPWNbZCsxXTtpZihoLmdsb2JhbCYmKGgubGFzdEluZGV4PTApLGgudGVzdCh1KSlyZXR1cm4gZn1yZXR1cm4gbnVsbH19fSxEZD1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLm1hbmFnZXI9dm9pZCAwIT09dD90Olg5ZSx0aGlzLmNyb3NzT3JpZ2luPSJhbm9ueW1vdXMiLHRoaXMud2l0aENyZWRlbnRpYWxzPSExLHRoaXMucGF0aD0iIix0aGlzLnJlc291cmNlUGF0aD0iIix0aGlzLnJlcXVlc3RIZWFkZXI9e319bG9hZCgpe31sb2FkQXN5bmModCxlKXtsZXQgaT10aGlzO3JldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbihyLG8pe2kubG9hZCh0LHIsZSxvKX0pfXBhcnNlKCl7fXNldENyb3NzT3JpZ2luKHQpe3JldHVybiB0aGlzLmNyb3NzT3JpZ2luPXQsdGhpc31zZXRXaXRoQ3JlZGVudGlhbHModCl7cmV0dXJuIHRoaXMud2l0aENyZWRlbnRpYWxzPXQsdGhpc31zZXRQYXRoKHQpe3JldHVybiB0aGlzLnBhdGg9dCx0aGlzfXNldFJlc291cmNlUGF0aCh0KXtyZXR1cm4gdGhpcy5yZXNvdXJjZVBhdGg9dCx0aGlzfXNldFJlcXVlc3RIZWFkZXIodCl7cmV0dXJuIHRoaXMucmVxdWVzdEhlYWRlcj10LHRoaXN9fSxTcD17fSxYOD1jbGFzcyBleHRlbmRzIERke2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpfWxvYWQodCxlLGkscil7dm9pZCAwPT09dCYmKHQ9IiIpLHZvaWQgMCE9PXRoaXMucGF0aCYmKHQ9dGhpcy5wYXRoK3QpLHQ9dGhpcy5tYW5hZ2VyLnJlc29sdmVVUkwodCk7bGV0IG89d2IuZ2V0KHQpO2lmKHZvaWQgMCE9PW8pcmV0dXJuIHRoaXMubWFuYWdlci5pdGVtU3RhcnQodCksc2V0VGltZW91dCgoKT0+e2UmJmUobyksdGhpcy5tYW5hZ2VyLml0ZW1FbmQodCl9LDApLG87aWYodm9pZCAwIT09U3BbdF0pcmV0dXJuIHZvaWQgU3BbdF0ucHVzaCh7b25Mb2FkOmUsb25Qcm9ncmVzczppLG9uRXJyb3I6cn0pO1NwW3RdPVtdLFNwW3RdLnB1c2goe29uTG9hZDplLG9uUHJvZ3Jlc3M6aSxvbkVycm9yOnJ9KTtsZXQgcz1uZXcgUmVxdWVzdCh0LHtoZWFkZXJzOm5ldyBIZWFkZXJzKHRoaXMucmVxdWVzdEhlYWRlciksY3JlZGVudGlhbHM6dGhpcy53aXRoQ3JlZGVudGlhbHM/ImluY2x1ZGUiOiJzYW1lLW9yaWdpbiJ9KSxhPXRoaXMubWltZVR5cGUsbD10aGlzLnJlc3BvbnNlVHlwZTtmZXRjaChzKS50aGVuKGM9PntpZigyMDA9PT1jLnN0YXR1c3x8MD09PWMuc3RhdHVzKXtpZigwPT09Yy5zdGF0dXMmJmNvbnNvbGUud2FybigiVEhSRUUuRmlsZUxvYWRlcjogSFRUUCBTdGF0dXMgMCByZWNlaXZlZC4iKSx0eXBlb2YgUmVhZGFibGVTdHJlYW0+InUifHx2b2lkIDA9PT1jLmJvZHkuZ2V0UmVhZGVyKXJldHVybiBjO2xldCB1PVNwW3RdLGQ9Yy5ib2R5LmdldFJlYWRlcigpLHA9Yy5oZWFkZXJzLmdldCgiQ29udGVudC1MZW5ndGgiKSxoPXA/cGFyc2VJbnQocCk6MCxmPTAhPT1oLG09MCx4PW5ldyBSZWFkYWJsZVN0cmVhbSh7c3RhcnQoZyl7IWZ1bmN0aW9uIGIoKXtkLnJlYWQoKS50aGVuKCh7ZG9uZTpELHZhbHVlOlR9KT0+e2lmKEQpZy5jbG9zZSgpO2Vsc2V7bSs9VC5ieXRlTGVuZ3RoO2xldCBrPW5ldyBQcm9ncmVzc0V2ZW50KCJwcm9ncmVzcyIse2xlbmd0aENvbXB1dGFibGU6Zixsb2FkZWQ6bSx0b3RhbDpofSk7Zm9yKGxldCBaPTAsej11Lmxlbmd0aDtaPHo7WisrKXtsZXQgZmU9dVtaXTtmZS5vblByb2dyZXNzJiZmZS5vblByb2dyZXNzKGspfWcuZW5xdWV1ZShUKSxiKCl9fSl9KCl9fSk7cmV0dXJuIG5ldyBSZXNwb25zZSh4KX10aHJvdyBFcnJvcihgZmV0Y2ggZm9yICIke2MudXJsfSIgcmVzcG9uZGVkIHdpdGggJHtjLnN0YXR1c306ICR7Yy5zdGF0dXNUZXh0fWApfSkudGhlbihjPT57c3dpdGNoKGwpe2Nhc2UiYXJyYXlidWZmZXIiOnJldHVybiBjLmFycmF5QnVmZmVyKCk7Y2FzZSJibG9iIjpyZXR1cm4gYy5ibG9iKCk7Y2FzZSJkb2N1bWVudCI6cmV0dXJuIGMudGV4dCgpLnRoZW4odT0+KG5ldyBET01QYXJzZXIpLnBhcnNlRnJvbVN0cmluZyh1LGEpKTtjYXNlImpzb24iOnJldHVybiBjLmpzb24oKTtkZWZhdWx0OmlmKHZvaWQgMD09PWEpcmV0dXJuIGMudGV4dCgpO3tsZXQgZD0vY2hhcnNldD0iPyhbXjsiXHNdKikiPy9pLmV4ZWMoYSkscD1kJiZkWzFdP2RbMV0udG9Mb3dlckNhc2UoKTp2b2lkIDAsaD1uZXcgVGV4dERlY29kZXIocCk7cmV0dXJuIGMuYXJyYXlCdWZmZXIoKS50aGVuKGY9PmguZGVjb2RlKGYpKX19fSkudGhlbihjPT57d2IuYWRkKHQsYyk7bGV0IHU9U3BbdF07ZGVsZXRlIFNwW3RdO2ZvcihsZXQgZD0wLHA9dS5sZW5ndGg7ZDxwO2QrKyl7bGV0IGg9dVtkXTtoLm9uTG9hZCYmaC5vbkxvYWQoYyl9fSkuY2F0Y2goYz0+e2xldCB1PVNwW3RdO2lmKHZvaWQgMD09PXUpdGhyb3cgdGhpcy5tYW5hZ2VyLml0ZW1FcnJvcih0KSxjO2RlbGV0ZSBTcFt0XTtmb3IobGV0IGQ9MCxwPXUubGVuZ3RoO2Q8cDtkKyspe2xldCBoPXVbZF07aC5vbkVycm9yJiZoLm9uRXJyb3IoYyl9dGhpcy5tYW5hZ2VyLml0ZW1FcnJvcih0KX0pLmZpbmFsbHkoKCk9Pnt0aGlzLm1hbmFnZXIuaXRlbUVuZCh0KX0pLHRoaXMubWFuYWdlci5pdGVtU3RhcnQodCl9c2V0UmVzcG9uc2VUeXBlKHQpe3JldHVybiB0aGlzLnJlc3BvbnNlVHlwZT10LHRoaXN9c2V0TWltZVR5cGUodCl7cmV0dXJuIHRoaXMubWltZVR5cGU9dCx0aGlzfX0sRWs9Y2xhc3MgZXh0ZW5kcyBEZHtjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KX1sb2FkKHQsZSxpLHIpe3ZvaWQgMCE9PXRoaXMucGF0aCYmKHQ9dGhpcy5wYXRoK3QpLHQ9dGhpcy5tYW5hZ2VyLnJlc29sdmVVUkwodCk7bGV0IG89dGhpcyxzPXdiLmdldCh0KTtpZih2b2lkIDAhPT1zKXJldHVybiBvLm1hbmFnZXIuaXRlbVN0YXJ0KHQpLHNldFRpbWVvdXQoZnVuY3Rpb24oKXtlJiZlKHMpLG8ubWFuYWdlci5pdGVtRW5kKHQpfSwwKSxzO2xldCBhPVlTKCJpbWciKTtmdW5jdGlvbiBsKCl7dSgpLHdiLmFkZCh0LHRoaXMpLGUmJmUodGhpcyksby5tYW5hZ2VyLml0ZW1FbmQodCl9ZnVuY3Rpb24gYyhkKXt1KCksciYmcihkKSxvLm1hbmFnZXIuaXRlbUVycm9yKHQpLG8ubWFuYWdlci5pdGVtRW5kKHQpfWZ1bmN0aW9uIHUoKXthLnJlbW92ZUV2ZW50TGlzdGVuZXIoImxvYWQiLGwsITEpLGEucmVtb3ZlRXZlbnRMaXN0ZW5lcigiZXJyb3IiLGMsITEpfXJldHVybiBhLmFkZEV2ZW50TGlzdGVuZXIoImxvYWQiLGwsITEpLGEuYWRkRXZlbnRMaXN0ZW5lcigiZXJyb3IiLGMsITEpLCJkYXRhOiIhPT10LnN1YnN0cigwLDUpJiZ2b2lkIDAhPT10aGlzLmNyb3NzT3JpZ2luJiYoYS5jcm9zc09yaWdpbj10aGlzLmNyb3NzT3JpZ2luKSxvLm1hbmFnZXIuaXRlbVN0YXJ0KHQpLGEuc3JjPXQsYX19LFE4PWNsYXNzIGV4dGVuZHMgRGR7Y29uc3RydWN0b3IodCl7c3VwZXIodCl9bG9hZCh0LGUsaSxyKXtsZXQgbz1uZXcgbWIscz1uZXcgRWsodGhpcy5tYW5hZ2VyKTtzLnNldENyb3NzT3JpZ2luKHRoaXMuY3Jvc3NPcmlnaW4pLHMuc2V0UGF0aCh0aGlzLnBhdGgpO2xldCBhPTA7ZnVuY3Rpb24gbChjKXtzLmxvYWQodFtjXSxmdW5jdGlvbih1KXtvLmltYWdlc1tjXT11LGErKyw2PT09YSYmKG8ubmVlZHNVcGRhdGU9ITAsZSYmZShvKSl9LHZvaWQgMCxyKX1mb3IobGV0IGM9MDtjPHQubGVuZ3RoOysrYylsKGMpO3JldHVybiBvfX0sSzg9Y2xhc3MgZXh0ZW5kcyBEZHtjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KX1sb2FkKHQsZSxpLHIpe2xldCBvPW5ldyBIbyxzPW5ldyBFayh0aGlzLm1hbmFnZXIpO3JldHVybiBzLnNldENyb3NzT3JpZ2luKHRoaXMuY3Jvc3NPcmlnaW4pLHMuc2V0UGF0aCh0aGlzLnBhdGgpLHMubG9hZCh0LGZ1bmN0aW9uKGEpe28uaW1hZ2U9YSxvLm5lZWRzVXBkYXRlPSEwLHZvaWQgMCE9PWUmJmUobyl9LGksciksb319LGZjPWNsYXNzIGV4dGVuZHMgWGl7Y29uc3RydWN0b3IodCxlPTEpe3N1cGVyKCksdGhpcy50eXBlPSJMaWdodCIsdGhpcy5jb2xvcj1uZXcgdm4odCksdGhpcy5pbnRlbnNpdHk9ZX1kaXNwb3NlKCl7fWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5jb2xvci5jb3B5KHQuY29sb3IpLHRoaXMuaW50ZW5zaXR5PXQuaW50ZW5zaXR5LHRoaXN9dG9KU09OKHQpe2xldCBlPXN1cGVyLnRvSlNPTih0KTtyZXR1cm4gZS5vYmplY3QuY29sb3I9dGhpcy5jb2xvci5nZXRIZXgoKSxlLm9iamVjdC5pbnRlbnNpdHk9dGhpcy5pbnRlbnNpdHksdm9pZCAwIT09dGhpcy5ncm91bmRDb2xvciYmKGUub2JqZWN0Lmdyb3VuZENvbG9yPXRoaXMuZ3JvdW5kQ29sb3IuZ2V0SGV4KCkpLHZvaWQgMCE9PXRoaXMuZGlzdGFuY2UmJihlLm9iamVjdC5kaXN0YW5jZT10aGlzLmRpc3RhbmNlKSx2b2lkIDAhPT10aGlzLmFuZ2xlJiYoZS5vYmplY3QuYW5nbGU9dGhpcy5hbmdsZSksdm9pZCAwIT09dGhpcy5kZWNheSYmKGUub2JqZWN0LmRlY2F5PXRoaXMuZGVjYXkpLHZvaWQgMCE9PXRoaXMucGVudW1icmEmJihlLm9iamVjdC5wZW51bWJyYT10aGlzLnBlbnVtYnJhKSx2b2lkIDAhPT10aGlzLnNoYWRvdyYmKGUub2JqZWN0LnNoYWRvdz10aGlzLnNoYWRvdy50b0pTT04oKSksZX19O2ZjLnByb3RvdHlwZS5pc0xpZ2h0PSEwLGNsYXNzIGV4dGVuZHMgZmN7Y29uc3RydWN0b3IodCxlLGkpe3N1cGVyKHQsaSksdGhpcy50eXBlPSJIZW1pc3BoZXJlTGlnaHQiLHRoaXMucG9zaXRpb24uY29weShYaS5EZWZhdWx0VXApLHRoaXMudXBkYXRlTWF0cml4KCksdGhpcy5ncm91bmRDb2xvcj1uZXcgdm4oZSl9Y29weSh0KXtyZXR1cm4gZmMucHJvdG90eXBlLmNvcHkuY2FsbCh0aGlzLHQpLHRoaXMuZ3JvdW5kQ29sb3IuY29weSh0Lmdyb3VuZENvbG9yKSx0aGlzfX0ucHJvdG90eXBlLmlzSGVtaXNwaGVyZUxpZ2h0PSEwO3ZhciB4ZGU9bmV3IFJuLENkZT1uZXcgaWUsTWRlPW5ldyBpZSxwRT1jbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLmNhbWVyYT10LHRoaXMuYmlhcz0wLHRoaXMubm9ybWFsQmlhcz0wLHRoaXMucmFkaXVzPTEsdGhpcy5ibHVyU2FtcGxlcz04LHRoaXMubWFwU2l6ZT1uZXcgYXQoNTEyLDUxMiksdGhpcy5tYXA9bnVsbCx0aGlzLm1hcFBhc3M9bnVsbCx0aGlzLm1hdHJpeD1uZXcgUm4sdGhpcy5hdXRvVXBkYXRlPSEwLHRoaXMubmVlZHNVcGRhdGU9ITEsdGhpcy5fZnJ1c3R1bT1uZXcgZ2IsdGhpcy5fZnJhbWVFeHRlbnRzPW5ldyBhdCgxLDEpLHRoaXMuX3ZpZXdwb3J0Q291bnQ9MSx0aGlzLl92aWV3cG9ydHM9W25ldyBhcigwLDAsMSwxKV19Z2V0Vmlld3BvcnRDb3VudCgpe3JldHVybiB0aGlzLl92aWV3cG9ydENvdW50fWdldEZydXN0dW0oKXtyZXR1cm4gdGhpcy5fZnJ1c3R1bX11cGRhdGVNYXRyaWNlcyh0KXtsZXQgZT10aGlzLmNhbWVyYSxpPXRoaXMubWF0cml4O0NkZS5zZXRGcm9tTWF0cml4UG9zaXRpb24odC5tYXRyaXhXb3JsZCksZS5wb3NpdGlvbi5jb3B5KENkZSksTWRlLnNldEZyb21NYXRyaXhQb3NpdGlvbih0LnRhcmdldC5tYXRyaXhXb3JsZCksZS5sb29rQXQoTWRlKSxlLnVwZGF0ZU1hdHJpeFdvcmxkKCkseGRlLm11bHRpcGx5TWF0cmljZXMoZS5wcm9qZWN0aW9uTWF0cml4LGUubWF0cml4V29ybGRJbnZlcnNlKSx0aGlzLl9mcnVzdHVtLnNldEZyb21Qcm9qZWN0aW9uTWF0cml4KHhkZSksaS5zZXQoLjUsMCwwLC41LDAsLjUsMCwuNSwwLDAsLjUsLjUsMCwwLDAsMSksaS5tdWx0aXBseShlLnByb2plY3Rpb25NYXRyaXgpLGkubXVsdGlwbHkoZS5tYXRyaXhXb3JsZEludmVyc2UpfWdldFZpZXdwb3J0KHQpe3JldHVybiB0aGlzLl92aWV3cG9ydHNbdF19Z2V0RnJhbWVFeHRlbnRzKCl7cmV0dXJuIHRoaXMuX2ZyYW1lRXh0ZW50c31kaXNwb3NlKCl7dGhpcy5tYXAmJnRoaXMubWFwLmRpc3Bvc2UoKSx0aGlzLm1hcFBhc3MmJnRoaXMubWFwUGFzcy5kaXNwb3NlKCl9Y29weSh0KXtyZXR1cm4gdGhpcy5jYW1lcmE9dC5jYW1lcmEuY2xvbmUoKSx0aGlzLmJpYXM9dC5iaWFzLHRoaXMucmFkaXVzPXQucmFkaXVzLHRoaXMubWFwU2l6ZS5jb3B5KHQubWFwU2l6ZSksdGhpc31jbG9uZSgpe3JldHVybihuZXcgdGhpcy5jb25zdHJ1Y3RvcikuY29weSh0aGlzKX10b0pTT04oKXtsZXQgdD17fTtyZXR1cm4gMCE9PXRoaXMuYmlhcyYmKHQuYmlhcz10aGlzLmJpYXMpLDAhPT10aGlzLm5vcm1hbEJpYXMmJih0Lm5vcm1hbEJpYXM9dGhpcy5ub3JtYWxCaWFzKSwxIT09dGhpcy5yYWRpdXMmJih0LnJhZGl1cz10aGlzLnJhZGl1cyksKDUxMiE9PXRoaXMubWFwU2l6ZS54fHw1MTIhPT10aGlzLm1hcFNpemUueSkmJih0Lm1hcFNpemU9dGhpcy5tYXBTaXplLnRvQXJyYXkoKSksdC5jYW1lcmE9dGhpcy5jYW1lcmEudG9KU09OKCExKS5vYmplY3QsZGVsZXRlIHQuY2FtZXJhLm1hdHJpeCx0fX0sVGs9Y2xhc3MgZXh0ZW5kcyBwRXtjb25zdHJ1Y3Rvcigpe3N1cGVyKG5ldyBXcyg1MCwxLC41LDUwMCkpLHRoaXMuZm9jdXM9MX11cGRhdGVNYXRyaWNlcyh0KXtsZXQgZT10aGlzLmNhbWVyYSxpPTIqaDgqdC5hbmdsZSp0aGlzLmZvY3VzLHI9dGhpcy5tYXBTaXplLndpZHRoL3RoaXMubWFwU2l6ZS5oZWlnaHQsbz10LmRpc3RhbmNlfHxlLmZhcjsoaSE9PWUuZm92fHxyIT09ZS5hc3BlY3R8fG8hPT1lLmZhcikmJihlLmZvdj1pLGUuYXNwZWN0PXIsZS5mYXI9byxlLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKSksc3VwZXIudXBkYXRlTWF0cmljZXModCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmZvY3VzPXQuZm9jdXMsdGhpc319O1RrLnByb3RvdHlwZS5pc1Nwb3RMaWdodFNoYWRvdz0hMCxjbGFzcyBleHRlbmRzIGZje2NvbnN0cnVjdG9yKHQsZSxpPTAscj1NYXRoLlBJLzMsbz0wLHM9MSl7c3VwZXIodCxlKSx0aGlzLnR5cGU9IlNwb3RMaWdodCIsdGhpcy5wb3NpdGlvbi5jb3B5KFhpLkRlZmF1bHRVcCksdGhpcy51cGRhdGVNYXRyaXgoKSx0aGlzLnRhcmdldD1uZXcgWGksdGhpcy5kaXN0YW5jZT1pLHRoaXMuYW5nbGU9cix0aGlzLnBlbnVtYnJhPW8sdGhpcy5kZWNheT1zLHRoaXMuc2hhZG93PW5ldyBUa31nZXQgcG93ZXIoKXtyZXR1cm4gdGhpcy5pbnRlbnNpdHkqTWF0aC5QSX1zZXQgcG93ZXIodCl7dGhpcy5pbnRlbnNpdHk9dC9NYXRoLlBJfWRpc3Bvc2UoKXt0aGlzLnNoYWRvdy5kaXNwb3NlKCl9Y29weSh0KXtyZXR1cm4gc3VwZXIuY29weSh0KSx0aGlzLmRpc3RhbmNlPXQuZGlzdGFuY2UsdGhpcy5hbmdsZT10LmFuZ2xlLHRoaXMucGVudW1icmE9dC5wZW51bWJyYSx0aGlzLmRlY2F5PXQuZGVjYXksdGhpcy50YXJnZXQ9dC50YXJnZXQuY2xvbmUoKSx0aGlzLnNoYWRvdz10LnNoYWRvdy5jbG9uZSgpLHRoaXN9fS5wcm90b3R5cGUuaXNTcG90TGlnaHQ9ITA7dmFyIHdkZT1uZXcgUm4sTFM9bmV3IGllLG84PW5ldyBpZSxEaz1jbGFzcyBleHRlbmRzIHBFe2NvbnN0cnVjdG9yKCl7c3VwZXIobmV3IFdzKDkwLDEsLjUsNTAwKSksdGhpcy5fZnJhbWVFeHRlbnRzPW5ldyBhdCg0LDIpLHRoaXMuX3ZpZXdwb3J0Q291bnQ9Nix0aGlzLl92aWV3cG9ydHM9W25ldyBhcigyLDEsMSwxKSxuZXcgYXIoMCwxLDEsMSksbmV3IGFyKDMsMSwxLDEpLG5ldyBhcigxLDEsMSwxKSxuZXcgYXIoMywwLDEsMSksbmV3IGFyKDEsMCwxLDEpXSx0aGlzLl9jdWJlRGlyZWN0aW9ucz1bbmV3IGllKDEsMCwwKSxuZXcgaWUoLTEsMCwwKSxuZXcgaWUoMCwwLDEpLG5ldyBpZSgwLDAsLTEpLG5ldyBpZSgwLDEsMCksbmV3IGllKDAsLTEsMCldLHRoaXMuX2N1YmVVcHM9W25ldyBpZSgwLDEsMCksbmV3IGllKDAsMSwwKSxuZXcgaWUoMCwxLDApLG5ldyBpZSgwLDEsMCksbmV3IGllKDAsMCwxKSxuZXcgaWUoMCwwLC0xKV19dXBkYXRlTWF0cmljZXModCxlPTApe2xldCBpPXRoaXMuY2FtZXJhLHI9dGhpcy5tYXRyaXgsbz10LmRpc3RhbmNlfHxpLmZhcjtvIT09aS5mYXImJihpLmZhcj1vLGkudXBkYXRlUHJvamVjdGlvbk1hdHJpeCgpKSxMUy5zZXRGcm9tTWF0cml4UG9zaXRpb24odC5tYXRyaXhXb3JsZCksaS5wb3NpdGlvbi5jb3B5KExTKSxvOC5jb3B5KGkucG9zaXRpb24pLG84LmFkZCh0aGlzLl9jdWJlRGlyZWN0aW9uc1tlXSksaS51cC5jb3B5KHRoaXMuX2N1YmVVcHNbZV0pLGkubG9va0F0KG84KSxpLnVwZGF0ZU1hdHJpeFdvcmxkKCksci5tYWtlVHJhbnNsYXRpb24oLUxTLngsLUxTLnksLUxTLnopLHdkZS5tdWx0aXBseU1hdHJpY2VzKGkucHJvamVjdGlvbk1hdHJpeCxpLm1hdHJpeFdvcmxkSW52ZXJzZSksdGhpcy5fZnJ1c3R1bS5zZXRGcm9tUHJvamVjdGlvbk1hdHJpeCh3ZGUpfX07RGsucHJvdG90eXBlLmlzUG9pbnRMaWdodFNoYWRvdz0hMCxjbGFzcyBleHRlbmRzIGZje2NvbnN0cnVjdG9yKHQsZSxpPTAscj0xKXtzdXBlcih0LGUpLHRoaXMudHlwZT0iUG9pbnRMaWdodCIsdGhpcy5kaXN0YW5jZT1pLHRoaXMuZGVjYXk9cix0aGlzLnNoYWRvdz1uZXcgRGt9Z2V0IHBvd2VyKCl7cmV0dXJuIDQqdGhpcy5pbnRlbnNpdHkqTWF0aC5QSX1zZXQgcG93ZXIodCl7dGhpcy5pbnRlbnNpdHk9dC8oNCpNYXRoLlBJKX1kaXNwb3NlKCl7dGhpcy5zaGFkb3cuZGlzcG9zZSgpfWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5kaXN0YW5jZT10LmRpc3RhbmNlLHRoaXMuZGVjYXk9dC5kZWNheSx0aGlzLnNoYWRvdz10LnNoYWRvdy5jbG9uZSgpLHRoaXN9fS5wcm90b3R5cGUuaXNQb2ludExpZ2h0PSEwO3ZhciBBaz1jbGFzcyBleHRlbmRzIHBFe2NvbnN0cnVjdG9yKCl7c3VwZXIobmV3IHFnKC01LDUsNSwtNSwuNSw1MDApKX19O0FrLnByb3RvdHlwZS5pc0RpcmVjdGlvbmFsTGlnaHRTaGFkb3c9ITAsY2xhc3MgZXh0ZW5kcyBmY3tjb25zdHJ1Y3Rvcih0LGUpe3N1cGVyKHQsZSksdGhpcy50eXBlPSJEaXJlY3Rpb25hbExpZ2h0Iix0aGlzLnBvc2l0aW9uLmNvcHkoWGkuRGVmYXVsdFVwKSx0aGlzLnVwZGF0ZU1hdHJpeCgpLHRoaXMudGFyZ2V0PW5ldyBYaSx0aGlzLnNoYWRvdz1uZXcgQWt9ZGlzcG9zZSgpe3RoaXMuc2hhZG93LmRpc3Bvc2UoKX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMudGFyZ2V0PXQudGFyZ2V0LmNsb25lKCksdGhpcy5zaGFkb3c9dC5zaGFkb3cuY2xvbmUoKSx0aGlzfX0ucHJvdG90eXBlLmlzRGlyZWN0aW9uYWxMaWdodD0hMCxjbGFzcyBleHRlbmRzIGZje2NvbnN0cnVjdG9yKHQsZSl7c3VwZXIodCxlKSx0aGlzLnR5cGU9IkFtYmllbnRMaWdodCJ9fS5wcm90b3R5cGUuaXNBbWJpZW50TGlnaHQ9ITAsY2xhc3MgZXh0ZW5kcyBmY3tjb25zdHJ1Y3Rvcih0LGUsaT0xMCxyPTEwKXtzdXBlcih0LGUpLHRoaXMudHlwZT0iUmVjdEFyZWFMaWdodCIsdGhpcy53aWR0aD1pLHRoaXMuaGVpZ2h0PXJ9Z2V0IHBvd2VyKCl7cmV0dXJuIHRoaXMuaW50ZW5zaXR5KnRoaXMud2lkdGgqdGhpcy5oZWlnaHQqTWF0aC5QSX1zZXQgcG93ZXIodCl7dGhpcy5pbnRlbnNpdHk9dC8odGhpcy53aWR0aCp0aGlzLmhlaWdodCpNYXRoLlBJKX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMud2lkdGg9dC53aWR0aCx0aGlzLmhlaWdodD10LmhlaWdodCx0aGlzfXRvSlNPTih0KXtsZXQgZT1zdXBlci50b0pTT04odCk7cmV0dXJuIGUub2JqZWN0LndpZHRoPXRoaXMud2lkdGgsZS5vYmplY3QuaGVpZ2h0PXRoaXMuaGVpZ2h0LGV9fS5wcm90b3R5cGUuaXNSZWN0QXJlYUxpZ2h0PSEwO3ZhciBJaz1jbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMuY29lZmZpY2llbnRzPVtdO2ZvcihsZXQgdD0wO3Q8OTt0KyspdGhpcy5jb2VmZmljaWVudHMucHVzaChuZXcgaWUpfXNldCh0KXtmb3IobGV0IGU9MDtlPDk7ZSsrKXRoaXMuY29lZmZpY2llbnRzW2VdLmNvcHkodFtlXSk7cmV0dXJuIHRoaXN9emVybygpe2ZvcihsZXQgdD0wO3Q8OTt0KyspdGhpcy5jb2VmZmljaWVudHNbdF0uc2V0KDAsMCwwKTtyZXR1cm4gdGhpc31nZXRBdCh0LGUpe2xldCBpPXQueCxyPXQueSxvPXQueixzPXRoaXMuY29lZmZpY2llbnRzO3JldHVybiBlLmNvcHkoc1swXSkubXVsdGlwbHlTY2FsYXIoLjI4MjA5NSksZS5hZGRTY2FsZWRWZWN0b3Ioc1sxXSwuNDg4NjAzKnIpLGUuYWRkU2NhbGVkVmVjdG9yKHNbMl0sLjQ4ODYwMypvKSxlLmFkZFNjYWxlZFZlY3RvcihzWzNdLC40ODg2MDMqaSksZS5hZGRTY2FsZWRWZWN0b3Ioc1s0XSxpKnIqMS4wOTI1NDgpLGUuYWRkU2NhbGVkVmVjdG9yKHNbNV0scipvKjEuMDkyNTQ4KSxlLmFkZFNjYWxlZFZlY3RvcihzWzZdLC4zMTUzOTIqKDMqbypvLTEpKSxlLmFkZFNjYWxlZFZlY3RvcihzWzddLGkqbyoxLjA5MjU0OCksZS5hZGRTY2FsZWRWZWN0b3Ioc1s4XSwuNTQ2Mjc0KihpKmktcipyKSksZX1nZXRJcnJhZGlhbmNlQXQodCxlKXtsZXQgaT10Lngscj10Lnksbz10Lnoscz10aGlzLmNvZWZmaWNpZW50cztyZXR1cm4gZS5jb3B5KHNbMF0pLm11bHRpcGx5U2NhbGFyKC44ODYyMjcpLGUuYWRkU2NhbGVkVmVjdG9yKHNbMV0sMS4wMjMzMjgqciksZS5hZGRTY2FsZWRWZWN0b3Ioc1syXSwxLjAyMzMyOCpvKSxlLmFkZFNjYWxlZFZlY3RvcihzWzNdLDEuMDIzMzI4KmkpLGUuYWRkU2NhbGVkVmVjdG9yKHNbNF0sLjg1ODA4NippKnIpLGUuYWRkU2NhbGVkVmVjdG9yKHNbNV0sLjg1ODA4NipyKm8pLGUuYWRkU2NhbGVkVmVjdG9yKHNbNl0sLjc0MzEyNSpvKm8tLjI0NzcwOCksZS5hZGRTY2FsZWRWZWN0b3Ioc1s3XSwuODU4MDg2KmkqbyksZS5hZGRTY2FsZWRWZWN0b3Ioc1s4XSwuNDI5MDQzKihpKmktcipyKSksZX1hZGQodCl7Zm9yKGxldCBlPTA7ZTw5O2UrKyl0aGlzLmNvZWZmaWNpZW50c1tlXS5hZGQodC5jb2VmZmljaWVudHNbZV0pO3JldHVybiB0aGlzfWFkZFNjYWxlZFNIKHQsZSl7Zm9yKGxldCBpPTA7aTw5O2krKyl0aGlzLmNvZWZmaWNpZW50c1tpXS5hZGRTY2FsZWRWZWN0b3IodC5jb2VmZmljaWVudHNbaV0sZSk7cmV0dXJuIHRoaXN9c2NhbGUodCl7Zm9yKGxldCBlPTA7ZTw5O2UrKyl0aGlzLmNvZWZmaWNpZW50c1tlXS5tdWx0aXBseVNjYWxhcih0KTtyZXR1cm4gdGhpc31sZXJwKHQsZSl7Zm9yKGxldCBpPTA7aTw5O2krKyl0aGlzLmNvZWZmaWNpZW50c1tpXS5sZXJwKHQuY29lZmZpY2llbnRzW2ldLGUpO3JldHVybiB0aGlzfWVxdWFscyh0KXtmb3IobGV0IGU9MDtlPDk7ZSsrKWlmKCF0aGlzLmNvZWZmaWNpZW50c1tlXS5lcXVhbHModC5jb2VmZmljaWVudHNbZV0pKXJldHVybiExO3JldHVybiEwfWNvcHkodCl7cmV0dXJuIHRoaXMuc2V0KHQuY29lZmZpY2llbnRzKX1jbG9uZSgpe3JldHVybihuZXcgdGhpcy5jb25zdHJ1Y3RvcikuY29weSh0aGlzKX1mcm9tQXJyYXkodCxlPTApe2xldCBpPXRoaXMuY29lZmZpY2llbnRzO2ZvcihsZXQgcj0wO3I8OTtyKyspaVtyXS5mcm9tQXJyYXkodCxlKzMqcik7cmV0dXJuIHRoaXN9dG9BcnJheSh0PVtdLGU9MCl7bGV0IGk9dGhpcy5jb2VmZmljaWVudHM7Zm9yKGxldCByPTA7cjw5O3IrKylpW3JdLnRvQXJyYXkodCxlKzMqcik7cmV0dXJuIHR9c3RhdGljIGdldEJhc2lzQXQodCxlKXtsZXQgaT10Lngscj10Lnksbz10Lno7ZVswXT0uMjgyMDk1LGVbMV09LjQ4ODYwMypyLGVbMl09LjQ4ODYwMypvLGVbM109LjQ4ODYwMyppLGVbNF09MS4wOTI1NDgqaSpyLGVbNV09MS4wOTI1NDgqcipvLGVbNl09LjMxNTM5MiooMypvKm8tMSksZVs3XT0xLjA5MjU0OCppKm8sZVs4XT0uNTQ2Mjc0KihpKmktcipyKX19O0lrLnByb3RvdHlwZS5pc1NwaGVyaWNhbEhhcm1vbmljczM9ITA7dmFyIGhFPWNsYXNzIGV4dGVuZHMgZmN7Y29uc3RydWN0b3IodD1uZXcgSWssZT0xKXtzdXBlcih2b2lkIDAsZSksdGhpcy5zaD10fWNvcHkodCl7cmV0dXJuIHN1cGVyLmNvcHkodCksdGhpcy5zaC5jb3B5KHQuc2gpLHRoaXN9ZnJvbUpTT04odCl7cmV0dXJuIHRoaXMuaW50ZW5zaXR5PXQuaW50ZW5zaXR5LHRoaXMuc2guZnJvbUFycmF5KHQuc2gpLHRoaXN9dG9KU09OKHQpe2xldCBlPXN1cGVyLnRvSlNPTih0KTtyZXR1cm4gZS5vYmplY3Quc2g9dGhpcy5zaC50b0FycmF5KCksZX19O2hFLnByb3RvdHlwZS5pc0xpZ2h0UHJvYmU9ITA7dmFyIGlHPWNsYXNze3N0YXRpYyBkZWNvZGVUZXh0KHQpe2lmKHR5cGVvZiBUZXh0RGVjb2RlcjwidSIpcmV0dXJuKG5ldyBUZXh0RGVjb2RlcikuZGVjb2RlKHQpO2xldCBlPSIiO2ZvcihsZXQgaT0wLHI9dC5sZW5ndGg7aTxyO2krKyllKz1TdHJpbmcuZnJvbUNoYXJDb2RlKHRbaV0pO3RyeXtyZXR1cm4gZGVjb2RlVVJJQ29tcG9uZW50KGVzY2FwZShlKSl9Y2F0Y2h7cmV0dXJuIGV9fXN0YXRpYyBleHRyYWN0VXJsQmFzZSh0KXtsZXQgZT10Lmxhc3RJbmRleE9mKCIvIik7cmV0dXJuLTE9PT1lPyIuLyI6dC5zdWJzdHIoMCxlKzEpfXN0YXRpYyByZXNvbHZlVVJMKHQsZSl7cmV0dXJuInN0cmluZyIhPXR5cGVvZiB0fHwiIj09PXQ/IiI6KC9eaHR0cHM/OlwvXC8vaS50ZXN0KGUpJiYvXlwvLy50ZXN0KHQpJiYoZT1lLnJlcGxhY2UoLyheaHR0cHM/OlwvXC9bXlwvXSspLiovaSwiJDEiKSksL14oaHR0cHM/Oik/XC9cLy9pLnRlc3QodCl8fC9eZGF0YTouKiwuKiQvaS50ZXN0KHQpfHwvXmJsb2I6LiokL2kudGVzdCh0KT90OmUrdCl9fTsoY2xhc3MgZXh0ZW5kcyBucntjb25zdHJ1Y3Rvcigpe3N1cGVyKCksdGhpcy50eXBlPSJJbnN0YW5jZWRCdWZmZXJHZW9tZXRyeSIsdGhpcy5pbnN0YW5jZUNvdW50PTEvMH1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMuaW5zdGFuY2VDb3VudD10Lmluc3RhbmNlQ291bnQsdGhpc31jbG9uZSgpe3JldHVybihuZXcgdGhpcy5jb25zdHJ1Y3RvcikuY29weSh0aGlzKX10b0pTT04oKXtsZXQgdD1zdXBlci50b0pTT04odGhpcyk7cmV0dXJuIHQuaW5zdGFuY2VDb3VudD10aGlzLmluc3RhbmNlQ291bnQsdC5pc0luc3RhbmNlZEJ1ZmZlckdlb21ldHJ5PSEwLHR9fSkucHJvdG90eXBlLmlzSW5zdGFuY2VkQnVmZmVyR2VvbWV0cnk9ITAsY2xhc3MgZXh0ZW5kcyBEZHtjb25zdHJ1Y3Rvcih0KXtzdXBlcih0KSx0eXBlb2YgY3JlYXRlSW1hZ2VCaXRtYXA+InUiJiZjb25zb2xlLndhcm4oIlRIUkVFLkltYWdlQml0bWFwTG9hZGVyOiBjcmVhdGVJbWFnZUJpdG1hcCgpIG5vdCBzdXBwb3J0ZWQuIiksdHlwZW9mIGZldGNoPiJ1IiYmY29uc29sZS53YXJuKCJUSFJFRS5JbWFnZUJpdG1hcExvYWRlcjogZmV0Y2goKSBub3Qgc3VwcG9ydGVkLiIpLHRoaXMub3B0aW9ucz17cHJlbXVsdGlwbHlBbHBoYToibm9uZSJ9fXNldE9wdGlvbnModCl7cmV0dXJuIHRoaXMub3B0aW9ucz10LHRoaXN9bG9hZCh0LGUsaSxyKXt2b2lkIDA9PT10JiYodD0iIiksdm9pZCAwIT09dGhpcy5wYXRoJiYodD10aGlzLnBhdGgrdCksdD10aGlzLm1hbmFnZXIucmVzb2x2ZVVSTCh0KTtsZXQgbz10aGlzLHM9d2IuZ2V0KHQpO2lmKHZvaWQgMCE9PXMpcmV0dXJuIG8ubWFuYWdlci5pdGVtU3RhcnQodCksc2V0VGltZW91dChmdW5jdGlvbigpe2UmJmUocyksby5tYW5hZ2VyLml0ZW1FbmQodCl9LDApLHM7bGV0IGE9e307YS5jcmVkZW50aWFscz0iYW5vbnltb3VzIj09PXRoaXMuY3Jvc3NPcmlnaW4/InNhbWUtb3JpZ2luIjoiaW5jbHVkZSIsYS5oZWFkZXJzPXRoaXMucmVxdWVzdEhlYWRlcixmZXRjaCh0LGEpLnRoZW4oZnVuY3Rpb24obCl7cmV0dXJuIGwuYmxvYigpfSkudGhlbihmdW5jdGlvbihsKXtyZXR1cm4gY3JlYXRlSW1hZ2VCaXRtYXAobCxPYmplY3QuYXNzaWduKG8ub3B0aW9ucyx7Y29sb3JTcGFjZUNvbnZlcnNpb246Im5vbmUifSkpfSkudGhlbihmdW5jdGlvbihsKXt3Yi5hZGQodCxsKSxlJiZlKGwpLG8ubWFuYWdlci5pdGVtRW5kKHQpfSkuY2F0Y2goZnVuY3Rpb24obCl7ciYmcihsKSxvLm1hbmFnZXIuaXRlbUVycm9yKHQpLG8ubWFuYWdlci5pdGVtRW5kKHQpfSksby5tYW5hZ2VyLml0ZW1TdGFydCh0KX19LnByb3RvdHlwZS5pc0ltYWdlQml0bWFwTG9hZGVyPSEwO3ZhciBKTyxzRz1jbGFzcyBleHRlbmRzIERke2NvbnN0cnVjdG9yKHQpe3N1cGVyKHQpfWxvYWQodCxlLGkscil7bGV0IG89dGhpcyxzPW5ldyBYOCh0aGlzLm1hbmFnZXIpO3Muc2V0UmVzcG9uc2VUeXBlKCJhcnJheWJ1ZmZlciIpLHMuc2V0UGF0aCh0aGlzLnBhdGgpLHMuc2V0UmVxdWVzdEhlYWRlcih0aGlzLnJlcXVlc3RIZWFkZXIpLHMuc2V0V2l0aENyZWRlbnRpYWxzKHRoaXMud2l0aENyZWRlbnRpYWxzKSxzLmxvYWQodCxmdW5jdGlvbihhKXt0cnl7bGV0IGw9YS5zbGljZSgwKTsodm9pZCAwPT09Sk8mJihKTz1uZXcod2luZG93LkF1ZGlvQ29udGV4dHx8d2luZG93LndlYmtpdEF1ZGlvQ29udGV4dCkpLEpPKS5kZWNvZGVBdWRpb0RhdGEobCxmdW5jdGlvbih1KXtlKHUpfSl9Y2F0Y2gobCl7cj9yKGwpOmNvbnNvbGUuZXJyb3IobCksby5tYW5hZ2VyLml0ZW1FcnJvcih0KX19LGkscil9fTsoY2xhc3MgZXh0ZW5kcyBoRXtjb25zdHJ1Y3Rvcih0LGUsaT0xKXtzdXBlcih2b2lkIDAsaSk7bGV0IHI9KG5ldyB2bikuc2V0KHQpLG89KG5ldyB2bikuc2V0KGUpLHM9bmV3IGllKHIucixyLmcsci5iKSxhPW5ldyBpZShvLnIsby5nLG8uYiksbD1NYXRoLnNxcnQoTWF0aC5QSSksYz1sKk1hdGguc3FydCguNzUpO3RoaXMuc2guY29lZmZpY2llbnRzWzBdLmNvcHkocykuYWRkKGEpLm11bHRpcGx5U2NhbGFyKGwpLHRoaXMuc2guY29lZmZpY2llbnRzWzFdLmNvcHkocykuc3ViKGEpLm11bHRpcGx5U2NhbGFyKGMpfX0pLnByb3RvdHlwZS5pc0hlbWlzcGhlcmVMaWdodFByb2JlPSEwLGNsYXNzIGV4dGVuZHMgaEV7Y29uc3RydWN0b3IodCxlPTEpe3N1cGVyKHZvaWQgMCxlKTtsZXQgaT0obmV3IHZuKS5zZXQodCk7dGhpcy5zaC5jb2VmZmljaWVudHNbMF0uc2V0KGkucixpLmcsaS5iKS5tdWx0aXBseVNjYWxhcigyKk1hdGguc3FydChNYXRoLlBJKSl9fS5wcm90b3R5cGUuaXNBbWJpZW50TGlnaHRQcm9iZT0hMDt2YXIgZEc9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkpe2xldCByLG8scztzd2l0Y2godGhpcy5iaW5kaW5nPXQsdGhpcy52YWx1ZVNpemU9aSxlKXtjYXNlInF1YXRlcm5pb24iOnI9dGhpcy5fc2xlcnAsbz10aGlzLl9zbGVycEFkZGl0aXZlLHM9dGhpcy5fc2V0QWRkaXRpdmVJZGVudGl0eVF1YXRlcm5pb24sdGhpcy5idWZmZXI9bmV3IEZsb2F0NjRBcnJheSg2KmkpLHRoaXMuX3dvcmtJbmRleD01O2JyZWFrO2Nhc2Uic3RyaW5nIjpjYXNlImJvb2wiOnI9dGhpcy5fc2VsZWN0LG89dGhpcy5fc2VsZWN0LHM9dGhpcy5fc2V0QWRkaXRpdmVJZGVudGl0eU90aGVyLHRoaXMuYnVmZmVyPW5ldyBBcnJheSg1KmkpO2JyZWFrO2RlZmF1bHQ6cj10aGlzLl9sZXJwLG89dGhpcy5fbGVycEFkZGl0aXZlLHM9dGhpcy5fc2V0QWRkaXRpdmVJZGVudGl0eU51bWVyaWMsdGhpcy5idWZmZXI9bmV3IEZsb2F0NjRBcnJheSg1KmkpfXRoaXMuX21peEJ1ZmZlclJlZ2lvbj1yLHRoaXMuX21peEJ1ZmZlclJlZ2lvbkFkZGl0aXZlPW8sdGhpcy5fc2V0SWRlbnRpdHk9cyx0aGlzLl9vcmlnSW5kZXg9Myx0aGlzLl9hZGRJbmRleD00LHRoaXMuY3VtdWxhdGl2ZVdlaWdodD0wLHRoaXMuY3VtdWxhdGl2ZVdlaWdodEFkZGl0aXZlPTAsdGhpcy51c2VDb3VudD0wLHRoaXMucmVmZXJlbmNlQ291bnQ9MH1hY2N1bXVsYXRlKHQsZSl7bGV0IGk9dGhpcy5idWZmZXIscj10aGlzLnZhbHVlU2l6ZSxvPXQqcityLHM9dGhpcy5jdW11bGF0aXZlV2VpZ2h0O2lmKDA9PT1zKXtmb3IobGV0IGE9MDthIT09cjsrK2EpaVtvK2FdPWlbYV07cz1lfWVsc2Ugcys9ZSx0aGlzLl9taXhCdWZmZXJSZWdpb24oaSxvLDAsZS9zLHIpO3RoaXMuY3VtdWxhdGl2ZVdlaWdodD1zfWFjY3VtdWxhdGVBZGRpdGl2ZSh0KXtsZXQgZT10aGlzLmJ1ZmZlcixpPXRoaXMudmFsdWVTaXplLHI9aSp0aGlzLl9hZGRJbmRleDswPT09dGhpcy5jdW11bGF0aXZlV2VpZ2h0QWRkaXRpdmUmJnRoaXMuX3NldElkZW50aXR5KCksdGhpcy5fbWl4QnVmZmVyUmVnaW9uQWRkaXRpdmUoZSxyLDAsdCxpKSx0aGlzLmN1bXVsYXRpdmVXZWlnaHRBZGRpdGl2ZSs9dH1hcHBseSh0KXtsZXQgZT10aGlzLnZhbHVlU2l6ZSxpPXRoaXMuYnVmZmVyLHI9dCplK2Usbz10aGlzLmN1bXVsYXRpdmVXZWlnaHQscz10aGlzLmN1bXVsYXRpdmVXZWlnaHRBZGRpdGl2ZSxhPXRoaXMuYmluZGluZzt0aGlzLmN1bXVsYXRpdmVXZWlnaHQ9MCx0aGlzLmN1bXVsYXRpdmVXZWlnaHRBZGRpdGl2ZT0wLG88MSYmdGhpcy5fbWl4QnVmZmVyUmVnaW9uKGkscixlKnRoaXMuX29yaWdJbmRleCwxLW8sZSkscz4wJiZ0aGlzLl9taXhCdWZmZXJSZWdpb25BZGRpdGl2ZShpLHIsdGhpcy5fYWRkSW5kZXgqZSwxLGUpO2ZvcihsZXQgbD1lLGM9ZStlO2whPT1jOysrbClpZihpW2xdIT09aVtsK2VdKXthLnNldFZhbHVlKGkscik7YnJlYWt9fXNhdmVPcmlnaW5hbFN0YXRlKCl7bGV0IGU9dGhpcy5idWZmZXIsaT10aGlzLnZhbHVlU2l6ZSxyPWkqdGhpcy5fb3JpZ0luZGV4O3RoaXMuYmluZGluZy5nZXRWYWx1ZShlLHIpO2ZvcihsZXQgbz1pLHM9cjtvIT09czsrK28pZVtvXT1lW3IrbyVpXTt0aGlzLl9zZXRJZGVudGl0eSgpLHRoaXMuY3VtdWxhdGl2ZVdlaWdodD0wLHRoaXMuY3VtdWxhdGl2ZVdlaWdodEFkZGl0aXZlPTB9cmVzdG9yZU9yaWdpbmFsU3RhdGUoKXt0aGlzLmJpbmRpbmcuc2V0VmFsdWUodGhpcy5idWZmZXIsMyp0aGlzLnZhbHVlU2l6ZSl9X3NldEFkZGl0aXZlSWRlbnRpdHlOdW1lcmljKCl7bGV0IHQ9dGhpcy5fYWRkSW5kZXgqdGhpcy52YWx1ZVNpemUsZT10K3RoaXMudmFsdWVTaXplO2ZvcihsZXQgaT10O2k8ZTtpKyspdGhpcy5idWZmZXJbaV09MH1fc2V0QWRkaXRpdmVJZGVudGl0eVF1YXRlcm5pb24oKXt0aGlzLl9zZXRBZGRpdGl2ZUlkZW50aXR5TnVtZXJpYygpLHRoaXMuYnVmZmVyW3RoaXMuX2FkZEluZGV4KnRoaXMudmFsdWVTaXplKzNdPTF9X3NldEFkZGl0aXZlSWRlbnRpdHlPdGhlcigpe2xldCB0PXRoaXMuX29yaWdJbmRleCp0aGlzLnZhbHVlU2l6ZSxlPXRoaXMuX2FkZEluZGV4KnRoaXMudmFsdWVTaXplO2ZvcihsZXQgaT0wO2k8dGhpcy52YWx1ZVNpemU7aSsrKXRoaXMuYnVmZmVyW2UraV09dGhpcy5idWZmZXJbdCtpXX1fc2VsZWN0KHQsZSxpLHIsbyl7aWYocj49LjUpZm9yKGxldCBzPTA7cyE9PW87KytzKXRbZStzXT10W2krc119X3NsZXJwKHQsZSxpLHIpe3FzLnNsZXJwRmxhdCh0LGUsdCxlLHQsaSxyKX1fc2xlcnBBZGRpdGl2ZSh0LGUsaSxyLG8pe2xldCBzPXRoaXMuX3dvcmtJbmRleCpvO3FzLm11bHRpcGx5UXVhdGVybmlvbnNGbGF0KHQscyx0LGUsdCxpKSxxcy5zbGVycEZsYXQodCxlLHQsZSx0LHMscil9X2xlcnAodCxlLGkscixvKXtsZXQgcz0xLXI7Zm9yKGxldCBhPTA7YSE9PW87KythKXtsZXQgbD1lK2E7dFtsXT10W2xdKnMrdFtpK2FdKnJ9fV9sZXJwQWRkaXRpdmUodCxlLGkscixvKXtmb3IobGV0IHM9MDtzIT09bzsrK3Mpe2xldCBhPWUrczt0W2FdPXRbYV0rdFtpK3NdKnJ9fX0sRUc9IlxcW1xcXVxcLjpcXC8iLEs5ZT1uZXcgUmVnRXhwKCJbIitFRysiXSIsImciKSxURz0iW14iK0VHKyJdIixaOWU9IlteIitFRy5yZXBsYWNlKCJcXC4iLCIiKSsiXSIsSjllPS8oKD86V0MrW1wvOl0pKikvLnNvdXJjZS5yZXBsYWNlKCJXQyIsVEcpLCQ5ZT0vKFdDT0QrKT8vLnNvdXJjZS5yZXBsYWNlKCJXQ09EIixaOWUpLGVxZT0vKD86XC4oV0MrKSg/OlxbKC4rKVxdKT8pPy8uc291cmNlLnJlcGxhY2UoIldDIixURyksdHFlPS9cLihXQyspKD86XFsoLispXF0pPy8uc291cmNlLnJlcGxhY2UoIldDIixURyksbnFlPW5ldyBSZWdFeHAoIl4iK0o5ZSskOWUrZXFlK3RxZSsiJCIpLGlxZT1bIm1hdGVyaWFsIiwibWF0ZXJpYWxzIiwiYm9uZXMiXSxwRz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSl7bGV0IHI9aXx8THIucGFyc2VUcmFja05hbWUoZSk7dGhpcy5fdGFyZ2V0R3JvdXA9dCx0aGlzLl9iaW5kaW5ncz10LnN1YnNjcmliZV8oZSxyKX1nZXRWYWx1ZSh0LGUpe3RoaXMuYmluZCgpO2xldCByPXRoaXMuX2JpbmRpbmdzW3RoaXMuX3RhcmdldEdyb3VwLm5DYWNoZWRPYmplY3RzX107dm9pZCAwIT09ciYmci5nZXRWYWx1ZSh0LGUpfXNldFZhbHVlKHQsZSl7bGV0IGk9dGhpcy5fYmluZGluZ3M7Zm9yKGxldCByPXRoaXMuX3RhcmdldEdyb3VwLm5DYWNoZWRPYmplY3RzXyxvPWkubGVuZ3RoO3IhPT1vOysrcilpW3JdLnNldFZhbHVlKHQsZSl9YmluZCgpe2xldCB0PXRoaXMuX2JpbmRpbmdzO2ZvcihsZXQgZT10aGlzLl90YXJnZXRHcm91cC5uQ2FjaGVkT2JqZWN0c18saT10Lmxlbmd0aDtlIT09aTsrK2UpdFtlXS5iaW5kKCl9dW5iaW5kKCl7bGV0IHQ9dGhpcy5fYmluZGluZ3M7Zm9yKGxldCBlPXRoaXMuX3RhcmdldEdyb3VwLm5DYWNoZWRPYmplY3RzXyxpPXQubGVuZ3RoO2UhPT1pOysrZSl0W2VdLnVuYmluZCgpfX0sTHI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5wYXRoPWksdGhpcy5wYXJzZWRQYXRoPXJ8fG4ucGFyc2VUcmFja05hbWUoaSksdGhpcy5ub2RlPW4uZmluZE5vZGUoZSx0aGlzLnBhcnNlZFBhdGgubm9kZU5hbWUpfHxlLHRoaXMucm9vdE5vZGU9ZSx0aGlzLmdldFZhbHVlPXRoaXMuX2dldFZhbHVlX3VuYm91bmQsdGhpcy5zZXRWYWx1ZT10aGlzLl9zZXRWYWx1ZV91bmJvdW5kfXN0YXRpYyBjcmVhdGUoZSxpLHIpe3JldHVybiBlJiZlLmlzQW5pbWF0aW9uT2JqZWN0R3JvdXA/bmV3IG4uQ29tcG9zaXRlKGUsaSxyKTpuZXcgbihlLGkscil9c3RhdGljIHNhbml0aXplTm9kZU5hbWUoZSl7cmV0dXJuIGUucmVwbGFjZSgvXHMvZywiXyIpLnJlcGxhY2UoSzllLCIiKX1zdGF0aWMgcGFyc2VUcmFja05hbWUoZSl7bGV0IGk9bnFlLmV4ZWMoZSk7aWYoIWkpdGhyb3cgbmV3IEVycm9yKCJQcm9wZXJ0eUJpbmRpbmc6IENhbm5vdCBwYXJzZSB0cmFja05hbWU6ICIrZSk7bGV0IHI9e25vZGVOYW1lOmlbMl0sb2JqZWN0TmFtZTppWzNdLG9iamVjdEluZGV4OmlbNF0scHJvcGVydHlOYW1lOmlbNV0scHJvcGVydHlJbmRleDppWzZdfSxvPXIubm9kZU5hbWUmJnIubm9kZU5hbWUubGFzdEluZGV4T2YoIi4iKTtpZih2b2lkIDAhPT1vJiYtMSE9PW8pe2xldCBzPXIubm9kZU5hbWUuc3Vic3RyaW5nKG8rMSk7LTEhPT1pcWUuaW5kZXhPZihzKSYmKHIubm9kZU5hbWU9ci5ub2RlTmFtZS5zdWJzdHJpbmcoMCxvKSxyLm9iamVjdE5hbWU9cyl9aWYobnVsbD09PXIucHJvcGVydHlOYW1lfHwwPT09ci5wcm9wZXJ0eU5hbWUubGVuZ3RoKXRocm93IG5ldyBFcnJvcigiUHJvcGVydHlCaW5kaW5nOiBjYW4gbm90IHBhcnNlIHByb3BlcnR5TmFtZSBmcm9tIHRyYWNrTmFtZTogIitlKTtyZXR1cm4gcn1zdGF0aWMgZmluZE5vZGUoZSxpKXtpZighaXx8IiI9PT1pfHwiLiI9PT1pfHwtMT09PWl8fGk9PT1lLm5hbWV8fGk9PT1lLnV1aWQpcmV0dXJuIGU7aWYoZS5za2VsZXRvbil7bGV0IHI9ZS5za2VsZXRvbi5nZXRCb25lQnlOYW1lKGkpO2lmKHZvaWQgMCE9PXIpcmV0dXJuIHJ9aWYoZS5jaGlsZHJlbil7bGV0IHI9ZnVuY3Rpb24ocyl7Zm9yKGxldCBhPTA7YTxzLmxlbmd0aDthKyspe2xldCBsPXNbYV07aWYobC5uYW1lPT09aXx8bC51dWlkPT09aSlyZXR1cm4gbDtsZXQgYz1yKGwuY2hpbGRyZW4pO2lmKGMpcmV0dXJuIGN9cmV0dXJuIG51bGx9LG89cihlLmNoaWxkcmVuKTtpZihvKXJldHVybiBvfXJldHVybiBudWxsfV9nZXRWYWx1ZV91bmF2YWlsYWJsZSgpe31fc2V0VmFsdWVfdW5hdmFpbGFibGUoKXt9X2dldFZhbHVlX2RpcmVjdChlLGkpe2VbaV09dGhpcy50YXJnZXRPYmplY3RbdGhpcy5wcm9wZXJ0eU5hbWVdfV9nZXRWYWx1ZV9hcnJheShlLGkpe2xldCByPXRoaXMucmVzb2x2ZWRQcm9wZXJ0eTtmb3IobGV0IG89MCxzPXIubGVuZ3RoO28hPT1zOysrbyllW2krK109cltvXX1fZ2V0VmFsdWVfYXJyYXlFbGVtZW50KGUsaSl7ZVtpXT10aGlzLnJlc29sdmVkUHJvcGVydHlbdGhpcy5wcm9wZXJ0eUluZGV4XX1fZ2V0VmFsdWVfdG9BcnJheShlLGkpe3RoaXMucmVzb2x2ZWRQcm9wZXJ0eS50b0FycmF5KGUsaSl9X3NldFZhbHVlX2RpcmVjdChlLGkpe3RoaXMudGFyZ2V0T2JqZWN0W3RoaXMucHJvcGVydHlOYW1lXT1lW2ldfV9zZXRWYWx1ZV9kaXJlY3Rfc2V0TmVlZHNVcGRhdGUoZSxpKXt0aGlzLnRhcmdldE9iamVjdFt0aGlzLnByb3BlcnR5TmFtZV09ZVtpXSx0aGlzLnRhcmdldE9iamVjdC5uZWVkc1VwZGF0ZT0hMH1fc2V0VmFsdWVfZGlyZWN0X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGUoZSxpKXt0aGlzLnRhcmdldE9iamVjdFt0aGlzLnByb3BlcnR5TmFtZV09ZVtpXSx0aGlzLnRhcmdldE9iamVjdC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlPSEwfV9zZXRWYWx1ZV9hcnJheShlLGkpe2xldCByPXRoaXMucmVzb2x2ZWRQcm9wZXJ0eTtmb3IobGV0IG89MCxzPXIubGVuZ3RoO28hPT1zOysrbylyW29dPWVbaSsrXX1fc2V0VmFsdWVfYXJyYXlfc2V0TmVlZHNVcGRhdGUoZSxpKXtsZXQgcj10aGlzLnJlc29sdmVkUHJvcGVydHk7Zm9yKGxldCBvPTAscz1yLmxlbmd0aDtvIT09czsrK28pcltvXT1lW2krK107dGhpcy50YXJnZXRPYmplY3QubmVlZHNVcGRhdGU9ITB9X3NldFZhbHVlX2FycmF5X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGUoZSxpKXtsZXQgcj10aGlzLnJlc29sdmVkUHJvcGVydHk7Zm9yKGxldCBvPTAscz1yLmxlbmd0aDtvIT09czsrK28pcltvXT1lW2krK107dGhpcy50YXJnZXRPYmplY3QubWF0cml4V29ybGROZWVkc1VwZGF0ZT0hMH1fc2V0VmFsdWVfYXJyYXlFbGVtZW50KGUsaSl7dGhpcy5yZXNvbHZlZFByb3BlcnR5W3RoaXMucHJvcGVydHlJbmRleF09ZVtpXX1fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE5lZWRzVXBkYXRlKGUsaSl7dGhpcy5yZXNvbHZlZFByb3BlcnR5W3RoaXMucHJvcGVydHlJbmRleF09ZVtpXSx0aGlzLnRhcmdldE9iamVjdC5uZWVkc1VwZGF0ZT0hMH1fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGUoZSxpKXt0aGlzLnJlc29sdmVkUHJvcGVydHlbdGhpcy5wcm9wZXJ0eUluZGV4XT1lW2ldLHRoaXMudGFyZ2V0T2JqZWN0Lm1hdHJpeFdvcmxkTmVlZHNVcGRhdGU9ITB9X3NldFZhbHVlX2Zyb21BcnJheShlLGkpe3RoaXMucmVzb2x2ZWRQcm9wZXJ0eS5mcm9tQXJyYXkoZSxpKX1fc2V0VmFsdWVfZnJvbUFycmF5X3NldE5lZWRzVXBkYXRlKGUsaSl7dGhpcy5yZXNvbHZlZFByb3BlcnR5LmZyb21BcnJheShlLGkpLHRoaXMudGFyZ2V0T2JqZWN0Lm5lZWRzVXBkYXRlPSEwfV9zZXRWYWx1ZV9mcm9tQXJyYXlfc2V0TWF0cml4V29ybGROZWVkc1VwZGF0ZShlLGkpe3RoaXMucmVzb2x2ZWRQcm9wZXJ0eS5mcm9tQXJyYXkoZSxpKSx0aGlzLnRhcmdldE9iamVjdC5tYXRyaXhXb3JsZE5lZWRzVXBkYXRlPSEwfV9nZXRWYWx1ZV91bmJvdW5kKGUsaSl7dGhpcy5iaW5kKCksdGhpcy5nZXRWYWx1ZShlLGkpfV9zZXRWYWx1ZV91bmJvdW5kKGUsaSl7dGhpcy5iaW5kKCksdGhpcy5zZXRWYWx1ZShlLGkpfWJpbmQoKXtsZXQgZT10aGlzLm5vZGUsaT10aGlzLnBhcnNlZFBhdGgscj1pLm9iamVjdE5hbWUsbz1pLnByb3BlcnR5TmFtZSxzPWkucHJvcGVydHlJbmRleDtpZihlfHwoZT1uLmZpbmROb2RlKHRoaXMucm9vdE5vZGUsaS5ub2RlTmFtZSl8fHRoaXMucm9vdE5vZGUsdGhpcy5ub2RlPWUpLHRoaXMuZ2V0VmFsdWU9dGhpcy5fZ2V0VmFsdWVfdW5hdmFpbGFibGUsdGhpcy5zZXRWYWx1ZT10aGlzLl9zZXRWYWx1ZV91bmF2YWlsYWJsZSwhZSlyZXR1cm4gdm9pZCBjb25zb2xlLmVycm9yKCJUSFJFRS5Qcm9wZXJ0eUJpbmRpbmc6IFRyeWluZyB0byB1cGRhdGUgbm9kZSBmb3IgdHJhY2s6ICIrdGhpcy5wYXRoKyIgYnV0IGl0IHdhc24ndCBmb3VuZC4iKTtpZihyKXtsZXQgdT1pLm9iamVjdEluZGV4O3N3aXRjaChyKXtjYXNlIm1hdGVyaWFscyI6aWYoIWUubWF0ZXJpYWwpcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbWF0ZXJpYWwgYXMgbm9kZSBkb2VzIG5vdCBoYXZlIGEgbWF0ZXJpYWwuIix0aGlzKTtpZighZS5tYXRlcmlhbC5tYXRlcmlhbHMpcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbWF0ZXJpYWwubWF0ZXJpYWxzIGFzIG5vZGUubWF0ZXJpYWwgZG9lcyBub3QgaGF2ZSBhIG1hdGVyaWFscyBhcnJheS4iLHRoaXMpO2U9ZS5tYXRlcmlhbC5tYXRlcmlhbHM7YnJlYWs7Y2FzZSJib25lcyI6aWYoIWUuc2tlbGV0b24pcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gYm9uZXMgYXMgbm9kZSBkb2VzIG5vdCBoYXZlIGEgc2tlbGV0b24uIix0aGlzKTtlPWUuc2tlbGV0b24uYm9uZXM7Zm9yKGxldCBkPTA7ZDxlLmxlbmd0aDtkKyspaWYoZVtkXS5uYW1lPT09dSl7dT1kO2JyZWFrfWJyZWFrO2RlZmF1bHQ6aWYodm9pZCAwPT09ZVtyXSlyZXR1cm4gdm9pZCBjb25zb2xlLmVycm9yKCJUSFJFRS5Qcm9wZXJ0eUJpbmRpbmc6IENhbiBub3QgYmluZCB0byBvYmplY3ROYW1lIG9mIG5vZGUgdW5kZWZpbmVkLiIsdGhpcyk7ZT1lW3JdfWlmKHZvaWQgMCE9PXUpe2lmKHZvaWQgMD09PWVbdV0pcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBUcnlpbmcgdG8gYmluZCB0byBvYmplY3RJbmRleCBvZiBvYmplY3ROYW1lLCBidXQgaXMgdW5kZWZpbmVkLiIsdGhpcyxlKTtlPWVbdV19fWxldCBhPWVbb107aWYodm9pZCAwPT09YSlyZXR1cm4gdm9pZCBjb25zb2xlLmVycm9yKCJUSFJFRS5Qcm9wZXJ0eUJpbmRpbmc6IFRyeWluZyB0byB1cGRhdGUgcHJvcGVydHkgZm9yIHRyYWNrOiAiK2kubm9kZU5hbWUrIi4iK28rIiBidXQgaXQgd2Fzbid0IGZvdW5kLiIsZSk7bGV0IGw9dGhpcy5WZXJzaW9uaW5nLk5vbmU7dGhpcy50YXJnZXRPYmplY3Q9ZSx2b2lkIDAhPT1lLm5lZWRzVXBkYXRlP2w9dGhpcy5WZXJzaW9uaW5nLk5lZWRzVXBkYXRlOnZvaWQgMCE9PWUubWF0cml4V29ybGROZWVkc1VwZGF0ZSYmKGw9dGhpcy5WZXJzaW9uaW5nLk1hdHJpeFdvcmxkTmVlZHNVcGRhdGUpO2xldCBjPXRoaXMuQmluZGluZ1R5cGUuRGlyZWN0O2lmKHZvaWQgMCE9PXMpe2lmKCJtb3JwaFRhcmdldEluZmx1ZW5jZXMiPT09byl7aWYoIWUuZ2VvbWV0cnkpcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbW9ycGhUYXJnZXRJbmZsdWVuY2VzIGJlY2F1c2Ugbm9kZSBkb2VzIG5vdCBoYXZlIGEgZ2VvbWV0cnkuIix0aGlzKTtpZighZS5nZW9tZXRyeS5pc0J1ZmZlckdlb21ldHJ5KXJldHVybiB2b2lkIGNvbnNvbGUuZXJyb3IoIlRIUkVFLlByb3BlcnR5QmluZGluZzogQ2FuIG5vdCBiaW5kIHRvIG1vcnBoVGFyZ2V0SW5mbHVlbmNlcyBvbiBUSFJFRS5HZW9tZXRyeS4gVXNlIFRIUkVFLkJ1ZmZlckdlb21ldHJ5IGluc3RlYWQuIix0aGlzKTtpZighZS5nZW9tZXRyeS5tb3JwaEF0dHJpYnV0ZXMpcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiVEhSRUUuUHJvcGVydHlCaW5kaW5nOiBDYW4gbm90IGJpbmQgdG8gbW9ycGhUYXJnZXRJbmZsdWVuY2VzIGJlY2F1c2Ugbm9kZSBkb2VzIG5vdCBoYXZlIGEgZ2VvbWV0cnkubW9ycGhBdHRyaWJ1dGVzLiIsdGhpcyk7dm9pZCAwIT09ZS5tb3JwaFRhcmdldERpY3Rpb25hcnlbc10mJihzPWUubW9ycGhUYXJnZXREaWN0aW9uYXJ5W3NdKX1jPXRoaXMuQmluZGluZ1R5cGUuQXJyYXlFbGVtZW50LHRoaXMucmVzb2x2ZWRQcm9wZXJ0eT1hLHRoaXMucHJvcGVydHlJbmRleD1zfWVsc2Ugdm9pZCAwIT09YS5mcm9tQXJyYXkmJnZvaWQgMCE9PWEudG9BcnJheT8oYz10aGlzLkJpbmRpbmdUeXBlLkhhc0Zyb21Ub0FycmF5LHRoaXMucmVzb2x2ZWRQcm9wZXJ0eT1hKTpBcnJheS5pc0FycmF5KGEpPyhjPXRoaXMuQmluZGluZ1R5cGUuRW50aXJlQXJyYXksdGhpcy5yZXNvbHZlZFByb3BlcnR5PWEpOnRoaXMucHJvcGVydHlOYW1lPW87dGhpcy5nZXRWYWx1ZT10aGlzLkdldHRlckJ5QmluZGluZ1R5cGVbY10sdGhpcy5zZXRWYWx1ZT10aGlzLlNldHRlckJ5QmluZGluZ1R5cGVBbmRWZXJzaW9uaW5nW2NdW2xdfXVuYmluZCgpe3RoaXMubm9kZT1udWxsLHRoaXMuZ2V0VmFsdWU9dGhpcy5fZ2V0VmFsdWVfdW5ib3VuZCx0aGlzLnNldFZhbHVlPXRoaXMuX3NldFZhbHVlX3VuYm91bmR9fXJldHVybiBuLkNvbXBvc2l0ZT1wRyxufSkoKTtMci5wcm90b3R5cGUuQmluZGluZ1R5cGU9e0RpcmVjdDowLEVudGlyZUFycmF5OjEsQXJyYXlFbGVtZW50OjIsSGFzRnJvbVRvQXJyYXk6M30sTHIucHJvdG90eXBlLlZlcnNpb25pbmc9e05vbmU6MCxOZWVkc1VwZGF0ZToxLE1hdHJpeFdvcmxkTmVlZHNVcGRhdGU6Mn0sTHIucHJvdG90eXBlLkdldHRlckJ5QmluZGluZ1R5cGU9W0xyLnByb3RvdHlwZS5fZ2V0VmFsdWVfZGlyZWN0LExyLnByb3RvdHlwZS5fZ2V0VmFsdWVfYXJyYXksTHIucHJvdG90eXBlLl9nZXRWYWx1ZV9hcnJheUVsZW1lbnQsTHIucHJvdG90eXBlLl9nZXRWYWx1ZV90b0FycmF5XSxMci5wcm90b3R5cGUuU2V0dGVyQnlCaW5kaW5nVHlwZUFuZFZlcnNpb25pbmc9W1tMci5wcm90b3R5cGUuX3NldFZhbHVlX2RpcmVjdCxMci5wcm90b3R5cGUuX3NldFZhbHVlX2RpcmVjdF9zZXROZWVkc1VwZGF0ZSxMci5wcm90b3R5cGUuX3NldFZhbHVlX2RpcmVjdF9zZXRNYXRyaXhXb3JsZE5lZWRzVXBkYXRlXSxbTHIucHJvdG90eXBlLl9zZXRWYWx1ZV9hcnJheSxMci5wcm90b3R5cGUuX3NldFZhbHVlX2FycmF5X3NldE5lZWRzVXBkYXRlLExyLnByb3RvdHlwZS5fc2V0VmFsdWVfYXJyYXlfc2V0TWF0cml4V29ybGROZWVkc1VwZGF0ZV0sW0xyLnByb3RvdHlwZS5fc2V0VmFsdWVfYXJyYXlFbGVtZW50LExyLnByb3RvdHlwZS5fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE5lZWRzVXBkYXRlLExyLnByb3RvdHlwZS5fc2V0VmFsdWVfYXJyYXlFbGVtZW50X3NldE1hdHJpeFdvcmxkTmVlZHNVcGRhdGVdLFtMci5wcm90b3R5cGUuX3NldFZhbHVlX2Zyb21BcnJheSxMci5wcm90b3R5cGUuX3NldFZhbHVlX2Zyb21BcnJheV9zZXROZWVkc1VwZGF0ZSxMci5wcm90b3R5cGUuX3NldFZhbHVlX2Zyb21BcnJheV9zZXRNYXRyaXhXb3JsZE5lZWRzVXBkYXRlXV0sY2xhc3MgZXh0ZW5kcyBFcHtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMuX3Jvb3Q9dCx0aGlzLl9pbml0TWVtb3J5TWFuYWdlcigpLHRoaXMuX2FjY3VJbmRleD0wLHRoaXMudGltZT0wLHRoaXMudGltZVNjYWxlPTF9X2JpbmRBY3Rpb24odCxlKXtsZXQgaT10Ll9sb2NhbFJvb3R8fHRoaXMuX3Jvb3Qscj10Ll9jbGlwLnRyYWNrcyxvPXIubGVuZ3RoLHM9dC5fcHJvcGVydHlCaW5kaW5ncyxhPXQuX2ludGVycG9sYW50cyxsPWkudXVpZCxjPXRoaXMuX2JpbmRpbmdzQnlSb290QW5kTmFtZSx1PWNbbF07dm9pZCAwPT09dSYmKHU9e30sY1tsXT11KTtmb3IobGV0IGQ9MDtkIT09bzsrK2Qpe2xldCBwPXJbZF0saD1wLm5hbWUsZj11W2hdO2lmKHZvaWQgMCE9PWYpKytmLnJlZmVyZW5jZUNvdW50LHNbZF09ZjtlbHNle2lmKGY9c1tkXSx2b2lkIDAhPT1mKXtudWxsPT09Zi5fY2FjaGVJbmRleCYmKCsrZi5yZWZlcmVuY2VDb3VudCx0aGlzLl9hZGRJbmFjdGl2ZUJpbmRpbmcoZixsLGgpKTtjb250aW51ZX1mPW5ldyBkRyhMci5jcmVhdGUoaSxoLGUmJmUuX3Byb3BlcnR5QmluZGluZ3NbZF0uYmluZGluZy5wYXJzZWRQYXRoKSxwLlZhbHVlVHlwZU5hbWUscC5nZXRWYWx1ZVNpemUoKSksKytmLnJlZmVyZW5jZUNvdW50LHRoaXMuX2FkZEluYWN0aXZlQmluZGluZyhmLGwsaCksc1tkXT1mfWFbZF0ucmVzdWx0QnVmZmVyPWYuYnVmZmVyfX1fYWN0aXZhdGVBY3Rpb24odCl7aWYoIXRoaXMuX2lzQWN0aXZlQWN0aW9uKHQpKXtpZihudWxsPT09dC5fY2FjaGVJbmRleCl7bGV0IGk9KHQuX2xvY2FsUm9vdHx8dGhpcy5fcm9vdCkudXVpZCxyPXQuX2NsaXAudXVpZCxvPXRoaXMuX2FjdGlvbnNCeUNsaXBbcl07dGhpcy5fYmluZEFjdGlvbih0LG8mJm8ua25vd25BY3Rpb25zWzBdKSx0aGlzLl9hZGRJbmFjdGl2ZUFjdGlvbih0LHIsaSl9bGV0IGU9dC5fcHJvcGVydHlCaW5kaW5ncztmb3IobGV0IGk9MCxyPWUubGVuZ3RoO2khPT1yOysraSl7bGV0IG89ZVtpXTswPT1vLnVzZUNvdW50KysmJih0aGlzLl9sZW5kQmluZGluZyhvKSxvLnNhdmVPcmlnaW5hbFN0YXRlKCkpfXRoaXMuX2xlbmRBY3Rpb24odCl9fV9kZWFjdGl2YXRlQWN0aW9uKHQpe2lmKHRoaXMuX2lzQWN0aXZlQWN0aW9uKHQpKXtsZXQgZT10Ll9wcm9wZXJ0eUJpbmRpbmdzO2ZvcihsZXQgaT0wLHI9ZS5sZW5ndGg7aSE9PXI7KytpKXtsZXQgbz1lW2ldOzA9PS0tby51c2VDb3VudCYmKG8ucmVzdG9yZU9yaWdpbmFsU3RhdGUoKSx0aGlzLl90YWtlQmFja0JpbmRpbmcobykpfXRoaXMuX3Rha2VCYWNrQWN0aW9uKHQpfX1faW5pdE1lbW9yeU1hbmFnZXIoKXt0aGlzLl9hY3Rpb25zPVtdLHRoaXMuX25BY3RpdmVBY3Rpb25zPTAsdGhpcy5fYWN0aW9uc0J5Q2xpcD17fSx0aGlzLl9iaW5kaW5ncz1bXSx0aGlzLl9uQWN0aXZlQmluZGluZ3M9MCx0aGlzLl9iaW5kaW5nc0J5Um9vdEFuZE5hbWU9e30sdGhpcy5fY29udHJvbEludGVycG9sYW50cz1bXSx0aGlzLl9uQWN0aXZlQ29udHJvbEludGVycG9sYW50cz0wO2xldCB0PXRoaXM7dGhpcy5zdGF0cz17YWN0aW9uczp7Z2V0IHRvdGFsKCl7cmV0dXJuIHQuX2FjdGlvbnMubGVuZ3RofSxnZXQgaW5Vc2UoKXtyZXR1cm4gdC5fbkFjdGl2ZUFjdGlvbnN9fSxiaW5kaW5nczp7Z2V0IHRvdGFsKCl7cmV0dXJuIHQuX2JpbmRpbmdzLmxlbmd0aH0sZ2V0IGluVXNlKCl7cmV0dXJuIHQuX25BY3RpdmVCaW5kaW5nc319LGNvbnRyb2xJbnRlcnBvbGFudHM6e2dldCB0b3RhbCgpe3JldHVybiB0Ll9jb250cm9sSW50ZXJwb2xhbnRzLmxlbmd0aH0sZ2V0IGluVXNlKCl7cmV0dXJuIHQuX25BY3RpdmVDb250cm9sSW50ZXJwb2xhbnRzfX19fV9pc0FjdGl2ZUFjdGlvbih0KXtsZXQgZT10Ll9jYWNoZUluZGV4O3JldHVybiBudWxsIT09ZSYmZTx0aGlzLl9uQWN0aXZlQWN0aW9uc31fYWRkSW5hY3RpdmVBY3Rpb24odCxlLGkpe2xldCByPXRoaXMuX2FjdGlvbnMsbz10aGlzLl9hY3Rpb25zQnlDbGlwLHM9b1tlXTtpZih2b2lkIDA9PT1zKXM9e2tub3duQWN0aW9uczpbdF0sYWN0aW9uQnlSb290Ont9fSx0Ll9ieUNsaXBDYWNoZUluZGV4PTAsb1tlXT1zO2Vsc2V7bGV0IGE9cy5rbm93bkFjdGlvbnM7dC5fYnlDbGlwQ2FjaGVJbmRleD1hLmxlbmd0aCxhLnB1c2godCl9dC5fY2FjaGVJbmRleD1yLmxlbmd0aCxyLnB1c2godCkscy5hY3Rpb25CeVJvb3RbaV09dH1fcmVtb3ZlSW5hY3RpdmVBY3Rpb24odCl7bGV0IGU9dGhpcy5fYWN0aW9ucyxpPWVbZS5sZW5ndGgtMV0scj10Ll9jYWNoZUluZGV4O2kuX2NhY2hlSW5kZXg9cixlW3JdPWksZS5wb3AoKSx0Ll9jYWNoZUluZGV4PW51bGw7bGV0IG89dC5fY2xpcC51dWlkLHM9dGhpcy5fYWN0aW9uc0J5Q2xpcCxhPXNbb10sbD1hLmtub3duQWN0aW9ucyxjPWxbbC5sZW5ndGgtMV0sdT10Ll9ieUNsaXBDYWNoZUluZGV4O2MuX2J5Q2xpcENhY2hlSW5kZXg9dSxsW3VdPWMsbC5wb3AoKSx0Ll9ieUNsaXBDYWNoZUluZGV4PW51bGwsZGVsZXRlIGEuYWN0aW9uQnlSb290Wyh0Ll9sb2NhbFJvb3R8fHRoaXMuX3Jvb3QpLnV1aWRdLDA9PT1sLmxlbmd0aCYmZGVsZXRlIHNbb10sdGhpcy5fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nc0ZvckFjdGlvbih0KX1fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nc0ZvckFjdGlvbih0KXtsZXQgZT10Ll9wcm9wZXJ0eUJpbmRpbmdzO2ZvcihsZXQgaT0wLHI9ZS5sZW5ndGg7aSE9PXI7KytpKXtsZXQgbz1lW2ldOzA9PS0tby5yZWZlcmVuY2VDb3VudCYmdGhpcy5fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nKG8pfX1fbGVuZEFjdGlvbih0KXtsZXQgZT10aGlzLl9hY3Rpb25zLGk9dC5fY2FjaGVJbmRleCxyPXRoaXMuX25BY3RpdmVBY3Rpb25zKyssbz1lW3JdO3QuX2NhY2hlSW5kZXg9cixlW3JdPXQsby5fY2FjaGVJbmRleD1pLGVbaV09b31fdGFrZUJhY2tBY3Rpb24odCl7bGV0IGU9dGhpcy5fYWN0aW9ucyxpPXQuX2NhY2hlSW5kZXgscj0tLXRoaXMuX25BY3RpdmVBY3Rpb25zLG89ZVtyXTt0Ll9jYWNoZUluZGV4PXIsZVtyXT10LG8uX2NhY2hlSW5kZXg9aSxlW2ldPW99X2FkZEluYWN0aXZlQmluZGluZyh0LGUsaSl7bGV0IHI9dGhpcy5fYmluZGluZ3NCeVJvb3RBbmROYW1lLG89dGhpcy5fYmluZGluZ3Mscz1yW2VdO3ZvaWQgMD09PXMmJihzPXt9LHJbZV09cyksc1tpXT10LHQuX2NhY2hlSW5kZXg9by5sZW5ndGgsby5wdXNoKHQpfV9yZW1vdmVJbmFjdGl2ZUJpbmRpbmcodCl7bGV0IGU9dGhpcy5fYmluZGluZ3MsaT10LmJpbmRpbmcscj1pLnJvb3ROb2RlLnV1aWQsbz1pLnBhdGgscz10aGlzLl9iaW5kaW5nc0J5Um9vdEFuZE5hbWUsYT1zW3JdLGw9ZVtlLmxlbmd0aC0xXSxjPXQuX2NhY2hlSW5kZXg7bC5fY2FjaGVJbmRleD1jLGVbY109bCxlLnBvcCgpLGRlbGV0ZSBhW29dLDA9PT1PYmplY3Qua2V5cyhhKS5sZW5ndGgmJmRlbGV0ZSBzW3JdfV9sZW5kQmluZGluZyh0KXtsZXQgZT10aGlzLl9iaW5kaW5ncyxpPXQuX2NhY2hlSW5kZXgscj10aGlzLl9uQWN0aXZlQmluZGluZ3MrKyxvPWVbcl07dC5fY2FjaGVJbmRleD1yLGVbcl09dCxvLl9jYWNoZUluZGV4PWksZVtpXT1vfV90YWtlQmFja0JpbmRpbmcodCl7bGV0IGU9dGhpcy5fYmluZGluZ3MsaT10Ll9jYWNoZUluZGV4LHI9LS10aGlzLl9uQWN0aXZlQmluZGluZ3Msbz1lW3JdO3QuX2NhY2hlSW5kZXg9cixlW3JdPXQsby5fY2FjaGVJbmRleD1pLGVbaV09b31fbGVuZENvbnRyb2xJbnRlcnBvbGFudCgpe2xldCB0PXRoaXMuX2NvbnRyb2xJbnRlcnBvbGFudHMsZT10aGlzLl9uQWN0aXZlQ29udHJvbEludGVycG9sYW50cysrLGk9dFtlXTtyZXR1cm4gdm9pZCAwPT09aSYmKGk9bmV3IE1rKG5ldyBGbG9hdDMyQXJyYXkoMiksbmV3IEZsb2F0MzJBcnJheSgyKSwxLHRoaXMuX2NvbnRyb2xJbnRlcnBvbGFudHNSZXN1bHRCdWZmZXIpLGkuX19jYWNoZUluZGV4PWUsdFtlXT1pKSxpfV90YWtlQmFja0NvbnRyb2xJbnRlcnBvbGFudCh0KXtsZXQgZT10aGlzLl9jb250cm9sSW50ZXJwb2xhbnRzLGk9dC5fX2NhY2hlSW5kZXgscj0tLXRoaXMuX25BY3RpdmVDb250cm9sSW50ZXJwb2xhbnRzLG89ZVtyXTt0Ll9fY2FjaGVJbmRleD1yLGVbcl09dCxvLl9fY2FjaGVJbmRleD1pLGVbaV09b31jbGlwQWN0aW9uKHQsZSxpKXtsZXQgcj1lfHx0aGlzLl9yb290LG89ci51dWlkLHM9InN0cmluZyI9PXR5cGVvZiB0P1NrLmZpbmRCeU5hbWUocix0KTp0LGE9bnVsbCE9PXM/cy51dWlkOnQsbD10aGlzLl9hY3Rpb25zQnlDbGlwW2FdLGM9bnVsbDtpZih2b2lkIDA9PT1pJiYoaT1udWxsIT09cz9zLmJsZW5kTW9kZToyNTAwKSx2b2lkIDAhPT1sKXtsZXQgZD1sLmFjdGlvbkJ5Um9vdFtvXTtpZih2b2lkIDAhPT1kJiZkLmJsZW5kTW9kZT09PWkpcmV0dXJuIGQ7Yz1sLmtub3duQWN0aW9uc1swXSxudWxsPT09cyYmKHM9Yy5fY2xpcCl9aWYobnVsbD09PXMpcmV0dXJuIG51bGw7bGV0IHU9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSxpPW51bGwscj1lLmJsZW5kTW9kZSl7dGhpcy5fbWl4ZXI9dCx0aGlzLl9jbGlwPWUsdGhpcy5fbG9jYWxSb290PWksdGhpcy5ibGVuZE1vZGU9cjtsZXQgbz1lLnRyYWNrcyxzPW8ubGVuZ3RoLGE9bmV3IEFycmF5KHMpLGw9e2VuZGluZ1N0YXJ0Om9iLGVuZGluZ0VuZDpvYn07Zm9yKGxldCBjPTA7YyE9PXM7KytjKXtsZXQgdT1vW2NdLmNyZWF0ZUludGVycG9sYW50KG51bGwpO2FbY109dSx1LnNldHRpbmdzPWx9dGhpcy5faW50ZXJwb2xhbnRTZXR0aW5ncz1sLHRoaXMuX2ludGVycG9sYW50cz1hLHRoaXMuX3Byb3BlcnR5QmluZGluZ3M9bmV3IEFycmF5KHMpLHRoaXMuX2NhY2hlSW5kZXg9bnVsbCx0aGlzLl9ieUNsaXBDYWNoZUluZGV4PW51bGwsdGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ9bnVsbCx0aGlzLl93ZWlnaHRJbnRlcnBvbGFudD1udWxsLHRoaXMubG9vcD0yMjAxLHRoaXMuX2xvb3BDb3VudD0tMSx0aGlzLl9zdGFydFRpbWU9bnVsbCx0aGlzLnRpbWU9MCx0aGlzLnRpbWVTY2FsZT0xLHRoaXMuX2VmZmVjdGl2ZVRpbWVTY2FsZT0xLHRoaXMud2VpZ2h0PTEsdGhpcy5fZWZmZWN0aXZlV2VpZ2h0PTEsdGhpcy5yZXBldGl0aW9ucz0xLzAsdGhpcy5wYXVzZWQ9ITEsdGhpcy5lbmFibGVkPSEwLHRoaXMuY2xhbXBXaGVuRmluaXNoZWQ9ITEsdGhpcy56ZXJvU2xvcGVBdFN0YXJ0PSEwLHRoaXMuemVyb1Nsb3BlQXRFbmQ9ITB9cGxheSgpe3JldHVybiB0aGlzLl9taXhlci5fYWN0aXZhdGVBY3Rpb24odGhpcyksdGhpc31zdG9wKCl7cmV0dXJuIHRoaXMuX21peGVyLl9kZWFjdGl2YXRlQWN0aW9uKHRoaXMpLHRoaXMucmVzZXQoKX1yZXNldCgpe3JldHVybiB0aGlzLnBhdXNlZD0hMSx0aGlzLmVuYWJsZWQ9ITAsdGhpcy50aW1lPTAsdGhpcy5fbG9vcENvdW50PS0xLHRoaXMuX3N0YXJ0VGltZT1udWxsLHRoaXMuc3RvcEZhZGluZygpLnN0b3BXYXJwaW5nKCl9aXNSdW5uaW5nKCl7cmV0dXJuIHRoaXMuZW5hYmxlZCYmIXRoaXMucGF1c2VkJiYwIT09dGhpcy50aW1lU2NhbGUmJm51bGw9PT10aGlzLl9zdGFydFRpbWUmJnRoaXMuX21peGVyLl9pc0FjdGl2ZUFjdGlvbih0aGlzKX1pc1NjaGVkdWxlZCgpe3JldHVybiB0aGlzLl9taXhlci5faXNBY3RpdmVBY3Rpb24odGhpcyl9c3RhcnRBdCh0KXtyZXR1cm4gdGhpcy5fc3RhcnRUaW1lPXQsdGhpc31zZXRMb29wKHQsZSl7cmV0dXJuIHRoaXMubG9vcD10LHRoaXMucmVwZXRpdGlvbnM9ZSx0aGlzfXNldEVmZmVjdGl2ZVdlaWdodCh0KXtyZXR1cm4gdGhpcy53ZWlnaHQ9dCx0aGlzLl9lZmZlY3RpdmVXZWlnaHQ9dGhpcy5lbmFibGVkP3Q6MCx0aGlzLnN0b3BGYWRpbmcoKX1nZXRFZmZlY3RpdmVXZWlnaHQoKXtyZXR1cm4gdGhpcy5fZWZmZWN0aXZlV2VpZ2h0fWZhZGVJbih0KXtyZXR1cm4gdGhpcy5fc2NoZWR1bGVGYWRpbmcodCwwLDEpfWZhZGVPdXQodCl7cmV0dXJuIHRoaXMuX3NjaGVkdWxlRmFkaW5nKHQsMSwwKX1jcm9zc0ZhZGVGcm9tKHQsZSxpKXtpZih0LmZhZGVPdXQoZSksdGhpcy5mYWRlSW4oZSksaSl7bGV0IHI9dGhpcy5fY2xpcC5kdXJhdGlvbixvPXQuX2NsaXAuZHVyYXRpb24sYT1yL287dC53YXJwKDEsby9yLGUpLHRoaXMud2FycChhLDEsZSl9cmV0dXJuIHRoaXN9Y3Jvc3NGYWRlVG8odCxlLGkpe3JldHVybiB0LmNyb3NzRmFkZUZyb20odGhpcyxlLGkpfXN0b3BGYWRpbmcoKXtsZXQgdD10aGlzLl93ZWlnaHRJbnRlcnBvbGFudDtyZXR1cm4gbnVsbCE9PXQmJih0aGlzLl93ZWlnaHRJbnRlcnBvbGFudD1udWxsLHRoaXMuX21peGVyLl90YWtlQmFja0NvbnRyb2xJbnRlcnBvbGFudCh0KSksdGhpc31zZXRFZmZlY3RpdmVUaW1lU2NhbGUodCl7cmV0dXJuIHRoaXMudGltZVNjYWxlPXQsdGhpcy5fZWZmZWN0aXZlVGltZVNjYWxlPXRoaXMucGF1c2VkPzA6dCx0aGlzLnN0b3BXYXJwaW5nKCl9Z2V0RWZmZWN0aXZlVGltZVNjYWxlKCl7cmV0dXJuIHRoaXMuX2VmZmVjdGl2ZVRpbWVTY2FsZX1zZXREdXJhdGlvbih0KXtyZXR1cm4gdGhpcy50aW1lU2NhbGU9dGhpcy5fY2xpcC5kdXJhdGlvbi90LHRoaXMuc3RvcFdhcnBpbmcoKX1zeW5jV2l0aCh0KXtyZXR1cm4gdGhpcy50aW1lPXQudGltZSx0aGlzLnRpbWVTY2FsZT10LnRpbWVTY2FsZSx0aGlzLnN0b3BXYXJwaW5nKCl9aGFsdCh0KXtyZXR1cm4gdGhpcy53YXJwKHRoaXMuX2VmZmVjdGl2ZVRpbWVTY2FsZSwwLHQpfXdhcnAodCxlLGkpe2xldCByPXRoaXMuX21peGVyLG89ci50aW1lLHM9dGhpcy50aW1lU2NhbGUsYT10aGlzLl90aW1lU2NhbGVJbnRlcnBvbGFudDtudWxsPT09YSYmKGE9ci5fbGVuZENvbnRyb2xJbnRlcnBvbGFudCgpLHRoaXMuX3RpbWVTY2FsZUludGVycG9sYW50PWEpO2xldCBsPWEucGFyYW1ldGVyUG9zaXRpb25zLGM9YS5zYW1wbGVWYWx1ZXM7cmV0dXJuIGxbMF09byxsWzFdPW8raSxjWzBdPXQvcyxjWzFdPWUvcyx0aGlzfXN0b3BXYXJwaW5nKCl7bGV0IHQ9dGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ7cmV0dXJuIG51bGwhPT10JiYodGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ9bnVsbCx0aGlzLl9taXhlci5fdGFrZUJhY2tDb250cm9sSW50ZXJwb2xhbnQodCkpLHRoaXN9Z2V0TWl4ZXIoKXtyZXR1cm4gdGhpcy5fbWl4ZXJ9Z2V0Q2xpcCgpe3JldHVybiB0aGlzLl9jbGlwfWdldFJvb3QoKXtyZXR1cm4gdGhpcy5fbG9jYWxSb290fHx0aGlzLl9taXhlci5fcm9vdH1fdXBkYXRlKHQsZSxpLHIpe2lmKCF0aGlzLmVuYWJsZWQpcmV0dXJuIHZvaWQgdGhpcy5fdXBkYXRlV2VpZ2h0KHQpO2xldCBvPXRoaXMuX3N0YXJ0VGltZTtpZihudWxsIT09byl7bGV0IGw9KHQtbykqaTtpZihsPDB8fDA9PT1pKXJldHVybjt0aGlzLl9zdGFydFRpbWU9bnVsbCxlPWkqbH1lKj10aGlzLl91cGRhdGVUaW1lU2NhbGUodCk7bGV0IHM9dGhpcy5fdXBkYXRlVGltZShlKSxhPXRoaXMuX3VwZGF0ZVdlaWdodCh0KTtpZihhPjApe2xldCBsPXRoaXMuX2ludGVycG9sYW50cyxjPXRoaXMuX3Byb3BlcnR5QmluZGluZ3M7aWYoMjUwMT09PXRoaXMuYmxlbmRNb2RlKWZvcihsZXQgdT0wLGQ9bC5sZW5ndGg7dSE9PWQ7Kyt1KWxbdV0uZXZhbHVhdGUocyksY1t1XS5hY2N1bXVsYXRlQWRkaXRpdmUoYSk7ZWxzZSBmb3IobGV0IHU9MCxkPWwubGVuZ3RoO3UhPT1kOysrdSlsW3VdLmV2YWx1YXRlKHMpLGNbdV0uYWNjdW11bGF0ZShyLGEpfX1fdXBkYXRlV2VpZ2h0KHQpe2xldCBlPTA7aWYodGhpcy5lbmFibGVkKXtlPXRoaXMud2VpZ2h0O2xldCBpPXRoaXMuX3dlaWdodEludGVycG9sYW50O2lmKG51bGwhPT1pKXtsZXQgcj1pLmV2YWx1YXRlKHQpWzBdO2UqPXIsdD5pLnBhcmFtZXRlclBvc2l0aW9uc1sxXSYmKHRoaXMuc3RvcEZhZGluZygpLDA9PT1yJiYodGhpcy5lbmFibGVkPSExKSl9fXJldHVybiB0aGlzLl9lZmZlY3RpdmVXZWlnaHQ9ZSxlfV91cGRhdGVUaW1lU2NhbGUodCl7bGV0IGU9MDtpZighdGhpcy5wYXVzZWQpe2U9dGhpcy50aW1lU2NhbGU7bGV0IGk9dGhpcy5fdGltZVNjYWxlSW50ZXJwb2xhbnQ7bnVsbCE9PWkmJihlKj1pLmV2YWx1YXRlKHQpWzBdLHQ+aS5wYXJhbWV0ZXJQb3NpdGlvbnNbMV0mJih0aGlzLnN0b3BXYXJwaW5nKCksMD09PWU/dGhpcy5wYXVzZWQ9ITA6dGhpcy50aW1lU2NhbGU9ZSkpfXJldHVybiB0aGlzLl9lZmZlY3RpdmVUaW1lU2NhbGU9ZSxlfV91cGRhdGVUaW1lKHQpe2xldCBlPXRoaXMuX2NsaXAuZHVyYXRpb24saT10aGlzLmxvb3Ascj10aGlzLnRpbWUrdCxvPXRoaXMuX2xvb3BDb3VudCxzPTIyMDI9PT1pO2lmKDA9PT10KXJldHVybi0xPT09bz9yOnMmJjE9PSgxJm8pP2UtcjpyO2lmKDIyMDA9PT1pKXstMT09PW8mJih0aGlzLl9sb29wQ291bnQ9MCx0aGlzLl9zZXRFbmRpbmdzKCEwLCEwLCExKSk7ZTp7aWYocj49ZSlyPWU7ZWxzZXtpZighKHI8MCkpe3RoaXMudGltZT1yO2JyZWFrIGV9cj0wfXRoaXMuY2xhbXBXaGVuRmluaXNoZWQ/dGhpcy5wYXVzZWQ9ITA6dGhpcy5lbmFibGVkPSExLHRoaXMudGltZT1yLHRoaXMuX21peGVyLmRpc3BhdGNoRXZlbnQoe3R5cGU6ImZpbmlzaGVkIixhY3Rpb246dGhpcyxkaXJlY3Rpb246dDwwPy0xOjF9KX19ZWxzZXtpZigtMT09PW8mJih0Pj0wPyhvPTAsdGhpcy5fc2V0RW5kaW5ncyghMCwwPT09dGhpcy5yZXBldGl0aW9ucyxzKSk6dGhpcy5fc2V0RW5kaW5ncygwPT09dGhpcy5yZXBldGl0aW9ucywhMCxzKSkscj49ZXx8cjwwKXtsZXQgYT1NYXRoLmZsb29yKHIvZSk7ci09ZSphLG8rPU1hdGguYWJzKGEpO2xldCBsPXRoaXMucmVwZXRpdGlvbnMtbztpZihsPD0wKXRoaXMuY2xhbXBXaGVuRmluaXNoZWQ/dGhpcy5wYXVzZWQ9ITA6dGhpcy5lbmFibGVkPSExLHI9dD4wP2U6MCx0aGlzLnRpbWU9cix0aGlzLl9taXhlci5kaXNwYXRjaEV2ZW50KHt0eXBlOiJmaW5pc2hlZCIsYWN0aW9uOnRoaXMsZGlyZWN0aW9uOnQ+MD8xOi0xfSk7ZWxzZXtpZigxPT09bCl7bGV0IGM9dDwwO3RoaXMuX3NldEVuZGluZ3MoYywhYyxzKX1lbHNlIHRoaXMuX3NldEVuZGluZ3MoITEsITEscyk7dGhpcy5fbG9vcENvdW50PW8sdGhpcy50aW1lPXIsdGhpcy5fbWl4ZXIuZGlzcGF0Y2hFdmVudCh7dHlwZToibG9vcCIsYWN0aW9uOnRoaXMsbG9vcERlbHRhOmF9KX19ZWxzZSB0aGlzLnRpbWU9cjtpZihzJiYxPT0oMSZvKSlyZXR1cm4gZS1yfXJldHVybiByfV9zZXRFbmRpbmdzKHQsZSxpKXtsZXQgcj10aGlzLl9pbnRlcnBvbGFudFNldHRpbmdzO2k/KHIuZW5kaW5nU3RhcnQ9c2Isci5lbmRpbmdFbmQ9c2IpOihyLmVuZGluZ1N0YXJ0PXQ/dGhpcy56ZXJvU2xvcGVBdFN0YXJ0P3NiOm9iOjI0MDIsci5lbmRpbmdFbmQ9ZT90aGlzLnplcm9TbG9wZUF0RW5kP3NiOm9iOjI0MDIpfV9zY2hlZHVsZUZhZGluZyh0LGUsaSl7bGV0IHI9dGhpcy5fbWl4ZXIsbz1yLnRpbWUscz10aGlzLl93ZWlnaHRJbnRlcnBvbGFudDtudWxsPT09cyYmKHM9ci5fbGVuZENvbnRyb2xJbnRlcnBvbGFudCgpLHRoaXMuX3dlaWdodEludGVycG9sYW50PXMpO2xldCBhPXMucGFyYW1ldGVyUG9zaXRpb25zLGw9cy5zYW1wbGVWYWx1ZXM7cmV0dXJuIGFbMF09byxsWzBdPWUsYVsxXT1vK3QsbFsxXT1pLHRoaXN9fSh0aGlzLHMsZSxpKTtyZXR1cm4gdGhpcy5fYmluZEFjdGlvbih1LGMpLHRoaXMuX2FkZEluYWN0aXZlQWN0aW9uKHUsYSxvKSx1fWV4aXN0aW5nQWN0aW9uKHQsZSl7bGV0IGk9ZXx8dGhpcy5fcm9vdCxyPWkudXVpZCxvPSJzdHJpbmciPT10eXBlb2YgdD9Tay5maW5kQnlOYW1lKGksdCk6dCxhPXRoaXMuX2FjdGlvbnNCeUNsaXBbbz9vLnV1aWQ6dF07cmV0dXJuIHZvaWQgMCE9PWEmJmEuYWN0aW9uQnlSb290W3JdfHxudWxsfXN0b3BBbGxBY3Rpb24oKXtsZXQgdD10aGlzLl9hY3Rpb25zO2ZvcihsZXQgaT10aGlzLl9uQWN0aXZlQWN0aW9ucy0xO2k+PTA7LS1pKXRbaV0uc3RvcCgpO3JldHVybiB0aGlzfXVwZGF0ZSh0KXtsZXQgZT10aGlzLl9hY3Rpb25zLGk9dGhpcy5fbkFjdGl2ZUFjdGlvbnMscj10aGlzLnRpbWUrPXQqPXRoaXMudGltZVNjYWxlLG89TWF0aC5zaWduKHQpLHM9dGhpcy5fYWNjdUluZGV4Xj0xO2ZvcihsZXQgYz0wO2MhPT1pOysrYyllW2NdLl91cGRhdGUocix0LG8scyk7bGV0IGE9dGhpcy5fYmluZGluZ3MsbD10aGlzLl9uQWN0aXZlQmluZGluZ3M7Zm9yKGxldCBjPTA7YyE9PWw7KytjKWFbY10uYXBwbHkocyk7cmV0dXJuIHRoaXN9c2V0VGltZSh0KXt0aGlzLnRpbWU9MDtmb3IobGV0IGU9MDtlPHRoaXMuX2FjdGlvbnMubGVuZ3RoO2UrKyl0aGlzLl9hY3Rpb25zW2VdLnRpbWU9MDtyZXR1cm4gdGhpcy51cGRhdGUodCl9Z2V0Um9vdCgpe3JldHVybiB0aGlzLl9yb290fXVuY2FjaGVDbGlwKHQpe2xldCBlPXRoaXMuX2FjdGlvbnMsaT10LnV1aWQscj10aGlzLl9hY3Rpb25zQnlDbGlwLG89cltpXTtpZih2b2lkIDAhPT1vKXtsZXQgcz1vLmtub3duQWN0aW9ucztmb3IobGV0IGE9MCxsPXMubGVuZ3RoO2EhPT1sOysrYSl7bGV0IGM9c1thXTt0aGlzLl9kZWFjdGl2YXRlQWN0aW9uKGMpO2xldCB1PWMuX2NhY2hlSW5kZXgsZD1lW2UubGVuZ3RoLTFdO2MuX2NhY2hlSW5kZXg9bnVsbCxjLl9ieUNsaXBDYWNoZUluZGV4PW51bGwsZC5fY2FjaGVJbmRleD11LGVbdV09ZCxlLnBvcCgpLHRoaXMuX3JlbW92ZUluYWN0aXZlQmluZGluZ3NGb3JBY3Rpb24oYyl9ZGVsZXRlIHJbaV19fXVuY2FjaGVSb290KHQpe2xldCBlPXQudXVpZCxpPXRoaXMuX2FjdGlvbnNCeUNsaXA7Zm9yKGxldCBzIGluIGkpe2xldCBsPWlbc10uYWN0aW9uQnlSb290W2VdO3ZvaWQgMCE9PWwmJih0aGlzLl9kZWFjdGl2YXRlQWN0aW9uKGwpLHRoaXMuX3JlbW92ZUluYWN0aXZlQWN0aW9uKGwpKX1sZXQgbz10aGlzLl9iaW5kaW5nc0J5Um9vdEFuZE5hbWVbZV07aWYodm9pZCAwIT09bylmb3IobGV0IHMgaW4gbyl7bGV0IGE9b1tzXTthLnJlc3RvcmVPcmlnaW5hbFN0YXRlKCksdGhpcy5fcmVtb3ZlSW5hY3RpdmVCaW5kaW5nKGEpfX11bmNhY2hlQWN0aW9uKHQsZSl7bGV0IGk9dGhpcy5leGlzdGluZ0FjdGlvbih0LGUpO251bGwhPT1pJiYodGhpcy5fZGVhY3RpdmF0ZUFjdGlvbihpKSx0aGlzLl9yZW1vdmVJbmFjdGl2ZUFjdGlvbihpKSl9fS5wcm90b3R5cGUuX2NvbnRyb2xJbnRlcnBvbGFudHNSZXN1bHRCdWZmZXI9bmV3IEZsb2F0MzJBcnJheSgxKTt2YXIgZkU9Y2xhc3N7Y29uc3RydWN0b3IodCl7InN0cmluZyI9PXR5cGVvZiB0JiYoY29uc29sZS53YXJuKCJUSFJFRS5Vbmlmb3JtOiBUeXBlIHBhcmFtZXRlciBpcyBubyBsb25nZXIgbmVlZGVkLiIpLHQ9YXJndW1lbnRzWzFdKSx0aGlzLnZhbHVlPXR9Y2xvbmUoKXtyZXR1cm4gbmV3IGZFKHZvaWQgMD09PXRoaXMudmFsdWUuY2xvbmU/dGhpcy52YWx1ZTp0aGlzLnZhbHVlLmNsb25lKCkpfX07KGNsYXNzIGV4dGVuZHMgWWd7Y29uc3RydWN0b3IodCxlLGk9MSl7c3VwZXIodCxlKSx0aGlzLm1lc2hQZXJBdHRyaWJ1dGU9aX1jb3B5KHQpe3JldHVybiBzdXBlci5jb3B5KHQpLHRoaXMubWVzaFBlckF0dHJpYnV0ZT10Lm1lc2hQZXJBdHRyaWJ1dGUsdGhpc31jbG9uZSh0KXtsZXQgZT1zdXBlci5jbG9uZSh0KTtyZXR1cm4gZS5tZXNoUGVyQXR0cmlidXRlPXRoaXMubWVzaFBlckF0dHJpYnV0ZSxlfXRvSlNPTih0KXtsZXQgZT1zdXBlci50b0pTT04odCk7cmV0dXJuIGUuaXNJbnN0YW5jZWRJbnRlcmxlYXZlZEJ1ZmZlcj0hMCxlLm1lc2hQZXJBdHRyaWJ1dGU9dGhpcy5tZXNoUGVyQXR0cmlidXRlLGV9fSkucHJvdG90eXBlLmlzSW5zdGFuY2VkSW50ZXJsZWF2ZWRCdWZmZXI9ITA7dmFyIFNkZT1uZXcgYXQsWmc9Y2xhc3N7Y29uc3RydWN0b3IodD1uZXcgYXQoMS8wLDEvMCksZT1uZXcgYXQoLTEvMCwtMS8wKSl7dGhpcy5taW49dCx0aGlzLm1heD1lfXNldCh0LGUpe3JldHVybiB0aGlzLm1pbi5jb3B5KHQpLHRoaXMubWF4LmNvcHkoZSksdGhpc31zZXRGcm9tUG9pbnRzKHQpe3RoaXMubWFrZUVtcHR5KCk7Zm9yKGxldCBlPTAsaT10Lmxlbmd0aDtlPGk7ZSsrKXRoaXMuZXhwYW5kQnlQb2ludCh0W2VdKTtyZXR1cm4gdGhpc31zZXRGcm9tQ2VudGVyQW5kU2l6ZSh0LGUpe2xldCBpPVNkZS5jb3B5KGUpLm11bHRpcGx5U2NhbGFyKC41KTtyZXR1cm4gdGhpcy5taW4uY29weSh0KS5zdWIoaSksdGhpcy5tYXguY29weSh0KS5hZGQoaSksdGhpc31jbG9uZSgpe3JldHVybihuZXcgdGhpcy5jb25zdHJ1Y3RvcikuY29weSh0aGlzKX1jb3B5KHQpe3JldHVybiB0aGlzLm1pbi5jb3B5KHQubWluKSx0aGlzLm1heC5jb3B5KHQubWF4KSx0aGlzfW1ha2VFbXB0eSgpe3JldHVybiB0aGlzLm1pbi54PXRoaXMubWluLnk9MS8wLHRoaXMubWF4Lng9dGhpcy5tYXgueT0tMS8wLHRoaXN9aXNFbXB0eSgpe3JldHVybiB0aGlzLm1heC54PHRoaXMubWluLnh8fHRoaXMubWF4Lnk8dGhpcy5taW4ueX1nZXRDZW50ZXIodCl7cmV0dXJuIHRoaXMuaXNFbXB0eSgpP3Quc2V0KDAsMCk6dC5hZGRWZWN0b3JzKHRoaXMubWluLHRoaXMubWF4KS5tdWx0aXBseVNjYWxhciguNSl9Z2V0U2l6ZSh0KXtyZXR1cm4gdGhpcy5pc0VtcHR5KCk/dC5zZXQoMCwwKTp0LnN1YlZlY3RvcnModGhpcy5tYXgsdGhpcy5taW4pfWV4cGFuZEJ5UG9pbnQodCl7cmV0dXJuIHRoaXMubWluLm1pbih0KSx0aGlzLm1heC5tYXgodCksdGhpc31leHBhbmRCeVZlY3Rvcih0KXtyZXR1cm4gdGhpcy5taW4uc3ViKHQpLHRoaXMubWF4LmFkZCh0KSx0aGlzfWV4cGFuZEJ5U2NhbGFyKHQpe3JldHVybiB0aGlzLm1pbi5hZGRTY2FsYXIoLXQpLHRoaXMubWF4LmFkZFNjYWxhcih0KSx0aGlzfWNvbnRhaW5zUG9pbnQodCl7cmV0dXJuISh0Lng8dGhpcy5taW4ueHx8dC54PnRoaXMubWF4Lnh8fHQueTx0aGlzLm1pbi55fHx0Lnk+dGhpcy5tYXgueSl9Y29udGFpbnNCb3godCl7cmV0dXJuIHRoaXMubWluLng8PXQubWluLngmJnQubWF4Lng8PXRoaXMubWF4LngmJnRoaXMubWluLnk8PXQubWluLnkmJnQubWF4Lnk8PXRoaXMubWF4Lnl9Z2V0UGFyYW1ldGVyKHQsZSl7cmV0dXJuIGUuc2V0KCh0LngtdGhpcy5taW4ueCkvKHRoaXMubWF4LngtdGhpcy5taW4ueCksKHQueS10aGlzLm1pbi55KS8odGhpcy5tYXgueS10aGlzLm1pbi55KSl9aW50ZXJzZWN0c0JveCh0KXtyZXR1cm4hKHQubWF4Lng8dGhpcy5taW4ueHx8dC5taW4ueD50aGlzLm1heC54fHx0Lm1heC55PHRoaXMubWluLnl8fHQubWluLnk+dGhpcy5tYXgueSl9Y2xhbXBQb2ludCh0LGUpe3JldHVybiBlLmNvcHkodCkuY2xhbXAodGhpcy5taW4sdGhpcy5tYXgpfWRpc3RhbmNlVG9Qb2ludCh0KXtyZXR1cm4gU2RlLmNvcHkodCkuY2xhbXAodGhpcy5taW4sdGhpcy5tYXgpLnN1Yih0KS5sZW5ndGgoKX1pbnRlcnNlY3QodCl7cmV0dXJuIHRoaXMubWluLm1heCh0Lm1pbiksdGhpcy5tYXgubWluKHQubWF4KSx0aGlzfXVuaW9uKHQpe3JldHVybiB0aGlzLm1pbi5taW4odC5taW4pLHRoaXMubWF4Lm1heCh0Lm1heCksdGhpc310cmFuc2xhdGUodCl7cmV0dXJuIHRoaXMubWluLmFkZCh0KSx0aGlzLm1heC5hZGQodCksdGhpc31lcXVhbHModCl7cmV0dXJuIHQubWluLmVxdWFscyh0aGlzLm1pbikmJnQubWF4LmVxdWFscyh0aGlzLm1heCl9fTtaZy5wcm90b3R5cGUuaXNCb3gyPSEwO3ZhciBmZj1uZXcgaWUsZWs9bmV3IFJuLHM4PW5ldyBSbjtmdW5jdGlvbiBLZGUobil7bGV0IHQ9W107biYmbi5pc0JvbmUmJnQucHVzaChuKTtmb3IobGV0IGU9MDtlPG4uY2hpbGRyZW4ubGVuZ3RoO2UrKyl0LnB1c2guYXBwbHkodCxLZGUobi5jaGlsZHJlbltlXSkpO3JldHVybiB0fXZhciBycWU9bmV3IEZsb2F0MzJBcnJheSgxKTtmdW5jdGlvbiBERyhuLHQsZSl7aWYoMT09PWUpcmV0dXJuIG5ldyB2bih0KTtsZXQgaT12Zyh0KTtpZighaSl0aHJvdyBuZXcgRXJyb3IoYGQzIGZhaWxlZCB0byByZWNvZ25pemUgdGhlIGNvbG9yOiAke3R9YCk7cmV0dXJuIG5ldyB2bihkeihpLG4pKDEtZSkpfW5ldyBJbnQzMkFycmF5KHJxZS5idWZmZXIpLHFhLmNyZWF0ZT1mdW5jdGlvbihuLHQpe3JldHVybiBjb25zb2xlLmxvZygiVEhSRUUuQ3VydmUuY3JlYXRlKCkgaGFzIGJlZW4gZGVwcmVjYXRlZCIpLG4ucHJvdG90eXBlPU9iamVjdC5jcmVhdGUocWEucHJvdG90eXBlKSxuLnByb3RvdHlwZS5jb25zdHJ1Y3Rvcj1uLG4ucHJvdG90eXBlLmdldFBvaW50PXQsbn0sbEUucHJvdG90eXBlLmZyb21Qb2ludHM9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUGF0aDogLmZyb21Qb2ludHMoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5zZXRGcm9tUG9pbnRzKCkuIiksdGhpcy5zZXRGcm9tUG9pbnRzKG4pfSxjbGFzcyBleHRlbmRzIHJFe2NvbnN0cnVjdG9yKHQ9MTAsZT0xMCxpPTQ0NzM5MjQscj04OTQ3ODQ4KXtpPW5ldyB2bihpKSxyPW5ldyB2bihyKTtsZXQgbz1lLzIscz10L2UsYT10LzIsbD1bXSxjPVtdO2ZvcihsZXQgcD0wLGg9MCxmPS1hO3A8PWU7cCsrLGYrPXMpe2wucHVzaCgtYSwwLGYsYSwwLGYpLGwucHVzaChmLDAsLWEsZiwwLGEpO2xldCBtPXA9PT1vP2k6cjttLnRvQXJyYXkoYyxoKSxoKz0zLG0udG9BcnJheShjLGgpLGgrPTMsbS50b0FycmF5KGMsaCksaCs9MyxtLnRvQXJyYXkoYyxoKSxoKz0zfWxldCB1PW5ldyBucjt1LnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyBKcihsLDMpKSx1LnNldEF0dHJpYnV0ZSgiY29sb3IiLG5ldyBKcihjLDMpKSxzdXBlcih1LG5ldyBBcCh7dmVydGV4Q29sb3JzOiEwLHRvbmVNYXBwZWQ6ITF9KSksdGhpcy50eXBlPSJHcmlkSGVscGVyIn19LnByb3RvdHlwZS5zZXRDb2xvcnM9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5HcmlkSGVscGVyOiBzZXRDb2xvcnMoKSBoYXMgYmVlbiBkZXByZWNhdGVkLCBwYXNzIHRoZW0gaW4gdGhlIGNvbnN0cnVjdG9yIGluc3RlYWQuIil9LGNsYXNzIGV4dGVuZHMgckV7Y29uc3RydWN0b3IodCl7bGV0IGU9S2RlKHQpLGk9bmV3IG5yLHI9W10sbz1bXSxzPW5ldyB2bigwLDAsMSksYT1uZXcgdm4oMCwxLDApO2ZvcihsZXQgYz0wO2M8ZS5sZW5ndGg7YysrKXtsZXQgdT1lW2NdO3UucGFyZW50JiZ1LnBhcmVudC5pc0JvbmUmJihyLnB1c2goMCwwLDApLHIucHVzaCgwLDAsMCksby5wdXNoKHMucixzLmcscy5iKSxvLnB1c2goYS5yLGEuZyxhLmIpKX1pLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLG5ldyBKcihyLDMpKSxpLnNldEF0dHJpYnV0ZSgiY29sb3IiLG5ldyBKcihvLDMpKSxzdXBlcihpLG5ldyBBcCh7dmVydGV4Q29sb3JzOiEwLGRlcHRoVGVzdDohMSxkZXB0aFdyaXRlOiExLHRvbmVNYXBwZWQ6ITEsdHJhbnNwYXJlbnQ6ITB9KSksdGhpcy50eXBlPSJTa2VsZXRvbkhlbHBlciIsdGhpcy5pc1NrZWxldG9uSGVscGVyPSEwLHRoaXMucm9vdD10LHRoaXMuYm9uZXM9ZSx0aGlzLm1hdHJpeD10Lm1hdHJpeFdvcmxkLHRoaXMubWF0cml4QXV0b1VwZGF0ZT0hMX11cGRhdGVNYXRyaXhXb3JsZCh0KXtsZXQgZT10aGlzLmJvbmVzLGk9dGhpcy5nZW9tZXRyeSxyPWkuZ2V0QXR0cmlidXRlKCJwb3NpdGlvbiIpO3M4LmNvcHkodGhpcy5yb290Lm1hdHJpeFdvcmxkKS5pbnZlcnQoKTtmb3IobGV0IG89MCxzPTA7bzxlLmxlbmd0aDtvKyspe2xldCBhPWVbb107YS5wYXJlbnQmJmEucGFyZW50LmlzQm9uZSYmKGVrLm11bHRpcGx5TWF0cmljZXMoczgsYS5tYXRyaXhXb3JsZCksZmYuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKGVrKSxyLnNldFhZWihzLGZmLngsZmYueSxmZi56KSxlay5tdWx0aXBseU1hdHJpY2VzKHM4LGEucGFyZW50Lm1hdHJpeFdvcmxkKSxmZi5zZXRGcm9tTWF0cml4UG9zaXRpb24oZWspLHIuc2V0WFlaKHMrMSxmZi54LGZmLnksZmYueikscys9Mil9aS5nZXRBdHRyaWJ1dGUoInBvc2l0aW9uIikubmVlZHNVcGRhdGU9ITAsc3VwZXIudXBkYXRlTWF0cml4V29ybGQodCl9fS5wcm90b3R5cGUudXBkYXRlPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuU2tlbGV0b25IZWxwZXI6IHVwZGF0ZSgpIG5vIGxvbmdlciBuZWVkcyB0byBiZSBjYWxsZWQuIil9LERkLnByb3RvdHlwZS5leHRyYWN0VXJsQmFzZT1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5Mb2FkZXI6IC5leHRyYWN0VXJsQmFzZSgpIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSBUSFJFRS5Mb2FkZXJVdGlscy5leHRyYWN0VXJsQmFzZSgpIGluc3RlYWQuIiksaUcuZXh0cmFjdFVybEJhc2Uobil9LERkLkhhbmRsZXJzPXthZGQ6ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5Mb2FkZXI6IEhhbmRsZXJzLmFkZCgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBMb2FkaW5nTWFuYWdlci5hZGRIYW5kbGVyKCkgaW5zdGVhZC4iKX0sZ2V0OmZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTG9hZGVyOiBIYW5kbGVycy5nZXQoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgTG9hZGluZ01hbmFnZXIuZ2V0SGFuZGxlcigpIGluc3RlYWQuIil9fSxaZy5wcm90b3R5cGUuY2VudGVyPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDI6IC5jZW50ZXIoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRDZW50ZXIoKS4iKSx0aGlzLmdldENlbnRlcihuKX0sWmcucHJvdG90eXBlLmVtcHR5PWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MjogLmVtcHR5KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaXNFbXB0eSgpLiIpLHRoaXMuaXNFbXB0eSgpfSxaZy5wcm90b3R5cGUuaXNJbnRlcnNlY3Rpb25Cb3g9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MjogLmlzSW50ZXJzZWN0aW9uQm94KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaW50ZXJzZWN0c0JveCgpLiIpLHRoaXMuaW50ZXJzZWN0c0JveChuKX0sWmcucHJvdG90eXBlLnNpemU9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MjogLnNpemUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRTaXplKCkuIiksdGhpcy5nZXRTaXplKG4pfSxUbC5wcm90b3R5cGUuY2VudGVyPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDM6IC5jZW50ZXIoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRDZW50ZXIoKS4iKSx0aGlzLmdldENlbnRlcihuKX0sVGwucHJvdG90eXBlLmVtcHR5PWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MzogLmVtcHR5KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaXNFbXB0eSgpLiIpLHRoaXMuaXNFbXB0eSgpfSxUbC5wcm90b3R5cGUuaXNJbnRlcnNlY3Rpb25Cb3g9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQm94MzogLmlzSW50ZXJzZWN0aW9uQm94KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaW50ZXJzZWN0c0JveCgpLiIpLHRoaXMuaW50ZXJzZWN0c0JveChuKX0sVGwucHJvdG90eXBlLmlzSW50ZXJzZWN0aW9uU3BoZXJlPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDM6IC5pc0ludGVyc2VjdGlvblNwaGVyZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmludGVyc2VjdHNTcGhlcmUoKS4iKSx0aGlzLmludGVyc2VjdHNTcGhlcmUobil9LFRsLnByb3RvdHlwZS5zaXplPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJveDM6IC5zaXplKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0U2l6ZSgpLiIpLHRoaXMuZ2V0U2l6ZShuKX0seGYucHJvdG90eXBlLmVtcHR5PWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuU3BoZXJlOiAuZW1wdHkoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5pc0VtcHR5KCkuIiksdGhpcy5pc0VtcHR5KCl9LGdiLnByb3RvdHlwZS5zZXRGcm9tTWF0cml4PWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkZydXN0dW06IC5zZXRGcm9tTWF0cml4KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuc2V0RnJvbVByb2plY3Rpb25NYXRyaXgoKS4iKSx0aGlzLnNldEZyb21Qcm9qZWN0aW9uTWF0cml4KG4pfSxKby5wcm90b3R5cGUuZmxhdHRlblRvQXJyYXlPZmZzZXQ9ZnVuY3Rpb24obix0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXgzOiAuZmxhdHRlblRvQXJyYXlPZmZzZXQoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnRvQXJyYXkoKSBpbnN0ZWFkLiIpLHRoaXMudG9BcnJheShuLHQpfSxKby5wcm90b3R5cGUubXVsdGlwbHlWZWN0b3IzPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDM6IC5tdWx0aXBseVZlY3RvcjMoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgdmVjdG9yLmFwcGx5TWF0cml4MyggbWF0cml4ICkgaW5zdGVhZC4iKSxuLmFwcGx5TWF0cml4Myh0aGlzKX0sSm8ucHJvdG90eXBlLm11bHRpcGx5VmVjdG9yM0FycmF5PWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWF0cml4MzogLm11bHRpcGx5VmVjdG9yM0FycmF5KCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sSm8ucHJvdG90eXBlLmFwcGx5VG9CdWZmZXJBdHRyaWJ1dGU9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4MzogLmFwcGx5VG9CdWZmZXJBdHRyaWJ1dGUoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgYXR0cmlidXRlLmFwcGx5TWF0cml4MyggbWF0cml4ICkgaW5zdGVhZC4iKSxuLmFwcGx5TWF0cml4Myh0aGlzKX0sSm8ucHJvdG90eXBlLmFwcGx5VG9WZWN0b3IzQXJyYXk9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXgzOiAuYXBwbHlUb1ZlY3RvcjNBcnJheSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9LEpvLnByb3RvdHlwZS5nZXRJbnZlcnNlPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDM6IC5nZXRJbnZlcnNlKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIG1hdHJpeEludi5jb3B5KCBtYXRyaXggKS5pbnZlcnQoKTsgaW5zdGVhZC4iKSx0aGlzLmNvcHkobikuaW52ZXJ0KCl9LFJuLnByb3RvdHlwZS5leHRyYWN0UG9zaXRpb249ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLmV4dHJhY3RQb3NpdGlvbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmNvcHlQb3NpdGlvbigpLiIpLHRoaXMuY29weVBvc2l0aW9uKG4pfSxSbi5wcm90b3R5cGUuZmxhdHRlblRvQXJyYXlPZmZzZXQ9ZnVuY3Rpb24obix0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAuZmxhdHRlblRvQXJyYXlPZmZzZXQoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnRvQXJyYXkoKSBpbnN0ZWFkLiIpLHRoaXMudG9BcnJheShuLHQpfSxSbi5wcm90b3R5cGUuZ2V0UG9zaXRpb249ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAuZ2V0UG9zaXRpb24oKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgVmVjdG9yMy5zZXRGcm9tTWF0cml4UG9zaXRpb24oIG1hdHJpeCApIGluc3RlYWQuIiksKG5ldyBpZSkuc2V0RnJvbU1hdHJpeENvbHVtbih0aGlzLDMpfSxSbi5wcm90b3R5cGUuc2V0Um90YXRpb25Gcm9tUXVhdGVybmlvbj1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAuc2V0Um90YXRpb25Gcm9tUXVhdGVybmlvbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLm1ha2VSb3RhdGlvbkZyb21RdWF0ZXJuaW9uKCkuIiksdGhpcy5tYWtlUm90YXRpb25Gcm9tUXVhdGVybmlvbihuKX0sUm4ucHJvdG90eXBlLm11bHRpcGx5VG9BcnJheT1mdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLm11bHRpcGx5VG9BcnJheSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9LFJuLnByb3RvdHlwZS5tdWx0aXBseVZlY3RvcjM9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLm11bHRpcGx5VmVjdG9yMygpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSB2ZWN0b3IuYXBwbHlNYXRyaXg0KCBtYXRyaXggKSBpbnN0ZWFkLiIpLG4uYXBwbHlNYXRyaXg0KHRoaXMpfSxSbi5wcm90b3R5cGUubXVsdGlwbHlWZWN0b3I0PWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5tdWx0aXBseVZlY3RvcjQoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgdmVjdG9yLmFwcGx5TWF0cml4NCggbWF0cml4ICkgaW5zdGVhZC4iKSxuLmFwcGx5TWF0cml4NCh0aGlzKX0sUm4ucHJvdG90eXBlLm11bHRpcGx5VmVjdG9yM0FycmF5PWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWF0cml4NDogLm11bHRpcGx5VmVjdG9yM0FycmF5KCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sUm4ucHJvdG90eXBlLnJvdGF0ZUF4aXM9ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5NYXRyaXg0OiAucm90YXRlQXhpcygpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBWZWN0b3IzLnRyYW5zZm9ybURpcmVjdGlvbiggbWF0cml4ICkgaW5zdGVhZC4iKSxuLnRyYW5zZm9ybURpcmVjdGlvbih0aGlzKX0sUm4ucHJvdG90eXBlLmNyb3NzVmVjdG9yPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5jcm9zc1ZlY3RvcigpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSB2ZWN0b3IuYXBwbHlNYXRyaXg0KCBtYXRyaXggKSBpbnN0ZWFkLiIpLG4uYXBwbHlNYXRyaXg0KHRoaXMpfSxSbi5wcm90b3R5cGUudHJhbnNsYXRlPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWF0cml4NDogLnRyYW5zbGF0ZSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9LFJuLnByb3RvdHlwZS5yb3RhdGVYPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuTWF0cml4NDogLnJvdGF0ZVgoKSBoYXMgYmVlbiByZW1vdmVkLiIpfSxSbi5wcm90b3R5cGUucm90YXRlWT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1hdHJpeDQ6IC5yb3RhdGVZKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sUm4ucHJvdG90eXBlLnJvdGF0ZVo9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXg0OiAucm90YXRlWigpIGhhcyBiZWVuIHJlbW92ZWQuIil9LFJuLnByb3RvdHlwZS5yb3RhdGVCeUF4aXM9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXg0OiAucm90YXRlQnlBeGlzKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sUm4ucHJvdG90eXBlLmFwcGx5VG9CdWZmZXJBdHRyaWJ1dGU9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLmFwcGx5VG9CdWZmZXJBdHRyaWJ1dGUoKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgYXR0cmlidXRlLmFwcGx5TWF0cml4NCggbWF0cml4ICkgaW5zdGVhZC4iKSxuLmFwcGx5TWF0cml4NCh0aGlzKX0sUm4ucHJvdG90eXBlLmFwcGx5VG9WZWN0b3IzQXJyYXk9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NYXRyaXg0OiAuYXBwbHlUb1ZlY3RvcjNBcnJheSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9LFJuLnByb3RvdHlwZS5tYWtlRnJ1c3R1bT1mdW5jdGlvbihuLHQsZSxpLHIsbyl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuTWF0cml4NDogLm1ha2VGcnVzdHVtKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIC5tYWtlUGVyc3BlY3RpdmUoIGxlZnQsIHJpZ2h0LCB0b3AsIGJvdHRvbSwgbmVhciwgZmFyICkgaW5zdGVhZC4iKSx0aGlzLm1ha2VQZXJzcGVjdGl2ZShuLHQsaSxlLHIsbyl9LFJuLnByb3RvdHlwZS5nZXRJbnZlcnNlPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdHJpeDQ6IC5nZXRJbnZlcnNlKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIG1hdHJpeEludi5jb3B5KCBtYXRyaXggKS5pbnZlcnQoKTsgaW5zdGVhZC4iKSx0aGlzLmNvcHkobikuaW52ZXJ0KCl9LHV1LnByb3RvdHlwZS5pc0ludGVyc2VjdGlvbkxpbmU9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUGxhbmU6IC5pc0ludGVyc2VjdGlvbkxpbmUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5pbnRlcnNlY3RzTGluZSgpLiIpLHRoaXMuaW50ZXJzZWN0c0xpbmUobil9LHFzLnByb3RvdHlwZS5tdWx0aXBseVZlY3RvcjM9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUXVhdGVybmlvbjogLm11bHRpcGx5VmVjdG9yMygpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBpcyBub3cgdmVjdG9yLmFwcGx5UXVhdGVybmlvbiggcXVhdGVybmlvbiApIGluc3RlYWQuIiksbi5hcHBseVF1YXRlcm5pb24odGhpcyl9LHFzLnByb3RvdHlwZS5pbnZlcnNlPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuUXVhdGVybmlvbjogLmludmVyc2UoKSBoYXMgYmVlbiByZW5hbWVkIHRvIGludmVydCgpLiIpLHRoaXMuaW52ZXJ0KCl9LENmLnByb3RvdHlwZS5pc0ludGVyc2VjdGlvbkJveD1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5SYXk6IC5pc0ludGVyc2VjdGlvbkJveCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmludGVyc2VjdHNCb3goKS4iKSx0aGlzLmludGVyc2VjdHNCb3gobil9LENmLnByb3RvdHlwZS5pc0ludGVyc2VjdGlvblBsYW5lPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlJheTogLmlzSW50ZXJzZWN0aW9uUGxhbmUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5pbnRlcnNlY3RzUGxhbmUoKS4iKSx0aGlzLmludGVyc2VjdHNQbGFuZShuKX0sQ2YucHJvdG90eXBlLmlzSW50ZXJzZWN0aW9uU3BoZXJlPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlJheTogLmlzSW50ZXJzZWN0aW9uU3BoZXJlKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuaW50ZXJzZWN0c1NwaGVyZSgpLiIpLHRoaXMuaW50ZXJzZWN0c1NwaGVyZShuKX0sbG8ucHJvdG90eXBlLmFyZWE9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5UcmlhbmdsZTogLmFyZWEoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRBcmVhKCkuIiksdGhpcy5nZXRBcmVhKCl9LGxvLnByb3RvdHlwZS5iYXJ5Y29vcmRGcm9tUG9pbnQ9ZnVuY3Rpb24obix0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5UcmlhbmdsZTogLmJhcnljb29yZEZyb21Qb2ludCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldEJhcnljb29yZCgpLiIpLHRoaXMuZ2V0QmFyeWNvb3JkKG4sdCl9LGxvLnByb3RvdHlwZS5taWRwb2ludD1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5UcmlhbmdsZTogLm1pZHBvaW50KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0TWlkcG9pbnQoKS4iKSx0aGlzLmdldE1pZHBvaW50KG4pfSxsby5wcm90b3R5cGVub3JtYWw9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVHJpYW5nbGU6IC5ub3JtYWwoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXROb3JtYWwoKS4iKSx0aGlzLmdldE5vcm1hbChuKX0sbG8ucHJvdG90eXBlLnBsYW5lPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlRyaWFuZ2xlOiAucGxhbmUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRQbGFuZSgpLiIpLHRoaXMuZ2V0UGxhbmUobil9LGxvLmJhcnljb29yZEZyb21Qb2ludD1mdW5jdGlvbihuLHQsZSxpLHIpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlRyaWFuZ2xlOiAuYmFyeWNvb3JkRnJvbVBvaW50KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ2V0QmFyeWNvb3JkKCkuIiksbG8uZ2V0QmFyeWNvb3JkKG4sdCxlLGkscil9LGxvLm5vcm1hbD1mdW5jdGlvbihuLHQsZSxpKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5UcmlhbmdsZTogLm5vcm1hbCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdldE5vcm1hbCgpLiIpLGxvLmdldE5vcm1hbChuLHQsZSxpKX0sSXAucHJvdG90eXBlLmV4dHJhY3RBbGxQb2ludHM9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuU2hhcGU6IC5leHRyYWN0QWxsUG9pbnRzKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIC5leHRyYWN0UG9pbnRzKCkgaW5zdGVhZC4iKSx0aGlzLmV4dHJhY3RQb2ludHMobil9LElwLnByb3RvdHlwZS5leHRydWRlPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlNoYXBlOiAuZXh0cnVkZSgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBFeHRydWRlR2VvbWV0cnkoKSBpbnN0ZWFkLiIpLG5ldyBTZih0aGlzLG4pfSxJcC5wcm90b3R5cGUubWFrZUdlb21ldHJ5PWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlNoYXBlOiAubWFrZUdlb21ldHJ5KCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIFNoYXBlR2VvbWV0cnkoKSBpbnN0ZWFkLiIpLG5ldyBRZyh0aGlzLG4pfSxhdC5wcm90b3R5cGUuZnJvbUF0dHJpYnV0ZT1mdW5jdGlvbihuLHQsZSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMjogLmZyb21BdHRyaWJ1dGUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5mcm9tQnVmZmVyQXR0cmlidXRlKCkuIiksdGhpcy5mcm9tQnVmZmVyQXR0cmlidXRlKG4sdCxlKX0sYXQucHJvdG90eXBlLmRpc3RhbmNlVG9NYW5oYXR0YW49ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMjogLmRpc3RhbmNlVG9NYW5oYXR0YW4oKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5tYW5oYXR0YW5EaXN0YW5jZVRvKCkuIiksdGhpcy5tYW5oYXR0YW5EaXN0YW5jZVRvKG4pfSxhdC5wcm90b3R5cGUubGVuZ3RoTWFuaGF0dGFuPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMjogLmxlbmd0aE1hbmhhdHRhbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLm1hbmhhdHRhbkxlbmd0aCgpLiIpLHRoaXMubWFuaGF0dGFuTGVuZ3RoKCl9LGllLnByb3RvdHlwZS5zZXRFdWxlckZyb21Sb3RhdGlvbk1hdHJpeD1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlZlY3RvcjM6IC5zZXRFdWxlckZyb21Sb3RhdGlvbk1hdHJpeCgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSBFdWxlci5zZXRGcm9tUm90YXRpb25NYXRyaXgoKSBpbnN0ZWFkLiIpfSxpZS5wcm90b3R5cGUuc2V0RXVsZXJGcm9tUXVhdGVybmlvbj1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlZlY3RvcjM6IC5zZXRFdWxlckZyb21RdWF0ZXJuaW9uKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIEV1bGVyLnNldEZyb21RdWF0ZXJuaW9uKCkgaW5zdGVhZC4iKX0saWUucHJvdG90eXBlLmdldFBvc2l0aW9uRnJvbU1hdHJpeD1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuZ2V0UG9zaXRpb25Gcm9tTWF0cml4KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuc2V0RnJvbU1hdHJpeFBvc2l0aW9uKCkuIiksdGhpcy5zZXRGcm9tTWF0cml4UG9zaXRpb24obil9LGllLnByb3RvdHlwZS5nZXRTY2FsZUZyb21NYXRyaXg9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVmVjdG9yMzogLmdldFNjYWxlRnJvbU1hdHJpeCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLnNldEZyb21NYXRyaXhTY2FsZSgpLiIpLHRoaXMuc2V0RnJvbU1hdHJpeFNjYWxlKG4pfSxpZS5wcm90b3R5cGUuZ2V0Q29sdW1uRnJvbU1hdHJpeD1mdW5jdGlvbihuLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5nZXRDb2x1bW5Gcm9tTWF0cml4KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuc2V0RnJvbU1hdHJpeENvbHVtbigpLiIpLHRoaXMuc2V0RnJvbU1hdHJpeENvbHVtbih0LG4pfSxpZS5wcm90b3R5cGUuYXBwbHlQcm9qZWN0aW9uPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjM6IC5hcHBseVByb2plY3Rpb24oKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLmFwcGx5TWF0cml4NCggbSApIGluc3RlYWQuIiksdGhpcy5hcHBseU1hdHJpeDQobil9LGllLnByb3RvdHlwZS5mcm9tQXR0cmlidXRlPWZ1bmN0aW9uKG4sdCxlKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuZnJvbUF0dHJpYnV0ZSgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmZyb21CdWZmZXJBdHRyaWJ1dGUoKS4iKSx0aGlzLmZyb21CdWZmZXJBdHRyaWJ1dGUobix0LGUpfSxpZS5wcm90b3R5cGUuZGlzdGFuY2VUb01hbmhhdHRhbj1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAuZGlzdGFuY2VUb01hbmhhdHRhbigpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLm1hbmhhdHRhbkRpc3RhbmNlVG8oKS4iKSx0aGlzLm1hbmhhdHRhbkRpc3RhbmNlVG8obil9LGllLnByb3RvdHlwZS5sZW5ndGhNYW5oYXR0YW49ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3IzOiAubGVuZ3RoTWFuaGF0dGFuKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAubWFuaGF0dGFuTGVuZ3RoKCkuIiksdGhpcy5tYW5oYXR0YW5MZW5ndGgoKX0sYXIucHJvdG90eXBlLmZyb21BdHRyaWJ1dGU9ZnVuY3Rpb24obix0LGUpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLlZlY3RvcjQ6IC5mcm9tQXR0cmlidXRlKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZnJvbUJ1ZmZlckF0dHJpYnV0ZSgpLiIpLHRoaXMuZnJvbUJ1ZmZlckF0dHJpYnV0ZShuLHQsZSl9LGFyLnByb3RvdHlwZS5sZW5ndGhNYW5oYXR0YW49ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5WZWN0b3I0OiAubGVuZ3RoTWFuaGF0dGFuKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAubWFuaGF0dGFuTGVuZ3RoKCkuIiksdGhpcy5tYW5oYXR0YW5MZW5ndGgoKX0sWGkucHJvdG90eXBlLmdldENoaWxkQnlOYW1lPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdDNEOiAuZ2V0Q2hpbGRCeU5hbWUoKSBoYXMgYmVlbiByZW5hbWVkIHRvIC5nZXRPYmplY3RCeU5hbWUoKS4iKSx0aGlzLmdldE9iamVjdEJ5TmFtZShuKX0sWGkucHJvdG90eXBlLnJlbmRlckRlcHRoPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5PYmplY3QzRDogLnJlbmRlckRlcHRoIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSAucmVuZGVyT3JkZXIsIGluc3RlYWQuIil9LFhpLnByb3RvdHlwZS50cmFuc2xhdGU9ZnVuY3Rpb24obix0KXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5PYmplY3QzRDogLnRyYW5zbGF0ZSgpIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSAudHJhbnNsYXRlT25BeGlzKCBheGlzLCBkaXN0YW5jZSApIGluc3RlYWQuIiksdGhpcy50cmFuc2xhdGVPbkF4aXModCxuKX0sWGkucHJvdG90eXBlLmdldFdvcmxkUm90YXRpb249ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5PYmplY3QzRDogLmdldFdvcmxkUm90YXRpb24oKSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgVEhSRUUuT2JqZWN0M0QuZ2V0V29ybGRRdWF0ZXJuaW9uKCB0YXJnZXQgKSBpbnN0ZWFkLiIpfSxYaS5wcm90b3R5cGUuYXBwbHlNYXRyaXg9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuT2JqZWN0M0Q6IC5hcHBseU1hdHJpeCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmFwcGx5TWF0cml4NCgpLiIpLHRoaXMuYXBwbHlNYXRyaXg0KG4pfSxPYmplY3QuZGVmaW5lUHJvcGVydGllcyhYaS5wcm90b3R5cGUse2V1bGVyT3JkZXI6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdDNEOiAuZXVsZXJPcmRlciBpcyBub3cgLnJvdGF0aW9uLm9yZGVyLiIpLHRoaXMucm90YXRpb24ub3JkZXJ9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLk9iamVjdDNEOiAuZXVsZXJPcmRlciBpcyBub3cgLnJvdGF0aW9uLm9yZGVyLiIpLHRoaXMucm90YXRpb24ub3JkZXI9bn19LHVzZVF1YXRlcm5pb246e2dldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuT2JqZWN0M0Q6IC51c2VRdWF0ZXJuaW9uIGhhcyBiZWVuIHJlbW92ZWQuIFRoZSBsaWJyYXJ5IG5vdyB1c2VzIHF1YXRlcm5pb25zIGJ5IGRlZmF1bHQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuT2JqZWN0M0Q6IC51c2VRdWF0ZXJuaW9uIGhhcyBiZWVuIHJlbW92ZWQuIFRoZSBsaWJyYXJ5IG5vdyB1c2VzIHF1YXRlcm5pb25zIGJ5IGRlZmF1bHQuIil9fX0pLFZvLnByb3RvdHlwZS5zZXREcmF3TW9kZT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLk1lc2g6IC5zZXREcmF3TW9kZSgpIGhhcyBiZWVuIHJlbW92ZWQuIFRoZSByZW5kZXJlciBub3cgYWx3YXlzIGFzc3VtZXMgVEhSRUUuVHJpYW5nbGVzRHJhd01vZGUuIFRyYW5zZm9ybSB5b3VyIGdlb21ldHJ5IHZpYSBCdWZmZXJHZW9tZXRyeVV0aWxzLnRvVHJpYW5nbGVzRHJhd01vZGUoKSBpZiBuZWNlc3NhcnkuIil9LE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKFZvLnByb3RvdHlwZSx7ZHJhd01vZGU6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLmVycm9yKCJUSFJFRS5NZXNoOiAuZHJhd01vZGUgaGFzIGJlZW4gcmVtb3ZlZC4gVGhlIHJlbmRlcmVyIG5vdyBhbHdheXMgYXNzdW1lcyBUSFJFRS5UcmlhbmdsZXNEcmF3TW9kZS4iKSwwfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5NZXNoOiAuZHJhd01vZGUgaGFzIGJlZW4gcmVtb3ZlZC4gVGhlIHJlbmRlcmVyIG5vdyBhbHdheXMgYXNzdW1lcyBUSFJFRS5UcmlhbmdsZXNEcmF3TW9kZS4gVHJhbnNmb3JtIHlvdXIgZ2VvbWV0cnkgdmlhIEJ1ZmZlckdlb21ldHJ5VXRpbHMudG9UcmlhbmdsZXNEcmF3TW9kZSgpIGlmIG5lY2Vzc2FyeS4iKX19fSksZ2sucHJvdG90eXBlLmluaXRCb25lcz1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLlNraW5uZWRNZXNoOiBpbml0Qm9uZXMoKSBoYXMgYmVlbiByZW1vdmVkLiIpfSxXcy5wcm90b3R5cGUuc2V0TGVucz1mdW5jdGlvbihuLHQpe2NvbnNvbGUud2FybigiVEhSRUUuUGVyc3BlY3RpdmVDYW1lcmEuc2V0TGVucyBpcyBkZXByZWNhdGVkLiBVc2UgLnNldEZvY2FsTGVuZ3RoIGFuZCAuZmlsbUdhdWdlIGZvciBhIHBob3RvZ3JhcGhpYyBzZXR1cC4iKSx2b2lkIDAhPT10JiYodGhpcy5maWxtR2F1Z2U9dCksdGhpcy5zZXRGb2NhbExlbmd0aChuKX0sT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoZmMucHJvdG90eXBlLHtvbmx5U2hhZG93OntzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAub25seVNoYWRvdyBoYXMgYmVlbiByZW1vdmVkLiIpfX0sc2hhZG93Q2FtZXJhRm92OntzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0NhbWVyYUZvdiBpcyBub3cgLnNoYWRvdy5jYW1lcmEuZm92LiIpLHRoaXMuc2hhZG93LmNhbWVyYS5mb3Y9bn19LHNoYWRvd0NhbWVyYUxlZnQ6e3NldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93Q2FtZXJhTGVmdCBpcyBub3cgLnNoYWRvdy5jYW1lcmEubGVmdC4iKSx0aGlzLnNoYWRvdy5jYW1lcmEubGVmdD1ufX0sc2hhZG93Q2FtZXJhUmlnaHQ6e3NldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93Q2FtZXJhUmlnaHQgaXMgbm93IC5zaGFkb3cuY2FtZXJhLnJpZ2h0LiIpLHRoaXMuc2hhZG93LmNhbWVyYS5yaWdodD1ufX0sc2hhZG93Q2FtZXJhVG9wOntzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0NhbWVyYVRvcCBpcyBub3cgLnNoYWRvdy5jYW1lcmEudG9wLiIpLHRoaXMuc2hhZG93LmNhbWVyYS50b3A9bn19LHNoYWRvd0NhbWVyYUJvdHRvbTp7c2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dDYW1lcmFCb3R0b20gaXMgbm93IC5zaGFkb3cuY2FtZXJhLmJvdHRvbS4iKSx0aGlzLnNoYWRvdy5jYW1lcmEuYm90dG9tPW59fSxzaGFkb3dDYW1lcmFOZWFyOntzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0NhbWVyYU5lYXIgaXMgbm93IC5zaGFkb3cuY2FtZXJhLm5lYXIuIiksdGhpcy5zaGFkb3cuY2FtZXJhLm5lYXI9bn19LHNoYWRvd0NhbWVyYUZhcjp7c2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dDYW1lcmFGYXIgaXMgbm93IC5zaGFkb3cuY2FtZXJhLmZhci4iKSx0aGlzLnNoYWRvdy5jYW1lcmEuZmFyPW59fSxzaGFkb3dDYW1lcmFWaXNpYmxlOntzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLkxpZ2h0OiAuc2hhZG93Q2FtZXJhVmlzaWJsZSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgbmV3IFRIUkVFLkNhbWVyYUhlbHBlciggbGlnaHQuc2hhZG93LmNhbWVyYSApIGluc3RlYWQuIil9fSxzaGFkb3dCaWFzOntzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0JpYXMgaXMgbm93IC5zaGFkb3cuYmlhcy4iKSx0aGlzLnNoYWRvdy5iaWFzPW59fSxzaGFkb3dEYXJrbmVzczp7c2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5MaWdodDogLnNoYWRvd0RhcmtuZXNzIGhhcyBiZWVuIHJlbW92ZWQuIil9fSxzaGFkb3dNYXBXaWR0aDp7c2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dNYXBXaWR0aCBpcyBub3cgLnNoYWRvdy5tYXBTaXplLndpZHRoLiIpLHRoaXMuc2hhZG93Lm1hcFNpemUud2lkdGg9bn19LHNoYWRvd01hcEhlaWdodDp7c2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuTGlnaHQ6IC5zaGFkb3dNYXBIZWlnaHQgaXMgbm93IC5zaGFkb3cubWFwU2l6ZS5oZWlnaHQuIiksdGhpcy5zaGFkb3cubWFwU2l6ZS5oZWlnaHQ9bn19fSksT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoWXIucHJvdG90eXBlLHtsZW5ndGg6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZTogLmxlbmd0aCBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLmNvdW50IGluc3RlYWQuIiksdGhpcy5hcnJheS5sZW5ndGh9fSxkeW5hbWljOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGU6IC5keW5hbWljIGhhcyBiZWVuIGRlcHJlY2F0ZWQuIFVzZSAudXNhZ2UgaW5zdGVhZC4iKSx0aGlzLnVzYWdlPT09b2t9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyQXR0cmlidXRlOiAuZHluYW1pYyBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnVzYWdlIGluc3RlYWQuIiksdGhpcy5zZXRVc2FnZShvayl9fX0pLFlyLnByb3RvdHlwZS5zZXREeW5hbWljPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckF0dHJpYnV0ZTogLnNldER5bmFtaWMoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnNldFVzYWdlKCkgaW5zdGVhZC4iKSx0aGlzLnNldFVzYWdlKCEwPT09bj9vazpxUyksdGhpc30sWXIucHJvdG90eXBlLmNvcHlJbmRpY2VzQXJyYXk9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5CdWZmZXJBdHRyaWJ1dGU6IC5jb3B5SW5kaWNlc0FycmF5KCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sWXIucHJvdG90eXBlLnNldEFycmF5PWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuQnVmZmVyQXR0cmlidXRlOiAuc2V0QXJyYXkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIEJ1ZmZlckdlb21ldHJ5IC5zZXRBdHRyaWJ1dGUgdG8gcmVwbGFjZS9yZXNpemUgYXR0cmlidXRlIGJ1ZmZlcnMiKX0sbnIucHJvdG90eXBlLmFkZEluZGV4PWZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5hZGRJbmRleCgpIGhhcyBiZWVuIHJlbmFtZWQgdG8gLnNldEluZGV4KCkuIiksdGhpcy5zZXRJbmRleChuKX0sbnIucHJvdG90eXBlLmFkZEF0dHJpYnV0ZT1mdW5jdGlvbihuLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuYWRkQXR0cmlidXRlKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuc2V0QXR0cmlidXRlKCkuIiksdCYmdC5pc0J1ZmZlckF0dHJpYnV0ZXx8dCYmdC5pc0ludGVybGVhdmVkQnVmZmVyQXR0cmlidXRlPyJpbmRleCI9PT1uPyhjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5LmFkZEF0dHJpYnV0ZTogVXNlIC5zZXRJbmRleCgpIGZvciBpbmRleCBhdHRyaWJ1dGUuIiksdGhpcy5zZXRJbmRleCh0KSx0aGlzKTp0aGlzLnNldEF0dHJpYnV0ZShuLHQpOihjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuYWRkQXR0cmlidXRlKCkgbm93IGV4cGVjdHMgKCBuYW1lLCBhdHRyaWJ1dGUgKS4iKSx0aGlzLnNldEF0dHJpYnV0ZShuLG5ldyBZcihhcmd1bWVudHNbMV0sYXJndW1lbnRzWzJdKSkpfSxuci5wcm90b3R5cGUuYWRkRHJhd0NhbGw9ZnVuY3Rpb24obix0LGUpe3ZvaWQgMCE9PWUmJmNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5hZGREcmF3Q2FsbCgpIG5vIGxvbmdlciBzdXBwb3J0cyBpbmRleE9mZnNldC4iKSxjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuYWRkRHJhd0NhbGwoKSBpcyBub3cgLmFkZEdyb3VwKCkuIiksdGhpcy5hZGRHcm91cChuLHQpfSxuci5wcm90b3R5cGUuY2xlYXJEcmF3Q2FsbHM9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuY2xlYXJEcmF3Q2FsbHMoKSBpcyBub3cgLmNsZWFyR3JvdXBzKCkuIiksdGhpcy5jbGVhckdyb3VwcygpfSxuci5wcm90b3R5cGUuY29tcHV0ZU9mZnNldHM9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAuY29tcHV0ZU9mZnNldHMoKSBoYXMgYmVlbiByZW1vdmVkLiIpfSxuci5wcm90b3R5cGUucmVtb3ZlQXR0cmlidXRlPWZ1bmN0aW9uKG4pe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkJ1ZmZlckdlb21ldHJ5OiAucmVtb3ZlQXR0cmlidXRlKCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuZGVsZXRlQXR0cmlidXRlKCkuIiksdGhpcy5kZWxldGVBdHRyaWJ1dGUobil9LG5yLnByb3RvdHlwZS5hcHBseU1hdHJpeD1mdW5jdGlvbihuKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5CdWZmZXJHZW9tZXRyeTogLmFwcGx5TWF0cml4KCkgaGFzIGJlZW4gcmVuYW1lZCB0byAuYXBwbHlNYXRyaXg0KCkuIiksdGhpcy5hcHBseU1hdHJpeDQobil9LE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKG5yLnByb3RvdHlwZSx7ZHJhd2NhbGxzOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS5lcnJvcigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5kcmF3Y2FsbHMgaGFzIGJlZW4gcmVuYW1lZCB0byAuZ3JvdXBzLiIpLHRoaXMuZ3JvdXBzfX0sb2Zmc2V0czp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQnVmZmVyR2VvbWV0cnk6IC5vZmZzZXRzIGhhcyBiZWVuIHJlbmFtZWQgdG8gLmdyb3Vwcy4iKSx0aGlzLmdyb3Vwc319fSksWWcucHJvdG90eXBlLnNldER5bmFtaWM9ZnVuY3Rpb24obil7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuSW50ZXJsZWF2ZWRCdWZmZXI6IC5zZXREeW5hbWljKCkgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIC5zZXRVc2FnZSgpIGluc3RlYWQuIiksdGhpcy5zZXRVc2FnZSghMD09PW4/b2s6cVMpLHRoaXN9LFlnLnByb3RvdHlwZS5zZXRBcnJheT1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkludGVybGVhdmVkQnVmZmVyOiAuc2V0QXJyYXkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIEJ1ZmZlckdlb21ldHJ5IC5zZXRBdHRyaWJ1dGUgdG8gcmVwbGFjZS9yZXNpemUgYXR0cmlidXRlIGJ1ZmZlcnMiKX0sU2YucHJvdG90eXBlLmdldEFycmF5cz1mdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLkV4dHJ1ZGVHZW9tZXRyeTogLmdldEFycmF5cygpIGhhcyBiZWVuIHJlbW92ZWQuIil9LFNmLnByb3RvdHlwZS5hZGRTaGFwZUxpc3Q9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5FeHRydWRlR2VvbWV0cnk6IC5hZGRTaGFwZUxpc3QoKSBoYXMgYmVlbiByZW1vdmVkLiIpfSxTZi5wcm90b3R5cGUuYWRkU2hhcGU9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5FeHRydWRlR2VvbWV0cnk6IC5hZGRTaGFwZSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9LHZiLnByb3RvdHlwZS5kaXNwb3NlPWZ1bmN0aW9uKCl7Y29uc29sZS5lcnJvcigiVEhSRUUuU2NlbmU6IC5kaXNwb3NlKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sZkUucHJvdG90eXBlLm9uVXBkYXRlPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuVW5pZm9ybTogLm9uVXBkYXRlKCkgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIG9iamVjdC5vbkJlZm9yZVJlbmRlcigpIGluc3RlYWQuIiksdGhpc30sT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoaHMucHJvdG90eXBlLHt3cmFwQXJvdW5kOntnZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLk1hdGVyaWFsOiAud3JhcEFyb3VuZCBoYXMgYmVlbiByZW1vdmVkLiIpfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLk1hdGVyaWFsOiAud3JhcEFyb3VuZCBoYXMgYmVlbiByZW1vdmVkLiIpfX0sb3ZlcmRyYXc6e2dldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuTWF0ZXJpYWw6IC5vdmVyZHJhdyBoYXMgYmVlbiByZW1vdmVkLiIpfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLk1hdGVyaWFsOiAub3ZlcmRyYXcgaGFzIGJlZW4gcmVtb3ZlZC4iKX19LHdyYXBSR0I6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLk1hdGVyaWFsOiAud3JhcFJHQiBoYXMgYmVlbiByZW1vdmVkLiIpLG5ldyB2bn19LHNoYWRpbmc6e2dldDpmdW5jdGlvbigpe2NvbnNvbGUuZXJyb3IoIlRIUkVFLiIrdGhpcy50eXBlKyI6IC5zaGFkaW5nIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSB0aGUgYm9vbGVhbiAuZmxhdFNoYWRpbmcgaW5zdGVhZC4iKX0sc2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuIit0aGlzLnR5cGUrIjogLnNoYWRpbmcgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIHRoZSBib29sZWFuIC5mbGF0U2hhZGluZyBpbnN0ZWFkLiIpLHRoaXMuZmxhdFNoYWRpbmc9MT09PW59fSxzdGVuY2lsTWFzazp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuIit0aGlzLnR5cGUrIjogLnN0ZW5jaWxNYXNrIGhhcyBiZWVuIHJlbW92ZWQuIFVzZSAuc3RlbmNpbEZ1bmNNYXNrIGluc3RlYWQuIiksdGhpcy5zdGVuY2lsRnVuY01hc2t9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLiIrdGhpcy50eXBlKyI6IC5zdGVuY2lsTWFzayBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLnN0ZW5jaWxGdW5jTWFzayBpbnN0ZWFkLiIpLHRoaXMuc3RlbmNpbEZ1bmNNYXNrPW59fSx2ZXJ0ZXhUYW5nZW50czp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS4iK3RoaXMudHlwZSsiOiAudmVydGV4VGFuZ2VudHMgaGFzIGJlZW4gcmVtb3ZlZC4iKX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS4iK3RoaXMudHlwZSsiOiAudmVydGV4VGFuZ2VudHMgaGFzIGJlZW4gcmVtb3ZlZC4iKX19fSksT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoRHAucHJvdG90eXBlLHtkZXJpdmF0aXZlczp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuU2hhZGVyTWF0ZXJpYWw6IC5kZXJpdmF0aXZlcyBoYXMgYmVlbiBtb3ZlZCB0byAuZXh0ZW5zaW9ucy5kZXJpdmF0aXZlcy4iKSx0aGlzLmV4dGVuc2lvbnMuZGVyaXZhdGl2ZXN9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLiBTaGFkZXJNYXRlcmlhbDogLmRlcml2YXRpdmVzIGhhcyBiZWVuIG1vdmVkIHRvIC5leHRlbnNpb25zLmRlcml2YXRpdmVzLiIpLHRoaXMuZXh0ZW5zaW9ucy5kZXJpdmF0aXZlcz1ufX19KSxpci5wcm90b3R5cGUuY2xlYXJUYXJnZXQ9ZnVuY3Rpb24obix0LGUsaSl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuY2xlYXJUYXJnZXQoKSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgLnNldFJlbmRlclRhcmdldCgpIGFuZCAuY2xlYXIoKSBpbnN0ZWFkLiIpLHRoaXMuc2V0UmVuZGVyVGFyZ2V0KG4pLHRoaXMuY2xlYXIodCxlLGkpfSxpci5wcm90b3R5cGUuYW5pbWF0ZT1mdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5hbmltYXRlKCkgaXMgbm93IC5zZXRBbmltYXRpb25Mb29wKCkuIiksdGhpcy5zZXRBbmltYXRpb25Mb29wKG4pfSxpci5wcm90b3R5cGUuZ2V0Q3VycmVudFJlbmRlclRhcmdldD1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRDdXJyZW50UmVuZGVyVGFyZ2V0KCkgaXMgbm93IC5nZXRSZW5kZXJUYXJnZXQoKS4iKSx0aGlzLmdldFJlbmRlclRhcmdldCgpfSxpci5wcm90b3R5cGUuZ2V0TWF4QW5pc290cm9weT1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRNYXhBbmlzb3Ryb3B5KCkgaXMgbm93IC5jYXBhYmlsaXRpZXMuZ2V0TWF4QW5pc290cm9weSgpLiIpLHRoaXMuY2FwYWJpbGl0aWVzLmdldE1heEFuaXNvdHJvcHkoKX0saXIucHJvdG90eXBlLmdldFByZWNpc2lvbj1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRQcmVjaXNpb24oKSBpcyBub3cgLmNhcGFiaWxpdGllcy5wcmVjaXNpb24uIiksdGhpcy5jYXBhYmlsaXRpZXMucHJlY2lzaW9ufSxpci5wcm90b3R5cGUucmVzZXRHTFN0YXRlPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnJlc2V0R0xTdGF0ZSgpIGlzIG5vdyAuc3RhdGUucmVzZXQoKS4iKSx0aGlzLnN0YXRlLnJlc2V0KCl9LGlyLnByb3RvdHlwZS5zdXBwb3J0c0Zsb2F0VGV4dHVyZXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNGbG9hdFRleHR1cmVzKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ09FU190ZXh0dXJlX2Zsb2F0JyApLiIpLHRoaXMuZXh0ZW5zaW9ucy5nZXQoIk9FU190ZXh0dXJlX2Zsb2F0Iil9LGlyLnByb3RvdHlwZS5zdXBwb3J0c0hhbGZGbG9hdFRleHR1cmVzPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnN1cHBvcnRzSGFsZkZsb2F0VGV4dHVyZXMoKSBpcyBub3cgLmV4dGVuc2lvbnMuZ2V0KCAnT0VTX3RleHR1cmVfaGFsZl9mbG9hdCcgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJPRVNfdGV4dHVyZV9oYWxmX2Zsb2F0Iil9LGlyLnByb3RvdHlwZS5zdXBwb3J0c1N0YW5kYXJkRGVyaXZhdGl2ZXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNTdGFuZGFyZERlcml2YXRpdmVzKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ09FU19zdGFuZGFyZF9kZXJpdmF0aXZlcycgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJPRVNfc3RhbmRhcmRfZGVyaXZhdGl2ZXMiKX0saXIucHJvdG90eXBlLnN1cHBvcnRzQ29tcHJlc3NlZFRleHR1cmVTM1RDPWZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnN1cHBvcnRzQ29tcHJlc3NlZFRleHR1cmVTM1RDKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjJyApLiIpLHRoaXMuZXh0ZW5zaW9ucy5nZXQoIldFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9zM3RjIil9LGlyLnByb3RvdHlwZS5zdXBwb3J0c0NvbXByZXNzZWRUZXh0dXJlUFZSVEM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNDb21wcmVzc2VkVGV4dHVyZVBWUlRDKCkgaXMgbm93IC5leHRlbnNpb25zLmdldCggJ1dFQkdMX2NvbXByZXNzZWRfdGV4dHVyZV9wdnJ0YycgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJXRUJHTF9jb21wcmVzc2VkX3RleHR1cmVfcHZydGMiKX0saXIucHJvdG90eXBlLnN1cHBvcnRzQmxlbmRNaW5NYXg9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNCbGVuZE1pbk1heCgpIGlzIG5vdyAuZXh0ZW5zaW9ucy5nZXQoICdFWFRfYmxlbmRfbWlubWF4JyApLiIpLHRoaXMuZXh0ZW5zaW9ucy5nZXQoIkVYVF9ibGVuZF9taW5tYXgiKX0saXIucHJvdG90eXBlLnN1cHBvcnRzVmVydGV4VGV4dHVyZXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNWZXJ0ZXhUZXh0dXJlcygpIGlzIG5vdyAuY2FwYWJpbGl0aWVzLnZlcnRleFRleHR1cmVzLiIpLHRoaXMuY2FwYWJpbGl0aWVzLnZlcnRleFRleHR1cmVzfSxpci5wcm90b3R5cGUuc3VwcG9ydHNJbnN0YW5jZWRBcnJheXM9ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc3VwcG9ydHNJbnN0YW5jZWRBcnJheXMoKSBpcyBub3cgLmV4dGVuc2lvbnMuZ2V0KCAnQU5HTEVfaW5zdGFuY2VkX2FycmF5cycgKS4iKSx0aGlzLmV4dGVuc2lvbnMuZ2V0KCJBTkdMRV9pbnN0YW5jZWRfYXJyYXlzIil9LGlyLnByb3RvdHlwZS5lbmFibGVTY2lzc29yVGVzdD1mdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5lbmFibGVTY2lzc29yVGVzdCgpIGlzIG5vdyAuc2V0U2Npc3NvclRlc3QoKS4iKSx0aGlzLnNldFNjaXNzb3JUZXN0KG4pfSxpci5wcm90b3R5cGUuaW5pdE1hdGVyaWFsPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuaW5pdE1hdGVyaWFsKCkgaGFzIGJlZW4gcmVtb3ZlZC4iKX0saXIucHJvdG90eXBlLmFkZFByZVBsdWdpbj1mdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLmFkZFByZVBsdWdpbigpIGhhcyBiZWVuIHJlbW92ZWQuIil9LGlyLnByb3RvdHlwZS5hZGRQb3N0UGx1Z2luPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuYWRkUG9zdFBsdWdpbigpIGhhcyBiZWVuIHJlbW92ZWQuIil9LGlyLnByb3RvdHlwZS51cGRhdGVTaGFkb3dNYXA9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC51cGRhdGVTaGFkb3dNYXAoKSBoYXMgYmVlbiByZW1vdmVkLiIpfSxpci5wcm90b3R5cGUuc2V0RmFjZUN1bGxpbmc9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zZXRGYWNlQ3VsbGluZygpIGhhcyBiZWVuIHJlbW92ZWQuIil9LGlyLnByb3RvdHlwZS5hbGxvY1RleHR1cmVVbml0PWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuYWxsb2NUZXh0dXJlVW5pdCgpIGhhcyBiZWVuIHJlbW92ZWQuIil9LGlyLnByb3RvdHlwZS5zZXRUZXh0dXJlPWZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc2V0VGV4dHVyZSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9LGlyLnByb3RvdHlwZS5zZXRUZXh0dXJlMkQ9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zZXRUZXh0dXJlMkQoKSBoYXMgYmVlbiByZW1vdmVkLiIpfSxpci5wcm90b3R5cGUuc2V0VGV4dHVyZUN1YmU9ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zZXRUZXh0dXJlQ3ViZSgpIGhhcyBiZWVuIHJlbW92ZWQuIil9LGlyLnByb3RvdHlwZS5nZXRBY3RpdmVNaXBNYXBMZXZlbD1mdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nZXRBY3RpdmVNaXBNYXBMZXZlbCgpIGlzIG5vdyAuZ2V0QWN0aXZlTWlwbWFwTGV2ZWwoKS4iKSx0aGlzLmdldEFjdGl2ZU1pcG1hcExldmVsKCl9LE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKGlyLnByb3RvdHlwZSx7c2hhZG93TWFwRW5hYmxlZDp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc2hhZG93TWFwLmVuYWJsZWR9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXBFbmFibGVkIGlzIG5vdyAuc2hhZG93TWFwLmVuYWJsZWQuIiksdGhpcy5zaGFkb3dNYXAuZW5hYmxlZD1ufX0sc2hhZG93TWFwVHlwZTp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc2hhZG93TWFwLnR5cGV9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXBUeXBlIGlzIG5vdyAuc2hhZG93TWFwLnR5cGUuIiksdGhpcy5zaGFkb3dNYXAudHlwZT1ufX0sc2hhZG93TWFwQ3VsbEZhY2U6e2dldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcEN1bGxGYWNlIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcEN1bGxGYWNlIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9fSxjb250ZXh0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuY29udGV4dCBoYXMgYmVlbiByZW1vdmVkLiBVc2UgLmdldENvbnRleHQoKSBpbnN0ZWFkLiIpLHRoaXMuZ2V0Q29udGV4dCgpfX0sdnI6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC52ciBoYXMgYmVlbiByZW5hbWVkIHRvIC54ciIpLHRoaXMueHJ9fSxnYW1tYUlucHV0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuZ2FtbWFJbnB1dCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgdGhlIGVuY29kaW5nIGZvciB0ZXh0dXJlcyB2aWEgVGV4dHVyZS5lbmNvZGluZyBpbnN0ZWFkLiIpLCExfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nYW1tYUlucHV0IGhhcyBiZWVuIHJlbW92ZWQuIFNldCB0aGUgZW5jb2RpbmcgZm9yIHRleHR1cmVzIHZpYSBUZXh0dXJlLmVuY29kaW5nIGluc3RlYWQuIil9fSxnYW1tYU91dHB1dDp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLmdhbW1hT3V0cHV0IGhhcyBiZWVuIHJlbW92ZWQuIFNldCBXZWJHTFJlbmRlcmVyLm91dHB1dEVuY29kaW5nIGluc3RlYWQuIiksITF9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5nYW1tYU91dHB1dCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgV2ViR0xSZW5kZXJlci5vdXRwdXRFbmNvZGluZyBpbnN0ZWFkLiIpLHRoaXMub3V0cHV0RW5jb2Rpbmc9ITA9PT1uP1dyOmJmfX0sdG9uZU1hcHBpbmdXaGl0ZVBvaW50OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAudG9uZU1hcHBpbmdXaGl0ZVBvaW50IGhhcyBiZWVuIHJlbW92ZWQuIiksMX0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAudG9uZU1hcHBpbmdXaGl0ZVBvaW50IGhhcyBiZWVuIHJlbW92ZWQuIil9fSxnYW1tYUZhY3Rvcjp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLmdhbW1hRmFjdG9yIGhhcyBiZWVuIHJlbW92ZWQuIiksMn0sc2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuZ2FtbWFGYWN0b3IgaGFzIGJlZW4gcmVtb3ZlZC4iKX19fSksT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoR2RlLnByb3RvdHlwZSx7Y3VsbEZhY2U6e2dldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcC5jdWxsRmFjZSBoYXMgYmVlbiByZW1vdmVkLiBTZXQgTWF0ZXJpYWwuc2hhZG93U2lkZSBpbnN0ZWFkLiIpfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXAuY3VsbEZhY2UgaGFzIGJlZW4gcmVtb3ZlZC4gU2V0IE1hdGVyaWFsLnNoYWRvd1NpZGUgaW5zdGVhZC4iKX19LHJlbmRlclJldmVyc2VTaWRlZDp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc2hhZG93TWFwLnJlbmRlclJldmVyc2VTaWRlZCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgTWF0ZXJpYWwuc2hhZG93U2lkZSBpbnN0ZWFkLiIpfSxzZXQ6ZnVuY3Rpb24oKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyZXI6IC5zaGFkb3dNYXAucmVuZGVyUmV2ZXJzZVNpZGVkIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9fSxyZW5kZXJTaW5nbGVTaWRlZDp7Z2V0OmZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlcmVyOiAuc2hhZG93TWFwLnJlbmRlclNpbmdsZVNpZGVkIGhhcyBiZWVuIHJlbW92ZWQuIFNldCBNYXRlcmlhbC5zaGFkb3dTaWRlIGluc3RlYWQuIil9LHNldDpmdW5jdGlvbigpe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJlcjogLnNoYWRvd01hcC5yZW5kZXJTaW5nbGVTaWRlZCBoYXMgYmVlbiByZW1vdmVkLiBTZXQgTWF0ZXJpYWwuc2hhZG93U2lkZSBpbnN0ZWFkLiIpfX19KSxPYmplY3QuZGVmaW5lUHJvcGVydGllcyhXYS5wcm90b3R5cGUse3dyYXBTOntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLndyYXBTIGlzIG5vdyAudGV4dHVyZS53cmFwUy4iKSx0aGlzLnRleHR1cmUud3JhcFN9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAud3JhcFMgaXMgbm93IC50ZXh0dXJlLndyYXBTLiIpLHRoaXMudGV4dHVyZS53cmFwUz1ufX0sd3JhcFQ6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAud3JhcFQgaXMgbm93IC50ZXh0dXJlLndyYXBULiIpLHRoaXMudGV4dHVyZS53cmFwVH0sc2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC53cmFwVCBpcyBub3cgLnRleHR1cmUud3JhcFQuIiksdGhpcy50ZXh0dXJlLndyYXBUPW59fSxtYWdGaWx0ZXI6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAubWFnRmlsdGVyIGlzIG5vdyAudGV4dHVyZS5tYWdGaWx0ZXIuIiksdGhpcy50ZXh0dXJlLm1hZ0ZpbHRlcn0sc2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5tYWdGaWx0ZXIgaXMgbm93IC50ZXh0dXJlLm1hZ0ZpbHRlci4iKSx0aGlzLnRleHR1cmUubWFnRmlsdGVyPW59fSxtaW5GaWx0ZXI6e2dldDpmdW5jdGlvbigpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAubWluRmlsdGVyIGlzIG5vdyAudGV4dHVyZS5taW5GaWx0ZXIuIiksdGhpcy50ZXh0dXJlLm1pbkZpbHRlcn0sc2V0OmZ1bmN0aW9uKG4pe2NvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5taW5GaWx0ZXIgaXMgbm93IC50ZXh0dXJlLm1pbkZpbHRlci4iKSx0aGlzLnRleHR1cmUubWluRmlsdGVyPW59fSxhbmlzb3Ryb3B5OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLmFuaXNvdHJvcHkgaXMgbm93IC50ZXh0dXJlLmFuaXNvdHJvcHkuIiksdGhpcy50ZXh0dXJlLmFuaXNvdHJvcHl9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAuYW5pc290cm9weSBpcyBub3cgLnRleHR1cmUuYW5pc290cm9weS4iKSx0aGlzLnRleHR1cmUuYW5pc290cm9weT1ufX0sb2Zmc2V0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLm9mZnNldCBpcyBub3cgLnRleHR1cmUub2Zmc2V0LiIpLHRoaXMudGV4dHVyZS5vZmZzZXR9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAub2Zmc2V0IGlzIG5vdyAudGV4dHVyZS5vZmZzZXQuIiksdGhpcy50ZXh0dXJlLm9mZnNldD1ufX0scmVwZWF0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLnJlcGVhdCBpcyBub3cgLnRleHR1cmUucmVwZWF0LiIpLHRoaXMudGV4dHVyZS5yZXBlYXR9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAucmVwZWF0IGlzIG5vdyAudGV4dHVyZS5yZXBlYXQuIiksdGhpcy50ZXh0dXJlLnJlcGVhdD1ufX0sZm9ybWF0OntnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gY29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLmZvcm1hdCBpcyBub3cgLnRleHR1cmUuZm9ybWF0LiIpLHRoaXMudGV4dHVyZS5mb3JtYXR9LHNldDpmdW5jdGlvbihuKXtjb25zb2xlLndhcm4oIlRIUkVFLldlYkdMUmVuZGVyVGFyZ2V0OiAuZm9ybWF0IGlzIG5vdyAudGV4dHVyZS5mb3JtYXQuIiksdGhpcy50ZXh0dXJlLmZvcm1hdD1ufX0sdHlwZTp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC50eXBlIGlzIG5vdyAudGV4dHVyZS50eXBlLiIpLHRoaXMudGV4dHVyZS50eXBlfSxzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLnR5cGUgaXMgbm93IC50ZXh0dXJlLnR5cGUuIiksdGhpcy50ZXh0dXJlLnR5cGU9bn19LGdlbmVyYXRlTWlwbWFwczp7Z2V0OmZ1bmN0aW9uKCl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuV2ViR0xSZW5kZXJUYXJnZXQ6IC5nZW5lcmF0ZU1pcG1hcHMgaXMgbm93IC50ZXh0dXJlLmdlbmVyYXRlTWlwbWFwcy4iKSx0aGlzLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzfSxzZXQ6ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5XZWJHTFJlbmRlclRhcmdldDogLmdlbmVyYXRlTWlwbWFwcyBpcyBub3cgLnRleHR1cmUuZ2VuZXJhdGVNaXBtYXBzLiIpLHRoaXMudGV4dHVyZS5nZW5lcmF0ZU1pcG1hcHM9bn19fSksY2xhc3MgZXh0ZW5kcyBYaXtjb25zdHJ1Y3Rvcih0KXtzdXBlcigpLHRoaXMudHlwZT0iQXVkaW8iLHRoaXMubGlzdGVuZXI9dCx0aGlzLmNvbnRleHQ9dC5jb250ZXh0LHRoaXMuZ2Fpbj10aGlzLmNvbnRleHQuY3JlYXRlR2FpbigpLHRoaXMuZ2Fpbi5jb25uZWN0KHQuZ2V0SW5wdXQoKSksdGhpcy5hdXRvcGxheT0hMSx0aGlzLmJ1ZmZlcj1udWxsLHRoaXMuZGV0dW5lPTAsdGhpcy5sb29wPSExLHRoaXMubG9vcFN0YXJ0PTAsdGhpcy5sb29wRW5kPTAsdGhpcy5vZmZzZXQ9MCx0aGlzLmR1cmF0aW9uPXZvaWQgMCx0aGlzLnBsYXliYWNrUmF0ZT0xLHRoaXMuaXNQbGF5aW5nPSExLHRoaXMuaGFzUGxheWJhY2tDb250cm9sPSEwLHRoaXMuc291cmNlPW51bGwsdGhpcy5zb3VyY2VUeXBlPSJlbXB0eSIsdGhpcy5fc3RhcnRlZEF0PTAsdGhpcy5fcHJvZ3Jlc3M9MCx0aGlzLl9jb25uZWN0ZWQ9ITEsdGhpcy5maWx0ZXJzPVtdfWdldE91dHB1dCgpe3JldHVybiB0aGlzLmdhaW59c2V0Tm9kZVNvdXJjZSh0KXtyZXR1cm4gdGhpcy5oYXNQbGF5YmFja0NvbnRyb2w9ITEsdGhpcy5zb3VyY2VUeXBlPSJhdWRpb05vZGUiLHRoaXMuc291cmNlPXQsdGhpcy5jb25uZWN0KCksdGhpc31zZXRNZWRpYUVsZW1lbnRTb3VyY2UodCl7cmV0dXJuIHRoaXMuaGFzUGxheWJhY2tDb250cm9sPSExLHRoaXMuc291cmNlVHlwZT0ibWVkaWFOb2RlIix0aGlzLnNvdXJjZT10aGlzLmNvbnRleHQuY3JlYXRlTWVkaWFFbGVtZW50U291cmNlKHQpLHRoaXMuY29ubmVjdCgpLHRoaXN9c2V0TWVkaWFTdHJlYW1Tb3VyY2UodCl7cmV0dXJuIHRoaXMuaGFzUGxheWJhY2tDb250cm9sPSExLHRoaXMuc291cmNlVHlwZT0ibWVkaWFTdHJlYW1Ob2RlIix0aGlzLnNvdXJjZT10aGlzLmNvbnRleHQuY3JlYXRlTWVkaWFTdHJlYW1Tb3VyY2UodCksdGhpcy5jb25uZWN0KCksdGhpc31zZXRCdWZmZXIodCl7cmV0dXJuIHRoaXMuYnVmZmVyPXQsdGhpcy5zb3VyY2VUeXBlPSJidWZmZXIiLHRoaXMuYXV0b3BsYXkmJnRoaXMucGxheSgpLHRoaXN9cGxheSh0PTApe2lmKCEwPT09dGhpcy5pc1BsYXlpbmcpcmV0dXJuIHZvaWQgY29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogQXVkaW8gaXMgYWxyZWFkeSBwbGF5aW5nLiIpO2lmKCExPT09dGhpcy5oYXNQbGF5YmFja0NvbnRyb2wpcmV0dXJuIHZvaWQgY29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogdGhpcyBBdWRpbyBoYXMgbm8gcGxheWJhY2sgY29udHJvbC4iKTt0aGlzLl9zdGFydGVkQXQ9dGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lK3Q7bGV0IGU9dGhpcy5jb250ZXh0LmNyZWF0ZUJ1ZmZlclNvdXJjZSgpO3JldHVybiBlLmJ1ZmZlcj10aGlzLmJ1ZmZlcixlLmxvb3A9dGhpcy5sb29wLGUubG9vcFN0YXJ0PXRoaXMubG9vcFN0YXJ0LGUubG9vcEVuZD10aGlzLmxvb3BFbmQsZS5vbmVuZGVkPXRoaXMub25FbmRlZC5iaW5kKHRoaXMpLGUuc3RhcnQodGhpcy5fc3RhcnRlZEF0LHRoaXMuX3Byb2dyZXNzK3RoaXMub2Zmc2V0LHRoaXMuZHVyYXRpb24pLHRoaXMuaXNQbGF5aW5nPSEwLHRoaXMuc291cmNlPWUsdGhpcy5zZXREZXR1bmUodGhpcy5kZXR1bmUpLHRoaXMuc2V0UGxheWJhY2tSYXRlKHRoaXMucGxheWJhY2tSYXRlKSx0aGlzLmNvbm5lY3QoKX1wYXVzZSgpe2lmKCExIT09dGhpcy5oYXNQbGF5YmFja0NvbnRyb2wpcmV0dXJuITA9PT10aGlzLmlzUGxheWluZyYmKHRoaXMuX3Byb2dyZXNzKz1NYXRoLm1heCh0aGlzLmNvbnRleHQuY3VycmVudFRpbWUtdGhpcy5fc3RhcnRlZEF0LDApKnRoaXMucGxheWJhY2tSYXRlLCEwPT09dGhpcy5sb29wJiYodGhpcy5fcHJvZ3Jlc3M9dGhpcy5fcHJvZ3Jlc3MlKHRoaXMuZHVyYXRpb258fHRoaXMuYnVmZmVyLmR1cmF0aW9uKSksdGhpcy5zb3VyY2Uuc3RvcCgpLHRoaXMuc291cmNlLm9uZW5kZWQ9bnVsbCx0aGlzLmlzUGxheWluZz0hMSksdGhpcztjb25zb2xlLndhcm4oIlRIUkVFLkF1ZGlvOiB0aGlzIEF1ZGlvIGhhcyBubyBwbGF5YmFjayBjb250cm9sLiIpfXN0b3AoKXtpZighMSE9PXRoaXMuaGFzUGxheWJhY2tDb250cm9sKXJldHVybiB0aGlzLl9wcm9ncmVzcz0wLHRoaXMuc291cmNlLnN0b3AoKSx0aGlzLnNvdXJjZS5vbmVuZGVkPW51bGwsdGhpcy5pc1BsYXlpbmc9ITEsdGhpcztjb25zb2xlLndhcm4oIlRIUkVFLkF1ZGlvOiB0aGlzIEF1ZGlvIGhhcyBubyBwbGF5YmFjayBjb250cm9sLiIpfWNvbm5lY3QoKXtpZih0aGlzLmZpbHRlcnMubGVuZ3RoPjApe3RoaXMuc291cmNlLmNvbm5lY3QodGhpcy5maWx0ZXJzWzBdKTtmb3IobGV0IHQ9MSxlPXRoaXMuZmlsdGVycy5sZW5ndGg7dDxlO3QrKyl0aGlzLmZpbHRlcnNbdC0xXS5jb25uZWN0KHRoaXMuZmlsdGVyc1t0XSk7dGhpcy5maWx0ZXJzW3RoaXMuZmlsdGVycy5sZW5ndGgtMV0uY29ubmVjdCh0aGlzLmdldE91dHB1dCgpKX1lbHNlIHRoaXMuc291cmNlLmNvbm5lY3QodGhpcy5nZXRPdXRwdXQoKSk7cmV0dXJuIHRoaXMuX2Nvbm5lY3RlZD0hMCx0aGlzfWRpc2Nvbm5lY3QoKXtpZih0aGlzLmZpbHRlcnMubGVuZ3RoPjApe3RoaXMuc291cmNlLmRpc2Nvbm5lY3QodGhpcy5maWx0ZXJzWzBdKTtmb3IobGV0IHQ9MSxlPXRoaXMuZmlsdGVycy5sZW5ndGg7dDxlO3QrKyl0aGlzLmZpbHRlcnNbdC0xXS5kaXNjb25uZWN0KHRoaXMuZmlsdGVyc1t0XSk7dGhpcy5maWx0ZXJzW3RoaXMuZmlsdGVycy5sZW5ndGgtMV0uZGlzY29ubmVjdCh0aGlzLmdldE91dHB1dCgpKX1lbHNlIHRoaXMuc291cmNlLmRpc2Nvbm5lY3QodGhpcy5nZXRPdXRwdXQoKSk7cmV0dXJuIHRoaXMuX2Nvbm5lY3RlZD0hMSx0aGlzfWdldEZpbHRlcnMoKXtyZXR1cm4gdGhpcy5maWx0ZXJzfXNldEZpbHRlcnModCl7cmV0dXJuIHR8fCh0PVtdKSwhMD09PXRoaXMuX2Nvbm5lY3RlZD8odGhpcy5kaXNjb25uZWN0KCksdGhpcy5maWx0ZXJzPXQuc2xpY2UoKSx0aGlzLmNvbm5lY3QoKSk6dGhpcy5maWx0ZXJzPXQuc2xpY2UoKSx0aGlzfXNldERldHVuZSh0KXtpZih0aGlzLmRldHVuZT10LHZvaWQgMCE9PXRoaXMuc291cmNlLmRldHVuZSlyZXR1cm4hMD09PXRoaXMuaXNQbGF5aW5nJiZ0aGlzLnNvdXJjZS5kZXR1bmUuc2V0VGFyZ2V0QXRUaW1lKHRoaXMuZGV0dW5lLHRoaXMuY29udGV4dC5jdXJyZW50VGltZSwuMDEpLHRoaXN9Z2V0RGV0dW5lKCl7cmV0dXJuIHRoaXMuZGV0dW5lfWdldEZpbHRlcigpe3JldHVybiB0aGlzLmdldEZpbHRlcnMoKVswXX1zZXRGaWx0ZXIodCl7cmV0dXJuIHRoaXMuc2V0RmlsdGVycyh0P1t0XTpbXSl9c2V0UGxheWJhY2tSYXRlKHQpe2lmKCExIT09dGhpcy5oYXNQbGF5YmFja0NvbnRyb2wpcmV0dXJuIHRoaXMucGxheWJhY2tSYXRlPXQsITA9PT10aGlzLmlzUGxheWluZyYmdGhpcy5zb3VyY2UucGxheWJhY2tSYXRlLnNldFRhcmdldEF0VGltZSh0aGlzLnBsYXliYWNrUmF0ZSx0aGlzLmNvbnRleHQuY3VycmVudFRpbWUsLjAxKSx0aGlzO2NvbnNvbGUud2FybigiVEhSRUUuQXVkaW86IHRoaXMgQXVkaW8gaGFzIG5vIHBsYXliYWNrIGNvbnRyb2wuIil9Z2V0UGxheWJhY2tSYXRlKCl7cmV0dXJuIHRoaXMucGxheWJhY2tSYXRlfW9uRW5kZWQoKXt0aGlzLmlzUGxheWluZz0hMX1nZXRMb29wKCl7cmV0dXJuITE9PT10aGlzLmhhc1BsYXliYWNrQ29udHJvbD8oY29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogdGhpcyBBdWRpbyBoYXMgbm8gcGxheWJhY2sgY29udHJvbC4iKSwhMSk6dGhpcy5sb29wfXNldExvb3AodCl7aWYoITEhPT10aGlzLmhhc1BsYXliYWNrQ29udHJvbClyZXR1cm4gdGhpcy5sb29wPXQsITA9PT10aGlzLmlzUGxheWluZyYmKHRoaXMuc291cmNlLmxvb3A9dGhpcy5sb29wKSx0aGlzO2NvbnNvbGUud2FybigiVEhSRUUuQXVkaW86IHRoaXMgQXVkaW8gaGFzIG5vIHBsYXliYWNrIGNvbnRyb2wuIil9c2V0TG9vcFN0YXJ0KHQpe3JldHVybiB0aGlzLmxvb3BTdGFydD10LHRoaXN9c2V0TG9vcEVuZCh0KXtyZXR1cm4gdGhpcy5sb29wRW5kPXQsdGhpc31nZXRWb2x1bWUoKXtyZXR1cm4gdGhpcy5nYWluLmdhaW4udmFsdWV9c2V0Vm9sdW1lKHQpe3JldHVybiB0aGlzLmdhaW4uZ2Fpbi5zZXRUYXJnZXRBdFRpbWUodCx0aGlzLmNvbnRleHQuY3VycmVudFRpbWUsLjAxKSx0aGlzfX0ucHJvdG90eXBlLmxvYWQ9ZnVuY3Rpb24obil7Y29uc29sZS53YXJuKCJUSFJFRS5BdWRpbzogLmxvYWQgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIFRIUkVFLkF1ZGlvTG9hZGVyIGluc3RlYWQuIik7bGV0IHQ9dGhpcztyZXR1cm4obmV3IHNHKS5sb2FkKG4sZnVuY3Rpb24oaSl7dC5zZXRCdWZmZXIoaSl9KSx0aGlzfSxLUy5wcm90b3R5cGUudXBkYXRlQ3ViZU1hcD1mdW5jdGlvbihuLHQpe3JldHVybiBjb25zb2xlLndhcm4oIlRIUkVFLkN1YmVDYW1lcmE6IC51cGRhdGVDdWJlTWFwKCkgaXMgbm93IC51cGRhdGUoKS4iKSx0aGlzLnVwZGF0ZShuLHQpfSxLUy5wcm90b3R5cGUuY2xlYXI9ZnVuY3Rpb24obix0LGUsaSl7cmV0dXJuIGNvbnNvbGUud2FybigiVEhSRUUuQ3ViZUNhbWVyYTogLmNsZWFyKCkgaXMgbm93IC5yZW5kZXJUYXJnZXQuY2xlYXIoKS4iKSx0aGlzLnJlbmRlclRhcmdldC5jbGVhcihuLHQsZSxpKX0sVHAuY3Jvc3NPcmlnaW49dm9pZCAwLFRwLmxvYWRUZXh0dXJlPWZ1bmN0aW9uKG4sdCxlLGkpe2NvbnNvbGUud2FybigiVEhSRUUuSW1hZ2VVdGlscy5sb2FkVGV4dHVyZSBoYXMgYmVlbiBkZXByZWNhdGVkLiBVc2UgVEhSRUUuVGV4dHVyZUxvYWRlcigpIGluc3RlYWQuIik7bGV0IHI9bmV3IEs4O3Iuc2V0Q3Jvc3NPcmlnaW4odGhpcy5jcm9zc09yaWdpbik7bGV0IG89ci5sb2FkKG4sZSx2b2lkIDAsaSk7cmV0dXJuIHQmJihvLm1hcHBpbmc9dCksb30sVHAubG9hZFRleHR1cmVDdWJlPWZ1bmN0aW9uKG4sdCxlLGkpe2NvbnNvbGUud2FybigiVEhSRUUuSW1hZ2VVdGlscy5sb2FkVGV4dHVyZUN1YmUgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIFRIUkVFLkN1YmVUZXh0dXJlTG9hZGVyKCkgaW5zdGVhZC4iKTtsZXQgcj1uZXcgUTg7ci5zZXRDcm9zc09yaWdpbih0aGlzLmNyb3NzT3JpZ2luKTtsZXQgbz1yLmxvYWQobixlLHZvaWQgMCxpKTtyZXR1cm4gdCYmKG8ubWFwcGluZz10KSxvfSxUcC5sb2FkQ29tcHJlc3NlZFRleHR1cmU9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5JbWFnZVV0aWxzLmxvYWRDb21wcmVzc2VkVGV4dHVyZSBoYXMgYmVlbiByZW1vdmVkLiBVc2UgVEhSRUUuRERTTG9hZGVyIGluc3RlYWQuIil9LFRwLmxvYWRDb21wcmVzc2VkVGV4dHVyZUN1YmU9ZnVuY3Rpb24oKXtjb25zb2xlLmVycm9yKCJUSFJFRS5JbWFnZVV0aWxzLmxvYWRDb21wcmVzc2VkVGV4dHVyZUN1YmUgaGFzIGJlZW4gcmVtb3ZlZC4gVXNlIFRIUkVFLkREU0xvYWRlciBpbnN0ZWFkLiIpfSx0eXBlb2YgX19USFJFRV9ERVZUT09MU19fPCJ1IiYmX19USFJFRV9ERVZUT09MU19fLmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCJyZWdpc3RlciIse2RldGFpbDp7cmV2aXNpb246IjEzNyJ9fSkpLHR5cGVvZiB3aW5kb3c8InUiJiYod2luZG93Ll9fVEhSRUVfXz9jb25zb2xlLndhcm4oIldBUk5JTkc6IE11bHRpcGxlIGluc3RhbmNlcyBvZiBUaHJlZS5qcyBiZWluZyBpbXBvcnRlZC4iKTp3aW5kb3cuX19USFJFRV9fPSIxMzciKTt2YXIgcHU9KCgpPT4oZnVuY3Rpb24obil7bltuLkNJUkNMRT0wXT0iQ0lSQ0xFIixuW24uTElORT0xXT0iTElORSIsbltuLlRSSUFOR0xFPTJdPSJUUklBTkdMRSIsbltuLlRSQVBFWk9JRD0zXT0iVFJBUEVaT0lEIn0ocHV8fChwdT17fSkpLHB1KSkoKTtmdW5jdGlvbiBKZGUobix0KXtsZXQgZT10Lmxlbmd0aC8yLGk9bi5hdHRyaWJ1dGVzLnBvc2l0aW9uOyghaXx8aS5jb3VudCE9PTMqZSkmJihpPW5ldyBZcihuZXcgRmxvYXQzMkFycmF5KDMqZSksMyksbi5zZXRBdHRyaWJ1dGUoInBvc2l0aW9uIixpKSk7bGV0IHI9aS5hcnJheTtmb3IobGV0IG89MDtvPGU7bysrKXJbMypvXT10WzIqb10sclszKm8rMV09dFsyKm8rMV07aS5uZWVkc1VwZGF0ZT0hMCxuLnNldERyYXdSYW5nZSgwLDMqZSksbi5jb21wdXRlQm91bmRpbmdTcGhlcmUoKX1mdW5jdGlvbiAkZGUobix0LGUpe2xldCBpPU1hdGgubWF4KHQubGVuZ3RoLzItMSwwKSxyPTIqaSozLG89MypyLHM9bi5hdHRyaWJ1dGVzLnBvc2l0aW9uOyghc3x8cy5jb3VudCE9PXIpJiYocz1uZXcgWXIobmV3IEZsb2F0MzJBcnJheShvKSwzKSxuLnNldEF0dHJpYnV0ZSgicG9zaXRpb24iLHMpKTtsZXQgYT1zLmFycmF5O2ZvcihsZXQgbD0wO2w8aTtsKyspe2xldFtjLHUsZCxwXT1bdFsyKmxdLHRbMipsKzFdLHRbMipsKzJdLHRbMipsKzNdXSxoPW5ldyBhdChjLHUpLGY9bmV3IGF0KGQscCksbT1uZXcgYXQoZC1jLHAtdSkseD1uZXcgYXQoLW0ueSxtLngpLnNldExlbmd0aChlLzIpLGc9aC5jbG9uZSgpLmFkZCh4KSxiPWguY2xvbmUoKS5zdWIoeCksRD1mLmNsb25lKCkuYWRkKHgpLFQ9Zi5jbG9uZSgpLnN1Yih4KSxrPVtnLngsZy55LDAsYi54LGIueSwwLEQueCxELnksMCxELngsRC55LDAsYi54LGIueSwwLFQueCxULnksMF07YS5zZXQoayxsKmsubGVuZ3RoKX1zLm5lZWRzVXBkYXRlPSEwLG4uc2V0RHJhd1JhbmdlKDAsbyksbi5jb21wdXRlQm91bmRpbmdTcGhlcmUoKX1mdW5jdGlvbiBOayhuLHQsZSxpKXtsZXR7dmlzaWJsZTpyLGNvbG9yOm8sb3BhY2l0eTpzfT1pO2lmKEFycmF5LmlzQXJyYXkodC5tYXRlcmlhbCkpdGhyb3cgbmV3IEVycm9yKCJJbnZhcmlhbnQgZXJyb3I6IG9ubHkgZXhwZWN0IG9uZSBtYXRlcmlhbCBvbiBhbiBvYmplY3QiKTtsZXQgYT10Lm1hdGVyaWFsO2lmKGEudmlzaWJsZSE9PXImJihhLnZpc2libGU9cixhLm5lZWRzVXBkYXRlPSEwKSwhcilyZXR1cm4hMTtsZXQgbD1ERyhuLG8scz8/MSksYz1lKHQuZ2VvbWV0cnkpO3JldHVybiB0Lmdlb21ldHJ5IT09YyYmKHQuZ2VvbWV0cnk9YyksYS5jb2xvci5lcXVhbHMobCl8fChhLmNvbG9yLnNldChsKSxhLm5lZWRzVXBkYXRlPSEwKSwhMH12YXIgQms9Y2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5yYXdTZXJpZXNEYXRhPVtdLHRoaXMuc2VyaWVzPVtdLHRoaXMucGFpbnREaXJ0eT0hMCx0aGlzLnJlbmRlckNhY2hlPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcigpe3RoaXMucHJldkZyYW1lQ2FjaGU9bmV3IE1hcCx0aGlzLmN1cnJGcmFtZUNhY2hlPW5ldyBNYXB9Z2V0RnJvbVByZXZpb3VzRnJhbWUodCl7cmV0dXJuIHRoaXMucHJldkZyYW1lQ2FjaGUuZ2V0KHQpPz9udWxsfXNldFRvQ3VycmVudEZyYW1lKHQsZSl7dGhpcy5jdXJyRnJhbWVDYWNoZS5zZXQodCxlKX1maW5hbGl6ZUZyYW1lQW5kR2V0UmVtb3ZlZCgpe2xldCB0PVtdO2ZvcihsZXRbZSxpXW9mIHRoaXMucHJldkZyYW1lQ2FjaGUuZW50cmllcygpKXRoaXMuY3VyckZyYW1lQ2FjaGUuaGFzKGUpfHx0LnB1c2goaSk7cmV0dXJuIHRoaXMucHJldkZyYW1lQ2FjaGU9dGhpcy5jdXJyRnJhbWVDYWNoZSx0aGlzLmN1cnJGcmFtZUNhY2hlPW5ldyBNYXAsdH19LHRoaXMuY29vcmRpbmF0ZUlkZW50aWZpZXI9bnVsbCx0aGlzLmxheW91dD17eDowLHdpZHRoOjEseTowLGhlaWdodDoxfSx0aGlzLmdldE1ldGFkYXRhTWFwSW1wbD10LmdldE1ldGFkYXRhTWFwLHRoaXMuY29vcmRpbmF0b3I9dC5jb29yZGluYXRvcix0aGlzLnJlbmRlcmVyPXQucmVuZGVyZXIsdGhpcy5wYWludEJydXNoPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUpe3RoaXMucmVuZGVyQ2FjaGU9dCx0aGlzLnJlbmRlcmVyPWV9c2V0TGluZSh0LGUsaSl7bGV0IHI9dGhpcy5yZW5kZXJlci5jcmVhdGVPclVwZGF0ZUxpbmVPYmplY3QodGhpcy5yZW5kZXJDYWNoZS5nZXRGcm9tUHJldmlvdXNGcmFtZSh0KSxlLGkpO3ImJnRoaXMucmVuZGVyQ2FjaGUuc2V0VG9DdXJyZW50RnJhbWUodCxyKX1zZXRUcmlhbmdsZSh0LGUsaSl7bGV0IHI9dGhpcy5yZW5kZXJlci5jcmVhdGVPclVwZGF0ZVRyaWFuZ2xlT2JqZWN0KHRoaXMucmVuZGVyQ2FjaGUuZ2V0RnJvbVByZXZpb3VzRnJhbWUodCksZSxpKTtyJiZ0aGlzLnJlbmRlckNhY2hlLnNldFRvQ3VycmVudEZyYW1lKHQscil9c2V0Q2lyY2xlKHQsZSxpKXtsZXQgcj10aGlzLnJlbmRlcmVyLmNyZWF0ZU9yVXBkYXRlQ2lyY2xlT2JqZWN0KHRoaXMucmVuZGVyQ2FjaGUuZ2V0RnJvbVByZXZpb3VzRnJhbWUodCksZSxpKTtyJiZ0aGlzLnJlbmRlckNhY2hlLnNldFRvQ3VycmVudEZyYW1lKHQscil9c2V0VHJhcGV6b2lkKHQsZSxpLHIpe2xldCBvPXRoaXMucmVuZGVyZXIuY3JlYXRlT3JVcGRhdGVUcmFwZXpvaWRPYmplY3QodGhpcy5yZW5kZXJDYWNoZS5nZXRGcm9tUHJldmlvdXNGcmFtZSh0KSxlLGkscik7byYmdGhpcy5yZW5kZXJDYWNoZS5zZXRUb0N1cnJlbnRGcmFtZSh0LG8pfX0odGhpcy5yZW5kZXJDYWNoZSx0aGlzLnJlbmRlcmVyKX1zZXRMYXlvdXRSZWN0KHQpeyh0aGlzLmxheW91dC54IT09dC54fHx0aGlzLmxheW91dC53aWR0aCE9PXQud2lkdGh8fHRoaXMubGF5b3V0LnkhPT10Lnl8fHRoaXMubGF5b3V0LmhlaWdodCE9PXQuaGVpZ2h0KSYmKHRoaXMucGFpbnREaXJ0eT0hMCksdGhpcy5sYXlvdXQ9dH1nZXRMYXlvdXRSZWN0KCl7cmV0dXJuIHRoaXMubGF5b3V0fWdldE1ldGFkYXRhTWFwKCl7cmV0dXJuIHRoaXMuZ2V0TWV0YWRhdGFNYXBJbXBsKCl9bWFya0FzUGFpbnREaXJ0eSgpe3RoaXMucGFpbnREaXJ0eT0hMH1yZW5kZXIoKXtpZih0aGlzLnRyYW5zZm9ybUNvb3JkaW5hdGVzSWZTdGFsZSgpLHRoaXMucGFpbnREaXJ0eSl7dGhpcy5yZWRyYXcoKTtmb3IobGV0IHQgb2YgdGhpcy5yZW5kZXJDYWNoZS5maW5hbGl6ZUZyYW1lQW5kR2V0UmVtb3ZlZCgpKXRoaXMucmVuZGVyZXIuZGVzdHJveU9iamVjdCh0KTt0aGlzLnBhaW50RGlydHk9ITF9fWlzQ29vcmRpbmF0ZVVwZGF0ZWQoKXtyZXR1cm4gdGhpcy5jb29yZGluYXRvci5nZXRVcGRhdGVJZGVudGlmaWVyKCkhPT10aGlzLmNvb3JkaW5hdGVJZGVudGlmaWVyfWNsZWFyQ29vcmRpbmF0ZUlkZW50aWZpZXIoKXt0aGlzLmNvb3JkaW5hdGVJZGVudGlmaWVyPW51bGx9c2V0RGF0YSh0KXt0aGlzLmNsZWFyQ29vcmRpbmF0ZUlkZW50aWZpZXIoKSx0aGlzLnJhd1Nlcmllc0RhdGE9dH10cmFuc2Zvcm1Db29yZGluYXRlc0lmU3RhbGUoKXtpZighdGhpcy5pc0Nvb3JkaW5hdGVVcGRhdGVkKCkpcmV0dXJuO2xldCB0PXRoaXMuZ2V0TGF5b3V0UmVjdCgpO3RoaXMuc2VyaWVzPW5ldyBBcnJheSh0aGlzLnJhd1Nlcmllc0RhdGEubGVuZ3RoKTtmb3IobGV0IGU9MDtlPHRoaXMucmF3U2VyaWVzRGF0YS5sZW5ndGg7ZSsrKXtsZXQgaT10aGlzLnJhd1Nlcmllc0RhdGFbZV07dGhpcy5zZXJpZXNbZV09e2lkOmkuaWQscG9seWxpbmU6bmV3IEZsb2F0MzJBcnJheSgyKmkucG9pbnRzLmxlbmd0aCl9O2ZvcihsZXQgcj0wO3I8aS5wb2ludHMubGVuZ3RoO3IrKyl7bGV0W28sc109dGhpcy5jb29yZGluYXRvci50cmFuc2Zvcm1EYXRhVG9VaUNvb3JkKHQsW2kucG9pbnRzW3JdLngsaS5wb2ludHNbcl0ueV0pO3RoaXMuc2VyaWVzW2VdLnBvbHlsaW5lWzIqcl09byx0aGlzLnNlcmllc1tlXS5wb2x5bGluZVsyKnIrMV09c319dGhpcy5jb29yZGluYXRlSWRlbnRpZmllcj10aGlzLmNvb3JkaW5hdG9yLmdldFVwZGF0ZUlkZW50aWZpZXIoKSx0aGlzLm1hcmtBc1BhaW50RGlydHkoKX19LFRiPSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5OVU1CRVI9MF09Ik5VTUJFUiIsbltuLk5BTj0xXT0iTkFOIn0oVGJ8fChUYj17fSkpLFRiKSkoKSxWaz1jbGFzcyBleHRlbmRzIEJre3JlY29yZFBhcnRpdGlvbih0LGUsaSl7cmV0dXJuIHQ/e3R5cGU6VGIuTlVNQkVSLHBvbHlsaW5lOmV9Ont0eXBlOlRiLk5BTixwb2x5bGluZTplLm1hcCgocixvKT0+aXNOYU4ocik/byUyPT0wP2kueDppLnk6cil9fXBhcnRpdGlvblBvbHlsaW5lKHQpe2xldCBlPVtdLGk9MCxyPSExLG89dGhpcy5jb29yZGluYXRvci50cmFuc2Zvcm1EYXRhVG9VaUNvb3JkKHRoaXMuZ2V0TGF5b3V0UmVjdCgpLFswLDBdKSxzPXt4Om9bMF0seTpvWzFdfSxhPW51bGw7Zm9yKGxldCBsPTA7bDx0Lmxlbmd0aDtsKz0yKXtsZXQgYz10W2xdLHU9dFtsKzFdLGQ9aXNOYU4oYyl8fGlzTmFOKHUpO2QhPT1yJiZpIT09bCYmKGUucHVzaCh0aGlzLnJlY29yZFBhcnRpdGlvbighcix0LnNsaWNlKGksbCksbnVsbD09PWE/e3g6Yyx5OnV9OmEpKSxpPWwpLGR8fChhPXt4OmMseTp1fSkscj1kfXJldHVybiBpIT09dC5sZW5ndGgtMSYmZS5wdXNoKHRoaXMucmVjb3JkUGFydGl0aW9uKCFyLHQuc2xpY2UoaSx0Lmxlbmd0aCksYT8/cykpLGV9cmVkcmF3KCl7Zm9yKGxldCB0IG9mIHRoaXMuc2VyaWVzKXtsZXQgaT10aGlzLmdldE1ldGFkYXRhTWFwKClbdC5pZF07aWYoIWkpY29udGludWU7aWYodC5wb2x5bGluZS5sZW5ndGglMiE9MCl0aHJvdyBuZXcgRXJyb3IoYENhbm5vdCBoYXZlIG9kZCBsZW5ndGgtZWQgcG9seWxpbmU6ICR7dC5wb2x5bGluZS5sZW5ndGh9YCk7bGV0IHI9dGhpcy5wYXJ0aXRpb25Qb2x5bGluZSh0LnBvbHlsaW5lKTtmb3IobGV0W28se3R5cGU6cyxwb2x5bGluZTphfV1vZiByLmVudHJpZXMoKSlpZihzPT09VGIuTlVNQkVSKTI9PT1hLmxlbmd0aD90aGlzLnBhaW50QnJ1c2guc2V0Q2lyY2xlKEpTT04uc3RyaW5naWZ5KFsiY2lyY2xlIix0LmlkLG9dKSx7eDphWzBdLHk6YVsxXX0se2NvbG9yOmkuY29sb3IsdmlzaWJsZTppLnZpc2libGUsb3BhY2l0eTppLm9wYWNpdHk/PzEscmFkaXVzOjR9KTp0aGlzLnBhaW50QnJ1c2guc2V0TGluZShKU09OLnN0cmluZ2lmeShbImxpbmUiLHQuaWQsb10pLGEse2NvbG9yOmkuY29sb3IsdmlzaWJsZTppLnZpc2libGUsb3BhY2l0eTppLm9wYWNpdHk/PzEsd2lkdGg6Mn0pO2Vsc2UgaWYoIWkuYXV4KWZvcihsZXQgbD0wO2w8YS5sZW5ndGg7bCs9Mil0aGlzLnBhaW50QnJ1c2guc2V0VHJpYW5nbGUoSlNPTi5zdHJpbmdpZnkoWyJOYU4iLHQuaWQsYVtsXSxhW2wrMV1dKSx7eDphW2xdLHk6YVtsKzFdfSx7Y29sb3I6aS5jb2xvcix2aXNpYmxlOmkudmlzaWJsZSxvcGFjaXR5Omkub3BhY2l0eT8/MSxzaXplOjEyfSl9fX0sSGs9Y2xhc3MgZXh0ZW5kcyBCeXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5jYW1lcmE9bmV3IHFnKDAsMWUzLDFlMywwLDAsMTAwKX1pc1lBeGlzUG9pbnRlZERvd24oKXtyZXR1cm4hMX1zZXREb21Db250YWluZXJSZWN0KHQpe3N1cGVyLnNldERvbUNvbnRhaW5lclJlY3QodCksdGhpcy5jYW1lcmEubGVmdD10LngsdGhpcy5jYW1lcmEucmlnaHQ9dC54K3Qud2lkdGgsdGhpcy5jYW1lcmEudG9wPXQueSt0LmhlaWdodCx0aGlzLmNhbWVyYS5ib3R0b209dC55LHRoaXMuY2FtZXJhLnVwZGF0ZVByb2plY3Rpb25NYXRyaXgoKX1nZXRDYW1lcmEoKXtyZXR1cm4gdGhpcy5jYW1lcmF9fSxVaz1jbGFzc3tjb25zdHJ1Y3Rvcih0KXtzd2l0Y2godGhpcy5tZXRhZGF0YU1hcD17fSx0aGlzLnNob3VsZFJlcGFpbnQ9ITEsdGhpcy5jYWxsYmFja3M9dC5jYWxsYmFja3MsdC50eXBlKXtjYXNlIGRyLlNWRzp0aGlzLmNvb3JkaW5hdG9yPW5ldyBCeSx0aGlzLnJlbmRlcmVyPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0KXt0aGlzLnN2Zz10fWZsdXNoKCl7fW9uUmVzaXplKHQpe31kZXN0cm95T2JqZWN0KHQpe3RoaXMuc3ZnLnJlbW92ZUNoaWxkKHQuZG9tKX1zZXRVc2VEYXJrTW9kZSh0KXt9Y3JlYXRlUGF0aERTdHJpbmcodCl7aWYoIXQubGVuZ3RoKXJldHVybiIiO2xldCBlPW5ldyBBcnJheSh0Lmxlbmd0aC8yKTtlWzBdPWBNJHt0WzBdfSwke3RbMV19YDtmb3IobGV0IGk9MTtpPHQubGVuZ3RoLzI7aSsrKWVbaV09YEwke3RbMippXX0sJHt0WzIqaSsxXX1gO3JldHVybiBlLmpvaW4oIiIpfWNyZWF0ZU9yVXBkYXRlTGluZU9iamVjdCh0LGUsaSl7bGV0IHI9eE8odD8uZG9tLCgpPT57bGV0IG89ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIsInBhdGgiKTtvLnN0eWxlLmZpbGw9Im5vbmUiO2xldCBzPXRoaXMuY3JlYXRlUGF0aERTdHJpbmcoZSk7cmV0dXJuIG8uc2V0QXR0cmlidXRlKCJkIixzKSx0aGlzLnN2Zy5hcHBlbmRDaGlsZChvKSxvfSxvPT57aWYoIXQ/LmRhdGF8fCFzdV9hcmVQb2x5bGluZXNFcXVhbChlLHQ/LmRhdGEpKXtsZXQgcz10aGlzLmNyZWF0ZVBhdGhEU3RyaW5nKGUpO28uc2V0QXR0cmlidXRlKCJkIixzKX1yZXR1cm4gb30saSk7cmV0dXJuIG51bGw9PT1yP251bGw6KHIuc3R5bGUuc3Ryb2tlV2lkdGg9U3RyaW5nKGkud2lkdGgpLHtkb206cixkYXRhOmV9KX1jcmVhdGVPclVwZGF0ZVRyaWFuZ2xlT2JqZWN0KHQsZSxpKXtsZXR7c2l6ZTpyLGNvbG9yOm99PWkscz1yKk1hdGguc3FydCgzKS8yLGE9bmV3IEZsb2F0MzJBcnJheShbZS54LXIvMixlLnkrcy8zLGUueCtyLzIsZS55K3MvMyxlLngsZS55LTIqcy8zXSksbD14Tyh0Py5kb20sKCk9PntsZXQgYz1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwicGF0aCIpO2MuY2xhc3NMaXN0LmFkZCgidHJpYW5nbGUiKSxjLnN0eWxlLmZpbGw9Im5vbmUiO2xldCB1PXRoaXMuY3JlYXRlUGF0aERTdHJpbmcoYSk7cmV0dXJuIGMuc2V0QXR0cmlidXRlKCJkIix1KyJaIiksdGhpcy5zdmcuYXBwZW5kQ2hpbGQoYyksY30sYz0+e2xldCB1PXRoaXMuY3JlYXRlUGF0aERTdHJpbmcoYSk7cmV0dXJuIGMuc2V0QXR0cmlidXRlKCJkIix1KyJaIiksY30saSk7cmV0dXJuIG51bGw9PT1sP251bGw6KGwuc3R5bGUuZmlsbD1vLHtkb206bCxkYXRhOmF9KX1jcmVhdGVPclVwZGF0ZUNpcmNsZU9iamVjdCh0LGUsaSl7bGV0e2NvbG9yOnIscmFkaXVzOm99PWkscz14Tyh0Py5kb20sKCk9PntsZXQgYT1kb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwiY2lyY2xlIik7cmV0dXJuIGEuc3R5bGUuZmlsbD1yLGEuc2V0QXR0cmlidXRlKCJjeCIsU3RyaW5nKGUueCkpLGEuc2V0QXR0cmlidXRlKCJjeSIsU3RyaW5nKGUueSkpLGEuc2V0QXR0cmlidXRlKCJyIixTdHJpbmcobykpLHRoaXMuc3ZnLmFwcGVuZENoaWxkKGEpLGF9LGE9PihhLnN0eWxlLmZpbGw9cixhLnNldEF0dHJpYnV0ZSgiY3giLFN0cmluZyhlLngpKSxhLnNldEF0dHJpYnV0ZSgiY3kiLFN0cmluZyhlLnkpKSxhLnNldEF0dHJpYnV0ZSgiciIsU3RyaW5nKG8pKSxhKSxpKTtyZXR1cm4gbnVsbD09PXM/bnVsbDp7ZG9tOnMsZGF0YTplfX1jcmVhdGVPclVwZGF0ZVRyYXBlem9pZE9iamVjdCh0LGUsaSxyKXtpZihlLnkhPT1pLnkpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIklucHV0IGVycm9yOiBzdGFydC55ICE9IGVuZC55LiIpO2xldHthbHRpdHVkZTpvLGNvbG9yOnN9PXIsYT0yL01hdGguc3FydCgzKSpvLGw9bmV3IEZsb2F0MzJBcnJheShbZS54LWEvMixlLnkrby8yLGUueCxlLnktby8yLGkueCxpLnktby8yLGkueCthLzIsaS55K28vMl0pLGM9eE8odD8uZG9tLCgpPT57bGV0IHU9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIsInBhdGgiKTt1LmNsYXNzTGlzdC5hZGQoInRyYXBlem9pZCIpLHUuc3R5bGUuZmlsbD0ibm9uZSI7bGV0IGQ9dGhpcy5jcmVhdGVQYXRoRFN0cmluZyhsKTtyZXR1cm4gdS5zZXRBdHRyaWJ1dGUoImQiLGQrIloiKSx0aGlzLnN2Zy5hcHBlbmRDaGlsZCh1KSx1fSx1PT57bGV0IGQ9dGhpcy5jcmVhdGVQYXRoRFN0cmluZyhsKTtyZXR1cm4gdS5zZXRBdHRyaWJ1dGUoImQiLGQrIloiKSx1fSxyKTtyZXR1cm4gbnVsbD09PWM/bnVsbDooYy5zdHlsZS5maWxsPXMse2RvbTpjLGRhdGE6bH0pfWRpc3Bvc2UoKXt9fSh0LmNvbnRhaW5lcik7YnJlYWs7Y2FzZSBkci5XRUJHTDp7bGV0IGU9bmV3IEhrO3RoaXMuY29vcmRpbmF0b3I9ZSx0aGlzLnJlbmRlcmVyPW5ldyBjbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXt0aGlzLmNvb3JkaW5hdG9yPWUsdGhpcy5zY2VuZT1uZXcgdmIsdGhpcy5iYWNrZ3JvdW5kQ29sb3I9IiNmZmYiLHN1X2lzT2Zmc2NyZWVuQ2FudmFzU3VwcG9ydGVkKCkmJnQgaW5zdGFuY2VvZiBPZmZzY3JlZW5DYW52YXMmJih0LnN0eWxlPXQuc3R5bGV8fHt9KSxyJiZ0LmFkZEV2ZW50TGlzdGVuZXIoIndlYmdsY29udGV4dGxvc3QiLHIpLHRoaXMucmVuZGVyZXI9bmV3IGlyKHtjYW52YXM6dCxhbnRpYWxpYXM6ITAsYWxwaGE6ITB9KSx0aGlzLnJlbmRlcmVyLnNldFBpeGVsUmF0aW8oaSl9b25SZXNpemUodCl7dGhpcy5yZW5kZXJlci5zZXRTaXplKHQud2lkdGgsdC5oZWlnaHQpfWRlc3Ryb3lPYmplY3QodCl7bGV0IGU9dC5vYmozZDtpZih0aGlzLnNjZW5lLnJlbW92ZShlKSxlIGluc3RhbmNlb2YgVm8pe2UuZ2VvbWV0cnkuZGlzcG9zZSgpO2xldCBpPUFycmF5LmlzQXJyYXkoZS5tYXRlcmlhbCk/ZS5tYXRlcmlhbDpbZS5tYXRlcmlhbF07Zm9yKGxldCByIG9mIGkpci5kaXNwb3NlKCl9fXNldFVzZURhcmtNb2RlKHQpe3RoaXMuYmFja2dyb3VuZENvbG9yPXQ/IiMzMDMwMzAiOiIjZmZmIn1jcmVhdGVPclVwZGF0ZUxpbmVPYmplY3QodCxlLGkpe2lmKCF0JiYhaS52aXNpYmxlKXJldHVybiBudWxsO2xldHt2aXNpYmxlOnIsd2lkdGg6b309aTtpZighdCl7bGV0IHU9REcodGhpcy5iYWNrZ3JvdW5kQ29sb3IsaS5jb2xvcixpLm9wYWNpdHk/PzEpLGQ9bmV3IG5yLHA9bmV3IEFwKHtjb2xvcjp1fSksaD1uZXcgVm8oZCxwKTtyZXR1cm4gcC52aXNpYmxlPXIsJGRlKGQsZSxvKSx0aGlzLnNjZW5lLmFkZChoKSx7dHlwZTpwdS5MSU5FLGRhdGE6ZSxvYmozZDpoLHdpZHRoOm99fWxldHtkYXRhOnMsb2JqM2Q6YSx3aWR0aDpsfT10O3JldHVybiBOayh0aGlzLmJhY2tncm91bmRDb2xvcixhLHU9PigobyE9PWx8fCFzfHwhc3VfYXJlUG9seWxpbmVzRXF1YWwocyxlKSkmJiRkZSh1LGUsbyksdSksaSk/e3R5cGU6cHUuTElORSxkYXRhOmUsb2JqM2Q6YSx3aWR0aDpvfTp0fWNyZWF0ZU1lc2godCxlKXtpZighZS52aXNpYmxlKXJldHVybiBudWxsO2xldHt2aXNpYmxlOmksY29sb3I6cixvcGFjaXR5Om99PWUscz1ERyh0aGlzLmJhY2tncm91bmRDb2xvcixyLG8/PzEpLGE9bmV3IEdnKHtjb2xvcjpzLHZpc2libGU6aX0pO3JldHVybiBuZXcgVm8odCxhKX1jcmVhdGVPclVwZGF0ZVRyaWFuZ2xlT2JqZWN0KHQsZSxpKXtsZXR7c2l6ZTpyfT1pLG89cipNYXRoLnNxcnQoMykvMixzPW5ldyBGbG9hdDMyQXJyYXkoW2UueC1yLzIsZS55LW8vMyxlLngrci8yLGUueS1vLzMsZS54LGUueSsyKm8vM10pO2lmKCF0KXtsZXQgbD1uZXcgbnI7SmRlKGwscyk7bGV0IGM9dGhpcy5jcmVhdGVNZXNoKGwsaSk7cmV0dXJuIG51bGw9PT1jP251bGw6KHRoaXMuc2NlbmUuYWRkKGMpLHt0eXBlOnB1LlRSSUFOR0xFLGRhdGE6ZSxvYmozZDpjfSl9cmV0dXJuIE5rKHRoaXMuYmFja2dyb3VuZENvbG9yLHQub2JqM2QsbD0+KEpkZShsLHMpLGwpLGkpP3t0eXBlOnB1LlRSSUFOR0xFLGRhdGE6ZSxvYmozZDp0Lm9iajNkfTp0fWNyZWF0ZU9yVXBkYXRlQ2lyY2xlT2JqZWN0KHQsZSxpKXtsZXR7cmFkaXVzOnJ9PWksbz1uZXcgeWIoaS5yYWRpdXMpO2lmKCF0KXtsZXQgYT10aGlzLmNyZWF0ZU1lc2gobyxpKTtyZXR1cm4gbnVsbD09PWE/bnVsbDooYS5wb3NpdGlvbi5zZXQoZS54LGUueSwwKSx0aGlzLnNjZW5lLmFkZChhKSx7dHlwZTpwdS5DSVJDTEUsZGF0YTp7bG9jOmUscmFkaXVzOnJ9LG9iajNkOmF9KX1yZXR1cm4gTmsodGhpcy5iYWNrZ3JvdW5kQ29sb3IsdC5vYmozZCwoKT0+byxpKT8odC5vYmozZC5wb3NpdGlvbi5zZXQoZS54LGUueSwwKSx7dHlwZTpwdS5DSVJDTEUsZGF0YTp7bG9jOmUscmFkaXVzOnJ9LG9iajNkOnQub2JqM2R9KTp0fWNyZWF0ZU9yVXBkYXRlVHJhcGV6b2lkT2JqZWN0KHQsZSxpLHIpe2lmKGUueSE9PWkueSl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiSW5wdXQgZXJyb3I6IHN0YXJ0LnkgIT0gZW5kLnkuIik7bGV0e2FsdGl0dWRlOm99PXIscz0yL01hdGguc3FydCgzKSpvLGE9bmV3IElwKFtuZXcgYXQoZS54LXMvMixlLnktby8yKSxuZXcgYXQoZS54LGUueStvLzIpLG5ldyBhdChpLngsaS55K28vMiksbmV3IGF0KGkueCtzLzIsaS55LW8vMildKTthLmF1dG9DbG9zZT0hMDtsZXQgbD1uZXcgUWcoYSk7aWYoIXQpe2xldCB1PXRoaXMuY3JlYXRlTWVzaChsLHIpO3JldHVybiBudWxsPT09dT9udWxsOih0aGlzLnNjZW5lLmFkZCh1KSx7dHlwZTpwdS5UUkFQRVpPSUQsZGF0YTpbZSxpXSxvYmozZDp1fSl9cmV0dXJuIE5rKHRoaXMuYmFja2dyb3VuZENvbG9yLHQub2JqM2QsKCk9Pmwscik/e3R5cGU6cHUuVFJBUEVaT0lELGRhdGE6W2UsaV0sb2JqM2Q6dC5vYmozZH06dH1mbHVzaCgpe3RoaXMucmVuZGVyZXIucmVuZGVyKHRoaXMuc2NlbmUsdGhpcy5jb29yZGluYXRvci5nZXRDYW1lcmEoKSl9ZGlzcG9zZSgpe3RoaXMucmVuZGVyZXIuZGlzcG9zZSgpfX0odC5jb250YWluZXIsZSx0LmRldmljZVBpeGVsUmF0aW8sdC5jYWxsYmFja3Mub25Db250ZXh0TG9zdCk7YnJlYWt9fXRoaXMucmVuZGVyZXIuc2V0VXNlRGFya01vZGUodC51c2VEYXJrTW9kZSksdGhpcy5zZXJpZXNMaW5lVmlldz1uZXcgVmsoe3JlbmRlcmVyOnRoaXMucmVuZGVyZXIsY29vcmRpbmF0b3I6dGhpcy5jb29yZGluYXRvcixnZXRNZXRhZGF0YU1hcDooKT0+dGhpcy5tZXRhZGF0YU1hcH0pLHRoaXMucmVzaXplKHQuZG9tRGltZW5zaW9uKX1kaXNwb3NlKCl7fXNldFhTY2FsZVR5cGUodCl7dGhpcy5jb29yZGluYXRvci5zZXRYU2NhbGUob3UodCkpLHRoaXMuc2NoZWR1bGVSZXBhaW50KCl9c2V0WVNjYWxlVHlwZSh0KXt0aGlzLmNvb3JkaW5hdG9yLnNldFlTY2FsZShvdSh0KSksdGhpcy5zY2hlZHVsZVJlcGFpbnQoKX1yZXNpemUodCl7dGhpcy5jb29yZGluYXRvci5zZXREb21Db250YWluZXJSZWN0KHt4OjAseTowLC4uLnR9KSx0aGlzLnJlbmRlcmVyLm9uUmVzaXplKHt4OjAseTowLC4uLnR9KSx0aGlzLnNlcmllc0xpbmVWaWV3LnNldExheW91dFJlY3Qoey4uLnQseDowLHk6MH0pLHRoaXMuc2NoZWR1bGVSZXBhaW50KCl9c2V0TWV0YWRhdGEodCl7bGV0IGU9ITE7T2JqZWN0LmVudHJpZXModCkuZm9yRWFjaCgoW2kscl0pPT57bGV0IG89dGhpcy5tZXRhZGF0YU1hcFtpXTsoIW98fHIuY29sb3IhPT1vLmNvbG9yfHxyLnZpc2libGUhPT1vLnZpc2libGV8fHIub3BhY2l0eSE9PW8ub3BhY2l0eSkmJihlPSEwKSx0aGlzLm1ldGFkYXRhTWFwW2ldPXJ9KSxlJiZ0aGlzLnNlcmllc0xpbmVWaWV3Lm1hcmtBc1BhaW50RGlydHkoKSx0aGlzLnNjaGVkdWxlUmVwYWludCgpfXNldFZpZXdCb3godCl7dGhpcy5jb29yZGluYXRvci5zZXRWaWV3Qm94UmVjdCh7eDp0LnhbMF0sd2lkdGg6dC54WzFdLXQueFswXSx5OnQueVswXSxoZWlnaHQ6dC55WzFdLXQueVswXX0pLHRoaXMuc2NoZWR1bGVSZXBhaW50KCl9c2V0RGF0YSh0KXt0aGlzLnNlcmllc0xpbmVWaWV3LnNldERhdGEodCksdGhpcy5zY2hlZHVsZVJlcGFpbnQoKX1zZXRVc2VEYXJrTW9kZSh0KXt0aGlzLnJlbmRlcmVyLnNldFVzZURhcmtNb2RlKHQpLHRoaXMuc2VyaWVzTGluZVZpZXcubWFya0FzUGFpbnREaXJ0eSgpLHRoaXMuc2NoZWR1bGVSZXBhaW50KCl9c2NoZWR1bGVSZXBhaW50KCl7dGhpcy5zaG91bGRSZXBhaW50fHwodGhpcy5zaG91bGRSZXBhaW50PSEwLChuPT57c2VsZi5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUobil9KSgoKT0+e3RoaXMucmVwYWludCgpLHRoaXMuc2hvdWxkUmVwYWludD0hMX0pKX1yZXBhaW50KCl7dGhpcy5zZXJpZXNMaW5lVmlldy5yZW5kZXIoKSx0aGlzLnJlbmRlcmVyLmZsdXNoKCksdGhpcy5jYWxsYmFja3Mub25EcmF3RW5kKCl9fSxEbD0oKCk9PihmdW5jdGlvbihuKXtuW24uU0VSSUVTX0RBVEFfVVBEQVRFRD0wXT0iU0VSSUVTX0RBVEFfVVBEQVRFRCIsbltuLlNFUklFU19NRVRBREFUQV9DSEFOR0VEPTFdPSJTRVJJRVNfTUVUQURBVEFfQ0hBTkdFRCIsbltuLlNDQUxFX1VQREFURUQ9Ml09IlNDQUxFX1VQREFURUQiLG5bbi5WSUVXX0JPWF9VUERBVEVEPTNdPSJWSUVXX0JPWF9VUERBVEVEIixuW24uSU5JVD00XT0iSU5JVCIsbltuLkRPTV9SRVNJWkVEPTVdPSJET01fUkVTSVpFRCIsbltuLkRBUktfTU9ERV9VUERBVEVEPTZdPSJEQVJLX01PREVfVVBEQVRFRCIsbltuLkRJU1BPU0VEPTddPSJESVNQT1NFRCJ9KERsfHwoRGw9e30pKSxEbCkpKCksRGI9KCgpPT4oZnVuY3Rpb24obil7bltuLk9OX1JFRFJBV19FTkQ9MF09Ik9OX1JFRFJBV19FTkQiLG5bbi5PTl9DT05URVhUX0xPU1Q9MV09Ik9OX0NPTlRFWFRfTE9TVCJ9KERifHwoRGI9e30pKSxEYikpKCk7ZnVuY3Rpb24gdHBlKG4pe2lmKG4uaW5jbHVkZXMoIi8iKSl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiV29ya2VyIGZhY3Rvcnkgb25seSBhbGxvd3MgZmlsZSBuYW1lIGFuZCBubyByZXNvdXJjZSBwYXRoLiIpO3JldHVybiBuZXcgV29ya2VyKG4pfXZhciBKZz1jbGFzc3tjb25zdHJ1Y3Rvcih0KXtpZih0aGlzLmNhbGxiYWNrcz10LmNhbGxiYWNrcyx0LnR5cGUhPT1kci5XRUJHTCl0aHJvdyBuZXcgUmFuZ2VFcnJvcihgQ2Fubm90IHVzZSBub24gV0VCR0wgcmVuZGVyZXIgZm9yIHRoZSBvZmZzY3JlZW4gbGluZSBjaGFydC4gUmVjZWl2ZWQgJHtkclt0LnR5cGVdfSBgKTtsZXQgZT1uZXcgTWVzc2FnZUNoYW5uZWw7ZS5wb3J0MS5vbm1lc3NhZ2U9bz0+e3RoaXMub25NZXNzYWdlRnJvbVdvcmtlcihvLmRhdGEpfSx0aGlzLnR4TWVzc2FnZVBvcnQ9ZS5wb3J0MTtsZXQgaT10LmNvbnRhaW5lci50cmFuc2ZlckNvbnRyb2xUb09mZnNjcmVlbigpO3RoaXMud29ya2VySW5zdGFuY2U9Smcud29ya2VyUG9vbC5nZXROZXh0KCk7bGV0IHI9e3R5cGU6RGwuSU5JVCxjYW52YXM6aSxkZXZpY2VQaXhlbFJhdGlvOndpbmRvdy5kZXZpY2VQaXhlbFJhdGlvLGRpbTp0LmRvbURpbWVuc2lvbixyZW5kZXJlclR5cGU6dC50eXBlLHVzZURhcmtNb2RlOnQudXNlRGFya01vZGV9O3RoaXMud29ya2VySW5zdGFuY2UucG9zdE1lc3NhZ2UocixbaSxlLnBvcnQyXSl9ZGlzcG9zZSgpe3RoaXMuc2VuZE1lc3NhZ2Uoe3R5cGU6RGwuRElTUE9TRUR9KSx0aGlzLndvcmtlckluc3RhbmNlLmZyZWUoKSx0aGlzLnR4TWVzc2FnZVBvcnQuY2xvc2UoKX1zZXRYU2NhbGVUeXBlKHQpe3RoaXMuc2VuZE1lc3NhZ2Uoe3R5cGU6RGwuU0NBTEVfVVBEQVRFRCxheGlzOiJ4IixzY2FsZVR5cGU6dH0pfXNldFlTY2FsZVR5cGUodCl7dGhpcy5zZW5kTWVzc2FnZSh7dHlwZTpEbC5TQ0FMRV9VUERBVEVELGF4aXM6InkiLHNjYWxlVHlwZTp0fSl9cmVzaXplKHQpe3RoaXMuc2VuZE1lc3NhZ2Uoe3R5cGU6RGwuRE9NX1JFU0laRUQsZGltOnR9KX1zZXRNZXRhZGF0YSh0KXt0aGlzLnNlbmRNZXNzYWdlKHt0eXBlOkRsLlNFUklFU19NRVRBREFUQV9DSEFOR0VELG1ldGFkYXRhOnR9KX1zZXRWaWV3Qm94KHQpe3RoaXMuc2VuZE1lc3NhZ2Uoe3R5cGU6RGwuVklFV19CT1hfVVBEQVRFRCxleHRlbnQ6dH0pfXNldERhdGEodCl7bGV0IGU9ZnVuY3Rpb24obil7bGV0IHQ9bi5yZWR1Y2UoKG8scyk9Pm8rcy5wb2ludHMubGVuZ3RoLDApLGU9MCxpPW5ldyBGbG9hdDY0QXJyYXkoMip0KSxyPVtdO2ZvcihsZXQgbyBvZiBuKXtyLnB1c2goe2lkOm8uaWQsbGVuZ3RoOm8ucG9pbnRzLmxlbmd0aH0pO2ZvcihsZXQgcz0wO3M8by5wb2ludHMubGVuZ3RoO3MrKylpW2UrK109by5wb2ludHNbc10ueCxpW2UrK109by5wb2ludHNbc10ueX1yZXR1cm57aWRzQW5kTGVuZ3RoczpyLGZsYXR0ZW5lZFNlcmllczppLmJ1ZmZlcn19KHQpO3RoaXMuc2VuZE1lc3NhZ2Uoe3R5cGU6RGwuU0VSSUVTX0RBVEFfVVBEQVRFRCxjb21wYWN0RGF0YVNlcmllczplfSxbZS5mbGF0dGVuZWRTZXJpZXNdKX1zZXRVc2VEYXJrTW9kZSh0KXt0aGlzLnNlbmRNZXNzYWdlKHt0eXBlOkRsLkRBUktfTU9ERV9VUERBVEVELHVzZURhcmtNb2RlOnR9KX1zZW5kTWVzc2FnZSh0LGUpe2U/dGhpcy50eE1lc3NhZ2VQb3J0LnBvc3RNZXNzYWdlKHQsZSk6dGhpcy50eE1lc3NhZ2VQb3J0LnBvc3RNZXNzYWdlKHQpfW9uTWVzc2FnZUZyb21Xb3JrZXIodCl7c3dpdGNoKHQudHlwZSl7Y2FzZSBEYi5PTl9SRURSQVdfRU5EOnRoaXMuY2FsbGJhY2tzLm9uRHJhd0VuZCgpO2JyZWFrO2Nhc2UgRGIuT05fQ09OVEVYVF9MT1NUOnRoaXMuY2FsbGJhY2tzLm9uQ29udGV4dExvc3QoKX19fTtmdW5jdGlvbiBBZChuLHQpe3JldHVybiJ4Ij09PXQ/WzAsbi53aWR0aF06W24uaGVpZ2h0LDBdfWZ1bmN0aW9uIHZFKG4sdCl7bGV0IGU9TWF0aC5mbG9vcihuLzUwKTtyZXR1cm4gTWF0aC5taW4oZSx0KX1mdW5jdGlvbiBQRyhuLHQsZSxpKXtyZXR1cm57bWFqb3I6W10sbWlub3I6bi50aWNrcyhpLGUpLm1hcChvPT4oe3ZhbHVlOm8sdGlja0Zvcm1hdHRlZFN0cmluZzp0LmZvcm1hdFRpY2sobyl9KSl9fUpnLndvcmtlclBvb2w9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZT0xMCxpPXRwZSl7dGhpcy53b3JrZXJSZXNvdXJjZVBhdGg9dCx0aGlzLm1heFBvb2xTaXplPWUsdGhpcy53b3JrZXJGYWN0b3J5PWksdGhpcy53b3JrZXJzPVtdfWdldE5leHQoKXtsZXQgdDtpZih0aGlzLndvcmtlcnMuZXZlcnkoKHthY3RpdmVDb3VudDppfSk9Pmk+MCkmJnRoaXMud29ya2Vycy5sZW5ndGg8dGhpcy5tYXhQb29sU2l6ZSl7bGV0IGk9dGhpcy53b3JrZXJGYWN0b3J5KHRoaXMud29ya2VyUmVzb3VyY2VQYXRoKTt0PXthY3RpdmVDb3VudDowLHBvc3RNZXNzYWdlOihyLG8pPT57aS5wb3N0TWVzc2FnZShyLG8pfSxmcmVlOigpPT57dC5hY3RpdmVDb3VudD1NYXRoLm1heCh0LmFjdGl2ZUNvdW50LTEsMCl9fSx0aGlzLndvcmtlcnMucHVzaCh0KX1lbHNle2xldCBpPXRoaXMud29ya2Vycy5tYXAoKHthY3RpdmVDb3VudDpvfSk9Pm8pLHI9aS5pbmRleE9mKE1hdGgubWluKC4uLmkpKTt0PXRoaXMud29ya2Vyc1tyXX1yZXR1cm4gdC5hY3RpdmVDb3VudCsrLHR9fSgiY2hhcnRfd29ya2VyLmpzP19maWxlX2hhc2g9YzQ0MTc2ODEiKTt2YXIgSUc9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiY2FudmFzIikuZ2V0Q29udGV4dCgiMmQiKSx5RT17Z2V0U3RhbmRhcmRUaWNrczpQRyxnZXRUaWNrc0ZvclRlbXBvcmFsU2NhbGU6ZnVuY3Rpb24obix0LGUsaSl7bGV0W3Isb109aSxzPW4udGlja3MoaSwyKTtpZihvLXI+PTg2NGU1fHxzLmxlbmd0aD4yKXJldHVybiBQRyhuLHQsZSxpKTtsZXQgYT1uLnRpY2tzKGksZSk7cmV0dXJue21ham9yOnMubWFwKGw9Pih7c3RhcnQ6bCx0aWNrRm9ybWF0dGVkU3RyaW5nOnQuZm9ybWF0U2hvcnQobCl9KSksbWlub3I6YS5tYXAobD0+KHt2YWx1ZTpsLHRpY2tGb3JtYXR0ZWRTdHJpbmc6dC5mb3JtYXRUaWNrKGwpfSkpfX0sZ2V0VGlja3NGb3JMaW5lYXJTY2FsZTpmdW5jdGlvbihuLHQsZSxpKXtsZXRbcixvXT1pLHM9TWF0aC5hYnMoby1yKTtpZihzPi4wMDEpcmV0dXJuIFBHKG4sdCxlLGkpO2xldCBhPW4udGlja3MoW3Isb10sZSksbD1uLnRpY2tzKFtyLG9dLDIpLGM9W10sdT1mdW5jdGlvbihuKXtsZXQgdD1uLnRvRXhwb25lbnRpYWwoKS5zcGxpdCgiZS0iLDIpO3JldHVybiAyPT09dC5sZW5ndGg/TnVtYmVyKHRbMV0pLTE6MH0ocyk7czwxJiZsLmV2ZXJ5KGg9PntsZXQgZj1NYXRoLmFicyhoKTtyZXR1cm4gZj49MCYmZjwxfSkmJih1Kz0xKTtsZXQgZD1uZXcgTWFwO2ZvcihsZXQgaCBvZiBsKXtsZXRbZixtPSIiXT1TdHJpbmcoaCkuc3BsaXQoIi4iLDIpLHg9TnVtYmVyKGYrIi4iK20uc2xpY2UoMCx1KSk7ZC5zZXQoeCx7c3RhcnQ6eCx0aWNrRm9ybWF0dGVkU3RyaW5nOjA9PT14PyJcdTIwMTQiOnQuZm9ybWF0UmVhZGFibGUoeCl9KX1sZXQgcD0xMCpNYXRoLnBvdygxMCwtdSk7Zm9yKGxldCBoIG9mIGEpZm9yKGxldCBmIG9mWy4uLmQua2V5cygpXS5yZXZlcnNlKCkpe2xldCBtPWgtZjtpZihtPj0wJiZtPHApe2lmKDA9PT1mKWMucHVzaCh7dmFsdWU6aCx0aWNrRm9ybWF0dGVkU3RyaW5nOnQuZm9ybWF0VGljayhoKX0pO2Vsc2V7bGV0IHg9U3RyaW5nKGgpLnNsaWNlKFN0cmluZyhmKS5sZW5ndGgpO2MucHVzaCh7dmFsdWU6aCx0aWNrRm9ybWF0dGVkU3RyaW5nOmBcdTIwMjYke3h8fCIwIn1gfSl9YnJlYWt9fXJldHVybnttYWpvcjpBcnJheS5mcm9tKGQudmFsdWVzKCkpLG1pbm9yOmN9fSxmaWx0ZXJUaWNrc0J5VmlzaWJpbGl0eTpmdW5jdGlvbihuLHQsZSxpLHI9NSl7aWYoIW4ubGVuZ3RofHwhSUcpcmV0dXJuIG47bGV0IG89IngiPT09ZT8xOi0xLHM9bnVsbDtyZXR1cm4gbi5maWx0ZXIoYT0+e2xldCBsPXQoYSk7SUcuZm9udD1pO2xldCBjPUlHLm1lYXN1cmVUZXh0KGEudGlja0Zvcm1hdHRlZFN0cmluZyksdT0ieCI9PT1lP2Mud2lkdGg6Yy5hY3R1YWxCb3VuZGluZ0JveEFzY2VudC1jLmFjdHVhbEJvdW5kaW5nQm94RGVzY2VudDtyZXR1cm4gbnVsbD09PXM/IShsK28qdTwwfHwocz1sK28qdSwwKSk6IShvKihzK28qci1sKT4wfHwocz1sK28qdSwwKSl9KX19O2Z1bmN0aW9uIGRxZShuLHQpe2lmKDEmbiYmKEluKCksXygwLCJnIiwxNykoMSwidGV4dCIpLEEoMiksdigpLF8oMywidGl0bGUiKSxBKDQpLHYoKSgpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0LGk9UygpO0MoMSksUHQoImZvbnQiLGkuYXhpc0ZvbnQpLHplKCJ4IixpLnRleHRYUG9zaXRpb24oZS52YWx1ZSkpKCJ5IixpLnRleHRZUG9zaXRpb24oZS52YWx1ZSkpLEMoMSksamUoIiAiLGUudGlja0Zvcm1hdHRlZFN0cmluZywiICIpLEMoMikseXQoaS5nZXRGb3JtYXR0ZXIoKS5mb3JtYXRMb25nKGUudmFsdWUpKX19ZnVuY3Rpb24gcHFlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiwyMCkoMSwic3BhbiIpLEEoMiksdigpKCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT10LmluZGV4LHI9dC5sYXN0LG89UygyKTtQdCgibGVmdCIsby5nZXRNYWpvclhQb3NpdGlvbihlKSwicHgiKSgid2lkdGgiLG8uZ2V0TWFqb3JXaWR0aFN0cmluZyhlLHIsby5tYWpvclRpY2tzW2krMV0pKSgiYm90dG9tIixvLmdldE1ham9yWVBvc2l0aW9uKGUpLCJweCIpKCJoZWlnaHQiLG8uZ2V0TWFqb3JIZWlnaHRTdHJpbmcoZSxyLG8ubWFqb3JUaWNrc1tpKzFdKSkoImZvbnQiLG8uYXhpc0ZvbnQpLGV0KCJtYWpvci1sYWJlbCIsITApKCJsYXN0IixyKSx5KCJ0aXRsZSIsby5nZXRGb3JtYXR0ZXIoKS5mb3JtYXRMb25nKGUuc3RhcnQpKSxDKDIpLHl0KGUudGlja0Zvcm1hdHRlZFN0cmluZyl9fWZ1bmN0aW9uIGhxZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwxOCksRSgxLHBxZSwzLDE2LCJzcGFuIiwxOSksdigpKSwyJm4pe2xldCBlPVMoKTtDKDEpLHkoIm5nRm9yT2YiLGUubWFqb3JUaWNrcykoIm5nRm9yVHJhY2tCeSIsZS50cmFja0J5TWFqb3JUaWNrKX19dmFyIG9wZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5vblZpZXdFeHRlbnRDaGFuZ2U9bmV3IEcsdGhpcy5lZGl0TWVudU9wZW5lZD0hMSx0aGlzLm1ham9yVGlja3M9W10sdGhpcy5taW5vclRpY2tzPVtdfW5nT25DaGFuZ2VzKCl7bGV0IGU9bnVsbCxyPXZFKCJ4Ij09PXRoaXMuYXhpcz90aGlzLmRvbURpbS53aWR0aDp0aGlzLmRvbURpbS5oZWlnaHQsdGhpcy5ncmlkQ291bnQpO2U9dGhpcy5zY2FsZSBpbnN0YW5jZW9mIEVTP3lFLmdldFRpY2tzRm9yTGluZWFyU2NhbGUodGhpcy5zY2FsZSx0aGlzLmdldEZvcm1hdHRlcigpLHIsdGhpcy5heGlzRXh0ZW50KTp0aGlzLnNjYWxlIGluc3RhbmNlb2YgVFM/eUUuZ2V0VGlja3NGb3JUZW1wb3JhbFNjYWxlKHRoaXMuc2NhbGUsdGhpcy5nZXRGb3JtYXR0ZXIoKSxyLHRoaXMuYXhpc0V4dGVudCk6eUUuZ2V0U3RhbmRhcmRUaWNrcyh0aGlzLnNjYWxlLHRoaXMuZ2V0Rm9ybWF0dGVyKCkscix0aGlzLmF4aXNFeHRlbnQpLHRoaXMubWFqb3JUaWNrcz1lLm1ham9yLHRoaXMubWlub3JUaWNrcz15RS5maWx0ZXJUaWNrc0J5VmlzaWJpbGl0eShlLm1pbm9yLG89PnRoaXMuZ2V0RG9tUG9zKG8udmFsdWUpLHRoaXMuYXhpcywiMTFweCBSb2JvdG8sIHNhbnMtc2VyaWYiKX1nZXRGb3JtYXR0ZXIoKXtyZXR1cm4gdGhpcy5jdXN0b21Gb3JtYXR0ZXI/P3RoaXMuc2NhbGUuZGVmYXVsdEZvcm1hdHRlcn10cmFja0J5TWlub3JUaWNrKGUpe3JldHVybiBlLnZhbHVlfXRyYWNrQnlNYWpvclRpY2soZSl7cmV0dXJuIGUuc3RhcnR9Z2V0RG9tUG9zKGUpe3JldHVybiB0aGlzLnNjYWxlLmZvcndhcmQodGhpcy5heGlzRXh0ZW50LEFkKHRoaXMuZG9tRGltLHRoaXMuYXhpcyksZSl9dGV4dFhQb3NpdGlvbihlKXtyZXR1cm4ieCI9PT10aGlzLmF4aXM/U3RyaW5nKHRoaXMuZ2V0RG9tUG9zKGUpKToiMTAwJSJ9dGV4dFlQb3NpdGlvbihlKXtyZXR1cm4ieCI9PT10aGlzLmF4aXM/IiI6U3RyaW5nKHRoaXMuZ2V0RG9tUG9zKGUpKX1nZXRNYWpvclhQb3NpdGlvbihlKXtyZXR1cm4ieSI9PT10aGlzLmF4aXM/MDpNYXRoLm1pbih0aGlzLmRvbURpbS53aWR0aCxNYXRoLm1heCgwLHRoaXMuZ2V0RG9tUG9zKGUuc3RhcnQpKSl9Z2V0TWFqb3JXaWR0aFN0cmluZyhlLGkscil7cmV0dXJuInkiPT09dGhpcy5heGlzPyIiOihpfHwhcj90aGlzLmRvbURpbS53aWR0aDp0aGlzLmdldE1ham9yWFBvc2l0aW9uKHIpKS10aGlzLmdldE1ham9yWFBvc2l0aW9uKGUpKyJweCJ9Z2V0TWFqb3JZUG9zaXRpb24oZSl7cmV0dXJuIngiPT09dGhpcy5heGlzPzA6dGhpcy5kb21EaW0uaGVpZ2h0LU1hdGgubWluKHRoaXMuZG9tRGltLmhlaWdodCxNYXRoLm1heCgwLHRoaXMuZ2V0RG9tUG9zKGUuc3RhcnQpKSl9Z2V0TWFqb3JIZWlnaHRTdHJpbmcoZSxpLHIpe3JldHVybiJ4Ij09PXRoaXMuYXhpcz8iIjooaXx8IXI/dGhpcy5kb21EaW0uaGVpZ2h0OnRoaXMuZ2V0TWFqb3JZUG9zaXRpb24ocikpLXRoaXMuZ2V0TWFqb3JZUG9zaXRpb24oZSkrInB4In1rZXlkb3duUHJldmVudENsb3NlKGUpeyJFc2NhcGUiIT09ZS5rZXkmJmUuc3RvcFByb3BhZ2F0aW9uKCl9ZXh0ZW50Q2hhbmdlZChlLGkpe2xldCByPU51bWJlcihlKSxvPU51bWJlcihpKTtpZihvPHIpe2xldCBzPXI7cj1vLG89c30hTnVtYmVyLmlzRmluaXRlKHIpfHwhTnVtYmVyLmlzRmluaXRlKG8pfHx0aGlzLm9uVmlld0V4dGVudENoYW5nZS5lbWl0KFtyLG9dKX1vbkF4aXNVcGRhdGVNZW51T3BlbihlLGkscil7ZS52YWx1ZT1TdHJpbmcoclswXSksaS52YWx1ZT1TdHJpbmcoclsxXSksZS5mb2N1cygpfXNldEVkaXRNZW51T3BlbmVkKGUpe3RoaXMuZWRpdE1lbnVPcGVuZWQ9ZX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibGluZS1jaGFydC1heGlzIl1dLGlucHV0czp7YXhpc0V4dGVudDoiYXhpc0V4dGVudCIsYXhpczoiYXhpcyIsc2NhbGU6InNjYWxlIixncmlkQ291bnQ6ImdyaWRDb3VudCIsZG9tRGltOiJkb21EaW0iLGN1c3RvbUZvcm1hdHRlcjoiY3VzdG9tRm9ybWF0dGVyIn0sb3V0cHV0czp7b25WaWV3RXh0ZW50Q2hhbmdlOiJvblZpZXdFeHRlbnRDaGFuZ2UifSxmZWF0dXJlczpbRnRdLGRlY2xzOjI2LHZhcnM6MTMsY29uc3RzOltbMSwibGluZSJdLFsxLCJtaW5vciJdLFsxLCJ0aWNrcyJdLFsiY2xhc3MiLCJtaW5vci10aWNrLWxhYmVsIiw0LCJuZ0ZvciIsIm5nRm9yT2YiLCJuZ0ZvclRyYWNrQnkiXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsInRpdGxlIiwiQ2xpY2sgdG8gbWFudWFsbHkgc2V0IG1pbiAmIG1heCB2YWx1ZXMiLDMsIm1hdE1lbnVUcmlnZ2VyRm9yIiwibWVudU9wZW5lZCIsIm1lbnVDbG9zZWQiXSxbIm1hdE1lbnVUcmlnZ2VyIiwibWF0TWVudVRyaWdnZXIiXSxbInN2Z0ljb24iLCJlZGl0XzI0cHgiXSxbImNsYXNzIiwibWFqb3IgdGlja3MiLDQsIm5nSWYiXSxbInhQb3NpdGlvbiIsImJlZm9yZSIsMywieVBvc2l0aW9uIl0sWyJtYW51YWxDb250cm9sIiwibWF0TWVudSJdLFsxLCJleHRlbnQtZWRpdC1pbnB1dCIsMywiY2xpY2siLCJrZXlkb3duIl0sWyJ0eXBlIiwibnVtYmVyIiwzLCJ2YWx1ZSJdLFsibWluSW5wdXQiLCIiXSxbIm1heElucHV0IiwiIl0sWzEsImV4dGVudC1lZGl0LWNvbnRyb2wiLDMsImtleWRvd24iXSxbIm1hdC1yYWlzZWQtYnV0dG9uIiwiIiwiY29sb3IiLCJwcmltYXJ5IiwxLCJleHRlbnQtZWRpdC1jaGFuZ2UiLDMsImNsaWNrIl0sWyJtYXQtc3Ryb2tlZC1idXR0b24iLCIiLDEsImV4dGVudC1lZGl0LWNhbmNlbCIsMywiY2xpY2siXSxbMSwibWlub3ItdGljay1sYWJlbCJdLFsxLCJtYWpvciIsInRpY2tzIl0sWzMsIm1ham9yLWxhYmVsIiwibGFzdCIsImxlZnQiLCJ3aWR0aCIsImJvdHRvbSIsImhlaWdodCIsImZvbnQiLCJ0aXRsZSIsNCwibmdGb3IiLCJuZ0Zvck9mIiwibmdGb3JUcmFja0J5Il0sWzMsInRpdGxlIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlKXtsZXQgcj1QZSgpO18oMCwiZGl2IiksTygxLCJkaXYiLDApLF8oMiwiZGl2IiwxKSxJbigpLF8oMywic3ZnIiwyKSxFKDQsZHFlLDUsNiwiZyIsMyksdigpLEpzKCksXyg1LCJidXR0b24iLDQsNSksUCgibWVudU9wZW5lZCIsZnVuY3Rpb24oKXtvZShyKTtsZXQgcz0kZSgxNSksYT0kZSgyMCk7cmV0dXJuIGkub25BeGlzVXBkYXRlTWVudU9wZW4ocyxhLGkuYXhpc0V4dGVudCksc2UoaS5zZXRFZGl0TWVudU9wZW5lZCghMCkpfSkoIm1lbnVDbG9zZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuc2V0RWRpdE1lbnVPcGVuZWQoITEpfSksTyg3LCJtYXQtaWNvbiIsNiksdigpKCksRSg4LGhxZSwyLDIsImRpdiIsNyksdigpLF8oOSwibWF0LW1lbnUiLDgsOSkoMTEsImRpdiIsMTApLFAoImNsaWNrIixmdW5jdGlvbihzKXtyZXR1cm4gcy5zdG9wUHJvcGFnYXRpb24oKX0pKCJrZXlkb3duIixmdW5jdGlvbihzKXtyZXR1cm4gaS5rZXlkb3duUHJldmVudENsb3NlKHMpfSksXygxMiwibGFiZWwiKSxBKDEzLCJtaW4iKSx2KCksTygxNCwiaW5wdXQiLDExLDEyKSx2KCksXygxNiwiZGl2IiwxMCksUCgiY2xpY2siLGZ1bmN0aW9uKHMpe3JldHVybiBzLnN0b3BQcm9wYWdhdGlvbigpfSkoImtleWRvd24iLGZ1bmN0aW9uKHMpe3JldHVybiBpLmtleWRvd25QcmV2ZW50Q2xvc2Uocyl9KSxfKDE3LCJsYWJlbCIpLEEoMTgsIm1heCIpLHYoKSxPKDE5LCJpbnB1dCIsMTEsMTMpLHYoKSxfKDIxLCJkaXYiLDE0KSxQKCJrZXlkb3duIixmdW5jdGlvbihzKXtyZXR1cm4gaS5rZXlkb3duUHJldmVudENsb3NlKHMpfSksXygyMiwiYnV0dG9uIiwxNSksUCgiY2xpY2siLGZ1bmN0aW9uKCl7b2Uocik7bGV0IHM9JGUoMTUpLGE9JGUoMjApLGw9JGUoNik7cmV0dXJuIGkuZXh0ZW50Q2hhbmdlZChzLnZhbHVlLGEudmFsdWUpLHNlKGwuY2xvc2VNZW51KCkpfSksQSgyMywiIENoYW5nZSAiKSx2KCksXygyNCwiYnV0dG9uIiwxNiksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKHIpLHNlKCRlKDYpLmNsb3NlTWVudSgpKX0pLEEoMjUsIiBDYW5jZWwgIiksdigpKCkoKX1pZigyJmUpe2xldCByPSRlKDEwKTtEYShpLmF4aXMrIi1heGlzIGF4aXMiKSxDKDQpLHkoIm5nRm9yT2YiLGkubWlub3JUaWNrcykoIm5nRm9yVHJhY2tCeSIsaS50cmFja0J5TWlub3JUaWNrKSxDKDEpLGV0KCJleHRlbnQtZWRpdC1idXR0b24iLCEwKSgiZXh0ZW50LWVkaXQtbWVudS1vcGVuZWQiLGkuZWRpdE1lbnVPcGVuZWQpLHkoIm1hdE1lbnVUcmlnZ2VyRm9yIixyKSxDKDMpLHkoIm5nSWYiLGkubWFqb3JUaWNrcy5sZW5ndGgpLEMoMSkseSgieVBvc2l0aW9uIiwieSI9PT1pLmF4aXM/ImFib3ZlIjoiYmVsb3ciKSxDKDUpLHkoInZhbHVlIixpLmF4aXNFeHRlbnRbMF0pLEMoNSkseSgidmFsdWUiLGkuYXhpc0V4dGVudFsxXSl9fSxkZXBlbmRlbmNpZXM6W2RuLEJlLF9uLEd0LGhkLGZkXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2NvbnRhaW46c3RyaWN0O2Rpc3BsYXk6ZmxleDtvdmVyZmxvdzpoaWRkZW59Lm1ham9yLWxhYmVsW19uZ2NvbnRlbnQtJUNPTVAlXSwgdGV4dFtfbmdjb250ZW50LSVDT01QJV17ZmlsbDpjdXJyZW50Q29sb3I7Zm9udC1zaXplOjExcHg7dXNlci1zZWxlY3Q6bm9uZX0uYXhpc1tfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpmbGV4O2hlaWdodDoxMDAlO3dpZHRoOjEwMCV9Lm1ham9yW19uZ2NvbnRlbnQtJUNPTVAlXSwgLm1pbm9yW19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4OjEgMDtvdmVyZmxvdzpoaWRkZW59LmxpbmVbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2FhYTtmbGV4OjAgMCAxcHg7anVzdGlmeS1jb250ZW50OnN0cmV0Y2h9LnRpY2tzW19uZ2NvbnRlbnQtJUNPTVAlXXtoZWlnaHQ6MTAwJTtwb3NpdGlvbjpyZWxhdGl2ZTt3aWR0aDoxMDAlfS54LWF4aXNbX25nY29udGVudC0lQ09NUCVde2ZsZXgtZGlyZWN0aW9uOmNvbHVtbn0ueC1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5saW5lW19uZ2NvbnRlbnQtJUNPTVAlXXttYXJnaW4tYm90dG9tOjNweH0ueC1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHRleHRbX25nY29udGVudC0lQ09NUCVde2RvbWluYW50LWJhc2VsaW5lOnRleHQtYmVmb3JlLWVkZ2U7dGV4dC1hbmNob3I6bWlkZGxlfS54LWF4aXNbX25nY29udGVudC0lQ09NUCVdICAgLnRpY2tzW19uZ2NvbnRlbnQtJUNPTVAlXXstd2Via2l0LW1hc2staW1hZ2U6bGluZWFyLWdyYWRpZW50KHRvIHJpZ2h0LCByZ2JhKDAsIDAsIDAsIDApIDAlLCAjMDAwIDEwJSwgIzAwMCA5MCUsIHJnYmEoMCwgMCwgMCwgMCkgMTAwJSk7bWFzay1pbWFnZTpsaW5lYXItZ3JhZGllbnQodG8gcmlnaHQsIHJnYmEoMCwgMCwgMCwgMCkgMCUsICMwMDAgMTAlLCAjMDAwIDkwJSwgcmdiYSgwLCAwLCAwLCAwKSAxMDAlKX0ueS1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4LWRpcmVjdGlvbjpyb3ctcmV2ZXJzZX0ueS1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5saW5lW19uZ2NvbnRlbnQtJUNPTVAlXXttYXJnaW4tbGVmdDo1cHh9LnktYXhpc1tfbmdjb250ZW50LSVDT01QJV0gICB0ZXh0W19uZ2NvbnRlbnQtJUNPTVAlXXtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsO3RleHQtYW5jaG9yOmVuZH0ueS1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC50aWNrc1tfbmdjb250ZW50LSVDT01QJV17LXdlYmtpdC1tYXNrLWltYWdlOmxpbmVhci1ncmFkaWVudCh0byBib3R0b20sIHJnYmEoMCwgMCwgMCwgMCkgMCUsICMwMDAgMTAlLCAjMDAwIDkwJSwgcmdiYSgwLCAwLCAwLCAwKSAxMDAlKTttYXNrLWltYWdlOmxpbmVhci1ncmFkaWVudCh0byBib3R0b20sIHJnYmEoMCwgMCwgMCwgMCkgMCUsICMwMDAgMTAlLCAjMDAwIDkwJSwgcmdiYSgwLCAwLCAwLCAwKSAxMDAlKX0uZXh0ZW50LWVkaXQtYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNlZWU7Zm9udC1zaXplOjA7aGVpZ2h0OjI0cHg7bGluZS1oZWlnaHQ6MjRweDtwb3NpdGlvbjphYnNvbHV0ZTtyaWdodDo1cHg7dG9wOjVweDt2aXNpYmlsaXR5OmhpZGRlbjt3aWR0aDoyNHB4fS5leHRlbnQtZWRpdC1idXR0b25bX25nY29udGVudC0lQ09NUCVdICAgbWF0LWljb25bX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxNnB4O3dpZHRoOjE2cHg7bGluZS1oZWlnaHQ6MTZweH0uZXh0ZW50LWVkaXQtaW5wdXRbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtjb2x1bW4tZ2FwOjVweDtkaXNwbGF5OmdyaWQ7Zm9udC1zaXplOjEycHg7Z3JpZC10ZW1wbGF0ZS1jb2x1bW5zOjMwcHggbWlubWF4KGF1dG8sIDEwMHB4KTtoZWlnaHQ6MzBweDttYXJnaW46MTBweCAyMHB4fS5leHRlbnQtZWRpdC1pbnB1dFtfbmdjb250ZW50LSVDT01QJV0gICBpbnB1dFtfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjppbmhlcml0O2JvcmRlci1yYWRpdXM6NHB4O2JvcmRlci1zdHlsZTpzb2xpZDtjb2xvcjppbmhlcml0fS5leHRlbnQtZWRpdC1jb250cm9sW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7ZGlzcGxheTpmbGV4O2ZsZXgtZGlyZWN0aW9uOnJvdy1yZXZlcnNlO2p1c3RpZnktY29udGVudDpmbGV4LWVuZDttYXJnaW46MTBweCAyMHB4fS5leHRlbnQtZWRpdC1jb250cm9sW19uZ2NvbnRlbnQtJUNPTVAlXSAgIGJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17Zm9udC1zaXplOjEycHg7aGVpZ2h0OjMwcHg7bGluZS1oZWlnaHQ6MS40O21hcmdpbi1sZWZ0OjVweDtwYWRkaW5nOjAgMTBweH0uYXhpc1tfbmdjb250ZW50LSVDT01QJV06aG92ZXIgICAuZXh0ZW50LWVkaXQtYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXSwgLmF4aXNbX25nY29udGVudC0lQ09NUCVdOmZvY3VzLXdpdGhpbiAgIC5leHRlbnQtZWRpdC1idXR0b25bX25nY29udGVudC0lQ09NUCVdLCAuZXh0ZW50LWVkaXQtbWVudS1vcGVuZWRbX25nY29udGVudC0lQ09NUCVde3Zpc2liaWxpdHk6dmlzaWJsZX0ubWFqb3JbX25nY29udGVudC0lQ09NUCVde3Bvc2l0aW9uOnJlbGF0aXZlO292ZXJmbG93OmhpZGRlbjtjb250YWluOnN0cmljdH0ubWFqb3JbX25nY29udGVudC0lQ09NUCVdICAgLm1ham9yLWxhYmVsW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7Ym94LXNpemluZzpib3JkZXItYm94O2Rpc3BsYXk6aW5saW5lLWZsZXg7anVzdGlmeS1jb250ZW50OmNlbnRlcjtvdmVyZmxvdzpoaWRkZW47cG9zaXRpb246YWJzb2x1dGU7d2hpdGUtc3BhY2U6bm93cmFwfS5tYWpvcltfbmdjb250ZW50LSVDT01QJV0gICAubWFqb3ItbGFiZWxbX25nY29udGVudC0lQ09NUCVdICAgc3Bhbltfbmdjb250ZW50LSVDT01QJV17bWF4LXdpZHRoOjEwMCV9LngtYXhpc1tfbmdjb250ZW50LSVDT01QJV0gICAubWFqb3ItbGFiZWxbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjFweCBzb2xpZCAjOWU5ZTllO3BhZGRpbmc6MCA1cHh9LngtYXhpc1tfbmdjb250ZW50LSVDT01QJV0gICAubWFqb3ItbGFiZWwubGFzdFtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLXJpZ2h0OjFweCBzb2xpZCAjOWU5ZTllfS55LWF4aXNbX25nY29udGVudC0lQ09NUCVdICAgLm1ham9yLWxhYmVsW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjOWU5ZTllO2hlaWdodDoxMDAlO3BhZGRpbmc6NXB4IDA7d2lkdGg6MTAwJX0ueS1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5tYWpvci1sYWJlbC5sYXN0W19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItdG9wOjFweCBzb2xpZCAjOWU5ZTllfS55LWF4aXNbX25nY29udGVudC0lQ09NUCVdICAgLm1ham9yLWxhYmVsW19uZ2NvbnRlbnQtJUNPTVAlXSA+IHNwYW5bX25nY29udGVudC0lQ09NUCVde3RyYW5zZm9ybTpyb3RhdGUoLTkwZGVnKTt0cmFuc2Zvcm0tb3JpZ2luOmNlbnRlcn0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIGJFKG4sdCl7bGV0IGU9TWF0aC5taW4oaXUobi5tYXAoKHt4Om99KT0+byksdCksbi5sZW5ndGgtMSksaT1NYXRoLm1heCgwLGUtMSk7cmV0dXJuIE1hdGguYWJzKG5baV0ueC10KS1NYXRoLmFicyhuW2VdLngtdCk8PTA/aTplfWZ1bmN0aW9uIHNwZShuLHQsZSxpLHIsbyl7bGV0IHM7c3dpdGNoKG4uZGVsdGFNb2RlKXtjYXNlIFdoZWVsRXZlbnQuRE9NX0RFTFRBX1BJWEVMOnM9MTticmVhaztjYXNlIFdoZWVsRXZlbnQuRE9NX0RFTFRBX0xJTkU6cz04O2JyZWFrO2Nhc2UgV2hlZWxFdmVudC5ET01fREVMVEFfUEFHRTpzPTIwO2JyZWFrO2RlZmF1bHQ6cz0xLGNvbnNvbGUud2FybihgVW5rbm93biBXaGVlbEV2ZW50IGRlbHRhTW9kZTogJHtuLmRlbHRhTW9kZX0uYCl9bGV0IGE9bi5kZWx0YVkqcyxsPWE8MD9NYXRoLm1heChhKmksLS45NSk6YSppLHt3aWR0aDpjLGhlaWdodDp1fT1lLGQ9W3IucmV2ZXJzZSh0LngsWzAsY10sLW4ub2Zmc2V0WCpsKSxyLnJldmVyc2UodC54LFswLGNdLGMrKGMtbi5vZmZzZXRYKSpsKV0scD1bby5yZXZlcnNlKHQueSxbdSwwXSwtbi5vZmZzZXRZKmwpLG8ucmV2ZXJzZSh0LnksW3UsMF0sdSsodS1uLm9mZnNldFkpKmwpXTtyZXR1cm57eDpkWzFdPGRbMF0/W2RbMV0sZFswXV06ZCx5OnBbMV08cFswXT9bcFsxXSxwWzBdXTpwfX12YXIgZ3FlPVsiZG90cyJdO2Z1bmN0aW9uIF9xZShuLHQpe2lmKDEmbiYmKEluKCksTygwLCJjaXJjbGUiLDEyKSksMiZuKXtsZXQgZT1TKCkuJGltcGxpY2l0LGk9UygyKTt6ZSgiY3giLGkuZ2V0RG9tWChlLmRhdGFQb2ludC54KSkoImN5IixpLmdldERvbVkoZS5kYXRhUG9pbnQueSkpKCJmaWxsIixlLm1ldGFkYXRhLmNvbG9yKX19ZnVuY3Rpb24gdnFlKG4sdCl7aWYoMSZuJiYoSW4oKSxzbigwKSxFKDEsX3FlLDEsMywiY2lyY2xlIiwxMSksYW4oKSksMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPVMoMik7QygxKSx5KCJuZ0lmIixpLnNob3VsZFJlbmRlclRvb2x0aXBQb2ludChlLmRhdGFQb2ludCkpfX1mdW5jdGlvbiB5cWUobix0KXtpZigxJm4mJihJbigpLHNuKDApLEUoMSx2cWUsMiwxLCJuZy1jb250YWluZXIiLDEwKSxhbigpKSwyJm4pe2xldCBlPVMoKTtDKDEpLHkoIm5nRm9yT2YiLGUuY3Vyc29yZWREYXRhKSgibmdGb3JUcmFja0J5IixlLnRyYWNrQnlTZXJpZXNOYW1lKX19ZnVuY3Rpb24gYnFlKG4sdCl7aWYoMSZuJiYoSW4oKSxPKDAsInJlY3QiLDEzKSksMiZuKXtsZXQgZT1TKCk7emUoIngiLGUuem9vbUJveEluVWlDb29yZGluYXRlLngpKCJ3aWR0aCIsZS56b29tQm94SW5VaUNvb3JkaW5hdGUud2lkdGgpKCJ5IixlLnpvb21Cb3hJblVpQ29vcmRpbmF0ZS55KSgiaGVpZ2h0IixlLnpvb21Cb3hJblVpQ29vcmRpbmF0ZS5oZWlnaHQpfX12YXIgeHFlPWZ1bmN0aW9uKG4sdCxlKXtyZXR1cm57ZGF0YTpuLGN1cnNvckxvY2F0aW9uSW5EYXRhQ29vcmQ6dCxjdXJzb3JMb2NhdGlvbjplfX07ZnVuY3Rpb24gQ3FlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDE0KSxOaSgxLDE1KSx2KCkpLDImbil7bGV0IGU9UygpLGk9JGUoMTEpO0MoMSkseSgibmdUZW1wbGF0ZU91dGxldCIsZS50b29sdGlwVGVtcGxhdGU/ZS50b29sdGlwVGVtcGxhdGU6aSkoIm5nVGVtcGxhdGVPdXRsZXRDb250ZXh0IixaeCgyLHhxZSxlLmN1cnNvcmVkRGF0YSxlLmN1cnNvckxvY2F0aW9uSW5EYXRhQ29vcmQsZS5jdXJzb3JMb2NhdGlvbikpfX1mdW5jdGlvbiBNcWUobix0KXtpZigxJm4mJihzbigwKSxfKDEsInRyIiwxNykoMiwidGQiLDE4KSxPKDMsInNwYW4iKSx2KCksXyg0LCJ0ZCIsMTkpLEEoNSksdigpLF8oNiwidGQiKSxBKDcpLHYoKSxfKDgsInRkIiksQSg5KSx2KCkoKSxhbigpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0O0MoMyksUHQoImJhY2tncm91bmQtY29sb3IiLGUubWV0YWRhdGEuY29sb3IpLEMoMikseXQoZS5tZXRhZGF0YS5kaXNwbGF5TmFtZSksQygyKSx5dChlLmRhdGFQb2ludC55KSxDKDIpLHl0KGUuZGF0YVBvaW50LngpfX1mdW5jdGlvbiB3cWUobix0KXtpZigxJm4mJihfKDAsInRhYmxlIikoMSwidGhlYWQiKSgyLCJ0ciIpLE8oMywidGgiLDE2KSxfKDQsInRoIiksQSg1LCJOYW1lIiksdigpLF8oNiwidGgiKSxBKDcsIlkiKSx2KCksXyg4LCJ0aCIpLEEoOSwiWCIpLHYoKSgpKCksXygxMCwidGJvZHkiKSxFKDExLE1xZSwxMCw1LCJuZy1jb250YWluZXIiLDEwKSx2KCkoKSksMiZuKXtsZXQgZT10LmRhdGEsaT1TKCk7QygxMSkseSgibmdGb3JPZiIsZSkoIm5nRm9yVHJhY2tCeSIsaS50cmFja0J5U2VyaWVzTmFtZSl9fWZ1bmN0aW9uIEVxZShuKXtyZXR1cm4gbi5zY3JvbGxTdHJhdGVnaWVzLnJlcG9zaXRpb24oKX12YXIgYXBlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXt0aGlzLmNoYW5nZURldGVjdG9yPWUsdGhpcy5zY3JvbGxTdHJhdGVneT1pLHRoaXMub25WaWV3RXh0ZW50Q2hhbmdlPW5ldyBHLHRoaXMub25WaWV3RXh0ZW50UmVzZXQ9bmV3IEcsdGhpcy5vbkludGVyYWN0aW9uU3RhdGVDaGFuZ2U9bmV3IEcsdGhpcy5JbnRlcmFjdGlvblN0YXRlPUdyLHRoaXMuc3RhdGU9bmV3IGhyKEdyLk5PTkUpLHRoaXMuc3BlY2lhbEtleVByZXNzZWQ9ITEsdGhpcy56b29tQm94SW5VaUNvb3JkaW5hdGU9e3g6MCx3aWR0aDowLGhlaWdodDowLHk6MH0sdGhpcy50b29sdGlwUG9zaXRpb25zPVt7b2Zmc2V0WTo1LG9yaWdpblg6InN0YXJ0IixvdmVybGF5WDoic3RhcnQiLG9yaWdpblk6ImJvdHRvbSIsb3ZlcmxheVk6InRvcCJ9LHtvZmZzZXRZOjUsb3JpZ2luWDoiZW5kIixvdmVybGF5WDoiZW5kIixvcmlnaW5ZOiJib3R0b20iLG92ZXJsYXlZOiJ0b3AifSx7b2Zmc2V0WTotMTUsb3JpZ2luWDoic3RhcnQiLG92ZXJsYXlYOiJzdGFydCIsb3JpZ2luWToidG9wIixvdmVybGF5WToiYm90dG9tIn0se29mZnNldFk6LTE1LG9yaWdpblg6ImVuZCIsb3ZlcmxheVg6ImVuZCIsb3JpZ2luWToidG9wIixvdmVybGF5WToiYm90dG9tIn0se29mZnNldFg6NSxvcmlnaW5YOiJlbmQiLG92ZXJsYXlYOiJzdGFydCIsb3JpZ2luWToidG9wIixvdmVybGF5WToidG9wIn0se29mZnNldFg6LTUsb3JpZ2luWDoic3RhcnQiLG92ZXJsYXlYOiJlbmQiLG9yaWdpblk6InRvcCIsb3ZlcmxheVk6InRvcCJ9XSx0aGlzLmN1cnNvckxvY2F0aW9uSW5EYXRhQ29vcmQ9bnVsbCx0aGlzLmN1cnNvckxvY2F0aW9uPW51bGwsdGhpcy5jdXJzb3JlZERhdGE9W10sdGhpcy50b29sdGlwRGlzcGxheUF0dGFjaGVkPSExLHRoaXMuc2hvd1pvb21JbnN0cnVjdGlvbj0hMSx0aGlzLmRyYWdTdGFydENvb3JkPW51bGwsdGhpcy5pc0N1cnNvckluc2lkZT0hMSx0aGlzLm5nVW5zdWJzY3JpYmU9bmV3IGtlLHRoaXMuc3Vic2NyaXB0aW9ucz1bXX1uZ0FmdGVyVmlld0luaXQoKXt0aGlzLnN1YnNjcmlwdGlvbnMucHVzaCh0aGlzLnN0YXRlLnN1YnNjcmliZShlPT57dGhpcy5vbkludGVyYWN0aW9uU3RhdGVDaGFuZ2UuZW1pdChlKX0pKSx0aGlzLm5nVW5zdWJzY3JpYmUucGlwZShMKCgpPT57dGhpcy5zdWJzY3JpcHRpb25zLmZvckVhY2goZT0+ZS51bnN1YnNjcmliZSgpKX0pKSxfaSh0aGlzLmRvdHNDb250YWluZXIubmF0aXZlRWxlbWVudCwiZGJsY2xpY2siLHtwYXNzaXZlOiEwfSkucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLm9uVmlld0V4dGVudFJlc2V0LmVtaXQoKSx0aGlzLnN0YXRlLm5leHQoR3IuTk9ORSksdGhpcy5jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKX0pLF9pKHdpbmRvdywia2V5ZG93biIse3Bhc3NpdmU6ITB9KS5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSkpLnN1YnNjcmliZShlPT57bGV0IGk9dGhpcy5zaG91bGRQYW4oZSk7aSE9PXRoaXMuc3BlY2lhbEtleVByZXNzZWQmJih0aGlzLnNwZWNpYWxLZXlQcmVzc2VkPWksdGhpcy5jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKSl9KSxfaSh3aW5kb3csImtleXVwIix7cGFzc2l2ZTohMH0pLnBpcGUoc3QodGhpcy5uZ1Vuc3Vic2NyaWJlKSkuc3Vic2NyaWJlKGU9PntsZXQgaT10aGlzLnNob3VsZFBhbihlKTtpIT09dGhpcy5zcGVjaWFsS2V5UHJlc3NlZCYmKHRoaXMuc3BlY2lhbEtleVByZXNzZWQ9aSx0aGlzLmNoYW5nZURldGVjdG9yLm1hcmtGb3JDaGVjaygpKX0pLF9pKHRoaXMuZG90c0NvbnRhaW5lci5uYXRpdmVFbGVtZW50LCJtb3VzZWRvd24iLHtwYXNzaXZlOiEwfSkucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpKS5zdWJzY3JpYmUoZT0+e2xldCBpPXRoaXMuc3RhdGUuZ2V0VmFsdWUoKSxyPXRoaXMuc2hvdWxkUGFuKGUpP0dyLlBBTk5JTkc6R3IuRFJBR19aT09NSU5HO2k9PT1Hci5OT05FJiZyPT09R3IuRFJBR19aT09NSU5HJiYodGhpcy5kcmFnU3RhcnRDb29yZD17eDplLm9mZnNldFgseTplLm9mZnNldFl9LHRoaXMuem9vbUJveEluVWlDb29yZGluYXRlPXt4OmUub2Zmc2V0WCx3aWR0aDowLHk6ZS5vZmZzZXRZLGhlaWdodDowfSksaSE9PXImJih0aGlzLnN0YXRlLm5leHQociksdGhpcy5jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKSl9KSxfaSh0aGlzLmRvdHNDb250YWluZXIubmF0aXZlRWxlbWVudCwibW91c2V1cCIse3Bhc3NpdmU6ITB9KS5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSkpLnN1YnNjcmliZShlPT57bGV0IGk9KGUuYnV0dG9ucyZDbC5MRUZUKT09PUNsLkxFRlQ7dGhpcy5kcmFnU3RhcnRDb29yZD1udWxsO2xldCByPXRoaXMuem9vbUJveEluVWlDb29yZGluYXRlO2lmKCFpJiZ0aGlzLnN0YXRlLmdldFZhbHVlKCk9PT1Hci5EUkFHX1pPT01JTkcmJnIud2lkdGg+MCYmci5oZWlnaHQ+MCl7bGV0IG89dGhpcy5nZXREYXRhWChyLngpLHM9dGhpcy5nZXREYXRhWChyLngrci53aWR0aCksYT10aGlzLmdldERhdGFZKHIueStyLmhlaWdodCksbD10aGlzLmdldERhdGFZKHIueSk7dGhpcy5vblZpZXdFeHRlbnRDaGFuZ2UuZW1pdCh7ZGF0YUV4dGVudDp7eDpbbyxzXSx5OlthLGxdfX0pfXRoaXMuc3RhdGUuZ2V0VmFsdWUoKSE9PUdyLk5PTkUmJih0aGlzLnN0YXRlLm5leHQoR3IuTk9ORSksdGhpcy5jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKSl9KSxfaSh0aGlzLmRvdHNDb250YWluZXIubmF0aXZlRWxlbWVudCwibW91c2VlbnRlciIse3Bhc3NpdmU6ITB9KS5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSkpLnN1YnNjcmliZShlPT57dGhpcy5pc0N1cnNvckluc2lkZT0hMCx0aGlzLnVwZGF0ZVRvb2x0aXAoZSksdGhpcy5jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKX0pLF9pKHRoaXMuZG90c0NvbnRhaW5lci5uYXRpdmVFbGVtZW50LCJtb3VzZWxlYXZlIix7cGFzc2l2ZTohMH0pLnBpcGUoc3QodGhpcy5uZ1Vuc3Vic2NyaWJlKSkuc3Vic2NyaWJlKGU9Pnt0aGlzLmRyYWdTdGFydENvb3JkPW51bGwsdGhpcy5pc0N1cnNvckluc2lkZT0hMSx0aGlzLnVwZGF0ZVRvb2x0aXAoZSksdGhpcy5zdGF0ZS5uZXh0KEdyLk5PTkUpLHRoaXMuY2hhbmdlRGV0ZWN0b3IubWFya0ZvckNoZWNrKCl9KSxfaSh0aGlzLmRvdHNDb250YWluZXIubmF0aXZlRWxlbWVudCwibW91c2Vtb3ZlIix7cGFzc2l2ZTohMH0pLnBpcGUoc3QodGhpcy5uZ1Vuc3Vic2NyaWJlKSkuc3Vic2NyaWJlKGU9Pntzd2l0Y2godGhpcy5zdGF0ZS5nZXRWYWx1ZSgpKXtjYXNlIEdyLlNDUk9MTF9aT09NSU5HOnRoaXMuc3RhdGUubmV4dChHci5OT05FKSx0aGlzLnVwZGF0ZVRvb2x0aXAoZSksdGhpcy5jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKTticmVhaztjYXNlIEdyLk5PTkU6dGhpcy51cGRhdGVUb29sdGlwKGUpLHRoaXMuY2hhbmdlRGV0ZWN0b3IubWFya0ZvckNoZWNrKCk7YnJlYWs7Y2FzZSBHci5QQU5OSU5HOntsZXQgaT0tZS5tb3ZlbWVudFgscj0tZS5tb3ZlbWVudFkse3dpZHRoOm8saGVpZ2h0OnN9PXRoaXMuZG9tRGltLGE9dGhpcy5nZXREYXRhWChpKSxsPXRoaXMuZ2V0RGF0YVgobytpKSxjPXRoaXMuZ2V0RGF0YVkocytyKSx1PXRoaXMuZ2V0RGF0YVkocik7dGhpcy5vblZpZXdFeHRlbnRDaGFuZ2UuZW1pdCh7ZGF0YUV4dGVudDp7eDpbYSxsXSx5OltjLHVdfX0pO2JyZWFrfWNhc2UgR3IuRFJBR19aT09NSU5HOntpZighdGhpcy5kcmFnU3RhcnRDb29yZClicmVhaztsZXQgaT1bdGhpcy5kcmFnU3RhcnRDb29yZC54LGUub2Zmc2V0WF0scj1bdGhpcy5kcmFnU3RhcnRDb29yZC55LGUub2Zmc2V0WV07dGhpcy56b29tQm94SW5VaUNvb3JkaW5hdGU9e3g6TWF0aC5taW4oLi4uaSksd2lkdGg6TWF0aC5tYXgoLi4uaSktTWF0aC5taW4oLi4uaSkseTpNYXRoLm1pbiguLi5yKSxoZWlnaHQ6TWF0aC5tYXgoLi4uciktTWF0aC5taW4oLi4ucil9fXRoaXMuY2hhbmdlRGV0ZWN0b3IubWFya0ZvckNoZWNrKCl9fSksX2kodGhpcy5kb3RzQ29udGFpbmVyLm5hdGl2ZUVsZW1lbnQsIndoZWVsIix7cGFzc2l2ZTohMX0pLnBpcGUoc3QodGhpcy5uZ1Vuc3Vic2NyaWJlKSx1aShlPT57bGV0IGk9IWUuY3RybEtleSYmIWUuc2hpZnRLZXkmJmUuYWx0S2V5O3JldHVybiB0aGlzLnNob3dab29tSW5zdHJ1Y3Rpb249IWksdGhpcy5jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKSxpPyhlLnByZXZlbnREZWZhdWx0KCksWHQoZSkpOkthKDNlMykucGlwZShrdCgoKT0+e3RoaXMuc2hvd1pvb21JbnN0cnVjdGlvbj0hMSx0aGlzLmNoYW5nZURldGVjdG9yLm1hcmtGb3JDaGVjaygpfSksTCgoKT0+bnVsbCkpfSksWWUoZT0+Qm9vbGVhbihlKSkpLnN1YnNjcmliZShlPT57dGhpcy5vblZpZXdFeHRlbnRDaGFuZ2UuZW1pdCh7ZGF0YUV4dGVudDpzcGUoZSx0aGlzLnZpZXdFeHRlbnQsdGhpcy5kb21EaW0sLjAxLHRoaXMueFNjYWxlLHRoaXMueVNjYWxlKX0pLHRoaXMuc3RhdGUuZ2V0VmFsdWUoKSE9PUdyLlNDUk9MTF9aT09NSU5HJiYodGhpcy5zdGF0ZS5uZXh0KEdyLlNDUk9MTF9aT09NSU5HKSx0aGlzLmNoYW5nZURldGVjdG9yLm1hcmtGb3JDaGVjaygpKX0pfW5nT25DaGFuZ2VzKCl7dGhpcy51cGRhdGVDdXJzb3JlZERhdGFBbmRUb29sdGlwVmlzaWJpbGl0eSgpfW5nT25EZXN0cm95KCl7dGhpcy5uZ1Vuc3Vic2NyaWJlLm5leHQoKSx0aGlzLm5nVW5zdWJzY3JpYmUuY29tcGxldGUoKX1zaG91bGRQYW4oZSl7bGV0IGk9ZS5zaGlmdEtleXx8ZS5hbHRLZXk7aWYoZSBpbnN0YW5jZW9mIEtleWJvYXJkRXZlbnQpcmV0dXJuIGk7bGV0IHI9KGUuYnV0dG9ucyZDbC5MRUZUKT09PUNsLkxFRlQsbz0oZS5idXR0b25zJkNsLk1JRERMRSk9PT1DbC5NSURETEU7cmV0dXJuISghciYmIW8pJiYobyYmIXJ8fGkpfXRyYWNrQnlTZXJpZXNOYW1lKGUsaSl7cmV0dXJuIGkuaWR9Z2V0RG9tWChlKXtyZXR1cm4gdGhpcy54U2NhbGUuZm9yd2FyZCh0aGlzLnZpZXdFeHRlbnQueCxBZCh0aGlzLmRvbURpbSwieCIpLGUpfWdldERhdGFYKGUpe3JldHVybiB0aGlzLnhTY2FsZS5yZXZlcnNlKHRoaXMudmlld0V4dGVudC54LEFkKHRoaXMuZG9tRGltLCJ4IiksZSl9Z2V0RG9tWShlKXtyZXR1cm4gdGhpcy55U2NhbGUuZm9yd2FyZCh0aGlzLnZpZXdFeHRlbnQueSxBZCh0aGlzLmRvbURpbSwieSIpLGUpfWdldERhdGFZKGUpe3JldHVybiB0aGlzLnlTY2FsZS5yZXZlcnNlKHRoaXMudmlld0V4dGVudC55LEFkKHRoaXMuZG9tRGltLCJ5IiksZSl9c2hvdWxkUmVuZGVyVG9vbHRpcFBvaW50KGUpe3JldHVybiBudWxsIT09ZSYmIWlzTmFOKGUueCkmJiFpc05hTihlLnkpfXVwZGF0ZVRvb2x0aXAoZSl7dGhpcy5jdXJzb3JMb2NhdGlvbkluRGF0YUNvb3JkPXt4OnRoaXMuZ2V0RGF0YVgoZS5vZmZzZXRYKSx5OnRoaXMuZ2V0RGF0YVkoZS5vZmZzZXRZKX0sdGhpcy5jdXJzb3JMb2NhdGlvbj17eDplLm9mZnNldFgseTplLm9mZnNldFl9LHRoaXMudXBkYXRlQ3Vyc29yZWREYXRhQW5kVG9vbHRpcFZpc2liaWxpdHkoKX1vblRvb2x0aXBEaXNwbGF5RGV0YWNoZWQoKXt0aGlzLnRvb2x0aXBEaXNwbGF5QXR0YWNoZWQ9ITF9dXBkYXRlQ3Vyc29yZWREYXRhQW5kVG9vbHRpcFZpc2liaWxpdHkoKXtsZXQgZT10aGlzLmN1cnNvckxvY2F0aW9uSW5EYXRhQ29vcmQ7aWYobnVsbD09PWUpcmV0dXJuIHRoaXMuY3Vyc29yZWREYXRhPVtdLHZvaWQodGhpcy50b29sdGlwRGlzcGxheUF0dGFjaGVkPSExKTt0aGlzLmN1cnNvcmVkRGF0YT10aGlzLmlzQ3Vyc29ySW5zaWRlP3RoaXMuc2VyaWVzRGF0YS5tYXAoaT0+KHtzZXJpZXNEYXR1bTppLG1ldGFkYXRhOnRoaXMuc2VyaWVzTWV0YWRhdGFNYXBbaS5pZF19KSkuZmlsdGVyKCh7bWV0YWRhdGE6aX0pPT5pJiZpLnZpc2libGUmJiFCb29sZWFuKGkuYXV4KSkubWFwKCh7c2VyaWVzRGF0dW06aSxtZXRhZGF0YTpyfSk9PntsZXQgbz1iRShpLnBvaW50cyxlLngpLHM9aS5wb2ludHNbb107cmV0dXJue2lkOmkuaWQsY2xvc2VzdFBvaW50SW5kZXg6byxkYXRhUG9pbnQ6cyxkb21Qb2ludDp7eDp0aGlzLmdldERvbVgocy54KSx5OnRoaXMuZ2V0RG9tWShzLnkpfSxtZXRhZGF0YTpyfX0pLmZpbHRlcihpPT5pKTpbXSx0aGlzLnRvb2x0aXBEaXNwbGF5QXR0YWNoZWQ9Qm9vbGVhbih0aGlzLmN1cnNvcmVkRGF0YS5sZW5ndGgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKG5uKSxNKG5nKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibGluZS1jaGFydC1pbnRlcmFjdGl2ZS12aWV3Il1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKG90KGdxZSw3LFJlKSxvdChSaCw1KSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5kb3RzQ29udGFpbmVyPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLm92ZXJsYXk9ci5maXJzdCl9fSxob3N0VmFyczoyLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmZXQoInNob3ctem9vbS1pbnN0cnVjdGlvbiIsaS5zaG93Wm9vbUluc3RydWN0aW9uKX0saW5wdXRzOntzZXJpZXNEYXRhOiJzZXJpZXNEYXRhIixzZXJpZXNNZXRhZGF0YU1hcDoic2VyaWVzTWV0YWRhdGFNYXAiLHZpZXdFeHRlbnQ6InZpZXdFeHRlbnQiLHhTY2FsZToieFNjYWxlIix5U2NhbGU6InlTY2FsZSIsZG9tRGltOiJkb21EaW0iLHRvb2x0aXBPcmlnaW5FbDoidG9vbHRpcE9yaWdpbkVsIix0b29sdGlwVGVtcGxhdGU6InRvb2x0aXBUZW1wbGF0ZSJ9LG91dHB1dHM6e29uVmlld0V4dGVudENoYW5nZToib25WaWV3RXh0ZW50Q2hhbmdlIixvblZpZXdFeHRlbnRSZXNldDoib25WaWV3RXh0ZW50UmVzZXQiLG9uSW50ZXJhY3Rpb25TdGF0ZUNoYW5nZToib25JbnRlcmFjdGlvblN0YXRlQ2hhbmdlIn0sZmVhdHVyZXM6WyR0KFt7cHJvdmlkZTpuZyx1c2VGYWN0b3J5OkVxZSxkZXBzOlt0cl19XSksRnRdLGRlY2xzOjEyLHZhcnM6MTUsY29uc3RzOltbMSwiZG90cyJdLFsiZG90cyIsIiJdLFs0LCJuZ0lmIl0sWyJjbGFzcyIsInpvb20tYm94Iiw0LCJuZ0lmIl0sWzEsInpvb20taW5zdHJ1Y3Rpb24iXSxbMSwiaW5zdHJ1Y3Rpb24tY29udGVudCJdLFsiY2RrT3ZlcmxheU9yaWdpbiIsIiIsMSwidG9vbHRpcC1vcmlnaW4iXSxbInRvb2x0aXBPcmlnaW4iLCJjZGtPdmVybGF5T3JpZ2luIl0sWyJjZGtDb25uZWN0ZWRPdmVybGF5IiwiIiwzLCJjZGtDb25uZWN0ZWRPdmVybGF5T3JpZ2luIiwiY2RrQ29ubmVjdGVkT3ZlcmxheU9wZW4iLCJjZGtDb25uZWN0ZWRPdmVybGF5UG9zaXRpb25zIiwiY2RrQ29ubmVjdGVkT3ZlcmxheVNjcm9sbFN0cmF0ZWd5IiwiY2RrQ29ubmVjdGVkT3ZlcmxheUxvY2tQb3NpdGlvbiIsImNka0Nvbm5lY3RlZE92ZXJsYXlGbGV4aWJsZURpbWVuc2lvbnMiLCJjZGtDb25uZWN0ZWRPdmVybGF5R3Jvd0FmdGVyT3BlbiIsImRldGFjaCJdLFsiZGVmYXVsdFRvb2x0aXAiLCIiXSxbNCwibmdGb3IiLCJuZ0Zvck9mIiwibmdGb3JUcmFja0J5Il0sWyJyIiwiNCIsNCwibmdJZiJdLFsiciIsIjQiXSxbMSwiem9vbS1ib3giXSxbMSwidG9vbHRpcC1jb250YWluZXIiXSxbMywibmdUZW1wbGF0ZU91dGxldCIsIm5nVGVtcGxhdGVPdXRsZXRDb250ZXh0Il0sWzEsImNpcmNsZS1oZWFkZXIiXSxbMSwidG9vbHRpcC1yb3ciXSxbMSwidG9vbHRpcC1yb3ctY2lyY2xlIl0sWzEsIm5hbWUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihJbigpLF8oMCwic3ZnIiwwLDEpLEUoMix5cWUsMiwyLCJuZy1jb250YWluZXIiLDIpLEUoMyxicWUsMSw0LCJyZWN0IiwzKSx2KCksSnMoKSxfKDQsImRpdiIsNCkoNSwic3BhbiIsNSksQSg2LCJBbHQgKyBTY3JvbGwgdG8gWm9vbSIpLHYoKSgpLE8oNywiZGl2Iiw2LDcpLEUoOSxDcWUsMiw2LCJuZy10ZW1wbGF0ZSIsOCksUCgiZGV0YWNoIixmdW5jdGlvbigpe3JldHVybiBpLm9uVG9vbHRpcERpc3BsYXlEZXRhY2hlZCgpfSksRSgxMCx3cWUsMTIsMiwibmctdGVtcGxhdGUiLG51bGwsOSxxdCkpLDImZSYmKGV0KCJwYW5uYWJsZSIsaS5zcGVjaWFsS2V5UHJlc3NlZCkoImRyYWdnYWJsZSIsaS5zdGF0ZS5nZXRWYWx1ZSgpPT09aS5JbnRlcmFjdGlvblN0YXRlLk5PTkV8fGkuc3RhdGUuZ2V0VmFsdWUoKT09PWkuSW50ZXJhY3Rpb25TdGF0ZS5EUkFHX1pPT01JTkcpKCJwYW5uaW5nIixpLnN0YXRlLmdldFZhbHVlKCk9PT1pLkludGVyYWN0aW9uU3RhdGUuUEFOTklORyksQygyKSx5KCJuZ0lmIixpLnN0YXRlLmdldFZhbHVlKCk9PT1pLkludGVyYWN0aW9uU3RhdGUuTk9ORSksQygxKSx5KCJuZ0lmIixpLnN0YXRlLmdldFZhbHVlKCk9PT1pLkludGVyYWN0aW9uU3RhdGUuRFJBR19aT09NSU5HKSxDKDYpLHkoImNka0Nvbm5lY3RlZE92ZXJsYXlPcmlnaW4iLGkudG9vbHRpcE9yaWdpbkVsKSgiY2RrQ29ubmVjdGVkT3ZlcmxheU9wZW4iLGkudG9vbHRpcERpc3BsYXlBdHRhY2hlZCYmaS5zdGF0ZS5nZXRWYWx1ZSgpPT09aS5JbnRlcmFjdGlvblN0YXRlLk5PTkUpKCJjZGtDb25uZWN0ZWRPdmVybGF5UG9zaXRpb25zIixpLnRvb2x0aXBQb3NpdGlvbnMpKCJjZGtDb25uZWN0ZWRPdmVybGF5U2Nyb2xsU3RyYXRlZ3kiLGkuc2Nyb2xsU3RyYXRlZ3kpKCJjZGtDb25uZWN0ZWRPdmVybGF5TG9ja1Bvc2l0aW9uIiwhMSkoImNka0Nvbm5lY3RlZE92ZXJsYXlGbGV4aWJsZURpbWVuc2lvbnMiLCEwKSgiY2RrQ29ubmVjdGVkT3ZlcmxheUdyb3dBZnRlck9wZW4iLCEwKSl9LGRlcGVuZGVuY2llczpbZG4sQmUsb3MsUmgsaWddLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17ZGlzcGxheTpmbGV4O3Bvc2l0aW9uOnJlbGF0aXZlO3VzZXItc2VsZWN0Om5vbmV9LmRvdHNbX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxMDAlO3dpZHRoOjEwMCV9LmRvdHMuZHJhZ2dhYmxlW19uZ2NvbnRlbnQtJUNPTVAlXXtjdXJzb3I6Y3Jvc3NoYWlyfS5kb3RzLnBhbm5hYmxlW19uZ2NvbnRlbnQtJUNPTVAlXXtjdXJzb3I6Z3JhYn0uZG90cy5wYW5uaW5nW19uZ2NvbnRlbnQtJUNPTVAlXXtjdXJzb3I6Z3JhYmJpbmd9LnRvb2x0aXAtcm93LWNpcmNsZVtfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6aW5saW5lLWZsZXg7aGVpZ2h0OjEycHg7d2lkdGg6MTJweH0udG9vbHRpcC1yb3ctY2lyY2xlW19uZ2NvbnRlbnQtJUNPTVAlXSA+IHNwYW5bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1yYWRpdXM6NTAlO2JvcmRlcjoxcHggc29saWQgcmdiYSgyNTUsMjU1LDI1NSwuNik7ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjEwcHg7d2lkdGg6MTBweH0udG9vbHRpcC1vcmlnaW5bX25nY29udGVudC0lQ09NUCVde2JvdHRvbTowO2xlZnQ6MDtwb3NpdGlvbjphYnNvbHV0ZTtyaWdodDowfS50b29sdGlwLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZDpyZ2JhKDAsMCwwLC44NSk7Ym9yZGVyLXJhZGl1czo0cHg7Y29sb3I6I2ZmZjtjb250YWluOnBhaW50IHN0eWxlIGxheW91dDtmb250LXNpemU6LjllbTtvdmVyZmxvdzphdXRvO3BhZGRpbmc6NXB4O3BvaW50ZXItZXZlbnRzOm5vbmU7d2lkdGg6MTAwJX10aFtfbmdjb250ZW50LSVDT01QJV0sIHRkW19uZ2NvbnRlbnQtJUNPTVAlXXtwYWRkaW5nOjJweCA1cHg7dGV4dC1hbGlnbjpsZWZ0fXRoW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXdlaWdodDo1MDA7cGFkZGluZy1ib3R0b206NXB4fS56b29tLWJveFtfbmdjb250ZW50LSVDT01QJV17ZmlsbC1vcGFjaXR5Oi4wMztmaWxsOiMwMDA7c3Ryb2tlOiNjY2N9Lnpvb20taW5zdHJ1Y3Rpb25bX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtkaXNwbGF5OmZsZXg7anVzdGlmeS1jb250ZW50OmNlbnRlcjtsZWZ0OjA7b3BhY2l0eTowO3BvaW50ZXItZXZlbnRzOm5vbmU7cG9zaXRpb246YWJzb2x1dGU7cmlnaHQ6MDt0b3A6MTBweDt0cmFuc2l0aW9uOm9wYWNpdHkgLjVzO3otaW5kZXg6MX0uaW5zdHJ1Y3Rpb24tY29udGVudFtfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZDpyZ2JhKDAsMCwwLC42KTtib3JkZXItcmFkaXVzOjVweDtjb2xvcjojZmZmO3BhZGRpbmc6NXB4IDEwcHg7dXNlci1zZWxlY3Q6bm9uZX0uc2hvdy16b29tLWluc3RydWN0aW9uW19uZ2hvc3QtJUNPTVAlXSAgIC56b29tLWluc3RydWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXtvcGFjaXR5OjF9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiBEcWUobix0KXtpZigxJm4mJihJbigpLE8oMCwibGluZSIsMikpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKCk7ZXQoInplcm8iLDA9PT1lKSx6ZSgieDEiLGkuZ2V0RG9tWChlKSkoIngyIixpLmdldERvbVgoZSkpKCJ5MiIsaS5kb21EaW0uaGVpZ2h0KX19ZnVuY3Rpb24gQXFlKG4sdCl7aWYoMSZuJiYoSW4oKSxPKDAsImxpbmUiLDMpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0LGk9UygpO2V0KCJ6ZXJvIiwwPT09ZSksemUoInkxIixpLmdldERvbVkoZSkpKCJ4MiIsaS5kb21EaW0ud2lkdGgpKCJ5MiIsaS5nZXREb21ZKGUpKX19dmFyIGxwZT0oKCk9PntjbGFzcyBue2dldERvbVgoZSl7cmV0dXJuIHRoaXMueFNjYWxlLmZvcndhcmQodGhpcy52aWV3RXh0ZW50LngsQWQodGhpcy5kb21EaW0sIngiKSxlKX1nZXREb21ZKGUpe3JldHVybiB0aGlzLnlTY2FsZS5mb3J3YXJkKHRoaXMudmlld0V4dGVudC55LEFkKHRoaXMuZG9tRGltLCJ5IiksZSl9Z2V0WFRpY2tzKCl7cmV0dXJuIHRoaXMueFNjYWxlLnRpY2tzKHRoaXMudmlld0V4dGVudC54LHZFKHRoaXMuZG9tRGltLndpZHRoLHRoaXMueEdyaWRDb3VudCkpfWdldFlUaWNrcygpe3JldHVybiB0aGlzLnlTY2FsZS50aWNrcyh0aGlzLnZpZXdFeHRlbnQueSx2RSh0aGlzLmRvbURpbS5oZWlnaHQsdGhpcy55R3JpZENvdW50KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImxpbmUtY2hhcnQtZ3JpZC12aWV3Il1dLGlucHV0czp7dmlld0V4dGVudDoidmlld0V4dGVudCIseFNjYWxlOiJ4U2NhbGUiLHhHcmlkQ291bnQ6InhHcmlkQ291bnQiLHlTY2FsZToieVNjYWxlIix5R3JpZENvdW50OiJ5R3JpZENvdW50Iixkb21EaW06ImRvbURpbSJ9LGRlY2xzOjMsdmFyczoyLGNvbnN0czpbWyJ5MSIsIjAiLDMsInplcm8iLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsieDEiLCIwIiwzLCJ6ZXJvIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbInkxIiwiMCJdLFsieDEiLCIwIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoSW4oKSxfKDAsInN2ZyIpLEUoMSxEcWUsMSw1LCJsaW5lIiwwKSxFKDIsQXFlLDEsNSwibGluZSIsMSksdigpKSwyJmUmJihDKDEpLHkoIm5nRm9yT2YiLGkuZ2V0WFRpY2tzKCkpLEMoMSkseSgibmdGb3JPZiIsaS5nZXRZVGlja3MoKSkpfSxkZXBlbmRlbmNpZXM6W2RuXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVdIHtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgICAgIH1cblxuICAgICAgc3ZnW19uZ2NvbnRlbnQtJUNPTVAlXSB7XG4gICAgICAgIGhlaWdodDogMTAwJTtcbiAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICB9XG5cbiAgICAgIGxpbmVbX25nY29udGVudC0lQ09NUCVdIHtcbiAgICAgICAgc3Ryb2tlOiAjY2NjO1xuICAgICAgICBzdHJva2Utd2lkdGg6IDFweDtcbiAgICAgIH1cblxuICAgICAgLnplcm9bX25nY29udGVudC0lQ09NUCVdIHtcbiAgICAgICAgc3Ryb2tlOiAjYWFhO1xuICAgICAgICBzdHJva2Utd2lkdGg6IDEuNXB4O1xuICAgICAgfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksUHFlPVsic2VyaWVzVmlldyJdLFJxZT1bInhBeGlzIl0sT3FlPVsieUF4aXMiXSxrcWU9WyJjaGFydEVsIl07ZnVuY3Rpb24gRnFlKG4sdCl7aWYoMSZuJiZPKDAsImxpbmUtY2hhcnQtZ3JpZC12aWV3IiwxNiksMiZuKXtsZXQgZT1TKCk7eSgidmlld0V4dGVudCIsZS52aWV3Qm94KSgieFNjYWxlIixlLnhTY2FsZSkoInlTY2FsZSIsZS55U2NhbGUpKCJ4R3JpZENvdW50IixlLlhfR1JJRF9DT1VOVCkoInlHcmlkQ291bnQiLGUuWV9HUklEX0NPVU5UKSgiZG9tRGltIixlLmRvbURpbWVuc2lvbnMubWFpbil9fWZ1bmN0aW9uIE5xZShuLHQpezEmbiYmKEluKCksTygwLCJzdmciLG51bGwsMTcpKX1mdW5jdGlvbiBMcWUobix0KXsxJm4mJk8oMCwiY2FudmFzIixudWxsLDE3KX1mdW5jdGlvbiBCcWUobix0KXtpZigxJm4mJihzbigwKSxFKDEsTnFlLDIsMCwic3ZnIiw1KSxFKDIsTHFlLDIsMCwiY2FudmFzIiw1KSxhbigpKSwyJm4pe2xldCBlPVMoKTtDKDEpLHkoIm5nSWYiLGUuZ2V0UmVuZGVyZXJUeXBlKCk9PT1lLlJlbmRlcmVyVHlwZS5TVkcpLEMoMSkseSgibmdJZiIsZS5nZXRSZW5kZXJlclR5cGUoKT09PWUuUmVuZGVyZXJUeXBlLldFQkdMKX19ZnVuY3Rpb24gVnFlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwibGluZS1jaGFydC1pbnRlcmFjdGl2ZS12aWV3IiwxOCksUCgib25WaWV3RXh0ZW50Q2hhbmdlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uVmlld0JveENoYW5nZWQocikpfSkoIm9uVmlld0V4dGVudFJlc2V0IixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkudmlld0JveFJlc2V0KCkpfSkoIm9uSW50ZXJhY3Rpb25TdGF0ZUNoYW5nZSIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5vbkludGVyYWN0aW9uU3RhdGVDaGFuZ2UocikpfSksdigpfWlmKDImbil7bGV0IGU9UygpLGk9JGUoMSk7eSgic2VyaWVzRGF0YSIsZS5zZXJpZXNEYXRhKSgic2VyaWVzTWV0YWRhdGFNYXAiLGUuc2VyaWVzTWV0YWRhdGFNYXApKCJ2aWV3RXh0ZW50IixlLnZpZXdCb3gpKCJ4U2NhbGUiLGUueFNjYWxlKSgieVNjYWxlIixlLnlTY2FsZSkoInRvb2x0aXBPcmlnaW5FbCIsaSkoImRvbURpbSIsZS5kb21EaW1lbnNpb25zLm1haW4pKCJ0b29sdGlwVGVtcGxhdGUiLGUudG9vbHRpcFRlbXBsYXRlKX19dmFyIEhxZT1mdW5jdGlvbihuLHQsZSxpKXtyZXR1cm57eFNjYWxlOm4seVNjYWxlOnQsZG9tRGltZW5zaW9uOmUsdmlld0V4dGVudDppfX07ZnVuY3Rpb24gVXFlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDE5KSxOaSgxLDIwKSx2KCkpLDImbil7bGV0IGU9UygpO0MoMSkseSgibmdUZW1wbGF0ZU91dGxldCIsZS5jdXN0b21WaXNUZW1wbGF0ZSkoIm5nVGVtcGxhdGVPdXRsZXRDb250ZXh0IixLMygyLEhxZSxlLnhTY2FsZSxlLnlTY2FsZSxlLmRvbURpbWVuc2lvbnMubWFpbixlLnZpZXdCb3gpKX19ZnVuY3Rpb24genFlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwibGluZS1jaGFydC1heGlzIiwyMSksUCgib25WaWV3RXh0ZW50Q2hhbmdlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uVmlld0JveENoYW5nZWRGcm9tQXhpcyhyLCJ5IikpfSksdigpfWlmKDImbil7bGV0IGU9UygpO3koImF4aXNFeHRlbnQiLGUudmlld0JveC55KSgiY3VzdG9tRm9ybWF0dGVyIixlLmN1c3RvbVlGb3JtYXR0ZXIpKCJkb21EaW0iLGUuZG9tRGltZW5zaW9ucy55QXhpcykoImdyaWRDb3VudCIsZS5ZX0dSSURfQ09VTlQpKCJzY2FsZSIsZS55U2NhbGUpfX1mdW5jdGlvbiBqcWUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJsaW5lLWNoYXJ0LWF4aXMiLDIyKSxQKCJvblZpZXdFeHRlbnRDaGFuZ2UiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25WaWV3Qm94Q2hhbmdlZEZyb21BeGlzKHIsIngiKSl9KSx2KCl9aWYoMiZuKXtsZXQgZT1TKCk7eSgiYXhpc0V4dGVudCIsZS52aWV3Qm94LngpKCJjdXN0b21Gb3JtYXR0ZXIiLGUuY3VzdG9tWEZvcm1hdHRlcikoImRvbURpbSIsZS5kb21EaW1lbnNpb25zLnhBeGlzKSgiZ3JpZENvdW50IixlLlhfR1JJRF9DT1VOVCkoInNjYWxlIixlLnhTY2FsZSl9fWZ1bmN0aW9uIEdxZShuLHQpezEmbiYmKF8oMCwiZGl2IiwyMyksTygxLCJzcGFuIiwyNCksdigpKX12YXIgV3FlPWZ1bmN0aW9uKG4sdCxlLGkscil7cmV0dXJue3hTY2FsZTpuLHlTY2FsZTp0LGRvbURpbWVuc2lvbjplLHZpZXdFeHRlbnQ6aSxpbnRlcmFjdGlvblN0YXRlOnJ9fTtmdW5jdGlvbiBxcWUobix0KXtpZigxJm4mJihfKDAsImRpdiIsMjUsMjYpLE5pKDIsMjApLHYoKSksMiZuKXtsZXQgZT1TKCk7QygyKSx5KCJuZ1RlbXBsYXRlT3V0bGV0IixlLmN1c3RvbUNoYXJ0T3ZlcmxheVRlbXBsYXRlKSgibmdUZW1wbGF0ZU91dGxldENvbnRleHQiLFozKDIsV3FlLGUueFNjYWxlLGUueVNjYWxlLGUuZG9tRGltZW5zaW9ucy5tYWluLGUudmlld0JveCxlLmludGVyYWN0aW9uU3RhdGUpKX19dmFyIFlxZT1mdW5jdGlvbihuLHQpe3JldHVybntjb250YWluZXI6ITAsImRhcmstbW9kZSI6biwibGluZS1vbmx5LW1vZGUiOnQsImxpbmUtY2hhcnQiOiEwfX0sUkc9e3g6WzAsMV0seTpbMCwxXX0sams9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLmNoYW5nZURldGVjdG9yPWUsdGhpcy5SZW5kZXJlclR5cGU9ZHIsdGhpcy51c2VEYXJrTW9kZT0hMSx0aGlzLnByZWZlcnJlZFJlbmRlcmVyVHlwZT1kci5XRUJHTCx0aGlzLnhTY2FsZVR5cGU9TnIuTElORUFSLHRoaXMueVNjYWxlVHlwZT1Oci5MSU5FQVIsdGhpcy5saW5lT25seT0hMSx0aGlzLnZpZXdCb3hDaGFuZ2VkPW5ldyBHLHRoaXMub25WaWV3Qm94T3ZlcnJpZGRlbj1uZXcgTGYoMSksdGhpcy5pZ25vcmVZT3V0bGllcnM9ITEsdGhpcy5ZX0dSSURfQ09VTlQ9Nix0aGlzLlhfR1JJRF9DT1VOVD0xMCx0aGlzLnhTY2FsZT1vdSh0aGlzLnhTY2FsZVR5cGUpLHRoaXMueVNjYWxlPW91KHRoaXMueFNjYWxlVHlwZSksdGhpcy52aWV3Qm94PVJHLHRoaXMuZG9tRGltZW5zaW9ucz17bWFpbjp7d2lkdGg6MCxoZWlnaHQ6MH0seEF4aXM6e3dpZHRoOjAsaGVpZ2h0OjB9LHlBeGlzOnt3aWR0aDowLGhlaWdodDowfX0sdGhpcy5zaG93Q2hhcnRSZW5kZXJlckVsZW1lbnQ9ITAsdGhpcy5pbnRlcmFjdGlvblN0YXRlPUdyLk5PTkUsdGhpcy5saW5lQ2hhcnQ9bnVsbCx0aGlzLmlzRGF0YVVwZGF0ZWQ9ITEsdGhpcy5pc01ldGFkYXRhVXBkYXRlZD0hMSx0aGlzLmlzRml4ZWRWaWV3Qm94VXBkYXRlZD0hMSx0aGlzLmlzVmlld0JveE92ZXJyaWRkZW49ITEsdGhpcy51c2VEYXJrTW9kZVVwZGF0ZWQ9ITEsdGhpcy5pc1ZpZXdCb3hDaGFuZ2VkPSEwLHRoaXMuc2NhbGVVcGRhdGVkPSEwLHRoaXMuaXNSZW5kZXJpbmdDb250ZXh0TG9zdD0hMX1uZ09uSW5pdCgpe3RoaXMub25WaWV3Qm94T3ZlcnJpZGRlbi5uZXh0KHRoaXMuaXNWaWV3Qm94T3ZlcnJpZGRlbil9bmdPbkNoYW5nZXMoZSl7ZS54U2NhbGVUeXBlJiYodGhpcy54U2NhbGU9b3UodGhpcy54U2NhbGVUeXBlKSx0aGlzLnNjYWxlVXBkYXRlZD0hMCksZS55U2NhbGVUeXBlJiYodGhpcy55U2NhbGU9b3UodGhpcy55U2NhbGVUeXBlKSx0aGlzLnNjYWxlVXBkYXRlZD0hMCksZS5zZXJpZXNEYXRhJiYodGhpcy5pc0RhdGFVcGRhdGVkPSEwKSxlLmZpeGVkVmlld0JveCYmKHRoaXMuaXNGaXhlZFZpZXdCb3hVcGRhdGVkPSEwKSxlLnNlcmllc01ldGFkYXRhTWFwJiYodGhpcy5pc01ldGFkYXRhVXBkYXRlZD0hMCksZS51c2VEYXJrTW9kZSYmKHRoaXMudXNlRGFya01vZGVVcGRhdGVkPSEwKSx0aGlzLnNjYWxlVXBkYXRlZCYmdGhpcy5zZXRJc1ZpZXdCb3hPdmVycmlkZGVuKCExKSx0aGlzLmlzVmlld0JveENoYW5nZWQ9dGhpcy5pc1ZpZXdCb3hDaGFuZ2VkfHx0aGlzLnNjYWxlVXBkYXRlZHx8IXRoaXMuaXNWaWV3Qm94T3ZlcnJpZGRlbiYmdGhpcy5zaG91bGRVcGRhdGVEZWZhdWx0Vmlld0JveChlKSx0aGlzLnVwZGF0ZUxpbmVDaGFydCgpfW5nQWZ0ZXJWaWV3SW5pdCgpe3RoaXMuaW5pdGlhbGl6ZUNoYXJ0KCksdGhpcy51cGRhdGVMaW5lQ2hhcnQoKSx0aGlzLmNoYW5nZURldGVjdG9yLmRldGVjdENoYW5nZXMoKX1yZWNvdmVyUmVuZGVyZXJJZk5lZWRlZCgpeyF0aGlzLmlzUmVuZGVyaW5nQ29udGV4dExvc3R8fHRoaXMuZGlzYWJsZVVwZGF0ZXx8KHRoaXMuc2hvd0NoYXJ0UmVuZGVyZXJFbGVtZW50PSExLHRoaXMuY2hhbmdlRGV0ZWN0b3IuZGV0ZWN0Q2hhbmdlcygpLHRoaXMuc2hvd0NoYXJ0UmVuZGVyZXJFbGVtZW50PSEwLHRoaXMuY2hhbmdlRGV0ZWN0b3IuZGV0ZWN0Q2hhbmdlcygpLHRoaXMuaW5pdGlhbGl6ZUNoYXJ0KCksdGhpcy5zY2FsZVVwZGF0ZWQ9ITAsdGhpcy5pc01ldGFkYXRhVXBkYXRlZD0hMCx0aGlzLmlzRGF0YVVwZGF0ZWQ9ITAsdGhpcy51c2VEYXJrTW9kZVVwZGF0ZWQ9ITAsdGhpcy5pc0ZpeGVkVmlld0JveFVwZGF0ZWQ9ITAsdGhpcy5pc1ZpZXdCb3hDaGFuZ2VkPSEwLHRoaXMuaXNSZW5kZXJpbmdDb250ZXh0TG9zdD0hMSl9b25WaWV3UmVzaXplKCl7IXRoaXMubGluZUNoYXJ0fHwodGhpcy5yZWFkQW5kVXBkYXRlRG9tRGltZW5zaW9ucygpLHRoaXMubGluZUNoYXJ0LnJlc2l6ZSh0aGlzLmRvbURpbWVuc2lvbnMubWFpbiksdGhpcy5jaGFuZ2VEZXRlY3Rvci5kZXRlY3RDaGFuZ2VzKCkpfXNob3VsZFVwZGF0ZURlZmF1bHRWaWV3Qm94KGUpe2lmKGUueFNjYWxlVHlwZXx8ZS55U2NhbGVUeXBlfHxlLmlnbm9yZVlPdXRsaWVyc3x8ZS5zZXJpZXNEYXRhKXJldHVybiEwO2xldCByPWUuc2VyaWVzTWV0YWRhdGFNYXA7aWYocil7bGV0IG89ci5wcmV2aW91c1ZhbHVlO2lmKE9iamVjdC5rZXlzKHRoaXMuc2VyaWVzTWV0YWRhdGFNYXApLmxlbmd0aCE9PU9iamVjdC5rZXlzKG8/P3t9KS5sZW5ndGgpcmV0dXJuITA7Zm9yKGxldFtzLGFdb2YgT2JqZWN0LmVudHJpZXModGhpcy5zZXJpZXNNZXRhZGF0YU1hcCkpe2xldCBsPW8mJm9bc107aWYoIWx8fGEudmlzaWJsZSE9PWwudmlzaWJsZSlyZXR1cm4hMH19cmV0dXJuITF9b25Db250ZXh0TG9zdCgpe3RoaXMuaXNSZW5kZXJpbmdDb250ZXh0TG9zdD0hMCx0aGlzLmxpbmVDaGFydCYmKHRoaXMubGluZUNoYXJ0LmRpc3Bvc2UoKSx0aGlzLmxpbmVDaGFydD1udWxsKX10cmlnZ2VyQ29udGV4dExvc3RGb3JUZXN0KCl7dGhpcy5vbkNvbnRleHRMb3N0KCl9Z2V0TGluZUNoYXJ0Rm9yVGVzdCgpe3JldHVybiB0aGlzLmxpbmVDaGFydH1pbml0aWFsaXplQ2hhcnQoKXt0aGlzLmxpbmVDaGFydCYmdGhpcy5saW5lQ2hhcnQuZGlzcG9zZSgpO2xldCBlPXRoaXMuZ2V0UmVuZGVyZXJUeXBlKCksaT17b25EcmF3RW5kOigpPT57fSxvbkNvbnRleHRMb3N0OnRoaXMub25Db250ZXh0TG9zdC5iaW5kKHRoaXMpfSxyPW51bGw7c3dpdGNoKHRoaXMucmVhZEFuZFVwZGF0ZURvbURpbWVuc2lvbnMoKSxlKXtjYXNlIGRyLlNWRzpyPXt0eXBlOmRyLlNWRyxjb250YWluZXI6dGhpcy5jaGFydEVsLm5hdGl2ZUVsZW1lbnQsY2FsbGJhY2tzOmksZG9tRGltZW5zaW9uOnRoaXMuZG9tRGltZW5zaW9ucy5tYWluLHVzZURhcmtNb2RlOnRoaXMudXNlRGFya01vZGV9O2JyZWFrO2Nhc2UgZHIuV0VCR0w6cj17dHlwZTpkci5XRUJHTCxjb250YWluZXI6dGhpcy5jaGFydEVsLm5hdGl2ZUVsZW1lbnQsZGV2aWNlUGl4ZWxSYXRpbzp3aW5kb3cuZGV2aWNlUGl4ZWxSYXRpbyxjYWxsYmFja3M6aSxkb21EaW1lbnNpb246dGhpcy5kb21EaW1lbnNpb25zLm1haW4sdXNlRGFya01vZGU6dGhpcy51c2VEYXJrTW9kZX07YnJlYWs7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoYDxsaW5lLWNoYXJ0PiBkb2VzIG5vdCB5ZXQgc3VwcG9ydCByZW5kZXJlclR5cGU6ICR7ZX1gKX1sZXQgcz1lIT09ZHIuU1ZHJiZzdV9pc09mZnNjcmVlbkNhbnZhc1N1cHBvcnRlZCgpP0pnOlVrO3RoaXMubGluZUNoYXJ0PW5ldyBzKHIpfW5nT25EZXN0cm95KCl7dGhpcy5saW5lQ2hhcnQmJnRoaXMubGluZUNoYXJ0LmRpc3Bvc2UoKX1nZXRSZW5kZXJlclR5cGUoKXtyZXR1cm4gZnVuY3Rpb24obil7c3dpdGNoKG4pe2Nhc2UgZHIuU1ZHOnJldHVybiBkci5TVkc7Y2FzZSBkci5XRUJHTDpyZXR1cm4gc3VfaXNXZWJHbDJTdXBwb3J0ZWQoKT9kci5XRUJHTDpkci5TVkc7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gcmVuZGVyZXJUeXBlOiAke259YCl9fSh0aGlzLnByZWZlcnJlZFJlbmRlcmVyVHlwZSl9cmVhZEFuZFVwZGF0ZURvbURpbWVuc2lvbnMoKXt0aGlzLmRvbURpbWVuc2lvbnM9e21haW46e3dpZHRoOnRoaXMuc2VyaWVzVmlldy5uYXRpdmVFbGVtZW50LmNsaWVudFdpZHRoLGhlaWdodDp0aGlzLnNlcmllc1ZpZXcubmF0aXZlRWxlbWVudC5jbGllbnRIZWlnaHR9LHhBeGlzOnt3aWR0aDp0aGlzLnhBeGlzLm5hdGl2ZUVsZW1lbnQuY2xpZW50V2lkdGgsaGVpZ2h0OnRoaXMueEF4aXMubmF0aXZlRWxlbWVudC5jbGllbnRIZWlnaHR9LHlBeGlzOnt3aWR0aDp0aGlzLnlBeGlzLm5hdGl2ZUVsZW1lbnQuY2xpZW50V2lkdGgsaGVpZ2h0OnRoaXMueUF4aXMubmF0aXZlRWxlbWVudC5jbGllbnRIZWlnaHR9fX11cGRhdGVMaW5lQ2hhcnQoKXtpZih0aGlzLnJlY292ZXJSZW5kZXJlcklmTmVlZGVkKCksdGhpcy5saW5lQ2hhcnQmJiF0aGlzLmRpc2FibGVVcGRhdGUpe2lmKHRoaXMuc2NhbGVVcGRhdGVkJiYodGhpcy5zY2FsZVVwZGF0ZWQ9ITEsdGhpcy5saW5lQ2hhcnQuc2V0WFNjYWxlVHlwZSh0aGlzLnhTY2FsZVR5cGUpLHRoaXMubGluZUNoYXJ0LnNldFlTY2FsZVR5cGUodGhpcy55U2NhbGVUeXBlKSksdGhpcy5pc01ldGFkYXRhVXBkYXRlZCYmKHRoaXMuaXNNZXRhZGF0YVVwZGF0ZWQ9ITEsdGhpcy5saW5lQ2hhcnQuc2V0TWV0YWRhdGEodGhpcy5zZXJpZXNNZXRhZGF0YU1hcCkpLHRoaXMuaXNEYXRhVXBkYXRlZCYmKHRoaXMuaXNEYXRhVXBkYXRlZD0hMSx0aGlzLmxpbmVDaGFydC5zZXREYXRhKHRoaXMuc2VyaWVzRGF0YSkpLHRoaXMudXNlRGFya01vZGVVcGRhdGVkJiYodGhpcy51c2VEYXJrTW9kZVVwZGF0ZWQ9ITEsdGhpcy5saW5lQ2hhcnQuc2V0VXNlRGFya01vZGUodGhpcy51c2VEYXJrTW9kZSkpLCF0aGlzLmlzVmlld0JveE92ZXJyaWRkZW4mJnRoaXMuZml4ZWRWaWV3Qm94KXRoaXMudmlld0JveD10aGlzLmZpeGVkVmlld0JveDtlbHNlIGlmKCF0aGlzLmlzVmlld0JveE92ZXJyaWRkZW4mJnRoaXMuaXNWaWV3Qm94Q2hhbmdlZCl7bGV0IGk9ZnVuY3Rpb24obix0LGUsaSxyKXtsZXQgbz1udWxsLHM9bnVsbCxhPVtdO2ZvcihsZXR7aWQ6ZCxwb2ludHM6cH1vZiBuKXtsZXQgaD10W2RdO2lmKGgmJiFoLmF1eCYmaC52aXNpYmxlKWZvcihsZXQgZj0wO2Y8cC5sZW5ndGg7ZisrKXtsZXR7eDptLHk6eH09cFtmXTtpKG0pJiYobz1udWxsPT09b3x8bTxvP206byxzPW51bGw9PT1zfHxtPnM/bTpzKSxyKHgpJiZhLnB1c2goeCl9fWEuc29ydCh1Yyk7bGV0IGM9YVswXSx1PWFbYS5sZW5ndGgtMV07cmV0dXJuIGUmJmEubGVuZ3RoPjImJihjPWFbTWF0aC5jZWlsKC4wNSooYS5sZW5ndGgtMSkpXSx1PWFbTWF0aC5mbG9vciguOTUqKGEubGVuZ3RoLTEpKV0pLHt4Om51bGwhPT1vJiZudWxsIT09cz9bbyxzXTp2b2lkIDAseTp2b2lkIDAhPT1jJiZ2b2lkIDAhPT11P1tjLHVdOnZvaWQgMH19KHRoaXMuc2VyaWVzRGF0YSx0aGlzLnNlcmllc01ldGFkYXRhTWFwLHRoaXMuaWdub3JlWU91dGxpZXJzLHRoaXMueFNjYWxlLmlzU2FmZU51bWJlcix0aGlzLnlTY2FsZS5pc1NhZmVOdW1iZXIpO3RoaXMudmlld0JveD17eDp0aGlzLnhTY2FsZS5uaWNlRG9tYWluKGkueD8/UkcueCkseTp0aGlzLnlTY2FsZS5uaWNlRG9tYWluKGkueT8/UkcueSl9fSh0aGlzLmlzRml4ZWRWaWV3Qm94VXBkYXRlZHx8dGhpcy5pc1ZpZXdCb3hDaGFuZ2VkKSYmKHRoaXMuaXNGaXhlZFZpZXdCb3hVcGRhdGVkPSExLHRoaXMuaXNWaWV3Qm94Q2hhbmdlZD0hMSx0aGlzLmxpbmVDaGFydC5zZXRWaWV3Qm94KHRoaXMudmlld0JveCksdGhpcy5jaGFuZ2VEZXRlY3Rvci5kZXRlY3RDaGFuZ2VzKCkpfX1vblZpZXdCb3hDaGFuZ2VkKHtkYXRhRXh0ZW50OmV9KXt0aGlzLnNldElzVmlld0JveE92ZXJyaWRkZW4oITApLHRoaXMuaXNWaWV3Qm94Q2hhbmdlZD0hMCx0aGlzLnZpZXdCb3g9ZSx0aGlzLnVwZGF0ZUxpbmVDaGFydCgpLHRoaXMudmlld0JveENoYW5nZWQuZW1pdChlKX12aWV3Qm94UmVzZXQoKXt0aGlzLnNldElzVmlld0JveE92ZXJyaWRkZW4oITEpLHRoaXMuaXNWaWV3Qm94Q2hhbmdlZD0hMCx0aGlzLnVwZGF0ZUxpbmVDaGFydCgpLHRoaXMudmlld0JveENoYW5nZWQuZW1pdCh0aGlzLnZpZXdCb3gpfXNldElzVmlld0JveE92ZXJyaWRkZW4oZSl7bGV0IGk9dGhpcy5pc1ZpZXdCb3hPdmVycmlkZGVuO3RoaXMuaXNWaWV3Qm94T3ZlcnJpZGRlbj1lLGkhPT1lJiZ0aGlzLm9uVmlld0JveE92ZXJyaWRkZW4ubmV4dChlKX1vbkludGVyYWN0aW9uU3RhdGVDaGFuZ2UoZSl7dGhpcy5pbnRlcmFjdGlvblN0YXRlPWV9Z2V0SXNWaWV3Qm94T3ZlcnJpZGRlbigpe3JldHVybiB0aGlzLm9uVmlld0JveE92ZXJyaWRkZW59b25WaWV3Qm94Q2hhbmdlZEZyb21BeGlzKGUsaSl7bGV0IHI9ey4uLnRoaXMudmlld0JveCxbaV06ZX07dGhpcy5vblZpZXdCb3hDaGFuZ2VkKHtkYXRhRXh0ZW50OnJ9KX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShubikpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImxpbmUtY2hhcnQiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYob3QoUHFlLDcsUmUpLG90KFJxZSw3LFJlKSxvdChPcWUsNyxSZSksb3Qoa3FlLDUsUmUpKSwyJmUpe2xldCByO05lKHI9TGUoKSkmJihpLnNlcmllc1ZpZXc9ci5maXJzdCksTmUocj1MZSgpKSYmKGkueEF4aXM9ci5maXJzdCksTmUocj1MZSgpKSYmKGkueUF4aXM9ci5maXJzdCksTmUocj1MZSgpKSYmKGkuY2hhcnRFbD1yLmZpcnN0KX19LGlucHV0czp7Y3VzdG9tVmlzVGVtcGxhdGU6ImN1c3RvbVZpc1RlbXBsYXRlIixjdXN0b21DaGFydE92ZXJsYXlUZW1wbGF0ZToiY3VzdG9tQ2hhcnRPdmVybGF5VGVtcGxhdGUiLHVzZURhcmtNb2RlOiJ1c2VEYXJrTW9kZSIscHJlZmVycmVkUmVuZGVyZXJUeXBlOiJwcmVmZXJyZWRSZW5kZXJlclR5cGUiLHNlcmllc0RhdGE6InNlcmllc0RhdGEiLGZpeGVkVmlld0JveDoiZml4ZWRWaWV3Qm94IixzZXJpZXNNZXRhZGF0YU1hcDoic2VyaWVzTWV0YWRhdGFNYXAiLHhTY2FsZVR5cGU6InhTY2FsZVR5cGUiLHlTY2FsZVR5cGU6InlTY2FsZVR5cGUiLGN1c3RvbVhGb3JtYXR0ZXI6ImN1c3RvbVhGb3JtYXR0ZXIiLGN1c3RvbVlGb3JtYXR0ZXI6ImN1c3RvbVlGb3JtYXR0ZXIiLHRvb2x0aXBUZW1wbGF0ZToidG9vbHRpcFRlbXBsYXRlIixsaW5lT25seToibGluZU9ubHkiLGRpc2FibGVVcGRhdGU6ImRpc2FibGVVcGRhdGUiLGlnbm9yZVlPdXRsaWVyczoiaWdub3JlWU91dGxpZXJzIn0sb3V0cHV0czp7dmlld0JveENoYW5nZWQ6InZpZXdCb3hDaGFuZ2VkIn0sZmVhdHVyZXM6W0Z0XSxkZWNsczoxNix2YXJzOjEzLGNvbnN0czpbWyJkZXRlY3RSZXNpemUiLCIiLCJjZGtPdmVybGF5T3JpZ2luIiwiIiwzLCJuZ0NsYXNzIiwicmVzaXplRXZlbnREZWJvdW5jZVBlcmlvZEluTXMiLCJvblJlc2l6ZSJdLFsib3ZlcmxheVRhcmdldCIsImNka092ZXJsYXlPcmlnaW4iXSxbMSwic2VyaWVzLXZpZXciXSxbInNlcmllc1ZpZXciLCIiXSxbMywidmlld0V4dGVudCIsInhTY2FsZSIsInlTY2FsZSIsInhHcmlkQ291bnQiLCJ5R3JpZENvdW50IiwiZG9tRGltIiw0LCJuZ0lmIl0sWzQsIm5nSWYiXSxbMywic2VyaWVzRGF0YSIsInNlcmllc01ldGFkYXRhTWFwIiwidmlld0V4dGVudCIsInhTY2FsZSIsInlTY2FsZSIsInRvb2x0aXBPcmlnaW5FbCIsImRvbURpbSIsInRvb2x0aXBUZW1wbGF0ZSIsIm9uVmlld0V4dGVudENoYW5nZSIsIm9uVmlld0V4dGVudFJlc2V0Iiwib25JbnRlcmFjdGlvblN0YXRlQ2hhbmdlIiw0LCJuZ0lmIl0sWyJjbGFzcyIsImN1c3RvbS12aXMiLDQsIm5nSWYiXSxbMSwieS1heGlzIl0sWyJ5QXhpcyIsIiJdLFsiYXhpcyIsInkiLDMsImF4aXNFeHRlbnQiLCJjdXN0b21Gb3JtYXR0ZXIiLCJkb21EaW0iLCJncmlkQ291bnQiLCJzY2FsZSIsIm9uVmlld0V4dGVudENoYW5nZSIsNCwibmdJZiJdLFsxLCJ4LWF4aXMiXSxbInhBeGlzIiwiIl0sWyJheGlzIiwieCIsMywiYXhpc0V4dGVudCIsImN1c3RvbUZvcm1hdHRlciIsImRvbURpbSIsImdyaWRDb3VudCIsInNjYWxlIiwib25WaWV3RXh0ZW50Q2hhbmdlIiw0LCJuZ0lmIl0sWyJjbGFzcyIsImRvdCIsNCwibmdJZiJdLFsiY2xhc3MiLCJjdXN0b20tdmlzIGN1c3RvbS1jaGFydC1vdmVybGF5LXZpcyIsNCwibmdJZiJdLFszLCJ2aWV3RXh0ZW50IiwieFNjYWxlIiwieVNjYWxlIiwieEdyaWRDb3VudCIsInlHcmlkQ291bnQiLCJkb21EaW0iXSxbImNoYXJ0RWwiLCIiXSxbMywic2VyaWVzRGF0YSIsInNlcmllc01ldGFkYXRhTWFwIiwidmlld0V4dGVudCIsInhTY2FsZSIsInlTY2FsZSIsInRvb2x0aXBPcmlnaW5FbCIsImRvbURpbSIsInRvb2x0aXBUZW1wbGF0ZSIsIm9uVmlld0V4dGVudENoYW5nZSIsIm9uVmlld0V4dGVudFJlc2V0Iiwib25JbnRlcmFjdGlvblN0YXRlQ2hhbmdlIl0sWzEsImN1c3RvbS12aXMiXSxbMywibmdUZW1wbGF0ZU91dGxldCIsIm5nVGVtcGxhdGVPdXRsZXRDb250ZXh0Il0sWyJheGlzIiwieSIsMywiYXhpc0V4dGVudCIsImN1c3RvbUZvcm1hdHRlciIsImRvbURpbSIsImdyaWRDb3VudCIsInNjYWxlIiwib25WaWV3RXh0ZW50Q2hhbmdlIl0sWyJheGlzIiwieCIsMywiYXhpc0V4dGVudCIsImN1c3RvbUZvcm1hdHRlciIsImRvbURpbSIsImdyaWRDb3VudCIsInNjYWxlIiwib25WaWV3RXh0ZW50Q2hhbmdlIl0sWzEsImRvdCJdLFsxLCJyZWN0Il0sWzEsImN1c3RvbS12aXMiLCJjdXN0b20tY2hhcnQtb3ZlcmxheS12aXMiXSxbImN1c3RvbUNoYXJ0T3ZlcmxheSIsIiJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IiwwLDEpLFAoIm9uUmVzaXplIixmdW5jdGlvbigpe3JldHVybiBpLm9uVmlld1Jlc2l6ZSgpfSksXygyLCJkaXYiLDIsMyksRSg0LEZxZSwxLDYsImxpbmUtY2hhcnQtZ3JpZC12aWV3Iiw0KSxFKDUsQnFlLDMsMiwibmctY29udGFpbmVyIiw1KSxFKDYsVnFlLDEsOCwibGluZS1jaGFydC1pbnRlcmFjdGl2ZS12aWV3Iiw2KSxFKDcsVXFlLDIsNywiZGl2Iiw3KSx2KCksXyg4LCJkaXYiLDgsOSksRSgxMCx6cWUsMSw1LCJsaW5lLWNoYXJ0LWF4aXMiLDEwKSx2KCksXygxMSwiZGl2IiwxMSwxMiksRSgxMyxqcWUsMSw1LCJsaW5lLWNoYXJ0LWF4aXMiLDEzKSx2KCksRSgxNCxHcWUsMiwwLCJkaXYiLDE0KSxFKDE1LHFxZSwzLDgsImRpdiIsMTUpLHYoKSksMiZlJiYoeSgibmdDbGFzcyIsUXIoMTAsWXFlLGkudXNlRGFya01vZGUsaS5saW5lT25seSkpKCJyZXNpemVFdmVudERlYm91bmNlUGVyaW9kSW5NcyIsMCksQyg0KSx5KCJuZ0lmIiwhaS5saW5lT25seSksQygxKSx5KCJuZ0lmIixpLnNob3dDaGFydFJlbmRlcmVyRWxlbWVudCksQygxKSx5KCJuZ0lmIiwhaS5saW5lT25seSksQygxKSx5KCJuZ0lmIixpLmN1c3RvbVZpc1RlbXBsYXRlKSxDKDMpLHkoIm5nSWYiLCFpLmxpbmVPbmx5KSxDKDMpLHkoIm5nSWYiLCFpLmxpbmVPbmx5KSxDKDEpLHkoIm5nSWYiLCFpLmxpbmVPbmx5KSxDKDEpLHkoIm5nSWYiLGkuY3VzdG9tQ2hhcnRPdmVybGF5VGVtcGxhdGUpKX0sZGVwZW5kZW5jaWVzOltGbixCZSxvcyxpZyxvcGUsYXBlLGxwZSxoZ10sc3R5bGVzOlsnW19uZ2hvc3QtJUNPTVAlXXtjb250YWluOnN0cmljdDtkaXNwbGF5OmlubGluZS1ibG9ja31bX25naG9zdC0lQ09NUCVdICAgICAubGluZS1jaGFydDpoYXMoLmhvcml6b250YWwtcHJvc3BlY3RpdmUtYXJlYTpob3ZlcikgLngtYXhpcyAuZXh0ZW50LWVkaXQtYnV0dG9ue3Zpc2liaWxpdHk6dmlzaWJsZX1bX25naG9zdC0lQ09NUCVdICAgLmN1c3RvbS12aXNbX25nY29udGVudC0lQ09NUCVde3BvaW50ZXItZXZlbnRzOm5vbmV9LmNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZDppbmhlcml0O2Rpc3BsYXk6Z3JpZDtoZWlnaHQ6MTAwJTtvdmVyZmxvdzpoaWRkZW47d2lkdGg6MTAwJTtncmlkLXRlbXBsYXRlLWFyZWFzOiJ5YXhpcyBzZXJpZXMiICJkb3QgeGF4aXMiICIuIGN1c3RvbUNoYXJ0T3ZlcmxheSI7Z3JpZC10ZW1wbGF0ZS1jb2x1bW5zOjUwcHggMWZyO2dyaWQtYXV0by1yb3dzOjFmciAzMHB4IDBweH0uY29udGFpbmVyLmRhcmstbW9kZVtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6I2ZmZn0uY29udGFpbmVyLmxpbmUtb25seS1tb2RlW19uZ2NvbnRlbnQtJUNPTVAlXXtncmlkLXRlbXBsYXRlLWNvbHVtbnM6MCAxZnI7Z3JpZC1hdXRvLXJvd3M6MWZyIDB9LnNlcmllcy12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXXtncmlkLWFyZWE6c2VyaWVzO3Bvc2l0aW9uOnJlbGF0aXZlO292ZXJmbG93OmhpZGRlbn0uc2VyaWVzLXZpZXdbX25nY29udGVudC0lQ09NUCVdICAgLmN1c3RvbS12aXNbX25nY29udGVudC0lQ09NUCVdLCAuc2VyaWVzLXZpZXdbX25nY29udGVudC0lQ09NUCVdICAgY2FudmFzW19uZ2NvbnRlbnQtJUNPTVAlXSwgLnNlcmllcy12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXSAgIHN2Z1tfbmdjb250ZW50LSVDT01QJV0sIC5zZXJpZXMtdmlld1tfbmdjb250ZW50LSVDT01QJV0gICBsaW5lLWNoYXJ0LWdyaWQtdmlld1tfbmdjb250ZW50LSVDT01QJV0sIC5zZXJpZXMtdmlld1tfbmdjb250ZW50LSVDT01QJV0gICBsaW5lLWNoYXJ0LWludGVyYWN0aXZlLXZpZXdbX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxMDAlO2xlZnQ6MDtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MDt3aWR0aDoxMDAlfS54LWF4aXNbX25nY29udGVudC0lQ09NUCVdICAgLmN1c3RvbS12aXNbX25nY29udGVudC0lQ09NUCVdLCAueS1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5jdXN0b20tdmlzW19uZ2NvbnRlbnQtJUNPTVAlXXtoZWlnaHQ6MTAwJTtsZWZ0OjA7b3ZlcmZsb3c6aGlkZGVuO3Bvc2l0aW9uOmFic29sdXRlO3RvcDowO3dpZHRoOjEwMCU7LXdlYmtpdC1tYXNrLWltYWdlOmxpbmVhci1ncmFkaWVudCh0byByaWdodCwgcmdiYSgwLCAwLCAwLCAwKSAwJSwgIzAwMCAxMCUsICMwMDAgOTAlLCByZ2JhKDAsIDAsIDAsIDApIDEwMCUpO21hc2staW1hZ2U6bGluZWFyLWdyYWRpZW50KHRvIHJpZ2h0LCByZ2JhKDAsIDAsIDAsIDApIDAlLCAjMDAwIDEwJSwgIzAwMCA5MCUsIHJnYmEoMCwgMCwgMCwgMCkgMTAwJSl9LngtYXhpc1tfbmdjb250ZW50LSVDT01QJV0gICBsaW5lLWNoYXJ0LWF4aXNbX25nY29udGVudC0lQ09NUCVdLCAueS1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIGxpbmUtY2hhcnQtYXhpc1tfbmdjb250ZW50LSVDT01QJV17aGVpZ2h0OjEwMCV9LngtYXhpc1tfbmdjb250ZW50LSVDT01QJV17Z3JpZC1hcmVhOnhheGlzO3Bvc2l0aW9uOnJlbGF0aXZlfS55LWF4aXNbX25nY29udGVudC0lQ09NUCVde2dyaWQtYXJlYTp5YXhpc30uZG90W19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpmbGV4LXN0YXJ0O2Rpc3BsYXk6ZmxleDtncmlkLWFyZWE6ZG90O2p1c3RpZnktY29udGVudDpmbGV4LWVuZH0uZG90W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5yZWN0W19uZ2NvbnRlbnQtJUNPTVAlXXtoZWlnaHQ6MXB4O3dpZHRoOjFweDtiYWNrZ3JvdW5kLWNvbG9yOiNhYWF9LmN1c3RvbS1jaGFydC1vdmVybGF5LXZpc1tfbmdjb250ZW50LSVDT01QJV17Z3JpZC1hcmVhOmN1c3RvbUNoYXJ0T3ZlcmxheTtncmlkLXJvdy1lbmQ6MjtncmlkLXJvdy1zdGFydDoxfSddLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gUXFlKG4sdCl7aWYoMSZuJiZPKDAsIm1hdC1pY29uIiwxMCksMiZuKXtsZXQgZT1TKDIpLiRpbXBsaWNpdCxpPVMoKTt5KCJuZ0NsYXNzIixlLnR5cGU9PT1pLnNvcnRpbmdJbmZvLmhlYWRlcj8ic2hvdyI6InNob3ctb24taG92ZXIiKX19ZnVuY3Rpb24gS3FlKG4sdCl7aWYoMSZuJiZPKDAsIm1hdC1pY29uIiwxMSksMiZuKXtsZXQgZT1TKDIpLiRpbXBsaWNpdCxpPVMoKTt5KCJuZ0NsYXNzIixlLnR5cGU9PT1pLnNvcnRpbmdJbmZvLmhlYWRlcj8ic2hvdyI6InNob3ctb24taG92ZXIiKX19ZnVuY3Rpb24gWnFlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwidGgiLDQpLFAoImNsaWNrIixmdW5jdGlvbigpe29lKGUpO2xldCByPVMoKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoKS5oZWFkZXJDbGlja2VkKHIudHlwZSkpfSksXygxLCJkaXYiLDUpLFAoImRyYWdzdGFydCIsZnVuY3Rpb24oKXtvZShlKTtsZXQgcj1TKCkuJGltcGxpY2l0O3JldHVybiBzZShTKCkuZHJhZ1N0YXJ0KHIpKX0pKCJkcmFnZW5kIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKDIpLmRyYWdFbmQoKSl9KSgiZHJhZ2VudGVyIixmdW5jdGlvbigpe29lKGUpO2xldCByPVMoKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoKS5kcmFnRW50ZXIocikpfSksTygyLCJ0Yi1kYXRhLXRhYmxlLWhlYWRlciIsNiksXygzLCJkaXYiLDcpLEUoNCxRcWUsMSwxLCJtYXQtaWNvbiIsOCksRSg1LEtxZSwxLDEsIm1hdC1pY29uIiw5KSx2KCkoKSgpfWlmKDImbil7bGV0IGU9UygpLiRpbXBsaWNpdCxpPVMoKTtDKDEpLHkoImRyYWdnYWJsZSIsaS5jb2x1bW5DdXN0b21pemF0aW9uRW5hYmxlZCkoIm5nQ2xhc3MiLGkuZ2V0SGVhZGVySGlnaGxpZ2h0U3R5bGUoZS50eXBlKSksQygxKSx5KCJoZWFkZXIiLGUpLEMoMikseSgibmdJZiIsaS5zb3J0aW5nSW5mby5vcmRlcj09PWkuU29ydGluZ09yZGVyLkFTQ0VORElOR3x8ZS50eXBlIT09aS5zb3J0aW5nSW5mby5oZWFkZXIpLEMoMSkseSgibmdJZiIsaS5zb3J0aW5nSW5mby5vcmRlcj09PWkuU29ydGluZ09yZGVyLkRFU0NFTkRJTkcmJmUudHlwZT09PWkuc29ydGluZ0luZm8uaGVhZGVyKX19ZnVuY3Rpb24gSnFlKG4sdCl7aWYoMSZuJiYoc24oMCksRSgxLFpxZSw2LDUsInRoIiwzKSxhbigpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0LGk9UygpO0MoMSkseSgibmdJZiIsaS5zaG93Q29sdW1uKGUpKX19ZnVuY3Rpb24gJHFlKG4sdCl7MSZuJiZOaSgwKX12YXIgY3BlPWZ1bmN0aW9uKG4pe3JldHVybnskaW1wbGljaXQ6bn19O2Z1bmN0aW9uIGVZZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwxOCksRSgxLCRxZSwxLDAsIm5nLWNvbnRhaW5lciIsMTkpLEEoMiksdigpKSwyJm4pe2xldCBlPVMoMikuJGltcGxpY2l0LGk9UygpLiRpbXBsaWNpdCxyPVMoKSxvPSRlKDkpO0MoMSkseSgibmdUZW1wbGF0ZU91dGxldCIsbykoIm5nVGVtcGxhdGVPdXRsZXRDb250ZXh0IixPbigzLGNwZSxpLlZBTFVFX0NIQU5HRSkpLEMoMSksamUoIiAiLHIuZ2V0Rm9ybWF0dGVkRGF0YUZvckNvbHVtbihlLnR5cGUsaSksIiAiKX19ZnVuY3Rpb24gdFllKG4sdCl7MSZuJiZOaSgwKX1mdW5jdGlvbiBuWWUobix0KXtpZigxJm4mJihfKDAsImRpdiIsMTgpLEUoMSx0WWUsMSwwLCJuZy1jb250YWluZXIiLDE5KSxBKDIpLHYoKSksMiZuKXtsZXQgZT1TKDIpLiRpbXBsaWNpdCxpPVMoKS4kaW1wbGljaXQscj1TKCksbz0kZSg5KTtDKDEpLHkoIm5nVGVtcGxhdGVPdXRsZXQiLG8pKCJuZ1RlbXBsYXRlT3V0bGV0Q29udGV4dCIsT24oMyxjcGUsaS5QRVJDRU5UQUdFX0NIQU5HRSkpLEMoMSksamUoIiAiLHIuZ2V0Rm9ybWF0dGVkRGF0YUZvckNvbHVtbihlLnR5cGUsaSksIiAiKX19ZnVuY3Rpb24gaVllKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDIwKSxBKDEpLHYoKSksMiZuKXtsZXQgZT1TKDIpLiRpbXBsaWNpdCxpPVMoKS4kaW1wbGljaXQscj1TKCk7QygxKSxqZSgiICIsci5nZXRGb3JtYXR0ZWREYXRhRm9yQ29sdW1uKGUudHlwZSxpKSwiICIpfX1mdW5jdGlvbiByWWUobix0KXtpZigxJm4mJihfKDAsInRkIiwxNSksRSgxLGVZZSwzLDUsImRpdiIsMTYpLEUoMixuWWUsMyw1LCJkaXYiLDE2KSxFKDMsaVllLDIsMSwiZGl2IiwxNyksdigpKSwyJm4pe2xldCBlPVMoKS4kaW1wbGljaXQsaT1TKDIpO3koIm5nU3dpdGNoIixlLnR5cGUpLEMoMSkseSgibmdTd2l0Y2hDYXNlIixpLkNvbHVtbkhlYWRlcnMuVkFMVUVfQ0hBTkdFKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsaS5Db2x1bW5IZWFkZXJzLlBFUkNFTlRBR0VfQ0hBTkdFKX19ZnVuY3Rpb24gb1llKG4sdCl7aWYoMSZuJiYoc24oMCksRSgxLHJZZSw0LDMsInRkIiwxNCksYW4oKSksMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPVMoMik7QygxKSx5KCJuZ0lmIixpLnNob3dDb2x1bW4oZSkpfX1mdW5jdGlvbiBzWWUobix0KXtpZigxJm4mJihzbigwKSxfKDEsInRyIiwxMikoMiwidGQiLDEzKSxPKDMsInNwYW4iKSx2KCksRSg0LG9ZZSwyLDEsIm5nLWNvbnRhaW5lciIsMSksdigpLGFuKCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKCk7QygzKSxQdCgiYmFja2dyb3VuZC1jb2xvciIsZS5DT0xPUiksQygxKSx5KCJuZ0Zvck9mIixpLmhlYWRlcnMpfX1mdW5jdGlvbiBhWWUobix0KXsxJm4mJk8oMCwibWF0LWljb24iLDIzKX1mdW5jdGlvbiBsWWUobix0KXsxJm4mJk8oMCwibWF0LWljb24iLDI0KX1mdW5jdGlvbiBjWWUobix0KXtpZigxJm4mJihFKDAsYVllLDEsMCwibWF0LWljb24iLDIxKSxFKDEsbFllLDEsMCwibWF0LWljb24iLDIyKSksMiZuKXtsZXQgZT10LiRpbXBsaWNpdDt5KCJuZ0lmIixlPj0wKSxDKDEpLHkoIm5nSWYiLGU8MCl9fXZhciBQcD0oKCk9PihmdW5jdGlvbihuKXtuW24uUklHSFQ9MF09IlJJR0hUIixuW24uTEVGVD0xXT0iTEVGVCJ9KFBwfHwoUHA9e30pKSxQcCkpKCksT0c9ZnVuY3Rpb24obil7bi5wcmV2ZW50RGVmYXVsdCgpfSx1cGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuc29ydERhdGFCeT1uZXcgRyx0aGlzLm9yZGVyQ29sdW1ucz1uZXcgRyx0aGlzLkNvbHVtbkhlYWRlcnM9S3QsdGhpcy5Tb3J0aW5nT3JkZXI9eGwsdGhpcy5TaWRlPVBwLHRoaXMuaGlnaGxpZ2h0U2lkZT1QcC5SSUdIVH1uZ09uRGVzdHJveSgpe2RvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoImRyYWdvdmVyIixPRyl9Z2V0Rm9ybWF0dGVkRGF0YUZvckNvbHVtbihlLGkpe3N3aXRjaChlKXtjYXNlIEt0LlJVTjpyZXR1cm4gdm9pZCAwPT09aS5SVU4/IiI6aS5SVU47Y2FzZSBLdC5WQUxVRTpyZXR1cm4gdm9pZCAwPT09aS5WQUxVRT8iIjp5cC5mb3JtYXRTaG9ydChpLlZBTFVFKTtjYXNlIEt0LlNURVA6cmV0dXJuIHZvaWQgMD09PWkuU1RFUD8iIjpNZC5mb3JtYXRTaG9ydChpLlNURVApO2Nhc2UgS3QuVElNRTpyZXR1cm4gdm9pZCAwPT09aS5USU1FPyIiOm5ldyBEYXRlKGkuVElNRSkudG9JU09TdHJpbmcoKTtjYXNlIEt0LlJFTEFUSVZFX1RJTUU6cmV0dXJuIHZvaWQgMD09PWkuUkVMQVRJVkVfVElNRT8iIjpTUy5mb3JtYXRSZWFkYWJsZShpLlJFTEFUSVZFX1RJTUUpO2Nhc2UgS3QuU01PT1RIRUQ6cmV0dXJuIHZvaWQgMD09PWkuU01PT1RIRUQ/IiI6eXAuZm9ybWF0U2hvcnQoaS5TTU9PVEhFRCk7Y2FzZSBLdC5WQUxVRV9DSEFOR0U6cmV0dXJuIHZvaWQgMD09PWkuVkFMVUVfQ0hBTkdFPyIiOnlwLmZvcm1hdFNob3J0KE1hdGguYWJzKGkuVkFMVUVfQ0hBTkdFKSk7Y2FzZSBLdC5TVEFSVF9TVEVQOnJldHVybiB2b2lkIDA9PT1pLlNUQVJUX1NURVA/IiI6TWQuZm9ybWF0U2hvcnQoaS5TVEFSVF9TVEVQKTtjYXNlIEt0LkVORF9TVEVQOnJldHVybiB2b2lkIDA9PT1pLkVORF9TVEVQPyIiOk1kLmZvcm1hdFNob3J0KGkuRU5EX1NURVApO2Nhc2UgS3QuU1RBUlRfVkFMVUU6cmV0dXJuIHZvaWQgMD09PWkuU1RBUlRfVkFMVUU/IiI6TWQuZm9ybWF0U2hvcnQoaS5TVEFSVF9WQUxVRSk7Y2FzZSBLdC5FTkRfVkFMVUU6cmV0dXJuIHZvaWQgMD09PWkuRU5EX1ZBTFVFPyIiOk1kLmZvcm1hdFNob3J0KGkuRU5EX1ZBTFVFKTtjYXNlIEt0Lk1JTl9WQUxVRTpyZXR1cm4gdm9pZCAwPT09aS5NSU5fVkFMVUU/IiI6TWQuZm9ybWF0U2hvcnQoaS5NSU5fVkFMVUUpO2Nhc2UgS3QuTUFYX1ZBTFVFOnJldHVybiB2b2lkIDA9PT1pLk1BWF9WQUxVRT8iIjpNZC5mb3JtYXRTaG9ydChpLk1BWF9WQUxVRSk7Y2FzZSBLdC5QRVJDRU5UQUdFX0NIQU5HRTpyZXR1cm4gdm9pZCAwPT09aS5QRVJDRU5UQUdFX0NIQU5HRT8iIjpNYXRoLnJvdW5kKDEwMCppLlBFUkNFTlRBR0VfQ0hBTkdFKS50b1N0cmluZygpKyIlIjtkZWZhdWx0OnJldHVybiIifX1oZWFkZXJDbGlja2VkKGUpe3RoaXMuc29ydERhdGFCeS5lbWl0KHRoaXMuc29ydGluZ0luZm8uaGVhZGVyIT09ZXx8dGhpcy5zb3J0aW5nSW5mby5vcmRlciE9PXhsLkFTQ0VORElORz97aGVhZGVyOmUsb3JkZXI6eGwuQVNDRU5ESU5HfTp7aGVhZGVyOmUsb3JkZXI6eGwuREVTQ0VORElOR30pfWRyYWdTdGFydChlKXt0aGlzLmRyYWdnaW5nSGVhZGVyVHlwZT1lLnR5cGUsZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigiZHJhZ292ZXIiLE9HKX1kcmFnRW5kKCl7IXRoaXMuZHJhZ2dpbmdIZWFkZXJUeXBlfHwhdGhpcy5oaWdobGlnaHRlZENvbHVtblR5cGV8fCh0aGlzLm9yZGVyQ29sdW1ucy5lbWl0KHRoaXMubW92ZUhlYWRlcih0aGlzLmdldEluZGV4T2ZIZWFkZXJXaXRoVHlwZSh0aGlzLmRyYWdnaW5nSGVhZGVyVHlwZSksdGhpcy5nZXRJbmRleE9mSGVhZGVyV2l0aFR5cGUodGhpcy5oaWdobGlnaHRlZENvbHVtblR5cGUpKSksdGhpcy5kcmFnZ2luZ0hlYWRlclR5cGU9dm9pZCAwLHRoaXMuaGlnaGxpZ2h0ZWRDb2x1bW5UeXBlPXZvaWQgMCxkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJkcmFnb3ZlciIsT0cpKX1kcmFnRW50ZXIoZSl7IXRoaXMuZHJhZ2dpbmdIZWFkZXJUeXBlfHwodGhpcy5oaWdobGlnaHRTaWRlPXRoaXMuZ2V0SW5kZXhPZkhlYWRlcldpdGhUeXBlKGUudHlwZSk8dGhpcy5nZXRJbmRleE9mSGVhZGVyV2l0aFR5cGUodGhpcy5kcmFnZ2luZ0hlYWRlclR5cGUpP1BwLkxFRlQ6UHAuUklHSFQsdGhpcy5oaWdobGlnaHRlZENvbHVtblR5cGU9ZS50eXBlKX1tb3ZlSGVhZGVyKGUsaSl7bGV0IHI9Wy4uLnRoaXMuaGVhZGVyc107cmV0dXJuIHIuc3BsaWNlKGUsMSksci5zcGxpY2UoaSwwLHRoaXMuaGVhZGVyc1tlXSkscn1nZXRIZWFkZXJIaWdobGlnaHRTdHlsZShlKXtyZXR1cm4gZSE9PXRoaXMuaGlnaGxpZ2h0ZWRDb2x1bW5UeXBlP3t9OntoaWdobGlnaHQ6ITAsImhpZ2hsaWdodC1ib3JkZXItcmlnaHQiOnRoaXMuaGlnaGxpZ2h0U2lkZT09PVBwLlJJR0hULCJoaWdobGlnaHQtYm9yZGVyLWxlZnQiOnRoaXMuaGlnaGxpZ2h0U2lkZT09PVBwLkxFRlR9fXNob3dDb2x1bW4oZSl7cmV0dXJuIGUuZW5hYmxlZCYmKHRoaXMuc21vb3RoaW5nRW5hYmxlZHx8ZS50eXBlIT09S3QuU01PT1RIRUQpfWdldEluZGV4T2ZIZWFkZXJXaXRoVHlwZShlKXtyZXR1cm4gdGhpcy5oZWFkZXJzLmZpbmRJbmRleChpPT5lPT09aS50eXBlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGItZGF0YS10YWJsZSJdXSxpbnB1dHM6e2hlYWRlcnM6ImhlYWRlcnMiLGRhdGE6ImRhdGEiLHNvcnRpbmdJbmZvOiJzb3J0aW5nSW5mbyIsY29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQ6ImNvbHVtbkN1c3RvbWl6YXRpb25FbmFibGVkIixzbW9vdGhpbmdFbmFibGVkOiJzbW9vdGhpbmdFbmFibGVkIn0sb3V0cHV0czp7c29ydERhdGFCeToic29ydERhdGFCeSIsb3JkZXJDb2x1bW5zOiJvcmRlckNvbHVtbnMifSxkZWNsczoxMCx2YXJzOjIsY29uc3RzOltbMSwiZGF0YS10YWJsZSJdLFs0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbImFycm93IiwiIl0sWzMsImNsaWNrIiw0LCJuZ0lmIl0sWzMsImNsaWNrIl0sWzEsImNlbGwiLDMsImRyYWdnYWJsZSIsIm5nQ2xhc3MiLCJkcmFnc3RhcnQiLCJkcmFnZW5kIiwiZHJhZ2VudGVyIl0sWzMsImhlYWRlciJdLFsxLCJzb3J0aW5nLWljb24tY29udGFpbmVyIl0sWyJzdmdJY29uIiwiYXJyb3dfdXB3YXJkXzI0cHgiLDMsIm5nQ2xhc3MiLDQsIm5nSWYiXSxbInN2Z0ljb24iLCJhcnJvd19kb3dud2FyZF8yNHB4IiwzLCJuZ0NsYXNzIiw0LCJuZ0lmIl0sWyJzdmdJY29uIiwiYXJyb3dfdXB3YXJkXzI0cHgiLDMsIm5nQ2xhc3MiXSxbInN2Z0ljb24iLCJhcnJvd19kb3dud2FyZF8yNHB4IiwzLCJuZ0NsYXNzIl0sWzEsInJvdyJdLFsxLCJyb3ctY2lyY2xlIl0sWzMsIm5nU3dpdGNoIiw0LCJuZ0lmIl0sWzMsIm5nU3dpdGNoIl0sWyJjbGFzcyIsImNlbGwiLDQsIm5nU3dpdGNoQ2FzZSJdLFsiY2xhc3MiLCJjZWxsIGV4dHJhLXJpZ2h0LXBhZGRpbmciLDQsIm5nU3dpdGNoRGVmYXVsdCJdLFsxLCJjZWxsIl0sWzQsIm5nVGVtcGxhdGVPdXRsZXQiLCJuZ1RlbXBsYXRlT3V0bGV0Q29udGV4dCJdLFsxLCJjZWxsIiwiZXh0cmEtcmlnaHQtcGFkZGluZyJdLFsic3ZnSWNvbiIsImFycm93X3Vwd2FyZF8yNHB4Iiw0LCJuZ0lmIl0sWyJzdmdJY29uIiwiYXJyb3dfZG93bndhcmRfMjRweCIsNCwibmdJZiJdLFsic3ZnSWNvbiIsImFycm93X3Vwd2FyZF8yNHB4Il0sWyJzdmdJY29uIiwiYXJyb3dfZG93bndhcmRfMjRweCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IikoMSwidGFibGUiLDApKDIsInRoZWFkIikoMywidHIiKSxPKDQsInRoIiksRSg1LEpxZSwyLDEsIm5nLWNvbnRhaW5lciIsMSksdigpKCksXyg2LCJ0Ym9keSIpLEUoNyxzWWUsNSwzLCJuZy1jb250YWluZXIiLDEpLHYoKSgpKCksRSg4LGNZZSwyLDIsIm5nLXRlbXBsYXRlIixudWxsLDIscXQpKSwyJmUmJihDKDUpLHkoIm5nRm9yT2YiLGkuaGVhZGVycyksQygyKSx5KCJuZ0Zvck9mIixpLmRhdGEpKX0sZGVwZW5kZW5jaWVzOltGbixkbixCZSxvcyxDcixVcixjaCxHdCx2Ul0sc3R5bGVzOlsiLmRhdGEtdGFibGVbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1zcGFjaW5nOjRweDtmb250LXNpemU6MTNweH0uZGF0YS10YWJsZVtfbmdjb250ZW50LSVDT01QJV0gICB0aFtfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZmZmO3Bvc2l0aW9uOnN0aWNreTt0ZXh0LWFsaWduOmxlZnQ7dG9wOjA7dmVydGljYWwtYWxpZ246Ym90dG9tfS5kYXRhLXRhYmxlW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHRoW19uZ2NvbnRlbnQtJUNPTVAlXTpob3ZlcntjdXJzb3I6cG9pbnRlcn1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuZGF0YS10YWJsZVtfbmdjb250ZW50LSVDT01QJV0gICB0aFtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5kYXRhLXRhYmxlW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHRoW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiMzMDMwMzB9LmRhdGEtdGFibGVbX25nY29udGVudC0lQ09NUCVdICAgLmNlbGxbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtkaXNwbGF5OmZsZXh9LmRhdGEtdGFibGVbX25nY29udGVudC0lQ09NUCVdICAgLmV4dHJhLXJpZ2h0LXBhZGRpbmdbX25nY29udGVudC0lQ09NUCVde3BhZGRpbmctcmlnaHQ6MXB4fS5kYXRhLXRhYmxlW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5yb3dbX25nY29udGVudC0lQ09NUCVde3doaXRlLXNwYWNlOm5vd3JhcH0uZGF0YS10YWJsZVtfbmdjb250ZW50LSVDT01QJV0gICAucm93LWNpcmNsZVtfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6aW5saW5lLWZsZXg7aGVpZ2h0OjEycHg7d2lkdGg6MTJweH0uZGF0YS10YWJsZVtfbmdjb250ZW50LSVDT01QJV0gICAucm93LWNpcmNsZVtfbmdjb250ZW50LSVDT01QJV0gPiBzcGFuW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItcmFkaXVzOjUwJTtib3JkZXI6MXB4IHNvbGlkIHJnYmEoMjU1LDI1NSwyNTUsLjQpO2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxMHB4O3dpZHRoOjEwcHh9LmRhdGEtdGFibGVbX25nY29udGVudC0lQ09NUCVdICAgLmNlbGxbX25nY29udGVudC0lQ09NUCVdICAgbWF0LWljb25bX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxMnB4O3dpZHRoOjEycHh9LmRhdGEtdGFibGVbX25nY29udGVudC0lQ09NUCVdICAgLnNvcnRpbmctaWNvbi1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde3dpZHRoOjEycHg7aGVpZ2h0OjEycHg7Ym9yZGVyLXJhZGl1czo1cHh9LmRhdGEtdGFibGVbX25nY29udGVudC0lQ09NUCVdICAgLnNob3dbX25nY29udGVudC0lQ09NUCVde29wYWNpdHk6MX0uZGF0YS10YWJsZVtfbmdjb250ZW50LSVDT01QJV0gICAuc2hvdy1vbi1ob3Zlcltfbmdjb250ZW50LSVDT01QJV17b3BhY2l0eTowfS5kYXRhLXRhYmxlW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHRoW19uZ2NvbnRlbnQtJUNPTVAlXTpob3ZlciAgIC5zaG93LW9uLWhvdmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtvcGFjaXR5Oi4zfS5kYXRhLXRhYmxlW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5oaWdobGlnaHRbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2VlZX0uZGF0YS10YWJsZVtfbmdjb250ZW50LSVDT01QJV0gICAuaGlnaGxpZ2h0LWJvcmRlci1yaWdodFtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLXJpZ2h0OjJweCBzb2xpZCAjZmY5ODAwfS5kYXRhLXRhYmxlW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5oaWdobGlnaHQtYm9yZGVyLWxlZnRbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjJweCBzb2xpZCAjZmY5ODAwfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCkscHBlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLnNvcnREYXRhQnk9bmV3IEcsdGhpcy5vcmRlckNvbHVtbnM9bmV3IEd9Z2V0TWluVmFsdWVJblJhbmdlKGUsaSxyLG89ITEpe2xldCBzPXRoaXMubWF5YmVTbW9vdGhlZFZhbHVlKGVbaV0sbyk7Zm9yKGxldCBhPWk7YTw9cjthKyspcz50aGlzLm1heWJlU21vb3RoZWRWYWx1ZShlW2FdLG8pJiYocz10aGlzLm1heWJlU21vb3RoZWRWYWx1ZShlW2FdLG8pKTtyZXR1cm4gc31nZXRNYXhWYWx1ZUluUmFuZ2UoZSxpLHIsbz0hMSl7bGV0IHM9dGhpcy5tYXliZVNtb290aGVkVmFsdWUoZVtpXSxvKTtmb3IobGV0IGE9aTthPD1yO2ErKylzPHRoaXMubWF5YmVTbW9vdGhlZFZhbHVlKGVbYV0sbykmJihzPXRoaXMubWF5YmVTbW9vdGhlZFZhbHVlKGVbYV0sbykpO3JldHVybiBzfW1heWJlU21vb3RoZWRWYWx1ZShlLGkpe3JldHVybiBpP2UueTplLnZhbHVlfWdldFRpbWVTZWxlY3Rpb25UYWJsZURhdGEoKXtpZihudWxsPT09dGhpcy5zdGVwT3JMaW5rZWRUaW1lU2VsZWN0aW9uKXJldHVybltdO2xldCBlPXRoaXMuc3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbi5zdGFydC5zdGVwLGk9dGhpcy5zdGVwT3JMaW5rZWRUaW1lU2VsZWN0aW9uLmVuZD8uc3RlcCxyPXRoaXMuZGF0YVNlcmllcy5maWx0ZXIobz0+e2xldCBzPXRoaXMuY2hhcnRNZXRhZGF0YU1hcFtvLmlkXTtyZXR1cm4gcyYmcy52aXNpYmxlJiYhQm9vbGVhbihzLmF1eCl9KS5tYXAobz0+e2xldCBzPXRoaXMuY2hhcnRNZXRhZGF0YU1hcFtvLmlkXSxhPWJFKG8ucG9pbnRzLGUpLGw9by5wb2ludHNbYV0sYz1udWxsLHU9bnVsbDtudWxsIT1pJiYodT1iRShvLnBvaW50cyxpKSxjPW8ucG9pbnRzW3VdKTtsZXQgZD17aWQ6by5pZH07ZC5DT0xPUj1zLmNvbG9yO2ZvcihsZXQgcCBvZiB0aGlzLmNvbHVtbkhlYWRlcnMpc3dpdGNoKHAudHlwZSl7Y2FzZSBLdC5SVU46bGV0IGg9IiI7cy5hbGlhcyYmKGg9YCR7cy5hbGlhcy5hbGlhc051bWJlcn0gJHtzLmFsaWFzLmFsaWFzVGV4dH0vYCksZC5SVU49YCR7aH0ke3MuZGlzcGxheU5hbWV9YDtjb250aW51ZTtjYXNlIEt0LlNURVA6ZC5TVEVQPWwuc3RlcDtjb250aW51ZTtjYXNlIEt0LlZBTFVFOmQuVkFMVUU9bC52YWx1ZTtjb250aW51ZTtjYXNlIEt0LlJFTEFUSVZFX1RJTUU6ZC5SRUxBVElWRV9USU1FPWwucmVsYXRpdmVUaW1lSW5Ncztjb250aW51ZTtjYXNlIEt0LlNNT09USEVEOmQuU01PT1RIRUQ9bC55O2NvbnRpbnVlO2Nhc2UgS3QuVkFMVUVfQ0hBTkdFOmlmKCFjKWNvbnRpbnVlO2QuVkFMVUVfQ0hBTkdFPWMueS1sLnk7Y29udGludWU7Y2FzZSBLdC5TVEFSVF9TVEVQOmQuU1RBUlRfU1RFUD1sLnN0ZXA7Y29udGludWU7Y2FzZSBLdC5FTkRfU1RFUDppZighYyljb250aW51ZTtkLkVORF9TVEVQPWMuc3RlcDtjb250aW51ZTtjYXNlIEt0LlNUQVJUX1ZBTFVFOmQuU1RBUlRfVkFMVUU9bC55O2NvbnRpbnVlO2Nhc2UgS3QuRU5EX1ZBTFVFOmlmKCFjKWNvbnRpbnVlO2QuRU5EX1ZBTFVFPWMueTtjb250aW51ZTtjYXNlIEt0Lk1JTl9WQUxVRTppZighdSljb250aW51ZTtkLk1JTl9WQUxVRT10aGlzLmdldE1pblZhbHVlSW5SYW5nZShvLnBvaW50cyxhLHUsITApO2NvbnRpbnVlO2Nhc2UgS3QuTUFYX1ZBTFVFOmlmKCF1KWNvbnRpbnVlO2QuTUFYX1ZBTFVFPXRoaXMuZ2V0TWF4VmFsdWVJblJhbmdlKG8ucG9pbnRzLGEsdSwhMCk7Y29udGludWU7Y2FzZSBLdC5QRVJDRU5UQUdFX0NIQU5HRTppZighYyljb250aW51ZTtkLlBFUkNFTlRBR0VfQ0hBTkdFPShjLnktbC55KS9sLnk7Y29udGludWU7ZGVmYXVsdDpjb250aW51ZX1yZXR1cm4gZH0pO3JldHVybiByLnNvcnQoKG8scyk9PntsZXQgYT10aGlzLmdldFNvcnRhYmxlVmFsdWUobyx0aGlzLnNvcnRpbmdJbmZvLmhlYWRlciksbD10aGlzLmdldFNvcnRhYmxlVmFsdWUocyx0aGlzLnNvcnRpbmdJbmZvLmhlYWRlcik7cmV0dXJuIGE8bD90aGlzLnNvcnRpbmdJbmZvLm9yZGVyPT09eGwuQVNDRU5ESU5HPy0xOjE6YT5sP3RoaXMuc29ydGluZ0luZm8ub3JkZXI9PT14bC5BU0NFTkRJTkc/MTotMTowfSkscn1nZXRTb3J0YWJsZVZhbHVlKGUsaSl7cmV0dXJuIGZ1bmN0aW9uKG4pe3JldHVybiBOdW1iZXIuaXNOYU4obil8fCJOYU4iPT09bnx8bnVsbD09bj8tMS8wOm59KGk9PT1LdC5SVU4/dGhpcy5jaGFydE1ldGFkYXRhTWFwW2UuaWRdLmRpc3BsYXlOYW1lOmVbaV0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJzY2FsYXItY2FyZC1kYXRhLXRhYmxlIl1dLGlucHV0czp7Y2hhcnRNZXRhZGF0YU1hcDoiY2hhcnRNZXRhZGF0YU1hcCIsZGF0YVNlcmllczoiZGF0YVNlcmllcyIsc3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbjoic3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbiIsY29sdW1uSGVhZGVyczoiY29sdW1uSGVhZGVycyIsc29ydGluZ0luZm86InNvcnRpbmdJbmZvIixjb2x1bW5DdXN0b21pemF0aW9uRW5hYmxlZDoiY29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQiLHNtb290aGluZ0VuYWJsZWQ6InNtb290aGluZ0VuYWJsZWQifSxvdXRwdXRzOntzb3J0RGF0YUJ5OiJzb3J0RGF0YUJ5IixvcmRlckNvbHVtbnM6Im9yZGVyQ29sdW1ucyJ9LGRlY2xzOjEsdmFyczo1LGNvbnN0czpbWzMsImhlYWRlcnMiLCJkYXRhIiwic29ydGluZ0luZm8iLCJjb2x1bW5DdXN0b21pemF0aW9uRW5hYmxlZCIsInNtb290aGluZ0VuYWJsZWQiLCJzb3J0RGF0YUJ5Iiwib3JkZXJDb2x1bW5zIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJ0Yi1kYXRhLXRhYmxlIiwwKSxQKCJzb3J0RGF0YUJ5IixmdW5jdGlvbihvKXtyZXR1cm4gaS5zb3J0RGF0YUJ5LmVtaXQobyl9KSgib3JkZXJDb2x1bW5zIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vcmRlckNvbHVtbnMuZW1pdChvKX0pLHYoKSksMiZlJiZ5KCJoZWFkZXJzIixpLmNvbHVtbkhlYWRlcnMpKCJkYXRhIixpLmdldFRpbWVTZWxlY3Rpb25UYWJsZURhdGEoKSkoInNvcnRpbmdJbmZvIixpLnNvcnRpbmdJbmZvKSgiY29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQiLGkuY29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQpKCJzbW9vdGhpbmdFbmFibGVkIixpLnNtb290aGluZ0VuYWJsZWQpfSxkZXBlbmRlbmNpZXM6W3VwZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7dmFyIHBZZT1bInN0ZXBTcGFuIl07ZnVuY3Rpb24gaFllKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiYnV0dG9uIiw0KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLmZvYlJlbW92ZWQuZW1pdCgpKX0pLE8oMSwibWF0LWljb24iLDUpLHYoKX19ZnVuY3Rpb24gZlllKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiYnV0dG9uIiw0KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLmZvYlJlbW92ZWQuZW1pdCgpKX0pLE8oMSwibWF0LWljb24iLDYpLHYoKX19dmFyIG1ZZT1mdW5jdGlvbihuLHQpe3JldHVybntmb2I6ITAsdW5yZW1vdmFibGU6bixwcm9zcGVjdGl2ZTp0fX0saHBlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLmFsbG93UmVtb3ZhbD0hMCx0aGlzLmlzUHJvc3BlY3RpdmU9ITEsdGhpcy5zdGVwQ2hhbmdlZD1uZXcgRyx0aGlzLmZvYlJlbW92ZWQ9bmV3IEd9bmdPbkNoYW5nZXMoZSl7ZS5zdGVwJiZkb2N1bWVudC5hY3RpdmVFbGVtZW50PT09dGhpcy5zdGVwU3Bhbi5uYXRpdmVFbGVtZW50JiZ0aGlzLnN0ZXBTcGFuLm5hdGl2ZUVsZW1lbnQuYmx1cigpfXZhbGlkYXRlU3RlcChlKXtsZXQgaT1TdHJpbmcuZnJvbUNoYXJDb2RlKGUud2hpY2gpOygiICI9PT1lLmtleXx8aXNOYU4oTnVtYmVyKGkpKSkmJmUucHJldmVudERlZmF1bHQoKX1zdGVwVHlwZWQoZSl7ZS5wcmV2ZW50RGVmYXVsdCgpO2xldCBpPWUudGFyZ2V0LmlubmVyVGV4dDt0aGlzLnN0ZXBDaGFuZ2VkLmVtaXQoIiIhPT1pP051bWJlcihpKTpudWxsKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siY2FyZC1mb2IiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiZvdChwWWUsNyxSZSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5zdGVwU3Bhbj1yLmZpcnN0KX19LGlucHV0czp7c3RlcDoic3RlcCIsYWxsb3dSZW1vdmFsOiJhbGxvd1JlbW92YWwiLGlzUHJvc3BlY3RpdmU6ImlzUHJvc3BlY3RpdmUifSxvdXRwdXRzOntzdGVwQ2hhbmdlZDoic3RlcENoYW5nZWQiLGZvYlJlbW92ZWQ6ImZvYlJlbW92ZWQifSxmZWF0dXJlczpbRnRdLGRlY2xzOjUsdmFyczo3LGNvbnN0czpbWzMsIm5nQ2xhc3MiXSxbImNvbnRlbnRlZGl0YWJsZSIsIiIsInJvbGUiLCJ0ZXh0Ym94IiwiYXJpYS1sYWJlbCIsIkVkaXQgc3RlcCIsMywiaW5uZXJIVE1MIiwiYmx1ciIsImtleXByZXNzIiwia2V5ZG93bi5lbnRlciIsImtleWRvd24uc2hpZnQuZW50ZXIiXSxbInN0ZXBTcGFuIiwiIl0sWyJhcmlhLWxhYmVsIiwiRGVzZWxlY3QgZm9iIiwzLCJjbGljayIsNCwibmdJZiJdLFsiYXJpYS1sYWJlbCIsIkRlc2VsZWN0IGZvYiIsMywiY2xpY2siXSxbInN2Z0ljb24iLCJjbG9zZV8yNHB4Il0sWyJzdmdJY29uIiwia2VlcF8yNHB4Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJkaXYiLDApKDEsInNwYW4iLDEsMiksUCgiYmx1ciIsZnVuY3Rpb24obyl7cmV0dXJuIGkuc3RlcFR5cGVkKG8pfSkoImtleXByZXNzIixmdW5jdGlvbihvKXtyZXR1cm4gaS52YWxpZGF0ZVN0ZXAobyl9KSgia2V5ZG93bi5lbnRlciIsZnVuY3Rpb24obyl7cmV0dXJuIGkuc3RlcFR5cGVkKG8pfSkoImtleWRvd24uc2hpZnQuZW50ZXIiLGZ1bmN0aW9uKG8pe3JldHVybiBvLnByZXZlbnREZWZhdWx0KCl9KSx2KCksRSgzLGhZZSwyLDAsImJ1dHRvbiIsMyksRSg0LGZZZSwyLDAsImJ1dHRvbiIsMyksdigpKSwyJmUmJih5KCJuZ0NsYXNzIixRcig0LG1ZZSwhaS5hbGxvd1JlbW92YWwsaS5pc1Byb3NwZWN0aXZlKSksQygxKSx5KCJpbm5lckhUTUwiLGkuc3RlcCxBMyksQygyKSx5KCJuZ0lmIixpLmFsbG93UmVtb3ZhbCksQygxKSx5KCJuZ0lmIixpLmlzUHJvc3BlY3RpdmUpKX0sZGVwZW5kZW5jaWVzOltGbixCZSxHdF0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9ja30uZm9iW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1mbGV4O2JhY2tncm91bmQtY29sb3I6I2UwZTBlMDtib3JkZXItcmFkaXVzOjI1cHg7cGFkZGluZzoycHggMnB4IDJweCA0cHg7Zm9udC1zaXplOjExcHg7dGV4dC1hbGlnbjpjZW50ZXI7d2lkdGg6bWluLWNvbnRlbnR9LmZvYltfbmdjb250ZW50LSVDT01QJV0gPiAucHJvc3BlY3RpdmVbX25nY29udGVudC0lQ09NUCVde3BhZGRpbmctdG9wOjFweH0uZm9iW19uZ2NvbnRlbnQtJUNPTVAlXTpob3ZlcntjdXJzb3I6Z3JhYn0uZm9iW19uZ2NvbnRlbnQtJUNPTVAlXTpob3Zlci5wcm9zcGVjdGl2ZXtjdXJzb3I6cG9pbnRlcn0uZm9iW19uZ2NvbnRlbnQtJUNPTVAlXTphY3RpdmV7Y3Vyc29yOmdyYWJiaW5nfS5mb2IudW5yZW1vdmFibGVbX25nY29udGVudC0lQ09NUCVde3BhZGRpbmc6MnB4IDRweH0uZm9iLnByb3NwZWN0aXZlW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7Ym94LXNpemluZzpib3JkZXItYm94O2JvcmRlcjoxcHggZGFzaGVkICM5ZTllOWU7Zm9udC13ZWlnaHQ6Ym9sZDtoZWlnaHQ6MTdweH1zcGFuW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjppbmhlcml0O2Rpc3BsYXk6aW5saW5lLWJsb2NrfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIHNwYW5bX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICBzcGFuW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjojNjE2MTYxfWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17bWFyZ2luLWxlZnQ6MnB4O3BhZGRpbmc6MDtib3JkZXI6MDtib3JkZXItcmFkaXVzOjUwJTtmb250LXNpemU6MTFweDt3aWR0aDoxMXB4O2hlaWdodDoxMXB4O2JhY2tncm91bmQtY29sb3I6aW5oZXJpdDtjb2xvcjppbmhlcml0fWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV0gICAubWF0LWljb25bX25nY29udGVudC0lQ09NUCVde3dpZHRoOjEwMCU7aGVpZ2h0OjExMCV9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjojNjE2MTYxfWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV06aG92ZXJ7YmFja2dyb3VuZC1jb2xvcjojOWU5ZTllO2NvbG9yOiNlZWU7Y3Vyc29yOnBvaW50ZXJ9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXTpob3ZlciwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXTpob3ZlcntiYWNrZ3JvdW5kLWNvbG9yOiM2MTYxNjE7Y29sb3I6I2UwZTBlMH0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLF9ZZT1bInN0YXJ0Rm9iV3JhcHBlciJdLHZZZT1bImVuZEZvYldyYXBwZXIiXSx5WWU9WyJwcm9zcGVjdGl2ZUZvYldyYXBwZXIiXTtmdW5jdGlvbiBiWWUobix0KXsxJm4mJk8oMCwiZGl2Iiw3KX1mdW5jdGlvbiB4WWUobix0KXtpZigxJm4mJihfKDAsImRpdiIsMyw0KSxFKDIsYlllLDEsMCwiZGl2Iiw1KSxPKDMsImNhcmQtZm9iIiw2KSx2KCkpLDImbil7bGV0IGU9UygyKTtQdCgidHJhbnNmb3JtIixlLmdldENzc1RyYW5zbGF0ZVB4Rm9yUHJvc3BlY3RpdmVGb2IoKSksQygyKSx5KCJuZ0lmIixlLnNob3dFeHRlbmRlZExpbmUpLEMoMSkseSgibmdDbGFzcyIsZS5pc1ZlcnRpY2FsKCk/InZlcnRpY2FsLWZvYiI6Imhvcml6b250YWwtZm9iIikoImFsbG93UmVtb3ZhbCIsITEpKCJpc1Byb3NwZWN0aXZlIiwhMCkoInN0ZXAiLGUucHJvc3BlY3RpdmVTdGVwKX19ZnVuY3Rpb24gQ1llKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO3NuKDApLEUoMSx4WWUsNCw3LCJkaXYiLDEpLF8oMiwiZGl2IiwyKSxQKCJtb3VzZW1vdmUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkubW91c2VPdmVyUHJvc3BlY3RpdmVGb2JBcmVhKHIpKX0pKCJjbGljayIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5wcm9zcGVjdGl2ZUZvYkNsaWNrZWQocikpfSkoIm1vdXNlbGVhdmUiLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5vblByb3NwZWN0aXZlQXJlYU1vdXNlTGVhdmUoKSl9KSx2KCksYW4oKX1pZigyJm4pe2xldCBlPVMoKTtDKDEpLHkoIm5nSWYiLG51bGwhPT1lLnByb3NwZWN0aXZlU3RlcCksQygxKSx5KCJuZ0NsYXNzIixlLmlzVmVydGljYWwoKT8idmVydGljYWwtcHJvc3BlY3RpdmUtYXJlYSI6Imhvcml6b250YWwtcHJvc3BlY3RpdmUtYXJlYSIpfX1mdW5jdGlvbiBNWWUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDExKSxQKCJtb3VzZWRvd24iLGZ1bmN0aW9uKHIpe29lKGUpO2xldCBvPVMoMik7cmV0dXJuIHNlKG8uc3RhcnREcmFnKG8uRm9iLlNUQVJULG8uVGltZVNlbGVjdGlvbkFmZm9yZGFuY2UuRVhURU5ERURfTElORSxyKSl9KSx2KCl9fWZ1bmN0aW9uIHdZZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMyw4KSxFKDIsTVllLDEsMCwiZGl2Iiw5KSxfKDMsImNhcmQtZm9iIiwxMCksUCgibW91c2Vkb3duIixmdW5jdGlvbihyKXtvZShlKTtsZXQgbz1TKCk7cmV0dXJuIHNlKG8uc3RhcnREcmFnKG8uRm9iLlNUQVJULG8uVGltZVNlbGVjdGlvbkFmZm9yZGFuY2UuRk9CLHIpKX0pKCJzdGVwQ2hhbmdlZCIsZnVuY3Rpb24ocil7b2UoZSk7bGV0IG89UygpO3JldHVybiBzZShvLnN0ZXBUeXBlZChvLkZvYi5TVEFSVCxyKSl9KSgiZm9iUmVtb3ZlZCIsZnVuY3Rpb24oKXtvZShlKTtsZXQgcj1TKCk7cmV0dXJuIHNlKHIub25Gb2JSZW1vdmVkKHIuRm9iLlNUQVJUKSl9KSx2KCkoKX1pZigyJm4pe2xldCBlPVMoKTtQdCgidHJhbnNmb3JtIixlLmdldENzc1RyYW5zbGF0ZVB4Rm9yU3RhcnRGb2IoKSksQygyKSx5KCJuZ0lmIixlLnNob3dFeHRlbmRlZExpbmUpLEMoMSkseSgibmdDbGFzcyIsZS5pc1ZlcnRpY2FsKCk/InZlcnRpY2FsLWZvYiI6Imhvcml6b250YWwtZm9iIikoInN0ZXAiLGUudGltZVNlbGVjdGlvbi5zdGFydC5zdGVwKX19ZnVuY3Rpb24gU1llKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IiwxMSksUCgibW91c2Vkb3duIixmdW5jdGlvbihyKXtvZShlKTtsZXQgbz1TKDIpO3JldHVybiBzZShvLnN0YXJ0RHJhZyhvLkZvYi5FTkQsby5UaW1lU2VsZWN0aW9uQWZmb3JkYW5jZS5FWFRFTkRFRF9MSU5FLHIpKX0pLHYoKX19ZnVuY3Rpb24gRVllKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IiwzLDEyKSxFKDIsU1llLDEsMCwiZGl2Iiw5KSxfKDMsImNhcmQtZm9iIiwxMyksUCgibW91c2Vkb3duIixmdW5jdGlvbihyKXtvZShlKTtsZXQgbz1TKCk7cmV0dXJuIHNlKG8uc3RhcnREcmFnKG8uRm9iLkVORCxvLlRpbWVTZWxlY3Rpb25BZmZvcmRhbmNlLkZPQixyKSl9KSgic3RlcENoYW5nZWQiLGZ1bmN0aW9uKHIpe29lKGUpO2xldCBvPVMoKTtyZXR1cm4gc2Uoby5zdGVwVHlwZWQoby5Gb2IuRU5ELHIpKX0pKCJmb2JSZW1vdmVkIixmdW5jdGlvbigpe29lKGUpO2xldCByPVMoKTtyZXR1cm4gc2Uoci5vbkZvYlJlbW92ZWQoci5Gb2IuRU5EKSl9KSx2KCkoKX1pZigyJm4pe2xldCBlPVMoKTtQdCgidHJhbnNmb3JtIixlLmdldENzc1RyYW5zbGF0ZVB4Rm9yRW5kRm9iKCkpLEMoMikseSgibmdJZiIsZS5zaG93RXh0ZW5kZWRMaW5lKSxDKDEpLHkoIm5nQ2xhc3MiLGUuaXNWZXJ0aWNhbCgpPyJ2ZXJ0aWNhbC1mb2IiOiJob3Jpem9udGFsLWZvYiIpKCJzdGVwIixlLnRpbWVTZWxlY3Rpb24uZW5kLnN0ZXApfX12YXIgWHI9KCgpPT4oZnVuY3Rpb24obil7bltuLk5PTkU9MF09Ik5PTkUiLG5bbi5TVEFSVD0xXT0iU1RBUlQiLG5bbi5FTkQ9Ml09IkVORCJ9KFhyfHwoWHI9e30pKSxYcikpKCksVFllPXtzdGFydDpYci5TVEFSVCxlbmQ6WHIuRU5EfSxHaz0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMucm9vdD1lLHRoaXMuc2hvd0V4dGVuZGVkTGluZT0hMSx0aGlzLmlzUHJvc3BlY3RpdmVGb2JGZWF0dXJlRW5hYmxlZD0hMSx0aGlzLnByb3NwZWN0aXZlU3RlcD1udWxsLHRoaXMucHJvc3BlY3RpdmVTdGVwQXhpc1Bvc2l0aW9uPW51bGwsdGhpcy5vblRpbWVTZWxlY3Rpb25DaGFuZ2VkPW5ldyBHLHRoaXMub25UaW1lU2VsZWN0aW9uVG9nZ2xlZD1uZXcgRyx0aGlzLm9uUHJvc3BlY3RpdmVTdGVwQ2hhbmdlZD1uZXcgRyx0aGlzLmhhc0ZvYk1vdmVkPSExLHRoaXMuY3VycmVudERyYWdnaW5nRm9iPVhyLk5PTkUsdGhpcy5hZmZvcmRhbmNlPWNzLk5PTkUsdGhpcy5tb3VzZUxpc3RlbmVyPXRoaXMubW91c2VNb3ZlLmJpbmQodGhpcyksdGhpcy5zdG9wTGlzdGVuZXI9dGhpcy5zdG9wRHJhZy5iaW5kKHRoaXMpLHRoaXMuRm9iPVhyLHRoaXMuVGltZVNlbGVjdGlvbkFmZm9yZGFuY2U9Y3N9Z2V0Q3NzVHJhbnNsYXRlUHhGb3JTdGFydEZvYigpe3JldHVybiB0aGlzLmF4aXNEaXJlY3Rpb249PT1wYS5WRVJUSUNBTD9gdHJhbnNsYXRlKDBweCwgJHt0aGlzLnN0YXJ0U3RlcEF4aXNQb3NpdGlvbn1weClgOmB0cmFuc2xhdGUoJHt0aGlzLnN0YXJ0U3RlcEF4aXNQb3NpdGlvbn1weCwgMHB4KWB9Z2V0Q3NzVHJhbnNsYXRlUHhGb3JFbmRGb2IoKXtyZXR1cm4gbnVsbD09PXRoaXMuZW5kU3RlcEF4aXNQb3NpdGlvbj8iIjp0aGlzLmF4aXNEaXJlY3Rpb249PT1wYS5WRVJUSUNBTD9gdHJhbnNsYXRlKDBweCwgJHt0aGlzLmVuZFN0ZXBBeGlzUG9zaXRpb259cHgpYDpgdHJhbnNsYXRlKCR7dGhpcy5lbmRTdGVwQXhpc1Bvc2l0aW9ufXB4LCAwcHgpYH1nZXRDc3NUcmFuc2xhdGVQeEZvclByb3NwZWN0aXZlRm9iKCl7cmV0dXJuIG51bGw9PT10aGlzLnByb3NwZWN0aXZlU3RlcD8iIjp0aGlzLmF4aXNEaXJlY3Rpb249PT1wYS5WRVJUSUNBTD9gdHJhbnNsYXRlKDBweCwgJHt0aGlzLnByb3NwZWN0aXZlU3RlcEF4aXNQb3NpdGlvbn1weClgOmB0cmFuc2xhdGUoJHt0aGlzLnByb3NwZWN0aXZlU3RlcEF4aXNQb3NpdGlvbn1weCwgMHB4KWB9c3RvcEV2ZW50UHJvcGFnYXRpb24oZSl7ZS5zdG9wUHJvcGFnYXRpb24oKSxlLnByZXZlbnREZWZhdWx0KCl9c3RhcnREcmFnKGUsaSxyKXtpIT09Y3MuRk9CJiZ0aGlzLnN0b3BFdmVudFByb3BhZ2F0aW9uKHIpLGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoIm1vdXNlbW92ZSIsdGhpcy5tb3VzZUxpc3RlbmVyKSxkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCJtb3VzZXVwIix0aGlzLnN0b3BMaXN0ZW5lciksdGhpcy5jdXJyZW50RHJhZ2dpbmdGb2I9ZSx0aGlzLmFmZm9yZGFuY2U9aX1zdG9wRHJhZygpe2RvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoIm1vdXNlbW92ZSIsdGhpcy5tb3VzZUxpc3RlbmVyKSxkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJtb3VzZXVwIix0aGlzLnN0b3BMaXN0ZW5lciksdGhpcy5jdXJyZW50RHJhZ2dpbmdGb2I9WHIuTk9ORSx0aGlzLmhhc0ZvYk1vdmVkJiZ0aGlzLnRpbWVTZWxlY3Rpb24mJnRoaXMub25UaW1lU2VsZWN0aW9uQ2hhbmdlZC5lbWl0KHt0aW1lU2VsZWN0aW9uOnRoaXMudGltZVNlbGVjdGlvbixhZmZvcmRhbmNlOnRoaXMuYWZmb3JkYW5jZX0pLHRoaXMuYWZmb3JkYW5jZT1jcy5OT05FLHRoaXMuaGFzRm9iTW92ZWQ9ITF9aXNWZXJ0aWNhbCgpe3JldHVybiB0aGlzLmF4aXNEaXJlY3Rpb249PT1wYS5WRVJUSUNBTH1zaG91bGRTd2FwRm9icyhlKXtyZXR1cm4hKCF0aGlzLnRpbWVTZWxlY3Rpb258fCF0aGlzLnRpbWVTZWxlY3Rpb24uZW5kKSYmKHRoaXMuY3VycmVudERyYWdnaW5nRm9iPT09WHIuRU5EP2U8dGhpcy50aW1lU2VsZWN0aW9uLnN0YXJ0LnN0ZXA6dGhpcy5jdXJyZW50RHJhZ2dpbmdGb2I9PT1Yci5TVEFSVCYmZT50aGlzLnRpbWVTZWxlY3Rpb24uZW5kLnN0ZXApfWdldE5ld1RpbWVTZWxlY3Rpb24oZSxpKXtsZXQgcj17Li4uaX07aWYoIXRoaXMudGltZVNlbGVjdGlvbilyZXR1cm4gcjtpZighdGhpcy50aW1lU2VsZWN0aW9uLmVuZClyZXR1cm4gci5zdGFydD17c3RlcDplfSxyO2lmKHRoaXMuc2hvdWxkU3dhcEZvYnMoZSkpe2xldFtvLHNdPXRoaXMuY3VycmVudERyYWdnaW5nRm9iPT09WHIuRU5EP1siZW5kIiwic3RhcnQiXTpbInN0YXJ0IiwiZW5kIl07cmV0dXJuIHRoaXMuY3VycmVudERyYWdnaW5nRm9iPVRZZVtzXSxyW29dPXRoaXMudGltZVNlbGVjdGlvbltzXSxyW3NdPXtzdGVwOmV9LHJ9cmV0dXJuIHRoaXMuY3VycmVudERyYWdnaW5nRm9iPT09WHIuRU5EPyhyLmVuZD17c3RlcDplfSxyKTooci5zdGFydD17c3RlcDplfSxyKX1nZXROZXdTdGVwRnJvbU1vdXNlRXZlbnQoZSl7bGV0IGk9bnVsbCxyPXRoaXMuZ2V0TW91c2VQb3NpdGlvbkZyb21FdmVudChlKSxvPXRoaXMuYXhpc0RpcmVjdGlvbj09PXBhLlZFUlRJQ0FMP2UubW92ZW1lbnRZOmUubW92ZW1lbnRYO3JldHVybiB0aGlzLmlzTW92aW5nSGlnaGVyKHIsbyk/aT10aGlzLmNhcmRGb2JIZWxwZXIuZ2V0U3RlcEhpZ2hlclRoYW5BeGlzUG9zaXRpb24ocik6dGhpcy5pc01vdmluZ0xvd2VyKHIsbykmJihpPXRoaXMuY2FyZEZvYkhlbHBlci5nZXRTdGVwTG93ZXJUaGFuQXhpc1Bvc2l0aW9uKHIpKSxudWxsPT09aT9udWxsOml9bW91c2VNb3ZlKGUpe2lmKHRoaXMuY3VycmVudERyYWdnaW5nRm9iPT09WHIuTk9ORSlyZXR1cm47bGV0IGk9dGhpcy5nZXROZXdTdGVwRnJvbU1vdXNlRXZlbnQoZSk7aWYobnVsbD09PWl8fCF0aGlzLnRpbWVTZWxlY3Rpb24pcmV0dXJuO2xldCByPXRoaXMuZ2V0TmV3VGltZVNlbGVjdGlvbihpLHRoaXMudGltZVNlbGVjdGlvbik7dGhpcy5vblRpbWVTZWxlY3Rpb25DaGFuZ2VkLmVtaXQoe3RpbWVTZWxlY3Rpb246cn0pLHRoaXMuaGFzRm9iTW92ZWQ9ITB9bW91c2VPdmVyUHJvc3BlY3RpdmVGb2JBcmVhKGUpe2lmKG51bGwhPXRoaXMudGltZVNlbGVjdGlvbj8uZW5kKXJldHVybjtsZXQgaT10aGlzLmdldE5ld1N0ZXBGcm9tTW91c2VFdmVudChlKTtudWxsIT09aSYmdGhpcy5vblByb3NwZWN0aXZlU3RlcENoYW5nZWQuZW1pdChpKX1pc01vdmluZ0xvd2VyKGUsaSl7aWYodGhpcy5jdXJyZW50RHJhZ2dpbmdGb2I9PT1Yci5OT05FJiZudWxsPT09dGhpcy5wcm9zcGVjdGl2ZVN0ZXApcmV0dXJuITA7bGV0IHI9dGhpcy5nZXRDdXJyZW50Rm9iU3RlcCgpO3JldHVybiB2b2lkIDAhPT1yJiZlPHRoaXMuZ2V0RHJhZ2dpbmdGb2JDZW50ZXIoKSYmaTwwJiZyPnRoaXMubG93ZXN0U3RlcH1pc01vdmluZ0hpZ2hlcihlLGkpe2lmKHRoaXMuY3VycmVudERyYWdnaW5nRm9iPT09WHIuTk9ORSYmbnVsbD09PXRoaXMucHJvc3BlY3RpdmVTdGVwKXJldHVybiEwO2xldCByPXRoaXMuZ2V0Q3VycmVudEZvYlN0ZXAoKTtyZXR1cm4gdm9pZCAwIT09ciYmZT50aGlzLmdldERyYWdnaW5nRm9iQ2VudGVyKCkmJmk+MCYmcjx0aGlzLmhpZ2hlc3RTdGVwfWdldERyYWdnaW5nRm9iQ2VudGVyKCl7bGV0IGU9dGhpcy5nZXRDdXJyZW50Rm9iKCk/Lm5hdGl2ZUVsZW1lbnQ7aWYoIWUpcmV0dXJuIDA7bGV0IGk9ZS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3Ascj1lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLmxlZnQ7cmV0dXJuIHRoaXMuYXhpc0RpcmVjdGlvbj09PXBhLlZFUlRJQ0FMP2ktdGhpcy5yb290Lm5hdGl2ZUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wOnItdGhpcy5yb290Lm5hdGl2ZUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkubGVmdH1nZXRDdXJyZW50Rm9iKCl7c3dpdGNoKHRoaXMuY3VycmVudERyYWdnaW5nRm9iKXtjYXNlIFhyLlNUQVJUOnJldHVybiB0aGlzLnN0YXJ0Rm9iV3JhcHBlcjtjYXNlIFhyLkVORDpyZXR1cm4gdGhpcy5lbmRGb2JXcmFwcGVyO2Nhc2UgWHIuTk9ORTpyZXR1cm4gdGhpcy5wcm9zcGVjdGl2ZUZvYldyYXBwZXJ9fWdldEN1cnJlbnRGb2JTdGVwKCl7c3dpdGNoKHRoaXMuY3VycmVudERyYWdnaW5nRm9iKXtjYXNlIFhyLlNUQVJUOnJldHVybiB0aGlzLnRpbWVTZWxlY3Rpb24/LnN0YXJ0LnN0ZXA7Y2FzZSBYci5FTkQ6cmV0dXJuIHRoaXMudGltZVNlbGVjdGlvbj8uZW5kPy5zdGVwO2Nhc2UgWHIuTk9ORTpyZXR1cm4gdGhpcy5wcm9zcGVjdGl2ZVN0ZXA/P3ZvaWQgMH19Z2V0TW91c2VQb3NpdGlvbkZyb21FdmVudChlKXtyZXR1cm4gdGhpcy5heGlzRGlyZWN0aW9uPT09cGEuVkVSVElDQUw/ZS5jbGllbnRZLXRoaXMucm9vdC5uYXRpdmVFbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcDplLmNsaWVudFgtdGhpcy5yb290Lm5hdGl2ZUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkubGVmdH1zdGVwVHlwZWQoZSxpKXtpZihudWxsPT09aSlyZXR1cm4gdm9pZChudWxsIT09dGhpcy50aW1lU2VsZWN0aW9uLmVuZCYmdGhpcy5vbkZvYlJlbW92ZWQoZSkpO2xldCByPXsuLi50aGlzLnRpbWVTZWxlY3Rpb259O2U9PT1Yci5TVEFSVD9yLnN0YXJ0PXtzdGVwOml9OmU9PT1Yci5FTkQmJihyLmVuZD17c3RlcDppfSksbnVsbCE9PXIuZW5kJiZyLnN0YXJ0LnN0ZXA+ci5lbmQuc3RlcCYmKHI9e3N0YXJ0OnIuZW5kLGVuZDpyLnN0YXJ0fSksdGhpcy5vblRpbWVTZWxlY3Rpb25DaGFuZ2VkLmVtaXQoe3RpbWVTZWxlY3Rpb246cixhZmZvcmRhbmNlOmNzLkZPQl9URVhUfSl9cHJvc3BlY3RpdmVGb2JDbGlja2VkKGUpe2Uuc3RvcFByb3BhZ2F0aW9uKCk7bGV0IGk9dGhpcy5nZXRQcm9zcGVjdGl2ZVRpbWVTZWxlY3Rpb24oKTshaXx8KHRoaXMub25UaW1lU2VsZWN0aW9uQ2hhbmdlZC5lbWl0KHthZmZvcmRhbmNlOmNzLkZPQl9BRERFRCx0aW1lU2VsZWN0aW9uOml9KSx0aGlzLm9uUHJvc3BlY3RpdmVTdGVwQ2hhbmdlZC5lbWl0KG51bGwpKX1nZXRQcm9zcGVjdGl2ZVRpbWVTZWxlY3Rpb24oKXtpZih0aGlzLnByb3NwZWN0aXZlU3RlcClyZXR1cm4gdGhpcy50aW1lU2VsZWN0aW9uP3tzdGFydDp7c3RlcDpNYXRoLm1pbih0aGlzLnRpbWVTZWxlY3Rpb24uc3RhcnQuc3RlcCx0aGlzLnByb3NwZWN0aXZlU3RlcCl9LGVuZDp7c3RlcDpNYXRoLm1heCh0aGlzLnRpbWVTZWxlY3Rpb24uc3RhcnQuc3RlcCx0aGlzLnByb3NwZWN0aXZlU3RlcCl9fTp7c3RhcnQ6e3N0ZXA6dGhpcy5wcm9zcGVjdGl2ZVN0ZXB9LGVuZDpudWxsfX1vbkZvYlJlbW92ZWQoZSl7ZSE9PVhyLkVORD9udWxsPT09dGhpcy50aW1lU2VsZWN0aW9uLmVuZD90aGlzLm9uVGltZVNlbGVjdGlvblRvZ2dsZWQuZW1pdCgpOnRoaXMub25UaW1lU2VsZWN0aW9uQ2hhbmdlZC5lbWl0KHthZmZvcmRhbmNlOmNzLkZPQl9SRU1PVkVELHRpbWVTZWxlY3Rpb246e3N0YXJ0OnRoaXMudGltZVNlbGVjdGlvbi5lbmQsZW5kOm51bGx9fSk6dGhpcy5vblRpbWVTZWxlY3Rpb25DaGFuZ2VkLmVtaXQoe2FmZm9yZGFuY2U6Y3MuRk9CX1JFTU9WRUQsdGltZVNlbGVjdGlvbjp7Li4udGhpcy50aW1lU2VsZWN0aW9uLGVuZDpudWxsfX0pfW9uUHJvc3BlY3RpdmVBcmVhTW91c2VMZWF2ZSgpe3RoaXMub25Qcm9zcGVjdGl2ZVN0ZXBDaGFuZ2VkLmVtaXQobnVsbCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJjYXJkLWZvYi1jb250cm9sbGVyIl1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKG90KF9ZZSw1KSxvdCh2WWUsNSksb3QoeVllLDUpKSwyJmUpe2xldCByO05lKHI9TGUoKSkmJihpLnN0YXJ0Rm9iV3JhcHBlcj1yLmZpcnN0KSxOZShyPUxlKCkpJiYoaS5lbmRGb2JXcmFwcGVyPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLnByb3NwZWN0aXZlRm9iV3JhcHBlcj1yLmZpcnN0KX19LGlucHV0czp7YXhpc0RpcmVjdGlvbjoiYXhpc0RpcmVjdGlvbiIsdGltZVNlbGVjdGlvbjoidGltZVNlbGVjdGlvbiIsY2FyZEZvYkhlbHBlcjoiY2FyZEZvYkhlbHBlciIsc3RhcnRTdGVwQXhpc1Bvc2l0aW9uOiJzdGFydFN0ZXBBeGlzUG9zaXRpb24iLGVuZFN0ZXBBeGlzUG9zaXRpb246ImVuZFN0ZXBBeGlzUG9zaXRpb24iLGhpZ2hlc3RTdGVwOiJoaWdoZXN0U3RlcCIsbG93ZXN0U3RlcDoibG93ZXN0U3RlcCIsc2hvd0V4dGVuZGVkTGluZToic2hvd0V4dGVuZGVkTGluZSIsaXNQcm9zcGVjdGl2ZUZvYkZlYXR1cmVFbmFibGVkOiJpc1Byb3NwZWN0aXZlRm9iRmVhdHVyZUVuYWJsZWQiLHByb3NwZWN0aXZlU3RlcDoicHJvc3BlY3RpdmVTdGVwIixwcm9zcGVjdGl2ZVN0ZXBBeGlzUG9zaXRpb246InByb3NwZWN0aXZlU3RlcEF4aXNQb3NpdGlvbiJ9LG91dHB1dHM6e29uVGltZVNlbGVjdGlvbkNoYW5nZWQ6Im9uVGltZVNlbGVjdGlvbkNoYW5nZWQiLG9uVGltZVNlbGVjdGlvblRvZ2dsZWQ6Im9uVGltZVNlbGVjdGlvblRvZ2dsZWQiLG9uUHJvc3BlY3RpdmVTdGVwQ2hhbmdlZDoib25Qcm9zcGVjdGl2ZVN0ZXBDaGFuZ2VkIn0sZGVjbHM6NCx2YXJzOjMsY29uc3RzOltbNCwibmdJZiJdLFsiY2xhc3MiLCJ0aW1lLWZvYi13cmFwcGVyIiwzLCJ0cmFuc2Zvcm0iLDQsIm5nSWYiXSxbMSwicHJvc3BlY3RpdmUtZm9iLWFyZWEiLDMsIm5nQ2xhc3MiLCJtb3VzZW1vdmUiLCJjbGljayIsIm1vdXNlbGVhdmUiXSxbMSwidGltZS1mb2Itd3JhcHBlciJdLFsicHJvc3BlY3RpdmVGb2JXcmFwcGVyIiwiIl0sWyJjbGFzcyIsImV4dGVuZGVkLWxpbmUiLDQsIm5nSWYiXSxbMywibmdDbGFzcyIsImFsbG93UmVtb3ZhbCIsImlzUHJvc3BlY3RpdmUiLCJzdGVwIl0sWzEsImV4dGVuZGVkLWxpbmUiXSxbInN0YXJ0Rm9iV3JhcHBlciIsIiJdLFsiY2xhc3MiLCJleHRlbmRlZC1saW5lIiwzLCJtb3VzZWRvd24iLDQsIm5nSWYiXSxbMSwic3RhcnRGb2IiLDMsIm5nQ2xhc3MiLCJzdGVwIiwibW91c2Vkb3duIiwic3RlcENoYW5nZWQiLCJmb2JSZW1vdmVkIl0sWzEsImV4dGVuZGVkLWxpbmUiLDMsIm1vdXNlZG93biJdLFsiZW5kRm9iV3JhcHBlciIsIiJdLFsxLCJlbmRGb2IiLDMsIm5nQ2xhc3MiLCJzdGVwIiwibW91c2Vkb3duIiwic3RlcENoYW5nZWQiLCJmb2JSZW1vdmVkIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJkaXYiKSxFKDEsQ1llLDMsMiwibmctY29udGFpbmVyIiwwKSxFKDIsd1llLDQsNSwiZGl2IiwxKSxFKDMsRVllLDQsNSwiZGl2IiwxKSx2KCkpLDImZSYmKEMoMSkseSgibmdJZiIsaS5pc1Byb3NwZWN0aXZlRm9iRmVhdHVyZUVuYWJsZWQpLEMoMSkseSgibmdJZiIsaS50aW1lU2VsZWN0aW9uKSxDKDEpLHkoIm5nSWYiLGkudGltZVNlbGVjdGlvbiYmaS50aW1lU2VsZWN0aW9uLmVuZCkpfSxkZXBlbmRlbmNpZXM6W0ZuLEJlLGhwZV0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXXtwb2ludGVyLWV2ZW50czphbGx9LnRpbWUtZm9iLXdyYXBwZXJbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6aW5saW5lLWJsb2NrO3Bvc2l0aW9uOmFic29sdXRlO3RvcDowO3dpZHRoOjB9LnZlcnRpY2FsLWZvYltfbmdjb250ZW50LSVDT01QJV17dHJhbnNmb3JtOnRyYW5zbGF0ZVkoLTUwJSl9Lmhvcml6b250YWwtZm9iW19uZ2NvbnRlbnQtJUNPTVAlXXt0cmFuc2Zvcm06dHJhbnNsYXRlWCgtNTAlKX0uZXh0ZW5kZWQtbGluZVtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLXN0eWxlOmRhc2hlZDtib3JkZXItd2lkdGg6MCAxcHg7aGVpZ2h0OmNhbGMoMTAwJSAtIDMwcHgpfS5leHRlbmRlZC1saW5lW19uZ2NvbnRlbnQtJUNPTVAlXTpob3ZlcntiYWNrZ3JvdW5kOmxpbmVhci1ncmFkaWVudCh0byByaWdodCwgdHJhbnNwYXJlbnQgMThweCwgI2NjYyAxOXB4LCAjY2NjIDIxcHgsIHRyYW5zcGFyZW50IDIycHgpO2JvcmRlcjowO2N1cnNvcjpldy1yZXNpemU7bWFyZ2luLWxlZnQ6LTIwcHg7cGFkZGluZzowIDIwcHh9Lmhvcml6b250YWwtcHJvc3BlY3RpdmUtYXJlYVtfbmdjb250ZW50LSVDT01QJV17Ym90dG9tOjA7Y3Vyc29yOnBvaW50ZXI7cG9zaXRpb246YWJzb2x1dGU7aGVpZ2h0OjMwcHg7d2lkdGg6Y2FsYygxMDAlIC0gNzRweCl9LnByb3NwZWN0aXZlLWFyZWFbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6YmxvY2t9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxtcGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuaXNQcm9zcGVjdGl2ZUZvYkZlYXR1cmVFbmFibGVkPSExLHRoaXMuZGlzYWJsZUludGVyYWN0aW9uPSExLHRoaXMub25UaW1lU2VsZWN0aW9uQ2hhbmdlZD1uZXcgRyx0aGlzLm9uVGltZVNlbGVjdGlvblRvZ2dsZWQ9bmV3IEcsdGhpcy5heGlzRGlyZWN0aW9uPXBhLkhPUklaT05UQUwsdGhpcy5jYXJkRm9iSGVscGVyPXtnZXRTdGVwSGlnaGVyVGhhbkF4aXNQb3NpdGlvbjp0aGlzLmdldFN0ZXBIaWdoZXJUaGFuQXhpc1Bvc2l0aW9uLmJpbmQodGhpcyksZ2V0U3RlcExvd2VyVGhhbkF4aXNQb3NpdGlvbjp0aGlzLmdldFN0ZXBMb3dlclRoYW5BeGlzUG9zaXRpb24uYmluZCh0aGlzKX0sdGhpcy5wcm9zcGVjdGl2ZVN0ZXA9bnVsbH1nZXRBeGlzUG9zaXRpb25Gcm9tU3RhcnRTdGVwKCl7cmV0dXJuIHRoaXMudGltZVNlbGVjdGlvbj90aGlzLnNjYWxlLmZvcndhcmQodGhpcy5taW5NYXhIb3Jpem9udGFsVmlld0V4dGVuZCxbMCx0aGlzLmF4aXNTaXplXSx0aGlzLnRpbWVTZWxlY3Rpb24uc3RhcnQuc3RlcCk6IiJ9Z2V0QXhpc1Bvc2l0aW9uRnJvbUVuZFN0ZXAoKXtyZXR1cm4gdGhpcy50aW1lU2VsZWN0aW9uPy5lbmQ/dGhpcy5zY2FsZS5mb3J3YXJkKHRoaXMubWluTWF4SG9yaXpvbnRhbFZpZXdFeHRlbmQsWzAsdGhpcy5heGlzU2l6ZV0sdGhpcy50aW1lU2VsZWN0aW9uPy5lbmQuc3RlcD8/dGhpcy5taW5NYXhTdGVwLm1heFN0ZXApOm51bGx9Z2V0QXhpc1Bvc2l0aW9uRnJvbVByb3NwZWN0aXZlU3RlcCgpe3JldHVybiBudWxsPT09dGhpcy5wcm9zcGVjdGl2ZVN0ZXA/bnVsbDp0aGlzLnNjYWxlLmZvcndhcmQodGhpcy5taW5NYXhIb3Jpem9udGFsVmlld0V4dGVuZCxbMCx0aGlzLmF4aXNTaXplXSx0aGlzLnByb3NwZWN0aXZlU3RlcCl9b25Qcm9zcGVjdGl2ZVN0ZXBDaGFuZ2VkKGUpe3RoaXMucHJvc3BlY3RpdmVTdGVwPWV9Z2V0SGlnaGVzdFN0ZXAoKXtyZXR1cm4gdGhpcy5taW5NYXhTdGVwLm1heFN0ZXB9Z2V0TG93ZXN0U3RlcCgpe3JldHVybiB0aGlzLm1pbk1heFN0ZXAubWluU3RlcH1nZXRTdGVwSGlnaGVyVGhhbkF4aXNQb3NpdGlvbihlKXtyZXR1cm4gdGhpcy5nZXRTdGVwQXRNb3VzZVBvc3Rpb24oZSl9Z2V0U3RlcExvd2VyVGhhbkF4aXNQb3NpdGlvbihlKXtyZXR1cm4gdGhpcy5nZXRTdGVwQXRNb3VzZVBvc3Rpb24oZSl9Z2V0U3RlcEF0TW91c2VQb3N0aW9uKGUpe2xldCBpPU1hdGgucm91bmQodGhpcy5zY2FsZS5yZXZlcnNlKHRoaXMubWluTWF4SG9yaXpvbnRhbFZpZXdFeHRlbmQsWzAsdGhpcy5heGlzU2l6ZV0sZSkpO3JldHVybiBpPnRoaXMuZ2V0SGlnaGVzdFN0ZXAoKT90aGlzLmdldEhpZ2hlc3RTdGVwKCk6aTx0aGlzLmdldExvd2VzdFN0ZXAoKT90aGlzLmdldExvd2VzdFN0ZXAoKTppfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJzY2FsYXItY2FyZC1mb2ItY29udHJvbGxlciJdXSxpbnB1dHM6e3RpbWVTZWxlY3Rpb246InRpbWVTZWxlY3Rpb24iLHNjYWxlOiJzY2FsZSIsbWluTWF4SG9yaXpvbnRhbFZpZXdFeHRlbmQ6Im1pbk1heEhvcml6b250YWxWaWV3RXh0ZW5kIixtaW5NYXhTdGVwOiJtaW5NYXhTdGVwIixheGlzU2l6ZToiYXhpc1NpemUiLGlzUHJvc3BlY3RpdmVGb2JGZWF0dXJlRW5hYmxlZDoiaXNQcm9zcGVjdGl2ZUZvYkZlYXR1cmVFbmFibGVkIixkaXNhYmxlSW50ZXJhY3Rpb246ImRpc2FibGVJbnRlcmFjdGlvbiJ9LG91dHB1dHM6e29uVGltZVNlbGVjdGlvbkNoYW5nZWQ6Im9uVGltZVNlbGVjdGlvbkNoYW5nZWQiLG9uVGltZVNlbGVjdGlvblRvZ2dsZWQ6Im9uVGltZVNlbGVjdGlvblRvZ2dsZWQifSxkZWNsczoxLHZhcnM6MTMsY29uc3RzOltbMywiYXhpc0RpcmVjdGlvbiIsInRpbWVTZWxlY3Rpb24iLCJzdGFydFN0ZXBBeGlzUG9zaXRpb24iLCJlbmRTdGVwQXhpc1Bvc2l0aW9uIiwicHJvc3BlY3RpdmVTdGVwQXhpc1Bvc2l0aW9uIiwiaGlnaGVzdFN0ZXAiLCJsb3dlc3RTdGVwIiwicHJvc3BlY3RpdmVTdGVwIiwiaXNQcm9zcGVjdGl2ZUZvYkZlYXR1cmVFbmFibGVkIiwiY2FyZEZvYkhlbHBlciIsInNob3dFeHRlbmRlZExpbmUiLCJvblByb3NwZWN0aXZlU3RlcENoYW5nZWQiLCJvblRpbWVTZWxlY3Rpb25DaGFuZ2VkIiwib25UaW1lU2VsZWN0aW9uVG9nZ2xlZCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiY2FyZC1mb2ItY29udHJvbGxlciIsMCksUCgib25Qcm9zcGVjdGl2ZVN0ZXBDaGFuZ2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblByb3NwZWN0aXZlU3RlcENoYW5nZWQobyl9KSgib25UaW1lU2VsZWN0aW9uQ2hhbmdlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25UaW1lU2VsZWN0aW9uQ2hhbmdlZC5lbWl0KG8pfSkoIm9uVGltZVNlbGVjdGlvblRvZ2dsZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uVGltZVNlbGVjdGlvblRvZ2dsZWQuZW1pdChvKX0pLHYoKSksMiZlJiYoUHQoInBvaW50ZXItZXZlbnRzIixpLmRpc2FibGVJbnRlcmFjdGlvbj8ibm9uZSI6ImFsbCIpLHkoImF4aXNEaXJlY3Rpb24iLGkuYXhpc0RpcmVjdGlvbikoInRpbWVTZWxlY3Rpb24iLGkudGltZVNlbGVjdGlvbikoInN0YXJ0U3RlcEF4aXNQb3NpdGlvbiIsaS5nZXRBeGlzUG9zaXRpb25Gcm9tU3RhcnRTdGVwKCkpKCJlbmRTdGVwQXhpc1Bvc2l0aW9uIixpLmdldEF4aXNQb3NpdGlvbkZyb21FbmRTdGVwKCkpKCJwcm9zcGVjdGl2ZVN0ZXBBeGlzUG9zaXRpb24iLGkuZ2V0QXhpc1Bvc2l0aW9uRnJvbVByb3NwZWN0aXZlU3RlcCgpKSgiaGlnaGVzdFN0ZXAiLGkuZ2V0SGlnaGVzdFN0ZXAoKSkoImxvd2VzdFN0ZXAiLGkuZ2V0TG93ZXN0U3RlcCgpKSgicHJvc3BlY3RpdmVTdGVwIixpLnByb3NwZWN0aXZlU3RlcCkoImlzUHJvc3BlY3RpdmVGb2JGZWF0dXJlRW5hYmxlZCIsaS5pc1Byb3NwZWN0aXZlRm9iRmVhdHVyZUVuYWJsZWQpKCJjYXJkRm9iSGVscGVyIixpLmNhcmRGb2JIZWxwZXIpKCJzaG93RXh0ZW5kZWRMaW5lIiwhMCkpfSxkZXBlbmRlbmNpZXM6W0drXSxzdHlsZXM6WyJzY2FsYXItY2FyZC1mb2ItY29udHJvbGxlciAudGltZS1mb2Itd3JhcHBlcntoZWlnaHQ6MTAwJX0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIEFZZShuLHQpezEmbiYmTygwLCJtYXQtc3Bpbm5lciIsMjUpfWZ1bmN0aW9uIElZZShuLHQpezEmbiYmKF8oMCwidGgiKSxBKDEsIlNtb290aGVkIiksdigpKX1mdW5jdGlvbiBQWWUobix0KXtpZigxJm4mJihzbigwKSxPKDEsInRiLWV4cGVyaW1lbnQtYWxpYXMiLDMxKSxBKDIsIi8iKSxhbigpKSwyJm4pe2xldCBlPVMoKS4kaW1wbGljaXQ7QygxKSx5KCJhbGlhcyIsZS5tZXRhZGF0YS5hbGlhcyl9fWZ1bmN0aW9uIFJZZShuLHQpe2lmKDEmbiYmKF8oMCwidGQiKSxBKDEpLHYoKSksMiZuKXtsZXQgZT1TKCkuJGltcGxpY2l0LGk9UygyKTtDKDEpLGplKCIgIixpLnZhbHVlRm9ybWF0dGVyLmZvcm1hdFNob3J0KGUuZGF0YVBvaW50LnkpLCIgIil9fWZ1bmN0aW9uIE9ZZShuLHQpe2lmKDEmbiYmKHNuKDApLF8oMSwidHIiLDI5KSgyLCJ0ZCIsMzApLE8oMywic3BhbiIpLHYoKSxfKDQsInRkIiwyKSxFKDUsUFllLDMsMSwibmctY29udGFpbmVyIiwyMiksQSg2KSx2KCksRSg3LFJZZSwyLDEsInRkIiwyMiksXyg4LCJ0ZCIpLEEoOSksdigpLF8oMTAsInRkIiksQSgxMSksdigpLF8oMTIsInRkIiksQSgxMyksQigxNCwiZGF0ZSIpLHYoKSxfKDE1LCJ0ZCIpLEEoMTYpLHYoKSgpLGFuKCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKDIpO0MoMSksZXQoImNsb3Nlc3QiLGUubWV0YWRhdGEuY2xvc2VzdCksQygyKSxQdCgiYmFja2dyb3VuZC1jb2xvciIsZS5tZXRhZGF0YS5jb2xvciksQygyKSx5KCJuZ0lmIixlLm1ldGFkYXRhLmFsaWFzKSxDKDEpLGplKCIiLGUubWV0YWRhdGEuZGlzcGxheU5hbWUsIiAiKSxDKDEpLHkoIm5nSWYiLGkuc21vb3RoaW5nRW5hYmxlZCksQygyKSx5dChpLnZhbHVlRm9ybWF0dGVyLmZvcm1hdFNob3J0KGUuZGF0YVBvaW50LnZhbHVlKSksQygyKSx5dChpLnN0ZXBGb3JtYXR0ZXIuZm9ybWF0U2hvcnQoZS5kYXRhUG9pbnQuc3RlcCkpLEMoMikseXQoSmYoMTQsMTEsZS5kYXRhUG9pbnQud2FsbFRpbWUsInNob3J0IikpLEMoMyksamUoIiAiLGkucmVsYXRpdmVYRm9ybWF0dGVyLmZvcm1hdFJlYWRhYmxlKGUuZGF0YVBvaW50LnJlbGF0aXZlVGltZUluTXMpLCIgIil9fWZ1bmN0aW9uIGtZZShuLHQpe2lmKDEmbiYmKF8oMCwidGFibGUiLDI2KSgxLCJ0aGVhZCIpKDIsInRyIiksTygzLCJ0aCIsMjcpLF8oNCwidGgiKSxBKDUsIlJ1biIpLHYoKSxFKDYsSVllLDIsMCwidGgiLDIyKSxfKDcsInRoIiksQSg4LCJWYWx1ZSIpLHYoKSxfKDksInRoIiksQSgxMCwiU3RlcCIpLHYoKSxfKDExLCJ0aCIpLEEoMTIsIlRpbWUiKSx2KCksXygxMywidGgiKSxBKDE0LCJSZWxhdGl2ZSIpLHYoKSgpKCksXygxNSwidGJvZHkiKSxFKDE2LE9ZZSwxNywxNCwibmctY29udGFpbmVyIiwyOCksdigpKCkpLDImbil7bGV0IGU9dC5kYXRhLGk9dC5jdXJzb3JMb2NhdGlvbkluRGF0YUNvb3JkLHI9dC5jdXJzb3JMb2NhdGlvbixvPVMoKTtDKDYpLHkoIm5nSWYiLG8uc21vb3RoaW5nRW5hYmxlZCksQygxMCkseSgibmdGb3JPZiIsby5nZXRDdXJzb3JBd2FyZVRvb2x0aXBEYXRhKGUsaSxyKSkoIm5nRm9yVHJhY2tCeSIsby50cmFja0J5VG9vbHRpcERhdHVtKX19ZnVuY3Rpb24gRlllKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO3NuKDApLF8oMSwiZGl2IiwzMikoMiwic2NhbGFyLWNhcmQtZGF0YS10YWJsZSIsMzMpLFAoInNvcnREYXRhQnkiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkuc29ydERhdGFCeShyKSl9KSgib3JkZXJDb2x1bW5zIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLnJlb3JkZXJDb2x1bW5IZWFkZXJzLmVtaXQocikpfSksdigpKCksYW4oKX1pZigyJm4pe2xldCBlPVMoKTtDKDIpLHkoImNoYXJ0TWV0YWRhdGFNYXAiLGUuY2hhcnRNZXRhZGF0YU1hcCkoImRhdGFTZXJpZXMiLGUuZGF0YVNlcmllcykoInN0ZXBPckxpbmtlZFRpbWVTZWxlY3Rpb24iLGUuc3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbikoImNvbHVtbkhlYWRlcnMiLGUuY29sdW1uSGVhZGVycykoInNvcnRpbmdJbmZvIixlLnNvcnRpbmdJbmZvKSgiY29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQiLGUuY29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQpKCJzbW9vdGhpbmdFbmFibGVkIixlLnNtb290aGluZ0VuYWJsZWQpfX12YXIgTlllPWZ1bmN0aW9uKG4pe3JldHVyblswLG5dfSxMWWU9ZnVuY3Rpb24oKXtyZXR1cm57Im91dC1vZi1zZWxlY3RlZC10aW1lIjohMCxlbmQ6ITAscmFuZ2U6ITB9fTtmdW5jdGlvbiBCWWUobix0KXtpZigxJm4mJk8oMCwiZGl2IiwzNCksMiZuKXtsZXQgZT1TKDIpLGk9ZS52aWV3RXh0ZW50LHI9ZS5kb21EaW1lbnNpb24sbz1lLnhTY2FsZSxzPVMoKTtQdCgibGVmdCIsby5mb3J3YXJkKGkueCxPbigzLE5ZZSxyLndpZHRoKSxudWxsPT1zLnN0ZXBPckxpbmtlZFRpbWVTZWxlY3Rpb24uZW5kP251bGw6cy5zdGVwT3JMaW5rZWRUaW1lU2VsZWN0aW9uLmVuZC5zdGVwKSsicHgiKSx5KCJuZ0NsYXNzIixRcCg1LExZZSkpfX12YXIgVlllPWZ1bmN0aW9uKG4pe3JldHVybltuLDBdfSxIWWU9ZnVuY3Rpb24obil7cmV0dXJueyJvdXQtb2Ytc2VsZWN0ZWQtdGltZSI6ITAsc3RhcnQ6ITAscmFuZ2U6bn19O2Z1bmN0aW9uIFVZZShuLHQpe2lmKDEmbiYmKHNuKDApLE8oMSwiZGl2IiwzNCksRSgyLEJZZSwxLDYsImRpdiIsMzUpLGFuKCkpLDImbil7bGV0IGU9UygpLGk9ZS52aWV3RXh0ZW50LHI9ZS5kb21EaW1lbnNpb24sbz1lLnhTY2FsZSxzPVMoKTtDKDEpLFB0KCJyaWdodCIsby5mb3J3YXJkKGkueCxPbig0LFZZZSxyLndpZHRoKSxzLnN0ZXBPckxpbmtlZFRpbWVTZWxlY3Rpb24uc3RhcnQuc3RlcCkrInB4IikseSgibmdDbGFzcyIsT24oNixIWWUsIShudWxsPT1zLnN0ZXBPckxpbmtlZFRpbWVTZWxlY3Rpb24uZW5kfHwhcy5zdGVwT3JMaW5rZWRUaW1lU2VsZWN0aW9uLmVuZC5zdGVwKSkpLEMoMSkseSgibmdJZiIsbnVsbD09cy5zdGVwT3JMaW5rZWRUaW1lU2VsZWN0aW9uLmVuZD9udWxsOnMuc3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbi5lbmQuc3RlcCl9fWZ1bmN0aW9uIHpZZShuLHQpezEmbiYmRSgwLFVZZSwzLDgsIm5nLWNvbnRhaW5lciIsMjIpLDImbiYmeSgibmdJZiIsUygpLnN0ZXBPckxpbmtlZFRpbWVTZWxlY3Rpb24pfWZ1bmN0aW9uIGpZZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtzbigwKSxfKDEsInNjYWxhci1jYXJkLWZvYi1jb250cm9sbGVyIiwzNiksUCgib25UaW1lU2VsZWN0aW9uQ2hhbmdlZCIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoMikub25UaW1lU2VsZWN0aW9uQ2hhbmdlZC5lbWl0KHIpKX0pKCJvblRpbWVTZWxlY3Rpb25Ub2dnbGVkIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKDIpLm9uRm9iUmVtb3ZlZCgpKX0pLHYoKSxhbigpfWlmKDImbil7bGV0IGU9UygpLGk9ZS5pbnRlcmFjdGlvblN0YXRlLHI9ZS54U2NhbGUsbz1lLnZpZXdFeHRlbnQscz1lLmRvbURpbWVuc2lvbixhPVMoKTtDKDEpLHkoImRpc2FibGVJbnRlcmFjdGlvbiIsIk5PTkUiIT09aSkoInRpbWVTZWxlY3Rpb24iLGEuc3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbikoInNjYWxlIixyKSgibWluTWF4SG9yaXpvbnRhbFZpZXdFeHRlbmQiLG8ueCkoIm1pbk1heFN0ZXAiLGEubWluTWF4U3RlcCkoImF4aXNTaXplIixzLndpZHRoKSgiaXNQcm9zcGVjdGl2ZUZvYkZlYXR1cmVFbmFibGVkIixhLmlzUHJvc3BlY3RpdmVGb2JGZWF0dXJlRW5hYmxlZCl9fWZ1bmN0aW9uIEdZZShuLHQpezEmbiYmRSgwLGpZZSwyLDcsIm5nLWNvbnRhaW5lciIsMjIpLDImbiYmeSgibmdJZiIsUygpLnNob3dGb2JDb250cm9sbGVyKCkpfXZhciBncGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMucmVmPWUsdGhpcy5kaWFsb2c9aSx0aGlzLkRhdGFMb2FkU3RhdGU9T2UsdGhpcy5SZW5kZXJlclR5cGU9ZHIsdGhpcy5TY2FsZVR5cGU9TnIsdGhpcy5pc1Byb3NwZWN0aXZlRm9iRmVhdHVyZUVuYWJsZWQ9ITEsdGhpcy5vbkZ1bGxTaXplVG9nZ2xlPW5ldyBHLHRoaXMub25QaW5DbGlja2VkPW5ldyBHLHRoaXMub25UaW1lU2VsZWN0aW9uQ2hhbmdlZD1uZXcgRyx0aGlzLm9uU3RlcFNlbGVjdG9yVG9nZ2xlZD1uZXcgRyx0aGlzLm9uRGF0YVRhYmxlU29ydGluZz1uZXcgRyx0aGlzLnJlb3JkZXJDb2x1bW5IZWFkZXJzPW5ldyBHLHRoaXMub25MaW5lQ2hhcnRab29tPW5ldyBHLHRoaXMuc29ydGluZ0luZm89e2hlYWRlcjpLdC5SVU4sb3JkZXI6eGwuQVNDRU5ESU5HfSx0aGlzLnlTY2FsZVR5cGU9TnIuTElORUFSLHRoaXMuaXNWaWV3Qm94T3ZlcnJpZGRlbj0hMSx0aGlzLnJlbGF0aXZlWEZvcm1hdHRlcj1TUyx0aGlzLnZhbHVlRm9ybWF0dGVyPXlwLHRoaXMuc3RlcEZvcm1hdHRlcj1NZH10b2dnbGVZU2NhbGVUeXBlKCl7dGhpcy55U2NhbGVUeXBlPXRoaXMueVNjYWxlVHlwZT09PU5yLkxJTkVBUj9Oci5MT0cxMDpOci5MSU5FQVJ9c29ydERhdGFCeShlKXt0aGlzLnNvcnRpbmdJbmZvPWUsdGhpcy5vbkRhdGFUYWJsZVNvcnRpbmcuZW1pdChlKX1yZXNldERvbWFpbigpe3RoaXMubGluZUNoYXJ0JiZ0aGlzLmxpbmVDaGFydC52aWV3Qm94UmVzZXQoKX10cmFja0J5VG9vbHRpcERhdHVtKGUsaSl7cmV0dXJuIGkuaWR9Z2V0Q3VzdG9tWEZvcm1hdHRlcigpe3N3aXRjaCh0aGlzLnhBeGlzVHlwZSl7Y2FzZSBKaS5SRUxBVElWRTpyZXR1cm4gU1M7Y2FzZSBKaS5TVEVQOnJldHVybiBqY2U7ZGVmYXVsdDpyZXR1cm59fWdldEN1cnNvckF3YXJlVG9vbHRpcERhdGEoZSxpLHIpe2xldCBvPWUubWFwKGw9Pih7Li4ubCxtZXRhZGF0YTp7Li4ubC5tZXRhZGF0YSxjbG9zZXN0OiExLGRpc3RUb0N1cnNvclBpeGVsczpNYXRoLmh5cG90KGwuZG9tUG9pbnQueC1yLngsbC5kb21Qb2ludC55LXIueSksZGlzdFRvQ3Vyc29yWDpsLmRhdGFQb2ludC54LWkueCxkaXN0VG9DdXJzb3JZOmwuZGF0YVBvaW50LnktaS55fX0pKSxzPTEvMCxhPTA7Zm9yKGxldCBsPTA7bDxvLmxlbmd0aDtsKyspcz5vW2xdLm1ldGFkYXRhLmRpc3RUb0N1cnNvclBpeGVscyYmKHM9b1tsXS5tZXRhZGF0YS5kaXN0VG9DdXJzb3JQaXhlbHMsYT1sKTtzd2l0Y2goby5sZW5ndGgmJihvW2FdLm1ldGFkYXRhLmNsb3Nlc3Q9ITApLHRoaXMudG9vbHRpcFNvcnQpe2Nhc2UgT28uQVNDRU5ESU5HOnJldHVybiBvLnNvcnQoKGwsYyk9PmwuZGF0YVBvaW50LnktYy5kYXRhUG9pbnQueSk7Y2FzZSBPby5ERVNDRU5ESU5HOnJldHVybiBvLnNvcnQoKGwsYyk9PmMuZGF0YVBvaW50LnktbC5kYXRhUG9pbnQueSk7Y2FzZSBPby5ORUFSRVNUOnJldHVybiBvLnNvcnQoKGwsYyk9PmwubWV0YWRhdGEuZGlzdFRvQ3Vyc29yUGl4ZWxzLWMubWV0YWRhdGEuZGlzdFRvQ3Vyc29yUGl4ZWxzKTtjYXNlIE9vLk5FQVJFU1RfWTpyZXR1cm4gby5zb3J0KChsLGMpPT5sLm1ldGFkYXRhLmRpc3RUb0N1cnNvclktYy5tZXRhZGF0YS5kaXN0VG9DdXJzb3JZKTtjYXNlIE9vLkRFRkFVTFQ6Y2FzZSBPby5BTFBIQUJFVElDQUw6cmV0dXJuIG8uc29ydCgobCxjKT0+bC5tZXRhZGF0YS5kaXNwbGF5TmFtZTxjLm1ldGFkYXRhLmRpc3BsYXlOYW1lPy0xOmwubWV0YWRhdGEuZGlzcGxheU5hbWU+Yy5tZXRhZGF0YS5kaXNwbGF5TmFtZT8xOjApfX1vcGVuRGF0YURvd25sb2FkRGlhbG9nKCl7dGhpcy5kaWFsb2cub3Blbih0aGlzLkRhdGFEb3dubG9hZENvbXBvbmVudCx7ZGF0YTp7Y2FyZElkOnRoaXMuY2FyZElkfX0pfW9uRm9iUmVtb3ZlZCgpe3RoaXMub25TdGVwU2VsZWN0b3JUb2dnbGVkLmVtaXQoYmwuRk9CX0RFU0VMRUNUKX1zaG93RGF0YVRhYmxlKCl7cmV0dXJuIHRoaXMueEF4aXNUeXBlPT09SmkuU1RFUCYmbnVsbCE9PXRoaXMuc3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbn1zaG93Rm9iQ29udHJvbGxlcigpe3JldHVybiB0aGlzLnhBeGlzVHlwZT09PUppLlNURVAmJihudWxsIT09dGhpcy5zdGVwT3JMaW5rZWRUaW1lU2VsZWN0aW9ufHx0aGlzLmlzUHJvc3BlY3RpdmVGb2JGZWF0dXJlRW5hYmxlZCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0odmwpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJzY2FsYXItY2FyZC1jb21wb25lbnQiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiZvdChqayw1KSwyJmUpe2xldCByO05lKHI9TGUoKSkmJihpLmxpbmVDaGFydD1yLmZpcnN0KX19LGlucHV0czp7Y2FyZElkOiJjYXJkSWQiLGNoYXJ0TWV0YWRhdGFNYXA6ImNoYXJ0TWV0YWRhdGFNYXAiLERhdGFEb3dubG9hZENvbXBvbmVudDoiRGF0YURvd25sb2FkQ29tcG9uZW50IixkYXRhU2VyaWVzOiJkYXRhU2VyaWVzIixpZ25vcmVPdXRsaWVyczoiaWdub3JlT3V0bGllcnMiLGlzQ2FyZFZpc2libGU6ImlzQ2FyZFZpc2libGUiLGlzUGlubmVkOiJpc1Bpbm5lZCIsbG9hZFN0YXRlOiJsb2FkU3RhdGUiLHNob3dGdWxsU2l6ZToic2hvd0Z1bGxTaXplIixzbW9vdGhpbmdFbmFibGVkOiJzbW9vdGhpbmdFbmFibGVkIix0YWc6InRhZyIsdGl0bGU6InRpdGxlIix0b29sdGlwU29ydDoidG9vbHRpcFNvcnQiLHhBeGlzVHlwZToieEF4aXNUeXBlIix4U2NhbGVUeXBlOiJ4U2NhbGVUeXBlIix1c2VEYXJrTW9kZToidXNlRGFya01vZGUiLGZvcmNlU3ZnOiJmb3JjZVN2ZyIsY29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQ6ImNvbHVtbkN1c3RvbWl6YXRpb25FbmFibGVkIixsaW5rZWRUaW1lU2VsZWN0aW9uOiJsaW5rZWRUaW1lU2VsZWN0aW9uIixzdGVwT3JMaW5rZWRUaW1lU2VsZWN0aW9uOiJzdGVwT3JMaW5rZWRUaW1lU2VsZWN0aW9uIixpc1Byb3NwZWN0aXZlRm9iRmVhdHVyZUVuYWJsZWQ6ImlzUHJvc3BlY3RpdmVGb2JGZWF0dXJlRW5hYmxlZCIsbWluTWF4U3RlcDoibWluTWF4U3RlcCIsY29sdW1uSGVhZGVyczoiY29sdW1uSGVhZGVycyJ9LG91dHB1dHM6e29uRnVsbFNpemVUb2dnbGU6Im9uRnVsbFNpemVUb2dnbGUiLG9uUGluQ2xpY2tlZDoib25QaW5DbGlja2VkIixvblRpbWVTZWxlY3Rpb25DaGFuZ2VkOiJvblRpbWVTZWxlY3Rpb25DaGFuZ2VkIixvblN0ZXBTZWxlY3RvclRvZ2dsZWQ6Im9uU3RlcFNlbGVjdG9yVG9nZ2xlZCIsb25EYXRhVGFibGVTb3J0aW5nOiJvbkRhdGFUYWJsZVNvcnRpbmciLHJlb3JkZXJDb2x1bW5IZWFkZXJzOiJyZW9yZGVyQ29sdW1uSGVhZGVycyIsb25MaW5lQ2hhcnRab29tOiJvbkxpbmVDaGFydFpvb20ifSxkZWNsczozNix2YXJzOjI3LGNvbnN0czpmdW5jdGlvbigpe2xldCB0LGUsaSxyLG87cmV0dXJuIHQ9JGxvY2FsaXplYDpBIGJ1dHRvbiB0aGF0IHJlc2V0cyBsaW5lIGNoYXJ0IGRvbWFpbiB0byB0aGUgZGF0YeKQn2U2OGE1NTI5NDFhYjQyN2E5OWU3NDM3ZTA4NDQzZjMwYWM3MWNjZDbikJ8zODMwNjQ2NTIxMDU4MjY4NTU4OkZpdCBsaW5lIGNoYXJ0IGRvbWFpbnMgdG8gZGF0YWAsZT0kbG9jYWxpemVgOkEgYnV0dG9uIHRvIHBpbiBhIGNhcmQu4pCfZTY2NWRjNzEyYmQ1ZjE4ZDRkZmEzYTI5ZTEyNWQ1NjVjYzUxZTJmNuKQnzcyODQ2MDY0MjYyMzQzNzUzNDQ6UGluIGNhcmRgLGk9JGxvY2FsaXplYDpBIGJ1dHRvbiBvbiBsaW5lIGNoYXJ0IHRoYXQgdG9nZ2xlcyBmdWxsIHNpemUgbW9kZS7ikJ9mYzhmNzY3ZDBiOWY5MzAxODdhMWJhZTM0NDc3YWQyODczNmVjZTMz4pCfOTE1NzIxNTYzNjM4OTI2NTk3OlRvZ2dsZSBmdWxsIHNpemUgbW9kZWAscj0kbG9jYWxpemVgOkFuIG92ZXJmbG93IG1lbnUgYnV0dG9uIHRoYXQgb3BlbnMgbW9yZSBsaW5lIGNoYXJ0IG9wdGlvbnPikJ9iMjYwZmFiOTQ2YTMwNzdjZTIwZmQyOGUzMzY5NzlmNTg2NzIwZThk4pCfODc4MDUzNzQwMjEwMzM2NDM1Ok1vcmUgbGluZSBjaGFydCBvcHRpb25zYCxvPSRsb2NhbGl6ZWA6QSBidXR0b24gdGhhdCB0b2dnbGVzIGxvZyBzY2FsZSBvbiB5LWF4aXMgb24gYSBsaW5lIGNoYXJ04pCfZmU5MWY5NmFiOWIzYmFjYTVhNDg5MTNmMmIwZmFlODQ0ODNkOTNlM+KQnzMzNzQ2NDU2MjA2Mzg4ODM5MjY6VG9nZ2xlIFktYXhpcyBsb2cgc2NhbGUgb24gbGluZSBjaGFydGAsW1sxLCJhbHdheXMtdmlzaWJsZSJdLFsxLCJoZWFkaW5nIl0sWzEsIm5hbWUiXSxbMSwidGFnIiwzLCJ0aXRsZSIsInZhbHVlIl0sWzMsImlzQ2xpcHBlZCJdLFsxLCJjb250cm9scyJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsdCwzLCJkaXNhYmxlZCIsInRpdGxlIiwiY2xpY2siXSxbInN2Z0ljb24iLCJzZXR0aW5nc19vdmVyc2Nhbl8yNHB4Il0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJhcmlhLWxhYmVsIixlLDEsInBpbi1idXR0b24iLDMsImNsaWNrIl0sWzMsInN2Z0ljb24iXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsImFyaWEtbGFiZWwiLGksInRpdGxlIiwiVG9nZ2xlIGZ1bGwgc2l6ZSBtb2RlIiwzLCJjbGljayJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsciwidGl0bGUiLCJNb3JlIGxpbmUgY2hhcnQgb3B0aW9ucyIsMywibWF0TWVudVRyaWdnZXJGb3IiXSxbInN2Z0ljb24iLCJtb3JlX3ZlcnRfMjRweCJdLFsibWVudSIsIm1hdE1lbnUiXSxbIm1hdC1tZW51LWl0ZW0iLCIiLCJhcmlhLWxhYmVsIixvLDMsImNsaWNrIl0sWyJzdmdJY29uIiwibGluZV93ZWlnaHRfMjRweCJdLFsibWF0LW1lbnUtaXRlbSIsIiIsImFyaWEtbGFiZWwiLCJPcGVuIGRpYWxvZyB0byBkb3dubG9hZCBkYXRhIiwzLCJjbGljayJdLFsic3ZnSWNvbiIsImdldF9hcHBfMjRweCJdLFsxLCJjaGFydC1jb250YWluZXIiXSxbImRpYW1ldGVyIiwiMTgiLDQsIm5nSWYiXSxbMywiZGlzYWJsZVVwZGF0ZSIsInByZWZlcnJlZFJlbmRlcmVyVHlwZSIsInNlcmllc0RhdGEiLCJzZXJpZXNNZXRhZGF0YU1hcCIsInhTY2FsZVR5cGUiLCJ5U2NhbGVUeXBlIiwiY3VzdG9tWEZvcm1hdHRlciIsImlnbm9yZVlPdXRsaWVycyIsInRvb2x0aXBUZW1wbGF0ZSIsInVzZURhcmtNb2RlIiwiY3VzdG9tVmlzVGVtcGxhdGUiLCJjdXN0b21DaGFydE92ZXJsYXlUZW1wbGF0ZSIsIm9uVmlld0JveE92ZXJyaWRkZW4iLCJ2aWV3Qm94Q2hhbmdlZCJdLFsidG9vbHRpcCIsIiJdLFs0LCJuZ0lmIl0sWyJsaW5lQ2hhcnRDdXN0b21WaXMiLCIiXSxbImxpbmVDaGFydEN1c3RvbVhBeGlzVmlzIiwiIl0sWyJkaWFtZXRlciIsIjE4Il0sWzEsInRvb2x0aXAiXSxbMSwiY2lyY2xlLWhlYWRlciJdLFs0LCJuZ0ZvciIsIm5nRm9yT2YiLCJuZ0ZvclRyYWNrQnkiXSxbMSwidG9vbHRpcC1yb3ciXSxbMSwidG9vbHRpcC1yb3ctY2lyY2xlIl0sWzMsImFsaWFzIl0sWzEsImRhdGEtdGFibGUtY29udGFpbmVyIl0sWzMsImNoYXJ0TWV0YWRhdGFNYXAiLCJkYXRhU2VyaWVzIiwic3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbiIsImNvbHVtbkhlYWRlcnMiLCJzb3J0aW5nSW5mbyIsImNvbHVtbkN1c3RvbWl6YXRpb25FbmFibGVkIiwic21vb3RoaW5nRW5hYmxlZCIsInNvcnREYXRhQnkiLCJvcmRlckNvbHVtbnMiXSxbMywibmdDbGFzcyJdLFszLCJuZ0NsYXNzIiwibGVmdCIsNCwibmdJZiJdLFszLCJkaXNhYmxlSW50ZXJhY3Rpb24iLCJ0aW1lU2VsZWN0aW9uIiwic2NhbGUiLCJtaW5NYXhIb3Jpem9udGFsVmlld0V4dGVuZCIsIm1pbk1heFN0ZXAiLCJheGlzU2l6ZSIsImlzUHJvc3BlY3RpdmVGb2JGZWF0dXJlRW5hYmxlZCIsIm9uVGltZVNlbGVjdGlvbkNoYW5nZWQiLCJvblRpbWVTZWxlY3Rpb25Ub2dnbGVkIl1dfSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKF8oMCwiZGl2IiwwKSgxLCJkaXYiLDEpKDIsInNwYW4iLDIpLE8oMywidGItdHJ1bmNhdGVkLXBhdGgiLDMpKDQsInZpcy1saW5rZWQtdGltZS1zZWxlY3Rpb24td2FybmluZyIsNCksdigpLF8oNSwic3BhbiIsNSkoNiwiYnV0dG9uIiw2KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5yZXNldERvbWFpbigpfSksQig3LCJhc3luYyIpLEIoOCwiYXN5bmMiKSxPKDksIm1hdC1pY29uIiw3KSx2KCksXygxMCwiYnV0dG9uIiw4KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblBpbkNsaWNrZWQuZW1pdCghaS5pc1Bpbm5lZCl9KSxPKDExLCJtYXQtaWNvbiIsOSksdigpLF8oMTIsImJ1dHRvbiIsMTApLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uRnVsbFNpemVUb2dnbGUuZW1pdCgpfSksTygxMywibWF0LWljb24iLDkpLHYoKSxfKDE0LCJidXR0b24iLDExKSxPKDE1LCJtYXQtaWNvbiIsMTIpLHYoKSxfKDE2LCJtYXQtbWVudSIsbnVsbCwxMykoMTgsImJ1dHRvbiIsMTQpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLnRvZ2dsZVlTY2FsZVR5cGUoKX0pLE8oMTksIm1hdC1pY29uIiwxNSksXygyMCwic3BhbiIpLEEoMjEsIlRvZ2dsZSBZLWF4aXMgbG9nIHNjYWxlIiksdigpKCksXygyMiwiYnV0dG9uIiwxNiksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkub3BlbkRhdGFEb3dubG9hZERpYWxvZygpfSksTygyMywibWF0LWljb24iLDE3KSxfKDI0LCJzcGFuIiksQSgyNSwiRG93bmxvYWQgZGF0YSIpLHYoKSgpKCkoKSgpLF8oMjYsImRpdiIsMTgpLEUoMjcsQVllLDEsMCwibWF0LXNwaW5uZXIiLDE5KSxfKDI4LCJsaW5lLWNoYXJ0IiwyMCksUCgib25WaWV3Qm94T3ZlcnJpZGRlbiIsZnVuY3Rpb24obyl7cmV0dXJuIGkuaXNWaWV3Qm94T3ZlcnJpZGRlbj1vfSkoInZpZXdCb3hDaGFuZ2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vbkxpbmVDaGFydFpvb20uZW1pdChvKX0pLHYoKSxFKDI5LGtZZSwxNywzLCJuZy10ZW1wbGF0ZSIsbnVsbCwyMSxxdCksdigpKCksRSgzMSxGWWUsMyw3LCJuZy1jb250YWluZXIiLDIyKSxFKDMyLHpZZSwxLDEsIm5nLXRlbXBsYXRlIixudWxsLDIzLHF0KSxFKDM0LEdZZSwxLDEsIm5nLXRlbXBsYXRlIixudWxsLDI0LHF0KSksMiZlKXtsZXQgcj0kZSgxNyksbz0kZSgzMCkscz0kZSgzMyksYT0kZSgzNSk7QygzKSxaaSgidGl0bGUiLGkudGFnKSxaaSgidmFsdWUiLGkudGl0bGUpLEMoMSkseSgiaXNDbGlwcGVkIixpLmxpbmtlZFRpbWVTZWxlY3Rpb24mJmkubGlua2VkVGltZVNlbGVjdGlvbi5jbGlwcGVkKSxDKDIpLHkoImRpc2FibGVkIiwhaS5saW5lQ2hhcnR8fCFVKDcsMjMsaS5saW5lQ2hhcnQuZ2V0SXNWaWV3Qm94T3ZlcnJpZGRlbigpKSkoInRpdGxlIixpLmxpbmVDaGFydCYmVSg4LDI1LGkubGluZUNoYXJ0LmdldElzVmlld0JveE92ZXJyaWRkZW4oKSk/IkxpbmUgY2hhcnQgaXMgYWxyZWFkeSBmaXR0ZWQgdG8gZGF0YS4gV2hlbiBkYXRhIHVwZGF0ZXMsIHRoZSBsaW5lIGNoYXJ0IHdpbGwgYXV0byBmaXQgdG8gaXRzIGRvbWFpbi4iOiJGaXQgbGluZSBjaGFydCBkb21haW5zIHRvIGRhdGEiKSxDKDQpLHplKCJ0aXRsZSIsaS5pc1Bpbm5lZD8iVW5waW4gY2FyZCI6IlBpbiBjYXJkIiksQygxKSx5KCJzdmdJY29uIixpLmlzUGlubmVkPyJrZWVwXzI0cHgiOiJrZWVwX291dGxpbmVfMjRweCIpLEMoMikseSgic3ZnSWNvbiIsaS5zaG93RnVsbFNpemU/ImZ1bGxzY3JlZW5fZXhpdF8yNHB4IjoiZnVsbHNjcmVlbl8yNHB4IiksQygxKSx5KCJtYXRNZW51VHJpZ2dlckZvciIsciksQygxMykseSgibmdJZiIsaS5sb2FkU3RhdGU9PT1pLkRhdGFMb2FkU3RhdGUuTE9BRElORyksQygxKSx5KCJkaXNhYmxlVXBkYXRlIiwhaS5pc0NhcmRWaXNpYmxlKSgicHJlZmVycmVkUmVuZGVyZXJUeXBlIixpLmZvcmNlU3ZnP2kuUmVuZGVyZXJUeXBlLlNWRzppLlJlbmRlcmVyVHlwZS5XRUJHTCkoInNlcmllc0RhdGEiLGkuZGF0YVNlcmllcykoInNlcmllc01ldGFkYXRhTWFwIixpLmNoYXJ0TWV0YWRhdGFNYXApKCJ4U2NhbGVUeXBlIixpLnhTY2FsZVR5cGUpKCJ5U2NhbGVUeXBlIixpLnlTY2FsZVR5cGUpKCJjdXN0b21YRm9ybWF0dGVyIixpLmdldEN1c3RvbVhGb3JtYXR0ZXIoKSkoImlnbm9yZVlPdXRsaWVycyIsaS5pZ25vcmVPdXRsaWVycykoInRvb2x0aXBUZW1wbGF0ZSIsbykoInVzZURhcmtNb2RlIixpLnVzZURhcmtNb2RlKSgiY3VzdG9tVmlzVGVtcGxhdGUiLHMpKCJjdXN0b21DaGFydE92ZXJsYXlUZW1wbGF0ZSIsYSksQygzKSx5KCJuZ0lmIixpLnNob3dEYXRhVGFibGUoKSl9fSxkZXBlbmRlbmNpZXM6W0ZuLGRuLEJlLGN5LGprLF9uLEd0LGhkLG51LGZkLEJvLHV5LGR5LHBwZSxtcGUsR2UsVV9dLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17ZGlzcGxheTpmbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjtib3gtc2l6aW5nOmJvcmRlci1ib3g7aGVpZ2h0OjEwMCU7b3ZlcmZsb3c6YXV0bztwYWRkaW5nOjE2cHg7cGFkZGluZy10b3A6NHB4fS5hbHdheXMtdmlzaWJsZVtfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpmbGV4O2ZsZXgtYmFzaXM6Mjk4cHg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2ZsZXgtZ3JvdzoxfS5oZWFkaW5nW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7ZGlzcGxheTpmbGV4O2ZvbnQtc2l6ZToxNHB4O2p1c3RpZnktY29udGVudDpzcGFjZS1iZXR3ZWVuO21hcmdpbi1ib3R0b206NHB4O3Bvc2l0aW9uOnJlbGF0aXZlfS5oZWFkaW5nW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5uYW1lW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7ZGlzcGxheTpncmlkO2dhcDo1cHg7Z3JpZC10ZW1wbGF0ZS1jb2x1bW5zOmF1dG8gYXV0b30uaGVhZGluZ1tfbmdjb250ZW50LSVDT01QJV0gICB2aXMtc2VsZWN0ZWQtdGltZS1jbGlwcGVkW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXNpemU6MS4yZW07bGluZS1oZWlnaHQ6MH0udGFnW19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdzpoaWRkZW59LnBpbi1idXR0b25bX25nY29udGVudC0lQ09NUCVdICAgbWF0LWljb25bX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxOHB4fS5jb250cm9sc1tfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTt3aGl0ZS1zcGFjZTpub3dyYXA7ZmxleC1zaHJpbms6MDttYXJnaW4tcmlnaHQ6LTEycHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmNvbnRyb2xzW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmNvbnRyb2xzW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX0uY2hhcnQtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4LWdyb3c6MX0uY2hhcnQtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXSAgIG1hdC1zcGlubmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtwb3NpdGlvbjphYnNvbHV0ZTtyaWdodDoxMXB4O3RvcDoxMXB4fS5jaGFydC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdICAgbGluZS1jaGFydFtfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpibG9jaztoZWlnaHQ6MTAwJX0udG9vbHRpcFtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLXNwYWNpbmc6NHB4O2ZvbnQtc2l6ZToxM3B4fS50b29sdGlwW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHRoW19uZ2NvbnRlbnQtJUNPTVAlXXt0ZXh0LWFsaWduOmxlZnR9LnRvb2x0aXBbX25nY29udGVudC0lQ09NUCVdICAgLnRvb2x0aXAtcm93W19uZ2NvbnRlbnQtJUNPTVAlXXt3aGl0ZS1zcGFjZTpub3dyYXB9LnRvb2x0aXBbX25nY29udGVudC0lQ09NUCVdICAgLnRvb2x0aXAtcm93LWNpcmNsZVtfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6aW5saW5lLWZsZXg7aGVpZ2h0OjEycHg7d2lkdGg6MTJweH0udG9vbHRpcFtfbmdjb250ZW50LSVDT01QJV0gICAudG9vbHRpcC1yb3ctY2lyY2xlW19uZ2NvbnRlbnQtJUNPTVAlXSA+IHNwYW5bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1yYWRpdXM6NTAlO2JvcmRlcjoxcHggc29saWQgcmdiYSgyNTUsMjU1LDI1NSwuNCk7ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjEwcHg7d2lkdGg6MTBweH0udG9vbHRpcFtfbmdjb250ZW50LSVDT01QJV0gICAuY2xvc2VzdFtfbmdjb250ZW50LSVDT01QJV0gICAudG9vbHRpcC1yb3ctY2lyY2xlW19uZ2NvbnRlbnQtJUNPTVAlXSA+IHNwYW5bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1jb2xvcjojZmZmO2JveC1zaGFkb3c6aW5zZXQgMCAwIDAgMXB4ICNmZmZ9Lm91dC1vZi1zZWxlY3RlZC10aW1lW19uZ2NvbnRlbnQtJUNPTVAlXXtoZWlnaHQ6MTAwJTtwb3NpdGlvbjphYnNvbHV0ZX0ub3V0LW9mLXNlbGVjdGVkLXRpbWUuc3RhcnRbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1yaWdodC13aWR0aDoycHg7bWFyZ2luLWxlZnQ6LTFweH0ub3V0LW9mLXNlbGVjdGVkLXRpbWUuc3RhcnQucmFuZ2VbX25nY29udGVudC0lQ09NUCVde2xlZnQ6MH0ub3V0LW9mLXNlbGVjdGVkLXRpbWUuZW5kW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItbGVmdC13aWR0aDoycHg7bWFyZ2luLXJpZ2h0Oi0xcHg7cmlnaHQ6MH0ub3V0LW9mLXNlbGVjdGVkLXRpbWUucmFuZ2VbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNSl9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLm91dC1vZi1zZWxlY3RlZC10aW1lLnJhbmdlW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLm91dC1vZi1zZWxlY3RlZC10aW1lLnJhbmdlW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjQpfS5kYXRhLXRhYmxlLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17d2lkdGg6MTAwJTtoZWlnaHQ6MTAwcHg7b3ZlcmZsb3c6YXV0b30iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLGtHPS0xLzAsRkc9MS8wO2Z1bmN0aW9uIHFZZShuLHQpe3JldHVybiBuLmxlbmd0aD09PXQubGVuZ3RoJiZuLmV2ZXJ5KChlLGkpPT57bGV0IHI9dFtpXSxvPWUucG9pbnRzLHM9ci5wb2ludHM7cmV0dXJuIGUucnVuSWQ9PT1yLnJ1bklkJiZvLmxlbmd0aD09PXMubGVuZ3RoJiZvLmV2ZXJ5KChhLGwpPT57bGV0IGM9c1tsXTtyZXR1cm4gYS54PT09Yy54JiZhLnk9PT1jLnl9KX0pfXZhciBfcGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5EYXRhRG93bmxvYWRDb21wb25lbnQ9b3NlLHRoaXMuZnVsbFdpZHRoQ2hhbmdlZD1uZXcgRyx0aGlzLmZ1bGxIZWlnaHRDaGFuZ2VkPW5ldyBHLHRoaXMucGluU3RhdGVDaGFuZ2VkPW5ldyBHLHRoaXMuaXNWaXNpYmxlPSExLHRoaXMuaXNQcm9zcGVjdGl2ZUZvYkZlYXR1cmVFbmFibGVkJD10aGlzLnN0b3JlLnNlbGVjdChEJCksdGhpcy5taW5NYXhTdGVwcyQ9bmV3IGhyKHttaW5TdGVwOmtHLG1heFN0ZXA6Rkd9KSx0aGlzLmxpbmVDaGFydFpvb20kPW5ldyBocih7bWluU3RlcDprRyxtYXhTdGVwOkZHfSksdGhpcy5zdGVwU2VsZWN0b3JUaW1lU2VsZWN0aW9uJD1uZXcgaHIobnVsbCksdGhpcy51c2VEYXJrTW9kZSQ9dGhpcy5zdG9yZS5zZWxlY3QoUXUpLHRoaXMuaWdub3JlT3V0bGllcnMkPXRoaXMuc3RvcmUuc2VsZWN0KGh2KSx0aGlzLnRvb2x0aXBTb3J0JD10aGlzLnN0b3JlLnNlbGVjdChwdiksdGhpcy54QXhpc1R5cGUkPXRoaXMuc3RvcmUuc2VsZWN0KHRkKSx0aGlzLmZvcmNlU3ZnJD10aGlzLnN0b3JlLnNlbGVjdCh3JCksdGhpcy5jb2x1bW5DdXN0b21pemF0aW9uRW5hYmxlZCQ9dGhpcy5zdG9yZS5zZWxlY3QocUEpLHRoaXMueFNjYWxlVHlwZSQ9dGhpcy5zdG9yZS5zZWxlY3QodGQpLnBpcGUoTChpPT57c3dpdGNoKGkpe2Nhc2UgSmkuU1RFUDpjYXNlIEppLlJFTEFUSVZFOnJldHVybiBOci5MSU5FQVI7Y2FzZSBKaS5XQUxMX1RJTUU6cmV0dXJuIE5yLlRJTUU7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgeEF4aXNUeXBlIGZvciBsaW5lIGNoYXJ0LiAke2l9YCl9fSkpLHRoaXMuc2NhbGFyU21vb3RoaW5nJD10aGlzLnN0b3JlLnNlbGVjdChvcCksdGhpcy5zbW9vdGhpbmdFbmFibGVkJD10aGlzLnN0b3JlLnNlbGVjdChvcCkucGlwZShMKGk9Pmk+MCkpLHRoaXMuc2hvd0Z1bGxTaXplPSExLHRoaXMubmdVbnN1YnNjcmliZT1uZXcga2V9b25WaXNpYmlsaXR5Q2hhbmdlKHt2aXNpYmxlOmV9KXt0aGlzLmlzVmlzaWJsZT1lfWlzU2NhbGFyQ2FyZE1ldGFkYXRhKGUpe2xldHtwbHVnaW46aX09ZTtyZXR1cm4gaT09PXJpLlNDQUxBUlN9b25GdWxsU2l6ZVRvZ2dsZSgpe3RoaXMuc2hvd0Z1bGxTaXplPSF0aGlzLnNob3dGdWxsU2l6ZSx0aGlzLmZ1bGxXaWR0aENoYW5nZWQuZW1pdCh0aGlzLnNob3dGdWxsU2l6ZSksdGhpcy5mdWxsSGVpZ2h0Q2hhbmdlZC5lbWl0KHRoaXMuc2hvd0Z1bGxTaXplKX1uZ09uSW5pdCgpe2xldCBpPXRoaXMuc3RvcmUuc2VsZWN0KHRjLHRoaXMuY2FyZElkKS5waXBlKFllKGw9PiEhbCYmdGhpcy5pc1NjYWxhckNhcmRNZXRhZGF0YShsKSksTChsPT5sKSk7ZnVuY3Rpb24gcyhsKXtyZXR1cm4gSlNPTi5zdHJpbmdpZnkoWyJzbW9vdGhlZCIsbF0pfWxldCBhPXRoaXMuc3RvcmUuc2VsZWN0KHhoLHRoaXMuY2FyZElkKS5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSksWWUobD0+Qm9vbGVhbihsKSksTChsPT5sKSxNYSgxKSkucGlwZShmcih0aGlzLnN0b3JlLnNlbGVjdCh0ZCkpLEwoKFtsLGNdKT0+T2JqZWN0LmtleXMobCkubWFwKHA9Pih7cnVuSWQ6cCxwb2ludHM6dGhpcy5zdGVwU2VyaWVzVG9MaW5lU2VyaWVzKGxbcF0sYyl9KSkpLHlpKHFZZSkpLnBpcGUoZnIodGhpcy5zdG9yZS5zZWxlY3QoT0kpKSxzdCh0aGlzLm5nVW5zdWJzY3JpYmUpLEwoKFtsLGNdKT0+Yz9mdW5jdGlvbihuKXtsZXQgdD1bXTtmb3IobGV0IGUgb2Ygbil7bGV0IGk9W10scj1OdW1iZXIuaXNGaW5pdGUoZS5wb2ludHNbMF0/LngpP2UucG9pbnRzWzBdLng6LTEvMCxvPVtdO2ZvcihsZXQgcyBvZiBlLnBvaW50cylOdW1iZXIuaXNGaW5pdGUocy54KT8ocy54PHImJihpLnB1c2goe3Nlcmllc0lkOkpTT04uc3RyaW5naWZ5KFtlLnJ1bklkLGkubGVuZ3RoXSkscnVuSWQ6ZS5ydW5JZCxwb2ludHM6b30pLG89W10pLG8ucHVzaChzKSxyPXMueCk6by5wdXNoKHMpO2kucHVzaCh7c2VyaWVzSWQ6SlNPTi5zdHJpbmdpZnkoW2UucnVuSWQsaS5sZW5ndGhdKSxydW5JZDplLnJ1bklkLHBvaW50czpvfSk7Zm9yKGxldCBzPTA7czxpLmxlbmd0aDtzKyspdC5wdXNoKHsuLi5pW3NdLHBhcnRpdGlvbkluZGV4OnMscGFydGl0aW9uU2l6ZTppLmxlbmd0aH0pfXJldHVybiB0fShsKTpsLm1hcCh1PT4oey4uLnUsc2VyaWVzSWQ6dS5ydW5JZCxwYXJ0aXRpb25JbmRleDowLHBhcnRpdGlvblNpemU6MX0pKSksTChsPT5sLm1hcChjPT57bGV0IHU9Yy5wb2ludHNbMF0/LndhbGxUaW1lO3JldHVybnsuLi5jLHBvaW50czpjLnBvaW50cy5tYXAoZD0+KHsuLi5kLHJlbGF0aXZlVGltZUluTXM6ZC53YWxsVGltZS11fSkpfX0pKSxmcih0aGlzLnN0b3JlLnNlbGVjdCh0ZCkpLEwoKFtsLGNdKT0+bC5tYXAodT0+KHsuLi51LHBvaW50czp1LnBvaW50cy5tYXAoZD0+e2xldCBwO3N3aXRjaChjKXtjYXNlIEppLlJFTEFUSVZFOnA9ZC5yZWxhdGl2ZVRpbWVJbk1zO2JyZWFrO2Nhc2UgSmkuV0FMTF9USU1FOnA9ZC53YWxsVGltZTticmVhaztkZWZhdWx0OnA9ZC5zdGVwfXJldHVybnsuLi5kLHg6cH19KX0pKSksTWEoMSkpO0x0KFthLHRoaXMubGluZUNoYXJ0Wm9vbSRdKS5zdWJzY3JpYmUoKFtsLGNdKT0+e2xldCB1PWwubWFwKCh7cG9pbnRzOm19KT0+bS5tYXAoKHt4Onh9KT0+eCkpLmZsYXQoKSxkPTA9PT11Lmxlbmd0aD9rRzpNYXRoLm1pbiguLi51KSxwPTA9PT11Lmxlbmd0aD9GRzpNYXRoLm1heCguLi51KSxoPU1hdGgubWF4KGQsYy5taW5TdGVwKSxmPU1hdGgubWluKHAsYy5tYXhTdGVwKTt0aGlzLm1pbk1heFN0ZXBzJC5uZXh0KHttaW5TdGVwOmgsbWF4U3RlcDpmfSl9KSx0aGlzLmRhdGFTZXJpZXMkPWEucGlwZShmcih0aGlzLnN0b3JlLnNlbGVjdChvcCkpLHVpKChbbCxjXSk9PntsZXQgdT1sLm1hcCgoe3Nlcmllc0lkOmQscG9pbnRzOnB9KT0+KHtpZDpkLHBvaW50czpwfSkpO3JldHVybiBjPD0wP1h0KHUpOkVvKGFzeW5jIGZ1bmN0aW9uKG4sdCl7TnVtYmVyLmlzRmluaXRlKHQpfHwodD0wKSx0PU1hdGgubWF4KDAsTWF0aC5taW4odCwxKSk7bGV0IGU9W107Zm9yKGxldCBpIG9mIG4pe2xldCByPWkucG9pbnRzWzBdPy55O2lmKGkucG9pbnRzLmV2ZXJ5KGM9PmMueT09cikpe2UucHVzaChpKTtjb250aW51ZX1sZXQgcz1pLnBvaW50cy5sZW5ndGg+MD8wOk5hTixhPTAsbD1pLnBvaW50cy5tYXAoYz0+e2xldCB1PWMueTtpZihOdW1iZXIuaXNGaW5pdGUodSkpe3M9cyp0KygxLXQpKnUsYSsrO2xldCBkPTE9PT10PzE6MS1NYXRoLnBvdyh0LGEpO3JldHVybnt4OmMueCx5OnMvZH19cmV0dXJue3g6Yy54LHk6dX19KTtlLnB1c2goe2lkOmkuaWQscG9pbnRzOmx9KX1yZXR1cm4gZX0odSxjKSkucGlwZShMKGQ9PntsZXQgcD11Lm1hcCgoaCxmKT0+KHtpZDpzKGguaWQpLHBvaW50czpkW2ZdLnBvaW50cy5tYXAoKHt5Om19LHgpPT4oey4uLmgucG9pbnRzW3hdLHk6bX0pKX0pKTtyZXR1cm5bLi4udSwuLi5wXX0pKX0pLHpuKFtdKSksdGhpcy5saW5rZWRUaW1lU2VsZWN0aW9uJD1MdChbdGhpcy5taW5NYXhTdGVwcyQsdGhpcy5zdG9yZS5zZWxlY3QoWW0pLHRoaXMuc3RvcmUuc2VsZWN0KFhtKSx0aGlzLnN0b3JlLnNlbGVjdCh0ZCldKS5waXBlKEwoKFt7bWluU3RlcDpsLG1heFN0ZXA6Y30sdSxkLHBdKT0+dSYmcD09PUppLlNURVAmJmQ/UWgoZCxsLGMpOm51bGwpKSx0aGlzLnN0ZXBPckxpbmtlZFRpbWVTZWxlY3Rpb24kPUx0KFt0aGlzLnN0ZXBTZWxlY3RvclRpbWVTZWxlY3Rpb24kLHRoaXMubGlua2VkVGltZVNlbGVjdGlvbiQsdGhpcy5zdG9yZS5zZWxlY3QoWW0pXSkucGlwZShMKChbbCxjLHVdKT0+dSYmYz97c3RhcnQ6e3N0ZXA6Yy5zdGFydFN0ZXB9LGVuZDpudWxsPT09Yy5lbmRTdGVwP251bGw6e3N0ZXA6Yy5lbmRTdGVwfX06bCkpLHRoaXMuY29sdW1uSGVhZGVycyQ9THQoW3RoaXMuc3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbiQsdGhpcy5zdG9yZS5zZWxlY3QoQkkpLHRoaXMuc3RvcmUuc2VsZWN0KFZJKV0pLnBpcGUoTCgoW2wsYyx1XSk9Pm51bGw9PT1sfHxudWxsPT09bC5lbmQ/Yzp1KSksdGhpcy5jaGFydE1ldGFkYXRhTWFwJD1hLnBpcGUodWkobD0+THQobC5tYXAoYz0+dGhpcy5nZXRSdW5EaXNwbGF5TmFtZUFuZEFsaWFzKGMucnVuSWQpLnBpcGUoTCh1PT4oey4uLmMsLi4udX0pKSkpKSksZnIodGhpcy5zdG9yZS5zZWxlY3Qob28pLHRoaXMuc3RvcmUuc2VsZWN0KG5jKSx0aGlzLnN0b3JlLnNlbGVjdChvcCkpLEhyKDApLEwoKFtsLGMsdSxkXSk9PntsZXQgcD17fSxoPWQ+MDtmb3IobGV0IGYgb2YgbCl7bGV0e3Nlcmllc0lkOm0scnVuSWQ6eCxkaXNwbGF5TmFtZTpnLGFsaWFzOmIscGFydGl0aW9uSW5kZXg6RCxwYXJ0aXRpb25TaXplOlR9PWY7cFttXT17dHlwZTpzeS5PUklHSU5BTCxpZDptLGFsaWFzOmIsZGlzcGxheU5hbWU6VD4xP2Ake2d9OiAke0R9YDpnLHZpc2libGU6Qm9vbGVhbihjJiZjLmdldCh4KSksY29sb3I6dVt4XT8/IiNmZmYiLGF1eDohMSxvcGFjaXR5OjF9fWlmKCFoKXJldHVybiBwO2ZvcihsZXRbZixtXW9mIE9iamVjdC5lbnRyaWVzKHApKXtsZXQgeD1zKGYpO3BbeF09ey4uLm0saWQ6eCx0eXBlOnN5LkRFUklWRUQsYXV4OiExLG9yaWdpbmFsU2VyaWVzSWQ6Zn0sbS5hdXg9ITAsbS5vcGFjaXR5PS4yNX1yZXR1cm4gcH0pLHpuKHt9KSksdGhpcy5sb2FkU3RhdGUkPXRoaXMuc3RvcmUuc2VsZWN0KGJoLHRoaXMuY2FyZElkKSx0aGlzLnRhZyQ9aS5waXBlKEwobD0+bC50YWcpKSx0aGlzLnRpdGxlJD10aGlzLnRhZyQucGlwZShMKGw9Pmx5KGwsdGhpcy5ncm91cE5hbWUpKSksdGhpcy5pc1Bpbm5lZCQ9dGhpcy5zdG9yZS5zZWxlY3QoQ2gsdGhpcy5jYXJkSWQpLHRoaXMuc3RvcmUuc2VsZWN0KGZ2KS5waXBlKFd0KHRoaXMubWluTWF4U3RlcHMkKSxzdCh0aGlzLm5nVW5zdWJzY3JpYmUpKS5zdWJzY3JpYmUoKFtsLGNdKT0+e2w/bnVsbCE9PXRoaXMuc3RlcFNlbGVjdG9yVGltZVNlbGVjdGlvbiQuZ2V0VmFsdWUoKXx8dGhpcy5zdGVwU2VsZWN0b3JUaW1lU2VsZWN0aW9uJC5uZXh0KHtzdGFydDp7c3RlcDpjLm1pblN0ZXB9LGVuZDpudWxsfSk6dGhpcy5zdGVwU2VsZWN0b3JUaW1lU2VsZWN0aW9uJC5uZXh0KG51bGwpfSksdGhpcy5zdG9yZS5zZWxlY3QobXYpLnBpcGUoV3QodGhpcy5taW5NYXhTdGVwcyQpLHN0KHRoaXMubmdVbnN1YnNjcmliZSkpLnN1YnNjcmliZSgoW2wsY10pPT57bGV0IHU9dGhpcy5zdGVwU2VsZWN0b3JUaW1lU2VsZWN0aW9uJC5nZXRWYWx1ZSgpO251bGwhPT11P2x8fG51bGw9PT11LmVuZD9sJiZudWxsPT09dS5lbmQmJnRoaXMuc3RlcFNlbGVjdG9yVGltZVNlbGVjdGlvbiQubmV4dCh7c3RhcnQ6dS5zdGFydCxlbmQ6e3N0ZXA6Yy5tYXhTdGVwfX0pOnRoaXMuc3RlcFNlbGVjdG9yVGltZVNlbGVjdGlvbiQubmV4dCh7c3RhcnQ6dS5zdGFydCxlbmQ6bnVsbH0pOmwmJnRoaXMuc3RlcFNlbGVjdG9yVGltZVNlbGVjdGlvbiQubmV4dCh7c3RhcnQ6e3N0ZXA6Yy5taW5TdGVwfSxlbmQ6bD97c3RlcDpjLm1heFN0ZXB9Om51bGx9KX0pLHRoaXMubWluTWF4U3RlcHMkLnBpcGUoc3QodGhpcy5uZ1Vuc3Vic2NyaWJlKSkuc3Vic2NyaWJlKCh7bWluU3RlcDpsLG1heFN0ZXA6Y30pPT57aWYoIXRoaXMuc3RlcFNlbGVjdG9yVGltZVNlbGVjdGlvbiQuZ2V0VmFsdWUoKSlyZXR1cm47bGV0IHU9dGhpcy5zdGVwU2VsZWN0b3JUaW1lU2VsZWN0aW9uJC5nZXRWYWx1ZSgpPy5zdGFydC5zdGVwLGQ9dGhpcy5zdGVwU2VsZWN0b3JUaW1lU2VsZWN0aW9uJC5nZXRWYWx1ZSgpPy5lbmQ/LnN0ZXAscD1mdW5jdGlvbihuLHQsZSl7bGV0IGk9UWgobix0LGUpO3JldHVybntzdGFydDp7c3RlcDppLnN0YXJ0U3RlcH0sZW5kOm51bGw9PT1pLmVuZFN0ZXA/bnVsbDp7c3RlcDppLmVuZFN0ZXB9fX0oe3N0YXJ0OntzdGVwOnU/P2x9LGVuZDp0aGlzLnN0ZXBTZWxlY3RvclRpbWVTZWxlY3Rpb24kLmdldFZhbHVlKCk/LmVuZD97c3RlcDpkPz9jfTpudWxsfSxsLGMpO3RoaXMuc3RlcFNlbGVjdG9yVGltZVNlbGVjdGlvbiQubmV4dChwKX0pfW5nT25EZXN0cm95KCl7dGhpcy5uZ1Vuc3Vic2NyaWJlLm5leHQoKSx0aGlzLm5nVW5zdWJzY3JpYmUuY29tcGxldGUoKX1nZXRSdW5EaXNwbGF5TmFtZUFuZEFsaWFzKGUpe3JldHVybiBMdChbdGhpcy5zdG9yZS5zZWxlY3QoR0kse3J1bklkOmV9KSx0aGlzLnN0b3JlLnNlbGVjdChZdSksdGhpcy5zdG9yZS5zZWxlY3QoV0kse3J1bklkOmV9KV0pLnBpcGUoTCgoW2kscixvXSk9PntsZXQgcz1udWxsIT09aT9yW2ldPz9udWxsOm51bGw7cmV0dXJue2Rpc3BsYXlOYW1lOm98fHM/bz8ubmFtZT8/Ii4uLiI6ZSxhbGlhczpzfX0pKX1zdGVwU2VyaWVzVG9MaW5lU2VyaWVzKGUsaSl7bGV0IHI9aT09PUppLlNURVA7cmV0dXJuIGUubWFwKG89PntsZXQgcz0xZTMqby53YWxsVGltZTtyZXR1cm57Li4ubyx4OnI/by5zdGVwOnMseTpvLnZhbHVlLHdhbGxUaW1lOnMscmVsYXRpdmVUaW1lSW5NczowfX0pfW9uRGF0YVRhYmxlU29ydGluZyhlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKHlvZShlKSl9b25UaW1lU2VsZWN0aW9uQ2hhbmdlZChlKXtsZXR7bWluU3RlcDppLG1heFN0ZXA6cn09dGhpcy5taW5NYXhTdGVwcyQuZ2V0VmFsdWUoKSx7c3RhcnRTdGVwOm8sZW5kU3RlcDpzfT1RaChlLnRpbWVTZWxlY3Rpb24saSxyKSxhPXtzdGFydDp7c3RlcDpvfSxlbmQ6cz97c3RlcDpzfTpudWxsfTt0aGlzLnN0b3JlLmRpc3BhdGNoKFloKGUpKSx0aGlzLnN0ZXBTZWxlY3RvclRpbWVTZWxlY3Rpb24kLm5leHQoYSl9b25TdGVwU2VsZWN0b3JUb2dnbGVkKGUpe3RoaXMuc3RlcFNlbGVjdG9yVGltZVNlbGVjdGlvbiQuZ2V0VmFsdWUoKSYmdGhpcy5zdGVwU2VsZWN0b3JUaW1lU2VsZWN0aW9uJC5uZXh0KG51bGwpLHRoaXMuc3RvcmUuZGlzcGF0Y2goWGgoe2FmZm9yZGFuY2U6ZX0pKX1vbkxpbmVDaGFydFpvb20oZSl7bGV0IGk9ZS54LHI9e21pblN0ZXA6TWF0aC5jZWlsKE1hdGgubWluKC4uLmkpKSxtYXhTdGVwOk1hdGguZmxvb3IoTWF0aC5tYXgoLi4uaSkpfTt0aGlzLmxpbmVDaGFydFpvb20kLm5leHQocil9cmVvcmRlckNvbHVtbkhlYWRlcnMoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChkUih7bmV3T3JkZXI6ZX0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInNjYWxhci1jYXJkIl1dLGlucHV0czp7RGF0YURvd25sb2FkQ29tcG9uZW50OiJEYXRhRG93bmxvYWRDb21wb25lbnQiLGNhcmRJZDoiY2FyZElkIixncm91cE5hbWU6Imdyb3VwTmFtZSJ9LG91dHB1dHM6e2Z1bGxXaWR0aENoYW5nZWQ6ImZ1bGxXaWR0aENoYW5nZWQiLGZ1bGxIZWlnaHRDaGFuZ2VkOiJmdWxsSGVpZ2h0Q2hhbmdlZCIscGluU3RhdGVDaGFuZ2VkOiJwaW5TdGF0ZUNoYW5nZWQifSxkZWNsczoyMCx2YXJzOjYxLGNvbnN0czpbWyJvYnNlcnZlSW50ZXJzZWN0aW9uIiwiIiwzLCJjYXJkSWQiLCJjaGFydE1ldGFkYXRhTWFwIiwiRGF0YURvd25sb2FkQ29tcG9uZW50IiwiZGF0YVNlcmllcyIsImlnbm9yZU91dGxpZXJzIiwiaXNDYXJkVmlzaWJsZSIsImlzUGlubmVkIiwibG9hZFN0YXRlIiwic2hvd0Z1bGxTaXplIiwic21vb3RoaW5nRW5hYmxlZCIsInRhZyIsInRpdGxlIiwidG9vbHRpcFNvcnQiLCJ4QXhpc1R5cGUiLCJ4U2NhbGVUeXBlIiwidXNlRGFya01vZGUiLCJsaW5rZWRUaW1lU2VsZWN0aW9uIiwic3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbiIsImlzUHJvc3BlY3RpdmVGb2JGZWF0dXJlRW5hYmxlZCIsImZvcmNlU3ZnIiwiY29sdW1uQ3VzdG9taXphdGlvbkVuYWJsZWQiLCJtaW5NYXhTdGVwIiwiY29sdW1uSGVhZGVycyIsIm9uRnVsbFNpemVUb2dnbGUiLCJvblBpbkNsaWNrZWQiLCJvblZpc2liaWxpdHlDaGFuZ2UiLCJvblRpbWVTZWxlY3Rpb25DaGFuZ2VkIiwib25TdGVwU2VsZWN0b3JUb2dnbGVkIiwib25EYXRhVGFibGVTb3J0aW5nIiwib25MaW5lQ2hhcnRab29tIiwicmVvcmRlckNvbHVtbkhlYWRlcnMiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsInNjYWxhci1jYXJkLWNvbXBvbmVudCIsMCksUCgib25GdWxsU2l6ZVRvZ2dsZSIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkZ1bGxTaXplVG9nZ2xlKCl9KSgib25QaW5DbGlja2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5waW5TdGF0ZUNoYW5nZWQuZW1pdChvKX0pKCJvblZpc2liaWxpdHlDaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uVmlzaWJpbGl0eUNoYW5nZShvKX0pKCJvblRpbWVTZWxlY3Rpb25DaGFuZ2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblRpbWVTZWxlY3Rpb25DaGFuZ2VkKG8pfSkoIm9uU3RlcFNlbGVjdG9yVG9nZ2xlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25TdGVwU2VsZWN0b3JUb2dnbGVkKG8pfSkoIm9uRGF0YVRhYmxlU29ydGluZyIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25EYXRhVGFibGVTb3J0aW5nKG8pfSkoIm9uTGluZUNoYXJ0Wm9vbSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25MaW5lQ2hhcnRab29tKG8pfSkoInJlb3JkZXJDb2x1bW5IZWFkZXJzIixmdW5jdGlvbihvKXtyZXR1cm4gaS5yZW9yZGVyQ29sdW1uSGVhZGVycyhvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLEIoNCwiYXN5bmMiKSxCKDUsImFzeW5jIiksQig2LCJhc3luYyIpLEIoNywiYXN5bmMiKSxCKDgsImFzeW5jIiksQig5LCJhc3luYyIpLEIoMTAsImFzeW5jIiksQigxMSwiYXN5bmMiKSxCKDEyLCJhc3luYyIpLEIoMTMsImFzeW5jIiksQigxNCwiYXN5bmMiKSxCKDE1LCJhc3luYyIpLEIoMTYsImFzeW5jIiksQigxNywiYXN5bmMiKSxCKDE4LCJhc3luYyIpLEIoMTksImFzeW5jIiksdigpKSwyJmUmJnkoImNhcmRJZCIsaS5jYXJkSWQpKCJjaGFydE1ldGFkYXRhTWFwIixVKDEsMjMsaS5jaGFydE1ldGFkYXRhTWFwJCkpKCJEYXRhRG93bmxvYWRDb21wb25lbnQiLGkuRGF0YURvd25sb2FkQ29tcG9uZW50KSgiZGF0YVNlcmllcyIsVSgyLDI1LGkuZGF0YVNlcmllcyQpKSgiaWdub3JlT3V0bGllcnMiLFUoMywyNyxpLmlnbm9yZU91dGxpZXJzJCkpKCJpc0NhcmRWaXNpYmxlIixpLmlzVmlzaWJsZSkoImlzUGlubmVkIixVKDQsMjksaS5pc1Bpbm5lZCQpKSgibG9hZFN0YXRlIixVKDUsMzEsaS5sb2FkU3RhdGUkKSkoInNob3dGdWxsU2l6ZSIsaS5zaG93RnVsbFNpemUpKCJzbW9vdGhpbmdFbmFibGVkIixVKDYsMzMsaS5zbW9vdGhpbmdFbmFibGVkJCkpKCJ0YWciLFUoNywzNSxpLnRhZyQpKSgidGl0bGUiLFUoOCwzNyxpLnRpdGxlJCkpKCJ0b29sdGlwU29ydCIsVSg5LDM5LGkudG9vbHRpcFNvcnQkKSkoInhBeGlzVHlwZSIsVSgxMCw0MSxpLnhBeGlzVHlwZSQpKSgieFNjYWxlVHlwZSIsVSgxMSw0MyxpLnhTY2FsZVR5cGUkKSkoInVzZURhcmtNb2RlIixVKDEyLDQ1LGkudXNlRGFya01vZGUkKSkoImxpbmtlZFRpbWVTZWxlY3Rpb24iLFUoMTMsNDcsaS5saW5rZWRUaW1lU2VsZWN0aW9uJCkpKCJzdGVwT3JMaW5rZWRUaW1lU2VsZWN0aW9uIixVKDE0LDQ5LGkuc3RlcE9yTGlua2VkVGltZVNlbGVjdGlvbiQpKSgiaXNQcm9zcGVjdGl2ZUZvYkZlYXR1cmVFbmFibGVkIixVKDE1LDUxLGkuaXNQcm9zcGVjdGl2ZUZvYkZlYXR1cmVFbmFibGVkJCkpKCJmb3JjZVN2ZyIsVSgxNiw1MyxpLmZvcmNlU3ZnJCkpKCJjb2x1bW5DdXN0b21pemF0aW9uRW5hYmxlZCIsVSgxNyw1NSxpLmNvbHVtbkN1c3RvbWl6YXRpb25FbmFibGVkJCkpKCJtaW5NYXhTdGVwIixVKDE4LDU3LGkubWluTWF4U3RlcHMkKSkoImNvbHVtbkhlYWRlcnMiLFUoMTksNTksaS5jb2x1bW5IZWFkZXJzJCkpfSxkZXBlbmRlbmNpZXM6W2F5LGdwZSxHZV0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXSB7XG4gICAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgICBoZWlnaHQ6IDEwMCU7XG4gICAgICB9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiBRWWUobix0LGUpe2xldCBpPVtdLHtsZWZ0OnIscmlnaHQ6b309dCxzPShvLXIpL2UsYT0wLGw9MDtmb3IobGV0IGM9MDtjPGU7YysrKXtsZXQgdT1yK2MqcyxkPXUrcyxwPWM9PT1lLTEsaD1sO2ZvcihsPTA7YTxuLmxlbmd0aDspe2xldCBmPW5bYV0sbT1LWWUoZix1LGQsIXApO2lmKGgrPW0uY3VycixsKz1tLm5leHQsZi54K2YuZHg+ZClicmVhazthKyt9aS5wdXNoKHt4OnUsZHg6cyx5Omh9KX1yZXR1cm4gaX1mdW5jdGlvbiBLWWUobix0LGUsaSl7bGV0IHI9bi54LG89bi54K24uZHg7aWYocj5lfHxvPHQpcmV0dXJue2N1cnI6MCxuZXh0OjB9O2lmKDA9PT1uLmR4KXJldHVybiBpJiZvPj1lP3tjdXJyOjAsbmV4dDpuLnl9OntjdXJyOm4ueSxuZXh0OjB9O2xldCBzPU1hdGgubWluKG8sZSktTWF0aC5tYXgocix0KTtyZXR1cm57Y3VycjpuLnkqcy9uLmR4LG5leHQ6MH19dmFyICRZZT14bygiLjJ+cyIpLGVYZT14bygiLjR+ciIpLHRYZT14bygiLjJ+ZSIpO2Z1bmN0aW9uIHlwZShuKXtpZigwPT09bilyZXR1cm4iMCI7bGV0IHQ9TWF0aC5hYnMobik7cmV0dXJuIHQ+PTFlND8kWWUobik6dDwuMDAxP3RYZShuKTplWGUobil9dmFyIG5YZT1bIm1haW4iXSxpWGU9WyJ4QXhpcyJdLHJYZT1bInlBeGlzIl0sb1hlPVsiY29udGVudCJdLHNYZT1bImhpc3RvZ3JhbXMiXTtmdW5jdGlvbiBhWGUobix0KXtpZigxJm4mJihJbigpLF8oMCwiZyIpKDEsInRleHQiKSxBKDIpLHYoKSgpKSwyJm4pe2xldCBlPVMoKTtQdCgidHJhbnNmb3JtIixlLmdldENzc1RyYW5zbGF0ZVB4KGUudG9vbHRpcERhdGEueEF4aXMucG9zaXRpb24sOSkpLEMoMikseXQoZS50b29sdGlwRGF0YS54QXhpcy5sYWJlbCl9fWZ1bmN0aW9uIGxYZShuLHQpe2lmKDEmbiYmKEluKCksXygwLCJnIikoMSwidGV4dCIpLEEoMiksdigpKCkpLDImbil7bGV0IGU9UygpO1B0KCJ0cmFuc2Zvcm0iLGUuZ2V0R3JvdXBUcmFuc2Zvcm0oZS50b29sdGlwRGF0YS5jbG9zZXN0RGF0dW0pKSxDKDEpLHplKCJ5IixlLnRvb2x0aXBEYXRhLnlBeGlzLnBvc2l0aW9uKSxDKDEpLGplKCIgIixlLnRvb2x0aXBEYXRhLnlBeGlzLmxhYmVsLCIgIil9fWZ1bmN0aW9uIGNYZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtJbigpLEpzKCksc24oMCksXygxLCJoaXN0b2dyYW0tY2FyZC1mb2ItY29udHJvbGxlciIsMTYpLFAoIm9uVGltZVNlbGVjdGlvbkNoYW5nZWQiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25MaW5rZWRUaW1lU2VsZWN0aW9uQ2hhbmdlZC5lbWl0KHIpKX0pKCJvblRpbWVTZWxlY3Rpb25Ub2dnbGVkIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkub25MaW5rZWRUaW1lVG9nZ2xlZC5lbWl0KCkpfSksdigpLGFuKCl9aWYoMiZuKXtsZXQgZT1TKCk7QygxKSx5KCJ0aW1lU2VsZWN0aW9uIixlLnRpbWVTZWxlY3Rpb24pKCJzdGVwcyIsZS5nZXRTdGVwcygpKSgidGVtcG9yYWxTY2FsZSIsZS5zY2FsZXMudGVtcG9yYWxTY2FsZSl9fWZ1bmN0aW9uIHVYZShuLHQpe2lmKDEmbiYmKEluKCksXygwLCJnIiksTygxLCJsaW5lIiwxNyksdigpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0O1B0KCJ0cmFuc2Zvcm0iLFMoKS5nZXRDc3NUcmFuc2xhdGVQeCgwLGUpKX19ZnVuY3Rpb24gZFhlKG4sdCl7MSZuJiYoSW4oKSxPKDAsImxpbmUiLDIxKSl9ZnVuY3Rpb24gcFhlKG4sdCl7aWYoMSZuJiYoSW4oKSxPKDAsImNpcmNsZSIsMjIpKSwyJm4pe2xldCBlPVMoKS4kaW1wbGljaXQsaT1TKCk7UHQoInRyYW5zZm9ybSIsaS5nZXRDc3NUcmFuc2xhdGVQeChpLmdldFVpQ29vcmRGcm9tQmluRm9yQ29udGVudChpLmdldENsb3Nlc3RCaW5Gcm9tQmluQ29vcmRpbmF0ZShlLGkudG9vbHRpcERhdGEueFBvc2l0aW9uSW5CaW5Db29yZCkpLngsaS5nZXRVaUNvb3JkRnJvbUJpbkZvckNvbnRlbnQoaS5nZXRDbG9zZXN0QmluRnJvbUJpbkNvb3JkaW5hdGUoZSxpLnRvb2x0aXBEYXRhLnhQb3NpdGlvbkluQmluQ29vcmQpKS55KSl9fWZ1bmN0aW9uIGhYZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtJbigpLF8oMCwiZyIsMTgpLFAoIm1vdXNlZW50ZXIiLGZ1bmN0aW9uKHIpe2xldCBzPW9lKGUpLiRpbXBsaWNpdDtyZXR1cm4gc2UoUygpLnVwZGF0ZUNvbG9yT25Ib3ZlcihyLHMsITApKX0pKCJtb3VzZWxlYXZlIixmdW5jdGlvbihyKXtsZXQgcz1vZShlKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoKS51cGRhdGVDb2xvck9uSG92ZXIocixzLCExKSl9KSgiY2xpY2siLGZ1bmN0aW9uKCl7bGV0IG89b2UoZSkuJGltcGxpY2l0O3JldHVybiBzZShTKCkub25MaW5rZWRUaW1lUmFuZ2VDaGFuZ2VkKG8pKX0pLEUoMSxkWGUsMSwwLCJsaW5lIiwxOSksTygyLCJwYXRoIiksRSgzLHBYZSwxLDIsImNpcmNsZSIsMjApLHYoKX1pZigyJm4pe2xldCBlPXQuJGltcGxpY2l0LGk9UygpO1B0KCJ0cmFuc2Zvcm0iLGkuZ2V0R3JvdXBUcmFuc2Zvcm0oZSkpKCJjb2xvciIsaS5nZXRIaXN0b2dyYW1GaWxsKGUpKSxldCgiaGlzdG9ncmFtIiwhMCkoIm5vLWNvbG9yIiwhaS5pc0RhdHVtSW5UaW1lU2VsZWN0aW9uUmFuZ2UoZSkpLEMoMSkseSgibmdJZiIsaS5tb2RlPT09aS5IaXN0b2dyYW1Nb2RlLk9GRlNFVCksQygxKSx6ZSgiZCIsaS5nZXRIaXN0b2dyYW1QYXRoKGUpKSxDKDEpLHkoIm5nSWYiLGkudG9vbHRpcERhdGEpfX1mdW5jdGlvbiBmWGUobix0KXtpZigxJm4mJihJbigpLE8oMCwiY2lyY2xlIiwyMikpLDImbil7bGV0IGU9UygyKTt6ZSgiY3giLGUuZ2V0VWlDb29yZEZyb21CaW5Gb3JDb250ZW50KGUudG9vbHRpcERhdGEuY2xvc2VzdEJpbikueCkoImN5IixlLmdldFVpQ29vcmRGcm9tQmluRm9yQ29udGVudChlLnRvb2x0aXBEYXRhLmNsb3Nlc3RCaW4pLnkpfX1mdW5jdGlvbiBtWGUobix0KXtpZigxJm4mJihJbigpLF8oMCwiZyIsNCkoMSwiZyIpLE8oMiwicGF0aCIpLEUoMyxmWGUsMSwyLCJjaXJjbGUiLDIzKSx2KCksXyg0LCJnIiwyNCkoNSwidGV4dCIsMjUpLEEoNiksdigpKCkoKSksMiZuKXtsZXQgZT1TKCk7QygxKSxQdCgidHJhbnNmb3JtIixlLmdldEdyb3VwVHJhbnNmb3JtKGUudG9vbHRpcERhdGEuY2xvc2VzdERhdHVtKSksQygxKSx6ZSgiZCIsZS5nZXRIaXN0b2dyYW1QYXRoKGUudG9vbHRpcERhdGEuY2xvc2VzdERhdHVtKSksQygxKSx5KCJuZ0lmIixlLnRvb2x0aXBEYXRhLmNsb3Nlc3RCaW4pLEMoMSksUHQoInRyYW5zZm9ybSIsZS5nZXRDc3NUcmFuc2xhdGVQeChlLnRvb2x0aXBEYXRhLnZhbHVlLnBvc2l0aW9uLngsZS50b29sdGlwRGF0YS52YWx1ZS5wb3NpdGlvbi55KSksQygyKSx5dChlLnRvb2x0aXBEYXRhLnZhbHVlLmxhYmVsKX19dmFyIFdrPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5jaGFuZ2VEZXRlY3Rvcj1lLHRoaXMubW9kZT16ci5PRkZTRVQsdGhpcy50aW1lUHJvcGVydHk9Um8uU1RFUCx0aGlzLnRpbWVTZWxlY3Rpb249bnVsbCx0aGlzLm9uTGlua2VkVGltZVNlbGVjdGlvbkNoYW5nZWQ9bmV3IEcsdGhpcy5vbkxpbmtlZFRpbWVUb2dnbGVkPW5ldyBHLHRoaXMuSGlzdG9ncmFtTW9kZT16cix0aGlzLlRpbWVQcm9wZXJ0eT1Sbyx0aGlzLnRvb2x0aXBEYXRhPW51bGwsdGhpcy5uZ1Vuc3Vic2NyaWJlPW5ldyBrZSx0aGlzLmxheW91dD17aGlzdG9ncmFtSGVpZ2h0OjAsY29udGVudENsaWVudFJlY3Q6e2hlaWdodDowLHdpZHRoOjB9fSx0aGlzLnNjYWxlcz1udWxsLHRoaXMuZm9ybWF0dGVycz17YmluTnVtYmVyOnlwZSxjb3VudDp4bygiLjNuIiksd2FsbFRpbWU6SXkoIiVtLyVkICVYIiksc3RlcDp4bygiLjBmIikscmVsYXRpdmU6aT0+eG8oIi4xciIpKGkvMzZlNSkrImgifSx0aGlzLmRvbVZpc2libGU9ITF9bmdPbkNoYW5nZXMoKXt0aGlzLnVwZGF0ZUNoYXJ0SWZWaXNpYmxlKCl9bmdPbkRlc3Ryb3koKXt0aGlzLm5nVW5zdWJzY3JpYmUubmV4dCgpLHRoaXMubmdVbnN1YnNjcmliZS5jb21wbGV0ZSgpfW5nQWZ0ZXJWaWV3SW5pdCgpe19pKHRoaXMubWFpbi5uYXRpdmVFbGVtZW50LCJtb3VzZW1vdmUiLHtwYXNzaXZlOiEwfSkucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpKS5zdWJzY3JpYmUoZT0+dGhpcy5vbk1vdXNlTW92ZShlKSl9Z2V0Q3NzVHJhbnNsYXRlUHgoZSxpKXtyZXR1cm5gdHJhbnNsYXRlKCR7ZX1weCwgJHtpfXB4KWB9Z2V0Q2xvc2VzdEJpbkZyb21CaW5Db29yZGluYXRlKGUsaSl7aWYoIWUuYmlucy5sZW5ndGgpcmV0dXJue3g6MCxkeDowLHk6MH07bGV0IHI9ZS5iaW5zWzBdLG89ZS5iaW5zLnNsaWNlKC0xKVswXTtyZXR1cm4gaTxyLng/cjppPj1vLngrby5keD9vOmUuYmlucy5maW5kKGE9PmEueDw9aSYmaTxhLngrYS5keCl9Z2V0VWlDb29yZEZyb21CaW5Gb3JDb250ZW50KGUpe3JldHVybiB0aGlzLnNjYWxlcz97eDp0aGlzLnNjYWxlcy5iaW5TY2FsZSh4RShlKSkseTp0aGlzLnNjYWxlcy5jb3VudFNjYWxlKGUueSl9Ont4OjAseTowfX1nZXRIaXN0b2dyYW1QYXRoKGUpe2lmKCF0aGlzLnNjYWxlc3x8IWUuYmlucy5sZW5ndGgpcmV0dXJuIiI7bGV0IGk9dGhpcy5zY2FsZXMuYmluU2NhbGUscj10aGlzLnNjYWxlcy5jb3VudFNjYWxlLG89ZS5iaW5zWzBdLHM9ZS5iaW5zLnNsaWNlKC0xKVswXSxhPVtgTSR7aSh4RShvKSl9LCR7cigwKX1gXTtmb3IobGV0IGwgb2YgZS5iaW5zKWEucHVzaChgTCR7aSh4RShsKSl9LCR7cihsLnkpfWApO3JldHVybiBhLnB1c2goYEwke2koeEUocykpfSwke3IoMCl9YCksYS5qb2luKCIiKX10cmFja0J5V2FsbFRpbWUoZSl7cmV0dXJuIGUud2FsbFRpbWV9Z2V0R3JvdXBUcmFuc2Zvcm0oZSl7cmV0dXJuIHRoaXMuc2NhbGVzJiZ0aGlzLm1vZGUhPT16ci5PVkVSTEFZP3RoaXMuZ2V0Q3NzVHJhbnNsYXRlUHgoMCx0aGlzLnNjYWxlcy50ZW1wb3JhbFNjYWxlKHRoaXMuZ2V0VGltZVZhbHVlKGUpKSk6IiJ9Z2V0U3RlcHMoKXtyZXR1cm4gdGhpcy5kYXRhLm1hcChlPT5lLnN0ZXApfWlzVGltZVNlbGVjdGlvbkVuYWJsZWQoZSl7cmV0dXJuIEJvb2xlYW4odGhpcy5tb2RlPT09enIuT0ZGU0VUJiZ0aGlzLnRpbWVQcm9wZXJ0eT09PVJvLlNURVAmJnRoaXMuc2NhbGVzJiZlKX1pc0RhdHVtSW5UaW1lU2VsZWN0aW9uUmFuZ2UoZSl7cmV0dXJuIXRoaXMuaXNUaW1lU2VsZWN0aW9uRW5hYmxlZCh0aGlzLnRpbWVTZWxlY3Rpb24pfHwobnVsbD09PXRoaXMudGltZVNlbGVjdGlvbi5lbmQ/dGhpcy50aW1lU2VsZWN0aW9uLnN0YXJ0LnN0ZXA9PT1lLnN0ZXA6dGhpcy50aW1lU2VsZWN0aW9uLnN0YXJ0LnN0ZXA8PWUuc3RlcCYmdGhpcy50aW1lU2VsZWN0aW9uLmVuZC5zdGVwPj1lLnN0ZXApfWdldEhpc3RvZ3JhbUZpbGwoZSl7cmV0dXJuIHRoaXMuc2NhbGVzP3RoaXMuc2NhbGVzLmQzQ29sb3JTY2FsZSh0aGlzLmdldFRpbWVWYWx1ZShlKSk6IiJ9dXBkYXRlQ29sb3JPbkhvdmVyKGUsaSxyKXshdGhpcy5pc1RpbWVTZWxlY3Rpb25FbmFibGVkKHRoaXMudGltZVNlbGVjdGlvbil8fHRoaXMuaXNEYXR1bUluVGltZVNlbGVjdGlvblJhbmdlKGkpfHwocj9lLnRhcmdldC5jbGFzc0xpc3QucmVtb3ZlKCJuby1jb2xvciIpOmUudGFyZ2V0LmNsYXNzTGlzdC5hZGQoIm5vLWNvbG9yIikpfWdldEdyaWRUaWNrWUxvY3MoKXtpZighdGhpcy5zY2FsZXN8fHRoaXMubW9kZT09PXpyLk9GRlNFVClyZXR1cm5bXTtsZXQgZT10aGlzLnNjYWxlcy5jb3VudFNjYWxlO3JldHVybiBlLnRpY2tzKCkubWFwKGk9PmUoaSkpfW9uUmVzaXplKCl7dGhpcy51cGRhdGVDbGllbnRSZWN0cygpLHRoaXMudXBkYXRlQ2hhcnRJZlZpc2libGUoKX1vblZpc2liaWxpdHlDaGFuZ2Uoe3Zpc2libGU6ZX0pe3RoaXMuZG9tVmlzaWJsZT1lLGUmJih0aGlzLnVwZGF0ZUNsaWVudFJlY3RzKCksdGhpcy51cGRhdGVDaGFydElmVmlzaWJsZSgpKX1vbkxpbmtlZFRpbWVSYW5nZUNoYW5nZWQoZSl7aWYoIXRoaXMuaXNUaW1lU2VsZWN0aW9uRW5hYmxlZCh0aGlzLnRpbWVTZWxlY3Rpb24pKXJldHVybjtsZXQgaT10aGlzLnRpbWVTZWxlY3Rpb24uc3RhcnQuc3RlcCxyPXRoaXMudGltZVNlbGVjdGlvbi5lbmQ/LnN0ZXAsbz1lLnN0ZXA8aT9lLnN0ZXA6aSxzPXI7cz12b2lkIDA9PT1zP2Uuc3RlcD5pP2Uuc3RlcDppOmUuc3RlcD5zP2Uuc3RlcDpzLChvIT09aXx8cyE9PXIpJiZvIT09cyYmdGhpcy5vbkxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkLmVtaXQoe3RpbWVTZWxlY3Rpb246e3N0YXJ0OntzdGVwOm99LGVuZDp7c3RlcDpzfX0sYWZmb3JkYW5jZTpjcy5ISVNUT0dSQU1fQ0xJQ0tfVE9fUkFOR0V9KX1nZXRUaW1lVmFsdWUoZSl7c3dpdGNoKHRoaXMudGltZVByb3BlcnR5KXtjYXNlIFJvLldBTExfVElNRTpyZXR1cm4gZS53YWxsVGltZTtjYXNlIFJvLlNURVA6cmV0dXJuIGUuc3RlcDtjYXNlIFJvLlJFTEFUSVZFOnJldHVybiBlLndhbGxUaW1lLXRoaXMuZGF0YVswXS53YWxsVGltZX19dXBkYXRlQ2xpZW50UmVjdHMoKXt0aGlzLmNvbnRlbnQmJih0aGlzLmxheW91dC5jb250ZW50Q2xpZW50UmVjdD10aGlzLmNvbnRlbnQubmF0aXZlRWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSx0aGlzLmxheW91dC5oaXN0b2dyYW1IZWlnaHQ9dGhpcy5sYXlvdXQuY29udGVudENsaWVudFJlY3QuaGVpZ2h0LzIuNSl9dXBkYXRlQ2hhcnRJZlZpc2libGUoKXshdGhpcy5kb21WaXNpYmxlfHwodGhpcy5zY2FsZXM9dGhpcy5jb21wdXRlU2NhbGVzKHRoaXMuZGF0YSksdGhpcy5yZW5kZXJYQXhpcygpLHRoaXMucmVuZGVyWUF4aXMoKSx0aGlzLmNoYW5nZURldGVjdG9yLmRldGVjdENoYW5nZXMoKSl9Y29tcHV0ZVNjYWxlcyhlKXtsZXR7d2lkdGg6aSxoZWlnaHQ6cn09dGhpcy5sYXlvdXQuY29udGVudENsaWVudFJlY3Qse21pbjpvLG1heDpzfT1icGUoZSxnPT5mdW5jdGlvbihuLHQpe3JldHVybiBuLnJlZHVjZSgoZSxpKT0+TWF0aC5taW4oZSxpLngpLDEvMCl9KGcuYmlucyksZz0+TkcoZy5iaW5zLCh7eDpiLGR4OkR9KT0+YitEKSksYT1ORyhlLGc9Pk5HKGcuYmlucywoe3k6Yn0pPT5iKSksbD1RbygpLmRvbWFpbihbbyxzXSkubmljZSgpLGM9dGhpcy5tb2RlIT09enIuT1ZFUkxBWSYmdGhpcy50aW1lUHJvcGVydHk9PVJvLldBTExfVElNRT9SZygpOlFvKCksdT1lLm1hcChnPT50aGlzLmdldFRpbWVWYWx1ZShnKSkse21pbjpkLG1heDpwfT1icGUodSxnPT5nKSxoPVtkLHBdO2MuZG9tYWluKGgpO2xldCBmPVFvKCk7Zi5kb21haW4oWzAsYV0pO2xldCBtPXlnKHRoaXMuY29sb3J8fCIjMDAwIikseD1RbygpO3JldHVybiB4LmRvbWFpbihoKSxsLnJhbmdlKFswLGldKSx4LnJhbmdlKFttLmJyaWdodGVyKCksbS5kYXJrZXIoKV0pLHguaW50ZXJwb2xhdGUocHopLHRoaXMubW9kZT09PXpyLk9WRVJMQVk/KGMucmFuZ2UoW3Iscl0pLGYucmFuZ2UoW3IsMF0pKTooYy5yYW5nZShbci0odGhpcy5tb2RlPT09enIuT0ZGU0VUP3ItdGhpcy5sYXlvdXQuaGlzdG9ncmFtSGVpZ2h0OjApLHJdKSxmLnJhbmdlKFswLC10aGlzLmxheW91dC5oaXN0b2dyYW1IZWlnaHRdKSkse2JpblNjYWxlOmwsZDNDb2xvclNjYWxlOngsY291bnRTY2FsZTpmLHRlbXBvcmFsU2NhbGU6Y319cmVuZGVyWEF4aXMoKXtpZighdGhpcy5zY2FsZXMpcmV0dXJuO2xldHt3aWR0aDplfT10aGlzLmxheW91dC5jb250ZW50Q2xpZW50UmVjdCxpPWp3KHRoaXMuc2NhbGVzLmJpblNjYWxlKS50aWNrcyhNYXRoLm1heCgyLGUvMjApKTtpLnRpY2tGb3JtYXQodGhpcy5mb3JtYXR0ZXJzLmJpbk51bWJlciksaShibyh0aGlzLnhBeGlzLm5hdGl2ZUVsZW1lbnQpKX1nZXRZQXhpc0Zvcm1hdHRlcigpe2lmKHRoaXMubW9kZT09PXpyLk9WRVJMQVkpcmV0dXJuIHRoaXMuZm9ybWF0dGVycy5jb3VudDtzd2l0Y2godGhpcy50aW1lUHJvcGVydHkpe2Nhc2UgUm8uV0FMTF9USU1FOnJldHVybiB0aGlzLmZvcm1hdHRlcnMud2FsbFRpbWU7Y2FzZSBSby5TVEVQOnJldHVybiB0aGlzLmZvcm1hdHRlcnMuc3RlcDtjYXNlIFJvLlJFTEFUSVZFOnJldHVybiB0aGlzLmZvcm1hdHRlcnMucmVsYXRpdmU7ZGVmYXVsdDp0aHJvdyBSYW5nZUVycm9yKGBZIGF4aXMgZm9ybWF0dGVyIGZvciAke3RoaXMudGltZVByb3BlcnR5fSBtdXN0IGJlIGltcGxlbWVudGVkYCl9fWdldE1heFRpY2tzKGUpe2xldHtoZWlnaHQ6aX09dGhpcy5sYXlvdXQuY29udGVudENsaWVudFJlY3Qscj1pLzE1O2lmKHRoaXMudGltZVByb3BlcnR5PT09Um8uU1RFUCl7bGV0W28sc109ZS5kb21haW4oKSxhPU1hdGgubWF4KHMtbysxLDEpO3JldHVybiBNYXRoLm1pbihhLHIpfXJldHVybiByfXJlbmRlcllBeGlzKCl7aWYoIXRoaXMuc2NhbGVzKXJldHVybjtsZXQgZT10aGlzLm1vZGU9PT16ci5PVkVSTEFZP3RoaXMuc2NhbGVzLmNvdW50U2NhbGU6dGhpcy5zY2FsZXMudGVtcG9yYWxTY2FsZSxpPXRoaXMuZ2V0TWF4VGlja3MoZSkscj16dyhlKS50aWNrcyhNYXRoLm1heCgyLGkpKTtyLnRpY2tGb3JtYXQodGhpcy5nZXRZQXhpc0Zvcm1hdHRlcigpKSxyKGJvKHRoaXMueUF4aXMubmF0aXZlRWxlbWVudCkpfWZpbmRDbG9zZXN0RGF0dW1JbmRleChlKXtsZXQgaT1lLnRhcmdldCxyPWk7Zm9yKDtpJiZpIT09dGhpcy5oaXN0b2dyYW1zLm5hdGl2ZUVsZW1lbnQ7KXI9aSxpPWkucGFyZW50RWxlbWVudDtyZXR1cm4gaT9BcnJheS5mcm9tKGkuY2hpbGRyZW4pLmluZGV4T2Yocik6LTF9b25Nb3VzZU1vdmVGb3JUZXN0T25seShlKXtyZXR1cm4gdGhpcy5vbk1vdXNlTW92ZShlKX1vbk1vdXNlTW92ZShlKXtpZighdGhpcy5zY2FsZXMpcmV0dXJuO2xldCBpPWUub2Zmc2V0WCxyPWUub2Zmc2V0WSxvPXRoaXMuZmluZENsb3Nlc3REYXR1bUluZGV4KGUpO2lmKG88MClyZXR1cm47bGV0IHM9dGhpcy5zY2FsZXMuYmluU2NhbGUuaW52ZXJ0KGkpLGE9dGhpcy5kYXRhW29dLGw9dGhpcy5nZXRDbG9zZXN0QmluRnJvbUJpbkNvb3JkaW5hdGUoYSxzKTt0aGlzLnRvb2x0aXBEYXRhPXt2YWx1ZTp7cG9zaXRpb246e3g6aSx5OnJ9LGxhYmVsOnRoaXMubW9kZT09PXpyLk9GRlNFVD90aGlzLmZvcm1hdHRlcnMuY291bnQobC55KTpgU3RlcDogJHt0aGlzLmZvcm1hdHRlcnMuc3RlcChhLnN0ZXApfWB9LHhBeGlzOntwb3NpdGlvbjp0aGlzLmdldFVpQ29vcmRGcm9tQmluRm9yQ29udGVudChsKS54LGxhYmVsOnRoaXMuZm9ybWF0dGVycy5iaW5OdW1iZXIoeEUobCkpfSx5QXhpczp7cG9zaXRpb246dGhpcy5zY2FsZXMuY291bnRTY2FsZSh0aGlzLm1vZGU9PT16ci5PRkZTRVQ/MDpsLnkpLGxhYmVsOnRoaXMubW9kZT09PXpyLk9GRlNFVD90aGlzLmdldFlBeGlzRm9ybWF0dGVyKCkodGhpcy5nZXRUaW1lVmFsdWUoYSkpOnRoaXMuZm9ybWF0dGVycy5iaW5OdW1iZXIobC55KX0seFBvc2l0aW9uSW5CaW5Db29yZDpzLGNsb3Nlc3REYXR1bTphLGNsb3Nlc3RCaW46bH0sdGhpcy5jaGFuZ2VEZXRlY3Rvci5kZXRlY3RDaGFuZ2VzKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0obm4pKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ0Yi1oaXN0b2dyYW0iXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYob3QoblhlLDUpLG90KGlYZSw1KSxvdChyWGUsNSksb3Qob1hlLDUpLG90KHNYZSw1KSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5tYWluPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLnhBeGlzPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLnlBeGlzPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLmNvbnRlbnQ9ci5maXJzdCksTmUocj1MZSgpKSYmKGkuaGlzdG9ncmFtcz1yLmZpcnN0KX19LGlucHV0czp7bW9kZToibW9kZSIsdGltZVByb3BlcnR5OiJ0aW1lUHJvcGVydHkiLGNvbG9yOiJjb2xvciIsZGF0YToiZGF0YSIsdGltZVNlbGVjdGlvbjoidGltZVNlbGVjdGlvbiJ9LG91dHB1dHM6e29uTGlua2VkVGltZVNlbGVjdGlvbkNoYW5nZWQ6Im9uTGlua2VkVGltZVNlbGVjdGlvbkNoYW5nZWQiLG9uTGlua2VkVGltZVRvZ2dsZWQ6Im9uTGlua2VkVGltZVRvZ2dsZWQifSxmZWF0dXJlczpbRnRdLGRlY2xzOjIyLHZhcnM6MTUsY29uc3RzOltbImRldGVjdFJlc2l6ZSIsIiIsIm9ic2VydmVJbnRlcnNlY3Rpb24iLCIiLDMsIm9uUmVzaXplIiwib25WaXNpYmlsaXR5Q2hhbmdlIl0sWyJtYWluIiwiIl0sWzEsImF4aXMiLCJ4LWF4aXMiXSxbInhBeGlzIiwiIl0sWzEsInRvb2x0aXAiXSxbMywidHJhbnNmb3JtIiw0LCJuZ0lmIl0sWzEsImF4aXMiLCJ5LWF4aXMiXSxbInlBeGlzIiwiIl0sWzQsIm5nSWYiXSxbMSwiY29udGVudCJdLFsiY29udGVudCIsIiJdLFsxLCJncmlkIl0sWzMsInRyYW5zZm9ybSIsNCwibmdGb3IiLCJuZ0Zvck9mIl0sWyJoaXN0b2dyYW1zIiwiIl0sWzMsInRyYW5zZm9ybSIsImhpc3RvZ3JhbSIsIm5vLWNvbG9yIiwiY29sb3IiLCJtb3VzZWVudGVyIiwibW91c2VsZWF2ZSIsImNsaWNrIiw0LCJuZ0ZvciIsIm5nRm9yT2YiLCJuZ0ZvclRyYWNrQnkiXSxbImNsYXNzIiwidG9vbHRpcCIsNCwibmdJZiJdLFsxLCJoaXN0b2dyYW0tY2FyZC1mb2IiLDMsInRpbWVTZWxlY3Rpb24iLCJzdGVwcyIsInRlbXBvcmFsU2NhbGUiLCJvblRpbWVTZWxlY3Rpb25DaGFuZ2VkIiwib25UaW1lU2VsZWN0aW9uVG9nZ2xlZCJdLFsieDIiLCIxMDAlIiwxLCJ0aWNrIl0sWzMsIm1vdXNlZW50ZXIiLCJtb3VzZWxlYXZlIiwiY2xpY2siXSxbImNsYXNzIiwiYmFzZWxpbmUiLCJ4MiIsIjEwMCUiLDQsIm5nSWYiXSxbInIiLCIyIiwzLCJ0cmFuc2Zvcm0iLDQsIm5nSWYiXSxbIngyIiwiMTAwJSIsMSwiYmFzZWxpbmUiXSxbInIiLCIyIl0sWyJyIiwiMiIsNCwibmdJZiJdLFsxLCJ2YWx1ZS1sYWJlbCJdLFsieCIsIjMiLCJ5IiwiLTMiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCwxKSxQKCJvblJlc2l6ZSIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblJlc2l6ZSgpfSkoIm9uVmlzaWJpbGl0eUNoYW5nZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25WaXNpYmlsaXR5Q2hhbmdlKG8pfSksSW4oKSxfKDIsInN2ZyIsMiksTygzLCJnIixudWxsLDMpLF8oNSwiZyIsNCksRSg2LGFYZSwzLDMsImciLDUpLHYoKSgpLEpzKCksXyg3LCJkaXYiLDYpLEluKCksXyg4LCJzdmciKSxPKDksImciLG51bGwsNyksXygxMSwiZyIsNCksRSgxMixsWGUsMyw0LCJnIiw1KSx2KCkoKSxFKDEzLGNYZSwyLDMsIm5nLWNvbnRhaW5lciIsOCksdigpLF8oMTQsInN2ZyIsOSwxMCkoMTYsImciLDExKSxFKDE3LHVYZSwyLDIsImciLDEyKSx2KCksXygxOCwiZyIsbnVsbCwxMyksRSgyMCxoWGUsNCwxMSwiZyIsMTQpLHYoKSxFKDIxLG1YZSw3LDcsImciLDE1KSx2KCkoKSksMiZlJiYoRGEoIm1haW4gIitpLm1vZGUrIiAiK2kudGltZVByb3BlcnR5KSxDKDYpLHkoIm5nSWYiLGkudG9vbHRpcERhdGEpLEMoNSksUHQoInRyYW5zZm9ybSIsaS5nZXRDc3NUcmFuc2xhdGVQeCg5LDApKSxDKDEpLHkoIm5nSWYiLGkudG9vbHRpcERhdGEpLEMoMSkseSgibmdJZiIsaS5pc1RpbWVTZWxlY3Rpb25FbmFibGVkKGkudGltZVNlbGVjdGlvbikpLEMoNCkseSgibmdGb3JPZiIsaS5nZXRHcmlkVGlja1lMb2NzKCkpLEMoMSksZXQoImhpc3RvZ3JhbXMiLCEwKSgibGlua2VkLXRpbWUtc2luZ2xlLXN0ZXAiLGkudGltZVNlbGVjdGlvbiYmIWkudGltZVNlbGVjdGlvbi5lbmQpLEMoMikseSgibmdGb3JPZiIsaS5kYXRhKSgibmdGb3JUcmFja0J5IixpLnRyYWNrQnlXYWxsVGltZSksQygxKSx5KCJuZ0lmIixpLnRvb2x0aXBEYXRhKSl9LHN0eWxlczpbJ1tfbmdob3N0LSVDT01QJV0sIC5tYWluW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6MTAwJTt3aWR0aDoxMDAlfVtfbmdob3N0LSVDT01QJV17Ym94LXNpemluZzpib3JkZXItYm94O3BhZGRpbmc6MTBweH0ubWFpbltfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpncmlkO2dyaWQtdGVtcGxhdGUtYXJlYXM6ImNvbnRlbnQgeS1heGlzIiAieC1heGlzIC4iO2dyaWQtdGVtcGxhdGUtY29sdW1uczoxZnIgNTBweDtncmlkLXRlbXBsYXRlLXJvd3M6MWZyIDMwcHh9Lm1haW4ud2FsbF90aW1lW19uZ2NvbnRlbnQtJUNPTVAlXXtncmlkLXRlbXBsYXRlLWNvbHVtbnM6MWZyIDc1cHh9LnRvb2x0aXBbX25nY29udGVudC0lQ09NUCVdLCAuYmFzZWxpbmVbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiMwMDB9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnRvb2x0aXBbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAudG9vbHRpcFtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuYmFzZWxpbmVbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuYmFzZWxpbmVbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiNmZmZ9LmxpbmtlZC10aW1lLWZvYltfbmdjb250ZW50LSVDT01QJV0gICB0ZXh0W19uZ2NvbnRlbnQtJUNPTVAlXSwgLnRvb2x0aXBbX25nY29udGVudC0lQ09NUCVdICAgdGV4dFtfbmdjb250ZW50LSVDT01QJV17Zm9udC13ZWlnaHQ6Ym9sZDtmb250LXNpemU6MTBweH0ubGlua2VkLXRpbWUtZm9iW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHRleHRbX25nY29udGVudC0lQ09NUCVdLCAubGlua2VkLXRpbWUtZm9iW19uZ2NvbnRlbnQtJUNPTVAlXSAgIGNpcmNsZVtfbmdjb250ZW50LSVDT01QJV0sIC50b29sdGlwW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHRleHRbX25nY29udGVudC0lQ09NUCVdLCAudG9vbHRpcFtfbmdjb250ZW50LSVDT01QJV0gICBjaXJjbGVbX25nY29udGVudC0lQ09NUCVde2ZpbGw6Y3VycmVudENvbG9yfS5saW5rZWQtdGltZS1mb2JbX25nY29udGVudC0lQ09NUCVdICAgLnZhbHVlLWxhYmVsW19uZ2NvbnRlbnQtJUNPTVAlXSwgLnRvb2x0aXBbX25nY29udGVudC0lQ09NUCVdICAgLnZhbHVlLWxhYmVsW19uZ2NvbnRlbnQtJUNPTVAlXXtkb21pbmFudC1iYXNlbGluZTppZGVvZ3JhcGhpYzt0ZXh0LWFuY2hvcjpzdGFydH0uYXhpc1tfbmdjb250ZW50LSVDT01QJV0gICAge2NvbG9yOiM2MTYxNjE7cG9zaXRpb246cmVsYXRpdmU7b3ZlcmZsb3c6aGlkZGVufWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgICAsIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgICB7Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9LmF4aXNbX25nY29udGVudC0lQ09NUCVdICAgICAuZG9tYWluLCAuYXhpc1tfbmdjb250ZW50LSVDT01QJV0gICAgIC50aWNrIHRleHR7ZGlzcGxheTpub25lfS5heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgICAgLnRpY2s6bnRoLWNoaWxkKDJuKzEpIHRleHR7ZGlzcGxheTppbml0aWFsfXN2Z1tfbmdjb250ZW50LSVDT01QJV17aGVpZ2h0OjEwMCU7d2lkdGg6MTAwJTtwb2ludGVyLWV2ZW50czp2aXNpYmxlUGFpbnRlZH1zdmdbX25nY29udGVudC0lQ09NUCVdICAgbGluZVtfbmdjb250ZW50LSVDT01QJV0sIHN2Z1tfbmdjb250ZW50LSVDT01QJV0gICBjaXJjbGVbX25nY29udGVudC0lQ09NUCVdLCBzdmdbX25nY29udGVudC0lQ09NUCVdICAgLnRvb2x0aXBbX25nY29udGVudC0lQ09NUCVde3BvaW50ZXItZXZlbnRzOm5vbmV9c3ZnW19uZ2NvbnRlbnQtJUNPTVAlXSAgIGdbX25nY29udGVudC0lQ09NUCVde3dpbGwtY2hhbmdlOnRyYW5zZm9ybX0ueC1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXXtncmlkLWFyZWE6eC1heGlzfS54LWF4aXNbX25nY29udGVudC0lQ09NUCVdICAgLnRvb2x0aXBbX25nY29udGVudC0lQ09NUCVde2RvbWluYW50LWJhc2VsaW5lOmhhbmdpbmc7dGV4dC1hbmNob3I6bWlkZGxlfS55LWF4aXNbX25nY29udGVudC0lQ09NUCVde2dyaWQtYXJlYTp5LWF4aXM7b3ZlcmZsb3c6Y2xpcCB2aXNpYmxlfS55LWF4aXNbX25nY29udGVudC0lQ09NUCVdICAgLnRvb2x0aXBbX25nY29udGVudC0lQ09NUCVde2RvbWluYW50LWJhc2VsaW5lOm1pZGRsZTt0ZXh0LWFuY2hvcjpzdGFydH0uaGlzdG9ncmFtLWNhcmQtZm9iW19uZ2NvbnRlbnQtJUNPTVAlXXtsZWZ0OjlweDtwb3NpdGlvbjphYnNvbHV0ZX0uY29udGVudFtfbmdjb250ZW50LSVDT01QJV0gICAudGlja1tfbmdjb250ZW50LSVDT01QJV0sIC5heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgICAgLnRpY2sgbGluZXtzdHJva2U6I2RkZH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuY29udGVudFtfbmdjb250ZW50LSVDT01QJV0gICAudGlja1tfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC50aWNrW19uZ2NvbnRlbnQtJUNPTVAlXXtzdHJva2U6IzU1NX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuYXhpc1tfbmdjb250ZW50LSVDT01QJV0gICAgIC50aWNrIGxpbmUsIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgICAgLnRpY2sgbGluZXtzdHJva2U6IzU1NX0uY29udGVudFtfbmdjb250ZW50LSVDT01QJV17Z3JpZC1hcmVhOmNvbnRlbnQ7b3ZlcmZsb3c6dmlzaWJsZTt6LWluZGV4OjF9LmNvbnRlbnRbX25nY29udGVudC0lQ09NUCVdICAgLnRpY2tbX25nY29udGVudC0lQ09NUCVde3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWRhc2hhcnJheToyfS5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIGNpcmNsZVtfbmdjb250ZW50LSVDT01QJV0sIC5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIHBhdGhbX25nY29udGVudC0lQ09NUCVde2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZS1vcGFjaXR5Oi42O3N0cm9rZS13aWR0aDoxcHh9LmNvbnRlbnRbX25nY29udGVudC0lQ09NUCVdICAgY2lyY2xlW19uZ2NvbnRlbnQtJUNPTVAlXXtmaWx0ZXI6ZHJvcC1zaGFkb3coMCAwIDFweCByZ2JhKDAsIDAsIDAsIDAuNikpO3N0cm9rZTojZmZmO3dpbGwtY2hhbmdlOnRyYW5zZm9ybX0uY29udGVudFtfbmdjb250ZW50LSVDT01QJV0gICAuYmFzZWxpbmVbX25nY29udGVudC0lQ09NUCVde3N0cm9rZS1vcGFjaXR5Oi4xO3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlOmN1cnJlbnRDb2xvcjt3aWR0aDoxMDAlfS5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC50b29sdGlwW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHBhdGhbX25nY29udGVudC0lQ09NUCVde3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlOmN1cnJlbnRDb2xvcjtmaWxsOnJnYmEoMCwwLDAsMCl9LmNvbnRlbnRbX25nY29udGVudC0lQ09NUCVdICAgLm5vLWNvbG9yW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjpyZ2JhKDIyMSwyMjEsMjIxLC40KSAhaW1wb3J0YW50fS5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5uby1jb2xvcltfbmdjb250ZW50LSVDT01QJV0gICBwYXRoW19uZ2NvbnRlbnQtJUNPTVAlXXtzdHJva2Utb3BhY2l0eTouMn1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuY29udGVudFtfbmdjb250ZW50LSVDT01QJV0gICAubm8tY29sb3JbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuY29udGVudFtfbmdjb250ZW50LSVDT01QJV0gICAubm8tY29sb3JbX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoNTEsNTEsNTEsLjQpICFpbXBvcnRhbnR9Lm9mZnNldFtfbmdjb250ZW50LSVDT01QJV0gICAuY29udGVudFtfbmdjb250ZW50LSVDT01QJV0gICAuaGlzdG9ncmFtc1tfbmdjb250ZW50LSVDT01QJV0gICBwYXRoW19uZ2NvbnRlbnQtJUNPTVAlXXtzdHJva2U6I2ZmZn1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAub2Zmc2V0W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5oaXN0b2dyYW1zW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHBhdGhbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAub2Zmc2V0W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5oaXN0b2dyYW1zW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHBhdGhbX25nY29udGVudC0lQ09NUCVde3N0cm9rZTojNTU1fS5vZmZzZXRbX25nY29udGVudC0lQ09NUCVdICAgLmNvbnRlbnRbX25nY29udGVudC0lQ09NUCVdICAgLmhpc3RvZ3JhbXMubGlua2VkLXRpbWUtc2luZ2xlLXN0ZXBbX25nY29udGVudC0lQ09NUCVdICAgW19uZ2NvbnRlbnQtJUNPTVAlXTpub3QoLm5vLWNvbG9yKSAgIHBhdGhbX25nY29udGVudC0lQ09NUCVde3N0cm9rZTojMDAwfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5vZmZzZXRbX25nY29udGVudC0lQ09NUCVdICAgLmNvbnRlbnRbX25nY29udGVudC0lQ09NUCVdICAgLmhpc3RvZ3JhbXMubGlua2VkLXRpbWUtc2luZ2xlLXN0ZXBbX25nY29udGVudC0lQ09NUCVdICAgW19uZ2NvbnRlbnQtJUNPTVAlXTpub3QoLm5vLWNvbG9yKSAgIHBhdGhbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAub2Zmc2V0W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5oaXN0b2dyYW1zLmxpbmtlZC10aW1lLXNpbmdsZS1zdGVwW19uZ2NvbnRlbnQtJUNPTVAlXSAgIFtfbmdjb250ZW50LSVDT01QJV06bm90KC5uby1jb2xvcikgICBwYXRoW19uZ2NvbnRlbnQtJUNPTVAlXXtzdHJva2U6I2ZmZn0ub3ZlcmxheVtfbmdjb250ZW50LSVDT01QJV0gICAueC1heGlzW19uZ2NvbnRlbnQtJUNPTVAlXSAgICAgLnRpY2sgbGluZXtkaXNwbGF5Om5vbmV9Lm92ZXJsYXlbX25nY29udGVudC0lQ09NUCVdICAgLmNvbnRlbnRbX25nY29udGVudC0lQ09NUCVdICAgcGF0aFtfbmdjb250ZW50LSVDT01QJV17ZmlsbC1vcGFjaXR5OjA7c3Ryb2tlOmN1cnJlbnRDb2xvcn0udG9vbHRpcFtfbmdjb250ZW50LSVDT01QJV0sIC5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIGNpcmNsZVtfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpub25lfS5tYWluW19uZ2NvbnRlbnQtJUNPTVAlXTpob3ZlciAgIC5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIGNpcmNsZVtfbmdjb250ZW50LSVDT01QJV0sIC5tYWluW19uZ2NvbnRlbnQtJUNPTVAlXTpob3ZlciAgIC50b29sdGlwW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmJsb2NrfSddLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gTkcobix0KXtyZXR1cm4gbi5yZWR1Y2UoKGUsaSk9Pk1hdGgubWF4KGUsdChpKSksLTEvMCl9ZnVuY3Rpb24gYnBlKG4sdCxlKXtlfHwoZT10KTtsZXQgaT0xLzAscj0tMS8wO2ZvcihsZXQgbyBvZiBuKWk9TWF0aC5taW4oaSx0KG8pKSxyPU1hdGgubWF4KHIsZShvKSk7cmV0dXJue21pbjppLG1heDpyfX1mdW5jdGlvbiB4RShuKXtyZXR1cm4gbi54Ky41Km4uZHh9ZnVuY3Rpb24gdlhlKG4sdCl7MSZuJiYoXygwLCJzcGFuIiwxNCksTygxLCJtYXQtc3Bpbm5lciIsMTUpLHYoKSl9ZnVuY3Rpb24geVhlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwidGItaGlzdG9ncmFtIiwxNiksUCgib25MaW5rZWRUaW1lU2VsZWN0aW9uQ2hhbmdlZCIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5vbkxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkLmVtaXQocikpfSkoIm9uTGlua2VkVGltZVRvZ2dsZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5vbkxpbmtlZFRpbWVUb2dnbGVkLmVtaXQoKSl9KSx2KCl9aWYoMiZuKXtsZXQgZT1TKCk7eSgiZGF0YSIsZS5kYXRhKSgibW9kZSIsZS5tb2RlKSgidGltZVByb3BlcnR5IixlLnRpbWVQcm9wZXJ0eShlLnhBeGlzVHlwZSkpKCJjb2xvciIsZS5ydW5Db2xvclNjYWxlKGUucnVuSWQpKSgidGltZVNlbGVjdGlvbiIsZS5jb252ZXJ0VG9UaW1lU2VsZWN0aW9uKGUubGlua2VkVGltZVNlbGVjdGlvbikpfX1mdW5jdGlvbiBiWGUobix0KXsxJm4mJihfKDAsImRpdiIsMTgpLEEoMSwiIERhdGEgZmFpbGVkIHRvIGxvYWQuICIpLHYoKSl9ZnVuY3Rpb24geFhlKG4sdCl7aWYoMSZuJiZFKDAsYlhlLDIsMCwiZGl2IiwxNyksMiZuKXtsZXQgZT1TKCk7eSgibmdJZiIsZS5sb2FkU3RhdGU9PT1lLkRhdGFMb2FkU3RhdGUuRkFJTEVEKX19dmFyIENYZT1mdW5jdGlvbihuKXtyZXR1cm57YmFja2dyb3VuZENvbG9yOm59fSx4cGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuRGF0YUxvYWRTdGF0ZT1PZSx0aGlzLm9uRnVsbFNpemVUb2dnbGU9bmV3IEcsdGhpcy5vblBpbkNsaWNrZWQ9bmV3IEcsdGhpcy5vbkxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkPW5ldyBHLHRoaXMub25MaW5rZWRUaW1lVG9nZ2xlZD1uZXcgR310aW1lUHJvcGVydHkoZSl7c3dpdGNoKGUpe2Nhc2UgSmkuU1RFUDpyZXR1cm4gUm8uU1RFUDtjYXNlIEppLldBTExfVElNRTpyZXR1cm4gUm8uV0FMTF9USU1FO2Nhc2UgSmkuUkVMQVRJVkU6cmV0dXJuIFJvLlJFTEFUSVZFO2RlZmF1bHQ6dGhyb3cgbmV3IEVycm9yKCJJbnZhbGlkIHhBeGlzVHlwZSBmb3IgaGlzdG9ncmFtIHRpbWUgcHJvcGVydHkuIil9fWNvbnZlcnRUb1RpbWVTZWxlY3Rpb24oZSl7cmV0dXJuIG51bGw9PT1lP251bGw6e3N0YXJ0OntzdGVwOmUuc3RhcnRTdGVwfSxlbmQ6ZS5lbmRTdGVwP3tzdGVwOmUuZW5kU3RlcH06bnVsbH19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImhpc3RvZ3JhbS1jYXJkLWNvbXBvbmVudCJdXSxpbnB1dHM6e2xvYWRTdGF0ZToibG9hZFN0YXRlIix0aXRsZToidGl0bGUiLHRhZzoidGFnIixydW5JZDoicnVuSWQiLGRhdGE6ImRhdGEiLG1vZGU6Im1vZGUiLHhBeGlzVHlwZToieEF4aXNUeXBlIixydW5Db2xvclNjYWxlOiJydW5Db2xvclNjYWxlIixzaG93RnVsbFNpemU6InNob3dGdWxsU2l6ZSIsaXNQaW5uZWQ6ImlzUGlubmVkIixsaW5rZWRUaW1lU2VsZWN0aW9uOiJsaW5rZWRUaW1lU2VsZWN0aW9uIixpc0Nsb3Nlc3RTdGVwSGlnaGxpZ2h0ZWQ6ImlzQ2xvc2VzdFN0ZXBIaWdobGlnaHRlZCJ9LG91dHB1dHM6e29uRnVsbFNpemVUb2dnbGU6Im9uRnVsbFNpemVUb2dnbGUiLG9uUGluQ2xpY2tlZDoib25QaW5DbGlja2VkIixvbkxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkOiJvbkxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkIixvbkxpbmtlZFRpbWVUb2dnbGVkOiJvbkxpbmtlZFRpbWVUb2dnbGVkIn0sZGVjbHM6MTYsdmFyczoxNCxjb25zdHM6ZnVuY3Rpb24oKXtsZXQgdCxlO3JldHVybiB0PSRsb2NhbGl6ZWA6QSBidXR0b24gdG8gcGluIGEgY2FyZC7ikJ9lNjY1ZGM3MTJiZDVmMThkNGRmYTNhMjllMTI1ZDU2NWNjNTFlMmY24pCfNzI4NDYwNjQyNjIzNDM3NTM0NDpQaW4gY2FyZGAsZT0kbG9jYWxpemVgOkEgYnV0dG9uIG9uIGEgaGlzdG9ncmFtIGNhcmQgdGhhdCB0b2dnbGVzIGZ1bGwgc2l6ZSBtb2RlLuKQn2ZjOGY3NjdkMGI5ZjkzMDE4N2ExYmFlMzQ0NzdhZDI4NzM2ZWNlMzPikJ85MTU3MjE1NjM2Mzg5MjY1OTc6VG9nZ2xlIGZ1bGwgc2l6ZSBtb2RlYCxbWzEsImhlYWRpbmciXSxbMSwidGFnIl0sWzMsInRpdGxlIiwidmFsdWUiXSxbMywiaXNDbGlwcGVkIiwiaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkIl0sWzEsInJ1biJdLFsxLCJkb3QiLDMsIm5nU3R5bGUiXSxbMSwicnVuLXRleHQiLDMsInJ1bklkIl0sWzEsImNvbnRyb2xzIl0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJhcmlhLWxhYmVsIix0LDEsInBpbi1idXR0b24iLDMsImNsaWNrIl0sWzMsInN2Z0ljb24iXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsImFyaWEtbGFiZWwiLGUsInRpdGxlIiwiVG9nZ2xlIGZ1bGwgc2l6ZSBtb2RlIiwzLCJjbGljayJdLFsiY2xhc3MiLCJzcGlubmVyIiw0LCJuZ0lmIl0sWzMsImRhdGEiLCJtb2RlIiwidGltZVByb3BlcnR5IiwiY29sb3IiLCJ0aW1lU2VsZWN0aW9uIiwib25MaW5rZWRUaW1lU2VsZWN0aW9uQ2hhbmdlZCIsIm9uTGlua2VkVGltZVRvZ2dsZWQiLDQsIm5nSWYiLCJuZ0lmRWxzZSJdLFsibm9EYXRhIiwiIl0sWzEsInNwaW5uZXIiXSxbImRpYW1ldGVyIiwiMTgiXSxbMywiZGF0YSIsIm1vZGUiLCJ0aW1lUHJvcGVydHkiLCJjb2xvciIsInRpbWVTZWxlY3Rpb24iLCJvbkxpbmtlZFRpbWVTZWxlY3Rpb25DaGFuZ2VkIiwib25MaW5rZWRUaW1lVG9nZ2xlZCJdLFsiY2xhc3MiLCJlbXB0eS1tZXNzYWdlIiw0LCJuZ0lmIl0sWzEsImVtcHR5LW1lc3NhZ2UiXV19LHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoXygwLCJkaXYiLDApKDEsImRpdiIsMSksTygyLCJ0Yi10cnVuY2F0ZWQtcGF0aCIsMikoMywidmlzLWxpbmtlZC10aW1lLXNlbGVjdGlvbi13YXJuaW5nIiwzKSx2KCksXyg0LCJkaXYiLDQpLE8oNSwic3BhbiIsNSkoNiwiY2FyZC1ydW4tbmFtZSIsNiksdigpLF8oNywic3BhbiIsNykoOCwiYnV0dG9uIiw4KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblBpbkNsaWNrZWQuZW1pdCghaS5pc1Bpbm5lZCl9KSxPKDksIm1hdC1pY29uIiw5KSx2KCksXygxMCwiYnV0dG9uIiwxMCksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25GdWxsU2l6ZVRvZ2dsZS5lbWl0KCl9KSxPKDExLCJtYXQtaWNvbiIsOSksdigpKCksRSgxMix2WGUsMiwwLCJzcGFuIiwxMSksdigpLEUoMTMseVhlLDEsNSwidGItaGlzdG9ncmFtIiwxMiksRSgxNCx4WGUsMSwxLCJuZy10ZW1wbGF0ZSIsbnVsbCwxMyxxdCkpLDImZSl7bGV0IHI9JGUoMTUpO0MoMikseSgidGl0bGUiLGkudGFnKSgidmFsdWUiLGkudGl0bGUpLEMoMSkseSgiaXNDbGlwcGVkIixpLmxpbmtlZFRpbWVTZWxlY3Rpb24mJmkubGlua2VkVGltZVNlbGVjdGlvbi5jbGlwcGVkKSgiaXNDbG9zZXN0U3RlcEhpZ2hsaWdodGVkIixpLmlzQ2xvc2VzdFN0ZXBIaWdobGlnaHRlZCksQygyKSx5KCJuZ1N0eWxlIixPbigxMixDWGUsaS5ydW5Db2xvclNjYWxlKGkucnVuSWQpKSksQygxKSx5KCJydW5JZCIsaS5ydW5JZCksQygyKSx6ZSgidGl0bGUiLGkuaXNQaW5uZWQ/IlVucGluIGNhcmQiOiJQaW4gY2FyZCIpLEMoMSkseSgic3ZnSWNvbiIsaS5pc1Bpbm5lZD8ia2VlcF8yNHB4Ijoia2VlcF9vdXRsaW5lXzI0cHgiKSxDKDIpLHkoInN2Z0ljb24iLGkuc2hvd0Z1bGxTaXplPyJmdWxsc2NyZWVuX2V4aXRfMjRweCI6ImZ1bGxzY3JlZW5fMjRweCIpLEMoMSkseSgibmdJZiIsaS5sb2FkU3RhdGU9PT1pLkRhdGFMb2FkU3RhdGUuTE9BRElORyksQygxKSx5KCJuZ0lmIixpLmRhdGEmJmkuZGF0YS5sZW5ndGgpKCJuZ0lmRWxzZSIscil9fSxkZXBlbmRlbmNpZXM6W0JlLHp1LFdrLF9uLEd0LEJvLHlSLHV5LGR5XSxzdHlsZXM6WydbX25naG9zdC0lQ09NUCVde2JveC1zaXppbmc6Ym9yZGVyLWJveDtkaXNwbGF5OmZsZXg7ZmxleC1iYXNpczozMThweDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47ZmxleC1ncm93OjE7aGVpZ2h0OjEwMCU7b3ZlcmZsb3c6YXV0bztwYWRkaW5nOjE2cHg7cGFkZGluZy10b3A6NHB4fS5oZWFkaW5nW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7ZGlzcGxheTpncmlkO2dyaWQtdGVtcGxhdGUtYXJlYXM6InRhZyBjb250cm9scyIgInJ1biBzcGlubmVyIjtncmlkLXRlbXBsYXRlLWNvbHVtbnM6MWZyIGF1dG87Zm9udC1zaXplOjE0cHg7bWFyZ2luLWJvdHRvbTo0cHh9LnRhZ1tfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleDtnYXA6NXB4O2dyaWQtYXJlYTp0YWc7b3ZlcmZsb3c6aGlkZGVufS50YWdbX25nY29udGVudC0lQ09NUCVdICAgdmlzLXNlbGVjdGVkLXRpbWUtY2xpcHBlZFtfbmdjb250ZW50LSVDT01QJV17bGluZS1oZWlnaHQ6MH0ucGluLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV0gICBtYXQtaWNvbltfbmdjb250ZW50LSVDT01QJV17aGVpZ2h0OjE4cHh9LnJ1bltfbmdjb250ZW50LSVDT01QJV17Z3JpZC1hcmVhOnJ1bjtkaXNwbGF5OmZsZXg7d2hpdGUtc3BhY2U6bm93cmFwO2ZvbnQtc2l6ZToxM3B4fS5ydW5bX25nY29udGVudC0lQ09NUCVdICAgLmRvdFtfbmdjb250ZW50LSVDT01QJV17ZmxleDpub25lO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3dpZHRoOjEzcHg7aGVpZ2h0OjEzcHg7Ym9yZGVyLXJhZGl1czo1MCU7bWFyZ2luLXJpZ2h0OjRweH0ucnVuW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5ydW4tdGV4dFtfbmdjb250ZW50LSVDT01QJV17b3ZlcmZsb3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXM7bWF4LXdpZHRoOjEyMHB4fS5jb250cm9sc1tfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTt3aGl0ZS1zcGFjZTpub3dyYXA7Z3JpZC1hcmVhOmNvbnRyb2xzO2p1c3RpZnktc2VsZjpmbGV4LWVuZDtmbGV4LXNocmluazowO21hcmdpbi1yaWdodDotMTJweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuY29udHJvbHNbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuY29udHJvbHNbX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfS5zcGlubmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmZsZXg7Z3JpZC1hcmVhOnNwaW5uZXI7aGVpZ2h0OjEwMCU7anVzdGlmeS1jb250ZW50OmNlbnRlcjtwb3NpdGlvbjpyZWxhdGl2ZX1tYXQtc3Bpbm5lcltfbmdjb250ZW50LSVDT01QJV17dG9wOjA7cmlnaHQ6MDtwb3NpdGlvbjphYnNvbHV0ZX10Yi1oaXN0b2dyYW1bX25nY29udGVudC0lQ09NUCVde2ZsZXgtZ3JvdzoxfS5lbXB0eS1tZXNzYWdlW19uZ2NvbnRlbnQtJUNPTVAlXXttYXJnaW4tdG9wOjFlbTtmb250LXNpemU6MTNweH0nXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLENwZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLmZ1bGxXaWR0aENoYW5nZWQ9bmV3IEcsdGhpcy5mdWxsSGVpZ2h0Q2hhbmdlZD1uZXcgRyx0aGlzLnBpblN0YXRlQ2hhbmdlZD1uZXcgRyx0aGlzLm1vZGUkPXRoaXMuc3RvcmUuc2VsZWN0KFJJKSx0aGlzLnhBeGlzVHlwZSQ9dGhpcy5zdG9yZS5zZWxlY3QodGQpLHRoaXMuc2hvd0Z1bGxTaXplPSExfWlzSGlzdG9ncmFtQ2FyZE1ldGFkYXRhKGUpe2xldHtwbHVnaW46aX09ZTtyZXR1cm4gaT09PXJpLkhJU1RPR1JBTVN9b25GdWxsU2l6ZVRvZ2dsZSgpe3RoaXMuc2hvd0Z1bGxTaXplPSF0aGlzLnNob3dGdWxsU2l6ZSx0aGlzLmZ1bGxXaWR0aENoYW5nZWQuZW1pdCh0aGlzLnNob3dGdWxsU2l6ZSksdGhpcy5mdWxsSGVpZ2h0Q2hhbmdlZC5lbWl0KHRoaXMuc2hvd0Z1bGxTaXplKX1uZ09uSW5pdCgpe2xldCBpPXRoaXMuc3RvcmUuc2VsZWN0KHRjLHRoaXMuY2FyZElkKS5waXBlKFllKG89PiEhbyYmdGhpcy5pc0hpc3RvZ3JhbUNhcmRNZXRhZGF0YShvKSksTChvPT5vKSkscj1MdChbaSx0aGlzLnN0b3JlLnNlbGVjdCh4aCx0aGlzLmNhcmRJZCldKTt0aGlzLmRhdGEkPXIucGlwZShMKChbbyxzXSk9PntsZXQgYT1vLnJ1bklkO3JldHVybiBzJiZzLmhhc093blByb3BlcnR5KGEpP2Z1bmN0aW9uKG4sdD0zMCl7aWYoIW4ubGVuZ3RofHx0PDEpcmV0dXJuW107bGV0IGU9ZnVuY3Rpb24obil7bGV0IHQ9bnVsbCxlPW51bGw7Zm9yKGxldHtiaW5zOml9b2Ygbil7aWYoIWkubGVuZ3RoKWNvbnRpbnVlO2xldCByPWlbaS5sZW5ndGgtMV0sbz1pWzBdLngscz1yLngrci5keDsobnVsbD09PXR8fG88dCkmJih0PW8pLChudWxsPT09ZXx8cz5lKSYmKGU9cyl9cmV0dXJuIG51bGw9PT10fHxudWxsPT09ZT9udWxsOntsZWZ0OnQscmlnaHQ6ZX19KG4pO3JldHVybiBlJiZlLmxlZnQ9PT1lLnJpZ2h0JiYoZS5yaWdodD0xLjEqZS5yaWdodCsxLGUubGVmdD1lLmxlZnQvMS4xLTEpLG4ubWFwKGk9Pih7c3RlcDppLnN0ZXAsd2FsbFRpbWU6aS53YWxsVGltZSxiaW5zOmU/UVllKGkuYmlucyxlLHQpOltdfSkpfShzW2FdLm1hcCh1PT57bGV0e3dhbGxUaW1lOmQsc3RlcDpwfT11O3JldHVybnt3YWxsVGltZTpkLHN0ZXA6cCxiaW5zOnUuYmlucy5tYXAoZj0+KHt4OmYubWluLGR4OmYubWF4LWYubWluLHk6Zi5jb3VudH0pKX19KSk6W119KSksdGhpcy5zdGVwcyQ9dGhpcy5kYXRhJC5waXBlKEwobz0+by5tYXAocz0+cy5zdGVwKSkpLHRoaXMubGlua2VkVGltZVNlbGVjdGlvbiQ9THQoW3RoaXMuc3RvcmUuc2VsZWN0KFhtKSx0aGlzLnN0ZXBzJF0pLnBpcGUoTCgoW28sc10pPT57aWYoIW8pcmV0dXJuIG51bGw7bGV0IGE9MS8wLGw9LTEvMDtmb3IobGV0IHUgb2YgcylhPU1hdGgubWluKHUsYSksbD1NYXRoLm1heCh1LGwpO3JldHVybiBmdW5jdGlvbihuLHQpe2lmKG51bGwhPT1uLmVuZFN0ZXApcmV0dXJuIG47bGV0IGU9ZnVuY3Rpb24obix0KXtsZXQgZT0xLzAsaT1udWxsO2ZvcihsZXQgciBvZiB0KXtsZXQgbz1NYXRoLmFicyhuLXIpO288ZSYmKGU9byxpPXIpfXJldHVybiBpfShuLnN0YXJ0U3RlcCx0KTtyZXR1cm4gbnVsbCE9PWU/ey4uLm4sc3RhcnRTdGVwOmV9Om59KFFoKG8sYSxsKSxzKX0pKSx0aGlzLmlzQ2xvc2VzdFN0ZXBIaWdobGlnaHRlZCQ9THQoW3RoaXMuc3RvcmUuc2VsZWN0KFhtKSx0aGlzLmxpbmtlZFRpbWVTZWxlY3Rpb24kXSkucGlwZShMKChbbyxzXSk9Pm8mJnMmJiFzLmNsaXBwZWQmJm51bGw9PT1vLmVuZCYmby5zdGFydC5zdGVwIT09cy5zdGFydFN0ZXApKSx0aGlzLmxvYWRTdGF0ZSQ9dGhpcy5zdG9yZS5zZWxlY3QoYmgsdGhpcy5jYXJkSWQpLHRoaXMudGFnJD1pLnBpcGUoTChvPT5vLnRhZykpLHRoaXMudGl0bGUkPXRoaXMudGFnJC5waXBlKEwobz0+bHkobyx0aGlzLmdyb3VwTmFtZSkpKSx0aGlzLnJ1bklkJD1pLnBpcGUoTChvPT5vLnJ1bklkKSksdGhpcy5pc1Bpbm5lZCQ9dGhpcy5zdG9yZS5zZWxlY3QoQ2gsdGhpcy5jYXJkSWQpfW9uTGlua2VkVGltZVNlbGVjdGlvbkNoYW5nZWQoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChZaChlKSl9b25MaW5rZWRUaW1lVG9nZ2xlZCgpe3RoaXMuc3RvcmUuZGlzcGF0Y2goWGgoe2FmZm9yZGFuY2U6YmwuRk9CX0RFU0VMRUNUfSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siaGlzdG9ncmFtLWNhcmQiXV0saW5wdXRzOntjYXJkSWQ6ImNhcmRJZCIsZ3JvdXBOYW1lOiJncm91cE5hbWUiLHJ1bkNvbG9yU2NhbGU6InJ1bkNvbG9yU2NhbGUifSxvdXRwdXRzOntmdWxsV2lkdGhDaGFuZ2VkOiJmdWxsV2lkdGhDaGFuZ2VkIixmdWxsSGVpZ2h0Q2hhbmdlZDoiZnVsbEhlaWdodENoYW5nZWQiLHBpblN0YXRlQ2hhbmdlZDoicGluU3RhdGVDaGFuZ2VkIn0sZGVjbHM6MTEsdmFyczozMixjb25zdHM6W1szLCJsb2FkU3RhdGUiLCJ0aXRsZSIsInRhZyIsInJ1bklkIiwiZGF0YSIsIm1vZGUiLCJ4QXhpc1R5cGUiLCJydW5Db2xvclNjYWxlIiwic2hvd0Z1bGxTaXplIiwiaXNQaW5uZWQiLCJpc0Nsb3Nlc3RTdGVwSGlnaGxpZ2h0ZWQiLCJsaW5rZWRUaW1lU2VsZWN0aW9uIiwib25GdWxsU2l6ZVRvZ2dsZSIsIm9uUGluQ2xpY2tlZCIsIm9uTGlua2VkVGltZVNlbGVjdGlvbkNoYW5nZWQiLCJvbkxpbmtlZFRpbWVUb2dnbGVkIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJoaXN0b2dyYW0tY2FyZC1jb21wb25lbnQiLDApLFAoIm9uRnVsbFNpemVUb2dnbGUiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25GdWxsU2l6ZVRvZ2dsZSgpfSkoIm9uUGluQ2xpY2tlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkucGluU3RhdGVDaGFuZ2VkLmVtaXQobyl9KSgib25MaW5rZWRUaW1lU2VsZWN0aW9uQ2hhbmdlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25MaW5rZWRUaW1lU2VsZWN0aW9uQ2hhbmdlZChvKX0pKCJvbkxpbmtlZFRpbWVUb2dnbGVkIixmdW5jdGlvbigpe3JldHVybiBpLm9uTGlua2VkVGltZVRvZ2dsZWQoKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLEIoNCwiYXN5bmMiKSxCKDUsImFzeW5jIiksQig2LCJhc3luYyIpLEIoNywiYXN5bmMiKSxCKDgsImFzeW5jIiksQig5LCJhc3luYyIpLEIoMTAsImFzeW5jIiksdigpKSwyJmUmJnkoImxvYWRTdGF0ZSIsVSgxLDEyLGkubG9hZFN0YXRlJCkpKCJ0aXRsZSIsVSgyLDE0LGkudGl0bGUkKSkoInRhZyIsVSgzLDE2LGkudGFnJCkpKCJydW5JZCIsVSg0LDE4LGkucnVuSWQkKSkoImRhdGEiLFUoNSwyMCxpLmRhdGEkKSkoIm1vZGUiLFUoNiwyMixpLm1vZGUkKSkoInhBeGlzVHlwZSIsVSg3LDI0LGkueEF4aXNUeXBlJCkpKCJydW5Db2xvclNjYWxlIixpLnJ1bkNvbG9yU2NhbGUpKCJzaG93RnVsbFNpemUiLGkuc2hvd0Z1bGxTaXplKSgiaXNQaW5uZWQiLFUoOCwyNixpLmlzUGlubmVkJCkpKCJpc0Nsb3Nlc3RTdGVwSGlnaGxpZ2h0ZWQiLFUoOSwyOCxpLmlzQ2xvc2VzdFN0ZXBIaWdobGlnaHRlZCQpKSgibGlua2VkVGltZVNlbGVjdGlvbiIsVSgxMCwzMCxpLmxpbmtlZFRpbWVTZWxlY3Rpb24kKSl9LGRlcGVuZGVuY2llczpbeHBlLEdlXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVdIHtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcbiAgICAgICAgaGVpZ2h0OiAxMDAlO1xuICAgICAgfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gU1hlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiaW1hZ2UtY2FyZCIsNiksUCgiZnVsbFdpZHRoQ2hhbmdlZCIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoMikub25GdWxsV2lkdGhDaGFuZ2VkKHIpKX0pKCJwaW5TdGF0ZUNoYW5nZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoMikub25QaW5TdGF0ZUNoYW5nZWQoKSl9KSx2KCl9aWYoMiZuKXtsZXQgZT1TKDIpO3koImNhcmRJZCIsZS5jYXJkSWQpKCJncm91cE5hbWUiLGUuZ3JvdXBOYW1lKSgicnVuQ29sb3JTY2FsZSIsZS5ydW5Db2xvclNjYWxlKX19ZnVuY3Rpb24gRVhlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwic2NhbGFyLWNhcmQiLDcpLFAoImZ1bGxXaWR0aENoYW5nZWQiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKDIpLm9uRnVsbFdpZHRoQ2hhbmdlZChyKSl9KSgiZnVsbEhlaWdodENoYW5nZWQiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKDIpLm9uRnVsbEhlaWdodENoYW5nZWQocikpfSkoInBpblN0YXRlQ2hhbmdlZCIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygyKS5vblBpblN0YXRlQ2hhbmdlZCgpKX0pLHYoKX1pZigyJm4pe2xldCBlPVMoMik7eSgiY2FyZElkIixlLmNhcmRJZCkoImdyb3VwTmFtZSIsZS5ncm91cE5hbWUpfX1mdW5jdGlvbiBUWGUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJoaXN0b2dyYW0tY2FyZCIsOCksUCgiZnVsbFdpZHRoQ2hhbmdlZCIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoMikub25GdWxsV2lkdGhDaGFuZ2VkKHIpKX0pKCJmdWxsSGVpZ2h0Q2hhbmdlZCIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoMikub25GdWxsSGVpZ2h0Q2hhbmdlZChyKSl9KSgicGluU3RhdGVDaGFuZ2VkIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKDIpLm9uUGluU3RhdGVDaGFuZ2VkKCkpfSksdigpfWlmKDImbil7bGV0IGU9UygyKTt5KCJjYXJkSWQiLGUuY2FyZElkKSgiZ3JvdXBOYW1lIixlLmdyb3VwTmFtZSkoInJ1bkNvbG9yU2NhbGUiLGUucnVuQ29sb3JTY2FsZSl9fWZ1bmN0aW9uIERYZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiksQSgxKSx2KCkpLDImbil7bGV0IGU9UygyKTtDKDEpLGplKCJQbGFjZWhvbGRlciBlcnJvciBmb3I6ICIsZS5jYXJkSWQsIiIpfX1mdW5jdGlvbiBBWGUobix0KXtpZigxJm4mJihzbigwLDEpLEUoMSxTWGUsMSwzLCJpbWFnZS1jYXJkIiwyKSxFKDIsRVhlLDEsMiwic2NhbGFyLWNhcmQiLDMpLEUoMyxUWGUsMSwzLCJoaXN0b2dyYW0tY2FyZCIsNCksRSg0LERYZSwyLDEsImRpdiIsNSksYW4oKSksMiZuKXtsZXQgZT1TKCk7eSgibmdTd2l0Y2giLGUucGx1Z2luVHlwZSksQygxKSx5KCJuZ1N3aXRjaENhc2UiLGUuUGx1Z2luVHlwZS5JTUFHRVMpLEMoMSkseSgibmdTd2l0Y2hDYXNlIixlLlBsdWdpblR5cGUuU0NBTEFSUyksQygxKSx5KCJuZ1N3aXRjaENhc2UiLGUuUGx1Z2luVHlwZS5ISVNUT0dSQU1TKX19dmFyIE1wZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5QbHVnaW5UeXBlPXJpLHRoaXMuZnVsbFdpZHRoQ2hhbmdlZD1uZXcgRyx0aGlzLmZ1bGxIZWlnaHRDaGFuZ2VkPW5ldyBHLHRoaXMucGluU3RhdGVDaGFuZ2VkPW5ldyBHfW9uRnVsbFdpZHRoQ2hhbmdlZChlKXt0aGlzLmZ1bGxXaWR0aENoYW5nZWQuZW1pdChlKX1vbkZ1bGxIZWlnaHRDaGFuZ2VkKGUpe3RoaXMuZnVsbEhlaWdodENoYW5nZWQuZW1pdChlKX1vblBpblN0YXRlQ2hhbmdlZCgpe3RoaXMucGluU3RhdGVDaGFuZ2VkLmVtaXQoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siY2FyZC12aWV3LWNvbXBvbmVudCJdXSxpbnB1dHM6e2lzRXZlclZpc2libGU6ImlzRXZlclZpc2libGUiLGNhcmRJZDoiY2FyZElkIixncm91cE5hbWU6Imdyb3VwTmFtZSIscGx1Z2luVHlwZToicGx1Z2luVHlwZSIscnVuQ29sb3JTY2FsZToicnVuQ29sb3JTY2FsZSJ9LG91dHB1dHM6e2Z1bGxXaWR0aENoYW5nZWQ6ImZ1bGxXaWR0aENoYW5nZWQiLGZ1bGxIZWlnaHRDaGFuZ2VkOiJmdWxsSGVpZ2h0Q2hhbmdlZCIscGluU3RhdGVDaGFuZ2VkOiJwaW5TdGF0ZUNoYW5nZWQifSxkZWNsczoxLHZhcnM6MSxjb25zdHM6W1szLCJuZ1N3aXRjaCIsNCwibmdJZiJdLFszLCJuZ1N3aXRjaCJdLFszLCJjYXJkSWQiLCJncm91cE5hbWUiLCJydW5Db2xvclNjYWxlIiwiZnVsbFdpZHRoQ2hhbmdlZCIsInBpblN0YXRlQ2hhbmdlZCIsNCwibmdTd2l0Y2hDYXNlIl0sWzMsImNhcmRJZCIsImdyb3VwTmFtZSIsImZ1bGxXaWR0aENoYW5nZWQiLCJmdWxsSGVpZ2h0Q2hhbmdlZCIsInBpblN0YXRlQ2hhbmdlZCIsNCwibmdTd2l0Y2hDYXNlIl0sWzMsImNhcmRJZCIsImdyb3VwTmFtZSIsInJ1bkNvbG9yU2NhbGUiLCJmdWxsV2lkdGhDaGFuZ2VkIiwiZnVsbEhlaWdodENoYW5nZWQiLCJwaW5TdGF0ZUNoYW5nZWQiLDQsIm5nU3dpdGNoQ2FzZSJdLFs0LCJuZ1N3aXRjaERlZmF1bHQiXSxbMywiY2FyZElkIiwiZ3JvdXBOYW1lIiwicnVuQ29sb3JTY2FsZSIsImZ1bGxXaWR0aENoYW5nZWQiLCJwaW5TdGF0ZUNoYW5nZWQiXSxbMywiY2FyZElkIiwiZ3JvdXBOYW1lIiwiZnVsbFdpZHRoQ2hhbmdlZCIsImZ1bGxIZWlnaHRDaGFuZ2VkIiwicGluU3RhdGVDaGFuZ2VkIl0sWzMsImNhcmRJZCIsImdyb3VwTmFtZSIsInJ1bkNvbG9yU2NhbGUiLCJmdWxsV2lkdGhDaGFuZ2VkIiwiZnVsbEhlaWdodENoYW5nZWQiLCJwaW5TdGF0ZUNoYW5nZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJkUoMCxBWGUsNSw0LCJuZy1jb250YWluZXIiLDApLDImZSYmeSgibmdJZiIsaS5pc0V2ZXJWaXNpYmxlKX0sZGVwZW5kZW5jaWVzOltCZSxDcixVcixjaCxuc2UsX3BlLENwZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksd3BlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuaXNFdmVyVmlzaWJsZT0hMSx0aGlzLmZ1bGxXaWR0aENoYW5nZWQ9bmV3IEcsdGhpcy5mdWxsSGVpZ2h0Q2hhbmdlZD1uZXcgRyx0aGlzLnJ1bkNvbG9yU2NhbGUkPXRoaXMuc3RvcmUuc2VsZWN0KG5jKS5waXBlKGIwKDM1MCx2b2lkIDAse2xlYWRpbmc6ITAsdHJhaWxpbmc6ITB9KSxMKGk9PnI9PmkuaGFzT3duUHJvcGVydHkocik/aVtyXToiI2ZmZiIpKX1vblZpc2liaWxpdHlDaGFuZ2Uoe3Zpc2libGU6ZX0pe3RoaXMuaXNFdmVyVmlzaWJsZT10aGlzLmlzRXZlclZpc2libGV8fGV9b25GdWxsV2lkdGhDaGFuZ2VkKGUpe3RoaXMuZnVsbFdpZHRoQ2hhbmdlZC5lbWl0KGUpfW9uRnVsbEhlaWdodENoYW5nZWQoZSl7dGhpcy5mdWxsSGVpZ2h0Q2hhbmdlZC5lbWl0KGUpfW9uUGluU3RhdGVDaGFuZ2VkKCl7dGhpcy5zdG9yZS5zZWxlY3QoQ2gsdGhpcy5jYXJkSWQpLnBpcGUoUXQoMSksV3QodGhpcy5zdG9yZS5zZWxlY3QoeWVlKSkpLnN1YnNjcmliZSgoW2UsaV0pPT57dGhpcy5zdG9yZS5kaXNwYXRjaChyeSh7Y2FyZElkOnRoaXMuY2FyZElkLGNhbkNyZWF0ZU5ld1BpbnM6aSx3YXNQaW5uZWQ6ZX0pKX0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siY2FyZC12aWV3Il1dLGlucHV0czp7Y2FyZElkOiJjYXJkSWQiLGdyb3VwTmFtZToiZ3JvdXBOYW1lIixwbHVnaW5UeXBlOiJwbHVnaW5UeXBlIn0sb3V0cHV0czp7ZnVsbFdpZHRoQ2hhbmdlZDoiZnVsbFdpZHRoQ2hhbmdlZCIsZnVsbEhlaWdodENoYW5nZWQ6ImZ1bGxIZWlnaHRDaGFuZ2VkIn0sZGVjbHM6Mix2YXJzOjcsY29uc3RzOltbIm9ic2VydmVJbnRlcnNlY3Rpb24iLCIiLCJpbnRlcnNlY3Rpb25PYnNlcnZlck1hcmdpbiIsIjIwMHB4IDIwMHB4IDIwMHB4IDIwMHB4IiwzLCJpc0V2ZXJWaXNpYmxlIiwiY2FyZElkIiwiZ3JvdXBOYW1lIiwicGx1Z2luVHlwZSIsInJ1bkNvbG9yU2NhbGUiLCJmdWxsV2lkdGhDaGFuZ2VkIiwiZnVsbEhlaWdodENoYW5nZWQiLCJwaW5TdGF0ZUNoYW5nZWQiLCJvblZpc2liaWxpdHlDaGFuZ2UiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImNhcmQtdmlldy1jb21wb25lbnQiLDApLFAoImZ1bGxXaWR0aENoYW5nZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uRnVsbFdpZHRoQ2hhbmdlZChvKX0pKCJmdWxsSGVpZ2h0Q2hhbmdlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25GdWxsSGVpZ2h0Q2hhbmdlZChvKX0pKCJwaW5TdGF0ZUNoYW5nZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25QaW5TdGF0ZUNoYW5nZWQoKX0pKCJvblZpc2liaWxpdHlDaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uVmlzaWJpbGl0eUNoYW5nZShvKX0pLEIoMSwiYXN5bmMiKSx2KCkpLDImZSYmeSgiaXNFdmVyVmlzaWJsZSIsaS5pc0V2ZXJWaXNpYmxlKSgiY2FyZElkIixpLmNhcmRJZCkoImdyb3VwTmFtZSIsaS5ncm91cE5hbWUpKCJwbHVnaW5UeXBlIixpLnBsdWdpblR5cGUpKCJydW5Db2xvclNjYWxlIixVKDEsNSxpLnJ1bkNvbG9yU2NhbGUkKSl9LGRlcGVuZGVuY2llczpbYXksTXBlLEdlXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2ZmZn1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojMzAzMDMwfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gT1hlKG4sdCl7MSZuJiZOaSgwKX12YXIga1hlPWZ1bmN0aW9uKG4sdCl7cmV0dXJueyJmdWxsLXdpZHRoIjpuLCJmdWxsLWhlaWdodCI6dH19O2Z1bmN0aW9uIEZYZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsNSkoMSwiY2FyZC12aWV3Iiw2KSxQKCJmdWxsV2lkdGhDaGFuZ2VkIixmdW5jdGlvbihyKXtsZXQgcz1vZShlKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoMikub25GdWxsV2lkdGhDaGFuZ2VkKHMuY2FyZElkLHIpKX0pKCJmdWxsSGVpZ2h0Q2hhbmdlZCIsZnVuY3Rpb24ocil7bGV0IHM9b2UoZSkuJGltcGxpY2l0O3JldHVybiBzZShTKDIpLm9uRnVsbEhlaWdodENoYW5nZWQocy5jYXJkSWQscikpfSksdigpKCl9aWYoMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPVMoMik7eSgibmdDbGFzcyIsUXIoNixrWGUsaS5jYXJkc0F0RnVsbFdpZHRoLmhhcyhlLmNhcmRJZCksaS5jYXJkc0F0RnVsbEhlaWdodC5oYXMoZS5jYXJkSWQpKSksQygxKSx5KCJjYXJkSWQiLGUuY2FyZElkKSgiZ3JvdXBOYW1lIixpLmdyb3VwTmFtZSkoInBsdWdpblR5cGUiLGUucGx1Z2luKSgiY2FyZE9ic2VydmVyIixpLmNhcmRPYnNlcnZlcikoImNhcmRMYXp5TG9hZGVyIixlLmNhcmRJZCl9fWZ1bmN0aW9uIE5YZShuLHQpezEmbiYmTmkoMCl9dmFyIExYZT1mdW5jdGlvbigpe3JldHVybntpc0JvdHRvbUNvbnRyb2w6ITF9fSxCWGU9ZnVuY3Rpb24oKXtyZXR1cm57aXNCb3R0b21Db250cm9sOiEwfX07ZnVuY3Rpb24gVlhlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiKSxFKDEsT1hlLDEsMCwibmctY29udGFpbmVyIiwyKSxfKDIsImRpdiIsMyksRSgzLEZYZSwyLDksImRpdiIsNCksdigpLEUoNCxOWGUsMSwwLCJuZy1jb250YWluZXIiLDIpLHYoKSksMiZuKXtsZXQgZT1TKCksaT0kZSgyKTtDKDEpLHkoIm5nVGVtcGxhdGVPdXRsZXQiLGkpKCJuZ1RlbXBsYXRlT3V0bGV0Q29udGV4dCIsUXAoOCxMWGUpKSxDKDEpLFB0KCJncmlkLXRlbXBsYXRlLWNvbHVtbnMiLGUuZ3JpZFRlbXBsYXRlQ29sdW1uKSxDKDEpLHkoIm5nRm9yT2YiLGUuY2FyZElkc1dpdGhNZXRhZGF0YSkoIm5nRm9yVHJhY2tCeSIsZS50cmFja0J5Q2FyZHMpLEMoMSkseSgibmdUZW1wbGF0ZU91dGxldCIsaSkoIm5nVGVtcGxhdGVPdXRsZXRDb250ZXh0IixRcCg5LEJYZSkpfX1mdW5jdGlvbiBIWGUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJidXR0b24iLDEyKSxQKCJjbGljayIsZnVuY3Rpb24ocil7b2UoZSk7bGV0IG89UygzKTtyZXR1cm4gc2Uoby5oYW5kbGVQYWdlQ2hhbmdlKG8ucGFnZUluZGV4LTEsci50YXJnZXQpKX0pLEEoMSwiIFByZXZpb3VzICIpLHYoKX0yJm4mJnkoImRpc2FibGVkIiwwPT09UygzKS5wYWdlSW5kZXgpfWZ1bmN0aW9uIFVYZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsInNwYW4iLDE1KSgxLCJpbnB1dCIsMTYpLFAoImlucHV0IixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUyg0KS5vblBhZ2luYXRpb25JbnB1dENoYW5nZShyKSl9KSgiY2hhbmdlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUyg0KS5vblBhZ2luYXRpb25JbnB1dENoYW5nZShyKSl9KSx2KCksQSgyKSx2KCl9aWYoMiZuKXtsZXQgZT1TKDQpO0MoMSkseSgidmFsdWUiLGUucGFnZUluZGV4KzEpKCJtYXgiLGUubnVtUGFnZXMpLEMoMSksamUoIiBvZiAiLGUubnVtUGFnZXMsIiIpfX1mdW5jdGlvbiB6WGUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJzcGFuIiksRSgxLFVYZSwzLDMsInNwYW4iLDEzKSxfKDIsImJ1dHRvbiIsMTQpLFAoImNsaWNrIixmdW5jdGlvbihyKXtvZShlKTtsZXQgbz1TKDMpO3JldHVybiBzZShvLmhhbmRsZVBhZ2VDaGFuZ2Uoby5wYWdlSW5kZXgrMSxyLnRhcmdldCkpfSksQSgzLCIgTmV4dCAiKSx2KCkoKX1pZigyJm4pe2xldCBlPVMoMikuaXNCb3R0b21Db250cm9sLGk9UygpO0MoMSkseSgibmdJZiIsaS5zaG93UGFnaW5hdGlvbklucHV0KGUpKSxDKDEpLHkoImRpc2FibGVkIixpLnBhZ2VJbmRleCsxPj1pLm51bVBhZ2VzKX19ZnVuY3Rpb24galhlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDgpKDEsInNwYW4iLDkpLEUoMixIWGUsMiwxLCJidXR0b24iLDEwKSx2KCksXygzLCJzcGFuIiwxMSksRSg0LHpYZSw0LDIsInNwYW4iLDApLHYoKSgpKSwyJm4pe2xldCBlPVMoMik7QygyKSx5KCJuZ0lmIixlLnNob3dQYWdpbmF0aW9uQ29udHJvbHMpLEMoMikseSgibmdJZiIsZS5zaG93UGFnaW5hdGlvbkNvbnRyb2xzKX19ZnVuY3Rpb24gR1hlKG4sdCl7MSZuJiZFKDAsalhlLDUsMiwiZGl2Iiw3KSwyJm4mJnkoIm5nSWYiLFMoKS5zaG93UGFnaW5hdGlvbkNvbnRyb2xzKX12YXIgU3BlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5jZGtTY3JvbGxhYmxlPWUsdGhpcy5QbHVnaW5UeXBlPXJpLHRoaXMuZ3JpZFRlbXBsYXRlQ29sdW1uPSIiLHRoaXMuY2FyZHNBdEZ1bGxXaWR0aD1uZXcgU2V0LHRoaXMuY2FyZHNBdEZ1bGxIZWlnaHQ9bmV3IFNldCx0aGlzLnBhZ2VJbmRleENoYW5nZWQ9bmV3IEd9bmdPbkluaXQoKXt0aGlzLmlzQ2FyZFdpZHRoVmFsaWQodGhpcy5jYXJkTWluV2lkdGgpJiYodGhpcy5ncmlkVGVtcGxhdGVDb2x1bW49YHJlcGVhdChhdXRvLWZpbGwsIG1pbm1heCgke3RoaXMuY2FyZE1pbldpZHRofXB4LCAxZnIpKWApfW5nT25DaGFuZ2VzKGUpe2lmKGUuY2FyZE1pbldpZHRoKXtsZXQgaT1lLmNhcmRNaW5XaWR0aC5jdXJyZW50VmFsdWU7dGhpcy5pc0NhcmRXaWR0aFZhbGlkKGkpPyh0aGlzLmNhcmRNaW5XaWR0aD1pLHRoaXMuZ3JpZFRlbXBsYXRlQ29sdW1uPWByZXBlYXQoYXV0by1maWxsLCBtaW5tYXgoJHt0aGlzLmNhcmRNaW5XaWR0aH1weCwgMWZyKSlgKTp0aGlzLmdyaWRUZW1wbGF0ZUNvbHVtbj0iIn19aXNDYXJkV2lkdGhWYWxpZChlKXtyZXR1cm4gZSYmZT49MzM1JiZlPD03MzV9c2hvd1BhZ2luYXRpb25JbnB1dChlKXtyZXR1cm4gZX1oYW5kbGVQYWdlQ2hhbmdlKGUsaSl7bGV0IHI9aS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3A7c2V0VGltZW91dCgoKT0+e3RoaXMuc2Nyb2xsVG9LZWVwVGFyZ2V0UG9zaXRpb24oaSxyKX0sMCksdGhpcy5wYWdlSW5kZXhDaGFuZ2VkLmVtaXQoZSl9c2Nyb2xsVG9LZWVwVGFyZ2V0UG9zaXRpb24oZSxpKXtsZXQgcj10aGlzLmNka1Njcm9sbGFibGU/LmdldEVsZW1lbnRSZWYoKS5uYXRpdmVFbGVtZW50O3ImJnIuc2Nyb2xsVG8oMCxlLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcC1pK3Iuc2Nyb2xsVG9wKX10cmFja0J5Q2FyZHMoZSxpKXtyZXR1cm4gaS5jYXJkSWR9b25QYWdpbmF0aW9uSW5wdXRDaGFuZ2UoZSl7bGV0IGk9ZS50YXJnZXQ7aWYoImlucHV0Ij09PWUudHlwZSYmIiI9PT1pLnZhbHVlKXJldHVybjtsZXQgcj1OdW1iZXIoaS52YWx1ZSktMSxvPU1hdGgubWluKE1hdGgubWF4KDAsciksdGhpcy5udW1QYWdlcy0xKTtpLnZhbHVlIT09U3RyaW5nKG8rMSkmJihpLnZhbHVlPVN0cmluZyhvKzEpKSx0aGlzLmhhbmRsZVBhZ2VDaGFuZ2UobyxpKX1vbkZ1bGxXaWR0aENoYW5nZWQoZSxpKXtpP3RoaXMuY2FyZHNBdEZ1bGxXaWR0aC5hZGQoZSk6dGhpcy5jYXJkc0F0RnVsbFdpZHRoLmRlbGV0ZShlKX1vbkZ1bGxIZWlnaHRDaGFuZ2VkKGUsaSl7aT90aGlzLmNhcmRzQXRGdWxsSGVpZ2h0LmFkZChlKTp0aGlzLmNhcmRzQXRGdWxsSGVpZ2h0LmRlbGV0ZShlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShJaCw4KSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1jYXJkLWdyaWQtY29tcG9uZW50Il1dLGlucHV0czp7aXNHcm91cEV4cGFuZGVkOiJpc0dyb3VwRXhwYW5kZWQiLHBhZ2VJbmRleDoicGFnZUluZGV4IixudW1QYWdlczoibnVtUGFnZXMiLGNhcmRJZHNXaXRoTWV0YWRhdGE6ImNhcmRJZHNXaXRoTWV0YWRhdGEiLGNhcmRNaW5XaWR0aDoiY2FyZE1pbldpZHRoIixjYXJkT2JzZXJ2ZXI6ImNhcmRPYnNlcnZlciIsc2hvd1BhZ2luYXRpb25Db250cm9sczoic2hvd1BhZ2luYXRpb25Db250cm9scyJ9LG91dHB1dHM6e3BhZ2VJbmRleENoYW5nZWQ6InBhZ2VJbmRleENoYW5nZWQifSxmZWF0dXJlczpbRnRdLGRlY2xzOjMsdmFyczoxLGNvbnN0czpmdW5jdGlvbigpe2xldCB0LGU7cmV0dXJuIHQ9JGxvY2FsaXplYDpBIGJ1dHRvbiB0aGF0IHNldHMgYSBncm91cCB0byB0aGUgcHJldmlvdXMgcGFnZS7ikJ81NzVlNzgyZmQyN2YyZWU3MGEwMzRhNzc1ZWZlOWFkMTYyNDcyMjUw4pCfMzYyOTk2MDU0NDg3NTM2MDA0NjpQcmV2aW91cyBwYWdlYCxlPSRsb2NhbGl6ZWA6QSBidXR0b24gdGhhdCBzZXRzIGEgZ3JvdXAgdG8gdGhlIG5leHQgcGFnZS7ikJ9jZTNjZWZiMWNkMDA5OWFhNTAwM2RkYTE2ZWM5ZWIyMWZkOGJhNzg54pCfMzMzNzMwMTY5NDIxMDI4NzU5NTpOZXh0IHBhZ2VgLFtbNCwibmdJZiJdLFsiZ3JvdXBDb250cm9scyIsIiJdLFs0LCJuZ1RlbXBsYXRlT3V0bGV0IiwibmdUZW1wbGF0ZU91dGxldENvbnRleHQiXSxbMSwiY2FyZC1ncmlkIl0sWyJjbGFzcyIsImNhcmQtc3BhY2UiLDMsIm5nQ2xhc3MiLDQsIm5nRm9yIiwibmdGb3JPZiIsIm5nRm9yVHJhY2tCeSJdLFsxLCJjYXJkLXNwYWNlIiwzLCJuZ0NsYXNzIl0sWzMsImNhcmRJZCIsImdyb3VwTmFtZSIsInBsdWdpblR5cGUiLCJjYXJkT2JzZXJ2ZXIiLCJjYXJkTGF6eUxvYWRlciIsImZ1bGxXaWR0aENoYW5nZWQiLCJmdWxsSGVpZ2h0Q2hhbmdlZCJdLFsiY2xhc3MiLCJncm91cC1jb250cm9scyIsNCwibmdJZiJdLFsxLCJncm91cC1jb250cm9scyJdLFsxLCJwcmV2LWNvbnRhaW5lciJdLFsiY2xhc3MiLCJwcmV2IHBhZ2luYXRpb24tYnV0dG9uIiwibWF0LWJ1dHRvbiIsIiIsImFyaWEtbGFiZWwiLHQsMywiZGlzYWJsZWQiLCJjbGljayIsNCwibmdJZiJdLFsxLCJpbnB1dC1hbmQtbmV4dC1jb250YWluZXIiXSxbIm1hdC1idXR0b24iLCIiLCJhcmlhLWxhYmVsIix0LDEsInByZXYiLCJwYWdpbmF0aW9uLWJ1dHRvbiIsMywiZGlzYWJsZWQiLCJjbGljayJdLFsiY2xhc3MiLCJwYWdpbmF0aW9uLWlucHV0Iiw0LCJuZ0lmIl0sWyJtYXQtYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsZSwxLCJuZXh0IiwicGFnaW5hdGlvbi1idXR0b24iLDMsImRpc2FibGVkIiwiY2xpY2siXSxbMSwicGFnaW5hdGlvbi1pbnB1dCJdLFsidHlwZSIsIm51bWJlciIsIm1pbiIsIjEiLDMsInZhbHVlIiwibWF4IiwiaW5wdXQiLCJjaGFuZ2UiXV19LHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoRSgwLFZYZSw1LDEwLCJkaXYiLDApLEUoMSxHWGUsMSwxLCJuZy10ZW1wbGF0ZSIsbnVsbCwxLHF0KSksMiZlJiZ5KCJuZ0lmIixpLmlzR3JvdXBFeHBhbmRlZCl9LGRlcGVuZGVuY2llczpbT29lLHdwZSxGbixkbixCZSxvcyxfbl0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXXtjb250YWluOmNvbnRlbnR9LmNhcmQtZ3JpZFtfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpncmlkO2dyaWQtdGVtcGxhdGUtY29sdW1uczpyZXBlYXQoYXV0by1maWxsLCBtaW5tYXgoMzM1cHgsIDFmcikpO2dhcDoxNnB4O3BhZGRpbmc6MTZweH0uY2FyZC1zcGFjZS5mdWxsLXdpZHRoW19uZ2NvbnRlbnQtJUNPTVAlXXtncmlkLWNvbHVtbi1zdGFydDoxO2dyaWQtY29sdW1uLWVuZDotMX0uY2FyZC1zcGFjZS5mdWxsLWhlaWdodFtfbmdjb250ZW50LSVDT01QJV17bWluLWhlaWdodDo0ODBweH0uY2FyZC1zcGFjZS5mdWxsLWhlaWdodFtfbmdjb250ZW50LSVDT01QJV0gICBjYXJkLXZpZXdbX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxMDAlfWNhcmQtdmlld1tfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyOjFweCBzb2xpZCAjZWJlYmViO2JvcmRlci1yYWRpdXM6NHB4O2JveC1zaXppbmc6Ym9yZGVyLWJveDtjb250YWluOmxheW91dCBwYWludDtkaXNwbGF5OmJsb2NrO21pbi1oZWlnaHQ6MzIwcHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgY2FyZC12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgY2FyZC12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXI6MXB4IHNvbGlkICM1NTV9Lmdyb3VwLWNvbnRyb2xzW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjojNjE2MTYxO2Rpc3BsYXk6Z3JpZDthbGlnbi1pdGVtczpjZW50ZXI7Z3JpZC10ZW1wbGF0ZS1jb2x1bW5zOjFmciAxZnI7Z2FwOjE2cHg7cGFkZGluZzowIDE2cHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmdyb3VwLWNvbnRyb2xzW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmdyb3VwLWNvbnRyb2xzW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX0uZ3JvdXAtY29udHJvbHNbX25nY29udGVudC0lQ09NUCVdOmZpcnN0LW9mLXR5cGV7cGFkZGluZy10b3A6MTZweH0uZ3JvdXAtY29udHJvbHNbX25nY29udGVudC0lQ09NUCVdOmxhc3Qtb2YtdHlwZXtwYWRkaW5nLWJvdHRvbToxNnB4fS5wcmV2LWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17anVzdGlmeS1zZWxmOmZsZXgtc3RhcnR9LmlucHV0LWFuZC1uZXh0LWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17anVzdGlmeS1zZWxmOmZsZXgtZW5kfS5wYWdpbmF0aW9uLWlucHV0W19uZ2NvbnRlbnQtJUNPTVAlXXttYXJnaW4tcmlnaHQ6MTZweH0ucGFnaW5hdGlvbi1pbnB1dFtfbmdjb250ZW50LSVDT01QJV0gICBpbnB1dFtfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZDpyZ2JhKDAsMCwwLDApO2JvcmRlcjoxcHggc29saWQgY3VycmVudENvbG9yO2NvbG9yOmluaGVyaXQ7Zm9udDppbmhlcml0fS5wYWdpbmF0aW9uLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTtiYWNrZ3JvdW5kLWNvbG9yOiNmZmZ9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnBhZ2luYXRpb24tYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnBhZ2luYXRpb24tYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX0ucGFnaW5hdGlvbi1idXR0b25bX25nY29udGVudC0lQ09NUCVdOmRpc2FibGVke2NvbG9yOiM3NTc1NzV9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnBhZ2luYXRpb24tYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXTpkaXNhYmxlZCwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnBhZ2luYXRpb24tYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXTpkaXNhYmxlZHtjb2xvcjojNjE2MTYxfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksQWI9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5ncm91cE5hbWU9bnVsbCx0aGlzLmdyb3VwTmFtZSQ9bmV3IGhyKG51bGwpLHRoaXMucGFnZUluZGV4JD1uZXcgaHIoMCksdGhpcy5pdGVtcyQ9bmV3IGhyKFtdKSx0aGlzLm5nVW5zdWJzY3JpYmU9bmV3IGtlLHRoaXMubnVtUGFnZXMkPUx0KFt0aGlzLml0ZW1zJCx0aGlzLnN0b3JlLnNlbGVjdChOYS5nZXRQYWdlU2l6ZSldKS5waXBlKEwoKFtpLHJdKT0+TWF0aC5jZWlsKGkubGVuZ3RoL3IpKSksdGhpcy5pc0dyb3VwRXhwYW5kZWQkPXRoaXMuZ3JvdXBOYW1lJC5waXBlKHVpKGk9Pm51bGwhPT1pP3RoaXMuc3RvcmUuc2VsZWN0KExJLGkpOlh0KCEwKSkpLHRoaXMuc2hvd1BhZ2luYXRpb25Db250cm9scyQ9dGhpcy5udW1QYWdlcyQucGlwZShMKGk9Pmk+MSkpLHRoaXMubm9ybWFsaXplZFBhZ2VJbmRleCQ9THQoW3RoaXMucGFnZUluZGV4JCx0aGlzLm51bVBhZ2VzJF0pLnBpcGUoc3QodGhpcy5uZ1Vuc3Vic2NyaWJlKSxrdCgoW2kscl0pPT57MCE9PXImJihpPj1yP3RoaXMucGFnZUluZGV4JC5uZXh0KHItMSk6aTwwJiZ0aGlzLnBhZ2VJbmRleCQubmV4dCgwKSl9KSxMKChbaSxyXSk9Pk1hdGgubWluKE1hdGgubWF4KGksMCksci0xKSksTWEoMSkpLHRoaXMucGFnZWRJdGVtcyQ9THQoW3RoaXMuaXRlbXMkLHRoaXMuc3RvcmUuc2VsZWN0KE5hLmdldFBhZ2VTaXplKSx0aGlzLm5vcm1hbGl6ZWRQYWdlSW5kZXgkLHRoaXMuaXNHcm91cEV4cGFuZGVkJF0pLnBpcGUoTCgoW2kscixvLHNdKT0+aS5zbGljZShyKm8scipvKyhzP3I6MCkpKSksdGhpcy5jYXJkTWluV2lkdGgkPXRoaXMuc3RvcmUuc2VsZWN0KGR2KX1uZ09uQ2hhbmdlcyhlKXtlLmNhcmRJZHNXaXRoTWV0YWRhdGEmJnRoaXMuaXRlbXMkLm5leHQodGhpcy5jYXJkSWRzV2l0aE1ldGFkYXRhKSxlLmdyb3VwTmFtZSYmdGhpcy5ncm91cE5hbWUkLm5leHQodGhpcy5ncm91cE5hbWUpfW5nT25EZXN0cm95KCl7dGhpcy5uZ1Vuc3Vic2NyaWJlLm5leHQoKSx0aGlzLm5nVW5zdWJzY3JpYmUuY29tcGxldGUoKX1vblBhZ2VJbmRleENoYW5nZWQoZSl7dGhpcy5wYWdlSW5kZXgkLm5leHQoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtZXRyaWNzLWNhcmQtZ3JpZCJdXSxpbnB1dHM6e2dyb3VwTmFtZToiZ3JvdXBOYW1lIixjYXJkSWRzV2l0aE1ldGFkYXRhOiJjYXJkSWRzV2l0aE1ldGFkYXRhIixjYXJkT2JzZXJ2ZXI6ImNhcmRPYnNlcnZlciJ9LGZlYXR1cmVzOltGdF0sZGVjbHM6Nyx2YXJzOjE5LGNvbnN0czpbWzMsImlzR3JvdXBFeHBhbmRlZCIsInBhZ2VJbmRleCIsIm51bVBhZ2VzIiwic2hvd1BhZ2luYXRpb25Db250cm9scyIsImNhcmRJZHNXaXRoTWV0YWRhdGEiLCJjYXJkTWluV2lkdGgiLCJjYXJkT2JzZXJ2ZXIiLCJwYWdlSW5kZXhDaGFuZ2VkIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJtZXRyaWNzLWNhcmQtZ3JpZC1jb21wb25lbnQiLDApLFAoInBhZ2VJbmRleENoYW5nZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uUGFnZUluZGV4Q2hhbmdlZChvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLEIoNCwiYXN5bmMiKSxCKDUsImFzeW5jIiksQig2LCJhc3luYyIpLHYoKSksMiZlJiZ5KCJpc0dyb3VwRXhwYW5kZWQiLFUoMSw3LGkuaXNHcm91cEV4cGFuZGVkJCkpKCJwYWdlSW5kZXgiLFUoMiw5LGkubm9ybWFsaXplZFBhZ2VJbmRleCQpKSgibnVtUGFnZXMiLFUoMywxMSxpLm51bVBhZ2VzJCkpKCJzaG93UGFnaW5hdGlvbkNvbnRyb2xzIixVKDQsMTMsaS5zaG93UGFnaW5hdGlvbkNvbnRyb2xzJCkpKCJjYXJkSWRzV2l0aE1ldGFkYXRhIixVKDUsMTUsaS5wYWdlZEl0ZW1zJCkpKCJjYXJkTWluV2lkdGgiLFUoNiwxNyxpLmNhcmRNaW5XaWR0aCQpKSgiY2FyZE9ic2VydmVyIixpLmNhcmRPYnNlcnZlcil9LGRlcGVuZGVuY2llczpbU3BlLEdlXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiBYWGUobix0KXtpZigxJm4mJihfKDAsInNwYW4iLDcpLEEoMSksQigyLCJudW1iZXIiKSx2KCkpLDImbil7bGV0IGU9UygpO0MoMSksamUoIiIsVSgyLDEsZS5udW1iZXJPZkNhcmRzKSwiIGNhcmRzIil9fWZ1bmN0aW9uIFFYZShuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsOCl9ZnVuY3Rpb24gS1hlKG4sdCl7MSZuJiZPKDAsIm1hdC1pY29uIiw5KX12YXIgRXBlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLmdyb3VwRXhwYW5zaW9uVG9nZ2xlZD1uZXcgR319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1jYXJkLWdyb3VwLXRvb2xiYXItY29tcG9uZW50Il1dLGlucHV0czp7Z3JvdXBOYW1lOiJncm91cE5hbWUiLG51bWJlck9mQ2FyZHM6Im51bWJlck9mQ2FyZHMiLGlzR3JvdXBFeHBhbmRlZDoiaXNHcm91cEV4cGFuZGVkIn0sb3V0cHV0czp7Z3JvdXBFeHBhbnNpb25Ub2dnbGVkOiJncm91cEV4cGFuc2lvblRvZ2dsZWQifSxkZWNsczo5LHZhcnM6NSxjb25zdHM6ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gdD0kbG9jYWxpemVgOkEgYnV0dG9uIHRoYXQgYWxsb3dzIHVzZXIgdG8gZXhwYW5kIGEgdGFnIGdyb3VwLuKQn2ZmYWExMTQ3MWI4NzhhNmRmZmUyZTY4YzZmMzcwNjRhOWUwNzQ4NTPikJ81Mzg2MDU0MzI1Mjc0Nzc5MjU4OkV4cGFuZCBncm91cGAsW1siYXJpYS1sYWJlbCIsdCwxLCJncm91cC10b29sYmFyIiwzLCJjbGljayJdLFsxLCJncm91cC10aXRsZS13cmFwcGVyIl0sWyJhcmlhLXJvbGUiLCJoZWFkaW5nIiwiYXJpYS1sZXZlbCIsIjMiLDEsImdyb3VwLXRpdGxlIiwzLCJ0aXRsZSJdLFsiY2xhc3MiLCJncm91cC1jYXJkLWNvdW50Iiw0LCJuZ0lmIl0sWzEsImV4cGFuZC1ncm91cC1pY29uIl0sWyJzdmdJY29uIiwiZXhwYW5kX2xlc3NfMjRweCIsNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJleHBhbmRNb3JlIiwiIl0sWzEsImdyb3VwLWNhcmQtY291bnQiXSxbInN2Z0ljb24iLCJleHBhbmRfbGVzc18yNHB4Il0sWyJzdmdJY29uIiwiZXhwYW5kX21vcmVfMjRweCJdXX0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJihfKDAsImJ1dHRvbiIsMCksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkuZ3JvdXBFeHBhbnNpb25Ub2dnbGVkLmVtaXQoKX0pLF8oMSwic3BhbiIsMSkoMiwic3BhbiIsMiksQSgzKSx2KCksRSg0LFhYZSwzLDMsInNwYW4iLDMpLHYoKSxfKDUsInNwYW4iLDQpLEUoNixRWGUsMSwwLCJtYXQtaWNvbiIsNSksRSg3LEtYZSwxLDAsIm5nLXRlbXBsYXRlIixudWxsLDYscXQpLHYoKSgpKSwyJmUpe2xldCByPSRlKDgpO0MoMiksWmkoInRpdGxlIixpLmdyb3VwTmFtZSksQygxKSx5dChpLmdyb3VwTmFtZSksQygxKSx5KCJuZ0lmIixpLm51bWJlck9mQ2FyZHM+MSksQygyKSx5KCJuZ0lmIixpLmlzR3JvdXBFeHBhbmRlZCkoIm5nSWZFbHNlIixyKX19LGRlcGVuZGVuY2llczpbQmUsR3QsUWxdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZmZmO2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7YWxpZ24taXRlbXM6Y2VudGVyO2JhY2tncm91bmQtY29sb3I6I2ZmZjtkaXNwbGF5OmZsZXg7ZmxleDpub25lO2hlaWdodDo0MnB4O21hcmdpbi1ib3R0b206LTFweDtwYWRkaW5nOjAgMTZweDtwb3NpdGlvbjpzdGlja3k7dG9wOjA7ei1pbmRleDoxO2JveC1zaGFkb3c6MHB4IDJweCA0cHggMHB4IHJnYmEoMCwwLDAsLjE1KX1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojMzAzMDMwfWJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjNTU1fWJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3gtc2hhZG93OjBweCAycHggNHB4IDBweCByZ2JhKDI1NSwyNTUsMjU1LC4xNSl9W19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXI6MDtib3JkZXItdG9wOjFweCBzb2xpZCAjZWJlYmViO2NvbG9yOiMyMTIxMjE7dG9wOi0xcHg7ZGlzcGxheTpmbGV4O3dpZHRoOjEwMCU7Zm9udDppbmhlcml0fWJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItdG9wOjFweCBzb2xpZCAjNTU1fWJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjojZmZmfS5jYXJkLWdyb3VwOmZpcnN0LW9mLXR5cGUgICBbX25naG9zdC0lQ09NUCVdICAgLmdyb3VwLXRvb2xiYXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci10b3A6MH1bX25naG9zdC0lQ09NUCVdICAgLmdyb3VwLXRvb2xiYXJbX25nY29udGVudC0lQ09NUCVdOmhvdmVye2N1cnNvcjpwb2ludGVyfS5leHBhbmQtZ3JvdXAtaWNvbltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuZXhwYW5kLWdyb3VwLWljb25bX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZXhwYW5kLWdyb3VwLWljb25bX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfS5leHBhbmQtZ3JvdXAtaWNvbltfbmdjb250ZW50LSVDT01QJV06ZGlzYWJsZWR7Y29sb3I6Izc1NzU3NX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuZXhwYW5kLWdyb3VwLWljb25bX25nY29udGVudC0lQ09NUCVdOmRpc2FibGVkLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZXhwYW5kLWdyb3VwLWljb25bX25nY29udGVudC0lQ09NUCVdOmRpc2FibGVke2NvbG9yOiM2MTYxNjF9Lmdyb3VwLXRpdGxlLXdyYXBwZXJbX25nY29udGVudC0lQ09NUCVde2ZsZXgtZ3JvdzoxO3RleHQtYWxpZ246bGVmdH0uZ3JvdXAtdGl0bGVbX25nY29udGVudC0lQ09NUCVde2ZvbnQtc2l6ZToxNHB4O2ZvbnQtd2VpZ2h0OjUwMH0uZ3JvdXAtY2FyZC1jb3VudFtfbmdjb250ZW50LSVDT01QJV17Zm9udC1zaXplOjEycHg7Zm9udC13ZWlnaHQ6NDAwO2NvbG9yOiM2MTYxNjE7bWFyZ2luLWxlZnQ6NnB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC1jYXJkLWNvdW50W19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmdyb3VwLWNhcmQtY291bnRbX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksVHBlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuZ3JvdXBOYW1lPW51bGwsdGhpcy5pc0dyb3VwRXhwYW5kZWQkPVh0KCExKX1uZ09uSW5pdCgpe3RoaXMuaXNHcm91cEV4cGFuZGVkJD1udWxsIT09dGhpcy5ncm91cE5hbWU/dGhpcy5zdG9yZS5zZWxlY3QoTEksdGhpcy5ncm91cE5hbWUpOlh0KCExKX1vbkdyb3VwRXhwYW5zaW9uVG9nZ2xlZCgpe2lmKG51bGw9PT10aGlzLmdyb3VwTmFtZSl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiSW52YXJpYW50IGVycm9yOiBleHBhbnNpb24gY2Fubm90IGJlIHRvZ2dsZWQgd2hlbiBncm91cE5hbWUgaXMgbnVsbCIpO3RoaXMuc3RvcmUuZGlzcGF0Y2goYVIoe3RhZ0dyb3VwOnRoaXMuZ3JvdXBOYW1lfSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1jYXJkLWdyb3VwLXRvb2xiYXIiXV0saW5wdXRzOntncm91cE5hbWU6Imdyb3VwTmFtZSIsbnVtYmVyT2ZDYXJkczoibnVtYmVyT2ZDYXJkcyJ9LGRlY2xzOjIsdmFyczo1LGNvbnN0czpbWzMsIm51bWJlck9mQ2FyZHMiLCJpc0dyb3VwRXhwYW5kZWQiLCJncm91cE5hbWUiLCJncm91cEV4cGFuc2lvblRvZ2dsZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1ldHJpY3MtY2FyZC1ncm91cC10b29sYmFyLWNvbXBvbmVudCIsMCksUCgiZ3JvdXBFeHBhbnNpb25Ub2dnbGVkIixmdW5jdGlvbigpe3JldHVybiBpLm9uR3JvdXBFeHBhbnNpb25Ub2dnbGVkKCl9KSxCKDEsImFzeW5jIiksdigpKSwyJmUmJnkoIm51bWJlck9mQ2FyZHMiLGkubnVtYmVyT2ZDYXJkcykoImlzR3JvdXBFeHBhbmRlZCIsVSgxLDMsaS5pc0dyb3VwRXhwYW5kZWQkKSkoImdyb3VwTmFtZSIsaS5ncm91cE5hbWUpfSxkZXBlbmRlbmNpZXM6W0VwZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gJFhlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDEpLE8oMSwibWV0cmljcy1jYXJkLWdyb3VwLXRvb2xiYXIiLDIpKDIsIm1ldHJpY3MtY2FyZC1ncmlkIiwzKSx2KCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKCk7QygxKSx5KCJudW1iZXJPZkNhcmRzIixlLml0ZW1zLmxlbmd0aCkoImdyb3VwTmFtZSIsZS5ncm91cE5hbWUpLEMoMSkseSgiY2FyZElkc1dpdGhNZXRhZGF0YSIsZS5pdGVtcykoImNhcmRPYnNlcnZlciIsaS5jYXJkT2JzZXJ2ZXIpKCJncm91cE5hbWUiLGUuZ3JvdXBOYW1lKX19dmFyIERwZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5QbHVnaW5UeXBlPXJpfXRyYWNrQnlHcm91cChlLGkpe3JldHVybiBpLmdyb3VwTmFtZX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1jYXJkLWdyb3Vwcy1jb21wb25lbnQiXV0saW5wdXRzOntjYXJkR3JvdXBzOiJjYXJkR3JvdXBzIixjYXJkT2JzZXJ2ZXI6ImNhcmRPYnNlcnZlciJ9LGRlY2xzOjEsdmFyczoyLGNvbnN0czpbWyJjbGFzcyIsImNhcmQtZ3JvdXAiLDQsIm5nRm9yIiwibmdGb3JPZiIsIm5nRm9yVHJhY2tCeSJdLFsxLCJjYXJkLWdyb3VwIl0sWzMsIm51bWJlck9mQ2FyZHMiLCJncm91cE5hbWUiXSxbMywiY2FyZElkc1dpdGhNZXRhZGF0YSIsImNhcmRPYnNlcnZlciIsImdyb3VwTmFtZSJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmRSgwLCRYZSwzLDUsImRpdiIsMCksMiZlJiZ5KCJuZ0Zvck9mIixpLmNhcmRHcm91cHMpKCJuZ0ZvclRyYWNrQnkiLGkudHJhY2tCeUdyb3VwKX0sZGVwZW5kZW5jaWVzOltkbixBYixUcGVdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZmZmO2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7YWxpZ24taXRlbXM6Y2VudGVyO2JhY2tncm91bmQtY29sb3I6I2ZmZjtkaXNwbGF5OmZsZXg7ZmxleDpub25lO2hlaWdodDo0MnB4O21hcmdpbi1ib3R0b206LTFweDtwYWRkaW5nOjAgMTZweDtwb3NpdGlvbjpzdGlja3k7dG9wOjA7ei1pbmRleDoxO2JveC1zaGFkb3c6MHB4IDJweCA0cHggMHB4IHJnYmEoMCwwLDAsLjE1KX1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojMzAzMDMwfWJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjNTU1fWJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3gtc2hhZG93OjBweCAycHggNHB4IDBweCByZ2JhKDI1NSwyNTUsMjU1LC4xNSl9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxBcGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5jYXJkR3JvdXBzJD10aGlzLnN0b3JlLnNlbGVjdChtZykucGlwZShmcih0aGlzLnN0b3JlLnNlbGVjdChuZCkpLEwoKFtpLHJdKT0+ci5zaXplP2kuZmlsdGVyKG89PnIuaGFzKG8ucGx1Z2luKSk6aSksTChpPT5mUihpKSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1jYXJkLWdyb3VwcyJdXSxpbnB1dHM6e2NhcmRPYnNlcnZlcjoiY2FyZE9ic2VydmVyIn0sZGVjbHM6Mix2YXJzOjQsY29uc3RzOltbMywiY2FyZEdyb3VwcyIsImNhcmRPYnNlcnZlciJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKE8oMCwibWV0cmljcy1jYXJkLWdyb3Vwcy1jb21wb25lbnQiLDApLEIoMSwiYXN5bmMiKSksMiZlJiZ5KCJjYXJkR3JvdXBzIixVKDEsMixpLmNhcmRHcm91cHMkKSkoImNhcmRPYnNlcnZlciIsaS5jYXJkT2JzZXJ2ZXIpfSxkZXBlbmRlbmNpZXM6W0RwZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gblFlKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiksQSgxKSx2KCkpLDImbil7bGV0IGU9UygpO0MoMSksamUoIiBhbmQgIixlLmdldFBsdWdpblR5cGVGaWx0ZXJTdHJpbmcoZS5wbHVnaW5UeXBlcyksIiB2aXN1YWxpemF0aW9uIGZpbHRlciIpfX12YXIgSXBlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLlBsdWdpblR5cGU9cmksdGhpcy5saXN0Rm9ybWF0dGVyPW5ldyBJbnRsLkxpc3RGb3JtYXQodm9pZCAwLHtzdHlsZToibG9uZyIsdHlwZToiZGlzanVuY3Rpb24ifSl9Z2V0UGx1Z2luVHlwZUZpbHRlclN0cmluZyhlKXtsZXQgaT1bLi4uZV0ubWFwKHI9Pntzd2l0Y2gocil7Y2FzZSByaS5TQ0FMQVJTOnJldHVybiJzY2FsYXIiO2Nhc2UgcmkuSU1BR0VTOnJldHVybiJpbWFnZSI7Y2FzZSByaS5ISVNUT0dSQU1TOnJldHVybiJoaXN0b2dyYW0iO2RlZmF1bHQ6dGhyb3cgbmV3IFJhbmdlRXJyb3IoYFBsZWFzZSBpbXBsZW1lbnQgaHVtYW4gcmVhZGFibGUgbmFtZSBmb3IgcGx1Z2luIHR5cGU6ICR7cn1gKX19KTtyZXR1cm4gdGhpcy5saXN0Rm9ybWF0dGVyLmZvcm1hdChpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1lbXB0eS10YWctbWF0Y2gtY29tcG9uZW50Il1dLGlucHV0czp7cGx1Z2luVHlwZXM6InBsdWdpblR5cGVzIix0YWdGaWx0ZXJSZWdleDoidGFnRmlsdGVyUmVnZXgiLHRhZ0NvdW50czoidGFnQ291bnRzIn0sZGVjbHM6Nix2YXJzOjUsY29uc3RzOltbNCwibmdJZiJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKEEoMCwiTm8gbWF0Y2hlcyBmb3IgdGFnIGZpbHRlciAiKSxfKDEsImNvZGUiKSxBKDIpLHYoKSxFKDMsblFlLDIsMSwic3BhbiIsMCksQSg0KSxCKDUsIm51bWJlciIpKSwyJmUmJihDKDIpLGplKCIvIixpLnRhZ0ZpbHRlclJlZ2V4LCIvIiksQygxKSx5KCJuZ0lmIixpLnBsdWdpblR5cGVzLnNpemUpLEMoMSksamUoIiBvdXQgb2YgIixVKDUsMyxpLnRhZ0NvdW50cyksIiB0YWdzLiIpKX0sZGVwZW5kZW5jaWVzOltCZSxRbF0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksUHBlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMucGx1Z2luVHlwZXMkPXRoaXMuc3RvcmUuc2VsZWN0KG5kKSx0aGlzLnRhZ0ZpbHRlclJlZ2V4JD10aGlzLnN0b3JlLnNlbGVjdChYYyksdGhpcy50YWdDb3VudHMkPXRoaXMuc3RvcmUuc2VsZWN0KG1nKS5waXBlKEwoaT0+bmV3IFNldChpLm1hcCgoe3RhZzpyfSk9PnIpKS5zaXplKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtZXRyaWNzLWVtcHR5LXRhZy1tYXRjaCJdXSxkZWNsczo0LHZhcnM6OSxjb25zdHM6W1szLCJwbHVnaW5UeXBlcyIsInRhZ0ZpbHRlclJlZ2V4IiwidGFnQ291bnRzIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJtZXRyaWNzLWVtcHR5LXRhZy1tYXRjaC1jb21wb25lbnQiLDApLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpKSwyJmUmJnkoInBsdWdpblR5cGVzIixVKDEsMyxpLnBsdWdpblR5cGVzJCkpKCJ0YWdGaWx0ZXJSZWdleCIsVSgyLDUsaS50YWdGaWx0ZXJSZWdleCQpKSgidGFnQ291bnRzIixVKDMsNyxpLnRhZ0NvdW50cyQpKX0sZGVwZW5kZW5jaWVzOltJcGUsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIG9RZShuLHQpe2lmKDEmbiYmKF8oMCwic3BhbiIsNiksQSgxKSxCKDIsIm51bWJlciIpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygxKSxqZSgiIixVKDIsMSxlLmNhcmRJZHNXaXRoTWV0YWRhdGEubGVuZ3RoKSwiIGNhcmRzIil9fWZ1bmN0aW9uIHNRZShuLHQpezEmbiYmTygwLCJtZXRyaWNzLWVtcHR5LXRhZy1tYXRjaCIsNyl9dmFyIFJwZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtZXRyaWNzLWZpbHRlcmVkLXZpZXctY29tcG9uZW50Il1dLGlucHV0czp7aXNFbXB0eU1hdGNoOiJpc0VtcHR5TWF0Y2giLGNhcmRPYnNlcnZlcjoiY2FyZE9ic2VydmVyIixjYXJkSWRzV2l0aE1ldGFkYXRhOiJjYXJkSWRzV2l0aE1ldGFkYXRhIn0sZGVjbHM6Nyx2YXJzOjQsY29uc3RzOltbMSwiZ3JvdXAtdG9vbGJhciJdLFsxLCJncm91cC10ZXh0Il0sWyJhcmlhLXJvbGUiLCJoZWFkaW5nIiwiYXJpYS1sZXZlbCIsIjMiLDEsImdyb3VwLXRpdGxlIl0sWyJjbGFzcyIsImdyb3VwLWNhcmQtY291bnQiLDQsIm5nSWYiXSxbImNsYXNzIiwid2FybiIsNCwibmdJZiJdLFszLCJjYXJkSWRzV2l0aE1ldGFkYXRhIiwiY2FyZE9ic2VydmVyIl0sWzEsImdyb3VwLWNhcmQtY291bnQiXSxbMSwid2FybiJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IiwwKSgxLCJzcGFuIiwxKSgyLCJzcGFuIiwyKSxBKDMsIlRhZ3MgbWF0Y2hpbmcgZmlsdGVyIiksdigpLEUoNCxvUWUsMywzLCJzcGFuIiwzKSx2KCkoKSxFKDUsc1FlLDEsMCwibWV0cmljcy1lbXB0eS10YWctbWF0Y2giLDQpLE8oNiwibWV0cmljcy1jYXJkLWdyaWQiLDUpKSwyJmUmJihDKDQpLHkoIm5nSWYiLGkuY2FyZElkc1dpdGhNZXRhZGF0YS5sZW5ndGg+MSksQygxKSx5KCJuZ0lmIixpLmlzRW1wdHlNYXRjaCksQygxKSx5KCJjYXJkSWRzV2l0aE1ldGFkYXRhIixpLmNhcmRJZHNXaXRoTWV0YWRhdGEpKCJjYXJkT2JzZXJ2ZXIiLGkuY2FyZE9ic2VydmVyKSl9LGRlcGVuZGVuY2llczpbQmUsQWIsUHBlLFFsXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVdICAgLmdyb3VwLXRvb2xiYXJbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2ZmZjtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjZWJlYmViO2FsaWduLWl0ZW1zOmNlbnRlcjtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7ZGlzcGxheTpmbGV4O2ZsZXg6bm9uZTtoZWlnaHQ6NDJweDttYXJnaW4tYm90dG9tOi0xcHg7cGFkZGluZzowIDE2cHg7cG9zaXRpb246c3RpY2t5O3RvcDowO3otaW5kZXg6MTtib3gtc2hhZG93OjBweCAycHggNHB4IDBweCByZ2JhKDAsMCwwLC4xNSl9Ym9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmdyb3VwLXRvb2xiYXJbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6IzMwMzAzMH1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWJvdHRvbToxcHggc29saWQgIzU1NX1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17Ym94LXNoYWRvdzowcHggMnB4IDRweCAwcHggcmdiYSgyNTUsMjU1LDI1NSwuMTUpfS5ncm91cC10ZXh0W19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmZsZXg7YWxpZ24taXRlbXM6YmFzZWxpbmV9Lmdyb3VwLXRpdGxlW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXNpemU6MTRweDtmb250LXdlaWdodDo1MDB9Lmdyb3VwLWNhcmQtY291bnRbX25nY29udGVudC0lQ09NUCVde2ZvbnQtc2l6ZToxMnB4O2ZvbnQtd2VpZ2h0OjQwMDtjb2xvcjojNjE2MTYxO21hcmdpbi1sZWZ0OjZweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtY2FyZC1jb3VudFtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC1jYXJkLWNvdW50W19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX1tZXRyaWNzLWVtcHR5LXRhZy1tYXRjaFtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTtmb250LXNpemU6MTNweDtmb250LXN0eWxlOml0YWxpYztwYWRkaW5nOjE2cHg7dGV4dC1hbGlnbjpjZW50ZXI7ZGlzcGxheTpibG9ja31ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICBtZXRyaWNzLWVtcHR5LXRhZy1tYXRjaFtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIG1ldHJpY3MtZW1wdHktdGFnLW1hdGNoW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLE9wZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLmNhcmRJZHNXaXRoTWV0YWRhdGEkPXRoaXMuc3RvcmUuc2VsZWN0KG1nKS5waXBlKGZyKHRoaXMuc3RvcmUuc2VsZWN0KG5kKSksTCgoW2kscl0pPT5yLnNpemU/aS5maWx0ZXIobz0+ci5oYXMoby5wbHVnaW4pKTppKSxmcih0aGlzLnN0b3JlLnNlbGVjdChYYykpLEhyKDIwMCksTCgoW2kscl0pPT57dHJ5e3JldHVybntjYXJkTGlzdDppLHJlZ2V4Om5ldyBSZWdFeHAociwiaSIpfX1jYXRjaHtyZXR1cm57Y2FyZExpc3Q6aSxyZWdleDpudWxsfX19KSxZZSgoe3JlZ2V4Oml9KT0+bnVsbCE9PWkpLEwoKHtjYXJkTGlzdDppLHJlZ2V4OnJ9KT0+aS5maWx0ZXIoKHt0YWc6b30pPT5yLnRlc3QobykpKSx5aSgoaSxyKT0+aS5sZW5ndGg9PT1yLmxlbmd0aCYmaS5ldmVyeSgobyxzKT0+by5jYXJkSWQ9PT1yW3NdLmNhcmRJZCkpLFRzKCksem4oW10pKSx0aGlzLmlzRW1wdHlNYXRjaCQ9dGhpcy5jYXJkSWRzV2l0aE1ldGFkYXRhJC5waXBlKGZyKHRoaXMuc3RvcmUuc2VsZWN0KG1nKSksTCgoW2kscl0pPT5Cb29sZWFuKHIubGVuZ3RoKSYmMD09PWkubGVuZ3RoKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtZXRyaWNzLWZpbHRlcmVkLXZpZXciXV0saW5wdXRzOntjYXJkT2JzZXJ2ZXI6ImNhcmRPYnNlcnZlciJ9LGRlY2xzOjMsdmFyczo3LGNvbnN0czpbWzMsImlzRW1wdHlNYXRjaCIsImNhcmRJZHNXaXRoTWV0YWRhdGEiLCJjYXJkT2JzZXJ2ZXIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihPKDAsIm1ldHJpY3MtZmlsdGVyZWQtdmlldy1jb21wb25lbnQiLDApLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIikpLDImZSYmeSgiaXNFbXB0eU1hdGNoIixVKDEsMyxpLmlzRW1wdHlNYXRjaCQpKSgiY2FyZElkc1dpdGhNZXRhZGF0YSIsVSgyLDUsaS5jYXJkSWRzV2l0aE1ldGFkYXRhJCkpKCJjYXJkT2JzZXJ2ZXIiLGkuY2FyZE9ic2VydmVyKX0sZGVwZW5kZW5jaWVzOltScGUsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLHVRZT1bInBhbmVsIl07ZnVuY3Rpb24gZFFlKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDAsMSksVm4oMiksdigpKSwyJm4pe2xldCBlPXQuaWQsaT1TKCk7eSgiaWQiLGkuaWQpKCJuZ0NsYXNzIixpLl9jbGFzc0xpc3QpLHplKCJhcmlhLWxhYmVsIixpLmFyaWFMYWJlbHx8bnVsbCkoImFyaWEtbGFiZWxsZWRieSIsaS5fZ2V0UGFuZWxBcmlhTGFiZWxsZWRieShlKSl9fXZhciBwUWU9WyIqIl0saFFlPTAsZlFlPXFvKGNsYXNze30pLGtwZT1uZXcgcGUoIm1hdC1hdXRvY29tcGxldGUtZGVmYXVsdC1vcHRpb25zIix7cHJvdmlkZWRJbjoicm9vdCIsZmFjdG9yeTpmdW5jdGlvbigpe3JldHVybnthdXRvQWN0aXZlRmlyc3RPcHRpb246ITEsYXV0b1NlbGVjdEFjdGl2ZU9wdGlvbjohMX19fSksZ1FlPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBmUWV7Y29uc3RydWN0b3IoZSxpLHIsbyl7c3VwZXIoKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj1lLHRoaXMuX2VsZW1lbnRSZWY9aSx0aGlzLl9hY3RpdmVPcHRpb25DaGFuZ2VzPVNuLkVNUFRZLHRoaXMuc2hvd1BhbmVsPSExLHRoaXMuX2lzT3Blbj0hMSx0aGlzLmRpc3BsYXlXaXRoPW51bGwsdGhpcy5vcHRpb25TZWxlY3RlZD1uZXcgRyx0aGlzLm9wZW5lZD1uZXcgRyx0aGlzLmNsb3NlZD1uZXcgRyx0aGlzLm9wdGlvbkFjdGl2YXRlZD1uZXcgRyx0aGlzLl9jbGFzc0xpc3Q9e30sdGhpcy5pZD0ibWF0LWF1dG9jb21wbGV0ZS0iK2hRZSsrLHRoaXMuaW5lcnRHcm91cHM9bz8uU0FGQVJJfHwhMSx0aGlzLl9hdXRvQWN0aXZlRmlyc3RPcHRpb249ISFyLmF1dG9BY3RpdmVGaXJzdE9wdGlvbix0aGlzLl9hdXRvU2VsZWN0QWN0aXZlT3B0aW9uPSEhci5hdXRvU2VsZWN0QWN0aXZlT3B0aW9ufWdldCBpc09wZW4oKXtyZXR1cm4gdGhpcy5faXNPcGVuJiZ0aGlzLnNob3dQYW5lbH1nZXQgYXV0b0FjdGl2ZUZpcnN0T3B0aW9uKCl7cmV0dXJuIHRoaXMuX2F1dG9BY3RpdmVGaXJzdE9wdGlvbn1zZXQgYXV0b0FjdGl2ZUZpcnN0T3B0aW9uKGUpe3RoaXMuX2F1dG9BY3RpdmVGaXJzdE9wdGlvbj1SdChlKX1nZXQgYXV0b1NlbGVjdEFjdGl2ZU9wdGlvbigpe3JldHVybiB0aGlzLl9hdXRvU2VsZWN0QWN0aXZlT3B0aW9ufXNldCBhdXRvU2VsZWN0QWN0aXZlT3B0aW9uKGUpe3RoaXMuX2F1dG9TZWxlY3RBY3RpdmVPcHRpb249UnQoZSl9c2V0IGNsYXNzTGlzdChlKXt0aGlzLl9jbGFzc0xpc3Q9ZSYmZS5sZW5ndGg/ZnVuY3Rpb24obix0PS9ccysvKXtsZXQgZT1bXTtpZihudWxsIT1uKXtsZXQgaT1BcnJheS5pc0FycmF5KG4pP246YCR7bn1gLnNwbGl0KHQpO2ZvcihsZXQgciBvZiBpKXtsZXQgbz1gJHtyfWAudHJpbSgpO28mJmUucHVzaChvKX19cmV0dXJuIGV9KGUpLnJlZHVjZSgoaSxyKT0+KGlbcl09ITAsaSkse30pOnt9LHRoaXMuX3NldFZpc2liaWxpdHlDbGFzc2VzKHRoaXMuX2NsYXNzTGlzdCksdGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LmNsYXNzTmFtZT0iIn1uZ0FmdGVyQ29udGVudEluaXQoKXt0aGlzLl9rZXlNYW5hZ2VyPW5ldyB3dih0aGlzLm9wdGlvbnMpLndpdGhXcmFwKCksdGhpcy5fYWN0aXZlT3B0aW9uQ2hhbmdlcz10aGlzLl9rZXlNYW5hZ2VyLmNoYW5nZS5zdWJzY3JpYmUoZT0+e3RoaXMuaXNPcGVuJiZ0aGlzLm9wdGlvbkFjdGl2YXRlZC5lbWl0KHtzb3VyY2U6dGhpcyxvcHRpb246dGhpcy5vcHRpb25zLnRvQXJyYXkoKVtlXXx8bnVsbH0pfSksdGhpcy5fc2V0VmlzaWJpbGl0eSgpfW5nT25EZXN0cm95KCl7dGhpcy5fYWN0aXZlT3B0aW9uQ2hhbmdlcy51bnN1YnNjcmliZSgpfV9zZXRTY3JvbGxUb3AoZSl7dGhpcy5wYW5lbCYmKHRoaXMucGFuZWwubmF0aXZlRWxlbWVudC5zY3JvbGxUb3A9ZSl9X2dldFNjcm9sbFRvcCgpe3JldHVybiB0aGlzLnBhbmVsP3RoaXMucGFuZWwubmF0aXZlRWxlbWVudC5zY3JvbGxUb3A6MH1fc2V0VmlzaWJpbGl0eSgpe3RoaXMuc2hvd1BhbmVsPSEhdGhpcy5vcHRpb25zLmxlbmd0aCx0aGlzLl9zZXRWaXNpYmlsaXR5Q2xhc3Nlcyh0aGlzLl9jbGFzc0xpc3QpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpfV9lbWl0U2VsZWN0RXZlbnQoZSl7bGV0IGk9bmV3IGNsYXNze2NvbnN0cnVjdG9yKHQsZSl7dGhpcy5zb3VyY2U9dCx0aGlzLm9wdGlvbj1lfX0odGhpcyxlKTt0aGlzLm9wdGlvblNlbGVjdGVkLmVtaXQoaSl9X2dldFBhbmVsQXJpYUxhYmVsbGVkYnkoZSl7cmV0dXJuIHRoaXMuYXJpYUxhYmVsP251bGw6dGhpcy5hcmlhTGFiZWxsZWRieT8oZT9lKyIgIjoiIikrdGhpcy5hcmlhTGFiZWxsZWRieTplfV9zZXRWaXNpYmlsaXR5Q2xhc3NlcyhlKXtlW3RoaXMuX3Zpc2libGVDbGFzc109dGhpcy5zaG93UGFuZWwsZVt0aGlzLl9oaWRkZW5DbGFzc109IXRoaXMuc2hvd1BhbmVsfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKG5uKSxNKFJlKSxNKGtwZSksTShvaSkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKG90KFZpLDcpLG90KHVRZSw1KSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS50ZW1wbGF0ZT1yLmZpcnN0KSxOZShyPUxlKCkpJiYoaS5wYW5lbD1yLmZpcnN0KX19LGlucHV0czp7YXJpYUxhYmVsOlsiYXJpYS1sYWJlbCIsImFyaWFMYWJlbCJdLGFyaWFMYWJlbGxlZGJ5OlsiYXJpYS1sYWJlbGxlZGJ5IiwiYXJpYUxhYmVsbGVkYnkiXSxkaXNwbGF5V2l0aDoiZGlzcGxheVdpdGgiLGF1dG9BY3RpdmVGaXJzdE9wdGlvbjoiYXV0b0FjdGl2ZUZpcnN0T3B0aW9uIixhdXRvU2VsZWN0QWN0aXZlT3B0aW9uOiJhdXRvU2VsZWN0QWN0aXZlT3B0aW9uIixwYW5lbFdpZHRoOiJwYW5lbFdpZHRoIixjbGFzc0xpc3Q6WyJjbGFzcyIsImNsYXNzTGlzdCJdfSxvdXRwdXRzOntvcHRpb25TZWxlY3RlZDoib3B0aW9uU2VsZWN0ZWQiLG9wZW5lZDoib3BlbmVkIixjbG9zZWQ6ImNsb3NlZCIsb3B0aW9uQWN0aXZhdGVkOiJvcHRpb25BY3RpdmF0ZWQifSxmZWF0dXJlczpbdHRdfSksbn0pKCkscWs9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGdRZXtjb25zdHJ1Y3Rvcigpe3N1cGVyKC4uLmFyZ3VtZW50cyksdGhpcy5fdmlzaWJsZUNsYXNzPSJtYXQtYXV0b2NvbXBsZXRlLXZpc2libGUiLHRoaXMuX2hpZGRlbkNsYXNzPSJtYXQtYXV0b2NvbXBsZXRlLWhpZGRlbiJ9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbigpe2xldCB0O3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4odHx8KHQ9cGkobikpKShpfHxuKX19KCksbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtYXQtYXV0b2NvbXBsZXRlIl1dLGNvbnRlbnRRdWVyaWVzOmZ1bmN0aW9uKGUsaSxyKXtpZigxJmUmJihFaShyLHJ3LDUpLEVpKHIsT3MsNSkpLDImZSl7bGV0IG87TmUobz1MZSgpKSYmKGkub3B0aW9uR3JvdXBzPW8pLE5lKG89TGUoKSkmJihpLm9wdGlvbnM9byl9fSxob3N0QXR0cnM6WzEsIm1hdC1hdXRvY29tcGxldGUiXSxpbnB1dHM6e2Rpc2FibGVSaXBwbGU6ImRpc2FibGVSaXBwbGUifSxleHBvcnRBczpbIm1hdEF1dG9jb21wbGV0ZSJdLGZlYXR1cmVzOlskdChbe3Byb3ZpZGU6aXcsdXNlRXhpc3Rpbmc6bn1dKSx0dF0sbmdDb250ZW50U2VsZWN0b3JzOnBRZSxkZWNsczoxLHZhcnM6MCxjb25zdHM6W1sicm9sZSIsImxpc3Rib3giLDEsIm1hdC1hdXRvY29tcGxldGUtcGFuZWwiLDMsImlkIiwibmdDbGFzcyJdLFsicGFuZWwiLCIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJih4aSgpLEUoMCxkUWUsMyw0LCJuZy10ZW1wbGF0ZSIpKX0sZGVwZW5kZW5jaWVzOltGbl0sc3R5bGVzOlsiLm1hdC1hdXRvY29tcGxldGUtcGFuZWx7bWluLXdpZHRoOjExMnB4O21heC13aWR0aDoyODBweDtvdmVyZmxvdzphdXRvOy13ZWJraXQtb3ZlcmZsb3ctc2Nyb2xsaW5nOnRvdWNoO3Zpc2liaWxpdHk6aGlkZGVuO21heC13aWR0aDpub25lO21heC1oZWlnaHQ6MjU2cHg7cG9zaXRpb246cmVsYXRpdmU7d2lkdGg6MTAwJTtib3JkZXItYm90dG9tLWxlZnQtcmFkaXVzOjRweDtib3JkZXItYm90dG9tLXJpZ2h0LXJhZGl1czo0cHh9Lm1hdC1hdXRvY29tcGxldGUtcGFuZWwubWF0LWF1dG9jb21wbGV0ZS12aXNpYmxle3Zpc2liaWxpdHk6dmlzaWJsZX0ubWF0LWF1dG9jb21wbGV0ZS1wYW5lbC5tYXQtYXV0b2NvbXBsZXRlLWhpZGRlbnt2aXNpYmlsaXR5OmhpZGRlbn0ubWF0LWF1dG9jb21wbGV0ZS1wYW5lbC1hYm92ZSAubWF0LWF1dG9jb21wbGV0ZS1wYW5lbHtib3JkZXItcmFkaXVzOjA7Ym9yZGVyLXRvcC1sZWZ0LXJhZGl1czo0cHg7Ym9yZGVyLXRvcC1yaWdodC1yYWRpdXM6NHB4fS5tYXQtYXV0b2NvbXBsZXRlLXBhbmVsIC5tYXQtZGl2aWRlci1ob3Jpem9udGFse21hcmdpbi10b3A6LTFweH0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtYXV0b2NvbXBsZXRlLXBhbmVse291dGxpbmU6c29saWQgMXB4fW1hdC1hdXRvY29tcGxldGV7ZGlzcGxheTpub25lfSJdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLEZwZT1uZXcgcGUoIm1hdC1hdXRvY29tcGxldGUtc2Nyb2xsLXN0cmF0ZWd5IiksdlFlPXtwcm92aWRlOkZwZSxkZXBzOlt0cl0sdXNlRmFjdG9yeTpmdW5jdGlvbihuKXtyZXR1cm4oKT0+bi5zY3JvbGxTdHJhdGVnaWVzLnJlcG9zaXRpb24oKX19LHlRZT17cHJvdmlkZTpObyx1c2VFeGlzdGluZzpKbigoKT0+JGcpLG11bHRpOiEwfSxiUWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsLGMsdSxkLHApe3RoaXMuX2VsZW1lbnQ9ZSx0aGlzLl9vdmVybGF5PWksdGhpcy5fdmlld0NvbnRhaW5lclJlZj1yLHRoaXMuX3pvbmU9byx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj1zLHRoaXMuX2Rpcj1sLHRoaXMuX2Zvcm1GaWVsZD1jLHRoaXMuX2RvY3VtZW50PXUsdGhpcy5fdmlld3BvcnRSdWxlcj1kLHRoaXMuX2RlZmF1bHRzPXAsdGhpcy5fY29tcG9uZW50RGVzdHJveWVkPSExLHRoaXMuX2F1dG9jb21wbGV0ZURpc2FibGVkPSExLHRoaXMuX21hbnVhbGx5RmxvYXRpbmdMYWJlbD0hMSx0aGlzLl92aWV3cG9ydFN1YnNjcmlwdGlvbj1Tbi5FTVBUWSx0aGlzLl9jYW5PcGVuT25OZXh0Rm9jdXM9ITAsdGhpcy5fY2xvc2VLZXlFdmVudFN0cmVhbT1uZXcga2UsdGhpcy5fd2luZG93Qmx1ckhhbmRsZXI9KCk9Pnt0aGlzLl9jYW5PcGVuT25OZXh0Rm9jdXM9dGhpcy5fZG9jdW1lbnQuYWN0aXZlRWxlbWVudCE9PXRoaXMuX2VsZW1lbnQubmF0aXZlRWxlbWVudHx8dGhpcy5wYW5lbE9wZW59LHRoaXMuX29uQ2hhbmdlPSgpPT57fSx0aGlzLl9vblRvdWNoZWQ9KCk9Pnt9LHRoaXMucG9zaXRpb249ImF1dG8iLHRoaXMuYXV0b2NvbXBsZXRlQXR0cmlidXRlPSJvZmYiLHRoaXMuX292ZXJsYXlBdHRhY2hlZD0hMSx0aGlzLm9wdGlvblNlbGVjdGlvbnM9UWEoKCk9PntsZXQgaD10aGlzLmF1dG9jb21wbGV0ZT90aGlzLmF1dG9jb21wbGV0ZS5vcHRpb25zOm51bGw7cmV0dXJuIGg/aC5jaGFuZ2VzLnBpcGUoem4oaCksdWkoKCk9Pkp0KC4uLmgubWFwKGY9PmYub25TZWxlY3Rpb25DaGFuZ2UpKSkpOnRoaXMuX3pvbmUub25TdGFibGUucGlwZShRdCgxKSx1aSgoKT0+dGhpcy5vcHRpb25TZWxlY3Rpb25zKSl9KSx0aGlzLl9zY3JvbGxTdHJhdGVneT1hfWdldCBhdXRvY29tcGxldGVEaXNhYmxlZCgpe3JldHVybiB0aGlzLl9hdXRvY29tcGxldGVEaXNhYmxlZH1zZXQgYXV0b2NvbXBsZXRlRGlzYWJsZWQoZSl7dGhpcy5fYXV0b2NvbXBsZXRlRGlzYWJsZWQ9UnQoZSl9bmdBZnRlclZpZXdJbml0KCl7bGV0IGU9dGhpcy5fZ2V0V2luZG93KCk7dHlwZW9mIGU8InUiJiZ0aGlzLl96b25lLnJ1bk91dHNpZGVBbmd1bGFyKCgpPT5lLmFkZEV2ZW50TGlzdGVuZXIoImJsdXIiLHRoaXMuX3dpbmRvd0JsdXJIYW5kbGVyKSl9bmdPbkNoYW5nZXMoZSl7ZS5wb3NpdGlvbiYmdGhpcy5fcG9zaXRpb25TdHJhdGVneSYmKHRoaXMuX3NldFN0cmF0ZWd5UG9zaXRpb25zKHRoaXMuX3Bvc2l0aW9uU3RyYXRlZ3kpLHRoaXMucGFuZWxPcGVuJiZ0aGlzLl9vdmVybGF5UmVmLnVwZGF0ZVBvc2l0aW9uKCkpfW5nT25EZXN0cm95KCl7bGV0IGU9dGhpcy5fZ2V0V2luZG93KCk7dHlwZW9mIGU8InUiJiZlLnJlbW92ZUV2ZW50TGlzdGVuZXIoImJsdXIiLHRoaXMuX3dpbmRvd0JsdXJIYW5kbGVyKSx0aGlzLl92aWV3cG9ydFN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX2NvbXBvbmVudERlc3Ryb3llZD0hMCx0aGlzLl9kZXN0cm95UGFuZWwoKSx0aGlzLl9jbG9zZUtleUV2ZW50U3RyZWFtLmNvbXBsZXRlKCl9Z2V0IHBhbmVsT3Blbigpe3JldHVybiB0aGlzLl9vdmVybGF5QXR0YWNoZWQmJnRoaXMuYXV0b2NvbXBsZXRlLnNob3dQYW5lbH1vcGVuUGFuZWwoKXt0aGlzLl9hdHRhY2hPdmVybGF5KCksdGhpcy5fZmxvYXRMYWJlbCgpfWNsb3NlUGFuZWwoKXt0aGlzLl9yZXNldExhYmVsKCksdGhpcy5fb3ZlcmxheUF0dGFjaGVkJiYodGhpcy5wYW5lbE9wZW4mJnRoaXMuX3pvbmUucnVuKCgpPT57dGhpcy5hdXRvY29tcGxldGUuY2xvc2VkLmVtaXQoKX0pLHRoaXMuYXV0b2NvbXBsZXRlLl9pc09wZW49dGhpcy5fb3ZlcmxheUF0dGFjaGVkPSExLHRoaXMuX3BlbmRpbmdBdXRvc2VsZWN0ZWRPcHRpb249bnVsbCx0aGlzLl9vdmVybGF5UmVmJiZ0aGlzLl9vdmVybGF5UmVmLmhhc0F0dGFjaGVkKCkmJih0aGlzLl9vdmVybGF5UmVmLmRldGFjaCgpLHRoaXMuX2Nsb3NpbmdBY3Rpb25zU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCkpLHRoaXMuX2NvbXBvbmVudERlc3Ryb3llZHx8dGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYuZGV0ZWN0Q2hhbmdlcygpKX11cGRhdGVQb3NpdGlvbigpe3RoaXMuX292ZXJsYXlBdHRhY2hlZCYmdGhpcy5fb3ZlcmxheVJlZi51cGRhdGVQb3NpdGlvbigpfWdldCBwYW5lbENsb3NpbmdBY3Rpb25zKCl7cmV0dXJuIEp0KHRoaXMub3B0aW9uU2VsZWN0aW9ucyx0aGlzLmF1dG9jb21wbGV0ZS5fa2V5TWFuYWdlci50YWJPdXQucGlwZShZZSgoKT0+dGhpcy5fb3ZlcmxheUF0dGFjaGVkKSksdGhpcy5fY2xvc2VLZXlFdmVudFN0cmVhbSx0aGlzLl9nZXRPdXRzaWRlQ2xpY2tTdHJlYW0oKSx0aGlzLl9vdmVybGF5UmVmP3RoaXMuX292ZXJsYXlSZWYuZGV0YWNobWVudHMoKS5waXBlKFllKCgpPT50aGlzLl9vdmVybGF5QXR0YWNoZWQpKTpYdCgpKS5waXBlKEwoZT0+ZSBpbnN0YW5jZW9mIG53P2U6bnVsbCkpfWdldCBhY3RpdmVPcHRpb24oKXtyZXR1cm4gdGhpcy5hdXRvY29tcGxldGUmJnRoaXMuYXV0b2NvbXBsZXRlLl9rZXlNYW5hZ2VyP3RoaXMuYXV0b2NvbXBsZXRlLl9rZXlNYW5hZ2VyLmFjdGl2ZUl0ZW06bnVsbH1fZ2V0T3V0c2lkZUNsaWNrU3RyZWFtKCl7cmV0dXJuIEp0KF9pKHRoaXMuX2RvY3VtZW50LCJjbGljayIpLF9pKHRoaXMuX2RvY3VtZW50LCJhdXhjbGljayIpLF9pKHRoaXMuX2RvY3VtZW50LCJ0b3VjaGVuZCIpKS5waXBlKFllKGU9PntsZXQgaT1RYyhlKSxyPXRoaXMuX2Zvcm1GaWVsZD90aGlzLl9mb3JtRmllbGQuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudDpudWxsLG89dGhpcy5jb25uZWN0ZWRUbz90aGlzLmNvbm5lY3RlZFRvLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudDpudWxsO3JldHVybiB0aGlzLl9vdmVybGF5QXR0YWNoZWQmJmkhPT10aGlzLl9lbGVtZW50Lm5hdGl2ZUVsZW1lbnQmJnRoaXMuX2RvY3VtZW50LmFjdGl2ZUVsZW1lbnQhPT10aGlzLl9lbGVtZW50Lm5hdGl2ZUVsZW1lbnQmJighcnx8IXIuY29udGFpbnMoaSkpJiYoIW98fCFvLmNvbnRhaW5zKGkpKSYmISF0aGlzLl9vdmVybGF5UmVmJiYhdGhpcy5fb3ZlcmxheVJlZi5vdmVybGF5RWxlbWVudC5jb250YWlucyhpKX0pKX13cml0ZVZhbHVlKGUpe1Byb21pc2UucmVzb2x2ZShudWxsKS50aGVuKCgpPT50aGlzLl9hc3NpZ25PcHRpb25WYWx1ZShlKSl9cmVnaXN0ZXJPbkNoYW5nZShlKXt0aGlzLl9vbkNoYW5nZT1lfXJlZ2lzdGVyT25Ub3VjaGVkKGUpe3RoaXMuX29uVG91Y2hlZD1lfXNldERpc2FibGVkU3RhdGUoZSl7dGhpcy5fZWxlbWVudC5uYXRpdmVFbGVtZW50LmRpc2FibGVkPWV9X2hhbmRsZUtleWRvd24oZSl7bGV0IGk9ZS5rZXlDb2RlLHI9a3IoZSk7aWYoMjc9PT1pJiYhciYmZS5wcmV2ZW50RGVmYXVsdCgpLHRoaXMuYWN0aXZlT3B0aW9uJiYxMz09PWkmJnRoaXMucGFuZWxPcGVuJiYhcil0aGlzLmFjdGl2ZU9wdGlvbi5fc2VsZWN0VmlhSW50ZXJhY3Rpb24oKSx0aGlzLl9yZXNldEFjdGl2ZUl0ZW0oKSxlLnByZXZlbnREZWZhdWx0KCk7ZWxzZSBpZih0aGlzLmF1dG9jb21wbGV0ZSl7bGV0IG89dGhpcy5hdXRvY29tcGxldGUuX2tleU1hbmFnZXIuYWN0aXZlSXRlbSxzPTM4PT09aXx8NDA9PT1pOzk9PT1pfHxzJiYhciYmdGhpcy5wYW5lbE9wZW4/dGhpcy5hdXRvY29tcGxldGUuX2tleU1hbmFnZXIub25LZXlkb3duKGUpOnMmJnRoaXMuX2Nhbk9wZW4oKSYmdGhpcy5vcGVuUGFuZWwoKSwoc3x8dGhpcy5hdXRvY29tcGxldGUuX2tleU1hbmFnZXIuYWN0aXZlSXRlbSE9PW8pJiYodGhpcy5fc2Nyb2xsVG9PcHRpb24odGhpcy5hdXRvY29tcGxldGUuX2tleU1hbmFnZXIuYWN0aXZlSXRlbUluZGV4fHwwKSx0aGlzLmF1dG9jb21wbGV0ZS5hdXRvU2VsZWN0QWN0aXZlT3B0aW9uJiZ0aGlzLmFjdGl2ZU9wdGlvbiYmKHRoaXMuX3BlbmRpbmdBdXRvc2VsZWN0ZWRPcHRpb258fCh0aGlzLl92YWx1ZUJlZm9yZUF1dG9TZWxlY3Rpb249dGhpcy5fZWxlbWVudC5uYXRpdmVFbGVtZW50LnZhbHVlKSx0aGlzLl9wZW5kaW5nQXV0b3NlbGVjdGVkT3B0aW9uPXRoaXMuYWN0aXZlT3B0aW9uLHRoaXMuX2Fzc2lnbk9wdGlvblZhbHVlKHRoaXMuYWN0aXZlT3B0aW9uLnZhbHVlKSkpfX1faGFuZGxlSW5wdXQoZSl7bGV0IGk9ZS50YXJnZXQscj1pLnZhbHVlOyJudW1iZXIiPT09aS50eXBlJiYocj0iIj09cj9udWxsOnBhcnNlRmxvYXQocikpLHRoaXMuX3ByZXZpb3VzVmFsdWUhPT1yJiYodGhpcy5fcHJldmlvdXNWYWx1ZT1yLHRoaXMuX3BlbmRpbmdBdXRvc2VsZWN0ZWRPcHRpb249bnVsbCx0aGlzLl9vbkNoYW5nZShyKSx0aGlzLl9jYW5PcGVuKCkmJnRoaXMuX2RvY3VtZW50LmFjdGl2ZUVsZW1lbnQ9PT1lLnRhcmdldCYmdGhpcy5vcGVuUGFuZWwoKSl9X2hhbmRsZUZvY3VzKCl7dGhpcy5fY2FuT3Blbk9uTmV4dEZvY3VzP3RoaXMuX2Nhbk9wZW4oKSYmKHRoaXMuX3ByZXZpb3VzVmFsdWU9dGhpcy5fZWxlbWVudC5uYXRpdmVFbGVtZW50LnZhbHVlLHRoaXMuX2F0dGFjaE92ZXJsYXkoKSx0aGlzLl9mbG9hdExhYmVsKCEwKSk6dGhpcy5fY2FuT3Blbk9uTmV4dEZvY3VzPSEwfV9oYW5kbGVDbGljaygpe3RoaXMuX2Nhbk9wZW4oKSYmIXRoaXMucGFuZWxPcGVuJiZ0aGlzLm9wZW5QYW5lbCgpfV9mbG9hdExhYmVsKGU9ITEpe3RoaXMuX2Zvcm1GaWVsZCYmImF1dG8iPT09dGhpcy5fZm9ybUZpZWxkLmZsb2F0TGFiZWwmJihlP3RoaXMuX2Zvcm1GaWVsZC5fYW5pbWF0ZUFuZExvY2tMYWJlbCgpOnRoaXMuX2Zvcm1GaWVsZC5mbG9hdExhYmVsPSJhbHdheXMiLHRoaXMuX21hbnVhbGx5RmxvYXRpbmdMYWJlbD0hMCl9X3Jlc2V0TGFiZWwoKXt0aGlzLl9tYW51YWxseUZsb2F0aW5nTGFiZWwmJih0aGlzLl9mb3JtRmllbGQuZmxvYXRMYWJlbD0iYXV0byIsdGhpcy5fbWFudWFsbHlGbG9hdGluZ0xhYmVsPSExKX1fc3Vic2NyaWJlVG9DbG9zaW5nQWN0aW9ucygpe3JldHVybiBKdCh0aGlzLl96b25lLm9uU3RhYmxlLnBpcGUoUXQoMSkpLHRoaXMuYXV0b2NvbXBsZXRlLm9wdGlvbnMuY2hhbmdlcy5waXBlKGt0KCgpPT50aGlzLl9wb3NpdGlvblN0cmF0ZWd5LnJlYXBwbHlMYXN0UG9zaXRpb24oKSksT2woMCkpKS5waXBlKHVpKCgpPT4odGhpcy5fem9uZS5ydW4oKCk9PntsZXQgcj10aGlzLnBhbmVsT3Blbjt0aGlzLl9yZXNldEFjdGl2ZUl0ZW0oKSx0aGlzLmF1dG9jb21wbGV0ZS5fc2V0VmlzaWJpbGl0eSgpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLmRldGVjdENoYW5nZXMoKSx0aGlzLnBhbmVsT3BlbiYmdGhpcy5fb3ZlcmxheVJlZi51cGRhdGVQb3NpdGlvbigpLHIhPT10aGlzLnBhbmVsT3BlbiYmKHRoaXMucGFuZWxPcGVuP3RoaXMuYXV0b2NvbXBsZXRlLm9wZW5lZC5lbWl0KCk6dGhpcy5hdXRvY29tcGxldGUuY2xvc2VkLmVtaXQoKSl9KSx0aGlzLnBhbmVsQ2xvc2luZ0FjdGlvbnMpKSxRdCgxKSkuc3Vic2NyaWJlKHI9PnRoaXMuX3NldFZhbHVlQW5kQ2xvc2UocikpfV9kZXN0cm95UGFuZWwoKXt0aGlzLl9vdmVybGF5UmVmJiYodGhpcy5jbG9zZVBhbmVsKCksdGhpcy5fb3ZlcmxheVJlZi5kaXNwb3NlKCksdGhpcy5fb3ZlcmxheVJlZj1udWxsKX1fYXNzaWduT3B0aW9uVmFsdWUoZSl7bGV0IGk9dGhpcy5hdXRvY29tcGxldGUmJnRoaXMuYXV0b2NvbXBsZXRlLmRpc3BsYXlXaXRoP3RoaXMuYXV0b2NvbXBsZXRlLmRpc3BsYXlXaXRoKGUpOmU7dGhpcy5fdXBkYXRlTmF0aXZlSW5wdXRWYWx1ZShpPz8iIil9X3VwZGF0ZU5hdGl2ZUlucHV0VmFsdWUoZSl7dGhpcy5fZm9ybUZpZWxkP3RoaXMuX2Zvcm1GaWVsZC5fY29udHJvbC52YWx1ZT1lOnRoaXMuX2VsZW1lbnQubmF0aXZlRWxlbWVudC52YWx1ZT1lLHRoaXMuX3ByZXZpb3VzVmFsdWU9ZX1fc2V0VmFsdWVBbmRDbG9zZShlKXtsZXQgaT1lP2Uuc291cmNlOnRoaXMuX3BlbmRpbmdBdXRvc2VsZWN0ZWRPcHRpb247aSYmKHRoaXMuX2NsZWFyUHJldmlvdXNTZWxlY3RlZE9wdGlvbihpKSx0aGlzLl9hc3NpZ25PcHRpb25WYWx1ZShpLnZhbHVlKSx0aGlzLl9vbkNoYW5nZShpLnZhbHVlKSx0aGlzLmF1dG9jb21wbGV0ZS5fZW1pdFNlbGVjdEV2ZW50KGkpLHRoaXMuX2VsZW1lbnQubmF0aXZlRWxlbWVudC5mb2N1cygpKSx0aGlzLmNsb3NlUGFuZWwoKX1fY2xlYXJQcmV2aW91c1NlbGVjdGVkT3B0aW9uKGUpe3RoaXMuYXV0b2NvbXBsZXRlLm9wdGlvbnMuZm9yRWFjaChpPT57aSE9PWUmJmkuc2VsZWN0ZWQmJmkuZGVzZWxlY3QoKX0pfV9hdHRhY2hPdmVybGF5KCl7bGV0IGU9dGhpcy5fb3ZlcmxheVJlZjtlPyh0aGlzLl9wb3NpdGlvblN0cmF0ZWd5LnNldE9yaWdpbih0aGlzLl9nZXRDb25uZWN0ZWRFbGVtZW50KCkpLGUudXBkYXRlU2l6ZSh7d2lkdGg6dGhpcy5fZ2V0UGFuZWxXaWR0aCgpfSkpOih0aGlzLl9wb3J0YWw9bmV3IGtzKHRoaXMuYXV0b2NvbXBsZXRlLnRlbXBsYXRlLHRoaXMuX3ZpZXdDb250YWluZXJSZWYse2lkOnRoaXMuX2Zvcm1GaWVsZD8uZ2V0TGFiZWxJZCgpfSksZT10aGlzLl9vdmVybGF5LmNyZWF0ZSh0aGlzLl9nZXRPdmVybGF5Q29uZmlnKCkpLHRoaXMuX292ZXJsYXlSZWY9ZSx0aGlzLl9oYW5kbGVPdmVybGF5RXZlbnRzKGUpLHRoaXMuX3ZpZXdwb3J0U3Vic2NyaXB0aW9uPXRoaXMuX3ZpZXdwb3J0UnVsZXIuY2hhbmdlKCkuc3Vic2NyaWJlKCgpPT57dGhpcy5wYW5lbE9wZW4mJmUmJmUudXBkYXRlU2l6ZSh7d2lkdGg6dGhpcy5fZ2V0UGFuZWxXaWR0aCgpfSl9KSksZSYmIWUuaGFzQXR0YWNoZWQoKSYmKGUuYXR0YWNoKHRoaXMuX3BvcnRhbCksdGhpcy5fY2xvc2luZ0FjdGlvbnNTdWJzY3JpcHRpb249dGhpcy5fc3Vic2NyaWJlVG9DbG9zaW5nQWN0aW9ucygpKTtsZXQgaT10aGlzLnBhbmVsT3Blbjt0aGlzLmF1dG9jb21wbGV0ZS5fc2V0VmlzaWJpbGl0eSgpLHRoaXMuYXV0b2NvbXBsZXRlLl9pc09wZW49dGhpcy5fb3ZlcmxheUF0dGFjaGVkPSEwLHRoaXMucGFuZWxPcGVuJiZpIT09dGhpcy5wYW5lbE9wZW4mJnRoaXMuYXV0b2NvbXBsZXRlLm9wZW5lZC5lbWl0KCl9X2dldE92ZXJsYXlDb25maWcoKXtyZXR1cm4gbmV3IHNjKHtwb3NpdGlvblN0cmF0ZWd5OnRoaXMuX2dldE92ZXJsYXlQb3NpdGlvbigpLHNjcm9sbFN0cmF0ZWd5OnRoaXMuX3Njcm9sbFN0cmF0ZWd5KCksd2lkdGg6dGhpcy5fZ2V0UGFuZWxXaWR0aCgpLGRpcmVjdGlvbjp0aGlzLl9kaXIscGFuZWxDbGFzczp0aGlzLl9kZWZhdWx0cz8ub3ZlcmxheVBhbmVsQ2xhc3N9KX1fZ2V0T3ZlcmxheVBvc2l0aW9uKCl7bGV0IGU9dGhpcy5fb3ZlcmxheS5wb3NpdGlvbigpLmZsZXhpYmxlQ29ubmVjdGVkVG8odGhpcy5fZ2V0Q29ubmVjdGVkRWxlbWVudCgpKS53aXRoRmxleGlibGVEaW1lbnNpb25zKCExKS53aXRoUHVzaCghMSk7cmV0dXJuIHRoaXMuX3NldFN0cmF0ZWd5UG9zaXRpb25zKGUpLHRoaXMuX3Bvc2l0aW9uU3RyYXRlZ3k9ZSxlfV9zZXRTdHJhdGVneVBvc2l0aW9ucyhlKXtsZXQgcyxpPVt7b3JpZ2luWDoic3RhcnQiLG9yaWdpblk6ImJvdHRvbSIsb3ZlcmxheVg6InN0YXJ0IixvdmVybGF5WToidG9wIn0se29yaWdpblg6ImVuZCIsb3JpZ2luWToiYm90dG9tIixvdmVybGF5WDoiZW5kIixvdmVybGF5WToidG9wIn1dLHI9dGhpcy5fYWJvdmVDbGFzcyxvPVt7b3JpZ2luWDoic3RhcnQiLG9yaWdpblk6InRvcCIsb3ZlcmxheVg6InN0YXJ0IixvdmVybGF5WToiYm90dG9tIixwYW5lbENsYXNzOnJ9LHtvcmlnaW5YOiJlbmQiLG9yaWdpblk6InRvcCIsb3ZlcmxheVg6ImVuZCIsb3ZlcmxheVk6ImJvdHRvbSIscGFuZWxDbGFzczpyfV07cz0iYWJvdmUiPT09dGhpcy5wb3NpdGlvbj9vOiJiZWxvdyI9PT10aGlzLnBvc2l0aW9uP2k6Wy4uLmksLi4ub10sZS53aXRoUG9zaXRpb25zKHMpfV9nZXRDb25uZWN0ZWRFbGVtZW50KCl7cmV0dXJuIHRoaXMuY29ubmVjdGVkVG8/dGhpcy5jb25uZWN0ZWRUby5lbGVtZW50UmVmOnRoaXMuX2Zvcm1GaWVsZD90aGlzLl9mb3JtRmllbGQuZ2V0Q29ubmVjdGVkT3ZlcmxheU9yaWdpbigpOnRoaXMuX2VsZW1lbnR9X2dldFBhbmVsV2lkdGgoKXtyZXR1cm4gdGhpcy5hdXRvY29tcGxldGUucGFuZWxXaWR0aHx8dGhpcy5fZ2V0SG9zdFdpZHRoKCl9X2dldEhvc3RXaWR0aCgpe3JldHVybiB0aGlzLl9nZXRDb25uZWN0ZWRFbGVtZW50KCkubmF0aXZlRWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS53aWR0aH1fcmVzZXRBY3RpdmVJdGVtKCl7bGV0IGU9dGhpcy5hdXRvY29tcGxldGU7ZS5hdXRvQWN0aXZlRmlyc3RPcHRpb24/ZS5fa2V5TWFuYWdlci5zZXRGaXJzdEl0ZW1BY3RpdmUoKTplLl9rZXlNYW5hZ2VyLnNldEFjdGl2ZUl0ZW0oLTEpfV9jYW5PcGVuKCl7bGV0IGU9dGhpcy5fZWxlbWVudC5uYXRpdmVFbGVtZW50O3JldHVybiFlLnJlYWRPbmx5JiYhZS5kaXNhYmxlZCYmIXRoaXMuX2F1dG9jb21wbGV0ZURpc2FibGVkfV9nZXRXaW5kb3coKXtyZXR1cm4gdGhpcy5fZG9jdW1lbnQ/LmRlZmF1bHRWaWV3fHx3aW5kb3d9X3Njcm9sbFRvT3B0aW9uKGUpe2xldCBpPXRoaXMuYXV0b2NvbXBsZXRlLHI9b3coZSxpLm9wdGlvbnMsaS5vcHRpb25Hcm91cHMpO2lmKDA9PT1lJiYxPT09cilpLl9zZXRTY3JvbGxUb3AoMCk7ZWxzZSBpZihpLnBhbmVsKXtsZXQgbz1pLm9wdGlvbnMudG9BcnJheSgpW2VdO2lmKG8pe2xldCBzPW8uX2dldEhvc3RFbGVtZW50KCksYT1fMihzLm9mZnNldFRvcCxzLm9mZnNldEhlaWdodCxpLl9nZXRTY3JvbGxUb3AoKSxpLnBhbmVsLm5hdGl2ZUVsZW1lbnQub2Zmc2V0SGVpZ2h0KTtpLl9zZXRTY3JvbGxUb3AoYSl9fX1faGFuZGxlT3ZlcmxheUV2ZW50cyhlKXtlLmtleWRvd25FdmVudHMoKS5zdWJzY3JpYmUoaT0+eygyNz09PWkua2V5Q29kZSYmIWtyKGkpfHwzOD09PWkua2V5Q29kZSYma3IoaSwiYWx0S2V5IikpJiYodGhpcy5fcGVuZGluZ0F1dG9zZWxlY3RlZE9wdGlvbiYmKHRoaXMuX3VwZGF0ZU5hdGl2ZUlucHV0VmFsdWUodGhpcy5fdmFsdWVCZWZvcmVBdXRvU2VsZWN0aW9uPz8iIiksdGhpcy5fcGVuZGluZ0F1dG9zZWxlY3RlZE9wdGlvbj1udWxsKSx0aGlzLl9jbG9zZUtleUV2ZW50U3RyZWFtLm5leHQoKSx0aGlzLl9yZXNldEFjdGl2ZUl0ZW0oKSxpLnN0b3BQcm9wYWdhdGlvbigpLGkucHJldmVudERlZmF1bHQoKSl9KSxlLm91dHNpZGVQb2ludGVyRXZlbnRzKCkuc3Vic2NyaWJlKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpLE0odHIpLE0oT2kpLE0oX3QpLE0obm4pLE0oRnBlKSxNKCRpLDgpLE0oc2csOSksTShIdCw4KSxNKFZhKSxNKGtwZSw4KSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4saW5wdXRzOnthdXRvY29tcGxldGU6WyJtYXRBdXRvY29tcGxldGUiLCJhdXRvY29tcGxldGUiXSxwb3NpdGlvbjpbIm1hdEF1dG9jb21wbGV0ZVBvc2l0aW9uIiwicG9zaXRpb24iXSxjb25uZWN0ZWRUbzpbIm1hdEF1dG9jb21wbGV0ZUNvbm5lY3RlZFRvIiwiY29ubmVjdGVkVG8iXSxhdXRvY29tcGxldGVBdHRyaWJ1dGU6WyJhdXRvY29tcGxldGUiLCJhdXRvY29tcGxldGVBdHRyaWJ1dGUiXSxhdXRvY29tcGxldGVEaXNhYmxlZDpbIm1hdEF1dG9jb21wbGV0ZURpc2FibGVkIiwiYXV0b2NvbXBsZXRlRGlzYWJsZWQiXX0sZmVhdHVyZXM6W0Z0XX0pLG59KSgpLCRnPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBiUWV7Y29uc3RydWN0b3IoKXtzdXBlciguLi5hcmd1bWVudHMpLHRoaXMuX2Fib3ZlQ2xhc3M9Im1hdC1hdXRvY29tcGxldGUtcGFuZWwtYWJvdmUifX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gZnVuY3Rpb24oaSl7cmV0dXJuKHR8fCh0PXBpKG4pKSkoaXx8bil9fSgpLG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbImlucHV0IiwibWF0QXV0b2NvbXBsZXRlIiwiIl0sWyJ0ZXh0YXJlYSIsIm1hdEF1dG9jb21wbGV0ZSIsIiJdXSxob3N0QXR0cnM6WzEsIm1hdC1hdXRvY29tcGxldGUtdHJpZ2dlciJdLGhvc3RWYXJzOjcsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MSZlJiZQKCJmb2N1c2luIixmdW5jdGlvbigpe3JldHVybiBpLl9oYW5kbGVGb2N1cygpfSkoImJsdXIiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuX29uVG91Y2hlZCgpfSkoImlucHV0IixmdW5jdGlvbihvKXtyZXR1cm4gaS5faGFuZGxlSW5wdXQobyl9KSgia2V5ZG93biIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX2hhbmRsZUtleWRvd24obyl9KSgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkuX2hhbmRsZUNsaWNrKCl9KSwyJmUmJnplKCJhdXRvY29tcGxldGUiLGkuYXV0b2NvbXBsZXRlQXR0cmlidXRlKSgicm9sZSIsaS5hdXRvY29tcGxldGVEaXNhYmxlZD9udWxsOiJjb21ib2JveCIpKCJhcmlhLWF1dG9jb21wbGV0ZSIsaS5hdXRvY29tcGxldGVEaXNhYmxlZD9udWxsOiJsaXN0IikoImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsaS5wYW5lbE9wZW4mJmkuYWN0aXZlT3B0aW9uP2kuYWN0aXZlT3B0aW9uLmlkOm51bGwpKCJhcmlhLWV4cGFuZGVkIixpLmF1dG9jb21wbGV0ZURpc2FibGVkP251bGw6aS5wYW5lbE9wZW4udG9TdHJpbmcoKSkoImFyaWEtb3ducyIsaS5hdXRvY29tcGxldGVEaXNhYmxlZHx8IWkucGFuZWxPcGVufHxudWxsPT1pLmF1dG9jb21wbGV0ZT9udWxsOmkuYXV0b2NvbXBsZXRlLmlkKSgiYXJpYS1oYXNwb3B1cCIsaS5hdXRvY29tcGxldGVEaXNhYmxlZD9udWxsOiJsaXN0Ym94Iil9LGV4cG9ydEFzOlsibWF0QXV0b2NvbXBsZXRlVHJpZ2dlciJdLGZlYXR1cmVzOlskdChbeVFlXSksdHRdfSksbn0pKCksSWI9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbdlFlXSxpbXBvcnRzOltzcyxBdixsbixNZSx1ZCxBdixsbl19KSxufSkoKSxZaz0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy52YWx1ZT0iIix0aGlzLnBsYWNlaG9sZGVyPSIifW9uSW5wdXRLZXlVcChlKXsiRW50ZXIiPT09ZS5rZXkmJnRoaXMuYXV0b2NvbXBsZXRlVHJpZ2dlci5jbG9zZVBhbmVsKCl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInRiLWZpbHRlci1pbnB1dCJdXSx2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJm90KCRnLDUpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkuYXV0b2NvbXBsZXRlVHJpZ2dlcj1yLmZpcnN0KX19LGlucHV0czp7dmFsdWU6InZhbHVlIixtYXRBdXRvY29tcGxldGU6Im1hdEF1dG9jb21wbGV0ZSIscGxhY2Vob2xkZXI6InBsYWNlaG9sZGVyIn0sZGVjbHM6Mix2YXJzOjQsY29uc3RzOltbInN2Z0ljb24iLCJzZWFyY2hfMjRweCJdLFsidHlwZSIsInRleHQiLCJhdXRvY29tcGxldGUiLCJvZmYiLDMsInBsYWNlaG9sZGVyIiwibWF0QXV0b2NvbXBsZXRlIiwibWF0QXV0b2NvbXBsZXRlRGlzYWJsZWQiLCJ2YWx1ZSIsImtleXVwIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJtYXQtaWNvbiIsMCksXygxLCJpbnB1dCIsMSksUCgia2V5dXAiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uSW5wdXRLZXlVcChvKX0pLHYoKSksMiZlJiYoQygxKSx5KCJwbGFjZWhvbGRlciIsaS5wbGFjZWhvbGRlcikoIm1hdEF1dG9jb21wbGV0ZSIsaS5tYXRBdXRvY29tcGxldGUpKCJtYXRBdXRvY29tcGxldGVEaXNhYmxlZCIsIWkubWF0QXV0b2NvbXBsZXRlKSgidmFsdWUiLGkudmFsdWUpKX0sZGVwZW5kZW5jaWVzOlskZyxHdF0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXXtkaXNwbGF5OmZsZXg7Zm9udC1zaXplOjEzcHh9bWF0LWljb25bX25nY29udGVudC0lQ09NUCVde2NvbG9yOiM2MTYxNjE7ZmxleDpub25lO21hcmdpbi1yaWdodDo1cHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgbWF0LWljb25bX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICBtYXQtaWNvbltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9aW5wdXRbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6aW5oZXJpdDtjYXJldC1jb2xvcjpjdXJyZW50Q29sb3I7Y29sb3I6Y3VycmVudENvbG9yO2ZvbnQ6aW5oZXJpdDtib3JkZXI6bm9uZTtvdXRsaW5lOm5vbmU7cGFkZGluZzowO2ZsZXgtZ3JvdzoxfSJdfSksbn0pKCk7ZnVuY3Rpb24geFFlKG4sdCl7MSZuJiZPKDAsIm1hdC1pY29uIiw3KX1mdW5jdGlvbiBDUWUobix0KXtpZigxJm4mJihfKDAsIm1hdC1vcHRpb24iLDgpLEEoMSksdigpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0O3koInZhbHVlIixlKSx6ZSgidGl0bGUiLGUpLEMoMSkseXQoZSl9fWZ1bmN0aW9uIE1RZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2Iiw5KSgxLCJlbSIpLEEoMiksQigzLCJudW1iZXIiKSx2KCkoKSksMiZuKXtsZXQgZT1TKCk7QygyKSxqZSgiYW5kICIsVSgzLDEsZS5jb21wbGV0aW9ucy5sZW5ndGgtMjUpLCIgbW9yZSB0YWdzIG1hdGNoZWQiKX19dmFyIExwZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5vblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2U9bmV3IEd9b25Db21wbGV0aW9uQWNjZXB0ZWQoZSl7dGhpcy5vblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2UuZW1pdChmdW5jdGlvbihuKXtyZXR1cm4gbi5yZXBsYWNlKE9PZSwiXFwkJiIpfShlKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1ldHJpY3MtdGFnLWZpbHRlci1jb21wb25lbnQiXV0saG9zdFZhcnM6Mixob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsyJmUmJmV0KCJ2YWxpZCIsaS5pc1JlZ2V4RmlsdGVyVmFsaWQpfSxpbnB1dHM6e3JlZ2V4RmlsdGVyVmFsdWU6InJlZ2V4RmlsdGVyVmFsdWUiLGlzUmVnZXhGaWx0ZXJWYWxpZDoiaXNSZWdleEZpbHRlclZhbGlkIixjb21wbGV0aW9uczoiY29tcGxldGlvbnMifSxvdXRwdXRzOntvblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2U6Im9uUmVnZXhGaWx0ZXJWYWx1ZUNoYW5nZSJ9LGRlY2xzOjcsdmFyczo1LGNvbnN0czpbWzEsInRhZy1maWx0ZXIiXSxbInBsYWNlaG9sZGVyIiwiRmlsdGVyIHRhZ3MgKHJlZ2V4KSIsMywidmFsdWUiLCJtYXRBdXRvY29tcGxldGUiLCJpbnB1dCJdLFsic3ZnSWNvbiIsImVycm9yXzI0cHgiLCJjbGFzcyIsImVycm9yLWljb24iLCJ0aXRsZSIsIkludmFsaWQgcmVnZXggZmlsdGVyLiBUaGUgcmVzdWx0IG1heSBiZSBzdGFsZS4iLDQsIm5nSWYiXSxbMSwidGFnLW9wdGlvbnMiLDMsIm9wdGlvblNlbGVjdGVkIl0sWyJmaWx0ZXJNYXRjaGVzIiwibWF0QXV0b2NvbXBsZXRlIl0sWyJjbGFzcyIsIm9wdGlvbiIsMywidmFsdWUiLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsiY2xhc3MiLCJhbmQtbW9yZSIsNCwibmdJZiJdLFsic3ZnSWNvbiIsImVycm9yXzI0cHgiLCJ0aXRsZSIsIkludmFsaWQgcmVnZXggZmlsdGVyLiBUaGUgcmVzdWx0IG1heSBiZSBzdGFsZS4iLDEsImVycm9yLWljb24iXSxbMSwib3B0aW9uIiwzLCJ2YWx1ZSJdLFsxLCJhbmQtbW9yZSJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKF8oMCwiZGl2IiwwKSgxLCJ0Yi1maWx0ZXItaW5wdXQiLDEpLFAoImlucHV0IixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2UuZW1pdChvLnRhcmdldC52YWx1ZSl9KSx2KCksRSgyLHhRZSwxLDAsIm1hdC1pY29uIiwyKSx2KCksXygzLCJtYXQtYXV0b2NvbXBsZXRlIiwzLDQpLFAoIm9wdGlvblNlbGVjdGVkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vbkNvbXBsZXRpb25BY2NlcHRlZChvLm9wdGlvbi52YWx1ZSl9KSxFKDUsQ1FlLDIsMywibWF0LW9wdGlvbiIsNSksRSg2LE1RZSw0LDMsImRpdiIsNiksdigpKSwyJmUpe2xldCByPSRlKDQpO0MoMSkseSgidmFsdWUiLGkucmVnZXhGaWx0ZXJWYWx1ZSkoIm1hdEF1dG9jb21wbGV0ZSIsciksQygxKSx5KCJuZ0lmIiwhaS5pc1JlZ2V4RmlsdGVyVmFsaWQpLEMoMykseSgibmdGb3JPZiIsbnVsbD09aS5jb21wbGV0aW9ucz9udWxsOmkuY29tcGxldGlvbnMuc2xpY2UoMCwyNSkpLEMoMSkseSgibmdJZiIsKG51bGw9PWkuY29tcGxldGlvbnM/bnVsbDppLmNvbXBsZXRpb25zLmxlbmd0aCk+MjUpfX0sZGVwZW5kZW5jaWVzOltkbixCZSxZayxxayxPcyxHdCxRbF0sc3R5bGVzOlsiLnRhZy1maWx0ZXJbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6ZmxleDtwb3NpdGlvbjpyZWxhdGl2ZX10Yi1maWx0ZXItaW5wdXRbX25nY29udGVudC0lQ09NUCVde2ZsZXgtZ3JvdzoxfVtfbmdob3N0LSVDT01QJV17Y29sb3I6IzIxMjEyMX1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV17Y29sb3I6I2ZmZn1bX25naG9zdC0lQ09NUCVdOm5vdCgudmFsaWQpe2NvbG9yOiNjNjI4Mjh9W19uZ2hvc3QtJUNPTVAlXTpub3QoLnZhbGlkKSAgIC5lcnJvci1pY29uW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjojYzYyODI4O3Bvc2l0aW9uOmFic29sdXRlO3JpZ2h0OjB9ICAudGFnLW9wdGlvbnMgLm9wdGlvbiwgICAudGFnLW9wdGlvbnMgLmFuZC1tb3Jley13ZWJraXQtYm94LW9yaWVudDp2ZXJ0aWNhbDstd2Via2l0LWxpbmUtY2xhbXA6MztkaXNwbGF5Oi13ZWJraXQtYm94O2ZvbnQtc2l6ZToxNHB4O2xpbmUtaGVpZ2h0OjEuNDtwYWRkaW5nOjhweCAxNnB4fSAgLnRhZy1vcHRpb25zIC5hbmQtbW9yZXtjb2xvcjojNjE2MTYxfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgICAgLnRhZy1vcHRpb25zIC5hbmQtbW9yZSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgICAudGFnLW9wdGlvbnMgLmFuZC1tb3Jle2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksQnBlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMudGFnRmlsdGVyJD10aGlzLnN0b3JlLnNlbGVjdChYYyksdGhpcy5pc1RhZ0ZpbHRlclJlZ2V4VmFsaWQkPXRoaXMudGFnRmlsdGVyJC5waXBlKEwoaT0+e3RyeXtyZXR1cm4gbmV3IFJlZ0V4cChpKSwhMH1jYXRjaHtyZXR1cm4hMX19KSksdGhpcy5jb21wbGV0aW9ucyQ9dGhpcy5zdG9yZS5zZWxlY3QoUEkpLnBpcGUoZnIodGhpcy5zdG9yZS5zZWxlY3QobmQpKSxMKChbaSxyXSk9PmkuZmlsdGVyKCh7cGx1Z2luOm99KT0+IXIuc2l6ZXx8ci5oYXMobykpLm1hcCgoe3RhZzpvfSk9Pm8pKSxMKGk9PlsuLi5uZXcgU2V0KGkpXSksTChpPT5pLnNvcnQoRncpKSxmcih0aGlzLnN0b3JlLnNlbGVjdChYYykpLEwoKFtpLHJdKT0+e3RyeXtyZXR1cm5baSxuZXcgUmVnRXhwKHIsImkiKV19Y2F0Y2h7cmV0dXJuW2ksbnVsbF19fSksWWUoKFssaV0pPT5udWxsIT09aSksTCgoW2kscl0pPT5pLmZpbHRlcihvPT5yLnRlc3QobykpKSl9b25UYWdGaWx0ZXJDaGFuZ2UoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChzUih7dGFnRmlsdGVyOmV9KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtZXRyaWNzLXRhZy1maWx0ZXIiXV0sZGVjbHM6NCx2YXJzOjksY29uc3RzOltbMywicmVnZXhGaWx0ZXJWYWx1ZSIsImlzUmVnZXhGaWx0ZXJWYWxpZCIsImNvbXBsZXRpb25zIiwib25SZWdleEZpbHRlclZhbHVlQ2hhbmdlIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJtZXRyaWNzLXRhZy1maWx0ZXItY29tcG9uZW50IiwwKSxQKCJvblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uVGFnRmlsdGVyQ2hhbmdlKG8pfSksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIiksdigpKSwyJmUmJnkoInJlZ2V4RmlsdGVyVmFsdWUiLFUoMSwzLGkudGFnRmlsdGVyJCkpKCJpc1JlZ2V4RmlsdGVyVmFsaWQiLFUoMiw1LGkuaXNUYWdGaWx0ZXJSZWdleFZhbGlkJCkpKCJjb21wbGV0aW9ucyIsVSgzLDcsaS5jb21wbGV0aW9ucyQpKX0sZGVwZW5kZW5jaWVzOltMcGUsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIEVRZShuLHQpe2lmKDEmbiYmKF8oMCwic3BhbiIsOCksQSgxKSx2KCkpLDImbil7bGV0IGU9UygpO0MoMSksamUoIiIsZS5jYXJkSWRzV2l0aE1ldGFkYXRhLmxlbmd0aCwiIGNhcmRzIil9fWZ1bmN0aW9uIFRRZShuLHQpezEmbiYmKF8oMCwic3BhbiIsOSksQSgxLCJOZXcgY2FyZCBwaW5uZWQiKSx2KCkpLDImbiYmemUoImRhdGEtaWQiLHQuJGltcGxpY2l0KX1mdW5jdGlvbiBEUWUobix0KXtpZigxJm4mJk8oMCwibWV0cmljcy1jYXJkLWdyaWQiLDEwKSwyJm4pe2xldCBlPVMoKTt5KCJjYXJkSWRzV2l0aE1ldGFkYXRhIixlLmNhcmRJZHNXaXRoTWV0YWRhdGEpKCJjYXJkT2JzZXJ2ZXIiLGUuY2FyZE9ic2VydmVyKX19ZnVuY3Rpb24gQVFlKG4sdCl7MSZuJiYoXygwLCJkaXYiLDExKSxBKDEsIlBpbiBjYXJkcyBmb3IgYSBxdWljayB2aWV3IGFuZCBjb21wYXJpc29uIiksdigpKX12YXIgVnBlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1ldHJpY3MtcGlubmVkLXZpZXctY29tcG9uZW50Il1dLGlucHV0czp7Y2FyZE9ic2VydmVyOiJjYXJkT2JzZXJ2ZXIiLGNhcmRJZHNXaXRoTWV0YWRhdGE6ImNhcmRJZHNXaXRoTWV0YWRhdGEiLG5ld0NhcmRQaW5uZWRJZHM6Im5ld0NhcmRQaW5uZWRJZHMifSxkZWNsczoxMCx2YXJzOjQsY29uc3RzOltbMSwiZ3JvdXAtdG9vbGJhciJdLFsic3ZnSWNvbiIsImtlZXBfMjRweCJdLFsxLCJncm91cC10ZXh0Il0sWyJhcmlhLXJvbGUiLCJoZWFkaW5nIiwiYXJpYS1sZXZlbCIsIjMiLDEsImdyb3VwLXRpdGxlIl0sWyJjbGFzcyIsImdyb3VwLWNhcmQtY291bnQiLDQsIm5nSWYiXSxbImNsYXNzIiwibmV3LWNhcmQtcGlubmVkIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbMywiY2FyZElkc1dpdGhNZXRhZGF0YSIsImNhcmRPYnNlcnZlciIsNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJlbXB0eVBpbm5lZFZpZXciLCIiXSxbMSwiZ3JvdXAtY2FyZC1jb3VudCJdLFsxLCJuZXctY2FyZC1waW5uZWQiXSxbMywiY2FyZElkc1dpdGhNZXRhZGF0YSIsImNhcmRPYnNlcnZlciJdLFsxLCJlbXB0eS1tZXNzYWdlIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoXygwLCJkaXYiLDApLE8oMSwibWF0LWljb24iLDEpLF8oMiwic3BhbiIsMikoMywic3BhbiIsMyksQSg0LCJQaW5uZWQiKSx2KCksRSg1LEVRZSwyLDEsInNwYW4iLDQpLEUoNixUUWUsMiwxLCJzcGFuIiw1KSx2KCkoKSxFKDcsRFFlLDEsMiwibWV0cmljcy1jYXJkLWdyaWQiLDYpLEUoOCxBUWUsMiwwLCJuZy10ZW1wbGF0ZSIsbnVsbCw3LHF0KSksMiZlKXtsZXQgcj0kZSg5KTtDKDUpLHkoIm5nSWYiLGkuY2FyZElkc1dpdGhNZXRhZGF0YS5sZW5ndGg+MSksQygxKSx5KCJuZ0Zvck9mIixpLm5ld0NhcmRQaW5uZWRJZHMpLEMoMSkseSgibmdJZiIsaS5jYXJkSWRzV2l0aE1ldGFkYXRhLmxlbmd0aCkoIm5nSWZFbHNlIixyKX19LGRlcGVuZGVuY2llczpbZG4sQmUsR3QsQWJdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZmZmO2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7YWxpZ24taXRlbXM6Y2VudGVyO2JhY2tncm91bmQtY29sb3I6I2ZmZjtkaXNwbGF5OmZsZXg7ZmxleDpub25lO2hlaWdodDo0MnB4O21hcmdpbi1ib3R0b206LTFweDtwYWRkaW5nOjAgMTZweDtwb3NpdGlvbjpzdGlja3k7dG9wOjA7ei1pbmRleDoxO2JveC1zaGFkb3c6MHB4IDJweCA0cHggMHB4IHJnYmEoMCwwLDAsLjE1KX1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojMzAzMDMwfWJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjNTU1fWJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3gtc2hhZG93OjBweCAycHggNHB4IDBweCByZ2JhKDI1NSwyNTUsMjU1LC4xNSl9bWF0LWljb25bX25nY29udGVudC0lQ09NUCVde2NvbG9yOiM2MTYxNjE7ZmxleDpub25lO21hcmdpbi1yaWdodDo1cHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgbWF0LWljb25bX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICBtYXQtaWNvbltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Lmdyb3VwLXRleHRbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6ZmxleDthbGlnbi1pdGVtczpiYXNlbGluZTtnYXA6NnB4fS5ncm91cC10aXRsZVtfbmdjb250ZW50LSVDT01QJV17Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwfS5ncm91cC1jYXJkLWNvdW50W19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXNpemU6MTJweDtmb250LXdlaWdodDo0MDA7Y29sb3I6IzYxNjE2MX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXAtY2FyZC1jb3VudFtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC1jYXJkLWNvdW50W19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX0uZW1wdHktbWVzc2FnZVtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTtmb250LXNpemU6MTNweDtmb250LXN0eWxlOml0YWxpYztwYWRkaW5nOjE2cHg7dGV4dC1hbGlnbjpjZW50ZXJ9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmVtcHR5LW1lc3NhZ2VbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZW1wdHktbWVzc2FnZVtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNyl9Lm5ldy1jYXJkLXBpbm5lZFtfbmdjb250ZW50LSVDT01QJV17YW5pbWF0aW9uOnBpbm5lZC12aWV3LWZhZGUtb3V0IDNzIGxpbmVhcjtiYWNrZ3JvdW5kOiNmNDQzMzY7Ym9yZGVyLXJhZGl1czo1cHg7Y29sb3I6I2ZmZjtkaXNwbGF5OmlubGluZS1ibG9jaztmb250LXNpemU6MTNweDtvcGFjaXR5OjA7cGFkZGluZzozcHggNXB4fUBrZXlmcmFtZXMgcGlubmVkLXZpZXctZmFkZS1vdXR7ZnJvbXtvcGFjaXR5OjF9NjYle29wYWNpdHk6Ljk5fXRve29wYWNpdHk6MH19Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxIcGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5jYXJkSWRzV2l0aE1ldGFkYXRhJD10aGlzLnN0b3JlLnNlbGVjdCh6TSkucGlwZSh6bihbXSkpLHRoaXMubmV3Q2FyZFBpbm5lZElkcyQ9dGhpcy5zdG9yZS5zZWxlY3Qoek0pLnBpcGUoWmEoMSksTChpPT5pLm1hcChyPT5yLmNhcmRJZCkpLHkwKCksTCgoW2kscl0pPT57bGV0IG89bmV3IFNldChpKSxzPW5ldyBTZXQocik7Zm9yKGxldCBhIG9mIHMpaWYoIW8uaGFzKGEpKXJldHVybiBEYXRlLm5vdygpO3JldHVybiBudWxsfSksem4obnVsbCkseTAoKSxMKChbaSxyXSk9Pm51bGw9PT1pJiZudWxsPT09cj9udWxsOm51bGw9PT1yP1tpXTpbcl0pLFllKGk9Pm51bGwhPT1pKSxMKGk9PltpWzBdXSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1waW5uZWQtdmlldyJdXSxpbnB1dHM6e2NhcmRPYnNlcnZlcjoiY2FyZE9ic2VydmVyIn0sZGVjbHM6Myx2YXJzOjcsY29uc3RzOltbMywiY2FyZElkc1dpdGhNZXRhZGF0YSIsIm5ld0NhcmRQaW5uZWRJZHMiLCJjYXJkT2JzZXJ2ZXIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihPKDAsIm1ldHJpY3MtcGlubmVkLXZpZXctY29tcG9uZW50IiwwKSxCKDEsImFzeW5jIiksQigyLCJhc3luYyIpKSwyJmUmJnkoImNhcmRJZHNXaXRoTWV0YWRhdGEiLFUoMSwzLGkuY2FyZElkc1dpdGhNZXRhZGF0YSQpKSgibmV3Q2FyZFBpbm5lZElkcyIsVSgyLDUsaS5uZXdDYXJkUGlubmVkSWRzJCkpKCJjYXJkT2JzZXJ2ZXIiLGkuY2FyZE9ic2VydmVyKX0sZGVwZW5kZW5jaWVzOltWcGUsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIFJRZShuLHQpezEmbiYmTygwLCJtZXRyaWNzLWZpbHRlcmVkLXZpZXciLDEyKSwyJm4mJnkoImNhcmRPYnNlcnZlciIsUygpLmNhcmRPYnNlcnZlcil9ZnVuY3Rpb24gT1FlKG4sdCl7MSZuJiYoXygwLCJkaXYiLDE2KSxPKDEsIm1hdC1zcGlubmVyIiwxNyksdigpKX12YXIga1FlPWZ1bmN0aW9uKG4pe3JldHVybnsic2xpZGUtb3V0LW1lbnUtZXhwYW5kZWQiOm59fTtmdW5jdGlvbiBGUWUobix0KXtpZigxJm4mJihfKDAsImRpdiIsMTgpLE8oMSwibWV0cmljcy1zY2FsYXItY29sdW1uLWVkaXRvciIpLHYoKSksMiZuKXtsZXQgZT1TKCk7eSgibmdDbGFzcyIsT24oMSxrUWUsZS5zbGlkZU91dE1lbnVPcGVuKSl9fWZ1bmN0aW9uIE5RZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMTkpKDEsImRpdiIsMjApKDIsImgyIiwyMSksQSgzLCJTZXR0aW5ncyIpLHYoKSxfKDQsImJ1dHRvbiIsMjIpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkub25DbG9zZVNpZGVwYW5lQnV0dG9uQ2xpY2tlZC5lbWl0KCkpfSksTyg1LCJtYXQtaWNvbiIsMjMpLHYoKSgpLE8oNiwibWV0cmljcy1kYXNoYm9hcmQtcmlnaHQtcGFuZSIpLHYoKX19dmFyIExRZT1mdW5jdGlvbihuKXtyZXR1cm57Y2hlY2tlZDpuLCJzZXR0aW5ncy1idXR0b24iOiEwfX0sVXBlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5ob3N0PWUsdGhpcy5vblNldHRpbmdzQnV0dG9uQ2xpY2tlZD1uZXcgRyx0aGlzLm9uQ2xvc2VTaWRlcGFuZUJ1dHRvbkNsaWNrZWQ9bmV3IEcsdGhpcy5vblBsdWdpblR5cGVUb2dnbGVkPW5ldyBHLHRoaXMub25QbHVnaW5UeXBlQWxsVG9nZ2xlZD1uZXcgRyx0aGlzLlBsdWdpblR5cGU9cmksdGhpcy5jYXJkT2JzZXJ2ZXI9bmV3IE53KHRoaXMuaG9zdC5uYXRpdmVFbGVtZW50LCI2MDBweCAwcHggNjAwcHggMHB4Iil9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtZXRyaWNzLW1haW4tdmlldy1jb21wb25lbnQiXV0saW5wdXRzOntzaG93RmlsdGVyZWRWaWV3OiJzaG93RmlsdGVyZWRWaWV3Iixpc1NpZGVwYW5lT3BlbjoiaXNTaWRlcGFuZU9wZW4iLGZpbHRlcmVkUGx1Z2luVHlwZXM6ImZpbHRlcmVkUGx1Z2luVHlwZXMiLGluaXRpYWxUYWdzTG9hZGluZzoiaW5pdGlhbFRhZ3NMb2FkaW5nIixzbGlkZU91dE1lbnVPcGVuOiJzbGlkZU91dE1lbnVPcGVuIn0sb3V0cHV0czp7b25TZXR0aW5nc0J1dHRvbkNsaWNrZWQ6Im9uU2V0dGluZ3NCdXR0b25DbGlja2VkIixvbkNsb3NlU2lkZXBhbmVCdXR0b25DbGlja2VkOiJvbkNsb3NlU2lkZXBhbmVCdXR0b25DbGlja2VkIixvblBsdWdpblR5cGVUb2dnbGVkOiJvblBsdWdpblR5cGVUb2dnbGVkIixvblBsdWdpblR5cGVBbGxUb2dnbGVkOiJvblBsdWdpblR5cGVBbGxUb2dnbGVkIn0sZGVjbHM6MjMsdmFyczoyMixjb25zdHM6ZnVuY3Rpb24oKXtsZXQgdCxlO3JldHVybiB0PSRsb2NhbGl6ZWA6TGFiZWwgb24gYSB0b29sYmFyIGJ1dHRvbiB0byB0b2dnbGUgdGhlIHNldHRpbmdzIHNpZGUgcGFuZS7ikJ9kMzUxNmRiNmJiZTY4NjBhNTViZWFiNjZlNDk2OWRhYzYyNWI4ZDcy4pCfNzY1OTI4NTQ0NTU4MDgzODkyNTpUb2dnbGUgc2V0dGluZ3Mgc2lkZSBwYW5lYCxlPSRsb2NhbGl6ZWA6TGFiZWwgb24gYSBidXR0b24gdG8gY2xvc2UgdGhlIHNldHRpbmdzIHNpZGUgcGFuZS7ikJ8wNDUyMWRjMGI2YTY1Y2Y1YzM4Mjk0NGM5YThiNGI4NDRhM2U5NTk44pCfODE1Njc2Njk5Nzc0NzE2NTg3MTpDbG9zZSBzaWRlIHBhbmVgLFtbMSwidG9vbGJhciJdLFsibXVsdGlwbGUiLCIiLCJhcHBlYXJhbmNlIiwic3RhbmRhcmQiLDEsImZpbHRlci12aWV3Il0sWyJtYXQtYnV0dG9uIiwiIiwicm9sZSIsImNoZWNrYm94IiwiZGF0YS12YWx1ZSIsImFsbCIsMSwiZmlsdGVyLXZpZXctYnV0dG9uIiwzLCJjbGljayJdLFsibWF0LWJ1dHRvbiIsIiIsInJvbGUiLCJjaGVja2JveCIsImRhdGEtdmFsdWUiLCJzY2FsYXJzIiwxLCJmaWx0ZXItdmlldy1idXR0b24iLDMsImNsaWNrIl0sWyJtYXQtYnV0dG9uIiwiIiwicm9sZSIsImNoZWNrYm94IiwiZGF0YS12YWx1ZSIsImltYWdlIiwxLCJmaWx0ZXItdmlldy1idXR0b24iLDMsImNsaWNrIl0sWyJtYXQtYnV0dG9uIiwiIiwicm9sZSIsImNoZWNrYm94IiwiZGF0YS12YWx1ZSIsImhpc3RvZ3JhbSIsMSwiZmlsdGVyLXZpZXctYnV0dG9uIiwzLCJjbGljayJdLFsxLCJyaWdodC1pdGVtcyJdLFsibWF0LXN0cm9rZWQtYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsdCwzLCJuZ0NsYXNzIiwiY2xpY2siXSxbInN2Z0ljb24iLCJzZXR0aW5nc18yNHB4Il0sWzEsInNwbGl0LWNvbnRlbnQiXSxbImNka1Njcm9sbGFibGUiLCIiXSxbMywiY2FyZE9ic2VydmVyIiw0LCJuZ0lmIl0sWzMsImNhcmRPYnNlcnZlciJdLFsiY2xhc3MiLCJsb2FkaW5nLWNvbnRhaW5lciIsNCwibmdJZiJdLFsiY2xhc3MiLCJzbGlkZS1vdXQtbWVudSIsMywibmdDbGFzcyIsNCwibmdJZiJdLFsiY2xhc3MiLCJzaWRlYmFyIiw0LCJuZ0lmIl0sWzEsImxvYWRpbmctY29udGFpbmVyIl0sWyJkaWFtZXRlciIsIjM2Il0sWzEsInNsaWRlLW91dC1tZW51IiwzLCJuZ0NsYXNzIl0sWzEsInNpZGViYXIiXSxbMSwiaGVhZGVyIl0sWzEsInRpdGxlIl0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJhcmlhLWxhYmVsIixlLDMsImNsaWNrIl0sWyJzdmdJY29uIiwiY2xvc2VfMjRweCJdXX0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCksTygxLCJtZXRyaWNzLXRhZy1maWx0ZXIiKSxfKDIsIm1hdC1idXR0b24tdG9nZ2xlLWdyb3VwIiwxKSgzLCJidXR0b24iLDIpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uUGx1Z2luVHlwZUFsbFRvZ2dsZWQuZW1pdCgpfSksQSg0LCIgQWxsICIpLHYoKSxfKDUsImJ1dHRvbiIsMyksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25QbHVnaW5UeXBlVG9nZ2xlZC5lbWl0KGkuUGx1Z2luVHlwZS5TQ0FMQVJTKX0pLEEoNiwiIFNjYWxhcnMgIiksdigpLF8oNywiYnV0dG9uIiw0KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblBsdWdpblR5cGVUb2dnbGVkLmVtaXQoaS5QbHVnaW5UeXBlLklNQUdFUyl9KSxBKDgsIiBJbWFnZSAiKSx2KCksXyg5LCJidXR0b24iLDUpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uUGx1Z2luVHlwZVRvZ2dsZWQuZW1pdChpLlBsdWdpblR5cGUuSElTVE9HUkFNUyl9KSxBKDEwLCIgSGlzdG9ncmFtICIpLHYoKSgpLF8oMTEsImRpdiIsNikoMTIsImJ1dHRvbiIsNyksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25TZXR0aW5nc0J1dHRvbkNsaWNrZWQuZW1pdCgpfSksTygxMywibWF0LWljb24iLDgpLEEoMTQsIiBTZXR0aW5ncyAiKSx2KCkoKSgpLF8oMTUsImRpdiIsOSkoMTYsImRpdiIsMTApLEUoMTcsUlFlLDEsMSwibWV0cmljcy1maWx0ZXJlZC12aWV3IiwxMSksTygxOCwibWV0cmljcy1waW5uZWQtdmlldyIsMTIpLEUoMTksT1FlLDIsMCwiZGl2IiwxMyksTygyMCwibWV0cmljcy1jYXJkLWdyb3VwcyIsMTIpLHYoKSxFKDIxLEZRZSwyLDMsImRpdiIsMTQpLEUoMjIsTlFlLDcsMCwiZGl2IiwxNSksdigpKSwyJmUmJihDKDMpLHplKCJhcmlhLWNoZWNrZWQiLDA9PT1pLmZpbHRlcmVkUGx1Z2luVHlwZXMuc2l6ZSksQygyKSx6ZSgiYXJpYS1jaGVja2VkIixpLmZpbHRlcmVkUGx1Z2luVHlwZXMuaGFzKGkuUGx1Z2luVHlwZS5TQ0FMQVJTKSksQygyKSx6ZSgiYXJpYS1jaGVja2VkIixpLmZpbHRlcmVkUGx1Z2luVHlwZXMuaGFzKGkuUGx1Z2luVHlwZS5JTUFHRVMpKSxDKDIpLHplKCJhcmlhLWNoZWNrZWQiLGkuZmlsdGVyZWRQbHVnaW5UeXBlcy5oYXMoaS5QbHVnaW5UeXBlLkhJU1RPR1JBTVMpKSxDKDMpLHkoIm5nQ2xhc3MiLE9uKDIwLExRZSxpLmlzU2lkZXBhbmVPcGVuKSksemUoImFyaWEtcHJlc3NlZCIsaS5pc1NpZGVwYW5lT3BlbiksQyg0KSxldCgibWFpbiIsITApKCJmaWx0ZXItdmlldyIsaS5zaG93RmlsdGVyZWRWaWV3KSxDKDEpLHkoIm5nSWYiLGkuc2hvd0ZpbHRlcmVkVmlldyksQygxKSxQdCgiZGlzcGxheSIsaS5zaG93RmlsdGVyZWRWaWV3PyJub25lIjoiIikseSgiY2FyZE9ic2VydmVyIixpLmNhcmRPYnNlcnZlciksQygxKSx5KCJuZ0lmIixpLmluaXRpYWxUYWdzTG9hZGluZyksQygxKSxQdCgiZGlzcGxheSIsaS5zaG93RmlsdGVyZWRWaWV3PyJub25lIjoiIikseSgiY2FyZE9ic2VydmVyIixpLmNhcmRPYnNlcnZlciksQygxKSx5KCJuZ0lmIixpLmlzU2lkZXBhbmVPcGVuKSxDKDEpLHkoIm5nSWYiLGkuaXNTaWRlcGFuZU9wZW4pKX0sZGVwZW5kZW5jaWVzOltGbixCZSxJaCxfbixFVSxHdCxCbyxVb2UsV29lLEFwZSxPcGUsQnBlLEhwZV0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXXtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDAlfS50b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjZWJlYmViO2ZsZXg6bm9uZTtkaXNwbGF5OmZsZXg7YWxpZ24taXRlbXM6Y2VudGVyO2p1c3RpZnktY29udGVudDpzcGFjZS1iZXR3ZWVuO2hlaWdodDo0OHB4O3BhZGRpbmc6MCAxNnB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC50b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnRvb2xiYXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1ib3R0b206MXB4IHNvbGlkICM1NTV9LnRvb2xiYXJbX25nY29udGVudC0lQ09NUCVdICAgbWV0cmljcy10YWctZmlsdGVyW19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4OjEgMSAxMDBweH0udG9vbGJhcltfbmdjb250ZW50LSVDT01QJV0gICAucmlnaHQtaXRlbXNbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjFweCBzb2xpZCAjZWJlYmViO21hcmdpbi1sZWZ0OjE2cHg7cGFkZGluZy1sZWZ0OjE2cHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnRvb2xiYXJbX25nY29udGVudC0lQ09NUCVdICAgLnJpZ2h0LWl0ZW1zW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnRvb2xiYXJbX25nY29udGVudC0lQ09NUCVdICAgLnJpZ2h0LWl0ZW1zW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItbGVmdDoxcHggc29saWQgIzU1NX0uZmlsdGVyLXZpZXdbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1yYWRpdXM6NHB4O2ZsZXg6bm9uZTttYXJnaW4tcmlnaHQ6NXB4fS5maWx0ZXItdmlld1tfbmdjb250ZW50LSVDT01QJV0gICBidXR0b25bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1yYWRpdXM6MDtmb250LXNpemU6MTJweDtmb250LXdlaWdodDpub3JtYWw7aGVpZ2h0OjI1cHg7bGluZS1oZWlnaHQ6MjVweDttaW4td2lkdGg6dW5zZXQ7cGFkZGluZzowIDEycHh9LmZpbHRlci12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXSAgIGJ1dHRvbltfbmdjb250ZW50LSVDT01QJV0gKyBidXR0b25bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjFweCBzb2xpZCAjZWJlYmVifWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5maWx0ZXItdmlld1tfbmdjb250ZW50LSVDT01QJV0gICBidXR0b25bX25nY29udGVudC0lQ09NUCVdICsgYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLmZpbHRlci12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXSAgIGJ1dHRvbltfbmdjb250ZW50LSVDT01QJV0gKyBidXR0b25bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjFweCBzb2xpZCAjNTU1fS5maWx0ZXItdmlld1tfbmdjb250ZW50LSVDT01QJV0gICBidXR0b25bYXJpYS1jaGVja2VkPXRydWVdW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNlMGUwZTB9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmZpbHRlci12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXSAgIGJ1dHRvblthcmlhLWNoZWNrZWQ9dHJ1ZV1bX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZmlsdGVyLXZpZXdbX25nY29udGVudC0lQ09NUCVdICAgYnV0dG9uW2FyaWEtY2hlY2tlZD10cnVlXVtfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojMjEyMTIxfS5zcGxpdC1jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmZsZXg7b3ZlcmZsb3cteTphdXRvO2ZsZXg6MX0ubWFpbltfbmdjb250ZW50LSVDT01QJV0sIC5zaWRlYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXtjb250YWluOnN0cmljdDtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7b3ZlcmZsb3cteDpoaWRkZW47b3ZlcmZsb3cteTphdXRvO3dpbGwtY2hhbmdlOnRyYW5zZm9ybSxzY3JvbGwtcG9zaXRpb259Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLm1haW5bX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAubWFpbltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojMzAzMDMwfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5zaWRlYmFyW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnNpZGViYXJbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6IzMwMzAzMH0ubWFpbltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZjVmNmY3O2ZsZXg6MSAxO2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW59Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLm1haW5bX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAubWFpbltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojM2EzYTNhfS5tYWluW19uZ2NvbnRlbnQtJUNPTVAlXSAgIG1ldHJpY3MtZmlsdGVyZWQtdmlld1tfbmdjb250ZW50LSVDT01QJV0sIC5tYWluW19uZ2NvbnRlbnQtJUNPTVAlXSAgIG1ldHJpY3MtcGlubmVkLXZpZXdbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWJ9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLm1haW5bX25nY29udGVudC0lQ09NUCVdICAgbWV0cmljcy1maWx0ZXJlZC12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLm1haW5bX25nY29udGVudC0lQ09NUCVdICAgbWV0cmljcy1maWx0ZXJlZC12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjNTU1fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5tYWluW19uZ2NvbnRlbnQtJUNPTVAlXSAgIG1ldHJpY3MtcGlubmVkLXZpZXdbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAubWFpbltfbmdjb250ZW50LSVDT01QJV0gICBtZXRyaWNzLXBpbm5lZC12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjNTU1fS5tYWluLmZpbHRlci12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdzpoaWRkZW59Lm1haW4uZmlsdGVyLXZpZXdbX25nY29udGVudC0lQ09NUCVdICAgbWV0cmljcy1maWx0ZXJlZC12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXXtjb250YWluOmNvbnRlbnQ7b3ZlcmZsb3c6YXV0bzt3aWxsLWNoYW5nZTp0cmFuc2Zvcm0sc2Nyb2xsLXBvc2l0aW9ufS5sb2FkaW5nLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleDtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyO21hcmdpbjoyMHB4IDB9LnNpZGViYXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjFweCBzb2xpZCAjZWJlYmViO2ZsZXg6MCAwIDI1MHB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5zaWRlYmFyW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnNpZGViYXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1sZWZ0OjFweCBzb2xpZCAjNTU1fS5zaWRlYmFyW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5oZWFkZXJbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcjtqdXN0aWZ5LWNvbnRlbnQ6c3BhY2UtYmV0d2VlbjtoZWlnaHQ6NDJweDtwYWRkaW5nOjAgMTZweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuc2lkZWJhcltfbmdjb250ZW50LSVDT01QJV0gICAuaGVhZGVyW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnNpZGViYXJbX25nY29udGVudC0lQ09NUCVdICAgLmhlYWRlcltfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWJvdHRvbToxcHggc29saWQgIzU1NX0uc2lkZWJhcltfbmdjb250ZW50LSVDT01QJV0gICAuaGVhZGVyW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC50aXRsZVtfbmdjb250ZW50LSVDT01QJV17Zm9udC1zaXplOjE4cHg7Zm9udC13ZWlnaHQ6NDAwO2xpbmUtaGVpZ2h0Om5vcm1hbDttYXJnaW46MH1bX25naG9zdC0lQ09NUCVdICAgLnNldHRpbmdzLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTtkaXNwbGF5OmlubGluZS1mbGV4fWJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5zZXR0aW5ncy1idXR0b25bX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfVtfbmdob3N0LSVDT01QJV0gICAuc2V0dGluZ3MtYnV0dG9uLmNoZWNrZWRbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2UwZTBlMDtib3JkZXItY29sb3I6I2UwZTBlMH1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuc2V0dGluZ3MtYnV0dG9uLmNoZWNrZWRbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6IzIxMjEyMX1bX25naG9zdC0lQ09NUCVdICAgLnNldHRpbmdzLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV0gICAgIC5tYXQtYnV0dG9uLXdyYXBwZXJ7ZGlzcGxheTppbmxpbmUtZmxleDthbGlnbi1pdGVtczpjZW50ZXJ9W19uZ2hvc3QtJUNPTVAlXSAgIC5zZXR0aW5ncy1idXR0b25bX25nY29udGVudC0lQ09NUCVdICAgbWF0LWljb25bX25nY29udGVudC0lQ09NUCVde21hcmdpbi1yaWdodDo0cHh9LnNsaWRlLW91dC1tZW51W19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7aGVpZ2h0OjEwMCU7cG9zaXRpb246YWJzb2x1dGU7cmlnaHQ6NTBweDt0b3A6NDlweDt0cmFuc2l0aW9uOmFsbCAuNzVzIGVhc2U7dmlzaWJpbGl0eTpoaWRkZW47d2lkdGg6MjAwcHg7Ym9yZGVyLWxlZnQ6MXB4IHNvbGlkICNlYmViZWJ9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnNsaWRlLW91dC1tZW51W19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnNsaWRlLW91dC1tZW51W19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItbGVmdDoxcHggc29saWQgIzU1NX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuc2xpZGUtb3V0LW1lbnVbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuc2xpZGUtb3V0LW1lbnVbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6IzMwMzAzMH0uc2xpZGUtb3V0LW1lbnUtZXhwYW5kZWRbX25nY29udGVudC0lQ09NUCVde3JpZ2h0OjI1MHB4O3Zpc2liaWxpdHk6dmlzaWJsZX0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLHpwZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLmlzU2lkZXBhbmVPcGVuJD10aGlzLnN0b3JlLnNlbGVjdChISSksdGhpcy5pbml0aWFsVGFnc0xvYWRpbmckPXRoaXMuc3RvcmUuc2VsZWN0KFVNKS5waXBlKGN4KGk9Pm51bGw9PT1pLmxhc3RMb2FkZWRUaW1lSW5NcywhMCksTChpPT5pLnN0YXRlPT09T2UuTE9BRElORyYmbnVsbD09PWkubGFzdExvYWRlZFRpbWVJbk1zKSksdGhpcy5zaG93RmlsdGVyZWRWaWV3JD10aGlzLnN0b3JlLnNlbGVjdChYYykucGlwZShMKGk9PmkubGVuZ3RoPjApKSx0aGlzLmZpbHRlcmVkUGx1Z2luVHlwZXMkPXRoaXMuc3RvcmUuc2VsZWN0KG5kKSx0aGlzLmlzU2xpZGVvdXRNZW51T3BlbiQ9dGhpcy5zdG9yZS5zZWxlY3QoVUkpfW9uU2V0dGluZ3NCdXR0b25DbGlja2VkKCl7dGhpcy5zdG9yZS5kaXNwYXRjaChCUCgpKX1vbkNsb3NlU2lkZXBhbmVCdXR0b25DbGlja2VkKCl7dGhpcy5zdG9yZS5kaXNwYXRjaChMUCgpKX1vblBsdWdpblZpc2liaWxpdHlUb2dnbGVkKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2gobFIoe3BsdWdpbjplfSkpfW9uU2hvd0FsbFBsdWdpbnMoKXt0aGlzLnN0b3JlLmRpc3BhdGNoKGNSKCkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljcy1tYWluLXZpZXciXV0sZGVjbHM6Nix2YXJzOjE1LGNvbnN0czpbWzMsInNob3dGaWx0ZXJlZFZpZXciLCJpc1NpZGVwYW5lT3BlbiIsImluaXRpYWxUYWdzTG9hZGluZyIsImZpbHRlcmVkUGx1Z2luVHlwZXMiLCJzbGlkZU91dE1lbnVPcGVuIiwib25TZXR0aW5nc0J1dHRvbkNsaWNrZWQiLCJvbkNsb3NlU2lkZXBhbmVCdXR0b25DbGlja2VkIiwib25QbHVnaW5UeXBlVG9nZ2xlZCIsIm9uUGx1Z2luVHlwZUFsbFRvZ2dsZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1ldHJpY3MtbWFpbi12aWV3LWNvbXBvbmVudCIsMCksUCgib25TZXR0aW5nc0J1dHRvbkNsaWNrZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25TZXR0aW5nc0J1dHRvbkNsaWNrZWQoKX0pKCJvbkNsb3NlU2lkZXBhbmVCdXR0b25DbGlja2VkIixmdW5jdGlvbigpe3JldHVybiBpLm9uQ2xvc2VTaWRlcGFuZUJ1dHRvbkNsaWNrZWQoKX0pKCJvblBsdWdpblR5cGVUb2dnbGVkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblBsdWdpblZpc2liaWxpdHlUb2dnbGVkKG8pfSkoIm9uUGx1Z2luVHlwZUFsbFRvZ2dsZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25TaG93QWxsUGx1Z2lucygpfSksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIiksQig0LCJhc3luYyIpLEIoNSwiYXN5bmMiKSx2KCkpLDImZSYmeSgic2hvd0ZpbHRlcmVkVmlldyIsVSgxLDUsaS5zaG93RmlsdGVyZWRWaWV3JCkpKCJpc1NpZGVwYW5lT3BlbiIsVSgyLDcsaS5pc1NpZGVwYW5lT3BlbiQpKSgiaW5pdGlhbFRhZ3NMb2FkaW5nIixVKDMsOSxpLmluaXRpYWxUYWdzTG9hZGluZyQpKSgiZmlsdGVyZWRQbHVnaW5UeXBlcyIsVSg0LDExLGkuZmlsdGVyZWRQbHVnaW5UeXBlcyQpKSgic2xpZGVPdXRNZW51T3BlbiIsVSg1LDEzLGkuaXNTbGlkZW91dE1lbnVPcGVuJCkpfSxkZXBlbmRlbmNpZXM6W1VwZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksdmE9KCgpPT4oZnVuY3Rpb24obil7bi5DSEVDS0JPWD0iY2hlY2tib3giLG4uUlVOX05BTUU9InJ1bl9uYW1lIixuLkVYUEVSSU1FTlRfTkFNRT0iZXhwZXJpbWVudF9uYW1lIixuLlJVTl9DT0xPUj0icnVuX2NvbG9yIn0odmF8fCh2YT17fSkpLHZhKSkoKSxEZj17fTtCRShEZix7Z2V0RXhwZXJpbWVudHNIcGFyYW1zQW5kTWV0cmljc1NwZWNzOigpPT5HUWUsZ2V0SHBhcmFtRmlsdGVyTWFwOigpPT5VUWUsZ2V0TWV0cmljRmlsdGVyTWFwOigpPT5qUWV9KTt2YXIgQ0U9TXIoZkkpLEhRZT1KKENFLChuLHQpPT57bGV0IGU9W107Zm9yKGxldCBpIG9mIHQpIW4uc3BlY3NbaV18fGUucHVzaChuLnNwZWNzW2ldLmhwYXJhbS5kZWZhdWx0RmlsdGVycyk7cmV0dXJuIExNKGUpfSksVVFlPUooSFFlLENFLChuLHQsZSk9PntsZXQgaT1XbShlKTtyZXR1cm4gbmV3IE1hcChbLi4ubiwuLi50LmZpbHRlcnNbaV0/LmhwYXJhbXM/P1tdXSl9KSx6UWU9SihDRSwobix0KT0+e2xldCBlPVtdO2ZvcihsZXQgaSBvZiB0KSFuLnNwZWNzW2ldfHxlLnB1c2gobi5zcGVjc1tpXS5tZXRyaWMuZGVmYXVsdEZpbHRlcnMpO3JldHVybiBoSShlKX0pLGpRZT1KKHpRZSxDRSwobix0LGUpPT57bGV0IGk9V20oZSk7cmV0dXJuIG5ldyBNYXAoWy4uLm4sLi4udC5maWx0ZXJzW2ldPy5tZXRyaWNzPz9bXV0pfSksR1FlPUooQ0UsKG4sdCk9PmZ1bmN0aW9uKC4uLm4pe2xldCB0PW5ldyBNYXAsZT1uZXcgTWFwLGk9bmV3IE1hcCxyPW5ldyBNYXAsbz1bXTtmb3IobGV0IHMgb2Ygbil7Zm9yKGxldCBhIG9mIHMuaHBhcmFtcylpZihlLmhhcyhhLm5hbWUpfHxlLnNldChhLm5hbWUsbmV3IFNldCksZS5nZXQoYS5uYW1lKS5hZGQoYS5kaXNwbGF5TmFtZSksdC5oYXMoYS5uYW1lKSl7bGV0IGw9dC5nZXQoYS5uYW1lKSxjPWE7aWYobC50eXBlIT09Yy50eXBlJiZvLnB1c2goYEhwYXJhbSwgJHtjLm5hbWV9LCB0eXBlcyBoYXZlIHRvIG1hdGNoLiBHb3Q6ICR7bC50eXBlfSB2cy4gJHtjLnR5cGV9YCksbC5kb21haW4udHlwZT09PUNpLklOVEVSVkFMJiZjLmRvbWFpbi50eXBlPT09Q2kuSU5URVJWQUwpKGwuZG9tYWluLm1pblZhbHVlIT09Yy5kb21haW4ubWluVmFsdWV8fGwuZG9tYWluLm1heFZhbHVlIT09Yy5kb21haW4ubWF4VmFsdWUpJiZvLnB1c2goYEhwYXJhbSwgJHtjLm5hbWV9LCBkb21haW5zIGhhdmUgdG8gbWF0Y2guIEdvdDogJHtsLmRvbWFpbn0gdnMuICR7Yy5kb21haW59YCk7ZWxzZSBpZihsLmRvbWFpbi50eXBlPT09Q2kuRElTQ1JFVEUmJmMuZG9tYWluLnR5cGU9PT1DaS5ESVNDUkVURSl7bGV0IHU9bmV3IFNldChbLi4ubC5kb21haW4udmFsdWVzLC4uLmMuZG9tYWluLnZhbHVlc10pOyhsLmRvbWFpbi52YWx1ZXMubGVuZ3RoIT09Yy5kb21haW4udmFsdWVzLmxlbmd0aHx8bC5kb21haW4udmFsdWVzLmxlbmd0aCE9PXUuc2l6ZSkmJm8ucHVzaChgSHBhcmFtLCAke2MubmFtZX0sIGRvbWFpbnMgaGF2ZSB0byBtYXRjaC4gR290OiAke2wuZG9tYWlufSB2cy4gJHtjLmRvbWFpbn1gKX1lbHNlIG8ucHVzaChgSHBhcmFtLCAke2MubmFtZX0sIGRvbWFpbnMgaGF2ZSB0byBtYXRjaC4gR290OiAke2wuZG9tYWlufSB2cy4gJHtjLmRvbWFpbn1gKX1lbHNlIHQuc2V0KGEubmFtZSx7Li4uYX0pO2ZvcihsZXQgYSBvZiBzLm1ldHJpY3MpaWYoci5oYXMoYS50YWcpfHxyLnNldChhLnRhZyxuZXcgU2V0KSxyLmdldChhLnRhZykuYWRkKGEuZGlzcGxheU5hbWUpLGkuaGFzKGEudGFnKSl7bGV0IGw9aS5nZXQoYS50YWcpLGM9YTtsLmRhdGFzZXRUeXBlIT09Yy5kYXRhc2V0VHlwZSYmby5wdXNoKGBNZXRyaWMsICR7Yy50YWd9LCBkYXRhc2V0VHlwZXMgaGF2ZSB0byBtYXRjaC4gR290OiAke2wuZGF0YXNldFR5cGV9IHZzLiAke2MuZGF0YXNldFR5cGV9YCl9ZWxzZSBpLnNldChhLnRhZyx7Li4uYX0pfWlmKG8ubGVuZ3RoKXRocm93IG5ldyBFcnJvcihgVmFsaWRhdGlvbiBlcnJvcjpcbiR7by5qb2luKCJcbiIpfWApO3JldHVybntocGFyYW1zOlsuLi50XS5tYXAoKFtzLGFdKT0+KHsuLi5hLGRpc3BsYXlOYW1lOlsuLi5lLmdldChzKV0uam9pbigiIG9yICIpfSkpLG1ldHJpY3M6Wy4uLmldLm1hcCgoW3MsYV0pPT4oey4uLmEsZGlzcGxheU5hbWU6Wy4uLnIuZ2V0KHMpXS5qb2luKCIgb3IgIil9KSl9fSguLi50LmV4cGVyaW1lbnRJZHMubWFwKGU9PntsZXQgaT1uLnNwZWNzW2VdO3JldHVybiBpP3tocGFyYW1zOmkuaHBhcmFtLnNwZWNzLG1ldHJpY3M6aS5tZXRyaWMuc3BlY3N9Om51bGx9KS5maWx0ZXIoQm9vbGVhbikpKSxXUWU9WyJ0b29sdGlwIl0sR3BlPSJ0b29sdGlwLXBhbmVsIixXcGU9bGEoe3Bhc3NpdmU6ITB9KSxxcGU9bmV3IHBlKCJtYXQtdG9vbHRpcC1zY3JvbGwtc3RyYXRlZ3kiKSxRUWU9e3Byb3ZpZGU6cXBlLGRlcHM6W3RyXSx1c2VGYWN0b3J5OmZ1bmN0aW9uKG4pe3JldHVybigpPT5uLnNjcm9sbFN0cmF0ZWdpZXMucmVwb3NpdGlvbih7c2Nyb2xsVGhyb3R0bGU6MjB9KX19LEtRZT1uZXcgcGUoIm1hdC10b29sdGlwLWRlZmF1bHQtb3B0aW9ucyIse3Byb3ZpZGVkSW46InJvb3QiLGZhY3Rvcnk6ZnVuY3Rpb24oKXtyZXR1cm57c2hvd0RlbGF5OjAsaGlkZURlbGF5OjAsdG91Y2hlbmRIaWRlRGVsYXk6MTUwMH19fSksSlFlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgpe3RoaXMuX292ZXJsYXk9ZSx0aGlzLl9lbGVtZW50UmVmPWksdGhpcy5fc2Nyb2xsRGlzcGF0Y2hlcj1yLHRoaXMuX3ZpZXdDb250YWluZXJSZWY9byx0aGlzLl9uZ1pvbmU9cyx0aGlzLl9wbGF0Zm9ybT1hLHRoaXMuX2FyaWFEZXNjcmliZXI9bCx0aGlzLl9mb2N1c01vbml0b3I9Yyx0aGlzLl9kaXI9ZCx0aGlzLl9kZWZhdWx0T3B0aW9ucz1wLHRoaXMuX3Bvc2l0aW9uPSJiZWxvdyIsdGhpcy5fZGlzYWJsZWQ9ITEsdGhpcy5fdmlld0luaXRpYWxpemVkPSExLHRoaXMuX3BvaW50ZXJFeGl0RXZlbnRzSW5pdGlhbGl6ZWQ9ITEsdGhpcy5fdmlld3BvcnRNYXJnaW49OCx0aGlzLl9jc3NDbGFzc1ByZWZpeD0ibWF0Iix0aGlzLl9zaG93RGVsYXk9dGhpcy5fZGVmYXVsdE9wdGlvbnMuc2hvd0RlbGF5LHRoaXMuX2hpZGVEZWxheT10aGlzLl9kZWZhdWx0T3B0aW9ucy5oaWRlRGVsYXksdGhpcy50b3VjaEdlc3R1cmVzPSJhdXRvIix0aGlzLl9tZXNzYWdlPSIiLHRoaXMuX3Bhc3NpdmVMaXN0ZW5lcnM9W10sdGhpcy5fZGVzdHJveWVkPW5ldyBrZSx0aGlzLl9zY3JvbGxTdHJhdGVneT11LHRoaXMuX2RvY3VtZW50PWgscCYmKHAucG9zaXRpb24mJih0aGlzLnBvc2l0aW9uPXAucG9zaXRpb24pLHAudG91Y2hHZXN0dXJlcyYmKHRoaXMudG91Y2hHZXN0dXJlcz1wLnRvdWNoR2VzdHVyZXMpKSxkLmNoYW5nZS5waXBlKHN0KHRoaXMuX2Rlc3Ryb3llZCkpLnN1YnNjcmliZSgoKT0+e3RoaXMuX292ZXJsYXlSZWYmJnRoaXMuX3VwZGF0ZVBvc2l0aW9uKHRoaXMuX292ZXJsYXlSZWYpfSl9Z2V0IHBvc2l0aW9uKCl7cmV0dXJuIHRoaXMuX3Bvc2l0aW9ufXNldCBwb3NpdGlvbihlKXtlIT09dGhpcy5fcG9zaXRpb24mJih0aGlzLl9wb3NpdGlvbj1lLHRoaXMuX292ZXJsYXlSZWYmJih0aGlzLl91cGRhdGVQb3NpdGlvbih0aGlzLl9vdmVybGF5UmVmKSx0aGlzLl90b29sdGlwSW5zdGFuY2U/LnNob3coMCksdGhpcy5fb3ZlcmxheVJlZi51cGRhdGVQb3NpdGlvbigpKSl9Z2V0IGRpc2FibGVkKCl7cmV0dXJuIHRoaXMuX2Rpc2FibGVkfXNldCBkaXNhYmxlZChlKXt0aGlzLl9kaXNhYmxlZD1SdChlKSx0aGlzLl9kaXNhYmxlZD90aGlzLmhpZGUoMCk6dGhpcy5fc2V0dXBQb2ludGVyRW50ZXJFdmVudHNJZk5lZWRlZCgpfWdldCBzaG93RGVsYXkoKXtyZXR1cm4gdGhpcy5fc2hvd0RlbGF5fXNldCBzaG93RGVsYXkoZSl7dGhpcy5fc2hvd0RlbGF5PUJpKGUpfWdldCBoaWRlRGVsYXkoKXtyZXR1cm4gdGhpcy5faGlkZURlbGF5fXNldCBoaWRlRGVsYXkoZSl7dGhpcy5faGlkZURlbGF5PUJpKGUpLHRoaXMuX3Rvb2x0aXBJbnN0YW5jZSYmKHRoaXMuX3Rvb2x0aXBJbnN0YW5jZS5fbW91c2VMZWF2ZUhpZGVEZWxheT10aGlzLl9oaWRlRGVsYXkpfWdldCBtZXNzYWdlKCl7cmV0dXJuIHRoaXMuX21lc3NhZ2V9c2V0IG1lc3NhZ2UoZSl7dGhpcy5fYXJpYURlc2NyaWJlci5yZW1vdmVEZXNjcmlwdGlvbih0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQsdGhpcy5fbWVzc2FnZSwidG9vbHRpcCIpLHRoaXMuX21lc3NhZ2U9bnVsbCE9ZT9TdHJpbmcoZSkudHJpbSgpOiIiLCF0aGlzLl9tZXNzYWdlJiZ0aGlzLl9pc1Rvb2x0aXBWaXNpYmxlKCk/dGhpcy5oaWRlKDApOih0aGlzLl9zZXR1cFBvaW50ZXJFbnRlckV2ZW50c0lmTmVlZGVkKCksdGhpcy5fdXBkYXRlVG9vbHRpcE1lc3NhZ2UoKSx0aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntQcm9taXNlLnJlc29sdmUoKS50aGVuKCgpPT57dGhpcy5fYXJpYURlc2NyaWJlci5kZXNjcmliZSh0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQsdGhpcy5tZXNzYWdlLCJ0b29sdGlwIil9KX0pKX1nZXQgdG9vbHRpcENsYXNzKCl7cmV0dXJuIHRoaXMuX3Rvb2x0aXBDbGFzc31zZXQgdG9vbHRpcENsYXNzKGUpe3RoaXMuX3Rvb2x0aXBDbGFzcz1lLHRoaXMuX3Rvb2x0aXBJbnN0YW5jZSYmdGhpcy5fc2V0VG9vbHRpcENsYXNzKHRoaXMuX3Rvb2x0aXBDbGFzcyl9bmdBZnRlclZpZXdJbml0KCl7dGhpcy5fdmlld0luaXRpYWxpemVkPSEwLHRoaXMuX3NldHVwUG9pbnRlckVudGVyRXZlbnRzSWZOZWVkZWQoKSx0aGlzLl9mb2N1c01vbml0b3IubW9uaXRvcih0aGlzLl9lbGVtZW50UmVmKS5waXBlKHN0KHRoaXMuX2Rlc3Ryb3llZCkpLnN1YnNjcmliZShlPT57ZT8ia2V5Ym9hcmQiPT09ZSYmdGhpcy5fbmdab25lLnJ1bigoKT0+dGhpcy5zaG93KCkpOnRoaXMuX25nWm9uZS5ydW4oKCk9PnRoaXMuaGlkZSgwKSl9KX1uZ09uRGVzdHJveSgpe2xldCBlPXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudDtjbGVhclRpbWVvdXQodGhpcy5fdG91Y2hzdGFydFRpbWVvdXQpLHRoaXMuX292ZXJsYXlSZWYmJih0aGlzLl9vdmVybGF5UmVmLmRpc3Bvc2UoKSx0aGlzLl90b29sdGlwSW5zdGFuY2U9bnVsbCksdGhpcy5fcGFzc2l2ZUxpc3RlbmVycy5mb3JFYWNoKChbaSxyXSk9PntlLnJlbW92ZUV2ZW50TGlzdGVuZXIoaSxyLFdwZSl9KSx0aGlzLl9wYXNzaXZlTGlzdGVuZXJzLmxlbmd0aD0wLHRoaXMuX2Rlc3Ryb3llZC5uZXh0KCksdGhpcy5fZGVzdHJveWVkLmNvbXBsZXRlKCksdGhpcy5fYXJpYURlc2NyaWJlci5yZW1vdmVEZXNjcmlwdGlvbihlLHRoaXMubWVzc2FnZSwidG9vbHRpcCIpLHRoaXMuX2ZvY3VzTW9uaXRvci5zdG9wTW9uaXRvcmluZyhlKX1zaG93KGU9dGhpcy5zaG93RGVsYXkpe2lmKHRoaXMuZGlzYWJsZWR8fCF0aGlzLm1lc3NhZ2V8fHRoaXMuX2lzVG9vbHRpcFZpc2libGUoKSlyZXR1cm4gdm9pZCB0aGlzLl90b29sdGlwSW5zdGFuY2U/Ll9jYW5jZWxQZW5kaW5nQW5pbWF0aW9ucygpO2xldCBpPXRoaXMuX2NyZWF0ZU92ZXJsYXkoKTt0aGlzLl9kZXRhY2goKSx0aGlzLl9wb3J0YWw9dGhpcy5fcG9ydGFsfHxuZXcgJGModGhpcy5fdG9vbHRpcENvbXBvbmVudCx0aGlzLl92aWV3Q29udGFpbmVyUmVmKTtsZXQgcj10aGlzLl90b29sdGlwSW5zdGFuY2U9aS5hdHRhY2godGhpcy5fcG9ydGFsKS5pbnN0YW5jZTtyLl90cmlnZ2VyRWxlbWVudD10aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQsci5fbW91c2VMZWF2ZUhpZGVEZWxheT10aGlzLl9oaWRlRGVsYXksci5hZnRlckhpZGRlbigpLnBpcGUoc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKCgpPT50aGlzLl9kZXRhY2goKSksdGhpcy5fc2V0VG9vbHRpcENsYXNzKHRoaXMuX3Rvb2x0aXBDbGFzcyksdGhpcy5fdXBkYXRlVG9vbHRpcE1lc3NhZ2UoKSxyLnNob3coZSl9aGlkZShlPXRoaXMuaGlkZURlbGF5KXtsZXQgaT10aGlzLl90b29sdGlwSW5zdGFuY2U7aSYmKGkuaXNWaXNpYmxlKCk/aS5oaWRlKGUpOihpLl9jYW5jZWxQZW5kaW5nQW5pbWF0aW9ucygpLHRoaXMuX2RldGFjaCgpKSl9dG9nZ2xlKCl7dGhpcy5faXNUb29sdGlwVmlzaWJsZSgpP3RoaXMuaGlkZSgpOnRoaXMuc2hvdygpfV9pc1Rvb2x0aXBWaXNpYmxlKCl7cmV0dXJuISF0aGlzLl90b29sdGlwSW5zdGFuY2UmJnRoaXMuX3Rvb2x0aXBJbnN0YW5jZS5pc1Zpc2libGUoKX1fY3JlYXRlT3ZlcmxheSgpe2lmKHRoaXMuX292ZXJsYXlSZWYpcmV0dXJuIHRoaXMuX292ZXJsYXlSZWY7bGV0IGU9dGhpcy5fc2Nyb2xsRGlzcGF0Y2hlci5nZXRBbmNlc3RvclNjcm9sbENvbnRhaW5lcnModGhpcy5fZWxlbWVudFJlZiksaT10aGlzLl9vdmVybGF5LnBvc2l0aW9uKCkuZmxleGlibGVDb25uZWN0ZWRUbyh0aGlzLl9lbGVtZW50UmVmKS53aXRoVHJhbnNmb3JtT3JpZ2luT24oYC4ke3RoaXMuX2Nzc0NsYXNzUHJlZml4fS10b29sdGlwYCkud2l0aEZsZXhpYmxlRGltZW5zaW9ucyghMSkud2l0aFZpZXdwb3J0TWFyZ2luKHRoaXMuX3ZpZXdwb3J0TWFyZ2luKS53aXRoU2Nyb2xsYWJsZUNvbnRhaW5lcnMoZSk7cmV0dXJuIGkucG9zaXRpb25DaGFuZ2VzLnBpcGUoc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKHI9Pnt0aGlzLl91cGRhdGVDdXJyZW50UG9zaXRpb25DbGFzcyhyLmNvbm5lY3Rpb25QYWlyKSx0aGlzLl90b29sdGlwSW5zdGFuY2UmJnIuc2Nyb2xsYWJsZVZpZXdQcm9wZXJ0aWVzLmlzT3ZlcmxheUNsaXBwZWQmJnRoaXMuX3Rvb2x0aXBJbnN0YW5jZS5pc1Zpc2libGUoKSYmdGhpcy5fbmdab25lLnJ1bigoKT0+dGhpcy5oaWRlKDApKX0pLHRoaXMuX292ZXJsYXlSZWY9dGhpcy5fb3ZlcmxheS5jcmVhdGUoe2RpcmVjdGlvbjp0aGlzLl9kaXIscG9zaXRpb25TdHJhdGVneTppLHBhbmVsQ2xhc3M6YCR7dGhpcy5fY3NzQ2xhc3NQcmVmaXh9LSR7R3BlfWAsc2Nyb2xsU3RyYXRlZ3k6dGhpcy5fc2Nyb2xsU3RyYXRlZ3koKX0pLHRoaXMuX3VwZGF0ZVBvc2l0aW9uKHRoaXMuX292ZXJsYXlSZWYpLHRoaXMuX292ZXJsYXlSZWYuZGV0YWNobWVudHMoKS5waXBlKHN0KHRoaXMuX2Rlc3Ryb3llZCkpLnN1YnNjcmliZSgoKT0+dGhpcy5fZGV0YWNoKCkpLHRoaXMuX292ZXJsYXlSZWYub3V0c2lkZVBvaW50ZXJFdmVudHMoKS5waXBlKHN0KHRoaXMuX2Rlc3Ryb3llZCkpLnN1YnNjcmliZSgoKT0+dGhpcy5fdG9vbHRpcEluc3RhbmNlPy5faGFuZGxlQm9keUludGVyYWN0aW9uKCkpLHRoaXMuX292ZXJsYXlSZWYua2V5ZG93bkV2ZW50cygpLnBpcGUoc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKHI9Pnt0aGlzLl9pc1Rvb2x0aXBWaXNpYmxlKCkmJjI3PT09ci5rZXlDb2RlJiYha3IocikmJihyLnByZXZlbnREZWZhdWx0KCksci5zdG9wUHJvcGFnYXRpb24oKSx0aGlzLl9uZ1pvbmUucnVuKCgpPT50aGlzLmhpZGUoMCkpKX0pLHRoaXMuX2RlZmF1bHRPcHRpb25zPy5kaXNhYmxlVG9vbHRpcEludGVyYWN0aXZpdHkmJnRoaXMuX292ZXJsYXlSZWYuYWRkUGFuZWxDbGFzcyhgJHt0aGlzLl9jc3NDbGFzc1ByZWZpeH0tdG9vbHRpcC1wYW5lbC1ub24taW50ZXJhY3RpdmVgKSx0aGlzLl9vdmVybGF5UmVmfV9kZXRhY2goKXt0aGlzLl9vdmVybGF5UmVmJiZ0aGlzLl9vdmVybGF5UmVmLmhhc0F0dGFjaGVkKCkmJnRoaXMuX292ZXJsYXlSZWYuZGV0YWNoKCksdGhpcy5fdG9vbHRpcEluc3RhbmNlPW51bGx9X3VwZGF0ZVBvc2l0aW9uKGUpe2xldCBpPWUuZ2V0Q29uZmlnKCkucG9zaXRpb25TdHJhdGVneSxyPXRoaXMuX2dldE9yaWdpbigpLG89dGhpcy5fZ2V0T3ZlcmxheVBvc2l0aW9uKCk7aS53aXRoUG9zaXRpb25zKFt0aGlzLl9hZGRPZmZzZXQoey4uLnIubWFpbiwuLi5vLm1haW59KSx0aGlzLl9hZGRPZmZzZXQoey4uLnIuZmFsbGJhY2ssLi4uby5mYWxsYmFja30pXSl9X2FkZE9mZnNldChlKXtyZXR1cm4gZX1fZ2V0T3JpZ2luKCl7bGV0IHIsZT0hdGhpcy5fZGlyfHwibHRyIj09dGhpcy5fZGlyLnZhbHVlLGk9dGhpcy5wb3NpdGlvbjsiYWJvdmUiPT1pfHwiYmVsb3ciPT1pP3I9e29yaWdpblg6ImNlbnRlciIsb3JpZ2luWToiYWJvdmUiPT1pPyJ0b3AiOiJib3R0b20ifToiYmVmb3JlIj09aXx8ImxlZnQiPT1pJiZlfHwicmlnaHQiPT1pJiYhZT9yPXtvcmlnaW5YOiJzdGFydCIsb3JpZ2luWToiY2VudGVyIn06KCJhZnRlciI9PWl8fCJyaWdodCI9PWkmJmV8fCJsZWZ0Ij09aSYmIWUpJiYocj17b3JpZ2luWDoiZW5kIixvcmlnaW5ZOiJjZW50ZXIifSk7bGV0e3g6byx5OnN9PXRoaXMuX2ludmVydFBvc2l0aW9uKHIub3JpZ2luWCxyLm9yaWdpblkpO3JldHVybnttYWluOnIsZmFsbGJhY2s6e29yaWdpblg6byxvcmlnaW5ZOnN9fX1fZ2V0T3ZlcmxheVBvc2l0aW9uKCl7bGV0IHIsZT0hdGhpcy5fZGlyfHwibHRyIj09dGhpcy5fZGlyLnZhbHVlLGk9dGhpcy5wb3NpdGlvbjsiYWJvdmUiPT1pP3I9e292ZXJsYXlYOiJjZW50ZXIiLG92ZXJsYXlZOiJib3R0b20ifToiYmVsb3ciPT1pP3I9e292ZXJsYXlYOiJjZW50ZXIiLG92ZXJsYXlZOiJ0b3AifToiYmVmb3JlIj09aXx8ImxlZnQiPT1pJiZlfHwicmlnaHQiPT1pJiYhZT9yPXtvdmVybGF5WDoiZW5kIixvdmVybGF5WToiY2VudGVyIn06KCJhZnRlciI9PWl8fCJyaWdodCI9PWkmJmV8fCJsZWZ0Ij09aSYmIWUpJiYocj17b3ZlcmxheVg6InN0YXJ0IixvdmVybGF5WToiY2VudGVyIn0pO2xldHt4Om8seTpzfT10aGlzLl9pbnZlcnRQb3NpdGlvbihyLm92ZXJsYXlYLHIub3ZlcmxheVkpO3JldHVybnttYWluOnIsZmFsbGJhY2s6e292ZXJsYXlYOm8sb3ZlcmxheVk6c319fV91cGRhdGVUb29sdGlwTWVzc2FnZSgpe3RoaXMuX3Rvb2x0aXBJbnN0YW5jZSYmKHRoaXMuX3Rvb2x0aXBJbnN0YW5jZS5tZXNzYWdlPXRoaXMubWVzc2FnZSx0aGlzLl90b29sdGlwSW5zdGFuY2UuX21hcmtGb3JDaGVjaygpLHRoaXMuX25nWm9uZS5vbk1pY3JvdGFza0VtcHR5LnBpcGUoUXQoMSksc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5fdG9vbHRpcEluc3RhbmNlJiZ0aGlzLl9vdmVybGF5UmVmLnVwZGF0ZVBvc2l0aW9uKCl9KSl9X3NldFRvb2x0aXBDbGFzcyhlKXt0aGlzLl90b29sdGlwSW5zdGFuY2UmJih0aGlzLl90b29sdGlwSW5zdGFuY2UudG9vbHRpcENsYXNzPWUsdGhpcy5fdG9vbHRpcEluc3RhbmNlLl9tYXJrRm9yQ2hlY2soKSl9X2ludmVydFBvc2l0aW9uKGUsaSl7cmV0dXJuImFib3ZlIj09PXRoaXMucG9zaXRpb258fCJiZWxvdyI9PT10aGlzLnBvc2l0aW9uPyJ0b3AiPT09aT9pPSJib3R0b20iOiJib3R0b20iPT09aSYmKGk9InRvcCIpOiJlbmQiPT09ZT9lPSJzdGFydCI6InN0YXJ0Ij09PWUmJihlPSJlbmQiKSx7eDplLHk6aX19X3VwZGF0ZUN1cnJlbnRQb3NpdGlvbkNsYXNzKGUpe2xldCBzLHtvdmVybGF5WTppLG9yaWdpblg6cixvcmlnaW5ZOm99PWU7aWYocz0iY2VudGVyIj09PWk/dGhpcy5fZGlyJiYicnRsIj09PXRoaXMuX2Rpci52YWx1ZT8iZW5kIj09PXI/ImxlZnQiOiJyaWdodCI6InN0YXJ0Ij09PXI/ImxlZnQiOiJyaWdodCI6ImJvdHRvbSI9PT1pJiYidG9wIj09PW8/ImFib3ZlIjoiYmVsb3ciLHMhPT10aGlzLl9jdXJyZW50UG9zaXRpb24pe2xldCBhPXRoaXMuX292ZXJsYXlSZWY7aWYoYSl7bGV0IGw9YCR7dGhpcy5fY3NzQ2xhc3NQcmVmaXh9LSR7R3BlfS1gO2EucmVtb3ZlUGFuZWxDbGFzcyhsK3RoaXMuX2N1cnJlbnRQb3NpdGlvbiksYS5hZGRQYW5lbENsYXNzKGwrcyl9dGhpcy5fY3VycmVudFBvc2l0aW9uPXN9fV9zZXR1cFBvaW50ZXJFbnRlckV2ZW50c0lmTmVlZGVkKCl7dGhpcy5fZGlzYWJsZWR8fCF0aGlzLm1lc3NhZ2V8fCF0aGlzLl92aWV3SW5pdGlhbGl6ZWR8fHRoaXMuX3Bhc3NpdmVMaXN0ZW5lcnMubGVuZ3RofHwodGhpcy5fcGxhdGZvcm1TdXBwb3J0c01vdXNlRXZlbnRzKCk/dGhpcy5fcGFzc2l2ZUxpc3RlbmVycy5wdXNoKFsibW91c2VlbnRlciIsKCk9Pnt0aGlzLl9zZXR1cFBvaW50ZXJFeGl0RXZlbnRzSWZOZWVkZWQoKSx0aGlzLnNob3coKX1dKToib2ZmIiE9PXRoaXMudG91Y2hHZXN0dXJlcyYmKHRoaXMuX2Rpc2FibGVOYXRpdmVHZXN0dXJlc0lmTmVjZXNzYXJ5KCksdGhpcy5fcGFzc2l2ZUxpc3RlbmVycy5wdXNoKFsidG91Y2hzdGFydCIsKCk9Pnt0aGlzLl9zZXR1cFBvaW50ZXJFeGl0RXZlbnRzSWZOZWVkZWQoKSxjbGVhclRpbWVvdXQodGhpcy5fdG91Y2hzdGFydFRpbWVvdXQpLHRoaXMuX3RvdWNoc3RhcnRUaW1lb3V0PXNldFRpbWVvdXQoKCk9PnRoaXMuc2hvdygpLDUwMCl9XSkpLHRoaXMuX2FkZExpc3RlbmVycyh0aGlzLl9wYXNzaXZlTGlzdGVuZXJzKSl9X3NldHVwUG9pbnRlckV4aXRFdmVudHNJZk5lZWRlZCgpe2lmKHRoaXMuX3BvaW50ZXJFeGl0RXZlbnRzSW5pdGlhbGl6ZWQpcmV0dXJuO3RoaXMuX3BvaW50ZXJFeGl0RXZlbnRzSW5pdGlhbGl6ZWQ9ITA7bGV0IGU9W107aWYodGhpcy5fcGxhdGZvcm1TdXBwb3J0c01vdXNlRXZlbnRzKCkpZS5wdXNoKFsibW91c2VsZWF2ZSIsaT0+e2xldCByPWkucmVsYXRlZFRhcmdldDsoIXJ8fCF0aGlzLl9vdmVybGF5UmVmPy5vdmVybGF5RWxlbWVudC5jb250YWlucyhyKSkmJnRoaXMuaGlkZSgpfV0sWyJ3aGVlbCIsaT0+dGhpcy5fd2hlZWxMaXN0ZW5lcihpKV0pO2Vsc2UgaWYoIm9mZiIhPT10aGlzLnRvdWNoR2VzdHVyZXMpe3RoaXMuX2Rpc2FibGVOYXRpdmVHZXN0dXJlc0lmTmVjZXNzYXJ5KCk7bGV0IGk9KCk9PntjbGVhclRpbWVvdXQodGhpcy5fdG91Y2hzdGFydFRpbWVvdXQpLHRoaXMuaGlkZSh0aGlzLl9kZWZhdWx0T3B0aW9ucy50b3VjaGVuZEhpZGVEZWxheSl9O2UucHVzaChbInRvdWNoZW5kIixpXSxbInRvdWNoY2FuY2VsIixpXSl9dGhpcy5fYWRkTGlzdGVuZXJzKGUpLHRoaXMuX3Bhc3NpdmVMaXN0ZW5lcnMucHVzaCguLi5lKX1fYWRkTGlzdGVuZXJzKGUpe2UuZm9yRWFjaCgoW2kscl0pPT57dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoaSxyLFdwZSl9KX1fcGxhdGZvcm1TdXBwb3J0c01vdXNlRXZlbnRzKCl7cmV0dXJuIXRoaXMuX3BsYXRmb3JtLklPUyYmIXRoaXMuX3BsYXRmb3JtLkFORFJPSUR9X3doZWVsTGlzdGVuZXIoZSl7aWYodGhpcy5faXNUb29sdGlwVmlzaWJsZSgpKXtsZXQgaT10aGlzLl9kb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KGUuY2xpZW50WCxlLmNsaWVudFkpLHI9dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50O2khPT1yJiYhci5jb250YWlucyhpKSYmdGhpcy5oaWRlKCl9fV9kaXNhYmxlTmF0aXZlR2VzdHVyZXNJZk5lY2Vzc2FyeSgpe2xldCBlPXRoaXMudG91Y2hHZXN0dXJlcztpZigib2ZmIiE9PWUpe2xldCBpPXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudCxyPWkuc3R5bGU7KCJvbiI9PT1lfHwiSU5QVVQiIT09aS5ub2RlTmFtZSYmIlRFWFRBUkVBIiE9PWkubm9kZU5hbWUpJiYoci51c2VyU2VsZWN0PXIubXNVc2VyU2VsZWN0PXIud2Via2l0VXNlclNlbGVjdD1yLk1velVzZXJTZWxlY3Q9Im5vbmUiKSwoIm9uIj09PWV8fCFpLmRyYWdnYWJsZSkmJihyLndlYmtpdFVzZXJEcmFnPSJub25lIiksci50b3VjaEFjdGlvbj0ibm9uZSIsci53ZWJraXRUYXBIaWdobGlnaHRDb2xvcj0idHJhbnNwYXJlbnQifX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe25sKCl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4saW5wdXRzOntwb3NpdGlvbjpbIm1hdFRvb2x0aXBQb3NpdGlvbiIsInBvc2l0aW9uIl0sZGlzYWJsZWQ6WyJtYXRUb29sdGlwRGlzYWJsZWQiLCJkaXNhYmxlZCJdLHNob3dEZWxheTpbIm1hdFRvb2x0aXBTaG93RGVsYXkiLCJzaG93RGVsYXkiXSxoaWRlRGVsYXk6WyJtYXRUb29sdGlwSGlkZURlbGF5IiwiaGlkZURlbGF5Il0sdG91Y2hHZXN0dXJlczpbIm1hdFRvb2x0aXBUb3VjaEdlc3R1cmVzIiwidG91Y2hHZXN0dXJlcyJdLG1lc3NhZ2U6WyJtYXRUb29sdGlwIiwibWVzc2FnZSJdLHRvb2x0aXBDbGFzczpbIm1hdFRvb2x0aXBDbGFzcyIsInRvb2x0aXBDbGFzcyJdfX0pLG59KSgpLFhrPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBKUWV7Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgpe3N1cGVyKGUsaSxyLG8scyxhLGwsYyx1LGQscCxoKSx0aGlzLl90b29sdGlwQ29tcG9uZW50PWVLZX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTSh0ciksTShSZSksTSgkbSksTShPaSksTShfdCksTShvaSksTShmMiksTShGciksTShxcGUpLE0oJGksOCksTShLUWUsOCksTShIdCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJtYXRUb29sdGlwIiwiIl1dLGhvc3RBdHRyczpbMSwibWF0LXRvb2x0aXAtdHJpZ2dlciJdLGV4cG9ydEFzOlsibWF0VG9vbHRpcCJdLGZlYXR1cmVzOlt0dF19KSxufSkoKSwkUWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMuX2NoYW5nZURldGVjdG9yUmVmPWUsdGhpcy5fY2xvc2VPbkludGVyYWN0aW9uPSExLHRoaXMuX2lzVmlzaWJsZT0hMSx0aGlzLl9vbkhpZGU9bmV3IGtlLHRoaXMuX2FuaW1hdGlvbnNEaXNhYmxlZD0iTm9vcEFuaW1hdGlvbnMiPT09aX1zaG93KGUpe2NsZWFyVGltZW91dCh0aGlzLl9oaWRlVGltZW91dElkKSx0aGlzLl9zaG93VGltZW91dElkPXNldFRpbWVvdXQoKCk9Pnt0aGlzLl90b2dnbGVWaXNpYmlsaXR5KCEwKSx0aGlzLl9zaG93VGltZW91dElkPXZvaWQgMH0sZSl9aGlkZShlKXtjbGVhclRpbWVvdXQodGhpcy5fc2hvd1RpbWVvdXRJZCksdGhpcy5faGlkZVRpbWVvdXRJZD1zZXRUaW1lb3V0KCgpPT57dGhpcy5fdG9nZ2xlVmlzaWJpbGl0eSghMSksdGhpcy5faGlkZVRpbWVvdXRJZD12b2lkIDB9LGUpfWFmdGVySGlkZGVuKCl7cmV0dXJuIHRoaXMuX29uSGlkZX1pc1Zpc2libGUoKXtyZXR1cm4gdGhpcy5faXNWaXNpYmxlfW5nT25EZXN0cm95KCl7dGhpcy5fY2FuY2VsUGVuZGluZ0FuaW1hdGlvbnMoKSx0aGlzLl9vbkhpZGUuY29tcGxldGUoKSx0aGlzLl90cmlnZ2VyRWxlbWVudD1udWxsfV9oYW5kbGVCb2R5SW50ZXJhY3Rpb24oKXt0aGlzLl9jbG9zZU9uSW50ZXJhY3Rpb24mJnRoaXMuaGlkZSgwKX1fbWFya0ZvckNoZWNrKCl7dGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCl9X2hhbmRsZU1vdXNlTGVhdmUoe3JlbGF0ZWRUYXJnZXQ6ZX0peyghZXx8IXRoaXMuX3RyaWdnZXJFbGVtZW50LmNvbnRhaW5zKGUpKSYmKHRoaXMuaXNWaXNpYmxlKCk/dGhpcy5oaWRlKHRoaXMuX21vdXNlTGVhdmVIaWRlRGVsYXkpOnRoaXMuX2ZpbmFsaXplQW5pbWF0aW9uKCExKSl9X29uU2hvdygpe31faGFuZGxlQW5pbWF0aW9uRW5kKHthbmltYXRpb25OYW1lOmV9KXsoZT09PXRoaXMuX3Nob3dBbmltYXRpb258fGU9PT10aGlzLl9oaWRlQW5pbWF0aW9uKSYmdGhpcy5fZmluYWxpemVBbmltYXRpb24oZT09PXRoaXMuX3Nob3dBbmltYXRpb24pfV9jYW5jZWxQZW5kaW5nQW5pbWF0aW9ucygpe2NsZWFyVGltZW91dCh0aGlzLl9zaG93VGltZW91dElkKSxjbGVhclRpbWVvdXQodGhpcy5faGlkZVRpbWVvdXRJZCksdGhpcy5fc2hvd1RpbWVvdXRJZD10aGlzLl9oaWRlVGltZW91dElkPXZvaWQgMH1fZmluYWxpemVBbmltYXRpb24oZSl7ZT90aGlzLl9jbG9zZU9uSW50ZXJhY3Rpb249ITA6dGhpcy5pc1Zpc2libGUoKXx8dGhpcy5fb25IaWRlLm5leHQoKX1fdG9nZ2xlVmlzaWJpbGl0eShlKXtsZXQgaT10aGlzLl90b29sdGlwLm5hdGl2ZUVsZW1lbnQscj10aGlzLl9zaG93QW5pbWF0aW9uLG89dGhpcy5faGlkZUFuaW1hdGlvbjtpZihpLmNsYXNzTGlzdC5yZW1vdmUoZT9vOnIpLGkuY2xhc3NMaXN0LmFkZChlP3I6byksdGhpcy5faXNWaXNpYmxlPWUsZSYmIXRoaXMuX2FuaW1hdGlvbnNEaXNhYmxlZCYmImZ1bmN0aW9uIj09dHlwZW9mIGdldENvbXB1dGVkU3R5bGUpe2xldCBzPWdldENvbXB1dGVkU3R5bGUoaSk7KCIwcyI9PT1zLmdldFByb3BlcnR5VmFsdWUoImFuaW1hdGlvbi1kdXJhdGlvbiIpfHwibm9uZSI9PT1zLmdldFByb3BlcnR5VmFsdWUoImFuaW1hdGlvbi1uYW1lIikpJiYodGhpcy5fYW5pbWF0aW9uc0Rpc2FibGVkPSEwKX1lJiZ0aGlzLl9vblNob3coKSx0aGlzLl9hbmltYXRpb25zRGlzYWJsZWQmJihpLmNsYXNzTGlzdC5hZGQoIl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIiksdGhpcy5fZmluYWxpemVBbmltYXRpb24oZSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKG5uKSxNKFBpLDgpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bn0pLG59KSgpLGVLZT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgJFFle2NvbnN0cnVjdG9yKGUsaSxyKXtzdXBlcihlLHIpLHRoaXMuX2JyZWFrcG9pbnRPYnNlcnZlcj1pLHRoaXMuX2lzSGFuZHNldD10aGlzLl9icmVha3BvaW50T2JzZXJ2ZXIub2JzZXJ2ZSgiKG1heC13aWR0aDogNTk5Ljk4cHgpIGFuZCAob3JpZW50YXRpb246IHBvcnRyYWl0KSwgKG1heC13aWR0aDogOTU5Ljk4cHgpIGFuZCAob3JpZW50YXRpb246IGxhbmRzY2FwZSkiKSx0aGlzLl9zaG93QW5pbWF0aW9uPSJtYXQtdG9vbHRpcC1zaG93Iix0aGlzLl9oaWRlQW5pbWF0aW9uPSJtYXQtdG9vbHRpcC1oaWRlIn19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShubiksTShKbSksTShQaSw4KSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LXRvb2x0aXAtY29tcG9uZW50Il1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmb3QoV1FlLDcpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkuX3Rvb2x0aXA9ci5maXJzdCl9fSxob3N0QXR0cnM6WyJhcmlhLWhpZGRlbiIsInRydWUiXSxob3N0VmFyczoyLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgibW91c2VsZWF2ZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX2hhbmRsZU1vdXNlTGVhdmUobyl9KSwyJmUmJlB0KCJ6b29tIixpLmlzVmlzaWJsZSgpPzE6bnVsbCl9LGZlYXR1cmVzOlt0dF0sZGVjbHM6NCx2YXJzOjYsY29uc3RzOltbMSwibWF0LXRvb2x0aXAiLDMsIm5nQ2xhc3MiLCJhbmltYXRpb25lbmQiXSxbInRvb2x0aXAiLCIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJihfKDAsImRpdiIsMCwxKSxQKCJhbmltYXRpb25lbmQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLl9oYW5kbGVBbmltYXRpb25FbmQobyl9KSxCKDIsImFzeW5jIiksQSgzKSx2KCkpLDImZSl7bGV0IHI7ZXQoIm1hdC10b29sdGlwLWhhbmRzZXQiLG51bGw9PShyPVUoMiw0LGkuX2lzSGFuZHNldCkpP251bGw6ci5tYXRjaGVzKSx5KCJuZ0NsYXNzIixpLnRvb2x0aXBDbGFzcyksQygzKSx5dChpLm1lc3NhZ2UpfX0sZGVwZW5kZW5jaWVzOltGbixHZV0sc3R5bGVzOlsiLm1hdC10b29sdGlwe2NvbG9yOiNmZmY7Ym9yZGVyLXJhZGl1czo0cHg7bWFyZ2luOjE0cHg7bWF4LXdpZHRoOjI1MHB4O3BhZGRpbmctbGVmdDo4cHg7cGFkZGluZy1yaWdodDo4cHg7b3ZlcmZsb3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXM7dHJhbnNmb3JtOnNjYWxlKDApfS5tYXQtdG9vbHRpcC5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZXthbmltYXRpb246bm9uZTt0cmFuc2Zvcm06c2NhbGUoMSl9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LXRvb2x0aXB7b3V0bGluZTpzb2xpZCAxcHh9Lm1hdC10b29sdGlwLWhhbmRzZXR7bWFyZ2luOjI0cHg7cGFkZGluZy1sZWZ0OjE2cHg7cGFkZGluZy1yaWdodDoxNnB4fS5tYXQtdG9vbHRpcC1wYW5lbC1ub24taW50ZXJhY3RpdmV7cG9pbnRlci1ldmVudHM6bm9uZX1Aa2V5ZnJhbWVzIG1hdC10b29sdGlwLXNob3d7MCV7b3BhY2l0eTowO3RyYW5zZm9ybTpzY2FsZSgwKX01MCV7b3BhY2l0eTouNTt0cmFuc2Zvcm06c2NhbGUoMC45OSl9MTAwJXtvcGFjaXR5OjE7dHJhbnNmb3JtOnNjYWxlKDEpfX1Aa2V5ZnJhbWVzIG1hdC10b29sdGlwLWhpZGV7MCV7b3BhY2l0eToxO3RyYW5zZm9ybTpzY2FsZSgxKX0xMDAle29wYWNpdHk6MDt0cmFuc2Zvcm06c2NhbGUoMSl9fS5tYXQtdG9vbHRpcC1zaG93e2FuaW1hdGlvbjptYXQtdG9vbHRpcC1zaG93IDIwMG1zIGN1YmljLWJlemllcigwLCAwLCAwLjIsIDEpIGZvcndhcmRzfS5tYXQtdG9vbHRpcC1oaWRle2FuaW1hdGlvbjptYXQtdG9vbHRpcC1oaWRlIDEwMG1zIGN1YmljLWJlemllcigwLCAwLCAwLjIsIDEpIGZvcndhcmRzfSJdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLFFrPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W1FRZV0saW1wb3J0czpbRXYsTWUsc3MsbG4sbG4sdWRdfSksbn0pKCk7ZnVuY3Rpb24gdEtlKG4sdCl7aWYoMSZuJiYoXygwLCJtYXQtb3B0aW9uIiwxOSksQSgxKSx2KCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQ7eSgidmFsdWUiLGUpLEMoMSksamUoIiAiLGUsIiAiKX19ZnVuY3Rpb24gbktlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwibWF0LWZvcm0tZmllbGQiLDE2KSgxLCJtYXQtc2VsZWN0IiwxNyksUCgic2VsZWN0aW9uQ2hhbmdlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygyKS5fY2hhbmdlUGFnZVNpemUoci52YWx1ZSkpfSksRSgyLHRLZSwyLDIsIm1hdC1vcHRpb24iLDE4KSx2KCkoKX1pZigyJm4pe2xldCBlPVMoMik7eSgiYXBwZWFyYW5jZSIsZS5fZm9ybUZpZWxkQXBwZWFyYW5jZSkoImNvbG9yIixlLmNvbG9yKSxDKDEpLHkoInZhbHVlIixlLnBhZ2VTaXplKSgiZGlzYWJsZWQiLGUuZGlzYWJsZWQpKCJwYW5lbENsYXNzIixlLnNlbGVjdENvbmZpZy5wYW5lbENsYXNzfHwiIikoImRpc2FibGVPcHRpb25DZW50ZXJpbmciLGUuc2VsZWN0Q29uZmlnLmRpc2FibGVPcHRpb25DZW50ZXJpbmcpKCJhcmlhLWxhYmVsIixlLl9pbnRsLml0ZW1zUGVyUGFnZUxhYmVsKSxDKDEpLHkoIm5nRm9yT2YiLGUuX2Rpc3BsYXllZFBhZ2VTaXplT3B0aW9ucyl9fWZ1bmN0aW9uIGlLZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiwyMCksQSgxKSx2KCkpLDImbil7bGV0IGU9UygyKTtDKDEpLHl0KGUucGFnZVNpemUpfX1mdW5jdGlvbiByS2Uobix0KXtpZigxJm4mJihfKDAsImRpdiIsMTIpKDEsImRpdiIsMTMpLEEoMiksdigpLEUoMyxuS2UsMyw4LCJtYXQtZm9ybS1maWVsZCIsMTQpLEUoNCxpS2UsMiwxLCJkaXYiLDE1KSx2KCkpLDImbil7bGV0IGU9UygpO0MoMiksamUoIiAiLGUuX2ludGwuaXRlbXNQZXJQYWdlTGFiZWwsIiAiKSxDKDEpLHkoIm5nSWYiLGUuX2Rpc3BsYXllZFBhZ2VTaXplT3B0aW9ucy5sZW5ndGg+MSksQygxKSx5KCJuZ0lmIixlLl9kaXNwbGF5ZWRQYWdlU2l6ZU9wdGlvbnMubGVuZ3RoPD0xKX19ZnVuY3Rpb24gb0tlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiYnV0dG9uIiwyMSksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5maXJzdFBhZ2UoKSl9KSxJbigpLF8oMSwic3ZnIiw3KSxPKDIsInBhdGgiLDIyKSx2KCkoKX1pZigyJm4pe2xldCBlPVMoKTt5KCJtYXRUb29sdGlwIixlLl9pbnRsLmZpcnN0UGFnZUxhYmVsKSgibWF0VG9vbHRpcERpc2FibGVkIixlLl9wcmV2aW91c0J1dHRvbnNEaXNhYmxlZCgpKSgibWF0VG9vbHRpcFBvc2l0aW9uIiwiYWJvdmUiKSgiZGlzYWJsZWQiLGUuX3ByZXZpb3VzQnV0dG9uc0Rpc2FibGVkKCkpLHplKCJhcmlhLWxhYmVsIixlLl9pbnRsLmZpcnN0UGFnZUxhYmVsKX19ZnVuY3Rpb24gc0tlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO0luKCksSnMoKSxfKDAsImJ1dHRvbiIsMjMpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkubGFzdFBhZ2UoKSl9KSxJbigpLF8oMSwic3ZnIiw3KSxPKDIsInBhdGgiLDI0KSx2KCkoKX1pZigyJm4pe2xldCBlPVMoKTt5KCJtYXRUb29sdGlwIixlLl9pbnRsLmxhc3RQYWdlTGFiZWwpKCJtYXRUb29sdGlwRGlzYWJsZWQiLGUuX25leHRCdXR0b25zRGlzYWJsZWQoKSkoIm1hdFRvb2x0aXBQb3NpdGlvbiIsImFib3ZlIikoImRpc2FibGVkIixlLl9uZXh0QnV0dG9uc0Rpc2FibGVkKCkpLHplKCJhcmlhLWxhYmVsIixlLl9pbnRsLmxhc3RQYWdlTGFiZWwpfX1Lcigic3RhdGUiLFtraSgiaW5pdGlhbCwgdm9pZCwgaGlkZGVuIixnbih7b3BhY2l0eTowLHRyYW5zZm9ybToic2NhbGUoMCkifSkpLGtpKCJ2aXNpYmxlIixnbih7dHJhbnNmb3JtOiJzY2FsZSgxKSJ9KSksTGkoIiogPT4gdmlzaWJsZSIsamkoIjIwMG1zIGN1YmljLWJlemllcigwLCAwLCAwLjIsIDEpIixEbShbZ24oe29wYWNpdHk6MCx0cmFuc2Zvcm06InNjYWxlKDApIixvZmZzZXQ6MH0pLGduKHtvcGFjaXR5Oi41LHRyYW5zZm9ybToic2NhbGUoMC45OSkiLG9mZnNldDouNX0pLGduKHtvcGFjaXR5OjEsdHJhbnNmb3JtOiJzY2FsZSgxKSIsb2Zmc2V0OjF9KV0pKSksTGkoIiogPT4gaGlkZGVuIixqaSgiMTAwbXMgY3ViaWMtYmV6aWVyKDAsIDAsIDAuMiwgMSkiLGduKHtvcGFjaXR5OjB9KSkpXSk7dmFyIGUwPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLmNoYW5nZXM9bmV3IGtlLHRoaXMuaXRlbXNQZXJQYWdlTGFiZWw9Ikl0ZW1zIHBlciBwYWdlOiIsdGhpcy5uZXh0UGFnZUxhYmVsPSJOZXh0IHBhZ2UiLHRoaXMucHJldmlvdXNQYWdlTGFiZWw9IlByZXZpb3VzIHBhZ2UiLHRoaXMuZmlyc3RQYWdlTGFiZWw9IkZpcnN0IHBhZ2UiLHRoaXMubGFzdFBhZ2VMYWJlbD0iTGFzdCBwYWdlIix0aGlzLmdldFJhbmdlTGFiZWw9KGUsaSxyKT0+e2lmKDA9PXJ8fDA9PWkpcmV0dXJuYDAgb2YgJHtyfWA7bGV0IG89ZSppO3JldHVybmAke28rMX0gXHUyMDEzICR7bzwocj1NYXRoLm1heChyLDApKT9NYXRoLm1pbihvK2kscik6bytpfSBvZiAke3J9YH19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpLGxLZT17cHJvdmlkZTplMCxkZXBzOltbbmV3IG5zLG5ldyB0bCxlMF1dLHVzZUZhY3Rvcnk6ZnVuY3Rpb24obil7cmV0dXJuIG58fG5ldyBlMH19LHVLZT1uZXcgcGUoIk1BVF9QQUdJTkFUT1JfREVGQVVMVF9PUFRJT05TIiksZEtlPXNvKG0yKGNsYXNze30pKSxwS2U9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGRLZXtjb25zdHJ1Y3RvcihlLGkscil7aWYoc3VwZXIoKSx0aGlzLl9pbnRsPWUsdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWY9aSx0aGlzLl9wYWdlSW5kZXg9MCx0aGlzLl9sZW5ndGg9MCx0aGlzLl9wYWdlU2l6ZU9wdGlvbnM9W10sdGhpcy5faGlkZVBhZ2VTaXplPSExLHRoaXMuX3Nob3dGaXJzdExhc3RCdXR0b25zPSExLHRoaXMuc2VsZWN0Q29uZmlnPXt9LHRoaXMucGFnZT1uZXcgRyx0aGlzLl9pbnRsQ2hhbmdlcz1lLmNoYW5nZXMuc3Vic2NyaWJlKCgpPT50aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSkscil7bGV0e3BhZ2VTaXplOm8scGFnZVNpemVPcHRpb25zOnMsaGlkZVBhZ2VTaXplOmEsc2hvd0ZpcnN0TGFzdEJ1dHRvbnM6bH09cjtudWxsIT1vJiYodGhpcy5fcGFnZVNpemU9byksbnVsbCE9cyYmKHRoaXMuX3BhZ2VTaXplT3B0aW9ucz1zKSxudWxsIT1hJiYodGhpcy5faGlkZVBhZ2VTaXplPWEpLG51bGwhPWwmJih0aGlzLl9zaG93Rmlyc3RMYXN0QnV0dG9ucz1sKX19Z2V0IHBhZ2VJbmRleCgpe3JldHVybiB0aGlzLl9wYWdlSW5kZXh9c2V0IHBhZ2VJbmRleChlKXt0aGlzLl9wYWdlSW5kZXg9TWF0aC5tYXgoQmkoZSksMCksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCl9Z2V0IGxlbmd0aCgpe3JldHVybiB0aGlzLl9sZW5ndGh9c2V0IGxlbmd0aChlKXt0aGlzLl9sZW5ndGg9QmkoZSksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCl9Z2V0IHBhZ2VTaXplKCl7cmV0dXJuIHRoaXMuX3BhZ2VTaXplfXNldCBwYWdlU2l6ZShlKXt0aGlzLl9wYWdlU2l6ZT1NYXRoLm1heChCaShlKSwwKSx0aGlzLl91cGRhdGVEaXNwbGF5ZWRQYWdlU2l6ZU9wdGlvbnMoKX1nZXQgcGFnZVNpemVPcHRpb25zKCl7cmV0dXJuIHRoaXMuX3BhZ2VTaXplT3B0aW9uc31zZXQgcGFnZVNpemVPcHRpb25zKGUpe3RoaXMuX3BhZ2VTaXplT3B0aW9ucz0oZXx8W10pLm1hcChpPT5CaShpKSksdGhpcy5fdXBkYXRlRGlzcGxheWVkUGFnZVNpemVPcHRpb25zKCl9Z2V0IGhpZGVQYWdlU2l6ZSgpe3JldHVybiB0aGlzLl9oaWRlUGFnZVNpemV9c2V0IGhpZGVQYWdlU2l6ZShlKXt0aGlzLl9oaWRlUGFnZVNpemU9UnQoZSl9Z2V0IHNob3dGaXJzdExhc3RCdXR0b25zKCl7cmV0dXJuIHRoaXMuX3Nob3dGaXJzdExhc3RCdXR0b25zfXNldCBzaG93Rmlyc3RMYXN0QnV0dG9ucyhlKXt0aGlzLl9zaG93Rmlyc3RMYXN0QnV0dG9ucz1SdChlKX1uZ09uSW5pdCgpe3RoaXMuX2luaXRpYWxpemVkPSEwLHRoaXMuX3VwZGF0ZURpc3BsYXllZFBhZ2VTaXplT3B0aW9ucygpLHRoaXMuX21hcmtJbml0aWFsaXplZCgpfW5nT25EZXN0cm95KCl7dGhpcy5faW50bENoYW5nZXMudW5zdWJzY3JpYmUoKX1uZXh0UGFnZSgpe2lmKCF0aGlzLmhhc05leHRQYWdlKCkpcmV0dXJuO2xldCBlPXRoaXMucGFnZUluZGV4O3RoaXMucGFnZUluZGV4PXRoaXMucGFnZUluZGV4KzEsdGhpcy5fZW1pdFBhZ2VFdmVudChlKX1wcmV2aW91c1BhZ2UoKXtpZighdGhpcy5oYXNQcmV2aW91c1BhZ2UoKSlyZXR1cm47bGV0IGU9dGhpcy5wYWdlSW5kZXg7dGhpcy5wYWdlSW5kZXg9dGhpcy5wYWdlSW5kZXgtMSx0aGlzLl9lbWl0UGFnZUV2ZW50KGUpfWZpcnN0UGFnZSgpe2lmKCF0aGlzLmhhc1ByZXZpb3VzUGFnZSgpKXJldHVybjtsZXQgZT10aGlzLnBhZ2VJbmRleDt0aGlzLnBhZ2VJbmRleD0wLHRoaXMuX2VtaXRQYWdlRXZlbnQoZSl9bGFzdFBhZ2UoKXtpZighdGhpcy5oYXNOZXh0UGFnZSgpKXJldHVybjtsZXQgZT10aGlzLnBhZ2VJbmRleDt0aGlzLnBhZ2VJbmRleD10aGlzLmdldE51bWJlck9mUGFnZXMoKS0xLHRoaXMuX2VtaXRQYWdlRXZlbnQoZSl9aGFzUHJldmlvdXNQYWdlKCl7cmV0dXJuIHRoaXMucGFnZUluZGV4Pj0xJiYwIT10aGlzLnBhZ2VTaXplfWhhc05leHRQYWdlKCl7bGV0IGU9dGhpcy5nZXROdW1iZXJPZlBhZ2VzKCktMTtyZXR1cm4gdGhpcy5wYWdlSW5kZXg8ZSYmMCE9dGhpcy5wYWdlU2l6ZX1nZXROdW1iZXJPZlBhZ2VzKCl7cmV0dXJuIHRoaXMucGFnZVNpemU/TWF0aC5jZWlsKHRoaXMubGVuZ3RoL3RoaXMucGFnZVNpemUpOjB9X2NoYW5nZVBhZ2VTaXplKGUpe2xldCByPXRoaXMucGFnZUluZGV4O3RoaXMucGFnZUluZGV4PU1hdGguZmxvb3IodGhpcy5wYWdlSW5kZXgqdGhpcy5wYWdlU2l6ZS9lKXx8MCx0aGlzLnBhZ2VTaXplPWUsdGhpcy5fZW1pdFBhZ2VFdmVudChyKX1fbmV4dEJ1dHRvbnNEaXNhYmxlZCgpe3JldHVybiB0aGlzLmRpc2FibGVkfHwhdGhpcy5oYXNOZXh0UGFnZSgpfV9wcmV2aW91c0J1dHRvbnNEaXNhYmxlZCgpe3JldHVybiB0aGlzLmRpc2FibGVkfHwhdGhpcy5oYXNQcmV2aW91c1BhZ2UoKX1fdXBkYXRlRGlzcGxheWVkUGFnZVNpemVPcHRpb25zKCl7IXRoaXMuX2luaXRpYWxpemVkfHwodGhpcy5wYWdlU2l6ZXx8KHRoaXMuX3BhZ2VTaXplPTAhPXRoaXMucGFnZVNpemVPcHRpb25zLmxlbmd0aD90aGlzLnBhZ2VTaXplT3B0aW9uc1swXTo1MCksdGhpcy5fZGlzcGxheWVkUGFnZVNpemVPcHRpb25zPXRoaXMucGFnZVNpemVPcHRpb25zLnNsaWNlKCksLTE9PT10aGlzLl9kaXNwbGF5ZWRQYWdlU2l6ZU9wdGlvbnMuaW5kZXhPZih0aGlzLnBhZ2VTaXplKSYmdGhpcy5fZGlzcGxheWVkUGFnZVNpemVPcHRpb25zLnB1c2godGhpcy5wYWdlU2l6ZSksdGhpcy5fZGlzcGxheWVkUGFnZVNpemVPcHRpb25zLnNvcnQoKGUsaSk9PmUtaSksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCkpfV9lbWl0UGFnZUV2ZW50KGUpe3RoaXMucGFnZS5lbWl0KHtwcmV2aW91c1BhZ2VJbmRleDplLHBhZ2VJbmRleDp0aGlzLnBhZ2VJbmRleCxwYWdlU2l6ZTp0aGlzLnBhZ2VTaXplLGxlbmd0aDp0aGlzLmxlbmd0aH0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7bmwoKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixpbnB1dHM6e2NvbG9yOiJjb2xvciIscGFnZUluZGV4OiJwYWdlSW5kZXgiLGxlbmd0aDoibGVuZ3RoIixwYWdlU2l6ZToicGFnZVNpemUiLHBhZ2VTaXplT3B0aW9uczoicGFnZVNpemVPcHRpb25zIixoaWRlUGFnZVNpemU6ImhpZGVQYWdlU2l6ZSIsc2hvd0ZpcnN0TGFzdEJ1dHRvbnM6InNob3dGaXJzdExhc3RCdXR0b25zIixzZWxlY3RDb25maWc6InNlbGVjdENvbmZpZyJ9LG91dHB1dHM6e3BhZ2U6InBhZ2UifSxmZWF0dXJlczpbdHRdfSksbn0pKCksS2s9KCgpPT57Y2xhc3MgbiBleHRlbmRzIHBLZXtjb25zdHJ1Y3RvcihlLGkscil7c3VwZXIoZSxpLHIpLHImJm51bGwhPXIuZm9ybUZpZWxkQXBwZWFyYW5jZSYmKHRoaXMuX2Zvcm1GaWVsZEFwcGVhcmFuY2U9ci5mb3JtRmllbGRBcHBlYXJhbmNlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShlMCksTShubiksTSh1S2UsOCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1wYWdpbmF0b3IiXV0saG9zdEF0dHJzOlsicm9sZSIsImdyb3VwIiwxLCJtYXQtcGFnaW5hdG9yIl0saW5wdXRzOntkaXNhYmxlZDoiZGlzYWJsZWQifSxleHBvcnRBczpbIm1hdFBhZ2luYXRvciJdLGZlYXR1cmVzOlt0dF0sZGVjbHM6MTQsdmFyczoxNCxjb25zdHM6W1sxLCJtYXQtcGFnaW5hdG9yLW91dGVyLWNvbnRhaW5lciJdLFsxLCJtYXQtcGFnaW5hdG9yLWNvbnRhaW5lciJdLFsiY2xhc3MiLCJtYXQtcGFnaW5hdG9yLXBhZ2Utc2l6ZSIsNCwibmdJZiJdLFsxLCJtYXQtcGFnaW5hdG9yLXJhbmdlLWFjdGlvbnMiXSxbMSwibWF0LXBhZ2luYXRvci1yYW5nZS1sYWJlbCJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwidHlwZSIsImJ1dHRvbiIsImNsYXNzIiwibWF0LXBhZ2luYXRvci1uYXZpZ2F0aW9uLWZpcnN0IiwzLCJtYXRUb29sdGlwIiwibWF0VG9vbHRpcERpc2FibGVkIiwibWF0VG9vbHRpcFBvc2l0aW9uIiwiZGlzYWJsZWQiLCJjbGljayIsNCwibmdJZiJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwidHlwZSIsImJ1dHRvbiIsMSwibWF0LXBhZ2luYXRvci1uYXZpZ2F0aW9uLXByZXZpb3VzIiwzLCJtYXRUb29sdGlwIiwibWF0VG9vbHRpcERpc2FibGVkIiwibWF0VG9vbHRpcFBvc2l0aW9uIiwiZGlzYWJsZWQiLCJjbGljayJdLFsidmlld0JveCIsIjAgMCAyNCAyNCIsImZvY3VzYWJsZSIsImZhbHNlIiwxLCJtYXQtcGFnaW5hdG9yLWljb24iXSxbImQiLCJNMTUuNDEgNy40MUwxNCA2bC02IDYgNiA2IDEuNDEtMS40MUwxMC44MyAxMnoiXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsInR5cGUiLCJidXR0b24iLDEsIm1hdC1wYWdpbmF0b3ItbmF2aWdhdGlvbi1uZXh0IiwzLCJtYXRUb29sdGlwIiwibWF0VG9vbHRpcERpc2FibGVkIiwibWF0VG9vbHRpcFBvc2l0aW9uIiwiZGlzYWJsZWQiLCJjbGljayJdLFsiZCIsIk0xMCA2TDguNTkgNy40MSAxMy4xNyAxMmwtNC41OCA0LjU5TDEwIDE4bDYtNnoiXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsInR5cGUiLCJidXR0b24iLCJjbGFzcyIsIm1hdC1wYWdpbmF0b3ItbmF2aWdhdGlvbi1sYXN0IiwzLCJtYXRUb29sdGlwIiwibWF0VG9vbHRpcERpc2FibGVkIiwibWF0VG9vbHRpcFBvc2l0aW9uIiwiZGlzYWJsZWQiLCJjbGljayIsNCwibmdJZiJdLFsxLCJtYXQtcGFnaW5hdG9yLXBhZ2Utc2l6ZSJdLFsxLCJtYXQtcGFnaW5hdG9yLXBhZ2Utc2l6ZS1sYWJlbCJdLFsiY2xhc3MiLCJtYXQtcGFnaW5hdG9yLXBhZ2Utc2l6ZS1zZWxlY3QiLDMsImFwcGVhcmFuY2UiLCJjb2xvciIsNCwibmdJZiJdLFsiY2xhc3MiLCJtYXQtcGFnaW5hdG9yLXBhZ2Utc2l6ZS12YWx1ZSIsNCwibmdJZiJdLFsxLCJtYXQtcGFnaW5hdG9yLXBhZ2Utc2l6ZS1zZWxlY3QiLDMsImFwcGVhcmFuY2UiLCJjb2xvciJdLFszLCJ2YWx1ZSIsImRpc2FibGVkIiwicGFuZWxDbGFzcyIsImRpc2FibGVPcHRpb25DZW50ZXJpbmciLCJhcmlhLWxhYmVsIiwic2VsZWN0aW9uQ2hhbmdlIl0sWzMsInZhbHVlIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbMywidmFsdWUiXSxbMSwibWF0LXBhZ2luYXRvci1wYWdlLXNpemUtdmFsdWUiXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsInR5cGUiLCJidXR0b24iLDEsIm1hdC1wYWdpbmF0b3ItbmF2aWdhdGlvbi1maXJzdCIsMywibWF0VG9vbHRpcCIsIm1hdFRvb2x0aXBEaXNhYmxlZCIsIm1hdFRvb2x0aXBQb3NpdGlvbiIsImRpc2FibGVkIiwiY2xpY2siXSxbImQiLCJNMTguNDEgMTYuNTlMMTMuODIgMTJsNC41OS00LjU5TDE3IDZsLTYgNiA2IDZ6TTYgNmgydjEySDZ6Il0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJ0eXBlIiwiYnV0dG9uIiwxLCJtYXQtcGFnaW5hdG9yLW5hdmlnYXRpb24tbGFzdCIsMywibWF0VG9vbHRpcCIsIm1hdFRvb2x0aXBEaXNhYmxlZCIsIm1hdFRvb2x0aXBQb3NpdGlvbiIsImRpc2FibGVkIiwiY2xpY2siXSxbImQiLCJNNS41OSA3LjQxTDEwLjE4IDEybC00LjU5IDQuNTlMNyAxOGw2LTYtNi02ek0xNiA2aDJ2MTJoLTJ6Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJkaXYiLDApKDEsImRpdiIsMSksRSgyLHJLZSw1LDMsImRpdiIsMiksXygzLCJkaXYiLDMpKDQsImRpdiIsNCksQSg1KSx2KCksRSg2LG9LZSwzLDUsImJ1dHRvbiIsNSksXyg3LCJidXR0b24iLDYpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLnByZXZpb3VzUGFnZSgpfSksSW4oKSxfKDgsInN2ZyIsNyksTyg5LCJwYXRoIiw4KSx2KCkoKSxKcygpLF8oMTAsImJ1dHRvbiIsOSksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkubmV4dFBhZ2UoKX0pLEluKCksXygxMSwic3ZnIiw3KSxPKDEyLCJwYXRoIiwxMCksdigpKCksRSgxMyxzS2UsMyw1LCJidXR0b24iLDExKSx2KCkoKSgpKSwyJmUmJihDKDIpLHkoIm5nSWYiLCFpLmhpZGVQYWdlU2l6ZSksQygzKSxqZSgiICIsaS5faW50bC5nZXRSYW5nZUxhYmVsKGkucGFnZUluZGV4LGkucGFnZVNpemUsaS5sZW5ndGgpLCIgIiksQygxKSx5KCJuZ0lmIixpLnNob3dGaXJzdExhc3RCdXR0b25zKSxDKDEpLHkoIm1hdFRvb2x0aXAiLGkuX2ludGwucHJldmlvdXNQYWdlTGFiZWwpKCJtYXRUb29sdGlwRGlzYWJsZWQiLGkuX3ByZXZpb3VzQnV0dG9uc0Rpc2FibGVkKCkpKCJtYXRUb29sdGlwUG9zaXRpb24iLCJhYm92ZSIpKCJkaXNhYmxlZCIsaS5fcHJldmlvdXNCdXR0b25zRGlzYWJsZWQoKSksemUoImFyaWEtbGFiZWwiLGkuX2ludGwucHJldmlvdXNQYWdlTGFiZWwpLEMoMykseSgibWF0VG9vbHRpcCIsaS5faW50bC5uZXh0UGFnZUxhYmVsKSgibWF0VG9vbHRpcERpc2FibGVkIixpLl9uZXh0QnV0dG9uc0Rpc2FibGVkKCkpKCJtYXRUb29sdGlwUG9zaXRpb24iLCJhYm92ZSIpKCJkaXNhYmxlZCIsaS5fbmV4dEJ1dHRvbnNEaXNhYmxlZCgpKSx6ZSgiYXJpYS1sYWJlbCIsaS5faW50bC5uZXh0UGFnZUxhYmVsKSxDKDMpLHkoIm5nSWYiLGkuc2hvd0ZpcnN0TGFzdEJ1dHRvbnMpKX0sZGVwZW5kZW5jaWVzOltkbixCZSxfbixwZCxIaCxPcyxYa10sc3R5bGVzOlsiLm1hdC1wYWdpbmF0b3J7ZGlzcGxheTpibG9ja30ubWF0LXBhZ2luYXRvci1vdXRlci1jb250YWluZXJ7ZGlzcGxheTpmbGV4fS5tYXQtcGFnaW5hdG9yLWNvbnRhaW5lcntkaXNwbGF5OmZsZXg7YWxpZ24taXRlbXM6Y2VudGVyO2p1c3RpZnktY29udGVudDpmbGV4LWVuZDtwYWRkaW5nOjAgOHB4O2ZsZXgtd3JhcDp3cmFwLXJldmVyc2U7d2lkdGg6MTAwJX0ubWF0LXBhZ2luYXRvci1wYWdlLXNpemV7ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmJhc2VsaW5lO21hcmdpbi1yaWdodDo4cHh9W2Rpcj1ydGxdIC5tYXQtcGFnaW5hdG9yLXBhZ2Utc2l6ZXttYXJnaW4tcmlnaHQ6MDttYXJnaW4tbGVmdDo4cHh9Lm1hdC1wYWdpbmF0b3ItcGFnZS1zaXplLWxhYmVse21hcmdpbjowIDRweH0ubWF0LXBhZ2luYXRvci1wYWdlLXNpemUtc2VsZWN0e21hcmdpbjo2cHggNHB4IDAgNHB4O3dpZHRoOjU2cHh9Lm1hdC1wYWdpbmF0b3ItcGFnZS1zaXplLXNlbGVjdC5tYXQtZm9ybS1maWVsZC1hcHBlYXJhbmNlLW91dGxpbmV7d2lkdGg6NjRweH0ubWF0LXBhZ2luYXRvci1wYWdlLXNpemUtc2VsZWN0Lm1hdC1mb3JtLWZpZWxkLWFwcGVhcmFuY2UtZmlsbHt3aWR0aDo2NHB4fS5tYXQtcGFnaW5hdG9yLXJhbmdlLWxhYmVse21hcmdpbjowIDMycHggMCAyNHB4fS5tYXQtcGFnaW5hdG9yLXJhbmdlLWFjdGlvbnN7ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcn0ubWF0LXBhZ2luYXRvci1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO3dpZHRoOjI4cHg7ZmlsbDpjdXJyZW50Q29sb3J9W2Rpcj1ydGxdIC5tYXQtcGFnaW5hdG9yLWljb257dHJhbnNmb3JtOnJvdGF0ZSgxODBkZWcpfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1wYWdpbmF0b3ItaWNvbntmaWxsOkNhbnZhc1RleHR9Il0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksWHBlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W2xLZV0saW1wb3J0czpbTWUsUG4sbGMsUWssbG5dfSksbn0pKCksZktlPVsibWF0LXNvcnQtaGVhZGVyIiwiIl07ZnVuY3Rpb24gbUtlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2IiwzKSxQKCJAYXJyb3dQb3NpdGlvbi5zdGFydCIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLl9kaXNhYmxlVmlld1N0YXRlQW5pbWF0aW9uPSEwKX0pKCJAYXJyb3dQb3NpdGlvbi5kb25lIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkuX2Rpc2FibGVWaWV3U3RhdGVBbmltYXRpb249ITEpfSksTygxLCJkaXYiLDQpLF8oMiwiZGl2Iiw1KSxPKDMsImRpdiIsNikoNCwiZGl2Iiw3KSg1LCJkaXYiLDgpLHYoKSgpfWlmKDImbil7bGV0IGU9UygpO3koIkBhcnJvd09wYWNpdHkiLGUuX2dldEFycm93Vmlld1N0YXRlKCkpKCJAYXJyb3dQb3NpdGlvbiIsZS5fZ2V0QXJyb3dWaWV3U3RhdGUoKSkoIkBhbGxvd0NoaWxkcmVuIixlLl9nZXRBcnJvd0RpcmVjdGlvblN0YXRlKCkpLEMoMikseSgiQGluZGljYXRvciIsZS5fZ2V0QXJyb3dEaXJlY3Rpb25TdGF0ZSgpKSxDKDEpLHkoIkBsZWZ0UG9pbnRlciIsZS5fZ2V0QXJyb3dEaXJlY3Rpb25TdGF0ZSgpKSxDKDEpLHkoIkByaWdodFBvaW50ZXIiLGUuX2dldEFycm93RGlyZWN0aW9uU3RhdGUoKSl9fXZhciBnS2U9WyIqIl0sUXBlPW5ldyBwZSgiTUFUX1NPUlRfREVGQVVMVF9PUFRJT05TIiksX0tlPW0yKHNvKGNsYXNze30pKSxNRT0oKCk9PntjbGFzcyBuIGV4dGVuZHMgX0tle2NvbnN0cnVjdG9yKGUpe3N1cGVyKCksdGhpcy5fZGVmYXVsdE9wdGlvbnM9ZSx0aGlzLnNvcnRhYmxlcz1uZXcgTWFwLHRoaXMuX3N0YXRlQ2hhbmdlcz1uZXcga2UsdGhpcy5zdGFydD0iYXNjIix0aGlzLl9kaXJlY3Rpb249IiIsdGhpcy5zb3J0Q2hhbmdlPW5ldyBHfWdldCBkaXJlY3Rpb24oKXtyZXR1cm4gdGhpcy5fZGlyZWN0aW9ufXNldCBkaXJlY3Rpb24oZSl7dGhpcy5fZGlyZWN0aW9uPWV9Z2V0IGRpc2FibGVDbGVhcigpe3JldHVybiB0aGlzLl9kaXNhYmxlQ2xlYXJ9c2V0IGRpc2FibGVDbGVhcihlKXt0aGlzLl9kaXNhYmxlQ2xlYXI9UnQoZSl9cmVnaXN0ZXIoZSl7dGhpcy5zb3J0YWJsZXMuc2V0KGUuaWQsZSl9ZGVyZWdpc3RlcihlKXt0aGlzLnNvcnRhYmxlcy5kZWxldGUoZS5pZCl9c29ydChlKXt0aGlzLmFjdGl2ZSE9ZS5pZD8odGhpcy5hY3RpdmU9ZS5pZCx0aGlzLmRpcmVjdGlvbj1lLnN0YXJ0P2Uuc3RhcnQ6dGhpcy5zdGFydCk6dGhpcy5kaXJlY3Rpb249dGhpcy5nZXROZXh0U29ydERpcmVjdGlvbihlKSx0aGlzLnNvcnRDaGFuZ2UuZW1pdCh7YWN0aXZlOnRoaXMuYWN0aXZlLGRpcmVjdGlvbjp0aGlzLmRpcmVjdGlvbn0pfWdldE5leHRTb3J0RGlyZWN0aW9uKGUpe2lmKCFlKXJldHVybiIiO2xldCByPWZ1bmN0aW9uKG4sdCl7bGV0IGU9WyJhc2MiLCJkZXNjIl07cmV0dXJuImRlc2MiPT1uJiZlLnJldmVyc2UoKSx0fHxlLnB1c2goIiIpLGV9KGUuc3RhcnR8fHRoaXMuc3RhcnQsZT8uZGlzYWJsZUNsZWFyPz90aGlzLmRpc2FibGVDbGVhcj8/ISF0aGlzLl9kZWZhdWx0T3B0aW9ucz8uZGlzYWJsZUNsZWFyKSxvPXIuaW5kZXhPZih0aGlzLmRpcmVjdGlvbikrMTtyZXR1cm4gbz49ci5sZW5ndGgmJihvPTApLHJbb119bmdPbkluaXQoKXt0aGlzLl9tYXJrSW5pdGlhbGl6ZWQoKX1uZ09uQ2hhbmdlcygpe3RoaXMuX3N0YXRlQ2hhbmdlcy5uZXh0KCl9bmdPbkRlc3Ryb3koKXt0aGlzLl9zdGF0ZUNoYW5nZXMuY29tcGxldGUoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShRcGUsOCkpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJtYXRTb3J0IiwiIl1dLGhvc3RBdHRyczpbMSwibWF0LXNvcnQiXSxpbnB1dHM6e2Rpc2FibGVkOlsibWF0U29ydERpc2FibGVkIiwiZGlzYWJsZWQiXSxhY3RpdmU6WyJtYXRTb3J0QWN0aXZlIiwiYWN0aXZlIl0sc3RhcnQ6WyJtYXRTb3J0U3RhcnQiLCJzdGFydCJdLGRpcmVjdGlvbjpbIm1hdFNvcnREaXJlY3Rpb24iLCJkaXJlY3Rpb24iXSxkaXNhYmxlQ2xlYXI6WyJtYXRTb3J0RGlzYWJsZUNsZWFyIiwiZGlzYWJsZUNsZWFyIl19LG91dHB1dHM6e3NvcnRDaGFuZ2U6Im1hdFNvcnRDaGFuZ2UifSxleHBvcnRBczpbIm1hdFNvcnQiXSxmZWF0dXJlczpbdHQsRnRdfSksbn0pKCksQWY9YXRlLkVOVEVSSU5HKyIgIitzdGUuU1RBTkRBUkRfQ1VSVkUsUGI9e2luZGljYXRvcjpLcigiaW5kaWNhdG9yIixba2koImFjdGl2ZS1hc2MsIGFzYyIsZ24oe3RyYW5zZm9ybToidHJhbnNsYXRlWSgwcHgpIn0pKSxraSgiYWN0aXZlLWRlc2MsIGRlc2MiLGduKHt0cmFuc2Zvcm06InRyYW5zbGF0ZVkoMTBweCkifSkpLExpKCJhY3RpdmUtYXNjIDw9PiBhY3RpdmUtZGVzYyIsamkoQWYpKV0pLGxlZnRQb2ludGVyOktyKCJsZWZ0UG9pbnRlciIsW2tpKCJhY3RpdmUtYXNjLCBhc2MiLGduKHt0cmFuc2Zvcm06InJvdGF0ZSgtNDVkZWcpIn0pKSxraSgiYWN0aXZlLWRlc2MsIGRlc2MiLGduKHt0cmFuc2Zvcm06InJvdGF0ZSg0NWRlZykifSkpLExpKCJhY3RpdmUtYXNjIDw9PiBhY3RpdmUtZGVzYyIsamkoQWYpKV0pLHJpZ2h0UG9pbnRlcjpLcigicmlnaHRQb2ludGVyIixba2koImFjdGl2ZS1hc2MsIGFzYyIsZ24oe3RyYW5zZm9ybToicm90YXRlKDQ1ZGVnKSJ9KSksa2koImFjdGl2ZS1kZXNjLCBkZXNjIixnbih7dHJhbnNmb3JtOiJyb3RhdGUoLTQ1ZGVnKSJ9KSksTGkoImFjdGl2ZS1hc2MgPD0+IGFjdGl2ZS1kZXNjIixqaShBZikpXSksYXJyb3dPcGFjaXR5OktyKCJhcnJvd09wYWNpdHkiLFtraSgiZGVzYy10by1hY3RpdmUsIGFzYy10by1hY3RpdmUsIGFjdGl2ZSIsZ24oe29wYWNpdHk6MX0pKSxraSgiZGVzYy10by1oaW50LCBhc2MtdG8taGludCwgaGludCIsZ24oe29wYWNpdHk6LjU0fSkpLGtpKCJoaW50LXRvLWRlc2MsIGFjdGl2ZS10by1kZXNjLCBkZXNjLCBoaW50LXRvLWFzYywgYWN0aXZlLXRvLWFzYywgYXNjLCB2b2lkIixnbih7b3BhY2l0eTowfSkpLExpKCIqID0+IGFzYywgKiA9PiBkZXNjLCAqID0+IGFjdGl2ZSwgKiA9PiBoaW50LCAqID0+IHZvaWQiLGppKCIwbXMiKSksTGkoIiogPD0+ICoiLGppKEFmKSldKSxhcnJvd1Bvc2l0aW9uOktyKCJhcnJvd1Bvc2l0aW9uIixbTGkoIiogPT4gZGVzYy10by1oaW50LCAqID0+IGRlc2MtdG8tYWN0aXZlIixqaShBZixEbShbZ24oe3RyYW5zZm9ybToidHJhbnNsYXRlWSgtMjUlKSJ9KSxnbih7dHJhbnNmb3JtOiJ0cmFuc2xhdGVZKDApIn0pXSkpKSxMaSgiKiA9PiBoaW50LXRvLWRlc2MsICogPT4gYWN0aXZlLXRvLWRlc2MiLGppKEFmLERtKFtnbih7dHJhbnNmb3JtOiJ0cmFuc2xhdGVZKDApIn0pLGduKHt0cmFuc2Zvcm06InRyYW5zbGF0ZVkoMjUlKSJ9KV0pKSksTGkoIiogPT4gYXNjLXRvLWhpbnQsICogPT4gYXNjLXRvLWFjdGl2ZSIsamkoQWYsRG0oW2duKHt0cmFuc2Zvcm06InRyYW5zbGF0ZVkoMjUlKSJ9KSxnbih7dHJhbnNmb3JtOiJ0cmFuc2xhdGVZKDApIn0pXSkpKSxMaSgiKiA9PiBoaW50LXRvLWFzYywgKiA9PiBhY3RpdmUtdG8tYXNjIixqaShBZixEbShbZ24oe3RyYW5zZm9ybToidHJhbnNsYXRlWSgwKSJ9KSxnbih7dHJhbnNmb3JtOiJ0cmFuc2xhdGVZKC0yNSUpIn0pXSkpKSxraSgiZGVzYy10by1oaW50LCBhc2MtdG8taGludCwgaGludCwgZGVzYy10by1hY3RpdmUsIGFzYy10by1hY3RpdmUsIGFjdGl2ZSIsZ24oe3RyYW5zZm9ybToidHJhbnNsYXRlWSgwKSJ9KSksa2koImhpbnQtdG8tZGVzYywgYWN0aXZlLXRvLWRlc2MsIGRlc2MiLGduKHt0cmFuc2Zvcm06InRyYW5zbGF0ZVkoLTI1JSkifSkpLGtpKCJoaW50LXRvLWFzYywgYWN0aXZlLXRvLWFzYywgYXNjIixnbih7dHJhbnNmb3JtOiJ0cmFuc2xhdGVZKDI1JSkifSkpXSksYWxsb3dDaGlsZHJlbjpLcigiYWxsb3dDaGlsZHJlbiIsW0xpKCIqIDw9PiAqIixbSW0oIkAqIixBbSgpLHtvcHRpb25hbDohMH0pXSldKX0sWms9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuY2hhbmdlcz1uZXcga2V9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhYyxwcm92aWRlZEluOiJyb290In0pLG59KSgpLGJLZT17cHJvdmlkZTpaayxkZXBzOltbbmV3IG5zLG5ldyB0bCxaa11dLHVzZUZhY3Rvcnk6ZnVuY3Rpb24obil7cmV0dXJuIG58fG5ldyBaa319LHhLZT1zbyhjbGFzc3t9KSxLcGU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIHhLZXtjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsLGMpe3N1cGVyKCksdGhpcy5faW50bD1lLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmPWksdGhpcy5fc29ydD1yLHRoaXMuX2NvbHVtbkRlZj1vLHRoaXMuX2ZvY3VzTW9uaXRvcj1zLHRoaXMuX2VsZW1lbnRSZWY9YSx0aGlzLl9hcmlhRGVzY3JpYmVyPWwsdGhpcy5fc2hvd0luZGljYXRvckhpbnQ9ITEsdGhpcy5fdmlld1N0YXRlPXt9LHRoaXMuX2Fycm93RGlyZWN0aW9uPSIiLHRoaXMuX2Rpc2FibGVWaWV3U3RhdGVBbmltYXRpb249ITEsdGhpcy5hcnJvd1Bvc2l0aW9uPSJhZnRlciIsdGhpcy5fc29ydEFjdGlvbkRlc2NyaXB0aW9uPSJTb3J0IixjPy5hcnJvd1Bvc2l0aW9uJiYodGhpcy5hcnJvd1Bvc2l0aW9uPWM/LmFycm93UG9zaXRpb24pLHRoaXMuX2hhbmRsZVN0YXRlQ2hhbmdlcygpfWdldCBzb3J0QWN0aW9uRGVzY3JpcHRpb24oKXtyZXR1cm4gdGhpcy5fc29ydEFjdGlvbkRlc2NyaXB0aW9ufXNldCBzb3J0QWN0aW9uRGVzY3JpcHRpb24oZSl7dGhpcy5fdXBkYXRlU29ydEFjdGlvbkRlc2NyaXB0aW9uKGUpfWdldCBkaXNhYmxlQ2xlYXIoKXtyZXR1cm4gdGhpcy5fZGlzYWJsZUNsZWFyfXNldCBkaXNhYmxlQ2xlYXIoZSl7dGhpcy5fZGlzYWJsZUNsZWFyPVJ0KGUpfW5nT25Jbml0KCl7IXRoaXMuaWQmJnRoaXMuX2NvbHVtbkRlZiYmKHRoaXMuaWQ9dGhpcy5fY29sdW1uRGVmLm5hbWUpLHRoaXMuX3VwZGF0ZUFycm93RGlyZWN0aW9uKCksdGhpcy5fc2V0QW5pbWF0aW9uVHJhbnNpdGlvblN0YXRlKHt0b1N0YXRlOnRoaXMuX2lzU29ydGVkKCk/ImFjdGl2ZSI6dGhpcy5fYXJyb3dEaXJlY3Rpb259KSx0aGlzLl9zb3J0LnJlZ2lzdGVyKHRoaXMpLHRoaXMuX3NvcnRCdXR0b249dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LnF1ZXJ5U2VsZWN0b3IoIi5tYXQtc29ydC1oZWFkZXItY29udGFpbmVyIiksdGhpcy5fdXBkYXRlU29ydEFjdGlvbkRlc2NyaXB0aW9uKHRoaXMuX3NvcnRBY3Rpb25EZXNjcmlwdGlvbil9bmdBZnRlclZpZXdJbml0KCl7dGhpcy5fZm9jdXNNb25pdG9yLm1vbml0b3IodGhpcy5fZWxlbWVudFJlZiwhMCkuc3Vic2NyaWJlKGU9PntsZXQgaT0hIWU7aSE9PXRoaXMuX3Nob3dJbmRpY2F0b3JIaW50JiYodGhpcy5fc2V0SW5kaWNhdG9ySGludFZpc2libGUoaSksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCkpfSl9bmdPbkRlc3Ryb3koKXt0aGlzLl9mb2N1c01vbml0b3Iuc3RvcE1vbml0b3JpbmcodGhpcy5fZWxlbWVudFJlZiksdGhpcy5fc29ydC5kZXJlZ2lzdGVyKHRoaXMpLHRoaXMuX3JlcmVuZGVyU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCl9X3NldEluZGljYXRvckhpbnRWaXNpYmxlKGUpe3RoaXMuX2lzRGlzYWJsZWQoKSYmZXx8KHRoaXMuX3Nob3dJbmRpY2F0b3JIaW50PWUsdGhpcy5faXNTb3J0ZWQoKXx8KHRoaXMuX3VwZGF0ZUFycm93RGlyZWN0aW9uKCksdGhpcy5fc2V0QW5pbWF0aW9uVHJhbnNpdGlvblN0YXRlKHRoaXMuX3Nob3dJbmRpY2F0b3JIaW50P3tmcm9tU3RhdGU6dGhpcy5fYXJyb3dEaXJlY3Rpb24sdG9TdGF0ZToiaGludCJ9Ontmcm9tU3RhdGU6ImhpbnQiLHRvU3RhdGU6dGhpcy5fYXJyb3dEaXJlY3Rpb259KSkpfV9zZXRBbmltYXRpb25UcmFuc2l0aW9uU3RhdGUoZSl7dGhpcy5fdmlld1N0YXRlPWV8fHt9LHRoaXMuX2Rpc2FibGVWaWV3U3RhdGVBbmltYXRpb24mJih0aGlzLl92aWV3U3RhdGU9e3RvU3RhdGU6ZS50b1N0YXRlfSl9X3RvZ2dsZU9uSW50ZXJhY3Rpb24oKXt0aGlzLl9zb3J0LnNvcnQodGhpcyksKCJoaW50Ij09PXRoaXMuX3ZpZXdTdGF0ZS50b1N0YXRlfHwiYWN0aXZlIj09PXRoaXMuX3ZpZXdTdGF0ZS50b1N0YXRlKSYmKHRoaXMuX2Rpc2FibGVWaWV3U3RhdGVBbmltYXRpb249ITApfV9oYW5kbGVDbGljaygpe3RoaXMuX2lzRGlzYWJsZWQoKXx8dGhpcy5fc29ydC5zb3J0KHRoaXMpfV9oYW5kbGVLZXlkb3duKGUpeyF0aGlzLl9pc0Rpc2FibGVkKCkmJigzMj09PWUua2V5Q29kZXx8MTM9PT1lLmtleUNvZGUpJiYoZS5wcmV2ZW50RGVmYXVsdCgpLHRoaXMuX3RvZ2dsZU9uSW50ZXJhY3Rpb24oKSl9X2lzU29ydGVkKCl7cmV0dXJuIHRoaXMuX3NvcnQuYWN0aXZlPT10aGlzLmlkJiYoImFzYyI9PT10aGlzLl9zb3J0LmRpcmVjdGlvbnx8ImRlc2MiPT09dGhpcy5fc29ydC5kaXJlY3Rpb24pfV9nZXRBcnJvd0RpcmVjdGlvblN0YXRlKCl7cmV0dXJuYCR7dGhpcy5faXNTb3J0ZWQoKT8iYWN0aXZlLSI6IiJ9JHt0aGlzLl9hcnJvd0RpcmVjdGlvbn1gfV9nZXRBcnJvd1ZpZXdTdGF0ZSgpe2xldCBlPXRoaXMuX3ZpZXdTdGF0ZS5mcm9tU3RhdGU7cmV0dXJuKGU/YCR7ZX0tdG8tYDoiIikrdGhpcy5fdmlld1N0YXRlLnRvU3RhdGV9X3VwZGF0ZUFycm93RGlyZWN0aW9uKCl7dGhpcy5fYXJyb3dEaXJlY3Rpb249dGhpcy5faXNTb3J0ZWQoKT90aGlzLl9zb3J0LmRpcmVjdGlvbjp0aGlzLnN0YXJ0fHx0aGlzLl9zb3J0LnN0YXJ0fV9pc0Rpc2FibGVkKCl7cmV0dXJuIHRoaXMuX3NvcnQuZGlzYWJsZWR8fHRoaXMuZGlzYWJsZWR9X2dldEFyaWFTb3J0QXR0cmlidXRlKCl7cmV0dXJuIHRoaXMuX2lzU29ydGVkKCk/ImFzYyI9PXRoaXMuX3NvcnQuZGlyZWN0aW9uPyJhc2NlbmRpbmciOiJkZXNjZW5kaW5nIjoibm9uZSJ9X3JlbmRlckFycm93KCl7cmV0dXJuIXRoaXMuX2lzRGlzYWJsZWQoKXx8dGhpcy5faXNTb3J0ZWQoKX1fdXBkYXRlU29ydEFjdGlvbkRlc2NyaXB0aW9uKGUpe3RoaXMuX3NvcnRCdXR0b24mJih0aGlzLl9hcmlhRGVzY3JpYmVyPy5yZW1vdmVEZXNjcmlwdGlvbih0aGlzLl9zb3J0QnV0dG9uLHRoaXMuX3NvcnRBY3Rpb25EZXNjcmlwdGlvbiksdGhpcy5fYXJpYURlc2NyaWJlcj8uZGVzY3JpYmUodGhpcy5fc29ydEJ1dHRvbixlKSksdGhpcy5fc29ydEFjdGlvbkRlc2NyaXB0aW9uPWV9X2hhbmRsZVN0YXRlQ2hhbmdlcygpe3RoaXMuX3JlcmVuZGVyU3Vic2NyaXB0aW9uPUp0KHRoaXMuX3NvcnQuc29ydENoYW5nZSx0aGlzLl9zb3J0Ll9zdGF0ZUNoYW5nZXMsdGhpcy5faW50bC5jaGFuZ2VzKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9pc1NvcnRlZCgpJiYodGhpcy5fdXBkYXRlQXJyb3dEaXJlY3Rpb24oKSwoImhpbnQiPT09dGhpcy5fdmlld1N0YXRlLnRvU3RhdGV8fCJhY3RpdmUiPT09dGhpcy5fdmlld1N0YXRlLnRvU3RhdGUpJiYodGhpcy5fZGlzYWJsZVZpZXdTdGF0ZUFuaW1hdGlvbj0hMCksdGhpcy5fc2V0QW5pbWF0aW9uVHJhbnNpdGlvblN0YXRlKHtmcm9tU3RhdGU6dGhpcy5fYXJyb3dEaXJlY3Rpb24sdG9TdGF0ZToiYWN0aXZlIn0pLHRoaXMuX3Nob3dJbmRpY2F0b3JIaW50PSExKSwhdGhpcy5faXNTb3J0ZWQoKSYmdGhpcy5fdmlld1N0YXRlJiYiYWN0aXZlIj09PXRoaXMuX3ZpZXdTdGF0ZS50b1N0YXRlJiYodGhpcy5fZGlzYWJsZVZpZXdTdGF0ZUFuaW1hdGlvbj0hMSx0aGlzLl9zZXRBbmltYXRpb25UcmFuc2l0aW9uU3RhdGUoe2Zyb21TdGF0ZToiYWN0aXZlIix0b1N0YXRlOnRoaXMuX2Fycm93RGlyZWN0aW9ufSkpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oWmspLE0obm4pLE0oTUUsOCksTSgiTUFUX1NPUlRfSEVBREVSX0NPTFVNTl9ERUYiLDgpLE0oRnIpLE0oUmUpLE0oZjIsOCksTShRcGUsOCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdC1zb3J0LWhlYWRlciIsIiJdXSxob3N0QXR0cnM6WzEsIm1hdC1zb3J0LWhlYWRlciJdLGhvc3RWYXJzOjMsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MSZlJiZQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5faGFuZGxlQ2xpY2soKX0pKCJrZXlkb3duIixmdW5jdGlvbihvKXtyZXR1cm4gaS5faGFuZGxlS2V5ZG93bihvKX0pKCJtb3VzZWVudGVyIixmdW5jdGlvbigpe3JldHVybiBpLl9zZXRJbmRpY2F0b3JIaW50VmlzaWJsZSghMCl9KSgibW91c2VsZWF2ZSIsZnVuY3Rpb24oKXtyZXR1cm4gaS5fc2V0SW5kaWNhdG9ySGludFZpc2libGUoITEpfSksMiZlJiYoemUoImFyaWEtc29ydCIsaS5fZ2V0QXJpYVNvcnRBdHRyaWJ1dGUoKSksZXQoIm1hdC1zb3J0LWhlYWRlci1kaXNhYmxlZCIsaS5faXNEaXNhYmxlZCgpKSl9LGlucHV0czp7ZGlzYWJsZWQ6ImRpc2FibGVkIixpZDpbIm1hdC1zb3J0LWhlYWRlciIsImlkIl0sYXJyb3dQb3NpdGlvbjoiYXJyb3dQb3NpdGlvbiIsc3RhcnQ6InN0YXJ0Iixzb3J0QWN0aW9uRGVzY3JpcHRpb246InNvcnRBY3Rpb25EZXNjcmlwdGlvbiIsZGlzYWJsZUNsZWFyOiJkaXNhYmxlQ2xlYXIifSxleHBvcnRBczpbIm1hdFNvcnRIZWFkZXIiXSxmZWF0dXJlczpbdHRdLGF0dHJzOmZLZSxuZ0NvbnRlbnRTZWxlY3RvcnM6Z0tlLGRlY2xzOjQsdmFyczo3LGNvbnN0czpbWzEsIm1hdC1zb3J0LWhlYWRlci1jb250YWluZXIiLCJtYXQtZm9jdXMtaW5kaWNhdG9yIl0sWzEsIm1hdC1zb3J0LWhlYWRlci1jb250ZW50Il0sWyJjbGFzcyIsIm1hdC1zb3J0LWhlYWRlci1hcnJvdyIsNCwibmdJZiJdLFsxLCJtYXQtc29ydC1oZWFkZXItYXJyb3ciXSxbMSwibWF0LXNvcnQtaGVhZGVyLXN0ZW0iXSxbMSwibWF0LXNvcnQtaGVhZGVyLWluZGljYXRvciJdLFsxLCJtYXQtc29ydC1oZWFkZXItcG9pbnRlci1sZWZ0Il0sWzEsIm1hdC1zb3J0LWhlYWRlci1wb2ludGVyLXJpZ2h0Il0sWzEsIm1hdC1zb3J0LWhlYWRlci1wb2ludGVyLW1pZGRsZSJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKHhpKCksXygwLCJkaXYiLDApKDEsImRpdiIsMSksVm4oMiksdigpLEUoMyxtS2UsNiw2LCJkaXYiLDIpLHYoKSksMiZlJiYoZXQoIm1hdC1zb3J0LWhlYWRlci1zb3J0ZWQiLGkuX2lzU29ydGVkKCkpKCJtYXQtc29ydC1oZWFkZXItcG9zaXRpb24tYmVmb3JlIiwiYmVmb3JlIj09PWkuYXJyb3dQb3NpdGlvbiksemUoInRhYmluZGV4IixpLl9pc0Rpc2FibGVkKCk/bnVsbDowKSgicm9sZSIsaS5faXNEaXNhYmxlZCgpP251bGw6ImJ1dHRvbiIpLEMoMykseSgibmdJZiIsaS5fcmVuZGVyQXJyb3coKSkpfSxkZXBlbmRlbmNpZXM6W0JlXSxzdHlsZXM6WyIubWF0LXNvcnQtaGVhZGVyLWNvbnRhaW5lcntkaXNwbGF5OmZsZXg7Y3Vyc29yOnBvaW50ZXI7YWxpZ24taXRlbXM6Y2VudGVyO2xldHRlci1zcGFjaW5nOm5vcm1hbDtvdXRsaW5lOjB9W21hdC1zb3J0LWhlYWRlcl0uY2RrLWtleWJvYXJkLWZvY3VzZWQgLm1hdC1zb3J0LWhlYWRlci1jb250YWluZXIsW21hdC1zb3J0LWhlYWRlcl0uY2RrLXByb2dyYW0tZm9jdXNlZCAubWF0LXNvcnQtaGVhZGVyLWNvbnRhaW5lcntib3JkZXItYm90dG9tOnNvbGlkIDFweCBjdXJyZW50Q29sb3J9Lm1hdC1zb3J0LWhlYWRlci1kaXNhYmxlZCAubWF0LXNvcnQtaGVhZGVyLWNvbnRhaW5lcntjdXJzb3I6ZGVmYXVsdH0ubWF0LXNvcnQtaGVhZGVyLWNvbnRhaW5lcjo6YmVmb3Jle21hcmdpbjpjYWxjKGNhbGModmFyKC0tbWF0LWZvY3VzLWluZGljYXRvci1ib3JkZXItd2lkdGgsIDNweCkgKyAycHgpICogLTEpfS5tYXQtc29ydC1oZWFkZXItY29udGVudHt0ZXh0LWFsaWduOmNlbnRlcjtkaXNwbGF5OmZsZXg7YWxpZ24taXRlbXM6Y2VudGVyfS5tYXQtc29ydC1oZWFkZXItcG9zaXRpb24tYmVmb3Jle2ZsZXgtZGlyZWN0aW9uOnJvdy1yZXZlcnNlfS5tYXQtc29ydC1oZWFkZXItYXJyb3d7aGVpZ2h0OjEycHg7d2lkdGg6MTJweDttaW4td2lkdGg6MTJweDtwb3NpdGlvbjpyZWxhdGl2ZTtkaXNwbGF5OmZsZXg7b3BhY2l0eTowfS5tYXQtc29ydC1oZWFkZXItYXJyb3csW2Rpcj1ydGxdIC5tYXQtc29ydC1oZWFkZXItcG9zaXRpb24tYmVmb3JlIC5tYXQtc29ydC1oZWFkZXItYXJyb3d7bWFyZ2luOjAgMCAwIDZweH0ubWF0LXNvcnQtaGVhZGVyLXBvc2l0aW9uLWJlZm9yZSAubWF0LXNvcnQtaGVhZGVyLWFycm93LFtkaXI9cnRsXSAubWF0LXNvcnQtaGVhZGVyLWFycm93e21hcmdpbjowIDZweCAwIDB9Lm1hdC1zb3J0LWhlYWRlci1zdGVte2JhY2tncm91bmQ6Y3VycmVudENvbG9yO2hlaWdodDoxMHB4O3dpZHRoOjJweDttYXJnaW46YXV0bztkaXNwbGF5OmZsZXg7YWxpZ24taXRlbXM6Y2VudGVyfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1zb3J0LWhlYWRlci1zdGVte3dpZHRoOjA7Ym9yZGVyLWxlZnQ6c29saWQgMnB4fS5tYXQtc29ydC1oZWFkZXItaW5kaWNhdG9ye3dpZHRoOjEwMCU7aGVpZ2h0OjJweDtkaXNwbGF5OmZsZXg7YWxpZ24taXRlbXM6Y2VudGVyO3Bvc2l0aW9uOmFic29sdXRlO3RvcDowO2xlZnQ6MH0ubWF0LXNvcnQtaGVhZGVyLXBvaW50ZXItbWlkZGxle21hcmdpbjphdXRvO2hlaWdodDoycHg7d2lkdGg6MnB4O2JhY2tncm91bmQ6Y3VycmVudENvbG9yO3RyYW5zZm9ybTpyb3RhdGUoNDVkZWcpfS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1zb3J0LWhlYWRlci1wb2ludGVyLW1pZGRsZXt3aWR0aDowO2hlaWdodDowO2JvcmRlci10b3A6c29saWQgMnB4O2JvcmRlci1sZWZ0OnNvbGlkIDJweH0ubWF0LXNvcnQtaGVhZGVyLXBvaW50ZXItbGVmdCwubWF0LXNvcnQtaGVhZGVyLXBvaW50ZXItcmlnaHR7YmFja2dyb3VuZDpjdXJyZW50Q29sb3I7d2lkdGg6NnB4O2hlaWdodDoycHg7cG9zaXRpb246YWJzb2x1dGU7dG9wOjB9LmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LXNvcnQtaGVhZGVyLXBvaW50ZXItbGVmdCwuY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtc29ydC1oZWFkZXItcG9pbnRlci1yaWdodHt3aWR0aDowO2hlaWdodDowO2JvcmRlci1sZWZ0OnNvbGlkIDZweDtib3JkZXItdG9wOnNvbGlkIDJweH0ubWF0LXNvcnQtaGVhZGVyLXBvaW50ZXItbGVmdHt0cmFuc2Zvcm0tb3JpZ2luOnJpZ2h0O2xlZnQ6MH0ubWF0LXNvcnQtaGVhZGVyLXBvaW50ZXItcmlnaHR7dHJhbnNmb3JtLW9yaWdpbjpsZWZ0O3JpZ2h0OjB9Il0sZW5jYXBzdWxhdGlvbjoyLGRhdGE6e2FuaW1hdGlvbjpbUGIuaW5kaWNhdG9yLFBiLmxlZnRQb2ludGVyLFBiLnJpZ2h0UG9pbnRlcixQYi5hcnJvd09wYWNpdHksUGIuYXJyb3dQb3NpdGlvbixQYi5hbGxvd0NoaWxkcmVuXX0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxacGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbYktlXSxpbXBvcnRzOltNZSxsbl19KSxufSkoKTtmdW5jdGlvbiBqRyhuKXtyZXR1cm4gY2xhc3MgZXh0ZW5kcyBue2NvbnN0cnVjdG9yKC4uLnQpe3N1cGVyKC4uLnQpLHRoaXMuX3N0aWNreT0hMSx0aGlzLl9oYXNTdGlja3lDaGFuZ2VkPSExfWdldCBzdGlja3koKXtyZXR1cm4gdGhpcy5fc3RpY2t5fXNldCBzdGlja3kodCl7bGV0IGU9dGhpcy5fc3RpY2t5O3RoaXMuX3N0aWNreT1SdCh0KSx0aGlzLl9oYXNTdGlja3lDaGFuZ2VkPWUhPT10aGlzLl9zdGlja3l9aGFzU3RpY2t5Q2hhbmdlZCgpe2xldCB0PXRoaXMuX2hhc1N0aWNreUNoYW5nZWQ7cmV0dXJuIHRoaXMuX2hhc1N0aWNreUNoYW5nZWQ9ITEsdH1yZXNldFN0aWNreUNoYW5nZWQoKXt0aGlzLl9oYXNTdGlja3lDaGFuZ2VkPSExfX19dmFyIEdHPW5ldyBwZSgiQ0RLX1RBQkxFIiksSnBlPShuZXcgcGUoInRleHQtY29sdW1uLW9wdGlvbnMiKSxqRyhjbGFzc3t9KSxuZXcgcGUoIl9DT0FMRVNDRURfU1RZTEVfU0NIRURVTEVSIiksKCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkpe3RoaXMudGVtcGxhdGU9ZSx0aGlzLl9kaWZmZXJzPWl9bmdPbkNoYW5nZXMoZSl7aWYoIXRoaXMuX2NvbHVtbnNEaWZmZXIpe2xldCBpPWUuY29sdW1ucyYmZS5jb2x1bW5zLmN1cnJlbnRWYWx1ZXx8W107dGhpcy5fY29sdW1uc0RpZmZlcj10aGlzLl9kaWZmZXJzLmZpbmQoaSkuY3JlYXRlKCksdGhpcy5fY29sdW1uc0RpZmZlci5kaWZmKGkpfX1nZXRDb2x1bW5zRGlmZigpe3JldHVybiB0aGlzLl9jb2x1bW5zRGlmZmVyLmRpZmYodGhpcy5jb2x1bW5zKX1leHRyYWN0Q2VsbFRlbXBsYXRlKGUpe3JldHVybiB0aGlzIGluc3RhbmNlb2YgJHBlP2UuaGVhZGVyQ2VsbC50ZW1wbGF0ZTp0aGlzIGluc3RhbmNlb2YgZWhlP2UuZm9vdGVyQ2VsbC50ZW1wbGF0ZTplLmNlbGwudGVtcGxhdGV9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oVmkpLE0oa2MpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixmZWF0dXJlczpbRnRdfSksbn0pKCkpLHdLZT1qRyhjbGFzcyBleHRlbmRzIEpwZXt9KSwkcGU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIHdLZXtjb25zdHJ1Y3RvcihlLGkscil7c3VwZXIoZSxpKSx0aGlzLl90YWJsZT1yfW5nT25DaGFuZ2VzKGUpe3N1cGVyLm5nT25DaGFuZ2VzKGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFZpKSxNKGtjKSxNKEdHLDgpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwiY2RrSGVhZGVyUm93RGVmIiwiIl1dLGlucHV0czp7Y29sdW1uczpbImNka0hlYWRlclJvd0RlZiIsImNvbHVtbnMiXSxzdGlja3k6WyJjZGtIZWFkZXJSb3dEZWZTdGlja3kiLCJzdGlja3kiXX0sZmVhdHVyZXM6W3R0LEZ0XX0pLG59KSgpLFNLZT1qRyhjbGFzcyBleHRlbmRzIEpwZXt9KSxlaGU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIFNLZXtjb25zdHJ1Y3RvcihlLGkscil7c3VwZXIoZSxpKSx0aGlzLl90YWJsZT1yfW5nT25DaGFuZ2VzKGUpe3N1cGVyLm5nT25DaGFuZ2VzKGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFZpKSxNKGtjKSxNKEdHLDgpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwiY2RrRm9vdGVyUm93RGVmIiwiIl1dLGlucHV0czp7Y29sdW1uczpbImNka0Zvb3RlclJvd0RlZiIsImNvbHVtbnMiXSxzdGlja3k6WyJjZGtGb290ZXJSb3dEZWZTdGlja3kiLCJzdGlja3kiXX0sZmVhdHVyZXM6W3R0LEZ0XX0pLG59KSgpLHRoZT0obmV3IHBlKCJDREtfU1BMIiksKCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W1pjXX0pLG59KSgpKSxuaGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W3RoZSxsbixsbl19KSxufSkoKSxXRz1jbGFzcyBleHRlbmRzIFB2e2NvbnN0cnVjdG9yKHQ9W10pe3N1cGVyKCksdGhpcy5fcmVuZGVyRGF0YT1uZXcgaHIoW10pLHRoaXMuX2ZpbHRlcj1uZXcgaHIoIiIpLHRoaXMuX2ludGVybmFsUGFnZUNoYW5nZXM9bmV3IGtlLHRoaXMuX3JlbmRlckNoYW5nZXNTdWJzY3JpcHRpb249bnVsbCx0aGlzLnNvcnRpbmdEYXRhQWNjZXNzb3I9KGUsaSk9PntsZXQgcj1lW2ldO2lmKGhIKHIpKXtsZXQgbz1OdW1iZXIocik7cmV0dXJuIG88OTAwNzE5OTI1NDc0MDk5MT9vOnJ9cmV0dXJuIHJ9LHRoaXMuc29ydERhdGE9KGUsaSk9PntsZXQgcj1pLmFjdGl2ZSxvPWkuZGlyZWN0aW9uO3JldHVybiByJiYiIiE9bz9lLnNvcnQoKHMsYSk9PntsZXQgbD10aGlzLnNvcnRpbmdEYXRhQWNjZXNzb3IocyxyKSxjPXRoaXMuc29ydGluZ0RhdGFBY2Nlc3NvcihhLHIpLHU9dHlwZW9mIGwsZD10eXBlb2YgYzt1IT09ZCYmKCJudW1iZXIiPT09dSYmKGwrPSIiKSwibnVtYmVyIj09PWQmJihjKz0iIikpO2xldCBwPTA7cmV0dXJuIG51bGwhPWwmJm51bGwhPWM/bD5jP3A9MTpsPGMmJihwPS0xKTpudWxsIT1sP3A9MTpudWxsIT1jJiYocD0tMSkscCooImFzYyI9PW8/MTotMSl9KTplfSx0aGlzLmZpbHRlclByZWRpY2F0ZT0oZSxpKT0+e2xldCByPU9iamVjdC5rZXlzKGUpLnJlZHVjZSgocyxhKT0+cytlW2FdKyJcdTI1ZWMiLCIiKS50b0xvd2VyQ2FzZSgpLG89aS50cmltKCkudG9Mb3dlckNhc2UoKTtyZXR1cm4tMSE9ci5pbmRleE9mKG8pfSx0aGlzLl9kYXRhPW5ldyBocih0KSx0aGlzLl91cGRhdGVDaGFuZ2VTdWJzY3JpcHRpb24oKX1nZXQgZGF0YSgpe3JldHVybiB0aGlzLl9kYXRhLnZhbHVlfXNldCBkYXRhKHQpe3Q9QXJyYXkuaXNBcnJheSh0KT90OltdLHRoaXMuX2RhdGEubmV4dCh0KSx0aGlzLl9yZW5kZXJDaGFuZ2VzU3Vic2NyaXB0aW9ufHx0aGlzLl9maWx0ZXJEYXRhKHQpfWdldCBmaWx0ZXIoKXtyZXR1cm4gdGhpcy5fZmlsdGVyLnZhbHVlfXNldCBmaWx0ZXIodCl7dGhpcy5fZmlsdGVyLm5leHQodCksdGhpcy5fcmVuZGVyQ2hhbmdlc1N1YnNjcmlwdGlvbnx8dGhpcy5fZmlsdGVyRGF0YSh0aGlzLmRhdGEpfWdldCBzb3J0KCl7cmV0dXJuIHRoaXMuX3NvcnR9c2V0IHNvcnQodCl7dGhpcy5fc29ydD10LHRoaXMuX3VwZGF0ZUNoYW5nZVN1YnNjcmlwdGlvbigpfWdldCBwYWdpbmF0b3IoKXtyZXR1cm4gdGhpcy5fcGFnaW5hdG9yfXNldCBwYWdpbmF0b3IodCl7dGhpcy5fcGFnaW5hdG9yPXQsdGhpcy5fdXBkYXRlQ2hhbmdlU3Vic2NyaXB0aW9uKCl9X3VwZGF0ZUNoYW5nZVN1YnNjcmlwdGlvbigpe2xldCB0PXRoaXMuX3NvcnQ/SnQodGhpcy5fc29ydC5zb3J0Q2hhbmdlLHRoaXMuX3NvcnQuaW5pdGlhbGl6ZWQpOlh0KG51bGwpLGU9dGhpcy5fcGFnaW5hdG9yP0p0KHRoaXMuX3BhZ2luYXRvci5wYWdlLHRoaXMuX2ludGVybmFsUGFnZUNoYW5nZXMsdGhpcy5fcGFnaW5hdG9yLmluaXRpYWxpemVkKTpYdChudWxsKSxyPUx0KFt0aGlzLl9kYXRhLHRoaXMuX2ZpbHRlcl0pLnBpcGUoTCgoW2FdKT0+dGhpcy5fZmlsdGVyRGF0YShhKSkpLG89THQoW3IsdF0pLnBpcGUoTCgoW2FdKT0+dGhpcy5fb3JkZXJEYXRhKGEpKSkscz1MdChbbyxlXSkucGlwZShMKChbYV0pPT50aGlzLl9wYWdlRGF0YShhKSkpO3RoaXMuX3JlbmRlckNoYW5nZXNTdWJzY3JpcHRpb24/LnVuc3Vic2NyaWJlKCksdGhpcy5fcmVuZGVyQ2hhbmdlc1N1YnNjcmlwdGlvbj1zLnN1YnNjcmliZShhPT50aGlzLl9yZW5kZXJEYXRhLm5leHQoYSkpfV9maWx0ZXJEYXRhKHQpe3JldHVybiB0aGlzLmZpbHRlcmVkRGF0YT1udWxsPT10aGlzLmZpbHRlcnx8IiI9PT10aGlzLmZpbHRlcj90OnQuZmlsdGVyKGU9PnRoaXMuZmlsdGVyUHJlZGljYXRlKGUsdGhpcy5maWx0ZXIpKSx0aGlzLnBhZ2luYXRvciYmdGhpcy5fdXBkYXRlUGFnaW5hdG9yKHRoaXMuZmlsdGVyZWREYXRhLmxlbmd0aCksdGhpcy5maWx0ZXJlZERhdGF9X29yZGVyRGF0YSh0KXtyZXR1cm4gdGhpcy5zb3J0P3RoaXMuc29ydERhdGEodC5zbGljZSgpLHRoaXMuc29ydCk6dH1fcGFnZURhdGEodCl7aWYoIXRoaXMucGFnaW5hdG9yKXJldHVybiB0O2xldCBlPXRoaXMucGFnaW5hdG9yLnBhZ2VJbmRleCp0aGlzLnBhZ2luYXRvci5wYWdlU2l6ZTtyZXR1cm4gdC5zbGljZShlLGUrdGhpcy5wYWdpbmF0b3IucGFnZVNpemUpfV91cGRhdGVQYWdpbmF0b3IodCl7UHJvbWlzZS5yZXNvbHZlKCkudGhlbigoKT0+e2xldCBlPXRoaXMucGFnaW5hdG9yO2lmKGUmJihlLmxlbmd0aD10LGUucGFnZUluZGV4PjApKXtsZXQgaT1NYXRoLmNlaWwoZS5sZW5ndGgvZS5wYWdlU2l6ZSktMXx8MCxyPU1hdGgubWluKGUucGFnZUluZGV4LGkpO3IhPT1lLnBhZ2VJbmRleCYmKGUucGFnZUluZGV4PXIsdGhpcy5faW50ZXJuYWxQYWdlQ2hhbmdlcy5uZXh0KCkpfX0pfWNvbm5lY3QoKXtyZXR1cm4gdGhpcy5fcmVuZGVyQ2hhbmdlc1N1YnNjcmlwdGlvbnx8dGhpcy5fdXBkYXRlQ2hhbmdlU3Vic2NyaXB0aW9uKCksdGhpcy5fcmVuZGVyRGF0YX1kaXNjb25uZWN0KCl7dGhpcy5fcmVuZGVyQ2hhbmdlc1N1YnNjcmlwdGlvbj8udW5zdWJzY3JpYmUoKSx0aGlzLl9yZW5kZXJDaGFuZ2VzU3Vic2NyaXB0aW9uPW51bGx9fSxKaz1jbGFzcyBleHRlbmRzIFdHe30sREtlPVsiZGlhbG9nUG9wdXAiXSxBS2U9WyJodWVTbGlkZXIiXSxJS2U9WyJhbHBoYVNsaWRlciJdO2Z1bmN0aW9uIFBLZShuLHQpe2lmKDEmbiYmTygwLCJkaXYiKSwyJm4pe2xldCBlPVMoKTtReCgiYXJyb3cgYXJyb3ctIixlLmNwVXNlUG9zaXRpb24sIiIpLFB0KCJ0b3AiLGUuYXJyb3dUb3AsInB4Iil9fWZ1bmN0aW9uIFJLZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMjgpLFAoIm5ld1ZhbHVlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uQ29sb3JDaGFuZ2UocikpfSkoImRyYWdTdGFydCIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uRHJhZ1N0YXJ0KCJzYXR1cmF0aW9uLWxpZ2h0bmVzcyIpKX0pKCJkcmFnRW5kIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkub25EcmFnRW5kKCJzYXR1cmF0aW9uLWxpZ2h0bmVzcyIpKX0pLE8oMSwiZGl2IiwxNCksdigpfWlmKDImbil7bGV0IGU9UygpO1B0KCJiYWNrZ3JvdW5kLWNvbG9yIixlLmh1ZVNsaWRlckNvbG9yKSx5KCJyZ1giLDEpKCJyZ1kiLDEpLEMoMSksUHQoInRvcCIsbnVsbD09ZS5zbGlkZXI/bnVsbDplLnNsaWRlci52LCJweCIpKCJsZWZ0IixudWxsPT1lLnNsaWRlcj9udWxsOmUuc2xpZGVyLnMsInB4Iil9fWZ1bmN0aW9uIE9LZShuLHQpezEmbiYmKEluKCksXygwLCJzdmciLDI5KSxPKDEsInBhdGgiLDMwKSgyLCJwYXRoIiwzMSksdigpKX1mdW5jdGlvbiBrS2Uobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJidXR0b24iLDMyKSxQKCJjbGljayIsZnVuY3Rpb24ocil7b2UoZSk7bGV0IG89UygpO3JldHVybiBzZShvLm9uQWRkUHJlc2V0Q29sb3IocixvLnNlbGVjdGVkQ29sb3IpKX0pLEEoMSksdigpfWlmKDImbil7bGV0IGU9UygpO0RhKGUuY3BBZGRDb2xvckJ1dHRvbkNsYXNzKSx5KCJkaXNhYmxlZCIsZS5jcFByZXNldENvbG9ycyYmZS5jcFByZXNldENvbG9ycy5sZW5ndGg+PWUuY3BNYXhQcmVzZXRDb2xvcnNMZW5ndGgpLEMoMSksamUoIiAiLGUuY3BBZGRDb2xvckJ1dHRvblRleHQsIiAiKX19ZnVuY3Rpb24gRktlKG4sdCl7MSZuJiZPKDAsImRpdiIsMzMpfWZ1bmN0aW9uIE5LZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImlucHV0IiwzOSksUCgia2V5dXAuZW50ZXIiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKDIpLm9uQWNjZXB0Q29sb3IocikpfSkoIm5ld1ZhbHVlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygyKS5vbkFscGhhSW5wdXQocikpfSksdigpfWlmKDImbil7bGV0IGU9UygyKTt5KCJyZyIsMSkoInZhbHVlIixudWxsPT1lLmNteWtUZXh0P251bGw6ZS5jbXlrVGV4dC5hKX19ZnVuY3Rpb24gTEtlKG4sdCl7MSZuJiYoXygwLCJkaXYiKSxBKDEsIkEiKSx2KCkpfWZ1bmN0aW9uIEJLZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMzQpKDEsImRpdiIsMzUpKDIsImlucHV0IiwzNiksUCgia2V5dXAuZW50ZXIiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25BY2NlcHRDb2xvcihyKSl9KSgibmV3VmFsdWUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25DeWFuSW5wdXQocikpfSksdigpLF8oMywiaW5wdXQiLDM2KSxQKCJrZXl1cC5lbnRlciIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5vbkFjY2VwdENvbG9yKHIpKX0pKCJuZXdWYWx1ZSIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5vbk1hZ2VudGFJbnB1dChyKSl9KSx2KCksXyg0LCJpbnB1dCIsMzYpLFAoImtleXVwLmVudGVyIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uQWNjZXB0Q29sb3IocikpfSkoIm5ld1ZhbHVlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uWWVsbG93SW5wdXQocikpfSksdigpLF8oNSwiaW5wdXQiLDM2KSxQKCJrZXl1cC5lbnRlciIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5vbkFjY2VwdENvbG9yKHIpKX0pKCJuZXdWYWx1ZSIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoKS5vbkJsYWNrSW5wdXQocikpfSksdigpLEUoNixOS2UsMSwyLCJpbnB1dCIsMzcpLHYoKSxfKDcsImRpdiIsMzUpKDgsImRpdiIpLEEoOSwiQyIpLHYoKSxfKDEwLCJkaXYiKSxBKDExLCJNIiksdigpLF8oMTIsImRpdiIpLEEoMTMsIlkiKSx2KCksXygxNCwiZGl2IiksQSgxNSwiSyIpLHYoKSxFKDE2LExLZSwyLDAsImRpdiIsMzgpLHYoKSgpfWlmKDImbil7bGV0IGU9UygpO1B0KCJkaXNwbGF5IiwzIT09ZS5mb3JtYXQ/Im5vbmUiOiJibG9jayIpLEMoMikseSgicmciLDEwMCkoInZhbHVlIixudWxsPT1lLmNteWtUZXh0P251bGw6ZS5jbXlrVGV4dC5jKSxDKDEpLHkoInJnIiwxMDApKCJ2YWx1ZSIsbnVsbD09ZS5jbXlrVGV4dD9udWxsOmUuY215a1RleHQubSksQygxKSx5KCJyZyIsMTAwKSgidmFsdWUiLG51bGw9PWUuY215a1RleHQ/bnVsbDplLmNteWtUZXh0LnkpLEMoMSkseSgicmciLDEwMCkoInZhbHVlIixudWxsPT1lLmNteWtUZXh0P251bGw6ZS5jbXlrVGV4dC5rKSxDKDEpLHkoIm5nSWYiLCJkaXNhYmxlZCIhPT1lLmNwQWxwaGFDaGFubmVsKSxDKDEwKSx5KCJuZ0lmIiwiZGlzYWJsZWQiIT09ZS5jcEFscGhhQ2hhbm5lbCl9fWZ1bmN0aW9uIFZLZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImlucHV0IiwzOSksUCgia2V5dXAuZW50ZXIiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKDIpLm9uQWNjZXB0Q29sb3IocikpfSkoIm5ld1ZhbHVlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygyKS5vbkFscGhhSW5wdXQocikpfSksdigpfWlmKDImbil7bGV0IGU9UygyKTt5KCJyZyIsMSkoInZhbHVlIixudWxsPT1lLmhzbGFUZXh0P251bGw6ZS5oc2xhVGV4dC5hKX19ZnVuY3Rpb24gSEtlKG4sdCl7MSZuJiYoXygwLCJkaXYiKSxBKDEsIkEiKSx2KCkpfWZ1bmN0aW9uIFVLZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsNDApKDEsImRpdiIsMzUpKDIsImlucHV0Iiw0MSksUCgia2V5dXAuZW50ZXIiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25BY2NlcHRDb2xvcihyKSl9KSgibmV3VmFsdWUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25IdWVJbnB1dChyKSl9KSx2KCksXygzLCJpbnB1dCIsMzYpLFAoImtleXVwLmVudGVyIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uQWNjZXB0Q29sb3IocikpfSkoIm5ld1ZhbHVlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uU2F0dXJhdGlvbklucHV0KHIpKX0pLHYoKSxfKDQsImlucHV0IiwzNiksUCgia2V5dXAuZW50ZXIiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25BY2NlcHRDb2xvcihyKSl9KSgibmV3VmFsdWUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25MaWdodG5lc3NJbnB1dChyKSl9KSx2KCksRSg1LFZLZSwxLDIsImlucHV0IiwzNyksdigpLF8oNiwiZGl2IiwzNSkoNywiZGl2IiksQSg4LCJIIiksdigpLF8oOSwiZGl2IiksQSgxMCwiUyIpLHYoKSxfKDExLCJkaXYiKSxBKDEyLCJMIiksdigpLEUoMTMsSEtlLDIsMCwiZGl2IiwzOCksdigpKCl9aWYoMiZuKXtsZXQgZT1TKCk7UHQoImRpc3BsYXkiLDIhPT1lLmZvcm1hdD8ibm9uZSI6ImJsb2NrIiksQygyKSx5KCJyZyIsMzYwKSgidmFsdWUiLG51bGw9PWUuaHNsYVRleHQ/bnVsbDplLmhzbGFUZXh0LmgpLEMoMSkseSgicmciLDEwMCkoInZhbHVlIixudWxsPT1lLmhzbGFUZXh0P251bGw6ZS5oc2xhVGV4dC5zKSxDKDEpLHkoInJnIiwxMDApKCJ2YWx1ZSIsbnVsbD09ZS5oc2xhVGV4dD9udWxsOmUuaHNsYVRleHQubCksQygxKSx5KCJuZ0lmIiwiZGlzYWJsZWQiIT09ZS5jcEFscGhhQ2hhbm5lbCksQyg4KSx5KCJuZ0lmIiwiZGlzYWJsZWQiIT09ZS5jcEFscGhhQ2hhbm5lbCl9fWZ1bmN0aW9uIHpLZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImlucHV0IiwzOSksUCgia2V5dXAuZW50ZXIiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKDIpLm9uQWNjZXB0Q29sb3IocikpfSkoIm5ld1ZhbHVlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygyKS5vbkFscGhhSW5wdXQocikpfSksdigpfWlmKDImbil7bGV0IGU9UygyKTt5KCJyZyIsMSkoInZhbHVlIixudWxsPT1lLnJnYmFUZXh0P251bGw6ZS5yZ2JhVGV4dC5hKX19ZnVuY3Rpb24gaktlKG4sdCl7MSZuJiYoXygwLCJkaXYiKSxBKDEsIkEiKSx2KCkpfWZ1bmN0aW9uIEdLZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsNDIpKDEsImRpdiIsMzUpKDIsImlucHV0Iiw0MyksUCgia2V5dXAuZW50ZXIiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25BY2NlcHRDb2xvcihyKSl9KSgibmV3VmFsdWUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25SZWRJbnB1dChyKSl9KSx2KCksXygzLCJpbnB1dCIsNDMpLFAoImtleXVwLmVudGVyIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uQWNjZXB0Q29sb3IocikpfSkoIm5ld1ZhbHVlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uR3JlZW5JbnB1dChyKSl9KSx2KCksXyg0LCJpbnB1dCIsNDMpLFAoImtleXVwLmVudGVyIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uQWNjZXB0Q29sb3IocikpfSkoIm5ld1ZhbHVlIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uQmx1ZUlucHV0KHIpKX0pLHYoKSxFKDUsektlLDEsMiwiaW5wdXQiLDM3KSx2KCksXyg2LCJkaXYiLDM1KSg3LCJkaXYiKSxBKDgsIlIiKSx2KCksXyg5LCJkaXYiKSxBKDEwLCJHIiksdigpLF8oMTEsImRpdiIpLEEoMTIsIkIiKSx2KCksRSgxMyxqS2UsMiwwLCJkaXYiLDM4KSx2KCkoKX1pZigyJm4pe2xldCBlPVMoKTtQdCgiZGlzcGxheSIsMSE9PWUuZm9ybWF0PyJub25lIjoiYmxvY2siKSxDKDIpLHkoInJnIiwyNTUpKCJ2YWx1ZSIsbnVsbD09ZS5yZ2JhVGV4dD9udWxsOmUucmdiYVRleHQuciksQygxKSx5KCJyZyIsMjU1KSgidmFsdWUiLG51bGw9PWUucmdiYVRleHQ/bnVsbDplLnJnYmFUZXh0LmcpLEMoMSkseSgicmciLDI1NSkoInZhbHVlIixudWxsPT1lLnJnYmFUZXh0P251bGw6ZS5yZ2JhVGV4dC5iKSxDKDEpLHkoIm5nSWYiLCJkaXNhYmxlZCIhPT1lLmNwQWxwaGFDaGFubmVsKSxDKDgpLHkoIm5nSWYiLCJkaXNhYmxlZCIhPT1lLmNwQWxwaGFDaGFubmVsKX19ZnVuY3Rpb24gV0tlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiaW5wdXQiLDM5KSxQKCJrZXl1cC5lbnRlciIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoMikub25BY2NlcHRDb2xvcihyKSl9KSgibmV3VmFsdWUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKDIpLm9uQWxwaGFJbnB1dChyKSl9KSx2KCl9aWYoMiZuKXtsZXQgZT1TKDIpO3koInJnIiwxKSgidmFsdWUiLGUuaGV4QWxwaGEpfX1mdW5jdGlvbiBxS2Uobix0KXsxJm4mJihfKDAsImRpdiIpLEEoMSwiQSIpLHYoKSl9ZnVuY3Rpb24gWUtlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2Iiw0NCkoMSwiZGl2IiwzNSkoMiwiaW5wdXQiLDQ1KSxQKCJibHVyIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkub25IZXhJbnB1dChudWxsKSl9KSgia2V5dXAuZW50ZXIiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25BY2NlcHRDb2xvcihyKSl9KSgibmV3VmFsdWUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25IZXhJbnB1dChyKSl9KSx2KCksRSgzLFdLZSwxLDIsImlucHV0IiwzNyksdigpLF8oNCwiZGl2IiwzNSkoNSwiZGl2IiksQSg2LCJIZXgiKSx2KCksRSg3LHFLZSwyLDAsImRpdiIsMzgpLHYoKSgpfWlmKDImbil7bGV0IGU9UygpO1B0KCJkaXNwbGF5IiwwIT09ZS5mb3JtYXQ/Im5vbmUiOiJibG9jayIpLGV0KCJoZXgtYWxwaGEiLCJmb3JjZWQiPT09ZS5jcEFscGhhQ2hhbm5lbCksQygyKSx5KCJ2YWx1ZSIsZS5oZXhUZXh0KSxDKDEpLHkoIm5nSWYiLCJmb3JjZWQiPT09ZS5jcEFscGhhQ2hhbm5lbCksQyg0KSx5KCJuZ0lmIiwiZm9yY2VkIj09PWUuY3BBbHBoYUNoYW5uZWwpfX1mdW5jdGlvbiBYS2Uobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJpbnB1dCIsMzkpLFAoImtleXVwLmVudGVyIixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygyKS5vbkFjY2VwdENvbG9yKHIpKX0pKCJuZXdWYWx1ZSIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoMikub25BbHBoYUlucHV0KHIpKX0pLHYoKX1pZigyJm4pe2xldCBlPVMoMik7eSgicmciLDEpKCJ2YWx1ZSIsbnVsbD09ZS5oc2xhVGV4dD9udWxsOmUuaHNsYVRleHQuYSl9fWZ1bmN0aW9uIFFLZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsNDYpKDEsImRpdiIsMzUpKDIsImlucHV0IiwzNiksUCgia2V5dXAuZW50ZXIiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25BY2NlcHRDb2xvcihyKSl9KSgibmV3VmFsdWUiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25WYWx1ZUlucHV0KHIpKX0pLHYoKSxFKDMsWEtlLDEsMiwiaW5wdXQiLDM3KSx2KCksXyg0LCJkaXYiLDM1KSg1LCJkaXYiKSxBKDYsIlYiKSx2KCksXyg3LCJkaXYiKSxBKDgsIkEiKSx2KCkoKSgpfWlmKDImbil7bGV0IGU9UygpO0MoMikseSgicmciLDEwMCkoInZhbHVlIixudWxsPT1lLmhzbGFUZXh0P251bGw6ZS5oc2xhVGV4dC5sKSxDKDEpLHkoIm5nSWYiLCJkaXNhYmxlZCIhPT1lLmNwQWxwaGFDaGFubmVsKX19ZnVuY3Rpb24gS0tlKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2Iiw0NykoMSwic3BhbiIsNDgpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkub25Gb3JtYXRUb2dnbGUoLTEpKX0pLHYoKSxfKDIsInNwYW4iLDQ4KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLm9uRm9ybWF0VG9nZ2xlKDEpKX0pLHYoKSgpfX1mdW5jdGlvbiBaS2Uobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJzcGFuIiw1NSksUCgiY2xpY2siLGZ1bmN0aW9uKHIpe29lKGUpO2xldCBvPVMoKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoMykub25SZW1vdmVQcmVzZXRDb2xvcihyLG8pKX0pLHYoKX0yJm4mJkRhKFMoNCkuY3BSZW1vdmVDb2xvckJ1dHRvbkNsYXNzKX1mdW5jdGlvbiBKS2Uobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDUzKSxQKCJjbGljayIsZnVuY3Rpb24oKXtsZXQgbz1vZShlKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoMykuc2V0Q29sb3JGcm9tU3RyaW5nKG8pKX0pLEUoMSxaS2UsMSwzLCJzcGFuIiw1NCksdigpfWlmKDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKDMpO1B0KCJiYWNrZ3JvdW5kLWNvbG9yIixlKSxDKDEpLHkoIm5nSWYiLGkuY3BBZGRDb2xvckJ1dHRvbil9fWZ1bmN0aW9uICRLZShuLHQpe2lmKDEmbiYmKF8oMCwiZGl2IiksRSgxLEpLZSwyLDMsImRpdiIsNTIpLHYoKSksMiZuKXtsZXQgZT1TKDIpO0RhKGUuY3BQcmVzZXRDb2xvcnNDbGFzcyksQygxKSx5KCJuZ0Zvck9mIixlLmNwUHJlc2V0Q29sb3JzKX19ZnVuY3Rpb24gZVplKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiKSxBKDEpLHYoKSksMiZuKXtsZXQgZT1TKDIpO0RhKGUuY3BQcmVzZXRFbXB0eU1lc3NhZ2VDbGFzcyksQygxKSx5dChlLmNwUHJlc2V0RW1wdHlNZXNzYWdlKX19ZnVuY3Rpb24gdFplKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDQ5KSxPKDEsImhyIiksXygyLCJkaXYiLDUwKSxBKDMpLHYoKSxFKDQsJEtlLDIsNCwiZGl2Iiw1MSksRSg1LGVaZSwyLDQsImRpdiIsNTEpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygzKSx5dChlLmNwUHJlc2V0TGFiZWwpLEMoMSkseSgibmdJZiIsbnVsbD09ZS5jcFByZXNldENvbG9ycz9udWxsOmUuY3BQcmVzZXRDb2xvcnMubGVuZ3RoKSxDKDEpLHkoIm5nSWYiLCEobnVsbCE9ZS5jcFByZXNldENvbG9ycyYmZS5jcFByZXNldENvbG9ycy5sZW5ndGgpJiZlLmNwQWRkQ29sb3JCdXR0b24pfX1mdW5jdGlvbiBuWmUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJidXR0b24iLDU4KSxQKCJjbGljayIsZnVuY3Rpb24ocil7cmV0dXJuIG9lKGUpLHNlKFMoMikub25DYW5jZWxDb2xvcihyKSl9KSxBKDEpLHYoKX1pZigyJm4pe2xldCBlPVMoMik7RGEoZS5jcENhbmNlbEJ1dHRvbkNsYXNzKSxDKDEpLHl0KGUuY3BDYW5jZWxCdXR0b25UZXh0KX19ZnVuY3Rpb24gaVplKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiYnV0dG9uIiw1OCksUCgiY2xpY2siLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKDIpLm9uQWNjZXB0Q29sb3IocikpfSksQSgxKSx2KCl9aWYoMiZuKXtsZXQgZT1TKDIpO0RhKGUuY3BPS0J1dHRvbkNsYXNzKSxDKDEpLHl0KGUuY3BPS0J1dHRvblRleHQpfX1mdW5jdGlvbiByWmUobix0KXtpZigxJm4mJihfKDAsImRpdiIsNTYpLEUoMSxuWmUsMiw0LCJidXR0b24iLDU3KSxFKDIsaVplLDIsNCwiYnV0dG9uIiw1NyksdigpKSwyJm4pe2xldCBlPVMoKTtDKDEpLHkoIm5nSWYiLGUuY3BDYW5jZWxCdXR0b24pLEMoMSkseSgibmdJZiIsZS5jcE9LQnV0dG9uKX19ZnVuY3Rpb24gb1plKG4sdCl7MSZuJiZOaSgwKX1mdW5jdGlvbiBzWmUobix0KXtpZigxJm4mJihfKDAsImRpdiIsNTkpLEUoMSxvWmUsMSwwLCJuZy1jb250YWluZXIiLDYwKSx2KCkpLDImbil7bGV0IGU9UygpO0MoMSkseSgibmdUZW1wbGF0ZU91dGxldCIsZS5jcEV4dHJhVGVtcGxhdGUpfX12YXIgd3M9KCgpPT4oZnVuY3Rpb24obil7bltuLkhFWD0wXT0iSEVYIixuW24uUkdCQT0xXT0iUkdCQSIsbltuLkhTTEE9Ml09IkhTTEEiLG5bbi5DTVlLPTNdPSJDTVlLIn0od3N8fCh3cz17fSkpLHdzKSkoKSxodT1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXt0aGlzLnI9dCx0aGlzLmc9ZSx0aGlzLmI9aSx0aGlzLmE9cn19LFJiPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe3RoaXMuaD10LHRoaXMucz1lLHRoaXMudj1pLHRoaXMuYT1yfX0sUnA9Y2xhc3N7Y29uc3RydWN0b3IodCxlLGkscil7dGhpcy5oPXQsdGhpcy5zPWUsdGhpcy5sPWksdGhpcy5hPXJ9fSx0MD1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyLG89MSl7dGhpcy5jPXQsdGhpcy5tPWUsdGhpcy55PWksdGhpcy5rPXIsdGhpcy5hPW99fSxjWmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMubmV3VmFsdWU9bmV3IEd9aW5wdXRDaGFuZ2UoZSl7bGV0IGk9ZS50YXJnZXQudmFsdWU7aWYodm9pZCAwPT09dGhpcy5yZyl0aGlzLm5ld1ZhbHVlLmVtaXQoaSk7ZWxzZXtsZXQgcj1wYXJzZUZsb2F0KGkpO3RoaXMubmV3VmFsdWUuZW1pdCh7djpyLHJnOnRoaXMucmd9KX19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyIiLCJ0ZXh0IiwiIl1dLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgiaW5wdXQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLmlucHV0Q2hhbmdlKG8pfSl9LGlucHV0czp7cmc6InJnIix0ZXh0OiJ0ZXh0In0sb3V0cHV0czp7bmV3VmFsdWU6Im5ld1ZhbHVlIn19KSxufSkoKSx1WmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLmVsUmVmPWUsdGhpcy5kcmFnRW5kPW5ldyBHLHRoaXMuZHJhZ1N0YXJ0PW5ldyBHLHRoaXMubmV3VmFsdWU9bmV3IEcsdGhpcy5saXN0ZW5lck1vdmU9aT0+dGhpcy5tb3ZlKGkpLHRoaXMubGlzdGVuZXJTdG9wPSgpPT50aGlzLnN0b3AoKX1tb3VzZURvd24oZSl7dGhpcy5zdGFydChlKX10b3VjaFN0YXJ0KGUpe3RoaXMuc3RhcnQoZSl9bW92ZShlKXtlLnByZXZlbnREZWZhdWx0KCksdGhpcy5zZXRDdXJzb3IoZSl9c3RhcnQoZSl7dGhpcy5zZXRDdXJzb3IoZSksZS5zdG9wUHJvcGFnYXRpb24oKSxkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCJtb3VzZXVwIix0aGlzLmxpc3RlbmVyU3RvcCksZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigidG91Y2hlbmQiLHRoaXMubGlzdGVuZXJTdG9wKSxkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCJtb3VzZW1vdmUiLHRoaXMubGlzdGVuZXJNb3ZlKSxkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCJ0b3VjaG1vdmUiLHRoaXMubGlzdGVuZXJNb3ZlKSx0aGlzLmRyYWdTdGFydC5lbWl0KCl9c3RvcCgpe2RvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoIm1vdXNldXAiLHRoaXMubGlzdGVuZXJTdG9wKSxkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJ0b3VjaGVuZCIsdGhpcy5saXN0ZW5lclN0b3ApLGRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoIm1vdXNlbW92ZSIsdGhpcy5saXN0ZW5lck1vdmUpLGRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoInRvdWNobW92ZSIsdGhpcy5saXN0ZW5lck1vdmUpLHRoaXMuZHJhZ0VuZC5lbWl0KCl9Z2V0WChlKXtsZXQgaT10aGlzLmVsUmVmLm5hdGl2ZUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7cmV0dXJuKHZvaWQgMCE9PWUucGFnZVg/ZS5wYWdlWDplLnRvdWNoZXNbMF0ucGFnZVgpLWkubGVmdC13aW5kb3cucGFnZVhPZmZzZXR9Z2V0WShlKXtsZXQgaT10aGlzLmVsUmVmLm5hdGl2ZUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7cmV0dXJuKHZvaWQgMCE9PWUucGFnZVk/ZS5wYWdlWTplLnRvdWNoZXNbMF0ucGFnZVkpLWkudG9wLXdpbmRvdy5wYWdlWU9mZnNldH1zZXRDdXJzb3IoZSl7bGV0IGk9dGhpcy5lbFJlZi5uYXRpdmVFbGVtZW50Lm9mZnNldFdpZHRoLHI9dGhpcy5lbFJlZi5uYXRpdmVFbGVtZW50Lm9mZnNldEhlaWdodCxvPU1hdGgubWF4KDAsTWF0aC5taW4odGhpcy5nZXRYKGUpLGkpKSxzPU1hdGgubWF4KDAsTWF0aC5taW4odGhpcy5nZXRZKGUpLHIpKTt2b2lkIDAhPT10aGlzLnJnWCYmdm9pZCAwIT09dGhpcy5yZ1k/dGhpcy5uZXdWYWx1ZS5lbWl0KHtzOm8vaSx2OjEtcy9yLHJnWDp0aGlzLnJnWCxyZ1k6dGhpcy5yZ1l9KTp2b2lkIDA9PT10aGlzLnJnWCYmdm9pZCAwIT09dGhpcy5yZ1k/dGhpcy5uZXdWYWx1ZS5lbWl0KHt2OnMvcixyZ1k6dGhpcy5yZ1l9KTp2b2lkIDAhPT10aGlzLnJnWCYmdm9pZCAwPT09dGhpcy5yZ1kmJnRoaXMubmV3VmFsdWUuZW1pdCh7djpvL2kscmdYOnRoaXMucmdYfSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oUmUpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwic2xpZGVyIiwiIl1dLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgibW91c2Vkb3duIixmdW5jdGlvbihvKXtyZXR1cm4gaS5tb3VzZURvd24obyl9KSgidG91Y2hzdGFydCIsZnVuY3Rpb24obyl7cmV0dXJuIGkudG91Y2hTdGFydChvKX0pfSxpbnB1dHM6e3JnWDoicmdYIixyZ1k6InJnWSIsc2xpZGVyOiJzbGlkZXIifSxvdXRwdXRzOntkcmFnRW5kOiJkcmFnRW5kIixkcmFnU3RhcnQ6ImRyYWdTdGFydCIsbmV3VmFsdWU6Im5ld1ZhbHVlIn19KSxufSkoKSwkaz1jbGFzc3tjb25zdHJ1Y3Rvcih0LGUsaSxyKXt0aGlzLmg9dCx0aGlzLnM9ZSx0aGlzLnY9aSx0aGlzLmE9cn19LGVGPWNsYXNze2NvbnN0cnVjdG9yKHQsZSxpLHIpe3RoaXMuaD10LHRoaXMucz1lLHRoaXMudj1pLHRoaXMuYT1yfX0scUc9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuYWN0aXZlPW51bGx9c2V0QWN0aXZlKGUpe3RoaXMuYWN0aXZlJiZ0aGlzLmFjdGl2ZSE9PWUmJiJpbmxpbmUiIT09dGhpcy5hY3RpdmUuY3BEaWFsb2dEaXNwbGF5JiZ0aGlzLmFjdGl2ZS5jbG9zZURpYWxvZygpLHRoaXMuYWN0aXZlPWV9aHN2YTJoc2xhKGUpe2xldCBpPWUuaCxyPWUucyxvPWUudixzPWUuYTtpZigwPT09bylyZXR1cm4gbmV3IFJwKGksMCwwLHMpO2lmKDA9PT1yJiYxPT09bylyZXR1cm4gbmV3IFJwKGksMSwxLHMpO3tsZXQgYT1vKigyLXIpLzI7cmV0dXJuIG5ldyBScChpLG8qci8oMS1NYXRoLmFicygyKmEtMSkpLGEscyl9fWhzbGEyaHN2YShlKXtsZXQgaT1NYXRoLm1pbihlLmgsMSkscj1NYXRoLm1pbihlLnMsMSksbz1NYXRoLm1pbihlLmwsMSkscz1NYXRoLm1pbihlLmEsMSk7aWYoMD09PW8pcmV0dXJuIG5ldyBSYihpLDAsMCxzKTt7bGV0IGE9bytyKigxLU1hdGguYWJzKDIqby0xKSkvMjtyZXR1cm4gbmV3IFJiKGksMiooYS1vKS9hLGEscyl9fWhzdmFUb1JnYmEoZSl7bGV0IGkscixvLHM9ZS5oLGE9ZS5zLGw9ZS52LGM9ZS5hLHU9TWF0aC5mbG9vcig2KnMpLGQ9NipzLXUscD1sKigxLWEpLGg9bCooMS1kKmEpLGY9bCooMS0oMS1kKSphKTtzd2l0Y2godSU2KXtjYXNlIDA6aT1sLHI9ZixvPXA7YnJlYWs7Y2FzZSAxOmk9aCxyPWwsbz1wO2JyZWFrO2Nhc2UgMjppPXAscj1sLG89ZjticmVhaztjYXNlIDM6aT1wLHI9aCxvPWw7YnJlYWs7Y2FzZSA0Omk9ZixyPXAsbz1sO2JyZWFrO2Nhc2UgNTppPWwscj1wLG89aDticmVhaztkZWZhdWx0Omk9MCxyPTAsbz0wfXJldHVybiBuZXcgaHUoaSxyLG8sYyl9Y215a1RvUmdiKGUpe3JldHVybiBuZXcgaHUoKDEtZS5jKSooMS1lLmspLCgxLWUubSkqKDEtZS5rKSwoMS1lLnkpKigxLWUuayksZS5hKX1yZ2JhVG9DbXlrKGUpe2xldCBpPTEtTWF0aC5tYXgoZS5yLGUuZyxlLmIpO3JldHVybiAxPT09aT9uZXcgdDAoMCwwLDAsMSxlLmEpOm5ldyB0MCgoMS1lLnItaSkvKDEtaSksKDEtZS5nLWkpLygxLWkpLCgxLWUuYi1pKS8oMS1pKSxpLGUuYSl9cmdiYVRvSHN2YShlKXtsZXQgaSxyLG89TWF0aC5taW4oZS5yLDEpLHM9TWF0aC5taW4oZS5nLDEpLGE9TWF0aC5taW4oZS5iLDEpLGw9TWF0aC5taW4oZS5hLDEpLGM9TWF0aC5tYXgobyxzLGEpLHU9TWF0aC5taW4obyxzLGEpLGQ9YyxwPWMtdTtpZihyPTA9PT1jPzA6cC9jLGM9PT11KWk9MDtlbHNle3N3aXRjaChjKXtjYXNlIG86aT0ocy1hKS9wKyhzPGE/NjowKTticmVhaztjYXNlIHM6aT0oYS1vKS9wKzI7YnJlYWs7Y2FzZSBhOmk9KG8tcykvcCs0O2JyZWFrO2RlZmF1bHQ6aT0wfWkvPTZ9cmV0dXJuIG5ldyBSYihpLHIsZCxsKX1yZ2JhVG9IZXgoZSxpKXtsZXQgcj0iIyIrKDE2Nzc3MjE2fGUucjw8MTZ8ZS5nPDw4fGUuYikudG9TdHJpbmcoMTYpLnN1YnN0cigxKTtyZXR1cm4gaSYmKHIrPSgyNTZ8TWF0aC5yb3VuZCgyNTUqZS5hKSkudG9TdHJpbmcoMTYpLnN1YnN0cigxKSkscn1ub3JtYWxpemVDTVlLKGUpe3JldHVybiBuZXcgdDAoZS5jLzEwMCxlLm0vMTAwLGUueS8xMDAsZS5rLzEwMCxlLmEpfWRlbm9ybWFsaXplQ01ZSyhlKXtyZXR1cm4gbmV3IHQwKE1hdGguZmxvb3IoMTAwKmUuYyksTWF0aC5mbG9vcigxMDAqZS5tKSxNYXRoLmZsb29yKDEwMCplLnkpLE1hdGguZmxvb3IoMTAwKmUuayksZS5hKX1kZW5vcm1hbGl6ZVJHQkEoZSl7cmV0dXJuIG5ldyBodShNYXRoLnJvdW5kKDI1NSplLnIpLE1hdGgucm91bmQoMjU1KmUuZyksTWF0aC5yb3VuZCgyNTUqZS5iKSxlLmEpfXN0cmluZ1RvSHN2YShlPSIiLGk9ITEpe2xldCByPW51bGw7ZT0oZXx8IiIpLnRvTG93ZXJDYXNlKCk7bGV0IG89W3tyZTovKHJnYilhP1woXHMqKFxkezEsM30pXHMqLFxzKihcZHsxLDN9KVxzKiU/LFxzKihcZHsxLDN9KVxzKiU/KD86LFxzKihcZCsoPzpcLlxkKyk/KVxzKik/XCkvLHBhcnNlOmZ1bmN0aW9uKHMpe3JldHVybiBuZXcgaHUocGFyc2VJbnQoc1syXSwxMCkvMjU1LHBhcnNlSW50KHNbM10sMTApLzI1NSxwYXJzZUludChzWzRdLDEwKS8yNTUsaXNOYU4ocGFyc2VGbG9hdChzWzVdKSk/MTpwYXJzZUZsb2F0KHNbNV0pKX19LHtyZTovKGhzbClhP1woXHMqKFxkezEsM30pXHMqLFxzKihcZHsxLDN9KSVccyosXHMqKFxkezEsM30pJVxzKig/OixccyooXGQrKD86XC5cZCspPylccyopP1wpLyxwYXJzZTpmdW5jdGlvbihzKXtyZXR1cm4gbmV3IFJwKHBhcnNlSW50KHNbMl0sMTApLzM2MCxwYXJzZUludChzWzNdLDEwKS8xMDAscGFyc2VJbnQoc1s0XSwxMCkvMTAwLGlzTmFOKHBhcnNlRmxvYXQoc1s1XSkpPzE6cGFyc2VGbG9hdChzWzVdKSl9fV07by5wdXNoKGk/e3JlOi8jKFthLWZBLUYwLTldezJ9KShbYS1mQS1GMC05XXsyfSkoW2EtZkEtRjAtOV17Mn0pKFthLWZBLUYwLTldezJ9KT8kLyxwYXJzZTpmdW5jdGlvbihzKXtyZXR1cm4gbmV3IGh1KHBhcnNlSW50KHNbMV0sMTYpLzI1NSxwYXJzZUludChzWzJdLDE2KS8yNTUscGFyc2VJbnQoc1szXSwxNikvMjU1LHBhcnNlSW50KHNbNF18fCJGRiIsMTYpLzI1NSl9fTp7cmU6LyMoW2EtZkEtRjAtOV17Mn0pKFthLWZBLUYwLTldezJ9KShbYS1mQS1GMC05XXsyfSkkLyxwYXJzZTpmdW5jdGlvbihzKXtyZXR1cm4gbmV3IGh1KHBhcnNlSW50KHNbMV0sMTYpLzI1NSxwYXJzZUludChzWzJdLDE2KS8yNTUscGFyc2VJbnQoc1szXSwxNikvMjU1LDEpfX0pLG8ucHVzaCh7cmU6LyMoW2EtZkEtRjAtOV0pKFthLWZBLUYwLTldKShbYS1mQS1GMC05XSkkLyxwYXJzZTpmdW5jdGlvbihzKXtyZXR1cm4gbmV3IGh1KHBhcnNlSW50KHNbMV0rc1sxXSwxNikvMjU1LHBhcnNlSW50KHNbMl0rc1syXSwxNikvMjU1LHBhcnNlSW50KHNbM10rc1szXSwxNikvMjU1LDEpfX0pO2ZvcihsZXQgcyBpbiBvKWlmKG8uaGFzT3duUHJvcGVydHkocykpe2xldCBhPW9bc10sbD1hLnJlLmV4ZWMoZSksYz1sJiZhLnBhcnNlKGwpO2lmKGMpcmV0dXJuIGMgaW5zdGFuY2VvZiBodT9yPXRoaXMucmdiYVRvSHN2YShjKTpjIGluc3RhbmNlb2YgUnAmJihyPXRoaXMuaHNsYTJoc3ZhKGMpKSxyfXJldHVybiByfW91dHB1dEZvcm1hdChlLGkscil7c3dpdGNoKCJhdXRvIj09PWkmJihpPWUuYTwxPyJyZ2JhIjoiaGV4IiksaSl7Y2FzZSJoc2xhIjpsZXQgbz10aGlzLmhzdmEyaHNsYShlKSxzPW5ldyBScChNYXRoLnJvdW5kKDM2MCpvLmgpLE1hdGgucm91bmQoMTAwKm8ucyksTWF0aC5yb3VuZCgxMDAqby5sKSxNYXRoLnJvdW5kKDEwMCpvLmEpLzEwMCk7cmV0dXJuIGUuYTwxfHwiYWx3YXlzIj09PXI/ImhzbGEoIitzLmgrIiwiK3MucysiJSwiK3MubCsiJSwiK3MuYSsiKSI6ImhzbCgiK3MuaCsiLCIrcy5zKyIlLCIrcy5sKyIlKSI7Y2FzZSJyZ2JhIjpsZXQgYT10aGlzLmRlbm9ybWFsaXplUkdCQSh0aGlzLmhzdmFUb1JnYmEoZSkpO3JldHVybiBlLmE8MXx8ImFsd2F5cyI9PT1yPyJyZ2JhKCIrYS5yKyIsIithLmcrIiwiK2EuYisiLCIrTWF0aC5yb3VuZCgxMDAqYS5hKS8xMDArIikiOiJyZ2IoIithLnIrIiwiK2EuZysiLCIrYS5iKyIpIjtkZWZhdWx0OmxldCBsPSJhbHdheXMiPT09cnx8ImZvcmNlZCI9PT1yO3JldHVybiB0aGlzLnJnYmFUb0hleCh0aGlzLmRlbm9ybWFsaXplUkdCQSh0aGlzLmhzdmFUb1JnYmEoZSkpLGwpfX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksaWhlPXR5cGVvZiB3aW5kb3c8InUiJiYib250b3VjaHN0YXJ0ImluIHdpbmRvdyxkWmU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscixvLHMsYSl7dGhpcy5uZ1pvbmU9ZSx0aGlzLmVsUmVmPWksdGhpcy5jZFJlZj1yLHRoaXMuZG9jdW1lbnQ9byx0aGlzLnBsYXRmb3JtSWQ9cyx0aGlzLnNlcnZpY2U9YSx0aGlzLmlzSUUxMD0hMSx0aGlzLmRpYWxvZ0Fycm93U2l6ZT0xMCx0aGlzLmRpYWxvZ0Fycm93T2Zmc2V0PTE1LHRoaXMuZGlhbG9nSW5wdXRGaWVsZHM9W3dzLkhFWCx3cy5SR0JBLHdzLkhTTEEsd3MuQ01ZS10sdGhpcy51c2VSb290Vmlld0NvbnRhaW5lcj0hMSx0aGlzLmV5ZURyb3BwZXJTdXBwb3J0ZWQ9WEQodGhpcy5wbGF0Zm9ybUlkKSYmIkV5ZURyb3BwZXIiaW4gdGhpcy5kb2N1bWVudC5kZWZhdWx0Vmlld31oYW5kbGVFc2MoZSl7dGhpcy5zaG93JiYicG9wdXAiPT09dGhpcy5jcERpYWxvZ0Rpc3BsYXkmJnRoaXMub25DYW5jZWxDb2xvcihlKX1oYW5kbGVFbnRlcihlKXt0aGlzLnNob3cmJiJwb3B1cCI9PT10aGlzLmNwRGlhbG9nRGlzcGxheSYmdGhpcy5vbkFjY2VwdENvbG9yKGUpfW5nT25Jbml0KCl7dGhpcy5zbGlkZXI9bmV3ICRrKDAsMCwwLDApLHRoaXMuc2xpZGVyRGltTWF4PW5ldyBlRih0aGlzLmh1ZVNsaWRlci5uYXRpdmVFbGVtZW50Lm9mZnNldFdpZHRofHwxNDAsdGhpcy5jcFdpZHRoLDEzMCx0aGlzLmFscGhhU2xpZGVyLm5hdGl2ZUVsZW1lbnQub2Zmc2V0V2lkdGh8fDE0MCksdGhpcy5mb3JtYXQ9dGhpcy5jcENteWtFbmFibGVkP3dzLkNNWUs6InJnYmEiPT09dGhpcy5jcE91dHB1dEZvcm1hdD93cy5SR0JBOiJoc2xhIj09PXRoaXMuY3BPdXRwdXRGb3JtYXQ/d3MuSFNMQTp3cy5IRVgsdGhpcy5saXN0ZW5lck1vdXNlRG93bj1yPT57dGhpcy5vbk1vdXNlRG93bihyKX0sdGhpcy5saXN0ZW5lclJlc2l6ZT0oKT0+e3RoaXMub25SZXNpemUoKX0sdGhpcy5vcGVuRGlhbG9nKHRoaXMuaW5pdGlhbENvbG9yLCExKX1uZ09uRGVzdHJveSgpe3RoaXMuY2xvc2VEaWFsb2coKX1uZ0FmdGVyVmlld0luaXQoKXsyMzA9PT10aGlzLmNwV2lkdGgmJiJpbmxpbmUiIT09dGhpcy5jcERpYWxvZ0Rpc3BsYXl8fCh0aGlzLnNsaWRlckRpbU1heD1uZXcgZUYodGhpcy5odWVTbGlkZXIubmF0aXZlRWxlbWVudC5vZmZzZXRXaWR0aHx8MTQwLHRoaXMuY3BXaWR0aCwxMzAsdGhpcy5hbHBoYVNsaWRlci5uYXRpdmVFbGVtZW50Lm9mZnNldFdpZHRofHwxNDApLHRoaXMudXBkYXRlQ29sb3JQaWNrZXIoITEpLHRoaXMuY2RSZWYuZGV0ZWN0Q2hhbmdlcygpKX1vcGVuRGlhbG9nKGUsaT0hMCl7dGhpcy5zZXJ2aWNlLnNldEFjdGl2ZSh0aGlzKSx0aGlzLndpZHRofHwodGhpcy5jcFdpZHRoPXRoaXMuZGlyZWN0aXZlRWxlbWVudFJlZi5uYXRpdmVFbGVtZW50Lm9mZnNldFdpZHRoKSx0aGlzLmhlaWdodHx8KHRoaXMuaGVpZ2h0PTMyMCksdGhpcy5zZXRJbml0aWFsQ29sb3IoZSksdGhpcy5zZXRDb2xvckZyb21TdHJpbmcoZSxpKSx0aGlzLm9wZW5Db2xvclBpY2tlcigpfWNsb3NlRGlhbG9nKCl7dGhpcy5jbG9zZUNvbG9yUGlja2VyKCl9c2V0dXBEaWFsb2coZSxpLHIsbyxzLGEsbCxjLHUsZCxwLGgsZixtLHgsZyxiLEQsVCxrLFoseixmZSx1ZSxoZSx3LEYscSxLLGRlLFksYWUsbGUsSWUsdmUsRGUsbnQsZ3Qpe3RoaXMuc2V0SW5pdGlhbENvbG9yKHIpLHRoaXMuc2V0Q29sb3JNb2RlKGMpLHRoaXMuaXNJRTEwPTEwPT09ZnVuY3Rpb24oKXtsZXQgbj0iIjt0eXBlb2YgbmF2aWdhdG9yPCJ1IiYmKG49bmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpKTtsZXQgdD1uLmluZGV4T2YoIm1zaWUgIik7cmV0dXJuIHQ+MCYmcGFyc2VJbnQobi5zdWJzdHJpbmcodCs1LG4uaW5kZXhPZigiLiIsdCkpLDEwKX0oKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlPWUsdGhpcy5kaXJlY3RpdmVFbGVtZW50UmVmPWksdGhpcy5jcERpc2FibGVJbnB1dD1oLHRoaXMuY3BDbXlrRW5hYmxlZD11LHRoaXMuY3BBbHBoYUNoYW5uZWw9ZCx0aGlzLmNwT3V0cHV0Rm9ybWF0PXAsdGhpcy5jcERpYWxvZ0Rpc3BsYXk9YSx0aGlzLmNwSWdub3JlZEVsZW1lbnRzPWYsdGhpcy5jcFNhdmVDbGlja091dHNpZGU9bSx0aGlzLmNwQ2xvc2VDbGlja091dHNpZGU9eCx0aGlzLnVzZVJvb3RWaWV3Q29udGFpbmVyPWcsdGhpcy53aWR0aD10aGlzLmNwV2lkdGg9cGFyc2VJbnQobywxMCksdGhpcy5oZWlnaHQ9dGhpcy5jcEhlaWdodD1wYXJzZUludChzLDEwKSx0aGlzLmNwUG9zaXRpb249Yix0aGlzLmNwUG9zaXRpb25PZmZzZXQ9cGFyc2VJbnQoRCwxMCksdGhpcy5jcE9LQnV0dG9uPXcsdGhpcy5jcE9LQnV0dG9uVGV4dD1xLHRoaXMuY3BPS0J1dHRvbkNsYXNzPUYsdGhpcy5jcENhbmNlbEJ1dHRvbj1LLHRoaXMuY3BDYW5jZWxCdXR0b25UZXh0PVksdGhpcy5jcENhbmNlbEJ1dHRvbkNsYXNzPWRlLHRoaXMuY3BFeWVEcm9wcGVyPURlLHRoaXMuZmFsbGJhY2tDb2xvcj1sfHwiI2ZmZiIsdGhpcy5zZXRQcmVzZXRDb25maWcoayxaKSx0aGlzLmNwUHJlc2V0Q29sb3JzQ2xhc3M9eix0aGlzLmNwTWF4UHJlc2V0Q29sb3JzTGVuZ3RoPWZlLHRoaXMuY3BQcmVzZXRFbXB0eU1lc3NhZ2U9dWUsdGhpcy5jcFByZXNldEVtcHR5TWVzc2FnZUNsYXNzPWhlLHRoaXMuY3BBZGRDb2xvckJ1dHRvbj1hZSx0aGlzLmNwQWRkQ29sb3JCdXR0b25UZXh0PUllLHRoaXMuY3BBZGRDb2xvckJ1dHRvbkNsYXNzPWxlLHRoaXMuY3BSZW1vdmVDb2xvckJ1dHRvbkNsYXNzPXZlLHRoaXMuY3BUcmlnZ2VyRWxlbWVudD1udCx0aGlzLmNwRXh0cmFUZW1wbGF0ZT1ndCxUfHwodGhpcy5kaWFsb2dBcnJvd09mZnNldD0wKSwiaW5saW5lIj09PWEmJih0aGlzLmRpYWxvZ0Fycm93U2l6ZT0wLHRoaXMuZGlhbG9nQXJyb3dPZmZzZXQ9MCksImhleCI9PT1wJiYiYWx3YXlzIiE9PWQmJiJmb3JjZWQiIT09ZCYmKHRoaXMuY3BBbHBoYUNoYW5uZWw9ImRpc2FibGVkIil9c2V0Q29sb3JNb2RlKGUpe3N3aXRjaChlLnRvU3RyaW5nKCkudG9VcHBlckNhc2UoKSl7Y2FzZSIxIjpjYXNlIkMiOmNhc2UiQ09MT1IiOmRlZmF1bHQ6dGhpcy5jcENvbG9yTW9kZT0xO2JyZWFrO2Nhc2UiMiI6Y2FzZSJHIjpjYXNlIkdSQVlTQ0FMRSI6dGhpcy5jcENvbG9yTW9kZT0yO2JyZWFrO2Nhc2UiMyI6Y2FzZSJQIjpjYXNlIlBSRVNFVFMiOnRoaXMuY3BDb2xvck1vZGU9M319c2V0SW5pdGlhbENvbG9yKGUpe3RoaXMuaW5pdGlhbENvbG9yPWV9c2V0UHJlc2V0Q29uZmlnKGUsaSl7dGhpcy5jcFByZXNldExhYmVsPWUsdGhpcy5jcFByZXNldENvbG9ycz1pfXNldENvbG9yRnJvbVN0cmluZyhlLGk9ITAscj0hMCl7bGV0IG87ImFsd2F5cyI9PT10aGlzLmNwQWxwaGFDaGFubmVsfHwiZm9yY2VkIj09PXRoaXMuY3BBbHBoYUNoYW5uZWw/KG89dGhpcy5zZXJ2aWNlLnN0cmluZ1RvSHN2YShlLCEwKSwhbyYmIXRoaXMuaHN2YSYmKG89dGhpcy5zZXJ2aWNlLnN0cmluZ1RvSHN2YShlLCExKSkpOm89dGhpcy5zZXJ2aWNlLnN0cmluZ1RvSHN2YShlLCExKSwhbyYmIXRoaXMuaHN2YSYmKG89dGhpcy5zZXJ2aWNlLnN0cmluZ1RvSHN2YSh0aGlzLmZhbGxiYWNrQ29sb3IsITEpKSxvJiYodGhpcy5oc3ZhPW8sdGhpcy5zbGlkZXJIPXRoaXMuaHN2YS5oLCJoZXgiPT09dGhpcy5jcE91dHB1dEZvcm1hdCYmImRpc2FibGVkIj09PXRoaXMuY3BBbHBoYUNoYW5uZWwmJih0aGlzLmhzdmEuYT0xKSx0aGlzLnVwZGF0ZUNvbG9yUGlja2VyKGkscikpfW9uUmVzaXplKCl7ImZpeGVkIj09PXRoaXMucG9zaXRpb24/dGhpcy5zZXREaWFsb2dQb3NpdGlvbigpOiJpbmxpbmUiIT09dGhpcy5jcERpYWxvZ0Rpc3BsYXkmJnRoaXMuY2xvc2VDb2xvclBpY2tlcigpfW9uRHJhZ0VuZChlKXt0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLnNsaWRlckRyYWdFbmQoe3NsaWRlcjplLGNvbG9yOnRoaXMub3V0cHV0Q29sb3J9KX1vbkRyYWdTdGFydChlKXt0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLnNsaWRlckRyYWdTdGFydCh7c2xpZGVyOmUsY29sb3I6dGhpcy5vdXRwdXRDb2xvcn0pfW9uTW91c2VEb3duKGUpe3RoaXMuc2hvdyYmIXRoaXMuaXNJRTEwJiYicG9wdXAiPT09dGhpcy5jcERpYWxvZ0Rpc3BsYXkmJmUudGFyZ2V0IT09dGhpcy5kaXJlY3RpdmVFbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQmJiF0aGlzLmlzRGVzY2VuZGFudCh0aGlzLmVsUmVmLm5hdGl2ZUVsZW1lbnQsZS50YXJnZXQpJiYhdGhpcy5pc0Rlc2NlbmRhbnQodGhpcy5kaXJlY3RpdmVFbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQsZS50YXJnZXQpJiYwPT09dGhpcy5jcElnbm9yZWRFbGVtZW50cy5maWx0ZXIoaT0+aT09PWUudGFyZ2V0KS5sZW5ndGgmJnRoaXMubmdab25lLnJ1bigoKT0+e3RoaXMuY3BTYXZlQ2xpY2tPdXRzaWRlP3RoaXMuZGlyZWN0aXZlSW5zdGFuY2UuY29sb3JTZWxlY3RlZCh0aGlzLm91dHB1dENvbG9yKToodGhpcy5oc3ZhPW51bGwsdGhpcy5zZXRDb2xvckZyb21TdHJpbmcodGhpcy5pbml0aWFsQ29sb3IsITEpLHRoaXMuY3BDbXlrRW5hYmxlZCYmdGhpcy5kaXJlY3RpdmVJbnN0YW5jZS5jbXlrQ2hhbmdlZCh0aGlzLmNteWtDb2xvciksdGhpcy5kaXJlY3RpdmVJbnN0YW5jZS5jb2xvckNoYW5nZWQodGhpcy5pbml0aWFsQ29sb3IpLHRoaXMuZGlyZWN0aXZlSW5zdGFuY2UuY29sb3JDYW5jZWxlZCgpKSx0aGlzLmNwQ2xvc2VDbGlja091dHNpZGUmJnRoaXMuY2xvc2VDb2xvclBpY2tlcigpfSl9b25BY2NlcHRDb2xvcihlKXtlLnN0b3BQcm9wYWdhdGlvbigpLHRoaXMub3V0cHV0Q29sb3ImJnRoaXMuZGlyZWN0aXZlSW5zdGFuY2UuY29sb3JTZWxlY3RlZCh0aGlzLm91dHB1dENvbG9yKSwicG9wdXAiPT09dGhpcy5jcERpYWxvZ0Rpc3BsYXkmJnRoaXMuY2xvc2VDb2xvclBpY2tlcigpfW9uQ2FuY2VsQ29sb3IoZSl7dGhpcy5oc3ZhPW51bGwsZS5zdG9wUHJvcGFnYXRpb24oKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLmNvbG9yQ2FuY2VsZWQoKSx0aGlzLnNldENvbG9yRnJvbVN0cmluZyh0aGlzLmluaXRpYWxDb2xvciwhMCksInBvcHVwIj09PXRoaXMuY3BEaWFsb2dEaXNwbGF5JiYodGhpcy5jcENteWtFbmFibGVkJiZ0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLmNteWtDaGFuZ2VkKHRoaXMuY215a0NvbG9yKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLmNvbG9yQ2hhbmdlZCh0aGlzLmluaXRpYWxDb2xvciwhMCksdGhpcy5jbG9zZUNvbG9yUGlja2VyKCkpfW9uRXllRHJvcHBlcigpe3RoaXMuZXllRHJvcHBlclN1cHBvcnRlZCYmKG5ldyB3aW5kb3cuRXllRHJvcHBlcikub3BlbigpLnRoZW4oaT0+e3RoaXMuc2V0Q29sb3JGcm9tU3RyaW5nKGkuc1JHQkhleCwhMCl9KX1vbkZvcm1hdFRvZ2dsZShlKXtsZXQgaT10aGlzLmRpYWxvZ0lucHV0RmllbGRzLmxlbmd0aC0odGhpcy5jcENteWtFbmFibGVkPzA6MSkscj0oKHRoaXMuZGlhbG9nSW5wdXRGaWVsZHMuaW5kZXhPZih0aGlzLmZvcm1hdCkrZSklaStpKSVpO3RoaXMuZm9ybWF0PXRoaXMuZGlhbG9nSW5wdXRGaWVsZHNbcl19b25Db2xvckNoYW5nZShlKXt0aGlzLmhzdmEucz1lLnMvZS5yZ1gsdGhpcy5oc3ZhLnY9ZS52L2UucmdZLHRoaXMudXBkYXRlQ29sb3JQaWNrZXIoKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLnNsaWRlckNoYW5nZWQoe3NsaWRlcjoibGlnaHRuZXNzIix2YWx1ZTp0aGlzLmhzdmEudixjb2xvcjp0aGlzLm91dHB1dENvbG9yfSksdGhpcy5kaXJlY3RpdmVJbnN0YW5jZS5zbGlkZXJDaGFuZ2VkKHtzbGlkZXI6InNhdHVyYXRpb24iLHZhbHVlOnRoaXMuaHN2YS5zLGNvbG9yOnRoaXMub3V0cHV0Q29sb3J9KX1vbkh1ZUNoYW5nZShlKXt0aGlzLmhzdmEuaD1lLnYvZS5yZ1gsdGhpcy5zbGlkZXJIPXRoaXMuaHN2YS5oLHRoaXMudXBkYXRlQ29sb3JQaWNrZXIoKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLnNsaWRlckNoYW5nZWQoe3NsaWRlcjoiaHVlIix2YWx1ZTp0aGlzLmhzdmEuaCxjb2xvcjp0aGlzLm91dHB1dENvbG9yfSl9b25WYWx1ZUNoYW5nZShlKXt0aGlzLmhzdmEudj1lLnYvZS5yZ1gsdGhpcy51cGRhdGVDb2xvclBpY2tlcigpLHRoaXMuZGlyZWN0aXZlSW5zdGFuY2Uuc2xpZGVyQ2hhbmdlZCh7c2xpZGVyOiJ2YWx1ZSIsdmFsdWU6dGhpcy5oc3ZhLnYsY29sb3I6dGhpcy5vdXRwdXRDb2xvcn0pfW9uQWxwaGFDaGFuZ2UoZSl7dGhpcy5oc3ZhLmE9ZS52L2UucmdYLHRoaXMudXBkYXRlQ29sb3JQaWNrZXIoKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLnNsaWRlckNoYW5nZWQoe3NsaWRlcjoiYWxwaGEiLHZhbHVlOnRoaXMuaHN2YS5hLGNvbG9yOnRoaXMub3V0cHV0Q29sb3J9KX1vbkhleElucHV0KGUpe2lmKG51bGw9PT1lKXRoaXMudXBkYXRlQ29sb3JQaWNrZXIoKTtlbHNle2UmJiIjIiE9PWVbMF0mJihlPSIjIitlKTtsZXQgaT0vXiMoW2EtZjAtOV17M318W2EtZjAtOV17Nn0pJC9naTsiYWx3YXlzIj09PXRoaXMuY3BBbHBoYUNoYW5uZWwmJihpPS9eIyhbYS1mMC05XXszfXxbYS1mMC05XXs2fXxbYS1mMC05XXs4fSkkL2dpKTtsZXQgcj1pLnRlc3QoZSk7ciYmKGUubGVuZ3RoPDUmJihlPSIjIitlLnN1YnN0cmluZygxKS5zcGxpdCgiIikubWFwKG89Pm8rbykuam9pbigiIikpLCJmb3JjZWQiPT09dGhpcy5jcEFscGhhQ2hhbm5lbCYmKGUrPU1hdGgucm91bmQoMjU1KnRoaXMuaHN2YS5hKS50b1N0cmluZygxNikpLHRoaXMuc2V0Q29sb3JGcm9tU3RyaW5nKGUsITAsITEpKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLmlucHV0Q2hhbmdlZCh7aW5wdXQ6ImhleCIsdmFsaWQ6cix2YWx1ZTplLGNvbG9yOnRoaXMub3V0cHV0Q29sb3J9KX19b25SZWRJbnB1dChlKXtsZXQgaT10aGlzLnNlcnZpY2UuaHN2YVRvUmdiYSh0aGlzLmhzdmEpLHI9IWlzTmFOKGUudikmJmUudj49MCYmZS52PD1lLnJnO3ImJihpLnI9ZS52L2UucmcsdGhpcy5oc3ZhPXRoaXMuc2VydmljZS5yZ2JhVG9Ic3ZhKGkpLHRoaXMuc2xpZGVySD10aGlzLmhzdmEuaCx0aGlzLnVwZGF0ZUNvbG9yUGlja2VyKCkpLHRoaXMuZGlyZWN0aXZlSW5zdGFuY2UuaW5wdXRDaGFuZ2VkKHtpbnB1dDoicmVkIix2YWxpZDpyLHZhbHVlOmkucixjb2xvcjp0aGlzLm91dHB1dENvbG9yfSl9b25CbHVlSW5wdXQoZSl7bGV0IGk9dGhpcy5zZXJ2aWNlLmhzdmFUb1JnYmEodGhpcy5oc3ZhKSxyPSFpc05hTihlLnYpJiZlLnY+PTAmJmUudjw9ZS5yZztyJiYoaS5iPWUudi9lLnJnLHRoaXMuaHN2YT10aGlzLnNlcnZpY2UucmdiYVRvSHN2YShpKSx0aGlzLnNsaWRlckg9dGhpcy5oc3ZhLmgsdGhpcy51cGRhdGVDb2xvclBpY2tlcigpKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLmlucHV0Q2hhbmdlZCh7aW5wdXQ6ImJsdWUiLHZhbGlkOnIsdmFsdWU6aS5iLGNvbG9yOnRoaXMub3V0cHV0Q29sb3J9KX1vbkdyZWVuSW5wdXQoZSl7bGV0IGk9dGhpcy5zZXJ2aWNlLmhzdmFUb1JnYmEodGhpcy5oc3ZhKSxyPSFpc05hTihlLnYpJiZlLnY+PTAmJmUudjw9ZS5yZztyJiYoaS5nPWUudi9lLnJnLHRoaXMuaHN2YT10aGlzLnNlcnZpY2UucmdiYVRvSHN2YShpKSx0aGlzLnNsaWRlckg9dGhpcy5oc3ZhLmgsdGhpcy51cGRhdGVDb2xvclBpY2tlcigpKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLmlucHV0Q2hhbmdlZCh7aW5wdXQ6ImdyZWVuIix2YWxpZDpyLHZhbHVlOmkuZyxjb2xvcjp0aGlzLm91dHB1dENvbG9yfSl9b25IdWVJbnB1dChlKXtsZXQgaT0haXNOYU4oZS52KSYmZS52Pj0wJiZlLnY8PWUucmc7aSYmKHRoaXMuaHN2YS5oPWUudi9lLnJnLHRoaXMuc2xpZGVySD10aGlzLmhzdmEuaCx0aGlzLnVwZGF0ZUNvbG9yUGlja2VyKCkpLHRoaXMuZGlyZWN0aXZlSW5zdGFuY2UuaW5wdXRDaGFuZ2VkKHtpbnB1dDoiaHVlIix2YWxpZDppLHZhbHVlOnRoaXMuaHN2YS5oLGNvbG9yOnRoaXMub3V0cHV0Q29sb3J9KX1vblZhbHVlSW5wdXQoZSl7bGV0IGk9IWlzTmFOKGUudikmJmUudj49MCYmZS52PD1lLnJnO2kmJih0aGlzLmhzdmEudj1lLnYvZS5yZyx0aGlzLnVwZGF0ZUNvbG9yUGlja2VyKCkpLHRoaXMuZGlyZWN0aXZlSW5zdGFuY2UuaW5wdXRDaGFuZ2VkKHtpbnB1dDoidmFsdWUiLHZhbGlkOmksdmFsdWU6dGhpcy5oc3ZhLnYsY29sb3I6dGhpcy5vdXRwdXRDb2xvcn0pfW9uQWxwaGFJbnB1dChlKXtsZXQgaT0haXNOYU4oZS52KSYmZS52Pj0wJiZlLnY8PWUucmc7aSYmKHRoaXMuaHN2YS5hPWUudi9lLnJnLHRoaXMudXBkYXRlQ29sb3JQaWNrZXIoKSksdGhpcy5kaXJlY3RpdmVJbnN0YW5jZS5pbnB1dENoYW5nZWQoe2lucHV0OiJhbHBoYSIsdmFsaWQ6aSx2YWx1ZTp0aGlzLmhzdmEuYSxjb2xvcjp0aGlzLm91dHB1dENvbG9yfSl9b25MaWdodG5lc3NJbnB1dChlKXtsZXQgaT10aGlzLnNlcnZpY2UuaHN2YTJoc2xhKHRoaXMuaHN2YSkscj0haXNOYU4oZS52KSYmZS52Pj0wJiZlLnY8PWUucmc7ciYmKGkubD1lLnYvZS5yZyx0aGlzLmhzdmE9dGhpcy5zZXJ2aWNlLmhzbGEyaHN2YShpKSx0aGlzLnNsaWRlckg9dGhpcy5oc3ZhLmgsdGhpcy51cGRhdGVDb2xvclBpY2tlcigpKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLmlucHV0Q2hhbmdlZCh7aW5wdXQ6ImxpZ2h0bmVzcyIsdmFsaWQ6cix2YWx1ZTppLmwsY29sb3I6dGhpcy5vdXRwdXRDb2xvcn0pfW9uU2F0dXJhdGlvbklucHV0KGUpe2xldCBpPXRoaXMuc2VydmljZS5oc3ZhMmhzbGEodGhpcy5oc3ZhKSxyPSFpc05hTihlLnYpJiZlLnY+PTAmJmUudjw9ZS5yZztyJiYoaS5zPWUudi9lLnJnLHRoaXMuaHN2YT10aGlzLnNlcnZpY2UuaHNsYTJoc3ZhKGkpLHRoaXMuc2xpZGVySD10aGlzLmhzdmEuaCx0aGlzLnVwZGF0ZUNvbG9yUGlja2VyKCkpLHRoaXMuZGlyZWN0aXZlSW5zdGFuY2UuaW5wdXRDaGFuZ2VkKHtpbnB1dDoic2F0dXJhdGlvbiIsdmFsaWQ6cix2YWx1ZTppLnMsY29sb3I6dGhpcy5vdXRwdXRDb2xvcn0pfW9uQ3lhbklucHV0KGUpeyFpc05hTihlLnYpJiZlLnY+PTAmJmUudjw9ZS5yZyYmKHRoaXMuY215ay5jPWUudix0aGlzLnVwZGF0ZUNvbG9yUGlja2VyKCExLCEwLCEwKSksdGhpcy5kaXJlY3RpdmVJbnN0YW5jZS5pbnB1dENoYW5nZWQoe2lucHV0OiJjeWFuIix2YWxpZDohMCx2YWx1ZTp0aGlzLmNteWsuYyxjb2xvcjp0aGlzLm91dHB1dENvbG9yfSl9b25NYWdlbnRhSW5wdXQoZSl7IWlzTmFOKGUudikmJmUudj49MCYmZS52PD1lLnJnJiYodGhpcy5jbXlrLm09ZS52LHRoaXMudXBkYXRlQ29sb3JQaWNrZXIoITEsITAsITApKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLmlucHV0Q2hhbmdlZCh7aW5wdXQ6Im1hZ2VudGEiLHZhbGlkOiEwLHZhbHVlOnRoaXMuY215ay5tLGNvbG9yOnRoaXMub3V0cHV0Q29sb3J9KX1vblllbGxvd0lucHV0KGUpeyFpc05hTihlLnYpJiZlLnY+PTAmJmUudjw9ZS5yZyYmKHRoaXMuY215ay55PWUudix0aGlzLnVwZGF0ZUNvbG9yUGlja2VyKCExLCEwLCEwKSksdGhpcy5kaXJlY3RpdmVJbnN0YW5jZS5pbnB1dENoYW5nZWQoe2lucHV0OiJ5ZWxsb3ciLHZhbGlkOiEwLHZhbHVlOnRoaXMuY215ay55LGNvbG9yOnRoaXMub3V0cHV0Q29sb3J9KX1vbkJsYWNrSW5wdXQoZSl7IWlzTmFOKGUudikmJmUudj49MCYmZS52PD1lLnJnJiYodGhpcy5jbXlrLms9ZS52LHRoaXMudXBkYXRlQ29sb3JQaWNrZXIoITEsITAsITApKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLmlucHV0Q2hhbmdlZCh7aW5wdXQ6ImJsYWNrIix2YWxpZDohMCx2YWx1ZTp0aGlzLmNteWsuayxjb2xvcjp0aGlzLm91dHB1dENvbG9yfSl9b25BZGRQcmVzZXRDb2xvcihlLGkpe2Uuc3RvcFByb3BhZ2F0aW9uKCksdGhpcy5jcFByZXNldENvbG9ycy5maWx0ZXIocj0+cj09PWkpLmxlbmd0aHx8KHRoaXMuY3BQcmVzZXRDb2xvcnM9dGhpcy5jcFByZXNldENvbG9ycy5jb25jYXQoaSksdGhpcy5kaXJlY3RpdmVJbnN0YW5jZS5wcmVzZXRDb2xvcnNDaGFuZ2VkKHRoaXMuY3BQcmVzZXRDb2xvcnMpKX1vblJlbW92ZVByZXNldENvbG9yKGUsaSl7ZS5zdG9wUHJvcGFnYXRpb24oKSx0aGlzLmNwUHJlc2V0Q29sb3JzPXRoaXMuY3BQcmVzZXRDb2xvcnMuZmlsdGVyKHI9PnIhPT1pKSx0aGlzLmRpcmVjdGl2ZUluc3RhbmNlLnByZXNldENvbG9yc0NoYW5nZWQodGhpcy5jcFByZXNldENvbG9ycyl9b3BlbkNvbG9yUGlja2VyKCl7dGhpcy5zaG93fHwodGhpcy5zaG93PSEwLHRoaXMuaGlkZGVuPSEwLHNldFRpbWVvdXQoKCk9Pnt0aGlzLmhpZGRlbj0hMSx0aGlzLnNldERpYWxvZ1Bvc2l0aW9uKCksdGhpcy5jZFJlZi5kZXRlY3RDaGFuZ2VzKCl9LDApLHRoaXMuZGlyZWN0aXZlSW5zdGFuY2Uuc3RhdGVDaGFuZ2VkKCEwKSx0aGlzLmlzSUUxMHx8dGhpcy5uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCk9PntpaGU/ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigidG91Y2hzdGFydCIsdGhpcy5saXN0ZW5lck1vdXNlRG93bik6ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigibW91c2Vkb3duIix0aGlzLmxpc3RlbmVyTW91c2VEb3duKX0pLHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCJyZXNpemUiLHRoaXMubGlzdGVuZXJSZXNpemUpKX1jbG9zZUNvbG9yUGlja2VyKCl7dGhpcy5zaG93JiYodGhpcy5zaG93PSExLHRoaXMuZGlyZWN0aXZlSW5zdGFuY2Uuc3RhdGVDaGFuZ2VkKCExKSx0aGlzLmlzSUUxMHx8KGloZT9kb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJ0b3VjaHN0YXJ0Iix0aGlzLmxpc3RlbmVyTW91c2VEb3duKTpkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJtb3VzZWRvd24iLHRoaXMubGlzdGVuZXJNb3VzZURvd24pKSx3aW5kb3cucmVtb3ZlRXZlbnRMaXN0ZW5lcigicmVzaXplIix0aGlzLmxpc3RlbmVyUmVzaXplKSx0aGlzLmNkUmVmLmRlc3Ryb3llZHx8dGhpcy5jZFJlZi5kZXRlY3RDaGFuZ2VzKCkpfXVwZGF0ZUNvbG9yUGlja2VyKGU9ITAsaT0hMCxyPSExKXtpZih0aGlzLnNsaWRlckRpbU1heCl7Mj09PXRoaXMuY3BDb2xvck1vZGUmJih0aGlzLmhzdmEucz0wKTtsZXQgbyxzLGEsbD10aGlzLm91dHB1dENvbG9yO2lmKHM9dGhpcy5zZXJ2aWNlLmhzdmEyaHNsYSh0aGlzLmhzdmEpLHRoaXMuY3BDbXlrRW5hYmxlZD8ocj8oYT10aGlzLnNlcnZpY2UuY215a1RvUmdiKHRoaXMuc2VydmljZS5ub3JtYWxpemVDTVlLKHRoaXMuY215aykpLHRoaXMuaHN2YT10aGlzLnNlcnZpY2UucmdiYVRvSHN2YShhKSk6KGE9dGhpcy5zZXJ2aWNlLmhzdmFUb1JnYmEodGhpcy5oc3ZhKSx0aGlzLmNteWs9dGhpcy5zZXJ2aWNlLmRlbm9ybWFsaXplQ01ZSyh0aGlzLnNlcnZpY2UucmdiYVRvQ215ayhhKSkpLGE9dGhpcy5zZXJ2aWNlLmRlbm9ybWFsaXplUkdCQShhKSx0aGlzLnNsaWRlckg9dGhpcy5oc3ZhLmgpOmE9dGhpcy5zZXJ2aWNlLmRlbm9ybWFsaXplUkdCQSh0aGlzLnNlcnZpY2UuaHN2YVRvUmdiYSh0aGlzLmhzdmEpKSxvPXRoaXMuc2VydmljZS5kZW5vcm1hbGl6ZVJHQkEodGhpcy5zZXJ2aWNlLmhzdmFUb1JnYmEobmV3IFJiKHRoaXMuc2xpZGVySHx8dGhpcy5oc3ZhLmgsMSwxLDEpKSksaSYmKHRoaXMuaHNsYVRleHQ9bmV3IFJwKE1hdGgucm91bmQoMzYwKnMuaCksTWF0aC5yb3VuZCgxMDAqcy5zKSxNYXRoLnJvdW5kKDEwMCpzLmwpLE1hdGgucm91bmQoMTAwKnMuYSkvMTAwKSx0aGlzLnJnYmFUZXh0PW5ldyBodShhLnIsYS5nLGEuYixNYXRoLnJvdW5kKDEwMCphLmEpLzEwMCksdGhpcy5jcENteWtFbmFibGVkJiYodGhpcy5jbXlrVGV4dD1uZXcgdDAodGhpcy5jbXlrLmMsdGhpcy5jbXlrLm0sdGhpcy5jbXlrLnksdGhpcy5jbXlrLmssTWF0aC5yb3VuZCgxMDAqdGhpcy5jbXlrLmEpLzEwMCkpLHRoaXMuaGV4VGV4dD10aGlzLnNlcnZpY2UucmdiYVRvSGV4KGEsImFsd2F5cyI9PT10aGlzLmNwQWxwaGFDaGFubmVsKSx0aGlzLmhleEFscGhhPXRoaXMucmdiYVRleHQuYSksImF1dG8iPT09dGhpcy5jcE91dHB1dEZvcm1hdCYmdGhpcy5mb3JtYXQhPT13cy5SR0JBJiZ0aGlzLmZvcm1hdCE9PXdzLkNNWUsmJnRoaXMuZm9ybWF0IT09d3MuSFNMQSYmdGhpcy5oc3ZhLmE8MSYmKHRoaXMuZm9ybWF0PXRoaXMuaHN2YS5hPDE/d3MuUkdCQTp3cy5IRVgpLHRoaXMuaHVlU2xpZGVyQ29sb3I9InJnYigiK28ucisiLCIrby5nKyIsIitvLmIrIikiLHRoaXMuYWxwaGFTbGlkZXJDb2xvcj0icmdiKCIrYS5yKyIsIithLmcrIiwiK2EuYisiKSIsdGhpcy5vdXRwdXRDb2xvcj10aGlzLnNlcnZpY2Uub3V0cHV0Rm9ybWF0KHRoaXMuaHN2YSx0aGlzLmNwT3V0cHV0Rm9ybWF0LHRoaXMuY3BBbHBoYUNoYW5uZWwpLHRoaXMuc2VsZWN0ZWRDb2xvcj10aGlzLnNlcnZpY2Uub3V0cHV0Rm9ybWF0KHRoaXMuaHN2YSwicmdiYSIsbnVsbCksdGhpcy5mb3JtYXQhPT13cy5DTVlLKXRoaXMuY215a0NvbG9yPSIiO2Vsc2UgaWYoImFsd2F5cyI9PT10aGlzLmNwQWxwaGFDaGFubmVsfHwiZW5hYmxlZCI9PT10aGlzLmNwQWxwaGFDaGFubmVsfHwiZm9yY2VkIj09PXRoaXMuY3BBbHBoYUNoYW5uZWwpe2xldCBjPU1hdGgucm91bmQoMTAwKnRoaXMuY215ay5hKS8xMDA7dGhpcy5jbXlrQ29sb3I9YGNteWthKCR7dGhpcy5jbXlrLmN9LCR7dGhpcy5jbXlrLm19LCR7dGhpcy5jbXlrLnl9LCR7dGhpcy5jbXlrLmt9LCR7Y30pYH1lbHNlIHRoaXMuY215a0NvbG9yPWBjbXlrKCR7dGhpcy5jbXlrLmN9LCR7dGhpcy5jbXlrLm19LCR7dGhpcy5jbXlrLnl9LCR7dGhpcy5jbXlrLmt9KWA7dGhpcy5zbGlkZXI9bmV3ICRrKCh0aGlzLnNsaWRlckh8fHRoaXMuaHN2YS5oKSp0aGlzLnNsaWRlckRpbU1heC5oLTgsdGhpcy5oc3ZhLnMqdGhpcy5zbGlkZXJEaW1NYXgucy04LCgxLXRoaXMuaHN2YS52KSp0aGlzLnNsaWRlckRpbU1heC52LTgsdGhpcy5oc3ZhLmEqdGhpcy5zbGlkZXJEaW1NYXguYS04KSxlJiZsIT09dGhpcy5vdXRwdXRDb2xvciYmKHRoaXMuY3BDbXlrRW5hYmxlZCYmdGhpcy5kaXJlY3RpdmVJbnN0YW5jZS5jbXlrQ2hhbmdlZCh0aGlzLmNteWtDb2xvciksdGhpcy5kaXJlY3RpdmVJbnN0YW5jZS5jb2xvckNoYW5nZWQodGhpcy5vdXRwdXRDb2xvcikpfX1zZXREaWFsb2dQb3NpdGlvbigpe2lmKCJpbmxpbmUiPT09dGhpcy5jcERpYWxvZ0Rpc3BsYXkpdGhpcy5wb3NpdGlvbj0icmVsYXRpdmUiO2Vsc2V7bGV0IHIsZT0ic3RhdGljIixpPSIiLG89bnVsbCxzPW51bGwsYT10aGlzLmRpcmVjdGl2ZUVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5wYXJlbnROb2RlLGw9dGhpcy5kaWFsb2dFbGVtZW50Lm5hdGl2ZUVsZW1lbnQub2Zmc2V0SGVpZ2h0O2Zvcig7bnVsbCE9PWEmJiJIVE1MIiE9PWEudGFnTmFtZTspe2lmKHI9d2luZG93LmdldENvbXB1dGVkU3R5bGUoYSksZT1yLmdldFByb3BlcnR5VmFsdWUoInBvc2l0aW9uIiksaT1yLmdldFByb3BlcnR5VmFsdWUoInRyYW5zZm9ybSIpLCJzdGF0aWMiIT09ZSYmbnVsbD09PW8mJihvPWEpLGkmJiJub25lIiE9PWkmJm51bGw9PT1zJiYocz1hKSwiZml4ZWQiPT09ZSl7bz1zO2JyZWFrfWE9YS5wYXJlbnROb2RlfWxldCBjPXRoaXMuY3JlYXRlRGlhbG9nQm94KHRoaXMuZGlyZWN0aXZlRWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LCJmaXhlZCIhPT1lKTtpZih0aGlzLnVzZVJvb3RWaWV3Q29udGFpbmVyfHwiZml4ZWQiPT09ZSYmKCFvfHxvIGluc3RhbmNlb2YgSFRNTFVua25vd25FbGVtZW50KSl0aGlzLnRvcD1jLnRvcCx0aGlzLmxlZnQ9Yy5sZWZ0O2Vsc2V7bnVsbD09PW8mJihvPWEpO2xldCBkPXRoaXMuY3JlYXRlRGlhbG9nQm94KG8sImZpeGVkIiE9PWUpO3RoaXMudG9wPWMudG9wLWQudG9wLHRoaXMubGVmdD1jLmxlZnQtZC5sZWZ0fSJmaXhlZCI9PT1lJiYodGhpcy5wb3NpdGlvbj0iZml4ZWQiKTtsZXQgdT10aGlzLmNwUG9zaXRpb247ImF1dG8iPT09dGhpcy5jcFBvc2l0aW9uJiYodT1mdW5jdGlvbihuLHQpe2xldCBlPSJyaWdodCIsaT0iYm90dG9tIix7aGVpZ2h0OnIsd2lkdGg6b309bix7dG9wOnMsbGVmdDphfT10LGw9cyt0LmhlaWdodCxjPWErdC53aWR0aCx1PXMtcjwwLGQ9bCtyPih3aW5kb3cuaW5uZXJIZWlnaHR8fGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5jbGllbnRIZWlnaHQpLHA9YS1vPDAsaD1jK28+KHdpbmRvdy5pbm5lcldpZHRofHxkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuY2xpZW50V2lkdGgpO3JldHVybiBkJiYoaT0idG9wIiksdSYmKGk9ImJvdHRvbSIpLHAmJihlPSJyaWdodCIpLGgmJihlPSJsZWZ0IiksdSYmZCYmcCYmaD9bImxlZnQiLCJyaWdodCIsInRvcCIsImJvdHRvbSJdLnJlZHVjZSgoeCxnKT0+blt4XT5uW2ddP3g6Zyk6cCYmaD91PyJib3R0b20iOmR8fHM+bD8idG9wIjoiYm90dG9tIjp1JiZkP3A/InJpZ2h0IjpofHxhPmM/ImxlZnQiOiJyaWdodCI6YCR7aX0tJHtlfWB9KHRoaXMuZGlhbG9nRWxlbWVudC5uYXRpdmVFbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLHRoaXMuY3BUcmlnZ2VyRWxlbWVudC5uYXRpdmVFbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpKSksInRvcCI9PT11Pyh0aGlzLmFycm93VG9wPWwtMSx0aGlzLnRvcC09bCt0aGlzLmRpYWxvZ0Fycm93U2l6ZSx0aGlzLmxlZnQrPXRoaXMuY3BQb3NpdGlvbk9mZnNldC8xMDAqYy53aWR0aC10aGlzLmRpYWxvZ0Fycm93T2Zmc2V0KToiYm90dG9tIj09PXU/KHRoaXMudG9wKz1jLmhlaWdodCt0aGlzLmRpYWxvZ0Fycm93U2l6ZSx0aGlzLmxlZnQrPXRoaXMuY3BQb3NpdGlvbk9mZnNldC8xMDAqYy53aWR0aC10aGlzLmRpYWxvZ0Fycm93T2Zmc2V0KToidG9wLWxlZnQiPT09dXx8ImxlZnQtdG9wIj09PXU/KHRoaXMudG9wLT1sLWMuaGVpZ2h0K2MuaGVpZ2h0KnRoaXMuY3BQb3NpdGlvbk9mZnNldC8xMDAsdGhpcy5sZWZ0LT10aGlzLmNwV2lkdGgrdGhpcy5kaWFsb2dBcnJvd1NpemUtMi10aGlzLmRpYWxvZ0Fycm93T2Zmc2V0KToidG9wLXJpZ2h0Ij09PXV8fCJyaWdodC10b3AiPT09dT8odGhpcy50b3AtPWwtYy5oZWlnaHQrYy5oZWlnaHQqdGhpcy5jcFBvc2l0aW9uT2Zmc2V0LzEwMCx0aGlzLmxlZnQrPWMud2lkdGgrdGhpcy5kaWFsb2dBcnJvd1NpemUtMi10aGlzLmRpYWxvZ0Fycm93T2Zmc2V0KToibGVmdCI9PT11fHwiYm90dG9tLWxlZnQiPT09dXx8ImxlZnQtYm90dG9tIj09PXU/KHRoaXMudG9wKz1jLmhlaWdodCp0aGlzLmNwUG9zaXRpb25PZmZzZXQvMTAwLXRoaXMuZGlhbG9nQXJyb3dPZmZzZXQsdGhpcy5sZWZ0LT10aGlzLmNwV2lkdGgrdGhpcy5kaWFsb2dBcnJvd1NpemUtMik6KHRoaXMudG9wKz1jLmhlaWdodCp0aGlzLmNwUG9zaXRpb25PZmZzZXQvMTAwLXRoaXMuZGlhbG9nQXJyb3dPZmZzZXQsdGhpcy5sZWZ0Kz1jLndpZHRoK3RoaXMuZGlhbG9nQXJyb3dTaXplLTIpLHRoaXMuY3BVc2VQb3NpdGlvbj11fX1pc0Rlc2NlbmRhbnQoZSxpKXtsZXQgcj1pLnBhcmVudE5vZGU7Zm9yKDtudWxsIT09cjspe2lmKHI9PT1lKXJldHVybiEwO3I9ci5wYXJlbnROb2RlfXJldHVybiExfWNyZWF0ZURpYWxvZ0JveChlLGkpe2xldHt0b3A6cixsZWZ0Om99PWUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7cmV0dXJue3RvcDpyKyhpP3dpbmRvdy5wYWdlWU9mZnNldDowKSxsZWZ0Om8rKGk/d2luZG93LnBhZ2VYT2Zmc2V0OjApLHdpZHRoOmUub2Zmc2V0V2lkdGgsaGVpZ2h0OmUub2Zmc2V0SGVpZ2h0fX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShfdCksTShSZSksTShubiksTShIdCksTShHZCksTShxRykpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbImNvbG9yLXBpY2tlciJdXSx2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJihvdChES2UsNyksb3QoQUtlLDcpLG90KElLZSw3KSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5kaWFsb2dFbGVtZW50PXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLmh1ZVNsaWRlcj1yLmZpcnN0KSxOZShyPUxlKCkpJiYoaS5hbHBoYVNsaWRlcj1yLmZpcnN0KX19LGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgia2V5dXAuZXNjIixmdW5jdGlvbihvKXtyZXR1cm4gaS5oYW5kbGVFc2Mobyl9LDAsX1QpKCJrZXl1cC5lbnRlciIsZnVuY3Rpb24obyl7cmV0dXJuIGkuaGFuZGxlRW50ZXIobyl9LCExLF9UKX0sZGVjbHM6MzAsdmFyczo1MSxjb25zdHM6W1sxLCJjb2xvci1waWNrZXIiLDMsImNsaWNrIl0sWyJkaWFsb2dQb3B1cCIsIiJdLFszLCJjbGFzcyIsInRvcCIsNCwibmdJZiJdLFsiY2xhc3MiLCJzYXR1cmF0aW9uLWxpZ2h0bmVzcyIsMywic2xpZGVyIiwicmdYIiwicmdZIiwiYmFja2dyb3VuZC1jb2xvciIsIm5ld1ZhbHVlIiwiZHJhZ1N0YXJ0IiwiZHJhZ0VuZCIsNCwibmdJZiJdLFsxLCJodWUtYWxwaGEiLCJib3giXSxbMSwibGVmdCJdLFsxLCJzZWxlY3RlZC1jb2xvci1iYWNrZ3JvdW5kIl0sWzEsInNlbGVjdGVkLWNvbG9yIiwzLCJjbGljayJdLFsiY2xhc3MiLCJleWVkcm9wcGVyLWljb24iLCJ4bWxucyIsImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwiaGVpZ2h0IiwiMjRweCIsInZpZXdCb3giLCIwIDAgMjQgMjQiLCJ3aWR0aCIsIjI0cHgiLCJmaWxsIiwiIzAwMDAwMCIsNCwibmdJZiJdLFsidHlwZSIsImJ1dHRvbiIsMywiY2xhc3MiLCJkaXNhYmxlZCIsImNsaWNrIiw0LCJuZ0lmIl0sWzEsInJpZ2h0Il0sWyJzdHlsZSIsImhlaWdodDogMTZweDsiLDQsIm5nSWYiXSxbMSwiaHVlIiwzLCJzbGlkZXIiLCJyZ1giLCJuZXdWYWx1ZSIsImRyYWdTdGFydCIsImRyYWdFbmQiXSxbImh1ZVNsaWRlciIsIiJdLFsxLCJjdXJzb3IiXSxbMSwidmFsdWUiLDMsInNsaWRlciIsInJnWCIsIm5ld1ZhbHVlIiwiZHJhZ1N0YXJ0IiwiZHJhZ0VuZCJdLFsidmFsdWVTbGlkZXIiLCIiXSxbMSwiYWxwaGEiLDMsInNsaWRlciIsInJnWCIsIm5ld1ZhbHVlIiwiZHJhZ1N0YXJ0IiwiZHJhZ0VuZCJdLFsiYWxwaGFTbGlkZXIiLCIiXSxbImNsYXNzIiwiY215ay10ZXh0IiwzLCJkaXNwbGF5Iiw0LCJuZ0lmIl0sWyJjbGFzcyIsImhzbGEtdGV4dCIsMywiZGlzcGxheSIsNCwibmdJZiJdLFsiY2xhc3MiLCJyZ2JhLXRleHQiLDMsImRpc3BsYXkiLDQsIm5nSWYiXSxbImNsYXNzIiwiaGV4LXRleHQiLDMsImhleC1hbHBoYSIsImRpc3BsYXkiLDQsIm5nSWYiXSxbImNsYXNzIiwidmFsdWUtdGV4dCIsNCwibmdJZiJdLFsiY2xhc3MiLCJ0eXBlLXBvbGljeSIsNCwibmdJZiJdLFsiY2xhc3MiLCJwcmVzZXQtYXJlYSIsNCwibmdJZiJdLFsiY2xhc3MiLCJidXR0b24tYXJlYSIsNCwibmdJZiJdLFsiY2xhc3MiLCJleHRyYS10ZW1wbGF0ZSIsNCwibmdJZiJdLFsxLCJzYXR1cmF0aW9uLWxpZ2h0bmVzcyIsMywic2xpZGVyIiwicmdYIiwicmdZIiwibmV3VmFsdWUiLCJkcmFnU3RhcnQiLCJkcmFnRW5kIl0sWyJ4bWxucyIsImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiwiaGVpZ2h0IiwiMjRweCIsInZpZXdCb3giLCIwIDAgMjQgMjQiLCJ3aWR0aCIsIjI0cHgiLCJmaWxsIiwiIzAwMDAwMCIsMSwiZXllZHJvcHBlci1pY29uIl0sWyJkIiwiTTAgMGgyNHYyNEgwVjB6IiwiZmlsbCIsIm5vbmUiXSxbImQiLCJNMTcuNjYgNS40MWwuOTIuOTItMi42OSAyLjY5LS45Mi0uOTIgMi42OS0yLjY5TTE3LjY3IDNjLS4yNiAwLS41MS4xLS43MS4yOWwtMy4xMiAzLjEyLTEuOTMtMS45MS0xLjQxIDEuNDEgMS40MiAxLjQyTDMgMTYuMjVWMjFoNC43NWw4LjkyLTguOTIgMS40MiAxLjQyIDEuNDEtMS40MS0xLjkyLTEuOTIgMy4xMi0zLjEyYy40LS40LjQtMS4wMy4wMS0xLjQybC0yLjM0LTIuMzRjLS4yLS4xOS0uNDUtLjI5LS43LS4yOXpNNi45MiAxOUw1IDE3LjA4bDguMDYtOC4wNiAxLjkyIDEuOTJMNi45MiAxOXoiXSxbInR5cGUiLCJidXR0b24iLDMsImRpc2FibGVkIiwiY2xpY2siXSxbMiwiaGVpZ2h0IiwiMTZweCJdLFsxLCJjbXlrLXRleHQiXSxbMSwiYm94Il0sWyJ0eXBlIiwibnVtYmVyIiwicGF0dGVybiIsIlswLTldKiIsIm1pbiIsIjAiLCJtYXgiLCIxMDAiLDMsInRleHQiLCJyZyIsInZhbHVlIiwia2V5dXAuZW50ZXIiLCJuZXdWYWx1ZSJdLFsidHlwZSIsIm51bWJlciIsInBhdHRlcm4iLCJbMC05XSsoW1xcLixdWzAtOV17MSwyfSk/IiwibWluIiwiMCIsIm1heCIsIjEiLCJzdGVwIiwiMC4xIiwzLCJ0ZXh0IiwicmciLCJ2YWx1ZSIsImtleXVwLmVudGVyIiwibmV3VmFsdWUiLDQsIm5nSWYiXSxbNCwibmdJZiJdLFsidHlwZSIsIm51bWJlciIsInBhdHRlcm4iLCJbMC05XSsoW1xcLixdWzAtOV17MSwyfSk/IiwibWluIiwiMCIsIm1heCIsIjEiLCJzdGVwIiwiMC4xIiwzLCJ0ZXh0IiwicmciLCJ2YWx1ZSIsImtleXVwLmVudGVyIiwibmV3VmFsdWUiXSxbMSwiaHNsYS10ZXh0Il0sWyJ0eXBlIiwibnVtYmVyIiwicGF0dGVybiIsIlswLTldKiIsIm1pbiIsIjAiLCJtYXgiLCIzNjAiLDMsInRleHQiLCJyZyIsInZhbHVlIiwia2V5dXAuZW50ZXIiLCJuZXdWYWx1ZSJdLFsxLCJyZ2JhLXRleHQiXSxbInR5cGUiLCJudW1iZXIiLCJwYXR0ZXJuIiwiWzAtOV0qIiwibWluIiwiMCIsIm1heCIsIjI1NSIsMywidGV4dCIsInJnIiwidmFsdWUiLCJrZXl1cC5lbnRlciIsIm5ld1ZhbHVlIl0sWzEsImhleC10ZXh0Il0sWzMsInRleHQiLCJ2YWx1ZSIsImJsdXIiLCJrZXl1cC5lbnRlciIsIm5ld1ZhbHVlIl0sWzEsInZhbHVlLXRleHQiXSxbMSwidHlwZS1wb2xpY3kiXSxbMSwidHlwZS1wb2xpY3ktYXJyb3ciLDMsImNsaWNrIl0sWzEsInByZXNldC1hcmVhIl0sWzEsInByZXNldC1sYWJlbCJdLFszLCJjbGFzcyIsNCwibmdJZiJdLFsiY2xhc3MiLCJwcmVzZXQtY29sb3IiLDMsImJhY2tncm91bmRDb2xvciIsImNsaWNrIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbMSwicHJlc2V0LWNvbG9yIiwzLCJjbGljayJdLFszLCJjbGFzcyIsImNsaWNrIiw0LCJuZ0lmIl0sWzMsImNsaWNrIl0sWzEsImJ1dHRvbi1hcmVhIl0sWyJ0eXBlIiwiYnV0dG9uIiwzLCJjbGFzcyIsImNsaWNrIiw0LCJuZ0lmIl0sWyJ0eXBlIiwiYnV0dG9uIiwzLCJjbGljayJdLFsxLCJleHRyYS10ZW1wbGF0ZSJdLFs0LCJuZ1RlbXBsYXRlT3V0bGV0Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJkaXYiLDAsMSksUCgiY2xpY2siLGZ1bmN0aW9uKG8pe3JldHVybiBvLnN0b3BQcm9wYWdhdGlvbigpfSksRSgyLFBLZSwxLDUsImRpdiIsMiksRSgzLFJLZSwyLDgsImRpdiIsMyksXyg0LCJkaXYiLDQpKDUsImRpdiIsNSksTyg2LCJkaXYiLDYpLF8oNywiZGl2Iiw3KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5leWVEcm9wcGVyU3VwcG9ydGVkJiZpLmNwRXllRHJvcHBlciYmaS5vbkV5ZURyb3BwZXIoKX0pLEUoOCxPS2UsMywwLCJzdmciLDgpLHYoKSxFKDksa0tlLDIsNSwiYnV0dG9uIiw5KSx2KCksXygxMCwiZGl2IiwxMCksRSgxMSxGS2UsMSwwLCJkaXYiLDExKSxfKDEyLCJkaXYiLDEyLDEzKSxQKCJuZXdWYWx1ZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25IdWVDaGFuZ2Uobyl9KSgiZHJhZ1N0YXJ0IixmdW5jdGlvbigpe3JldHVybiBpLm9uRHJhZ1N0YXJ0KCJodWUiKX0pKCJkcmFnRW5kIixmdW5jdGlvbigpe3JldHVybiBpLm9uRHJhZ0VuZCgiaHVlIil9KSxPKDE0LCJkaXYiLDE0KSx2KCksXygxNSwiZGl2IiwxNSwxNiksUCgibmV3VmFsdWUiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uVmFsdWVDaGFuZ2Uobyl9KSgiZHJhZ1N0YXJ0IixmdW5jdGlvbigpe3JldHVybiBpLm9uRHJhZ1N0YXJ0KCJ2YWx1ZSIpfSkoImRyYWdFbmQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25EcmFnRW5kKCJ2YWx1ZSIpfSksTygxNywiZGl2IiwxNCksdigpLF8oMTgsImRpdiIsMTcsMTgpLFAoIm5ld1ZhbHVlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vbkFscGhhQ2hhbmdlKG8pfSkoImRyYWdTdGFydCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkRyYWdTdGFydCgiYWxwaGEiKX0pKCJkcmFnRW5kIixmdW5jdGlvbigpe3JldHVybiBpLm9uRHJhZ0VuZCgiYWxwaGEiKX0pLE8oMjAsImRpdiIsMTQpLHYoKSgpKCksRSgyMSxCS2UsMTcsMTIsImRpdiIsMTkpLEUoMjIsVUtlLDE0LDEwLCJkaXYiLDIwKSxFKDIzLEdLZSwxNCwxMCwiZGl2IiwyMSksRSgyNCxZS2UsOCw3LCJkaXYiLDIyKSxFKDI1LFFLZSw5LDMsImRpdiIsMjMpLEUoMjYsS0tlLDMsMCwiZGl2IiwyNCksRSgyNyx0WmUsNiwzLCJkaXYiLDI1KSxFKDI4LHJaZSwzLDIsImRpdiIsMjYpLEUoMjksc1plLDIsMSwiZGl2IiwyNyksdigpKSwyJmUmJihQdCgiZGlzcGxheSIsaS5zaG93PyJibG9jayI6Im5vbmUiKSgidmlzaWJpbGl0eSIsaS5oaWRkZW4/ImhpZGRlbiI6InZpc2libGUiKSgidG9wIixpLnRvcCwicHgiKSgibGVmdCIsaS5sZWZ0LCJweCIpKCJwb3NpdGlvbiIsaS5wb3NpdGlvbikoImhlaWdodCIsaS5jcEhlaWdodCwicHgiKSgid2lkdGgiLGkuY3BXaWR0aCwicHgiKSxldCgib3BlbiIsaS5zaG93KSxDKDIpLHkoIm5nSWYiLCJwb3B1cCI9PT1pLmNwRGlhbG9nRGlzcGxheSksQygxKSx5KCJuZ0lmIiwxPT09KGkuY3BDb2xvck1vZGV8fDEpKSxDKDQpLFB0KCJiYWNrZ3JvdW5kLWNvbG9yIixpLnNlbGVjdGVkQ29sb3IpKCJjdXJzb3IiLGkuZXllRHJvcHBlclN1cHBvcnRlZCYmaS5jcEV5ZURyb3BwZXI/InBvaW50ZXIiOm51bGwpLEMoMSkseSgibmdJZiIsaS5leWVEcm9wcGVyU3VwcG9ydGVkJiZpLmNwRXllRHJvcHBlciksQygxKSx5KCJuZ0lmIixpLmNwQWRkQ29sb3JCdXR0b24pLEMoMikseSgibmdJZiIsImRpc2FibGVkIj09PWkuY3BBbHBoYUNoYW5uZWwpLEMoMSksUHQoImRpc3BsYXkiLDE9PT0oaS5jcENvbG9yTW9kZXx8MSk/ImJsb2NrIjoibm9uZSIpLHkoInJnWCIsMSksQygyKSxQdCgibGVmdCIsbnVsbD09aS5zbGlkZXI/bnVsbDppLnNsaWRlci5oLCJweCIpLEMoMSksUHQoImRpc3BsYXkiLDI9PT0oaS5jcENvbG9yTW9kZXx8MSk/ImJsb2NrIjoibm9uZSIpLHkoInJnWCIsMSksQygyKSxQdCgicmlnaHQiLG51bGw9PWkuc2xpZGVyP251bGw6aS5zbGlkZXIudiwicHgiKSxDKDEpLFB0KCJkaXNwbGF5IiwiZGlzYWJsZWQiPT09aS5jcEFscGhhQ2hhbm5lbD8ibm9uZSI6ImJsb2NrIikoImJhY2tncm91bmQtY29sb3IiLGkuYWxwaGFTbGlkZXJDb2xvcikseSgicmdYIiwxKSxDKDIpLFB0KCJsZWZ0IixudWxsPT1pLnNsaWRlcj9udWxsOmkuc2xpZGVyLmEsInB4IiksQygxKSx5KCJuZ0lmIiwhaS5jcERpc2FibGVJbnB1dCYmMT09PShpLmNwQ29sb3JNb2RlfHwxKSksQygxKSx5KCJuZ0lmIiwhaS5jcERpc2FibGVJbnB1dCYmMT09PShpLmNwQ29sb3JNb2RlfHwxKSksQygxKSx5KCJuZ0lmIiwhaS5jcERpc2FibGVJbnB1dCYmMT09PShpLmNwQ29sb3JNb2RlfHwxKSksQygxKSx5KCJuZ0lmIiwhaS5jcERpc2FibGVJbnB1dCYmMT09PShpLmNwQ29sb3JNb2RlfHwxKSksQygxKSx5KCJuZ0lmIiwhaS5jcERpc2FibGVJbnB1dCYmMj09PShpLmNwQ29sb3JNb2RlfHwxKSksQygxKSx5KCJuZ0lmIiwhaS5jcERpc2FibGVJbnB1dCYmMT09PShpLmNwQ29sb3JNb2RlfHwxKSksQygxKSx5KCJuZ0lmIiwobnVsbD09aS5jcFByZXNldENvbG9ycz9udWxsOmkuY3BQcmVzZXRDb2xvcnMubGVuZ3RoKXx8aS5jcEFkZENvbG9yQnV0dG9uKSxDKDEpLHkoIm5nSWYiLGkuY3BPS0J1dHRvbnx8aS5jcENhbmNlbEJ1dHRvbiksQygxKSx5KCJuZ0lmIixpLmNwRXh0cmFUZW1wbGF0ZSkpfSxkZXBlbmRlbmNpZXM6W2RuLEJlLG9zLGNaZSx1WmVdLHN0eWxlczpbJy5jb2xvci1waWNrZXJ7cG9zaXRpb246YWJzb2x1dGU7ei1pbmRleDoxMDAwO3dpZHRoOjIzMHB4O2hlaWdodDphdXRvO2JvcmRlcjojNzc3IHNvbGlkIDFweDtjdXJzb3I6ZGVmYXVsdDstd2Via2l0LXVzZXItc2VsZWN0Om5vbmU7dXNlci1zZWxlY3Q6bm9uZTtiYWNrZ3JvdW5kLWNvbG9yOiNmZmZ9LmNvbG9yLXBpY2tlciAqe2JveC1zaXppbmc6Ym9yZGVyLWJveDttYXJnaW46MDtmb250LXNpemU6MTFweH0uY29sb3ItcGlja2VyIGlucHV0e3dpZHRoOjA7aGVpZ2h0OjI2cHg7bWluLXdpZHRoOjA7Zm9udC1zaXplOjEzcHg7dGV4dC1hbGlnbjpjZW50ZXI7Y29sb3I6IzAwMH0uY29sb3ItcGlja2VyIGlucHV0OmludmFsaWQsLmNvbG9yLXBpY2tlciBpbnB1dDotbW96LXVpLWludmFsaWQsLmNvbG9yLXBpY2tlciBpbnB1dDotbW96LXN1Ym1pdC1pbnZhbGlke2JveC1zaGFkb3c6bm9uZX0uY29sb3ItcGlja2VyIGlucHV0Ojotd2Via2l0LWlubmVyLXNwaW4tYnV0dG9uLC5jb2xvci1waWNrZXIgaW5wdXQ6Oi13ZWJraXQtb3V0ZXItc3Bpbi1idXR0b257bWFyZ2luOjA7LXdlYmtpdC1hcHBlYXJhbmNlOm5vbmV9LmNvbG9yLXBpY2tlciAuYXJyb3d7cG9zaXRpb246YWJzb2x1dGU7ei1pbmRleDo5OTk5OTk7d2lkdGg6MDtoZWlnaHQ6MDtib3JkZXItc3R5bGU6c29saWR9LmNvbG9yLXBpY2tlciAuYXJyb3cuYXJyb3ctdG9we2xlZnQ6OHB4O2JvcmRlci13aWR0aDoxMHB4IDVweDtib3JkZXItY29sb3I6Izc3NyByZ2JhKDAsMCwwLDApIHJnYmEoMCwwLDAsMCkgcmdiYSgwLDAsMCwwKX0uY29sb3ItcGlja2VyIC5hcnJvdy5hcnJvdy1ib3R0b217dG9wOi0yMHB4O2xlZnQ6OHB4O2JvcmRlci13aWR0aDoxMHB4IDVweDtib3JkZXItY29sb3I6cmdiYSgwLDAsMCwwKSByZ2JhKDAsMCwwLDApICM3NzcgcmdiYSgwLDAsMCwwKX0uY29sb3ItcGlja2VyIC5hcnJvdy5hcnJvdy10b3AtbGVmdCwuY29sb3ItcGlja2VyIC5hcnJvdy5hcnJvdy1sZWZ0LXRvcHtyaWdodDotMjFweDtib3R0b206OHB4O2JvcmRlci13aWR0aDo1cHggMTBweDtib3JkZXItY29sb3I6cmdiYSgwLDAsMCwwKSByZ2JhKDAsMCwwLDApIHJnYmEoMCwwLDAsMCkgIzc3N30uY29sb3ItcGlja2VyIC5hcnJvdy5hcnJvdy10b3AtcmlnaHQsLmNvbG9yLXBpY2tlciAuYXJyb3cuYXJyb3ctcmlnaHQtdG9we2JvdHRvbTo4cHg7bGVmdDotMjBweDtib3JkZXItd2lkdGg6NXB4IDEwcHg7Ym9yZGVyLWNvbG9yOnJnYmEoMCwwLDAsMCkgIzc3NyByZ2JhKDAsMCwwLDApIHJnYmEoMCwwLDAsMCl9LmNvbG9yLXBpY2tlciAuYXJyb3cuYXJyb3ctbGVmdCwuY29sb3ItcGlja2VyIC5hcnJvdy5hcnJvdy1sZWZ0LWJvdHRvbSwuY29sb3ItcGlja2VyIC5hcnJvdy5hcnJvdy1ib3R0b20tbGVmdHt0b3A6OHB4O3JpZ2h0Oi0yMXB4O2JvcmRlci13aWR0aDo1cHggMTBweDtib3JkZXItY29sb3I6cmdiYSgwLDAsMCwwKSByZ2JhKDAsMCwwLDApIHJnYmEoMCwwLDAsMCkgIzc3N30uY29sb3ItcGlja2VyIC5hcnJvdy5hcnJvdy1yaWdodCwuY29sb3ItcGlja2VyIC5hcnJvdy5hcnJvdy1yaWdodC1ib3R0b20sLmNvbG9yLXBpY2tlciAuYXJyb3cuYXJyb3ctYm90dG9tLXJpZ2h0e3RvcDo4cHg7bGVmdDotMjBweDtib3JkZXItd2lkdGg6NXB4IDEwcHg7Ym9yZGVyLWNvbG9yOnJnYmEoMCwwLDAsMCkgIzc3NyByZ2JhKDAsMCwwLDApIHJnYmEoMCwwLDAsMCl9LmNvbG9yLXBpY2tlciAuY3Vyc29ye3Bvc2l0aW9uOnJlbGF0aXZlO3dpZHRoOjE2cHg7aGVpZ2h0OjE2cHg7Ym9yZGVyOiMyMjIgc29saWQgMnB4O2JvcmRlci1yYWRpdXM6NTAlO2N1cnNvcjpkZWZhdWx0fS5jb2xvci1waWNrZXIgLmJveHtkaXNwbGF5OmZsZXg7cGFkZGluZzo0cHggOHB4fS5jb2xvci1waWNrZXIgLmxlZnR7cG9zaXRpb246cmVsYXRpdmU7cGFkZGluZzoxNnB4IDhweH0uY29sb3ItcGlja2VyIC5yaWdodHtmbGV4OjEgMSBhdXRvO3BhZGRpbmc6MTJweCA4cHh9LmNvbG9yLXBpY2tlciAuYnV0dG9uLWFyZWF7cGFkZGluZzowIDE2cHggMTZweDt0ZXh0LWFsaWduOnJpZ2h0fS5jb2xvci1waWNrZXIgLmJ1dHRvbi1hcmVhIGJ1dHRvbnttYXJnaW4tbGVmdDo4cHh9LmNvbG9yLXBpY2tlciAucHJlc2V0LWFyZWF7cGFkZGluZzo0cHggMTVweH0uY29sb3ItcGlja2VyIC5wcmVzZXQtYXJlYSAucHJlc2V0LWxhYmVse292ZXJmbG93OmhpZGRlbjt3aWR0aDoxMDAlO3BhZGRpbmc6NHB4O2ZvbnQtc2l6ZToxMXB4O3doaXRlLXNwYWNlOm5vd3JhcDt0ZXh0LWFsaWduOmxlZnQ7dGV4dC1vdmVyZmxvdzplbGxpcHNpcztjb2xvcjojNTU1fS5jb2xvci1waWNrZXIgLnByZXNldC1hcmVhIC5wcmVzZXQtY29sb3J7cG9zaXRpb246cmVsYXRpdmU7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2lkdGg6MThweDtoZWlnaHQ6MThweDttYXJnaW46NHB4IDZweCA4cHg7Ym9yZGVyOiNhOWE5YTkgc29saWQgMXB4O2JvcmRlci1yYWRpdXM6MjUlO2N1cnNvcjpwb2ludGVyfS5jb2xvci1waWNrZXIgLnByZXNldC1hcmVhIC5wcmVzZXQtZW1wdHktbWVzc2FnZXttaW4taGVpZ2h0OjE4cHg7bWFyZ2luLXRvcDo0cHg7bWFyZ2luLWJvdHRvbTo4cHg7Zm9udC1zdHlsZTppdGFsaWM7dGV4dC1hbGlnbjpjZW50ZXJ9LmNvbG9yLXBpY2tlciAuaGV4LXRleHR7d2lkdGg6MTAwJTtwYWRkaW5nOjRweCA4cHg7Zm9udC1zaXplOjExcHh9LmNvbG9yLXBpY2tlciAuaGV4LXRleHQgLmJveHtwYWRkaW5nOjAgMjRweCA4cHggOHB4fS5jb2xvci1waWNrZXIgLmhleC10ZXh0IC5ib3ggZGl2e2Zsb2F0OmxlZnQ7ZmxleDoxIDEgYXV0bzt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjojNTU1O2NsZWFyOmxlZnR9LmNvbG9yLXBpY2tlciAuaGV4LXRleHQgLmJveCBpbnB1dHtmbGV4OjEgMSBhdXRvO3BhZGRpbmc6MXB4O2JvcmRlcjojYTlhOWE5IHNvbGlkIDFweH0uY29sb3ItcGlja2VyIC5oZXgtYWxwaGEgLmJveCBkaXY6Zmlyc3QtY2hpbGQsLmNvbG9yLXBpY2tlciAuaGV4LWFscGhhIC5ib3ggaW5wdXQ6Zmlyc3QtY2hpbGR7ZmxleC1ncm93OjM7bWFyZ2luLXJpZ2h0OjhweH0uY29sb3ItcGlja2VyIC5jbXlrLXRleHQsLmNvbG9yLXBpY2tlciAuaHNsYS10ZXh0LC5jb2xvci1waWNrZXIgLnJnYmEtdGV4dCwuY29sb3ItcGlja2VyIC52YWx1ZS10ZXh0e3dpZHRoOjEwMCU7cGFkZGluZzo0cHggOHB4O2ZvbnQtc2l6ZToxMXB4fS5jb2xvci1waWNrZXIgLmNteWstdGV4dCAuYm94LC5jb2xvci1waWNrZXIgLmhzbGEtdGV4dCAuYm94LC5jb2xvci1waWNrZXIgLnJnYmEtdGV4dCAuYm94e3BhZGRpbmc6MCAyNHB4IDhweCA4cHh9LmNvbG9yLXBpY2tlciAudmFsdWUtdGV4dCAuYm94e3BhZGRpbmc6MCA4cHggOHB4fS5jb2xvci1waWNrZXIgLmNteWstdGV4dCAuYm94IGRpdiwuY29sb3ItcGlja2VyIC5oc2xhLXRleHQgLmJveCBkaXYsLmNvbG9yLXBpY2tlciAucmdiYS10ZXh0IC5ib3ggZGl2LC5jb2xvci1waWNrZXIgLnZhbHVlLXRleHQgLmJveCBkaXZ7ZmxleDoxIDEgYXV0bzttYXJnaW4tcmlnaHQ6OHB4O3RleHQtYWxpZ246Y2VudGVyO2NvbG9yOiM1NTV9LmNvbG9yLXBpY2tlciAuY215ay10ZXh0IC5ib3ggZGl2Omxhc3QtY2hpbGQsLmNvbG9yLXBpY2tlciAuaHNsYS10ZXh0IC5ib3ggZGl2Omxhc3QtY2hpbGQsLmNvbG9yLXBpY2tlciAucmdiYS10ZXh0IC5ib3ggZGl2Omxhc3QtY2hpbGQsLmNvbG9yLXBpY2tlciAudmFsdWUtdGV4dCAuYm94IGRpdjpsYXN0LWNoaWxke21hcmdpbi1yaWdodDowfS5jb2xvci1waWNrZXIgLmNteWstdGV4dCAuYm94IGlucHV0LC5jb2xvci1waWNrZXIgLmhzbGEtdGV4dCAuYm94IGlucHV0LC5jb2xvci1waWNrZXIgLnJnYmEtdGV4dCAuYm94IGlucHV0LC5jb2xvci1waWNrZXIgLnZhbHVlLXRleHQgLmJveCBpbnB1dHtmbG9hdDpsZWZ0O2ZsZXg6MTtwYWRkaW5nOjFweDttYXJnaW46MCA4cHggMCAwO2JvcmRlcjojYTlhOWE5IHNvbGlkIDFweH0uY29sb3ItcGlja2VyIC5jbXlrLXRleHQgLmJveCBpbnB1dDpsYXN0LWNoaWxkLC5jb2xvci1waWNrZXIgLmhzbGEtdGV4dCAuYm94IGlucHV0Omxhc3QtY2hpbGQsLmNvbG9yLXBpY2tlciAucmdiYS10ZXh0IC5ib3ggaW5wdXQ6bGFzdC1jaGlsZCwuY29sb3ItcGlja2VyIC52YWx1ZS10ZXh0IC5ib3ggaW5wdXQ6bGFzdC1jaGlsZHttYXJnaW4tcmlnaHQ6MH0uY29sb3ItcGlja2VyIC5odWUtYWxwaGF7YWxpZ24taXRlbXM6Y2VudGVyO21hcmdpbi1ib3R0b206M3B4fS5jb2xvci1waWNrZXIgLmh1ZXtkaXJlY3Rpb246bHRyO3dpZHRoOjEwMCU7aGVpZ2h0OjE2cHg7bWFyZ2luLWJvdHRvbToxNnB4O2JvcmRlcjpub25lO2N1cnNvcjpwb2ludGVyO2JhY2tncm91bmQtc2l6ZToxMDAlIDEwMCU7YmFja2dyb3VuZC1pbWFnZTp1cmwoZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFKWUFBQUFRQ0FZQUFBRDA2SVluQUFBQUJtSkxSMFFBL3dEL0FQK2d2YWVUQUFBQUNYQklXWE1BQUFzVEFBQUxFd0VBbXB3WUFBQUFCM1JKVFVVSDRBSVdEd2tVRldiQ0NBQUFBRnhKUkVGVWFON3Qwa0VLZzBBUUFNRTJ4ODMvbjJxdTVxQ2dEMWlEaENvWWRwbmJRQzliYlkxcVZPL2p2YzZrM2FkOTFzNy83RjEvY3NnUHJ1anVRMTdCRFlTRnNCQVd3Z0poSVN5RUJjSkNXQWdMaElXd0VCWUlpMmY3QXIvMVRDZ0ZIMlg5QUFBQUFFbEZUa1N1UW1DQyl9LmNvbG9yLXBpY2tlciAudmFsdWV7ZGlyZWN0aW9uOnJ0bDt3aWR0aDoxMDAlO2hlaWdodDoxNnB4O21hcmdpbi1ib3R0b206MTZweDtib3JkZXI6bm9uZTtjdXJzb3I6cG9pbnRlcjtiYWNrZ3JvdW5kLXNpemU6MTAwJSAxMDAlO2JhY2tncm91bmQtaW1hZ2U6dXJsKGRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBSllBQUFBUUNBWUFBQUQwNklZbkFBQUNUa2xFUVZSNDJ1M1NZVWNyQUJoQTRVMlNrbVJKTW1XU0prbEtKaVdaWnBLVUpKc2tLVW1hVEZJbUtaT1V6TXlTcEdSbWxpUk5KaWxKU3BLU0pFdG1TcElwbVdtU2RPNzM2LzZEK3g3T1AzZ1VDb1dDdjFjcWxTUWxKWkdjbkV4S1NncXBxYW1rcGFXUm5wNU9Sa1lHbVptWnFGUXFzckt5eU03T0ppY25oOXpjWE5ScU5YbDVlZVRuNTZQUmFDZ29LS0N3c0pDaW9pSzBXaTNGeGNXVWxKUlFXbHBLV1ZrWjVlWGxWRlJVVUZsWmlVNm5vNnFxaXVycWFtcHFhcWl0cmFXdXJnNjlYazk5ZlQwR2d3R2owVWhEUXdPTmpZMDBOVFhSM054TVMwc0xyYTJ0dExXMTBkN2Vqc2xrd213MjA5SFJRV2RuSjExZFhYUjNkOVBUMDBOdmJ5OTlmWDMwOS9jek1EREE0T0FnRm91Rm9hRWhyRllydzhQRGpJeU1NRG82eXRqWUdEYWJqZkh4Y1NZbUpwaWNuR1JxYWdxNzNjNzA5RFF6TXpQTXpzNHlOemZIL1B3OERvY0RwOU9KeStYQzdYYXpzTERBNHVJaVMwdExMQzh2czdLeXd1cnFLbXRyYTNnOEhyeGVMejZmRDcvZnovcjZPaHNiRzJ4dWJySzF0Y1gyOWphQlFJQ2RuUjJDd1NDN3U3dnM3ZTJ4djcvUHdjRUJoNGVISEIwZGNYeDh6TW5KQ2FlbnA1eWRuWEYrZnM3RnhRV1hsNWRjWFYxeGZYM056YzBOdDdlMzNOM2RFUXFGdUwrLzUrSGhnWEE0VENRUzRmSHhrYWVuSjU2Zm4zbDVlZUgxOVpWb05NcmIyeHZ2Nys5OGZId1FpOFdJeCtOOGZuNlNTQ1Q0K3ZyaSsvdWJuNThmZm45LytWY0tnU1d3QkpiQUVsZ0NTMkFKTElFbHNBU1d3QkpZQWt0Z0NTeUJKYkFFbHNBU1dBSkxZQWtzZ1NXd0JKYkFFbGdDUzJBSkxJRWxzUDQvV0g4QW1KNVo2akhTNGg4QUFBQUFTVVZPUks1Q1lJST0pfS5jb2xvci1waWNrZXIgLmFscGhhe2RpcmVjdGlvbjpsdHI7d2lkdGg6MTAwJTtoZWlnaHQ6MTZweDtib3JkZXI6bm9uZTtjdXJzb3I6cG9pbnRlcjtiYWNrZ3JvdW5kLXNpemU6MTAwJSAxMDAlO2JhY2tncm91bmQtaW1hZ2U6dXJsKGRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBSllBQUFBUUNBWUFBQUQwNklZbkFBQUFCbUpMUjBRQS93RC9BUCtndmFlVEFBQUFDWEJJV1hNQUFBc1RBQUFMRXdFQW1wd1lBQUFBQjNSSlRVVUg0QUlXRHdZUWxaTWEzZ0FBQVdWSlJFRlVhTjd0bUVHTzZqQVFSQ3NPQXJIZ0JweUFKWUdqY0dvY3hBbTRBMklIcG1vV0UwZUJIK2V6bUZsTnZVMDZzaEozVzZWRWVsV01VUUFJSUY5ZjZxWnBpbXNBMUxZdFMydUY1MS91MjdZVkFGWlZSVWtFb0dIZFBWL3NJY2JJRUlJa1VkSS85WGE3bmV5djYxK1NXRlVWQVZDU2N0MDBUV24yZnY2dTMrRWNmZDN0WHp5LzArbkVVdStTUGpvL2txenJtaVFwU2NONnY5OFhld2ZBOC9sTWtpTEoyV3hHU1VvcGNUNmZNNlUwTlg5L2ZyZmJqZXYxV3RmcmxaZkxoWWZEUVFIRy9BSU9sbkd3aklObEhDeGpIQ3pqWUptL1RKV2RDd3F1SlhzZUZGekd3RE5OZWlLTU9KVE84eFFkRFFhZUIyOStLOWVmZUxhQm85Sjd2ZHZ0SmoxUmpGRmpmaXY3cXY5NXRqeC83bGVTUWdoOTNlMWZmTWVJcDZPK1lRamhvL043OTF0MVhWT1NTSTdOLy9LKzQvR294V0xCeCtQQjUvT3A1WExKKy8zT2xKSldxeFUzbTgzb3Z2NWlHZjhLallObEhDeGpIQ3pqWUJrSHk1Z2Y1Z3VzdlFVN1UzN2pUQUFBQUFCSlJVNUVya0pnZ2c9PSl9LmNvbG9yLXBpY2tlciAudHlwZS1wb2xpY3l7cG9zaXRpb246YWJzb2x1dGU7dG9wOjIxOHB4O3JpZ2h0OjEycHg7d2lkdGg6MTZweDtoZWlnaHQ6MjRweDtiYWNrZ3JvdW5kLXNpemU6OHB4IDE2cHg7YmFja2dyb3VuZC1pbWFnZTp1cmwoZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFCSUFBQUFnQ0FZQUFBQWZmQ2p4QUFBQUJITkNTVlFJQ0FnSWZBaGtpQUFBQUFsd1NGbHpBQUFDZXdBQUFuc0IwMUNPM0FBQUFCbDBSVmgwVTI5bWRIZGhjbVVBZDNkM0xtbHVhM05qWVhCbExtOXlaNXZ1UEJvQUFBSUFTVVJCVkVpSjdaWTlheFJSRklhZnN4TVN0ckxRSnBBZ3BCRmhpK0M5dzFZU28wMEk2UlovZzl2WnBCZi9RT3I0R3lSZ2tTS05TckFhZHNacVFHd0NrdUFXeVJaSnN5U3d2aFo3Ti92aHpyZ2JMSDNMZDg1OTdqbHp6NTB6Sm9reXhYSDhEcURWYXIwcWk2djhCYkl0cVNHcEVjZnhkbG1zRldYa3ZYOEFmQVZXZzNVS1BFblQ5R0t1ak16c0FGZ1pzVmFDTjFWVFFkNzdYVW5yZ0Uxa3YrNjkzNTI2OFdScHpybkhadllSV0M3WXZDM3BSWlpsM3dvenF0VnFpeUg5SWdqQXNwa2QxR3ExeFVKUXRWcmRCOVpLSUFPdGhkZy9RYzY1TFVrN3dOSU1vQ1ZKTzg2NXJZRmhrcWpYNi9kN3ZWNEdQSndCTXFvZlVSUzVKRWs2RllCZXIvZWVZYi9NbzlXd0ZuUE92UWJlQXZmdUFBSzRCTjRzQUp0QUcvZ0pJRWxtTnVpSnliYTNFR05tWmlQZVp1RVZtVmVsbC9ZLzZOK0N6RG4zQVhoRU9PbzdIdi8zQmVBejhJelFrTVBuSmJ1UHgxd0MreVlKNy8wbllJUDVTLzBGSEtkcCtyd0NFRVhSUy9yZjVIbDFHdGIyTTBpU3BDT3BDWnpQQVRtWDFFeVNwSE1MQXNpeTdNak1Eb0hyR1NEWFpuYVlaZG5Sd0JoN0o5MXV0d21jekFBNkNiRzNHZ1BsZVg0anFVSC9hMUNrdHFSR251YzNoU0NBTUIzMmdLc3BrQ3RnYjNLQ1FNbWtqZVA0V05KVGhyTk5admFsMVdwdFRJc3Y3SnRRNHRtSWRSYThxU29FcFdsNllXWk5vQU4wekt4Wk5QZWhwTFNCWnYydCtRMENKOWxMbkFSUUxBQUFBQUJKUlU1RXJrSmdnZz09KTtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7YmFja2dyb3VuZC1wb3NpdGlvbjpjZW50ZXJ9LmNvbG9yLXBpY2tlciAudHlwZS1wb2xpY3kgLnR5cGUtcG9saWN5LWFycm93e2Rpc3BsYXk6YmxvY2s7d2lkdGg6MTAwJTtoZWlnaHQ6NTAlfS5jb2xvci1waWNrZXIgLnNlbGVjdGVkLWNvbG9ye3Bvc2l0aW9uOmFic29sdXRlO3RvcDoxNnB4O2xlZnQ6OHB4O3dpZHRoOjQwcHg7aGVpZ2h0OjQwcHg7Ym9yZGVyOjFweCBzb2xpZCAjYTlhOWE5O2JvcmRlci1yYWRpdXM6NTAlfS5jb2xvci1waWNrZXIgLnNlbGVjdGVkLWNvbG9yLWJhY2tncm91bmR7d2lkdGg6NDBweDtoZWlnaHQ6NDBweDtib3JkZXItcmFkaXVzOjUwJTtiYWNrZ3JvdW5kLWltYWdlOnVybChkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUNnQUFBQW9DQVlBQUFDTS9yaHRBQUFBaDBsRVFWUllSKzJXMFFsQU1RZ0Q2MHpkZndPZHFhOFRtSS93UU1yNUswSTViWkxJekxPYTJudDM3VlZWYmQrZER4NW9iZ0NDM0tCTHdKMmZmNFBuVmlka2YrdWNJaHc4MEhRYUNMbzNETUgzQ1JLM2lGc21BV1ZsNmhQTkR3dDhFdk5FNXErWXVFWGNNZ2tvblZNNlNkeUNvRXZBblo4djFIang4MTdNaWxteFNVQjVyZExKRHljWmdVQVpVY2gvQUFBQUFFbEZUa1N1UW1DQyl9LmNvbG9yLXBpY2tlciAuc2F0dXJhdGlvbi1saWdodG5lc3N7ZGlyZWN0aW9uOmx0cjt3aWR0aDoxMDAlO2hlaWdodDoxMzBweDtib3JkZXI6bm9uZTtjdXJzb3I6cG9pbnRlcjt0b3VjaC1hY3Rpb246bWFuaXB1bGF0aW9uO2JhY2tncm91bmQtc2l6ZToxMDAlIDEwMCU7YmFja2dyb3VuZC1pbWFnZTp1cmwoZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFPWUFBQUNDQ0FZQUFBQlNEN1QzQUFBQUJtSkxSMFFBL3dEL0FQK2d2YWVUQUFBQUNYQklXWE1BQUFzVEFBQUxFd0VBbXB3WUFBQUFCM1JKVFVVSDRBSVdEd2tzUFdSNmxnQUFJQUJKUkVGVWVOcnRuVnVUNDdnUnJBSE4rUC8vT3IvNjFZNXdPTlo3bVoxdTNYQWVMTWpKWkdaVmdkS3NmYzV4UjNTMFJJSVVXK0NIekNwYzJNY1lvN1hHdjNleDdVaVpkNTdyanl6enYrdiszM1gvUi8rM3IvZjd2UjM4NlkrVHZLTmNmL3dkaFRMUGN2OXFVMndaZDc0dXRoMHQxODIxamtJWkxQY3NJLzZuV2E0WHZ1dHF1VTBaODVtbng4MFMvWnpncG5Mbk90SE50Ny9vZngxVEtYY1NOek4vN3FiTVEzanU3ck5RbU1ZWWQvNHMyajlhYStQK2dHYU1jWnJiMU0vdGRydmY3L2Qydjk5UDkvdDkzTy8zY2J2ZHh1MTJHOWZyZFZ3dWwzRStuOGMvLy9uUCsyKy8vWGI2NmFlZnhsLy8rdGZ4NXovLzJZSzVBbDJyZ3ZmNFVzYnBkR3JCNTJiQXZBclhwdXpqbWlxQVZTR3o1ZURtR1lYemhiQVptQ3JubXpkZHBVVSs4WTFkQU9ZZVhDdERVd1Z3VjdZQ0dINnVBbXlNY1o5bDV2a1VhQlBHTVVaNy9KNXcvNzkyL2Z2djlYcTkzMjYzZHIvZlR4UEVDZU1FOG5LNWpNL1B6L0hUVHovZHYzMzdkdnJsbDEvR1AvN3hqL0czdi8xdC9PVXZmd2tWc3dvbmdqZE9wOVB6SDNVM0Qzem1XR25aVlhuNGpDcXM3d0MyQktQNC84dEF6a1pzb1d4NlhycWVIWnltdnA0QUJDQkpoVFF3S2ZEVDhnenJaQ0lxaTVBaGlBQ2pCZkVCMnJQOC9YNjNNTTdmNi9WNnY5L3Y3WGE3YllDODNXN2pjcmxzVkhJcTVmZnYzMCsvL2ZiYitPV1hYOFpQUC8wMC92NzN2NCtmZi83NUpTdmJldStiTDJXTU1hRmJBbHBCTk04NVFYK2N0NnFvU3FrUEF3dVFsQlZLcUdORlNVT0FBM0JtdTdnQzVoTk9kMTVuU3d2QU9VVzdDNGdpVUNWOFNnbjVMOWhORklxVHNwMEd4STB5c2lveWpBamtZL3RHSlZFcHorZnorT1dYWCs3ZnYzOC8vZjc3NytQYnQyL2oxMTkvSFQvLy9QUDQ5ZGRmeDhmSFJ3cm1UalY3NzlFWHUycHgyeGhqd3RkSlpRY0FXUUlQTFBJU3NNSmFTd2lEOGd6SUtyd1N5QVRFNWo1bkFiUjVjMWRCVXdCbHNFV1cwaDZMcWlZc3FGUEFReEN5Ulozd09TQVJ4bWxYTVg1azY0cFFmdnYyN2Y3NStkaytQajVPSHg4ZjQvdjM3K1BidDIvanQ5OStHOSsrZlJzZkh4L2pjcm1VRkxPMzFnWURXYmx4UklzL1RxZlQ3b3VzeEpzQXhYQTJHYzdUQTlYZGdmZG9IYkZzajc2WDIrMVdBcmdJMWFnZUd3QTNxdXBxb0hzbWNiSTZGdTkzcXVnZ0ZhOWQ3TGVEdGdLZkFGSEJKK05FQnlJa2NKNUtlcnZkVG1oaEdjZ0pKU1o1dm4vL2ZqK2Z6KzE4UHA4K1B6L0g1K2ZubUdEKy92dnY0L3YzNytQajQyTjhmbjZPMisxV3M3SmpqUDZ3cmFNSTVFNFJaOHgydlY1VFN3a3F1b3RWNy9kN1R6NkhGV3NEL3FOY2R3MENRM3EvMzIxYzY4NlR3RFZJZGJ1eTczek5sZGhTSGI4STJrbFp6bm0rSW5CUzRVNm4wMzAyYUJGc0xoSERBS0pWSlZnbGZJOWpodnU1M1c1M3NMQU5ZTnhBaURBNk1DZVVIeDhmOSt2MTJpNlhTN3RjTHFjWlc1N1A1eWVZOC9mejgzT2N6K2Zuc1NtWVV5a25XRUc4NVdCc3Q5c3R6U0x5TWRmcjlRaTA4aVkxNVVaMExsREdMaFIzbzV6SzJqN09QVVREMEUrblUzdGs3WGIvMTZORmJobG9BTXVZMXpqTFVPTzNCS2VJRGUrWjhzMy9KNGdGbzRUTTVqUG11UmcyOGZvVUtLVlN3bzE2VGdBNW5weXdjV0xIZ1lsL1B6OC83My82MDUvYWI3LzkxbTYzVzd0Y0xpZTBzWmo0bWFvNWdUeWZ6ODhFMGYxK2o4RWNZendUUEVHMmNxanlmSE5GME04ZnVxRWlhT1ZuUnpaWlFOaDVmd1F5SGcvSERHZkpvODlRMXpiL3F1dTVYQzY3NzNJMlhLZlRxZC92OStkM3d1cVd2YS9ZVGRVZEVWM2ZoSXYvVml5cHM2WUUzeDNyNDNLNWJKUVM2NnpheFZHRnN2ZCsvL2o0YUYrL2ZtM2Z2Mzl2dDl1dGZmMzZ0ZjMrKysvdGRydWR2bjM3WnVOTEJhYUNNZ1V6QytyWlJpRm93eFV1Skk4WU1xY0NwOU9wcTV2YWdhWVU2bEdKQTFYUXFlamNodzZDajBHdzVuWUJyR3cwMUEyTzIwNm4wNEJHb3VOTnlUZnAvRndFbGhVZXk2blhySUt3N1FRV2RkeHVOMmxkTDVmTDgzOWdTUEY4YWh1L0p2Qk80OENQU3VxTWY4VnA5L1A1M0w1OCtkTHU5M3M3bjgvdGZyOC8zOS92OS9iNStUa2hQSjNQNTZtUTQzNi9qKy9mdisvaVNnYnplcjArQVp4LzUrODhidjZPTWRhNlM1ejZrZDIxZllDOWR4djdjSUpKMmQ5QU9TMzBmUE16eUhpVE04QjRERjZYVWxZSHA0S1FXM1crMXQ3N01OQjF2R0h4V3E3WGE3dmY3OCt5NS9ONUErSDFldDI5eHVQNWRiWXR5YVJ1NEFrc2JQcTY5MzZmalJ6WFJ4QmJQci9iK2IxOCtmS2xqVEhhQkJCZm44L24wLzErSDErK2ZCbm44em0wc0I4Zkg1dTRjcjVHdUJoTVZrMEVFbjlSc2N0Z1ZoTStpeGxKdE1BMjNSOEI2eXlzQXN0Qk9nRlhJS0tDTUlnVG9NcU5FdTJmWU1IN3p0YzczMmRRS2tDajF5dEFadFkwS3g4cElyOEdHSitBVDNWKzJIaXJobCsrZkJtWHkyV3o3M3crYjE3UDhwK2ZuOC90VXdHVmxlVmtUeVViNjhEa2ZheVdZNHp4TlJpaFU0RXBMSlBaVnJLK3U3SjQvbWdmS3FlTFc5WDJSRVdsSXRMMWRpeW5iRERiMytqWGdZalFxbjBycnhXYytOa0lMUDdGN3hJYk12eDd2VjUzeDQweG5sYldKRjEyWlNhZy9OMHBXNnQrWnptT016SGphakt3RGZvbmQ3OHpZVGRmcTE4dXA5N3pyMnE4djNJaW9CcHJSdEJsMEVaOW9nNVdCUkdPZE9IaklqWEY3VW90RmJnT1duWHpJSnl6WXZqRzVJWWdzbU1PeEhrejhPc01TclZOV2VxNVQ4RGFPY2JFdjFPZDVyYnM5YU83WXZNZXQ2M0VrRisrZk1FeHErTVJsNC9MNWJMWk4vK2V6K2ZuWjZLYXp1TXFYU1FWTzVzcEpYZmxIQUl6ZXMveEpzZWNrUkppRE1vZzlkNlZmUnJxWE1yNktwVlYyN2pSd0phY0dvdk9BTTF6TWRRTW53SzFBdWJLNjNrZENDaHZJMUM3ZzB6OW5mL0QrWHplMlZqOEg3R3g0UDlkdVFsc1lDcnF5TjhYcUczSG0vMTBPajNqdy9uK2NybHN0dU0ralBtbXhUMmRUdVB6ODNQenQycG4xWHNFSFgvYm5QYVZxVm1oMHh3T3QwbzZYTExBSGVQVVUyMDN3SGZjcnNwQ3dtVjNUcnlCNXMwTXNlZWc5N3gvQnd6Q2pCbGJCK3BSQVBsYTBCVlF1VDZWNlFIZEJsajNkMEtHMTQ3YitEcXhRZVV5bURPNDNXNGRRYXIrVElqd21BZDB6OC9oNjV2ZjAveUx2M1BiNVhMcHJ1L3lkRG85czdFVDBJK1BqNmRLSzlWVUVJZUtXUVdQQU9ySjhMS2Q0dkUrdDkxWTNlN1VGbFdhdGcyVndKbmIrSFBtdHZtL3NmSzU5L09hV0YzeC9lUDFVUEh2QTVERFlEcFlYZmIwZHJ2MVYyRGtCa3h0dy90RVdWVmxYV2RDOXBGWXM1L2pmaDlkUy8xNnZXN3M2bFRHK1RmcXN4U0pIeGtYWHEvWGRyMWV1NExzZkQ2UDN2c1QzTjc3RGtMK3pQbTVqU2RLTDR6UjNBeFFkNnJIa0xrWWxTb3dzcnE3em56dTZ3U3dkc01KT1htQTVmQmNqeHRnTUdCWUhscjV6b2todHNNQ1RnWExRT1c0WEM2ZEV5RU1wckw4bUFRelhSZ2R1aXgyeVp6b3J4a1lzRG4zaEIxVmVNTEdzWHNWdGdsMnBXOFMzc3ZrMHZ3N1I0aE5hSHZ2NGNBQ2w1SEZ6d0lIMEtjNnp1NFhqRFBSL2pwQVZ4V3pPMVhrMkREYjN2VGN4ZUdVMWlXWkhrbUlEV3ppV0t2aXJDSjREcmF2czZJSi9HRzZjVHFXZFhEeStmQXJRRFZWa0xxa1ZqQW9aSUlUZG1tSXFYd3FhOTVOMytNR1lvWlFkUlZOTzUzWTF4UmtoTzE2dlk3ZXU1MDdDYTlsSm5iR3B4T2VtUWhTdy9BUXNtbXA1elU5QmlVOEc2d3ZYNzZNNi9VNlBqNCtkbzBCejRDcGdpa25UVWVEcXdsS0JtZzN1NE9WanJaMUErckFjZ2FlaldxNmVKQ3ZDWUZET05Td09nSFg0RVFSdzhseGJ6RE9kRUs2Z1ozSGsxYis4ZzJvMUpGdEtYeXYvZkVkVFh1V2pXWGRBWmlCcDZBRGVEckNGaWltN0I2WkZuZWVJN0d2bS9QTWtVRFg2N1c3eEk4YjBENy92OGRBOXFmTjVvYUNmNzRXWmpIMG1mMWNtZlkxWTBKVUZtVnJUV3U4dXprTmNMdEVqN3U1RlhCVGtmQzZHT0E1cThZTXhPOEtWdkY2c0FWR2RjclVic0tPRGNRS2tMTU9NZG1seHVtNjQyWXJQbTI2QWxoWlcxWUIxUityckdzd0U4VGFZQVdlVU14ZGYrV2p3U3ZaMkVmM3l0T3lmbjUrUHBWUEFhcU9uNDNNdE5CcXZtamp4YmpNNGxaalpZNGdxTk1JNWt0YVcvc1lLTndTKzlsRlF6R2lobU1DS1BhNytaMFY2RWIwR1Jtb2J0cFg4SmxqV3U1Rk1MTjVqYTZoRzlrd1FnWnFmNSsxTkg1VXh6a0ZSZUNkV2hKOFhkbEdVa3hPN0hSbFlSbTRtVk80M1c3dGVyMTJUUEpFdy9ybUVOM0w1U0tISVdaZzlteitwVW9LT1lxNWJKVEpkWDJnbWUxVWN4TVpRRmFFUUlsSGN0MzJNK1kxQnpHa0d1emZpeUFOOXordWdwbFoxc3ltQ3JEQ1lZa0d4RFRwSTlSekJ5MHJIeWVEVUMxbldhZVVhRDluNHhrTnlZTUJEWnR6WjNCKytmSmxZMjFYRkRPY0FSSmxhYk95aVMzdUNwTEk5anJaakNEa2FWdmNDQ2p3b2duS1NoV2R6WFpXbFpNdlZUZ0Q4THBxbENMcnFnYmNCK3FZd3JnS1lwVDBjY0NxYkt5Q1ZhbGtFYWJuL0Z5bm9nQ3JQS2ZxZjUxeEo3c0dCMlpYY1pteG9TT3p0angzMDBEWmk3YTAvMkFJUjBVbEJhZzlTdUR3NktjQXpsYUI3dkhadldwaks5MGR5cnE2Ykt5RFVaUWJSMEIwNWJpTFFrSEljU1VtZ0lLK1N3dXFnSENub2lvMlJRVTF5aitCbkJ5OXBwaFZLTEd5QzdaekZLMXB4V0srRThJaFZDV0xOL3VMdG5VVTRheW9ZTG9hQU56OEZkdGFTdlk0cFYwQkVXMmxzNjFjenFsbEJLcFR5S2dNQWhyWjFjZGMxUlJPdFBtdldOa2RjS1o3Wkt4YVdqaVBMSk1wcDdPWkt4QStycUcvb0pManhmMHBuSmxxTG9EWm8zZ3lVMG1LR3lzMnRhS2Vjai9kMUMrckpTcGxCcWxUeUFxZ1IrRDhLaktsbVJMMmd0VWNBZEN0c0wraWpDTlQxb3FxcWtIMk9IRWJHNXNERm5VZzVBYSt5TG91MlZVMXB0ajFTMlpRcXYxT1JaTjlJV3pSZmdhUkJ4S29CRThVV3lxbEpGdHJJYzBBeE5qU2plZDk5Q1RZL1hEZlN6Q3o1TTBJWm9WRXNXblBGTlRzbDhvb1ZDMVR6YkdncUZaTkRTZ1Z3S0srMXNHRE1LcXhaQ1dHVk1EeXNpRXIxalZTUUpVWXdqNWlIT2xUaGRIdDQ0U1FnOUNOK25sOEQ5ME5NSWdBZGdyNDZKcVJpUjlJOHZSZEZ2YnIxN20veXhVTUtqTkxNaVZVQUR3dTJDV0doaGkrRjU1VFdNOU05Y29nem1zMWRuTTR1T0YvTEFFWVdkY3FuTTd5Rm15cTNJZndtT1JPZDdZMWlGV3RPam9ZOFRvNDFtVFY1SXlzZ0ZGdVJ6c2JXRkdiTklJSkNEdjFkT280bFpHN2pXQndSRnRWVEt1V3llQ0J5SktPYW44b1ozZXA5WGRkTmwwdER1YXl3THo5Y1hQWWVEQUEwU3BrQk85c2JWY1RPVldsZFB2NHV5ekVrenhIdGp2b25Ib1NrRkVXTm9vMWQ4RGhjUXB1dGQycHBOb240QnpvQWlKMWhCRlFnMGRWdGRiR0hIRFFXdXNobU5FUXVrTE0yUU8xRzJZOGJnVFhxRmhjQkpqN0VqUGdjUHRzOFVTOHFQcFBCL2RYem5PaDVaNDM4dHpINWVjNlFnck9LclJSZktteXNCbVVEQitQaFlhYk1sVlBFUitHQ1NJVFR6cjdhbTJ0QXJIM2JnY0V6UEptK2NyNWpKNE5uSE5GRFZyRlhjSTVMZTlrNUpudytiZWRiVitGZlJ6WklIYU9PYU9zTFkwLzdVR3M1OERqckd3S01JTUZJR3pPRVcxL2pHc2RBdENONmhFQUk0aEJlOVlYZVJST0JTVlBBVlBBcXZJTTVieDVoVktXQU1QNnpCUnkzaWVzY3JpZFZkRkJpbkJ4WERuRzJHUlkyWGJDdnAxbGh2R3RPOUJ4dTVoOTA4WFF1NDJsblNBck1GZGl6TWltOHV3UkN4UEdubk9TOGx3cG5iT2lEcVRBanNyUk4vUGNvQVNjQ2JhQUNxVk00MHlsbmpqVEJzK2J3V2xBRzIzL1VLYmRraXdLV0lRUEd6V2FjenBvU2x4UEVqODIyY05Xa3BTN0Z5enNEcnFwZmdwRzNqYWh3MnZnYmFTUUF4dUxXWll0N0p6eU5lOEpvWnBOQWN2REZPZHcwd3FZVDlBSzFyWnovRGRiU2xMUHAwcnlJeGdRSmxLOUFabEVxN0lPWHBvaGc5UElockNuZzg4SnNPeGlWNFpXQVlmZzRzaWt4LzhreTJaOWw4NjJ1cXdyZnNjSUg4K3VnVG1WR3lpZGRlVllVZ0VNbjRHWnpnMTRFd0lzaDlzeDJjS0tpV1hSZXVPRTVnekdPUWdkbFJLVlZkbGV2cWIyNzlYcTBRbnN0czJWRGFCTzBjb2V6c3J1V3RIQXB1NnNLRzRJQmhOMGFHVTJrTHJNS0dSVE4zSG1iQ0R3S1YxNHp2a01FREc0UWZaVnNwVmxhTlUybWhjNVRFWjNOMWgvenFUaGV1THBXMDVaV1RHVmpiM2Ribk5teEtaQm5OOEpxaWRhVkxLQU95QVJOTFMrTUI1NFoyK1ZhcW9NTEtyb1ZCbG5nZWZuVFBBY29ITldDU3ZsZkE4Q0kwSEVtQk5CbkJsWHlNcnpVN0E3V1ZtOTRQUHFRMmdtcUt4K1dER3NudmlsbWNTT0JKcU9LMW5ZeUFJenVBeWVzcTNVZFNLM0tmV2NZS0Q5NUhtZllPVTNxc2VyMkN0WUVVQStGcGZxZE52Z1BCWlVCaERyR09OUlZsUXNoOHJMY2FVQ3lrSEcwT09Vd1RsTEJyc2g1c29FTUdlemkxRTRIUlZ0MWljcDV3WkVGWGRpYkNrRzhZOHZYNzVzYk80RTBpb205eitoalNpT2Z5M0RocFhJdHBWaEUrVUdRZHZvV2p0Q2htckdIZjRZQXpLZ0JObkd0dUp4RkNlR2RoVUFmUUxMSzhrQllBUDZndkZKWmFqTUczWGt5Y3k4S3VDMHE0RXl5bXd0d2R4ZHYyTTBtSUJ0SzBMS25mNjQwajAwQXVxNGdVa2RXR2xoczIycUpjNmRaQ3NMMTlveG5sVEpHNFNZVlJJR3BEOFRQRkJ1TTZPRWxiUzFwbGRpZDRtR0F5TjZaSXVwYkM1YlhKTjlmZHBiVGhTeExVYUk4SUcxWElZQnhXM1RqczZLUW9zS2N4ZnhjUW1kbndSR00xMEduRmNDeTJYWXVuTE15QWtkZ2s0bWVQaWN6c0x5Z3RoY0J1dDZnb09xUzdZVkZYQURMamFvc0I2czZvZmNaV0FaU0lSWXFTVWtpell3dHRZYWIzdlVPUTl3MkhSeElJZzhXd1JWZUU2OHhpNFV0TDN6UnBoeHBsend1WnJjcVlDcTFJM2pQSTVkbkpJeWdFb2hNYlBxVkpTenJ3enhCSlRzNXpOK1JlVVNneGlrUFFWRjNKVkJlTlF4YkhFTnJFTU52RWRGWlZWOWxIOStPUkdFc05aUXB5VE5jNEMzQUc3WEY0bmd6cStEck8yemJ1YWFPWGdkYUZjZGtFb3RvU0ZCVlgycUowQzhPV1plRzRLR2xwZ2hBMFhmVE9QQ3FWMnFxd1EyNlFXZkYyUE1MaEkydzFsVkFhMmFQc1lkMHphMjVNUVJ3Z2NaTjZ1UURDaStaeGlENFhFTTJrWnhPVDQxRm5abmFSbGNwWm91emxScXFkYlFWV29wUW9TQjU4UlY1MGxCTnJIaS9Bd1hTNUxyd0RWbHBZM0ZjM0J5aVlHYzUyVHJpc3Q2a09YZHdJbkFRdEpwcDVRY2h5YXF1WU9WN1N1K2Z4Vk1hVjNkYzBSRTJTNm1VWTBnTHQycE1jWXFyS0lROXcybDFncFFVTXRRWWNtbWJ0NURUTnhkaG5VQ2pRcXRiSzlTVVN6dnJDMG1taGhFMWUyRlMyK294eXB5L1pBU3V0a210angzdmNCQzI0UFg2NW5icWtCQ1JoZmpTOWtJWVBuZWU4Y01hZ1ZPaEkvM1QxZkFtZHRBV1pzQ3N3VEpDa1FWTmEwcVdLU0tQT3BIQVVoRDlEcmJWY3lvWWt3cWh2aDE3dllBYXlYTFF5S0dZZHhsVURGcDQ5NHJCWFJqWWdPMTdERFlldE5JVWovZXpwNlMwbG5scEV3c1dtSk1rT3dzS1hlWktFQWpJSG4wRVFKSVNhUkJjTzZVTUlOejdwL2JFampudzRmdCt4bUR2a3N4WDRHMnJJcmlzN3FhZUt3QUZNUDJPaTduNGNyaXVad3RwU1V3cGZMeFNuT1JTcklxdXNjNVpGYVh5c3FSV2ppWjJEeUFXRUlMMzV0VlNvUUVsRkFDak9lR0dTRTdBSEVRZ2RvL0xTdkNPZ0dCdmt4c21EYnZsUzNGcDV2aGFCMlRBR3FSS3JLS01yaExWcGFHekVWalowT1F4RGhhQ1RBK1F5UlIxZDE1YVF6ckpudEwzUmlic2lwakc2amxnTDR5cWJTMHNOWWcxZTg0dmhiQlZyRWxLNjRDVWNXWVhEZkt4aHBJdXhpVkpaVXhzYk15L3VSQktUTlJRNGtRM0xkUllMUzBySmpSUGxUUHFZNmdkSnNFRGMrYVFYQW4rSGdzTlVDYlJ1RjBPajB6d25BN2JXRGtiaE81RW5zMDBxZVFoUzFsYUJNbDVNL2NBYXhzTEY4ckt5cWwrVGY3RUxMRUd1L2l4aWltZEN2bzBUamZwakt3YWdnZW40ZWg1djdMb2tMS2JMdXl2SGhjWkc4ZGhHckVEeDdIZzkzWnBwSkY3cUJxTzNpVnZlWEVEUU5Jbnplb2U4WXE2ZVBhWkJaMkp2aU0zVzJVQUdvdGVrUkNBR3E0RWtGMVgzRE9uUjExeVJzQkwxdFJhMFBWY1ppTkZYWjJjMzRGc2t2b21JblFRNmx6cEpvWmJKeGs0M053S0pGQnF1SlNzckJ5SHlkeEtPblR4UUFTQm1TM2orSk1uc0hTbGEzRWM2SzlWV29KVm45emZqd09NN2hxWUFBcUpRd0UyYTNuQTQ4SjJRR2VnUmtwWk5pdlNZK3lzM0VrS2Q0b0pJd3N2SUhsM2NXZ0x0NWs0Tkg2T210TFdkcHVyT2t3RU11cFljN2VNdERSaE9jSTJ1aTVKaFZJelh6THl0by9HQVB1Wm95bzh3a29kdVZnSmdsQ3Q3T2hHYmdJRDRNcTRzaSs2M3pVUzFGdUZGWEZscXlhajJlbUhsTE1jQnFZdTBGTXVSMjhCYkI3bE94Uk1TaUNRWEZoQ0t1d2toWitwWURpR1NnYnNLS1Y4TWlTUnN1SFNJV005cmtsUmlJbFpadXFYanNRSzhvb1lKTWdxM0pLV1ZraEhiaHNWeEZVenRoT1dQa1lpamNieDU0SUtzU2RUK3VMcjNjckdLeW9ZZ0ZpR1I5aUJrNGtmbG9VWCtKSWxRUlFxYWJtcGduaHF0cFFwYjZSVlExV0g1RG5yUzRoRW9HWnFhZXJRMmRoRmJ6OFhlUHhTaG1EYm83MGVJU2pvb3JPMnZLOFNKWEk0U1VtRVU0eldLRHpVRHRXVFl3N3hYbGJTVEVqNEZSZzd6S25Lb0dSQUx2MEdzOVRnYzFCcEN5d0daUlFBdHFWejJ4ckJjQU16RXBmWndGU2EyRzVXMFFCRmpTTWFwV0FFRmEzSGNHTjdDeER6RUN5SWtKOTdxd3JxV05UV1ZvODc2UFBzalBrajJ3dmdyb001bExaS01FVEtWcWwvQ3ZuV1ZGaUZhL1N6SlVRd2tvWnNyNjdZNnZsU1JWMy8ydG1OVE9ZM3ZuYXhZd011b1BLcWR6UjF3N0lxSHltbFB4YUFUaGZVN0tvMlpYWWo0QVlKSEwra05kS3dSUVlFU1RSYTVmc1VaL3JWQzFUTVR5V1Z5WW9xTnR1emFIc015djJ0dm9hcnhkZnF3WWdVMWF4Rm8vY25xbDFGR3NxSyt1QVJPVjhCWDRHVThXY1pUQVRpMnE3UWN5aTBPMFYrR2hXQk1OUlVrbjhIMVNzV1ZFNUJ5M0dpMEVDcVVlSm9CZkF0RGE0YW1rZFhHMzdBR1A1R2dlYjg0cDdVYXpwb0tSemRGemVROEhrb0hHeHByS3kvSHBtNXQxMnA0N0o2eFRZREV6N3VJTkVYU3V4WVh2RnNrWUFjK3lTeEg5c2Y1ZnRLelU2SWJ3VkJjVUdnNWU1Rk1DRVhTRXJaUjB3R2F5VjE5d29NOWd1UGpUcUpkVlRxUjR1RTRuSm5MbGRXVmtFQ0NaTGQyVkxGK3h0YW1leDdJcGlyaVNEVXB2cnBuOWxyd0dNQ0h5cHBNSCtwczZMSUxzdUZHVWoxWEVPWGlxYnFTSFBVS25DbHBXVjY4a3F0VVJWTkRZNFROYW9jeWtvWWVUVTVuZ0dFUWEvUzFEbm5FNEFlWE1jS2pIUEFtRlZqQ0JFTmFleUxWTkhmcjNweDh4VXN0Sjk0aElwZkg0SEtFL2VEYUFySzZsU3lWVkZiZHQxZ3hUSVZrM3BwcFZsRlhpNHBFaFZCVE9icXVvaFU4NU1MWG4xaWFodlVrSEpqU0NNYzAxdExGdmVWVkJ4MERvZE02amZ0Q3U3RE90SXpZeHJjMHFwMUpHUDJheVlGejJHYjZIdk1yTzhjbkd0VjZHam0zdUltU2ZEMkdwV0s2dW93YlpHTXhGS1FDbzFwT010Y01YRnBSc3QraFhHb0FvbUYzc1NUQkdnVGdsYkJLV3dzUTN0WnFhWVNwMFoxQ2ltUkRXRmNDSlVQWUowMEJJNUZrS1lOb2lmdVF4bU44OFNXVlhXTE1hVXFxcWdDMEJtUUpSNnNrM3U5TkNmNmpZTFh4QWZxc1lFZ1ZMQWhSWTJBdGd0ZmxaTkZtRnloeGRyTGtBZFdsazREODhNMml4SHllcElkaE1IckcvaVIxWkd0cTBNR3BiRGJSUFlPWGVTWTFNNk55NFpzdHZHU2t0SytYYkZQQVRqMkQzNzFzYVBFc0FNWGhYcnNaMGttL1hTdGtoaE15QmZzYTZ1WEZaZTJWQ2UrWU1yMStHS2d3clF5TllxMVZSckIrRWl6QW93Nk5zZE5LY3lWRWtZZU03M3lzNnE0a0FIcDZCaUZrbFRrSXJWQzVvWVY3dXp3T0dDejRVSjBTdHEybFdNSnk0d3RiK1JldEw2dFpGaWNuSm1CdzVVakN2WFhNWlZKWDJNUWtiZitYTjVFV2Q3OFZ6OC9KRXNNWlRCaUtOenNtMWluTFJVUTc0SDROaWRhcUk2OGo1c0FGZ3hjUnZlQzdpZUxKWGZRWXhqWloyQ3NpV0Zld1pYSm1CSWxaMXRkdHJYNGhTdWF0ZUtzby9SWk90T0tXMm5tcTFvVHplSzZkUldBV3UyTlJWYjRocTBTWG0xR3Z0dWdIcmJyNUlYcW1Ta3RnNUN1REUyTVNsUHdzWTVrTkUyV3AzQXFpWmJXVkxBeGlCRisyaUJaYnVOajZNQjZyc01MQzdGeWFzYVlEeW83S2tvUHlFdHczcEVNWGZQdnhBSmkyakFRUWdqcnowckxJWlNXWmxJb05od2Q1eEs0QVI5bVlOaldBYUxybnVJbUplQlZOOXpCT1JPYlZ2YnIrbVRUZkZTRUpMU1JuSG83aEVKb0lpOE1GcWp4bXZnbUY1VVJaejR6TEZnWlo4Q3R1Mlg3Z2dWY2NLbTlnVnhJc09IcXhYZ05NS25GV1pZbmYxZEJuT2hheVhxMTdRd0ZsV1cwOWVOS3lWSkZtWHFhT05HQTVhQ2VnTWJKM1VVa0dZMWljM25LV2dqcThxZlZZR1FHMWdSdDZyczYyYTZIaXFxVU9xZGVzSzVObVg0bkdvZkpvaUUxZDBkRjlsVlZrdlQxL2tFRWFhQ29ZT3dGcGNWY29MTSs3NjY5UHhDOXJXcWt0SDBzV1VZbGQwVkNwdUJaL3N0VlJjR2d5OVdYMitVMVF0aGk5U3pBcVN4elpzeStPaUZ6QllueVNHVjZHa3U0NHJEOEJDT1pCVjNCdkQ1K0FLUkhOd01Fc0I2RXpIbkpwa1RBZWlVbEVHa2NFQ2VCNkdEWlRwNVlFSlRsdmRya254WWpUbGxNa2ZOdFh3RGpNN3VWaks1SlhVVW40M3JycXBLMmp5dGF4SFcwTTVHOERDOHJ0SE1ZczdLU2dkdVZRTUdUWUZxRnZWUzZya0Qzc0RKNDZhZmRZRndvcTExQU9LQ0JMaHZ3b1VnYzhJR0FOeWNSNmtuWnJkSlBkc3V4bnlqZmQzRm92VGxSTWRFZHRPbDVDTVY1RUhzWFFCaXM3VE93dklEWmFHajJWbnBiaDdjcEs2M1Z3WUVNTHdxYmp6eWw2OTlzYXdGRmtGMXlxalVVMzFIZkM2c1cxWkZWRnVYVlhWZ3o5a2VFYXcweXMxbFdmbSthelFBUVNXQStoS1lWZnNaalBuY0FjVUI5b0lheXkvVVpYUk5ja0RHamk3N0dzV2J2Qm82dFByV1BxT3lWa0JVcStJTmVxcHpOZFlzL3UwaWZoNXFtcHFJVyszM0pWU1Vjd1k3MEtMNFU5bFlkVTZsanRTbHM3bG1maTlnM1l6ZVFmVmthR0ZhVjNPRENuYUQyTjh3c0VERmtsRTNSek0zWmdoZFlrV0hzc3pxNzBGSWVjbktrVmt0OGV6TXpScTlia0d1S29qUkxCVlNvZDNZMXlQcUtnWVc3SlJRVFBWeXk1eElZTGpPZ3hnVDUyUktKVVkxZE9ySWlSZDRmdXRReC9BNUFjU21FanowdkZXcmtMenZiV0F1OUhPV2JHZ3hGazFWTlRwbkJLazZUZ3dpc0kvSGN4WVhQMXVBV083MlVMRmxCVHErYVN1MlZUVXM2aHJ4TTJDRitoRW9yMVZJQTlabUZVYWFiMWxTU2dac1ZzNHN4ekhsVkxvSkhyOUg0RGhPTlRrSTFYQzAvd2lZMk5vV0FHNVJsbkhGbnE2b0xjY3BRZGRNdUovTzE3SlZBNU9ITGkwQnFDenRxN1kxKyt1Y0NkOThxTEk4TUlIQlYvY0tqeFFUbWUzaEZCUzNNeUNxbkRzdXltMm84MEhqdkZGVHRyVVJtTmFHSnNtVmFoSW1qVHNVWEt0UVpUQVZzN012djgvK2Z6VXJaQVhjTEo2TTRrb2U2WFAwYjZTbVdXTkR6eVVwUThibCtMdFd4NHR1cVozNmNSWVYzeXVWeFBOd3ZJaXFpUUNTbXU3c3JnVHpSNm5reWhwQ2FyWHdGeTF2R2Q1aVAyY1kwNmxGcjVOamhoZzFZNitOQjI4ZnRiSzgzczhyZjdrTEpiS3dERlBiTGcyNWEwQWRaSkVpcXI1cGhpeEtNRGxSVXRjc3NxMWhyaUxxR29IK3plTmdWbTlPZW1qc0VUVjhKZEYwTkhua0lGeFdZMU9CNFlycDdydFdKN05nQUFBUFhrbEVRVlEzb05zNW5wbHlWZjh1MkZvTHUxSnJIdmVhWldRanFBa3NodEZhMmd6c1NHM1pwa2J2ZzNIYWZGOXNsUFBsbGRqRmxLODBHeXNtOE1yNE1QaG5lTldFTlBHakFJcG1pbFRQQVRkVFJUWGxDQllIWUFRdVB3QTM2eElwV3RHTjRxM1kyTWhpR3NVcHVTU25sRUpSRDhQb3JDN0NGWVZ3K0Y1MXFUaGdhYnhzVHhXekNHWTBaU3NiM2xmcUF5ME9QTmpOeTh4aVFRS3NIWUZRMkhCWlZ2VmJCdXEzbTFvV0thanFhb25zTTZ1WlVyNkNqWFdOWjBsNUUzaDNqVVJtYTZrUDNNSklpeTFMbStrYWhRcTQxTjJpWmphNXNqdGxMWU5aSFpySDZxVUdtNHZNYkRwNlJ3MkNGbXZ1eUZrckJjQ3lNdEZxQmFFQ21zSG9LOUJaMkxBL2xKY1JxU2FEcW5hV2JyWmRHYXozRExnSXZCbG40d29HenRieUpHcXNsd3hraGhIclRqVFlGWEN0T29LUzh1TGRvZlZkQWJPeWxHVTZubFlwWFdadHM0blhCcTZXeEppdE1Ob2tIVUpuYm5KcGxRbSthR3BZMmE1R01WMlFEMWhSdWJCUEZLZHVtZjVPSGtMSHowRjlsdUU1a2pCalJhMG5GRTVDVUdxSHczMk1talo2eGtnSU5WblNuWjFWWlN0SzJxS2xSYUxsUWdLN3VUcTdKRlhKd00rM1NPRUt5aFpOSSt0SjBJNXFNWXk5azJxSkQ3ZFZXZHFLWGEwQ0tOUjBDY2pnK0IySVl1MmZjQlpKWmtNRmdNMTFyMFg5MndpbGdoRkdnelZuZXhscUI3eEw5bVMyOVNpWVVWWTJuWE9aak5CUnN5RHNRUFJXVzVoclo0WGNkQzRIVldSYmpnSnI0c0ZvZks1U3pqUTdyaEkxVWViZFBkRWJqNnNxSXZUWlFaNXZhMDhyQUJzQVcwVXhlV3l0QWs3QTJLSjlacHh6Q2lvQjI0WEZ0WUFlWFl4cjZhblNxaExncHBFcVdiR3dMdW5UZ3JWK0lqV2xMMjlsamFBbDRFUU1Hc0VycDRhcGVaaXF1d1JYTFhBcU9DZXJ1MzJtbXlkYzZvV1RTV3BGQUdkemVUQjhSVEhWTUV0bE05MENiYlFDWWhQanEzZWdZcjFGR2RZSVFqaXVER1o1elovQXpvYktHT3lMeHRpNmM0Und0djJhbnlXbExJQ25sTGh4SlJYdDZBNWViREJXRk5PTmJ4V1oyZDAybW51NFM5WUVDcGVwcFYxelNXUkJXeEhZelZJdjFDWFNvdXdxcVgzakJCQkRaZFlRYnBUUVc0WlFsUzhyNWtINHN1U1JtZzIrKzNKTjEweDFQYUFtRWttdFlsRWRlR3BKRU02a091Q3FDUjIyb1N1amo1SVYySGRUMHpqNXByTEtUalhGQVBqZFFseXE3eElCeEFRUDV5TWN6RzRWeEFLdzBuNmlsWjJRQmNlMnBMdWxrdXh4cW5vSXpGZmdxeXFqaWw5UzFWTndCckZtZXllb3BzOHlPalpVeWJaZGZTOEN1YVRJSnVtenM1dE9EYU50THBGRFEvUGNKR3dlTGhtZUwxbkIwS3FpVURTY3NpVVZEODlEaTNIdHJLdFNVTHczUkxpeWdaRCs3c0Y4SlRPYmdZc3JHdkROVUZSR2wxaXkwTGwxWWtVYzJhSllNb2c5MjBJOHFXNllEQ2cxTXFrMEpISkZLWGtiZ2JScmVJK3FwWU5PWkhyVmNEVWJhN3Bqc3BoU0pOdEs2dXBnUk5BVm9PUzBtdWdCZU40YklaZ0hodVBaL3MxRU5hWDZLc1ZyK1lOcmgxTmI3aXBSMFBFNXpiTlJlZ0NickhSVXc2WWYwN2RMQkpsMWY4S0I5YXMyVjFuTnFBc2w2MkxCQmhlaHdhbGVya0htQjFKRklFWktTRXVzZGw1SlFqMW5KbEhYU0NGMzQyZ0o5Q1lHclhlbGtuSklYcVZQOHNEK3F0cGxDUjNYSDJxZktxMHlnTXArS25Wa0t4TmxaOG0yWWtJbFZNaUNuWFV3bDdxem5CS1N2UXozbTNQdDZvUWJYTzViNUZpeENoL2ZIeFVRVy9BRWNLNnpDTnFLUW5MOXN5d3FtS3V3dnFTWXpUL2FQVk5OcFZ5aHZSVzIxYXFjaUNzamRXdkJ3SUxVdmg1VnlDemJXb0MxcEpqSjY4MENXc2wrdWRLQjZUNVJ3RzFtbG9obmxwYmc0N2l6NVU5aGEwRkd0bVJMRllCdE85OXk5N0FwMHorWkRUQW9nNmtTTFpzTUhnL0lGa2tncDZDcHZVMlUwY1lWU2RubWtqd0JkT21YYnhUV05XenVJYmlwTWlvVnhFY2taRW9haFNPaXkyTTNLMGpjQzFMaFZEd2FxRzBadmtjV3FDbnJHNEdJeHlrcnFsYldkdzZMUXlCYVpSOEhtTFJJaFFXc0hzd0Q0MlpYVkxOa2Y5bCtGbFcwSFZRMmx3RnNDL1oxRmR6bFFSMEthUGZvK0ZkZnUrL2R3VlJJQ3UxQ0dSN0FFSWlBaGMrQVpVRjBrT0JhUHhtVXFnNGk2NHZRblU0bkZEWUo5TnorMWZWWHZlSDlxbXIra1BJTHg4b0tjUlYvQkZieGJFMEpNVDBrU0Q0dzZML2xOWThvY3NxYWdWZFUzQTNNanhoeGNHdXF6c1BINGlycGFvdzFxNk95clZqdnA5TnBjNTlFOTFMbGRib1lWekpXZGltV2ZBVzJTTkVLY0RhWDJGbUJMTEEvdUt4bG1oaDYxM0lzMVVSUUFwYktmdHR3eEwwMnE2T254NXBReFNiUG9qQWcrdjVoQW5ONkxIVlJEWElzdkt0UmppUzBxSlV5WlRBWFZiQUs4MkVsRkpXYVFkVm9xVUMxVW50N0JWYVRRdWRNNlN1cWV4alFKTjQrMGljYXh2L3V0Ykt2ODNFVGJUOEg4Z2pjT0t4T0ptYlVhNk9PVlhodDNkRlk2ckh2OVhvTnpGTGNlRUExbzgrcEttMExBSFBIWjJyWUtqRnEwaGZaRml4c3FISmdEM2VENW4rVTBrYjFtRmpYa24ybHZNU1NPc05FL0NkSUFLRjBTeXRxNnVyT0hVTjVnd2c0R1pvc2dibWdnTTV1Y3JhMnFyUzJJZzFjYmlCQmN4WXpnelVETkxDdkw4R2JaWE5wNk9SeTNMbVMrS2s4M3pSSUFLNkExaW9LYTJJOU5hcEl1aVVGZGZDOTc2NlBGWlV0cVVyNktiV2srelpVMWEvWnJJWEV6dHJqVE9mejdod0t6aUNlWElhcmFIdGJaSU16KzJwR2dhekNtdzRxV0FGdkVkaG9kWXAwWHEwcFY3RzFZV1lXYk80cWhHcTQyK1o4Qll0ckxXdmx1TlBwWkFlYUZGUzF2dWJQZ2JneHNxY3BuQWFzekJvdkthRm9EUThCR3RqZlVPbDROQUcybm1RVjA0ZmVKZ3VtdlgyZnNyUUVXWmdoTDBKblZkWWtuM0RPWkllUk44NlJxUFdDbXN2R1ZxRU1Sbnd4UUF4d1M4RU1ZbzNJem1ZMitCQ2NMcDRNS2l1eXVoSW1hbWxiWkZjTm9ObDd0cCtSSGQxOFpqUUlSS3lYZEZSaE45OC9oeUtxd1hXTm83TzF3aWFYb0hOMTA4UkVaWldFcTZncm5JZmp6ZWc4amRSZjFYRUw0a2tYYTViQmpLeG9LYWxqQmplSGxWeFE0R2F5Y3BXNGxET0FLdG5UeEhBdE9mek90WndIQU03c3FWWGtWNnl1NmthcDFuSGtYS3FXRi80WEhxamVuTktxQmpwUjNsMWNoM0VqZzErRXNnZFFoc2RHMEI0Rk05c1dBVldwdUF5aXdUUGxlWnh0OVZ5WlZTMnFYZlJlV3FUQWlscHI5QXBvV1RqeHltaXQ3TndWNEpUcmlaeU9BOUIwazdIRmZVTG91cm1LWUhWblJRdnFHTDVITUhkcUZjUjJxV3BtY0s2ZVR3eDJkaXBXcnZpRGlscitmS1dxM09XUldkSEt3QTRldTh3amNoYmVSekZpbHFqalpOM3VmQ3Bma0owL3NjVnBuWWs2TDBQSTc3bHhkV0NaODdXaVdtN0IvQUdxdVFTbnVqR0tzQjhDSm1pSnE4cTFwS0lWV3lxT2lUSzY2cjE4Qk44cjc0L0FFNzFmZEMzeVBTMk14ZE9wbkUxdGxWeEQ5Sm1WT29nZ04rcjRQakFYVkZQYTNFZzVqVkpHRlZVR05vbEgyMEdWclVCN0JPeVNXcTZXcVlRZFdSOTJwY0ZNWU13Y2tiU2dDS0NxRDY3RGlpV3UxZzhNUUM5QnlmY0ZxVzFMK2pMNzE0cU5DdXpub1N4dDBkYTJndFdOMUc4RjBCSzBOTjBudWltZWxVRjlkSWRBZmpPNDRVVDNDalFMb1VlTEhKRlRPM2dtcFJ1SUlPdndCUUNicU5lbzNxdFo5aUY2eFZLMTNHUmxvNHpxaW1xK0NHZFRpUjF1Ulk4b3FnRTAyaFpCYTc5a1pYUE1xdXhSSEtsYTJzYVpXTjRtUnFaVWowdkxDS2hraktucU9RSE51U1pWSm9LdkFxUzF3cEVxdXZXREMxQjJ5cHdyQ1BzUk1FUFZUT0RNTEpNRHY2cWVLWHdpMkpZVjVTcTRxS3l2Z0dzSENMaXVqMmpSNTlWOGdNcVNKMkZKWlJYRUhWUkhqM3NGUHJjdDZPcHFsVzFHcGF0UWR0MEd2d2ZNNm42M0luc0dWRmhKR2FCcWdxcUlWNklzWGxsWmd5U1BxNFIzYm50M3dpNWN2K2NOMnlxUUxXMVQ5NUtZVnNXV3RLazRjQjlXNTNXUVFmbFFZUjZXbDRIYUpaanZWRTBENXl2cStSS2daQ3M1cWRCRVA1c0Q5NGNBdlFMbFNnTmFTTUF0SHg4OEJ1TlE0MXpkRnNYMzB6S2JjczBNTEQvaWhrcFF6bDB3aVRxS0xUZmJLbUNteVlJQ25LMEliYWllQzRDRzlpU3lMUTdjSU1HUXdhdTZUS29xNjBBcGwzV040MExacGNhMUNLS0s5VlF5eUlFbjh3MEY4RjZDTDJoOG8zaXhHd0M3czdFV3pDT3FtY0FwWXhZRDRqc0F6VlMwc2wydDk4cEE3dnJLb3BoQ1ZTb25iWXBnSDZtdlNuMjRwVEJWNHNkdFYzQnRNcTVrODJ5K0lBRHZVSjB1QWxrQ1ZUeElhUG0rVU51L3FrVjRGMVR6SFhDR3JYSUFxSXRCS3lwcUs5OVZ0QU9WczY0TzRPYlg3cEhMVkNwWUhjUm13dkxSN1R2WUFLQkJONThMR1Z6RHVGeitoUWJXZ25jUXlDWkFrK1Zic1BTb3VmOTMyNjFpWmdtZkNwd1JiQXZxbVNxcmlVMlB3aGphb095WXF0SWVnVlhWaVRzbXl0YTZiR3lTcFkzZ3lScnBJeUFlYVdERHh0cHNYd0t5YWxNREtOUDdZQlhNcUVza1VzaTJ1QzhGTkFQeEFLVFZmVDFvNlZ6TTBFMGpGKzFyV2NVdUh2ZHlnN3Znb0ZwbFg4SHB2SHBNQ09NUlVQSHpaa0luc3FsRktOWC9FSU81MkUwU3hTek93b2IyVm1STFc1RDFYSVUwcmJnTTFBeldneUM3ZmU4Rzd4VUFLL3RhRUJhdDdsdXF0eVA3RW1zYUpRT2o1Rittcm5aZkN1WUNmQlVBV3dTaHlkNnBNWS92QUhHMVVxT1lwYkkvZ3k1VDBDTUttK1VPM2dGdUM4NWRnZkRWZWd1UERmSVRySUJMc0xyY2dkaDNDRmdGWmphS0o0SXYzRjhBTkVxdnV4UjF0VktPZ0xvQ2Exanhib0JBa2o2djdqL2ljRmJBN2Y0cmZSblFETFJWaUcxM2kwdnFCUXJZVnFCYkFEWlQwWnBpSG9TenZRcG9wS0lGUzNzRTFIZkJXbEhYZDBIN0xuQXJxdm91Z010bGpIQmdabmgzRW96L0JLakxNTDRaMkFxMCtoRUpyOWphVlVCYnZOekNJVWlyb0M3QVdtbUZ3NG81QUszTXRCNVZ5cFpNU0ZnczA1SnlHVndsd0Jxc0VHQUFhMlpVMUNqVWV4WEdzRTRyS3JpaWxCdkZ6T0tLbzNBdUFyb0U2UUZRVTN1OFlwTlh3UzVrKzFUWnQ1VXJ3b3VONEtpVUV3K2szWldEcDFSWEhOUnFYYjIxVHMzOTk0NXlaU2czVm5aRk5ROUNGM1hlWnlyNURnQlhLaXdDTWEyTXhlVERZWGdQMUZzZjlRTktaYzBrODFSSmszcjZFUTNyQ21CVnlMTDc1RWpaMXBJVkRIb0Z0aU9BSG9CMEJkVFZ5bHFCc0tLS1MrQWVCWEpWTFkrQ1hBU3VHdk8vQXVxN0d1RWpEZkdLZzFvS2Exei9kbW1pOUk5U1VHTmhsMEF0ZnVsSEFhd29Zcm5Ta21OWEFWdUdFaHJFVlh2VUYrQTVDdDJQcU5PakRldHluYTRDbWVVb2xtZVhMTjRBcTdDNVNqMTBRN3lqZ2wrdDZDTnhTUkhtSTVYK0Nwd3JlWUIzUWZkcW5hNHEyMUtkQnVjNEdvWnNuNDlaT09pVmlud0hxSzlXemp2Z2V3ZUVoMkFVNSt2dHhaOUNkOVdxa2g0OVYxOEU1b2o2dlZ5bjBSU3RBeUdJTzVlZFhSS2Q1QjBWR1ZYcTJ5cjN4WXArNVV0K0M0UUo0UDFOMzM5cFFNalJlamo0dmIvRGNyNnJRYzNPLzByam10WnBlWUNCaUNIZkNlbVJiTmhiSy9wTlVQYzN3Zkt5NWYyRDdPbEwzL3VQaHZlL29VNFQwRjhmK1ZOTTJ2eW9pdjBqSytLSFFmZEhxKzBibmN6NG96NzMvK1k2TGJLdzFvLzVCN2VPZjFSbC8wZHU5Qjl0bi85YnZyZi9qK3YwaDZ0dG4ydHAvci80ODE5eTQvenY1MzkxdXZ6emZ3RGlmejZwaFQxTVBnQUFBQUJKUlU1RXJrSmdnZz09KX0uY29sb3ItcGlja2VyIC5jcC1hZGQtY29sb3ItYnV0dG9uLWNsYXNze3Bvc2l0aW9uOmFic29sdXRlO2Rpc3BsYXk6aW5saW5lO3BhZGRpbmc6MDttYXJnaW46M3B4IC0zcHg7Ym9yZGVyOjA7Y3Vyc29yOnBvaW50ZXI7YmFja2dyb3VuZDp0cmFuc3BhcmVudH0uY29sb3ItcGlja2VyIC5jcC1hZGQtY29sb3ItYnV0dG9uLWNsYXNzOmhvdmVye3RleHQtZGVjb3JhdGlvbjp1bmRlcmxpbmV9LmNvbG9yLXBpY2tlciAuY3AtYWRkLWNvbG9yLWJ1dHRvbi1jbGFzczpkaXNhYmxlZHtjdXJzb3I6bm90LWFsbG93ZWQ7Y29sb3I6Izk5OX0uY29sb3ItcGlja2VyIC5jcC1hZGQtY29sb3ItYnV0dG9uLWNsYXNzOmRpc2FibGVkOmhvdmVye3RleHQtZGVjb3JhdGlvbjpub25lfS5jb2xvci1waWNrZXIgLmNwLXJlbW92ZS1jb2xvci1idXR0b24tY2xhc3N7cG9zaXRpb246YWJzb2x1dGU7dG9wOi01cHg7cmlnaHQ6LTVweDtkaXNwbGF5OmJsb2NrO3dpZHRoOjEwcHg7aGVpZ2h0OjEwcHg7Ym9yZGVyLXJhZGl1czo1MCU7Y3Vyc29yOnBvaW50ZXI7dGV4dC1hbGlnbjpjZW50ZXI7YmFja2dyb3VuZDojZmZmO2JveC1zaGFkb3c6MXB4IDFweCA1cHggIzMzM30uY29sb3ItcGlja2VyIC5jcC1yZW1vdmUtY29sb3ItYnV0dG9uLWNsYXNzOmJlZm9yZXtjb250ZW50OiJ4Ijtwb3NpdGlvbjpyZWxhdGl2ZTtib3R0b206My41cHg7ZGlzcGxheTppbmxpbmUtYmxvY2s7Zm9udC1zaXplOjEwcHh9LmNvbG9yLXBpY2tlciAuZXllZHJvcHBlci1pY29ue3Bvc2l0aW9uOmFic29sdXRlO3RvcDo1MCU7bGVmdDo1MCU7dHJhbnNmb3JtOnRyYW5zbGF0ZSgtNTAlLC01MCUpO2ZpbGw6I2ZmZjttaXgtYmxlbmQtbW9kZTpleGNsdXNpb259XG4nXSxlbmNhcHN1bGF0aW9uOjJ9KSxufSkoKSxyaGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscixvLHMsYSl7dGhpcy5pbmplY3Rvcj1lLHRoaXMuY2ZyPWksdGhpcy5hcHBSZWY9cix0aGlzLnZjUmVmPW8sdGhpcy5lbFJlZj1zLHRoaXMuX3NlcnZpY2U9YSx0aGlzLmRpYWxvZ0NyZWF0ZWQ9ITEsdGhpcy5pZ25vcmVDaGFuZ2VzPSExLHRoaXMudmlld0F0dGFjaGVkVG9BcHBSZWY9ITEsdGhpcy5jcFdpZHRoPSIyMzBweCIsdGhpcy5jcEhlaWdodD0iYXV0byIsdGhpcy5jcFRvZ2dsZT0hMSx0aGlzLmNwRGlzYWJsZWQ9ITEsdGhpcy5jcElnbm9yZWRFbGVtZW50cz1bXSx0aGlzLmNwRmFsbGJhY2tDb2xvcj0iIix0aGlzLmNwQ29sb3JNb2RlPSJjb2xvciIsdGhpcy5jcENteWtFbmFibGVkPSExLHRoaXMuY3BPdXRwdXRGb3JtYXQ9ImF1dG8iLHRoaXMuY3BBbHBoYUNoYW5uZWw9ImVuYWJsZWQiLHRoaXMuY3BEaXNhYmxlSW5wdXQ9ITEsdGhpcy5jcERpYWxvZ0Rpc3BsYXk9InBvcHVwIix0aGlzLmNwU2F2ZUNsaWNrT3V0c2lkZT0hMCx0aGlzLmNwQ2xvc2VDbGlja091dHNpZGU9ITAsdGhpcy5jcFVzZVJvb3RWaWV3Q29udGFpbmVyPSExLHRoaXMuY3BQb3NpdGlvbj0iYXV0byIsdGhpcy5jcFBvc2l0aW9uT2Zmc2V0PSIwJSIsdGhpcy5jcFBvc2l0aW9uUmVsYXRpdmVUb0Fycm93PSExLHRoaXMuY3BPS0J1dHRvbj0hMSx0aGlzLmNwT0tCdXR0b25UZXh0PSJPSyIsdGhpcy5jcE9LQnV0dG9uQ2xhc3M9ImNwLW9rLWJ1dHRvbi1jbGFzcyIsdGhpcy5jcENhbmNlbEJ1dHRvbj0hMSx0aGlzLmNwQ2FuY2VsQnV0dG9uVGV4dD0iQ2FuY2VsIix0aGlzLmNwQ2FuY2VsQnV0dG9uQ2xhc3M9ImNwLWNhbmNlbC1idXR0b24tY2xhc3MiLHRoaXMuY3BFeWVEcm9wcGVyPSExLHRoaXMuY3BQcmVzZXRMYWJlbD0iUHJlc2V0IGNvbG9ycyIsdGhpcy5jcFByZXNldENvbG9yc0NsYXNzPSJjcC1wcmVzZXQtY29sb3JzLWNsYXNzIix0aGlzLmNwTWF4UHJlc2V0Q29sb3JzTGVuZ3RoPTYsdGhpcy5jcFByZXNldEVtcHR5TWVzc2FnZT0iTm8gY29sb3JzIGFkZGVkIix0aGlzLmNwUHJlc2V0RW1wdHlNZXNzYWdlQ2xhc3M9InByZXNldC1lbXB0eS1tZXNzYWdlIix0aGlzLmNwQWRkQ29sb3JCdXR0b249ITEsdGhpcy5jcEFkZENvbG9yQnV0dG9uVGV4dD0iQWRkIGNvbG9yIix0aGlzLmNwQWRkQ29sb3JCdXR0b25DbGFzcz0iY3AtYWRkLWNvbG9yLWJ1dHRvbi1jbGFzcyIsdGhpcy5jcFJlbW92ZUNvbG9yQnV0dG9uQ2xhc3M9ImNwLXJlbW92ZS1jb2xvci1idXR0b24tY2xhc3MiLHRoaXMuY3BJbnB1dENoYW5nZT1uZXcgRyghMCksdGhpcy5jcFRvZ2dsZUNoYW5nZT1uZXcgRyghMCksdGhpcy5jcFNsaWRlckNoYW5nZT1uZXcgRyghMCksdGhpcy5jcFNsaWRlckRyYWdFbmQ9bmV3IEcoITApLHRoaXMuY3BTbGlkZXJEcmFnU3RhcnQ9bmV3IEcoITApLHRoaXMuY29sb3JQaWNrZXJPcGVuPW5ldyBHKCEwKSx0aGlzLmNvbG9yUGlja2VyQ2xvc2U9bmV3IEcoITApLHRoaXMuY29sb3JQaWNrZXJDYW5jZWw9bmV3IEcoITApLHRoaXMuY29sb3JQaWNrZXJTZWxlY3Q9bmV3IEcoITApLHRoaXMuY29sb3JQaWNrZXJDaGFuZ2U9bmV3IEcoITEpLHRoaXMuY3BDbXlrQ29sb3JDaGFuZ2U9bmV3IEcoITApLHRoaXMuY3BQcmVzZXRDb2xvcnNDaGFuZ2U9bmV3IEcoITApfWhhbmRsZUNsaWNrKCl7dGhpcy5pbnB1dEZvY3VzKCl9aGFuZGxlRm9jdXMoKXt0aGlzLmlucHV0Rm9jdXMoKX1oYW5kbGVJbnB1dChlKXt0aGlzLmlucHV0Q2hhbmdlKGUpfW5nT25EZXN0cm95KCl7bnVsbCE9dGhpcy5jbXBSZWYmJih0aGlzLnZpZXdBdHRhY2hlZFRvQXBwUmVmJiZ0aGlzLmFwcFJlZi5kZXRhY2hWaWV3KHRoaXMuY21wUmVmLmhvc3RWaWV3KSx0aGlzLmNtcFJlZi5kZXN0cm95KCksdGhpcy5jbXBSZWY9bnVsbCx0aGlzLmRpYWxvZz1udWxsKX1uZ09uQ2hhbmdlcyhlKXtlLmNwVG9nZ2xlJiYhdGhpcy5jcERpc2FibGVkJiYoZS5jcFRvZ2dsZS5jdXJyZW50VmFsdWU/dGhpcy5vcGVuRGlhbG9nKCk6ZS5jcFRvZ2dsZS5jdXJyZW50VmFsdWV8fHRoaXMuY2xvc2VEaWFsb2coKSksZS5jb2xvclBpY2tlciYmKHRoaXMuZGlhbG9nJiYhdGhpcy5pZ25vcmVDaGFuZ2VzJiYoImlubGluZSI9PT10aGlzLmNwRGlhbG9nRGlzcGxheSYmdGhpcy5kaWFsb2cuc2V0SW5pdGlhbENvbG9yKGUuY29sb3JQaWNrZXIuY3VycmVudFZhbHVlKSx0aGlzLmRpYWxvZy5zZXRDb2xvckZyb21TdHJpbmcoZS5jb2xvclBpY2tlci5jdXJyZW50VmFsdWUsITEpLHRoaXMuY3BVc2VSb290Vmlld0NvbnRhaW5lciYmImlubGluZSIhPT10aGlzLmNwRGlhbG9nRGlzcGxheSYmdGhpcy5jbXBSZWYuY2hhbmdlRGV0ZWN0b3JSZWYuZGV0ZWN0Q2hhbmdlcygpKSx0aGlzLmlnbm9yZUNoYW5nZXM9ITEpLChlLmNwUHJlc2V0TGFiZWx8fGUuY3BQcmVzZXRDb2xvcnMpJiZ0aGlzLmRpYWxvZyYmdGhpcy5kaWFsb2cuc2V0UHJlc2V0Q29uZmlnKHRoaXMuY3BQcmVzZXRMYWJlbCx0aGlzLmNwUHJlc2V0Q29sb3JzKX1vcGVuRGlhbG9nKCl7aWYodGhpcy5kaWFsb2dDcmVhdGVkKXRoaXMuZGlhbG9nJiZ0aGlzLmRpYWxvZy5vcGVuRGlhbG9nKHRoaXMuY29sb3JQaWNrZXIpO2Vsc2V7bGV0IGU9dGhpcy52Y1JlZjtpZih0aGlzLmRpYWxvZ0NyZWF0ZWQ9ITAsdGhpcy52aWV3QXR0YWNoZWRUb0FwcFJlZj0hMSx0aGlzLmNwVXNlUm9vdFZpZXdDb250YWluZXImJiJpbmxpbmUiIT09dGhpcy5jcERpYWxvZ0Rpc3BsYXkpe2xldCBvPXRoaXMuaW5qZWN0b3IuZ2V0KHRoaXMuYXBwUmVmLmNvbXBvbmVudFR5cGVzWzBdLFhuLk5VTEwpO28hPT1Ybi5OVUxMP2U9by52Y1JlZnx8by52aWV3Q29udGFpbmVyUmVmfHx0aGlzLnZjUmVmOnRoaXMudmlld0F0dGFjaGVkVG9BcHBSZWY9ITB9bGV0IGk9dGhpcy5jZnIucmVzb2x2ZUNvbXBvbmVudEZhY3RvcnkoZFplKTtpZih0aGlzLnZpZXdBdHRhY2hlZFRvQXBwUmVmKXRoaXMuY21wUmVmPWkuY3JlYXRlKHRoaXMuaW5qZWN0b3IpLHRoaXMuYXBwUmVmLmF0dGFjaFZpZXcodGhpcy5jbXBSZWYuaG9zdFZpZXcpLGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQodGhpcy5jbXBSZWYuaG9zdFZpZXcucm9vdE5vZGVzWzBdKTtlbHNle2xldCByPVhuLmNyZWF0ZSh7cHJvdmlkZXJzOltdLHBhcmVudDplLmluamVjdG9yfSk7dGhpcy5jbXBSZWY9ZS5jcmVhdGVDb21wb25lbnQoaSwwLHIsW10pfXRoaXMuY21wUmVmLmluc3RhbmNlLnNldHVwRGlhbG9nKHRoaXMsdGhpcy5lbFJlZix0aGlzLmNvbG9yUGlja2VyLHRoaXMuY3BXaWR0aCx0aGlzLmNwSGVpZ2h0LHRoaXMuY3BEaWFsb2dEaXNwbGF5LHRoaXMuY3BGYWxsYmFja0NvbG9yLHRoaXMuY3BDb2xvck1vZGUsdGhpcy5jcENteWtFbmFibGVkLHRoaXMuY3BBbHBoYUNoYW5uZWwsdGhpcy5jcE91dHB1dEZvcm1hdCx0aGlzLmNwRGlzYWJsZUlucHV0LHRoaXMuY3BJZ25vcmVkRWxlbWVudHMsdGhpcy5jcFNhdmVDbGlja091dHNpZGUsdGhpcy5jcENsb3NlQ2xpY2tPdXRzaWRlLHRoaXMuY3BVc2VSb290Vmlld0NvbnRhaW5lcix0aGlzLmNwUG9zaXRpb24sdGhpcy5jcFBvc2l0aW9uT2Zmc2V0LHRoaXMuY3BQb3NpdGlvblJlbGF0aXZlVG9BcnJvdyx0aGlzLmNwUHJlc2V0TGFiZWwsdGhpcy5jcFByZXNldENvbG9ycyx0aGlzLmNwUHJlc2V0Q29sb3JzQ2xhc3MsdGhpcy5jcE1heFByZXNldENvbG9yc0xlbmd0aCx0aGlzLmNwUHJlc2V0RW1wdHlNZXNzYWdlLHRoaXMuY3BQcmVzZXRFbXB0eU1lc3NhZ2VDbGFzcyx0aGlzLmNwT0tCdXR0b24sdGhpcy5jcE9LQnV0dG9uQ2xhc3MsdGhpcy5jcE9LQnV0dG9uVGV4dCx0aGlzLmNwQ2FuY2VsQnV0dG9uLHRoaXMuY3BDYW5jZWxCdXR0b25DbGFzcyx0aGlzLmNwQ2FuY2VsQnV0dG9uVGV4dCx0aGlzLmNwQWRkQ29sb3JCdXR0b24sdGhpcy5jcEFkZENvbG9yQnV0dG9uQ2xhc3MsdGhpcy5jcEFkZENvbG9yQnV0dG9uVGV4dCx0aGlzLmNwUmVtb3ZlQ29sb3JCdXR0b25DbGFzcyx0aGlzLmNwRXllRHJvcHBlcix0aGlzLmVsUmVmLHRoaXMuY3BFeHRyYVRlbXBsYXRlKSx0aGlzLmRpYWxvZz10aGlzLmNtcFJlZi5pbnN0YW5jZSx0aGlzLnZjUmVmIT09ZSYmdGhpcy5jbXBSZWYuY2hhbmdlRGV0ZWN0b3JSZWYuZGV0ZWN0Q2hhbmdlcygpfX1jbG9zZURpYWxvZygpe3RoaXMuZGlhbG9nJiYicG9wdXAiPT09dGhpcy5jcERpYWxvZ0Rpc3BsYXkmJnRoaXMuZGlhbG9nLmNsb3NlRGlhbG9nKCl9Y215a0NoYW5nZWQoZSl7dGhpcy5jcENteWtDb2xvckNoYW5nZS5lbWl0KGUpfXN0YXRlQ2hhbmdlZChlKXt0aGlzLmNwVG9nZ2xlQ2hhbmdlLmVtaXQoZSksZT90aGlzLmNvbG9yUGlja2VyT3Blbi5lbWl0KHRoaXMuY29sb3JQaWNrZXIpOnRoaXMuY29sb3JQaWNrZXJDbG9zZS5lbWl0KHRoaXMuY29sb3JQaWNrZXIpfWNvbG9yQ2hhbmdlZChlLGk9ITApe3RoaXMuaWdub3JlQ2hhbmdlcz1pLHRoaXMuY29sb3JQaWNrZXJDaGFuZ2UuZW1pdChlKX1jb2xvclNlbGVjdGVkKGUpe3RoaXMuY29sb3JQaWNrZXJTZWxlY3QuZW1pdChlKX1jb2xvckNhbmNlbGVkKCl7dGhpcy5jb2xvclBpY2tlckNhbmNlbC5lbWl0KCl9aW5wdXRGb2N1cygpe2xldCBlPXRoaXMuZWxSZWYubmF0aXZlRWxlbWVudCxpPXRoaXMuY3BJZ25vcmVkRWxlbWVudHMuZmlsdGVyKHI9PnI9PT1lKTshdGhpcy5jcERpc2FibGVkJiYhaS5sZW5ndGgmJih0eXBlb2YgZG9jdW1lbnQ8InUiJiZlPT09ZG9jdW1lbnQuYWN0aXZlRWxlbWVudD90aGlzLm9wZW5EaWFsb2coKTp0aGlzLmRpYWxvZyYmdGhpcy5kaWFsb2cuc2hvdz90aGlzLmNsb3NlRGlhbG9nKCk6dGhpcy5vcGVuRGlhbG9nKCkpfWlucHV0Q2hhbmdlKGUpe3RoaXMuZGlhbG9nP3RoaXMuZGlhbG9nLnNldENvbG9yRnJvbVN0cmluZyhlLnRhcmdldC52YWx1ZSwhMCk6KHRoaXMuY29sb3JQaWNrZXI9ZS50YXJnZXQudmFsdWUsdGhpcy5jb2xvclBpY2tlckNoYW5nZS5lbWl0KHRoaXMuY29sb3JQaWNrZXIpKX1pbnB1dENoYW5nZWQoZSl7dGhpcy5jcElucHV0Q2hhbmdlLmVtaXQoZSl9c2xpZGVyQ2hhbmdlZChlKXt0aGlzLmNwU2xpZGVyQ2hhbmdlLmVtaXQoZSl9c2xpZGVyRHJhZ0VuZChlKXt0aGlzLmNwU2xpZGVyRHJhZ0VuZC5lbWl0KGUpfXNsaWRlckRyYWdTdGFydChlKXt0aGlzLmNwU2xpZGVyRHJhZ1N0YXJ0LmVtaXQoZSl9cHJlc2V0Q29sb3JzQ2hhbmdlZChlKXt0aGlzLmNwUHJlc2V0Q29sb3JzQ2hhbmdlLmVtaXQoZSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oWG4pLE0oZ3MpLE0oSXUpLE0oT2kpLE0oUmUpLE0ocUcpKX0sbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1siIiwiY29sb3JQaWNrZXIiLCIiXV0saG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MSZlJiZQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5oYW5kbGVDbGljaygpfSkoImZvY3VzIixmdW5jdGlvbigpe3JldHVybiBpLmhhbmRsZUZvY3VzKCl9KSgiaW5wdXQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLmhhbmRsZUlucHV0KG8pfSl9LGlucHV0czp7Y29sb3JQaWNrZXI6ImNvbG9yUGlja2VyIixjcFdpZHRoOiJjcFdpZHRoIixjcEhlaWdodDoiY3BIZWlnaHQiLGNwVG9nZ2xlOiJjcFRvZ2dsZSIsY3BEaXNhYmxlZDoiY3BEaXNhYmxlZCIsY3BJZ25vcmVkRWxlbWVudHM6ImNwSWdub3JlZEVsZW1lbnRzIixjcEZhbGxiYWNrQ29sb3I6ImNwRmFsbGJhY2tDb2xvciIsY3BDb2xvck1vZGU6ImNwQ29sb3JNb2RlIixjcENteWtFbmFibGVkOiJjcENteWtFbmFibGVkIixjcE91dHB1dEZvcm1hdDoiY3BPdXRwdXRGb3JtYXQiLGNwQWxwaGFDaGFubmVsOiJjcEFscGhhQ2hhbm5lbCIsY3BEaXNhYmxlSW5wdXQ6ImNwRGlzYWJsZUlucHV0IixjcERpYWxvZ0Rpc3BsYXk6ImNwRGlhbG9nRGlzcGxheSIsY3BTYXZlQ2xpY2tPdXRzaWRlOiJjcFNhdmVDbGlja091dHNpZGUiLGNwQ2xvc2VDbGlja091dHNpZGU6ImNwQ2xvc2VDbGlja091dHNpZGUiLGNwVXNlUm9vdFZpZXdDb250YWluZXI6ImNwVXNlUm9vdFZpZXdDb250YWluZXIiLGNwUG9zaXRpb246ImNwUG9zaXRpb24iLGNwUG9zaXRpb25PZmZzZXQ6ImNwUG9zaXRpb25PZmZzZXQiLGNwUG9zaXRpb25SZWxhdGl2ZVRvQXJyb3c6ImNwUG9zaXRpb25SZWxhdGl2ZVRvQXJyb3ciLGNwT0tCdXR0b246ImNwT0tCdXR0b24iLGNwT0tCdXR0b25UZXh0OiJjcE9LQnV0dG9uVGV4dCIsY3BPS0J1dHRvbkNsYXNzOiJjcE9LQnV0dG9uQ2xhc3MiLGNwQ2FuY2VsQnV0dG9uOiJjcENhbmNlbEJ1dHRvbiIsY3BDYW5jZWxCdXR0b25UZXh0OiJjcENhbmNlbEJ1dHRvblRleHQiLGNwQ2FuY2VsQnV0dG9uQ2xhc3M6ImNwQ2FuY2VsQnV0dG9uQ2xhc3MiLGNwRXllRHJvcHBlcjoiY3BFeWVEcm9wcGVyIixjcFByZXNldExhYmVsOiJjcFByZXNldExhYmVsIixjcFByZXNldENvbG9yczoiY3BQcmVzZXRDb2xvcnMiLGNwUHJlc2V0Q29sb3JzQ2xhc3M6ImNwUHJlc2V0Q29sb3JzQ2xhc3MiLGNwTWF4UHJlc2V0Q29sb3JzTGVuZ3RoOiJjcE1heFByZXNldENvbG9yc0xlbmd0aCIsY3BQcmVzZXRFbXB0eU1lc3NhZ2U6ImNwUHJlc2V0RW1wdHlNZXNzYWdlIixjcFByZXNldEVtcHR5TWVzc2FnZUNsYXNzOiJjcFByZXNldEVtcHR5TWVzc2FnZUNsYXNzIixjcEFkZENvbG9yQnV0dG9uOiJjcEFkZENvbG9yQnV0dG9uIixjcEFkZENvbG9yQnV0dG9uVGV4dDoiY3BBZGRDb2xvckJ1dHRvblRleHQiLGNwQWRkQ29sb3JCdXR0b25DbGFzczoiY3BBZGRDb2xvckJ1dHRvbkNsYXNzIixjcFJlbW92ZUNvbG9yQnV0dG9uQ2xhc3M6ImNwUmVtb3ZlQ29sb3JCdXR0b25DbGFzcyIsY3BFeHRyYVRlbXBsYXRlOiJjcEV4dHJhVGVtcGxhdGUifSxvdXRwdXRzOntjcElucHV0Q2hhbmdlOiJjcElucHV0Q2hhbmdlIixjcFRvZ2dsZUNoYW5nZToiY3BUb2dnbGVDaGFuZ2UiLGNwU2xpZGVyQ2hhbmdlOiJjcFNsaWRlckNoYW5nZSIsY3BTbGlkZXJEcmFnRW5kOiJjcFNsaWRlckRyYWdFbmQiLGNwU2xpZGVyRHJhZ1N0YXJ0OiJjcFNsaWRlckRyYWdTdGFydCIsY29sb3JQaWNrZXJPcGVuOiJjb2xvclBpY2tlck9wZW4iLGNvbG9yUGlja2VyQ2xvc2U6ImNvbG9yUGlja2VyQ2xvc2UiLGNvbG9yUGlja2VyQ2FuY2VsOiJjb2xvclBpY2tlckNhbmNlbCIsY29sb3JQaWNrZXJTZWxlY3Q6ImNvbG9yUGlja2VyU2VsZWN0Iixjb2xvclBpY2tlckNoYW5nZToiY29sb3JQaWNrZXJDaGFuZ2UiLGNwQ215a0NvbG9yQ2hhbmdlOiJjcENteWtDb2xvckNoYW5nZSIsY3BQcmVzZXRDb2xvcnNDaGFuZ2U6ImNwUHJlc2V0Q29sb3JzQ2hhbmdlIn0sZXhwb3J0QXM6WyJuZ3hDb2xvclBpY2tlciJdLGZlYXR1cmVzOltGdF19KSxufSkoKSxvaGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbcUddLGltcG9ydHM6W01lXX0pLG59KSgpLGZaZT1bImNvbnRhaW5lciJdO2Z1bmN0aW9uIG1aZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsIm1hdC1zbGlkZXIiLDQpLFAoImlucHV0IixmdW5jdGlvbihyKXtyZXR1cm4gb2UoZSksc2UoUygpLmhhbmRsZVNpbmdsZVNsaWRlckNoYW5nZShyLnZhbHVlKSl9KSx2KCl9aWYoMiZuKXtsZXQgZT1TKCk7eSgiZGlzYWJsZWQiLCFlLmVuYWJsZWQpKCJtaW4iLGUubWluKSgibWF4IixlLm1heCkoInN0ZXAiLDEpKCJ2YWx1ZSIsZS5sb3dlclZhbHVlKX19ZnVuY3Rpb24gZ1plKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwic3BhbiIsNSw2KSxPKDIsInNwYW4iLDcpKDMsInNwYW4iLDgpLF8oNCwic3BhbiIsOSksUCgibW91c2Vkb3duIixmdW5jdGlvbihyKXtvZShlKTtsZXQgbz1TKCk7cmV0dXJuIHNlKG8uaGFuZGxlTW91c2VEb3duKHIsby5Qb3NpdGlvbi5MRUZUKSl9KSx2KCksXyg1LCJzcGFuIiw5KSxQKCJtb3VzZWRvd24iLGZ1bmN0aW9uKHIpe29lKGUpO2xldCBvPVMoKTtyZXR1cm4gc2Uoby5oYW5kbGVNb3VzZURvd24ocixvLlBvc2l0aW9uLlJJR0hUKSl9KSx2KCkoKX1pZigyJm4pe2xldCBlPVMoKTtDKDMpLFB0KCJsZWZ0IixlLmdldFRodW1iUG9zaXRpb24oZS5sb3dlclZhbHVlKSkoIndpZHRoIixlLmdldFRyYWNrV2lkdGgoKSksQygxKSxQdCgibGVmdCIsZS5nZXRUaHVtYlBvc2l0aW9uKGUubG93ZXJWYWx1ZSkpLGV0KCJhY3RpdmUiLGUuaXNUaHVtYkFjdGl2ZShlLlBvc2l0aW9uLkxFRlQpKSxDKDEpLFB0KCJsZWZ0IixlLmdldFRodW1iUG9zaXRpb24oZS51cHBlclZhbHVlKSksZXQoImFjdGl2ZSIsZS5pc1RodW1iQWN0aXZlKGUuUG9zaXRpb24uUklHSFQpKX19dmFyIG1jPSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5OT05FPTBdPSJOT05FIixuW24uTEVGVD0xXT0iTEVGVCIsbltuLlJJR0hUPTJdPSJSSUdIVCJ9KG1jfHwobWM9e30pKSxtYykpKCksc2hlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5jaGFuZ2VEZXRlY3Rvcj1lLHRoaXMudGlja0NvdW50PTIwLHRoaXMuZW5hYmxlZD0hMCx0aGlzLnJldHVybkludGVnZXJzPSExLHRoaXMucmFuZ2VWYWx1ZXNDaGFuZ2VkPW5ldyBHLHRoaXMuc2luZ2xlVmFsdWVDaGFuZ2VkPW5ldyBHLHRoaXMuUG9zaXRpb249bWMsdGhpcy5hY3RpdmVUaHVtYj1tYy5OT05FLHRoaXMub2Zmc2V0WEZyb21PcmlnaW5PZkFjdGl2ZVRodW1iPTAsdGhpcy5uZ1Vuc3Vic2NyaWJlPW5ldyBrZX1nZXRUaHVtYlBvc2l0aW9uKGUpe2xldCBpPXRoaXMuZ2V0Q2xpcHBlZFZhbHVlKGUpLHI9dGhpcy5tYXgtdGhpcy5taW47cmV0dXJuIHI8PTA/IjUwJSI6KGktdGhpcy5taW4pL3IqMTAwKyIlIn1nZXRUcmFja1dpZHRoKCl7aWYobnVsbD09PXRoaXMudXBwZXJWYWx1ZSlyZXR1cm4iMCUiO2xldCBlPXRoaXMubWF4LXRoaXMubWluO3JldHVybiBlPD0wPyIwJSI6KHRoaXMuZ2V0Q2xpcHBlZFZhbHVlKHRoaXMudXBwZXJWYWx1ZSktdGhpcy5nZXRDbGlwcGVkVmFsdWUodGhpcy5sb3dlclZhbHVlKSkvZSoxMDArIiUifWdldENsaXBwZWRWYWx1ZShlKXtyZXR1cm4gTWF0aC5taW4oTWF0aC5tYXgoZSx0aGlzLm1pbiksdGhpcy5tYXgpfW5nT25EZXN0cm95KCl7dGhpcy5uZ1Vuc3Vic2NyaWJlLm5leHQoKSx0aGlzLm5nVW5zdWJzY3JpYmUuY29tcGxldGUoKX1uZ09uSW5pdCgpe19pKGRvY3VtZW50LCJtb3VzZW1vdmUiLHtwYXNzaXZlOiEwfSkucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpKS5zdWJzY3JpYmUoZT0+e3RoaXMuaGFuZGxlTW91c2VNb3ZlKGUpfSksX2koZG9jdW1lbnQsIm1vdXNldXAiLHtwYXNzaXZlOiEwfSkucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpKS5zdWJzY3JpYmUoZT0+e3RoaXMuaGFuZGxlTW91c2VPdXQoZSl9KX1oYW5kbGVNb3VzZURvd24oZSxpKXtlLnN0b3BQcm9wYWdhdGlvbigpLGUucHJldmVudERlZmF1bHQoKSx0aGlzLmFjdGl2ZVRodW1iPWksdGhpcy5vZmZzZXRYRnJvbU9yaWdpbk9mQWN0aXZlVGh1bWI9Ni1lLm9mZnNldFh9Y2FsY3VsYXRlVmFsdWVGcm9tTW91c2VFdmVudChlKXtsZXQgYSx7bGVmdDppLHJpZ2h0OnJ9PXRoaXMuY29udGFpbmVyLm5hdGl2ZUVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkscz1lLmNsaWVudFgtaSt0aGlzLm9mZnNldFhGcm9tT3JpZ2luT2ZBY3RpdmVUaHVtYjtpZihudWxsIT09dGhpcy50aWNrQ291bnQmJnRoaXMudGlja0NvdW50PjApe2xldCBjPShyLWkpL3RoaXMudGlja0NvdW50O2E9TWF0aC5yb3VuZChzL2MpKmMvKHItaSl9ZWxzZSBhPXMvKHItaSk7bGV0IGw9dGhpcy5nZXRDbGlwcGVkVmFsdWUodGhpcy5taW4rKHRoaXMubWF4LXRoaXMubWluKSphKTtyZXR1cm4gdGhpcy5yZXR1cm5JbnRlZ2Vycz9NYXRoLnJvdW5kKGwpOk51bWJlcihsLnRvRml4ZWQoMTApKX1oYW5kbGVNb3VzZU1vdmUoZSl7aWYodGhpcy5hY3RpdmVUaHVtYj09PW1jLk5PTkV8fG51bGw9PT10aGlzLnVwcGVyVmFsdWUpcmV0dXJuO2xldCBpPXRoaXMuY2FsY3VsYXRlVmFsdWVGcm9tTW91c2VFdmVudChlKSxyPVt0aGlzLmxvd2VyVmFsdWUsdGhpcy51cHBlclZhbHVlXTt0aGlzLmFjdGl2ZVRodW1iPT09bWMuTEVGVD8oaT50aGlzLnVwcGVyVmFsdWUmJih0aGlzLmFjdGl2ZVRodW1iPW1jLlJJR0hUKSxyPVtpLHRoaXMudXBwZXJWYWx1ZV0pOihpPHRoaXMubG93ZXJWYWx1ZSYmKHRoaXMuYWN0aXZlVGh1bWI9bWMuTEVGVCkscj1bdGhpcy5sb3dlclZhbHVlLGldKSx0aGlzLm1heWJlTm90aWZ5TmV4dFJhbmdlVmFsdWVzKHIsIlNMSURFUiIpLHRoaXMuY2hhbmdlRGV0ZWN0b3IubWFya0ZvckNoZWNrKCl9bWF5YmVOb3RpZnlOZXh0UmFuZ2VWYWx1ZXMoZSxpKXtsZXRbcixvXT1lLnNvcnQoKHMsYSk9PnMtYSk7KHRoaXMubG93ZXJWYWx1ZSE9PXJ8fHRoaXMudXBwZXJWYWx1ZSE9PW8pJiZ0aGlzLnJhbmdlVmFsdWVzQ2hhbmdlZC5lbWl0KHtsb3dlclZhbHVlOnIsdXBwZXJWYWx1ZTpvLHNvdXJjZTppfSl9aGFuZGxlTW91c2VPdXQoZSl7dGhpcy5hY3RpdmVUaHVtYiE9PW1jLk5PTkUmJih0aGlzLmFjdGl2ZVRodW1iPW1jLk5PTkUsdGhpcy5jaGFuZ2VEZXRlY3Rvci5tYXJrRm9yQ2hlY2soKSl9aGFuZGxlU2luZ2xlU2xpZGVyQ2hhbmdlKGUpe3RoaXMuc2luZ2xlVmFsdWVDaGFuZ2VkLmVtaXQoe3ZhbHVlOmUsc291cmNlOiJTTElERVIifSl9aGFuZGxlSW5wdXRDaGFuZ2UoZSxpKXtsZXQgcj1lLnRhcmdldCxvPXRoaXMuZ2V0Q2xpcHBlZFZhbHVlKE51bWJlcihyLnZhbHVlKSk7aXNOYU4obyl8fChpPT09bWMuTEVGVD9udWxsPT09dGhpcy51cHBlclZhbHVlP3RoaXMuc2luZ2xlVmFsdWVDaGFuZ2VkLmVtaXQoe3ZhbHVlOm8sc291cmNlOiJURVhUIn0pOnRoaXMubWF5YmVOb3RpZnlOZXh0UmFuZ2VWYWx1ZXMoW28sdGhpcy51cHBlclZhbHVlXSwiVEVYVCIpOiIiPT09ci52YWx1ZT90aGlzLnNpbmdsZVZhbHVlQ2hhbmdlZC5lbWl0KHt2YWx1ZTp0aGlzLmxvd2VyVmFsdWUsc291cmNlOiJURVhUX0RFTEVURUQifSk6dGhpcy5tYXliZU5vdGlmeU5leHRSYW5nZVZhbHVlcyhbdGhpcy5sb3dlclZhbHVlLG9dLCJURVhUIikpfWlzVGh1bWJBY3RpdmUoZSl7cmV0dXJuIHRoaXMuYWN0aXZlVGh1bWI9PT1lfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKG5uKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGItcmFuZ2UtaW5wdXQiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiZvdChmWmUsNSxSZSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5jb250YWluZXI9ci5maXJzdCl9fSxpbnB1dHM6e21pbjoibWluIixtYXg6Im1heCIsbG93ZXJWYWx1ZToibG93ZXJWYWx1ZSIsdXBwZXJWYWx1ZToidXBwZXJWYWx1ZSIsdGlja0NvdW50OiJ0aWNrQ291bnQiLGVuYWJsZWQ6ImVuYWJsZWQiLHJldHVybkludGVnZXJzOiJyZXR1cm5JbnRlZ2VycyJ9LG91dHB1dHM6e3JhbmdlVmFsdWVzQ2hhbmdlZDoicmFuZ2VWYWx1ZXNDaGFuZ2VkIixzaW5nbGVWYWx1ZUNoYW5nZWQ6InNpbmdsZVZhbHVlQ2hhbmdlZCJ9LGRlY2xzOjUsdmFyczo2LGNvbnN0czpbWyJ0eXBlIiwibnVtYmVyIiwxLCJsb3dlci1pbnB1dCIsMywiZGlzYWJsZWQiLCJ2YWx1ZSIsImNoYW5nZSJdLFsidHlwZSIsIm51bWJlciIsMSwidXBwZXItaW5wdXQiLDMsImRpc2FibGVkIiwidmFsdWUiLCJjaGFuZ2UiXSxbImNsYXNzIiwic2luZ2xlLXNsaWRlciIsImNvbG9yIiwicHJpbWFyeSIsMywiZGlzYWJsZWQiLCJtaW4iLCJtYXgiLCJzdGVwIiwidmFsdWUiLCJpbnB1dCIsNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJyYW5nZSIsIiJdLFsiY29sb3IiLCJwcmltYXJ5IiwxLCJzaW5nbGUtc2xpZGVyIiwzLCJkaXNhYmxlZCIsIm1pbiIsIm1heCIsInN0ZXAiLCJ2YWx1ZSIsImlucHV0Il0sWzEsImNvbnRhaW5lciJdLFsiY29udGFpbmVyIiwiIl0sWzEsInNsaWRlci10cmFjayJdLFsxLCJzbGlkZXItdHJhY2stZmlsbCJdLFsxLCJ0aHVtYiIsMywibW91c2Vkb3duIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoXygwLCJpbnB1dCIsMCksUCgiY2hhbmdlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5oYW5kbGVJbnB1dENoYW5nZShvLGkuUG9zaXRpb24uTEVGVCl9KSx2KCksXygxLCJpbnB1dCIsMSksUCgiY2hhbmdlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5oYW5kbGVJbnB1dENoYW5nZShvLGkuUG9zaXRpb24uUklHSFQpfSksdigpLEUoMixtWmUsMSw1LCJtYXQtc2xpZGVyIiwyKSxFKDMsZ1plLDYsMTIsIm5nLXRlbXBsYXRlIixudWxsLDMscXQpKSwyJmUpe2xldCByPSRlKDQpO3koImRpc2FibGVkIiwhaS5lbmFibGVkKSgidmFsdWUiLGkubG93ZXJWYWx1ZSksQygxKSx5KCJkaXNhYmxlZCIsIWkuZW5hYmxlZCkoInZhbHVlIixudWxsIT09aS51cHBlclZhbHVlP2kudXBwZXJWYWx1ZToiIiksQygxKSx5KCJuZ0lmIixudWxsPT09aS51cHBlclZhbHVlKSgibmdJZkVsc2UiLHIpfX0sZGVwZW5kZW5jaWVzOltCZSx1cF0sc3R5bGVzOlsnW19uZ2hvc3QtJUNPTVAlXXtib3gtc2l6aW5nOmJvcmRlci1ib3g7ZGlzcGxheTppbmxpbmUtZ3JpZDtncmlkLWdhcDoxMHB4O2dyaWQtdGVtcGxhdGUtYXJlYXM6Imxvd2VyLWlucHV0IHVwcGVyLWlucHV0IiAic2xpZGVyIHNsaWRlciI7Z3JpZC10ZW1wbGF0ZS1jb2x1bW5zOjFmciAxZnI7Zm9udC1zaXplOjA7bWluLXdpZHRoOjEwMHB4O3BhZGRpbmc6NnB4fWlucHV0W19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOmluaGVyaXQ7Ym9yZGVyLXN0eWxlOnNvbGlkO2JveC1zaXppbmc6Ym9yZGVyLWJveDtjb2xvcjppbmhlcml0O292ZXJmbG93OmhpZGRlbjt3aWR0aDoxMDAlfS5sb3dlci1pbnB1dFtfbmdjb250ZW50LSVDT01QJV17Z3JpZC1hcmVhOmxvd2VyLWlucHV0fS51cHBlci1pbnB1dFtfbmdjb250ZW50LSVDT01QJV17Z3JpZC1hcmVhOnVwcGVyLWlucHV0O2p1c3RpZnktc2VsZjpmbGV4LWVuZH0uc2luZ2xlLXNsaWRlcltfbmdjb250ZW50LSVDT01QJV17Z3JpZC1hcmVhOnNsaWRlcjtwYWRkaW5nOjBweH0uc2luZ2xlLXNsaWRlcltfbmdjb250ZW50LSVDT01QJV0gICAgIC5tYXQtc2xpZGVyLXdyYXBwZXJ7dG9wOjVweDtsZWZ0OjBweDtyaWdodDowcHh9ICAuc2luZ2xlLXNsaWRlci5tYXQtc2xpZGVyLWhvcml6b250YWx7aGVpZ2h0OjEycHh9LmNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17Z3JpZC1hcmVhOnNsaWRlcjthbGlnbi1pdGVtczpjZW50ZXI7Ym94LXNpemluZzpib3JkZXItYm94O2Rpc3BsYXk6aW5saW5lLWZsZXg7aGVpZ2h0OjEycHg7anVzdGlmeS1jb250ZW50OmNlbnRlcjtwb3NpdGlvbjpyZWxhdGl2ZTt3aWR0aDoxMDAlfS5zbGlkZXItdHJhY2tbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQ6cmdiYSgwLDAsMCwuMjYpO2hlaWdodDoycHg7d2lkdGg6MTAwJX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuc2xpZGVyLXRyYWNrW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnNsaWRlci10cmFja1tfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZDpyZ2JhKDI1NSwyNTUsMjU1LC4zKX0uc2xpZGVyLXRyYWNrLWZpbGxbX25nY29udGVudC0lQ09NUCVde3Bvc2l0aW9uOmFic29sdXRlO2hlaWdodDoycHh9LnRodW1iW19uZ2NvbnRlbnQtJUNPTVAlXXtib3gtc2FkaG93OjAgMCAwIDFweCByZ2JhKDAsMCwwLC4yNik7Ym9yZGVyLXJhZGl1czoxMDAlO2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxMnB4O21hcmdpbi1sZWZ0Oi02cHg7cG9zaXRpb246YWJzb2x1dGU7dG9wOjA7dHJhbnNmb3JtLW9yaWdpbjpjZW50ZXI7dHJhbnNpdGlvbjp0cmFuc2Zvcm0gLjNzIGVhc2U7d2lkdGg6MTJweDt3aWxsLWNoYW5nZTp0cmFuc2Zvcm19Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLnRodW1iW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLnRodW1iW19uZ2NvbnRlbnQtJUNPTVAlXXtib3gtc2FkaG93OjAgMCAwIDFweCByZ2JhKDI1NSwyNTUsMjU1LC4zKX0udGh1bWJbX25nY29udGVudC0lQ09NUCVdOmhvdmVye2N1cnNvcjpncmFifS50aHVtYltfbmdjb250ZW50LSVDT01QJV06YWN0aXZle2N1cnNvcjpncmFiYmluZ30udGh1bWIuYWN0aXZlW19uZ2NvbnRlbnQtJUNPTVAlXXt0cmFuc2Zvcm06c2NhbGUoMS4yKX0uc2xpZGVyLXRyYWNrLWZpbGxbX25nY29udGVudC0lQ09NUCVdLCAudGh1bWJbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQ6I2Y1N2MwMH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuc2xpZGVyLXRyYWNrLWZpbGxbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuc2xpZGVyLXRyYWNrLWZpbGxbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQ6I2VmNmMwMH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAudGh1bWJbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAudGh1bWJbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQ6I2VmNmMwMH0nXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLHlaZT1bInJlZ2V4U3RyaW5nSW5wdXQiXTtmdW5jdGlvbiBiWmUobix0KXtpZigxJm4mJihzbigwKSxfKDEsImxpIiwyMiksQSgyKSx2KCksYW4oKSksMiZuKXtsZXQgZT10LiRpbXBsaWNpdDtDKDEpLHkoInRpdGxlIixlLm5hbWUpLEMoMSkseXQoZS5uYW1lKX19ZnVuY3Rpb24geFplKG4sdCl7aWYoMSZuJiYoXygwLCJsaSIsMjMpKDEsImVtIiksQSgyKSxCKDMsIm51bWJlciIpLHYoKSgpKSwyJm4pe2xldCBlPVMoKS4kaW1wbGljaXQ7QygyKSxqZSgiYW5kICIsVSgzLDEsZS5ydW5zLmxlbmd0aC01KSwiIG1vcmUiKX19ZnVuY3Rpb24gQ1plKG4sdCl7MSZuJiYoXygwLCJsaSIsMjQpKDEsImVtIiksQSgyLCJObyBydW5zIGFyZSBpbiB0aGUgZ3JvdXAiKSx2KCkoKSl9dmFyIE1aZT1mdW5jdGlvbihuKXtyZXR1cm57Ym9yZGVyQ29sb3I6bn19LHdaZT1mdW5jdGlvbihuKXtyZXR1cm57YmFja2dyb3VuZENvbG9yOm59fTtmdW5jdGlvbiBTWmUobix0KXtpZigxJm4mJihfKDAsInVsIiwxNikoMSwibGkiKSgyLCJsYWJlbCIpLE8oMywic3BhbiIsMTcpLF8oNCwiY29kZSIsMTgpLEEoNSksdigpKCksXyg2LCJ1bCIpLEUoNyxiWmUsMywyLCJuZy1jb250YWluZXIiLDE5KSxCKDgsInNsaWNlIiksRSg5LHhaZSw0LDMsImxpIiwyMCksRSgxMCxDWmUsMywwLCJsaSIsMjEpLHYoKSgpKCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQ7eSgibmdTdHlsZSIsT24oMTEsTVplLGUuY29sb3IpKSxDKDMpLHkoIm5nU3R5bGUiLE9uKDEzLHdaZSxlLmNvbG9yKSksQygxKSx5KCJ0aXRsZSIsZS5ncm91cElkKSxDKDEpLHl0KGUuZ3JvdXBJZCksQygyKSx5KCJuZ0Zvck9mIixKMyg4LDcsZS5ydW5zLDAsNSkpLEMoMikseSgibmdJZiIsZS5ydW5zLmxlbmd0aD41KSxDKDEpLHkoIm5nSWYiLDA9PT1lLnJ1bnMubGVuZ3RoKX19ZnVuY3Rpb24gRVplKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDE0KSxFKDEsU1plLDExLDE1LCJ1bCIsMTUpLHYoKSksMiZuKXtsZXQgZT1TKDIpO0MoMSkseSgibmdGb3JPZiIsZS5jb2xvclJ1blBhaXJMaXN0KX19ZnVuY3Rpb24gVFplKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDI1KSxBKDEsIiBUaGVyZSBhcmUgbm8gcnVucyBtYXRjaGluZyB0aGUgcmVnZXgsICIpLF8oMiwiY29kZSIpLEEoMyksdigpLEEoNCwiLiBQbGVhc2UgY2hlY2sgaWYgeW91ciByZWdleCBzdHJpbmcgaXMgY29ycmVjdC4gIiksdigpKSwyJm4pe2xldCBlPVMoMik7QygzKSxqZSgiLyIsZS5yZWdleFN0cmluZywiLyIpfX1mdW5jdGlvbiBEWmUobix0KXtpZigxJm4mJihfKDAsImRpdiIsMTApKDEsImg0IiksQSgyLCJDb2xvciBncm91cCBwcmV2aWV3IiksdigpLF8oMywiZGl2IiwxMSksRSg0LEVaZSwyLDEsImRpdiIsMTIpLEUoNSxUWmUsNSwxLCJuZy10ZW1wbGF0ZSIsbnVsbCwxMyxxdCksdigpKCkpLDImbil7bGV0IGU9JGUoNiksaT1TKCk7Qyg0KSx5KCJuZ0lmIixpLmNvbG9yUnVuUGFpckxpc3QubGVuZ3RoKSgibmdJZkVsc2UiLGUpfX12YXIgYWhlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXt0aGlzLmRpYWxvZ1JlZj1lLHRoaXMuaG9zdEVsUmVmPWksdGhpcy5vblNhdmU9bmV3IEcsdGhpcy5yZWdleElucHV0T25DaGFuZ2U9bmV3IEcsdGhpcy50aW1lT3V0SWQ9MH1yZXNldEZvY3VzKCl7dGhpcy5ob3N0RWxSZWYubmF0aXZlRWxlbWVudC5jb250YWlucyhkb2N1bWVudC5hY3RpdmVFbGVtZW50KXx8dGhpcy5yZWdleFN0cmluZ0lucHV0Lm5hdGl2ZUVsZW1lbnQuZm9jdXMoKX1vbkVudGVyKGUpe3RoaXMub25TYXZlQ2xpY2soZSksdGhpcy5kaWFsb2dSZWYuY2xvc2UoKX1vblNhdmVDbGljayhlKXt0aGlzLm9uU2F2ZS5lbWl0KGUpfWZpbGxFeGFtcGxlKGUpe3RoaXMucmVnZXhTdHJpbmc9ZSx0aGlzLnJlZ2V4SW5wdXRDaGFuZ2UoZSl9cmVnZXhJbnB1dENoYW5nZShlKXt0aGlzLnJlZ2V4SW5wdXRPbkNoYW5nZS5lbWl0KGUpfWhhbmRsZUZvY3VzT3V0KCl7Y2xlYXJUaW1lb3V0KHRoaXMudGltZU91dElkKSx0aGlzLnRpbWVPdXRJZD1zZXRUaW1lb3V0KHRoaXMucmVzZXRGb2N1cy5iaW5kKHRoaXMpLDApfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKHR1KSxNKFJlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sicmVnZXgtZWRpdC1kaWFsb2ctY29tcG9uZW50Il1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmb3QoeVplLDcpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkucmVnZXhTdHJpbmdJbnB1dD1yLmZpcnN0KX19LGlucHV0czp7cmVnZXhTdHJpbmc6InJlZ2V4U3RyaW5nIixjb2xvclJ1blBhaXJMaXN0OiJjb2xvclJ1blBhaXJMaXN0In0sb3V0cHV0czp7b25TYXZlOiJvblNhdmUiLHJlZ2V4SW5wdXRPbkNoYW5nZToicmVnZXhJbnB1dE9uQ2hhbmdlIn0sZGVjbHM6MzAsdmFyczoyLGNvbnN0czpmdW5jdGlvbigpe2xldCB0O3JldHVybiB0PSRsb2NhbGl6ZWA6Q29sb3IgUnVucyBieSBSZWdleCBRdWVyeeKQnzE1ZWQ5ZjZmZDJkNDkwNmE0ODAzZmMxMjU1ZGUzYzVkYjJjNTY1MzDikJ85MDg4OTg1MTEzOTYwMzEyODA4OkNvbG9yIFJ1bnMgYnkgUmVnZXggUXVlcnlgLFtbMSwicmVnZXgtZWRpdC1kaWFsb2ciLDMsImZvY3Vzb3V0Il0sWyJtYXQtZGlhbG9nLXRpdGxlIiwiIl0sWyJtYXRJbnB1dCIsIiIsImFyaWEtbGFiZWwiLHQsImNka0ZvY3VzSW5pdGlhbCIsIiIsMywidmFsdWUiLCJrZXlkb3duLmVudGVyIiwiaW5wdXQiXSxbInJlZ2V4U3RyaW5nSW5wdXQiLCIiXSxbMSwiZXhhbXBsZS1kZXRhaWxzIl0sWzMsImNsaWNrIl0sWyJjbGFzcyIsImdyb3VwLWNvbnRhaW5lciIsNCwibmdJZiJdLFsibWF0LWRpYWxvZy1hY3Rpb25zIiwiIiwiYWxpZ24iLCJlbmQiXSxbIm1hdC1idXR0b24iLCIiLCJtYXQtZGlhbG9nLWNsb3NlIiwiIl0sWyJtYXQtcmFpc2VkLWJ1dHRvbiIsIiIsImNvbG9yIiwicHJpbWFyeSIsIm1hdC1kaWFsb2ctY2xvc2UiLCIiLDMsImNsaWNrIl0sWzEsImdyb3VwLWNvbnRhaW5lciJdLFsxLCJncm91cGluZy1wcmV2aWV3Il0sWyJjbGFzcyIsIm1hdGNoLWNvbnRhaW5lciIsNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJlbXB0eSIsIiJdLFsxLCJtYXRjaC1jb250YWluZXIiXSxbImNsYXNzIiwiZ3JvdXAiLDMsIm5nU3R5bGUiLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsxLCJncm91cCIsMywibmdTdHlsZSJdLFsxLCJjb2xvci1zd2F0Y2giLDMsIm5nU3R5bGUiXSxbMSwiZ3JvdXAtaWQiLDMsInRpdGxlIl0sWzQsIm5nRm9yIiwibmdGb3JPZiJdLFsiY2xhc3MiLCJtb3JlIiw0LCJuZ0lmIl0sWyJjbGFzcyIsIm5vLW1hdGNoIiw0LCJuZ0lmIl0sWzMsInRpdGxlIl0sWzEsIm1vcmUiXSxbMSwibm8tbWF0Y2giXSxbMSwid2FybmluZyJdXX0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUpe2xldCByPVBlKCk7XygwLCJkaXYiLDApLFAoImZvY3Vzb3V0IixmdW5jdGlvbigpe3JldHVybiBpLmhhbmRsZUZvY3VzT3V0KCl9KSxfKDEsImgxIiwxKSxBKDIsIkNvbG9yIHJ1bnMgYnkgcmVnZXgiKSx2KCksXygzLCJtYXQtZGlhbG9nLWNvbnRlbnQiKSg0LCJwIiksQSg1LCJFbnRlciBhIHJlZ2V4IHdpdGggY2FwdHVyaW5nIGdyb3VwcyB0byBtYXRjaCBhZ2FpbnN0IHJ1biBuYW1lczoiKSx2KCksXyg2LCJtYXQtZm9ybS1maWVsZCIpKDcsImlucHV0IiwyLDMpLFAoImtleWRvd24uZW50ZXIiLGZ1bmN0aW9uKHMpe3JldHVybiBpLm9uRW50ZXIocy50YXJnZXQudmFsdWUpfSkoImlucHV0IixmdW5jdGlvbihzKXtyZXR1cm4gaS5yZWdleElucHV0Q2hhbmdlKHMudGFyZ2V0LnZhbHVlKX0pLHYoKSgpKCksXyg5LCJkaXYiLDQpKDEwLCJwIiksQSgxMSwnIEVhY2ggbWF0Y2hpbmcgcnVuIHdpbGwgYmUgYXNzaWduZWQgYSBjb2xvciBiYXNlZCBvbiB0aGUgImtleSIgZm9ybWVkIGJ5IGl0cyBtYXRjaGVzIHRvIHRoZSBjYXB0dXJpbmcgZ3JvdXBzLiAnKSxPKDEyLCJiciIpLF8oMTMsImJ1dHRvbiIsNSksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkuZmlsbEV4YW1wbGUoIih0cmFpbnxldmFsKSIpfSksQSgxNCwiIFRyeSAiKSxfKDE1LCJjb2RlIiksQSgxNiwiKHRyYWlufGV2YWwpIiksdigpKCksQSgxNywiIHRvIGFzc2lnbiBhbGwgcnVucyBjb250YWluaW5nICIpLF8oMTgsImNvZGUiKSxBKDE5LCJ0cmFpbiIpLHYoKSxBKDIwLCIgdG8gb25lIGNvbG9yIGFuZCBhbGwgcnVucyBjb250YWluaW5nICIpLF8oMjEsImNvZGUiKSxBKDIyLCJldmFsIiksdigpLEEoMjMsIiB0byBhbm90aGVyIGNvbG9yLiAiKSx2KCkoKSxFKDI0LERaZSw3LDIsImRpdiIsNiksXygyNSwiZGl2Iiw3KSgyNiwiYnV0dG9uIiw4KSxBKDI3LCJDYW5jZWwiKSx2KCksXygyOCwiYnV0dG9uIiw5KSxQKCJjbGljayIsZnVuY3Rpb24oKXtvZShyKTtsZXQgcz0kZSg4KTtyZXR1cm4gc2UoaS5vblNhdmVDbGljayhzLnZhbHVlKSl9KSxBKDI5LCIgU2F2ZSAiKSx2KCkoKSgpfTImZSYmKEMoNyksWmkoInZhbHVlIixpLnJlZ2V4U3RyaW5nKSxDKDE3KSx5KCJuZ0lmIixpLnJlZ2V4U3RyaW5nKSl9LGRlcGVuZGVuY2llczpbZG4sQmUsenUscGQsX24sVDIsRnRlLEQyLEEyLFVoLG5aLFFsXSxzdHlsZXM6WyIuZXhhbXBsZS1kZXRhaWxzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIGJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLDApO3BhZGRpbmc6MDtib3JkZXI6bm9uZTtjdXJzb3I6cG9pbnRlcjt0ZXh0LWRlY29yYXRpb246dW5kZXJsaW5lO2NvbG9yOiMxOTc2ZDJ9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmV4YW1wbGUtZGV0YWlsc1tfbmdjb250ZW50LSVDT01QJV0gICBidXR0b25bX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZXhhbXBsZS1kZXRhaWxzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIGJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzQyYTVmNX0uZXhhbXBsZS1kZXRhaWxzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIGJ1dHRvbltfbmdjb250ZW50LSVDT01QJV06dmlzaXRlZHtjb2xvcjojN2IxZmEyfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5leGFtcGxlLWRldGFpbHNbX25nY29udGVudC0lQ09NUCVdICAgYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXTp2aXNpdGVkLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZXhhbXBsZS1kZXRhaWxzW19uZ2NvbnRlbnQtJUNPTVAlXSAgIGJ1dHRvbltfbmdjb250ZW50LSVDT01QJV06dmlzaXRlZHtjb2xvcjojYmE2OGM4fS5ncm91cC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde21hcmdpbjoxMHB4IDB9Lmdyb3VwLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV0gICBoNFtfbmdjb250ZW50LSVDT01QJV17bWFyZ2luLWJvdHRvbToxMHB4fS5ncm91cC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdICAgLndhcm5pbmdbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiM2MTYxNjE7Zm9udC1zaXplOi45ZW19Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmdyb3VwLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV0gICAud2FybmluZ1tfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdICAgLndhcm5pbmdbX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfS5ncm91cGluZy1wcmV2aWV3W19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXI6MXB4IHNvbGlkICNlYmViZWI7bWF4LWhlaWdodDo1MHZoO292ZXJmbG93LXk6YXV0bztwYWRkaW5nOjIwcHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmdyb3VwaW5nLXByZXZpZXdbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXBpbmctcHJldmlld1tfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyOjFweCBzb2xpZCAjNTU1fS5tYXRjaC1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmZsZXgtc3RhcnQ7ZGlzcGxheTpncmlkO2ZsZXgtd3JhcDp3cmFwO2dhcDoxMHB4O2dyaWQtdGVtcGxhdGUtY29sdW1uczpyZXBlYXQoMiwgbWlubWF4KDUwJSwgMWZyKSl9LmNvbG9yLXN3YXRjaFtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLXJhZGl1czo1MCU7Ym94LXNoYWRvdzowIDAgMnB4ICMwMDA7ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjE1cHg7d2lkdGg6MTVweH11bFtfbmdjb250ZW50LSVDT01QJV17bGlzdC1zdHlsZS10eXBlOm5vbmU7cGFkZGluZzowfW1hdC1mb3JtLWZpZWxkW19uZ2NvbnRlbnQtJUNPTVAlXXt3aWR0aDoxMDAlfS5ncm91cFtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyOjFweCBzb2xpZCAjZWJlYmViO2JvcmRlci1yYWRpdXM6M3B4O21hcmdpbjowO3BhZGRpbmc6MTBweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXBbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXBbX25nY29udGVudC0lQ09NUCVde2JvcmRlcjoxcHggc29saWQgIzU1NX0uZ3JvdXBbX25nY29udGVudC0lQ09NUCVdICAgbGFiZWxbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6Z3JpZDtnYXA6MTBweDtncmlkLXRlbXBsYXRlLWNvbHVtbnM6bWF4LWNvbnRlbnQgYXV0bztwYWRkaW5nOjVweCAwfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cFtfbmdjb250ZW50LSVDT01QJV0gICBsYWJlbFtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cFtfbmdjb250ZW50LSVDT01QJV0gICBsYWJlbFtfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWJvdHRvbToxcHggc29saWQgIzU1NX0uZ3JvdXBbX25nY29udGVudC0lQ09NUCVdICAgbGFiZWxbX25nY29udGVudC0lQ09NUCVdICAgLmdyb3VwLWlkW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXNpemU6Ljk1ZW07b3ZlcmZsb3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXM7d2hpdGUtc3BhY2U6bm93cmFwfS5ncm91cFtfbmdjb250ZW50LSVDT01QJV0gICB1bFtfbmdjb250ZW50LSVDT01QJV17Zm9udC1zaXplOi45ZW19Lmdyb3VwW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHVsW19uZ2NvbnRlbnQtJUNPTVAlXSAgIGxpW19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpczt3aGl0ZS1zcGFjZTpub3dyYXB9Lmdyb3VwW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5tb3JlW19uZ2NvbnRlbnQtJUNPTVAlXSwgLmdyb3VwW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5uby1tYXRjaFtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6IzYxNjE2MTttYXJnaW4tdG9wOjVweH1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXBbX25nY29udGVudC0lQ09NUCVdICAgLm1vcmVbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXBbX25nY29udGVudC0lQ09NUCVdICAgLm1vcmVbX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC5ncm91cFtfbmdjb250ZW50LSVDT01QJV0gICAubm8tbWF0Y2hbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZ3JvdXBbX25nY29udGVudC0lQ09NUCVdICAgLm5vLW1hdGNoW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43KX0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLGxoZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLnN0b3JlPWUsdGhpcy5kaWFsb2dSZWY9aSx0aGlzLnRlbnRhdGl2ZVJlZ2V4U3RyaW5nJD1uZXcga2UsdGhpcy5ncm91cEJ5UmVnZXhTdHJpbmckPVFhKCgpPT5KdCh0aGlzLnN0b3JlLnNlbGVjdChZSSkucGlwZShRdCgxKSksdGhpcy50ZW50YXRpdmVSZWdleFN0cmluZyQpKS5waXBlKHpuKCIiKSksdGhpcy5jb2xvclJ1blBhaXJMaXN0JD1RYSgoKT0+dGhpcy5ncm91cEJ5UmVnZXhTdHJpbmckLnBpcGUoSHIoNTAwKSxZZShvPT57dHJ5e2xldCBzPW5ldyBSZWdFeHAobyk7cmV0dXJuIEJvb2xlYW4ocyl9Y2F0Y2h7cmV0dXJuITF9fSksZnIodGhpcy5hbGxSdW5zJCx0aGlzLnJ1bklkVG9FaWQkLHRoaXMuc3RvcmUuc2VsZWN0KE5hLmdldENvbG9yUGFsZXR0ZSksdGhpcy5zdG9yZS5zZWxlY3QoUXUpKSxMKChbbyxzLGEsbCxjXSk9PntsZXQgZD1HTSh7a2V5OnNyLlJFR0VYLHJlZ2V4U3RyaW5nOm99LHMsYSkscD1uZXcgTWFwLGg9W107Zm9yKGxldFtmLG1db2YgT2JqZWN0LmVudHJpZXMoZC5tYXRjaGVzKSl7bGV0IHg9cC5nZXQoZik7aWYoIXgpe2xldCBnPWwuY29sb3JzW3Auc2l6ZSVsLmNvbG9ycy5sZW5ndGhdO3g9Yz9nLmRhcmtIZXg6Zy5saWdodEhleCxwLnNldChmLHgpfWgucHVzaCh7Z3JvdXBJZDpmLGNvbG9yOngscnVuczptfSl9cmV0dXJuIGh9KSkpLnBpcGUoem4oW10pKSx0aGlzLmV4cGVyaW1lbnRJZHM9ci5leHBlcmltZW50SWRzLHRoaXMucnVuSWRUb0VpZCQ9THQodGhpcy5leHBlcmltZW50SWRzLm1hcChvPT50aGlzLnN0b3JlLnNlbGVjdChTZWUse2V4cGVyaW1lbnRJZDpvfSkucGlwZShMKHM9Pih7ZXhwZXJpbWVudElkOm8scnVuSWRzOnN9KSkpKSkucGlwZShMKG89PntsZXQgcz17fTtmb3IobGV0e3J1bklkczphLGV4cGVyaW1lbnRJZDpsfW9mIG8pZm9yKGxldCBjIG9mIGEpc1tjXT1sO3JldHVybiBzfSkpLHRoaXMuYWxsUnVucyQ9THQodGhpcy5leHBlcmltZW50SWRzLm1hcChvPT50aGlzLnN0b3JlLnNlbGVjdChyZCx7ZXhwZXJpbWVudElkOm99KSkpLnBpcGUoTChvPT5vLmZsYXQoKSkpfW9uUmVnZXhJbnB1dE9uQ2hhbmdlKGUpe3RoaXMudGVudGF0aXZlUmVnZXhTdHJpbmckLm5leHQoZSl9b25TYXZlKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goYXYoe2V4cGVyaW1lbnRJZHM6dGhpcy5leHBlcmltZW50SWRzLGdyb3VwQnk6e2tleTpzci5SRUdFWCxyZWdleFN0cmluZzplfX0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSksTSh0dSksTShjdykpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInJlZ2V4LWVkaXQtZGlhbG9nIl1dLGRlY2xzOjMsdmFyczo2LGNvbnN0czpbWzMsInJlZ2V4U3RyaW5nIiwiY29sb3JSdW5QYWlyTGlzdCIsIm9uU2F2ZSIsInJlZ2V4SW5wdXRPbkNoYW5nZSJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwicmVnZXgtZWRpdC1kaWFsb2ctY29tcG9uZW50IiwwKSxQKCJvblNhdmUiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uU2F2ZShvKX0pKCJyZWdleElucHV0T25DaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uUmVnZXhJbnB1dE9uQ2hhbmdlKG8pfSksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSx2KCkpLDImZSYmeSgicmVnZXhTdHJpbmciLFUoMSwyLGkuZ3JvdXBCeVJlZ2V4U3RyaW5nJCkpKCJjb2xvclJ1blBhaXJMaXN0IixVKDIsNCxpLmNvbG9yUnVuUGFpckxpc3QkKSl9LGRlcGVuZGVuY2llczpbYWhlLEdlXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVdLCByZWdleC1lZGl0LWRpYWxvZy1jb21wb25lbnRbX25nY29udGVudC0lQ09NUCVdIHtcbiAgICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICAgIGhlaWdodDogMTAwJTtcbiAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICB9Il19KSxufSkoKTtmdW5jdGlvbiBQWmUobix0KXsxJm4mJk8oMCwibWF0LWljb24iLDE0KX1mdW5jdGlvbiBSWmUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJidXR0b24iLDEzKSxQKCJjbGljayIsZnVuY3Rpb24oKXtvZShlKTtsZXQgcj1TKCk7cmV0dXJuIHNlKHIub25Hcm91cEJ5Q2hhbmdlLmVtaXQoe2tleTpyLkdyb3VwQnlLZXkuRVhQRVJJTUVOVH0pKX0pLF8oMSwic3BhbiIpLEUoMixQWmUsMSwwLCJtYXQtaWNvbiIsNyksdigpLF8oMywibGFiZWwiKSxBKDQsIkV4cGVyaW1lbnQiKSx2KCkoKX1pZigyJm4pe2xldCBlPVMoKTt6ZSgiYXJpYS1jaGVja2VkIixlLnNlbGVjdGVkR3JvdXBCeS5rZXk9PT1lLkdyb3VwQnlLZXkuRVhQRVJJTUVOVCksQygyKSx5KCJuZ0lmIixlLnNlbGVjdGVkR3JvdXBCeS5rZXk9PT1lLkdyb3VwQnlLZXkuRVhQRVJJTUVOVCl9fWZ1bmN0aW9uIE9aZShuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsMTQpfWZ1bmN0aW9uIGtaZShuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsMTQpfWZ1bmN0aW9uIEZaZShuLHQpe2lmKDEmbiYmKF8oMCwibGFiZWwiKSxBKDEpLHYoKSksMiZuKXtsZXQgZT1TKCk7QygxKSx5dChlLnJlZ2V4U3RyaW5nKX19ZnVuY3Rpb24gTlplKG4sdCl7MSZuJiYoXygwLCJsYWJlbCIsMTUpLEEoMSwiKG5vbmUgc2V0KSIpLHYoKSl9dmFyIGNoZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuZGlhbG9nPWUsdGhpcy5Hcm91cEJ5S2V5PXNyLHRoaXMub25Hcm91cEJ5Q2hhbmdlPW5ldyBHfW9uUmVnZXhTdHJpbmdFZGl0KCl7dGhpcy5kaWFsb2cub3BlbihsaGUse21heEhlaWdodDoiOTV2aCIsbWF4V2lkdGg6IjgwdnciLGRhdGE6e2V4cGVyaW1lbnRJZHM6dGhpcy5leHBlcmltZW50SWRzfX0pfW9uR3JvdXBCeVJlZ2V4Q2xpY2soKXt0aGlzLnJlZ2V4U3RyaW5nP3RoaXMub25Hcm91cEJ5Q2hhbmdlLmVtaXQoe2tleTpzci5SRUdFWCxyZWdleFN0cmluZzp0aGlzLnJlZ2V4U3RyaW5nfSk6dGhpcy5vblJlZ2V4U3RyaW5nRWRpdCgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKHZsKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sicnVucy1ncm91cC1tZW51LWJ1dHRvbi1jb21wb25lbnQiXV0saW5wdXRzOntzaG93RXhwZXJpbWVudHNHcm91cEJ5OiJzaG93RXhwZXJpbWVudHNHcm91cEJ5IixleHBlcmltZW50SWRzOiJleHBlcmltZW50SWRzIixyZWdleFN0cmluZzoicmVnZXhTdHJpbmciLHNlbGVjdGVkR3JvdXBCeToic2VsZWN0ZWRHcm91cEJ5In0sb3V0cHV0czp7b25Hcm91cEJ5Q2hhbmdlOiJvbkdyb3VwQnlDaGFuZ2UifSxkZWNsczoyMix2YXJzOjgsY29uc3RzOltbIm1hdC1pY29uLWJ1dHRvbiIsIiIsInRpdGxlIiwiQ29sb3IgcnVucyBieS4uLiIsMywibWF0TWVudVRyaWdnZXJGb3IiXSxbInN2Z0ljb24iLCJwYWxldHRlXzI0cHgiXSxbMSwicnVuLXRhYmxlLWNvbG9yLWdyb3VwLWJ5Il0sWyJncm91cEJ5TWVudSIsIm1hdE1lbnUiXSxbMSwibGFiZWwiXSxbIm1hdC1tZW51LWl0ZW0iLCIiLCJyb2xlIiwibWVudWl0ZW1yYWRpbyIsImRhdGEtdmFsdWUiLCJleHBlcmltZW50IiwzLCJjbGljayIsNCwibmdJZiJdLFsibWF0LW1lbnUtaXRlbSIsIiIsInJvbGUiLCJtZW51aXRlbXJhZGlvIiwiZGF0YS12YWx1ZSIsInJ1biIsMywiY2xpY2siXSxbInN2Z0ljb24iLCJkb25lXzI0cHgiLDQsIm5nSWYiXSxbIm1hdC1tZW51LWl0ZW0iLCIiLCJyb2xlIiwibWVudWl0ZW1yYWRpbyIsImRhdGEtdmFsdWUiLCJyZWdleCIsMywiY2xpY2siXSxbIm1hdC1tZW51LWl0ZW0iLCIiLCJyb2xlIiwibWVudWl0ZW0iLCJkYXRhLXZhbHVlIiwicmVnZXgtZWRpdCIsMSwiZGlzcGxheS1yZWdleC1zdHJpbmciLDMsImNsaWNrIl0sWyJzdmdJY29uIiwiZWRpdF8yNHB4Il0sWzQsIm5nSWYiXSxbImNsYXNzIiwibm9uZS1zZXQtc3RyaW5nIiw0LCJuZ0lmIl0sWyJtYXQtbWVudS1pdGVtIiwiIiwicm9sZSIsIm1lbnVpdGVtcmFkaW8iLCJkYXRhLXZhbHVlIiwiZXhwZXJpbWVudCIsMywiY2xpY2siXSxbInN2Z0ljb24iLCJkb25lXzI0cHgiXSxbMSwibm9uZS1zZXQtc3RyaW5nIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJidXR0b24iLDApLE8oMSwibWF0LWljb24iLDEpLHYoKSxfKDIsIm1hdC1tZW51IiwyLDMpKDQsImRpdiIsNCksQSg1LCJDb2xvciBydW5zIGJ5IiksdigpLEUoNixSWmUsNSwyLCJidXR0b24iLDUpLF8oNywiYnV0dG9uIiw2KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkdyb3VwQnlDaGFuZ2UuZW1pdCh7a2V5OmkuR3JvdXBCeUtleS5SVU59KX0pLF8oOCwic3BhbiIpLEUoOSxPWmUsMSwwLCJtYXQtaWNvbiIsNyksdigpLF8oMTAsImxhYmVsIiksQSgxMSwiUnVuIiksdigpKCksXygxMiwiYnV0dG9uIiw4KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkdyb3VwQnlSZWdleENsaWNrKCl9KSxfKDEzLCJzcGFuIiksRSgxNCxrWmUsMSwwLCJtYXQtaWNvbiIsNyksdigpLF8oMTUsImxhYmVsIiksQSgxNiwiUmVnZXgiKSx2KCkoKSxfKDE3LCJidXR0b24iLDkpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uUmVnZXhTdHJpbmdFZGl0KCl9KSxfKDE4LCJzcGFuIiksTygxOSwibWF0LWljb24iLDEwKSx2KCksRSgyMCxGWmUsMiwxLCJsYWJlbCIsMTEpLEUoMjEsTlplLDIsMCwibGFiZWwiLDEyKSx2KCkoKSksMiZlJiYoeSgibWF0TWVudVRyaWdnZXJGb3IiLCRlKDMpKSxDKDYpLHkoIm5nSWYiLGkuc2hvd0V4cGVyaW1lbnRzR3JvdXBCeSksQygxKSx6ZSgiYXJpYS1jaGVja2VkIixpLnNlbGVjdGVkR3JvdXBCeS5rZXk9PT1pLkdyb3VwQnlLZXkuUlVOKSxDKDIpLHkoIm5nSWYiLGkuc2VsZWN0ZWRHcm91cEJ5LmtleT09PWkuR3JvdXBCeUtleS5SVU4pLEMoMyksemUoImFyaWEtY2hlY2tlZCIsaS5zZWxlY3RlZEdyb3VwQnkua2V5PT09aS5Hcm91cEJ5S2V5LlJFR0VYKSxDKDIpLHkoIm5nSWYiLGkuc2VsZWN0ZWRHcm91cEJ5LmtleT09PWkuR3JvdXBCeUtleS5SRUdFWCksQyg2KSx5KCJuZ0lmIixpLnJlZ2V4U3RyaW5nKSxDKDEpLHkoIm5nSWYiLCFpLnJlZ2V4U3RyaW5nKSl9LGRlcGVuZGVuY2llczpbQmUsX24sR3QsaGQsbnUsZmRdLHN0eWxlczpbIi5ydW4tdGFibGUtY29sb3ItZ3JvdXAtYnl7Zm9udC1zaXplOjE2cHh9ICAucnVuLXRhYmxlLWNvbG9yLWdyb3VwLWJ5IC5sYWJlbHtjb2xvcjojNjE2MTYxO2ZvbnQtc2l6ZTouOWVtO21hcmdpbjoxMHB4IDA7cGFkZGluZzowIDE2cHg7cG9pbnRlci1ldmVudHM6bm9uZX0gIC5ydW4tdGFibGUtY29sb3ItZ3JvdXAtYnkgYnV0dG9ue2Rpc3BsYXk6Z3JpZDtnYXA6MnB4IDEwcHg7Z3JpZC10ZW1wbGF0ZS1jb2x1bW5zOjIwcHggYXV0b30gIC5ydW4tdGFibGUtY29sb3ItZ3JvdXAtYnkgbWF0LWljb257aGVpZ2h0OjIwcHg7d2lkdGg6MjBweH0gIC5ydW4tdGFibGUtY29sb3ItZ3JvdXAtYnkgLmRpc3BsYXktcmVnZXgtc3RyaW5ne3BhZGRpbmctbGVmdDo0MHB4fSAgLnJ1bi10YWJsZS1jb2xvci1ncm91cC1ieSAuZGlzcGxheS1yZWdleC1zdHJpbmcgLm5vbmUtc2V0LXN0cmluZ3tjb2xvcjojNjE2MTYxfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgICAgLnJ1bi10YWJsZS1jb2xvci1ncm91cC1ieSAuZGlzcGxheS1yZWdleC1zdHJpbmcgLm5vbmUtc2V0LXN0cmluZywgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgICAucnVuLXRhYmxlLWNvbG9yLWdyb3VwLWJ5IC5kaXNwbGF5LXJlZ2V4LXN0cmluZyAubm9uZS1zZXQtc3RyaW5ne2NvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjcpfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksdWhlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuc2hvd0V4cGVyaW1lbnRzR3JvdXBCeSQ9dGhpcy5zdG9yZS5zZWxlY3QoZSQpLnBpcGUoTChpPT5pLmhhcyhoaS5DT01QQVJFX0VYUEVSSU1FTlQpKSksdGhpcy5zZWxlY3RlZEdyb3VwQnkkPXRoaXMuc3RvcmUuc2VsZWN0KEVlZSksdGhpcy5ncm91cEJ5UmVnZXhTdHJpbmckPXRoaXMuc3RvcmUuc2VsZWN0KFlJKX1vbkdyb3VwQnlDaGFuZ2UoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChhdih7ZXhwZXJpbWVudElkczp0aGlzLmV4cGVyaW1lbnRJZHMsZ3JvdXBCeTplfSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sicnVucy1ncm91cC1tZW51LWJ1dHRvbiJdXSxpbnB1dHM6e2V4cGVyaW1lbnRJZHM6ImV4cGVyaW1lbnRJZHMifSxkZWNsczo0LHZhcnM6MTAsY29uc3RzOltbMywicmVnZXhTdHJpbmciLCJzZWxlY3RlZEdyb3VwQnkiLCJzaG93RXhwZXJpbWVudHNHcm91cEJ5IiwiZXhwZXJpbWVudElkcyIsIm9uR3JvdXBCeUNoYW5nZSJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwicnVucy1ncm91cC1tZW51LWJ1dHRvbi1jb21wb25lbnQiLDApLFAoIm9uR3JvdXBCeUNoYW5nZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25Hcm91cEJ5Q2hhbmdlKG8pfSksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIiksdigpKSwyJmUmJnkoInJlZ2V4U3RyaW5nIixVKDEsNCxpLmdyb3VwQnlSZWdleFN0cmluZyQpKSgic2VsZWN0ZWRHcm91cEJ5IixVKDIsNixpLnNlbGVjdGVkR3JvdXBCeSQpKSgic2hvd0V4cGVyaW1lbnRzR3JvdXBCeSIsVSgzLDgsaS5zaG93RXhwZXJpbWVudHNHcm91cEJ5JCkpKCJleHBlcmltZW50SWRzIixpLmV4cGVyaW1lbnRJZHMpfSxkZXBlbmRlbmNpZXM6W2NoZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksVlplPVsiZmlsdGVyIl07ZnVuY3Rpb24gSFplKG4sdCl7MSZuJiZOaSgwKX1mdW5jdGlvbiBVWmUobix0KXsxJm4mJk5pKDApfXZhciB6WmU9ZnVuY3Rpb24obil7cmV0dXJue2l0ZW06bn19O2Z1bmN0aW9uIGpaZShuLHQpe2lmKDEmbiYmKHNuKDApLEUoMSxVWmUsMSwwLCJuZy1jb250YWluZXIiLDEyKSxhbigpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0O1MoKTtsZXQgaT0kZSgxNCk7QygxKSx5KCJuZ1RlbXBsYXRlT3V0bGV0IixpKSgibmdUZW1wbGF0ZU91dGxldENvbnRleHQiLE9uKDIselplLGUpKX19ZnVuY3Rpb24gR1plKG4sdCl7MSZuJiYoXygwLCJkaXYiLDEzKSxPKDEsIm1hdC1zcGlubmVyIiwxNCksdigpKX1mdW5jdGlvbiBXWmUobix0KXsxJm4mJihfKDAsImRpdiIsMTUpLEEoMSwiTm8gUnVucyIpLHYoKSl9ZnVuY3Rpb24gcVplKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDE1KSgxLCJzcGFuIiksQSgyLCdObyBydW5zIG1hdGNoICInKSxfKDMsImNvZGUiKSxBKDQpLHYoKSxBKDUsJyInKSx2KCkoKSksMiZuKXtsZXQgZT1TKCk7Qyg0KSx5dChlLnJlZ2V4RmlsdGVyKX19dmFyIFlaZT1mdW5jdGlvbigpe3JldHVybls1LDEwLDIwXX07ZnVuY3Rpb24gWFplKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwibWF0LXBhZ2luYXRvciIsMTYpLFAoInBhZ2UiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkub25QYWdpbmF0aW9uQ2hhbmdlLmVtaXQocikpfSksdigpfWlmKDImbil7bGV0IGU9UygpO3koInBhZ2VTaXplT3B0aW9ucyIsUXAoNCxZWmUpKSgicGFnZUluZGV4IixlLnBhZ2luYXRpb25PcHRpb24ucGFnZUluZGV4KSgicGFnZVNpemUiLGUucGFnaW5hdGlvbk9wdGlvbi5wYWdlU2l6ZSkoImxlbmd0aCIsZS5maWx0ZXJlZEl0ZW1zTGVuZ3RoKX19ZnVuY3Rpb24gUVplKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwibWF0LWNoZWNrYm94IiwyNiksUCgiY2hhbmdlIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKDMpLmhhbmRsZVBhZ2VUb2dnbGUoKSl9KSx2KCl9aWYoMiZuKXtsZXQgZT1TKDMpO3koImNoZWNrZWQiLGUuYWxsUGFnZUl0ZW1zU2VsZWN0ZWQoKSkoImluZGV0ZXJtaW5hdGUiLCFlLmFsbFBhZ2VJdGVtc1NlbGVjdGVkKCkmJmUuc29tZVBhZ2VJdGVtc1NlbGVjdGVkKCkpfX12YXIgZGhlPWZ1bmN0aW9uKG4pe3JldHVybnt0eXBlOm59fTtmdW5jdGlvbiBLWmUobix0KXtpZigxJm4mJihfKDAsInNwYW4iLDI3KSxBKDEsIkV4cGVyaW1lbnQiKSx2KCkpLDImbil7bGV0IGU9UygzKTt5KCJtYXQtc29ydC1oZWFkZXIiLE9uKDEsZGhlLGUuU29ydFR5cGUuRVhQRVJJTUVOVF9OQU1FKSl9fWZ1bmN0aW9uIFpaZShuLHQpe2lmKDEmbiYmKF8oMCwic3BhbiIsMjcpLEEoMSwiUnVuIiksdigpKSwyJm4pe2xldCBlPVMoMyk7eSgibWF0LXNvcnQtaGVhZGVyIixPbigxLGRoZSxlLlNvcnRUeXBlLlJVTl9OQU1FKSl9fWZ1bmN0aW9uIEpaZShuLHQpe2lmKDEmbiYmKF8oMCwic3BhbiIpLE8oMSwicnVucy1ncm91cC1tZW51LWJ1dHRvbiIsMjgpLHYoKSksMiZuKXtsZXQgZT1TKDMpO0MoMSkseSgiZXhwZXJpbWVudElkcyIsZS5leHBlcmltZW50SWRzKX19dmFyIHBoZT1mdW5jdGlvbihuKXtyZXR1cm5bImNvbHVtbiIsbl19O2Z1bmN0aW9uICRaZShuLHQpe2lmKDEmbiYmKF8oMCwic3BhbiIsMjEpLHNuKDEsMjIpLEUoMixRWmUsMSwyLCJtYXQtY2hlY2tib3giLDIzKSxFKDMsS1plLDIsMywic3BhbiIsMjQpLEUoNCxaWmUsMiwzLCJzcGFuIiwyNCksRSg1LEpaZSwyLDEsInNwYW4iLDI1KSxhbigpLHYoKSksMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPVMoMik7eSgibmdDbGFzcyIsT24oNixwaGUsInRiLWNvbHVtbi0iK2UpKSxDKDEpLHkoIm5nU3dpdGNoIixlKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsaS5SdW5zVGFibGVDb2x1bW4uQ0hFQ0tCT1gpLEMoMSkseSgibmdTd2l0Y2hDYXNlIixpLlJ1bnNUYWJsZUNvbHVtbi5FWFBFUklNRU5UX05BTUUpLEMoMSkseSgibmdTd2l0Y2hDYXNlIixpLlJ1bnNUYWJsZUNvbHVtbi5SVU5fTkFNRSksQygxKSx5KCJuZ1N3aXRjaENhc2UiLGkuUnVuc1RhYmxlQ29sdW1uLlJVTl9DT0xPUil9fWZ1bmN0aW9uIGVKZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtzbigwKSxfKDEsImRpdiIsMzcpLFAoImNsaWNrIixmdW5jdGlvbihyKXtyZXR1cm4gci5zdG9wUHJvcGFnYXRpb24oKX0pLF8oMiwidGItcmFuZ2UtaW5wdXQiLDM4KSxQKCJyYW5nZVZhbHVlc0NoYW5nZWQiLGZ1bmN0aW9uKHIpe29lKGUpO2xldCBvPVMoMikuJGltcGxpY2l0O3JldHVybiBzZShTKDIpLmhhbmRsZUhwYXJhbUludGVydmFsQ2hhbmdlZChvLHIpKX0pLHYoKSgpLGFuKCl9aWYoMiZuKXtsZXQgZT1TKDIpLiRpbXBsaWNpdDtDKDIpLHkoIm1pbiIsZS5maWx0ZXIubWluVmFsdWUpKCJtYXgiLGUuZmlsdGVyLm1heFZhbHVlKSgibG93ZXJWYWx1ZSIsZS5maWx0ZXIuZmlsdGVyTG93ZXJWYWx1ZSkoInVwcGVyVmFsdWUiLGUuZmlsdGVyLmZpbHRlclVwcGVyVmFsdWUpfX1mdW5jdGlvbiB0SmUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDQwKSxQKCJjbGljayIsZnVuY3Rpb24ocil7cmV0dXJuIHIuc3RvcFByb3BhZ2F0aW9uKCl9KSxfKDEsIm1hdC1jaGVja2JveCIsMzYpLFAoImNoYW5nZSIsZnVuY3Rpb24oKXtsZXQgbz1vZShlKS4kaW1wbGljaXQscz1TKDMpLiRpbXBsaWNpdDtyZXR1cm4gc2UoUygyKS5oYW5kbGVIcGFyYW1EaXNjcmV0ZUNoYW5nZWQocyxvKSl9KSxfKDIsInNwYW4iKSxBKDMpLHYoKSgpKCl9aWYoMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPVMoMykuJGltcGxpY2l0O0MoMSkseSgiY2hlY2tlZCIsaS5maWx0ZXIuZmlsdGVyVmFsdWVzLmluY2x1ZGVzKGUpKSxDKDIpLHl0KGUpfX1mdW5jdGlvbiBuSmUobix0KXtpZigxJm4mJihzbigwKSxFKDEsdEplLDQsMiwiZGl2IiwzOSksYW4oKSksMiZuKXtsZXQgZT1TKDIpLiRpbXBsaWNpdDtDKDEpLHkoIm5nRm9yT2YiLGUuZmlsdGVyLnBvc3NpYmxlVmFsdWVzKX19ZnVuY3Rpb24gaUplKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO3NuKDApLF8oMSwiYnV0dG9uIiwzMiksUCgiY2xpY2siLGZ1bmN0aW9uKHIpe3JldHVybiByLnN0b3BQcm9wYWdhdGlvbigpfSksTygyLCJtYXQtaWNvbiIsMzMpLHYoKSxfKDMsIm1hdC1tZW51IixudWxsLDM0KSg1LCJkaXYiLDM1KSxQKCJjbGljayIsZnVuY3Rpb24ocil7cmV0dXJuIHIuc3RvcFByb3BhZ2F0aW9uKCl9KSxfKDYsIm1hdC1jaGVja2JveCIsMzYpLFAoImNoYW5nZSIsZnVuY3Rpb24oKXtvZShlKTtsZXQgcj1TKCkuJGltcGxpY2l0O3JldHVybiBzZShTKDIpLmhhbmRsZUhwYXJhbUluY2x1ZGVVbmRlZmluZWRUb2dnbGVkKHIpKX0pLF8oNywic3BhbiIpLEEoOCwiKHNob3cgZW1wdHkgdmFsdWUpIiksdigpKCkoKSxFKDksZUplLDMsNCwibmctY29udGFpbmVyIiwzMSksRSgxMCxuSmUsMiwxLCJuZy1jb250YWluZXIiLDMxKSx2KCksYW4oKX1pZigyJm4pe2xldCBlPSRlKDQpLGk9UygpLiRpbXBsaWNpdCxyPVMoMik7QygxKSx5KCJtYXRNZW51VHJpZ2dlckZvciIsZSksemUoImFyaWEtbGFiZWwiLCJGaWx0ZXIgaHBhcmFtICIrKGkuZGlzcGxheU5hbWV8fGkubmFtZSkpLEMoNSkseSgiY2hlY2tlZCIsaS5maWx0ZXIuaW5jbHVkZVVuZGVmaW5lZCksQygzKSx5KCJuZ0lmIixpLmZpbHRlci50eXBlPT09ci5Eb21haW5UeXBlLklOVEVSVkFMKSxDKDEpLHkoIm5nSWYiLGkuZmlsdGVyLnR5cGU9PT1yLkRvbWFpblR5cGUuRElTQ1JFVEUpfX12YXIgckplPWZ1bmN0aW9uKG4sdCl7cmV0dXJue3R5cGU6bixuYW1lOnR9fTtmdW5jdGlvbiBvSmUobix0KXtpZigxJm4mJihfKDAsInNwYW4iLDI5KSgxLCJzcGFuIiwzMCksQSgyKSx2KCksRSgzLGlKZSwxMSw1LCJuZy1jb250YWluZXIiLDMxKSx2KCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKDIpO3koIm1hdC1zb3J0LWhlYWRlciIsUXIoMyxySmUsaS5Tb3J0VHlwZS5IUEFSQU0sZS5uYW1lKSksQygyKSx5dChlLmRpc3BsYXlOYW1lfHxlLm5hbWUpLEMoMSkseSgibmdJZiIsZS5maWx0ZXIpfX1mdW5jdGlvbiBzSmUobix0KXtpZigxJm4pe2xldCBlPVBlKCk7c24oMCksXygxLCJidXR0b24iLDMyKSxQKCJjbGljayIsZnVuY3Rpb24ocil7cmV0dXJuIHIuc3RvcFByb3BhZ2F0aW9uKCl9KSxPKDIsIm1hdC1pY29uIiwzMyksdigpLF8oMywibWF0LW1lbnUiLG51bGwsMzQpKDUsImRpdiIsMzUpLFAoImNsaWNrIixmdW5jdGlvbihyKXtyZXR1cm4gci5zdG9wUHJvcGFnYXRpb24oKX0pLF8oNiwibWF0LWNoZWNrYm94IiwzNiksUCgiY2hhbmdlIixmdW5jdGlvbigpe29lKGUpO2xldCByPVMoKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoMikuaGFuZGxlTWV0cmljSW5jbHVkZVVuZGVmaW5lZENoYW5nZWQocikpfSksXyg3LCJzcGFuIiksQSg4LCIoc2hvdyBlbXB0eSB2YWx1ZSkiKSx2KCkoKSgpLF8oOSwiZGl2IiwzNyksUCgiY2xpY2siLGZ1bmN0aW9uKHIpe3JldHVybiByLnN0b3BQcm9wYWdhdGlvbigpfSksXygxMCwidGItcmFuZ2UtaW5wdXQiLDM4KSxQKCJyYW5nZVZhbHVlc0NoYW5nZWQiLGZ1bmN0aW9uKHIpe29lKGUpO2xldCBvPVMoKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoMikuaGFuZGxlTWV0cmljRmlsdGVyQ2hhbmdlZChvLHIpKX0pLHYoKSgpKCksYW4oKX1pZigyJm4pe2xldCBlPSRlKDQpLGk9UygpLiRpbXBsaWNpdDtDKDEpLHkoIm1hdE1lbnVUcmlnZ2VyRm9yIixlKSx6ZSgiYXJpYS1sYWJlbCIsIkZpbHRlciBtZXRyaWMgIisoaS5kaXNwbGF5TmFtZXx8aS50YWcpKSxDKDUpLHkoImNoZWNrZWQiLGkuZmlsdGVyLmluY2x1ZGVVbmRlZmluZWQpLEMoNCkseSgibWluIixpLmZpbHRlci5taW5WYWx1ZSkoIm1heCIsaS5maWx0ZXIubWF4VmFsdWUpKCJsb3dlclZhbHVlIixpLmZpbHRlci5maWx0ZXJMb3dlclZhbHVlKSgidXBwZXJWYWx1ZSIsaS5maWx0ZXIuZmlsdGVyVXBwZXJWYWx1ZSl9fXZhciBhSmU9ZnVuY3Rpb24obix0KXtyZXR1cm57dHlwZTpuLHRhZzp0fX07ZnVuY3Rpb24gbEplKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiwyOSkoMSwic3BhbiIsMzApLEEoMiksdigpLEUoMyxzSmUsMTEsNywibmctY29udGFpbmVyIiwzMSksdigpKSwyJm4pe2xldCBlPXQuJGltcGxpY2l0LGk9UygyKTt5KCJtYXQtc29ydC1oZWFkZXIiLFFyKDMsYUplLGkuU29ydFR5cGUuTUVUUklDLGUudGFnKSksQygyKSx5dChlLmRpc3BsYXlOYW1lfHxlLnRhZyksQygxKSx5KCJuZ0lmIixlLmZpbHRlcil9fWZ1bmN0aW9uIGNKZShuLHQpe2lmKDEmbil7bGV0IGU9UGUoKTtfKDAsImRpdiIsMTcpKDEsImRpdiIsMTgpLFAoIm1hdFNvcnRDaGFuZ2UiLGZ1bmN0aW9uKHIpe3JldHVybiBvZShlKSxzZShTKCkuaGFuZGxlU29ydENoYW5nZShyKSl9KSxFKDIsJFplLDYsOCwic3BhbiIsMTkpLEUoMyxvSmUsNCw2LCJzcGFuIiwyMCksRSg0LGxKZSw0LDYsInNwYW4iLDIwKSx2KCkoKX1pZigyJm4pe2xldCBlPVMoKTtDKDEpLHkoIm1hdFNvcnRBY3RpdmUiLGUuc29ydE9wdGlvbi5jb2x1bW4pLEMoMSkseSgibmdGb3JPZiIsZS5jb2x1bW5zKSxDKDEpLHkoIm5nRm9yT2YiLGUuaHBhcmFtQ29sdW1ucykoIm5nRm9yVHJhY2tCeSIsZS50cmFja0J5SHBhcmFtQ29sdW1uKSxDKDEpLHkoIm5nRm9yT2YiLGUubWV0cmljQ29sdW1ucykoIm5nRm9yVHJhY2tCeSIsZS50cmFja0J5TWV0cmljQ29sdW1uKX19ZnVuY3Rpb24gdUplKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwic3BhbiIpKDEsIm1hdC1jaGVja2JveCIsNDcpLFAoImNoYW5nZSIsZnVuY3Rpb24oKXtvZShlKTtsZXQgcj1TKDIpLml0ZW07cmV0dXJuIHNlKFMoKS5vblNlbGVjdGlvblRvZ2dsZS5lbWl0KHIpKX0pKCJkYmxjbGljayIsZnVuY3Rpb24oKXtvZShlKTtsZXQgcj1TKDIpLml0ZW07cmV0dXJuIHNlKFMoKS5vblNlbGVjdGlvbkRibENsaWNrLmVtaXQocikpfSksdigpKCl9aWYoMiZuKXtsZXQgZT1TKDIpLml0ZW07QygxKSx5KCJjaGVja2VkIixlLnNlbGVjdGVkKX19ZnVuY3Rpb24gZEplKG4sdCl7aWYoMSZuJiZPKDAsInRiLWV4cGVyaW1lbnQtYWxpYXMiLDQ4KSwyJm4pe2xldCBlPVMoMikuaXRlbTt5KCJhbGlhcyIsZS5leHBlcmltZW50QWxpYXMpKCJ0aXRsZSIsZS5leHBlcmltZW50TmFtZSl9fWZ1bmN0aW9uIHBKZShuLHQpe2lmKDEmbiYmKF8oMCwic3BhbiIsMzApLEEoMSksdigpKSwyJm4pe2xldCBlPVMoMikuaXRlbTtDKDEpLHl0KGUucnVuLm5hbWUpfX12YXIgaEplPWZ1bmN0aW9uKG4pe3JldHVybnsicnVuLWNvbG9yLXN3YXRjaCI6ITAsIm5vLWNvbG9yIjpufX07ZnVuY3Rpb24gZkplKG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwic3BhbiIpKDEsImJ1dHRvbiIsNDkpLFAoImNvbG9yUGlja2VyQ2hhbmdlIixmdW5jdGlvbihyKXtvZShlKTtsZXQgbz1TKDIpLml0ZW07cmV0dXJuIHNlKFMoKS5vblJ1bkNvbG9yQ2hhbmdlLmVtaXQoe3J1bklkOm8ucnVuLmlkLG5ld0NvbG9yOnJ9KSl9KSx2KCkoKX1pZigyJm4pe2xldCBlPVMoMikuaXRlbTtDKDEpLFB0KCJiYWNrZ3JvdW5kIixlLnJ1bkNvbG9yKSx5KCJuZ0NsYXNzIixPbig4LGhKZSwhZS5ydW5Db2xvcikpKCJjb2xvclBpY2tlciIsZS5ydW5Db2xvcikoImNwRGlhbG9nRGlzcGxheSIsInBvcHVwIikoImNwUG9zaXRpb25PZmZzZXQiLC0yMCkoImNwVXNlUm9vdFZpZXdDb250YWluZXIiLCEwKSgiY3BPdXRwdXRGb3JtYXQiLCJoZXgiKX19ZnVuY3Rpb24gbUplKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiw0NCksc24oMSwyMiksRSgyLHVKZSwyLDEsInNwYW4iLDI1KSxFKDMsZEplLDEsMiwidGItZXhwZXJpbWVudC1hbGlhcyIsNDUpLEUoNCxwSmUsMiwxLCJzcGFuIiw0NiksRSg1LGZKZSwyLDEwLCJzcGFuIiwyNSksYW4oKSx2KCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKDIpO3koIm5nQ2xhc3MiLE9uKDYscGhlLCJ0Yi1jb2x1bW4tIitlKSksQygxKSx5KCJuZ1N3aXRjaCIsZSksQygxKSx5KCJuZ1N3aXRjaENhc2UiLGkuUnVuc1RhYmxlQ29sdW1uLkNIRUNLQk9YKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsaS5SdW5zVGFibGVDb2x1bW4uRVhQRVJJTUVOVF9OQU1FKSxDKDEpLHkoIm5nU3dpdGNoQ2FzZSIsaS5SdW5zVGFibGVDb2x1bW4uUlVOX05BTUUpLEMoMSkseSgibmdTd2l0Y2hDYXNlIixpLlJ1bnNUYWJsZUNvbHVtbi5SVU5fQ09MT1IpfX1mdW5jdGlvbiBnSmUobix0KXtpZigxJm4mJihfKDAsInNwYW4iLDUwKSxBKDEpLHYoKSksMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPVMoKS5pdGVtO0MoMSkseXQoaS5ocGFyYW1zLmdldChlLm5hbWUpKX19ZnVuY3Rpb24gX0plKG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiw1MCksQSgxKSx2KCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKCkuaXRlbTtDKDEpLHl0KGkubWV0cmljcy5nZXQoZS50YWcpKX19ZnVuY3Rpb24gdkplKG4sdCl7aWYoMSZuJiYoXygwLCJkaXYiLDQxKSxFKDEsbUplLDYsOCwic3BhbiIsNDIpLEUoMixnSmUsMiwxLCJzcGFuIiw0MyksRSgzLF9KZSwyLDEsInNwYW4iLDQzKSx2KCkpLDImbil7bGV0IGU9dC5pdGVtLGk9UygpO3plKCJkYXRhLWlkIixlLnJ1bi5pZCksQygxKSx5KCJuZ0Zvck9mIixpLmNvbHVtbnMpLEMoMSkseSgibmdGb3JPZiIsaS5ocGFyYW1Db2x1bW5zKSxDKDEpLHkoIm5nRm9yT2YiLGkubWV0cmljQ29sdW1ucyl9fXZhciB5SmU9KCgpPT57Y2xhc3MgbiBleHRlbmRzIGUwe2NvbnN0cnVjdG9yKCl7c3VwZXIoLi4uYXJndW1lbnRzKSx0aGlzLml0ZW1zUGVyUGFnZUxhYmVsPSJTaG93IHJ1bnM6In19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKCl7bGV0IHQ7cmV0dXJuIGZ1bmN0aW9uKGkpe3JldHVybih0fHwodD1waShuKSkpKGl8fG4pfX0oKSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLGhoZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5kYXRhU291cmNlPW5ldyBKayx0aGlzLkRvbWFpblR5cGU9Q2ksdGhpcy5SdW5zVGFibGVDb2x1bW49dmEsdGhpcy5Tb3J0VHlwZT1pZCx0aGlzLm9uUmVnZXhGaWx0ZXJDaGFuZ2U9bmV3IEcsdGhpcy5vblNlbGVjdGlvblRvZ2dsZT1uZXcgRyx0aGlzLm9uU2VsZWN0aW9uRGJsQ2xpY2s9bmV3IEcsdGhpcy5vblBhZ2VTZWxlY3Rpb25Ub2dnbGU9bmV3IEcsdGhpcy5vblBhZ2luYXRpb25DaGFuZ2U9bmV3IEcsdGhpcy5vblNvcnRDaGFuZ2U9bmV3IEcsdGhpcy5vblJ1bkNvbG9yQ2hhbmdlPW5ldyBHLHRoaXMub25IcGFyYW1EaXNjcmV0ZUZpbHRlckNoYW5nZWQ9bmV3IEcsdGhpcy5vbkhwYXJhbUludGVydmFsRmlsdGVyQ2hhbmdlZD1uZXcgRyx0aGlzLm9uTWV0cmljRmlsdGVyQ2hhbmdlZD1uZXcgR31uZ09uQ2hhbmdlcygpe3RoaXMuZGF0YVNvdXJjZS5kYXRhPXRoaXMucGFnZUl0ZW1zfWdldEhwYXJhbUNvbHVtbklkKGUpe3JldHVybmBoOiR7ZS5uYW1lfWB9Z2V0TWV0cmljQ29sdW1uSWQoZSl7cmV0dXJuYG06JHtlLnRhZ31gfWdldENvbHVtbklkcygpe3JldHVyblsuLi50aGlzLmNvbHVtbnMsLi4udGhpcy5ocGFyYW1Db2x1bW5zLm1hcCh0aGlzLmdldEhwYXJhbUNvbHVtbklkKSwuLi50aGlzLm1ldHJpY0NvbHVtbnMubWFwKHRoaXMuZ2V0TWV0cmljQ29sdW1uSWQpXX1hbGxQYWdlSXRlbXNTZWxlY3RlZCgpe3JldHVybiBCb29sZWFuKHRoaXMucGFnZUl0ZW1zLmxlbmd0aCkmJnRoaXMucGFnZUl0ZW1zLmV2ZXJ5KGU9PmUuc2VsZWN0ZWQpfXNvbWVQYWdlSXRlbXNTZWxlY3RlZCgpe3JldHVybiB0aGlzLnBhZ2VJdGVtcy5zb21lKGU9PmUuc2VsZWN0ZWQpfWhhbmRsZVBhZ2VUb2dnbGUoKXt0aGlzLm9uUGFnZVNlbGVjdGlvblRvZ2dsZS5lbWl0KHtpdGVtczp0aGlzLnBhZ2VJdGVtc30pfWhhbmRsZVNvcnRDaGFuZ2UoZSl7bGV0IGk7c3dpdGNoKGUuZGlyZWN0aW9uKXtjYXNlImFzYyI6aT1pYy5BU0M7YnJlYWs7Y2FzZSJkZXNjIjppPWljLkRFU0M7YnJlYWs7ZGVmYXVsdDppPWljLlVOU0VUfXRoaXMub25Tb3J0Q2hhbmdlLmVtaXQoe2tleTplLmFjdGl2ZSxkaXJlY3Rpb246aX0pfW9uRmlsdGVyS2V5VXAoZSl7dGhpcy5vblJlZ2V4RmlsdGVyQ2hhbmdlLmVtaXQoZS50YXJnZXQudmFsdWUpfXRhYmxlVHJhY2tCeShlLGkpe3JldHVybiBpLnJ1bi5pZH1oYW5kbGVIcGFyYW1JbmNsdWRlVW5kZWZpbmVkVG9nZ2xlZChlKXtsZXR7bmFtZTppLGZpbHRlcjpyfT1lO2lmKCFyKXRocm93IG5ldyBSYW5nZUVycm9yKCJJbnZhcmlhbnQgZXJyb3I6IHJlcXVpcmUgZmlsdGVyIHRvIGV4aXN0IGZvciBpdCB0byBjaGFuZ2UiKTtyLnR5cGU9PT1DaS5ESVNDUkVURT90aGlzLm9uSHBhcmFtRGlzY3JldGVGaWx0ZXJDaGFuZ2VkLmVtaXQoe2hwYXJhbU5hbWU6aSxpbmNsdWRlVW5kZWZpbmVkOiFyLmluY2x1ZGVVbmRlZmluZWQsZmlsdGVyVmFsdWVzOnIuZmlsdGVyVmFsdWVzfSk6dGhpcy5vbkhwYXJhbUludGVydmFsRmlsdGVyQ2hhbmdlZC5lbWl0KHtuYW1lOmksaW5jbHVkZVVuZGVmaW5lZDohci5pbmNsdWRlVW5kZWZpbmVkLGZpbHRlckxvd2VyVmFsdWU6ci5maWx0ZXJMb3dlclZhbHVlLGZpbHRlclVwcGVyVmFsdWU6ci5maWx0ZXJVcHBlclZhbHVlfSl9aGFuZGxlSHBhcmFtSW50ZXJ2YWxDaGFuZ2VkKGUsaSl7bGV0e25hbWU6cixmaWx0ZXI6b309ZTtpZighbyl0aHJvdyBuZXcgUmFuZ2VFcnJvcigiSW52YXJpYW50IGVycm9yOiByZXF1aXJlIGZpbHRlciB0byBleGlzdCBmb3IgaXQgdG8gY2hhbmdlIik7dGhpcy5vbkhwYXJhbUludGVydmFsRmlsdGVyQ2hhbmdlZC5lbWl0KHtuYW1lOnIsaW5jbHVkZVVuZGVmaW5lZDpvLmluY2x1ZGVVbmRlZmluZWQsZmlsdGVyTG93ZXJWYWx1ZTppLmxvd2VyVmFsdWUsZmlsdGVyVXBwZXJWYWx1ZTppLnVwcGVyVmFsdWV9KX1oYW5kbGVIcGFyYW1EaXNjcmV0ZUNoYW5nZWQoZSxpKXtsZXR7bmFtZTpyLGZpbHRlcjpvfT1lO2lmKCFvKXRocm93IG5ldyBSYW5nZUVycm9yKCJJbnZhcmlhbnQgZXJyb3I6IHJlcXVpcmUgZmlsdGVyIHRvIGV4aXN0IGZvciBpdCB0byBjaGFuZ2UiKTtpZihvLnR5cGUhPT1DaS5ESVNDUkVURSl0aHJvdyBuZXcgUmFuZ2VFcnJvcihgSW52YXJpYW50IGVycm9yOiBleHBlY3RlZCBkaXNjcmV0ZSBkb21haW4gZm9yICR7cn1gKTtsZXQgcz1uZXcgU2V0KFsuLi5vLmZpbHRlclZhbHVlc10pO3MuaGFzKGkpP3MuZGVsZXRlKGkpOnMuYWRkKGkpLHRoaXMub25IcGFyYW1EaXNjcmV0ZUZpbHRlckNoYW5nZWQuZW1pdCh7aHBhcmFtTmFtZTpyLGluY2x1ZGVVbmRlZmluZWQ6by5pbmNsdWRlVW5kZWZpbmVkLGZpbHRlclZhbHVlczpbLi4uc119KX1oYW5kbGVNZXRyaWNJbmNsdWRlVW5kZWZpbmVkQ2hhbmdlZChlKXtpZighZS5maWx0ZXIpdGhyb3cgbmV3IFJhbmdlRXJyb3IoIkludmFyaWFudCBlcnJvcjogcmVxdWlyZSBmaWx0ZXIgdG8gZXhpc3QgZm9yIGl0IHRvIGNoYW5nZSIpO3RoaXMub25NZXRyaWNGaWx0ZXJDaGFuZ2VkLmVtaXQoe25hbWU6ZS50YWcsaW5jbHVkZVVuZGVmaW5lZDohZS5maWx0ZXIuaW5jbHVkZVVuZGVmaW5lZCxmaWx0ZXJMb3dlclZhbHVlOmUuZmlsdGVyLmZpbHRlckxvd2VyVmFsdWUsZmlsdGVyVXBwZXJWYWx1ZTplLmZpbHRlci5maWx0ZXJVcHBlclZhbHVlfSl9aGFuZGxlTWV0cmljRmlsdGVyQ2hhbmdlZChlLGkpe2lmKCFlLmZpbHRlcil0aHJvdyBuZXcgUmFuZ2VFcnJvcigiSW52YXJpYW50IGVycm9yOiByZXF1aXJlIGZpbHRlciB0byBleGlzdCBmb3IgaXQgdG8gY2hhbmdlIik7dGhpcy5vbk1ldHJpY0ZpbHRlckNoYW5nZWQuZW1pdCh7bmFtZTplLnRhZyxpbmNsdWRlVW5kZWZpbmVkOmUuZmlsdGVyLmluY2x1ZGVVbmRlZmluZWQsZmlsdGVyTG93ZXJWYWx1ZTppLmxvd2VyVmFsdWUsZmlsdGVyVXBwZXJWYWx1ZTppLnVwcGVyVmFsdWV9KX10cmFja0J5SHBhcmFtQ29sdW1uKGUpe3JldHVybiBlLm5hbWV9dHJhY2tCeU1ldHJpY0NvbHVtbihlKXtyZXR1cm4gZS50YWd9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInJ1bnMtdGFibGUtY29tcG9uZW50Il1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKG90KFZaZSw3LFJlKSxvdChLayw3KSxvdChNRSw3KSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5maWx0ZXI9ci5maXJzdCksTmUocj1MZSgpKSYmKGkucGFnaW5hdG9yPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLnNvcnQ9ci5maXJzdCl9fSxob3N0VmFyczoyLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmZXQoImZsZXgtbGF5b3V0IixpLnVzZUZsZXhpYmxlTGF5b3V0KX0saW5wdXRzOntleHBlcmltZW50SWRzOiJleHBlcmltZW50SWRzIixzaG93RXhwZXJpbWVudE5hbWU6InNob3dFeHBlcmltZW50TmFtZSIsY29sdW1uczoiY29sdW1ucyIsaHBhcmFtQ29sdW1uczoiaHBhcmFtQ29sdW1ucyIsbWV0cmljQ29sdW1uczoibWV0cmljQ29sdW1ucyIsYWxsSXRlbXNMZW5ndGg6ImFsbEl0ZW1zTGVuZ3RoIixmaWx0ZXJlZEl0ZW1zTGVuZ3RoOiJmaWx0ZXJlZEl0ZW1zTGVuZ3RoIix1c2VGbGV4aWJsZUxheW91dDoidXNlRmxleGlibGVMYXlvdXQiLHVzZVBhZ2luYXRpb246InVzZVBhZ2luYXRpb24iLHBhZ2VJdGVtczoicGFnZUl0ZW1zIixsb2FkaW5nOiJsb2FkaW5nIixudW1TZWxlY3RlZEl0ZW1zOiJudW1TZWxlY3RlZEl0ZW1zIixzb3J0T3B0aW9uOiJzb3J0T3B0aW9uIixwYWdpbmF0aW9uT3B0aW9uOiJwYWdpbmF0aW9uT3B0aW9uIixyZWdleEZpbHRlcjoicmVnZXhGaWx0ZXIifSxvdXRwdXRzOntvblJlZ2V4RmlsdGVyQ2hhbmdlOiJvblJlZ2V4RmlsdGVyQ2hhbmdlIixvblNlbGVjdGlvblRvZ2dsZToib25TZWxlY3Rpb25Ub2dnbGUiLG9uU2VsZWN0aW9uRGJsQ2xpY2s6Im9uU2VsZWN0aW9uRGJsQ2xpY2siLG9uUGFnZVNlbGVjdGlvblRvZ2dsZToib25QYWdlU2VsZWN0aW9uVG9nZ2xlIixvblBhZ2luYXRpb25DaGFuZ2U6Im9uUGFnaW5hdGlvbkNoYW5nZSIsb25Tb3J0Q2hhbmdlOiJvblNvcnRDaGFuZ2UiLG9uUnVuQ29sb3JDaGFuZ2U6Im9uUnVuQ29sb3JDaGFuZ2UiLG9uSHBhcmFtRGlzY3JldGVGaWx0ZXJDaGFuZ2VkOiJvbkhwYXJhbURpc2NyZXRlRmlsdGVyQ2hhbmdlZCIsb25IcGFyYW1JbnRlcnZhbEZpbHRlckNoYW5nZWQ6Im9uSHBhcmFtSW50ZXJ2YWxGaWx0ZXJDaGFuZ2VkIixvbk1ldHJpY0ZpbHRlckNoYW5nZWQ6Im9uTWV0cmljRmlsdGVyQ2hhbmdlZCJ9LGZlYXR1cmVzOlskdChbe3Byb3ZpZGU6ZTAsdXNlQ2xhc3M6eUplfV0pLEZ0XSxkZWNsczoxNSx2YXJzOjgsY29uc3RzOltbMSwiZmlsdGVyLXJvdyJdLFsicGxhY2Vob2xkZXIiLCJGaWx0ZXIgcnVucyAocmVnZXgpIiwxLCJydW4tZmlsdGVyIiwzLCJ2YWx1ZSIsImtleXVwIl0sWzEsInRhYmxlLWNvbnRhaW5lciJdLFsicm9sZSIsInRhYmxlIl0sWzQsIm5nVGVtcGxhdGVPdXRsZXQiXSxbInJvbGUiLCJyb3dncm91cCIsMSwicm93cyJdLFs0LCJuZ0ZvciIsIm5nRm9yT2YiLCJuZ0ZvclRyYWNrQnkiXSxbImNsYXNzIiwibG9hZGluZyIsNCwibmdJZiJdLFsiY2xhc3MiLCJuby1ydW5zIiw0LCJuZ0lmIl0sWyJzaG93Rmlyc3RMYXN0QnV0dG9ucyIsIiIsMywicGFnZVNpemVPcHRpb25zIiwicGFnZUluZGV4IiwicGFnZVNpemUiLCJsZW5ndGgiLCJwYWdlIiw0LCJuZ0lmIl0sWyJoZWFkZXIiLCIiXSxbInJvdyIsIiJdLFs0LCJuZ1RlbXBsYXRlT3V0bGV0IiwibmdUZW1wbGF0ZU91dGxldENvbnRleHQiXSxbMSwibG9hZGluZyJdLFsibW9kZSIsImluZGV0ZXJtaW5hdGUiLCJkaWFtZXRlciIsIjI4Il0sWzEsIm5vLXJ1bnMiXSxbInNob3dGaXJzdExhc3RCdXR0b25zIiwiIiwzLCJwYWdlU2l6ZU9wdGlvbnMiLCJwYWdlSW5kZXgiLCJwYWdlU2l6ZSIsImxlbmd0aCIsInBhZ2UiXSxbInJvbGUiLCJyb3dncm91cCIsMSwiaGVhZGVyIl0sWyJtYXRTb3J0IiwiIiwicm9sZSIsInJvdyIsMywibWF0U29ydEFjdGl2ZSIsIm1hdFNvcnRDaGFuZ2UiXSxbInJvbGUiLCJjb2x1bW5oZWFkZXIiLDMsIm5nQ2xhc3MiLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsicm9sZSIsImNvbHVtbmhlYWRlciIsImNsYXNzIiwiY29sdW1uIiwzLCJtYXQtc29ydC1oZWFkZXIiLDQsIm5nRm9yIiwibmdGb3JPZiIsIm5nRm9yVHJhY2tCeSJdLFsicm9sZSIsImNvbHVtbmhlYWRlciIsMywibmdDbGFzcyJdLFszLCJuZ1N3aXRjaCJdLFszLCJjaGVja2VkIiwiaW5kZXRlcm1pbmF0ZSIsImNoYW5nZSIsNCwibmdTd2l0Y2hDYXNlIl0sWzMsIm1hdC1zb3J0LWhlYWRlciIsNCwibmdTd2l0Y2hDYXNlIl0sWzQsIm5nU3dpdGNoQ2FzZSJdLFszLCJjaGVja2VkIiwiaW5kZXRlcm1pbmF0ZSIsImNoYW5nZSJdLFszLCJtYXQtc29ydC1oZWFkZXIiXSxbMywiZXhwZXJpbWVudElkcyJdLFsicm9sZSIsImNvbHVtbmhlYWRlciIsMSwiY29sdW1uIiwzLCJtYXQtc29ydC1oZWFkZXIiXSxbMSwibmFtZSJdLFs0LCJuZ0lmIl0sWyJtYXQtaWNvbi1idXR0b24iLCIiLDMsIm1hdE1lbnVUcmlnZ2VyRm9yIiwiY2xpY2siXSxbInN2Z0ljb24iLCJmaWx0ZXJfYWx0XzI0cHgiXSxbImZpbHRlck1lbnUiLCJtYXRNZW51Il0sWyJtYXQtbWVudS1pdGVtIiwiIiwicm9sZSIsIm1lbnVpdGVtY2hlY2tib3giLCJkaXNhYmxlUmlwcGxlIiwiIiwxLCJmaWx0ZXItbWVudS1jaGVja2JveC1yb3ciLDMsImNsaWNrIl0sWzMsImNoZWNrZWQiLCJjaGFuZ2UiXSxbImRpc2FibGVSaXBwbGUiLCIiLCJtYXQtbWVudS1pdGVtIiwiIiwxLCJyYW5nZS1pbnB1dC1jb250YWluZXIiLDMsImNsaWNrIl0sWzMsIm1pbiIsIm1heCIsImxvd2VyVmFsdWUiLCJ1cHBlclZhbHVlIiwicmFuZ2VWYWx1ZXNDaGFuZ2VkIl0sWyJtYXQtbWVudS1pdGVtIiwiIiwiY2xhc3MiLCJmaWx0ZXItbWVudS1jaGVja2JveC1yb3ciLCJyb2xlIiwibWVudWl0ZW1jaGVja2JveCIsMywiY2xpY2siLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsibWF0LW1lbnUtaXRlbSIsIiIsInJvbGUiLCJtZW51aXRlbWNoZWNrYm94IiwxLCJmaWx0ZXItbWVudS1jaGVja2JveC1yb3ciLDMsImNsaWNrIl0sWyJyb2xlIiwicm93Il0sWyJyb2xlIiwiY2VsbCIsMywibmdDbGFzcyIsNCwibmdGb3IiLCJuZ0Zvck9mIl0sWyJyb2xlIiwiY2VsbCIsImNsYXNzIiwiY29sdW1uIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbInJvbGUiLCJjZWxsIiwzLCJuZ0NsYXNzIl0sWzMsImFsaWFzIiwidGl0bGUiLDQsIm5nU3dpdGNoQ2FzZSJdLFsiY2xhc3MiLCJuYW1lIiw0LCJuZ1N3aXRjaENhc2UiXSxbInRpdGxlIiwiQ2xpY2sgdG8gdG9nZ2xlIHJ1biBzZWxlY3Rpb24gb3IgZG91YmxlIGNsaWNrIHRvIHNlbGVjdCBvbmx5IHRoaXMgcnVuLiIsMywiY2hlY2tlZCIsImNoYW5nZSIsImRibGNsaWNrIl0sWzMsImFsaWFzIiwidGl0bGUiXSxbMywibmdDbGFzcyIsImNvbG9yUGlja2VyIiwiY3BEaWFsb2dEaXNwbGF5IiwiY3BQb3NpdGlvbk9mZnNldCIsImNwVXNlUm9vdFZpZXdDb250YWluZXIiLCJjcE91dHB1dEZvcm1hdCIsImNvbG9yUGlja2VyQ2hhbmdlIl0sWyJyb2xlIiwiY2VsbCIsMSwiY29sdW1uIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoXygwLCJkaXYiLDApKDEsInRiLWZpbHRlci1pbnB1dCIsMSksUCgia2V5dXAiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uRmlsdGVyS2V5VXAobyl9KSx2KCkoKSxfKDIsImRpdiIsMikoMywiZGl2IiwzKSxFKDQsSFplLDEsMCwibmctY29udGFpbmVyIiw0KSxfKDUsImRpdiIsNSksRSg2LGpaZSwyLDQsIm5nLWNvbnRhaW5lciIsNiksdigpKCksRSg3LEdaZSwyLDAsImRpdiIsNyksRSg4LFdaZSwyLDAsImRpdiIsOCksRSg5LHFaZSw2LDEsImRpdiIsOCksdigpLEUoMTAsWFplLDEsNSwibWF0LXBhZ2luYXRvciIsOSksRSgxMSxjSmUsNSw2LCJuZy10ZW1wbGF0ZSIsbnVsbCwxMCxxdCksRSgxMyx2SmUsNCw0LCJuZy10ZW1wbGF0ZSIsbnVsbCwxMSxxdCkpLDImZSl7bGV0IHI9JGUoMTIpO0MoMSksWmkoInZhbHVlIixpLnJlZ2V4RmlsdGVyKSxDKDMpLHkoIm5nVGVtcGxhdGVPdXRsZXQiLHIpLEMoMikseSgibmdGb3JPZiIsaS5wYWdlSXRlbXMpKCJuZ0ZvclRyYWNrQnkiLGkudGFibGVUcmFja0J5KSxDKDEpLHkoIm5nSWYiLGkubG9hZGluZyksQygxKSx5KCJuZ0lmIiwhaS5sb2FkaW5nJiYwPT09aS5hbGxJdGVtc0xlbmd0aCksQygxKSx5KCJuZ0lmIiwhaS5sb2FkaW5nJiZpLmFsbEl0ZW1zTGVuZ3RoPjAmJjA9PT1pLmZpbHRlcmVkSXRlbXNMZW5ndGgpLEMoMSkseSgibmdJZiIsaS51c2VQYWdpbmF0aW9uKX19LGRlcGVuZGVuY2llczpbcmhlLEZuLGRuLEJlLG9zLENyLFVyLGN5LFlrLF9uLHlsLEd0LGhkLG51LGZkLEtrLEJvLE1FLEtwZSxzaGUsdWhlXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47Zm9udC1zaXplOjEzcHg7b3ZlcmZsb3c6aGlkZGVufS5maWx0ZXItcm93W19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4Om5vbmV9LnRhYmxlLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17Y29udGFpbjpsYXlvdXQgcGFpbnQ7ZmxleC1ncm93OjE7bWF4LXdpZHRoOjEwMCU7b3ZlcmZsb3cteDphdXRvO292ZXJmbG93LXk6YXV0bzt3aWxsLWNoYW5nZTp0cmFuc2Zvcm0sc2Nyb2xsLXBvc2l0aW9ufS5mbGV4LWxheW91dFtfbmdob3N0LSVDT01QJV0gICAubmFtZVtfbmdjb250ZW50LSVDT01QJV17d29yZC1icmVhazpicmVhay13b3JkO292ZXJmbG93LXdyYXA6YnJlYWstd29yZH0uZmxleC1sYXlvdXRbX25naG9zdC0lQ09NUCVdICAgbWF0LXBhZ2luYXRvcltfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLXRvcDoxcHggc29saWQgI2ViZWJlYjtwYWRkaW5nLWJvdHRvbToxMnB4fWJvZHkuZGFyay1tb2RlICAgLmZsZXgtbGF5b3V0W19uZ2hvc3QtJUNPTVAlXSAgIG1hdC1wYWdpbmF0b3JbX25nY29udGVudC0lQ09NUCVde2JvcmRlci10b3A6MXB4IHNvbGlkICM1NTV9W3JvbGU9dGFibGVdW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OnRhYmxlO3dpZHRoOjEwMCV9W3JvbGU9dGFibGVdW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5oZWFkZXJbX25nY29udGVudC0lQ09NUCVde3doaXRlLXNwYWNlOm5vd3JhcH1bcm9sZT10YWJsZV1bX25nY29udGVudC0lQ09NUCVdICAgLmhlYWRlcltfbmdjb250ZW50LSVDT01QJV0gICBbcm9sZT1jb2x1bW5oZWFkZXJdW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7cG9zaXRpb246c3RpY2t5O3RvcDowO3otaW5kZXg6MX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICBbcm9sZT10YWJsZV1bX25nY29udGVudC0lQ09NUCVdICAgLmhlYWRlcltfbmdjb250ZW50LSVDT01QJV0gICBbcm9sZT1jb2x1bW5oZWFkZXJdW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgW3JvbGU9dGFibGVdW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5oZWFkZXJbX25nY29udGVudC0lQ09NUCVdICAgW3JvbGU9Y29sdW1uaGVhZGVyXVtfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojMzAzMDMwfVtyb2xlPXRhYmxlXVtfbmdjb250ZW50LSVDT01QJV0gICBbcm9sZT1yb3ddW19uZ2NvbnRlbnQtJUNPTVAlXXtjb250YWluOnN0cmljdDtkaXNwbGF5OnRhYmxlLXJvdztoZWlnaHQ6NDNweH1bcm9sZT10YWJsZV1bX25nY29udGVudC0lQ09NUCVdICAgW3JvbGU9cm93XVtfbmdjb250ZW50LSVDT01QJV0gICAuY29sdW1uW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjZWJlYmViO2Rpc3BsYXk6dGFibGUtY2VsbDtwYWRkaW5nOjVweDt2ZXJ0aWNhbC1hbGlnbjptaWRkbGV9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgW3JvbGU9dGFibGVdW19uZ2NvbnRlbnQtJUNPTVAlXSAgIFtyb2xlPXJvd11bX25nY29udGVudC0lQ09NUCVdICAgLmNvbHVtbltfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIFtyb2xlPXRhYmxlXVtfbmdjb250ZW50LSVDT01QJV0gICBbcm9sZT1yb3ddW19uZ2NvbnRlbnQtJUNPTVAlXSAgIC5jb2x1bW5bX25nY29udGVudC0lQ09NUCVde2JvcmRlci1ib3R0b206MXB4IHNvbGlkICM1NTV9W3JvbGU9dGFibGVdW19uZ2NvbnRlbnQtJUNPTVAlXSAgIFtyb2xlPXJvd11bX25nY29udGVudC0lQ09NUCVdICAgLmNvbHVtbltfbmdjb250ZW50LSVDT01QJV06Zmlyc3QtY2hpbGR7cGFkZGluZy1sZWZ0OjI0cHh9W3JvbGU9dGFibGVdW19uZ2NvbnRlbnQtJUNPTVAlXSAgIFtyb2xlPXJvd11bX25nY29udGVudC0lQ09NUCVdICAgLmNvbHVtbltfbmdjb250ZW50LSVDT01QJV06bGFzdC1jaGlsZHtwYWRkaW5nLXJpZ2h0OjI0cHh9W3JvbGU9cm93Z3JvdXBdW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OnRhYmxlLXJvdy1ncm91cH1bcm9sZT1yb3dncm91cF0uaGVhZGVyW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OnRhYmxlLWhlYWRlci1ncm91cH0ubG9hZGluZ1tfbmdjb250ZW50LSVDT01QJV0sIC5uby1ydW5zW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7Ym9yZGVyOjA7Ym9yZGVyLWJvdHRvbToxcHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmZsZXg7aGVpZ2h0OjQ4cHg7cGFkZGluZzowIDI0cHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmxvYWRpbmdbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAubG9hZGluZ1tfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWJvdHRvbToxcHggc29saWQgIzU1NX1ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICAubm8tcnVuc1tfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC5uby1ydW5zW19uZ2NvbnRlbnQtJUNPTVAlXXtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjNTU1fS5sb2FkaW5nW19uZ2NvbnRlbnQtJUNPTVAlXXtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyfS5zZWxlY3QtYWxsW19uZ2NvbnRlbnQtJUNPTVAlXSAgIHRoW19uZ2NvbnRlbnQtJUNPTVAlXXtwYWRkaW5nLWJvdHRvbToxMnB4O3BhZGRpbmctdG9wOjEycHh9LnNlbGVjdC1hbGwtY29udGVudFtfbmdjb250ZW50LSVDT01QJV0sIC5zZWxlY3QtYWxsLWNvbnRlbnRbX25nY29udGVudC0lQ09NUCVdICAgYnV0dG9uW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXdlaWdodDo0MDA7bGluZS1oZWlnaHQ6MS42O3RleHQtYWxpZ246bGVmdH0uc2VsZWN0LWFsbC1jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXSAgIGJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17Zm9udC13ZWlnaHQ6NTAwO3BhZGRpbmc6MCA0cHh9LmZpbHRlci1yb3dbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcjtoZWlnaHQ6NDhweDtwYWRkaW5nOjAgMTZweCAwIDIxcHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLmZpbHRlci1yb3dbX25nY29udGVudC0lQ09NUCVdLCBib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAuZmlsdGVyLXJvd1tfbmdjb250ZW50LSVDT01QJV17Ym9yZGVyLWJvdHRvbToxcHggc29saWQgIzU1NX0uZmlsdGVyLXJvd1tfbmdjb250ZW50LSVDT01QJV0gICB0Yi1maWx0ZXItaW5wdXRbX25nY29udGVudC0lQ09NUCVde2ZsZXgtZ3JvdzoxfS50Yi1jb2x1bW4tY2hlY2tib3hbX25nY29udGVudC0lQ09NUCVdLCAudGItY29sdW1uLXJ1bl9jb2xvcltfbmdjb250ZW50LSVDT01QJV17d2lkdGg6MjBweH0udGItY29sdW1uLXJ1bl9jb2xvcltfbmdjb250ZW50LSVDT01QJV17dGV4dC1hbGlnbjpjZW50ZXJ9LnJ1bi1jb2xvci1zd2F0Y2hbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1yYWRpdXM6MTAwJTtib3JkZXI6MXB4IHNvbGlkICNlYmViZWI7aGVpZ2h0OjIwcHg7d2lkdGg6MjBweDtvdXRsaW5lOm5vbmV9LnJ1bi1jb2xvci1zd2F0Y2gubm8tY29sb3JbX25nY29udGVudC0lQ09NUCVde2JvcmRlci1jb2xvcjojYzZjYWQxO2JvcmRlci13aWR0aDoycHh9LnJhbmdlLWlucHV0LWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17aGVpZ2h0OmF1dG99W19uZ2hvc3QtJUNPTVAlXSAgICAgbWF0LXBhZ2luYXRvciBtYXQtZm9ybS1maWVsZHttYXJnaW46MH0uZmlsdGVyLW1lbnUtY2hlY2tib3gtcm93W19uZ2NvbnRlbnQtJUNPTVAlXSAgIG1hdC1jaGVja2JveFtfbmdjb250ZW50LSVDT01QJV0gICAgIGxhYmVse2Rpc3BsYXk6ZmxleDtoZWlnaHQ6MTAwJTthbGlnbi1pdGVtczpjZW50ZXJ9LmZpbHRlci1tZW51LWNoZWNrYm94LXJvd1tfbmdjb250ZW50LSVDT01QJV0gICBtYXQtY2hlY2tib3hbX25nY29udGVudC0lQ09NUCVdICAgICBsYWJlbCAubWF0LWNoZWNrYm94LWlubmVyLWNvbnRhaW5lcnttYXJnaW4tbGVmdDowfS5maWx0ZXItbWVudS1jaGVja2JveC1yb3dbX25nY29udGVudC0lQ09NUCVdICAgbWF0LWNoZWNrYm94W19uZ2NvbnRlbnQtJUNPTVAlXSAgICAgbGFiZWwgLm1hdC1jaGVja2JveC1sYWJlbHtvdmVyZmxvdzpoaWRkZW47dGV4dC1vdmVyZmxvdzplbGxpcHNpc31ib2R5LmRhcmstbW9kZVtfbmdob3N0LSVDT01QJV0gICBtYXQtcGFnaW5hdG9yW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgbWF0LXBhZ2luYXRvcltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojMzAzMDMwfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCkseEplPUooV00sbj0+bi5zdGF0ZT09PU9lLkxPQURJTkcpO2Z1bmN0aW9uIGZoZShuLHQpe3N3aXRjaCh0LnR5cGUpe2Nhc2UgaWQuRVhQRVJJTUVOVF9OQU1FOnJldHVybltuLmV4cGVyaW1lbnRBbGlhcyxuLnJ1bi5uYW1lLG4ucnVuLmlkXTtjYXNlIGlkLlJVTl9OQU1FOnJldHVybltuLnJ1bi5uYW1lLG4uZXhwZXJpbWVudEFsaWFzLG4ucnVuLmlkXTtjYXNlIGlkLkhQQVJBTTpyZXR1cm5bbi5ocGFyYW1zLmdldCh0Lm5hbWUpLG4ucnVuLm5hbWUsbi5leHBlcmltZW50QWxpYXMsbi5ydW4uaWRdO2Nhc2UgaWQuTUVUUklDOnJldHVybltuLm1ldHJpY3MuZ2V0KHQudGFnKSxuLnJ1bi5uYW1lLG4uZXhwZXJpbWVudEFsaWFzLG4ucnVuLmlkXTtkZWZhdWx0OnRocm93IG5ldyBFcnJvcihgTm90IHlldCBpbXBsZW1lbnRlZDogJHt0fWApfX1mdW5jdGlvbiBtaGUobix0KXtyZXR1cm4gdm9pZCAwPT09dD9uLmluY2x1ZGVVbmRlZmluZWQ6bi50eXBlPT09Q2kuRElTQ1JFVEU/bi5maWx0ZXJWYWx1ZXMuaW5jbHVkZXModCk6bi50eXBlPT09Q2kuSU5URVJWQUwmJm4uZmlsdGVyTG93ZXJWYWx1ZTw9dCYmdDw9bi5maWx0ZXJVcHBlclZhbHVlfXZhciBnaGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5sb2FkaW5nJD1udWxsLHRoaXMuaHBhcmFtQ29sdW1ucyQ9WHQoW10pLHRoaXMubWV0cmljQ29sdW1ucyQ9WHQoW10pLHRoaXMudXNlRmxleGlibGVMYXlvdXQ9ITEsdGhpcy51c2VQYWdpbmF0aW9uPSExLHRoaXMuY29sdW1ucz1bdmEuUlVOX05BTUVdLHRoaXMuc2hvd0hwYXJhbXNBbmRNZXRyaWNzPSExLHRoaXMuc29ydE9wdGlvbiQ9dGhpcy5zdG9yZS5zZWxlY3QoYUgpLHRoaXMucGFnaW5hdGlvbk9wdGlvbiQ9dGhpcy5zdG9yZS5zZWxlY3Qoc0gpLHRoaXMucmVnZXhGaWx0ZXIkPXRoaXMuc3RvcmUuc2VsZWN0KFFtKSx0aGlzLm5nVW5zdWJzY3JpYmU9bmV3IGtlfWlzRXhwZXJpbWVudE5hbWVWaXNpYmxlKCl7cmV0dXJuIHRoaXMuY29sdW1ucy5zb21lKGU9PmU9PT12YS5FWFBFUklNRU5UX05BTUUpfW5nT25Jbml0KCl7bGV0IGk9THQodGhpcy5leHBlcmltZW50SWRzLm1hcChzPT50aGlzLmdldFJ1blRhYmxlSXRlbXNGb3JFeHBlcmltZW50KHMpKSkucGlwZShMKHM9PltdLmNvbmNhdCguLi5zKSkpO3RoaXMuYWxsVW5zb3J0ZWRSdW5UYWJsZUl0ZW1zJD1pLnBpcGUoc3QodGhpcy5uZ1Vuc3Vic2NyaWJlKSxNYSgxKSksdGhpcy5hbGxJdGVtc0xlbmd0aCQ9dGhpcy5hbGxVbnNvcnRlZFJ1blRhYmxlSXRlbXMkLnBpcGUoTChzPT5zLmxlbmd0aCkpO2xldCByPXRoaXMuZ2V0RmlsdGVyZWRJdGVtcyQodGhpcy5hbGxVbnNvcnRlZFJ1blRhYmxlSXRlbXMkKS5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSksTWEoMSkpO3RoaXMuZmlsdGVyZWRJdGVtc0xlbmd0aCQ9ci5waXBlKEwocz0+cy5sZW5ndGgpKSx0aGlzLnBhZ2VJdGVtcyQ9dGhpcy5zb3J0ZWRBbmRTbGljZWRJdGVtcyQociksdGhpcy5udW1TZWxlY3RlZEl0ZW1zJD10aGlzLmFsbFVuc29ydGVkUnVuVGFibGVJdGVtcyQucGlwZShMKHM9PnMucmVkdWNlKChhLGwpPT5hK051bWJlcihsLnNlbGVjdGVkKSwwKSkpO2xldCBvPXRoaXMuZXhwZXJpbWVudElkcy5tYXAocz0+dGhpcy5zdG9yZS5zZWxlY3QoeEplLHtleHBlcmltZW50SWQ6c30pKTtpZih0aGlzLmxvYWRpbmckPUx0KG8pLnBpcGUoTChzPT5zLnNvbWUoYT0+YSkpKSx0aGlzLnNob3dIcGFyYW1zQW5kTWV0cmljcyl7bGV0IHM9dGhpcy5zdG9yZS5zZWxlY3QoRGYuZ2V0RXhwZXJpbWVudHNIcGFyYW1zQW5kTWV0cmljc1NwZWNzLHtleHBlcmltZW50SWRzOnRoaXMuZXhwZXJpbWVudElkc30pO3RoaXMuaHBhcmFtQ29sdW1ucyQ9THQoW3RoaXMuc3RvcmUuc2VsZWN0KERmLmdldEhwYXJhbUZpbHRlck1hcCx0aGlzLmV4cGVyaW1lbnRJZHMpLHNdKS5waXBlKEwoKFthLHtocGFyYW1zOmx9XSk9PmwubWFwKCh7bmFtZTpjLGRpc3BsYXlOYW1lOnV9KT0+e2xldCBwPWEuZ2V0KGMpO2lmKCFwKXRocm93IG5ldyBSYW5nZUVycm9yKGBJbnZhcmlhbnQgZXJyb3I6IGEgZmlsdGVyIGZvciAke2N9IG11c3QgZXhpc3Qgd2hlbiB0aGUgaHBhcmFtIGV4aXN0c2ApO3JldHVybntkaXNwbGF5TmFtZTp1LG5hbWU6YyxmaWx0ZXI6cH19KSkpLHRoaXMubWV0cmljQ29sdW1ucyQ9THQoW3RoaXMuc3RvcmUuc2VsZWN0KERmLmdldE1ldHJpY0ZpbHRlck1hcCx0aGlzLmV4cGVyaW1lbnRJZHMpLHNdKS5waXBlKEwoKFthLHttZXRyaWNzOmx9XSk9PmwubWFwKCh7dGFnOmMsZGlzcGxheU5hbWU6dX0pPT57bGV0IGQ9YS5nZXQoYyk7aWYoIWQpdGhyb3cgbmV3IFJhbmdlRXJyb3IoYEludmFyaWFudCBlcnJvcjogYSBmaWx0ZXIgZm9yICR7Y30gbXVzdCBleGlzdCB3aGVuIHRoZSBtZXRyaWMgZXhpc3RzYCk7cmV0dXJue2Rpc3BsYXlOYW1lOnUsdGFnOmMsZmlsdGVyOmR9fSkpKX10aGlzLmNvbHVtbnMuaW5jbHVkZXModmEuQ0hFQ0tCT1gpJiZ0aGlzLnN0b3JlLnNlbGVjdChSYSkucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpLHlpKChhLGwpPT5QcyhhLGwpKSx1aSgoKT0+aS5waXBlKFllKGE9PmEubGVuZ3RoPjUwMCksUXQoMSkpKSkuc3Vic2NyaWJlKCgpPT57dGhpcy5zdG9yZS5kaXNwYXRjaCh2dih7bG9jYWxpemVkTWVzc2FnZToiVGhlIG51bWJlciBvZiBydW5zIGV4Y2VlZHMgNTAwLiBOZXcgcnVucyBhcmUgdW5zZWxlY3RlZCBmb3IgcGVyZm9ybWFuY2UgcmVhc29ucy4ifSkpfSksdGhpcy5zdG9yZS5kaXNwYXRjaChkSSh7ZXhwZXJpbWVudElkczp0aGlzLmV4cGVyaW1lbnRJZHN9KSl9bmdPbkRlc3Ryb3koKXt0aGlzLm5nVW5zdWJzY3JpYmUubmV4dCgpLHRoaXMubmdVbnN1YnNjcmliZS5jb21wbGV0ZSgpfWdldEZpbHRlcmVkSXRlbXMkKGUpe3JldHVybiBMdChbZSx0aGlzLnN0b3JlLnNlbGVjdChRbSldKS5waXBlKEwoKFtpLHJdKT0+e2lmKCFyKXJldHVybiBpO2xldCBvPXRoaXMuY29sdW1ucy5pbmNsdWRlcyh2YS5FWFBFUklNRU5UX05BTUUpO3JldHVybiBpLmZpbHRlcihzPT5KSSh7cnVuTmFtZTpzLnJ1bi5uYW1lLGV4cGVyaW1lbnRBbGlhczpzLmV4cGVyaW1lbnRBbGlhc30scixvKSl9KSx1aShpPT50aGlzLnNob3dIcGFyYW1zQW5kTWV0cmljcz9MdCh0aGlzLnN0b3JlLnNlbGVjdChEZi5nZXRIcGFyYW1GaWx0ZXJNYXAsdGhpcy5leHBlcmltZW50SWRzKSx0aGlzLnN0b3JlLnNlbGVjdChEZi5nZXRNZXRyaWNGaWx0ZXJNYXAsdGhpcy5leHBlcmltZW50SWRzKSkucGlwZShMKChbcixvXSk9PmkuZmlsdGVyKCh7aHBhcmFtczpzLG1ldHJpY3M6YX0pPT5bLi4uci5lbnRyaWVzKCldLmV2ZXJ5KChbYyx1XSk9Pm1oZSh1LHMuZ2V0KGMpKSkmJlsuLi5vLmVudHJpZXMoKV0uZXZlcnkoKFtjLHVdKT0+bWhlKHUsYS5nZXQoYykpKSkpKTpYdChpKSkpfXNvcnRlZEFuZFNsaWNlZEl0ZW1zJChlKXtsZXQgaT1MdChbZSx0aGlzLnN0b3JlLnNlbGVjdChhSCldKS5waXBlKEwoKFtvLHNdKT0+ZnVuY3Rpb24obix0KXtsZXQgZT10LmtleSxpPVsuLi5uXTtyZXR1cm4gbnVsbD09PWV8fHQuZGlyZWN0aW9uPT09aWMuVU5TRVR8fGkuc29ydCgocixvKT0+e2xldCBzPWZoZShyLGUpLGE9ZmhlKG8sZSk7aWYocy5sZW5ndGghPT1hLmxlbmd0aCl0aHJvdyBuZXcgRXJyb3IoYEludmFyaWFudCBlcnJvcjogYSBnaXZlbiBzb3J0IHNob3VsZCByZXN1bHQgaW4gc2FtZSBudW1iZXIgb2YgaXRlbXM6ICR7dH1gKTtmb3IobGV0IGw9MDtsPHMubGVuZ3RoO2wrKyl7bGV0IGM9c1tsXSx1PWFbbF07aWYoYyE9PXUpe2lmKHZvaWQgMD09PWN8fHZvaWQgMD09PXUpcmV0dXJuIHZvaWQgMD09PXU/LTE6MTtpZih0eXBlb2YgYyE9dHlwZW9mIHUpdGhyb3cgbmV3IEVycm9yKGBDYW5ub3QgY29tcGFyZSB2YWx1ZXMgb2YgZGlmZmVyZW50IHR5cGVzOiAke3R5cGVvZiBjfSB2cy4gJHt0eXBlb2YgdX1gKTtyZXR1cm4gYzx1PT0odC5kaXJlY3Rpb249PT1pYy5BU0MpPy0xOjF9fXJldHVybiAwfSksaX0obyxzKSkpO3JldHVybiBMdChbaSx0aGlzLnN0b3JlLnNlbGVjdChzSCldKS5waXBlKEwoKFtvLHNdKT0+e2lmKCF0aGlzLnVzZVBhZ2luYXRpb24pcmV0dXJuIG8uc2xpY2UoKTtsZXR7cGFnZVNpemU6YSxwYWdlSW5kZXg6bH09cztyZXR1cm4gby5zbGljZShsKmEsKGwrMSkqYSl9KSx6bihbXSkpfWdldFJ1blRhYmxlSXRlbXNGb3JFeHBlcmltZW50KGUpe3JldHVybiBMdChbdGhpcy5zdG9yZS5zZWxlY3QocmQse2V4cGVyaW1lbnRJZDplfSksdGhpcy5zdG9yZS5zZWxlY3Qodkkse2V4cGVyaW1lbnRJZDplfSksdGhpcy5zdG9yZS5zZWxlY3Qob28pLHRoaXMuc3RvcmUuc2VsZWN0KG5jKSx0aGlzLnN0b3JlLnNlbGVjdChZdSldKS5waXBlKEwoKFtpLHIsbyxzLGFdKT0+aS5tYXAobD0+e2xldCBjPW5ldyBNYXA7KGwuaHBhcmFtc3x8W10pLmZvckVhY2goZD0+e2Muc2V0KGQubmFtZSxkLnZhbHVlKX0pO2xldCB1PW5ldyBNYXA7cmV0dXJuKGwubWV0cmljc3x8W10pLmZvckVhY2goZD0+e3Uuc2V0KGQudGFnLGQudmFsdWUpfSkse3J1bjpsLGV4cGVyaW1lbnROYW1lOnI/Lm5hbWV8fCIiLGV4cGVyaW1lbnRBbGlhczphW2VdLHNlbGVjdGVkOkJvb2xlYW4obyYmby5nZXQobC5pZCkpLHJ1bkNvbG9yOnNbbC5pZF0saHBhcmFtczpjLG1ldHJpY3M6dX19KSkpfW9uUnVuU2VsZWN0aW9uVG9nZ2xlKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2gob0koe3J1bklkOmUucnVuLmlkfSkpfW9uUnVuU2VsZWN0aW9uRGJsQ2xpY2soZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChzSSh7cnVuSWQ6ZS5ydW4uaWR9KSl9b25QYWdlU2VsZWN0aW9uVG9nZ2xlKGUpe2xldHtpdGVtczppfT1lLHI9aS5tYXAoKHtydW46b30pPT5vLmlkKTt0aGlzLnN0b3JlLmRpc3BhdGNoKGFJKHtydW5JZHM6cn0pKX1vblBhZ2luYXRpb25DaGFuZ2UoZSl7aWYoIXRoaXMudXNlUGFnaW5hdGlvbil0aHJvdyBuZXcgRXJyb3IoIlBhZ2luYXRpb24gZXZlbnRzIGNhbm5vdCBiZSBkaXNwYXRjaGVkIHdoZW4gcGFnaW5hdGlvbiBpcyBkaXNhYmxlZCIpO2xldHtwYWdlSW5kZXg6aSxwYWdlU2l6ZTpyfT1lO3RoaXMuc3RvcmUuZGlzcGF0Y2gobEkoe3BhZ2VJbmRleDppLHBhZ2VTaXplOnJ9KSl9b25Tb3J0Q2hhbmdlKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goY0koZSkpfW9uUmVnZXhGaWx0ZXJDaGFuZ2UoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChOTSh7cmVnZXhTdHJpbmc6ZX0pKX1vblJ1bkNvbG9yQ2hhbmdlKHtydW5JZDplLG5ld0NvbG9yOml9KXt0aGlzLnN0b3JlLmRpc3BhdGNoKHVJKHtydW5JZDplLG5ld0NvbG9yOml9KSl9b25IcGFyYW1EaXNjcmV0ZUZpbHRlckNoYW5nZWQoZSl7bGV0e2hwYXJhbU5hbWU6aSxmaWx0ZXJWYWx1ZXM6cixpbmNsdWRlVW5kZWZpbmVkOm99PWU7dGhpcy5zdG9yZS5kaXNwYXRjaChHbS5ocGFyYW1zRGlzY3JldGVIcGFyYW1GaWx0ZXJDaGFuZ2VkKHtleHBlcmltZW50SWRzOnRoaXMuZXhwZXJpbWVudElkcyxocGFyYW1OYW1lOmksZmlsdGVyVmFsdWVzOnIsaW5jbHVkZVVuZGVmaW5lZDpvfSkpfW9uSHBhcmFtSW50ZXJ2YWxGaWx0ZXJDaGFuZ2VkKGUpe2xldHtuYW1lOmksZmlsdGVyTG93ZXJWYWx1ZTpyLGZpbHRlclVwcGVyVmFsdWU6byxpbmNsdWRlVW5kZWZpbmVkOnN9PWU7dGhpcy5zdG9yZS5kaXNwYXRjaChHbS5ocGFyYW1zSW50ZXJ2YWxIcGFyYW1GaWx0ZXJDaGFuZ2VkKHtleHBlcmltZW50SWRzOnRoaXMuZXhwZXJpbWVudElkcyxocGFyYW1OYW1lOmksZmlsdGVyTG93ZXJWYWx1ZTpyLGZpbHRlclVwcGVyVmFsdWU6byxpbmNsdWRlVW5kZWZpbmVkOnN9KSl9b25NZXRyaWNGaWx0ZXJDaGFuZ2VkKGUpe2xldHtuYW1lOmksaW5jbHVkZVVuZGVmaW5lZDpyLGZpbHRlckxvd2VyVmFsdWU6byxmaWx0ZXJVcHBlclZhbHVlOnN9PWU7dGhpcy5zdG9yZS5kaXNwYXRjaChHbS5ocGFyYW1zTWV0cmljRmlsdGVyQ2hhbmdlZCh7ZXhwZXJpbWVudElkczp0aGlzLmV4cGVyaW1lbnRJZHMsbWV0cmljVGFnOmksaW5jbHVkZVVuZGVmaW5lZDpyLGZpbHRlckxvd2VyVmFsdWU6byxmaWx0ZXJVcHBlclZhbHVlOnN9KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJydW5zLXRhYmxlIl1dLGhvc3RWYXJzOjIsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiZldCgiZmxleC1sYXlvdXQiLGkudXNlRmxleGlibGVMYXlvdXQpfSxpbnB1dHM6e3VzZUZsZXhpYmxlTGF5b3V0OiJ1c2VGbGV4aWJsZUxheW91dCIsdXNlUGFnaW5hdGlvbjoidXNlUGFnaW5hdGlvbiIsY29sdW1uczoiY29sdW1ucyIsZXhwZXJpbWVudElkczoiZXhwZXJpbWVudElkcyIsc2hvd0hwYXJhbXNBbmRNZXRyaWNzOiJzaG93SHBhcmFtc0FuZE1ldHJpY3MifSxkZWNsczoxMSx2YXJzOjM1LGNvbnN0czpbWzMsImV4cGVyaW1lbnRJZHMiLCJ1c2VGbGV4aWJsZUxheW91dCIsIm51bVNlbGVjdGVkSXRlbXMiLCJjb2x1bW5zIiwiaHBhcmFtQ29sdW1ucyIsIm1ldHJpY0NvbHVtbnMiLCJzaG93RXhwZXJpbWVudE5hbWUiLCJwYWdlSXRlbXMiLCJmaWx0ZXJlZEl0ZW1zTGVuZ3RoIiwiYWxsSXRlbXNMZW5ndGgiLCJsb2FkaW5nIiwicGFnaW5hdGlvbk9wdGlvbiIsInJlZ2V4RmlsdGVyIiwic29ydE9wdGlvbiIsInVzZVBhZ2luYXRpb24iLCJvblNlbGVjdGlvblRvZ2dsZSIsIm9uU2VsZWN0aW9uRGJsQ2xpY2siLCJvblBhZ2VTZWxlY3Rpb25Ub2dnbGUiLCJvblBhZ2luYXRpb25DaGFuZ2UiLCJvblJlZ2V4RmlsdGVyQ2hhbmdlIiwib25Tb3J0Q2hhbmdlIiwib25SdW5Db2xvckNoYW5nZSIsIm9uSHBhcmFtSW50ZXJ2YWxGaWx0ZXJDaGFuZ2VkIiwib25IcGFyYW1EaXNjcmV0ZUZpbHRlckNoYW5nZWQiLCJvbk1ldHJpY0ZpbHRlckNoYW5nZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsInJ1bnMtdGFibGUtY29tcG9uZW50IiwwKSxQKCJvblNlbGVjdGlvblRvZ2dsZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25SdW5TZWxlY3Rpb25Ub2dnbGUobyl9KSgib25TZWxlY3Rpb25EYmxDbGljayIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25SdW5TZWxlY3Rpb25EYmxDbGljayhvKX0pKCJvblBhZ2VTZWxlY3Rpb25Ub2dnbGUiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uUGFnZVNlbGVjdGlvblRvZ2dsZShvKX0pKCJvblBhZ2luYXRpb25DaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uUGFnaW5hdGlvbkNoYW5nZShvKX0pKCJvblJlZ2V4RmlsdGVyQ2hhbmdlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblJlZ2V4RmlsdGVyQ2hhbmdlKG8pfSkoIm9uU29ydENoYW5nZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25Tb3J0Q2hhbmdlKG8pfSkoIm9uUnVuQ29sb3JDaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uUnVuQ29sb3JDaGFuZ2Uobyl9KSgib25IcGFyYW1JbnRlcnZhbEZpbHRlckNoYW5nZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uSHBhcmFtSW50ZXJ2YWxGaWx0ZXJDaGFuZ2VkKG8pfSkoIm9uSHBhcmFtRGlzY3JldGVGaWx0ZXJDaGFuZ2VkIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vbkhwYXJhbURpc2NyZXRlRmlsdGVyQ2hhbmdlZChvKX0pKCJvbk1ldHJpY0ZpbHRlckNoYW5nZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uTWV0cmljRmlsdGVyQ2hhbmdlZChvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLEIoNCwiYXN5bmMiKSxCKDUsImFzeW5jIiksQig2LCJhc3luYyIpLEIoNywiYXN5bmMiKSxCKDgsImFzeW5jIiksQig5LCJhc3luYyIpLEIoMTAsImFzeW5jIiksdigpKSwyJmUmJnkoImV4cGVyaW1lbnRJZHMiLGkuZXhwZXJpbWVudElkcykoInVzZUZsZXhpYmxlTGF5b3V0IixpLnVzZUZsZXhpYmxlTGF5b3V0KSgibnVtU2VsZWN0ZWRJdGVtcyIsVSgxLDE1LGkubnVtU2VsZWN0ZWRJdGVtcyQpKSgiY29sdW1ucyIsaS5jb2x1bW5zKSgiaHBhcmFtQ29sdW1ucyIsVSgyLDE3LGkuaHBhcmFtQ29sdW1ucyQpKSgibWV0cmljQ29sdW1ucyIsVSgzLDE5LGkubWV0cmljQ29sdW1ucyQpKSgic2hvd0V4cGVyaW1lbnROYW1lIixpLmlzRXhwZXJpbWVudE5hbWVWaXNpYmxlKCkpKCJwYWdlSXRlbXMiLFUoNCwyMSxpLnBhZ2VJdGVtcyQpKSgiZmlsdGVyZWRJdGVtc0xlbmd0aCIsVSg1LDIzLGkuZmlsdGVyZWRJdGVtc0xlbmd0aCQpKSgiYWxsSXRlbXNMZW5ndGgiLFUoNiwyNSxpLmFsbEl0ZW1zTGVuZ3RoJCkpKCJsb2FkaW5nIixVKDcsMjcsaS5sb2FkaW5nJCkpKCJwYWdpbmF0aW9uT3B0aW9uIixVKDgsMjksaS5wYWdpbmF0aW9uT3B0aW9uJCkpKCJyZWdleEZpbHRlciIsVSg5LDMxLGkucmVnZXhGaWx0ZXIkKSkoInNvcnRPcHRpb24iLFUoMTAsMzMsaS5zb3J0T3B0aW9uJCkpKCJ1c2VQYWdpbmF0aW9uIixpLnVzZVBhZ2luYXRpb24pfSxkZXBlbmRlbmNpZXM6W2hoZSxHZV0sc3R5bGVzOlsiLmZsZXgtbGF5b3V0W19uZ2hvc3QtJUNPTVAlXSB7XG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICB9XG5cbiAgICAgIC5mbGV4LWxheW91dFtfbmdob3N0LSVDT01QJV0gICAgPiBydW5zLXRhYmxlLWNvbXBvbmVudFtfbmdjb250ZW50LSVDT01QJV0ge1xuICAgICAgICB3aWR0aDogMTAwJTtcbiAgICAgIH0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLF9oZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJydW5zLXNlbGVjdG9yLWNvbXBvbmVudCJdXSxpbnB1dHM6e2V4cGVyaW1lbnRJZHM6ImV4cGVyaW1lbnRJZHMiLHNob3dIcGFyYW1zQW5kTWV0cmljczoic2hvd0hwYXJhbXNBbmRNZXRyaWNzIixjb2x1bW5zOiJjb2x1bW5zIn0sZGVjbHM6MSx2YXJzOjQsY29uc3RzOltbMywidXNlRmxleGlibGVMYXlvdXQiLCJjb2x1bW5zIiwiZXhwZXJpbWVudElkcyIsInNob3dIcGFyYW1zQW5kTWV0cmljcyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmTygwLCJydW5zLXRhYmxlIiwwKSwyJmUmJnkoInVzZUZsZXhpYmxlTGF5b3V0IiwhMCkoImNvbHVtbnMiLGkuY29sdW1ucykoImV4cGVyaW1lbnRJZHMiLGkuZXhwZXJpbWVudElkcykoInNob3dIcGFyYW1zQW5kTWV0cmljcyIsaS5zaG93SHBhcmFtc0FuZE1ldHJpY3MpfSxkZXBlbmRlbmNpZXM6W2doZV0sc3R5bGVzOlsicnVucy10YWJsZVtfbmdjb250ZW50LSVDT01QJV0ge1xuICAgICAgICBoZWlnaHQ6IDEwMCU7XG4gICAgICB9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxPYj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLmV4cGVyaW1lbnRJZHMkPXRoaXMuc3RvcmUuc2VsZWN0KFdvKS5waXBlKEwoaT0+aT8/W10pKSx0aGlzLmNvbHVtbnMkPXRoaXMuc3RvcmUuc2VsZWN0KFdvKS5waXBlKEwoaT0+W3ZhLkNIRUNLQk9YLHZhLlJVTl9OQU1FLGkmJmkubGVuZ3RoPjE/dmEuRVhQRVJJTUVOVF9OQU1FOm51bGwsdmEuUlVOX0NPTE9SXS5maWx0ZXIocj0+bnVsbCE9PXIpKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJydW5zLXNlbGVjdG9yIl1dLGlucHV0czp7c2hvd0hwYXJhbXNBbmRNZXRyaWNzOiJzaG93SHBhcmFtc0FuZE1ldHJpY3MifSxkZWNsczozLHZhcnM6Nyxjb25zdHM6W1szLCJleHBlcmltZW50SWRzIiwiY29sdW1ucyIsInNob3dIcGFyYW1zQW5kTWV0cmljcyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKE8oMCwicnVucy1zZWxlY3Rvci1jb21wb25lbnQiLDApLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIikpLDImZSYmeSgiZXhwZXJpbWVudElkcyIsVSgxLDMsaS5leHBlcmltZW50SWRzJCkpKCJjb2x1bW5zIixVKDIsNSxpLmNvbHVtbnMkKSkoInNob3dIcGFyYW1zQW5kTWV0cmljcyIsaS5zaG93SHBhcmFtc0FuZE1ldHJpY3MpfSxkZXBlbmRlbmNpZXM6W19oZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksdmhlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1ldHJpY3MtZGFzaGJvYXJkIl1dLGRlY2xzOjMsdmFyczowLGNvbnN0czpbWyJzaWRlYmFyIiwiIl0sWyJtYWluIiwiIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJ0Yi1kYXNoYm9hcmQtbGF5b3V0IiksTygxLCJydW5zLXNlbGVjdG9yIiwwKSgyLCJtZXRyaWNzLW1haW4tdmlldyIsMSksdigpKX0sZGVwZW5kZW5jaWVzOltSb2UsenBlLE9iXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2NvbnRhaW46c3RyaWN0O2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47aGVpZ2h0OjEwMCU7anVzdGlmeS1jb250ZW50OnN0cmV0Y2g7b3ZlcmZsb3c6aGlkZGVufS5ub3RpY2VbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjQ1LDE1NywuODUpO2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNmZmViM2I7Y29sb3I6IzIxMjEyMTtkaXNwbGF5OmJsb2NrO2ZsZXg6MCAwfXRiLWRhc2hib2FyZC1sYXlvdXRbX25nY29udGVudC0lQ09NUCVde2ZsZXg6MSAxO292ZXJmbG93OmhpZGRlbn1uYXZbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6I2ZmZjtib3JkZXItcmlnaHQ6MXB4IHNvbGlkICNlYmViZWI7ZmxleDpub25lO3dpZHRoOjM0MHB4fWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIG5hdltfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIG5hdltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojMzAzMDMwO2JvcmRlci1yaWdodC1jb2xvcjojNTU1fW1ldHJpY3MtbWFpbi12aWV3W19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4OjEgMX0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLFhHPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxwbixQbl19KSxufSkoKSx5aGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lXX0pLG59KSgpLGJoZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksa2I9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLGJoZV19KSxufSkoKSx0Rj0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsSWIscG5dfSksbn0pKCksbkY9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLFdoXX0pLG59KSgpLHhoZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbb2hlLE1lLGtiLHRGLGFnLFBuLExzLE9oLEhhLHBuLHpoLFhwZSxfZCxacGUsbmhlLG5GLEUyXX0pLG59KSgpLEZiPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSx4aGVdfSksbn0pKCksTmI9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLGlGPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxwbl19KSxufSkoKSxDaGU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMub25UaW1lU2VsZWN0aW9uQ2hhbmdlZD1uZXcgRyx0aGlzLm9uVGltZVNlbGVjdGlvblRvZ2dsZWQ9bmV3IEcsdGhpcy5heGlzRGlyZWN0aW9uPXBhLlZFUlRJQ0FMLHRoaXMuY2FyZEZvYkhlbHBlcj17Z2V0U3RlcEhpZ2hlclRoYW5BeGlzUG9zaXRpb246dGhpcy5nZXRTdGVwSGlnaGVyVGhhbkF4aXNQb3NpdGlvbi5iaW5kKHRoaXMpLGdldFN0ZXBMb3dlclRoYW5BeGlzUG9zaXRpb246dGhpcy5nZXRTdGVwTG93ZXJUaGFuQXhpc1Bvc2l0aW9uLmJpbmQodGhpcyl9fWdldEF4aXNQb3NpdGlvbkZyb21TdGFydFN0ZXAoKXtyZXR1cm4gdGhpcy50ZW1wb3JhbFNjYWxlKHRoaXMudGltZVNlbGVjdGlvbi5zdGFydC5zdGVwKX1nZXRBeGlzUG9zaXRpb25Gcm9tRW5kU3RlcCgpe3JldHVybiBudWxsPT09dGhpcy50aW1lU2VsZWN0aW9uLmVuZD9udWxsOnRoaXMudGVtcG9yYWxTY2FsZSh0aGlzLnRpbWVTZWxlY3Rpb24uZW5kLnN0ZXApfWdldEhpZ2hlc3RTdGVwKCl7cmV0dXJuIHRoaXMuc3RlcHNbdGhpcy5zdGVwcy5sZW5ndGgtMV19Z2V0TG93ZXN0U3RlcCgpe3JldHVybiB0aGlzLnN0ZXBzWzBdfWdldFN0ZXBIaWdoZXJUaGFuQXhpc1Bvc2l0aW9uKGUpe2xldCBpPTA7Zm9yKDtlPnRoaXMudGVtcG9yYWxTY2FsZSh0aGlzLnN0ZXBzW2ldKSYmaTx0aGlzLnN0ZXBzLmxlbmd0aC0xOylpKys7cmV0dXJuIHRoaXMuc3RlcHNbaV19Z2V0U3RlcExvd2VyVGhhbkF4aXNQb3NpdGlvbihlKXtsZXQgaT10aGlzLnN0ZXBzLmxlbmd0aC0xO2Zvcig7ZTx0aGlzLnRlbXBvcmFsU2NhbGUodGhpcy5zdGVwc1tpXSkmJmk+MDspaS0tO3JldHVybiB0aGlzLnN0ZXBzW2ldfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJoaXN0b2dyYW0tY2FyZC1mb2ItY29udHJvbGxlciJdXSxpbnB1dHM6e3N0ZXBzOiJzdGVwcyIsdGltZVNlbGVjdGlvbjoidGltZVNlbGVjdGlvbiIsdGVtcG9yYWxTY2FsZToidGVtcG9yYWxTY2FsZSJ9LG91dHB1dHM6e29uVGltZVNlbGVjdGlvbkNoYW5nZWQ6Im9uVGltZVNlbGVjdGlvbkNoYW5nZWQiLG9uVGltZVNlbGVjdGlvblRvZ2dsZWQ6Im9uVGltZVNlbGVjdGlvblRvZ2dsZWQifSxkZWNsczoxLHZhcnM6Nyxjb25zdHM6W1szLCJheGlzRGlyZWN0aW9uIiwidGltZVNlbGVjdGlvbiIsInN0YXJ0U3RlcEF4aXNQb3NpdGlvbiIsImVuZFN0ZXBBeGlzUG9zaXRpb24iLCJoaWdoZXN0U3RlcCIsImxvd2VzdFN0ZXAiLCJjYXJkRm9iSGVscGVyIiwib25UaW1lU2VsZWN0aW9uQ2hhbmdlZCIsIm9uVGltZVNlbGVjdGlvblRvZ2dsZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImNhcmQtZm9iLWNvbnRyb2xsZXIiLDApLFAoIm9uVGltZVNlbGVjdGlvbkNoYW5nZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uVGltZVNlbGVjdGlvbkNoYW5nZWQuZW1pdChvKX0pKCJvblRpbWVTZWxlY3Rpb25Ub2dnbGVkIixmdW5jdGlvbigpe3JldHVybiBpLm9uVGltZVNlbGVjdGlvblRvZ2dsZWQuZW1pdCgpfSksdigpKSwyJmUmJnkoImF4aXNEaXJlY3Rpb24iLGkuYXhpc0RpcmVjdGlvbikoInRpbWVTZWxlY3Rpb24iLGkudGltZVNlbGVjdGlvbikoInN0YXJ0U3RlcEF4aXNQb3NpdGlvbiIsaS5nZXRBeGlzUG9zaXRpb25Gcm9tU3RhcnRTdGVwKCkpKCJlbmRTdGVwQXhpc1Bvc2l0aW9uIixpLmdldEF4aXNQb3NpdGlvbkZyb21FbmRTdGVwKCkpKCJoaWdoZXN0U3RlcCIsaS5nZXRIaWdoZXN0U3RlcCgpKSgibG93ZXN0U3RlcCIsaS5nZXRMb3dlc3RTdGVwKCkpKCJjYXJkRm9iSGVscGVyIixpLmNhcmRGb2JIZWxwZXIpfSxkZXBlbmRlbmNpZXM6W0drXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxNaGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W2lGLE1lLHFoLE5iXX0pLG59KSgpO054KFdrLFtkbixCZSxoZyxheSxDaGVdLFtdKTt2YXIgTGI9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lXX0pLG59KSgpLHJGPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxrYl19KSxufSkoKSxCYj0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUscG5dfSksbn0pKCksd2hlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxNaGUsUG4scG4sX2QsckYsTGIsQmJdfSksbn0pKCksU2hlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxQbixwbixfZCxXaCxyRixMYixCYl19KSxufSkoKSxvRj0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUscG5dfSksbn0pKCksRWhlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxwbixvRl19KSxufSkoKSxUaGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLHNzLFBuLHBuLEhhLHpoXX0pLG59KSgpLERoZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsc3MsVGhlLHFoXX0pLG59KSgpLEFoZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsanIsUG4sT2gsSGEsbGMsU0ldfSksbn0pKCksSWhlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltpRixNZSxBaGUsRWhlLGtiLE5iLERoZSxQbixwbix6aCxfZCxxaCxMYixCYl19KSxufSkoKSxQaGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLFNoZSxJaGUsd2hlLE5iXX0pLG59KSgpLFJoZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsbGNdfSksbn0pKCksc0Y9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLFJoZSxQbixnUixMcyxwbixsYyxXaCxjdixuRl19KSxufSkoKSxPaGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLG9GLExzLEoyXX0pLG59KSgpLGtoZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbUGhlLE1lLHRGLEliLFBuLGdSLHBuLEhhLF9kLHNGLE9oZSxaY119KSxufSkoKSxGaGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLHloZSxYRyxraGUscG4sc0YsRmJdfSksbn0pKCk7ZnVuY3Rpb24gRUplKCl7cmV0dXJuW3thY3Rpb25DcmVhdG9yOnJ5LGFsZXJ0RnJvbUFjdGlvbjpuPT57bGV0e3dhc1Bpbm5lZDp0LGNhbkNyZWF0ZU5ld1BpbnM6ZX09bjtyZXR1cm4gdHx8ZT9udWxsOntsb2NhbGl6ZWRNZXNzYWdlOiJNYXggcGluIGxpbWl0IGV4Y2VlZGVkLiBSZW1vdmUgZXhpc3RpbmcgcGlucyBiZWZvcmUgYWRkaW5nIG1vcmUuIFNlZSBodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9pc3N1ZXMvNDI0MiJ9fX1dfWZ1bmN0aW9uIFRKZSgpe3JldHVybiBKKG9wLG49Pih7c2NhbGFyU21vb3RoaW5nOm59KSl9ZnVuY3Rpb24gREplKCl7cmV0dXJuIEooaHYsbj0+KHtpZ25vcmVPdXRsaWVyczpufSkpfWZ1bmN0aW9uIEFKZSgpe3JldHVybiBKKHB2LG49Pih7dG9vbHRpcFNvcnQ6U3RyaW5nKG4pfSkpfWZ1bmN0aW9uIElKZSgpe3JldHVybiBKKEhJLG49Pih7dGltZVNlcmllc1NldHRpbmdzUGFuZU9wZW5lZDpufSkpfWZ1bmN0aW9uIFBKZSgpe3JldHVybiBKKGR2LG49Pih7dGltZVNlcmllc0NhcmRNaW5XaWR0aDpufSkpfWZ1bmN0aW9uIFJKZSgpe3JldHVybiBKKGZ2LG49Pih7c3RlcFNlbGVjdG9yRW5hYmxlZDpufSkpfWZ1bmN0aW9uIE9KZSgpe3JldHVybiBKKG12LG49Pih7cmFuZ2VTZWxlY3Rpb25FbmFibGVkOm59KSl9ZnVuY3Rpb24ga0plKCl7cmV0dXJuIEooWW0sbj0+KHtsaW5rZWRUaW1lRW5hYmxlZDpufSkpfXZhciBOaGU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbe3Byb3ZpZGU6TVUsdXNlRmFjdG9yeTpBb2UsZGVwczpbd1VdfSx7cHJvdmlkZTp3VSx1c2VWYWx1ZTpJSX1dLGltcG9ydHM6W01lLEpfLGVjLEJzLmZvclBsdWdpbihCTSx2aGUpLFNJLEZoZSx3ci5mb3JGZWF0dXJlKEFJLEVvZSxNVSkscm8uZm9yRmVhdHVyZShbRG9lXSksSnUucmVnaXN0ZXJBbGVydEFjdGlvbnMoRUplKSxTci5kZWZpbmVHbG9iYWxTZXR0aW5nKFRKZSksU3IuZGVmaW5lR2xvYmFsU2V0dGluZyhESmUpLFNyLmRlZmluZUdsb2JhbFNldHRpbmcoQUplKSxTci5kZWZpbmVHbG9iYWxTZXR0aW5nKElKZSksU3IuZGVmaW5lR2xvYmFsU2V0dGluZyhQSmUpLFNyLmRlZmluZUdsb2JhbFNldHRpbmcoUkplKSxTci5kZWZpbmVHbG9iYWxTZXR0aW5nKE9KZSksU3IuZGVmaW5lR2xvYmFsU2V0dGluZyhrSmUpXX0pLG59KSgpO2Z1bmN0aW9uIExoZShuKXtyZXR1cm4gbi5zdGFydHNXaXRoKCJjb3VudEAiKX1mdW5jdGlvbiBhRihuKXtyZXR1cm4gbi5zdGFydHNXaXRoKCJuUE1JQCIpfHxuLnN0YXJ0c1dpdGgoIm5QTUlfZGlmZkAiKX1mdW5jdGlvbiBWYihuKXtyZXR1cm4gbi5zdGFydHNXaXRoKCJuUE1JQCIpfWZ1bmN0aW9uIFNzKG4pe3JldHVybiBuLnNwbGl0KCJAIiwyKVsxXX1mdW5jdGlvbiBxJGUobil7bGV0IGUsaSx0PW4ubGVuZ3RoO2Zvcig7dDspaT1NYXRoLmZsb29yKE1hdGgucmFuZG9tKCkqdC0tKSxlPW5bdF0sblt0XT1uW2ldLG5baV09ZTtyZXR1cm4gbn1mdW5jdGlvbiBZJGUobil7cmV0dXJuWy4uLm5ldyBBcnJheShuKV0ubWFwKCh0LGUpPT5lKX1mdW5jdGlvbiB2RihuLHQpe3JldHVybmAke3R9LyR7bn1gfW9OKCRmZSgpLDEpO3ZhciB5Rj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuaHR0cD1lLHRoaXMuaHR0cFBhdGhQcmVmaXg9ImRhdGEvcGx1Z2luL25wbWkifWZldGNoRGF0YShlKXtyZXR1cm4gbHIodGhpcy5mZXRjaEFubm90YXRpb25zKGUpLHRoaXMuZmV0Y2hNZXRyaWNzKGUpLHRoaXMuZmV0Y2hWYWx1ZXMoZSksdGhpcy5mZXRjaEVtYmVkZGluZ3MoZSkpLnBpcGUoTCgoW2kscixvLHNdKT0+e2xldCBjLGE9e30sbD17fSx1PTA7Zm9yKGxldCBkIG9mIE9iamVjdC5rZXlzKGkpKWZvcihsZXQgcCBpbiBpW2RdKXtsZXQgaD1pW2RdW3BdO09iamVjdC5rZXlzKHMpLmxlbmd0aCYmIWxbaF0mJnNbZF1bcF0mJnNbZF1bcF0uc29tZSh4PT4wIT09eCkmJihsW2hdPXt2ZWN0b3I6c1tkXVtwXSxpbmRleDp1LG5hbWU6aH0sdSs9MSk7bGV0IGY9bmV3IE1hcDtmb3IobGV0IHggaW4gcltkXSl7bGV0IGc9cltkXVt4XSxiPVNzKGcpLEQ9Zi5nZXQoYik7RHx8KEQ9e25QTUlWYWx1ZTpudWxsLGNvdW50VmFsdWU6bnVsbCxhbm5vdGF0aW9uOmgsbWV0cmljOmIscnVuOmR9LGYuc2V0KGIsRCkpLExoZShnKT9ELmNvdW50VmFsdWU9b1tkXVtwXVt4XTphRihnKSYmKEQublBNSVZhbHVlPW9bZF1bcF1beF0pfWFbaF09Wy4uLmFbaF0/YVtoXTpbXSwuLi5mLnZhbHVlcygpXX1yZXR1cm4gT2JqZWN0LmtleXMobCkubGVuZ3RoJiYoYz1mdW5jdGlvbihuKXtsZXQgdD1PYmplY3Qua2V5cyhuKTtyZXR1cm57cG9pbnRzOm4scG9pbnRLZXlzOnQsc2h1ZmZsZWREYXRhSW5kaWNlczpxJGUoWSRlKHQubGVuZ3RoKSksaGFzVW1hcFJ1bjohMX19KGwpKSx7YW5ub3RhdGlvbkRhdGE6YSxtZXRyaWNzOnIsZW1iZWRkaW5nRGF0YVNldDpjfX0pLGZvKGk9PmkgaW5zdGFuY2VvZiBucCYmNDAwPD1pLnN0YXR1cyYmaS5zdGF0dXM8NTAwP1h0KHthbm5vdGF0aW9uRGF0YTp7fSxtZXRyaWNzOnt9LGVtYmVkZGluZ0RhdGFTZXQ6dm9pZCAwfSk6d2MoaSkpKX1mZXRjaEFubm90YXRpb25zKGUpe2xldCBpPWUubWFwKHI9PnRoaXMuaHR0cC5nZXQoYC9leHBlcmltZW50LyR7cn0vJHt0aGlzLmh0dHBQYXRoUHJlZml4fS9hbm5vdGF0aW9uc2ApLnBpcGUoTChzPT5mdW5jdGlvbihuLHQpe3JldHVybiBPYmplY3QuZnJvbUVudHJpZXMoT2JqZWN0LmVudHJpZXMobikubWFwKChbZSxpXSk9Plt2RihlLHQpLGldKSl9KHMscikpKSk7cmV0dXJuIGxyKGkpLnBpcGUoTChyPT57bGV0IG89e307Zm9yKGxldCBzIG9mIHIpbz17Li4ubywuLi5zfTtyZXR1cm4gb30pKX1mZXRjaE1ldHJpY3MoZSl7bGV0IGk9ZS5tYXAocj0+dGhpcy5odHRwLmdldChgL2V4cGVyaW1lbnQvJHtyfS8ke3RoaXMuaHR0cFBhdGhQcmVmaXh9L21ldHJpY3NgKS5waXBlKEwocz0+ZnVuY3Rpb24obix0KXtyZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKE9iamVjdC5lbnRyaWVzKG4pLm1hcCgoW2UsaV0pPT5bdkYoZSx0KSxpXSkpfShzLHIpKSkpO3JldHVybiBscihpKS5waXBlKEwocj0+e2xldCBvPXt9O2ZvcihsZXQgcyBvZiByKW89ey4uLm8sLi4uc307cmV0dXJuIG99KSl9ZmV0Y2hWYWx1ZXMoZSl7bGV0IGk9ZS5tYXAocj0+dGhpcy5odHRwLmdldChgL2V4cGVyaW1lbnQvJHtyfS8ke3RoaXMuaHR0cFBhdGhQcmVmaXh9L3ZhbHVlc2ApLnBpcGUoTChzPT5mdW5jdGlvbihuLHQpe3JldHVybiBPYmplY3QuZnJvbUVudHJpZXMoT2JqZWN0LmVudHJpZXMobikubWFwKChbZSxpXSk9Plt2RihlLHQpLGldKSl9KHMscikpKSk7cmV0dXJuIGxyKGkpLnBpcGUoTChyPT57bGV0IG89e307Zm9yKGxldCBzIG9mIHIpbz17Li4ubywuLi5zfTtyZXR1cm4gb30pKX1mZXRjaEVtYmVkZGluZ3MoZSl7bGV0IGk9ZS5tYXAocj0+dGhpcy5odHRwLmdldChgL2V4cGVyaW1lbnQvJHtyfS8ke3RoaXMuaHR0cFBhdGhQcmVmaXh9L2VtYmVkZGluZ3NgKS5waXBlKEwocz0+ZnVuY3Rpb24obix0KXtyZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKE9iamVjdC5lbnRyaWVzKG4pLm1hcCgoW2UsaV0pPT5bdkYoZSx0KSxpXSkpfShzLHIpKSkpO3JldHVybiBscihpKS5waXBlKEwocj0+e2xldCBvPXt9O2ZvcihsZXQgcyBvZiByKW89ey4uLm8sLi4uc307cmV0dXJuIG99KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooa2EpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSx0bWU9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbeUZdLGltcG9ydHM6W0t1XX0pLG59KSgpLGJGPWJlKCJbTlBNSV0gblBNSSBMb2FkZWQiKSx4Rj1iZSgiW05QTUldIG5QTUkgUGx1Z2luIERhdGEgUmVxdWVzdGVkIiksQ0Y9YmUoIltOUE1JXSBuUE1JIFBsdWdpbiBEYXRhIExvYWRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLE1GPWJlKCJbTlBNSV0gblBNSSBQbHVnaW4gRGF0YSBSZXF1ZXN0IEZhaWxlZCIpLHdGPWJlKCJbTlBNSV0gQWRkaW5nL1JlbW92aW5nIEFubm90YXRpb25zIHRvL2Zyb20gU2VsZWN0ZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxSRT1iZSgiW05QTUldIEFubm90YXRpb25zIFNldCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFNGPWJlKCJbTlBNSV0gQ2xlYXJpbmcgdGhlIEFubm90YXRpb24gU2VsZWN0aW9uIiksRUY9YmUoIltOUE1JXSBBZGRpbmcvUmVtb3ZpbmcgQW5ub3RhdGlvbnMgdG8vZnJvbSBGbGFnZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksVEY9YmUoIltOUE1JXSBBZGRpbmcvUmVtb3ZpbmcgQW5ub3RhdGlvbnMgdG8vZnJvbSBIaWRkZW4iLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxqYj1iZSgiW05QTUldIEFubm90YXRpb25zIFJlZ2V4IENoYW5nZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxPRT1iZSgiW05QTUldIE1ldHJpY3MgUmVnZXggQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLERGPWJlKCJbTlBNSV0gTWV0cmljIEZpbHRlciBBZGRlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLEdiPWJlKCJbTlBNSV0gTWV0cmljIEZpbHRlciBSZW1vdmVkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksV2I9YmUoIltOUE1JXSBNZXRyaWMgRmlsdGVyIENoYW5nZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxBRj1iZSgiW05QTUldIEFubm90YXRpb24gU29ydCBDaGFuZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksSUY9YmUoIltOUE1JXSBTaW1pbGFyaXR5IFNvcnQgQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFBGPWJlKCJbTlBNSV0gVG9nZ2xlIFBDIEV4cGFuZGVkIiksUkY9YmUoIltOUE1JXSBUb2dnbGUgQW5ub3RhdGlvbnMgRXhwYW5kZWQiKSxxYj1iZSgiW05QTUldIFRvZ2dsZSBTaWRlYmFyIEV4cGFuZGVkIiksT0Y9YmUoIltOUE1JXSBTaG93IENvdW50cyBUb2dnbGVkIiksa0Y9YmUoIltOUE1JXSBTaG93IEhpZGRlbiBBbm5vdGF0aW9ucyBUb2dnbGVkIiksRkY9YmUoIltOUE1JXSBFbWJlZGRpbmdzIFZpZXcgVG9nZ2xlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLE5GPWJlKCJbTlBNSV0gU2lkZWJhciBXaWR0aCBDaGFuZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksTEY9YmUoIltOUE1JXSBFbWJlZGRpbmdzIFNpZGViYXIgV2lkdGggQ2hhbmdlZCIse19hczoicHJvcHMiLF9wOnZvaWQgMH0pLFliPWJlKCJbTlBNSV0gRW1iZWRkaW5ncyBTaWRlYmFyIEV4cGFuZGVkIFRvZ2dsZWQiKSxubWU9YmUoIltOUE1JXSBDaGFuZ2UgRW1iZWRkaW5nIERhdGFTZXQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxzMD0oKCk9PihmdW5jdGlvbihuKXtuW24uQU5EPTBdPSJBTkQifShzMHx8KHMwPXt9KSksczApKSgpLG11PSgoKT0+KGZ1bmN0aW9uKG4pe25bbi5NRVRSSUM9MF09Ik1FVFJJQyIsbltuLk9QRVJBVE9SPTFdPSJPUEVSQVRPUiJ9KG11fHwobXU9e30pKSxtdSkpKCksa3A9KCgpPT4oZnVuY3Rpb24obil7bltuLkRFRkFVTFQ9MF09IkRFRkFVTFQiLG5bbi5FTUJFRERJTkdTPTFdPSJFTUJFRERJTkdTIn0oa3B8fChrcD17fSkpLGtwKSkoKSwkcj0oKCk9PihmdW5jdGlvbihuKXtuW24uREVTQ0VORElORz0wXT0iREVTQ0VORElORyIsbltuLkFTQ0VORE5HPTFdPSJBU0NFTkRORyIsbltuLlNJTUlMQVI9Ml09IlNJTUlMQVIiLG5bbi5ESVNTSU1JTEFSPTNdPSJESVNTSU1JTEFSIn0oJHJ8fCgkcj17fSkpLCRyKSkoKSx1bz1NcigibnBtaSIpLGltZT1KKHVvLG49Pm4ucGx1Z2luRGF0YUxvYWRlZC5zdGF0ZSksUGY9Sih1byxuPT5uLmFubm90YXRpb25EYXRhKSxSZj1KKHVvLG49Pm4ucnVuVG9NZXRyaWNzKSxCRj1KKHVvLG49Pm4uZW1iZWRkaW5nRGF0YVNldCksdmM9Sih1byxuPT5uLnNlbGVjdGVkQW5ub3RhdGlvbnMpLFZGPUoodW8sbj0+bi5mbGFnZ2VkQW5ub3RhdGlvbnMpLFhiPUoodW8sbj0+bi5oaWRkZW5Bbm5vdGF0aW9ucyksUWI9Sih1byxuPT5uLmFubm90YXRpb25zUmVnZXgpLHJtZT1KKHVvLG49Pm4ubWV0cmljc1JlZ2V4KSxIRj1KKHVvLG49Pm4ubWV0cmljQXJpdGhtZXRpYyksSWw9Sih1byxuPT5uLm1ldHJpY0ZpbHRlcnMpLEtiPUoodW8sbj0+bi5zb3J0KSxvbWU9Sih1byxuPT5uLnBjRXhwYW5kZWQpLFVGPUoodW8sbj0+bi5hbm5vdGF0aW9uc0V4cGFuZGVkKSx6Rj1KKHVvLG49Pm4uc2lkZWJhckV4cGFuZGVkKSxqRj1KKHVvLG49Pm4uc2hvd0NvdW50cyksWmI9Sih1byxuPT5uLnNob3dIaWRkZW5Bbm5vdGF0aW9ucyksc21lPUoodW8sbj0+bi52aWV3QWN0aXZlKSxPZj1KKHVvLG49Pm4uc2lkZWJhcldpZHRoKSxhbWU9Sih1byxuPT5uLmVtYmVkZGluZ3NNZXRyaWMpLGxtZT1KKHVvLG49Pm4uZW1iZWRkaW5nc1NpZGViYXJXaWR0aCksR0Y9Sih1byxuPT5uLmVtYmVkZGluZ3NTaWRlYmFyRXhwYW5kZWQpLGNtZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSxyKXt0aGlzLmFjdGlvbnMkPWUsdGhpcy5zdG9yZT1pLHRoaXMuZGF0YVNvdXJjZT1yLHRoaXMubG9hZERhdGEkPWNyKCgpPT5KdCh0aGlzLmxvYWRQbHVnaW5EYXRhKCkpLnBpcGUoTCgoKT0+KHt9KSkpLHtkaXNwYXRjaDohMX0pfWxvYWRQbHVnaW5EYXRhKCl7cmV0dXJuIHRoaXMuYWN0aW9ucyQucGlwZShpaShiRiksV3QodGhpcy5zdG9yZS5zZWxlY3QoaW1lKSx0aGlzLnN0b3JlLnNlbGVjdChXbykpLFllKChbLGUsaV0pPT5lIT09T2UuTE9BRElORyYmbnVsbCE9PWkpLGt0KCgpPT50aGlzLnN0b3JlLmRpc3BhdGNoKHhGKCkpKSx4bigoWywsZV0pPT50aGlzLmRhdGFTb3VyY2UuZmV0Y2hEYXRhKGUpLnBpcGUoa3QoaT0+e3RoaXMuc3RvcmUuZGlzcGF0Y2goQ0YoaSkpfSksTCgoKT0+e30pLGZvKCgpPT4odGhpcy5zdG9yZS5kaXNwYXRjaChNRigpKSxlbykpKSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShqKFBvKSxqKENlKSxqKHlGKSl9LG4uXHUwMjc1cHJvdj15ZSh7dG9rZW46bixmYWN0b3J5Om4uXHUwMjc1ZmFjfSksbn0pKCksdW1lPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktaW5hY3RpdmUtdmlldyJdXSxkZWNsczo2LHZhcnM6MCxjb25zdHM6W1sxLCJjb250YWluZXIiXSxbMSwidGl0bGUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIpKDEsImRpdiIsMCkoMiwiZGl2IiwxKSxBKDMsIm5QTUkgaXMgaW5hY3RpdmUgYmVjYXVzZSBubyBkYXRhIGlzIGF2YWlsYWJsZS4iKSx2KCksXyg0LCJkaXYiKSxBKDUsIiBUbyB1c2UgdGhlIG5QTUksIGNhbGN1bGF0ZSBuUE1JIHZhbHVlcywgYW5kIGxvZyB0aGVtIHVzaW5nIHRoZSBzdW1tYXJ5IHdyaXRlci4gIiksdigpKCkoKSl9LHN0eWxlczpbIi5jb250YWluZXJbX25nY29udGVudC0lQ09NUCVdIHtcbiAgaGVpZ2h0OiAxMDAlO1xuICBmb250LWZhbWlseTogUm9ib3RvO1xuICBmb250LXNpemU6IDE1cHg7XG4gIHBhZGRpbmc6IDUwcHg7XG59XG5cbi50aXRsZVtfbmdjb250ZW50LSVDT01QJV0ge1xuICBmb250LXNpemU6IDEzNSU7XG4gIGZvbnQtd2VpZ2h0OiBib2xkO1xuICBtYXJnaW4tYm90dG9tOiAyNXB4O1xufSJdfSksbn0pKCksdGV0PXZyKHtwbHVnaW5EYXRhTG9hZGVkOntzdGF0ZTpPZS5OT1RfTE9BREVELGxhc3RMb2FkZWRUaW1lSW5NczpudWxsfSxhbm5vdGF0aW9uRGF0YTp7fSxlbWJlZGRpbmdEYXRhU2V0OnZvaWQgMCxydW5Ub01ldHJpY3M6e30sc2VsZWN0ZWRBbm5vdGF0aW9uczpbXSxmbGFnZ2VkQW5ub3RhdGlvbnM6W10saGlkZGVuQW5ub3RhdGlvbnM6W10sYW5ub3RhdGlvbnNSZWdleDoiIixtZXRyaWNzUmVnZXg6IiIsbWV0cmljQXJpdGhtZXRpYzpbXSxtZXRyaWNGaWx0ZXJzOnt9LHNvcnQ6e21ldHJpYzoiIixvcmRlcjokci5ERVNDRU5ESU5HfSxwY0V4cGFuZGVkOiEwLGFubm90YXRpb25zRXhwYW5kZWQ6ITAsc2lkZWJhckV4cGFuZGVkOiEwLHNob3dDb3VudHM6ITAsc2hvd0hpZGRlbkFubm90YXRpb25zOiExLHNpZGViYXJXaWR0aDozMDAsdmlld0FjdGl2ZTprcC5ERUZBVUxULGVtYmVkZGluZ3NNZXRyaWM6IiIsZW1iZWRkaW5nc1NpZGViYXJXaWR0aDo1MDAsZW1iZWRkaW5nc1NpZGViYXJFeHBhbmRlZDohMH0sU2UoeEYsbj0+KHsuLi5uLHBsdWdpbkRhdGFMb2FkZWQ6ey4uLm4ucGx1Z2luRGF0YUxvYWRlZCxzdGF0ZTpPZS5MT0FESU5HfX0pKSxTZShNRixuPT4oey4uLm4scGx1Z2luRGF0YUxvYWRlZDp7Li4ubi5wbHVnaW5EYXRhTG9hZGVkLHN0YXRlOk9lLkZBSUxFRH19KSksU2UoQ0YsKG4se2Fubm90YXRpb25EYXRhOnQsbWV0cmljczplLGVtYmVkZGluZ0RhdGFTZXQ6aX0pPT57bGV0IHI9e307Zm9yKGxldCBvIGluIGUpe3Jbb109W107Zm9yKGxldCBzIG9mIGVbb10pYUYocykmJnJbb10ucHVzaChzKX1yZXR1cm57Li4ubixydW5Ub01ldHJpY3M6cixhbm5vdGF0aW9uRGF0YTp0LGVtYmVkZGluZ0RhdGFTZXQ6aSxwbHVnaW5EYXRhTG9hZGVkOntzdGF0ZTpPZS5MT0FERUQsbGFzdExvYWRlZFRpbWVJbk1zOkRhdGUubm93KCl9fX0pLFNlKHdGLChuLHthbm5vdGF0aW9uczp0fSk9PntsZXQgZT1uZXcgU2V0KFsuLi5uLnNlbGVjdGVkQW5ub3RhdGlvbnMsLi4udF0pO2lmKGUuc2l6ZT09PW4uc2VsZWN0ZWRBbm5vdGF0aW9ucy5sZW5ndGgpZm9yKGxldCBpIG9mIHQpZS5kZWxldGUoaSk7cmV0dXJuey4uLm4sc2VsZWN0ZWRBbm5vdGF0aW9uczpbLi4uZV19fSksU2UoUkUsKG4se2Fubm90YXRpb25zOnR9KT0+KHsuLi5uLHNlbGVjdGVkQW5ub3RhdGlvbnM6dH0pKSxTZShTRixuPT4oey4uLm4sc2VsZWN0ZWRBbm5vdGF0aW9uczpbXX0pKSxTZShFRiwobix7YW5ub3RhdGlvbnM6dH0pPT57bGV0IGU9bmV3IFNldChbLi4ubi5mbGFnZ2VkQW5ub3RhdGlvbnMsLi4udF0pO2lmKGUuc2l6ZT09PW4uZmxhZ2dlZEFubm90YXRpb25zLmxlbmd0aClmb3IobGV0IGkgb2YgdCllLmRlbGV0ZShpKTtyZXR1cm57Li4ubixmbGFnZ2VkQW5ub3RhdGlvbnM6Wy4uLmVdLHNlbGVjdGVkQW5ub3RhdGlvbnM6W119fSksU2UoVEYsKG4se2Fubm90YXRpb25zOnR9KT0+e2xldCBlPW5ldyBTZXQoWy4uLm4uaGlkZGVuQW5ub3RhdGlvbnMsLi4udF0pO2lmKGUuc2l6ZT09PW4uaGlkZGVuQW5ub3RhdGlvbnMubGVuZ3RoKWZvcihsZXQgaSBvZiB0KWUuZGVsZXRlKGkpO3JldHVybnsuLi5uLGhpZGRlbkFubm90YXRpb25zOlsuLi5lXSxzZWxlY3RlZEFubm90YXRpb25zOltdfX0pLFNlKGpiLChuLHtyZWdleDp0fSk9Pih7Li4ubixhbm5vdGF0aW9uc1JlZ2V4OnR9KSksU2UoT0UsKG4se3JlZ2V4OnR9KT0+KHsuLi5uLG1ldHJpY3NSZWdleDp0fSkpLFNlKERGLChuLHttZXRyaWM6dH0pPT57aWYobi5tZXRyaWNGaWx0ZXJzW3RdKXJldHVybiBuO2xldCBlPVtdO3JldHVybiAwIT09bi5tZXRyaWNBcml0aG1ldGljLmxlbmd0aCYmZS5wdXNoKHtraW5kOm11Lk9QRVJBVE9SLG9wZXJhdG9yOnMwLkFORH0pLGUucHVzaCh7a2luZDptdS5NRVRSSUMsbWV0cmljOnR9KSx7Li4ubixtZXRyaWNBcml0aG1ldGljOlsuLi5uLm1ldHJpY0FyaXRobWV0aWMsLi4uZV0sbWV0cmljRmlsdGVyczp7Li4ubi5tZXRyaWNGaWx0ZXJzLFt0XTp7bWF4OjEsbWluOi0xLGluY2x1ZGVOYU46ITF9fSxzb3J0OnttZXRyaWM6dCxvcmRlcjokci5ERVNDRU5ESU5HfX19KSxTZShHYiwobix7bWV0cmljOnR9KT0+e2lmKCFuLm1ldHJpY0ZpbHRlcnNbdF0pcmV0dXJuIG47bGV0IGU9MCxpPTAscj0yLHtbdF06bywuLi5zfT1uLm1ldHJpY0ZpbHRlcnM7Zm9yKGxldCBhIGluIG4ubWV0cmljQXJpdGhtZXRpYyl7bGV0IGw9bi5tZXRyaWNBcml0aG1ldGljW2FdO2wua2luZD09PW11Lk1FVFJJQyYmbC5tZXRyaWM9PT10JiYoZT1wYXJzZUludChhKSl9cmV0dXJuIDAhPT1lJiYoaT1lLTEscj1lKzEpLHsuLi5uLG1ldHJpY0FyaXRobWV0aWM6Wy4uLm4ubWV0cmljQXJpdGhtZXRpYy5zbGljZSgwLGkpLC4uLm4ubWV0cmljQXJpdGhtZXRpYy5zbGljZShyKV0sbWV0cmljRmlsdGVyczpzfX0pLFNlKFdiLChuLHttZXRyaWM6dCxtYXg6ZSxtaW46aSxpbmNsdWRlTmFOOnJ9KT0+bi5tZXRyaWNGaWx0ZXJzW3RdP3suLi5uLG1ldHJpY0ZpbHRlcnM6ey4uLm4ubWV0cmljRmlsdGVycyxbdF06e21heDplLG1pbjppLGluY2x1ZGVOYU46cn19fTpuKSxTZShBRiwobix7bWV0cmljOnR9KT0+e2xldCBlPXttZXRyaWM6dCxvcmRlcjokci5ERVNDRU5ESU5HfTtyZXR1cm4gbi5zb3J0Lm1ldHJpYz09PXQmJm4uc29ydC5vcmRlcj09PSRyLkRFU0NFTkRJTkcmJihlLm9yZGVyPSRyLkFTQ0VORE5HKSx7Li4ubixzb3J0OmV9fSksU2UoSUYsKG4se2Fubm90YXRpb246dH0pPT57bGV0IGU9e21ldHJpYzp0LG9yZGVyOiRyLlNJTUlMQVJ9O3JldHVybiBuLnNvcnQubWV0cmljPT09dCYmbi5zb3J0Lm9yZGVyPT09JHIuU0lNSUxBUiYmKGUub3JkZXI9JHIuRElTU0lNSUxBUiksey4uLm4sc29ydDplfX0pLFNlKFBGLG49Pih7Li4ubixwY0V4cGFuZGVkOiFuLnBjRXhwYW5kZWR9KSksU2UoUkYsbj0+KHsuLi5uLGFubm90YXRpb25zRXhwYW5kZWQ6IW4uYW5ub3RhdGlvbnNFeHBhbmRlZH0pKSxTZShxYixuPT4oey4uLm4sc2lkZWJhckV4cGFuZGVkOiFuLnNpZGViYXJFeHBhbmRlZH0pKSxTZShPRixuPT4oey4uLm4sc2hvd0NvdW50czohbi5zaG93Q291bnRzfSkpLFNlKGtGLG49Pih7Li4ubixzaG93SGlkZGVuQW5ub3RhdGlvbnM6IW4uc2hvd0hpZGRlbkFubm90YXRpb25zfSkpLFNlKEZGLChuLHttZXRyaWM6dH0pPT57bGV0IGU9a3AuRU1CRURESU5HUyxpPXQ7cmV0dXJuIHQ9PT1uLmVtYmVkZGluZ3NNZXRyaWMmJihlPWtwLkRFRkFVTFQsaT0iIiksey4uLm4sdmlld0FjdGl2ZTplLGVtYmVkZGluZ3NNZXRyaWM6aX19KSxTZShORiwobix7c2lkZWJhcldpZHRoOnR9KT0+KHsuLi5uLHNpZGViYXJXaWR0aDp0fSkpLFNlKExGLChuLHtzaWRlYmFyV2lkdGg6dH0pPT4oey4uLm4sZW1iZWRkaW5nc1NpZGViYXJXaWR0aDp0fSkpLFNlKFliLG49Pih7Li4ubixlbWJlZGRpbmdzU2lkZWJhckV4cGFuZGVkOiFuLmVtYmVkZGluZ3NTaWRlYmFyRXhwYW5kZWR9KSksU2Uobm1lLChuLHtkYXRhU2V0OnR9KT0+KHsuLi5uLGVtYmVkZGluZ0RhdGFTZXQ6dH0pKSk7ZnVuY3Rpb24gZG1lKG4sdCl7cmV0dXJuIHRldChuLHQpfWZ1bmN0aW9uIG5ldChuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsNyl9ZnVuY3Rpb24gaWV0KG4sdCl7aWYoMSZuJiYoXygwLCJtYXQtb3B0aW9uIiw4KSxBKDEpLHYoKSksMiZuKXtsZXQgZT10LiRpbXBsaWNpdDt5KCJ2YWx1ZSIsZSksQygxKSx5dChlKX19dmFyIHBtZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5vblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2U9bmV3IEcsdGhpcy5vbkFkZEZpbHRlcj1uZXcgR31vbk9wdGlvblNlbGVjdGVkKGUsaSl7dGhpcy5vbkFkZEZpbHRlci5lbWl0KGUub3B0aW9uLnZhbHVlKSxpLnZhbHVlPSIifX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJtZXRyaWMtc2VhcmNoLWNvbXBvbmVudCJdXSxob3N0VmFyczoyLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmZXQoInZhbGlkIixpLmlzUmVnZXhGaWx0ZXJWYWxpZCl9LGlucHV0czp7Y29tcGxldGlvbnM6ImNvbXBsZXRpb25zIixyZWdleEZpbHRlclZhbHVlOiJyZWdleEZpbHRlclZhbHVlIixpc1JlZ2V4RmlsdGVyVmFsaWQ6ImlzUmVnZXhGaWx0ZXJWYWxpZCJ9LG91dHB1dHM6e29uUmVnZXhGaWx0ZXJWYWx1ZUNoYW5nZToib25SZWdleEZpbHRlclZhbHVlQ2hhbmdlIixvbkFkZEZpbHRlcjoib25BZGRGaWx0ZXIifSxkZWNsczo3LHZhcnM6NCxjb25zdHM6W1sic3ZnSWNvbiIsInNlYXJjaF8yNHB4Il0sWyJtYXRJbnB1dCIsIiIsImF1dG9jb21wbGV0ZSIsIm9mZiIsInBsYWNlaG9sZGVyIiwiQWRkIE1ldHJpYyBGaWx0ZXIiLDMsInZhbHVlIiwibWF0QXV0b2NvbXBsZXRlIiwiaW5wdXQiXSxbIm1hdElucHV0IiwiIl0sWyJzdmdJY29uIiwiZXJyb3JfMjRweCIsImNsYXNzIiwiZXJyb3ItaWNvbiIsIm1hdFRvb2x0aXAiLCJJbnZhbGlkIHJlZ2V4IGZpbHRlci4gVGhlIHJlc3VsdCBtYXkgYmUgc3RhbGUuIiw0LCJuZ0lmIl0sWyJhdXRvQWN0aXZlRmlyc3RPcHRpb24iLCIiLDMsIm9wdGlvblNlbGVjdGVkIl0sWyJmaWx0ZXJNYXRjaGVzIiwibWF0QXV0b2NvbXBsZXRlIl0sWzMsInZhbHVlIiw0LCJuZ0ZvciIsIm5nRm9yT2YiXSxbInN2Z0ljb24iLCJlcnJvcl8yNHB4IiwibWF0VG9vbHRpcCIsIkludmFsaWQgcmVnZXggZmlsdGVyLiBUaGUgcmVzdWx0IG1heSBiZSBzdGFsZS4iLDEsImVycm9yLWljb24iXSxbMywidmFsdWUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUpe2xldCByPVBlKCk7TygwLCJtYXQtaWNvbiIsMCksXygxLCJpbnB1dCIsMSwyKSxQKCJpbnB1dCIsZnVuY3Rpb24ocyl7cmV0dXJuIGkub25SZWdleEZpbHRlclZhbHVlQ2hhbmdlLmVtaXQocy50YXJnZXQudmFsdWUpfSksdigpLEUoMyxuZXQsMSwwLCJtYXQtaWNvbiIsMyksXyg0LCJtYXQtYXV0b2NvbXBsZXRlIiw0LDUpLFAoIm9wdGlvblNlbGVjdGVkIixmdW5jdGlvbihzKXtvZShyKTtsZXQgYT0kZSgyKTtyZXR1cm4gc2UoaS5vbk9wdGlvblNlbGVjdGVkKHMsYSkpfSksRSg2LGlldCwyLDIsIm1hdC1vcHRpb24iLDYpLHYoKX1pZigyJmUpe2xldCByPSRlKDUpO0MoMSkseSgidmFsdWUiLGkucmVnZXhGaWx0ZXJWYWx1ZSkoIm1hdEF1dG9jb21wbGV0ZSIsciksQygyKSx5KCJuZ0lmIiwhaS5pc1JlZ2V4RmlsdGVyVmFsaWQpLEMoMykseSgibmdGb3JPZiIsaS5jb21wbGV0aW9ucyl9fSxkZXBlbmRlbmNpZXM6W2RuLEJlLEd0LFVoLHFrLCRnLE9zXSxzdHlsZXM6WyJtYXQtaWNvbltfbmdjb250ZW50LSVDT01QJV17ZmxleDpub25lO21hcmdpbi1yaWdodDo1cHh9W19uZ2hvc3QtJUNPTVAlXXtkaXNwbGF5OmZsZXg7cGFkZGluZzowIDEwcHg7cG9zaXRpb246cmVsYXRpdmU7Zm9udC1zaXplOi45ZW19W19uZ2hvc3QtJUNPTVAlXTpub3QoLnZhbGlkKXtjb2xvcjojYzYyODI4fVtfbmdob3N0LSVDT01QJV06bm90KC52YWxpZCkgICBpbnB1dFtfbmdjb250ZW50LSVDT01QJV17Y2FyZXQtY29sb3I6Y3VycmVudENvbG9yfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksaG1lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMubWV0cmljc1JlZ2V4JD10aGlzLnN0b3JlLnNlbGVjdChybWUpLHRoaXMuYWN0aXZlUnVucyQ9dGhpcy5zdG9yZS5waXBlKHZ0KG9vKSkucGlwZShMKGk9Pmk/QXJyYXkuZnJvbShpLmVudHJpZXMoKSkuZmlsdGVyKHI9PnJbMV0pLm1hcChyPT5yWzBdKTpbXSkpLHRoaXMubWV0cmljc0ZvckFjdGl2ZVJ1bnMkPUx0KHRoaXMuYWN0aXZlUnVucyQsdGhpcy5zdG9yZS5zZWxlY3QoUmYpKS5waXBlKEwoKFtpLHJdKT0+e2xldCBvPW5ldyBTZXQ7Zm9yKGxldCBzIG9mIGkpaWYocltzXSlmb3IobGV0IGEgb2YgcltzXSlvLmFkZChhKTtyZXR1cm5bLi4ub119KSksdGhpcy5pc01ldHJpY3NGaWx0ZXJWYWxpZCQ9dGhpcy5tZXRyaWNzUmVnZXgkLnBpcGUoTChpPT57dHJ5e3JldHVybiBCb29sZWFuKG5ldyBSZWdFeHAoaSkpfWNhdGNoe3JldHVybiExfX0pKSx0aGlzLm1ldHJpY0ZpbHRlcktleXMkPXRoaXMuc3RvcmUucGlwZSh2dChJbCkpLnBpcGUoTChpPT5PYmplY3Qua2V5cyhpKSkpLHRoaXMuY29tcGxldGlvbnMkPUx0KHRoaXMubWV0cmljc0ZvckFjdGl2ZVJ1bnMkLHRoaXMubWV0cmljc1JlZ2V4JCx0aGlzLm1ldHJpY0ZpbHRlcktleXMkKS5waXBlKEwoKFtpLHIsb10pPT57bGV0IHM9aS5maWx0ZXIoYT0+IW8uaW5jbHVkZXMoYSkpO3RyeXtsZXQgYT1uZXcgUmVnRXhwKHIsImkiKTtyZXR1cm4gcy5maWx0ZXIobD0+YS50ZXN0KGwpKS5zb3J0KCl9Y2F0Y2h7cmV0dXJuW119fSkpfW9uRmlsdGVyQ2hhbmdlKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goT0Uoe3JlZ2V4OmV9KSl9b25BZGRGaWx0ZXIoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChERih7bWV0cmljOmV9KSksdGhpcy5zdG9yZS5kaXNwYXRjaChPRSh7cmVnZXg6IiJ9KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJucG1pLW1ldHJpYy1zZWFyY2giXV0sZGVjbHM6NCx2YXJzOjksY29uc3RzOltbMywicmVnZXhGaWx0ZXJWYWx1ZSIsImNvbXBsZXRpb25zIiwiaXNSZWdleEZpbHRlclZhbGlkIiwib25SZWdleEZpbHRlclZhbHVlQ2hhbmdlIiwib25BZGRGaWx0ZXIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1ldHJpYy1zZWFyY2gtY29tcG9uZW50IiwwKSxQKCJvblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uRmlsdGVyQ2hhbmdlKG8pfSkoIm9uQWRkRmlsdGVyIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vbkFkZEZpbHRlcihvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLHYoKSksMiZlJiZ5KCJyZWdleEZpbHRlclZhbHVlIixVKDEsMyxpLm1ldHJpY3NSZWdleCQpKSgiY29tcGxldGlvbnMiLFUoMiw1LGkuY29tcGxldGlvbnMkKSkoImlzUmVnZXhGaWx0ZXJWYWxpZCIsVSgzLDcsaS5pc01ldHJpY3NGaWx0ZXJWYWxpZCQpKX0sZGVwZW5kZW5jaWVzOltwbWUsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLHNldD1bIioiXSxmbWU9bmV3IHBlKCJNYXRDaGlwUmVtb3ZlIiksYWV0PW5ldyBwZSgiTWF0Q2hpcEF2YXRhciIpLGNldD1uZXcgcGUoIk1hdENoaXBUcmFpbGluZ0ljb24iKSx1ZXQ9b2Moa28ocW8oY2xhc3N7Y29uc3RydWN0b3IodCl7dGhpcy5fZWxlbWVudFJlZj10fX0pLCJwcmltYXJ5IiksLTEpLEpiPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyB1ZXR7Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEsbCxjKXtzdXBlcihlKSx0aGlzLl9uZ1pvbmU9aSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj1zLHRoaXMuX2hhc0ZvY3VzPSExLHRoaXMuY2hpcExpc3RTZWxlY3RhYmxlPSEwLHRoaXMuX2NoaXBMaXN0TXVsdGlwbGU9ITEsdGhpcy5fY2hpcExpc3REaXNhYmxlZD0hMSx0aGlzLnJvbGU9Im9wdGlvbiIsdGhpcy5fc2VsZWN0ZWQ9ITEsdGhpcy5fc2VsZWN0YWJsZT0hMCx0aGlzLl9kaXNhYmxlZD0hMSx0aGlzLl9yZW1vdmFibGU9ITAsdGhpcy5fb25Gb2N1cz1uZXcga2UsdGhpcy5fb25CbHVyPW5ldyBrZSx0aGlzLnNlbGVjdGlvbkNoYW5nZT1uZXcgRyx0aGlzLmRlc3Ryb3llZD1uZXcgRyx0aGlzLnJlbW92ZWQ9bmV3IEcsdGhpcy5fYWRkSG9zdENsYXNzTmFtZSgpLHRoaXMuX2NoaXBSaXBwbGVUYXJnZXQ9YS5jcmVhdGVFbGVtZW50KCJkaXYiKSx0aGlzLl9jaGlwUmlwcGxlVGFyZ2V0LmNsYXNzTGlzdC5hZGQoIm1hdC1jaGlwLXJpcHBsZSIpLHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5hcHBlbmRDaGlsZCh0aGlzLl9jaGlwUmlwcGxlVGFyZ2V0KSx0aGlzLl9jaGlwUmlwcGxlPW5ldyBUdih0aGlzLGksdGhpcy5fY2hpcFJpcHBsZVRhcmdldCxyKSx0aGlzLl9jaGlwUmlwcGxlLnNldHVwVHJpZ2dlckV2ZW50cyhlKSx0aGlzLnJpcHBsZUNvbmZpZz1vfHx7fSx0aGlzLl9hbmltYXRpb25zRGlzYWJsZWQ9Ik5vb3BBbmltYXRpb25zIj09PWwsdGhpcy50YWJJbmRleD1udWxsIT1jJiZwYXJzZUludChjKXx8LTF9Z2V0IHJpcHBsZURpc2FibGVkKCl7cmV0dXJuIHRoaXMuZGlzYWJsZWR8fHRoaXMuZGlzYWJsZVJpcHBsZXx8dGhpcy5fYW5pbWF0aW9uc0Rpc2FibGVkfHwhIXRoaXMucmlwcGxlQ29uZmlnLmRpc2FibGVkfWdldCBzZWxlY3RlZCgpe3JldHVybiB0aGlzLl9zZWxlY3RlZH1zZXQgc2VsZWN0ZWQoZSl7bGV0IGk9UnQoZSk7aSE9PXRoaXMuX3NlbGVjdGVkJiYodGhpcy5fc2VsZWN0ZWQ9aSx0aGlzLl9kaXNwYXRjaFNlbGVjdGlvbkNoYW5nZSgpKX1nZXQgdmFsdWUoKXtyZXR1cm4gdm9pZCAwIT09dGhpcy5fdmFsdWU/dGhpcy5fdmFsdWU6dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LnRleHRDb250ZW50fXNldCB2YWx1ZShlKXt0aGlzLl92YWx1ZT1lfWdldCBzZWxlY3RhYmxlKCl7cmV0dXJuIHRoaXMuX3NlbGVjdGFibGUmJnRoaXMuY2hpcExpc3RTZWxlY3RhYmxlfXNldCBzZWxlY3RhYmxlKGUpe3RoaXMuX3NlbGVjdGFibGU9UnQoZSl9Z2V0IGRpc2FibGVkKCl7cmV0dXJuIHRoaXMuX2NoaXBMaXN0RGlzYWJsZWR8fHRoaXMuX2Rpc2FibGVkfXNldCBkaXNhYmxlZChlKXt0aGlzLl9kaXNhYmxlZD1SdChlKX1nZXQgcmVtb3ZhYmxlKCl7cmV0dXJuIHRoaXMuX3JlbW92YWJsZX1zZXQgcmVtb3ZhYmxlKGUpe3RoaXMuX3JlbW92YWJsZT1SdChlKX1nZXQgYXJpYVNlbGVjdGVkKCl7cmV0dXJuIHRoaXMuc2VsZWN0YWJsZSYmKHRoaXMuX2NoaXBMaXN0TXVsdGlwbGV8fHRoaXMuc2VsZWN0ZWQpP3RoaXMuc2VsZWN0ZWQudG9TdHJpbmcoKTpudWxsfV9hZGRIb3N0Q2xhc3NOYW1lKCl7bGV0IGU9Im1hdC1iYXNpYy1jaGlwIixpPXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudDtpLmhhc0F0dHJpYnV0ZShlKXx8aS50YWdOYW1lLnRvTG93ZXJDYXNlKCk9PT1lP2kuY2xhc3NMaXN0LmFkZChlKTppLmNsYXNzTGlzdC5hZGQoIm1hdC1zdGFuZGFyZC1jaGlwIil9bmdPbkRlc3Ryb3koKXt0aGlzLmRlc3Ryb3llZC5lbWl0KHtjaGlwOnRoaXN9KSx0aGlzLl9jaGlwUmlwcGxlLl9yZW1vdmVUcmlnZ2VyRXZlbnRzKCl9c2VsZWN0KCl7dGhpcy5fc2VsZWN0ZWR8fCh0aGlzLl9zZWxlY3RlZD0hMCx0aGlzLl9kaXNwYXRjaFNlbGVjdGlvbkNoYW5nZSgpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpKX1kZXNlbGVjdCgpe3RoaXMuX3NlbGVjdGVkJiYodGhpcy5fc2VsZWN0ZWQ9ITEsdGhpcy5fZGlzcGF0Y2hTZWxlY3Rpb25DaGFuZ2UoKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSl9c2VsZWN0VmlhSW50ZXJhY3Rpb24oKXt0aGlzLl9zZWxlY3RlZHx8KHRoaXMuX3NlbGVjdGVkPSEwLHRoaXMuX2Rpc3BhdGNoU2VsZWN0aW9uQ2hhbmdlKCEwKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKSl9dG9nZ2xlU2VsZWN0ZWQoZT0hMSl7cmV0dXJuIHRoaXMuX3NlbGVjdGVkPSF0aGlzLnNlbGVjdGVkLHRoaXMuX2Rpc3BhdGNoU2VsZWN0aW9uQ2hhbmdlKGUpLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmLm1hcmtGb3JDaGVjaygpLHRoaXMuc2VsZWN0ZWR9Zm9jdXMoKXt0aGlzLl9oYXNGb2N1c3x8KHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5mb2N1cygpLHRoaXMuX29uRm9jdXMubmV4dCh7Y2hpcDp0aGlzfSkpLHRoaXMuX2hhc0ZvY3VzPSEwfXJlbW92ZSgpe3RoaXMucmVtb3ZhYmxlJiZ0aGlzLnJlbW92ZWQuZW1pdCh7Y2hpcDp0aGlzfSl9X2hhbmRsZUNsaWNrKGUpe3RoaXMuZGlzYWJsZWQmJmUucHJldmVudERlZmF1bHQoKX1faGFuZGxlS2V5ZG93bihlKXtpZighdGhpcy5kaXNhYmxlZClzd2l0Y2goZS5rZXlDb2RlKXtjYXNlIDQ2OmNhc2UgODp0aGlzLnJlbW92ZSgpLGUucHJldmVudERlZmF1bHQoKTticmVhaztjYXNlIDMyOnRoaXMuc2VsZWN0YWJsZSYmdGhpcy50b2dnbGVTZWxlY3RlZCghMCksZS5wcmV2ZW50RGVmYXVsdCgpfX1fYmx1cigpe3RoaXMuX25nWm9uZS5vblN0YWJsZS5waXBlKFF0KDEpKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9uZ1pvbmUucnVuKCgpPT57dGhpcy5faGFzRm9jdXM9ITEsdGhpcy5fb25CbHVyLm5leHQoe2NoaXA6dGhpc30pfSl9KX1fZGlzcGF0Y2hTZWxlY3Rpb25DaGFuZ2UoZT0hMSl7dGhpcy5zZWxlY3Rpb25DaGFuZ2UuZW1pdCh7c291cmNlOnRoaXMsaXNVc2VySW5wdXQ6ZSxzZWxlY3RlZDp0aGlzLl9zZWxlY3RlZH0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKF90KSxNKG9pKSxNKGcyLDgpLE0obm4pLE0oSHQpLE0oUGksOCksdm8oInRhYmluZGV4IikpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLHNlbGVjdG9yczpbWyJtYXQtYmFzaWMtY2hpcCJdLFsiIiwibWF0LWJhc2ljLWNoaXAiLCIiXSxbIm1hdC1jaGlwIl0sWyIiLCJtYXQtY2hpcCIsIiJdXSxjb250ZW50UXVlcmllczpmdW5jdGlvbihlLGkscil7aWYoMSZlJiYoRWkocixhZXQsNSksRWkocixjZXQsNSksRWkocixmbWUsNSkpLDImZSl7bGV0IG87TmUobz1MZSgpKSYmKGkuYXZhdGFyPW8uZmlyc3QpLE5lKG89TGUoKSkmJihpLnRyYWlsaW5nSWNvbj1vLmZpcnN0KSxOZShvPUxlKCkpJiYoaS5yZW1vdmVJY29uPW8uZmlyc3QpfX0saG9zdEF0dHJzOlsxLCJtYXQtY2hpcCIsIm1hdC1mb2N1cy1pbmRpY2F0b3IiXSxob3N0VmFyczoxNSxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJlAoImNsaWNrIixmdW5jdGlvbihvKXtyZXR1cm4gaS5faGFuZGxlQ2xpY2sobyl9KSgia2V5ZG93biIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX2hhbmRsZUtleWRvd24obyl9KSgiZm9jdXMiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuZm9jdXMoKX0pKCJibHVyIixmdW5jdGlvbigpe3JldHVybiBpLl9ibHVyKCl9KSwyJmUmJih6ZSgidGFiaW5kZXgiLGkuZGlzYWJsZWQ/bnVsbDppLnRhYkluZGV4KSgicm9sZSIsaS5yb2xlKSgiZGlzYWJsZWQiLGkuZGlzYWJsZWR8fG51bGwpKCJhcmlhLWRpc2FibGVkIixpLmRpc2FibGVkLnRvU3RyaW5nKCkpKCJhcmlhLXNlbGVjdGVkIixpLmFyaWFTZWxlY3RlZCksZXQoIm1hdC1jaGlwLXNlbGVjdGVkIixpLnNlbGVjdGVkKSgibWF0LWNoaXAtd2l0aC1hdmF0YXIiLGkuYXZhdGFyKSgibWF0LWNoaXAtd2l0aC10cmFpbGluZy1pY29uIixpLnRyYWlsaW5nSWNvbnx8aS5yZW1vdmVJY29uKSgibWF0LWNoaXAtZGlzYWJsZWQiLGkuZGlzYWJsZWQpKCJfbWF0LWFuaW1hdGlvbi1ub29wYWJsZSIsaS5fYW5pbWF0aW9uc0Rpc2FibGVkKSl9LGlucHV0czp7Y29sb3I6ImNvbG9yIixkaXNhYmxlUmlwcGxlOiJkaXNhYmxlUmlwcGxlIix0YWJJbmRleDoidGFiSW5kZXgiLHJvbGU6InJvbGUiLHNlbGVjdGVkOiJzZWxlY3RlZCIsdmFsdWU6InZhbHVlIixzZWxlY3RhYmxlOiJzZWxlY3RhYmxlIixkaXNhYmxlZDoiZGlzYWJsZWQiLHJlbW92YWJsZToicmVtb3ZhYmxlIn0sb3V0cHV0czp7c2VsZWN0aW9uQ2hhbmdlOiJzZWxlY3Rpb25DaGFuZ2UiLGRlc3Ryb3llZDoiZGVzdHJveWVkIixyZW1vdmVkOiJyZW1vdmVkIn0sZXhwb3J0QXM6WyJtYXRDaGlwIl0sZmVhdHVyZXM6W3R0XX0pLG59KSgpLG1tZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUsaSl7dGhpcy5fcGFyZW50Q2hpcD1lLCJCVVRUT04iPT09aS5uYXRpdmVFbGVtZW50Lm5vZGVOYW1lJiZpLm5hdGl2ZUVsZW1lbnQuc2V0QXR0cmlidXRlKCJ0eXBlIiwiYnV0dG9uIil9X2hhbmRsZUNsaWNrKGUpe2xldCBpPXRoaXMuX3BhcmVudENoaXA7aS5yZW1vdmFibGUmJiFpLmRpc2FibGVkJiZpLnJlbW92ZSgpLGUuc3RvcFByb3BhZ2F0aW9uKCksZS5wcmV2ZW50RGVmYXVsdCgpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKEpiKSxNKFJlKSl9LG4uXHUwMjc1ZGlyPUhlKHt0eXBlOm4sc2VsZWN0b3JzOltbIiIsIm1hdENoaXBSZW1vdmUiLCIiXV0saG9zdEF0dHJzOlsxLCJtYXQtY2hpcC1yZW1vdmUiLCJtYXQtY2hpcC10cmFpbGluZy1pY29uIl0saG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MSZlJiZQKCJjbGljayIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX2hhbmRsZUNsaWNrKG8pfSl9LGZlYXR1cmVzOlskdChbe3Byb3ZpZGU6Zm1lLHVzZUV4aXN0aW5nOm59XSldfSksbn0pKCksZGV0PW5ldyBwZSgibWF0LWNoaXBzLWRlZmF1bHQtb3B0aW9ucyIpLHBldD1EdihjbGFzc3tjb25zdHJ1Y3RvcihuLHQsZSxpKXt0aGlzLl9kZWZhdWx0RXJyb3JTdGF0ZU1hdGNoZXI9bix0aGlzLl9wYXJlbnRGb3JtPXQsdGhpcy5fcGFyZW50Rm9ybUdyb3VwPWUsdGhpcy5uZ0NvbnRyb2w9aSx0aGlzLnN0YXRlQ2hhbmdlcz1uZXcga2V9fSksaGV0PTAsZ21lPSgoKT0+e2NsYXNzIG4gZXh0ZW5kcyBwZXR7Y29uc3RydWN0b3IoZSxpLHIsbyxzLGEsbCl7c3VwZXIoYSxvLHMsbCksdGhpcy5fZWxlbWVudFJlZj1lLHRoaXMuX2NoYW5nZURldGVjdG9yUmVmPWksdGhpcy5fZGlyPXIsdGhpcy5jb250cm9sVHlwZT0ibWF0LWNoaXAtbGlzdCIsdGhpcy5fbGFzdERlc3Ryb3llZENoaXBJbmRleD1udWxsLHRoaXMuX2Rlc3Ryb3llZD1uZXcga2UsdGhpcy5fdWlkPSJtYXQtY2hpcC1saXN0LSIraGV0KyssdGhpcy5fdGFiSW5kZXg9MCx0aGlzLl91c2VyVGFiSW5kZXg9bnVsbCx0aGlzLl9vblRvdWNoZWQ9KCk9Pnt9LHRoaXMuX29uQ2hhbmdlPSgpPT57fSx0aGlzLl9tdWx0aXBsZT0hMSx0aGlzLl9jb21wYXJlV2l0aD0oYyx1KT0+Yz09PXUsdGhpcy5fZGlzYWJsZWQ9ITEsdGhpcy5hcmlhT3JpZW50YXRpb249Imhvcml6b250YWwiLHRoaXMuX3NlbGVjdGFibGU9ITAsdGhpcy5jaGFuZ2U9bmV3IEcsdGhpcy52YWx1ZUNoYW5nZT1uZXcgRyx0aGlzLm5nQ29udHJvbCYmKHRoaXMubmdDb250cm9sLnZhbHVlQWNjZXNzb3I9dGhpcyl9Z2V0IHNlbGVjdGVkKCl7cmV0dXJuIHRoaXMubXVsdGlwbGU/dGhpcy5fc2VsZWN0aW9uTW9kZWw/LnNlbGVjdGVkfHxbXTp0aGlzLl9zZWxlY3Rpb25Nb2RlbD8uc2VsZWN0ZWRbMF19Z2V0IHJvbGUoKXtyZXR1cm4gdGhpcy5fZXhwbGljaXRSb2xlP3RoaXMuX2V4cGxpY2l0Um9sZTp0aGlzLmVtcHR5P251bGw6Imxpc3Rib3gifXNldCByb2xlKGUpe3RoaXMuX2V4cGxpY2l0Um9sZT1lfWdldCBtdWx0aXBsZSgpe3JldHVybiB0aGlzLl9tdWx0aXBsZX1zZXQgbXVsdGlwbGUoZSl7dGhpcy5fbXVsdGlwbGU9UnQoZSksdGhpcy5fc3luY0NoaXBzU3RhdGUoKX1nZXQgY29tcGFyZVdpdGgoKXtyZXR1cm4gdGhpcy5fY29tcGFyZVdpdGh9c2V0IGNvbXBhcmVXaXRoKGUpe3RoaXMuX2NvbXBhcmVXaXRoPWUsdGhpcy5fc2VsZWN0aW9uTW9kZWwmJnRoaXMuX2luaXRpYWxpemVTZWxlY3Rpb24oKX1nZXQgdmFsdWUoKXtyZXR1cm4gdGhpcy5fdmFsdWV9c2V0IHZhbHVlKGUpe3RoaXMud3JpdGVWYWx1ZShlKSx0aGlzLl92YWx1ZT1lfWdldCBpZCgpe3JldHVybiB0aGlzLl9jaGlwSW5wdXQ/dGhpcy5fY2hpcElucHV0LmlkOnRoaXMuX3VpZH1nZXQgcmVxdWlyZWQoKXtyZXR1cm4gdGhpcy5fcmVxdWlyZWQ/P3RoaXMubmdDb250cm9sPy5jb250cm9sPy5oYXNWYWxpZGF0b3IoRm8ucmVxdWlyZWQpPz8hMX1zZXQgcmVxdWlyZWQoZSl7dGhpcy5fcmVxdWlyZWQ9UnQoZSksdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpfWdldCBwbGFjZWhvbGRlcigpe3JldHVybiB0aGlzLl9jaGlwSW5wdXQ/dGhpcy5fY2hpcElucHV0LnBsYWNlaG9sZGVyOnRoaXMuX3BsYWNlaG9sZGVyfXNldCBwbGFjZWhvbGRlcihlKXt0aGlzLl9wbGFjZWhvbGRlcj1lLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKX1nZXQgZm9jdXNlZCgpe3JldHVybiB0aGlzLl9jaGlwSW5wdXQmJnRoaXMuX2NoaXBJbnB1dC5mb2N1c2VkfHx0aGlzLl9oYXNGb2N1c2VkQ2hpcCgpfWdldCBlbXB0eSgpe3JldHVybighdGhpcy5fY2hpcElucHV0fHx0aGlzLl9jaGlwSW5wdXQuZW1wdHkpJiYoIXRoaXMuY2hpcHN8fDA9PT10aGlzLmNoaXBzLmxlbmd0aCl9Z2V0IHNob3VsZExhYmVsRmxvYXQoKXtyZXR1cm4hdGhpcy5lbXB0eXx8dGhpcy5mb2N1c2VkfWdldCBkaXNhYmxlZCgpe3JldHVybiB0aGlzLm5nQ29udHJvbD8hIXRoaXMubmdDb250cm9sLmRpc2FibGVkOnRoaXMuX2Rpc2FibGVkfXNldCBkaXNhYmxlZChlKXt0aGlzLl9kaXNhYmxlZD1SdChlKSx0aGlzLl9zeW5jQ2hpcHNTdGF0ZSgpfWdldCBzZWxlY3RhYmxlKCl7cmV0dXJuIHRoaXMuX3NlbGVjdGFibGV9c2V0IHNlbGVjdGFibGUoZSl7dGhpcy5fc2VsZWN0YWJsZT1SdChlKSx0aGlzLl9zeW5jQ2hpcHNTdGF0ZSgpfXNldCB0YWJJbmRleChlKXt0aGlzLl91c2VyVGFiSW5kZXg9ZSx0aGlzLl90YWJJbmRleD1lfWdldCBjaGlwU2VsZWN0aW9uQ2hhbmdlcygpe3JldHVybiBKdCguLi50aGlzLmNoaXBzLm1hcChlPT5lLnNlbGVjdGlvbkNoYW5nZSkpfWdldCBjaGlwRm9jdXNDaGFuZ2VzKCl7cmV0dXJuIEp0KC4uLnRoaXMuY2hpcHMubWFwKGU9PmUuX29uRm9jdXMpKX1nZXQgY2hpcEJsdXJDaGFuZ2VzKCl7cmV0dXJuIEp0KC4uLnRoaXMuY2hpcHMubWFwKGU9PmUuX29uQmx1cikpfWdldCBjaGlwUmVtb3ZlQ2hhbmdlcygpe3JldHVybiBKdCguLi50aGlzLmNoaXBzLm1hcChlPT5lLmRlc3Ryb3llZCkpfW5nQWZ0ZXJDb250ZW50SW5pdCgpe3RoaXMuX2tleU1hbmFnZXI9bmV3IFNoKHRoaXMuY2hpcHMpLndpdGhXcmFwKCkud2l0aFZlcnRpY2FsT3JpZW50YXRpb24oKS53aXRoSG9tZUFuZEVuZCgpLndpdGhIb3Jpem9udGFsT3JpZW50YXRpb24odGhpcy5fZGlyP3RoaXMuX2Rpci52YWx1ZToibHRyIiksdGhpcy5fZGlyJiZ0aGlzLl9kaXIuY2hhbmdlLnBpcGUoc3QodGhpcy5fZGVzdHJveWVkKSkuc3Vic2NyaWJlKGU9PnRoaXMuX2tleU1hbmFnZXIud2l0aEhvcml6b250YWxPcmllbnRhdGlvbihlKSksdGhpcy5fa2V5TWFuYWdlci50YWJPdXQucGlwZShzdCh0aGlzLl9kZXN0cm95ZWQpKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLl9hbGxvd0ZvY3VzRXNjYXBlKCl9KSx0aGlzLmNoaXBzLmNoYW5nZXMucGlwZSh6bihudWxsKSxzdCh0aGlzLl9kZXN0cm95ZWQpKS5zdWJzY3JpYmUoKCk9PnsodGhpcy5kaXNhYmxlZHx8IXRoaXMuc2VsZWN0YWJsZSkmJlByb21pc2UucmVzb2x2ZSgpLnRoZW4oKCk9Pnt0aGlzLl9zeW5jQ2hpcHNTdGF0ZSgpfSksdGhpcy5fcmVzZXRDaGlwcygpLHRoaXMuX2luaXRpYWxpemVTZWxlY3Rpb24oKSx0aGlzLl91cGRhdGVUYWJJbmRleCgpLHRoaXMuX3VwZGF0ZUZvY3VzRm9yRGVzdHJveWVkQ2hpcHMoKSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCl9KX1uZ09uSW5pdCgpe3RoaXMuX3NlbGVjdGlvbk1vZGVsPW5ldyBBaCh0aGlzLm11bHRpcGxlLHZvaWQgMCwhMSksdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpfW5nRG9DaGVjaygpe3RoaXMubmdDb250cm9sJiYodGhpcy51cGRhdGVFcnJvclN0YXRlKCksdGhpcy5uZ0NvbnRyb2wuZGlzYWJsZWQhPT10aGlzLl9kaXNhYmxlZCYmKHRoaXMuZGlzYWJsZWQ9ISF0aGlzLm5nQ29udHJvbC5kaXNhYmxlZCkpfW5nT25EZXN0cm95KCl7dGhpcy5fZGVzdHJveWVkLm5leHQoKSx0aGlzLl9kZXN0cm95ZWQuY29tcGxldGUoKSx0aGlzLnN0YXRlQ2hhbmdlcy5jb21wbGV0ZSgpLHRoaXMuX2Ryb3BTdWJzY3JpcHRpb25zKCl9cmVnaXN0ZXJJbnB1dChlKXt0aGlzLl9jaGlwSW5wdXQ9ZSx0aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuc2V0QXR0cmlidXRlKCJkYXRhLW1hdC1jaGlwLWlucHV0IixlLmlkKX1zZXREZXNjcmliZWRCeUlkcyhlKXtlLmxlbmd0aD90aGlzLl9lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuc2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IixlLmpvaW4oIiAiKSk6dGhpcy5fZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50LnJlbW92ZUF0dHJpYnV0ZSgiYXJpYS1kZXNjcmliZWRieSIpfXdyaXRlVmFsdWUoZSl7dGhpcy5jaGlwcyYmdGhpcy5fc2V0U2VsZWN0aW9uQnlWYWx1ZShlLCExKX1yZWdpc3Rlck9uQ2hhbmdlKGUpe3RoaXMuX29uQ2hhbmdlPWV9cmVnaXN0ZXJPblRvdWNoZWQoZSl7dGhpcy5fb25Ub3VjaGVkPWV9c2V0RGlzYWJsZWRTdGF0ZShlKXt0aGlzLmRpc2FibGVkPWUsdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpfW9uQ29udGFpbmVyQ2xpY2soZSl7dGhpcy5fb3JpZ2luYXRlc0Zyb21DaGlwKGUpfHx0aGlzLmZvY3VzKCl9Zm9jdXMoZSl7dGhpcy5kaXNhYmxlZHx8dGhpcy5fY2hpcElucHV0JiZ0aGlzLl9jaGlwSW5wdXQuZm9jdXNlZHx8KHRoaXMuY2hpcHMubGVuZ3RoPjA/KHRoaXMuX2tleU1hbmFnZXIuc2V0Rmlyc3RJdGVtQWN0aXZlKCksdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpKToodGhpcy5fZm9jdXNJbnB1dChlKSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCkpKX1fZm9jdXNJbnB1dChlKXt0aGlzLl9jaGlwSW5wdXQmJnRoaXMuX2NoaXBJbnB1dC5mb2N1cyhlKX1fa2V5ZG93bihlKXtsZXQgaT1lLnRhcmdldDtpJiZpLmNsYXNzTGlzdC5jb250YWlucygibWF0LWNoaXAiKSYmKHRoaXMuX2tleU1hbmFnZXIub25LZXlkb3duKGUpLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKSl9X3VwZGF0ZVRhYkluZGV4KCl7dGhpcy5fdGFiSW5kZXg9dGhpcy5fdXNlclRhYkluZGV4fHwoMD09PXRoaXMuY2hpcHMubGVuZ3RoPy0xOjApfV91cGRhdGVGb2N1c0ZvckRlc3Ryb3llZENoaXBzKCl7aWYobnVsbCE9dGhpcy5fbGFzdERlc3Ryb3llZENoaXBJbmRleClpZih0aGlzLmNoaXBzLmxlbmd0aCl7bGV0IGU9TWF0aC5taW4odGhpcy5fbGFzdERlc3Ryb3llZENoaXBJbmRleCx0aGlzLmNoaXBzLmxlbmd0aC0xKTt0aGlzLl9rZXlNYW5hZ2VyLnNldEFjdGl2ZUl0ZW0oZSl9ZWxzZSB0aGlzLmZvY3VzKCk7dGhpcy5fbGFzdERlc3Ryb3llZENoaXBJbmRleD1udWxsfV9pc1ZhbGlkSW5kZXgoZSl7cmV0dXJuIGU+PTAmJmU8dGhpcy5jaGlwcy5sZW5ndGh9X3NldFNlbGVjdGlvbkJ5VmFsdWUoZSxpPSEwKXtpZih0aGlzLl9jbGVhclNlbGVjdGlvbigpLHRoaXMuY2hpcHMuZm9yRWFjaChyPT5yLmRlc2VsZWN0KCkpLEFycmF5LmlzQXJyYXkoZSkpZS5mb3JFYWNoKHI9PnRoaXMuX3NlbGVjdFZhbHVlKHIsaSkpLHRoaXMuX3NvcnRWYWx1ZXMoKTtlbHNle2xldCByPXRoaXMuX3NlbGVjdFZhbHVlKGUsaSk7ciYmaSYmdGhpcy5fa2V5TWFuYWdlci5zZXRBY3RpdmVJdGVtKHIpfX1fc2VsZWN0VmFsdWUoZSxpPSEwKXtsZXQgcj10aGlzLmNoaXBzLmZpbmQobz0+bnVsbCE9by52YWx1ZSYmdGhpcy5fY29tcGFyZVdpdGgoby52YWx1ZSxlKSk7cmV0dXJuIHImJihpP3Iuc2VsZWN0VmlhSW50ZXJhY3Rpb24oKTpyLnNlbGVjdCgpLHRoaXMuX3NlbGVjdGlvbk1vZGVsLnNlbGVjdChyKSkscn1faW5pdGlhbGl6ZVNlbGVjdGlvbigpe1Byb21pc2UucmVzb2x2ZSgpLnRoZW4oKCk9PnsodGhpcy5uZ0NvbnRyb2x8fHRoaXMuX3ZhbHVlKSYmKHRoaXMuX3NldFNlbGVjdGlvbkJ5VmFsdWUodGhpcy5uZ0NvbnRyb2w/dGhpcy5uZ0NvbnRyb2wudmFsdWU6dGhpcy5fdmFsdWUsITEpLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKSl9KX1fY2xlYXJTZWxlY3Rpb24oZSl7dGhpcy5fc2VsZWN0aW9uTW9kZWwuY2xlYXIoKSx0aGlzLmNoaXBzLmZvckVhY2goaT0+e2khPT1lJiZpLmRlc2VsZWN0KCl9KSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCl9X3NvcnRWYWx1ZXMoKXt0aGlzLl9tdWx0aXBsZSYmKHRoaXMuX3NlbGVjdGlvbk1vZGVsLmNsZWFyKCksdGhpcy5jaGlwcy5mb3JFYWNoKGU9PntlLnNlbGVjdGVkJiZ0aGlzLl9zZWxlY3Rpb25Nb2RlbC5zZWxlY3QoZSl9KSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCkpfV9wcm9wYWdhdGVDaGFuZ2VzKGUpe2xldCBpPW51bGw7aT1BcnJheS5pc0FycmF5KHRoaXMuc2VsZWN0ZWQpP3RoaXMuc2VsZWN0ZWQubWFwKHI9PnIudmFsdWUpOnRoaXMuc2VsZWN0ZWQ/dGhpcy5zZWxlY3RlZC52YWx1ZTplLHRoaXMuX3ZhbHVlPWksdGhpcy5jaGFuZ2UuZW1pdChuZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLnNvdXJjZT10LHRoaXMudmFsdWU9ZX19KHRoaXMsaSkpLHRoaXMudmFsdWVDaGFuZ2UuZW1pdChpKSx0aGlzLl9vbkNoYW5nZShpKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKX1fYmx1cigpe3RoaXMuX2hhc0ZvY3VzZWRDaGlwKCl8fHRoaXMuX2tleU1hbmFnZXIuc2V0QWN0aXZlSXRlbSgtMSksdGhpcy5kaXNhYmxlZHx8KHRoaXMuX2NoaXBJbnB1dD9zZXRUaW1lb3V0KCgpPT57dGhpcy5mb2N1c2VkfHx0aGlzLl9tYXJrQXNUb3VjaGVkKCl9KTp0aGlzLl9tYXJrQXNUb3VjaGVkKCkpfV9tYXJrQXNUb3VjaGVkKCl7dGhpcy5fb25Ub3VjaGVkKCksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCksdGhpcy5zdGF0ZUNoYW5nZXMubmV4dCgpfV9hbGxvd0ZvY3VzRXNjYXBlKCl7LTEhPT10aGlzLl90YWJJbmRleCYmKHRoaXMuX3RhYkluZGV4PS0xLHNldFRpbWVvdXQoKCk9Pnt0aGlzLl90YWJJbmRleD10aGlzLl91c2VyVGFiSW5kZXh8fDAsdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCl9KSl9X3Jlc2V0Q2hpcHMoKXt0aGlzLl9kcm9wU3Vic2NyaXB0aW9ucygpLHRoaXMuX2xpc3RlblRvQ2hpcHNGb2N1cygpLHRoaXMuX2xpc3RlblRvQ2hpcHNTZWxlY3Rpb24oKSx0aGlzLl9saXN0ZW5Ub0NoaXBzUmVtb3ZlZCgpfV9kcm9wU3Vic2NyaXB0aW9ucygpe3RoaXMuX2NoaXBGb2N1c1N1YnNjcmlwdGlvbiYmKHRoaXMuX2NoaXBGb2N1c1N1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpLHRoaXMuX2NoaXBGb2N1c1N1YnNjcmlwdGlvbj1udWxsKSx0aGlzLl9jaGlwQmx1clN1YnNjcmlwdGlvbiYmKHRoaXMuX2NoaXBCbHVyU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5fY2hpcEJsdXJTdWJzY3JpcHRpb249bnVsbCksdGhpcy5fY2hpcFNlbGVjdGlvblN1YnNjcmlwdGlvbiYmKHRoaXMuX2NoaXBTZWxlY3Rpb25TdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKSx0aGlzLl9jaGlwU2VsZWN0aW9uU3Vic2NyaXB0aW9uPW51bGwpLHRoaXMuX2NoaXBSZW1vdmVTdWJzY3JpcHRpb24mJih0aGlzLl9jaGlwUmVtb3ZlU3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCksdGhpcy5fY2hpcFJlbW92ZVN1YnNjcmlwdGlvbj1udWxsKX1fbGlzdGVuVG9DaGlwc1NlbGVjdGlvbigpe3RoaXMuX2NoaXBTZWxlY3Rpb25TdWJzY3JpcHRpb249dGhpcy5jaGlwU2VsZWN0aW9uQ2hhbmdlcy5zdWJzY3JpYmUoZT0+e2Uuc291cmNlLnNlbGVjdGVkP3RoaXMuX3NlbGVjdGlvbk1vZGVsLnNlbGVjdChlLnNvdXJjZSk6dGhpcy5fc2VsZWN0aW9uTW9kZWwuZGVzZWxlY3QoZS5zb3VyY2UpLHRoaXMubXVsdGlwbGV8fHRoaXMuY2hpcHMuZm9yRWFjaChpPT57IXRoaXMuX3NlbGVjdGlvbk1vZGVsLmlzU2VsZWN0ZWQoaSkmJmkuc2VsZWN0ZWQmJmkuZGVzZWxlY3QoKX0pLGUuaXNVc2VySW5wdXQmJnRoaXMuX3Byb3BhZ2F0ZUNoYW5nZXMoKX0pfV9saXN0ZW5Ub0NoaXBzRm9jdXMoKXt0aGlzLl9jaGlwRm9jdXNTdWJzY3JpcHRpb249dGhpcy5jaGlwRm9jdXNDaGFuZ2VzLnN1YnNjcmliZShlPT57bGV0IGk9dGhpcy5jaGlwcy50b0FycmF5KCkuaW5kZXhPZihlLmNoaXApO3RoaXMuX2lzVmFsaWRJbmRleChpKSYmdGhpcy5fa2V5TWFuYWdlci51cGRhdGVBY3RpdmVJdGVtKGkpLHRoaXMuc3RhdGVDaGFuZ2VzLm5leHQoKX0pLHRoaXMuX2NoaXBCbHVyU3Vic2NyaXB0aW9uPXRoaXMuY2hpcEJsdXJDaGFuZ2VzLnN1YnNjcmliZSgoKT0+e3RoaXMuX2JsdXIoKSx0aGlzLnN0YXRlQ2hhbmdlcy5uZXh0KCl9KX1fbGlzdGVuVG9DaGlwc1JlbW92ZWQoKXt0aGlzLl9jaGlwUmVtb3ZlU3Vic2NyaXB0aW9uPXRoaXMuY2hpcFJlbW92ZUNoYW5nZXMuc3Vic2NyaWJlKGU9PntsZXQgaT1lLmNoaXAscj10aGlzLmNoaXBzLnRvQXJyYXkoKS5pbmRleE9mKGUuY2hpcCk7dGhpcy5faXNWYWxpZEluZGV4KHIpJiZpLl9oYXNGb2N1cyYmKHRoaXMuX2xhc3REZXN0cm95ZWRDaGlwSW5kZXg9cil9KX1fb3JpZ2luYXRlc0Zyb21DaGlwKGUpe2xldCBpPWUudGFyZ2V0O2Zvcig7aSYmaSE9PXRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudDspe2lmKGkuY2xhc3NMaXN0LmNvbnRhaW5zKCJtYXQtY2hpcCIpKXJldHVybiEwO2k9aS5wYXJlbnRFbGVtZW50fXJldHVybiExfV9oYXNGb2N1c2VkQ2hpcCgpe3JldHVybiB0aGlzLmNoaXBzJiZ0aGlzLmNoaXBzLnNvbWUoZT0+ZS5faGFzRm9jdXMpfV9zeW5jQ2hpcHNTdGF0ZSgpe3RoaXMuY2hpcHMmJnRoaXMuY2hpcHMuZm9yRWFjaChlPT57ZS5fY2hpcExpc3REaXNhYmxlZD10aGlzLl9kaXNhYmxlZCxlLl9jaGlwTGlzdE11bHRpcGxlPXRoaXMubXVsdGlwbGUsZS5jaGlwTGlzdFNlbGVjdGFibGU9dGhpcy5fc2VsZWN0YWJsZX0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKFJlKSxNKG5uKSxNKCRpLDgpLE0oTGgsOCksTShWaCw4KSxNKGNkKSxNKE5zLDEwKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LWNoaXAtbGlzdCJdXSxjb250ZW50UXVlcmllczpmdW5jdGlvbihlLGkscil7aWYoMSZlJiZFaShyLEpiLDUpLDImZSl7bGV0IG87TmUobz1MZSgpKSYmKGkuY2hpcHM9byl9fSxob3N0QXR0cnM6WzEsIm1hdC1jaGlwLWxpc3QiXSxob3N0VmFyczoxNCxob3N0QmluZGluZ3M6ZnVuY3Rpb24oZSxpKXsxJmUmJlAoImZvY3VzIixmdW5jdGlvbigpe3JldHVybiBpLmZvY3VzKCl9KSgiYmx1ciIsZnVuY3Rpb24oKXtyZXR1cm4gaS5fYmx1cigpfSkoImtleWRvd24iLGZ1bmN0aW9uKG8pe3JldHVybiBpLl9rZXlkb3duKG8pfSksMiZlJiYoX3MoImlkIixpLl91aWQpLHplKCJ0YWJpbmRleCIsaS5kaXNhYmxlZD9udWxsOmkuX3RhYkluZGV4KSgiYXJpYS1yZXF1aXJlZCIsaS5yb2xlP2kucmVxdWlyZWQ6bnVsbCkoImFyaWEtZGlzYWJsZWQiLGkuZGlzYWJsZWQudG9TdHJpbmcoKSkoImFyaWEtaW52YWxpZCIsaS5lcnJvclN0YXRlKSgiYXJpYS1tdWx0aXNlbGVjdGFibGUiLGkubXVsdGlwbGUpKCJyb2xlIixpLnJvbGUpKCJhcmlhLW9yaWVudGF0aW9uIixpLmFyaWFPcmllbnRhdGlvbiksZXQoIm1hdC1jaGlwLWxpc3QtZGlzYWJsZWQiLGkuZGlzYWJsZWQpKCJtYXQtY2hpcC1saXN0LWludmFsaWQiLGkuZXJyb3JTdGF0ZSkoIm1hdC1jaGlwLWxpc3QtcmVxdWlyZWQiLGkucmVxdWlyZWQpKX0saW5wdXRzOntyb2xlOiJyb2xlIix1c2VyQXJpYURlc2NyaWJlZEJ5OlsiYXJpYS1kZXNjcmliZWRieSIsInVzZXJBcmlhRGVzY3JpYmVkQnkiXSxlcnJvclN0YXRlTWF0Y2hlcjoiZXJyb3JTdGF0ZU1hdGNoZXIiLG11bHRpcGxlOiJtdWx0aXBsZSIsY29tcGFyZVdpdGg6ImNvbXBhcmVXaXRoIix2YWx1ZToidmFsdWUiLHJlcXVpcmVkOiJyZXF1aXJlZCIscGxhY2Vob2xkZXI6InBsYWNlaG9sZGVyIixkaXNhYmxlZDoiZGlzYWJsZWQiLGFyaWFPcmllbnRhdGlvbjpbImFyaWEtb3JpZW50YXRpb24iLCJhcmlhT3JpZW50YXRpb24iXSxzZWxlY3RhYmxlOiJzZWxlY3RhYmxlIix0YWJJbmRleDoidGFiSW5kZXgifSxvdXRwdXRzOntjaGFuZ2U6ImNoYW5nZSIsdmFsdWVDaGFuZ2U6InZhbHVlQ2hhbmdlIn0sZXhwb3J0QXM6WyJtYXRDaGlwTGlzdCJdLGZlYXR1cmVzOlskdChbe3Byb3ZpZGU6a2gsdXNlRXhpc3Rpbmc6bn1dKSx0dF0sbmdDb250ZW50U2VsZWN0b3JzOnNldCxkZWNsczoyLHZhcnM6MCxjb25zdHM6W1sxLCJtYXQtY2hpcC1saXN0LXdyYXBwZXIiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJih4aSgpLF8oMCwiZGl2IiwwKSxWbigxKSx2KCkpfSxzdHlsZXM6WycubWF0LWNoaXB7cG9zaXRpb246cmVsYXRpdmU7Ym94LXNpemluZzpib3JkZXItYm94Oy13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjpyZ2JhKDAsMCwwLDApO2JvcmRlcjpub25lOy13ZWJraXQtYXBwZWFyYW5jZTpub25lOy1tb3otYXBwZWFyYW5jZTpub25lfS5tYXQtY2hpcDo6YmVmb3Jle21hcmdpbjpjYWxjKGNhbGModmFyKC0tbWF0LWZvY3VzLWluZGljYXRvci1ib3JkZXItd2lkdGgsIDNweCkgKyAycHgpICogLTEpfS5tYXQtc3RhbmRhcmQtY2hpcHt0cmFuc2l0aW9uOmJveC1zaGFkb3cgMjgwbXMgY3ViaWMtYmV6aWVyKDAuNCwgMCwgMC4yLCAxKTtkaXNwbGF5OmlubGluZS1mbGV4O3BhZGRpbmc6N3B4IDEycHg7Ym9yZGVyLXJhZGl1czoxNnB4O2FsaWduLWl0ZW1zOmNlbnRlcjtjdXJzb3I6ZGVmYXVsdDttaW4taGVpZ2h0OjMycHg7aGVpZ2h0OjFweH0ubWF0LXN0YW5kYXJkLWNoaXAuX21hdC1hbmltYXRpb24tbm9vcGFibGV7dHJhbnNpdGlvbjpub25lICFpbXBvcnRhbnQ7YW5pbWF0aW9uOm5vbmUgIWltcG9ydGFudH0ubWF0LXN0YW5kYXJkLWNoaXAgLm1hdC1jaGlwLXJlbW92ZXtib3JkZXI6bm9uZTstd2Via2l0LWFwcGVhcmFuY2U6bm9uZTstbW96LWFwcGVhcmFuY2U6bm9uZTtwYWRkaW5nOjA7YmFja2dyb3VuZDpub25lfS5tYXQtc3RhbmRhcmQtY2hpcCAubWF0LWNoaXAtcmVtb3ZlLm1hdC1pY29uLC5tYXQtc3RhbmRhcmQtY2hpcCAubWF0LWNoaXAtcmVtb3ZlIC5tYXQtaWNvbnt3aWR0aDoxOHB4O2hlaWdodDoxOHB4O2ZvbnQtc2l6ZToxOHB4fS5tYXQtc3RhbmRhcmQtY2hpcDo6YWZ0ZXJ7dG9wOjA7bGVmdDowO3JpZ2h0OjA7Ym90dG9tOjA7cG9zaXRpb246YWJzb2x1dGU7Ym9yZGVyLXJhZGl1czppbmhlcml0O29wYWNpdHk6MDtjb250ZW50OiIiO3BvaW50ZXItZXZlbnRzOm5vbmU7dHJhbnNpdGlvbjpvcGFjaXR5IDIwMG1zIGN1YmljLWJlemllcigwLjM1LCAwLCAwLjI1LCAxKX0ubWF0LXN0YW5kYXJkLWNoaXA6aG92ZXI6OmFmdGVye29wYWNpdHk6LjEyfS5tYXQtc3RhbmRhcmQtY2hpcDpmb2N1c3tvdXRsaW5lOm5vbmV9Lm1hdC1zdGFuZGFyZC1jaGlwOmZvY3VzOjphZnRlcntvcGFjaXR5Oi4xNn0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtc3RhbmRhcmQtY2hpcHtvdXRsaW5lOnNvbGlkIDFweH0uY2RrLWhpZ2gtY29udHJhc3QtYWN0aXZlIC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC1zZWxlY3RlZHtvdXRsaW5lLXdpZHRoOjNweH0ubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtZGlzYWJsZWQ6OmFmdGVye29wYWNpdHk6MH0ubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtZGlzYWJsZWQgLm1hdC1jaGlwLXJlbW92ZSwubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtZGlzYWJsZWQgLm1hdC1jaGlwLXRyYWlsaW5nLWljb257Y3Vyc29yOmRlZmF1bHR9Lm1hdC1zdGFuZGFyZC1jaGlwLm1hdC1jaGlwLXdpdGgtdHJhaWxpbmctaWNvbi5tYXQtY2hpcC13aXRoLWF2YXRhciwubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtd2l0aC1hdmF0YXJ7cGFkZGluZy10b3A6MDtwYWRkaW5nLWJvdHRvbTowfS5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC13aXRoLXRyYWlsaW5nLWljb24ubWF0LWNoaXAtd2l0aC1hdmF0YXJ7cGFkZGluZy1yaWdodDo4cHg7cGFkZGluZy1sZWZ0OjB9W2Rpcj1ydGxdIC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC13aXRoLXRyYWlsaW5nLWljb24ubWF0LWNoaXAtd2l0aC1hdmF0YXJ7cGFkZGluZy1sZWZ0OjhweDtwYWRkaW5nLXJpZ2h0OjB9Lm1hdC1zdGFuZGFyZC1jaGlwLm1hdC1jaGlwLXdpdGgtdHJhaWxpbmctaWNvbntwYWRkaW5nLXRvcDo3cHg7cGFkZGluZy1ib3R0b206N3B4O3BhZGRpbmctcmlnaHQ6OHB4O3BhZGRpbmctbGVmdDoxMnB4fVtkaXI9cnRsXSAubWF0LXN0YW5kYXJkLWNoaXAubWF0LWNoaXAtd2l0aC10cmFpbGluZy1pY29ue3BhZGRpbmctbGVmdDo4cHg7cGFkZGluZy1yaWdodDoxMnB4fS5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC13aXRoLWF2YXRhcntwYWRkaW5nLWxlZnQ6MDtwYWRkaW5nLXJpZ2h0OjEycHh9W2Rpcj1ydGxdIC5tYXQtc3RhbmRhcmQtY2hpcC5tYXQtY2hpcC13aXRoLWF2YXRhcntwYWRkaW5nLXJpZ2h0OjA7cGFkZGluZy1sZWZ0OjEycHh9Lm1hdC1zdGFuZGFyZC1jaGlwIC5tYXQtY2hpcC1hdmF0YXJ7d2lkdGg6MjRweDtoZWlnaHQ6MjRweDttYXJnaW4tcmlnaHQ6OHB4O21hcmdpbi1sZWZ0OjRweH1bZGlyPXJ0bF0gLm1hdC1zdGFuZGFyZC1jaGlwIC5tYXQtY2hpcC1hdmF0YXJ7bWFyZ2luLWxlZnQ6OHB4O21hcmdpbi1yaWdodDo0cHh9Lm1hdC1zdGFuZGFyZC1jaGlwIC5tYXQtY2hpcC1yZW1vdmUsLm1hdC1zdGFuZGFyZC1jaGlwIC5tYXQtY2hpcC10cmFpbGluZy1pY29ue3dpZHRoOjE4cHg7aGVpZ2h0OjE4cHg7Y3Vyc29yOnBvaW50ZXJ9Lm1hdC1zdGFuZGFyZC1jaGlwIC5tYXQtY2hpcC1yZW1vdmUsLm1hdC1zdGFuZGFyZC1jaGlwIC5tYXQtY2hpcC10cmFpbGluZy1pY29ue21hcmdpbi1sZWZ0OjhweDttYXJnaW4tcmlnaHQ6MH1bZGlyPXJ0bF0gLm1hdC1zdGFuZGFyZC1jaGlwIC5tYXQtY2hpcC1yZW1vdmUsW2Rpcj1ydGxdIC5tYXQtc3RhbmRhcmQtY2hpcCAubWF0LWNoaXAtdHJhaWxpbmctaWNvbnttYXJnaW4tcmlnaHQ6OHB4O21hcmdpbi1sZWZ0OjB9Lm1hdC1jaGlwLXJpcHBsZXt0b3A6MDtsZWZ0OjA7cmlnaHQ6MDtib3R0b206MDtwb3NpdGlvbjphYnNvbHV0ZTtwb2ludGVyLWV2ZW50czpub25lO2JvcmRlci1yYWRpdXM6aW5oZXJpdDtvdmVyZmxvdzpoaWRkZW47dHJhbnNmb3JtOnRyYW5zbGF0ZVooMCl9Lm1hdC1jaGlwLWxpc3Qtd3JhcHBlcntkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246cm93O2ZsZXgtd3JhcDp3cmFwO2FsaWduLWl0ZW1zOmNlbnRlcjttYXJnaW46LTRweH0ubWF0LWNoaXAtbGlzdC13cmFwcGVyIGlucHV0Lm1hdC1pbnB1dC1lbGVtZW50LC5tYXQtY2hpcC1saXN0LXdyYXBwZXIgLm1hdC1zdGFuZGFyZC1jaGlwe21hcmdpbjo0cHh9Lm1hdC1jaGlwLWxpc3Qtc3RhY2tlZCAubWF0LWNoaXAtbGlzdC13cmFwcGVye2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjthbGlnbi1pdGVtczpmbGV4LXN0YXJ0fS5tYXQtY2hpcC1saXN0LXN0YWNrZWQgLm1hdC1jaGlwLWxpc3Qtd3JhcHBlciAubWF0LXN0YW5kYXJkLWNoaXB7d2lkdGg6MTAwJX0ubWF0LWNoaXAtYXZhdGFye2JvcmRlci1yYWRpdXM6NTAlO2p1c3RpZnktY29udGVudDpjZW50ZXI7YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleDtvdmVyZmxvdzpoaWRkZW47b2JqZWN0LWZpdDpjb3Zlcn1pbnB1dC5tYXQtY2hpcC1pbnB1dHt3aWR0aDoxNTBweDttYXJnaW46NHB4O2ZsZXg6MSAwIDE1MHB4fSddLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLCRiPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtwcm92aWRlcnM6W2NkLHtwcm92aWRlOmRldCx1c2VWYWx1ZTp7c2VwYXJhdG9yS2V5Q29kZXM6WzEzXX19XSxpbXBvcnRzOltsbl19KSxufSkoKSxmZXQ9ZnVuY3Rpb24obix0KXtyZXR1cm57ImVtYmVkZGluZy1zZWxlY3RlZCI6biwiZW1iZWRkaW5nLXVuc2VsZWN0ZWQiOnR9fTtmdW5jdGlvbiBtZXQobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJtYXQtaWNvbiIsNSksUCgiY2xpY2siLGZ1bmN0aW9uKCl7b2UoZSk7bGV0IHI9UygpO3JldHVybiBzZShyLm9uU2VsZWN0LmVtaXQoci5tZXRyaWMpKX0pLHYoKX1pZigyJm4pe2xldCBlPVMoKTt5KCJuZ0NsYXNzIixRcigxLGZldCxlLmVtYmVkZGluZ3NNZXRyaWM9PT1lLm1ldHJpYyxlLmVtYmVkZGluZ3NNZXRyaWMhPT1lLm1ldHJpYykpfX12YXIgX21lPWZ1bmN0aW9uKG4pe3JldHVybnt3aWR0aDpufX0sdm1lPWZ1bmN0aW9uKG4pe3JldHVybnsidmFsdWUtaW52YWxpZCI6bn19LHltZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5vblJlbW92ZT1uZXcgRyx0aGlzLm9uU2VsZWN0PW5ldyBHLHRoaXMub25GaWx0ZXJDaGFuZ2U9bmV3IEcsdGhpcy5mb2N1c01pbj0hMSx0aGlzLmZvY3VzTWF4PSExLHRoaXMubmdVbnN1YnNjcmliZT1uZXcga2V9bmdPbkluaXQoKXt0aGlzLm1pbkZvcm1Db250cm9sPW5ldyBCaCh0aGlzLmZpbHRlclZhbHVlcy5taW4sW0ZvLnJlcXVpcmVkLEZvLm1pbigtMSksRm8ubWF4KDEpLHRoaXMubWluVmFsdWVWYWxpZGF0b3IuYmluZCh0aGlzKV0pLHRoaXMubWF4Rm9ybUNvbnRyb2w9bmV3IEJoKHRoaXMuZmlsdGVyVmFsdWVzLm1heCxbRm8ucmVxdWlyZWQsRm8ubWluKC0xKSxGby5tYXgoMSksdGhpcy5tYXhWYWx1ZVZhbGlkYXRvci5iaW5kKHRoaXMpXSksdGhpcy5taW5Gb3JtQ29udHJvbC52YWx1ZUNoYW5nZXMucGlwZShzdCh0aGlzLm5nVW5zdWJzY3JpYmUpKS5zdWJzY3JpYmUoKCk9Pnt0aGlzLm1pbkZvcm1Db250cm9sLnZhbGlkJiZ0aGlzLm1heEZvcm1Db250cm9sLnZhbGlkJiZ0aGlzLm9uRmlsdGVyQ2hhbmdlLmVtaXQoe21pbjpwYXJzZUZsb2F0KHRoaXMubWluRm9ybUNvbnRyb2wudmFsdWUpLG1heDpwYXJzZUZsb2F0KHRoaXMubWF4Rm9ybUNvbnRyb2wudmFsdWUpfSl9KSx0aGlzLm1heEZvcm1Db250cm9sLnZhbHVlQ2hhbmdlcy5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSkpLnN1YnNjcmliZSgoKT0+e3RoaXMubWluRm9ybUNvbnRyb2wudmFsaWQmJnRoaXMubWF4Rm9ybUNvbnRyb2wudmFsaWQmJnRoaXMub25GaWx0ZXJDaGFuZ2UuZW1pdCh7bWluOnBhcnNlRmxvYXQodGhpcy5taW5Gb3JtQ29udHJvbC52YWx1ZSksbWF4OnBhcnNlRmxvYXQodGhpcy5tYXhGb3JtQ29udHJvbC52YWx1ZSl9KX0pfW5nT25DaGFuZ2VzKGUpe3RoaXMubWluRm9ybUNvbnRyb2wmJnRoaXMubWF4Rm9ybUNvbnRyb2wmJih0aGlzLm1pbkZvcm1Db250cm9sLnNldFZhbHVlKHRoaXMuZmlsdGVyVmFsdWVzLm1pbix7ZW1pdEV2ZW50OiExfSksdGhpcy5tYXhGb3JtQ29udHJvbC5zZXRWYWx1ZSh0aGlzLmZpbHRlclZhbHVlcy5tYXgse2VtaXRFdmVudDohMX0pKX1uZ09uRGVzdHJveSgpe3RoaXMubmdVbnN1YnNjcmliZS5uZXh0KCksdGhpcy5uZ1Vuc3Vic2NyaWJlLmNvbXBsZXRlKCl9bWluVmFsdWVWYWxpZGF0b3IoZSl7cmV0dXJuIHRoaXMubWF4Rm9ybUNvbnRyb2wmJiJOYU4iIT09ZS52YWx1ZT9pc05hTihwYXJzZUZsb2F0KGUudmFsdWUpKT97dmFsdWU6InRoZSBzdHJpbmcgeW91IGVudGVyZWQgaXMgbmVpdGhlciBOYU4gbm9yIGEgbnVtYmVyIn06cGFyc2VGbG9hdChlLnZhbHVlKT5wYXJzZUZsb2F0KHRoaXMubWF4Rm9ybUNvbnRyb2wudmFsdWUpP3t2YWx1ZToidGhlIG51bWJlciB5b3UgZW50ZXJlZCBpcyBsYXJnZXIgdGhhbiB0aGUgbWF4IHZhbHVlIn06bnVsbDpudWxsfW1heFZhbHVlVmFsaWRhdG9yKGUpe3JldHVybiB0aGlzLm1pbkZvcm1Db250cm9sPyJOYU4iPT09dGhpcy5taW5Gb3JtQ29udHJvbC52YWx1ZSYmIk5hTiI9PT1lLnZhbHVlP251bGw6aXNOYU4ocGFyc2VGbG9hdChlLnZhbHVlKSk/e3ZhbHVlOiJ0aGUgc3RyaW5nIHlvdSBlbnRlcmVkIGlzIG5laXRoZXIgTmFOIG5vciBhIG51bWJlciJ9OmUudmFsdWU8dGhpcy5taW5Gb3JtQ29udHJvbC52YWx1ZT97dmFsdWU6InRoZSBudW1iZXIgeW91IGVudGVyZWQgaXMgc21hbGxlciB0aGFuIHRoZSBtaW4gdmFsdWUifTpudWxsOm51bGx9Z2V0RXJyb3JEZXNjcmlwdGlvbihlKXtpZihlKXtsZXQgaT1PYmplY3Qua2V5cyhlKVswXTtyZXR1cm4icmVxdWlyZWQiPT09aT8ieW91IGRpZCBub3QgZW50ZXIgYW55dGhpbmciOiJtaW4iPT09aT8idGhlIG51bWJlciBtdXN0IGJlIGF0IGxlYXN0IC0xLjAiOiJtYXgiPT09aT8idGhlIG51bWJlciBpcyBiaWdnZXIgdGhhbiAxLjAiOmVbaV19cmV0dXJuIiJ9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1ldHJpYy1hcml0aG1ldGljLWVsZW1lbnQtY29tcG9uZW50Il1dLGlucHV0czp7bWV0cmljOiJtZXRyaWMiLGZpbHRlclZhbHVlczoiZmlsdGVyVmFsdWVzIixoYXNFbWJlZGRpbmdzRGF0YToiaGFzRW1iZWRkaW5nc0RhdGEiLGVtYmVkZGluZ3NNZXRyaWM6ImVtYmVkZGluZ3NNZXRyaWMifSxvdXRwdXRzOntvblJlbW92ZToib25SZW1vdmUiLG9uU2VsZWN0OiJvblNlbGVjdCIsb25GaWx0ZXJDaGFuZ2U6Im9uRmlsdGVyQ2hhbmdlIn0sZmVhdHVyZXM6W0Z0XSxkZWNsczoxMCx2YXJzOjIyLGNvbnN0czpbWzEsImZpbHRlci1jaGlwIiwzLCJyZW1vdmVkIl0sWyJjbGFzcyIsImVtYmVkZGluZ3MtYnV0dG9uIiwic3ZnSWNvbiIsImdyb3VwX3dvcmtfMjRweCIsMywibmdDbGFzcyIsImNsaWNrIiw0LCJuZ0lmIl0sWzEsIm1ldHJpYy1hcml0aG1ldGljLWVsZW1lbnQtcmFuZ2UiLDMsImtleWRvd24iXSxbIm1hdElucHV0IiwiIiwxLCJpbnB1dC1maWVsZCIsMywidmFsdWUiLCJtYXRUb29sdGlwIiwibWF0VG9vbHRpcERpc2FibGVkIiwibmdTdHlsZSIsIm5nQ2xhc3MiLCJmb3JtQ29udHJvbCIsImZvY3VzIiwiZm9jdXNvdXQiXSxbIm1hdENoaXBSZW1vdmUiLCIiLCJzdmdJY29uIiwiY2FuY2VsXzI0cHgiXSxbInN2Z0ljb24iLCJncm91cF93b3JrXzI0cHgiLDEsImVtYmVkZGluZ3MtYnV0dG9uIiwzLCJuZ0NsYXNzIiwiY2xpY2siXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1hdC1jaGlwIiwwKSxQKCJyZW1vdmVkIixmdW5jdGlvbigpe3JldHVybiBpLm9uUmVtb3ZlLmVtaXQoaS5tZXRyaWMpfSksRSgxLG1ldCwxLDQsIm1hdC1pY29uIiwxKSxBKDIpLF8oMywiZGl2IiwyKSxQKCJrZXlkb3duIixmdW5jdGlvbihvKXtyZXR1cm4gby5zdG9wUHJvcGFnYXRpb24oKX0pLEEoNCwiIFsgIiksXyg1LCJpbnB1dCIsMyksUCgiZm9jdXMiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuZm9jdXNNaW49ITB9KSgiZm9jdXNvdXQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuZm9jdXNNaW49ITF9KSx2KCksQSg2LCIgOyAiKSxfKDcsImlucHV0IiwzKSxQKCJmb2N1cyIsZnVuY3Rpb24oKXtyZXR1cm4gaS5mb2N1c01heD0hMH0pKCJmb2N1c291dCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5mb2N1c01heD0hMX0pLHYoKSxBKDgsIiBdICIpLHYoKSxPKDksIm1hdC1pY29uIiw0KSx2KCkpLDImZSYmKEMoMSkseSgibmdJZiIsaS5oYXNFbWJlZGRpbmdzRGF0YSksQygxKSxqZSgiICIsaS5tZXRyaWMsIiAiKSxDKDMpLHkoInZhbHVlIixpLmZpbHRlclZhbHVlcy5taW4pKCJtYXRUb29sdGlwIixpLmdldEVycm9yRGVzY3JpcHRpb24oaS5taW5Gb3JtQ29udHJvbC5lcnJvcnMpKSgibWF0VG9vbHRpcERpc2FibGVkIiwhaS5taW5Gb3JtQ29udHJvbC5pbnZhbGlkKSgibmdTdHlsZSIsT24oMTQsX21lLGkuZm9jdXNNaW4/IjEwMHB4IjppLm1pbkZvcm1Db250cm9sLnZhbHVlLnRvU3RyaW5nKCkubGVuZ3RoKyJjaCIpKSgibmdDbGFzcyIsT24oMTYsdm1lLCFpLm1pbkZvcm1Db250cm9sLnZhbGlkKSkoImZvcm1Db250cm9sIixpLm1pbkZvcm1Db250cm9sKSxDKDIpLHkoInZhbHVlIixpLmZpbHRlclZhbHVlcy5tYXgpKCJtYXRUb29sdGlwIixpLmdldEVycm9yRGVzY3JpcHRpb24oaS5tYXhGb3JtQ29udHJvbC5lcnJvcnMpKSgibWF0VG9vbHRpcERpc2FibGVkIiwhaS5tYXhGb3JtQ29udHJvbC5pbnZhbGlkKSgibmdTdHlsZSIsT24oMTgsX21lLGkuZm9jdXNNYXg/IjEwMHB4IjppLm1heEZvcm1Db250cm9sLnZhbHVlLnRvU3RyaW5nKCkubGVuZ3RoKyJjaCIpKSgibmdDbGFzcyIsT24oMjAsdm1lLCFpLm1heEZvcm1Db250cm9sLnZhbGlkKSkoImZvcm1Db250cm9sIixpLm1heEZvcm1Db250cm9sKSl9LGRlcGVuZGVuY2llczpbRm4sQmUsenUsQnYsVjIsbXcsR3QsSmIsbW1lLFhrXSxzdHlsZXM6WyIuZmlsdGVyLWNoaXBbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtkaXNwbGF5OmZsZXg7bWFyZ2luLWxlZnQ6NXB4fS5tZXRyaWMtYXJpdGhtZXRpYy1lbGVtZW50LXJhbmdlW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7YmFja2dyb3VuZC1jb2xvcjojZmZmO2ZvbnQtc2l6ZTouOGVtO2hlaWdodDozMHB4O2p1c3RpZnktY29udGVudDpjZW50ZXI7bGluZS1oZWlnaHQ6MzBweDtwYWRkaW5nOjAgNXB4O21hcmdpbi1sZWZ0OjVweH0uaW5wdXQtZmllbGRbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwwKTtib3JkZXI6bm9uZTtmb250LWZhbWlseTptb25vc3BhY2U7Zm9udC1zaXplOjEuMWVtO3RyYW5zaXRpb246d2lkdGggMXN9LmlucHV0LWZpZWxkW19uZ2NvbnRlbnQtJUNPTVAlXTpmb2N1c3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjEyKTtib3JkZXI6bm9uZTtvdXRsaW5lOm5vbmV9LnZhbHVlLWludmFsaWRbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiNmNDQzMzZ9LmVtYmVkZGluZy1zZWxlY3RlZFtfbmdjb250ZW50LSVDT01QJV17Y29sb3I6I2Y1N2MwMDtvcGFjaXR5OjF9LmVtYmVkZGluZy11bnNlbGVjdGVkW19uZ2NvbnRlbnQtJUNPTVAlXXtvcGFjaXR5Oi40fS5lbWJlZGRpbmdzLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17d2lkdGg6MThweDtoZWlnaHQ6MThweDttYXJnaW4tcmlnaHQ6OHB4O2N1cnNvcjpwb2ludGVyfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksYm1lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuZmlsdGVyVmFsdWVzJD10aGlzLnN0b3JlLnBpcGUodnQoSWwpKS5waXBlKEwoaT0+e2xldCByPWlbdGhpcy5tZXRyaWNdO3JldHVybiByP3ttaW46ci5pbmNsdWRlTmFOPyJOYU4iOnRoaXMucm91bmRUb1RocmVlRGVjaW1hbFBvaW50cyhyLm1pbiksbWF4OnIubWF4PHIubWluPyJOYU4iOnRoaXMucm91bmRUb1RocmVlRGVjaW1hbFBvaW50cyhyLm1heCl9OnttaW46LTEsbWF4OjF9fSkpLHRoaXMuaGFzRW1iZWRkaW5nc0RhdGEkPXRoaXMuc3RvcmUucGlwZSh2dChCRikpLnBpcGUoTChpPT52b2lkIDAhPT1pKSksdGhpcy5lbWJlZGRpbmdzTWV0cmljJD10aGlzLnN0b3JlLnBpcGUodnQoYW1lKSl9cmVtb3ZlKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goR2Ioe21ldHJpYzplfSkpfXNlbGVjdChlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKEZGKHttZXRyaWM6ZX0pKX1maWx0ZXJDaGFuZ2UoZSl7bGV0IGk9aXNOYU4oZS5taW4pPy0xOmUubWluLHI9aXNOYU4oZS5tYXgpPy0yOmUubWF4LG89aXNOYU4oZS5taW4pO3RoaXMuc3RvcmUuZGlzcGF0Y2goV2Ioe21ldHJpYzp0aGlzLm1ldHJpYyxtYXg6cixtaW46aSxpbmNsdWRlTmFOOm99KSl9cm91bmRUb1RocmVlRGVjaW1hbFBvaW50cyhlKXtyZXR1cm4gTWF0aC5yb3VuZCgxZTMqKGUrTnVtYmVyLkVQU0lMT04pKS8xZTN9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJucG1pLW1ldHJpYy1hcml0aG1ldGljLWVsZW1lbnQiXV0saW5wdXRzOnttZXRyaWM6Im1ldHJpYyJ9LGRlY2xzOjQsdmFyczoxMCxjb25zdHM6W1szLCJtZXRyaWMiLCJmaWx0ZXJWYWx1ZXMiLCJoYXNFbWJlZGRpbmdzRGF0YSIsImVtYmVkZGluZ3NNZXRyaWMiLCJvblJlbW92ZSIsIm9uU2VsZWN0Iiwib25GaWx0ZXJDaGFuZ2UiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1ldHJpYy1hcml0aG1ldGljLWVsZW1lbnQtY29tcG9uZW50IiwwKSxQKCJvblJlbW92ZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkucmVtb3ZlKG8pfSkoIm9uU2VsZWN0IixmdW5jdGlvbihvKXtyZXR1cm4gaS5zZWxlY3Qobyl9KSgib25GaWx0ZXJDaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLmZpbHRlckNoYW5nZShvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLHYoKSksMiZlJiZ5KCJtZXRyaWMiLGkubWV0cmljKSgiZmlsdGVyVmFsdWVzIixVKDEsNCxpLmZpbHRlclZhbHVlcyQpKSgiaGFzRW1iZWRkaW5nc0RhdGEiLFUoMiw2LGkuaGFzRW1iZWRkaW5nc0RhdGEkKSkoImVtYmVkZGluZ3NNZXRyaWMiLFUoMyw4LGkuZW1iZWRkaW5nc01ldHJpYyQpKX0sZGVwZW5kZW5jaWVzOlt5bWUsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLHhtZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5PcGVyYXRvcj1zMH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibnBtaS1tZXRyaWMtYXJpdGhtZXRpYy1vcGVyYXRvciJdXSxpbnB1dHM6e29wZXJhdG9yOiJvcGVyYXRvciJ9LGRlY2xzOjIsdmFyczoxLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJtYXQtY2hpcCIpLEEoMSksdigpKSwyJmUmJihDKDEpLGplKCIgIixpLm9wZXJhdG9yPT09aS5PcGVyYXRvci5BTkQ/IiYiOiIiLCIgIikpfSxkZXBlbmRlbmNpZXM6W0piXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiB5ZXQobix0KXsxJm4mJk8oMCwibnBtaS1tZXRyaWMtYXJpdGhtZXRpYy1lbGVtZW50Iiw0KSwyJm4mJnkoIm1ldHJpYyIsUygpLiRpbXBsaWNpdC5tZXRyaWMpfWZ1bmN0aW9uIGJldChuLHQpezEmbiYmTygwLCJucG1pLW1ldHJpYy1hcml0aG1ldGljLW9wZXJhdG9yIiw1KSwyJm4mJnkoIm9wZXJhdG9yIixTKCkuJGltcGxpY2l0Lm9wZXJhdG9yKX1mdW5jdGlvbiB4ZXQobix0KXtpZigxJm4mJihfKDAsImRpdiIpLEUoMSx5ZXQsMSwxLCJucG1pLW1ldHJpYy1hcml0aG1ldGljLWVsZW1lbnQiLDIpLEUoMixiZXQsMSwxLCJucG1pLW1ldHJpYy1hcml0aG1ldGljLW9wZXJhdG9yIiwzKSx2KCkpLDImbil7bGV0IGU9dC4kaW1wbGljaXQsaT1TKCk7QygxKSx5KCJuZ0lmIixlLmtpbmQ9PT1pLkFyaXRobWV0aWNLaW5kLk1FVFJJQyksQygxKSx5KCJuZ0lmIixlLmtpbmQ9PT1pLkFyaXRobWV0aWNLaW5kLk9QRVJBVE9SKX19dmFyIENtZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5Bcml0aG1ldGljS2luZD1tdX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWV0cmljLWFyaXRobWV0aWMtY29tcG9uZW50Il1dLGlucHV0czp7bWV0cmljQXJpdGhtZXRpYzoibWV0cmljQXJpdGhtZXRpYyJ9LGRlY2xzOjIsdmFyczoyLGNvbnN0czpbWzMsInNlbGVjdGFibGUiXSxbNCwibmdGb3IiLCJuZ0Zvck9mIl0sWzMsIm1ldHJpYyIsNCwibmdJZiJdLFszLCJvcGVyYXRvciIsNCwibmdJZiJdLFszLCJtZXRyaWMiXSxbMywib3BlcmF0b3IiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1hdC1jaGlwLWxpc3QiLDApLEUoMSx4ZXQsMywyLCJkaXYiLDEpLHYoKSksMiZlJiYoeSgic2VsZWN0YWJsZSIsITEpLEMoMSkseSgibmdGb3JPZiIsaS5tZXRyaWNBcml0aG1ldGljKSl9LGRlcGVuZGVuY2llczpbZG4sQmUsZ21lLGJtZSx4bWVdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpyb3c7ZmxleC13cmFwOndyYXB9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxNbWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5tZXRyaWNBcml0aG1ldGljJD10aGlzLnN0b3JlLnBpcGUodnQoSEYpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktbWV0cmljLWFyaXRobWV0aWMiXV0sZGVjbHM6Mix2YXJzOjMsY29uc3RzOltbMywibWV0cmljQXJpdGhtZXRpYyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKE8oMCwibWV0cmljLWFyaXRobWV0aWMtY29tcG9uZW50IiwwKSxCKDEsImFzeW5jIikpLDImZSYmeSgibWV0cmljQXJpdGhtZXRpYyIsVSgxLDEsaS5tZXRyaWNBcml0aG1ldGljJCkpfSxkZXBlbmRlbmNpZXM6W0NtZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gd21lKG4sdCxlKXtsZXQgaT1bW3QsLi4uZV1dO2lmKCFlLmxlbmd0aHx8IW4ubGVuZ3RoKXJldHVybiJkYXRhOnRleHQvY3N2O2NoYXJzZXQ9dXRmLTgsIitpLm1hcChhPT5hLmpvaW4oIiwiKSkuam9pbigiXG4iKTtsZXQgcj1lLm1hcChzPT5TcyhzKSk7Zm9yKGxldFtzLGFdb2Ygbil7bGV0IGw9YS5maWx0ZXIoYz0+Yy5ydW49PT10KTtpZihsLmxlbmd0aCl7bGV0IGM9W3NdO2ZvcihsZXQgdSBvZiByKXtsZXQgZD1sLmZpbmQocD0+cC5tZXRyaWM9PT11KTtjLnB1c2godm9pZCAwPT09ZD8ibnVsbCI6YCR7ZC5uUE1JVmFsdWV9YCl9aS5wdXNoKGMpfX1yZXR1cm4iZGF0YTp0ZXh0L2NzdjtjaGFyc2V0PXV0Zi04LCIraS5tYXAocz0+cy5qb2luKCIsIikpLmpvaW4oIlxuIil9dmFyIHdldD1mdW5jdGlvbihuKXtyZXR1cm57ImFjdGl2ZS1idXR0b24iOm59fSxTbWU9KCgpPT57Y2xhc3Mgbntkb3dubG9hZFJlc3VsdHMoKXtmb3IobGV0IGUgb2YgdGhpcy5ydW5zKXtsZXQgaT13bWUodGhpcy5mbGFnZ2VkRGF0YSxlLHRoaXMubWV0cmljcykscj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJhIik7ci5zZXRBdHRyaWJ1dGUoImhyZWYiLGkpLHIuc2V0QXR0cmlidXRlKCJkb3dubG9hZCIsYHJlcG9ydF8ke2V9LmNzdmApLHIuY2xpY2soKX19fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInJlc3VsdHMtZG93bmxvYWQtY29tcG9uZW50Il1dLGlucHV0czp7bnVtRmxhZ2dlZEFubm90YXRpb25zOiJudW1GbGFnZ2VkQW5ub3RhdGlvbnMiLHJ1bnM6InJ1bnMiLGZsYWdnZWREYXRhOiJmbGFnZ2VkRGF0YSIsbWV0cmljczoibWV0cmljcyJ9LGRlY2xzOjQsdmFyczo1LGNvbnN0czpbWyJtYXQtc3Ryb2tlZC1idXR0b24iLCIiLCJ0aXRsZSIsIkV4cG9ydCBDU1YgcmVwb3J0cyBvZiBhbGwgZmxhZ2dlZCBhbm5vdGF0aW9ucy4gV2lsbCBnZW5lcmF0ZSBvbmUgQ1NWIHBlciBhY3RpdmUgcnVuLiIsMywiZGlzYWJsZWQiLCJuZ0NsYXNzIiwiY2xpY2siXSxbMSwiYnV0dG9uLWNvbnRlbnRzIl0sWyJzdmdJY29uIiwiZ2V0X2FwcF8yNHB4Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJidXR0b24iLDApLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLmRvd25sb2FkUmVzdWx0cygpfSksXygxLCJzcGFuIiwxKSxPKDIsIm1hdC1pY29uIiwyKSxBKDMpLHYoKSgpKSwyJmUmJih5KCJkaXNhYmxlZCIsMD09PWkubnVtRmxhZ2dlZEFubm90YXRpb25zKSgibmdDbGFzcyIsT24oMyx3ZXQsaS5udW1GbGFnZ2VkQW5ub3RhdGlvbnM+MCkpLEMoMyksamUoIiBGbGFnZ2VkIFJvd3MgKCIsaS5udW1GbGFnZ2VkQW5ub3RhdGlvbnMsIikgIikpfSxkZXBlbmRlbmNpZXM6W0ZuLEd0LF9uXSxzdHlsZXM6WyIuYWN0aXZlLWJ1dHRvbltfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZmY5ODAwO2JvcmRlcjoxcHggc29saWQgI2ViZWJlYjtjb2xvcjojZmZmfS5idXR0b24tY29udGVudHNbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtkaXNwbGF5OmZsZXg7dGV4dC10cmFuc2Zvcm06dXBwZXJjYXNlfW1hdC1pY29uW19uZ2NvbnRlbnQtJUNPTVAlXXttYXJnaW4tcmlnaHQ6NnB4fSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksRW1lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuZmxhZ2dlZEFubm90YXRpb25zJD10aGlzLnN0b3JlLnNlbGVjdChWRiksdGhpcy5udW1GbGFnZ2VkQW5ub3RhdGlvbnMkPXRoaXMuZmxhZ2dlZEFubm90YXRpb25zJC5waXBlKEwoaT0+aS5sZW5ndGgpKSx0aGlzLmFjdGl2ZVJ1bnMkPXRoaXMuc3RvcmUuc2VsZWN0KG9vKS5waXBlKEwoaT0+aT9BcnJheS5mcm9tKGkuZW50cmllcygpKS5maWx0ZXIocj0+clsxXSkubWFwKHI9PnJbMF0pOltdKSksdGhpcy5mbGFnZ2VkRGF0YSQ9THQoW3RoaXMuc3RvcmUuc2VsZWN0KFBmKSx0aGlzLmZsYWdnZWRBbm5vdGF0aW9ucyRdKS5waXBlKEwoKFtpLHJdKT0+e2xldCBvPW5ldyBTZXQocik7cmV0dXJuIE9iamVjdC5lbnRyaWVzKGkpLmZpbHRlcihhPT5vLmhhcyhhWzBdKSl9KSksdGhpcy5tZXRyaWNzJD1MdChbdGhpcy5zdG9yZS5zZWxlY3QoUmYpLHRoaXMuYWN0aXZlUnVucyQsdGhpcy5zdG9yZS5zZWxlY3QoSWwpXSkucGlwZShMKChbaSxyLG9dKT0+e2xldCBzPU9iamVjdC5rZXlzKG8pO2ZvcihsZXQgYSBvZiByKWlbYV0mJihzPXMuY29uY2F0KGlbYV0uZmlsdGVyKGw9PlZiKGwpKSkpO3JldHVybiBzPVsuLi5uZXcgU2V0KHMpXSxzfSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibnBtaS1yZXN1bHRzLWRvd25sb2FkIl1dLGRlY2xzOjUsdmFyczoxMixjb25zdHM6W1szLCJudW1GbGFnZ2VkQW5ub3RhdGlvbnMiLCJydW5zIiwiZmxhZ2dlZERhdGEiLCJtZXRyaWNzIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJyZXN1bHRzLWRvd25sb2FkLWNvbXBvbmVudCIsMCksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIiksQig0LCJhc3luYyIpKSwyJmUmJnkoIm51bUZsYWdnZWRBbm5vdGF0aW9ucyIsVSgxLDQsaS5udW1GbGFnZ2VkQW5ub3RhdGlvbnMkKSkoInJ1bnMiLFUoMiw2LGkuYWN0aXZlUnVucyQpKSgiZmxhZ2dlZERhdGEiLFUoMyw4LGkuZmxhZ2dlZERhdGEkKSkoIm1ldHJpY3MiLFUoNCwxMCxpLm1ldHJpY3MkKSl9LGRlcGVuZGVuY2llczpbU21lLEdlXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxXRj0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJucG1pLWRhdGEtc2VsZWN0aW9uIl1dLGRlY2xzOjQsdmFyczowLGNvbnN0czpbWzEsImRhdGEtc2VsZWN0aW9uIl0sWzEsIm1ldHJpY3Mtc2VsZWN0b3IiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCksTygxLCJucG1pLW1ldHJpYy1zZWFyY2giLDEpKDIsIm5wbWktcmVzdWx0cy1kb3dubG9hZCIpLHYoKSxPKDMsIm5wbWktbWV0cmljLWFyaXRobWV0aWMiKSl9LGRlcGVuZGVuY2llczpbaG1lLE1tZSxFbWVdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17ZGlzcGxheTpmbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7Ym9yZGVyOjFweCBzb2xpZCAjZWJlYmViO3BhZGRpbmc6MTBweCAyMHB4fS5kYXRhLXNlbGVjdGlvbltfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcn0ubWV0cmljcy1zZWxlY3Rvcltfbmdjb250ZW50LSVDT01QJV17ZmxleDoxIDF9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiBxRihuLHQsZSl7aWYoZSlyZXR1cm4gbjtsZXQgaT17Li4ubn07cmV0dXJuIHQuZm9yRWFjaChyPT5kZWxldGUgaVtyXSksaX12YXIgQWV0PVsiY2hhcnQiXSxJbWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMub25SZW1vdmU9bmV3IEcsdGhpcy5vblVwZGF0ZUZpbHRlcj1uZXcgRyx0aGlzLmhlaWdodD0zMDAsdGhpcy5jaGFydFdpZHRoPTAsdGhpcy5jaGFydEhlaWdodD0wLHRoaXMuZHJhd0hlaWdodD0wLHRoaXMuZHJhd1dpZHRoPTAsdGhpcy5tYXJnaW49e3RvcDoyMCxyaWdodDoxMCxib3R0b206MjAsbGVmdDoxMH0sdGhpcy5kcmF3TWFyZ2luPXt0b3A6MCxyaWdodDowLGJvdHRvbToyMCxsZWZ0OjIwfSx0aGlzLmJydXNoPWZ1bmN0aW9uKG4pe3ZhciBhLHQ9VFVlLGU9RVVlLGk9RFVlLHI9ITAsbz1Hdygic3RhcnQiLCJicnVzaCIsImVuZCIpLHM9NjtmdW5jdGlvbiBsKHgpe3ZhciBnPXgucHJvcGVydHkoIl9fYnJ1c2giLG0pLnNlbGVjdEFsbCgiLm92ZXJsYXkiKS5kYXRhKFtvUygib3ZlcmxheSIpXSk7Zy5lbnRlcigpLmFwcGVuZCgicmVjdCIpLmF0dHIoImNsYXNzIiwib3ZlcmxheSIpLmF0dHIoInBvaW50ZXItZXZlbnRzIiwiYWxsIikuYXR0cigiY3Vyc29yIixtcC5vdmVybGF5KS5tZXJnZShnKS5lYWNoKGZ1bmN0aW9uKCl7dmFyIEQ9YnoodGhpcykuZXh0ZW50O2JvKHRoaXMpLmF0dHIoIngiLERbMF1bMF0pLmF0dHIoInkiLERbMF1bMV0pLmF0dHIoIndpZHRoIixEWzFdWzBdLURbMF1bMF0pLmF0dHIoImhlaWdodCIsRFsxXVsxXS1EWzBdWzFdKX0pLHguc2VsZWN0QWxsKCIuc2VsZWN0aW9uIikuZGF0YShbb1MoInNlbGVjdGlvbiIpXSkuZW50ZXIoKS5hcHBlbmQoInJlY3QiKS5hdHRyKCJjbGFzcyIsInNlbGVjdGlvbiIpLmF0dHIoImN1cnNvciIsbXAuc2VsZWN0aW9uKS5hdHRyKCJmaWxsIiwiIzc3NyIpLmF0dHIoImZpbGwtb3BhY2l0eSIsLjMpLmF0dHIoInN0cm9rZSIsIiNmZmYiKS5hdHRyKCJzaGFwZS1yZW5kZXJpbmciLCJjcmlzcEVkZ2VzIik7dmFyIGI9eC5zZWxlY3RBbGwoIi5oYW5kbGUiKS5kYXRhKG4uaGFuZGxlcyxmdW5jdGlvbihEKXtyZXR1cm4gRC50eXBlfSk7Yi5leGl0KCkucmVtb3ZlKCksYi5lbnRlcigpLmFwcGVuZCgicmVjdCIpLmF0dHIoImNsYXNzIixmdW5jdGlvbihEKXtyZXR1cm4iaGFuZGxlIGhhbmRsZS0tIitELnR5cGV9KS5hdHRyKCJjdXJzb3IiLGZ1bmN0aW9uKEQpe3JldHVybiBtcFtELnR5cGVdfSkseC5lYWNoKGMpLmF0dHIoImZpbGwiLCJub25lIikuYXR0cigicG9pbnRlci1ldmVudHMiLCJhbGwiKS5vbigibW91c2Vkb3duLmJydXNoIixwKS5maWx0ZXIoaSkub24oInRvdWNoc3RhcnQuYnJ1c2giLHApLm9uKCJ0b3VjaG1vdmUuYnJ1c2giLGgpLm9uKCJ0b3VjaGVuZC5icnVzaCB0b3VjaGNhbmNlbC5icnVzaCIsZikuc3R5bGUoInRvdWNoLWFjdGlvbiIsIm5vbmUiKS5zdHlsZSgiLXdlYmtpdC10YXAtaGlnaGxpZ2h0LWNvbG9yIiwicmdiYSgwLDAsMCwwKSIpfWZ1bmN0aW9uIGMoKXt2YXIgeD1ibyh0aGlzKSxnPWJ6KHRoaXMpLnNlbGVjdGlvbjtnPyh4LnNlbGVjdEFsbCgiLnNlbGVjdGlvbiIpLnN0eWxlKCJkaXNwbGF5IixudWxsKS5hdHRyKCJ4IixnWzBdWzBdKS5hdHRyKCJ5IixnWzBdWzFdKS5hdHRyKCJ3aWR0aCIsZ1sxXVswXS1nWzBdWzBdKS5hdHRyKCJoZWlnaHQiLGdbMV1bMV0tZ1swXVsxXSkseC5zZWxlY3RBbGwoIi5oYW5kbGUiKS5zdHlsZSgiZGlzcGxheSIsbnVsbCkuYXR0cigieCIsZnVuY3Rpb24oYil7cmV0dXJuImUiPT09Yi50eXBlW2IudHlwZS5sZW5ndGgtMV0/Z1sxXVswXS1zLzI6Z1swXVswXS1zLzJ9KS5hdHRyKCJ5IixmdW5jdGlvbihiKXtyZXR1cm4icyI9PT1iLnR5cGVbMF0/Z1sxXVsxXS1zLzI6Z1swXVsxXS1zLzJ9KS5hdHRyKCJ3aWR0aCIsZnVuY3Rpb24oYil7cmV0dXJuIm4iPT09Yi50eXBlfHwicyI9PT1iLnR5cGU/Z1sxXVswXS1nWzBdWzBdK3M6c30pLmF0dHIoImhlaWdodCIsZnVuY3Rpb24oYil7cmV0dXJuImUiPT09Yi50eXBlfHwidyI9PT1iLnR5cGU/Z1sxXVsxXS1nWzBdWzFdK3M6c30pKTp4LnNlbGVjdEFsbCgiLnNlbGVjdGlvbiwuaGFuZGxlIikuc3R5bGUoImRpc3BsYXkiLCJub25lIikuYXR0cigieCIsbnVsbCkuYXR0cigieSIsbnVsbCkuYXR0cigid2lkdGgiLG51bGwpLmF0dHIoImhlaWdodCIsbnVsbCl9ZnVuY3Rpb24gdSh4LGcsYil7dmFyIEQ9eC5fX2JydXNoLmVtaXR0ZXI7cmV0dXJuIUR8fGImJkQuY2xlYW4/bmV3IGQoeCxnLGIpOkR9ZnVuY3Rpb24gZCh4LGcsYil7dGhpcy50aGF0PXgsdGhpcy5hcmdzPWcsdGhpcy5zdGF0ZT14Ll9fYnJ1c2gsdGhpcy5hY3RpdmU9MCx0aGlzLmNsZWFuPWJ9ZnVuY3Rpb24gcCgpe2lmKCghYXx8c2kudG91Y2hlcykmJmUuYXBwbHkodGhpcyxhcmd1bWVudHMpKXt2YXIgdWUsaGUsRixxLGRlLFksbGUsSWUsbnQsVWUsQWUseD10aGlzLGc9c2kudGFyZ2V0Ll9fZGF0YV9fLnR5cGUsYj0ic2VsZWN0aW9uIj09PShyJiZzaS5tZXRhS2V5P2c9Im92ZXJsYXkiOmcpP3BsZTpyJiZzaS5hbHRLZXk/TXk6Q3ksRD1uPT09JFI/bnVsbDp3VWVbZ10sVD1uPT09eXo/bnVsbDpTVWVbZ10saz1ieih4KSxaPWsuZXh0ZW50LHo9ay5zZWxlY3Rpb24sZmU9WlswXVswXSx3PVpbMF1bMV0sSz1aWzFdWzBdLGFlPVpbMV1bMV0sdmU9MCxEZT0wLGd0PUQmJlQmJnImJnNpLnNoaWZ0S2V5LHRuPXNpLnRvdWNoZXM/TVVlKHNpLmNoYW5nZWRUb3VjaGVzWzBdLmlkZW50aWZpZXIpOkdVLHB0PXRuKHgpLHd0PXB0LFRlPXUoeCxhcmd1bWVudHMsITApLmJlZm9yZXN0YXJ0KCk7Im92ZXJsYXkiPT09Zz8oeiYmKG50PSEwKSxrLnNlbGVjdGlvbj16PVtbdWU9bj09PSRSP2ZlOnB0WzBdLEY9bj09PXl6P3c6cHRbMV1dLFtkZT1uPT09JFI/Szp1ZSxsZT1uPT09eXo/YWU6Rl1dKToodWU9elswXVswXSxGPXpbMF1bMV0sZGU9elsxXVswXSxsZT16WzFdWzFdKSxoZT11ZSxxPUYsWT1kZSxJZT1sZTt2YXIgeHQ9Ym8oeCkuYXR0cigicG9pbnRlci1ldmVudHMiLCJub25lIiksbXQ9eHQuc2VsZWN0QWxsKCIub3ZlcmxheSIpLmF0dHIoImN1cnNvciIsbXBbZ10pO2lmKHNpLnRvdWNoZXMpVGUubW92ZWQ9ZHQsVGUuZW5kZWQ9TXQ7ZWxzZXt2YXIgY2U9Ym8oc2kudmlldykub24oIm1vdXNlbW92ZS5icnVzaCIsZHQsITApLm9uKCJtb3VzZXVwLmJydXNoIixNdCwhMCk7ciYmY2Uub24oImtleWRvd24uYnJ1c2giLGJ0LCEwKS5vbigia2V5dXAuYnJ1c2giLGhuLCEwKSxxVShzaS52aWV3KX1feigpLENnKHgpLGMuY2FsbCh4KSxUZS5zdGFydCgpfWZ1bmN0aW9uIGR0KCl7dmFyIG9uPXRuKHgpO2d0JiYhVWUmJiFBZSYmKE1hdGguYWJzKG9uWzBdLXd0WzBdKT5NYXRoLmFicyhvblsxXS13dFsxXSk/QWU9ITA6VWU9ITApLHd0PW9uLG50PSEwLEpSKCksV2UoKX1mdW5jdGlvbiBXZSgpe3ZhciBvbjtzd2l0Y2godmU9d3RbMF0tcHRbMF0sRGU9d3RbMV0tcHRbMV0sYil7Y2FzZSB2ejpjYXNlIHBsZTpEJiYodmU9TWF0aC5tYXgoZmUtdWUsTWF0aC5taW4oSy1kZSx2ZSkpLGhlPXVlK3ZlLFk9ZGUrdmUpLFQmJihEZT1NYXRoLm1heCh3LUYsTWF0aC5taW4oYWUtbGUsRGUpKSxxPUYrRGUsSWU9bGUrRGUpO2JyZWFrO2Nhc2UgQ3k6RDwwPyh2ZT1NYXRoLm1heChmZS11ZSxNYXRoLm1pbihLLXVlLHZlKSksaGU9dWUrdmUsWT1kZSk6RD4wJiYodmU9TWF0aC5tYXgoZmUtZGUsTWF0aC5taW4oSy1kZSx2ZSkpLGhlPXVlLFk9ZGUrdmUpLFQ8MD8oRGU9TWF0aC5tYXgody1GLE1hdGgubWluKGFlLUYsRGUpKSxxPUYrRGUsSWU9bGUpOlQ+MCYmKERlPU1hdGgubWF4KHctbGUsTWF0aC5taW4oYWUtbGUsRGUpKSxxPUYsSWU9bGUrRGUpO2JyZWFrO2Nhc2UgTXk6RCYmKGhlPU1hdGgubWF4KGZlLE1hdGgubWluKEssdWUtdmUqRCkpLFk9TWF0aC5tYXgoZmUsTWF0aC5taW4oSyxkZSt2ZSpEKSkpLFQmJihxPU1hdGgubWF4KHcsTWF0aC5taW4oYWUsRi1EZSpUKSksSWU9TWF0aC5tYXgodyxNYXRoLm1pbihhZSxsZStEZSpUKSkpfVk8aGUmJihEKj0tMSxvbj11ZSx1ZT1kZSxkZT1vbixvbj1oZSxoZT1ZLFk9b24sZyBpbiBmbGUmJm10LmF0dHIoImN1cnNvciIsbXBbZz1mbGVbZ11dKSksSWU8cSYmKFQqPS0xLG9uPUYsRj1sZSxsZT1vbixvbj1xLHE9SWUsSWU9b24sZyBpbiBtbGUmJm10LmF0dHIoImN1cnNvciIsbXBbZz1tbGVbZ11dKSksay5zZWxlY3Rpb24mJih6PWsuc2VsZWN0aW9uKSxVZSYmKGhlPXpbMF1bMF0sWT16WzFdWzBdKSxBZSYmKHE9elswXVsxXSxJZT16WzFdWzFdKSwoelswXVswXSE9PWhlfHx6WzBdWzFdIT09cXx8elsxXVswXSE9PVl8fHpbMV1bMV0hPT1JZSkmJihrLnNlbGVjdGlvbj1bW2hlLHFdLFtZLEllXV0sYy5jYWxsKHgpLFRlLmJydXNoKCkpfWZ1bmN0aW9uIE10KCl7aWYoX3ooKSxzaS50b3VjaGVzKXtpZihzaS50b3VjaGVzLmxlbmd0aClyZXR1cm47YSYmY2xlYXJUaW1lb3V0KGEpLGE9c2V0VGltZW91dChmdW5jdGlvbigpe2E9bnVsbH0sNTAwKX1lbHNlIFlVKHNpLnZpZXcsbnQpLGNlLm9uKCJrZXlkb3duLmJydXNoIGtleXVwLmJydXNoIG1vdXNlbW92ZS5icnVzaCBtb3VzZXVwLmJydXNoIixudWxsKTt4dC5hdHRyKCJwb2ludGVyLWV2ZW50cyIsImFsbCIpLG10LmF0dHIoImN1cnNvciIsbXAub3ZlcmxheSksay5zZWxlY3Rpb24mJih6PWsuc2VsZWN0aW9uKSxBVWUoeikmJihrLnNlbGVjdGlvbj1udWxsLGMuY2FsbCh4KSksVGUuZW5kKCl9ZnVuY3Rpb24gYnQoKXtzd2l0Y2goc2kua2V5Q29kZSl7Y2FzZSAxNjpndD1EJiZUO2JyZWFrO2Nhc2UgMTg6Yj09PUN5JiYoRCYmKGRlPVktdmUqRCx1ZT1oZSt2ZSpEKSxUJiYobGU9SWUtRGUqVCxGPXErRGUqVCksYj1NeSxXZSgpKTticmVhaztjYXNlIDMyOihiPT09Q3l8fGI9PT1NeSkmJihEPDA/ZGU9WS12ZTpEPjAmJih1ZT1oZS12ZSksVDwwP2xlPUllLURlOlQ+MCYmKEY9cS1EZSksYj12eixtdC5hdHRyKCJjdXJzb3IiLG1wLnNlbGVjdGlvbiksV2UoKSk7YnJlYWs7ZGVmYXVsdDpyZXR1cm59SlIoKX1mdW5jdGlvbiBobigpe3N3aXRjaChzaS5rZXlDb2RlKXtjYXNlIDE2Omd0JiYoVWU9QWU9Z3Q9ITEsV2UoKSk7YnJlYWs7Y2FzZSAxODpiPT09TXkmJihEPDA/ZGU9WTpEPjAmJih1ZT1oZSksVDwwP2xlPUllOlQ+MCYmKEY9cSksYj1DeSxXZSgpKTticmVhaztjYXNlIDMyOmI9PT12eiYmKHNpLmFsdEtleT8oRCYmKGRlPVktdmUqRCx1ZT1oZSt2ZSpEKSxUJiYobGU9SWUtRGUqVCxGPXErRGUqVCksYj1NeSk6KEQ8MD9kZT1ZOkQ+MCYmKHVlPWhlKSxUPDA/bGU9SWU6VD4wJiYoRj1xKSxiPUN5KSxtdC5hdHRyKCJjdXJzb3IiLG1wW2ddKSxXZSgpKTticmVhaztkZWZhdWx0OnJldHVybn1KUigpfX1mdW5jdGlvbiBoKCl7dSh0aGlzLGFyZ3VtZW50cykubW92ZWQoKX1mdW5jdGlvbiBmKCl7dSh0aGlzLGFyZ3VtZW50cykuZW5kZWQoKX1mdW5jdGlvbiBtKCl7dmFyIHg9dGhpcy5fX2JydXNofHx7c2VsZWN0aW9uOm51bGx9O3JldHVybiB4LmV4dGVudD14eih0LmFwcGx5KHRoaXMsYXJndW1lbnRzKSkseC5kaW09bix4fXJldHVybiBsLm1vdmU9ZnVuY3Rpb24oeCxnKXt4LnNlbGVjdGlvbj94Lm9uKCJzdGFydC5icnVzaCIsZnVuY3Rpb24oKXt1KHRoaXMsYXJndW1lbnRzKS5iZWZvcmVzdGFydCgpLnN0YXJ0KCl9KS5vbigiaW50ZXJydXB0LmJydXNoIGVuZC5icnVzaCIsZnVuY3Rpb24oKXt1KHRoaXMsYXJndW1lbnRzKS5lbmQoKX0pLnR3ZWVuKCJicnVzaCIsZnVuY3Rpb24oKXt2YXIgYj10aGlzLEQ9Yi5fX2JydXNoLFQ9dShiLGFyZ3VtZW50cyksaz1ELnNlbGVjdGlvbixaPW4uaW5wdXQoImZ1bmN0aW9uIj09dHlwZW9mIGc/Zy5hcHBseSh0aGlzLGFyZ3VtZW50cyk6ZyxELmV4dGVudCksej1mcChrLFopO2Z1bmN0aW9uIGZlKHVlKXtELnNlbGVjdGlvbj0xPT09dWUmJm51bGw9PT1aP251bGw6eih1ZSksYy5jYWxsKGIpLFQuYnJ1c2goKX1yZXR1cm4gbnVsbCE9PWsmJm51bGwhPT1aP2ZlOmZlKDEpfSk6eC5lYWNoKGZ1bmN0aW9uKCl7dmFyIGI9dGhpcyxEPWFyZ3VtZW50cyxUPWIuX19icnVzaCxrPW4uaW5wdXQoImZ1bmN0aW9uIj09dHlwZW9mIGc/Zy5hcHBseShiLEQpOmcsVC5leHRlbnQpLFo9dShiLEQpLmJlZm9yZXN0YXJ0KCk7Q2coYiksVC5zZWxlY3Rpb249bnVsbD09PWs/bnVsbDprLGMuY2FsbChiKSxaLnN0YXJ0KCkuYnJ1c2goKS5lbmQoKX0pfSxsLmNsZWFyPWZ1bmN0aW9uKHgpe2wubW92ZSh4LG51bGwpfSxkLnByb3RvdHlwZT17YmVmb3Jlc3RhcnQ6ZnVuY3Rpb24oKXtyZXR1cm4gMT09Kyt0aGlzLmFjdGl2ZSYmKHRoaXMuc3RhdGUuZW1pdHRlcj10aGlzLHRoaXMuc3RhcnRpbmc9ITApLHRoaXN9LHN0YXJ0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuc3RhcnRpbmc/KHRoaXMuc3RhcnRpbmc9ITEsdGhpcy5lbWl0KCJzdGFydCIpKTp0aGlzLmVtaXQoImJydXNoIiksdGhpc30sYnJ1c2g6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5lbWl0KCJicnVzaCIpLHRoaXN9LGVuZDpmdW5jdGlvbigpe3JldHVybiAwPT0tLXRoaXMuYWN0aXZlJiYoZGVsZXRlIHRoaXMuc3RhdGUuZW1pdHRlcix0aGlzLmVtaXQoImVuZCIpKSx0aGlzfSxlbWl0OmZ1bmN0aW9uKHgpeyFmdW5jdGlvbihuLHQsZSxpKXt2YXIgcj1zaTtuLnNvdXJjZUV2ZW50PXNpLHNpPW47dHJ5e3QuYXBwbHkoZSxpKX1maW5hbGx5e3NpPXJ9fShuZXcgZGxlKGwseCxuLm91dHB1dCh0aGlzLnN0YXRlLnNlbGVjdGlvbikpLG8uYXBwbHksbyxbeCx0aGlzLnRoYXQsdGhpcy5hcmdzXSl9fSxsLmV4dGVudD1mdW5jdGlvbih4KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8odD0iZnVuY3Rpb24iPT10eXBlb2YgeD94OlpSKHh6KHgpKSxsKTp0fSxsLmZpbHRlcj1mdW5jdGlvbih4KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT0iZnVuY3Rpb24iPT10eXBlb2YgeD94OlpSKCEheCksbCk6ZX0sbC50b3VjaGFibGU9ZnVuY3Rpb24oeCl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KGk9ImZ1bmN0aW9uIj09dHlwZW9mIHg/eDpaUighIXgpLGwpOml9LGwuaGFuZGxlU2l6ZT1mdW5jdGlvbih4KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocz0reCxsKTpzfSxsLmtleU1vZGlmaWVycz1mdW5jdGlvbih4KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8ocj0hIXgsbCk6cn0sbC5vbj1mdW5jdGlvbigpe3ZhciB4PW8ub24uYXBwbHkobyxhcmd1bWVudHMpO3JldHVybiB4PT09bz9sOnh9LGx9KCRSKSx0aGlzLm1heEJpblNpemU9MCx0aGlzLmFyZWE9ZnVuY3Rpb24oKXt2YXIgbj1oTyx0PW51bGwsZT1mYSgwKSxpPWZPLHI9ZmEoITApLG89bnVsbCxzPXBPLGE9bnVsbDtmdW5jdGlvbiBsKHUpe3ZhciBkLHAsaCxtLGcsZj11Lmxlbmd0aCx4PSExLGI9bmV3IEFycmF5KGYpLEQ9bmV3IEFycmF5KGYpO2ZvcihudWxsPT1vJiYoYT1zKGc9c1MoKSkpLGQ9MDtkPD1mOysrZCl7aWYoIShkPGYmJnIobT11W2RdLGQsdSkpPT09eClpZih4PSF4KXA9ZCxhLmFyZWFTdGFydCgpLGEubGluZVN0YXJ0KCk7ZWxzZXtmb3IoYS5saW5lRW5kKCksYS5saW5lU3RhcnQoKSxoPWQtMTtoPj1wOy0taClhLnBvaW50KGJbaF0sRFtoXSk7YS5saW5lRW5kKCksYS5hcmVhRW5kKCl9eCYmKGJbZF09K24obSxkLHUpLERbZF09K2UobSxkLHUpLGEucG9pbnQodD8rdChtLGQsdSk6YltkXSxpPytpKG0sZCx1KTpEW2RdKSl9aWYoZylyZXR1cm4gYT1udWxsLGcrIiJ8fG51bGx9ZnVuY3Rpb24gYygpe3JldHVybiB4UygpLmRlZmluZWQocikuY3VydmUocykuY29udGV4dChvKX1yZXR1cm4gbC54PWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPSJmdW5jdGlvbiI9PXR5cGVvZiB1P3U6ZmEoK3UpLHQ9bnVsbCxsKTpufSxsLngwPWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhuPSJmdW5jdGlvbiI9PXR5cGVvZiB1P3U6ZmEoK3UpLGwpOm59LGwueDE9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHQ9bnVsbD09dT9udWxsOiJmdW5jdGlvbiI9PXR5cGVvZiB1P3U6ZmEoK3UpLGwpOnR9LGwueT1mdW5jdGlvbih1KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT0iZnVuY3Rpb24iPT10eXBlb2YgdT91OmZhKCt1KSxpPW51bGwsbCk6ZX0sbC55MD1mdW5jdGlvbih1KXtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aD8oZT0iZnVuY3Rpb24iPT10eXBlb2YgdT91OmZhKCt1KSxsKTplfSxsLnkxPWZ1bmN0aW9uKHUpe3JldHVybiBhcmd1bWVudHMubGVuZ3RoPyhpPW51bGw9PXU/bnVsbDoiZnVuY3Rpb24iPT10eXBlb2YgdT91OmZhKCt1KSxsKTppfSxsLmxpbmVYMD1sLmxpbmVZMD1mdW5jdGlvbigpe3JldHVybiBjKCkueChuKS55KGUpfSxsLmxpbmVZMT1mdW5jdGlvbigpe3JldHVybiBjKCkueChuKS55KGkpfSxsLmxpbmVYMT1mdW5jdGlvbigpe3JldHVybiBjKCkueCh0KS55KGUpfSxsLmRlZmluZWQ9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHI9ImZ1bmN0aW9uIj09dHlwZW9mIHU/dTpmYSghIXUpLGwpOnJ9LGwuY3VydmU9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KHM9dSxudWxsIT1vJiYoYT1zKG8pKSxsKTpzfSxsLmNvbnRleHQ9ZnVuY3Rpb24odSl7cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGg/KG51bGw9PXU/bz1hPW51bGw6YT1zKG89dSksbCk6b30sbH0oKS54MChmdW5jdGlvbihlKXtyZXR1cm4gdGhpcy54U2NhbGVOdW0oLWUubGVuZ3RoKX0uYmluZCh0aGlzKSkueDEoZnVuY3Rpb24oZSl7cmV0dXJuIHRoaXMueFNjYWxlTnVtKGUubGVuZ3RoKX0uYmluZCh0aGlzKSkueShmdW5jdGlvbihlKXtyZXR1cm4gZS54MD09PS0xLzA/dGhpcy5jaGFydEhlaWdodC10aGlzLmRyYXdNYXJnaW4udG9wOnRoaXMueVNjYWxlKChlLngxK2UueDApLzIpfS5iaW5kKHRoaXMpKS5jdXJ2ZShzail9bmdBZnRlclZpZXdJbml0KCl7dGhpcy51cGRhdGVEaW1lbnNpb25zKCksdGhpcy5zdmc9Ym8odGhpcy5jaGFydENvbnRhaW5lci5uYXRpdmVFbGVtZW50KS5zZWxlY3QoInN2ZyIpLHRoaXMubWFpbkNvbnRhaW5lcj10aGlzLnN2Zy5hcHBlbmQoImciKS5hdHRyKCJ0cmFuc2Zvcm0iLGB0cmFuc2xhdGUoJHt0aGlzLm1hcmdpbi5sZWZ0fSwgJHt0aGlzLm1hcmdpbi50b3B9KWApLHRoaXMuZHJhd0NvbnRhaW5lcj10aGlzLm1haW5Db250YWluZXIuYXBwZW5kKCJnIikuYXR0cigidHJhbnNmb3JtIixgdHJhbnNsYXRlKCR7dGhpcy5kcmF3TWFyZ2luLmxlZnR9LCAke3RoaXMuZHJhd01hcmdpbi50b3B9KWApLHRoaXMuZG90c0dyb3VwPXRoaXMuZHJhd0NvbnRhaW5lci5hcHBlbmQoImciKS5hdHRyKCJjbGFzcyIsImRvdHNHcm91cCIpLHRoaXMueUF4aXNHcm91cD10aGlzLm1haW5Db250YWluZXIuYXBwZW5kKCJnIikuYXR0cigiY2xhc3MiLCJheGlzIGF4aXMtLXkiKSx0aGlzLnhBeGlzR3JvdXA9dGhpcy5tYWluQ29udGFpbmVyLmFwcGVuZCgiZyIpLmF0dHIoImNsYXNzIiwiYXhpcyBheGlzLS14IiksdGhpcy5taXNjR3JvdXA9dGhpcy5kcmF3Q29udGFpbmVyLmFwcGVuZCgiZyIpLHRoaXMueFNjYWxlPXd5KCkucGFkZGluZyguMDUpLHRoaXMueEF4aXM9ancodGhpcy54U2NhbGUpLHRoaXMueVNjYWxlPVFvKCkucmFuZ2UoW3RoaXMuZHJhd0hlaWdodCwwXSksdGhpcy55QXhpcz1mdW5jdGlvbihuKXtyZXR1cm4gQlUoNCxuKX0odGhpcy55U2NhbGUpLHRoaXMueFNjYWxlTnVtPVFvKCksdGhpcy5pbml0aWFsaXplQnJ1c2goKSx0aGlzLmRyYXdNaXNjKCksdGhpcy5yZWRyYXcoKX1uZ09uQ2hhbmdlcyhlKXt0aGlzLnN2ZyYmdGhpcy5yZWRyYXcoKX1yZWRyYXcoKXt0aGlzLnVwZGF0ZURpbWVuc2lvbnMoKSx0aGlzLnNldE1heEJpblNpemUoKSx0aGlzLnVwZGF0ZUF4ZXMoKSx0aGlzLmRyYXcoKX11cGRhdGVEaW1lbnNpb25zKCl7dGhpcy5jaGFydFdpZHRoPXRoaXMud2lkdGgtdGhpcy5tYXJnaW4ubGVmdC10aGlzLm1hcmdpbi5yaWdodCx0aGlzLmRyYXdXaWR0aD10aGlzLmNoYXJ0V2lkdGgtdGhpcy5kcmF3TWFyZ2luLmxlZnQtdGhpcy5kcmF3TWFyZ2luLnJpZ2h0LHRoaXMuY2hhcnRIZWlnaHQ9dGhpcy5oZWlnaHQtdGhpcy5tYXJnaW4udG9wLXRoaXMubWFyZ2luLmJvdHRvbSx0aGlzLmRyYXdIZWlnaHQ9dGhpcy5jaGFydEhlaWdodC10aGlzLmRyYXdNYXJnaW4udG9wLXRoaXMuZHJhd01hcmdpbi5ib3R0b219c2V0TWF4QmluU2l6ZSgpe09iamVjdC52YWx1ZXModGhpcy5jaGFydERhdGEudmlvbGluRGF0YSkuZm9yRWFjaChlPT57bGV0IGk9ZS5tYXAobz0+by5sZW5ndGgpLHI9TWF0aC5tYXgoLi4uaSk7dGhpcy5tYXhCaW5TaXplPU1hdGgubWF4KHIsdGhpcy5tYXhCaW5TaXplKX0pfXVwZGF0ZUF4ZXMoKXt0aGlzLnhTY2FsZS5yYW5nZShbMCx0aGlzLmRyYXdXaWR0aF0pLmRvbWFpbihPYmplY3Qua2V5cyh0aGlzLmNoYXJ0RGF0YS52aW9saW5EYXRhKSksdGhpcy55U2NhbGUuZG9tYWluKFt0aGlzLmNoYXJ0RGF0YS5leHRyZW1lcy5taW4sdGhpcy5jaGFydERhdGEuZXh0cmVtZXMubWF4XSksdGhpcy54U2NhbGVOdW0ucmFuZ2UoWzAsdGhpcy54U2NhbGUuYmFuZHdpZHRoKCldKS5kb21haW4oWy10aGlzLm1heEJpblNpemUsdGhpcy5tYXhCaW5TaXplXSl9aW5pdGlhbGl6ZUJydXNoKCl7dGhpcy5icnVzaC5vbigiZW5kIix0aGlzLmJydXNoTW92ZWQuYmluZCh0aGlzKSl9ZHJhdygpe3RoaXMuZHJhd0F4ZXMoKSx0aGlzLmRyYXdQbG90KCksdGhpcy5yZWZyZXNoTWlzYygpLHRoaXMucmVmcmVzaEJydXNoKCl9ZHJhd0F4ZXMoKXt0aGlzLnlBeGlzR3JvdXAuYXR0cigidHJhbnNmb3JtIixgdHJhbnNsYXRlKCR7dGhpcy5kcmF3TWFyZ2luLmxlZnR9LFxuICAgICAgJHt0aGlzLmRyYXdNYXJnaW4udG9wfSlgKS5jYWxsKHRoaXMueUF4aXMpLHRoaXMueEF4aXNHcm91cC5hdHRyKCJ0cmFuc2Zvcm0iLGB0cmFuc2xhdGUoJHt0aGlzLmRyYXdNYXJnaW4ubGVmdH0sXG4gICAgICAke3RoaXMuZHJhd01hcmdpbi50b3ArdGhpcy5jaGFydEhlaWdodH0pYCkuY2FsbCh0aGlzLnhBeGlzKX1kcmF3UGxvdCgpe2xldCBlPXRoaXMuZG90c0dyb3VwLnNlbGVjdEFsbCgiLnZpb2xpbi1wbG90IikuZGF0YShPYmplY3QuZW50cmllcyh0aGlzLmNoYXJ0RGF0YS52aW9saW5EYXRhKSk7ZS5lbnRlcigpLmFwcGVuZCgicGF0aCIpLmF0dHIoImNsYXNzIiwidmlvbGluLXBsb3QiKS5zdHlsZSgic3Ryb2tlIixmdW5jdGlvbihpKXtyZXR1cm4gdGhpcy5jb2xvclNjYWxlKGlbMF0pfS5iaW5kKHRoaXMpKS5zdHlsZSgiZmlsbCIsZnVuY3Rpb24oaSl7cmV0dXJuYCR7dGhpcy5jb2xvclNjYWxlKGlbMF0pfTMzYH0uYmluZCh0aGlzKSkuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihpKXtyZXR1cm5gdHJhbnNsYXRlKCR7dGhpcy54U2NhbGUoaVswXSl9LCAwKWB9LmJpbmQodGhpcykpLmRhdHVtKGZ1bmN0aW9uKGkpe3JldHVybiBpWzFdfSkuYXR0cigiZCIsdGhpcy5hcmVhKSxlLmF0dHIoInRyYW5zZm9ybSIsZnVuY3Rpb24oaSl7cmV0dXJuYHRyYW5zbGF0ZSgke3RoaXMueFNjYWxlKGlbMF0pfSwgMClgfS5iaW5kKHRoaXMpKS5kYXR1bShmdW5jdGlvbihpKXtyZXR1cm4gaVsxXX0pLmF0dHIoImQiLHRoaXMuYXJlYSksZS5leGl0KCkucmVtb3ZlKCl9ZHJhd01pc2MoKXt0aGlzLnplcm9MaW5lPXRoaXMubWlzY0dyb3VwLmFwcGVuZCgibGluZSIpLnN0eWxlKCJzdHJva2UiLCJibGFjayIpLmF0dHIoIngxIiwwKS5hdHRyKCJ5MSIsdGhpcy55U2NhbGUoMCkpLmF0dHIoIngyIix0aGlzLmRyYXdXaWR0aCkuYXR0cigieTIiLHRoaXMueVNjYWxlKDApKSx0aGlzLm5hblRleHQ9dGhpcy5taXNjR3JvdXAuYXBwZW5kKCJ0ZXh0Iikuc3R5bGUoImZpbGwiLCJibGFjayIpLnRleHQoIk5hTiIpLmF0dHIoImZvbnQtc2l6ZSIsIjEwcHgiKS5hdHRyKCJ0ZXh0LWFuY2hvciIsImVuZCIpLmF0dHIoImFsaWdubWVudC1iYXNlbGluZSIsIm1pZGRsZSIpLmF0dHIoIngiLC01KS5hdHRyKCJ5Iix0aGlzLmNoYXJ0SGVpZ2h0LXRoaXMuZHJhd01hcmdpbi50b3ApLHRoaXMubmFuTGluZT10aGlzLm1pc2NHcm91cC5hcHBlbmQoImxpbmUiKS5zdHlsZSgic3Ryb2tlIiwiZ3JleSIpLnN0eWxlKCJzdHJva2UtZGFzaGFycmF5IiwiMywgMyIpLmF0dHIoIngxIiwwKS5hdHRyKCJ5MSIsdGhpcy5jaGFydEhlaWdodC10aGlzLmRyYXdNYXJnaW4udG9wKS5hdHRyKCJ4MiIsdGhpcy5kcmF3V2lkdGgpLmF0dHIoInkyIix0aGlzLmNoYXJ0SGVpZ2h0LXRoaXMuZHJhd01hcmdpbi50b3ApfXJlZnJlc2hNaXNjKCl7dGhpcy56ZXJvTGluZS5hdHRyKCJ5MSIsdGhpcy55U2NhbGUoMCkpLmF0dHIoIngyIix0aGlzLmRyYXdXaWR0aCkuYXR0cigieTIiLHRoaXMueVNjYWxlKDApKSx0aGlzLm5hblRleHQuYXR0cigieSIsdGhpcy5jaGFydEhlaWdodC10aGlzLmRyYXdNYXJnaW4udG9wKSx0aGlzLm5hbkxpbmUuYXR0cigieTEiLHRoaXMuZHJhd0hlaWdodCt0aGlzLmRyYXdNYXJnaW4udG9wKS5hdHRyKCJ4MiIsdGhpcy5kcmF3V2lkdGgpLmF0dHIoInkyIix0aGlzLmRyYXdIZWlnaHQrdGhpcy5kcmF3TWFyZ2luLnRvcCl9cmVmcmVzaEJydXNoKCl7dGhpcy5icnVzaC5leHRlbnQoW1swLDBdLFt0aGlzLmRyYXdXaWR0aCx0aGlzLmRyYXdIZWlnaHQrdGhpcy5tYXJnaW4udG9wXV0pO2xldCBlPVswLHRoaXMuZHJhd0hlaWdodCt0aGlzLm1hcmdpbi50b3BdO2lmKHRoaXMuZmlsdGVyLm1heDx0aGlzLmZpbHRlci5taW4pZVswXT10aGlzLmZpbHRlci5pbmNsdWRlTmFOP3RoaXMueVNjYWxlKHRoaXMuY2hhcnREYXRhLmV4dHJlbWVzLm1pbik6ZVsxXTtlbHNle2lmKCF0aGlzLmZpbHRlci5pbmNsdWRlTmFOKXtsZXQgcj1NYXRoLm1heCh0aGlzLmNoYXJ0RGF0YS5leHRyZW1lcy5taW4sdGhpcy5maWx0ZXIubWluKTtlWzFdPXRoaXMueVNjYWxlKHIpfWxldCBpPU1hdGgubWluKHRoaXMuY2hhcnREYXRhLmV4dHJlbWVzLm1heCx0aGlzLmZpbHRlci5tYXgpO2VbMF09dGhpcy55U2NhbGUoaSl9dGhpcy5kcmF3Q29udGFpbmVyLmNhbGwodGhpcy5icnVzaCkuY2FsbCh0aGlzLmJydXNoLm1vdmUsZSl9YnJ1c2hNb3ZlZCgpe2lmKCFzaXx8IXNpLnNvdXJjZUV2ZW50KXJldHVybjtsZXQgZT1zaS5zZWxlY3Rpb247aWYoZSl7bGV0IGk9ITEscj0tMixvPXRoaXMuY2hhcnREYXRhLmV4dHJlbWVzLm1pbjtlWzBdPD10aGlzLmRyYXdIZWlnaHQrdGhpcy5tYXJnaW4udG9wJiZlWzFdPj10aGlzLmRyYXdIZWlnaHQmJihpPSEwKSxlWzBdPHRoaXMuZHJhd0hlaWdodCYmKHI9dGhpcy55U2NhbGUuaW52ZXJ0KGVbMF0pKSxlWzFdPHRoaXMuZHJhd0hlaWdodCYmKG89dGhpcy55U2NhbGUuaW52ZXJ0KGVbMV0pKSx0aGlzLm9uVXBkYXRlRmlsdGVyLmVtaXQoe21heDpyLG1pbjpvLGluY2x1ZGVOYU46aX0pfWVsc2UgdGhpcy5vblVwZGF0ZUZpbHRlci5lbWl0KHttYXg6MSxtaW46LTEsaW5jbHVkZU5hTjohMH0pfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ2aW9saW4tZmlsdGVyLWNvbXBvbmVudCJdXSx2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJm90KEFldCw3LFJlKSwyJmUpe2xldCByO05lKHI9TGUoKSkmJihpLmNoYXJ0Q29udGFpbmVyPXIuZmlyc3QpfX0saW5wdXRzOnttZXRyaWNOYW1lOiJtZXRyaWNOYW1lIixmaWx0ZXI6ImZpbHRlciIsY2hhcnREYXRhOiJjaGFydERhdGEiLHdpZHRoOiJ3aWR0aCIsY29sb3JTY2FsZToiY29sb3JTY2FsZSJ9LG91dHB1dHM6e29uUmVtb3ZlOiJvblJlbW92ZSIsb25VcGRhdGVGaWx0ZXI6Im9uVXBkYXRlRmlsdGVyIn0sZmVhdHVyZXM6W0Z0XSxkZWNsczo5LHZhcnM6MSxjb25zdHM6ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gdD0kbG9jYWxpemVgOkxhYmVsIGZvciBhIGJ1dHRvbiB0aGF0IHJlbW92ZXMgYSBtZXRyaWMgZmlsdGVyLuKQn2E2YmZhZDU4YmIzNjNkNWM4OTFkMGE1NDc0YjFkNzdlZjkwYTM0ZGHikJ84NDU0OTYxNzk3NzYyOTA3NjI0OlJlbW92ZSBGaWx0ZXJgLFtbMSwiY2hhcnQtY29udGFpbmVyIl0sWyJ0aXRsZSIsIlNob3dzIHRoZSBuUE1JIHZhbHVlIGRpc3RyaWJ1dGlvbiBwZXIgcnVuLiBSYW5nZXMgb2Ygc2VsZWN0ZWQgdmFsdWVzIGNhbiBiZSBtYW5pcHVsYXRlZCBieSBtb2RpZnlpbmcgdGhlIGdyZXkgYm94LiIsMSwiY2hhcnQtaGVhZCJdLFsxLCJjaGFydC1oZWFkaW5nIl0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJhcmlhLWxhYmVsIix0LDMsImNsaWNrIl0sWyJzdmdJY29uIiwiY2xlYXJfMjRweCJdLFsxLCJjaGFydCJdLFsiY2hhcnQiLCIiXSxbMSwiZHJhdy1hcmVhIl1dfSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IiwwKSgxLCJkaXYiLDEpKDIsImRpdiIsMiksQSgzKSx2KCksXyg0LCJidXR0b24iLDMpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uUmVtb3ZlLmVtaXQoKX0pLE8oNSwibWF0LWljb24iLDQpLHYoKSgpLF8oNiwiZGl2Iiw1LDYpLEluKCksTyg4LCJzdmciLDcpLHYoKSgpKSwyJmUmJihDKDMpLHl0KGkubWV0cmljTmFtZSkpfSxkZXBlbmRlbmNpZXM6W19uLEd0XSxzdHlsZXM6WyIuY2hhcnQtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7Ym9yZGVyLWJvdHRvbToxcHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO292ZXJmbG93OmhpZGRlbn0uY2hhcnRbX25nY29udGVudC0lQ09NUCVde2hlaWdodDozMDBweDt3aWR0aDoxMDAlfS5jaGFydC1oZWFkW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7ZGlzcGxheTpmbGV4O2p1c3RpZnktY29udGVudDpzcGFjZS1iZXR3ZWVufS5jaGFydC1oZWFkaW5nW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXNpemU6MTNweDtwYWRkaW5nLWxlZnQ6MTBweDtwYWRkaW5nLXRvcDoxMHB4fS5kcmF3LWFyZWFbX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxMDAlO3dpZHRoOjEwMCV9LnN0cm9rZWQtbGluZVtfbmdjb250ZW50LSVDT01QJV17c3Ryb2tlOnJnYmEoMCwwLDAsLjEyKTtzdHJva2UtZGFzaGFycmF5OjMgM30iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLFBtZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLmFjdGl2ZVJ1bnMkPXRoaXMuc3RvcmUucGlwZSh2dChvbykpLnBpcGUoTChpPT5pP0FycmF5LmZyb20oaS5lbnRyaWVzKCkpLmZpbHRlcihyPT5yWzFdKS5tYXAocj0+clswXSk6W10pKSx0aGlzLnZpc2libGVBbm5vdGF0aW9ucyQ9THQoW3RoaXMuc3RvcmUuc2VsZWN0KFBmKSx0aGlzLnN0b3JlLnNlbGVjdChYYiksdGhpcy5zdG9yZS5zZWxlY3QoWmIpXSkucGlwZShMKChbaSxyLG9dKT0+cUYoaSxyLG8pKSksdGhpcy5jaGFydFdpZHRoJD10aGlzLnN0b3JlLnBpcGUodnQoT2YpKS5waXBlKEwoaT0+TWF0aC5tYXgoMTUwLGkpKSksdGhpcy5ydW5Db2xvclNjYWxlJD10aGlzLnN0b3JlLnNlbGVjdChuYykucGlwZShMKGk9PnI9PntpZighaS5oYXNPd25Qcm9wZXJ0eShyKSl0aHJvdyBuZXcgRXJyb3IoYFtDb2xvciBzY2FsZV0gdW5rbm93biBydW5JZDogJHtyfS5gKTtyZXR1cm4gaVtyXX0pKX1uZ09uSW5pdCgpe3RoaXMuY2hhcnREYXRhJD1MdChbdGhpcy52aXNpYmxlQW5ub3RhdGlvbnMkLHRoaXMuYWN0aXZlUnVucyRdKS5waXBlKEwoKFtlLGldKT0+ZnVuY3Rpb24obix0LGUpe2xldCBpPXt9LHI9e30sbz1uZXcgU2V0KHQpLHM9U3MoZSksYT17bWF4Oi0xLG1pbjoxfTtPYmplY3QudmFsdWVzKG4pLmZvckVhY2goZD0+e2QuZm9yRWFjaChwPT57bGV0IGg9cC5ydW47aWYoby5oYXMoaCkmJnAubWV0cmljPT09cylpZihudWxsPT09cC5uUE1JVmFsdWUpcltoXT9yW2hdLnB1c2gobnVsbCk6cltoXT1bbnVsbF07ZWxzZXtsZXQgZj1wLm5QTUlWYWx1ZTthLm1heD1hLm1heDxmP2Y6YS5tYXgsYS5taW49YS5taW4+Zj9mOmEubWluLGlbcC5ydW5dP2lbaF0ucHVzaChmKTppW2hdPVtmXX19KX0pO2xldCBsPXt9LGM9TVIoKS5kb21haW4oW2EubWluLGEubWF4XSkudmFsdWUoZD0+ZCksdT1NUigpLmRvbWFpbihbLTEvMCwxLzBdKS50aHJlc2hvbGRzKDApLnZhbHVlKGQ9PmQpO2ZvcihsZXQgZCBvZiBvKWlmKGxbZF09YyhpW2RdKSxyW2RdKXtsZXQgcD11KHJbZF0pO2xbZF0udW5zaGlmdChwWzBdKX1yZXR1cm57dmlvbGluRGF0YTpsLGV4dHJlbWVzOmF9fShlLGksdGhpcy5tZXRyaWNOYW1lKSkpfXJlbW92ZU1ldHJpYygpe3RoaXMuc3RvcmUuZGlzcGF0Y2goR2Ioe21ldHJpYzp0aGlzLm1ldHJpY05hbWV9KSl9dXBkYXRlRmlsdGVyKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goV2Ioe21ldHJpYzp0aGlzLm1ldHJpY05hbWUsLi4uZX0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktdmlvbGluLWZpbHRlciJdXSxpbnB1dHM6e21ldHJpY05hbWU6Im1ldHJpY05hbWUiLGZpbHRlcjoiZmlsdGVyIn0sZGVjbHM6NCx2YXJzOjExLGNvbnN0czpbWzMsIm1ldHJpY05hbWUiLCJmaWx0ZXIiLCJjaGFydERhdGEiLCJ3aWR0aCIsImNvbG9yU2NhbGUiLCJvblJlbW92ZSIsIm9uVXBkYXRlRmlsdGVyIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJ2aW9saW4tZmlsdGVyLWNvbXBvbmVudCIsMCksUCgib25SZW1vdmUiLGZ1bmN0aW9uKCl7cmV0dXJuIGkucmVtb3ZlTWV0cmljKCl9KSgib25VcGRhdGVGaWx0ZXIiLGZ1bmN0aW9uKG8pe3JldHVybiBpLnVwZGF0ZUZpbHRlcihvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLHYoKSksMiZlJiZ5KCJtZXRyaWNOYW1lIixpLm1ldHJpY05hbWUpKCJmaWx0ZXIiLGkuZmlsdGVyKSgiY2hhcnREYXRhIixVKDEsNSxpLmNoYXJ0RGF0YSQpKSgid2lkdGgiLFUoMiw3LGkuY2hhcnRXaWR0aCQpKSgiY29sb3JTY2FsZSIsVSgzLDksaS5ydW5Db2xvclNjYWxlJCkpfSxkZXBlbmRlbmNpZXM6W0ltZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gUmV0KG4sdCl7aWYoMSZuJiZPKDAsIm5wbWktdmlvbGluLWZpbHRlciIsOCksMiZuKXtsZXQgZT10LiRpbXBsaWNpdDt5KCJtZXRyaWNOYW1lIixlWzBdKSgiZmlsdGVyIixlWzFdKX19ZnVuY3Rpb24gT2V0KG4sdCl7MSZuJiYoXygwLCJkaXYiLDkpKDEsInNwYW4iLDEwKSxBKDIsIiBZb3UgY2FuIGFkZCBtb3JlIGZpbHRlcnMgYXQgdGhlIHRvcC4gIiksdigpKCkpfXZhciBSbWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMudG9nZ2xlU2lkZWJhckV4cGFuZGVkPW5ldyBHfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJ2aW9saW4tZmlsdGVycy1jb21wb25lbnQiXV0saW5wdXRzOntzaWRlYmFyRXhwYW5kZWQ6InNpZGViYXJFeHBhbmRlZCIsbWV0cmljRmlsdGVyczoibWV0cmljRmlsdGVycyJ9LG91dHB1dHM6e3RvZ2dsZVNpZGViYXJFeHBhbmRlZDoidG9nZ2xlU2lkZWJhckV4cGFuZGVkIn0sZGVjbHM6OSx2YXJzOjIsY29uc3RzOmZ1bmN0aW9uKCl7bGV0IHQ7cmV0dXJuIHQ9JGxvY2FsaXplYDpMYWJlbCBmb3IgYSBidXR0b24gdGhhdCBleHBhbmRzL2hpZGVzIHRoZSBzaWRlYmFyLuKQnzQ4YzI5OTAzY2U4ODFhYjYxMDg4ZjhkNDlkODI3MjAzNzE2YWFlZDTikJ80NjU4NjAyOTkxOTcwMjYwMjE1OkV4cGFuZC9IaWRlIFNpZGViYXJgLFtbMSwiZmlsdGVycy10b29sYmFyIl0sWzEsImZpbHRlcnMtdGl0bGUiXSxbMSwic2lkZS10b2dnbGUiXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsImFyaWEtbGFiZWwiLHQsMywiY2xpY2siXSxbInN2Z0ljb24iLCJjaGV2cm9uX2xlZnRfMjRweCJdLFsxLCJmaWx0ZXJzIl0sWzMsIm1ldHJpY05hbWUiLCJmaWx0ZXIiLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsiY2xhc3MiLCJmaWx0ZXJzLWhpbnQiLDQsIm5nSWYiXSxbMywibWV0cmljTmFtZSIsImZpbHRlciJdLFsxLCJmaWx0ZXJzLWhpbnQiXSxbMSwiZmlsdGVycy1oaW50LXRleHQiXV19LHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJkaXYiLDApKDEsImgzIiwxKSxBKDIsIkFjdGl2ZSBGaWx0ZXJzIiksdigpLF8oMywiZGl2IiwyKSg0LCJidXR0b24iLDMpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLnRvZ2dsZVNpZGViYXJFeHBhbmRlZC5lbWl0KCl9KSxPKDUsIm1hdC1pY29uIiw0KSx2KCkoKSgpLF8oNiwiZGl2Iiw1KSxFKDcsUmV0LDEsMiwibnBtaS12aW9saW4tZmlsdGVyIiw2KSx2KCksRSg4LE9ldCwzLDAsImRpdiIsNykpLDImZSYmKEMoNykseSgibmdGb3JPZiIsaS5tZXRyaWNGaWx0ZXJzKSxDKDEpLHkoIm5nSWYiLDA9PT1pLm1ldHJpY0ZpbHRlcnMubGVuZ3RoKSl9LGRlcGVuZGVuY2llczpbZG4sQmUsR3QsX24sUG1lXSxzdHlsZXM6WyJbX25naG9zdC0lQ09NUCVde2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47aGVpZ2h0OjEwMCV9LmZpbHRlcnMtdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7ZGlzcGxheTpmbGV4O2hlaWdodDo0MnB4O2p1c3RpZnktY29udGVudDpzcGFjZS1iZXR3ZWVuO3BhZGRpbmc6MCAxMHB4fS5maWx0ZXJzLXRpdGxlW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZTtmb250LXNpemU6MTRweDtmb250LXdlaWdodDo1MDB9LnNpZGUtdG9nZ2xlW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JvcmRlci1yYWRpdXM6M3B4O2JvcmRlcjoxcHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmZsZXg7aGVpZ2h0OjMwcHg7anVzdGlmeS1jb250ZW50OmNlbnRlcjt3aWR0aDozMHB4fS5maWx0ZXJzW19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdy15OmF1dG99LmZpbHRlcnMtaGludFtfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleDtoZWlnaHQ6NDJweDtwYWRkaW5nOjAgMTZweH0uZmlsdGVycy1oaW50LXRleHRbX25nY29udGVudC0lQ09NUCVde2NvbG9yOnJnYmEoMCwwLDAsLjM4KX0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLE9tZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLnNpZGViYXJFeHBhbmRlZCQ9dGhpcy5zdG9yZS5zZWxlY3QoekYpLHRoaXMubWV0cmljRmlsdGVycyQ9dGhpcy5zdG9yZS5zZWxlY3QoSWwpLnBpcGUoTChpPT5PYmplY3QuZW50cmllcyhpKSkpfW9uVG9nZ2xlU2lkZWJhckV4cGFuZGVkKCl7dGhpcy5zdG9yZS5kaXNwYXRjaChxYigpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktdmlvbGluLWZpbHRlcnMiXV0sZGVjbHM6Myx2YXJzOjYsY29uc3RzOltbMywic2lkZWJhckV4cGFuZGVkIiwibWV0cmljRmlsdGVycyIsInRvZ2dsZVNpZGViYXJFeHBhbmRlZCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwidmlvbGluLWZpbHRlcnMtY29tcG9uZW50IiwwKSxQKCJ0b2dnbGVTaWRlYmFyRXhwYW5kZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25Ub2dnbGVTaWRlYmFyRXhwYW5kZWQoKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksdigpKSwyJmUmJnkoInNpZGViYXJFeHBhbmRlZCIsVSgxLDIsaS5zaWRlYmFyRXhwYW5kZWQkKSkoIm1ldHJpY0ZpbHRlcnMiLFUoMiw0LGkubWV0cmljRmlsdGVycyQpKX0sZGVwZW5kZW5jaWVzOltSbWUsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIFZldChuLHQsZSl7cmV0dXJuIG4ubGVuZ3RoIT10Lmxlbmd0aD9lOnQubWFwKChvLHMpPT5vLW5bc10pLm1hcChvPT5NYXRoLnBvdyhvLDIpKS5yZWR1Y2UoKG8scyk9Pm8rcywwKX12YXIgSGV0PVsiaW5wdXQiXSxVZXQ9ZnVuY3Rpb24obil7cmV0dXJue2VudGVyRHVyYXRpb246bn19LHpldD1bIioiXSxqZXQ9bmV3IHBlKCJtYXQtc2xpZGUtdG9nZ2xlLWRlZmF1bHQtb3B0aW9ucyIse3Byb3ZpZGVkSW46InJvb3QiLGZhY3Rvcnk6KCk9Pih7ZGlzYWJsZVRvZ2dsZVZhbHVlOiExfSl9KSxHZXQ9MCxXZXQ9e3Byb3ZpZGU6Tm8sdXNlRXhpc3Rpbmc6Sm4oKCk9Pl82KSxtdWx0aTohMH0scWV0PW9jKGtvKHFvKHNvKGNsYXNze2NvbnN0cnVjdG9yKG4pe3RoaXMuX2VsZW1lbnRSZWY9bn19KSkpKSxZZXQ9KCgpPT57Y2xhc3MgbiBleHRlbmRzIHFldHtjb25zdHJ1Y3RvcihlLGkscixvLHMsYSxsKXtzdXBlcihlKSx0aGlzLl9mb2N1c01vbml0b3I9aSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZj1yLHRoaXMuZGVmYXVsdHM9cyx0aGlzLl9vbkNoYW5nZT1jPT57fSx0aGlzLl9vblRvdWNoZWQ9KCk9Pnt9LHRoaXMuX3JlcXVpcmVkPSExLHRoaXMuX2NoZWNrZWQ9ITEsdGhpcy5uYW1lPW51bGwsdGhpcy5sYWJlbFBvc2l0aW9uPSJhZnRlciIsdGhpcy5hcmlhTGFiZWw9bnVsbCx0aGlzLmFyaWFMYWJlbGxlZGJ5PW51bGwsdGhpcy5jaGFuZ2U9bmV3IEcsdGhpcy50b2dnbGVDaGFuZ2U9bmV3IEcsdGhpcy50YWJJbmRleD1wYXJzZUludChvKXx8MCx0aGlzLmNvbG9yPXRoaXMuZGVmYXVsdENvbG9yPXMuY29sb3J8fCJhY2NlbnQiLHRoaXMuX25vb3BBbmltYXRpb25zPSJOb29wQW5pbWF0aW9ucyI9PT1hLHRoaXMuaWQ9dGhpcy5fdW5pcXVlSWQ9YCR7bH0keysrR2V0fWB9Z2V0IHJlcXVpcmVkKCl7cmV0dXJuIHRoaXMuX3JlcXVpcmVkfXNldCByZXF1aXJlZChlKXt0aGlzLl9yZXF1aXJlZD1SdChlKX1nZXQgY2hlY2tlZCgpe3JldHVybiB0aGlzLl9jaGVja2VkfXNldCBjaGVja2VkKGUpe3RoaXMuX2NoZWNrZWQ9UnQoZSksdGhpcy5fY2hhbmdlRGV0ZWN0b3JSZWYubWFya0ZvckNoZWNrKCl9Z2V0IGlucHV0SWQoKXtyZXR1cm5gJHt0aGlzLmlkfHx0aGlzLl91bmlxdWVJZH0taW5wdXRgfW5nQWZ0ZXJDb250ZW50SW5pdCgpe3RoaXMuX2ZvY3VzTW9uaXRvci5tb25pdG9yKHRoaXMuX2VsZW1lbnRSZWYsITApLnN1YnNjcmliZShlPT57ImtleWJvYXJkIj09PWV8fCJwcm9ncmFtIj09PWU/dGhpcy5fZm9jdXNlZD0hMDplfHxQcm9taXNlLnJlc29sdmUoKS50aGVuKCgpPT57dGhpcy5fZm9jdXNlZD0hMSx0aGlzLl9vblRvdWNoZWQoKSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKX0pfSl9bmdPbkRlc3Ryb3koKXt0aGlzLl9mb2N1c01vbml0b3Iuc3RvcE1vbml0b3JpbmcodGhpcy5fZWxlbWVudFJlZil9d3JpdGVWYWx1ZShlKXt0aGlzLmNoZWNrZWQ9ISFlfXJlZ2lzdGVyT25DaGFuZ2UoZSl7dGhpcy5fb25DaGFuZ2U9ZX1yZWdpc3Rlck9uVG91Y2hlZChlKXt0aGlzLl9vblRvdWNoZWQ9ZX1zZXREaXNhYmxlZFN0YXRlKGUpe3RoaXMuZGlzYWJsZWQ9ZSx0aGlzLl9jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKX10b2dnbGUoKXt0aGlzLmNoZWNrZWQ9IXRoaXMuY2hlY2tlZCx0aGlzLl9vbkNoYW5nZSh0aGlzLmNoZWNrZWQpfV9lbWl0Q2hhbmdlRXZlbnQoKXt0aGlzLl9vbkNoYW5nZSh0aGlzLmNoZWNrZWQpLHRoaXMuY2hhbmdlLmVtaXQodGhpcy5fY3JlYXRlQ2hhbmdlRXZlbnQodGhpcy5jaGVja2VkKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtubCgpfSxuLlx1MDI3NWRpcj1IZSh7dHlwZTpuLGlucHV0czp7bmFtZToibmFtZSIsaWQ6ImlkIixsYWJlbFBvc2l0aW9uOiJsYWJlbFBvc2l0aW9uIixhcmlhTGFiZWw6WyJhcmlhLWxhYmVsIiwiYXJpYUxhYmVsIl0sYXJpYUxhYmVsbGVkYnk6WyJhcmlhLWxhYmVsbGVkYnkiLCJhcmlhTGFiZWxsZWRieSJdLGFyaWFEZXNjcmliZWRieTpbImFyaWEtZGVzY3JpYmVkYnkiLCJhcmlhRGVzY3JpYmVkYnkiXSxyZXF1aXJlZDoicmVxdWlyZWQiLGNoZWNrZWQ6ImNoZWNrZWQifSxvdXRwdXRzOntjaGFuZ2U6ImNoYW5nZSIsdG9nZ2xlQ2hhbmdlOiJ0b2dnbGVDaGFuZ2UifSxmZWF0dXJlczpbdHRdfSksbn0pKCksXzY9KCgpPT57Y2xhc3MgbiBleHRlbmRzIFlldHtjb25zdHJ1Y3RvcihlLGkscixvLHMsYSl7c3VwZXIoZSxpLHIsbyxzLGEsIm1hdC1zbGlkZS10b2dnbGUtIil9X2NyZWF0ZUNoYW5nZUV2ZW50KGUpe3JldHVybiBuZXcgY2xhc3N7Y29uc3RydWN0b3IodCxlKXt0aGlzLnNvdXJjZT10LHRoaXMuY2hlY2tlZD1lfX0odGhpcyxlKX1fb25DaGFuZ2VFdmVudChlKXtlLnN0b3BQcm9wYWdhdGlvbigpLHRoaXMudG9nZ2xlQ2hhbmdlLmVtaXQoKSx0aGlzLmRlZmF1bHRzLmRpc2FibGVUb2dnbGVWYWx1ZT90aGlzLl9pbnB1dEVsZW1lbnQubmF0aXZlRWxlbWVudC5jaGVja2VkPXRoaXMuY2hlY2tlZDoodGhpcy5jaGVja2VkPXRoaXMuX2lucHV0RWxlbWVudC5uYXRpdmVFbGVtZW50LmNoZWNrZWQsdGhpcy5fZW1pdENoYW5nZUV2ZW50KCkpfV9vbklucHV0Q2xpY2soZSl7ZS5zdG9wUHJvcGFnYXRpb24oKX1mb2N1cyhlLGkpe2k/dGhpcy5fZm9jdXNNb25pdG9yLmZvY3VzVmlhKHRoaXMuX2lucHV0RWxlbWVudCxpLGUpOnRoaXMuX2lucHV0RWxlbWVudC5uYXRpdmVFbGVtZW50LmZvY3VzKGUpfV9vbkxhYmVsVGV4dENoYW5nZSgpe3RoaXMuX2NoYW5nZURldGVjdG9yUmVmLmRldGVjdENoYW5nZXMoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShSZSksTShGciksTShubiksdm8oInRhYmluZGV4IiksTShqZXQpLE0oUGksOCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm1hdC1zbGlkZS10b2dnbGUiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiZvdChIZXQsNSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5faW5wdXRFbGVtZW50PXIuZmlyc3QpfX0saG9zdEF0dHJzOlsxLCJtYXQtc2xpZGUtdG9nZ2xlIl0saG9zdFZhcnM6MTMsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MiZlJiYoX3MoImlkIixpLmlkKSx6ZSgidGFiaW5kZXgiLG51bGwpKCJhcmlhLWxhYmVsIixudWxsKSgiYXJpYS1sYWJlbGxlZGJ5IixudWxsKSgibmFtZSIsbnVsbCksZXQoIm1hdC1jaGVja2VkIixpLmNoZWNrZWQpKCJtYXQtZGlzYWJsZWQiLGkuZGlzYWJsZWQpKCJtYXQtc2xpZGUtdG9nZ2xlLWxhYmVsLWJlZm9yZSIsImJlZm9yZSI9PWkubGFiZWxQb3NpdGlvbikoIl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIixpLl9ub29wQW5pbWF0aW9ucykpfSxpbnB1dHM6e2Rpc2FibGVkOiJkaXNhYmxlZCIsZGlzYWJsZVJpcHBsZToiZGlzYWJsZVJpcHBsZSIsY29sb3I6ImNvbG9yIix0YWJJbmRleDoidGFiSW5kZXgifSxleHBvcnRBczpbIm1hdFNsaWRlVG9nZ2xlIl0sZmVhdHVyZXM6WyR0KFtXZXRdKSx0dF0sbmdDb250ZW50U2VsZWN0b3JzOnpldCxkZWNsczoxNCx2YXJzOjIwLGNvbnN0czpbWzEsIm1hdC1zbGlkZS10b2dnbGUtbGFiZWwiXSxbImxhYmVsIiwiIl0sWzEsIm1hdC1zbGlkZS10b2dnbGUtYmFyIl0sWyJ0eXBlIiwiY2hlY2tib3giLCJyb2xlIiwic3dpdGNoIiwxLCJtYXQtc2xpZGUtdG9nZ2xlLWlucHV0IiwiY2RrLXZpc3VhbGx5LWhpZGRlbiIsMywiaWQiLCJyZXF1aXJlZCIsInRhYkluZGV4IiwiY2hlY2tlZCIsImRpc2FibGVkIiwiY2hhbmdlIiwiY2xpY2siXSxbImlucHV0IiwiIl0sWzEsIm1hdC1zbGlkZS10b2dnbGUtdGh1bWItY29udGFpbmVyIl0sWzEsIm1hdC1zbGlkZS10b2dnbGUtdGh1bWIiXSxbIm1hdC1yaXBwbGUiLCIiLDEsIm1hdC1zbGlkZS10b2dnbGUtcmlwcGxlIiwibWF0LWZvY3VzLWluZGljYXRvciIsMywibWF0UmlwcGxlVHJpZ2dlciIsIm1hdFJpcHBsZURpc2FibGVkIiwibWF0UmlwcGxlQ2VudGVyZWQiLCJtYXRSaXBwbGVSYWRpdXMiLCJtYXRSaXBwbGVBbmltYXRpb24iXSxbMSwibWF0LXJpcHBsZS1lbGVtZW50IiwibWF0LXNsaWRlLXRvZ2dsZS1wZXJzaXN0ZW50LXJpcHBsZSJdLFsxLCJtYXQtc2xpZGUtdG9nZ2xlLWNvbnRlbnQiLDMsImNka09ic2VydmVDb250ZW50Il0sWyJsYWJlbENvbnRlbnQiLCIiXSxbMiwiZGlzcGxheSIsIm5vbmUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJih4aSgpLF8oMCwibGFiZWwiLDAsMSkoMiwic3BhbiIsMikoMywiaW5wdXQiLDMsNCksUCgiY2hhbmdlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5fb25DaGFuZ2VFdmVudChvKX0pKCJjbGljayIsZnVuY3Rpb24obyl7cmV0dXJuIGkuX29uSW5wdXRDbGljayhvKX0pLHYoKSxfKDUsInNwYW4iLDUpLE8oNiwic3BhbiIsNiksXyg3LCJzcGFuIiw3KSxPKDgsInNwYW4iLDgpLHYoKSgpKCksXyg5LCJzcGFuIiw5LDEwKSxQKCJjZGtPYnNlcnZlQ29udGVudCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5fb25MYWJlbFRleHRDaGFuZ2UoKX0pLF8oMTEsInNwYW4iLDExKSxBKDEyLCJceGEwIiksdigpLFZuKDEzKSx2KCkoKSksMiZlKXtsZXQgcj0kZSgxKSxvPSRlKDEwKTt6ZSgiZm9yIixpLmlucHV0SWQpLEMoMiksZXQoIm1hdC1zbGlkZS10b2dnbGUtYmFyLW5vLXNpZGUtbWFyZ2luIiwhby50ZXh0Q29udGVudHx8IW8udGV4dENvbnRlbnQudHJpbSgpKSxDKDEpLHkoImlkIixpLmlucHV0SWQpKCJyZXF1aXJlZCIsaS5yZXF1aXJlZCkoInRhYkluZGV4IixpLnRhYkluZGV4KSgiY2hlY2tlZCIsaS5jaGVja2VkKSgiZGlzYWJsZWQiLGkuZGlzYWJsZWQpLHplKCJuYW1lIixpLm5hbWUpKCJhcmlhLWNoZWNrZWQiLGkuY2hlY2tlZCkoImFyaWEtbGFiZWwiLGkuYXJpYUxhYmVsKSgiYXJpYS1sYWJlbGxlZGJ5IixpLmFyaWFMYWJlbGxlZGJ5KSgiYXJpYS1kZXNjcmliZWRieSIsaS5hcmlhRGVzY3JpYmVkYnkpLEMoNCkseSgibWF0UmlwcGxlVHJpZ2dlciIscikoIm1hdFJpcHBsZURpc2FibGVkIixpLmRpc2FibGVSaXBwbGV8fGkuZGlzYWJsZWQpKCJtYXRSaXBwbGVDZW50ZXJlZCIsITApKCJtYXRSaXBwbGVSYWRpdXMiLDIwKSgibWF0UmlwcGxlQW5pbWF0aW9uIixPbigxOCxVZXQsaS5fbm9vcEFuaW1hdGlvbnM/MDoxNTApKX19LGRlcGVuZGVuY2llczpbWW8sd2hdLHN0eWxlczpbJy5tYXQtc2xpZGUtdG9nZ2xle2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoyNHB4O21heC13aWR0aDoxMDAlO2xpbmUtaGVpZ2h0OjI0cHg7d2hpdGUtc3BhY2U6bm93cmFwO291dGxpbmU6bm9uZTstd2Via2l0LXRhcC1oaWdobGlnaHQtY29sb3I6cmdiYSgwLDAsMCwwKX0ubWF0LXNsaWRlLXRvZ2dsZS5tYXQtY2hlY2tlZCAubWF0LXNsaWRlLXRvZ2dsZS10aHVtYi1jb250YWluZXJ7dHJhbnNmb3JtOnRyYW5zbGF0ZTNkKDE2cHgsIDAsIDApfVtkaXI9cnRsXSAubWF0LXNsaWRlLXRvZ2dsZS5tYXQtY2hlY2tlZCAubWF0LXNsaWRlLXRvZ2dsZS10aHVtYi1jb250YWluZXJ7dHJhbnNmb3JtOnRyYW5zbGF0ZTNkKC0xNnB4LCAwLCAwKX0ubWF0LXNsaWRlLXRvZ2dsZS5tYXQtZGlzYWJsZWR7b3BhY2l0eTouMzh9Lm1hdC1zbGlkZS10b2dnbGUubWF0LWRpc2FibGVkIC5tYXQtc2xpZGUtdG9nZ2xlLWxhYmVsLC5tYXQtc2xpZGUtdG9nZ2xlLm1hdC1kaXNhYmxlZCAubWF0LXNsaWRlLXRvZ2dsZS10aHVtYi1jb250YWluZXJ7Y3Vyc29yOmRlZmF1bHR9Lm1hdC1zbGlkZS10b2dnbGUtbGFiZWx7LXdlYmtpdC11c2VyLXNlbGVjdDpub25lO3VzZXItc2VsZWN0Om5vbmU7ZGlzcGxheTpmbGV4O2ZsZXg6MTtmbGV4LWRpcmVjdGlvbjpyb3c7YWxpZ24taXRlbXM6Y2VudGVyO2hlaWdodDppbmhlcml0O2N1cnNvcjpwb2ludGVyfS5tYXQtc2xpZGUtdG9nZ2xlLWNvbnRlbnR7d2hpdGUtc3BhY2U6bm93cmFwO292ZXJmbG93OmhpZGRlbjt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzfS5tYXQtc2xpZGUtdG9nZ2xlLWxhYmVsLWJlZm9yZSAubWF0LXNsaWRlLXRvZ2dsZS1sYWJlbHtvcmRlcjoxfS5tYXQtc2xpZGUtdG9nZ2xlLWxhYmVsLWJlZm9yZSAubWF0LXNsaWRlLXRvZ2dsZS1iYXJ7b3JkZXI6Mn1bZGlyPXJ0bF0gLm1hdC1zbGlkZS10b2dnbGUtbGFiZWwtYmVmb3JlIC5tYXQtc2xpZGUtdG9nZ2xlLWJhciwubWF0LXNsaWRlLXRvZ2dsZS1iYXJ7bWFyZ2luLXJpZ2h0OjhweDttYXJnaW4tbGVmdDowfVtkaXI9cnRsXSAubWF0LXNsaWRlLXRvZ2dsZS1iYXIsLm1hdC1zbGlkZS10b2dnbGUtbGFiZWwtYmVmb3JlIC5tYXQtc2xpZGUtdG9nZ2xlLWJhcnttYXJnaW4tbGVmdDo4cHg7bWFyZ2luLXJpZ2h0OjB9Lm1hdC1zbGlkZS10b2dnbGUtYmFyLW5vLXNpZGUtbWFyZ2lue21hcmdpbi1sZWZ0OjA7bWFyZ2luLXJpZ2h0OjB9Lm1hdC1zbGlkZS10b2dnbGUtdGh1bWItY29udGFpbmVye3Bvc2l0aW9uOmFic29sdXRlO3otaW5kZXg6MTt3aWR0aDoyMHB4O2hlaWdodDoyMHB4O3RvcDotM3B4O2xlZnQ6MDt0cmFuc2Zvcm06dHJhbnNsYXRlM2QoMCwgMCwgMCk7dHJhbnNpdGlvbjphbGwgODBtcyBsaW5lYXI7dHJhbnNpdGlvbi1wcm9wZXJ0eTp0cmFuc2Zvcm19Ll9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1iLWNvbnRhaW5lcnt0cmFuc2l0aW9uOm5vbmV9W2Rpcj1ydGxdIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1iLWNvbnRhaW5lcntsZWZ0OmF1dG87cmlnaHQ6MH0ubWF0LXNsaWRlLXRvZ2dsZS10aHVtYntoZWlnaHQ6MjBweDt3aWR0aDoyMHB4O2JvcmRlci1yYWRpdXM6NTAlO2Rpc3BsYXk6YmxvY2t9Lm1hdC1zbGlkZS10b2dnbGUtYmFye3Bvc2l0aW9uOnJlbGF0aXZlO3dpZHRoOjM2cHg7aGVpZ2h0OjE0cHg7ZmxleC1zaHJpbms6MDtib3JkZXItcmFkaXVzOjhweH0ubWF0LXNsaWRlLXRvZ2dsZS1pbnB1dHtib3R0b206MDtsZWZ0OjEwcHh9W2Rpcj1ydGxdIC5tYXQtc2xpZGUtdG9nZ2xlLWlucHV0e2xlZnQ6YXV0bztyaWdodDoxMHB4fS5tYXQtc2xpZGUtdG9nZ2xlLWJhciwubWF0LXNsaWRlLXRvZ2dsZS10aHVtYnt0cmFuc2l0aW9uOmFsbCA4MG1zIGxpbmVhcjt0cmFuc2l0aW9uLXByb3BlcnR5OmJhY2tncm91bmQtY29sb3I7dHJhbnNpdGlvbi1kZWxheTo1MG1zfS5fbWF0LWFuaW1hdGlvbi1ub29wYWJsZSAubWF0LXNsaWRlLXRvZ2dsZS1iYXIsLl9tYXQtYW5pbWF0aW9uLW5vb3BhYmxlIC5tYXQtc2xpZGUtdG9nZ2xlLXRodW1ie3RyYW5zaXRpb246bm9uZX0ubWF0LXNsaWRlLXRvZ2dsZSAubWF0LXNsaWRlLXRvZ2dsZS1yaXBwbGV7cG9zaXRpb246YWJzb2x1dGU7dG9wOmNhbGMoNTAlIC0gMjBweCk7bGVmdDpjYWxjKDUwJSAtIDIwcHgpO2hlaWdodDo0MHB4O3dpZHRoOjQwcHg7ei1pbmRleDoxO3BvaW50ZXItZXZlbnRzOm5vbmV9Lm1hdC1zbGlkZS10b2dnbGUgLm1hdC1zbGlkZS10b2dnbGUtcmlwcGxlIC5tYXQtcmlwcGxlLWVsZW1lbnQ6bm90KC5tYXQtc2xpZGUtdG9nZ2xlLXBlcnNpc3RlbnQtcmlwcGxlKXtvcGFjaXR5Oi4xMn0ubWF0LXNsaWRlLXRvZ2dsZS1wZXJzaXN0ZW50LXJpcHBsZXt3aWR0aDoxMDAlO2hlaWdodDoxMDAlO3RyYW5zZm9ybTpub25lfS5tYXQtc2xpZGUtdG9nZ2xlLWJhcjpob3ZlciAubWF0LXNsaWRlLXRvZ2dsZS1wZXJzaXN0ZW50LXJpcHBsZXtvcGFjaXR5Oi4wNH0ubWF0LXNsaWRlLXRvZ2dsZTpub3QoLm1hdC1kaXNhYmxlZCkuY2RrLWtleWJvYXJkLWZvY3VzZWQgLm1hdC1zbGlkZS10b2dnbGUtcGVyc2lzdGVudC1yaXBwbGV7b3BhY2l0eTouMTJ9Lm1hdC1zbGlkZS10b2dnbGUtcGVyc2lzdGVudC1yaXBwbGUsLm1hdC1zbGlkZS10b2dnbGUubWF0LWRpc2FibGVkIC5tYXQtc2xpZGUtdG9nZ2xlLWJhcjpob3ZlciAubWF0LXNsaWRlLXRvZ2dsZS1wZXJzaXN0ZW50LXJpcHBsZXtvcGFjaXR5OjB9QG1lZGlhKGhvdmVyOiBub25lKXsubWF0LXNsaWRlLXRvZ2dsZS1iYXI6aG92ZXIgLm1hdC1zbGlkZS10b2dnbGUtcGVyc2lzdGVudC1yaXBwbGV7ZGlzcGxheTpub25lfX0ubWF0LXNsaWRlLXRvZ2dsZS1pbnB1dDpmb2N1c34ubWF0LXNsaWRlLXRvZ2dsZS10aHVtYi1jb250YWluZXIgLm1hdC1mb2N1cy1pbmRpY2F0b3I6OmJlZm9yZXtjb250ZW50OiIifS5jZGstaGlnaC1jb250cmFzdC1hY3RpdmUgLm1hdC1zbGlkZS10b2dnbGUtdGh1bWIsLmNkay1oaWdoLWNvbnRyYXN0LWFjdGl2ZSAubWF0LXNsaWRlLXRvZ2dsZS1iYXJ7Ym9yZGVyOjFweCBzb2xpZH0nXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxYZXQ9e3Byb3ZpZGU6TG8sdXNlRXhpc3Rpbmc6Sm4oKCk9PlFldCksbXVsdGk6ITB9LFFldD0oKCk9PntjbGFzcyBuIGV4dGVuZHMgZ3d7fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbigpe2xldCB0O3JldHVybiBmdW5jdGlvbihpKXtyZXR1cm4odHx8KHQ9cGkobikpKShpfHxuKX19KCksbi5cdTAyNzVkaXI9SGUoe3R5cGU6bixzZWxlY3RvcnM6W1sibWF0LXNsaWRlLXRvZ2dsZSIsInJlcXVpcmVkIiwiIiwiZm9ybUNvbnRyb2xOYW1lIiwiIl0sWyJtYXQtc2xpZGUtdG9nZ2xlIiwicmVxdWlyZWQiLCIiLCJmb3JtQ29udHJvbCIsIiJdLFsibWF0LXNsaWRlLXRvZ2dsZSIsInJlcXVpcmVkIiwiIiwibmdNb2RlbCIsIiJdXSxmZWF0dXJlczpbJHQoW1hldF0pLHR0XX0pLG59KSgpLEZtZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksTm1lPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltGbWUsX2wsbG4sb2QsRm1lLGxuXX0pLG59KSgpO2Z1bmN0aW9uIFpldChuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsMyl9dmFyIExtZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5vblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2U9bmV3IEd9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktYW5ub3RhdGlvbnMtc2VhcmNoLWNvbXBvbmVudCJdXSxob3N0VmFyczoyLGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezImZSYmZXQoInZhbGlkIixpLmlzUmVnZXhGaWx0ZXJWYWxpZCl9LGlucHV0czp7cmVnZXhGaWx0ZXJWYWx1ZToicmVnZXhGaWx0ZXJWYWx1ZSIsaXNSZWdleEZpbHRlclZhbGlkOiJpc1JlZ2V4RmlsdGVyVmFsaWQifSxvdXRwdXRzOntvblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2U6Im9uUmVnZXhGaWx0ZXJWYWx1ZUNoYW5nZSJ9LGRlY2xzOjMsdmFyczoyLGNvbnN0czpbWyJzdmdJY29uIiwic2VhcmNoXzI0cHgiXSxbImF1dG9jb21wbGV0ZSIsIm9mZiIsInBsYWNlaG9sZGVyIiwiRmlsdGVyIEFubm90YXRpb25zIiwzLCJ2YWx1ZSIsImlucHV0Il0sWyJzdmdJY29uIiwiZXJyb3JfMjRweCIsImNsYXNzIiwiZXJyb3ItaWNvbiIsIm1hdFRvb2x0aXAiLCJJbnZhbGlkIHJlZ2V4IGZpbHRlci4gVGhlIHJlc3VsdCBtYXkgYmUgc3RhbGUuIiw0LCJuZ0lmIl0sWyJzdmdJY29uIiwiZXJyb3JfMjRweCIsIm1hdFRvb2x0aXAiLCJJbnZhbGlkIHJlZ2V4IGZpbHRlci4gVGhlIHJlc3VsdCBtYXkgYmUgc3RhbGUuIiwxLCJlcnJvci1pY29uIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJtYXQtaWNvbiIsMCksXygxLCJpbnB1dCIsMSksUCgiaW5wdXQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uUmVnZXhGaWx0ZXJWYWx1ZUNoYW5nZS5lbWl0KG8udGFyZ2V0LnZhbHVlKX0pLHYoKSxFKDIsWmV0LDEsMCwibWF0LWljb24iLDIpKSwyJmUmJihDKDEpLHkoInZhbHVlIixpLnJlZ2V4RmlsdGVyVmFsdWUpLEMoMSkseSgibmdJZiIsIWkuaXNSZWdleEZpbHRlclZhbGlkKSl9LGRlcGVuZGVuY2llczpbQmUsR3RdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17ZGlzcGxheTpmbGV4O3Bvc2l0aW9uOnJlbGF0aXZlfVtfbmdob3N0LSVDT01QJV06bm90KC52YWxpZCl7Y29sb3I6I2M2MjgyOH1bX25naG9zdC0lQ09NUCVdOm5vdCgudmFsaWQpICAgaW5wdXRbX25nY29udGVudC0lQ09NUCVde2NhcmV0LWNvbG9yOmN1cnJlbnRDb2xvcn1bX25naG9zdC0lQ09NUCVdOm5vdCgudmFsaWQpICAgLmVycm9yLWljb25bX25nY29udGVudC0lQ09NUCVde2NvbG9yOiNjNjI4Mjg7cG9zaXRpb246YWJzb2x1dGU7cmlnaHQ6MH0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLEJtZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLmFubm90YXRpb25zRmlsdGVyJD10aGlzLnN0b3JlLnNlbGVjdChRYiksdGhpcy5pc0Fubm90YXRpb25zRmlsdGVyVmFsaWQkPXRoaXMuYW5ub3RhdGlvbnNGaWx0ZXIkLnBpcGUoTChpPT57dHJ5e3JldHVybiBuZXcgUmVnRXhwKGkpLCEwfWNhdGNoe3JldHVybiExfX0pKX1maWx0ZXJDaGFuZ2UoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChqYih7cmVnZXg6ZX0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktYW5ub3RhdGlvbnMtc2VhcmNoIl1dLGRlY2xzOjMsdmFyczo2LGNvbnN0czpbWzMsInJlZ2V4RmlsdGVyVmFsdWUiLCJpc1JlZ2V4RmlsdGVyVmFsaWQiLCJvblJlZ2V4RmlsdGVyVmFsdWVDaGFuZ2UiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm5wbWktYW5ub3RhdGlvbnMtc2VhcmNoLWNvbXBvbmVudCIsMCksUCgib25SZWdleEZpbHRlclZhbHVlQ2hhbmdlIixmdW5jdGlvbihvKXtyZXR1cm4gaS5maWx0ZXJDaGFuZ2Uobyl9KSxCKDEsImFzeW5jIiksQigyLCJhc3luYyIpLHYoKSksMiZlJiZ5KCJyZWdleEZpbHRlclZhbHVlIixVKDEsMixpLmFubm90YXRpb25zRmlsdGVyJCkpKCJpc1JlZ2V4RmlsdGVyVmFsaWQiLFUoMiw0LGkuaXNBbm5vdGF0aW9uc0ZpbHRlclZhbGlkJCkpfSxkZXBlbmRlbmNpZXM6W0xtZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gZXR0KG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO3NuKDApLF8oMSwiYnV0dG9uIiw1KSxQKCJjbGljayIsZnVuY3Rpb24oKXtvZShlKTtsZXQgcj1TKCk7cmV0dXJuIHNlKHIub25GbGFnQW5ub3RhdGlvbnMuZW1pdChyLnNlbGVjdGVkQW5ub3RhdGlvbnMpKX0pLE8oMiwibWF0LWljb24iLDYpLHYoKSxfKDMsImJ1dHRvbiIsNyksUCgiY2xpY2siLGZ1bmN0aW9uKCl7b2UoZSk7bGV0IHI9UygpO3JldHVybiBzZShyLm9uSGlkZUFubm90YXRpb25zLmVtaXQoci5zZWxlY3RlZEFubm90YXRpb25zKSl9KSxPKDQsIm1hdC1pY29uIiw4KSx2KCksYW4oKX1pZigyJm4pe2xldCBlPVMoKTtDKDEpLHkoImRpc2FibGVkIiwwPT09ZS5zZWxlY3RlZEFubm90YXRpb25zLmxlbmd0aCksQygyKSx5KCJkaXNhYmxlZCIsMD09PWUuc2VsZWN0ZWRBbm5vdGF0aW9ucy5sZW5ndGgpfX1mdW5jdGlvbiB0dHQobix0KXtpZigxJm4pe2xldCBlPVBlKCk7c24oMCksXygxLCJtYXQtc2xpZGUtdG9nZ2xlIiw5KSxQKCJjaGFuZ2UiLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5vblRvZ2dsZVNob3dDb3VudHMuZW1pdCgpKX0pLEEoMiwiIFNhbXBsZSBDb3VudCAiKSx2KCksXygzLCJtYXQtc2xpZGUtdG9nZ2xlIiwxMCksUCgiY2hhbmdlIixmdW5jdGlvbigpe3JldHVybiBvZShlKSxzZShTKCkub25Ub2dnbGVTaG93SGlkZGVuLmVtaXQoKSl9KSxBKDQsIiBTaG93IEhpZGRlbiAiKSx2KCksTyg1LCJucG1pLWFubm90YXRpb25zLXNlYXJjaCIpLGFuKCl9aWYoMiZuKXtsZXQgZT1TKCk7QygxKSx5KCJjaGVja2VkIixlLnNob3dDb3VudHMpLEMoMikseSgiY2hlY2tlZCIsZS5zaG93SGlkZGVuKX19dmFyIFZtZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5vbkZsYWdBbm5vdGF0aW9ucz1uZXcgRyx0aGlzLm9uSGlkZUFubm90YXRpb25zPW5ldyBHLHRoaXMub25Ub2dnbGVFeHBhbmRlZD1uZXcgRyx0aGlzLm9uVG9nZ2xlU2hvd0NvdW50cz1uZXcgRyx0aGlzLm9uVG9nZ2xlU2hvd0hpZGRlbj1uZXcgR319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibnBtaS1hbm5vdGF0aW9ucy1saXN0LXRvb2xiYXItY29tcG9uZW50Il1dLGlucHV0czp7bnVtQW5ub3RhdGlvbnM6Im51bUFubm90YXRpb25zIixleHBhbmRlZDoiZXhwYW5kZWQiLHNlbGVjdGVkQW5ub3RhdGlvbnM6InNlbGVjdGVkQW5ub3RhdGlvbnMiLGFubm90YXRpb25zRXhwYW5kZWQ6ImFubm90YXRpb25zRXhwYW5kZWQiLHNob3dDb3VudHM6InNob3dDb3VudHMiLHNob3dIaWRkZW46InNob3dIaWRkZW4ifSxvdXRwdXRzOntvbkZsYWdBbm5vdGF0aW9uczoib25GbGFnQW5ub3RhdGlvbnMiLG9uSGlkZUFubm90YXRpb25zOiJvbkhpZGVBbm5vdGF0aW9ucyIsb25Ub2dnbGVFeHBhbmRlZDoib25Ub2dnbGVFeHBhbmRlZCIsb25Ub2dnbGVTaG93Q291bnRzOiJvblRvZ2dsZVNob3dDb3VudHMiLG9uVG9nZ2xlU2hvd0hpZGRlbjoib25Ub2dnbGVTaG93SGlkZGVuIn0sZGVjbHM6Nyx2YXJzOjQsY29uc3RzOmZ1bmN0aW9uKCl7bGV0IHQsZSxpO3JldHVybiB0PSRsb2NhbGl6ZWA6TGFiZWwgZm9yIGEgYnV0dG9uIHRoYXQgaGlkZXMvc2hvd3MgdGhlIGFubm90YXRpb25zIGxpc3Qu4pCfYjM2MDNiYTMzZTUzMDhkZDhjNWU4MDVlNTA4YjJmNzIzM2RmODlkNOKQnzczMzYzNzQ0MTMwNTYzNDI0OTI6SGlkZXMvU2hvd3MgdGhlIEFubm90YXRpb25zIExpc3RgLGU9JGxvY2FsaXplYDpMYWJlbCBmb3IgYSBidXR0b24gdGhhdCBmbGFncyBzZWxlY3RlZCBhbm5vdGF0aW9ucy7ikJ81NjkyYWQ4ODMxMDM4YTkwYzU4NjNhMWU5YWRmOTc0OGNhYzNjYWQ44pCfMjI0NDA5OTg5MTMxMzMzNjU5NTpGbGFnIFNlbGVjdGVkIEFubm90YXRpb25zYCxpPSRsb2NhbGl6ZWA6TGFiZWwgZm9yIGEgYnV0dG9uIHRoYXQgaGlkZXMgc2VsZWN0ZWQgYW5ub3RhdGlvbnMu4pCfMDM0MmNkYjMzNThmYThlM2ZhMjcyMjBhODI1OGE3Mjg3NDMwYjcwZuKQnzU0NjI4MzIzOTEwOTIwODc0ODU6SGlkZSBTZWxlY3RlZCBBbm5vdGF0aW9uc2AsW1sxLCJhbm5vdGF0aW9ucy10aXRsZS1jb250YWluZXIiXSxbMSwiYW5ub3RhdGlvbnMtdGl0bGUiXSxbNCwibmdJZiJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsdCwxLCJleHBhbmQtYnV0dG9uIiwzLCJjbGljayJdLFszLCJzdmdJY29uIl0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJhcmlhLWxhYmVsIixlLCJ0aXRsZSIsIkZsYWdnaW5nIGFubm90YXRpb25zIGFkZHMgdGhlbSB0byB5b3VyIGludmVzdGlnYXRpb24gcmVzdWx0cywgd2hpY2ggY2FuIGxhdGVyIGJlIGV4cG9ydGVkLiIsMywiZGlzYWJsZWQiLCJjbGljayJdLFsic3ZnSWNvbiIsImZsYWdfMjRweCJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsaSwidGl0bGUiLCJSZW1vdmluZyBub24tY3JpdGljYWwgYW5ub3RhdGlvbnMgdW5jbHV0dGVycyB0aGUgdmlldy4gUmVtb3ZlZCBhbm5vdGF0aW9ucyBhcmUgcmVtb3ZlZCBmcm9tIGFsbCB2aXN1YWxpemF0aW9ucy4iLDMsImRpc2FibGVkIiwiY2xpY2siXSxbInN2Z0ljb24iLCJ2aXNpYmlsaXR5X29mZl8yNHB4Il0sWyJ0aXRsZSIsIkhpZGVzIGFuZCBzaG93cyB0aGUgc2FtcGxlIGNvdW50IHdoZXJlIGFwcGxpY2FibGUgKGhvdyBtYW55IHNhbXBsZXMgYmVsb25nIHRvIGEgY2F0ZWdvcnkpLiIsMSwic2hvdy10b2dnbGUiLDMsImNoZWNrZWQiLCJjaGFuZ2UiXSxbInRpdGxlIiwiSGlkZXMgYW5kIHNob3dzIGhpZGRlbiBhbm5vdGF0aW9ucyBpbiBhbGwgdmlzdWFsaXphdGlvbnMuIiwxLCJzaG93LXRvZ2dsZSIsMywiY2hlY2tlZCIsImNoYW5nZSJdXX0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCkoMSwiaDMiLDEpLEEoMiksdigpLEUoMyxldHQsNSwyLCJuZy1jb250YWluZXIiLDIpLHYoKSxFKDQsdHR0LDYsMiwibmctY29udGFpbmVyIiwyKSxfKDUsImJ1dHRvbiIsMyksUCgiY2xpY2siLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25Ub2dnbGVFeHBhbmRlZC5lbWl0KCl9KSxPKDYsIm1hdC1pY29uIiw0KSx2KCkpLDImZSYmKEMoMiksamUoIkFubm90YXRpb25zICgiLGkubnVtQW5ub3RhdGlvbnMsIikiKSxDKDEpLHkoIm5nSWYiLGkuZXhwYW5kZWQpLEMoMSkseSgibmdJZiIsaS5leHBhbmRlZCksQygyKSx5KCJzdmdJY29uIixpLmV4cGFuZGVkPyJleHBhbmRfbGVzc18yNHB4IjoiZXhwYW5kX21vcmVfMjRweCIpKX0sZGVwZW5kZW5jaWVzOltCZSxHdCxfbixfNixCbWVdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2JveC1zaXppbmc6Ym9yZGVyLWJveDtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246cm93O3BhZGRpbmc6MCAxNnB4O3dpZHRoOjEwMCV9LmFubm90YXRpb25zLXRpdGxlW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmlubGluZTtmb250LXNpemU6LjllbTtmb250LXdlaWdodDo1MDA7cGFkZGluZy1yaWdodDoxMHB4fS5hbm5vdGF0aW9ucy10aXRsZS1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2FsaWduLWl0ZW1zOmNlbnRlcjtkaXNwbGF5OmZsZXg7ZmxleC13cmFwOm5vd3JhcDtmbGV4OjEgMTtoZWlnaHQ6NDJweH0uc2hvdy10b2dnbGVbX25nY29udGVudC0lQ09NUCVde2ZvbnQtc2l6ZTouOWVtO21hcmdpbi1yaWdodDouOGVtfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksSG1lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuc2VsZWN0ZWRBbm5vdGF0aW9ucyQ9dGhpcy5zdG9yZS5zZWxlY3QodmMpLHRoaXMuYW5ub3RhdGlvbnNFeHBhbmRlZCQ9dGhpcy5zdG9yZS5zZWxlY3QoVUYpLHRoaXMuc2hvd0NvdW50cyQ9dGhpcy5zdG9yZS5zZWxlY3QoakYpLHRoaXMuc2hvd0hpZGRlbiQ9dGhpcy5zdG9yZS5zZWxlY3QoWmIpLHRoaXMuYW5ub3RhdGlvbnNGaWx0ZXIkPXRoaXMuc3RvcmUuc2VsZWN0KFFiKSx0aGlzLmlzQW5ub3RhdGlvbnNGaWx0ZXJWYWxpZCQ9dGhpcy5hbm5vdGF0aW9uc0ZpbHRlciQucGlwZShMKGk9Pnt0cnl7cmV0dXJuIEJvb2xlYW4obmV3IFJlZ0V4cChpKSl9Y2F0Y2h7cmV0dXJuITF9fSkpfWZpbHRlckNoYW5nZShlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKGpiKHtyZWdleDplfSkpfWZsYWdBbm5vdGF0aW9ucyhlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKEVGKHthbm5vdGF0aW9uczplfSkpfWhpZGVBbm5vdGF0aW9ucyhlKXt0aGlzLnN0b3JlLmRpc3BhdGNoKFRGKHthbm5vdGF0aW9uczplfSkpfXRvZ2dsZUV4cGFuZGVkKCl7dGhpcy5zdG9yZS5kaXNwYXRjaChSRigpKX10b2dnbGVTaG93Q291bnRzKCl7dGhpcy5zdG9yZS5kaXNwYXRjaChPRigpKX10b2dnbGVTaG93SGlkZGVuKCl7dGhpcy5zdG9yZS5kaXNwYXRjaChrRigpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktYW5ub3RhdGlvbnMtbGlzdC10b29sYmFyIl1dLGlucHV0czp7bnVtQW5ub3RhdGlvbnM6Im51bUFubm90YXRpb25zIixleHBhbmRlZDoiZXhwYW5kZWQifSxkZWNsczo1LHZhcnM6MTQsY29uc3RzOltbMywibnVtQW5ub3RhdGlvbnMiLCJleHBhbmRlZCIsInNlbGVjdGVkQW5ub3RhdGlvbnMiLCJhbm5vdGF0aW9uc0V4cGFuZGVkIiwic2hvd0NvdW50cyIsInNob3dIaWRkZW4iLCJvbkZsYWdBbm5vdGF0aW9ucyIsIm9uSGlkZUFubm90YXRpb25zIiwib25Ub2dnbGVFeHBhbmRlZCIsIm9uVG9nZ2xlU2hvd0NvdW50cyIsIm9uVG9nZ2xlU2hvd0hpZGRlbiJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwibnBtaS1hbm5vdGF0aW9ucy1saXN0LXRvb2xiYXItY29tcG9uZW50IiwwKSxQKCJvbkZsYWdBbm5vdGF0aW9ucyIsZnVuY3Rpb24obyl7cmV0dXJuIGkuZmxhZ0Fubm90YXRpb25zKG8pfSkoIm9uSGlkZUFubm90YXRpb25zIixmdW5jdGlvbihvKXtyZXR1cm4gaS5oaWRlQW5ub3RhdGlvbnMobyl9KSgib25Ub2dnbGVFeHBhbmRlZCIsZnVuY3Rpb24oKXtyZXR1cm4gaS50b2dnbGVFeHBhbmRlZCgpfSkoIm9uVG9nZ2xlU2hvd0NvdW50cyIsZnVuY3Rpb24oKXtyZXR1cm4gaS50b2dnbGVTaG93Q291bnRzKCl9KSgib25Ub2dnbGVTaG93SGlkZGVuIixmdW5jdGlvbigpe3JldHVybiBpLnRvZ2dsZVNob3dIaWRkZW4oKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLEIoNCwiYXN5bmMiKSx2KCkpLDImZSYmeSgibnVtQW5ub3RhdGlvbnMiLGkubnVtQW5ub3RhdGlvbnMpKCJleHBhbmRlZCIsaS5leHBhbmRlZCkoInNlbGVjdGVkQW5ub3RhdGlvbnMiLFUoMSw2LGkuc2VsZWN0ZWRBbm5vdGF0aW9ucyQpKSgiYW5ub3RhdGlvbnNFeHBhbmRlZCIsVSgyLDgsaS5hbm5vdGF0aW9uc0V4cGFuZGVkJCkpKCJzaG93Q291bnRzIixVKDMsMTAsaS5zaG93Q291bnRzJCkpKCJzaG93SGlkZGVuIixVKDQsMTIsaS5zaG93SGlkZGVuJCkpfSxkZXBlbmRlbmNpZXM6W1ZtZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCk7ZnVuY3Rpb24gcnR0KG4sdCl7aWYoMSZuJiZPKDAsIm1hdC1pY29uIiw4KSwyJm4pe2xldCBlPVMoMik7eSgic3ZnSWNvbiIsZS5zb3J0Lm9yZGVyPT09ZS5Tb3J0T3JkZXIuREVTQ0VORElORz8iYXJyb3dfZG93bndhcmRfMjRweCI6ImFycm93X3Vwd2FyZF8yNHB4IikoIm5nQ2xhc3MiLGUuc29ydC5vcmRlcj09PWUuU29ydE9yZGVyLkRFU0NFTkRJTkc/ImRvd24taWNvbiI6InVwLWljb24iKX19ZnVuY3Rpb24gb3R0KG4sdCl7aWYoMSZuKXtsZXQgZT1QZSgpO18oMCwiZGl2Iiw0KSgxLCJkaXYiLDUpKDIsImRpdiIsNiksUCgiY2xpY2siLGZ1bmN0aW9uKCl7bGV0IG89b2UoZSkuJGltcGxpY2l0O3JldHVybiBzZShTKCkub25DaGFuZ2VTb3J0LmVtaXQobykpfSksQSgzKSxFKDQscnR0LDEsMiwibWF0LWljb24iLDcpLHYoKSgpKCl9aWYoMiZuKXtsZXQgZT10LiRpbXBsaWNpdCxpPVMoKTtDKDMpLGplKCIgIixpLnN0cmlwTWV0cmljKGUpLCIgIiksQygxKSx5KCJuZ0lmIixlPT09aS5zb3J0Lm1ldHJpYyl9fXZhciBVbWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMub25DaGFuZ2VTb3J0PW5ldyBHLHRoaXMub25BbGxBbm5vdGF0aW9uc1RvZ2dsZWQ9bmV3IEcsdGhpcy5Tb3J0T3JkZXI9JHJ9c3RyaXBNZXRyaWMoZSl7cmV0dXJuIFNzKGUpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJucG1pLWFubm90YXRpb25zLWxpc3QtaGVhZGVyLWNvbXBvbmVudCJdXSxpbnB1dHM6e251bUFubm90YXRpb25zOiJudW1Bbm5vdGF0aW9ucyIsc2VsZWN0ZWRBbm5vdGF0aW9uczoic2VsZWN0ZWRBbm5vdGF0aW9ucyIsYWN0aXZlTWV0cmljczoiYWN0aXZlTWV0cmljcyIsc29ydDoic29ydCJ9LG91dHB1dHM6e29uQ2hhbmdlU29ydDoib25DaGFuZ2VTb3J0IixvbkFsbEFubm90YXRpb25zVG9nZ2xlZDoib25BbGxBbm5vdGF0aW9uc1RvZ2dsZWQifSxkZWNsczo0LHZhcnM6Mixjb25zdHM6W1sxLCJ0b2dnbGUtYWxsLWNvbnRhaW5lciJdLFszLCJjaGVja2VkIiwiY2hhbmdlIl0sWzEsImFubm90YXRpb25zLWhlYWRlci1jb250YWluZXJzIl0sWyJjbGFzcyIsImhlYWRlci1jb2x1bW4iLDQsIm5nRm9yIiwibmdGb3JPZiJdLFsxLCJoZWFkZXItY29sdW1uIl0sWzEsImhlYWRlci1jb250YWluZXIiXSxbInRhYmluZGV4IiwiMCIsInJvbGUiLCJidXR0b24iLCJ0aXRsZSIsIkNoYW5nZSB0aGUgc29ydCBieSBjbGlja2luZyBhbnkgb2YgdGhlIG1ldHJpY3MuIiwxLCJoZWFkZXItY2xpY2thYmxlIiwzLCJjbGljayJdLFsiY2xhc3MiLCJzb3J0LWljb24iLDMsInN2Z0ljb24iLCJuZ0NsYXNzIiw0LCJuZ0lmIl0sWzEsInNvcnQtaWNvbiIsMywic3ZnSWNvbiIsIm5nQ2xhc3MiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCkoMSwibWF0LWNoZWNrYm94IiwxKSxQKCJjaGFuZ2UiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uQWxsQW5ub3RhdGlvbnNUb2dnbGVkLmVtaXQoby5jaGVja2VkKX0pLHYoKSgpLF8oMiwiZGl2IiwyKSxFKDMsb3R0LDUsMiwiZGl2IiwzKSx2KCkpLDImZSYmKEMoMSkseSgiY2hlY2tlZCIsaS5zZWxlY3RlZEFubm90YXRpb25zLmxlbmd0aD09PWkubnVtQW5ub3RhdGlvbnMpLEMoMikseSgibmdGb3JPZiIsaS5hY3RpdmVNZXRyaWNzKSl9LGRlcGVuZGVuY2llczpbRm4sZG4sQmUseWwsR3RdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17Ym9yZGVyLWJvdHRvbToycHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmZsZXg7aGVpZ2h0OjI4cHg7YWxpZ24taXRlbXM6ZmxleC1lbmQ7bWFyZ2luLXRvcDo4cHh9LmFubm90YXRpb25zLWhlYWRlci1jb250YWluZXJzW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmZsZXg7Zm9udC1zaXplOi45ZW07Zm9udC13ZWlnaHQ6NTAwO2ZsZXgtZ3JvdzoxfS5oZWFkZXItY29sdW1uW19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4OjEgMX0uaGVhZGVyLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTppbmxpbmUtYmxvY2t9LmhlYWRlci1jbGlja2FibGVbX25nY29udGVudC0lQ09NUCVde2N1cnNvcjpwb2ludGVyO2Rpc3BsYXk6ZmxleDtvdXRsaW5lOm5vbmV9LnRvZ2dsZS1hbGwtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXttYXJnaW4tbGVmdDoxMHB4O3dpZHRoOjkwcHh9LnNvcnQtaWNvbltfbmdjb250ZW50LSVDT01QJV17aGVpZ2h0OjE2cHh9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSx6bWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5zZWxlY3RlZEFubm90YXRpb25zJD10aGlzLnN0b3JlLnNlbGVjdCh2YyksdGhpcy5hbm5vdGF0aW9uU29ydCQ9dGhpcy5zdG9yZS5zZWxlY3QoS2IpfWNoYW5nZVNvcnQoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaChBRih7bWV0cmljOmV9KSl9YWxsQW5ub3RhdGlvbnNUb2dnbGVkKGUpe3RoaXMuc3RvcmUuZGlzcGF0Y2goUkUoZT97YW5ub3RhdGlvbnM6T2JqZWN0LmtleXModGhpcy5hbm5vdGF0aW9ucyl9Onthbm5vdGF0aW9uczpbXX0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktYW5ub3RhdGlvbnMtbGlzdC1oZWFkZXIiXV0saW5wdXRzOntudW1Bbm5vdGF0aW9uczoibnVtQW5ub3RhdGlvbnMiLGFubm90YXRpb25zOiJhbm5vdGF0aW9ucyIsYWN0aXZlTWV0cmljczoiYWN0aXZlTWV0cmljcyJ9LGRlY2xzOjMsdmFyczo4LGNvbnN0czpbWzMsIm51bUFubm90YXRpb25zIiwic2VsZWN0ZWRBbm5vdGF0aW9ucyIsInNvcnQiLCJhY3RpdmVNZXRyaWNzIiwib25DaGFuZ2VTb3J0Iiwib25BbGxBbm5vdGF0aW9uc1RvZ2dsZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm5wbWktYW5ub3RhdGlvbnMtbGlzdC1oZWFkZXItY29tcG9uZW50IiwwKSxQKCJvbkNoYW5nZVNvcnQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLmNoYW5nZVNvcnQobyl9KSgib25BbGxBbm5vdGF0aW9uc1RvZ2dsZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLmFsbEFubm90YXRpb25zVG9nZ2xlZChvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksdigpKSwyJmUmJnkoIm51bUFubm90YXRpb25zIixpLm51bUFubm90YXRpb25zKSgic2VsZWN0ZWRBbm5vdGF0aW9ucyIsVSgxLDQsaS5zZWxlY3RlZEFubm90YXRpb25zJCkpKCJzb3J0IixVKDIsNixpLmFubm90YXRpb25Tb3J0JCkpKCJhY3RpdmVNZXRyaWNzIixpLmFjdGl2ZU1ldHJpY3MpfSxkZXBlbmRlbmNpZXM6W1VtZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksbHR0PVsiZ2x5cGgiXSxqbWU9KCgpPT57Y2xhc3MgbntuZ0FmdGVyVmlld0luaXQoKXt0aGlzLnN2Zz1ibyh0aGlzLmdseXBoU1ZHLm5hdGl2ZUVsZW1lbnQpLHRoaXMubWFpbkNvbnRhaW5lcj10aGlzLnN2Zy5hcHBlbmQoImciKSx0aGlzLmRyYXcoKX1kcmF3KCl7ImNpcmNsZSI9PXRoaXMuc2hhcGU/dGhpcy5tYWluQ29udGFpbmVyLmFwcGVuZCgiY2lyY2xlIikuYXR0cigiZmlsbCIsdGhpcy5jb2xvcikuYXR0cigic3Ryb2tlIiwiYmxhY2siKS5hdHRyKCJjeCIsNSkuYXR0cigiY3kiLDUpLmF0dHIoInIiLDUpOiJiYXIiPT10aGlzLnNoYXBlP3RoaXMubWFpbkNvbnRhaW5lci5hcHBlbmQoInJlY3QiKS5hdHRyKCJmaWxsIix0aGlzLmNvbG9yKS5hdHRyKCJ4IiwwKS5hdHRyKCJ5IiwwKS5hdHRyKCJ3aWR0aCIsMTApLmF0dHIoImhlaWdodCIsMTApOiJydW5JbmRpY2F0b3IiPT10aGlzLnNoYXBlJiZ0aGlzLm1haW5Db250YWluZXIuYXBwZW5kKCJnIikuYXBwZW5kKCJwYXRoIikuYXR0cigiZmlsbCIsdGhpcy5jb2xvcikuYXR0cigic3Ryb2tlIiwiYmxhY2siKS5hdHRyKCJkIiwiTSAyIDAgTCAxMCAwIEwgNyA1IEwgMTAgMTAgTCAyIDEwIFoiKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibnBtaS1sZWdlbmQtZWxlbWVudCJdXSx2aWV3UXVlcnk6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJm90KGx0dCw3LFJlKSwyJmUpe2xldCByO05lKHI9TGUoKSkmJihpLmdseXBoU1ZHPXIuZmlyc3QpfX0saW5wdXRzOnt0ZXh0OiJ0ZXh0Iixjb2xvcjoiY29sb3IiLHNoYXBlOiJzaGFwZSJ9LGRlY2xzOjQsdmFyczoxLGNvbnN0czpbWzEsImdseXBoIl0sWyJnbHlwaCIsIiJdLFsxLCJsZWdlbmQtZWxlbWVudC10aXRsZSJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKEluKCksTygwLCJzdmciLDAsMSksSnMoKSxfKDIsImRpdiIsMiksQSgzKSx2KCkpLDImZSYmKEMoMykseXQoaS50ZXh0KSl9LHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2Rpc3BsYXk6ZmxleDtwYWRkaW5nLXJpZ2h0OjEwcHh9LmxlZ2VuZC1lbGVtZW50LXRpdGxlW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXNpemU6LjhlbTtwYWRkaW5nLWxlZnQ6NXB4fS5nbHlwaFtfbmdjb250ZW50LSVDT01QJV17d2lkdGg6MTBweDtoZWlnaHQ6MTBweH0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLEdtZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJucG1pLWFubm90YXRpb25zLWxpc3QtbGVnZW5kIl1dLGRlY2xzOjQsdmFyczowLGNvbnN0czpbWyJ0ZXh0IiwicnVuIGluZGljYXRvciIsImNvbG9yIiwicmdiKDAsMCwwKSIsInNoYXBlIiwicnVuSW5kaWNhdG9yIl0sWyJ0ZXh0IiwicG9zaXRpdmUgY29ycmVsYXRpb24iLCJjb2xvciIsInJnYigxMDksIDE3NCwgMjEzKSIsInNoYXBlIiwiYmFyIl0sWyJ0ZXh0IiwibmVnYXRpdmUgY29ycmVsYXRpb24iLCJjb2xvciIsInJnYigyNDksIDEwNSwgNzYpIiwic2hhcGUiLCJiYXIiXSxbInRleHQiLCJzYW1wbGUgY291bnQiLCJjb2xvciIsInJnYigxNTEsIDE1MSwgMTUxKSIsInNoYXBlIiwiY2lyY2xlIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiZPKDAsIm5wbWktbGVnZW5kLWVsZW1lbnQiLDApKDEsIm5wbWktbGVnZW5kLWVsZW1lbnQiLDEpKDIsIm5wbWktbGVnZW5kLWVsZW1lbnQiLDIpKDMsIm5wbWktbGVnZW5kLWVsZW1lbnQiLDMpfSxkZXBlbmRlbmNpZXM6W2ptZV0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXXtkaXNwbGF5OmZsZXg7cGFkZGluZzowIDE2cHh9Il19KSxufSkoKSxkdHQ9WyJjaGFydCJdLHB0dD1bImhpbnRDbGlwIl07ZnVuY3Rpb24gaHR0KG4sdCl7MSZuJiZPKDAsIm1hdC1pY29uIiwxMil9ZnVuY3Rpb24gZnR0KG4sdCl7MSZuJiZPKDAsIm1hdC1pY29uIiwxMyl9ZnVuY3Rpb24gbXR0KG4sdCl7aWYoMSZuJiZPKDAsIm1hdC1pY29uIiwxNCksMiZuKXtsZXQgZT1TKCk7eSgic3ZnSWNvbiIsZS5zb3J0Lm9yZGVyPT09ZS5Tb3J0T3JkZXIuU0lNSUxBUj8iYXJyb3dfZG93bndhcmRfMjRweCI6ImFycm93X3Vwd2FyZF8yNHB4IikoIm5nQ2xhc3MiLGUuc29ydC5vcmRlcj09PWUuU29ydE9yZGVyLlNJTUlMQVI/ImRvd24taWNvbiI6InVwLWljb24iKX19dmFyIFdtZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5zZWxlY3RlZD0hMSx0aGlzLm9uU2hvd1NpbWlsYXJBbm5vdGF0aW9ucz1uZXcgRyx0aGlzLlNvcnRPcmRlcj0kcix0aGlzLndpZHRoPTEwLHRoaXMuY2hhcnRXaWR0aD0xMCx0aGlzLmNoYXJ0SGVpZ2h0PTEwLHRoaXMubWF4RG90UmFkaXVzPTEwLHRoaXMuY291bnREb3RPZmZzZXQ9NzAsdGhpcy5jb3VudFRleHRQYWRkaW5nPTIsdGhpcy5tYXJnaW49e3RvcDowLHJpZ2h0OjAsYm90dG9tOjAsbGVmdDoxMDB9LHRoaXMuc3Ryb2tlQ29sb3I9IiNmZmYiLHRoaXMudGV4dENsYXNzPSJkZWZhdWx0LXRleHQiLHRoaXMucnVucz1bXX1vblJlc2l6ZShlKXt0aGlzLnJlZHJhdygpfW5nQWZ0ZXJWaWV3SW5pdCgpe3RoaXMuc3ZnPWJvKHRoaXMuYW5ub3RhdGlvbkNvbnRhaW5lci5uYXRpdmVFbGVtZW50KS5zZWxlY3QoInN2ZyIpLHRoaXMueFNjYWxlPVN5KCkucGFkZGluZygwKSx0aGlzLnlTY2FsZT1TeSgpLnBhZGRpbmcoMCksdGhpcy5zaXplU2NhbGU9UW8oKS5kb21haW4oWzAsMV0pLHRoaXMuY291bnRTaXplU2NhbGU9UW8oKS5yYW5nZShbMix0aGlzLm1heERvdFJhZGl1c10pLHRoaXMubWFpbkNvbnRhaW5lcj10aGlzLnN2Zy5hcHBlbmQoImciKS5hdHRyKCJ0cmFuc2Zvcm0iLGB0cmFuc2xhdGUoJHt0aGlzLm1hcmdpbi5sZWZ0fSwgJHt0aGlzLm1hcmdpbi50b3B9KWApLHRoaXMuYmFyc0dyb3VwPXRoaXMubWFpbkNvbnRhaW5lci5hcHBlbmQoImciKSx0aGlzLmNvdW50RG90c0dyb3VwPXRoaXMubWFpbkNvbnRhaW5lci5hcHBlbmQoImciKSx0aGlzLnRleHRzR3JvdXA9dGhpcy5tYWluQ29udGFpbmVyLmFwcGVuZCgiZyIpLHRoaXMuY291bnRUZXh0c0dyb3VwPXRoaXMubWFpbkNvbnRhaW5lci5hcHBlbmQoImciKSx0aGlzLnJ1bkhpbnRHcm91cD10aGlzLnN2Zy5hcHBlbmQoImciKSx0aGlzLnJlZHJhdygpfW5nT25DaGFuZ2VzKGUpe3RoaXMuc3ZnJiZ0aGlzLnJlZHJhdygpfXJlZHJhdygpe3RoaXMuc2VsZWN0ZWQ9dGhpcy5zZWxlY3RlZEFubm90YXRpb25zLmluY2x1ZGVzKHRoaXMuYW5ub3RhdGlvbiksdGhpcy51cGRhdGVEaW1lbnNpb25zKCksdGhpcy5zZXRUZXh0Q2xhc3MoKSx0aGlzLnVwZGF0ZUF4ZXMoKSx0aGlzLmRyYXcoKX11cGRhdGVEaW1lbnNpb25zKCl7bGV0IGU9bmV3IFNldDt0aGlzLmRhdGEuZm9yRWFjaChpPT57ZS5hZGQoaS5ydW4pfSksdGhpcy5ydW5zPVsuLi5lXSx0aGlzLnN2Zy5zdHlsZSgiaGVpZ2h0Iix0aGlzLm51bUFjdGl2ZVJ1bnMqdGhpcy5ydW5IZWlnaHQrInB4IiksdGhpcy5jaGFydEhlaWdodD10aGlzLnJ1bnMubGVuZ3RoKnRoaXMucnVuSGVpZ2h0LXRoaXMubWFyZ2luLnRvcC10aGlzLm1hcmdpbi5ib3R0b20sdGhpcy53aWR0aD10aGlzLmFubm90YXRpb25Db250YWluZXIubmF0aXZlRWxlbWVudC5jbGllbnRXaWR0aHx8MTAsdGhpcy5jaGFydFdpZHRoPXRoaXMud2lkdGgtdGhpcy5tYXJnaW4ubGVmdC10aGlzLm1hcmdpbi5yaWdodH1zZXRUZXh0Q2xhc3MoKXt0aGlzLnRleHRDbGFzcz0iZGVmYXVsdC10ZXh0Iix0aGlzLmZsYWdnZWRBbm5vdGF0aW9ucy5pbmNsdWRlcyh0aGlzLmFubm90YXRpb24pP3RoaXMudGV4dENsYXNzPSJmbGFnLXRleHQiOnRoaXMuaGlkZGVuQW5ub3RhdGlvbnMuaW5jbHVkZXModGhpcy5hbm5vdGF0aW9uKSYmKHRoaXMudGV4dENsYXNzPSJoaWRkZW4tdGV4dCIpfXVwZGF0ZUF4ZXMoKXt0aGlzLnhTY2FsZS5yYW5nZVJvdW5kKFswLHRoaXMuY2hhcnRXaWR0aC10aGlzLmNoYXJ0V2lkdGgvdGhpcy5hY3RpdmVNZXRyaWNzLmxlbmd0aF0pLmRvbWFpbih0aGlzLmFjdGl2ZU1ldHJpY3MubWFwKGU9PlNzKGUpKSksdGhpcy55U2NhbGUucmFuZ2VSb3VuZChbMCx0aGlzLmNoYXJ0SGVpZ2h0LXRoaXMucnVuSGVpZ2h0XSkuZG9tYWluKHRoaXMucnVucyksdGhpcy5zaXplU2NhbGUucmFuZ2UoWzAsdGhpcy5jaGFydFdpZHRoL3RoaXMuYWN0aXZlTWV0cmljcy5sZW5ndGhdKSx0aGlzLmNvdW50U2l6ZVNjYWxlLmRvbWFpbihbMCx0aGlzLm1heENvdW50XSl9ZHJhdygpe3RoaXMuZHJhd1J1bkluZGljYXRvcnMoKSx0aGlzLmRyYXdSdW5IaW50VGV4dHMoKSx0aGlzLmRyYXdCYXJzKCksdGhpcy5kcmF3VGV4dHMoKSx0aGlzLnNob3dDb3VudHM/KHRoaXMuZHJhd0NvdW50RG90cygpLHRoaXMuZHJhd0NvdW50VGV4dHMoKSk6KHRoaXMuY291bnREb3RzR3JvdXAuc2VsZWN0QWxsKCIuY291bnQtZG90IikucmVtb3ZlKCksdGhpcy5jb3VudFRleHRzR3JvdXAuc2VsZWN0QWxsKCIuY291bnQtYmFja2dyb3VuZC10ZXh0IikucmVtb3ZlKCksdGhpcy5jb3VudFRleHRzR3JvdXAuc2VsZWN0QWxsKCIuY291bnQtdGV4dCIpLnJlbW92ZSgpKX1kcmF3UnVuSW5kaWNhdG9ycygpe2JvKHRoaXMuY2xpcFBhdGhFbGVtZW50Lm5hdGl2ZUVsZW1lbnQpLnNlbGVjdCgicmVjdCIpLmF0dHIoIndpZHRoIix0aGlzLm1hcmdpbi5sZWZ0LTMwKS5hdHRyKCJoZWlnaHQiLHRoaXMuY2hhcnRIZWlnaHQpO2xldCBlPXRoaXMucnVuSGludEdyb3VwLnNlbGVjdEFsbCgiLmhpbnQiKS5kYXRhKHRoaXMucnVucyksaT1lLmVudGVyKCkuYXBwZW5kKCJnIikuYXR0cigiY2xhc3MiLCJoaW50Iik7aS5hcHBlbmQoInBhdGgiKS5hdHRyKCJkIiwiTSAwIDAgTCAxNSAwIEwgMTAgMTAgTCAxNSAyMCBMIDAgMjAgWiIpLGkubWVyZ2UoZSkuYXR0cigidHJhbnNmb3JtIixmdW5jdGlvbihyKXtyZXR1cm5gdHJhbnNsYXRlKDEwLCAke3RoaXMueVNjYWxlKHIpKzV9KWB9LmJpbmQodGhpcykpLmF0dHIoImZpbGwiLGZ1bmN0aW9uKHIpe3JldHVybiB0aGlzLmNvbG9yU2NhbGUocil9LmJpbmQodGhpcykpLGUuZXhpdCgpLnJlbW92ZSgpfWRyYXdSdW5IaW50VGV4dHMoKXtsZXQgZT10aGlzLnJ1bkhpbnRHcm91cC5zZWxlY3RBbGwoIi5oaW50LXRleHQiKS5kYXRhKHRoaXMucnVucyk7ZS5lbnRlcigpLmFwcGVuZCgidGV4dCIpLmF0dHIoIngiLDI1KS5hdHRyKCJmb250LXNpemUiLCIxMHB4IikuYXR0cigiYWxpZ25tZW50LWJhc2VsaW5lIiwibWlkZGxlIikuYXR0cigiY2xpcC1wYXRoIiwidXJsKCNoaW50LWNsaXApIikubWVyZ2UoZSkuYXR0cigieSIsZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMueVNjYWxlKHIpKzE1fS5iaW5kKHRoaXMpKS5hdHRyKCJjbGFzcyIsYGhpbnQtdGV4dCAke3RoaXMudGV4dENsYXNzfWApLnRleHQocj0+dGhpcy5ydW5JZFRvUnVucy5nZXQocik/Lm5hbWV8fCIiKSxlLmV4aXQoKS5yZW1vdmUoKX1kcmF3QmFycygpe2xldCBlPXRoaXMuYmFyc0dyb3VwLnNlbGVjdEFsbCgiLmJhciIpLmRhdGEodGhpcy5kYXRhKTtlLmVudGVyKCkuYXBwZW5kKCJyZWN0IikuYXR0cigiY2xhc3MiLCJiYXIiKS5hdHRyKCJoZWlnaHQiLDIwKS5tZXJnZShlKS5hdHRyKCJmaWxsIixyPT5udWxsPT09ci5uUE1JVmFsdWU/IiI6ci5uUE1JVmFsdWU+PTA/dGooci5uUE1JVmFsdWUpOmlqKC0xKnIublBNSVZhbHVlKSkuYXR0cigieCIsZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMueFNjYWxlKHIubWV0cmljKX0uYmluZCh0aGlzKSkuYXR0cigieSIsZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMueVNjYWxlKHIucnVuKSs1fS5iaW5kKHRoaXMpKS5hdHRyKCJ3aWR0aCIsZnVuY3Rpb24ocil7cmV0dXJuIG51bGw9PT1yLm5QTUlWYWx1ZT8wOnRoaXMuc2l6ZVNjYWxlKE1hdGguYWJzKHIublBNSVZhbHVlKSl9LmJpbmQodGhpcykpLGUuZXhpdCgpLnJlbW92ZSgpfWRyYXdDb3VudERvdHMoKXtsZXQgZT10aGlzLmNvdW50RG90c0dyb3VwLnNlbGVjdEFsbCgiLmNvdW50LWRvdCIpLmRhdGEodGhpcy5kYXRhKTtlLmVudGVyKCkuYXBwZW5kKCJjaXJjbGUiKS5hdHRyKCJjbGFzcyIsImNvdW50LWRvdCIpLmF0dHIoInN0cm9rZSIsImJsYWNrIikubWVyZ2UoZSkuYXR0cigiZmlsbCIsZnVuY3Rpb24ocil7cmV0dXJuIG51bGw9PT1yLmNvdW50VmFsdWU/IiI6bmooci5jb3VudFZhbHVlL3RoaXMubWF4Q291bnQpfS5iaW5kKHRoaXMpKS5hdHRyKCJjeCIsZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMueFNjYWxlKHIubWV0cmljKSt0aGlzLmNvdW50RG90T2Zmc2V0fS5iaW5kKHRoaXMpKS5hdHRyKCJjeSIsZnVuY3Rpb24ocil7cmV0dXJuIHRoaXMueVNjYWxlKHIucnVuKSt0aGlzLnJ1bkhlaWdodC8yfS5iaW5kKHRoaXMpKS5hdHRyKCJyIixmdW5jdGlvbihyKXtyZXR1cm4gbnVsbD09PXIuY291bnRWYWx1ZT8wOnRoaXMuY291bnRTaXplU2NhbGUoci5jb3VudFZhbHVlKX0uYmluZCh0aGlzKSksZS5leGl0KCkucmVtb3ZlKCl9ZHJhd1RleHRzKCl7bGV0IGU9dGhpcy50ZXh0c0dyb3VwLnNlbGVjdEFsbCgiLm5wbWktYmFja2dyb3VuZC10ZXh0IikuZGF0YSh0aGlzLmRhdGEpO2UuZW50ZXIoKS5hcHBlbmQoInRleHQiKS5hdHRyKCJjbGFzcyIsIm5wbWktYmFja2dyb3VuZC10ZXh0IikuYXR0cigic3Ryb2tlLXdpZHRoIiwzKS5hdHRyKCJzdHJva2UtbGluZWpvaW4iLCJyb3VuZCIpLmF0dHIoInN0cm9rZSIsdGhpcy5zdHJva2VDb2xvcikuYXR0cigiZm9udC1zaXplIiwiMTNweCIpLmF0dHIoImFsaWdubWVudC1iYXNlbGluZSIsIm1pZGRsZSIpLm1lcmdlKGUpLmF0dHIoIngiLGZ1bmN0aW9uKHMpe3JldHVybiB0aGlzLnhTY2FsZShzLm1ldHJpYykrNX0uYmluZCh0aGlzKSkuYXR0cigieSIsZnVuY3Rpb24ocyl7cmV0dXJuIHRoaXMueVNjYWxlKHMucnVuKSt0aGlzLnJ1bkhlaWdodC8yfS5iaW5kKHRoaXMpKS50ZXh0KHM9Pm51bGw9PT1zLm5QTUlWYWx1ZT8ibnVsbCI6TWF0aC5yb3VuZCgxZTMqKHMublBNSVZhbHVlK051bWJlci5FUFNJTE9OKSkvMWUzKSxlLmV4aXQoKS5yZW1vdmUoKTtsZXQgcj10aGlzLnRleHRzR3JvdXAuc2VsZWN0QWxsKCIubnBtaS10ZXh0IikuZGF0YSh0aGlzLmRhdGEpO3IuZW50ZXIoKS5hcHBlbmQoInRleHQiKS5hdHRyKCJjbGFzcyIsIm5wbWktdGV4dCIpLmF0dHIoImZvbnQtc2l6ZSIsIjEzcHgiKS5hdHRyKCJhbGlnbm1lbnQtYmFzZWxpbmUiLCJtaWRkbGUiKS5tZXJnZShyKS5hdHRyKCJ4IixmdW5jdGlvbihzKXtyZXR1cm4gdGhpcy54U2NhbGUocy5tZXRyaWMpKzV9LmJpbmQodGhpcykpLmF0dHIoInkiLGZ1bmN0aW9uKHMpe3JldHVybiB0aGlzLnlTY2FsZShzLnJ1bikrdGhpcy5ydW5IZWlnaHQvMn0uYmluZCh0aGlzKSkudGV4dChzPT5udWxsPT09cy5uUE1JVmFsdWU/Im51bGwiOk1hdGgucm91bmQoMWUzKihzLm5QTUlWYWx1ZStOdW1iZXIuRVBTSUxPTikpLzFlMyksci5leGl0KCkucmVtb3ZlKCl9ZHJhd0NvdW50VGV4dHMoKXtsZXQgZT10aGlzLmNvdW50VGV4dHNHcm91cC5zZWxlY3RBbGwoIi5jb3VudC1iYWNrZ3JvdW5kLXRleHQiKS5kYXRhKHRoaXMuZGF0YSk7ZS5lbnRlcigpLmFwcGVuZCgidGV4dCIpLmF0dHIoImNsYXNzIiwiY291bnQtYmFja2dyb3VuZC10ZXh0IikuYXR0cigic3Ryb2tlLXdpZHRoIiwzKS5hdHRyKCJzdHJva2UtbGluZWpvaW4iLCJyb3VuZCIpLmF0dHIoInN0cm9rZSIsdGhpcy5zdHJva2VDb2xvcikuYXR0cigiZm9udC1zaXplIiwiMTBweCIpLmF0dHIoImFsaWdubWVudC1iYXNlbGluZSIsIm1pZGRsZSIpLm1lcmdlKGUpLmF0dHIoIngiLGZ1bmN0aW9uKHMpe3JldHVybiB0aGlzLnhTY2FsZShzLm1ldHJpYykrdGhpcy5jb3VudERvdE9mZnNldCt0aGlzLmNvdW50VGV4dFBhZGRpbmcrdGhpcy5tYXhEb3RSYWRpdXN9LmJpbmQodGhpcykpLmF0dHIoInkiLGZ1bmN0aW9uKHMpe3JldHVybiB0aGlzLnlTY2FsZShzLnJ1bikrdGhpcy5ydW5IZWlnaHQvMn0uYmluZCh0aGlzKSkudGV4dChzPT5udWxsPT09cy5jb3VudFZhbHVlPyIiOkludGwuTnVtYmVyRm9ybWF0KCkuZm9ybWF0KHMuY291bnRWYWx1ZSkpLGUuZXhpdCgpLnJlbW92ZSgpO2xldCByPXRoaXMuY291bnRUZXh0c0dyb3VwLnNlbGVjdEFsbCgiLmNvdW50LXRleHQiKS5kYXRhKHRoaXMuZGF0YSk7ci5lbnRlcigpLmFwcGVuZCgidGV4dCIpLmF0dHIoImNsYXNzIiwiY291bnQtdGV4dCIpLmF0dHIoImZvbnQtc2l6ZSIsIjEwcHgiKS5hdHRyKCJhbGlnbm1lbnQtYmFzZWxpbmUiLCJtaWRkbGUiKS5tZXJnZShyKS5hdHRyKCJ4IixmdW5jdGlvbihzKXtyZXR1cm4gdGhpcy54U2NhbGUocy5tZXRyaWMpK3RoaXMuY291bnREb3RPZmZzZXQrdGhpcy5jb3VudFRleHRQYWRkaW5nK3RoaXMubWF4RG90UmFkaXVzfS5iaW5kKHRoaXMpKS5hdHRyKCJ5IixmdW5jdGlvbihzKXtyZXR1cm4gdGhpcy55U2NhbGUocy5ydW4pK3RoaXMucnVuSGVpZ2h0LzJ9LmJpbmQodGhpcykpLnRleHQocz0+bnVsbD09PXMuY291bnRWYWx1ZT8iIjpJbnRsLk51bWJlckZvcm1hdCgpLmZvcm1hdChzLmNvdW50VmFsdWUpKSxyLmV4aXQoKS5yZW1vdmUoKX1zaW1pbGFyaXR5U29ydChlKXt0aGlzLmhhc0VtYmVkZGluZyYmKGUuc3RvcFByb3BhZ2F0aW9uKCksdGhpcy5vblNob3dTaW1pbGFyQW5ub3RhdGlvbnMuZW1pdCgpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siYW5ub3RhdGlvbi1jb21wb25lbnQiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYob3QoZHR0LDcsUmUpLG90KHB0dCw3LFJlKSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5hbm5vdGF0aW9uQ29udGFpbmVyPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLmNsaXBQYXRoRWxlbWVudD1yLmZpcnN0KX19LGhvc3RWYXJzOjIsaG9zdEJpbmRpbmdzOmZ1bmN0aW9uKGUsaSl7MSZlJiZQKCJyZXNpemUiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25SZXNpemUoKX0sMCxXeCksMiZlJiZldCgic2VsZWN0ZWQtcm93IixpLnNlbGVjdGVkKX0saW5wdXRzOntkYXRhOiJkYXRhIixtYXhDb3VudDoibWF4Q291bnQiLHNlbGVjdGVkQW5ub3RhdGlvbnM6InNlbGVjdGVkQW5ub3RhdGlvbnMiLGZsYWdnZWRBbm5vdGF0aW9uczoiZmxhZ2dlZEFubm90YXRpb25zIixoaWRkZW5Bbm5vdGF0aW9uczoiaGlkZGVuQW5ub3RhdGlvbnMiLGFjdGl2ZU1ldHJpY3M6ImFjdGl2ZU1ldHJpY3MiLG51bUFjdGl2ZVJ1bnM6Im51bUFjdGl2ZVJ1bnMiLHNob3dDb3VudHM6InNob3dDb3VudHMiLGFubm90YXRpb246ImFubm90YXRpb24iLHJ1bkhlaWdodDoicnVuSGVpZ2h0IixoYXNFbWJlZGRpbmc6Imhhc0VtYmVkZGluZyIsc29ydDoic29ydCIsc2lkZWJhcldpZHRoOiJzaWRlYmFyV2lkdGgiLGNvbG9yU2NhbGU6ImNvbG9yU2NhbGUiLHJ1bklkVG9SdW5zOiJydW5JZFRvUnVucyJ9LG91dHB1dHM6e29uU2hvd1NpbWlsYXJBbm5vdGF0aW9uczoib25TaG93U2ltaWxhckFubm90YXRpb25zIn0sZmVhdHVyZXM6W0Z0XSxkZWNsczoxNCx2YXJzOjEwLGNvbnN0czpbWzEsImFubm90YXRpb24tdGl0bGUiXSxbMSwiYW5ub3RhdGlvbi1jaGVja2JveCIsMywiY2hlY2tlZCIsImNsaWNrIl0sWzEsImFubm90YXRpb24tYnV0dG9uIiwzLCJuZ0NsYXNzIiwiY2xpY2siXSxbImNsYXNzIiwiZmxhZ2dlZC1pY29uIiwic3ZnSWNvbiIsImZsYWdfMjRweCIsNCwibmdJZiJdLFsiY2xhc3MiLCJoaWRkZW4taWNvbiIsInN2Z0ljb24iLCJ2aXNpYmlsaXR5X29mZl8yNHB4Iiw0LCJuZ0lmIl0sWyJjbGFzcyIsImFubm90YXRpb24taWNvbiIsMywic3ZnSWNvbiIsIm5nQ2xhc3MiLDQsIm5nSWYiXSxbMSwiY2hhcnQtZGl2Il0sWyJjaGFydCIsIiJdLFsxLCJjaGFydC1zdmciXSxbImlkIiwiaGludC1jbGlwIl0sWyJoaW50Q2xpcCIsIiJdLFsieCIsIjAiLCJ5IiwiMCJdLFsic3ZnSWNvbiIsImZsYWdfMjRweCIsMSwiZmxhZ2dlZC1pY29uIl0sWyJzdmdJY29uIiwidmlzaWJpbGl0eV9vZmZfMjRweCIsMSwiaGlkZGVuLWljb24iXSxbMSwiYW5ub3RhdGlvbi1pY29uIiwzLCJzdmdJY29uIiwibmdDbGFzcyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZGl2IiwwKSgxLCJtYXQtY2hlY2tib3giLDEpLFAoImNsaWNrIixmdW5jdGlvbihvKXtyZXR1cm4gby5wcmV2ZW50RGVmYXVsdCgpfSksdigpLF8oMiwiYnV0dG9uIiwyKSxQKCJjbGljayIsZnVuY3Rpb24obyl7cmV0dXJuIGkuc2ltaWxhcml0eVNvcnQobyl9KSxBKDMpLHYoKSxFKDQsaHR0LDEsMCwibWF0LWljb24iLDMpLEUoNSxmdHQsMSwwLCJtYXQtaWNvbiIsNCksRSg2LG10dCwxLDIsIm1hdC1pY29uIiw1KSx2KCksXyg3LCJkaXYiLDYsNyksSW4oKSxfKDksInN2ZyIsOCkoMTAsImRlZnMiKSgxMSwiY2xpcFBhdGgiLDksMTApLE8oMTMsInJlY3QiLDExKSx2KCkoKSgpKCkpLDImZSYmKGV0KCJmbGFnZ2VkLWFubm90YXRpb24iLGkuZmxhZ2dlZEFubm90YXRpb25zLmluY2x1ZGVzKGkuYW5ub3RhdGlvbikpKCJoaWRkZW4tYW5ub3RhdGlvbiIsaS5oaWRkZW5Bbm5vdGF0aW9ucy5pbmNsdWRlcyhpLmFubm90YXRpb24pJiYhaS5mbGFnZ2VkQW5ub3RhdGlvbnMuaW5jbHVkZXMoaS5hbm5vdGF0aW9uKSksQygxKSx5KCJjaGVja2VkIixpLnNlbGVjdGVkQW5ub3RhdGlvbnMuaW5jbHVkZXMoaS5hbm5vdGF0aW9uKSksQygxKSx5KCJuZ0NsYXNzIixpLmhhc0VtYmVkZGluZz8iY2xpY2thYmxlLWFubm90YXRpb24iOiIiKSxDKDEpLGplKCIgIixpLmFubm90YXRpb24sIiAiKSxDKDEpLHkoIm5nSWYiLGkuZmxhZ2dlZEFubm90YXRpb25zLmluY2x1ZGVzKGkuYW5ub3RhdGlvbikpLEMoMSkseSgibmdJZiIsaS5oaWRkZW5Bbm5vdGF0aW9ucy5pbmNsdWRlcyhpLmFubm90YXRpb24pKSxDKDEpLHkoIm5nSWYiLGkuYW5ub3RhdGlvbj09PWkuc29ydC5tZXRyaWMpKX0sZGVwZW5kZW5jaWVzOltGbixCZSx5bCxHdF0sc3R5bGVzOlsiOmhvc3R7cGFkZGluZy10b3A6NXB4fS5hbm5vdGF0aW9uLXRpdGxle2FsaWduLWl0ZW1zOmNlbnRlcjtkaXNwbGF5OmZsZXg7Zm9udC1zaXplOjEzcHg7aGVpZ2h0OjIwcHg7cGFkZGluZzowIDEwcHg7dXNlci1zZWxlY3Q6bm9uZX0uc2VsZWN0ZWQtcm93e2JhY2tncm91bmQtY29sb3I6I2UwZTBlMDtkaXNwbGF5OmJsb2NrfS5mbGFnZ2VkLWFubm90YXRpb257Y29sb3I6I2Y1N2MwMH0uaGlkZGVuLWFubm90YXRpb257Y29sb3I6Izc1NzU3NX0uYW5ub3RhdGlvbi1jaGVja2JveHtwYWRkaW5nLXJpZ2h0OjVweH0uZmxhZ2dlZC1pY29ue3RyYW5zZm9ybTpzY2FsZSgwLjYpfS5oaWRkZW4taWNvbnt0cmFuc2Zvcm06c2NhbGUoMC42KX0uYW5ub3RhdGlvbi1pY29ue3RyYW5zZm9ybTpzY2FsZSgwLjYpfS5jaGFydC1kaXZ7Ym9yZGVyLWJvdHRvbToxcHggc29saWQgI2ViZWJlYn0uY2hhcnQtc3Zne3dpZHRoOjEwMCU7dXNlci1zZWxlY3Q6bm9uZX0uZGVmYXVsdC10ZXh0e2ZpbGw6IzAwMH0uZmxhZy10ZXh0e2ZpbGw6I2Y1N2MwMH0uaGlkZGVuLXRleHR7ZmlsbDojNzU3NTc1fS5jbGlja2FibGUtYW5ub3RhdGlvbntjdXJzb3I6cG9pbnRlcn1idXR0b257YWxsOnVuc2V0fVxuIl0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCkscW1lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuc29ydCQ9dGhpcy5zdG9yZS5zZWxlY3QoS2IpLHRoaXMuZmxhZ2dlZEFubm90YXRpb25zJD10aGlzLnN0b3JlLnNlbGVjdChWRiksdGhpcy5oaWRkZW5Bbm5vdGF0aW9ucyQ9dGhpcy5zdG9yZS5zZWxlY3QoWGIpLHRoaXMuc2VsZWN0ZWRBbm5vdGF0aW9ucyQ9dGhpcy5zdG9yZS5zZWxlY3QodmMpLHRoaXMuc2hvd0NvdW50cyQ9dGhpcy5zdG9yZS5zZWxlY3QoakYpLHRoaXMuc2lkZWJhcldpZHRoJD10aGlzLnN0b3JlLnNlbGVjdChPZiksdGhpcy5ydW5Db2xvclNjYWxlJD10aGlzLnN0b3JlLnNlbGVjdChuYykucGlwZShMKGk9PnI9PntpZighaS5oYXNPd25Qcm9wZXJ0eShyKSl0aHJvdyBuZXcgRXJyb3IoYFtDb2xvciBzY2FsZV0gdW5rbm93biBydW5JZDogJHtyfS5gKTtyZXR1cm4gaVtyXX0pKSx0aGlzLnJ1bklkVG9SdW5zJD10aGlzLnN0b3JlLnNlbGVjdChxSSl9c2hvd1NpbWlsYXJBbm5vdGF0aW9ucygpe3RoaXMuc3RvcmUuZGlzcGF0Y2goSUYoe2Fubm90YXRpb246dGhpcy5hbm5vdGF0aW9ufSkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibnBtaS1hbm5vdGF0aW9uIl1dLGlucHV0czp7ZGF0YToiZGF0YSIsbWF4Q291bnQ6Im1heENvdW50IixhY3RpdmVNZXRyaWNzOiJhY3RpdmVNZXRyaWNzIixudW1BY3RpdmVSdW5zOiJudW1BY3RpdmVSdW5zIixhbm5vdGF0aW9uOiJhbm5vdGF0aW9uIixydW5IZWlnaHQ6InJ1bkhlaWdodCIsaGFzRW1iZWRkaW5nOiJoYXNFbWJlZGRpbmcifSxkZWNsczo5LHZhcnM6MzEsY29uc3RzOltbMywiZGF0YSIsIm1heENvdW50IiwiYWN0aXZlTWV0cmljcyIsIm51bUFjdGl2ZVJ1bnMiLCJhbm5vdGF0aW9uIiwicnVuSGVpZ2h0IiwiaGFzRW1iZWRkaW5nIiwic29ydCIsInNlbGVjdGVkQW5ub3RhdGlvbnMiLCJmbGFnZ2VkQW5ub3RhdGlvbnMiLCJoaWRkZW5Bbm5vdGF0aW9ucyIsInNob3dDb3VudHMiLCJzaWRlYmFyV2lkdGgiLCJjb2xvclNjYWxlIiwicnVuSWRUb1J1bnMiLCJvblNob3dTaW1pbGFyQW5ub3RhdGlvbnMiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImFubm90YXRpb24tY29tcG9uZW50IiwwKSxQKCJvblNob3dTaW1pbGFyQW5ub3RhdGlvbnMiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuc2hvd1NpbWlsYXJBbm5vdGF0aW9ucygpfSksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIiksQig0LCJhc3luYyIpLEIoNSwiYXN5bmMiKSxCKDYsImFzeW5jIiksQig3LCJhc3luYyIpLEIoOCwiYXN5bmMiKSx2KCkpLDImZSYmeSgiZGF0YSIsaS5kYXRhKSgibWF4Q291bnQiLGkubWF4Q291bnQpKCJhY3RpdmVNZXRyaWNzIixpLmFjdGl2ZU1ldHJpY3MpKCJudW1BY3RpdmVSdW5zIixpLm51bUFjdGl2ZVJ1bnMpKCJhbm5vdGF0aW9uIixpLmFubm90YXRpb24pKCJydW5IZWlnaHQiLGkucnVuSGVpZ2h0KSgiaGFzRW1iZWRkaW5nIixpLmhhc0VtYmVkZGluZykoInNvcnQiLFUoMSwxNSxpLnNvcnQkKSkoInNlbGVjdGVkQW5ub3RhdGlvbnMiLFUoMiwxNyxpLnNlbGVjdGVkQW5ub3RhdGlvbnMkKSkoImZsYWdnZWRBbm5vdGF0aW9ucyIsVSgzLDE5LGkuZmxhZ2dlZEFubm90YXRpb25zJCkpKCJoaWRkZW5Bbm5vdGF0aW9ucyIsVSg0LDIxLGkuaGlkZGVuQW5ub3RhdGlvbnMkKSkoInNob3dDb3VudHMiLFUoNSwyMyxpLnNob3dDb3VudHMkKSkoInNpZGViYXJXaWR0aCIsVSg2LDI1LGkuc2lkZWJhcldpZHRoJCkpKCJjb2xvclNjYWxlIixVKDcsMjcsaS5ydW5Db2xvclNjYWxlJCkpKCJydW5JZFRvUnVucyIsVSg4LDI5LGkucnVuSWRUb1J1bnMkKSl9LGRlcGVuZGVuY2llczpbV21lLEdlXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiB2dHQobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJucG1pLWFubm90YXRpb24iLDUpLFAoImNsaWNrIixmdW5jdGlvbihyKXtsZXQgcz1vZShlKS4kaW1wbGljaXQ7cmV0dXJuIHNlKFMoMikucm93Q2xpY2tlZChyLHMpKX0pLHYoKX1pZigyJm4pe2xldCBlPXQuJGltcGxpY2l0LGk9UygyKTt5KCJkYXRhIixpLmFubm90YXRpb25zW2VdKSgiYWN0aXZlTWV0cmljcyIsaS5hY3RpdmVNZXRyaWNzKSgibnVtQWN0aXZlUnVucyIsaS5udW1BY3RpdmVSdW5zKSgibWF4Q291bnQiLGkubWF4Q291bnQpKCJhbm5vdGF0aW9uIixlKSgicnVuSGVpZ2h0IixpLnJ1bkhlaWdodCkoImhhc0VtYmVkZGluZyIsaS5lbWJlZGRpbmdEYXRhJiZ2b2lkIDAhPT1pLmVtYmVkZGluZ0RhdGFbZV0pfX1mdW5jdGlvbiB5dHQobix0KXtpZigxJm4mJihzbigwKSxPKDEsIm5wbWktYW5ub3RhdGlvbnMtbGlzdC1sZWdlbmQiKSgyLCJucG1pLWFubm90YXRpb25zLWxpc3QtaGVhZGVyIiwyKSxfKDMsImNkay12aXJ0dWFsLXNjcm9sbC12aWV3cG9ydCIsMyksRSg0LHZ0dCwxLDcsIm5wbWktYW5ub3RhdGlvbiIsNCksdigpLGFuKCkpLDImbil7bGV0IGU9UygpO0MoMikseSgiYW5ub3RhdGlvbnMiLGUuYW5ub3RhdGlvbnMpKCJudW1Bbm5vdGF0aW9ucyIsZS5udW1Bbm5vdGF0aW9ucykoImFjdGl2ZU1ldHJpY3MiLGUuYWN0aXZlTWV0cmljcyksQygxKSxaaSgiaXRlbVNpemUiLGUubnVtQWN0aXZlUnVucyplLnJ1bkhlaWdodCsyNSksQygxKSx5KCJjZGtWaXJ0dWFsRm9yT2YiLGUuc29ydGVkQW5ub3RhdGlvbnMpfX12YXIgWW1lPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLm9uUm93Q2xpY2s9bmV3IEcsdGhpcy5ydW5IZWlnaHQ9MzB9cm93Q2xpY2tlZChlLGkpe2lmKGUuc2hpZnRLZXkpe2xldCByPXRoaXMuc29ydGVkQW5ub3RhdGlvbnMuaW5kZXhPZihpKTtpZigwPT09dGhpcy5zZWxlY3RlZEFubm90YXRpb25zLmxlbmd0aCl0aGlzLm9uUm93Q2xpY2suZW1pdCh0aGlzLnNvcnRlZEFubm90YXRpb25zLnNsaWNlKDAscisxKSk7ZWxzZXtsZXQgcz10aGlzLnNvcnRlZEFubm90YXRpb25zLmluZGV4T2YodGhpcy5zZWxlY3RlZEFubm90YXRpb25zW3RoaXMuc2VsZWN0ZWRBbm5vdGF0aW9ucy5sZW5ndGgtMV0pO3RoaXMub25Sb3dDbGljay5lbWl0KHM8cj90aGlzLnNvcnRlZEFubm90YXRpb25zLnNsaWNlKHMscisxKTp0aGlzLnNvcnRlZEFubm90YXRpb25zLnNsaWNlKHIscysxKSl9fWVsc2UgdGhpcy5vblJvd0NsaWNrLmVtaXQoW2ldKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siYW5ub3RhdGlvbnMtbGlzdC1jb21wb25lbnQiXV0saW5wdXRzOnthbm5vdGF0aW9uczoiYW5ub3RhdGlvbnMiLGVtYmVkZGluZ0RhdGE6ImVtYmVkZGluZ0RhdGEiLGFubm90YXRpb25zRXhwYW5kZWQ6ImFubm90YXRpb25zRXhwYW5kZWQiLG51bUFubm90YXRpb25zOiJudW1Bbm5vdGF0aW9ucyIsYW5ub3RhdGlvblNvcnQ6ImFubm90YXRpb25Tb3J0IixhY3RpdmVNZXRyaWNzOiJhY3RpdmVNZXRyaWNzIixudW1BY3RpdmVSdW5zOiJudW1BY3RpdmVSdW5zIixzb3J0ZWRBbm5vdGF0aW9uczoic29ydGVkQW5ub3RhdGlvbnMiLHNlbGVjdGVkQW5ub3RhdGlvbnM6InNlbGVjdGVkQW5ub3RhdGlvbnMiLG1heENvdW50OiJtYXhDb3VudCJ9LG91dHB1dHM6e29uUm93Q2xpY2s6Im9uUm93Q2xpY2sifSxkZWNsczoyLHZhcnM6Myxjb25zdHM6W1szLCJudW1Bbm5vdGF0aW9ucyIsImV4cGFuZGVkIl0sWzQsIm5nSWYiXSxbMywiYW5ub3RhdGlvbnMiLCJudW1Bbm5vdGF0aW9ucyIsImFjdGl2ZU1ldHJpY3MiXSxbIm1pbkJ1ZmZlclB4IiwiMzAwIiwibWF4QnVmZmVyUHgiLCI2MDAiLDEsImFubm90YXRpb24tcm93cyIsMywiaXRlbVNpemUiXSxbMywiZGF0YSIsImFjdGl2ZU1ldHJpY3MiLCJudW1BY3RpdmVSdW5zIiwibWF4Q291bnQiLCJhbm5vdGF0aW9uIiwicnVuSGVpZ2h0IiwiaGFzRW1iZWRkaW5nIiwiY2xpY2siLDQsImNka1ZpcnR1YWxGb3IiLCJjZGtWaXJ0dWFsRm9yT2YiXSxbMywiZGF0YSIsImFjdGl2ZU1ldHJpY3MiLCJudW1BY3RpdmVSdW5zIiwibWF4Q291bnQiLCJhbm5vdGF0aW9uIiwicnVuSGVpZ2h0IiwiaGFzRW1iZWRkaW5nIiwiY2xpY2siXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihPKDAsIm5wbWktYW5ub3RhdGlvbnMtbGlzdC10b29sYmFyIiwwKSxFKDEseXR0LDUsNSwibmctY29udGFpbmVyIiwxKSksMiZlJiYoeSgibnVtQW5ub3RhdGlvbnMiLGkubnVtQW5ub3RhdGlvbnMpKCJleHBhbmRlZCIsaS5hbm5vdGF0aW9uc0V4cGFuZGVkKSxDKDEpLHkoIm5nSWYiLGkuYW5ub3RhdGlvbnNFeHBhbmRlZCkpfSxkZXBlbmRlbmNpZXM6W0JlLEhtZSx6bWUsR21lLGIyLHgyLGVnLHFtZV0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7Ym9yZGVyOjFweCBzb2xpZCAjZWJlYmViO2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47aGVpZ2h0OmNhbGMoMTAwJSAtIDJweCk7d2lkdGg6Y2FsYygxMDAlIC0gMnB4KX0uYW5ub3RhdGlvbi1yb3dzW19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2ZsZXg6MSAxO292ZXJmbG93LXk6YXV0b30iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLFlGPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuYW5ub3RhdGlvbnNFeHBhbmRlZCQ9dGhpcy5zdG9yZS5waXBlKHZ0KFVGKSksdGhpcy5hY3RpdmVSdW5zJD10aGlzLnN0b3JlLnBpcGUodnQob28pKS5waXBlKEwoaT0+aT9BcnJheS5mcm9tKGkuZW50cmllcygpKS5maWx0ZXIocj0+clsxXSkubWFwKHI9PnJbMF0pOltdKSksdGhpcy5lbWJlZGRpbmdEYXRhJD10aGlzLnN0b3JlLnBpcGUodnQoQkYpKSx0aGlzLm51bUFjdGl2ZVJ1bnMkPXRoaXMuYWN0aXZlUnVucyQucGlwZShMKGk9PmkubGVuZ3RoKSksdGhpcy5hY3RpdmVNZXRyaWNzJD1MdChbdGhpcy5zdG9yZS5zZWxlY3QoUmYpLHRoaXMuYWN0aXZlUnVucyQsdGhpcy5zdG9yZS5zZWxlY3QoSWwpXSkucGlwZShMKChbaSxyLG9dKT0+e2xldCBzPVtdO2ZvcihsZXQgYSBvZiByKWlbYV0mJihzPXMuY29uY2F0KGlbYV0uZmlsdGVyKGw9PlZiKGwpKSkpO3JldHVybiBzPVsuLi5uZXcgU2V0KFsuLi5PYmplY3Qua2V5cyhvKSwuLi5zXSldLHN9KSksdGhpcy52aXNpYmxlQW5ub3RhdGlvbnMkPUx0KFt0aGlzLnN0b3JlLnNlbGVjdChQZiksdGhpcy5zdG9yZS5zZWxlY3QoWGIpLHRoaXMuc3RvcmUuc2VsZWN0KFpiKV0pLnBpcGUoTCgoW2kscixvXSk9PnFGKGkscixvKSkpLHRoaXMuZmlsdGVyZWRBbm5vdGF0aW9ucyQ9THQoW3RoaXMudmlzaWJsZUFubm90YXRpb25zJCx0aGlzLnN0b3JlLnNlbGVjdChIRiksdGhpcy5zdG9yZS5zZWxlY3QoSWwpLHRoaXMuYWN0aXZlUnVucyQsdGhpcy5hY3RpdmVNZXRyaWNzJCx0aGlzLnN0b3JlLnNlbGVjdChRYildKS5waXBlKEwoKFtpLHIsbyxzLGEsbF0pPT5mdW5jdGlvbihuLHQsZSxpLHIsbyl7bGV0IHM9e30sYT1uZXcgU2V0KHQpLGw9bmV3IFNldChyLm1hcCh1PT5Tcyh1KSkpLGM9bmV3IFJlZ0V4cChvLCJpIik7cmV0dXJuIE9iamVjdC5lbnRyaWVzKG4pLmZvckVhY2godT0+e2lmKCFjLnRlc3QodVswXSkpcmV0dXJuO2xldCBkPXVbMV07ZD1kLmZpbHRlcihwPT5hLmhhcyhwLnJ1bikmJmwuaGFzKHAubWV0cmljKSksZnVuY3Rpb24obix0LGUpe3JldHVybiBuLmV2ZXJ5KGk9PntpZihpLmtpbmQ9PT1tdS5PUEVSQVRPUilyZXR1cm4hMDtsZXQgcj10W2kubWV0cmljXTtyZXR1cm4gdm9pZCAwPT09cnx8ZS5zb21lKG89Pm8ubWV0cmljPT09U3MoaS5tZXRyaWMpJiYobnVsbD09PW8ublBNSVZhbHVlP3IuaW5jbHVkZU5hTjpvLm5QTUlWYWx1ZTw9ci5tYXgmJm8ublBNSVZhbHVlPj1yLm1pbikpfSl9KGUsaSxkKSYmMCE9PWQubGVuZ3RoJiYoc1t1WzBdXT1kKX0pLHN9KGkscyxyLG8sYSxsKSkpLnBpcGUoVHMoKSksdGhpcy5udW1Bbm5vdGF0aW9ucyQ9dGhpcy5maWx0ZXJlZEFubm90YXRpb25zJC5waXBlKEwoaT0+T2JqZWN0LmtleXMoaSkubGVuZ3RoKSksdGhpcy5zb3J0ZWRBbm5vdGF0aW9ucyQ9THQoW3RoaXMuZmlsdGVyZWRBbm5vdGF0aW9ucyQsdGhpcy5zdG9yZS5waXBlKHZ0KEtiKSksdGhpcy5lbWJlZGRpbmdEYXRhJF0pLnBpcGUoTCgoW2kscixvXSk9PmZ1bmN0aW9uKG4sdCxlKXtsZXQgaT1PYmplY3Qua2V5cyhuKSxyPXQub3JkZXI9PT0kci5ESVNTSU1JTEFSfHx0Lm9yZGVyPT09JHIuU0lNSUxBUjtpZigiIj09PXQubWV0cmljfHwodm9pZCAwPT09ZXx8dm9pZCAwPT09ZS5wb2ludHNbdC5tZXRyaWNdKSYmcilyZXR1cm4gaTtsZXQgbz1yP2Z1bmN0aW9uKG4sdCxlKXtsZXQgaT17fSxyPU51bWJlci5QT1NJVElWRV9JTkZJTklUWSxvPU51bWJlci5ORUdBVElWRV9JTkZJTklUWTtlLm9yZGVyPT09JHIuU0lNSUxBUiYmKHI9TnVtYmVyLk5FR0FUSVZFX0lORklOSVRZLG89TnVtYmVyLlBPU0lUSVZFX0lORklOSVRZKTtmb3IobGV0IHMgb2YgbilpW3NdPXM9PT1lLm1ldHJpYz9yOnZvaWQgMD09PXQucG9pbnRzW3NdP286dC5wb2ludHNbc10udmVjdG9yP1ZldCh0LnBvaW50c1tlLm1ldHJpY10udmVjdG9yLHQucG9pbnRzW3NdLnZlY3RvcixvKTpvO3JldHVybiBpfShpLGUsdCk6ZnVuY3Rpb24obix0LGUpe2xldCBpPVNzKGUubWV0cmljKSxyPXt9O2lmKGUub3JkZXI9PT0kci5ERVNDRU5ESU5HKWZvcihsZXQgbyBvZiBuKXJbb109TWF0aC5tYXgoLi4udFtvXS5maWx0ZXIocz0+cy5tZXRyaWM9PT1pKS5tYXAocz0+bnVsbD09PXMublBNSVZhbHVlPy0xLzA6cy5uUE1JVmFsdWUpKTtlbHNlIGZvcihsZXQgbyBvZiBuKXJbb109TWF0aC5taW4oLi4udFtvXS5maWx0ZXIocz0+cy5tZXRyaWM9PT1pKS5tYXAocz0+bnVsbD09PXMublBNSVZhbHVlPzEvMDpzLm5QTUlWYWx1ZSkpO3JldHVybiByfShpLG4sdCk7cmV0dXJuIGZ1bmN0aW9uKG4sdCxlKXtyZXR1cm4gbi5zb3J0KGU/KGkscik9PnRbaV0tdFtyXTooaSxyKT0+dFtyXS10W2ldKX0oaSxvLHQub3JkZXI9PT0kci5BU0NFTkROR3x8dC5vcmRlcj09PSRyLlNJTUlMQVIpfShpLHIsbykpKSx0aGlzLnNlbGVjdGVkQW5ub3RhdGlvbnMkPXRoaXMuc3RvcmUucGlwZSh2dCh2YykpLHRoaXMubWF4Q291bnQkPXRoaXMuZmlsdGVyZWRBbm5vdGF0aW9ucyQucGlwZShMKGk9PntsZXQgcj0wO3JldHVybiBPYmplY3QudmFsdWVzKGkpLmZvckVhY2gobz0+e28uZm9yRWFjaChzPT57cy5jb3VudFZhbHVlJiYocj1NYXRoLm1heChyLHMuY291bnRWYWx1ZSkpfSl9KSxyfSkpfXJvd0NsaWNrZWQoZSl7dGhpcy5zdG9yZS5kaXNwYXRjaCh3Rih7YW5ub3RhdGlvbnM6ZX0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktYW5ub3RhdGlvbnMtbGlzdCJdXSxkZWNsczoxMCx2YXJzOjI3LGNvbnN0czpbWzMsImFubm90YXRpb25zIiwiZW1iZWRkaW5nRGF0YSIsImFubm90YXRpb25zRXhwYW5kZWQiLCJudW1Bbm5vdGF0aW9ucyIsImFjdGl2ZU1ldHJpY3MiLCJudW1BY3RpdmVSdW5zIiwic29ydGVkQW5ub3RhdGlvbnMiLCJzZWxlY3RlZEFubm90YXRpb25zIiwibWF4Q291bnQiLCJvblJvd0NsaWNrIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJhbm5vdGF0aW9ucy1saXN0LWNvbXBvbmVudCIsMCksUCgib25Sb3dDbGljayIsZnVuY3Rpb24obyl7cmV0dXJuIGkucm93Q2xpY2tlZChvKX0pLEIoMSwiYXN5bmMiKSxCKDIsImFzeW5jIiksQigzLCJhc3luYyIpLEIoNCwiYXN5bmMiKSxCKDUsImFzeW5jIiksQig2LCJhc3luYyIpLEIoNywiYXN5bmMiKSxCKDgsImFzeW5jIiksQig5LCJhc3luYyIpLHYoKSksMiZlJiZ5KCJhbm5vdGF0aW9ucyIsVSgxLDksaS5maWx0ZXJlZEFubm90YXRpb25zJCkpKCJlbWJlZGRpbmdEYXRhIixVKDIsMTEsaS5lbWJlZGRpbmdEYXRhJCkpKCJhbm5vdGF0aW9uc0V4cGFuZGVkIixVKDMsMTMsaS5hbm5vdGF0aW9uc0V4cGFuZGVkJCkpKCJudW1Bbm5vdGF0aW9ucyIsVSg0LDE1LGkubnVtQW5ub3RhdGlvbnMkKSkoImFjdGl2ZU1ldHJpY3MiLFUoNSwxNyxpLmFjdGl2ZU1ldHJpY3MkKSkoIm51bUFjdGl2ZVJ1bnMiLFUoNiwxOSxpLm51bUFjdGl2ZVJ1bnMkKSkoInNvcnRlZEFubm90YXRpb25zIixVKDcsMjEsaS5zb3J0ZWRBbm5vdGF0aW9ucyQpKSgic2VsZWN0ZWRBbm5vdGF0aW9ucyIsVSg4LDIzLGkuc2VsZWN0ZWRBbm5vdGF0aW9ucyQpKSgibWF4Q291bnQiLFUoOSwyNSxpLm1heENvdW50JCkpfSxkZXBlbmRlbmNpZXM6W1ltZSxHZV0sZW5jYXBzdWxhdGlvbjoyLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCkseHR0PVsiY2hhcnQiXSxLbWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMud2lkdGg9MCx0aGlzLmNoYXJ0V2lkdGg9MCx0aGlzLmhlaWdodD0zMDAsdGhpcy5tYXJnaW49e3RvcDoyMCxyaWdodDo0MCxib3R0b206MjAsbGVmdDo0MH0sdGhpcy5jaGFydEhlaWdodD10aGlzLmhlaWdodC10aGlzLm1hcmdpbi50b3AtdGhpcy5tYXJnaW4uYm90dG9tfW9uUmVzaXplKGUpe3RoaXMucmVkcmF3KCl9bmdBZnRlclZpZXdJbml0KCl7dGhpcy5zdmc9Ym8odGhpcy5zdmdFbGVtZW50Lm5hdGl2ZUVsZW1lbnQpLHRoaXMubWFpbkNvbnRhaW5lcj10aGlzLnN2Zy5hcHBlbmQoImciKS5hdHRyKCJ0cmFuc2Zvcm0iLGB0cmFuc2xhdGUoJHt0aGlzLm1hcmdpbi5sZWZ0fSwgJHt0aGlzLm1hcmdpbi50b3B9KWApLHRoaXMuY29vcmRpbmF0ZXNHcm91cD10aGlzLm1haW5Db250YWluZXIuYXBwZW5kKCJnIiksdGhpcy5sYWJlbHNHcm91cD10aGlzLm1haW5Db250YWluZXIuYXBwZW5kKCJnIiksdGhpcy5heGlzR3JvdXA9dGhpcy5tYWluQ29udGFpbmVyLmFwcGVuZCgiZyIpLHRoaXMueFNjYWxlPVN5KCkucGFkZGluZyguMSksdGhpcy55U2NhbGU9UW8oKS5yYW5nZShbdGhpcy5jaGFydEhlaWdodCwwXSksdGhpcy55QXhpcz16dyh0aGlzLnlTY2FsZSksdGhpcy5yZWRyYXcoKX1uZ09uQ2hhbmdlcyhlKXt0aGlzLnN2ZyYmdGhpcy5yZWRyYXcoKX1yZWRyYXcoKXt0aGlzLnVwZGF0ZURpbWVuc2lvbnMoKSx0aGlzLnVwZGF0ZUF4ZXMoKSx0aGlzLmRyYXcoKX11cGRhdGVEaW1lbnNpb25zKCl7dGhpcy53aWR0aD10aGlzLnN2Z0VsZW1lbnQubmF0aXZlRWxlbWVudC5jbGllbnRXaWR0aHx8MTAsdGhpcy5jaGFydFdpZHRoPXRoaXMud2lkdGgtdGhpcy5tYXJnaW4ubGVmdC10aGlzLm1hcmdpbi5yaWdodH11cGRhdGVBeGVzKCl7dGhpcy54U2NhbGUucmFuZ2VSb3VuZChbMCx0aGlzLmNoYXJ0V2lkdGhdKS5kb21haW4odGhpcy5hY3RpdmVNZXRyaWNzKSx0aGlzLnlTY2FsZS5kb21haW4oW3RoaXMuY29vcmRpbmF0ZURhdGEuZXh0cmVtZXMubWluLHRoaXMuY29vcmRpbmF0ZURhdGEuZXh0cmVtZXMubWF4XSl9ZHJhdygpe3RoaXMuZHJhd0F4ZXMoKSx0aGlzLmRyYXdBeGlzTGFiZWxzKCksdGhpcy5kcmF3Q29vcmRpbmF0ZXMoKSx0aGlzLmRyYXdMYWJlbHMoKX1kcmF3QXhlcygpe2xldCBlPXRoaXMuYXhpc0dyb3VwLnNlbGVjdEFsbCgiLmF4aXMteSIpLmRhdGEodGhpcy5hY3RpdmVNZXRyaWNzKTtlLmVudGVyKCkuYXBwZW5kKCJnIikuYXR0cigiY2xhc3MiLCJheGlzLXkiKS5tZXJnZShlKS5hdHRyKCJ0cmFuc2Zvcm0iLGZ1bmN0aW9uKHIpe3JldHVybmB0cmFuc2xhdGUoJHt0aGlzLnhTY2FsZShyKX0sIDApYH0uYmluZCh0aGlzKSkuY2FsbCh0aGlzLnlBeGlzKSxlLmV4aXQoKS5yZW1vdmUoKX1kcmF3QXhpc0xhYmVscygpe2xldCBlPXRoaXMuYXhpc0dyb3VwLnNlbGVjdEFsbCgiLmF4aXMtYmctdGV4dCIpLmRhdGEodGhpcy5hY3RpdmVNZXRyaWNzKTtlLmVudGVyKCkuYXBwZW5kKCJ0ZXh0IikuYXR0cigiY2xhc3MiLCJheGlzLWJnLXRleHQiKS5hdHRyKCJmb250LXNpemUiLCIxM3B4IikuYXR0cigic3Ryb2tlLXdpZHRoIiwyKS5hdHRyKCJzdHJva2UtbGluZWpvaW4iLCJyb3VuZCIpLmF0dHIoInN0cm9rZSIsIndoaXRlIikubWVyZ2UoZSkudGV4dChzPT5zKS5hdHRyKCJ0cmFuc2Zvcm0iLGZ1bmN0aW9uKHMpe3JldHVybmB0cmFuc2xhdGUoJHt0aGlzLnhTY2FsZShzKS01fSwgJHt0aGlzLnlTY2FsZSh0aGlzLmNvb3JkaW5hdGVEYXRhLmV4dHJlbWVzLm1pbil9KSByb3RhdGUoLTkwKWB9LmJpbmQodGhpcykpLGUuZXhpdCgpLnJlbW92ZSgpO2xldCByPXRoaXMuYXhpc0dyb3VwLnNlbGVjdEFsbCgiLmF4aXMtdGV4dCIpLmRhdGEodGhpcy5hY3RpdmVNZXRyaWNzKTtyLmVudGVyKCkuYXBwZW5kKCJ0ZXh0IikuYXR0cigiZm9udC1zaXplIiwiMTNweCIpLmF0dHIoImNsYXNzIiwiYXhpcy10ZXh0IikubWVyZ2UocikudGV4dChzPT5zKS5hdHRyKCJ0cmFuc2Zvcm0iLGZ1bmN0aW9uKHMpe3JldHVybmB0cmFuc2xhdGUoJHt0aGlzLnhTY2FsZShzKS01fSwgJHt0aGlzLnlTY2FsZSh0aGlzLmNvb3JkaW5hdGVEYXRhLmV4dHJlbWVzLm1pbil9KSByb3RhdGUoLTkwKWB9LmJpbmQodGhpcykpLHIuZXhpdCgpLnJlbW92ZSgpfWRyYXdDb29yZGluYXRlcygpe2xldCBlPXRoaXMuY29vcmRpbmF0ZXNHcm91cC5zZWxlY3RBbGwoIi5jb29yZCIpLmRhdGEodGhpcy5jb29yZGluYXRlRGF0YS5jb29yZGluYXRlcyk7ZS5lbnRlcigpLmFwcGVuZCgicGF0aCIpLmF0dHIoImNsYXNzIiwiY29vcmQiKS5hdHRyKCJmaWxsIiwibm9uZSIpLm1lcmdlKGUpLmF0dHIoImQiLHRoaXMucGF0aC5iaW5kKHRoaXMpKS5hdHRyKCJzdHJva2UiLGZ1bmN0aW9uKHMpe3JldHVybiB0aGlzLmNvbG9yU2NhbGUocy5ydW5JZCl9LmJpbmQodGhpcykpLGUuZXhpdCgpLnJlbW92ZSgpO2xldCByPXRoaXMuY29vcmRpbmF0ZXNHcm91cC5zZWxlY3RBbGwoIi5oaWRkZW5Db29yZCIpLmRhdGEodGhpcy5jb29yZGluYXRlRGF0YS5jb29yZGluYXRlcyk7ci5lbnRlcigpLmFwcGVuZCgicGF0aCIpLmF0dHIoImNsYXNzIiwiaGlkZGVuQ29vcmQiKS5hdHRyKCJzdHJva2Utd2lkdGgiLCIxMHB4IikuYXR0cigiZmlsbCIsIm5vbmUiKS5hdHRyKCJzdHJva2UiLCJyZ2JhKDAsIDAsIDAsIDAuMCkiKS5vbigibW91c2VvdmVyIix0aGlzLmhhbmRsZUNvb3JkaW5hdGVNb3VzZU92ZXIuYmluZCh0aGlzKSkub24oIm1vdXNlb3V0Iix0aGlzLmhhbmRsZUNvb3JkaW5hdGVNb3VzZU91dC5iaW5kKHRoaXMpKS5tZXJnZShyKS5hdHRyKCJkIix0aGlzLnBhdGguYmluZCh0aGlzKSksci5leGl0KCkucmVtb3ZlKCl9cGF0aChlKXtyZXR1cm4gZS52YWx1ZXMuc29ydCgocixvKT0+dGhpcy5hY3RpdmVNZXRyaWNzLmluZGV4T2Yoci5tZXRyaWMpLXRoaXMuYWN0aXZlTWV0cmljcy5pbmRleE9mKG8ubWV0cmljKSkseFMoKShlLnZhbHVlcy5tYXAoZnVuY3Rpb24ocil7bGV0IG89dGhpcy55U2NhbGUoci5uUE1JVmFsdWUpO3JldHVyblt0aGlzLnhTY2FsZShyLm1ldHJpYyksb119LmJpbmQodGhpcykpKX1oYW5kbGVDb29yZGluYXRlTW91c2VPdmVyKGUsaSl7dGhpcy5sYWJlbHNHcm91cC5zZWxlY3RBbGwoIi5jb29yZGluYXRlLWxhYmVsIikuZmlsdGVyKGZ1bmN0aW9uKHIpe3JldHVybiByLmFubm90YXRpb24hPT1lLmFubm90YXRpb259KS5zdHlsZSgib3BhY2l0eSIsLjEpLHRoaXMuY29vcmRpbmF0ZXNHcm91cC5zZWxlY3RBbGwoIi5jb29yZCIpLmZpbHRlcihmdW5jdGlvbihyKXtyZXR1cm4gci5hbm5vdGF0aW9uIT09ZS5hbm5vdGF0aW9ufSkuc3R5bGUoIm9wYWNpdHkiLC4xKX1oYW5kbGVDb29yZGluYXRlTW91c2VPdXQoKXt0aGlzLmxhYmVsc0dyb3VwLnNlbGVjdEFsbCgiLmNvb3JkaW5hdGUtbGFiZWwiKS5zdHlsZSgib3BhY2l0eSIsMSksdGhpcy5jb29yZGluYXRlc0dyb3VwLnNlbGVjdEFsbCgiLmNvb3JkIikuc3R5bGUoIm9wYWNpdHkiLDEpfWRyYXdMYWJlbHMoKXtsZXQgZT0zMC90aGlzLnhTY2FsZS5zdGVwKCksaT10aGlzLmNvb3JkaW5hdGVEYXRhLmNvb3JkaW5hdGVzLmxlbmd0aDwzMD90aGlzLmNvb3JkaW5hdGVEYXRhLmNvb3JkaW5hdGVzOltdLHI9dGhpcy5sYWJlbHNHcm91cC5zZWxlY3RBbGwoIi5jb29yZGluYXRlLWxhYmVsIikuZGF0YShpKTtyLmVudGVyKCkuYXBwZW5kKCJ0ZXh0IikuYXR0cigiY2xhc3MiLCJjb29yZGluYXRlLWxhYmVsIikuYXR0cigiZm9udC1zaXplIiwiMTBweCIpLm1lcmdlKHIpLnRleHQoZnVuY3Rpb24ocyl7cmV0dXJuIHMuYW5ub3RhdGlvbn0pLmF0dHIoIngiLHRoaXMueFNjYWxlKHRoaXMuYWN0aXZlTWV0cmljc1swXSkrMzApLmF0dHIoInkiLGZ1bmN0aW9uKHMpe2xldCBhPXRoaXMueVNjYWxlKHMudmFsdWVzWzBdLm5QTUlWYWx1ZT9zLnZhbHVlc1swXS5uUE1JVmFsdWU6MCksbD10aGlzLnlTY2FsZShzLnZhbHVlc1sxXS5uUE1JVmFsdWU/cy52YWx1ZXNbMV0ublBNSVZhbHVlOjApO3JldHVybigxLWUpKmErZSpsfS5iaW5kKHRoaXMpKSxyLmV4aXQoKS5yZW1vdmUoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sicGFyYWxsZWwtY29vcmRpbmF0ZXMtY29tcG9uZW50Il1dLHZpZXdRdWVyeTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmb3QoeHR0LDcsUmUpLDImZSl7bGV0IHI7TmUocj1MZSgpKSYmKGkuc3ZnRWxlbWVudD1yLmZpcnN0KX19LGhvc3RCaW5kaW5nczpmdW5jdGlvbihlLGkpezEmZSYmUCgicmVzaXplIixmdW5jdGlvbihvKXtyZXR1cm4gaS5vblJlc2l6ZShvKX0sMCxXeCl9LGlucHV0czp7YWN0aXZlTWV0cmljczoiYWN0aXZlTWV0cmljcyIsY29vcmRpbmF0ZURhdGE6ImNvb3JkaW5hdGVEYXRhIixzaWRlYmFyV2lkdGg6InNpZGViYXJXaWR0aCIsY29sb3JTY2FsZToiY29sb3JTY2FsZSJ9LGZlYXR1cmVzOltGdF0sZGVjbHM6Mix2YXJzOjAsY29uc3RzOltbMSwicGMtY2hhcnQiXSxbImNoYXJ0IiwiIl1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoSW4oKSxPKDAsInN2ZyIsMCwxKSl9LHN0eWxlczpbIi5wYy1jaGFydFtfbmdjb250ZW50LSVDT01QJV17aGVpZ2h0OjMwMHB4O3dpZHRoOjEwMCV9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxabWU9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5hY3RpdmVSdW5zJD10aGlzLnN0b3JlLnBpcGUodnQob28pKS5waXBlKEwoaT0+aT9BcnJheS5mcm9tKGkuZW50cmllcygpKS5maWx0ZXIocj0+clsxXSkubWFwKHI9PnJbMF0pOltdKSksdGhpcy5hY3RpdmVNZXRyaWNzJD1MdCh0aGlzLnN0b3JlLnNlbGVjdChSZiksdGhpcy5hY3RpdmVSdW5zJCx0aGlzLnN0b3JlLnNlbGVjdChJbCkpLnBpcGUoTCgoW2kscixvXSk9PntsZXQgcz1bXTtmb3IobGV0IGEgb2YgcilpW2FdJiYocz1zLmNvbmNhdChpW2FdLmZpbHRlcihsPT5WYihsKSkpKTtyZXR1cm4gcz1bLi4ubmV3IFNldChbLi4uT2JqZWN0LmtleXMobyksLi4uc10pXSxzLm1hcChhPT5TcyhhKSl9KSksdGhpcy5jb29yZGluYXRlRGF0YSQ9THQoW3RoaXMuc3RvcmUuc2VsZWN0KFBmKSx0aGlzLnN0b3JlLnNlbGVjdCh2YyksdGhpcy5hY3RpdmVSdW5zJCx0aGlzLmFjdGl2ZU1ldHJpY3MkXSkucGlwZShMKChbaSxyLG8sc10pPT5mdW5jdGlvbihuLHQsZSxpKXtsZXQgcj1bXSxvPXttYXg6LTEsbWluOjF9LHM9bmV3IFNldChlKSxhPW5ldyBTZXQoaSk7cmV0dXJuIDA9PT1zLnNpemV8fDA9PT1hLnNpemV8fDA9PT1PYmplY3Qua2V5cyhuKS5sZW5ndGg/e2Nvb3JkaW5hdGVzOltdLGV4dHJlbWVzOnttaW46LTEsbWF4OjF9fToodC5mb3JFYWNoKGw9PntsZXQgdT17fTtuW2xdLmZvckVhY2goZD0+eyFzLmhhcyhkLnJ1bil8fCFhLmhhcyhkLm1ldHJpYyl8fCh1W2QucnVuXT91W2QucnVuXS5wdXNoKGQpOnVbZC5ydW5dPVtkXSxudWxsIT09ZC5uUE1JVmFsdWU/KG8ubWF4PU1hdGgubWF4KG8ubWF4LGQublBNSVZhbHVlKSxvLm1pbj1NYXRoLm1pbihvLm1pbixkLm5QTUlWYWx1ZSkpOihvLm1heD1NYXRoLm1heChvLm1heCwwKSxvLm1pbj1NYXRoLm1pbihvLm1pbiwwKSkpfSk7Zm9yKGxldCBkIG9mIE9iamVjdC5rZXlzKHUpKXIucHVzaCh7YW5ub3RhdGlvbjpsLHJ1bklkOmQsdmFsdWVzOnVbZF19KX0pLG8ubWF4PG8ubWluJiYoby5tYXg9MSxvLm1pbj0tMSkse2Nvb3JkaW5hdGVzOnIsZXh0cmVtZXM6b30pfShpLHIsbyxzKSkpLHRoaXMuc2lkZWJhcldpZHRoJD10aGlzLnN0b3JlLnNlbGVjdChPZiksdGhpcy5ydW5Db2xvclNjYWxlJD10aGlzLnN0b3JlLnNlbGVjdChuYykucGlwZShMKGk9PnI9PntpZighaS5oYXNPd25Qcm9wZXJ0eShyKSl0aHJvdyBuZXcgRXJyb3IoYFtDb2xvciBzY2FsZV0gdW5rbm93biBydW5JZDogJHtyfS5gKTtyZXR1cm4gaVtyXX0pKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktcGFyYWxsZWwtY29vcmRpbmF0ZXMiXV0sZGVjbHM6NSx2YXJzOjEyLGNvbnN0czpbWzMsImFjdGl2ZU1ldHJpY3MiLCJjb29yZGluYXRlRGF0YSIsInNpZGViYXJXaWR0aCIsImNvbG9yU2NhbGUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihPKDAsInBhcmFsbGVsLWNvb3JkaW5hdGVzLWNvbXBvbmVudCIsMCksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIiksQig0LCJhc3luYyIpKSwyJmUmJnkoImFjdGl2ZU1ldHJpY3MiLFUoMSw0LGkuYWN0aXZlTWV0cmljcyQpKSgiY29vcmRpbmF0ZURhdGEiLFUoMiw2LGkuY29vcmRpbmF0ZURhdGEkKSkoInNpZGViYXJXaWR0aCIsVSgzLDgsaS5zaWRlYmFyV2lkdGgkKSkoImNvbG9yU2NhbGUiLFUoNCwxMCxpLnJ1bkNvbG9yU2NhbGUkKSl9LGRlcGVuZGVuY2llczpbS21lLEdlXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiB3dHQobix0KXsxJm4mJk8oMCwibWF0LWljb24iLDkpfWZ1bmN0aW9uIFN0dChuLHQpezEmbiYmTygwLCJtYXQtaWNvbiIsMTApfWZ1bmN0aW9uIEV0dChuLHQpezEmbiYmTygwLCJucG1pLXBhcmFsbGVsLWNvb3JkaW5hdGVzIil9dmFyIEptZT0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKCl7dGhpcy5vbkNsZWFyU2VsZWN0ZWRBbm5vdGF0aW9ucz1uZXcgRyx0aGlzLm9uVG9nZ2xlRXhwYW5kZWQ9bmV3IEd9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInNlbGVjdGVkLWFubm90YXRpb25zLWNvbXBvbmVudCJdXSxpbnB1dHM6e3BjRXhwYW5kZWQ6InBjRXhwYW5kZWQiLHNlbGVjdGVkQW5ub3RhdGlvbnM6InNlbGVjdGVkQW5ub3RhdGlvbnMifSxvdXRwdXRzOntvbkNsZWFyU2VsZWN0ZWRBbm5vdGF0aW9uczoib25DbGVhclNlbGVjdGVkQW5ub3RhdGlvbnMiLG9uVG9nZ2xlRXhwYW5kZWQ6Im9uVG9nZ2xlRXhwYW5kZWQifSxkZWNsczoxMSx2YXJzOjQsY29uc3RzOmZ1bmN0aW9uKCl7bGV0IHQsZTtyZXR1cm4gdD0kbG9jYWxpemVgOkxhYmVsIGZvciBhIGJ1dHRvbiB0aGF0IGNsZWFycyB0aGUgYW5ub3RhdGlvbiBzZWxlY3Rpb24u4pCfMmI1MjI4OWYyYzRiN2Y1YjE4MmVkMjNjOTE1NDcyMmVjZDQ2YTJkNOKQnzI0OTQ2NjA1MjAzNDEzMDg4MzA6Q2xlYXIgQW5ub3RhdGlvbiBTZWxlY3Rpb25gLGU9JGxvY2FsaXplYDpMYWJlbCBmb3IgYSBidXR0b24gdGhhdCBleHBhbmRzIG9yIGhpZGVzIHNlbGVjdGVkIGFubm90YXRpb25zLuKQnzhmNGFkMzA1YzE5ZTM2NTVmMzE4OWUzZTI2NGU4M2ZiNzZmN2JiOTXikJ81NjcwNzk3Nzg0MDcwOTUyMDk1OkV4cGFuZC9IaWRlIFNlbGVjdGVkIEFubm90YXRpb25zYCxbWzEsInBjLWNvbnRhaW5lciJdLFsxLCJwYy10b29sYmFyIl0sWzEsInBjLXRpdGxlIl0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJhcmlhLWxhYmVsIix0LCJ0aXRsZSIsIkRlc2VsZWN0cyBhbGwgc2VsZWN0ZWQgYW5ub3RhdGlvbnMuIiwxLCJjbGVhci1idXR0b24iLDMsImRpc2FibGVkIiwiY2xpY2siXSxbInN2Z0ljb24iLCJjbGVhcl8yNHB4Il0sWyJtYXQtaWNvbi1idXR0b24iLCIiLCJhcmlhLWxhYmVsIixlLDEsImV4cGFuZC1idXR0b24iLDMsImNsaWNrIl0sWyJzdmdJY29uIiwiZXhwYW5kX2xlc3NfMjRweCIsImNsYXNzIiwiZXhwYW5kLWxlc3MtaWNvbiIsNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJub3RFeHBhbmRlZCIsIiJdLFs0LCJuZ0lmIl0sWyJzdmdJY29uIiwiZXhwYW5kX2xlc3NfMjRweCIsMSwiZXhwYW5kLWxlc3MtaWNvbiJdLFsic3ZnSWNvbiIsImV4cGFuZF9tb3JlXzI0cHgiLDEsImV4cGFuZC1pY29uIl1dfSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpe2lmKDEmZSYmKF8oMCwiZGl2IiwwKSgxLCJkaXYiLDEpKDIsImgzIiwyKSxBKDMsIlNlbGVjdGVkIEFubm90YXRpb25zIiksdigpLF8oNCwiYnV0dG9uIiwzKSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vbkNsZWFyU2VsZWN0ZWRBbm5vdGF0aW9ucy5lbWl0KCl9KSxPKDUsIm1hdC1pY29uIiw0KSx2KCksXyg2LCJidXR0b24iLDUpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLm9uVG9nZ2xlRXhwYW5kZWQuZW1pdCgpfSksRSg3LHd0dCwxLDAsIm1hdC1pY29uIiw2KSxFKDgsU3R0LDEsMCwibmctdGVtcGxhdGUiLG51bGwsNyxxdCksdigpKCksRSgxMCxFdHQsMSwwLCJucG1pLXBhcmFsbGVsLWNvb3JkaW5hdGVzIiw4KSx2KCkpLDImZSl7bGV0IHI9JGUoOSk7Qyg0KSx5KCJkaXNhYmxlZCIsMD09PWkuc2VsZWN0ZWRBbm5vdGF0aW9ucy5sZW5ndGgpLEMoMykseSgibmdJZiIsaS5wY0V4cGFuZGVkKSgibmdJZkVsc2UiLHIpLEMoMykseSgibmdJZiIsaS5wY0V4cGFuZGVkKX19LGRlcGVuZGVuY2llczpbQmUsR3QsX24sWm1lXSxzdHlsZXM6WyIucGMtY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7Ym9yZGVyOjFweCBzb2xpZCAjZWJlYmVifS5wYy10b29sYmFyW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7Ym9yZGVyLWJvdHRvbToxcHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmZsZXg7aGVpZ2h0OjQycHg7cGFkZGluZzowIDE2cHh9LnBjLXRpdGxlW19uZ2NvbnRlbnQtJUNPTVAlXXtmb250LXNpemU6MTRweDtmb250LXdlaWdodDo1MDA7ZGlzcGxheTppbmxpbmU7ZmxleDoxIDF9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxYRj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuc3RvcmU9ZSx0aGlzLnBjRXhwYW5kZWQkPXRoaXMuc3RvcmUucGlwZSh2dChvbWUpKSx0aGlzLnNlbGVjdGVkQW5ub3RhdGlvbnMkPXRoaXMuc3RvcmUuc2VsZWN0KHZjKX1jbGVhclNlbGVjdGVkQW5ub3RhdGlvbnMoKXt0aGlzLnN0b3JlLmRpc3BhdGNoKFNGKCkpfXRvZ2dsZUV4cGFuZGVkKCl7dGhpcy5zdG9yZS5kaXNwYXRjaChQRigpKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktc2VsZWN0ZWQtYW5ub3RhdGlvbnMiXV0sZGVjbHM6Myx2YXJzOjYsY29uc3RzOltbMywicGNFeHBhbmRlZCIsInNlbGVjdGVkQW5ub3RhdGlvbnMiLCJvbkNsZWFyU2VsZWN0ZWRBbm5vdGF0aW9ucyIsIm9uVG9nZ2xlRXhwYW5kZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsInNlbGVjdGVkLWFubm90YXRpb25zLWNvbXBvbmVudCIsMCksUCgib25DbGVhclNlbGVjdGVkQW5ub3RhdGlvbnMiLGZ1bmN0aW9uKCl7cmV0dXJuIGkuY2xlYXJTZWxlY3RlZEFubm90YXRpb25zKCl9KSgib25Ub2dnbGVFeHBhbmRlZCIsZnVuY3Rpb24oKXtyZXR1cm4gaS50b2dnbGVFeHBhbmRlZCgpfSksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSx2KCkpLDImZSYmeSgicGNFeHBhbmRlZCIsVSgxLDIsaS5wY0V4cGFuZGVkJCkpKCJzZWxlY3RlZEFubm90YXRpb25zIixVKDIsNCxpLnNlbGVjdGVkQW5ub3RhdGlvbnMkKSl9LGRlcGVuZGVuY2llczpbSm1lLEdlXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiBEdHQobix0KXsxJm4mJihfKDAsImRpdiIsNikoMSwiZGl2Iiw3KSxPKDIsIm5wbWktdmlvbGluLWZpbHRlcnMiLDgpLF8oMywiZGl2Iiw5KSxPKDQsInJ1bnMtc2VsZWN0b3IiKSx2KCkoKSgpKSwyJm4mJlB0KCJ3aWR0aCIsUygpLnNpZGViYXJXaWR0aCwicHgiKX1mdW5jdGlvbiBBdHQobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDEwKSxQKCJtb3VzZWRvd24iLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5yZXNpemVHcmFiYmVkLmVtaXQoKSl9KSx2KCl9fWZ1bmN0aW9uIEl0dChuLHQpezEmbiYmKF8oMCwiZGl2IiwxMSksTygxLCJucG1pLWFubm90YXRpb25zLWxpc3QiLDEyKSgyLCJucG1pLXNlbGVjdGVkLWFubm90YXRpb25zIiksdigpKX1mdW5jdGlvbiBQdHQobix0KXsxJm4mJihfKDAsImRpdiIsMTMpLEEoMSwiWW91IG5lZWQgdG8gc2VsZWN0IGF0IGxlYXN0IG9uZSBydW4uIiksdigpKX1mdW5jdGlvbiBSdHQobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDE0KSgxLCJidXR0b24iLDE1KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLnRvZ2dsZVNpZGViYXJFeHBhbmRlZC5lbWl0KCkpfSksTygyLCJtYXQtaWNvbiIsMTYpLHYoKSgpfX12YXIgZWdlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLnRvZ2dsZVNpZGViYXJFeHBhbmRlZD1uZXcgRyx0aGlzLnJlc2l6ZVRyaWdnZXJlZD1uZXcgRyx0aGlzLnJlc2l6ZUdyYWJiZWQ9bmV3IEcsdGhpcy5yZXNpemVSZWxlYXNlZD1uZXcgR319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibWFpbi1jb21wb25lbnQiXV0saW5wdXRzOntydW5BY3RpdmU6InJ1bkFjdGl2ZSIsc2lkZWJhckV4cGFuZGVkOiJzaWRlYmFyRXhwYW5kZWQiLHNpZGViYXJXaWR0aDoic2lkZWJhcldpZHRoIn0sb3V0cHV0czp7dG9nZ2xlU2lkZWJhckV4cGFuZGVkOiJ0b2dnbGVTaWRlYmFyRXhwYW5kZWQiLHJlc2l6ZVRyaWdnZXJlZDoicmVzaXplVHJpZ2dlcmVkIixyZXNpemVHcmFiYmVkOiJyZXNpemVHcmFiYmVkIixyZXNpemVSZWxlYXNlZDoicmVzaXplUmVsZWFzZWQifSxkZWNsczo4LHZhcnM6NSxjb25zdHM6ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gdD0kbG9jYWxpemVgOkxhYmVsIGZvciBhIGJ1dHRvbiB0aGF0IHRvZ2dsZXMgdGhlIHNpZGViYXIu4pCfZjYzYjU3OTMyZDE3OWNjYTYyYWM5ZmNhZTYzZGQ3ZjVlNmZhMzg5ZuKQnzQ0ODUwMDkzNDc0NDQ3MDQ4Nzg6VG9nZ2xlIFNpZGViYXJgLFtbMSwiY29udGVudCIsMywibW91c2V1cCIsIm1vdXNlbW92ZSJdLFsiY2xhc3MiLCJzaWRlYmFyLWNvbnRhaW5lciIsMywid2lkdGgiLDQsIm5nSWYiXSxbImNsYXNzIiwiZ3JhYmJlciIsMywibW91c2Vkb3duIiw0LCJuZ0lmIl0sWyJjbGFzcyIsImFuYWx5c2lzLWNvbnRhaW5lciIsNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJub1J1biIsIiJdLFsiY2xhc3MiLCJzaWRlLXRvZ2dsZSIsNCwibmdJZiJdLFsxLCJzaWRlYmFyLWNvbnRhaW5lciJdLFsxLCJzaWRlYmFyLWNvbnRlbnRzIl0sWzEsInZpb2xpbi1maWx0ZXJzIl0sWzEsInJ1bi1zZWxlY3RvciJdLFsxLCJncmFiYmVyIiwzLCJtb3VzZWRvd24iXSxbMSwiYW5hbHlzaXMtY29udGFpbmVyIl0sWzEsImFubm90YXRpb25zLWxpc3QiXSxbMSwibm9SdW4iXSxbMSwic2lkZS10b2dnbGUiXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsImFyaWEtbGFiZWwiLHQsMywiY2xpY2siXSxbInN2Z0ljb24iLCJjaGV2cm9uX3JpZ2h0XzI0cHgiXV19LHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoTygwLCJucG1pLWRhdGEtc2VsZWN0aW9uIiksXygxLCJkaXYiLDApLFAoIm1vdXNldXAiLGZ1bmN0aW9uKCl7cmV0dXJuIGkucmVzaXplUmVsZWFzZWQuZW1pdCgpfSkoIm1vdXNlbW92ZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkucmVzaXplVHJpZ2dlcmVkLmVtaXQobyl9KSxFKDIsRHR0LDUsMiwiZGl2IiwxKSxFKDMsQXR0LDEsMCwiZGl2IiwyKSxFKDQsSXR0LDMsMCwiZGl2IiwzKSxFKDUsUHR0LDIsMCwibmctdGVtcGxhdGUiLG51bGwsNCxxdCksdigpLEUoNyxSdHQsMywwLCJkaXYiLDUpKSwyJmUpe2xldCByPSRlKDYpO0MoMikseSgibmdJZiIsaS5zaWRlYmFyRXhwYW5kZWQpLEMoMSkseSgibmdJZiIsaS5zaWRlYmFyRXhwYW5kZWQpLEMoMSkseSgibmdJZiIsITA9PT1pLnJ1bkFjdGl2ZSkoIm5nSWZFbHNlIixyKSxDKDMpLHkoIm5nSWYiLCFpLnNpZGViYXJFeHBhbmRlZCl9fSxkZXBlbmRlbmNpZXM6W0JlLEd0LE9iLFdGLF9uLE9tZSxZRixYRl0sc3R5bGVzOlsnW19uZ2hvc3QtJUNPTVAlXXtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDAlfS5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmZsZXg7ZmxleDoxO21pbi1oZWlnaHQ6MHB4O3dpZHRoOjEwMCV9LnNpZGViYXItY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtoZWlnaHQ6MTAwJTtvdmVyZmxvdzpoaWRkZW59LnNpZGViYXItY29udGVudHNbX25nY29udGVudC0lQ09NUCVde2hlaWdodDoxMDAlO2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47bWluLXdpZHRoOjE1MHB4fS5hbmFseXNpcy1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47aGVpZ2h0OjEwMCU7ZmxleDoxIDF9LnJ1bi1zZWxlY3Rvcltfbmdjb250ZW50LSVDT01QJV17bWF4LWhlaWdodDoxMDAlO3dpZHRoOjEwMCV9LnZpb2xpbi1maWx0ZXJzW19uZ2NvbnRlbnQtJUNPTVAlXXt3aWR0aDoxMDAlfS5zaWRlLXRvZ2dsZVtfbmdjb250ZW50LSVDT01QJV17d2lkdGg6MzBweDtoZWlnaHQ6MzBweDtwb3NpdGlvbjphYnNvbHV0ZTtsZWZ0OjEwcHg7Ym90dG9tOjEwcHg7Ym9yZGVyOjFweCBzb2xpZCAjZWJlYmViO2JvcmRlci1yYWRpdXM6M3B4O2Rpc3BsYXk6ZmxleDthbGlnbi1pdGVtczpjZW50ZXI7anVzdGlmeS1jb250ZW50OmNlbnRlcn0uZ3JhYmJlcltfbmdjb250ZW50LSVDT01QJV17Y29udGVudDoiIjtjdXJzb3I6ZXctcmVzaXplO2hlaWdodDoxMDAlO3dpZHRoOjNweDtvdmVyZmxvdzpoaWRkZW47YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xMil9LmFubm90YXRpb25zLWxpc3RbX25nY29udGVudC0lQ09NUCVde3dpZHRoOjEwMCU7bWluLWhlaWdodDowcHg7ZmxleDoxIDF9J10sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSx0Z2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5ydW5BY3RpdmUkPXRoaXMuc3RvcmUucGlwZSh2dChvbykpLnBpcGUoTChpPT4hIWkmJlsuLi5pLnZhbHVlcygpXS5pbmNsdWRlcyghMCkpKSx0aGlzLnNpZGViYXJFeHBhbmRlZCQ9dGhpcy5zdG9yZS5waXBlKHZ0KHpGKSksdGhpcy5zaWRlYmFyV2lkdGgkPXRoaXMuc3RvcmUucGlwZSh2dChPZikpLHRoaXMucmVzaXppbmc9ITF9b25Ub2dnbGVTaWRlYmFyRXhwYW5kZWQoKXt0aGlzLnN0b3JlLmRpc3BhdGNoKHFiKCkpfW9uUmVzaXplVHJpZ2dlcmVkKGUpe3RoaXMucmVzaXppbmcmJnRoaXMuc3RvcmUuZGlzcGF0Y2goTkYoe3NpZGViYXJXaWR0aDplLmNsaWVudFh9KSl9b25SZXNpemVHcmFiYmVkKCl7dGhpcy5yZXNpemluZz0hMH1vblJlc2l6ZVJlbGVhc2VkKCl7dGhpcy5yZXNpemluZz0hMX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbIm5wbWktbWFpbiJdXSxkZWNsczo0LHZhcnM6OSxjb25zdHM6W1szLCJydW5BY3RpdmUiLCJzaWRlYmFyRXhwYW5kZWQiLCJzaWRlYmFyV2lkdGgiLCJ0b2dnbGVTaWRlYmFyRXhwYW5kZWQiLCJyZXNpemVUcmlnZ2VyZWQiLCJyZXNpemVHcmFiYmVkIiwicmVzaXplUmVsZWFzZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsIm1haW4tY29tcG9uZW50IiwwKSxQKCJ0b2dnbGVTaWRlYmFyRXhwYW5kZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25Ub2dnbGVTaWRlYmFyRXhwYW5kZWQoKX0pKCJyZXNpemVUcmlnZ2VyZWQiLGZ1bmN0aW9uKG8pe3JldHVybiBpLm9uUmVzaXplVHJpZ2dlcmVkKG8pfSkoInJlc2l6ZUdyYWJiZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25SZXNpemVHcmFiYmVkKCl9KSgicmVzaXplUmVsZWFzZWQiLGZ1bmN0aW9uKCl7cmV0dXJuIGkub25SZXNpemVSZWxlYXNlZCgpfSksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSxCKDMsImFzeW5jIiksdigpKSwyJmUmJnkoInJ1bkFjdGl2ZSIsVSgxLDMsaS5ydW5BY3RpdmUkKSkoInNpZGViYXJFeHBhbmRlZCIsVSgyLDUsaS5zaWRlYmFyRXhwYW5kZWQkKSkoInNpZGViYXJXaWR0aCIsVSgzLDcsaS5zaWRlYmFyV2lkdGgkKSl9LGRlcGVuZGVuY2llczpbZWdlLEdlXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxuZ2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMudG9nZ2xlU2lkZWJhckV4cGFuZGVkPW5ldyBHfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJlbWJlZGRpbmctcHJvamVjdGlvbi1jb21wb25lbnQiXV0saW5wdXRzOntzaWRlYmFyRXhwYW5kZWQ6InNpZGViYXJFeHBhbmRlZCJ9LG91dHB1dHM6e3RvZ2dsZVNpZGViYXJFeHBhbmRlZDoidG9nZ2xlU2lkZWJhckV4cGFuZGVkIn0sZGVjbHM6Nyx2YXJzOjAsY29uc3RzOmZ1bmN0aW9uKCl7bGV0IHQ7cmV0dXJuIHQ9JGxvY2FsaXplYDpMYWJlbCBmb3IgYSBidXR0b24gdGhhdCBleHBhbmRzL2hpZGVzIHRoZSBzaWRlYmFyLuKQnzQ4YzI5OTAzY2U4ODFhYjYxMDg4ZjhkNDlkODI3MjAzNzE2YWFlZDTikJ80NjU4NjAyOTkxOTcwMjYwMjE1OkV4cGFuZC9IaWRlIFNpZGViYXJgLFtbMSwiZW1iZWRkaW5nLXByb2plY3Rpb24tdG9vbGJhciJdLFsxLCJlbWJlZGRpbmctcHJvamVjdGlvbi10aXRsZSJdLFsxLCJzaWRlLXRvZ2dsZSJdLFsibWF0LWljb24tYnV0dG9uIiwiIiwiYXJpYS1sYWJlbCIsdCwzLCJjbGljayJdLFsic3ZnSWNvbiIsImNoZXZyb25fbGVmdF8yNHB4Il0sWzEsInByb2plY3Rpb24iXV19LHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoXygwLCJkaXYiLDApKDEsImgzIiwxKSxBKDIsIkVtYmVkZGluZ3MgUHJvamVjdGVkIiksdigpLF8oMywiZGl2IiwyKSg0LCJidXR0b24iLDMpLFAoImNsaWNrIixmdW5jdGlvbigpe3JldHVybiBpLnRvZ2dsZVNpZGViYXJFeHBhbmRlZC5lbWl0KCl9KSxPKDUsIm1hdC1pY29uIiw0KSx2KCkoKSgpLE8oNiwiZGl2Iiw1KSl9LGRlcGVuZGVuY2llczpbR3QsX25dLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17ZGlzcGxheTpmbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjtoZWlnaHQ6MTAwJX0uZW1iZWRkaW5nLXByb2plY3Rpb24tdG9vbGJhcltfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNlYmViZWI7ZGlzcGxheTpmbGV4O2hlaWdodDo0MnB4O2p1c3RpZnktY29udGVudDpzcGFjZS1iZXR3ZWVuO3BhZGRpbmc6MCAxMHB4fS5lbWJlZGRpbmctcHJvamVjdGlvbi10aXRsZVtfbmdjb250ZW50LSVDT01QJV17ZGlzcGxheTppbmxpbmU7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwfS5wcm9qZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXtvdmVyZmxvdy15OmF1dG99LnNpZGUtdG9nZ2xlW19uZ2NvbnRlbnQtJUNPTVAlXXthbGlnbi1pdGVtczpjZW50ZXI7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JvcmRlci1yYWRpdXM6M3B4O2JvcmRlcjoxcHggc29saWQgI2ViZWJlYjtkaXNwbGF5OmZsZXg7aGVpZ2h0OjMwcHg7anVzdGlmeS1jb250ZW50OmNlbnRlcjt3aWR0aDozMHB4fSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksaWdlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuc2lkZWJhckV4cGFuZGVkJD10aGlzLnN0b3JlLnNlbGVjdChHRil9b25Ub2dnbGVTaWRlYmFyRXhwYW5kZWQoKXt0aGlzLnN0b3JlLmRpc3BhdGNoKFliKCkpfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibnBtaS1lbWJlZGRpbmctcHJvamVjdGlvbiJdXSxkZWNsczoyLHZhcnM6Myxjb25zdHM6W1szLCJzaWRlYmFyRXhwYW5kZWQiLCJ0b2dnbGVTaWRlYmFyRXhwYW5kZWQiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImVtYmVkZGluZy1wcm9qZWN0aW9uLWNvbXBvbmVudCIsMCksUCgidG9nZ2xlU2lkZWJhckV4cGFuZGVkIixmdW5jdGlvbigpe3JldHVybiBpLm9uVG9nZ2xlU2lkZWJhckV4cGFuZGVkKCl9KSxCKDEsImFzeW5jIiksdigpKSwyJmUmJnkoInNpZGViYXJFeHBhbmRlZCIsVSgxLDEsaS5zaWRlYmFyRXhwYW5kZWQkKSl9LGRlcGVuZGVuY2llczpbbmdlLEdlXSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKTtmdW5jdGlvbiBMdHQobix0KXsxJm4mJihfKDAsImRpdiIsNikoMSwiZGl2Iiw3KSxPKDIsIm5wbWktZW1iZWRkaW5nLXByb2plY3Rpb24iLDgpKDMsImRpdiIsOSksXyg0LCJkaXYiLDEwKSxPKDUsInJ1bnMtc2VsZWN0b3IiKSx2KCkoKSgpKSwyJm4mJlB0KCJ3aWR0aCIsUygpLnNpZGViYXJXaWR0aCwicHgiKX1mdW5jdGlvbiBCdHQobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDExKSxQKCJtb3VzZWRvd24iLGZ1bmN0aW9uKCl7cmV0dXJuIG9lKGUpLHNlKFMoKS5yZXNpemVHcmFiYmVkLmVtaXQoKSl9KSx2KCl9fWZ1bmN0aW9uIFZ0dChuLHQpezEmbiYmKF8oMCwiZGl2IiwxMiksTygxLCJucG1pLWFubm90YXRpb25zLWxpc3QiLDEzKSgyLCJucG1pLXNlbGVjdGVkLWFubm90YXRpb25zIiksdigpKX1mdW5jdGlvbiBIdHQobix0KXsxJm4mJihfKDAsImRpdiIsMTQpLEEoMSwiWW91IG5lZWQgdG8gc2VsZWN0IGF0IGxlYXN0IG9uZSBydW4uIiksdigpKX1mdW5jdGlvbiBVdHQobix0KXtpZigxJm4pe2xldCBlPVBlKCk7XygwLCJkaXYiLDE1KSgxLCJidXR0b24iLDE2KSxQKCJjbGljayIsZnVuY3Rpb24oKXtyZXR1cm4gb2UoZSksc2UoUygpLnRvZ2dsZVNpZGViYXJFeHBhbmRlZC5lbWl0KCkpfSksTygyLCJtYXQtaWNvbiIsMTcpLHYoKSgpfX12YXIgcmdlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoKXt0aGlzLnRvZ2dsZVNpZGViYXJFeHBhbmRlZD1uZXcgRyx0aGlzLnJlc2l6ZVRyaWdnZXJlZD1uZXcgRyx0aGlzLnJlc2l6ZUdyYWJiZWQ9bmV3IEcsdGhpcy5yZXNpemVSZWxlYXNlZD1uZXcgR319cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1siZW1iZWRkaW5ncy1jb21wb25lbnQiXV0saW5wdXRzOntydW5BY3RpdmU6InJ1bkFjdGl2ZSIsc2lkZWJhckV4cGFuZGVkOiJzaWRlYmFyRXhwYW5kZWQiLHNpZGViYXJXaWR0aDoic2lkZWJhcldpZHRoIn0sb3V0cHV0czp7dG9nZ2xlU2lkZWJhckV4cGFuZGVkOiJ0b2dnbGVTaWRlYmFyRXhwYW5kZWQiLHJlc2l6ZVRyaWdnZXJlZDoicmVzaXplVHJpZ2dlcmVkIixyZXNpemVHcmFiYmVkOiJyZXNpemVHcmFiYmVkIixyZXNpemVSZWxlYXNlZDoicmVzaXplUmVsZWFzZWQifSxkZWNsczo4LHZhcnM6NSxjb25zdHM6ZnVuY3Rpb24oKXtsZXQgdDtyZXR1cm4gdD0kbG9jYWxpemVgOkxhYmVsIGZvciBhIGJ1dHRvbiB0aGF0IHRvZ2dsZXMgdGhlIHNpZGViYXIu4pCfZjYzYjU3OTMyZDE3OWNjYTYyYWM5ZmNhZTYzZGQ3ZjVlNmZhMzg5ZuKQnzQ0ODUwMDkzNDc0NDQ3MDQ4Nzg6VG9nZ2xlIFNpZGViYXJgLFtbMSwiY29udGVudCIsMywibW91c2V1cCIsIm1vdXNlbW92ZSJdLFsiY2xhc3MiLCJzaWRlYmFyLWNvbnRhaW5lciIsMywid2lkdGgiLDQsIm5nSWYiXSxbImNsYXNzIiwiZ3JhYmJlciIsMywibW91c2Vkb3duIiw0LCJuZ0lmIl0sWyJjbGFzcyIsImFuYWx5c2lzLWNvbnRhaW5lciIsNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJub1J1biIsIiJdLFsiY2xhc3MiLCJzaWRlLXRvZ2dsZSIsNCwibmdJZiJdLFsxLCJzaWRlYmFyLWNvbnRhaW5lciJdLFsxLCJzaWRlYmFyLWNvbnRlbnRzIl0sWzEsImVtYmVkZGluZy1wcm9qZWN0aW9uIl0sWzEsInJ1bi1kaXZpZGVyIl0sWzEsInJ1bi1zZWxlY3RvciJdLFsxLCJncmFiYmVyIiwzLCJtb3VzZWRvd24iXSxbMSwiYW5hbHlzaXMtY29udGFpbmVyIl0sWzEsImFubm90YXRpb25zLWxpc3QiXSxbMSwibm9SdW4iXSxbMSwic2lkZS10b2dnbGUiXSxbIm1hdC1pY29uLWJ1dHRvbiIsIiIsImFyaWEtbGFiZWwiLHQsMywiY2xpY2siXSxbInN2Z0ljb24iLCJjaGV2cm9uX3JpZ2h0XzI0cHgiXV19LHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYoTygwLCJucG1pLWRhdGEtc2VsZWN0aW9uIiksXygxLCJkaXYiLDApLFAoIm1vdXNldXAiLGZ1bmN0aW9uKCl7cmV0dXJuIGkucmVzaXplUmVsZWFzZWQuZW1pdCgpfSkoIm1vdXNlbW92ZSIsZnVuY3Rpb24obyl7cmV0dXJuIGkucmVzaXplVHJpZ2dlcmVkLmVtaXQobyl9KSxFKDIsTHR0LDYsMiwiZGl2IiwxKSxFKDMsQnR0LDEsMCwiZGl2IiwyKSxFKDQsVnR0LDMsMCwiZGl2IiwzKSxFKDUsSHR0LDIsMCwibmctdGVtcGxhdGUiLG51bGwsNCxxdCksdigpLEUoNyxVdHQsMywwLCJkaXYiLDUpKSwyJmUpe2xldCByPSRlKDYpO0MoMikseSgibmdJZiIsaS5zaWRlYmFyRXhwYW5kZWQpLEMoMSkseSgibmdJZiIsaS5zaWRlYmFyRXhwYW5kZWQpLEMoMSkseSgibmdJZiIsITA9PT1pLnJ1bkFjdGl2ZSkoIm5nSWZFbHNlIixyKSxDKDMpLHkoIm5nSWYiLCFpLnNpZGViYXJFeHBhbmRlZCl9fSxkZXBlbmRlbmNpZXM6W0JlLEd0LE9iLFdGLF9uLFlGLFhGLGlnZV0sc3R5bGVzOlsnW19uZ2hvc3QtJUNPTVAlXXtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDAlfS5jb250ZW50W19uZ2NvbnRlbnQtJUNPTVAlXXtkaXNwbGF5OmZsZXg7ZmxleDoxO21pbi1oZWlnaHQ6MHB4O3dpZHRoOjEwMCV9LnNpZGViYXItY29udGFpbmVyW19uZ2NvbnRlbnQtJUNPTVAlXXtoZWlnaHQ6MTAwJTtvdmVyZmxvdzpoaWRkZW59LnNpZGViYXItY29udGVudHNbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47aGVpZ2h0OjEwMCU7bWluLXdpZHRoOjE1MHB4fS5hbmFseXNpcy1jb250YWluZXJbX25nY29udGVudC0lQ09NUCVde2Rpc3BsYXk6ZmxleDtmbGV4OjEgMTtmbGV4LWRpcmVjdGlvbjpjb2x1bW47aGVpZ2h0OjEwMCV9LnJ1bi1zZWxlY3Rvcltfbmdjb250ZW50LSVDT01QJV17bWF4LWhlaWdodDoxMDAlO3dpZHRoOjEwMCV9LmVtYmVkZGluZy1wcm9qZWN0aW9uW19uZ2NvbnRlbnQtJUNPTVAlXXttaW4taGVpZ2h0OjBweDt3aWR0aDoxMDAlfS5zaWRlLXRvZ2dsZVtfbmdjb250ZW50LSVDT01QJV17YWxpZ24taXRlbXM6Y2VudGVyO2JvcmRlcjoxcHggc29saWQgI2ViZWJlYjtib3JkZXItcmFkaXVzOjNweDtib3R0b206MTBweDtkaXNwbGF5OmZsZXg7aGVpZ2h0OjMwcHg7anVzdGlmeS1jb250ZW50OmNlbnRlcjtsZWZ0OjEwcHg7cG9zaXRpb246YWJzb2x1dGU7d2lkdGg6MzBweH0ucnVuLWRpdmlkZXJbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMTIpO2NvbnRlbnQ6IiI7aGVpZ2h0OjFweDtvdmVyZmxvdzpoaWRkZW59LmdyYWJiZXJbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMTIpO2NvbnRlbnQ6IiI7Y3Vyc29yOmV3LXJlc2l6ZTtoZWlnaHQ6MTAwJTtvdmVyZmxvdzpoaWRkZW47d2lkdGg6M3B4fS5hbm5vdGF0aW9ucy1saXN0W19uZ2NvbnRlbnQtJUNPTVAlXXtmbGV4OjEgMTttaW4taGVpZ2h0OjBweDt3aWR0aDoxMDAlfSddLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksb2dlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMucnVuQWN0aXZlJD10aGlzLnN0b3JlLnBpcGUodnQob28pKS5waXBlKEwoaT0+ISFpJiZbLi4uaS52YWx1ZXMoKV0uaW5jbHVkZXMoITApKSksdGhpcy5zaWRlYmFyRXhwYW5kZWQkPXRoaXMuc3RvcmUucGlwZSh2dChHRikpLHRoaXMuc2lkZWJhcldpZHRoJD10aGlzLnN0b3JlLnBpcGUodnQobG1lKSksdGhpcy5yZXNpemluZz0hMX1vblRvZ2dsZVNpZGViYXJFeHBhbmRlZCgpe3RoaXMuc3RvcmUuZGlzcGF0Y2goWWIoKSl9b25SZXNpemVUcmlnZ2VyZWQoZSl7dGhpcy5yZXNpemluZyYmdGhpcy5zdG9yZS5kaXNwYXRjaChMRih7c2lkZWJhcldpZHRoOmUuY2xpZW50WH0pKX1vblJlc2l6ZUdyYWJiZWQoKXt0aGlzLnJlc2l6aW5nPSEwfW9uUmVzaXplUmVsZWFzZWQoKXt0aGlzLnJlc2l6aW5nPSExfX1yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKShNKENlKSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibnBtaS1lbWJlZGRpbmdzIl1dLGRlY2xzOjQsdmFyczo5LGNvbnN0czpbWzMsInJ1bkFjdGl2ZSIsInNpZGViYXJFeHBhbmRlZCIsInNpZGViYXJXaWR0aCIsInRvZ2dsZVNpZGViYXJFeHBhbmRlZCIsInJlc2l6ZVRyaWdnZXJlZCIsInJlc2l6ZUdyYWJiZWQiLCJyZXNpemVSZWxlYXNlZCJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmKF8oMCwiZW1iZWRkaW5ncy1jb21wb25lbnQiLDApLFAoInRvZ2dsZVNpZGViYXJFeHBhbmRlZCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblRvZ2dsZVNpZGViYXJFeHBhbmRlZCgpfSkoInJlc2l6ZVRyaWdnZXJlZCIsZnVuY3Rpb24obyl7cmV0dXJuIGkub25SZXNpemVUcmlnZ2VyZWQobyl9KSgicmVzaXplR3JhYmJlZCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblJlc2l6ZUdyYWJiZWQoKX0pKCJyZXNpemVSZWxlYXNlZCIsZnVuY3Rpb24oKXtyZXR1cm4gaS5vblJlc2l6ZVJlbGVhc2VkKCl9KSxCKDEsImFzeW5jIiksQigyLCJhc3luYyIpLEIoMywiYXN5bmMiKSx2KCkpLDImZSYmeSgicnVuQWN0aXZlIixVKDEsMyxpLnJ1bkFjdGl2ZSQpKSgic2lkZWJhckV4cGFuZGVkIixVKDIsNSxpLnNpZGViYXJFeHBhbmRlZCQpKSgic2lkZWJhcldpZHRoIixVKDMsNyxpLnNpZGViYXJXaWR0aCQpKX0sZGVwZW5kZW5jaWVzOltyZ2UsR2VdLGVuY2Fwc3VsYXRpb246MixjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpO2Z1bmN0aW9uIEd0dChuLHQpezEmbiYmTygwLCJucG1pLWluYWN0aXZlLXZpZXciKX1mdW5jdGlvbiBXdHQobix0KXsxJm4mJk8oMCwibnBtaS1tYWluIil9ZnVuY3Rpb24gcXR0KG4sdCl7MSZuJiZPKDAsIm5wbWktZW1iZWRkaW5ncyIpfWZ1bmN0aW9uIFl0dChuLHQpe2lmKDEmbiYmKEUoMCxXdHQsMSwwLCJucG1pLW1haW4iLDMpLEUoMSxxdHQsMSwwLCJucG1pLWVtYmVkZGluZ3MiLDMpKSwyJm4pe2xldCBlPVMoKTt5KCJuZ0lmIixlLmFjdGl2ZVZpZXc9PT1lLlZpZXdBY3RpdmUuREVGQVVMVCksQygxKSx5KCJuZ0lmIixlLmFjdGl2ZVZpZXc9PT1lLlZpZXdBY3RpdmUuRU1CRURESU5HUyl9fXZhciBzZ2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3Rvcigpe3RoaXMuVmlld0FjdGl2ZT1rcH19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sibnBtaS1jb21wb25lbnQiXV0saW5wdXRzOntydW5zOiJydW5zIixhY3RpdmVWaWV3OiJhY3RpdmVWaWV3In0sZGVjbHM6NCx2YXJzOjIsY29uc3RzOltbMSwibnBtaS1jb250YWluZXIiXSxbNCwibmdJZiIsIm5nSWZFbHNlIl0sWyJkYXRhQXZhaWxhYmxlIiwiIl0sWzQsIm5nSWYiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXtpZigxJmUmJihfKDAsImRpdiIsMCksRSgxLEd0dCwxLDAsIm5wbWktaW5hY3RpdmUtdmlldyIsMSksRSgyLFl0dCwyLDIsIm5nLXRlbXBsYXRlIixudWxsLDIscXQpLHYoKSksMiZlKXtsZXQgcj0kZSgzKTtDKDEpLHkoIm5nSWYiLDA9PT1pLnJ1bnMuc2l6ZSkoIm5nSWZFbHNlIixyKX19LGRlcGVuZGVuY2llczpbQmUsdW1lLHRnZSxvZ2VdLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17ZGlzcGxheTpmbGV4O2hlaWdodDoxMDAlfS5ucG1pLWNvbnRhaW5lcltfbmdjb250ZW50LSVDT01QJV17ZmxleDoxIDF9Il0sY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxhZ2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlKXt0aGlzLnN0b3JlPWUsdGhpcy5ydW5zJD10aGlzLnN0b3JlLnBpcGUodnQob28pKSx0aGlzLmFjdGl2ZVZpZXckPXRoaXMuc3RvcmUucGlwZSh2dChzbWUpKX1uZ09uSW5pdCgpe3RoaXMuc3RvcmUuZGlzcGF0Y2goYkYoKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKE0oQ2UpKX0sbi5cdTAyNzVjbXA9Uih7dHlwZTpuLHNlbGVjdG9yczpbWyJucG1pIl1dLGRlY2xzOjMsdmFyczo2LGNvbnN0czpbWzMsInJ1bnMiLCJhY3RpdmVWaWV3Il1dLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7MSZlJiYoTygwLCJucG1pLWNvbXBvbmVudCIsMCksQigxLCJhc3luYyIpLEIoMiwiYXN5bmMiKSksMiZlJiZ5KCJydW5zIixVKDEsMixpLnJ1bnMkKSkoImFjdGl2ZVZpZXciLFUoMiw0LGkuYWN0aXZlVmlldyQpKX0sZGVwZW5kZW5jaWVzOltzZ2UsR2VdLGVuY2Fwc3VsYXRpb246Mn0pLG59KSgpLGxnZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsanIsTHMscG4sUG5dfSksbn0pKCksY2dlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxqcixwbixIYV19KSxufSkoKSx1Z2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLGpyLHBuLFBuLE5tZSxjZ2VdfSksbn0pKCksZGdlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxqcixMcyxwbl19KSxufSkoKSxwZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lXX0pLG59KSgpLGhnZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbcGdlXX0pLG59KSgpLFFGPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSx1Z2UsZGdlLGhnZSxaYyxsZ2VdfSksbn0pKCksZmdlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxqcixwbixQbl19KSxufSkoKSxtZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLGpyLHoyLHBuLCRiLFFrXX0pLG59KSgpLGdnZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsJGJdfSksbn0pKCksX2dlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSwkYixtZ2UsZ2dlXX0pLG59KSgpLHZnZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUscG4sSGEsSWJdfSksbn0pKCkseWdlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxwbixQbl19KSxufSkoKSxLRj0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsdmdlLF9nZSx5Z2VdfSksbn0pKCksYmdlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZV19KSxufSkoKSxaRj0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUscG4sUG4sYmdlXX0pLG59KSgpLHhnZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsanIsTHMscG4sRmIsS0YsUG4sUUYsWkYsZmdlXX0pLG59KSgpLENnZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7fSksbn0pKCksTWdlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltNZSxqcixQbixwbl19KSxufSkoKSx3Z2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLGpyLHBuLFBuLE1nZV19KSxufSkoKSxTZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLGpyLExzLHBuLEZiLEtGLFBuLHdnZSxRRixaRl19KSxufSkoKSxFZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W01lLENnZSxTZ2UseGdlLHRtZSx3ci5mb3JGZWF0dXJlKCJucG1pIixkbWUpLHJvLmZvckZlYXR1cmUoW2NtZV0pLEJzLmZvclBsdWdpbigibnBtaSIsYWdlKV19KSxufSkoKSxKRj0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSx2Nj0oKCk9PntjbGFzcyBue2NvbnN0cnVjdG9yKGUpe3RoaXMuaHR0cD1lLHRoaXMuaHR0cFBhdGhQcmVmaXg9ImRhdGEvcGx1Z2luL3RleHRfdjIifWZldGNoUnVuVG9UYWcoKXtyZXR1cm4gdGhpcy5odHRwLmdldCh0aGlzLmh0dHBQYXRoUHJlZml4KyIvdGFncyIpLnBpcGUoTChlPT57bGV0IGk9bmV3IE1hcDtyZXR1cm4gT2JqZWN0LmVudHJpZXMoZSkuZm9yRWFjaCgoW3Isb10pPT57aS5zZXQocixvKX0pLGl9KSl9ZmV0Y2hUZXh0RGF0YShlLGkpe2xldCByPW5ldyBVUkxTZWFyY2hQYXJhbXMoe3J1bjplLHRhZzppfSk7cmV0dXJuIHRoaXMuaHR0cC5nZXQodGhpcy5odHRwUGF0aFByZWZpeCtgL3RleHQ/JHtyLnRvU3RyaW5nKCl9YCkucGlwZShMKG89Pm8ubWFwKHM9Pih7b3JpZ2luYWxTaGFwZTpzLm9yaWdpbmFsX3NoYXBlLHN0ZXA6cy5zdGVwLHN0cmluZ0FycmF5OnMuc3RyaW5nX2FycmF5LHdhbGxUaW1lSW5NczoxZTMqcy53YWxsX3RpbWUsdHJ1bmNhdGVkOnMudHJ1bmNhdGVkfSkpKSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooa2EpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSxUZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbdjYse3Byb3ZpZGU6SkYsdXNlRXhpc3Rpbmc6djZ9XSxpbXBvcnRzOltLdV19KSxufSkoKSxEZ2U9YmUoIltUZXh0XSBUZXh0IFBsdWdpbiBMb2FkZWQiKSxBZ2U9YmUoIltUZXh0XSBSdW5zIFRvIFRhZyBMb2FkZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSxJZ2U9YmUoIltUZXh0XSBUYWcgR3JvdXAgVmlzaWJpbGl0eSBDaGFuZ2VkIix7X2FzOiJwcm9wcyIsX3A6dm9pZCAwfSksUGdlPWJlKCJbVGV4dF0gVGV4dCBEYXRhIExvYWRlZCBMb2FkZWQiLHtfYXM6InByb3BzIixfcDp2b2lkIDB9KSx5Nj1NcigidGV4dCIpLFJnZT0oSih5NixuPT5uLnJ1blRvVGFncyksSih5NixuPT57bGV0IHQ9bmV3IFNldCxlPW5ldyBTZXQ7Zm9yKGxldCBpIG9mIG4udmlzaWJsZVJ1blRhZ3MudmFsdWVzKCkpZm9yKGxldCByIG9mIGkpe2xldCBvPUpTT04uc3RyaW5naWZ5KHIpO3QuaGFzKG8pfHwodC5hZGQobyksZS5hZGQocikpfXJldHVyblsuLi5lXX0pKSxPZ2U9Sih5Niwobix0KT0+e2xldCBlPW4uZGF0YS5nZXQodC5ydW4pO3JldHVybiBlJiZlLmdldCh0LnRhZyl8fG51bGx9KSxrZ2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5hY3Rpb25zJD1lLHRoaXMuc3RvcmU9aSx0aGlzLmRhdGFTb3VyY2U9cix0aGlzLmxvYWRSdW5Ub1RhZ3MkPWNyKCgpPT50aGlzLmFjdGlvbnMkLnBpcGUoaWkoRGdlKSx1aSgoKT0+dGhpcy5kYXRhU291cmNlLmZldGNoUnVuVG9UYWcoKS5waXBlKGt0KG89Pnt0aGlzLnN0b3JlLmRpc3BhdGNoKEFnZSh7cnVuVG9UYWdzOm99KSl9KSxMKCgpPT57fSkpKSkse2Rpc3BhdGNoOiExfSksdGhpcy5sb2FkRGF0YSQ9Y3IoKCk9PntsZXQgbz10aGlzLmFjdGlvbnMkLnBpcGUoaWkoSWdlKSx1aSgoe3Zpc2libGVUZXh0Q2FyZHM6YX0pPT57bGV0IGw9YS5tYXAoKHtydW46Yyx0YWc6dX0pPT50aGlzLnN0b3JlLnNlbGVjdChPZ2Use3J1bjpjLHRhZzp1fSkucGlwZShmdW5jdGlvbihuLHQpe2xldCBlPWFyZ3VtZW50cy5sZW5ndGg+PTI7cmV0dXJuIGk9PmkucGlwZShuP1llKChyLG8pPT5uKHIsbyxpKSk6bXMsUFcoMSksZT9fMSh0KTp2MSgoKT0+bmV3IF8wKSl9KCksTChkPT4oe3J1bjpjLHRhZzp1LHRleHREYXRhOmR9KSkpKTtyZXR1cm4gbHIobCkucGlwZShMKGM9PmMuZmlsdGVyKCh7dGV4dERhdGE6dX0pPT5udWxsPT09dSkubWFwKCh7cnVuOnUsdGFnOmR9KT0+KHtydW46dSx0YWc6ZH0pKSkpfSkpO3JldHVybiBKdChvLHRoaXMuYWN0aW9ucyQucGlwZShpaShGYSxhYSksV3QodGhpcy5zdG9yZS5zZWxlY3QoUmdlKSksTCgoWyxhXSk9PmEpKSkucGlwZSh4bihhPT5scihhLm1hcChsPT50aGlzLmZldGNoVGV4dERhdGEobCkpKSkpfSx7ZGlzcGF0Y2g6ITF9KX1mZXRjaFRleHREYXRhKGUpe2xldHtydW46aSx0YWc6cn09ZTtyZXR1cm4gdGhpcy5kYXRhU291cmNlLmZldGNoVGV4dERhdGEoaSxyKS5waXBlKGt0KG89Pnt0aGlzLnN0b3JlLmRpc3BhdGNoKFBnZSh7cnVuOmksdGFnOnIsc3RlcERhdGE6b30pKX0pLEwoKCk9Pnt9KSl9fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pKGooUG8pLGooQ2UpLGooSkYpKX0sbi5cdTAyNzVwcm92PXllKHt0b2tlbjpuLGZhY3Rvcnk6bi5cdTAyNzVmYWN9KSxufSkoKSwkdHQ9dnIoe3J1blRvVGFnczpuZXcgTWFwKFtbInJ1bjEiLFsiYS9iIiwiYS9jIl1dLFsicnVuMiIsWyJhL2IiLCJhL2QiXV0sWyJydW4zIixbImMiLCJhL2IiXV1dKSxkYXRhOm5ldyBNYXAoW1sicnVuMSIsbmV3IE1hcChbWyJhL2IiLFt7b3JpZ2luYWxTaGFwZTpbM10sc3RlcDowLHN0cmluZ0FycmF5OltbImZvbyIsImJhciIsImJheiJdXSx3YWxsVGltZUluTXM6MTU3Nzg2NTZlNSx0cnVuY2F0ZWQ6ITF9LHtvcmlnaW5hbFNoYXBlOlszXSxzdGVwOjEsc3RyaW5nQXJyYXk6W1siZm9vIiwiYmF6Il1dLHdhbGxUaW1lSW5NczoxNTc3ODY1NjAxZTMsdHJ1bmNhdGVkOiExfV1dLFsiYS9jIixbe29yaWdpbmFsU2hhcGU6WzNdLHN0ZXA6MCxzdHJpbmdBcnJheTpbWyJXZSBjb25kdWN0ZWQgYW4gZXhwZXJpbWVudCBhbmQgZm91bmQgdGhlIGZvbGxvd2luZyBkYXRhOlxuXG5Qb3VuZHMgb2YgY2hvY29sYXRlIHwgSGFwcGluZXNzXG4tLS18LS0tXG4wIHwgMVxuMSB8IDRcbjIgfCA5XG4zIHwgMTZcbjQgfCAyNVxuNSB8IDM2XG42IHwgNDlcbjcgfCA2NFxuOCB8IDgxXG45IHwgMTAwXG4xMCB8IDEyMSJdXSx3YWxsVGltZUluTXM6MTU3Nzg2NTZlNSx0cnVuY2F0ZWQ6ITF9LHtvcmlnaW5hbFNoYXBlOlszXSxzdGVwOjEsc3RyaW5nQXJyYXk6W1siXHhkNyIsIioqMCoqIiwiKioxKioiLCIqKjIqKiIsIioqMyoqIiwiKio0KioiLCIqKjUqKiJdLFsiKiowKioiLCIwIiwiMCIsIjAiLCIwIiwiMCIsIjAiXSxbIioqMSoqIiwiMCIsIjEiLCIyIiwiMyIsIjQiLCI1Il0sWyIqKjIqKiIsIjAiLCIyIiwiNCIsIjYiLCI4IiwiMTAiXSxbIioqMyoqIiwiMCIsIjMiLCI2IiwiOSIsIjEyIiwiMTUiXSxbIioqNCoqIiwiMCIsIjQiLCI4IiwiMTIiLCIxNiIsIjIwIl0sWyIqKjUqKiIsIjAiLCI1IiwiMTAiLCIxNSIsIjIwIiwiMjUiXV0sd2FsbFRpbWVJbk1zOjE1Nzc4NjU2MDFlMyx0cnVuY2F0ZWQ6ITF9XV1dKV1dKSx2aXNpYmxlUnVuVGFnczpuZXcgTWFwfSk7ZnVuY3Rpb24gRmdlKG4sdCl7cmV0dXJuICR0dChuLHQpfXZhciBOZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGV4dC1kYXNoYm9hcmQiXV0sZGVjbHM6MSx2YXJzOjAsdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJkEoMCwiIFRoaXMgaXMgdGhlIHRleHQgZGFzaGJvYXJkICIpfSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSxMZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLEJnZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsTGdlLEJzLmZvclBsdWdpbigidGV4dF92MiIsTmdlKSxUZ2Usd3IuZm9yRmVhdHVyZSgidGV4dCIsRmdlKSxyby5mb3JGZWF0dXJlKFtrZ2VdKV19KSxufSkoKSxWZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe2ltcG9ydHM6W19vZSxOaGUsRWdlLEJnZV19KSxufSkoKSxGcD0oKCk9PihmdW5jdGlvbihuKXtuLkNVU1RPTV9FTEVNRU5UPSJDVVNUT01fRUxFTUVOVCIsbi5JRlJBTUU9IklGUkFNRSIsbi5OR19DT01QT05FTlQ9Ik5HX0NPTVBPTkVOVCIsbi5OT05FPSJOT05FIn0oRnB8fChGcD17fSkpLEZwKSkoKSxlbnQ9WyJwbHVnaW5Db250YWluZXIiXSx0bnQ9WyJuZ1BsdWdpbkNvbnRhaW5lciJdO2Z1bmN0aW9uIG5udChuLHQpezEmbiYmTmkoMCl9ZnVuY3Rpb24gaW50KG4sdCl7aWYoMSZuJiYoc24oMCksRSgxLG5udCwxLDAsIm5nLWNvbnRhaW5lciIsOSksYW4oKSksMiZuKXtsZXQgZT1TKDIpLGk9JGUoNik7QygxKSx5KCJuZ1RlbXBsYXRlT3V0bGV0IixlLmVudmlyb25tZW50RmFpbHVyZU5vdEZvdW5kVGVtcGxhdGU/ZS5lbnZpcm9ubWVudEZhaWx1cmVOb3RGb3VuZFRlbXBsYXRlOmkpfX1mdW5jdGlvbiBybnQobix0KXsxJm4mJk5pKDApfWZ1bmN0aW9uIG9udChuLHQpe2lmKDEmbiYmKHNuKDApLEUoMSxybnQsMSwwLCJuZy1jb250YWluZXIiLDkpLGFuKCkpLDImbil7bGV0IGU9UygyKSxpPSRlKDYpO0MoMSkseSgibmdUZW1wbGF0ZU91dGxldCIsZS5lbnZpcm9ubWVudEZhaWx1cmVQZXJtaXNzaW9uRGVuaWVkVGVtcGxhdGU/ZS5lbnZpcm9ubWVudEZhaWx1cmVQZXJtaXNzaW9uRGVuaWVkVGVtcGxhdGU6aSl9fWZ1bmN0aW9uIHNudChuLHQpezEmbiYmTmkoMCl9ZnVuY3Rpb24gYW50KG4sdCl7aWYoMSZuJiYoc24oMCksRSgxLHNudCwxLDAsIm5nLWNvbnRhaW5lciIsOSksYW4oKSksMiZuKXtsZXQgZT1TKDIpLGk9JGUoNik7QygxKSx5KCJuZ1RlbXBsYXRlT3V0bGV0IixlLmVudmlyb25tZW50RmFpbHVyZVVua25vd25UZW1wbGF0ZT9lLmVudmlyb25tZW50RmFpbHVyZVVua25vd25UZW1wbGF0ZTppKX19ZnVuY3Rpb24gbG50KG4sdCl7aWYoMSZuJiYoc24oMCksXygxLCJoMyIsMTApLEEoMiwiIFRoZXJlXHUyMDE5cyBubyBkYXNoYm9hcmQgYnkgdGhlIG5hbWUgb2YgXHUyMDFjIiksXygzLCJjb2RlIiksQSg0KSx2KCksQSg1LCJcdTIwMWQuICIpLHYoKSxfKDYsInAiKSxBKDcsIllvdSBjYW4gc2VsZWN0IGEgZGFzaGJvYXJkIGZyb20gdGhlIGxpc3QgYWJvdmUuIiksdigpLF8oOCwicCIpLE5pKDksMTEpLHYoKSxhbigpKSwyJm4pe2xldCBlPVMoMiksaT0kZSg4KTtDKDQpLHl0KGUuYWN0aXZlUGx1Z2luSWQpLEMoNSkseSgibmdUZW1wbGF0ZU91dGxldCIsaSl9fWZ1bmN0aW9uIGNudChuLHQpe2lmKDEmbiYmKHNuKDApLF8oMSwiaDMiLDEyKSxBKDIsIiBObyBkYXNoYm9hcmRzIGFyZSBhY3RpdmUgZm9yIHRoZSBjdXJyZW50IGRhdGEgc2V0LiAiKSx2KCksXygzLCJwIiksQSg0LCJQcm9iYWJsZSBjYXVzZXM6IiksdigpLF8oNSwidWwiKSg2LCJsaSIpLEEoNywiWW91IGhhdmVuXHUyMDE5dCB3cml0dGVuIGFueSBkYXRhIHRvIHlvdXIgZXZlbnQgZmlsZXMuIiksdigpLF8oOCwibGkiKSxBKDksIlRlbnNvckJvYXJkIGNhblx1MjAxOXQgZmluZCB5b3VyIGV2ZW50IGZpbGVzLiIpLHYoKSgpLEEoMTAsIiBJZiB5b3VcdTIwMTlyZSBuZXcgdG8gdXNpbmcgVGVuc29yQm9hcmQsIGFuZCB3YW50IHRvIGZpbmQgb3V0IGhvdyB0byBhZGQgZGF0YSBhbmQgc2V0IHVwIHlvdXIgZXZlbnQgZmlsZXMsIGNoZWNrIG91dCB0aGUgIiksXygxMSwiYSIsMTMpLEEoMTIsIlJFQURNRSIpLHYoKSxBKDEzLCIgYW5kIHBlcmhhcHMgdGhlICIpLF8oMTQsImEiLDE0KSxBKDE1LCJUZW5zb3JCb2FyZCB0dXRvcmlhbCIpLHYoKSxBKDE2LCIuICIpLF8oMTcsInAiKSxBKDE4LCIgSWYgeW91IHRoaW5rIFRlbnNvckJvYXJkIGlzIGNvbmZpZ3VyZWQgcHJvcGVybHksIHBsZWFzZSBzZWUgIiksXygxOSwiYSIsMTUpLEEoMjAsInRoZSBzZWN0aW9uIG9mIHRoZSBSRUFETUUgZGV2b3RlZCB0byBtaXNzaW5nIGRhdGEgcHJvYmxlbXMiKSx2KCksQSgyMSwiIGFuZCBjb25zaWRlciBmaWxpbmcgYW4gaXNzdWUgb24gR2l0SHViLiAiKSx2KCksXygyMiwicCIpLE5pKDIzLDExKSx2KCksYW4oKSksMiZuKXtTKDIpO2xldCBlPSRlKDgpO0MoMjMpLHkoIm5nVGVtcGxhdGVPdXRsZXQiLGUpfX1mdW5jdGlvbiB1bnQobix0KXtpZigxJm4mJihfKDAsImRpdiIsNikoMSwiZGl2Iiw3KSxFKDIsaW50LDIsMSwibmctY29udGFpbmVyIiw4KSxFKDMsb250LDIsMSwibmctY29udGFpbmVyIiw4KSxFKDQsYW50LDIsMSwibmctY29udGFpbmVyIiw4KSxFKDUsbG50LDEwLDIsIm5nLWNvbnRhaW5lciIsOCksRSg2LGNudCwyNCwxLCJuZy1jb250YWluZXIiLDgpLHYoKSgpKSwyJm4pe2xldCBlPVMoKTt5KCJuZ1N3aXRjaCIsZS5wbHVnaW5Mb2FkU3RhdGUpLEMoMikseSgibmdTd2l0Y2hDYXNlIixlLlBsdWdpbkxvYWRTdGF0ZS5FTlZJUk9OTUVOVF9GQUlMVVJFX05PVF9GT1VORCksQygxKSx5KCJuZ1N3aXRjaENhc2UiLGUuUGx1Z2luTG9hZFN0YXRlLkVOVklST05NRU5UX0ZBSUxVUkVfUEVSTUlTU0lPTl9ERU5JRUQpLEMoMSkseSgibmdTd2l0Y2hDYXNlIixlLlBsdWdpbkxvYWRTdGF0ZS5FTlZJUk9OTUVOVF9GQUlMVVJFX1VOS05PV04pLEMoMSkseSgibmdTd2l0Y2hDYXNlIixlLlBsdWdpbkxvYWRTdGF0ZS5VTktOT1dOX1BMVUdJTl9JRCksQygxKSx5KCJuZ1N3aXRjaENhc2UiLGUuUGx1Z2luTG9hZFN0YXRlLk5PX0VOQUJMRURfUExVR0lOUyl9fWZ1bmN0aW9uIGRudChuLHQpe2lmKDEmbiYmKF8oMCwiaDMiLDE2KSxBKDEsIkRhdGEgY291bGQgbm90IGJlIGxvYWRlZC4iKSx2KCksXygyLCJwIiksQSgzLCJUaGUgVGVuc29yQm9hcmQgc2VydmVyIG1heSBiZSBkb3duIG9yIGluYWNjZXNzaWJsZS4iKSx2KCksXyg0LCJwIiksTmkoNSwxMSksdigpKSwyJm4pe1MoKTtsZXQgZT0kZSg4KTtDKDUpLHkoIm5nVGVtcGxhdGVPdXRsZXQiLGUpfX1mdW5jdGlvbiBwbnQobix0KXtpZigxJm4mJihfKDAsInAiLDE5KSgxLCJpIiksQSgyLCJMb2cgZGlyZWN0b3J5OiAiKSxfKDMsInNwYW4iKSxBKDQpLHYoKSgpKCkpLDImbil7bGV0IGU9UygyKTtDKDQpLHl0KGUuZGF0YUxvY2F0aW9uKX19ZnVuY3Rpb24gaG50KG4sdCl7aWYoMSZuJiYoXygwLCJzcGFuIiwxNyksQSgxKSxCKDIsImRhdGUiKSx2KCksRSgzLHBudCw1LDEsInAiLDE4KSksMiZuKXtsZXQgZT1TKCk7QygxKSxqZSgiTGFzdCByZWxvYWQ6ICIsSmYoMiwyLGUubGFzdFVwZGF0ZWQsIm1lZGl1bSIpLCIiKSxDKDIpLHkoIm5nSWYiLGUuZGF0YUxvY2F0aW9uKX19dmFyIGZudD1mdW5jdGlvbihuKXtyZXR1cm57cGx1Z2luczohMCwiaXMtZmlyc3QtcGFydHktcGx1Z2luIjpufX0seWM9KCgpPT4oZnVuY3Rpb24obil7bltuLkVOVklST05NRU5UX0ZBSUxVUkVfTk9UX0ZPVU5EPTBdPSJFTlZJUk9OTUVOVF9GQUlMVVJFX05PVF9GT1VORCIsbltuLkVOVklST05NRU5UX0ZBSUxVUkVfUEVSTUlTU0lPTl9ERU5JRUQ9MV09IkVOVklST05NRU5UX0ZBSUxVUkVfUEVSTUlTU0lPTl9ERU5JRUQiLG5bbi5FTlZJUk9OTUVOVF9GQUlMVVJFX1VOS05PV049Ml09IkVOVklST05NRU5UX0ZBSUxVUkVfVU5LTk9XTiIsbltuLk5PX0VOQUJMRURfUExVR0lOUz0zXT0iTk9fRU5BQkxFRF9QTFVHSU5TIixuW24uVU5LTk9XTl9QTFVHSU5fSUQ9NF09IlVOS05PV05fUExVR0lOX0lEIixuW24uTE9BREVEPTVdPSJMT0FERUQiLG5bbi5MT0FESU5HPTZdPSJMT0FESU5HIn0oeWN8fCh5Yz17fSkpLHljKSkoKSxIZ2U9KCgpPT57Y2xhc3Mgbntjb25zdHJ1Y3RvcihlLGkscil7dGhpcy5jb21wb25lbnRGYWN0b3J5UmVzb2x2ZXI9ZSx0aGlzLnBsdWdpblJlZ2lzdHJ5PWksdGhpcy5wbHVnaW5BcGlIb3N0PXIsdGhpcy5QbHVnaW5Mb2FkU3RhdGU9eWMsdGhpcy5Mb2FkaW5nTWVjaGFuaXNtVHlwZT1GcCx0aGlzLnBsdWdpbkluc3RhbmNlcz1uZXcgTWFwfW5nT25DaGFuZ2VzKGUpe2lmKCF0aGlzLmlzRmVhdHVyZUZsYWdzTG9hZGVkfHwhdGhpcy5hY3RpdmVLbm93blBsdWdpbnx8dGhpcy5zZXR0aW5nc0xvYWRTdGF0ZT09PU9lLk5PVF9MT0FERUR8fHRoaXMuc2V0dGluZ3NMb2FkU3RhdGU9PT1PZS5MT0FESU5HKXJldHVybjtsZXQgaT1Cb29sZWFuKHRoaXMuYWN0aXZlS25vd25QbHVnaW4mJiF0aGlzLnBsdWdpbkluc3RhbmNlcy5oYXModGhpcy5hY3RpdmVLbm93blBsdWdpbi5pZCkpO2lmKGUuYWN0aXZlS25vd25QbHVnaW58fGUuaXNGZWF0dXJlRmxhZ3NMb2FkZWR8fGUuc2V0dGluZ3NMb2FkU3RhdGUpe2xldCByPWUuYWN0aXZlS25vd25QbHVnaW4/LnByZXZpb3VzVmFsdWU7aWYociYmci5pZCE9PXRoaXMuYWN0aXZlS25vd25QbHVnaW4uaWQmJnRoaXMuaGlkZVBsdWdpbihyKSxpKXtsZXQgbz10aGlzLmNyZWF0ZVBsdWdpbih0aGlzLmFjdGl2ZUtub3duUGx1Z2luKTtvJiZ0aGlzLnBsdWdpbkluc3RhbmNlcy5zZXQodGhpcy5hY3RpdmVLbm93blBsdWdpbi5pZCxvKX1lbHNlIHRoaXMuc2hvd1BsdWdpbih0aGlzLmFjdGl2ZUtub3duUGx1Z2luKX0oaXx8ZS5sYXN0VXBkYXRlZCkmJnRoaXMucmVsb2FkKHRoaXMuYWN0aXZlS25vd25QbHVnaW4saSl9aGlkZVBsdWdpbihlKXtpZighdGhpcy5wbHVnaW5JbnN0YW5jZXMuaGFzKGUuaWQpKXJldHVybjtsZXQgaT10aGlzLnBsdWdpbkluc3RhbmNlcy5nZXQoZS5pZCk7T2JqZWN0LmFzc2lnbihpLnN0eWxlLHttYXhIZWlnaHQ6MCxvdmVyZmxvdzoiaGlkZGVuIix2aXNpYmlsaXR5OiJoaWRkZW4iLHBvc2l0aW9uOiJhYnNvbHV0ZSJ9KX1zaG93UGx1Z2luKGUpe2lmKCF0aGlzLnBsdWdpbkluc3RhbmNlcy5oYXMoZS5pZCkpcmV0dXJuO2xldCBpPXRoaXMucGx1Z2luSW5zdGFuY2VzLmdldChlLmlkKTtPYmplY3QuYXNzaWduKGkuc3R5bGUse21heEhlaWdodDpudWxsLG92ZXJmbG93Om51bGwsdmlzaWJpbGl0eTpudWxsLHBvc2l0aW9uOm51bGx9KX1jcmVhdGVQbHVnaW4oZSl7bGV0IGk9bnVsbDtzd2l0Y2goZS5sb2FkaW5nX21lY2hhbmlzbS50eXBlKXtjYXNlIEZwLkNVU1RPTV9FTEVNRU5UOmk9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudChlLmxvYWRpbmdfbWVjaGFuaXNtLmVsZW1lbnRfbmFtZSksaS5yZWxvYWRPblJlYWR5PSExLGkuZmVhdHVyZUZsYWdzPXRoaXMuZmVhdHVyZUZsYWdzLHRoaXMucGx1Z2luc0NvbnRhaW5lci5uYXRpdmVFbGVtZW50LmFwcGVuZENoaWxkKGkpO2JyZWFrO2Nhc2UgRnAuSUZSQU1FOmlmKCF0aGlzLnBsdWdpbkFwaUhvc3QpdGhyb3cgRXJyb3IoYElGUkFNRS1iYXNlZCBwbHVnaW5zIG5vdCBzdXBwb3J0ZWQ6ICR7ZS5pZH1gKTtpPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImlmcmFtZSIpLGkuc2V0QXR0cmlidXRlKCJzcmMiLGBkYXRhL3BsdWdpbl9lbnRyeS5odG1sP25hbWU9JHtlLmlkfWApLHRoaXMucGx1Z2luQXBpSG9zdC5yZWdpc3RlclBsdWdpbklmcmFtZShpLGUuaWQpLHRoaXMucGx1Z2luc0NvbnRhaW5lci5uYXRpdmVFbGVtZW50LmFwcGVuZENoaWxkKGkpO2JyZWFrO2Nhc2UgRnAuTkdfQ09NUE9ORU5UOmxldCByPXRoaXMucGx1Z2luUmVnaXN0cnkuZ2V0Q29tcG9uZW50KGUuaWQpO2lmKHIpe2xldCBvPXRoaXMuY29tcG9uZW50RmFjdG9yeVJlc29sdmVyLnJlc29sdmVDb21wb25lbnRGYWN0b3J5KHIpO2k9dGhpcy5uZ1BsdWdpbkNvbnRhaW5lci5jcmVhdGVDb21wb25lbnQobykubG9jYXRpb24ubmF0aXZlRWxlbWVudH1lbHNlIGNvbnNvbGUuZXJyb3IoYE5vIHJlZ2lzdGVyZWQgQW5ndWxhciBjb21wb25lbnQgZm9yIHBsdWdpbjogJHtlLmlkfWApO2JyZWFrO2Nhc2UgRnAuTk9ORTpicmVhaztkZWZhdWx0OmNvbnNvbGUuZXJyb3IoIlVuZXhwZWN0ZWQgcGx1Z2luIil9cmV0dXJuIGl9cmVsb2FkKGUsaSl7aWYoIWkmJmUuZGlzYWJsZV9yZWxvYWQpcmV0dXJuO2xldCByPXRoaXMucGx1Z2luSW5zdGFuY2VzLmdldChlLmlkKTtyJiZyLnJlbG9hZCYmci5yZWxvYWQoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShncyksTShCcyksTShyMiw4KSl9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sicGx1Z2lucy1jb21wb25lbnQiXV0sdmlld1F1ZXJ5OmZ1bmN0aW9uKGUsaSl7aWYoMSZlJiYob3QoZW50LDcsUmUpLG90KHRudCw3LE9pKSksMiZlKXtsZXQgcjtOZShyPUxlKCkpJiYoaS5wbHVnaW5zQ29udGFpbmVyPXIuZmlyc3QpLE5lKHI9TGUoKSkmJihpLm5nUGx1Z2luQ29udGFpbmVyPXIuZmlyc3QpfX0saW5wdXRzOnthY3RpdmVQbHVnaW5JZDoiYWN0aXZlUGx1Z2luSWQiLGFjdGl2ZUtub3duUGx1Z2luOiJhY3RpdmVLbm93blBsdWdpbiIscGx1Z2luTG9hZFN0YXRlOiJwbHVnaW5Mb2FkU3RhdGUiLGRhdGFMb2NhdGlvbjoiZGF0YUxvY2F0aW9uIixpc0ZlYXR1cmVGbGFnc0xvYWRlZDoiaXNGZWF0dXJlRmxhZ3NMb2FkZWQiLHNldHRpbmdzTG9hZFN0YXRlOiJzZXR0aW5nc0xvYWRTdGF0ZSIsZmVhdHVyZUZsYWdzOiJmZWF0dXJlRmxhZ3MiLGxhc3RVcGRhdGVkOiJsYXN0VXBkYXRlZCIsZW52aXJvbm1lbnRGYWlsdXJlTm90Rm91bmRUZW1wbGF0ZToiZW52aXJvbm1lbnRGYWlsdXJlTm90Rm91bmRUZW1wbGF0ZSIsZW52aXJvbm1lbnRGYWlsdXJlUGVybWlzc2lvbkRlbmllZFRlbXBsYXRlOiJlbnZpcm9ubWVudEZhaWx1cmVQZXJtaXNzaW9uRGVuaWVkVGVtcGxhdGUiLGVudmlyb25tZW50RmFpbHVyZVVua25vd25UZW1wbGF0ZToiZW52aXJvbm1lbnRGYWlsdXJlVW5rbm93blRlbXBsYXRlIn0sZmVhdHVyZXM6W0Z0XSxkZWNsczo5LHZhcnM6NCxjb25zdHM6W1szLCJuZ0NsYXNzIl0sWyJwbHVnaW5Db250YWluZXIiLCIiXSxbIm5nUGx1Z2luQ29udGFpbmVyIiwiIl0sWyJjbGFzcyIsIndhcm5pbmciLDMsIm5nU3dpdGNoIiw0LCJuZ0lmIl0sWyJlbnZpcm9ubWVudEZhaWx1cmVEZWZhdWx0VGVtcGxhdGUiLCIiXSxbImRhdGVBbmREYXRhTG9jYXRpb24iLCIiXSxbMSwid2FybmluZyIsMywibmdTd2l0Y2giXSxbMSwid2FybmluZy1tZXNzYWdlIl0sWzQsIm5nU3dpdGNoQ2FzZSJdLFs0LCJuZ1RlbXBsYXRlT3V0bGV0Il0sWzEsInVua25vd24tcGx1Z2luIl0sWzMsIm5nVGVtcGxhdGVPdXRsZXQiXSxbMSwibm8tYWN0aXZlLXBsdWdpbiJdLFsiaHJlZiIsImh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmJvYXJkL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCJdLFsiaHJlZiIsImh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2dldF9zdGFydGVkL3N1bW1hcmllc19hbmRfdGVuc29yYm9hcmQiXSxbImhyZWYiLCJodHRwczovL2dpdGh1Yi5jb20vdGVuc29yZmxvdy90ZW5zb3Jib2FyZC9ibG9iL21hc3Rlci9SRUFETUUubWQjbXktdGVuc29yYm9hcmQtaXNudC1zaG93aW5nLWFueS1kYXRhLXdoYXRzLXdyb25nIl0sWzEsImVudmlyb25tZW50LW5vdC1sb2FkZWQiXSxbMSwibGFzdC1yZWxvYWQtdGltZSJdLFsiY2xhc3MiLCJkYXRhLWxvY2F0aW9uIiw0LCJuZ0lmIl0sWzEsImRhdGEtbG9jYXRpb24iXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihfKDAsImRpdiIsMCwxKSxOaSgyLG51bGwsMiksdigpLEUoNCx1bnQsNyw2LCJkaXYiLDMpLEUoNSxkbnQsNiwxLCJuZy10ZW1wbGF0ZSIsbnVsbCw0LHF0KSxFKDcsaG50LDQsNSwibmctdGVtcGxhdGUiLG51bGwsNSxxdCkpLDImZSYmKHkoIm5nQ2xhc3MiLE9uKDIsZm50LChudWxsPT1pLmFjdGl2ZUtub3duUGx1Z2luP251bGw6aS5hY3RpdmVLbm93blBsdWdpbi5sb2FkaW5nX21lY2hhbmlzbS50eXBlKSE9PWkuTG9hZGluZ01lY2hhbmlzbVR5cGUuSUZSQU1FKSksQyg0KSx5KCJuZ0lmIixpLnBsdWdpbkxvYWRTdGF0ZSE9PWkuUGx1Z2luTG9hZFN0YXRlLkxPQURFRCYmaS5wbHVnaW5Mb2FkU3RhdGUhPT1pLlBsdWdpbkxvYWRTdGF0ZS5MT0FESU5HKSl9LGRlcGVuZGVuY2llczpbRm4sQmUsb3MsQ3IsVXIsVV9dLHN0eWxlczpbIltfbmdob3N0LSVDT01QJV17YmFja2dyb3VuZC1jb2xvcjojZmZmO2NvbG9yOiMyMTIxMjE7ZGlzcGxheTpibG9jaztwb3NpdGlvbjpyZWxhdGl2ZX1ib2R5LmRhcmstbW9kZSAgIFtfbmdob3N0LSVDT01QJV0gICAucGx1Z2lucy5pcy1maXJzdC1wYXJ0eS1wbHVnaW5bX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQtY29sb3I6IzMwMzAzMDtjb2xvcjojZmZmfS5wbHVnaW5zW19uZ2NvbnRlbnQtJUNPTVAlXXtoZWlnaHQ6MTAwJTtwb3NpdGlvbjpyZWxhdGl2ZX0ud2FybmluZ1tfbmdjb250ZW50LSVDT01QJV17YmFja2dyb3VuZDojZmZmO2JvdHRvbTowO2xlZnQ6MDtwb3NpdGlvbjphYnNvbHV0ZTtyaWdodDowO3RvcDowfWJvZHkuZGFyay1tb2RlW19uZ2hvc3QtJUNPTVAlXSAgIC53YXJuaW5nW19uZ2NvbnRlbnQtJUNPTVAlXSwgYm9keS5kYXJrLW1vZGUgICBbX25naG9zdC0lQ09NUCVdICAgLndhcm5pbmdbX25nY29udGVudC0lQ09NUCVde2JhY2tncm91bmQ6IzMwMzAzMH0ud2FybmluZy1tZXNzYWdlW19uZ2NvbnRlbnQtJUNPTVAlXXtjb2xvcjojMjEyMTIxO21hcmdpbjo4MHB4IGF1dG8gMDttYXgtd2lkdGg6NTQwcHh9Ym9keS5kYXJrLW1vZGVbX25naG9zdC0lQ09NUCVdICAgLndhcm5pbmctbWVzc2FnZVtfbmdjb250ZW50LSVDT01QJV0sIGJvZHkuZGFyay1tb2RlICAgW19uZ2hvc3QtJUNPTVAlXSAgIC53YXJuaW5nLW1lc3NhZ2VbX25nY29udGVudC0lQ09NUCVde2NvbG9yOiNmZmZ9Lmxhc3QtcmVsb2FkLXRpbWVbX25nY29udGVudC0lQ09NUCVde2ZvbnQtc3R5bGU6aXRhbGljfS5wbHVnaW5zW19uZ2NvbnRlbnQtJUNPTVAlXSAgICAgaWZyYW1le2JvcmRlcjowO2Rpc3BsYXk6YmxvY2s7aGVpZ2h0OjEwMCU7d2lkdGg6MTAwJX0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLG1udD1KKHJ2LFJzLChuLHQpPT50JiZuW3RdP09iamVjdC5hc3NpZ24oe2lkOnR9LG5bdF0pOm51bGwpLGVOPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSl7dGhpcy5zdG9yZT1lLHRoaXMuYWN0aXZlS25vd25QbHVnaW4kPXRoaXMuc3RvcmUuc2VsZWN0KG1udCksdGhpcy5hY3RpdmVQbHVnaW5JZCQ9dGhpcy5zdG9yZS5zZWxlY3QoUnMpLHRoaXMucGx1Z2luTG9hZFN0YXRlJD1MdCh0aGlzLmFjdGl2ZUtub3duUGx1Z2luJCx0aGlzLmFjdGl2ZVBsdWdpbklkJCx0aGlzLnN0b3JlLnNlbGVjdChuSSkpLnBpcGUoTCgoW2kscixvXSk9Pm51bGwhPT1vLmZhaWx1cmVDb2RlP28uZmFpbHVyZUNvZGU9PT0kbC5OT1RfRk9VTkQ/eWMuRU5WSVJPTk1FTlRfRkFJTFVSRV9OT1RfRk9VTkQ6by5mYWlsdXJlQ29kZT09PSRsLlBFUk1JU1NJT05fREVOSUVEP3ljLkVOVklST05NRU5UX0ZBSUxVUkVfUEVSTUlTU0lPTl9ERU5JRUQ6eWMuRU5WSVJPTk1FTlRfRkFJTFVSRV9VTktOT1dOOm51bGwhPT1pP3ljLkxPQURFRDpudWxsPT09by5sYXN0TG9hZGVkVGltZUluTXMmJm8uc3RhdGU9PT1PZS5MT0FESU5HP3ljLkxPQURJTkc6cj95Yy5VTktOT1dOX1BMVUdJTl9JRDp5Yy5OT19FTkFCTEVEX1BMVUdJTlMpKSx0aGlzLmxhc3RMb2FkZWRUaW1lSW5NcyQ9dGhpcy5zdG9yZS5zZWxlY3QoaXYpLHRoaXMuZGF0YUxvY2F0aW9uJD10aGlzLnN0b3JlLnNlbGVjdChvdikucGlwZShMKGk9PmkuZGF0YV9sb2NhdGlvbikpLHRoaXMuaXNGZWF0dXJlRmxhZ3NMb2FkZWQkPXRoaXMuc3RvcmUuc2VsZWN0KGdoKSx0aGlzLmZlYXR1cmVGbGFncyQ9dGhpcy5zdG9yZS5zZWxlY3QoYnMpLHRoaXMuc2V0dGluZ3NMb2FkU3RhdGUkPXRoaXMuc3RvcmUuc2VsZWN0KE5hLmdldFNldHRpbmdzTG9hZFN0YXRlKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInBsdWdpbnMiXV0saW5wdXRzOntlbnZpcm9ubWVudEZhaWx1cmVOb3RGb3VuZFRlbXBsYXRlOiJlbnZpcm9ubWVudEZhaWx1cmVOb3RGb3VuZFRlbXBsYXRlIixlbnZpcm9ubWVudEZhaWx1cmVQZXJtaXNzaW9uRGVuaWVkVGVtcGxhdGU6ImVudmlyb25tZW50RmFpbHVyZVBlcm1pc3Npb25EZW5pZWRUZW1wbGF0ZSIsZW52aXJvbm1lbnRGYWlsdXJlVW5rbm93blRlbXBsYXRlOiJlbnZpcm9ubWVudEZhaWx1cmVVbmtub3duVGVtcGxhdGUifSxkZWNsczo5LHZhcnM6MjcsY29uc3RzOltbMywiYWN0aXZlS25vd25QbHVnaW4iLCJhY3RpdmVQbHVnaW5JZCIsImRhdGFMb2NhdGlvbiIsImxhc3RVcGRhdGVkIiwicGx1Z2luTG9hZFN0YXRlIiwiaXNGZWF0dXJlRmxhZ3NMb2FkZWQiLCJzZXR0aW5nc0xvYWRTdGF0ZSIsImZlYXR1cmVGbGFncyIsImVudmlyb25tZW50RmFpbHVyZU5vdEZvdW5kVGVtcGxhdGUiLCJlbnZpcm9ubWVudEZhaWx1cmVQZXJtaXNzaW9uRGVuaWVkVGVtcGxhdGUiLCJlbnZpcm9ubWVudEZhaWx1cmVVbmtub3duVGVtcGxhdGUiXV0sdGVtcGxhdGU6ZnVuY3Rpb24oZSxpKXsxJmUmJihPKDAsInBsdWdpbnMtY29tcG9uZW50IiwwKSxCKDEsImFzeW5jIiksQigyLCJhc3luYyIpLEIoMywiYXN5bmMiKSxCKDQsImFzeW5jIiksQig1LCJhc3luYyIpLEIoNiwiYXN5bmMiKSxCKDcsImFzeW5jIiksQig4LCJhc3luYyIpKSwyJmUmJnkoImFjdGl2ZUtub3duUGx1Z2luIixVKDEsMTEsaS5hY3RpdmVLbm93blBsdWdpbiQpKSgiYWN0aXZlUGx1Z2luSWQiLFUoMiwxMyxpLmFjdGl2ZVBsdWdpbklkJCkpKCJkYXRhTG9jYXRpb24iLFUoMywxNSxpLmRhdGFMb2NhdGlvbiQpKSgibGFzdFVwZGF0ZWQiLFUoNCwxNyxpLmxhc3RMb2FkZWRUaW1lSW5NcyQpKSgicGx1Z2luTG9hZFN0YXRlIixVKDUsMTksaS5wbHVnaW5Mb2FkU3RhdGUkKSkoImlzRmVhdHVyZUZsYWdzTG9hZGVkIixVKDYsMjEsaS5pc0ZlYXR1cmVGbGFnc0xvYWRlZCQpKSgic2V0dGluZ3NMb2FkU3RhdGUiLFUoNywyMyxpLnNldHRpbmdzTG9hZFN0YXRlJCkpKCJmZWF0dXJlRmxhZ3MiLFUoOCwyNSxpLmZlYXR1cmVGbGFncyQpKSgiZW52aXJvbm1lbnRGYWlsdXJlTm90Rm91bmRUZW1wbGF0ZSIsaS5lbnZpcm9ubWVudEZhaWx1cmVOb3RGb3VuZFRlbXBsYXRlKSgiZW52aXJvbm1lbnRGYWlsdXJlUGVybWlzc2lvbkRlbmllZFRlbXBsYXRlIixpLmVudmlyb25tZW50RmFpbHVyZVBlcm1pc3Npb25EZW5pZWRUZW1wbGF0ZSkoImVudmlyb25tZW50RmFpbHVyZVVua25vd25UZW1wbGF0ZSIsaS5lbnZpcm9ubWVudEZhaWx1cmVVbmtub3duVGVtcGxhdGUpfSxzdHlsZXM6WyJwbHVnaW5zLWNvbXBvbmVudFtfbmdjb250ZW50LSVDT01QJV0geyBoZWlnaHQ6IDEwMCU7IH0iXSxjaGFuZ2VEZXRlY3Rpb246MH0pLG59KSgpLHROPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm59KSxuLlx1MDI3NWluaj1WKHtpbXBvcnRzOltlYyxNZSxCc119KSxufSkoKTtOeChlTixbSGdlXSxbR2VdKTt2YXIgVWdlPSgoKT0+e2NsYXNzIG57Y29uc3RydWN0b3IoZSxpKXt0aGlzLnN0b3JlPWUsdGhpcy5kb2N1bWVudD1pLHRoaXMub25WaXNpYmlsaXR5Q2hhbmdlPXRoaXMub25WaXNpYmlsaXR5Q2hhbmdlSW1wbC5iaW5kKHRoaXMpLHRoaXMucmVsb2FkRW5hYmxlZCQ9dGhpcy5zdG9yZS5waXBlKHZ0KE5hLmdldFJlbG9hZEVuYWJsZWQpKSx0aGlzLnJlbG9hZFBlcmlvZEluTXMkPXRoaXMuc3RvcmUucGlwZSh2dChOYS5nZXRSZWxvYWRQZXJpb2RJbk1zKSksdGhpcy5yZWxvYWRUaW1lcklkPW51bGwsdGhpcy5taXNzZWRBdXRvUmVsb2FkPSExLHRoaXMubmdVbnN1YnNjcmliZT1uZXcga2V9bmdPbkluaXQoKXt0aGlzLmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoInZpc2liaWxpdHljaGFuZ2UiLHRoaXMub25WaXNpYmlsaXR5Q2hhbmdlKSxMdCh0aGlzLnJlbG9hZEVuYWJsZWQkLnBpcGUoeWkoKSksdGhpcy5yZWxvYWRQZXJpb2RJbk1zJC5waXBlKHlpKCkpKS5waXBlKHN0KHRoaXMubmdVbnN1YnNjcmliZSkpLnN1YnNjcmliZSgoW2UsaV0pPT57dGhpcy5jYW5jZWxMb2FkKCksZSYmdGhpcy5sb2FkKGkpfSl9b25WaXNpYmlsaXR5Q2hhbmdlSW1wbCgpeyJ2aXNpYmxlIj09PXRoaXMuZG9jdW1lbnQudmlzaWJpbGl0eVN0YXRlJiZ0aGlzLm1pc3NlZEF1dG9SZWxvYWQmJih0aGlzLm1pc3NlZEF1dG9SZWxvYWQ9ITEsdGhpcy5zdG9yZS5kaXNwYXRjaChhYSgpKSl9bG9hZChlKXt0aGlzLnJlbG9hZFRpbWVySWQ9c2V0VGltZW91dCgoKT0+eyJ2aXNpYmxlIj09PXRoaXMuZG9jdW1lbnQudmlzaWJpbGl0eVN0YXRlP3RoaXMuc3RvcmUuZGlzcGF0Y2goYWEoKSk6dGhpcy5taXNzZWRBdXRvUmVsb2FkPSEwLHRoaXMubG9hZChlKX0sZSl9Y2FuY2VsTG9hZCgpe251bGwhPT10aGlzLnJlbG9hZFRpbWVySWQmJmNsZWFyVGltZW91dCh0aGlzLnJlbG9hZFRpbWVySWQpLHRoaXMucmVsb2FkVGltZXJJZD1udWxsfW5nT25EZXN0cm95KCl7dGhpcy5jYW5jZWxMb2FkKCksdGhpcy5kb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCJ2aXNpYmlsaXR5Y2hhbmdlIix0aGlzLm9uVmlzaWJpbGl0eUNoYW5nZSksdGhpcy5uZ1Vuc3Vic2NyaWJlLm5leHQoKSx0aGlzLm5nVW5zdWJzY3JpYmUuY29tcGxldGUoKX19cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bikoTShDZSksTShIdCkpfSxuLlx1MDI3NWNtcD1SKHt0eXBlOm4sc2VsZWN0b3JzOltbInJlbG9hZGVyIl1dLGRlY2xzOjAsdmFyczowLHRlbXBsYXRlOmZ1bmN0aW9uKGUsaSl7fSxlbmNhcHN1bGF0aW9uOjIsY2hhbmdlRGV0ZWN0aW9uOjB9KSxufSkoKSx6Z2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1Y21wPVIoe3R5cGU6bixzZWxlY3RvcnM6W1sidGVuc29yYm9hcmQtd3JhcHBlci1jb21wb25lbnQiXV0sZGVjbHM6Mix2YXJzOjAsY29uc3RzOltbMSwicGx1Z2lucyJdXSx0ZW1wbGF0ZTpmdW5jdGlvbihlLGkpezEmZSYmTygwLCJwbHVnaW5zIiwwKSgxLCJyZWxvYWRlciIpfSxkZXBlbmRlbmNpZXM6W2VOLFVnZV0sc3R5bGVzOlsiW19uZ2hvc3QtJUNPTVAlXSB7XG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG4gICAgICAgIGhlaWdodDogMTAwJTtcbiAgICAgIH1cblxuICAgICAgLnBsdWdpbnNbX25nY29udGVudC0lQ09NUCVdIHtcbiAgICAgICAgZmxleDogMSAxO1xuICAgICAgICBvdmVyZmxvdzogYXV0bztcbiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgICAgfSJdLGNoYW5nZURldGVjdGlvbjowfSksbn0pKCksamdlPSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NXByb3Y9eWUoe3Rva2VuOm4sZmFjdG9yeTpuLlx1MDI3NWZhY30pLG59KSgpLGI2PSJzbW9vdGhpbmciLHg2PSJydW5Db2xvckdyb3VwIixDNj0idGFnRmlsdGVyIixNNj0icnVuRmlsdGVyIix3Nj0icmVnZXg6IixuTj1jbGFzcyBleHRlbmRzIGpnZXtnZXRNZXRyaWNzUGlubmVkQ2FyZHModCl7cmV0dXJuIEx0KFt0LnNlbGVjdCh6TSksdC5zZWxlY3QodmVlKV0pLnBpcGUoTCgoW2UsaV0pPT57aWYoIWUubGVuZ3RoJiYhaS5sZW5ndGgpcmV0dXJuW107bGV0IG89Wy4uLmUubWFwKCh7cGx1Z2luOnMsdGFnOmEsc2FtcGxlOmwscnVuSWQ6Y30pPT57bGV0IHU9e3BsdWdpbjpzLHRhZzphfTtyZXR1cm4gbWwocykmJih1LnJ1bklkPWMpLGZsKHMpJiYodS5zYW1wbGU9bCksdX0pLC4uLmldO3JldHVyblt7a2V5OiJwaW5uZWRDYXJkcyIsdmFsdWU6SlNPTi5zdHJpbmdpZnkobyl9XX0pKX1zZXJpYWxpemVTdGF0ZVRvUXVlcnlQYXJhbXModCl7cmV0dXJuIEx0KFt0aGlzLmdldE1ldHJpY3NQaW5uZWRDYXJkcyh0KSx0LnNlbGVjdChYYykucGlwZShMKGU9PmU/W3trZXk6QzYsdmFsdWU6ZX1dOltdKSksTHQoW3Quc2VsZWN0KHpBKSx0LnNlbGVjdChIbSldKS5waXBlKEwoKFtlLGldKT0+ZnVuY3Rpb24obix0KXtyZXR1cm4gT2JqZWN0LmVudHJpZXMobikubWFwKChbZSxpXSk9PntpZih2b2lkIDA9PT1pKXJldHVybnt9O2xldCByPXRbZV07cmV0dXJuIHImJnIucXVlcnlQYXJhbU92ZXJyaWRlP3trZXk6ci5xdWVyeVBhcmFtT3ZlcnJpZGUsdmFsdWU6aT8udG9TdHJpbmcoKX06e319KS5maWx0ZXIoKHtrZXk6ZSx2YWx1ZTppfSk9PmUmJnZvaWQgMCE9PWkpfShlLGkpKSksdC5zZWxlY3QoYmVlKS5waXBlKEwoZT0+TnVtYmVyLmlzRmluaXRlKGUuc2NhbGFyU21vb3RoaW5nKT9be2tleTpiNix2YWx1ZTpTdHJpbmcoZS5zY2FsYXJTbW9vdGhpbmcpfV06W10pKSx0LnNlbGVjdChySCkucGlwZShMKGU9PntpZighZSlyZXR1cm5bXTtsZXQgaTtzd2l0Y2goZS5rZXkpe2Nhc2Ugc3IuRVhQRVJJTUVOVDppPSJleHBlcmltZW50IjticmVhaztjYXNlIHNyLlJVTjppPSJydW4iO2JyZWFrO2Nhc2Ugc3IuUkVHRVg6aT1gJHt3Nn0ke2UucmVnZXhTdHJpbmd9YDticmVhaztkZWZhdWx0OnRocm93IG5ldyBSYW5nZUVycm9yKCJTZXJpYWxpemF0aW9uIG5vdCBpbXBsZW1lbnRlZCIpfXJldHVyblt7a2V5Ong2LHZhbHVlOml9XX0pKSx0LnNlbGVjdChRbSkucGlwZShMKGU9PmU/W3trZXk6TTYsdmFsdWU6ZX1dOltdKSldKS5waXBlKEwoZT0+ZS5mbGF0KCkpKX1kZXNlcmlhbGl6ZVF1ZXJ5UGFyYW1zKHQpe2xldCBlPW51bGwsaT1udWxsLHI9bnVsbCxvPW51bGwscz1udWxsO2ZvcihsZXR7a2V5OmEsdmFsdWU6bH1vZiB0KXN3aXRjaChhKXtjYXNlInBpbm5lZENhcmRzIjplPXZudChsKTticmVhaztjYXNlIGI2Omk9TnVtYmVyKGwpO2JyZWFrO2Nhc2UgeDY6c3dpdGNoKGwpe2Nhc2UiZXhwZXJpbWVudCI6bz17a2V5OnNyLkVYUEVSSU1FTlR9O2JyZWFrO2Nhc2UicnVuIjpvPXtrZXk6c3IuUlVOfX1pZihsLnN0YXJ0c1dpdGgodzYpKXtsZXQgYz1sLnNsaWNlKHc2Lmxlbmd0aCk7bz17a2V5OnNyLlJFR0VYLHJlZ2V4U3RyaW5nOmN9fWJyZWFrO2Nhc2UgQzY6cj1sO2JyZWFrO2Nhc2UgTTY6cz1sfXJldHVybnttZXRyaWNzOntwaW5uZWRDYXJkczplfHxbXSxzbW9vdGhpbmc6aSx0YWdGaWx0ZXI6cn0scnVuczp7Z3JvdXBCeTpvLHJlZ2V4RmlsdGVyOnN9fX19O2Z1bmN0aW9uIHZudChuKXtsZXQgdDt0cnl7dD1KU09OLnBhcnNlKG4pfWNhdGNoe3JldHVybiBudWxsfWlmKCFBcnJheS5pc0FycmF5KHQpKXJldHVybiBudWxsO2xldCBlPVtdO2ZvcihsZXQgaSBvZiB0KXtsZXQgbz0ic3RyaW5nIj09dHlwZW9mIGkucnVuSWQscz0ibnVtYmVyIj09dHlwZW9mIGkuc2FtcGxlLGE9InN0cmluZyI9PXR5cGVvZiBpLnRhZyxsPW98fHR5cGVvZiBpLnJ1bklkPiJ1IixjPXN8fHR5cGVvZiBpLnNhbXBsZT4idSI7aWYoISgic3RyaW5nIj09dHlwZW9mIGkucGx1Z2luJiZhJiZsJiZjJiZYJChpLnBsdWdpbikmJmkudGFnKSljb250aW51ZTtpZihtbChpLnBsdWdpbikpe2lmKCFpLnJ1bklkKWNvbnRpbnVlfWVsc2UgaWYoaS5ydW5JZCljb250aW51ZTtpZihzJiYoIWZsKGkucGx1Z2luKXx8IU51bWJlci5pc0ludGVnZXIoaS5zYW1wbGUpfHxpLnNhbXBsZTwwKSljb250aW51ZTtsZXQgdT17cGx1Z2luOmkucGx1Z2luLHRhZzppLnRhZ307byYmKHUucnVuSWQ9aS5ydW5JZCkscyYmKHUuc2FtcGxlPWkuc2FtcGxlKSxlLnB1c2godSl9cmV0dXJuIGV9ZnVuY3Rpb24gV2dlKCl7cmV0dXJuW3tyb3V0ZUtpbmQ6aGkuRVhQRVJJTUVOVCxwYXRoOiIvIixuZ0NvbXBvbmVudDp6Z2UsZGVmYXVsdFJvdXRlOiEwLGRlZXBMaW5rUHJvdmlkZXI6bmV3IG5OfSx7cm91dGVLaW5kOmhpLkZMQUdTLHBhdGg6Ii9mbGFncy8iLG5nQ29tcG9uZW50OlcyfV19ZnVuY3Rpb24geW50KG4pe3JldHVybih0LGUpPT57bGV0IGk9bih0LGUpO3JldHVybiBjb25zb2xlLmdyb3VwQ29sbGFwc2VkKGUudHlwZSksY29uc29sZS5sb2coInByZXYgc3RhdGUiLHQpLGNvbnNvbGUubG9nKCJhY3Rpb24iLGUpLGNvbnNvbGUubG9nKCJuZXh0IHN0YXRlIixpKSxjb25zb2xlLmdyb3VwRW5kKCksaX19ZnVuY3Rpb24gcWdlKCl7cmV0dXJuIHRDKCk/eW50Om49Pih0LGUpPT5uKHQsZSl9bk49cFcoW3JxKCldLG5OKTt2YXIgWWdlPW5ldyBwZSgiUm9vdCByZWR1Y2VycyB0b2tlbiIse2ZhY3Rvcnk6KCk9Pih7fSl9KSxYZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe3Byb3ZpZGVyczpbe3Byb3ZpZGU6V18sdXNlRmFjdG9yeTpxZ2UsbXVsdGk6ITB9XSxpbXBvcnRzOlt3ci5mb3JSb290KFlnZSx7cnVudGltZUNoZWNrczp7c3RyaWN0U3RhdGVJbW11dGFiaWxpdHk6ITAsc3RyaWN0QWN0aW9uSW1tdXRhYmlsaXR5OiEwLHN0cmljdEFjdGlvblNlcmlhbGl6YWJpbGl0eTohMSxzdHJpY3RTdGF0ZVNlcmlhbGl6YWJpbGl0eTohMX19KSxyby5mb3JSb290KFtdKV19KSxufSkoKSxRZ2U9KCgpPT57Y2xhc3Mgbnt9cmV0dXJuIG4uXHUwMjc1ZmFjPWZ1bmN0aW9uKGUpe3JldHVybiBuZXcoZXx8bil9LG4uXHUwMjc1bW9kPUgoe3R5cGU6bn0pLG4uXHUwMjc1aW5qPVYoe30pLG59KSgpLEtnZT0oKCk9PntjbGFzcyBue31yZXR1cm4gbi5cdTAyNzVmYWM9ZnVuY3Rpb24oZSl7cmV0dXJuIG5ldyhlfHxuKX0sbi5cdTAyNzVtb2Q9SCh7dHlwZTpufSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbTWUsdE4sUWdlXX0pLG59KSgpLFM2PSgoKT0+e2NsYXNzIG57fXJldHVybiBuLlx1MDI3NWZhYz1mdW5jdGlvbihlKXtyZXR1cm4gbmV3KGV8fG4pfSxuLlx1MDI3NW1vZD1IKHt0eXBlOm4sYm9vdHN0cmFwOltVaWVdfSksbi5cdTAyNzVpbmo9Vih7aW1wb3J0czpbY3YsWmllLHRBLGVKLEpfLHppZSxxYy5yZWdpc3RlclJvdXRlcyhXZ2UpLEUyLFMyLGppZSxLZ2UsZWMsUWllLFdpZSxlcmUsbUksdHJlLHFpZSxUNCxyMix0TiwkSSwkMixYZ2UsVmdlXX0pLG59KSgpOyJsb2FkaW5nIiE9PWRvY3VtZW50LnJlYWR5U3RhdGU/eTUoKS5ib290c3RyYXBNb2R1bGUoUzYpOndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCJET01Db250ZW50TG9hZGVkIiwoKT0+e3k1KCkuYm9vdHN0cmFwTW9kdWxlKFM2KX0pLGZ1bmN0aW9uKCl7aWYoYVgpdGhyb3cgbmV3IEVycm9yKCJDYW5ub3QgZW5hYmxlIHByb2QgbW9kZSBhZnRlciBwbGF0Zm9ybSBzZXR1cC4iKTtzWD0hMX0oKX0pKCk7Ci8qKiB2aW06IGV0OnRzPTQ6c3c9NDpzdHM9NAogKiBAbGljZW5zZSBSZXF1aXJlSlMgMi4zLjYgQ29weXJpZ2h0IGpRdWVyeSBGb3VuZGF0aW9uIGFuZCBvdGhlciBjb250cmlidXRvcnMuCiAqIFJlbGVhc2VkIHVuZGVyIE1JVCBsaWNlbnNlLCBodHRwczovL2dpdGh1Yi5jb20vcmVxdWlyZWpzL3JlcXVpcmVqcy9ibG9iL21hc3Rlci9MSUNFTlNFCiAqLwovL05vdCB1c2luZyBzdHJpY3Q6IHVuZXZlbiBzdHJpY3Qgc3VwcG9ydCBpbiBicm93c2VycywgIzM5MiwgYW5kIGNhdXNlcwovL3Byb2JsZW1zIHdpdGggcmVxdWlyZWpzLmV4ZWMoKS90cmFuc3BpbGVyIHBsdWdpbnMgdGhhdCBtYXkgbm90IGJlIHN0cmljdC4KLypqc2xpbnQgcmVnZXhwOiB0cnVlLCBub21lbjogdHJ1ZSwgc2xvcHB5OiB0cnVlICovCi8qZ2xvYmFsIHdpbmRvdywgbmF2aWdhdG9yLCBkb2N1bWVudCwgaW1wb3J0U2NyaXB0cywgc2V0VGltZW91dCwgb3BlcmEgKi8KCnZhciByZXF1aXJlanMsIHJlcXVpcmUsIGRlZmluZTsKKGZ1bmN0aW9uIChnbG9iYWwsIHNldFRpbWVvdXQpIHsKICAgIHZhciByZXEsIHMsIGhlYWQsIGJhc2VFbGVtZW50LCBkYXRhTWFpbiwgc3JjLAogICAgICAgIGludGVyYWN0aXZlU2NyaXB0LCBjdXJyZW50bHlBZGRpbmdTY3JpcHQsIG1haW5TY3JpcHQsIHN1YlBhdGgsCiAgICAgICAgdmVyc2lvbiA9ICcyLjMuNicsCiAgICAgICAgY29tbWVudFJlZ0V4cCA9IC9cL1wqW1xzXFNdKj9cKlwvfChbXjoiJz1dfF4pXC9cLy4qJC9tZywKICAgICAgICBjanNSZXF1aXJlUmVnRXhwID0gL1teLl1ccypyZXF1aXJlXHMqXChccypbIiddKFteJyJcc10rKVsiJ11ccypcKS9nLAogICAgICAgIGpzU3VmZml4UmVnRXhwID0gL1wuanMkLywKICAgICAgICBjdXJyRGlyUmVnRXhwID0gL15cLlwvLywKICAgICAgICBvcCA9IE9iamVjdC5wcm90b3R5cGUsCiAgICAgICAgb3N0cmluZyA9IG9wLnRvU3RyaW5nLAogICAgICAgIGhhc093biA9IG9wLmhhc093blByb3BlcnR5LAogICAgICAgIGlzQnJvd3NlciA9ICEhKHR5cGVvZiB3aW5kb3cgIT09ICd1bmRlZmluZWQnICYmIHR5cGVvZiBuYXZpZ2F0b3IgIT09ICd1bmRlZmluZWQnICYmIHdpbmRvdy5kb2N1bWVudCksCiAgICAgICAgaXNXZWJXb3JrZXIgPSAhaXNCcm93c2VyICYmIHR5cGVvZiBpbXBvcnRTY3JpcHRzICE9PSAndW5kZWZpbmVkJywKICAgICAgICAvL1BTMyBpbmRpY2F0ZXMgbG9hZGVkIGFuZCBjb21wbGV0ZSwgYnV0IG5lZWQgdG8gd2FpdCBmb3IgY29tcGxldGUKICAgICAgICAvL3NwZWNpZmljYWxseS4gU2VxdWVuY2UgaXMgJ2xvYWRpbmcnLCAnbG9hZGVkJywgZXhlY3V0aW9uLAogICAgICAgIC8vIHRoZW4gJ2NvbXBsZXRlJy4gVGhlIFVBIGNoZWNrIGlzIHVuZm9ydHVuYXRlLCBidXQgbm90IHN1cmUgaG93CiAgICAgICAgLy90byBmZWF0dXJlIHRlc3Qgdy9vIGNhdXNpbmcgcGVyZiBpc3N1ZXMuCiAgICAgICAgcmVhZHlSZWdFeHAgPSBpc0Jyb3dzZXIgJiYgbmF2aWdhdG9yLnBsYXRmb3JtID09PSAnUExBWVNUQVRJT04gMycgPwogICAgICAgICAgICAgICAgICAgICAgL15jb21wbGV0ZSQvIDogL14oY29tcGxldGV8bG9hZGVkKSQvLAogICAgICAgIGRlZkNvbnRleHROYW1lID0gJ18nLAogICAgICAgIC8vT2ggdGhlIHRyYWdlZHksIGRldGVjdGluZyBvcGVyYS4gU2VlIHRoZSB1c2FnZSBvZiBpc09wZXJhIGZvciByZWFzb24uCiAgICAgICAgaXNPcGVyYSA9IHR5cGVvZiBvcGVyYSAhPT0gJ3VuZGVmaW5lZCcgJiYgb3BlcmEudG9TdHJpbmcoKSA9PT0gJ1tvYmplY3QgT3BlcmFdJywKICAgICAgICBjb250ZXh0cyA9IHt9LAogICAgICAgIGNmZyA9IHt9LAogICAgICAgIGdsb2JhbERlZlF1ZXVlID0gW10sCiAgICAgICAgdXNlSW50ZXJhY3RpdmUgPSBmYWxzZTsKCiAgICAvL0NvdWxkIG1hdGNoIHNvbWV0aGluZyBsaWtlICcpLy9jb21tZW50JywgZG8gbm90IGxvc2UgdGhlIHByZWZpeCB0byBjb21tZW50LgogICAgZnVuY3Rpb24gY29tbWVudFJlcGxhY2UobWF0Y2gsIHNpbmdsZVByZWZpeCkgewogICAgICAgIHJldHVybiBzaW5nbGVQcmVmaXggfHwgJyc7CiAgICB9CgogICAgZnVuY3Rpb24gaXNGdW5jdGlvbihpdCkgewogICAgICAgIHJldHVybiBvc3RyaW5nLmNhbGwoaXQpID09PSAnW29iamVjdCBGdW5jdGlvbl0nOwogICAgfQoKICAgIGZ1bmN0aW9uIGlzQXJyYXkoaXQpIHsKICAgICAgICByZXR1cm4gb3N0cmluZy5jYWxsKGl0KSA9PT0gJ1tvYmplY3QgQXJyYXldJzsKICAgIH0KCiAgICAvKioKICAgICAqIEhlbHBlciBmdW5jdGlvbiBmb3IgaXRlcmF0aW5nIG92ZXIgYW4gYXJyYXkuIElmIHRoZSBmdW5jIHJldHVybnMKICAgICAqIGEgdHJ1ZSB2YWx1ZSwgaXQgd2lsbCBicmVhayBvdXQgb2YgdGhlIGxvb3AuCiAgICAgKi8KICAgIGZ1bmN0aW9uIGVhY2goYXJ5LCBmdW5jKSB7CiAgICAgICAgaWYgKGFyeSkgewogICAgICAgICAgICB2YXIgaTsKICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGFyeS5sZW5ndGg7IGkgKz0gMSkgewogICAgICAgICAgICAgICAgaWYgKGFyeVtpXSAmJiBmdW5jKGFyeVtpXSwgaSwgYXJ5KSkgewogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfQoKICAgIC8qKgogICAgICogSGVscGVyIGZ1bmN0aW9uIGZvciBpdGVyYXRpbmcgb3ZlciBhbiBhcnJheSBiYWNrd2FyZHMuIElmIHRoZSBmdW5jCiAgICAgKiByZXR1cm5zIGEgdHJ1ZSB2YWx1ZSwgaXQgd2lsbCBicmVhayBvdXQgb2YgdGhlIGxvb3AuCiAgICAgKi8KICAgIGZ1bmN0aW9uIGVhY2hSZXZlcnNlKGFyeSwgZnVuYykgewogICAgICAgIGlmIChhcnkpIHsKICAgICAgICAgICAgdmFyIGk7CiAgICAgICAgICAgIGZvciAoaSA9IGFyeS5sZW5ndGggLSAxOyBpID4gLTE7IGkgLT0gMSkgewogICAgICAgICAgICAgICAgaWYgKGFyeVtpXSAmJiBmdW5jKGFyeVtpXSwgaSwgYXJ5KSkgewogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfQoKICAgIGZ1bmN0aW9uIGhhc1Byb3Aob2JqLCBwcm9wKSB7CiAgICAgICAgcmV0dXJuIGhhc093bi5jYWxsKG9iaiwgcHJvcCk7CiAgICB9CgogICAgZnVuY3Rpb24gZ2V0T3duKG9iaiwgcHJvcCkgewogICAgICAgIHJldHVybiBoYXNQcm9wKG9iaiwgcHJvcCkgJiYgb2JqW3Byb3BdOwogICAgfQoKICAgIC8qKgogICAgICogQ3ljbGVzIG92ZXIgcHJvcGVydGllcyBpbiBhbiBvYmplY3QgYW5kIGNhbGxzIGEgZnVuY3Rpb24gZm9yIGVhY2gKICAgICAqIHByb3BlcnR5IHZhbHVlLiBJZiB0aGUgZnVuY3Rpb24gcmV0dXJucyBhIHRydXRoeSB2YWx1ZSwgdGhlbiB0aGUKICAgICAqIGl0ZXJhdGlvbiBpcyBzdG9wcGVkLgogICAgICovCiAgICBmdW5jdGlvbiBlYWNoUHJvcChvYmosIGZ1bmMpIHsKICAgICAgICB2YXIgcHJvcDsKICAgICAgICBmb3IgKHByb3AgaW4gb2JqKSB7CiAgICAgICAgICAgIGlmIChoYXNQcm9wKG9iaiwgcHJvcCkpIHsKICAgICAgICAgICAgICAgIGlmIChmdW5jKG9ialtwcm9wXSwgcHJvcCkpIHsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0KCiAgICAvKioKICAgICAqIFNpbXBsZSBmdW5jdGlvbiB0byBtaXggaW4gcHJvcGVydGllcyBmcm9tIHNvdXJjZSBpbnRvIHRhcmdldCwKICAgICAqIGJ1dCBvbmx5IGlmIHRhcmdldCBkb2VzIG5vdCBhbHJlYWR5IGhhdmUgYSBwcm9wZXJ0eSBvZiB0aGUgc2FtZSBuYW1lLgogICAgICovCiAgICBmdW5jdGlvbiBtaXhpbih0YXJnZXQsIHNvdXJjZSwgZm9yY2UsIGRlZXBTdHJpbmdNaXhpbikgewogICAgICAgIGlmIChzb3VyY2UpIHsKICAgICAgICAgICAgZWFjaFByb3Aoc291cmNlLCBmdW5jdGlvbiAodmFsdWUsIHByb3ApIHsKICAgICAgICAgICAgICAgIGlmIChmb3JjZSB8fCAhaGFzUHJvcCh0YXJnZXQsIHByb3ApKSB7CiAgICAgICAgICAgICAgICAgICAgaWYgKGRlZXBTdHJpbmdNaXhpbiAmJiB0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnICYmIHZhbHVlICYmCiAgICAgICAgICAgICAgICAgICAgICAgICFpc0FycmF5KHZhbHVlKSAmJiAhaXNGdW5jdGlvbih2YWx1ZSkgJiYKICAgICAgICAgICAgICAgICAgICAgICAgISh2YWx1ZSBpbnN0YW5jZW9mIFJlZ0V4cCkpIHsKCiAgICAgICAgICAgICAgICAgICAgICAgIGlmICghdGFyZ2V0W3Byb3BdKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRbcHJvcF0gPSB7fTsKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICBtaXhpbih0YXJnZXRbcHJvcF0sIHZhbHVlLCBmb3JjZSwgZGVlcFN0cmluZ01peGluKTsKICAgICAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRbcHJvcF0gPSB2YWx1ZTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0pOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdGFyZ2V0OwogICAgfQoKICAgIC8vU2ltaWxhciB0byBGdW5jdGlvbi5wcm90b3R5cGUuYmluZCwgYnV0IHRoZSAndGhpcycgb2JqZWN0IGlzIHNwZWNpZmllZAogICAgLy9maXJzdCwgc2luY2UgaXQgaXMgZWFzaWVyIHRvIHJlYWQvZmlndXJlIG91dCB3aGF0ICd0aGlzJyB3aWxsIGJlLgogICAgZnVuY3Rpb24gYmluZChvYmosIGZuKSB7CiAgICAgICAgcmV0dXJuIGZ1bmN0aW9uICgpIHsKICAgICAgICAgICAgcmV0dXJuIGZuLmFwcGx5KG9iaiwgYXJndW1lbnRzKTsKICAgICAgICB9OwogICAgfQoKICAgIGZ1bmN0aW9uIHNjcmlwdHMoKSB7CiAgICAgICAgcmV0dXJuIGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdzY3JpcHQnKTsKICAgIH0KCiAgICBmdW5jdGlvbiBkZWZhdWx0T25FcnJvcihlcnIpIHsKICAgICAgICB0aHJvdyBlcnI7CiAgICB9CgogICAgLy9BbGxvdyBnZXR0aW5nIGEgZ2xvYmFsIHRoYXQgaXMgZXhwcmVzc2VkIGluCiAgICAvL2RvdCBub3RhdGlvbiwgbGlrZSAnYS5iLmMnLgogICAgZnVuY3Rpb24gZ2V0R2xvYmFsKHZhbHVlKSB7CiAgICAgICAgaWYgKCF2YWx1ZSkgewogICAgICAgICAgICByZXR1cm4gdmFsdWU7CiAgICAgICAgfQogICAgICAgIHZhciBnID0gZ2xvYmFsOwogICAgICAgIGVhY2godmFsdWUuc3BsaXQoJy4nKSwgZnVuY3Rpb24gKHBhcnQpIHsKICAgICAgICAgICAgZyA9IGdbcGFydF07CiAgICAgICAgfSk7CiAgICAgICAgcmV0dXJuIGc7CiAgICB9CgogICAgLyoqCiAgICAgKiBDb25zdHJ1Y3RzIGFuIGVycm9yIHdpdGggYSBwb2ludGVyIHRvIGFuIFVSTCB3aXRoIG1vcmUgaW5mb3JtYXRpb24uCiAgICAgKiBAcGFyYW0ge1N0cmluZ30gaWQgdGhlIGVycm9yIElEIHRoYXQgbWFwcyB0byBhbiBJRCBvbiBhIHdlYiBwYWdlLgogICAgICogQHBhcmFtIHtTdHJpbmd9IG1lc3NhZ2UgaHVtYW4gcmVhZGFibGUgZXJyb3IuCiAgICAgKiBAcGFyYW0ge0Vycm9yfSBbZXJyXSB0aGUgb3JpZ2luYWwgZXJyb3IsIGlmIHRoZXJlIGlzIG9uZS4KICAgICAqCiAgICAgKiBAcmV0dXJucyB7RXJyb3J9CiAgICAgKi8KICAgIGZ1bmN0aW9uIG1ha2VFcnJvcihpZCwgbXNnLCBlcnIsIHJlcXVpcmVNb2R1bGVzKSB7CiAgICAgICAgdmFyIGUgPSBuZXcgRXJyb3IobXNnICsgJ1xuaHR0cHM6Ly9yZXF1aXJlanMub3JnL2RvY3MvZXJyb3JzLmh0bWwjJyArIGlkKTsKICAgICAgICBlLnJlcXVpcmVUeXBlID0gaWQ7CiAgICAgICAgZS5yZXF1aXJlTW9kdWxlcyA9IHJlcXVpcmVNb2R1bGVzOwogICAgICAgIGlmIChlcnIpIHsKICAgICAgICAgICAgZS5vcmlnaW5hbEVycm9yID0gZXJyOwogICAgICAgIH0KICAgICAgICByZXR1cm4gZTsKICAgIH0KCiAgICBpZiAodHlwZW9mIGRlZmluZSAhPT0gJ3VuZGVmaW5lZCcpIHsKICAgICAgICAvL0lmIGEgZGVmaW5lIGlzIGFscmVhZHkgaW4gcGxheSB2aWEgYW5vdGhlciBBTUQgbG9hZGVyLAogICAgICAgIC8vZG8gbm90IG92ZXJ3cml0ZS4KICAgICAgICByZXR1cm47CiAgICB9CgogICAgaWYgKHR5cGVvZiByZXF1aXJlanMgIT09ICd1bmRlZmluZWQnKSB7CiAgICAgICAgaWYgKGlzRnVuY3Rpb24ocmVxdWlyZWpzKSkgewogICAgICAgICAgICAvL0RvIG5vdCBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgcmVxdWlyZWpzIGluc3RhbmNlLgogICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQogICAgICAgIGNmZyA9IHJlcXVpcmVqczsKICAgICAgICByZXF1aXJlanMgPSB1bmRlZmluZWQ7CiAgICB9CgogICAgLy9BbGxvdyBmb3IgYSByZXF1aXJlIGNvbmZpZyBvYmplY3QKICAgIGlmICh0eXBlb2YgcmVxdWlyZSAhPT0gJ3VuZGVmaW5lZCcgJiYgIWlzRnVuY3Rpb24ocmVxdWlyZSkpIHsKICAgICAgICAvL2Fzc3VtZSBpdCBpcyBhIGNvbmZpZyBvYmplY3QuCiAgICAgICAgY2ZnID0gcmVxdWlyZTsKICAgICAgICByZXF1aXJlID0gdW5kZWZpbmVkOwogICAgfQoKICAgIGZ1bmN0aW9uIG5ld0NvbnRleHQoY29udGV4dE5hbWUpIHsKICAgICAgICB2YXIgaW5DaGVja0xvYWRlZCwgTW9kdWxlLCBjb250ZXh0LCBoYW5kbGVycywKICAgICAgICAgICAgY2hlY2tMb2FkZWRUaW1lb3V0SWQsCiAgICAgICAgICAgIGNvbmZpZyA9IHsKICAgICAgICAgICAgICAgIC8vRGVmYXVsdHMuIERvIG5vdCBzZXQgYSBkZWZhdWx0IGZvciBtYXAKICAgICAgICAgICAgICAgIC8vY29uZmlnIHRvIHNwZWVkIHVwIG5vcm1hbGl6ZSgpLCB3aGljaAogICAgICAgICAgICAgICAgLy93aWxsIHJ1biBmYXN0ZXIgaWYgdGhlcmUgaXMgbm8gZGVmYXVsdC4KICAgICAgICAgICAgICAgIHdhaXRTZWNvbmRzOiA3LAogICAgICAgICAgICAgICAgYmFzZVVybDogJy4vJywKICAgICAgICAgICAgICAgIHBhdGhzOiB7fSwKICAgICAgICAgICAgICAgIGJ1bmRsZXM6IHt9LAogICAgICAgICAgICAgICAgcGtnczoge30sCiAgICAgICAgICAgICAgICBzaGltOiB7fSwKICAgICAgICAgICAgICAgIGNvbmZpZzoge30KICAgICAgICAgICAgfSwKICAgICAgICAgICAgcmVnaXN0cnkgPSB7fSwKICAgICAgICAgICAgLy9yZWdpc3RyeSBvZiBqdXN0IGVuYWJsZWQgbW9kdWxlcywgdG8gc3BlZWQKICAgICAgICAgICAgLy9jeWNsZSBicmVha2luZyBjb2RlIHdoZW4gbG90cyBvZiBtb2R1bGVzCiAgICAgICAgICAgIC8vYXJlIHJlZ2lzdGVyZWQsIGJ1dCBub3QgYWN0aXZhdGVkLgogICAgICAgICAgICBlbmFibGVkUmVnaXN0cnkgPSB7fSwKICAgICAgICAgICAgdW5kZWZFdmVudHMgPSB7fSwKICAgICAgICAgICAgZGVmUXVldWUgPSBbXSwKICAgICAgICAgICAgZGVmaW5lZCA9IHt9LAogICAgICAgICAgICB1cmxGZXRjaGVkID0ge30sCiAgICAgICAgICAgIGJ1bmRsZXNNYXAgPSB7fSwKICAgICAgICAgICAgcmVxdWlyZUNvdW50ZXIgPSAxLAogICAgICAgICAgICB1bm5vcm1hbGl6ZWRDb3VudGVyID0gMTsKCiAgICAgICAgLyoqCiAgICAgICAgICogVHJpbXMgdGhlIC4gYW5kIC4uIGZyb20gYW4gYXJyYXkgb2YgcGF0aCBzZWdtZW50cy4KICAgICAgICAgKiBJdCB3aWxsIGtlZXAgYSBsZWFkaW5nIHBhdGggc2VnbWVudCBpZiBhIC4uIHdpbGwgYmVjb21lCiAgICAgICAgICogdGhlIGZpcnN0IHBhdGggc2VnbWVudCwgdG8gaGVscCB3aXRoIG1vZHVsZSBuYW1lIGxvb2t1cHMsCiAgICAgICAgICogd2hpY2ggYWN0IGxpa2UgcGF0aHMsIGJ1dCBjYW4gYmUgcmVtYXBwZWQuIEJ1dCB0aGUgZW5kIHJlc3VsdCwKICAgICAgICAgKiBhbGwgcGF0aHMgdGhhdCB1c2UgdGhpcyBmdW5jdGlvbiBzaG91bGQgbG9vayBub3JtYWxpemVkLgogICAgICAgICAqIE5PVEU6IHRoaXMgbWV0aG9kIE1PRElGSUVTIHRoZSBpbnB1dCBhcnJheS4KICAgICAgICAgKiBAcGFyYW0ge0FycmF5fSBhcnkgdGhlIGFycmF5IG9mIHBhdGggc2VnbWVudHMuCiAgICAgICAgICovCiAgICAgICAgZnVuY3Rpb24gdHJpbURvdHMoYXJ5KSB7CiAgICAgICAgICAgIHZhciBpLCBwYXJ0OwogICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgYXJ5Lmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICAgICAgICBwYXJ0ID0gYXJ5W2ldOwogICAgICAgICAgICAgICAgaWYgKHBhcnQgPT09ICcuJykgewogICAgICAgICAgICAgICAgICAgIGFyeS5zcGxpY2UoaSwgMSk7CiAgICAgICAgICAgICAgICAgICAgaSAtPSAxOwogICAgICAgICAgICAgICAgfSBlbHNlIGlmIChwYXJ0ID09PSAnLi4nKSB7CiAgICAgICAgICAgICAgICAgICAgLy8gSWYgYXQgdGhlIHN0YXJ0LCBvciBwcmV2aW91cyB2YWx1ZSBpcyBzdGlsbCAuLiwKICAgICAgICAgICAgICAgICAgICAvLyBrZWVwIHRoZW0gc28gdGhhdCB3aGVuIGNvbnZlcnRlZCB0byBhIHBhdGggaXQgbWF5CiAgICAgICAgICAgICAgICAgICAgLy8gc3RpbGwgd29yayB3aGVuIGNvbnZlcnRlZCB0byBhIHBhdGgsIGV2ZW4gdGhvdWdoCiAgICAgICAgICAgICAgICAgICAgLy8gYXMgYW4gSUQgaXQgaXMgbGVzcyB0aGFuIGlkZWFsLiBJbiBsYXJnZXIgcG9pbnQKICAgICAgICAgICAgICAgICAgICAvLyByZWxlYXNlcywgbWF5IGJlIGJldHRlciB0byBqdXN0IGtpY2sgb3V0IGFuIGVycm9yLgogICAgICAgICAgICAgICAgICAgIGlmIChpID09PSAwIHx8IChpID09PSAxICYmIGFyeVsyXSA9PT0gJy4uJykgfHwgYXJ5W2kgLSAxXSA9PT0gJy4uJykgewogICAgICAgICAgICAgICAgICAgICAgICBjb250aW51ZTsKICAgICAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKGkgPiAwKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGFyeS5zcGxpY2UoaSAtIDEsIDIpOwogICAgICAgICAgICAgICAgICAgICAgICBpIC09IDI7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICAvKioKICAgICAgICAgKiBHaXZlbiBhIHJlbGF0aXZlIG1vZHVsZSBuYW1lLCBsaWtlIC4vc29tZXRoaW5nLCBub3JtYWxpemUgaXQgdG8KICAgICAgICAgKiBhIHJlYWwgbmFtZSB0aGF0IGNhbiBiZSBtYXBwZWQgdG8gYSBwYXRoLgogICAgICAgICAqIEBwYXJhbSB7U3RyaW5nfSBuYW1lIHRoZSByZWxhdGl2ZSBuYW1lCiAgICAgICAgICogQHBhcmFtIHtTdHJpbmd9IGJhc2VOYW1lIGEgcmVhbCBuYW1lIHRoYXQgdGhlIG5hbWUgYXJnIGlzIHJlbGF0aXZlCiAgICAgICAgICogdG8uCiAgICAgICAgICogQHBhcmFtIHtCb29sZWFufSBhcHBseU1hcCBhcHBseSB0aGUgbWFwIGNvbmZpZyB0byB0aGUgdmFsdWUuIFNob3VsZAogICAgICAgICAqIG9ubHkgYmUgZG9uZSBpZiB0aGlzIG5vcm1hbGl6YXRpb24gaXMgZm9yIGEgZGVwZW5kZW5jeSBJRC4KICAgICAgICAgKiBAcmV0dXJucyB7U3RyaW5nfSBub3JtYWxpemVkIG5hbWUKICAgICAgICAgKi8KICAgICAgICBmdW5jdGlvbiBub3JtYWxpemUobmFtZSwgYmFzZU5hbWUsIGFwcGx5TWFwKSB7CiAgICAgICAgICAgIHZhciBwa2dNYWluLCBtYXBWYWx1ZSwgbmFtZVBhcnRzLCBpLCBqLCBuYW1lU2VnbWVudCwgbGFzdEluZGV4LAogICAgICAgICAgICAgICAgZm91bmRNYXAsIGZvdW5kSSwgZm91bmRTdGFyTWFwLCBzdGFySSwgbm9ybWFsaXplZEJhc2VQYXJ0cywKICAgICAgICAgICAgICAgIGJhc2VQYXJ0cyA9IChiYXNlTmFtZSAmJiBiYXNlTmFtZS5zcGxpdCgnLycpKSwKICAgICAgICAgICAgICAgIG1hcCA9IGNvbmZpZy5tYXAsCiAgICAgICAgICAgICAgICBzdGFyTWFwID0gbWFwICYmIG1hcFsnKiddOwoKICAgICAgICAgICAgLy9BZGp1c3QgYW55IHJlbGF0aXZlIHBhdGhzLgogICAgICAgICAgICBpZiAobmFtZSkgewogICAgICAgICAgICAgICAgbmFtZSA9IG5hbWUuc3BsaXQoJy8nKTsKICAgICAgICAgICAgICAgIGxhc3RJbmRleCA9IG5hbWUubGVuZ3RoIC0gMTsKCiAgICAgICAgICAgICAgICAvLyBJZiB3YW50aW5nIG5vZGUgSUQgY29tcGF0aWJpbGl0eSwgc3RyaXAgLmpzIGZyb20gZW5kCiAgICAgICAgICAgICAgICAvLyBvZiBJRHMuIEhhdmUgdG8gZG8gdGhpcyBoZXJlLCBhbmQgbm90IGluIG5hbWVUb1VybAogICAgICAgICAgICAgICAgLy8gYmVjYXVzZSBub2RlIGFsbG93cyBlaXRoZXIgLmpzIG9yIG5vbiAuanMgdG8gbWFwCiAgICAgICAgICAgICAgICAvLyB0byBzYW1lIGZpbGUuCiAgICAgICAgICAgICAgICBpZiAoY29uZmlnLm5vZGVJZENvbXBhdCAmJiBqc1N1ZmZpeFJlZ0V4cC50ZXN0KG5hbWVbbGFzdEluZGV4XSkpIHsKICAgICAgICAgICAgICAgICAgICBuYW1lW2xhc3RJbmRleF0gPSBuYW1lW2xhc3RJbmRleF0ucmVwbGFjZShqc1N1ZmZpeFJlZ0V4cCwgJycpOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIC8vIFN0YXJ0cyB3aXRoIGEgJy4nIHNvIG5lZWQgdGhlIGJhc2VOYW1lCiAgICAgICAgICAgICAgICBpZiAobmFtZVswXS5jaGFyQXQoMCkgPT09ICcuJyAmJiBiYXNlUGFydHMpIHsKICAgICAgICAgICAgICAgICAgICAvL0NvbnZlcnQgYmFzZU5hbWUgdG8gYXJyYXksIGFuZCBsb3Agb2ZmIHRoZSBsYXN0IHBhcnQsCiAgICAgICAgICAgICAgICAgICAgLy9zbyB0aGF0IC4gbWF0Y2hlcyB0aGF0ICdkaXJlY3RvcnknIGFuZCBub3QgbmFtZSBvZiB0aGUgYmFzZU5hbWUncwogICAgICAgICAgICAgICAgICAgIC8vbW9kdWxlLiBGb3IgaW5zdGFuY2UsIGJhc2VOYW1lIG9mICdvbmUvdHdvL3RocmVlJywgbWFwcyB0bwogICAgICAgICAgICAgICAgICAgIC8vJ29uZS90d28vdGhyZWUuanMnLCBidXQgd2Ugd2FudCB0aGUgZGlyZWN0b3J5LCAnb25lL3R3bycgZm9yCiAgICAgICAgICAgICAgICAgICAgLy90aGlzIG5vcm1hbGl6YXRpb24uCiAgICAgICAgICAgICAgICAgICAgbm9ybWFsaXplZEJhc2VQYXJ0cyA9IGJhc2VQYXJ0cy5zbGljZSgwLCBiYXNlUGFydHMubGVuZ3RoIC0gMSk7CiAgICAgICAgICAgICAgICAgICAgbmFtZSA9IG5vcm1hbGl6ZWRCYXNlUGFydHMuY29uY2F0KG5hbWUpOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIHRyaW1Eb3RzKG5hbWUpOwogICAgICAgICAgICAgICAgbmFtZSA9IG5hbWUuam9pbignLycpOwogICAgICAgICAgICB9CgogICAgICAgICAgICAvL0FwcGx5IG1hcCBjb25maWcgaWYgYXZhaWxhYmxlLgogICAgICAgICAgICBpZiAoYXBwbHlNYXAgJiYgbWFwICYmIChiYXNlUGFydHMgfHwgc3Rhck1hcCkpIHsKICAgICAgICAgICAgICAgIG5hbWVQYXJ0cyA9IG5hbWUuc3BsaXQoJy8nKTsKCiAgICAgICAgICAgICAgICBvdXRlckxvb3A6IGZvciAoaSA9IG5hbWVQYXJ0cy5sZW5ndGg7IGkgPiAwOyBpIC09IDEpIHsKICAgICAgICAgICAgICAgICAgICBuYW1lU2VnbWVudCA9IG5hbWVQYXJ0cy5zbGljZSgwLCBpKS5qb2luKCcvJyk7CgogICAgICAgICAgICAgICAgICAgIGlmIChiYXNlUGFydHMpIHsKICAgICAgICAgICAgICAgICAgICAgICAgLy9GaW5kIHRoZSBsb25nZXN0IGJhc2VOYW1lIHNlZ21lbnQgbWF0Y2ggaW4gdGhlIGNvbmZpZy4KICAgICAgICAgICAgICAgICAgICAgICAgLy9TbywgZG8gam9pbnMgb24gdGhlIGJpZ2dlc3QgdG8gc21hbGxlc3QgbGVuZ3RocyBvZiBiYXNlUGFydHMuCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciAoaiA9IGJhc2VQYXJ0cy5sZW5ndGg7IGogPiAwOyBqIC09IDEpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcFZhbHVlID0gZ2V0T3duKG1hcCwgYmFzZVBhcnRzLnNsaWNlKDAsIGopLmpvaW4oJy8nKSk7CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy9iYXNlTmFtZSBzZWdtZW50IGhhcyBjb25maWcsIGZpbmQgaWYgaXQgaGFzIG9uZSBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vdGhpcyBuYW1lLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG1hcFZhbHVlKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwVmFsdWUgPSBnZXRPd24obWFwVmFsdWUsIG5hbWVTZWdtZW50KTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAobWFwVmFsdWUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy9NYXRjaCwgdXBkYXRlIG5hbWUgdG8gdGhlIG5ldyB2YWx1ZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm91bmRNYXAgPSBtYXBWYWx1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm91bmRJID0gaTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWsgb3V0ZXJMb29wOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgLy9DaGVjayBmb3IgYSBzdGFyIG1hcCBtYXRjaCwgYnV0IGp1c3QgaG9sZCBvbiB0byBpdCwKICAgICAgICAgICAgICAgICAgICAvL2lmIHRoZXJlIGlzIGEgc2hvcnRlciBzZWdtZW50IG1hdGNoIGxhdGVyIGluIGEgbWF0Y2hpbmcKICAgICAgICAgICAgICAgICAgICAvL2NvbmZpZywgdGhlbiBmYXZvciBvdmVyIHRoaXMgc3RhciBtYXAuCiAgICAgICAgICAgICAgICAgICAgaWYgKCFmb3VuZFN0YXJNYXAgJiYgc3Rhck1hcCAmJiBnZXRPd24oc3Rhck1hcCwgbmFtZVNlZ21lbnQpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGZvdW5kU3Rhck1hcCA9IGdldE93bihzdGFyTWFwLCBuYW1lU2VnbWVudCk7CiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJJID0gaTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgaWYgKCFmb3VuZE1hcCAmJiBmb3VuZFN0YXJNYXApIHsKICAgICAgICAgICAgICAgICAgICBmb3VuZE1hcCA9IGZvdW5kU3Rhck1hcDsKICAgICAgICAgICAgICAgICAgICBmb3VuZEkgPSBzdGFySTsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICBpZiAoZm91bmRNYXApIHsKICAgICAgICAgICAgICAgICAgICBuYW1lUGFydHMuc3BsaWNlKDAsIGZvdW5kSSwgZm91bmRNYXApOwogICAgICAgICAgICAgICAgICAgIG5hbWUgPSBuYW1lUGFydHMuam9pbignLycpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CgogICAgICAgICAgICAvLyBJZiB0aGUgbmFtZSBwb2ludHMgdG8gYSBwYWNrYWdlJ3MgbmFtZSwgdXNlCiAgICAgICAgICAgIC8vIHRoZSBwYWNrYWdlIG1haW4gaW5zdGVhZC4KICAgICAgICAgICAgcGtnTWFpbiA9IGdldE93bihjb25maWcucGtncywgbmFtZSk7CgogICAgICAgICAgICByZXR1cm4gcGtnTWFpbiA/IHBrZ01haW4gOiBuYW1lOwogICAgICAgIH0KCiAgICAgICAgZnVuY3Rpb24gcmVtb3ZlU2NyaXB0KG5hbWUpIHsKICAgICAgICAgICAgaWYgKGlzQnJvd3NlcikgewogICAgICAgICAgICAgICAgZWFjaChzY3JpcHRzKCksIGZ1bmN0aW9uIChzY3JpcHROb2RlKSB7CiAgICAgICAgICAgICAgICAgICAgaWYgKHNjcmlwdE5vZGUuZ2V0QXR0cmlidXRlKCdkYXRhLXJlcXVpcmVtb2R1bGUnKSA9PT0gbmFtZSAmJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NyaXB0Tm9kZS5nZXRBdHRyaWJ1dGUoJ2RhdGEtcmVxdWlyZWNvbnRleHQnKSA9PT0gY29udGV4dC5jb250ZXh0TmFtZSkgewogICAgICAgICAgICAgICAgICAgICAgICBzY3JpcHROb2RlLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoc2NyaXB0Tm9kZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBmdW5jdGlvbiBoYXNQYXRoRmFsbGJhY2soaWQpIHsKICAgICAgICAgICAgdmFyIHBhdGhDb25maWcgPSBnZXRPd24oY29uZmlnLnBhdGhzLCBpZCk7CiAgICAgICAgICAgIGlmIChwYXRoQ29uZmlnICYmIGlzQXJyYXkocGF0aENvbmZpZykgJiYgcGF0aENvbmZpZy5sZW5ndGggPiAxKSB7CiAgICAgICAgICAgICAgICAvL1BvcCBvZmYgdGhlIGZpcnN0IGFycmF5IHZhbHVlLCBzaW5jZSBpdCBmYWlsZWQsIGFuZAogICAgICAgICAgICAgICAgLy9yZXRyeQogICAgICAgICAgICAgICAgcGF0aENvbmZpZy5zaGlmdCgpOwogICAgICAgICAgICAgICAgY29udGV4dC5yZXF1aXJlLnVuZGVmKGlkKTsKCiAgICAgICAgICAgICAgICAvL0N1c3RvbSByZXF1aXJlIHRoYXQgZG9lcyBub3QgZG8gbWFwIHRyYW5zbGF0aW9uLCBzaW5jZQogICAgICAgICAgICAgICAgLy9JRCBpcyAiYWJzb2x1dGUiLCBhbHJlYWR5IG1hcHBlZC9yZXNvbHZlZC4KICAgICAgICAgICAgICAgIGNvbnRleHQubWFrZVJlcXVpcmUobnVsbCwgewogICAgICAgICAgICAgICAgICAgIHNraXBNYXA6IHRydWUKICAgICAgICAgICAgICAgIH0pKFtpZF0pOwoKICAgICAgICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICAvL1R1cm5zIGEgcGx1Z2luIXJlc291cmNlIHRvIFtwbHVnaW4sIHJlc291cmNlXQogICAgICAgIC8vd2l0aCB0aGUgcGx1Z2luIGJlaW5nIHVuZGVmaW5lZCBpZiB0aGUgbmFtZQogICAgICAgIC8vZGlkIG5vdCBoYXZlIGEgcGx1Z2luIHByZWZpeC4KICAgICAgICBmdW5jdGlvbiBzcGxpdFByZWZpeChuYW1lKSB7CiAgICAgICAgICAgIHZhciBwcmVmaXgsCiAgICAgICAgICAgICAgICBpbmRleCA9IG5hbWUgPyBuYW1lLmluZGV4T2YoJyEnKSA6IC0xOwogICAgICAgICAgICBpZiAoaW5kZXggPiAtMSkgewogICAgICAgICAgICAgICAgcHJlZml4ID0gbmFtZS5zdWJzdHJpbmcoMCwgaW5kZXgpOwogICAgICAgICAgICAgICAgbmFtZSA9IG5hbWUuc3Vic3RyaW5nKGluZGV4ICsgMSwgbmFtZS5sZW5ndGgpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHJldHVybiBbcHJlZml4LCBuYW1lXTsKICAgICAgICB9CgogICAgICAgIC8qKgogICAgICAgICAqIENyZWF0ZXMgYSBtb2R1bGUgbWFwcGluZyB0aGF0IGluY2x1ZGVzIHBsdWdpbiBwcmVmaXgsIG1vZHVsZQogICAgICAgICAqIG5hbWUsIGFuZCBwYXRoLiBJZiBwYXJlbnRNb2R1bGVNYXAgaXMgcHJvdmlkZWQgaXQgd2lsbAogICAgICAgICAqIGFsc28gbm9ybWFsaXplIHRoZSBuYW1lIHZpYSByZXF1aXJlLm5vcm1hbGl6ZSgpCiAgICAgICAgICoKICAgICAgICAgKiBAcGFyYW0ge1N0cmluZ30gbmFtZSB0aGUgbW9kdWxlIG5hbWUKICAgICAgICAgKiBAcGFyYW0ge1N0cmluZ30gW3BhcmVudE1vZHVsZU1hcF0gcGFyZW50IG1vZHVsZSBtYXAKICAgICAgICAgKiBmb3IgdGhlIG1vZHVsZSBuYW1lLCB1c2VkIHRvIHJlc29sdmUgcmVsYXRpdmUgbmFtZXMuCiAgICAgICAgICogQHBhcmFtIHtCb29sZWFufSBpc05vcm1hbGl6ZWQ6IGlzIHRoZSBJRCBhbHJlYWR5IG5vcm1hbGl6ZWQuCiAgICAgICAgICogVGhpcyBpcyB0cnVlIGlmIHRoaXMgY2FsbCBpcyBkb25lIGZvciBhIGRlZmluZSgpIG1vZHVsZSBJRC4KICAgICAgICAgKiBAcGFyYW0ge0Jvb2xlYW59IGFwcGx5TWFwOiBhcHBseSB0aGUgbWFwIGNvbmZpZyB0byB0aGUgSUQuCiAgICAgICAgICogU2hvdWxkIG9ubHkgYmUgdHJ1ZSBpZiB0aGlzIG1hcCBpcyBmb3IgYSBkZXBlbmRlbmN5LgogICAgICAgICAqCiAgICAgICAgICogQHJldHVybnMge09iamVjdH0KICAgICAgICAgKi8KICAgICAgICBmdW5jdGlvbiBtYWtlTW9kdWxlTWFwKG5hbWUsIHBhcmVudE1vZHVsZU1hcCwgaXNOb3JtYWxpemVkLCBhcHBseU1hcCkgewogICAgICAgICAgICB2YXIgdXJsLCBwbHVnaW5Nb2R1bGUsIHN1ZmZpeCwgbmFtZVBhcnRzLAogICAgICAgICAgICAgICAgcHJlZml4ID0gbnVsbCwKICAgICAgICAgICAgICAgIHBhcmVudE5hbWUgPSBwYXJlbnRNb2R1bGVNYXAgPyBwYXJlbnRNb2R1bGVNYXAubmFtZSA6IG51bGwsCiAgICAgICAgICAgICAgICBvcmlnaW5hbE5hbWUgPSBuYW1lLAogICAgICAgICAgICAgICAgaXNEZWZpbmUgPSB0cnVlLAogICAgICAgICAgICAgICAgbm9ybWFsaXplZE5hbWUgPSAnJzsKCiAgICAgICAgICAgIC8vSWYgbm8gbmFtZSwgdGhlbiBpdCBtZWFucyBpdCBpcyBhIHJlcXVpcmUgY2FsbCwgZ2VuZXJhdGUgYW4KICAgICAgICAgICAgLy9pbnRlcm5hbCBuYW1lLgogICAgICAgICAgICBpZiAoIW5hbWUpIHsKICAgICAgICAgICAgICAgIGlzRGVmaW5lID0gZmFsc2U7CiAgICAgICAgICAgICAgICBuYW1lID0gJ19AcicgKyAocmVxdWlyZUNvdW50ZXIgKz0gMSk7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIG5hbWVQYXJ0cyA9IHNwbGl0UHJlZml4KG5hbWUpOwogICAgICAgICAgICBwcmVmaXggPSBuYW1lUGFydHNbMF07CiAgICAgICAgICAgIG5hbWUgPSBuYW1lUGFydHNbMV07CgogICAgICAgICAgICBpZiAocHJlZml4KSB7CiAgICAgICAgICAgICAgICBwcmVmaXggPSBub3JtYWxpemUocHJlZml4LCBwYXJlbnROYW1lLCBhcHBseU1hcCk7CiAgICAgICAgICAgICAgICBwbHVnaW5Nb2R1bGUgPSBnZXRPd24oZGVmaW5lZCwgcHJlZml4KTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy9BY2NvdW50IGZvciByZWxhdGl2ZSBwYXRocyBpZiB0aGVyZSBpcyBhIGJhc2UgbmFtZS4KICAgICAgICAgICAgaWYgKG5hbWUpIHsKICAgICAgICAgICAgICAgIGlmIChwcmVmaXgpIHsKICAgICAgICAgICAgICAgICAgICBpZiAoaXNOb3JtYWxpemVkKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZWROYW1lID0gbmFtZTsKICAgICAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKHBsdWdpbk1vZHVsZSAmJiBwbHVnaW5Nb2R1bGUubm9ybWFsaXplKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIC8vUGx1Z2luIGlzIGxvYWRlZCwgdXNlIGl0cyBub3JtYWxpemUgbWV0aG9kLgogICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVkTmFtZSA9IHBsdWdpbk1vZHVsZS5ub3JtYWxpemUobmFtZSwgZnVuY3Rpb24gKG5hbWUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBub3JtYWxpemUobmFtZSwgcGFyZW50TmFtZSwgYXBwbHlNYXApOwogICAgICAgICAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICAvLyBJZiBuZXN0ZWQgcGx1Z2luIHJlZmVyZW5jZXMsIHRoZW4gZG8gbm90IHRyeSB0bwogICAgICAgICAgICAgICAgICAgICAgICAvLyBub3JtYWxpemUsIGFzIGl0IHdpbGwgbm90IG5vcm1hbGl6ZSBjb3JyZWN0bHkuIFRoaXMKICAgICAgICAgICAgICAgICAgICAgICAgLy8gcGxhY2VzIGEgcmVzdHJpY3Rpb24gb24gcmVzb3VyY2VJZHMsIGFuZCB0aGUgbG9uZ2VyCiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHRlcm0gc29sdXRpb24gaXMgbm90IHRvIG5vcm1hbGl6ZSB1bnRpbCBwbHVnaW5zIGFyZQogICAgICAgICAgICAgICAgICAgICAgICAvLyBsb2FkZWQgYW5kIGFsbCBub3JtYWxpemF0aW9ucyB0byBhbGxvdyBmb3IgYXN5bmMKICAgICAgICAgICAgICAgICAgICAgICAgLy8gbG9hZGluZyBvZiBhIGxvYWRlciBwbHVnaW4uIEJ1dCBmb3Igbm93LCBmaXhlcyB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgLy8gY29tbW9uIHVzZXMuIERldGFpbHMgaW4gIzExMzEKICAgICAgICAgICAgICAgICAgICAgICAgbm9ybWFsaXplZE5hbWUgPSBuYW1lLmluZGV4T2YoJyEnKSA9PT0gLTEgPwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZShuYW1lLCBwYXJlbnROYW1lLCBhcHBseU1hcCkgOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWU7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAvL0EgcmVndWxhciBtb2R1bGUuCiAgICAgICAgICAgICAgICAgICAgbm9ybWFsaXplZE5hbWUgPSBub3JtYWxpemUobmFtZSwgcGFyZW50TmFtZSwgYXBwbHlNYXApOwoKICAgICAgICAgICAgICAgICAgICAvL05vcm1hbGl6ZWQgbmFtZSBtYXkgYmUgYSBwbHVnaW4gSUQgZHVlIHRvIG1hcCBjb25maWcKICAgICAgICAgICAgICAgICAgICAvL2FwcGxpY2F0aW9uIGluIG5vcm1hbGl6ZS4gVGhlIG1hcCBjb25maWcgdmFsdWVzIG11c3QKICAgICAgICAgICAgICAgICAgICAvL2FscmVhZHkgYmUgbm9ybWFsaXplZCwgc28gZG8gbm90IG5lZWQgdG8gcmVkbyB0aGF0IHBhcnQuCiAgICAgICAgICAgICAgICAgICAgbmFtZVBhcnRzID0gc3BsaXRQcmVmaXgobm9ybWFsaXplZE5hbWUpOwogICAgICAgICAgICAgICAgICAgIHByZWZpeCA9IG5hbWVQYXJ0c1swXTsKICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVkTmFtZSA9IG5hbWVQYXJ0c1sxXTsKICAgICAgICAgICAgICAgICAgICBpc05vcm1hbGl6ZWQgPSB0cnVlOwoKICAgICAgICAgICAgICAgICAgICB1cmwgPSBjb250ZXh0Lm5hbWVUb1VybChub3JtYWxpemVkTmFtZSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIC8vSWYgdGhlIGlkIGlzIGEgcGx1Z2luIGlkIHRoYXQgY2Fubm90IGJlIGRldGVybWluZWQgaWYgaXQgbmVlZHMKICAgICAgICAgICAgLy9ub3JtYWxpemF0aW9uLCBzdGFtcCBpdCB3aXRoIGEgdW5pcXVlIElEIHNvIHR3byBtYXRjaGluZyByZWxhdGl2ZQogICAgICAgICAgICAvL2lkcyB0aGF0IG1heSBjb25mbGljdCBjYW4gYmUgc2VwYXJhdGUuCiAgICAgICAgICAgIHN1ZmZpeCA9IHByZWZpeCAmJiAhcGx1Z2luTW9kdWxlICYmICFpc05vcm1hbGl6ZWQgPwogICAgICAgICAgICAgICAgICAgICAnX3Vubm9ybWFsaXplZCcgKyAodW5ub3JtYWxpemVkQ291bnRlciArPSAxKSA6CiAgICAgICAgICAgICAgICAgICAgICcnOwoKICAgICAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgICAgIHByZWZpeDogcHJlZml4LAogICAgICAgICAgICAgICAgbmFtZTogbm9ybWFsaXplZE5hbWUsCiAgICAgICAgICAgICAgICBwYXJlbnRNYXA6IHBhcmVudE1vZHVsZU1hcCwKICAgICAgICAgICAgICAgIHVubm9ybWFsaXplZDogISFzdWZmaXgsCiAgICAgICAgICAgICAgICB1cmw6IHVybCwKICAgICAgICAgICAgICAgIG9yaWdpbmFsTmFtZTogb3JpZ2luYWxOYW1lLAogICAgICAgICAgICAgICAgaXNEZWZpbmU6IGlzRGVmaW5lLAogICAgICAgICAgICAgICAgaWQ6IChwcmVmaXggPwogICAgICAgICAgICAgICAgICAgICAgICBwcmVmaXggKyAnIScgKyBub3JtYWxpemVkTmFtZSA6CiAgICAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZWROYW1lKSArIHN1ZmZpeAogICAgICAgICAgICB9OwogICAgICAgIH0KCiAgICAgICAgZnVuY3Rpb24gZ2V0TW9kdWxlKGRlcE1hcCkgewogICAgICAgICAgICB2YXIgaWQgPSBkZXBNYXAuaWQsCiAgICAgICAgICAgICAgICBtb2QgPSBnZXRPd24ocmVnaXN0cnksIGlkKTsKCiAgICAgICAgICAgIGlmICghbW9kKSB7CiAgICAgICAgICAgICAgICBtb2QgPSByZWdpc3RyeVtpZF0gPSBuZXcgY29udGV4dC5Nb2R1bGUoZGVwTWFwKTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgcmV0dXJuIG1vZDsKICAgICAgICB9CgogICAgICAgIGZ1bmN0aW9uIG9uKGRlcE1hcCwgbmFtZSwgZm4pIHsKICAgICAgICAgICAgdmFyIGlkID0gZGVwTWFwLmlkLAogICAgICAgICAgICAgICAgbW9kID0gZ2V0T3duKHJlZ2lzdHJ5LCBpZCk7CgogICAgICAgICAgICBpZiAoaGFzUHJvcChkZWZpbmVkLCBpZCkgJiYKICAgICAgICAgICAgICAgICAgICAoIW1vZCB8fCBtb2QuZGVmaW5lRW1pdENvbXBsZXRlKSkgewogICAgICAgICAgICAgICAgaWYgKG5hbWUgPT09ICdkZWZpbmVkJykgewogICAgICAgICAgICAgICAgICAgIGZuKGRlZmluZWRbaWRdKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIG1vZCA9IGdldE1vZHVsZShkZXBNYXApOwogICAgICAgICAgICAgICAgaWYgKG1vZC5lcnJvciAmJiBuYW1lID09PSAnZXJyb3InKSB7CiAgICAgICAgICAgICAgICAgICAgZm4obW9kLmVycm9yKTsKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgbW9kLm9uKG5hbWUsIGZuKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgZnVuY3Rpb24gb25FcnJvcihlcnIsIGVycmJhY2spIHsKICAgICAgICAgICAgdmFyIGlkcyA9IGVyci5yZXF1aXJlTW9kdWxlcywKICAgICAgICAgICAgICAgIG5vdGlmaWVkID0gZmFsc2U7CgogICAgICAgICAgICBpZiAoZXJyYmFjaykgewogICAgICAgICAgICAgICAgZXJyYmFjayhlcnIpOwogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgZWFjaChpZHMsIGZ1bmN0aW9uIChpZCkgewogICAgICAgICAgICAgICAgICAgIHZhciBtb2QgPSBnZXRPd24ocmVnaXN0cnksIGlkKTsKICAgICAgICAgICAgICAgICAgICBpZiAobW9kKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIC8vU2V0IGVycm9yIG9uIG1vZHVsZSwgc28gaXQgc2tpcHMgdGltZW91dCBjaGVja3MuCiAgICAgICAgICAgICAgICAgICAgICAgIG1vZC5lcnJvciA9IGVycjsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG1vZC5ldmVudHMuZXJyb3IpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vdGlmaWVkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZC5lbWl0KCdlcnJvcicsIGVycik7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9KTsKCiAgICAgICAgICAgICAgICBpZiAoIW5vdGlmaWVkKSB7CiAgICAgICAgICAgICAgICAgICAgcmVxLm9uRXJyb3IoZXJyKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgLyoqCiAgICAgICAgICogSW50ZXJuYWwgbWV0aG9kIHRvIHRyYW5zZmVyIGdsb2JhbFF1ZXVlIGl0ZW1zIHRvIHRoaXMgY29udGV4dCdzCiAgICAgICAgICogZGVmUXVldWUuCiAgICAgICAgICovCiAgICAgICAgZnVuY3Rpb24gdGFrZUdsb2JhbFF1ZXVlKCkgewogICAgICAgICAgICAvL1B1c2ggYWxsIHRoZSBnbG9iYWxEZWZRdWV1ZSBpdGVtcyBpbnRvIHRoZSBjb250ZXh0J3MgZGVmUXVldWUKICAgICAgICAgICAgaWYgKGdsb2JhbERlZlF1ZXVlLmxlbmd0aCkgewogICAgICAgICAgICAgICAgZWFjaChnbG9iYWxEZWZRdWV1ZSwgZnVuY3Rpb24ocXVldWVJdGVtKSB7CiAgICAgICAgICAgICAgICAgICAgdmFyIGlkID0gcXVldWVJdGVtWzBdOwogICAgICAgICAgICAgICAgICAgIGlmICh0eXBlb2YgaWQgPT09ICdzdHJpbmcnKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRleHQuZGVmUXVldWVNYXBbaWRdID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgZGVmUXVldWUucHVzaChxdWV1ZUl0ZW0pOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICBnbG9iYWxEZWZRdWV1ZSA9IFtdOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBoYW5kbGVycyA9IHsKICAgICAgICAgICAgJ3JlcXVpcmUnOiBmdW5jdGlvbiAobW9kKSB7CiAgICAgICAgICAgICAgICBpZiAobW9kLnJlcXVpcmUpIHsKICAgICAgICAgICAgICAgICAgICByZXR1cm4gbW9kLnJlcXVpcmU7CiAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgIHJldHVybiAobW9kLnJlcXVpcmUgPSBjb250ZXh0Lm1ha2VSZXF1aXJlKG1vZC5tYXApKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgJ2V4cG9ydHMnOiBmdW5jdGlvbiAobW9kKSB7CiAgICAgICAgICAgICAgICBtb2QudXNpbmdFeHBvcnRzID0gdHJ1ZTsKICAgICAgICAgICAgICAgIGlmIChtb2QubWFwLmlzRGVmaW5lKSB7CiAgICAgICAgICAgICAgICAgICAgaWYgKG1vZC5leHBvcnRzKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiAoZGVmaW5lZFttb2QubWFwLmlkXSA9IG1vZC5leHBvcnRzKTsKICAgICAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gKG1vZC5leHBvcnRzID0gZGVmaW5lZFttb2QubWFwLmlkXSA9IHt9KTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICdtb2R1bGUnOiBmdW5jdGlvbiAobW9kKSB7CiAgICAgICAgICAgICAgICBpZiAobW9kLm1vZHVsZSkgewogICAgICAgICAgICAgICAgICAgIHJldHVybiBtb2QubW9kdWxlOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICByZXR1cm4gKG1vZC5tb2R1bGUgPSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGlkOiBtb2QubWFwLmlkLAogICAgICAgICAgICAgICAgICAgICAgICB1cmk6IG1vZC5tYXAudXJsLAogICAgICAgICAgICAgICAgICAgICAgICBjb25maWc6IGZ1bmN0aW9uICgpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBnZXRPd24oY29uZmlnLmNvbmZpZywgbW9kLm1hcC5pZCkgfHwge307CiAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgIGV4cG9ydHM6IG1vZC5leHBvcnRzIHx8IChtb2QuZXhwb3J0cyA9IHt9KQogICAgICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfTsKCiAgICAgICAgZnVuY3Rpb24gY2xlYW5SZWdpc3RyeShpZCkgewogICAgICAgICAgICAvL0NsZWFuIHVwIG1hY2hpbmVyeSB1c2VkIGZvciB3YWl0aW5nIG1vZHVsZXMuCiAgICAgICAgICAgIGRlbGV0ZSByZWdpc3RyeVtpZF07CiAgICAgICAgICAgIGRlbGV0ZSBlbmFibGVkUmVnaXN0cnlbaWRdOwogICAgICAgIH0KCiAgICAgICAgZnVuY3Rpb24gYnJlYWtDeWNsZShtb2QsIHRyYWNlZCwgcHJvY2Vzc2VkKSB7CiAgICAgICAgICAgIHZhciBpZCA9IG1vZC5tYXAuaWQ7CgogICAgICAgICAgICBpZiAobW9kLmVycm9yKSB7CiAgICAgICAgICAgICAgICBtb2QuZW1pdCgnZXJyb3InLCBtb2QuZXJyb3IpOwogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgdHJhY2VkW2lkXSA9IHRydWU7CiAgICAgICAgICAgICAgICBlYWNoKG1vZC5kZXBNYXBzLCBmdW5jdGlvbiAoZGVwTWFwLCBpKSB7CiAgICAgICAgICAgICAgICAgICAgdmFyIGRlcElkID0gZGVwTWFwLmlkLAogICAgICAgICAgICAgICAgICAgICAgICBkZXAgPSBnZXRPd24ocmVnaXN0cnksIGRlcElkKTsKCiAgICAgICAgICAgICAgICAgICAgLy9Pbmx5IGZvcmNlIHRoaW5ncyB0aGF0IGhhdmUgbm90IGNvbXBsZXRlZAogICAgICAgICAgICAgICAgICAgIC8vYmVpbmcgZGVmaW5lZCwgc28gc3RpbGwgaW4gdGhlIHJlZ2lzdHJ5LAogICAgICAgICAgICAgICAgICAgIC8vYW5kIG9ubHkgaWYgaXQgaGFzIG5vdCBiZWVuIG1hdGNoZWQgdXAKICAgICAgICAgICAgICAgICAgICAvL2luIHRoZSBtb2R1bGUgYWxyZWFkeS4KICAgICAgICAgICAgICAgICAgICBpZiAoZGVwICYmICFtb2QuZGVwTWF0Y2hlZFtpXSAmJiAhcHJvY2Vzc2VkW2RlcElkXSkgewogICAgICAgICAgICAgICAgICAgICAgICBpZiAoZ2V0T3duKHRyYWNlZCwgZGVwSWQpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2QuZGVmaW5lRGVwKGksIGRlZmluZWRbZGVwSWRdKTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZC5jaGVjaygpOyAvL3Bhc3MgZmFsc2U/CiAgICAgICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha0N5Y2xlKGRlcCwgdHJhY2VkLCBwcm9jZXNzZWQpOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICBwcm9jZXNzZWRbaWRdID0gdHJ1ZTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgZnVuY3Rpb24gY2hlY2tMb2FkZWQoKSB7CiAgICAgICAgICAgIHZhciBlcnIsIHVzaW5nUGF0aEZhbGxiYWNrLAogICAgICAgICAgICAgICAgd2FpdEludGVydmFsID0gY29uZmlnLndhaXRTZWNvbmRzICogMTAwMCwKICAgICAgICAgICAgICAgIC8vSXQgaXMgcG9zc2libGUgdG8gZGlzYWJsZSB0aGUgd2FpdCBpbnRlcnZhbCBieSB1c2luZyB3YWl0U2Vjb25kcyBvZiAwLgogICAgICAgICAgICAgICAgZXhwaXJlZCA9IHdhaXRJbnRlcnZhbCAmJiAoY29udGV4dC5zdGFydFRpbWUgKyB3YWl0SW50ZXJ2YWwpIDwgbmV3IERhdGUoKS5nZXRUaW1lKCksCiAgICAgICAgICAgICAgICBub0xvYWRzID0gW10sCiAgICAgICAgICAgICAgICByZXFDYWxscyA9IFtdLAogICAgICAgICAgICAgICAgc3RpbGxMb2FkaW5nID0gZmFsc2UsCiAgICAgICAgICAgICAgICBuZWVkQ3ljbGVDaGVjayA9IHRydWU7CgogICAgICAgICAgICAvL0RvIG5vdCBib3RoZXIgaWYgdGhpcyBjYWxsIHdhcyBhIHJlc3VsdCBvZiBhIGN5Y2xlIGJyZWFrLgogICAgICAgICAgICBpZiAoaW5DaGVja0xvYWRlZCkgewogICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICB9CgogICAgICAgICAgICBpbkNoZWNrTG9hZGVkID0gdHJ1ZTsKCiAgICAgICAgICAgIC8vRmlndXJlIG91dCB0aGUgc3RhdGUgb2YgYWxsIHRoZSBtb2R1bGVzLgogICAgICAgICAgICBlYWNoUHJvcChlbmFibGVkUmVnaXN0cnksIGZ1bmN0aW9uIChtb2QpIHsKICAgICAgICAgICAgICAgIHZhciBtYXAgPSBtb2QubWFwLAogICAgICAgICAgICAgICAgICAgIG1vZElkID0gbWFwLmlkOwoKICAgICAgICAgICAgICAgIC8vU2tpcCB0aGluZ3MgdGhhdCBhcmUgbm90IGVuYWJsZWQgb3IgaW4gZXJyb3Igc3RhdGUuCiAgICAgICAgICAgICAgICBpZiAoIW1vZC5lbmFibGVkKSB7CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIGlmICghbWFwLmlzRGVmaW5lKSB7CiAgICAgICAgICAgICAgICAgICAgcmVxQ2FsbHMucHVzaChtb2QpOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIGlmICghbW9kLmVycm9yKSB7CiAgICAgICAgICAgICAgICAgICAgLy9JZiB0aGUgbW9kdWxlIHNob3VsZCBiZSBleGVjdXRlZCwgYW5kIGl0IGhhcyBub3QKICAgICAgICAgICAgICAgICAgICAvL2JlZW4gaW5pdGVkIGFuZCB0aW1lIGlzIHVwLCByZW1lbWJlciBpdC4KICAgICAgICAgICAgICAgICAgICBpZiAoIW1vZC5pbml0ZWQgJiYgZXhwaXJlZCkgewogICAgICAgICAgICAgICAgICAgICAgICBpZiAoaGFzUGF0aEZhbGxiYWNrKG1vZElkKSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNpbmdQYXRoRmFsbGJhY2sgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RpbGxMb2FkaW5nID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vTG9hZHMucHVzaChtb2RJZCk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZW1vdmVTY3JpcHQobW9kSWQpOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIGlmICghbW9kLmluaXRlZCAmJiBtb2QuZmV0Y2hlZCAmJiBtYXAuaXNEZWZpbmUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgc3RpbGxMb2FkaW5nID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFtYXAucHJlZml4KSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvL05vIHJlYXNvbiB0byBrZWVwIGxvb2tpbmcgZm9yIHVuZmluaXNoZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vbG9hZGluZy4gSWYgdGhlIG9ubHkgc3RpbGxMb2FkaW5nIGlzIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vcGx1Z2luIHJlc291cmNlIHRob3VnaCwga2VlcCBnb2luZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vYmVjYXVzZSBpdCBtYXkgYmUgdGhhdCBhIHBsdWdpbiByZXNvdXJjZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy9pcyB3YWl0aW5nIG9uIGEgbm9uLXBsdWdpbiBjeWNsZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiAobmVlZEN5Y2xlQ2hlY2sgPSBmYWxzZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0pOwoKICAgICAgICAgICAgaWYgKGV4cGlyZWQgJiYgbm9Mb2Fkcy5sZW5ndGgpIHsKICAgICAgICAgICAgICAgIC8vSWYgd2FpdCB0aW1lIGV4cGlyZWQsIHRocm93IGVycm9yIG9mIHVubG9hZGVkIG1vZHVsZXMuCiAgICAgICAgICAgICAgICBlcnIgPSBtYWtlRXJyb3IoJ3RpbWVvdXQnLCAnTG9hZCB0aW1lb3V0IGZvciBtb2R1bGVzOiAnICsgbm9Mb2FkcywgbnVsbCwgbm9Mb2Fkcyk7CiAgICAgICAgICAgICAgICBlcnIuY29udGV4dE5hbWUgPSBjb250ZXh0LmNvbnRleHROYW1lOwogICAgICAgICAgICAgICAgcmV0dXJuIG9uRXJyb3IoZXJyKTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy9Ob3QgZXhwaXJlZCwgY2hlY2sgZm9yIGEgY3ljbGUuCiAgICAgICAgICAgIGlmIChuZWVkQ3ljbGVDaGVjaykgewogICAgICAgICAgICAgICAgZWFjaChyZXFDYWxscywgZnVuY3Rpb24gKG1vZCkgewogICAgICAgICAgICAgICAgICAgIGJyZWFrQ3ljbGUobW9kLCB7fSwge30pOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIC8vSWYgc3RpbGwgd2FpdGluZyBvbiBsb2FkcywgYW5kIHRoZSB3YWl0aW5nIGxvYWQgaXMgc29tZXRoaW5nCiAgICAgICAgICAgIC8vb3RoZXIgdGhhbiBhIHBsdWdpbiByZXNvdXJjZSwgb3IgdGhlcmUgYXJlIHN0aWxsIG91dHN0YW5kaW5nCiAgICAgICAgICAgIC8vc2NyaXB0cywgdGhlbiBqdXN0IHRyeSBiYWNrIGxhdGVyLgogICAgICAgICAgICBpZiAoKCFleHBpcmVkIHx8IHVzaW5nUGF0aEZhbGxiYWNrKSAmJiBzdGlsbExvYWRpbmcpIHsKICAgICAgICAgICAgICAgIC8vU29tZXRoaW5nIGlzIHN0aWxsIHdhaXRpbmcgdG8gbG9hZC4gV2FpdCBmb3IgaXQsIGJ1dCBvbmx5CiAgICAgICAgICAgICAgICAvL2lmIGEgdGltZW91dCBpcyBub3QgYWxyZWFkeSBpbiBlZmZlY3QuCiAgICAgICAgICAgICAgICBpZiAoKGlzQnJvd3NlciB8fCBpc1dlYldvcmtlcikgJiYgIWNoZWNrTG9hZGVkVGltZW91dElkKSB7CiAgICAgICAgICAgICAgICAgICAgY2hlY2tMb2FkZWRUaW1lb3V0SWQgPSBzZXRUaW1lb3V0KGZ1bmN0aW9uICgpIHsKICAgICAgICAgICAgICAgICAgICAgICAgY2hlY2tMb2FkZWRUaW1lb3V0SWQgPSAwOwogICAgICAgICAgICAgICAgICAgICAgICBjaGVja0xvYWRlZCgpOwogICAgICAgICAgICAgICAgICAgIH0sIDUwKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQoKICAgICAgICAgICAgaW5DaGVja0xvYWRlZCA9IGZhbHNlOwogICAgICAgIH0KCiAgICAgICAgTW9kdWxlID0gZnVuY3Rpb24gKG1hcCkgewogICAgICAgICAgICB0aGlzLmV2ZW50cyA9IGdldE93bih1bmRlZkV2ZW50cywgbWFwLmlkKSB8fCB7fTsKICAgICAgICAgICAgdGhpcy5tYXAgPSBtYXA7CiAgICAgICAgICAgIHRoaXMuc2hpbSA9IGdldE93bihjb25maWcuc2hpbSwgbWFwLmlkKTsKICAgICAgICAgICAgdGhpcy5kZXBFeHBvcnRzID0gW107CiAgICAgICAgICAgIHRoaXMuZGVwTWFwcyA9IFtdOwogICAgICAgICAgICB0aGlzLmRlcE1hdGNoZWQgPSBbXTsKICAgICAgICAgICAgdGhpcy5wbHVnaW5NYXBzID0ge307CiAgICAgICAgICAgIHRoaXMuZGVwQ291bnQgPSAwOwoKICAgICAgICAgICAgLyogdGhpcy5leHBvcnRzIHRoaXMuZmFjdG9yeQogICAgICAgICAgICAgICB0aGlzLmRlcE1hcHMgPSBbXSwKICAgICAgICAgICAgICAgdGhpcy5lbmFibGVkLCB0aGlzLmZldGNoZWQKICAgICAgICAgICAgKi8KICAgICAgICB9OwoKICAgICAgICBNb2R1bGUucHJvdG90eXBlID0gewogICAgICAgICAgICBpbml0OiBmdW5jdGlvbiAoZGVwTWFwcywgZmFjdG9yeSwgZXJyYmFjaywgb3B0aW9ucykgewogICAgICAgICAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307CgogICAgICAgICAgICAgICAgLy9EbyBub3QgZG8gbW9yZSBpbml0cyBpZiBhbHJlYWR5IGRvbmUuIENhbiBoYXBwZW4gaWYgdGhlcmUKICAgICAgICAgICAgICAgIC8vYXJlIG11bHRpcGxlIGRlZmluZSBjYWxscyBmb3IgdGhlIHNhbWUgbW9kdWxlLiBUaGF0IGlzIG5vdAogICAgICAgICAgICAgICAgLy9hIG5vcm1hbCwgY29tbW9uIGNhc2UsIGJ1dCBpdCBpcyBhbHNvIG5vdCB1bmV4cGVjdGVkLgogICAgICAgICAgICAgICAgaWYgKHRoaXMuaW5pdGVkKSB7CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIHRoaXMuZmFjdG9yeSA9IGZhY3Rvcnk7CgogICAgICAgICAgICAgICAgaWYgKGVycmJhY2spIHsKICAgICAgICAgICAgICAgICAgICAvL1JlZ2lzdGVyIGZvciBlcnJvcnMgb24gdGhpcyBtb2R1bGUuCiAgICAgICAgICAgICAgICAgICAgdGhpcy5vbignZXJyb3InLCBlcnJiYWNrKTsKICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAodGhpcy5ldmVudHMuZXJyb3IpIHsKICAgICAgICAgICAgICAgICAgICAvL0lmIG5vIGVycmJhY2sgYWxyZWFkeSwgYnV0IHRoZXJlIGFyZSBlcnJvciBsaXN0ZW5lcnMKICAgICAgICAgICAgICAgICAgICAvL29uIHRoaXMgbW9kdWxlLCBzZXQgdXAgYW4gZXJyYmFjayB0byBwYXNzIHRvIHRoZSBkZXBzLgogICAgICAgICAgICAgICAgICAgIGVycmJhY2sgPSBiaW5kKHRoaXMsIGZ1bmN0aW9uIChlcnIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbWl0KCdlcnJvcicsIGVycik7CiAgICAgICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgLy9EbyBhIGNvcHkgb2YgdGhlIGRlcGVuZGVuY3kgYXJyYXksIHNvIHRoYXQKICAgICAgICAgICAgICAgIC8vc291cmNlIGlucHV0cyBhcmUgbm90IG1vZGlmaWVkLiBGb3IgZXhhbXBsZQogICAgICAgICAgICAgICAgLy8ic2hpbSIgZGVwcyBhcmUgcGFzc2VkIGluIGhlcmUgZGlyZWN0bHksIGFuZAogICAgICAgICAgICAgICAgLy9kb2luZyBhIGRpcmVjdCBtb2RpZmljYXRpb24gb2YgdGhlIGRlcE1hcHMgYXJyYXkKICAgICAgICAgICAgICAgIC8vd291bGQgYWZmZWN0IHRoYXQgY29uZmlnLgogICAgICAgICAgICAgICAgdGhpcy5kZXBNYXBzID0gZGVwTWFwcyAmJiBkZXBNYXBzLnNsaWNlKDApOwoKICAgICAgICAgICAgICAgIHRoaXMuZXJyYmFjayA9IGVycmJhY2s7CgogICAgICAgICAgICAgICAgLy9JbmRpY2F0ZSB0aGlzIG1vZHVsZSBoYXMgYmUgaW5pdGlhbGl6ZWQKICAgICAgICAgICAgICAgIHRoaXMuaW5pdGVkID0gdHJ1ZTsKCiAgICAgICAgICAgICAgICB0aGlzLmlnbm9yZSA9IG9wdGlvbnMuaWdub3JlOwoKICAgICAgICAgICAgICAgIC8vQ291bGQgaGF2ZSBvcHRpb24gdG8gaW5pdCB0aGlzIG1vZHVsZSBpbiBlbmFibGVkIG1vZGUsCiAgICAgICAgICAgICAgICAvL29yIGNvdWxkIGhhdmUgYmVlbiBwcmV2aW91c2x5IG1hcmtlZCBhcyBlbmFibGVkLiBIb3dldmVyLAogICAgICAgICAgICAgICAgLy90aGUgZGVwZW5kZW5jaWVzIGFyZSBub3Qga25vd24gdW50aWwgaW5pdCBpcyBjYWxsZWQuIFNvCiAgICAgICAgICAgICAgICAvL2lmIGVuYWJsZWQgcHJldmlvdXNseSwgbm93IHRyaWdnZXIgZGVwZW5kZW5jaWVzIGFzIGVuYWJsZWQuCiAgICAgICAgICAgICAgICBpZiAob3B0aW9ucy5lbmFibGVkIHx8IHRoaXMuZW5hYmxlZCkgewogICAgICAgICAgICAgICAgICAgIC8vRW5hYmxlIHRoaXMgbW9kdWxlIGFuZCBkZXBlbmRlbmNpZXMuCiAgICAgICAgICAgICAgICAgICAgLy9XaWxsIGNhbGwgdGhpcy5jaGVjaygpCiAgICAgICAgICAgICAgICAgICAgdGhpcy5lbmFibGUoKTsKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5jaGVjaygpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAoKICAgICAgICAgICAgZGVmaW5lRGVwOiBmdW5jdGlvbiAoaSwgZGVwRXhwb3J0cykgewogICAgICAgICAgICAgICAgLy9CZWNhdXNlIG9mIGN5Y2xlcywgZGVmaW5lZCBjYWxsYmFjayBmb3IgYSBnaXZlbgogICAgICAgICAgICAgICAgLy9leHBvcnQgY2FuIGJlIGNhbGxlZCBtb3JlIHRoYW4gb25jZS4KICAgICAgICAgICAgICAgIGlmICghdGhpcy5kZXBNYXRjaGVkW2ldKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5kZXBNYXRjaGVkW2ldID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICB0aGlzLmRlcENvdW50IC09IDE7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5kZXBFeHBvcnRzW2ldID0gZGVwRXhwb3J0czsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKCiAgICAgICAgICAgIGZldGNoOiBmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgICAgICBpZiAodGhpcy5mZXRjaGVkKSB7CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgdGhpcy5mZXRjaGVkID0gdHJ1ZTsKCiAgICAgICAgICAgICAgICBjb250ZXh0LnN0YXJ0VGltZSA9IChuZXcgRGF0ZSgpKS5nZXRUaW1lKCk7CgogICAgICAgICAgICAgICAgdmFyIG1hcCA9IHRoaXMubWFwOwoKICAgICAgICAgICAgICAgIC8vSWYgdGhlIG1hbmFnZXIgaXMgZm9yIGEgcGx1Z2luIG1hbmFnZWQgcmVzb3VyY2UsCiAgICAgICAgICAgICAgICAvL2FzayB0aGUgcGx1Z2luIHRvIGxvYWQgaXQgbm93LgogICAgICAgICAgICAgICAgaWYgKHRoaXMuc2hpbSkgewogICAgICAgICAgICAgICAgICAgIGNvbnRleHQubWFrZVJlcXVpcmUodGhpcy5tYXAsIHsKICAgICAgICAgICAgICAgICAgICAgICAgZW5hYmxlQnVpbGRDYWxsYmFjazogdHJ1ZQogICAgICAgICAgICAgICAgICAgIH0pKHRoaXMuc2hpbS5kZXBzIHx8IFtdLCBiaW5kKHRoaXMsIGZ1bmN0aW9uICgpIHsKICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG1hcC5wcmVmaXggPyB0aGlzLmNhbGxQbHVnaW4oKSA6IHRoaXMubG9hZCgpOwogICAgICAgICAgICAgICAgICAgIH0pKTsKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgLy9SZWd1bGFyIGRlcGVuZGVuY3kuCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG1hcC5wcmVmaXggPyB0aGlzLmNhbGxQbHVnaW4oKSA6IHRoaXMubG9hZCgpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAoKICAgICAgICAgICAgbG9hZDogZnVuY3Rpb24gKCkgewogICAgICAgICAgICAgICAgdmFyIHVybCA9IHRoaXMubWFwLnVybDsKCiAgICAgICAgICAgICAgICAvL1JlZ3VsYXIgZGVwZW5kZW5jeS4KICAgICAgICAgICAgICAgIGlmICghdXJsRmV0Y2hlZFt1cmxdKSB7CiAgICAgICAgICAgICAgICAgICAgdXJsRmV0Y2hlZFt1cmxdID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICBjb250ZXh0LmxvYWQodGhpcy5tYXAuaWQsIHVybCk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCgogICAgICAgICAgICAvKioKICAgICAgICAgICAgICogQ2hlY2tzIGlmIHRoZSBtb2R1bGUgaXMgcmVhZHkgdG8gZGVmaW5lIGl0c2VsZiwgYW5kIGlmIHNvLAogICAgICAgICAgICAgKiBkZWZpbmUgaXQuCiAgICAgICAgICAgICAqLwogICAgICAgICAgICBjaGVjazogZnVuY3Rpb24gKCkgewogICAgICAgICAgICAgICAgaWYgKCF0aGlzLmVuYWJsZWQgfHwgdGhpcy5lbmFibGluZykgewogICAgICAgICAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICB2YXIgZXJyLCBjanNNb2R1bGUsCiAgICAgICAgICAgICAgICAgICAgaWQgPSB0aGlzLm1hcC5pZCwKICAgICAgICAgICAgICAgICAgICBkZXBFeHBvcnRzID0gdGhpcy5kZXBFeHBvcnRzLAogICAgICAgICAgICAgICAgICAgIGV4cG9ydHMgPSB0aGlzLmV4cG9ydHMsCiAgICAgICAgICAgICAgICAgICAgZmFjdG9yeSA9IHRoaXMuZmFjdG9yeTsKCiAgICAgICAgICAgICAgICBpZiAoIXRoaXMuaW5pdGVkKSB7CiAgICAgICAgICAgICAgICAgICAgLy8gT25seSBmZXRjaCBpZiBub3QgYWxyZWFkeSBpbiB0aGUgZGVmUXVldWUuCiAgICAgICAgICAgICAgICAgICAgaWYgKCFoYXNQcm9wKGNvbnRleHQuZGVmUXVldWVNYXAsIGlkKSkgewogICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmZldGNoKCk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfSBlbHNlIGlmICh0aGlzLmVycm9yKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5lbWl0KCdlcnJvcicsIHRoaXMuZXJyb3IpOwogICAgICAgICAgICAgICAgfSBlbHNlIGlmICghdGhpcy5kZWZpbmluZykgewogICAgICAgICAgICAgICAgICAgIC8vVGhlIGZhY3RvcnkgY291bGQgdHJpZ2dlciBhbm90aGVyIHJlcXVpcmUgY2FsbAogICAgICAgICAgICAgICAgICAgIC8vdGhhdCB3b3VsZCByZXN1bHQgaW4gY2hlY2tpbmcgdGhpcyBtb2R1bGUgdG8KICAgICAgICAgICAgICAgICAgICAvL2RlZmluZSBpdHNlbGYgYWdhaW4uIElmIGFscmVhZHkgaW4gdGhlIHByb2Nlc3MKICAgICAgICAgICAgICAgICAgICAvL29mIGRvaW5nIHRoYXQsIHNraXAgdGhpcyB3b3JrLgogICAgICAgICAgICAgICAgICAgIHRoaXMuZGVmaW5pbmcgPSB0cnVlOwoKICAgICAgICAgICAgICAgICAgICBpZiAodGhpcy5kZXBDb3VudCA8IDEgJiYgIXRoaXMuZGVmaW5lZCkgewogICAgICAgICAgICAgICAgICAgICAgICBpZiAoaXNGdW5jdGlvbihmYWN0b3J5KSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy9JZiB0aGVyZSBpcyBhbiBlcnJvciBsaXN0ZW5lciwgZmF2b3IgcGFzc2luZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy90byB0aGF0IGluc3RlYWQgb2YgdGhyb3dpbmcgYW4gZXJyb3IuIEhvd2V2ZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvL29ubHkgZG8gaXQgZm9yIGRlZmluZSgpJ2QgIG1vZHVsZXMuIHJlcXVpcmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vZXJyYmFja3Mgc2hvdWxkIG5vdCBiZSBjYWxsZWQgZm9yIGZhaWx1cmVzIGluCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvL3RoZWlyIGNhbGxiYWNrcyAoIzY5OSkuIEhvd2V2ZXIgaWYgYSBnbG9iYWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vb25FcnJvciBpcyBzZXQsIHVzZSB0aGF0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCh0aGlzLmV2ZW50cy5lcnJvciAmJiB0aGlzLm1hcC5pc0RlZmluZSkgfHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXEub25FcnJvciAhPT0gZGVmYXVsdE9uRXJyb3IpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBleHBvcnRzID0gY29udGV4dC5leGVjQ2IoaWQsIGZhY3RvcnksIGRlcEV4cG9ydHMsIGV4cG9ydHMpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXJyID0gZTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cG9ydHMgPSBjb250ZXh0LmV4ZWNDYihpZCwgZmFjdG9yeSwgZGVwRXhwb3J0cywgZXhwb3J0cyk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gRmF2b3IgcmV0dXJuIHZhbHVlIG92ZXIgZXhwb3J0cy4gSWYgbm9kZS9janMgaW4gcGxheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIHRoZW4gd2lsbCBub3QgaGF2ZSBhIHJldHVybiB2YWx1ZSBhbnl3YXkuIEZhdm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBtb2R1bGUuZXhwb3J0cyBhc3NpZ25tZW50IG92ZXIgZXhwb3J0cyBvYmplY3QuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAodGhpcy5tYXAuaXNEZWZpbmUgJiYgZXhwb3J0cyA9PT0gdW5kZWZpbmVkKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2pzTW9kdWxlID0gdGhpcy5tb2R1bGU7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGNqc01vZHVsZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBleHBvcnRzID0gY2pzTW9kdWxlLmV4cG9ydHM7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIGlmICh0aGlzLnVzaW5nRXhwb3J0cykgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvL2V4cG9ydHMgYWxyZWFkeSBzZXQgdGhlIGRlZmluZWQgdmFsdWUuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cG9ydHMgPSB0aGlzLmV4cG9ydHM7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChlcnIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlcnIucmVxdWlyZU1hcCA9IHRoaXMubWFwOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVyci5yZXF1aXJlTW9kdWxlcyA9IHRoaXMubWFwLmlzRGVmaW5lID8gW3RoaXMubWFwLmlkXSA6IG51bGw7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXJyLnJlcXVpcmVUeXBlID0gdGhpcy5tYXAuaXNEZWZpbmUgPyAnZGVmaW5lJyA6ICdyZXF1aXJlJzsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gb25FcnJvcigodGhpcy5lcnJvciA9IGVycikpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vSnVzdCBhIGxpdGVyYWwgdmFsdWUKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cG9ydHMgPSBmYWN0b3J5OwogICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmV4cG9ydHMgPSBleHBvcnRzOwoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXMubWFwLmlzRGVmaW5lICYmICF0aGlzLmlnbm9yZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVmaW5lZFtpZF0gPSBleHBvcnRzOwoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChyZXEub25SZXNvdXJjZUxvYWQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXIgcmVzTG9hZE1hcHMgPSBbXTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlYWNoKHRoaXMuZGVwTWFwcywgZnVuY3Rpb24gKGRlcE1hcCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXNMb2FkTWFwcy5wdXNoKGRlcE1hcC5ub3JtYWxpemVkTWFwIHx8IGRlcE1hcCk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVxLm9uUmVzb3VyY2VMb2FkKGNvbnRleHQsIHRoaXMubWFwLCByZXNMb2FkTWFwcyk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIC8vQ2xlYW4gdXAKICAgICAgICAgICAgICAgICAgICAgICAgY2xlYW5SZWdpc3RyeShpZCk7CgogICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmRlZmluZWQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgLy9GaW5pc2hlZCB0aGUgZGVmaW5lIHN0YWdlLiBBbGxvdyBjYWxsaW5nIGNoZWNrIGFnYWluCiAgICAgICAgICAgICAgICAgICAgLy90byBhbGxvdyBkZWZpbmUgbm90aWZpY2F0aW9ucyBiZWxvdyBpbiB0aGUgY2FzZSBvZiBhCiAgICAgICAgICAgICAgICAgICAgLy9jeWNsZS4KICAgICAgICAgICAgICAgICAgICB0aGlzLmRlZmluaW5nID0gZmFsc2U7CgogICAgICAgICAgICAgICAgICAgIGlmICh0aGlzLmRlZmluZWQgJiYgIXRoaXMuZGVmaW5lRW1pdHRlZCkgewogICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmRlZmluZUVtaXR0ZWQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmVtaXQoJ2RlZmluZWQnLCB0aGlzLmV4cG9ydHMpOwogICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmRlZmluZUVtaXRDb21wbGV0ZSA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKCiAgICAgICAgICAgIGNhbGxQbHVnaW46IGZ1bmN0aW9uICgpIHsKICAgICAgICAgICAgICAgIHZhciBtYXAgPSB0aGlzLm1hcCwKICAgICAgICAgICAgICAgICAgICBpZCA9IG1hcC5pZCwKICAgICAgICAgICAgICAgICAgICAvL01hcCBhbHJlYWR5IG5vcm1hbGl6ZWQgdGhlIHByZWZpeC4KICAgICAgICAgICAgICAgICAgICBwbHVnaW5NYXAgPSBtYWtlTW9kdWxlTWFwKG1hcC5wcmVmaXgpOwoKICAgICAgICAgICAgICAgIC8vTWFyayB0aGlzIGFzIGEgZGVwZW5kZW5jeSBmb3IgdGhpcyBwbHVnaW4sIHNvIGl0CiAgICAgICAgICAgICAgICAvL2NhbiBiZSB0cmFjZWQgZm9yIGN5Y2xlcy4KICAgICAgICAgICAgICAgIHRoaXMuZGVwTWFwcy5wdXNoKHBsdWdpbk1hcCk7CgogICAgICAgICAgICAgICAgb24ocGx1Z2luTWFwLCAnZGVmaW5lZCcsIGJpbmQodGhpcywgZnVuY3Rpb24gKHBsdWdpbikgewogICAgICAgICAgICAgICAgICAgIHZhciBsb2FkLCBub3JtYWxpemVkTWFwLCBub3JtYWxpemVkTW9kLAogICAgICAgICAgICAgICAgICAgICAgICBidW5kbGVJZCA9IGdldE93bihidW5kbGVzTWFwLCB0aGlzLm1hcC5pZCksCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSB0aGlzLm1hcC5uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICBwYXJlbnROYW1lID0gdGhpcy5tYXAucGFyZW50TWFwID8gdGhpcy5tYXAucGFyZW50TWFwLm5hbWUgOiBudWxsLAogICAgICAgICAgICAgICAgICAgICAgICBsb2NhbFJlcXVpcmUgPSBjb250ZXh0Lm1ha2VSZXF1aXJlKG1hcC5wYXJlbnRNYXAsIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuYWJsZUJ1aWxkQ2FsbGJhY2s6IHRydWUKICAgICAgICAgICAgICAgICAgICAgICAgfSk7CgogICAgICAgICAgICAgICAgICAgIC8vSWYgY3VycmVudCBtYXAgaXMgbm90IG5vcm1hbGl6ZWQsIHdhaXQgZm9yIHRoYXQKICAgICAgICAgICAgICAgICAgICAvL25vcm1hbGl6ZWQgbmFtZSB0byBsb2FkIGluc3RlYWQgb2YgY29udGludWluZy4KICAgICAgICAgICAgICAgICAgICBpZiAodGhpcy5tYXAudW5ub3JtYWxpemVkKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIC8vTm9ybWFsaXplIHRoZSBJRCBpZiB0aGUgcGx1Z2luIGFsbG93cyBpdC4KICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHBsdWdpbi5ub3JtYWxpemUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSBwbHVnaW4ubm9ybWFsaXplKG5hbWUsIGZ1bmN0aW9uIChuYW1lKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG5vcm1hbGl6ZShuYW1lLCBwYXJlbnROYW1lLCB0cnVlKTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pIHx8ICcnOwogICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICAvL3ByZWZpeCBhbmQgbmFtZSBzaG91bGQgYWxyZWFkeSBiZSBub3JtYWxpemVkLCBubyBuZWVkCiAgICAgICAgICAgICAgICAgICAgICAgIC8vZm9yIGFwcGx5aW5nIG1hcCBjb25maWcgYWdhaW4gZWl0aGVyLgogICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVkTWFwID0gbWFrZU1vZHVsZU1hcChtYXAucHJlZml4ICsgJyEnICsgbmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5tYXAucGFyZW50TWFwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnVlKTsKICAgICAgICAgICAgICAgICAgICAgICAgb24obm9ybWFsaXplZE1hcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdkZWZpbmVkJywgYmluZCh0aGlzLCBmdW5jdGlvbiAodmFsdWUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLm1hcC5ub3JtYWxpemVkTWFwID0gbm9ybWFsaXplZE1hcDsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmluaXQoW10sIGZ1bmN0aW9uICgpIHsgcmV0dXJuIHZhbHVlOyB9LCBudWxsLCB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuYWJsZWQ6IHRydWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlnbm9yZTogdHJ1ZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgfSkpOwoKICAgICAgICAgICAgICAgICAgICAgICAgbm9ybWFsaXplZE1vZCA9IGdldE93bihyZWdpc3RyeSwgbm9ybWFsaXplZE1hcC5pZCk7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChub3JtYWxpemVkTW9kKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvL01hcmsgdGhpcyBhcyBhIGRlcGVuZGVuY3kgZm9yIHRoaXMgcGx1Z2luLCBzbyBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy9jYW4gYmUgdHJhY2VkIGZvciBjeWNsZXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmRlcE1hcHMucHVzaChub3JtYWxpemVkTWFwKTsKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAodGhpcy5ldmVudHMuZXJyb3IpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVkTW9kLm9uKCdlcnJvcicsIGJpbmQodGhpcywgZnVuY3Rpb24gKGVycikgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmVtaXQoJ2Vycm9yJywgZXJyKTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KSk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVkTW9kLmVuYWJsZSgpOwogICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAvL0lmIGEgcGF0aHMgY29uZmlnLCB0aGVuIGp1c3QgbG9hZCB0aGF0IGZpbGUgaW5zdGVhZCB0bwogICAgICAgICAgICAgICAgICAgIC8vcmVzb2x2ZSB0aGUgcGx1Z2luLCBhcyBpdCBpcyBidWlsdCBpbnRvIHRoYXQgcGF0aHMgbGF5ZXIuCiAgICAgICAgICAgICAgICAgICAgaWYgKGJ1bmRsZUlkKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMubWFwLnVybCA9IGNvbnRleHQubmFtZVRvVXJsKGJ1bmRsZUlkKTsKICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5sb2FkKCk7CiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgIGxvYWQgPSBiaW5kKHRoaXMsIGZ1bmN0aW9uICh2YWx1ZSkgewogICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmluaXQoW10sIGZ1bmN0aW9uICgpIHsgcmV0dXJuIHZhbHVlOyB9LCBudWxsLCB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmFibGVkOiB0cnVlCiAgICAgICAgICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgICAgIH0pOwoKICAgICAgICAgICAgICAgICAgICBsb2FkLmVycm9yID0gYmluZCh0aGlzLCBmdW5jdGlvbiAoZXJyKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuaW5pdGVkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lcnJvciA9IGVycjsKICAgICAgICAgICAgICAgICAgICAgICAgZXJyLnJlcXVpcmVNb2R1bGVzID0gW2lkXTsKCiAgICAgICAgICAgICAgICAgICAgICAgIC8vUmVtb3ZlIHRlbXAgdW5ub3JtYWxpemVkIG1vZHVsZXMgZm9yIHRoaXMgbW9kdWxlLAogICAgICAgICAgICAgICAgICAgICAgICAvL3NpbmNlIHRoZXkgd2lsbCBuZXZlciBiZSByZXNvbHZlZCBvdGhlcndpc2Ugbm93LgogICAgICAgICAgICAgICAgICAgICAgICBlYWNoUHJvcChyZWdpc3RyeSwgZnVuY3Rpb24gKG1vZCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG1vZC5tYXAuaWQuaW5kZXhPZihpZCArICdfdW5ub3JtYWxpemVkJykgPT09IDApIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGVhblJlZ2lzdHJ5KG1vZC5tYXAuaWQpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICB9KTsKCiAgICAgICAgICAgICAgICAgICAgICAgIG9uRXJyb3IoZXJyKTsKICAgICAgICAgICAgICAgICAgICB9KTsKCiAgICAgICAgICAgICAgICAgICAgLy9BbGxvdyBwbHVnaW5zIHRvIGxvYWQgb3RoZXIgY29kZSB3aXRob3V0IGhhdmluZyB0byBrbm93IHRoZQogICAgICAgICAgICAgICAgICAgIC8vY29udGV4dCBvciBob3cgdG8gJ2NvbXBsZXRlJyB0aGUgbG9hZC4KICAgICAgICAgICAgICAgICAgICBsb2FkLmZyb21UZXh0ID0gYmluZCh0aGlzLCBmdW5jdGlvbiAodGV4dCwgdGV4dEFsdCkgewogICAgICAgICAgICAgICAgICAgICAgICAvKmpzbGludCBldmlsOiB0cnVlICovCiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBtb2R1bGVOYW1lID0gbWFwLm5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2R1bGVNYXAgPSBtYWtlTW9kdWxlTWFwKG1vZHVsZU5hbWUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaGFzSW50ZXJhY3RpdmUgPSB1c2VJbnRlcmFjdGl2ZTsKCiAgICAgICAgICAgICAgICAgICAgICAgIC8vQXMgb2YgMi4xLjAsIHN1cHBvcnQganVzdCBwYXNzaW5nIHRoZSB0ZXh0LCB0byByZWluZm9yY2UKICAgICAgICAgICAgICAgICAgICAgICAgLy9mcm9tVGV4dCBvbmx5IGJlaW5nIGNhbGxlZCBvbmNlIHBlciByZXNvdXJjZS4gU3RpbGwKICAgICAgICAgICAgICAgICAgICAgICAgLy9zdXBwb3J0IG9sZCBzdHlsZSBvZiBwYXNzaW5nIG1vZHVsZU5hbWUgYnV0IGRpc2NhcmQKICAgICAgICAgICAgICAgICAgICAgICAgLy90aGF0IG1vZHVsZU5hbWUgaW4gZmF2b3Igb2YgdGhlIGludGVybmFsIHJlZi4KICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHRleHRBbHQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRleHQgPSB0ZXh0QWx0OwogICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICAvL1R1cm4gb2ZmIGludGVyYWN0aXZlIHNjcmlwdCBtYXRjaGluZyBmb3IgSUUgZm9yIGFueSBkZWZpbmUKICAgICAgICAgICAgICAgICAgICAgICAgLy9jYWxscyBpbiB0aGUgdGV4dCwgdGhlbiB0dXJuIGl0IGJhY2sgb24gYXQgdGhlIGVuZC4KICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGhhc0ludGVyYWN0aXZlKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2VJbnRlcmFjdGl2ZSA9IGZhbHNlOwogICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICAvL1ByaW1lIHRoZSBzeXN0ZW0gYnkgY3JlYXRpbmcgYSBtb2R1bGUgaW5zdGFuY2UgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgIC8vaXQuCiAgICAgICAgICAgICAgICAgICAgICAgIGdldE1vZHVsZShtb2R1bGVNYXApOwoKICAgICAgICAgICAgICAgICAgICAgICAgLy9UcmFuc2ZlciBhbnkgY29uZmlnIHRvIHRoaXMgb3RoZXIgbW9kdWxlLgogICAgICAgICAgICAgICAgICAgICAgICBpZiAoaGFzUHJvcChjb25maWcuY29uZmlnLCBpZCkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbmZpZy5jb25maWdbbW9kdWxlTmFtZV0gPSBjb25maWcuY29uZmlnW2lkXTsKICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcS5leGVjKHRleHQpOwogICAgICAgICAgICAgICAgICAgICAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gb25FcnJvcihtYWtlRXJyb3IoJ2Zyb210ZXh0ZXZhbCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdmcm9tVGV4dCBldmFsIGZvciAnICsgaWQgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcgZmFpbGVkOiAnICsgZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgW2lkXSkpOwogICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICBpZiAoaGFzSW50ZXJhY3RpdmUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZUludGVyYWN0aXZlID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgLy9NYXJrIHRoaXMgYXMgYSBkZXBlbmRlbmN5IGZvciB0aGUgcGx1Z2luCiAgICAgICAgICAgICAgICAgICAgICAgIC8vcmVzb3VyY2UKICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5kZXBNYXBzLnB1c2gobW9kdWxlTWFwKTsKCiAgICAgICAgICAgICAgICAgICAgICAgIC8vU3VwcG9ydCBhbm9ueW1vdXMgbW9kdWxlcy4KICAgICAgICAgICAgICAgICAgICAgICAgY29udGV4dC5jb21wbGV0ZUxvYWQobW9kdWxlTmFtZSk7CgogICAgICAgICAgICAgICAgICAgICAgICAvL0JpbmQgdGhlIHZhbHVlIG9mIHRoYXQgbW9kdWxlIHRvIHRoZSB2YWx1ZSBmb3IgdGhpcwogICAgICAgICAgICAgICAgICAgICAgICAvL3Jlc291cmNlIElELgogICAgICAgICAgICAgICAgICAgICAgICBsb2NhbFJlcXVpcmUoW21vZHVsZU5hbWVdLCBsb2FkKTsKICAgICAgICAgICAgICAgICAgICB9KTsKCiAgICAgICAgICAgICAgICAgICAgLy9Vc2UgcGFyZW50TmFtZSBoZXJlIHNpbmNlIHRoZSBwbHVnaW4ncyBuYW1lIGlzIG5vdCByZWxpYWJsZSwKICAgICAgICAgICAgICAgICAgICAvL2NvdWxkIGJlIHNvbWUgd2VpcmQgc3RyaW5nIHdpdGggbm8gcGF0aCB0aGF0IGFjdHVhbGx5IHdhbnRzIHRvCiAgICAgICAgICAgICAgICAgICAgLy9yZWZlcmVuY2UgdGhlIHBhcmVudE5hbWUncyBwYXRoLgogICAgICAgICAgICAgICAgICAgIHBsdWdpbi5sb2FkKG1hcC5uYW1lLCBsb2NhbFJlcXVpcmUsIGxvYWQsIGNvbmZpZyk7CiAgICAgICAgICAgICAgICB9KSk7CgogICAgICAgICAgICAgICAgY29udGV4dC5lbmFibGUocGx1Z2luTWFwLCB0aGlzKTsKICAgICAgICAgICAgICAgIHRoaXMucGx1Z2luTWFwc1twbHVnaW5NYXAuaWRdID0gcGx1Z2luTWFwOwogICAgICAgICAgICB9LAoKICAgICAgICAgICAgZW5hYmxlOiBmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgICAgICBlbmFibGVkUmVnaXN0cnlbdGhpcy5tYXAuaWRdID0gdGhpczsKICAgICAgICAgICAgICAgIHRoaXMuZW5hYmxlZCA9IHRydWU7CgogICAgICAgICAgICAgICAgLy9TZXQgZmxhZyBtZW50aW9uaW5nIHRoYXQgdGhlIG1vZHVsZSBpcyBlbmFibGluZywKICAgICAgICAgICAgICAgIC8vc28gdGhhdCBpbW1lZGlhdGUgY2FsbHMgdG8gdGhlIGRlZmluZWQgY2FsbGJhY2tzCiAgICAgICAgICAgICAgICAvL2ZvciBkZXBlbmRlbmNpZXMgZG8gbm90IHRyaWdnZXIgaW5hZHZlcnRlbnQgbG9hZAogICAgICAgICAgICAgICAgLy93aXRoIHRoZSBkZXBDb3VudCBzdGlsbCBiZWluZyB6ZXJvLgogICAgICAgICAgICAgICAgdGhpcy5lbmFibGluZyA9IHRydWU7CgogICAgICAgICAgICAgICAgLy9FbmFibGUgZWFjaCBkZXBlbmRlbmN5CiAgICAgICAgICAgICAgICBlYWNoKHRoaXMuZGVwTWFwcywgYmluZCh0aGlzLCBmdW5jdGlvbiAoZGVwTWFwLCBpKSB7CiAgICAgICAgICAgICAgICAgICAgdmFyIGlkLCBtb2QsIGhhbmRsZXI7CgogICAgICAgICAgICAgICAgICAgIGlmICh0eXBlb2YgZGVwTWFwID09PSAnc3RyaW5nJykgewogICAgICAgICAgICAgICAgICAgICAgICAvL0RlcGVuZGVuY3kgbmVlZHMgdG8gYmUgY29udmVydGVkIHRvIGEgZGVwTWFwCiAgICAgICAgICAgICAgICAgICAgICAgIC8vYW5kIHdpcmVkIHVwIHRvIHRoaXMgbW9kdWxlLgogICAgICAgICAgICAgICAgICAgICAgICBkZXBNYXAgPSBtYWtlTW9kdWxlTWFwKGRlcE1hcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAodGhpcy5tYXAuaXNEZWZpbmUgPyB0aGlzLm1hcCA6IHRoaXMubWFwLnBhcmVudE1hcCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFsc2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIXRoaXMuc2tpcE1hcCk7CiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuZGVwTWFwc1tpXSA9IGRlcE1hcDsKCiAgICAgICAgICAgICAgICAgICAgICAgIGhhbmRsZXIgPSBnZXRPd24oaGFuZGxlcnMsIGRlcE1hcC5pZCk7CgogICAgICAgICAgICAgICAgICAgICAgICBpZiAoaGFuZGxlcikgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5kZXBFeHBvcnRzW2ldID0gaGFuZGxlcih0aGlzKTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5kZXBDb3VudCArPSAxOwoKICAgICAgICAgICAgICAgICAgICAgICAgb24oZGVwTWFwLCAnZGVmaW5lZCcsIGJpbmQodGhpcywgZnVuY3Rpb24gKGRlcEV4cG9ydHMpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICh0aGlzLnVuZGVmZWQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmRlZmluZURlcChpLCBkZXBFeHBvcnRzKTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuY2hlY2soKTsKICAgICAgICAgICAgICAgICAgICAgICAgfSkpOwoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXMuZXJyYmFjaykgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgb24oZGVwTWFwLCAnZXJyb3InLCBiaW5kKHRoaXMsIHRoaXMuZXJyYmFjaykpOwogICAgICAgICAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKHRoaXMuZXZlbnRzLmVycm9yKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBObyBkaXJlY3QgZXJyYmFjayBvbiB0aGlzIG1vZHVsZSwgYnV0IHNvbWV0aGluZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gZWxzZSBpcyBsaXN0ZW5pbmcgZm9yIGVycm9ycywgc28gYmUgc3VyZSB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gcHJvcGFnYXRlIHRoZSBlcnJvciBjb3JyZWN0bHkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbihkZXBNYXAsICdlcnJvcicsIGJpbmQodGhpcywgZnVuY3Rpb24oZXJyKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbWl0KCdlcnJvcicsIGVycik7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KSk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgIGlkID0gZGVwTWFwLmlkOwogICAgICAgICAgICAgICAgICAgIG1vZCA9IHJlZ2lzdHJ5W2lkXTsKCiAgICAgICAgICAgICAgICAgICAgLy9Ta2lwIHNwZWNpYWwgbW9kdWxlcyBsaWtlICdyZXF1aXJlJywgJ2V4cG9ydHMnLCAnbW9kdWxlJwogICAgICAgICAgICAgICAgICAgIC8vQWxzbywgZG9uJ3QgY2FsbCBlbmFibGUgaWYgaXQgaXMgYWxyZWFkeSBlbmFibGVkLAogICAgICAgICAgICAgICAgICAgIC8vaW1wb3J0YW50IGluIGNpcmN1bGFyIGRlcGVuZGVuY3kgY2FzZXMuCiAgICAgICAgICAgICAgICAgICAgaWYgKCFoYXNQcm9wKGhhbmRsZXJzLCBpZCkgJiYgbW9kICYmICFtb2QuZW5hYmxlZCkgewogICAgICAgICAgICAgICAgICAgICAgICBjb250ZXh0LmVuYWJsZShkZXBNYXAsIHRoaXMpOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0pKTsKCiAgICAgICAgICAgICAgICAvL0VuYWJsZSBlYWNoIHBsdWdpbiB0aGF0IGlzIHVzZWQgaW4KICAgICAgICAgICAgICAgIC8vYSBkZXBlbmRlbmN5CiAgICAgICAgICAgICAgICBlYWNoUHJvcCh0aGlzLnBsdWdpbk1hcHMsIGJpbmQodGhpcywgZnVuY3Rpb24gKHBsdWdpbk1hcCkgewogICAgICAgICAgICAgICAgICAgIHZhciBtb2QgPSBnZXRPd24ocmVnaXN0cnksIHBsdWdpbk1hcC5pZCk7CiAgICAgICAgICAgICAgICAgICAgaWYgKG1vZCAmJiAhbW9kLmVuYWJsZWQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgY29udGV4dC5lbmFibGUocGx1Z2luTWFwLCB0aGlzKTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9KSk7CgogICAgICAgICAgICAgICAgdGhpcy5lbmFibGluZyA9IGZhbHNlOwoKICAgICAgICAgICAgICAgIHRoaXMuY2hlY2soKTsKICAgICAgICAgICAgfSwKCiAgICAgICAgICAgIG9uOiBmdW5jdGlvbiAobmFtZSwgY2IpIHsKICAgICAgICAgICAgICAgIHZhciBjYnMgPSB0aGlzLmV2ZW50c1tuYW1lXTsKICAgICAgICAgICAgICAgIGlmICghY2JzKSB7CiAgICAgICAgICAgICAgICAgICAgY2JzID0gdGhpcy5ldmVudHNbbmFtZV0gPSBbXTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGNicy5wdXNoKGNiKTsKICAgICAgICAgICAgfSwKCiAgICAgICAgICAgIGVtaXQ6IGZ1bmN0aW9uIChuYW1lLCBldnQpIHsKICAgICAgICAgICAgICAgIGVhY2godGhpcy5ldmVudHNbbmFtZV0sIGZ1bmN0aW9uIChjYikgewogICAgICAgICAgICAgICAgICAgIGNiKGV2dCk7CiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIGlmIChuYW1lID09PSAnZXJyb3InKSB7CiAgICAgICAgICAgICAgICAgICAgLy9Ob3cgdGhhdCB0aGUgZXJyb3IgaGFuZGxlciB3YXMgdHJpZ2dlcmVkLCByZW1vdmUKICAgICAgICAgICAgICAgICAgICAvL3RoZSBsaXN0ZW5lcnMsIHNpbmNlIHRoaXMgYnJva2VuIE1vZHVsZSBpbnN0YW5jZQogICAgICAgICAgICAgICAgICAgIC8vY2FuIHN0YXkgYXJvdW5kIGZvciBhIHdoaWxlIGluIHRoZSByZWdpc3RyeS4KICAgICAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5ldmVudHNbbmFtZV07CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9OwoKICAgICAgICBmdW5jdGlvbiBjYWxsR2V0TW9kdWxlKGFyZ3MpIHsKICAgICAgICAgICAgLy9Ta2lwIG1vZHVsZXMgYWxyZWFkeSBkZWZpbmVkLgogICAgICAgICAgICBpZiAoIWhhc1Byb3AoZGVmaW5lZCwgYXJnc1swXSkpIHsKICAgICAgICAgICAgICAgIGdldE1vZHVsZShtYWtlTW9kdWxlTWFwKGFyZ3NbMF0sIG51bGwsIHRydWUpKS5pbml0KGFyZ3NbMV0sIGFyZ3NbMl0pOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBmdW5jdGlvbiByZW1vdmVMaXN0ZW5lcihub2RlLCBmdW5jLCBuYW1lLCBpZU5hbWUpIHsKICAgICAgICAgICAgLy9GYXZvciBkZXRhY2hFdmVudCBiZWNhdXNlIG9mIElFOQogICAgICAgICAgICAvL2lzc3VlLCBzZWUgYXR0YWNoRXZlbnQvYWRkRXZlbnRMaXN0ZW5lciBjb21tZW50IGVsc2V3aGVyZQogICAgICAgICAgICAvL2luIHRoaXMgZmlsZS4KICAgICAgICAgICAgaWYgKG5vZGUuZGV0YWNoRXZlbnQgJiYgIWlzT3BlcmEpIHsKICAgICAgICAgICAgICAgIC8vUHJvYmFibHkgSUUuIElmIG5vdCBpdCB3aWxsIHRocm93IGFuIGVycm9yLCB3aGljaCB3aWxsIGJlCiAgICAgICAgICAgICAgICAvL3VzZWZ1bCB0byBrbm93LgogICAgICAgICAgICAgICAgaWYgKGllTmFtZSkgewogICAgICAgICAgICAgICAgICAgIG5vZGUuZGV0YWNoRXZlbnQoaWVOYW1lLCBmdW5jKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIG5vZGUucmVtb3ZlRXZlbnRMaXN0ZW5lcihuYW1lLCBmdW5jLCBmYWxzZSk7CiAgICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIC8qKgogICAgICAgICAqIEdpdmVuIGFuIGV2ZW50IGZyb20gYSBzY3JpcHQgbm9kZSwgZ2V0IHRoZSByZXF1aXJlanMgaW5mbyBmcm9tIGl0LAogICAgICAgICAqIGFuZCB0aGVuIHJlbW92ZXMgdGhlIGV2ZW50IGxpc3RlbmVycyBvbiB0aGUgbm9kZS4KICAgICAgICAgKiBAcGFyYW0ge0V2ZW50fSBldnQKICAgICAgICAgKiBAcmV0dXJucyB7T2JqZWN0fQogICAgICAgICAqLwogICAgICAgIGZ1bmN0aW9uIGdldFNjcmlwdERhdGEoZXZ0KSB7CiAgICAgICAgICAgIC8vVXNpbmcgY3VycmVudFRhcmdldCBpbnN0ZWFkIG9mIHRhcmdldCBmb3IgRmlyZWZveCAyLjAncyBzYWtlLiBOb3QKICAgICAgICAgICAgLy9hbGwgb2xkIGJyb3dzZXJzIHdpbGwgYmUgc3VwcG9ydGVkLCBidXQgdGhpcyBvbmUgd2FzIGVhc3kgZW5vdWdoCiAgICAgICAgICAgIC8vdG8gc3VwcG9ydCBhbmQgc3RpbGwgbWFrZXMgc2Vuc2UuCiAgICAgICAgICAgIHZhciBub2RlID0gZXZ0LmN1cnJlbnRUYXJnZXQgfHwgZXZ0LnNyY0VsZW1lbnQ7CgogICAgICAgICAgICAvL1JlbW92ZSB0aGUgbGlzdGVuZXJzIG9uY2UgaGVyZS4KICAgICAgICAgICAgcmVtb3ZlTGlzdGVuZXIobm9kZSwgY29udGV4dC5vblNjcmlwdExvYWQsICdsb2FkJywgJ29ucmVhZHlzdGF0ZWNoYW5nZScpOwogICAgICAgICAgICByZW1vdmVMaXN0ZW5lcihub2RlLCBjb250ZXh0Lm9uU2NyaXB0RXJyb3IsICdlcnJvcicpOwoKICAgICAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgICAgIG5vZGU6IG5vZGUsCiAgICAgICAgICAgICAgICBpZDogbm9kZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgnZGF0YS1yZXF1aXJlbW9kdWxlJykKICAgICAgICAgICAgfTsKICAgICAgICB9CgogICAgICAgIGZ1bmN0aW9uIGludGFrZURlZmluZXMoKSB7CiAgICAgICAgICAgIHZhciBhcmdzOwoKICAgICAgICAgICAgLy9BbnkgZGVmaW5lZCBtb2R1bGVzIGluIHRoZSBnbG9iYWwgcXVldWUsIGludGFrZSB0aGVtIG5vdy4KICAgICAgICAgICAgdGFrZUdsb2JhbFF1ZXVlKCk7CgogICAgICAgICAgICAvL01ha2Ugc3VyZSBhbnkgcmVtYWluaW5nIGRlZlF1ZXVlIGl0ZW1zIGdldCBwcm9wZXJseSBwcm9jZXNzZWQuCiAgICAgICAgICAgIHdoaWxlIChkZWZRdWV1ZS5sZW5ndGgpIHsKICAgICAgICAgICAgICAgIGFyZ3MgPSBkZWZRdWV1ZS5zaGlmdCgpOwogICAgICAgICAgICAgICAgaWYgKGFyZ3NbMF0gPT09IG51bGwpIHsKICAgICAgICAgICAgICAgICAgICByZXR1cm4gb25FcnJvcihtYWtlRXJyb3IoJ21pc21hdGNoJywgJ01pc21hdGNoZWQgYW5vbnltb3VzIGRlZmluZSgpIG1vZHVsZTogJyArCiAgICAgICAgICAgICAgICAgICAgICAgIGFyZ3NbYXJncy5sZW5ndGggLSAxXSkpOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAvL2FyZ3MgYXJlIGlkLCBkZXBzLCBmYWN0b3J5LiBTaG91bGQgYmUgbm9ybWFsaXplZCBieSB0aGUKICAgICAgICAgICAgICAgICAgICAvL2RlZmluZSgpIGZ1bmN0aW9uLgogICAgICAgICAgICAgICAgICAgIGNhbGxHZXRNb2R1bGUoYXJncyk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY29udGV4dC5kZWZRdWV1ZU1hcCA9IHt9OwogICAgICAgIH0KCiAgICAgICAgY29udGV4dCA9IHsKICAgICAgICAgICAgY29uZmlnOiBjb25maWcsCiAgICAgICAgICAgIGNvbnRleHROYW1lOiBjb250ZXh0TmFtZSwKICAgICAgICAgICAgcmVnaXN0cnk6IHJlZ2lzdHJ5LAogICAgICAgICAgICBkZWZpbmVkOiBkZWZpbmVkLAogICAgICAgICAgICB1cmxGZXRjaGVkOiB1cmxGZXRjaGVkLAogICAgICAgICAgICBkZWZRdWV1ZTogZGVmUXVldWUsCiAgICAgICAgICAgIGRlZlF1ZXVlTWFwOiB7fSwKICAgICAgICAgICAgTW9kdWxlOiBNb2R1bGUsCiAgICAgICAgICAgIG1ha2VNb2R1bGVNYXA6IG1ha2VNb2R1bGVNYXAsCiAgICAgICAgICAgIG5leHRUaWNrOiByZXEubmV4dFRpY2ssCiAgICAgICAgICAgIG9uRXJyb3I6IG9uRXJyb3IsCgogICAgICAgICAgICAvKioKICAgICAgICAgICAgICogU2V0IGEgY29uZmlndXJhdGlvbiBmb3IgdGhlIGNvbnRleHQuCiAgICAgICAgICAgICAqIEBwYXJhbSB7T2JqZWN0fSBjZmcgY29uZmlnIG9iamVjdCB0byBpbnRlZ3JhdGUuCiAgICAgICAgICAgICAqLwogICAgICAgICAgICBjb25maWd1cmU6IGZ1bmN0aW9uIChjZmcpIHsKICAgICAgICAgICAgICAgIC8vTWFrZSBzdXJlIHRoZSBiYXNlVXJsIGVuZHMgaW4gYSBzbGFzaC4KICAgICAgICAgICAgICAgIGlmIChjZmcuYmFzZVVybCkgewogICAgICAgICAgICAgICAgICAgIGlmIChjZmcuYmFzZVVybC5jaGFyQXQoY2ZnLmJhc2VVcmwubGVuZ3RoIC0gMSkgIT09ICcvJykgewogICAgICAgICAgICAgICAgICAgICAgICBjZmcuYmFzZVVybCArPSAnLyc7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIC8vIENvbnZlcnQgb2xkIHN0eWxlIHVybEFyZ3Mgc3RyaW5nIHRvIGEgZnVuY3Rpb24uCiAgICAgICAgICAgICAgICBpZiAodHlwZW9mIGNmZy51cmxBcmdzID09PSAnc3RyaW5nJykgewogICAgICAgICAgICAgICAgICAgIHZhciB1cmxBcmdzID0gY2ZnLnVybEFyZ3M7CiAgICAgICAgICAgICAgICAgICAgY2ZnLnVybEFyZ3MgPSBmdW5jdGlvbihpZCwgdXJsKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiAodXJsLmluZGV4T2YoJz8nKSA9PT0gLTEgPyAnPycgOiAnJicpICsgdXJsQXJnczsKICAgICAgICAgICAgICAgICAgICB9OwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIC8vU2F2ZSBvZmYgdGhlIHBhdGhzIHNpbmNlIHRoZXkgcmVxdWlyZSBzcGVjaWFsIHByb2Nlc3NpbmcsCiAgICAgICAgICAgICAgICAvL3RoZXkgYXJlIGFkZGl0aXZlLgogICAgICAgICAgICAgICAgdmFyIHNoaW0gPSBjb25maWcuc2hpbSwKICAgICAgICAgICAgICAgICAgICBvYmpzID0gewogICAgICAgICAgICAgICAgICAgICAgICBwYXRoczogdHJ1ZSwKICAgICAgICAgICAgICAgICAgICAgICAgYnVuZGxlczogdHJ1ZSwKICAgICAgICAgICAgICAgICAgICAgICAgY29uZmlnOiB0cnVlLAogICAgICAgICAgICAgICAgICAgICAgICBtYXA6IHRydWUKICAgICAgICAgICAgICAgICAgICB9OwoKICAgICAgICAgICAgICAgIGVhY2hQcm9wKGNmZywgZnVuY3Rpb24gKHZhbHVlLCBwcm9wKSB7CiAgICAgICAgICAgICAgICAgICAgaWYgKG9ianNbcHJvcF0pIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFjb25maWdbcHJvcF0pIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbmZpZ1twcm9wXSA9IHt9OwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgIG1peGluKGNvbmZpZ1twcm9wXSwgdmFsdWUsIHRydWUsIHRydWUpOwogICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGNvbmZpZ1twcm9wXSA9IHZhbHVlOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0pOwoKICAgICAgICAgICAgICAgIC8vUmV2ZXJzZSBtYXAgdGhlIGJ1bmRsZXMKICAgICAgICAgICAgICAgIGlmIChjZmcuYnVuZGxlcykgewogICAgICAgICAgICAgICAgICAgIGVhY2hQcm9wKGNmZy5idW5kbGVzLCBmdW5jdGlvbiAodmFsdWUsIHByb3ApIHsKICAgICAgICAgICAgICAgICAgICAgICAgZWFjaCh2YWx1ZSwgZnVuY3Rpb24gKHYpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICh2ICE9PSBwcm9wKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnVuZGxlc01hcFt2XSA9IHByb3A7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIC8vTWVyZ2Ugc2hpbQogICAgICAgICAgICAgICAgaWYgKGNmZy5zaGltKSB7CiAgICAgICAgICAgICAgICAgICAgZWFjaFByb3AoY2ZnLnNoaW0sIGZ1bmN0aW9uICh2YWx1ZSwgaWQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgLy9Ob3JtYWxpemUgdGhlIHN0cnVjdHVyZQogICAgICAgICAgICAgICAgICAgICAgICBpZiAoaXNBcnJheSh2YWx1ZSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcHM6IHZhbHVlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9OwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgIGlmICgodmFsdWUuZXhwb3J0cyB8fCB2YWx1ZS5pbml0KSAmJiAhdmFsdWUuZXhwb3J0c0ZuKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZS5leHBvcnRzRm4gPSBjb250ZXh0Lm1ha2VTaGltRXhwb3J0cyh2YWx1ZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgc2hpbVtpZF0gPSB2YWx1ZTsKICAgICAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgICAgICBjb25maWcuc2hpbSA9IHNoaW07CiAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgLy9BZGp1c3QgcGFja2FnZXMgaWYgbmVjZXNzYXJ5LgogICAgICAgICAgICAgICAgaWYgKGNmZy5wYWNrYWdlcykgewogICAgICAgICAgICAgICAgICAgIGVhY2goY2ZnLnBhY2thZ2VzLCBmdW5jdGlvbiAocGtnT2JqKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBsb2NhdGlvbiwgbmFtZTsKCiAgICAgICAgICAgICAgICAgICAgICAgIHBrZ09iaiA9IHR5cGVvZiBwa2dPYmogPT09ICdzdHJpbmcnID8ge25hbWU6IHBrZ09ian0gOiBwa2dPYmo7CgogICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gcGtnT2JqLm5hbWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGxvY2F0aW9uID0gcGtnT2JqLmxvY2F0aW9uOwogICAgICAgICAgICAgICAgICAgICAgICBpZiAobG9jYXRpb24pIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbmZpZy5wYXRoc1tuYW1lXSA9IHBrZ09iai5sb2NhdGlvbjsKICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgLy9TYXZlIHBvaW50ZXIgdG8gbWFpbiBtb2R1bGUgSUQgZm9yIHBrZyBuYW1lLgogICAgICAgICAgICAgICAgICAgICAgICAvL1JlbW92ZSBsZWFkaW5nIGRvdCBpbiBtYWluLCBzbyBtYWluIHBhdGhzIGFyZSBub3JtYWxpemVkLAogICAgICAgICAgICAgICAgICAgICAgICAvL2FuZCByZW1vdmUgYW55IHRyYWlsaW5nIC5qcywgc2luY2UgZGlmZmVyZW50IHBhY2thZ2UKICAgICAgICAgICAgICAgICAgICAgICAgLy9lbnZzIGhhdmUgZGlmZmVyZW50IGNvbnZlbnRpb25zOiBzb21lIHVzZSBhIG1vZHVsZSBuYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAvL3NvbWUgdXNlIGEgZmlsZSBuYW1lLgogICAgICAgICAgICAgICAgICAgICAgICBjb25maWcucGtnc1tuYW1lXSA9IHBrZ09iai5uYW1lICsgJy8nICsgKHBrZ09iai5tYWluIHx8ICdtYWluJykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5yZXBsYWNlKGN1cnJEaXJSZWdFeHAsICcnKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnJlcGxhY2UoanNTdWZmaXhSZWdFeHAsICcnKTsKICAgICAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAvL0lmIHRoZXJlIGFyZSBhbnkgIndhaXRpbmcgdG8gZXhlY3V0ZSIgbW9kdWxlcyBpbiB0aGUgcmVnaXN0cnksCiAgICAgICAgICAgICAgICAvL3VwZGF0ZSB0aGUgbWFwcyBmb3IgdGhlbSwgc2luY2UgdGhlaXIgaW5mbywgbGlrZSBVUkxzIHRvIGxvYWQsCiAgICAgICAgICAgICAgICAvL21heSBoYXZlIGNoYW5nZWQuCiAgICAgICAgICAgICAgICBlYWNoUHJvcChyZWdpc3RyeSwgZnVuY3Rpb24gKG1vZCwgaWQpIHsKICAgICAgICAgICAgICAgICAgICAvL0lmIG1vZHVsZSBhbHJlYWR5IGhhcyBpbml0IGNhbGxlZCwgc2luY2UgaXQgaXMgdG9vCiAgICAgICAgICAgICAgICAgICAgLy9sYXRlIHRvIG1vZGlmeSB0aGVtLCBhbmQgaWdub3JlIHVubm9ybWFsaXplZCBvbmVzCiAgICAgICAgICAgICAgICAgICAgLy9zaW5jZSB0aGV5IGFyZSB0cmFuc2llbnQuCiAgICAgICAgICAgICAgICAgICAgaWYgKCFtb2QuaW5pdGVkICYmICFtb2QubWFwLnVubm9ybWFsaXplZCkgewogICAgICAgICAgICAgICAgICAgICAgICBtb2QubWFwID0gbWFrZU1vZHVsZU1hcChpZCwgbnVsbCwgdHJ1ZSk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfSk7CgogICAgICAgICAgICAgICAgLy9JZiBhIGRlcHMgYXJyYXkgb3IgYSBjb25maWcgY2FsbGJhY2sgaXMgc3BlY2lmaWVkLCB0aGVuIGNhbGwKICAgICAgICAgICAgICAgIC8vcmVxdWlyZSB3aXRoIHRob3NlIGFyZ3MuIFRoaXMgaXMgdXNlZnVsIHdoZW4gcmVxdWlyZSBpcyBkZWZpbmVkIGFzIGEKICAgICAgICAgICAgICAgIC8vY29uZmlnIG9iamVjdCBiZWZvcmUgcmVxdWlyZS5qcyBpcyBsb2FkZWQuCiAgICAgICAgICAgICAgICBpZiAoY2ZnLmRlcHMgfHwgY2ZnLmNhbGxiYWNrKSB7CiAgICAgICAgICAgICAgICAgICAgY29udGV4dC5yZXF1aXJlKGNmZy5kZXBzIHx8IFtdLCBjZmcuY2FsbGJhY2spOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAoKICAgICAgICAgICAgbWFrZVNoaW1FeHBvcnRzOiBmdW5jdGlvbiAodmFsdWUpIHsKICAgICAgICAgICAgICAgIGZ1bmN0aW9uIGZuKCkgewogICAgICAgICAgICAgICAgICAgIHZhciByZXQ7CiAgICAgICAgICAgICAgICAgICAgaWYgKHZhbHVlLmluaXQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgcmV0ID0gdmFsdWUuaW5pdC5hcHBseShnbG9iYWwsIGFyZ3VtZW50cyk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIHJldHVybiByZXQgfHwgKHZhbHVlLmV4cG9ydHMgJiYgZ2V0R2xvYmFsKHZhbHVlLmV4cG9ydHMpKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIHJldHVybiBmbjsKICAgICAgICAgICAgfSwKCiAgICAgICAgICAgIG1ha2VSZXF1aXJlOiBmdW5jdGlvbiAocmVsTWFwLCBvcHRpb25zKSB7CiAgICAgICAgICAgICAgICBvcHRpb25zID0gb3B0aW9ucyB8fCB7fTsKCiAgICAgICAgICAgICAgICBmdW5jdGlvbiBsb2NhbFJlcXVpcmUoZGVwcywgY2FsbGJhY2ssIGVycmJhY2spIHsKICAgICAgICAgICAgICAgICAgICB2YXIgaWQsIG1hcCwgcmVxdWlyZU1vZDsKCiAgICAgICAgICAgICAgICAgICAgaWYgKG9wdGlvbnMuZW5hYmxlQnVpbGRDYWxsYmFjayAmJiBjYWxsYmFjayAmJiBpc0Z1bmN0aW9uKGNhbGxiYWNrKSkgewogICAgICAgICAgICAgICAgICAgICAgICBjYWxsYmFjay5fX3JlcXVpcmVKc0J1aWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgIGlmICh0eXBlb2YgZGVwcyA9PT0gJ3N0cmluZycpIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24oY2FsbGJhY2spKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvL0ludmFsaWQgY2FsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG9uRXJyb3IobWFrZUVycm9yKCdyZXF1aXJlYXJncycsICdJbnZhbGlkIHJlcXVpcmUgY2FsbCcpLCBlcnJiYWNrKTsKICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgLy9JZiByZXF1aXJlfGV4cG9ydHN8bW9kdWxlIGFyZSByZXF1ZXN0ZWQsIGdldCB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgLy92YWx1ZSBmb3IgdGhlbSBmcm9tIHRoZSBzcGVjaWFsIGhhbmRsZXJzLiBDYXZlYXQ6CiAgICAgICAgICAgICAgICAgICAgICAgIC8vdGhpcyBvbmx5IHdvcmtzIHdoaWxlIG1vZHVsZSBpcyBiZWluZyBkZWZpbmVkLgogICAgICAgICAgICAgICAgICAgICAgICBpZiAocmVsTWFwICYmIGhhc1Byb3AoaGFuZGxlcnMsIGRlcHMpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gaGFuZGxlcnNbZGVwc10ocmVnaXN0cnlbcmVsTWFwLmlkXSk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIC8vU3luY2hyb25vdXMgYWNjZXNzIHRvIG9uZSBtb2R1bGUuIElmIHJlcXVpcmUuZ2V0IGlzCiAgICAgICAgICAgICAgICAgICAgICAgIC8vYXZhaWxhYmxlIChhcyBpbiB0aGUgTm9kZSBhZGFwdGVyKSwgcHJlZmVyIHRoYXQuCiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChyZXEuZ2V0KSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVxLmdldChjb250ZXh0LCBkZXBzLCByZWxNYXAsIGxvY2FsUmVxdWlyZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIC8vTm9ybWFsaXplIG1vZHVsZSBuYW1lLCBpZiBpdCBjb250YWlucyAuIG9yIC4uCiAgICAgICAgICAgICAgICAgICAgICAgIG1hcCA9IG1ha2VNb2R1bGVNYXAoZGVwcywgcmVsTWFwLCBmYWxzZSwgdHJ1ZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gbWFwLmlkOwoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFoYXNQcm9wKGRlZmluZWQsIGlkKSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG9uRXJyb3IobWFrZUVycm9yKCdub3Rsb2FkZWQnLCAnTW9kdWxlIG5hbWUgIicgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJyIgaGFzIG5vdCBiZWVuIGxvYWRlZCB5ZXQgZm9yIGNvbnRleHQ6ICcgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGV4dE5hbWUgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKHJlbE1hcCA/ICcnIDogJy4gVXNlIHJlcXVpcmUoW10pJykpKTsKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gZGVmaW5lZFtpZF07CiAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAvL0dyYWIgZGVmaW5lcyB3YWl0aW5nIGluIHRoZSBnbG9iYWwgcXVldWUuCiAgICAgICAgICAgICAgICAgICAgaW50YWtlRGVmaW5lcygpOwoKICAgICAgICAgICAgICAgICAgICAvL01hcmsgYWxsIHRoZSBkZXBlbmRlbmNpZXMgYXMgbmVlZGluZyB0byBiZSBsb2FkZWQuCiAgICAgICAgICAgICAgICAgICAgY29udGV4dC5uZXh0VGljayhmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIC8vU29tZSBkZWZpbmVzIGNvdWxkIGhhdmUgYmVlbiBhZGRlZCBzaW5jZSB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgLy9yZXF1aXJlIGNhbGwsIGNvbGxlY3QgdGhlbS4KICAgICAgICAgICAgICAgICAgICAgICAgaW50YWtlRGVmaW5lcygpOwoKICAgICAgICAgICAgICAgICAgICAgICAgcmVxdWlyZU1vZCA9IGdldE1vZHVsZShtYWtlTW9kdWxlTWFwKG51bGwsIHJlbE1hcCkpOwoKICAgICAgICAgICAgICAgICAgICAgICAgLy9TdG9yZSBpZiBtYXAgY29uZmlnIHNob3VsZCBiZSBhcHBsaWVkIHRvIHRoaXMgcmVxdWlyZQogICAgICAgICAgICAgICAgICAgICAgICAvL2NhbGwgZm9yIGRlcGVuZGVuY2llcy4KICAgICAgICAgICAgICAgICAgICAgICAgcmVxdWlyZU1vZC5za2lwTWFwID0gb3B0aW9ucy5za2lwTWFwOwoKICAgICAgICAgICAgICAgICAgICAgICAgcmVxdWlyZU1vZC5pbml0KGRlcHMsIGNhbGxiYWNrLCBlcnJiYWNrLCB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmFibGVkOiB0cnVlCiAgICAgICAgICAgICAgICAgICAgICAgIH0pOwoKICAgICAgICAgICAgICAgICAgICAgICAgY2hlY2tMb2FkZWQoKTsKICAgICAgICAgICAgICAgICAgICB9KTsKCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGxvY2FsUmVxdWlyZTsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICBtaXhpbihsb2NhbFJlcXVpcmUsIHsKICAgICAgICAgICAgICAgICAgICBpc0Jyb3dzZXI6IGlzQnJvd3NlciwKCiAgICAgICAgICAgICAgICAgICAgLyoqCiAgICAgICAgICAgICAgICAgICAgICogQ29udmVydHMgYSBtb2R1bGUgbmFtZSArIC5leHRlbnNpb24gaW50byBhbiBVUkwgcGF0aC4KICAgICAgICAgICAgICAgICAgICAgKiAqUmVxdWlyZXMqIHRoZSB1c2Ugb2YgYSBtb2R1bGUgbmFtZS4gSXQgZG9lcyBub3Qgc3VwcG9ydCB1c2luZwogICAgICAgICAgICAgICAgICAgICAqIHBsYWluIFVSTHMgbGlrZSBuYW1lVG9VcmwuCiAgICAgICAgICAgICAgICAgICAgICovCiAgICAgICAgICAgICAgICAgICAgdG9Vcmw6IGZ1bmN0aW9uIChtb2R1bGVOYW1lUGx1c0V4dCkgewogICAgICAgICAgICAgICAgICAgICAgICB2YXIgZXh0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXggPSBtb2R1bGVOYW1lUGx1c0V4dC5sYXN0SW5kZXhPZignLicpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VnbWVudCA9IG1vZHVsZU5hbWVQbHVzRXh0LnNwbGl0KCcvJylbMF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpc1JlbGF0aXZlID0gc2VnbWVudCA9PT0gJy4nIHx8IHNlZ21lbnQgPT09ICcuLic7CgogICAgICAgICAgICAgICAgICAgICAgICAvL0hhdmUgYSBmaWxlIGV4dGVuc2lvbiBhbGlhcywgYW5kIGl0IGlzIG5vdCB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgLy9kb3RzIGZyb20gYSByZWxhdGl2ZSBwYXRoLgogICAgICAgICAgICAgICAgICAgICAgICBpZiAoaW5kZXggIT09IC0xICYmICghaXNSZWxhdGl2ZSB8fCBpbmRleCA+IDEpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBleHQgPSBtb2R1bGVOYW1lUGx1c0V4dC5zdWJzdHJpbmcoaW5kZXgsIG1vZHVsZU5hbWVQbHVzRXh0Lmxlbmd0aCk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2R1bGVOYW1lUGx1c0V4dCA9IG1vZHVsZU5hbWVQbHVzRXh0LnN1YnN0cmluZygwLCBpbmRleCk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0Lm5hbWVUb1VybChub3JtYWxpemUobW9kdWxlTmFtZVBsdXNFeHQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbE1hcCAmJiByZWxNYXAuaWQsIHRydWUpLCBleHQsICB0cnVlKTsKICAgICAgICAgICAgICAgICAgICB9LAoKICAgICAgICAgICAgICAgICAgICBkZWZpbmVkOiBmdW5jdGlvbiAoaWQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGhhc1Byb3AoZGVmaW5lZCwgbWFrZU1vZHVsZU1hcChpZCwgcmVsTWFwLCBmYWxzZSwgdHJ1ZSkuaWQpOwogICAgICAgICAgICAgICAgICAgIH0sCgogICAgICAgICAgICAgICAgICAgIHNwZWNpZmllZDogZnVuY3Rpb24gKGlkKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gbWFrZU1vZHVsZU1hcChpZCwgcmVsTWFwLCBmYWxzZSwgdHJ1ZSkuaWQ7CiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBoYXNQcm9wKGRlZmluZWQsIGlkKSB8fCBoYXNQcm9wKHJlZ2lzdHJ5LCBpZCk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfSk7CgogICAgICAgICAgICAgICAgLy9Pbmx5IGFsbG93IHVuZGVmIG9uIHRvcCBsZXZlbCByZXF1aXJlIGNhbGxzCiAgICAgICAgICAgICAgICBpZiAoIXJlbE1hcCkgewogICAgICAgICAgICAgICAgICAgIGxvY2FsUmVxdWlyZS51bmRlZiA9IGZ1bmN0aW9uIChpZCkgewogICAgICAgICAgICAgICAgICAgICAgICAvL0JpbmQgYW55IHdhaXRpbmcgZGVmaW5lKCkgY2FsbHMgdG8gdGhpcyBjb250ZXh0LAogICAgICAgICAgICAgICAgICAgICAgICAvL2ZpeCBmb3IgIzQwOAogICAgICAgICAgICAgICAgICAgICAgICB0YWtlR2xvYmFsUXVldWUoKTsKCiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBtYXAgPSBtYWtlTW9kdWxlTWFwKGlkLCByZWxNYXAsIHRydWUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kID0gZ2V0T3duKHJlZ2lzdHJ5LCBpZCk7CgogICAgICAgICAgICAgICAgICAgICAgICBtb2QudW5kZWZlZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZVNjcmlwdChpZCk7CgogICAgICAgICAgICAgICAgICAgICAgICBkZWxldGUgZGVmaW5lZFtpZF07CiAgICAgICAgICAgICAgICAgICAgICAgIGRlbGV0ZSB1cmxGZXRjaGVkW21hcC51cmxdOwogICAgICAgICAgICAgICAgICAgICAgICBkZWxldGUgdW5kZWZFdmVudHNbaWRdOwoKICAgICAgICAgICAgICAgICAgICAgICAgLy9DbGVhbiBxdWV1ZWQgZGVmaW5lcyB0b28uIEdvIGJhY2t3YXJkcwogICAgICAgICAgICAgICAgICAgICAgICAvL2luIGFycmF5IHNvIHRoYXQgdGhlIHNwbGljZXMgZG8gbm90CiAgICAgICAgICAgICAgICAgICAgICAgIC8vbWVzcyB1cCB0aGUgaXRlcmF0aW9uLgogICAgICAgICAgICAgICAgICAgICAgICBlYWNoUmV2ZXJzZShkZWZRdWV1ZSwgZnVuY3Rpb24oYXJncywgaSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGFyZ3NbMF0gPT09IGlkKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVmUXVldWUuc3BsaWNlKGksIDEpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgICAgICAgICAgZGVsZXRlIGNvbnRleHQuZGVmUXVldWVNYXBbaWRdOwoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG1vZCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy9Ib2xkIG9uIHRvIGxpc3RlbmVycyBpbiBjYXNlIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy9tb2R1bGUgd2lsbCBiZSBhdHRlbXB0ZWQgdG8gYmUgcmVsb2FkZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vdXNpbmcgYSBkaWZmZXJlbnQgY29uZmlnLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG1vZC5ldmVudHMuZGVmaW5lZCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVuZGVmRXZlbnRzW2lkXSA9IG1vZC5ldmVudHM7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xlYW5SZWdpc3RyeShpZCk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB9OwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIHJldHVybiBsb2NhbFJlcXVpcmU7CiAgICAgICAgICAgIH0sCgogICAgICAgICAgICAvKioKICAgICAgICAgICAgICogQ2FsbGVkIHRvIGVuYWJsZSBhIG1vZHVsZSBpZiBpdCBpcyBzdGlsbCBpbiB0aGUgcmVnaXN0cnkKICAgICAgICAgICAgICogYXdhaXRpbmcgZW5hYmxlbWVudC4gQSBzZWNvbmQgYXJnLCBwYXJlbnQsIHRoZSBwYXJlbnQgbW9kdWxlLAogICAgICAgICAgICAgKiBpcyBwYXNzZWQgaW4gZm9yIGNvbnRleHQsIHdoZW4gdGhpcyBtZXRob2QgaXMgb3ZlcnJpZGRlbiBieQogICAgICAgICAgICAgKiB0aGUgb3B0aW1pemVyLiBOb3Qgc2hvd24gaGVyZSB0byBrZWVwIGNvZGUgY29tcGFjdC4KICAgICAgICAgICAgICovCiAgICAgICAgICAgIGVuYWJsZTogZnVuY3Rpb24gKGRlcE1hcCkgewogICAgICAgICAgICAgICAgdmFyIG1vZCA9IGdldE93bihyZWdpc3RyeSwgZGVwTWFwLmlkKTsKICAgICAgICAgICAgICAgIGlmIChtb2QpIHsKICAgICAgICAgICAgICAgICAgICBnZXRNb2R1bGUoZGVwTWFwKS5lbmFibGUoKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKCiAgICAgICAgICAgIC8qKgogICAgICAgICAgICAgKiBJbnRlcm5hbCBtZXRob2QgdXNlZCBieSBlbnZpcm9ubWVudCBhZGFwdGVycyB0byBjb21wbGV0ZSBhIGxvYWQgZXZlbnQuCiAgICAgICAgICAgICAqIEEgbG9hZCBldmVudCBjb3VsZCBiZSBhIHNjcmlwdCBsb2FkIG9yIGp1c3QgYSBsb2FkIHBhc3MgZnJvbSBhIHN5bmNocm9ub3VzCiAgICAgICAgICAgICAqIGxvYWQgY2FsbC4KICAgICAgICAgICAgICogQHBhcmFtIHtTdHJpbmd9IG1vZHVsZU5hbWUgdGhlIG5hbWUgb2YgdGhlIG1vZHVsZSB0byBwb3RlbnRpYWxseSBjb21wbGV0ZS4KICAgICAgICAgICAgICovCiAgICAgICAgICAgIGNvbXBsZXRlTG9hZDogZnVuY3Rpb24gKG1vZHVsZU5hbWUpIHsKICAgICAgICAgICAgICAgIHZhciBmb3VuZCwgYXJncywgbW9kLAogICAgICAgICAgICAgICAgICAgIHNoaW0gPSBnZXRPd24oY29uZmlnLnNoaW0sIG1vZHVsZU5hbWUpIHx8IHt9LAogICAgICAgICAgICAgICAgICAgIHNoRXhwb3J0cyA9IHNoaW0uZXhwb3J0czsKCiAgICAgICAgICAgICAgICB0YWtlR2xvYmFsUXVldWUoKTsKCiAgICAgICAgICAgICAgICB3aGlsZSAoZGVmUXVldWUubGVuZ3RoKSB7CiAgICAgICAgICAgICAgICAgICAgYXJncyA9IGRlZlF1ZXVlLnNoaWZ0KCk7CiAgICAgICAgICAgICAgICAgICAgaWYgKGFyZ3NbMF0gPT09IG51bGwpIHsKICAgICAgICAgICAgICAgICAgICAgICAgYXJnc1swXSA9IG1vZHVsZU5hbWU7CiAgICAgICAgICAgICAgICAgICAgICAgIC8vSWYgYWxyZWFkeSBmb3VuZCBhbiBhbm9ueW1vdXMgbW9kdWxlIGFuZCBib3VuZCBpdAogICAgICAgICAgICAgICAgICAgICAgICAvL3RvIHRoaXMgbmFtZSwgdGhlbiB0aGlzIGlzIHNvbWUgb3RoZXIgYW5vbiBtb2R1bGUKICAgICAgICAgICAgICAgICAgICAgICAgLy93YWl0aW5nIGZvciBpdHMgY29tcGxldGVMb2FkIHRvIGZpcmUuCiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChmb3VuZCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgZm91bmQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAoYXJnc1swXSA9PT0gbW9kdWxlTmFtZSkgewogICAgICAgICAgICAgICAgICAgICAgICAvL0ZvdW5kIG1hdGNoaW5nIGRlZmluZSBjYWxsIGZvciB0aGlzIHNjcmlwdCEKICAgICAgICAgICAgICAgICAgICAgICAgZm91bmQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgY2FsbEdldE1vZHVsZShhcmdzKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGNvbnRleHQuZGVmUXVldWVNYXAgPSB7fTsKCiAgICAgICAgICAgICAgICAvL0RvIHRoaXMgYWZ0ZXIgdGhlIGN5Y2xlIG9mIGNhbGxHZXRNb2R1bGUgaW4gY2FzZSB0aGUgcmVzdWx0CiAgICAgICAgICAgICAgICAvL29mIHRob3NlIGNhbGxzL2luaXQgY2FsbHMgY2hhbmdlcyB0aGUgcmVnaXN0cnkuCiAgICAgICAgICAgICAgICBtb2QgPSBnZXRPd24ocmVnaXN0cnksIG1vZHVsZU5hbWUpOwoKICAgICAgICAgICAgICAgIGlmICghZm91bmQgJiYgIWhhc1Byb3AoZGVmaW5lZCwgbW9kdWxlTmFtZSkgJiYgbW9kICYmICFtb2QuaW5pdGVkKSB7CiAgICAgICAgICAgICAgICAgICAgaWYgKGNvbmZpZy5lbmZvcmNlRGVmaW5lICYmICghc2hFeHBvcnRzIHx8ICFnZXRHbG9iYWwoc2hFeHBvcnRzKSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGhhc1BhdGhGYWxsYmFjayhtb2R1bGVOYW1lKSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG9uRXJyb3IobWFrZUVycm9yKCdub2RlZmluZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdObyBkZWZpbmUgY2FsbCBmb3IgJyArIG1vZHVsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bGwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFttb2R1bGVOYW1lXSkpOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAgICAgLy9BIHNjcmlwdCB0aGF0IGRvZXMgbm90IGNhbGwgZGVmaW5lKCksIHNvIGp1c3Qgc2ltdWxhdGUKICAgICAgICAgICAgICAgICAgICAgICAgLy90aGUgY2FsbCBmb3IgaXQuCiAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxHZXRNb2R1bGUoW21vZHVsZU5hbWUsIChzaGltLmRlcHMgfHwgW10pLCBzaGltLmV4cG9ydHNGbl0pOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICBjaGVja0xvYWRlZCgpOwogICAgICAgICAgICB9LAoKICAgICAgICAgICAgLyoqCiAgICAgICAgICAgICAqIENvbnZlcnRzIGEgbW9kdWxlIG5hbWUgdG8gYSBmaWxlIHBhdGguIFN1cHBvcnRzIGNhc2VzIHdoZXJlCiAgICAgICAgICAgICAqIG1vZHVsZU5hbWUgbWF5IGFjdHVhbGx5IGJlIGp1c3QgYW4gVVJMLgogICAgICAgICAgICAgKiBOb3RlIHRoYXQgaXQgKipkb2VzIG5vdCoqIGNhbGwgbm9ybWFsaXplIG9uIHRoZSBtb2R1bGVOYW1lLAogICAgICAgICAgICAgKiBpdCBpcyBhc3N1bWVkIHRvIGhhdmUgYWxyZWFkeSBiZWVuIG5vcm1hbGl6ZWQuIFRoaXMgaXMgYW4KICAgICAgICAgICAgICogaW50ZXJuYWwgQVBJLCBub3QgYSBwdWJsaWMgb25lLiBVc2UgdG9VcmwgZm9yIHRoZSBwdWJsaWMgQVBJLgogICAgICAgICAgICAgKi8KICAgICAgICAgICAgbmFtZVRvVXJsOiBmdW5jdGlvbiAobW9kdWxlTmFtZSwgZXh0LCBza2lwRXh0KSB7CiAgICAgICAgICAgICAgICB2YXIgcGF0aHMsIHN5bXMsIGksIHBhcmVudE1vZHVsZSwgdXJsLAogICAgICAgICAgICAgICAgICAgIHBhcmVudFBhdGgsIGJ1bmRsZUlkLAogICAgICAgICAgICAgICAgICAgIHBrZ01haW4gPSBnZXRPd24oY29uZmlnLnBrZ3MsIG1vZHVsZU5hbWUpOwoKICAgICAgICAgICAgICAgIGlmIChwa2dNYWluKSB7CiAgICAgICAgICAgICAgICAgICAgbW9kdWxlTmFtZSA9IHBrZ01haW47CiAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgYnVuZGxlSWQgPSBnZXRPd24oYnVuZGxlc01hcCwgbW9kdWxlTmFtZSk7CgogICAgICAgICAgICAgICAgaWYgKGJ1bmRsZUlkKSB7CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQubmFtZVRvVXJsKGJ1bmRsZUlkLCBleHQsIHNraXBFeHQpOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIC8vSWYgYSBjb2xvbiBpcyBpbiB0aGUgVVJMLCBpdCBpbmRpY2F0ZXMgYSBwcm90b2NvbCBpcyB1c2VkIGFuZCBpdCBpcyBqdXN0CiAgICAgICAgICAgICAgICAvL2FuIFVSTCB0byBhIGZpbGUsIG9yIGlmIGl0IHN0YXJ0cyB3aXRoIGEgc2xhc2gsIGNvbnRhaW5zIGEgcXVlcnkgYXJnIChpLmUuID8pCiAgICAgICAgICAgICAgICAvL29yIGVuZHMgd2l0aCAuanMsIHRoZW4gYXNzdW1lIHRoZSB1c2VyIG1lYW50IHRvIHVzZSBhbiB1cmwgYW5kIG5vdCBhIG1vZHVsZSBpZC4KICAgICAgICAgICAgICAgIC8vVGhlIHNsYXNoIGlzIGltcG9ydGFudCBmb3IgcHJvdG9jb2wtbGVzcyBVUkxzIGFzIHdlbGwgYXMgZnVsbCBwYXRocy4KICAgICAgICAgICAgICAgIGlmIChyZXEuanNFeHRSZWdFeHAudGVzdChtb2R1bGVOYW1lKSkgewogICAgICAgICAgICAgICAgICAgIC8vSnVzdCBhIHBsYWluIHBhdGgsIG5vdCBtb2R1bGUgbmFtZSBsb29rdXAsIHNvIGp1c3QgcmV0dXJuIGl0LgogICAgICAgICAgICAgICAgICAgIC8vQWRkIGV4dGVuc2lvbiBpZiBpdCBpcyBpbmNsdWRlZC4gVGhpcyBpcyBhIGJpdCB3b25reSwgb25seSBub24tLmpzIHRoaW5ncyBwYXNzCiAgICAgICAgICAgICAgICAgICAgLy9hbiBleHRlbnNpb24sIHRoaXMgbWV0aG9kIHByb2JhYmx5IG5lZWRzIHRvIGJlIHJld29ya2VkLgogICAgICAgICAgICAgICAgICAgIHVybCA9IG1vZHVsZU5hbWUgKyAoZXh0IHx8ICcnKTsKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgLy9BIG1vZHVsZSB0aGF0IG5lZWRzIHRvIGJlIGNvbnZlcnRlZCB0byBhIHBhdGguCiAgICAgICAgICAgICAgICAgICAgcGF0aHMgPSBjb25maWcucGF0aHM7CgogICAgICAgICAgICAgICAgICAgIHN5bXMgPSBtb2R1bGVOYW1lLnNwbGl0KCcvJyk7CiAgICAgICAgICAgICAgICAgICAgLy9Gb3IgZWFjaCBtb2R1bGUgbmFtZSBzZWdtZW50LCBzZWUgaWYgdGhlcmUgaXMgYSBwYXRoCiAgICAgICAgICAgICAgICAgICAgLy9yZWdpc3RlcmVkIGZvciBpdC4gU3RhcnQgd2l0aCBtb3N0IHNwZWNpZmljIG5hbWUKICAgICAgICAgICAgICAgICAgICAvL2FuZCB3b3JrIHVwIGZyb20gaXQuCiAgICAgICAgICAgICAgICAgICAgZm9yIChpID0gc3ltcy5sZW5ndGg7IGkgPiAwOyBpIC09IDEpIHsKICAgICAgICAgICAgICAgICAgICAgICAgcGFyZW50TW9kdWxlID0gc3ltcy5zbGljZSgwLCBpKS5qb2luKCcvJyk7CgogICAgICAgICAgICAgICAgICAgICAgICBwYXJlbnRQYXRoID0gZ2V0T3duKHBhdGhzLCBwYXJlbnRNb2R1bGUpOwogICAgICAgICAgICAgICAgICAgICAgICBpZiAocGFyZW50UGF0aCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy9JZiBhbiBhcnJheSwgaXQgbWVhbnMgdGhlcmUgYXJlIGEgZmV3IGNob2ljZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvL0Nob29zZSB0aGUgb25lIHRoYXQgaXMgZGVzaXJlZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGlzQXJyYXkocGFyZW50UGF0aCkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJlbnRQYXRoID0gcGFyZW50UGF0aFswXTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN5bXMuc3BsaWNlKDAsIGksIHBhcmVudFBhdGgpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgIC8vSm9pbiB0aGUgcGF0aCBwYXJ0cyB0b2dldGhlciwgdGhlbiBmaWd1cmUgb3V0IGlmIGJhc2VVcmwgaXMgbmVlZGVkLgogICAgICAgICAgICAgICAgICAgIHVybCA9IHN5bXMuam9pbignLycpOwogICAgICAgICAgICAgICAgICAgIHVybCArPSAoZXh0IHx8ICgvXmRhdGFcOnxeYmxvYlw6fFw/Ly50ZXN0KHVybCkgfHwgc2tpcEV4dCA/ICcnIDogJy5qcycpKTsKICAgICAgICAgICAgICAgICAgICB1cmwgPSAodXJsLmNoYXJBdCgwKSA9PT0gJy8nIHx8IHVybC5tYXRjaCgvXltcd1wrXC5cLV0rOi8pID8gJycgOiBjb25maWcuYmFzZVVybCkgKyB1cmw7CiAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbmZpZy51cmxBcmdzICYmICEvXmJsb2JcOi8udGVzdCh1cmwpID8KICAgICAgICAgICAgICAgICAgICAgICB1cmwgKyBjb25maWcudXJsQXJncyhtb2R1bGVOYW1lLCB1cmwpIDogdXJsOwogICAgICAgICAgICB9LAoKICAgICAgICAgICAgLy9EZWxlZ2F0ZXMgdG8gcmVxLmxvYWQuIEJyb2tlbiBvdXQgYXMgYSBzZXBhcmF0ZSBmdW5jdGlvbiB0bwogICAgICAgICAgICAvL2FsbG93IG92ZXJyaWRpbmcgaW4gdGhlIG9wdGltaXplci4KICAgICAgICAgICAgbG9hZDogZnVuY3Rpb24gKGlkLCB1cmwpIHsKICAgICAgICAgICAgICAgIHJlcS5sb2FkKGNvbnRleHQsIGlkLCB1cmwpOwogICAgICAgICAgICB9LAoKICAgICAgICAgICAgLyoqCiAgICAgICAgICAgICAqIEV4ZWN1dGVzIGEgbW9kdWxlIGNhbGxiYWNrIGZ1bmN0aW9uLiBCcm9rZW4gb3V0IGFzIGEgc2VwYXJhdGUgZnVuY3Rpb24KICAgICAgICAgICAgICogc29sZWx5IHRvIGFsbG93IHRoZSBidWlsZCBzeXN0ZW0gdG8gc2VxdWVuY2UgdGhlIGZpbGVzIGluIHRoZSBidWlsdAogICAgICAgICAgICAgKiBsYXllciBpbiB0aGUgcmlnaHQgc2VxdWVuY2UuCiAgICAgICAgICAgICAqCiAgICAgICAgICAgICAqIEBwcml2YXRlCiAgICAgICAgICAgICAqLwogICAgICAgICAgICBleGVjQ2I6IGZ1bmN0aW9uIChuYW1lLCBjYWxsYmFjaywgYXJncywgZXhwb3J0cykgewogICAgICAgICAgICAgICAgcmV0dXJuIGNhbGxiYWNrLmFwcGx5KGV4cG9ydHMsIGFyZ3MpOwogICAgICAgICAgICB9LAoKICAgICAgICAgICAgLyoqCiAgICAgICAgICAgICAqIGNhbGxiYWNrIGZvciBzY3JpcHQgbG9hZHMsIHVzZWQgdG8gY2hlY2sgc3RhdHVzIG9mIGxvYWRpbmcuCiAgICAgICAgICAgICAqCiAgICAgICAgICAgICAqIEBwYXJhbSB7RXZlbnR9IGV2dCB0aGUgZXZlbnQgZnJvbSB0aGUgYnJvd3NlciBmb3IgdGhlIHNjcmlwdAogICAgICAgICAgICAgKiB0aGF0IHdhcyBsb2FkZWQuCiAgICAgICAgICAgICAqLwogICAgICAgICAgICBvblNjcmlwdExvYWQ6IGZ1bmN0aW9uIChldnQpIHsKICAgICAgICAgICAgICAgIC8vVXNpbmcgY3VycmVudFRhcmdldCBpbnN0ZWFkIG9mIHRhcmdldCBmb3IgRmlyZWZveCAyLjAncyBzYWtlLiBOb3QKICAgICAgICAgICAgICAgIC8vYWxsIG9sZCBicm93c2VycyB3aWxsIGJlIHN1cHBvcnRlZCwgYnV0IHRoaXMgb25lIHdhcyBlYXN5IGVub3VnaAogICAgICAgICAgICAgICAgLy90byBzdXBwb3J0IGFuZCBzdGlsbCBtYWtlcyBzZW5zZS4KICAgICAgICAgICAgICAgIGlmIChldnQudHlwZSA9PT0gJ2xvYWQnIHx8CiAgICAgICAgICAgICAgICAgICAgICAgIChyZWFkeVJlZ0V4cC50ZXN0KChldnQuY3VycmVudFRhcmdldCB8fCBldnQuc3JjRWxlbWVudCkucmVhZHlTdGF0ZSkpKSB7CiAgICAgICAgICAgICAgICAgICAgLy9SZXNldCBpbnRlcmFjdGl2ZSBzY3JpcHQgc28gYSBzY3JpcHQgbm9kZSBpcyBub3QgaGVsZCBvbnRvIGZvcgogICAgICAgICAgICAgICAgICAgIC8vdG8gbG9uZy4KICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGl2ZVNjcmlwdCA9IG51bGw7CgogICAgICAgICAgICAgICAgICAgIC8vUHVsbCBvdXQgdGhlIG5hbWUgb2YgdGhlIG1vZHVsZSBhbmQgdGhlIGNvbnRleHQuCiAgICAgICAgICAgICAgICAgICAgdmFyIGRhdGEgPSBnZXRTY3JpcHREYXRhKGV2dCk7CiAgICAgICAgICAgICAgICAgICAgY29udGV4dC5jb21wbGV0ZUxvYWQoZGF0YS5pZCk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCgogICAgICAgICAgICAvKioKICAgICAgICAgICAgICogQ2FsbGJhY2sgZm9yIHNjcmlwdCBlcnJvcnMuCiAgICAgICAgICAgICAqLwogICAgICAgICAgICBvblNjcmlwdEVycm9yOiBmdW5jdGlvbiAoZXZ0KSB7CiAgICAgICAgICAgICAgICB2YXIgZGF0YSA9IGdldFNjcmlwdERhdGEoZXZ0KTsKICAgICAgICAgICAgICAgIGlmICghaGFzUGF0aEZhbGxiYWNrKGRhdGEuaWQpKSB7CiAgICAgICAgICAgICAgICAgICAgdmFyIHBhcmVudHMgPSBbXTsKICAgICAgICAgICAgICAgICAgICBlYWNoUHJvcChyZWdpc3RyeSwgZnVuY3Rpb24odmFsdWUsIGtleSkgewogICAgICAgICAgICAgICAgICAgICAgICBpZiAoa2V5LmluZGV4T2YoJ19AcicpICE9PSAwKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlYWNoKHZhbHVlLmRlcE1hcHMsIGZ1bmN0aW9uKGRlcE1hcCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChkZXBNYXAuaWQgPT09IGRhdGEuaWQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyZW50cy5wdXNoKGtleSk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG9uRXJyb3IobWFrZUVycm9yKCdzY3JpcHRlcnJvcicsICdTY3JpcHQgZXJyb3IgZm9yICInICsgZGF0YS5pZCArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChwYXJlbnRzLmxlbmd0aCA/CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICciLCBuZWVkZWQgYnk6ICcgKyBwYXJlbnRzLmpvaW4oJywgJykgOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnIicpLCBldnQsIFtkYXRhLmlkXSkpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfTsKCiAgICAgICAgY29udGV4dC5yZXF1aXJlID0gY29udGV4dC5tYWtlUmVxdWlyZSgpOwogICAgICAgIHJldHVybiBjb250ZXh0OwogICAgfQoKICAgIC8qKgogICAgICogTWFpbiBlbnRyeSBwb2ludC4KICAgICAqCiAgICAgKiBJZiB0aGUgb25seSBhcmd1bWVudCB0byByZXF1aXJlIGlzIGEgc3RyaW5nLCB0aGVuIHRoZSBtb2R1bGUgdGhhdAogICAgICogaXMgcmVwcmVzZW50ZWQgYnkgdGhhdCBzdHJpbmcgaXMgZmV0Y2hlZCBmb3IgdGhlIGFwcHJvcHJpYXRlIGNvbnRleHQuCiAgICAgKgogICAgICogSWYgdGhlIGZpcnN0IGFyZ3VtZW50IGlzIGFuIGFycmF5LCB0aGVuIGl0IHdpbGwgYmUgdHJlYXRlZCBhcyBhbiBhcnJheQogICAgICogb2YgZGVwZW5kZW5jeSBzdHJpbmcgbmFtZXMgdG8gZmV0Y2guIEFuIG9wdGlvbmFsIGZ1bmN0aW9uIGNhbGxiYWNrIGNhbgogICAgICogYmUgc3BlY2lmaWVkIHRvIGV4ZWN1dGUgd2hlbiBhbGwgb2YgdGhvc2UgZGVwZW5kZW5jaWVzIGFyZSBhdmFpbGFibGUuCiAgICAgKgogICAgICogTWFrZSBhIGxvY2FsIHJlcSB2YXJpYWJsZSB0byBoZWxwIENhamEgY29tcGxpYW5jZSAoaXQgYXNzdW1lcyB0aGluZ3MKICAgICAqIG9uIGEgcmVxdWlyZSB0aGF0IGFyZSBub3Qgc3RhbmRhcmRpemVkKSwgYW5kIHRvIGdpdmUgYSBzaG9ydAogICAgICogbmFtZSBmb3IgbWluaWZpY2F0aW9uL2xvY2FsIHNjb3BlIHVzZS4KICAgICAqLwogICAgcmVxID0gcmVxdWlyZWpzID0gZnVuY3Rpb24gKGRlcHMsIGNhbGxiYWNrLCBlcnJiYWNrLCBvcHRpb25hbCkgewoKICAgICAgICAvL0ZpbmQgdGhlIHJpZ2h0IGNvbnRleHQsIHVzZSBkZWZhdWx0CiAgICAgICAgdmFyIGNvbnRleHQsIGNvbmZpZywKICAgICAgICAgICAgY29udGV4dE5hbWUgPSBkZWZDb250ZXh0TmFtZTsKCiAgICAgICAgLy8gRGV0ZXJtaW5lIGlmIGhhdmUgY29uZmlnIG9iamVjdCBpbiB0aGUgY2FsbC4KICAgICAgICBpZiAoIWlzQXJyYXkoZGVwcykgJiYgdHlwZW9mIGRlcHMgIT09ICdzdHJpbmcnKSB7CiAgICAgICAgICAgIC8vIGRlcHMgaXMgYSBjb25maWcgb2JqZWN0CiAgICAgICAgICAgIGNvbmZpZyA9IGRlcHM7CiAgICAgICAgICAgIGlmIChpc0FycmF5KGNhbGxiYWNrKSkgewogICAgICAgICAgICAgICAgLy8gQWRqdXN0IGFyZ3MgaWYgdGhlcmUgYXJlIGRlcGVuZGVuY2llcwogICAgICAgICAgICAgICAgZGVwcyA9IGNhbGxiYWNrOwogICAgICAgICAgICAgICAgY2FsbGJhY2sgPSBlcnJiYWNrOwogICAgICAgICAgICAgICAgZXJyYmFjayA9IG9wdGlvbmFsOwogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgZGVwcyA9IFtdOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBpZiAoY29uZmlnICYmIGNvbmZpZy5jb250ZXh0KSB7CiAgICAgICAgICAgIGNvbnRleHROYW1lID0gY29uZmlnLmNvbnRleHQ7CiAgICAgICAgfQoKICAgICAgICBjb250ZXh0ID0gZ2V0T3duKGNvbnRleHRzLCBjb250ZXh0TmFtZSk7CiAgICAgICAgaWYgKCFjb250ZXh0KSB7CiAgICAgICAgICAgIGNvbnRleHQgPSBjb250ZXh0c1tjb250ZXh0TmFtZV0gPSByZXEucy5uZXdDb250ZXh0KGNvbnRleHROYW1lKTsKICAgICAgICB9CgogICAgICAgIGlmIChjb25maWcpIHsKICAgICAgICAgICAgY29udGV4dC5jb25maWd1cmUoY29uZmlnKTsKICAgICAgICB9CgogICAgICAgIHJldHVybiBjb250ZXh0LnJlcXVpcmUoZGVwcywgY2FsbGJhY2ssIGVycmJhY2spOwogICAgfTsKCiAgICAvKioKICAgICAqIFN1cHBvcnQgcmVxdWlyZS5jb25maWcoKSB0byBtYWtlIGl0IGVhc2llciB0byBjb29wZXJhdGUgd2l0aCBvdGhlcgogICAgICogQU1EIGxvYWRlcnMgb24gZ2xvYmFsbHkgYWdyZWVkIG5hbWVzLgogICAgICovCiAgICByZXEuY29uZmlnID0gZnVuY3Rpb24gKGNvbmZpZykgewogICAgICAgIHJldHVybiByZXEoY29uZmlnKTsKICAgIH07CgogICAgLyoqCiAgICAgKiBFeGVjdXRlIHNvbWV0aGluZyBhZnRlciB0aGUgY3VycmVudCB0aWNrCiAgICAgKiBvZiB0aGUgZXZlbnQgbG9vcC4gT3ZlcnJpZGUgZm9yIG90aGVyIGVudnMKICAgICAqIHRoYXQgaGF2ZSBhIGJldHRlciBzb2x1dGlvbiB0aGFuIHNldFRpbWVvdXQuCiAgICAgKiBAcGFyYW0gIHtGdW5jdGlvbn0gZm4gZnVuY3Rpb24gdG8gZXhlY3V0ZSBsYXRlci4KICAgICAqLwogICAgcmVxLm5leHRUaWNrID0gdHlwZW9mIHNldFRpbWVvdXQgIT09ICd1bmRlZmluZWQnID8gZnVuY3Rpb24gKGZuKSB7CiAgICAgICAgc2V0VGltZW91dChmbiwgNCk7CiAgICB9IDogZnVuY3Rpb24gKGZuKSB7IGZuKCk7IH07CgogICAgLyoqCiAgICAgKiBFeHBvcnQgcmVxdWlyZSBhcyBhIGdsb2JhbCwgYnV0IG9ubHkgaWYgaXQgZG9lcyBub3QgYWxyZWFkeSBleGlzdC4KICAgICAqLwogICAgaWYgKCFyZXF1aXJlKSB7CiAgICAgICAgcmVxdWlyZSA9IHJlcTsKICAgIH0KCiAgICByZXEudmVyc2lvbiA9IHZlcnNpb247CgogICAgLy9Vc2VkIHRvIGZpbHRlciBvdXQgZGVwZW5kZW5jaWVzIHRoYXQgYXJlIGFscmVhZHkgcGF0aHMuCiAgICByZXEuanNFeHRSZWdFeHAgPSAvXlwvfDp8XD98XC5qcyQvOwogICAgcmVxLmlzQnJvd3NlciA9IGlzQnJvd3NlcjsKICAgIHMgPSByZXEucyA9IHsKICAgICAgICBjb250ZXh0czogY29udGV4dHMsCiAgICAgICAgbmV3Q29udGV4dDogbmV3Q29udGV4dAogICAgfTsKCiAgICAvL0NyZWF0ZSBkZWZhdWx0IGNvbnRleHQuCiAgICByZXEoe30pOwoKICAgIC8vRXhwb3J0cyBzb21lIGNvbnRleHQtc2Vuc2l0aXZlIG1ldGhvZHMgb24gZ2xvYmFsIHJlcXVpcmUuCiAgICBlYWNoKFsKICAgICAgICAndG9VcmwnLAogICAgICAgICd1bmRlZicsCiAgICAgICAgJ2RlZmluZWQnLAogICAgICAgICdzcGVjaWZpZWQnCiAgICBdLCBmdW5jdGlvbiAocHJvcCkgewogICAgICAgIC8vUmVmZXJlbmNlIGZyb20gY29udGV4dHMgaW5zdGVhZCBvZiBlYXJseSBiaW5kaW5nIHRvIGRlZmF1bHQgY29udGV4dCwKICAgICAgICAvL3NvIHRoYXQgZHVyaW5nIGJ1aWxkcywgdGhlIGxhdGVzdCBpbnN0YW5jZSBvZiB0aGUgZGVmYXVsdCBjb250ZXh0CiAgICAgICAgLy93aXRoIGl0cyBjb25maWcgZ2V0cyB1c2VkLgogICAgICAgIHJlcVtwcm9wXSA9IGZ1bmN0aW9uICgpIHsKICAgICAgICAgICAgdmFyIGN0eCA9IGNvbnRleHRzW2RlZkNvbnRleHROYW1lXTsKICAgICAgICAgICAgcmV0dXJuIGN0eC5yZXF1aXJlW3Byb3BdLmFwcGx5KGN0eCwgYXJndW1lbnRzKTsKICAgICAgICB9OwogICAgfSk7CgogICAgaWYgKGlzQnJvd3NlcikgewogICAgICAgIGhlYWQgPSBzLmhlYWQgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgnaGVhZCcpWzBdOwogICAgICAgIC8vSWYgQkFTRSB0YWcgaXMgaW4gcGxheSwgdXNpbmcgYXBwZW5kQ2hpbGQgaXMgYSBwcm9ibGVtIGZvciBJRTYuCiAgICAgICAgLy9XaGVuIHRoYXQgYnJvd3NlciBkaWVzLCB0aGlzIGNhbiBiZSByZW1vdmVkLiBEZXRhaWxzIGluIHRoaXMgalF1ZXJ5IGJ1ZzoKICAgICAgICAvL2h0dHA6Ly9kZXYuanF1ZXJ5LmNvbS90aWNrZXQvMjcwOQogICAgICAgIGJhc2VFbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ2Jhc2UnKVswXTsKICAgICAgICBpZiAoYmFzZUVsZW1lbnQpIHsKICAgICAgICAgICAgaGVhZCA9IHMuaGVhZCA9IGJhc2VFbGVtZW50LnBhcmVudE5vZGU7CiAgICAgICAgfQogICAgfQoKICAgIC8qKgogICAgICogQW55IGVycm9ycyB0aGF0IHJlcXVpcmUgZXhwbGljaXRseSBnZW5lcmF0ZXMgd2lsbCBiZSBwYXNzZWQgdG8gdGhpcwogICAgICogZnVuY3Rpb24uIEludGVyY2VwdC9vdmVycmlkZSBpdCBpZiB5b3Ugd2FudCBjdXN0b20gZXJyb3IgaGFuZGxpbmcuCiAgICAgKiBAcGFyYW0ge0Vycm9yfSBlcnIgdGhlIGVycm9yIG9iamVjdC4KICAgICAqLwogICAgcmVxLm9uRXJyb3IgPSBkZWZhdWx0T25FcnJvcjsKCiAgICAvKioKICAgICAqIENyZWF0ZXMgdGhlIG5vZGUgZm9yIHRoZSBsb2FkIGNvbW1hbmQuIE9ubHkgdXNlZCBpbiBicm93c2VyIGVudnMuCiAgICAgKi8KICAgIHJlcS5jcmVhdGVOb2RlID0gZnVuY3Rpb24gKGNvbmZpZywgbW9kdWxlTmFtZSwgdXJsKSB7CiAgICAgICAgdmFyIG5vZGUgPSBjb25maWcueGh0bWwgPwogICAgICAgICAgICAgICAgZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKCdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sJywgJ2h0bWw6c2NyaXB0JykgOgogICAgICAgICAgICAgICAgZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7CiAgICAgICAgbm9kZS50eXBlID0gY29uZmlnLnNjcmlwdFR5cGUgfHwgJ3RleHQvamF2YXNjcmlwdCc7CiAgICAgICAgbm9kZS5jaGFyc2V0ID0gJ3V0Zi04JzsKICAgICAgICBub2RlLmFzeW5jID0gdHJ1ZTsKICAgICAgICByZXR1cm4gbm9kZTsKICAgIH07CgogICAgLyoqCiAgICAgKiBEb2VzIHRoZSByZXF1ZXN0IHRvIGxvYWQgYSBtb2R1bGUgZm9yIHRoZSBicm93c2VyIGNhc2UuCiAgICAgKiBNYWtlIHRoaXMgYSBzZXBhcmF0ZSBmdW5jdGlvbiB0byBhbGxvdyBvdGhlciBlbnZpcm9ubWVudHMKICAgICAqIHRvIG92ZXJyaWRlIGl0LgogICAgICoKICAgICAqIEBwYXJhbSB7T2JqZWN0fSBjb250ZXh0IHRoZSByZXF1aXJlIGNvbnRleHQgdG8gZmluZCBzdGF0ZS4KICAgICAqIEBwYXJhbSB7U3RyaW5nfSBtb2R1bGVOYW1lIHRoZSBuYW1lIG9mIHRoZSBtb2R1bGUuCiAgICAgKiBAcGFyYW0ge09iamVjdH0gdXJsIHRoZSBVUkwgdG8gdGhlIG1vZHVsZS4KICAgICAqLwogICAgcmVxLmxvYWQgPSBmdW5jdGlvbiAoY29udGV4dCwgbW9kdWxlTmFtZSwgdXJsKSB7CiAgICAgICAgdmFyIGNvbmZpZyA9IChjb250ZXh0ICYmIGNvbnRleHQuY29uZmlnKSB8fCB7fSwKICAgICAgICAgICAgbm9kZTsKICAgICAgICBpZiAoaXNCcm93c2VyKSB7CiAgICAgICAgICAgIC8vSW4gdGhlIGJyb3dzZXIgc28gdXNlIGEgc2NyaXB0IHRhZwogICAgICAgICAgICBub2RlID0gcmVxLmNyZWF0ZU5vZGUoY29uZmlnLCBtb2R1bGVOYW1lLCB1cmwpOwoKICAgICAgICAgICAgbm9kZS5zZXRBdHRyaWJ1dGUoJ2RhdGEtcmVxdWlyZWNvbnRleHQnLCBjb250ZXh0LmNvbnRleHROYW1lKTsKICAgICAgICAgICAgbm9kZS5zZXRBdHRyaWJ1dGUoJ2RhdGEtcmVxdWlyZW1vZHVsZScsIG1vZHVsZU5hbWUpOwoKICAgICAgICAgICAgLy9TZXQgdXAgbG9hZCBsaXN0ZW5lci4gVGVzdCBhdHRhY2hFdmVudCBmaXJzdCBiZWNhdXNlIElFOSBoYXMKICAgICAgICAgICAgLy9hIHN1YnRsZSBpc3N1ZSBpbiBpdHMgYWRkRXZlbnRMaXN0ZW5lciBhbmQgc2NyaXB0IG9ubG9hZCBmaXJpbmdzCiAgICAgICAgICAgIC8vdGhhdCBkbyBub3QgbWF0Y2ggdGhlIGJlaGF2aW9yIG9mIGFsbCBvdGhlciBicm93c2VycyB3aXRoCiAgICAgICAgICAgIC8vYWRkRXZlbnRMaXN0ZW5lciBzdXBwb3J0LCB3aGljaCBmaXJlIHRoZSBvbmxvYWQgZXZlbnQgZm9yIGEKICAgICAgICAgICAgLy9zY3JpcHQgcmlnaHQgYWZ0ZXIgdGhlIHNjcmlwdCBleGVjdXRpb24uIFNlZToKICAgICAgICAgICAgLy9odHRwczovL2Nvbm5lY3QubWljcm9zb2Z0LmNvbS9JRS9mZWVkYmFjay9kZXRhaWxzLzY0ODA1Ny9zY3JpcHQtb25sb2FkLWV2ZW50LWlzLW5vdC1maXJlZC1pbW1lZGlhdGVseS1hZnRlci1zY3JpcHQtZXhlY3V0aW9uCiAgICAgICAgICAgIC8vVU5GT1JUVU5BVEVMWSBPcGVyYSBpbXBsZW1lbnRzIGF0dGFjaEV2ZW50IGJ1dCBkb2VzIG5vdCBmb2xsb3cgdGhlIHNjcmlwdAogICAgICAgICAgICAvL3NjcmlwdCBleGVjdXRpb24gbW9kZS4KICAgICAgICAgICAgaWYgKG5vZGUuYXR0YWNoRXZlbnQgJiYKICAgICAgICAgICAgICAgICAgICAvL0NoZWNrIGlmIG5vZGUuYXR0YWNoRXZlbnQgaXMgYXJ0aWZpY2lhbGx5IGFkZGVkIGJ5IGN1c3RvbSBzY3JpcHQgb3IKICAgICAgICAgICAgICAgICAgICAvL25hdGl2ZWx5IHN1cHBvcnRlZCBieSBicm93c2VyCiAgICAgICAgICAgICAgICAgICAgLy9yZWFkIGh0dHBzOi8vZ2l0aHViLmNvbS9yZXF1aXJlanMvcmVxdWlyZWpzL2lzc3Vlcy8xODcKICAgICAgICAgICAgICAgICAgICAvL2lmIHdlIGNhbiBOT1QgZmluZCBbbmF0aXZlIGNvZGVdIHRoZW4gaXQgbXVzdCBOT1QgbmF0aXZlbHkgc3VwcG9ydGVkLgogICAgICAgICAgICAgICAgICAgIC8vaW4gSUU4LCBub2RlLmF0dGFjaEV2ZW50IGRvZXMgbm90IGhhdmUgdG9TdHJpbmcoKQogICAgICAgICAgICAgICAgICAgIC8vTm90ZSB0aGUgdGVzdCBmb3IgIltuYXRpdmUgY29kZSIgd2l0aCBubyBjbG9zaW5nIGJyYWNlLCBzZWU6CiAgICAgICAgICAgICAgICAgICAgLy9odHRwczovL2dpdGh1Yi5jb20vcmVxdWlyZWpzL3JlcXVpcmVqcy9pc3N1ZXMvMjczCiAgICAgICAgICAgICAgICAgICAgIShub2RlLmF0dGFjaEV2ZW50LnRvU3RyaW5nICYmIG5vZGUuYXR0YWNoRXZlbnQudG9TdHJpbmcoKS5pbmRleE9mKCdbbmF0aXZlIGNvZGUnKSA8IDApICYmCiAgICAgICAgICAgICAgICAgICAgIWlzT3BlcmEpIHsKICAgICAgICAgICAgICAgIC8vUHJvYmFibHkgSUUuIElFIChhdCBsZWFzdCA2LTgpIGRvIG5vdCBmaXJlCiAgICAgICAgICAgICAgICAvL3NjcmlwdCBvbmxvYWQgcmlnaHQgYWZ0ZXIgZXhlY3V0aW5nIHRoZSBzY3JpcHQsIHNvCiAgICAgICAgICAgICAgICAvL3dlIGNhbm5vdCB0aWUgdGhlIGFub255bW91cyBkZWZpbmUgY2FsbCB0byBhIG5hbWUuCiAgICAgICAgICAgICAgICAvL0hvd2V2ZXIsIElFIHJlcG9ydHMgdGhlIHNjcmlwdCBhcyBiZWluZyBpbiAnaW50ZXJhY3RpdmUnCiAgICAgICAgICAgICAgICAvL3JlYWR5U3RhdGUgYXQgdGhlIHRpbWUgb2YgdGhlIGRlZmluZSBjYWxsLgogICAgICAgICAgICAgICAgdXNlSW50ZXJhY3RpdmUgPSB0cnVlOwoKICAgICAgICAgICAgICAgIG5vZGUuYXR0YWNoRXZlbnQoJ29ucmVhZHlzdGF0ZWNoYW5nZScsIGNvbnRleHQub25TY3JpcHRMb2FkKTsKICAgICAgICAgICAgICAgIC8vSXQgd291bGQgYmUgZ3JlYXQgdG8gYWRkIGFuIGVycm9yIGhhbmRsZXIgaGVyZSB0byBjYXRjaAogICAgICAgICAgICAgICAgLy80MDRzIGluIElFOSsuIEhvd2V2ZXIsIG9ucmVhZHlzdGF0ZWNoYW5nZSB3aWxsIGZpcmUgYmVmb3JlCiAgICAgICAgICAgICAgICAvL3RoZSBlcnJvciBoYW5kbGVyLCBzbyB0aGF0IGRvZXMgbm90IGhlbHAuIElmIGFkZEV2ZW50TGlzdGVuZXIKICAgICAgICAgICAgICAgIC8vaXMgdXNlZCwgdGhlbiBJRSB3aWxsIGZpcmUgZXJyb3IgYmVmb3JlIGxvYWQsIGJ1dCB3ZSBjYW5ub3QKICAgICAgICAgICAgICAgIC8vdXNlIHRoYXQgcGF0aHdheSBnaXZlbiB0aGUgY29ubmVjdC5taWNyb3NvZnQuY29tIGlzc3VlCiAgICAgICAgICAgICAgICAvL21lbnRpb25lZCBhYm92ZSBhYm91dCBub3QgZG9pbmcgdGhlICdzY3JpcHQgZXhlY3V0ZSwKICAgICAgICAgICAgICAgIC8vdGhlbiBmaXJlIHRoZSBzY3JpcHQgbG9hZCBldmVudCBsaXN0ZW5lciBiZWZvcmUgZXhlY3V0ZQogICAgICAgICAgICAgICAgLy9uZXh0IHNjcmlwdCcgdGhhdCBvdGhlciBicm93c2VycyBkby4KICAgICAgICAgICAgICAgIC8vQmVzdCBob3BlOiBJRTEwIGZpeGVzIHRoZSBpc3N1ZXMsCiAgICAgICAgICAgICAgICAvL2FuZCB0aGVuIGRlc3Ryb3lzIGFsbCBpbnN0YWxscyBvZiBJRSA2LTkuCiAgICAgICAgICAgICAgICAvL25vZGUuYXR0YWNoRXZlbnQoJ29uZXJyb3InLCBjb250ZXh0Lm9uU2NyaXB0RXJyb3IpOwogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgbm9kZS5hZGRFdmVudExpc3RlbmVyKCdsb2FkJywgY29udGV4dC5vblNjcmlwdExvYWQsIGZhbHNlKTsKICAgICAgICAgICAgICAgIG5vZGUuYWRkRXZlbnRMaXN0ZW5lcignZXJyb3InLCBjb250ZXh0Lm9uU2NyaXB0RXJyb3IsIGZhbHNlKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBub2RlLnNyYyA9IHVybDsKCiAgICAgICAgICAgIC8vQ2FsbGluZyBvbk5vZGVDcmVhdGVkIGFmdGVyIGFsbCBwcm9wZXJ0aWVzIG9uIHRoZSBub2RlIGhhdmUgYmVlbgogICAgICAgICAgICAvL3NldCwgYnV0IGJlZm9yZSBpdCBpcyBwbGFjZWQgaW4gdGhlIERPTS4KICAgICAgICAgICAgaWYgKGNvbmZpZy5vbk5vZGVDcmVhdGVkKSB7CiAgICAgICAgICAgICAgICBjb25maWcub25Ob2RlQ3JlYXRlZChub2RlLCBjb25maWcsIG1vZHVsZU5hbWUsIHVybCk7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIC8vRm9yIHNvbWUgY2FjaGUgY2FzZXMgaW4gSUUgNi04LCB0aGUgc2NyaXB0IGV4ZWN1dGVzIGJlZm9yZSB0aGUgZW5kCiAgICAgICAgICAgIC8vb2YgdGhlIGFwcGVuZENoaWxkIGV4ZWN1dGlvbiwgc28gdG8gdGllIGFuIGFub255bW91cyBkZWZpbmUKICAgICAgICAgICAgLy9jYWxsIHRvIHRoZSBtb2R1bGUgbmFtZSAod2hpY2ggaXMgc3RvcmVkIG9uIHRoZSBub2RlKSwgaG9sZCBvbgogICAgICAgICAgICAvL3RvIGEgcmVmZXJlbmNlIHRvIHRoaXMgbm9kZSwgYnV0IGNsZWFyIGFmdGVyIHRoZSBET00gaW5zZXJ0aW9uLgogICAgICAgICAgICBjdXJyZW50bHlBZGRpbmdTY3JpcHQgPSBub2RlOwogICAgICAgICAgICBpZiAoYmFzZUVsZW1lbnQpIHsKICAgICAgICAgICAgICAgIGhlYWQuaW5zZXJ0QmVmb3JlKG5vZGUsIGJhc2VFbGVtZW50KTsKICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIGhlYWQuYXBwZW5kQ2hpbGQobm9kZSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY3VycmVudGx5QWRkaW5nU2NyaXB0ID0gbnVsbDsKCiAgICAgICAgICAgIHJldHVybiBub2RlOwogICAgICAgIH0gZWxzZSBpZiAoaXNXZWJXb3JrZXIpIHsKICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgIC8vSW4gYSB3ZWIgd29ya2VyLCB1c2UgaW1wb3J0U2NyaXB0cy4gVGhpcyBpcyBub3QgYSB2ZXJ5CiAgICAgICAgICAgICAgICAvL2VmZmljaWVudCB1c2Ugb2YgaW1wb3J0U2NyaXB0cywgaW1wb3J0U2NyaXB0cyB3aWxsIGJsb2NrIHVudGlsCiAgICAgICAgICAgICAgICAvL2l0cyBzY3JpcHQgaXMgZG93bmxvYWRlZCBhbmQgZXZhbHVhdGVkLiBIb3dldmVyLCBpZiB3ZWIgd29ya2VycwogICAgICAgICAgICAgICAgLy9hcmUgaW4gcGxheSwgdGhlIGV4cGVjdGF0aW9uIGlzIHRoYXQgYSBidWlsZCBoYXMgYmVlbiBkb25lIHNvCiAgICAgICAgICAgICAgICAvL3RoYXQgb25seSBvbmUgc2NyaXB0IG5lZWRzIHRvIGJlIGxvYWRlZCBhbnl3YXkuIFRoaXMgbWF5IG5lZWQKICAgICAgICAgICAgICAgIC8vdG8gYmUgcmVldmFsdWF0ZWQgaWYgb3RoZXIgdXNlIGNhc2VzIGJlY29tZSBjb21tb24uCgogICAgICAgICAgICAgICAgLy8gUG9zdCBhIHRhc2sgdG8gdGhlIGV2ZW50IGxvb3AgdG8gd29yayBhcm91bmQgYSBidWcgaW4gV2ViS2l0CiAgICAgICAgICAgICAgICAvLyB3aGVyZSB0aGUgd29ya2VyIGdldHMgZ2FyYmFnZS1jb2xsZWN0ZWQgYWZ0ZXIgY2FsbGluZwogICAgICAgICAgICAgICAgLy8gaW1wb3J0U2NyaXB0cygpOiBodHRwczovL3dlYmtpdC5vcmcvYi8xNTMzMTcKICAgICAgICAgICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24oKSB7fSwgMCk7CiAgICAgICAgICAgICAgICBpbXBvcnRTY3JpcHRzKHVybCk7CgogICAgICAgICAgICAgICAgLy9BY2NvdW50IGZvciBhbm9ueW1vdXMgbW9kdWxlcwogICAgICAgICAgICAgICAgY29udGV4dC5jb21wbGV0ZUxvYWQobW9kdWxlTmFtZSk7CiAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICAgICAgICAgIGNvbnRleHQub25FcnJvcihtYWtlRXJyb3IoJ2ltcG9ydHNjcmlwdHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdpbXBvcnRTY3JpcHRzIGZhaWxlZCBmb3IgJyArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZHVsZU5hbWUgKyAnIGF0ICcgKyB1cmwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbbW9kdWxlTmFtZV0pKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH07CgogICAgZnVuY3Rpb24gZ2V0SW50ZXJhY3RpdmVTY3JpcHQoKSB7CiAgICAgICAgaWYgKGludGVyYWN0aXZlU2NyaXB0ICYmIGludGVyYWN0aXZlU2NyaXB0LnJlYWR5U3RhdGUgPT09ICdpbnRlcmFjdGl2ZScpIHsKICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0aXZlU2NyaXB0OwogICAgICAgIH0KCiAgICAgICAgZWFjaFJldmVyc2Uoc2NyaXB0cygpLCBmdW5jdGlvbiAoc2NyaXB0KSB7CiAgICAgICAgICAgIGlmIChzY3JpcHQucmVhZHlTdGF0ZSA9PT0gJ2ludGVyYWN0aXZlJykgewogICAgICAgICAgICAgICAgcmV0dXJuIChpbnRlcmFjdGl2ZVNjcmlwdCA9IHNjcmlwdCk7CiAgICAgICAgICAgIH0KICAgICAgICB9KTsKICAgICAgICByZXR1cm4gaW50ZXJhY3RpdmVTY3JpcHQ7CiAgICB9CgogICAgLy9Mb29rIGZvciBhIGRhdGEtbWFpbiBzY3JpcHQgYXR0cmlidXRlLCB3aGljaCBjb3VsZCBhbHNvIGFkanVzdCB0aGUgYmFzZVVybC4KICAgIGlmIChpc0Jyb3dzZXIgJiYgIWNmZy5za2lwRGF0YU1haW4pIHsKICAgICAgICAvL0ZpZ3VyZSBvdXQgYmFzZVVybC4gR2V0IGl0IGZyb20gdGhlIHNjcmlwdCB0YWcgd2l0aCByZXF1aXJlLmpzIGluIGl0LgogICAgICAgIGVhY2hSZXZlcnNlKHNjcmlwdHMoKSwgZnVuY3Rpb24gKHNjcmlwdCkgewogICAgICAgICAgICAvL1NldCB0aGUgJ2hlYWQnIHdoZXJlIHdlIGNhbiBhcHBlbmQgY2hpbGRyZW4gYnkKICAgICAgICAgICAgLy91c2luZyB0aGUgc2NyaXB0J3MgcGFyZW50LgogICAgICAgICAgICBpZiAoIWhlYWQpIHsKICAgICAgICAgICAgICAgIGhlYWQgPSBzY3JpcHQucGFyZW50Tm9kZTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy9Mb29rIGZvciBhIGRhdGEtbWFpbiBhdHRyaWJ1dGUgdG8gc2V0IG1haW4gc2NyaXB0IGZvciB0aGUgcGFnZQogICAgICAgICAgICAvL3RvIGxvYWQuIElmIGl0IGlzIHRoZXJlLCB0aGUgcGF0aCB0byBkYXRhIG1haW4gYmVjb21lcyB0aGUKICAgICAgICAgICAgLy9iYXNlVXJsLCBpZiBpdCBpcyBub3QgYWxyZWFkeSBzZXQuCiAgICAgICAgICAgIGRhdGFNYWluID0gc2NyaXB0LmdldEF0dHJpYnV0ZSgnZGF0YS1tYWluJyk7CiAgICAgICAgICAgIGlmIChkYXRhTWFpbikgewogICAgICAgICAgICAgICAgLy9QcmVzZXJ2ZSBkYXRhTWFpbiBpbiBjYXNlIGl0IGlzIGEgcGF0aCAoaS5lLiBjb250YWlucyAnPycpCiAgICAgICAgICAgICAgICBtYWluU2NyaXB0ID0gZGF0YU1haW47CgogICAgICAgICAgICAgICAgLy9TZXQgZmluYWwgYmFzZVVybCBpZiB0aGVyZSBpcyBub3QgYWxyZWFkeSBhbiBleHBsaWNpdCBvbmUsCiAgICAgICAgICAgICAgICAvL2J1dCBvbmx5IGRvIHNvIGlmIHRoZSBkYXRhLW1haW4gdmFsdWUgaXMgbm90IGEgbG9hZGVyIHBsdWdpbgogICAgICAgICAgICAgICAgLy9tb2R1bGUgSUQuCiAgICAgICAgICAgICAgICBpZiAoIWNmZy5iYXNlVXJsICYmIG1haW5TY3JpcHQuaW5kZXhPZignIScpID09PSAtMSkgewogICAgICAgICAgICAgICAgICAgIC8vUHVsbCBvZmYgdGhlIGRpcmVjdG9yeSBvZiBkYXRhLW1haW4gZm9yIHVzZSBhcyB0aGUKICAgICAgICAgICAgICAgICAgICAvL2Jhc2VVcmwuCiAgICAgICAgICAgICAgICAgICAgc3JjID0gbWFpblNjcmlwdC5zcGxpdCgnLycpOwogICAgICAgICAgICAgICAgICAgIG1haW5TY3JpcHQgPSBzcmMucG9wKCk7CiAgICAgICAgICAgICAgICAgICAgc3ViUGF0aCA9IHNyYy5sZW5ndGggPyBzcmMuam9pbignLycpICArICcvJyA6ICcuLyc7CgogICAgICAgICAgICAgICAgICAgIGNmZy5iYXNlVXJsID0gc3ViUGF0aDsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAvL1N0cmlwIG9mZiBhbnkgdHJhaWxpbmcgLmpzIHNpbmNlIG1haW5TY3JpcHQgaXMgbm93CiAgICAgICAgICAgICAgICAvL2xpa2UgYSBtb2R1bGUgbmFtZS4KICAgICAgICAgICAgICAgIG1haW5TY3JpcHQgPSBtYWluU2NyaXB0LnJlcGxhY2UoanNTdWZmaXhSZWdFeHAsICcnKTsKCiAgICAgICAgICAgICAgICAvL0lmIG1haW5TY3JpcHQgaXMgc3RpbGwgYSBwYXRoLCBmYWxsIGJhY2sgdG8gZGF0YU1haW4KICAgICAgICAgICAgICAgIGlmIChyZXEuanNFeHRSZWdFeHAudGVzdChtYWluU2NyaXB0KSkgewogICAgICAgICAgICAgICAgICAgIG1haW5TY3JpcHQgPSBkYXRhTWFpbjsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAvL1B1dCB0aGUgZGF0YS1tYWluIHNjcmlwdCBpbiB0aGUgZmlsZXMgdG8gbG9hZC4KICAgICAgICAgICAgICAgIGNmZy5kZXBzID0gY2ZnLmRlcHMgPyBjZmcuZGVwcy5jb25jYXQobWFpblNjcmlwdCkgOiBbbWFpblNjcmlwdF07CgogICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7CiAgICAgICAgICAgIH0KICAgICAgICB9KTsKICAgIH0KCiAgICAvKioKICAgICAqIFRoZSBmdW5jdGlvbiB0aGF0IGhhbmRsZXMgZGVmaW5pdGlvbnMgb2YgbW9kdWxlcy4gRGlmZmVycyBmcm9tCiAgICAgKiByZXF1aXJlKCkgaW4gdGhhdCBhIHN0cmluZyBmb3IgdGhlIG1vZHVsZSBzaG91bGQgYmUgdGhlIGZpcnN0IGFyZ3VtZW50LAogICAgICogYW5kIHRoZSBmdW5jdGlvbiB0byBleGVjdXRlIGFmdGVyIGRlcGVuZGVuY2llcyBhcmUgbG9hZGVkIHNob3VsZAogICAgICogcmV0dXJuIGEgdmFsdWUgdG8gZGVmaW5lIHRoZSBtb2R1bGUgY29ycmVzcG9uZGluZyB0byB0aGUgZmlyc3QgYXJndW1lbnQncwogICAgICogbmFtZS4KICAgICAqLwogICAgZGVmaW5lID0gZnVuY3Rpb24gKG5hbWUsIGRlcHMsIGNhbGxiYWNrKSB7CiAgICAgICAgdmFyIG5vZGUsIGNvbnRleHQ7CgogICAgICAgIC8vQWxsb3cgZm9yIGFub255bW91cyBtb2R1bGVzCiAgICAgICAgaWYgKHR5cGVvZiBuYW1lICE9PSAnc3RyaW5nJykgewogICAgICAgICAgICAvL0FkanVzdCBhcmdzIGFwcHJvcHJpYXRlbHkKICAgICAgICAgICAgY2FsbGJhY2sgPSBkZXBzOwogICAgICAgICAgICBkZXBzID0gbmFtZTsKICAgICAgICAgICAgbmFtZSA9IG51bGw7CiAgICAgICAgfQoKICAgICAgICAvL1RoaXMgbW9kdWxlIG1heSBub3QgaGF2ZSBkZXBlbmRlbmNpZXMKICAgICAgICBpZiAoIWlzQXJyYXkoZGVwcykpIHsKICAgICAgICAgICAgY2FsbGJhY2sgPSBkZXBzOwogICAgICAgICAgICBkZXBzID0gbnVsbDsKICAgICAgICB9CgogICAgICAgIC8vSWYgbm8gbmFtZSwgYW5kIGNhbGxiYWNrIGlzIGEgZnVuY3Rpb24sIHRoZW4gZmlndXJlIG91dCBpZiBpdCBhCiAgICAgICAgLy9Db21tb25KUyB0aGluZyB3aXRoIGRlcGVuZGVuY2llcy4KICAgICAgICBpZiAoIWRlcHMgJiYgaXNGdW5jdGlvbihjYWxsYmFjaykpIHsKICAgICAgICAgICAgZGVwcyA9IFtdOwogICAgICAgICAgICAvL1JlbW92ZSBjb21tZW50cyBmcm9tIHRoZSBjYWxsYmFjayBzdHJpbmcsCiAgICAgICAgICAgIC8vbG9vayBmb3IgcmVxdWlyZSBjYWxscywgYW5kIHB1bGwgdGhlbSBpbnRvIHRoZSBkZXBlbmRlbmNpZXMsCiAgICAgICAgICAgIC8vYnV0IG9ubHkgaWYgdGhlcmUgYXJlIGZ1bmN0aW9uIGFyZ3MuCiAgICAgICAgICAgIGlmIChjYWxsYmFjay5sZW5ndGgpIHsKICAgICAgICAgICAgICAgIGNhbGxiYWNrCiAgICAgICAgICAgICAgICAgICAgLnRvU3RyaW5nKCkKICAgICAgICAgICAgICAgICAgICAucmVwbGFjZShjb21tZW50UmVnRXhwLCBjb21tZW50UmVwbGFjZSkKICAgICAgICAgICAgICAgICAgICAucmVwbGFjZShjanNSZXF1aXJlUmVnRXhwLCBmdW5jdGlvbiAobWF0Y2gsIGRlcCkgewogICAgICAgICAgICAgICAgICAgICAgICBkZXBzLnB1c2goZGVwKTsKICAgICAgICAgICAgICAgICAgICB9KTsKCiAgICAgICAgICAgICAgICAvL01heSBiZSBhIENvbW1vbkpTIHRoaW5nIGV2ZW4gd2l0aG91dCByZXF1aXJlIGNhbGxzLCBidXQgc3RpbGwKICAgICAgICAgICAgICAgIC8vY291bGQgdXNlIGV4cG9ydHMsIGFuZCBtb2R1bGUuIEF2b2lkIGRvaW5nIGV4cG9ydHMgYW5kIG1vZHVsZQogICAgICAgICAgICAgICAgLy93b3JrIHRob3VnaCBpZiBpdCBqdXN0IG5lZWRzIHJlcXVpcmUuCiAgICAgICAgICAgICAgICAvL1JFUVVJUkVTIHRoZSBmdW5jdGlvbiB0byBleHBlY3QgdGhlIENvbW1vbkpTIHZhcmlhYmxlcyBpbiB0aGUKICAgICAgICAgICAgICAgIC8vb3JkZXIgbGlzdGVkIGJlbG93LgogICAgICAgICAgICAgICAgZGVwcyA9IChjYWxsYmFjay5sZW5ndGggPT09IDEgPyBbJ3JlcXVpcmUnXSA6IFsncmVxdWlyZScsICdleHBvcnRzJywgJ21vZHVsZSddKS5jb25jYXQoZGVwcyk7CiAgICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIC8vSWYgaW4gSUUgNi04IGFuZCBoaXQgYW4gYW5vbnltb3VzIGRlZmluZSgpIGNhbGwsIGRvIHRoZSBpbnRlcmFjdGl2ZQogICAgICAgIC8vd29yay4KICAgICAgICBpZiAodXNlSW50ZXJhY3RpdmUpIHsKICAgICAgICAgICAgbm9kZSA9IGN1cnJlbnRseUFkZGluZ1NjcmlwdCB8fCBnZXRJbnRlcmFjdGl2ZVNjcmlwdCgpOwogICAgICAgICAgICBpZiAobm9kZSkgewogICAgICAgICAgICAgICAgaWYgKCFuYW1lKSB7CiAgICAgICAgICAgICAgICAgICAgbmFtZSA9IG5vZGUuZ2V0QXR0cmlidXRlKCdkYXRhLXJlcXVpcmVtb2R1bGUnKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGNvbnRleHQgPSBjb250ZXh0c1tub2RlLmdldEF0dHJpYnV0ZSgnZGF0YS1yZXF1aXJlY29udGV4dCcpXTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgLy9BbHdheXMgc2F2ZSBvZmYgZXZhbHVhdGluZyB0aGUgZGVmIGNhbGwgdW50aWwgdGhlIHNjcmlwdCBvbmxvYWQgaGFuZGxlci4KICAgICAgICAvL1RoaXMgYWxsb3dzIG11bHRpcGxlIG1vZHVsZXMgdG8gYmUgaW4gYSBmaWxlIHdpdGhvdXQgcHJlbWF0dXJlbHkKICAgICAgICAvL3RyYWNpbmcgZGVwZW5kZW5jaWVzLCBhbmQgYWxsb3dzIGZvciBhbm9ueW1vdXMgbW9kdWxlIHN1cHBvcnQsCiAgICAgICAgLy93aGVyZSB0aGUgbW9kdWxlIG5hbWUgaXMgbm90IGtub3duIHVudGlsIHRoZSBzY3JpcHQgb25sb2FkIGV2ZW50CiAgICAgICAgLy9vY2N1cnMuIElmIG5vIGNvbnRleHQsIHVzZSB0aGUgZ2xvYmFsIHF1ZXVlLCBhbmQgZ2V0IGl0IHByb2Nlc3NlZAogICAgICAgIC8vaW4gdGhlIG9uc2NyaXB0IGxvYWQgY2FsbGJhY2suCiAgICAgICAgaWYgKGNvbnRleHQpIHsKICAgICAgICAgICAgY29udGV4dC5kZWZRdWV1ZS5wdXNoKFtuYW1lLCBkZXBzLCBjYWxsYmFja10pOwogICAgICAgICAgICBjb250ZXh0LmRlZlF1ZXVlTWFwW25hbWVdID0gdHJ1ZTsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBnbG9iYWxEZWZRdWV1ZS5wdXNoKFtuYW1lLCBkZXBzLCBjYWxsYmFja10pOwogICAgICAgIH0KICAgIH07CgogICAgZGVmaW5lLmFtZCA9IHsKICAgICAgICBqUXVlcnk6IHRydWUKICAgIH07CgogICAgLyoqCiAgICAgKiBFeGVjdXRlcyB0aGUgdGV4dC4gTm9ybWFsbHkganVzdCB1c2VzIGV2YWwsIGJ1dCBjYW4gYmUgbW9kaWZpZWQKICAgICAqIHRvIHVzZSBhIGJldHRlciwgZW52aXJvbm1lbnQtc3BlY2lmaWMgY2FsbC4gT25seSB1c2VkIGZvciB0cmFuc3BpbGluZwogICAgICogbG9hZGVyIHBsdWdpbnMsIG5vdCBmb3IgcGxhaW4gSlMgbW9kdWxlcy4KICAgICAqIEBwYXJhbSB7U3RyaW5nfSB0ZXh0IHRoZSB0ZXh0IHRvIGV4ZWN1dGUvZXZhbHVhdGUuCiAgICAgKi8KICAgIHJlcS5leGVjID0gZnVuY3Rpb24gKHRleHQpIHsKICAgICAgICAvKmpzbGludCBldmlsOiB0cnVlICovCiAgICAgICAgcmV0dXJuIGV2YWwodGV4dCk7CiAgICB9OwoKICAgIC8vU2V0IHVwIHdpdGggY29uZmlnIGluZm8uCiAgICByZXEoY2ZnKTsKfSh0aGlzLCAodHlwZW9mIHNldFRpbWVvdXQgPT09ICd1bmRlZmluZWQnID8gdW5kZWZpbmVkIDogc2V0VGltZW91dCkpKTsKCg==", + "headers": [ + [ + "content-type", + "text/javascript; charset=utf-8" + ] + ], + "ok": true, + "status": 200, + "status_text": "" + }, + "https://localhost:6006/experiment/defaultExperimentId/data/plugin/timeseries/timeSeries?requests=%5B%7B%22plugin%22:%22scalars%22,%22tag%22:%22coco/bbox_mAP_75%22%7D%5D": { + "data": "W3sicGx1Z2luIjogInNjYWxhcnMiLCAidGFnIjogImNvY28vYmJveF9tQVBfNzUiLCAicnVuVG9TZXJpZXMiOiB7InJ0bWRldF90aW55XzF4YjQtMjBlX2JhbGxvb24vMjAyMzA0MTdfMTAyODM1L3Zpc19kYXRhIjogW3sid2FsbFRpbWUiOiAxNjgxNzI3MzM1LjQ0MjQ4NjMsICJzdGVwIjogMSwgInZhbHVlIjogMC4wNTA5OTk5OTkwNDYzMjU2ODR9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM0NS42MzkxMzkyLCAic3RlcCI6IDIsICJ2YWx1ZSI6IDAuMTQ2OTk5OTk5ODgwNzkwN30sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzUyLjk2ODg0NywgInN0ZXAiOiAzLCAidmFsdWUiOiAwLjM1NjAwMDAwNjE5ODg4MzA2fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczNjIuNDkxNDgxOCwgInN0ZXAiOiA0LCAidmFsdWUiOiAwLjQ0OTAwMDAwMDk1MzY3NDN9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM3MS42NTA2NjE3LCAic3RlcCI6IDUsICJ2YWx1ZSI6IDAuNDc2MDAwMDEwOTY3MjU0NjR9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM4MS40MjcxMjQ1LCAic3RlcCI6IDYsICJ2YWx1ZSI6IDAuNDkyMDAwMDEzNTg5ODU5fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczODkuNTM3NTgyNCwgInN0ZXAiOiA3LCAidmFsdWUiOiAwLjU1ODAwMDAyODEzMzM5MjN9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM5Ny42ODUxMiwgInN0ZXAiOiA4LCAidmFsdWUiOiAwLjYyMDAwMDAwNDc2ODM3MTZ9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQwNy4wMzMzNDkzLCAic3RlcCI6IDksICJ2YWx1ZSI6IDAuNjg5MDAwMDEwNDkwNDE3NX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDE2LjAzOTQ5MzYsICJzdGVwIjogMTAsICJ2YWx1ZSI6IDAuNjk4MDAwMDEzODI4Mjc3Nn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDI1LjY1NTM4MzMsICJzdGVwIjogMTEsICJ2YWx1ZSI6IDAuNzA4OTk5OTkxNDE2OTMxMn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDMzLjAwNDM3MjEsICJzdGVwIjogMTIsICJ2YWx1ZSI6IDAuNjk1OTk5OTc5OTcyODM5NH0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDQyLjA0OTg1ODYsICJzdGVwIjogMTMsICJ2YWx1ZSI6IDAuNzE3OTk5OTk0NzU0NzkxM30sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDQ3LjkzNDg2MjksICJzdGVwIjogMTQsICJ2YWx1ZSI6IDAuNzA4MDAwMDA0MjkxNTM0NH0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDU3Ljk3NTQzNDMsICJzdGVwIjogMTUsICJ2YWx1ZSI6IDAuNzAwOTk5OTc1MjA0NDY3OH0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDYzLjgyNjA1NDgsICJzdGVwIjogMTYsICJ2YWx1ZSI6IDAuNzQwOTk5OTk2NjYyMTM5OX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDczLjQ2ODgzNjgsICJzdGVwIjogMTcsICJ2YWx1ZSI6IDAuNzM2MDAwMDAxNDMwNTExNX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDgwLjc5MDA2NjcsICJzdGVwIjogMTgsICJ2YWx1ZSI6IDAuNzIwMDAwMDI4NjEwMjI5NX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDg4LjkyNDA1OTQsICJzdGVwIjogMTksICJ2YWx1ZSI6IDAuNzEzOTk5OTg2NjQ4NTU5Nn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDk0LjU2OTU2ODQsICJzdGVwIjogMjAsICJ2YWx1ZSI6IDAuNzV9XX19XQ==", + "ok": true, + "headers": [ + [ + "content-type", + "application/json" + ] + ], + "status": 200, + "status_text": "" + }, + "https://localhost:6006/experiment/defaultExperimentId/data/plugin/timeseries/timeSeries?requests=%5B%7B%22plugin%22:%22scalars%22,%22tag%22:%22coco/bbox_mAP_l%22%7D%5D": { + "data": "W3sicGx1Z2luIjogInNjYWxhcnMiLCAidGFnIjogImNvY28vYmJveF9tQVBfbCIsICJydW5Ub1NlcmllcyI6IHsicnRtZGV0X3RpbnlfMXhiNC0yMGVfYmFsbG9vbi8yMDIzMDQxN18xMDI4MzUvdmlzX2RhdGEiOiBbeyJ3YWxsVGltZSI6IDE2ODE3MjczMzUuNDQyNTc2NiwgInN0ZXAiOiAxLCAidmFsdWUiOiAwLjA1NDAwMDAwMTQwMDcwOTE1fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczNDUuNjM5MTk1MiwgInN0ZXAiOiAyLCAidmFsdWUiOiAwLjE2NDAwMDAwNDUyOTk1M30sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzUyLjk2ODkwMzgsICJzdGVwIjogMywgInZhbHVlIjogMC40MDkwMDAwMDkyOTgzMjQ2fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczNjIuNDkxNTcxLCAic3RlcCI6IDQsICJ2YWx1ZSI6IDAuNTI0OTk5OTc2MTU4MTQyMX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3MzcxLjY1MDc2MzMsICJzdGVwIjogNSwgInZhbHVlIjogMC41NTU5OTk5OTQyNzc5NTQxfSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczODEuNDI3MTg0NiwgInN0ZXAiOiA2LCAidmFsdWUiOiAwLjU4Mzk5OTk5MTQxNjkzMTJ9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM4OS41Mzc2ODA0LCAic3RlcCI6IDcsICJ2YWx1ZSI6IDAuNjEyOTk5OTc1NjgxMzA0OX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3Mzk3LjY4NTIxMzgsICJzdGVwIjogOCwgInZhbHVlIjogMC42NjYwMDAwMDg1ODMwNjg4fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0MDcuMDMzNDQ5LCAic3RlcCI6IDksICJ2YWx1ZSI6IDAuNzUwOTk5OTg3MTI1Mzk2N30sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDE2LjAzOTU3NjMsICJzdGVwIjogMTAsICJ2YWx1ZSI6IDAuNzYwOTk5OTc3NTg4NjUzNn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDI1LjY1NTQzOTYsICJzdGVwIjogMTEsICJ2YWx1ZSI6IDAuNzc3OTk5OTk3MTM4OTc3fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0MzMuMDA0NDI5LCAic3RlcCI6IDEyLCAidmFsdWUiOiAwLjc1fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0NDIuMDQ5OTE4MiwgInN0ZXAiOiAxMywgInZhbHVlIjogMC43NzEwMDAwMjc2NTY1NTUyfSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0NDcuOTM0OTE4OSwgInN0ZXAiOiAxNCwgInZhbHVlIjogMC43ODIwMDAwMDUyNDUyMDg3fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0NTcuOTc1NDg5NiwgInN0ZXAiOiAxNSwgInZhbHVlIjogMC43ODc5OTk5ODc2MDIyMzM5fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0NjMuODI2MTEwNCwgInN0ZXAiOiAxNiwgInZhbHVlIjogMC44MDAwMDAwMTE5MjA5Mjl9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ3My40Njg4OTIzLCAic3RlcCI6IDE3LCAidmFsdWUiOiAwLjgxMDk5OTk4OTUwOTU4MjV9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ4MC43OTAxMjMsICJzdGVwIjogMTgsICJ2YWx1ZSI6IDAuODAxOTk5OTg2MTcxNzIyNH0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDg4LjkyNDEyLCAic3RlcCI6IDE5LCAidmFsdWUiOiAwLjc4NjAwMDAxMzM1MTQ0MDR9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ5NC41Njk2Mjc4LCAic3RlcCI6IDIwLCAidmFsdWUiOiAwLjc4NTAwMDAyNjIyNjA0Mzd9XX19XQ==", + "ok": true, + "headers": [ + [ + "content-type", + "application/json" + ] + ], + "status": 200, + "status_text": "" + }, + "https://localhost:6006/experiment/defaultExperimentId/data/plugin/timeseries/timeSeries?requests=%5B%7B%22plugin%22:%22scalars%22,%22tag%22:%22coco/bbox_mAP_m%22%7D%5D": { + "data": "W3sicGx1Z2luIjogInNjYWxhcnMiLCAidGFnIjogImNvY28vYmJveF9tQVBfbSIsICJydW5Ub1NlcmllcyI6IHsicnRtZGV0X3RpbnlfMXhiNC0yMGVfYmFsbG9vbi8yMDIzMDQxN18xMDI4MzUvdmlzX2RhdGEiOiBbeyJ3YWxsVGltZSI6IDE2ODE3MjczMzUuNDQyNTQ5MiwgInN0ZXAiOiAxLCAidmFsdWUiOiAwLjA3NTk5OTk5NzU1NjIwOTU2fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczNDUuNjM5MTc3NiwgInN0ZXAiOiAyLCAidmFsdWUiOiAwLjA4MTAwMDAwMDIzODQxODU4fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczNTIuOTY4ODg2LCAic3RlcCI6IDMsICJ2YWx1ZSI6IDAuMDk3OTk5OTk3NDM3MDAwMjd9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM2Mi40OTE1NDA3LCAic3RlcCI6IDQsICJ2YWx1ZSI6IDAuMTE1OTk5OTk2NjYyMTM5ODl9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM3MS42NTA3MzA4LCAic3RlcCI6IDUsICJ2YWx1ZSI6IDAuMTA5OTk5OTk5NDAzOTUzNTV9LCB7IndhbGxUaW1lIjogMTY4MTcyNzM4MS40MjcxNjYsICJzdGVwIjogNiwgInZhbHVlIjogMC4xNzI5OTk5OTI5NjY2NTE5Mn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3Mzg5LjUzNzY0MjcsICJzdGVwIjogNywgInZhbHVlIjogMC4xODYwMDAwMDQ0MTA3NDM3fSwgeyJ3YWxsVGltZSI6IDE2ODE3MjczOTcuNjg1MTg3LCAic3RlcCI6IDgsICJ2YWx1ZSI6IDAuMjMxMDAwMDA2MTk4ODgzMDZ9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQwNy4wMzM0MTY1LCAic3RlcCI6IDksICJ2YWx1ZSI6IDAuMjI2OTk5OTk4MDkyNjUxMzd9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQxNi4wMzk1NTUsICJzdGVwIjogMTAsICJ2YWx1ZSI6IDAuMjczMDAwMDAxOTA3MzQ4NjN9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQyNS42NTU0MjE3LCAic3RlcCI6IDExLCAidmFsdWUiOiAwLjIzMTAwMDAwNjE5ODg4MzA2fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0MzMuMDA0NDExNSwgInN0ZXAiOiAxMiwgInZhbHVlIjogMC4yMTc5OTk5OTQ3NTQ3OTEyNn0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDQyLjA0OTg5OTgsICJzdGVwIjogMTMsICJ2YWx1ZSI6IDAuMjQxOTk5OTk4Njg4Njk3ODF9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ0Ny45MzQ5MDE1LCAic3RlcCI6IDE0LCAidmFsdWUiOiAwLjIxOTk5OTk5ODgwNzkwNzF9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ1Ny45NzU0NzI1LCAic3RlcCI6IDE1LCAidmFsdWUiOiAwLjIzMTk5OTk5MzMyNDI3OTc5fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0NjMuODI2MDkyNSwgInN0ZXAiOiAxNiwgInZhbHVlIjogMC4yNjM5OTk5OTg1Njk0ODg1fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0NzMuNDY4ODc1LCAic3RlcCI6IDE3LCAidmFsdWUiOiAwLjI2MTk5OTk5NDUxNjM3Mjd9LCB7IndhbGxUaW1lIjogMTY4MTcyNzQ4MC43OTAxMDUzLCAic3RlcCI6IDE4LCAidmFsdWUiOiAwLjI0MzAwMDAwMDcxNTI1NTc0fSwgeyJ3YWxsVGltZSI6IDE2ODE3Mjc0ODguOTI0MTAxNCwgInN0ZXAiOiAxOSwgInZhbHVlIjogMC4yNzc5OTk5OTcxMzg5NzcwNX0sIHsid2FsbFRpbWUiOiAxNjgxNzI3NDk0LjU2OTYwOTksICJzdGVwIjogMjAsICJ2YWx1ZSI6IDAuMjQwOTk5OTk2NjYyMTM5OX1dfX1d", + "ok": true, + "headers": [ + [ + "content-type", + "application/json" + ] + ], + "status": 200, + "status_text": "" + } + } + }, + "id": "_wpQGXu9aONh", + "outputId": "1093cdcd-5b8a-44b4-f3b0-cbc73083ba78" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "application/javascript": [ + "\n", + " (async () => {\n", + " const url = new URL(await google.colab.kernel.proxyPort(6006, {'cache': true}));\n", + " url.searchParams.set('tensorboardColab', 'true');\n", + " const iframe = document.createElement('iframe');\n", + " iframe.src = url;\n", + " iframe.setAttribute('width', '100%');\n", + " iframe.setAttribute('height', '800');\n", + " iframe.setAttribute('frameborder', 0);\n", + " document.body.appendChild(iframe);\n", + " })();\n", + " " + ] + }, + "metadata": {} + } + ], + "source": [ + "# load tensorboard in colab\n", + "%load_ext tensorboard\n", + "\n", + "# see curves in tensorboard\n", + "%tensorboard --logdir ./work_dirs" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MfQ-yspZLuuI" + }, + "source": [ + "From the tensorboard, we can observe that changes of loss and learning rate. We can see the losses of each branch gradually decrease as the training goes by.\n", + "\n", + "## Test the Trained Detector\n", + "\n", + "After finetuning the detector, let's visualize the prediction results!" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 71, + "referenced_widgets": [ + "01b8530a4c8c48ee9ce8f7ff552a293f", + "4bb4e998336e4889afec5712698e98a8" + ] + }, + "id": "CHDYQSRNNDxt", + "outputId": "10dfc047-8adf-4e30-a737-230c23602a1b" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Loads checkpoint by local backend from path: ./work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco_bbox_mAP_epoch_17.pth\n" + ] + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "Output()" + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "01b8530a4c8c48ee9ce8f7ff552a293f" + } + }, + "metadata": {} + }, + { + "output_type": "display_data", + "data": { + "text/plain": [], + "text/html": [ + "
    \n"
    +            ]
    +          },
    +          "metadata": {}
    +        },
    +        {
    +          "output_type": "display_data",
    +          "data": {
    +            "text/plain": [
    +              "\n"
    +            ],
    +            "text/html": [
    +              "
    \n",
    +              "
    \n" + ] + }, + "metadata": {} + } + ], + "source": [ + "from mmdet.apis import DetInferencer\n", + "import glob\n", + "\n", + "# Choose to use a config\n", + "config = 'configs/rtmdet/rtmdet_tiny_1xb4-20e_balloon.py'\n", + "# Setup a checkpoint file to load\n", + "checkpoint = glob.glob('./work_dirs/rtmdet_tiny_1xb4-20e_balloon/best_coco*.pth')[0]\n", + "\n", + "# Set the device to be used for evaluation\n", + "device = 'cuda:0'\n", + "\n", + "# Initialize the DetInferencer\n", + "inferencer = DetInferencer(config, checkpoint, device)\n", + "\n", + "# Use the detector to do inference\n", + "img = './data/balloon/val/4838031651_3e7b5ea5c7_b.jpg'\n", + "result = inferencer(img, out_dir='./output')" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 618 + }, + "id": "zDzwbUsfN4lR", + "outputId": "26cbef68-3f0e-4958-af2c-48b837f61ae7" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "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6bNN9HNR9bPC2C+w5XiBW/pc9P/ss8826dO6PsBnN2xeYCL/0i0cABBkQAADB9fCxU688Pdmnrs1wZmC93zvXyh+zJv1cyrqXKKgqqrQQ/DWhTqyUgJCORoFeIoiGSTIAwnRA3tCj14AotARcIJKIwCAA7boujKOhBCgiLklVxIXxAWwwzkqmhgYPAYJYgldrWNrB557Aj27UokylrPGOhQhSWBEYCnPABVOxx8DS/xZ+HImCxILwJpnDECsebyymMcBL0QcGLbEJEAAIqEQPL9ITOADappByjmu5HORUwNzcDwDI0gZeqkgQaBnXzs3daZgqABaAI9wAVELpVETYktcOVN7awCU5Ag5AtAIc3NMlCQgA+wpXZJ1nr11AFIAwMJfg/lZH+kXE9Gzc8kznTbvmFkEag4zsCAPzjklRAD7mqYVQjBAaEtogXGiQYIjS+SlShCxbas8S4pp8847P3zztVfu3fno5q2PHbcHo6POx/rg6PDy5cudtH308FNX23g5O3zwqLm/+ehn93qf3KvIHcesM8WekcEBAwJZpxRGiUZTS/KsACUIIgWCyAMlKktclF564aXv/YO/b+L80eGREVG3N9g/eCJY9jsDb30wEPNkoyhy1gqU4AlCeuO9klIgWH9awJh31QGAwf+leBtEnDfVaKEDCyJ45VrrlQqsJgYCMS8EkhIohDh1oecFGvh05M9erLMLCy0+QkRSC2b6XO3g7Dd87s8FFfEveZuOIuccLRT3womHAvDZ810E9nNgxi/+1i+eAi9al+zmeFlH3lqrNUhEzyxAhjTSe6+TmIhYoG3daDwdT2ZNY46ORl/+8lc2npzf2d2cjIu6Lre3d3vd4be//cVPPr4eYAzONz/80Q/ee/ejfr/P0HS6q2Vpj44n+/uzjY2r3/vu3zLfckUxta567+c/eu65K0+2n159/oXJpLQZnjt34donNz+9u7mysrq6sjGbTTvdxDl3dHTy5a+88cYbr7/99ttHmRudTLxnLxgUTSaTzc3NqqqWl5fruj44ONBaplnqvZVSrq+vtzUsL/WyVEupr924PpkWT55uXb/+SRTLDz76+euvv/7tb333ww8/vnfvHlO0vnbxwvolT7acVm+8/oX79+58+NH7ly5v/Oit73vPl69cePmlL/S7g6pqy3I6nZU/e+enf/b9P/n7/+B3tc6apmGe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\n" + }, + "metadata": {}, + "execution_count": 14 + } + ], + "source": [ + "# Show the output image\n", + "Image.open('./output/vis/4838031651_3e7b5ea5c7_b.jpg')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "F1L8o3rtc37M" + }, + "source": [ + "## What to Do Next?\n", + "\n", + "So far, we have learnt how to test and train a one-stage detector using MMDetection. To further explore MMDetection, you could do several other things as shown below:\n", + "\n", + "- Try YOLO series object detection using [MMYOLO](https://github.com/open-mmlab/mmyolo), also one of the OpenMMLab projects. In MMYOLO, not only can you try all the methods supported in MMDetection but also some YOLO series detectors.\n", + "- Try rotated object detection using [MMRotate](https://github.com/open-mmlab/mmrotate), also one of the OpenMMLab projects. In MMRotate, not only can you try all the methods supported in MMDetection but also some rotated object detectors.\n", + "- Try 3D object detection using [MMDetection3D](https://github.com/open-mmlab/mmdetection3d), also one of the OpenMMLab projects. In MMDetection3D, not only can you try all the methods supported in MMDetection but also some 3D object detectors.\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "b1188048a1f04c2fa77c0d3829da39bd": { + "model_module": "@jupyter-widgets/output", + "model_name": "OutputModel", + "model_module_version": "1.0.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_534561e3c4804bae96a30d44493d701d", + "msg_id": "", + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": "Inference \u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m \u001b[36m \u001b[0m\n", + "text/html": "
    Inference    \n
    \n" + }, + "metadata": {} + } + ] + } + }, + "534561e3c4804bae96a30d44493d701d": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "01b8530a4c8c48ee9ce8f7ff552a293f": { + "model_module": "@jupyter-widgets/output", + "model_name": "OutputModel", + "model_module_version": "1.0.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_4bb4e998336e4889afec5712698e98a8", + "msg_id": "", + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": "Inference \u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m \u001b[36m \u001b[0m\n", + "text/html": "
    Inference    \n
    \n" + }, + "metadata": {} + } + ] + } + }, + "4bb4e998336e4889afec5712698e98a8": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/mmdetection/demo/create_result_gif.py b/mmdetection/demo/create_result_gif.py new file mode 100644 index 00000000..8e56a33a --- /dev/null +++ b/mmdetection/demo/create_result_gif.py @@ -0,0 +1,165 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp + +import matplotlib.patches as mpatches +import matplotlib.pyplot as plt +import mmcv +import numpy as np +from mmengine.utils import scandir + +try: + import imageio +except ImportError: + imageio = None + + +# TODO verify after refactoring analyze_results.py +def parse_args(): + parser = argparse.ArgumentParser(description='Create GIF for demo') + parser.add_argument( + 'image_dir', + help='directory where result ' + 'images save path generated by ‘analyze_results.py’') + parser.add_argument( + '--out', + type=str, + default='result.gif', + help='gif path where will be saved') + args = parser.parse_args() + return args + + +def _generate_batch_data(sampler, batch_size): + batch = [] + for idx in sampler: + batch.append(idx) + if len(batch) == batch_size: + yield batch + batch = [] + if len(batch) > 0: + yield batch + + +def create_gif(frames, gif_name, duration=2): + """Create gif through imageio. + + Args: + frames (list[ndarray]): Image frames + gif_name (str): Saved gif name + duration (int): Display interval (s), + Default: 2 + """ + if imageio is None: + raise RuntimeError('imageio is not installed,' + 'Please use “pip install imageio” to install') + imageio.mimsave(gif_name, frames, 'GIF', duration=duration) + + +def create_frame_by_matplotlib(image_dir, + nrows=1, + fig_size=(300, 300), + font_size=15): + """Create gif frame image through matplotlib. + + Args: + image_dir (str): Root directory of result images + nrows (int): Number of rows displayed, Default: 1 + fig_size (tuple): Figure size of the pyplot figure. + Default: (300, 300) + font_size (int): Font size of texts. Default: 15 + + Returns: + list[ndarray]: image frames + """ + + result_dir_names = os.listdir(image_dir) + assert len(result_dir_names) == 2 + # Longer length has higher priority + result_dir_names.reverse() + + images_list = [] + for dir_names in result_dir_names: + images_list.append(scandir(osp.join(image_dir, dir_names))) + + frames = [] + for paths in _generate_batch_data(zip(*images_list), nrows): + + fig, axes = plt.subplots(nrows=nrows, ncols=2) + fig.suptitle('Good/bad case selected according ' + 'to the COCO mAP of the single image') + + det_patch = mpatches.Patch(color='salmon', label='prediction') + gt_patch = mpatches.Patch(color='royalblue', label='ground truth') + # bbox_to_anchor may need to be finetuned + plt.legend( + handles=[det_patch, gt_patch], + bbox_to_anchor=(1, -0.18), + loc='lower right', + borderaxespad=0.) + + if nrows == 1: + axes = [axes] + + dpi = fig.get_dpi() + # set fig size and margin + fig.set_size_inches( + (fig_size[0] * 2 + fig_size[0] // 20) / dpi, + (fig_size[1] * nrows + fig_size[1] // 3) / dpi, + ) + + fig.tight_layout() + # set subplot margin + plt.subplots_adjust( + hspace=.05, + wspace=0.05, + left=0.02, + right=0.98, + bottom=0.02, + top=0.98) + + for i, (path_tuple, ax_tuple) in enumerate(zip(paths, axes)): + image_path_left = osp.join( + osp.join(image_dir, result_dir_names[0], path_tuple[0])) + image_path_right = osp.join( + osp.join(image_dir, result_dir_names[1], path_tuple[1])) + image_left = mmcv.imread(image_path_left) + image_left = mmcv.rgb2bgr(image_left) + image_right = mmcv.imread(image_path_right) + image_right = mmcv.rgb2bgr(image_right) + + if i == 0: + ax_tuple[0].set_title( + result_dir_names[0], fontdict={'size': font_size}) + ax_tuple[1].set_title( + result_dir_names[1], fontdict={'size': font_size}) + ax_tuple[0].imshow( + image_left, extent=(0, *fig_size, 0), interpolation='bilinear') + ax_tuple[0].axis('off') + ax_tuple[1].imshow( + image_right, + extent=(0, *fig_size, 0), + interpolation='bilinear') + ax_tuple[1].axis('off') + + canvas = fig.canvas + s, (width, height) = canvas.print_to_buffer() + buffer = np.frombuffer(s, dtype='uint8') + img_rgba = buffer.reshape(height, width, 4) + rgb, alpha = np.split(img_rgba, [3], axis=2) + img = rgb.astype('uint8') + + frames.append(img) + + return frames + + +def main(): + args = parse_args() + frames = create_frame_by_matplotlib(args.image_dir) + create_gif(frames, args.out) + + +if __name__ == '__main__': + main() diff --git a/mmdetection/demo/demo.jpg b/mmdetection/demo/demo.jpg new file mode 100644 index 00000000..dd613cee Binary files /dev/null and b/mmdetection/demo/demo.jpg differ diff --git a/mmdetection/demo/demo.mp4 b/mmdetection/demo/demo.mp4 new file mode 100644 index 00000000..6c06d15d Binary files /dev/null and b/mmdetection/demo/demo.mp4 differ diff --git a/mmdetection/demo/demo_mot.mp4 b/mmdetection/demo/demo_mot.mp4 new file mode 100644 index 00000000..12e377e6 Binary files /dev/null and b/mmdetection/demo/demo_mot.mp4 differ diff --git a/mmdetection/demo/demo_multi_model.py b/mmdetection/demo/demo_multi_model.py new file mode 100644 index 00000000..f7935de6 --- /dev/null +++ b/mmdetection/demo/demo_multi_model.py @@ -0,0 +1,212 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Support for multi-model fusion, and currently only the Weighted Box Fusion +(WBF) fusion method is supported. + +References: https://github.com/ZFTurbo/Weighted-Boxes-Fusion + +Example: + + python demo/demo_multi_model.py demo/demo.jpg \ + ./configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_1x_coco.py \ + ./configs/retinanet/retinanet_r50-caffe_fpn_1x_coco.py \ + --checkpoints \ + https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_caffe_fpn_1x_coco/faster_rcnn_r50_caffe_fpn_1x_coco_bbox_mAP-0.378_20200504_180032-c5925ee5.pth \ # noqa + https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_caffe_fpn_1x_coco/retinanet_r50_caffe_fpn_1x_coco_20200531-f11027c5.pth \ + --weights 1 2 +""" + +import argparse +import os.path as osp + +import mmcv +import mmengine +from mmengine.fileio import isdir, join_path, list_dir_or_file +from mmengine.logging import print_log +from mmengine.structures import InstanceData + +from mmdet.apis import DetInferencer +from mmdet.models.utils import weighted_boxes_fusion +from mmdet.registry import VISUALIZERS +from mmdet.structures import DetDataSample + +IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', + '.tiff', '.webp') + + +def parse_args(): + parser = argparse.ArgumentParser( + description='MMDetection multi-model inference demo') + parser.add_argument( + 'inputs', type=str, help='Input image file or folder path.') + parser.add_argument( + 'config', + type=str, + nargs='*', + help='Config file(s), support receive multiple files') + parser.add_argument( + '--checkpoints', + type=str, + nargs='*', + help='Checkpoint file(s), support receive multiple files, ' + 'remember to correspond to the above config', + ) + parser.add_argument( + '--weights', + type=float, + nargs='*', + default=None, + help='weights for each model, remember to ' + 'correspond to the above config') + parser.add_argument( + '--fusion-iou-thr', + type=float, + default=0.55, + help='IoU value for boxes to be a match in wbf') + parser.add_argument( + '--skip-box-thr', + type=float, + default=0.0, + help='exclude boxes with score lower than this variable in wbf') + parser.add_argument( + '--conf-type', + type=str, + default='avg', # avg, max, box_and_model_avg, absent_model_aware_avg + help='how to calculate confidence in weighted boxes in wbf') + parser.add_argument( + '--out-dir', + type=str, + default='outputs', + help='Output directory of images or prediction results.') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + parser.add_argument( + '--pred-score-thr', + type=float, + default=0.3, + help='bbox score threshold') + parser.add_argument( + '--batch-size', type=int, default=1, help='Inference batch size.') + parser.add_argument( + '--show', + action='store_true', + help='Display the image in a popup window.') + parser.add_argument( + '--no-save-vis', + action='store_true', + help='Do not save detection vis results') + parser.add_argument( + '--no-save-pred', + action='store_true', + help='Do not save detection json results') + parser.add_argument( + '--palette', + default='none', + choices=['coco', 'voc', 'citys', 'random', 'none'], + help='Color palette used for visualization') + + args = parser.parse_args() + + if args.no_save_vis and args.no_save_pred: + args.out_dir = '' + + return args + + +def main(): + args = parse_args() + + results = [] + cfg_visualizer = None + dataset_meta = None + + inputs = [] + filename_list = [] + if isdir(args.inputs): + dir = list_dir_or_file( + args.inputs, list_dir=False, suffix=IMG_EXTENSIONS) + for filename in dir: + img = mmcv.imread(join_path(args.inputs, filename)) + inputs.append(img) + filename_list.append(filename) + else: + img = mmcv.imread(args.inputs) + inputs.append(img) + img_name = osp.basename(args.inputs) + filename_list.append(img_name) + + for i, (config, + checkpoint) in enumerate(zip(args.config, args.checkpoints)): + inferencer = DetInferencer( + config, checkpoint, device=args.device, palette=args.palette) + + result_raw = inferencer( + inputs=inputs, + batch_size=args.batch_size, + no_save_vis=True, + pred_score_thr=args.pred_score_thr) + + if i == 0: + cfg_visualizer = inferencer.cfg.visualizer + dataset_meta = inferencer.model.dataset_meta + results = [{ + 'bboxes_list': [], + 'scores_list': [], + 'labels_list': [] + } for _ in range(len(result_raw['predictions']))] + + for res, raw in zip(results, result_raw['predictions']): + res['bboxes_list'].append(raw['bboxes']) + res['scores_list'].append(raw['scores']) + res['labels_list'].append(raw['labels']) + + visualizer = VISUALIZERS.build(cfg_visualizer) + visualizer.dataset_meta = dataset_meta + + for i in range(len(results)): + bboxes, scores, labels = weighted_boxes_fusion( + results[i]['bboxes_list'], + results[i]['scores_list'], + results[i]['labels_list'], + weights=args.weights, + iou_thr=args.fusion_iou_thr, + skip_box_thr=args.skip_box_thr, + conf_type=args.conf_type) + + pred_instances = InstanceData() + pred_instances.bboxes = bboxes + pred_instances.scores = scores + pred_instances.labels = labels + + fusion_result = DetDataSample(pred_instances=pred_instances) + + img_name = filename_list[i] + + if not args.no_save_pred: + out_json_path = ( + args.out_dir + '/preds/' + img_name.split('.')[0] + '.json') + mmengine.dump( + { + 'labels': labels.tolist(), + 'scores': scores.tolist(), + 'bboxes': bboxes.tolist() + }, out_json_path) + + out_file = osp.join(args.out_dir, 'vis', + img_name) if not args.no_save_vis else None + + visualizer.add_datasample( + img_name, + inputs[i][..., ::-1], + data_sample=fusion_result, + show=args.show, + draw_gt=False, + wait_time=0, + pred_score_thr=args.pred_score_thr, + out_file=out_file) + + if not args.no_save_vis: + print_log(f'results have been saved at {args.out_dir}') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/demo/image_demo.py b/mmdetection/demo/image_demo.py new file mode 100644 index 00000000..1f994cb4 --- /dev/null +++ b/mmdetection/demo/image_demo.py @@ -0,0 +1,192 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Image Demo. + +This script adopts a new infenence class, currently supports image path, +np.array and folder input formats, and will support video and webcam +in the future. + +Example: + Save visualizations and predictions results:: + + python demo/image_demo.py demo/demo.jpg rtmdet-s + + python demo/image_demo.py demo/demo.jpg \ + configs/rtmdet/rtmdet_s_8xb32-300e_coco.py \ + --weights rtmdet_s_8xb32-300e_coco_20220905_161602-387a891e.pth + + python demo/image_demo.py demo/demo.jpg \ + glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365 --texts bench + + python demo/image_demo.py demo/demo.jpg \ + glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365 --texts 'bench . car .' + + python demo/image_demo.py demo/demo.jpg \ + glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365 + --texts 'bench . car .' -c + + python demo/image_demo.py demo/demo.jpg \ + glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365 \ + --texts 'There are a lot of cars here.' + + python demo/image_demo.py demo/demo.jpg \ + glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365 \ + --texts '$: coco' + + python demo/image_demo.py demo/demo.jpg \ + glip_atss_swin-t_a_fpn_dyhead_pretrain_obj365 \ + --texts '$: lvis' --pred-score-thr 0.7 \ + --palette random --chunked-size 80 + + python demo/image_demo.py demo/demo.jpg \ + grounding_dino_swin-t_pretrain_obj365_goldg_cap4m \ + --texts '$: lvis' --pred-score-thr 0.4 \ + --palette random --chunked-size 80 + + python demo/image_demo.py demo/demo.jpg \ + grounding_dino_swin-t_pretrain_obj365_goldg_cap4m \ + --texts "a red car in the upper right corner" \ + --tokens-positive -1 + + Visualize prediction results:: + + python demo/image_demo.py demo/demo.jpg rtmdet-ins-s --show + + python demo/image_demo.py demo/demo.jpg rtmdet-ins_s_8xb32-300e_coco \ + --show +""" + +import ast +from argparse import ArgumentParser + +from mmengine.logging import print_log + +from mmdet.apis import DetInferencer +from mmdet.evaluation import get_classes + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument( + 'inputs', type=str, help='Input image file or folder path.') + parser.add_argument( + 'model', + type=str, + help='Config or checkpoint .pth file or the model name ' + 'and alias defined in metafile. The model configuration ' + 'file will try to read from .pth if the parameter is ' + 'a .pth weights file.') + parser.add_argument('--weights', default=None, help='Checkpoint file') + parser.add_argument( + '--out-dir', + type=str, + default='outputs', + help='Output directory of images or prediction results.') + # Once you input a format similar to $: xxx, it indicates that + # the prompt is based on the dataset class name. + # support $: coco, $: voc, $: cityscapes, $: lvis, $: imagenet_det. + # detail to `mmdet/evaluation/functional/class_names.py` + parser.add_argument( + '--texts', help='text prompt, such as "bench . car .", "$: coco"') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + parser.add_argument( + '--pred-score-thr', + type=float, + default=0.3, + help='bbox score threshold') + parser.add_argument( + '--batch-size', type=int, default=1, help='Inference batch size.') + parser.add_argument( + '--show', + action='store_true', + help='Display the image in a popup window.') + parser.add_argument( + '--no-save-vis', + action='store_true', + help='Do not save detection vis results') + parser.add_argument( + '--no-save-pred', + action='store_true', + help='Do not save detection json results') + parser.add_argument( + '--print-result', + action='store_true', + help='Whether to print the results.') + parser.add_argument( + '--palette', + default='none', + choices=['coco', 'voc', 'citys', 'random', 'none'], + help='Color palette used for visualization') + # only for GLIP and Grounding DINO + parser.add_argument( + '--custom-entities', + '-c', + action='store_true', + help='Whether to customize entity names? ' + 'If so, the input text should be ' + '"cls_name1 . cls_name2 . cls_name3 ." format') + parser.add_argument( + '--chunked-size', + '-s', + type=int, + default=-1, + help='If the number of categories is very large, ' + 'you can specify this parameter to truncate multiple predictions.') + # only for Grounding DINO + parser.add_argument( + '--tokens-positive', + '-p', + type=str, + help='Used to specify which locations in the input text are of ' + 'interest to the user. -1 indicates that no area is of interest, ' + 'None indicates ignoring this parameter. ' + 'The two-dimensional array represents the start and end positions.') + + call_args = vars(parser.parse_args()) + + if call_args['no_save_vis'] and call_args['no_save_pred']: + call_args['out_dir'] = '' + + if call_args['model'].endswith('.pth'): + print_log('The model is a weight file, automatically ' + 'assign the model to --weights') + call_args['weights'] = call_args['model'] + call_args['model'] = None + + if call_args['texts'] is not None: + if call_args['texts'].startswith('$:'): + dataset_name = call_args['texts'][3:].strip() + class_names = get_classes(dataset_name) + call_args['texts'] = [tuple(class_names)] + + if call_args['tokens_positive'] is not None: + call_args['tokens_positive'] = ast.literal_eval( + call_args['tokens_positive']) + + init_kws = ['model', 'weights', 'device', 'palette'] + init_args = {} + for init_kw in init_kws: + init_args[init_kw] = call_args.pop(init_kw) + + return init_args, call_args + + +def main(): + init_args, call_args = parse_args() + # TODO: Video and Webcam are currently not supported and + # may consume too much memory if your input folder has a lot of images. + # We will be optimized later. + inferencer = DetInferencer(**init_args) + + chunked_size = call_args.pop('chunked_size') + inferencer.model.test_cfg.chunked_size = chunked_size + + inferencer(**call_args) + + if call_args['out_dir'] != '' and not (call_args['no_save_vis'] + and call_args['no_save_pred']): + print_log(f'results have been saved at {call_args["out_dir"]}') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/demo/inference_demo.ipynb b/mmdetection/demo/inference_demo.ipynb new file mode 100644 index 00000000..36df6f84 --- /dev/null +++ b/mmdetection/demo/inference_demo.ipynb @@ -0,0 +1,1413 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "gCMycQ_2U8SA" + }, + "source": [ + "
    \n", + " \n", + "
     
    \n", + "
    \n", + " OpenMMLab website\n", + " \n", + " \n", + " HOT\n", + " \n", + " \n", + "     \n", + " OpenMMLab platform\n", + " \n", + " \n", + " TRY IT OUT\n", + " \n", + " \n", + "
    \n", + "
     
    \n", + "\n", + "\"Open\n", + "\n", + "[![PyPI](https://img.shields.io/pypi/v/mmdet)](https://pypi.org/project/mmdet)\n", + "[![docs](https://img.shields.io/badge/docs-latest-blue)](https://mmdetection.readthedocs.io/en/latest/)\n", + "[![badge](https://github.com/open-mmlab/mmdetection/workflows/build/badge.svg)](https://github.com/open-mmlab/mmdetection/actions)\n", + "[![codecov](https://codecov.io/gh/open-mmlab/mmdetection/branch/master/graph/badge.svg)](https://codecov.io/gh/open-mmlab/mmdetection)\n", + "[![license](https://img.shields.io/github/license/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/blob/master/LICENSE)\n", + "[![open issues](https://isitmaintained.com/badge/open/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/issues)\n", + "[![issue resolution](https://isitmaintained.com/badge/resolution/open-mmlab/mmdetection.svg)](https://github.com/open-mmlab/mmdetection/issues)\n", + "\n", + "[📘Documentation](https://mmdetection.readthedocs.io/en/3.x/) |\n", + "[🛠️Installation](https://mmdetection.readthedocs.io/en/3.x/get_started.html) |\n", + "[👀Model Zoo](https://mmdetection.readthedocs.io/en/3.x/model_zoo.html) |\n", + "[🆕Update News](https://mmdetection.readthedocs.io/en/3.x/notes/changelog.html) |\n", + "[🚀Ongoing Projects](https://github.com/open-mmlab/mmdetection/projects) |\n", + "[🤔Reporting Issues](https://github.com/open-mmlab/mmdetection/issues/new/choose)\n", + "\n", + "
    \n", + "\n", + "
    \n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + " \"\"\n", + " \n", + " \"\"\n", + "
    " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aGYwt_UjIrqp" + }, + "source": [ + "# Inferencer\n", + "\n", + "In this tutorial, you will learn how to perform inference with a MMDetection `DetInferencer`.\n", + "\n", + "Let's start!\n", + "\n", + "```{note}\n", + "The commands in this tutorial are mainly for Colab.\n", + "You can click the button above, `Open in Colab`, to run this notebook in Colab.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tJxJHruNLb7Y" + }, + "source": [ + "## Install MMDetection" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Wi4LPmsR66sy", + "outputId": "0077a9a3-0183-4002-fe7a-2a12f020cf69" + }, + "outputs": [], + "source": [ + "# Check nvcc version\n", + "!nvcc -V\n", + "# Check GCC version\n", + "!gcc --version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gkGnB9WyHSXB", + "outputId": "5945fe0b-13a5-4f1b-dff9-8beb1df67ab0" + }, + "outputs": [], + "source": [ + "# install dependencies\n", + "%pip install -U openmim\n", + "!mim install \"mmengine>=0.7.0\"\n", + "!mim install \"mmcv>=2.0.0rc4\"\n", + "\n", + "# Install mmdetection\n", + "!rm -rf mmdetection\n", + "!git clone https://github.com/open-mmlab/mmdetection.git -b dev-3.x\n", + "%cd mmdetection\n", + "\n", + "%pip install -e ." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "_YeUiqAoCaoV", + "outputId": "06e4c803-ac46-49e6-b8fa-1a85c23fa482" + }, + "outputs": [], + "source": [ + "from mmengine.utils import get_git_hash\n", + "from mmengine.utils.dl_utils import collect_env as collect_base_env\n", + "\n", + "import mmdet\n", + "\n", + "\n", + "def collect_env():\n", + " \"\"\"Collect the information of the running environments.\"\"\"\n", + " env_info = collect_base_env()\n", + " env_info['MMDetection'] = f'{mmdet.__version__}+{get_git_hash()[:7]}'\n", + " return env_info\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " for name, val in collect_env().items():\n", + " print(f'{name}: {val}')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fLgFRMtP91ue" + }, + "source": [ + "## `DetInferencer`\n", + "\n", + "### Basic Usage\n", + "\n", + "We use the high-level API `DetInferencer` implemented in the MMDetection. This API is created to ease the inference process. The details of the codes can be found [here](https://github.com/open-mmlab/mmdetection/blob/dev-3.x/mmdet/apis/det_inferencer.py)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000, + "referenced_widgets": [ + "6fa2cda48fda43f9bf53a0f533392eba", + "0226fedc26044ab2abdccc4fcbe226f8" + ] + }, + "id": "WJHpC402p2w9", + "outputId": "c2326326-d198-4fce-ec0e-a9cc2e35ba09" + }, + "outputs": [], + "source": [ + "from mmdet.apis import DetInferencer\n", + "\n", + "# Initialize the DetInferencer\n", + "inferencer = DetInferencer('rtmdet_tiny_8xb32-300e_coco')\n", + "\n", + "# Perform inference\n", + "inferencer('demo/demo.jpg', out_dir='./output')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 444 + }, + "id": "34JfPWRRSlNh", + "outputId": "8eec8bc4-4824-47ac-b10f-41538422fb28" + }, + "outputs": [], + "source": [ + "# Show the output image\n", + "from PIL import Image\n", + "Image.open('./output/vis/demo.jpg')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-53WPeyBqRHe" + }, + "source": [ + "### Initialization\n", + "\n", + "Each Inferencer must be initialized with a model. You can also choose the inference device during initialization.\n", + "\n", + "#### Model Initialization\n", + "\n", + "- To infer with MMDetection's pre-trained model, passing its name to the argument `model` can work. The weights will be automatically downloaded and loaded from OpenMMLab's model zoo." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "bbMu3IPtv-cX", + "outputId": "2bceb594-06c8-4c18-e8c6-b1816b0acb23" + }, + "outputs": [], + "source": [ + "inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "AwKtnol3TQlM" + }, + "source": [ + "There is a very easy to list all model names in MMDetection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "3kYfK3ssTIQE", + "outputId": "88c4fbb9-bb92-42af-baaa-14cee5b5bdc1" + }, + "outputs": [], + "source": [ + "# models is a list of model names, and them will print automatically\n", + "models = DetInferencer.list_models('mmdet')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "G-25HR9HTZvr" + }, + "source": [ + "You can load another weight by passing its path/url to `weights`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "j4doHX4exvS1", + "outputId": "54ac0be2-835f-4390-aa0e-3be5236d8cc9" + }, + "outputs": [], + "source": [ + "!mkdir ./checkpoints\n", + "!mim download mmdet --config rtmdet_tiny_8xb32-300e_coco --dest ./checkpoints" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8LQB4EC-Tako", + "outputId": "2cc0960e-d5b5-4c3a-8a0c-cec23989f6a0" + }, + "outputs": [], + "source": [ + "# Setup a checkpoint file to load\n", + "checkpoint = './checkpoints/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth'\n", + "\n", + "# Initialize the DetInferencer\n", + "inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', weights=checkpoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Atft9tjcwgeD" + }, + "source": [ + "- To load custom config and weight, you can pass the path to the config file to `model` and the path to the weight to `weights`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "eukDD4Rzwp9P", + "outputId": "0a34392c-0544-4a90-c844-7628d184efc0" + }, + "outputs": [], + "source": [ + "# Choose to use a config\n", + "config_path = './configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py'\n", + "\n", + "# Setup a checkpoint file to load\n", + "checkpoint = './checkpoints/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth'\n", + "\n", + "# Initialize the DetInferencer\n", + "inferencer = DetInferencer(model=config_path, weights=checkpoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FC1je9iiTuMS" + }, + "source": [ + "- By default, [MMEngine](https://github.com/open-mmlab/mmengine/) dumps config to the weight. If you have a weight trained on MMEngine, you can also pass the path to the weight file to `weights` without specifying `model`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Kenyo80RTx63", + "outputId": "46fe8219-d1a7-4e45-b5a1-b6d21c30be42" + }, + "outputs": [], + "source": [ + "# It will raise an error if the config file cannot be found in the weight. Currently, within the MMDetection model repository, only the weights of ddq-detr-4scale_r50 can be loaded in this manner.\n", + "inferencer = DetInferencer(weights='https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq-detr-4scale_r50_8xb2-12e_coco/ddq-detr-4scale_r50_8xb2-12e_coco_20230809_170711-42528127.pth')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "b-AlYOw4T3AO" + }, + "source": [ + "- Passing config file to `model` without specifying `weight` will result in a randomly initialized model." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "quFQ8abYT6As" + }, + "source": [ + "### Device\n", + "\n", + "Each Inferencer instance is bound to a device.\n", + "By default, the best device is automatically decided by [MMEngine](https://github.com/open-mmlab/mmengine/). You can also alter the device by specifying the `device` argument. For example, you can use the following code to create an Inferencer on GPU 0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Wi6DRpsQPEmV", + "outputId": "9eac2017-cce6-491a-ef51-3e7e2560f107" + }, + "outputs": [], + "source": [ + "inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', device='cuda:0')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "h3pgIACHUXEv" + }, + "source": [ + "To create an Inferencer on CPU:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "JsAotaiRUXWH", + "outputId": "531b1cb0-e986-4e0a-91c9-6d3ad65544e7" + }, + "outputs": [], + "source": [ + "inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', device='cpu')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0a4Zw5plUisX" + }, + "source": [ + "### Inference\n", + "\n", + "Once the Inferencer is initialized, you can directly pass in the raw data to be inferred and get the inference results from return values.\n", + "\n", + "#### Input\n", + "\n", + "Input can be either of these types:\n", + "\n", + "- str: Path/URL to the image." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000, + "referenced_widgets": [ + "2abd7eef6f1f4b9c865a466b3dd5ef24", + "8951ec1ee7164f7ca7239a37e80e98ea" + ] + }, + "id": "C4McAmYdUnCL", + "outputId": "50bea3e2-a912-497e-cee9-26109dccdc12" + }, + "outputs": [], + "source": [ + "inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', device='cuda:0')\n", + "inferencer('demo/demo.jpg')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3G_TPKrMUp2T" + }, + "source": [ + "- array: Image in numpy array. It should be in BGR order." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000, + "referenced_widgets": [ + "59bfd22c751f4ed4baefa466e7653315", + "0164804ae2f842fe8d2a4c5414c4a0c2" + ] + }, + "id": "-M1qGlfaUpha", + "outputId": "5a06cfe8-e056-4d56-c8e9-489e8f6633a0" + }, + "outputs": [], + "source": [ + "import mmcv\n", + "array = mmcv.imread('demo/demo.jpg')\n", + "inferencer(array)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "I45B_CtzUuh2" + }, + "source": [ + "- list: A list of basic types above. Each element in the list will be processed separately." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000, + "referenced_widgets": [ + "a64a6eb038c44236b80579b2bfc4b8e3", + "eef25a0509854f98883395a2c0fc2134", + "f87f0b153b0342ad99dcd320a1302c92", + "f6634888109048069b6844e9f9b4ec13" + ] + }, + "id": "k1IXIWXHUwKP", + "outputId": "0af73b0b-d703-4cbc-91ad-052f0b521d50" + }, + "outputs": [], + "source": [ + "inferencer(['tests/data/color.jpg', 'tests/data/gray.jpg'])\n", + "# You can even mix the types\n", + "inferencer(['tests/data/color.jpg', array])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "hUGrTtxrVBAS" + }, + "source": [ + "- str: Path to the directory. All images in the directory will be processed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000, + "referenced_widgets": [ + "07ed8efcd87a40059af36f0c43ef5147", + "69ce7e58e27f4e1186ab0afcb99d37c3" + ] + }, + "id": "JWK10ZD6VDDE", + "outputId": "91418597-d9ea-4613-b141-16bc8bcc8caf" + }, + "outputs": [], + "source": [ + "inferencer('tests/data/')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BQxEVr2pVGen" + }, + "source": [ + "### Output\n", + "\n", + "By default, each `Inferencer` returns the prediction results in a dictionary format.\n", + "\n", + "- `visualization` contains the visualized predictions.\n", + "\n", + "- `predictions` contains the predictions results in a json-serializable format. But it's an empty list by default unless `return_vis=True`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 306, + "referenced_widgets": [ + "95674a6baa1842d2981fe60b31ab6cad", + "7e62816d1f6c441fb98c1f8e942fff1d" + ] + }, + "id": "m6a8T4goU8Sq", + "outputId": "6f74098f-a3d3-4897-c58f-68fae88889af" + }, + "outputs": [], + "source": [ + "# Show the structure of result dict\n", + "from rich.pretty import pprint\n", + "\n", + "result = inferencer('demo/demo.jpg')\n", + "pprint(result, max_length=4)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "a93hFT0jVkrR" + }, + "source": [ + "If you wish to get the raw outputs from the model, you can set `return_datasamples` to `True` to get the original `DataSample`, which will be stored in `predictions`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000, + "referenced_widgets": [ + "060f510b5bda498583d7212060bb528c", + "1bb724cb12c240a18f651dd99842e5b0" + ] + }, + "id": "U5DFI7QAVbnP", + "outputId": "effaf3ec-2476-4b64-dcbd-802a18a26479" + }, + "outputs": [], + "source": [ + "result = inferencer('demo/demo.jpg', return_datasamples=True)\n", + "pprint(result, max_length=4)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JHdcUnGzVsk1" + }, + "source": [ + "#### Dumping Results\n", + "\n", + "Apart from obtaining predictions from the return value, you can also export the predictions/visualizations to files by setting `out_dir` and `no_save_pred`/`no_save_vis` arguments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000, + "referenced_widgets": [ + "38083c2f29604d1d9a7dcf9845dfbf33", + "54cdfe55e0f04df9ab844961a089fe2f" + ] + }, + "id": "0dr-ixmfVtng", + "outputId": "af22d458-9aed-41e2-f675-e017a0cb588b" + }, + "outputs": [], + "source": [ + "inferencer('demo/demo.jpg', out_dir='outputs/', no_save_pred=False)" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "0164804ae2f842fe8d2a4c5414c4a0c2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "0226fedc26044ab2abdccc4fcbe226f8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "060f510b5bda498583d7212060bb528c": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_1bb724cb12c240a18f651dd99842e5b0", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
    Inference    \n
    \n", + "text/plain": "Inference \u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m \u001b[36m \u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "07ed8efcd87a40059af36f0c43ef5147": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_69ce7e58e27f4e1186ab0afcb99d37c3", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
    Inference  13.5 it/s  \n
    \n", + "text/plain": "Inference \u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m \u001b[35m13.5 it/s\u001b[0m \u001b[36m \u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "1bb724cb12c240a18f651dd99842e5b0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2abd7eef6f1f4b9c865a466b3dd5ef24": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_8951ec1ee7164f7ca7239a37e80e98ea", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
    Inference    \n
    \n", + "text/plain": "Inference \u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m \u001b[36m \u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "38083c2f29604d1d9a7dcf9845dfbf33": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_54cdfe55e0f04df9ab844961a089fe2f", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
    Inference    \n
    \n", + "text/plain": "Inference \u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m \u001b[36m \u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "54cdfe55e0f04df9ab844961a089fe2f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "59bfd22c751f4ed4baefa466e7653315": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_0164804ae2f842fe8d2a4c5414c4a0c2", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
    Inference    \n
    \n", + "text/plain": "Inference \u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m \u001b[36m \u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "69ce7e58e27f4e1186ab0afcb99d37c3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "6fa2cda48fda43f9bf53a0f533392eba": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_0226fedc26044ab2abdccc4fcbe226f8", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
    Inference    \n
    \n", + "text/plain": "Inference \u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m \u001b[36m \u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "7e62816d1f6c441fb98c1f8e942fff1d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8951ec1ee7164f7ca7239a37e80e98ea": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "95674a6baa1842d2981fe60b31ab6cad": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_7e62816d1f6c441fb98c1f8e942fff1d", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
    Inference    \n
    \n", + "text/plain": "Inference \u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m \u001b[36m \u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "a64a6eb038c44236b80579b2bfc4b8e3": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_eef25a0509854f98883395a2c0fc2134", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
    Inference  9.7 it/s  \n
    \n", + "text/plain": "Inference \u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m \u001b[35m9.7 it/s\u001b[0m \u001b[36m \u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "eef25a0509854f98883395a2c0fc2134": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f6634888109048069b6844e9f9b4ec13": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f87f0b153b0342ad99dcd320a1302c92": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_f6634888109048069b6844e9f9b4ec13", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
    Inference  9.0 it/s  \n
    \n", + "text/plain": "Inference \u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;249;38;114m━\u001b[0m\u001b[38;2;244;38;112m━\u001b[0m\u001b[38;2;230;39;108m━\u001b[0m\u001b[38;2;209;42;102m━\u001b[0m\u001b[38;2;183;44;94m━\u001b[0m\u001b[38;2;153;48;86m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;58;58;58m━\u001b[0m\u001b[38;2;62;57;59m━\u001b[0m\u001b[38;2;76;56;63m━\u001b[0m\u001b[38;2;97;53;69m━\u001b[0m\u001b[38;2;123;51;77m━\u001b[0m \u001b[35m9.0 it/s\u001b[0m \u001b[36m \u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/mmdetection/demo/large_image.jpg b/mmdetection/demo/large_image.jpg new file mode 100644 index 00000000..1abbc5d9 Binary files /dev/null and b/mmdetection/demo/large_image.jpg differ diff --git a/mmdetection/demo/large_image_demo.py b/mmdetection/demo/large_image_demo.py new file mode 100644 index 00000000..f3d8d22f --- /dev/null +++ b/mmdetection/demo/large_image_demo.py @@ -0,0 +1,282 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Perform MMDET inference on large images (as satellite imagery) as: + +```shell +wget -P checkpoint https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_fpn_2x_coco/faster_rcnn_r101_fpn_2x_coco_bbox_mAP-0.398_20200504_210455-1d2dac9c.pth # noqa: E501, E261. + +python demo/large_image_demo.py \ + demo/large_image.jpg \ + configs/faster_rcnn/faster-rcnn_r101_fpn_2x_coco.py \ + checkpoint/faster_rcnn_r101_fpn_2x_coco_bbox_mAP-0.398_20200504_210455-1d2dac9c.pth +``` +""" + +import os +import random +from argparse import ArgumentParser +from pathlib import Path + +import mmcv +import numpy as np +from mmengine.config import Config, ConfigDict +from mmengine.logging import print_log +from mmengine.utils import ProgressBar + +from mmdet.apis import inference_detector, init_detector + +try: + from sahi.slicing import slice_image +except ImportError: + raise ImportError('Please run "pip install -U sahi" ' + 'to install sahi first for large image inference.') + +from mmdet.registry import VISUALIZERS +from mmdet.utils.large_image import merge_results_by_nms, shift_predictions +from mmdet.utils.misc import get_file_list + + +def parse_args(): + parser = ArgumentParser( + description='Perform MMDET inference on large images.') + parser.add_argument( + 'img', help='Image path, include image file, dir and URL.') + parser.add_argument('config', help='Config file') + parser.add_argument('checkpoint', help='Checkpoint file') + parser.add_argument( + '--out-dir', default='./output', help='Path to output file') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + parser.add_argument( + '--show', action='store_true', help='Show the detection results') + parser.add_argument( + '--tta', + action='store_true', + help='Whether to use test time augmentation') + parser.add_argument( + '--score-thr', type=float, default=0.3, help='Bbox score threshold') + parser.add_argument( + '--patch-size', type=int, default=640, help='The size of patches') + parser.add_argument( + '--patch-overlap-ratio', + type=float, + default=0.25, + help='Ratio of overlap between two patches') + parser.add_argument( + '--merge-iou-thr', + type=float, + default=0.25, + help='IoU threshould for merging results') + parser.add_argument( + '--merge-nms-type', + type=str, + default='nms', + help='NMS type for merging results') + parser.add_argument( + '--batch-size', + type=int, + default=1, + help='Batch size, must greater than or equal to 1') + parser.add_argument( + '--debug', + action='store_true', + help='Export debug results before merging') + parser.add_argument( + '--save-patch', + action='store_true', + help='Save the results of each patch. ' + 'The `--debug` must be enabled.') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + + config = args.config + + if isinstance(config, (str, Path)): + config = Config.fromfile(config) + elif not isinstance(config, Config): + raise TypeError('config must be a filename or Config object, ' + f'but got {type(config)}') + if 'init_cfg' in config.model.backbone: + config.model.backbone.init_cfg = None + + if args.tta: + assert 'tta_model' in config, 'Cannot find ``tta_model`` in config.' \ + " Can't use tta !" + assert 'tta_pipeline' in config, 'Cannot find ``tta_pipeline`` ' \ + "in config. Can't use tta !" + config.model = ConfigDict(**config.tta_model, module=config.model) + test_data_cfg = config.test_dataloader.dataset + while 'dataset' in test_data_cfg: + test_data_cfg = test_data_cfg['dataset'] + + test_data_cfg.pipeline = config.tta_pipeline + + # TODO: TTA mode will error if cfg_options is not set. + # This is an mmdet issue and needs to be fixed later. + # build the model from a config file and a checkpoint file + model = init_detector( + config, args.checkpoint, device=args.device, cfg_options={}) + + if not os.path.exists(args.out_dir) and not args.show: + os.mkdir(args.out_dir) + + # init visualizer + visualizer = VISUALIZERS.build(model.cfg.visualizer) + visualizer.dataset_meta = model.dataset_meta + + # get file list + files, source_type = get_file_list(args.img) + + # start detector inference + print(f'Performing inference on {len(files)} images.... ' + 'This may take a while.') + progress_bar = ProgressBar(len(files)) + for file in files: + # read image + img = mmcv.imread(file) + + # arrange slices + height, width = img.shape[:2] + sliced_image_object = slice_image( + img, + slice_height=args.patch_size, + slice_width=args.patch_size, + auto_slice_resolution=False, + overlap_height_ratio=args.patch_overlap_ratio, + overlap_width_ratio=args.patch_overlap_ratio, + ) + # perform sliced inference + slice_results = [] + start = 0 + while True: + # prepare batch slices + end = min(start + args.batch_size, len(sliced_image_object)) + images = [] + for sliced_image in sliced_image_object.images[start:end]: + images.append(sliced_image) + + # forward the model + slice_results.extend(inference_detector(model, images)) + + if end >= len(sliced_image_object): + break + start += args.batch_size + + if source_type['is_dir']: + filename = os.path.relpath(file, args.img).replace('/', '_') + else: + filename = os.path.basename(file) + + img = mmcv.imconvert(img, 'bgr', 'rgb') + out_file = None if args.show else os.path.join(args.out_dir, filename) + + # export debug images + if args.debug: + # export sliced image results + name, suffix = os.path.splitext(filename) + + shifted_instances = shift_predictions( + slice_results, + sliced_image_object.starting_pixels, + src_image_shape=(height, width)) + merged_result = slice_results[0].clone() + merged_result.pred_instances = shifted_instances + + debug_file_name = name + '_debug' + suffix + debug_out_file = None if args.show else os.path.join( + args.out_dir, debug_file_name) + visualizer.set_image(img.copy()) + + debug_grids = [] + for starting_point in sliced_image_object.starting_pixels: + start_point_x = starting_point[0] + start_point_y = starting_point[1] + end_point_x = start_point_x + args.patch_size + end_point_y = start_point_y + args.patch_size + debug_grids.append( + [start_point_x, start_point_y, end_point_x, end_point_y]) + debug_grids = np.array(debug_grids) + debug_grids[:, 0::2] = np.clip(debug_grids[:, 0::2], 1, + img.shape[1] - 1) + debug_grids[:, 1::2] = np.clip(debug_grids[:, 1::2], 1, + img.shape[0] - 1) + + palette = np.random.randint(0, 256, size=(len(debug_grids), 3)) + palette = [tuple(c) for c in palette] + line_styles = random.choices(['-', '-.', ':'], k=len(debug_grids)) + visualizer.draw_bboxes( + debug_grids, + edge_colors=palette, + alpha=1, + line_styles=line_styles) + visualizer.draw_bboxes( + debug_grids, face_colors=palette, alpha=0.15) + + visualizer.draw_texts( + list(range(len(debug_grids))), + debug_grids[:, :2] + 5, + colors='w') + + visualizer.add_datasample( + debug_file_name, + visualizer.get_image(), + data_sample=merged_result, + draw_gt=False, + show=args.show, + wait_time=0, + out_file=debug_out_file, + pred_score_thr=args.score_thr, + ) + + if args.save_patch: + debug_patch_out_dir = os.path.join(args.out_dir, + f'{name}_patch') + for i, slice_result in enumerate(slice_results): + patch_out_file = os.path.join( + debug_patch_out_dir, + f'{filename}_slice_{i}_result.jpg') + image = mmcv.imconvert(sliced_image_object.images[i], + 'bgr', 'rgb') + + visualizer.add_datasample( + 'patch_result', + image, + data_sample=slice_result, + draw_gt=False, + show=False, + wait_time=0, + out_file=patch_out_file, + pred_score_thr=args.score_thr, + ) + + image_result = merge_results_by_nms( + slice_results, + sliced_image_object.starting_pixels, + src_image_shape=(height, width), + nms_cfg={ + 'type': args.merge_nms_type, + 'iou_threshold': args.merge_iou_thr + }) + + visualizer.add_datasample( + filename, + img, + data_sample=image_result, + draw_gt=False, + show=args.show, + wait_time=0, + out_file=out_file, + pred_score_thr=args.score_thr, + ) + progress_bar.update() + + if not args.show or (args.debug and args.save_patch): + print_log( + f'\nResults have been saved at {os.path.abspath(args.out_dir)}') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/demo/mot_demo.py b/mmdetection/demo/mot_demo.py new file mode 100644 index 00000000..4595cdc5 --- /dev/null +++ b/mmdetection/demo/mot_demo.py @@ -0,0 +1,130 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import os.path as osp +import tempfile +from argparse import ArgumentParser + +import mmcv +import mmengine +from mmengine.registry import init_default_scope + +from mmdet.apis import inference_mot, init_track_model +from mmdet.registry import VISUALIZERS + +IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png') + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument( + 'inputs', type=str, help='Input image file or folder path.') + parser.add_argument('config', help='config file') + parser.add_argument('--checkpoint', help='checkpoint file') + parser.add_argument('--detector', help='det checkpoint file') + parser.add_argument('--reid', help='reid checkpoint file') + parser.add_argument( + '--device', default='cuda:0', help='device used for inference') + parser.add_argument( + '--score-thr', + type=float, + default=0.0, + help='The threshold of score to filter bboxes.') + parser.add_argument( + '--out', help='output video file (mp4 format) or folder') + parser.add_argument( + '--show', + action='store_true', + help='whether show the results on the fly') + parser.add_argument('--fps', help='FPS of the output video') + args = parser.parse_args() + return args + + +def main(args): + assert args.out or args.show + # load images + if osp.isdir(args.inputs): + imgs = sorted( + filter(lambda x: x.endswith(IMG_EXTENSIONS), + os.listdir(args.inputs)), + key=lambda x: int(x.split('.')[0])) + in_video = False + else: + imgs = mmcv.VideoReader(args.inputs) + in_video = True + + # define output + out_video = False + if args.out is not None: + if args.out.endswith('.mp4'): + out_video = True + out_dir = tempfile.TemporaryDirectory() + out_path = out_dir.name + _out = args.out.rsplit(os.sep, 1) + if len(_out) > 1: + os.makedirs(_out[0], exist_ok=True) + else: + out_path = args.out + os.makedirs(out_path, exist_ok=True) + + fps = args.fps + if args.show or out_video: + if fps is None and in_video: + fps = imgs.fps + if not fps: + raise ValueError('Please set the FPS for the output video.') + fps = int(fps) + + init_default_scope('mmdet') + + # build the model from a config file and a checkpoint file + model = init_track_model( + args.config, + args.checkpoint, + args.detector, + args.reid, + device=args.device) + + # build the visualizer + visualizer = VISUALIZERS.build(model.cfg.visualizer) + visualizer.dataset_meta = model.dataset_meta + + prog_bar = mmengine.ProgressBar(len(imgs)) + # test and show/save the images + for i, img in enumerate(imgs): + if isinstance(img, str): + img_path = osp.join(args.inputs, img) + img = mmcv.imread(img_path) + # result [TrackDataSample] + result = inference_mot(model, img, frame_id=i, video_len=len(imgs)) + if args.out is not None: + if in_video or out_video: + out_file = osp.join(out_path, f'{i:06d}.jpg') + else: + out_file = osp.join(out_path, img.rsplit(os.sep, 1)[-1]) + else: + out_file = None + + # show the results + visualizer.add_datasample( + 'mot', + img[..., ::-1], + data_sample=result[0], + show=args.show, + draw_gt=False, + out_file=out_file, + wait_time=float(1 / int(fps)) if fps else 0, + pred_score_thr=args.score_thr, + step=i) + + prog_bar.update() + + if args.out and out_video: + print(f'making the output video at {args.out} with a FPS of {fps}') + mmcv.frames2video(out_path, args.out, fps=fps, fourcc='mp4v') + out_dir.cleanup() + + +if __name__ == '__main__': + args = parse_args() + main(args) diff --git a/mmdetection/demo/video_demo.py b/mmdetection/demo/video_demo.py new file mode 100644 index 00000000..f72d6176 --- /dev/null +++ b/mmdetection/demo/video_demo.py @@ -0,0 +1,84 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse + +import cv2 +import mmcv +from mmcv.transforms import Compose +from mmengine.utils import track_iter_progress + +from mmdet.apis import inference_detector, init_detector +from mmdet.registry import VISUALIZERS + + +def parse_args(): + parser = argparse.ArgumentParser(description='MMDetection video demo') + parser.add_argument('video', help='Video file') + parser.add_argument('config', help='Config file') + parser.add_argument('checkpoint', help='Checkpoint file') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + parser.add_argument( + '--score-thr', type=float, default=0.3, help='Bbox score threshold') + parser.add_argument('--out', type=str, help='Output video file') + parser.add_argument('--show', action='store_true', help='Show video') + parser.add_argument( + '--wait-time', + type=float, + default=1, + help='The interval of show (s), 0 is block') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + assert args.out or args.show, \ + ('Please specify at least one operation (save/show the ' + 'video) with the argument "--out" or "--show"') + + # build the model from a config file and a checkpoint file + model = init_detector(args.config, args.checkpoint, device=args.device) + + # build test pipeline + model.cfg.test_dataloader.dataset.pipeline[ + 0].type = 'mmdet.LoadImageFromNDArray' + test_pipeline = Compose(model.cfg.test_dataloader.dataset.pipeline) + + # init visualizer + visualizer = VISUALIZERS.build(model.cfg.visualizer) + # the dataset_meta is loaded from the checkpoint and + # then pass to the model in init_detector + visualizer.dataset_meta = model.dataset_meta + + video_reader = mmcv.VideoReader(args.video) + video_writer = None + if args.out: + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + video_writer = cv2.VideoWriter( + args.out, fourcc, video_reader.fps, + (video_reader.width, video_reader.height)) + + for frame in track_iter_progress((video_reader, len(video_reader))): + result = inference_detector(model, frame, test_pipeline=test_pipeline) + visualizer.add_datasample( + name='video', + image=frame, + data_sample=result, + draw_gt=False, + show=False, + pred_score_thr=args.score_thr) + frame = visualizer.get_image() + + if args.show: + cv2.namedWindow('video', 0) + mmcv.imshow(frame, 'video', args.wait_time) + if args.out: + video_writer.write(frame) + + if video_writer: + video_writer.release() + cv2.destroyAllWindows() + + +if __name__ == '__main__': + main() diff --git a/mmdetection/demo/video_gpuaccel_demo.py b/mmdetection/demo/video_gpuaccel_demo.py new file mode 100644 index 00000000..3b091647 --- /dev/null +++ b/mmdetection/demo/video_gpuaccel_demo.py @@ -0,0 +1,144 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +from typing import Tuple + +import cv2 +import mmcv +import numpy as np +import torch +import torch.nn as nn +from mmcv.transforms import Compose +from mmengine.utils import track_iter_progress + +from mmdet.apis import init_detector +from mmdet.registry import VISUALIZERS +from mmdet.structures import DetDataSample + +try: + import ffmpegcv +except ImportError: + raise ImportError( + 'Please install ffmpegcv with:\n\n pip install ffmpegcv') + + +def parse_args(): + parser = argparse.ArgumentParser( + description='MMDetection video demo with GPU acceleration') + parser.add_argument('video', help='Video file') + parser.add_argument('config', help='Config file') + parser.add_argument('checkpoint', help='Checkpoint file') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + parser.add_argument( + '--score-thr', type=float, default=0.3, help='Bbox score threshold') + parser.add_argument('--out', type=str, help='Output video file') + parser.add_argument('--show', action='store_true', help='Show video') + parser.add_argument( + '--nvdecode', action='store_true', help='Use NVIDIA decoder') + parser.add_argument( + '--wait-time', + type=float, + default=1, + help='The interval of show (s), 0 is block') + args = parser.parse_args() + return args + + +def prefetch_batch_input_shape(model: nn.Module, ori_wh: Tuple[int, + int]) -> dict: + cfg = model.cfg + w, h = ori_wh + cfg.test_dataloader.dataset.pipeline[0].type = 'LoadImageFromNDArray' + test_pipeline = Compose(cfg.test_dataloader.dataset.pipeline) + data = {'img': np.zeros((h, w, 3), dtype=np.uint8), 'img_id': 0} + data = test_pipeline(data) + data['inputs'] = [data['inputs']] + data['data_samples'] = [data['data_samples']] + data_sample = model.data_preprocessor(data, False)['data_samples'] + batch_input_shape = data_sample[0].batch_input_shape + return batch_input_shape + + +def pack_data(frame_resize: np.ndarray, batch_input_shape: Tuple[int, int], + ori_shape: Tuple[int, int]) -> dict: + assert frame_resize.shape[:2] == batch_input_shape + data_sample = DetDataSample() + data_sample.set_metainfo({ + 'img_shape': + batch_input_shape, + 'ori_shape': + ori_shape, + 'scale_factor': (batch_input_shape[0] / ori_shape[0], + batch_input_shape[1] / ori_shape[1]) + }) + frame_resize = torch.from_numpy(frame_resize).permute((2, 0, 1)).cuda() + data = {'inputs': [frame_resize], 'data_samples': [data_sample]} + return data + + +def main(): + args = parse_args() + assert args.out or args.show, \ + ('Please specify at least one operation (save/show the ' + 'video) with the argument "--out" or "--show"') + + model = init_detector(args.config, args.checkpoint, device=args.device) + + # init visualizer + visualizer = VISUALIZERS.build(model.cfg.visualizer) + # the dataset_meta is loaded from the checkpoint and + # then pass to the model in init_detector + visualizer.dataset_meta = model.dataset_meta + + if args.nvdecode: + VideoCapture = ffmpegcv.VideoCaptureNV + else: + VideoCapture = ffmpegcv.VideoCapture + video_origin = VideoCapture(args.video) + + batch_input_shape = prefetch_batch_input_shape( + model, (video_origin.width, video_origin.height)) + ori_shape = (video_origin.height, video_origin.width) + resize_wh = batch_input_shape[::-1] + video_resize = VideoCapture( + args.video, + resize=resize_wh, + resize_keepratio=True, + resize_keepratioalign='topleft') + + video_writer = None + if args.out: + video_writer = ffmpegcv.VideoWriter(args.out, fps=video_origin.fps) + + with torch.no_grad(): + for i, (frame_resize, frame_origin) in enumerate( + zip(track_iter_progress(video_resize), video_origin)): + data = pack_data(frame_resize, batch_input_shape, ori_shape) + result = model.test_step(data)[0] + + visualizer.add_datasample( + name='video', + image=frame_origin, + data_sample=result, + draw_gt=False, + show=False, + pred_score_thr=args.score_thr) + + frame_mask = visualizer.get_image() + + if args.show: + cv2.namedWindow('video', 0) + mmcv.imshow(frame_mask, 'video', args.wait_time) + if args.out: + video_writer.write(frame_mask) + + if video_writer: + video_writer.release() + video_origin.release() + video_resize.release() + + cv2.destroyAllWindows() + + +if __name__ == '__main__': + main() diff --git a/mmdetection/demo/webcam_demo.py b/mmdetection/demo/webcam_demo.py new file mode 100644 index 00000000..d0900303 --- /dev/null +++ b/mmdetection/demo/webcam_demo.py @@ -0,0 +1,65 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse + +import cv2 +import mmcv +import torch + +from mmdet.apis import inference_detector, init_detector +from mmdet.registry import VISUALIZERS + + +def parse_args(): + parser = argparse.ArgumentParser(description='MMDetection webcam demo') + parser.add_argument('config', help='test config file path') + parser.add_argument('checkpoint', help='checkpoint file') + parser.add_argument( + '--device', type=str, default='cuda:0', help='CPU/CUDA device option') + parser.add_argument( + '--camera-id', type=int, default=0, help='camera device id') + parser.add_argument( + '--score-thr', type=float, default=0.5, help='bbox score threshold') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + + # build the model from a config file and a checkpoint file + device = torch.device(args.device) + model = init_detector(args.config, args.checkpoint, device=device) + + # init visualizer + visualizer = VISUALIZERS.build(model.cfg.visualizer) + # the dataset_meta is loaded from the checkpoint and + # then pass to the model in init_detector + visualizer.dataset_meta = model.dataset_meta + + camera = cv2.VideoCapture(args.camera_id) + + print('Press "Esc", "q" or "Q" to exit.') + while True: + ret_val, img = camera.read() + result = inference_detector(model, img) + + img = mmcv.imconvert(img, 'bgr', 'rgb') + visualizer.add_datasample( + name='result', + image=img, + data_sample=result, + draw_gt=False, + pred_score_thr=args.score_thr, + show=False) + + img = visualizer.get_image() + img = mmcv.imconvert(img, 'bgr', 'rgb') + cv2.imshow('result', img) + + ch = cv2.waitKey(1) + if ch == 27 or ch == ord('q') or ch == ord('Q'): + break + + +if __name__ == '__main__': + main() diff --git a/mmdetection/docker/Dockerfile b/mmdetection/docker/Dockerfile new file mode 100644 index 00000000..2737ec0e --- /dev/null +++ b/mmdetection/docker/Dockerfile @@ -0,0 +1,40 @@ +ARG PYTORCH="1.9.0" +ARG CUDA="11.1" +ARG CUDNN="8" + +FROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel + +ENV TORCH_CUDA_ARCH_LIST="6.0 6.1 7.0 7.5 8.0 8.6+PTX" \ + TORCH_NVCC_FLAGS="-Xfatbin -compress-all" \ + CMAKE_PREFIX_PATH="$(dirname $(which conda))/../" \ + FORCE_CUDA="1" + +# Avoid Public GPG key error +# https://github.com/NVIDIA/nvidia-docker/issues/1631 +RUN rm /etc/apt/sources.list.d/cuda.list \ + && rm /etc/apt/sources.list.d/nvidia-ml.list \ + && apt-key del 7fa2af80 \ + && apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/3bf863cc.pub \ + && apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/7fa2af80.pub + +# (Optional, use Mirror to speed up downloads) +# RUN sed -i 's/http:\/\/archive.ubuntu.com\/ubuntu\//http:\/\/mirrors.aliyun.com\/ubuntu\//g' /etc/apt/sources.list && \ +# pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple + +# Install the required packages +RUN apt-get update \ + && apt-get install -y ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install MMEngine and MMCV +RUN pip install openmim && \ + mim install "mmengine>=0.7.1" "mmcv>=2.0.0rc4" + +# Install MMDetection +RUN conda clean --all \ + && git clone https://github.com/open-mmlab/mmdetection.git /mmdetection \ + && cd /mmdetection \ + && pip install --no-cache-dir -e . + +WORKDIR /mmdetection diff --git a/mmdetection/docker/serve/Dockerfile b/mmdetection/docker/serve/Dockerfile new file mode 100644 index 00000000..aa307cf6 --- /dev/null +++ b/mmdetection/docker/serve/Dockerfile @@ -0,0 +1,62 @@ +ARG PYTORCH="1.9.0" +ARG CUDA="11.1" +ARG CUDNN="8" +FROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel + +ARG MMCV="2.0.0rc4" +ARG MMDET="3.3.0" + +ENV PYTHONUNBUFFERED TRUE + +# Avoid Public GPG key error +# https://github.com/NVIDIA/nvidia-docker/issues/1631 +RUN rm /etc/apt/sources.list.d/cuda.list \ + && rm /etc/apt/sources.list.d/nvidia-ml.list \ + && apt-key del 7fa2af80 \ + && apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/3bf863cc.pub \ + && apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/7fa2af80.pub + +# (Optional, use Mirror to speed up downloads) +# RUN sed -i 's/http:\/\/archive.ubuntu.com\/ubuntu\//http:\/\/mirrors.aliyun.com\/ubuntu\//g' /etc/apt/sources.list + +# Install the required packages +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + ca-certificates \ + g++ \ + openjdk-11-jre-headless \ + # MMDet Requirements + ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \ + && rm -rf /var/lib/apt/lists/* + +ENV PATH="/opt/conda/bin:$PATH" \ + FORCE_CUDA="1" + +# TORCHSEVER +RUN pip install torchserve torch-model-archiver + +# MMLAB +ARG PYTORCH +ARG CUDA +RUN pip install mmengine +RUN ["/bin/bash", "-c", "pip install mmcv==${MMCV} -f https://download.openmmlab.com/mmcv/dist/cu${CUDA//./}/torch${PYTORCH}/index.html"] +RUN pip install mmdet==${MMDET} + +RUN useradd -m model-server \ + && mkdir -p /home/model-server/tmp + +COPY entrypoint.sh /usr/local/bin/entrypoint.sh + +RUN chmod +x /usr/local/bin/entrypoint.sh \ + && chown -R model-server /home/model-server + +COPY config.properties /home/model-server/config.properties +RUN mkdir /home/model-server/model-store && chown -R model-server /home/model-server/model-store + +EXPOSE 8080 8081 8082 + +USER model-server +WORKDIR /home/model-server +ENV TEMP=/home/model-server/tmp +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["serve"] diff --git a/mmdetection/docker/serve/config.properties b/mmdetection/docker/serve/config.properties new file mode 100644 index 00000000..efb9c47e --- /dev/null +++ b/mmdetection/docker/serve/config.properties @@ -0,0 +1,5 @@ +inference_address=http://0.0.0.0:8080 +management_address=http://0.0.0.0:8081 +metrics_address=http://0.0.0.0:8082 +model_store=/home/model-server/model-store +load_models=all diff --git a/mmdetection/docker/serve/entrypoint.sh b/mmdetection/docker/serve/entrypoint.sh new file mode 100644 index 00000000..41ba00b0 --- /dev/null +++ b/mmdetection/docker/serve/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +if [[ "$1" = "serve" ]]; then + shift 1 + torchserve --start --ts-config /home/model-server/config.properties +else + eval "$@" +fi + +# prevent docker exit +tail -f /dev/null diff --git a/mmdetection/docker/serve_cn/Dockerfile b/mmdetection/docker/serve_cn/Dockerfile new file mode 100644 index 00000000..894e15dd --- /dev/null +++ b/mmdetection/docker/serve_cn/Dockerfile @@ -0,0 +1,65 @@ +ARG PYTORCH="1.9.0" +ARG CUDA="11.1" +ARG CUDNN="8" +FROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel + +ARG MMCV="2.0.0rc4" +ARG MMDET="3.3.0" + +ENV PYTHONUNBUFFERED TRUE + +# Avoid Public GPG key error +# - https://github.com/NVIDIA/nvidia-docker/issues/1631 +RUN rm /etc/apt/sources.list.d/cuda.list \ + && rm /etc/apt/sources.list.d/nvidia-ml.list \ + && apt-get update \ + && apt-get install -y wget \ + && rm -rf /var/lib/apt/lists/* \ + && apt-key del 7fa2af80 \ + && apt-get update && apt-get install -y --no-install-recommends wget \ + && wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-keyring_1.0-1_all.deb \ + && dpkg -i cuda-keyring_1.0-1_all.deb +# (Optional, use Mirror to speed up downloads) +# RUN sed -i 's/http:\/\/archive.ubuntu.com\/ubuntu\//http:\/\/mirrors.aliyun.com\/ubuntu\//g' /etc/apt/sources.list + +# Install the required packages +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + ca-certificates \ + g++ \ + openjdk-11-jre-headless \ + # MMDet Requirements + ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \ + && rm -rf /var/lib/apt/lists/* + +ENV PATH="/opt/conda/bin:$PATH" \ + FORCE_CUDA="1" + +# TORCHSEVER +RUN pip install torchserve torch-model-archiver nvgpu -i https://pypi.mirrors.ustc.edu.cn/simple/ + +# MMLAB +ARG PYTORCH +ARG CUDA +RUN pip install mmengine -i https://pypi.mirrors.ustc.edu.cn/simple/ +RUN ["/bin/bash", "-c", "pip install mmcv==${MMCV} -f https://download.openmmlab.com/mmcv/dist/cu${CUDA//./}/torch${PYTORCH}/index.html"] +RUN pip install mmdet==${MMDET} -i https://pypi.mirrors.ustc.edu.cn/simple/ + +RUN useradd -m model-server \ + && mkdir -p /home/model-server/tmp + +COPY entrypoint.sh /usr/local/bin/entrypoint.sh + +RUN chmod +x /usr/local/bin/entrypoint.sh \ + && chown -R model-server /home/model-server + +COPY config.properties /home/model-server/config.properties +RUN mkdir /home/model-server/model-store && chown -R model-server /home/model-server/model-store + +EXPOSE 8080 8081 8082 + +USER model-server +WORKDIR /home/model-server +ENV TEMP=/home/model-server/tmp +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["serve"] diff --git a/mmdetection/docs/en/Makefile b/mmdetection/docs/en/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/mmdetection/docs/en/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/mmdetection/docs/en/_static/css/readthedocs.css b/mmdetection/docs/en/_static/css/readthedocs.css new file mode 100644 index 00000000..57ed0ad0 --- /dev/null +++ b/mmdetection/docs/en/_static/css/readthedocs.css @@ -0,0 +1,6 @@ +.header-logo { + background-image: url("../image/mmdet-logo.png"); + background-size: 156px 40px; + height: 40px; + width: 156px; +} diff --git a/mmdetection/docs/en/_static/image/mmdet-logo.png b/mmdetection/docs/en/_static/image/mmdet-logo.png new file mode 100644 index 00000000..58e2b5e6 Binary files /dev/null and b/mmdetection/docs/en/_static/image/mmdet-logo.png differ diff --git a/mmdetection/docs/en/advanced_guides/conventions.md b/mmdetection/docs/en/advanced_guides/conventions.md new file mode 100644 index 00000000..da159ac6 --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/conventions.md @@ -0,0 +1,111 @@ +# Conventions + +Please check the following conventions if you would like to modify MMDetection as your own project. + +## About the order of image shape + +In OpenMMLab 2.0, to be consistent with the input argument of OpenCV, the argument about image shape in the data transformation pipeline is always in the `(width, height)` order. On the contrary, for computation convenience, the order of the field going through the data pipeline and the model is `(height, width)`. Specifically, in the results processed by each data transform pipeline, the fields and their value meaning is as below: + +- img_shape: (height, width) +- ori_shape: (height, width) +- pad_shape: (height, width) +- batch_input_shape: (height, width) + +As an example, the initialization arguments of `Mosaic` are as below: + +```python +@TRANSFORMS.register_module() +class Mosaic(BaseTransform): + def __init__(self, + img_scale: Tuple[int, int] = (640, 640), + center_ratio_range: Tuple[float, float] = (0.5, 1.5), + bbox_clip_border: bool = True, + pad_val: float = 114.0, + prob: float = 1.0) -> None: + ... + + # img_scale order should be (width, height) + self.img_scale = img_scale + + def transform(self, results: dict) -> dict: + ... + + results['img'] = mosaic_img + # (height, width) + results['img_shape'] = mosaic_img.shape[:2] +``` + +## Loss + +In MMDetection, a `dict` containing losses and metrics will be returned by `model(**data)`. + +For example, in bbox head, + +```python +class BBoxHead(nn.Module): + ... + def loss(self, ...): + losses = dict() + # classification loss + losses['loss_cls'] = self.loss_cls(...) + # classification accuracy + losses['acc'] = accuracy(...) + # bbox regression loss + losses['loss_bbox'] = self.loss_bbox(...) + return losses +``` + +`bbox_head.loss()` will be called during model forward. +The returned dict contains `'loss_bbox'`, `'loss_cls'`, `'acc'` . +Only `'loss_bbox'`, `'loss_cls'` will be used during back propagation, +`'acc'` will only be used as a metric to monitor training process. + +By default, only values whose keys contain `'loss'` will be back propagated. +This behavior could be changed by modifying `BaseDetector.train_step()`. + +## Empty Proposals + +In MMDetection, We have added special handling and unit test for empty proposals of two-stage. We need to deal with the empty proposals of the entire batch and single image at the same time. For example, in CascadeRoIHead, + +```python +# simple_test method +... +# There is no proposal in the whole batch +if rois.shape[0] == 0: + bbox_results = [[ + np.zeros((0, 5), dtype=np.float32) + for _ in range(self.bbox_head[-1].num_classes) + ]] * num_imgs + if self.with_mask: + mask_classes = self.mask_head[-1].num_classes + segm_results = [[[] for _ in range(mask_classes)] + for _ in range(num_imgs)] + results = list(zip(bbox_results, segm_results)) + else: + results = bbox_results + return results +... + +# There is no proposal in the single image +for i in range(self.num_stages): + ... + if i < self.num_stages - 1: + for j in range(num_imgs): + # Handle empty proposal + if rois[j].shape[0] > 0: + bbox_label = cls_score[j][:, :-1].argmax(dim=1) + refine_roi = self.bbox_head[i].regress_by_class( + rois[j], bbox_label, bbox_pred[j], img_metas[j]) + refine_roi_list.append(refine_roi) +``` + +If you have customized `RoIHead`, you can refer to the above method to deal with empty proposals. + +## Coco Panoptic Dataset + +In MMDetection, we have supported COCO Panoptic dataset. We clarify a few conventions about the implementation of `CocoPanopticDataset` here. + +1. For mmdet\<=2.16.0, the range of foreground and background labels in semantic segmentation are different from the default setting of MMDetection. The label `0` stands for `VOID` label and the category labels start from `1`. + Since mmdet=2.17.0, the category labels of semantic segmentation start from `0` and label `255` stands for `VOID` for consistency with labels of bounding boxes. + To achieve that, the `Pad` pipeline supports setting the padding value for `seg`. +2. In the evaluation, the panoptic result is a map with the same shape as the original image. Each value in the result map has the format of `instance_id * INSTANCE_OFFSET + category_id`. diff --git a/mmdetection/docs/en/advanced_guides/customize_dataset.md b/mmdetection/docs/en/advanced_guides/customize_dataset.md new file mode 100644 index 00000000..3d63d12c --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/customize_dataset.md @@ -0,0 +1,433 @@ +# Customize Datasets + +## Support new data format + +To support a new data format, you can either convert them to existing formats (COCO format or PASCAL format) or directly convert them to the middle format. You could also choose to convert them offline (before training by a script) or online (implement a new dataset and do the conversion at training). In MMDetection, we recommend to convert the data into COCO formats and do the conversion offline, thus you only need to modify the config's data annotation paths and classes after the conversion of your data. + +### Reorganize new data formats to existing format + +The simplest way is to convert your dataset to existing dataset formats (COCO or PASCAL VOC). + +The annotation JSON files in COCO format has the following necessary keys: + +```python +'images': [ + { + 'file_name': 'COCO_val2014_000000001268.jpg', + 'height': 427, + 'width': 640, + 'id': 1268 + }, + ... +], + +'annotations': [ + { + 'segmentation': [[192.81, + 247.09, + ... + 219.03, + 249.06]], # If you have mask labels, and it is in polygon XY point coordinate format, you need to ensure that at least 3 point coordinates are included. Otherwise, it is an invalid polygon. + 'area': 1035.749, + 'iscrowd': 0, + 'image_id': 1268, + 'bbox': [192.81, 224.8, 74.73, 33.43], + 'category_id': 16, + 'id': 42986 + }, + ... +], + +'categories': [ + {'id': 0, 'name': 'car'}, + ] +``` + +There are three necessary keys in the JSON file: + +- `images`: contains a list of images with their information like `file_name`, `height`, `width`, and `id`. +- `annotations`: contains the list of instance annotations. +- `categories`: contains the list of categories names and their ID. + +After the data pre-processing, there are two steps for users to train the customized new dataset with existing format (e.g. COCO format): + +1. Modify the config file for using the customized dataset. +2. Check the annotations of the customized dataset. + +Here we give an example to show the above two steps, which uses a customized dataset of 5 classes with COCO format to train an existing Cascade Mask R-CNN R50-FPN detector. + +#### 1. Modify the config file for using the customized dataset + +There are two aspects involved in the modification of config file: + +1. The `data` field. Specifically, you need to explicitly add the `metainfo=dict(classes=classes)` fields in `train_dataloader.dataset`, `val_dataloader.dataset` and `test_dataloader.dataset` and `classes` must be a tuple type. +2. The `num_classes` field in the `model` part. Explicitly over-write all the `num_classes` from default value (e.g. 80 in COCO) to your classes number. + +In `configs/my_custom_config.py`: + +```python + +# the new config inherits the base configs to highlight the necessary modification +_base_ = './cascade_mask_rcnn_r50_fpn_1x_coco.py' + +# 1. dataset settings +dataset_type = 'CocoDataset' +classes = ('a', 'b', 'c', 'd', 'e') +data_root='path/to/your/' + +train_dataloader = dict( + batch_size=2, + num_workers=2, + dataset=dict( + type=dataset_type, + # explicitly add your class names to the field `metainfo` + metainfo=dict(classes=classes), + data_root=data_root, + ann_file='train/annotation_data', + data_prefix=dict(img='train/image_data') + ) + ) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + dataset=dict( + type=dataset_type, + test_mode=True, + # explicitly add your class names to the field `metainfo` + metainfo=dict(classes=classes), + data_root=data_root, + ann_file='val/annotation_data', + data_prefix=dict(img='val/image_data') + ) + ) + +test_dataloader = dict( + batch_size=1, + num_workers=2, + dataset=dict( + type=dataset_type, + test_mode=True, + # explicitly add your class names to the field `metainfo` + metainfo=dict(classes=classes), + data_root=data_root, + ann_file='test/annotation_data', + data_prefix=dict(img='test/image_data') + ) + ) + +# 2. model settings + +# explicitly over-write all the `num_classes` field from default 80 to 5. +model = dict( + roi_head=dict( + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + # explicitly over-write all the `num_classes` field from default 80 to 5. + num_classes=5), + dict( + type='Shared2FCBBoxHead', + # explicitly over-write all the `num_classes` field from default 80 to 5. + num_classes=5), + dict( + type='Shared2FCBBoxHead', + # explicitly over-write all the `num_classes` field from default 80 to 5. + num_classes=5)], + # explicitly over-write all the `num_classes` field from default 80 to 5. + mask_head=dict(num_classes=5))) +``` + +#### 2. Check the annotations of the customized dataset + +Assuming your customized dataset is COCO format, make sure you have the correct annotations in the customized dataset: + +1. The length for `categories` field in annotations should exactly equal the tuple length of `classes` fields in your config, meaning the number of classes (e.g. 5 in this example). +2. The `classes` fields in your config file should have exactly the same elements and the same order with the `name` in `categories` of annotations. MMDetection automatically maps the uncontinuous `id` in `categories` to the continuous label indices, so the string order of `name` in `categories` field affects the order of label indices. Meanwhile, the string order of `classes` in config affects the label text during visualization of predicted bounding boxes. +3. The `category_id` in `annotations` field should be valid, i.e., all values in `category_id` should belong to `id` in `categories`. + +Here is a valid example of annotations: + +```python + +'annotations': [ + { + 'segmentation': [[192.81, + 247.09, + ... + 219.03, + 249.06]], # if you have mask labels + 'area': 1035.749, + 'iscrowd': 0, + 'image_id': 1268, + 'bbox': [192.81, 224.8, 74.73, 33.43], + 'category_id': 16, + 'id': 42986 + }, + ... +], + +# MMDetection automatically maps the uncontinuous `id` to the continuous label indices. +'categories': [ + {'id': 1, 'name': 'a'}, {'id': 3, 'name': 'b'}, {'id': 4, 'name': 'c'}, {'id': 16, 'name': 'd'}, {'id': 17, 'name': 'e'}, + ] +``` + +We use this way to support CityScapes dataset. The script is in [cityscapes.py](../../../tools/dataset_converters/cityscapes.py) and we also provide the finetuning [configs](../../../configs/cityscapes). + +**Note** + +1. For instance segmentation datasets, **MMDetection only supports evaluating mask AP of dataset in COCO format for now**. +2. It is recommended to convert the data offline before training, thus you can still use `CocoDataset` and only need to modify the path of annotations and the training classes. + +### Reorganize new data format to middle format + +It is also fine if you do not want to convert the annotation format to COCO or PASCAL format. +Actually, we define a simple annotation format in MMEninge's [BaseDataset](https://github.com/open-mmlab/mmengine/blob/main/mmengine/dataset/base_dataset.py#L116) and all existing datasets are +processed to be compatible with it, either online or offline. + +The annotation of the dataset must be in `json` or `yaml`, `yml` or `pickle`, `pkl` format; the dictionary stored in the annotation file must contain two fields `metainfo` and `data_list`. The `metainfo` is a dictionary, which contains the metadata of the dataset, such as class information; `data_list` is a list, each element in the list is a dictionary, the dictionary defines the raw data of one image, and each raw data contains a or several training/testing samples. + +Here is an example. + +```python +{ + 'metainfo': + { + 'classes': ('person', 'bicycle', 'car', 'motorcycle'), + ... + }, + 'data_list': + [ + { + "img_path": "xxx/xxx_1.jpg", + "height": 604, + "width": 640, + "instances": + [ + { + "bbox": [0, 0, 10, 20], + "bbox_label": 1, + "ignore_flag": 0 + }, + { + "bbox": [10, 10, 110, 120], + "bbox_label": 2, + "ignore_flag": 0 + } + ] + }, + { + "img_path": "xxx/xxx_2.jpg", + "height": 320, + "width": 460, + "instances": + [ + { + "bbox": [10, 0, 20, 20], + "bbox_label": 3, + "ignore_flag": 1, + } + ] + }, + ... + ] +} +``` + +Some datasets may provide annotations like crowd/difficult/ignored bboxes, we use `ignore_flag`to cover them. + +After obtaining the above standard data annotation format, you can directly use [BaseDetDataset](../../../mmdet/datasets/base_det_dataset.py#L13) of MMDetection in the configuration , without conversion. + +### An example of customized dataset + +Assume the annotation is in a new format in text files. +The bounding boxes annotations are stored in text file `annotation.txt` as the following + +``` +# +000001.jpg +1280 720 +2 +10 20 40 60 1 +20 40 50 60 2 +# +000002.jpg +1280 720 +3 +50 20 40 60 2 +20 40 30 45 2 +30 40 50 60 3 +``` + +We can create a new dataset in `mmdet/datasets/my_dataset.py` to load the data. + +```python +import mmengine + +from mmdet.base_det_dataset import BaseDetDataset +from mmdet.registry import DATASETS + + +@DATASETS.register_module() +class MyDataset(BaseDetDataset): + + METAINFO = { + 'classes': ('person', 'bicycle', 'car', 'motorcycle'), + 'palette': [(220, 20, 60), (119, 11, 32), (0, 0, 142), (0, 0, 230)] + } + + def load_data_list(self, ann_file): + ann_list = mmengine.list_from_file(ann_file) + + data_infos = [] + for i, ann_line in enumerate(ann_list): + if ann_line != '#': + continue + + img_shape = ann_list[i + 2].split(' ') + width = int(img_shape[0]) + height = int(img_shape[1]) + bbox_number = int(ann_list[i + 3]) + + instances = [] + for anns in ann_list[i + 4:i + 4 + bbox_number]: + instance = {} + instance['bbox'] = [float(ann) for ann in anns.split(' ')[:4]] + instance['bbox_label']=int(anns[4]) + instances.append(instance) + + data_infos.append( + dict( + img_path=ann_list[i + 1], + img_id=i, + width=width, + height=height, + instances=instances + )) + + return data_infos +``` + +Then in the config, to use `MyDataset` you can modify the config as the following + +```python +dataset_A_train = dict( + type='MyDataset', + ann_file = 'image_list.txt', + pipeline=train_pipeline +) +``` + +## Customize datasets by dataset wrappers + +MMEngine also supports many dataset wrappers to mix the dataset or modify the dataset distribution for training. +Currently it supports to three dataset wrappers as below: + +- `RepeatDataset`: simply repeat the whole dataset. +- `ClassBalancedDataset`: repeat dataset in a class balanced manner. +- `ConcatDataset`: concat datasets. + +For detailed usage, see [MMEngine Dataset Wrapper](#TODO). + +## Modify Dataset Classes + +With existing dataset types, we can modify the metainfo of them to train subset of the annotations. +For example, if you want to train only three classes of the current dataset, +you can modify the classes of dataset. +The dataset will filter out the ground truth boxes of other classes automatically. + +```python +classes = ('person', 'bicycle', 'car') +train_dataloader = dict( + dataset=dict( + metainfo=dict(classes=classes)) + ) +val_dataloader = dict( + dataset=dict( + metainfo=dict(classes=classes)) + ) +test_dataloader = dict( + dataset=dict( + metainfo=dict(classes=classes)) + ) +``` + +**Note**: + +- Before MMDetection v2.5.0, the dataset will filter out the empty GT images automatically if the classes are set and there is no way to disable that through config. This is an undesirable behavior and introduces confusion because if the classes are not set, the dataset only filter the empty GT images when `filter_empty_gt=True` and `test_mode=False`. After MMDetection v2.5.0, we decouple the image filtering process and the classes modification, i.e., the dataset will only filter empty GT images when `filter_cfg=dict(filter_empty_gt=True)` and `test_mode=False`, no matter whether the classes are set. Thus, setting the classes only influences the annotations of classes used for training and users could decide whether to filter empty GT images by themselves. +- When directly using `BaseDataset` in MMEngine or `BaseDetDataset` in MMDetection, users cannot filter images without GT by modifying the configuration, but it can be solved in an offline way. +- Please remember to modify the `num_classes` in the head when specifying `classes` in dataset. We implemented [NumClassCheckHook](../../../mmdet/engine/hooks/num_class_check_hook.py) to check whether the numbers are consistent since v2.9.0(after PR#4508). + +## COCO Panoptic Dataset + +Now we support COCO Panoptic Dataset, the format of panoptic annotations is different from COCO format. +Both the foreground and the background will exist in the annotation file. +The annotation json files in COCO Panoptic format has the following necessary keys: + +```python +'images': [ + { + 'file_name': '000000001268.jpg', + 'height': 427, + 'width': 640, + 'id': 1268 + }, + ... +] + +'annotations': [ + { + 'filename': '000000001268.jpg', + 'image_id': 1268, + 'segments_info': [ + { + 'id':8345037, # One-to-one correspondence with the id in the annotation map. + 'category_id': 51, + 'iscrowd': 0, + 'bbox': (x1, y1, w, h), # The bbox of the background is the outer rectangle of its mask. + 'area': 24315 + }, + ... + ] + }, + ... +] + +'categories': [ # including both foreground categories and background categories + {'id': 0, 'name': 'person'}, + ... + ] +``` + +Moreover, the `seg` must be set to the path of the panoptic annotation images. + +```python +dataset_type = 'CocoPanopticDataset' +data_root='path/to/your/' + +train_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img='train/image_data/', seg='train/panoptic/image_annotation_data/') + ) +) +val_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img='val/image_data/', seg='val/panoptic/image_annotation_data/') + ) +) +test_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img='test/image_data/', seg='test/panoptic/image_annotation_data/') + ) +) +``` diff --git a/mmdetection/docs/en/advanced_guides/customize_losses.md b/mmdetection/docs/en/advanced_guides/customize_losses.md new file mode 100644 index 00000000..3120dc01 --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/customize_losses.md @@ -0,0 +1,126 @@ +# Customize Losses + +MMDetection provides users with different loss functions. But the default configuration may be not applicable for different datasets or models, so users may want to modify a specific loss to adapt the new situation. + +This tutorial first elaborate the computation pipeline of losses, then give some instructions about how to modify each step. The modification can be categorized as tweaking and weighting. + +## Computation pipeline of a loss + +Given the input prediction and target, as well as the weights, a loss function maps the input tensor to the final loss scalar. The mapping can be divided into five steps: + +1. Set the sampling method to sample positive and negative samples. + +2. Get **element-wise** or **sample-wise** loss by the loss kernel function. + +3. Weighting the loss with a weight tensor **element-wisely**. + +4. Reduce the loss tensor to a **scalar**. + +5. Weighting the loss with a **scalar**. + +## Set sampling method (step 1) + +For some loss functions, sampling strategies are needed to avoid imbalance between positive and negative samples. + +For example, when using `CrossEntropyLoss` in RPN head, we need to set `RandomSampler` in `train_cfg` + +```python +train_cfg=dict( + rpn=dict( + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False)) +``` + +For some other losses which have positive and negative sample balance mechanism such as Focal Loss, GHMC, and QualityFocalLoss, the sampler is no more necessary. + +## Tweaking loss + +Tweaking a loss is more related with step 2, 4, 5, and most modifications can be specified in the config. +Here we take [Focal Loss (FL)](../../../mmdet/models/losses/focal_loss.py) as an example. +The following code sniper are the construction method and config of FL respectively, they are actually one to one correspondence. + +```python +@LOSSES.register_module() +class FocalLoss(nn.Module): + + def __init__(self, + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + reduction='mean', + loss_weight=1.0): +``` + +```python +loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0) +``` + +### Tweaking hyper-parameters (step 2) + +`gamma` and `beta` are two hyper-parameters in the Focal Loss. Say if we want to change the value of `gamma` to be 1.5 and `alpha` to be 0.5, then we can specify them in the config as follows: + +```python +loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=1.5, + alpha=0.5, + loss_weight=1.0) +``` + +### Tweaking the way of reduction (step 3) + +The default way of reduction is `mean` for FL. Say if we want to change the reduction from `mean` to `sum`, we can specify it in the config as follows: + +```python +loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0, + reduction='sum') +``` + +### Tweaking loss weight (step 5) + +The loss weight here is a scalar which controls the weight of different losses in multi-task learning, e.g. classification loss and regression loss. Say if we want to change to loss weight of classification loss to be 0.5, we can specify it in the config as follows: + +```python +loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=0.5) +``` + +## Weighting loss (step 3) + +Weighting loss means we re-weight the loss element-wisely. To be more specific, we multiply the loss tensor with a weight tensor which has the same shape. As a result, different entries of the loss can be scaled differently, and so called element-wisely. +The loss weight varies across different models and highly context related, but overall there are two kinds of loss weights, `label_weights` for classification loss and `bbox_weights` for bbox regression loss. You can find them in the `get_target` method of the corresponding head. Here we take [ATSSHead](../../../mmdet/models/dense_heads/atss_head.py#L322) as an example, which inherit [AnchorHead](../../../mmdet/models/dense_heads/anchor_head.py) but overwrite its `get_targets` method which yields different `label_weights` and `bbox_weights`. + +``` +class ATSSHead(AnchorHead): + + ... + + def get_targets(self, + anchor_list, + valid_flag_list, + gt_bboxes_list, + img_metas, + gt_bboxes_ignore_list=None, + gt_labels_list=None, + label_channels=1, + unmap_outputs=True): +``` diff --git a/mmdetection/docs/en/advanced_guides/customize_models.md b/mmdetection/docs/en/advanced_guides/customize_models.md new file mode 100644 index 00000000..1779aeb1 --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/customize_models.md @@ -0,0 +1,412 @@ +# Customize Models + +We basically categorize model components into 5 types. + +- backbone: usually an FCN network to extract feature maps, e.g., ResNet, MobileNet. +- neck: the component between backbones and heads, e.g., FPN, PAFPN. +- head: the component for specific tasks, e.g., bbox prediction and mask prediction. +- roi extractor: the part for extracting RoI features from feature maps, e.g., RoI Align. +- loss: the component in head for calculating losses, e.g., FocalLoss, L1Loss, and GHMLoss. + +## Develop new components + +### Add a new backbone + +Here we show how to develop new components with an example of MobileNet. + +#### 1. Define a new backbone (e.g. MobileNet) + +Create a new file `mmdet/models/backbones/mobilenet.py`. + +```python +import torch.nn as nn + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class MobileNet(nn.Module): + + def __init__(self, arg1, arg2): + pass + + def forward(self, x): # should return a tuple + pass +``` + +#### 2. Import the module + +You can either add the following line to `mmdet/models/backbones/__init__.py` + +```python +from .mobilenet import MobileNet +``` + +or alternatively add + +```python +custom_imports = dict( + imports=['mmdet.models.backbones.mobilenet'], + allow_failed_imports=False) +``` + +to the config file to avoid modifying the original code. + +#### 3. Use the backbone in your config file + +```python +model = dict( + ... + backbone=dict( + type='MobileNet', + arg1=xxx, + arg2=xxx), + ... +``` + +### Add new necks + +#### 1. Define a neck (e.g. PAFPN) + +Create a new file `mmdet/models/necks/pafpn.py`. + +```python +import torch.nn as nn + +from mmdet.registry import MODELS + +@MODELS.register_module() +class PAFPN(nn.Module): + + def __init__(self, + in_channels, + out_channels, + num_outs, + start_level=0, + end_level=-1, + add_extra_convs=False): + pass + + def forward(self, inputs): + # implementation is ignored + pass +``` + +#### 2. Import the module + +You can either add the following line to `mmdet/models/necks/__init__.py`, + +```python +from .pafpn import PAFPN +``` + +or alternatively add + +```python +custom_imports = dict( + imports=['mmdet.models.necks.pafpn'], + allow_failed_imports=False) +``` + +to the config file and avoid modifying the original code. + +#### 3. Modify the config file + +```python +neck=dict( + type='PAFPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5) +``` + +### Add new heads + +Here we show how to develop a new head with the example of [Double Head R-CNN](https://arxiv.org/abs/1904.06493) as the following. + +First, add a new bbox head in `mmdet/models/roi_heads/bbox_heads/double_bbox_head.py`. +Double Head R-CNN implements a new bbox head for object detection. +To implement a bbox head, basically we need to implement three functions of the new module as the following. + +```python +from typing import Tuple + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule, ModuleList +from torch import Tensor + +from mmdet.models.backbones.resnet import Bottleneck +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, MultiConfig, OptConfigType, OptMultiConfig +from .bbox_head import BBoxHead + +@MODELS.register_module() +class DoubleConvFCBBoxHead(BBoxHead): + r"""Bbox head used in Double-Head R-CNN + + .. code-block:: none + + /-> cls + /-> shared convs -> + \-> reg + roi features + /-> cls + \-> shared fc -> + \-> reg + """ # noqa: W605 + + def __init__(self, + num_convs: int = 0, + num_fcs: int = 0, + conv_out_channels: int = 1024, + fc_out_channels: int = 1024, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN'), + init_cfg: MultiConfig = dict( + type='Normal', + override=[ + dict(type='Normal', name='fc_cls', std=0.01), + dict(type='Normal', name='fc_reg', std=0.001), + dict( + type='Xavier', + name='fc_branch', + distribution='uniform') + ]), + **kwargs) -> None: + kwargs.setdefault('with_avg_pool', True) + super().__init__(init_cfg=init_cfg, **kwargs) + + def forward(self, x_cls: Tensor, x_reg: Tensor) -> Tuple[Tensor]: + +``` + +Second, implement a new RoI Head if it is necessary. We plan to inherit the new `DoubleHeadRoIHead` from `StandardRoIHead`. We can find that a `StandardRoIHead` already implements the following functions. + +```python +from typing import List, Optional, Tuple + +import torch +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList +from ..task_modules.samplers import SamplingResult +from ..utils import empty_instances, unpack_gt_instances +from .base_roi_head import BaseRoIHead + + +@MODELS.register_module() +class StandardRoIHead(BaseRoIHead): + """Simplest base roi head including one bbox head and one mask head.""" + + def init_assigner_sampler(self) -> None: + + def init_bbox_head(self, bbox_roi_extractor: ConfigType, + bbox_head: ConfigType) -> None: + + def init_mask_head(self, mask_roi_extractor: ConfigType, + mask_head: ConfigType) -> None: + + def forward(self, x: Tuple[Tensor], + rpn_results_list: InstanceList) -> tuple: + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: List[DetDataSample]) -> dict: + + def _bbox_forward(self, x: Tuple[Tensor], rois: Tensor) -> dict: + + def bbox_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult]) -> dict: + + def mask_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult], bbox_feats: Tensor, + batch_gt_instances: InstanceList) -> dict: + + def _mask_forward(self, + x: Tuple[Tensor], + rois: Tensor = None, + pos_inds: Optional[Tensor] = None, + bbox_feats: Optional[Tensor] = None) -> dict: + + def predict_bbox(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + rpn_results_list: InstanceList, + rcnn_test_cfg: ConfigType, + rescale: bool = False) -> InstanceList: + + def predict_mask(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + results_list: InstanceList, + rescale: bool = False) -> InstanceList: + +``` + +Double Head's modification is mainly in the `bbox_forward` logic, and it inherits other logics from the `StandardRoIHead`. In the `mmdet/models/roi_heads/double_roi_head.py`, we implement the new RoI Head as the following: + +```python +from typing import Tuple + +from torch import Tensor + +from mmdet.registry import MODELS +from .standard_roi_head import StandardRoIHead + + +@MODELS.register_module() +class DoubleHeadRoIHead(StandardRoIHead): + """RoI head for `Double Head RCNN `_. + + Args: + reg_roi_scale_factor (float): The scale factor to extend the rois + used to extract the regression features. + """ + + def __init__(self, reg_roi_scale_factor: float, **kwargs): + super().__init__(**kwargs) + self.reg_roi_scale_factor = reg_roi_scale_factor + + def _bbox_forward(self, x: Tuple[Tensor], rois: Tensor) -> dict: + """Box head forward function used in both training and testing. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + """ + bbox_cls_feats = self.bbox_roi_extractor( + x[:self.bbox_roi_extractor.num_inputs], rois) + bbox_reg_feats = self.bbox_roi_extractor( + x[:self.bbox_roi_extractor.num_inputs], + rois, + roi_scale_factor=self.reg_roi_scale_factor) + if self.with_shared_head: + bbox_cls_feats = self.shared_head(bbox_cls_feats) + bbox_reg_feats = self.shared_head(bbox_reg_feats) + cls_score, bbox_pred = self.bbox_head(bbox_cls_feats, bbox_reg_feats) + + bbox_results = dict( + cls_score=cls_score, + bbox_pred=bbox_pred, + bbox_feats=bbox_cls_feats) + return bbox_results +``` + +Last, the users need to add the module in +`mmdet/models/bbox_heads/__init__.py` and `mmdet/models/roi_heads/__init__.py` thus the corresponding registry could find and load them. + +Alternatively, the users can add + +```python +custom_imports=dict( + imports=['mmdet.models.roi_heads.double_roi_head', 'mmdet.models.roi_heads.bbox_heads.double_bbox_head']) +``` + +to the config file and achieve the same goal. + +The config file of Double Head R-CNN is as the following + +```python +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + type='DoubleHeadRoIHead', + reg_roi_scale_factor=1.3, + bbox_head=dict( + _delete_=True, + type='DoubleConvFCBBoxHead', + num_convs=4, + num_fcs=2, + in_channels=256, + conv_out_channels=1024, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=2.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=2.0)))) + +``` + +Since MMDetection 2.0, the config system supports to inherit configs such that the users can focus on the modification. +The Double Head R-CNN mainly uses a new `DoubleHeadRoIHead` and a new `DoubleConvFCBBoxHead `, the arguments are set according to the `__init__` function of each module. + +### Add new loss + +Assume you want to add a new loss as `MyLoss`, for bounding box regression. +To add a new loss function, the users need implement it in `mmdet/models/losses/my_loss.py`. +The decorator `weighted_loss` enable the loss to be weighted for each element. + +```python +import torch +import torch.nn as nn + +from mmdet.registry import MODELS +from .utils import weighted_loss + +@weighted_loss +def my_loss(pred, target): + assert pred.size() == target.size() and target.numel() > 0 + loss = torch.abs(pred - target) + return loss + +@MODELS.register_module() +class MyLoss(nn.Module): + + def __init__(self, reduction='mean', loss_weight=1.0): + super(MyLoss, self).__init__() + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None): + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss_bbox = self.loss_weight * my_loss( + pred, target, weight, reduction=reduction, avg_factor=avg_factor) + return loss_bbox +``` + +Then the users need to add it in the `mmdet/models/losses/__init__.py`. + +```python +from .my_loss import MyLoss, my_loss + +``` + +Alternatively, you can add + +```python +custom_imports=dict( + imports=['mmdet.models.losses.my_loss']) +``` + +to the config file and achieve the same goal. + +To use it, modify the `loss_xxx` field. +Since MyLoss is for regression, you need to modify the `loss_bbox` field in the head. + +```python +loss_bbox=dict(type='MyLoss', loss_weight=1.0)) +``` diff --git a/mmdetection/docs/en/advanced_guides/customize_runtime.md b/mmdetection/docs/en/advanced_guides/customize_runtime.md new file mode 100644 index 00000000..e6ce740a --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/customize_runtime.md @@ -0,0 +1,391 @@ +# Customize Runtime Settings + +## Customize optimization settings + +Optimization related configuration is now all managed by `optim_wrapper` which usually has three fields: `optimizer`, `paramwise_cfg`, `clip_grad`, refer to [OptimWrapper](https://mmengine.readthedocs.io/en/latest/tutorials/optim_wrapper.md) for more detail. See the example below, where `Adamw` is used as an `optimizer`, the learning rate of the backbone is reduced by a factor of 10, and gradient clipping is added. + +```python +optim_wrapper = dict( + type='OptimWrapper', + # optimizer + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.05, + eps=1e-8, + betas=(0.9, 0.999)), + + # Parameter-level learning rate and weight decay settings + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + }, + norm_decay_mult=0.0), + + # gradient clipping + clip_grad=dict(max_norm=0.01, norm_type=2)) +``` + +### Customize optimizer supported by Pytorch + +We already support to use all the optimizers implemented by PyTorch, and the only modification is to change the `optimizer` field in `optim_wrapper` field of config files. For example, if you want to use `ADAM` (note that the performance could drop a lot), the modification could be as the following. + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='Adam', lr=0.0003, weight_decay=0.0001)) +``` + +To modify the learning rate of the model, the users only need to modify the `lr` in `optimizer`. The users can directly set arguments following the [API doc](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) of PyTorch. + +### Customize self-implemented optimizer + +#### 1. Define a new optimizer + +A customized optimizer could be defined as following. + +Assume you want to add a optimizer named `MyOptimizer`, which has arguments `a`, `b`, and `c`. +You need to create a new directory named `mmdet/engine/optimizers`. And then implement the new optimizer in a file, e.g., in `mmdet/engine/optimizers/my_optimizer.py`: + +```python +from mmdet.registry import OPTIMIZERS +from torch.optim import Optimizer + + +@OPTIMIZERS.register_module() +class MyOptimizer(Optimizer): + + def __init__(self, a, b, c) + +``` + +#### 2. Add the optimizer to registry + +To find the above module defined above, this module should be imported into the main namespace at first. There are two options to achieve it. + +- Modify `mmdet/engine/optimizers/__init__.py` to import it. + + The newly defined module should be imported in `mmdet/engine/optimizers/__init__.py` so that the registry will find the new module and add it: + +```python +from .my_optimizer import MyOptimizer +``` + +- Use `custom_imports` in the config to manually import it + +```python +custom_imports = dict(imports=['mmdet.engine.optimizers.my_optimizer'], allow_failed_imports=False) +``` + +The module `mmdet.engine.optimizers.my_optimizer` will be imported at the beginning of the program and the class `MyOptimizer` is then automatically registered. +Note that only the package containing the class `MyOptimizer` should be imported. +`mmdet.engine.optimizers.my_optimizer.MyOptimizer` **cannot** be imported directly. + +Actually users can use a totally different file directory structure using this importing method, as long as the module root can be located in `PYTHONPATH`. + +#### 3. Specify the optimizer in the config file + +Then you can use `MyOptimizer` in `optimizer` field in `optim_wrapper` field of config files. In the configs, the optimizers are defined by the field `optimizer` like the following: + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) +``` + +To use your own optimizer, the field can be changed to + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)) +``` + +### Customize optimizer wrapper constructor + +Some models may have some parameter-specific settings for optimization, e.g. weight decay for BatchNorm layers. +The users can do those fine-grained parameter tuning through customizing optimizer wrapper constructor. + +```python +from mmengine.optim import DefaultOptiWrapperConstructor + +from mmdet.registry import OPTIM_WRAPPER_CONSTRUCTORS +from .my_optimizer import MyOptimizer + + +@OPTIM_WRAPPER_CONSTRUCTORS.register_module() +class MyOptimizerWrapperConstructor(DefaultOptimWrapperConstructor): + + def __init__(self, + optim_wrapper_cfg: dict, + paramwise_cfg: Optional[dict] = None): + + def __call__(self, model: nn.Module) -> OptimWrapper: + + return optim_wrapper + +``` + +The default optimizer wrapper constructor is implemented [here](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L18), which could also serve as a template for the new optimizer wrapper constructor. + +### Additional settings + +Tricks not implemented by the optimizer should be implemented through optimizer wrapper constructor (e.g., set parameter-wise learning rates) or hooks. We list some common settings that could stabilize the training or accelerate the training. Feel free to create PR, issue for more settings. + +- __Use gradient clip to stabilize training__: + Some models need gradient clip to clip the gradients to stabilize the training process. An example is as below: + + ```python + optim_wrapper = dict( + _delete_=True, clip_grad=dict(max_norm=35, norm_type=2)) + ``` + + If your config inherits the base config which already sets the `optim_wrapper`, you might need `_delete_=True` to override the unnecessary settings. See the [config documentation](../user_guides/config.md) for more details. + +- __Use momentum schedule to accelerate model convergence__: + We support momentum scheduler to modify model's momentum according to learning rate, which could make the model converge in a faster way. + Momentum scheduler is usually used with LR scheduler, for example, the following config is used in [3D detection](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/_base_/schedules/cyclic-20e.py) to accelerate convergence. + For more details, please refer to the implementation of [CosineAnnealingLR](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py#L43) and [CosineAnnealingMomentum](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/momentum_scheduler.py#L71). + + ```python + param_scheduler = [ + # learning rate scheduler + # During the first 8 epochs, learning rate increases from 0 to lr * 10 + # during the next 12 epochs, learning rate decreases from lr * 10 to lr * 1e-4 + dict( + type='CosineAnnealingLR', + T_max=8, + eta_min=lr * 10, + begin=0, + end=8, + by_epoch=True, + convert_to_iter_based=True), + dict( + type='CosineAnnealingLR', + T_max=12, + eta_min=lr * 1e-4, + begin=8, + end=20, + by_epoch=True, + convert_to_iter_based=True), + # momentum scheduler + # During the first 8 epochs, momentum increases from 0 to 0.85 / 0.95 + # during the next 12 epochs, momentum increases from 0.85 / 0.95 to 1 + dict( + type='CosineAnnealingMomentum', + T_max=8, + eta_min=0.85 / 0.95, + begin=0, + end=8, + by_epoch=True, + convert_to_iter_based=True), + dict( + type='CosineAnnealingMomentum', + T_max=12, + eta_min=1, + begin=8, + end=20, + by_epoch=True, + convert_to_iter_based=True) + ] + ``` + +## Customize training schedules + +By default we use step learning rate with 1x schedule, this calls [MultiStepLR](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py#L139) in MMEngine. +We support many other learning rate schedule [here](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py), such as `CosineAnnealingLR` and `PolyLR` schedule. Here are some examples + +- Poly schedule: + + ```python + param_scheduler = [ + dict( + type='PolyLR', + power=0.9, + eta_min=1e-4, + begin=0, + end=8, + by_epoch=True)] + ``` + +- ConsineAnnealing schedule: + + ```python + param_scheduler = [ + dict( + type='CosineAnnealingLR', + T_max=8, + eta_min=lr * 1e-5, + begin=0, + end=8, + by_epoch=True)] + + ``` + +## Customize train loop + +By default, `EpochBasedTrainLoop` is used in `train_cfg` and validation is done after every train epoch, as follows. + +```python +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_begin=1, val_interval=1) +``` + +Actually, both [`IterBasedTrainLoop`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L183%5D) and [`EpochBasedTrainLoop`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L18) support dynamical interval, see the following example. + +```python +# Before 365001th iteration, we do evaluation every 5000 iterations. +# After 365000th iteration, we do evaluation every 368750 iterations, +# which means that we do evaluation at the end of training. + +interval = 5000 +max_iters = 368750 +dynamic_intervals = [(max_iters // interval * interval + 1, max_iters)] +train_cfg = dict( + type='IterBasedTrainLoop', + max_iters=max_iters, + val_interval=interval, + dynamic_intervals=dynamic_intervals) +``` + +## Customize hooks + +### Customize self-implemented hooks + +#### 1. Implement a new hook + +MMEngine provides many useful [hooks](https://mmengine.readthedocs.io/en/latest/tutorials/hooks.html), but there are some occasions when the users might need to implement a new hook. MMDetection supports customized hooks in training in v3.0 . Thus the users could implement a hook directly in mmdet or their mmdet-based codebases and use the hook by only modifying the config in training. +Here we give an example of creating a new hook in mmdet and using it in training. + +```python +from mmengine.hooks import Hook +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class MyHook(Hook): + + def __init__(self, a, b): + + def before_run(self, runner) -> None: + + def after_run(self, runner) -> None: + + def before_train(self, runner) -> None: + + def after_train(self, runner) -> None: + + def before_train_epoch(self, runner) -> None: + + def after_train_epoch(self, runner) -> None: + + def before_train_iter(self, + runner, + batch_idx: int, + data_batch: DATA_BATCH = None) -> None: + + def after_train_iter(self, + runner, + batch_idx: int, + data_batch: DATA_BATCH = None, + outputs: Optional[dict] = None) -> None: +``` + +Depending on the functionality of the hook, the users need to specify what the hook will do at each stage of the training in `before_run`, `after_run`, `before_train`, `after_train` , `before_train_epoch`, `after_train_epoch`, `before_train_iter`, and `after_train_iter`. There are more points where hooks can be inserted, refer to [base hook class](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/hook.py#L9) for more detail. + +#### 2. Register the new hook + +Then we need to make `MyHook` imported. Assuming the file is in `mmdet/engine/hooks/my_hook.py` there are two ways to do that: + +- Modify `mmdet/engine/hooks/__init__.py` to import it. + + The newly defined module should be imported in `mmdet/engine/hooks/__init__.py` so that the registry will find the new module and add it: + +```python +from .my_hook import MyHook +``` + +- Use `custom_imports` in the config to manually import it + +```python +custom_imports = dict(imports=['mmdet.engine.hooks.my_hook'], allow_failed_imports=False) +``` + +#### 3. Modify the config + +```python +custom_hooks = [ + dict(type='MyHook', a=a_value, b=b_value) +] +``` + +You can also set the priority of the hook by adding key `priority` to `'NORMAL'` or `'HIGHEST'` as below + +```python +custom_hooks = [ + dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL') +] +``` + +By default the hook's priority is set as `NORMAL` during registration. + +### Use hooks implemented in MMDetection + +If the hook is already implemented in MMDectection, you can directly modify the config to use the hook as below + +#### Example: `NumClassCheckHook` + +We implement a customized hook named [NumClassCheckHook](../../../mmdet/engine/hooks/num_class_check_hook.py) to check whether the `num_classes` in head matches the length of `classes` in the metainfo of `dataset`. + +We set it in [default_runtime.py](../../../configs/_base_/default_runtime.py). + +```python +custom_hooks = [dict(type='NumClassCheckHook')] +``` + +### Modify default runtime hooks + +There are some common hooks that are registered through `default_hooks`, they are + +- `IterTimerHook`: A hook that logs 'data_time' for loading data and 'time' for a model train step. +- `LoggerHook`: A hook that Collect logs from different components of `Runner` and write them to terminal, JSON file, tensorboard and wandb .etc. +- `ParamSchedulerHook`: A hook to update some hyper-parameters in optimizer, e.g., learning rate and momentum. +- `CheckpointHook`: A hook that saves checkpoints periodically. +- `DistSamplerSeedHook`: A hook that sets the seed for sampler and batch_sampler. +- `DetVisualizationHook`: A hook used to visualize validation and testing process prediction results. + +`IterTimerHook`, `ParamSchedulerHook` and `DistSamplerSeedHook` are simple and no need to be modified usually, so here we reveals how what we can do with `LoggerHook`, `CheckpointHook` and `DetVisualizationHook`. + +#### CheckpointHook + +Except saving checkpoints periodically, [`CheckpointHook`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L19) provides other options such as `max_keep_ckpts`, `save_optimizer` and etc. The users could set `max_keep_ckpts` to only save small number of checkpoints or decide whether to store state dict of optimizer by `save_optimizer`. More details of the arguments are [here](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L19) + +```python +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + interval=1, + max_keep_ckpts=3, + save_optimizer=True)) +``` + +#### LoggerHook + +The `LoggerHook` enables to set intervals. And the detail usages can be found in the [docstring](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/logger_hook.py#L18). + +```python +default_hooks = dict(logger=dict(type='LoggerHook', interval=50)) +``` + +#### DetVisualizationHook + +`DetVisualizationHook` use `DetLocalVisualizer` to visualize prediction results, and `DetLocalVisualizer` current supports different backends, e.g., `TensorboardVisBackend` and `WandbVisBackend` (see [docstring](https://github.com/open-mmlab/mmengine/blob/main/mmengine/visualization/vis_backend.py) for more detail). The users could add multi backbends to do visualization, as follows. + +```python +default_hooks = dict( + visualization=dict(type='DetVisualizationHook', draw=True)) + +vis_backends = [dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend')] +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') +``` diff --git a/mmdetection/docs/en/advanced_guides/customize_transforms.md b/mmdetection/docs/en/advanced_guides/customize_transforms.md new file mode 100644 index 00000000..5fe84e9f --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/customize_transforms.md @@ -0,0 +1,49 @@ +# Customize Data Pipelines + +1. Write a new transform in a file, e.g., in `my_pipeline.py`. It takes a dict as input and returns a dict. + + ```python + import random + from mmcv.transforms import BaseTransform + from mmdet.registry import TRANSFORMS + + + @TRANSFORMS.register_module() + class MyTransform(BaseTransform): + """Add your transform + + Args: + p (float): Probability of shifts. Default 0.5. + """ + + def __init__(self, prob=0.5): + self.prob = prob + + def transform(self, results): + if random.random() > self.prob: + results['dummy'] = True + return results + ``` + +2. Import and use the pipeline in your config file. + Make sure the import is relative to where your train script is located. + + ```python + custom_imports = dict(imports=['path.to.my_pipeline'], allow_failed_imports=False) + + train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='MyTransform', prob=0.2), + dict(type='PackDetInputs') + ] + ``` + +3. Visualize the output of your transforms pipeline + + To visualize the output of your transforms pipeline, `tools/misc/browse_dataset.py` + can help the user to browse a detection dataset (both images and bounding box annotations) + visually, or save the image to a designated directory. More details can refer to + [visualization documentation](../user_guides/visualization.md) diff --git a/mmdetection/docs/en/advanced_guides/data_flow.md b/mmdetection/docs/en/advanced_guides/data_flow.md new file mode 100644 index 00000000..59e7ca32 --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/data_flow.md @@ -0,0 +1 @@ +# Data Flow diff --git a/mmdetection/docs/en/advanced_guides/datasets.md b/mmdetection/docs/en/advanced_guides/datasets.md new file mode 100644 index 00000000..157ea3aa --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/datasets.md @@ -0,0 +1 @@ +# Datasets diff --git a/mmdetection/docs/en/advanced_guides/engine.md b/mmdetection/docs/en/advanced_guides/engine.md new file mode 100644 index 00000000..eaa55b0c --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/engine.md @@ -0,0 +1 @@ +# Engine diff --git a/mmdetection/docs/en/advanced_guides/evaluation.md b/mmdetection/docs/en/advanced_guides/evaluation.md new file mode 100644 index 00000000..b394c769 --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/evaluation.md @@ -0,0 +1 @@ +# Evaluation diff --git a/mmdetection/docs/en/advanced_guides/how_to.md b/mmdetection/docs/en/advanced_guides/how_to.md new file mode 100644 index 00000000..7eb41cee --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/how_to.md @@ -0,0 +1,222 @@ +This tutorial collects answers to any `How to xxx with MMDetection`. Feel free to update this doc if you meet new questions about `How to` and find the answers! + +# Use backbone network through MMPretrain + +The model registry in MMDet, MMPreTrain, MMSeg all inherit from the root registry in MMEngine. This allows these repositories to directly use the modules already implemented by each other. Therefore, users can use backbone networks from MMPretrain in MMDetection without implementing a network that already exists in MMPretrain. + +## Use backbone network implemented in MMPretrain + +Suppose you want to use `MobileNetV3-small` as the backbone network of `RetinaNet`, the example config is as the following. + +```python +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# please install mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict(imports=['mmpretrain.models'], allow_failed_imports=False) +pretrained = 'https://download.openmmlab.com/mmclassification/v0/mobilenet_v3/convert/mobilenet_v3_small-8427ecf0.pth' +model = dict( + backbone=dict( + _delete_=True, # Delete the backbone field in _base_ + type='mmpretrain.MobileNetV3', # Using MobileNetV3 from mmpretrain + arch='small', + out_indices=(3, 8, 11), # Modify out_indices + init_cfg=dict( + type='Pretrained', + checkpoint=pretrained, + prefix='backbone.')), # The pre-trained weights of backbone network in mmpretrain have prefix='backbone.'. The prefix in the keys will be removed so that these weights can be normally loaded. + # Modify in_channels + neck=dict(in_channels=[24, 48, 96], start_level=0)) +``` + +## Use backbone network in TIMM through MMPretrain + +MMPretrain also provides a wrapper for the PyTorch Image Models (timm) backbone network, users can directly use the backbone network in timm through MMPretrain. Suppose you want to use [EfficientNet-B1](../../../configs/timm_example/retinanet_timm-efficientnet-b1_fpn_1x_coco.py) as the backbone network of RetinaNet, the example config is as the following. + +```python +# https://github.com/open-mmlab/mmdetection/blob/main/configs/timm_example/retinanet_timm-efficientnet-b1_fpn_1x_coco.py + +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# please install mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict(imports=['mmpretrain.models'], allow_failed_imports=False) +model = dict( + backbone=dict( + _delete_=True, # Delete the backbone field in _base_ + type='mmpretrain.TIMMBackbone', # Using timm from mmpretrain + model_name='efficientnet_b1', + features_only=True, + pretrained=True, + out_indices=(1, 2, 3, 4)), # Modify out_indices + neck=dict(in_channels=[24, 40, 112, 320])) # Modify in_channels + +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001) +``` + +`type='mmpretrain.TIMMBackbone'` means use the `TIMMBackbone` class from MMPretrain in MMDetection, and the model used is `EfficientNet-B1`, where `mmpretrain` means the MMPretrain repo and `TIMMBackbone` means the TIMMBackbone wrapper implemented in MMPretrain. + +For the principle of the Hierarchy Registry, please refer to the [MMEngine document](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/config.md). For how to use other backbones in MMPretrain, you can refer to the [MMPretrain document](https://mmpretrain.readthedocs.io/en/latest/user_guides/config.html). + +# Use Mosaic augmentation + +If you want to use `Mosaic` in training, please make sure that you use `MultiImageMixDataset` at the same time. Taking the 'Faster R-CNN' algorithm as an example, you should modify the values of `train_pipeline` and `train_dataset` in the config as below: + +```python +# Open configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py directly and add the following fields +data_root = 'data/coco/' +dataset_type = 'CocoDataset' +img_scale=(1333, 800) + +train_pipeline = [ + dict(type='Mosaic', img_scale=img_scale, pad_val=114.0), + dict( + type='RandomAffine', + scaling_ratio_range=(0.1, 2), + border=(-img_scale[0] // 2, -img_scale[1] // 2)), # The image will be enlarged by 4 times after Mosaic processing,so we use affine transformation to restore the image size. + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataset = dict( + _delete_ = True, # remove unnecessary Settings + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + ann_file=data_root + 'annotations/instances_train2017.json', + img_prefix=data_root + 'train2017/', + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True) + ], + filter_empty_gt=False, + ), + pipeline=train_pipeline + ) + +data = dict( + train=train_dataset + ) +``` + +# Unfreeze backbone network after freezing the backbone in the config + +If you have freezed the backbone network in the config and want to unfreeze it after some epoches, you can write a hook function to do it. Taking the Faster R-CNN with the resnet backbone as an example, you can freeze one stage of the backbone network and add a `custom_hooks` in the config as below: + +```python +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + # freeze one stage of the backbone network. + backbone=dict(frozen_stages=1), +) +custom_hooks = [dict(type="UnfreezeBackboneEpochBasedHook", unfreeze_epoch=1)] +``` + +Meanwhile write the hook class `UnfreezeBackboneEpochBasedHook` in `mmdet/core/hook/unfreeze_backbone_epoch_based_hook.py` + +```python +from mmengine.model import is_model_wrapper +from mmengine.hooks import Hook +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class UnfreezeBackboneEpochBasedHook(Hook): + """Unfreeze backbone network Hook. + + Args: + unfreeze_epoch (int): The epoch unfreezing the backbone network. + """ + + def __init__(self, unfreeze_epoch=1): + self.unfreeze_epoch = unfreeze_epoch + + def before_train_epoch(self, runner): + # Unfreeze the backbone network. + # Only valid for resnet. + if runner.epoch == self.unfreeze_epoch: + model = runner.model + if is_model_wrapper(model): + model = model.module + backbone = model.backbone + if backbone.frozen_stages >= 0: + if backbone.deep_stem: + backbone.stem.train() + for param in backbone.stem.parameters(): + param.requires_grad = True + else: + backbone.norm1.train() + for m in [backbone.conv1, backbone.norm1]: + for param in m.parameters(): + param.requires_grad = True + + for i in range(1, backbone.frozen_stages + 1): + m = getattr(backbone, f'layer{i}') + m.train() + for param in m.parameters(): + param.requires_grad = True +``` + +# Get the channels of a new backbone + +If you want to get the channels of a new backbone, you can build this backbone alone and input a pseudo image to get each stage output. + +Take `ResNet` as an example: + +```python +from mmdet.models import ResNet +import torch +self = ResNet(depth=18) +self.eval() +inputs = torch.rand(1, 3, 32, 32) +level_outputs = self.forward(inputs) +for level_out in level_outputs: + print(tuple(level_out.shape)) + +``` + +Output of the above script is as below: + +```python +(1, 64, 8, 8) +(1, 128, 4, 4) +(1, 256, 2, 2) +(1, 512, 1, 1) +``` + +Users can get the channels of the new backbone by Replacing the `ResNet(depth=18)` in this script with their customized backbone. + +# Use Detectron2 Model in MMDetection + +Users can use Detectron2Wrapper to run Detectron2's model in MMDetection. We provide examples of [Faster R-CNN](../../../configs/misc/d2_faster-rcnn_r50-caffe_fpn_ms-90k_coco.py), +[Mask R-CNN](../../../configs/misc/d2_mask-rcnn_r50-caffe_fpn_ms-90k_coco.py), and [RetinaNet](../../../configs/misc/d2_retinanet_r50-caffe_fpn_ms-90k_coco.py) in MMDetection. + +The algorithm components in config file should be the same as those of in Detectron2. During setup, we will first initialize the default settings, which can be found in [Detectron2](https://github.com/facebookresearch/detectron2/blob/main/detectron2/config/defaults.py). +Then, the settings in config file will overwrite the default settings and the model will be built with these settings. +The input data will first convert to Detectron2's type and feed into Detectron2's model. +During inference the results calculate from Detectron2's model will reconvert back to the MMDetection's type. + +## Use Detectron2's pre-trained weights + +The weight initialization in `Detectron2Wrapper` will not use the logic of MMDetection. Users can set `model.d2_detector.weights=xxx` to load pre-trained weights. +For example, we can use `model.d2_detector.weights='detectron2://ImageNetPretrained/MSRA/R-50.pkl'` to load the pre-trained ResNet-50 or use +`model.d2_detector.weights='detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x/137260431/model_final_a54504.pkl'` to load the pre-trained Mask R-CNN weights proposed in Detectron2. + +**Note:** Detectron2's pretrained model cannot be loaded directly by using `load_from`, it should be first converted via `tools/model_converters/detectron2_to_mmdet.py` + +For inference of released detectron2 checkpoints, users should first use `tools/model_converters/detectron2_to_mmdet.py` to convert Detectron2 checkpoint to MMDetection. + +```shell +python tools/model_converters/detectron2_to_mmdet.py ${Detectron2 ckpt path} ${MMDetectron ckpt path} +``` diff --git a/mmdetection/docs/en/advanced_guides/index.rst b/mmdetection/docs/en/advanced_guides/index.rst new file mode 100644 index 00000000..20d81774 --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/index.rst @@ -0,0 +1,34 @@ +Basic Concepts +*************** + +.. toctree:: + :maxdepth: 1 + + data_flow.md + structures.md + models.md + datasets.md + transforms.md + evaluation.md + engine.md + conventions.md + +Component Customization +************************ + +.. toctree:: + :maxdepth: 1 + + customize_models.md + customize_losses.md + customize_dataset.md + customize_transforms.md + customize_runtime.md + +How to +************************ + +.. toctree:: + :maxdepth: 1 + + how_to.md diff --git a/mmdetection/docs/en/advanced_guides/models.md b/mmdetection/docs/en/advanced_guides/models.md new file mode 100644 index 00000000..91361720 --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/models.md @@ -0,0 +1 @@ +# Models diff --git a/mmdetection/docs/en/advanced_guides/structures.md b/mmdetection/docs/en/advanced_guides/structures.md new file mode 100644 index 00000000..98528617 --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/structures.md @@ -0,0 +1 @@ +# Structures diff --git a/mmdetection/docs/en/advanced_guides/transforms.md b/mmdetection/docs/en/advanced_guides/transforms.md new file mode 100644 index 00000000..4db036ae --- /dev/null +++ b/mmdetection/docs/en/advanced_guides/transforms.md @@ -0,0 +1,42 @@ +# Data Transforms (Need to update) + +## Design of Data transforms pipeline + +Following typical conventions, we use `Dataset` and `DataLoader` for data loading +with multiple workers. `Dataset` returns a dict of data items corresponding +the arguments of models' forward method. + +The data transforms pipeline and the dataset is decomposed. Usually a dataset +defines how to process the annotations and a data transforms pipeline defines all the steps to prepare a data dict. +A pipeline consists of a sequence of data transforms. Each operation takes a dict as input and also output a dict for the next transform. + +We present a classical pipeline in the following figure. The blue blocks are pipeline operations. With the pipeline going on, each operator can add new keys (marked as green) to the result dict or update the existing keys (marked as orange). +![pipeline figure](../../../resources/data_pipeline.png) + +Here is a pipeline example for Faster R-CNN. + +```python +train_pipeline = [ # Training data processing pipeline + dict(type='LoadImageFromFile', backend_args=backend_args), # First pipeline to load images from file path + dict( + type='LoadAnnotations', # Second pipeline to load annotations for current image + with_bbox=True), # Whether to use bounding box, True for detection + dict( + type='Resize', # Pipeline that resize the images and their annotations + scale=(1333, 800), # The largest scale of image + keep_ratio=True # Whether to keep the ratio between height and width + ), + dict( + type='RandomFlip', # Augmentation pipeline that flip the images and their annotations + prob=0.5), # The probability to flip + dict(type='PackDetInputs') # Pipeline that formats the annotation data and decides which keys in the data should be packed into data_samples +] +test_pipeline = [ # Testing data processing pipeline + dict(type='LoadImageFromFile', backend_args=backend_args), # First pipeline to load images from file path + dict(type='Resize', scale=(1333, 800), keep_ratio=True), # Pipeline that resize the images + dict( + type='PackDetInputs', # Pipeline that formats the annotation data and decides which keys in the data should be packed into data_samples + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +``` diff --git a/mmdetection/docs/en/api.rst b/mmdetection/docs/en/api.rst new file mode 100644 index 00000000..1b127321 --- /dev/null +++ b/mmdetection/docs/en/api.rst @@ -0,0 +1,161 @@ +mmdet.apis +-------------- +.. automodule:: mmdet.apis + :members: + +mmdet.datasets +-------------- + +datasets +^^^^^^^^^^ +.. automodule:: mmdet.datasets + :members: + +api_wrappers +^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.datasets.api_wrappers + :members: + +samplers +^^^^^^^^^^ +.. automodule:: mmdet.datasets.samplers + :members: + +transforms +^^^^^^^^^^^^ +.. automodule:: mmdet.datasets.transforms + :members: + +mmdet.engine +-------------- + +hooks +^^^^^^^^^^ +.. automodule:: mmdet.engine.hooks + :members: + +optimizers +^^^^^^^^^^^^^^^ +.. automodule:: mmdet.engine.optimizers + :members: + +runner +^^^^^^^^^^ +.. automodule:: mmdet.engine.runner + :members: + +schedulers +^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.engine.schedulers + :members: + +mmdet.evaluation +-------------------- + +functional +^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.evaluation.functional + :members: + +metrics +^^^^^^^^^^ +.. automodule:: mmdet.evaluation.metrics + :members: + + +mmdet.models +-------------- + +backbones +^^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.models.backbones + :members: + +data_preprocessors +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.models.data_preprocessors + :members: + +dense_heads +^^^^^^^^^^^^^^^ +.. automodule:: mmdet.models.dense_heads + :members: + +detectors +^^^^^^^^^^ +.. automodule:: mmdet.models.detectors + :members: + +layers +^^^^^^^^^^ +.. automodule:: mmdet.models.layers + :members: + +losses +^^^^^^^^^^ +.. automodule:: mmdet.models.losses + :members: + +necks +^^^^^^^^^^^^ +.. automodule:: mmdet.models.necks + :members: + +roi_heads +^^^^^^^^^^^^^ +.. automodule:: mmdet.models.roi_heads + :members: + +seg_heads +^^^^^^^^^^^^^ +.. automodule:: mmdet.models.seg_heads + :members: + +task_modules +^^^^^^^^^^^^^ +.. automodule:: mmdet.models.task_modules + :members: + +test_time_augs +^^^^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.models.test_time_augs + :members: + +utils +^^^^^^^^^^ +.. automodule:: mmdet.models.utils + :members: + + +mmdet.structures +-------------------- + +structures +^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.structures + :members: + +bbox +^^^^^^^^^^ +.. automodule:: mmdet.structures.bbox + :members: + +mask +^^^^^^^^^^ +.. automodule:: mmdet.structures.mask + :members: + +mmdet.testing +---------------- +.. automodule:: mmdet.testing + :members: + +mmdet.visualization +-------------------- +.. automodule:: mmdet.visualization + :members: + +mmdet.utils +-------------- +.. automodule:: mmdet.utils + :members: diff --git a/mmdetection/docs/en/conf.py b/mmdetection/docs/en/conf.py new file mode 100644 index 00000000..d2beaf1e --- /dev/null +++ b/mmdetection/docs/en/conf.py @@ -0,0 +1,116 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/main/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import subprocess +import sys + +import pytorch_sphinx_theme + +sys.path.insert(0, os.path.abspath('../..')) + +# -- Project information ----------------------------------------------------- + +project = 'MMDetection' +copyright = '2018-2021, OpenMMLab' +author = 'MMDetection Authors' +version_file = '../../mmdet/version.py' + + +def get_version(): + with open(version_file, 'r') as f: + exec(compile(f.read(), version_file, 'exec')) + return locals()['__version__'] + + +# The full version, including alpha/beta/rc tags +release = get_version() + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'myst_parser', + 'sphinx_markdown_tables', + 'sphinx_copybutton', +] + +myst_enable_extensions = ['colon_fence'] +myst_heading_anchors = 3 + +autodoc_mock_imports = [ + 'matplotlib', 'pycocotools', 'terminaltables', 'mmdet.version', 'mmcv.ops' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +# The main toctree document. +master_doc = 'index' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +# html_theme = 'sphinx_rtd_theme' +html_theme = 'pytorch_sphinx_theme' +html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()] + +html_theme_options = { + 'menu': [ + { + 'name': 'GitHub', + 'url': 'https://github.com/open-mmlab/mmdetection' + }, + ], + # Specify the language of shared menu + 'menu_lang': + 'en' +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = ['css/readthedocs.css'] + +# -- Extension configuration ------------------------------------------------- +# Ignore >>> when copying code +copybutton_prompt_text = r'>>> |\.\.\. ' +copybutton_prompt_is_regexp = True + + +def builder_inited_handler(app): + subprocess.run(['./stat.py']) + + +def setup(app): + app.connect('builder-inited', builder_inited_handler) diff --git a/mmdetection/docs/en/dataset_zoo.md b/mmdetection/docs/en/dataset_zoo.md new file mode 100644 index 00000000..c35cc220 --- /dev/null +++ b/mmdetection/docs/en/dataset_zoo.md @@ -0,0 +1 @@ +# Dataset Zoo diff --git a/mmdetection/docs/en/get_started.md b/mmdetection/docs/en/get_started.md new file mode 100644 index 00000000..f65878b6 --- /dev/null +++ b/mmdetection/docs/en/get_started.md @@ -0,0 +1,297 @@ +# GET STARTED + +## Prerequisites + +In this section, we demonstrate how to prepare an environment with PyTorch. + +MMDetection works on Linux, Windows, and macOS. It requires Python 3.7+, CUDA 9.2+, and PyTorch 1.8+. + +```{note} +If you are experienced with PyTorch and have already installed it, just skip this part and jump to the [next section](#installation). Otherwise, you can follow these steps for the preparation. +``` + +**Step 0.** Download and install Miniconda from the [official website](https://docs.conda.io/en/latest/miniconda.html). + +**Step 1.** Create a conda environment and activate it. + +```shell +conda create --name openmmlab python=3.8 -y +conda activate openmmlab +``` + +**Step 2.** Install PyTorch following [official instructions](https://pytorch.org/get-started/locally/), e.g. + +On GPU platforms: + +```shell +conda install pytorch torchvision -c pytorch +``` + +On CPU platforms: + +```shell +conda install pytorch torchvision cpuonly -c pytorch +``` + +## Installation + +We recommend that users follow our best practices to install MMDetection. However, the whole process is highly customizable. See [Customize Installation](#customize-installation) section for more information. + +### Best Practices + +**Step 0.** Install [MMEngine](https://github.com/open-mmlab/mmengine) and [MMCV](https://github.com/open-mmlab/mmcv) using [MIM](https://github.com/open-mmlab/mim). + +```shell +pip install -U openmim +mim install mmengine +mim install "mmcv>=2.0.0" +``` + +**Note:** In MMCV-v2.x, `mmcv-full` is rename to `mmcv`, if you want to install `mmcv` without CUDA ops, you can use `mim install "mmcv-lite>=2.0.0rc1"` to install the lite version. + +**Step 1.** Install MMDetection. + +Case a: If you develop and run mmdet directly, install it from source: + +```shell +git clone https://github.com/open-mmlab/mmdetection.git +cd mmdetection +pip install -v -e . +# "-v" means verbose, or more output +# "-e" means installing a project in editable mode, +# thus any local modifications made to the code will take effect without reinstallation. +``` + +Case b: If you use mmdet as a dependency or third-party package, install it with MIM: + +```shell +mim install mmdet +``` + +## Verify the installation + +To verify whether MMDetection is installed correctly, we provide some sample codes to run an inference demo. + +**Step 1.** We need to download config and checkpoint files. + +```shell +mim download mmdet --config rtmdet_tiny_8xb32-300e_coco --dest . +``` + +The downloading will take several seconds or more, depending on your network environment. When it is done, you will find two files `rtmdet_tiny_8xb32-300e_coco.py` and `rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth` in your current folder. + +**Step 2.** Verify the inference demo. + +Case a: If you install MMDetection from source, just run the following command. + +```shell +python demo/image_demo.py demo/demo.jpg rtmdet_tiny_8xb32-300e_coco.py --weights rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth --device cpu +``` + +You will see a new image `demo.jpg` on your `./outputs/vis` folder, where bounding boxes are plotted on cars, benches, etc. + +Case b: If you install MMDetection with MIM, open your python interpreter and copy&paste the following codes. + +```python +from mmdet.apis import init_detector, inference_detector + +config_file = 'rtmdet_tiny_8xb32-300e_coco.py' +checkpoint_file = 'rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth' +model = init_detector(config_file, checkpoint_file, device='cpu') # or device='cuda:0' +inference_detector(model, 'demo/demo.jpg') +``` + +You will see a list of `DetDataSample`, and the predictions are in the `pred_instance`, indicating the detected bounding boxes, labels, and scores. + +## Tracking Installation + +We recommend that users follow our best practices to install MMDetection for tracking task. + +### Best Practices + +**Step 0.** Install [MMEngine](https://github.com/open-mmlab/mmengine) and [MMCV](https://github.com/open-mmlab/mmcv) using [MIM](https://github.com/open-mmlab/mim). + +```shell +pip install -U openmim +mim install mmengine +mim install "mmcv>=2.0.0" +``` + +**Step 1.** Install MMDetection. + +Case a: If you develop and run mmdet directly, install it from source: + +```shell +git clone https://github.com/open-mmlab/mmdetection.git +cd mmdetection +pip install -v -e . -r requirements/tracking.txt +# "-v" means verbose, or more output +# "-e" means installing a project in editable mode, +# thus any local modifications made to the code will take effect without reinstallation. +``` + +Case b: If you use mmdet as a dependency or third-party package, install it with MIM: + +```shell +mim install mmdet[tracking] +``` + +**Step 2.** Install TrackEval. + +```shell +pip install git+https://github.com/JonathonLuiten/TrackEval.git +``` + +## Verify the installation + +To verify whether MMDetection is installed correctly, we provide some sample codes to run an inference demo. + +**Step 1.** We need to download config and checkpoint files. + +```shell +mim download mmdet --config bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval --dest . +``` + +The downloading will take several seconds or more, depending on your network environment. When it is done, you will find two files `bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py` and `bytetrack_yolox_x_crowdhuman_mot17-private-half_20211218_205500-1985c9f0.pth` in your current folder. + +**Step 2.** Verify the inference demo. + +Case a: If you install MMDetection from source, just run the following command. + +```shell +python demo/mot_demo.py demo/demo_mot.mp4 bytetrack_yolox_x_8xb4-amp-80e_crowdhuman-mot17halftrain_test-mot17halfval.py --checkpoint bytetrack_yolox_x_crowdhuman_mot17-private-half_20211218_205500-1985c9f0.pth --out mot.mp4 +``` + +You will see a new video `mot.mp4` on your folder, where bounding boxes are plotted on person. + +Case b: If you install MMDetection with MIM, open your python interpreter and demo/mot_demo.py, then run it like Case a. + +### Customize Installation + +#### CUDA versions + +When installing PyTorch, you need to specify the version of CUDA. If you are not clear on which to choose, follow our recommendations: + +- For Ampere-based NVIDIA GPUs, such as GeForce 30 series and NVIDIA A100, CUDA 11 is a must. +- For older NVIDIA GPUs, CUDA 11 is backward compatible, but CUDA 10.2 offers better compatibility and is more lightweight. + +Please make sure the GPU driver satisfies the minimum version requirements. See [this table](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions__table-cuda-toolkit-driver-versions) for more information. + +```{note} +Installing CUDA runtime libraries is enough if you follow our best practices, because no CUDA code will be compiled locally. However, if you hope to compile MMCV from source or develop other CUDA operators, you need to install the complete CUDA toolkit from NVIDIA's [website](https://developer.nvidia.com/cuda-downloads), and its version should match the CUDA version of PyTorch. i.e., the specified version of cudatoolkit in the `conda install` command. +``` + +#### Install MMEngine without MIM + +To install MMEngine with pip instead of MIM, please follow [MMEngine installation guides](https://mmengine.readthedocs.io/en/latest/get_started/installation.html). + +For example, you can install MMEngine by the following command. + +```shell +pip install mmengine +``` + +#### Install MMCV without MIM + +MMCV contains C++ and CUDA extensions, thus depending on PyTorch in a complex way. MIM solves such dependencies automatically and makes the installation easier. However, it is not a must. + +To install MMCV with pip instead of MIM, please follow [MMCV installation guides](https://mmcv.readthedocs.io/en/2.x/get_started/installation.html). This requires manually specifying a find-url based on the PyTorch version and its CUDA version. + +For example, the following command installs MMCV built for PyTorch 1.12.x and CUDA 11.6. + +```shell +pip install "mmcv>=2.0.0" -f https://download.openmmlab.com/mmcv/dist/cu116/torch1.12.0/index.html +``` + +#### Install on CPU-only platforms + +MMDetection can be built for CPU-only environments. In CPU mode you can train (requires MMCV version >= 2.0.0rc1), test, or infer a model. + +However, some functionalities are gone in this mode: + +- Deformable Convolution +- Modulated Deformable Convolution +- ROI pooling +- Deformable ROI pooling +- CARAFE +- SyncBatchNorm +- CrissCrossAttention +- MaskedConv2d +- Temporal Interlace Shift +- nms_cuda +- sigmoid_focal_loss_cuda +- bbox_overlaps + +If you try to train/test/infer a model containing the above ops, an error will be raised. +The following table lists affected algorithms. + +| Operator | Model | +| :-----------------------------------------------------: | :--------------------------------------------------------------------------------------: | +| Deformable Convolution/Modulated Deformable Convolution | DCN, Guided Anchoring, RepPoints, CentripetalNet, VFNet, CascadeRPN, NAS-FCOS, DetectoRS | +| MaskedConv2d | Guided Anchoring | +| CARAFE | CARAFE | +| SyncBatchNorm | ResNeSt | + +#### Install on Google Colab + +[Google Colab](https://colab.research.google.com/) usually has PyTorch installed, +thus we only need to install MMEngine, MMCV, and MMDetection with the following commands. + +**Step 1.** Install [MMEngine](https://github.com/open-mmlab/mmengine) and [MMCV](https://github.com/open-mmlab/mmcv) using [MIM](https://github.com/open-mmlab/mim). + +```shell +!pip3 install openmim +!mim install mmengine +!mim install "mmcv>=2.0.0,<2.1.0" +``` + +**Step 2.** Install MMDetection from the source. + +```shell +!git clone https://github.com/open-mmlab/mmdetection.git +%cd mmdetection +!pip install -e . +``` + +**Step 3.** Verification. + +```python +import mmdet +print(mmdet.__version__) +# Example output: 3.0.0, or an another version. +``` + +```{note} +Within Jupyter, the exclamation mark `!` is used to call external executables and `%cd` is a [magic command](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-cd) to change the current working directory of Python. +``` + +#### Use MMDetection with Docker + +We provide a [Dockerfile](../../docker/Dockerfile) to build an image. Ensure that your [docker version](https://docs.docker.com/engine/install/) >=19.03. + +```shell +# build an image with PyTorch 1.9, CUDA 11.1 +# If you prefer other versions, just modified the Dockerfile +docker build -t mmdetection docker/ +``` + +Run it with + +```shell +docker run --gpus all --shm-size=8g -it -v {DATA_DIR}:/mmdetection/data mmdetection +``` + +### Troubleshooting + +If you have some issues during the installation, please first view the [FAQ](notes/faq.md) page. +You may [open an issue](https://github.com/open-mmlab/mmdetection/issues/new/choose) on GitHub if no solution is found. + +### Use Multiple Versions of MMDetection in Development + +Training and testing scripts have already been modified in `PYTHONPATH` in order to make sure the scripts are using their own versions of MMDetection. + +To install the default version of MMDetection in your environment, you can exclude the follow code in the relative scripts: + +```shell +PYTHONPATH="$(dirname $0)/..":$PYTHONPATH +``` diff --git a/mmdetection/docs/en/index.rst b/mmdetection/docs/en/index.rst new file mode 100644 index 00000000..32c5952a --- /dev/null +++ b/mmdetection/docs/en/index.rst @@ -0,0 +1,63 @@ +Welcome to MMDetection's documentation! +======================================= + +.. toctree:: + :maxdepth: 1 + :caption: Get Started + + overview.md + get_started.md + +.. toctree:: + :maxdepth: 2 + :caption: User Guides + + user_guides/index.rst + +.. toctree:: + :maxdepth: 2 + :caption: Advanced Guides + + advanced_guides/index.rst + +.. toctree:: + :maxdepth: 1 + :caption: Migration + + migration/migration.md + +.. toctree:: + :maxdepth: 1 + :caption: API Reference + + api.rst + +.. toctree:: + :maxdepth: 1 + :caption: Model Zoo + + model_zoo.md + +.. toctree:: + :maxdepth: 1 + :caption: Notes + + notes/contribution_guide.md + notes/projects.md + notes/changelog.md + notes/changelog_v2.x.md + notes/faq.md + notes/compatibility.md + +.. toctree:: + :caption: Switch Language + + switch_language.md + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/mmdetection/docs/en/make.bat b/mmdetection/docs/en/make.bat new file mode 100644 index 00000000..922152e9 --- /dev/null +++ b/mmdetection/docs/en/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/mmdetection/docs/en/migration.md b/mmdetection/docs/en/migration.md new file mode 100644 index 00000000..689e8d24 --- /dev/null +++ b/mmdetection/docs/en/migration.md @@ -0,0 +1 @@ +# Migration diff --git a/mmdetection/docs/en/migration/api_and_registry_migration.md b/mmdetection/docs/en/migration/api_and_registry_migration.md new file mode 100644 index 00000000..72bfd3ae --- /dev/null +++ b/mmdetection/docs/en/migration/api_and_registry_migration.md @@ -0,0 +1 @@ +# Migrate API and Registry from MMDetection 2.x to 3.x diff --git a/mmdetection/docs/en/migration/config_migration.md b/mmdetection/docs/en/migration/config_migration.md new file mode 100644 index 00000000..1177fa9f --- /dev/null +++ b/mmdetection/docs/en/migration/config_migration.md @@ -0,0 +1,819 @@ +# Migrate Configuration File from MMDetection 2.x to 3.x + +The configuration file of MMDetection 3.x has undergone significant changes in comparison to the 2.x version. This document explains how to migrate 2.x configuration files to 3.x. + +In the previous tutorial [Learn about Configs](../user_guides/config.md), we used Mask R-CNN as an example to introduce the configuration file structure of MMDetection 3.x. Here, we will follow the same structure to demonstrate how to migrate 2.x configuration files to 3.x. + +## Model Configuration + +There have been no major changes to the model configuration in 3.x compared to 2.x. For the model's backbone, neck, head, as well as train_cfg and test_cfg, the parameters remain the same as in version 2.x. + +On the other hand, we have added the `DataPreprocessor` module in MMDetection 3.x. The configuration for the `DataPreprocessor` module is located in `model.data_preprocessor`. It is used to preprocess the input data, such as normalizing input images and padding images of different sizes into batches, and loading images from memory to VRAM. This configuration replaces the `Normalize` and `Pad` modules in `train_pipeline` and `test_pipeline` of the earlier version. + + + + + + + + + +
    2.x Config + +```python +# Image normalization parameters +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + to_rgb=True) +pipeline=[ + ..., + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), # Padding the image to multiples of 32 + ... +] +``` + +
    3.x Config + +```python +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + # Image normalization parameters + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + # Image padding parameters + pad_mask=True, # In instance segmentation, the mask needs to be padded + pad_size_divisor=32) # Padding the image to multiples of 32 +) + +``` + +
    + +## Dataset and Evaluator Configuration + +The dataset and evaluator configurations have undergone major changes compared to version 2.x. We will introduce how to migrate from version 2.x to version 3.x from three aspects: Dataloader and Dataset, Data transform pipeline, and Evaluator configuration. + +### Dataloader and Dataset Configuration + +In the new version, we set the data loading settings consistent with PyTorch's official DataLoader, +making it easier for users to understand and get started with. +We put the data loading settings for training, validation, and testing separately in `train_dataloader`, `val_dataloader`, and `test_dataloader`. +Users can set different parameters for these dataloaders. +The input parameters are basically the same as those required by [PyTorch DataLoader](https://pytorch.org/docs/stable/data.html?highlight=dataloader#torch.utils.data.DataLoader). + +This way, we put the unconfigurable parameters in version 2.x, such as `sampler`, `batch_sampler`, and `persistent_workers`, in the configuration file, so that users can set dataloader parameters more flexibly. + +Users can set the dataset configuration through `train_dataloader.dataset`, `val_dataloader.dataset`, and `test_dataloader.dataset`, which correspond to `data.train`, `data.val`, and `data.test` in version 2.x. + + + + + + + + + +
    2.x Config + +```python +data = dict( + samples_per_gpu=2, + workers_per_gpu=2, + train=dict( + type=dataset_type, + ann_file=data_root + 'annotations/instances_train2017.json', + img_prefix=data_root + 'train2017/', + pipeline=train_pipeline), + val=dict( + type=dataset_type, + ann_file=data_root + 'annotations/instances_val2017.json', + img_prefix=data_root + 'val2017/', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + ann_file=data_root + 'annotations/instances_val2017.json', + img_prefix=data_root + 'val2017/', + pipeline=test_pipeline)) +``` + +
    3.x Config + +```python +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, # Avoid recreating subprocesses after each iteration + sampler=dict(type='DefaultSampler', shuffle=True), # Default sampler, supports both distributed and non-distributed training + batch_sampler=dict(type='AspectRatioBatchSampler'), # Default batch_sampler, used to ensure that images in the batch have similar aspect ratios, so as to better utilize graphics memory + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline)) +# In version 3.x, validation and test dataloaders can be configured independently +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader # The configuration of the testing dataloader is the same as that of the validation dataloader, which is omitted here + +``` + +
    + +### Data Transform Pipeline Configuration + +As mentioned earlier, we have separated the normalization and padding configurations for images from the `train_pipeline` and `test_pipeline`, and have placed them in `model.data_preprocessor` instead. Hence, in the 3.x version of the pipeline, we no longer require the `Normalize` and `Pad` transforms. + +At the same time, we have also refactored the transform responsible for packing the data format, and have merged the `Collect` and `DefaultFormatBundle` transforms into `PackDetInputs`. This transform is responsible for packing the data from the data pipeline into the input format of the model. For more details on the input format conversion, please refer to the [data flow documentation](../advanced_guides/data_flow.md). + +Below, we will use the `train_pipeline` of Mask R-CNN as an example, to demonstrate how to migrate from the 2.x configuration to the 3.x configuration: + + + + + + + + + +
    2.x Config + +```python +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', img_scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', flip_ratio=0.5), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='DefaultFormatBundle'), + dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']), +] +``` + +
    3.x Config + +```python +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +``` + +
    + +For the `test_pipeline`, apart from removing the `Normalize` and `Pad` transforms, we have also separated the data augmentation for testing (TTA) from the normal testing process, and have removed `MultiScaleFlipAug`. For more information on how to use the new TTA version, please refer to the [TTA documentation](../advanced_guides/tta.md). + +Below, we will again use the `test_pipeline` of Mask R-CNN as an example, to demonstrate how to migrate from the 2.x configuration to the 3.x configuration: + + + + + + + + + +
    2.x Config + +```python +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(1333, 800), + flip=False, + transforms=[ + dict(type='Resize', keep_ratio=True), + dict(type='RandomFlip'), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='ImageToTensor', keys=['img']), + dict(type='Collect', keys=['img']), + ]) +] +``` + +
    3.x Config + +```python +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +``` + +
    + +In addition, we have also refactored some data augmentation transforms. The following table lists the mapping between the transforms used in the 2.x version and the 3.x version: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Name2.x Config3.x Config
    Resize + +```python +dict(type='Resize', + img_scale=(1333, 800), + keep_ratio=True) +``` + + + +```python +dict(type='Resize', + scale=(1333, 800), + keep_ratio=True) +``` + +
    RandomResize + +```python +dict( + type='Resize', + img_scale=[ + (1333, 640), (1333, 800)], + multiscale_mode='range', + keep_ratio=True) +``` + + + +```python +dict( + type='RandomResize', + scale=[ + (1333, 640), (1333, 800)], + keep_ratio=True) +``` + +
    RandomChoiceResize + +```python +dict( + type='Resize', + img_scale=[ + (1333, 640), (1333, 672), + (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + multiscale_mode='value', + keep_ratio=True) +``` + + + +```python +dict( + type='RandomChoiceResize', + scales=[ + (1333, 640), (1333, 672), + (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True) +``` + +
    RandomFlip + +```python +dict(type='RandomFlip', flip_ratio=0.5) +``` + + + +```python +dict(type='RandomFlip', prob=0.5) +``` + +
    + +### 评测器配置 + +In version 3.x, model accuracy evaluation is no longer tied to the dataset, but is instead accomplished through the use of an Evaluator. +The Evaluator configuration is divided into two parts: `val_evaluator` and `test_evaluator`. The `val_evaluator` is used for validation dataset evaluation, while the `test_evaluator` is used for testing dataset evaluation. +This corresponds to the `evaluation` field in version 2.x. + +The following table shows the corresponding relationship between Evaluators in version 2.x and 3.x. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Metric Name2.x Config3.x Config
    COCO + +```python +data = dict( + val=dict( + type='CocoDataset', + ann_file=data_root + 'annotations/instances_val2017.json')) +evaluation = dict(metric=['bbox', 'segm']) +``` + + + +```python +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False) +``` + +
    Pascal VOC + +```python +data = dict( + val=dict( + type=dataset_type, + ann_file=data_root + 'VOC2007/ImageSets/Main/test.txt')) +evaluation = dict(metric='mAP') +``` + + + +```python +val_evaluator = dict( + type='VOCMetric', + metric='mAP', + eval_mode='11points') +``` + +
    OpenImages + +```python +data = dict( + val=dict( + type='OpenImagesDataset', + ann_file=data_root + 'annotations/validation-annotations-bbox.csv', + img_prefix=data_root + 'OpenImages/validation/', + label_file=data_root + 'annotations/class-descriptions-boxable.csv', + hierarchy_file=data_root + + 'annotations/bbox_labels_600_hierarchy.json', + meta_file=data_root + 'annotations/validation-image-metas.pkl', + image_level_ann_file=data_root + + 'annotations/validation-annotations-human-imagelabels-boxable.csv')) +evaluation = dict(interval=1, metric='mAP') +``` + + + +```python +val_evaluator = dict( + type='OpenImagesMetric', + iou_thrs=0.5, + ioa_thrs=0.5, + use_group_of=True, + get_supercategory=True) +``` + +
    CityScapes + +```python +data = dict( + val=dict( + type='CityScapesDataset', + ann_file=data_root + + 'annotations/instancesonly_filtered_gtFine_val.json', + img_prefix=data_root + 'leftImg8bit/val/', + pipeline=test_pipeline)) +evaluation = dict(metric=['bbox', 'segm']) +``` + + + +```python +val_evaluator = [ + dict( + type='CocoMetric', + ann_file=data_root + + 'annotations/instancesonly_filtered_gtFine_val.json', + metric=['bbox', 'segm']), + dict( + type='CityScapesMetric', + ann_file=data_root + + 'annotations/instancesonly_filtered_gtFine_val.json', + seg_prefix=data_root + '/gtFine/val', + outfile_prefix='./work_dirs/cityscapes_metric/instance') +] +``` + +
    + +## Configuration for Training and Testing + + + + + + + + + +
    2.x Config + +```python +runner = dict( + type='EpochBasedRunner', # Type of training loop + max_epochs=12) # Maximum number of training epochs +evaluation = dict(interval=2) # Interval for evaluation, check the performance every 2 epochs +``` + +
    3.x Config + +```python +train_cfg = dict( + type='EpochBasedTrainLoop', # Type of training loop, please refer to https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py + max_epochs=12, # Maximum number of training epochs + val_interval=2) # Interval for validation, check the performance every 2 epochs +val_cfg = dict(type='ValLoop') # Type of validation loop +test_cfg = dict(type='TestLoop') # Type of testing loop +``` + +
    + +## Optimization Configuration + +The configuration for optimizer and gradient clipping is moved to the `optim_wrapper` field. +The following table shows the correspondences for optimizer configuration between 2.x version and 3.x version: + + + + + + + + + +
    2.x Config + +```python +optimizer = dict( + type='SGD', # Optimizer: Stochastic Gradient Descent + lr=0.02, # Base learning rate + momentum=0.9, # SGD with momentum + weight_decay=0.0001) # Weight decay +optimizer_config = dict(grad_clip=None) # Configuration for gradient clipping, set to None to disable +``` + +
    3.x Config + +```python +optim_wrapper = dict( # Configuration for the optimizer wrapper + type='OptimWrapper', # Type of optimizer wrapper, you can switch to AmpOptimWrapper to enable mixed precision training + optimizer=dict( # Optimizer configuration, supports various PyTorch optimizers, please refer to https://pytorch.org/docs/stable/optim.html#algorithms + type='SGD', # SGD + lr=0.02, # Base learning rate + momentum=0.9, # SGD with momentum + weight_decay=0.0001), # Weight decay + clip_grad=None, # Configuration for gradient clipping, set to None to disable. For usage, please see https://mmengine.readthedocs.io/en/latest/tutorials/optimizer.html + ) +``` + +
    + +The configuration for learning rate is also moved from the `lr_config` field to the `param_scheduler` field. The `param_scheduler` configuration is more similar to PyTorch's learning rate scheduler and more flexible. The following table shows the correspondences for learning rate configuration between 2.x version and 3.x version: + + + + + + + + + +
    2.x Config + +```python +lr_config = dict( + policy='step', # Use multi-step learning rate strategy during training + warmup='linear', # Use linear learning rate warmup + warmup_iters=500, # End warmup at iteration 500 + warmup_ratio=0.001, # Coefficient for learning rate warmup + step=[8, 11], # Learning rate decay at which epochs + gamma=0.1) # Learning rate decay coefficient + +``` + +
    3.x Config + +```python +param_scheduler = [ + dict( + type='LinearLR', # Use linear learning rate warmup + start_factor=0.001, # Coefficient for learning rate warmup + by_epoch=False, # Update the learning rate during warmup at each iteration + begin=0, # Starting from the first iteration + end=500), # End at the 500th iteration + dict( + type='MultiStepLR', # Use multi-step learning rate strategy during training + by_epoch=True, # Update the learning rate at each epoch + begin=0, # Starting from the first epoch + end=12, # Ending at the 12th epoch + milestones=[8, 11], # Learning rate decay at which epochs + gamma=0.1) # Learning rate decay coefficient +] + +``` + +
    + +For information on how to migrate other learning rate adjustment policies, please refer to the [learning rate migration document of MMEngine](https://mmengine.readthedocs.io/zh_CN/latest/migration/param_scheduler.html). + +## Migration of Other Configurations + +### Configuration for Saving Checkpoints + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Function2.x Config3.x Config
    Set Save Interval + +```python +checkpoint_config = dict( + interval=1) +``` + + + +```python +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + interval=1)) +``` + +
    Save Best Model + +```python +evaluation = dict( + save_best='auto') +``` + + + +```python +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + save_best='auto')) +``` + +
    Keep Latest Model + +```python +checkpoint_config = dict( + max_keep_ckpts=3) +``` + + + +```python +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + max_keep_ckpts=3)) +``` + +
    + +### Logging Configuration + +In MMDetection 3.x, the logging and visualization of the log are carried out respectively by the logger and visualizer in MMEngine. The following table shows the comparison between the configuration of printing logs and visualizing logs in MMDetection 2.x and 3.x. + + + + + + + + + + + + + + + + + + + + + + + + +
    Function2.x Config3.x Config
    Set Log Printing Interval + +```python +log_config = dict(interval=50) +``` + + + +```python +default_hooks = dict( + logger=dict(type='LoggerHook', interval=50)) +# Optional: set moving average window size +log_processor = dict( + type='LogProcessor', window_size=50) +``` + +
    Use TensorBoard or WandB to visualize logs + +```python +log_config = dict( + interval=50, + hooks=[ + dict(type='TextLoggerHook'), + dict(type='TensorboardLoggerHook'), + dict(type='MMDetWandbHook', + init_kwargs={ + 'project': 'mmdetection', + 'group': 'maskrcnn-r50-fpn-1x-coco' + }, + interval=50, + log_checkpoint=True, + log_checkpoint_metadata=True, + num_eval_images=100) + ]) +``` + + + +```python +vis_backends = [ + dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend'), + dict(type='WandbVisBackend', + init_kwargs={ + 'project': 'mmdetection', + 'group': 'maskrcnn-r50-fpn-1x-coco' + }) +] +visualizer = dict( + type='DetLocalVisualizer', + vis_backends=vis_backends, + name='visualizer') +``` + +
    + +For visualization-related tutorials, please refer to [Visualization Tutorial](../user_guides/visualization.md) of MMDetection. + +### Runtime Configuration + +The runtime configuration fields in version 3.x have been adjusted, and the specific correspondence is as follows: + + + + + + + + + + + + + + + + +
    2.x Config3.x Config
    + +```python +cudnn_benchmark = False +opencv_num_threads = 0 +mp_start_method = 'fork' +dist_params = dict(backend='nccl') +log_level = 'INFO' +load_from = None +resume_from = None + + +``` + + + +```python +env_cfg = dict( + cudnn_benchmark=False, + mp_cfg=dict(mp_start_method='fork', + opencv_num_threads=0), + dist_cfg=dict(backend='nccl')) +log_level = 'INFO' +load_from = None +resume = False +``` + +
    diff --git a/mmdetection/docs/en/migration/dataset_migration.md b/mmdetection/docs/en/migration/dataset_migration.md new file mode 100644 index 00000000..75d09329 --- /dev/null +++ b/mmdetection/docs/en/migration/dataset_migration.md @@ -0,0 +1 @@ +# Migrate dataset from MMDetection 2.x to 3.x diff --git a/mmdetection/docs/en/migration/migration.md b/mmdetection/docs/en/migration/migration.md new file mode 100644 index 00000000..ec6a2f89 --- /dev/null +++ b/mmdetection/docs/en/migration/migration.md @@ -0,0 +1,12 @@ +# Migrating from MMDetection 2.x to 3.x + +MMDetection 3.x is a significant update that includes many changes to API and configuration files. This document aims to help users migrate from MMDetection 2.x to 3.x. +We divided the migration guide into the following sections: + +- [Configuration file migration](./config_migration.md) +- [API and Registry migration](./api_and_registry_migration.md) +- [Dataset migration](./dataset_migration.md) +- [Model migration](./model_migration.md) +- [Frequently Asked Questions](./migration_faq.md) + +If you encounter any problems during the migration process, feel free to raise an issue. We also welcome contributions to this document. diff --git a/mmdetection/docs/en/migration/migration_faq.md b/mmdetection/docs/en/migration/migration_faq.md new file mode 100644 index 00000000..a6e3c356 --- /dev/null +++ b/mmdetection/docs/en/migration/migration_faq.md @@ -0,0 +1 @@ +# Migration FAQ diff --git a/mmdetection/docs/en/migration/model_migration.md b/mmdetection/docs/en/migration/model_migration.md new file mode 100644 index 00000000..04e28087 --- /dev/null +++ b/mmdetection/docs/en/migration/model_migration.md @@ -0,0 +1 @@ +# Migrate models from MMDetection 2.x to 3.x diff --git a/mmdetection/docs/en/model_zoo.md b/mmdetection/docs/en/model_zoo.md new file mode 100644 index 00000000..15dd7b2f --- /dev/null +++ b/mmdetection/docs/en/model_zoo.md @@ -0,0 +1,358 @@ +# Benchmark and Model Zoo + +## Mirror sites + +We only use aliyun to maintain the model zoo since MMDetection V2.0. The model zoo of V1.x has been deprecated. + +## Common settings + +- All models were trained on `coco_2017_train`, and tested on the `coco_2017_val`. +- We use distributed training. +- All pytorch-style pretrained backbones on ImageNet are from PyTorch model zoo, caffe-style pretrained backbones are converted from the newly released model from detectron2. +- For fair comparison with other codebases, we report the GPU memory as the maximum value of `torch.cuda.max_memory_allocated()` for all 8 GPUs. Note that this value is usually less than what `nvidia-smi` shows. +- We report the inference time as the total time of network forwarding and post-processing, excluding the data loading time. Results are obtained with the script [benchmark.py](https://github.com/open-mmlab/mmdetection/blob/main/tools/analysis_tools/benchmark.py) which computes the average time on 2000 images. + +## ImageNet Pretrained Models + +It is common to initialize from backbone models pre-trained on ImageNet classification task. All pre-trained model links can be found at [open_mmlab](https://github.com/open-mmlab/mmcv/blob/master/mmcv/model_zoo/open_mmlab.json). According to `img_norm_cfg` and source of weight, we can divide all the ImageNet pre-trained model weights into some cases: + +- TorchVision: Corresponding to torchvision weight, including ResNet50, ResNet101. The `img_norm_cfg` is `dict(mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)`. +- Pycls: Corresponding to [pycls](https://github.com/facebookresearch/pycls) weight, including RegNetX. The `img_norm_cfg` is `dict( mean=[103.530, 116.280, 123.675], std=[57.375, 57.12, 58.395], to_rgb=False)`. +- MSRA styles: Corresponding to [MSRA](https://github.com/KaimingHe/deep-residual-networks) weights, including ResNet50_Caffe and ResNet101_Caffe. The `img_norm_cfg` is `dict( mean=[103.530, 116.280, 123.675], std=[1.0, 1.0, 1.0], to_rgb=False)`. +- Caffe2 styles: Currently only contains ResNext101_32x8d. The `img_norm_cfg` is `dict(mean=[103.530, 116.280, 123.675], std=[57.375, 57.120, 58.395], to_rgb=False)`. +- Other styles: E.g SSD which corresponds to `img_norm_cfg` is `dict(mean=[123.675, 116.28, 103.53], std=[1, 1, 1], to_rgb=True)` and YOLOv3 which corresponds to `img_norm_cfg` is `dict(mean=[0, 0, 0], std=[255., 255., 255.], to_rgb=True)`. + +The detailed table of the commonly used backbone models in MMDetection is listed below : + +| model | source | link | description | +| ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ResNet50 | TorchVision | [torchvision's ResNet-50](https://download.pytorch.org/models/resnet50-19c8e357.pth) | From [torchvision's ResNet-50](https://download.pytorch.org/models/resnet50-19c8e357.pth). | +| ResNet101 | TorchVision | [torchvision's ResNet-101](https://download.pytorch.org/models/resnet101-5d3b4d8f.pth) | From [torchvision's ResNet-101](https://download.pytorch.org/models/resnet101-5d3b4d8f.pth). | +| RegNetX | Pycls | [RegNetX_3.2gf](https://download.openmmlab.com/pretrain/third_party/regnetx_3.2gf-c2599b0f.pth), [RegNetX_800mf](https://download.openmmlab.com/pretrain/third_party/regnetx_800mf-1f4be4c7.pth). etc. | From [pycls](https://github.com/facebookresearch/pycls). | +| ResNet50_Caffe | MSRA | [MSRA's ResNet-50](https://download.openmmlab.com/pretrain/third_party/resnet50_caffe-788b5fa3.pth) | Converted copy of [Detectron2's R-50.pkl](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/MSRA/R-50.pkl) model. The original weight comes from [MSRA's original ResNet-50](https://github.com/KaimingHe/deep-residual-networks). | +| ResNet101_Caffe | MSRA | [MSRA's ResNet-101](https://download.openmmlab.com/pretrain/third_party/resnet101_caffe-3ad79236.pth) | Converted copy of [Detectron2's R-101.pkl](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/MSRA/R-101.pkl) model. The original weight comes from [MSRA's original ResNet-101](https://github.com/KaimingHe/deep-residual-networks). | +| ResNext101_32x8d | Caffe2 | [Caffe2 ResNext101_32x8d](https://download.openmmlab.com/pretrain/third_party/resnext101_32x8d-1516f1aa.pth) | Converted copy of [Detectron2's X-101-32x8d.pkl](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/FAIR/X-101-32x8d.pkl) model. The ResNeXt-101-32x8d model trained with Caffe2 at FB. | + +## Baselines + +### RPN + +Please refer to [RPN](https://github.com/open-mmlab/mmdetection/blob/main/configs/rpn) for details. + +### Faster R-CNN + +Please refer to [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/faster_rcnn) for details. + +### Mask R-CNN + +Please refer to [Mask R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/mask_rcnn) for details. + +### Fast R-CNN (with pre-computed proposals) + +Please refer to [Fast R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/fast_rcnn) for details. + +### RetinaNet + +Please refer to [RetinaNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/retinanet) for details. + +### Cascade R-CNN and Cascade Mask R-CNN + +Please refer to [Cascade R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/cascade_rcnn) for details. + +### Hybrid Task Cascade (HTC) + +Please refer to [HTC](https://github.com/open-mmlab/mmdetection/blob/main/configs/htc) for details. + +### SSD + +Please refer to [SSD](https://github.com/open-mmlab/mmdetection/blob/main/configs/ssd) for details. + +### Group Normalization (GN) + +Please refer to [Group Normalization](https://github.com/open-mmlab/mmdetection/blob/main/configs/gn) for details. + +### Weight Standardization + +Please refer to [Weight Standardization](https://github.com/open-mmlab/mmdetection/blob/main/configs/gn+ws) for details. + +### Deformable Convolution v2 + +Please refer to [Deformable Convolutional Networks](https://github.com/open-mmlab/mmdetection/blob/main/configs/dcn) for details. + +### CARAFE: Content-Aware ReAssembly of FEatures + +Please refer to [CARAFE](https://github.com/open-mmlab/mmdetection/blob/main/configs/carafe) for details. + +### Instaboost + +Please refer to [Instaboost](https://github.com/open-mmlab/mmdetection/blob/main/configs/instaboost) for details. + +### Libra R-CNN + +Please refer to [Libra R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/libra_rcnn) for details. + +### Guided Anchoring + +Please refer to [Guided Anchoring](https://github.com/open-mmlab/mmdetection/blob/main/configs/guided_anchoring) for details. + +### FCOS + +Please refer to [FCOS](https://github.com/open-mmlab/mmdetection/blob/main/configs/fcos) for details. + +### FoveaBox + +Please refer to [FoveaBox](https://github.com/open-mmlab/mmdetection/blob/main/configs/foveabox) for details. + +### RepPoints + +Please refer to [RepPoints](https://github.com/open-mmlab/mmdetection/blob/main/configs/reppoints) for details. + +### FreeAnchor + +Please refer to [FreeAnchor](https://github.com/open-mmlab/mmdetection/blob/main/configs/free_anchor) for details. + +### Grid R-CNN (plus) + +Please refer to [Grid R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/grid_rcnn) for details. + +### GHM + +Please refer to [GHM](https://github.com/open-mmlab/mmdetection/blob/main/configs/ghm) for details. + +### GCNet + +Please refer to [GCNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/gcnet) for details. + +### HRNet + +Please refer to [HRNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/hrnet) for details. + +### Mask Scoring R-CNN + +Please refer to [Mask Scoring R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/ms_rcnn) for details. + +### Train from Scratch + +Please refer to [Rethinking ImageNet Pre-training](https://github.com/open-mmlab/mmdetection/blob/main/configs/scratch) for details. + +### NAS-FPN + +Please refer to [NAS-FPN](https://github.com/open-mmlab/mmdetection/blob/main/configs/nas_fpn) for details. + +### ATSS + +Please refer to [ATSS](https://github.com/open-mmlab/mmdetection/blob/main/configs/atss) for details. + +### FSAF + +Please refer to [FSAF](https://github.com/open-mmlab/mmdetection/blob/main/configs/fsaf) for details. + +### RegNetX + +Please refer to [RegNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/regnet) for details. + +### Res2Net + +Please refer to [Res2Net](https://github.com/open-mmlab/mmdetection/blob/main/configs/res2net) for details. + +### GRoIE + +Please refer to [GRoIE](https://github.com/open-mmlab/mmdetection/blob/main/configs/groie) for details. + +### Dynamic R-CNN + +Please refer to [Dynamic R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/dynamic_rcnn) for details. + +### PointRend + +Please refer to [PointRend](https://github.com/open-mmlab/mmdetection/blob/main/configs/point_rend) for details. + +### DetectoRS + +Please refer to [DetectoRS](https://github.com/open-mmlab/mmdetection/blob/main/configs/detectors) for details. + +### Generalized Focal Loss + +Please refer to [Generalized Focal Loss](https://github.com/open-mmlab/mmdetection/blob/main/configs/gfl) for details. + +### CornerNet + +Please refer to [CornerNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/cornernet) for details. + +### YOLOv3 + +Please refer to [YOLOv3](https://github.com/open-mmlab/mmdetection/blob/main/configs/yolo) for details. + +### PAA + +Please refer to [PAA](https://github.com/open-mmlab/mmdetection/blob/main/configs/paa) for details. + +### SABL + +Please refer to [SABL](https://github.com/open-mmlab/mmdetection/blob/main/configs/sabl) for details. + +### CentripetalNet + +Please refer to [CentripetalNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/centripetalnet) for details. + +### ResNeSt + +Please refer to [ResNeSt](https://github.com/open-mmlab/mmdetection/blob/main/configs/resnest) for details. + +### DETR + +Please refer to [DETR](https://github.com/open-mmlab/mmdetection/blob/main/configs/detr) for details. + +### Deformable DETR + +Please refer to [Deformable DETR](https://github.com/open-mmlab/mmdetection/blob/main/configs/deformable_detr) for details. + +### AutoAssign + +Please refer to [AutoAssign](https://github.com/open-mmlab/mmdetection/blob/main/configs/autoassign) for details. + +### YOLOF + +Please refer to [YOLOF](https://github.com/open-mmlab/mmdetection/blob/main/configs/yolof) for details. + +### Seesaw Loss + +Please refer to [Seesaw Loss](https://github.com/open-mmlab/mmdetection/blob/main/configs/seesaw_loss) for details. + +### CenterNet + +Please refer to [CenterNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/centernet) for details. + +### YOLOX + +Please refer to [YOLOX](https://github.com/open-mmlab/mmdetection/blob/main/configs/yolox) for details. + +### PVT + +Please refer to [PVT](https://github.com/open-mmlab/mmdetection/blob/main/configs/pvt) for details. + +### SOLO + +Please refer to [SOLO](https://github.com/open-mmlab/mmdetection/blob/main/configs/solo) for details. + +### QueryInst + +Please refer to [QueryInst](https://github.com/open-mmlab/mmdetection/blob/main/configs/queryinst) for details. + +### PanopticFPN + +Please refer to [PanopticFPN](https://github.com/open-mmlab/mmdetection/blob/main/configs/panoptic_fpn) for details. + +### MaskFormer + +Please refer to [MaskFormer](https://github.com/open-mmlab/mmdetection/blob/main/configs/maskformer) for details. + +### DyHead + +Please refer to [DyHead](https://github.com/open-mmlab/mmdetection/blob/main/configs/dyhead) for details. + +### Mask2Former + +Please refer to [Mask2Former](https://github.com/open-mmlab/mmdetection/blob/main/configs/mask2former) for details. + +### Efficientnet + +Please refer to [Efficientnet](https://github.com/open-mmlab/mmdetection/blob/main/configs/efficientnet) for details. + +### Other datasets + +We also benchmark some methods on [PASCAL VOC](https://github.com/open-mmlab/mmdetection/blob/main/configs/pascal_voc), [Cityscapes](https://github.com/open-mmlab/mmdetection/blob/main/configs/cityscapes), [OpenImages](https://github.com/open-mmlab/mmdetection/blob/main/configs/openimages) and [WIDER FACE](https://github.com/open-mmlab/mmdetection/blob/main/configs/wider_face). + +### Pre-trained Models + +We also train [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/faster_rcnn) and [Mask R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/mask_rcnn) using ResNet-50 and [RegNetX-3.2G](https://github.com/open-mmlab/mmdetection/blob/main/configs/regnet) with multi-scale training and longer schedules. These models serve as strong pre-trained models for downstream tasks for convenience. + +## Speed benchmark + +### Training Speed benchmark + +We provide [analyze_logs.py](https://github.com/open-mmlab/mmdetection/blob/main/tools/analysis_tools/analyze_logs.py) to get average time of iteration in training. You can find examples in [Log Analysis](https://mmdetection.readthedocs.io/en/latest/useful_tools.html#log-analysis). + +We compare the training speed of Mask R-CNN with some other popular frameworks (The data is copied from [detectron2](https://github.com/facebookresearch/detectron2/blob/main/docs/notes/benchmarks.md/)). +For mmdetection, we benchmark with [mask-rcnn_r50-caffe_fpn_poly-1x_coco_v1.py](https://github.com/open-mmlab/mmdetection/blob/main/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_poly-1x_coco_v1.py), which should have the same setting with [mask_rcnn_R_50_FPN_noaug_1x.yaml](https://github.com/facebookresearch/detectron2/blob/main/configs/Detectron1-Comparisons/mask_rcnn_R_50_FPN_noaug_1x.yaml) of detectron2. +We also provide the [checkpoint](https://download.openmmlab.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug_compare_20200518-10127928.pth) and [training log](https://download.openmmlab.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug_20200518_105755.log.json) for reference. The throughput is computed as the average throughput in iterations 100-500 to skip GPU warmup time. + +| Implementation | Throughput (img/s) | +| -------------------------------------------------------------------------------------- | ------------------ | +| [Detectron2](https://github.com/facebookresearch/detectron2) | 62 | +| [MMDetection](https://github.com/open-mmlab/mmdetection) | 61 | +| [maskrcnn-benchmark](https://github.com/facebookresearch/maskrcnn-benchmark/) | 53 | +| [tensorpack](https://github.com/tensorpack/tensorpack/tree/master/examples/FasterRCNN) | 50 | +| [simpledet](https://github.com/TuSimple/simpledet/) | 39 | +| [Detectron](https://github.com/facebookresearch/Detectron) | 19 | +| [matterport/Mask_RCNN](https://github.com/matterport/Mask_RCNN/) | 14 | + +### Inference Speed Benchmark + +We provide [benchmark.py](https://github.com/open-mmlab/mmdetection/blob/main/tools/analysis_tools/benchmark.py) to benchmark the inference latency. +The script benchmarkes the model with 2000 images and calculates the average time ignoring first 5 times. You can change the output log interval (defaults: 50) by setting `LOG-INTERVAL`. + +```shell +python tools/benchmark.py ${CONFIG} ${CHECKPOINT} [--log-interval $[LOG-INTERVAL]] [--fuse-conv-bn] +``` + +The latency of all models in our model zoo is benchmarked without setting `fuse-conv-bn`, you can get a lower latency by setting it. + +## Comparison with Detectron2 + +We compare mmdetection with [Detectron2](https://github.com/facebookresearch/detectron2.git) in terms of speed and performance. +We use the commit id [185c27e](https://github.com/facebookresearch/detectron2/tree/185c27e4b4d2d4c68b5627b3765420c6d7f5a659)(30/4/2020) of detectron. +For fair comparison, we install and run both frameworks on the same machine. + +### Hardware + +- 8 NVIDIA Tesla V100 (32G) GPUs +- Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz + +### Software environment + +- Python 3.7 +- PyTorch 1.4 +- CUDA 10.1 +- CUDNN 7.6.03 +- NCCL 2.4.08 + +### Performance + +| Type | Lr schd | Detectron2 | mmdetection | Download | +| ------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco.py) | 1x | [37.9](https://github.com/facebookresearch/detectron2/blob/main/configs/COCO-Detection/faster_rcnn_R_50_FPN_1x.yaml) | 38.0 | [model](https://download.openmmlab.com/mmdetection/v2.0/benchmark/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco-5324cff8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/benchmark/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco_20200429_234554.log.json) | +| [Mask R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py) | 1x | [38.6 & 35.2](https://github.com/facebookresearch/detectron2/blob/main/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml) | 38.8 & 35.4 | [model](https://download.openmmlab.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco-dbecf295.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco_20200430_054239.log.json) | +| [Retinanet](https://github.com/open-mmlab/mmdetection/blob/main/configs/retinanet/retinanet_r50-caffe_fpn_ms-1x_coco.py) | 1x | [36.5](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-Detection/retinanet_R_50_FPN_1x.yaml) | 37.0 | [model](https://download.openmmlab.com/mmdetection/v2.0/benchmark/retinanet_r50_caffe_fpn_mstrain_1x_coco/retinanet_r50_caffe_fpn_mstrain_1x_coco-586977a0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/benchmark/retinanet_r50_caffe_fpn_mstrain_1x_coco/retinanet_r50_caffe_fpn_mstrain_1x_coco_20200430_014748.log.json) | + +### Training Speed + +The training speed is measure with s/iter. The lower, the better. + +| Type | Detectron2 | mmdetection | +| ------------ | ---------- | ----------- | +| Faster R-CNN | 0.210 | 0.216 | +| Mask R-CNN | 0.261 | 0.265 | +| Retinanet | 0.200 | 0.205 | + +### Inference Speed + +The inference speed is measured with fps (img/s) on a single GPU, the higher, the better. +To be consistent with Detectron2, we report the pure inference speed (without the time of data loading). +For Mask R-CNN, we exclude the time of RLE encoding in post-processing. +We also include the officially reported speed in the parentheses, which is slightly higher +than the results tested on our server due to differences of hardwares. + +| Type | Detectron2 | mmdetection | +| ------------ | ----------- | ----------- | +| Faster R-CNN | 25.6 (26.3) | 22.2 | +| Mask R-CNN | 22.5 (23.3) | 19.6 | +| Retinanet | 17.8 (18.2) | 20.6 | + +### Training memory + +| Type | Detectron2 | mmdetection | +| ------------ | ---------- | ----------- | +| Faster R-CNN | 3.0 | 3.8 | +| Mask R-CNN | 3.4 | 3.9 | +| Retinanet | 3.9 | 3.4 | diff --git a/mmdetection/docs/en/notes/changelog.md b/mmdetection/docs/en/notes/changelog.md new file mode 100644 index 00000000..00ed8f1c --- /dev/null +++ b/mmdetection/docs/en/notes/changelog.md @@ -0,0 +1,631 @@ +# Changelog of v3.x + +## v3.3.0 (05/01/2024) + +### Highlights + +Grounding-DINO is a state-of-the-art open-set detection model that tackles multiple vision tasks including Open-Vocabulary Detection (OVD), Phrase Grounding (PG), and Referring Expression Comprehension (REC). Its effectiveness has led to its widespread adoption as a mainstream architecture for various downstream applications. However, despite its significance, the original Grounding-DINO model lacks comprehensive public technical details due to the unavailability of its training code. To bridge this gap, we present MM-Grounding-DINO, an open-source, comprehensive, and user-friendly baseline, which is built with the MMDetection toolbox. It adopts abundant vision datasets for pre-training and various detection and grounding datasets for fine-tuning. We give a comprehensive analysis of each reported result and detailed settings for reproduction. The extensive experiments on the benchmarks mentioned demonstrate that our MM-Grounding-DINO-Tiny outperforms the Grounding-DINO-Tiny baseline. We release all our models to the research community. + +### New Features + +- Add RTMDet Swin / ConvNeXt backbone and results (#11259) +- Add `odinw` configs and evaluation results of `GLIP` (#11175) +- Add optional score threshold option to `coco_error_analysis.py` (#11117) +- Add new configs for `panoptic_fpn` (#11109) +- Replace partially weighted download links with OpenXLab for the `Faster-RCNN` (#11173) + +### Bug Fixes + +- Fix `Grounding DINO` nan when class tokens exceeds 256 (#11066) +- Fix the `CO-DETR` config files error (#11325) +- Fix `CO-DETR` load_from url in config (#11220) +- Fixed mask shape after Albu postprocess (#11280) +- Fix bug in `convert_coco_format` and `youtubevis2coco` (#11251, #11086) + +### Contributors + +A total of 15 developers contributed to this release. + +Thank @adnan-mujagic, @Cycyes, @ilcopione, @returnL, @honeybadger78, @okotaku, @xushilin1, @keyhsw, @guyleaf, @Crescent-Saturn, @LRJKD, @aaronzs, @Divadi, @AwePhD, @hhaAndroid + +## v3.2.0 (12/10/2023) + +### Highlights + +**(1) Detection Transformer SOTA Model Collection** + +- Supported four updated and stronger SOTA Transformer models: DDQ, CO-DETR, AlignDETR, and H-DINO. +- Based on CO-DETR, MMDet released a model with a COCO performance of 64.1 mAP. +- Algorithms such as DINO support AMP/Checkpoint/FrozenBN, which can effectively reduce memory usage. + +**(2) Comprehensive Performance Comparison between CNN and Transformer** + +RF100 consists of a dataset collection of 100 real-world datasets, including 7 domains. It can be used to assess the performance differences of Transformer models like DINO and CNN-based algorithms under different scenarios and data volumes. Users can utilize this benchmark to quickly evaluate the robustness of their algorithms in various scenarios. + +**(3) Support for GLIP and Grounding DINO fine-tuning, the only algorithm library that supports Grounding DINO fine-tuning** + +The Grounding DINO algorithm in MMDet is the only library that supports fine-tuning. Its performance is one point higher than the official version, and of course, GLIP also outperforms the official version. +We also provide a detailed process for training and evaluating Grounding DINO on custom datasets. Everyone is welcome to give it a try. + +**(4) Support for the open-vocabulary detection algorithm Detic and multi-dataset joint training.** + +**(5) Training detection models using FSDP and DeepSpeed.** + +**(6) Support for the V3Det dataset, a large-scale detection dataset with over 13,000 categories.** + +### New Features + +- Support CO-DETR/DDQ/AlignDETR/H-DINO +- Support GLIP and Grounding DINO fine-tuning +- Support Detic and Multi-Datasets training (#10926) +- Support V3Det and benchmark (#10938) +- Support Roboflow 100 Benchmark (#10915) +- Add custom dataset of grounding dino (#11012) +- Release RTMDet-X p6 (#10993) +- Support AMP of DINO (#10827) +- Support FrozenBN (#10845) +- Add new configuration files for `QDTrack/DETR/RTMDet/MaskRCNN/DINO/DeformableDETR/MaskFormer` algorithm +- Add a new script to support the WBF (#10808) +- Add `large_image_demo` (#10719) +- Support download dataset from OpenXLab (#10799) +- Update to support torch2onnx for DETR series models (#10910) +- Translation into Chinese of an English document (#10744, #10756, #10805, #10848) + +### Bug Fixes + +- Fix name error in DETR metafile.yml (#10595) +- Fix device of the tensors in `set_nms` (#10574) +- Remove some unicode chars from `en/` docs (#10648) +- Fix download dataset with mim script. (#10727) +- Fix export to torchserve (#10694) +- Fix typo in `mask-rcnn_r50_fpn_1x-wandb_coco` (#10757) +- Fix `eval_recalls` error in `voc_metric` (#10770) +- Fix torch version comparison (#10934) +- Fix incorrect behavior to access train pipeline from ConcatDataset in `analyze_results.py` (#11004) + +### Improvements + +- Update `useful_tools.md` (#10587) +- Update Instance segmentation Tutorial (#10711) +- Update `train.py` to compat with new config (#11025) +- Support `torch2onnx` for maskformer series (#10782) + +### Contributors + +A total of 36 developers contributed to this release. + +Thank @YQisme, @nskostas, @max-unfinity, @evdcush, @Xiangxu-0103, @ZhaoCake, @RangeKing, @captainIT, @ODAncona, @aaronzs, @zeyuanyin, @gotjd709, @Musiyuan, @YanxingLiu, @RunningLeon, @ytzfhqs, @zhangzhidaSunny, @yeungkong, @crazysteeaam, @timerring, @okotaku, @apatsekin, @Morty-Xu, @Markson-Young, @ZhaoQiiii, @Kuro96, @PhoenixZ810, @yhcao6, @myownskyW7, @jiongjiongli, @Johnson-Wang, @ryylcc, @guyleaf, @agpeshal, @SimonGuoNjust, @hhaAndroid + +## v3.1.0 (30/6/2023) + +### Highlights + +- Supports tracking algorithms including multi-object tracking (MOT) algorithms SORT, DeepSORT, StrongSORT, OCSORT, ByteTrack, QDTrack, and video instance segmentation (VIS) algorithm MaskTrackRCNN, Mask2Former-VIS. +- Support [ViTDet](../../../projects/ViTDet) +- Supports inference and evaluation of multimodal algorithms [GLIP](../../../configs/glip) and [XDecoder](../../../projects/XDecoder), and also supports datasets such as COCO semantic segmentation, COCO Caption, ADE20k general segmentation, and RefCOCO. GLIP fine-tuning will be supported in the future. +- Provides a [gradio demo](https://github.com/open-mmlab/mmdetection/blob/dev-3.x/projects/gradio_demo/README.md) for image type tasks of MMDetection, making it easy for users to experience. + +### New Features + +- Support DSDL Dataset (#9801) +- Support iSAID dataset (#10028) +- Support VISION dataset (#10530) +- Release SoftTeacher checkpoints (#10119) +- Release `centernet-update_r50-caffe_fpn_ms-1x_coco` checkpoints (#10327) +- Support SIoULoss (#10290) +- Support Eqlv2 loss (#10120) +- Support CopyPaste when mask is not available (#10509) +- Support MIM to download ODL dataset (#10460) +- Support new config (#10566) + +### Bug Fixes + +- Fix benchmark scripts error in windows (#10128) +- Fix error of `YOLOXModeSwitchHook` does not switch the mode when resumed from the checkpoint after switched (#10116) +- Fix pred and weight dims unmatch in SmoothL1Loss (#10423) + +### Improvements + +- Update MMDet_Tutorial.ipynb (#10081) +- Support to hide inference progress (#10519) +- Replace mmcls with mmpretrain (#10545) + +### Contributors + +A total of 29 developers contributed to this release. + +Thanks @lovelykite, @minato-ellie, @freepoet, @wufan-tb, @yalibian, @keyakiluo, @gihanjayatilaka, @i-aki-y, @xin-li-67, @RangeKing, @JingweiZhang12, @MambaWong, @lucianovk, @tall-josh, @xiuqhou, @jamiechoi1995, @YQisme, @yechenzhi, @bjzhb666, @xiexinch, @jamiechoi1995, @yarkable, @Renzhihan, @nijkah, @amaizr, @Lum1104, @zwhus, @Czm369, @hhaAndroid + +## v3.0.0 (6/4/2023) + +### Highlights + +- Support Semi-automatic annotation Base [Label-Studio](../../../projects/LabelStudio) (#10039) +- Support [EfficientDet](../../../projects/EfficientDet) in projects (#9810) + +### New Features + +- File I/O migration and reconstruction (#9709) +- Release DINO Swin-L 36e model (#9927) + +### Bug Fixes + +- Fix benchmark script (#9865) +- Fix the crop method of PolygonMasks (#9858) +- Fix Albu augmentation with the mask shape (#9918) +- Fix `RTMDetIns` prior generator device error (#9964) +- Fix `img_shape` in data pipeline (#9966) +- Fix cityscapes import error (#9984) +- Fix `solov2_r50_fpn_ms-3x_coco.py` config error (#10030) +- Fix Conditional DETR AP and Log (#9889) +- Fix accepting an unexpected argument local-rank in PyTorch 2.0 (#10050) +- Fix `common/ms_3x_coco-instance.py` config error (#10056) +- Fix compute flops error (#10051) +- Delete `data_root` in `CocoOccludedSeparatedMetric` to fix bug (#9969) +- Unifying metafile.yml (#9849) + +### Improvements + +- Added BoxInst r101 config (#9967) +- Added config migration guide (#9960) +- Added more social networking links (#10021) +- Added RTMDet config introduce (#10042) +- Added visualization docs (#9938, #10058) +- Refined data_prepare docs (#9935) +- Added support for setting the cache_size_limit parameter of dynamo in PyTorch 2.0 (#10054) +- Updated coco_metric.py (#10033) +- Update type hint (#10040) + +### Contributors + +A total of 19 developers contributed to this release. + +Thanks @IRONICBo, @vansin, @RangeKing, @Ghlerrix, @okotaku, @JosonChan1998, @zgzhengSE, @bobo0810, @yechenzh, @Zheng-LinXiao, @LYMDLUT, @yarkable, @xiejiajiannb, @chhluo, @BIGWangYuDong, @RangiLy, @zwhus, @hhaAndroid, @ZwwWayne + +## v3.0.0rc6 (24/2/2023) + +### Highlights + +- Support [Boxinst](../../../configs/boxinst), [Objects365 Dataset](../../../configs/objects365), and [Separated and Occluded COCO metric](../user_guides/useful_tools.md#COCO-Separated-&-Occluded-Mask-Metric) +- Support [ConvNeXt-V2](../../../projects/ConvNeXt-V2), [DiffusionDet](../../../projects/DiffusionDet), and inference of [EfficientDet](../../../projects/EfficientDet) and [Detic](../../../projects/Detic) in `Projects` +- Refactor [DETR](../../../configs/detr) series and support [Conditional-DETR](../../../configs/conditional_detr), [DAB-DETR](../../../configs/dab_detr), and [DINO](../../../configs/detr) +- Support `DetInferencer` for inference, Test Time Augmentation, and automatically importing modules from registry +- Support RTMDet-Ins ONNXRuntime and TensorRT [deployment](../../../configs/rtmdet/README.md#deployment-tutorial) +- Support [calculating FLOPs of detectors](../user_guides/useful_tools.md#Model-Complexity) + +### New Features + +- Support [Boxinst](https://arxiv.org/abs/2012.02310) (#9525) +- Support [Objects365 Dataset](https://openaccess.thecvf.com/content_ICCV_2019/papers/Shao_Objects365_A_Large-Scale_High-Quality_Dataset_for_Object_Detection_ICCV_2019_paper.pdf) (#9600) +- Support [ConvNeXt-V2](http://arxiv.org/abs/2301.00808) in `Projects` (#9619) +- Support [DiffusionDet](https://arxiv.org/abs/2211.09788) in `Projects` (#9639, #9768) +- Support [Detic](http://arxiv.org/abs/2201.02605) inference in `Projects` (#9645) +- Support [EfficientDet](https://arxiv.org/abs/1911.09070) inference in `Projects` (#9645) +- Support [Separated and Occluded COCO metric](https://arxiv.org/abs/2210.10046) (#9710) +- Support auto import modules from registry (#9143) +- Refactor DETR series and support Conditional-DETR, DAB-DETR and DINO (#9646) +- Support `DetInferencer` for inference (#9561) +- Support Test Time Augmentation (#9452) +- Support calculating FLOPs of detectors (#9777) + +### Bug Fixes + +- Fix deprecating old type alias due to new version of numpy (#9625, #9537) +- Fix VOC metrics (#9784) +- Fix the wrong link of RTMDet-x log (#9549) +- Fix RTMDet link in README (#9575) +- Fix MMDet get flops error (#9589) +- Fix `use_depthwise` in RTMDet (#9624) +- Fix `albumentations` augmentation post process with masks (#9551) +- Fix DETR series Unit Test (#9647) +- Fix `LoadPanopticAnnotations` bug (#9703) +- Fix `isort` CI (#9680) +- Fix amp pooling overflow (#9670) +- Fix docstring about noise in DINO (#9747) +- Fix potential bug in `MultiImageMixDataset` (#9764) + +### Improvements + +- Replace NumPy transpose with PyTorch permute to speed-up (#9762) +- Deprecate `sklearn` (#9725) +- Add RTMDet-Ins deployment guide (#9823) +- Update RTMDet config and README (#9603) +- Replace the models used in the tutorial document with RTMDet (#9843) +- Adjust the minimum supported python version to 3.7 (#9602) +- Support modifying palette through configuration (#9445) +- Update README document in `Project` (#9599) +- Replace `github` with `gitee` in `.pre-commit-config-zh-cn.yaml` file (#9586) +- Use official `isort` in `.pre-commit-config.yaml` file (#9701) +- Change MMCV minimum version to `2.0.0rc4` for `dev-3.x` (#9695) +- Add Chinese version of single_stage_as_rpn.md and test_results_submission.md (#9434) +- Add OpenDataLab download link (#9605, #9738) +- Add type hints of several layers (#9346) +- Add typehint for `DarknetBottleneck` (#9591) +- Add dockerfile (#9659) +- Add twitter, discord, medium, and youtube link (#9775) +- Prepare for merging refactor-detr (#9656) +- Add metafile to ConditionalDETR, DABDETR and DINO (#9715) +- Support to modify `non_blocking` parameters (#9723) +- Comment repeater visualizer register (#9740) +- Update user guide: `finetune.md` and `inference.md` (#9578) + +### New Contributors + +- @NoFish-528 made their first contribution in +- @137208 made their first contribution in +- @lyviva made their first contribution in +- @zwhus made their first contribution in +- @zylo117 made their first contribution in +- @chg0901 made their first contribution in +- @DanShouzhu made their first contribution in https://github.com/open-mmlab/mmdetection/pull/9578 + +### Contributors + +A total of 27 developers contributed to this release. + +Thanks @JosonChan1998, @RangeKing, @NoFish-528, @likyoo, @Xiangxu-0103, @137208, @PeterH0323, @tianleiSHI, @wufan-tb, @lyviva, @zwhus, @jshilong, @Li-Qingyun, @sanbuphy, @zylo117, @triple-Mu, @KeiChiTse, @LYMDLUT, @nijkah, @chg0901, @DanShouzhu, @zytx121, @vansin, @BIGWangYuDong, @hhaAndroid, @RangiLyu, @ZwwWayne + +## v3.0.0rc5 (26/12/2022) + +### Highlights + +- Support [RTMDet](https://arxiv.org/abs/2212.07784) instance segmentation models. The technical report of RTMDet is on [arxiv](https://arxiv.org/abs/2212.07784) +- Support SSHContextModule in paper [SSH: Single Stage Headless Face Detector](https://arxiv.org/abs/1708.03979). + +### New Features + +- Support [RTMDet](https://arxiv.org/abs/2212.07784) instance segmentation models and improve RTMDet test config (#9494) +- Support SSHContextModule in paper [SSH: Single Stage Headless Face Detector](https://arxiv.org/abs/1708.03979) (#8953) +- Release [CondInst](https://arxiv.org/abs/2003.05664) pre-trained model (#9406) + +### Bug Fixes + +- Fix CondInst predict error when `batch_size` is greater than 1 in inference (#9400) +- Fix the bug of visualization when the dtype of the pipeline output image is not uint8 in browse dataset (#9401) +- Fix `analyze_logs.py` to plot mAP and calculate train time correctly (#9409) +- Fix backward inplace error with `PAFPN` (#9450) +- Fix config import links in model converters (#9441) +- Fix `DeformableDETRHead` object has no attribute `loss_single` (#9477) +- Fix the logic of pseudo bboxes predicted by teacher model in SemiBaseDetector (#9414) +- Fix demo API in instance segmentation tutorial (#9226) +- Fix `analyze_results` (#9380) +- Fix the error that Readthedocs API cannot be displayed (#9510) +- Fix the error when there are no prediction results and support visualize the groundtruth of TTA (#9840) + +### Improvements + +- Remove legacy `builder.py` (#9479) +- Make sure the pipeline argument shape is in `(width, height)` order (#9324) +- Add `.pre-commit-config-zh-cn.yaml` file (#9388) +- Refactor dataset metainfo to lowercase (#9469) +- Add PyTorch 1.13 checking in CI (#9478) +- Adjust `FocalLoss` and `QualityFocalLoss` to allow different kinds of targets (#9481) +- Refactor `setup.cfg` (#9370) +- Clip saturation value to valid range `[0, 1]` (#9391) +- Only keep meta and state_dict when publishing model (#9356) +- Add segm evaluator in ms-poly_3x_coco_instance config (#9524) +- Update deployment guide (#9527) +- Update zh_cn `faq.md` (#9396) +- Update `get_started` (#9480) +- Update the zh_cn user_guides of `useful_tools.md` and `useful_hooks.md` (#9453) +- Add type hints for `bfp` and `channel_mapper` (#9410) +- Add type hints of several losses (#9397) +- Add type hints and update docstring for task modules (#9468) + +### New Contributors + +- @lihua199710 made their first contribution in +- @twmht made their first contribution in +- @tianleiSHI made their first contribution in +- @kitecats made their first contribution in +- @QJC123654 made their first contribution in + +### Contributors + +A total of 20 developers contributed to this release. + +Thanks @liuyanyi, @RangeKing, @lihua199710, @MambaWong, @sanbuphy, @Xiangxu-0103, @twmht, @JunyaoHu, @Chan-Sun, @tianleiSHI, @zytx121, @kitecats, @QJC123654, @JosonChan1998, @lvhan028, @Czm369, @BIGWangYuDong, @RangiLyu, @hhaAndroid, @ZwwWayne + +## v3.0.0rc4 (23/11/2022) + +### Highlights + +- Support [CondInst](https://arxiv.org/abs/2003.05664) +- Add `projects/` folder, which will be a place for some experimental models/features. +- Support [SparseInst](https://arxiv.org/abs/2203.12827) in [`projects`](./projects/SparseInst/README.md) + +### New Features + +- Support [CondInst](https://arxiv.org/abs/2003.05664) (#9223) +- Add `projects/` folder, which will be a place for some experimental models/features (#9341) +- Support [SparseInst](https://arxiv.org/abs/2203.12827) in [`projects`](./projects/SparseInst/README.md) (#9377) + +### Bug Fixes + +- Fix `pixel_decoder_type` discrimination in MaskFormer Head. (#9176) +- Fix wrong padding value in cached MixUp (#9259) +- Rename `utils/typing.py` to `utils/typing_utils.py` to fix `collect_env` error (#9265) +- Fix resume arg conflict (#9287) +- Fix the configs of Faster R-CNN with caffe backbone (#9319) +- Fix torchserve and update related documentation (#9343) +- Fix bbox refine bug with sigmooid activation (#9538) + +### Improvements + +- Update the docs of GIoU Loss in README (#8810) +- Handle dataset wrapper in `inference_detector` (#9144) +- Update the type of `counts` in COCO's compressed RLE (#9274) +- Support saving config file in `print_config` (#9276) +- Update docs about video inference (#9305) +- Update guide about model deployment (#9344) +- Fix doc typos of useful tools (#9177) +- Allow to resume from specific checkpoint in CLI (#9284) +- Update FAQ about windows installation issues of pycocotools (#9292) + +### New Contributors + +- @Daa98 made their first contribution in +- @lvhan028 made their first contribution in + +### Contributors + +A total of 12 developers contributed to this release. + +Thanks @sanbuphy, @Czm369, @Daa98, @jbwang1997, @BIGWangYuDong, @JosonChan1998, @lvhan028, @RunningLeon, @RangiLyu, @Daa98, @ZwwWayne, @hhaAndroid + +## v3.0.0rc3 (4/11/2022) + +Upgrade the minimum version requirement of MMEngine to 0.3.0 to use `ignore_key` of `ConcatDataset` for training VOC datasets (#9058) + +### Highlights + +- Support [CrowdDet](https://arxiv.org/abs/2003.09163) and [EIoU Loss](https://ieeexplore.ieee.org/document/9429909) +- Support training detection models in Detectron2 +- Refactor Fast R-CNN + +### New Features + +- Support [CrowdDet](https://arxiv.org/abs/2003.09163) (#8744) +- Support training detection models in Detectron2 with examples of Mask R-CNN, Faster R-CNN, and RetinaNet (#8672) +- Support [EIoU Loss](https://ieeexplore.ieee.org/document/9429909) (#9086) + +### Bug Fixes + +- Fix `XMLDataset` image size error (#9216) +- Fix bugs of empty_instances when predicting without nms in roi_head (#9015) +- Fix the config file of DETR (#9158) +- Fix SOLOv2 cannot dealing with empty gt image (#9192) +- Fix inference demo (#9153) +- Add `ignore_key` in VOC `ConcatDataset` (#9058) +- Fix dumping results issue in test scripts. (#9241) +- Fix configs of training coco subsets on MMDet 3.x (#9225) +- Fix corner2hbox of HorizontalBoxes for supporting empty bboxes (#9140) + +### Improvements + +- Refactor Fast R-CNN (#9132) +- Clean requirements of mmcv-full due to SyncBN (#9207) +- Support training detection models in detectron2 (#8672) +- Add `box_type` support for `DynamicSoftLabelAssigner` (#9179) +- Make scipy as a default dependency in runtime (#9187) +- Update eval_metric (#9062) +- Add `seg_map_suffix` in `BaseDetDataset` (#9088) + +### New Contributors + +- @Wwupup made their first contribution in +- @sanbuphy made their first contribution in +- @cxiang26 made their first contribution in +- @JosonChan1998 made their first contribution in + +### Contributors + +A total of 13 developers contributed to this release. + +Thanks @wanghonglie, @Wwupup, @sanbuphy, @BIGWangYuDong, @liuyanyi, @cxiang26, @jbwang1997, @ZwwWayne, @yuyoujiang, @RangiLyu, @hhaAndroid, @JosonChan1998, @Czm369 + +## v3.0.0rc2 (21/10/2022) + +### Highlights + +- Support [imagenet pre-training](configs/rtmdet/cspnext_imagenet_pretrain) for RTMDet's backbone + +### New Features + +- Support [imagenet pre-training](configs/rtmdet/cspnext_imagenet_pretrain) for RTMDet's backbone (#8887) +- Add `CrowdHumanDataset` and Metric (#8430) +- Add `FixShapeResize` to support resize of fixed shape (#8665) + +### Bug Fixes + +- Fix `ConcatDataset` Import Error (#8909) +- Fix `CircleCI` and `readthedoc` build failed (#8980, #8963) +- Fix bitmap mask translate when `out_shape` is different (#8993) +- Fix inconsistency in `Conv2d` weight channels (#8948) +- Fix bugs when plotting loss curve by analyze_logs.py (#8944) +- Fix type change of labels in `albumentations` (#9074) +- Fix some docs and types error (#8818) +- Update memory occupation of `RTMDet` in metafile (#9098) +- Fix wrong arguments of `OpenImageMetrics` in the config (#9061) + +### Improvements + +- Refactor standard roi head with `box type` (#8658) +- Support mask concatenation in `BitmapMasks` and `PolygonMasks` (#9006) +- Update PyTorch and dependencies' version in dockerfile (#8845) +- Update `robustness_eval.py` and `print_config` (#8452) +- Make compatible with `ConfigDict` and `dict` in `dense_heads` (#8942) +- Support logging coco metric copypaste (#9012) +- Remove `Normalize` transform (#8913) +- Support jittering the color of different instances of the same class (#8988) +- Add assertion for missing key in `PackDetInputs` (#8982) + +### New Contributors + +- @Chan-Sun made their first contribution in +- @MambaWong made their first contribution in +- @yuyoujiang made their first contribution in +- @sltlls made their first contribution in +- @Nioolek made their first contribution in +- @wufan-tb made their first contribution in + +### Contributors + +A total of 13 developers contributed to this release. + +Thanks @RangiLyu, @jbwang1997, @wanghonglie, @Chan-Sun, @RangeKing, @chhluo, @MambaWong, @yuyoujiang, @hhaAndroid, @sltlls, @Nioolek, @ZwwWayne, @wufan-tb + +## v3.0.0rc1 (26/9/2022) + +### Highlights + +- Release a high-precision, low-latency single-stage object detector [RTMDet](configs/rtmdet). + +### Bug Fixes + +- Fix UT to be compatible with PyTorch 1.6 (#8707) +- Fix `NumClassCheckHook` bug when model is wrapped (#8794) +- Update the right URL of R-50-FPN with BoundedIoULoss (#8805) +- Fix potential bug of indices in RandAugment (#8826) +- Fix some types and links (#8839, #8820, #8793, #8868) +- Fix incorrect background fill values in `FSAF` and `RepPoints` Head (#8813) + +### Improvements + +- Refactored anchor head and base head with `box type` (#8625) +- Refactored `SemiBaseDetector` and `SoftTeacher` (#8786) +- Add list to dict keys to avoid modify loss dict (#8828) +- Update `analyze_results.py` , `analyze_logs.py` and `loading.py` (#8430, #8402, #8784) +- Support dump results in `test.py` (#8814) +- Check empty predictions in `DetLocalVisualizer._draw_instances` (#8830) +- Fix `floordiv` warning in `SOLO` (#8738) + +### Contributors + +A total of 16 developers contributed to this release. + +Thanks @ZwwWayne, @jbwang1997, @Czm369, @ice-tong, @Zheng-LinXiao, @chhluo, @RangiLyu, @liuyanyi, @wanghonglie, @levan92, @JiayuXu0, @nye0, @hhaAndroid, @xin-li-67, @shuxp, @zytx121 + +## v3.0.0rc0 (31/8/2022) + +We are excited to announce the release of MMDetection 3.0.0rc0. MMDet 3.0.0rc0 is the first version of MMDetection 3.x, a part of the OpenMMLab 2.0 projects. Built upon the new [training engine](https://github.com/open-mmlab/mmengine), MMDet 3.x unifies the interfaces of the dataset, models, evaluation, and visualization with faster training and testing speed. It also provides a general semi-supervised object detection framework and strong baselines. + +### Highlights + +1. **New engine**. MMDet 3.x is based on [MMEngine](https://github.com/open-mmlab/mmengine), which provides a universal and powerful runner that allows more flexible customizations and significantly simplifies the entry points of high-level interfaces. + +2. **Unified interfaces**. As a part of the OpenMMLab 2.0 projects, MMDet 3.x unifies and refactors the interfaces and internal logic of training, testing, datasets, models, evaluation, and visualization. All the OpenMMLab 2.0 projects share the same design in those interfaces and logic to allow the emergence of multi-task/modality algorithms. + +3. **Faster speed**. We optimize the training and inference speed for common models and configurations, achieving a faster or similar speed than [Detection2](https://github.com/facebookresearch/detectron2/). Model details of benchmark will be updated in [this note](./benchmark.md#comparison-with-detectron2). + +4. **General semi-supervised object detection**. Benefitting from the unified interfaces, we support a general semi-supervised learning framework that works with all the object detectors supported in MMDet 3.x. Please refer to [semi-supervised object detection](../user_guides/semi_det.md) for details. + +5. **Strong baselines**. We release strong baselines of many popular models to enable fair comparisons among state-of-the-art models. + +6. **New features and algorithms**: + + - Enable all the single-stage detectors to serve as region proposal networks + - [SoftTeacher](https://arxiv.org/abs/2106.09018) + - [the updated CenterNet](https://arxiv.org/abs/2103.07461) + +7. **More documentation and tutorials**. We add a bunch of documentation and tutorials to help users get started more smoothly. Read it [here](https://mmdetection.readthedocs.io/en/3.x/). + +### Breaking Changes + +MMDet 3.x has undergone significant changes for better design, higher efficiency, more flexibility, and more unified interfaces. +Besides the changes in API, we briefly list the major breaking changes in this section. +We will update the [migration guide](../migration.md) to provide complete details and migration instructions. +Users can also refer to the [API doc](https://mmdetection.readthedocs.io/en/3.x/) for more details. + +#### Dependencies + +- MMDet 3.x runs on PyTorch>=1.6. We have deprecated the support of PyTorch 1.5 to embrace mixed precision training and other new features since PyTorch 1.6. Some models can still run on PyTorch 1.5, but the full functionality of MMDet 3.x is not guaranteed. +- MMDet 3.x relies on MMEngine to run. MMEngine is a new foundational library for training deep learning models of OpenMMLab and is the core dependency of OpenMMLab 2.0 projects. The dependencies of file IO and training are migrated from MMCV 1.x to MMEngine. +- MMDet 3.x relies on MMCV>=2.0.0rc0. Although MMCV no longer maintains the training functionalities since 2.0.0rc0, MMDet 3.x relies on the data transforms, CUDA operators, and image processing interfaces in MMCV. Note that the package `mmcv` is the version that provides pre-built CUDA operators and `mmcv-lite` does not since MMCV 2.0.0rc0, while `mmcv-full` has been deprecated since 2.0.0rc0. + +#### Training and testing + +- MMDet 3.x uses Runner in [MMEngine](https://github.com/open-mmlab/mmengine) rather than that in MMCV. The new Runner implements and unifies the building logic of the dataset, model, evaluation, and visualizer. Therefore, MMDet 3.x no longer maintains the building logic of those modules in `mmdet.train.apis` and `tools/train.py`. Those codes have been migrated into [MMEngine](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py). Please refer to the [migration guide of Runner in MMEngine](https://mmengine.readthedocs.io/en/latest/migration/runner.html) for more details. +- The Runner in MMEngine also supports testing and validation. The testing scripts are also simplified, which has similar logic to that in training scripts to build the runner. +- The execution points of hooks in the new Runner have been enriched to allow more flexible customization. Please refer to the [migration guide of Hook in MMEngine](https://mmengine.readthedocs.io/en/latest/migration/hook.html) for more details. +- Learning rate and momentum schedules have been migrated from Hook to [Parameter Scheduler in MMEngine](https://mmengine.readthedocs.io/en/latest/tutorials/param_scheduler.html). Please refer to the [migration guide of Parameter Scheduler in MMEngine](https://mmengine.readthedocs.io/en/latest/migration/param_scheduler.html) for more details. + +#### Configs + +- The [Runner in MMEngine](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py) uses a different config structure to ease the understanding of the components in the runner. Users can read the [config example of MMDet 3.x](../user_guides/config.md) or refer to the [migration guide in MMEngine](https://mmengine.readthedocs.io/en/latest/migration/runner.html) for migration details. +- The file names of configs and models are also refactored to follow the new rules unified across OpenMMLab 2.0 projects. The names of checkpoints are not updated for now as there is no BC-breaking of model weights between MMDet 3.x and 2.x. We will progressively replace all the model weights with those trained in MMDet 3.x. Please refer to the [user guides of config](../user_guides/config.md) for more details. + +#### Dataset + +The Dataset classes implemented in MMDet 3.x all inherit from the `BaseDetDataset`, which inherits from the [BaseDataset in MMEngine](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/basedataset.html). In addition to the changes in interfaces, there are several changes in Dataset in MMDet 3.x. + +- All the datasets support serializing the internal data list to reduce the memory when multiple workers are built for data loading. +- The internal data structure in the dataset is changed to be self-contained (without losing information like class names in MMDet 2.x) while keeping simplicity. +- The evaluation functionality of each dataset has been removed from the dataset so that some specific evaluation metrics like COCO AP can be used to evaluate the prediction on other datasets. + +#### Data Transforms + +The data transforms in MMDet 3.x all inherits from `BaseTransform` in MMCV>=2.0.0rc0, which defines a new convention in OpenMMLab 2.0 projects. +Besides the interface changes, there are several changes listed below: + +- The functionality of some data transforms (e.g., `Resize`) are decomposed into several transforms to simplify and clarify the usages. +- The format of data dict processed by each data transform is changed according to the new data structure of dataset. +- Some inefficient data transforms (e.g., normalization and padding) are moved into data preprocessor of model to improve data loading and training speed. +- The same data transforms in different OpenMMLab 2.0 libraries have the same augmentation implementation and the logic given the same arguments, i.e., `Resize` in MMDet 3.x and MMSeg 1.x will resize the image in the exact same manner given the same arguments. + +#### Model + +The models in MMDet 3.x all inherit from `BaseModel` in MMEngine, which defines a new convention of models in OpenMMLab 2.0 projects. +Users can refer to [the tutorial of the model in MMengine](https://mmengine.readthedocs.io/en/latest/tutorials/model.html) for more details. +Accordingly, there are several changes as the following: + +- The model interfaces, including the input and output formats, are significantly simplified and unified following the new convention in MMDet 3.x. + Specifically, all the input data in training and testing are packed into `inputs` and `data_samples`, where `inputs` contains model inputs like a list of image tensors, and `data_samples` contains other information of the current data sample such as ground truths, region proposals, and model predictions. In this way, different tasks in MMDet 3.x can share the same input arguments, which makes the models more general and suitable for multi-task learning and some flexible training paradigms like semi-supervised learning. +- The model has a data preprocessor module, which is used to pre-process the input data of the model. In MMDet 3.x, the data preprocessor usually does the necessary steps to form the input images into a batch, such as padding. It can also serve as a place for some special data augmentations or more efficient data transformations like normalization. +- The internal logic of the model has been changed. In MMdet 2.x, model uses `forward_train`, `forward_test`, `simple_test`, and `aug_test` to deal with different model forward logics. In MMDet 3.x and OpenMMLab 2.0, the forward function has three modes: 'loss', 'predict', and 'tensor' for training, inference, and tracing or other purposes, respectively. + The forward function calls `self.loss`, `self.predict`, and `self._forward` given the modes 'loss', 'predict', and 'tensor', respectively. + +#### Evaluation + +The evaluation in MMDet 2.x strictly binds with the dataset. In contrast, MMDet 3.x decomposes the evaluation from dataset so that all the detection datasets can evaluate with COCO AP and other metrics implemented in MMDet 3.x. +MMDet 3.x mainly implements corresponding metrics for each dataset, which are manipulated by [Evaluator](https://mmengine.readthedocs.io/en/latest/design/evaluator.html) to complete the evaluation. +Users can build an evaluator in MMDet 3.x to conduct offline evaluation, i.e., evaluate predictions that may not produce in MMDet 3.x with the dataset as long as the dataset and the prediction follow the dataset conventions. More details can be found in the [tutorial in mmengine](https://mmengine.readthedocs.io/en/latest/tutorials/evaluation.html). + +#### Visualization + +The functions of visualization in MMDet 2.x are removed. Instead, in OpenMMLab 2.0 projects, we use [Visualizer](https://mmengine.readthedocs.io/en/latest/design/visualization.html) to visualize data. MMDet 3.x implements `DetLocalVisualizer` to allow visualization of ground truths, model predictions, feature maps, etc., at any place. It also supports sending the visualization data to any external visualization backends such as Tensorboard. + +### Improvements + +- Optimized training and testing speed of FCOS, RetinaNet, Faster R-CNN, Mask R-CNN, and Cascade R-CNN. The training speed of those models with some common training strategies is also optimized, including those with synchronized batch normalization and mixed precision training. +- Support mixed precision training of all the models. However, some models may get undesirable performance due to some numerical issues. We will update the documentation and list the results (accuracy of failure) of mixed precision training. +- Release strong baselines of some popular object detectors. Their accuracy and pre-trained checkpoints will be released. + +### Bug Fixes + +- DeepFashion dataset: the config and results have been updated. + +### New Features + +1. Support a general semi-supervised learning framework that works with all the object detectors supported in MMDet 3.x. Please refer to [semi-supervised object detection](../user_guides/semi_det.md) for details. +2. Enable all the single-stage detectors to serve as region proposal networks. We give [an example of using FCOS as RPN](../user_guides/single_stage_as_rpn.md). +3. Support a semi-supervised object detection algorithm: [SoftTeacher](https://arxiv.org/abs/2106.09018). +4. Support [the updated CenterNet](https://arxiv.org/abs/2103.07461). +5. Support data structures `HorizontalBoxes` and `BaseBoxes` to encapsulate different kinds of bounding boxes. We are migrating to use data structures of boxes to replace the use of pure tensor boxes. This will unify the usages of different kinds of bounding boxes in MMDet 3.x and MMRotate 1.x to simplify the implementation and reduce redundant codes. + +### Planned changes + +We list several planned changes of MMDet 3.0.0rc0 so that the community could more comprehensively know the progress of MMDet 3.x. Feel free to create a PR, issue, or discussion if you are interested, have any suggestions and feedback, or want to participate. + +1. Test-time augmentation: which is supported in MMDet 2.x, is not implemented in this version due to the limited time slot. We will support it in the following releases with a new and simplified design. +2. Inference interfaces: unified inference interfaces will be supported in the future to ease the use of released models. +3. Interfaces of useful tools that can be used in Jupyter Notebook or Colab: more useful tools that are implemented in the `tools` directory will have their python interfaces so that they can be used in Jupyter Notebook, Colab, and downstream libraries. +4. Documentation: we will add more design docs, tutorials, and migration guidance so that the community can deep dive into our new design, participate the future development, and smoothly migrate downstream libraries to MMDet 3.x. +5. Wandb visualization: MMDet 2.x supports data visualization since v2.25.0, which has not been migrated to MMDet 3.x for now. Since WandB provides strong visualization and experiment management capabilities, a `DetWandBVisualizer` and maybe a hook are planned to fully migrate those functionalities from MMDet 2.x. +6. Full support of WiderFace dataset (#8508) and Fast R-CNN: we are verifying their functionalities and will fix related issues soon. +7. Migrate DETR-series algorithms (#8655, #8533) and YOLOv3 on IPU (#8552) from MMDet 2.x. + +### Contributors + +A total of 11 developers contributed to this release. +Thanks @shuxp, @wanghonglie, @Czm369, @BIGWangYuDong, @zytx121, @jbwang1997, @chhluo, @jshilong, @RangiLyu, @hhaAndroid, @ZwwWayne diff --git a/mmdetection/docs/en/notes/changelog_v2.x.md b/mmdetection/docs/en/notes/changelog_v2.x.md new file mode 100644 index 00000000..2b3a230c --- /dev/null +++ b/mmdetection/docs/en/notes/changelog_v2.x.md @@ -0,0 +1,1681 @@ +# Changelog v2.x + +### v2.25.0 (31/5/2022) + +#### Highlights + +- Support dedicated `WandbLogger` hook +- Support [ConvNeXt](configs/convnext), [DDOD](configs/ddod), [SOLOv2](configs/solov2) +- Support [Mask2Former](configs/mask2former) for instance segmentation +- Rename [config files of Mask2Former](configs/mask2former) + +#### Backwards incompatible changes + +- Rename [config files of Mask2Former](configs/mask2former) (#7571) + + + + + + + + + + + +
    before v2.25.0after v2.25.0
    + + - `mask2former_xxx_coco.py` represents config files for **panoptic segmentation**. + + + + - `mask2former_xxx_coco.py` represents config files for **instance segmentation**. + - `mask2former_xxx_coco-panoptic.py` represents config files for **panoptic segmentation**. + +
    + +#### New Features + +- Support [ConvNeXt](https://arxiv.org/abs/2201.03545) (#7281) +- Support [DDOD](https://arxiv.org/abs/2107.02963) (#7279) +- Support [SOLOv2](https://arxiv.org/abs/2003.10152) (#7441) +- Support [Mask2Former](https://arxiv.org/abs/2112.01527) for instance segmentation (#7571, #8032) + +#### Bug Fixes + +- Enable YOLOX training on different devices (#7912) +- Fix the log plot error when evaluation with `interval != 1` (#7784) +- Fix RuntimeError of HTC (#8083) + +#### Improvements + +- Support dedicated `WandbLogger` hook (#7459) + + Users can set + + ```python + cfg.log_config.hooks = [ + dict(type='MMDetWandbHook', + init_kwargs={'project': 'MMDetection-tutorial'}, + interval=10, + log_checkpoint=True, + log_checkpoint_metadata=True, + num_eval_images=10)] + ``` + + in the config to use `MMDetWandbHook`. Example can be found in this [colab tutorial](https://colab.research.google.com/drive/1RCSXHZwDZvakFh3eo9RuNrJbCGqD0dru?usp=sharing#scrollTo=WTEdPDRaBz2C) + +- Add `AvoidOOM` to avoid OOM (#7434, #8091) + + Try to use `AvoidCUDAOOM` to avoid GPU out of memory. It will first retry after calling `torch.cuda.empty_cache()`. If it still fails, it will then retry by converting the type of inputs to FP16 format. If it still fails, it will try to copy inputs from GPUs to CPUs to continue computing. Try AvoidOOM in code to make the code continue to run when GPU memory runs out: + + ```python + from mmdet.utils import AvoidCUDAOOM + + output = AvoidCUDAOOM.retry_if_cuda_oom(some_function)(input1, input2) + ``` + + Users can also try `AvoidCUDAOOM` as a decorator to make the code continue to run when GPU memory runs out: + + ```python + from mmdet.utils import AvoidCUDAOOM + + @AvoidCUDAOOM.retry_if_cuda_oom + def function(*args, **kwargs): + ... + return xxx + ``` + +- Support reading `gpu_collect` from `cfg.evaluation.gpu_collect` (#7672) + +- Speedup the Video Inference by Accelerating data-loading Stage (#7832) + +- Support replacing the `${key}` with the value of `cfg.key` (#7492) + +- Accelerate result analysis in `analyze_result.py`. The evaluation time is speedup by 10 ~ 15 times and only tasks 10 ~ 15 minutes now. (#7891) + +- Support to set `block_dilations` in `DilatedEncoder` (#7812) + +- Support panoptic segmentation result analysis (#7922) + +- Release DyHead with Swin-Large backbone (#7733) + +- Documentations updating and adding + + - Fix wrong default type of `act_cfg` in `SwinTransformer` (#7794) + - Fix text errors in the tutorials (#7959) + - Rewrite the [installation guide](docs/en/get_started.md) (#7897) + - [Useful hooks](docs/en/tutorials/useful_hooks.md) (#7810) + - Fix heading anchor in documentation (#8006) + - Replace `markdownlint` with `mdformat` for avoiding installing ruby (#8009) + +#### Contributors + +A total of 20 developers contributed to this release. + +Thanks @ZwwWayne, @DarthThomas, @solyaH, @LutingWang, @chenxinfeng4, @Czm369, @Chenastron, @chhluo, @austinmw, @Shanyaliux @hellock, @Y-M-Y, @jbwang1997, @hhaAndroid, @Irvingao, @zhanggefan, @BIGWangYuDong, @Keiku, @PeterVennerstrom, @ayulockin + +### v2.24.0 (26/4/2022) + +#### Highlights + +- Support [Simple Copy-Paste is a Strong Data Augmentation Method for Instance Segmentation](https://arxiv.org/abs/2012.07177) +- Support automatically scaling LR according to GPU number and samples per GPU +- Support Class Aware Sampler that improves performance on OpenImages Dataset + +#### New Features + +- Support [Simple Copy-Paste is a Strong Data Augmentation Method for Instance Segmentation](https://arxiv.org/abs/2012.07177), see [example configs](configs/simple_copy_paste/mask_rcnn_r50_fpn_syncbn-all_rpn-2conv_ssj_scp_32x2_270k_coco.py) (#7501) + +- Support Class Aware Sampler, users can set + + ```python + data=dict(train_dataloader=dict(class_aware_sampler=dict(num_sample_class=1)))) + ``` + + in the config to use `ClassAwareSampler`. Examples can be found in [the configs of OpenImages Dataset](https://github.com/open-mmlab/mmdetection/tree/main/configs/openimages/faster_rcnn_r50_fpn_32x2_cas_1x_openimages.py). (#7436) + +- Support automatically scaling LR according to GPU number and samples per GPU. (#7482) + In each config, there is a corresponding config of auto-scaling LR as below, + + ```python + auto_scale_lr = dict(enable=True, base_batch_size=N) + ``` + + where `N` is the batch size used for the current learning rate in the config (also equals to `samples_per_gpu` * gpu number to train this config). + By default, we set `enable=False` so that the original usages will not be affected. Users can set `enable=True` in each config or add `--auto-scale-lr` after the command line to enable this feature and should check the correctness of `base_batch_size` in customized configs. + +- Support setting dataloader arguments in config and add functions to handle config compatibility. (#7668) + The comparison between the old and new usages is as below. + + + + + + + + + + + +
    v2.23.0v2.24.0
    + + ```python + data = dict( + samples_per_gpu=64, workers_per_gpu=4, + train=dict(type='xxx', ...), + val=dict(type='xxx', samples_per_gpu=4, ...), + test=dict(type='xxx', ...), + ) + ``` + + + + ```python + # A recommended config that is clear + data = dict( + train=dict(type='xxx', ...), + val=dict(type='xxx', ...), + test=dict(type='xxx', ...), + # Use different batch size during inference. + train_dataloader=dict(samples_per_gpu=64, workers_per_gpu=4), + val_dataloader=dict(samples_per_gpu=8, workers_per_gpu=2), + test_dataloader=dict(samples_per_gpu=8, workers_per_gpu=2), + ) + + # Old style still works but allows to set more arguments about data loaders + data = dict( + samples_per_gpu=64, # only works for train_dataloader + workers_per_gpu=4, # only works for train_dataloader + train=dict(type='xxx', ...), + val=dict(type='xxx', ...), + test=dict(type='xxx', ...), + # Use different batch size during inference. + val_dataloader=dict(samples_per_gpu=8, workers_per_gpu=2), + test_dataloader=dict(samples_per_gpu=8, workers_per_gpu=2), + ) + ``` + +
    + +- Support memory profile hook. Users can use it to monitor the memory usages during training as below (#7560) + + ```python + custom_hooks = [ + dict(type='MemoryProfilerHook', interval=50) + ] + ``` + +- Support to run on PyTorch with MLU chip (#7578) + +- Support re-spliting data batch with tag (#7641) + +- Support the `DiceCost` used by [K-Net](https://arxiv.org/abs/2106.14855) in `MaskHungarianAssigner` (#7716) + +- Support splitting COCO data for Semi-supervised object detection (#7431) + +- Support Pathlib for Config.fromfile (#7685) + +- Support to use file client in OpenImages dataset (#7433) + +- Add a probability parameter to Mosaic transformation (#7371) + +- Support specifying interpolation mode in `Resize` pipeline (#7585) + +#### Bug Fixes + +- Avoid invalid bbox after deform_sampling (#7567) +- Fix the issue that argument color_theme does not take effect when exporting confusion matrix (#7701) +- Fix the `end_level` in Necks, which should be the index of the end input backbone level (#7502) +- Fix the bug that `mix_results` may be None in `MultiImageMixDataset` (#7530) +- Fix the bug in ResNet plugin when two plugins are used (#7797) + +#### Improvements + +- Enhance `load_json_logs` of analyze_logs.py for resumed training logs (#7732) +- Add argument `out_file` in image_demo.py (#7676) +- Allow mixed precision training with `SimOTAAssigner` (#7516) +- Updated INF to 100000.0 to be the same as that in the official YOLOX (#7778) +- Add documentations of: + - how to get channels of a new backbone (#7642) + - how to unfreeze the backbone network (#7570) + - how to train fast_rcnn model (#7549) + - proposals in Deformable DETR (#7690) + - from-scratch install script in get_started.md (#7575) +- Release pre-trained models of + - [Mask2Former](configs/mask2former) (#7595, #7709) + - RetinaNet with ResNet-18 and release models (#7387) + - RetinaNet with EfficientNet backbone (#7646) + +#### Contributors + +A total of 27 developers contributed to this release. +Thanks @jovialio, @zhangsanfeng2022, @HarryZJ, @jamiechoi1995, @nestiank, @PeterH0323, @RangeKing, @Y-M-Y, @mattcasey02, @weiji14, @Yulv-git, @xiefeifeihu, @FANG-MING, @meng976537406, @nijkah, @sudz123, @CCODING04, @SheffieldCao, @Czm369, @BIGWangYuDong, @zytx121, @jbwang1997, @chhluo, @jshilong, @RangiLyu, @hhaAndroid, @ZwwWayne + +### v2.23.0 (28/3/2022) + +#### Highlights + +- Support Mask2Former: [Masked-attention Mask Transformer for Universal Image Segmentation](https://arxiv.org/abs/2112.01527) +- Support EfficientNet: [EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks](https://arxiv.org/abs/1905.11946) +- Support setting data root through environment variable `MMDET_DATASETS`, users don't have to modify the corresponding path in config files anymore. +- Find a good recipe for fine-tuning high precision ResNet backbone pre-trained by Torchvision. + +#### New Features + +- Support [Mask2Former](configs/mask2former)(#6938)(#7466)(#7471) +- Support [EfficientNet](configs/efficientnet) (#7514) +- Support setting data root through environment variable `MMDET_DATASETS`, users don't have to modify the corresponding path in config files anymore. (#7386) +- Support setting different seeds to different ranks (#7432) +- Update the `dist_train.sh` so that the script can be used to support launching multi-node training on machines without slurm (#7415) +- Find a good recipe for fine-tuning high precision ResNet backbone pre-trained by Torchvision (#7489) + +#### Bug Fixes + +- Fix bug in VOC unit test which removes the data directory (#7270) +- Adjust the order of `get_classes` and `FileClient` (#7276) +- Force the inputs of `get_bboxes` in yolox_head to float32 (#7324) +- Fix misplaced arguments in LoadPanopticAnnotations (#7388) +- Fix reduction=mean in CELoss. (#7449) +- Update unit test of CrossEntropyCost (#7537) +- Fix memory leaking in panpotic segmentation evaluation (#7538) +- Fix the bug of shape broadcast in YOLOv3 (#7551) + +#### Improvements + +- Add Chinese version of onnx2tensorrt.md (#7219) +- Update colab tutorials (#7310) +- Update information about Localization Distillation (#7350) +- Add Chinese version of `finetune.md` (#7178) +- Update YOLOX log for non square input (#7235) +- Add `nproc` in `coco_panoptic.py` for panoptic quality computing (#7315) +- Allow to set channel_order in LoadImageFromFile (#7258) +- Take point sample related functions out of mask_point_head (#7353) +- Add instance evaluation for coco_panoptic (#7313) +- Enhance the robustness of analyze_logs.py (#7407) +- Supplementary notes of sync_random_seed (#7440) +- Update docstring of cross entropy loss (#7472) +- Update pascal voc result (#7503) +- We create How-to documentation to record any questions about How to xxx. In this version, we added + - How to use Mosaic augmentation (#7507) + - How to use backbone in mmcls (#7438) + - How to produce and submit the prediction results of panoptic segmentation models on COCO test-dev set (#7430)) + +#### Contributors + +A total of 27 developers contributed to this release. +Thanks @ZwwWayne, @haofanwang, @shinya7y, @chhluo, @yangrisheng, @triple-Mu, @jbwang1997, @HikariTJU, @imflash217, @274869388, @zytx121, @matrixgame2018, @jamiechoi1995, @BIGWangYuDong, @JingweiZhang12, @Xiangxu-0103, @hhaAndroid, @jshilong, @osbm, @ceroytres, @bunge-bedstraw-herb, @Youth-Got, @daavoo, @jiangyitong, @RangiLyu, @CCODING04, @yarkable + +### v2.22.0 (24/2/2022) + +#### Highlights + +- Support MaskFormer: [Per-Pixel Classification is Not All You Need for Semantic Segmentation](https://arxiv.org/abs/2107.06278) (#7212) +- Support DyHead: [Dynamic Head: Unifying Object Detection Heads with Attentions](https://arxiv.org/abs/2106.08322) (#6823) +- Release a good recipe of using ResNet in object detectors pre-trained by [ResNet Strikes Back](https://arxiv.org/abs/2110.00476), which consistently brings about 3~4 mAP improvements over RetinaNet, Faster/Mask/Cascade Mask R-CNN (#7001) +- Support [Open Images Dataset](https://storage.googleapis.com/openimages/web/index.html) (#6331) +- Support TIMM backbone: [PyTorch Image Models](https://github.com/rwightman/pytorch-image-models) (#7020) + +#### New Features + +- Support [MaskFormer](configs/maskformer) (#7212) +- Support [DyHead](configs/dyhead) (#6823) +- Support [ResNet Strikes Back](configs/resnet_strikes_back) (#7001) +- Support [OpenImages Dataset](configs/openimages) (#6331) +- Support [TIMM backbone](configs/timm_example) (#7020) +- Support visualization for Panoptic Segmentation (#7041) + +#### Breaking Changes + +In order to support the visualization for Panoptic Segmentation, the `num_classes` can not be `None` when using the `get_palette` function to determine whether to use the panoptic palette. + +#### Bug Fixes + +- Fix bug for the best checkpoints can not be saved when the `key_score` is None (#7101) +- Fix MixUp transform filter boxes failing case (#7080) +- Add missing properties in SABLHead (#7091) +- Fix bug when NaNs exist in confusion matrix (#7147) +- Fix PALETTE AttributeError in downstream task (#7230) + +#### Improvements + +- Speed up SimOTA matching (#7098) +- Add Chinese translation of `docs_zh-CN/tutorials/init_cfg.md` (#7188) + +#### Contributors + +A total of 20 developers contributed to this release. +Thanks @ZwwWayne, @hhaAndroid, @RangiLyu, @AronLin, @BIGWangYuDong, @jbwang1997, @zytx121, @chhluo, @shinya7y, @LuooChen, @dvansa, @siatwangmin, @del-zhenwu, @vikashranjan26, @haofanwang, @jamiechoi1995, @HJoonKwon, @yarkable, @zhijian-liu, @RangeKing + +### v2.21.0 (8/2/2022) + +### Breaking Changes + +To standardize the contents in config READMEs and meta files of OpenMMLab projects, the READMEs and meta files in each config directory have been significantly changed. The template will be released in the future, for now, you can refer to the examples of README for [algorithm](https://github.com/open-mmlab/mmdetection/blob/master/configs/faster_rcnn/README.md), [dataset](https://github.com/open-mmlab/mmdetection/blob/master/configs/deepfashion/README.md) and [backbone](https://github.com/open-mmlab/mmdetection/blob/master/configs/regnet/README.md). To align with the standard, the configs in dcn are put into to two directories named `dcn` and `dcnv2`. + +#### New Features + +- Allow to customize colors of different classes during visualization (#6716) +- Support CPU training (#7016) +- Add download script of COCO, LVIS, and VOC dataset (#7015) + +#### Bug Fixes + +- Fix weight conversion issue of RetinaNet with Swin-S (#6973) +- Update `__repr__` of `Compose` (#6951) +- Fix BadZipFile Error when build docker (#6966) +- Fix bug in non-distributed multi-gpu training/testing (#7019) +- Fix bbox clamp in PyTorch 1.10 (#7074) +- Relax the requirement of PALETTE in dataset wrappers (#7085) +- Keep the same weights before reassign in the PAA head (#7032) +- Update code demo in doc (#7092) + +#### Improvements + +- Speed-up training by allow to set variables of multi-processing (#6974, #7036) +- Add links of Chinese tutorials in readme (#6897) +- Disable cv2 multiprocessing by default for acceleration (#6867) +- Deprecate the support for "python setup.py test" (#6998) +- Re-organize metafiles and config readmes (#7051) +- Fix None grad problem during training TOOD by adding `SigmoidGeometricMean` (#7090) + +#### Contributors + +A total of 26 developers contributed to this release. +Thanks @del-zhenwu, @zimoqingfeng, @srishilesh, @imyhxy, @jenhaoyang, @jliu-ac, @kimnamu, @ShengliLiu, @garvan2021, @ciusji, @DIYer22, @kimnamu, @q3394101, @zhouzaida, @gaotongxiao, @topsy404, @AntoAndGar, @jbwang1997, @nijkah, @ZwwWayne, @Czm369, @jshilong, @RangiLyu, @BIGWangYuDong, @hhaAndroid, @AronLin + +### v2.20.0 (27/12/2021) + +#### New Features + +- Support [TOOD](configs/tood/README.md): Task-aligned One-stage Object Detection (ICCV 2021 Oral) (#6746) +- Support resuming from the latest checkpoint automatically (#6727) + +#### Bug Fixes + +- Fix wrong bbox `loss_weight` of the PAA head (#6744) +- Fix the padding value of `gt_semantic_seg` in batch collating (#6837) +- Fix test error of lvis when using `classwise` (#6845) +- Avoid BC-breaking of `get_local_path` (#6719) +- Fix bug in `sync_norm_hook` when the BN layer does not exist (#6852) +- Use pycocotools directly no matter what platform it is (#6838) + +#### Improvements + +- Add unit test for SimOTA with no valid bbox (#6770) +- Use precommit to check readme (#6802) +- Support selecting GPU-ids in non-distributed testing time (#6781) + +#### Contributors + +A total of 16 developers contributed to this release. +Thanks @ZwwWayne, @Czm369, @jshilong, @RangiLyu, @BIGWangYuDong, @hhaAndroid, @jamiechoi1995, @AronLin, @Keiku, @gkagkos, @fcakyon, @www516717402, @vansin, @zactodd, @kimnamu, @jenhaoyang + +### v2.19.1 (14/12/2021) + +#### New Features + +- Release [YOLOX](configs/yolox/README.md) COCO pretrained models (#6698) + +#### Bug Fixes + +- Fix DCN initialization in DenseHead (#6625) +- Fix initialization of ConvFCHead (#6624) +- Fix PseudoSampler in RCNN (#6622) +- Fix weight initialization in Swin and PVT (#6663) +- Fix dtype bug in BaseDenseHead (#6767) +- Fix SimOTA with no valid bbox (#6733) + +#### Improvements + +- Add an example of combining swin and one-stage models (#6621) +- Add `get_ann_info` to dataset_wrappers (#6526) +- Support keeping image ratio in the multi-scale training of YOLOX (#6732) +- Support `bbox_clip_border` for the augmentations of YOLOX (#6730) + +#### Documents + +- Update metafile (#6717) +- Add mmhuman3d in readme (#6699) +- Update FAQ docs (#6587) +- Add doc for `detect_anomalous_params` (#6697) + +#### Contributors + +A total of 11 developers contributed to this release. +Thanks @ZwwWayne, @LJoson, @Czm369, @jshilong, @ZCMax, @RangiLyu, @BIGWangYuDong, @hhaAndroid, @zhaoxin111, @GT9505, @shinya7y + +### v2.19.0 (29/11/2021) + +#### Highlights + +- Support [Label Assignment Distillation](https://arxiv.org/abs/2108.10520) +- Support `persistent_workers` for Pytorch >= 1.7 +- Align accuracy to the updated official YOLOX + +#### New Features + +- Support [Label Assignment Distillation](https://arxiv.org/abs/2108.10520) (#6342) +- Support `persistent_workers` for Pytorch >= 1.7 (#6435) + +#### Bug Fixes + +- Fix repeatedly output warning message (#6584) +- Avoid infinite GPU waiting in dist training (#6501) +- Fix SSD512 config error (#6574) +- Fix MMDetection model to ONNX command (#6558) + +#### Improvements + +- Refactor configs of FP16 models (#6592) +- Align accuracy to the updated official YOLOX (#6443) +- Speed up training and reduce memory cost when using PhotoMetricDistortion. (#6442) +- Make OHEM work with seesaw loss (#6514) + +#### Documents + +- Update README.md (#6567) + +#### Contributors + +A total of 11 developers contributed to this release. +Thanks @FloydHsiu, @RangiLyu, @ZwwWayne, @AndreaPi, @st9007a, @hachreak, @BIGWangYuDong, @hhaAndroid, @AronLin, @chhluo, @vealocia, @HarborYuan, @st9007a, @jshilong + +### v2.18.1 (15/11/2021) + +#### Highlights + +- Release [QueryInst](http://arxiv.org/abs/2105.01928) pre-trained weights (#6460) +- Support plot confusion matrix (#6344) + +#### New Features + +- Release [QueryInst](http://arxiv.org/abs/2105.01928) pre-trained weights (#6460) +- Support plot confusion matrix (#6344) + +#### Bug Fixes + +- Fix aug test error when the number of prediction bboxes is 0 (#6398) +- Fix SpatialReductionAttention in PVT (#6488) +- Fix wrong use of `trunc_normal_init` in PVT and Swin-Transformer (#6432) + +#### Improvements + +- Save the printed AP information of COCO API to logger (#6505) +- Always map location to cpu when load checkpoint (#6405) +- Set a random seed when the user does not set a seed (#6457) + +#### Documents + +- Chinese version of [Corruption Benchmarking](robustness_benchmarking.md) (#6375) +- Fix config path in docs (#6396) +- Update GRoIE readme (#6401) + +#### Contributors + +A total of 11 developers contributed to this release. +Thanks @st9007a, @hachreak, @HarborYuan, @vealocia, @chhluo, @AndreaPi, @AronLin, @BIGWangYuDong, @hhaAndroid, @RangiLyu, @ZwwWayne + +### v2.18.0 (27/10/2021) + +#### Highlights + +- Support [QueryInst](http://arxiv.org/abs/2105.01928) (#6050) +- Refactor dense heads to decouple onnx export logics from `get_bboxes` and speed up inference (#5317, #6003, #6369, #6268, #6315) + +#### New Features + +- Support [QueryInst](http://arxiv.org/abs/2105.01928) (#6050) +- Support infinite sampler (#5996) + +#### Bug Fixes + +- Fix init_weight in fcn_mask_head (#6378) +- Fix type error in imshow_bboxes of RPN (#6386) +- Fix broken colab link in MMDetection Tutorial (#6382) +- Make sure the device and dtype of scale_factor are the same as bboxes (#6374) +- Remove sampling hardcode (#6317) +- Fix RandomAffine bbox coordinate recorrection (#6293) +- Fix init bug of final cls/reg layer in convfc head (#6279) +- Fix img_shape broken in auto_augment (#6259) +- Fix kwargs parameter missing error in two_stage (#6256) + +#### Improvements + +- Unify the interface of stuff head and panoptic head (#6308) +- Polish readme (#6243) +- Add code-spell pre-commit hook and fix a typo (#6306) +- Fix typo (#6245, #6190) +- Fix sampler unit test (#6284) +- Fix `forward_dummy` of YOLACT to enable `get_flops` (#6079) +- Fix link error in the config documentation (#6252) +- Adjust the order to beautify the document (#6195) + +#### Refactors + +- Refactor one-stage get_bboxes logic (#5317) +- Refactor ONNX export of One-Stage models (#6003, #6369) +- Refactor dense_head and speedup (#6268) +- Migrate to use prior_generator in training of dense heads (#6315) + +#### Contributors + +A total of 18 developers contributed to this release. +Thanks @Boyden, @onnkeat, @st9007a, @vealocia, @yhcao6, @DapangpangX, @yellowdolphin, @cclauss, @kennymckormick, +@pingguokiller, @collinzrj, @AndreaPi, @AronLin, @BIGWangYuDong, @hhaAndroid, @jshilong, @RangiLyu, @ZwwWayne + +### v2.17.0 (28/9/2021) + +#### Highlights + +- Support [PVT](https://arxiv.org/abs/2102.12122) and [PVTv2](https://arxiv.org/abs/2106.13797) +- Support [SOLO](https://arxiv.org/abs/1912.04488) +- Support large scale jittering and New Mask R-CNN baselines +- Speed up `YOLOv3` inference + +#### New Features + +- Support [PVT](https://arxiv.org/abs/2102.12122) and [PVTv2](https://arxiv.org/abs/2106.13797) (#5780) +- Support [SOLO](https://arxiv.org/abs/1912.04488) (#5832) +- Support large scale jittering and New Mask R-CNN baselines (#6132) +- Add a general data structure for the results of models (#5508) +- Added a base class for one-stage instance segmentation (#5904) +- Speed up `YOLOv3` inference (#5991) +- Release Swin Transformer pre-trained models (#6100) +- Support mixed precision training in `YOLOX` (#5983) +- Support `val` workflow in `YOLACT` (#5986) +- Add script to test `torchserve` (#5936) +- Support `onnxsim` with dynamic input shape (#6117) + +#### Bug Fixes + +- Fix the function naming errors in `model_wrappers` (#5975) +- Fix regression loss bug when the input is an empty tensor (#5976) +- Fix scores not contiguous error in `centernet_head` (#6016) +- Fix missing parameters bug in `imshow_bboxes` (#6034) +- Fix bug in `aug_test` of `HTC` when the length of `det_bboxes` is 0 (#6088) +- Fix empty proposal errors in the training of some two-stage models (#5941) +- Fix `dynamic_axes` parameter error in `ONNX` dynamic shape export (#6104) +- Fix `dynamic_shape` bug of `SyncRandomSizeHook` (#6144) +- Fix the Swin Transformer config link error in the configuration (#6172) + +#### Improvements + +- Add filter rules in `Mosaic` transform (#5897) +- Add size divisor in get flops to avoid some potential bugs (#6076) +- Add Chinese translation of `docs_zh-CN/tutorials/customize_dataset.md` (#5915) +- Add Chinese translation of `conventions.md` (#5825) +- Add description of the output of data pipeline (#5886) +- Add dataset information in the README file for `PanopticFPN` (#5996) +- Add `extra_repr` for `DropBlock` layer to get details in the model printing (#6140) +- Fix CI out of memory and add PyTorch1.9 Python3.9 unit tests (#5862) +- Fix download links error of some model (#6069) +- Improve the generalization of XML dataset (#5943) +- Polish assertion error messages (#6017) +- Remove `opencv-python-headless` dependency by `albumentations` (#5868) +- Check dtype in transform unit tests (#5969) +- Replace the default theme of documentation with PyTorch Sphinx Theme (#6146) +- Update the paper and code fields in the metafile (#6043) +- Support to customize padding value of segmentation map (#6152) +- Support to resize multiple segmentation maps (#5747) + +#### Contributors + +A total of 24 developers contributed to this release. +Thanks @morkovka1337, @HarborYuan, @guillaumefrd, @guigarfr, @www516717402, @gaotongxiao, @ypwhs, @MartaYang, @shinya7y, @justiceeem, @zhaojinjian0000, @VVsssssk, @aravind-anantha, @wangbo-zhao, @czczup, @whai362, @czczup, @marijnl, @AronLin, @BIGWangYuDong, @hhaAndroid, @jshilong, @RangiLyu, @ZwwWayne + +### v2.16.0 (30/8/2021) + +#### Highlights + +- Support [Panoptic FPN](https://arxiv.org/abs/1901.02446) and [Swin Transformer](https://arxiv.org/abs/2103.14030) + +#### New Features + +- Support [Panoptic FPN](https://arxiv.org/abs/1901.02446) and release models (#5577, #5902) +- Support Swin Transformer backbone (#5748) +- Release RetinaNet models pre-trained with multi-scale 3x schedule (#5636) +- Add script to convert unlabeled image list to coco format (#5643) +- Add hook to check whether the loss value is valid (#5674) +- Add YOLO anchor optimizing tool (#5644) +- Support export onnx models without post process. (#5851) +- Support classwise evaluation in CocoPanopticDataset (#5896) +- Adapt browse_dataset for concatenated datasets. (#5935) +- Add `PatchEmbed` and `PatchMerging` with `AdaptivePadding` (#5952) + +#### Bug Fixes + +- Fix unit tests of YOLOX (#5859) +- Fix lose randomness in `imshow_det_bboxes` (#5845) +- Make output result of `ImageToTensor` contiguous (#5756) +- Fix inference bug when calling `regress_by_class` in RoIHead in some cases (#5884) +- Fix bug in CIoU loss where alpha should not have gradient. (#5835) +- Fix the bug that `multiscale_output` is defined but not used in HRNet (#5887) +- Set the priority of EvalHook to LOW. (#5882) +- Fix a YOLOX bug when applying bbox rescaling in test mode (#5899) +- Fix mosaic coordinate error (#5947) +- Fix dtype of bbox in RandomAffine. (#5930) + +#### Improvements + +- Add Chinese version of `data_pipeline` and (#5662) +- Support to remove state dicts of EMA when publishing models. (#5858) +- Refactor the loss function in HTC and SCNet (#5881) +- Use warnings instead of logger.warning (#5540) +- Use legacy coordinate in metric of VOC (#5627) +- Add Chinese version of customize_losses (#5826) +- Add Chinese version of model_zoo (#5827) + +#### Contributors + +A total of 19 developers contributed to this release. +Thanks @ypwhs, @zywvvd, @collinzrj, @OceanPang, @ddonatien, @@haotian-liu, @viibridges, @Muyun99, @guigarfr, @zhaojinjian0000, @jbwang1997,@wangbo-zhao, @xvjiarui, @RangiLyu, @jshilong, @AronLin, @BIGWangYuDong, @hhaAndroid, @ZwwWayne + +### v2.15.1 (11/8/2021) + +#### Highlights + +- Support [YOLOX](https://arxiv.org/abs/2107.08430) + +#### New Features + +- Support [YOLOX](https://arxiv.org/abs/2107.08430)(#5756, #5758, #5760, #5767, #5770, #5774, #5777, #5808, #5828, #5848) + +#### Bug Fixes + +- Update correct SSD models. (#5789) +- Fix casting error in mask structure (#5820) +- Fix MMCV deployment documentation links. (#5790) + +#### Improvements + +- Use dynamic MMCV download link in TorchServe dockerfile (#5779) +- Rename the function `upsample_like` to `interpolate_as` for more general usage (#5788) + +#### Contributors + +A total of 14 developers contributed to this release. +Thanks @HAOCHENYE, @xiaohu2015, @HsLOL, @zhiqwang, @Adamdad, @shinya7y, @Johnson-Wang, @RangiLyu, @jshilong, @mmeendez8, @AronLin, @BIGWangYuDong, @hhaAndroid, @ZwwWayne + +### v2.15.0 (02/8/2021) + +#### Highlights + +- Support adding [MIM](https://github.com/open-mmlab/mim) dependencies during pip installation +- Support MobileNetV2 for SSD-Lite and YOLOv3 +- Support Chinese Documentation + +#### New Features + +- Add function `upsample_like` (#5732) +- Support to output pdf and epub format documentation (#5738) +- Support and release Cascade Mask R-CNN 3x pre-trained models (#5645) +- Add `ignore_index` to CrossEntropyLoss (#5646) +- Support adding [MIM](https://github.com/open-mmlab/mim) dependencies during pip installation (#5676) +- Add MobileNetV2 config and models for YOLOv3 (#5510) +- Support COCO Panoptic Dataset (#5231) +- Support ONNX export of cascade models (#5486) +- Support DropBlock with RetinaNet (#5544) +- Support MobileNetV2 SSD-Lite (#5526) + +#### Bug Fixes + +- Fix the device of label in multiclass_nms (#5673) +- Fix error of backbone initialization from pre-trained checkpoint in config file (#5603, #5550) +- Fix download links of RegNet pretrained weights (#5655) +- Fix two-stage runtime error given empty proposal (#5559) +- Fix flops count error in DETR (#5654) +- Fix unittest for `NumClassCheckHook` when it is not used. (#5626) +- Fix description bug of using custom dataset (#5546) +- Fix bug of `multiclass_nms` that returns the global indices (#5592) +- Fix `valid_mask` logic error in RPNHead (#5562) +- Fix unit test error of pretrained configs (#5561) +- Fix typo error in anchor_head.py (#5555) +- Fix bug when using dataset wrappers (#5552) +- Fix a typo error in demo/MMDet_Tutorial.ipynb (#5511) +- Fixing crash in `get_root_logger` when `cfg.log_level` is not None (#5521) +- Fix docker version (#5502) +- Fix optimizer parameter error when using `IterBasedRunner` (#5490) + +#### Improvements + +- Add unit tests for MMTracking (#5620) +- Add Chinese translation of documentation (#5718, #5618, #5558, #5423, #5593, #5421, #5408. #5369, #5419, #5530, #5531) +- Update resource limit (#5697) +- Update docstring for InstaBoost (#5640) +- Support key `reduction_override` in all loss functions (#5515) +- Use repeatdataset to accelerate CenterNet training (#5509) +- Remove unnecessary code in autoassign (#5519) +- Add documentation about `init_cfg` (#5273) + +#### Contributors + +A total of 18 developers contributed to this release. +Thanks @OceanPang, @AronLin, @hellock, @Outsider565, @RangiLyu, @ElectronicElephant, @likyoo, @BIGWangYuDong, @hhaAndroid, @noobying, @yyz561, @likyoo, +@zeakey, @ZwwWayne, @ChenyangLiu, @johnson-magic, @qingswu, @BuxianChen + +### v2.14.0 (29/6/2021) + +#### Highlights + +- Add `simple_test` to dense heads to improve the consistency of single-stage and two-stage detectors +- Revert the `test_mixins` to single image test to improve efficiency and readability +- Add Faster R-CNN and Mask R-CNN config using multi-scale training with 3x schedule + +#### New Features + +- Support pretrained models from MoCo v2 and SwAV (#5286) +- Add Faster R-CNN and Mask R-CNN config using multi-scale training with 3x schedule (#5179, #5233) +- Add `reduction_override` in MSELoss (#5437) +- Stable support of exporting DETR to ONNX with dynamic shapes and batch inference (#5168) +- Stable support of exporting PointRend to ONNX with dynamic shapes and batch inference (#5440) + +#### Bug Fixes + +- Fix size mismatch bug in `multiclass_nms` (#4980) +- Fix the import path of `MultiScaleDeformableAttention` (#5338) +- Fix errors in config of GCNet ResNext101 models (#5360) +- Fix Grid-RCNN error when there is no bbox result (#5357) +- Fix errors in `onnx_export` of bbox_head when setting reg_class_agnostic (#5468) +- Fix type error of AutoAssign in the document (#5478) +- Fix web links ending with `.md` (#5315) + +#### Improvements + +- Add `simple_test` to dense heads to improve the consistency of single-stage and two-stage detectors (#5264) +- Add support for mask diagonal flip in TTA (#5403) +- Revert the `test_mixins` to single image test to improve efficiency and readability (#5249) +- Make YOLOv3 Neck more flexible (#5218) +- Refactor SSD to make it more general (#5291) +- Refactor `anchor_generator` and `point_generator` (#5349) +- Allow to configure out the `mask_head` of the HTC algorithm (#5389) +- Delete deprecated warning in FPN (#5311) +- Move `model.pretrained` to `model.backbone.init_cfg` (#5370) +- Make deployment tools more friendly to use (#5280) +- Clarify installation documentation (#5316) +- Add ImageNet Pretrained Models docs (#5268) +- Add FAQ about training loss=nan solution and COCO AP or AR =-1 (# 5312, #5313) +- Change all weight links of http to https (#5328) + +### v2.13.0 (01/6/2021) + +#### Highlights + +- Support new methods: [CenterNet](https://arxiv.org/abs/1904.07850), [Seesaw Loss](https://arxiv.org/abs/2008.10032), [MobileNetV2](https://arxiv.org/abs/1801.04381) + +#### New Features + +- Support paper [Objects as Points](https://arxiv.org/abs/1904.07850) (#4602) +- Support paper [Seesaw Loss for Long-Tailed Instance Segmentation (CVPR 2021)](https://arxiv.org/abs/2008.10032) (#5128) +- Support [MobileNetV2](https://arxiv.org/abs/1801.04381) backbone and inverted residual block (#5122) +- Support [MIM](https://github.com/open-mmlab/mim) (#5143) +- ONNX exportation with dynamic shapes of CornerNet (#5136) +- Add `mask_soft` config option to allow non-binary masks (#4615) +- Add PWC metafile (#5135) + +#### Bug Fixes + +- Fix YOLOv3 FP16 training error (#5172) +- Fix Cacscade R-CNN TTA test error when `det_bboxes` length is 0 (#5221) +- Fix `iou_thr` variable naming errors in VOC recall calculation function (#5195) +- Fix Faster R-CNN performance dropped in ONNX Runtime (#5197) +- Fix DETR dict changed error when using python 3.8 during iteration (#5226) + +#### Improvements + +- Refactor ONNX export of two stage detector (#5205) +- Replace MMDetection's EvalHook with MMCV's EvalHook for consistency (#4806) +- Update RoI extractor for ONNX (#5194) +- Use better parameter initialization in YOLOv3 head for higher performance (#5181) +- Release new DCN models of Mask R-CNN by mixed-precision training (#5201) +- Update YOLOv3 model weights (#5229) +- Add DetectoRS ResNet-101 model weights (#4960) +- Discard bboxes with sizes equals to `min_bbox_size` (#5011) +- Remove duplicated code in DETR head (#5129) +- Remove unnecessary object in class definition (#5180) +- Fix doc link (#5192) + +### v2.12.0 (01/5/2021) + +#### Highlights + +- Support new methods: [AutoAssign](https://arxiv.org/abs/2007.03496), [YOLOF](https://arxiv.org/abs/2103.09460), and [Deformable DETR](https://arxiv.org/abs/2010.04159) +- Stable support of exporting models to ONNX with batched images and dynamic shape (#5039) + +#### Backwards Incompatible Changes + +MMDetection is going through big refactoring for more general and convenient usages during the releases from v2.12.0 to v2.15.0 (maybe longer). +In v2.12.0 MMDetection inevitably brings some BC-breakings, including the MMCV dependency, model initialization, model registry, and mask AP evaluation. + +- MMCV version. MMDetection v2.12.0 relies on the newest features in MMCV 1.3.3, including `BaseModule` for unified parameter initialization, model registry, and the CUDA operator `MultiScaleDeformableAttn` for [Deformable DETR](https://arxiv.org/abs/2010.04159). Note that MMCV 1.3.2 already contains all the features used by MMDet but has known issues. Therefore, we recommend users skip MMCV v1.3.2 and use v1.3.3, though v1.3.2 might work for most cases. +- Unified model initialization (#4750). To unify the parameter initialization in OpenMMLab projects, MMCV supports `BaseModule` that accepts `init_cfg` to allow the modules' parameters initialized in a flexible and unified manner. Now the users need to explicitly call `model.init_weights()` in the training script to initialize the model (as in [here](https://github.com/open-mmlab/mmdetection/blob/master/tools/train.py#L162), previously this was handled by the detector. The models in MMDetection have been re-benchmarked to ensure accuracy based on PR #4750. __The downstream projects should update their code accordingly to use MMDetection v2.12.0__. +- Unified model registry (#5059). To easily use backbones implemented in other OpenMMLab projects, MMDetection migrates to inherit the model registry created in MMCV (#760). In this way, as long as the backbone is supported in an OpenMMLab project and that project also uses the registry in MMCV, users can use that backbone in MMDetection by simply modifying the config without copying the code of that backbone into MMDetection. +- Mask AP evaluation (#4898). Previous versions calculate the areas of masks through the bounding boxes when calculating the mask AP of small, medium, and large instances. To indeed use the areas of masks, we pop the key `bbox` during mask AP calculation. This change does not affect the overall mask AP evaluation and aligns the mask AP of similar models in other projects like Detectron2. + +#### New Features + +- Support paper [AutoAssign: Differentiable Label Assignment for Dense Object Detection](https://arxiv.org/abs/2007.03496) (#4295) +- Support paper [You Only Look One-level Feature](https://arxiv.org/abs/2103.09460) (#4295) +- Support paper [Deformable DETR: Deformable Transformers for End-to-End Object Detection](https://arxiv.org/abs/2010.04159) (#4778) +- Support calculating IoU with FP16 tensor in `bbox_overlaps` to save memory and keep speed (#4889) +- Add `__repr__` in custom dataset to count the number of instances (#4756) +- Add windows support by updating requirements.txt (#5052) +- Stable support of exporting models to ONNX with batched images and dynamic shape, including SSD, FSAF,FCOS, YOLOv3, RetinaNet, Faster R-CNN, and Mask R-CNN (#5039) + +#### Improvements + +- Use MMCV `MODEL_REGISTRY` (#5059) +- Unified parameter initialization for more flexible usage (#4750) +- Rename variable names and fix docstring in anchor head (#4883) +- Support training with empty GT in Cascade RPN (#4928) +- Add more details of usage of `test_robustness` in documentation (#4917) +- Changing to use `pycocotools` instead of `mmpycocotools` to fully support Detectron2 and MMDetection in one environment (#4939) +- Update torch serve dockerfile to support dockers of more versions (#4954) +- Add check for training with single class dataset (#4973) +- Refactor transformer and DETR Head (#4763) +- Update FPG model zoo (#5079) +- More accurate mask AP of small/medium/large instances (#4898) + +#### Bug Fixes + +- Fix bug in mean_ap.py when calculating mAP by 11 points (#4875) +- Fix error when key `meta` is not in old checkpoints (#4936) +- Fix hanging bug when training with empty GT in VFNet, GFL, and FCOS by changing the place of `reduce_mean` (#4923, #4978, #5058) +- Fix asyncronized inference error and provide related demo (#4941) +- Fix IoU losses dimensionality unmatch error (#4982) +- Fix torch.randperm whtn using PyTorch 1.8 (#5014) +- Fix empty bbox error in `mask_head` when using CARAFE (#5062) +- Fix `supplement_mask` bug when there are zero-size RoIs (#5065) +- Fix testing with empty rois in RoI Heads (#5081) + +### v2.11.0 (01/4/2021) + +__Highlights__ + +- Support new method: [Localization Distillation for Object Detection](https://arxiv.org/pdf/2102.12252.pdf) +- Support Pytorch2ONNX with batch inference and dynamic shape + +__New Features__ + +- Support [Localization Distillation for Object Detection](https://arxiv.org/pdf/2102.12252.pdf) (#4758) +- Support Pytorch2ONNX with batch inference and dynamic shape for Faster-RCNN and mainstream one-stage detectors (#4796) + +__Improvements__ + +- Support batch inference in head of RetinaNet (#4699) +- Add batch dimension in second stage of Faster-RCNN (#4785) +- Support batch inference in bbox coder (#4721) +- Add check for `ann_ids` in `COCODataset` to ensure it is unique (#4789) +- support for showing the FPN results (#4716) +- support dynamic shape for grid_anchor (#4684) +- Move pycocotools version check to when it is used (#4880) + +__Bug Fixes__ + +- Fix a bug of TridentNet when doing the batch inference (#4717) +- Fix a bug of Pytorch2ONNX in FASF (#4735) +- Fix a bug when show the image with float type (#4732) + +### v2.10.0 (01/03/2021) + +#### Highlights + +- Support new methods: [FPG](https://arxiv.org/abs/2004.03580) +- Support ONNX2TensorRT for SSD, FSAF, FCOS, YOLOv3, and Faster R-CNN. + +#### New Features + +- Support ONNX2TensorRT for SSD, FSAF, FCOS, YOLOv3, and Faster R-CNN (#4569) +- Support [Feature Pyramid Grids (FPG)](https://arxiv.org/abs/2004.03580) (#4645) +- Support video demo (#4420) +- Add seed option for sampler (#4665) +- Support to customize type of runner (#4570, #4669) +- Support synchronizing BN buffer in `EvalHook` (#4582) +- Add script for GIF demo (#4573) + +#### Bug Fixes + +- Fix ConfigDict AttributeError and add Colab link (#4643) +- Avoid crash in empty gt training of GFL head (#4631) +- Fix `iou_thrs` bug in RPN evaluation (#4581) +- Fix syntax error of config when upgrading model version (#4584) + +#### Improvements + +- Refactor unit test file structures (#4600) +- Refactor nms config (#4636) +- Get loading pipeline by checking the class directly rather than through config strings (#4619) +- Add doctests for mask target generation and mask structures (#4614) +- Use deep copy when copying pipeline arguments (#4621) +- Update documentations (#4642, #4650, #4620, #4630) +- Remove redundant code calling `import_modules_from_strings` (#4601) +- Clean deprecated FP16 API (#4571) +- Check whether `CLASSES` is correctly initialized in the initialization of `XMLDataset` (#4555) +- Support batch inference in the inference API (#4462, #4526) +- Clean deprecated warning and fix 'meta' error (#4695) + +### v2.9.0 (01/02/2021) + +#### Highlights + +- Support new methods: [SCNet](https://arxiv.org/abs/2012.10150), [Sparse R-CNN](https://arxiv.org/abs/2011.12450) +- Move `train_cfg` and `test_cfg` into model in configs +- Support to visualize results based on prediction quality + +#### New Features + +- Support [SCNet](https://arxiv.org/abs/2012.10150) (#4356) +- Support [Sparse R-CNN](https://arxiv.org/abs/2011.12450) (#4219) +- Support evaluate mAP by multiple IoUs (#4398) +- Support concatenate dataset for testing (#4452) +- Support to visualize results based on prediction quality (#4441) +- Add ONNX simplify option to Pytorch2ONNX script (#4468) +- Add hook for checking compatibility of class numbers in heads and datasets (#4508) + +#### Bug Fixes + +- Fix CPU inference bug of Cascade RPN (#4410) +- Fix NMS error of CornerNet when there is no prediction box (#4409) +- Fix TypeError in CornerNet inference (#4411) +- Fix bug of PAA when training with background images (#4391) +- Fix the error that the window data is not destroyed when `out_file is not None` and `show==False` (#4442) +- Fix order of NMS `score_factor` that will decrease the performance of YOLOv3 (#4473) +- Fix bug in HTC TTA when the number of detection boxes is 0 (#4516) +- Fix resize error in mask data structures (#4520) + +#### Improvements + +- Allow to customize classes in LVIS dataset (#4382) +- Add tutorials for building new models with existing datasets (#4396) +- Add CPU compatibility information in documentation (#4405) +- Add documentation of deprecated `ImageToTensor` for batch inference (#4408) +- Add more details in documentation for customizing dataset (#4430) +- Switch `imshow_det_bboxes` visualization backend from OpenCV to Matplotlib (#4389) +- Deprecate `ImageToTensor` in `image_demo.py` (#4400) +- Move train_cfg/test_cfg into model (#4347, #4489) +- Update docstring for `reg_decoded_bbox` option in bbox heads (#4467) +- Update dataset information in documentation (#4525) +- Release pre-trained R50 and R101 PAA detectors with multi-scale 3x training schedules (#4495) +- Add guidance for speed benchmark (#4537) + +### v2.8.0 (04/01/2021) + +#### Highlights + +- Support new methods: [Cascade RPN](https://arxiv.org/abs/1909.06720), [TridentNet](https://arxiv.org/abs/1901.01892) + +#### New Features + +- Support [Cascade RPN](https://arxiv.org/abs/1909.06720) (#1900) +- Support [TridentNet](https://arxiv.org/abs/1901.01892) (#3313) + +#### Bug Fixes + +- Fix bug of show result in async_benchmark (#4367) +- Fix scale factor in MaskTestMixin (#4366) +- Fix but when returning indices in `multiclass_nms` (#4362) +- Fix bug of empirical attention in resnext backbone error (#4300) +- Fix bug of `img_norm_cfg` in FCOS-HRNet models with updated performance and models (#4250) +- Fix invalid checkpoint and log in Mask R-CNN models on Cityscapes dataset (#4287) +- Fix bug in distributed sampler when dataset is too small (#4257) +- Fix bug of 'PAFPN has no attribute extra_convs_on_inputs' (#4235) + +#### Improvements + +- Update model url from aws to aliyun (#4349) +- Update ATSS for PyTorch 1.6+ (#4359) +- Update script to install ruby in pre-commit installation (#4360) +- Delete deprecated `mmdet.ops` (#4325) +- Refactor hungarian assigner for more general usage in Sparse R-CNN (#4259) +- Handle scipy import in DETR to reduce package dependencies (#4339) +- Update documentation of usages for config options after MMCV (1.2.3) supports overriding list in config (#4326) +- Update pre-train models of faster rcnn trained on COCO subsets (#4307) +- Avoid zero or too small value for beta in Dynamic R-CNN (#4303) +- Add doccumentation for Pytorch2ONNX (#4271) +- Add deprecated warning FPN arguments (#4264) +- Support returning indices of kept bboxes when using nms (#4251) +- Update type and device requirements when creating tensors `GFLHead` (#4210) +- Update device requirements when creating tensors in `CrossEntropyLoss` (#4224) + +### v2.7.0 (30/11/2020) + +- Support new method: [DETR](https://arxiv.org/abs/2005.12872), [ResNest](https://arxiv.org/abs/2004.08955), Faster R-CNN DC5. +- Support YOLO, Mask R-CNN, and Cascade R-CNN models exportable to ONNX. + +#### New Features + +- Support [DETR](https://arxiv.org/abs/2005.12872) (#4201, #4206) +- Support to link the best checkpoint in training (#3773) +- Support to override config through options in inference.py (#4175) +- Support YOLO, Mask R-CNN, and Cascade R-CNN models exportable to ONNX (#4087, #4083) +- Support [ResNeSt](https://arxiv.org/abs/2004.08955) backbone (#2959) +- Support unclip border bbox regression (#4076) +- Add tpfp func in evaluating AP (#4069) +- Support mixed precision training of SSD detector with other backbones (#4081) +- Add Faster R-CNN DC5 models (#4043) + +#### Bug Fixes + +- Fix bug of `gpu_id` in distributed training mode (#4163) +- Support Albumentations with version higher than 0.5 (#4032) +- Fix num_classes bug in faster rcnn config (#4088) +- Update code in docs/2_new_data_model.md (#4041) + +#### Improvements + +- Ensure DCN offset to have similar type as features in VFNet (#4198) +- Add config links in README files of models (#4190) +- Add tutorials for loss conventions (#3818) +- Add solution to installation issues in 30-series GPUs (#4176) +- Update docker version in get_started.md (#4145) +- Add model statistics and polish some titles in configs README (#4140) +- Clamp neg probability in FreeAnchor (#4082) +- Speed up expanding large images (#4089) +- Fix Pytorch 1.7 incompatibility issues (#4103) +- Update trouble shooting page to resolve segmentation fault (#4055) +- Update aLRP-Loss in project page (#4078) +- Clean duplicated `reduce_mean` function (#4056) +- Refactor Q&A (#4045) + +### v2.6.0 (1/11/2020) + +- Support new method: [VarifocalNet](https://arxiv.org/abs/2008.13367). +- Refactored documentation with more tutorials. + +#### New Features + +- Support GIoU calculation in `BboxOverlaps2D`, and re-implement `giou_loss` using `bbox_overlaps` (#3936) +- Support random sampling in CPU mode (#3948) +- Support VarifocalNet (#3666, #4024) + +#### Bug Fixes + +- Fix SABL validating bug in Cascade R-CNN (#3913) +- Avoid division by zero in PAA head when num_pos=0 (#3938) +- Fix temporary directory bug of multi-node testing error (#4034, #4017) +- Fix `--show-dir` option in test script (#4025) +- Fix GA-RetinaNet r50 model url (#3983) +- Update code in docs and fix broken urls (#3947) + +#### Improvements + +- Refactor pytorch2onnx API into `mmdet.core.export` and use `generate_inputs_and_wrap_model` for pytorch2onnx (#3857, #3912) +- Update RPN upgrade scripts for v2.5.0 compatibility (#3986) +- Use mmcv `tensor2imgs` (#4010) +- Update test robustness (#4000) +- Update trouble shooting page (#3994) +- Accelerate PAA training speed (#3985) +- Support batch_size > 1 in validation (#3966) +- Use RoIAlign implemented in MMCV for inference in CPU mode (#3930) +- Documentation refactoring (#4031) + +### v2.5.0 (5/10/2020) + +#### Highlights + +- Support new methods: [YOLACT](https://arxiv.org/abs/1904.02689), [CentripetalNet](https://arxiv.org/abs/2003.09119). +- Add more documentations for easier and more clear usage. + +#### Backwards Incompatible Changes + +__FP16 related methods are imported from mmcv instead of mmdet. (#3766, #3822)__ +Mixed precision training utils in `mmdet.core.fp16` are moved to `mmcv.runner`, including `force_fp32`, `auto_fp16`, `wrap_fp16_model`, and `Fp16OptimizerHook`. A deprecation warning will be raised if users attempt to import those methods from `mmdet.core.fp16`, and will be finally removed in V2.10.0. + +__\[0, N-1\] represents foreground classes and N indicates background classes for all models. (#3221)__ +Before v2.5.0, the background label for RPN is 0, and N for other heads. Now the behavior is consistent for all models. Thus `self.background_labels` in `dense_heads` is removed and all heads use `self.num_classes` to indicate the class index of background labels. +This change has no effect on the pre-trained models in the v2.x model zoo, but will affect the training of all models with RPN heads. Two-stage detectors whose RPN head uses softmax will be affected because the order of categories is changed. + +**Only call `get_subset_by_classes` when `test_mode=True` and `self.filter_empty_gt=True` (#3695)** +Function `get_subset_by_classes` in dataset is refactored and only filters out images when `test_mode=True` and `self.filter_empty_gt=True`. +In the original implementation, `get_subset_by_classes` is not related to the flag `self.filter_empty_gt` and will only be called when the classes is set during initialization no matter `test_mode` is `True` or `False`. This brings ambiguous behavior and potential bugs in many cases. After v2.5.0, if `filter_empty_gt=False`, no matter whether the classes are specified in a dataset, the dataset will use all the images in the annotations. If `filter_empty_gt=True` and `test_mode=True`, no matter whether the classes are specified, the dataset will call \`\`get_subset_by_classes\` to check the images and filter out images containing no GT boxes. Therefore, the users should be responsible for the data filtering/cleaning process for the test dataset. + +#### New Features + +- Test time augmentation for single stage detectors (#3844, #3638) +- Support to show the name of experiments during training (#3764) +- Add `Shear`, `Rotate`, `Translate` Augmentation (#3656, #3619, #3687) +- Add image-only transformations including `Constrast`, `Equalize`, `Color`, and `Brightness`. (#3643) +- Support [YOLACT](https://arxiv.org/abs/1904.02689) (#3456) +- Support [CentripetalNet](https://arxiv.org/abs/2003.09119) (#3390) +- Support PyTorch 1.6 in docker (#3905) + +#### Bug Fixes + +- Fix the bug of training ATSS when there is no ground truth boxes (#3702) +- Fix the bug of using Focal Loss when there is `num_pos` is 0 (#3702) +- Fix the label index mapping in dataset browser (#3708) +- Fix Mask R-CNN training stuck problem when their is no positive rois (#3713) +- Fix the bug of `self.rpn_head.test_cfg` in `RPNTestMixin` by using `self.rpn_head` in rpn head (#3808) +- Fix deprecated `Conv2d` from mmcv.ops (#3791) +- Fix device bug in RepPoints (#3836) +- Fix SABL validating bug (#3849) +- Use `https://download.openmmlab.com/mmcv/dist/index.html` for installing MMCV (#3840) +- Fix nonzero in NMS for PyTorch 1.6.0 (#3867) +- Fix the API change bug of PAA (#3883) +- Fix typo in bbox_flip (#3886) +- Fix cv2 import error of ligGL.so.1 in Dockerfile (#3891) + +#### Improvements + +- Change to use `mmcv.utils.collect_env` for collecting environment information to avoid duplicate codes (#3779) +- Update checkpoint file names to v2.0 models in documentation (#3795) +- Update tutorials for changing runtime settings (#3778), modifying loss (#3777) +- Improve the function of `simple_test_bboxes` in SABL (#3853) +- Convert mask to bool before using it as img's index for robustness and speedup (#3870) +- Improve documentation of modules and dataset customization (#3821) + +### v2.4.0 (5/9/2020) + +__Highlights__ + +- Fix lots of issues/bugs and reorganize the trouble shooting page +- Support new methods [SABL](https://arxiv.org/abs/1912.04260), [YOLOv3](https://arxiv.org/abs/1804.02767), and [PAA Assign](https://arxiv.org/abs/2007.08103) +- Support Batch Inference +- Start to publish `mmdet` package to PyPI since v2.3.0 +- Switch model zoo to download.openmmlab.com + +__Backwards Incompatible Changes__ + +- Support Batch Inference (#3564, #3686, #3705): Since v2.4.0, MMDetection could inference model with multiple images in a single GPU. + This change influences all the test APIs in MMDetection and downstream codebases. To help the users migrate their code, we use `replace_ImageToTensor` (#3686) to convert legacy test data pipelines during dataset initialization. +- Support RandomFlip with horizontal/vertical/diagonal direction (#3608): Since v2.4.0, MMDetection supports horizontal/vertical/diagonal flip in the data augmentation. This influences bounding box, mask, and image transformations in data augmentation process and the process that will map those data back to the original format. +- Migrate to use `mmlvis` and `mmpycocotools` for COCO and LVIS dataset (#3727). The APIs are fully compatible with the original `lvis` and `pycocotools`. Users need to uninstall the existing pycocotools and lvis packages in their environment first and install `mmlvis` & `mmpycocotools`. + +__Bug Fixes__ + +- Fix default mean/std for onnx (#3491) +- Fix coco evaluation and add metric items (#3497) +- Fix typo for install.md (#3516) +- Fix atss when sampler per gpu is 1 (#3528) +- Fix import of fuse_conv_bn (#3529) +- Fix bug of gaussian_target, update unittest of heatmap (#3543) +- Fixed VOC2012 evaluate (#3553) +- Fix scale factor bug of rescale (#3566) +- Fix with_xxx_attributes in base detector (#3567) +- Fix boxes scaling when number is 0 (#3575) +- Fix rfp check when neck config is a list (#3591) +- Fix import of fuse conv bn in benchmark.py (#3606) +- Fix webcam demo (#3634) +- Fix typo and itemize issues in tutorial (#3658) +- Fix error in distributed training when some levels of FPN are not assigned with bounding boxes (#3670) +- Fix the width and height orders of stride in valid flag generation (#3685) +- Fix weight initialization bug in Res2Net DCN (#3714) +- Fix bug in OHEMSampler (#3677) + +__New Features__ + +- Support Cutout augmentation (#3521) +- Support evaluation on multiple datasets through ConcatDataset (#3522) +- Support [PAA assign](https://arxiv.org/abs/2007.08103) #(3547) +- Support eval metric with pickle results (#3607) +- Support [YOLOv3](https://arxiv.org/abs/1804.02767) (#3083) +- Support [SABL](https://arxiv.org/abs/1912.04260) (#3603) +- Support to publish to Pypi in github-action (#3510) +- Support custom imports (#3641) + +__Improvements__ + +- Refactor common issues in documentation (#3530) +- Add pytorch 1.6 to CI config (#3532) +- Add config to runner meta (#3534) +- Add eval-option flag for testing (#3537) +- Add init_eval to evaluation hook (#3550) +- Add include_bkg in ClassBalancedDataset (#3577) +- Using config's loading in inference_detector (#3611) +- Add ATSS ResNet-101 models in model zoo (#3639) +- Update urls to download.openmmlab.com (#3665) +- Support non-mask training for CocoDataset (#3711) + +### v2.3.0 (5/8/2020) + +__Highlights__ + +- The CUDA/C++ operators have been moved to `mmcv.ops`. For backward compatibility `mmdet.ops` is kept as warppers of `mmcv.ops`. +- Support new methods [CornerNet](https://arxiv.org/abs/1808.01244), [DIOU](https://arxiv.org/abs/1911.08287)/[CIOU](https://arxiv.org/abs/2005.03572) loss, and new dataset: [LVIS V1](https://arxiv.org/abs/1908.03195) +- Provide more detailed colab training tutorials and more complete documentation. +- Support to convert RetinaNet from Pytorch to ONNX. + +__Bug Fixes__ + +- Fix the model initialization bug of DetectoRS (#3187) +- Fix the bug of module names in NASFCOSHead (#3205) +- Fix the filename bug in publish_model.py (#3237) +- Fix the dimensionality bug when `inside_flags.any()` is `False` in dense heads (#3242) +- Fix the bug of forgetting to pass flip directions in `MultiScaleFlipAug` (#3262) +- Fixed the bug caused by default value of `stem_channels` (#3333) +- Fix the bug of model checkpoint loading for CPU inference (#3318, #3316) +- Fix topk bug when box number is smaller than the expected topk number in ATSSAssigner (#3361) +- Fix the gt priority bug in center_region_assigner.py (#3208) +- Fix NaN issue of iou calculation in iou_loss.py (#3394) +- Fix the bug that `iou_thrs` is not actually used during evaluation in coco.py (#3407) +- Fix test-time augmentation of RepPoints (#3435) +- Fix runtimeError caused by incontiguous tensor in Res2Net+DCN (#3412) + +__New Features__ + +- Support [CornerNet](https://arxiv.org/abs/1808.01244) (#3036) +- Support [DIOU](https://arxiv.org/abs/1911.08287)/[CIOU](https://arxiv.org/abs/2005.03572) loss (#3151) +- Support [LVIS V1](https://arxiv.org/abs/1908.03195) dataset (#) +- Support customized hooks in training (#3395) +- Support fp16 training of generalized focal loss (#3410) +- Support to convert RetinaNet from Pytorch to ONNX (#3075) + +__Improvements__ + +- Support to process ignore boxes in ATSS assigner (#3082) +- Allow to crop images without ground truth in `RandomCrop` (#3153) +- Enable the the `Accuracy` module to set threshold (#3155) +- Refactoring unit tests (#3206) +- Unify the training settings of `to_float32` and `norm_cfg` in RegNets configs (#3210) +- Add colab training tutorials for beginners (#3213, #3273) +- Move CUDA/C++ operators into `mmcv.ops` and keep `mmdet.ops` as warppers for backward compatibility (#3232)(#3457) +- Update installation scripts in documentation (#3290) and dockerfile (#3320) +- Support to set image resize backend (#3392) +- Remove git hash in version file (#3466) +- Check mmcv version to force version compatibility (#3460) + +### v2.2.0 (1/7/2020) + +__Highlights__ + +- Support new methods: [DetectoRS](https://arxiv.org/abs/2006.02334), [PointRend](https://arxiv.org/abs/1912.08193), [Generalized Focal Loss](https://arxiv.org/abs/2006.04388), [Dynamic R-CNN](https://arxiv.org/abs/2004.06002) + +__Bug Fixes__ + +- Fix FreeAnchor when no gt in image (#3176) +- Clean up deprecated usage of `register_module()` (#3092, #3161) +- Fix pretrain bug in NAS FCOS (#3145) +- Fix `num_classes` in SSD (#3142) +- Fix FCOS warmup (#3119) +- Fix `rstrip` in `tools/publish_model.py` +- Fix `flip_ratio` default value in RandomFLip pipeline (#3106) +- Fix cityscapes eval with ms_rcnn (#3112) +- Fix RPN softmax (#3056) +- Fix filename of LVIS@v0.5 (#2998) +- Fix nan loss by filtering out-of-frame gt_bboxes in COCO (#2999) +- Fix bug in FSAF (#3018) +- Add FocalLoss `num_classes` check (#2964) +- Fix PISA Loss when there are no gts (#2992) +- Avoid nan in `iou_calculator` (#2975) +- Prevent possible bugs in loading and transforms caused by shallow copy (#2967) + +__New Features__ + +- Add DetectoRS (#3064) +- Support Generalize Focal Loss (#3097) +- Support PointRend (#2752) +- Support Dynamic R-CNN (#3040) +- Add DeepFashion dataset (#2968) +- Implement FCOS training tricks (#2935) +- Use BaseDenseHead as base class for anchor-base heads (#2963) +- Add `with_cp` for BasicBlock (#2891) +- Add `stem_channels` argument for ResNet (#2954) + +__Improvements__ + +- Add anchor free base head (#2867) +- Migrate to github action (#3137) +- Add docstring for datasets, pipelines, core modules and methods (#3130, #3125, #3120) +- Add VOC benchmark (#3060) +- Add `concat` mode in GRoI (#3098) +- Remove cmd arg `autorescale-lr` (#3080) +- Use `len(data['img_metas'])` to indicate `num_samples` (#3073, #3053) +- Switch to EpochBasedRunner (#2976) + +### v2.1.0 (8/6/2020) + +__Highlights__ + +- Support new backbones: [RegNetX](https://arxiv.org/abs/2003.13678), [Res2Net](https://arxiv.org/abs/1904.01169) +- Support new methods: [NASFCOS](https://arxiv.org/abs/1906.04423), [PISA](https://arxiv.org/abs/1904.04821), [GRoIE](https://arxiv.org/abs/2004.13665) +- Support new dataset: [LVIS](https://arxiv.org/abs/1908.03195) + +__Bug Fixes__ + +- Change the CLI argument `--validate` to `--no-validate` to enable validation after training epochs by default. (#2651) +- Add missing cython to docker file (#2713) +- Fix bug in nms cpu implementation (#2754) +- Fix bug when showing mask results (#2763) +- Fix gcc requirement (#2806) +- Fix bug in async test (#2820) +- Fix mask encoding-decoding bugs in test API (#2824) +- Fix bug in test time augmentation (#2858, #2921, #2944) +- Fix a typo in comment of apis/train (#2877) +- Fix the bug of returning None when no gt bboxes are in the original image in `RandomCrop`. Fix the bug that misses to handle `gt_bboxes_ignore`, `gt_label_ignore`, and `gt_masks_ignore` in `RandomCrop`, `MinIoURandomCrop` and `Expand` modules. (#2810) +- Fix bug of `base_channels` of regnet (#2917) +- Fix the bug of logger when loading pre-trained weights in base detector (#2936) + +__New Features__ + +- Add IoU models (#2666) +- Add colab demo for inference +- Support class agnostic nms (#2553) +- Add benchmark gathering scripts for development only (#2676) +- Add mmdet-based project links (#2736, #2767, #2895) +- Add config dump in training (#2779) +- Add ClassBalancedDataset (#2721) +- Add res2net backbone (#2237) +- Support RegNetX models (#2710) +- Use `mmcv.FileClient` to support different storage backends (#2712) +- Add ClassBalancedDataset (#2721) +- Code Release: Prime Sample Attention in Object Detection (CVPR 2020) (#2626) +- Implement NASFCOS (#2682) +- Add class weight in CrossEntropyLoss (#2797) +- Support LVIS dataset (#2088) +- Support GRoIE (#2584) + +__Improvements__ + +- Allow different x and y strides in anchor heads. (#2629) +- Make FSAF loss more robust to no gt (#2680) +- Compute pure inference time instead (#2657) and update inference speed (#2730) +- Avoided the possibility that a patch with 0 area is cropped. (#2704) +- Add warnings when deprecated `imgs_per_gpu` is used. (#2700) +- Add a mask rcnn example for config (#2645) +- Update model zoo (#2762, #2866, #2876, #2879, #2831) +- Add `ori_filename` to img_metas and use it in test show-dir (#2612) +- Use `img_fields` to handle multiple images during image transform (#2800) +- Add upsample_cfg support in FPN (#2787) +- Add `['img']` as default `img_fields` for back compatibility (#2809) +- Rename the pretrained model from `open-mmlab://resnet50_caffe` and `open-mmlab://resnet50_caffe_bgr` to `open-mmlab://detectron/resnet50_caffe` and `open-mmlab://detectron2/resnet50_caffe`. (#2832) +- Added sleep(2) in test.py to reduce hanging problem (#2847) +- Support `c10::half` in CARAFE (#2890) +- Improve documentations (#2918, #2714) +- Use optimizer constructor in mmcv and clean the original implementation in `mmdet.core.optimizer` (#2947) + +### v2.0.0 (6/5/2020) + +In this release, we made lots of major refactoring and modifications. + +1. __Faster speed__. We optimize the training and inference speed for common models, achieving up to 30% speedup for training and 25% for inference. Please refer to [model zoo](model_zoo.md#comparison-with-detectron2) for details. + +2. __Higher performance__. We change some default hyperparameters with no additional cost, which leads to a gain of performance for most models. Please refer to [compatibility](compatibility.md#training-hyperparameters) for details. + +3. __More documentation and tutorials__. We add a bunch of documentation and tutorials to help users get started more smoothly. Read it [here](https://mmdetection.readthedocs.io/en/latest/). + +4. __Support PyTorch 1.5__. The support for 1.1 and 1.2 is dropped, and we switch to some new APIs. + +5. __Better configuration system__. Inheritance is supported to reduce the redundancy of configs. + +6. __Better modular design__. Towards the goal of simplicity and flexibility, we simplify some encapsulation while add more other configurable modules like BBoxCoder, IoUCalculator, OptimizerConstructor, RoIHead. Target computation is also included in heads and the call hierarchy is simpler. + +7. Support new methods: [FSAF](https://arxiv.org/abs/1903.00621) and PAFPN (part of [PAFPN](https://arxiv.org/abs/1803.01534)). + +__Breaking Changes__ +Models training with MMDetection 1.x are not fully compatible with 2.0, please refer to the [compatibility doc](compatibility.md) for the details and how to migrate to the new version. + +__Improvements__ + +- Unify cuda and cpp API for custom ops. (#2277) +- New config files with inheritance. (#2216) +- Encapsulate the second stage into RoI heads. (#1999) +- Refactor GCNet/EmpericalAttention into plugins. (#2345) +- Set low quality match as an option in IoU-based bbox assigners. (#2375) +- Change the codebase's coordinate system. (#2380) +- Refactor the category order in heads. 0 means the first positive class instead of background now. (#2374) +- Add bbox sampler and assigner registry. (#2419) +- Speed up the inference of RPN. (#2420) +- Add `train_cfg` and `test_cfg` as class members in all anchor heads. (#2422) +- Merge target computation methods into heads. (#2429) +- Add bbox coder to support different bbox encoding and losses. (#2480) +- Unify the API for regression loss. (#2156) +- Refactor Anchor Generator. (#2474) +- Make `lr` an optional argument for optimizers. (#2509) +- Migrate to modules and methods in MMCV. (#2502, #2511, #2569, #2572) +- Support PyTorch 1.5. (#2524) +- Drop the support for Python 3.5 and use F-string in the codebase. (#2531) + +__Bug Fixes__ + +- Fix the scale factors for resized images without keep the aspect ratio. (#2039) +- Check if max_num > 0 before slicing in NMS. (#2486) +- Fix Deformable RoIPool when there is no instance. (#2490) +- Fix the default value of assigned labels. (#2536) +- Fix the evaluation of Cityscapes. (#2578) + +__New Features__ + +- Add deep_stem and avg_down option to ResNet, i.e., support ResNetV1d. (#2252) +- Add L1 loss. (#2376) +- Support both polygon and bitmap for instance masks. (#2353, #2540) +- Support CPU mode for inference. (#2385) +- Add optimizer constructor for complicated configuration of optimizers. (#2397, #2488) +- Implement PAFPN. (#2392) +- Support empty tensor input for some modules. (#2280) +- Support for custom dataset classes without overriding it. (#2408, #2443) +- Support to train subsets of coco dataset. (#2340) +- Add iou_calculator to potentially support more IoU calculation methods. (2405) +- Support class wise mean AP (was removed in the last version). (#2459) +- Add option to save the testing result images. (#2414) +- Support MomentumUpdaterHook. (#2571) +- Add a demo to inference a single image. (#2605) + +### v1.1.0 (24/2/2020) + +__Highlights__ + +- Dataset evaluation is rewritten with a unified api, which is used by both evaluation hooks and test scripts. +- Support new methods: [CARAFE](https://arxiv.org/abs/1905.02188). + +__Breaking Changes__ + +- The new MMDDP inherits from the official DDP, thus the `__init__` api is changed to be the same as official DDP. +- The `mask_head` field in HTC config files is modified. +- The evaluation and testing script is updated. +- In all transforms, instance masks are stored as a numpy array shaped (n, h, w) instead of a list of (h, w) arrays, where n is the number of instances. + +__Bug Fixes__ + +- Fix IOU assigners when ignore_iof_thr > 0 and there is no pred boxes. (#2135) +- Fix mAP evaluation when there are no ignored boxes. (#2116) +- Fix the empty RoI input for Deformable RoI Pooling. (#2099) +- Fix the dataset settings for multiple workflows. (#2103) +- Fix the warning related to `torch.uint8` in PyTorch 1.4. (#2105) +- Fix the inference demo on devices other than gpu:0. (#2098) +- Fix Dockerfile. (#2097) +- Fix the bug that `pad_val` is unused in Pad transform. (#2093) +- Fix the albumentation transform when there is no ground truth bbox. (#2032) + +__Improvements__ + +- Use torch instead of numpy for random sampling. (#2094) +- Migrate to the new MMDDP implementation in MMCV v0.3. (#2090) +- Add meta information in logs. (#2086) +- Rewrite Soft NMS with pytorch extension and remove cython as a dependency. (#2056) +- Rewrite dataset evaluation. (#2042, #2087, #2114, #2128) +- Use numpy array for masks in transforms. (#2030) + +__New Features__ + +- Implement "CARAFE: Content-Aware ReAssembly of FEatures". (#1583) +- Add `worker_init_fn()` in data_loader when seed is set. (#2066, #2111) +- Add logging utils. (#2035) + +### v1.0.0 (30/1/2020) + +This release mainly improves the code quality and add more docstrings. + +__Highlights__ + +- Documentation is online now: . +- Support new models: [ATSS](https://arxiv.org/abs/1912.02424). +- DCN is now available with the api `build_conv_layer` and `ConvModule` like the normal conv layer. +- A tool to collect environment information is available for trouble shooting. + +__Bug Fixes__ + +- Fix the incompatibility of the latest numpy and pycocotools. (#2024) +- Fix the case when distributed package is unavailable, e.g., on Windows. (#1985) +- Fix the dimension issue for `refine_bboxes()`. (#1962) +- Fix the typo when `seg_prefix` is a list. (#1906) +- Add segmentation map cropping to RandomCrop. (#1880) +- Fix the return value of `ga_shape_target_single()`. (#1853) +- Fix the loaded shape of empty proposals. (#1819) +- Fix the mask data type when using albumentation. (#1818) + +__Improvements__ + +- Enhance AssignResult and SamplingResult. (#1995) +- Add ability to overwrite existing module in Registry. (#1982) +- Reorganize requirements and make albumentations and imagecorruptions optional. (#1969) +- Check NaN in `SSDHead`. (#1935) +- Encapsulate the DCN in ResNe(X)t into a ConvModule & Conv_layers. (#1894) +- Refactoring for mAP evaluation and support multiprocessing and logging. (#1889) +- Init the root logger before constructing Runner to log more information. (#1865) +- Split `SegResizeFlipPadRescale` into different existing transforms. (#1852) +- Move `init_dist()` to MMCV. (#1851) +- Documentation and docstring improvements. (#1971, #1938, #1869, #1838) +- Fix the color of the same class for mask visualization. (#1834) +- Remove the option `keep_all_stages` in HTC and Cascade R-CNN. (#1806) + +__New Features__ + +- Add two test-time options `crop_mask` and `rle_mask_encode` for mask heads. (#2013) +- Support loading grayscale images as single channel. (#1975) +- Implement "Bridging the Gap Between Anchor-based and Anchor-free Detection via Adaptive Training Sample Selection". (#1872) +- Add sphinx generated docs. (#1859, #1864) +- Add GN support for flops computation. (#1850) +- Collect env info for trouble shooting. (#1812) + +### v1.0rc1 (13/12/2019) + +The RC1 release mainly focuses on improving the user experience, and fixing bugs. + +__Highlights__ + +- Support new models: [FoveaBox](https://arxiv.org/abs/1904.03797), [RepPoints](https://arxiv.org/abs/1904.11490) and [FreeAnchor](https://arxiv.org/abs/1909.02466). +- Add a Dockerfile. +- Add a jupyter notebook demo and a webcam demo. +- Setup the code style and CI. +- Add lots of docstrings and unit tests. +- Fix lots of bugs. + +__Breaking Changes__ + +- There was a bug for computing COCO-style mAP w.r.t different scales (AP_s, AP_m, AP_l), introduced by #621. (#1679) + +__Bug Fixes__ + +- Fix a sampling interval bug in Libra R-CNN. (#1800) +- Fix the learning rate in SSD300 WIDER FACE. (#1781) +- Fix the scaling issue when `keep_ratio=False`. (#1730) +- Fix typos. (#1721, #1492, #1242, #1108, #1107) +- Fix the shuffle argument in `build_dataloader`. (#1693) +- Clip the proposal when computing mask targets. (#1688) +- Fix the "index out of range" bug for samplers in some corner cases. (#1610, #1404) +- Fix the NMS issue on devices other than GPU:0. (#1603) +- Fix SSD Head and GHM Loss on CPU. (#1578) +- Fix the OOM error when there are too many gt bboxes. (#1575) +- Fix the wrong keyword argument `nms_cfg` in HTC. (#1573) +- Process masks and semantic segmentation in Expand and MinIoUCrop transforms. (#1550, #1361) +- Fix a scale bug in the Non Local op. (#1528) +- Fix a bug in transforms when `gt_bboxes_ignore` is None. (#1498) +- Fix a bug when `img_prefix` is None. (#1497) +- Pass the device argument to `grid_anchors` and `valid_flags`. (#1478) +- Fix the data pipeline for test_robustness. (#1476) +- Fix the argument type of deformable pooling. (#1390) +- Fix the coco_eval when there are only two classes. (#1376) +- Fix a bug in Modulated DeformableConv when deformable_group>1. (#1359) +- Fix the mask cropping in RandomCrop. (#1333) +- Fix zero outputs in DeformConv when not running on cuda:0. (#1326) +- Fix the type issue in Expand. (#1288) +- Fix the inference API. (#1255) +- Fix the inplace operation in Expand. (#1249) +- Fix the from-scratch training config. (#1196) +- Fix inplace add in RoIExtractor which cause an error in PyTorch 1.2. (#1160) +- Fix FCOS when input images has no positive sample. (#1136) +- Fix recursive imports. (#1099) + +__Improvements__ + +- Print the config file and mmdet version in the log. (#1721) +- Lint the code before compiling in travis CI. (#1715) +- Add a probability argument for the `Expand` transform. (#1651) +- Update the PyTorch and CUDA version in the docker file. (#1615) +- Raise a warning when specifying `--validate` in non-distributed training. (#1624, #1651) +- Beautify the mAP printing. (#1614) +- Add pre-commit hook. (#1536) +- Add the argument `in_channels` to backbones. (#1475) +- Add lots of docstrings and unit tests, thanks to [@Erotemic](https://github.com/Erotemic). (#1603, #1517, #1506, #1505, #1491, #1479, #1477, #1475, #1474) +- Add support for multi-node distributed test when there is no shared storage. (#1399) +- Optimize Dockerfile to reduce the image size. (#1306) +- Update new results of HRNet. (#1284, #1182) +- Add an argument `no_norm_on_lateral` in FPN. (#1240) +- Test the compiling in CI. (#1235) +- Move docs to a separate folder. (#1233) +- Add a jupyter notebook demo. (#1158) +- Support different type of dataset for training. (#1133) +- Use int64_t instead of long in cuda kernels. (#1131) +- Support unsquare RoIs for bbox and mask heads. (#1128) +- Manually add type promotion to make compatible to PyTorch 1.2. (#1114) +- Allowing validation dataset for computing validation loss. (#1093) +- Use `.scalar_type()` instead of `.type()` to suppress some warnings. (#1070) + +__New Features__ + +- Add an option `--with_ap` to compute the AP for each class. (#1549) +- Implement "FreeAnchor: Learning to Match Anchors for Visual Object Detection". (#1391) +- Support [Albumentations](https://github.com/albumentations-team/albumentations) for augmentations in the data pipeline. (#1354) +- Implement "FoveaBox: Beyond Anchor-based Object Detector". (#1339) +- Support horizontal and vertical flipping. (#1273, #1115) +- Implement "RepPoints: Point Set Representation for Object Detection". (#1265) +- Add test-time augmentation to HTC and Cascade R-CNN. (#1251) +- Add a COCO result analysis tool. (#1228) +- Add Dockerfile. (#1168) +- Add a webcam demo. (#1155, #1150) +- Add FLOPs counter. (#1127) +- Allow arbitrary layer order for ConvModule. (#1078) + +### v1.0rc0 (27/07/2019) + +- Implement lots of new methods and components (Mixed Precision Training, HTC, Libra R-CNN, Guided Anchoring, Empirical Attention, Mask Scoring R-CNN, Grid R-CNN (Plus), GHM, GCNet, FCOS, HRNet, Weight Standardization, etc.). Thank all collaborators! +- Support two additional datasets: WIDER FACE and Cityscapes. +- Refactoring for loss APIs and make it more flexible to adopt different losses and related hyper-parameters. +- Speed up multi-gpu testing. +- Integrate all compiling and installing in a single script. + +### v0.6.0 (14/04/2019) + +- Up to 30% speedup compared to the model zoo. +- Support both PyTorch stable and nightly version. +- Replace NMS and SigmoidFocalLoss with Pytorch CUDA extensions. + +### v0.6rc0(06/02/2019) + +- Migrate to PyTorch 1.0. + +### v0.5.7 (06/02/2019) + +- Add support for Deformable ConvNet v2. (Many thanks to the authors and [@chengdazhi](https://github.com/chengdazhi)) +- This is the last release based on PyTorch 0.4.1. + +### v0.5.6 (17/01/2019) + +- Add support for Group Normalization. +- Unify RPNHead and single stage heads (RetinaHead, SSDHead) with AnchorHead. + +### v0.5.5 (22/12/2018) + +- Add SSD for COCO and PASCAL VOC. +- Add ResNeXt backbones and detection models. +- Refactoring for Samplers/Assigners and add OHEM. +- Add VOC dataset and evaluation scripts. + +### v0.5.4 (27/11/2018) + +- Add SingleStageDetector and RetinaNet. + +### v0.5.3 (26/11/2018) + +- Add Cascade R-CNN and Cascade Mask R-CNN. +- Add support for Soft-NMS in config files. + +### v0.5.2 (21/10/2018) + +- Add support for custom datasets. +- Add a script to convert PASCAL VOC annotations to the expected format. + +### v0.5.1 (20/10/2018) + +- Add BBoxAssigner and BBoxSampler, the `train_cfg` field in config files are restructured. +- `ConvFCRoIHead` / `SharedFCRoIHead` are renamed to `ConvFCBBoxHead` / `SharedFCBBoxHead` for consistency. diff --git a/mmdetection/docs/en/notes/compatibility.md b/mmdetection/docs/en/notes/compatibility.md new file mode 100644 index 00000000..26325e24 --- /dev/null +++ b/mmdetection/docs/en/notes/compatibility.md @@ -0,0 +1,178 @@ +# Compatibility of MMDetection 2.x + +## MMDetection 2.25.0 + +In order to support Mask2Former for instance segmentation, the original config files of Mask2Former for panpotic segmentation need to be renamed [PR #7571](https://github.com/open-mmlab/mmdetection/pull/7571). + + + + + + + + + + + +
    before v2.25.0after v2.25.0
    + +``` +'mask2former_xxx_coco.py' represents config files for **panoptic segmentation**. +``` + + + +``` +'mask2former_xxx_coco.py' represents config files for **instance segmentation**. +'mask2former_xxx_coco-panoptic.py' represents config files for **panoptic segmentation**. +``` + +
    + +## MMDetection 2.21.0 + +In order to support CPU training, the logic of scatter in batch collating has been changed. We recommend to use +MMCV v1.4.4 or higher. For more details, please refer to [MMCV PR #1621](https://github.com/open-mmlab/mmcv/pull/1621). + +## MMDetection 2.18.1 + +### MMCV compatibility + +In order to fix the wrong weight reference bug in BaseTransformerLayer, the logic in batch first mode of MultiheadAttention has been changed. +We recommend to use MMCV v1.3.17 or higher. For more details, please refer to [MMCV PR #1418](https://github.com/open-mmlab/mmcv/pull/1418). + +## MMDetection 2.18.0 + +### DIIHead compatibility + +In order to support QueryInst, attn_feats is added into the returned tuple of DIIHead. + +## MMDetection 2.14.0 + +### MMCV Version + +In order to fix the problem that the priority of EvalHook is too low, all hook priorities have been re-adjusted in 1.3.8, so MMDetection 2.14.0 needs to rely on the latest MMCV 1.3.8 version. For related information, please refer to [#1120](https://github.com/open-mmlab/mmcv/pull/1120), for related issues, please refer to [#5343](https://github.com/open-mmlab/mmdetection/issues/5343). + +### SSD compatibility + +In v2.14.0, to make SSD more flexible to use, [PR5291](https://github.com/open-mmlab/mmdetection/pull/5291) refactored its backbone, neck and head. The users can use the script `tools/model_converters/upgrade_ssd_version.py` to convert their models. + +```bash +python tools/model_converters/upgrade_ssd_version.py ${OLD_MODEL_PATH} ${NEW_MODEL_PATH} +``` + +- OLD_MODEL_PATH: the path to load the old version SSD model. +- NEW_MODEL_PATH: the path to save the converted model weights. + +## MMDetection 2.12.0 + +MMDetection is going through big refactoring for more general and convenient usages during the releases from v2.12.0 to v2.18.0 (maybe longer). +In v2.12.0 MMDetection inevitably brings some BC-breakings, including the MMCV dependency, model initialization, model registry, and mask AP evaluation. + +### MMCV Version + +MMDetection v2.12.0 relies on the newest features in MMCV 1.3.3, including `BaseModule` for unified parameter initialization, model registry, and the CUDA operator `MultiScaleDeformableAttn` for [Deformable DETR](https://arxiv.org/abs/2010.04159). Note that MMCV 1.3.2 already contains all the features used by MMDet but has known issues. Therefore, we recommend users to skip MMCV v1.3.2 and use v1.3.2, though v1.3.2 might work for most of the cases. + +### Unified model initialization + +To unify the parameter initialization in OpenMMLab projects, MMCV supports `BaseModule` that accepts `init_cfg` to allow the modules' parameters initialized in a flexible and unified manner. Now the users need to explicitly call `model.init_weights()` in the training script to initialize the model (as in [here](https://github.com/open-mmlab/mmdetection/blob/main/tools/train.py#L162), previously this was handled by the detector. **The downstream projects must update their model initialization accordingly to use MMDetection v2.12.0**. Please refer to PR #4750 for details. + +### Unified model registry + +To easily use backbones implemented in other OpenMMLab projects, MMDetection v2.12.0 inherits the model registry created in MMCV (#760). In this way, as long as the backbone is supported in an OpenMMLab project and that project also uses the registry in MMCV, users can use that backbone in MMDetection by simply modifying the config without copying the code of that backbone into MMDetection. Please refer to PR #5059 for more details. + +### Mask AP evaluation + +Before [PR 4898](https://github.com/open-mmlab/mmdetection/pull/4898) and V2.12.0, the mask AP of small, medium, and large instances is calculated based on the bounding box area rather than the real mask area. This leads to higher `APs` and `APm` but lower `APl` but will not affect the overall mask AP. [PR 4898](https://github.com/open-mmlab/mmdetection/pull/4898) change it to use mask areas by deleting `bbox` in mask AP calculation. +The new calculation does not affect the overall mask AP evaluation and is consistent with [Detectron2](https://github.com/facebookresearch/detectron2/). + +## Compatibility with MMDetection 1.x + +MMDetection 2.0 goes through a big refactoring and addresses many legacy issues. It is not compatible with the 1.x version, i.e., running inference with the same model weights in these two versions will produce different results. Thus, MMDetection 2.0 re-benchmarks all the models and provides their links and logs in the model zoo. + +The major differences are in four folds: coordinate system, codebase conventions, training hyperparameters, and modular design. + +### Coordinate System + +The new coordinate system is consistent with [Detectron2](https://github.com/facebookresearch/detectron2/) and treats the center of the most left-top pixel as (0, 0) rather than the left-top corner of that pixel. +Accordingly, the system interprets the coordinates in COCO bounding box and segmentation annotations as coordinates in range `[0, width]` or `[0, height]`. +This modification affects all the computation related to the bbox and pixel selection, +which is more natural and accurate. + +- The height and width of a box with corners (x1, y1) and (x2, y2) in the new coordinate system is computed as `width = x2 - x1` and `height = y2 - y1`. + In MMDetection 1.x and previous version, a "+ 1" was added both height and width. + This modification are in three folds: + + 1. Box transformation and encoding/decoding in regression. + 2. IoU calculation. This affects the matching process between ground truth and bounding box and the NMS process. The effect to compatibility is very negligible, though. + 3. The corners of bounding box is in float type and no longer quantized. This should provide more accurate bounding box results. This also makes the bounding box and RoIs not required to have minimum size of 1, whose effect is small, though. + +- The anchors are center-aligned to feature grid points and in float type. + In MMDetection 1.x and previous version, the anchors are in `int` type and not center-aligned. + This affects the anchor generation in RPN and all the anchor-based methods. + +- ROIAlign is better aligned with the image coordinate system. The new implementation is adopted from [Detectron2](https://github.com/facebookresearch/detectron2/tree/master/detectron2/layers/csrc/ROIAlign). + The RoIs are shifted by half a pixel by default when they are used to cropping RoI features, compared to MMDetection 1.x. + The old behavior is still available by setting `aligned=False` instead of `aligned=True`. + +- Mask cropping and pasting are more accurate. + + 1. We use the new RoIAlign to crop mask targets. In MMDetection 1.x, the bounding box is quantized before it is used to crop mask target, and the crop process is implemented by numpy. In new implementation, the bounding box for crop is not quantized and sent to RoIAlign. This implementation accelerates the training speed by a large margin (~0.1s per iter, ~2 hour when training Mask R50 for 1x schedule) and should be more accurate. + + 2. In MMDetection 2.0, the "`paste_mask()`" function is different and should be more accurate than those in previous versions. This change follows the modification in [Detectron2](https://github.com/facebookresearch/detectron2/blob/master/detectron2/structures/masks.py) and can improve mask AP on COCO by ~0.5% absolute. + +### Codebase Conventions + +- MMDetection 2.0 changes the order of class labels to reduce unused parameters in regression and mask branch more naturally (without +1 and -1). + This effect all the classification layers of the model to have a different ordering of class labels. The final layers of regression branch and mask head no longer keep K+1 channels for K categories, and their class orders are consistent with the classification branch. + + - In MMDetection 2.0, label "K" means background, and labels \[0, K-1\] correspond to the K = num_categories object categories. + + - In MMDetection 1.x and previous version, label "0" means background, and labels \[1, K\] correspond to the K categories. + + - **Note**: The class order of softmax RPN is still the same as that in 1.x in versions\<=2.4.0 while sigmoid RPN is not affected. The class orders in all heads are unified since MMDetection v2.5.0. + +- Low quality matching in R-CNN is not used. In MMDetection 1.x and previous versions, the `max_iou_assigner` will match low quality boxes for each ground truth box in both RPN and R-CNN training. We observe this sometimes does not assign the most perfect GT box to some bounding boxes, + thus MMDetection 2.0 do not allow low quality matching by default in R-CNN training in the new system. This sometimes may slightly improve the box AP (~0.1% absolute). + +- Separate scale factors for width and height. In MMDetection 1.x and previous versions, the scale factor is a single float in mode `keep_ratio=True`. This is slightly inaccurate because the scale factors for width and height have slight difference. MMDetection 2.0 adopts separate scale factors for width and height, the improvement on AP ~0.1% absolute. + +- Configs name conventions are changed. MMDetection V2.0 adopts the new name convention to maintain the gradually growing model zoo as the following: + + ```shell + [model]_(model setting)_[backbone]_[neck]_(norm setting)_(misc)_(gpu x batch)_[schedule]_[dataset].py, + ``` + + where the (`misc`) includes DCN and GCBlock, etc. More details are illustrated in the [documentation for config](tutorials/config) + +- MMDetection V2.0 uses new ResNet Caffe backbones to reduce warnings when loading pre-trained models. Most of the new backbones' weights are the same as the former ones but do not have `conv.bias`, except that they use a different `img_norm_cfg`. Thus, the new backbone will not cause warning of unexpected keys. + +### Training Hyperparameters + +The change in training hyperparameters does not affect +model-level compatibility but slightly improves the performance. The major ones are: + +- The number of proposals after nms is changed from 2000 to 1000 by setting `nms_post=1000` and `max_num=1000`. + This slightly improves both mask AP and bbox AP by ~0.2% absolute. + +- The default box regression losses for Mask R-CNN, Faster R-CNN and RetinaNet are changed from smooth L1 Loss to L1 loss. This leads to an overall improvement in box AP (~0.6% absolute). However, using L1-loss for other methods such as Cascade R-CNN and HTC does not improve the performance, so we keep the original settings for these methods. + +- The sample num of RoIAlign layer is set to be 0 for simplicity. This leads to slightly improvement on mask AP (~0.2% absolute). + +- The default setting does not use gradient clipping anymore during training for faster training speed. This does not degrade performance of the most of models. For some models such as RepPoints we keep using gradient clipping to stabilize the training process and to obtain better performance. + +- The default warmup ratio is changed from 1/3 to 0.001 for a more smooth warming up process since the gradient clipping is usually not used. The effect is found negligible during our re-benchmarking, though. + +### Upgrade Models from 1.x to 2.0 + +To convert the models trained by MMDetection V1.x to MMDetection V2.0, the users can use the script `tools/model_converters/upgrade_model_version.py` to convert +their models. The converted models can be run in MMDetection V2.0 with slightly dropped performance (less than 1% AP absolute). +Details can be found in `configs/legacy`. + +## pycocotools compatibility + +`mmpycocotools` is the OpenMMlab's fork of official `pycocotools`, which works for both MMDetection and Detectron2. +Before [PR 4939](https://github.com/open-mmlab/mmdetection/pull/4939), since `pycocotools` and `mmpycocotool` have the same package name, if users already installed `pycocotools` (installed Detectron2 first under the same environment), then the setup of MMDetection will skip installing `mmpycocotool`. Thus MMDetection fails due to the missing `mmpycocotools`. +If MMDetection is installed before Detectron2, they could work under the same environment. +[PR 4939](https://github.com/open-mmlab/mmdetection/pull/4939) deprecates mmpycocotools in favor of official pycocotools. +Users may install MMDetection and Detectron2 under the same environment after [PR 4939](https://github.com/open-mmlab/mmdetection/pull/4939), no matter what the installation order is. diff --git a/mmdetection/docs/en/notes/contribution_guide.md b/mmdetection/docs/en/notes/contribution_guide.md new file mode 100644 index 00000000..d622c0ab --- /dev/null +++ b/mmdetection/docs/en/notes/contribution_guide.md @@ -0,0 +1 @@ +# Contribution diff --git a/mmdetection/docs/en/notes/faq.md b/mmdetection/docs/en/notes/faq.md new file mode 100644 index 00000000..f1a176e4 --- /dev/null +++ b/mmdetection/docs/en/notes/faq.md @@ -0,0 +1,241 @@ +# Frequently Asked Questions + +We list some common troubles faced by many users and their corresponding solutions here. Feel free to enrich the list if you find any frequent issues and have ways to help others to solve them. If the contents here do not cover your issue, please create an issue using the [provided templates](https://github.com/open-mmlab/mmdetection/blob/main/.github/ISSUE_TEMPLATE/error-report.md/) and make sure you fill in all required information in the template. + +## PyTorch 2.0 Support + +The vast majority of algorithms in MMDetection now support PyTorch 2.0 and its `torch.compile` function. Users only need to install MMDetection 3.0.0rc7 or later versions to enjoy this feature. If any unsupported algorithms are found during use, please feel free to give us feedback. We also welcome contributions from the community to benchmark the speed improvement brought by using the `torch.compile` function. + +To enable the `torch.compile` function, simply add `--cfg-options compile=True` after `train.py` or `test.py`. For example, to enable `torch.compile` for RTMDet, you can use the following command: + +```shell +# Single GPU +python tools/train.py configs/rtmdet/rtmdet_s_8xb32-300e_coco.py --cfg-options compile=True + +# Single node multiple GPUs +./tools/dist_train.sh configs/rtmdet/rtmdet_s_8xb32-300e_coco.py 8 --cfg-options compile=True + +# Single node multiple GPUs + AMP +./tools/dist_train.sh configs/rtmdet/rtmdet_s_8xb32-300e_coco.py 8 --cfg-options compile=True --amp +``` + +It is important to note that PyTorch 2.0's support for dynamic shapes is not yet fully developed. In most object detection algorithms, not only are the input shapes dynamic, but the loss calculation and post-processing parts are also dynamic. This can lead to slower training speeds when using the `torch.compile` function. Therefore, if you wish to enable the `torch.compile` function, you should follow these principles: + +1. Input images to the network are fixed shape, not multi-scale +2. set `torch._dynamo.config.cache_size_limit` parameter. TorchDynamo will convert and cache the Python bytecode, and the compiled functions will be stored in the cache. When the next check finds that the function needs to be recompiled, the function will be recompiled and cached. However, if the number of recompilations exceeds the maximum value set (64), the function will no longer be cached or recompiled. As mentioned above, the loss calculation and post-processing parts of the object detection algorithm are also dynamically calculated, and these functions need to be recompiled every time. Therefore, setting the `torch._dynamo.config.cache_size_limit` parameter to a smaller value can effectively reduce the compilation time + +In MMDetection, you can set the `torch._dynamo.config.cache_size_limit` parameter through the environment variable `DYNAMO_CACHE_SIZE_LIMIT`. For example, the command is as follows: + +```shell +# Single GPU +export DYNAMO_CACHE_SIZE_LIMIT = 4 +python tools/train.py configs/rtmdet/rtmdet_s_8xb32-300e_coco.py --cfg-options compile=True + +# Single node multiple GPUs +export DYNAMO_CACHE_SIZE_LIMIT = 4 +./tools/dist_train.sh configs/rtmdet/rtmdet_s_8xb32-300e_coco.py 8 --cfg-options compile=True +``` + +About the common questions about PyTorch 2.0's dynamo, you can refer to [here](https://pytorch.org/docs/stable/dynamo/faq.html) + +## Installation + +Compatibility issue between MMCV and MMDetection; "ConvWS is already registered in conv layer"; "AssertionError: MMCV==xxx is used but incompatible. Please install mmcv>=xxx, \<=xxx." + +Compatible MMDetection, MMEngine, and MMCV versions are shown as below. Please choose the correct version of MMCV to avoid installation issues. + +| MMDetection version | MMCV version | MMEngine version | +| :-----------------: | :---------------------: | :----------------------: | +| main | mmcv>=2.0.0, \<2.2.0 | mmengine>=0.7.1, \<1.0.0 | +| 3.3.0 | mmcv>=2.0.0, \<2.2.0 | mmengine>=0.7.1, \<1.0.0 | +| 3.2.0 | mmcv>=2.0.0, \<2.2.0 | mmengine>=0.7.1, \<1.0.0 | +| 3.1.0 | mmcv>=2.0.0, \<2.1.0 | mmengine>=0.7.1, \<1.0.0 | +| 3.0.0 | mmcv>=2.0.0, \<2.1.0 | mmengine>=0.7.1, \<1.0.0 | +| 3.0.0rc6 | mmcv>=2.0.0rc4, \<2.1.0 | mmengine>=0.6.0, \<1.0.0 | +| 3.0.0rc5 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.3.0, \<1.0.0 | +| 3.0.0rc4 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.3.0, \<1.0.0 | +| 3.0.0rc3 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.3.0, \<1.0.0 | +| 3.0.0rc2 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.1.0, \<1.0.0 | +| 3.0.0rc1 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.1.0, \<1.0.0 | +| 3.0.0rc0 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.1.0, \<1.0.0 | + +**Note:** + +1. If you want to install mmdet-v2.x, the compatible MMDetection and MMCV versions table can be found at [here](https://mmdetection.readthedocs.io/en/stable/faq.html#installation). Please choose the correct version of MMCV to avoid installation issues. +2. In MMCV-v2.x, `mmcv-full` is rename to `mmcv`, if you want to install `mmcv` without CUDA ops, you can install `mmcv-lite`. + +- "No module named 'mmcv.ops'"; "No module named 'mmcv.\_ext'". + + 1. Uninstall existing `mmcv-lite` in the environment using `pip uninstall mmcv-lite`. + 2. Install `mmcv` following the [installation instruction](https://mmcv.readthedocs.io/en/2.x/get_started/installation.html). + +- "Microsoft Visual C++ 14.0 or graeter is required" during installation on Windows. + + This error happens when building the 'pycocotools.\_mask' extension of pycocotools and the environment lacks corresponding C++ compilation dependencies. You need to download it at Microsoft officials [visual-cpp-build-tools](https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/), select the "Use C ++ Desktop Development" option to install the minimum dependencies, and then reinstall pycocotools. + +- Using Albumentations + + If you would like to use `albumentations`, we suggest using `pip install -r requirements/albu.txt` or + `pip install -U albumentations --no-binary qudida,albumentations`. + If you simply use `pip install albumentations>=0.3.2`, it will install `opencv-python-headless` simultaneously (even though you have already installed `opencv-python`). + Please refer to the [official documentation](https://albumentations.ai/docs/getting_started/installation/#note-on-opencv-dependencies) for details. + +- ModuleNotFoundError is raised when using some algorithms + + Some extra dependencies are required for Instaboost, Panoptic Segmentation, LVIS dataset, etc. Please note the error message and install corresponding packages, e.g., + + ```shell + # for instaboost + pip install instaboostfast + # for panoptic segmentation + pip install git+https://github.com/cocodataset/panopticapi.git + # for LVIS dataset + pip install git+https://github.com/lvis-dataset/lvis-api.git + ``` + +## Coding + +- Do I need to reinstall mmdet after some code modifications + + If you follow the best practice and install mmdet with `pip install -e .`, any local modifications made to the code will take effect without reinstallation. + +- How to develop with multiple MMDetection versions + + You can have multiple folders like mmdet-3.0, mmdet-3.1. + When you run the train or test script, it will adopt the mmdet package in the current folder. + + To use the default MMDetection installed in the environment rather than the one you are working with, you can remove the following line in those scripts: + + ```shell + PYTHONPATH="$(dirname $0)/..":$PYTHONPATH + ``` + +## PyTorch/CUDA Environment + +- "RTX 30 series card fails when building MMCV or MMDet" + + 1. Temporary work-around: do `MMCV_WITH_OPS=1 MMCV_CUDA_ARGS='-gencode=arch=compute_80,code=sm_80' pip install -e .`. + The common issue is `nvcc fatal : Unsupported gpu architecture 'compute_86'`. This means that the compiler should optimize for sm_86, i.e., nvidia 30 series card, but such optimizations have not been supported by CUDA toolkit 11.0. + This work-around modifies the compile flag by adding `MMCV_CUDA_ARGS='-gencode=arch=compute_80,code=sm_80'`, which tells `nvcc` to optimize for **sm_80**, i.e., Nvidia A100. Although A100 is different from the 30 series card, they use similar ampere architecture. This may hurt the performance but it works. + 2. PyTorch developers have updated that the default compiler flags should be fixed by [pytorch/pytorch#47585](https://github.com/pytorch/pytorch/pull/47585). So using PyTorch-nightly may also be able to solve the problem, though we have not tested it yet. + +- "invalid device function" or "no kernel image is available for execution". + + 1. Check if your cuda runtime version (under `/usr/local/`), `nvcc --version` and `conda list cudatoolkit` version match. + 2. Run `python mmdet/utils/collect_env.py` to check whether PyTorch, torchvision, and MMCV are built for the correct GPU architecture. + You may need to set `TORCH_CUDA_ARCH_LIST` to reinstall MMCV. + The GPU arch table could be found [here](https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#gpu-feature-list), + i.e. run `TORCH_CUDA_ARCH_LIST=7.0 pip install mmcv` to build MMCV for Volta GPUs. + The compatibility issue could happen when using old GPUS, e.g., Tesla K80 (3.7) on colab. + 3. Check whether the running environment is the same as that when mmcv/mmdet has compiled. + For example, you may compile mmcv using CUDA 10.0 but run it on CUDA 9.0 environments. + +- "undefined symbol" or "cannot open xxx.so". + + 1. If those symbols are CUDA/C++ symbols (e.g., libcudart.so or GLIBCXX), check whether the CUDA/GCC runtimes are the same as those used for compiling mmcv, + i.e. run `python mmdet/utils/collect_env.py` to see if `"MMCV Compiler"`/`"MMCV CUDA Compiler"` is the same as `"GCC"`/`"CUDA_HOME"`. + 2. If those symbols are PyTorch symbols (e.g., symbols containing caffe, aten, and TH), check whether the PyTorch version is the same as that used for compiling mmcv. + 3. Run `python mmdet/utils/collect_env.py` to check whether PyTorch, torchvision, and MMCV are built by and running on the same environment. + +- setuptools.sandbox.UnpickleableException: DistutilsSetupError("each element of 'ext_modules' option must be an Extension instance or 2-tuple") + + 1. If you are using miniconda rather than anaconda, check whether Cython is installed as indicated in [#3379](https://github.com/open-mmlab/mmdetection/issues/3379). + You need to manually install Cython first and then run command `pip install -r requirements.txt`. + 2. You may also need to check the compatibility between the `setuptools`, `Cython`, and `PyTorch` in your environment. + +- "Segmentation fault". + + 1. Check you GCC version and use GCC 5.4. This usually caused by the incompatibility between PyTorch and the environment (e.g., GCC \< 4.9 for PyTorch). We also recommend the users to avoid using GCC 5.5 because many feedbacks report that GCC 5.5 will cause "segmentation fault" and simply changing it to GCC 5.4 could solve the problem. + + 2. Check whether PyTorch is correctly installed and could use CUDA op, e.g. type the following command in your terminal. + + ```shell + python -c 'import torch; print(torch.cuda.is_available())' + ``` + + And see whether they could correctly output results. + + 3. If Pytorch is correctly installed, check whether MMCV is correctly installed. + + ```shell + python -c 'import mmcv; import mmcv.ops' + ``` + + If MMCV is correctly installed, then there will be no issue of the above two commands. + + 4. If MMCV and Pytorch is correctly installed, you man use `ipdb`, `pdb` to set breakpoints or directly add 'print' in mmdetection code and see which part leads the segmentation fault. + +## Training + +- "Loss goes Nan" + + 1. Check if the dataset annotations are valid: zero-size bounding boxes will cause the regression loss to be Nan due to the commonly used transformation for box regression. Some small size (width or height are smaller than 1) boxes will also cause this problem after data augmentation (e.g., instaboost). So check the data and try to filter out those zero-size boxes and skip some risky augmentations on the small-size boxes when you face the problem. + 2. Reduce the learning rate: the learning rate might be too large due to some reasons, e.g., change of batch size. You can rescale them to the value that could stably train the model. + 3. Extend the warmup iterations: some models are sensitive to the learning rate at the start of the training. You can extend the warmup iterations, e.g., change the `warmup_iters` from 500 to 1000 or 2000. + 4. Add gradient clipping: some models requires gradient clipping to stabilize the training process. The default of `grad_clip` is `None`, you can add gradient clippint to avoid gradients that are too large, i.e., set `optim_wrapper=dict(clip_grad=dict(max_norm=35, norm_type=2))` in your config file. + +- "GPU out of memory" + + 1. There are some scenarios when there are large amount of ground truth boxes, which may cause OOM during target assignment. You can set `gpu_assign_thr=N` in the config of assigner thus the assigner will calculate box overlaps through CPU when there are more than N GT boxes. + + 2. Set `with_cp=True` in the backbone. This uses the sublinear strategy in PyTorch to reduce GPU memory cost in the backbone. + + 3. Try mixed precision training using following the examples in `config/fp16`. The `loss_scale` might need further tuning for different models. + + 4. Try to use `AvoidCUDAOOM` to avoid GPU out of memory. It will first retry after calling `torch.cuda.empty_cache()`. If it still fails, it will then retry by converting the type of inputs to FP16 format. If it still fails, it will try to copy inputs from GPUs to CPUs to continue computing. Try AvoidOOM in you code to make the code continue to run when GPU memory runs out: + + ```python + from mmdet.utils import AvoidCUDAOOM + + output = AvoidCUDAOOM.retry_if_cuda_oom(some_function)(input1, input2) + ``` + + You can also try `AvoidCUDAOOM` as a decorator to make the code continue to run when GPU memory runs out: + + ```python + from mmdet.utils import AvoidCUDAOOM + + @AvoidCUDAOOM.retry_if_cuda_oom + def function(*args, **kwargs): + ... + return xxx + ``` + +- "RuntimeError: Expected to have finished reduction in the prior iteration before starting a new one" + + 1. This error indicates that your module has parameters that were not used in producing loss. This phenomenon may be caused by running different branches in your code in DDP mode. + 2. You can set `find_unused_parameters = True` in the config to solve the above problems, but this will slow down the training speed. + 3. You can set `detect_anomalous_params = True` in the config or `model_wrapper_cfg = dict(type='MMDistributedDataParallel', detect_anomalous_params=True)` (More details please refer to [MMEngine](https://github.com/open-mmlab/mmengine/blob/main/mmengine/model/wrappers/distributed.py#L91)) to get the name of those unused parameters. Note `detect_anomalous_params = True` will slow down the training speed, so it is recommended for debugging only. + +- Save the best model + + It can be turned on by configuring `default_hooks = dict(checkpoint=dict(type='CheckpointHook', interval=1, save_best='auto'),`. In the case of the `auto` parameter, the first key in the returned evaluation result will be used as the basis for selecting the best model. You can also directly set the key in the evaluation result to manually set it, for example, `save_best='coco/bbox_mAP'`. + +## Evaluation + +- COCO Dataset, AP or AR = -1 + 1. According to the definition of COCO dataset, the small and medium areas in an image are less than 1024 (32\*32), 9216 (96\*96), respectively. + 2. If the corresponding area has no object, the result of AP and AR will set to -1. + +## Model + +- `style` in ResNet + + The `style` parameter in ResNet allows either `pytorch` or `caffe` style. It indicates the difference in the Bottleneck module. Bottleneck is a stacking structure of `1x1-3x3-1x1` convolutional layers. In the case of `caffe` mode, the convolution layer with `stride=2` is the first `1x1` convolution, while in `pyorch` mode, it is the second `3x3` convolution has `stride=2`. A sample code is as below: + + ```python + if self.style == 'pytorch': + self.conv1_stride = 1 + self.conv2_stride = stride + else: + self.conv1_stride = stride + self.conv2_stride = 1 + ``` + +- ResNeXt parameter description + + ResNeXt comes from the paper [`Aggregated Residual Transformations for Deep Neural Networks`](https://arxiv.org/abs/1611.05431). It introduces group and uses “cardinality” to control the number of groups to achieve a balance between accuracy and complexity. It controls the basic width and grouping parameters of the internal Bottleneck module through two hyperparameters `baseWidth` and `cardinality`. An example configuration name in MMDetection is `mask_rcnn_x101_64x4d_fpn_mstrain-poly_3x_coco.py`, where `mask_rcnn` represents the algorithm using Mask R-CNN, `x101` represents the backbone network using ResNeXt-101, and `64x4d` represents that the bottleneck block has 64 group and each group has basic width of 4. + +- `norm_eval` in backbone + + Since the detection model is usually large and the input image resolution is high, this will result in a small batch of the detection model, which will make the variance of the statistics calculated by BatchNorm during the training process very large and not as stable as the statistics obtained during the pre-training of the backbone network . Therefore, the `norm_eval=True` mode is generally used in training, and the BatchNorm statistics in the pre-trained backbone network are directly used. The few algorithms that use large batches are the `norm_eval=False` mode, such as NASFPN. For the backbone network without ImageNet pre-training and the batch is relatively small, you can consider using `SyncBN`. diff --git a/mmdetection/docs/en/notes/projects.md b/mmdetection/docs/en/notes/projects.md new file mode 100644 index 00000000..3123e2b0 --- /dev/null +++ b/mmdetection/docs/en/notes/projects.md @@ -0,0 +1,57 @@ +# Projects based on MMDetection + +There are many projects built upon MMDetection. +We list some of them as examples of how to extend MMDetection for your own projects. +As the page might not be completed, please feel free to create a PR to update this page. + +## Projects as an extension + +Some projects extend the boundary of MMDetection for deployment or other research fields. +They reveal the potential of what MMDetection can do. We list several of them as below. + +- [OTEDetection](https://github.com/opencv/mmdetection): OpenVINO training extensions for object detection. +- [MMDetection3d](https://github.com/open-mmlab/mmdetection3d): OpenMMLab's next-generation platform for general 3D object detection. + +## Projects of papers + +There are also projects released with papers. +Some of the papers are published in top-tier conferences (CVPR, ICCV, and ECCV), the others are also highly influential. +To make this list also a reference for the community to develop and compare new object detection algorithms, we list them following the time order of top-tier conferences. +Methods already supported and maintained by MMDetection are not listed. + +- Involution: Inverting the Inherence of Convolution for Visual Recognition, CVPR21. [\[paper\]](https://arxiv.org/abs/2103.06255)[\[github\]](https://github.com/d-li14/involution) +- Multiple Instance Active Learning for Object Detection, CVPR 2021. [\[paper\]](https://openaccess.thecvf.com/content/CVPR2021/papers/Yuan_Multiple_Instance_Active_Learning_for_Object_Detection_CVPR_2021_paper.pdf)[\[github\]](https://github.com/yuantn/MI-AOD) +- Adaptive Class Suppression Loss for Long-Tail Object Detection, CVPR 2021. [\[paper\]](https://arxiv.org/abs/2104.00885)[\[github\]](https://github.com/CASIA-IVA-Lab/ACSL) +- Generalizable Pedestrian Detection: The Elephant In The Room, CVPR2021. [\[paper\]](https://arxiv.org/abs/2003.08799)[\[github\]](https://github.com/hasanirtiza/Pedestron) +- Group Fisher Pruning for Practical Network Compression, ICML2021. [\[paper\]](https://github.com/jshilong/FisherPruning/blob/main/resources/paper.pdf)[\[github\]](https://github.com/jshilong/FisherPruning) +- Overcoming Classifier Imbalance for Long-tail Object Detection with Balanced Group Softmax, CVPR2020. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2020/papers/Li_Overcoming_Classifier_Imbalance_for_Long-Tail_Object_Detection_With_Balanced_Group_CVPR_2020_paper.pdf)[\[github\]](https://github.com/FishYuLi/BalancedGroupSoftmax) +- Coherent Reconstruction of Multiple Humans from a Single Image, CVPR2020. [\[paper\]](https://jiangwenpl.github.io/multiperson/)[\[github\]](https://github.com/JiangWenPL/multiperson) +- Look-into-Object: Self-supervised Structure Modeling for Object Recognition, CVPR 2020. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2020/papers/Zhou_Look-Into-Object_Self-Supervised_Structure_Modeling_for_Object_Recognition_CVPR_2020_paper.pdf)[\[github\]](https://github.com/JDAI-CV/LIO) +- Video Panoptic Segmentation, CVPR2020. [\[paper\]](https://arxiv.org/abs/2006.11339)[\[github\]](https://github.com/mcahny/vps) +- D2Det: Towards High Quality Object Detection and Instance Segmentation, CVPR2020. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2020/html/Cao_D2Det_Towards_High_Quality_Object_Detection_and_Instance_Segmentation_CVPR_2020_paper.html)[\[github\]](https://github.com/JialeCao001/D2Det) +- CentripetalNet: Pursuing High-quality Keypoint Pairs for Object Detection, CVPR2020. [\[paper\]](https://arxiv.org/abs/2003.09119)[\[github\]](https://github.com/KiveeDong/CentripetalNet) +- Learning a Unified Sample Weighting Network for Object Detection, CVPR 2020. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2020/html/Cai_Learning_a_Unified_Sample_Weighting_Network_for_Object_Detection_CVPR_2020_paper.html)[\[github\]](https://github.com/caiqi/sample-weighting-network) +- Scale-equalizing Pyramid Convolution for Object Detection, CVPR2020. [\[paper\]](https://arxiv.org/abs/2005.03101) [\[github\]](https://github.com/jshilong/SEPC) +- Revisiting the Sibling Head in Object Detector, CVPR2020. [\[paper\]](https://arxiv.org/abs/2003.07540)[\[github\]](https://github.com/Sense-X/TSD) +- PolarMask: Single Shot Instance Segmentation with Polar Representation, CVPR2020. [\[paper\]](https://arxiv.org/abs/1909.13226)[\[github\]](https://github.com/xieenze/PolarMask) +- Hit-Detector: Hierarchical Trinity Architecture Search for Object Detection, CVPR2020. [\[paper\]](https://arxiv.org/abs/2003.11818)[\[github\]](https://github.com/ggjy/HitDet.pytorch) +- ZeroQ: A Novel Zero Shot Quantization Framework, CVPR2020. [\[paper\]](https://arxiv.org/abs/2001.00281)[\[github\]](https://github.com/amirgholami/ZeroQ) +- CBNet: A Novel Composite Backbone Network Architecture for Object Detection, AAAI2020. [\[paper\]](https://aaai.org/Papers/AAAI/2020GB/AAAI-LiuY.1833.pdf)[\[github\]](https://github.com/VDIGPKU/CBNet) +- RDSNet: A New Deep Architecture for Reciprocal Object Detection and Instance Segmentation, AAAI2020. [\[paper\]](https://arxiv.org/abs/1912.05070)[\[github\]](https://github.com/wangsr126/RDSNet) +- Training-Time-Friendly Network for Real-Time Object Detection, AAAI2020. [\[paper\]](https://arxiv.org/abs/1909.00700)[\[github\]](https://github.com/ZJULearning/ttfnet) +- Cascade RPN: Delving into High-Quality Region Proposal Network with Adaptive Convolution, NeurIPS 2019. [\[paper\]](https://arxiv.org/abs/1909.06720)[\[github\]](https://github.com/thangvubk/Cascade-RPN) +- Reasoning R-CNN: Unifying Adaptive Global Reasoning into Large-scale Object Detection, CVPR2019. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2019/papers/Xu_Reasoning-RCNN_Unifying_Adaptive_Global_Reasoning_Into_Large-Scale_Object_Detection_CVPR_2019_paper.pdf)[\[github\]](https://github.com/chanyn/Reasoning-RCNN) +- Learning RoI Transformer for Oriented Object Detection in Aerial Images, CVPR2019. [\[paper\]](https://arxiv.org/abs/1812.00155)[\[github\]](https://github.com/dingjiansw101/AerialDetection) +- SOLO: Segmenting Objects by Locations. [\[paper\]](https://arxiv.org/abs/1912.04488)[\[github\]](https://github.com/WXinlong/SOLO) +- SOLOv2: Dynamic, Faster and Stronger. [\[paper\]](https://arxiv.org/abs/2003.10152)[\[github\]](https://github.com/WXinlong/SOLO) +- Dense Peppoints: Representing Visual Objects with Dense Point Sets. [\[paper\]](https://arxiv.org/abs/1912.11473)[\[github\]](https://github.com/justimyhxu/Dense-RepPoints) +- IterDet: Iterative Scheme for Object Detection in Crowded Environments. [\[paper\]](https://arxiv.org/abs/2005.05708)[\[github\]](https://github.com/saic-vul/iterdet) +- Cross-Iteration Batch Normalization. [\[paper\]](https://arxiv.org/abs/2002.05712)[\[github\]](https://github.com/Howal/Cross-iterationBatchNorm) +- A Ranking-based, Balanced Loss Function Unifying Classification and Localisation in Object Detection, NeurIPS2020 [\[paper\]](https://arxiv.org/abs/2009.13592)[\[github\]](https://github.com/kemaloksuz/aLRPLoss) +- RelationNet++: Bridging Visual Representations for Object Detection via Transformer Decoder, NeurIPS2020 [\[paper\]](https://arxiv.org/abs/2010.15831)[\[github\]](https://github.com/microsoft/RelationNet2) +- Generalized Focal Loss V2: Learning Reliable Localization Quality Estimation for Dense Object Detection, CVPR2021[\[paper\]](https://arxiv.org/abs/2011.12885)[\[github\]](https://github.com/implus/GFocalV2) +- Swin Transformer: Hierarchical Vision Transformer using Shifted Windows, ICCV2021[\[paper\]](https://arxiv.org/abs/2103.14030)[\[github\]](https://github.com/SwinTransformer/) +- Focal Transformer: Focal Self-attention for Local-Global Interactions in Vision Transformers, NeurIPS2021[\[paper\]](https://arxiv.org/abs/2107.00641)[\[github\]](https://github.com/microsoft/Focal-Transformer) +- End-to-End Semi-Supervised Object Detection with Soft Teacher, ICCV2021[\[paper\]](https://arxiv.org/abs/2106.09018)[\[github\]](https://github.com/microsoft/SoftTeacher) +- CBNetV2: A Novel Composite Backbone Network Architecture for Object Detection [\[paper\]](http://arxiv.org/abs/2107.00420)[\[github\]](https://github.com/VDIGPKU/CBNetV2) +- Instances as Queries, ICCV2021 [\[paper\]](https://openaccess.thecvf.com/content/ICCV2021/papers/Fang_Instances_As_Queries_ICCV_2021_paper.pdf)[\[github\]](https://github.com/hustvl/QueryInst) diff --git a/mmdetection/docs/en/overview.md b/mmdetection/docs/en/overview.md new file mode 100644 index 00000000..7c7d96b7 --- /dev/null +++ b/mmdetection/docs/en/overview.md @@ -0,0 +1,54 @@ +# OVERVIEW + +This chapter introduces you to the framework of MMDetection, and provides links to detailed tutorials about MMDetection. + +## What is MMDetection + +![image](https://user-images.githubusercontent.com/12907710/137271636-56ba1cd2-b110-4812-8221-b4c120320aa9.png) + +MMDetection is an object detection toolbox that contains a rich set of object detection, instance segmentation, and panoptic segmentation methods as well as related components and modules, and below is its whole framework: + +MMDetection consists of 7 main parts, apis, structures, datasets, models, engine, evaluation and visualization. + +- **apis** provides high-level APIs for model inference. +- **structures** provides data structures like bbox, mask, and DetDataSample. +- **datasets** supports various dataset for object detection, instance segmentation, and panoptic segmentation. + - **transforms** contains a lot of useful data augmentation transforms. + - **samplers** defines different data loader sampling strategy. +- **models** is the most vital part for detectors and contains different components of a detector. + - **detectors** defines all of the detection model classes. + - **data_preprocessors** is for preprocessing the input data of the model. + - **backbones** contains various backbone networks. + - **necks** contains various neck components. + - **dense_heads** contains various detection heads that perform dense predictions. + - **roi_heads** contains various detection heads that predict from RoIs. + - **seg_heads** contains various segmentation heads. + - **losses** contains various loss functions. + - **task_modules** provides modules for detection tasks. E.g. assigners, samplers, box coders, and prior generators. + - **layers** provides some basic neural network layers. +- **engine** is a part for runtime components. + - **runner** provides extensions for [MMEngine's runner](https://mmengine.readthedocs.io/en/latest/tutorials/runner.html). + - **schedulers** provides schedulers for adjusting optimization hyperparameters. + - **optimizers** provides optimizers and optimizer wrappers. + - **hooks** provides various hooks of the runner. +- **evaluation** provides different metrics for evaluating model performance. +- **visualization** is for visualizing detection results. + +## How to Use this Guide + +Here is a detailed step-by-step guide to learn more about MMDetection: + +1. For installation instructions, please see [get_started](get_started.md). + +2. Refer to the below tutorials for the basic usage of MMDetection. + + - [Train and Test](https://mmdetection.readthedocs.io/en/latest/user_guides/index.html#train-test) + + - [Useful Tools](https://mmdetection.readthedocs.io/en/latest/user_guides/index.html#useful-tools) + +3. Refer to the below tutorials to dive deeper: + + - [Basic Concepts](https://mmdetection.readthedocs.io/en/latest/advanced_guides/index.html#basic-concepts) + - [Component Customization](https://mmdetection.readthedocs.io/en/latest/advanced_guides/index.html#component-customization) + +4. For users of MMDetection 2.x version, we provide a guide to help you adapt to the new version. You can find it in the [migration guide](./migration/migration.md). diff --git a/mmdetection/docs/en/stat.py b/mmdetection/docs/en/stat.py new file mode 100644 index 00000000..f0589e33 --- /dev/null +++ b/mmdetection/docs/en/stat.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +import functools as func +import glob +import os.path as osp +import re + +import numpy as np + +url_prefix = 'https://github.com/open-mmlab/mmdetection/blob/main/configs' + +files = sorted(glob.glob('../../configs/*/README.md')) + +stats = [] +titles = [] +num_ckpts = 0 + +for f in files: + url = osp.dirname(f.replace('../../configs', url_prefix)) + + with open(f, 'r') as content_file: + content = content_file.read() + + title = content.split('\n')[0].replace('# ', '').strip() + ckpts = set(x.lower().strip() + for x in re.findall(r'\[model\]\((https?.*)\)', content)) + + if len(ckpts) == 0: + continue + + _papertype = [x for x in re.findall(r'\[([A-Z]+)\]', content)] + assert len(_papertype) > 0 + papertype = _papertype[0] + + paper = set([(papertype, title)]) + + titles.append(title) + num_ckpts += len(ckpts) + + statsmsg = f""" +\t* [{papertype}] [{title}]({url}) ({len(ckpts)} ckpts) +""" + stats.append((paper, ckpts, statsmsg)) + +allpapers = func.reduce(lambda a, b: a.union(b), [p for p, _, _ in stats]) +msglist = '\n'.join(x for _, _, x in stats) + +papertypes, papercounts = np.unique([t for t, _ in allpapers], + return_counts=True) +countstr = '\n'.join( + [f' - {t}: {c}' for t, c in zip(papertypes, papercounts)]) + +modelzoo = f""" +# Model Zoo Statistics + +* Number of papers: {len(set(titles))} +{countstr} + +* Number of checkpoints: {num_ckpts} + +{msglist} +""" + +with open('modelzoo_statistics.md', 'w') as f: + f.write(modelzoo) diff --git a/mmdetection/docs/en/switch_language.md b/mmdetection/docs/en/switch_language.md new file mode 100644 index 00000000..b2c4ad9d --- /dev/null +++ b/mmdetection/docs/en/switch_language.md @@ -0,0 +1,3 @@ +## English + +## 简体中文 diff --git a/mmdetection/docs/en/user_guides/config.md b/mmdetection/docs/en/user_guides/config.md new file mode 100644 index 00000000..69bd9119 --- /dev/null +++ b/mmdetection/docs/en/user_guides/config.md @@ -0,0 +1,612 @@ +# Learn about Configs + +MMDetection and other OpenMMLab repositories use [MMEngine's config system](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html). It has a modular and inheritance design, which is convenient to conduct various experiments. + +## Config file content + +MMDetection uses a modular design, all modules with different functions can be configured through the config. Taking Mask R-CNN as an example, we will introduce each field in the config according to different function modules: + +### Model config + +In MMDetection's config, we use `model` to set up detection algorithm components. In addition to neural network components such as `backbone`, `neck`, etc, it also requires `data_preprocessor`, `train_cfg`, and `test_cfg`. `data_preprocessor` is responsible for processing a batch of data output by dataloader. `train_cfg`, and `test_cfg` in the model config are for training and testing hyperparameters of the components. + +```python +model = dict( + type='MaskRCNN', # The name of detector + data_preprocessor=dict( # The config of data preprocessor, usually includes image normalization and padding + type='DetDataPreprocessor', # The type of the data preprocessor, refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.data_preprocessors.DetDataPreprocessor + mean=[123.675, 116.28, 103.53], # Mean values used to pre-training the pre-trained backbone models, ordered in R, G, B + std=[58.395, 57.12, 57.375], # Standard variance used to pre-training the pre-trained backbone models, ordered in R, G, B + bgr_to_rgb=True, # whether to convert image from BGR to RGB + pad_mask=True, # whether to pad instance masks + pad_size_divisor=32), # The size of padded image should be divisible by ``pad_size_divisor`` + backbone=dict( # The config of backbone + type='ResNet', # The type of backbone network. Refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.backbones.ResNet + depth=50, # The depth of backbone, usually it is 50 or 101 for ResNet and ResNext backbones. + num_stages=4, # Number of stages of the backbone. + out_indices=(0, 1, 2, 3), # The index of output feature maps produced in each stage + frozen_stages=1, # The weights in the first stage are frozen + norm_cfg=dict( # The config of normalization layers. + type='BN', # Type of norm layer, usually it is BN or GN + requires_grad=True), # Whether to train the gamma and beta in BN + norm_eval=True, # Whether to freeze the statistics in BN + style='pytorch', # The style of backbone, 'pytorch' means that stride 2 layers are in 3x3 Conv, 'caffe' means stride 2 layers are in 1x1 Convs. + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), # The ImageNet pretrained backbone to be loaded + neck=dict( + type='FPN', # The neck of detector is FPN. We also support 'NASFPN', 'PAFPN', etc. Refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.necks.FPN for more details. + in_channels=[256, 512, 1024, 2048], # The input channels, this is consistent with the output channels of backbone + out_channels=256, # The output channels of each level of the pyramid feature map + num_outs=5), # The number of output scales + rpn_head=dict( + type='RPNHead', # The type of RPN head is 'RPNHead', we also support 'GARPNHead', etc. Refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.dense_heads.RPNHead for more details. + in_channels=256, # The input channels of each input feature map, this is consistent with the output channels of neck + feat_channels=256, # Feature channels of convolutional layers in the head. + anchor_generator=dict( # The config of anchor generator + type='AnchorGenerator', # Most of methods use AnchorGenerator, SSD Detectors uses `SSDAnchorGenerator`. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/prior_generators/anchor_generator.py#L18 for more details + scales=[8], # Basic scale of the anchor, the area of the anchor in one position of a feature map will be scale * base_sizes + ratios=[0.5, 1.0, 2.0], # The ratio between height and width. + strides=[4, 8, 16, 32, 64]), # The strides of the anchor generator. This is consistent with the FPN feature strides. The strides will be taken as base_sizes if base_sizes is not set. + bbox_coder=dict( # Config of box coder to encode and decode the boxes during training and testing + type='DeltaXYWHBBoxCoder', # Type of box coder. 'DeltaXYWHBBoxCoder' is applied for most of the methods. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/coders/delta_xywh_bbox_coder.py#L13 for more details. + target_means=[0.0, 0.0, 0.0, 0.0], # The target means used to encode and decode boxes + target_stds=[1.0, 1.0, 1.0, 1.0]), # The standard variance used to encode and decode boxes + loss_cls=dict( # Config of loss function for the classification branch + type='CrossEntropyLoss', # Type of loss for classification branch, we also support FocalLoss etc. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/losses/cross_entropy_loss.py#L201 for more details + use_sigmoid=True, # RPN usually performs two-class classification, so it usually uses the sigmoid function. + loss_weight=1.0), # Loss weight of the classification branch. + loss_bbox=dict( # Config of loss function for the regression branch. + type='L1Loss', # Type of loss, we also support many IoU Losses and smooth L1-loss, etc. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/losses/smooth_l1_loss.py#L56 for implementation. + loss_weight=1.0)), # Loss weight of the regression branch. + roi_head=dict( # RoIHead encapsulates the second stage of two-stage/cascade detectors. + type='StandardRoIHead', + bbox_roi_extractor=dict( # RoI feature extractor for bbox regression. + type='SingleRoIExtractor', # Type of the RoI feature extractor, most of methods uses SingleRoIExtractor. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/roi_heads/roi_extractors/single_level_roi_extractor.py#L13 for details. + roi_layer=dict( # Config of RoI Layer + type='RoIAlign', # Type of RoI Layer, DeformRoIPoolingPack and ModulatedDeformRoIPoolingPack are also supported. Refer to https://mmcv.readthedocs.io/en/latest/api.html#mmcv.ops.RoIAlign for details. + output_size=7, # The output size of feature maps. + sampling_ratio=0), # Sampling ratio when extracting the RoI features. 0 means adaptive ratio. + out_channels=256, # output channels of the extracted feature. + featmap_strides=[4, 8, 16, 32]), # Strides of multi-scale feature maps. It should be consistent with the architecture of the backbone. + bbox_head=dict( # Config of box head in the RoIHead. + type='Shared2FCBBoxHead', # Type of the bbox head, Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/roi_heads/bbox_heads/convfc_bbox_head.py#L220 for implementation details. + in_channels=256, # Input channels for bbox head. This is consistent with the out_channels in roi_extractor + fc_out_channels=1024, # Output feature channels of FC layers. + roi_feat_size=7, # Size of RoI features + num_classes=80, # Number of classes for classification + bbox_coder=dict( # Box coder used in the second stage. + type='DeltaXYWHBBoxCoder', # Type of box coder. 'DeltaXYWHBBoxCoder' is applied for most of the methods. + target_means=[0.0, 0.0, 0.0, 0.0], # Means used to encode and decode box + target_stds=[0.1, 0.1, 0.2, 0.2]), # Standard variance for encoding and decoding. It is smaller since the boxes are more accurate. [0.1, 0.1, 0.2, 0.2] is a conventional setting. + reg_class_agnostic=False, # Whether the regression is class agnostic. + loss_cls=dict( # Config of loss function for the classification branch + type='CrossEntropyLoss', # Type of loss for classification branch, we also support FocalLoss etc. + use_sigmoid=False, # Whether to use sigmoid. + loss_weight=1.0), # Loss weight of the classification branch. + loss_bbox=dict( # Config of loss function for the regression branch. + type='L1Loss', # Type of loss, we also support many IoU Losses and smooth L1-loss, etc. + loss_weight=1.0)), # Loss weight of the regression branch. + mask_roi_extractor=dict( # RoI feature extractor for mask generation. + type='SingleRoIExtractor', # Type of the RoI feature extractor, most of methods uses SingleRoIExtractor. + roi_layer=dict( # Config of RoI Layer that extracts features for instance segmentation + type='RoIAlign', # Type of RoI Layer, DeformRoIPoolingPack and ModulatedDeformRoIPoolingPack are also supported + output_size=14, # The output size of feature maps. + sampling_ratio=0), # Sampling ratio when extracting the RoI features. + out_channels=256, # Output channels of the extracted feature. + featmap_strides=[4, 8, 16, 32]), # Strides of multi-scale feature maps. + mask_head=dict( # Mask prediction head + type='FCNMaskHead', # Type of mask head, refer to https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.roi_heads.FCNMaskHead for implementation details. + num_convs=4, # Number of convolutional layers in mask head. + in_channels=256, # Input channels, should be consistent with the output channels of mask roi extractor. + conv_out_channels=256, # Output channels of the convolutional layer. + num_classes=80, # Number of class to be segmented. + loss_mask=dict( # Config of loss function for the mask branch. + type='CrossEntropyLoss', # Type of loss used for segmentation + use_mask=True, # Whether to only train the mask in the correct class. + loss_weight=1.0))), # Loss weight of mask branch. + train_cfg = dict( # Config of training hyperparameters for rpn and rcnn + rpn=dict( # Training config of rpn + assigner=dict( # Config of assigner + type='MaxIoUAssigner', # Type of assigner, MaxIoUAssigner is used for many common detectors. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/assigners/max_iou_assigner.py#L14 for more details. + pos_iou_thr=0.7, # IoU >= threshold 0.7 will be taken as positive samples + neg_iou_thr=0.3, # IoU < threshold 0.3 will be taken as negative samples + min_pos_iou=0.3, # The minimal IoU threshold to take boxes as positive samples + match_low_quality=True, # Whether to match the boxes under low quality (see API doc for more details). + ignore_iof_thr=-1), # IoF threshold for ignoring bboxes + sampler=dict( # Config of positive/negative sampler + type='RandomSampler', # Type of sampler, PseudoSampler and other samplers are also supported. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/samplers/random_sampler.py#L14 for implementation details. + num=256, # Number of samples + pos_fraction=0.5, # The ratio of positive samples in the total samples. + neg_pos_ub=-1, # The upper bound of negative samples based on the number of positive samples. + add_gt_as_proposals=False), # Whether add GT as proposals after sampling. + allowed_border=-1, # The border allowed after padding for valid anchors. + pos_weight=-1, # The weight of positive samples during training. + debug=False), # Whether to set the debug mode + rpn_proposal=dict( # The config to generate proposals during training + nms_across_levels=False, # Whether to do NMS for boxes across levels. Only work in `GARPNHead`, naive rpn does not support do nms cross levels. + nms_pre=2000, # The number of boxes before NMS + nms_post=1000, # The number of boxes to be kept by NMS. Only work in `GARPNHead`. + max_per_img=1000, # The number of boxes to be kept after NMS. + nms=dict( # Config of NMS + type='nms', # Type of NMS + iou_threshold=0.7 # NMS threshold + ), + min_bbox_size=0), # The allowed minimal box size + rcnn=dict( # The config for the roi heads. + assigner=dict( # Config of assigner for second stage, this is different for that in rpn + type='MaxIoUAssigner', # Type of assigner, MaxIoUAssigner is used for all roi_heads for now. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/assigners/max_iou_assigner.py#L14 for more details. + pos_iou_thr=0.5, # IoU >= threshold 0.5 will be taken as positive samples + neg_iou_thr=0.5, # IoU < threshold 0.5 will be taken as negative samples + min_pos_iou=0.5, # The minimal IoU threshold to take boxes as positive samples + match_low_quality=False, # Whether to match the boxes under low quality (see API doc for more details). + ignore_iof_thr=-1), # IoF threshold for ignoring bboxes + sampler=dict( + type='RandomSampler', # Type of sampler, PseudoSampler and other samplers are also supported. Refer to https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/samplers/random_sampler.py#L14 for implementation details. + num=512, # Number of samples + pos_fraction=0.25, # The ratio of positive samples in the total samples. + neg_pos_ub=-1, # The upper bound of negative samples based on the number of positive samples. + add_gt_as_proposals=True + ), # Whether add GT as proposals after sampling. + mask_size=28, # Size of mask + pos_weight=-1, # The weight of positive samples during training. + debug=False)), # Whether to set the debug mode + test_cfg = dict( # Config for testing hyperparameters for rpn and rcnn + rpn=dict( # The config to generate proposals during testing + nms_across_levels=False, # Whether to do NMS for boxes across levels. Only work in `GARPNHead`, naive rpn does not support do nms cross levels. + nms_pre=1000, # The number of boxes before NMS + nms_post=1000, # The number of boxes to be kept by NMS. Only work in `GARPNHead`. + max_per_img=1000, # The number of boxes to be kept after NMS. + nms=dict( # Config of NMS + type='nms', #Type of NMS + iou_threshold=0.7 # NMS threshold + ), + min_bbox_size=0), # The allowed minimal box size + rcnn=dict( # The config for the roi heads. + score_thr=0.05, # Threshold to filter out boxes + nms=dict( # Config of NMS in the second stage + type='nms', # Type of NMS + iou_thr=0.5), # NMS threshold + max_per_img=100, # Max number of detections of each image + mask_thr_binary=0.5))) # Threshold of mask prediction +``` + +### Dataset and evaluator config + +[Dataloaders](https://mmengine.readthedocs.io/en/latest/tutorials/dataset.html) are required for the training, validation, and testing of the [runner](https://mmengine.readthedocs.io/en/latest/tutorials/runner.html). Dataset and data pipeline need to be set to build the dataloader. Due to the complexity of this part, we use intermediate variables to simplify the writing of dataloader configs. + +```python +dataset_type = 'CocoDataset' # Dataset type, this will be used to define the dataset +data_root = 'data/coco/' # Root path of data +backend_args = None # Arguments to instantiate the corresponding file backend + +train_pipeline = [ # Training data processing pipeline + dict(type='LoadImageFromFile', backend_args=backend_args), # First pipeline to load images from file path + dict( + type='LoadAnnotations', # Second pipeline to load annotations for current image + with_bbox=True, # Whether to use bounding box, True for detection + with_mask=True, # Whether to use instance mask, True for instance segmentation + poly2mask=True), # Whether to convert the polygon mask to instance mask, set False for acceleration and to save memory + dict( + type='Resize', # Pipeline that resizes the images and their annotations + scale=(1333, 800), # The largest scale of the images + keep_ratio=True # Whether to keep the ratio between height and width + ), + dict( + type='RandomFlip', # Augmentation pipeline that flips the images and their annotations + prob=0.5), # The probability to flip + dict(type='PackDetInputs') # Pipeline that formats the annotation data and decides which keys in the data should be packed into data_samples +] +test_pipeline = [ # Testing data processing pipeline + dict(type='LoadImageFromFile', backend_args=backend_args), # First pipeline to load images from file path + dict(type='Resize', scale=(1333, 800), keep_ratio=True), # Pipeline that resizes the images + dict( + type='PackDetInputs', # Pipeline that formats the annotation data and decides which keys in the data should be packed into data_samples + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( # Train dataloader config + batch_size=2, # Batch size of a single GPU + num_workers=2, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # If ``True``, the dataloader will not shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict( # training data sampler + type='DefaultSampler', # DefaultSampler which supports both distributed and non-distributed training. Refer to https://mmengine.readthedocs.io/en/latest/api/generated/mmengine.dataset.DefaultSampler.html#mmengine.dataset.DefaultSampler + shuffle=True), # randomly shuffle the training data in each epoch + batch_sampler=dict(type='AspectRatioBatchSampler'), # Batch sampler for grouping images with similar aspect ratio into a same batch. It can reduce GPU memory cost. + dataset=dict( # Train dataset config + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', # Path of annotation file + data_prefix=dict(img='train2017/'), # Prefix of image path + filter_cfg=dict(filter_empty_gt=True, min_size=32), # Config of filtering images and annotations + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( # Validation dataloader config + batch_size=1, # Batch size of a single GPU. If batch-size > 1, the extra padding area may influence the performance. + num_workers=2, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # If ``True``, the dataloader will not shut down the worker processes after an epoch end, which can accelerate training speed. + drop_last=False, # Whether to drop the last incomplete batch, if the dataset size is not divisible by the batch size + sampler=dict( + type='DefaultSampler', + shuffle=False), # not shuffle during validation and testing + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, # Turn on the test mode of the dataset to avoid filtering annotations or images + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader # Testing dataloader config +``` + +[Evaluators](https://mmengine.readthedocs.io/en/latest/tutorials/evaluation.html) are used to compute the metrics of the trained model on the validation and testing datasets. The config of evaluators consists of one or a list of metric configs: + +```python +val_evaluator = dict( # Validation evaluator config + type='CocoMetric', # The coco metric used to evaluate AR, AP, and mAP for detection and instance segmentation + ann_file=data_root + 'annotations/instances_val2017.json', # Annotation file path + metric=['bbox', 'segm'], # Metrics to be evaluated, `bbox` for detection and `segm` for instance segmentation + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator # Testing evaluator config +``` + +Since the test dataset has no annotation files, the test_dataloader and test_evaluator config in MMDetection are generally equal to the val's. If you want to save the detection results on the test dataset, you can write the config like this: + +```python +# inference on test dataset and +# format the output results for submission. +test_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file=data_root + 'annotations/image_info_test-dev2017.json', + data_prefix=dict(img='test2017/'), + test_mode=True, + pipeline=test_pipeline)) +test_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/image_info_test-dev2017.json', + metric=['bbox', 'segm'], # Metrics to be evaluated + format_only=True, # Only format and save the results to coco json file + outfile_prefix='./work_dirs/coco_detection/test') # The prefix of output json files +``` + +### Training and testing config + +MMEngine's runner uses Loop to control the training, validation, and testing processes. +Users can set the maximum training epochs and validation intervals with these fields. + +```python +train_cfg = dict( + type='EpochBasedTrainLoop', # The training loop type. Refer to https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py + max_epochs=12, # Maximum training epochs + val_interval=1) # Validation intervals. Run validation every epoch. +val_cfg = dict(type='ValLoop') # The validation loop type +test_cfg = dict(type='TestLoop') # The testing loop type +``` + +### Optimization config + +`optim_wrapper` is the field to configure optimization-related settings. The optimizer wrapper not only provides the functions of the optimizer, but also supports functions such as gradient clipping, mixed precision training, etc. Find more in [optimizer wrapper tutorial](https://mmengine.readthedocs.io/en/latest/tutorials/optim_wrapper.html). + +```python +optim_wrapper = dict( # Optimizer wrapper config + type='OptimWrapper', # Optimizer wrapper type, switch to AmpOptimWrapper to enable mixed precision training. + optimizer=dict( # Optimizer config. Support all kinds of optimizers in PyTorch. Refer to https://pytorch.org/docs/stable/optim.html#algorithms + type='SGD', # Stochastic gradient descent optimizer + lr=0.02, # The base learning rate + momentum=0.9, # Stochastic gradient descent with momentum + weight_decay=0.0001), # Weight decay of SGD + clip_grad=None, # Gradient clip option. Set None to disable gradient clip. Find usage in https://mmengine.readthedocs.io/en/latest/tutorials/optimizer.html + ) +``` + +`param_scheduler` is a field that configures methods of adjusting optimization hyperparameters such as learning rate and momentum. Users can combine multiple schedulers to create a desired parameter adjustment strategy. Find more in [parameter scheduler tutorial](https://mmengine.readthedocs.io/en/latest/tutorials/param_scheduler.html) and [parameter scheduler API documents](https://mmengine.readthedocs.io/en/latest/api/generated/mmengine.optim._ParamScheduler.html#mmengine.optim._ParamScheduler) + +```python +param_scheduler = [ + # Linear learning rate warm-up scheduler + dict( + type='LinearLR', # Use linear policy to warmup learning rate + start_factor=0.001, # The ratio of the starting learning rate used for warmup + by_epoch=False, # The warmup learning rate is updated by iteration + begin=0, # Start from the first iteration + end=500), # End the warmup at the 500th iteration + # The main LRScheduler + dict( + type='MultiStepLR', # Use multi-step learning rate policy during training + by_epoch=True, # The learning rate is updated by epoch + begin=0, # Start from the first epoch + end=12, # End at the 12th epoch + milestones=[8, 11], # Epochs to decay the learning rate + gamma=0.1) # The learning rate decay ratio +] +``` + +### Hook config + +Users can attach Hooks to training, validation, and testing loops to insert some operations during running. There are two different hook fields, one is `default_hooks` and the other is `custom_hooks`. + +`default_hooks` is a dict of hook configs, and they are the hooks must be required at the runtime. They have default priority which should not be modified. If not set, runner will use the default values. To disable a default hook, users can set its config to `None`. Find more in [HOOK](https://mmengine.readthedocs.io/en/latest/tutorials/hook.html). + +```python +default_hooks = dict( + timer=dict(type='IterTimerHook'), # Update the time spent during iteration into message hub + logger=dict(type='LoggerHook', interval=50), # Collect logs from different components of Runner and write them to terminal, JSON file, tensorboard and wandb .etc + param_scheduler=dict(type='ParamSchedulerHook'), # update some hyper-parameters of optimizer + checkpoint=dict(type='CheckpointHook', interval=1), # Save checkpoints periodically + sampler_seed=dict(type='DistSamplerSeedHook'), # Ensure distributed Sampler shuffle is active + visualization=dict(type='DetVisualizationHook')) # Detection Visualization Hook. Used to visualize validation and testing process prediction results +``` + +`custom_hooks` is a list of all other hook configs. Users can develop their own hooks and insert them in this field. + +```python +custom_hooks = [] +``` + +### Runtime config + +```python +default_scope = 'mmdet' # The default registry scope to find modules. Refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/registry.html + +env_cfg = dict( + cudnn_benchmark=False, # Whether to enable cudnn benchmark + mp_cfg=dict( # Multi-processing config + mp_start_method='fork', # Use fork to start multi-processing threads. 'fork' usually faster than 'spawn' but maybe unsafe. See discussion in https://github.com/pytorch/pytorch/issues/1355 + opencv_num_threads=0), # Disable opencv multi-threads to avoid system being overloaded + dist_cfg=dict(backend='nccl'), # Distribution configs +) + +vis_backends = [dict(type='LocalVisBackend')] # Visualization backends. Refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/visualization.html +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') +log_processor = dict( + type='LogProcessor', # Log processor to process runtime logs + window_size=50, # Smooth interval of log values + by_epoch=True) # Whether to format logs with epoch type. Should be consistent with the train loop's type. + +log_level = 'INFO' # The level of logging. +load_from = None # Load model checkpoint as a pre-trained model from a given path. This will not resume training. +resume = False # Whether to resume from the checkpoint defined in `load_from`. If `load_from` is None, it will resume the latest checkpoint in the `work_dir`. +``` + +## Iter-based config + +MMEngine's Runner also provides an iter-based training loop except for epoch-based. +To use iter-based training, users should modify the `train_cfg`, `param_scheduler`, `train_dataloader`, `default_hooks`, and `log_processor`. +Here is an example of changing an epoch-based RetinaNet config to iter-based: `configs/retinanet/retinanet_r50_fpn_90k_coco.py` + +```python +# Iter-based training config +train_cfg = dict( + _delete_=True, # Ignore the base config setting (optional) + type='IterBasedTrainLoop', # Use iter-based training loop + max_iters=90000, # Maximum iterations + val_interval=10000) # Validation interval + + +# Change the scheduler to iter-based +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=90000, + by_epoch=False, + milestones=[60000, 80000], + gamma=0.1) +] + +# Switch to InfiniteSampler to avoid dataloader restart +train_dataloader = dict(sampler=dict(type='InfiniteSampler')) + +# Change the checkpoint saving interval to iter-based +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=10000)) + +# Change the log format to iter-based +log_processor = dict(by_epoch=False) +``` + +## Config file inheritance + +There are 4 basic component types under `config/_base_`, dataset, model, schedule, default_runtime. +Many methods could be easily constructed with one of these models like Faster R-CNN, Mask R-CNN, Cascade R-CNN, RPN, SSD. +The configs that are composed by components from `_base_` are called the _primitive_. + +For all configs under the same folder, it is recommended to have only **one** _primitive_ config. All other configs should inherit from the _primitive_ config. In this way, the maximum of inheritance level is 3. + +For easy understanding, we recommend contributors to inherit from existing methods. +For example, if some modification is made based on Faster R-CNN, users may first inherit the basic Faster R-CNN structure by specifying `_base_ = ../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py`, then modify the necessary fields in the config files. + +If you are building an entirely new method that does not share the structure with any of the existing methods, you may create a folder `xxx_rcnn` under `configs`, + +Please refer to [mmengine config tutorial](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html) for detailed documentation. + +By setting the `_base_` field, we can set which files the current configuration file inherits from. + +When `_base_` is a string of a file path, it means inheriting the contents from one config file. + +```python +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' +``` + +When `_base_` is a list of multiple file paths, it means inheriting from multiple files. + +```python +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +``` + +If you wish to inspect the config file, you may run `python tools/misc/print_config.py /PATH/TO/CONFIG` to see the complete config. + +### Ignore some fields in the base configs + +Sometimes, you may set `_delete_=True` to ignore some of the fields in base configs. +You may refer to [mmengine config tutorial](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html) for a simple illustration. + +In MMDetection, for example, to change the backbone of Mask R-CNN with the following config. + +```python +model = dict( + type='MaskRCNN', + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict(...), + rpn_head=dict(...), + roi_head=dict(...)) +``` + +`ResNet` and `HRNet` use different keywords to construct. + +```python +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + _delete_=True, + type='HRNet', + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))), + init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w32')), + neck=dict(...)) +``` + +The `_delete_=True` would replace all old keys in `backbone` field with new keys. + +### Use intermediate variables in configs + +Some intermediate variables are used in the configs files, like `train_pipeline`/`test_pipeline` in datasets. +It's worth noting that when modifying intermediate variables in the children configs, users need to pass the intermediate variables into corresponding fields again. +For example, we would like to use a multi-scale strategy to train a Mask R-CNN. `train_pipeline`/`test_pipeline` are intermediate variables we would like to modify. + +```python +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +``` + +We first define the new `train_pipeline`/`test_pipeline` and pass them into dataloader fields. + +Similarly, if we would like to switch from `SyncBN` to `BN` or `MMSyncBN`, we need to substitute every `norm_cfg` in the config. + +```python +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + backbone=dict(norm_cfg=norm_cfg), + neck=dict(norm_cfg=norm_cfg), + ...) +``` + +### Reuse variables in \_base\_ file + +If the users want to reuse the variables in the base file, they can get a copy of the corresponding variable by using `{{_base_.xxx}}`. E.g: + +```python +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' + +a = {{_base_.model}} # Variable `a` is equal to the `model` defined in `_base_` +``` + +## Modify config through script arguments + +When submitting jobs using `tools/train.py` or `tools/test.py`, you may specify `--cfg-options` to in-place modify the config. + +- Update config keys of dict chains. + + The config options can be specified following the order of the dict keys in the original config. + For example, `--cfg-options model.backbone.norm_eval=False` changes the all BN modules in model backbones to `train` mode. + +- Update keys inside a list of configs. + + Some config dicts are composed as a list in your config. For example, the training pipeline `train_dataloader.dataset.pipeline` is normally a list + e.g. `[dict(type='LoadImageFromFile'), ...]`. If you want to change `'LoadImageFromFile'` to `'LoadImageFromNDArray'` in the pipeline, + you may specify `--cfg-options data.train.pipeline.0.type=LoadImageFromNDArray`. + +- Update values of list/tuples. + + If the value to be updated is a list or a tuple. For example, the config file normally sets `model.data_preprocessor.mean=[123.675, 116.28, 103.53]`. If you want to + change the mean values, you may specify `--cfg-options model.data_preprocessor.mean="[127,127,127]"`. Note that the quotation mark `"` is necessary to + support list/tuple data types, and **NO** white space is allowed inside the quotation marks in the specified value. + +## Config name style + +We follow the below style to name config files. Contributors are advised to follow the same style. + +``` +{algorithm name}_{model component names [component1]_[component2]_[...]}_{training settings}_{training dataset information}_{testing dataset information}.py +``` + +The file name is divided into five parts. All parts and components are connected with `_` and words of each part or component should be connected with `-`. + +- `{algorithm name}`: The name of the algorithm. It can be a detector name such as `faster-rcnn`, `mask-rcnn`, etc. Or can be a semi-supervised or knowledge-distillation algorithm such as `soft-teacher`, `lad`. etc. +- `{model component names}`: Names of the components used in the algorithm such as backbone, neck, etc. For example, `r50-caffe_fpn_gn-head` means using caffe-style ResNet50, FPN and detection head with Group Norm in the algorithm. +- `{training settings}`: Information of training settings such as batch size, augmentations, loss trick, scheduler, and epochs/iterations. For example: `4xb4-mixup-giou-coslr-100e` means using 8-gpus x 4-images-per-gpu, mixup augmentation, GIoU loss, cosine annealing learning rate, and train 100 epochs. + Some abbreviations: + - `{gpu x batch_per_gpu}`: GPUs and samples per GPU. `bN` indicates N batch size per GPU. E.g. `4xb4` is the short term of 4-GPUs x 4-images-per-GPU. And `8xb2` is used by default if not mentioned. + - `{schedule}`: training schedule, options are `1x`, `2x`, `20e`, etc. + `1x` and `2x` means 12 epochs and 24 epochs respectively. + `20e` is adopted in cascade models, which denotes 20 epochs. + For `1x`/`2x`, the initial learning rate decays by a factor of 10 at the 8/16th and 11/22th epochs. + For `20e`, the initial learning rate decays by a factor of 10 at the 16th and 19th epochs. +- `{training dataset information}`: Training dataset names like `coco`, `coco-panoptic`, `cityscapes`, `voc-0712`, `wider-face`. +- `{testing dataset information}` (optional): Testing dataset name for models trained on one dataset but tested on another. If not mentioned, it means the model was trained and tested on the same dataset type. diff --git a/mmdetection/docs/en/user_guides/dataset_prepare.md b/mmdetection/docs/en/user_guides/dataset_prepare.md new file mode 100644 index 00000000..1e0259a1 --- /dev/null +++ b/mmdetection/docs/en/user_guides/dataset_prepare.md @@ -0,0 +1,310 @@ +# Dataset Prepare + +### Basic Detection Dataset Preparation + +MMDetection supports multiple public datasets including COCO, Pascal VOC, CityScapes, and [more](../../../configs/_base_/datasets). + +Public datasets like [Pascal VOC](http://host.robots.ox.ac.uk/pascal/VOC/index.html) or mirror and [COCO](https://cocodataset.org/#download) are available from official websites or mirrors. Note: In the detection task, Pascal VOC 2012 is an extension of Pascal VOC 2007 without overlap, and we usually use them together. +It is recommended to download and extract the dataset somewhere outside the project directory and symlink the dataset root to `$MMDETECTION/data` as below. +If your folder structure is different, you may need to change the corresponding paths in config files. + +We provide a script to download datasets such as COCO, you can run `python tools/misc/download_dataset.py --dataset-name coco2017` to download COCO dataset. +For users in China, more datasets can be downloaded from the opensource dataset platform: [OpenDataLab](https://opendatalab.com/?source=OpenMMLab%20GitHub). + +For more usage please refer to [dataset-download](./useful_tools.md#dataset-download) + +```text +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ ├── cityscapes +│ │ ├── annotations +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +│ ├── VOCdevkit +│ │ ├── VOC2007 +│ │ ├── VOC2012 +``` + +Some models require additional [COCO-stuff](http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip) datasets, such as HTC, DetectoRS and SCNet, you can download, unzip, and then move them to the coco folder. The directory should be like this. + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── stuffthingmaps +``` + +Panoptic segmentation models like PanopticFPN require additional [COCO Panoptic](http://images.cocodataset.org/annotations/panoptic_annotations_trainval2017.zip) datasets, you can download, unzip, and then move them to the coco annotation folder. The directory should be like this. + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── panoptic_train2017.json +│ │ │ ├── panoptic_train2017 +│ │ │ ├── panoptic_val2017.json +│ │ │ ├── panoptic_val2017 +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +``` + +The [cityscapes](https://www.cityscapes-dataset.com/) annotations need to be converted into the coco format using `tools/dataset_converters/cityscapes.py`: + +```shell +pip install cityscapesscripts + +python tools/dataset_converters/cityscapes.py \ + ./data/cityscapes \ + --nproc 8 \ + --out-dir ./data/cityscapes/annotations +``` + +### COCO Caption Dataset Preparation + +COCO Caption uses the COCO2014 dataset image and uses the annotation of karpathy. + +At first, you need to download the COCO2014 dataset. + +```shell +python tools/misc/download_dataset.py --dataset-name coco2014 --unzip +``` + +The dataset will be downloaded to `data/coco` under the current path. Then download the annotation of karpathy. + +```shell +cd data/coco/annotations +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_train.json +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_val.json +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_test.json +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_val_gt.json +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_test_gt.json +``` + +The final directory structure of the dataset folder that can be directly used for training and testing is as follows: + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── coco_karpathy_train.json +│ │ │ ├── coco_karpathy_test.json +│ │ │ ├── coco_karpathy_val.json +│ │ │ ├── coco_karpathy_val_gt.json +│ │ │ ├── coco_karpathy_test_gt.json +│ │ ├── train2014 +│ │ ├── val2014 +│ │ ├── test2014 +``` + +### COCO Semantic Dataset Preparation + +There are two types of annotations for COCO semantic segmentation, which differ mainly in the definition of category names, so there are two ways to handle them. The first is to directly use the stuffthingmaps dataset, and the second is to use the panoptic dataset. + +**(1) Use stuffthingmaps dataset** + +The download link for this dataset is [stuffthingmaps_trainval2017](http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip). Please download and extract it to the `data/coco` folder. + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── stuffthingmaps +``` + +This dataset is different from the standard COCO category annotation in that it includes 172 classes: 80 "thing" classes, 91 "stuff" classes, and 1 "unlabeled" class. The description of each class can be found at https://github.com/nightrome/cocostuff/blob/master/labels.md. + +Although only 172 categories are annotated, the maximum label ID in `stuffthingmaps` is 182, and some categories in the middle are not annotated. In addition, the "unlabeled" category of class 0 is removed. Therefore, the relationship between the value at each position in the final `stuffthingmaps` image can be found at https://github.com/kazuto1011/deeplab-pytorch/blob/master/data/datasets/cocostuff/labels.txt. + +To train efficiently and conveniently for users, we need to remove 12 unannotated classes before starting training or evaluation. The names of these 12 classes are: `street sign, hat, shoe, eye glasses, plate, mirror, window, desk, door, blender, hair brush`. The category information that can be used for training and evaluation can be found in `mmdet/datasets/coco_semantic.py`. + +You can use `tools/dataset_converters/coco_stuff164k.py` to convert the downloaded `stuffthingmaps` to a dataset that can be directly used for training and evaluation. The directory structure of the converted dataset is as follows: + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── stuffthingmaps +│ │ ├── stuffthingmaps_semseg +``` + +`stuffthingmaps_semseg` is the newly generated COCO semantic segmentation dataset that can be directly used for training and testing. + +**(2) use panoptic dataset** + +The number of categories in the semantic segmentation dataset generated through panoptic annotation will be less than that generated using the `stuffthingmaps` dataset. First, you need to prepare the panoptic segmentation annotations, and then use the following script to complete the conversion. + +```shell +python tools/dataset_converters/prepare_coco_semantic_annos_from_panoptic_annos.py data/coco +``` + +The directory structure of the converted dataset is as follows: + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── panoptic_train2017.json +│ │ │ ├── panoptic_train2017 +│ │ │ ├── panoptic_val2017.json +│ │ │ ├── panoptic_val2017 +│ │ │ ├── panoptic_semseg_train2017 +│ │ │ ├── panoptic_semseg_val2017 +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +``` + +`panoptic_semseg_train2017` and `panoptic_semseg_val2017` are the newly generated COCO semantic segmentation datasets that can be directly used for training and testing. Note that their category information is the same as that of COCO panoptic segmentation, including both "thing" and "stuff" categories. + +### RefCOCO Dataset Preparation + +The images and annotations of [RefCOCO](https://github.com/lichengunc/refer) series datasets can be download by running `tools/misc/download_dataset.py`: + +```shell +python tools/misc/download_dataset.py --dataset-name refcoco --save-dir data/coco --unzip +``` + +Then the directory should be like this: + +```text +data +├── coco +│ ├── refcoco +│ │ ├── instances.json +│ │ ├── refs(google).p +│ │ └── refs(unc).p +│ ├── refcoco+ +│ │ ├── instances.json +│ │ └── refs(unc).p +│ ├── refcocog +│ │ ├── instances.json +│ │ ├── refs(google).p +│ │ └── refs(umd).p +│ │── train2014 +``` + +### ADE20K 2016 Dataset Preparation + +The images and annotations of [ADE20K](https://groups.csail.mit.edu/vision/datasets/ADE20K/) dataset can be download by running `tools/misc/download_dataset.py`: + +```shell +python tools/misc/download_dataset.py --dataset-name ade20k_2016 --save-dir data --unzip +``` + +Then move the annotations to the `data/ADEChallengeData2016` directory and run the preprocess script to produce the coco format annotations: + +```shell +mv data/annotations_instance data/ADEChallengeData2016/ +mv data/categoryMapping.txt data/ADEChallengeData2016/ +mv data/imgCatIds.json data/ADEChallengeData2016/ +python tools/dataset_converters/ade20k2coco.py data/ADEChallengeData2016 --task panoptic +python tools/dataset_converters/ade20k2coco.py data/ADEChallengeData2016 --task instance +``` + +The directory should be like this. + +```text +data +├── ADEChallengeData2016 +│ ├── ade20k_instance_train.json +│ ├── ade20k_instance_val.json +│ ├── ade20k_panoptic_train +│ │ ├── ADE_train_00000001.png +│ │ ├── ADE_train_00000002.png +│ │ ├── ... +│ ├── ade20k_panoptic_train.json +│ ├── ade20k_panoptic_val +│ │ ├── ADE_val_00000001.png +│ │ ├── ADE_val_00000002.png +│ │ ├── ... +│ ├── ade20k_panoptic_val.json +│ ├── annotations +│ │ ├── training +│ │ │ ├── ADE_train_00000001.png +│ │ │ ├── ADE_train_00000002.png +│ │ │ ├── ... +│ │ ├── validation +│ │ │ ├── ADE_val_00000001.png +│ │ │ ├── ADE_val_00000002.png +│ │ │ ├── ... +│ ├── annotations_instance +│ │ ├── training +│ │ │ ├── ADE_train_00000001.png +│ │ │ ├── ADE_train_00000002.png +│ │ │ ├── ... +│ │ ├── validation +│ │ │ ├── ADE_val_00000001.png +│ │ │ ├── ADE_val_00000002.png +│ │ │ ├── ... +│ ├── categoryMapping.txt +│ ├── images +│ │ ├── training +│ │ │ ├── ADE_train_00000001.jpg +│ │ │ ├── ADE_train_00000002.jpg +│ │ │ ├── ... +│ │ ├── validation +│ │ │ ├── ADE_val_00000001.jpg +│ │ │ ├── ADE_val_00000002.jpg +│ │ │ ├── ... +│ ├── imgCatIds.json +│ ├── objectInfo150.txt +│ │── sceneCategories.txt +``` + +The above folders include all data of ADE20K's semantic segmentation, instance segmentation, and panoptic segmentation. + +### Download from OpenDataLab + +By using [OpenDataLab](https://opendatalab.com/), researchers can obtain free formatted datasets in various fields. Through the search function of the platform, researchers may address the dataset they look for quickly and easily. Using the formatted datasets from the platform, researchers can efficiently conduct tasks across datasets. + +Currently, MIM supports downloading VOC and COCO datasets from OpenDataLab with one command line. More datasets will be supported in the future. You can also directly download the datasets you need from the OpenDataLab platform and then convert them to the format required by MMDetection. + +If you use MIM to download, make sure that the version is greater than v0.3.8. You can use the following command to update: + +```Bash +pip install -U openmim +``` + +```Bash +# install OpenXLab CLI tools +pip install -U openxlab +# log in OpenXLab, registry +openxlab login + +# download voc2007 and preprocess by MIM +mim download mmdet --dataset voc2007 + +# download voc2012 and preprocess by MIM +mim download mmdet --dataset voc2012 + +# download coco2017 and preprocess by MIM +mim download mmdet --dataset coco2017 +``` diff --git a/mmdetection/docs/en/user_guides/deploy.md b/mmdetection/docs/en/user_guides/deploy.md new file mode 100644 index 00000000..db320d14 --- /dev/null +++ b/mmdetection/docs/en/user_guides/deploy.md @@ -0,0 +1,173 @@ +# Model Deployment + +The deployment of OpenMMLab codebases, including MMDetection, MMPretrain and so on are supported by [MMDeploy](https://github.com/open-mmlab/mmdeploy). +The latest deployment guide for MMDetection can be found from [here](https://mmdeploy.readthedocs.io/en/dev-1.x/04-supported-codebases/mmdet.html). + +This tutorial is organized as follows: + +- [Installation](#installation) +- [Convert model](#convert-model) +- [Model specification](#model-specification) +- [Model inference](#model-inference) + - [Backend model inference](#backend-model-inference) + - [SDK model inference](#sdk-model-inference) +- [Supported models](#supported-models) + +## Installation + +Please follow the [guide](https://mmdetection.readthedocs.io/en/latest/get_started.html) to install mmdet. And then install mmdeploy from source by following [this](https://mmdeploy.readthedocs.io/en/1.x/get_started.html#installation) guide. + +```{note} +If you install mmdeploy prebuilt package, please also clone its repository by 'git clone https://github.com/open-mmlab/mmdeploy.git --depth=1' to get the deployment config files. +``` + +## Convert model + +Suppose mmdetection and mmdeploy repositories are in the same directory, and the working directory is the root path of mmdetection. + +Take [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py) model as an example. You can download its checkpoint from [here](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth), and then convert it to onnx model as follows: + +```python +from mmdeploy.apis import torch2onnx +from mmdeploy.backend.sdk.export_info import export2SDK + +img = 'demo/demo.jpg' +work_dir = 'mmdeploy_models/mmdet/onnx' +save_file = 'end2end.onnx' +deploy_cfg = '../mmdeploy/configs/mmdet/detection/detection_onnxruntime_dynamic.py' +model_cfg = 'configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model_checkpoint = 'faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth' +device = 'cpu' + +# 1. convert model to onnx +torch2onnx(img, work_dir, save_file, deploy_cfg, model_cfg, + model_checkpoint, device) + +# 2. extract pipeline info for inference by MMDeploy SDK +export2SDK(deploy_cfg, model_cfg, work_dir, pth=model_checkpoint, + device=device) +``` + +It is crucial to specify the correct deployment config during model conversion. MMDeploy has already provided builtin deployment config [files](https://github.com/open-mmlab/mmdeploy/tree/1.x/configs/mmdet) of all supported backends for mmdetection, under which the config file path follows the pattern: + +``` +{task}/{task}_{backend}-{precision}_{static | dynamic}_{shape}.py +``` + +- **{task}:** task in mmdetection. + + There are two of them. One is `detection` and the other is `instance-seg`, indicating instance segmentation. + + mmdet models like `RetinaNet`, `Faster R-CNN` and `DETR` and so on belongs to `detection` task. While `Mask R-CNN` is one of `instance-seg` models. + + **DO REMEMBER TO USE** `detection/detection_*.py` deployment config file when trying to convert detection models and use `instance-seg/instance-seg_*.py` to deploy instance segmentation models. + +- **{backend}:** inference backend, such as onnxruntime, tensorrt, pplnn, ncnn, openvino, coreml etc. + +- **{precision}:** fp16, int8. When it's empty, it means fp32 + +- **{static | dynamic}:** static shape or dynamic shape + +- **{shape}:** input shape or shape range of a model + +Therefore, in the above example, you can also convert `Faster R-CNN` to tensorrt-fp16 model by `detection_tensorrt-fp16_dynamic-320x320-1344x1344.py`. + +```{tip} +When converting mmdet models to tensorrt models, --device should be set to "cuda" +``` + +## Model specification + +Before moving on to model inference chapter, let's know more about the converted model structure which is very important for model inference. + +The converted model locates in the working directory like `mmdeploy_models/mmdet/onnx` in the previous example. It includes: + +``` +mmdeploy_models/mmdet/onnx +├── deploy.json +├── detail.json +├── end2end.onnx +└── pipeline.json +``` + +in which, + +- **end2end.onnx**: backend model which can be inferred by ONNX Runtime +- ***xxx*.json**: the necessary information for mmdeploy SDK + +The whole package **mmdeploy_models/mmdet/onnx** is defined as **mmdeploy SDK model**, i.e., **mmdeploy SDK model** includes both backend model and inference meta information. + +## Model inference + +### Backend model inference + +Take the previous converted `end2end.onnx` model as an example, you can use the following code to inference the model and visualize the results. + +```python +from mmdeploy.apis.utils import build_task_processor +from mmdeploy.utils import get_input_shape, load_config +import torch + +deploy_cfg = '../mmdeploy/configs/mmdet/detection/detection_onnxruntime_dynamic.py' +model_cfg = 'configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +device = 'cpu' +backend_model = ['mmdeploy_models/mmdet/onnx/end2end.onnx'] +image = 'demo/demo.jpg' + +# read deploy_cfg and model_cfg +deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg) + +# build task and backend model +task_processor = build_task_processor(model_cfg, deploy_cfg, device) +model = task_processor.build_backend_model(backend_model) + +# process input image +input_shape = get_input_shape(deploy_cfg) +model_inputs, _ = task_processor.create_input(image, input_shape) + +# do model inference +with torch.no_grad(): + result = model.test_step(model_inputs) + +# visualize results +task_processor.visualize( + image=image, + model=model, + result=result[0], + window_name='visualize', + output_file='output_detection.png') +``` + +### SDK model inference + +You can also perform SDK model inference like following, + +```python +from mmdeploy_python import Detector +import cv2 + +img = cv2.imread('demo/demo.jpg') + +# create a detector +detector = Detector(model_path='mmdeploy_models/mmdet/onnx', + device_name='cpu', device_id=0) +# perform inference +bboxes, labels, masks = detector(img) + +# visualize inference result +indices = [i for i in range(len(bboxes))] +for index, bbox, label_id in zip(indices, bboxes, labels): + [left, top, right, bottom], score = bbox[0:4].astype(int), bbox[4] + if score < 0.3: + continue + + cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0)) + +cv2.imwrite('output_detection.png', img) +``` + +Besides python API, mmdeploy SDK also provides other FFI (Foreign Function Interface), such as C, C++, C#, Java and so on. You can learn their usage from [demos](https://github.com/open-mmlab/mmdeploy/tree/1.x/demo). + +## Supported models + +Please refer to [here](https://mmdeploy.readthedocs.io/en/1.x/04-supported-codebases/mmdet.html#supported-models) for the supported model list. diff --git a/mmdetection/docs/en/user_guides/finetune.md b/mmdetection/docs/en/user_guides/finetune.md new file mode 100644 index 00000000..e181ebae --- /dev/null +++ b/mmdetection/docs/en/user_guides/finetune.md @@ -0,0 +1,96 @@ +# Finetuning Models + +Detectors pre-trained on the COCO dataset can serve as a good pre-trained model for other datasets, e.g., CityScapes and KITTI Dataset. +This tutorial provides instructions for users to use the models provided in the [Model Zoo](../model_zoo.md) for other datasets to obtain better performance. + +There are two steps to finetune a model on a new dataset. + +- Add support for the new dataset following [Customize Datasets](../advanced_guides/customize_dataset.md). +- Modify the configs as will be discussed in this tutorial. + +Take the finetuning process on Cityscapes Dataset as an example, the users need to modify five parts in the config. + +## Inherit base configs + +To release the burden and reduce bugs in writing the whole configs, MMDetection V3.0 support inheriting configs from multiple existing configs. To finetune a Mask RCNN model, the new config needs to inherit +`_base_/models/mask-rcnn_r50_fpn.py` to build the basic structure of the model. To use the Cityscapes Dataset, the new config can also simply inherit `_base_/datasets/cityscapes_instance.py`. For runtime settings such as logger settings, the new config needs to inherit `_base_/default_runtime.py`. For training schedules, the new config can to inherit `_base_/schedules/schedule_1x.py`. These configs are in the `configs` directory and the users can also choose to write the whole contents rather than use inheritance. + +```python +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/cityscapes_instance.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_1x.py' +] +``` + +## Modify head + +Then the new config needs to modify the head according to the class numbers of the new datasets. By only changing `num_classes` in the roi_head, the weights of the pre-trained models are mostly reused except for the final prediction head. + +```python +model = dict( + roi_head=dict( + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=8, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), + mask_head=dict( + type='FCNMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=8, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)))) +``` + +## Modify dataset + +The users may also need to prepare the dataset and write the configs about dataset, refer to [Customize Datasets](../advanced_guides/customize_dataset.md) for more detail. MMDetection V3.0 already supports VOC, WIDERFACE, COCO, LIVS, OpenImages, DeepFashion, Objects365, and Cityscapes Dataset. + +## Modify training schedule + +The finetuning hyperparameters vary from the default schedule. It usually requires a smaller learning rate and fewer training epochs + +```python +# optimizer +# lr is set for a batch size of 8 +optim_wrapper = dict(optimizer=dict(lr=0.01)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=8, + by_epoch=True, + milestones=[7], + gamma=0.1) +] + +# max_epochs +train_cfg = dict(max_epochs=8) + +# log config +default_hooks = dict(logger=dict(interval=100)), +``` + +## Use pre-trained model + +To use the pre-trained model, the new config adds the link of pre-trained models in the `load_from`. The users might need to download the model weights before training to avoid the download time during training. + +```python +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth' # noqa +``` diff --git a/mmdetection/docs/en/user_guides/index.rst b/mmdetection/docs/en/user_guides/index.rst new file mode 100644 index 00000000..e74fc5fb --- /dev/null +++ b/mmdetection/docs/en/user_guides/index.rst @@ -0,0 +1,41 @@ +Train & Test +************** + +MMDetection provides hundreds of pretrained detection models in `Model Zoo `_, +and supports multiple standard datasets, including Pascal VOC, COCO, CityScapes, LVIS, etc. This note will show how to perform common tasks on these existing models and standard datasets: + + +.. toctree:: + :maxdepth: 1 + + config.md + inference.md + dataset_prepare.md + test.md + train.md + new_model.md + finetune.md + test_results_submission.md + init_cfg.md + single_stage_as_rpn.md + semi_det.md + + +Useful Tools +************ + +.. toctree:: + :maxdepth: 1 + + useful_tools.md + useful_hooks.md + visualization.md + robustness_benchmarking.md + deploy.md + label_studio.md + tracking_analysis_tools.md + tracking_config.md + tracking_dataset_prepare.md + tracking_inference.md + tracking_train_test.md + tracking_visualization.md diff --git a/mmdetection/docs/en/user_guides/inference.md b/mmdetection/docs/en/user_guides/inference.md new file mode 100644 index 00000000..49186d23 --- /dev/null +++ b/mmdetection/docs/en/user_guides/inference.md @@ -0,0 +1,440 @@ +# Inference with existing models + +MMDetection provides hundreds of pre-trained detection models in [Model Zoo](https://mmdetection.readthedocs.io/en/latest/model_zoo.html). +This note will show how to inference, which means using trained models to detect objects on images. + +In MMDetection, a model is defined by a [configuration file](https://mmdetection.readthedocs.io/en/latest/user_guides/config.html) and existing model parameters are saved in a checkpoint file. + +To start with, we recommend [RTMDet](https://github.com/open-mmlab/mmdetection/tree/main/configs/rtmdet) with this [configuration file](https://github.com/open-mmlab/mmdetection/blob/main/configs/rtmdet/rtmdet_l_8xb32-300e_coco.py) and this [checkpoint file](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_l_8xb32-300e_coco/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth). It is recommended to download the checkpoint file to `checkpoints` directory. + +## High-level APIs for inference - `Inferencer` + +In OpenMMLab, all the inference operations are unified into a new interface - Inferencer. Inferencer is designed to expose a neat and simple API to users, and shares very similar interface across different OpenMMLab libraries. +A notebook demo can be found in [demo/inference_demo.ipynb](https://github.com/open-mmlab/mmdetection/blob/main/demo/inference_demo.ipynb). + +### Basic Usage + +You can get inference results for an image with only 3 lines of code. + +```python +from mmdet.apis import DetInferencer + +# Initialize the DetInferencer +inferencer = DetInferencer('rtmdet_tiny_8xb32-300e_coco') + +# Perform inference +inferencer('demo/demo.jpg', show=True) +``` + +The resulting output will be displayed in a new window:. + +
    + +
    + +```{note} +If you are running MMDetection on a server without GUI or via SSH tunnel with X11 forwarding disabled, the `show` option will not work. However, you can still save visualizations to files by setting `out_dir` arguments. Read [Dumping Results](#dumping-results) for details. +``` + +### Initialization + +Each Inferencer must be initialized with a model. You can also choose the inference device during initialization. + +#### Model Initialization + +- To infer with MMDetection's pre-trained model, passing its name to the argument `model` can work. The weights will be automatically downloaded and loaded from OpenMMLab's model zoo. + + ```python + inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco') + ``` + + There is a very easy to list all model names in MMDetection. + + ```python + # models is a list of model names, and them will print automatically + models = DetInferencer.list_models('mmdet') + ``` + + You can load another weight by passing its path/url to `weights`. + + ```python + inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', weights='path/to/rtmdet.pth') + ``` + +- To load custom config and weight, you can pass the path to the config file to `model` and the path to the weight to `weights`. + + ```python + inferencer = DetInferencer(model='path/to/rtmdet_config.py', weights='path/to/rtmdet.pth') + ``` + +- By default, [MMEngine](https://github.com/open-mmlab/mmengine/) dumps config to the weight. If you have a weight trained on MMEngine, you can also pass the path to the weight file to `weights` without specifying `model`: + + ```python + # It will raise an error if the config file cannot be found in the weight. Currently, within the MMDetection model repository, only the weights of ddq-detr-4scale_r50 can be loaded in this manner. + inferencer = DetInferencer(weights='https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq-detr-4scale_r50_8xb2-12e_coco/ddq-detr-4scale_r50_8xb2-12e_coco_20230809_170711-42528127.pth') + ``` + +- Passing config file to `model` without specifying `weight` will result in a randomly initialized model. + +### Device + +Each Inferencer instance is bound to a device. +By default, the best device is automatically decided by [MMEngine](https://github.com/open-mmlab/mmengine/). You can also alter the device by specifying the `device` argument. For example, you can use the following code to create an Inferencer on GPU 1. + +```python +inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', device='cuda:1') +``` + +To create an Inferencer on CPU: + +```python +inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', device='cpu') +``` + +Refer to [torch.device](https://pytorch.org/docs/stable/tensor_attributes.html#torch.device) for all the supported forms. + +### Inference + +Once the Inferencer is initialized, you can directly pass in the raw data to be inferred and get the inference results from return values. + +#### Input + +Input can be either of these types: + +- str: Path/URL to the image. + + ```python + inferencer('demo/demo.jpg') + ``` + +- array: Image in numpy array. It should be in BGR order. + + ```python + import mmcv + array = mmcv.imread('demo/demo.jpg') + inferencer(array) + ``` + +- list: A list of basic types above. Each element in the list will be processed separately. + + ```python + inferencer(['img_1.jpg', 'img_2.jpg]) + # You can even mix the types + inferencer(['img_1.jpg', array]) + ``` + +- str: Path to the directory. All images in the directory will be processed. + + ```python + inferencer('path/to/your_imgs/') + ``` + +### Output + +By default, each `Inferencer` returns the prediction results in a dictionary format. + +- `visualization` contains the visualized predictions. + +- `predictions` contains the predictions results in a json-serializable format. But it's an empty list by default unless `return_vis=True`. + +```python +{ + 'predictions' : [ + # Each instance corresponds to an input image + { + 'labels': [...], # int list of length (N, ) + 'scores': [...], # float list of length (N, ) + 'bboxes': [...], # 2d list of shape (N, 4), format: [min_x, min_y, max_x, max_y] + }, + ... + ], + 'visualization' : [ + array(..., dtype=uint8), + ] + } +``` + +If you wish to get the raw outputs from the model, you can set `return_datasamples` to `True` to get the original [DataSample](advanced_guides/structures.md), which will be stored in `predictions`. + +#### Dumping Results + +Apart from obtaining predictions from the return value, you can also export the predictions/visualizations to files by setting `out_dir` and `no_save_pred`/`no_save_vis` arguments. + +```python +inferencer('demo/demo.jpg', out_dir='outputs/', no_save_pred=False) +``` + +Results in the directory structure like: + +```text +outputs +├── preds +│ └── demo.json +└── vis + └── demo.jpg +``` + +The filename of each file is the same as the corresponding input image filename. If the input image is an array, the filename will be a number starting from 0. + +#### Batch Inference + +You can customize the batch size by setting `batch_size`. The default batch size is 1. + +### API + +Here are extensive lists of parameters that you can use. + +- **DetInferencer.\_\_init\_\_():** + +| Arguments | Type | Type | Description | +| --------------- | ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `model` | str, optional | None | Path to the config file or the model name defined in metafile. For example, it could be 'rtmdet-s' or 'rtmdet_s_8xb32-300e_coco' or 'configs/rtmdet/rtmdet_s_8xb32-300e_coco.py'. If the model is not specified, the user must provide the `weights` saved by MMEngine which contains the config string. | +| `weights` | str, optional | None | Path to the checkpoint. If it is not specified and `model` is a model name of metafile, the weights will be loaded from metafile. | +| `device` | str, optional | None | Device used for inference, accepting all allowed strings by `torch.device`. E.g., 'cuda:0' or 'cpu'. If None, the available device will be automatically used. | +| `scope` | str, optional | 'mmdet' | The scope of the model. | +| `palette` | str | 'none' | Color palette used for visualization. The order of priority is palette -> config -> checkpoint. | +| `show_progress` | bool | True | Control whether to display the progress bar during the inference process. | + +- **DetInferencer.\_\_call\_\_()** + +| Arguments | Type | Default | Description | +| -------------------- | ------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `inputs` | str/list/tuple/np.array | **required** | It can be a path to an image/a folder, an np array or a list/tuple (with img paths or np arrays) | +| `batch_size` | int | 1 | Inference batch size. | +| `print_result` | bool | False | Whether to print the inference result to the console. | +| `show` | bool | False | Whether to display the visualization results in a popup window. | +| `wait_time` | float | 0 | The interval of show(s). | +| `no_save_vis` | bool | False | Whether to force not to save prediction vis results. | +| `draw_pred` | bool | True | Whether to draw predicted bounding boxes. | +| `pred_score_thr` | float | 0.3 | Minimum score of bboxes to draw. | +| `return_datasamples` | bool | False | Whether to return results as DataSamples. If False, the results will be packed into a dict. | +| `print_result` | bool | False | Whether to print the inference result to the console. | +| `no_save_pred` | bool | True | Whether to force not to save prediction results. | +| `out_dir` | str | '' | Output directory of results. | +| `texts` | str/list\[str\], optional | None | Text prompts. | +| `stuff_texts` | str/list\[str\], optional | None | Stuff text prompts of open panoptic task. | +| `custom_entities` | bool | False | Whether to use custom entities. Only used in GLIP. | +| \*\*kwargs | | | Other keyword arguments passed to :meth:`preprocess`, :meth:`forward`, :meth:`visualize` and :meth:`postprocess`. Each key in kwargs should be in the corresponding set of `preprocess_kwargs`, `forward_kwargs`, `visualize_kwargs` and `postprocess_kwargs`. | + +## Demos + +We also provide four demo scripts, implemented with high-level APIs and supporting functionality codes. +Source codes are available [here](https://github.com/open-mmlab/mmdetection/blob/main/demo). + +### Image demo + +This script performs inference on a single image. + +```shell +python demo/image_demo.py \ + ${IMAGE_FILE} \ + ${CONFIG_FILE} \ + [--weights ${WEIGHTS}] \ + [--device ${GPU_ID}] \ + [--pred-score-thr ${SCORE_THR}] +``` + +Examples: + +```shell +python demo/image_demo.py demo/demo.jpg \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + --weights checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --device cpu +``` + +### Webcam demo + +This is a live demo from a webcam. + +```shell +python demo/webcam_demo.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--device ${GPU_ID}] \ + [--camera-id ${CAMERA-ID}] \ + [--score-thr ${SCORE_THR}] +``` + +Examples: + +```shell +python demo/webcam_demo.py \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth +``` + +### Video demo + +This script performs inference on a video. + +```shell +python demo/video_demo.py \ + ${VIDEO_FILE} \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--device ${GPU_ID}] \ + [--score-thr ${SCORE_THR}] \ + [--out ${OUT_FILE}] \ + [--show] \ + [--wait-time ${WAIT_TIME}] +``` + +Examples: + +```shell +python demo/video_demo.py demo/demo.mp4 \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --out result.mp4 +``` + +#### Video demo with GPU acceleration + +This script performs inference on a video with GPU acceleration. + +```shell +python demo/video_gpuaccel_demo.py \ + ${VIDEO_FILE} \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--device ${GPU_ID}] \ + [--score-thr ${SCORE_THR}] \ + [--nvdecode] \ + [--out ${OUT_FILE}] \ + [--show] \ + [--wait-time ${WAIT_TIME}] +``` + +Examples: + +```shell +python demo/video_gpuaccel_demo.py demo/demo.mp4 \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --nvdecode --out result.mp4 +``` + +### Large-image inference demo + +This is a script for slicing inference on large images. + +``` +python demo/large_image_demo.py \ + ${IMG_PATH} \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + --device ${GPU_ID} \ + --show \ + --tta \ + --score-thr ${SCORE_THR} \ + --patch-size ${PATCH_SIZE} \ + --patch-overlap-ratio ${PATCH_OVERLAP_RATIO} \ + --merge-iou-thr ${MERGE_IOU_THR} \ + --merge-nms-type ${MERGE_NMS_TYPE} \ + --batch-size ${BATCH_SIZE} \ + --debug \ + --save-patch +``` + +Examples: + +```shell +# inferecnce without tta +wget -P checkpoint https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_fpn_2x_coco/faster_rcnn_r101_fpn_2x_coco_bbox_mAP-0.398_20200504_210455-1d2dac9c.pth + +python demo/large_image_demo.py \ + demo/large_image.jpg \ + configs/faster_rcnn/faster-rcnn_r101_fpn_2x_coco.py \ + checkpoint/faster_rcnn_r101_fpn_2x_coco_bbox_mAP-0.398_20200504_210455-1d2dac9c.pth + +# inference with tta +wget -P checkpoint https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_1x_coco/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth + +python demo/large_image_demo.py \ + demo/large_image.jpg \ + configs/retinanet/retinanet_r50_fpn_1x_coco.py \ + checkpoint/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth --tta + +``` + +## Multi-modal algorithm inference demo and evaluation + +As multimodal vision algorithms continue to evolve, MMDetection has also supported such algorithms. This section demonstrates how to use the demo and eval scripts corresponding to multimodal algorithms using the GLIP algorithm and model as the example. Moreover, MMDetection integrated a [gradio_demo project](../../../projects/gradio_demo/), which allows developers to quickly play with all image input tasks in MMDetection on their local devices. Check the [document](../../../projects/gradio_demo/README.md) for more details. + +### Preparation + +Please first make sure that you have the correct dependencies installed: + +```shell +# if source +pip install -r requirements/multimodal.txt + +# if wheel +mim install mmdet[multimodal] +``` + +MMDetection has already implemented GLIP algorithms and provided the weights, you can download directly from urls: + +```shell +cd mmdetection +wget https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_a_mmdet-b3654169.pth +``` + +### Inference + +Once the model is successfully downloaded, you can use the `demo/image_demo.py` script to run the inference. + +```shell +python demo/image_demo.py demo/demo.jpg glip_tiny_a_mmdet-b3654169.pth --texts bench +``` + +Demo result will be similar to this: + +
    + +
    + +If users would like to detect multiple targets, please declare them in the format of `xx. xx` after the `--texts`. + +```shell +python demo/image_demo.py demo/demo.jpg glip_tiny_a_mmdet-b3654169.pth --texts 'bench. car' +``` + +And the result will be like this one: + +
    + +
    + +You can also use a sentence as the input prompt for the `--texts` field, for example: + +```shell +python demo/image_demo.py demo/demo.jpg glip_tiny_a_mmdet-b3654169.pth --texts 'There are a lot of cars here.' +``` + +The result will be similar to this: + +
    + +
    + +### Evaluation + +The GLIP implementation in MMDetection does not have any performance degradation, our benchmark is as follows: + +| Model | official mAP | mmdet mAP | +| ----------------------- | :----------: | :-------: | +| glip_A_Swin_T_O365.yaml | 42.9 | 43.0 | +| glip_Swin_T_O365.yaml | 44.9 | 44.9 | +| glip_Swin_L.yaml | 51.4 | 51.3 | + +Users can use the test script we provided to run evaluation as well. Here is a basic example: + +```shell +# 1 gpu +python tools/test.py configs/glip/glip_atss_swin-t_fpn_dyhead_pretrain_obj365.py glip_tiny_a_mmdet-b3654169.pth + +# 8 GPU +./tools/dist_test.sh configs/glip/glip_atss_swin-t_fpn_dyhead_pretrain_obj365.py glip_tiny_a_mmdet-b3654169.pth 8 +``` diff --git a/mmdetection/docs/en/user_guides/init_cfg.md b/mmdetection/docs/en/user_guides/init_cfg.md new file mode 100644 index 00000000..312b67a8 --- /dev/null +++ b/mmdetection/docs/en/user_guides/init_cfg.md @@ -0,0 +1,161 @@ +# Weight initialization + +During training, a proper initialization strategy is beneficial to speeding up the training or obtaining a higher performance. [MMCV](https://github.com/open-mmlab/mmcv/blob/master/mmcv/cnn/utils/weight_init.py) provide some commonly used methods for initializing modules like `nn.Conv2d`. Model initialization in MMdetection mainly uses `init_cfg`. Users can initialize models with following two steps: + +1. Define `init_cfg` for a model or its components in `model_cfg`, but `init_cfg` of children components have higher priority and will override `init_cfg` of parents modules. +2. Build model as usual, but call `model.init_weights()` method explicitly, and model parameters will be initialized as configuration. + +The high-level workflow of initialization in MMdetection is : + +model_cfg(init_cfg) -> build_from_cfg -> model -> init_weight() -> initialize(self, self.init_cfg) -> children's init_weight() + +### Description + +It is dict or list\[dict\], and contains the following keys and values: + +- `type` (str), containing the initializer name in `INTIALIZERS`, and followed by arguments of the initializer. +- `layer` (str or list\[str\]), containing the names of basic layers in Pytorch or MMCV with learnable parameters that will be initialized, e.g. `'Conv2d'`,`'DeformConv2d'`. +- `override` (dict or list\[dict\]), containing the sub-modules that not inherit from BaseModule and whose initialization configuration is different from other layers' which are in `'layer'` key. Initializer defined in `type` will work for all layers defined in `layer`, so if sub-modules are not derived Classes of `BaseModule` but can be initialized as same ways of layers in `layer`, it does not need to use `override`. `override` contains: + - `type` followed by arguments of initializer; + - `name` to indicate sub-module which will be initialized. + +### Initialize parameters + +Inherit a new model from `mmcv.runner.BaseModule` or `mmdet.models` Here we show an example of FooModel. + +```python +import torch.nn as nn +from mmcv.runner import BaseModule + +class FooModel(BaseModule) + def __init__(self, + arg1, + arg2, + init_cfg=None): + super(FooModel, self).__init__(init_cfg) + ... +``` + +- Initialize model by using `init_cfg` directly in code + + ```python + import torch.nn as nn + from mmcv.runner import BaseModule + # or directly inherit mmdet models + + class FooModel(BaseModule) + def __init__(self, + arg1, + arg2, + init_cfg=XXX): + super(FooModel, self).__init__(init_cfg) + ... + ``` + +- Initialize model by using `init_cfg` directly in `mmcv.Sequential` or `mmcv.ModuleList` code + + ```python + from mmcv.runner import BaseModule, ModuleList + + class FooModel(BaseModule) + def __init__(self, + arg1, + arg2, + init_cfg=None): + super(FooModel, self).__init__(init_cfg) + ... + self.conv1 = ModuleList(init_cfg=XXX) + ``` + +- Initialize model by using `init_cfg` in config file + + ```python + model = dict( + ... + model = dict( + type='FooModel', + arg1=XXX, + arg2=XXX, + init_cfg=XXX), + ... + ``` + +### Usage of init_cfg + +1. Initialize model by `layer` key + + If we only define `layer`, it just initialize the layer in `layer` key. + + NOTE: Value of `layer` key is the class name with attributes weights and bias of Pytorch, (so such as `MultiheadAttention layer` is not supported). + +- Define `layer` key for initializing module with same configuration. + + ```python + init_cfg = dict(type='Constant', layer=['Conv1d', 'Conv2d', 'Linear'], val=1) + # initialize whole module with same configuration + ``` + +- Define `layer` key for initializing layer with different configurations. + +```python +init_cfg = [dict(type='Constant', layer='Conv1d', val=1), + dict(type='Constant', layer='Conv2d', val=2), + dict(type='Constant', layer='Linear', val=3)] +# nn.Conv1d will be initialized with dict(type='Constant', val=1) +# nn.Conv2d will be initialized with dict(type='Constant', val=2) +# nn.Linear will be initialized with dict(type='Constant', val=3) +``` + +2. Initialize model by `override` key + +- When initializing some specific part with its attribute name, we can use `override` key, and the value in `override` will ignore the value in init_cfg. + + ```python + # layers: + # self.feat = nn.Conv1d(3, 1, 3) + # self.reg = nn.Conv2d(3, 3, 3) + # self.cls = nn.Linear(1,2) + + init_cfg = dict(type='Constant', + layer=['Conv1d','Conv2d'], val=1, bias=2, + override=dict(type='Constant', name='reg', val=3, bias=4)) + # self.feat and self.cls will be initialized with dict(type='Constant', val=1, bias=2) + # The module called 'reg' will be initialized with dict(type='Constant', val=3, bias=4) + ``` + +- If `layer` is None in init_cfg, only sub-module with the name in override will be initialized, and type and other args in override can be omitted. + + ```python + # layers: + # self.feat = nn.Conv1d(3, 1, 3) + # self.reg = nn.Conv2d(3, 3, 3) + # self.cls = nn.Linear(1,2) + + init_cfg = dict(type='Constant', val=1, bias=2, override=dict(name='reg')) + + # self.feat and self.cls will be initialized by Pytorch + # The module called 'reg' will be initialized with dict(type='Constant', val=1, bias=2) + ``` + +- If we don't define `layer` key or `override` key, it will not initialize anything. + +- Invalid usage + + ```python + # It is invalid that override don't have name key + init_cfg = dict(type='Constant', layer=['Conv1d','Conv2d'], val=1, bias=2, + override=dict(type='Constant', val=3, bias=4)) + + # It is also invalid that override has name and other args except type + init_cfg = dict(type='Constant', layer=['Conv1d','Conv2d'], val=1, bias=2, + override=dict(name='reg', val=3, bias=4)) + ``` + +3. Initialize model with the pretrained model + + ```python + init_cfg = dict(type='Pretrained', + checkpoint='torchvision://resnet50') + ``` + +More details can refer to the documentation in [MMEngine](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/initialize.html) diff --git a/mmdetection/docs/en/user_guides/label_studio.md b/mmdetection/docs/en/user_guides/label_studio.md new file mode 100644 index 00000000..d4b37447 --- /dev/null +++ b/mmdetection/docs/en/user_guides/label_studio.md @@ -0,0 +1,256 @@ +# Semi-automatic Object Detection Annotation with MMDetection and Label-Studio + +Annotation data is a time-consuming and laborious task. This article introduces how to perform semi-automatic annotation using the RTMDet algorithm in MMDetection in conjunction with Label-Studio software. Specifically, using RTMDet to predict image annotations and then refining the annotations with Label-Studio. Community users can refer to this process and methodology and apply it to other fields. + +- RTMDet: RTMDet is a high-precision single-stage object detection algorithm developed by OpenMMLab, open-sourced in the MMDetection object detection toolbox. Its open-source license is Apache 2.0, and it can be used freely without restrictions by industrial users. + +- [Label Studio](https://github.com/heartexlabs/label-studio) is an excellent annotation software covering the functionality of dataset annotation in areas such as image classification, object detection, and segmentation. + +In this article, we will use [cat](https://download.openmmlab.com/mmyolo/data/cat_dataset.zip) images for semi-automatic annotation. + +## Environment Configuration + +To begin with, you need to create a virtual environment and then install PyTorch and MMCV. In this article, we will specify the versions of PyTorch and MMCV. Next, you can install MMDetection, Label-Studio, and label-studio-ml-backend using the following steps: + +Create a virtual environment: + +```shell +conda create -n rtmdet python=3.9 -y +conda activate rtmdet +``` + +Install PyTorch: + +```shell +# Linux and Windows CPU only +pip install torch==1.10.1+cpu torchvision==0.11.2+cpu torchaudio==0.10.1 -f https://download.pytorch.org/whl/cpu/torch_stable.html +# Linux and Windows CUDA 11.3 +pip install torch==1.10.1+cu113 torchvision==0.11.2+cu113 torchaudio==0.10.1 -f https://download.pytorch.org/whl/cu113/torch_stable.html +# OSX +pip install torch==1.10.1 torchvision==0.11.2 torchaudio==0.10.1 +``` + +Install MMCV: + +```shell +pip install -U openmim +mim install "mmcv>=2.0.0" +# Installing mmcv will automatically install mmengine +``` + +Install MMDetection: + +```shell +git clone https://github.com/open-mmlab/mmdetection +cd mmdetection +pip install -v -e . +``` + +Install Label-Studio and label-studio-ml-backend: + +```shell +# Installing Label-Studio may take some time, if the version is not found, please use the official source +pip install label-studio==1.7.2 +pip install label-studio-ml==1.0.9 +``` + +Download the rtmdet weights: + +```shell +cd path/to/mmetection +mkdir work_dirs +cd work_dirs +wget https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_m_8xb32-300e_coco/rtmdet_m_8xb32-300e_coco_20220719_112220-229f527c.pth +``` + +## Start the Service + +Start the RTMDet backend inference service: + +```shell +cd path/to/mmetection + +label-studio-ml start projects/LabelStudio/backend_template --with \ +config_file=configs/rtmdet/rtmdet_m_8xb32-300e_coco.py \ +checkpoint_file=./work_dirs/rtmdet_m_8xb32-300e_coco_20220719_112220-229f527c.pth \ +device=cpu \ +--port 8003 +# Set device=cpu to use CPU inference, and replace cpu with cuda:0 to use GPU inference. +``` + +![](https://cdn.vansin.top/picgo20230330131601.png) + +The RTMDet backend inference service has now been started. To configure it in the Label-Studio web system, use http://localhost:8003 as the backend inference service. + +Now, start the Label-Studio web service: + +```shell +label-studio start +``` + +![](https://cdn.vansin.top/picgo20230330132913.png) + +Open your web browser and go to http://localhost:8080/ to see the Label-Studio interface. + +![](https://cdn.vansin.top/picgo20230330133118.png) + +Register a user and then create an RTMDet-Semiautomatic-Label project. + +![](https://cdn.vansin.top/picgo20230330133333.png) + +Download the example cat images by running the following command and import them using the Data Import button: + +```shell +cd path/to/mmetection +mkdir data && cd data + +wget https://download.openmmlab.com/mmyolo/data/cat_dataset.zip && unzip cat_dataset.zip +``` + +![](https://cdn.vansin.top/picgo20230330133628.png) + +![](https://cdn.vansin.top/picgo20230330133715.png) + +Then, select the Object Detection With Bounding Boxes template. + +![](https://cdn.vansin.top/picgo20230330133807.png) + +```shell +airplane +apple +backpack +banana +baseball_bat +baseball_glove +bear +bed +bench +bicycle +bird +boat +book +bottle +bowl +broccoli +bus +cake +car +carrot +cat +cell_phone +chair +clock +couch +cow +cup +dining_table +dog +donut +elephant +fire_hydrant +fork +frisbee +giraffe +hair_drier +handbag +horse +hot_dog +keyboard +kite +knife +laptop +microwave +motorcycle +mouse +orange +oven +parking_meter +person +pizza +potted_plant +refrigerator +remote +sandwich +scissors +sheep +sink +skateboard +skis +snowboard +spoon +sports_ball +stop_sign +suitcase +surfboard +teddy_bear +tennis_racket +tie +toaster +toilet +toothbrush +traffic_light +train +truck +tv +umbrella +vase +wine_glass +zebra +``` + +Then, copy and add the above categories to Label-Studio and click Save. + +![](https://cdn.vansin.top/picgo20230330134027.png) + +In the Settings, click Add Model to add the RTMDet backend inference service. + +![](https://cdn.vansin.top/picgo20230330134320.png) + +Click Validate and Save, and then click Start Labeling. + +![](https://cdn.vansin.top/picgo20230330134424.png) + +If you see Connected as shown below, the backend inference service has been successfully added. + +![](https://cdn.vansin.top/picgo20230330134554.png) + +## Start Semi-Automatic Labeling + +Click on Label to start labeling. + +![](https://cdn.vansin.top/picgo20230330134804.png) + +We can see that the RTMDet backend inference service has successfully returned the predicted results and displayed them on the image. However, we noticed that the predicted bounding boxes for the cats are a bit too large and not very accurate. + +![](https://cdn.vansin.top/picgo20230403104419.png) + +We manually adjust the position of the cat bounding box, and then click Submit to complete the annotation of this image. + +![](https://cdn.vansin.top/picgo/20230403105923.png) + +After submitting all images, click export to export the labeled dataset in COCO format. + +![](https://cdn.vansin.top/picgo20230330135921.png) + +Use VS Code to open the unzipped folder to see the labeled dataset, which includes the images and the annotation files in JSON format. + +![](https://cdn.vansin.top/picgo20230330140321.png) + +At this point, the semi-automatic labeling is complete. We can use this dataset to train a more accurate model in MMDetection and then continue semi-automatic labeling on newly collected images with this model. This way, we can iteratively expand the high-quality dataset and improve the accuracy of the model. + +## Use MMYOLO as the Backend Inference Service + +If you want to use Label-Studio in MMYOLO, you can refer to replacing the config_file and checkpoint_file with the configuration file and weight file of MMYOLO when starting the backend inference service. + +```shell +cd path/to/mmetection + +label-studio-ml start projects/LabelStudio/backend_template --with \ +config_file= path/to/mmyolo_config.py \ +checkpoint_file= path/to/mmyolo_weights.pth \ +device=cpu \ +--port 8003 +# device=cpu is for using CPU inference. If using GPU inference, replace cpu with cuda:0. +``` + +Rotation object detection and instance segmentation are still under development, please stay tuned. diff --git a/mmdetection/docs/en/user_guides/new_model.md b/mmdetection/docs/en/user_guides/new_model.md new file mode 100644 index 00000000..c7af855a --- /dev/null +++ b/mmdetection/docs/en/user_guides/new_model.md @@ -0,0 +1,290 @@ +# Train with customized models and standard datasets + +In this note, you will know how to train, test and inference your own customized models under standard datasets. We use the cityscapes dataset to train a customized Cascade Mask R-CNN R50 model as an example to demonstrate the whole process, which using [`AugFPN`](https://github.com/Gus-Guo/AugFPN) to replace the default `FPN` as neck, and add `Rotate` or `TranslateX` as training-time auto augmentation. + +The basic steps are as below: + +1. Prepare the standard dataset +2. Prepare your own customized model +3. Prepare a config +4. Train, test, and inference models on the standard dataset. + +## Prepare the standard dataset + +In this note, as we use the standard cityscapes dataset as an example. + +It is recommended to symlink the dataset root to `$MMDETECTION/data`. +If your folder structure is different, you may need to change the corresponding paths in config files. + +```none +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ ├── cityscapes +│ │ ├── annotations +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +│ ├── VOCdevkit +│ │ ├── VOC2007 +│ │ ├── VOC2012 + +``` + +Or you can set your dataset root through + +```bash +export MMDET_DATASETS=$data_root +``` + +We will replace dataset root with `$MMDET_DATASETS`, so you don't have to modify the corresponding path in config files. + +The cityscapes annotations have to be converted into the coco format using `tools/dataset_converters/cityscapes.py`: + +```shell +pip install cityscapesscripts +python tools/dataset_converters/cityscapes.py ./data/cityscapes --nproc 8 --out-dir ./data/cityscapes/annotations +``` + +Currently, the config files in `cityscapes` use COCO pre-trained weights to initialize. +You could download the pre-trained models in advance if the network is unavailable or slow, otherwise, it would cause errors at the beginning of training. + +## Prepare your own customized model + +The second step is to use your own module or training setting. Assume that we want to implement a new neck called `AugFPN` to replace with the default `FPN` under the existing detector Cascade Mask R-CNN R50. The following implements `AugFPN` under MMDetection. + +### 1. Define a new neck (e.g. AugFPN) + +Firstly create a new file `mmdet/models/necks/augfpn.py`. + +```python +import torch.nn as nn +from mmdet.registry import MODELS + + +@MODELS.register_module() +class AugFPN(nn.Module): + + def __init__(self, + in_channels, + out_channels, + num_outs, + start_level=0, + end_level=-1, + add_extra_convs=False): + pass + + def forward(self, inputs): + # implementation is ignored + pass +``` + +### 2. Import the module + +You can either add the following line to `mmdet/models/necks/__init__.py`, + +```python +from .augfpn import AugFPN +``` + +or alternatively add + +```python +custom_imports = dict( + imports=['mmdet.models.necks.augfpn'], + allow_failed_imports=False) +``` + +to the config file and avoid modifying the original code. + +### 3. Modify the config file + +```python +neck=dict( + type='AugFPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5) +``` + +For more detailed usages about customizing your own models (e.g. implement a new backbone, head, loss, etc) and runtime training settings (e.g. define a new optimizer, use gradient clip, customize training schedules and hooks, etc), please refer to the guideline [Customize Models](../advanced_guides/customize_models.md) and [Customize Runtime Settings](../advanced_guides/customize_runtime.md) respectively. + +## Prepare a config + +The third step is to prepare a config for your own training setting. Assume that we want to add `AugFPN` and `Rotate` or `Translate` augmentation to existing Cascade Mask R-CNN R50 to train the cityscapes dataset, and assume the config is under directory `configs/cityscapes/` and named as `cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes.py`, the config is as below. + +```python +# The new config inherits the base configs to highlight the necessary modification +_base_ = [ + '../_base_/models/cascade-mask-rcnn_r50_fpn.py', + '../_base_/datasets/cityscapes_instance.py', '../_base_/default_runtime.py' +] + +model = dict( + # set None to avoid loading ImageNet pre-trained backbone, + # instead here we set `load_from` to load from COCO pre-trained detectors. + backbone=dict(init_cfg=None), + # replace neck from defaultly `FPN` to our new implemented module `AugFPN` + neck=dict( + type='AugFPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + # We also need to change the num_classes in head from 80 to 8, to match the + # cityscapes dataset's annotation. This modification involves `bbox_head` and `mask_head`. + roi_head=dict( + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + # change the number of classes from defaultly COCO to cityscapes + num_classes=8, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + # change the number of classes from defaultly COCO to cityscapes + num_classes=8, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + # change the number of classes from defaultly COCO to cityscapes + num_classes=8, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], + mask_head=dict( + type='FCNMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + # change the number of classes from default COCO to cityscapes + num_classes=8, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)))) + +# over-write `train_pipeline` for new added `AutoAugment` training setting +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='AutoAugment', + policies=[ + [dict( + type='Rotate', + level=5, + img_border_value=(124, 116, 104), + prob=0.5) + ], + [dict(type='Rotate', level=7, img_border_value=(124, 116, 104)), + dict( + type='TranslateX', + level=5, + prob=0.5, + img_border_value=(124, 116, 104)) + ], + ]), + dict( + type='RandomResize', + scale=[(2048, 800), (2048, 1024)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs'), +] + +# set batch_size per gpu, and set new training pipeline +train_dataloader = dict( + batch_size=1, + num_workers=3, + # over-write `pipeline` with new training pipeline setting + dataset=dict(pipeline=train_pipeline)) + +# Set optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +# Set customized learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=10, + by_epoch=True, + milestones=[8], + gamma=0.1) +] + +# train, val, test loop config +train_cfg = dict(max_epochs=10, val_interval=1) + +# We can use the COCO pre-trained Cascade Mask R-CNN R50 model for a more stable performance initialization +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco/cascade_mask_rcnn_r50_fpn_1x_coco_20200203-9d4dcb24.pth' +``` + +## Train a new model + +To train a model with the new config, you can simply run + +```shell +python tools/train.py configs/cityscapes/cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes.py +``` + +For more detailed usages, please refer to the [training guide](train.md). + +## Test and inference + +To test the trained model, you can simply run + +```shell +python tools/test.py configs/cityscapes/cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes.py work_dirs/cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes/epoch_10.pth +``` + +For more detailed usages, please refer to the [testing guide](test.md). diff --git a/mmdetection/docs/en/user_guides/robustness_benchmarking.md b/mmdetection/docs/en/user_guides/robustness_benchmarking.md new file mode 100644 index 00000000..f6579564 --- /dev/null +++ b/mmdetection/docs/en/user_guides/robustness_benchmarking.md @@ -0,0 +1,110 @@ +# Corruption Benchmarking + +## Introduction + +We provide tools to test object detection and instance segmentation models on the image corruption benchmark defined in [Benchmarking Robustness in Object Detection: Autonomous Driving when Winter is Coming](https://arxiv.org/abs/1907.07484). +This page provides basic tutorials how to use the benchmark. + +```latex +@article{michaelis2019winter, + title={Benchmarking Robustness in Object Detection: + Autonomous Driving when Winter is Coming}, + author={Michaelis, Claudio and Mitzkus, Benjamin and + Geirhos, Robert and Rusak, Evgenia and + Bringmann, Oliver and Ecker, Alexander S. and + Bethge, Matthias and Brendel, Wieland}, + journal={arXiv:1907.07484}, + year={2019} +} +``` + +![image corruption example](../../../resources/corruptions_sev_3.png) + +## About the benchmark + +To submit results to the benchmark please visit the [benchmark homepage](https://github.com/bethgelab/robust-detection-benchmark) + +The benchmark is modelled after the [imagenet-c benchmark](https://github.com/hendrycks/robustness) which was originally +published in [Benchmarking Neural Network Robustness to Common Corruptions and Perturbations](https://arxiv.org/abs/1903.12261) (ICLR 2019) by Dan Hendrycks and Thomas Dietterich. + +The image corruption functions are included in this library but can be installed separately using: + +```shell +pip install imagecorruptions +``` + +Compared to imagenet-c a few changes had to be made to handle images of arbitrary size and greyscale images. +We also modified the 'motion blur' and 'snow' corruptions to remove dependency from a linux specific library, +which would have to be installed separately otherwise. For details please refer to the [imagecorruptions repository](https://github.com/bethgelab/imagecorruptions). + +## Inference with pretrained models + +We provide a testing script to evaluate a models performance on any combination of the corruptions provided in the benchmark. + +### Test a dataset + +- [x] single GPU testing +- [ ] multiple GPU testing +- [ ] visualize detection results + +You can use the following commands to test a models performance under the 15 corruptions used in the benchmark. + +```shell +# single-gpu testing +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] +``` + +Alternatively different group of corruptions can be selected. + +```shell +# noise +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] --corruptions noise + +# blur +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] --corruptions blur + +# wetaher +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] --corruptions weather + +# digital +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] --corruptions digital +``` + +Or a costom set of corruptions e.g.: + +```shell +# gaussian noise, zoom blur and snow +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions gaussian_noise zoom_blur snow +``` + +Finally the corruption severities to evaluate can be chosen. +Severity 0 corresponds to clean data and the effect increases from 1 to 5. + +```shell +# severity 1 +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --severities 1 + +# severities 0,2,4 +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --severities 0 2 4 +``` + +## Results for modelzoo models + +The results on COCO 2017val are shown in the below table. + +| Model | Backbone | Style | Lr schd | box AP clean | box AP corr. | box % | mask AP clean | mask AP corr. | mask % | +| :-----------------: | :-----------------: | :-----: | :-----: | :----------: | :----------: | :---: | :-----------: | :-----------: | :----: | +| Faster R-CNN | R-50-FPN | pytorch | 1x | 36.3 | 18.2 | 50.2 | - | - | - | +| Faster R-CNN | R-101-FPN | pytorch | 1x | 38.5 | 20.9 | 54.2 | - | - | - | +| Faster R-CNN | X-101-32x4d-FPN | pytorch | 1x | 40.1 | 22.3 | 55.5 | - | - | - | +| Faster R-CNN | X-101-64x4d-FPN | pytorch | 1x | 41.3 | 23.4 | 56.6 | - | - | - | +| Faster R-CNN | R-50-FPN-DCN | pytorch | 1x | 40.0 | 22.4 | 56.1 | - | - | - | +| Faster R-CNN | X-101-32x4d-FPN-DCN | pytorch | 1x | 43.4 | 26.7 | 61.6 | - | - | - | +| Mask R-CNN | R-50-FPN | pytorch | 1x | 37.3 | 18.7 | 50.1 | 34.2 | 16.8 | 49.1 | +| Mask R-CNN | R-50-FPN-DCN | pytorch | 1x | 41.1 | 23.3 | 56.7 | 37.2 | 20.7 | 55.7 | +| Cascade R-CNN | R-50-FPN | pytorch | 1x | 40.4 | 20.1 | 49.7 | - | - | - | +| Cascade Mask R-CNN | R-50-FPN | pytorch | 1x | 41.2 | 20.7 | 50.2 | 35.7 | 17.6 | 49.3 | +| RetinaNet | R-50-FPN | pytorch | 1x | 35.6 | 17.8 | 50.1 | - | - | - | +| Hybrid Task Cascade | X-101-64x4d-FPN-DCN | pytorch | 1x | 50.6 | 32.7 | 64.7 | 43.8 | 28.1 | 64.0 | + +Results may vary slightly due to the stochastic application of the corruptions. diff --git a/mmdetection/docs/en/user_guides/semi_det.md b/mmdetection/docs/en/user_guides/semi_det.md new file mode 100644 index 00000000..ee86c302 --- /dev/null +++ b/mmdetection/docs/en/user_guides/semi_det.md @@ -0,0 +1,325 @@ +# Semi-supervised Object Detection + +Semi-supervised object detection uses both labeled data and unlabeled data for training. It not only reduces the annotation burden for training high-performance object detectors but also further improves the object detector by using a large number of unlabeled data. + +A typical procedure to train a semi-supervised object detector is as below: + +- [Semi-supervised Object Detection](#semi-supervised-object-detection) + - [Prepare and split dataset](#prepare-and-split-dataset) + - [Configure multi-branch pipeline](#configure-multi-branch-pipeline) + - [Configure semi-supervised dataloader](#configure-semi-supervised-dataloader) + - [Configure semi-supervised model](#configure-semi-supervised-model) + - [Configure MeanTeacherHook](#configure-meanteacherhook) + - [Configure TeacherStudentValLoop](#configure-teacherstudentvalloop) + +## Prepare and split dataset + +We provide a dataset download script, which downloads the coco2017 dataset by default and decompresses it automatically. + +```shell +python tools/misc/download_dataset.py +``` + +The decompressed dataset directory structure is as below: + +```plain +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── image_info_unlabeled2017.json +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── test2017 +│ │ ├── train2017 +│ │ ├── unlabeled2017 +│ │ ├── val2017 +``` + +There are two common experimental settings for semi-supervised object detection on the coco2017 dataset: + +(1) Split `train2017` according to a fixed percentage (1%, 2%, 5% and 10%) as a labeled dataset, and the rest of `train2017` as an unlabeled dataset. Because the different splits of `train2017` as labeled datasets will cause significant fluctuation on the accuracy of the semi-supervised detectors, five-fold cross-validation is used in practice to evaluate the algorithm. We provide the dataset split script: + +```shell +python tools/misc/split_coco.py +``` + +By default, the script will split `train2017` according to the labeled data ratio 1%, 2%, 5% and 10%, and each split will be randomly repeated 5 times for cross-validation. The generated semi-supervised annotation file name format is as below: + +- the name format of labeled dataset: `instances_train2017.{fold}@{percent}.json` +- the name format of unlabeled dataset: `instances_train2017.{fold}@{percent}-unlabeled.json` + +Here, `fold` is used for cross-validation, and `percent` represents the ratio of labeled data. The directory structure of the divided dataset is as below: + +```plain +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── image_info_unlabeled2017.json +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── semi_anns +│ │ │ ├── instances_train2017.1@1.json +│ │ │ ├── instances_train2017.1@1-unlabeled.json +│ │ │ ├── instances_train2017.1@2.json +│ │ │ ├── instances_train2017.1@2-unlabeled.json +│ │ │ ├── instances_train2017.1@5.json +│ │ │ ├── instances_train2017.1@5-unlabeled.json +│ │ │ ├── instances_train2017.1@10.json +│ │ │ ├── instances_train2017.1@10-unlabeled.json +│ │ │ ├── instances_train2017.2@1.json +│ │ │ ├── instances_train2017.2@1-unlabeled.json +│ │ ├── test2017 +│ │ ├── train2017 +│ │ ├── unlabeled2017 +│ │ ├── val2017 +``` + +(2) Use `train2017` as the labeled dataset and `unlabeled2017` as the unlabeled dataset. Since `image_info_unlabeled2017.json` does not contain `categories` information, the `CocoDataset` cannot be initialized, so you need to write the `categories` of `instances_train2017.json` into `image_info_unlabeled2017.json` and save it as `instances_unlabeled2017.json`, the relevant script is as below: + +```python +from mmengine.fileio import load, dump + +anns_train = load('instances_train2017.json') +anns_unlabeled = load('image_info_unlabeled2017.json') +anns_unlabeled['categories'] = anns_train['categories'] +dump(anns_unlabeled, 'instances_unlabeled2017.json') +``` + +The processed dataset directory is as below: + +```plain +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── image_info_unlabeled2017.json +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_unlabeled2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── test2017 +│ │ ├── train2017 +│ │ ├── unlabeled2017 +│ │ ├── val2017 +``` + +## Configure multi-branch pipeline + +There are two main approaches to semi-supervised learning, +[consistency regularization](https://research.nvidia.com/sites/default/files/publications/laine2017iclr_paper.pdf) +and [pseudo label](https://www.researchgate.net/profile/Dong-Hyun-Lee/publication/280581078_Pseudo-Label_The_Simple_and_Efficient_Semi-Supervised_Learning_Method_for_Deep_Neural_Networks/links/55bc4ada08ae092e9660b776/Pseudo-Label-The-Simple-and-Efficient-Semi-Supervised-Learning-Method-for-Deep-Neural-Networks.pdf). +Consistency regularization often requires some careful design, while pseudo label have a simpler form and are easier to extend to downstream tasks. +We adopt a teacher-student joint training semi-supervised object detection framework based on pseudo label, so labeled data and unlabeled data need to configure different data pipeline: + +(1) Pipeline for labeled data: + +```python +# pipeline used to augment labeled data, +# which will be sent to student model for supervised training. +sup_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomResize', scale=scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='MultiBranch', sup=dict(type='PackDetInputs')) +] +``` + +(2) Pipeline for unlabeled data: + +```python +# pipeline used to augment unlabeled data weakly, +# which will be sent to teacher model for predicting pseudo instances. +weak_pipeline = [ + dict(type='RandomResize', scale=scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', + 'homography_matrix')), +] + +# pipeline used to augment unlabeled data strongly, +# which will be sent to student model for unsupervised training. +strong_pipeline = [ + dict(type='RandomResize', scale=scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomOrder', + transforms=[ + dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict(type='RandAugment', aug_space=geometric, aug_num=1), + ]), + dict(type='RandomErasing', n_patches=(1, 5), ratio=(0, 0.2)), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', + 'homography_matrix')), +] + +# pipeline used to augment unlabeled data into different views +unsup_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadEmptyAnnotations'), + dict( + type='MultiBranch', + unsup_teacher=weak_pipeline, + unsup_student=strong_pipeline, + ) +] +``` + +## Configure semi-supervised dataloader + +(1) Build a semi-supervised dataset. Use `ConcatDataset` to concatenate labeled and unlabeled datasets. + +```python +labeled_dataset = dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=sup_pipeline) + +unlabeled_dataset = dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_unlabeled2017.json', + data_prefix=dict(img='unlabeled2017/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=unsup_pipeline) + +train_dataloader = dict( + batch_size=batch_size, + num_workers=num_workers, + persistent_workers=True, + sampler=dict( + type='GroupMultiSourceSampler', + batch_size=batch_size, + source_ratio=[1, 4]), + dataset=dict( + type='ConcatDataset', datasets=[labeled_dataset, unlabeled_dataset])) +``` + +(2) Use multi-source dataset sampler. Use `GroupMultiSourceSampler` to sample data form batches from `labeled_dataset` and `labeled_dataset`, `source_ratio` controls the proportion of labeled data and unlabeled data in the batch. `GroupMultiSourceSampler` also ensures that the images in the same batch have similar aspect ratios. If you don't need to guarantee the aspect ratio of the images in the batch, you can use `MultiSourceSampler`. The sampling diagram of `GroupMultiSourceSampler` is as below: + +
    + +
    + +`sup=1000` indicates that the scale of the labeled dataset is 1000, `sup_h=200` indicates that the scale of the images with an aspect ratio greater than or equal to 1 in the labeled dataset is 200, and `sup_w=800` indicates that the scale of the images with an aspect ratio less than 1 in the labeled dataset is 800, +`unsup=9000` indicates that the scale of the unlabeled dataset is 9000, `unsup_h=1800` indicates that the scale of the images with an aspect ratio greater than or equal to 1 in the unlabeled dataset is 1800, and `unsup_w=7200` indicates the scale of the images with an aspect ratio less than 1 in the unlabeled dataset is 7200. +`GroupMultiSourceSampler` randomly selects a group according to the overall aspect ratio distribution of the images in the labeled dataset and the unlabeled dataset, and then sample data to form batches from the two datasets according to `source_ratio`, so labeled datasets and unlabeled datasets have different repetitions. + +## Configure semi-supervised model + +We choose `Faster R-CNN` as `detector` for semi-supervised training. Take the semi-supervised object detection algorithm `SoftTeacher` as an example, +the model configuration can be inherited from `_base_/models/faster-rcnn_r50_fpn.py`, replacing the backbone network of the detector with `caffe` style. +Note that unlike the supervised training configs, `Faster R-CNN` as `detector` is an attribute of `model`, not `model` . +In addition, `data_preprocessor` needs to be set to `MultiBranchDataPreprocessor`, which is used to pad and normalize images from different pipelines. +Finally, parameters required for semi-supervised training and testing can be configured via `semi_train_cfg` and `semi_test_cfg`. + +```python +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', '../_base_/default_runtime.py', + '../_base_/datasets/semi_coco_detection.py' +] + +detector = _base_.model +detector.data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32) +detector.backbone = dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')) + +model = dict( + _delete_=True, + type='SoftTeacher', + detector=detector, + data_preprocessor=dict( + type='MultiBranchDataPreprocessor', + data_preprocessor=detector.data_preprocessor), + semi_train_cfg=dict( + freeze_teacher=True, + sup_weight=1.0, + unsup_weight=4.0, + pseudo_label_initial_score_thr=0.5, + rpn_pseudo_thr=0.9, + cls_pseudo_thr=0.9, + reg_pseudo_thr=0.02, + jitter_times=10, + jitter_scale=0.06, + min_pseudo_bbox_wh=(1e-2, 1e-2)), + semi_test_cfg=dict(predict_on='teacher')) +``` + +In addition, we also support semi-supervised training for other detection models, such as `RetinaNet` and `Cascade R-CNN`. Since `SoftTeacher` only supports `Faster R-CNN`, it needs to be replaced with `SemiBaseDetector`, example is as below: + +```python +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', '../_base_/default_runtime.py', + '../_base_/datasets/semi_coco_detection.py' +] + +detector = _base_.model + +model = dict( + _delete_=True, + type='SemiBaseDetector', + detector=detector, + data_preprocessor=dict( + type='MultiBranchDataPreprocessor', + data_preprocessor=detector.data_preprocessor), + semi_train_cfg=dict( + freeze_teacher=True, + sup_weight=1.0, + unsup_weight=1.0, + cls_pseudo_thr=0.9, + min_pseudo_bbox_wh=(1e-2, 1e-2)), + semi_test_cfg=dict(predict_on='teacher')) +``` + +Following the semi-supervised training configuration of `SoftTeacher`, change `batch_size` to 2 and `source_ratio` to `[1, 1]`, the experimental results of supervised and semi-supervised training of `RetinaNet`, `Faster R-CNN`, `Cascade R-CNN` and `SoftTeacher` on the 10% coco `train2017` are as below: + +| Model | Detector | BackBone | Style | sup-0.1-coco mAP | semi-0.1-coco mAP | +| :--------------: | :-----------: | :------: | :---: | :--------------: | :---------------: | +| SemiBaseDetector | RetinaNet | R-50-FPN | caffe | 23.5 | 27.7 | +| SemiBaseDetector | Faster R-CNN | R-50-FPN | caffe | 26.7 | 28.4 | +| SemiBaseDetector | Cascade R-CNN | R-50-FPN | caffe | 28.0 | 29.7 | +| SoftTeacher | Faster R-CNN | R-50-FPN | caffe | 26.7 | 31.1 | + +## Configure MeanTeacherHook + +Usually, the teacher model is updated by Exponential Moving Average (EMA) the student model, and then the teacher model is optimized with the optimization of the student model, which can be achieved by configuring `custom_hooks`: + +```python +custom_hooks = [dict(type='MeanTeacherHook')] +``` + +## Configure TeacherStudentValLoop + +Since there are two models in the teacher-student joint training framework, we can replace `ValLoop` with `TeacherStudentValLoop` to test the accuracy of both models during the training process. + +```python +val_cfg = dict(type='TeacherStudentValLoop') +``` diff --git a/mmdetection/docs/en/user_guides/single_stage_as_rpn.md b/mmdetection/docs/en/user_guides/single_stage_as_rpn.md new file mode 100644 index 00000000..93a48dd7 --- /dev/null +++ b/mmdetection/docs/en/user_guides/single_stage_as_rpn.md @@ -0,0 +1,176 @@ +# Use a single stage detector as RPN + +Region proposal network (RPN) is a submodule in [Faster R-CNN](https://arxiv.org/abs/1506.01497), which generates proposals for the second stage of Faster R-CNN. Most two-stage detectors in MMDetection use [`RPNHead`](../../../mmdet/models/dense_heads/rpn_head.py) to generate proposals as RPN. However, any single-stage detector can serve as an RPN since their bounding box predictions can also be regarded as region proposals and thus be refined in the R-CNN. Therefore, MMDetection v3.0 supports that. + +To illustrate the whole process, here we give an example of how to use an anchor-free single-stage model [FCOS](../../../configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py) as an RPN in [Faster R-CNN](../../../configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py). + +The outline of this tutorial is as below: + +1. Use `FCOSHead` as an `RPNHead` in Faster R-CNN +2. Evaluate proposals +3. Train the customized Faster R-CNN with pre-trained FCOS + +## Use `FCOSHead` as an `RPNHead` in Faster R-CNN + +To set `FCOSHead` as an `RPNHead` in Faster R-CNN, we should create a new config file named `configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py`, and replace with the setting of `rpn_head` with the setting of `bbox_head` in `configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py`. Besides, we still use the neck setting of FCOS with strides of `[8, 16, 32, 64, 128]`, and update `featmap_strides` of `bbox_roi_extractor` to `[8, 16, 32, 64, 128]`. To avoid loss goes NAN, we apply warmup during the first 1000 iterations instead of the first 500 iterations, which means that the lr increases more slowly. The config is as follows: + +```python +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + # copied from configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py + neck=dict( + start_level=1, + add_extra_convs='on_output', # use P5 + relu_before_extra_convs=True), + rpn_head=dict( + _delete_=True, # ignore the unused old settings + type='FCOSHead', + num_classes=1, # num_classes = 1 for rpn, if num_classes > 1, it will be set to 1 in TwoStageDetector automatically + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + roi_head=dict( # update featmap_strides due to the strides in neck + bbox_roi_extractor=dict(featmap_strides=[8, 16, 32, 64, 128]))) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), # Slowly increase lr, otherwise loss becomes NAN + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] +``` + +Then, we could use the following command to train our customized model. For more training commands, please refer to [here](train.md). + +```python +# training with 8 GPUS +bash tools/dist_train.sh configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py \ + 8 \ + --work-dir ./work_dirs/faster-rcnn_r50_fpn_fcos-rpn_1x_coco +``` + +## Evaluate proposals + +The quality of proposals is of great importance to the performance of detector, therefore, we also provide a way to evaluate proposals. Same as above, create a new config file named `configs/rpn/fcos-rpn_r50_fpn_1x_coco.py`, and replace with setting of `rpn_head` with the setting of `bbox_head` in `configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py`. + +```python +_base_ = [ + '../_base_/models/rpn_r50_fpn.py', '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +val_evaluator = dict(metric='proposal_fast') +test_evaluator = val_evaluator + +model = dict( + # copied from configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py + neck=dict( + start_level=1, + add_extra_convs='on_output', # use P5 + relu_before_extra_convs=True), + rpn_head=dict( + _delete_=True, # ignore the unused old settings + type='FCOSHead', + num_classes=1, # num_classes = 1 for rpn, if num_classes > 1, it will be set to 1 in RPN automatically + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0))) +``` + +Suppose we have the checkpoint `./work_dirs/faster-rcnn_r50_fpn_fcos-rpn_1x_coco/epoch_12.pth` after training, then we can evaluate the quality of proposals with the following command. + +```python +# testing with 8 GPUs +bash tools/dist_test.sh \ + configs/rpn/fcos-rpn_r50_fpn_1x_coco.py \ + ./work_dirs/faster-rcnn_r50_fpn_fcos-rpn_1x_coco/epoch_12.pth \ + 8 +``` + +## Train the customized Faster R-CNN with pre-trained FCOS + +Pre-training not only speeds up convergence of training, but also improves the performance of the detector. Therefore, here we give an example to illustrate how to do use a pre-trained FCOS as an RPN to accelerate training and improve the accuracy. Suppose we want to use `FCOSHead` as an rpn head in Faster R-CNN and train with the pre-trained [`fcos_r50-caffe_fpn_gn-head_1x_coco`](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_1x_coco/fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth). The content of config file named `configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_fcos-rpn_1x_coco.py` is as the following. Note that `fcos_r50-caffe_fpn_gn-head_1x_coco` uses a caffe version of ResNet50, the pixel mean and std in `data_preprocessor` thus need to be updated. + +```python +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +model = dict( + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(type='BN', requires_grad=False), + style='caffe', + init_cfg=None), # the checkpoint in ``load_from`` contains the weights of backbone + neck=dict( + start_level=1, + add_extra_convs='on_output', # use P5 + relu_before_extra_convs=True), + rpn_head=dict( + _delete_=True, # ignore the unused old settings + type='FCOSHead', + num_classes=1, # num_classes = 1 for rpn, if num_classes > 1, it will be set to 1 in TwoStageDetector automatically + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + roi_head=dict( # update featmap_strides due to the strides in neck + bbox_roi_extractor=dict(featmap_strides=[8, 16, 32, 64, 128]))) + +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_1x_coco/fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth' +``` + +The command for training is as below. + +```python +bash tools/dist_train.sh \ + configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_fcos-rpn_1x_coco.py \ + 8 \ + --work-dir ./work_dirs/faster-rcnn_r50-caffe_fpn_fcos-rpn_1x_coco +``` diff --git a/mmdetection/docs/en/user_guides/test.md b/mmdetection/docs/en/user_guides/test.md new file mode 100644 index 00000000..129a2409 --- /dev/null +++ b/mmdetection/docs/en/user_guides/test.md @@ -0,0 +1,303 @@ +# Test existing models on standard datasets + +To evaluate a model's accuracy, one usually tests the model on some standard datasets, please refer to [dataset prepare guide](dataset_prepare.md) to prepare the dataset. + +This section will show how to test existing models on supported datasets. + +## Test existing models + +We provide testing scripts for evaluating an existing model on the whole dataset (COCO, PASCAL VOC, Cityscapes, etc.). +The following testing environments are supported: + +- single GPU +- CPU +- single node multiple GPUs +- multiple nodes + +Choose the proper script to perform testing depending on the testing environment. + +```shell +# Single-gpu testing +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--out ${RESULT_FILE}] \ + [--show] + +# CPU: disable GPUs and run single-gpu testing script +export CUDA_VISIBLE_DEVICES=-1 +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--out ${RESULT_FILE}] \ + [--show] + +# Multi-gpu testing +bash tools/dist_test.sh \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + ${GPU_NUM} \ + [--out ${RESULT_FILE}] +``` + +`tools/dist_test.sh` also supports multi-node testing, but relies on PyTorch's [launch utility](https://pytorch.org/docs/stable/distributed.html#launch-utility). + +Optional arguments: + +- `RESULT_FILE`: Filename of the output results in pickle format. If not specified, the results will not be saved to a file. +- `--show`: If specified, detection results will be plotted on the images and shown in a new window. It is only applicable to single GPU testing and used for debugging and visualization. Please make sure that GUI is available in your environment. Otherwise, you may encounter an error like `cannot connect to X server`. +- `--show-dir`: If specified, detection results will be plotted on the images and saved to the specified directory. It is only applicable to single GPU testing and used for debugging and visualization. You do NOT need a GUI available in your environment for using this option. +- `--work-dir`: If specified, detection results containing evaluation metrics will be saved to the specified directory. +- `--cfg-options`: If specified, the key-value pair optional cfg will be merged into config file + +## Examples + +Assuming that you have already downloaded the checkpoints to the directory `checkpoints/`. + +1. Test RTMDet and visualize the results. Press any key for the next image. + Config and checkpoint files are available [here](https://github.com/open-mmlab/mmdetection/tree/main/configs/rtmdet). + + ```shell + python tools/test.py \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --show + ``` + +2. Test RTMDet and save the painted images for future visualization. + Config and checkpoint files are available [here](https://github.com/open-mmlab/mmdetection/tree/main/configs/rtmdet). + + ```shell + python tools/test.py \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --show-dir faster_rcnn_r50_fpn_1x_results + ``` + +3. Test Faster R-CNN on PASCAL VOC (without saving the test results). + Config and checkpoint files are available [here](../../../configs/pascal_voc). + + ```shell + python tools/test.py \ + configs/pascal_voc/faster-rcnn_r50_fpn_1x_voc0712.py \ + checkpoints/faster_rcnn_r50_fpn_1x_voc0712_20200624-c9895d40.pth + ``` + +4. Test Mask R-CNN with 8 GPUs, and evaluate. + Config and checkpoint files are available [here](../../../configs/mask_rcnn). + + ```shell + ./tools/dist_test.sh \ + configs/mask-rcnn_r50_fpn_1x_coco.py \ + checkpoints/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth \ + 8 \ + --out results.pkl + ``` + +5. Test Mask R-CNN with 8 GPUs, and evaluate the metric **class-wise**. + Config and checkpoint files are available [here](../../../configs/mask_rcnn). + + ```shell + ./tools/dist_test.sh \ + configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py \ + checkpoints/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth \ + 8 \ + --out results.pkl \ + --cfg-options test_evaluator.classwise=True + ``` + +6. Test Mask R-CNN on COCO test-dev with 8 GPUs, and generate JSON files for submitting to the official evaluation server. + Config and checkpoint files are available [here](../../../configs/mask_rcnn). + + Replace the original test_evaluator and test_dataloader with test_evaluator and test_dataloader in the comment in [config](../../../configs/_base_/datasets/coco_instance.py) and run: + + ```shell + ./tools/dist_test.sh \ + configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py \ + checkpoints/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth \ + 8 + ``` + + This command generates two JSON files `./work_dirs/coco_instance/test.bbox.json` and `./work_dirs/coco_instance/test.segm.json`. + +7. Test Mask R-CNN on Cityscapes test with 8 GPUs, and generate txt and png files for submitting to the official evaluation server. + Config and checkpoint files are available [here](../../../configs/cityscapes). + + Replace the original test_evaluator and test_dataloader with test_evaluator and test_dataloader in the comment in [config](../../../configs/_base_/datasets/cityscapes_instance.py) and run: + + ```shell + ./tools/dist_test.sh \ + configs/cityscapes/mask-rcnn_r50_fpn_1x_cityscapes.py \ + checkpoints/mask_rcnn_r50_fpn_1x_cityscapes_20200227-afe51d5a.pth \ + 8 + ``` + + The generated png and txt would be under `./work_dirs/cityscapes_metric/` directory. + +## Test without Ground Truth Annotations + +MMDetection supports to test models without ground-truth annotations using `CocoDataset`. If your dataset format is not in COCO format, please convert them to COCO format. For example, if your dataset format is VOC, you can directly convert it to COCO format by the [script in tools.](../../../tools/dataset_converters/pascal_voc.py) If your dataset format is Cityscapes, you can directly convert it to COCO format by the [script in tools.](../../../tools/dataset_converters/cityscapes.py) The rest of the formats can be converted using [this script](../../../tools/dataset_converters/images2coco.py). + +```shell +python tools/dataset_converters/images2coco.py \ + ${IMG_PATH} \ + ${CLASSES} \ + ${OUT} \ + [--exclude-extensions] +``` + +arguments: + +- `IMG_PATH`: The root path of images. +- `CLASSES`: The text file with a list of categories. +- `OUT`: The output annotation json file name. The save dir is in the same directory as `IMG_PATH`. +- `exclude-extensions`: The suffix of images to be excluded, such as 'png' and 'bmp'. + +After the conversion is complete, you need to replace the original test_evaluator and test_dataloader with test_evaluator and test_dataloader in the comment in [config](../../../configs/_base_/datasets/coco_detection.py)(find which dataset in 'configs/_base_/datasets' the current config corresponds to) and run: + +```shell +# Single-gpu testing +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--show] + +# CPU: disable GPUs and run single-gpu testing script +export CUDA_VISIBLE_DEVICES=-1 +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--out ${RESULT_FILE}] \ + [--show] + +# Multi-gpu testing +bash tools/dist_test.sh \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + ${GPU_NUM} \ + [--show] +``` + +Assuming that the checkpoints in the [model zoo](https://mmdetection.readthedocs.io/en/latest/modelzoo_statistics.html) have been downloaded to the directory `checkpoints/`, we can test Mask R-CNN on COCO test-dev with 8 GPUs, and generate JSON files using the following command. + +```sh +./tools/dist_test.sh \ + configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py \ + checkpoints/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth \ + 8 +``` + +This command generates two JSON files `./work_dirs/coco_instance/test.bbox.json` and `./work_dirs/coco_instance/test.segm.json`. + +## Batch Inference + +MMDetection supports inference with a single image or batched images in test mode. By default, we use single-image inference and you can use batch inference by modifying `samples_per_gpu` in the config of test data. You can do that either by modifying the config as below. + +```shell +data = dict(train_dataloader=dict(...), val_dataloader=dict(...), test_dataloader=dict(batch_size=2, ...)) +``` + +Or you can set it through `--cfg-options` as `--cfg-options test_dataloader.batch_size=2` + +## Test Time Augmentation (TTA) + +Test time augmentation (TTA) is a data augmentation strategy used during the test phase. It applies different augmentations, such as flipping and scaling, to the same image for model inference, and then merges the predictions of each augmented image to obtain more accurate predictions. To make it easier for users to use TTA, MMEngine provides [BaseTTAModel](https://mmengine.readthedocs.io/en/latest/api/generated/mmengine.model.BaseTTAModel.html#mmengine.model.BaseTTAModel) class, which allows users to implement different TTA strategies by simply extending the BaseTTAModel class according to their needs. + +In MMDetection, we provides [DetTTAModel](../../../mmdet/models/test_time_augs/det_tta.py) class, which inherits from BaseTTAModel. + +### Use case + +Using TTA requires two steps. First, you need to add `tta_model` and `tta_pipeline` in the configuration file: + +```shell +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict( + type='nms', + iou_threshold=0.5), + max_per_img=100)) + +tta_pipeline = [ + dict(type='LoadImageFromFile', + backend_args=None), + dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale=(1333, 800), keep_ratio=True) + ], [ # It uses 2 flipping transformations (flipping and not flipping). + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'flip', + 'flip_direction')) + ]])] +``` + +Second, set `--tta` when running the test scripts as examples below: + +```shell +# Single-gpu testing +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--tta] + +# CPU: disable GPUs and run single-gpu testing script +export CUDA_VISIBLE_DEVICES=-1 +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--out ${RESULT_FILE}] \ + [--tta] + +# Multi-gpu testing +bash tools/dist_test.sh \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + ${GPU_NUM} \ + [--tta] +``` + +You can also modify the TTA config by yourself, such as adding scaling enhancement: + +```shell +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict( + type='nms', + iou_threshold=0.5), + max_per_img=100)) + +img_scales = [(1333, 800), (666, 400), (2000, 1200)] +tta_pipeline = [ + dict(type='LoadImageFromFile', + backend_args=None), + dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale=s, keep_ratio=True) for s in img_scales + ], [ + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'flip', + 'flip_direction')) + ]])] +``` + +The above data augmentation pipeline will first perform 3 multi-scaling transformations on the image, followed by 2 flipping transformations (flipping and not flipping). Finally, the image is packaged into the final result using PackDetInputs. + +Here are more TTA use cases for your reference: + +- [RetinaNet](../../../configs/retinanet/retinanet_tta.py) +- [CenterNet](../../../configs/centernet/centernet_tta.py) +- [YOLOX](../../../configs/rtmdet/rtmdet_tta.py) +- [RTMDet](../../../configs/yolox/yolox_tta.py) + +For more advanced usage and data flow of TTA, please refer to [MMEngine](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/test_time_augmentation.html#data-flow). We will support instance segmentation TTA latter. diff --git a/mmdetection/docs/en/user_guides/test_results_submission.md b/mmdetection/docs/en/user_guides/test_results_submission.md new file mode 100644 index 00000000..721347ea --- /dev/null +++ b/mmdetection/docs/en/user_guides/test_results_submission.md @@ -0,0 +1,182 @@ +# Test Results Submission + +## Panoptic segmentation test results submission + +The following sections introduce how to produce the prediction results of panoptic segmentation models on the COCO test-dev set and submit the predictions to [COCO evaluation server](https://competitions.codalab.org/competitions/19507). + +### Prerequisites + +- Download [COCO test dataset images](http://images.cocodataset.org/zips/test2017.zip), [testing image info](http://images.cocodataset.org/annotations/image_info_test2017.zip), and [panoptic train/val annotations](http://images.cocodataset.org/annotations/panoptic_annotations_trainval2017.zip), then unzip them, put 'test2017' to `data/coco/`, put json files and annotation files to `data/coco/annotations/`. + +```shell +# suppose data/coco/ does not exist +mkdir -pv data/coco/ + +# download test2017 +wget -P data/coco/ http://images.cocodataset.org/zips/test2017.zip +wget -P data/coco/ http://images.cocodataset.org/annotations/image_info_test2017.zip +wget -P data/coco/ http://images.cocodataset.org/annotations/panoptic_annotations_trainval2017.zip + +# unzip them +unzip data/coco/test2017.zip -d data/coco/ +unzip data/coco/image_info_test2017.zip -d data/coco/ +unzip data/coco/panoptic_annotations_trainval2017.zip -d data/coco/ + +# remove zip files (optional) +rm -rf data/coco/test2017.zip data/coco/image_info_test2017.zip data/coco/panoptic_annotations_trainval2017.zip +``` + +- Run the following code to update category information in testing image info. Since the attribute `isthing` is missing in category information of 'image_info_test-dev2017.json', we need to update it with the category information in 'panoptic_val2017.json'. + +```shell +python tools/misc/gen_coco_panoptic_test_info.py data/coco/annotations +``` + +After completing the above preparations, your directory structure of `data` should be like this: + +```text +data +`-- coco + |-- annotations + | |-- image_info_test-dev2017.json + | |-- image_info_test2017.json + | |-- panoptic_image_info_test-dev2017.json + | |-- panoptic_train2017.json + | |-- panoptic_train2017.zip + | |-- panoptic_val2017.json + | `-- panoptic_val2017.zip + `-- test2017 +``` + +### Inference on coco test-dev + +To do inference on coco test-dev, we should update the setting of `test_dataloder` and `test_evaluator` first. There two ways to do this: 1. update them in config file; 2. update them in command line. + +#### Update them in config file + +The relevant settings are provided at the end of `configs/_base_/datasets/coco_panoptic.py`, as below. + +```python +test_dataloader = dict( + batch_size=1, + num_workers=1, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/panoptic_image_info_test-dev2017.json', + data_prefix=dict(img='test2017/'), + test_mode=True, + pipeline=test_pipeline)) +test_evaluator = dict( + type='CocoPanopticMetric', + format_only=True, + ann_file=data_root + 'annotations/panoptic_image_info_test-dev2017.json', + outfile_prefix='./work_dirs/coco_panoptic/test') +``` + +Any of the following way can be used to update the setting for inference on coco test-dev set. + +Case 1: Directly uncomment the setting in `configs/_base_/datasets/coco_panoptic.py`. + +Case 2: Copy the following setting to the config file you used now. + +```python +test_dataloader = dict( + dataset=dict( + ann_file='annotations/panoptic_image_info_test-dev2017.json', + data_prefix=dict(img='test2017/', _delete_=True))) +test_evaluator = dict( + format_only=True, + ann_file=data_root + 'annotations/panoptic_image_info_test-dev2017.json', + outfile_prefix='./work_dirs/coco_panoptic/test') +``` + +Then infer on coco test-dev et by the following command. + +```shell +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} +``` + +#### Update them in command line + +The command for update of the related settings and inference on coco test-dev are as below. + +```shell +# test with single gpu +CUDA_VISIBLE_DEVICES=0 python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + --cfg-options \ + test_dataloader.dataset.ann_file=annotations/panoptic_image_info_test-dev2017.json \ + test_dataloader.dataset.data_prefix.img=test2017 \ + test_dataloader.dataset.data_prefix._delete_=True \ + test_evaluator.format_only=True \ + test_evaluator.ann_file=data/coco/annotations/panoptic_image_info_test-dev2017.json \ + test_evaluator.outfile_prefix=${WORK_DIR}/results + +# test with four gpus +CUDA_VISIBLE_DEVICES=0,1,3,4 bash tools/dist_test.sh \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + 8 \ # eights gpus + --cfg-options \ + test_dataloader.dataset.ann_file=annotations/panoptic_image_info_test-dev2017.json \ + test_dataloader.dataset.data_prefix.img=test2017 \ + test_dataloader.dataset.data_prefix._delete_=True \ + test_evaluator.format_only=True \ + test_evaluator.ann_file=data/coco/annotations/panoptic_image_info_test-dev2017.json \ + test_evaluator.outfile_prefix=${WORK_DIR}/results + +# test with slurm +GPUS=8 tools/slurm_test.sh \ + ${Partition} \ + ${JOB_NAME} \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + --cfg-options \ + test_dataloader.dataset.ann_file=annotations/panoptic_image_info_test-dev2017.json \ + test_dataloader.dataset.data_prefix.img=test2017 \ + test_dataloader.dataset.data_prefix._delete_=True \ + test_evaluator.format_only=True \ + test_evaluator.ann_file=data/coco/annotations/panoptic_image_info_test-dev2017.json \ + test_evaluator.outfile_prefix=${WORK_DIR}/results +``` + +Example + +Suppose we perform inference on `test2017` using pretrained MaskFormer with ResNet-50 backbone. + +```shell +# test with single gpu +CUDA_VISIBLE_DEVICES=0 python tools/test.py \ + configs/maskformer/maskformer_r50_mstrain_16x1_75e_coco.py \ + checkpoints/maskformer_r50_mstrain_16x1_75e_coco_20220221_141956-bc2699cb.pth \ + --cfg-options \ + test_dataloader.dataset.ann_file=annotations/panoptic_image_info_test-dev2017.json \ + test_dataloader.dataset.data_prefix.img=test2017 \ + test_dataloader.dataset.data_prefix._delete_=True \ + test_evaluator.format_only=True \ + test_evaluator.ann_file=data/coco/annotations/panoptic_image_info_test-dev2017.json \ + test_evaluator.outfile_prefix=work_dirs/maskformer/results +``` + +### Rename files and zip results + +After inference, the panoptic segmentation results (a json file and a directory where the masks are stored) will be in `WORK_DIR`. We should rename them according to the naming convention described on [COCO's Website](https://cocodataset.org/#upload). Finally, we need to compress the json and the directory where the masks are stored into a zip file, and rename the zip file according to the naming convention. Note that the zip file should **directly** contains the above two files. + +The commands to rename files and zip results: + +```shell +# In WORK_DIR, we have panoptic segmentation results: 'panoptic' and 'results.panoptic.json'. +cd ${WORK_DIR} + +# replace '[algorithm_name]' with the name of algorithm you used. +mv ./panoptic ./panoptic_test-dev2017_[algorithm_name]_results +mv ./results.panoptic.json ./panoptic_test-dev2017_[algorithm_name]_results.json +zip panoptic_test-dev2017_[algorithm_name]_results.zip -ur panoptic_test-dev2017_[algorithm_name]_results panoptic_test-dev2017_[algorithm_name]_results.json +``` diff --git a/mmdetection/docs/en/user_guides/tracking_analysis_tools.md b/mmdetection/docs/en/user_guides/tracking_analysis_tools.md new file mode 100644 index 00000000..acced58d --- /dev/null +++ b/mmdetection/docs/en/user_guides/tracking_analysis_tools.md @@ -0,0 +1,86 @@ +**We provide lots of useful tools under the `tools/` directory.** + +## MOT Test-time Parameter Search + +`tools/analysis_tools/mot/mot_param_search.py` can search the parameters of the `tracker` in MOT models. +It is used as the same manner with `tools/test.py` but **different** in the configs. + +Here is an example that shows how to modify the configs: + +1. Define the desirable evaluation metrics to record. + + For example, you can define the `evaluator` as + + ```python + test_evaluator=dict(type='MOTChallengeMetrics', metric=['HOTA', 'CLEAR', 'Identity']) + ``` + + Of course, you can also customize the content of `metric` in `test_evaluator`. You are free to choose one or more of `['HOTA', 'CLEAR', 'Identity']`. + +2. Define the parameters and the values to search. + + Assume you have a tracker like + + ```python + model=dict( + tracker=dict( + type='BaseTracker', + obj_score_thr=0.5, + match_iou_thr=0.5 + ) + ) + ``` + + If you want to search the parameters of the tracker, just change the value to a list as follow + + ```python + model=dict( + tracker=dict( + type='BaseTracker', + obj_score_thr=[0.4, 0.5, 0.6], + match_iou_thr=[0.4, 0.5, 0.6, 0.7] + ) + ) + ``` + + Then the script will test the totally 12 cases and log the results. + +## MOT Error Visualize + +`tools/analysis_tools/mot/mot_error_visualize.py` can visualize errors for multiple object tracking. +This script needs the result of inference. By Default, the **red** bounding box denotes false positive, the **yellow** bounding box denotes the false negative and the **blue** bounding box denotes ID switch. + +``` +python tools/analysis_tools/mot/mot_error_visualize.py \ + ${CONFIG_FILE}\ + --input ${INPUT} \ + --result-dir ${RESULT_DIR} \ + [--output-dir ${OUTPUT}] \ + [--fps ${FPS}] \ + [--show] \ + [--backend ${BACKEND}] +``` + +The `RESULT_DIR` contains the inference results of all videos and the inference result is a `txt` file. + +Optional arguments: + +- `OUTPUT`: Output of the visualized demo. If not specified, the `--show` is obligate to show the video on the fly. +- `FPS`: FPS of the output video. +- `--show`: Whether show the video on the fly. +- `BACKEND`: The backend to visualize the boxes. Options are `cv2` and `plt`. + +## Browse dataset + +`tools/analysis_tools/mot/browse_dataset.py` can visualize the training dataset to check whether the dataset configuration is correct. + +**Examples:** + +```shell +python tools/analysis_tools/browse_dataset.py ${CONFIG_FILE} [--show-interval ${SHOW_INTERVAL}] +``` + +Optional arguments: + +- `SHOW_INTERVAL`: The interval of show (s). +- `--show`: Whether show the images on the fly. diff --git a/mmdetection/docs/en/user_guides/tracking_config.md b/mmdetection/docs/en/user_guides/tracking_config.md new file mode 100644 index 00000000..fa8aeea0 --- /dev/null +++ b/mmdetection/docs/en/user_guides/tracking_config.md @@ -0,0 +1,112 @@ +# Learn about Configs + +We use python files as our config system. You can find all the provided configs under $MMDetection/configs. + +We incorporate modular and inheritance design into our config system, +which is convenient to conduct various experiments. +If you wish to inspect the config file, +you may run `python tools/misc/print_config.py /PATH/TO/CONFIG` to see the complete config. + +## A brief description of a complete config + +A complete config usually contains the following primary fields: + +- `model`: the basic config of model, which may contain `data_preprocessor`, modules (e.g., `detector`, `motion`),`train_cfg`, `test_cfg`, etc. +- `train_dataloader`: the config of training dataloader, which usually contains `batch_size`, `num_workers`, `sampler`, `dataset`, etc. +- `val_dataloader`: the config of validation dataloader, which is similar with `train_dataloader`. +- `test_dataloader`: the config of testing dataloader, which is similar with `train_dataloader`. +- `val_evaluator`: the config of validation evaluator. For example,`type='MOTChallengeMetrics'` for MOT task on the MOTChallenge benchmarks. +- `test_evaluator`: the config of testing evaluator, which is similar with `val_evaluator`. +- `train_cfg`: the config of training loop. For example, `type='EpochBasedTrainLoop'`. +- `val_cfg`: the config of validation loop. For example, `type='VideoValLoop'`. +- `test_cfg`: the config of testing loop. For example, `type='VideoTestLoop'`. +- `default_hooks`: the config of default hooks, which may include hooks for timer, logger, param_scheduler, checkpoint, sampler_seed, visualization, etc. +- `vis_backends`: the config of visualization backends, which uses `type='LocalVisBackend'` as default. +- `visualizer`: the config of visualizer. `type='TrackLocalVisualizer'` for MOT tasks. +- `param_scheduler`: the config of parameter scheduler, which usually sets the learning rate scheduler. +- `optim_wrapper`: the config of optimizer wrapper, which contains optimization-related information, for example optimizer, gradient clipping, etc. +- `load_from`: load models as a pre-trained model from a given path. +- `resume`: If `True`, resume checkpoints from `load_from`, and the training will be resumed from the epoch when the checkpoint is saved. + +## Modify config through script arguments + +When submitting jobs using `tools/train.py` or `tools/test_tracking.py`, +you may specify `--cfg-options` to in-place modify the config. +We present several examples as follows. +For more details, please refer to [MMEngine](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/config.md). + +- **Update config keys of dict chains.** + + The config options can be specified following the order of the dict keys in the original config. + For example, `--cfg-options model.detector.backbone.norm_eval=False` changes the all BN modules in model backbones to train mode. + +- **Update keys inside a list of configs.** + + Some config dicts are composed as a list in your config. + For example, the testing pipeline `test_dataloader.dataset.pipeline` is normally a list e.g. `[dict(type='LoadImageFromFile'), ...]`. + If you want to change `LoadImageFromFile` to `LoadImageFromWebcam` in the pipeline, + you may specify `--cfg-options test_dataloader.dataset.pipeline.0.type=LoadImageFromWebcam`. + +- **Update values of list/tuples.** + + Maybe the value to be updated is a list or a tuple. + For example, you can change the key `mean` of `data_preprocessor` by specifying `--cfg-options model.data_preprocessor.mean=[0,0,0]`. + Note that **NO** white space is allowed inside the specified value. + +## Config File Structure + +There are 3 basic component types under `config/_base_`, i.e., dataset, model and default_runtime. +Many methods could be easily constructed with one of each like SORT, DeepSORT. +The configs that are composed by components from `_base_` are called *primitive*. + +For all configs under the same folder, it is recommended to have only **one** *primitive* config. +All other configs should inherit from the *primitive* config. +In this way, the maximum of inheritance level is 3. + +For easy understanding, we recommend contributors to inherit from exiting methods. +For example, if some modification is made base on Faster R-CNN, +user may first inherit the basic Faster R-CNN structure +by specifying `_base_ = ../_base_/models/faster-rcnn_r50-dc5.py`, +then modify the necessary fields in the config files. + +If you are building an entirely new method that does not share the structure with any of the existing methods, +you may create a folder `method_name` under `configs`. + +Please refer to [MMEngine](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/config.md) for detailed documentation. + +## Config Name Style + +We follow the below style to name config files. Contributors are advised to follow the same style. + +```shell +{method}_{module}_{train_cfg}_{train_data}_{test_data} +``` + +- `{method}`: method name, like `sort`. +- `{module}`: basic modules of the method, like `faster-rcnn_r50_fpn`. +- `{train_cfg}`: training config which usually contains batch size, epochs, etc, like `8xb4-80e`. +- `{train_data}`: training data, like `mot17halftrain`. +- `{test_data}`: testing data, like `test-mot17halfval`. + +## FAQ + +**Ignore some fields in the base configs** + +Sometimes, you may set `_delete_=True` to ignore some of fields in base configs. +You may refer to [MMEngine](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/config.md) for simple illustration. + +## Tracking Data Structure Introduction + +### Advantages and new features + +In mmdetection tracking task, we employ videos to organize the dataset and use +TrackDataSample to descirbe dataset info. + +- Based on video organization, we provide transform `UniformRefFrameSample` to sample key frames and ref frames and use `TransformBroadcaster` for for clip training. +- TrackDataSample can be viewd as a wrapper of multiple DetDataSample to some extent. It contains a property `video_data_samples` which is a list of DetDataSample, each of which corresponds to a single frame. In addition, it's metainfo includes key_frames_inds and ref_frames_inds to apply clip training way. +- Thanks to video-based data organization, the entire video can be directly tested. This way is more concise and intuitive. We also provide image_based test method, if your GPU mmemory cannot fit the entire video. + +### TODO + +- Some algorithms like StrongSORT, Mask2Former can not support video_based testing. These algorithms pose a challenge to GPU memory. we will optimize this problem in the future. +- Now we do not support joint training of video_based dataset like MOT Challenge Dataset and image_based dataset like Crowdhuman for the algorithm QDTrack. we will optimize this problem in the future. diff --git a/mmdetection/docs/en/user_guides/tracking_dataset_prepare.md b/mmdetection/docs/en/user_guides/tracking_dataset_prepare.md new file mode 100644 index 00000000..56a4b77f --- /dev/null +++ b/mmdetection/docs/en/user_guides/tracking_dataset_prepare.md @@ -0,0 +1,247 @@ +## Dataset Preparation + +This page provides the instructions for dataset preparation on existing benchmarks, include + +- Multiple Object Tracking + + - [MOT Challenge](https://motchallenge.net/) + - [CrowdHuman](https://www.crowdhuman.org/) + +- Video Instance Segmentation + + - [YouTube-VIS](https://youtube-vos.org/dataset/vis/) + +### 1. Download Datasets + +Please download the datasets from the official websites. It is recommended to symlink the root of the datasets to `$MMDETECTION/data`. + +#### 1.1 Multiple Object Tracking + +- For the training and testing of multi object tracking task, one of the MOT Challenge datasets (e.g. MOT17, MOT20) are needed, CrowdHuman can be served as comlementary dataset. + +- For users in China, the following datasets can be downloaded from [OpenDataLab](https://opendatalab.com/) with high speed: + + - [MOT17](https://opendatalab.com/MOT17/download) + - [MOT20](https://opendatalab.com/MOT20/download) + - [CrowdHuman](https://opendatalab.com/CrowdHuman/download) + +#### 1.2 Video Instance Segmentation + +- For the training and testing of video instance segmetatioon task, only one of YouTube-VIS datasets (e.g. YouTube-VIS 2019, YouTube-VIS 2021) is needed. + +- YouTube-VIS 2019 dataset can be download from [YouTubeVOS](https://codalab.lisn.upsaclay.fr/competitions/6064) + +- YouTube-VIS 2021 dataset can be download from [YouTubeVOS](https://codalab.lisn.upsaclay.fr/competitions/7680) + +#### 1.3 Data Structure + +If your folder structure is different from the following, you may need to change the corresponding paths in config files. + +``` +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── annotations +│ │ +| ├── MOT15/MOT16/MOT17/MOT20 +| | ├── train +| | | ├── MOT17-02-DPM +| | | | ├── det +| │ │ │ ├── gt +| │ │ │ ├── img1 +| │ │ │ ├── seqinfo.ini +│ │ │ ├── ...... +| | ├── test +| | | ├── MOT17-01-DPM +| | | | ├── det +| │ │ │ ├── img1 +| │ │ │ ├── seqinfo.ini +│ │ │ ├── ...... +│ │ +│ ├── crowdhuman +│ │ ├── annotation_train.odgt +│ │ ├── annotation_val.odgt +│ │ ├── train +│ │ │ ├── Images +│ │ │ ├── CrowdHuman_train01.zip +│ │ │ ├── CrowdHuman_train02.zip +│ │ │ ├── CrowdHuman_train03.zip +│ │ ├── val +│ │ │ ├── Images +│ │ │ ├── CrowdHuman_val.zip +│ │ +``` + +### 2. Convert Annotations + +In this case, you need to convert the official annotations to coco style. We provide scripts and the usages are as following: + +```shell +# MOT17 +# The processing of other MOT Challenge dataset is the same as MOT17 +python ./tools/dataset_converters/mot2coco.py -i ./data/MOT17/ -o ./data/MOT17/annotations --split-train --convert-det +python ./tools/dataset_converters/mot2reid.py -i ./data/MOT17/ -o ./data/MOT17/reid --val-split 0.2 --vis-threshold 0.3 + +# CrowdHuman +python ./tools/dataset_converters/crowdhuman2coco.py -i ./data/crowdhuman -o ./data/crowdhuman/annotations + +# YouTube-VIS 2019 +python ./tools/dataset_converters/youtubevis2coco.py -i ./data/youtube_vis_2019 -o ./data/youtube_vis_2019/annotations --version 2019 + +# YouTube-VIS 2021 +python ./tools/dataset_converters/youtubevis2coco.py -i ./data/youtube_vis_2021 -o ./data/youtube_vis_2021/annotations --version 2021 + +``` + +The folder structure will be as following after your run these scripts: + +``` +mmdetection +├── mmtrack +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── annotations +│ │ +| ├── MOT15/MOT16/MOT17/MOT20 +| | ├── train +| | | ├── MOT17-02-DPM +| | | | ├── det +| │ │ │ ├── gt +| │ │ │ ├── img1 +| │ │ │ ├── seqinfo.ini +│ │ │ ├── ...... +| | ├── test +| | | ├── MOT17-01-DPM +| | | | ├── det +| │ │ │ ├── img1 +| │ │ │ ├── seqinfo.ini +│ │ │ ├── ...... +| | ├── annotations +| | ├── reid +│ │ │ ├── imgs +│ │ │ ├── meta +│ │ +│ ├── crowdhuman +│ │ ├── annotation_train.odgt +│ │ ├── annotation_val.odgt +│ │ ├── train +│ │ │ ├── Images +│ │ │ ├── CrowdHuman_train01.zip +│ │ │ ├── CrowdHuman_train02.zip +│ │ │ ├── CrowdHuman_train03.zip +│ │ ├── val +│ │ │ ├── Images +│ │ │ ├── CrowdHuman_val.zip +│ │ ├── annotations +│ │ │ ├── crowdhuman_train.json +│ │ │ ├── crowdhuman_val.json +│ │ +│ ├── youtube_vis_2019 +│ │ │── train +│ │ │ │── JPEGImages +│ │ │ │── ...... +│ │ │── valid +│ │ │ │── JPEGImages +│ │ │ │── ...... +│ │ │── test +│ │ │ │── JPEGImages +│ │ │ │── ...... +│ │ │── train.json (the official annotation files) +│ │ │── valid.json (the official annotation files) +│ │ │── test.json (the official annotation files) +│ │ │── annotations (the converted annotation file) +│ │ +│ ├── youtube_vis_2021 +│ │ │── train +│ │ │ │── JPEGImages +│ │ │ │── instances.json (the official annotation files) +│ │ │ │── ...... +│ │ │── valid +│ │ │ │── JPEGImages +│ │ │ │── instances.json (the official annotation files) +│ │ │ │── ...... +│ │ │── test +│ │ │ │── JPEGImages +│ │ │ │── instances.json (the official annotation files) +│ │ │ │── ...... +│ │ │── annotations (the converted annotation file) +``` + +#### The folder of annotations and reid in MOT15/MOT16/MOT17/MOT20 + +We take MOT17 dataset as examples, the other datasets share similar structure. + +There are 8 JSON files in `data/MOT17/annotations`: + +`train_cocoformat.json`: JSON file containing the annotations information of the training set in MOT17 dataset. + +`train_detections.pkl`: Pickle file containing the public detections of the training set in MOT17 dataset. + +`test_cocoformat.json`: JSON file containing the annotations information of the testing set in MOT17 dataset. + +`test_detections.pkl`: Pickle file containing the public detections of the testing set in MOT17 dataset. + +`half-train_cocoformat.json`, `half-train_detections.pkl`, `half-val_cocoformat.json`and `half-val_detections.pkl` share similar meaning with `train_cocoformat.json` and `train_detections.pkl`. The `half` means we split each video in the training set into half. The first half videos are denoted as `half-train` set, and the second half videos are denoted as`half-val` set. + +The structure of `data/MOT17/reid` is as follows: + +``` +reid +├── imgs +│ ├── MOT17-02-FRCNN_000002 +│ │ ├── 000000.jpg +│ │ ├── 000001.jpg +│ │ ├── ... +│ ├── MOT17-02-FRCNN_000003 +│ │ ├── 000000.jpg +│ │ ├── 000001.jpg +│ │ ├── ... +├── meta +│ ├── train_80.txt +│ ├── val_20.txt +``` + +The `80` in `train_80.txt` means the proportion of the training dataset to the whole ReID dataset is 80%. While the proportion of the validation dataset is 20%. + +For training, we provide a annotation list `train_80.txt`. Each line of the list contains a filename and its corresponding ground-truth labels. The format is as follows: + +``` +MOT17-05-FRCNN_000110/000018.jpg 0 +MOT17-13-FRCNN_000146/000014.jpg 1 +MOT17-05-FRCNN_000088/000004.jpg 2 +MOT17-02-FRCNN_000009/000081.jpg 3 +``` + +`MOT17-05-FRCNN_000110` denotes the 110-th person in `MOT17-05-FRCNN` video. + +For validation, The annotation list `val_20.txt` remains the same as format above. + +Images in `reid/imgs` are cropped from raw images in `MOT17/train` by the corresponding `gt.txt`. The value of ground-truth labels should fall in range `[0, num_classes - 1]`. + +#### The folder of annotations in crowdhuman + +There are 2 JSON files in `data/crowdhuman/annotations`: + +`crowdhuman_train.json`: JSON file containing the annotations information of the training set in CrowdHuman dataset. +`crowdhuman_val.json`: JSON file containing the annotations information of the validation set in CrowdHuman dataset. + +#### The folder of annotations in youtube_vis_2019/youtube_vis2021 + +There are 3 JSON files in `data/youtube_vis_2019/annotations` or `data/youtube_vis_2021/annotations`: + +`youtube_vis_2019_train.json`/`youtube_vis_2021_train.json`: JSON file containing the annotations information of the training set in youtube_vis_2019/youtube_vis2021 dataset. + +`youtube_vis_2019_valid.json`/`youtube_vis_2021_valid.json`: JSON file containing the annotations information of the validation set in youtube_vis_2019/youtube_vis2021 dataset. + +`youtube_vis_2019_test.json`/`youtube_vis_2021_test.json`: JSON file containing the annotations information of the testing set in youtube_vis_2019/youtube_vis2021 dataset. diff --git a/mmdetection/docs/en/user_guides/tracking_inference.md b/mmdetection/docs/en/user_guides/tracking_inference.md new file mode 100644 index 00000000..06a6912a --- /dev/null +++ b/mmdetection/docs/en/user_guides/tracking_inference.md @@ -0,0 +1,55 @@ +# Inference + +We provide demo scripts to inference a given video or a folder that contains continuous images. The source codes are available [here](https://github.com/open-mmlab/mmdetection/tree/tracking/demo). + +Note that if you use a folder as the input, the image names there must be **sortable** , which means we can re-order the images according to the numbers contained in the filenames. We now only support reading the images whose filenames end with `.jpg`, `.jpeg` and `.png`. + +## Inference MOT models + +This script can inference an input video / images with a multiple object tracking or video instance segmentation model. + +```shell +python demo/mot_demo.py \ + ${INPUTS} + ${CONFIG_FILE} \ + [--checkpoint ${CHECKPOINT_FILE}] \ + [--detector ${DETECTOR_FILE}] \ + [--reid ${REID_FILE}] \ + [--score-thr ${SCORE_THR}] \ + [--device ${DEVICE}] \ + [--out ${OUTPUT}] \ + [--show] +``` + +The `INPUT` and `OUTPUT` support both _mp4 video_ format and the _folder_ format. + +**Important:** For `DeepSORT`, `SORT`, `StrongSORT`, they need load the weight of the `reid` and the weight of the `detector` separately. Therefore, we use `--detector` and `--reid` to load weights. Other algorithms such as `ByteTrack`, `OCSORT` `QDTrack` `MaskTrackRCNN` and `Mask2Former` use `--checkpoint` to load weights. + +Optional arguments: + +- `CHECKPOINT_FILE`: The checkpoint is optional. +- `DETECTOR_FILE`: The detector is optional. +- `REID_FILE`: The reid is optional. +- `SCORE_THR`: The threshold of score to filter bboxes. +- `DEVICE`: The device for inference. Options are `cpu` or `cuda:0`, etc. +- `OUTPUT`: Output of the visualized demo. If not specified, the `--show` is obligate to show the video on the fly. +- `--show`: Whether show the video on the fly. + +**Examples of running mot model:** + +```shell +# Example 1: do not specify --checkpoint to use --detector +python demo/mot_demo.py \ + demo/demo_mot.mp4 \ + configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py \ + --detector \ + https://download.openmmlab.com/mmtracking/mot/faster_rcnn/faster-rcnn_r50_fpn_4e_mot17-half-64ee2ed4.pth \ + --out mot.mp4 + +# Example 2: use --checkpoint +python demo/mot_demo.py \ + demo/demo_mot.mp4 \ + configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py \ + --checkpoint https://download.openmmlab.com/mmtracking/mot/qdtrack/mot_dataset/qdtrack_faster-rcnn_r50_fpn_4e_mot17_20220315_145635-76f295ef.pth \ + --out mot.mp4 +``` diff --git a/mmdetection/docs/en/user_guides/tracking_train_test.md b/mmdetection/docs/en/user_guides/tracking_train_test.md new file mode 100644 index 00000000..1a6871d7 --- /dev/null +++ b/mmdetection/docs/en/user_guides/tracking_train_test.md @@ -0,0 +1,229 @@ +# Learn to train and test + +## Train + +This section will show how to train existing models on supported datasets. +The following training environments are supported: + +- CPU +- single GPU +- single node multiple GPUs +- multiple nodes + +You can also manage jobs with Slurm. + +Important: + +- You can change the evaluation interval during training by modifying the `train_cfg` as + `train_cfg = dict(val_interval=10)`. That means evaluating the model every 10 epochs. +- The default learning rate in all config files is for 8 GPUs. + According to the [Linear Scaling Rule](https://arxiv.org/abs/1706.02677), + you need to set the learning rate proportional to the batch size if you use different GPUs or images per GPU, + e.g., `lr=0.01` for 8 GPUs * 1 img/gpu and lr=0.04 for 16 GPUs * 2 imgs/gpu. +- During training, log files and checkpoints will be saved to the working directory, + which is specified by CLI argument `--work-dir`. It uses `./work_dirs/CONFIG_NAME` as default. +- If you want the mixed precision training, simply specify CLI argument `--amp`. + +#### 1. Train on CPU + +The model is default put on cuda device. +Only if there are no cuda devices, the model will be put on cpu. +So if you want to train the model on CPU, you need to `export CUDA_VISIBLE_DEVICES=-1` to disable GPU visibility first. +More details in [MMEngine](https://github.com/open-mmlab/mmengine/blob/ca282aee9e402104b644494ca491f73d93a9544f/mmengine/runner/runner.py#L849-L850). + +```shell script +CUDA_VISIBLE_DEVICES=-1 python tools/train.py ${CONFIG_FILE} [optional arguments] +``` + +An example of training the MOT model QDTrack on CPU: + +```shell script +CUDA_VISIBLE_DEVICES=-1 python tools/train.py configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py +``` + +#### 2. Train on single GPU + +If you want to train the model on single GPU, you can directly use the `tools/train.py` as follows. + +```shell script +python tools/train.py ${CONFIG_FILE} [optional arguments] +``` + +You can use `export CUDA_VISIBLE_DEVICES=$GPU_ID` to select the GPU. + +An example of training the MOT model QDTrack on single GPU: + +```shell script +CUDA_VISIBLE_DEVICES=2 python tools/train.py configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py +``` + +#### 3. Train on single node multiple GPUs + +We provide `tools/dist_train.sh` to launch training on multiple GPUs. +The basic usage is as follows. + +```shell script +bash ./tools/dist_train.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments] +``` + +If you would like to launch multiple jobs on a single machine, +e.g., 2 jobs of 4-GPU training on a machine with 8 GPUs, +you need to specify different ports (29500 by default) for each job to avoid communication conflict. + +For example, you can set the port in commands as follows. + +```shell script +CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4 +CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4 +``` + +An example of training the MOT model QDTrack on single node multiple GPUs: + +```shell script +bash ./tools/dist_train.sh configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 +``` + +#### 4. Train on multiple nodes + +If you launch with multiple machines simply connected with ethernet, you can simply run following commands: + +On the first machine: + +```shell script +NNODES=2 NODE_RANK=0 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR bash tools/dist_train.sh $CONFIG $GPUS +``` + +On the second machine: + +```shell script +NNODES=2 NODE_RANK=1 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR bash tools/dist_train.sh $CONFIG $GPUS +``` + +Usually it is slow if you do not have high speed networking like InfiniBand. + +#### 5. Train with Slurm + +[Slurm](https://slurm.schedmd.com/) is a good job scheduling system for computing clusters. +On a cluster managed by Slurm, you can use `slurm_train.sh` to spawn training jobs. +It supports both single-node and multi-node training. + +The basic usage is as follows. + +```shell script +bash ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} ${WORK_DIR} ${GPUS} +``` + +An example of training the MOT model QDTrack with Slurm: + +```shell script +PORT=29501 \ +GPUS_PER_NODE=8 \ +SRUN_ARGS="--quotatype=reserved" \ +bash ./tools/slurm_train.sh \ +mypartition \ +mottrack +configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py +./work_dirs/QDTrack \ +8 +``` + +## Test + +This section will show how to test existing models on supported datasets. +The following testing environments are supported: + +- CPU +- single GPU +- single node multiple GPUs +- multiple nodes + +You can also manage jobs with Slurm. + +Important: + +- In MOT, some algorithms like `DeepSORT`, `SORT`, `StrongSORT` need load the weight of the `reid` and the weight of the `detector` separately. + Other algorithms such as `ByteTrack`, `OCSORT` and `QDTrack` don't need. So we provide `--checkpoint`, `--detector` and `--reid` to load weights. +- We provide two ways to evaluate and test models, video_basede test and image_based test. some algorithms like `StrongSORT`, `Mask2former` only support + video_based test. if your GPU memory can't fit the entire video, you can switch test way by set sampler type. + For example: + video_based test: `sampler=dict(type='DefaultSampler', shuffle=False, round_up=False)` + image_based test: `sampler=dict(type='TrackImgSampler')` +- You can set the results saving path by modifying the key `outfile_prefix` in evaluator. + For example, `val_evaluator = dict(outfile_prefix='results/sort_mot17')`. + Otherwise, a temporal file will be created and will be removed after evaluation. +- If you just want the formatted results without evaluation, you can set `format_only=True`. + For example, `test_evaluator = dict(type='MOTChallengeMetric', metric=['HOTA', 'CLEAR', 'Identity'], outfile_prefix='sort_mot17_results', format_only=True)` + +#### 1. Test on CPU + +The model is default put on cuda device. +Only if there are no cuda devices, the model will be put on cpu. +So if you want to test the model on CPU, you need to `export CUDA_VISIBLE_DEVICES=-1` to disable GPU visibility first. +More details in [MMEngine](https://github.com/open-mmlab/mmengine/blob/ca282aee9e402104b644494ca491f73d93a9544f/mmengine/runner/runner.py#L849-L850). + +```shell script +CUDA_VISIBLE_DEVICES=-1 python tools/test_tracking.py ${CONFIG_FILE} [optional arguments] +``` + +An example of testing the MOT model SORT on CPU: + +```shell script +CUDA_VISIBLE_DEVICES=-1 python tools/test_tracking.py configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py --detector ${CHECKPOINT_FILE} +``` + +#### 2. Test on single GPU + +If you want to test the model on single GPU, you can directly use the `tools/test_tracking.py` as follows. + +```shell script +python tools/test_tracking.py ${CONFIG_FILE} [optional arguments] +``` + +You can use `export CUDA_VISIBLE_DEVICES=$GPU_ID` to select the GPU. + +An example of testing the MOT model QDTrack on single GPU: + +```shell script +CUDA_VISIBLE_DEVICES=2 python tools/test_tracking.py configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py --detector ${CHECKPOINT_FILE} +``` + +#### 3. Test on single node multiple GPUs + +We provide `tools/dist_test_tracking.sh` to launch testing on multiple GPUs. +The basic usage is as follows. + +```shell script +bash ./tools/dist_test_tracking.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments] +``` + +An example of testing the MOT model DeepSort on single node multiple GPUs: + +```shell script +bash ./tools/dist_test_tracking.sh configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 --detector ${CHECKPOINT_FILE} --reid ${CHECKPOINT_FILE} +``` + +#### 4. Test on multiple nodes + +You can test on multiple nodes, which is similar with "Train on multiple nodes". + +#### 5. Test with Slurm + +On a cluster managed by Slurm, you can use `slurm_test_tracking.sh` to spawn testing jobs. +It supports both single-node and multi-node testing. + +The basic usage is as follows. + +```shell script +[GPUS=${GPUS}] bash tools/slurm_test_tracking.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} [optional arguments] +``` + +An example of testing the VIS model Mask2former with Slurm: + +```shell script +GPUS=8 +bash tools/slurm_test_tracking.sh \ +mypartition \ +vis \ +configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py \ +--checkpoint ${CHECKPOINT_FILE} +``` diff --git a/mmdetection/docs/en/user_guides/tracking_visualization.md b/mmdetection/docs/en/user_guides/tracking_visualization.md new file mode 100644 index 00000000..28953256 --- /dev/null +++ b/mmdetection/docs/en/user_guides/tracking_visualization.md @@ -0,0 +1,47 @@ +# Learn about Visualization + +## Local Visualization + +This section will present how to visualize the detection/tracking results with local visualizer. + +If you want to draw prediction results, you can turn this feature on by setting `draw=True` in `TrackVisualizationHook` as follows. + +```shell script +default_hooks = dict(visualization=dict(type='TrackVisualizationHook', draw=True)) +``` + +Specifically, the `TrackVisualizationHook` has the following arguments: + +- `draw`: whether to draw prediction results. If it is False, it means that no drawing will be done. Defaults to False. +- `interval`: The interval of visualization. Defaults to 30. +- `score_thr`: The threshold to visualize the bboxes and masks. Defaults to 0.3. +- `show`: Whether to display the drawn image. Default to False. +- `wait_time`: The interval of show (s). Defaults to 0. +- `test_out_dir`: directory where painted images will be saved in testing process. +- `backend_args`: Arguments to instantiate a file client. Defaults to `None`. + +In the `TrackVisualizationHook`, `TrackLocalVisualizer` will be called to implement visualization for MOT and VIS tasks. +We will present the details below. +You can refer to MMEngine for more details about [Visualization](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/visualization.md) and [Hook](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/hook.md). + +#### Tracking Visualization + +We realize the tracking visualization with class `TrackLocalVisualizer`. +You can call it as follows. + +```python +visualizer = dict(type='TrackLocalVisualizer') +``` + +It has the following arguments: + +- `name`: Name of the instance. Defaults to 'visualizer'. +- `image`: The origin image to draw. The format should be RGB. Defaults to None. +- `vis_backends`: Visual backend config list. Defaults to None. +- `save_dir`: Save file dir for all storage backends. If it is None, the backend storage will not save any data. +- `line_width`: The linewidth of lines. Defaults to 3. +- `alpha`: The transparency of bboxes or mask. Defaults to 0.8. + +Here is a visualization example of DeepSORT: + +![test_img_89](https://user-images.githubusercontent.com/99722489/186062929-6d0e4663-0d8e-4045-9ec8-67e0e41da876.png) diff --git a/mmdetection/docs/en/user_guides/train.md b/mmdetection/docs/en/user_guides/train.md new file mode 100644 index 00000000..a68d5e4f --- /dev/null +++ b/mmdetection/docs/en/user_guides/train.md @@ -0,0 +1,456 @@ +# Train predefined models on standard datasets + +MMDetection also provides out-of-the-box tools for training detection models. +This section will show how to train _predefined_ models (under [configs](../../../configs)) on standard datasets i.e. COCO. + +## Prepare datasets + +Preparing datasets is also necessary for training. See section [Prepare datasets](#prepare-datasets) above for details. + +**Note**: +Currently, the config files under `configs/cityscapes` use COCO pre-trained weights to initialize. +If your network connection is slow or unavailable, it's advisable to download existing models before beginning training to avoid errors. + +## Learning rate auto scaling + +**Important**: The default learning rate in config files is for 8 GPUs and 2 sample per GPU (batch size = 8 * 2 = 16). And it had been set to `auto_scale_lr.base_batch_size` in `config/_base_/schedules/schedule_1x.py`. The learning rate will be automatically scaled based on the value at a batch size of 16. Meanwhile, to avoid affecting other codebases that use mmdet, the default setting for the `auto_scale_lr.enable` flag is `False`. + +If you want to enable this feature, you need to add argument `--auto-scale-lr`. And you need to check the config name which you want to use before you process the command, because the config name indicates the default batch size. +By default, it is `8 x 2 = 16 batch size`, like `faster_rcnn_r50_caffe_fpn_90k_coco.py` or `pisa_faster_rcnn_x101_32x4d_fpn_1x_coco.py`. In other cases, you will see the config file name have `_NxM_` in dictating, like `cornernet_hourglass104_mstest_32x3_210e_coco.py` which batch size is `32 x 3 = 96`, or `scnet_x101_64x4d_fpn_8x1_20e_coco.py` which batch size is `8 x 1 = 8`. + +**Please remember to check the bottom of the specific config file you want to use, it will have `auto_scale_lr.base_batch_size` if the batch size is not `16`. If you can't find those values, check the config file which in `_base_=[xxx]` and you will find it. Please do not modify its values if you want to automatically scale the LR.** + +The basic usage of learning rate auto scaling is as follows. + +```shell +python tools/train.py \ + ${CONFIG_FILE} \ + --auto-scale-lr \ + [optional arguments] +``` + +If you enabled this feature, the learning rate will be automatically scaled according to the number of GPUs on the machine and the batch size of training. See [linear scaling rule](https://arxiv.org/abs/1706.02677) for details. For example, If there are 4 GPUs and 2 pictures on each GPU, `lr = 0.01`, then if there are 16 GPUs and 4 pictures on each GPU, it will automatically scale to `lr = 0.08`. + +If you don't want to use it, you need to calculate the learning rate according to the [linear scaling rule](https://arxiv.org/abs/1706.02677) manually then change `optimizer.lr` in specific config file. + +## Training on a single GPU + +We provide `tools/train.py` to launch training jobs on a single GPU. +The basic usage is as follows. + +```shell +python tools/train.py \ + ${CONFIG_FILE} \ + [optional arguments] +``` + +During training, log files and checkpoints will be saved to the working directory, which is specified by `work_dir` in the config file or via CLI argument `--work-dir`. + +By default, the model is evaluated on the validation set every epoch, the evaluation interval can be specified in the config file as shown below. + +```python +# evaluate the model every 12 epochs. +train_cfg = dict(val_interval=12) +``` + +This tool accepts several optional arguments, including: + +- `--work-dir ${WORK_DIR}`: Override the working directory. +- `--resume`: resume from the latest checkpoint in the work_dir automatically. +- `--resume ${CHECKPOINT_FILE}`: resume from the specific checkpoint. +- `--cfg-options 'Key=value'`: Overrides other settings in the used config. + +**Note:** + +There is a difference between `resume` and `load-from`: + +`resume` loads both the weights of the model and the state of the optimizer, and it inherits the iteration number from the specified checkpoint, so training does not start again from scratch. `load-from`, on the other hand, only loads the weights of the model, and its training starts from scratch. It is often used for fine-tuning a model. `load-from` needs to be written in the config file, while `resume` is passed as a command line argument. + +## Training on CPU + +The process of training on the CPU is consistent with single GPU training. We just need to disable GPUs before the training process. + +```shell +export CUDA_VISIBLE_DEVICES=-1 +``` + +And then run the script [above](#training-on-a-single-GPU). + +**Note**: + +We do not recommend users to use the CPU for training because it is too slow. We support this feature to allow users to debug on machines without GPU for convenience. + +## Training on multiple GPUs + +We provide `tools/dist_train.sh` to launch training on multiple GPUs. +The basic usage is as follows. + +```shell +bash ./tools/dist_train.sh \ + ${CONFIG_FILE} \ + ${GPU_NUM} \ + [optional arguments] +``` + +Optional arguments remain the same as stated [above](#training-on-a-single-GPU). + +### Launch multiple jobs simultaneously + +If you would like to launch multiple jobs on a single machine, e.g., 2 jobs of 4-GPU training on a machine with 8 GPUs, +you need to specify different ports (29500 by default) for each job to avoid communication conflict. + +If you use `dist_train.sh` to launch training jobs, you can set the port in the commands. + +```shell +CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4 +CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4 +``` + +## Train with multiple machines + +If you launch with multiple machines simply connected with ethernet, you can simply run the following commands: + +On the first machine: + +```shell +NNODES=2 NODE_RANK=0 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS +``` + +On the second machine: + +```shell +NNODES=2 NODE_RANK=1 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS +``` + +Usually, it is slow if you do not have high-speed networking like InfiniBand. + +## Manage jobs with Slurm + +[Slurm](https://slurm.schedmd.com/) is a good job scheduling system for computing clusters. +On a cluster managed by Slurm, you can use `slurm_train.sh` to spawn training jobs. It supports both single-node and multi-node training. + +The basic usage is as follows. + +```shell +[GPUS=${GPUS}] ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} ${WORK_DIR} +``` + +Below is an example of using 16 GPUs to train Mask R-CNN on a Slurm partition named _dev_, and set the work-dir to some shared file systems. + +```shell +GPUS=16 ./tools/slurm_train.sh dev mask_r50_1x configs/mask-rcnn_r50_fpn_1x_coco.py /nfs/xxxx/mask_rcnn_r50_fpn_1x +``` + +You can check [the source code](../../../tools/slurm_train.sh) to review full arguments and environment variables. + +When using Slurm, the port option needs to be set in one of the following ways: + +1. Set the port through `--options`. This is more recommended since it does not change the original configs. + + ```shell + CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR} --cfg-options 'dist_params.port=29500' + CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR} --cfg-options 'dist_params.port=29501' + ``` + +2. Modify the config files to set different communication ports. + + In `config1.py`, set + + ```python + dist_params = dict(backend='nccl', port=29500) + ``` + + In `config2.py`, set + + ```python + dist_params = dict(backend='nccl', port=29501) + ``` + + Then you can launch two jobs with `config1.py` and `config2.py`. + + ```shell + CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR} + CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR} + ``` + +# Train with customized datasets + +In this part, you will know how to train predefined models with customized datasets and then test it. We use the [balloon dataset](https://github.com/matterport/Mask_RCNN/tree/master/samples/balloon) as an example to describe the whole process. + +The basic steps are as below: + +1. Prepare the customized dataset +2. Prepare a config +3. Train, test, and infer models on the customized dataset. + +## Prepare the customized dataset + +There are three ways to support a new dataset in MMDetection: + +1. Reorganize the dataset into COCO format. +2. Reorganize the dataset into a middle format. +3. Implement a new dataset. + +Usually, we recommend using the first two methods which are usually easier than the third. + +In this note, we give an example of converting the data into COCO format. + +**Note**: Datasets and metrics have been decoupled except CityScapes since MMDetection 3.0. Therefore, users can use any kind of evaluation metrics for any format of datasets during validation. For example: evaluate on COCO dataset with VOC metric, or evaluate on OpenImages dataset with both VOC and COCO metrics. + +### COCO annotation format + +The necessary keys of COCO format for instance segmentation are as below, for the complete details, please refer [here](https://cocodataset.org/#format-data). + +```json +{ + "images": [image], + "annotations": [annotation], + "categories": [category] +} + +image = { + "id": int, + "width": int, + "height": int, + "file_name": str, +} + +annotation = { + "id": int, + "image_id": int, + "category_id": int, + "segmentation": RLE or [polygon], + "area": float, + "bbox": [x,y,width,height], # (x, y) are the coordinates of the upper left corner of the bbox + "iscrowd": 0 or 1, +} + +categories = [{ + "id": int, + "name": str, + "supercategory": str, +}] +``` + +Assume we use the balloon dataset. +After downloading the data, we need to implement a function to convert the annotation format into the COCO format. Then we can use implemented `CocoDataset` to load the data and perform training and evaluation. + +If you take a look at the dataset, you will find the dataset format is as below: + +```json +{'base64_img_data': '', + 'file_attributes': {}, + 'filename': '34020010494_e5cb88e1c4_k.jpg', + 'fileref': '', + 'regions': {'0': {'region_attributes': {}, + 'shape_attributes': {'all_points_x': [1020, + 1000, + 994, + 1003, + 1023, + 1050, + 1089, + 1134, + 1190, + 1265, + 1321, + 1361, + 1403, + 1428, + 1442, + 1445, + 1441, + 1427, + 1400, + 1361, + 1316, + 1269, + 1228, + 1198, + 1207, + 1210, + 1190, + 1177, + 1172, + 1174, + 1170, + 1153, + 1127, + 1104, + 1061, + 1032, + 1020], + 'all_points_y': [963, + 899, + 841, + 787, + 738, + 700, + 663, + 638, + 621, + 619, + 643, + 672, + 720, + 765, + 800, + 860, + 896, + 942, + 990, + 1035, + 1079, + 1112, + 1129, + 1134, + 1144, + 1153, + 1166, + 1166, + 1150, + 1136, + 1129, + 1122, + 1112, + 1084, + 1037, + 989, + 963], + 'name': 'polygon'}}}, + 'size': 1115004} +``` + +The annotation is a JSON file where each key indicates an image's all annotations. +The code to convert the balloon dataset into coco format is as below. + +```python +import os.path as osp + +import mmcv + +from mmengine.fileio import dump, load +from mmengine.utils import track_iter_progress + + +def convert_balloon_to_coco(ann_file, out_file, image_prefix): + data_infos = load(ann_file) + + annotations = [] + images = [] + obj_count = 0 + for idx, v in enumerate(track_iter_progress(data_infos.values())): + filename = v['filename'] + img_path = osp.join(image_prefix, filename) + height, width = mmcv.imread(img_path).shape[:2] + + images.append( + dict(id=idx, file_name=filename, height=height, width=width)) + + for _, obj in v['regions'].items(): + assert not obj['region_attributes'] + obj = obj['shape_attributes'] + px = obj['all_points_x'] + py = obj['all_points_y'] + poly = [(x + 0.5, y + 0.5) for x, y in zip(px, py)] + poly = [p for x in poly for p in x] + + x_min, y_min, x_max, y_max = (min(px), min(py), max(px), max(py)) + + data_anno = dict( + image_id=idx, + id=obj_count, + category_id=0, + bbox=[x_min, y_min, x_max - x_min, y_max - y_min], + area=(x_max - x_min) * (y_max - y_min), + segmentation=[poly], + iscrowd=0) + annotations.append(data_anno) + obj_count += 1 + + coco_format_json = dict( + images=images, + annotations=annotations, + categories=[{ + 'id': 0, + 'name': 'balloon' + }]) + dump(coco_format_json, out_file) + + +if __name__ == '__main__': + convert_balloon_to_coco(ann_file='data/balloon/train/via_region_data.json', + out_file='data/balloon/train/annotation_coco.json', + image_prefix='data/balloon/train') + convert_balloon_to_coco(ann_file='data/balloon/val/via_region_data.json', + out_file='data/balloon/val/annotation_coco.json', + image_prefix='data/balloon/val') + +``` + +Using the function above, users can successfully convert the annotation file into json format, then we can use `CocoDataset` to train and evaluate the model with `CocoMetric`. + +## Prepare a config + +The second step is to prepare a config thus the dataset could be successfully loaded. Assume that we want to use Mask R-CNN with FPN, the config to train the detector on balloon dataset is as below. Assume the config is under directory `configs/balloon/` and named as `mask-rcnn_r50-caffe_fpn_ms-poly-1x_balloon.py`, the config is as below. Please refer [Learn about Configs - MMDetection 3.0.0 documentation](https://mmdetection.readthedocs.io/en/latest/user_guides/config.html) to get detailed information about config files. + +```python +# The new config inherits a base config to highlight the necessary modification +_base_ = '../mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py' + +# We also need to change the num_classes in head to match the dataset's annotation +model = dict( + roi_head=dict( + bbox_head=dict(num_classes=1), mask_head=dict(num_classes=1))) + +# Modify dataset related settings +data_root = 'data/balloon/' +metainfo = { + 'classes': ('balloon', ), + 'palette': [ + (220, 20, 60), + ] +} +train_dataloader = dict( + batch_size=1, + dataset=dict( + data_root=data_root, + metainfo=metainfo, + ann_file='train/annotation_coco.json', + data_prefix=dict(img='train/'))) +val_dataloader = dict( + dataset=dict( + data_root=data_root, + metainfo=metainfo, + ann_file='val/annotation_coco.json', + data_prefix=dict(img='val/'))) +test_dataloader = val_dataloader + +# Modify metric related settings +val_evaluator = dict(ann_file=data_root + 'val/annotation_coco.json') +test_evaluator = val_evaluator + +# We can use the pre-trained Mask RCNN model to obtain higher performance +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth' + +``` + +## Train a new model + +To train a model with the new config, you can simply run + +```shell +python tools/train.py configs/balloon/mask-rcnn_r50-caffe_fpn_ms-poly-1x_balloon.py +``` + +For more detailed usages, please refer to the [training guide](https://mmdetection.readthedocs.io/en/latest/user_guides/train.html#train-predefined-models-on-standard-datasets). + +## Test and inference + +To test the trained model, you can simply run + +```shell +python tools/test.py configs/balloon/mask-rcnn_r50-caffe_fpn_ms-poly-1x_balloon.py work_dirs/mask-rcnn_r50-caffe_fpn_ms-poly-1x_balloon/epoch_12.pth +``` + +For more detailed usages, please refer to the [testing guide](https://mmdetection.readthedocs.io/en/latest/user_guides/test.html). diff --git a/mmdetection/docs/en/user_guides/useful_hooks.md b/mmdetection/docs/en/user_guides/useful_hooks.md new file mode 100644 index 00000000..4c30686d --- /dev/null +++ b/mmdetection/docs/en/user_guides/useful_hooks.md @@ -0,0 +1,105 @@ +# Useful Hooks + +MMDetection and MMEngine provide users with various useful hooks including log hooks, `NumClassCheckHook`, etc. This tutorial introduces the functionalities and usages of hooks implemented in MMDetection. For using hooks in MMEngine, please read the [API documentation in MMEngine](https://github.com/open-mmlab/mmengine/tree/main/docs/en/tutorials/hook.md). + +## CheckInvalidLossHook + +## NumClassCheckHook + +## MemoryProfilerHook + +[Memory profiler hook](https://github.com/open-mmlab/mmdetection/blob/main/mmdet/engine/hooks/memory_profiler_hook.py) records memory information including virtual memory, swap memory, and the memory of the current process. This hook helps grasp the memory usage of the system and discover potential memory leak bugs. To use this hook, users should install `memory_profiler` and `psutil` by `pip install memory_profiler psutil` first. + +### Usage + +To use this hook, users should add the following code to the config file. + +```python +custom_hooks = [ + dict(type='MemoryProfilerHook', interval=50) +] +``` + +### Result + +During training, you can see the messages in the log recorded by `MemoryProfilerHook` as below. + +```text +The system has 250 GB (246360 MB + 9407 MB) of memory and 8 GB (5740 MB + 2452 MB) of swap memory in total. Currently 9407 MB (4.4%) of memory and 5740 MB (29.9%) of swap memory were consumed. And the current training process consumed 5434 MB of memory. +``` + +```text +2022-04-21 08:49:56,881 - mmengine - INFO - Memory information available_memory: 246360 MB, used_memory: 9407 MB, memory_utilization: 4.4 %, available_swap_memory: 5740 MB, used_swap_memory: 2452 MB, swap_memory_utilization: 29.9 %, current_process_memory: 5434 MB +``` + +## SetEpochInfoHook + +## SyncNormHook + +## SyncRandomSizeHook + +## YOLOXLrUpdaterHook + +## YOLOXModeSwitchHook + +## How to implement a custom hook + +In general, there are 20 points where hooks can be inserted from the beginning to the end of model training. The users can implement custom hooks and insert them at different points in the process of training to do what they want. + +- global points: `before_run`, `after_run` +- points in training: `before_train`, `before_train_epoch`, `before_train_iter`, `after_train_iter`, `after_train_epoch`, `after_train` +- points in validation: `before_val`, `before_val_epoch`, `before_val_iter`, `after_val_iter`, `after_val_epoch`, `after_val` +- points at testing: `before_test`, `before_test_epoch`, `before_test_iter`, `after_test_iter`, `after_test_epoch`, `after_test` +- other points: `before_save_checkpoint`, `after_save_checkpoint` + +For example, users can implement a hook to check loss and terminate training when loss goes NaN. To achieve that, there are three steps to go: + +1. Implement a new hook that inherits the `Hook` class in MMEngine, and implement `after_train_iter` method which checks whether loss goes NaN after every `n` training iterations. +2. The implemented hook should be registered in `HOOKS` by `@HOOKS.register_module()` as shown in the code below. +3. Add `custom_hooks = [dict(type='MemoryProfilerHook', interval=50)]` in the config file. + +```python +from typing import Optional + +import torch +from mmengine.hooks import Hook +from mmengine.runner import Runner + +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class CheckInvalidLossHook(Hook): + """Check invalid loss hook. + + This hook will regularly check whether the loss is valid + during training. + + Args: + interval (int): Checking interval (every k iterations). + Default: 50. + """ + + def __init__(self, interval: int = 50) -> None: + self.interval = interval + + def after_train_iter(self, + runner: Runner, + batch_idx: int, + data_batch: Optional[dict] = None, + outputs: Optional[dict] = None) -> None: + """Regularly check whether the loss is valid every n iterations. + + Args: + runner (:obj:`Runner`): The runner of the training process. + batch_idx (int): The index of the current batch in the train loop. + data_batch (dict, Optional): Data from dataloader. + Defaults to None. + outputs (dict, Optional): Outputs from model. Defaults to None. + """ + if self.every_n_train_iters(runner, self.interval): + assert torch.isfinite(outputs['loss']), \ + runner.logger.info('loss become infinite or NaN!') +``` + +Please read [customize_runtime](../advanced_guides/customize_runtime.md) for more about implementing a custom hook. diff --git a/mmdetection/docs/en/user_guides/useful_tools.md b/mmdetection/docs/en/user_guides/useful_tools.md new file mode 100644 index 00000000..8a79f0c2 --- /dev/null +++ b/mmdetection/docs/en/user_guides/useful_tools.md @@ -0,0 +1,660 @@ +Apart from training/testing scripts, We provide lots of useful tools under the +`tools/` directory. + +## Log Analysis + +`tools/analysis_tools/analyze_logs.py` plots loss/mAP curves given a training +log file. Run `pip install seaborn` first to install the dependency. + +```shell +python tools/analysis_tools/analyze_logs.py plot_curve [--keys ${KEYS}] [--eval-interval ${EVALUATION_INTERVAL}] [--title ${TITLE}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}] +``` + +![loss curve image](../../../resources/loss_curve.png) + +Examples: + +- Plot the classification loss of some run. + + ```shell + python tools/analysis_tools/analyze_logs.py plot_curve log.json --keys loss_cls --legend loss_cls + ``` + +- Plot the classification and regression loss of some run, and save the figure to a pdf. + + ```shell + python tools/analysis_tools/analyze_logs.py plot_curve log.json --keys loss_cls loss_bbox --out losses.pdf + ``` + +- Compare the bbox mAP of two runs in the same figure. + + ```shell + python tools/analysis_tools/analyze_logs.py plot_curve log1.json log2.json --keys bbox_mAP --legend run1 run2 + ``` + +- Compute the average training speed. + + ```shell + python tools/analysis_tools/analyze_logs.py cal_train_time log.json [--include-outliers] + ``` + + The output is expected to be like the following. + + ```text + -----Analyze train time of work_dirs/some_exp/20190611_192040.log.json----- + slowest epoch 11, average time is 1.2024 + fastest epoch 1, average time is 1.1909 + time std over epochs is 0.0028 + average iter time: 1.1959 s/iter + ``` + +## Result Analysis + +`tools/analysis_tools/analyze_results.py` calculates single image mAP and saves or shows the topk images with the highest and lowest scores based on prediction results. + +**Usage** + +```shell +python tools/analysis_tools/analyze_results.py \ + ${CONFIG} \ + ${PREDICTION_PATH} \ + ${SHOW_DIR} \ + [--show] \ + [--wait-time ${WAIT_TIME}] \ + [--topk ${TOPK}] \ + [--show-score-thr ${SHOW_SCORE_THR}] \ + [--cfg-options ${CFG_OPTIONS}] +``` + +Description of all arguments: + +- `config` : The path of a model config file. +- `prediction_path`: Output result file in pickle format from `tools/test.py` +- `show_dir`: Directory where painted GT and detection images will be saved +- `--show`: Determines whether to show painted images, If not specified, it will be set to `False` +- `--wait-time`: The interval of show (s), 0 is block +- `--topk`: The number of saved images that have the highest and lowest `topk` scores after sorting. If not specified, it will be set to `20`. +- `--show-score-thr`: Show score threshold. If not specified, it will be set to `0`. +- `--cfg-options`: If specified, the key-value pair optional cfg will be merged into config file + +**Examples**: + +Assume that you have got result file in pickle format from `tools/test.py` in the path './result.pkl'. + +1. Test Faster R-CNN and visualize the results, save images to the directory `results/` + +```shell +python tools/analysis_tools/analyze_results.py \ + configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \ + result.pkl \ + results \ + --show +``` + +2. Test Faster R-CNN and specified topk to 50, save images to the directory `results/` + +```shell +python tools/analysis_tools/analyze_results.py \ + configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \ + result.pkl \ + results \ + --topk 50 +``` + +3. If you want to filter the low score prediction results, you can specify the `show-score-thr` parameter + +```shell +python tools/analysis_tools/analyze_results.py \ + configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \ + result.pkl \ + results \ + --show-score-thr 0.3 +``` + +## Fusing results from multiple models + +`tools/analysis_tools/fusion_results.py` can fusing predictions using Weighted Boxes Fusion(WBF) from different object detection models. (Currently support coco format only) + +**Usage** + +```shell +python tools/analysis_tools/fuse_results.py \ + ${PRED_RESULTS} \ + [--annotation ${ANNOTATION}] \ + [--weights ${WEIGHTS}] \ + [--fusion-iou-thr ${FUSION_IOU_THR}] \ + [--skip-box-thr ${SKIP_BOX_THR}] \ + [--conf-type ${CONF_TYPE}] \ + [--eval-single ${EVAL_SINGLE}] \ + [--save-fusion-results ${SAVE_FUSION_RESULTS}] \ + [--out-dir ${OUT_DIR}] +``` + +Description of all arguments: + +- `pred-results`: Paths of detection results from different models.(Currently support coco format only) +- `--annotation`: Path of ground-truth. +- `--weights`: List of weights for each model. Default: `None`, which means weight == 1 for each model. +- `--fusion-iou-thr`: IoU value for boxes to be a match。Default: `0.55`。 +- `--skip-box-thr`: The confidence threshold that needs to be excluded in the WBF algorithm. bboxes whose confidence is less than this value will be excluded.。Default: `0`。 +- `--conf-type`: How to calculate confidence in weighted boxes. + - `avg`: average value,default. + - `max`: maximum value. + - `box_and_model_avg`: box and model wise hybrid weighted average. + - `absent_model_aware_avg`: weighted average that takes into account the absent model. +- `--eval-single`: Whether evaluate every single model. Default: `False`. +- `--save-fusion-results`: Whether save fusion results. Default: `False`. +- `--out-dir`: Path of fusion results. + +**Examples**: +Assume that you have got 3 result files from corresponding models through `tools/test.py`, which paths are './faster-rcnn_r50-caffe_fpn_1x_coco.json', './retinanet_r50-caffe_fpn_1x_coco.json', './cascade-rcnn_r50-caffe_fpn_1x_coco.json' respectively. The ground-truth file path is './annotation.json'. + +1. Fusion of predictions from three models and evaluation of their effectiveness + +```shell +python tools/analysis_tools/fuse_results.py \ + ./faster-rcnn_r50-caffe_fpn_1x_coco.json \ + ./retinanet_r50-caffe_fpn_1x_coco.json \ + ./cascade-rcnn_r50-caffe_fpn_1x_coco.json \ + --annotation ./annotation.json \ + --weights 1 2 3 \ +``` + +2. Simultaneously evaluate each single model and fusion results + +```shell +python tools/analysis_tools/fuse_results.py \ + ./faster-rcnn_r50-caffe_fpn_1x_coco.json \ + ./retinanet_r50-caffe_fpn_1x_coco.json \ + ./cascade-rcnn_r50-caffe_fpn_1x_coco.json \ + --annotation ./annotation.json \ + --weights 1 2 3 \ + --eval-single +``` + +3. Fusion of prediction results from three models and save + +```shell +python tools/analysis_tools/fuse_results.py \ + ./faster-rcnn_r50-caffe_fpn_1x_coco.json \ + ./retinanet_r50-caffe_fpn_1x_coco.json \ + ./cascade-rcnn_r50-caffe_fpn_1x_coco.json \ + --annotation ./annotation.json \ + --weights 1 2 3 \ + --save-fusion-results \ + --out-dir outputs/fusion +``` + +## Visualization + +### Visualize Datasets + +`tools/analysis_tools/browse_dataset.py` helps the user to browse a detection dataset (both +images and bounding box annotations) visually, or save the image to a +designated directory. + +```shell +python tools/analysis_tools/browse_dataset.py ${CONFIG} [-h] [--skip-type ${SKIP_TYPE[SKIP_TYPE...]}] [--output-dir ${OUTPUT_DIR}] [--not-show] [--show-interval ${SHOW_INTERVAL}] +``` + +### Visualize Models + +First, convert the model to ONNX as described +[here](#convert-mmdetection-model-to-onnx-experimental). +Note that currently only RetinaNet is supported, support for other models +will be coming in later versions. +The converted model could be visualized by tools like [Netron](https://github.com/lutzroeder/netron). + +### Visualize Predictions + +If you need a lightweight GUI for visualizing the detection results, you can refer [DetVisGUI project](https://github.com/Chien-Hung/DetVisGUI/tree/mmdetection). + +## Error Analysis + +`tools/analysis_tools/coco_error_analysis.py` analyzes COCO results per category and by +different criterion. It can also make a plot to provide useful information. + +```shell +python tools/analysis_tools/coco_error_analysis.py ${RESULT} ${OUT_DIR} [-h] [--ann ${ANN}] [--types ${TYPES[TYPES...]}] +``` + +Example: + +Assume that you have got [Mask R-CNN checkpoint file](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth) in the path 'checkpoint'. For other checkpoints, please refer to our [model zoo](./model_zoo.md). + +You can modify the test_evaluator to save the results bbox by: + +1. Find which dataset in 'configs/base/datasets' the current config corresponds to. +2. Replace the original test_evaluator and test_dataloader with test_evaluator and test_dataloader in the comment in dataset config. +3. Use the following command to get the results bbox and segmentation json file. + +```shell +python tools/test.py \ + configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py \ + checkpoint/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth \ +``` + +1. Get COCO bbox error results per category , save analyze result images to the directory(In [config](../../../configs/_base_/datasets/coco_instance.py) the default directory is './work_dirs/coco_instance/test') + +```shell +python tools/analysis_tools/coco_error_analysis.py \ + results.bbox.json \ + results \ + --ann=data/coco/annotations/instances_val2017.json \ +``` + +2. Get COCO segmentation error results per category , save analyze result images to the directory + +```shell +python tools/analysis_tools/coco_error_analysis.py \ + results.segm.json \ + results \ + --ann=data/coco/annotations/instances_val2017.json \ + --types='segm' +``` + +## Model Serving + +In order to serve an `MMDetection` model with [`TorchServe`](https://pytorch.org/serve/), you can follow the steps: + +### 1. Install TorchServe + +Suppose you have a `Python` environment with `PyTorch` and `MMDetection` successfully installed, +then you could run the following command to install `TorchServe` and its dependencies. +For more other installation options, please refer to the [quick start](https://github.com/pytorch/serve/blob/master/README.md#serve-a-model). + +```shell +python -m pip install torchserve torch-model-archiver torch-workflow-archiver nvgpu +``` + +**Note**: Please refer to [torchserve docker](https://github.com/pytorch/serve/blob/master/docker/README.md) if you want to use `TorchServe` in docker. + +### 2. Convert model from MMDetection to TorchServe + +```shell +python tools/deployment/mmdet2torchserve.py ${CONFIG_FILE} ${CHECKPOINT_FILE} \ +--output-folder ${MODEL_STORE} \ +--model-name ${MODEL_NAME} +``` + +### 3. Start `TorchServe` + +```shell +torchserve --start --ncs \ + --model-store ${MODEL_STORE} \ + --models ${MODEL_NAME}.mar +``` + +### 4. Test deployment + +```shell +curl -O curl -O https://raw.githubusercontent.com/pytorch/serve/master/docs/images/3dogs.jpg +curl http://127.0.0.1:8080/predictions/${MODEL_NAME} -T 3dogs.jpg +``` + +You should obtain a response similar to: + +```json +[ + { + "class_label": 16, + "class_name": "dog", + "bbox": [ + 294.63409423828125, + 203.99111938476562, + 417.048583984375, + 281.62744140625 + ], + "score": 0.9987992644309998 + }, + { + "class_label": 16, + "class_name": "dog", + "bbox": [ + 404.26019287109375, + 126.0080795288086, + 574.5091552734375, + 293.6662292480469 + ], + "score": 0.9979367256164551 + }, + { + "class_label": 16, + "class_name": "dog", + "bbox": [ + 197.2144775390625, + 93.3067855834961, + 307.8505554199219, + 276.7560119628906 + ], + "score": 0.993338406085968 + } +] +``` + +#### Compare results + +And you can use `test_torchserver.py` to compare result of `TorchServe` and `PyTorch`, and visualize them. + +```shell +python tools/deployment/test_torchserver.py ${IMAGE_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} ${MODEL_NAME} +[--inference-addr ${INFERENCE_ADDR}] [--device ${DEVICE}] [--score-thr ${SCORE_THR}] [--work-dir ${WORK_DIR}] +``` + +Example: + +```shell +python tools/deployment/test_torchserver.py \ +demo/demo.jpg \ +configs/yolo/yolov3_d53_8xb8-320-273e_coco.py \ +checkpoint/yolov3_d53_320_273e_coco-421362b6.pth \ +yolov3 \ +--work-dir ./work-dir +``` + +### 5. Stop `TorchServe` + +```shell +torchserve --stop +``` + +## Model Complexity + +`tools/analysis_tools/get_flops.py` is a script adapted from [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) to compute the FLOPs and params of a given model. + +```shell +python tools/analysis_tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}] +``` + +You will get the results like this. + +```text +============================== +Input shape: (3, 1280, 800) +Flops: 239.32 GFLOPs +Params: 37.74 M +============================== +``` + +**Note**: This tool is still experimental and we do not guarantee that the +number is absolutely correct. You may well use the result for simple +comparisons, but double check it before you adopt it in technical reports or papers. + +1. FLOPs are related to the input shape while parameters are not. The default + input shape is (1, 3, 1280, 800). +2. Some operators are not counted into FLOPs like GN and custom operators. Refer to [`mmcv.cnn.get_model_complexity_info()`](https://github.com/open-mmlab/mmcv/blob/2.x/mmcv/cnn/utils/flops_counter.py) for details. +3. The FLOPs of two-stage detectors is dependent on the number of proposals. + +## Model conversion + +### MMDetection model to ONNX + +We provide a script to convert model to [ONNX](https://github.com/onnx/onnx) format. We also support comparing the output results between Pytorch and ONNX model for verification. More details can refer to [mmdeploy](https://github.com/open-mmlab/mmdeploy) + +### MMDetection 1.x model to MMDetection 2.x + +`tools/model_converters/upgrade_model_version.py` upgrades a previous MMDetection checkpoint +to the new version. Note that this script is not guaranteed to work as some +breaking changes are introduced in the new version. It is recommended to +directly use the new checkpoints. + +```shell +python tools/model_converters/upgrade_model_version.py ${IN_FILE} ${OUT_FILE} [-h] [--num-classes NUM_CLASSES] +``` + +### RegNet model to MMDetection + +`tools/model_converters/regnet2mmdet.py` convert keys in pycls pretrained RegNet models to +MMDetection style. + +```shell +python tools/model_converters/regnet2mmdet.py ${SRC} ${DST} [-h] +``` + +### Detectron ResNet to Pytorch + +`tools/model_converters/detectron2pytorch.py` converts keys in the original detectron pretrained +ResNet models to PyTorch style. + +```shell +python tools/model_converters/detectron2pytorch.py ${SRC} ${DST} ${DEPTH} [-h] +``` + +### Prepare a model for publishing + +`tools/model_converters/publish_model.py` helps users to prepare their model for publishing. + +Before you upload a model to AWS, you may want to + +1. convert model weights to CPU tensors +2. delete the optimizer states and +3. compute the hash of the checkpoint file and append the hash id to the + filename. + +```shell +python tools/model_converters/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME} +``` + +E.g., + +```shell +python tools/model_converters/publish_model.py work_dirs/faster_rcnn/latest.pth faster_rcnn_r50_fpn_1x_20190801.pth +``` + +The final output filename will be `faster_rcnn_r50_fpn_1x_20190801-{hash id}.pth`. + +## Dataset Conversion + +`tools/data_converters/` contains tools to convert the Cityscapes dataset +and Pascal VOC dataset to the COCO format. + +```shell +python tools/dataset_converters/cityscapes.py ${CITYSCAPES_PATH} [-h] [--img-dir ${IMG_DIR}] [--gt-dir ${GT_DIR}] [-o ${OUT_DIR}] [--nproc ${NPROC}] +python tools/dataset_converters/pascal_voc.py ${DEVKIT_PATH} [-h] [-o ${OUT_DIR}] +``` + +## Dataset Download + +`tools/misc/download_dataset.py` supports downloading datasets such as COCO, VOC, and LVIS. + +```shell +python tools/misc/download_dataset.py --dataset-name coco2017 +python tools/misc/download_dataset.py --dataset-name voc2007 +python tools/misc/download_dataset.py --dataset-name lvis +``` + +For users in China, these datasets can also be downloaded from [OpenDataLab](https://opendatalab.com/?source=OpenMMLab%20GitHub) with high speed: + +- [COCO2017](https://opendatalab.com/COCO_2017/download?source=OpenMMLab%20GitHub) +- [VOC2007](https://opendatalab.com/PASCAL_VOC2007/download?source=OpenMMLab%20GitHub) +- [VOC2012](https://opendatalab.com/PASCAL_VOC2012/download?source=OpenMMLab%20GitHub) +- [LVIS](https://opendatalab.com/LVIS/download?source=OpenMMLab%20GitHub) + +## Benchmark + +### Robust Detection Benchmark + +`tools/analysis_tools/test_robustness.py` and`tools/analysis_tools/robustness_eval.py` helps users to evaluate model robustness. The core idea comes from [Benchmarking Robustness in Object Detection: Autonomous Driving when Winter is Coming](https://arxiv.org/abs/1907.07484). For more information how to evaluate models on corrupted images and results for a set of standard models please refer to [robustness_benchmarking.md](robustness_benchmarking.md). + +### FPS Benchmark + +`tools/analysis_tools/benchmark.py` helps users to calculate FPS. The FPS value includes model forward and post-processing. In order to get a more accurate value, currently only supports single GPU distributed startup mode. + +```shell +python -m torch.distributed.launch --nproc_per_node=1 --master_port=${PORT} tools/analysis_tools/benchmark.py \ + ${CONFIG} \ + [--checkpoint ${CHECKPOINT}] \ + [--repeat-num ${REPEAT_NUM}] \ + [--max-iter ${MAX_ITER}] \ + [--log-interval ${LOG_INTERVAL}] \ + --launcher pytorch +``` + +Examples: Assuming that you have already downloaded the `Faster R-CNN` model checkpoint to the directory `checkpoints/`. + +```shell +python -m torch.distributed.launch --nproc_per_node=1 --master_port=29500 tools/analysis_tools/benchmark.py \ + configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \ + checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth \ + --launcher pytorch +``` + +## Miscellaneous + +### Evaluating a metric + +`tools/analysis_tools/eval_metric.py` evaluates certain metrics of a pkl result file +according to a config file. + +```shell +python tools/analysis_tools/eval_metric.py ${CONFIG} ${PKL_RESULTS} [-h] [--format-only] [--eval ${EVAL[EVAL ...]}] + [--cfg-options ${CFG_OPTIONS [CFG_OPTIONS ...]}] + [--eval-options ${EVAL_OPTIONS [EVAL_OPTIONS ...]}] +``` + +### Print the entire config + +`tools/misc/print_config.py` prints the whole config verbatim, expanding all its +imports. + +```shell +python tools/misc/print_config.py ${CONFIG} [-h] [--options ${OPTIONS [OPTIONS...]}] +``` + +## Hyper-parameter Optimization + +### YOLO Anchor Optimization + +`tools/analysis_tools/optimize_anchors.py` provides two method to optimize YOLO anchors. + +One is k-means anchor cluster which refers from [darknet](https://github.com/AlexeyAB/darknet/blob/master/src/detector.c#L1421). + +```shell +python tools/analysis_tools/optimize_anchors.py ${CONFIG} --algorithm k-means --input-shape ${INPUT_SHAPE [WIDTH HEIGHT]} --output-dir ${OUTPUT_DIR} +``` + +Another is using differential evolution to optimize anchors. + +```shell +python tools/analysis_tools/optimize_anchors.py ${CONFIG} --algorithm differential_evolution --input-shape ${INPUT_SHAPE [WIDTH HEIGHT]} --output-dir ${OUTPUT_DIR} +``` + +E.g., + +```shell +python tools/analysis_tools/optimize_anchors.py configs/yolo/yolov3_d53_8xb8-320-273e_coco.py --algorithm differential_evolution --input-shape 608 608 --device cuda --output-dir work_dirs +``` + +You will get: + +``` +loading annotations into memory... +Done (t=9.70s) +creating index... +index created! +2021-07-19 19:37:20,951 - mmdet - INFO - Collecting bboxes from annotation... +[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 117266/117266, 15874.5 task/s, elapsed: 7s, ETA: 0s + +2021-07-19 19:37:28,753 - mmdet - INFO - Collected 849902 bboxes. +differential_evolution step 1: f(x)= 0.506055 +differential_evolution step 2: f(x)= 0.506055 +...... + +differential_evolution step 489: f(x)= 0.386625 +2021-07-19 19:46:40,775 - mmdet - INFO Anchor evolution finish. Average IOU: 0.6133754253387451 +2021-07-19 19:46:40,776 - mmdet - INFO Anchor differential evolution result:[[10, 12], [15, 30], [32, 22], [29, 59], [61, 46], [57, 116], [112, 89], [154, 198], [349, 336]] +2021-07-19 19:46:40,798 - mmdet - INFO Result saved in work_dirs/anchor_optimize_result.json +``` + +## Confusion Matrix + +A confusion matrix is a summary of prediction results. + +`tools/analysis_tools/confusion_matrix.py` can analyze the prediction results and plot a confusion matrix table. + +First, run `tools/test.py` to save the `.pkl` detection results. + +Then, run + +``` +python tools/analysis_tools/confusion_matrix.py ${CONFIG} ${DETECTION_RESULTS} ${SAVE_DIR} --show +``` + +And you will get a confusion matrix like this: + +![confusion_matrix_example](https://user-images.githubusercontent.com/12907710/140513068-994cdbf4-3a4a-48f0-8fd8-2830d93fd963.png) + +## COCO Separated & Occluded Mask Metric + +Detecting occluded objects still remains a challenge for state-of-the-art object detectors. +We implemented the metric presented in paper [A Tri-Layer Plugin to Improve Occluded Detection](https://arxiv.org/abs/2210.10046) to calculate the recall of separated and occluded masks. + +There are two ways to use this metric: + +### Offline evaluation + +We provide a script to calculate the metric with a dumped prediction file. + +First, use the `tools/test.py` script to dump the detection results: + +```shell +python tools/test.py ${CONFIG} ${MODEL_PATH} --out results.pkl +``` + +Then, run the `tools/analysis_tools/coco_occluded_separated_recall.py` script to get the recall of separated and occluded masks: + +```shell +python tools/analysis_tools/coco_occluded_separated_recall.py results.pkl --out occluded_separated_recall.json +``` + +The output should be like this: + +``` +loading annotations into memory... +Done (t=0.51s) +creating index... +index created! +processing detection results... +[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 5000/5000, 109.3 task/s, elapsed: 46s, ETA: 0s +computing occluded mask recall... +[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 5550/5550, 780.5 task/s, elapsed: 7s, ETA: 0s +COCO occluded mask recall: 58.79% +COCO occluded mask success num: 3263 +computing separated mask recall... +[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 3522/3522, 778.3 task/s, elapsed: 5s, ETA: 0s +COCO separated mask recall: 31.94% +COCO separated mask success num: 1125 + ++-----------+--------+-------------+ +| mask type | recall | num correct | ++-----------+--------+-------------+ +| occluded | 58.79% | 3263 | +| separated | 31.94% | 1125 | ++-----------+--------+-------------+ +Evaluation results have been saved to occluded_separated_recall.json. +``` + +### Online evaluation + +We implement `CocoOccludedSeparatedMetric` which inherits from the `CocoMetic`. +To evaluate the recall of separated and occluded masks during training, just replace the evaluator metric type with `'CocoOccludedSeparatedMetric'` in your config: + +```python +val_evaluator = dict( + type='CocoOccludedSeparatedMetric', # modify this + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False) +test_evaluator = val_evaluator +``` + +Please cite the paper if you use this metric: + +```latex +@article{zhan2022triocc, + title={A Tri-Layer Plugin to Improve Occluded Detection}, + author={Zhan, Guanqi and Xie, Weidi and Zisserman, Andrew}, + journal={British Machine Vision Conference}, + year={2022} +} +``` diff --git a/mmdetection/docs/en/user_guides/visualization.md b/mmdetection/docs/en/user_guides/visualization.md new file mode 100644 index 00000000..dade26ed --- /dev/null +++ b/mmdetection/docs/en/user_guides/visualization.md @@ -0,0 +1,91 @@ +# Visualization + +Before reading this tutorial, it is recommended to read MMEngine's [Visualization](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/visualization.md) documentation to get a first glimpse of the `Visualizer` definition and usage. + +In brief, the [`Visualizer`](mmengine.visualization.Visualizer) is implemented in MMEngine to meet the daily visualization needs, and contains three main functions: + +- Implement common drawing APIs, such as [`draw_bboxes`](mmengine.visualization.Visualizer.draw_bboxes) which implements bounding box drawing functions, [`draw_lines`](mmengine.visualization.Visualizer.draw_lines) implements the line drawing function. +- Support writing visualization results, learning rate curves, loss function curves, and verification accuracy curves to various backends, including local disks and common deep learning training logging tools such as [TensorBoard](https://www.tensorflow.org/tensorboard) and [Wandb](https://wandb.ai/site). +- Support calling anywhere in the code to visualize or record intermediate states of the model during training or testing, such as feature maps and validation results. + +Based on MMEngine's Visualizer, MMDet comes with a variety of pre-built visualization tools that can be used by the user by simply modifying the following configuration files. + +- The `tools/analysis_tools/browse_dataset.py` script provides a dataset visualization function that draws images and corresponding annotations after Data Transforms, as described in [`browse_dataset.py`](useful_tools.md#Visualization). +- MMEngine implements `LoggerHook`, which uses `Visualizer` to write the learning rate, loss and evaluation results to the backend set by `Visualizer`. Therefore, by modifying the `Visualizer` backend in the configuration file, for example to ` TensorBoardVISBackend` or `WandbVISBackend`, you can implement logging to common training logging tools such as `TensorBoard` or `WandB`, thus making it easy for users to use these visualization tools to analyze and monitor the training process. +- The `VisualizerHook` is implemented in MMDet, which uses the `Visualizer` to visualize or store the prediction results of the validation or prediction phase into the backend set by the `Visualizer`, so by modifying the `Visualizer` backend in the configuration file, for example, to ` TensorBoardVISBackend` or `WandbVISBackend`, you can implement storing the predicted images to `TensorBoard` or `Wandb`. + +## Configuration + +Thanks to the use of the registration mechanism, in MMDet we can set the behavior of the `Visualizer` by modifying the configuration file. Usually, we define the default configuration for the visualizer in `configs/_base_/default_runtime.py`, see [configuration tutorial](config.md) for details. + +```Python +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='DetLocalVisualizer', + vis_backends=vis_backends, + name='visualizer') +``` + +Based on the above example, we can see that the configuration of `Visualizer` consists of two main parts, namely, the type of `Visualizer` and the visualization backend `vis_backends` it uses. + +- Users can directly use `DetLocalVisualizer` to visualize labels or predictions for support tasks. +- MMDet sets the visualization backend `vis_backend` to the local visualization backend `LocalVisBackend` by default, saving all visualization results and other training information in a local folder. + +## Storage + +MMDet uses the local visualization backend [`LocalVisBackend`](mmengine.visualization.LocalVisBackend) by default, and the model loss, learning rate, model evaluation accuracy and visualization The information stored in `VisualizerHook` and `LoggerHook`, including loss, learning rate, evaluation accuracy will be saved to the `{work_dir}/{config_name}/{time}/{vis_data}` folder by default. In addition, MMDet also supports other common visualization backends, such as `TensorboardVisBackend` and `WandbVisBackend`, and you only need to change the `vis_backends` type in the configuration file to the corresponding visualization backend. For example, you can store data to `TensorBoard` and `Wandb` by simply inserting the following code block into the configuration file. + +```Python +# https://mmengine.readthedocs.io/en/latest/api/visualization.html +_base_.visualizer.vis_backends = [ + dict(type='LocalVisBackend'), # + dict(type='TensorboardVisBackend'), + dict(type='WandbVisBackend'),] +``` + +## Plot + +### Plot the prediction results + +MMDet mainly uses [`DetVisualizationHook`](mmdet.engine.hooks.DetVisualizationHook) to plot the prediction results of validation and test, by default `DetVisualizationHook` is off, and the default configuration is as follows. + +```Python +visualization=dict( # user visualization of validation and test results + type='DetVisualizationHook', + draw=False, + interval=1, + show=False) +``` + +The following table shows the parameters supported by `DetVisualizationHook`. + +| Parameters | Description | +| :--------: | :-----------------------------------------------------------------------------------------------------------: | +| draw | The DetVisualizationHook is turned on and off by the enable parameter, which is the default state. | +| interval | Controls how much iteration to store or display the results of a val or test if VisualizationHook is enabled. | +| show | Controls whether to visualize the results of val or test. | + +If you want to enable `DetVisualizationHook` related functions and configurations during training or testing, you only need to modify the configuration, take `configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py` as an example, draw annotations and predictions at the same time, and display the images, the configuration can be modified as follows + +```Python +visualization = _base_.default_hooks.visualization +visualization.update(dict(draw=True, show=True)) +``` + +
    + +
    + +The `test.py` procedure is further simplified by providing the `--show` and `--show-dir` parameters to visualize the annotation and prediction results during the test without modifying the configuration. + +```Shell +# Show test results +python tools/test.py configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_tiny_8xb32-300e_coco/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth --show + +# Specify where to store the prediction results +python tools/test.py configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_tiny_8xb32-300e_coco/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth --show-dir imgs/ +``` + +
    + +
    diff --git a/mmdetection/docs/zh_cn/Makefile b/mmdetection/docs/zh_cn/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/mmdetection/docs/zh_cn/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/mmdetection/docs/zh_cn/_static/css/readthedocs.css b/mmdetection/docs/zh_cn/_static/css/readthedocs.css new file mode 100644 index 00000000..57ed0ad0 --- /dev/null +++ b/mmdetection/docs/zh_cn/_static/css/readthedocs.css @@ -0,0 +1,6 @@ +.header-logo { + background-image: url("../image/mmdet-logo.png"); + background-size: 156px 40px; + height: 40px; + width: 156px; +} diff --git a/mmdetection/docs/zh_cn/_static/image/mmdet-logo.png b/mmdetection/docs/zh_cn/_static/image/mmdet-logo.png new file mode 100644 index 00000000..58e2b5e6 Binary files /dev/null and b/mmdetection/docs/zh_cn/_static/image/mmdet-logo.png differ diff --git a/mmdetection/docs/zh_cn/advanced_guides/conventions.md b/mmdetection/docs/zh_cn/advanced_guides/conventions.md new file mode 100644 index 00000000..9fb1f14c --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/conventions.md @@ -0,0 +1,109 @@ +# 默认约定 + +如果你想把 MMDetection 修改为自己的项目,请遵循下面的约定。 + +## 关于图片 shape 顺序的说明 + +在OpenMMLab 2.0中, 为了与 OpenCV 的输入参数相一致,图片处理 pipeline 中关于图像 shape 的输入参数总是以 `(width, height)` 的顺序排列。 +相反,为了计算方便,经过 pipeline 和 model 的字段的顺序是 `(height, width)`。具体来说在每个数据 pipeline 处理的结果中,字段和它们的值含义如下: + +- img_shape: (height, width) +- ori_shape: (height, width) +- pad_shape: (height, width) +- batch_input_shape: (height, width) + +以 `Mosaic` 为例,其初始化参数如下所示: + +```python +@TRANSFORMS.register_module() +class Mosaic(BaseTransform): + def __init__(self, + img_scale: Tuple[int, int] = (640, 640), + center_ratio_range: Tuple[float, float] = (0.5, 1.5), + bbox_clip_border: bool = True, + pad_val: float = 114.0, + prob: float = 1.0) -> None: + ... + + # img_scale 顺序应该是 (width, height) + self.img_scale = img_scale + + def transform(self, results: dict) -> dict: + ... + + results['img'] = mosaic_img + # (height, width) + results['img_shape'] = mosaic_img.shape[:2] +``` + +## 损失 + +在 MMDetection 中,`model(**data)` 的返回值是一个字典,包含着所有的损失和评价指标,他们将会由 `model(**data)` 返回。 + +例如,在 bbox head 中, + +```python +class BBoxHead(nn.Module): + ... + def loss(self, ...): + losses = dict() + # 分类损失 + losses['loss_cls'] = self.loss_cls(...) + # 分类准确率 + losses['acc'] = accuracy(...) + # 边界框损失 + losses['loss_bbox'] = self.loss_bbox(...) + return losses +``` + +`'bbox_head.loss()'` 在模型 forward 阶段会被调用。返回的字典中包含了 `'loss_bbox'`,`'loss_cls'`,`'acc'`。只有 `'loss_bbox'`, `'loss_cls'` 会被用于反向传播,`'acc'` 只会被作为评价指标来监控训练过程。 + +我们默认,只有那些键的名称中包含 `'loss'` 的值会被用于反向传播。这个行为可以通过修改 `BaseDetector.train_step()` 来改变。 + +## 空 proposals + +在 MMDetection 中,我们为两阶段方法中空 proposals 的情况增加了特殊处理和单元测试。我们同时需要处理整个 batch 和单一图片中空 proposals 的情况。例如,在 CascadeRoIHead 中, + +```python +# 简单的测试 +... + +# 在整个 batch中 都没有 proposals +if rois.shape[0] == 0: + bbox_results = [[ + np.zeros((0, 5), dtype=np.float32) + for _ in range(self.bbox_head[-1].num_classes) + ]] * num_imgs + if self.with_mask: + mask_classes = self.mask_head[-1].num_classes + segm_results = [[[] for _ in range(mask_classes)] + for _ in range(num_imgs)] + results = list(zip(bbox_results, segm_results)) + else: + results = bbox_results + return results +... + +# 在单张图片中没有 proposals +for i in range(self.num_stages): + ... + if i < self.num_stages - 1: + for j in range(num_imgs): + # 处理空 proposals + if rois[j].shape[0] > 0: + bbox_label = cls_score[j][:, :-1].argmax(dim=1) + refine_roi = self.bbox_head[i].regress_by_class( + rois[j], bbox_label[j], bbox_pred[j], img_metas[j]) + refine_roi_list.append(refine_roi) +``` + +如果你有自定义的 `RoIHead`, 你可以参考上面的方法来处理空 proposals 的情况。 + +## 全景分割数据集 + +在 MMDetection 中,我们支持了 COCO 全景分割数据集 `CocoPanopticDataset`。对于它的实现,我们在这里声明一些默认约定。 + +1. 在 mmdet\<=2.16.0 时,语义分割标注中的前景和背景标签范围与 MMDetection 中的默认规定有所不同。标签 `0` 代表 `VOID` 标签。 + 从 mmdet=2.17.0 开始,为了和框的类别标注保持一致,语义分割标注的类别标签也改为从 `0` 开始,标签 `255` 代表 `VOID` 类。 + 为了达成这一目标,我们在流程 `Pad` 里支持了设置 `seg` 的填充值的功能。 +2. 在评估中,全景分割结果必须是一个与原图大小相同的图。结果图中每个像素的值有如此形式:`instance_id * INSTANCE_OFFSET + category_id`。 diff --git a/mmdetection/docs/zh_cn/advanced_guides/customize_dataset.md b/mmdetection/docs/zh_cn/advanced_guides/customize_dataset.md new file mode 100644 index 00000000..e845f37f --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/customize_dataset.md @@ -0,0 +1,425 @@ +# 自定义数据集 + +## 支持新的数据格式 + +为了支持新的数据格式,可以选择将数据转换成现成的格式(COCO 或者 PASCAL)或将其转换成中间格式。当然也可以选择以离线的形式(在训练之前使用脚本转换)或者在线的形式(实现一个新的 dataset 在训练中进行转换)来转换数据。 + +在 MMDetection 中,建议将数据转换成 COCO 格式并以离线的方式进行,因此在完成数据转换后只需修改配置文件中的标注数据的路径和类别即可。 + +### 将新的数据格式转换为现有的数据格式 + +最简单的方法就是将你的数据集转换成现有的数据格式(COCO 或者 PASCAL VOC) + +COCO 格式的 JSON 标注文件有如下必要的字段: + +```python +'images': [ + { + 'file_name': 'COCO_val2014_000000001268.jpg', + 'height': 427, + 'width': 640, + 'id': 1268 + }, + ... +], + +'annotations': [ + { + 'segmentation': [[192.81, + 247.09, + ... + 219.03, + 249.06]], # 如果有 mask 标签且为多边形 XY 点坐标格式,则需要保证至少包括 3 个点坐标,否则为无效多边形 + 'area': 1035.749, + 'iscrowd': 0, + 'image_id': 1268, + 'bbox': [192.81, 224.8, 74.73, 33.43], + 'category_id': 16, + 'id': 42986 + }, + ... +], + +'categories': [ + {'id': 0, 'name': 'car'}, + ] +``` + +在 JSON 文件中有三个必要的键: + +- `images`: 包含多个图片以及它们的信息的数组,例如 `file_name`、`height`、`width` 和 `id`。 +- `annotations`: 包含多个实例标注信息的数组。 +- `categories`: 包含多个类别名字和 ID 的数组。 + +在数据预处理之后,使用现有的数据格式来训练自定义的新数据集有如下两步(以 COCO 为例): + +1. 为自定义数据集修改配置文件。 +2. 检查自定义数据集的标注。 + +这里我们举一个例子来展示上面的两个步骤,这个例子使用包括 5 个类别的 COCO 格式的数据集来训练一个现有的 Cascade Mask R-CNN R50-FPN 检测器 + +#### 1. 为自定义数据集修改配置文件 + +配置文件的修改涉及两个方面: + +1. `dataloaer` 部分。需要在 `train_dataloader.dataset`、`val_dataloader.dataset` 和 `test_dataloader.dataset` 中添加 `metainfo=dict(classes=classes)`, 其中 classes 必须是 tuple 类型。 +2. `model` 部分中的 `num_classes`。需要将默认值(COCO 数据集中为 80)修改为自定义数据集中的类别数。 + +`configs/my_custom_config.py` 内容如下: + +```python + +# 新的配置来自基础的配置以更好地说明需要修改的地方 +_base_ = './cascade_mask_rcnn_r50_fpn_1x_coco.py' + +# 1. 数据集设定 +dataset_type = 'CocoDataset' +classes = ('a', 'b', 'c', 'd', 'e') +data_root='path/to/your/' + +train_dataloader = dict( + batch_size=2, + num_workers=2, + dataset=dict( + type=dataset_type, + # 将类别名字添加至 `metainfo` 字段中 + metainfo=dict(classes=classes), + data_root=data_root, + ann_file='train/annotation_data', + data_prefix=dict(img='train/image_data') + ) + ) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + dataset=dict( + type=dataset_type, + test_mode=True, + # 将类别名字添加至 `metainfo` 字段中 + metainfo=dict(classes=classes), + data_root=data_root, + ann_file='val/annotation_data', + data_prefix=dict(img='val/image_data') + ) + +test_dataloader = dict( + batch_size=1, + num_workers=2, + dataset=dict( + type=dataset_type, + test_mode=True, + # 将类别名字添加至 `metainfo` 字段中 + metainfo=dict(classes=classes), + data_root=data_root, + ann_file='test/annotation_data', + data_prefix=dict(img='test/image_data') + ) + ) + +# 2. 模型设置 + +# 将所有的 `num_classes` 默认值修改为 5(原来为80) +model = dict( + roi_head=dict( + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + # 将所有的 `num_classes` 默认值修改为 5(原来为 80) + num_classes=5), + dict( + type='Shared2FCBBoxHead', + # 将所有的 `num_classes` 默认值修改为 5(原来为 80) + num_classes=5), + dict( + type='Shared2FCBBoxHead', + # 将所有的 `num_classes` 默认值修改为 5(原来为 80) + num_classes=5)], + # 将所有的 `num_classes` 默认值修改为 5(原来为 80) + mask_head=dict(num_classes=5))) +``` + +#### 2. 检查自定义数据集的标注 + +假设你自己的数据集是 COCO 格式,那么需要保证数据的标注没有问题: + +1. 标注文件中 `categories` 的长度要与配置中的 `classes` 元组长度相匹配,它们都表示有几类。(如例子中有 5 个类别) +2. 配置文件中 `classes` 字段应与标注文件里 `categories` 下的 `name` 有相同的元素且顺序一致。MMDetection 会自动将 `categories` 中不连续的 `id` 映射成连续的索引,因此 `categories` 下的 `name`的字符串顺序会影响标签的索引。同时,配置文件中的 `classes` 的字符串顺序也会影响到预测框可视化时的标签。 +3. `annotations` 中的 `category_id` 必须是有效的值。比如所有 `category_id` 的值都应该属于 `categories` 中的 `id`。 + +下面是一个有效标注的例子: + +```python + +'annotations': [ + { + 'segmentation': [[192.81, + 247.09, + ... + 219.03, + 249.06]], # 如果有 mask 标签。 + 'area': 1035.749, + 'iscrowd': 0, + 'image_id': 1268, + 'bbox': [192.81, 224.8, 74.73, 33.43], + 'category_id': 16, + 'id': 42986 + }, + ... +], + +# MMDetection 会自动将 `categories` 中不连续的 `id` 映射成连续的索引。 +'categories': [ + {'id': 1, 'name': 'a'}, {'id': 3, 'name': 'b'}, {'id': 4, 'name': 'c'}, {'id': 16, 'name': 'd'}, {'id': 17, 'name': 'e'}, + ] +``` + +我们使用这种方式来支持 CityScapes 数据集。脚本在 [cityscapes.py](https://github.com/open-mmlab/mmdetection/blob/main/tools/dataset_converters/cityscapes.py) 并且我们提供了微调的 [configs](https://github.com/open-mmlab/mmdetection/blob/main/configs/cityscapes). + +**注意** + +1. 对于实例分割数据集, **MMDetection 目前只支持评估 COCO 格式的 mask AP**. +2. 推荐训练之前进行离线转换,这样就可以继续使用 `CocoDataset` 且只需修改标注文件的路径以及训练的种类。 + +### 调整新的数据格式为中间格式 + +如果不想将标注格式转换为 COCO 或者 PASCAL 格式也是可行的。实际上,我们在 MMEngine 的 [BaseDataset](https://github.com/open-mmlab/mmengine/blob/main/mmengine/dataset/base_dataset.py#L116) 中定义了一种简单的标注格式并且与所有现有的数据格式兼容,也能进行离线或者在线转换。 + +数据集的标注必须为 `json` 或 `yaml`,`yml` 或 `pickle`,`pkl` 格式;标注文件中存储的字典必须包含 `metainfo` 和 `data_list` 两个字段。其中 `metainfo` 是一个字典,里面包含数据集的元信息,例如类别信息;`data_list` 是一个列表,列表中每个元素是一个字典,该字典定义了一个原始数据(raw data),每个原始数据包含一个或若干个训练/测试样本。 + +以下是一个 JSON 标注文件的例子: + +```json +{ + 'metainfo': + { + 'classes': ('person', 'bicycle', 'car', 'motorcycle'), + ... + }, + 'data_list': + [ + { + "img_path": "xxx/xxx_1.jpg", + "height": 604, + "width": 640, + "instances": + [ + { + "bbox": [0, 0, 10, 20], + "bbox_label": 1, + "ignore_flag": 0 + }, + { + "bbox": [10, 10, 110, 120], + "bbox_label": 2, + "ignore_flag": 0 + } + ] + }, + { + "img_path": "xxx/xxx_2.jpg", + "height": 320, + "width": 460, + "instances": + [ + { + "bbox": [10, 0, 20, 20], + "bbox_label": 3, + "ignore_flag": 1 + } + ] + }, + ... + ] +} +``` + +有些数据集可能会提供如:crowd/difficult/ignored bboxes 标注,那么我们使用 `ignore_flag`来包含它们。 + +在得到上述标准的数据标注格式后,可以直接在配置中使用 MMDetection 的 [BaseDetDataset](https://github.com/open-mmlab/mmdetection/blob/main/mmdet/datasets/base_det_dataset.py#L13) ,而无需进行转换。 + +### 自定义数据集例子 + +假设文本文件中表示的是一种全新的标注格式。边界框的标注信息保存在 `annotation.txt` 中,内容如下: + +``` +# +000001.jpg +1280 720 +2 +10 20 40 60 1 +20 40 50 60 2 +# +000002.jpg +1280 720 +3 +50 20 40 60 2 +20 40 30 45 2 +30 40 50 60 3 +``` + +我们可以在 `mmdet/datasets/my_dataset.py` 中创建一个新的 dataset 用以加载数据。 + +```python +import mmengine +from mmdet.base_det_dataset import BaseDetDataset +from mmdet.registry import DATASETS + + +@DATASETS.register_module() +class MyDataset(BaseDetDataset): + + METAINFO = { + 'classes': ('person', 'bicycle', 'car', 'motorcycle'), + 'palette': [(220, 20, 60), (119, 11, 32), (0, 0, 142), (0, 0, 230)] + } + + def load_data_list(self, ann_file): + ann_list = mmengine.list_from_file(ann_file) + + data_infos = [] + for i, ann_line in enumerate(ann_list): + if ann_line != '#': + continue + + img_shape = ann_list[i + 2].split(' ') + width = int(img_shape[0]) + height = int(img_shape[1]) + bbox_number = int(ann_list[i + 3]) + + instances = [] + for anns in ann_list[i + 4:i + 4 + bbox_number]: + instance = {} + instance['bbox'] = [float(ann) for ann in anns.split(' ')[:4]] + instance['bbox_label']=int(anns[4]) + instances.append(instance) + + data_infos.append( + dict( + img_path=ann_list[i + 1], + img_id=i, + width=width, + height=height, + instances=instances + )) + + return data_infos +``` + +配置文件中,可以使用 `MyDataset` 进行如下修改 + +```python +dataset_A_train = dict( + type='MyDataset', + ann_file = 'image_list.txt', + pipeline=train_pipeline +) +``` + +## 使用 dataset 包装器自定义数据集 + +MMEngine 也支持非常多的数据集包装器(wrapper)来混合数据集或在训练时修改数据集的分布,其支持如下三种数据集包装: + +- `RepeatDataset`:将整个数据集简单地重复。 +- `ClassBalancedDataset`:以类别均衡的方式重复数据集。 +- `ConcatDataset`:合并数据集。 + +具体使用方式见 [MMEngine 数据集包装器](#TODO)。 + +## 修改数据集的类别 + +根据现有数据集的类型,我们可以修改它们的类别名称来训练其标注的子集。 +例如,如果只想训练当前数据集中的三个类别,那么就可以修改数据集的 `metainfo` 字典,数据集就会自动屏蔽掉其他类别的真实框。 + +```python +classes = ('person', 'bicycle', 'car') +train_dataloader = dict( + dataset=dict( + metainfo=dict(classes=classes)) + ) +val_dataloader = dict( + dataset=dict( + metainfo=dict(classes=classes)) + ) +test_dataloader = dict( + dataset=dict( + metainfo=dict(classes=classes)) + ) +``` + +**注意** + +- 在 MMDetection v2.5.0 之前,如果类别为集合时数据集将自动过滤掉不包含 GT 的图片,且没办法通过修改配置将其关闭。这是一种不可取的行为而且会引起混淆,因为当类别不是集合时数据集时,只有在 `filter_empty_gt=True` 以及 `test_mode=False` 的情况下才会过滤掉不包含 GT 的图片。在 MMDetection v2.5.0 之后,我们将图片的过滤以及类别的修改进行解耦,数据集只有在 `filter_cfg=dict(filter_empty_gt=True)` 和 `test_mode=False` 的情况下才会过滤掉不包含 GT 的图片,无论类别是否为集合。设置类别只会影响用于训练的标注类别,用户可以自行决定是否过滤不包含 GT 的图片。 +- 直接使用 MMEngine 中的 `BaseDataset` 或者 MMDetection 中的 `BaseDetDataset` 时用户不能通过修改配置来过滤不含 GT 的图片,但是可以通过离线的方式来解决。 +- 当设置数据集中的 `classes` 时,记得修改 `num_classes`。从 v2.9.0 (PR#4508) 之后,我们实现了 [NumClassCheckHook](https://github.com/open-mmlab/mmdetection/blob/main/mmdet/engine/hooks/num_class_check_hook.py) 来检查类别数是否一致。 + +## COCO 全景分割数据集 + +现在我们也支持 COCO Panoptic Dataset,全景注释的格式与 COCO 格式不同,其前景和背景都将存在于注释文件中。COCO Panoptic 格式的注释 JSON 文件具有以下必要的键: + +```python +'images': [ + { + 'file_name': '000000001268.jpg', + 'height': 427, + 'width': 640, + 'id': 1268 + }, + ... +] + +'annotations': [ + { + 'filename': '000000001268.jpg', + 'image_id': 1268, + 'segments_info': [ + { + 'id':8345037, # One-to-one correspondence with the id in the annotation map. + 'category_id': 51, + 'iscrowd': 0, + 'bbox': (x1, y1, w, h), # The bbox of the background is the outer rectangle of its mask. + 'area': 24315 + }, + ... + ] + }, + ... +] + +'categories': [ # including both foreground categories and background categories + {'id': 0, 'name': 'person'}, + ... + ] +``` + +此外,`seg` 必须设置为全景注释图像的路径。 + +```python +dataset_type = 'CocoPanopticDataset' +data_root='path/to/your/' + +train_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img='train/image_data/', seg='train/panoptic/image_annotation_data/') + ) +) +val_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img='val/image_data/', seg='val/panoptic/image_annotation_data/') + ) +) +test_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img='test/image_data/', seg='test/panoptic/image_annotation_data/') + ) +) +``` diff --git a/mmdetection/docs/zh_cn/advanced_guides/customize_losses.md b/mmdetection/docs/zh_cn/advanced_guides/customize_losses.md new file mode 100644 index 00000000..07ccccda --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/customize_losses.md @@ -0,0 +1,125 @@ +# 自定义损失函数 + +MMDetection 为用户提供了不同的损失函数。但是默认的配置可能无法适应不同的数据和模型,所以用户可能会希望修改某一个损失函数来适应新的情况。 + +本教程首先详细的解释计算损失的过程然后给出一些关于如何修改每一个步骤的指导。对损失的修改可以被分为微调和加权。 + +## 一个损失的计算过程 + +给定输入(包括预测和目标,以及权重),损失函数会把输入的张量映射到最后的损失标量。映射过程可以分为下面五个步骤: + +1. 设置采样方法为对正负样本进行采样。 + +2. 通过损失核函数获取**元素**或者**样本**损失。 + +3. 通过权重张量来给损失**逐元素**权重。 + +4. 把损失张量归纳为一个**标量**。 + +5. 用一个**张量**给当前损失一个权重。 + +## 设置采样方法(步骤 1) + +对于一些损失函数,需要采样策略来避免正负样本之间的不平衡。 + +例如,在RPN head中使用`CrossEntropyLoss`时,我们需要在`train_cfg`中设置`RandomSampler` + +```python +train_cfg=dict( + rpn=dict( + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False)) +``` + +对于其他一些具有正负样本平衡机制的损失,例如 Focal Loss、GHMC 和 QualityFocalLoss,不再需要进行采样。 + +## 微调损失 + +微调一个损失主要与步骤 2,4,5 有关,大部分的修改可以在配置文件中指定。这里我们用 [Focal Loss (FL)](https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/losses/focal_loss.py) 作为例子。 +下面的代码分别是构建 FL 的方法和它的配置文件,他们是一一对应的。 + +```python +@LOSSES.register_module() +class FocalLoss(nn.Module): + + def __init__(self, + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + reduction='mean', + loss_weight=1.0): +``` + +```python +loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0) +``` + +### 微调超参数(步骤2) + +`gamma` 和 `beta` 是 Focal Loss 中的两个超参数。如果我们想把 `gamma` 的值设为 1.5,把 `alpha` 的值设为 0.5,我们可以在配置文件中按照如下指定: + +```python +loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=1.5, + alpha=0.5, + loss_weight=1.0) +``` + +### 微调归纳方式(步骤4) + +Focal Loss 默认的归纳方式是 `mean`。如果我们想把归纳方式从 `mean` 改成 `sum`,我们可以在配置文件中按照如下指定: + +```python +loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0, + reduction='sum') +``` + +### 微调损失权重(步骤5) + +这里的损失权重是一个标量,他用来控制多任务学习中不同损失的重要程度,例如,分类损失和回归损失。如果我们想把分类损失的权重设为 0.5,我们可以在配置文件中如下指定: + +```python +loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=0.5) +``` + +## 加权损失(步骤3) + +加权损失就是我们逐元素修改损失权重。更具体来说,我们给损失张量乘以一个与他有相同形状的权重张量。所以,损失中不同的元素可以被赋予不同的比例,所以这里叫做逐元素。损失的权重在不同模型中变化很大,而且与上下文相关,但是总的来说主要有两种损失权重:分类损失的 `label_weights` 和边界框的 `bbox_weights`。你可以在相应的头中的 `get_target` 方法中找到他们。这里我们使用 [ATSSHead](https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/dense_heads/atss_head.py#L322) 作为一个例子。它继承了 [AnchorHead](https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/dense_heads/anchor_head.py) ,但是我们重写它的 +`get_targets` 方法来产生不同的 `label_weights` 和 `bbox_weights`。 + +``` +class ATSSHead(AnchorHead): + + ... + + def get_targets(self, + anchor_list, + valid_flag_list, + gt_bboxes_list, + img_metas, + gt_bboxes_ignore_list=None, + gt_labels_list=None, + label_channels=1, + unmap_outputs=True): +``` diff --git a/mmdetection/docs/zh_cn/advanced_guides/customize_models.md b/mmdetection/docs/zh_cn/advanced_guides/customize_models.md new file mode 100644 index 00000000..5fa77e41 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/customize_models.md @@ -0,0 +1,412 @@ +# 自定义模型 + +我们简单地把模型的各个组件分为五类: + +- 主干网络 (backbone):通常是一个用来提取特征图 (feature map) 的全卷积网络 (FCN network),例如:ResNet, MobileNet。 +- Neck:主干网络和 Head 之间的连接部分,例如:FPN, PAFPN。 +- Head:用于具体任务的组件,例如:边界框预测和掩码预测。 +- 区域提取器 (roi extractor):从特征图中提取 RoI 特征,例如:RoI Align。 +- 损失 (loss):在 Head 组件中用于计算损失的部分,例如:FocalLoss, L1Loss, GHMLoss. + +## 开发新的组件 + +### 添加一个新的主干网络 + +这里,我们以 MobileNet 为例来展示如何开发新组件。 + +#### 1. 定义一个新的主干网络(以 MobileNet 为例) + +新建一个文件 `mmdet/models/backbones/mobilenet.py` + +```python +import torch.nn as nn + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class MobileNet(nn.Module): + + def __init__(self, arg1, arg2): + pass + + def forward(self, x): # should return a tuple + pass +``` + +#### 2. 导入该模块 + +你可以添加下述代码到 `mmdet/models/backbones/__init__.py` + +```python +from .mobilenet import MobileNet +``` + +或添加: + +```python +custom_imports = dict( + imports=['mmdet.models.backbones.mobilenet'], + allow_failed_imports=False) +``` + +到配置文件以避免原始代码被修改。 + +#### 3. 在你的配置文件中使用该主干网络 + +```python +model = dict( + ... + backbone=dict( + type='MobileNet', + arg1=xxx, + arg2=xxx), + ... +``` + +### 添加新的 Neck + +#### 1. 定义一个 Neck(以 PAFPN 为例) + +新建一个文件 `mmdet/models/necks/pafpn.py` + +```python +import torch.nn as nn + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class PAFPN(nn.Module): + + def __init__(self, + in_channels, + out_channels, + num_outs, + start_level=0, + end_level=-1, + add_extra_convs=False): + pass + + def forward(self, inputs): + # implementation is ignored + pass +``` + +#### 2. 导入该模块 + +你可以添加下述代码到 `mmdet/models/necks/__init__.py` + +```python +from .pafpn import PAFPN +``` + +或添加: + +```python +custom_imports = dict( + imports=['mmdet.models.necks.pafpn'], + allow_failed_imports=False) +``` + +到配置文件以避免原始代码被修改。 + +#### 3. 修改配置文件 + +```python +neck=dict( + type='PAFPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5) +``` + +### 添加新的 Head + +我们以 [Double Head R-CNN](https://arxiv.org/abs/1904.06493) 为例来展示如何添加一个新的 Head。 + +首先,添加一个新的 bbox head 到 `mmdet/models/roi_heads/bbox_heads/double_bbox_head.py`。 +Double Head R-CNN 在目标检测上实现了一个新的 bbox head。为了实现 bbox head,我们需要使用如下的新模块中三个函数。 + +```python +from typing import Tuple + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule, ModuleList +from torch import Tensor + +from mmdet.models.backbones.resnet import Bottleneck +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, MultiConfig, OptConfigType, OptMultiConfig +from .bbox_head import BBoxHead + + +@MODELS.register_module() +class DoubleConvFCBBoxHead(BBoxHead): + r"""Bbox head used in Double-Head R-CNN + + .. code-block:: none + + /-> cls + /-> shared convs -> + \-> reg + roi features + /-> cls + \-> shared fc -> + \-> reg + """ # noqa: W605 + + def __init__(self, + num_convs: int = 0, + num_fcs: int = 0, + conv_out_channels: int = 1024, + fc_out_channels: int = 1024, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN'), + init_cfg: MultiConfig = dict( + type='Normal', + override=[ + dict(type='Normal', name='fc_cls', std=0.01), + dict(type='Normal', name='fc_reg', std=0.001), + dict( + type='Xavier', + name='fc_branch', + distribution='uniform') + ]), + **kwargs) -> None: + kwargs.setdefault('with_avg_pool', True) + super().__init__(init_cfg=init_cfg, **kwargs) + + def forward(self, x_cls: Tensor, x_reg: Tensor) -> Tuple[Tensor]: + +``` + +然后,如有必要,实现一个新的 bbox head。我们打算从 `StandardRoIHead` 来继承新的 `DoubleHeadRoIHead`。我们可以发现 `StandardRoIHead` 已经实现了下述函数。 + +```python +from typing import List, Optional, Tuple + +import torch +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList +from ..task_modules.samplers import SamplingResult +from ..utils import empty_instances, unpack_gt_instances +from .base_roi_head import BaseRoIHead + + +@MODELS.register_module() +class StandardRoIHead(BaseRoIHead): + """Simplest base roi head including one bbox head and one mask head.""" + + def init_assigner_sampler(self) -> None: + + def init_bbox_head(self, bbox_roi_extractor: ConfigType, + bbox_head: ConfigType) -> None: + + def init_mask_head(self, mask_roi_extractor: ConfigType, + mask_head: ConfigType) -> None: + + def forward(self, x: Tuple[Tensor], + rpn_results_list: InstanceList) -> tuple: + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: List[DetDataSample]) -> dict: + + def _bbox_forward(self, x: Tuple[Tensor], rois: Tensor) -> dict: + + def bbox_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult]) -> dict: + + def mask_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult], bbox_feats: Tensor, + batch_gt_instances: InstanceList) -> dict: + + def _mask_forward(self, + x: Tuple[Tensor], + rois: Tensor = None, + pos_inds: Optional[Tensor] = None, + bbox_feats: Optional[Tensor] = None) -> dict: + + def predict_bbox(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + rpn_results_list: InstanceList, + rcnn_test_cfg: ConfigType, + rescale: bool = False) -> InstanceList: + + def predict_mask(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + results_list: InstanceList, + rescale: bool = False) -> InstanceList: + +``` + +Double Head 的修改主要在 bbox_forward 的逻辑中,且它从 `StandardRoIHead` 中继承了其他逻辑。在 `mmdet/models/roi_heads/double_roi_head.py` 中,我们用下述代码实现新的 bbox head: + +```python +from typing import Tuple + +from torch import Tensor + +from mmdet.registry import MODELS +from .standard_roi_head import StandardRoIHead + + +@MODELS.register_module() +class DoubleHeadRoIHead(StandardRoIHead): + """RoI head for `Double Head RCNN `_. + + Args: + reg_roi_scale_factor (float): The scale factor to extend the rois + used to extract the regression features. + """ + + def __init__(self, reg_roi_scale_factor: float, **kwargs): + super().__init__(**kwargs) + self.reg_roi_scale_factor = reg_roi_scale_factor + + def _bbox_forward(self, x: Tuple[Tensor], rois: Tensor) -> dict: + """Box head forward function used in both training and testing. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + """ + bbox_cls_feats = self.bbox_roi_extractor( + x[:self.bbox_roi_extractor.num_inputs], rois) + bbox_reg_feats = self.bbox_roi_extractor( + x[:self.bbox_roi_extractor.num_inputs], + rois, + roi_scale_factor=self.reg_roi_scale_factor) + if self.with_shared_head: + bbox_cls_feats = self.shared_head(bbox_cls_feats) + bbox_reg_feats = self.shared_head(bbox_reg_feats) + cls_score, bbox_pred = self.bbox_head(bbox_cls_feats, bbox_reg_feats) + + bbox_results = dict( + cls_score=cls_score, + bbox_pred=bbox_pred, + bbox_feats=bbox_cls_feats) + return bbox_results +``` + +最终,用户需要把该模块添加到 `mmdet/models/bbox_heads/__init__.py` 和 `mmdet/models/roi_heads/__init__.py` 以使相关的注册表可以找到并加载他们。 + +或者,用户可以添加: + +```python +custom_imports=dict( + imports=['mmdet.models.roi_heads.double_roi_head', 'mmdet.models.roi_heads.bbox_heads.double_bbox_head']) +``` + +到配置文件并实现相同的目的。 + +Double Head R-CNN 的配置文件如下: + +```python +_base_ = '../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model = dict( + roi_head=dict( + type='DoubleHeadRoIHead', + reg_roi_scale_factor=1.3, + bbox_head=dict( + _delete_=True, + type='DoubleConvFCBBoxHead', + num_convs=4, + num_fcs=2, + in_channels=256, + conv_out_channels=1024, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=2.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=2.0)))) + +``` + +从 MMDetection 2.0 版本起,配置系统支持继承配置以使用户可以专注于修改。 +Double Head R-CNN 主要使用了一个新的 `DoubleHeadRoIHead` 和一个新的 `DoubleConvFCBBoxHead`,参数需要根据每个模块的 `__init__` 函数来设置。 + +### 添加新的损失 + +假设你想添加一个新的损失 `MyLoss` 用于边界框回归。 +为了添加一个新的损失函数,用户需要在 `mmdet/models/losses/my_loss.py` 中实现。 +装饰器 `weighted_loss` 可以使损失每个部分加权。 + +```python +import torch +import torch.nn as nn + +from mmdet.registry import LOSSES +from .utils import weighted_loss + + +@weighted_loss +def my_loss(pred, target): + assert pred.size() == target.size() and target.numel() > 0 + loss = torch.abs(pred - target) + return loss + +@LOSSES.register_module() +class MyLoss(nn.Module): + + def __init__(self, reduction='mean', loss_weight=1.0): + super(MyLoss, self).__init__() + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None): + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss_bbox = self.loss_weight * my_loss( + pred, target, weight, reduction=reduction, avg_factor=avg_factor) + return loss_bbox +``` + +然后,用户需要把它加到 `mmdet/models/losses/__init__.py`。 + +```python +from .my_loss import MyLoss, my_loss +``` + +或者,你可以添加: + +```python +custom_imports=dict( + imports=['mmdet.models.losses.my_loss']) +``` + +到配置文件来实现相同的目的。 + +如使用,请修改 `loss_xxx` 字段。 +因为 MyLoss 是用于回归的,你需要在 Head 中修改 `loss_xxx` 字段。 + +```python +loss_bbox=dict(type='MyLoss', loss_weight=1.0)) +``` diff --git a/mmdetection/docs/zh_cn/advanced_guides/customize_runtime.md b/mmdetection/docs/zh_cn/advanced_guides/customize_runtime.md new file mode 100644 index 00000000..d4a19098 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/customize_runtime.md @@ -0,0 +1,387 @@ +# 自定义训练配置 + +## 自定义优化相关的配置 + +优化相关的配置现在已全部集成到 `optim_wrapper` 中,通常包含三个域:`optimizer`, `paramwise_cfg`,`clip_grad`,具体细节见 [OptimWrapper](https://mmengine.readthedocs.io/en/latest/tutorials/optim_wrapper.md)。下面这个例子中,使用了 `AdamW` 作为优化器,主干部分的学习率缩小到原来的十分之一,以及添加了梯度裁剪。 + +```python +optim_wrapper = dict( + type='OptimWrapper', + # 优化器 + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.05, + eps=1e-8, + betas=(0.9, 0.999)), + + # 参数层面的学习率和正则化设置 + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + }, + norm_decay_mult=0.0), + + # 梯度裁剪 + clip_grad=dict(max_norm=0.01, norm_type=2)) +``` + +### 自定义 Pytorch 中优化器设置 + +我们已经支持了 Pytorch 中实现的所有优化器,要使用这些优化器唯一要做就是修改配置文件中的 `optimi_wrapper` 中的 `optimzer` 域。比如,如果想要使用 `ADAM` 作为优化器(可能会导致性能下降),所需要做的修改如下。 + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='Adam', lr=0.0003, weight_decay=0.0001)) +``` + +要修改模型的学习率,用户只需要修改 `optimizer` 中的 `lr` 域。用户可以直接参考 PyToch 的 [API doc](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) 来进行参数的设置。 + +### 自定义优化器 + +#### 1. 定义一个新优化器 + +自定义优化器可以定义的方式如下: + +假设你想要添加一个名为 `MyOptimizer` 的优化器,它包含三个参数 `a`,`b`,`c`。你需要新建一个名为 +`mmdet/engine/optimizers` 的文件夹。然后在文件(比如,`mmdet/engine/optimizers/my_optimizer.py`)实现一个新的优化器。 + +```python +from mmdet.registry import OPTIMIZERS +from torch.optim import Optimizer + + +@OPTIMIZERS.register_module() +class MyOptimizer(Optimizer): + + def __init__(self, a, b, c) + +``` + +#### 2. 导入自定义的优化器 + +为了能找到上面的所定义的模块,这个模块必须要先导入到主命名空间中。有两种方式可以实现这一点。 + +- 修改 `mmdet/engine/optimizers/__init__.py` 来导入模块。 + + 新定义的模块必须导入到 `mmdet/engine/optimizers/__init__.py`,这样注册器才能找到该模块并添加它。 + +```python +from .my_optimizer import MyOptimizer +``` + +- 在配置文件使用 `custom_imports` 来手动导入模块。 + +```python +custom_imports = dict(imports=['mmdet.engine.optimizers.my_optimizer'], allow_failed_imports=False) +``` + +`mmdet.engine.optimizers.my_optimizer` 模块将在程序开始时导入,之后 `MyOptimizer` 类会被自动注册。注意:应该导入 `MyOptimizer` 所在的文件,即 `mmdet.engine.optimizers.my_optimizer`,而不是 `mmdet.engine.optimizers.my_optimizer.MyOptimizer`。 + +实际上,用户也可以在别的目录结构下来进行导入模块,只要改模块可以在 `PYTHONPATH` 中找到。 + +#### 3. 在配置文件中指定优化器 + +接下来,你可以在配置文件中的 `optim_wrapper` 域中的中 `optimizer` 域中设置你实现的优化器 `MyOptimizer`。在配置文件中,优化器在 `optimizer` 域中的配置方式如下: + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)) +``` + +为了使用你的优化器,可以进行如下修改 + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)) +``` + +### 自定义优化器包装构造类 + +一些模型可能存在一些特定参数的优化设置,比如,BN 层的权重衰减。用户可以通过自定义优化器包装构造类来实现这些精细化的参数调整。 + +```python +from mmengine.optim import DefaultOptiWrapperConstructor + +from mmdet.registry import OPTIM_WRAPPER_CONSTRUCTORS +from .my_optimizer import MyOptimizer + + +@OPTIM_WRAPPER_CONSTRUCTORS.register_module() +class MyOptimizerWrapperConstructor(DefaultOptimWrapperConstructor): + + def __init__(self, + optim_wrapper_cfg: dict, + paramwise_cfg: Optional[dict] = None): + + def __call__(self, model: nn.Module) -> OptimWrapper: + + return optim_wrapper + +``` + +优化器包装构造类的具体实现见[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L18),用户以它为模板,来实现新的优化器包装构造类。 + +### 额外的设置 + +一些没有被优化器实现的技巧(比如,参数层面的学习率设置)应该通过优化器包装构造类来实现或者钩子。我们列出了一些常用的设置用于稳定训练或者加速训练。请随意创建 PR,发布更多设置。 + +- __使用梯度裁剪来稳定训练__: + 一些模型需要进行梯度裁剪来稳定训练过程,例子如下: + + ```python + optim_wrapper = dict( + _delete_=True, clip_grad=dict(max_norm=35, norm_type=2)) + ``` + + 如果你的配置已经集成了基础配置(包含了 `optim_wrapper` 的配置),那么你需要添加 `_delete_=True` 来覆盖掉不需要的设置。具体见[配置相关的文档](https://mmdetection.readthedocs.io/en/latest/tutorials/config.html)。 + +- __使用动量调度加速模型收敛__: + 我们支持动量调度器根据学习率修改模型的动量,这可以使模型以更快的方式收敛。动量调度器通常与学习率调度器一起使用,例如 [3D 检测](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/_base_/schedules/cyclic-20e.py) 中使用以下配置以加速收敛。 + 更多细节请参考 [CosineAnnealingLR](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py#L43) 和 [CosineAnnealingMomentum](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/momentum_scheduler.py#L71) 的具体实现。 + + ```python + param_scheduler = [ + # 学习率调度器 + # 在前 8 个 epoch, 学习率从 0 增大到 lr * 10 + # 在接下来 12 个 epoch, 学习率从 lr * 10 减小到 lr * 1e-4 + dict( + type='CosineAnnealingLR', + T_max=8, + eta_min=lr * 10, + begin=0, + end=8, + by_epoch=True, + convert_to_iter_based=True), + dict( + type='CosineAnnealingLR', + T_max=12, + eta_min=lr * 1e-4, + begin=8, + end=20, + by_epoch=True, + convert_to_iter_based=True), + # 动量调度器 + # 在前 8 个 epoch, 动量从 0 增大到 0.85 / 0.95 + # 在接下来 12 个 epoch, 学习率从 0.85 / 0.95 增大到 1 + dict( + type='CosineAnnealingMomentum', + T_max=8, + eta_min=0.85 / 0.95, + begin=0, + end=8, + by_epoch=True, + convert_to_iter_based=True), + dict( + type='CosineAnnealingMomentum', + T_max=12, + eta_min=1, + begin=8, + end=20, + by_epoch=True, + convert_to_iter_based=True) + ] + ``` + +## 自定义训练策略 + +默认情况下,我们使用 1x 的学习率调整策略,这会条用 MMEngine 中的 [MultiStepLR](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py#L139)。 +我们支持许多其他学习率调整策略,具体见[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py),例如 `CosineAnnealingLR` 和 `PolyLR` 策略。下面有些例子 + +- 多项式学习率调整策略: + + ```python + param_scheduler = [ + dict( + type='PolyLR', + power=0.9, + eta_min=1e-4, + begin=0, + end=8, + by_epoch=True)] + ``` + +- 余弦退火学习率调整策略 + + ```python + param_scheduler = [ + dict( + type='CosineAnnealingLR', + T_max=8, + eta_min=lr * 1e-5, + begin=0, + end=8, + by_epoch=True)] + + ``` + +## 自定义训练循环 + +默认情况下,在 `train_cfg` 中使用 `EpochBasedTrainLoop`,并且在每个 epoch 训练之后进行验证,如下所示。 + +```python +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_begin=1, val_interval=1) +``` + +实际上,[`IterBasedTrainLoop`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L183%5D) 和\[`EpochBasedTrainLoop`\](https:// github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L18) 支持动态区间的方式进行验证,见下例。 + +```python +# 在第 365001 次迭代之前,我们每 5000 次迭代进行一次评估。 +# 在第 365000 次迭代后,我们每 368750 次迭代进行一次评估, +# 这意味着我们在训练结束时进行评估。 + +interval = 5000 +max_iters = 368750 +dynamic_intervals = [(max_iters // interval * interval + 1, max_iters)] +train_cfg = dict( + type='IterBasedTrainLoop', + max_iters=max_iters, + val_interval=interval, + dynamic_intervals=dynamic_intervals) +``` + +## 自定义钩子 + +### 自定义自行实现的钩子 + +#### 1. 实现一个新的钩子 + +MMEngine 提供了许多有用的[钩子](https://mmdetection.readthedocs.io/en/latest/tutorials/hooks.html),但在某些情况下用户可能需要实现新的钩子。MMDetection 在 v3.0 中支持自定义钩子。因此,用户可以直接在 mmdet 或其基于 mmdet 的代码库中实现钩子,并通过仅在训练中修改配置来使用钩子。 +这里我们给出一个在 mmdet 中创建一个新的钩子并在训练中使用它的例子。 + +```python +from mmengine.hooks import Hook +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class MyHook(Hook): + + def __init__(self, a, b): + + def before_run(self, runner) -> None: + + def after_run(self, runner) -> None: + + def before_train(self, runner) -> None: + + def after_train(self, runner) -> None: + + def before_train_epoch(self, runner) -> None: + + def after_train_epoch(self, runner) -> None: + + def before_train_iter(self, + runner, + batch_idx: int, + data_batch: DATA_BATCH = None) -> None: + + def after_train_iter(self, + runner, + batch_idx: int, + data_batch: DATA_BATCH = None, + outputs: Optional[dict] = None) -> None: +``` + +根据钩子的功能,用户需要在 `before_run`、`after_run`、`before_train`、`after_train`、`before_train_epoch`、`after_train_epoch`、`before_train_iter` 和 `after_train_iter`。还有更多可以插入钩子的点,更多细节请参考 [base hook class](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/hook.py#L9)。 + +#### 2. 注册新钩子 + +然后我们需要导入 `MyHook`。假设该文件位于 `mmdet/engine/hooks/my_hook.py` 中,有两种方法可以做到这一点: + +- 修改 `mmdet/engine/hooks/__init__.py` 以导入它。 + + 新定义的模块应该在 `mmdet/engine/hooks/__init__.py` 中导入,以便注册表找到新模块并添加它: + +```python +from .my_hook import MyHook +``` + +- 在配置中使用 `custom_imports` 手动导入它 + +```python +custom_imports = dict(imports=['mmdet.engine.hooks.my_hook'], allow_failed_imports=False) +``` + +#### 3. 修改配置 + +```python +custom_hooks = [ + dict(type='MyHook', a=a_value, b=b_value) +] +``` + +你还可以通过修改键 `priority` 的值为 `NORMAL` 或 `HIGHEST` 来设置挂钩的优先级,如下所示 + +```python +custom_hooks = [ + dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL') +] +``` + +默认情况下,钩子的优先级在注册期间设置为 `NORMAL`。 + +### 使用 MMDetection 中实现的钩子 + +如果 MMDetection 中已经实现了该钩子,你可以直接修改配置以使用该钩子,如下所示 + +#### 例子: `NumClassCheckHook` + +我们实现了一个名为 [NumClassCheckHook](https://github.com/open-mmlab/mmdetection/blob/main/mmdet/engine/hooks/num_class_check_hook.py) 的自定义钩子来检查 `num_classes` 是否在 head 中和 `dataset` 中的 `classes` 的长度相匹配。 + +我们在 [default_runtime.py](https://github.com/open-mmlab/mmdetection/blob/main/configs/_base_/default_runtime.py) 中设置它。 + +```python +custom_hooks = [dict(type='NumClassCheckHook')] +``` + +### 修改默认运行时钩子 + +有一些常见的钩子是通过 `default_hooks` 注册的,它们是 + +- `IterTimerHook`:记录 “data_time” 用于加载数据和 “time” 用于模型训练步骤的钩子。 +- `LoggerHook`:从`Runner`的不同组件收集日志并将它们写入终端、JSON文件、tensorboard和 wandb 等的钩子。 +- `ParamSchedulerHook`:更新优化器中一些超参数的钩子,例如学习率和动量。 +- `CheckpointHook`:定期保存检查点的钩子。 +- `DistSamplerSeedHook`:为采样器和批处理采样器设置种子的钩子。 +- `DetVisualizationHook`:用于可视化验证和测试过程预测结果的钩子。 + +`IterTimerHook`、`ParamSchedulerHook` 和 `DistSamplerSeedHook` 很简单,通常不需要修改,所以这里我们将展示如何使用 `LoggerHook`、`CheckpointHook` 和 `DetVisualizationHook`。 + +#### CheckpointHook + +除了定期保存检查点,[`CheckpointHook`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L19) 提供了其他选项,例如`max_keep_ckpts`、`save_optimizer ` 等。用户可以设置 `max_keep_ckpts` 只保存少量检查点或通过 `save_optimizer` 决定是否存储优化器的状态字典。参数的更多细节在[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L19)可以找到。 + +```python +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + interval=1, + max_keep_ckpts=3, + save_optimizer=True)) +``` + +#### LoggerHook + +`LoggerHook` 可以设置间隔。详细用法可以在 [docstring](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/logger_hook.py#L18) 中找到。 + +```python +default_hooks = dict(logger=dict(type='LoggerHook', interval=50)) +``` + +#### DetVisualizationHook + +`DetVisualizationHook` 使用 `DetLocalVisualizer` 来可视化预测结果,`DetLocalVisualizer` 支持不同的后端,例如 `TensorboardVisBackend` 和 `WandbVisBackend` (见 [docstring](https://github.com/open-mmlab/mmengine/blob/main/mmengine/visualization/vis_backend.py) 了解更多细节)。用户可以添加多个后端来进行可视化,如下所示。 + +```python +default_hooks = dict( + visualization=dict(type='DetVisualizationHook', draw=True)) + +vis_backends = [dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend')] +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') +``` diff --git a/mmdetection/docs/zh_cn/advanced_guides/customize_transforms.md b/mmdetection/docs/zh_cn/advanced_guides/customize_transforms.md new file mode 100644 index 00000000..aa407179 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/customize_transforms.md @@ -0,0 +1,47 @@ +# 自定义数据预处理流程 + +1. 在任意文件里写一个新的流程,例如在 `my_pipeline.py`,它以一个字典作为输入并且输出一个字典: + + ```python + import random + from mmcv.transforms import BaseTransform + from mmdet.registry import TRANSFORMS + + + @TRANSFORMS.register_module() + class MyTransform(BaseTransform): + """Add your transform + + Args: + p (float): Probability of shifts. Default 0.5. + """ + + def __init__(self, prob=0.5): + self.prob = prob + + def transform(self, results): + if random.random() > self.prob: + results['dummy'] = True + return results + ``` + +2. 在配置文件里调用并使用你写的数据处理流程,需要确保你的训练脚本能够正确导入新增模块: + + ```python + custom_imports = dict(imports=['path.to.my_pipeline'], allow_failed_imports=False) + + train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='MyTransform', prob=0.2), + dict(type='PackDetInputs') + ] + ``` + +3. 可视化数据增强处理流程的结果 + + 如果想要可视化数据增强处理流程的结果,可以使用 `tools/misc/browse_dataset.py` 直观 + 地浏览检测数据集(图像和标注信息),或将图像保存到指定目录。 + 使用方法请参考[可视化文档](../user_guides/visualization.md) diff --git a/mmdetection/docs/zh_cn/advanced_guides/data_flow.md b/mmdetection/docs/zh_cn/advanced_guides/data_flow.md new file mode 100644 index 00000000..ccc734f7 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/data_flow.md @@ -0,0 +1 @@ +# 数据流(待更新) diff --git a/mmdetection/docs/zh_cn/advanced_guides/datasets.md b/mmdetection/docs/zh_cn/advanced_guides/datasets.md new file mode 100644 index 00000000..16cc9bfc --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/datasets.md @@ -0,0 +1 @@ +# 数据集(待更新) diff --git a/mmdetection/docs/zh_cn/advanced_guides/engine.md b/mmdetection/docs/zh_cn/advanced_guides/engine.md new file mode 100644 index 00000000..fa1a2561 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/engine.md @@ -0,0 +1 @@ +# 执行引擎(待更新) diff --git a/mmdetection/docs/zh_cn/advanced_guides/evaluation.md b/mmdetection/docs/zh_cn/advanced_guides/evaluation.md new file mode 100644 index 00000000..0b495448 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/evaluation.md @@ -0,0 +1 @@ +# 精度评测(待更新) diff --git a/mmdetection/docs/zh_cn/advanced_guides/how_to.md b/mmdetection/docs/zh_cn/advanced_guides/how_to.md new file mode 100644 index 00000000..6705dafd --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/how_to.md @@ -0,0 +1,220 @@ +本教程收集了任何如何使用 MMDetection 进行 xxx 的答案。 如果您遇到有关`如何做`的问题及答案,请随时更新此文档! + +## 使用 MMPretrain 的骨干网络 + +MMDet、MMPretrain、MMSeg 中的模型注册表都继承自 MMEngine 中的根注册表,允许这些存储库直接使用彼此已经实现的模块。 因此用户可以在 MMDetection 中使用来自 MMPretrain 的骨干网络,而无需实现MMPretrain 中已经存在的网络。 + +### 使用在 MMPretrain 中实现的骨干网络 + +假设想将 `MobileNetV3-small` 作为 `RetinaNet` 的骨干网络,则配置文件如下。 + +```python +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +# please install mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict(imports=['mmpretrain.models'], allow_failed_imports=False) +pretrained = 'https://download.openmmlab.com/mmclassification/v0/mobilenet_v3/convert/mobilenet_v3_small-8427ecf0.pth' +model = dict( + backbone=dict( + _delete_=True, # 将 _base_ 中关于 backbone 的字段删除 + type='mmpretrain.MobileNetV3', # 使用 mmpretrain 中的 MobileNetV3 + arch='small', + out_indices=(3, 8, 11), # 修改 out_indices + init_cfg=dict( + type='Pretrained', + checkpoint=pretrained, + prefix='backbone.')), # mmpretrain 中骨干网络的预训练权重含义 prefix='backbone.',为了正常加载权重,需要把这个 prefix 去掉。 + # 修改 in_channels + neck=dict(in_channels=[24, 48, 96], start_level=0)) +``` + +### 通过 MMPretrain 使用 TIMM 中实现的骨干网络 + +由于 MMPretrain 提供了 Py**T**orch **Im**age **M**odels (`timm`) 骨干网络的封装,用户也可以通过 MMPretrain 直接使用 `timm` 中的骨干网络。假设想将 [`EfficientNet-B1`](../../../configs/timm_example/retinanet_timm-efficientnet-b1_fpn_1x_coco.py) 作为 `RetinaNet` 的骨干网络,则配置文件如下。 + +```python +# https://github.com/open-mmlab/mmdetection/blob/main/configs/timm_example/retinanet_timm_efficientnet_b1_fpn_1x_coco.py +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] + +# please install mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict(imports=['mmpretrain.models'], allow_failed_imports=False) +model = dict( + backbone=dict( + _delete_=True, # 将 _base_ 中关于 backbone 的字段删除 + type='mmpretrain.TIMMBackbone', # 使用 mmpretrain 中 timm 骨干网络 + model_name='efficientnet_b1', + features_only=True, + pretrained=True, + out_indices=(1, 2, 3, 4)), # 修改 out_indices + neck=dict(in_channels=[24, 40, 112, 320])) # 修改 in_channels + +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001) +``` + +`type='mmpretrain.TIMMBackbone'` 表示在 MMDetection 中使用 MMPretrain 中的 `TIMMBackbone` 类,并且使用的模型为` EfficientNet-B1`,其中 `mmpretrain` 表示 MMPretrain 库,而 `TIMMBackbone ` 表示 MMPretrain 中实现的 TIMMBackbone 包装器。 + +关于层次注册器的具体原理可以参考 [MMEngine 文档](https://mmengine.readthedocs.io/zh_cn/latest/tutorials/config.md#跨项目继承配置文件),关于如何使用 MMPretrain 中的其他 backbone,可以参考 [MMPretrain 文档](https://mmpretrain.readthedocs.io/en/latest/user_guides/config.html)。 + +## 使用马赛克数据增强 + +如果你想在训练中使用 `Mosaic`,那么请确保你同时使用 `MultiImageMixDataset`。以 `Faster R-CNN` 算法为例,你可以通过如下做法实现: + +```python +# 直接打开 configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py ,增添如下字段 +data_root = 'data/coco/' +dataset_type = 'CocoDataset' +img_scale=(1333, 800) + +train_pipeline = [ + dict(type='Mosaic', img_scale=img_scale, pad_val=114.0), + dict( + type='RandomAffine', + scaling_ratio_range=(0.1, 2), + border=(-img_scale[0] // 2, -img_scale[1] // 2)), # 图像经过马赛克处理后会放大4倍,所以我们使用仿射变换来恢复图像的大小。 + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs')) +] + +train_dataset = dict( + _delete_ = True, # 删除不必要的设置 + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + ann_file=data_root + 'annotations/instances_train2017.json', + img_prefix=data_root + 'train2017/', + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True) + ], + filter_empty_gt=False, + ), + pipeline=train_pipeline + ) + +data = dict( + train=train_dataset + ) +``` + +## 在配置文件中冻结骨干网络后在训练中解冻骨干网络 + +如果你在配置文件中已经冻结了骨干网络并希望在几个训练周期后解冻它,你可以通过 hook 来实现这个功能。以用 ResNet 为骨干网络的 Faster R-CNN 为例,你可以冻结一个骨干网络的一个层并在配置文件中添加如下 `custom_hooks`: + +```python +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + # freeze one stage of the backbone network. + backbone=dict(frozen_stages=1), +) +custom_hooks = [dict(type="UnfreezeBackboneEpochBasedHook", unfreeze_epoch=1)] +``` + +同时在 `mmdet/core/hook/unfreeze_backbone_epoch_based_hook.py` 当中书写 `UnfreezeBackboneEpochBasedHook` 类 + +```python +from mmengine.model import is_model_wrapper +from mmengine.hooks import Hook +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class UnfreezeBackboneEpochBasedHook(Hook): + """Unfreeze backbone network Hook. + + Args: + unfreeze_epoch (int): The epoch unfreezing the backbone network. + """ + + def __init__(self, unfreeze_epoch=1): + self.unfreeze_epoch = unfreeze_epoch + + def before_train_epoch(self, runner): + # Unfreeze the backbone network. + # Only valid for resnet. + if runner.epoch == self.unfreeze_epoch: + model = runner.model + if is_module_wrapper(model): + model = model.module + backbone = model.backbone + if backbone.frozen_stages >= 0: + if backbone.deep_stem: + backbone.stem.train() + for param in backbone.stem.parameters(): + param.requires_grad = True + else: + backbone.norm1.train() + for m in [backbone.conv1, backbone.norm1]: + for param in m.parameters(): + param.requires_grad = True + + for i in range(1, backbone.frozen_stages + 1): + m = getattr(backbone, f'layer{i}') + m.train() + for param in m.parameters(): + param.requires_grad = True +``` + +## 获得新的骨干网络的通道数 + +如果你想获得一个新骨干网络的通道数,你可以单独构建这个骨干网络并输入一个伪造的图片来获取每一个阶段的输出。 + +以 `ResNet` 为例: + +```python +from mmdet.models import ResNet +import torch +self = ResNet(depth=18) +self.eval() +inputs = torch.rand(1, 3, 32, 32) +level_outputs = self.forward(inputs) +for level_out in level_outputs: + print(tuple(level_out.shape)) + +``` + +以上脚本的输出为: + +```python +(1, 64, 8, 8) +(1, 128, 4, 4) +(1, 256, 2, 2) +(1, 512, 1, 1) +``` + +用户可以通过将脚本中的 `ResNet(depth=18)` 替换为自己的骨干网络配置来得到新的骨干网络的通道数。 + +# MMDetection 中训练 Detectron2 的模型 + +用户可以使用 `Detectron2Wrapper` 从而在 MMDetection 中使用 Detectron2 的模型。 +我们提供了 [Faster R-CNN](../../../configs/misc/d2_faster-rcnn_r50-caffe_fpn_ms-90k_coco.py), +[Mask R-CNN](../../../configs/misc/d2_mask-rcnn_r50-caffe_fpn_ms-90k_coco.py) 和 [RetinaNet](../../../configs/misc/d2_retinanet_r50-caffe_fpn_ms-90k_coco.py) 的示例来在 MMDetection 中训练/测试 Detectron2 的模型。 + +使用过程中需要注意配置文件中算法组件要和 Detectron2 中的相同。模型初始化时,我们首先初始化 [Detectron2](https://github.com/facebookresearch/detectron2/blob/main/detectron2/config/defaults.py) 的默认设置,然后配置文件中的设置将覆盖默认设置,模型将基于更新过的设置来建立。 +输入数据首先转换成 Detectron2 的类型并输入进 Detectron2 的模型中。在推理阶段,Detectron2 的模型结果将会转换回 MMDetection 的类型。 + +## 使用 Detectron2 的预训练权重 + +`Detectron2Wrapper` 中的权重初始化将不使用 MMDetection 的逻辑。用户可以设置 `model.d2_detector.weights=xxx` 来加载预训练的权重。 +例如,我们可以使用 `model.d2_detector.weights='detectron2://ImageNetPretrained/MSRA/R-50.pkl'` 来加载 ResNet-50 的预训练权重,或者使用 +`model.d2_detector.weights='detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x/137260431/model_final_a54504.pkl'` 来加载 Detectron2 中提出的预训练的Mask R-CNN权重。 + +**注意:** 不能直接使用 `load_from` 来加载 Detectron2 的预训练模型,但可以通过 `tools/model_converters/detectron2_to_mmdet.py` 先对该预训练模型进行转换。 + +在测试时,用户应该首先使用 `tools/model_converters/detectron2_to_mmdet.py` 将 Detectron2 的预训练权重转换为 MMDetection 可读取的结构。 + +```shell +python tools/model_converters/detectron2_to_mmdet.py ${Detectron2 ckpt path} ${MMDetectron ckpt path}。 +``` diff --git a/mmdetection/docs/zh_cn/advanced_guides/index.rst b/mmdetection/docs/zh_cn/advanced_guides/index.rst new file mode 100644 index 00000000..8e925394 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/index.rst @@ -0,0 +1,34 @@ +基础概念 +*************** + +.. toctree:: + :maxdepth: 1 + + data_flow.md + structures.md + models.md + datasets.md + transforms.md + evaluation.md + engine.md + conventions.md + +组件定制 +************************ + +.. toctree:: + :maxdepth: 1 + + customize_models.md + customize_losses.md + customize_dataset.md + customize_transforms.md + customize_runtime.md + +How to +************************ + +.. toctree:: + :maxdepth: 1 + + how_to.md diff --git a/mmdetection/docs/zh_cn/advanced_guides/models.md b/mmdetection/docs/zh_cn/advanced_guides/models.md new file mode 100644 index 00000000..c5119d06 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/models.md @@ -0,0 +1 @@ +# 模型(待更新) diff --git a/mmdetection/docs/zh_cn/advanced_guides/structures.md b/mmdetection/docs/zh_cn/advanced_guides/structures.md new file mode 100644 index 00000000..c2118c34 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/structures.md @@ -0,0 +1 @@ +# 数据结构(待更新) diff --git a/mmdetection/docs/zh_cn/advanced_guides/transforms.md b/mmdetection/docs/zh_cn/advanced_guides/transforms.md new file mode 100644 index 00000000..07d7db24 --- /dev/null +++ b/mmdetection/docs/zh_cn/advanced_guides/transforms.md @@ -0,0 +1,43 @@ +# 数据变换(待更新) + +按照惯例,我们使用 `Dataset` 和 `DataLoader` 进行多进程的数据加载。`Dataset` 返回字典类型的数据,数据内容为模型 `forward` 方法的各个参数。由于在目标检测中,输入的图像数据具有不同的大小,我们在 `MMCV` 里引入一个新的 `DataContainer` 类去收集和分发不同大小的输入数据。更多细节请参考[这里](https://github.com/open-mmlab/mmcv/blob/master/mmcv/parallel/data_container.py)。 + +数据的准备流程和数据集是解耦的。通常一个数据集定义了如何处理标注数据(annotations)信息,而一个数据流程定义了准备一个数据字典的所有步骤。一个流程包括一系列的操作,每个操作都把一个字典作为输入,然后再输出一个新的字典给下一个变换操作。 + +我们在下图展示了一个经典的数据处理流程。蓝色块是数据处理操作,随着数据流程的处理,每个操作都可以在结果字典中加入新的键(标记为绿色)或更新现有的键(标记为橙色)。 + +![pipeline figure](../../../resources/data_pipeline.png) + +这些操作可以分为数据加载(data loading)、预处理(pre-processing)、格式变化(formatting)和测试时数据增强(test-time augmentation)。 + +下面的例子是 `Faster R-CNN` 的一个流程: + +```python +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', img_scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', flip_ratio=0.5), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='DefaultFormatBundle'), + dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(1333, 800), + flip=False, + transforms=[ + dict(type='Resize', keep_ratio=True), + dict(type='RandomFlip'), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='ImageToTensor', keys=['img']), + dict(type='Collect', keys=['img']), + ]) +] +``` diff --git a/mmdetection/docs/zh_cn/api.rst b/mmdetection/docs/zh_cn/api.rst new file mode 100644 index 00000000..1b127321 --- /dev/null +++ b/mmdetection/docs/zh_cn/api.rst @@ -0,0 +1,161 @@ +mmdet.apis +-------------- +.. automodule:: mmdet.apis + :members: + +mmdet.datasets +-------------- + +datasets +^^^^^^^^^^ +.. automodule:: mmdet.datasets + :members: + +api_wrappers +^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.datasets.api_wrappers + :members: + +samplers +^^^^^^^^^^ +.. automodule:: mmdet.datasets.samplers + :members: + +transforms +^^^^^^^^^^^^ +.. automodule:: mmdet.datasets.transforms + :members: + +mmdet.engine +-------------- + +hooks +^^^^^^^^^^ +.. automodule:: mmdet.engine.hooks + :members: + +optimizers +^^^^^^^^^^^^^^^ +.. automodule:: mmdet.engine.optimizers + :members: + +runner +^^^^^^^^^^ +.. automodule:: mmdet.engine.runner + :members: + +schedulers +^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.engine.schedulers + :members: + +mmdet.evaluation +-------------------- + +functional +^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.evaluation.functional + :members: + +metrics +^^^^^^^^^^ +.. automodule:: mmdet.evaluation.metrics + :members: + + +mmdet.models +-------------- + +backbones +^^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.models.backbones + :members: + +data_preprocessors +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.models.data_preprocessors + :members: + +dense_heads +^^^^^^^^^^^^^^^ +.. automodule:: mmdet.models.dense_heads + :members: + +detectors +^^^^^^^^^^ +.. automodule:: mmdet.models.detectors + :members: + +layers +^^^^^^^^^^ +.. automodule:: mmdet.models.layers + :members: + +losses +^^^^^^^^^^ +.. automodule:: mmdet.models.losses + :members: + +necks +^^^^^^^^^^^^ +.. automodule:: mmdet.models.necks + :members: + +roi_heads +^^^^^^^^^^^^^ +.. automodule:: mmdet.models.roi_heads + :members: + +seg_heads +^^^^^^^^^^^^^ +.. automodule:: mmdet.models.seg_heads + :members: + +task_modules +^^^^^^^^^^^^^ +.. automodule:: mmdet.models.task_modules + :members: + +test_time_augs +^^^^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.models.test_time_augs + :members: + +utils +^^^^^^^^^^ +.. automodule:: mmdet.models.utils + :members: + + +mmdet.structures +-------------------- + +structures +^^^^^^^^^^^^^^^^^ +.. automodule:: mmdet.structures + :members: + +bbox +^^^^^^^^^^ +.. automodule:: mmdet.structures.bbox + :members: + +mask +^^^^^^^^^^ +.. automodule:: mmdet.structures.mask + :members: + +mmdet.testing +---------------- +.. automodule:: mmdet.testing + :members: + +mmdet.visualization +-------------------- +.. automodule:: mmdet.visualization + :members: + +mmdet.utils +-------------- +.. automodule:: mmdet.utils + :members: diff --git a/mmdetection/docs/zh_cn/article.md b/mmdetection/docs/zh_cn/article.md new file mode 100644 index 00000000..3b698308 --- /dev/null +++ b/mmdetection/docs/zh_cn/article.md @@ -0,0 +1,53 @@ +## 中文解读文案汇总(待更新) + +### 1 官方解读文案(v2.x) + +#### 1.1 框架解读 + +- **[轻松掌握 MMDetection 整体构建流程(一)](https://zhuanlan.zhihu.com/p/337375549)** +- **[轻松掌握 MMDetection 整体构建流程(二)](https://zhuanlan.zhihu.com/p/341954021)** +- **[轻松掌握 MMDetection 中 Head 流程](https://zhuanlan.zhihu.com/p/343433169)** + +#### 1.2 算法解读 + +- **[轻松掌握 MMDetection 中常用算法(一):RetinaNet 及配置详解](https://zhuanlan.zhihu.com/p/346198300)** +- **[轻松掌握 MMDetection 中常用算法(二):Faster R-CNN|Mask R-CNN](https://zhuanlan.zhihu.com/p/349807581)** +- [轻松掌握 MMDetection 中常用算法(三):FCOS](https://zhuanlan.zhihu.com/p/358056615) +- [轻松掌握 MMDetection 中常用算法(四):ATSS](https://zhuanlan.zhihu.com/p/358125611) +- [轻松掌握 MMDetection 中常用算法(五):Cascade R-CNN](https://zhuanlan.zhihu.com/p/360952172) +- [轻松掌握 MMDetection 中常用算法(六):YOLOF](https://zhuanlan.zhihu.com/p/370758213) +- [轻松掌握 MMDetection 中常用算法(七):CenterNet](https://zhuanlan.zhihu.com/p/374891478) +- [轻松掌握 MMDetection 中常用算法(八):YOLACT](https://zhuanlan.zhihu.com/p/376347955) +- [轻松掌握 MMDetection 中常用算法(九):AutoAssign](https://zhuanlan.zhihu.com/p/378581552) +- [YOLOX 在 MMDetection 中复现全流程解析](https://zhuanlan.zhihu.com/p/398545304) +- [喂喂喂!你可以减重了!小模型 - MMDetection 新增SSDLite 、 MobileNetV2YOLOV3 两大经典算法](https://zhuanlan.zhihu.com/p/402781143) + +#### 1.3 工具解读 + +- [OpenMMLab 中混合精度训练 AMP 的正确打开方式](https://zhuanlan.zhihu.com/p/375224982) +- [小白都能看懂!手把手教你使用混淆矩阵分析目标检测](https://zhuanlan.zhihu.com/p/443499860) +- [MMDetection 图像缩放 Resize 详细说明 OpenMMLab](https://zhuanlan.zhihu.com/p/381117525) +- [拿什么拯救我的 4G 显卡](https://zhuanlan.zhihu.com/p/430123077) +- [MMDet居然能用MMCls的Backbone?论配置文件的打开方式](https://zhuanlan.zhihu.com/p/436865195) + +#### 1.4 知乎问答 + +- [COCO数据集上1x模式下为什么不采用多尺度训练?](https://www.zhihu.com/question/462170786/answer/1915119662) +- [MMDetection中SOTA论文源码中将训练过程中BN层的eval打开?](https://www.zhihu.com/question/471189603/answer/2195540892) +- [基于PyTorch的MMDetection中训练的随机性来自何处?](https://www.zhihu.com/question/453511684/answer/1839683634) +- [单阶段、双阶段、anchor-based、anchor-free 这四者之间有什么联系吗?](https://www.zhihu.com/question/428972054/answer/1619925296) +- [目标检测的深度学习方法,有推荐的书籍或资料吗?](https://www.zhihu.com/question/391577080/answer/1612593817) +- [大佬们,刚入学研究生,想入门目标检测,有什么学习路线可以入门的?](https://www.zhihu.com/question/343768934/answer/1612580715) +- [目标检测领域还有什么可以做的?](https://www.zhihu.com/question/280703314/answer/1627885518) +- [如何看待Transformer在CV上的应用前景,未来有可能替代CNN吗?](https://www.zhihu.com/question/437495132/answer/1686380553) +- [MMDetection如何学习源码?](https://www.zhihu.com/question/451585041/answer/1832498963) +- [如何具体上手实现目标检测呢?](https://www.zhihu.com/question/341401981/answer/1848561187) + +#### 1.5 其他 + +- **[不得不知的 MMDetection 学习路线(个人经验版)](https://zhuanlan.zhihu.com/p/369826931)** +- [OpenMMLab 社区专访之 YOLOX 复现篇](https://zhuanlan.zhihu.com/p/405913343) + +### 2 社区解读文案(v2.x) + +- [手把手带你实现经典检测网络 Mask R-CNN 的推理](https://zhuanlan.zhihu.com/p/414082071) diff --git a/mmdetection/docs/zh_cn/conf.py b/mmdetection/docs/zh_cn/conf.py new file mode 100644 index 00000000..e6878408 --- /dev/null +++ b/mmdetection/docs/zh_cn/conf.py @@ -0,0 +1,118 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import subprocess +import sys + +import pytorch_sphinx_theme + +sys.path.insert(0, os.path.abspath('../../')) + +# -- Project information ----------------------------------------------------- + +project = 'MMDetection' +copyright = '2018-2021, OpenMMLab' +author = 'MMDetection Authors' +version_file = '../../mmdet/version.py' + + +def get_version(): + with open(version_file, 'r') as f: + exec(compile(f.read(), version_file, 'exec')) + return locals()['__version__'] + + +# The full version, including alpha/beta/rc tags +release = get_version() + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'myst_parser', + 'sphinx_markdown_tables', + 'sphinx_copybutton', +] + +myst_enable_extensions = ['colon_fence'] +myst_heading_anchors = 3 + +autodoc_mock_imports = [ + 'matplotlib', 'pycocotools', 'terminaltables', 'mmdet.version', 'mmcv.ops' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +# The main toctree document. +master_doc = 'index' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +# html_theme = 'sphinx_rtd_theme' +html_theme = 'pytorch_sphinx_theme' +html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()] + +html_theme_options = { + 'menu': [ + { + 'name': 'GitHub', + 'url': 'https://github.com/open-mmlab/mmdetection' + }, + ], + # Specify the language of shared menu + 'menu_lang': + 'cn', +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = ['css/readthedocs.css'] + +language = 'zh_CN' + +# -- Extension configuration ------------------------------------------------- +# Ignore >>> when copying code +copybutton_prompt_text = r'>>> |\.\.\. ' +copybutton_prompt_is_regexp = True + + +def builder_inited_handler(app): + subprocess.run(['./stat.py']) + + +def setup(app): + app.connect('builder-inited', builder_inited_handler) diff --git a/mmdetection/docs/zh_cn/get_started.md b/mmdetection/docs/zh_cn/get_started.md new file mode 100644 index 00000000..52d061ef --- /dev/null +++ b/mmdetection/docs/zh_cn/get_started.md @@ -0,0 +1,230 @@ +# 开始你的第一步 + +## 依赖 + +本节中,我们将演示如何用 PyTorch 准备一个环境。 + +MMDetection 支持在 Linux,Windows 和 macOS 上运行。它需要 Python 3.7 以上,CUDA 9.2 以上和 PyTorch 1.8 及其以上。 + +```{note} +如果你对 PyTorch 有经验并且已经安装了它,你可以直接跳转到[下一小节](#安装流程)。否则,你可以按照下述步骤进行准备。 +``` + +**步骤 0.** 从[官方网站](https://docs.conda.io/en/latest/miniconda.html)下载并安装 Miniconda。 + +**步骤 1.** 创建并激活一个 conda 环境。 + +```shell +conda create --name openmmlab python=3.8 -y +conda activate openmmlab +``` + +**步骤 2.** 基于 [PyTorch 官方说明](https://pytorch.org/get-started/locally/)安装 PyTorch。 + +在 GPU 平台上: + +```shell +conda install pytorch torchvision -c pytorch +``` + +在 CPU 平台上: + +```shell +conda install pytorch torchvision cpuonly -c pytorch +``` + +## 安装流程 + +我们推荐用户参照我们的最佳实践安装 MMDetection。不过,整个过程也是可定制化的,更多信息请参考[自定义安装](#自定义安装)章节。 + +### 最佳实践 + +**步骤 0.** 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMEngine](https://github.com/open-mmlab/mmengine) 和 [MMCV](https://github.com/open-mmlab/mmcv)。 + +```shell +pip install -U openmim +mim install mmengine +mim install "mmcv>=2.0.0" +``` + +**注意:** 在 MMCV-v2.x 中,`mmcv-full` 改名为 `mmcv`,如果你想安装不包含 CUDA 算子精简版,可以通过 `mim install "mmcv-lite>=2.0.0rc1"` 来安装。 + +**步骤 1.** 安装 MMDetection。 + +方案 a:如果你开发并直接运行 mmdet,从源码安装它: + +```shell +git clone https://github.com/open-mmlab/mmdetection.git +cd mmdetection +pip install -v -e . +# "-v" 指详细说明,或更多的输出 +# "-e" 表示在可编辑模式下安装项目,因此对代码所做的任何本地修改都会生效,从而无需重新安装。 +``` + +方案 b:如果你将 mmdet 作为依赖或第三方 Python 包,使用 MIM 安装: + +```shell +mim install mmdet +``` + +## 验证安装 + +为了验证 MMDetection 是否安装正确,我们提供了一些示例代码来执行模型推理。 + +**步骤 1.** 我们需要下载配置文件和模型权重文件。 + +```shell +mim download mmdet --config rtmdet_tiny_8xb32-300e_coco --dest . +``` + +下载将需要几秒钟或更长时间,这取决于你的网络环境。完成后,你会在当前文件夹中发现两个文件 `rtmdet_tiny_8xb32-300e_coco.py` 和 `rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth`。 + +**步骤 2.** 推理验证。 + +方案 a:如果你通过源码安装的 MMDetection,那么直接运行以下命令进行验证: + +```shell +python demo/image_demo.py demo/demo.jpg rtmdet_tiny_8xb32-300e_coco.py --weights rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth --device cpu +``` + +你会在当前文件夹中的 `outputs/vis` 文件夹中看到一个新的图像 `demo.jpg`,图像中包含有网络预测的检测框。 + +方案 b:如果你通过 MIM 安装的 MMDetection,那么可以打开你的 Python 解析器,复制并粘贴以下代码: + +```python +from mmdet.apis import init_detector, inference_detector + +config_file = 'rtmdet_tiny_8xb32-300e_coco.py' +checkpoint_file = 'rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth' +model = init_detector(config_file, checkpoint_file, device='cpu') # or device='cuda:0' +inference_detector(model, 'demo/demo.jpg') +``` + +你将会看到一个包含 `DetDataSample` 的列表,预测结果在 `pred_instance` 里,包含有检测框,类别和得分。 + +### 自定义安装 + +#### CUDA 版本 + +在安装 PyTorch 时,你需要指定 CUDA 的版本。如果你不清楚应该选择哪一个,请遵循我们的建议: + +- 对于 Ampere 架构的 NVIDIA GPU,例如 GeForce 30 系列以及 NVIDIA A100,CUDA 11 是必需的。 +- 对于更早的 NVIDIA GPU,CUDA 11 是向后兼容 (backward compatible) 的,但 CUDA 10.2 能够提供更好的兼容性,也更加轻量。 + +请确保你的 GPU 驱动版本满足最低的版本需求,参阅 NVIDIA 官方的 [CUDA 工具箱和相应的驱动版本关系表](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions__table-cuda-toolkit-driver-versions)。 + +```{note} +如果按照我们的最佳实践,安装 CUDA 运行时库就足够了,这是因为不需要在本地编译 CUDA 代码。但如果你希望从源码编译 MMCV,或是开发其他 CUDA 算子,那么就必须安装完整的 CUDA 工具链,参见 [NVIDIA 官网](https://developer.nvidia.com/cuda-downloads),另外还需要确保该 CUDA 工具链的版本与 PyTorch 安装时的配置相匹配(如用 `conda install` 安装 PyTorch 时指定的 cudatoolkit 版本)。 +``` + +#### 不使用 MIM 安装 MMEngine + +要使用 pip 而不是 MIM 来安装 MMEngine,请遵照 [MMEngine 安装指南](https://mmengine.readthedocs.io/zh_CN/latest/get_started/installation.html)。 + +例如,你可以通过以下命令安装 MMEngine。 + +```shell +pip install mmengine +``` + +#### 不使用 MIM 安装 MMCV + +MMCV 包含 C++ 和 CUDA 扩展,因此其对 PyTorch 的依赖比较复杂。MIM 会自动解析这些依赖,选择合适的 MMCV 预编译包,使安装更简单,但它并不是必需的。 + +要使用 pip 而不是 MIM 来安装 MMCV,请遵照 [MMCV 安装指南](https://mmcv.readthedocs.io/zh_CN/2.x/get_started/installation.html)。它需要您用指定 url 的形式手动指定对应的 PyTorch 和 CUDA 版本。 + +例如,下述命令将会安装基于 PyTorch 1.12.x 和 CUDA 11.6 编译的 MMCV。 + +```shell +pip install "mmcv>=2.0.0" -f https://download.openmmlab.com/mmcv/dist/cu116/torch1.12.0/index.html +``` + +#### 在 CPU 环境中安装 + +MMDetection 可以在 CPU 环境中构建。在 CPU 模式下,可以进行模型训练(需要 MMCV 版本 >= 2.0.0rc1)、测试或者推理。 + +但是,以下功能在该模式下不能使用: + +- Deformable Convolution +- Modulated Deformable Convolution +- ROI pooling +- Deformable ROI pooling +- CARAFE +- SyncBatchNorm +- CrissCrossAttention +- MaskedConv2d +- Temporal Interlace Shift +- nms_cuda +- sigmoid_focal_loss_cuda +- bbox_overlaps + +因此,如果尝试训练/测试/推理包含上述算子的模型,将会报错。下表列出了将会受影响的相关算法。 + +| 操作 | 模型 | +| :-----------------------------------------------------: | :--------------------------------------------------------------------------------------: | +| Deformable Convolution/Modulated Deformable Convolution | DCN、Guided Anchoring、RepPoints、CentripetalNet、VFNet、CascadeRPN、NAS-FCOS、DetectoRS | +| MaskedConv2d | Guided Anchoring | +| CARAFE | CARAFE | +| SyncBatchNorm | ResNeSt | + +#### 在 Google Colab 中安装 + +[Google Colab](https://colab.research.google.com/) 通常已经包含了 PyTorch 环境,因此我们只需要安装 MMEngine,MMCV 和 MMDetection 即可,命令如下: + +**步骤 1.** 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMEngine](https://github.com/open-mmlab/mmengine) 和 [MMCV](https://github.com/open-mmlab/mmcv)。 + +```shell +!pip3 install openmim +!mim install mmengine +!mim install "mmcv>=2.0.0,<2.1.0" +``` + +**步骤 2.** 使用源码安装 MMDetection。 + +```shell +!git clone https://github.com/open-mmlab/mmdetection.git +%cd mmdetection +!pip install -e . +``` + +**步骤 3.** 验证安装是否成功。 + +```python +import mmdet +print(mmdet.__version__) +# 预期输出:3.0.0 或其他版本号 +``` + +```{note} +在 Jupyter Notebook 中,感叹号 `!` 用于执行外部命令,而 `%cd` 是一个[魔术命令](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-cd),用于切换 Python 的工作路径。 +``` + +#### 通过 Docker 使用 MMDetection + +我们提供了一个 [Dockerfile](../../docker/Dockerfile) 来构建一个镜像。请确保你的 [docker 版本](https://docs.docker.com/engine/install/) >=19.03。 + +```shell +# 基于 PyTorch 1.9,CUDA 11.1 构建镜像 +# 如果你想要其他版本,只需要修改 Dockerfile +docker build -t mmdetection docker/ +``` + +用以下命令运行 Docker 镜像: + +```shell +docker run --gpus all --shm-size=8g -it -v {DATA_DIR}:/mmdetection/data mmdetection +``` + +### 排除故障 + +如果你在安装过程中遇到一些问题,请先查看 [FAQ](notes/faq.md) 页面。如果没有找到解决方案,你也可以在 GitHub 上[提出一个问题](https://github.com/open-mmlab/mmdetection/issues/new/choose)。 + +### 使用多个 MMDetection 版本进行开发 + +训练和测试的脚本已经在 `PYTHONPATH` 中进行了修改,以确保脚本使用当前目录中的 MMDetection。 + +要使环境中安装默认版本的 MMDetection 而不是当前正在使用的,可以删除出现在相关脚本中的代码: + +```shell +PYTHONPATH="$(dirname $0)/..":$PYTHONPATH +``` diff --git a/mmdetection/docs/zh_cn/index.rst b/mmdetection/docs/zh_cn/index.rst new file mode 100644 index 00000000..58a4d8a5 --- /dev/null +++ b/mmdetection/docs/zh_cn/index.rst @@ -0,0 +1,67 @@ +Welcome to MMDetection's documentation! +======================================= + +.. toctree:: + :maxdepth: 1 + :caption: 开始你的第一步 + + overview.md + get_started.md + +.. toctree:: + :maxdepth: 2 + :caption: 使用指南 + + user_guides/index.rst + +.. toctree:: + :maxdepth: 2 + :caption: 进阶教程 + + advanced_guides/index.rst + +.. toctree:: + :maxdepth: 1 + :caption: 迁移版本 + + migration/migration.md + +.. toctree:: + :maxdepth: 1 + :caption: 接口文档(英文) + + api.rst + +.. toctree:: + :maxdepth: 1 + :caption: 模型仓库 + + model_zoo.md + +.. toctree:: + :maxdepth: 1 + :caption: 说明 + + notes/contribution_guide.md + notes/projects.md + notes/faq.md + notes/compatibility.md + +.. toctree:: + :maxdepth: 1 + :caption: 文章 + + article.md + +.. toctree:: + :caption: 语言切换 + + switch_language.md + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/mmdetection/docs/zh_cn/make.bat b/mmdetection/docs/zh_cn/make.bat new file mode 100644 index 00000000..922152e9 --- /dev/null +++ b/mmdetection/docs/zh_cn/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/mmdetection/docs/zh_cn/migration/api_and_registry_migration.md b/mmdetection/docs/zh_cn/migration/api_and_registry_migration.md new file mode 100644 index 00000000..66e1c340 --- /dev/null +++ b/mmdetection/docs/zh_cn/migration/api_and_registry_migration.md @@ -0,0 +1 @@ +# 将 API 和注册器从 MMDetection 2.x 迁移至 3.x diff --git a/mmdetection/docs/zh_cn/migration/config_migration.md b/mmdetection/docs/zh_cn/migration/config_migration.md new file mode 100644 index 00000000..c4f9c8e3 --- /dev/null +++ b/mmdetection/docs/zh_cn/migration/config_migration.md @@ -0,0 +1,814 @@ +# 将配置文件从 MMDetection 2.x 迁移至 3.x + +MMDetection 3.x 的配置文件与 2.x 相比有较大变化,这篇文档将介绍如何将 2.x 的配置文件迁移到 3.x。 + +在前面的[配置文件教程](../user_guides/config.md)中,我们以 Mask R-CNN 为例介绍了 MMDetection 3.x 的配置文件结构,这里我们将按同样的结构介绍如何将 2.x 的配置文件迁移至 3.x。 + +## 模型配置 + +模型的配置与 2.x 相比并没有太大变化,对于模型的 backbone,neck,head,以及 train_cfg 和 test_cfg,它们的参数与 2.x 版本的参数保持一致。 + +不同的是,我们在 3.x 版本的模型中新增了 `DataPreprocessor` 模块。 +`DataPreprocessor` 模块的配置位于 `model.data_preprocessor` 中,它用于对输入数据进行预处理,例如对输入图像进行归一化,将不同大小的图片进行 padding 从而组成 batch,将图像从内存中读取到显存中等。这部分配置取代了原本存在于 train_pipeline 和 test_pipeline 中的 `Normalize` 和 `Pad`。 + + + + + + + + + +
    原配置 + +```python +# 图像归一化参数 +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + to_rgb=True) +pipeline=[ + ..., + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), # 图像 padding 到 32 的倍数 + ... +] +``` + +
    新配置 + +```python +model = dict( + data_preprocessor=dict( + type='DetDataPreprocessor', + # 图像归一化参数 + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + # 图像 padding 参数 + pad_mask=True, # 在实例分割中,需要将 mask 也进行 padding + pad_size_divisor=32) # 图像 padding 到 32 的倍数 +) +``` + +
    + +## 数据集和评测器配置 + +数据集和评测部分的配置相比 2.x 版本有较大的变化。我们将从 Dataloader 和 Dataset,Data transform pipeline,以及评测器配置三个方面介绍如何将 2.x 版本的配置迁移到 3.x 版本。 + +### Dataloader 和 Dataset 配置 + +在新版本中,我们将数据加载的设置与 PyTorch 官方的 DataLoader 保持一致,这样可以使用户更容易理解和上手。 +我们将训练、验证和测试的数据加载设置分别放在 `train_dataloader`,`val_dataloader` 和 `test_dataloader` 中,用户可以分别对这些 dataloader 设置不同的参数,其输入参数与 [PyTorch 的 Dataloader](https://pytorch.org/docs/stable/data.html?highlight=dataloader#torch.utils.data.DataLoader) 所需要的参数基本一致。 + +通过这种方式,我们将 2.x 版本中不可配置的 `sampler`,`batch_sampler`,`persistent_workers` 等参数都放到了配置文件中,使得用户可以更加灵活地设置数据加载的参数。 + +用户可以通过 `train_dataloader.dataset`,`val_dataloader.dataset` 和 `test_dataloader.dataset` 来设置数据集的配置,它们分别对应 2.x 版本中的 `data.train`,`data.val` 和 `data.test`。 + + + + + + + + + +
    原配置 + +```python +data = dict( + samples_per_gpu=2, + workers_per_gpu=2, + train=dict( + type=dataset_type, + ann_file=data_root + 'annotations/instances_train2017.json', + img_prefix=data_root + 'train2017/', + pipeline=train_pipeline), + val=dict( + type=dataset_type, + ann_file=data_root + 'annotations/instances_val2017.json', + img_prefix=data_root + 'val2017/', + pipeline=test_pipeline), + test=dict( + type=dataset_type, + ann_file=data_root + 'annotations/instances_val2017.json', + img_prefix=data_root + 'val2017/', + pipeline=test_pipeline)) +``` + +
    新配置 + +```python +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, # 避免每次迭代后 dataloader 重新创建子进程 + sampler=dict(type='DefaultSampler', shuffle=True), # 默认的 sampler,同时支持分布式训练和非分布式训练 + batch_sampler=dict(type='AspectRatioBatchSampler'), # 默认的 batch_sampler,用于保证 batch 中的图片具有相似的长宽比,从而可以更好地利用显存 + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline)) +# 在 3.x 版本中可以独立配置验证和测试的 dataloader +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader # 测试 dataloader 的配置与验证 dataloader 的配置相同,这里省略 +``` + +
    + +### Data transform pipeline 配置 + +上文中提到,我们将图像 normalize 和 padding 的配置从 `train_pipeline` 和 `test_pipeline` 中独立出来,放到了 `model.data_preprocessor` 中,因此在 3.x 版本的 pipeline 中,我们不再需要 `Normalize` 和 `Pad` 这两个 transform。 + +同时,我们也对负责数据格式打包的 transform 进行了重构,将 `Collect` 和 `DefaultFormatBundle` 这两个 transform 合并为了 `PackDetInputs`,它负责将 data pipeline 中的数据打包成模型的输入格式,关于输入格式的转换,详见[数据流文档](../advanced_guides/data_flow.md)。 + +下面以 Mask R-CNN 1x 的 train_pipeline 为例,介绍如何将 2.x 版本的配置迁移到 3.x 版本: + + + + + + + + + +
    原配置 + +```python +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', img_scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', flip_ratio=0.5), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='DefaultFormatBundle'), + dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']), +] +``` + +
    新配置 + +```python +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +``` + +
    + +对于 test_pipeline,除了将 `Normalize` 和 `Pad` 这两个 transform 去掉之外,我们也将测试时的数据增强(TTA)与普通的测试流程分开,移除了 `MultiScaleFlipAug`。关于新版的 TTA 如何使用,详见[TTA 文档](../advanced_guides/tta.md)。 + +下面同样以 Mask R-CNN 1x 的 test_pipeline 为例,介绍如何将 2.x 版本的配置迁移到 3.x 版本: + + + + + + + + + +
    原配置 + +```python +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(1333, 800), + flip=False, + transforms=[ + dict(type='Resize', keep_ratio=True), + dict(type='RandomFlip'), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size_divisor=32), + dict(type='ImageToTensor', keys=['img']), + dict(type='Collect', keys=['img']), + ]) +] +``` + +
    新配置 + +```python +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +``` + +
    + +除此之外,我们还对一些数据增强进行了重构,下表列出了 2.x 版本中的 transform 与 3.x 版本中的 transform 的对应关系: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    名称原配置新配置
    Resize + +```python +dict(type='Resize', + img_scale=(1333, 800), + keep_ratio=True) +``` + + + +```python +dict(type='Resize', + scale=(1333, 800), + keep_ratio=True) +``` + +
    RandomResize + +```python +dict( + type='Resize', + img_scale=[ + (1333, 640), (1333, 800)], + multiscale_mode='range', + keep_ratio=True) +``` + + + +```python +dict( + type='RandomResize', + scale=[ + (1333, 640), (1333, 800)], + keep_ratio=True) +``` + +
    RandomChoiceResize + +```python +dict( + type='Resize', + img_scale=[ + (1333, 640), (1333, 672), + (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + multiscale_mode='value', + keep_ratio=True) +``` + + + +```python +dict( + type='RandomChoiceResize', + scales=[ + (1333, 640), (1333, 672), + (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True) +``` + +
    RandomFlip + +```python +dict(type='RandomFlip', + flip_ratio=0.5) +``` + + + +```python +dict(type='RandomFlip', + prob=0.5) +``` + +
    + +### 评测器配置 + +在 3.x 版本中,模型精度评测不再与数据集绑定,而是通过评测器(Evaluator)来完成。 +评测器配置分为 val_evaluator 和 test_evaluator 两部分,其中 val_evaluator 用于验证集评测,test_evaluator 用于测试集评测,对应 2.x 版本中的 evaluation 字段。 +下表列出了 2.x 版本与 3.x 版本中的评测器的对应关系: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    评测指标名称原配置新配置
    COCO + +```python +data = dict( + val=dict( + type='CocoDataset', + ann_file=data_root + 'annotations/instances_val2017.json')) +evaluation = dict(metric=['bbox', 'segm']) +``` + + + +```python +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False) +``` + +
    Pascal VOC + +```python +data = dict( + val=dict( + type=dataset_type, + ann_file=data_root + 'VOC2007/ImageSets/Main/test.txt')) +evaluation = dict(metric='mAP') +``` + + + +```python +val_evaluator = dict( + type='VOCMetric', + metric='mAP', + eval_mode='11points') +``` + +
    OpenImages + +```python +data = dict( + val=dict( + type='OpenImagesDataset', + ann_file=data_root + 'annotations/validation-annotations-bbox.csv', + img_prefix=data_root + 'OpenImages/validation/', + label_file=data_root + 'annotations/class-descriptions-boxable.csv', + hierarchy_file=data_root + + 'annotations/bbox_labels_600_hierarchy.json', + meta_file=data_root + 'annotations/validation-image-metas.pkl', + image_level_ann_file=data_root + + 'annotations/validation-annotations-human-imagelabels-boxable.csv')) +evaluation = dict(interval=1, metric='mAP') +``` + + + +```python +val_evaluator = dict( + type='OpenImagesMetric', + iou_thrs=0.5, + ioa_thrs=0.5, + use_group_of=True, + get_supercategory=True) +``` + +
    CityScapes + +```python +data = dict( + val=dict( + type='CityScapesDataset', + ann_file=data_root + + 'annotations/instancesonly_filtered_gtFine_val.json', + img_prefix=data_root + 'leftImg8bit/val/', + pipeline=test_pipeline)) +evaluation = dict(metric=['bbox', 'segm']) +``` + + + +```python +val_evaluator = [ + dict( + type='CocoMetric', + ann_file=data_root + + 'annotations/instancesonly_filtered_gtFine_val.json', + metric=['bbox', 'segm']), + dict( + type='CityScapesMetric', + ann_file=data_root + + 'annotations/instancesonly_filtered_gtFine_val.json', + seg_prefix=data_root + '/gtFine/val', + outfile_prefix='./work_dirs/cityscapes_metric/instance') +] +``` + +
    + +## 训练和测试的配置 + + + + + + + + + +
    原配置 + +```python +runner = dict( + type='EpochBasedRunner', # 训练循环的类型 + max_epochs=12) # 最大训练轮次 +evaluation = dict(interval=2) # 验证间隔。每 2 个 epoch 验证一次 +``` + +
    新配置 + +```python +train_cfg = dict( + type='EpochBasedTrainLoop', # 训练循环的类型,请参考 https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py + max_epochs=12, # 最大训练轮次 + val_interval=2) # 验证间隔。每 2 个 epoch 验证一次 +val_cfg = dict(type='ValLoop') # 验证循环的类型 +test_cfg = dict(type='TestLoop') # 测试循环的类型 +``` + +
    + +## 优化相关配置 + +优化器以及梯度裁剪的配置都移至 optim_wrapper 字段中。下表列出了 2.x 版本与 3.x 版本中的优化器配置的对应关系: + + + + + + + + + +
    原配置 + +```python +optimizer = dict( + type='SGD', # 随机梯度下降优化器 + lr=0.02, # 基础学习率 + momentum=0.9, # 带动量的随机梯度下降 + weight_decay=0.0001) # 权重衰减 +optimizer_config = dict(grad_clip=None) # 梯度裁剪的配置,设置为 None 关闭梯度裁剪 +``` + +
    新配置 + +```python +optim_wrapper = dict( # 优化器封装的配置 + type='OptimWrapper', # 优化器封装的类型。可以切换至 AmpOptimWrapper 来启用混合精度训练 + optimizer=dict( # 优化器配置。支持 PyTorch 的各种优化器。请参考 https://pytorch.org/docs/stable/optim.html#algorithms + type='SGD', # 随机梯度下降优化器 + lr=0.02, # 基础学习率 + momentum=0.9, # 带动量的随机梯度下降 + weight_decay=0.0001), # 权重衰减 + clip_grad=None, # 梯度裁剪的配置,设置为 None 关闭梯度裁剪。使用方法请见 https://mmengine.readthedocs.io/en/latest/tutorials/optimizer.html + ) +``` + +
    + +学习率的配置也从 lr_config 字段中移至 param_scheduler 字段中。param_scheduler 的配置更贴近 PyTorch 的学习率调整策略,更加灵活。下表列出了 2.x 版本与 3.x 版本中的学习率配置的对应关系: + + + + + + + + + +
    原配置 + +```python +lr_config = dict( + policy='step', # 在训练过程中使用 multi step 学习率策略 + warmup='linear', # 使用线性学习率预热 + warmup_iters=500, # 到第 500 个 iteration 结束预热 + warmup_ratio=0.001, # 学习率预热的系数 + step=[8, 11], # 在哪几个 epoch 进行学习率衰减 + gamma=0.1) # 学习率衰减系数 +``` + +
    新配置 + +```python +param_scheduler = [ + dict( + type='LinearLR', # 使用线性学习率预热 + start_factor=0.001, # 学习率预热的系数 + by_epoch=False, # 按 iteration 更新预热学习率 + begin=0, # 从第一个 iteration 开始 + end=500), # 到第 500 个 iteration 结束 + dict( + type='MultiStepLR', # 在训练过程中使用 multi step 学习率策略 + by_epoch=True, # 按 epoch 更新学习率 + begin=0, # 从第一个 epoch 开始 + end=12, # 到第 12 个 epoch 结束 + milestones=[8, 11], # 在哪几个 epoch 进行学习率衰减 + gamma=0.1) # 学习率衰减系数 +] +``` + +
    + +关于其他的学习率调整策略的迁移,请参考 MMEngine 的[学习率迁移文档](https://mmengine.readthedocs.io/zh_CN/latest/migration/param_scheduler.html)。 + +## 其他配置的迁移 + +### 保存 checkpoint 的配置 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    功能原配置新配置
    设置保存间隔 + +```python +checkpoint_config = dict( + interval=1) +``` + + + +```python +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + interval=1)) +``` + +
    保存最佳模型 + +```python +evaluation = dict( + save_best='auto') +``` + + + +```python +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + save_best='auto')) +``` + +
    只保留最新的几个模型 + +```python +checkpoint_config = dict( + max_keep_ckpts=3) +``` + + + +```python +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + max_keep_ckpts=3)) +``` + +
    + +### 日志的配置 + +3.x 版本中,日志的打印和可视化由 MMEngine 中的 logger 和 visualizer 分别完成。下表列出了 2.x 版本与 3.x 版本中的日志配置的对应关系: + + + + + + + + + + + + + + + + + + + + + + + + +
    功能原配置新配置
    设置日志打印间隔 + +```python +log_config = dict( + interval=50) +``` + + + +```python +default_hooks = dict( + logger=dict( + type='LoggerHook', + interval=50)) +# 可选: 配置日志打印数值的平滑窗口大小 +log_processor = dict( + type='LogProcessor', + window_size=50) +``` + +
    使用 TensorBoard 或 WandB 可视化日志 + +```python +log_config = dict( + interval=50, + hooks=[ + dict(type='TextLoggerHook'), + dict(type='TensorboardLoggerHook'), + dict(type='MMDetWandbHook', + init_kwargs={ + 'project': 'mmdetection', + 'group': 'maskrcnn-r50-fpn-1x-coco' + }, + interval=50, + log_checkpoint=True, + log_checkpoint_metadata=True, + num_eval_images=100) + ]) +``` + + + +```python +vis_backends = [ + dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend'), + dict(type='WandbVisBackend', + init_kwargs={ + 'project': 'mmdetection', + 'group': 'maskrcnn-r50-fpn-1x-coco' + }) +] +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') +``` + +
    + +关于可视化相关的教程,请参考 MMDetection 的[可视化教程](../user_guides/visualization.md)。 + +### Runtime 的配置 + +3.x 版本中 runtime 的配置字段有所调整,具体的对应关系如下: + + + + + + + + + + + + + + + + +
    原配置新配置
    + +```python +cudnn_benchmark = False +opencv_num_threads = 0 +mp_start_method = 'fork' +dist_params = dict(backend='nccl') +log_level = 'INFO' +load_from = None +resume_from = None + + +``` + + + +```python +env_cfg = dict( + cudnn_benchmark=False, + mp_cfg=dict(mp_start_method='fork', + opencv_num_threads=0), + dist_cfg=dict(backend='nccl')) +log_level = 'INFO' +load_from = None +resume = False +``` + +
    diff --git a/mmdetection/docs/zh_cn/migration/dataset_migration.md b/mmdetection/docs/zh_cn/migration/dataset_migration.md new file mode 100644 index 00000000..c379b9f1 --- /dev/null +++ b/mmdetection/docs/zh_cn/migration/dataset_migration.md @@ -0,0 +1 @@ +# 将数据集从 MMDetection 2.x 迁移至 3.x diff --git a/mmdetection/docs/zh_cn/migration/migration.md b/mmdetection/docs/zh_cn/migration/migration.md new file mode 100644 index 00000000..d706856f --- /dev/null +++ b/mmdetection/docs/zh_cn/migration/migration.md @@ -0,0 +1,12 @@ +# 从 MMDetection 2.x 迁移至 3.x + +MMDetection 3.x 版本是一个重大更新,包含了许多 API 和配置文件的变化。本文档旨在帮助用户从 MMDetection 2.x 版本迁移到 3.x 版本。 +我们将迁移指南分为以下几个部分: + +- [配置文件迁移](./config_migration.md) +- [API 和 Registry 迁移](./api_and_registry_migration.md) +- [数据集迁移](./dataset_migration.md) +- [模型迁移](./model_migration.md) +- [常见问题](./migration_faq.md) + +如果您在迁移过程中遇到任何问题,欢迎在 issue 中提出。我们也欢迎您为本文档做出贡献。 diff --git a/mmdetection/docs/zh_cn/migration/migration_faq.md b/mmdetection/docs/zh_cn/migration/migration_faq.md new file mode 100644 index 00000000..208a138b --- /dev/null +++ b/mmdetection/docs/zh_cn/migration/migration_faq.md @@ -0,0 +1 @@ +# 迁移 FAQ diff --git a/mmdetection/docs/zh_cn/migration/model_migration.md b/mmdetection/docs/zh_cn/migration/model_migration.md new file mode 100644 index 00000000..d7992440 --- /dev/null +++ b/mmdetection/docs/zh_cn/migration/model_migration.md @@ -0,0 +1 @@ +# 将模型从 MMDetection 2.x 迁移至 3.x diff --git a/mmdetection/docs/zh_cn/model_zoo.md b/mmdetection/docs/zh_cn/model_zoo.md new file mode 100644 index 00000000..b5376152 --- /dev/null +++ b/mmdetection/docs/zh_cn/model_zoo.md @@ -0,0 +1,333 @@ +# 模型库 + +## 镜像地址 + +从 MMDetection V2.0 起,我们只通过阿里云维护模型库。V1.x 版本的模型已经弃用。 + +## 共同设置 + +- 所有模型都是在 `coco_2017_train` 上训练,在 `coco_2017_val` 上测试。 +- 我们使用分布式训练。 +- 所有 pytorch-style 的 ImageNet 预训练主干网络来自 PyTorch 的模型库,caffe-style 的预训练主干网络来自 detectron2 最新开源的模型。 +- 为了与其他代码库公平比较,文档中所写的 GPU 内存是8个 GPU 的 `torch.cuda.max_memory_allocated()` 的最大值,此值通常小于 nvidia-smi 显示的值。 +- 我们以网络 forward 和后处理的时间加和作为推理时间,不包含数据加载时间。所有结果通过 [benchmark.py](https://github.com/open-mmlab/mmdetection/blob/main/tools/analysis_tools/benchmark.py) 脚本计算所得。该脚本会计算推理 2000 张图像的平均时间。 + +## ImageNet 预训练模型 + +通过 ImageNet 分类任务预训练的主干网络进行初始化是很常见的操作。所有预训练模型的链接都可以在 [open_mmlab](https://github.com/open-mmlab/mmcv/blob/master/mmcv/model_zoo/open_mmlab.json) 中找到。根据 `img_norm_cfg` 和原始权重,我们可以将所有 ImageNet 预训练模型分为以下几种情况: + +- TorchVision:torchvision 模型权重,包含 ResNet50, ResNet101。`img_norm_cfg` 为 `dict(mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)`。 +- Pycls:[pycls](https://github.com/facebookresearch/pycls) 模型权重,包含 RegNetX。`img_norm_cfg` 为 `dict( mean=[103.530, 116.280, 123.675], std=[57.375, 57.12, 58.395], to_rgb=False)`。 +- MSRA styles:[MSRA](https://github.com/KaimingHe/deep-residual-networks) 模型权重,包含 ResNet50_Caffe,ResNet101_Caffe。`img_norm_cfg` 为 `dict( mean=[103.530, 116.280, 123.675], std=[1.0, 1.0, 1.0], to_rgb=False)`。 +- Caffe2 styles:现阶段只包含 ResNext101_32x8d。`img_norm_cfg` 为 `dict(mean=[103.530, 116.280, 123.675], std=[57.375, 57.120, 58.395], to_rgb=False)`。 +- Other styles: SSD 的 `img_norm_cfg` 为 `dict(mean=[123.675, 116.28, 103.53], std=[1, 1, 1], to_rgb=True)`,YOLOv3 的 `img_norm_cfg` 为 `dict(mean=[0, 0, 0], std=[255., 255., 255.], to_rgb=True)`。 + +MMdetection 常用到的主干网络细节如下表所示: + +| 模型 | 来源 | 链接 | 描述 | +| ---------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ResNet50 | TorchVision | [torchvision 中的 ResNet-50](https://download.pytorch.org/models/resnet50-19c8e357.pth) | 来自 [torchvision 中的 ResNet-50](https://download.pytorch.org/models/resnet50-19c8e357.pth)。 | +| ResNet101 | TorchVision | [torchvision 中的 ResNet-101](https://download.pytorch.org/models/resnet101-5d3b4d8f.pth) | 来自 [torchvision 中的 ResNet-101](https://download.pytorch.org/models/resnet101-5d3b4d8f.pth)。 | +| RegNetX | Pycls | [RegNetX_3.2gf](https://download.openmmlab.com/pretrain/third_party/regnetx_3.2gf-c2599b0f.pth),[RegNetX_800mf](https://download.openmmlab.com/pretrain/third_party/regnetx_800mf-1f4be4c7.pth) 等 | 来自 [pycls](https://github.com/facebookresearch/pycls)。 | +| ResNet50_Caffe | MSRA | [MSRA 中的 ResNet-50](https://download.openmmlab.com/pretrain/third_party/resnet50_caffe-788b5fa3.pth) | 由 [Detectron2 中的 R-50.pkl](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/MSRA/R-50.pkl) 转化的副本。原始权重文件来自 [MSRA 中的原始 ResNet-50](https://github.com/KaimingHe/deep-residual-networks)。 | +| ResNet101_Caffe | MSRA | [MSRA 中的 ResNet-101](https://download.openmmlab.com/pretrain/third_party/resnet101_caffe-3ad79236.pth) | 由 [Detectron2 中的 R-101.pkl](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/MSRA/R-101.pkl) 转化的副本。原始权重文件来自 [MSRA 中的原始 ResNet-101](https://github.com/KaimingHe/deep-residual-networks)。 | +| ResNext101_32x8d | Caffe2 | [Caffe2 ResNext101_32x8d](https://download.openmmlab.com/pretrain/third_party/resnext101_32x8d-1516f1aa.pth) | 由 [Detectron2 中的 X-101-32x8d.pkl](https://dl.fbaipublicfiles.com/detectron2/ImageNetPretrained/FAIR/X-101-32x8d.pkl) 转化的副本。原始 ResNeXt-101-32x8d 由 FB 使用 Caffe2 训练。 | + +## Baselines + +### RPN + +请参考 [RPN](https://github.com/open-mmlab/mmdetection/blob/main/configs/rpn)。 + +### Faster R-CNN + +请参考 [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/faster_rcnn)。 + +### Mask R-CNN + +请参考 [Mask R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/mask_rcnn)。 + +### Fast R-CNN (使用提前计算的 proposals) + +请参考 [Fast R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/fast_rcnn)。 + +### RetinaNet + +请参考 [RetinaNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/retinanet)。 + +### Cascade R-CNN and Cascade Mask R-CNN + +请参考 [Cascade R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/cascade_rcnn)。 + +### Hybrid Task Cascade (HTC) + +请参考 [HTC](https://github.com/open-mmlab/mmdetection/blob/main/configs/htc)。 + +### SSD + +请参考 [SSD](https://github.com/open-mmlab/mmdetection/blob/main/configs/ssd)。 + +### Group Normalization (GN) + +请参考 [Group Normalization](https://github.com/open-mmlab/mmdetection/blob/main/configs/gn)。 + +### Weight Standardization + +请参考 [Weight Standardization](https://github.com/open-mmlab/mmdetection/blob/main/configs/gn+ws)。 + +### Deformable Convolution v2 + +请参考 [Deformable Convolutional Networks](https://github.com/open-mmlab/mmdetection/blob/main/configs/dcn)。 + +### CARAFE: Content-Aware ReAssembly of FEatures + +请参考 [CARAFE](https://github.com/open-mmlab/mmdetection/blob/main/configs/carafe)。 + +### Instaboost + +请参考 [Instaboost](https://github.com/open-mmlab/mmdetection/blob/main/configs/instaboost)。 + +### Libra R-CNN + +请参考 [Libra R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/libra_rcnn)。 + +### Guided Anchoring + +请参考 [Guided Anchoring](https://github.com/open-mmlab/mmdetection/blob/main/configs/guided_anchoring)。 + +### FCOS + +请参考 [FCOS](https://github.com/open-mmlab/mmdetection/blob/main/configs/fcos)。 + +### FoveaBox + +请参考 [FoveaBox](https://github.com/open-mmlab/mmdetection/blob/main/configs/foveabox)。 + +### RepPoints + +请参考 [RepPoints](https://github.com/open-mmlab/mmdetection/blob/main/configs/reppoints)。 + +### FreeAnchor + +请参考 [FreeAnchor](https://github.com/open-mmlab/mmdetection/blob/main/configs/free_anchor)。 + +### Grid R-CNN (plus) + +请参考 [Grid R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/grid_rcnn)。 + +### GHM + +请参考 [GHM](https://github.com/open-mmlab/mmdetection/blob/main/configs/ghm)。 + +### GCNet + +请参考 [GCNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/gcnet)。 + +### HRNet + +请参考 [HRNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/hrnet)。 + +### Mask Scoring R-CNN + +请参考 [Mask Scoring R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/ms_rcnn)。 + +### Train from Scratch + +请参考 [Rethinking ImageNet Pre-training](https://github.com/open-mmlab/mmdetection/blob/main/configs/scratch)。 + +### NAS-FPN + +请参考 [NAS-FPN](https://github.com/open-mmlab/mmdetection/blob/main/configs/nas_fpn)。 + +### ATSS + +请参考 [ATSS](https://github.com/open-mmlab/mmdetection/blob/main/configs/atss)。 + +### FSAF + +请参考 [FSAF](https://github.com/open-mmlab/mmdetection/blob/main/configs/fsaf)。 + +### RegNetX + +请参考 [RegNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/regnet)。 + +### Res2Net + +请参考 [Res2Net](https://github.com/open-mmlab/mmdetection/blob/main/configs/res2net)。 + +### GRoIE + +请参考 [GRoIE](https://github.com/open-mmlab/mmdetection/blob/main/configs/groie)。 + +### Dynamic R-CNN + +请参考 [Dynamic R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/dynamic_rcnn)。 + +### PointRend + +请参考 [PointRend](https://github.com/open-mmlab/mmdetection/blob/main/configs/point_rend)。 + +### DetectoRS + +请参考 [DetectoRS](https://github.com/open-mmlab/mmdetection/blob/main/configs/detectors)。 + +### Generalized Focal Loss + +请参考 [Generalized Focal Loss](https://github.com/open-mmlab/mmdetection/blob/main/configs/gfl)。 + +### CornerNet + +请参考 [CornerNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/cornernet)。 + +### YOLOv3 + +请参考 [YOLOv3](https://github.com/open-mmlab/mmdetection/blob/main/configs/yolo)。 + +### PAA + +请参考 [PAA](https://github.com/open-mmlab/mmdetection/blob/main/configs/paa)。 + +### SABL + +请参考 [SABL](https://github.com/open-mmlab/mmdetection/blob/main/configs/sabl)。 + +### CentripetalNet + +请参考 [CentripetalNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/centripetalnet)。 + +### ResNeSt + +请参考 [ResNeSt](https://github.com/open-mmlab/mmdetection/blob/main/configs/resnest)。 + +### DETR + +请参考 [DETR](https://github.com/open-mmlab/mmdetection/blob/main/configs/detr)。 + +### Deformable DETR + +请参考 [Deformable DETR](https://github.com/open-mmlab/mmdetection/blob/main/configs/deformable_detr)。 + +### AutoAssign + +请参考 [AutoAssign](https://github.com/open-mmlab/mmdetection/blob/main/configs/autoassign)。 + +### YOLOF + +请参考 [YOLOF](https://github.com/open-mmlab/mmdetection/blob/main/configs/yolof)。 + +### Seesaw Loss + +请参考 [Seesaw Loss](https://github.com/open-mmlab/mmdetection/blob/main/configs/seesaw_loss)。 + +### CenterNet + +请参考 [CenterNet](https://github.com/open-mmlab/mmdetection/blob/main/configs/centernet)。 + +### YOLOX + +请参考 [YOLOX](https://github.com/open-mmlab/mmdetection/blob/main/configs/yolox)。 + +### PVT + +请参考 [PVT](https://github.com/open-mmlab/mmdetection/blob/main/configs/pvt)。 + +### SOLO + +请参考 [SOLO](https://github.com/open-mmlab/mmdetection/blob/main/configs/solo)。 + +### QueryInst + +请参考 [QueryInst](https://github.com/open-mmlab/mmdetection/blob/main/configs/queryinst)。 + +### Other datasets + +我们还在 [PASCAL VOC](https://github.com/open-mmlab/mmdetection/blob/main/configs/pascal_voc),[Cityscapes](https://github.com/open-mmlab/mmdetection/blob/main/configs/cityscapes) 和 [WIDER FACE](https://github.com/open-mmlab/mmdetection/blob/main/configs/wider_face) 上对一些方法进行了基准测试。 + +### Pre-trained Models + +我们还通过多尺度训练和更长的训练策略来训练用 ResNet-50 和 [RegNetX-3.2G](https://github.com/open-mmlab/mmdetection/blob/main/configs/regnet) 作为主干网络的 [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/faster_rcnn) 和 [Mask R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/mask_rcnn)。这些模型可以作为下游任务的预训练模型。 + +## 速度基准 + +### 训练速度基准 + +我们提供 [analyze_logs.py](https://github.com/open-mmlab/mmdetection/blob/main/tools/analysis_tools/analyze_logs.py) 来得到训练中每一次迭代的平均时间。示例请参考 [Log Analysis](https://mmdetection.readthedocs.io/en/latest/useful_tools.html#log-analysis)。 + +我们与其他流行框架的 Mask R-CNN 训练速度进行比较(数据是从 [detectron2](https://github.com/facebookresearch/detectron2/blob/main/docs/notes/benchmarks.md/) 复制而来)。在 mmdetection 中,我们使用 [mask-rcnn_r50-caffe_fpn_poly-1x_coco_v1.py](https://github.com/open-mmlab/mmdetection/blob/main/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_poly-1x_coco_v1.py) 进行基准测试。它与 detectron2 的 [mask_rcnn_R_50_FPN_noaug_1x.yaml](https://github.com/facebookresearch/detectron2/blob/main/configs/Detectron1-Comparisons/mask_rcnn_R_50_FPN_noaug_1x.yaml) 设置完全一样。同时,我们还提供了[模型权重](https://download.openmmlab.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug_compare_20200518-10127928.pth)和[训练 log](https://download.openmmlab.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug/mask_rcnn_r50_caffe_fpn_poly_1x_coco_no_aug_20200518_105755.log.json) 作为参考。为了跳过 GPU 预热时间,吞吐量按照100-500次迭代之间的平均吞吐量来计算。 + +| 框架 | 吞吐量 (img/s) | +| -------------------------------------------------------------------------------------- | -------------- | +| [Detectron2](https://github.com/facebookresearch/detectron2) | 62 | +| [MMDetection](https://github.com/open-mmlab/mmdetection) | 61 | +| [maskrcnn-benchmark](https://github.com/facebookresearch/maskrcnn-benchmark/) | 53 | +| [tensorpack](https://github.com/tensorpack/tensorpack/tree/master/examples/FasterRCNN) | 50 | +| [simpledet](https://github.com/TuSimple/simpledet/) | 39 | +| [Detectron](https://github.com/facebookresearch/Detectron) | 19 | +| [matterport/Mask_RCNN](https://github.com/matterport/Mask_RCNN/) | 14 | + +### 推理时间基准 + +我们提供 [benchmark.py](https://github.com/open-mmlab/mmdetection/blob/main/tools/analysis_tools/benchmark.py) 对推理时间进行基准测试。此脚本将推理 2000 张图片并计算忽略前 5 次推理的平均推理时间。可以通过设置 `LOG-INTERVAL` 来改变 log 输出间隔(默认为 50)。 + +```shell +python tools/benchmark.py ${CONFIG} ${CHECKPOINT} [--log-interval $[LOG-INTERVAL]] [--fuse-conv-bn] +``` + +模型库中,所有模型在基准测量推理时间时都没设置 `fuse-conv-bn`, 此设置可以使推理时间更短。 + +## 与 Detectron2 对比 + +我们在速度和精度方面对 mmdetection 和 [Detectron2](https://github.com/facebookresearch/detectron2.git) 进行对比。对比所使用的 detectron2 的 commit id 为 [185c27e](https://github.com/facebookresearch/detectron2/tree/185c27e4b4d2d4c68b5627b3765420c6d7f5a659)(30/4/2020)。 +为了公平对比,我们所有的实验都在同一机器下进行。 + +### 硬件 + +- 8 NVIDIA Tesla V100 (32G) GPUs +- Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz + +### 软件环境 + +- Python 3.7 +- PyTorch 1.4 +- CUDA 10.1 +- CUDNN 7.6.03 +- NCCL 2.4.08 + +### 精度 + +| 模型 | 训练策略 | Detectron2 | mmdetection | 下载 | +| ------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_ms-1x_coco.py) | 1x | [37.9](https://github.com/facebookresearch/detectron2/blob/main/configs/COCO-Detection/faster_rcnn_R_50_FPN_1x.yaml) | 38.0 | [model](https://download.openmmlab.com/mmdetection/v2.0/benchmark/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco-5324cff8.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/benchmark/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco/faster_rcnn_r50_caffe_fpn_mstrain_1x_coco_20200429_234554.log.json) | +| [Mask R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py) | 1x | [38.6 & 35.2](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml) | 38.8 & 35.4 | [model](https://download.openmmlab.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco-dbecf295.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/benchmark/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_1x_coco_20200430_054239.log.json) | +| [Retinanet](https://github.com/open-mmlab/mmdetection/blob/main/configs/retinanet/retinanet_r50-caffe_fpn_ms-1x_coco.py) | 1x | [36.5](https://github.com/facebookresearch/detectron2/blob/master/configs/COCO-Detection/retinanet_R_50_FPN_1x.yaml) | 37.0 | [model](https://download.openmmlab.com/mmdetection/v2.0/benchmark/retinanet_r50_caffe_fpn_mstrain_1x_coco/retinanet_r50_caffe_fpn_mstrain_1x_coco-586977a0.pth) \| [log](https://download.openmmlab.com/mmdetection/v2.0/benchmark/retinanet_r50_caffe_fpn_mstrain_1x_coco/retinanet_r50_caffe_fpn_mstrain_1x_coco_20200430_014748.log.json) | + +### 训练速度 + +训练速度使用 s/iter 来度量。结果越低越好。 + +| 模型 | Detectron2 | mmdetection | +| ------------ | ---------- | ----------- | +| Faster R-CNN | 0.210 | 0.216 | +| Mask R-CNN | 0.261 | 0.265 | +| Retinanet | 0.200 | 0.205 | + +### 推理速度 + +推理速度通过单张 GPU 下的 fps(img/s) 来度量,越高越好。 +为了与 Detectron2 保持一致,我们所写的推理时间除去了数据加载时间。 +对于 Mask RCNN,我们去除了后处理中 RLE 编码的时间。 +我们在括号中给出了官方给出的速度。由于硬件差异,官方给出的速度会比我们所测试得到的速度快一些。 + +| 模型 | Detectron2 | mmdetection | +| ------------ | ----------- | ----------- | +| Faster R-CNN | 25.6 (26.3) | 22.2 | +| Mask R-CNN | 22.5 (23.3) | 19.6 | +| Retinanet | 17.8 (18.2) | 20.6 | + +### 训练内存 + +| 模型 | Detectron2 | mmdetection | +| ------------ | ---------- | ----------- | +| Faster R-CNN | 3.0 | 3.8 | +| Mask R-CNN | 3.4 | 3.9 | +| Retinanet | 3.9 | 3.4 | diff --git a/mmdetection/docs/zh_cn/notes/compatibility.md b/mmdetection/docs/zh_cn/notes/compatibility.md new file mode 100644 index 00000000..e9ebdd97 --- /dev/null +++ b/mmdetection/docs/zh_cn/notes/compatibility.md @@ -0,0 +1,177 @@ +# MMDetection v2.x 兼容性说明 + +## MMDetection 2.25.0 + +为了加入 Mask2Former 实例分割模型,对 Mask2Former 的配置文件进行了重命名 [PR #7571](https://github.com/open-mmlab/mmdetection/pull/7571): + + + + + + + + + + + +
    在 v2.25.0 之前v2.25.0 及之后
    + +``` +'mask2former_xxx_coco.py' 代表全景分割的配置文件 +``` + + + +``` +'mask2former_xxx_coco.py' 代表实例分割的配置文件 +'mask2former_xxx_coco-panoptic.py' 代表全景分割的配置文件 +``` + +
    + +## MMDetection 2.21.0 + +为了支持 CPU 训练,MMCV 中进行批处理的 scatter 的代码逻辑已经被修改。我们推荐使用 MMCV v1.4.4 或更高版本, +更多信息请参考 [MMCV PR #1621](https://github.com/open-mmlab/mmcv/pull/1621). + +## MMDetection 2.18.1 + +### MMCV compatibility + +为了修复 BaseTransformerLayer 中的权重引用问题, MultiheadAttention 中 batch first 的逻辑有所改变。 +我们推荐使用 MMCV v1.3.17 或更高版本。 更多信息请参考 [MMCV PR #1418](https://github.com/open-mmlab/mmcv/pull/1418) 。 + +## MMDetection 2.18.0 + +### DIIHead 兼容性 + +为了支持 QueryInst,在 DIIHead 的返回元组中加入了 attn_feats。 + +## MMDetection v2.14.0 + +### MMCV 版本 + +为了修复 EvalHook 优先级过低的问题,MMCV v1.3.8 中所有 hook 的优先级都重新进行了调整,因此 MMDetection v2.14.0 需要依赖最新的 MMCV v1.3.8 版本。 相关信息请参考[PR #1120](https://github.com/open-mmlab/mmcv/pull/1120) ,相关问题请参考[#5343](https://github.com/open-mmlab/mmdetection/issues/5343) 。 + +### SSD 兼容性 + +在 v2.14.0 中,为了使 SSD 能够被更灵活地使用,[PR #5291](https://github.com/open-mmlab/mmdetection/pull/5291) 重构了 SSD 的 backbone、neck 和 head。用户可以使用 tools/model_converters/upgrade_ssd_version.py 转换旧版本训练的模型。 + +```shell +python tools/model_converters/upgrade_ssd_version.py ${OLD_MODEL_PATH} ${NEW_MODEL_PATH} + +``` + +- OLD_MODEL_PATH:旧版 SSD 模型的路径。 +- NEW_MODEL_PATH:保存转换后模型权重的路径。 + +## MMDetection v2.12.0 + +在 v2.12.0 到 v2.18.0(或以上)版本的这段时间,为了提升通用性和便捷性,MMDetection 正在进行大规模重构。在升级到 v2.12.0 后 MMDetection 不可避免地带来了一些 BC Breaking,包括 MMCV 的版本依赖、模型初始化方式、模型 registry 和 mask AP 的评估。 + +### MMCV 版本 + +MMDetection v2.12.0 依赖 MMCV v1.3.3 中新增加的功能,包括:使用 `BaseModule` 统一参数初始化,模型 registry,以及[Deformable DETR](https://arxiv.org/abs/2010.04159) 中的 `MultiScaleDeformableAttn` CUDA 算子。 +注意,尽管 MMCV v1.3.2 已经包含了 MMDet 所需的功能,但是存在一些已知的问题。我们建议用户跳过 MMCV v1.3.2 使用 v1.3.3 版本。 + +### 统一模型初始化 + +为了统一 OpenMMLab 项目中的参数初始化方式,MMCV 新增加了 `BaseModule` 类,使用 `init_cfg` 参数对模块进行统一且灵活的初始化配置管理。 +现在用户需要在训练脚本中显式调用 `model.init_weights()` 来初始化模型(例如 [这行代码](https://github.com/open-mmlab/mmdetection/blob/master/tools/train.py#L162) ,在这之前则是在 detector 中进行处理的。 +**下游项目必须相应地更新模型初始化方式才能使用 MMDetection v2.12.0**。请参阅 [PR #4750](https://github.com/open-mmlab/mmdetection/pull/4750) 了解详情。 + +### 统一模型 registry + +为了能够使用在其他 OpenMMLab 项目中实现的 backbone,MMDetection v2.12.0 继承了在 MMCV (#760) 中创建的模型 registry。 +这样,只要 OpenMMLab 项目实现了某个 backbone,并且该项目也使用 MMCV 中的 registry,那么用户只需修改配置即可在 MMDetection 中使用该 backbone,不再需要将代码复制到 MMDetection 中。 更多详细信息,请参阅 [PR #5059](https://github.com/open-mmlab/mmdetection/pull/5059) 。 + +### Mask AP 评估 + +在 [PR #4898](https://github.com/open-mmlab/mmdetection/pull/4898) 和 v2.12.0 之前,对小、中、大目标的 mask AP 的评估是基于其边界框区域而不是真正的 mask 区域。 +这导致 `APs` 和 `APm` 变得更高但 `APl` 变得更低,但是不会影响整体的 mask AP。 [PR #4898](https://github.com/open-mmlab/mmdetection/pull/4898) 删除了 mask AP 计算中的 `bbox` ,改为使用 mask 区域。 +新的计算方式不会影响整体的 mask AP 评估,与 [Detectron2](https://github.com/facebookresearch/detectron2/)一致。 + +## 与 MMDetection v1.x 的兼容性 + +MMDetection v2.0 经过了大规模重构并解决了许多遗留问题。 MMDetection v2.0 不兼容 v1.x 版本,在这两个版本中使用相同的模型权重运行推理会产生不同的结果。 因此,MMDetection v2.0 重新对所有模型进行了 benchmark,并在 model zoo 中提供了新模型的权重和训练记录。 + +新旧版本的主要的区别有四方面:坐标系、代码库约定、训练超参和模块设计。 + +### 坐标系 + +新坐标系与 [Detectron2](https://github.com/facebookresearch/detectron2/) 一致, +将最左上角的像素的中心视为坐标原点 (0, 0) 而不是最左上角像素的左上角。 因此 COCO 边界框和分割标注中的坐标被解析为范围 `[0,width]` 和 `[0,height]` 中的坐标。 这个修改影响了所有与 bbox 及像素选择相关的计算,变得更加自然且更加准确。 + +- 在新坐标系中,左上角和右下角为 (x1, y1) (x2, y2) 的框的宽度及高度计算公式为 `width = x2 - x1` 和 `height = y2 - y1`。 + 在 MMDetection v1.x 和之前的版本中,高度和宽度都多了 `+ 1` 的操作。 + 本次修改包括三部分: + + 1. box 回归中的检测框变换以及编码/解码。 + 2. IoU 计算。这会影响 ground truth 和检测框之间的匹配以及 NMS 。但对兼容性的影响可以忽略不计。 + 3. Box 的角点坐标为浮点型,不再取整。这能使得检测结果更为准确,也使得检测框和 RoI 的最小尺寸不再为 1,但影响很小。 + +- Anchor 的中心与特征图的网格点对齐,类型变为 float。 + 在 MMDetection v1.x 和之前的版本中,anchors 是 `int` 类型且没有居中对齐。 + 这会影响 RPN 中的 Anchor 生成和所有基于 Anchor 的方法。 + +- ROIAlign 更好地与图像坐标系对齐。新的实现来自 [Detectron2](https://github.com/facebookresearch/detectron2/tree/master/detectron2/layers/csrc/ROIAlign) 。 + 当 RoI 用于提取 RoI 特征时,与 MMDetection v1.x 相比默认情况下相差半个像素。 + 能够通过设置 `aligned=False` 而不是 `aligned=True` 来维持旧版本的设置。 + +- Mask 的裁剪和粘贴更准确。 + + 1. 我们使用新的 RoIAlign 来提取 mask 目标。 在 MMDetection v1.x 中,bounding box 在提取 mask 目标之前被取整,裁剪过程是 numpy 实现的。 而在新版本中,裁剪的边界框不经过取整直接输入 RoIAlign。 此实现大大加快了训练速度(每次迭代约加速 0.1 秒,1x schedule 训练 Mask R50 时加速约 2 小时)并且理论上会更准确。 + 2. 在 MMDetection v2.0 中,修改后的 `paste_mask()` 函数应该比之前版本更准确。 此更改参考了 [Detectron2](https://github.com/facebookresearch/detectron2/blob/master/detectron2/structures/masks.py) 中的修改,可以将 COCO 上的 mask AP 提高约 0.5%。 + +### 代码库约定 + +- MMDetection v2.0 更改了类别标签的顺序,减少了回归和 mask 分支里的无用参数并使得顺序更加自然(没有 +1 和 -1)。 + 这会影响模型的所有分类层,使其输出的类别标签顺序发生改变。回归分支和 mask head 的最后一层不再为 K 个类别保留 K+1 个通道,类别顺序与分类分支一致。 + + - 在 MMDetection v2.0 中,标签 “K” 表示背景,标签 \[0, K-1\] 对应于 K = num_categories 个对象类别。 + + - 在 MMDetection v1.x 及之前的版本中,标签 “0” 表示背景,标签 \[1, K\] 对应 K 个类别。 + + - **注意**:softmax RPN 的类顺序在 version\<=2.4.0 中仍然和 1.x 中的一样,而 sigmoid RPN 不受影响。从 MMDetection v2.5.0 开始,所有 head 中的类顺序是统一的。 + +- 不使用 R-CNN 中的低质量匹配。在 MMDetection v1.x 和之前的版本中,`max_iou_assigner` 会在 RPN 和 R-CNN 训练时给每个 ground truth 匹配低质量框。我们发现这会导致最佳的 GT 框不会被分配给某些边界框, + 因此,在MMDetection v2.0 的 R-CNN 训练中默认不允许低质量匹配。这有时可能会稍微改善 box AP(约为 0.1%)。 + +- 单独的宽高比例系数。在 MMDetection v1.x 和以前的版本中,`keep_ratio=True` 时比例系数是单个浮点数,这并不准确,因为宽度和高度的比例系数会有一定的差异。 MMDetection v2.0 对宽度和高度使用单独的比例系数,对 AP 的提升约为 0.1%。 + +- 修改了 config 文件名称的规范。 由于 model zoo 中模型不断增多, MMDetection v2.0 采用新的命名规则: + + ```shell + [model]_(model setting)_[backbone]_[neck]_(norm setting)_(misc)_(gpu x batch)_[schedule]_[dataset].py + ``` + + 其中 (`misc`) 包括 DCN 和 GCBlock 等。更多详细信息在 [配置文件说明文档](config.md) 中说明 + +- MMDetection v2.0 使用新的 ResNet Caffe backbone 来减少加载预训练模型时的警告。新 backbone 中的大部分权重与以前的相同,但没有 `conv.bias`,且它们使用不同的 `img_norm_cfg`。因此,新的 backbone 不会报 `unexpected keys` 的警告。 + +### 训练超参 + +训练超参的调整不会影响模型的兼容性,但会略微提高性能。主要有: + +- 通过设置 `nms_post=1000` 和 `max_num=1000`,将 nms 之后的 proposal 数量从 2000 更改为 1000。使 mask AP 和 bbox AP 提高了约 0.2%。 + +- Mask R-CNN、Faster R-CNN 和 RetinaNet 的默认回归损失从 smooth L1 损失更改为 L1 损失,使得 box AP 整体上都有所提升(约 0.6%)。但是,将 L1-loss 用在 Cascade R-CNN 和 HTC 等其他方法上并不能提高性能,因此我们保留这些方法的原始设置。 + +- 为简单起见,RoIAlign 层的 `sampling_ratio` 设置为 0。略微提升了 AP(约 0.2% 绝对值)。 + +- 为了提升训练速度,默认设置在训练过程中不再使用梯度裁剪。大多数模型的性能不会受到影响。对于某些模型(例如 RepPoints),我们依旧使用梯度裁剪来稳定训练过程从而获得更好的性能。 + +- 因为不再默认使用梯度裁剪,默认 warmup 比率从 1/3 更改为 0.001,以使模型训练预热更加平缓。不过我们重新进行基准测试时发现这种影响可以忽略不计。 + +### 将模型从 v1.x 升级至 v2.0 + +用户可以使用脚本 `tools/model_converters/upgrade_model_version.py` 来将 MMDetection 1.x 训练的模型转换为 MMDetection v2.0。转换后的模型可以在 MMDetection v2.0 中运行,但性能略有下降(小于 1% AP)。 +详细信息可以在 `configs/legacy` 中找到。 + +## pycocotools 兼容性 + +`mmpycocotools` 是 OpenMMLab 维护的 `pycocotools` 的复刻版,适用于 MMDetection 和 Detectron2。 +在 [PR #4939](https://github.com/open-mmlab/mmdetection/pull/4939) 之前,由于 `pycocotools` 和 `mmpycocotool` 具有相同的包名,如果用户已经安装了 `pyccocotools`(在相同环境下先安装了 Detectron2 ),那么 MMDetection 的安装过程会跳过安装 `mmpycocotool`。 导致 MMDetection 缺少 `mmpycocotools` 而报错。 +但如果在 Detectron2 之前安装 MMDetection,则可以在相同的环境下工作。 +[PR #4939](https://github.com/open-mmlab/mmdetection/pull/4939) 弃用 mmpycocotools,使用官方 pycocotools。 +在 [PR #4939](https://github.com/open-mmlab/mmdetection/pull/4939) 之后,用户能够在相同环境下安装 MMDetection 和 Detectron2,不再需要关注安装顺序。 diff --git a/mmdetection/docs/zh_cn/notes/faq.md b/mmdetection/docs/zh_cn/notes/faq.md new file mode 100644 index 00000000..2b4237c7 --- /dev/null +++ b/mmdetection/docs/zh_cn/notes/faq.md @@ -0,0 +1,260 @@ +# 常见问题解答 + +我们在这里列出了使用时的一些常见问题及其相应的解决方案。 如果您发现有一些问题被遗漏,请随时提 PR 丰富这个列表。 如果您无法在此获得帮助,请使用 [issue模板](https://github.com/open-mmlab/mmdetection/blob/main/.github/ISSUE_TEMPLATE/error-report.md/)创建问题,但是请在模板中填写所有必填信息,这有助于我们更快定位问题。 + +## PyTorch 2.0 支持 + +MMDetection 目前绝大部分算法已经支持了 PyTorch 2.0 及其 `torch.compile` 功能, 用户只需要安装 MMDetection 3.0.0rc7 及其以上版本即可。如果你在使用中发现有不支持的算法,欢迎给我们反馈。我们也非常欢迎社区贡献者来 benchmark 对比 `torch.compile` 功能所带来的速度提升。 + +如果你想启动 `torch.compile` 功能,只需要在 `train.py` 或者 `test.py` 后面加上 `--cfg-options compile=True`。 以 RTMDet 为例,你可以使用以下命令启动 `torch.compile` 功能: + +```shell +# 单卡 +python tools/train.py configs/rtmdet/rtmdet_s_8xb32-300e_coco.py --cfg-options compile=True + +# 单机 8 卡 +./tools/dist_train.sh configs/rtmdet/rtmdet_s_8xb32-300e_coco.py 8 --cfg-options compile=True + +# 单机 8 卡 + AMP 混合精度训练 +./tools/dist_train.sh configs/rtmdet/rtmdet_s_8xb32-300e_coco.py 8 --cfg-options compile=True --amp +``` + +需要特别注意的是,PyTorch 2.0 对于动态 shape 支持不是非常完善,目标检测算法中大部分不仅输入 shape 是动态的,而且 loss 计算和后处理过程中也是动态的,这会导致在开启 `torch.compile` 功能后训练速度会变慢。基于此,如果你想启动 `torch.compile` 功能,则应该遵循如下原则: + +1. 输入到网络的图片是固定 shape 的,而非多尺度的 +2. 设置 `torch._dynamo.config.cache_size_limit` 参数。TorchDynamo 会将 Python 字节码转换并缓存,已编译的函数会被存入缓存中。当下一次检查发现需要重新编译时,该函数会被重新编译并缓存。但是如果重编译次数超过预设的最大值(64),则该函数将不再被缓存或重新编译。前面说过目标检测算法中的 loss 计算和后处理部分也是动态计算的,这些函数需要在每次迭代中重新编译。因此将 `torch._dynamo.config.cache_size_limit` 参数设置得更小一些可以有效减少编译时间 + +在 MMDetection 中可以通过环境变量 `DYNAMO_CACHE_SIZE_LIMIT` 设置 `torch._dynamo.config.cache_size_limit` 参数,以 RTMDet 为例,命令如下所示: + +```shell +# 单卡 +export DYNAMO_CACHE_SIZE_LIMIT = 4 +python tools/train.py configs/rtmdet/rtmdet_s_8xb32-300e_coco.py --cfg-options compile=True + +# 单机 8 卡 +export DYNAMO_CACHE_SIZE_LIMIT = 4 +./tools/dist_train.sh configs/rtmdet/rtmdet_s_8xb32-300e_coco.py 8 --cfg-options compile=True +``` + +关于 PyTorch 2.0 的 dynamo 常见问题,可以参考 [这里](https://pytorch.org/docs/stable/dynamo/faq.html) + +## 安装 + +- MMCV 与 MMDetection 的兼容问题: "ConvWS is already registered in conv layer"; "AssertionError: MMCV==xxx is used but incompatible. Please install mmcv>=xxx, \<=xxx." + + MMDetection,MMEngine 和 MMCV 的版本兼容关系如下。请选择合适的版本避免安装错误 。 + + | MMDetection 版本 | MMCV 版本 | MMEngine 版本 | + | :--------------: | :---------------------: | :----------------------: | + | main | mmcv>=2.0.0, \<2.2.0 | mmengine>=0.7.1, \<1.0.0 | + | 3.3.0 | mmcv>=2.0.0, \<2.2.0 | mmengine>=0.7.1, \<1.0.0 | + | 3.2.0 | mmcv>=2.0.0, \<2.2.0 | mmengine>=0.7.1, \<1.0.0 | + | 3.1.0 | mmcv>=2.0.0, \<2.1.0 | mmengine>=0.7.1, \<1.0.0 | + | 3.0.0 | mmcv>=2.0.0, \<2.1.0 | mmengine>=0.7.1, \<1.0.0 | + | 3.0.0rc6 | mmcv>=2.0.0rc4, \<2.1.0 | mmengine>=0.6.0, \<1.0.0 | + | 3.0.0rc5 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.3.0, \<1.0.0 | + | 3.0.0rc4 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.3.0, \<1.0.0 | + | 3.0.0rc3 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.3.0, \<1.0.0 | + | 3.0.0rc2 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.1.0, \<1.0.0 | + | 3.0.0rc1 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.1.0, \<1.0.0 | + | 3.0.0rc0 | mmcv>=2.0.0rc1, \<2.1.0 | mmengine>=0.1.0, \<1.0.0 | + + **注意:** + + 1. 如果你希望安装 mmdet-v2.x, MMDetection 和 MMCV 版本兼容表可以在 [这里](https://mmdetection.readthedocs.io/en/stable/faq.html#installation) 找到,请选择合适的版本避免安装错误。 + 2. 在 MMCV-v2.x 中,`mmcv-full` 改名为 `mmcv`,如果你想安装不包含 CUDA 算子的版本,可以选择安装 MMCV 精简版 `mmcv-lite`。 + +- "No module named 'mmcv.ops'"; "No module named 'mmcv.\_ext'". + + 原因是安装了 `mmcv-lite` 而不是 `mmcv`。 + + 1. `pip uninstall mmcv-lite` 卸载安装的 `mmcv-lite` + + 2. 安装 `mmcv` 根据 [安装说明](https://mmcv.readthedocs.io/zh_CN/2.x/get_started/installation.html)。 + +- 在 Windows 环境下安装过程中遇到 "Microsoft Visual C++ 14.0 or graeter is required" error . + + 这个错误发生在 pycotools 的 'pycocotools.\_mask' 扩展构建过程,其原因是缺少了对应 C++ 环境依赖。你需要到微软官方下载[对应工具](https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/),选择“使用 C++ 的桌面开发”选项安装最小依赖,随后重新安装 pycocotools。 + +- 使用 albumentations + +如果你希望使用 `albumentations`,我们建议使用 `pip install -r requirements/albu.txt` +或者 `pip install -U albumentations --no-binary qudida,albumentations` 进行安装。 +如果简单地使用 `pip install albumentations>=0.3.2` 进行安装, +则会同时安装 `opencv-python-headless`(即便已经安装了 `opencv-python` 也会再次安装)。 +我们建议在安装 `albumentations` 后检查环境,以确保没有同时安装 `opencv-python` 和 `opencv-python-headless`, +因为同时安装可能会导致一些问题。更多细节请参考[官方文档](https://albumentations.ai/docs/getting_started/installation/#note-on-opencv-dependencies) 。 + +- 在某些算法中出现 ModuleNotFoundError 错误 + +一些算法或者数据需要额外的依赖,例如 Instaboost、 Panoptic Segmentation、 LVIS dataset 等。请注意错误信息并安装相应的包,例如: + +```shell +# 安装 instaboost 依赖 +pip install instaboostfast +# 安装 panoptic segmentation 依赖 +pip install git+https://github.com/cocodataset/panopticapi.git +# 安装 LVIS dataset 依赖 +pip install git+https://github.com/lvis-dataset/lvis-api.git +``` + +## 代码 + +- 修改一些代码后是否需要重新安装 mmdet + +如果你遵循最佳实践,即使用 `pip install -v -e .` 安装的 mmdet,则对本地代码所作的任何修改都会生效,无需重新安装 + +- 如何使用多个 MMDetection 版本进行开发 + +你可以拥有多个文件夹,例如 mmdet-3.0,mmdet-3.1。 + +要使环境中安装默认的 MMDetection 而不是当前正在在使用的,可以删除出现在相关脚本中的代码: + +```shell +PYTHONPATH="$(dirname $0)/..":$PYTHONPATH +``` + +## PyTorch/CUDA 环境相关 + +- "RTX 30 series card fails when building MMCV or MMDet" + + 1. 临时解决方案为使用命令 `MMCV_WITH_OPS=1 MMCV_CUDA_ARGS='-gencode=arch=compute_80,code=sm_80' pip install -e .` 进行编译。 常见报错信息为 `nvcc fatal : Unsupported gpu architecture 'compute_86'` 意思是你的编译器不支持 sm_86 架构(包括英伟达 30 系列的显卡)的优化,至 CUDA toolkit 11.0 依旧未支持. 这个命令是通过增加宏 `MMCV_CUDA_ARGS='-gencode=arch=compute_80,code=sm_80` 让 nvcc 编译器为英伟达 30 系列显卡进行 `sm_80` 的优化,虽然这有可能会无法发挥出显卡所有性能。 + + 2. 有开发者已经在 [pytorch/pytorch#47585](https://github.com/pytorch/pytorch/pull/47585) 更新了 PyTorch 默认的编译 flag, 但是我们对此并没有进行测试。 + +- "invalid device function" 或者 "no kernel image is available for execution". + + 1. 检查您正常安装了 CUDA runtime (一般在`/usr/local/`),或者使用 `nvcc --version` 检查本地版本,有时安装 PyTorch 会顺带安装一个 CUDA runtime,并且实际优先使用 conda 环境中的版本,你可以使用 `conda list cudatoolkit` 查看其版本。 + + 2. 编译 extension 的 CUDA Toolkit 版本与运行时的 CUDA Toolkit 版本是否相符, + + - 如果您从源码自己编译的,使用 `python mmdet/utils/collect_env.py` 检查编译编译 extension 的 CUDA Toolkit 版本,然后使用 `conda list cudatoolkit` 检查当前 conda 环境是否有 CUDA Toolkit,若有检查版本是否匹配, 如不匹配,更换 conda 环境的 CUDA Toolkit,或者使用匹配的 CUDA Toolkit 中的 nvcc 编译即可,如环境中无 CUDA Toolkit,可以使用 `nvcc -V`。 + + 等命令查看当前使用的 CUDA runtime。 + + - 如果您是通过 pip 下载的预编译好的版本,请确保与当前 CUDA runtime 一致。 + + 3. 运行 `python mmdet/utils/collect_env.py` 检查是否为正确的 GPU 架构编译的 PyTorch, torchvision, 与 MMCV。 你或许需要设置 `TORCH_CUDA_ARCH_LIST` 来重新安装 MMCV,可以参考 [GPU 架构表](https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#gpu-feature-list), + 例如, 运行 `TORCH_CUDA_ARCH_LIST=7.0 pip install mmcv` 为 Volta GPU 编译 MMCV。这种架构不匹配的问题一般会出现在使用一些旧型号的 GPU 时候出现, 例如, Tesla K80。 + +- "undefined symbol" 或者 "cannot open xxx.so". + + 1. 如果这些 symbol 属于 CUDA/C++ (如 libcudart.so 或者 GLIBCXX),使用 `python mmdet/utils/collect_env.py`检查 CUDA/GCC runtime 与编译 MMCV 的 CUDA 版本是否相同。 + 2. 如果这些 symbols 属于 PyTorch,(例如, symbols containing caffe, aten, and TH), 检查当前 Pytorch 版本是否与编译 MMCV 的版本一致。 + 3. 运行 `python mmdet/utils/collect_env.py` 检查 PyTorch, torchvision, MMCV 等的编译环境与运行环境一致。 + +- setuptools.sandbox.UnpickleableException: DistutilsSetupError("each element of 'ext_modules' option must be an Extension instance or 2-tuple") + + 1. 如果你在使用 miniconda 而不是 anaconda,检查是否正确的安装了 Cython 如 [#3379](https://github.com/open-mmlab/mmdetection/issues/3379). + 2. 检查环境中的 `setuptools`, `Cython`, and `PyTorch` 相互之间版本是否匹配。 + +- "Segmentation fault". + + 1. 检查 GCC 的版本,通常是因为 PyTorch 版本与 GCC 版本不匹配 (例如 GCC \< 4.9 ),我们推荐用户使用 GCC 5.4,我们也不推荐使用 GCC 5.5, 因为有反馈 GCC 5.5 会导致 "segmentation fault" 并且切换到 GCC 5.4 就可以解决问题。 + + 2. 检查是否正确安装了 CUDA 版本的 PyTorch 。 + + ```shell + python -c 'import torch; print(torch.cuda.is_available())' + ``` + + 是否返回True。 + + 3. 如果 `torch` 的安装是正确的,检查是否正确编译了 MMCV。 + + ```shell + python -c 'import mmcv; import mmcv.ops' + ``` + + 4. 如果 MMCV 与 PyTorch 都被正确安装了,则使用 `ipdb`, `pdb` 设置断点,直接查找哪一部分的代码导致了 `segmentation fault`。 + +## Training 相关 + +- "Loss goes Nan" + + 1. 检查数据的标注是否正常, 长或宽为 0 的框可能会导致回归 loss 变为 nan,一些小尺寸(宽度或高度小于 1)的框在数据增强(例如,instaboost)后也会导致此问题。 因此,可以检查标注并过滤掉那些特别小甚至面积为 0 的框,并关闭一些可能会导致 0 面积框出现数据增强。 + 2. 降低学习率:由于某些原因,例如 batch size 大小的变化, 导致当前学习率可能太大。 您可以降低为可以稳定训练模型的值。 + 3. 延长 warm up 的时间:一些模型在训练初始时对学习率很敏感,您可以把 `warmup_iters` 从 500 更改为 1000 或 2000。 + 4. 添加 gradient clipping: 一些模型需要梯度裁剪来稳定训练过程。 默认的 `grad_clip` 是 `None`, 你可以在 config 设置 `optimizer_config=dict(_delete_=True, grad_clip=dict(max_norm=35, norm_type=2))` 如果你的 config 没有继承任何包含 `optimizer_config=dict(grad_clip=None)`, 你可以直接设置`optimizer_config=dict(grad_clip=dict(max_norm=35, norm_type=2))`. + +- "GPU out of memory" + + 1. 存在大量 ground truth boxes 或者大量 anchor 的场景,可能在 assigner 会 OOM。 您可以在 assigner 的配置中设置 `gpu_assign_thr=N`,这样当超过 N 个 GT boxes 时,assigner 会通过 CPU 计算 IOU。 + + 2. 在 backbone 中设置 `with_cp=True`。 这使用 PyTorch 中的 `sublinear strategy` 来降低 backbone 占用的 GPU 显存。 + + 3. 使用 `config/fp16` 中的示例尝试混合精度训练。`loss_scale` 可能需要针对不同模型进行调整。 + + 4. 你也可以尝试使用 `AvoidCUDAOOM` 来避免该问题。首先它将尝试调用 `torch.cuda.empty_cache()`。如果失败,将会尝试把输入类型转换到 FP16。如果仍然失败,将会把输入从 GPUs 转换到 CPUs 进行计算。这里提供了两个使用的例子: + + ```python + from mmdet.utils import AvoidCUDAOOM + + output = AvoidCUDAOOM.retry_if_cuda_oom(some_function)(input1, input2) + ``` + + 你也可也使用 `AvoidCUDAOOM` 作为装饰器让代码遇到 OOM 的时候继续运行: + + ```python + from mmdet.utils import AvoidCUDAOOM + + @AvoidCUDAOOM.retry_if_cuda_oom + def function(*args, **kwargs): + ... + return xxx + ``` + +- "RuntimeError: Expected to have finished reduction in the prior iteration before starting a new one" + + 1. 这个错误出现在存在参数没有在 forward 中使用,容易在 DDP 中运行不同分支时发生。 + 2. 你可以在 config 设置 `find_unused_parameters = True` 进行训练 (会降低训练速度)。 + 3. 你也可以通过在 config 中的 `optimizer_config` 里设置 `detect_anomalous_params=True` 查找哪些参数没有用到,但是需要 MMCV 的版本 >= 1.4.1。 + +- 训练中保存最好模型 + + 可以通过配置 `default_hooks = dict(checkpoint=dict(type='CheckpointHook', interval=1, save_best='auto')`开启。在 `auto` 参数情况下会根据返回的验证结果中的第一个 key 作为选择最优模型的依据,你也可以直接设置评估结果中的 key 来手动设置,例如 `save_best='coco/bbox_mAP'`。 + +- 在 Resume 训练中使用 `ExpMomentumEMAHook` + + 如果在训练中使用了 `ExpMomentumEMAHook`,那么 resume 时候不能仅仅通过命令行参数 `--resume-from` 或 `--cfg-options resume_from` 实现恢复模型参数功能例如 `python tools/train.py configs/yolox/yolox_s_8x8_300e_coco.py --resume-from ./work_dir/yolox_s_8x8_300e_coco/epoch_x.pth`。以 `yolox_s` 算法为例,由于 `ExpMomentumEMAHook` 需要重新加载权重,你可以通过如下做法实现: + + ```python + # 直接打开 configs/yolox/yolox_s_8x8_300e_coco.py 修改所有 resume_from 字段 + resume_from=./work_dir/yolox_s_8x8_300e_coco/epoch_x.pth + custom_hooks=[... + dict( + type='ExpMomentumEMAHook', + resume_from=./work_dir/yolox_s_8x8_300e_coco/epoch_x.pth, + momentum=0.0001, + priority=49) + ] + ``` + +## Evaluation 相关 + +- 使用 COCO Dataset 的测评接口时, 测评结果中 AP 或者 AR = -1 + 1. 根据COCO数据集的定义,一张图像中的中等物体与小物体面积的阈值分别为 9216(96\*96)与 1024(32\*32)。 + 2. 如果在某个区间没有检测框 AP 与 AR 认定为 -1. + +## Model 相关 + +- **ResNet style 参数说明** + + ResNet style 可选参数允许 `pytorch` 和 `caffe`,其差别在于 Bottleneck 模块。Bottleneck 是 `1x1-3x3-1x1` 堆叠结构,在 `caffe` 模式模式下 stride=2 参数放置在第一个 `1x1` 卷积处,而 `pyorch` 模式下 stride=2 放在第二个 `3x3` 卷积处。一个简单示例如下: + + ```python + if self.style == 'pytorch': + self.conv1_stride = 1 + self.conv2_stride = stride + else: + self.conv1_stride = stride + self.conv2_stride = 1 + ``` + +- **ResNeXt 参数说明** + + ResNeXt 来自论文 [`Aggregated Residual Transformations for Deep Neural Networks`](https://arxiv.org/abs/1611.05431). 其引入分组卷积,并且通过变量基数来控制组的数量达到精度和复杂度的平衡,其有两个超参 `baseWidth` 和 `cardinality `来控制内部 Bottleneck 模块的基本宽度和分组数参数。以 MMDetection 中配置名为 `mask_rcnn_x101_64x4d_fpn_mstrain-poly_3x_coco.py` 为例,其中 `mask_rcnn` 代表算法采用 Mask R-CNN,`x101` 代表骨架网络采用 ResNeXt-101,`64x4d`代表 Bottleneck 一共分成 64 组,每组的基本宽度是 4。 + +- **骨架网络 eval 模式说明** + + 因为检测模型通常比较大且输入图片分辨率很高,这会导致检测模型的 batch 很小,通常是 2,这会使得 BatchNorm 在训练过程计算的统计量方差非常大,不如主干网络预训练时得到的统计量稳定,因此在训练是一般都会使用 `norm_eval=True` 模式,直接使用预训练主干网络中的 BatchNorm 统计量,少数使用大 batch 的算法是 `norm_eval=False` 模式,例如 NASFPN。对于没有 ImageNet 预训练的骨架网络,如果 batch 比较小,可以考虑使用 `SyncBN`。 diff --git a/mmdetection/docs/zh_cn/notes/projects.md b/mmdetection/docs/zh_cn/notes/projects.md new file mode 100644 index 00000000..6b9d300d --- /dev/null +++ b/mmdetection/docs/zh_cn/notes/projects.md @@ -0,0 +1,48 @@ +# 基于 MMDetection 的项目 + +有许多开源项目都是基于 MMDetection 搭建的,我们在这里列举一部分作为样例,展示如何基于 MMDetection 搭建您自己的项目。 +由于这个页面列举的项目并不完全,我们欢迎社区提交 Pull Request 来更新这个文档。 + +## MMDetection 的拓展项目 + +一些项目拓展了 MMDetection 的边界,如将 MMDetection 拓展支持 3D 检测或者将 MMDetection 用于部署。 +它们展示了 MMDetection 的许多可能性,所以我们在这里也列举一些。 + +- [OTEDetection](https://github.com/opencv/mmdetection): OpenVINO training extensions for object detection. +- [MMDetection3d](https://github.com/open-mmlab/mmdetection3d): OpenMMLab's next-generation platform for general 3D object detection. + +## 研究项目 + +同样有许多研究论文是基于 MMDetection 进行的。许多论文都发表在了顶级的会议或期刊上,或者对社区产生了深远的影响。 +为了向社区提供一个可以参考的论文列表,帮助大家开发或者比较新的前沿算法,我们在这里也遵循会议的时间顺序列举了一些论文。 +MMDetection 中已经支持的算法不在此列。 + +- Involution: Inverting the Inherence of Convolution for Visual Recognition, CVPR21. [\[paper\]](https://arxiv.org/abs/2103.06255)[\[github\]](https://github.com/d-li14/involution) +- Multiple Instance Active Learning for Object Detection, CVPR 2021. [\[paper\]](https://openaccess.thecvf.com/content/CVPR2021/papers/Yuan_Multiple_Instance_Active_Learning_for_Object_Detection_CVPR_2021_paper.pdf)[\[github\]](https://github.com/yuantn/MI-AOD) +- Adaptive Class Suppression Loss for Long-Tail Object Detection, CVPR 2021. [\[paper\]](https://arxiv.org/abs/2104.00885)[\[github\]](https://github.com/CASIA-IVA-Lab/ACSL) +- Generalizable Pedestrian Detection: The Elephant In The Room, CVPR2021. [\[paper\]](https://arxiv.org/abs/2003.08799)[\[github\]](https://github.com/hasanirtiza/Pedestron) +- Group Fisher Pruning for Practical Network Compression, ICML2021. [\[paper\]](https://github.com/jshilong/FisherPruning/blob/main/resources/paper.pdf)[\[github\]](https://github.com/jshilong/FisherPruning) +- Overcoming Classifier Imbalance for Long-tail Object Detection with Balanced Group Softmax, CVPR2020. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2020/papers/Li_Overcoming_Classifier_Imbalance_for_Long-Tail_Object_Detection_With_Balanced_Group_CVPR_2020_paper.pdf)[\[github\]](https://github.com/FishYuLi/BalancedGroupSoftmax) +- Coherent Reconstruction of Multiple Humans from a Single Image, CVPR2020. [\[paper\]](https://jiangwenpl.github.io/multiperson/)[\[github\]](https://github.com/JiangWenPL/multiperson) +- Look-into-Object: Self-supervised Structure Modeling for Object Recognition, CVPR 2020. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2020/papers/Zhou_Look-Into-Object_Self-Supervised_Structure_Modeling_for_Object_Recognition_CVPR_2020_paper.pdf)[\[github\]](https://github.com/JDAI-CV/LIO) +- Video Panoptic Segmentation, CVPR2020. [\[paper\]](https://arxiv.org/abs/2006.11339)[\[github\]](https://github.com/mcahny/vps) +- D2Det: Towards High Quality Object Detection and Instance Segmentation, CVPR2020. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2020/html/Cao_D2Det_Towards_High_Quality_Object_Detection_and_Instance_Segmentation_CVPR_2020_paper.html)[\[github\]](https://github.com/JialeCao001/D2Det) +- CentripetalNet: Pursuing High-quality Keypoint Pairs for Object Detection, CVPR2020. [\[paper\]](https://arxiv.org/abs/2003.09119)[\[github\]](https://github.com/KiveeDong/CentripetalNet) +- Learning a Unified Sample Weighting Network for Object Detection, CVPR 2020. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2020/html/Cai_Learning_a_Unified_Sample_Weighting_Network_for_Object_Detection_CVPR_2020_paper.html)[\[github\]](https://github.com/caiqi/sample-weighting-network) +- Scale-equalizing Pyramid Convolution for Object Detection, CVPR2020. [\[paper\]](https://arxiv.org/abs/2005.03101) [\[github\]](https://github.com/jshilong/SEPC) +- Revisiting the Sibling Head in Object Detector, CVPR2020. [\[paper\]](https://arxiv.org/abs/2003.07540)[\[github\]](https://github.com/Sense-X/TSD) +- PolarMask: Single Shot Instance Segmentation with Polar Representation, CVPR2020. [\[paper\]](https://arxiv.org/abs/1909.13226)[\[github\]](https://github.com/xieenze/PolarMask) +- Hit-Detector: Hierarchical Trinity Architecture Search for Object Detection, CVPR2020. [\[paper\]](https://arxiv.org/abs/2003.11818)[\[github\]](https://github.com/ggjy/HitDet.pytorch) +- ZeroQ: A Novel Zero Shot Quantization Framework, CVPR2020. [\[paper\]](https://arxiv.org/abs/2001.00281)[\[github\]](https://github.com/amirgholami/ZeroQ) +- CBNet: A Novel Composite Backbone Network Architecture for Object Detection, AAAI2020. [\[paper\]](https://aaai.org/Papers/AAAI/2020GB/AAAI-LiuY.1833.pdf)[\[github\]](https://github.com/VDIGPKU/CBNet) +- RDSNet: A New Deep Architecture for Reciprocal Object Detection and Instance Segmentation, AAAI2020. [\[paper\]](https://arxiv.org/abs/1912.05070)[\[github\]](https://github.com/wangsr126/RDSNet) +- Training-Time-Friendly Network for Real-Time Object Detection, AAAI2020. [\[paper\]](https://arxiv.org/abs/1909.00700)[\[github\]](https://github.com/ZJULearning/ttfnet) +- Cascade RPN: Delving into High-Quality Region Proposal Network with Adaptive Convolution, NeurIPS 2019. [\[paper\]](https://arxiv.org/abs/1909.06720)[\[github\]](https://github.com/thangvubk/Cascade-RPN) +- Reasoning R-CNN: Unifying Adaptive Global Reasoning into Large-scale Object Detection, CVPR2019. [\[paper\]](http://openaccess.thecvf.com/content_CVPR_2019/papers/Xu_Reasoning-RCNN_Unifying_Adaptive_Global_Reasoning_Into_Large-Scale_Object_Detection_CVPR_2019_paper.pdf)[\[github\]](https://github.com/chanyn/Reasoning-RCNN) +- Learning RoI Transformer for Oriented Object Detection in Aerial Images, CVPR2019. [\[paper\]](https://arxiv.org/abs/1812.00155)[\[github\]](https://github.com/dingjiansw101/AerialDetection) +- SOLO: Segmenting Objects by Locations. [\[paper\]](https://arxiv.org/abs/1912.04488)[\[github\]](https://github.com/WXinlong/SOLO) +- SOLOv2: Dynamic, Faster and Stronger. [\[paper\]](https://arxiv.org/abs/2003.10152)[\[github\]](https://github.com/WXinlong/SOLO) +- Dense Peppoints: Representing Visual Objects with Dense Point Sets. [\[paper\]](https://arxiv.org/abs/1912.11473)[\[github\]](https://github.com/justimyhxu/Dense-RepPoints) +- IterDet: Iterative Scheme for Object Detection in Crowded Environments. [\[paper\]](https://arxiv.org/abs/2005.05708)[\[github\]](https://github.com/saic-vul/iterdet) +- Cross-Iteration Batch Normalization. [\[paper\]](https://arxiv.org/abs/2002.05712)[\[github\]](https://github.com/Howal/Cross-iterationBatchNorm) +- A Ranking-based, Balanced Loss Function Unifying Classification and Localisation in Object Detection, NeurIPS2020 [\[paper\]](https://arxiv.org/abs/2009.13592)[\[github\]](https://github.com/kemaloksuz/aLRPLoss) diff --git a/mmdetection/docs/zh_cn/overview.md b/mmdetection/docs/zh_cn/overview.md new file mode 100644 index 00000000..5269aed8 --- /dev/null +++ b/mmdetection/docs/zh_cn/overview.md @@ -0,0 +1,54 @@ +# 概述 + +本章向您介绍 MMDetection 的整体框架,并提供详细的教程链接。 + +## 什么是 MMDetection + +![图片](https://user-images.githubusercontent.com/12907710/137271636-56ba1cd2-b110-4812-8221-b4c120320aa9.png) + +MMDetection 是一个目标检测工具箱,包含了丰富的目标检测、实例分割、全景分割算法以及相关的组件和模块,下面是它的整体框架: + +MMDetection 由 7 个主要部分组成,apis、structures、datasets、models、engine、evaluation 和 visualization。 + +- **apis** 为模型推理提供高级 API。 +- **structures** 提供 bbox、mask 和 DetDataSample 等数据结构。 +- **datasets** 支持用于目标检测、实例分割和全景分割的各种数据集。 + - **transforms** 包含各种数据增强变换。 + - **samplers** 定义了不同的数据加载器采样策略。 +- **models** 是检测器最重要的部分,包含检测器的不同组件。 + - **detectors** 定义所有检测模型类。 + - **data_preprocessors** 用于预处理模型的输入数据。 + - **backbones** 包含各种骨干网络。 + - **necks** 包含各种模型颈部组件。 + - **dense_heads** 包含执行密集预测的各种检测头。 + - **roi_heads** 包含从 RoI 预测的各种检测头。 + - **seg_heads** 包含各种分割头。 + - **losses** 包含各种损失函数。 + - **task_modules** 为检测任务提供模块,例如 assigners、samplers、box coders 和 prior generators。 + - **layers** 提供了一些基本的神经网络层。 +- **engine** 是运行时组件的一部分。 + - **runner** 为 [MMEngine 的执行器](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/runner.html)提供扩展。 + - **schedulers** 提供用于调整优化超参数的调度程序。 + - **optimizers** 提供优化器和优化器封装。 + - **hooks** 提供执行器的各种钩子。 +- **evaluation** 为评估模型性能提供不同的指标。 +- **visualization** 用于可视化检测结果。 + +## 如何使用本指南 + +以下是 MMDetection 的详细指南: + +1. 安装说明见[开始你的第一步](get_started.md)。 + +2. MMDetection 的基本使用方法请参考以下教程。 + + - [训练和测试](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/index.html#train-test) + + - [实用工具](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/index.html#useful-tools) + +3. 参考以下教程深入了解: + + - [基础概念](https://mmdetection.readthedocs.io/zh_CN/latest/advanced_guides/index.html#basic-concepts) + - [组件定制](https://mmdetection.readthedocs.io/zh_CN/latest/advanced_guides/index.html#component-customization) + +4. 对于 MMDetection 2.x 版本的用户,我们提供了[迁移指南](./migration/migration.md),帮助您完成新版本的适配。 diff --git a/mmdetection/docs/zh_cn/stat.py b/mmdetection/docs/zh_cn/stat.py new file mode 100644 index 00000000..1ea5fbd2 --- /dev/null +++ b/mmdetection/docs/zh_cn/stat.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +import functools as func +import glob +import os.path as osp +import re + +import numpy as np + +url_prefix = 'https://github.com/open-mmlab/mmdetection/blob/main/' + +files = sorted(glob.glob('../configs/*/README.md')) + +stats = [] +titles = [] +num_ckpts = 0 + +for f in files: + url = osp.dirname(f.replace('../', url_prefix)) + + with open(f, 'r') as content_file: + content = content_file.read() + + title = content.split('\n')[0].replace('# ', '').strip() + ckpts = set(x.lower().strip() + for x in re.findall(r'\[model\]\((https?.*)\)', content)) + + if len(ckpts) == 0: + continue + + _papertype = [x for x in re.findall(r'\[([A-Z]+)\]', content)] + assert len(_papertype) > 0 + papertype = _papertype[0] + + paper = set([(papertype, title)]) + + titles.append(title) + num_ckpts += len(ckpts) + + statsmsg = f""" +\t* [{papertype}] [{title}]({url}) ({len(ckpts)} ckpts) +""" + stats.append((paper, ckpts, statsmsg)) + +allpapers = func.reduce(lambda a, b: a.union(b), [p for p, _, _ in stats]) +msglist = '\n'.join(x for _, _, x in stats) + +papertypes, papercounts = np.unique([t for t, _ in allpapers], + return_counts=True) +countstr = '\n'.join( + [f' - {t}: {c}' for t, c in zip(papertypes, papercounts)]) + +modelzoo = f""" +# Model Zoo Statistics + +* Number of papers: {len(set(titles))} +{countstr} + +* Number of checkpoints: {num_ckpts} + +{msglist} +""" + +with open('modelzoo_statistics.md', 'w') as f: + f.write(modelzoo) diff --git a/mmdetection/docs/zh_cn/switch_language.md b/mmdetection/docs/zh_cn/switch_language.md new file mode 100644 index 00000000..b2c4ad9d --- /dev/null +++ b/mmdetection/docs/zh_cn/switch_language.md @@ -0,0 +1,3 @@ +## English + +## 简体中文 diff --git a/mmdetection/docs/zh_cn/user_guides/config.md b/mmdetection/docs/zh_cn/user_guides/config.md new file mode 100644 index 00000000..3a670bf8 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/config.md @@ -0,0 +1,589 @@ +# 学习配置文件 + +MMDetection 和其他 OpenMMLab 仓库使用 [MMEngine 的配置文件系统](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html)。 配置文件使用了模块化和继承设计,以便于进行各类实验。 + +## 配置文件的内容 + +MMDetection 采用模块化设计,所有功能的模块都可以通过配置文件进行配置。 以 Mask R-CNN 为例,我们将根据不同的功能模块介绍配置文件中的各个字段: + +### 模型配置 + +在 mmdetection 的配置中,我们使用 `model` 字段来配置检测算法的组件。 除了 `backbone`、`neck` 等神经网络组件外,还需要 `data_preprocessor`、`train_cfg` 和 `test_cfg`。 `data_preprocessor` 负责对 dataloader 输出的每一批数据进行预处理。 模型配置中的 `train_cfg` 和 `test_cfg` 用于设置训练和测试组件的超参数。 + +```python +model = dict( + type='MaskRCNN', # 检测器名 + data_preprocessor=dict( # 数据预处理器的配置,通常包括图像归一化和 padding + type='DetDataPreprocessor', # 数据预处理器的类型,参考 https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.data_preprocessors.DetDataPreprocessor + mean=[123.675, 116.28, 103.53], # 用于预训练骨干网络的图像归一化通道均值,按 R、G、B 排序 + std=[58.395, 57.12, 57.375], # 用于预训练骨干网络的图像归一化通道标准差,按 R、G、B 排序 + bgr_to_rgb=True, # 是否将图片通道从 BGR 转为 RGB + pad_mask=True, # 是否填充实例分割掩码 + pad_size_divisor=32), # padding 后的图像的大小应该可以被 ``pad_size_divisor`` 整除 + backbone=dict( # 主干网络的配置文件 + type='ResNet', # 主干网络的类别,可用选项请参考 https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.backbones.ResNet + depth=50, # 主干网络的深度,对于 ResNet 和 ResNext 通常设置为 50 或 101 + num_stages=4, # 主干网络状态(stages)的数目,这些状态产生的特征图作为后续的 head 的输入 + out_indices=(0, 1, 2, 3), # 每个状态产生的特征图输出的索引 + frozen_stages=1, # 第一个状态的权重被冻结 + norm_cfg=dict( # 归一化层(norm layer)的配置项 + type='BN', # 归一化层的类别,通常是 BN 或 GN + requires_grad=True), # 是否训练归一化里的 gamma 和 beta + norm_eval=True, # 是否冻结 BN 里的统计项 + style='pytorch', # 主干网络的风格,'pytorch' 意思是步长为2的层为 3x3 卷积, 'caffe' 意思是步长为2的层为 1x1 卷积 + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), # 加载通过 ImageNet 预训练的模型 + neck=dict( + type='FPN', # 检测器的 neck 是 FPN,我们同样支持 'NASFPN', 'PAFPN' 等,更多细节可以参考 https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.necks.FPN + in_channels=[256, 512, 1024, 2048], # 输入通道数,这与主干网络的输出通道一致 + out_channels=256, # 金字塔特征图每一层的输出通道 + num_outs=5), # 输出的范围(scales) + rpn_head=dict( + type='RPNHead', # rpn_head 的类型是 'RPNHead', 我们也支持 'GARPNHead' 等,更多细节可以参考 https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.dense_heads.RPNHead + in_channels=256, # 每个输入特征图的输入通道,这与 neck 的输出通道一致 + feat_channels=256, # head 卷积层的特征通道 + anchor_generator=dict( # 锚点(Anchor)生成器的配置 + type='AnchorGenerator', # 大多数方法使用 AnchorGenerator 作为锚点生成器, SSD 检测器使用 `SSDAnchorGenerator`。更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/prior_generators/anchor_generator.py#L18 + scales=[8], # 锚点的基本比例,特征图某一位置的锚点面积为 scale * base_sizes + ratios=[0.5, 1.0, 2.0], # 高度和宽度之间的比率 + strides=[4, 8, 16, 32, 64]), # 锚生成器的步幅。这与 FPN 特征步幅一致。 如果未设置 base_sizes,则当前步幅值将被视为 base_sizes + bbox_coder=dict( # 在训练和测试期间对框进行编码和解码 + type='DeltaXYWHBBoxCoder', # 框编码器的类别,'DeltaXYWHBBoxCoder' 是最常用的,更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/coders/delta_xywh_bbox_coder.py#L13 + target_means=[0.0, 0.0, 0.0, 0.0], # 用于编码和解码框的目标均值 + target_stds=[1.0, 1.0, 1.0, 1.0]), # 用于编码和解码框的标准差 + loss_cls=dict( # 分类分支的损失函数配置 + type='CrossEntropyLoss', # 分类分支的损失类型,我们也支持 FocalLoss 等,更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/losses/cross_entropy_loss.py#L201 + use_sigmoid=True, # RPN 通常进行二分类,所以通常使用 sigmoid 函数 + los_weight=1.0), # 分类分支的损失权重 + loss_bbox=dict( # 回归分支的损失函数配置 + type='L1Loss', # 损失类型,我们还支持许多 IoU Losses 和 Smooth L1-loss 等,更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/losses/smooth_l1_loss.py#L56 + loss_weight=1.0)), # 回归分支的损失权重 + roi_head=dict( # RoIHead 封装了两步(two-stage)/级联(cascade)检测器的第二步 + type='StandardRoIHead', # RoI head 的类型,更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/roi_heads/standard_roi_head.py#L17 + bbox_roi_extractor=dict( # 用于 bbox 回归的 RoI 特征提取器 + type='SingleRoIExtractor', # RoI 特征提取器的类型,大多数方法使用 SingleRoIExtractor,更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/roi_heads/roi_extractors/single_level_roi_extractor.py#L13 + roi_layer=dict( # RoI 层的配置 + type='RoIAlign', # RoI 层的类别, 也支持 DeformRoIPoolingPack 和 ModulatedDeformRoIPoolingPack,更多细节请参考 https://mmcv.readthedocs.io/en/latest/api.html#mmcv.ops.RoIAlign + output_size=7, # 特征图的输出大小 + sampling_ratio=0), # 提取 RoI 特征时的采样率。0 表示自适应比率 + out_channels=256, # 提取特征的输出通道 + featmap_strides=[4, 8, 16, 32]), # 多尺度特征图的步幅,应该与主干的架构保持一致 + bbox_head=dict( # RoIHead 中 box head 的配置 + type='Shared2FCBBoxHead', # bbox head 的类别,更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/roi_heads/bbox_heads/convfc_bbox_head.py#L220 + in_channels=256, # bbox head 的输入通道。 这与 roi_extractor 中的 out_channels 一致 + fc_out_channels=1024, # FC 层的输出特征通道 + roi_feat_size=7, # 候选区域(Region of Interest)特征的大小 + num_classes=80, # 分类的类别数量 + bbox_coder=dict( # 第二阶段使用的框编码器 + type='DeltaXYWHBBoxCoder', # 框编码器的类别,大多数情况使用 'DeltaXYWHBBoxCoder' + target_means=[0.0, 0.0, 0.0, 0.0], # 用于编码和解码框的均值 + target_stds=[0.1, 0.1, 0.2, 0.2]), # 编码和解码的标准差。因为框更准确,所以值更小,常规设置时 [0.1, 0.1, 0.2, 0.2]。 + reg_class_agnostic=False, # 回归是否与类别无关 + loss_cls=dict( # 分类分支的损失函数配 + type='CrossEntropyLoss', # 分类分支的损失类型,我们也支持 FocalLoss 等 + use_sigmoid=False, # 是否使用 sigmoid + loss_weight=1.0), # 分类分支的损失权重 + loss_bbox=dict( # 回归分支的损失函数配置 + type='L1Loss', # 损失类型,我们还支持许多 IoU Losses 和 Smooth L1-loss 等 + loss_weight=1.0)), # 回归分支的损失权重 + mask_roi_extractor=dict( # 用于 mask 生成的 RoI 特征提取器 + type='SingleRoIExtractor', # RoI 特征提取器的类型,大多数方法使用 SingleRoIExtractor + roi_layer=dict( # 提取实例分割特征的 RoI 层配置 + type='RoIAlign', # RoI 层的类型,也支持 DeformRoIPoolingPack 和 ModulatedDeformRoIPoolingPack + output_size=14, # 特征图的输出大小 + sampling_ratio=0), # 提取 RoI 特征时的采样率 + out_channels=256, # 提取特征的输出通道 + featmap_strides=[4, 8, 16, 32]), # 多尺度特征图的步幅 + mask_head=dict( # mask 预测 head 模型 + type='FCNMaskHead', # mask head 的类型,更多细节请参考 https://mmdetection.readthedocs.io/en/latest/api.html#mmdet.models.roi_heads.FCNMaskHead + num_convs=4, # mask head 中的卷积层数 + in_channels=256, # 输入通道,应与 mask roi extractor 的输出通道一致 + conv_out_channels=256, # 卷积层的输出通道 + num_classes=80, # 要分割的类别数 + loss_mask=dict( # mask 分支的损失函数配置 + type='CrossEntropyLoss', # 用于分割的损失类型 + use_mask=True, # 是否只在正确的类中训练 mask + loss_weight=1.0))), # mask 分支的损失权重 + train_cfg = dict( # rpn 和 rcnn 训练超参数的配置 + rpn=dict( # rpn 的训练配置 + assigner=dict( # 分配器(assigner)的配置 + type='MaxIoUAssigner', # 分配器的类型,MaxIoUAssigner 用于许多常见的检测器,更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/assigners/max_iou_assigner.py#L14 + pos_iou_thr=0.7, # IoU >= 0.7(阈值) 被视为正样本 + neg_iou_thr=0.3, # IoU < 0.3(阈值) 被视为负样本 + min_pos_iou=0.3, # 将框作为正样本的最小 IoU 阈值 + match_low_quality=True, # 是否匹配低质量的框(更多细节见 API 文档) + ignore_iof_thr=-1), # 忽略 bbox 的 IoF 阈值 + sampler=dict( # 正/负采样器(sampler)的配置 + type='RandomSampler', # 采样器类型,还支持 PseudoSampler 和其他采样器,更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/samplers/random_sampler.py#L14 + num=256, # 样本数量。 + pos_fraction=0.5, # 正样本占总样本的比例 + neg_pos_ub=-1, # 基于正样本数量的负样本上限 + add_gt_as_proposals=False), # 采样后是否添加 GT 作为 proposal + allowed_border=-1, # 填充有效锚点后允许的边框 + pos_weight=-1, # 训练期间正样本的权重 + debug=False), # 是否设置调试(debug)模式 + rpn_proposal=dict( # 在训练期间生成 proposals 的配置 + nms_across_levels=False, # 是否对跨层的 box 做 NMS。仅适用于 `GARPNHead` ,naive rpn 不支持 nms cross levels + nms_pre=2000, # NMS 前的 box 数 + nms_post=1000, # NMS 要保留的 box 的数量,只在 GARPNHHead 中起作用 + max_per_img=1000, # NMS 后要保留的 box 数量 + nms=dict( # NMS 的配置 + type='nms', # NMS 的类别 + iou_threshold=0.7 # NMS 的阈值 + ), + min_bbox_size=0), # 允许的最小 box 尺寸 + rcnn=dict( # roi head 的配置。 + assigner=dict( # 第二阶段分配器的配置,这与 rpn 中的不同 + type='MaxIoUAssigner', # 分配器的类型,MaxIoUAssigner 目前用于所有 roi_heads。更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/assigners/max_iou_assigner.py#L14 + pos_iou_thr=0.5, # IoU >= 0.5(阈值)被认为是正样本 + neg_iou_thr=0.5, # IoU < 0.5(阈值)被认为是负样本 + min_pos_iou=0.5, # 将 box 作为正样本的最小 IoU 阈值 + match_low_quality=False, # 是否匹配低质量下的 box(有关更多详细信息,请参阅 API 文档) + ignore_iof_thr=-1), # 忽略 bbox 的 IoF 阈值 + sampler=dict( + type='RandomSampler', # 采样器的类型,还支持 PseudoSampler 和其他采样器,更多细节请参考 https://github.com/open-mmlab/mmdetection/blob/main/mmdet/models/task_modules/samplers/random_sampler.py#L14 + num=512, # 样本数量 + pos_fraction=0.25, # 正样本占总样本的比例 + neg_pos_ub=-1, # 基于正样本数量的负样本上限 + add_gt_as_proposals=True + ), # 采样后是否添加 GT 作为 proposal + mask_size=28, # mask 的大小 + pos_weight=-1, # 训练期间正样本的权重 + debug=False)), # 是否设置调试模式 + test_cfg = dict( # 用于测试 rpn 和 rcnn 超参数的配置 + rpn=dict( # 测试阶段生成 proposals 的配置 + nms_across_levels=False, # 是否对跨层的 box 做 NMS。仅适用于 `GARPNHead`,naive rpn 不支持做 NMS cross levels + nms_pre=1000, # NMS 前的 box 数 + nms_post=1000, # NMS 要保留的 box 的数量,只在 `GARPNHHead` 中起作用 + max_per_img=1000, # NMS 后要保留的 box 数量 + nms=dict( # NMS 的配置 + type='nms', # NMS 的类型 + iou_threshold=0.7 # NMS 阈值 + ), + min_bbox_size=0), # box 允许的最小尺寸 + rcnn=dict( # roi heads 的配置 + score_thr=0.05, # bbox 的分数阈值 + nms=dict( # 第二步的 NMS 配置 + type='nms', # NMS 的类型 + iou_thr=0.5), # NMS 的阈值 + max_per_img=100, # 每张图像的最大检测次数 + mask_thr_binary=0.5))) # mask 预处的阈值 +``` + +### 数据集和评测器配置 + +在使用[执行器](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/runner.html) 进行训练、测试、验证时,我们需要配置 [Dataloader](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/dataset.html)。构建数据 dataloader 需要设置数据集(dataset)和数据处理流程(data pipeline)。 由于这部分的配置较为复杂,我们使用中间变量来简化 dataloader 配置的编写。 + +```python +dataset_type = 'CocoDataset' # 数据集类型,这将被用来定义数据集。 +data_root = 'data/coco/' # 数据的根路径。 + +train_pipeline = [ # 训练数据处理流程 + dict(type='LoadImageFromFile'), # 第 1 个流程,从文件路径里加载图像。 + dict( + type='LoadAnnotations', # 第 2 个流程,对于当前图像,加载它的注释信息。 + with_bbox=True, # 是否使用标注框(bounding box), 目标检测需要设置为 True。 + with_mask=True, # 是否使用 instance mask,实例分割需要设置为 True。 + poly2mask=False), # 是否将 polygon mask 转化为 instance mask, 设置为 False 以加速和节省内存。 + dict( + type='Resize', # 变化图像和其标注大小的流程。 + scale=(1333, 800), # 图像的最大尺寸 + keep_ratio=True # 是否保持图像的长宽比。 + ), + dict( + type='RandomFlip', # 翻转图像和其标注的数据增广流程。 + prob=0.5), # 翻转图像的概率。 + dict(type='PackDetInputs') # 将数据转换为检测器输入格式的流程 +] +test_pipeline = [ # 测试数据处理流程 + dict(type='LoadImageFromFile'), # 第 1 个流程,从文件路径里加载图像。 + dict(type='Resize', scale=(1333, 800), keep_ratio=True), # 变化图像大小的流程。 + dict( + type='PackDetInputs', # 将数据转换为检测器输入格式的流程 + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( # 训练 dataloader 配置 + batch_size=2, # 单个 GPU 的 batch size + num_workers=2, # 单个 GPU 分配的数据加载线程数 + persistent_workers=True, # 如果设置为 True,dataloader 在迭代完一轮之后不会关闭数据读取的子进程,可以加速训练 + sampler=dict( # 训练数据的采样器 + type='DefaultSampler', # 默认的采样器,同时支持分布式和非分布式训练。请参考 https://mmengine.readthedocs.io/zh_CN/latest/api/generated/mmengine.dataset.DefaultSampler.html#mmengine.dataset.DefaultSampler + shuffle=True), # 随机打乱每个轮次训练数据的顺序 + batch_sampler=dict(type='AspectRatioBatchSampler'), # 批数据采样器,用于确保每一批次内的数据拥有相似的长宽比,可用于节省显存 + dataset=dict( # 训练数据集的配置 + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', # 标注文件路径 + data_prefix=dict(img='train2017/'), # 图片路径前缀 + filter_cfg=dict(filter_empty_gt=True, min_size=32), # 图片和标注的过滤配置 + pipeline=train_pipeline)) # 这是由之前创建的 train_pipeline 定义的数据处理流程。 +val_dataloader = dict( # 验证 dataloader 配置 + batch_size=1, # 单个 GPU 的 Batch size。如果 batch-szie > 1,组成 batch 时的额外填充会影响模型推理精度 + num_workers=2, # 单个 GPU 分配的数据加载线程数 + persistent_workers=True, # 如果设置为 True,dataloader 在迭代完一轮之后不会关闭数据读取的子进程,可以加速训练 + drop_last=False, # 是否丢弃最后未能组成一个批次的数据 + sampler=dict( + type='DefaultSampler', + shuffle=False), # 验证和测试时不打乱数据顺序 + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, # 开启测试模式,避免数据集过滤图片和标注 + pipeline=test_pipeline)) +test_dataloader = val_dataloader # 测试 dataloader 配置 +``` + +[评测器](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/evaluation.html) 用于计算训练模型在验证和测试数据集上的指标。评测器的配置由一个或一组评价指标(Metric)配置组成: + +```python +val_evaluator = dict( # 验证过程使用的评测器 + type='CocoMetric', # 用于评估检测和实例分割的 AR、AP 和 mAP 的 coco 评价指标 + ann_file=data_root + 'annotations/instances_val2017.json', # 标注文件路径 + metric=['bbox', 'segm'], # 需要计算的评价指标,`bbox` 用于检测,`segm` 用于实例分割 + format_only=False) +test_evaluator = val_evaluator # 测试过程使用的评测器 +``` + +由于测试数据集没有标注文件,因此 MMDetection 中的 test_dataloader 和 test_evaluator 配置通常等于val。 如果要保存在测试数据集上的检测结果,则可以像这样编写配置: + +```python +# 在测试集上推理, +# 并将检测结果转换格式以用于提交结果 +test_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file=data_root + 'annotations/image_info_test-dev2017.json', + data_prefix=dict(img='test2017/'), + test_mode=True, + pipeline=test_pipeline)) +test_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/image_info_test-dev2017.json', + metric=['bbox', 'segm'], + format_only=True, # 只将模型输出转换为 coco 的 JSON 格式并保存 + outfile_prefix='./work_dirs/coco_detection/test') # 要保存的 JSON 文件的前缀 +``` + +### 训练和测试的配置 + +MMEngine 的 Runner 使用 Loop 来控制训练,验证和测试过程。 +用户可以使用这些字段设置最大训练轮次和验证间隔。 + +```python +train_cfg = dict( + type='EpochBasedTrainLoop', # 训练循环的类型,请参考 https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py + max_epochs=12, # 最大训练轮次 + val_interval=1) # 验证间隔。每个 epoch 验证一次 +val_cfg = dict(type='ValLoop') # 验证循环的类型 +test_cfg = dict(type='TestLoop') # 测试循环的类型 +``` + +### 优化相关配置 + +`optim_wrapper` 是配置优化相关设置的字段。优化器封装(OptimWrapper)不仅提供了优化器的功能,还支持梯度裁剪、混合精度训练等功能。更多内容请看[优化器封装教程](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/optim_wrapper.html) 。 + +```python +optim_wrapper = dict( # 优化器封装的配置 + type='OptimWrapper', # 优化器封装的类型。可以切换至 AmpOptimWrapper 来启用混合精度训练 + optimizer=dict( # 优化器配置。支持 PyTorch 的各种优化器。请参考 https://pytorch.org/docs/stable/optim.html#algorithms + type='SGD', # 随机梯度下降优化器 + lr=0.02, # 基础学习率 + momentum=0.9, # 带动量的随机梯度下降 + weight_decay=0.0001), # 权重衰减 + clip_grad=None, # 梯度裁剪的配置,设置为 None 关闭梯度裁剪。使用方法请见 https://mmengine.readthedocs.io/en/latest/tutorials/optimizer.html + ) +``` + +`param_scheduler` 字段用于配置参数调度器(Parameter Scheduler)来调整优化器的超参数(例如学习率和动量)。 用户可以组合多个调度器来创建所需的参数调整策略。 在 [参数调度器教程](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/param_scheduler.html) 和 [参数调度器 API 文档](https://mmengine.readthedocs.io/zh_CN/latest/api/generated/mmengine.optim._ParamScheduler.html#mmengine.optim._ParamScheduler) 中查找更多信息。 + +```python +param_scheduler = [ + dict( + type='LinearLR', # 使用线性学习率预热 + start_factor=0.001, # 学习率预热的系数 + by_epoch=False, # 按 iteration 更新预热学习率 + begin=0, # 从第一个 iteration 开始 + end=500), # 到第 500 个 iteration 结束 + dict( + type='MultiStepLR', # 在训练过程中使用 multi step 学习率策略 + by_epoch=True, # 按 epoch 更新学习率 + begin=0, # 从第一个 epoch 开始 + end=12, # 到第 12 个 epoch 结束 + milestones=[8, 11], # 在哪几个 epoch 进行学习率衰减 + gamma=0.1) # 学习率衰减系数 +] +``` + +### 钩子配置 + +用户可以在训练、验证和测试循环上添加钩子,以便在运行期间插入一些操作。配置中有两种不同的钩子字段,一种是 `default_hooks`,另一种是 `custom_hooks`。 + +`default_hooks` 是一个字典,用于配置运行时必须使用的钩子。这些钩子具有默认优先级,如果未设置,runner 将使用默认值。如果要禁用默认钩子,用户可以将其配置设置为 `None`。更多内容请看 [钩子教程](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/hook.html) 。 + +```python +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', interval=1), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='DetVisualizationHook')) +``` + +`custom_hooks` 是一个列表。用户可以在这个字段中加入自定义的钩子。 + +```python +custom_hooks = [] +``` + +### 运行相关配置 + +```python +default_scope = 'mmdet' # 默认的注册器域名,默认从此注册器域中寻找模块。请参考 https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/registry.html + +env_cfg = dict( + cudnn_benchmark=False, # 是否启用 cudnn benchmark + mp_cfg=dict( # 多进程设置 + mp_start_method='fork', # 使用 fork 来启动多进程。'fork' 通常比 'spawn' 更快,但可能存在隐患。请参考 https://github.com/pytorch/pytorch/issues/1355 + opencv_num_threads=0), # 关闭 opencv 的多线程以避免系统超负荷 + dist_cfg=dict(backend='nccl'), # 分布式相关设置 +) + +vis_backends = [dict(type='LocalVisBackend')] # 可视化后端,请参考 https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/visualization.html +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') +log_processor = dict( + type='LogProcessor', # 日志处理器用于处理运行时日志 + window_size=50, # 日志数值的平滑窗口 + by_epoch=True) # 是否使用 epoch 格式的日志。需要与训练循环的类型保存一致。 + +log_level = 'INFO' # 日志等级 +load_from = None # 从给定路径加载模型检查点作为预训练模型。这不会恢复训练。 +resume = False # 是否从 `load_from` 中定义的检查点恢复。 如果 `load_from` 为 None,它将恢复 `work_dir` 中的最新检查点。 +``` + +## Iter-based 配置 + +MMEngine 的 Runner 除了基于轮次的训练循环(epoch)外,还提供了基于迭代(iteration)的训练循环。 +要使用基于迭代的训练,用户应该修改 `train_cfg`、`param_scheduler`、`train_dataloader`、`default_hooks` 和 `log_processor`。 +以下是将基于 epoch 的 RetinaNet 配置更改为基于 iteration 的示例:configs/retinanet/retinanet_r50_fpn_90k_coco.py + +```python +# iter-based 训练配置 +train_cfg = dict( + _delete_=True, # 忽略继承的配置文件中的值(可选) + type='IterBasedTrainLoop', # iter-based 训练循环 + max_iters=90000, # 最大迭代次数 + val_interval=10000) # 每隔多少次进行一次验证 + + +# 将参数调度器修改为 iter-based +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=90000, + by_epoch=False, + milestones=[60000, 80000], + gamma=0.1) +] + +# 切换至 InfiniteSampler 来避免 dataloader 重启 +train_dataloader = dict(sampler=dict(type='InfiniteSampler')) + +# 将模型检查点保存间隔设置为按 iter 保存 +default_hooks = dict(checkpoint=dict(by_epoch=False, interval=10000)) + +# 将日志格式修改为 iter-based +log_processor = dict(by_epoch=False) +``` + +## 配置文件继承 + +在 `config/_base_` 文件夹下有 4 个基本组件类型,分别是:数据集(dataset),模型(model),训练策略(schedule)和运行时的默认设置(default runtime)。许多方法,例如 Faster R-CNN、Mask R-CNN、Cascade R-CNN、RPN、SSD 能够很容易地构建出来。由 `_base_` 下的组件组成的配置,被我们称为 _原始配置(primitive)_。 + +对于同一文件夹下的所有配置,推荐**只有一个**对应的**原始配置**文件。所有其他的配置文件都应该继承自这个**原始配置**文件。这样就能保证配置文件的最大继承深度为 3。 + +为了便于理解,我们建议贡献者继承现有方法。例如,如果在 Faster R-CNN 的基础上做了一些修改,用户首先可以通过指定 `_base_ = ../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py` 来继承基础的 Faster R-CNN 结构,然后修改配置文件中的必要参数以完成继承。 + +如果你在构建一个与任何现有方法不共享结构的全新方法,那么可以在 `configs` 文件夹下创建一个新的例如 `xxx_rcnn` 文件夹。 + +更多细节请参考 [MMEngine 配置文件教程](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html) 。 + +通过设置 `_base_` 字段,我们可以设置当前配置文件继承自哪些文件。 + +当 `_base_` 为文件路径字符串时,表示继承一个配置文件的内容。 + +```python +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' +``` + +当 `_base_` 是多个文件路径的列表时,表示继承多个文件。 + +```python +_base_ = [ + '../_base_/models/mask-rcnn_r50_fpn.py', + '../_base_/datasets/coco_instance.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +``` + +如果需要检查配置文件,可以通过运行 `python tools/misc/print_config.py /PATH/TO/CONFIG` 来查看完整的配置。 + +### 忽略基础配置文件里的部分内容 + +有时,您也许会设置 `_delete_=True` 去忽略基础配置文件里的一些域内容。 您也许可以参照 [MMEngine 配置文件教程](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html) 来获得一些简单的指导。 + +在 MMDetection 里,例如为了改变 Mask R-CNN 的主干网络的某些内容: + +```python +model = dict( + type='MaskRCNN', + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict(...), + rpn_head=dict(...), + roi_head=dict(...)) +``` + +基础配置的 `Mask R-CNN` 使用 `ResNet-50`,在需要将主干网络改成 `HRNet` 的时候,因为 `HRNet` 和 `ResNet` 中有不同的字段,需要使用 `_delete_=True` 将新的键去替换 `backbone` 域内所有老的键。 + +```python +_base_ = '../mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py' +model = dict( + backbone=dict( + _delete_=True, + type='HRNet', + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))), + init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w32')), + neck=dict(...)) +``` + +### 使用配置文件里的中间变量 + +配置文件里会使用一些中间变量,例如数据集里的 `train_pipeline`/`test_pipeline`。我们在定义新的 `train_pipeline`/`test_pipeline` 之后,需要将它们传递到 `data` 里。例如,我们想在训练或测试时,改变 Mask R-CNN 的多尺度策略 (multi scale strategy),`train_pipeline`/`test_pipeline` 是我们想要修改的中间变量。 + +```python +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +``` + +我们首先定义新的 `train_pipeline`/`test_pipeline` 然后传递到 `data` 里。 + +同样的,如果我们想从 `SyncBN` 切换到 `BN` 或者 `MMSyncBN`,我们需要修改配置文件里的每一个 `norm_cfg`。 + +```python +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + backbone=dict(norm_cfg=norm_cfg), + neck=dict(norm_cfg=norm_cfg), + ...) +``` + +### 复用 \_base\_ 文件中的变量 + +如果用户希望在当前配置中复用 base 文件中的变量,则可以通过使用 `{{_base_.xxx}}` 的方式来获取对应变量的拷贝。例如: + +```python +_base_ = './mask-rcnn_r50_fpn_1x_coco.py' + +a = {{_base_.model}} # 变量 a 等于 _base_ 中定义的 model +``` + +## 通过脚本参数修改配置 + +当运行 `tools/train.py` 和 `tools/test.py` 时,可以通过 `--cfg-options` 来修改配置文件。 + +- 更新字典链中的配置 + + 可以按照原始配置文件中的 dict 键顺序地指定配置预选项。例如,使用 `--cfg-options model.backbone.norm_eval=False` 将模型主干网络中的所有 BN 模块都改为 `train` 模式。 + +- 更新配置列表中的键 + + 在配置文件里,一些字典型的配置被包含在列表中。例如,数据训练流程 `data.train.pipeline` 通常是一个列表,比如 `[dict(type='LoadImageFromFile'), ...]`。如果需要将 `'LoadImageFromFile'` 改成 `'LoadImageFromWebcam'`,需要写成下述形式: `--cfg-options data.train.pipeline.0.type=LoadImageFromNDArray`. + +- 更新列表或元组的值 + + 如果要更新的值是列表或元组。例如,配置文件通常设置 `model.data_preprocessor.mean=[123.675, 116.28, 103.53]`. 如果需要改变这个键,可以通过 `--cfg-options model.data_preprocessor.mean="[127,127,127]"` 来重新设置。需要注意,引号 " 是支持列表或元组数据类型所必需的,并且在指定值的引号内**不允许**有空格。 + +## 配置文件名称风格 + +我们遵循以下样式来命名配置文件。建议贡献者遵循相同的风格。 + +``` +{algorithm name}_{model component names [component1]_[component2]_[...]}_{training settings}_{training dataset information}_{testing dataset information}.py +``` + +文件名分为五个部分。 每个部分用`_`连接,每个部分内的单词应该用`-`连接。 + +- `{algorithm name}`: 算法的名称。 它可以是检测器名称,例如 `faster-rcnn`、`mask-rcnn` 等。也可以是半监督或知识蒸馏算法,例如 `soft-teacher`、`lad` 等等 +- `{component names}`: 算法中使用的组件名称,如 backbone、neck 等。例如 `r50-caffe_fpn_gn-head` 表示在算法中使用 caffe 版本的 ResNet50、FPN 和 使用了 Group Norm 的检测头。 +- `{training settings}`: 训练设置的信息,例如 batch 大小、数据增强、损失、参数调度方式和训练最大轮次/迭代。 例如:`4xb4-mixup-giou-coslr-100e` 表示使用 8 个 gpu 每个 gpu 4 张图、mixup 数据增强、GIoU loss、余弦退火学习率,并训练 100 个 epoch。 + 缩写介绍: + - `{gpu x batch_per_gpu}`: GPU 数和每个 GPU 的样本数。`bN` 表示每个 GPU 上的 batch 大小为 N。例如 `4x4b` 是 4 个 GPU 每个 GPU 4 张图的缩写。如果没有注明,默认为 8 卡每卡 2 张图。 + - `{schedule}`: 训练方案,选项是 `1x`、 `2x`、 `20e` 等。`1x` 和 `2x` 分别代表 12 epoch 和 24 epoch,`20e` 在级联模型中使用,表示 20 epoch。对于 `1x`/`2x`,初始学习率在第 8/16 和第 11/22 epoch 衰减 10 倍;对于 `20e` ,初始学习率在第 16 和第 19 epoch 衰减 10 倍。 +- `{training dataset information}`: 训练数据集,例如 `coco`, `coco-panoptic`, `cityscapes`, `voc-0712`, `wider-face`。 +- `{testing dataset information}` (可选): 测试数据集,用于训练和测试在不同数据集上的模型配置。 如果没有注明,则表示训练和测试的数据集类型相同。 diff --git a/mmdetection/docs/zh_cn/user_guides/dataset_prepare.md b/mmdetection/docs/zh_cn/user_guides/dataset_prepare.md new file mode 100644 index 00000000..1caad856 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/dataset_prepare.md @@ -0,0 +1,362 @@ +## 数据集准备 + +### 基础检测数据集准备 + +MMDetection 支持多个公共数据集,包括 [COCO](https://cocodataset.org/), [Pascal VOC](http://host.robots.ox.ac.uk/pascal/VOC), [Cityscapes](https://www.cityscapes-dataset.com/) 和 [其他更多数据集](https://github.com/open-mmlab/mmdetection/tree/main/configs/_base_/datasets)。 + +一些公共数据集,比如 Pascal VOC 及其镜像数据集,或者 COCO 等数据集都可以从官方网站或者镜像网站获取。注意:在检测任务中,Pascal VOC 2012 是 Pascal VOC 2007 的无交集扩展,我们通常将两者一起使用。 我们建议将数据集下载,然后解压到项目外部的某个文件夹内,然后通过符号链接的方式,将数据集根目录链接到 `$MMDETECTION/data` 文件夹下, 如果你的文件夹结构和下方不同的话,你需要在配置文件中改变对应的路径。 + +我们提供了下载 COCO 等数据集的脚本,你可以运行 `python tools/misc/download_dataset.py --dataset-name coco2017` 下载 COCO 数据集。 对于中国境内的用户,我们也推荐通过开源数据平台 [OpenDataLab](https://opendatalab.com/?source=OpenMMLab%20GitHub) 来下载数据,以获得更好的下载体验。 + +更多用法请参考[数据集下载](./useful_tools.md#dataset-download) + +```text +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ ├── cityscapes +│ │ ├── annotations +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +│ ├── VOCdevkit +│ │ ├── VOC2007 +│ │ ├── VOC2012 +``` + +有些模型需要额外的 [COCO-stuff](http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip) 数据集,比如 HTC,DetectoRS 和 SCNet,你可以下载并解压它们到 `coco` 文件夹下。文件夹会是如下结构: + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── stuffthingmaps +``` + +PanopticFPN 等全景分割模型需要额外的 [COCO Panoptic](http://images.cocodataset.org/annotations/panoptic_annotations_trainval2017.zip) 数据集,你可以下载并解压它们到 `coco/annotations` 文件夹下。文件夹会是如下结构: + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── panoptic_train2017.json +│ │ │ ├── panoptic_train2017 +│ │ │ ├── panoptic_val2017.json +│ │ │ ├── panoptic_val2017 +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +``` + +Cityscape 数据集的标注格式需要转换,以与 COCO 数据集标注格式保持一致,使用 `tools/dataset_converters/cityscapes.py` 来完成转换: + +```shell +pip install cityscapesscripts + +python tools/dataset_converters/cityscapes.py \ + ./data/cityscapes \ + --nproc 8 \ + --out-dir ./data/cityscapes/annotations +``` + +### COCO Caption 数据集准备 + +COCO Caption 采用的是 COCO2014 数据集作为图片,并且使用了 karpathy 的标注, + +首先你需要下载 COCO2014 数据集 + +```shell +python tools/misc/download_dataset.py --dataset-name coco2014 --unzip +``` + +数据集会下载到当前路径的 `data/coco` 下。然后下载 karpathy 的标注 + +```shell +cd data/coco/annotations +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_train.json +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_val.json +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_test.json +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_val_gt.json +wget https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_test_gt.json +``` + +最终直接可用于训练和测试的数据集文件夹结构如下: + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── coco_karpathy_train.json +│ │ │ ├── coco_karpathy_test.json +│ │ │ ├── coco_karpathy_val.json +│ │ │ ├── coco_karpathy_val_gt.json +│ │ │ ├── coco_karpathy_test_gt.json +│ │ ├── train2014 +│ │ ├── val2014 +│ │ ├── test2014 +``` + +### COCO semantic 数据集准备 + +COCO 语义分割有两种类型标注,主要差别在于类别名定义不一样,因此处理方式也有两种,第一种是直接使用 stuffthingmaps 数据集,第二种是使用 panoptic 数据集。 + +**(1) 使用 stuffthingmaps 数据集** + +该数据集的下载地址为 [stuffthingmaps_trainval2017](http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip),请下载后解压到 `data/coco` 文件夹下。 + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── stuffthingmaps +``` + +该数据集不同于标准的 COCO 类别标注,其包括 172 个类: 80 thing 类、91 stuff 类和 1 个 'unlabeled',其每个类别的说明见 https://github.com/nightrome/cocostuff/blob/master/labels.md + +虽然只标注了 172 个类别,但是 `stuffthingmaps` 中最大标签 id 是 182,中间有些类别是没有标注的,并且第 0 类的 `unlabeled` 类别被移除。因此最终的 `stuffthingmaps` 图片中每个位置的值对应的类别关系见 https://github.com/kazuto1011/deeplab-pytorch/blob/master/data/datasets/cocostuff/labels.txt + +考虑到训练高效和方便用户,在开启训练或者评估前,我们需要将没有标注的 12 个类移除,这 12 个类的名字为: `street sign、hat、shoe、eye glasses、plate、mirror、window、desk、door、blender、hair brush`,最终可用于训练和评估的类别信息见 `mmdet/datasets/coco_semantic.py` + +你可以使用 `tools/dataset_converters/coco_stuff164k.py` 来完成将下载的 `stuffthingmaps` 转换为直接可以训练和评估的数据集,转换后的数据集文件夹结构如下: + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── stuffthingmaps +│ │ ├── stuffthingmaps_semseg +``` + +`stuffthingmaps_semseg` 即为新生成的可以直接训练和测试的 COCO 语义分割数据集。 + +**(2) 使用 panoptic 数据集** + +通过 panoptic 标注生成的语义分割数据集类别数相比使用 `stuffthingmaps` 数据集生成的会少一些。首先你需要准备全景分割标注,然后使用如下脚本完成转换 + +```shell +python tools/dataset_converters/prepare_coco_semantic_annos_from_panoptic_annos.py data/coco +``` + +转换后的数据集文件夹结构如下: + +```text +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── panoptic_train2017.json +│ │ │ ├── panoptic_train2017 +│ │ │ ├── panoptic_val2017.json +│ │ │ ├── panoptic_val2017 +│ │ │ ├── panoptic_semseg_train2017 +│ │ │ ├── panoptic_semseg_val2017 +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +``` + +`panoptic_semseg_train2017` 和 `panoptic_semseg_val2017` 即为新生成的可以直接训练和测试的 COCO 语义分割数据集。注意其类别信息就是 COCO 全景分割的类别信息,包括 thing 和 stuff。 + +### RefCOCO 数据集准备 + +[RefCOCO](https://github.com/lichengunc/refer)系列数据集的图像和注释可以通过运行 `tools/misc/download_dataset.py` 下载: + +```shell +python tools/misc/download_dataset.py --dataset-name refcoco --save-dir data/coco --unzip +``` + +然后,目录应该是这样的: + +```text +data +├── coco +│ ├── refcoco +│   │   ├── instances.json +│   │   ├── refs(google).p +│   │   └── refs(unc).p +│   ├── refcoco+ +│   │   ├── instances.json +│   │   └── refs(unc).p +│   ├── refcocog +│   │   ├── instances.json +│   │   ├── refs(google).p +│   │   └── refs(umd).p +| |── train2014 +``` + +### ADE20K 数据集准备 + +[ADE20K](http://groups.csail.mit.edu/vision/datasets/ADE20K/)数据集的图像和注释可以通过运行 `tools/misc/download_dataset.py` 下载: + +```shell +python tools/misc/download_dataset.py --dataset-name ade20k_2016 --save-dir data --unzip +``` + +然后将注释移至`data/ADEChallengeData2016`目录,并运行预处理脚本以产生coco格式注释: + +```shell +mv data/annotations_instance data/ADEChallengeData2016/ +mv data/categoryMapping.txt data/ADEChallengeData2016/ +mv data/imgCatIds.json data/ADEChallengeData2016/ +python tools/dataset_converters/ade20k2coco.py data/ADEChallengeData2016 --task panoptic +python tools/dataset_converters/ade20k2coco.py data/ADEChallengeData2016 --task instance +``` + +然后,目录应该是这样的: + +```text +data +├── ADEChallengeData2016 +│   ├── ade20k_instance_train.json +│   ├── ade20k_instance_val.json +│   ├── ade20k_panoptic_train +| | ├── ADE_train_00000001.png +| | ├── ADE_train_00000002.png +| | ├── ... +│   ├── ade20k_panoptic_train.json +│   ├── ade20k_panoptic_val +| | ├── ADE_val_00000001.png +| | ├── ADE_val_00000002.png +| | ├── ... +│   ├── ade20k_panoptic_val.json +│   ├── annotations +| | ├── training +| | | ├── ADE_train_00000001.png +| | | ├── ADE_train_00000002.png +| | | ├── ... +| | ├── validation +| | | ├── ADE_val_00000001.png +| | | ├── ADE_val_00000002.png +| | | ├── ... +│   ├── annotations_instance +| | ├── training +| | | ├── ADE_train_00000001.png +| | | ├── ADE_train_00000002.png +| | | ├── ... +| | ├── validation +| | | ├── ADE_val_00000001.png +| | | ├── ADE_val_00000002.png +| | | ├── ... +│   ├── categoryMapping.txt +│   ├── images +│   | ├── training +| | | ├── ADE_train_00000001.jpg +| | | ├── ADE_train_00000002.jpg +| | | ├── ... +| | ├── validation +| | | ├── ADE_val_00000001.jpg +| | | ├── ADE_val_00000002.jpg +| | | ├── ... +│   ├── imgCatIds.json +│   ├── objectInfo150.txt +| |── sceneCategories.txt +``` + +上述文件夹包括ADE20K的语义分割、实例分割和泛在分割的所有数据。 + +### 从 OpenDataLab 中下载 + +[OpenDataLab](https://opendatalab.com/) 为人工智能研究者提供免费开源的数据集,通过 OpenDataLab,研究者可以获得格式统一的各领域经典数据集。通过平台的搜索功能,研究者可以迅速便捷地找到自己所需数据集;通过平台的统一格式,研究者可以便捷地对跨数据集任务进行开发。 + +目前,MIM 支持使用一条命令行从 OpenDataLab 中下载 VOC 和 COCO 数据集,后续将支持更多数据集。你也可以直接访问 OpenDataLab 平台下载你所需的数据集,然后将其转化为 MMDetection 所要求的格式。 + +如果使用 MIM 下载,请确保版本大于 v0.3.8,你可以使用如下命令更新: + +```Bash +pip install -U openmim +``` + +```Bash +# install OpenXLab CLI tools +pip install -U openxlab +# log in OpenXLab, registry +openxlab login + +# download voc2007 and preprocess by MIM +mim download mmdet --dataset voc2007 + +# download voc2012 and preprocess by MIM +mim download mmdet --dataset voc2012 + +# download coco2017 and preprocess by MIM +mim download mmdet --dataset coco2017 +``` + +### ODinW 数据集准备 + +ODinW 数据集来自 GLIP 论文,用于评估预训练模型泛化性能。一共包括 ODinW-13 和 ODinW-35 两个版本,其中 ODinW-35 包括了 ODinW-13 的所有数据。 目前数据托管在 [huggingface](https://huggingface.co/GLIPModel/GLIP) + +请确保你提前安装好了 [git lfs](https://git-lfs.com), 然后按照如下命令下载 + +```shell +cd mmdetection + +git lfs install +# 我们不需要下载权重 +GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/GLIPModel/GLIP + +cd GLIP +git lfs pull --include="odinw_35" +``` + +下载完成后,目录结构如下所示: + +```text +mmdetection +├── GLIP +| ├── odinw_35 +| | ├── AerialMaritimeDrone.zip +| | ├── AmericanSignLanguageLetters.zip +... +``` + +你可以采用如下命令全部解压并移动到 `mmdetection/data` 路径下: + +```shell +#!/bin/bash + +folder="./GLIP/odinw_35/" + +find "$folder" -type f -name "*.zip" | while read -r file; do + unzip "$file" -d "$(dirname "$file")" +done + +mv GLIP/odinw_35 data/ +``` + +最终结构如下所示: + +```text +mmdetection +├── tools +├── configs +├── data +| ├── odinw_35 +| | ├── AerialMaritimeDrone +... +│ ├── coco +``` diff --git a/mmdetection/docs/zh_cn/user_guides/deploy.md b/mmdetection/docs/zh_cn/user_guides/deploy.md new file mode 100644 index 00000000..f796b004 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/deploy.md @@ -0,0 +1,174 @@ +# 模型部署 + +[MMDeploy](https://github.com/open-mmlab/mmdeploy) 是 OpenMMLab 的部署仓库,负责包括 MMPretrain、MMDetection 等在内的各算法库的部署工作。 +你可以从[这里](https://mmdeploy.readthedocs.io/zh_CN/1.x/04-supported-codebases/mmdet.html)获取 MMDeploy 对 MMDetection 部署支持的最新文档。 + +本文的结构如下: + +- [安装](#安装) +- [模型转换](#模型转换) +- [模型规范](#模型规范) +- [模型推理](#模型推理) + - [后端模型推理](#后端模型推理) + - [SDK 模型推理](#sdk-模型推理) +- [模型支持列表](#模型支持列表) +- + +## 安装 + +请参考[此处](https://mmdetection.readthedocs.io/en/latest/get_started.html)安装 mmdet。然后,按照[说明](https://mmdeploy.readthedocs.io/zh_CN/1.x/get_started.html#mmdeploy)安装 mmdeploy。 + +```{note} +如果安装的是 mmdeploy 预编译包,那么也请通过 'git clone https://github.com/open-mmlab/mmdeploy.git --depth=1' 下载 mmdeploy 源码。因为它包含了部署时要用到的配置文件 +``` + +## 模型转换 + +假设在安装步骤中,mmdetection 和 mmdeploy 代码库在同级目录下,并且当前的工作目录为 mmdetection 的根目录,那么以 [Faster R-CNN](https://github.com/open-mmlab/mmdetection/blob/main/configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py) 模型为例,你可以从[此处](https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth)下载对应的 checkpoint,并使用以下代码将之转换为 onnx 模型: + +```python +from mmdeploy.apis import torch2onnx +from mmdeploy.backend.sdk.export_info import export2SDK + +img = 'demo/demo.jpg' +work_dir = 'mmdeploy_models/mmdet/onnx' +save_file = 'end2end.onnx' +deploy_cfg = '../mmdeploy/configs/mmdet/detection/detection_onnxruntime_dynamic.py' +model_cfg = 'configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +model_checkpoint = 'faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth' +device = 'cpu' + +# 1. convert model to onnx +torch2onnx(img, work_dir, save_file, deploy_cfg, model_cfg, + model_checkpoint, device) + +# 2. extract pipeline info for inference by MMDeploy SDK +export2SDK(deploy_cfg, model_cfg, work_dir, pth=model_checkpoint, + device=device) +``` + +转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署[配置文件](https://github.com/open-mmlab/mmdeploy/tree/1.x/configs/mmdet)。 +文件的命名模式是: + +``` +{task}/{task}_{backend}-{precision}_{static | dynamic}_{shape}.py +``` + +其中: + +- **{task}:** mmdet 中的任务 + + mmdet 任务有2种:物体检测(detection)、实例分割(instance-seg)。例如,`RetinaNet`、`Faster R-CNN`、`DETR`等属于前者。`Mask R-CNN`、`SOLO`等属于后者。更多`模型-任务`的划分,请参考章节[模型支持列表](#模型支持列表)。 + + **请务必**使用 `detection/detection_*.py` 转换检测模型,使用 `instance-seg/instance-seg_*.py` 转换实例分割模型。 + +- **{backend}:** 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等 + +- **{precision}:** 推理精度。比如,fp16、int8。不填表示 fp32 + +- **{static | dynamic}:** 动态、静态 shape + +- **{shape}:** 模型输入的 shape 或者 shape 范围 + +在上例中,你也可以把 `Faster R-CNN` 转为其他后端模型。比如使用`detection_tensorrt-fp16_dynamic-320x320-1344x1344.py`,把模型转为 tensorrt-fp16 模型。 + +```{tip} +当转 tensorrt 模型时, --device 需要被设置为 "cuda" +``` + +## 模型规范 + +在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 `--work-dir` 指定的路路径下。 + +上例中的`mmdeploy_models/mmdet/onnx`,结构如下: + +``` +mmdeploy_models/mmdet/onnx +├── deploy.json +├── detail.json +├── end2end.onnx +└── pipeline.json +``` + +重要的是: + +- **end2end.onnx**: 推理引擎文件。可用 ONNX Runtime 推理 +- ***xxx*.json**: mmdeploy SDK 推理所需的 meta 信息 + +整个文件夹被定义为**mmdeploy SDK model**。换言之,**mmdeploy SDK model**既包括推理引擎,也包括推理 meta 信息。 + +## 模型推理 + +## 后端模型推理 + +以上述模型转换后的 `end2end.onnx` 为例,你可以使用如下代码进行推理: + +```python +from mmdeploy.apis.utils import build_task_processor +from mmdeploy.utils import get_input_shape, load_config +import torch + +deploy_cfg = '../mmdeploy/configs/mmdet/detection/detection_onnxruntime_dynamic.py' +model_cfg = 'configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py' +device = 'cpu' +backend_model = ['mmdeploy_models/mmdet/onnx/end2end.onnx'] +image = 'demo/demo.jpg' + +# read deploy_cfg and model_cfg +deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg) + +# build task and backend model +task_processor = build_task_processor(model_cfg, deploy_cfg, device) +model = task_processor.build_backend_model(backend_model) + +# process input image +input_shape = get_input_shape(deploy_cfg) +model_inputs, _ = task_processor.create_input(image, input_shape) + +# do model inference +with torch.no_grad(): + result = model.test_step(model_inputs) + +# visualize results +task_processor.visualize( + image=image, + model=model, + result=result[0], + window_name='visualize', + output_file='output_detection.png') +``` + +## SDK 模型推理 + +你也可以参考如下代码,对 SDK model 进行推理: + +```python +from mmdeploy_python import Detector +import cv2 + +img = cv2.imread('demo/demo.jpg') + +# create a detector +detector = Detector(model_path='mmdeploy_models/mmdet/onnx', + device_name='cpu', device_id=0) +# perform inference +bboxes, labels, masks = detector(img) + +# visualize inference result +indices = [i for i in range(len(bboxes))] +for index, bbox, label_id in zip(indices, bboxes, labels): + [left, top, right, bottom], score = bbox[0:4].astype(int), bbox[4] + if score < 0.3: + continue + + cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0)) + +cv2.imwrite('output_detection.png', img) +``` + +除了python API,mmdeploy SDK 还提供了诸如 C、C++、C#、Java等多语言接口。 +你可以参考[样例](https://github.com/open-mmlab/mmdeploy/tree/1.x/demo)学习其他语言接口的使用方法。 + +## 模型支持列表 + +请参考[这里](https://mmdeploy.readthedocs.io/zh_CN/1.x/04-supported-codebases/mmdet.html#id6) diff --git a/mmdetection/docs/zh_cn/user_guides/finetune.md b/mmdetection/docs/zh_cn/user_guides/finetune.md new file mode 100644 index 00000000..66bad94e --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/finetune.md @@ -0,0 +1,96 @@ +# 模型微调 + +在 COCO 数据集上预训练的检测器可以作为其他数据集(例如 CityScapes 和 KITTI 数据集)优质的预训练模型。 +本教程将指导用户如何把 [ModelZoo](../model_zoo.md) 中提供的模型用于其他数据集中并使得当前所训练的模型获得更好性能。 + +以下是在新数据集中微调模型需要的两个步骤。 + +- 按 [教程2:自定义数据集](../advanced_guides/customize_dataset.md) 中的方法对新数据集添加支持中的方法对新数据集添加支持 +- 按照本教程中所讨论方法,修改配置信息 + +接下来将会以 Cityscapes Dataset 上的微调过程作为例子,具体讲述用户需要在配置中修改的五个部分。 + +## 继承基础配置 + +为了减轻编写整个配置的负担并减少漏洞的数量, MMDetection V3.0 支持从多个现有配置中继承配置信息。微调 MaskRCNN 模型的时候,新的配置信息需要使用从 `_base_/models/mask_rcnn_r50_fpn.py` 中继承的配置信息来构建模型的基本结构。当使用 Cityscapes 数据集时,新的配置信息可以简便地从`_base_/datasets/cityscapes_instance.py` 中继承。对于训练过程的运行设置部分,例如 `logger settings`,配置文件可以从 `_base_/default_runtime.py` 中继承。对于训练计划的配置则可以从`_base_/schedules/schedule_1x.py` 中继承。这些配置文件存放于 `configs` 目录下,用户可以选择全部内容的重新编写而不是使用继承方法。 + +```python +_base_ = [ + '../_base_/models/mask_rcnn_r50_fpn.py', + '../_base_/datasets/cityscapes_instance.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_1x.py' +] +``` + +## Head 的修改 + +接下来新的配置还需要根据新数据集的类别数量对 Head 进行修改。只需要对 roi_head 中的 `num_classes`进行修改。修改后除了最后的预测模型的 Head 之外,预训练模型的权重的大部分都会被重新使用。 + +```python +model = dict( + roi_head=dict( + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=8, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)), + mask_head=dict( + type='FCNMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=8, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)))) +``` + +## 数据集的修改 + +用户可能还需要准备数据集并编写有关数据集的配置,可在 [Customize Datasets](../advanced_guides/customize_dataset.md) 中获取更多信息。目前 MMDetection V3.0 的配置文件已经支持 VOC、WIDERFACE、COCO、LIVS、OpenImages、DeepFashion、Objects365 和 Cityscapes Dataset 的数据集信息。 + +## 训练策略的修改 + +微调超参数与默认的训练策略不同。它通常需要更小的学习率和更少的训练回合。 + +```python +# 优化器 +# batch size 为 8 时的 lr 配置 +optim_wrapper = dict(optimizer=dict(lr=0.01)) + +# 学习率 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=8, + by_epoch=True, + milestones=[7], + gamma=0.1) +] + +# 设置 max epoch +train_cfg = dict(max_epochs=8) + +# 设置 log config +default_hooks = dict(logger=dict(interval=100)), + +``` + +## 使用预训练模型 + +如果要使用预训练模型,可以在 `load_from` 中查阅新的配置信息,用户需要在训练开始之前下载好需要的模型权重,从而避免在训练过程中浪费了宝贵时间。 + +```python +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth' # noqa +``` diff --git a/mmdetection/docs/zh_cn/user_guides/index.rst b/mmdetection/docs/zh_cn/user_guides/index.rst new file mode 100644 index 00000000..5abc50ad --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/index.rst @@ -0,0 +1,34 @@ +训练 & 测试 +************** + +MMDetection 在 `Model Zoo `_ 中提供了数百个预训练的检测模型, +并支持多种标准数据集格式,包括 Pascal VOC、COCO、CityScapes、LVIS 等。本文档将展示如何使用这些模型和数据集来执行常见的训练和测试任务: + +.. toctree:: + :maxdepth: 1 + + config.md + inference.md + dataset_prepare.md + test.md + train.md + new_model.md + finetune.md + test_results_submission.md + init_cfg.md + single_stage_as_rpn.md + semi_det.md + + +实用工具 +************ + +.. toctree:: + :maxdepth: 1 + + useful_tools.md + useful_hooks.md + visualization.md + robustness_benchmarking.md + deploy.md + label_studio.md diff --git a/mmdetection/docs/zh_cn/user_guides/inference.md b/mmdetection/docs/zh_cn/user_guides/inference.md new file mode 100644 index 00000000..a0fb08fa --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/inference.md @@ -0,0 +1,438 @@ +# 使用已有模型在标准数据集上进行推理 + +MMDetection 提供了许多预训练好的检测模型,可以在 [Model Zoo](https://mmdetection.readthedocs.io/zh_CN/latest/model_zoo.html) 查看具体有哪些模型。 + +推理具体指使用训练好的模型来检测图像上的目标,本文将会展示具体步骤。 + +在 MMDetection 中,一个模型被定义为一个[配置文件](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/config.html) 和对应被存储在 checkpoint 文件内的模型参数的集合。 + +首先,我们建议从 [RTMDet](https://github.com/open-mmlab/mmdetection/tree/main/configs/rtmdet) 开始,其 [配置](https://github.com/open-mmlab/mmdetection/blob/main/configs/rtmdet/rtmdet_l_8xb32-300e_coco.py) 文件和 [checkpoint](https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_l_8xb32-300e_coco/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth) 文件在此。 +我们建议将 checkpoint 文件下载到 `checkpoints` 文件夹内。 + +## 推理的高层编程接口——推理器 + +在 OpenMMLab 中,所有的推理操作都被统一到了推理器 `Inferencer` 中。推理器被设计成为一个简洁易用的 API,它在不同的 OpenMMLab 库中都有着非常相似的接口。 +下面介绍的演示样例都放在 [demo/inference_demo.ipynb](https://github.com/open-mmlab/mmdetection/blob/main/demo/inference_demo.ipynb) 中方便大家尝试。 + +### 基础用法 + +使用 `DetInferencer`,您只需 3 行代码就可以获得推理结果。 + +```python +from mmdet.apis import DetInferencer + +# 初始化模型 +inferencer = DetInferencer('rtmdet_tiny_8xb32-300e_coco') + +# 推理示例图片 +inferencer('demo/demo.jpg', show=True) +``` + +可视化结果将被显示在一个新窗口中: + +
    + +
    + +```{note} +如果你在没有 GUI 的服务器上,或者通过禁用 X11 转发的 SSH 隧道运行以上命令,`show` 选项将不起作用。然而,你仍然可以通过设置 `out_dir` 参数将可视化数据保存到文件。阅读 [储存结果](#储存结果) 了解详情。 +``` + +### 初始化 + +每个推理器必须使用一个模型进行初始化。初始化时,可以手动选择推理设备。 + +#### 模型初始化 + +- 要用 MMDetection 的预训练模型进行推理,只需要把它的名字传给参数 `model`,权重将自动从 OpenMMLab 的模型库中下载和加载。 + + ```python + inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco') + ``` + + 在 MMDetection 中有一个非常容易的方法,可以列出所有模型名称。 + + ```python + # models 是一个模型名称列表,它们将自动打印 + models = DetInferencer.list_models('mmdet') + ``` + + 你可以通过将权重的路径或 URL 传递给 `weights` 来让推理器加载自定义的权重。 + + ```python + inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', weights='path/to/rtmdet.pth') + ``` + +- 要加载自定义的配置和权重,你可以把配置文件的路径传给 `model`,把权重的路径传给 `weights`。 + + ```python + inferencer = DetInferencer(model='path/to/rtmdet_config.py', weights='path/to/rtmdet.pth') + ``` + +- 默认情况下,[MMEngine](https://github.com/open-mmlab/mmengine/) 会在训练模型时自动将配置文件转储到权重文件中。如果你有一个在 MMEngine 上训练的权重,你也可以将权重文件的路径传递给 `weights`,而不需要指定 `model`: + + ```python + # 如果无法在权重中找到配置文件,则会引发错误。目前 MMDetection 模型库中只有 ddq-detr-4scale_r50 的权重可以这样加载。 + inferencer = DetInferencer(weights='https://download.openmmlab.com/mmdetection/v3.0/ddq/ddq-detr-4scale_r50_8xb2-12e_coco/ddq-detr-4scale_r50_8xb2-12e_coco_20230809_170711-42528127.pth') + ``` + +- 传递配置文件到 `model` 而不指定 `weights` 则会产生一个随机初始化的模型。 + +#### 推理设备 + +每个推理器实例都会跟一个设备绑定。默认情况下,最佳设备是由 [MMEngine](https://github.com/open-mmlab/mmengine/) 自动决定的。你也可以通过指定 `device` 参数来改变设备。例如,你可以使用以下代码在 GPU 1 上创建一个推理器。 + +```python +inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', device='cuda:1') +``` + +如要在 CPU 上创建一个推理器: + +```python +inferencer = DetInferencer(model='rtmdet_tiny_8xb32-300e_coco', device='cpu') +``` + +请参考 [torch.device](https://pytorch.org/docs/stable/tensor_attributes.html#torch.device) 了解 `device` 参数支持的所有形式。 + +### 推理 + +当推理器初始化后,你可以直接传入要推理的原始数据,从返回值中获取推理结果。 + +#### 输入 + +输入可以是以下任意一种格式: + +- str: 图像的路径/URL。 + + ```python + inferencer('demo/demo.jpg') + ``` + +- array: 图像的 numpy 数组。它应该是 BGR 格式。 + + ```python + import mmcv + array = mmcv.imread('demo/demo.jpg') + inferencer(array) + ``` + +- list: 基本类型的列表。列表中的每个元素都将单独处理。 + + ```python + inferencer(['img_1.jpg', 'img_2.jpg]) + # 列表内混合类型也是允许的 + inferencer(['img_1.jpg', array]) + ``` + +- str: 目录的路径。目录中的所有图像都将被处理。 + + ```python + inferencer('path/to/your_imgs/') + ``` + +#### 输出 + +默认情况下,每个推理器都以字典格式返回预测结果。 + +- `visualization` 包含可视化的预测结果。但默认情况下,它是一个空列表,除非 `return_vis=True`。 + +- `predictions` 包含以 json-可序列化格式返回的预测结果。 + +```python +{ + 'predictions' : [ + # 每个实例都对应于一个输入图像 + { + 'labels': [...], # 整数列表,长度为 (N, ) + 'scores': [...], # 浮点列表,长度为 (N, ) + 'bboxes': [...], # 2d 列表,形状为 (N, 4),格式为 [min_x, min_y, max_x, max_y] + }, + ... + ], + 'visualization' : [ + array(..., dtype=uint8), + ] + } +``` + +如果你想要从模型中获取原始输出,可以将 `return_datasamples` 设置为 `True` 来获取原始的 [DataSample](advanced_guides/structures.md),它将存储在 `predictions` 中。 + +#### 储存结果 + +除了从返回值中获取预测结果,你还可以通过设置 `out_dir` 和 `no_save_pred`/`no_save_vis` 参数将预测结果和可视化结果导出到文件中。 + +```python +inferencer('demo/demo.jpg', out_dir='outputs/', no_save_pred=False) +``` + +结果目录结构如下: + +```text +outputs +├── preds +│ └── demo.json +└── vis + └── demo.jpg +``` + +#### 批量推理 + +你可以通过设置 `batch_size` 来自定义批量推理的批大小。默认批大小为 1。 + +### API + +这里列出了推理器详尽的参数列表。 + +- **DetInferencer.\_\_init\_\_():** + +| 参数 | 类型 | 默认值 | 描述 | +| --------------- | ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `model` | str , 可选 | None | 配置文件的路径或 metafile 中定义的模型名称。例如,可以是 'rtmdet-s' 或 'rtmdet_s_8xb32-300e_coco' 或 'configs/rtmdet/rtmdet_s_8xb32-300e_coco.py'。如果未指定模型,用户必须提供 MMEngine 保存的包含配置字符串的 "weights"。 | +| `weights` | str, 可选 | None | 模型权重文件的路径。如果未指定且 `model` 是 metafile 中的模型名称,权重将从 metafile 中加载。 | +| `device` | str, 可选 | None | 推理使用的设备,接受 `torch.device` 允许的所有字符串。例如,'cuda:0' 或 'cpu'。如果为 None,将自动使用可用设备。 默认为 None。 | +| `scope` | str, 可选 | 'mmdet' | 模型的”域名“。 | +| `palette` | str | 'none' | 用于可视化的配色。优先顺序为 palette -> config -> checkpoint。 | +| `show_progress` | bool | True | 控制是否在推理过程中显示进度条。 | + +- **DetInferencer.\_\_call\_\_()** + +| 参数 | 类型 | 默认值 | 描述 | +| -------------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `inputs` | str/list/tuple/np.array | **必需** | 它可以是一个图片/文件夹的路径,一个 numpy 数组,或者是一个包含图片路径或 numpy 数组的列表/元组 | +| `batch_size` | int | 1 | 推理的批大小。 | +| `return_vis` | bool | False | 是否返回可视化结果。 | +| `show` | bool | False | 是否在弹出窗口中显示可视化结果。 | +| `wait_time` | float | 0 | 弹窗展示可视化结果的时间间隔。 | +| `no_save_vis` | bool | False | 是否将可视化结果保存到 `out_dir`。默认为保存。 | +| `draw_pred` | bool | True | 是否绘制预测的边界框。 | +| `pred_score_thr` | float | 0.3 | 显示预测框的最低置信度。 | +| `return_datasamples` | bool | False | 是否将结果作为 `DetDataSample` 返回。 如果为 False,则结果将被打包到一个 dict 中。 | +| `print_result` | bool | False | 是否将推理结果打印到控制台。 | +| `no_save_pred` | bool | True | 是否将推理结果保存到 `out_dir`。默认为不保存。 | +| `out_dir` | str | '' | 结果的输出目录。 | +| `texts` | str/list\[str\],可选 | None | 文本提示词。 | +| `stuff_texts` | str/list\[str\],可选 | None | 物体文本提示词。 | +| `custom_entities` | bool | False | 是否使用自定义实体。只用于 GLIP 算法。 | +| \*\*kwargs | | | 传递给 :meth:`preprocess`、:meth:`forward`、:meth:`visualize` 和 :meth:`postprocess` 的其他关键字参数。kwargs 中的每个关键字都应在相应的 `preprocess_kwargs`、`forward_kwargs`、`visualize_kwargs` 和 `postprocess_kwargs` 中。 | + +## 演示脚本样例 + +我们还提供了四个演示脚本,它们是使用高层编程接口实现的。[源码在此](https://github.com/open-mmlab/mmdetection/blob/main/demo) 。 + +### 图片样例 + +这是在单张图片上进行推理的脚本。 + +```shell +python demo/image_demo.py \ + ${IMAGE_FILE} \ + ${CONFIG_FILE} \ + [--weights ${WEIGHTS}] \ + [--device ${GPU_ID}] \ + [--pred-score-thr ${SCORE_THR}] +``` + +运行样例: + +```shell +python demo/image_demo.py demo/demo.jpg \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + --weights checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --device cpu +``` + +### 摄像头样例 + +这是使用摄像头实时图片的推理脚本。 + +```shell +python demo/webcam_demo.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--device ${GPU_ID}] \ + [--camera-id ${CAMERA-ID}] \ + [--score-thr ${SCORE_THR}] +``` + +运行样例: + +```shell +python demo/webcam_demo.py \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth +``` + +### 视频样例 + +这是在视频样例上进行推理的脚本。 + +```shell +python demo/video_demo.py \ + ${VIDEO_FILE} \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--device ${GPU_ID}] \ + [--score-thr ${SCORE_THR}] \ + [--out ${OUT_FILE}] \ + [--show] \ + [--wait-time ${WAIT_TIME}] +``` + +运行样例: + +```shell +python demo/video_demo.py demo/demo.mp4 \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --out result.mp4 +``` + +#### 视频样例,显卡加速版本 + +这是在视频样例上进行推理的脚本,使用显卡加速。 + +```shell +python demo/video_gpuaccel_demo.py \ + ${VIDEO_FILE} \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--device ${GPU_ID}] \ + [--score-thr ${SCORE_THR}] \ + [--nvdecode] \ + [--out ${OUT_FILE}] \ + [--show] \ + [--wait-time ${WAIT_TIME}] + +``` + +运行样例: + +```shell +python demo/video_gpuaccel_demo.py demo/demo.mp4 \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --nvdecode --out result.mp4 +``` + +### 大图推理样例 + +这是在大图上进行切片推理的脚本。 + +```shell +python demo/large_image_demo.py \ + ${IMG_PATH} \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + --device ${GPU_ID} \ + --show \ + --tta \ + --score-thr ${SCORE_THR} \ + --patch-size ${PATCH_SIZE} \ + --patch-overlap-ratio ${PATCH_OVERLAP_RATIO} \ + --merge-iou-thr ${MERGE_IOU_THR} \ + --merge-nms-type ${MERGE_NMS_TYPE} \ + --batch-size ${BATCH_SIZE} \ + --debug \ + --save-patch +``` + +运行样例: + +```shell +# inferecnce without tta +wget -P checkpoint https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r101_fpn_2x_coco/faster_rcnn_r101_fpn_2x_coco_bbox_mAP-0.398_20200504_210455-1d2dac9c.pth + +python demo/large_image_demo.py \ + demo/large_image.jpg \ + configs/faster_rcnn/faster-rcnn_r101_fpn_2x_coco.py \ + checkpoint/faster_rcnn_r101_fpn_2x_coco_bbox_mAP-0.398_20200504_210455-1d2dac9c.pth + +# inference with tta +wget -P checkpoint https://download.openmmlab.com/mmdetection/v2.0/retinanet/retinanet_r50_fpn_1x_coco/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth + +python demo/large_image_demo.py \ + demo/large_image.jpg \ + configs/retinanet/retinanet_r50_fpn_1x_coco.py \ + checkpoint/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth --tta +``` + +## 多模态算法的推理和验证 + +随着多模态视觉算法的不断发展,MMDetection 也完成了对这类算法的支持。这一小节我们通过 GLIP 算法和模型来演示如何使用对应多模态算法的 demo 和 eval 脚本。同时 MMDetection 也在 projects 下完成了 [gradio_demo 项目](../../../projects/gradio_demo/),用户可以参照[文档](../../../projects/gradio_demo/README.md)在本地快速体验 MMDetection 中支持的各类图片输入的任务。 + +### 模型准备 + +首先需要安装多模态依赖: + +```shell +# if source +pip install -r requirements/multimodal.txt + +# if wheel +mim install mmdet[multimodal] +``` + +MMDetection 已经集成了 glip 算法和模型,可以直接使用链接下载使用: + +```shell +cd mmdetection +wget https://download.openmmlab.com/mmdetection/v3.0/glip/glip_tiny_a_mmdet-b3654169.pth +``` + +### 推理演示 + +下载完成后我们就可以利用 `demo` 下的多模态推理脚本完成推理: + +```shell +python demo/image_demo.py demo/demo.jpg glip_tiny_a_mmdet-b3654169.pth --texts bench +``` + +demo 效果如下图所示: + +
    + +
    + +如果想进行多种类型的识别,需要使用 `xx. xx` 的格式在 `--texts` 字段后声明目标类型: + +```shell +python demo/image_demo.py demo/demo.jpg glip_tiny_a_mmdet-b3654169.pth --texts 'bench. car' +``` + +结果如下图所示: + +
    + +
    + +推理脚本还支持输入一个句子作为 `--texts` 字段的输入: + +```shell +python demo/image_demo.py demo/demo.jpg glip_tiny_a_mmdet-b3654169.pth --texts 'There are a lot of cars here.' +``` + +结果可以参考下图: + +
    + +
    + +### 验证演示 + +MMDetection 支持后的 GLIP 算法对比官方版本没有精度上的损失, benchmark 如下所示: + +| Model | official mAP | mmdet mAP | +| ----------------------- | :----------: | :-------: | +| glip_A_Swin_T_O365.yaml | 42.9 | 43.0 | +| glip_Swin_T_O365.yaml | 44.9 | 44.9 | +| glip_Swin_L.yaml | 51.4 | 51.3 | + +用户可以使用 `test.py` 脚本对模型精度进行验证,使用如下所示: + +```shell +# 1 gpu +python tools/test.py configs/glip/glip_atss_swin-t_fpn_dyhead_pretrain_obj365.py glip_tiny_a_mmdet-b3654169.pth + +# 8 GPU +./tools/dist_test.sh configs/glip/glip_atss_swin-t_fpn_dyhead_pretrain_obj365.py glip_tiny_a_mmdet-b3654169.pth 8 +``` diff --git a/mmdetection/docs/zh_cn/user_guides/init_cfg.md b/mmdetection/docs/zh_cn/user_guides/init_cfg.md new file mode 100644 index 00000000..b58b19d5 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/init_cfg.md @@ -0,0 +1,161 @@ +# 权重初始化 + +在训练过程中,适当的初始化策略有利于加快训练速度或获得更⾼的性能。 [MMCV](https://github.com/open-mmlab/mmcv/blob/master/mmcv/cnn/utils/weight_init.py) 提供了一些常⽤的初始化模块的⽅法,如 `nn.Conv2d`。 MMdetection 中的模型初始化主要使⽤ `init_cfg`。⽤⼾可以通过以下两个步骤来初始化模型: + +1. 在 `model_cfg` 中为模型或其组件定义 `init_cfg`,但⼦组件的 `init_cfg` 优先级更⾼,会覆盖⽗模块的 `init_cfg` 。 +2. 像往常一样构建模型,然后显式调⽤ `model.init_weights()` ⽅法,此时模型参数将会被按照配置文件写法进行初始化。 + +MMdetection 初始化工作流的高层 API 调用流程是: + +model_cfg(init_cfg) -> build_from_cfg -> model -> init_weight() -> initialize(self, self.init_cfg) -> children's init_weight() + +### 描述 + +它的数据类型是 dict 或者 list\[dict\],包含了下列键值: + +- `type` (str),包含 `INTIALIZERS` 中的初始化器名称,后面跟着初始化器的参数。 +- `layer`(str 或 list\[str\]),包含 Pytorch 或 MMCV 中基本层的名称,以及将被初始化的可学习参数,例如 `'Conv2d'`,`'DeformConv2d'`。 +- `override` (dict 或 list\[dict\]),包含不继承⾃ `BaseModule` 且其初始化配置与 `layer` 键中的其他层不同的⼦模块。 `type` 中定义的初始化器将适⽤于 `layer` 中定义的所有层,因此如果⼦模块不是 `BaseModule` 的派⽣类但可以与 `layer` 中的层相同的⽅式初始化,则不需要使⽤ `override`。`override` 包含了: + - `type` 后跟初始化器的参数; + - `name` 用以指⽰将被初始化的⼦模块。 + +### 初始化参数 + +从 `mmcv.runner.BaseModule` 或 `mmdet.models` 继承一个新模型。这里我们用 FooModel 来举个例子。 + +```python +import torch.nn as nn +from mmcv.runner import BaseModule + +class FooModel(BaseModule) + def __init__(self, + arg1, + arg2, + init_cfg=None): + super(FooModel, self).__init__(init_cfg) + ... +``` + +- 直接在代码中使⽤ `init_cfg` 初始化模型 + + ```python + import torch.nn as nn + from mmcv.runner import BaseModule + # or directly inherit mmdet models + + class FooModel(BaseModule) + def __init__(self, + arg1, + arg2, + init_cfg=XXX): + super(FooModel, self).__init__(init_cfg) + ... + ``` + +- 在 `mmcv.Sequential` 或 `mmcv.ModuleList` 代码中直接使⽤ `init_cfg` 初始化模型 + + ```python + from mmcv.runner import BaseModule, ModuleList + + class FooModel(BaseModule) + def __init__(self, + arg1, + arg2, + init_cfg=None): + super(FooModel, self).__init__(init_cfg) + ... + self.conv1 = ModuleList(init_cfg=XXX) + ``` + +- 使⽤配置⽂件中的 `init_cfg` 初始化模型 + + ```python + model = dict( + ... + model = dict( + type='FooModel', + arg1=XXX, + arg2=XXX, + init_cfg=XXX), + ... + ``` + +### init_cfg 的使用 + +1. 用 `layer` 键初始化模型 + + 如果我们只定义了 `layer`, 它只会在 `layer` 键中初始化网络层。 + + 注意: `layer` 键对应的值是 Pytorch 的带有 weights 和 bias 属性的类名(因此不⽀持 `MultiheadAttention` 层)。 + +- 定义⽤于初始化具有相同配置的模块的 `layer` 键。 + + ```python + init_cfg = dict(type='Constant', layer=['Conv1d', 'Conv2d', 'Linear'], val=1) + # ⽤相同的配置初始化整个模块 + ``` + +- 定义⽤于初始化具有不同配置的层的 `layer` 键。 + + ```python + init_cfg = [dict(type='Constant', layer='Conv1d', val=1), + dict(type='Constant', layer='Conv2d', val=2), + dict(type='Constant', layer='Linear', val=3)] + # nn.Conv1d 将被初始化为 dict(type='Constant', val=1) + # nn.Conv2d 将被初始化为 dict(type='Constant', val=2) + # nn.Linear 将被初始化为 dict(type='Constant', val=3) + ``` + +2. 使⽤ `override` 键初始化模型 + +- 当使⽤属性名初始化某些特定部分时,我们可以使⽤ `override` 键, `override` 中的值将忽略 init_cfg 中的值。 + + ```python + # layers: + # self.feat = nn.Conv1d(3, 1, 3) + # self.reg = nn.Conv2d(3, 3, 3) + # self.cls = nn.Linear(1,2) + + init_cfg = dict(type='Constant', + layer=['Conv1d','Conv2d'], val=1, bias=2, + override=dict(type='Constant', name='reg', val=3, bias=4)) + # self.feat and self.cls 将被初始化为 dict(type='Constant', val=1, bias=2) + # 叫 'reg' 的模块将被初始化为 dict(type='Constant', val=3, bias=4) + ``` + +- 如果 init_cfg 中的 `layer` 为 None,则只会初始化 override 中有 name 的⼦模块,⽽ override 中的 type 和其他参数可以省略。 + + ```python + # layers: + # self.feat = nn.Conv1d(3, 1, 3) + # self.reg = nn.Conv2d(3, 3, 3) + # self.cls = nn.Linear(1,2) + + init_cfg = dict(type='Constant', val=1, bias=2, override=dict(name='reg')) + + # self.feat and self.cls 将被 Pytorch 初始化 + # 叫 'reg' 的模块将被 dict(type='Constant', val=1, bias=2) 初始化 + ``` + +- 如果我们不定义 `layer` 或 `override` 键,它不会初始化任何东西。 + +- 无效的使用 + + ```python + # override 没有 name 键的话是无效的 + init_cfg = dict(type='Constant', layer=['Conv1d','Conv2d'], val=1, bias=2, + override=dict(type='Constant', val=3, bias=4)) + + # override 有 name 键和其他参数但是没有 type 键也是无效的 + init_cfg = dict(type='Constant', layer=['Conv1d','Conv2d'], val=1, bias=2, + override=dict(name='reg', val=3, bias=4)) + ``` + +3. 使⽤预训练模型初始化模型 + + ```python + init_cfg = dict(type='Pretrained', + checkpoint='torchvision://resnet50') + ``` + +更多细节可以参考 [MMEngine](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/initialize.html) 的文档 diff --git a/mmdetection/docs/zh_cn/user_guides/label_studio.md b/mmdetection/docs/zh_cn/user_guides/label_studio.md new file mode 100644 index 00000000..202122f6 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/label_studio.md @@ -0,0 +1,255 @@ +# 使用 MMDetection 和 Label-Studio 进行半自动化目标检测标注 + +标注数据是一个费时费力的任务,本文介绍了如何使用 MMDetection 中的 RTMDet 算法联合 Label-Studio 软件进行半自动化标注。具体来说,使用 RTMDet 预测图片生成标注,然后使用 Label-Studio 进行微调标注,社区用户可以参考此流程和方法,将其应用到其他领域。 + +- RTMDet:RTMDet 是 OpenMMLab 自研的高精度单阶段的目标检测算法,开源于 MMDetection 目标检测工具箱中,其开源协议为 Apache 2.0,工业界的用户可以不受限的免费使用。 +- [Label Studio](https://github.com/heartexlabs/label-studio) 是一款优秀的标注软件,覆盖图像分类、目标检测、分割等领域数据集标注的功能。 + +本文将使用[喵喵数据集](https://download.openmmlab.com/mmyolo/data/cat_dataset.zip)的图片,进行半自动化标注。 + +## 环境配置 + +首先需要创建一个虚拟环境,然后安装 PyTorch 和 MMCV。在本文中,我们将指定 PyTorch 和 MMCV 的版本。接下来安装 MMDetection、Label-Studio 和 label-studio-ml-backend,具体步骤如下: + +创建虚拟环境: + +```shell +conda create -n rtmdet python=3.9 -y +conda activate rtmdet +``` + +安装 PyTorch + +```shell +# Linux and Windows CPU only +pip install torch==1.10.1+cpu torchvision==0.11.2+cpu torchaudio==0.10.1 -f https://download.pytorch.org/whl/cpu/torch_stable.html +# Linux and Windows CUDA 11.3 +pip install torch==1.10.1+cu113 torchvision==0.11.2+cu113 torchaudio==0.10.1 -f https://download.pytorch.org/whl/cu113/torch_stable.html +# OSX +pip install torch==1.10.1 torchvision==0.11.2 torchaudio==0.10.1 +``` + +安装 MMCV + +```shell +pip install -U openmim +mim install "mmcv>=2.0.0" +# 安装 mmcv 的过程中会自动安装 mmengine +``` + +安装 MMDetection + +```shell +git clone https://github.com/open-mmlab/mmdetection +cd mmdetection +pip install -v -e . +``` + +安装 Label-Studio 和 label-studio-ml-backend + +```shell +# 安装 label-studio 需要一段时间,如果找不到版本请使用官方源 +pip install label-studio==1.7.2 +pip install label-studio-ml==1.0.9 +``` + +下载rtmdet权重 + +```shell +cd path/to/mmetection +mkdir work_dirs +cd work_dirs +wget https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_m_8xb32-300e_coco/rtmdet_m_8xb32-300e_coco_20220719_112220-229f527c.pth +``` + +## 启动服务 + +启动 RTMDet 后端推理服务: + +```shell +cd path/to/mmetection + +label-studio-ml start projects/LabelStudio/backend_template --with \ +config_file=configs/rtmdet/rtmdet_m_8xb32-300e_coco.py \ +checkpoint_file=./work_dirs/rtmdet_m_8xb32-300e_coco_20220719_112220-229f527c.pth \ +device=cpu \ +--port 8003 +# device=cpu 为使用 CPU 推理,如果使用 GPU 推理,将 cpu 替换为 cuda:0 +``` + +![](https://cdn.vansin.top/picgo20230330131601.png) + +此时,RTMDet 后端推理服务已经启动,后续在 Label-Studio Web 系统中配置 http://localhost:8003 后端推理服务即可。 + +现在启动 Label-Studio 网页服务: + +```shell +label-studio start +``` + +![](https://cdn.vansin.top/picgo20230330132913.png) + +打开浏览器访问 [http://localhost:8080/](http://localhost:8080/) 即可看到 Label-Studio 的界面。 + +![](https://cdn.vansin.top/picgo20230330133118.png) + +我们注册一个用户,然后创建一个 RTMDet-Semiautomatic-Label 项目。 + +![](https://cdn.vansin.top/picgo20230330133333.png) + +我们通过下面的方式下载好示例的喵喵图片,点击 Data Import 导入需要标注的猫图片。 + +```shell +cd path/to/mmetection +mkdir data && cd data + +wget https://download.openmmlab.com/mmyolo/data/cat_dataset.zip && unzip cat_dataset.zip +``` + +![](https://cdn.vansin.top/picgo20230330133628.png) + +![](https://cdn.vansin.top/picgo20230330133715.png) + +然后选择 Object Detection With Bounding Boxes 模板 + +![](https://cdn.vansin.top/picgo20230330133807.png) + +```shell +airplane +apple +backpack +banana +baseball_bat +baseball_glove +bear +bed +bench +bicycle +bird +boat +book +bottle +bowl +broccoli +bus +cake +car +carrot +cat +cell_phone +chair +clock +couch +cow +cup +dining_table +dog +donut +elephant +fire_hydrant +fork +frisbee +giraffe +hair_drier +handbag +horse +hot_dog +keyboard +kite +knife +laptop +microwave +motorcycle +mouse +orange +oven +parking_meter +person +pizza +potted_plant +refrigerator +remote +sandwich +scissors +sheep +sink +skateboard +skis +snowboard +spoon +sports_ball +stop_sign +suitcase +surfboard +teddy_bear +tennis_racket +tie +toaster +toilet +toothbrush +traffic_light +train +truck +tv +umbrella +vase +wine_glass +zebra +``` + +然后将上述类别复制添加到 Label-Studio,然后点击 Save。 + +![](https://cdn.vansin.top/picgo20230330134027.png) + +然后在设置中点击 Add Model 添加 RTMDet 后端推理服务。 + +![](https://cdn.vansin.top/picgo20230330134320.png) + +点击 Validate and Save,然后点击 Start Labeling。 + +![](https://cdn.vansin.top/picgo20230330134424.png) + +看到如下 Connected 就说明后端推理服务添加成功。 + +![](https://cdn.vansin.top/picgo20230330134554.png) + +## 开始半自动化标注 + +点击 Label 开始标注 + +![](https://cdn.vansin.top/picgo20230330134804.png) + +我们可以看到 RTMDet 后端推理服务已经成功返回了预测结果并显示在图片上,我们可以发现这个喵喵预测的框有点大。 + +![](https://cdn.vansin.top/picgo20230403104419.png) + +我们手工拖动框,修正一下框的位置,得到以下修正过后的标注,然后点击 Submit,本张图片就标注完毕了。 + +![](https://cdn.vansin.top/picgo/20230403105923.png) + +我们 submit 完毕所有图片后,点击 exprot 导出 COCO 格式的数据集,就能把标注好的数据集的压缩包导出来了。 + +![](https://cdn.vansin.top/picgo20230330135921.png) + +用 vscode 打开解压后的文件夹,可以看到标注好的数据集,包含了图片和 json 格式的标注文件。 + +![](https://cdn.vansin.top/picgo20230330140321.png) + +到此半自动化标注就完成了,我们可以用这个数据集在 MMDetection 训练精度更高的模型了,训练出更好的模型,然后再用这个模型继续半自动化标注新采集的图片,这样就可以不断迭代,扩充高质量数据集,提高模型的精度。 + +## 使用 MMYOLO 作为后端推理服务 + +如果想在 MMYOLO 中使用 Label-Studio,可以参考在启动后端推理服务时,将 config_file 和 checkpoint_file 替换为 MMYOLO 的配置文件和权重文件即可。 + +```shell +cd path/to/mmetection + +label-studio-ml start projects/LabelStudio/backend_template --with \ +config_file= path/to/mmyolo_config.py \ +checkpoint_file= path/to/mmyolo_weights.pth \ +device=cpu \ +--port 8003 +# device=cpu 为使用 CPU 推理,如果使用 GPU 推理,将 cpu 替换为 cuda:0 +``` + +旋转目标检测和实例分割还在支持中,敬请期待。 diff --git a/mmdetection/docs/zh_cn/user_guides/new_model.md b/mmdetection/docs/zh_cn/user_guides/new_model.md new file mode 100644 index 00000000..424c4f90 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/new_model.md @@ -0,0 +1,289 @@ +# 在标准数据集上训练自定义模型(待更新) + +在本文中,你将知道如何在标准数据集上训练、测试和推理自定义模型。我们将在 cityscapes 数据集上以自定义 Cascade Mask R-CNN R50 模型为例演示整个过程,为了方便说明,我们将 neck 模块中的 `FPN` 替换为 `AugFPN`,并且在训练中的自动增强类中增加 `Rotate` 或 `TranslateX`。 + +基本步骤如下所示: + +1. 准备标准数据集 +2. 准备你的自定义模型 +3. 准备配置文件 +4. 在标准数据集上对模型进行训练、测试和推理 + +## 准备标准数据集 + +在本文中,我们使用 cityscapes 标准数据集为例进行说明。 + +推荐将数据集根路径采用符号链接方式链接到 `$MMDETECTION/data`。 + +如果你的文件结构不同,你可能需要在配置文件中进行相应的路径更改。标准的文件组织格式如下所示: + +```none +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── annotations +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ ├── cityscapes +│ │ ├── annotations +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +│ ├── VOCdevkit +│ │ ├── VOC2007 +│ │ ├── VOC2012 +``` + +你也可以通过如下方式设定数据集根路径 + +```bash +export MMDET_DATASETS=$data_root +``` + +我们将会使用环境便变量 `$MMDET_DATASETS` 作为数据集的根目录,因此你无需再修改相应配置文件的路径信息。 + +你需要使用脚本 `tools/dataset_converters/cityscapes.py` 将 cityscapes 标注转化为 coco 标注格式。 + +```shell +pip install cityscapesscripts +python tools/dataset_converters/cityscapes.py ./data/cityscapes --nproc 8 --out-dir ./data/cityscapes/annotations +``` + +目前在 `cityscapes `文件夹中的配置文件所对应模型是采用 COCO 预训练权重进行初始化的。 + +如果你的网络不可用或者比较慢,建议你先手动下载对应的预训练权重,否则可能在训练开始时候出现错误。 + +## 准备你的自定义模型 + +第二步是准备你的自定义模型或者训练相关配置。假设你想在已有的 Cascade Mask R-CNN R50 检测模型基础上,新增一个新的 neck 模块 `AugFPN` 去代替默认的 `FPN`,以下是具体实现: + +### 1 定义新的 neck (例如 AugFPN) + +首先创建新文件 `mmdet/models/necks/augfpn.py`. + +```python +import torch.nn as nn +from mmdet.registry import MODELS + +@MODELS.register_module() +class AugFPN(nn.Module): + + def __init__(self, + in_channels, + out_channels, + num_outs, + start_level=0, + end_level=-1, + add_extra_convs=False): + pass + + def forward(self, inputs): + # implementation is ignored + pass +``` + +### 2 导入模块 + +你可以采用两种方式导入模块,第一种是在 `mmdet/models/necks/__init__.py` 中添加如下内容 + +```python +from .augfpn import AugFPN +``` + +第二种是增加如下代码到对应配置中,这种方式的好处是不需要改动代码 + +```python +custom_imports = dict( + imports=['mmdet.models.necks.augfpn'], + allow_failed_imports=False) +``` + +### 3 修改配置 + +```python +neck=dict( + type='AugFPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5) +``` + +关于自定义模型其余相关细节例如实现新的骨架网络,头部网络、损失函数,以及运行时训练配置例如定义新的优化器、使用梯度裁剪、定制训练调度策略和钩子等,请参考文档 [自定义模型](tutorials/customize_models.md) 和 [自定义运行时训练配置](tutorials/customize_runtime.md)。 + +## 准备配置文件 + +第三步是准备训练配置所需要的配置文件。假设你打算基于 cityscapes 数据集,在 Cascade Mask R-CNN R50 中新增 `AugFPN` 模块,同时增加 `Rotate` 或者 `Translate` 数据增强策略,假设你的配置文件位于 `configs/cityscapes/` 目录下,并且取名为 `cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes.py`,则配置信息如下: + +```python +# 继承 base 配置,然后进行针对性修改 +_base_ = [ + '../_base_/models/cascade-mask-rcnn_r50_fpn.py', + '../_base_/datasets/cityscapes_instance.py', '../_base_/default_runtime.py' +] + +model = dict( + # 设置 `init_cfg` 为 None,表示不加载 ImageNet 预训练权重, + # 后续可以设置 `load_from` 参数用来加载 COCO 预训练权重 + backbone=dict(init_cfg=None), + # 使用新增的 `AugFPN` 模块代替默认的 `FPN` + neck=dict( + type='AugFPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + # 我们也需要将 num_classes 从 80 修改为 8 来匹配 cityscapes 数据集标注 + # 这个修改包括 `bbox_head` 和 `mask_head`. + roi_head=dict( + bbox_head=[ + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + # 将 COCO 类别修改为 cityscapes 类别 + num_classes=8, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + # 将 COCO 类别修改为 cityscapes 类别 + num_classes=8, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + # 将 COCO 类别修改为 cityscapes 类别 + num_classes=8, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], + mask_head=dict( + type='FCNMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + # 将 COCO 类别修改为 cityscapes 类别 + num_classes=8, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)))) + +# 覆写 `train_pipeline`,然后新增 `AutoAugment` 训练配置 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='AutoAugment', + policies=[ + [dict( + type='Rotate', + level=5, + img_border_value=(124, 116, 104), + prob=0.5) + ], + [dict(type='Rotate', level=7, img_border_value=(124, 116, 104)), + dict( + type='TranslateX', + level=5, + prob=0.5, + img_border_value=(124, 116, 104)) + ], + ]), + dict( + type='RandomResize', + scale=[(2048, 800), (2048, 1024)], + keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs'), +] + +# 设置每张显卡的批处理大小,同时设置新的训练 pipeline +data = dict( + samples_per_gpu=1, + workers_per_gpu=3, + train=dict(dataset=dict(pipeline=train_pipeline))) + +# 设置优化器 +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)) + +# 设置定制的学习率策略 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type='MultiStepLR', + begin=0, + end=10, + by_epoch=True, + milestones=[8], + gamma=0.1) +] + +# 训练,验证,测试配置 +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=10, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# 我们采用 COCO 预训练过的 Cascade Mask R-CNN R50 模型权重作为初始化权重,可以得到更加稳定的性能 +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco/cascade_mask_rcnn_r50_fpn_1x_coco_20200203-9d4dcb24.pth' +``` + +## 训练新模型 + +为了能够使用新增配置来训练模型,你可以运行如下命令: + +```shell +python tools/train.py configs/cityscapes/cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes.py +``` + +如果想了解更多用法,可以参考 [例子1](1_exist_data_model.md)。 + +## 测试和推理 + +为了能够测试训练好的模型,你可以运行如下命令: + +```shell +python tools/test.py configs/cityscapes/cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes.py work_dirs/cascade-mask-rcnn_r50_augfpn_autoaug-10e_cityscapes/epoch_10.pth +``` + +如果想了解更多用法,可以参考 [例子1](1_exist_data_model.md)。 diff --git a/mmdetection/docs/zh_cn/user_guides/robustness_benchmarking.md b/mmdetection/docs/zh_cn/user_guides/robustness_benchmarking.md new file mode 100644 index 00000000..e95c79a9 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/robustness_benchmarking.md @@ -0,0 +1,109 @@ +# 检测器鲁棒性检查 + +## 介绍 + +我们提供了在 [Benchmarking Robustness in Object Detection: Autonomous Driving when Winter is Coming](https://arxiv.org/abs/1907.07484) 中定义的「图像损坏基准测试」上测试目标检测和实例分割模型的工具。 +此页面提供了如何使用该基准测试的基本教程。 + +```latex +@article{michaelis2019winter, + title={Benchmarking Robustness in Object Detection: + Autonomous Driving when Winter is Coming}, + author={Michaelis, Claudio and Mitzkus, Benjamin and + Geirhos, Robert and Rusak, Evgenia and + Bringmann, Oliver and Ecker, Alexander S. and + Bethge, Matthias and Brendel, Wieland}, + journal={arXiv:1907.07484}, + year={2019} +} +``` + +![image corruption example](../../../resources/corruptions_sev_3.png) + +## 关于基准测试 + +要将结果提交到基准测试,请访问[基准测试主页](https://github.com/bethgelab/robust-detection-benchmark) + +基准测试是仿照 [imagenet-c 基准测试](https://github.com/hendrycks/robustness),由 Dan Hendrycks 和 Thomas Dietterich 在[Benchmarking Neural Network Robustness to Common Corruptions and Perturbations](https://arxiv.org/abs/1903.12261)(ICLR 2019)中发表。 + +图像损坏变换功能包含在此库中,但可以使用以下方法单独安装: + +```shell +pip install imagecorruptions +``` + +与 imagenet-c 相比,我们必须进行一些更改以处理任意大小的图像和灰度图像。 +我们还修改了“运动模糊”和“雪”损坏,以解除对于 linux 特定库的依赖, +否则必须单独安装这些库。有关详细信息,请参阅 [imagecorruptions](https://github.com/bethgelab/imagecorruptions)。 + +## 使用预训练模型进行推理 + +我们提供了一个测试脚本来评估模型在基准测试中提供的各种损坏变换组合下的性能。 + +### 在数据集上测试 + +- [x] 单张 GPU 测试 +- [ ] 多张 GPU 测试 +- [ ] 可视化检测结果 + +您可以使用以下命令在基准测试中使用 15 种损坏变换来测试模型性能。 + +```shell +# single-gpu testing +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] +``` + +也可以选择其它不同类型的损坏变换。 + +```shell +# noise +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions noise + +# blur +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions blur + +# wetaher +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions weather + +# digital +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions digital +``` + +或者使用一组自定义的损坏变换,例如: + +```shell +# gaussian noise, zoom blur and snow +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --corruptions gaussian_noise zoom_blur snow +``` + +最后,我们也可以选择施加在图像上的损坏变换的严重程度。 +严重程度从 1 到 5 逐级增强,0 表示不对图像施加损坏变换,即原始图像数据。 + +```shell +# severity 1 +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --severities 1 + +# severities 0,2,4 +python tools/analysis_tools/test_robustness.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] --severities 0 2 4 +``` + +## 模型测试结果 + +下表是各模型在 COCO 2017val 上的测试结果。 + +| Model | Backbone | Style | Lr schd | box AP clean | box AP corr. | box % | mask AP clean | mask AP corr. | mask % | +| :-----------------: | :-----------------: | :-----: | :-----: | :----------: | :----------: | :---: | :-----------: | :-----------: | :----: | +| Faster R-CNN | R-50-FPN | pytorch | 1x | 36.3 | 18.2 | 50.2 | - | - | - | +| Faster R-CNN | R-101-FPN | pytorch | 1x | 38.5 | 20.9 | 54.2 | - | - | - | +| Faster R-CNN | X-101-32x4d-FPN | pytorch | 1x | 40.1 | 22.3 | 55.5 | - | - | - | +| Faster R-CNN | X-101-64x4d-FPN | pytorch | 1x | 41.3 | 23.4 | 56.6 | - | - | - | +| Faster R-CNN | R-50-FPN-DCN | pytorch | 1x | 40.0 | 22.4 | 56.1 | - | - | - | +| Faster R-CNN | X-101-32x4d-FPN-DCN | pytorch | 1x | 43.4 | 26.7 | 61.6 | - | - | - | +| Mask R-CNN | R-50-FPN | pytorch | 1x | 37.3 | 18.7 | 50.1 | 34.2 | 16.8 | 49.1 | +| Mask R-CNN | R-50-FPN-DCN | pytorch | 1x | 41.1 | 23.3 | 56.7 | 37.2 | 20.7 | 55.7 | +| Cascade R-CNN | R-50-FPN | pytorch | 1x | 40.4 | 20.1 | 49.7 | - | - | - | +| Cascade Mask R-CNN | R-50-FPN | pytorch | 1x | 41.2 | 20.7 | 50.2 | 35.7 | 17.6 | 49.3 | +| RetinaNet | R-50-FPN | pytorch | 1x | 35.6 | 17.8 | 50.1 | - | - | - | +| Hybrid Task Cascade | X-101-64x4d-FPN-DCN | pytorch | 1x | 50.6 | 32.7 | 64.7 | 43.8 | 28.1 | 64.0 | + +由于对图像的损坏变换存在随机性,测试结果可能略有不同。 diff --git a/mmdetection/docs/zh_cn/user_guides/semi_det.md b/mmdetection/docs/zh_cn/user_guides/semi_det.md new file mode 100644 index 00000000..a2235237 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/semi_det.md @@ -0,0 +1,320 @@ +# 半监督目标检测 + +半监督目标检测同时利用标签数据和无标签数据进行训练,一方面可以减少模型对检测框数量的依赖,另一方面也可以利用大量的未标记数据进一步提高模型。 + +按照以下流程进行半监督目标检测: + +- [半监督目标检测](#半监督目标检测) + - [准备和拆分数据集](#准备和拆分数据集) + - [配置多分支数据流程](#配置多分支数据流程) + - [配置半监督数据加载](#配置半监督数据加载) + - [配置半监督模型](#配置半监督模型) + - [配置MeanTeacherHook](#配置meanteacherhook) + - [配置TeacherStudentValLoop](#配置teacherstudentvalloop) + +## 准备和拆分数据集 + +我们提供了数据集下载脚本,默认下载 coco2017 数据集,并且自动解压。 + +```shell +python tools/misc/download_dataset.py +``` + +解压后的数据集目录如下: + +```plain +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── image_info_unlabeled2017.json +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── test2017 +│ │ ├── train2017 +│ │ ├── unlabeled2017 +│ │ ├── val2017 +``` + +半监督目标检测在 coco 数据集上有两种比较通用的实验设置: + +(1)将 `train2017` 按照固定百分比(1%,2%,5% 和 10%)划分出一部分数据作为标签数据集,剩余的训练集数据作为无标签数据集,同时考虑划分不同的训练集数据作为标签数据集对半监督训练的结果影响较大,所以采用五折交叉验证来评估算法性能。我们提供了数据集划分脚本: + +```shell +python tools/misc/split_coco.py +``` + +该脚本默认会按照 1%,2%,5% 和 10% 的标签数据占比划分 `train2017`,每一种划分会随机重复 5 次,用于交叉验证。生成的半监督标注文件名称格式如下: + +- 标签数据集标注名称格式:`instances_train2017.{fold}@{percent}.json` + +- 无标签数据集名称标注:`instances_train2017.{fold}@{percent}-unlabeled.json` + +其中,`fold` 用于交叉验证,`percent` 表示标签数据的占比。 划分后的数据集目录结构如下: + +```plain +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── image_info_unlabeled2017.json +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── semi_anns +│ │ │ ├── instances_train2017.1@1.json +│ │ │ ├── instances_train2017.1@1-unlabeled.json +│ │ │ ├── instances_train2017.1@2.json +│ │ │ ├── instances_train2017.1@2-unlabeled.json +│ │ │ ├── instances_train2017.1@5.json +│ │ │ ├── instances_train2017.1@5-unlabeled.json +│ │ │ ├── instances_train2017.1@10.json +│ │ │ ├── instances_train2017.1@10-unlabeled.json +│ │ │ ├── instances_train2017.2@1.json +│ │ │ ├── instances_train2017.2@1-unlabeled.json +│ │ ├── test2017 +│ │ ├── train2017 +│ │ ├── unlabeled2017 +│ │ ├── val2017 +``` + +(2)将 `train2017` 作为标签数据集,`unlabeled2017` 作为无标签数据集。由于 `image_info_unlabeled2017.json` 没有 `categories` 信息,无法初始化 `CocoDataset` ,所以需要将 `instances_train2017.json` 的 `categories` 写入 `image_info_unlabeled2017.json` ,另存为 `instances_unlabeled2017.json`,相关脚本如下: + +```python +from mmengine.fileio import load, dump + +anns_train = load('instances_train2017.json') +anns_unlabeled = load('image_info_unlabeled2017.json') +anns_unlabeled['categories'] = anns_train['categories'] +dump(anns_unlabeled, 'instances_unlabeled2017.json') +``` + +处理后的数据集目录如下: + +```plain +mmdetection +├── data +│ ├── coco +│ │ ├── annotations +│ │ │ ├── image_info_unlabeled2017.json +│ │ │ ├── instances_train2017.json +│ │ │ ├── instances_unlabeled2017.json +│ │ │ ├── instances_val2017.json +│ │ ├── test2017 +│ │ ├── train2017 +│ │ ├── unlabeled2017 +│ │ ├── val2017 +``` + +## 配置多分支数据流程 + +半监督学习有两个主要的方法,分别是 +[一致性正则化](https://research.nvidia.com/sites/default/files/publications/laine2017iclr_paper.pdf) +和[伪标签](https://www.researchgate.net/profile/Dong-Hyun-Lee/publication/280581078_Pseudo-Label_The_Simple_and_Efficient_Semi-Supervised_Learning_Method_for_Deep_Neural_Networks/links/55bc4ada08ae092e9660b776/Pseudo-Label-The-Simple-and-Efficient-Semi-Supervised-Learning-Method-for-Deep-Neural-Networks.pdf) 。 +一致性正则化往往需要一些精心的设计,而伪标签的形式比较简单,更容易拓展到下游任务。我们主要采用了基于伪标签的教师学生联合训练的半监督目标检测框架,对于标签数据和无标签数据需要配置不同的数据流程: +(1)标签数据的数据流程: + +```python +# pipeline used to augment labeled data, +# which will be sent to student model for supervised training. +sup_pipeline = [ + dict(type='LoadImageFromFile',backend_args = backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomResize', scale=scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='MultiBranch', sup=dict(type='PackDetInputs')) +] +``` + +(2)无标签的数据流程: + +```python +# pipeline used to augment unlabeled data weakly, +# which will be sent to teacher model for predicting pseudo instances. +weak_pipeline = [ + dict(type='RandomResize', scale=scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', + 'homography_matrix')), +] + +# pipeline used to augment unlabeled data strongly, +# which will be sent to student model for unsupervised training. +strong_pipeline = [ + dict(type='RandomResize', scale=scale, keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomOrder', + transforms=[ + dict(type='RandAugment', aug_space=color_space, aug_num=1), + dict(type='RandAugment', aug_space=geometric, aug_num=1), + ]), + dict(type='RandomErasing', n_patches=(1, 5), ratio=(0, 0.2)), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction', + 'homography_matrix')), +] + +# pipeline used to augment unlabeled data into different views +unsup_pipeline = [ + dict(type='LoadImageFromFile', backend_args = backend_args), + dict(type='LoadEmptyAnnotations'), + dict( + type='MultiBranch', + unsup_teacher=weak_pipeline, + unsup_student=strong_pipeline, + ) +] +``` + +## 配置半监督数据加载 + +(1)构建半监督数据集。使用 `ConcatDataset` 拼接标签数据集和无标签数据集。 + +```python +labeled_dataset = dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=sup_pipeline) + +unlabeled_dataset = dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_unlabeled2017.json', + data_prefix=dict(img='unlabeled2017/'), + filter_cfg=dict(filter_empty_gt=False), + pipeline=unsup_pipeline) + +train_dataloader = dict( + batch_size=batch_size, + num_workers=num_workers, + persistent_workers=True, + sampler=dict( + type='GroupMultiSourceSampler', + batch_size=batch_size, + source_ratio=[1, 4]), + dataset=dict( + type='ConcatDataset', datasets=[labeled_dataset, unlabeled_dataset])) +``` + +(2)使用多源数据集采样器。 使用 `GroupMultiSourceSampler` 从 `labeled_dataset` 和 `labeled_dataset` 采样数据组成 batch , `source_ratio` 控制 batch 中标签数据和无标签数据的占比。`GroupMultiSourceSampler` 还保证了同一个 batch 中的图片具有相近的长宽比例,如果不需要保证batch内图片的长宽比例,可以使用 `MultiSourceSampler`。`GroupMultiSourceSampler` 采样示意图如下: + +
    + +
    + +`sup=1000` 表示标签数据集的规模为 1000 ,`sup_h=200` 表示标签数据集中长宽比大于等于1的图片规模为 200,`sup_w=800` 表示标签数据集中长宽比小于1的图片规模为 800 ,`unsup=9000` 表示无标签数据集的规模为 9000 ,`unsup_h=1800` 表示无标签数据集中长宽比大于等于1的图片规模为 1800,`unsup_w=7200` 表示标签数据集中长宽比小于1的图片规模为 7200 ,`GroupMultiSourceSampler` 每次按照标签数据集和无标签数据集的图片的总体长宽比分布随机选择一组,然后按照 `source_ratio` 从两个数据集中采样组成 batch ,因此标签数据集和无标签数据集重复采样次数不同。 + +## 配置半监督模型 + +我们选择 `Faster R-CNN` 作为 `detector` 进行半监督训练,以半监督目标检测算法 `SoftTeacher` 为例,模型的配置可以继承 `_base_/models/faster-rcnn_r50_fpn.py`,将检测器的骨干网络替换成 `caffe` 风格。 +注意,与监督训练的配置文件不同的是,`Faster R-CNN` 作为 `detector`,是作为 `model`的一个属性,而不是 `model` 。此外,还需要将`data_preprocessor`设置为`MultiBranchDataPreprocessor`,用于处理不同数据流程图片的填充和归一化。 +最后,可以通过 `semi_train_cfg` 和 `semi_test_cfg` 配置半监督训练和测试需要的参数。 + +```python +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', '../_base_/default_runtime.py', + '../_base_/datasets/semi_coco_detection.py' +] + +detector = _base_.model +detector.data_preprocessor = dict( + type='DetDataPreprocessor', + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32) +detector.backbone = dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type='Pretrained', + checkpoint='open-mmlab://detectron2/resnet50_caffe')) + +model = dict( + _delete_=True, + type='SoftTeacher', + detector=detector, + data_preprocessor=dict( + type='MultiBranchDataPreprocessor', + data_preprocessor=detector.data_preprocessor), + semi_train_cfg=dict( + freeze_teacher=True, + sup_weight=1.0, + unsup_weight=4.0, + pseudo_label_initial_score_thr=0.5, + rpn_pseudo_thr=0.9, + cls_pseudo_thr=0.9, + reg_pseudo_thr=0.02, + jitter_times=10, + jitter_scale=0.06, + min_pseudo_bbox_wh=(1e-2, 1e-2)), + semi_test_cfg=dict(predict_on='teacher')) +``` + +此外,我们也支持其他检测模型进行半监督训练,比如,`RetinaNet` 和 `Cascade R-CNN`。由于 `SoftTeacher` 仅支持 `Faster R-CNN`,所以需要将其替换为 `SemiBaseDetector`,示例如下: + +```python +_base_ = [ + '../_base_/models/retinanet_r50_fpn.py', '../_base_/default_runtime.py', + '../_base_/datasets/semi_coco_detection.py' +] + +detector = _base_.model + +model = dict( + _delete_=True, + type='SemiBaseDetector', + detector=detector, + data_preprocessor=dict( + type='MultiBranchDataPreprocessor', + data_preprocessor=detector.data_preprocessor), + semi_train_cfg=dict( + freeze_teacher=True, + sup_weight=1.0, + unsup_weight=1.0, + cls_pseudo_thr=0.9, + min_pseudo_bbox_wh=(1e-2, 1e-2)), + semi_test_cfg=dict(predict_on='teacher')) +``` + +沿用 `SoftTeacher` 的半监督训练配置,将 `batch_size` 改为 2 ,`source_ratio` 改为 `[1, 1]`,`RetinaNet`,`Faster R-CNN`, `Cascade R-CNN` 以及 `SoftTeacher` 在 10% coco 训练集上的监督训练和半监督训练的实验结果如下: + +| Model | Detector | BackBone | Style | sup-0.1-coco mAP | semi-0.1-coco mAP | +| :--------------: | :-----------: | :------: | :---: | :--------------: | :---------------: | +| SemiBaseDetector | RetinaNet | R-50-FPN | caffe | 23.5 | 27.7 | +| SemiBaseDetector | Faster R-CNN | R-50-FPN | caffe | 26.7 | 28.4 | +| SemiBaseDetector | Cascade R-CNN | R-50-FPN | caffe | 28.0 | 29.7 | +| SoftTeacher | Faster R-CNN | R-50-FPN | caffe | 26.7 | 31.1 | + +## 配置MeanTeacherHook + +通常,教师模型采用对学生模型指数滑动平均(EMA)的方式进行更新,进而教师模型随着学生模型的优化而优化,可以通过配置 `custom_hooks` 实现: + +```python +custom_hooks = [dict(type='MeanTeacherHook')] +``` + +## 配置TeacherStudentValLoop + +由于教师学生联合训练框架存在两个模型,我们可以用 `TeacherStudentValLoop` 替换 `ValLoop`,在训练的过程中同时检验两个模型的精度。 + +```python +val_cfg = dict(type='TeacherStudentValLoop') +``` diff --git a/mmdetection/docs/zh_cn/user_guides/single_stage_as_rpn.md b/mmdetection/docs/zh_cn/user_guides/single_stage_as_rpn.md new file mode 100644 index 00000000..39db35c2 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/single_stage_as_rpn.md @@ -0,0 +1,171 @@ +# 将单阶段检测器作为 RPN + +候选区域网络 (Region Proposal Network, RPN) 作为 [Faster R-CNN](https://arxiv.org/abs/1506.01497) 的一个子模块,将为 Faster R-CNN 的第二阶段产生候选区域。在 MMDetection 里大多数的二阶段检测器使用 [`RPNHead`](../../../mmdet/models/dense_heads/rpn_head.py)作为候选区域网络来产生候选区域。然而,任何的单阶段检测器都可以作为候选区域网络,是因为他们对边界框的预测可以被视为是一种候选区域,并且因此能够在 R-CNN 中得到改进。因此在 MMDetection v3.0 中会支持将单阶段检测器作为 RPN 使用。 + +接下来我们通过一个例子,即如何在 [Faster R-CNN](../../../configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py) 中使用一个无锚框的单阶段的检测器模型 [FCOS](../../../configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py) 作为 RPN ,详细阐述具体的全部流程。 + +主要流程如下: + +1. 在 Faster R-CNN 中使用 `FCOSHead` 作为 `RPNHead` +2. 评估候选区域 +3. 用预先训练的 FCOS 训练定制的 Faster R-CNN + +## 在 Faster R-CNN 中使用 `FCOSHead` 作为` RPNHead` + +为了在 Faster R-CNN 中使用 `FCOSHead` 作为 `RPNHead` ,我们应该创建一个名为 `configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py` 的配置文件,并且在 `configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py` 中将 `rpn_head` 的设置替换为 `bbox_head` 的设置,此外我们仍然使用 FCOS 的瓶颈设置,步幅为`[8,16,32,64,128]`,并且更新 `bbox_roi_extractor` 的 `featmap_stride` 为 ` [8,16,32,64,128]`。为了避免损失变慢,我们在前1000次迭代而不是前500次迭代中应用预热,这意味着 lr 增长得更慢。相关配置如下: + +```python +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + # 从 configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py 复制 + neck=dict( + start_level=1, + add_extra_convs='on_output', # 使用 P5 + relu_before_extra_convs=True), + rpn_head=dict( + _delete_=True, # 忽略未使用的旧设置 + type='FCOSHead', + num_classes=1, # 对于 rpn, num_classes = 1,如果 num_classes > 1,它将在 TwoStageDetector 中自动设置为1 + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + roi_head=dict( # featmap_strides 的更新取决于于颈部的步伐 + bbox_roi_extractor=dict(featmap_strides=[8, 16, 32, 64, 128]))) +# 学习率 +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), # 慢慢增加 lr,否则损失变成 NAN + dict( + type='MultiStepLR', + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] +``` + +然后,我们可以使用下面的命令来训练我们的定制模型。更多训练命令,请参考[这里](train.md)。 + +```python +# 使用8个 GPU 进行训练 +bash +tools/dist_train.sh +configs/faster_rcnn/faster-rcnn_r50_fpn_fcos-rpn_1x_coco.py +--work-dir /work_dirs/faster-rcnn_r50_fpn_fcos-rpn_1x_coco +``` + +## 评估候选区域 + +候选区域的质量对检测器的性能有重要影响,因此,我们也提供了一种评估候选区域的方法。和上面一样创建一个新的名为 `configs/rpn/fcos-rpn_r50_fpn_1x_coco.py` 的配置文件,并且在 `configs/rpn/fcos-rpn_r50_fpn_1x_coco.py` 中将 `rpn_head` 的设置替换为 `bbox_head` 的设置。 + +```python +_base_ = [ + '../_base_/models/rpn_r50_fpn.py', '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +val_evaluator = dict(metric='proposal_fast') +test_evaluator = val_evaluator +model = dict( + # 从 configs/fcos/fcos_r50-caffe_fpn_gn-head_1x_coco.py 复制 + neck=dict( + start_level=1, + add_extra_convs='on_output', # 使用 P5 + relu_before_extra_convs=True), + rpn_head=dict( + _delete_=True, # 忽略未使用的旧设置 + type='FCOSHead', + num_classes=1, # 对于 rpn, num_classes = 1,如果 num_classes >为1,它将在 rpn 中自动设置为1 + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0))) +``` + +假设我们在训练之后有检查点 `./work_dirs/faster-rcnn_r50_fpn_fcos-rpn_1x_coco/epoch_12.pth` ,然后,我们可以使用下面的命令来评估建议的质量。 + +```python +# 使用8个 GPU 进行测试 +bash +tools/dist_test.sh +configs/rpn/fcos-rpn_r50_fpn_1x_coco.py +--work_dirs /faster-rcnn_r50_fpn_fcos-rpn_1x_coco/epoch_12.pth +``` + +## 用预先训练的 FCOS 训练定制的 Faster R-CNN + +预训练不仅加快了训练的收敛速度,而且提高了检测器的性能。因此,我们在这里给出一个例子来说明如何使用预先训练的 FCOS 作为 RPN 来加速训练和提高精度。假设我们想在 Faster R-CNN 中使用 `FCOSHead` 作为 `rpn_head`,并加载预先训练权重来进行训练 [`fcos_r50-caffe_fpn_gn-head_1x_coco`](https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_1x_coco/fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth)。 配置文件 `configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_fcos- rpn_1x_copy .py` 的内容如下所示。注意,`fcos_r50-caffe_fpn_gn-head_1x_coco` 使用 ResNet50 的 caffe 版本,因此需要更新 `data_preprocessor` 中的像素平均值和 std。 + +```python +_base_ = [ + '../_base_/models/faster-rcnn_r50_fpn.py', + '../_base_/datasets/coco_detection.py', + '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' +] +model = dict( + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(type='BN', requires_grad=False), + style='caffe', + init_cfg=None), # the checkpoint in ``load_from`` contains the weights of backbone + neck=dict( + start_level=1, + add_extra_convs='on_output', # 使用 P5 + relu_before_extra_convs=True), + rpn_head=dict( + _delete_=True, # 忽略未使用的旧设置 + type='FCOSHead', + num_classes=1, # 对于 rpn, num_classes = 1,如果 num_classes > 1,它将在 TwoStageDetector 中自动设置为1 + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='IoULoss', loss_weight=1.0), + loss_centerness=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), + roi_head=dict( # update featmap_strides due to the strides in neck + bbox_roi_extractor=dict(featmap_strides=[8, 16, 32, 64, 128]))) +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/fcos/fcos_r50_caffe_fpn_gn-head_1x_coco/fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth' +``` + +训练命令如下。 + +```python +bash +tools/dist_train.sh +configs/faster_rcnn/faster-rcnn_r50-caffe_fpn_fcos-rpn_1x_coco.py \ +--work-dir /work_dirs/faster-rcnn_r50-caffe_fpn_fcos-rpn_1x_coco +``` diff --git a/mmdetection/docs/zh_cn/user_guides/test.md b/mmdetection/docs/zh_cn/user_guides/test.md new file mode 100644 index 00000000..2ada04d2 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/test.md @@ -0,0 +1,285 @@ +# 测试现有模型 + +我们提供了测试脚本,能够测试一个现有模型在所有数据集(COCO,Pascal VOC,Cityscapes 等)上的性能。我们支持在如下环境下测试: + +- 单 GPU 测试 +- CPU 测试 +- 单节点多 GPU 测试 +- 多节点测试 + +根据以上测试环境,选择合适的脚本来执行测试过程。 + +```shell +# 单 GPU 测试 +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--out ${RESULT_FILE}] \ + [--show] + +# CPU 测试:禁用 GPU 并运行单 GPU 测试脚本 +export CUDA_VISIBLE_DEVICES=-1 +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--out ${RESULT_FILE}] \ + [--show] + +# 单节点多 GPU 测试 +bash tools/dist_test.sh \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + ${GPU_NUM} \ + [--out ${RESULT_FILE}] +``` + +`tools/dist_test.sh` 也支持多节点测试,不过需要依赖 PyTorch 的 [启动工具](https://pytorch.org/docs/stable/distributed.html#launch-utility) 。 + +可选参数: + +- `RESULT_FILE`: 结果文件名称,需以 .pkl 形式存储。如果没有声明,则不将结果存储到文件。 +- `--show`: 如果开启,检测结果将被绘制在图像上,以一个新窗口的形式展示。它只适用于单 GPU 的测试,是用于调试和可视化的。请确保使用此功能时,你的 GUI 可以在环境中打开。否则,你可能会遇到这么一个错误 `cannot connect to X server`。 +- `--show-dir`: 如果指明,检测结果将会被绘制在图像上并保存到指定目录。它只适用于单 GPU 的测试,是用于调试和可视化的。即使你的环境中没有 GUI,这个选项也可使用。 +- `--cfg-options`: 如果指明,这里的键值对将会被合并到配置文件中。 + +### 样例 + +假设你已经下载了 checkpoint 文件到 `checkpoints/` 文件下了。 + +1. 测试 RTMDet 并可视化其结果。按任意键继续下张图片的测试。配置文件和 checkpoint 文件 [在此](https://github.com/open-mmlab/mmdetection/tree/main/configs/rtmdet) 。 + + ```shell + python tools/test.py \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --show + ``` + +2. 测试 RTMDet,并为了之后的可视化保存绘制的图像。配置文件和 checkpoint 文件 [在此](https://github.com/open-mmlab/mmdetection/tree/main/configs/rtmdet) 。 + + ```shell + python tools/test.py \ + configs/rtmdet/rtmdet_l_8xb32-300e_coco.py \ + checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth \ + --show-dir rtmdet_l_8xb32-300e_coco_results + ``` + +3. 在 Pascal VOC 数据集上测试 Faster R-CNN,不保存测试结果,测试 `mAP`。配置文件和 checkpoint 文件 [在此](../../../configs/pascal_voc) 。 + + ```shell + python tools/test.py \ + configs/pascal_voc/faster-rcnn_r50_fpn_1x_voc0712.py \ + checkpoints/faster_rcnn_r50_fpn_1x_voc0712_20200624-c9895d40.pth + ``` + +4. 使用 8 块 GPU 测试 Mask R-CNN,测试 `bbox` 和 `mAP` 。配置文件和 checkpoint 文件 [在此](../../../configs/mask_rcnn) 。 + + ```shell + ./tools/dist_test.sh \ + configs/mask-rcnn_r50_fpn_1x_coco.py \ + checkpoints/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth \ + 8 \ + --out results.pkl + ``` + +5. 使用 8 块 GPU 测试 Mask R-CNN,测试**每类**的 `bbox` 和 `mAP`。配置文件和 checkpoint 文件 [在此](../../../configs/mask_rcnn) 。 + + ```shell + ./tools/dist_test.sh \ + configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py \ + checkpoints/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth \ + 8 + ``` + + 该命令生成两个JSON文件 `./work_dirs/coco_instance/test.bbox.json` 和 `./work_dirs/coco_instance/test.segm.json`。 + +6. 在 COCO test-dev 数据集上,使用 8 块 GPU 测试 Mask R-CNN,并生成 JSON 文件提交到官方评测服务器,配置文件和 checkpoint 文件 [在此](../../../configs/mask_rcnnn) 。你可以在 [config](./././configs/_base_/datasets/coco_instance.py) 的注释中用 test_evaluator 和 test_dataloader 替换原来的 test_evaluator 和 test_dataloader,然后运行: + + ```shell + ./tools/dist_test.sh \ + configs/cityscapes/mask-rcnn_r50_fpn_1x_cityscapes.py \ + checkpoints/mask_rcnn_r50_fpn_1x_cityscapes_20200227-afe51d5a.pth \ + 8 + ``` + + 这行命令生成两个 JSON 文件 `mask_rcnn_test-dev_results.bbox.json` 和 `mask_rcnn_test-dev_results.segm.json`。 + +7. 在 Cityscapes 数据集上,使用 8 块 GPU 测试 Mask R-CNN,生成 txt 和 png 文件,并上传到官方评测服务器。配置文件和 checkpoint 文件 [在此](../../../configs/cityscapes) 。 你可以在 [config](./././configs/_base_/datasets/cityscapes_instance.py) 的注释中用 test_evaluator 和 test_dataloader 替换原来的 test_evaluator 和 test_dataloader,然后运行: + + ```shell + ./tools/dist_test.sh \ + configs/cityscapes/mask-rcnn_r50_fpn_1x_cityscapes.py \ + checkpoints/mask_rcnn_r50_fpn_1x_cityscapes_20200227-afe51d5a.pth \ + 8 + ``` + + 生成的 png 和 txt 文件在 `./work_dirs/cityscapes_metric` 文件夹下。 + +### 不使用 Ground Truth 标注进行测试 + +MMDetection 支持在不使用 ground-truth 标注的情况下对模型进行测试,这需要用到 `CocoDataset`。如果你的数据集格式不是 COCO 格式的,请将其转化成 COCO 格式。如果你的数据集格式是 VOC 或者 Cityscapes,你可以使用 [tools/dataset_converters](https://github.com/open-mmlab/mmdetection/tree/main/tools/dataset_converters) 内的脚本直接将其转化成 COCO 格式。如果是其他格式,可以使用 [images2coco 脚本](https://github.com/open-mmlab/mmdetection/tree/master/tools/dataset_converters/images2coco.py) 进行转换。 + +```shell +python tools/dataset_converters/images2coco.py \ + ${IMG_PATH} \ + ${CLASSES} \ + ${OUT} \ + [--exclude-extensions] +``` + +参数: + +- `IMG_PATH`: 图片根路径。 +- `CLASSES`: 类列表文本文件名。文本中每一行存储一个类别。 +- `OUT`: 输出 json 文件名。 默认保存目录和 `IMG_PATH` 在同一级。 +- `exclude-extensions`: 待排除的文件后缀名。 + +在转换完成后,使用如下命令进行测试 + +```shell +# 单 GPU 测试 +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--show] + +# CPU 测试:禁用 GPU 并运行单 GPU 测试脚本 +export CUDA_VISIBLE_DEVICES=-1 +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--out ${RESULT_FILE}] \ + [--show] + +# 单节点多 GPU 测试 +bash tools/dist_test.sh \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + ${GPU_NUM} \ + [--show] +``` + +假设 [model zoo](https://mmdetection.readthedocs.io/en/latest/modelzoo_statistics.html) 中的 checkpoint 文件被下载到了 `checkpoints/` 文件夹下, +我们可以使用以下命令,用 8 块 GPU 在 COCO test-dev 数据集上测试 Mask R-CNN,并且生成 JSON 文件。 + +```sh +./tools/dist_test.sh \ + configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py \ + checkpoints/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth \ + 8 +``` + +这行命令生成两个 JSON 文件 `./work_dirs/coco_instance/test.bbox.json` 和 `./work_dirs/coco_instance/test.segm.json`。 + +### 批量推理 + +MMDetection 在测试模式下,既支持单张图片的推理,也支持对图像进行批量推理。默认情况下,我们使用单张图片的测试,你可以通过修改测试数据配置文件中的 `samples_per_gpu` 来开启批量测试。 +开启批量推理的配置文件修改方法为: + +```shell +data = dict(train_dataloader=dict(...), val_dataloader=dict(...), test_dataloader=dict(batch_size=2, ...)) +``` + +或者你可以通过将 `--cfg-options` 设置为 `--cfg-options test_dataloader.batch_size=` 来开启它。 + +## 测试时增强 (TTA) + +测试时增强 (TTA) 是一种在测试阶段使用的数据增强策略。它对同一张图片应用不同的增强,例如翻转和缩放,用于模型推理,然后将每个增强后的图像的预测结果合并,以获得更准确的预测结果。为了让用户更容易使用 TTA,MMEngine 提供了 [BaseTTAModel](https://mmengine.readthedocs.io/en/latest/api/generated/mmengine.model.BaseTTAModel.html#mmengine.model.BaseTTAModel) 类,允许用户根据自己的需求通过简单地扩展 BaseTTAModel 类来实现不同的 TTA 策略。 + +在 MMDetection 中,我们提供了 [DetTTAModel](../../../mmdet/models/test_time_augs/det_tta.py) 类,它继承自 BaseTTAModel。 + +### 使用案例 + +使用 TTA 需要两个步骤。首先,你需要在配置文件中添加 `tta_model` 和 `tta_pipeline`: + +```shell +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict( + type='nms', + iou_threshold=0.5), + max_per_img=100)) + +tta_pipeline = [ + dict(type='LoadImageFromFile', + backend_args=None), + dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale=(1333, 800), keep_ratio=True) + ], [ # It uses 2 flipping transformations (flipping and not flipping). + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'flip', + 'flip_direction')) + ]])] +``` + +第二步,运行测试脚本时,设置 `--tta` 参数,如下所示: + +```shell +# 单 GPU 测试 +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--tta] + +# CPU 测试:禁用 GPU 并运行单 GPU 测试脚本 +export CUDA_VISIBLE_DEVICES=-1 +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + [--out ${RESULT_FILE}] \ + [--tta] + +# 多 GPU 测试 +bash tools/dist_test.sh \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + ${GPU_NUM} \ + [--tta] +``` + +你也可以自己修改 TTA 配置,例如添加缩放增强: + +```shell +tta_model = dict( + type='DetTTAModel', + tta_cfg=dict(nms=dict( + type='nms', + iou_threshold=0.5), + max_per_img=100)) + +img_scales = [(1333, 800), (666, 400), (2000, 1200)] +tta_pipeline = [ + dict(type='LoadImageFromFile', + backend_args=None), + dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale=s, keep_ratio=True) for s in img_scales + ], [ + dict(type='RandomFlip', prob=1.), + dict(type='RandomFlip', prob=0.) + ], [ + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', 'flip', + 'flip_direction')) + ]])] +``` + +以上数据增强管道将首先对图像执行 3 个多尺度转换,然后执行 2 个翻转转换(翻转和不翻转),最后使用 PackDetInputs 将图像打包到最终结果中。 +这里有更多的 TTA 使用案例供您参考: + +- [RetinaNet](../../../configs/retinanet/retinanet_tta.py) +- [CenterNet](../../../configs/centernet/centernet_tta.py) +- [YOLOX](../../../configs/rtmdet/rtmdet_tta.py) +- [RTMDet](../../../configs/yolox/yolox_tta.py) + +更多高级用法和 TTA 的数据流,请参考 [MMEngine](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/test_time_augmentation.html#data-flow)。我们将在后续支持实例分割 TTA。 diff --git a/mmdetection/docs/zh_cn/user_guides/test_results_submission.md b/mmdetection/docs/zh_cn/user_guides/test_results_submission.md new file mode 100644 index 00000000..7a076585 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/test_results_submission.md @@ -0,0 +1,174 @@ +# 提交测试结果 + +## 全景分割测试结果提交 + +下面几节介绍如何在 COCO 测试开发集上生成泛视分割模型的预测结果,并将预测提交到 [COCO评估服务器](https://competitions.codalab.org/competitions/19507) + +### 前提条件 + +- 下载 [COCO测试数据集图像](http://images.cocodataset.org/zips/test2017.zip),[测试图像信息](http://images.cocodataset.org/annotations/image_info_test2017.zip),和[全景训练/相关注释](http://images.cocodataset.org/annotations/panoptic_annotations_trainval2017.zip),然后解压缩它们,把 `test2017` 放到 `data/coco/`,把 json 文件和注释文件放到 `data/coco/annotations/` 。 + +```shell +# 假设 data/coco/ 不存在 +mkdir -pv data/coco/ +# 下载 test2017 +wget -P data/coco/ http://images.cocodataset.org/zips/test2017.zip +wget -P data/coco/ http://images.cocodataset.org/annotations/image_info_test2017.zip +wget -P data/coco/ http://images.cocodataset.org/annotations/panoptic_annotations_trainval2017.zip +# 解压缩它们 +unzip data/coco/test2017.zip -d data/coco/ +unzip data/coco/image_info_test2017.zip -d data/coco/ +unzip data/coco/panoptic_annotations_trainval2017.zip -d data/coco/ +# 删除 zip 文件(可选) +rm -rf data/coco/test2017.zip data/coco/image_info_test2017.zip data/coco/panoptic_annotations_trainval2017.zip +``` + +- 运行以下代码更新测试图像信息中的类别信息。由于 `image_info_test-dev2017.json` 的类别信息中缺少属性 `isthing` ,我们需要用 `panoptic_val2017.json` 中的类别信息更新它。 + +```shell +python tools/misc/gen_coco_panoptic_test_info.py data/coco/annotations +``` + +在完成上述准备之后,你的 `data` 目录结构应该是这样: + +```text +data +`-- coco + |-- annotations + | |-- image_info_test-dev2017.json + | |-- image_info_test2017.json + | |-- panoptic_image_info_test-dev2017.json + | |-- panoptic_train2017.json + | |-- panoptic_train2017.zip + | |-- panoptic_val2017.json + | `-- panoptic_val2017.zip + `-- test2017 +``` + +### coco 测试开发的推理 + +要在 coco test-dev 上进行推断,我们应该首先更新 `test_dataloder` 和 `test_evaluator` 的设置。有两种方法可以做到这一点:1. 在配置文件中更新它们;2. 在命令行中更新它们。 + +#### 在配置文件中更新它们 + +相关的设置在 `configs/_base_/datasets/ coco_panoptical .py` 的末尾,如下所示。 + +```python +test_dataloader = dict( + batch_size=1, + num_workers=1, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/panoptic_image_info_test-dev2017.json', + data_prefix=dict(img='test2017/'), + test_mode=True, + pipeline=test_pipeline)) +test_evaluator = dict( + type='CocoPanopticMetric', + format_only=True, + ann_file=data_root + 'annotations/panoptic_image_info_test-dev2017.json', + outfile_prefix='./work_dirs/coco_panoptic/test') +``` + +以下任何一种方法都可以用于更新 coco test-dev 集上的推理设置 + +情况1:直接取消注释 `configs/_base_/datasets/ coco_panoptical .py` 中的设置。 + +情况2:将以下设置复制到您现在使用的配置文件中。 + +```python +test_dataloader = dict( + dataset=dict( + ann_file='annotations/panoptic_image_info_test-dev2017.json', + data_prefix=dict(img='test2017/', _delete_=True))) +test_evaluator = dict( + format_only=True, + ann_file=data_root + 'annotations/panoptic_image_info_test-dev2017.json', + outfile_prefix='./work_dirs/coco_panoptic/test') +``` + +然后通过以下命令对 coco test-dev et 进行推断。 + +```shell +python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} +``` + +#### 在命令行中更新它们 + +coco test-dev 上更新相关设置和推理的命令如下所示。 + +```shell +# 用一个 gpu 测试 +CUDA_VISIBLE_DEVICES=0 python tools/test.py \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + --cfg-options \ + test_dataloader.dataset.ann_file=annotations/panoptic_image_info_test-dev2017.json \ + test_dataloader.dataset.data_prefix.img=test2017 \ + test_dataloader.dataset.data_prefix._delete_=True \ + test_evaluator.format_only=True \ + test_evaluator.ann_file=data/coco/annotations/panoptic_image_info_test-dev2017.json \ + test_evaluator.outfile_prefix=${WORK_DIR}/results +# 用四个 gpu 测试 +CUDA_VISIBLE_DEVICES=0,1,3,4 bash tools/dist_test.sh \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + 8 \ # eights gpus + --cfg-options \ + test_dataloader.dataset.ann_file=annotations/panoptic_image_info_test-dev2017.json \ + test_dataloader.dataset.data_prefix.img=test2017 \ + test_dataloader.dataset.data_prefix._delete_=True \ + test_evaluator.format_only=True \ + test_evaluator.ann_file=data/coco/annotations/panoptic_image_info_test-dev2017.json \ + test_evaluator.outfile_prefix=${WORK_DIR}/results +# 用 slurm 测试 +GPUS=8 tools/slurm_test.sh \ + ${Partition} \ + ${JOB_NAME} \ + ${CONFIG_FILE} \ + ${CHECKPOINT_FILE} \ + --cfg-options \ + test_dataloader.dataset.ann_file=annotations/panoptic_image_info_test-dev2017.json \ + test_dataloader.dataset.data_prefix.img=test2017 \ + test_dataloader.dataset.data_prefix._delete_=True \ + test_evaluator.format_only=True \ + test_evaluator.ann_file=data/coco/annotations/panoptic_image_info_test-dev2017.json \ + test_evaluator.outfile_prefix=${WORK_DIR}/results +``` + +例子:假设我们使用预先训练的带有 ResNet-50 骨干网的 MaskFormer 对 `test2017` 执行推断。 + +```shell +# 单 gpu 测试 +CUDA_VISIBLE_DEVICES=0 python tools/test.py \ + configs/maskformer/maskformer_r50_mstrain_16x1_75e_coco.py \ + checkpoints/maskformer_r50_mstrain_16x1_75e_coco_20220221_141956-bc2699cb.pth \ + --cfg-options \ + test_dataloader.dataset.ann_file=annotations/panoptic_image_info_test-dev2017.json \ + test_dataloader.dataset.data_prefix.img=test2017 \ + test_dataloader.dataset.data_prefix._delete_=True \ + test_evaluator.format_only=True \ + test_evaluator.ann_file=data/coco/annotations/panoptic_image_info_test-dev2017.json \ + test_evaluator.outfile_prefix=work_dirs/maskformer/results +``` + +### 重命名文件并压缩结果 + +推理之后,全景分割结果(一个 json 文件和一个存储掩码的目录)将在 `WORK_DIR` 中。我们应该按照 [COCO's Website](https://cocodataset.org/#upload)上的命名约定重新命名它们。最后,我们需要将 json 和存储掩码的目录压缩到 zip 文件中,并根据命名约定重命名该 zip 文件。注意, zip 文件应该**直接**包含上述两个文件。 + +重命名文件和压缩结果的命令: + +```shell +# 在 WORK_DIR 中,我们有 panoptic 分割结果: 'panoptic' 和 'results. panoptical .json'。 +cd ${WORK_DIR} +# 将 '[algorithm_name]' 替换为您使用的算法名称 +mv ./panoptic ./panoptic_test-dev2017_[algorithm_name]_results +mv ./results.panoptic.json ./panoptic_test-dev2017_[algorithm_name]_results.json +zip panoptic_test-dev2017_[algorithm_name]_results.zip -ur panoptic_test-dev2017_[algorithm_name]_results panoptic_test-dev2017_[algorithm_name]_results.json +``` diff --git a/mmdetection/docs/zh_cn/user_guides/tracking_analysis_tools.md b/mmdetection/docs/zh_cn/user_guides/tracking_analysis_tools.md new file mode 100644 index 00000000..5330af1d --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/tracking_analysis_tools.md @@ -0,0 +1,87 @@ +**我们在 `tools/` 目录下提供了很多有用的工具。** + +## MOT 测试时参数搜索 + +`tools/analysis_tools/mot/mot_param_search.py` 可以搜索 MOT 模型中 `tracker` 的参数。 +它与 `tools/test.py` 的使用方式相同,但配置上**有所不同**。 + +下面是修改配置的示例: + +1. 定义要记录的期望评估指标。 + + 例如,你可以将 `evaluator` 定义为: + + ```python + test_evaluator=dict(type='MOTChallengeMetrics', metric=['HOTA', 'CLEAR', 'Identity']) + ``` + + 当然,你也可以自定义 `test_evaluator` 中 `metric` 的内容。你可以自由选择 `['HOTA', 'CLEAR', 'Identity']` 中的一个或多个指标。 + +2. 定义要搜索的参数及其取值。 + + 假设你有一个 `tracker` 的配置如下: + + ```python + model=dict( + tracker=dict( + type='BaseTracker', + obj_score_thr=0.5, + match_iou_thr=0.5 + ) + ) + ``` + + 如果你想要搜索 `tracker` 的参数,只需将其值改为一个列表,如下所示: + + ```python + model=dict( + tracker=dict( + type='BaseTracker', + obj_score_thr=[0.4, 0.5, 0.6], + match_iou_thr=[0.4, 0.5, 0.6, 0.7] + ) + ) + ``` + + 然后,脚本将测试一共12种情况并且记录结果。 + +## MOT 误差可视化 + +`tools/analysis_tools/mot/mot_error_visualize.py` 可以为多目标跟踪可视化错误。 + +该脚本需要推断的结果作为输入。默认情况下,**红色**边界框表示误检(false positive),**黄色**边界框表示漏检(false negative),**蓝色**边界框表示ID切换(ID switch)。 + +``` +python tools/analysis_tools/mot/mot_error_visualize.py \ + ${CONFIG_FILE}\ + --input ${INPUT} \ + --result-dir ${RESULT_DIR} \ + [--output-dir ${OUTPUT}] \ + [--fps ${FPS}] \ + [--show] \ + [--backend ${BACKEND}] +``` + +`RESULT_DIR` 中包含了所有视频的推断结果,推断结果是一个 `txt` 文件。 + +可选参数: + +- `OUTPUT`:可视化演示的输出。如果未指定,`--show` 是必选的,用于即时显示视频。 +- `FPS`:输出视频的帧率。 +- `--show`:是否即时显示视频。 +- `BACKEND`:用于可视化边界框的后端。选项包括 `cv2` 和 `plt`。 + +## 浏览数据集 + +`tools/analysis_tools/mot/browse_dataset.py` 可以可视化训练数据集,以检查数据集配置是否正确。 + +**示例:** + +```shell +python tools/analysis_tools/browse_dataset.py ${CONFIG_FILE} [--show-interval ${SHOW_INTERVAL}] +``` + +可选参数: + +- `SHOW_INTERVAL`: 显示的间隔时间(秒)。 +- `--show`: 是否即时显示图像。 diff --git a/mmdetection/docs/zh_cn/user_guides/tracking_config.md b/mmdetection/docs/zh_cn/user_guides/tracking_config.md new file mode 100644 index 00000000..4a20da77 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/tracking_config.md @@ -0,0 +1,109 @@ +# 学习更多与配置相关的事 + +我们用 python 文档作为我们的配置系统。你可以在 `MMDetection/configs` 底下找到所有已提供的配置文件。 + +我们把模块化和继承化设计融入我们的配置系统,这使我们很方便去进行各种实验。如果你想查看相关的配置文件,你可以跑 `python tools/misc/print_config.py /PATH/TO/CONFIG` 去看完整的详细配置。 + +## 完整配置的简要说明 + +一个完整的配置通常包含以下主要的字段: + +`model`:一个模型的基本配置,包含 `data_preprocessor`、`detector`、`motion` 之类的模块,还有 `train_cfg`、`test_cfg` 等等; + +`train_dataloader`:训练数据集的配置,通常包含 `batch_size`、 `num_workers`、 `sampler`、 `dataset` 等等; + +`val_dataloader`:验证数据集的配置,与训练数据集的配置类似; + +`test_dataloader`:测试数据集的配置,与训练数据集的配置类似; + +`val_evaluator`:验证评估器的配置,例如 `type='MOTChallengeMetrics'` 是 MOT 任务里面的测量标准; + +`test_evaluator`:测试评估器的配置,与验证评估器的配置类似; + +`train_cfg`:训练循环的配置,例如 `type='EpochBasedTrainLoop'` ; + +`val_cfg`:验证循环的配置,例如 `type='VideoValLoop'` ; + +`test_cfg`:测试循环的配置,例如 `type='VideoTestLoop'` ; + +`default_hooks`:默认鱼钩的配置,包含计时器、日志、参数调度程序、检查点、样本种子、可视化; + +`vis_backends`:可视化后端的配置,默认使用 `type='LocalVisBackend'` ; + +`visualizer`:可视化工具的配置,例如MOT任务使用 `type='TrackLocalVisualizer'` ; + +`param_scheduler`:参数调度程序的配置,通常里面设置学习率调度程序; + +`optim_wrapper`:优化器封装的配置,包含优化相关的信息,例如优化器、梯度剪裁等; + +`load_from`:加载预训练模型的路径; + +`resume`:布尔值,如果是 `True` ,会从 `load_from` 加载模型的检查点,训练会恢复至检查点的迭代次数。 + +## 通过脚本参数修改配置 + +当使用 `tools/train.py` 或 `tools/test_trackin.py` 执行任务时,可以指定 `--cfg-options` 来就地修改配置。我们举几个例子如下。有关更多详细信息,请参阅[MMEngine](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html)。 + +### 更新 dict 链的配置键 + +可以按照原始配置中 `dict` 键的顺序指定配置选项,例如,设置 `--cfg-options model.detector.backbone.norm_eval=False` 会将模型主干中的所有 `BN` 模块更改为训练模式。 + +### 更新配置列表中的关键字 + +一些配置的 `dict` 关键字会以列表的形式组成,例如,测试管道中的 `test_dataloader.dataset.pipeline` 以列表形式出现,即 `[dict(type='LoadImageFromFile'), ...]`。如果你想在测试管道中将 `LoadImageFromFile` 更改为 `LoadImageFromWebcam`,可以设置 `--cfg-options test_dataloader.dataset.pipeline.0.type=LoadImageFromWebcam`。 + +### 更新列表/元组的值 + +要被更新的可能是一个列表或一个元组,例如,你可以通过指定 `--cfg options model.data_processor.mean=[0,0,0]` 来更改 `data_preprocessor` 的平均值的关键字。请注意,指定值内不允许有空格。 + +## 配置文件结构 + +`config/_base_` 下有三种基本组件类型,即数据集、模型和默认运行时间。可以用它们来轻松构建许多方法,例如 `SORT`,`DeepSORT`。由 `_base_` 中的组件组成的配置称为基元。 + +对于同一文件夹下的配置文件,建议只有一个基元配置文件。其他配置文件都应该从基元配置文件继承基本结构,这样,继承级别的最大值为 3。 + +为了便于理解,我们建议贡献者继承现有的方法。例如,如果在 `Faster R-CNN` 的基础上进行了一些修改,用户可以首先通过指定 `_base_ = ../_base_/models/faster-rcnn_r50-dc5.py` 来继承基本的 `Faster R-CNN` 结构,然后修改配置文件中的必要字段。 + +如果你正在构建一个与任何现有方法都不共享结构的全新方法,则可以在 `configs` 下创建一个新文件夹 method_name。 + +有关详细文档,请参阅[MMEngine](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html)。 + +## 配置命名风格 + +我们根据以下风格去命名配置文件,建议贡献者遵从相同风格。 + +`{method}_{module}_{train_cfg}_{train_data}_{test_data}` + +`{method}`: 方法名称,例如 `sort`; + +`{module}`: 方法的基本模块,例如 `faster-rcnn_r50_fpn`; + +`{train_cfg}`: 训练配置通常包含批量大小、迭代次数等,例如 `8xb4-80e`; + +`{train_data}`: 训练数据集,例如 `mot17halftrain`; + +`{test_data}`: 测试数据集,例如 `test-mot17halfval`。 + +## 常问问题 + +### 忽略基本配置中的某些字段 + +有时候你可以设置 `_delete_=True` 去忽略基本配置中的一些字段,你可以参考[MMEngine](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html)进行简单说明。 + +### 跟踪数据结构介绍 + +#### 优点和新功能 + +在 `mmdetection` 跟踪任务中,我们使用视频来组织数据集,并使用 `TrackDataSample` 来描述数据集信息。 + +基于视频组织,我们提供了 `transform UniformRefFrameSample` 来对关键帧和参考帧进行采样,并使用 `TransformBroadcaster` 进行剪辑训练。 + +在某种程度上,`TrackDataSample` 可以被视为多个 `DetDataSample` 的包装器。它包含一个 `video_data_samples`,这是一个以 `DetDataSample` 组成的列表,里面每个 `DetDataSample` 对应一个帧。此外,它的元信息包括关键帧的索引和参考帧的索引,用与剪辑训练。 + +得益于基于视频的数据组织,整个视频可以直接被测试。这种方式更简洁直观。如果你的 GPU 内存无法容纳整个视频,我们还提供基于图像的测试方法。 + +## 要做的事 + +`StrongSORT`、`Mask2Former` 等算法不支持基于视频的测试,这些算法对 GPU 内存提出了挑战,我们将来会优化这个问题。 + +现在,我们不支持像 `MOT Challenge dataset` 这样的基于视频的数据集和像 `Crowdhuman` 用于 `QDTrack` 算法这样的基于图像的数据集进行联合训练。我们将来会优化这个问题。 diff --git a/mmdetection/docs/zh_cn/user_guides/tracking_dataset_prepare.md b/mmdetection/docs/zh_cn/user_guides/tracking_dataset_prepare.md new file mode 100644 index 00000000..0db495b5 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/tracking_dataset_prepare.md @@ -0,0 +1,245 @@ +## 数据集准备 + +本页面提供了现有基准数据集的准备说明,包括: + +- 多目标跟踪 + + - [MOT Challenge](https://motchallenge.net/) + - [CrowdHuman](https://www.crowdhuman.org/) + +- 视频实例分割 + + - [YouTube-VIS](https://youtube-vos.org/dataset/vis/) + +### 1. 下载数据集 + +请从官方网站下载数据集,并将数据集的根目录建立软链接到 `$MMDETECTION/data` 目录下。 + +#### 1.1 多目标跟踪 + +- 对于多目标跟踪任务的训练和测试,需要下载MOT Challenge数据集之一(例如MOT17、MOT20),CrowdHuman数据集可以作为补充数据集。 + +- 对于中国的用户,可以从 [OpenDataLab](https://opendatalab.com/) 上高速下载如下数据集: + + - [MOT17](https://opendatalab.com/MOT17/download) + - [MOT20](https://opendatalab.com/MOT20/download) + - [CrowdHuman](https://opendatalab.com/CrowdHuman/download) + +#### 1.2 视频实例分割 + +- 对于视频实例分割任务的训练和测试,只需要选择一个YouTube-VIS数据集(例如YouTube-VIS 2019、YouTube-VIS 2021)即可。 +- 可以从 [YouTubeVOS](https://codalab.lisn.upsaclay.fr/competitions/6064) 上下载YouTube-VIS 2019数据集。 +- 可以从 [YouTubeVOS](https://codalab.lisn.upsaclay.fr/competitions/7680) 上下载YouTube-VIS 2021数据集。 + +#### 1.3 数据结构 + +如果您的文件夹结构与以下结构不同,则可能需要在配置文件中更改相应的路径。 + +``` +mmdetection +├── mmdet +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── annotations +│ │ +| ├── MOT15/MOT16/MOT17/MOT20 +| | ├── train +| | | ├── MOT17-02-DPM +| | | | ├── det +| │ │ │ ├── gt +| │ │ │ ├── img1 +| │ │ │ ├── seqinfo.ini +│ │ │ ├── ...... +| | ├── test +| | | ├── MOT17-01-DPM +| | | | ├── det +| │ │ │ ├── img1 +| │ │ │ ├── seqinfo.ini +│ │ │ ├── ...... +│ │ +│ ├── crowdhuman +│ │ ├── annotation_train.odgt +│ │ ├── annotation_val.odgt +│ │ ├── train +│ │ │ ├── Images +│ │ │ ├── CrowdHuman_train01.zip +│ │ │ ├── CrowdHuman_train02.zip +│ │ │ ├── CrowdHuman_train03.zip +│ │ ├── val +│ │ │ ├── Images +│ │ │ ├── CrowdHuman_val.zip +│ │ +``` + +### 2. 转换注释 + +在这种情况下,您需要将官方注释(Annotations)转换为COCO格式。我们提供了相应的脚本,使用方法如下: + +```shell +# MOT17 +# 其他 MOT Challenge 数据集的处理方式与 MOT17 相同。 +python ./tools/dataset_converters/mot2coco.py -i ./data/MOT17/ -o ./data/MOT17/annotations --split-train --convert-det +python ./tools/dataset_converters/mot2reid.py -i ./data/MOT17/ -o ./data/MOT17/reid --val-split 0.2 --vis-threshold 0.3 + +# CrowdHuman +python ./tools/dataset_converters/crowdhuman2coco.py -i ./data/crowdhuman -o ./data/crowdhuman/annotations + +# YouTube-VIS 2019 +python ./tools/dataset_converters/youtubevis2coco.py -i ./data/youtube_vis_2019 -o ./data/youtube_vis_2019/annotations --version 2019 + +# YouTube-VIS 2021 +python ./tools/dataset_converters/youtubevis2coco.py -i ./data/youtube_vis_2021 -o ./data/youtube_vis_2021/annotations --version 2021 + +``` + +运行这些脚本后,文件夹结构将如下所示: + +``` +mmdetection +├── mmtrack +├── tools +├── configs +├── data +│ ├── coco +│ │ ├── train2017 +│ │ ├── val2017 +│ │ ├── test2017 +│ │ ├── annotations +│ │ +| ├── MOT15/MOT16/MOT17/MOT20 +| | ├── train +| | | ├── MOT17-02-DPM +| | | | ├── det +| │ │ │ ├── gt +| │ │ │ ├── img1 +| │ │ │ ├── seqinfo.ini +│ │ │ ├── ...... +| | ├── test +| | | ├── MOT17-01-DPM +| | | | ├── det +| │ │ │ ├── img1 +| │ │ │ ├── seqinfo.ini +│ │ │ ├── ...... +| | ├── annotations +| | ├── reid +│ │ │ ├── imgs +│ │ │ ├── meta +│ │ +│ ├── crowdhuman +│ │ ├── annotation_train.odgt +│ │ ├── annotation_val.odgt +│ │ ├── train +│ │ │ ├── Images +│ │ │ ├── CrowdHuman_train01.zip +│ │ │ ├── CrowdHuman_train02.zip +│ │ │ ├── CrowdHuman_train03.zip +│ │ ├── val +│ │ │ ├── Images +│ │ │ ├── CrowdHuman_val.zip +│ │ ├── annotations +│ │ │ ├── crowdhuman_train.json +│ │ │ ├── crowdhuman_val.json +│ │ +│ ├── youtube_vis_2019 +│ │ │── train +│ │ │ │── JPEGImages +│ │ │ │── ...... +│ │ │── valid +│ │ │ │── JPEGImages +│ │ │ │── ...... +│ │ │── test +│ │ │ │── JPEGImages +│ │ │ │── ...... +│ │ │── train.json (the official annotation files) +│ │ │── valid.json (the official annotation files) +│ │ │── test.json (the official annotation files) +│ │ │── annotations (the converted annotation file) +│ │ +│ ├── youtube_vis_2021 +│ │ │── train +│ │ │ │── JPEGImages +│ │ │ │── instances.json (the official annotation files) +│ │ │ │── ...... +│ │ │── valid +│ │ │ │── JPEGImages +│ │ │ │── instances.json (the official annotation files) +│ │ │ │── ...... +│ │ │── test +│ │ │ │── JPEGImages +│ │ │ │── instances.json (the official annotation files) +│ │ │ │── ...... +│ │ │── annotations (the converted annotation file) +``` + +#### MOT15/MOT16/MOT17/MOT20中的注释和reid文件夹 + +以 MOT17 数据集为例,其他数据集的结构类似。 + +在 `data/MOT17/annotations` 文件夹中有8个JSON文件: + +`train_cocoformat.json`: 包含MOT17数据集训练集的注释信息的JSON文件。 + +`train_detections.pkl`: 包含MOT17数据集训练集的公共检测结果的Pickle文件。 + +`test_cocoformat.json`: 包含MOT17数据集测试集的注释信息的JSON文件。 + +`test_detections.pkl`: 包含MOT17数据集测试集的公共检测结果的Pickle文件。 + +`half-train_cocoformat.json`、`half-train_detections.pkl`、`half-val_cocoformat.json` 和 `half-val_detections.pkl` 与 `train_cocoformat.json` 和 `train_detections.pkl` 具有类似的含义。`half` 表示将训练集中的每个视频分成两半。前一半的视频被标记为 `half-train` 集,后一半的视频被标记为 `half-val` 集。 + +`data/MOT17/reid` 文件夹的结构如下所示: + +``` +reid +├── imgs +│ ├── MOT17-02-FRCNN_000002 +│ │ ├── 000000.jpg +│ │ ├── 000001.jpg +│ │ ├── ... +│ ├── MOT17-02-FRCNN_000003 +│ │ ├── 000000.jpg +│ │ ├── 000001.jpg +│ │ ├── ... +├── meta +│ ├── train_80.txt +│ ├── val_20.txt +``` + +`train_80.txt` 中的 `80` 表示训练数据集在整个ReID数据集中的比例为80%。而验证数据集的比例为20%。 + +关于训练,我们提供了一个注释列表 `train_80.txt`。列表中的每一行包含一个文件名及其对应的真实标签。格式如下所示: + +``` +MOT17-05-FRCNN_000110/000018.jpg 0 +MOT17-13-FRCNN_000146/000014.jpg 1 +MOT17-05-FRCNN_000088/000004.jpg 2 +MOT17-02-FRCNN_000009/000081.jpg 3 +``` + +`MOT17-05-FRCNN_000110` 表示 `MOT17-05-FRCNN` 视频中的第110个人。 + +对于验证集,注释列表 `val_20.txt` 的格式与上述相同。 + +`reid/imgs` 中的图像是通过相应的 `gt.txt` 从 `MOT17/train` 中的原始图像中裁剪而来。真实标签的值应在 `[0, num_classes - 1]` 的范围内。 + +#### CrowdHuman 中的 annotations 文件夹 + +`data/crowdhuman/annotations` 文件夹下有两个JSON文件: + +`crowdhuman_train.json`:包含 CrowdHuman 数据集训练集的注释信息的JSON文件。 +`crowdhuman_val.json`:包含 CrowdHuman 数据集验证集的注释信息的JSON文件。 + +#### youtube_vis_2019/youtube_vis2021 中的 annotations 文件夹 + +There are 3 JSON files in `data/youtube_vis_2019/annotations` or `data/youtube_vis_2021/annotations`: + +`youtube_vis_2019_train.json`/`youtube_vis_2021_train.json`:包含 youtube_vis_2019/youtube_vis2021 数据集训练集的注释信息的JSON文件。 + +`youtube_vis_2019_valid.json`/`youtube_vis_2021_valid.json`:包含 youtube_vis_2019/youtube_vis2021 数据集验证集的注释信息的JSON文件。 + +`youtube_vis_2019_test.json`/`youtube_vis_2021_test.json`:包含 youtube_vis_2019/youtube_vis2021 数据集测试集的注释信息的JSON文件。 diff --git a/mmdetection/docs/zh_cn/user_guides/tracking_interference.md b/mmdetection/docs/zh_cn/user_guides/tracking_interference.md new file mode 100644 index 00000000..1b1fc08a --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/tracking_interference.md @@ -0,0 +1,55 @@ +# 推理 + +我们提供了一些演示脚本去推理一个给出的视频,或者是推理包含一系列连续照片的文件夹。想要获取该代码资源,请点击 [这里](https://github.com/open-mmlab/mmdetection/tree/tracking/demo)。 + +若输入为文件夹格式,你需要标明这点。并且,图片命名应该**易于整理**,以便于你根据文件名字中包含的数字信息来重新调整图片的顺序。我们现在只支持 `.jpg`,`.jpeg` 和 `.png` 格式的图片。 + +## MOT models 的推理 + +该脚本能够使用多任务跟踪或者视频实例分割方法来推理一段输入的视频/一张图片。 + +```shell +python demo/mot_demo.py \ + ${INPUTS} + ${CONFIG_FILE} \ + [--checkpoint ${CHECKPOINT_FILE}] \ + [--detector ${DETECTOR_FILE}] \ + [--reid ${REID_FILE}] \ + [--score-thr ${SCORE_THR}] \ + [--device ${DEVICE}] \ + [--out ${OUTPUT}] \ + [--show] +``` + +`INPUTS` 和 `OUTPUT` 参数支持 _mp4 视频_ 格式和_文件夹_格式。 + +**特别注意**:对于 `DeepSORT`、`SORT`、`StrongSORT`,他们需要单独加载 `reid` 和 `detector` 的权重。因此,我们会使用 `--detector` 和 `--reid` 来加载权重参数。其他的例如 `ByteTrack`、`OCSORT`、`QDTrack`、`MaskTrackRCNN` 以及 `Mask2Former` 这样的算法则使用 `--checkpoint` 来加载权重参数。 + +可选参数: + +- `CHECKPOINT_FILE`: 可选择 checkpoint。 +- `DETECTOR_FILE`: 可选择 detector。 +- `REID_FILE`: 可选择 reid。 +- `SCORE_THR`: bboxes 的得分阈值。 +- `DEVICE`: 推理所需配置。可以选择 `cpu`,`cuda:0`,或者其他。 +- `OUTPUT`: 输出结果可视化的示例。如果未指定, `--show` 将强制显示动态视频。 +- `--show`: 是否即时显示视频。 + +**运行 mot model 的示例:** + +```shell +# 示例 1:不指定 --checkpoint 使用 --detector +python demo/mot_demo.py \ + demo/demo_mot.mp4 \ + configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py \ + --detector \ + https://download.openmmlab.com/mmtracking/mot/faster_rcnn/faster-rcnn_r50_fpn_4e_mot17-half-64ee2ed4.pth \ + --out mot.mp4 + +# 示例 2:使用 --checkpoint +python demo/mot_demo.py \ + demo/demo_mot.mp4 \ + configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py \ + --checkpoint https://download.openmmlab.com/mmtracking/mot/qdtrack/mot_dataset/qdtrack_faster-rcnn_r50_fpn_4e_mot17_20220315_145635-76f295ef.pth \ + --out mot.mp4 +``` diff --git a/mmdetection/docs/zh_cn/user_guides/tracking_train_test_zh_cn.md b/mmdetection/docs/zh_cn/user_guides/tracking_train_test_zh_cn.md new file mode 100644 index 00000000..0542b9af --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/tracking_train_test_zh_cn.md @@ -0,0 +1,229 @@ +# 学习训练和测试 + +## 训练 + +本节将介绍如何在支持的数据集上训练现有模型。 +支持以下训练环境: + +- CPU +- 单 GPU +- 单节点多 GPU +- 多节点 + +您还可以使用 Slurm 管理作业。 + +重要: + +- 在训练过程中,您可以通过修改 `train_cfg` 来改变评估间隔。 + `train_cfg = dict(val_interval=10)`。这意味着每 10 个 epoch 对模型进行一次评估。 +- 所有配置文件中的默认学习率为 8 个 GPU。 + 根据[线性扩展规则](https://arxiv.org/abs/1706.02677)、 + 如果在每个 GPU 上使用不同的 GPU 或图像,则需要设置与批次大小成比例的学习率、 + 例如,8 个 GPU * 1 个图像/GPU 的学习率为 `lr=0.01`,16 个 GPU * 2 个图像/GPU 的学习率为 lr=0.04。 +- 在训练过程中,日志文件和检查点将保存到工作目录、 + 该目录由 CLI 参数 `--work-dir`指定。它默认使用 `./work_dirs/CONFIG_NAME`。 +- 如果需要混合精度训练,只需指定 CLI 参数 `--amp`。 + +#### 1.在 CPU 上训练 + +该模型默认放在 cuda 设备上。 +仅当没有 cuda 设备时,该模型才会放在 CPU 上。 +因此,如果要在 CPU 上训练模型,则需要先 `export CUDA_VISIBLE_DEVICES=-1` 以禁用 GPU 可见性。 +更多细节参见 [MMEngine](https://github.com/open-mmlab/mmengine/blob/ca282aee9e402104b644494ca491f73d93a9544f/mmengine/runner/runner.py#L849-L850). + +```shell 脚本 +CUDA_VISIBLE_DEVICES=-1 python tools/train.py ${CONFIG_FILE} [optional arguments] +``` + +在 CPU 上训练 MOT 模型 QDTrack 的示例: + +```shell 脚本 +CUDA_VISIBLE_DEVICES=-1 python tools/train.py configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py +``` + +#### 2. 在单 GPU 上训练 + +如果您想在单 GPU 上训练模型, 您可以按照如下方法直接使用 `tools/train.py`. + +```shell 脚本 +python tools/train.py ${CONFIG_FILE} [optional arguments] +``` + +您可以使用 `export CUDA_VISIBLE_DEVICES=$GPU_ID` 命令选择GPU. + +在单 GPU 上训练 MOT 模型 QDTrack 的示例: + +```shell 脚本 +CUDA_VISIBLE_DEVICES=2 python tools/train.py configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py +``` + +#### 3. 在单节点多 GPU 上进行训练 + +我们提供了 `tools/dist_train.sh`,用于在多个 GPU 上启动训练。 +基本用法如下。 + +```shell 脚本 +bash ./tools/dist_train.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments] +``` + +如果您想在一台机器上启动多个作业、 +例如,在拥有 8 个 GPU 的机器上启动 2 个 4-GPU 训练作业、 +需要为每个作业指定不同的端口(默认为 29500),以避免通信冲突。 + +例如,可以在命令中设置端口如下。 + +```shell 脚本 +CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4 +CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4 +``` + +在单节点多 GPU 上训练 MOT 模型 QDTrack 的示例: + +```shell脚本 +bash ./tools/dist_train.sh configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 +``` + +#### 4. 在多个节点上训练 + +如果使用以太网连接多台机器,只需运行以下命令即可: + +在第一台机器上 + +```shell 脚本 +NNODES=2 NODE_RANK=0 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR bash tools/dist_train.sh $CONFIG $GPUS +``` + +在第二台机器上: + +```shell script +NNODES=2 NODE_RANK=1 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR bash tools/dist_train.sh $CONFIG $GPUS +``` + +如果没有 InfiniBand 等高速网络,速度通常会很慢。 + +#### 5. 使用 Slurm 进行训练 + +[Slurm](https://slurm.schedmd.com/)是一个用于计算集群的优秀作业调度系统。 +在 Slurm 管理的集群上,您可以使用 `slurm_train.sh` 生成训练作业。 +它支持单节点和多节点训练。 + +基本用法如下。 + +```shell 脚本 +bash ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} ${WORK_DIR} ${GPUS} +``` + +使用 Slurm 训练 MOT 模型 QDTrack 的示例: + +```shell脚本 +PORT=29501 \ +GPUS_PER_NODE=8 \ +SRUN_ARGS="--quotatype=reserved" \ +bash ./tools/slurm_train.sh \ +mypartition \ +mottrack +configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py +./work_dirs/QDTrack \ +8 +``` + +## 测试 + +本节将介绍如何在支持的数据集上测试现有模型。 +支持以下测试环境: + +- CPU +- 单 GPU +- 单节点多 GPU +- 多节点 + +您还可以使用 Slurm 管理作业。 + +重要: + +- 在 MOT 中,某些算法(如 `DeepSORT`、`SORT`、`StrongSORT`)需要分别加载 `reid` 的权重和 `detector` 的权重。 + 其他算法,如`ByteTrack`、`OCSORT`和`QDTrack`则不需要。因此,我们提供了 `--checkpoint`、`--detector` 和 `--reid`来加载权重。 +- 我们提供了两种评估和测试模型的方法,即基于视频的测试和基于图像的测试。 有些算法如 `StrongSORT`, `Mask2former` 只支持基于视频的测试. 如果您的 GPU 内存无法容纳整个视频,您可以通过设置采样器类型来切换测试方式。 + 例如 + 基于视频的测试:`sampler=dict(type='DefaultSampler', shuffle=False, round_up=False)` + 基于图像的测试:`sampler=dict(type='TrackImgSampler')` +- 您可以通过修改 evaluator 中的关键字 `outfile_prefix` 来设置结果保存路径。 + 例如,`val_evaluator = dict(outfile_prefix='results/sort_mot17')`。 + 否则,将创建一个临时文件,并在评估后删除。 +- 如果您只想要格式化的结果而不需要评估,可以设置 `format_only=True`。 + 例如,`test_evaluator = dict(type='MOTChallengeMetric', metric=['HOTA', 'CLEAR', 'Identity'], outfile_prefix='sort_mot17_results', format_only=True)` + +#### 1. 在 CPU 上测试 + +模型默认在 cuda 设备上运行。 +只有在没有 cuda 设备的情况下,模型才会在 CPU 上运行。 +因此,如果要在 CPU 上测试模型,您需要 `export CUDA_VISIBLE_DEVICES=-1` 先禁用 GPU 可见性。 + +更多细节请参考[MMEngine](https://github.com/open-mmlab/mmengine/blob/ca282aee9e402104b644494ca491f73d93a9544f/mmengine/runner/runner.py#L849-L850). + +```shell 脚本 +CUDA_VISIBLE_DEVICES=-1 python tools/test_tracking.py ${CONFIG_FILE} [optional arguments] +``` + +在 CPU 上测试 MOT 模型 SORT 的示例: + +```shell 脚本 +CUDA_VISIBLE_DEVICES=-1 python tools/test_tracking.py configs/sort/sort_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py --detector ${CHECKPOINT_FILE} +``` + +#### 2. 在单 GPU 上测试 + +如果您想在单 GPU 上测试模型,可以直接使用 `tools/test_tracking.py`,如下所示。 + +```shell 脚本 +python tools/test_tracking.py ${CONFIG_FILE} [optional arguments] +``` + +您可以使用 `export CUDA_VISIBLE_DEVICES=$GPU_ID` 来选择 GPU。 + +在单 GPU 上测试 MOT 模型 QDTrack 的示例: + +```shell 脚本 +CUDA_VISIBLE_DEVICES=2 python tools/test_tracking.py configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py --detector ${CHECKPOINT_FILE} +``` + +#### 3. 在单节点多 GPU 上进行测试 + +我们提供了 `tools/dist_test_tracking.sh`,用于在多个 GPU 上启动测试。 +基本用法如下。 + +```shell 脚本 +bash ./tools/dist_test_tracking.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments] +``` + +在单节点多 GPU 上测试 MOT 模型 DeepSort 的示例: + +```shell 脚本 +bash ./tools/dist_test_tracking.sh configs/qdtrack/qdtrack_faster-rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py 8 --detector ${CHECKPOINT_FILE} --reid ${CHECKPOINT_FILE} +``` + +#### 4. 在多个节点上测试 + +您可以在多个节点上进行测试,这与 "在多个节点上进行训练 "类似。 + +#### 5. 使用 Slurm 进行测试 + +在 Slurm 管理的集群上,您可以使用 `slurm_test_tracking.sh` 生成测试作业。 +它支持单节点和多节点测试。 + +基本用法如下。 + +```shell 脚本 +[GPUS=${GPUS}] bash tools/slurm_test_tracking.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} [optional arguments] +``` + +使用 Slurm 测试 VIS 模型 Mask2former 的示例: + +```shell 脚本 +GPUS=8 +bash tools/slurm_test_tracking.sh \ +mypartition \ +vis \ +configs/mask2former_vis/mask2former_r50_8xb2-8e_youtubevis2021.py \ +--checkpoint ${CHECKPOINT_FILE} +``` diff --git a/mmdetection/docs/zh_cn/user_guides/tracking_visualization.md b/mmdetection/docs/zh_cn/user_guides/tracking_visualization.md new file mode 100644 index 00000000..0d10952a --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/tracking_visualization.md @@ -0,0 +1,51 @@ +# 了解可视化 + +## 本地的可视化 + +这一节将会展示如何使用本地的工具可视化 detection/tracking 的运行结果。 + +如果你想要画出预测结果的图像,你可以如下示例,将 `TrackVisualizationHook` 中的 draw 的参数设置为 `draw=True`。 + +```shell +default_hooks = dict(visualization=dict(type='TrackVisualizationHook', draw=True)) +``` + +`TrackVisualizationHook` 共有如下参数: + +- `draw`: 是否绘制预测结果。如果选择 False,将不会显示图像。该参数默认设置为 False。 +- `interval`: 可视化的间隔。默认值为 30。 +- `score_thr`: 确定是否可视化边界框和掩码的阈值。默认值是 0.3。 +- `show`: 是否展示绘制的图像。默认不显示。 +- `wait_time`: 展示的时间间隔(秒)。默认为 0。 +- `test_out_dir`: 测试过程中绘制图像保存的目录。 +- `backend_args`: 用于实例化文件客户端的参数。默认值为 `None `。 + +在 `TrackVisualizationHook` 中,将调用 `TrackLocalVisualizer` 来实现 MOT 和 VIS 任务的可视化。具体细节如下。 + +你可以通过 MMEngine 获取 [Visualization](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/advanced_tutorials/visualization.md) 和 [Hook](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/tutorials/hook.md) 的更多细节。 + +### Tracking 的可视化 + +我们使用 `TrackLocalVisualizer` 这个类以实现跟踪任务可视化。调用方式如下: + +```python +visualizer = dict(type='TrackLocalVisualizer') +``` + +visualizer 共有如下的参数: + +- `name`: 所选实例的名称。默认值为 ‘visualizer’。 + +- `image`: 用于绘制的原始图像。格式需要为 RGB。默认为 None。 + +- `vis_backends`: 可视化后端配置列表。默认为 None。 + +- `save_dir`: 所有后端存储的保存文件目录。如果为 None,后端将不会保存任何数据。 + +- `line_width`: 边框宽度。默认值为 3。 + +- `alpha`: 边界框和掩码的透明度。默认为 0.8。 + +这里提供了一个 DeepSORT 的可视化示例: + +![test_img_89](https://user-images.githubusercontent.com/99722489/186062929-6d0e4663-0d8e-4045-9ec8-67e0e41da876.png) diff --git a/mmdetection/docs/zh_cn/user_guides/train.md b/mmdetection/docs/zh_cn/user_guides/train.md new file mode 100644 index 00000000..8feb1aa6 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/train.md @@ -0,0 +1,451 @@ +# 在标准数据集上训练预定义的模型 + +MMDetection 也为训练检测模型提供了开盖即食的工具。本节将展示在标准数据集(比如 COCO)上如何训练一个预定义的模型。 + +### 数据集 + +训练需要准备好数据集,细节请参考 [数据集准备](#%E6%95%B0%E6%8D%AE%E9%9B%86%E5%87%86%E5%A4%87) 。 + +**注意**: +目前,`configs/cityscapes` 文件夹下的配置文件都是使用 COCO 预训练权值进行初始化的。如果网络连接不可用或者速度很慢,你可以提前下载现存的模型。否则可能在训练的开始会有错误发生。 + +### 学习率自动缩放 + +**注意**:在配置文件中的学习率是在 8 块 GPU,每块 GPU 有 2 张图像(批大小为 8\*2=16)的情况下设置的。其已经设置在 `config/_base_/schedules/schedule_1x.py` 中的 `auto_scale_lr.base_batch_size`。学习率会基于批次大小为 `16`时的值进行自动缩放。同时,为了不影响其他基于 mmdet 的 codebase,启用自动缩放标志 `auto_scale_lr.enable` 默认设置为 `False`。 + +如果要启用此功能,需在命令添加参数 `--auto-scale-lr`。并且在启动命令之前,请检查下即将使用的配置文件的名称,因为配置名称指示默认的批处理大小。 +在默认情况下,批次大小是 `8 x 2 = 16`,例如:`faster_rcnn_r50_caffe_fpn_90k_coco.py` 或者 `pisa_faster_rcnn_x101_32x4d_fpn_1x_coco.py`;若不是默认批次,你可以在配置文件看到像 `_NxM_` 字样的,例如:`cornernet_hourglass104_mstest_32x3_210e_coco.py` 的批次大小是 `32 x 3 = 96`, 或者 `scnet_x101_64x4d_fpn_8x1_20e_coco.py` 的批次大小是 `8 x 1 = 8`。 + +**请记住:如果使用不是默认批次大小为 `16`的配置文件,请检查配置文件中的底部,会有 `auto_scale_lr.base_batch_size`。如果找不到,可以在其继承的 `_base_=[xxx]` 文件中找到。另外,如果想使用自动缩放学习率的功能,请不要修改这些值。** + +学习率自动缩放基本用法如下: + +```shell +python tools/train.py \ + ${CONFIG_FILE} \ + --auto-scale-lr \ + [optional arguments] +``` + +执行命令之后,会根据机器的GPU数量和训练的批次大小对学习率进行自动缩放,缩放方式详见 [线性扩展规则](https://arxiv.org/abs/1706.02677) ,比如:在 4 块 GPU 并且每张 GPU 上有 2 张图片的情况下 `lr=0.01`,那么在 16 块 GPU 并且每张 GPU 上有 4 张图片的情况下, LR 会自动缩放至 `lr=0.08`。 + +如果不启用该功能,则需要根据 [线性扩展规则](https://arxiv.org/abs/1706.02677) 来手动计算并修改配置文件里面 `optimizer.lr` 的值。 + +### 使用单 GPU 训练 + +我们提供了 `tools/train.py` 来开启在单张 GPU 上的训练任务。基本使用如下: + +```shell +python tools/train.py \ + ${CONFIG_FILE} \ + [optional arguments] +``` + +在训练期间,日志文件和 checkpoint 文件将会被保存在工作目录下,它需要通过配置文件中的 `work_dir` 或者 CLI 参数中的 `--work-dir` 来指定。 + +默认情况下,模型将在每轮训练之后在 validation 集上进行测试,测试的频率可以通过设置配置文件来指定: + +```python +# 每 12 轮迭代进行一次测试评估 +train_cfg = dict(val_interval=12) +``` + +这个工具接受以下参数: + +- `--work-dir ${WORK_DIR}`: 覆盖工作目录. +- `--resume`:自动从work_dir中的最新检查点恢复. +- `--resume ${CHECKPOINT_FILE}`: 从某个 checkpoint 文件继续训练. +- `--cfg-options 'Key=value'`: 覆盖使用的配置文件中的其他设置. + +**注意**: +`resume` 和 `load-from` 的区别: + +`resume` 既加载了模型的权重和优化器的状态,也会继承指定 checkpoint 的迭代次数,不会重新开始训练。`load-from` 则是只加载模型的权重,它的训练是从头开始的,经常被用于微调模型。其中load-from需要写入配置文件中,而resume作为命令行参数传入。 + +### 使用 CPU 训练 + +使用 CPU 训练的流程和使用单 GPU 训练的流程一致,我们仅需要在训练流程开始前禁用 GPU。 + +```shell +export CUDA_VISIBLE_DEVICES=-1 +``` + +之后运行单 GPU 训练脚本即可。 + +**注意**: + +我们不推荐用户使用 CPU 进行训练,这太过缓慢。我们支持这个功能是为了方便用户在没有 GPU 的机器上进行调试。 + +### 在多 GPU 上训练 + +我们提供了 `tools/dist_train.sh` 来开启在多 GPU 上的训练。基本使用如下: + +```shell +bash ./tools/dist_train.sh \ + ${CONFIG_FILE} \ + ${GPU_NUM} \ + [optional arguments] +``` + +可选参数和单 GPU 训练的可选参数一致。 + +#### 同时启动多个任务 + +如果你想在一台机器上启动多个任务的话,比如在一个有 8 块 GPU 的机器上启动 2 个需要 4 块GPU的任务,你需要给不同的训练任务指定不同的端口(默认为 29500)来避免冲突。 + +如果你使用 `dist_train.sh` 来启动训练任务,你可以使用命令来设置端口。 + +```shell +CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4 +CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4 +``` + +### 使用多台机器训练 + +如果您想使用由 ethernet 连接起来的多台机器, 您可以使用以下命令: + +在第一台机器上: + +```shell +NNODES=2 NODE_RANK=0 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS +``` + +在第二台机器上: + +```shell +NNODES=2 NODE_RANK=1 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS +``` + +但是,如果您不使用高速网路连接这几台机器的话,训练将会非常慢。 + +### 使用 Slurm 来管理任务 + +Slurm 是一个常见的计算集群调度系统。在 Slurm 管理的集群上,你可以使用 `slurm.sh` 来开启训练任务。它既支持单节点训练也支持多节点训练。 + +基本使用如下: + +```shell +[GPUS=${GPUS}] ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} ${WORK_DIR} +``` + +以下是在一个名称为 _dev_ 的 Slurm 分区上,使用 16 块 GPU 来训练 Mask R-CNN 的例子,并且将 `work-dir` 设置在了某些共享文件系统下。 + +```shell +GPUS=16 ./tools/slurm_train.sh dev mask_r50_1x configs/mask_rcnn_r50_fpn_1x_coco.py /nfs/xxxx/mask_rcnn_r50_fpn_1x +``` + +你可以查看 [源码](https://github.com/open-mmlab/mmdetection/blob/main/tools/slurm_train.sh) 来检查全部的参数和环境变量. + +在使用 Slurm 时,端口需要以下方的某个方法之一来设置。 + +1. 通过 `--options` 来设置端口。我们非常建议用这种方法,因为它无需改变原始的配置文件。 + + ```shell + CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR} --cfg-options 'dist_params.port=29500' + CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR} --cfg-options 'dist_params.port=29501' + ``` + +2. 修改配置文件来设置不同的交流端口。 + + 在 `config1.py` 中,设置: + + ```python + dist_params = dict(backend='nccl', port=29500) + ``` + + 在 `config2.py` 中,设置: + + ```python + dist_params = dict(backend='nccl', port=29501) + ``` + + 然后你可以使用 `config1.py` 和 `config2.py` 来启动两个任务了。 + + ```shell + CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR} + CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR} + ``` + +# 在自定义数据集上进行训练 + +通过本文档,你将会知道如何使用自定义数据集对预先定义好的模型进行推理,测试以及训练。我们使用 [balloon dataset](https://github.com/matterport/Mask_RCNN/tree/master/samples/balloon) 作为例子来描述整个过程。 + +基本步骤如下: + +1. 准备自定义数据集 +2. 准备配置文件 +3. 在自定义数据集上进行训练,测试和推理。 + +## 准备自定义数据集 + +MMDetection 一共支持三种形式应用新数据集: + +1. 将数据集重新组织为 COCO 格式。 +2. 将数据集重新组织为一个中间格式。 +3. 实现一个新的数据集。 + +我们通常建议使用前面两种方法,因为它们通常来说比第三种方法要简单。 + +在本文档中,我们展示一个例子来说明如何将数据转化为 COCO 格式。 + +**注意**:在 MMDetection 3.0 之后,数据集和指标已经解耦(除了 CityScapes)。因此,用户在验证阶段使用任意的评价指标来评价模型在任意数据集上的性能。比如,用 VOC 评价指标来评价模型在 COCO 数据集的性能,或者同时使用 VOC 评价指标和 COCO 评价指标来评价模型在 OpenImages 数据集上的性能。 + +### COCO标注格式 + +用于实例分割的 COCO 数据集格式如下所示,其中的键(key)都是必要的,参考[这里](https://cocodataset.org/#format-data)来获取更多细节。 + +```json +{ + "images": [image], + "annotations": [annotation], + "categories": [category] +} + + +image = { + "id": int, + "width": int, + "height": int, + "file_name": str, +} + +annotation = { + "id": int, + "image_id": int, + "category_id": int, + "segmentation": RLE or [polygon], + "area": float, + "bbox": [x,y,width,height], # (x, y) 为 bbox 左上角的坐标 + "iscrowd": 0 or 1, +} + +categories = [{ + "id": int, + "name": str, + "supercategory": str, +}] +``` + +现在假设我们使用 balloon dataset。 + +下载了数据集之后,我们需要实现一个函数将标注格式转化为 COCO 格式。然后我们就可以使用已经实现的 `CocoDataset` 类来加载数据并进行训练以及评测。 + +如果你浏览过新数据集,你会发现格式如下: + +```json +{'base64_img_data': '', + 'file_attributes': {}, + 'filename': '34020010494_e5cb88e1c4_k.jpg', + 'fileref': '', + 'regions': {'0': {'region_attributes': {}, + 'shape_attributes': {'all_points_x': [1020, + 1000, + 994, + 1003, + 1023, + 1050, + 1089, + 1134, + 1190, + 1265, + 1321, + 1361, + 1403, + 1428, + 1442, + 1445, + 1441, + 1427, + 1400, + 1361, + 1316, + 1269, + 1228, + 1198, + 1207, + 1210, + 1190, + 1177, + 1172, + 1174, + 1170, + 1153, + 1127, + 1104, + 1061, + 1032, + 1020], + 'all_points_y': [963, + 899, + 841, + 787, + 738, + 700, + 663, + 638, + 621, + 619, + 643, + 672, + 720, + 765, + 800, + 860, + 896, + 942, + 990, + 1035, + 1079, + 1112, + 1129, + 1134, + 1144, + 1153, + 1166, + 1166, + 1150, + 1136, + 1129, + 1122, + 1112, + 1084, + 1037, + 989, + 963], + 'name': 'polygon'}}}, + 'size': 1115004} +``` + +标注文件时是 JSON 格式的,其中所有键(key)组成了一张图片的所有标注。 + +其中将 balloon dataset 转化为 COCO 格式的代码如下所示。 + +```python +import os.path as osp + +import mmcv + +from mmengine.fileio import dump, load +from mmengine.utils import track_iter_progress + + +def convert_balloon_to_coco(ann_file, out_file, image_prefix): + data_infos = load(ann_file) + + annotations = [] + images = [] + obj_count = 0 + for idx, v in enumerate(track_iter_progress(data_infos.values())): + filename = v['filename'] + img_path = osp.join(image_prefix, filename) + height, width = mmcv.imread(img_path).shape[:2] + + images.append( + dict(id=idx, file_name=filename, height=height, width=width)) + + for _, obj in v['regions'].items(): + assert not obj['region_attributes'] + obj = obj['shape_attributes'] + px = obj['all_points_x'] + py = obj['all_points_y'] + poly = [(x + 0.5, y + 0.5) for x, y in zip(px, py)] + poly = [p for x in poly for p in x] + + x_min, y_min, x_max, y_max = (min(px), min(py), max(px), max(py)) + + data_anno = dict( + image_id=idx, + id=obj_count, + category_id=0, + bbox=[x_min, y_min, x_max - x_min, y_max - y_min], + area=(x_max - x_min) * (y_max - y_min), + segmentation=[poly], + iscrowd=0) + annotations.append(data_anno) + obj_count += 1 + + coco_format_json = dict( + images=images, + annotations=annotations, + categories=[{ + 'id': 0, + 'name': 'balloon' + }]) + dump(coco_format_json, out_file) + + +if __name__ == '__main__': + convert_balloon_to_coco(ann_file='data/balloon/train/via_region_data.json', + out_file='data/balloon/train/annotation_coco.json', + image_prefix='data/balloon/train') + convert_balloon_to_coco(ann_file='data/balloon/val/via_region_data.json', + out_file='data/balloon/val/annotation_coco.json', + image_prefix='data/balloon/val') +``` + +使用如上的函数,用户可以成功将标注文件转化为 JSON 格式,之后可以使用 `CocoDataset` 对模型进行训练,并用 `CocoMetric` 评测。 + +## 准备配置文件 + +第二步需要准备一个配置文件来成功加载数据集。假设我们想要用 balloon dataset 来训练配备了 FPN 的 Mask R-CNN ,如下是我们的配置文件。假设配置文件命名为 `mask-rcnn_r50-caffe_fpn_ms-poly-1x_balloon.py`,相应保存路径为 `configs/balloon/`,配置文件内容如下所示。详细的配置文件方法可以参考[学习配置文件 — MMDetection 3.0.0 文档](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/config.html#base)。 + +```python +# 新配置继承了基本配置,并做了必要的修改 +_base_ = '../mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py' + +# 我们还需要更改 head 中的 num_classes 以匹配数据集中的类别数 +model = dict( + roi_head=dict( + bbox_head=dict(num_classes=1), mask_head=dict(num_classes=1))) + +# 修改数据集相关配置 +data_root = 'data/balloon/' +metainfo = { + 'classes': ('balloon', ), + 'palette': [ + (220, 20, 60), + ] +} +train_dataloader = dict( + batch_size=1, + dataset=dict( + data_root=data_root, + metainfo=metainfo, + ann_file='train/annotation_coco.json', + data_prefix=dict(img='train/'))) +val_dataloader = dict( + dataset=dict( + data_root=data_root, + metainfo=metainfo, + ann_file='val/annotation_coco.json', + data_prefix=dict(img='val/'))) +test_dataloader = val_dataloader + +# 修改评价指标相关配置 +val_evaluator = dict(ann_file=data_root + 'val/annotation_coco.json') +test_evaluator = val_evaluator + +# 使用预训练的 Mask R-CNN 模型权重来做初始化,可以提高模型性能 +load_from = 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth' + +``` + +## 训练一个新的模型 + +为了使用新的配置方法来对模型进行训练,你只需要运行如下命令。 + +```shell +python tools/train.py configs/balloon/mask-rcnn_r50-caffe_fpn_ms-poly-1x_balloon.py +``` + +参考 [在标准数据集上训练预定义的模型](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/train.html#id1) 来获取更多详细的使用方法。 + +## 测试以及推理 + +为了测试训练完毕的模型,你只需要运行如下命令。 + +```shell +python tools/test.py configs/balloon/mask-rcnn_r50-caffe_fpn_ms-poly-1x_balloon.py work_dirs/mask-rcnn_r50-caffe_fpn_ms-poly-1x_balloon/epoch_12.pth +``` + +参考 [测试现有模型](https://mmdetection.readthedocs.io/zh_CN/latest/user_guides/test.html) 来获取更多详细的使用方法。 diff --git a/mmdetection/docs/zh_cn/user_guides/useful_hooks.md b/mmdetection/docs/zh_cn/user_guides/useful_hooks.md new file mode 100644 index 00000000..07a59df2 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/useful_hooks.md @@ -0,0 +1,107 @@ +# 实用的钩子 + +MMDetection 和 MMEngine 为用户提供了多种多样实用的钩子(Hook),包括 `MemoryProfilerHook`、`NumClassCheckHook` 等等。 +这篇教程介绍了 MMDetection 中实现的钩子功能及使用方式。若使用 MMEngine 定义的钩子请参考 [MMEngine 的钩子API文档](https://github.com/open-mmlab/mmengine/tree/main/docs/en/tutorials/hook.md). + +## CheckInvalidLossHook + +## NumClassCheckHook + +## MemoryProfilerHook + +[内存分析钩子](https://github.com/open-mmlab/mmdetection/blob/main/mmdet/engine/hooks/memory_profiler_hook.py) +记录了包括虚拟内存、交换内存、当前进程在内的所有内存信息,它能够帮助捕捉系统的使用状况与发现隐藏的内存泄露问题。为了使用这个钩子,你需要先通过 `pip install memory_profiler psutil` 命令安装 `memory_profiler` 和 `psutil`。 + +### 使用 + +为了使用这个钩子,使用者需要添加如下代码至 config 文件 + +```python +custom_hooks = [ + dict(type='MemoryProfilerHook', interval=50) +] +``` + +### 结果 + +在训练中,你会看到 `MemoryProfilerHook` 记录的如下信息: + +```text +The system has 250 GB (246360 MB + 9407 MB) of memory and 8 GB (5740 MB + 2452 MB) of swap memory in total. Currently 9407 MB (4.4%) of memory and 5740 MB (29.9%) of swap memory were consumed. And the current training process consumed 5434 MB of memory. +``` + +```text +2022-04-21 08:49:56,881 - mmengine - INFO - Memory information available_memory: 246360 MB, used_memory: 9407 MB, memory_utilization: 4.4 %, available_swap_memory: 5740 MB, used_swap_memory: 2452 MB, swap_memory_utilization: 29.9 %, current_process_memory: 5434 MB +``` + +## SetEpochInfoHook + +## SyncNormHook + +## SyncRandomSizeHook + +## YOLOXLrUpdaterHook + +## YOLOXModeSwitchHook + +## 如何实现自定义钩子 + +通常,从模型训练的开始到结束,共有20个点位可以执行钩子。我们可以实现自定义钩子在不同点位执行,以便在训练中实现自定义操作。 + +- global points: `before_run`, `after_run` +- points in training: `before_train`, `before_train_epoch`, `before_train_iter`, `after_train_iter`, `after_train_epoch`, `after_train` +- points in validation: `before_val`, `before_val_epoch`, `before_val_iter`, `after_val_iter`, `after_val_epoch`, `after_val` +- points at testing: `before_test`, `before_test_epoch`, `before_test_iter`, `after_test_iter`, `after_test_epoch`, `after_test` +- other points: `before_save_checkpoint`, `after_save_checkpoint` + +比如,我们要实现一个检查 loss 的钩子,当损失为 NaN 时自动结束训练。我们可以把这个过程分为三步: + +1. 在 MMEngine 实现一个继承于 `Hook` 类的新钩子,并实现 `after_train_iter` 方法用于检查每 `n` 次训练迭代后损失是否变为 NaN 。 +2. 使用 `@HOOKS.register_module()` 注册实现好了的自定义钩子,如下列代码所示。 +3. 在配置文件中添加 `custom_hooks = [dict(type='MemoryProfilerHook', interval=50)]` + +```python +from typing import Optional + +import torch +from mmengine.hooks import Hook +from mmengine.runner import Runner + +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class CheckInvalidLossHook(Hook): + """Check invalid loss hook. + + This hook will regularly check whether the loss is valid + during training. + + Args: + interval (int): Checking interval (every k iterations). + Default: 50. + """ + + def __init__(self, interval: int = 50) -> None: + self.interval = interval + + def after_train_iter(self, + runner: Runner, + batch_idx: int, + data_batch: Optional[dict] = None, + outputs: Optional[dict] = None) -> None: + """Regularly check whether the loss is valid every n iterations. + + Args: + runner (:obj:`Runner`): The runner of the training process. + batch_idx (int): The index of the current batch in the train loop. + data_batch (dict, Optional): Data from dataloader. + Defaults to None. + outputs (dict, Optional): Outputs from model. Defaults to None. + """ + if self.every_n_train_iters(runner, self.interval): + assert torch.isfinite(outputs['loss']), \ + runner.logger.info('loss become infinite or NaN!') +``` + +请参考 [自定义训练配置](../advanced_guides/customize_runtime.md) 了解更多与自定义钩子相关的内容。 diff --git a/mmdetection/docs/zh_cn/user_guides/useful_tools.md b/mmdetection/docs/zh_cn/user_guides/useful_tools.md new file mode 100644 index 00000000..8416472c --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/useful_tools.md @@ -0,0 +1,636 @@ +除了训练和测试脚本,我们还在 `tools/` 目录下提供了许多有用的工具。 + +## 日志分析 + +`tools/analysis_tools/analyze_logs.py` 可利用指定的训练 log 文件绘制 loss/mAP 曲线图, +第一次运行前请先运行 `pip install seaborn` 安装必要依赖. + +```shell +python tools/analysis_tools/analyze_logs.py plot_curve [--keys ${KEYS}] [--eval-interval ${EVALUATION_INTERVAL}] [--title ${TITLE}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}] +``` + +![loss curve image](../../../resources/loss_curve.png) + +样例: + +- 绘制分类损失曲线图 + + ```shell + python tools/analysis_tools/analyze_logs.py plot_curve log.json --keys loss_cls --legend loss_cls + ``` + +- 绘制分类损失、回归损失曲线图,保存图片为对应的 pdf 文件 + + ```shell + python tools/analysis_tools/analyze_logs.py plot_curve log.json --keys loss_cls loss_bbox --out losses.pdf + ``` + +- 在相同图像中比较两次运行结果的 bbox mAP + + ```shell + python tools/analysis_tools/analyze_logs.py plot_curve log1.json log2.json --keys bbox_mAP --legend run1 run2 + ``` + +- 计算平均训练速度 + + ```shell + python tools/analysis_tools/analyze_logs.py cal_train_time log.json [--include-outliers] + ``` + + 输出以如下形式展示 + + ```text + -----Analyze train time of work_dirs/some_exp/20190611_192040.log.json----- + slowest epoch 11, average time is 1.2024 + fastest epoch 1, average time is 1.1909 + time std over epochs is 0.0028 + average iter time: 1.1959 s/iter + ``` + +## 结果分析 + +使用 `tools/analysis_tools/analyze_results.py` 可计算每个图像 mAP,随后根据真实标注框与预测框的比较结果,展示或保存最高与最低 top-k 得分的预测图像。 + +**使用方法** + +```shell +python tools/analysis_tools/analyze_results.py \ + ${CONFIG} \ + ${PREDICTION_PATH} \ + ${SHOW_DIR} \ + [--show] \ + [--wait-time ${WAIT_TIME}] \ + [--topk ${TOPK}] \ + [--show-score-thr ${SHOW_SCORE_THR}] \ + [--cfg-options ${CFG_OPTIONS}] +``` + +各个参数选项的作用: + +- `config`: model config 文件的路径。 +- `prediction_path`: 使用 `tools/test.py` 输出的 pickle 格式结果文件。 +- `show_dir`: 绘制真实标注框与预测框的图像存放目录。 +- `--show`:决定是否展示绘制 box 后的图片,默认值为 `False`。 +- `--wait-time`: show 时间的间隔,若为 0 表示持续显示。 +- `--topk`: 根据最高或最低 `topk` 概率排序保存的图片数量,若不指定,默认设置为 `20`。 +- `--show-score-thr`: 能够展示的概率阈值,默认为 `0`。 +- `--cfg-options`: 如果指定,可根据指定键值对覆盖更新配置文件的对应选项 + +**样例**: +假设你已经通过 `tools/test.py` 得到了 pickle 格式的结果文件,路径为 './result.pkl'。 + +1. 测试 Faster R-CNN 并可视化结果,保存图片至 `results/` + +```shell +python tools/analysis_tools/analyze_results.py \ + configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \ + result.pkl \ + results \ + --show +``` + +2. 测试 Faster R-CNN 并指定 top-k 参数为 50,保存结果图片至 `results/` + +```shell +python tools/analysis_tools/analyze_results.py \ + configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \ + result.pkl \ + results \ + --topk 50 +``` + +3. 如果你想过滤低概率的预测结果,指定 `show-score-thr` 参数 + +```shell +python tools/analysis_tools/analyze_results.py \ + configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \ + result.pkl \ + results \ + --show-score-thr 0.3 +``` + +## 多模型检测结果融合 + +`tools/analysis_tools/fuse_results.py` 可使用 Weighted Boxes Fusion(WBF) 方法将多个模型的检测结果进行融合。(当前仅支持 COCO 格式) + +**使用方法** + +```shell +python tools/analysis_tools/fuse_results.py \ + ${PRED_RESULTS} \ + [--annotation ${ANNOTATION}] \ + [--weights ${WEIGHTS}] \ + [--fusion-iou-thr ${FUSION_IOU_THR}] \ + [--skip-box-thr ${SKIP_BOX_THR}] \ + [--conf-type ${CONF_TYPE}] \ + [--eval-single ${EVAL_SINGLE}] \ + [--save-fusion-results ${SAVE_FUSION_RESULTS}] \ + [--out-dir ${OUT_DIR}] +``` + +各个参数选项的作用: + +- `pred-results`: 多模型测试结果的保存路径。(目前仅支持 json 格式) +- `--annotation`: 真实标注框的保存路径。 +- `--weights`: 模型融合权重。默认设置下,每个模型的权重均为1。 +- `--fusion-iou-thr`: 在WBF算法中,匹配成功的 IoU 阈值,默认值为`0.55`。 +- `--skip-box-thr`: WBF算法中需剔除的置信度阈值,置信度小于该值的 bbox 会被剔除,默认值为`0`。 +- `--conf-type`: 如何计算融合后 bbox 的置信度。有以下四种选项: + - `avg`: 取平均值,默认为此选项。 + - `max`: 取最大值。 + - `box_and_model_avg`: box和模型尺度的加权平均值。 + - `absent_model_aware_avg`: 考虑缺失模型的加权平均值。 +- `--eval-single`: 是否评估每个单一模型,默认值为`False`。 +- `--save-fusion-results`: 是否保存融合结果,默认值为`False`。 +- `--out-dir`: 融合结果保存的路径。 + +**样例**: +假设你已经通过 `tools/test.py` 得到了3个模型的 json 格式的结果文件,路径分别为 './faster-rcnn_r50-caffe_fpn_1x_coco.json', './retinanet_r50-caffe_fpn_1x_coco.json', './cascade-rcnn_r50-caffe_fpn_1x_coco.json',真实标注框的文件路径为'./annotation.json'。 + +1. 融合三个模型的预测结果并评估其效果 + +```shell +python tools/analysis_tools/fuse_results.py \ + ./faster-rcnn_r50-caffe_fpn_1x_coco.json \ + ./retinanet_r50-caffe_fpn_1x_coco.json \ + ./cascade-rcnn_r50-caffe_fpn_1x_coco.json \ + --annotation ./annotation.json \ + --weights 1 2 3 \ +``` + +2. 同时评估每个单一模型与融合结果 + +```shell +python tools/analysis_tools/fuse_results.py \ + ./faster-rcnn_r50-caffe_fpn_1x_coco.json \ + ./retinanet_r50-caffe_fpn_1x_coco.json \ + ./cascade-rcnn_r50-caffe_fpn_1x_coco.json \ + --annotation ./annotation.json \ + --weights 1 2 3 \ + --eval-single +``` + +3. 融合三个模型的预测结果并保存 + +```shell +python tools/analysis_tools/fuse_results.py \ + ./faster-rcnn_r50-caffe_fpn_1x_coco.json \ + ./retinanet_r50-caffe_fpn_1x_coco.json \ + ./cascade-rcnn_r50-caffe_fpn_1x_coco.json \ + --annotation ./annotation.json \ + --weights 1 2 3 \ + --save-fusion-results \ + --out-dir outputs/fusion +``` + +## 可视化 + +### 可视化数据集 + +`tools/analysis_tools/browse_dataset.py` 可帮助使用者检查所使用的检测数据集(包括图像和标注),或保存图像至指定目录。 + +```shell +python tools/analysis_tools/browse_dataset.py ${CONFIG} [-h] [--skip-type ${SKIP_TYPE[SKIP_TYPE...]}] [--output-dir ${OUTPUT_DIR}] [--not-show] [--show-interval ${SHOW_INTERVAL}] +``` + +### 可视化模型 + +在可视化之前,需要先转换模型至 ONNX 格式,[可参考此处](#convert-mmdetection-model-to-onnx-experimental)。 +注意,现在只支持 RetinaNet,之后的版本将会支持其他模型 +转换后的模型可以被其他工具可视化[Netron](https://github.com/lutzroeder/netron)。 + +### 可视化预测结果 + +如果你想要一个轻量 GUI 可视化检测结果,你可以参考 [DetVisGUI project](https://github.com/Chien-Hung/DetVisGUI/tree/mmdetection)。 + +## 误差分析 + +`tools/analysis_tools/coco_error_analysis.py` 使用不同标准分析每个类别的 COCO 评估结果。同时将一些有帮助的信息体现在图表上。 + +```shell +python tools/analysis_tools/coco_error_analysis.py ${RESULT} ${OUT_DIR} [-h] [--ann ${ANN}] [--types ${TYPES[TYPES...]}] +``` + +样例: + +假设你已经把 [Mask R-CNN checkpoint file](https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth) 放置在文件夹 'checkpoint' 中(其他模型请在 [model zoo](./model_zoo.md) 中获取)。 + +为了保存 bbox 结果信息,我们需要用下列方式修改 `test_evaluator` : + +1. 查找当前 config 文件相对应的 'configs/base/datasets' 数据集信息。 +2. 用当前数据集 config 中的 test_evaluator 以及 test_dataloader 替换原始文件的 test_evaluator 以及 test_dataloader。 +3. 使用以下命令得到 bbox 或 segmentation 的 json 格式文件。 + +```shell +python tools/test.py \ + configs/mask_rcnn/mask-rcnn_r50_fpn_1x_coco.py \ + checkpoint/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth \ +``` + +1. 得到每一类的 COCO bbox 误差结果,并保存分析结果图像至指定目录。(在 [config](../../../configs/_base_/datasets/coco_instance.py) 中默认目录是 './work_dirs/coco_instance/test') + +```shell +python tools/analysis_tools/coco_error_analysis.py \ + results.bbox.json \ + results \ + --ann=data/coco/annotations/instances_val2017.json \ +``` + +2. 得到每一类的 COCO 分割误差结果,并保存分析结果图像至指定目录。 + +```shell +python tools/analysis_tools/coco_error_analysis.py \ + results.segm.json \ + results \ + --ann=data/coco/annotations/instances_val2017.json \ + --types='segm' +``` + +## 模型服务部署 + +如果你想使用 [`TorchServe`](https://pytorch.org/serve/) 搭建一个 `MMDetection` 模型服务,可以参考以下步骤: + +### 1. 安装 TorchServe + +假设你已经成功安装了包含 `PyTorch` 和 `MMDetection` 的 `Python` 环境,那么你可以运行以下命令来安装 `TorchServe` 及其依赖项。有关更多其他安装选项,请参考[快速入门](https://github.com/pytorch/serve/blob/master/README.md#serve-a-model)。 + +```shell +python -m pip install torchserve torch-model-archiver torch-workflow-archiver nvgpu +``` + +**注意**: 如果你想在 docker 中使用`TorchServe`,请参考[torchserve docker](https://github.com/pytorch/serve/blob/master/docker/README.md)。 + +### 2. 把 MMDetection 模型转换至 TorchServe + +```shell +python tools/deployment/mmdet2torchserve.py ${CONFIG_FILE} ${CHECKPOINT_FILE} \ +--output-folder ${MODEL_STORE} \ +--model-name ${MODEL_NAME} +``` + +### 3. 启动 `TorchServe` + +```shell +torchserve --start --ncs \ + --model-store ${MODEL_STORE} \ + --models ${MODEL_NAME}.mar +``` + +### 4. 测试部署效果 + +```shell +curl -O curl -O https://raw.githubusercontent.com/pytorch/serve/master/docs/images/3dogs.jpg +curl http://127.0.0.1:8080/predictions/${MODEL_NAME} -T 3dogs.jpg +``` + +你可以得到下列 json 信息: + +```json +[ + { + "class_label": 16, + "class_name": "dog", + "bbox": [ + 294.63409423828125, + 203.99111938476562, + 417.048583984375, + 281.62744140625 + ], + "score": 0.9987992644309998 + }, + { + "class_label": 16, + "class_name": "dog", + "bbox": [ + 404.26019287109375, + 126.0080795288086, + 574.5091552734375, + 293.6662292480469 + ], + "score": 0.9979367256164551 + }, + { + "class_label": 16, + "class_name": "dog", + "bbox": [ + 197.2144775390625, + 93.3067855834961, + 307.8505554199219, + 276.7560119628906 + ], + "score": 0.993338406085968 + } +] +``` + +#### 结果对比 + +你也可以使用 `test_torchserver.py` 来比较 `TorchServe` 和 `PyTorch` 的结果,并可视化: + +```shell +python tools/deployment/test_torchserver.py ${IMAGE_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} ${MODEL_NAME} +[--inference-addr ${INFERENCE_ADDR}] [--device ${DEVICE}] [--score-thr ${SCORE_THR}] [--work-dir ${WORK_DIR}] +``` + +样例: + +```shell +python tools/deployment/test_torchserver.py \ +demo/demo.jpg \ +configs/yolo/yolov3_d53_8xb8-320-273e_coco.py \ +checkpoint/yolov3_d53_320_273e_coco-421362b6.pth \ +yolov3 \ +--work-dir ./work-dir +``` + +### 5. 停止 `TorchServe` + +```shell +torchserve --stop +``` + +## 模型复杂度 + +`tools/analysis_tools/get_flops.py` 工具可用于计算指定模型的 FLOPs、参数量大小(改编自 [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) )。 + +```shell +python tools/analysis_tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}] +``` + +获得的结果如下: + +```text +============================== +Input shape: (3, 1280, 800) +Flops: 239.32 GFLOPs +Params: 37.74 M +============================== +``` + +**注意**:这个工具还只是实验性质,我们不保证这个数值是绝对正确的。你可以将他用于简单的比较,但如果用于科技论文报告需要再三检查确认。 + +1. FLOPs 与输入的形状大小相关,参数量没有这个关系,默认的输入形状大小为 (1, 3, 1280, 800) 。 +2. 一些算子并不计入 FLOPs,比如 GN 或其他自定义的算子。你可以参考 [`mmcv.cnn.get_model_complexity_info()`](https://github.com/open-mmlab/mmcv/blob/2.x/mmcv/cnn/utils/flops_counter.py) 查看更详细的说明。 +3. 两阶段检测的 FLOPs 大小取决于 proposal 的数量。 + +## 模型转换 + +### MMDetection 模型转换至 ONNX 格式 + +我们提供了一个脚本用于转换模型至 [ONNX](https://github.com/onnx/onnx) 格式。同时还支持比较 Pytorch 与 ONNX 模型的输出结果以便对照。更详细的内容可以参考 [mmdeploy](https://github.com/open-mmlab/mmdeploy)。 + +### MMDetection 1.x 模型转换至 MMDetection 2.x 模型 + +`tools/model_converters/upgrade_model_version.py` 可将旧版本的 MMDetection checkpoints 转换至新版本。但要注意此脚本不保证在新版本加入非兼容更新后还能正常转换,建议您直接使用新版本的 checkpoints。 + +```shell +python tools/model_converters/upgrade_model_version.py ${IN_FILE} ${OUT_FILE} [-h] [--num-classes NUM_CLASSES] +``` + +### RegNet 模型转换至 MMDetection 模型 + +`tools/model_converters/regnet2mmdet.py` 将 pycls 编码的预训练 RegNet 模型转换为 MMDetection 风格。 + +```shell +python tools/model_converters/regnet2mmdet.py ${SRC} ${DST} [-h] +``` + +### Detectron ResNet 模型转换至 Pytorch 模型 + +`tools/model_converters/detectron2pytorch.py` 将 detectron 的原始预训练 RegNet 模型转换为 MMDetection 风格。 + +```shell +python tools/model_converters/detectron2pytorch.py ${SRC} ${DST} ${DEPTH} [-h] +``` + +### 制作发布用模型 + +`tools/model_converters/publish_model.py` 可用来制作一个发布用的模型。 + +在发布模型至 AWS 之前,你可能需要: + +1. 将模型转换至 CPU 张量 +2. 删除优化器状态 +3. 计算 checkpoint 文件的 hash 值,并将 hash 号码记录至文件名。 + +```shell +python tools/model_converters/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME} +``` + +样例: + +```shell +python tools/model_converters/publish_model.py work_dirs/faster_rcnn/latest.pth faster_rcnn_r50_fpn_1x_20190801.pth +``` + +最后输出的文件名如下所示: `faster_rcnn_r50_fpn_1x_20190801-{hash id}.pth`. + +## 数据集转换 + +`tools/data_converters/` 提供了将 Cityscapes 数据集与 Pascal VOC 数据集转换至 COCO 数据集格式的工具 + +```shell +python tools/dataset_converters/cityscapes.py ${CITYSCAPES_PATH} [-h] [--img-dir ${IMG_DIR}] [--gt-dir ${GT_DIR}] [-o ${OUT_DIR}] [--nproc ${NPROC}] +python tools/dataset_converters/pascal_voc.py ${DEVKIT_PATH} [-h] [-o ${OUT_DIR}] +``` + +## 数据集下载 + +`tools/misc/download_dataset.py` 可以下载各类形如 COCO, VOC, LVIS 数据集。 + +```shell +python tools/misc/download_dataset.py --dataset-name coco2017 +python tools/misc/download_dataset.py --dataset-name voc2007 +python tools/misc/download_dataset.py --dataset-name lvis +``` + +对于中国境内的用户,我们也推荐使用开源数据平台 [OpenDataLab](https://opendatalab.com/?source=OpenMMLab%20GitHub) 来获取这些数据集,以获得更好的下载体验: + +- [COCO2017](https://opendatalab.com/COCO_2017/download?source=OpenMMLab%20GitHub) +- [VOC2007](https://opendatalab.com/PASCAL_VOC2007/download?source=OpenMMLab%20GitHub) +- [VOC2012](https://opendatalab.com/PASCAL_VOC2012/download?source=OpenMMLab%20GitHub) +- [LVIS](https://opendatalab.com/LVIS/download?source=OpenMMLab%20GitHub) + +## 基准测试 + +### 鲁棒性测试基准 + +`tools/analysis_tools/test_robustness.py` 及 `tools/analysis_tools/robustness_eval.py` 帮助使用者衡量模型的鲁棒性。其核心思想来源于 [Benchmarking Robustness in Object Detection: Autonomous Driving when Winter is Coming](https://arxiv.org/abs/1907.07484)。如果你想了解如何在污损图像上评估模型的效果,以及参考该基准的一组标准模型,请参照 [robustness_benchmarking.md](robustness_benchmarking.md)。 + +### FPS 测试基准 + +`tools/analysis_tools/benchmark.py` 可帮助使用者计算 FPS,FPS 计算包括了模型向前传播与后处理过程。为了得到更精确的计算值,现在的分布式计算模式只支持一个 GPU。 + +```shell +python -m torch.distributed.launch --nproc_per_node=1 --master_port=${PORT} tools/analysis_tools/benchmark.py \ + ${CONFIG} \ + [--checkpoint ${CHECKPOINT}] \ + [--repeat-num ${REPEAT_NUM}] \ + [--max-iter ${MAX_ITER}] \ + [--log-interval ${LOG_INTERVAL}] \ + --launcher pytorch +``` + +样例:假设你已经下载了 `Faster R-CNN` 模型 checkpoint 并放置在 `checkpoints/` 目录下。 + +```shell +python -m torch.distributed.launch --nproc_per_node=1 --master_port=29500 tools/analysis_tools/benchmark.py \ + configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \ + checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth \ + --launcher pytorch +``` + +## 更多工具 + +### 以某个评估标准进行评估 + +`tools/analysis_tools/eval_metric.py` 根据配置文件中的评估方式对 pkl 结果文件进行评估。 + +```shell +python tools/analysis_tools/eval_metric.py ${CONFIG} ${PKL_RESULTS} [-h] [--format-only] [--eval ${EVAL[EVAL ...]}] + [--cfg-options ${CFG_OPTIONS [CFG_OPTIONS ...]}] + [--eval-options ${EVAL_OPTIONS [EVAL_OPTIONS ...]}] +``` + +### 打印全部 config + +`tools/misc/print_config.py` 可将所有配置继承关系展开,完全打印相应的配置文件。 + +```shell +python tools/misc/print_config.py ${CONFIG} [-h] [--options ${OPTIONS [OPTIONS...]}] +``` + +## 超参数优化 + +### YOLO Anchor 优化 + +`tools/analysis_tools/optimize_anchors.py` 提供了两种方法优化 YOLO 的 anchors。 + +其中一种方法使用 K 均值 anchor 聚类(k-means anchor cluster),源自 [darknet](https://github.com/AlexeyAB/darknet/blob/master/src/detector.c#L1421)。 + +```shell +python tools/analysis_tools/optimize_anchors.py ${CONFIG} --algorithm k-means --input-shape ${INPUT_SHAPE [WIDTH HEIGHT]} --output-dir ${OUTPUT_DIR} +``` + +另一种方法使用差分进化算法优化 anchors。 + +```shell +python tools/analysis_tools/optimize_anchors.py ${CONFIG} --algorithm differential_evolution --input-shape ${INPUT_SHAPE [WIDTH HEIGHT]} --output-dir ${OUTPUT_DIR} +``` + +样例: + +```shell +python tools/analysis_tools/optimize_anchors.py configs/yolo/yolov3_d53_8xb8-320-273e_coco.py --algorithm differential_evolution --input-shape 608 608 --device cuda --output-dir work_dirs +``` + +你可能会看到如下结果: + +``` +loading annotations into memory... +Done (t=9.70s) +creating index... +index created! +2021-07-19 19:37:20,951 - mmdet - INFO - Collecting bboxes from annotation... +[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 117266/117266, 15874.5 task/s, elapsed: 7s, ETA: 0s + +2021-07-19 19:37:28,753 - mmdet - INFO - Collected 849902 bboxes. +differential_evolution step 1: f(x)= 0.506055 +differential_evolution step 2: f(x)= 0.506055 +...... + +differential_evolution step 489: f(x)= 0.386625 +2021-07-19 19:46:40,775 - mmdet - INFO Anchor evolution finish. Average IOU: 0.6133754253387451 +2021-07-19 19:46:40,776 - mmdet - INFO Anchor differential evolution result:[[10, 12], [15, 30], [32, 22], [29, 59], [61, 46], [57, 116], [112, 89], [154, 198], [349, 336]] +2021-07-19 19:46:40,798 - mmdet - INFO Result saved in work_dirs/anchor_optimize_result.json +``` + +## 混淆矩阵 + +混淆矩阵是对检测结果的概览。 +`tools/analysis_tools/confusion_matrix.py` 可对预测结果进行分析,绘制成混淆矩阵表。 +首先,运行 `tools/test.py` 保存 `.pkl` 预测结果。 +之后再运行: + +``` +python tools/analysis_tools/confusion_matrix.py ${CONFIG} ${DETECTION_RESULTS} ${SAVE_DIR} --show +``` + +最后你可以得到如图的混淆矩阵: + +![confusion_matrix_example](https://user-images.githubusercontent.com/12907710/140513068-994cdbf4-3a4a-48f0-8fd8-2830d93fd963.png) + +## COCO 分离和遮挡实例分割性能评估 + +对于最先进的目标检测器来说,检测被遮挡的物体仍然是一个挑战。 +我们实现了论文 [A Tri-Layer Plugin to Improve Occluded Detection](https://arxiv.org/abs/2210.10046) 中提出的指标来计算分离和遮挡目标的召回率。 + +使用此评价指标有两种方法: + +### 离线评测 + +我们提供了一个脚本对存储后的检测结果文件计算指标。 + +首先,使用 `tools/test.py` 脚本存储检测结果: + +```shell +python tools/test.py ${CONFIG} ${MODEL_PATH} --out results.pkl +``` + +然后,运行 `tools/analysis_tools/coco_occluded_separated_recall.py` 脚本来计算分离和遮挡目标的掩码的召回率: + +```shell +python tools/analysis_tools/coco_occluded_separated_recall.py results.pkl --out occluded_separated_recall.json +``` + +输出如下: + +``` +loading annotations into memory... +Done (t=0.51s) +creating index... +index created! +processing detection results... +[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 5000/5000, 109.3 task/s, elapsed: 46s, ETA: 0s +computing occluded mask recall... +[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 5550/5550, 780.5 task/s, elapsed: 7s, ETA: 0s +COCO occluded mask recall: 58.79% +COCO occluded mask success num: 3263 +computing separated mask recall... +[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 3522/3522, 778.3 task/s, elapsed: 5s, ETA: 0s +COCO separated mask recall: 31.94% +COCO separated mask success num: 1125 + ++-----------+--------+-------------+ +| mask type | recall | num correct | ++-----------+--------+-------------+ +| occluded | 58.79% | 3263 | +| separated | 31.94% | 1125 | ++-----------+--------+-------------+ +Evaluation results have been saved to occluded_separated_recall.json. +``` + +### 在线评测 + +我们实现继承自 `CocoMetic` 的 `CocoOccludedSeparatedMetric`。 +要在训练期间评估分离和遮挡掩码的召回率,只需在配置中将 evaluator 类型替换为 `CocoOccludedSeparatedMetric`: + +```python +val_evaluator = dict( + type='CocoOccludedSeparatedMetric', # 修改此处 + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False) +test_evaluator = val_evaluator +``` + +如果您使用了此指标,请引用论文: + +```latex +@article{zhan2022triocc, + title={A Tri-Layer Plugin to Improve Occluded Detection}, + author={Zhan, Guanqi and Xie, Weidi and Zisserman, Andrew}, + journal={British Machine Vision Conference}, + year={2022} +} +``` diff --git a/mmdetection/docs/zh_cn/user_guides/visualization.md b/mmdetection/docs/zh_cn/user_guides/visualization.md new file mode 100644 index 00000000..f90ab6d4 --- /dev/null +++ b/mmdetection/docs/zh_cn/user_guides/visualization.md @@ -0,0 +1,93 @@ +# 可视化 + +在阅读本教程之前,建议先阅读 MMEngine 的 [Visualization](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/visualization.md) 文档,以对 `Visualizer` 的定义和用法有一个初步的了解。 + +简而言之,`Visualizer` 在 MMEngine 中实现以满足日常可视化需求,并包含以下三个主要功能: + +- 实现通用的绘图 API,例如 [`draw_bboxes`](mmengine.visualization.Visualizer.draw_bboxes) 实现了绘制边界框的功能,[`draw_lines`](mmengine.visualization.Visualizer.draw_lines) 实现了绘制线条的功能。 +- 支持将可视化结果、学习率曲线、损失函数曲线以及验证精度曲线写入到各种后端中,包括本地磁盘以及常见的深度学习训练日志工具,例如 [TensorBoard](https://www.tensorflow.org/tensorboard) 和 [Wandb](https://wandb.ai/site)。 +- 支持在代码的任何位置调用以可视化或记录模型在训练或测试期间的中间状态,例如特征图和验证结果。 + +基于 MMEngine 的 `Visualizer`,MMDet 提供了各种预构建的可视化工具,用户可以通过简单地修改以下配置文件来使用它们。 + +- `tools/analysis_tools/browse_dataset.py` 脚本提供了一个数据集可视化功能,可以在数据经过数据转换后绘制图像和相应的注释,具体描述请参见[`browse_dataset.py`](useful_tools.md#Visualization)。 + +- MMEngine实现了`LoggerHook`,使用`Visualizer`将学习率、损失和评估结果写入由`Visualizer`设置的后端。因此,通过修改配置文件中的`Visualizer`后端,例如修改为`TensorBoardVISBackend`或`WandbVISBackend`,可以实现日志记录到常用的训练日志工具,如`TensorBoard`或`WandB`,从而方便用户使用这些可视化工具来分析和监控训练过程。 + +- 在MMDet中实现了`VisualizerHook`,它使用`Visualizer`将验证或预测阶段的预测结果可视化或存储到由`Visualizer`设置的后端。因此,通过修改配置文件中的`Visualizer`后端,例如修改为`TensorBoardVISBackend`或`WandbVISBackend`,可以将预测图像存储到`TensorBoard`或`Wandb`中。 + +## 配置 + +由于使用了注册机制,在MMDet中我们可以通过修改配置文件来设置`Visualizer`的行为。通常,我们会在`configs/_base_/default_runtime.py`中为可视化器定义默认配置,详细信息请参见[配置教程](config.md)。 + +```Python +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='DetLocalVisualizer', + vis_backends=vis_backends, + name='visualizer') +``` + +基于上面的例子,我们可以看到`Visualizer`的配置由两个主要部分组成,即`Visualizer`类型和其使用的可视化后端`vis_backends`。 + +- 用户可直接使用`DetLocalVisualizer`来可视化支持任务的标签或预测结果。 +- MMDet默认将可视化后端`vis_backend`设置为本地可视化后端`LocalVisBackend`,将所有可视化结果和其他训练信息保存在本地文件夹中。 + +## 存储 + +MMDet默认使用本地可视化后端[`LocalVisBackend`](mmengine.visualization.LocalVisBackend),`VisualizerHook`和`LoggerHook`中存储的模型损失、学习率、模型评估精度和可视化信息,包括损失、学习率、评估精度将默认保存到`{work_dir}/{config_name}/{time}/{vis_data}`文件夹中。此外,MMDet还支持其他常见的可视化后端,例如`TensorboardVisBackend`和`WandbVisBackend`,您只需要在配置文件中更改`vis_backends`类型为相应的可视化后端即可。例如,只需在配置文件中插入以下代码块即可将数据存储到`TensorBoard`和`Wandb`中。 + +```Python +# https://mmengine.readthedocs.io/en/latest/api/visualization.html +_base_.visualizer.vis_backends = [ + dict(type='LocalVisBackend'), # + dict(type='TensorboardVisBackend'), + dict(type='WandbVisBackend'),] +``` + +## 绘图 + +### 绘制预测结果 + +MMDet主要使用[`DetVisualizationHook`](mmdet.engine.hooks.DetVisualizationHook)来绘制验证和测试的预测结果,默认情况下`DetVisualizationHook`是关闭的,其默认配置如下。 + +```Python +visualization=dict( #用户可视化验证和测试结果 + type='DetVisualizationHook', + draw=False, + interval=1, + show=False) +``` + +以下表格展示了`DetVisualizationHook`支持的参数。 + +| 参数 | 描述 | +| :------: | :------------------------------------------------------------------------------: | +| draw | DetVisualizationHook通过enable参数打开和关闭,默认状态为关闭。 | +| interval | 控制在DetVisualizationHook启用时存储或显示验证或测试结果的间隔,单位为迭代次数。 | +| show | 控制是否可视化验证或测试的结果。 | + +如果您想在训练或测试期间启用 `DetVisualizationHook` 相关功能和配置,您只需要修改配置文件,以 `configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py` 为例,同时绘制注释和预测,并显示图像,配置文件可以修改如下: + +```Python +visualization = _base_.default_hooks.visualization +visualization.update(dict(draw=True, show=True)) +``` + +
    + +
    + +`test.py`程序提供了`--show`和`--show-dir`参数,可以在测试过程中可视化注释和预测结果,而不需要修改配置文件,从而进一步简化了测试过程。 + +```Shell +# 展示测试结果 +python tools/test.py configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_tiny_8xb32-300e_coco/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth --show + +# 指定存储预测结果的位置 +python tools/test.py configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_tiny_8xb32-300e_coco/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth --show-dir imgs/ +``` + +
    + +
    diff --git a/mmdetection/mmdet/__init__.py b/mmdetection/mmdet/__init__.py new file mode 100644 index 00000000..3ac884ac --- /dev/null +++ b/mmdetection/mmdet/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmcv +import mmengine +from mmengine.utils import digit_version + +from .version import __version__, version_info + +mmcv_minimum_version = '2.0.0rc4' +mmcv_maximum_version = '2.2.0' +mmcv_version = digit_version(mmcv.__version__) + +mmengine_minimum_version = '0.7.1' +mmengine_maximum_version = '1.0.0' +mmengine_version = digit_version(mmengine.__version__) + +assert (mmcv_version >= digit_version(mmcv_minimum_version) + and mmcv_version < digit_version(mmcv_maximum_version)), \ + f'MMCV=={mmcv.__version__} is used but incompatible. ' \ + f'Please install mmcv>={mmcv_minimum_version}, <{mmcv_maximum_version}.' + +assert (mmengine_version >= digit_version(mmengine_minimum_version) + and mmengine_version < digit_version(mmengine_maximum_version)), \ + f'MMEngine=={mmengine.__version__} is used but incompatible. ' \ + f'Please install mmengine>={mmengine_minimum_version}, ' \ + f'<{mmengine_maximum_version}.' + +__all__ = ['__version__', 'version_info', 'digit_version'] diff --git a/mmdetection/mmdet/apis/__init__.py b/mmdetection/mmdet/apis/__init__.py new file mode 100644 index 00000000..c89dc729 --- /dev/null +++ b/mmdetection/mmdet/apis/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .det_inferencer import DetInferencer +from .inference import (async_inference_detector, inference_detector, + inference_mot, init_detector, init_track_model) + +__all__ = [ + 'init_detector', 'async_inference_detector', 'inference_detector', + 'DetInferencer', 'inference_mot', 'init_track_model' +] diff --git a/mmdetection/mmdet/apis/det_inferencer.py b/mmdetection/mmdet/apis/det_inferencer.py new file mode 100644 index 00000000..ce8532eb --- /dev/null +++ b/mmdetection/mmdet/apis/det_inferencer.py @@ -0,0 +1,652 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +import warnings +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, Union + +import mmcv +import mmengine +import numpy as np +import torch.nn as nn +from mmcv.transforms import LoadImageFromFile +from mmengine.dataset import Compose +from mmengine.fileio import (get_file_backend, isdir, join_path, + list_dir_or_file) +from mmengine.infer.infer import BaseInferencer, ModelType +from mmengine.model.utils import revert_sync_batchnorm +from mmengine.registry import init_default_scope +from mmengine.runner.checkpoint import _load_checkpoint_to_model +from mmengine.visualization import Visualizer +from rich.progress import track + +from mmdet.evaluation import INSTANCE_OFFSET +from mmdet.registry import DATASETS +from mmdet.structures import DetDataSample +from mmdet.structures.mask import encode_mask_results, mask2bbox +from mmdet.utils import ConfigType +from ..evaluation import get_classes + +try: + from panopticapi.evaluation import VOID + from panopticapi.utils import id2rgb +except ImportError: + id2rgb = None + VOID = None + +InputType = Union[str, np.ndarray] +InputsType = Union[InputType, Sequence[InputType]] +PredType = List[DetDataSample] +ImgType = Union[np.ndarray, Sequence[np.ndarray]] + +IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', + '.tiff', '.webp') + + +class DetInferencer(BaseInferencer): + """Object Detection Inferencer. + + Args: + model (str, optional): Path to the config file or the model name + defined in metafile. For example, it could be + "rtmdet-s" or 'rtmdet_s_8xb32-300e_coco' or + "configs/rtmdet/rtmdet_s_8xb32-300e_coco.py". + If model is not specified, user must provide the + `weights` saved by MMEngine which contains the config string. + Defaults to None. + weights (str, optional): Path to the checkpoint. If it is not specified + and model is a model name of metafile, the weights will be loaded + from metafile. Defaults to None. + device (str, optional): Device to run inference. If None, the available + device will be automatically used. Defaults to None. + scope (str, optional): The scope of the model. Defaults to mmdet. + palette (str): Color palette used for visualization. The order of + priority is palette -> config -> checkpoint. Defaults to 'none'. + show_progress (bool): Control whether to display the progress + bar during the inference process. Defaults to True. + """ + + preprocess_kwargs: set = set() + forward_kwargs: set = set() + visualize_kwargs: set = { + 'return_vis', + 'show', + 'wait_time', + 'draw_pred', + 'pred_score_thr', + 'img_out_dir', + 'no_save_vis', + } + postprocess_kwargs: set = { + 'print_result', + 'pred_out_dir', + 'return_datasamples', + 'no_save_pred', + } + + def __init__(self, + model: Optional[Union[ModelType, str]] = None, + weights: Optional[str] = None, + device: Optional[str] = None, + scope: Optional[str] = 'mmdet', + palette: str = 'none', + show_progress: bool = True) -> None: + # A global counter tracking the number of images processed, for + # naming of the output images + self.num_visualized_imgs = 0 + self.num_predicted_imgs = 0 + self.palette = palette + init_default_scope(scope) + super().__init__( + model=model, weights=weights, device=device, scope=scope) + self.model = revert_sync_batchnorm(self.model) + self.show_progress = show_progress + + def _load_weights_to_model(self, model: nn.Module, + checkpoint: Optional[dict], + cfg: Optional[ConfigType]) -> None: + """Loading model weights and meta information from cfg and checkpoint. + + Args: + model (nn.Module): Model to load weights and meta information. + checkpoint (dict, optional): The loaded checkpoint. + cfg (Config or ConfigDict, optional): The loaded config. + """ + + if checkpoint is not None: + _load_checkpoint_to_model(model, checkpoint) + checkpoint_meta = checkpoint.get('meta', {}) + # save the dataset_meta in the model for convenience + if 'dataset_meta' in checkpoint_meta: + # mmdet 3.x, all keys should be lowercase + model.dataset_meta = { + k.lower(): v + for k, v in checkpoint_meta['dataset_meta'].items() + } + elif 'CLASSES' in checkpoint_meta: + # < mmdet 3.x + classes = checkpoint_meta['CLASSES'] + model.dataset_meta = {'classes': classes} + else: + warnings.warn( + 'dataset_meta or class names are not saved in the ' + 'checkpoint\'s meta data, use COCO classes by default.') + model.dataset_meta = {'classes': get_classes('coco')} + else: + warnings.warn('Checkpoint is not loaded, and the inference ' + 'result is calculated by the randomly initialized ' + 'model!') + warnings.warn('weights is None, use COCO classes by default.') + model.dataset_meta = {'classes': get_classes('coco')} + + # Priority: args.palette -> config -> checkpoint + if self.palette != 'none': + model.dataset_meta['palette'] = self.palette + else: + test_dataset_cfg = copy.deepcopy(cfg.test_dataloader.dataset) + # lazy init. We only need the metainfo. + test_dataset_cfg['lazy_init'] = True + metainfo = DATASETS.build(test_dataset_cfg).metainfo + cfg_palette = metainfo.get('palette', None) + if cfg_palette is not None: + model.dataset_meta['palette'] = cfg_palette + else: + if 'palette' not in model.dataset_meta: + warnings.warn( + 'palette does not exist, random is used by default. ' + 'You can also set the palette to customize.') + model.dataset_meta['palette'] = 'random' + + def _init_pipeline(self, cfg: ConfigType) -> Compose: + """Initialize the test pipeline.""" + pipeline_cfg = cfg.test_dataloader.dataset.pipeline + + # For inference, the key of ``img_id`` is not used. + if 'meta_keys' in pipeline_cfg[-1]: + pipeline_cfg[-1]['meta_keys'] = tuple( + meta_key for meta_key in pipeline_cfg[-1]['meta_keys'] + if meta_key != 'img_id') + + load_img_idx = self._get_transform_idx( + pipeline_cfg, ('LoadImageFromFile', LoadImageFromFile)) + if load_img_idx == -1: + raise ValueError( + 'LoadImageFromFile is not found in the test pipeline') + pipeline_cfg[load_img_idx]['type'] = 'mmdet.InferencerLoader' + return Compose(pipeline_cfg) + + def _get_transform_idx(self, pipeline_cfg: ConfigType, + name: Union[str, Tuple[str, type]]) -> int: + """Returns the index of the transform in a pipeline. + + If the transform is not found, returns -1. + """ + for i, transform in enumerate(pipeline_cfg): + if transform['type'] in name: + return i + return -1 + + def _init_visualizer(self, cfg: ConfigType) -> Optional[Visualizer]: + """Initialize visualizers. + + Args: + cfg (ConfigType): Config containing the visualizer information. + + Returns: + Visualizer or None: Visualizer initialized with config. + """ + visualizer = super()._init_visualizer(cfg) + visualizer.dataset_meta = self.model.dataset_meta + return visualizer + + def _inputs_to_list(self, inputs: InputsType) -> list: + """Preprocess the inputs to a list. + + Preprocess inputs to a list according to its type: + + - list or tuple: return inputs + - str: + - Directory path: return all files in the directory + - other cases: return a list containing the string. The string + could be a path to file, a url or other types of string according + to the task. + + Args: + inputs (InputsType): Inputs for the inferencer. + + Returns: + list: List of input for the :meth:`preprocess`. + """ + if isinstance(inputs, str): + backend = get_file_backend(inputs) + if hasattr(backend, 'isdir') and isdir(inputs): + # Backends like HttpsBackend do not implement `isdir`, so only + # those backends that implement `isdir` could accept the inputs + # as a directory + filename_list = list_dir_or_file( + inputs, list_dir=False, suffix=IMG_EXTENSIONS) + inputs = [ + join_path(inputs, filename) for filename in filename_list + ] + + if not isinstance(inputs, (list, tuple)): + inputs = [inputs] + + return list(inputs) + + def preprocess(self, inputs: InputsType, batch_size: int = 1, **kwargs): + """Process the inputs into a model-feedable format. + + Customize your preprocess by overriding this method. Preprocess should + return an iterable object, of which each item will be used as the + input of ``model.test_step``. + + ``BaseInferencer.preprocess`` will return an iterable chunked data, + which will be used in __call__ like this: + + .. code-block:: python + + def __call__(self, inputs, batch_size=1, **kwargs): + chunked_data = self.preprocess(inputs, batch_size, **kwargs) + for batch in chunked_data: + preds = self.forward(batch, **kwargs) + + Args: + inputs (InputsType): Inputs given by user. + batch_size (int): batch size. Defaults to 1. + + Yields: + Any: Data processed by the ``pipeline`` and ``collate_fn``. + """ + chunked_data = self._get_chunk_data(inputs, batch_size) + yield from map(self.collate_fn, chunked_data) + + def _get_chunk_data(self, inputs: Iterable, chunk_size: int): + """Get batch data from inputs. + + Args: + inputs (Iterable): An iterable dataset. + chunk_size (int): Equivalent to batch size. + + Yields: + list: batch data. + """ + inputs_iter = iter(inputs) + while True: + try: + chunk_data = [] + for _ in range(chunk_size): + inputs_ = next(inputs_iter) + if isinstance(inputs_, dict): + if 'img' in inputs_: + ori_inputs_ = inputs_['img'] + else: + ori_inputs_ = inputs_['img_path'] + chunk_data.append( + (ori_inputs_, + self.pipeline(copy.deepcopy(inputs_)))) + else: + chunk_data.append((inputs_, self.pipeline(inputs_))) + yield chunk_data + except StopIteration: + if chunk_data: + yield chunk_data + break + + # TODO: Video and Webcam are currently not supported and + # may consume too much memory if your input folder has a lot of images. + # We will be optimized later. + def __call__( + self, + inputs: InputsType, + batch_size: int = 1, + return_vis: bool = False, + show: bool = False, + wait_time: int = 0, + no_save_vis: bool = False, + draw_pred: bool = True, + pred_score_thr: float = 0.3, + return_datasamples: bool = False, + print_result: bool = False, + no_save_pred: bool = True, + out_dir: str = '', + # by open image task + texts: Optional[Union[str, list]] = None, + # by open panoptic task + stuff_texts: Optional[Union[str, list]] = None, + # by GLIP and Grounding DINO + custom_entities: bool = False, + # by Grounding DINO + tokens_positive: Optional[Union[int, list]] = None, + **kwargs) -> dict: + """Call the inferencer. + + Args: + inputs (InputsType): Inputs for the inferencer. + batch_size (int): Inference batch size. Defaults to 1. + show (bool): Whether to display the visualization results in a + popup window. Defaults to False. + wait_time (float): The interval of show (s). Defaults to 0. + no_save_vis (bool): Whether to force not to save prediction + vis results. Defaults to False. + draw_pred (bool): Whether to draw predicted bounding boxes. + Defaults to True. + pred_score_thr (float): Minimum score of bboxes to draw. + Defaults to 0.3. + return_datasamples (bool): Whether to return results as + :obj:`DetDataSample`. Defaults to False. + print_result (bool): Whether to print the inference result w/o + visualization to the console. Defaults to False. + no_save_pred (bool): Whether to force not to save prediction + results. Defaults to True. + out_dir: Dir to save the inference results or + visualization. If left as empty, no file will be saved. + Defaults to ''. + texts (str | list[str]): Text prompts. Defaults to None. + stuff_texts (str | list[str]): Stuff text prompts of open + panoptic task. Defaults to None. + custom_entities (bool): Whether to use custom entities. + Defaults to False. Only used in GLIP and Grounding DINO. + **kwargs: Other keyword arguments passed to :meth:`preprocess`, + :meth:`forward`, :meth:`visualize` and :meth:`postprocess`. + Each key in kwargs should be in the corresponding set of + ``preprocess_kwargs``, ``forward_kwargs``, ``visualize_kwargs`` + and ``postprocess_kwargs``. + + Returns: + dict: Inference and visualization results. + """ + ( + preprocess_kwargs, + forward_kwargs, + visualize_kwargs, + postprocess_kwargs, + ) = self._dispatch_kwargs(**kwargs) + + ori_inputs = self._inputs_to_list(inputs) + + if texts is not None and isinstance(texts, str): + texts = [texts] * len(ori_inputs) + if stuff_texts is not None and isinstance(stuff_texts, str): + stuff_texts = [stuff_texts] * len(ori_inputs) + + # Currently only supports bs=1 + tokens_positive = [tokens_positive] * len(ori_inputs) + + if texts is not None: + assert len(texts) == len(ori_inputs) + for i in range(len(texts)): + if isinstance(ori_inputs[i], str): + ori_inputs[i] = { + 'text': texts[i], + 'img_path': ori_inputs[i], + 'custom_entities': custom_entities, + 'tokens_positive': tokens_positive[i] + } + else: + ori_inputs[i] = { + 'text': texts[i], + 'img': ori_inputs[i], + 'custom_entities': custom_entities, + 'tokens_positive': tokens_positive[i] + } + if stuff_texts is not None: + assert len(stuff_texts) == len(ori_inputs) + for i in range(len(stuff_texts)): + ori_inputs[i]['stuff_text'] = stuff_texts[i] + + inputs = self.preprocess( + ori_inputs, batch_size=batch_size, **preprocess_kwargs) + + results_dict = {'predictions': [], 'visualization': []} + for ori_imgs, data in (track(inputs, description='Inference') + if self.show_progress else inputs): + preds = self.forward(data, **forward_kwargs) + visualization = self.visualize( + ori_imgs, + preds, + return_vis=return_vis, + show=show, + wait_time=wait_time, + draw_pred=draw_pred, + pred_score_thr=pred_score_thr, + no_save_vis=no_save_vis, + img_out_dir=out_dir, + **visualize_kwargs) + results = self.postprocess( + preds, + visualization, + return_datasamples=return_datasamples, + print_result=print_result, + no_save_pred=no_save_pred, + pred_out_dir=out_dir, + **postprocess_kwargs) + results_dict['predictions'].extend(results['predictions']) + if results['visualization'] is not None: + results_dict['visualization'].extend(results['visualization']) + return results_dict + + def visualize(self, + inputs: InputsType, + preds: PredType, + return_vis: bool = False, + show: bool = False, + wait_time: int = 0, + draw_pred: bool = True, + pred_score_thr: float = 0.3, + no_save_vis: bool = False, + img_out_dir: str = '', + **kwargs) -> Union[List[np.ndarray], None]: + """Visualize predictions. + + Args: + inputs (List[Union[str, np.ndarray]]): Inputs for the inferencer. + preds (List[:obj:`DetDataSample`]): Predictions of the model. + return_vis (bool): Whether to return the visualization result. + Defaults to False. + show (bool): Whether to display the image in a popup window. + Defaults to False. + wait_time (float): The interval of show (s). Defaults to 0. + draw_pred (bool): Whether to draw predicted bounding boxes. + Defaults to True. + pred_score_thr (float): Minimum score of bboxes to draw. + Defaults to 0.3. + no_save_vis (bool): Whether to force not to save prediction + vis results. Defaults to False. + img_out_dir (str): Output directory of visualization results. + If left as empty, no file will be saved. Defaults to ''. + + Returns: + List[np.ndarray] or None: Returns visualization results only if + applicable. + """ + if no_save_vis is True: + img_out_dir = '' + + if not show and img_out_dir == '' and not return_vis: + return None + + if self.visualizer is None: + raise ValueError('Visualization needs the "visualizer" term' + 'defined in the config, but got None.') + + results = [] + + for single_input, pred in zip(inputs, preds): + if isinstance(single_input, str): + img_bytes = mmengine.fileio.get(single_input) + img = mmcv.imfrombytes(img_bytes) + img = img[:, :, ::-1] + img_name = osp.basename(single_input) + elif isinstance(single_input, np.ndarray): + img = single_input.copy() + img_num = str(self.num_visualized_imgs).zfill(8) + img_name = f'{img_num}.jpg' + else: + raise ValueError('Unsupported input type: ' + f'{type(single_input)}') + + out_file = osp.join(img_out_dir, 'vis', + img_name) if img_out_dir != '' else None + + self.visualizer.add_datasample( + img_name, + img, + pred, + show=show, + wait_time=wait_time, + draw_gt=False, + draw_pred=draw_pred, + pred_score_thr=pred_score_thr, + out_file=out_file, + ) + results.append(self.visualizer.get_image()) + self.num_visualized_imgs += 1 + + return results + + def postprocess( + self, + preds: PredType, + visualization: Optional[List[np.ndarray]] = None, + return_datasamples: bool = False, + print_result: bool = False, + no_save_pred: bool = False, + pred_out_dir: str = '', + **kwargs, + ) -> Dict: + """Process the predictions and visualization results from ``forward`` + and ``visualize``. + + This method should be responsible for the following tasks: + + 1. Convert datasamples into a json-serializable dict if needed. + 2. Pack the predictions and visualization results and return them. + 3. Dump or log the predictions. + + Args: + preds (List[:obj:`DetDataSample`]): Predictions of the model. + visualization (Optional[np.ndarray]): Visualized predictions. + return_datasamples (bool): Whether to use Datasample to store + inference results. If False, dict will be used. + print_result (bool): Whether to print the inference result w/o + visualization to the console. Defaults to False. + no_save_pred (bool): Whether to force not to save prediction + results. Defaults to False. + pred_out_dir: Dir to save the inference results w/o + visualization. If left as empty, no file will be saved. + Defaults to ''. + + Returns: + dict: Inference and visualization results with key ``predictions`` + and ``visualization``. + + - ``visualization`` (Any): Returned by :meth:`visualize`. + - ``predictions`` (dict or DataSample): Returned by + :meth:`forward` and processed in :meth:`postprocess`. + If ``return_datasamples=False``, it usually should be a + json-serializable dict containing only basic data elements such + as strings and numbers. + """ + if no_save_pred is True: + pred_out_dir = '' + + result_dict = {} + results = preds + if not return_datasamples: + results = [] + for pred in preds: + result = self.pred2dict(pred, pred_out_dir) + results.append(result) + elif pred_out_dir != '': + warnings.warn('Currently does not support saving datasample ' + 'when return_datasamples is set to True. ' + 'Prediction results are not saved!') + # Add img to the results after printing and dumping + result_dict['predictions'] = results + if print_result: + print(result_dict) + result_dict['visualization'] = visualization + return result_dict + + # TODO: The data format and fields saved in json need further discussion. + # Maybe should include model name, timestamp, filename, image info etc. + def pred2dict(self, + data_sample: DetDataSample, + pred_out_dir: str = '') -> Dict: + """Extract elements necessary to represent a prediction into a + dictionary. + + It's better to contain only basic data elements such as strings and + numbers in order to guarantee it's json-serializable. + + Args: + data_sample (:obj:`DetDataSample`): Predictions of the model. + pred_out_dir: Dir to save the inference results w/o + visualization. If left as empty, no file will be saved. + Defaults to ''. + + Returns: + dict: Prediction results. + """ + is_save_pred = True + if pred_out_dir == '': + is_save_pred = False + + if is_save_pred and 'img_path' in data_sample: + img_path = osp.basename(data_sample.img_path) + img_path = osp.splitext(img_path)[0] + out_img_path = osp.join(pred_out_dir, 'preds', + img_path + '_panoptic_seg.png') + out_json_path = osp.join(pred_out_dir, 'preds', img_path + '.json') + elif is_save_pred: + out_img_path = osp.join( + pred_out_dir, 'preds', + f'{self.num_predicted_imgs}_panoptic_seg.png') + out_json_path = osp.join(pred_out_dir, 'preds', + f'{self.num_predicted_imgs}.json') + self.num_predicted_imgs += 1 + + result = {} + if 'pred_instances' in data_sample: + masks = data_sample.pred_instances.get('masks') + pred_instances = data_sample.pred_instances.numpy() + result = { + 'labels': pred_instances.labels.tolist(), + 'scores': pred_instances.scores.tolist() + } + if 'bboxes' in pred_instances: + result['bboxes'] = pred_instances.bboxes.tolist() + if masks is not None: + if 'bboxes' not in pred_instances or pred_instances.bboxes.sum( + ) == 0: + # Fake bbox, such as the SOLO. + bboxes = mask2bbox(masks.cpu()).numpy().tolist() + result['bboxes'] = bboxes + encode_masks = encode_mask_results(pred_instances.masks) + for encode_mask in encode_masks: + if isinstance(encode_mask['counts'], bytes): + encode_mask['counts'] = encode_mask['counts'].decode() + result['masks'] = encode_masks + + if 'pred_panoptic_seg' in data_sample: + if VOID is None: + raise RuntimeError( + 'panopticapi is not installed, please install it by: ' + 'pip install git+https://github.com/cocodataset/' + 'panopticapi.git.') + + pan = data_sample.pred_panoptic_seg.sem_seg.cpu().numpy()[0] + pan[pan % INSTANCE_OFFSET == len( + self.model.dataset_meta['classes'])] = VOID + pan = id2rgb(pan).astype(np.uint8) + + if is_save_pred: + mmcv.imwrite(pan[:, :, ::-1], out_img_path) + result['panoptic_seg_path'] = out_img_path + else: + result['panoptic_seg'] = pan + + if is_save_pred: + mmengine.dump(result, out_json_path) + + return result diff --git a/mmdetection/mmdet/apis/inference.py b/mmdetection/mmdet/apis/inference.py new file mode 100644 index 00000000..7e6f914e --- /dev/null +++ b/mmdetection/mmdet/apis/inference.py @@ -0,0 +1,372 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import warnings +from pathlib import Path +from typing import Optional, Sequence, Union + +import numpy as np +import torch +import torch.nn as nn +from mmcv.ops import RoIPool +from mmcv.transforms import Compose +from mmengine.config import Config +from mmengine.dataset import default_collate +from mmengine.model.utils import revert_sync_batchnorm +from mmengine.registry import init_default_scope +from mmengine.runner import load_checkpoint + +from mmdet.registry import DATASETS +from mmdet.utils import ConfigType +from ..evaluation import get_classes +from ..registry import MODELS +from ..structures import DetDataSample, SampleList +from ..utils import get_test_pipeline_cfg + + +def init_detector( + config: Union[str, Path, Config], + checkpoint: Optional[str] = None, + palette: str = 'none', + device: str = 'cuda:0', + cfg_options: Optional[dict] = None, +) -> nn.Module: + """Initialize a detector from config file. + + Args: + config (str, :obj:`Path`, or :obj:`mmengine.Config`): Config file path, + :obj:`Path`, or the config object. + checkpoint (str, optional): Checkpoint path. If left as None, the model + will not load any weights. + palette (str): Color palette used for visualization. If palette + is stored in checkpoint, use checkpoint's palette first, otherwise + use externally passed palette. Currently, supports 'coco', 'voc', + 'citys' and 'random'. Defaults to none. + device (str): The device where the anchors will be put on. + Defaults to cuda:0. + cfg_options (dict, optional): Options to override some settings in + the used config. + + Returns: + nn.Module: The constructed detector. + """ + if isinstance(config, (str, Path)): + config = Config.fromfile(config) + elif not isinstance(config, Config): + raise TypeError('config must be a filename or Config object, ' + f'but got {type(config)}') + if cfg_options is not None: + config.merge_from_dict(cfg_options) + elif 'init_cfg' in config.model.backbone: + config.model.backbone.init_cfg = None + + scope = config.get('default_scope', 'mmdet') + if scope is not None: + init_default_scope(config.get('default_scope', 'mmdet')) + + model = MODELS.build(config.model) + model = revert_sync_batchnorm(model) + if checkpoint is None: + warnings.simplefilter('once') + warnings.warn('checkpoint is None, use COCO classes by default.') + model.dataset_meta = {'classes': get_classes('coco')} + else: + checkpoint = load_checkpoint(model, checkpoint, map_location='cpu') + # Weights converted from elsewhere may not have meta fields. + checkpoint_meta = checkpoint.get('meta', {}) + + # save the dataset_meta in the model for convenience + if 'dataset_meta' in checkpoint_meta: + # mmdet 3.x, all keys should be lowercase + model.dataset_meta = { + k.lower(): v + for k, v in checkpoint_meta['dataset_meta'].items() + } + elif 'CLASSES' in checkpoint_meta: + # < mmdet 3.x + classes = checkpoint_meta['CLASSES'] + model.dataset_meta = {'classes': classes} + else: + warnings.simplefilter('once') + warnings.warn( + 'dataset_meta or class names are not saved in the ' + 'checkpoint\'s meta data, use COCO classes by default.') + model.dataset_meta = {'classes': get_classes('coco')} + + # Priority: args.palette -> config -> checkpoint + if palette != 'none': + model.dataset_meta['palette'] = palette + else: + test_dataset_cfg = copy.deepcopy(config.test_dataloader.dataset) + # lazy init. We only need the metainfo. + test_dataset_cfg['lazy_init'] = True + metainfo = DATASETS.build(test_dataset_cfg).metainfo + cfg_palette = metainfo.get('palette', None) + if cfg_palette is not None: + model.dataset_meta['palette'] = cfg_palette + else: + if 'palette' not in model.dataset_meta: + warnings.warn( + 'palette does not exist, random is used by default. ' + 'You can also set the palette to customize.') + model.dataset_meta['palette'] = 'random' + + model.cfg = config # save the config in the model for convenience + model.to(device) + model.eval() + return model + + +ImagesType = Union[str, np.ndarray, Sequence[str], Sequence[np.ndarray]] + + +def inference_detector( + model: nn.Module, + imgs: ImagesType, + test_pipeline: Optional[Compose] = None, + text_prompt: Optional[str] = None, + custom_entities: bool = False, +) -> Union[DetDataSample, SampleList]: + """Inference image(s) with the detector. + + Args: + model (nn.Module): The loaded detector. + imgs (str, ndarray, Sequence[str/ndarray]): + Either image files or loaded images. + test_pipeline (:obj:`Compose`): Test pipeline. + + Returns: + :obj:`DetDataSample` or list[:obj:`DetDataSample`]: + If imgs is a list or tuple, the same length list type results + will be returned, otherwise return the detection results directly. + """ + + if isinstance(imgs, (list, tuple)): + is_batch = True + else: + imgs = [imgs] + is_batch = False + + cfg = model.cfg + + if test_pipeline is None: + cfg = cfg.copy() + test_pipeline = get_test_pipeline_cfg(cfg) + if isinstance(imgs[0], np.ndarray): + # Calling this method across libraries will result + # in module unregistered error if not prefixed with mmdet. + test_pipeline[0].type = 'mmdet.LoadImageFromNDArray' + + test_pipeline = Compose(test_pipeline) + + if model.data_preprocessor.device.type == 'cpu': + for m in model.modules(): + assert not isinstance( + m, RoIPool + ), 'CPU inference with RoIPool is not supported currently.' + + result_list = [] + for i, img in enumerate(imgs): + # prepare data + if isinstance(img, np.ndarray): + # TODO: remove img_id. + data_ = dict(img=img, img_id=0) + else: + # TODO: remove img_id. + data_ = dict(img_path=img, img_id=0) + + if text_prompt: + data_['text'] = text_prompt + data_['custom_entities'] = custom_entities + + # build the data pipeline + data_ = test_pipeline(data_) + + data_['inputs'] = [data_['inputs']] + data_['data_samples'] = [data_['data_samples']] + + # forward the model + with torch.no_grad(): + results = model.test_step(data_)[0] + + result_list.append(results) + + if not is_batch: + return result_list[0] + else: + return result_list + + +# TODO: Awaiting refactoring +async def async_inference_detector(model, imgs): + """Async inference image(s) with the detector. + + Args: + model (nn.Module): The loaded detector. + img (str | ndarray): Either image files or loaded images. + + Returns: + Awaitable detection results. + """ + if not isinstance(imgs, (list, tuple)): + imgs = [imgs] + + cfg = model.cfg + + if isinstance(imgs[0], np.ndarray): + cfg = cfg.copy() + # set loading pipeline type + cfg.data.test.pipeline[0].type = 'LoadImageFromNDArray' + + # cfg.data.test.pipeline = replace_ImageToTensor(cfg.data.test.pipeline) + test_pipeline = Compose(cfg.data.test.pipeline) + + datas = [] + for img in imgs: + # prepare data + if isinstance(img, np.ndarray): + # directly add img + data = dict(img=img) + else: + # add information into dict + data = dict(img_info=dict(filename=img), img_prefix=None) + # build the data pipeline + data = test_pipeline(data) + datas.append(data) + + for m in model.modules(): + assert not isinstance( + m, + RoIPool), 'CPU inference with RoIPool is not supported currently.' + + # We don't restore `torch.is_grad_enabled()` value during concurrent + # inference since execution can overlap + torch.set_grad_enabled(False) + results = await model.aforward_test(data, rescale=True) + return results + + +def build_test_pipeline(cfg: ConfigType) -> ConfigType: + """Build test_pipeline for mot/vis demo. In mot/vis infer, original + test_pipeline should remove the "LoadImageFromFile" and + "LoadTrackAnnotations". + + Args: + cfg (ConfigDict): The loaded config. + Returns: + ConfigType: new test_pipeline + """ + # remove the "LoadImageFromFile" and "LoadTrackAnnotations" in pipeline + transform_broadcaster = cfg.test_dataloader.dataset.pipeline[0].copy() + for transform in transform_broadcaster['transforms']: + if transform['type'] == 'Resize': + transform_broadcaster['transforms'] = transform + pack_track_inputs = cfg.test_dataloader.dataset.pipeline[-1].copy() + test_pipeline = Compose([transform_broadcaster, pack_track_inputs]) + + return test_pipeline + + +def inference_mot(model: nn.Module, img: np.ndarray, frame_id: int, + video_len: int) -> SampleList: + """Inference image(s) with the mot model. + + Args: + model (nn.Module): The loaded mot model. + img (np.ndarray): Loaded image. + frame_id (int): frame id. + video_len (int): demo video length + Returns: + SampleList: The tracking data samples. + """ + cfg = model.cfg + data = dict( + img=[img.astype(np.float32)], + frame_id=[frame_id], + ori_shape=[img.shape[:2]], + img_id=[frame_id + 1], + ori_video_length=[video_len]) + + test_pipeline = build_test_pipeline(cfg) + data = test_pipeline(data) + + if not next(model.parameters()).is_cuda: + for m in model.modules(): + assert not isinstance( + m, RoIPool + ), 'CPU inference with RoIPool is not supported currently.' + + # forward the model + with torch.no_grad(): + data = default_collate([data]) + result = model.test_step(data)[0] + return result + + +def init_track_model(config: Union[str, Config], + checkpoint: Optional[str] = None, + detector: Optional[str] = None, + reid: Optional[str] = None, + device: str = 'cuda:0', + cfg_options: Optional[dict] = None) -> nn.Module: + """Initialize a model from config file. + + Args: + config (str or :obj:`mmengine.Config`): Config file path or the config + object. + checkpoint (Optional[str], optional): Checkpoint path. Defaults to + None. + detector (Optional[str], optional): Detector Checkpoint path, use in + some tracking algorithms like sort. Defaults to None. + reid (Optional[str], optional): Reid checkpoint path. use in + some tracking algorithms like sort. Defaults to None. + device (str, optional): The device that the model inferences on. + Defaults to `cuda:0`. + cfg_options (Optional[dict], optional): Options to override some + settings in the used config. Defaults to None. + + Returns: + nn.Module: The constructed model. + """ + if isinstance(config, str): + config = Config.fromfile(config) + elif not isinstance(config, Config): + raise TypeError('config must be a filename or Config object, ' + f'but got {type(config)}') + if cfg_options is not None: + config.merge_from_dict(cfg_options) + + model = MODELS.build(config.model) + + if checkpoint is not None: + checkpoint = load_checkpoint(model, checkpoint, map_location='cpu') + # Weights converted from elsewhere may not have meta fields. + checkpoint_meta = checkpoint.get('meta', {}) + # save the dataset_meta in the model for convenience + if 'dataset_meta' in checkpoint_meta: + if 'CLASSES' in checkpoint_meta['dataset_meta']: + value = checkpoint_meta['dataset_meta'].pop('CLASSES') + checkpoint_meta['dataset_meta']['classes'] = value + model.dataset_meta = checkpoint_meta['dataset_meta'] + + if detector is not None: + assert not (checkpoint and detector), \ + 'Error: checkpoint and detector checkpoint cannot both exist' + load_checkpoint(model.detector, detector, map_location='cpu') + + if reid is not None: + assert not (checkpoint and reid), \ + 'Error: checkpoint and reid checkpoint cannot both exist' + load_checkpoint(model.reid, reid, map_location='cpu') + + # Some methods don't load checkpoints or checkpoints don't contain + # 'dataset_meta' + # VIS need dataset_meta, MOT don't need dataset_meta + if not hasattr(model, 'dataset_meta'): + warnings.warn('dataset_meta or class names are missed, ' + 'use None by default.') + model.dataset_meta = {'classes': None} + + model.cfg = config # save the config in the model for convenience + model.to(device) + model.eval() + return model diff --git a/mmdetection/mmdet/configs/_base_/datasets/coco_detection.py b/mmdetection/mmdet/configs/_base_/datasets/coco_detection.py new file mode 100644 index 00000000..45041f6d --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/datasets/coco_detection.py @@ -0,0 +1,104 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms import LoadImageFromFile +from mmengine.dataset.sampler import DefaultSampler + +from mmdet.datasets import AspectRatioBatchSampler, CocoDataset +from mmdet.datasets.transforms import (LoadAnnotations, PackDetInputs, + RandomFlip, Resize) +from mmdet.evaluation import CocoMetric + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type=LoadAnnotations, with_bbox=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type=DefaultSampler, shuffle=True), + batch_sampler=dict(type=AspectRatioBatchSampler), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +# inference on test dataset and +# format the output results for submission. +# test_dataloader = dict( +# batch_size=1, +# num_workers=2, +# persistent_workers=True, +# drop_last=False, +# sampler=dict(type=DefaultSampler, shuffle=False), +# dataset=dict( +# type=dataset_type, +# data_root=data_root, +# ann_file=data_root + 'annotations/image_info_test-dev2017.json', +# data_prefix=dict(img='test2017/'), +# test_mode=True, +# pipeline=test_pipeline)) +# test_evaluator = dict( +# type=CocoMetric, +# metric='bbox', +# format_only=True, +# ann_file=data_root + 'annotations/image_info_test-dev2017.json', +# outfile_prefix='./work_dirs/coco_detection/test') diff --git a/mmdetection/mmdet/configs/_base_/datasets/coco_instance.py b/mmdetection/mmdet/configs/_base_/datasets/coco_instance.py new file mode 100644 index 00000000..b9575432 --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/datasets/coco_instance.py @@ -0,0 +1,106 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms.loading import LoadImageFromFile +from mmengine.dataset.sampler import DefaultSampler + +from mmdet.datasets.coco import CocoDataset +from mmdet.datasets.samplers.batch_sampler import AspectRatioBatchSampler +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadAnnotations +from mmdet.datasets.transforms.transforms import RandomFlip, Resize +from mmdet.evaluation.metrics.coco_metric import CocoMetric + +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type=DefaultSampler, shuffle=True), + batch_sampler=dict(type=AspectRatioBatchSampler), + dataset=dict( + type=CocoDataset, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=CocoDataset, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +# inference on test dataset and +# format the output results for submission. +# test_dataloader = dict( +# batch_size=1, +# num_workers=2, +# persistent_workers=True, +# drop_last=False, +# sampler=dict(type=DefaultSampler, shuffle=False), +# dataset=dict( +# type=CocoDataset, +# data_root=data_root, +# ann_file=data_root + 'annotations/image_info_test-dev2017.json', +# data_prefix=dict(img='test2017/'), +# test_mode=True, +# pipeline=test_pipeline)) +# test_evaluator = dict( +# type=CocoMetric, +# metric=['bbox', 'segm'], +# format_only=True, +# ann_file=data_root + 'annotations/image_info_test-dev2017.json', +# outfile_prefix='./work_dirs/coco_instance/test') diff --git a/mmdetection/mmdet/configs/_base_/datasets/coco_instance_semantic.py b/mmdetection/mmdet/configs/_base_/datasets/coco_instance_semantic.py new file mode 100644 index 00000000..7cf5b2cf --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/datasets/coco_instance_semantic.py @@ -0,0 +1,87 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms.loading import LoadImageFromFile +from mmengine.dataset.sampler import DefaultSampler + +from mmdet.datasets.coco import CocoDataset +from mmdet.datasets.samplers.batch_sampler import AspectRatioBatchSampler +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadAnnotations +from mmdet.datasets.transforms.transforms import RandomFlip, Resize +from mmdet.evaluation.metrics.coco_metric import CocoMetric + +# dataset settings +dataset_type = 'CocoDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True, with_seg=True), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + # If you don't have a gt annotation, delete the pipeline + dict(type=LoadAnnotations, with_bbox=True, with_mask=True, with_seg=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type=DefaultSampler, shuffle=True), + batch_sampler=dict(type=AspectRatioBatchSampler), + dataset=dict( + type=CocoDataset, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/', seg='stuffthingmaps/train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) + +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=CocoDataset, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) + +test_dataloader = val_dataloader + +val_evaluator = dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator diff --git a/mmdetection/mmdet/configs/_base_/datasets/coco_panoptic.py b/mmdetection/mmdet/configs/_base_/datasets/coco_panoptic.py new file mode 100644 index 00000000..29d655ff --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/datasets/coco_panoptic.py @@ -0,0 +1,105 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms.loading import LoadImageFromFile +from mmengine.dataset.sampler import DefaultSampler + +from mmdet.datasets.coco_panoptic import CocoPanopticDataset +from mmdet.datasets.samplers.batch_sampler import AspectRatioBatchSampler +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadPanopticAnnotations +from mmdet.datasets.transforms.transforms import RandomFlip, Resize +from mmdet.evaluation.metrics.coco_panoptic_metric import CocoPanopticMetric + +# dataset settings +dataset_type = 'CocoPanopticDataset' +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadPanopticAnnotations, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=LoadPanopticAnnotations, backend_args=backend_args), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type=DefaultSampler, shuffle=True), + batch_sampler=dict(type=AspectRatioBatchSampler), + dataset=dict( + type=CocoPanopticDataset, + data_root=data_root, + ann_file='annotations/panoptic_train2017.json', + data_prefix=dict( + img='train2017/', seg='annotations/panoptic_train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=CocoPanopticDataset, + data_root=data_root, + ann_file='annotations/panoptic_val2017.json', + data_prefix=dict(img='val2017/', seg='annotations/panoptic_val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type=CocoPanopticMetric, + ann_file=data_root + 'annotations/panoptic_val2017.json', + seg_prefix=data_root + 'annotations/panoptic_val2017/', + backend_args=backend_args) +test_evaluator = val_evaluator + +# inference on test dataset and +# format the output results for submission. +# test_dataloader = dict( +# batch_size=1, +# num_workers=1, +# persistent_workers=True, +# drop_last=False, +# sampler=dict(type=DefaultSampler, shuffle=False), +# dataset=dict( +# type=CocoPanopticDataset, +# data_root=data_root, +# ann_file='annotations/panoptic_image_info_test-dev2017.json', +# data_prefix=dict(img='test2017/'), +# test_mode=True, +# pipeline=test_pipeline)) +# test_evaluator = dict( +# type=CocoPanopticMetric, +# format_only=True, +# ann_file=data_root + 'annotations/panoptic_image_info_test-dev2017.json', +# outfile_prefix='./work_dirs/coco_panoptic/test') diff --git a/mmdetection/mmdet/configs/_base_/datasets/mot_challenge.py b/mmdetection/mmdet/configs/_base_/datasets/mot_challenge.py new file mode 100644 index 00000000..a71520a8 --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/datasets/mot_challenge.py @@ -0,0 +1,101 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms import (LoadImageFromFile, RandomResize, + TransformBroadcaster) + +from mmdet.datasets import MOTChallengeDataset +from mmdet.datasets.samplers import TrackImgSampler +from mmdet.datasets.transforms import (LoadTrackAnnotations, PackTrackInputs, + PhotoMetricDistortion, RandomCrop, + RandomFlip, Resize, + UniformRefFrameSample) +from mmdet.evaluation import MOTChallengeMetric + +# dataset settings +dataset_type = MOTChallengeDataset +data_root = 'data/MOT17/' +img_scale = (1088, 1088) + +backend_args = None +# data pipeline +train_pipeline = [ + dict( + type=UniformRefFrameSample, + num_ref_imgs=1, + frame_range=10, + filter_key_img=True), + dict( + type=TransformBroadcaster, + share_random_params=True, + transforms=[ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadTrackAnnotations), + dict( + type=RandomResize, + scale=img_scale, + ratio_range=(0.8, 1.2), + keep_ratio=True, + clip_object_border=False), + dict(type=PhotoMetricDistortion) + ]), + dict( + type=TransformBroadcaster, + # different cropped positions for different frames + share_random_params=False, + transforms=[ + dict(type=RandomCrop, crop_size=img_scale, bbox_clip_border=False) + ]), + dict( + type=TransformBroadcaster, + share_random_params=True, + transforms=[ + dict(type=RandomFlip, prob=0.5), + ]), + dict(type=PackTrackInputs) +] + +test_pipeline = [ + dict( + type=TransformBroadcaster, + transforms=[ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=img_scale, keep_ratio=True), + dict(type=LoadTrackAnnotations) + ]), + dict(type=PackTrackInputs) +] + +# dataloader +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type=TrackImgSampler), # image-based sampling + dataset=dict( + type=dataset_type, + data_root=data_root, + visibility_thr=-1, + ann_file='annotations/half-train_cocoformat.json', + data_prefix=dict(img_path='train'), + metainfo=dict(classes=('pedestrian', )), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + # Now we support two ways to test, image_based and video_based + # if you want to use video_based sampling, you can use as follows + # sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), + sampler=dict(type=TrackImgSampler), # image-based sampling + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/half-val_cocoformat.json', + data_prefix=dict(img_path='train'), + test_mode=True, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# evaluator +val_evaluator = dict( + type=MOTChallengeMetric, metric=['HOTA', 'CLEAR', 'Identity']) +test_evaluator = val_evaluator diff --git a/mmdetection/mmdet/configs/_base_/default_runtime.py b/mmdetection/mmdet/configs/_base_/default_runtime.py new file mode 100644 index 00000000..ff96dbf2 --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/default_runtime.py @@ -0,0 +1,33 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook, + LoggerHook, ParamSchedulerHook) +from mmengine.runner import LogProcessor +from mmengine.visualization import LocalVisBackend + +from mmdet.engine.hooks import DetVisualizationHook +from mmdet.visualization import DetLocalVisualizer + +default_scope = None + +default_hooks = dict( + timer=dict(type=IterTimerHook), + logger=dict(type=LoggerHook, interval=50), + param_scheduler=dict(type=ParamSchedulerHook), + checkpoint=dict(type=CheckpointHook, interval=1), + sampler_seed=dict(type=DistSamplerSeedHook), + visualization=dict(type=DetVisualizationHook)) + +env_cfg = dict( + cudnn_benchmark=False, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) + +vis_backends = [dict(type=LocalVisBackend)] +visualizer = dict( + type=DetLocalVisualizer, vis_backends=vis_backends, name='visualizer') +log_processor = dict(type=LogProcessor, window_size=50, by_epoch=True) + +log_level = 'INFO' +load_from = None +resume = False diff --git a/mmdetection/mmdet/configs/_base_/models/cascade_mask_rcnn_r50_fpn.py b/mmdetection/mmdet/configs/_base_/models/cascade_mask_rcnn_r50_fpn.py new file mode 100644 index 00000000..b9132ac4 --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/models/cascade_mask_rcnn_r50_fpn.py @@ -0,0 +1,220 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.ops import RoIAlign, nms +from torch.nn import BatchNorm2d + +from mmdet.models.backbones.resnet import ResNet +from mmdet.models.data_preprocessors.data_preprocessor import \ + DetDataPreprocessor +from mmdet.models.dense_heads.rpn_head import RPNHead +from mmdet.models.detectors.cascade_rcnn import CascadeRCNN +from mmdet.models.losses.cross_entropy_loss import CrossEntropyLoss +from mmdet.models.losses.smooth_l1_loss import SmoothL1Loss +from mmdet.models.necks.fpn import FPN +from mmdet.models.roi_heads.bbox_heads.convfc_bbox_head import \ + Shared2FCBBoxHead +from mmdet.models.roi_heads.cascade_roi_head import CascadeRoIHead +from mmdet.models.roi_heads.mask_heads.fcn_mask_head import FCNMaskHead +from mmdet.models.roi_heads.roi_extractors.single_level_roi_extractor import \ + SingleRoIExtractor +from mmdet.models.task_modules.assigners.max_iou_assigner import MaxIoUAssigner +from mmdet.models.task_modules.coders.delta_xywh_bbox_coder import \ + DeltaXYWHBBoxCoder +from mmdet.models.task_modules.prior_generators.anchor_generator import \ + AnchorGenerator +from mmdet.models.task_modules.samplers.random_sampler import RandomSampler + +# model settings +model = dict( + type=CascadeRCNN, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type=ResNet, + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type=FPN, + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type=RPNHead, + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type=AnchorGenerator, + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type=SmoothL1Loss, beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + type=CascadeRoIHead, + num_stages=3, + stage_loss_weights=[1, 0.5, 0.25], + bbox_roi_extractor=dict( + type=SingleRoIExtractor, + roi_layer=dict(type=RoIAlign, output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type=Shared2FCBBoxHead, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type=SmoothL1Loss, beta=1.0, loss_weight=1.0)), + dict( + type=Shared2FCBBoxHead, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type=SmoothL1Loss, beta=1.0, loss_weight=1.0)), + dict( + type=Shared2FCBBoxHead, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type=SmoothL1Loss, beta=1.0, loss_weight=1.0)) + ], + mask_roi_extractor=dict( + type=SingleRoIExtractor, + roi_layer=dict(type=RoIAlign, output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + mask_head=dict( + type=FCNMaskHead, + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=80, + loss_mask=dict( + type=CrossEntropyLoss, use_mask=True, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=[ + dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type=nms, iou_threshold=0.5), + max_per_img=100, + mask_thr_binary=0.5))) diff --git a/mmdetection/mmdet/configs/_base_/models/cascade_rcnn_r50_fpn.py b/mmdetection/mmdet/configs/_base_/models/cascade_rcnn_r50_fpn.py new file mode 100644 index 00000000..8e6654f3 --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/models/cascade_rcnn_r50_fpn.py @@ -0,0 +1,201 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.ops import RoIAlign, nms +from torch.nn import BatchNorm2d + +from mmdet.models.backbones.resnet import ResNet +from mmdet.models.data_preprocessors.data_preprocessor import \ + DetDataPreprocessor +from mmdet.models.dense_heads.rpn_head import RPNHead +from mmdet.models.detectors.cascade_rcnn import CascadeRCNN +from mmdet.models.losses.cross_entropy_loss import CrossEntropyLoss +from mmdet.models.losses.smooth_l1_loss import SmoothL1Loss +from mmdet.models.necks.fpn import FPN +from mmdet.models.roi_heads.bbox_heads.convfc_bbox_head import \ + Shared2FCBBoxHead +from mmdet.models.roi_heads.cascade_roi_head import CascadeRoIHead +from mmdet.models.roi_heads.roi_extractors.single_level_roi_extractor import \ + SingleRoIExtractor +from mmdet.models.task_modules.assigners.max_iou_assigner import MaxIoUAssigner +from mmdet.models.task_modules.coders.delta_xywh_bbox_coder import \ + DeltaXYWHBBoxCoder +from mmdet.models.task_modules.prior_generators.anchor_generator import \ + AnchorGenerator +from mmdet.models.task_modules.samplers.random_sampler import RandomSampler + +# model settings +model = dict( + type=CascadeRCNN, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type=ResNet, + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type=FPN, + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type=RPNHead, + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type=AnchorGenerator, + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type=SmoothL1Loss, beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + type=CascadeRoIHead, + num_stages=3, + stage_loss_weights=[1, 0.5, 0.25], + bbox_roi_extractor=dict( + type=SingleRoIExtractor, + roi_layer=dict(type=RoIAlign, output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=[ + dict( + type=Shared2FCBBoxHead, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type=SmoothL1Loss, beta=1.0, loss_weight=1.0)), + dict( + type=Shared2FCBBoxHead, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type=SmoothL1Loss, beta=1.0, loss_weight=1.0)), + dict( + type=Shared2FCBBoxHead, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type=SmoothL1Loss, beta=1.0, loss_weight=1.0)) + ]), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=[ + dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type=nms, iou_threshold=0.5), + max_per_img=100))) diff --git a/mmdetection/mmdet/configs/_base_/models/faster_rcnn_r50_fpn.py b/mmdetection/mmdet/configs/_base_/models/faster_rcnn_r50_fpn.py new file mode 100644 index 00000000..7e18de22 --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/models/faster_rcnn_r50_fpn.py @@ -0,0 +1,138 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.ops import RoIAlign, nms +from torch.nn import BatchNorm2d + +from mmdet.models.backbones.resnet import ResNet +from mmdet.models.data_preprocessors.data_preprocessor import \ + DetDataPreprocessor +from mmdet.models.dense_heads.rpn_head import RPNHead +from mmdet.models.detectors.faster_rcnn import FasterRCNN +from mmdet.models.losses.cross_entropy_loss import CrossEntropyLoss +from mmdet.models.losses.smooth_l1_loss import L1Loss +from mmdet.models.necks.fpn import FPN +from mmdet.models.roi_heads.bbox_heads.convfc_bbox_head import \ + Shared2FCBBoxHead +from mmdet.models.roi_heads.roi_extractors.single_level_roi_extractor import \ + SingleRoIExtractor +from mmdet.models.roi_heads.standard_roi_head import StandardRoIHead +from mmdet.models.task_modules.assigners.max_iou_assigner import MaxIoUAssigner +from mmdet.models.task_modules.coders.delta_xywh_bbox_coder import \ + DeltaXYWHBBoxCoder +from mmdet.models.task_modules.prior_generators.anchor_generator import \ + AnchorGenerator +from mmdet.models.task_modules.samplers.random_sampler import RandomSampler + +# model settings +model = dict( + type=FasterRCNN, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type=ResNet, + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type=FPN, + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type=RPNHead, + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type=AnchorGenerator, + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type=L1Loss, loss_weight=1.0)), + roi_head=dict( + type=StandardRoIHead, + bbox_roi_extractor=dict( + type=SingleRoIExtractor, + roi_layer=dict(type=RoIAlign, output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=dict( + type=Shared2FCBBoxHead, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type=L1Loss, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=1000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type=nms, iou_threshold=0.5), + max_per_img=100) + # soft-nms is also supported for rcnn testing + # e.g., nms=dict(type='soft_nms', iou_threshold=0.5, min_score=0.05) + )) diff --git a/mmdetection/mmdet/configs/_base_/models/mask_rcnn_r50_caffe_c4.py b/mmdetection/mmdet/configs/_base_/models/mask_rcnn_r50_caffe_c4.py new file mode 100644 index 00000000..30548183 --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/models/mask_rcnn_r50_caffe_c4.py @@ -0,0 +1,158 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.ops import RoIAlign, nms +from mmengine.model.weight_init import PretrainedInit +from torch.nn import BatchNorm2d + +from mmdet.models.backbones.resnet import ResNet +from mmdet.models.data_preprocessors.data_preprocessor import \ + DetDataPreprocessor +from mmdet.models.dense_heads.rpn_head import RPNHead +from mmdet.models.detectors.mask_rcnn import MaskRCNN +from mmdet.models.layers import ResLayer +from mmdet.models.losses.cross_entropy_loss import CrossEntropyLoss +from mmdet.models.losses.smooth_l1_loss import L1Loss +from mmdet.models.roi_heads.bbox_heads.bbox_head import BBoxHead +from mmdet.models.roi_heads.mask_heads.fcn_mask_head import FCNMaskHead +from mmdet.models.roi_heads.roi_extractors.single_level_roi_extractor import \ + SingleRoIExtractor +from mmdet.models.roi_heads.standard_roi_head import StandardRoIHead +from mmdet.models.task_modules.assigners.max_iou_assigner import MaxIoUAssigner +from mmdet.models.task_modules.coders.delta_xywh_bbox_coder import \ + DeltaXYWHBBoxCoder +from mmdet.models.task_modules.prior_generators.anchor_generator import \ + AnchorGenerator +from mmdet.models.task_modules.samplers.random_sampler import RandomSampler + +# model settings +norm_cfg = dict(type=BatchNorm2d, requires_grad=False) +# model settings +model = dict( + type=MaskRCNN, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type=ResNet, + depth=50, + num_stages=3, + strides=(1, 2, 2), + dilations=(1, 1, 1), + out_indices=(2, ), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + norm_eval=True, + style='caffe', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + rpn_head=dict( + type=RPNHead, + in_channels=1024, + feat_channels=1024, + anchor_generator=dict( + type=AnchorGenerator, + scales=[2, 4, 8, 16, 32], + ratios=[0.5, 1.0, 2.0], + strides=[16]), + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type=L1Loss, loss_weight=1.0)), + roi_head=dict( + type=StandardRoIHead, + shared_head=dict( + type=ResLayer, + depth=50, + stage=3, + stride=2, + dilation=1, + style='caffe', + norm_cfg=norm_cfg, + norm_eval=True), + bbox_roi_extractor=dict( + type=SingleRoIExtractor, + roi_layer=dict(type=RoIAlign, output_size=14, sampling_ratio=0), + out_channels=1024, + featmap_strides=[16]), + bbox_head=dict( + type=BBoxHead, + with_avg_pool=True, + roi_feat_size=7, + in_channels=2048, + num_classes=80, + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type=L1Loss, loss_weight=1.0)), + mask_roi_extractor=None, + mask_head=dict( + type=FCNMaskHead, + num_convs=0, + in_channels=2048, + conv_out_channels=256, + num_classes=80, + loss_mask=dict( + type=CrossEntropyLoss, use_mask=True, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=12000, + max_per_img=2000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=14, + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=6000, + max_per_img=1000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type=nms, iou_threshold=0.5), + max_per_img=100, + mask_thr_binary=0.5))) diff --git a/mmdetection/mmdet/configs/_base_/models/mask_rcnn_r50_fpn.py b/mmdetection/mmdet/configs/_base_/models/mask_rcnn_r50_fpn.py new file mode 100644 index 00000000..c8a0b031 --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/models/mask_rcnn_r50_fpn.py @@ -0,0 +1,154 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.ops import RoIAlign, nms +from mmengine.model.weight_init import PretrainedInit +from torch.nn import BatchNorm2d + +from mmdet.models.backbones.resnet import ResNet +from mmdet.models.data_preprocessors.data_preprocessor import \ + DetDataPreprocessor +from mmdet.models.dense_heads.rpn_head import RPNHead +from mmdet.models.detectors.mask_rcnn import MaskRCNN +from mmdet.models.losses.cross_entropy_loss import CrossEntropyLoss +from mmdet.models.losses.smooth_l1_loss import L1Loss +from mmdet.models.necks.fpn import FPN +from mmdet.models.roi_heads.bbox_heads.convfc_bbox_head import \ + Shared2FCBBoxHead +from mmdet.models.roi_heads.mask_heads.fcn_mask_head import FCNMaskHead +from mmdet.models.roi_heads.roi_extractors.single_level_roi_extractor import \ + SingleRoIExtractor +from mmdet.models.roi_heads.standard_roi_head import StandardRoIHead +from mmdet.models.task_modules.assigners.max_iou_assigner import MaxIoUAssigner +from mmdet.models.task_modules.coders.delta_xywh_bbox_coder import \ + DeltaXYWHBBoxCoder +from mmdet.models.task_modules.prior_generators.anchor_generator import \ + AnchorGenerator +from mmdet.models.task_modules.samplers.random_sampler import RandomSampler + +# model settings +model = dict( + type=MaskRCNN, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + pad_size_divisor=32), + backbone=dict( + type=ResNet, + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet50')), + neck=dict( + type=FPN, + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=5), + rpn_head=dict( + type=RPNHead, + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type=AnchorGenerator, + scales=[8], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=True, loss_weight=1.0), + loss_bbox=dict(type=L1Loss, loss_weight=1.0)), + roi_head=dict( + type=StandardRoIHead, + bbox_roi_extractor=dict( + type=SingleRoIExtractor, + roi_layer=dict(type=RoIAlign, output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + bbox_head=dict( + type=Shared2FCBBoxHead, + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=80, + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + loss_cls=dict( + type=CrossEntropyLoss, use_sigmoid=False, loss_weight=1.0), + loss_bbox=dict(type=L1Loss, loss_weight=1.0)), + mask_roi_extractor=dict( + type=SingleRoIExtractor, + roi_layer=dict(type=RoIAlign, output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + mask_head=dict( + type=FCNMaskHead, + num_convs=4, + in_channels=256, + conv_out_channels=256, + num_classes=80, + loss_mask=dict( + type=CrossEntropyLoss, use_mask=True, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=1000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type=RandomSampler, + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False)), + test_cfg=dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type=nms, iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.05, + nms=dict(type=nms, iou_threshold=0.5), + max_per_img=100, + mask_thr_binary=0.5))) diff --git a/mmdetection/mmdet/configs/_base_/models/retinanet_r50_fpn.py b/mmdetection/mmdet/configs/_base_/models/retinanet_r50_fpn.py new file mode 100644 index 00000000..33e5cc4f --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/models/retinanet_r50_fpn.py @@ -0,0 +1,77 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.ops import nms +from torch.nn import BatchNorm2d + +from mmdet.models import (FPN, DetDataPreprocessor, FocalLoss, L1Loss, ResNet, + RetinaHead, RetinaNet) +from mmdet.models.task_modules import (AnchorGenerator, DeltaXYWHBBoxCoder, + MaxIoUAssigner, PseudoSampler) + +# model settings +model = dict( + type=RetinaNet, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type=ResNet, + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type=FPN, + in_channels=[256, 512, 1024, 2048], + out_channels=256, + start_level=1, + add_extra_convs='on_input', + num_outs=5), + bbox_head=dict( + type=RetinaHead, + num_classes=80, + in_channels=256, + stacked_convs=4, + feat_channels=256, + anchor_generator=dict( + type=AnchorGenerator, + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + bbox_coder=dict( + type=DeltaXYWHBBoxCoder, + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type=FocalLoss, + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type=L1Loss, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.5, + neg_iou_thr=0.4, + min_pos_iou=0, + ignore_iof_thr=-1), + sampler=dict( + type=PseudoSampler), # Focal loss should use PseudoSampler + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type=nms, iou_threshold=0.5), + max_per_img=100)) diff --git a/mmdetection/mmdet/configs/_base_/schedules/schedule_1x.py b/mmdetection/mmdet/configs/_base_/schedules/schedule_1x.py new file mode 100644 index 00000000..47d1fa6a --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/schedules/schedule_1x.py @@ -0,0 +1,33 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.optim.sgd import SGD + +# training schedule for 1x +train_cfg = dict(type=EpochBasedTrainLoop, max_epochs=12, val_interval=1) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=12, + by_epoch=True, + milestones=[8, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.02, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/mmdet/configs/_base_/schedules/schedule_2x.py b/mmdetection/mmdet/configs/_base_/schedules/schedule_2x.py new file mode 100644 index 00000000..51ba09a4 --- /dev/null +++ b/mmdetection/mmdet/configs/_base_/schedules/schedule_2x.py @@ -0,0 +1,33 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.optim.sgd import SGD + +# training schedule for 1x +train_cfg = dict(type=EpochBasedTrainLoop, max_epochs=24, val_interval=1) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=24, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.02, momentum=0.9, weight_decay=0.0001)) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/mmdet/configs/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco.py b/mmdetection/mmdet/configs/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..a81c25af --- /dev/null +++ b/mmdetection/mmdet/configs/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.coco_instance import * + from .._base_.default_runtime import * + from .._base_.models.cascade_mask_rcnn_r50_fpn import * + from .._base_.schedules.schedule_1x import * diff --git a/mmdetection/mmdet/configs/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco.py b/mmdetection/mmdet/configs/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..883f09be --- /dev/null +++ b/mmdetection/mmdet/configs/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.coco_detection import * + from .._base_.default_runtime import * + from .._base_.models.cascade_rcnn_r50_fpn import * + from .._base_.schedules.schedule_1x import * diff --git a/mmdetection/mmdet/configs/common/lsj_100e_coco_detection.py b/mmdetection/mmdet/configs/common/lsj_100e_coco_detection.py new file mode 100644 index 00000000..ea2d6bad --- /dev/null +++ b/mmdetection/mmdet/configs/common/lsj_100e_coco_detection.py @@ -0,0 +1,134 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.default_runtime import * + +from mmengine.dataset.sampler import DefaultSampler +from mmengine.optim import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.optim import SGD + +from mmdet.datasets import CocoDataset, RepeatDataset +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations, + LoadImageFromFile) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + RandomResize, Resize) +from mmdet.evaluation import CocoMetric + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' +image_size = (1024, 1024) + +backend_args = None + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type=RandomResize, + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type=RandomCrop, + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type=FilterAnnotations, min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=LoadAnnotations, with_bbox=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +# Use RepeatDataset to speed up training +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type=DefaultSampler, shuffle=True), + dataset=dict( + type=RepeatDataset, + times=4, # simply change this from 2 to 16 for 50e - 400e training. + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +max_epochs = 25 + +train_cfg = dict( + type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=5) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +# optimizer assumes bs=64 +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.1, momentum=0.9, weight_decay=0.00004)) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.067, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] + +# only keep latest 2 checkpoints +default_hooks.update(dict(checkpoint=dict(max_keep_ckpts=2))) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/mmdet/configs/common/lsj_100e_coco_instance.py b/mmdetection/mmdet/configs/common/lsj_100e_coco_instance.py new file mode 100644 index 00000000..90104ee5 --- /dev/null +++ b/mmdetection/mmdet/configs/common/lsj_100e_coco_instance.py @@ -0,0 +1,134 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.default_runtime import * + +from mmengine.dataset.sampler import DefaultSampler +from mmengine.optim import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.optim import SGD + +from mmdet.datasets import CocoDataset, RepeatDataset +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations, + LoadImageFromFile) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + RandomResize, Resize) +from mmdet.evaluation import CocoMetric + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' +image_size = (1024, 1024) + +backend_args = None + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type=RandomResize, + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type=RandomCrop, + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type=FilterAnnotations, min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +# Use RepeatDataset to speed up training +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type=DefaultSampler, shuffle=True), + dataset=dict( + type=RepeatDataset, + times=4, # simply change this from 2 to 16 for 50e - 400e training. + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +max_epochs = 25 + +train_cfg = dict( + type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=5) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +# optimizer assumes bs=64 +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.1, momentum=0.9, weight_decay=0.00004)) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.067, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] + +# only keep latest 2 checkpoints +default_hooks.update(dict(checkpoint=dict(max_keep_ckpts=2))) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (32 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/mmdet/configs/common/lsj_200e_coco_detection.py b/mmdetection/mmdet/configs/common/lsj_200e_coco_detection.py new file mode 100644 index 00000000..5759499e --- /dev/null +++ b/mmdetection/mmdet/configs/common/lsj_200e_coco_detection.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .lsj_100e_coco_detection import * + +# 8x25=200e +train_dataloader.update(dict(dataset=dict(times=8))) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.067, by_epoch=False, begin=0, end=1000), + dict( + type=MultiStepLR, + begin=0, + end=25, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] diff --git a/mmdetection/mmdet/configs/common/lsj_200e_coco_instance.py b/mmdetection/mmdet/configs/common/lsj_200e_coco_instance.py new file mode 100644 index 00000000..77c5cdd4 --- /dev/null +++ b/mmdetection/mmdet/configs/common/lsj_200e_coco_instance.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .lsj_100e_coco_instance import * + +# 8x25=200e +train_dataloader.update(dict(dataset=dict(times=8))) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.067, by_epoch=False, begin=0, end=1000), + dict( + type=MultiStepLR, + begin=0, + end=25, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] diff --git a/mmdetection/mmdet/configs/common/ms_3x_coco.py b/mmdetection/mmdet/configs/common/ms_3x_coco.py new file mode 100644 index 00000000..c32b24d9 --- /dev/null +++ b/mmdetection/mmdet/configs/common/ms_3x_coco.py @@ -0,0 +1,130 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.default_runtime import * + +from mmcv.transforms import RandomResize +from mmengine.dataset import RepeatDataset +from mmengine.dataset.sampler import DefaultSampler +from mmengine.optim import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.optim import SGD + +from mmdet.datasets import AspectRatioBatchSampler, CocoDataset +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (LoadAnnotations, + LoadImageFromFile) +from mmdet.datasets.transforms.transforms import RandomFlip, Resize +from mmdet.evaluation import CocoMetric + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# In mstrain 3x config, img_scale=[(1333, 640), (1333, 800)], +# multiscale_mode='range' +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict(type=RandomResize, scale=[(1333, 640), (1333, 800)], keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=LoadAnnotations, with_bbox=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + pin_memory=True, + sampler=dict(type=DefaultSampler, shuffle=True), + batch_sampler=dict(type=AspectRatioBatchSampler), + dataset=dict( + type=RepeatDataset, + times=3, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader = dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + backend_args=backend_args) +test_evaluator = val_evaluator + +# training schedule for 3x with `RepeatDataset` +train_cfg = dict(type=EpochBasedTrainLoop, max_iters=12, val_interval=1) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=12, + by_epoch=False, + milestones=[9, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.02, momentum=0.9, weight_decay=0.0001)) +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/mmdet/configs/common/ms_3x_coco_instance.py b/mmdetection/mmdet/configs/common/ms_3x_coco_instance.py new file mode 100644 index 00000000..3c78909d --- /dev/null +++ b/mmdetection/mmdet/configs/common/ms_3x_coco_instance.py @@ -0,0 +1,136 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.default_runtime import * + +from mmcv.transforms import RandomChoiceResize +from mmengine.dataset import RepeatDataset +from mmengine.dataset.sampler import DefaultSampler, InfiniteSampler +from mmengine.optim import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +from torch.optim import SGD + +from mmdet.datasets import AspectRatioBatchSampler, CocoDataset +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations, + LoadImageFromFile) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + RandomResize, Resize) +from mmdet.evaluation import CocoMetric + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader.update( + dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type=DefaultSampler, shuffle=True), + batch_sampler=dict(type=AspectRatioBatchSampler), + dataset=dict( + type=RepeatDataset, + times=3, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args)))) +val_dataloader.update( + dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args))) +test_dataloader = val_dataloader + +val_evaluator.update( + dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + backend_args=backend_args)) +test_evaluator = val_evaluator + +# training schedule for 3x with `RepeatDataset` +train_cfg.update(dict(type=EpochBasedTrainLoop, max_epochs=12, val_interval=1)) +val_cfg.update(dict(type=ValLoop)) +test_cfg.update(dict(type=TestLoop)) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=12, + by_epoch=False, + milestones=[9, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper.update( + dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.02, momentum=0.9, weight_decay=0.0001))) +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr.update(dict(enable=False, base_batch_size=16)) diff --git a/mmdetection/mmdet/configs/common/ms_90k_coco.py b/mmdetection/mmdet/configs/common/ms_90k_coco.py new file mode 100644 index 00000000..3abf1d4a --- /dev/null +++ b/mmdetection/mmdet/configs/common/ms_90k_coco.py @@ -0,0 +1,151 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.default_runtime import * + +from mmcv.transforms import RandomChoiceResize +from mmengine.dataset import RepeatDataset +from mmengine.dataset.sampler import DefaultSampler, InfiniteSampler +from mmengine.optim import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +from torch.optim import SGD + +from mmdet.datasets import AspectRatioBatchSampler, CocoDataset +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations, + LoadImageFromFile) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + RandomResize, Resize) +from mmdet.evaluation import CocoMetric + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# Align with Detectron2 +backend = 'pillow' +train_pipeline = [ + dict( + type=LoadImageFromFile, + backend_args=backend_args, + imdecode_backend=backend), + dict(type=LoadAnnotations, with_bbox=True), + dict( + type=RandomChoiceResize, + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True, + backend=backend), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict( + type=LoadImageFromFile, + backend_args=backend_args, + imdecode_backend=backend), + dict(type=Resize, scale=(1333, 800), keep_ratio=True, backend=backend), + dict(type=LoadAnnotations, with_bbox=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader.update( + dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + pin_memory=True, + sampler=dict(type=InfiniteSampler, shuffle=True), + batch_sampler=dict(type=AspectRatioBatchSampler), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader.update( + dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + pin_memory=True, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args))) +test_dataloader = val_dataloader + +val_evaluator.update( + dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + format_only=False, + backend_args=backend_args)) +test_evaluator = val_evaluator + +# training schedule for 90k +max_iter = 90000 +train_cfg.update( + dict(type=IterBasedTrainLoop, max_iters=max_iter, val_interval=10000)) +val_cfg.update(dict(type=ValLoop)) +test_cfg.update(dict(type=TestLoop)) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=1000), + dict( + type=MultiStepLR, + begin=0, + end=max_iter, + by_epoch=False, + milestones=[60000, 80000], + gamma=0.1) +] + +# optimizer +optim_wrapper.update( + dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.02, momentum=0.9, weight_decay=0.0001))) +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr.update(dict(enable=False, base_batch_size=16)) + +default_hooks.update(dict(checkpoint=dict(by_epoch=False, interval=10000))) +log_processor.update(dict(by_epoch=False)) diff --git a/mmdetection/mmdet/configs/common/ms_poly_3x_coco_instance.py b/mmdetection/mmdet/configs/common/ms_poly_3x_coco_instance.py new file mode 100644 index 00000000..53913a05 --- /dev/null +++ b/mmdetection/mmdet/configs/common/ms_poly_3x_coco_instance.py @@ -0,0 +1,138 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.default_runtime import * + +from mmcv.transforms import RandomChoiceResize +from mmengine.dataset import RepeatDataset +from mmengine.dataset.sampler import DefaultSampler, InfiniteSampler +from mmengine.optim import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +from torch.optim import SGD + +from mmdet.datasets import AspectRatioBatchSampler, CocoDataset +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations, + LoadImageFromFile) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + RandomResize, Resize) +from mmdet.evaluation import CocoMetric + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# In mstrain 3x config, img_scale=[(1333, 640), (1333, 800)], +# multiscale_mode='range' +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict( + type='RandomResize', scale=[(1333, 640), (1333, 800)], + keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader.update( + dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + pin_memory=True, + sampler=dict(type=DefaultSampler, shuffle=True), + batch_sampler=dict(type=AspectRatioBatchSampler), + dataset=dict( + type=RepeatDataset, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader.update( + dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + drop_last=False, + pin_memory=True, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args))) +test_dataloader = val_dataloader + +val_evaluator.update( + dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + backend_args=backend_args)) +test_evaluator = val_evaluator + +# training schedule for 3x with `RepeatDataset` +train_cfg.update(dict(type=EpochBasedTrainLoop, max_iters=12, val_interval=1)) +val_cfg.update(dict(type=ValLoop)) +test_cfg.update(dict(type=TestLoop)) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=12, + by_epoch=False, + milestones=[9, 11], + gamma=0.1) +] + +# optimizer +optim_wrapper.update( + dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.02, momentum=0.9, weight_decay=0.0001))) +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr.update(dict(enable=False, base_batch_size=16)) diff --git a/mmdetection/mmdet/configs/common/ms_poly_90k_coco_instance.py b/mmdetection/mmdet/configs/common/ms_poly_90k_coco_instance.py new file mode 100644 index 00000000..52367350 --- /dev/null +++ b/mmdetection/mmdet/configs/common/ms_poly_90k_coco_instance.py @@ -0,0 +1,153 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.default_runtime import * + +from mmcv.transforms import RandomChoiceResize +from mmengine.dataset import RepeatDataset +from mmengine.dataset.sampler import DefaultSampler, InfiniteSampler +from mmengine.optim import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +from torch.optim import SGD + +from mmdet.datasets import AspectRatioBatchSampler, CocoDataset +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations, + LoadImageFromFile) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + RandomResize, Resize) +from mmdet.evaluation import CocoMetric + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# Align with Detectron2 +backend = 'pillow' +train_pipeline = [ + dict( + type=LoadImageFromFile, + backend_args=backend_args, + imdecode_backend=backend), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict( + type=RandomChoiceResize, + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True, + backend=backend), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict( + type=LoadImageFromFile, + backend_args=backend_args, + imdecode_backend=backend), + dict(type=Resize, scale=(1333, 800), keep_ratio=True, backend=backend), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader.update( + dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + pin_memory=True, + sampler=dict(type=InfiniteSampler, shuffle=True), + batch_sampler=dict(type=AspectRatioBatchSampler), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader.update( + dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + pin_memory=True, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args))) +test_dataloader = val_dataloader + +val_evaluator.update( + dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args)) +test_evaluator = val_evaluator + +# training schedule for 90k +max_iter = 90000 +train_cfg.update( + dict(type=IterBasedTrainLoop, max_iters=max_iter, val_interval=10000)) +val_cfg.update(dict(type=ValLoop)) +test_cfg.update(dict(type=TestLoop)) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=1000), + dict( + type=MultiStepLR, + begin=0, + end=max_iter, + by_epoch=False, + milestones=[60000, 80000], + gamma=0.1) +] + +# optimizer +optim_wrapper.update( + dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.02, momentum=0.9, weight_decay=0.0001))) +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr.update(dict(enable=False, base_batch_size=16)) + +default_hooks.update(dict(checkpoint=dict(by_epoch=False, interval=10000))) +log_processor.update(dict(by_epoch=False)) diff --git a/mmdetection/mmdet/configs/common/ssj_270_coco_instance.py b/mmdetection/mmdet/configs/common/ssj_270_coco_instance.py new file mode 100644 index 00000000..ee86fdad --- /dev/null +++ b/mmdetection/mmdet/configs/common/ssj_270_coco_instance.py @@ -0,0 +1,158 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.default_runtime import * + +from mmcv.transforms import RandomChoiceResize +from mmengine.dataset import RepeatDataset +from mmengine.dataset.sampler import DefaultSampler, InfiniteSampler +from mmengine.optim import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +from torch.optim import SGD + +from mmdet.datasets import AspectRatioBatchSampler, CocoDataset +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations, + LoadImageFromFile) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + RandomResize, Resize) +from mmdet.evaluation import CocoMetric + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# Standard Scale Jittering (SSJ) resizes and crops an image +# with a resize range of 0.8 to 1.25 of the original image size. +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type=RandomResize, + scale=image_size, + ratio_range=(0.8, 1.25), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader.update( + dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type=InfiniteSampler), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) +val_dataloader.update( + dict( + batch_size=1, + num_workers=2, + persistent_workers=True, + drop_last=False, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args))) +test_dataloader = val_dataloader + +val_evaluator.update( + dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args)) +test_evaluator = val_evaluator + +val_evaluator = dict( + type=CocoMetric, + ann_file=data_root + 'annotations/instances_val2017.json', + metric=['bbox', 'segm'], + format_only=False, + backend_args=backend_args) +test_evaluator = val_evaluator + +# The model is trained by 270k iterations with batch_size 64, +# which is roughly equivalent to 144 epochs. + +max_iter = 270000 +train_cfg.update( + dict(type=IterBasedTrainLoop, max_iters=max_iter, val_interval=10000)) +val_cfg.update(dict(type=ValLoop)) +test_cfg.update(dict(type=TestLoop)) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=1000), + dict( + type=MultiStepLR, + begin=0, + end=max_iter, + by_epoch=False, + milestones=[243000, 256500, 263250], + gamma=0.1) +] + +# optimizer +optim_wrapper.update( + dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.1, momentum=0.9, weight_decay=0.00004))) +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr.update(dict(base_batch_size=64)) + +default_hooks.update(dict(checkpoint=dict(by_epoch=False, interval=10000))) +log_processor.update(dict(by_epoch=False)) diff --git a/mmdetection/mmdet/configs/common/ssj_scp_270k_coco_instance.py b/mmdetection/mmdet/configs/common/ssj_scp_270k_coco_instance.py new file mode 100644 index 00000000..68bb1f09 --- /dev/null +++ b/mmdetection/mmdet/configs/common/ssj_scp_270k_coco_instance.py @@ -0,0 +1,70 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .ssj_270_coco_instance import * + +from mmdet.datasets import MultiImageMixDataset +from mmdet.datasets.transforms import CopyPaste + +# dataset settings +dataset_type = CocoDataset +data_root = 'data/coco/' +image_size = (1024, 1024) +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +# Standard Scale Jittering (SSJ) resizes and crops an image +# with a resize range of 0.8 to 1.25 of the original image size. +load_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type=RandomResize, + scale=image_size, + ratio_range=(0.8, 1.25), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=image_size), +] +train_pipeline = [ + dict(type=CopyPaste, max_num_pasted=100), + dict(type=PackDetInputs) +] + +train_dataloader.update( + dict( + type=MultiImageMixDataset, + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=load_pipeline, + backend_args=backend_args), + pipeline=train_pipeline)) diff --git a/mmdetection/mmdet/configs/deformable_detr/deformable_detr_r50_16xb2_50e_coco.py b/mmdetection/mmdet/configs/deformable_detr/deformable_detr_r50_16xb2_50e_coco.py new file mode 100644 index 00000000..ee2a4163 --- /dev/null +++ b/mmdetection/mmdet/configs/deformable_detr/deformable_detr_r50_16xb2_50e_coco.py @@ -0,0 +1,186 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.coco_detection import * + from .._base_.default_runtime import * + +from mmcv.transforms import LoadImageFromFile, RandomChoice, RandomChoiceResize +from mmengine.optim.optimizer import OptimWrapper +from mmengine.optim.scheduler import MultiStepLR +from mmengine.runner.loops import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.optim.adamw import AdamW + +from mmdet.datasets.transforms import (LoadAnnotations, PackDetInputs, + RandomCrop, RandomFlip, Resize) +from mmdet.models.backbones import ResNet +from mmdet.models.data_preprocessors import DetDataPreprocessor +from mmdet.models.dense_heads import DeformableDETRHead +from mmdet.models.detectors import DeformableDETR +from mmdet.models.losses import FocalLoss, GIoULoss, L1Loss +from mmdet.models.necks import ChannelMapper +from mmdet.models.task_modules import (BBoxL1Cost, FocalLossCost, + HungarianAssigner, IoUCost) + +model = dict( + type=DeformableDETR, + num_queries=300, + num_feature_levels=4, + with_box_refine=False, + as_two_stage=False, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type=ResNet, + depth=50, + num_stages=4, + out_indices=(1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type=ChannelMapper, + in_channels=[512, 1024, 2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + encoder=dict( # DeformableDetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DeformableDetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=1024, ffn_drop=0.1))), + decoder=dict( # DeformableDetrTransformerDecoder + num_layers=6, + return_intermediate=True, + layer_cfg=dict( # DeformableDetrTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + cross_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, feedforward_channels=1024, ffn_drop=0.1)), + post_norm_cfg=None), + positional_encoding=dict(num_feats=128, normalize=True, offset=-0.5), + bbox_head=dict( + type=DeformableDETRHead, + num_classes=80, + sync_cls_avg_factor=True, + loss_cls=dict( + type=FocalLoss, + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=2.0), + loss_bbox=dict(type=L1Loss, loss_weight=5.0), + loss_iou=dict(type=GIoULoss, loss_weight=2.0)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type=HungarianAssigner, + match_costs=[ + dict(type=FocalLossCost, weight=2.0), + dict(type=BBoxL1Cost, weight=5.0, box_format='xywh'), + dict(type=IoUCost, iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=100)) + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict(type=RandomFlip, prob=0.5), + dict( + type=RandomChoice, + transforms=[ + [ + dict( + type=RandomChoiceResize, + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + resize_type=Resize, + keep_ratio=True) + ], + [ + dict( + type=RandomChoiceResize, + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + resize_type=Resize, + keep_ratio=True), + dict( + type=RandomCrop, + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type=RandomChoiceResize, + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + resize_type=Resize, + keep_ratio=True) + ] + ]), + dict(type=PackDetInputs) +] +train_dataloader.update( + dict( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), pipeline=train_pipeline))) + +# optimizer +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict(type=AdamW, lr=0.0002, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1), + 'sampling_offsets': dict(lr_mult=0.1), + 'reference_points': dict(lr_mult=0.1) + })) + +# learning policy +max_epochs = 50 +train_cfg = dict( + type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +param_scheduler = [ + dict( + type=MultiStepLR, + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[40], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (16 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=32) diff --git a/mmdetection/mmdet/configs/deformable_detr/deformable_detr_refine_r50_16xb2_50e_coco.py b/mmdetection/mmdet/configs/deformable_detr/deformable_detr_refine_r50_16xb2_50e_coco.py new file mode 100644 index 00000000..4f232d61 --- /dev/null +++ b/mmdetection/mmdet/configs/deformable_detr/deformable_detr_refine_r50_16xb2_50e_coco.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .deformable_detr_r50_16xb2_50e_coco import * + +model.update(dict(with_box_refine=True)) diff --git a/mmdetection/mmdet/configs/deformable_detr/deformable_detr_refine_twostage_r50_16xb2_50e_coco.py b/mmdetection/mmdet/configs/deformable_detr/deformable_detr_refine_twostage_r50_16xb2_50e_coco.py new file mode 100644 index 00000000..1fac4d8c --- /dev/null +++ b/mmdetection/mmdet/configs/deformable_detr/deformable_detr_refine_twostage_r50_16xb2_50e_coco.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .deformable_detr_refine_r50_16xb2_50e_coco import * + +model.update(dict(as_two_stage=True)) diff --git a/mmdetection/mmdet/configs/detr/detr_r101_8xb2_500e_coco.py b/mmdetection/mmdet/configs/detr/detr_r101_8xb2_500e_coco.py new file mode 100644 index 00000000..b9614681 --- /dev/null +++ b/mmdetection/mmdet/configs/detr/detr_r101_8xb2_500e_coco.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.model.weight_init import PretrainedInit + +with read_base(): + from .detr_r50_8xb2_500e_coco import * + +model.update( + dict( + backbone=dict( + depth=101, + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet101')))) diff --git a/mmdetection/mmdet/configs/detr/detr_r18_8xb2_500e_coco.py b/mmdetection/mmdet/configs/detr/detr_r18_8xb2_500e_coco.py new file mode 100644 index 00000000..11360af1 --- /dev/null +++ b/mmdetection/mmdet/configs/detr/detr_r18_8xb2_500e_coco.py @@ -0,0 +1,14 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.model.weight_init import PretrainedInit + +with read_base(): + from .detr_r50_8xb2_500e_coco import * + +model.update( + dict( + backbone=dict( + depth=18, + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[512]))) diff --git a/mmdetection/mmdet/configs/detr/detr_r50_8xb2_150e_coco.py b/mmdetection/mmdet/configs/detr/detr_r50_8xb2_150e_coco.py new file mode 100644 index 00000000..c50726c7 --- /dev/null +++ b/mmdetection/mmdet/configs/detr/detr_r50_8xb2_150e_coco.py @@ -0,0 +1,182 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms import RandomChoice, RandomChoiceResize +from mmcv.transforms.loading import LoadImageFromFile +from mmengine.config import read_base +from mmengine.model.weight_init import PretrainedInit +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import MultiStepLR +from mmengine.runner.loops import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.nn.modules.activation import ReLU +from torch.nn.modules.batchnorm import BatchNorm2d +from torch.optim.adamw import AdamW + +from mmdet.datasets.transforms import (LoadAnnotations, PackDetInputs, + RandomCrop, RandomFlip, Resize) +from mmdet.models import (DETR, ChannelMapper, DetDataPreprocessor, DETRHead, + ResNet) +from mmdet.models.losses.cross_entropy_loss import CrossEntropyLoss +from mmdet.models.losses.iou_loss import GIoULoss +from mmdet.models.losses.smooth_l1_loss import L1Loss +from mmdet.models.task_modules import (BBoxL1Cost, ClassificationCost, + HungarianAssigner, IoUCost) + +with read_base(): + from .._base_.datasets.coco_detection import * + from .._base_.default_runtime import * + +model = dict( + type=DETR, + num_queries=100, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type=ResNet, + depth=50, + num_stages=4, + out_indices=(3, ), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet50')), + neck=dict( + type=ChannelMapper, + in_channels=[2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=None, + num_outs=1), + encoder=dict( # DetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.1, + act_cfg=dict(type=ReLU, inplace=True)))), + decoder=dict( # DetrTransformerDecoder + num_layers=6, + layer_cfg=dict( # DetrTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.1, + act_cfg=dict(type=ReLU, inplace=True))), + return_intermediate=True), + positional_encoding=dict(num_feats=128, normalize=True), + bbox_head=dict( + type=DETRHead, + num_classes=80, + embed_dims=256, + loss_cls=dict( + type=CrossEntropyLoss, + bg_cls_weight=0.1, + use_sigmoid=False, + loss_weight=1.0, + class_weight=1.0), + loss_bbox=dict(type=L1Loss, loss_weight=5.0), + loss_iou=dict(type=GIoULoss, loss_weight=2.0)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type=HungarianAssigner, + match_costs=[ + dict(type=ClassificationCost, weight=1.), + dict(type=BBoxL1Cost, weight=5.0, box_format='xywh'), + dict(type=IoUCost, iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=100)) + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict(type=RandomFlip, prob=0.5), + dict( + type=RandomChoice, + transforms=[[ + dict( + type=RandomChoiceResize, + resize_type=Resize, + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type=RandomChoiceResize, + resize_type=Resize, + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True), + dict( + type=RandomCrop, + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type=RandomChoiceResize, + resize_type=Resize, + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True) + ]]), + dict(type=PackDetInputs) +] +train_dataloader.update(dataset=dict(pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict(type=AdamW, lr=0.0001, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={'backbone': dict(lr_mult=0.1, decay_mult=1.0)})) + +# learning policy +max_epochs = 150 +train_cfg = dict( + type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +param_scheduler = [ + dict( + type=MultiStepLR, + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[100], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/mmdet/configs/detr/detr_r50_8xb2_500e_coco.py b/mmdetection/mmdet/configs/detr/detr_r50_8xb2_500e_coco.py new file mode 100644 index 00000000..d7d08177 --- /dev/null +++ b/mmdetection/mmdet/configs/detr/detr_r50_8xb2_500e_coco.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.optim.scheduler.lr_scheduler import MultiStepLR +from mmengine.runner.loops import EpochBasedTrainLoop + +with read_base(): + from .detr_r50_8xb2_150e_coco import * + +# learning policy +max_epochs = 500 +train_cfg.update( + type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=10) + +param_scheduler = [ + dict( + type=MultiStepLR, + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[334], + gamma=0.1) +] + +# only keep latest 2 checkpoints +default_hooks.update(checkpoint=dict(max_keep_ckpts=2)) diff --git a/mmdetection/mmdet/configs/dino/dino_4scale_r50_8xb2_12e_coco.py b/mmdetection/mmdet/configs/dino/dino_4scale_r50_8xb2_12e_coco.py new file mode 100644 index 00000000..ab8e95a9 --- /dev/null +++ b/mmdetection/mmdet/configs/dino/dino_4scale_r50_8xb2_12e_coco.py @@ -0,0 +1,190 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms import RandomChoice, RandomChoiceResize +from mmcv.transforms.loading import LoadImageFromFile +from mmengine.config import read_base +from mmengine.model.weight_init import PretrainedInit +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import MultiStepLR +from mmengine.runner.loops import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.nn.modules.batchnorm import BatchNorm2d +from torch.nn.modules.normalization import GroupNorm +from torch.optim.adamw import AdamW + +from mmdet.datasets.transforms import (LoadAnnotations, PackDetInputs, + RandomCrop, RandomFlip, Resize) +from mmdet.models import (DINO, ChannelMapper, DetDataPreprocessor, DINOHead, + ResNet) +from mmdet.models.losses.focal_loss import FocalLoss +from mmdet.models.losses.iou_loss import GIoULoss +from mmdet.models.losses.smooth_l1_loss import L1Loss +from mmdet.models.task_modules import (BBoxL1Cost, FocalLossCost, + HungarianAssigner, IoUCost) + +with read_base(): + from .._base_.datasets.coco_detection import * + from .._base_.default_runtime import * + +model = dict( + type=DINO, + num_queries=900, # num_matching_queries + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type=ResNet, + depth=50, + num_stages=4, + out_indices=(1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet50')), + neck=dict( + type=ChannelMapper, + in_channels=[512, 1024, 2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type=GroupNorm, num_groups=32), + num_outs=4), + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0))), # 0.1 for DeformDETR + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_heads=8, + dropout=0.0), # 0.1 for DeformDETR + cross_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0)), # 0.1 for DeformDETR + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, + normalize=True, + offset=0.0, # -0.5 for DeformDETR + temperature=20), # 10000 for DeformDETR + bbox_head=dict( + type=DINOHead, + num_classes=80, + sync_cls_avg_factor=True, + loss_cls=dict( + type=FocalLoss, + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), # 2.0 in DeformDETR + loss_bbox=dict(type=L1Loss, loss_weight=5.0), + loss_iou=dict(type=GIoULoss, loss_weight=2.0)), + dn_cfg=dict( # TODO: Move to model.train_cfg ? + label_noise_scale=0.5, + box_noise_scale=1.0, # 0.4 for DN-DETR + group_cfg=dict(dynamic=True, num_groups=None, + num_dn_queries=100)), # TODO: half num_dn_queries + # training and testing settings + train_cfg=dict( + assigner=dict( + type=HungarianAssigner, + match_costs=[ + dict(type=FocalLossCost, weight=2.0), + dict(type=BBoxL1Cost, weight=5.0, box_format='xywh'), + dict(type=IoUCost, iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) # 100 for DeformDETR + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict(type=RandomFlip, prob=0.5), + dict( + type=RandomChoice, + transforms=[ + [ + dict( + type=RandomChoiceResize, + resize_type=Resize, + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type=RandomChoiceResize, + resize_type=Resize, + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type=RandomCrop, + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type=RandomChoiceResize, + resize_type=Resize, + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type=PackDetInputs) +] +train_dataloader.update( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict( + type=AdamW, + lr=0.0001, # 0.0002 for DeformDETR + weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.1)}) +) # custom_keys contains sampling_offsets and reference_points in DeformDETR # noqa + +# learning policy +max_epochs = 12 +train_cfg = dict( + type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=1) + +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +param_scheduler = [ + dict( + type=MultiStepLR, + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/mmdet/configs/dino/dino_4scale_r50_8xb2_24e_coco.py b/mmdetection/mmdet/configs/dino/dino_4scale_r50_8xb2_24e_coco.py new file mode 100644 index 00000000..c10cc218 --- /dev/null +++ b/mmdetection/mmdet/configs/dino/dino_4scale_r50_8xb2_24e_coco.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.runner.loops import EpochBasedTrainLoop + +with read_base(): + from .dino_4scale_r50_8xb2_12e_coco import * + +max_epochs = 24 +train_cfg.update( + dict(type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=1)) + +param_scheduler[0].update(dict(milestones=[20])) diff --git a/mmdetection/mmdet/configs/dino/dino_4scale_r50_8xb2_36e_coco.py b/mmdetection/mmdet/configs/dino/dino_4scale_r50_8xb2_36e_coco.py new file mode 100644 index 00000000..37797443 --- /dev/null +++ b/mmdetection/mmdet/configs/dino/dino_4scale_r50_8xb2_36e_coco.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.runner.loops import EpochBasedTrainLoop + +with read_base(): + from .dino_4scale_r50_8xb2_12e_coco import * + +max_epochs = 36 +train_cfg.update( + dict(type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=1)) + +param_scheduler[0].update(dict(milestones=[30])) diff --git a/mmdetection/mmdet/configs/dino/dino_4scale_r50_improved_8xb2_12e_coco.py b/mmdetection/mmdet/configs/dino/dino_4scale_r50_improved_8xb2_12e_coco.py new file mode 100644 index 00000000..43c07201 --- /dev/null +++ b/mmdetection/mmdet/configs/dino/dino_4scale_r50_improved_8xb2_12e_coco.py @@ -0,0 +1,24 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base + +with read_base(): + from .dino_4scale_r50_8xb2_12e_coco import * + +# from deformable detr hyper +model.update( + dict( + backbone=dict(frozen_stages=-1), + bbox_head=dict(loss_cls=dict(loss_weight=2.0)), + positional_encoding=dict(offset=-0.5, temperature=10000), + dn_cfg=dict(group_cfg=dict(num_dn_queries=300)))) + +# optimizer +optim_wrapper.update( + dict( + optimizer=dict(lr=0.0002), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1), + 'sampling_offsets': dict(lr_mult=0.1), + 'reference_points': dict(lr_mult=0.1) + }))) diff --git a/mmdetection/mmdet/configs/dino/dino_5scale_swin_l_8xb2_12e_coco.py b/mmdetection/mmdet/configs/dino/dino_5scale_swin_l_8xb2_12e_coco.py new file mode 100644 index 00000000..25aac018 --- /dev/null +++ b/mmdetection/mmdet/configs/dino/dino_5scale_swin_l_8xb2_12e_coco.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.model.weight_init import PretrainedInit + +from mmdet.models import SwinTransformer + +with read_base(): + from .dino_4scale_r50_8xb2_12e_coco import * + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa +num_levels = 5 +model.merge( + dict( + num_feature_levels=num_levels, + backbone=dict( + _delete_=True, + type=SwinTransformer, + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + patch_norm=True, + out_indices=(0, 1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=True, + convert_weights=True, + init_cfg=dict(type=PretrainedInit, checkpoint=pretrained)), + neck=dict(in_channels=[192, 384, 768, 1536], num_outs=num_levels), + encoder=dict( + layer_cfg=dict(self_attn_cfg=dict(num_levels=num_levels))), + decoder=dict( + layer_cfg=dict(cross_attn_cfg=dict(num_levels=num_levels))))) diff --git a/mmdetection/mmdet/configs/dino/dino_5scale_swin_l_8xb2_36e_coco.py b/mmdetection/mmdet/configs/dino/dino_5scale_swin_l_8xb2_36e_coco.py new file mode 100644 index 00000000..494acf59 --- /dev/null +++ b/mmdetection/mmdet/configs/dino/dino_5scale_swin_l_8xb2_36e_coco.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.runner.loops import EpochBasedTrainLoop + +with read_base(): + from .dino_5scale_swin_l_8xb2_12e_coco import * + +max_epochs = 36 +train_cfg.update( + dict(type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=1)) + +param_scheduler[0].update(dict(milestones=[27, 33])) diff --git a/mmdetection/mmdet/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py b/mmdetection/mmdet/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..f0a6d5a2 --- /dev/null +++ b/mmdetection/mmdet/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.coco_detection import * + from .._base_.default_runtime import * + from .._base_.models.faster_rcnn_r50_fpn import * + from .._base_.schedules.schedule_1x import * diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_caffe_fpn_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_caffe_fpn_1x_coco.py new file mode 100644 index 00000000..2780f4af --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_caffe_fpn_1x_coco.py @@ -0,0 +1,19 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_fpn_poly_1x_coco import * + +from mmengine.model.weight_init import PretrainedInit + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_caffe_fpn_ms_poly_3x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_caffe_fpn_ms_poly_3x_coco.py new file mode 100644 index 00000000..8a1badfc --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_caffe_fpn_ms_poly_3x_coco.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from ..common.ms_poly_3x_coco_instance import * + from .._base_.models.mask_rcnn_r50_fpn import * + +from mmengine.model.weight_init import PretrainedInit + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + depth=101, + norm_cfg=dict(requires_grad=False), + norm_eval=True, + style='caffe', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnet101_caffe'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..6770cec8 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_1x_coco.py @@ -0,0 +1,18 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.models.mask_rcnn_r50_fpn import * + +from mmengine.model.weight_init import PretrainedInit + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_2x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_2x_coco.py new file mode 100644 index 00000000..fd2aafb9 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_2x_coco.py @@ -0,0 +1,18 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_fpn_2x_coco import * + +from mmengine.model.weight_init import PretrainedInit + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_8xb8_amp_lsj_200e_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_8xb8_amp_lsj_200e_coco.py new file mode 100644 index 00000000..665808d5 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_8xb8_amp_lsj_200e_coco.py @@ -0,0 +1,18 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r18_fpn_8xb8_amp_lsj_200e_coco import * + +from mmengine.model.weight_init import PretrainedInit + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_ms_poly_3x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_ms_poly_3x_coco.py new file mode 100644 index 00000000..14688795 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r101_fpn_ms_poly_3x_coco.py @@ -0,0 +1,19 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from ..common.ms_poly_3x_coco_instance import * + from .._base_.models.mask_rcnn_r50_fpn import * + +from mmengine.model.weight_init import PretrainedInit + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet101'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r18_fpn_8xb8_amp_lsj_200e_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r18_fpn_8xb8_amp_lsj_200e_coco.py new file mode 100644 index 00000000..67bd86fa --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r18_fpn_8xb8_amp_lsj_200e_coco.py @@ -0,0 +1,19 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_fpn_8xb8_amp_lsj_200e_coco import * + +from mmengine.model.weight_init import PretrainedInit + +model = dict( + backbone=dict( + depth=18, + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet18')), + neck=dict(in_channels=[64, 128, 256, 512])) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_c4_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_c4_1x_coco.py new file mode 100644 index 00000000..494e6ba5 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_c4_1x_coco.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.coco_instance import * + from .._base_.default_runtime import * + from .._base_.models.mask_rcnn_r50_caffe_c4 import * + from .._base_.schedules.schedule_1x import * diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_1x_coco.py new file mode 100644 index 00000000..6481fcfd --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_1x_coco.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_fpn_1x_coco import * + +from mmengine.model.weight_init import PretrainedInit + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + style='caffe', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_1x_coco.py new file mode 100644 index 00000000..5952ed58 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_1x_coco.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_fpn_1x_coco import * + +from mmcv.transforms import RandomChoiceResize +from mmengine.model.weight_init import PretrainedInit + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + style='caffe', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args={{_base_.backend_args}}), + dict(type=LoadAnnotations, with_bbox=True, with_mask=True), + dict( + type=RandomChoiceResize, + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs), +] + +train_dataloader.update(dict(dataset=dict(pipeline=train_pipeline))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_poly_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_poly_1x_coco.py new file mode 100644 index 00000000..d62b9ebe --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_poly_1x_coco.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_fpn_1x_coco import * + +from mmcv.transforms import RandomChoiceResize +from mmengine.model.weight_init import PretrainedInit + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + style='caffe', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args={{_base_.backend_args}}), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict( + type=RandomChoiceResize, + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] + +train_dataloader.update(dict(dataset=dict(pipeline=train_pipeline))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_poly_2x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_poly_2x_coco.py new file mode 100644 index 00000000..fa41b7e0 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_poly_2x_coco.py @@ -0,0 +1,23 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_caffe_fpn_ms_poly_1x_coco import * + +train_cfg = dict(max_epochs=24) +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=24, + by_epoch=True, + milestones=[16, 22], + gamma=0.1) +] diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_poly_3x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_poly_3x_coco.py new file mode 100644 index 00000000..c5f9b977 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_ms_poly_3x_coco.py @@ -0,0 +1,23 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_caffe_fpn_ms_poly_1x_coco import * + +train_cfg = dict(max_epochs=36) +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=24, + by_epoch=True, + milestones=[28, 34], + gamma=0.1) +] diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_poly_1x_coco_v1.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_poly_1x_coco_v1.py new file mode 100644 index 00000000..28ba7c77 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_poly_1x_coco_v1.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_fpn_1x_coco import * + +from mmengine.model.weight_init import PretrainedInit + +from mmdet.models.losses import SmoothL1Loss + +model = dict( + # use caffe img_norm + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False), + backbone=dict( + norm_cfg=dict(requires_grad=False), + style='caffe', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnet50_caffe')), + rpn_head=dict( + loss_bbox=dict(type=SmoothL1Loss, beta=1.0 / 9.0, loss_weight=1.0)), + roi_head=dict( + bbox_roi_extractor=dict( + roi_layer=dict( + type=RoIAlign, output_size=7, sampling_ratio=2, + aligned=False)), + bbox_head=dict( + loss_bbox=dict(type=SmoothL1Loss, beta=1.0, loss_weight=1.0)), + mask_roi_extractor=dict( + roi_layer=dict( + type=RoIAlign, output_size=14, sampling_ratio=2, + aligned=False)))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..8145d08f --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.coco_instance import * + from .._base_.default_runtime import * + from .._base_.models.mask_rcnn_r50_fpn import * + from .._base_.schedules.schedule_1x import * diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_1x_wandb_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_1x_wandb_coco.py new file mode 100644 index 00000000..d2c08765 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_1x_wandb_coco.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.coco_instance import * + from .._base_.default_runtime import * + from .._base_.models.mask_rcnn_r50_fpn import * + from .._base_.schedules.schedule_1x import * + +from mmengine.visualization import LocalVisBackend, WandbVisBackend + +vis_backends.update(dict(type=WandbVisBackend)) +vis_backends.update(dict(type=LocalVisBackend)) +visualizer.update(dict(vis_backends=vis_backends)) + +# MMEngine support the following two ways, users can choose +# according to convenience +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +default_hooks.update(dict(checkpoint=dict(interval=4))) + +train_cfg.update(dict(val_interval=2)) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_2x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_2x_coco.py new file mode 100644 index 00000000..6be010b4 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_2x_coco.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.coco_instance import * + from .._base_.default_runtime import * + from .._base_.models.mask_rcnn_r50_fpn import * + from .._base_.schedules.schedule_2x import * diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_8xb8_amp_lsj_200e_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_8xb8_amp_lsj_200e_coco.py new file mode 100644 index 00000000..ef101fec --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_8xb8_amp_lsj_200e_coco.py @@ -0,0 +1 @@ +# Copyright (c) OpenMMLab. All rights reserved. diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_amp_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_amp_1x_coco.py new file mode 100644 index 00000000..110c3c47 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_amp_1x_coco.py @@ -0,0 +1,14 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_fpn_1x_coco import * + +from mmengine.optim.optimizer.amp_optimizer_wrapper import AmpOptimWrapper + +optim_wrapper.update(dict(type=AmpOptimWrapper)) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_ms_poly_-3x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_ms_poly_-3x_coco.py new file mode 100644 index 00000000..ff4eec6d --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_ms_poly_-3x_coco.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.models.mask_rcnn_r50_fpn import * + from ..common.ms_poly_3x_coco_instance import * diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_poly_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_poly_1x_coco.py new file mode 100644 index 00000000..012e711c --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_r50_fpn_poly_1x_coco.py @@ -0,0 +1,23 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.coco_instance import * + from .._base_.default_runtime import * + from .._base_.models.mask_rcnn_r50_fpn import * + from .._base_.schedules.schedule_1x import * + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs), +] +train_dataloader.update(dict(dataset=dict(pipeline=train_pipeline))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x4d_fpn_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x4d_fpn_1x_coco.py new file mode 100644 index 00000000..5429b1bd --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x4d_fpn_1x_coco.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r101_fpn_1x_coco import * + +from mmengine.model.weight_init import PretrainedInit + +from mmdet.models.backbones.resnext import ResNeXt + +model = dict( + backbone=dict( + type=ResNeXt, + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x4d_fpn_2x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x4d_fpn_2x_coco.py new file mode 100644 index 00000000..ebae6c1d --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x4d_fpn_2x_coco.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r50_fpn_2x_coco import * + +from mmengine.model.weight_init import PretrainedInit + +from mmdet.models import ResNeXt + +model = dict( + backbone=dict( + type=ResNeXt, + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x4d_fpn_ms_poly_3x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x4d_fpn_ms_poly_3x_coco.py new file mode 100644 index 00000000..aff45d89 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x4d_fpn_ms_poly_3x_coco.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from ..common.ms_poly_3x_coco_instance import * + from .._base_.models.mask_rcnn_r50_fpn import * + +from mmengine.model.weight_init import PretrainedInit + +from mmdet.models.backbones import ResNeXt + +model = dict( + backbone=dict( + type=ResNeXt, + depth=101, + groups=32, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='open-mmlab://resnext101_32x4d'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x8d_fpn_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x8d_fpn_1x_coco.py new file mode 100644 index 00000000..d9f2095d --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x8d_fpn_1x_coco.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_x101_32x4d_fpn_1x_coco import * + +model = dict( + # ResNeXt-101-32x8d model trained with Caffe2 at FB, + # so the mean and std need to be changed. + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[57.375, 57.120, 58.395], + bgr_to_rgb=False), + backbone=dict( + type=ResNeXt, + depth=101, + groups=32, + base_width=8, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=False), + style='pytorch', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnext101_32x8d'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x8d_fpn_ms_poly_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x8d_fpn_ms_poly_1x_coco.py new file mode 100644 index 00000000..8eded941 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x8d_fpn_ms_poly_1x_coco.py @@ -0,0 +1,54 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_r101_fpn_1x_coco import * + +from mmcv.transforms import RandomChoiceResize, RandomFlip +from mmcv.transforms.loading import LoadImageFromFile + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadAnnotations +from mmdet.models.backbones import ResNeXt + +model = dict( + # ResNeXt-101-32x8d model trained with Caffe2 at FB, + # so the mean and std need to be changed. + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[57.375, 57.120, 58.395], + bgr_to_rgb=False), + backbone=dict( + type=ResNeXt, + depth=101, + groups=32, + base_width=8, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=False), + style='pytorch', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnext101_32x8d'))) + +backend_args = None +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict( + type=RandomChoiceResize, + scales=[(1333, 640), (1333, 672), (1333, 704), (1333, 736), + (1333, 768), (1333, 800)], + keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs), +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x8d_fpn_ms_poly_3x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x8d_fpn_ms_poly_3x_coco.py new file mode 100644 index 00000000..b3f58467 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_32x8d_fpn_ms_poly_3x_coco.py @@ -0,0 +1,34 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from ..common.ms_poly_3x_coco_instance import * + from .._base_.models.mask_rcnn_r50_fpn import * + +from mmdet.models.backbones import ResNeXt + +model = dict( + # ResNeXt-101-32x8d model trained with Caffe2 at FB, + # so the mean and std need to be changed. + data_preprocessor=dict( + mean=[103.530, 116.280, 123.675], + std=[57.375, 57.120, 58.395], + bgr_to_rgb=False), + backbone=dict( + type=ResNeXt, + depth=101, + groups=32, + base_width=8, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=False), + style='pytorch', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnext101_32x8d'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_64_4d_fpn_1x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_64_4d_fpn_1x_coco.py new file mode 100644 index 00000000..8bb6f636 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_64_4d_fpn_1x_coco.py @@ -0,0 +1,24 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_x101_32x4d_fpn_1x_coco import * + +model = dict( + backbone=dict( + type=ResNeXt, + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_64x4d_fpn_2x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_64x4d_fpn_2x_coco.py new file mode 100644 index 00000000..d661076d --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_64x4d_fpn_2x_coco.py @@ -0,0 +1,24 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .mask_rcnn_x101_32x4d_fpn_2x_coco import * + +model = dict( + backbone=dict( + type=ResNeXt, + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_64x4d_fpn_ms_poly_3x_coco.py b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_64x4d_fpn_ms_poly_3x_coco.py new file mode 100644 index 00000000..d9ab3643 --- /dev/null +++ b/mmdetection/mmdet/configs/mask_rcnn/mask_rcnn_x101_64x4d_fpn_ms_poly_3x_coco.py @@ -0,0 +1,27 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from ..common.ms_poly_3x_coco_instance import * + from .._base_.models.mask_rcnn_r50_fpn import * + +from mmdet.models.backbones import ResNeXt + +model = dict( + backbone=dict( + type=ResNeXt, + depth=101, + groups=64, + base_width=4, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=True), + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='open-mmlab://resnext101_64x4d'))) diff --git a/mmdetection/mmdet/configs/maskformer/maskformer_r50_ms_16xb1_75e_coco.py b/mmdetection/mmdet/configs/maskformer/maskformer_r50_ms_16xb1_75e_coco.py new file mode 100644 index 00000000..70744013 --- /dev/null +++ b/mmdetection/mmdet/configs/maskformer/maskformer_r50_ms_16xb1_75e_coco.py @@ -0,0 +1,249 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms import RandomChoice, RandomChoiceResize +from mmengine.config import read_base +from mmengine.model.weight_init import PretrainedInit +from mmengine.optim.optimizer import OptimWrapper +from mmengine.optim.scheduler import MultiStepLR +from mmengine.runner import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.nn.modules.activation import ReLU +from torch.nn.modules.batchnorm import BatchNorm2d +from torch.nn.modules.normalization import GroupNorm +from torch.optim.adamw import AdamW + +from mmdet.datasets.transforms.transforms import RandomCrop +from mmdet.models import MaskFormer +from mmdet.models.backbones import ResNet +from mmdet.models.data_preprocessors.data_preprocessor import \ + DetDataPreprocessor +from mmdet.models.dense_heads.maskformer_head import MaskFormerHead +from mmdet.models.layers.pixel_decoder import TransformerEncoderPixelDecoder +from mmdet.models.losses import CrossEntropyLoss, DiceLoss, FocalLoss +from mmdet.models.seg_heads.panoptic_fusion_heads import MaskFormerFusionHead +from mmdet.models.task_modules.assigners.hungarian_assigner import \ + HungarianAssigner +from mmdet.models.task_modules.assigners.match_cost import (ClassificationCost, + DiceCost, + FocalLossCost) +from mmdet.models.task_modules.samplers import MaskPseudoSampler + +with read_base(): + from .._base_.datasets.coco_panoptic import * + from .._base_.default_runtime import * + +data_preprocessor = dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1, + pad_mask=True, + mask_pad_value=0, + pad_seg=True, + seg_pad_value=255) + +num_things_classes = 80 +num_stuff_classes = 53 +num_classes = num_things_classes + num_stuff_classes +model = dict( + type=MaskFormer, + data_preprocessor=data_preprocessor, + backbone=dict( + type=ResNet, + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=-1, + norm_cfg=dict(type=BatchNorm2d, requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet50')), + panoptic_head=dict( + type=MaskFormerHead, + in_channels=[256, 512, 1024, 2048], # pass to pixel_decoder inside + feat_channels=256, + out_channels=256, + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes, + num_queries=100, + pixel_decoder=dict( + type=TransformerEncoderPixelDecoder, + norm_cfg=dict(type=GroupNorm, num_groups=32), + act_cfg=dict(type=ReLU), + encoder=dict( # DetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.1, + act_cfg=dict(type=ReLU, inplace=True)))), + positional_encoding=dict(num_feats=128, normalize=True)), + enforce_decoder_input_project=False, + positional_encoding=dict(num_feats=128, normalize=True), + transformer_decoder=dict( # DetrTransformerDecoder + num_layers=6, + layer_cfg=dict( # DetrTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + dropout=0.1, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + ffn_drop=0.1, + act_cfg=dict(type=ReLU, inplace=True))), + return_intermediate=True), + loss_cls=dict( + type=CrossEntropyLoss, + use_sigmoid=False, + loss_weight=1.0, + reduction='mean', + class_weight=[1.0] * num_classes + [0.1]), + loss_mask=dict( + type=FocalLoss, + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + reduction='mean', + loss_weight=20.0), + loss_dice=dict( + type=DiceLoss, + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=1.0)), + panoptic_fusion_head=dict( + type=MaskFormerFusionHead, + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes, + loss_panoptic=None, + init_cfg=None), + train_cfg=dict( + assigner=dict( + type=HungarianAssigner, + match_costs=[ + dict(type=ClassificationCost, weight=1.0), + dict(type=FocalLossCost, weight=20.0, binary_input=True), + dict(type=DiceCost, weight=1.0, pred_act=True, eps=1.0) + ]), + sampler=dict(type=MaskPseudoSampler)), + test_cfg=dict( + panoptic_on=True, + # For now, the dataset does not support + # evaluating semantic segmentation metric. + semantic_on=False, + instance_on=False, + # max_per_image is for instance segmentation. + max_per_image=100, + object_mask_thr=0.8, + iou_thr=0.8, + # In MaskFormer's panoptic postprocessing, + # it will not filter masks whose score is smaller than 0.5 . + filter_low_score=False), + init_cfg=None) + +# dataset settings +train_pipeline = [ + dict(type=LoadImageFromFile), + dict( + type=LoadPanopticAnnotations, + with_bbox=True, + with_mask=True, + with_seg=True), + dict(type=RandomFlip, prob=0.5), + # dict(type=Resize, scale=(1333, 800), keep_ratio=True), + dict( + type=RandomChoice, + transforms=[[ + dict( + type=RandomChoiceResize, + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + resize_type=Resize, + keep_ratio=True) + ], + [ + dict( + type=RandomChoiceResize, + scales=[(400, 1333), (500, 1333), (600, 1333)], + resize_type=Resize, + keep_ratio=True), + dict( + type=RandomCrop, + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type=RandomChoiceResize, + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + resize_type=Resize, + keep_ratio=True) + ]]), + dict(type=PackDetInputs) +] + +train_dataloader.update( + dict(batch_size=1, num_workers=1, dataset=dict(pipeline=train_pipeline))) + +val_dataloader.update(dict(batch_size=1, num_workers=1)) + +test_dataloader = val_dataloader + +# optimizer +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict( + type=AdamW, + lr=0.0001, + weight_decay=0.0001, + eps=1e-8, + betas=(0.9, 0.999)), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'query_embed': dict(lr_mult=1.0, decay_mult=0.0) + }, + norm_decay_mult=0.0), + clip_grad=dict(max_norm=0.01, norm_type=2)) + +max_epochs = 75 + +# learning rate +param_scheduler = dict( + type=MultiStepLR, + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[50], + gamma=0.1) + +train_cfg = dict( + type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=1) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (16 GPUs) x (1 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/mmdetection/mmdet/configs/maskformer/maskformer_swin_l_p4_w12_64xb1_ms_300e_coco.py b/mmdetection/mmdet/configs/maskformer/maskformer_swin_l_p4_w12_64xb1_ms_300e_coco.py new file mode 100644 index 00000000..2affe520 --- /dev/null +++ b/mmdetection/mmdet/configs/maskformer/maskformer_swin_l_p4_w12_64xb1_ms_300e_coco.py @@ -0,0 +1,82 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.optim.scheduler import LinearLR + +from mmdet.models.backbones import SwinTransformer +from mmdet.models.layers import PixelDecoder + +with read_base(): + from .maskformer_r50_ms_16xb1_75e_coco import * + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa +depths = [2, 2, 18, 2] +model.update( + dict( + backbone=dict( + _delete_=True, + type=SwinTransformer, + pretrain_img_size=384, + embed_dims=192, + patch_size=4, + window_size=12, + mlp_ratio=4, + depths=depths, + num_heads=[6, 12, 24, 48], + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict(type=PretrainedInit, checkpoint=pretrained)), + panoptic_head=dict( + in_channels=[192, 384, 768, 1536], # pass to pixel_decoder inside + pixel_decoder=dict( + _delete_=True, + type=PixelDecoder, + norm_cfg=dict(type=GroupNorm, num_groups=32), + act_cfg=dict(type=ReLU)), + enforce_decoder_input_project=True))) + +# optimizer + +# weight_decay = 0.01 +# norm_weight_decay = 0.0 +# embed_weight_decay = 0.0 +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +norm_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'norm': norm_multi, + 'absolute_pos_embed': embed_multi, + 'relative_position_bias_table': embed_multi, + 'query_embed': embed_multi +} + +optim_wrapper.update( + dict( + optimizer=dict(lr=6e-5, weight_decay=0.01), + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0))) + +max_epochs = 300 + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type=MultiStepLR, + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[250], + gamma=0.1) +] + +train_cfg.update(dict(max_epochs=max_epochs)) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (64 GPUs) x (1 samples per GPU) +auto_scale_lr.update(dict(base_batch_size=64)) diff --git a/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r101_fpn_1x_coco.py b/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r101_fpn_1x_coco.py new file mode 100644 index 00000000..c6059780 --- /dev/null +++ b/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r101_fpn_1x_coco.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.model.weight_init import PretrainedInit + +with read_base(): + from .panoptic_fpn_r50_fpn_1x_coco import * + +model.update( + dict( + backbone=dict( + depth=101, + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet101')))) diff --git a/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r101_fpn_ms_3x_coco.py b/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r101_fpn_ms_3x_coco.py new file mode 100644 index 00000000..c02c3237 --- /dev/null +++ b/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r101_fpn_ms_3x_coco.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.model.weight_init import PretrainedInit + +with read_base(): + from .panoptic_fpn_r50_fpn_ms_3x_coco import * + +model.update( + dict( + backbone=dict( + depth=101, + init_cfg=dict( + type=PretrainedInit, checkpoint='torchvision://resnet101')))) diff --git a/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r50_fpn_1x_coco.py b/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r50_fpn_1x_coco.py new file mode 100644 index 00000000..fc893280 --- /dev/null +++ b/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r50_fpn_1x_coco.py @@ -0,0 +1,64 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.models.mask_rcnn_r50_fpn import * + from .._base_.datasets.coco_panoptic import * + from .._base_.schedules.schedule_1x import * + from .._base_.default_runtime import * + +from mmcv.ops import nms +from torch.nn import GroupNorm + +from mmdet.models.data_preprocessors.data_preprocessor import \ + DetDataPreprocessor +from mmdet.models.detectors.panoptic_fpn import PanopticFPN +from mmdet.models.losses.cross_entropy_loss import CrossEntropyLoss +from mmdet.models.seg_heads.panoptic_fpn_head import PanopticFPNHead +from mmdet.models.seg_heads.panoptic_fusion_heads import HeuristicFusionHead + +model.update( + dict( + type=PanopticFPN, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + pad_mask=True, + mask_pad_value=0, + pad_seg=True, + seg_pad_value=255), + semantic_head=dict( + type=PanopticFPNHead, + num_things_classes=80, + num_stuff_classes=53, + in_channels=256, + inner_channels=128, + start_level=0, + end_level=4, + norm_cfg=dict(type=GroupNorm, num_groups=32, requires_grad=True), + conv_cfg=None, + loss_seg=dict( + type=CrossEntropyLoss, ignore_index=255, loss_weight=0.5)), + panoptic_fusion_head=dict( + type=HeuristicFusionHead, + num_things_classes=80, + num_stuff_classes=53), + test_cfg=dict( + rcnn=dict( + score_thr=0.6, + nms=dict(type=nms, iou_threshold=0.5, class_agnostic=True), + max_per_img=100, + mask_thr_binary=0.5), + # used in HeuristicFusionHead + panoptic=dict(mask_overlap=0.5, stuff_area_limit=4096)))) + +# Forced to remove NumClassCheckHook +custom_hooks = [] diff --git a/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r50_fpn_ms_3x_coco.py b/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r50_fpn_ms_3x_coco.py new file mode 100644 index 00000000..25ebe5d6 --- /dev/null +++ b/mmdetection/mmdet/configs/panoptic_fpn/panoptic_fpn_r50_fpn_ms_3x_coco.py @@ -0,0 +1,45 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base +from mmengine.optim.scheduler.lr_scheduler import LinearLR, MultiStepLR + +with read_base(): + from .panoptic_fpn_r50_fpn_1x_coco import * + +from mmcv.transforms import RandomResize +from mmcv.transforms.loading import LoadImageFromFile + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadPanopticAnnotations +from mmdet.datasets.transforms.transforms import RandomFlip + +# In mstrain 3x config, img_scale=[(1333, 640), (1333, 800)], +# multiscale_mode='range' +train_pipeline = [ + dict(type=LoadImageFromFile), + dict( + type=LoadPanopticAnnotations, + with_bbox=True, + with_mask=True, + with_seg=True), + dict(type=RandomResize, scale=[(1333, 640), (1333, 800)], keep_ratio=True), + dict(type=RandomFlip, prob=0.5), + dict(type=PackDetInputs) +] + +train_dataloader.update(dict(dataset=dict(pipeline=train_pipeline))) + +# TODO: Use RepeatDataset to speed up training +# training schedule for 3x +train_cfg.update(dict(max_epochs=36, val_interval=3)) + +# learning rate +param_scheduler = [ + dict(type=LinearLR, start_factor=0.001, by_epoch=False, begin=0, end=500), + dict( + type=MultiStepLR, + begin=0, + end=36, + by_epoch=True, + milestones=[24, 33], + gamma=0.1) +] diff --git a/mmdetection/mmdet/configs/qdtrack/qdtrack_faster_rcnn_r50_fpn_4e_base.py b/mmdetection/mmdet/configs/qdtrack/qdtrack_faster_rcnn_r50_fpn_4e_base.py new file mode 100644 index 00000000..c672e82c --- /dev/null +++ b/mmdetection/mmdet/configs/qdtrack/qdtrack_faster_rcnn_r50_fpn_4e_base.py @@ -0,0 +1,141 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base + +with read_base(): + from .._base_.models.faster_rcnn_r50_fpn import * + from .._base_.models.faster_rcnn_r50_fpn import model + from .._base_.default_runtime import * + +from mmcv.ops import RoIAlign +from mmengine.hooks import LoggerHook, SyncBuffersHook +from mmengine.model.weight_init import PretrainedInit +from mmengine.optim import MultiStepLR, OptimWrapper +from mmengine.runner.runner import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.nn.modules.batchnorm import BatchNorm2d +from torch.nn.modules.normalization import GroupNorm +from torch.optim import SGD + +from mmdet.engine.hooks import TrackVisualizationHook +from mmdet.models import (QDTrack, QuasiDenseEmbedHead, QuasiDenseTracker, + QuasiDenseTrackHead, SingleRoIExtractor, + TrackDataPreprocessor) +from mmdet.models.losses import (L1Loss, MarginL2Loss, + MultiPosCrossEntropyLoss, SmoothL1Loss) +from mmdet.models.task_modules import (CombinedSampler, + InstanceBalancedPosSampler, + MaxIoUAssigner, RandomSampler) +from mmdet.visualization import TrackLocalVisualizer + +detector = model +detector.pop('data_preprocessor') + +detector['backbone'].update( + dict( + norm_cfg=dict(type=BatchNorm2d, requires_grad=False), + style='caffe', + init_cfg=dict( + type=PretrainedInit, + checkpoint='open-mmlab://detectron2/resnet50_caffe'))) +detector.rpn_head.loss_bbox.update( + dict(type=SmoothL1Loss, beta=1.0 / 9.0, loss_weight=1.0)) +detector.rpn_head.bbox_coder.update(dict(clip_border=False)) +detector.roi_head.bbox_head.update(dict(num_classes=1)) +detector.roi_head.bbox_head.bbox_coder.update(dict(clip_border=False)) +detector['init_cfg'] = dict( + type=PretrainedInit, + checkpoint= # noqa: E251 + 'https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/' + 'faster_rcnn_r50_fpn_1x_coco-person/' + 'faster_rcnn_r50_fpn_1x_coco-person_20201216_175929-d022e227.pth' + # noqa: E501 +) +del model + +model = dict( + type=QDTrack, + data_preprocessor=dict( + type=TrackDataPreprocessor, + mean=[103.530, 116.280, 123.675], + std=[1.0, 1.0, 1.0], + bgr_to_rgb=False, + pad_size_divisor=32), + detector=detector, + track_head=dict( + type=QuasiDenseTrackHead, + roi_extractor=dict( + type=SingleRoIExtractor, + roi_layer=dict(type=RoIAlign, output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + embed_head=dict( + type=QuasiDenseEmbedHead, + num_convs=4, + num_fcs=1, + embed_channels=256, + norm_cfg=dict(type=GroupNorm, num_groups=32), + loss_track=dict(type=MultiPosCrossEntropyLoss, loss_weight=0.25), + loss_track_aux=dict( + type=MarginL2Loss, + neg_pos_ub=3, + pos_margin=0, + neg_margin=0.1, + hard_mining=True, + loss_weight=1.0)), + loss_bbox=dict(type=L1Loss, loss_weight=1.0), + train_cfg=dict( + assigner=dict( + type=MaxIoUAssigner, + pos_iou_thr=0.7, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type=CombinedSampler, + num=256, + pos_fraction=0.5, + neg_pos_ub=3, + add_gt_as_proposals=True, + pos_sampler=dict(type=InstanceBalancedPosSampler), + neg_sampler=dict(type=RandomSampler)))), + tracker=dict( + type=QuasiDenseTracker, + init_score_thr=0.9, + obj_score_thr=0.5, + match_score_thr=0.5, + memo_tracklet_frames=30, + memo_backdrop_frames=1, + memo_momentum=0.8, + nms_conf_thr=0.5, + nms_backdrop_iou_thr=0.3, + nms_class_iou_thr=0.7, + with_cats=True, + match_metric='bisoftmax')) +# optimizer +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict(type=SGD, lr=0.02, momentum=0.9, weight_decay=0.0001), + clip_grad=dict(max_norm=35, norm_type=2)) +# learning policy +param_scheduler = [ + dict(type=MultiStepLR, begin=0, end=4, by_epoch=True, milestones=[3]) +] + +# runtime settings +train_cfg = dict(type=EpochBasedTrainLoop, max_epochs=4, val_interval=4) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +default_hooks.update( + logger=dict(type=LoggerHook, interval=50), + visualization=dict(type=TrackVisualizationHook, draw=False)) + +visualizer.update( + type=TrackLocalVisualizer, vis_backends=vis_backends, name='visualizer') + +# custom hooks +custom_hooks = [ + # Synchronize model buffers such as running_mean and running_var in BN + # at the end of each epoch + dict(type=SyncBuffersHook) +] diff --git a/mmdetection/mmdet/configs/qdtrack/qdtrack_faster_rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py b/mmdetection/mmdet/configs/qdtrack/qdtrack_faster_rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py new file mode 100644 index 00000000..2fa715e1 --- /dev/null +++ b/mmdetection/mmdet/configs/qdtrack/qdtrack_faster_rcnn_r50_fpn_8xb2-4e_mot17halftrain_test-mot17halfval.py @@ -0,0 +1,14 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import read_base + +with read_base(): + from .._base_.datasets.mot_challenge import * + from .qdtrack_faster_rcnn_r50_fpn_4e_base import * + +from mmdet.evaluation import CocoVideoMetric, MOTChallengeMetric + +# evaluator +val_evaluator = [ + dict(type=CocoVideoMetric, metric=['bbox'], classwise=True), + dict(type=MOTChallengeMetric, metric=['HOTA', 'CLEAR', 'Identity']) +] diff --git a/mmdetection/mmdet/configs/retinanet/retinanet_r50_fpn_1x_coco.py b/mmdetection/mmdet/configs/retinanet/retinanet_r50_fpn_1x_coco.py new file mode 100644 index 00000000..847600e6 --- /dev/null +++ b/mmdetection/mmdet/configs/retinanet/retinanet_r50_fpn_1x_coco.py @@ -0,0 +1,20 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.models.retinanet_r50_fpn import * + from .._base_.datasets.coco_detection import * + from .._base_.schedules.schedule_1x import * + from .._base_.default_runtime import * + from .retinanet_tta import * + +from torch.optim.sgd import SGD + +# optimizer +optim_wrapper.update( + dict(optimizer=dict(type=SGD, lr=0.01, momentum=0.9, weight_decay=0.0001))) diff --git a/mmdetection/mmdet/configs/retinanet/retinanet_tta.py b/mmdetection/mmdet/configs/retinanet/retinanet_tta.py new file mode 100644 index 00000000..4e340e58 --- /dev/null +++ b/mmdetection/mmdet/configs/retinanet/retinanet_tta.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import TestTimeAug + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadAnnotations +from mmdet.datasets.transforms.transforms import RandomFlip, Resize +from mmdet.models.test_time_augs.det_tta import DetTTAModel + +tta_model = dict( + type=DetTTAModel, + tta_cfg=dict(nms=dict(type='nms', iou_threshold=0.5), max_per_img=100)) + +img_scales = [(1333, 800), (666, 400), (2000, 1200)] +tta_pipeline = [ + dict(type=LoadImageFromFile, backend_args=None), + dict( + type=TestTimeAug, + transforms=[ + [dict(type=Resize, scale=s, keep_ratio=True) for s in img_scales], + [dict(type=RandomFlip, prob=1.), + dict(type=RandomFlip, prob=0.)], + [dict(type=LoadAnnotations, with_bbox=True)], + [ + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction')) + ] + ]) +] diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_l_8xb32_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_l_8xb32_300e_coco.py new file mode 100644 index 00000000..302d7cda --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_l_8xb32_300e_coco.py @@ -0,0 +1,134 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .rtmdet_l_8xb32_300e_coco import * + +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import RandomResize +from mmengine.hooks.ema_hook import EMAHook +from torch.nn.modules.activation import SiLU + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + Resize, YOLOXHSVRandomAug) +from mmdet.engine.hooks.pipeline_switch_hook import PipelineSwitchHook +from mmdet.models.dense_heads.rtmdet_ins_head import RTMDetInsSepBNHead +from mmdet.models.layers.ema import ExpMomentumEMA +from mmdet.models.losses.dice_loss import DiceLoss +from mmdet.models.losses.gfocal_loss import QualityFocalLoss +from mmdet.models.losses.iou_loss import GIoULoss +from mmdet.models.task_modules.coders.distance_point_bbox_coder import \ + DistancePointBBoxCoder +from mmdet.models.task_modules.prior_generators.point_generator import \ + MlvlPointGenerator + +model.merge( + dict( + bbox_head=dict( + _delete_=True, + type=RTMDetInsSepBNHead, + num_classes=80, + in_channels=256, + stacked_convs=2, + share_conv=True, + pred_kernel_size=1, + feat_channels=256, + act_cfg=dict(type=SiLU, inplace=True), + norm_cfg=dict(type='SyncBN', requires_grad=True), + anchor_generator=dict( + type=MlvlPointGenerator, offset=0, strides=[8, 16, 32]), + bbox_coder=dict(type=DistancePointBBoxCoder), + loss_cls=dict( + type=QualityFocalLoss, + use_sigmoid=True, + beta=2.0, + loss_weight=1.0), + loss_bbox=dict(type=GIoULoss, loss_weight=2.0), + loss_mask=dict( + type=DiceLoss, loss_weight=2.0, eps=5e-6, reduction='mean')), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100, + mask_thr_binary=0.5), + )) + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict(type=CachedMosaic, img_scale=(640, 640), pad_val=114.0), + dict( + type=RandomResize, + scale=(1280, 1280), + ratio_range=(0.1, 2.0), + resize_type=Resize, + keep_ratio=True), + dict( + type=RandomCrop, + crop_size=(640, 640), + recompute_bbox=True, + allow_negative_crop=True), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type=CachedMixUp, + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type=FilterAnnotations, min_gt_bbox_wh=(1, 1)), + dict(type=PackDetInputs) +] + +train_dataloader.update( + dict(pin_memory=True, dataset=dict(pipeline=train_pipeline))) + +train_pipeline_stage2 = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict( + type=RandomResize, + scale=(640, 640), + ratio_range=(0.1, 2.0), + resize_type=Resize, + keep_ratio=True), + dict( + type=RandomCrop, + crop_size=(640, 640), + recompute_bbox=True, + allow_negative_crop=True), + dict(type=FilterAnnotations, min_gt_bbox_wh=(1, 1)), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type=PackDetInputs) +] +custom_hooks = [ + dict( + type=EMAHook, + ema_type=ExpMomentumEMA, + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type=PipelineSwitchHook, + switch_epoch=280, + switch_pipeline=train_pipeline_stage2) +] + +val_evaluator.update(dict(metric=['bbox', 'segm'])) +test_evaluator = val_evaluator diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_m_8xb32_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_m_8xb32_300e_coco.py new file mode 100644 index 00000000..d90be929 --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_m_8xb32_300e_coco.py @@ -0,0 +1,17 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .rtmdet_ins_l_8xb32_300e_coco import * + +model.update( + dict( + backbone=dict(deepen_factor=0.67, widen_factor=0.75), + neck=dict( + in_channels=[192, 384, 768], out_channels=192, num_csp_blocks=2), + bbox_head=dict(in_channels=192, feat_channels=192))) diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_s_8xb32_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_s_8xb32_300e_coco.py new file mode 100644 index 00000000..58b5b1af --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_s_8xb32_300e_coco.py @@ -0,0 +1,101 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .rtmdet_ins_l_8xb32_300e_coco import * + +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import RandomResize +from mmengine.hooks.ema_hook import EMAHook + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + Resize, YOLOXHSVRandomAug) +from mmdet.engine.hooks.pipeline_switch_hook import PipelineSwitchHook +from mmdet.models.layers.ema import ExpMomentumEMA + +checkpoint = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-s_imagenet_600e.pth' # noqa +model.update( + dict( + backbone=dict( + deepen_factor=0.33, + widen_factor=0.5, + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint)), + neck=dict( + in_channels=[128, 256, 512], out_channels=128, num_csp_blocks=1), + bbox_head=dict(in_channels=128, feat_channels=128))) + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict(type=CachedMosaic, img_scale=(640, 640), pad_val=114.0), + dict( + type=RandomResize, + scale=(1280, 1280), + ratio_range=(0.5, 2.0), + resize_type=Resize, + keep_ratio=True), + dict( + type=RandomCrop, + crop_size=(640, 640), + recompute_bbox=True, + allow_negative_crop=True), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type=CachedMixUp, + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type=FilterAnnotations, min_gt_bbox_wh=(1, 1)), + dict(type=PackDetInputs) +] + +train_pipeline_stage2 = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict( + type=RandomResize, + scale=(640, 640), + ratio_range=(0.5, 2.0), + resize_type=Resize, + keep_ratio=True), + dict( + type=RandomCrop, + crop_size=(640, 640), + recompute_bbox=True, + allow_negative_crop=True), + dict(type=FilterAnnotations, min_gt_bbox_wh=(1, 1)), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type=PackDetInputs) +] + +train_dataloader.update(dict(dataset=dict(pipeline=train_pipeline))) + +custom_hooks = [ + dict( + type=EMAHook, + ema_type=ExpMomentumEMA, + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type=PipelineSwitchHook, + switch_epoch=280, + switch_pipeline=train_pipeline_stage2) +] diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_tiny_8xb32_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_tiny_8xb32_300e_coco.py new file mode 100644 index 00000000..0356b195 --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_tiny_8xb32_300e_coco.py @@ -0,0 +1,67 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .rtmdet_ins_s_8xb32_300e_coco import * + +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import RandomResize + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import (FilterAnnotations, + LoadAnnotations) +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + Resize, YOLOXHSVRandomAug) + +checkpoint = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e.pth' # noqa + +model.update( + dict( + backbone=dict( + deepen_factor=0.167, + widen_factor=0.375, + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint)), + neck=dict( + in_channels=[96, 192, 384], out_channels=96, num_csp_blocks=1), + bbox_head=dict(in_channels=96, feat_channels=96))) + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict( + type=LoadAnnotations, with_bbox=True, with_mask=True, poly2mask=False), + dict( + type=CachedMosaic, + img_scale=(640, 640), + pad_val=114.0, + max_cached_images=20, + random_pop=False), + dict( + type=RandomResize, + scale=(1280, 1280), + ratio_range=(0.5, 2.0), + resize_type=Resize, + keep_ratio=True), + dict(type=RandomCrop, crop_size=(640, 640)), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type=CachedMixUp, + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=10, + random_pop=False, + pad_val=(114, 114, 114), + prob=0.5), + dict(type=FilterAnnotations, min_gt_bbox_wh=(1, 1)), + dict(type=PackDetInputs) +] + +train_dataloader.update(dict(dataset=dict(pipeline=train_pipeline))) diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_x_8xb16_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_x_8xb16_300e_coco.py new file mode 100644 index 00000000..555b1010 --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_ins_x_8xb16_300e_coco.py @@ -0,0 +1,38 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .rtmdet_ins_l_8xb32_300e_coco import * +from mmengine.optim.scheduler.lr_scheduler import CosineAnnealingLR, LinearLR + +model.update( + dict( + backbone=dict(deepen_factor=1.33, widen_factor=1.25), + neck=dict( + in_channels=[320, 640, 1280], out_channels=320, num_csp_blocks=4), + bbox_head=dict(in_channels=320, feat_channels=320))) + +base_lr = 0.002 + +# optimizer +optim_wrapper.update(dict(optimizer=dict(lr=base_lr))) + +# learning rate +param_scheduler = [ + dict( + type=LinearLR, start_factor=1.0e-5, by_epoch=False, begin=0, end=1000), + dict( + # use cosine lr from 150 to 300 epoch + type=CosineAnnealingLR, + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_l_8xb32_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_l_8xb32_300e_coco.py new file mode 100644 index 00000000..5dcda7bf --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_l_8xb32_300e_coco.py @@ -0,0 +1,220 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .._base_.default_runtime import * + from .._base_.schedules.schedule_1x import * + from .._base_.datasets.coco_detection import * + from .rtmdet_tta import * + +from mmcv.ops import nms +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import RandomResize +from mmengine.hooks.ema_hook import EMAHook +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import CosineAnnealingLR, LinearLR +from torch.nn import SyncBatchNorm +from torch.nn.modules.activation import SiLU +from torch.optim.adamw import AdamW + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadAnnotations +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + Resize, YOLOXHSVRandomAug) +from mmdet.engine.hooks.pipeline_switch_hook import PipelineSwitchHook +from mmdet.models.backbones.cspnext import CSPNeXt +from mmdet.models.data_preprocessors.data_preprocessor import \ + DetDataPreprocessor +from mmdet.models.dense_heads.rtmdet_head import RTMDetSepBNHead +from mmdet.models.detectors.rtmdet import RTMDet +from mmdet.models.layers.ema import ExpMomentumEMA +from mmdet.models.losses.gfocal_loss import QualityFocalLoss +from mmdet.models.losses.iou_loss import GIoULoss +from mmdet.models.necks.cspnext_pafpn import CSPNeXtPAFPN +from mmdet.models.task_modules.assigners.dynamic_soft_label_assigner import \ + DynamicSoftLabelAssigner +from mmdet.models.task_modules.coders.distance_point_bbox_coder import \ + DistancePointBBoxCoder +from mmdet.models.task_modules.prior_generators.point_generator import \ + MlvlPointGenerator + +model = dict( + type=RTMDet, + data_preprocessor=dict( + type=DetDataPreprocessor, + mean=[103.53, 116.28, 123.675], + std=[57.375, 57.12, 58.395], + bgr_to_rgb=False, + batch_augments=None), + backbone=dict( + type=CSPNeXt, + arch='P5', + expand_ratio=0.5, + deepen_factor=1, + widen_factor=1, + channel_attention=True, + norm_cfg=dict(type=SyncBatchNorm), + act_cfg=dict(type=SiLU, inplace=True)), + neck=dict( + type=CSPNeXtPAFPN, + in_channels=[256, 512, 1024], + out_channels=256, + num_csp_blocks=3, + expand_ratio=0.5, + norm_cfg=dict(type=SyncBatchNorm), + act_cfg=dict(type=SiLU, inplace=True)), + bbox_head=dict( + type=RTMDetSepBNHead, + num_classes=80, + in_channels=256, + stacked_convs=2, + feat_channels=256, + anchor_generator=dict( + type=MlvlPointGenerator, offset=0, strides=[8, 16, 32]), + bbox_coder=dict(type=DistancePointBBoxCoder), + loss_cls=dict( + type=QualityFocalLoss, use_sigmoid=True, beta=2.0, + loss_weight=1.0), + loss_bbox=dict(type=GIoULoss, loss_weight=2.0), + with_objectness=False, + exp_on_reg=True, + share_conv=True, + pred_kernel_size=1, + norm_cfg=dict(type=SyncBatchNorm), + act_cfg=dict(type=SiLU, inplace=True)), + train_cfg=dict( + assigner=dict(type=DynamicSoftLabelAssigner, topk=13), + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=30000, + min_bbox_size=0, + score_thr=0.001, + nms=dict(type=nms, iou_threshold=0.65), + max_per_img=300), +) + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict(type=CachedMosaic, img_scale=(640, 640), pad_val=114.0), + dict( + type=RandomResize, + scale=(1280, 1280), + ratio_range=(0.1, 2.0), + resize_type=Resize, + keep_ratio=True), + dict(type=RandomCrop, crop_size=(640, 640)), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type=CachedMixUp, + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type=PackDetInputs) +] + +train_pipeline_stage2 = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict( + type=RandomResize, + scale=(640, 640), + ratio_range=(0.1, 2.0), + resize_type=Resize, + keep_ratio=True), + dict(type=RandomCrop, crop_size=(640, 640)), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type=PackDetInputs) +] + +test_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=Resize, scale=(640, 640), keep_ratio=True), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type=LoadAnnotations, with_bbox=True), + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader.update( + dict( + batch_size=32, + num_workers=10, + batch_sampler=None, + pin_memory=True, + dataset=dict(pipeline=train_pipeline))) +val_dataloader.update( + dict(batch_size=5, num_workers=10, dataset=dict(pipeline=test_pipeline))) +test_dataloader = val_dataloader + +max_epochs = 300 +stage2_num_epochs = 20 +base_lr = 0.004 +interval = 10 + +train_cfg.update( + dict( + max_epochs=max_epochs, + val_interval=interval, + dynamic_intervals=[(max_epochs - stage2_num_epochs, 1)])) + +val_evaluator.update(dict(proposal_nums=(100, 1, 10))) +test_evaluator = val_evaluator + +# optimizer +optim_wrapper = dict( + type=OptimWrapper, + optimizer=dict(type=AdamW, lr=base_lr, weight_decay=0.05), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True)) + +# learning rate +param_scheduler = [ + dict( + type=LinearLR, start_factor=1.0e-5, by_epoch=False, begin=0, end=1000), + dict( + # use cosine lr from 150 to 300 epoch + type=CosineAnnealingLR, + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] + +# hooks +default_hooks.update( + dict( + checkpoint=dict( + interval=interval, + max_keep_ckpts=3 # only keep latest 3 checkpoints + ))) + +custom_hooks = [ + dict( + type=EMAHook, + ema_type=ExpMomentumEMA, + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type=PipelineSwitchHook, + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline=train_pipeline_stage2) +] diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_m_8xb32_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_m_8xb32_300e_coco.py new file mode 100644 index 00000000..e741d822 --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_m_8xb32_300e_coco.py @@ -0,0 +1,17 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .rtmdet_l_8xb32_300e_coco import * + +model.update( + dict( + backbone=dict(deepen_factor=0.67, widen_factor=0.75), + neck=dict( + in_channels=[192, 384, 768], out_channels=192, num_csp_blocks=2), + bbox_head=dict(in_channels=192, feat_channels=192))) diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_s_8xb32_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_s_8xb32_300e_coco.py new file mode 100644 index 00000000..db21b747 --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_s_8xb32_300e_coco.py @@ -0,0 +1,88 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .rtmdet_l_8xb32_300e_coco import * + +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import RandomResize +from mmengine.hooks.ema_hook import EMAHook + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadAnnotations +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + Resize, YOLOXHSVRandomAug) +from mmdet.engine.hooks.pipeline_switch_hook import PipelineSwitchHook +from mmdet.models.layers.ema import ExpMomentumEMA + +checkpoint = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-s_imagenet_600e.pth' # noqa +model.update( + dict( + backbone=dict( + deepen_factor=0.33, + widen_factor=0.5, + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint)), + neck=dict( + in_channels=[128, 256, 512], out_channels=128, num_csp_blocks=1), + bbox_head=dict(in_channels=128, feat_channels=128, exp_on_reg=False))) + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict(type=CachedMosaic, img_scale=(640, 640), pad_val=114.0), + dict( + type=RandomResize, + scale=(1280, 1280), + ratio_range=(0.5, 2.0), + resize_type=Resize, + keep_ratio=True), + dict(type=RandomCrop, crop_size=(640, 640)), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type=CachedMixUp, + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=20, + pad_val=(114, 114, 114)), + dict(type=PackDetInputs) +] + +train_pipeline_stage2 = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict( + type=RandomResize, + scale=(640, 640), + ratio_range=(0.5, 2.0), + resize_type=Resize, + keep_ratio=True), + dict(type=RandomCrop, crop_size=(640, 640)), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict(type=PackDetInputs) +] + +train_dataloader.update(dict(dataset=dict(pipeline=train_pipeline))) + +custom_hooks = [ + dict( + type=EMAHook, + ema_type=ExpMomentumEMA, + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type=PipelineSwitchHook, + switch_epoch=280, + switch_pipeline=train_pipeline_stage2) +] diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_tiny_8xb32_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_tiny_8xb32_300e_coco.py new file mode 100644 index 00000000..949d056f --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_tiny_8xb32_300e_coco.py @@ -0,0 +1,64 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .rtmdet_s_8xb32_300e_coco import * + +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import RandomResize + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadAnnotations +from mmdet.datasets.transforms.transforms import (CachedMixUp, CachedMosaic, + Pad, RandomCrop, RandomFlip, + Resize, YOLOXHSVRandomAug) + +checkpoint = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/cspnext_rsb_pretrain/cspnext-tiny_imagenet_600e.pth' # noqa + +model.update( + dict( + backbone=dict( + deepen_factor=0.167, + widen_factor=0.375, + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint)), + neck=dict( + in_channels=[96, 192, 384], out_channels=96, num_csp_blocks=1), + bbox_head=dict(in_channels=96, feat_channels=96, exp_on_reg=False))) + +train_pipeline = [ + dict(type=LoadImageFromFile, backend_args=backend_args), + dict(type=LoadAnnotations, with_bbox=True), + dict( + type=CachedMosaic, + img_scale=(640, 640), + pad_val=114.0, + max_cached_images=20, + random_pop=False), + dict( + type=RandomResize, + scale=(1280, 1280), + ratio_range=(0.5, 2.0), + resize_type=Resize, + keep_ratio=True), + dict(type=RandomCrop, crop_size=(640, 640)), + dict(type=YOLOXHSVRandomAug), + dict(type=RandomFlip, prob=0.5), + dict(type=Pad, size=(640, 640), pad_val=dict(img=(114, 114, 114))), + dict( + type=CachedMixUp, + img_scale=(640, 640), + ratio_range=(1.0, 1.0), + max_cached_images=10, + random_pop=False, + pad_val=(114, 114, 114), + prob=0.5), + dict(type=PackDetInputs) +] + +train_dataloader.update(dict(dataset=dict(pipeline=train_pipeline))) diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_tta.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_tta.py new file mode 100644 index 00000000..f27b7aa4 --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_tta.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import TestTimeAug + +from mmdet.datasets.transforms.formatting import PackDetInputs +from mmdet.datasets.transforms.loading import LoadAnnotations +from mmdet.datasets.transforms.transforms import Pad, RandomFlip, Resize +from mmdet.models.test_time_augs.det_tta import DetTTAModel + +tta_model = dict( + type=DetTTAModel, + tta_cfg=dict(nms=dict(type='nms', iou_threshold=0.6), max_per_img=100)) + +img_scales = [(640, 640), (320, 320), (960, 960)] + +tta_pipeline = [ + dict(type=LoadImageFromFile, backend_args=None), + dict( + type=TestTimeAug, + transforms=[ + [dict(type=Resize, scale=s, keep_ratio=True) for s in img_scales], + [ + # ``RandomFlip`` must be placed before ``Pad``, otherwise + # bounding box coordinates after flipping cannot be + # recovered correctly. + dict(type=RandomFlip, prob=1.), + dict(type=RandomFlip, prob=0.) + ], + [ + dict( + type=Pad, + size=(960, 960), + pad_val=dict(img=(114, 114, 114))), + ], + [dict(type=LoadAnnotations, with_bbox=True)], + [ + dict( + type=PackDetInputs, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction')) + ] + ]) +] diff --git a/mmdetection/mmdet/configs/rtmdet/rtmdet_x_8xb32_300e_coco.py b/mmdetection/mmdet/configs/rtmdet/rtmdet_x_8xb32_300e_coco.py new file mode 100644 index 00000000..04d67d0c --- /dev/null +++ b/mmdetection/mmdet/configs/rtmdet/rtmdet_x_8xb32_300e_coco.py @@ -0,0 +1,17 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Please refer to https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#a-pure-python-style-configuration-file-beta for more details. # noqa +# mmcv >= 2.0.1 +# mmengine >= 0.8.0 + +from mmengine.config import read_base + +with read_base(): + from .rtmdet_l_8xb32_300e_coco import * + +model.update( + dict( + backbone=dict(deepen_factor=1.33, widen_factor=1.25), + neck=dict( + in_channels=[320, 640, 1280], out_channels=320, num_csp_blocks=4), + bbox_head=dict(in_channels=320, feat_channels=320))) diff --git a/mmdetection/mmdet/datasets/__init__.py b/mmdetection/mmdet/datasets/__init__.py new file mode 100644 index 00000000..670c207c --- /dev/null +++ b/mmdetection/mmdet/datasets/__init__.py @@ -0,0 +1,53 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .ade20k import (ADE20KInstanceDataset, ADE20KPanopticDataset, + ADE20KSegDataset) +from .base_det_dataset import BaseDetDataset +from .base_semseg_dataset import BaseSegDataset +from .base_video_dataset import BaseVideoDataset +from .cityscapes import CityscapesDataset +from .coco import CocoDataset +from .coco_caption import CocoCaptionDataset +from .coco_panoptic import CocoPanopticDataset +from .coco_semantic import CocoSegDataset +from .crowdhuman import CrowdHumanDataset +from .dataset_wrappers import ConcatDataset, MultiImageMixDataset +from .deepfashion import DeepFashionDataset +from .dod import DODDataset +from .dsdl import DSDLDetDataset +from .flickr30k import Flickr30kDataset +from .isaid import iSAIDDataset +from .lvis import LVISDataset, LVISV1Dataset, LVISV05Dataset +from .mdetr_style_refcoco import MDETRStyleRefCocoDataset +from .mot_challenge_dataset import MOTChallengeDataset +from .objects365 import Objects365V1Dataset, Objects365V2Dataset +from .odvg import ODVGDataset +from .openimages import OpenImagesChallengeDataset, OpenImagesDataset +from .refcoco import RefCocoDataset +from .reid_dataset import ReIDDataset +from .samplers import (AspectRatioBatchSampler, ClassAwareSampler, + CustomSampleSizeSampler, GroupMultiSourceSampler, + MultiSourceSampler, TrackAspectRatioBatchSampler, + TrackImgSampler) +from .utils import get_loading_pipeline +from .v3det import V3DetDataset +from .voc import VOCDataset +from .wider_face import WIDERFaceDataset +from .xml_style import XMLDataset +from .youtube_vis_dataset import YouTubeVISDataset + +__all__ = [ + 'XMLDataset', 'CocoDataset', 'DeepFashionDataset', 'VOCDataset', + 'CityscapesDataset', 'LVISDataset', 'LVISV05Dataset', 'LVISV1Dataset', + 'WIDERFaceDataset', 'get_loading_pipeline', 'CocoPanopticDataset', + 'MultiImageMixDataset', 'OpenImagesDataset', 'OpenImagesChallengeDataset', + 'AspectRatioBatchSampler', 'ClassAwareSampler', 'MultiSourceSampler', + 'GroupMultiSourceSampler', 'BaseDetDataset', 'CrowdHumanDataset', + 'Objects365V1Dataset', 'Objects365V2Dataset', 'DSDLDetDataset', + 'BaseVideoDataset', 'MOTChallengeDataset', 'TrackImgSampler', + 'ReIDDataset', 'YouTubeVISDataset', 'TrackAspectRatioBatchSampler', + 'ADE20KPanopticDataset', 'CocoCaptionDataset', 'RefCocoDataset', + 'BaseSegDataset', 'ADE20KSegDataset', 'CocoSegDataset', + 'ADE20KInstanceDataset', 'iSAIDDataset', 'V3DetDataset', 'ConcatDataset', + 'ODVGDataset', 'MDETRStyleRefCocoDataset', 'DODDataset', + 'CustomSampleSizeSampler', 'Flickr30kDataset' +] diff --git a/mmdetection/mmdet/datasets/ade20k.py b/mmdetection/mmdet/datasets/ade20k.py new file mode 100644 index 00000000..573271cb --- /dev/null +++ b/mmdetection/mmdet/datasets/ade20k.py @@ -0,0 +1,260 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from typing import List + +from mmengine import fileio + +from mmdet.registry import DATASETS +from .base_semseg_dataset import BaseSegDataset +from .coco import CocoDataset +from .coco_panoptic import CocoPanopticDataset + +ADE_PALETTE = [(120, 120, 120), (180, 120, 120), (6, 230, 230), (80, 50, 50), + (4, 200, 3), (120, 120, 80), (140, 140, 140), (204, 5, 255), + (230, 230, 230), (4, 250, 7), (224, 5, 255), (235, 255, 7), + (150, 5, 61), (120, 120, 70), (8, 255, 51), (255, 6, 82), + (143, 255, 140), (204, 255, 4), (255, 51, 7), (204, 70, 3), + (0, 102, 200), (61, 230, 250), (255, 6, 51), (11, 102, 255), + (255, 7, 71), (255, 9, 224), (9, 7, 230), (220, 220, 220), + (255, 9, 92), (112, 9, 255), (8, 255, 214), (7, 255, 224), + (255, 184, 6), (10, 255, 71), (255, 41, 10), (7, 255, 255), + (224, 255, 8), (102, 8, 255), (255, 61, 6), (255, 194, 7), + (255, 122, 8), (0, 255, 20), (255, 8, 41), (255, 5, 153), + (6, 51, 255), (235, 12, 255), (160, 150, 20), (0, 163, 255), + (140, 140, 140), (250, 10, 15), (20, 255, 0), (31, 255, 0), + (255, 31, 0), (255, 224, 0), (153, 255, 0), (0, 0, 255), + (255, 71, 0), (0, 235, 255), (0, 173, 255), (31, 0, 255), + (11, 200, 200), (255, 82, 0), (0, 255, 245), (0, 61, 255), + (0, 255, 112), (0, 255, 133), (255, 0, 0), (255, 163, 0), + (255, 102, 0), (194, 255, 0), (0, 143, 255), (51, 255, 0), + (0, 82, 255), (0, 255, 41), (0, 255, 173), (10, 0, 255), + (173, 255, 0), (0, 255, 153), (255, 92, 0), (255, 0, 255), + (255, 0, 245), (255, 0, 102), (255, 173, 0), (255, 0, 20), + (255, 184, 184), (0, 31, 255), (0, 255, 61), (0, 71, 255), + (255, 0, 204), (0, 255, 194), (0, 255, 82), (0, 10, 255), + (0, 112, 255), (51, 0, 255), (0, 194, 255), (0, 122, 255), + (0, 255, 163), (255, 153, 0), (0, 255, 10), (255, 112, 0), + (143, 255, 0), (82, 0, 255), (163, 255, 0), (255, 235, 0), + (8, 184, 170), (133, 0, 255), (0, 255, 92), (184, 0, 255), + (255, 0, 31), (0, 184, 255), (0, 214, 255), (255, 0, 112), + (92, 255, 0), (0, 224, 255), (112, 224, 255), (70, 184, 160), + (163, 0, 255), (153, 0, 255), (71, 255, 0), (255, 0, 163), + (255, 204, 0), (255, 0, 143), (0, 255, 235), (133, 255, 0), + (255, 0, 235), (245, 0, 255), (255, 0, 122), (255, 245, 0), + (10, 190, 212), (214, 255, 0), (0, 204, 255), (20, 0, 255), + (255, 255, 0), (0, 153, 255), (0, 41, 255), (0, 255, 204), + (41, 0, 255), (41, 255, 0), (173, 0, 255), (0, 245, 255), + (71, 0, 255), (122, 0, 255), (0, 255, 184), (0, 92, 255), + (184, 255, 0), (0, 133, 255), (255, 214, 0), (25, 194, 194), + (102, 255, 0), (92, 0, 255)] + + +@DATASETS.register_module() +class ADE20KPanopticDataset(CocoPanopticDataset): + METAINFO = { + 'classes': + ('bed', 'window', 'cabinet', 'person', 'door', 'table', 'curtain', + 'chair', 'car', 'painting, picture', 'sofa', 'shelf', 'mirror', + 'armchair', 'seat', 'fence', 'desk', 'wardrobe, closet, press', + 'lamp', 'tub', 'rail', 'cushion', 'box', 'column, pillar', + 'signboard, sign', 'chest of drawers, chest, bureau, dresser', + 'counter', 'sink', 'fireplace', 'refrigerator, icebox', 'stairs', + 'case, display case, showcase, vitrine', + 'pool table, billiard table, snooker table', 'pillow', + 'screen door, screen', 'bookcase', 'coffee table', + 'toilet, can, commode, crapper, pot, potty, stool, throne', 'flower', + 'book', 'bench', 'countertop', 'stove', 'palm, palm tree', + 'kitchen island', 'computer', 'swivel chair', 'boat', + 'arcade machine', 'bus', 'towel', 'light', 'truck', 'chandelier', + 'awning, sunshade, sunblind', 'street lamp', 'booth', 'tv', + 'airplane', 'clothes', 'pole', + 'bannister, banister, balustrade, balusters, handrail', + 'ottoman, pouf, pouffe, puff, hassock', 'bottle', 'van', 'ship', + 'fountain', 'washer, automatic washer, washing machine', + 'plaything, toy', 'stool', 'barrel, cask', 'basket, handbasket', + 'bag', 'minibike, motorbike', 'oven', 'ball', 'food, solid food', + 'step, stair', 'trade name', 'microwave', 'pot', 'animal', 'bicycle', + 'dishwasher', 'screen', 'sculpture', 'hood, exhaust hood', 'sconce', + 'vase', 'traffic light', 'tray', 'trash can', 'fan', 'plate', + 'monitor', 'bulletin board', 'radiator', 'glass, drinking glass', + 'clock', 'flag', 'wall', 'building', 'sky', 'floor', 'tree', + 'ceiling', 'road, route', 'grass', 'sidewalk, pavement', + 'earth, ground', 'mountain, mount', 'plant', 'water', 'house', 'sea', + 'rug', 'field', 'rock, stone', 'base, pedestal, stand', 'sand', + 'skyscraper', 'grandstand, covered stand', 'path', 'runway', + 'stairway, staircase', 'river', 'bridge, span', 'blind, screen', + 'hill', 'bar', 'hovel, hut, hutch, shack, shanty', 'tower', + 'dirt track', 'land, ground, soil', + 'escalator, moving staircase, moving stairway', + 'buffet, counter, sideboard', + 'poster, posting, placard, notice, bill, card', 'stage', + 'conveyer belt, conveyor belt, conveyer, conveyor, transporter', + 'canopy', 'pool', 'falls', 'tent', 'cradle', 'tank, storage tank', + 'lake', 'blanket, cover', 'pier', 'crt screen', 'shower'), + 'thing_classes': + ('bed', 'window', 'cabinet', 'person', 'door', 'table', 'curtain', + 'chair', 'car', 'painting, picture', 'sofa', 'shelf', 'mirror', + 'armchair', 'seat', 'fence', 'desk', 'wardrobe, closet, press', + 'lamp', 'tub', 'rail', 'cushion', 'box', 'column, pillar', + 'signboard, sign', 'chest of drawers, chest, bureau, dresser', + 'counter', 'sink', 'fireplace', 'refrigerator, icebox', 'stairs', + 'case, display case, showcase, vitrine', + 'pool table, billiard table, snooker table', 'pillow', + 'screen door, screen', 'bookcase', 'coffee table', + 'toilet, can, commode, crapper, pot, potty, stool, throne', 'flower', + 'book', 'bench', 'countertop', 'stove', 'palm, palm tree', + 'kitchen island', 'computer', 'swivel chair', 'boat', + 'arcade machine', 'bus', 'towel', 'light', 'truck', 'chandelier', + 'awning, sunshade, sunblind', 'street lamp', 'booth', 'tv', + 'airplane', 'clothes', 'pole', + 'bannister, banister, balustrade, balusters, handrail', + 'ottoman, pouf, pouffe, puff, hassock', 'bottle', 'van', 'ship', + 'fountain', 'washer, automatic washer, washing machine', + 'plaything, toy', 'stool', 'barrel, cask', 'basket, handbasket', + 'bag', 'minibike, motorbike', 'oven', 'ball', 'food, solid food', + 'step, stair', 'trade name', 'microwave', 'pot', 'animal', 'bicycle', + 'dishwasher', 'screen', 'sculpture', 'hood, exhaust hood', 'sconce', + 'vase', 'traffic light', 'tray', 'trash can', 'fan', 'plate', + 'monitor', 'bulletin board', 'radiator', 'glass, drinking glass', + 'clock', 'flag'), + 'stuff_classes': + ('wall', 'building', 'sky', 'floor', 'tree', 'ceiling', 'road, route', + 'grass', 'sidewalk, pavement', 'earth, ground', 'mountain, mount', + 'plant', 'water', 'house', 'sea', 'rug', 'field', 'rock, stone', + 'base, pedestal, stand', 'sand', 'skyscraper', + 'grandstand, covered stand', 'path', 'runway', 'stairway, staircase', + 'river', 'bridge, span', 'blind, screen', 'hill', 'bar', + 'hovel, hut, hutch, shack, shanty', 'tower', 'dirt track', + 'land, ground, soil', 'escalator, moving staircase, moving stairway', + 'buffet, counter, sideboard', + 'poster, posting, placard, notice, bill, card', 'stage', + 'conveyer belt, conveyor belt, conveyer, conveyor, transporter', + 'canopy', 'pool', 'falls', 'tent', 'cradle', 'tank, storage tank', + 'lake', 'blanket, cover', 'pier', 'crt screen', 'shower'), + 'palette': + ADE_PALETTE + } + + +@DATASETS.register_module() +class ADE20KInstanceDataset(CocoDataset): + METAINFO = { + 'classes': + ('bed', 'windowpane', 'cabinet', 'person', 'door', 'table', 'curtain', + 'chair', 'car', 'painting', 'sofa', 'shelf', 'mirror', 'armchair', + 'seat', 'fence', 'desk', 'wardrobe', 'lamp', 'bathtub', 'railing', + 'cushion', 'box', 'column', 'signboard', 'chest of drawers', + 'counter', 'sink', 'fireplace', 'refrigerator', 'stairs', 'case', + 'pool table', 'pillow', 'screen door', 'bookcase', 'coffee table', + 'toilet', 'flower', 'book', 'bench', 'countertop', 'stove', 'palm', + 'kitchen island', 'computer', 'swivel chair', 'boat', + 'arcade machine', 'bus', 'towel', 'light', 'truck', 'chandelier', + 'awning', 'streetlight', 'booth', 'television receiver', 'airplane', + 'apparel', 'pole', 'bannister', 'ottoman', 'bottle', 'van', 'ship', + 'fountain', 'washer', 'plaything', 'stool', 'barrel', 'basket', 'bag', + 'minibike', 'oven', 'ball', 'food', 'step', 'trade name', 'microwave', + 'pot', 'animal', 'bicycle', 'dishwasher', 'screen', 'sculpture', + 'hood', 'sconce', 'vase', 'traffic light', 'tray', 'ashcan', 'fan', + 'plate', 'monitor', 'bulletin board', 'radiator', 'glass', 'clock', + 'flag'), + 'palette': [(204, 5, 255), (230, 230, 230), (224, 5, 255), + (150, 5, 61), (8, 255, 51), (255, 6, 82), (255, 51, 7), + (204, 70, 3), (0, 102, 200), (255, 6, 51), (11, 102, 255), + (255, 7, 71), (220, 220, 220), (8, 255, 214), + (7, 255, 224), (255, 184, 6), (10, 255, 71), (7, 255, 255), + (224, 255, 8), (102, 8, 255), (255, 61, 6), (255, 194, 7), + (0, 255, 20), (255, 8, 41), (255, 5, 153), (6, 51, 255), + (235, 12, 255), (0, 163, 255), (250, 10, 15), (20, 255, 0), + (255, 224, 0), (0, 0, 255), (255, 71, 0), (0, 235, 255), + (0, 173, 255), (0, 255, 245), (0, 255, 112), (0, 255, 133), + (255, 0, 0), (255, 163, 0), (194, 255, 0), (0, 143, 255), + (51, 255, 0), (0, 82, 255), (0, 255, 41), (0, 255, 173), + (10, 0, 255), (173, 255, 0), (255, 92, 0), (255, 0, 245), + (255, 0, 102), (255, 173, 0), (255, 0, 20), (0, 31, 255), + (0, 255, 61), (0, 71, 255), (255, 0, 204), (0, 255, 194), + (0, 255, 82), (0, 112, 255), (51, 0, 255), (0, 122, 255), + (255, 153, 0), (0, 255, 10), (163, 255, 0), (255, 235, 0), + (8, 184, 170), (184, 0, 255), (255, 0, 31), (0, 214, 255), + (255, 0, 112), (92, 255, 0), (70, 184, 160), (163, 0, 255), + (71, 255, 0), (255, 0, 163), (255, 204, 0), (255, 0, 143), + (133, 255, 0), (255, 0, 235), (245, 0, 255), (255, 0, 122), + (255, 245, 0), (214, 255, 0), (0, 204, 255), (255, 255, 0), + (0, 153, 255), (0, 41, 255), (0, 255, 204), (41, 0, 255), + (41, 255, 0), (173, 0, 255), (0, 245, 255), (0, 255, 184), + (0, 92, 255), (184, 255, 0), (255, 214, 0), (25, 194, 194), + (102, 255, 0), (92, 0, 255)], + } + + +@DATASETS.register_module() +class ADE20KSegDataset(BaseSegDataset): + """ADE20K dataset. + + In segmentation map annotation for ADE20K, 0 stands for background, which + is not included in 150 categories. The ``img_suffix`` is fixed to '.jpg', + and ``seg_map_suffix`` is fixed to '.png'. + """ + METAINFO = dict( + classes=('wall', 'building', 'sky', 'floor', 'tree', 'ceiling', 'road', + 'bed ', 'windowpane', 'grass', 'cabinet', 'sidewalk', + 'person', 'earth', 'door', 'table', 'mountain', 'plant', + 'curtain', 'chair', 'car', 'water', 'painting', 'sofa', + 'shelf', 'house', 'sea', 'mirror', 'rug', 'field', 'armchair', + 'seat', 'fence', 'desk', 'rock', 'wardrobe', 'lamp', + 'bathtub', 'railing', 'cushion', 'base', 'box', 'column', + 'signboard', 'chest of drawers', 'counter', 'sand', 'sink', + 'skyscraper', 'fireplace', 'refrigerator', 'grandstand', + 'path', 'stairs', 'runway', 'case', 'pool table', 'pillow', + 'screen door', 'stairway', 'river', 'bridge', 'bookcase', + 'blind', 'coffee table', 'toilet', 'flower', 'book', 'hill', + 'bench', 'countertop', 'stove', 'palm', 'kitchen island', + 'computer', 'swivel chair', 'boat', 'bar', 'arcade machine', + 'hovel', 'bus', 'towel', 'light', 'truck', 'tower', + 'chandelier', 'awning', 'streetlight', 'booth', + 'television receiver', 'airplane', 'dirt track', 'apparel', + 'pole', 'land', 'bannister', 'escalator', 'ottoman', 'bottle', + 'buffet', 'poster', 'stage', 'van', 'ship', 'fountain', + 'conveyer belt', 'canopy', 'washer', 'plaything', + 'swimming pool', 'stool', 'barrel', 'basket', 'waterfall', + 'tent', 'bag', 'minibike', 'cradle', 'oven', 'ball', 'food', + 'step', 'tank', 'trade name', 'microwave', 'pot', 'animal', + 'bicycle', 'lake', 'dishwasher', 'screen', 'blanket', + 'sculpture', 'hood', 'sconce', 'vase', 'traffic light', + 'tray', 'ashcan', 'fan', 'pier', 'crt screen', 'plate', + 'monitor', 'bulletin board', 'shower', 'radiator', 'glass', + 'clock', 'flag'), + palette=ADE_PALETTE) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + return_classes=False, + **kwargs) -> None: + self.return_classes = return_classes + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) + + def load_data_list(self) -> List[dict]: + """Load annotation from directory or annotation file. + + Returns: + List[dict]: All data info of dataset. + """ + data_list = [] + img_dir = self.data_prefix.get('img_path', None) + ann_dir = self.data_prefix.get('seg_map_path', None) + for img in fileio.list_dir_or_file( + dir_path=img_dir, + list_dir=False, + suffix=self.img_suffix, + recursive=True, + backend_args=self.backend_args): + data_info = dict(img_path=osp.join(img_dir, img)) + if ann_dir is not None: + seg_map = img.replace(self.img_suffix, self.seg_map_suffix) + data_info['seg_map_path'] = osp.join(ann_dir, seg_map) + data_info['label_map'] = self.label_map + if self.return_classes: + data_info['text'] = list(self._metainfo['classes']) + data_list.append(data_info) + return data_list diff --git a/mmdetection/mmdet/datasets/api_wrappers/__init__.py b/mmdetection/mmdet/datasets/api_wrappers/__init__.py new file mode 100644 index 00000000..8e3c41a2 --- /dev/null +++ b/mmdetection/mmdet/datasets/api_wrappers/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .coco_api import COCO, COCOeval, COCOPanoptic +from .cocoeval_mp import COCOevalMP + +__all__ = ['COCO', 'COCOeval', 'COCOPanoptic', 'COCOevalMP'] diff --git a/mmdetection/mmdet/datasets/api_wrappers/coco_api.py b/mmdetection/mmdet/datasets/api_wrappers/coco_api.py new file mode 100644 index 00000000..b2d11a12 --- /dev/null +++ b/mmdetection/mmdet/datasets/api_wrappers/coco_api.py @@ -0,0 +1,137 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# This file add snake case alias for coco api + +import warnings +from collections import defaultdict +from typing import List, Optional, Union + +import pycocotools +from pycocotools.coco import COCO as _COCO +from pycocotools.cocoeval import COCOeval as _COCOeval + + +class COCO(_COCO): + """This class is almost the same as official pycocotools package. + + It implements some snake case function aliases. So that the COCO class has + the same interface as LVIS class. + """ + + def __init__(self, annotation_file=None): + if getattr(pycocotools, '__version__', '0') >= '12.0.2': + warnings.warn( + 'mmpycocotools is deprecated. Please install official pycocotools by "pip install pycocotools"', # noqa: E501 + UserWarning) + super().__init__(annotation_file=annotation_file) + self.img_ann_map = self.imgToAnns + self.cat_img_map = self.catToImgs + + def get_ann_ids(self, img_ids=[], cat_ids=[], area_rng=[], iscrowd=None): + return self.getAnnIds(img_ids, cat_ids, area_rng, iscrowd) + + def get_cat_ids(self, cat_names=[], sup_names=[], cat_ids=[]): + return self.getCatIds(cat_names, sup_names, cat_ids) + + def get_img_ids(self, img_ids=[], cat_ids=[]): + return self.getImgIds(img_ids, cat_ids) + + def load_anns(self, ids): + return self.loadAnns(ids) + + def load_cats(self, ids): + return self.loadCats(ids) + + def load_imgs(self, ids): + return self.loadImgs(ids) + + +# just for the ease of import +COCOeval = _COCOeval + + +class COCOPanoptic(COCO): + """This wrapper is for loading the panoptic style annotation file. + + The format is shown in the CocoPanopticDataset class. + + Args: + annotation_file (str, optional): Path of annotation file. + Defaults to None. + """ + + def __init__(self, annotation_file: Optional[str] = None) -> None: + super(COCOPanoptic, self).__init__(annotation_file) + + def createIndex(self) -> None: + """Create index.""" + # create index + print('creating index...') + # anns stores 'segment_id -> annotation' + anns, cats, imgs = {}, {}, {} + img_to_anns, cat_to_imgs = defaultdict(list), defaultdict(list) + if 'annotations' in self.dataset: + for ann in self.dataset['annotations']: + for seg_ann in ann['segments_info']: + # to match with instance.json + seg_ann['image_id'] = ann['image_id'] + img_to_anns[ann['image_id']].append(seg_ann) + # segment_id is not unique in coco dataset orz... + # annotations from different images but + # may have same segment_id + if seg_ann['id'] in anns.keys(): + anns[seg_ann['id']].append(seg_ann) + else: + anns[seg_ann['id']] = [seg_ann] + + # filter out annotations from other images + img_to_anns_ = defaultdict(list) + for k, v in img_to_anns.items(): + img_to_anns_[k] = [x for x in v if x['image_id'] == k] + img_to_anns = img_to_anns_ + + if 'images' in self.dataset: + for img_info in self.dataset['images']: + img_info['segm_file'] = img_info['file_name'].replace( + '.jpg', '.png') + imgs[img_info['id']] = img_info + + if 'categories' in self.dataset: + for cat in self.dataset['categories']: + cats[cat['id']] = cat + + if 'annotations' in self.dataset and 'categories' in self.dataset: + for ann in self.dataset['annotations']: + for seg_ann in ann['segments_info']: + cat_to_imgs[seg_ann['category_id']].append(ann['image_id']) + + print('index created!') + + self.anns = anns + self.imgToAnns = img_to_anns + self.catToImgs = cat_to_imgs + self.imgs = imgs + self.cats = cats + + def load_anns(self, + ids: Union[List[int], int] = []) -> Optional[List[dict]]: + """Load anns with the specified ids. + + ``self.anns`` is a list of annotation lists instead of a + list of annotations. + + Args: + ids (Union[List[int], int]): Integer ids specifying anns. + + Returns: + anns (List[dict], optional): Loaded ann objects. + """ + anns = [] + + if hasattr(ids, '__iter__') and hasattr(ids, '__len__'): + # self.anns is a list of annotation lists instead of + # a list of annotations + for id in ids: + anns += self.anns[id] + return anns + elif type(ids) == int: + return self.anns[ids] diff --git a/mmdetection/mmdet/datasets/api_wrappers/cocoeval_mp.py b/mmdetection/mmdet/datasets/api_wrappers/cocoeval_mp.py new file mode 100644 index 00000000..b3673ea7 --- /dev/null +++ b/mmdetection/mmdet/datasets/api_wrappers/cocoeval_mp.py @@ -0,0 +1,296 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import itertools +import time +from collections import defaultdict + +import numpy as np +import torch.multiprocessing as mp +from mmengine.logging import MMLogger +from pycocotools.cocoeval import COCOeval +from tqdm import tqdm + + +class COCOevalMP(COCOeval): + + def _prepare(self): + ''' + Prepare ._gts and ._dts for evaluation based on params + :return: None + ''' + + def _toMask(anns, coco): + # modify ann['segmentation'] by reference + for ann in anns: + rle = coco.annToRLE(ann) + ann['segmentation'] = rle + + p = self.params + if p.useCats: + gts = [] + dts = [] + img_ids = set(p.imgIds) + cat_ids = set(p.catIds) + for gt in self.cocoGt.dataset['annotations']: + if (gt['category_id'] in cat_ids) and (gt['image_id'] + in img_ids): + gts.append(gt) + for dt in self.cocoDt.dataset['annotations']: + if (dt['category_id'] in cat_ids) and (dt['image_id'] + in img_ids): + dts.append(dt) + # gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds)) # noqa + # dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds, catIds=p.catIds)) # noqa + # gts=self.cocoGt.dataset['annotations'] + # dts=self.cocoDt.dataset['annotations'] + else: + gts = self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds)) + dts = self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds)) + + # convert ground truth to mask if iouType == 'segm' + if p.iouType == 'segm': + _toMask(gts, self.cocoGt) + _toMask(dts, self.cocoDt) + # set ignore flag + for gt in gts: + gt['ignore'] = gt['ignore'] if 'ignore' in gt else 0 + gt['ignore'] = 'iscrowd' in gt and gt['iscrowd'] + if p.iouType == 'keypoints': + gt['ignore'] = (gt['num_keypoints'] == 0) or gt['ignore'] + self._gts = defaultdict(list) # gt for evaluation + self._dts = defaultdict(list) # dt for evaluation + for gt in gts: + self._gts[gt['image_id'], gt['category_id']].append(gt) + for dt in dts: + self._dts[dt['image_id'], dt['category_id']].append(dt) + self.evalImgs = defaultdict( + list) # per-image per-category evaluation results + self.eval = {} # accumulated evaluation results + + def evaluate(self): + """Run per image evaluation on given images and store results (a list + of dict) in self.evalImgs. + + :return: None + """ + tic = time.time() + print('Running per image evaluation...') + p = self.params + # add backward compatibility if useSegm is specified in params + if p.useSegm is not None: + p.iouType = 'segm' if p.useSegm == 1 else 'bbox' + print('useSegm (deprecated) is not None. Running {} evaluation'. + format(p.iouType)) + print('Evaluate annotation type *{}*'.format(p.iouType)) + p.imgIds = list(np.unique(p.imgIds)) + if p.useCats: + p.catIds = list(np.unique(p.catIds)) + p.maxDets = sorted(p.maxDets) + self.params = p + + # loop through images, area range, max detection number + catIds = p.catIds if p.useCats else [-1] + + nproc = 8 + split_size = len(catIds) // nproc + mp_params = [] + for i in range(nproc): + begin = i * split_size + end = (i + 1) * split_size + if i == nproc - 1: + end = len(catIds) + mp_params.append((catIds[begin:end], )) + + MMLogger.get_current_instance().info( + 'start multi processing evaluation ...') + with mp.Pool(nproc) as pool: + self.evalImgs = pool.starmap(self._evaluateImg, mp_params) + + self.evalImgs = list(itertools.chain(*self.evalImgs)) + + self._paramsEval = copy.deepcopy(self.params) + toc = time.time() + print('DONE (t={:0.2f}s).'.format(toc - tic)) + + def _evaluateImg(self, catids_chunk): + self._prepare() + p = self.params + maxDet = max(p.maxDets) + all_params = [] + for catId in catids_chunk: + for areaRng in p.areaRng: + for imgId in p.imgIds: + all_params.append((catId, areaRng, imgId)) + evalImgs = [ + self.evaluateImg(imgId, catId, areaRng, maxDet) + for catId, areaRng, imgId in tqdm(all_params) + ] + return evalImgs + + def evaluateImg(self, imgId, catId, aRng, maxDet): + p = self.params + if p.useCats: + gt = self._gts[imgId, catId] + dt = self._dts[imgId, catId] + else: + gt = [_ for cId in p.catIds for _ in self._gts[imgId, cId]] + dt = [_ for cId in p.catIds for _ in self._dts[imgId, cId]] + if len(gt) == 0 and len(dt) == 0: + return None + + for g in gt: + if g['ignore'] or (g['area'] < aRng[0] or g['area'] > aRng[1]): + g['_ignore'] = 1 + else: + g['_ignore'] = 0 + + # sort dt highest score first, sort gt ignore last + gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort') + gt = [gt[i] for i in gtind] + dtind = np.argsort([-d['score'] for d in dt], kind='mergesort') + dt = [dt[i] for i in dtind[0:maxDet]] + iscrowd = [int(o['iscrowd']) for o in gt] + # load computed ious + # ious = self.ious[imgId, catId][:, gtind] if len(self.ious[imgId, catId]) > 0 else self.ious[imgId, catId] # noqa + ious = self.computeIoU(imgId, catId) + ious = ious[:, gtind] if len(ious) > 0 else ious + + T = len(p.iouThrs) + G = len(gt) + D = len(dt) + gtm = np.zeros((T, G)) + dtm = np.zeros((T, D)) + gtIg = np.array([g['_ignore'] for g in gt]) + dtIg = np.zeros((T, D)) + if not len(ious) == 0: + for tind, t in enumerate(p.iouThrs): + for dind, d in enumerate(dt): + # information about best match so far (m=-1 -> unmatched) + iou = min([t, 1 - 1e-10]) + m = -1 + for gind, g in enumerate(gt): + # if this gt already matched, and not a crowd, continue + if gtm[tind, gind] > 0 and not iscrowd[gind]: + continue + # if dt matched to reg gt, and on ignore gt, stop + if m > -1 and gtIg[m] == 0 and gtIg[gind] == 1: + break + # continue to next gt unless better match made + if ious[dind, gind] < iou: + continue + # if match successful and best so far, + # store appropriately + iou = ious[dind, gind] + m = gind + # if match made store id of match for both dt and gt + if m == -1: + continue + dtIg[tind, dind] = gtIg[m] + dtm[tind, dind] = gt[m]['id'] + gtm[tind, m] = d['id'] + # set unmatched detections outside of area range to ignore + a = np.array([d['area'] < aRng[0] or d['area'] > aRng[1] + for d in dt]).reshape((1, len(dt))) + dtIg = np.logical_or(dtIg, np.logical_and(dtm == 0, np.repeat(a, T, + 0))) + # store results for given image and category + + return { + 'image_id': imgId, + 'category_id': catId, + 'aRng': aRng, + 'maxDet': maxDet, + 'dtIds': [d['id'] for d in dt], + 'gtIds': [g['id'] for g in gt], + 'dtMatches': dtm, + 'gtMatches': gtm, + 'dtScores': [d['score'] for d in dt], + 'gtIgnore': gtIg, + 'dtIgnore': dtIg, + } + + def summarize(self): + """Compute and display summary metrics for evaluation results. + + Note this function can *only* be applied on the default parameter + setting + """ + + def _summarize(ap=1, iouThr=None, areaRng='all', maxDets=100): + p = self.params + iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}' # noqa + titleStr = 'Average Precision' if ap == 1 else 'Average Recall' + typeStr = '(AP)' if ap == 1 else '(AR)' + iouStr = '{:0.2f}:{:0.2f}'.format(p.iouThrs[0], p.iouThrs[-1]) \ + if iouThr is None else '{:0.2f}'.format(iouThr) + + aind = [ + i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng + ] + mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets] + if ap == 1: + # dimension of precision: [TxRxKxAxM] + s = self.eval['precision'] + # IoU + if iouThr is not None: + t = np.where(iouThr == p.iouThrs)[0] + s = s[t] + s = s[:, :, :, aind, mind] + else: + # dimension of recall: [TxKxAxM] + s = self.eval['recall'] + if iouThr is not None: + t = np.where(iouThr == p.iouThrs)[0] + s = s[t] + s = s[:, :, aind, mind] + if len(s[s > -1]) == 0: + mean_s = -1 + else: + mean_s = np.mean(s[s > -1]) + print( + iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, + mean_s)) + return mean_s + + def _summarizeDets(): + stats = [] + stats.append(_summarize(1, maxDets=self.params.maxDets[-1])) + stats.append( + _summarize(1, iouThr=.5, maxDets=self.params.maxDets[-1])) + stats.append( + _summarize(1, iouThr=.75, maxDets=self.params.maxDets[-1])) + for area_rng in ('small', 'medium', 'large'): + stats.append( + _summarize( + 1, areaRng=area_rng, maxDets=self.params.maxDets[-1])) + for max_det in self.params.maxDets: + stats.append(_summarize(0, maxDets=max_det)) + for area_rng in ('small', 'medium', 'large'): + stats.append( + _summarize( + 0, areaRng=area_rng, maxDets=self.params.maxDets[-1])) + stats = np.array(stats) + return stats + + def _summarizeKps(): + stats = np.zeros((10, )) + stats[0] = _summarize(1, maxDets=20) + stats[1] = _summarize(1, maxDets=20, iouThr=.5) + stats[2] = _summarize(1, maxDets=20, iouThr=.75) + stats[3] = _summarize(1, maxDets=20, areaRng='medium') + stats[4] = _summarize(1, maxDets=20, areaRng='large') + stats[5] = _summarize(0, maxDets=20) + stats[6] = _summarize(0, maxDets=20, iouThr=.5) + stats[7] = _summarize(0, maxDets=20, iouThr=.75) + stats[8] = _summarize(0, maxDets=20, areaRng='medium') + stats[9] = _summarize(0, maxDets=20, areaRng='large') + return stats + + if not self.eval: + raise Exception('Please run accumulate() first') + iouType = self.params.iouType + if iouType == 'segm' or iouType == 'bbox': + summarize = _summarizeDets + elif iouType == 'keypoints': + summarize = _summarizeKps + self.stats = summarize() diff --git a/mmdetection/mmdet/datasets/base_det_dataset.py b/mmdetection/mmdet/datasets/base_det_dataset.py new file mode 100644 index 00000000..8b3876d5 --- /dev/null +++ b/mmdetection/mmdet/datasets/base_det_dataset.py @@ -0,0 +1,131 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from typing import List, Optional + +from mmengine.dataset import BaseDataset +from mmengine.fileio import load +from mmengine.utils import is_abs + +from ..registry import DATASETS + + +@DATASETS.register_module() +class BaseDetDataset(BaseDataset): + """Base dataset for detection. + + Args: + proposal_file (str, optional): Proposals file path. Defaults to None. + file_client_args (dict): Arguments to instantiate the + corresponding backend in mmdet <= 3.0.0rc6. Defaults to None. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + return_classes (bool): Whether to return class information + for open vocabulary-based algorithms. Defaults to False. + caption_prompt (dict, optional): Prompt for captioning. + Defaults to None. + """ + + def __init__(self, + *args, + seg_map_suffix: str = '.png', + proposal_file: Optional[str] = None, + file_client_args: dict = None, + backend_args: dict = None, + return_classes: bool = False, + caption_prompt: Optional[dict] = None, + **kwargs) -> None: + self.seg_map_suffix = seg_map_suffix + self.proposal_file = proposal_file + self.backend_args = backend_args + self.return_classes = return_classes + self.caption_prompt = caption_prompt + if self.caption_prompt is not None: + assert self.return_classes, \ + 'return_classes must be True when using caption_prompt' + if file_client_args is not None: + raise RuntimeError( + 'The `file_client_args` is deprecated, ' + 'please use `backend_args` instead, please refer to' + 'https://github.com/open-mmlab/mmdetection/blob/main/configs/_base_/datasets/coco_detection.py' # noqa: E501 + ) + super().__init__(*args, **kwargs) + + def full_init(self) -> None: + """Load annotation file and set ``BaseDataset._fully_initialized`` to + True. + + If ``lazy_init=False``, ``full_init`` will be called during the + instantiation and ``self._fully_initialized`` will be set to True. If + ``obj._fully_initialized=False``, the class method decorated by + ``force_full_init`` will call ``full_init`` automatically. + + Several steps to initialize annotation: + + - load_data_list: Load annotations from annotation file. + - load_proposals: Load proposals from proposal file, if + `self.proposal_file` is not None. + - filter data information: Filter annotations according to + filter_cfg. + - slice_data: Slice dataset according to ``self._indices`` + - serialize_data: Serialize ``self.data_list`` if + ``self.serialize_data`` is True. + """ + if self._fully_initialized: + return + # load data information + self.data_list = self.load_data_list() + # get proposals from file + if self.proposal_file is not None: + self.load_proposals() + # filter illegal data, such as data that has no annotations. + self.data_list = self.filter_data() + + # Get subset data according to indices. + if self._indices is not None: + self.data_list = self._get_unserialized_subset(self._indices) + + # serialize data_list + if self.serialize_data: + self.data_bytes, self.data_address = self._serialize_data() + + self._fully_initialized = True + + def load_proposals(self) -> None: + """Load proposals from proposals file. + + The `proposals_list` should be a dict[img_path: proposals] + with the same length as `data_list`. And the `proposals` should be + a `dict` or :obj:`InstanceData` usually contains following keys. + + - bboxes (np.ndarry): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - scores (np.ndarry): Classification scores, has a shape + (num_instance, ). + """ + # TODO: Add Unit Test after fully support Dump-Proposal Metric + if not is_abs(self.proposal_file): + self.proposal_file = osp.join(self.data_root, self.proposal_file) + proposals_list = load( + self.proposal_file, backend_args=self.backend_args) + assert len(self.data_list) == len(proposals_list) + for data_info in self.data_list: + img_path = data_info['img_path'] + # `file_name` is the key to obtain the proposals from the + # `proposals_list`. + file_name = osp.join( + osp.split(osp.split(img_path)[0])[-1], + osp.split(img_path)[-1]) + proposals = proposals_list[file_name] + data_info['proposals'] = proposals + + def get_cat_ids(self, idx: int) -> List[int]: + """Get COCO category ids by index. + + Args: + idx (int): Index of data. + + Returns: + List[int]: All categories in the image of specified index. + """ + instances = self.get_data_info(idx)['instances'] + return [instance['bbox_label'] for instance in instances] diff --git a/mmdetection/mmdet/datasets/base_semseg_dataset.py b/mmdetection/mmdet/datasets/base_semseg_dataset.py new file mode 100644 index 00000000..d10f762a --- /dev/null +++ b/mmdetection/mmdet/datasets/base_semseg_dataset.py @@ -0,0 +1,265 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +from typing import Callable, Dict, List, Optional, Sequence, Union + +import mmengine +import mmengine.fileio as fileio +import numpy as np +from mmengine.dataset import BaseDataset, Compose + +from mmdet.registry import DATASETS + + +@DATASETS.register_module() +class BaseSegDataset(BaseDataset): + """Custom dataset for semantic segmentation. An example of file structure + is as followed. + + .. code-block:: none + + ├── data + │ ├── my_dataset + │ │ ├── img_dir + │ │ │ ├── train + │ │ │ │ ├── xxx{img_suffix} + │ │ │ │ ├── yyy{img_suffix} + │ │ │ │ ├── zzz{img_suffix} + │ │ │ ├── val + │ │ ├── ann_dir + │ │ │ ├── train + │ │ │ │ ├── xxx{seg_map_suffix} + │ │ │ │ ├── yyy{seg_map_suffix} + │ │ │ │ ├── zzz{seg_map_suffix} + │ │ │ ├── val + + The img/gt_semantic_seg pair of BaseSegDataset should be of the same + except suffix. A valid img/gt_semantic_seg filename pair should be like + ``xxx{img_suffix}`` and ``xxx{seg_map_suffix}`` (extension is also included + in the suffix). If split is given, then ``xxx`` is specified in txt file. + Otherwise, all files in ``img_dir/``and ``ann_dir`` will be loaded. + Please refer to ``docs/en/tutorials/new_dataset.md`` for more details. + + + Args: + ann_file (str): Annotation file path. Defaults to ''. + metainfo (dict, optional): Meta information for dataset, such as + specify classes to load. Defaults to None. + data_root (str, optional): The root directory for ``data_prefix`` and + ``ann_file``. Defaults to None. + data_prefix (dict, optional): Prefix for training data. Defaults to + dict(img_path=None, seg_map_path=None). + img_suffix (str): Suffix of images. Default: '.jpg' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + filter_cfg (dict, optional): Config for filter data. Defaults to None. + indices (int or Sequence[int], optional): Support using first few + data in annotation file to facilitate training/testing on a smaller + dataset. Defaults to None which means using all ``data_infos``. + serialize_data (bool, optional): Whether to hold memory using + serialized objects, when enabled, data loader workers can use + shared RAM from master process instead of making a copy. Defaults + to True. + pipeline (list, optional): Processing pipeline. Defaults to []. + test_mode (bool, optional): ``test_mode=True`` means in test phase. + Defaults to False. + lazy_init (bool, optional): Whether to load annotation during + instantiation. In some cases, such as visualization, only the meta + information of the dataset is needed, which is not necessary to + load annotation file. ``Basedataset`` can skip load annotations to + save time by set ``lazy_init=True``. Defaults to False. + use_label_map (bool, optional): Whether to use label map. + Defaults to False. + max_refetch (int, optional): If ``Basedataset.prepare_data`` get a + None img. The maximum extra number of cycles to get a valid + image. Defaults to 1000. + backend_args (dict, Optional): Arguments to instantiate a file backend. + See https://mmengine.readthedocs.io/en/latest/api/fileio.htm + for details. Defaults to None. + Notes: mmcv>=2.0.0rc4 required. + """ + METAINFO: dict = dict() + + def __init__(self, + ann_file: str = '', + img_suffix='.jpg', + seg_map_suffix='.png', + metainfo: Optional[dict] = None, + data_root: Optional[str] = None, + data_prefix: dict = dict(img_path='', seg_map_path=''), + filter_cfg: Optional[dict] = None, + indices: Optional[Union[int, Sequence[int]]] = None, + serialize_data: bool = True, + pipeline: List[Union[dict, Callable]] = [], + test_mode: bool = False, + lazy_init: bool = False, + use_label_map: bool = False, + max_refetch: int = 1000, + backend_args: Optional[dict] = None) -> None: + + self.img_suffix = img_suffix + self.seg_map_suffix = seg_map_suffix + self.backend_args = backend_args.copy() if backend_args else None + + self.data_root = data_root + self.data_prefix = copy.copy(data_prefix) + self.ann_file = ann_file + self.filter_cfg = copy.deepcopy(filter_cfg) + self._indices = indices + self.serialize_data = serialize_data + self.test_mode = test_mode + self.max_refetch = max_refetch + self.data_list: List[dict] = [] + self.data_bytes: np.ndarray + + # Set meta information. + self._metainfo = self._load_metainfo(copy.deepcopy(metainfo)) + + # Get label map for custom classes + new_classes = self._metainfo.get('classes', None) + self.label_map = self.get_label_map( + new_classes) if use_label_map else None + self._metainfo.update(dict(label_map=self.label_map)) + + # Update palette based on label map or generate palette + # if it is not defined + updated_palette = self._update_palette() + self._metainfo.update(dict(palette=updated_palette)) + + # Join paths. + if self.data_root is not None: + self._join_prefix() + + # Build pipeline. + self.pipeline = Compose(pipeline) + # Full initialize the dataset. + if not lazy_init: + self.full_init() + + if test_mode: + assert self._metainfo.get('classes') is not None, \ + 'dataset metainfo `classes` should be specified when testing' + + @classmethod + def get_label_map(cls, + new_classes: Optional[Sequence] = None + ) -> Union[Dict, None]: + """Require label mapping. + + The ``label_map`` is a dictionary, its keys are the old label ids and + its values are the new label ids, and is used for changing pixel + labels in load_annotations. If and only if old classes in cls.METAINFO + is not equal to new classes in self._metainfo and nether of them is not + None, `label_map` is not None. + + Args: + new_classes (list, tuple, optional): The new classes name from + metainfo. Default to None. + + + Returns: + dict, optional: The mapping from old classes in cls.METAINFO to + new classes in self._metainfo + """ + old_classes = cls.METAINFO.get('classes', None) + if (new_classes is not None and old_classes is not None + and list(new_classes) != list(old_classes)): + + label_map = {} + if not set(new_classes).issubset(cls.METAINFO['classes']): + raise ValueError( + f'new classes {new_classes} is not a ' + f'subset of classes {old_classes} in METAINFO.') + for i, c in enumerate(old_classes): + if c not in new_classes: + # 0 is background + label_map[i] = 0 + else: + label_map[i] = new_classes.index(c) + return label_map + else: + return None + + def _update_palette(self) -> list: + """Update palette after loading metainfo. + + If length of palette is equal to classes, just return the palette. + If palette is not defined, it will randomly generate a palette. + If classes is updated by customer, it will return the subset of + palette. + + Returns: + Sequence: Palette for current dataset. + """ + palette = self._metainfo.get('palette', []) + classes = self._metainfo.get('classes', []) + # palette does match classes + if len(palette) == len(classes): + return palette + + if len(palette) == 0: + # Get random state before set seed, and restore + # random state later. + # It will prevent loss of randomness, as the palette + # may be different in each iteration if not specified. + # See: https://github.com/open-mmlab/mmdetection/issues/5844 + state = np.random.get_state() + np.random.seed(42) + # random palette + new_palette = np.random.randint( + 0, 255, size=(len(classes), 3)).tolist() + np.random.set_state(state) + elif len(palette) >= len(classes) and self.label_map is not None: + new_palette = [] + # return subset of palette + for old_id, new_id in sorted( + self.label_map.items(), key=lambda x: x[1]): + # 0 is background + if new_id != 0: + new_palette.append(palette[old_id]) + new_palette = type(palette)(new_palette) + elif len(palette) >= len(classes): + # Allow palette length is greater than classes. + return palette + else: + raise ValueError('palette does not match classes ' + f'as metainfo is {self._metainfo}.') + return new_palette + + def load_data_list(self) -> List[dict]: + """Load annotation from directory or annotation file. + + Returns: + list[dict]: All data info of dataset. + """ + data_list = [] + img_dir = self.data_prefix.get('img_path', None) + ann_dir = self.data_prefix.get('seg_map_path', None) + if not osp.isdir(self.ann_file) and self.ann_file: + assert osp.isfile(self.ann_file), \ + f'Failed to load `ann_file` {self.ann_file}' + lines = mmengine.list_from_file( + self.ann_file, backend_args=self.backend_args) + for line in lines: + img_name = line.strip() + data_info = dict( + img_path=osp.join(img_dir, img_name + self.img_suffix)) + if ann_dir is not None: + seg_map = img_name + self.seg_map_suffix + data_info['seg_map_path'] = osp.join(ann_dir, seg_map) + data_info['label_map'] = self.label_map + data_list.append(data_info) + else: + for img in fileio.list_dir_or_file( + dir_path=img_dir, + list_dir=False, + suffix=self.img_suffix, + recursive=True, + backend_args=self.backend_args): + data_info = dict(img_path=osp.join(img_dir, img)) + if ann_dir is not None: + seg_map = img.replace(self.img_suffix, self.seg_map_suffix) + data_info['seg_map_path'] = osp.join(ann_dir, seg_map) + data_info['label_map'] = self.label_map + data_list.append(data_info) + data_list = sorted(data_list, key=lambda x: x['img_path']) + return data_list diff --git a/mmdetection/mmdet/datasets/base_video_dataset.py b/mmdetection/mmdet/datasets/base_video_dataset.py new file mode 100644 index 00000000..0a4a7a25 --- /dev/null +++ b/mmdetection/mmdet/datasets/base_video_dataset.py @@ -0,0 +1,304 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +from collections import defaultdict +from typing import Any, List, Tuple + +import mmengine.fileio as fileio +from mmengine.dataset import BaseDataset +from mmengine.logging import print_log + +from mmdet.datasets.api_wrappers import COCO +from mmdet.registry import DATASETS + + +@DATASETS.register_module() +class BaseVideoDataset(BaseDataset): + """Base video dataset for VID, MOT and VIS tasks.""" + + META = dict(classes=None) + # ann_id is unique in coco dataset. + ANN_ID_UNIQUE = True + + def __init__(self, *args, backend_args: dict = None, **kwargs): + self.backend_args = backend_args + super().__init__(*args, **kwargs) + + def load_data_list(self) -> Tuple[List[dict], List]: + """Load annotations from an annotation file named as ``self.ann_file``. + + Returns: + tuple(list[dict], list): A list of annotation and a list of + valid data indices. + """ + with fileio.get_local_path(self.ann_file) as local_path: + self.coco = COCO(local_path) + # The order of returned `cat_ids` will not + # change with the order of the classes + self.cat_ids = self.coco.get_cat_ids( + cat_names=self.metainfo['classes']) + self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)} + self.cat_img_map = copy.deepcopy(self.coco.cat_img_map) + # used in `filter_data` + self.img_ids_with_ann = set() + + img_ids = self.coco.get_img_ids() + total_ann_ids = [] + # if ``video_id`` is not in the annotation file, we will assign a big + # unique video_id for this video. + single_video_id = 100000 + videos = {} + for img_id in img_ids: + raw_img_info = self.coco.load_imgs([img_id])[0] + raw_img_info['img_id'] = img_id + if 'video_id' not in raw_img_info: + single_video_id = single_video_id + 1 + video_id = single_video_id + else: + video_id = raw_img_info['video_id'] + + if video_id not in videos: + videos[video_id] = { + 'video_id': video_id, + 'images': [], + 'video_length': 0 + } + + videos[video_id]['video_length'] += 1 + ann_ids = self.coco.get_ann_ids( + img_ids=[img_id], cat_ids=self.cat_ids) + raw_ann_info = self.coco.load_anns(ann_ids) + total_ann_ids.extend(ann_ids) + + parsed_data_info = self.parse_data_info( + dict(raw_img_info=raw_img_info, raw_ann_info=raw_ann_info)) + + if len(parsed_data_info['instances']) > 0: + self.img_ids_with_ann.add(parsed_data_info['img_id']) + + videos[video_id]['images'].append(parsed_data_info) + + data_list = [v for v in videos.values()] + + if self.ANN_ID_UNIQUE: + assert len(set(total_ann_ids)) == len( + total_ann_ids + ), f"Annotation ids in '{self.ann_file}' are not unique!" + + del self.coco + + return data_list + + def parse_data_info(self, raw_data_info: dict) -> dict: + """Parse raw annotation to target format. + + Args: + raw_data_info (dict): Raw data information loaded from + ``ann_file``. + + Returns: + dict: Parsed annotation. + """ + img_info = raw_data_info['raw_img_info'] + ann_info = raw_data_info['raw_ann_info'] + data_info = {} + + data_info.update(img_info) + if self.data_prefix.get('img_path', None) is not None: + img_path = osp.join(self.data_prefix['img_path'], + img_info['file_name']) + else: + img_path = img_info['file_name'] + data_info['img_path'] = img_path + + instances = [] + for i, ann in enumerate(ann_info): + instance = {} + + if ann.get('ignore', False): + continue + x1, y1, w, h = ann['bbox'] + inter_w = max(0, min(x1 + w, img_info['width']) - max(x1, 0)) + inter_h = max(0, min(y1 + h, img_info['height']) - max(y1, 0)) + if inter_w * inter_h == 0: + continue + if ann['area'] <= 0 or w < 1 or h < 1: + continue + if ann['category_id'] not in self.cat_ids: + continue + bbox = [x1, y1, x1 + w, y1 + h] + + if ann.get('iscrowd', False): + instance['ignore_flag'] = 1 + else: + instance['ignore_flag'] = 0 + instance['bbox'] = bbox + instance['bbox_label'] = self.cat2label[ann['category_id']] + if ann.get('segmentation', None): + instance['mask'] = ann['segmentation'] + if ann.get('instance_id', None): + instance['instance_id'] = ann['instance_id'] + else: + # image dataset usually has no `instance_id`. + # Therefore, we set it to `i`. + instance['instance_id'] = i + instances.append(instance) + data_info['instances'] = instances + return data_info + + def filter_data(self) -> List[int]: + """Filter image annotations according to filter_cfg. + + Returns: + list[int]: Filtered results. + """ + if self.test_mode: + return self.data_list + + num_imgs_before_filter = sum( + [len(info['images']) for info in self.data_list]) + num_imgs_after_filter = 0 + + # obtain images that contain annotations of the required categories + ids_in_cat = set() + for i, class_id in enumerate(self.cat_ids): + ids_in_cat |= set(self.cat_img_map[class_id]) + # merge the image id sets of the two conditions and use the merged set + # to filter out images if self.filter_empty_gt=True + ids_in_cat &= self.img_ids_with_ann + + new_data_list = [] + for video_data_info in self.data_list: + imgs_data_info = video_data_info['images'] + valid_imgs_data_info = [] + + for data_info in imgs_data_info: + img_id = data_info['img_id'] + width = data_info['width'] + height = data_info['height'] + # TODO: simplify these conditions + if self.filter_cfg is None: + if img_id not in ids_in_cat: + video_data_info['video_length'] -= 1 + continue + if min(width, height) >= 32: + valid_imgs_data_info.append(data_info) + num_imgs_after_filter += 1 + else: + video_data_info['video_length'] -= 1 + else: + if self.filter_cfg.get('filter_empty_gt', + True) and img_id not in ids_in_cat: + video_data_info['video_length'] -= 1 + continue + if min(width, height) >= self.filter_cfg.get( + 'min_size', 32): + valid_imgs_data_info.append(data_info) + num_imgs_after_filter += 1 + else: + video_data_info['video_length'] -= 1 + video_data_info['images'] = valid_imgs_data_info + new_data_list.append(video_data_info) + + print_log( + 'The number of samples before and after filtering: ' + f'{num_imgs_before_filter} / {num_imgs_after_filter}', 'current') + return new_data_list + + def prepare_data(self, idx) -> Any: + """Get date processed by ``self.pipeline``. Note that ``idx`` is a + video index in default since the base element of video dataset is a + video. However, in some cases, we need to specific both the video index + and frame index. For example, in traing mode, we may want to sample the + specific frames and all the frames must be sampled once in a epoch; in + test mode, we may want to output data of a single image rather than the + whole video for saving memory. + + Args: + idx (int): The index of ``data_info``. + + Returns: + Any: Depends on ``self.pipeline``. + """ + if isinstance(idx, tuple): + assert len(idx) == 2, 'The length of idx must be 2: ' + '(video_index, frame_index)' + video_idx, frame_idx = idx[0], idx[1] + else: + video_idx, frame_idx = idx, None + + data_info = self.get_data_info(video_idx) + if self.test_mode: + # Support two test_mode: frame-level and video-level + final_data_info = defaultdict(list) + if frame_idx is None: + frames_idx_list = list(range(data_info['video_length'])) + else: + frames_idx_list = [frame_idx] + for index in frames_idx_list: + frame_ann = data_info['images'][index] + frame_ann['video_id'] = data_info['video_id'] + # Collate data_list (list of dict to dict of list) + for key, value in frame_ann.items(): + final_data_info[key].append(value) + # copy the info in video-level into img-level + # TODO: the value of this key is the same as that of + # `video_length` in test mode + final_data_info['ori_video_length'].append( + data_info['video_length']) + + final_data_info['video_length'] = [len(frames_idx_list) + ] * len(frames_idx_list) + return self.pipeline(final_data_info) + else: + # Specify `key_frame_id` for the frame sampling in the pipeline + if frame_idx is not None: + data_info['key_frame_id'] = frame_idx + return self.pipeline(data_info) + + def get_cat_ids(self, index) -> List[int]: + """Following image detection, we provide this interface function. Get + category ids by video index and frame index. + + Args: + index: The index of the dataset. It support two kinds of inputs: + Tuple: + video_idx (int): Index of video. + frame_idx (int): Index of frame. + Int: Index of video. + + Returns: + List[int]: All categories in the image of specified video index + and frame index. + """ + if isinstance(index, tuple): + assert len( + index + ) == 2, f'Expect the length of index is 2, but got {len(index)}' + video_idx, frame_idx = index + instances = self.get_data_info( + video_idx)['images'][frame_idx]['instances'] + return [instance['bbox_label'] for instance in instances] + else: + cat_ids = [] + for img in self.get_data_info(index)['images']: + for instance in img['instances']: + cat_ids.append(instance['bbox_label']) + return cat_ids + + @property + def num_all_imgs(self): + """Get the number of all the images in this video dataset.""" + return sum( + [len(self.get_data_info(i)['images']) for i in range(len(self))]) + + def get_len_per_video(self, idx): + """Get length of one video. + + Args: + idx (int): Index of video. + + Returns: + int (int): The length of the video. + """ + return len(self.get_data_info(idx)['images']) diff --git a/mmdetection/mmdet/datasets/cityscapes.py b/mmdetection/mmdet/datasets/cityscapes.py new file mode 100644 index 00000000..09755eb1 --- /dev/null +++ b/mmdetection/mmdet/datasets/cityscapes.py @@ -0,0 +1,61 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Modified from https://github.com/facebookresearch/detectron2/blob/master/detectron2/data/datasets/cityscapes.py # noqa +# and https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/evaluation/evalInstanceLevelSemanticLabeling.py # noqa + +from typing import List + +from mmdet.registry import DATASETS +from .coco import CocoDataset + + +@DATASETS.register_module() +class CityscapesDataset(CocoDataset): + """Dataset for Cityscapes.""" + + METAINFO = { + 'classes': ('person', 'rider', 'car', 'truck', 'bus', 'train', + 'motorcycle', 'bicycle'), + 'palette': [(220, 20, 60), (255, 0, 0), (0, 0, 142), (0, 0, 70), + (0, 60, 100), (0, 80, 100), (0, 0, 230), (119, 11, 32)] + } + + def filter_data(self) -> List[dict]: + """Filter annotations according to filter_cfg. + + Returns: + List[dict]: Filtered results. + """ + if self.test_mode: + return self.data_list + + if self.filter_cfg is None: + return self.data_list + + filter_empty_gt = self.filter_cfg.get('filter_empty_gt', False) + min_size = self.filter_cfg.get('min_size', 0) + + # obtain images that contain annotation + ids_with_ann = set(data_info['img_id'] for data_info in self.data_list) + # obtain images that contain annotations of the required categories + ids_in_cat = set() + for i, class_id in enumerate(self.cat_ids): + ids_in_cat |= set(self.cat_img_map[class_id]) + # merge the image id sets of the two conditions and use the merged set + # to filter out images if self.filter_empty_gt=True + ids_in_cat &= ids_with_ann + + valid_data_infos = [] + for i, data_info in enumerate(self.data_list): + img_id = data_info['img_id'] + width = data_info['width'] + height = data_info['height'] + all_is_crowd = all([ + instance['ignore_flag'] == 1 + for instance in data_info['instances'] + ]) + if filter_empty_gt and (img_id not in ids_in_cat or all_is_crowd): + continue + if min(width, height) >= min_size: + valid_data_infos.append(data_info) + + return valid_data_infos diff --git a/mmdetection/mmdet/datasets/coco.py b/mmdetection/mmdet/datasets/coco.py new file mode 100644 index 00000000..1cf21c4e --- /dev/null +++ b/mmdetection/mmdet/datasets/coco.py @@ -0,0 +1,201 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +from typing import List, Union + +from mmengine.fileio import get_local_path + +from mmdet.registry import DATASETS +from .api_wrappers import COCO +from .base_det_dataset import BaseDetDataset + + +@DATASETS.register_module() +class CocoDataset(BaseDetDataset): + """Dataset for COCO.""" + + METAINFO = { + 'classes': + ('person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', + 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', + 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', + 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', + 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', + 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', + 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', + 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', + 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', + 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy bear', 'hair drier', 'toothbrush'), + # palette is a list of color tuples, which is used for visualization. + 'palette': + [(220, 20, 60), (119, 11, 32), (0, 0, 142), (0, 0, 230), (106, 0, 228), + (0, 60, 100), (0, 80, 100), (0, 0, 70), (0, 0, 192), (250, 170, 30), + (100, 170, 30), (220, 220, 0), (175, 116, 175), (250, 0, 30), + (165, 42, 42), (255, 77, 255), (0, 226, 252), (182, 182, 255), + (0, 82, 0), (120, 166, 157), (110, 76, 0), (174, 57, 255), + (199, 100, 0), (72, 0, 118), (255, 179, 240), (0, 125, 92), + (209, 0, 151), (188, 208, 182), (0, 220, 176), (255, 99, 164), + (92, 0, 73), (133, 129, 255), (78, 180, 255), (0, 228, 0), + (174, 255, 243), (45, 89, 255), (134, 134, 103), (145, 148, 174), + (255, 208, 186), (197, 226, 255), (171, 134, 1), (109, 63, 54), + (207, 138, 255), (151, 0, 95), (9, 80, 61), (84, 105, 51), + (74, 65, 105), (166, 196, 102), (208, 195, 210), (255, 109, 65), + (0, 143, 149), (179, 0, 194), (209, 99, 106), (5, 121, 0), + (227, 255, 205), (147, 186, 208), (153, 69, 1), (3, 95, 161), + (163, 255, 0), (119, 0, 170), (0, 182, 199), (0, 165, 120), + (183, 130, 88), (95, 32, 0), (130, 114, 135), (110, 129, 133), + (166, 74, 118), (219, 142, 185), (79, 210, 114), (178, 90, 62), + (65, 70, 15), (127, 167, 115), (59, 105, 106), (142, 108, 45), + (196, 172, 0), (95, 54, 80), (128, 76, 255), (201, 57, 1), + (246, 0, 122), (191, 162, 208)] + } + COCOAPI = COCO + # ann_id is unique in coco dataset. + ANN_ID_UNIQUE = True + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ # noqa: E501 + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + self.coco = self.COCOAPI(local_path) + # The order of returned `cat_ids` will not + # change with the order of the `classes` + self.cat_ids = self.coco.get_cat_ids( + cat_names=self.metainfo['classes']) + self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)} + self.cat_img_map = copy.deepcopy(self.coco.cat_img_map) + + img_ids = self.coco.get_img_ids() + data_list = [] + total_ann_ids = [] + for img_id in img_ids: + raw_img_info = self.coco.load_imgs([img_id])[0] + raw_img_info['img_id'] = img_id + + ann_ids = self.coco.get_ann_ids(img_ids=[img_id]) + raw_ann_info = self.coco.load_anns(ann_ids) + total_ann_ids.extend(ann_ids) + + parsed_data_info = self.parse_data_info({ + 'raw_ann_info': + raw_ann_info, + 'raw_img_info': + raw_img_info + }) + data_list.append(parsed_data_info) + if self.ANN_ID_UNIQUE: + assert len(set(total_ann_ids)) == len( + total_ann_ids + ), f"Annotation ids in '{self.ann_file}' are not unique!" + + del self.coco + + return data_list + + def parse_data_info(self, raw_data_info: dict) -> Union[dict, List[dict]]: + """Parse raw annotation to target format. + + Args: + raw_data_info (dict): Raw data information load from ``ann_file`` + + Returns: + Union[dict, List[dict]]: Parsed annotation. + """ + img_info = raw_data_info['raw_img_info'] + ann_info = raw_data_info['raw_ann_info'] + + data_info = {} + + # TODO: need to change data_prefix['img'] to data_prefix['img_path'] + img_path = osp.join(self.data_prefix['img'], img_info['file_name']) + if self.data_prefix.get('seg', None): + seg_map_path = osp.join( + self.data_prefix['seg'], + img_info['file_name'].rsplit('.', 1)[0] + self.seg_map_suffix) + else: + seg_map_path = None + data_info['img_path'] = img_path + data_info['img_id'] = img_info['img_id'] + data_info['seg_map_path'] = seg_map_path + data_info['height'] = img_info['height'] + data_info['width'] = img_info['width'] + + if self.return_classes: + data_info['text'] = self.metainfo['classes'] + data_info['caption_prompt'] = self.caption_prompt + data_info['custom_entities'] = True + + instances = [] + for i, ann in enumerate(ann_info): + instance = {} + + if ann.get('ignore', False): + continue + x1, y1, w, h = ann['bbox'] + inter_w = max(0, min(x1 + w, img_info['width']) - max(x1, 0)) + inter_h = max(0, min(y1 + h, img_info['height']) - max(y1, 0)) + if inter_w * inter_h == 0: + continue + if ann['area'] <= 0 or w < 1 or h < 1: + continue + if ann['category_id'] not in self.cat_ids: + continue + bbox = [x1, y1, x1 + w, y1 + h] + + if ann.get('iscrowd', False): + instance['ignore_flag'] = 1 + else: + instance['ignore_flag'] = 0 + instance['bbox'] = bbox + instance['bbox_label'] = self.cat2label[ann['category_id']] + + if ann.get('segmentation', None): + instance['mask'] = ann['segmentation'] + + instances.append(instance) + data_info['instances'] = instances + return data_info + + def filter_data(self) -> List[dict]: + """Filter annotations according to filter_cfg. + + Returns: + List[dict]: Filtered results. + """ + if self.test_mode: + return self.data_list + + if self.filter_cfg is None: + return self.data_list + + filter_empty_gt = self.filter_cfg.get('filter_empty_gt', False) + min_size = self.filter_cfg.get('min_size', 0) + + # obtain images that contain annotation + ids_with_ann = set(data_info['img_id'] for data_info in self.data_list) + # obtain images that contain annotations of the required categories + ids_in_cat = set() + for i, class_id in enumerate(self.cat_ids): + ids_in_cat |= set(self.cat_img_map[class_id]) + # merge the image id sets of the two conditions and use the merged set + # to filter out images if self.filter_empty_gt=True + ids_in_cat &= ids_with_ann + + valid_data_infos = [] + for i, data_info in enumerate(self.data_list): + img_id = data_info['img_id'] + width = data_info['width'] + height = data_info['height'] + if filter_empty_gt and img_id not in ids_in_cat: + continue + if min(width, height) >= min_size: + valid_data_infos.append(data_info) + + return valid_data_infos diff --git a/mmdetection/mmdet/datasets/coco_caption.py b/mmdetection/mmdet/datasets/coco_caption.py new file mode 100644 index 00000000..ee695fe9 --- /dev/null +++ b/mmdetection/mmdet/datasets/coco_caption.py @@ -0,0 +1,32 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from pathlib import Path +from typing import List + +import mmengine +from mmengine.dataset import BaseDataset +from mmengine.fileio import get_file_backend + +from mmdet.registry import DATASETS + + +@DATASETS.register_module() +class CocoCaptionDataset(BaseDataset): + """COCO2014 Caption dataset.""" + + def load_data_list(self) -> List[dict]: + """Load data list.""" + img_prefix = self.data_prefix['img_path'] + annotations = mmengine.load(self.ann_file) + file_backend = get_file_backend(img_prefix) + + data_list = [] + for ann in annotations: + data_info = { + 'img_id': Path(ann['image']).stem.split('_')[-1], + 'img_path': file_backend.join_path(img_prefix, ann['image']), + 'gt_caption': ann['caption'], + } + + data_list.append(data_info) + + return data_list diff --git a/mmdetection/mmdet/datasets/coco_panoptic.py b/mmdetection/mmdet/datasets/coco_panoptic.py new file mode 100644 index 00000000..b7a200e0 --- /dev/null +++ b/mmdetection/mmdet/datasets/coco_panoptic.py @@ -0,0 +1,292 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from typing import Callable, List, Optional, Sequence, Union + +from mmdet.registry import DATASETS +from .api_wrappers import COCOPanoptic +from .coco import CocoDataset + + +@DATASETS.register_module() +class CocoPanopticDataset(CocoDataset): + """Coco dataset for Panoptic segmentation. + + The annotation format is shown as follows. The `ann` field is optional + for testing. + + .. code-block:: none + + [ + { + 'filename': f'{image_id:012}.png', + 'image_id':9 + 'segments_info': + [ + { + 'id': 8345037, (segment_id in panoptic png, + convert from rgb) + 'category_id': 51, + 'iscrowd': 0, + 'bbox': (x1, y1, w, h), + 'area': 24315 + }, + ... + ] + }, + ... + ] + + Args: + ann_file (str): Annotation file path. Defaults to ''. + metainfo (dict, optional): Meta information for dataset, such as class + information. Defaults to None. + data_root (str, optional): The root directory for ``data_prefix`` and + ``ann_file``. Defaults to None. + data_prefix (dict, optional): Prefix for training data. Defaults to + ``dict(img=None, ann=None, seg=None)``. The prefix ``seg`` which is + for panoptic segmentation map must be not None. + filter_cfg (dict, optional): Config for filter data. Defaults to None. + indices (int or Sequence[int], optional): Support using first few + data in annotation file to facilitate training/testing on a smaller + dataset. Defaults to None which means using all ``data_infos``. + serialize_data (bool, optional): Whether to hold memory using + serialized objects, when enabled, data loader workers can use + shared RAM from master process instead of making a copy. Defaults + to True. + pipeline (list, optional): Processing pipeline. Defaults to []. + test_mode (bool, optional): ``test_mode=True`` means in test phase. + Defaults to False. + lazy_init (bool, optional): Whether to load annotation during + instantiation. In some cases, such as visualization, only the meta + information of the dataset is needed, which is not necessary to + load annotation file. ``Basedataset`` can skip load annotations to + save time by set ``lazy_init=False``. Defaults to False. + max_refetch (int, optional): If ``Basedataset.prepare_data`` get a + None img. The maximum extra number of cycles to get a valid + image. Defaults to 1000. + """ + + METAINFO = { + 'classes': + ('person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', + 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', + 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', + 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', + 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', + 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', + 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', + 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', + 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', + 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy bear', 'hair drier', 'toothbrush', 'banner', + 'blanket', 'bridge', 'cardboard', 'counter', 'curtain', 'door-stuff', + 'floor-wood', 'flower', 'fruit', 'gravel', 'house', 'light', + 'mirror-stuff', 'net', 'pillow', 'platform', 'playingfield', + 'railroad', 'river', 'road', 'roof', 'sand', 'sea', 'shelf', 'snow', + 'stairs', 'tent', 'towel', 'wall-brick', 'wall-stone', 'wall-tile', + 'wall-wood', 'water-other', 'window-blind', 'window-other', + 'tree-merged', 'fence-merged', 'ceiling-merged', 'sky-other-merged', + 'cabinet-merged', 'table-merged', 'floor-other-merged', + 'pavement-merged', 'mountain-merged', 'grass-merged', 'dirt-merged', + 'paper-merged', 'food-other-merged', 'building-other-merged', + 'rock-merged', 'wall-other-merged', 'rug-merged'), + 'thing_classes': + ('person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', + 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', + 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', + 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', + 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', + 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', + 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', + 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', + 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', + 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy bear', 'hair drier', 'toothbrush'), + 'stuff_classes': + ('banner', 'blanket', 'bridge', 'cardboard', 'counter', 'curtain', + 'door-stuff', 'floor-wood', 'flower', 'fruit', 'gravel', 'house', + 'light', 'mirror-stuff', 'net', 'pillow', 'platform', 'playingfield', + 'railroad', 'river', 'road', 'roof', 'sand', 'sea', 'shelf', 'snow', + 'stairs', 'tent', 'towel', 'wall-brick', 'wall-stone', 'wall-tile', + 'wall-wood', 'water-other', 'window-blind', 'window-other', + 'tree-merged', 'fence-merged', 'ceiling-merged', 'sky-other-merged', + 'cabinet-merged', 'table-merged', 'floor-other-merged', + 'pavement-merged', 'mountain-merged', 'grass-merged', 'dirt-merged', + 'paper-merged', 'food-other-merged', 'building-other-merged', + 'rock-merged', 'wall-other-merged', 'rug-merged'), + 'palette': + [(220, 20, 60), (119, 11, 32), (0, 0, 142), (0, 0, 230), (106, 0, 228), + (0, 60, 100), (0, 80, 100), (0, 0, 70), (0, 0, 192), (250, 170, 30), + (100, 170, 30), (220, 220, 0), (175, 116, 175), (250, 0, 30), + (165, 42, 42), (255, 77, 255), (0, 226, 252), (182, 182, 255), + (0, 82, 0), (120, 166, 157), (110, 76, 0), (174, 57, 255), + (199, 100, 0), (72, 0, 118), (255, 179, 240), (0, 125, 92), + (209, 0, 151), (188, 208, 182), (0, 220, 176), (255, 99, 164), + (92, 0, 73), (133, 129, 255), (78, 180, 255), (0, 228, 0), + (174, 255, 243), (45, 89, 255), (134, 134, 103), (145, 148, 174), + (255, 208, 186), (197, 226, 255), (171, 134, 1), (109, 63, 54), + (207, 138, 255), (151, 0, 95), (9, 80, 61), (84, 105, 51), + (74, 65, 105), (166, 196, 102), (208, 195, 210), (255, 109, 65), + (0, 143, 149), (179, 0, 194), (209, 99, 106), (5, 121, 0), + (227, 255, 205), (147, 186, 208), (153, 69, 1), (3, 95, 161), + (163, 255, 0), (119, 0, 170), (0, 182, 199), (0, 165, 120), + (183, 130, 88), (95, 32, 0), (130, 114, 135), (110, 129, 133), + (166, 74, 118), (219, 142, 185), (79, 210, 114), (178, 90, 62), + (65, 70, 15), (127, 167, 115), (59, 105, 106), (142, 108, 45), + (196, 172, 0), (95, 54, 80), (128, 76, 255), (201, 57, 1), + (246, 0, 122), (191, 162, 208), (255, 255, 128), (147, 211, 203), + (150, 100, 100), (168, 171, 172), (146, 112, 198), (210, 170, 100), + (92, 136, 89), (218, 88, 184), (241, 129, 0), (217, 17, 255), + (124, 74, 181), (70, 70, 70), (255, 228, 255), (154, 208, 0), + (193, 0, 92), (76, 91, 113), (255, 180, 195), (106, 154, 176), + (230, 150, 140), (60, 143, 255), (128, 64, 128), (92, 82, 55), + (254, 212, 124), (73, 77, 174), (255, 160, 98), (255, 255, 255), + (104, 84, 109), (169, 164, 131), (225, 199, 255), (137, 54, 74), + (135, 158, 223), (7, 246, 231), (107, 255, 200), (58, 41, 149), + (183, 121, 142), (255, 73, 97), (107, 142, 35), (190, 153, 153), + (146, 139, 141), (70, 130, 180), (134, 199, 156), (209, 226, 140), + (96, 36, 108), (96, 96, 96), (64, 170, 64), (152, 251, 152), + (208, 229, 228), (206, 186, 171), (152, 161, 64), (116, 112, 0), + (0, 114, 143), (102, 102, 156), (250, 141, 255)] + } + COCOAPI = COCOPanoptic + # ann_id is not unique in coco panoptic dataset. + ANN_ID_UNIQUE = False + + def __init__(self, + ann_file: str = '', + metainfo: Optional[dict] = None, + data_root: Optional[str] = None, + data_prefix: dict = dict(img=None, ann=None, seg=None), + filter_cfg: Optional[dict] = None, + indices: Optional[Union[int, Sequence[int]]] = None, + serialize_data: bool = True, + pipeline: List[Union[dict, Callable]] = [], + test_mode: bool = False, + lazy_init: bool = False, + max_refetch: int = 1000, + backend_args: dict = None, + **kwargs) -> None: + super().__init__( + ann_file=ann_file, + metainfo=metainfo, + data_root=data_root, + data_prefix=data_prefix, + filter_cfg=filter_cfg, + indices=indices, + serialize_data=serialize_data, + pipeline=pipeline, + test_mode=test_mode, + lazy_init=lazy_init, + max_refetch=max_refetch, + backend_args=backend_args, + **kwargs) + + def parse_data_info(self, raw_data_info: dict) -> dict: + """Parse raw annotation to target format. + + Args: + raw_data_info (dict): Raw data information load from ``ann_file``. + + Returns: + dict: Parsed annotation. + """ + img_info = raw_data_info['raw_img_info'] + ann_info = raw_data_info['raw_ann_info'] + # filter out unmatched annotations which have + # same segment_id but belong to other image + ann_info = [ + ann for ann in ann_info if ann['image_id'] == img_info['img_id'] + ] + data_info = {} + + img_path = osp.join(self.data_prefix['img'], img_info['file_name']) + if self.data_prefix.get('seg', None): + seg_map_path = osp.join( + self.data_prefix['seg'], + img_info['file_name'].replace('.jpg', '.png')) + else: + seg_map_path = None + data_info['img_path'] = img_path + data_info['img_id'] = img_info['img_id'] + data_info['seg_map_path'] = seg_map_path + data_info['height'] = img_info['height'] + data_info['width'] = img_info['width'] + + if self.return_classes: + data_info['text'] = self.metainfo['thing_classes'] + data_info['stuff_text'] = self.metainfo['stuff_classes'] + data_info['custom_entities'] = True # no important + + instances = [] + segments_info = [] + for ann in ann_info: + instance = {} + x1, y1, w, h = ann['bbox'] + if ann['area'] <= 0 or w < 1 or h < 1: + continue + bbox = [x1, y1, x1 + w, y1 + h] + category_id = ann['category_id'] + contiguous_cat_id = self.cat2label[category_id] + + is_thing = self.coco.load_cats(ids=category_id)[0]['isthing'] + if is_thing: + is_crowd = ann.get('iscrowd', False) + instance['bbox'] = bbox + instance['bbox_label'] = contiguous_cat_id + if not is_crowd: + instance['ignore_flag'] = 0 + else: + instance['ignore_flag'] = 1 + is_thing = False + + segment_info = { + 'id': ann['id'], + 'category': contiguous_cat_id, + 'is_thing': is_thing + } + segments_info.append(segment_info) + if len(instance) > 0 and is_thing: + instances.append(instance) + data_info['instances'] = instances + data_info['segments_info'] = segments_info + return data_info + + def filter_data(self) -> List[dict]: + """Filter images too small or without ground truth. + + Returns: + List[dict]: ``self.data_list`` after filtering. + """ + if self.test_mode: + return self.data_list + + if self.filter_cfg is None: + return self.data_list + + filter_empty_gt = self.filter_cfg.get('filter_empty_gt', False) + min_size = self.filter_cfg.get('min_size', 0) + + ids_with_ann = set() + # check whether images have legal thing annotations. + for data_info in self.data_list: + for segment_info in data_info['segments_info']: + if not segment_info['is_thing']: + continue + ids_with_ann.add(data_info['img_id']) + + valid_data_list = [] + for data_info in self.data_list: + img_id = data_info['img_id'] + width = data_info['width'] + height = data_info['height'] + if filter_empty_gt and img_id not in ids_with_ann: + continue + if min(width, height) >= min_size: + valid_data_list.append(data_info) + + return valid_data_list diff --git a/mmdetection/mmdet/datasets/coco_semantic.py b/mmdetection/mmdet/datasets/coco_semantic.py new file mode 100644 index 00000000..75256845 --- /dev/null +++ b/mmdetection/mmdet/datasets/coco_semantic.py @@ -0,0 +1,90 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import DATASETS +from .ade20k import ADE20KSegDataset + + +@DATASETS.register_module() +class CocoSegDataset(ADE20KSegDataset): + """COCO dataset. + + In segmentation map annotation for COCO. The ``img_suffix`` is fixed to + '.jpg', and ``seg_map_suffix`` is fixed to '.png'. + """ + + METAINFO = dict( + classes=( + 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', + 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', + 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', + 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', + 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', + 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', + 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', + 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', + 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', + 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', + 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', + 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', + 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy bear', 'hair drier', 'toothbrush', 'banner', + 'blanket', 'branch', 'bridge', 'building-other', 'bush', 'cabinet', + 'cage', 'cardboard', 'carpet', 'ceiling-other', 'ceiling-tile', + 'cloth', 'clothes', 'clouds', 'counter', 'cupboard', 'curtain', + 'desk-stuff', 'dirt', 'door-stuff', 'fence', 'floor-marble', + 'floor-other', 'floor-stone', 'floor-tile', 'floor-wood', 'flower', + 'fog', 'food-other', 'fruit', 'furniture-other', 'grass', 'gravel', + 'ground-other', 'hill', 'house', 'leaves', 'light', 'mat', 'metal', + 'mirror-stuff', 'moss', 'mountain', 'mud', 'napkin', 'net', + 'paper', 'pavement', 'pillow', 'plant-other', 'plastic', + 'platform', 'playingfield', 'railing', 'railroad', 'river', 'road', + 'rock', 'roof', 'rug', 'salad', 'sand', 'sea', 'shelf', + 'sky-other', 'skyscraper', 'snow', 'solid-other', 'stairs', + 'stone', 'straw', 'structural-other', 'table', 'tent', + 'textile-other', 'towel', 'tree', 'vegetable', 'wall-brick', + 'wall-concrete', 'wall-other', 'wall-panel', 'wall-stone', + 'wall-tile', 'wall-wood', 'water-other', 'waterdrops', + 'window-blind', 'window-other', 'wood'), + palette=[(120, 120, 120), (180, 120, 120), (6, 230, 230), (80, 50, 50), + (4, 200, 3), (120, 120, 80), (140, 140, 140), (204, 5, 255), + (230, 230, 230), (4, 250, 7), (224, 5, 255), (235, 255, 7), + (150, 5, 61), (120, 120, 70), (8, 255, 51), (255, 6, 82), + (143, 255, 140), (204, 255, 4), (255, 51, 7), (204, 70, 3), + (0, 102, 200), (61, 230, 250), (255, 6, 51), (11, 102, 255), + (255, 7, 71), (255, 9, 224), (9, 7, 230), (220, 220, 220), + (255, 9, 92), (112, 9, 255), (8, 255, 214), (7, 255, 224), + (255, 184, 6), (10, 255, 71), (255, 41, 10), (7, 255, 255), + (224, 255, 8), (102, 8, 255), (255, 61, 6), (255, 194, 7), + (255, 122, 8), (0, 255, 20), (255, 8, 41), (255, 5, 153), + (6, 51, 255), (235, 12, 255), (160, 150, 20), (0, 163, 255), + (140, 140, 140), (250, 10, 15), (20, 255, 0), (31, 255, 0), + (255, 31, 0), (255, 224, 0), (153, 255, 0), (0, 0, 255), + (255, 71, 0), (0, 235, 255), (0, 173, 255), (31, 0, 255), + (11, 200, 200), (255, 82, 0), (0, 255, 245), (0, 61, 255), + (0, 255, 112), (0, 255, 133), (255, 0, 0), (255, 163, 0), + (255, 102, 0), (194, 255, 0), (0, 143, 255), (51, 255, 0), + (0, 82, 255), (0, 255, 41), (0, 255, 173), (10, 0, 255), + (173, 255, 0), (0, 255, 153), (255, 92, 0), (255, 0, 255), + (255, 0, 245), (255, 0, 102), (255, 173, 0), (255, 0, 20), + (255, 184, 184), (0, 31, 255), (0, 255, 61), (0, 71, 255), + (255, 0, 204), (0, 255, 194), (0, 255, 82), (0, 10, 255), + (0, 112, 255), (51, 0, 255), (0, 194, 255), (0, 122, 255), + (0, 255, 163), (255, 153, 0), (0, 255, 10), (255, 112, 0), + (143, 255, 0), (82, 0, 255), (163, 255, 0), (255, 235, 0), + (8, 184, 170), (133, 0, 255), (0, 255, 92), (184, 0, 255), + (255, 0, 31), (0, 184, 255), (0, 214, 255), (255, 0, 112), + (92, 255, 0), (0, 224, 255), (112, 224, 255), (70, 184, 160), + (163, 0, 255), (153, 0, 255), (71, 255, 0), (255, 0, 163), + (255, 204, 0), (255, 0, 143), (0, 255, 235), (133, 255, 0), + (255, 0, 235), (245, 0, 255), (255, 0, 122), (255, 245, 0), + (10, 190, 212), (214, 255, 0), (0, 204, 255), (20, 0, 255), + (255, 255, 0), (0, 153, 255), (0, 41, 255), (0, 255, 204), + (41, 0, 255), (41, 255, 0), (173, 0, 255), (0, 245, 255), + (71, 0, 255), (122, 0, 255), (0, 255, 184), (0, 92, 255), + (184, 255, 0), (0, 133, 255), (255, 214, 0), (25, 194, 194), + (102, 255, 0), (92, 0, 255), (107, 255, 200), (58, 41, 149), + (183, 121, 142), (255, 73, 97), (107, 142, 35), + (190, 153, 153), (146, 139, 141), (70, 130, 180), + (134, 199, 156), (209, 226, 140), (96, 36, 108), (96, 96, 96), + (64, 170, 64), (152, 251, 152), (208, 229, 228), + (206, 186, 171), (152, 161, 64), (116, 112, 0), (0, 114, 143), + (102, 102, 156), (250, 141, 255)]) diff --git a/mmdetection/mmdet/datasets/crowdhuman.py b/mmdetection/mmdet/datasets/crowdhuman.py new file mode 100644 index 00000000..650176ee --- /dev/null +++ b/mmdetection/mmdet/datasets/crowdhuman.py @@ -0,0 +1,159 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +import logging +import os.path as osp +import warnings +from typing import List, Union + +import mmcv +from mmengine.dist import get_rank +from mmengine.fileio import dump, get, get_text, load +from mmengine.logging import print_log +from mmengine.utils import ProgressBar + +from mmdet.registry import DATASETS +from .base_det_dataset import BaseDetDataset + + +@DATASETS.register_module() +class CrowdHumanDataset(BaseDetDataset): + r"""Dataset for CrowdHuman. + + Args: + data_root (str): The root directory for + ``data_prefix`` and ``ann_file``. + ann_file (str): Annotation file path. + extra_ann_file (str | optional):The path of extra image metas + for CrowdHuman. It can be created by CrowdHumanDataset + automatically or by tools/misc/get_crowdhuman_id_hw.py + manually. Defaults to None. + """ + + METAINFO = { + 'classes': ('person', ), + # palette is a list of color tuples, which is used for visualization. + 'palette': [(220, 20, 60)] + } + + def __init__(self, data_root, ann_file, extra_ann_file=None, **kwargs): + # extra_ann_file record the size of each image. This file is + # automatically created when you first load the CrowdHuman + # dataset by mmdet. + if extra_ann_file is not None: + self.extra_ann_exist = True + self.extra_anns = load(extra_ann_file) + else: + ann_file_name = osp.basename(ann_file) + if 'train' in ann_file_name: + self.extra_ann_file = osp.join(data_root, 'id_hw_train.json') + elif 'val' in ann_file_name: + self.extra_ann_file = osp.join(data_root, 'id_hw_val.json') + self.extra_ann_exist = False + if not osp.isfile(self.extra_ann_file): + print_log( + 'extra_ann_file does not exist, prepare to collect ' + 'image height and width...', + level=logging.INFO) + self.extra_anns = {} + else: + self.extra_ann_exist = True + self.extra_anns = load(self.extra_ann_file) + super().__init__(data_root=data_root, ann_file=ann_file, **kwargs) + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ # noqa: E501 + anno_strs = get_text( + self.ann_file, backend_args=self.backend_args).strip().split('\n') + print_log('loading CrowdHuman annotation...', level=logging.INFO) + data_list = [] + prog_bar = ProgressBar(len(anno_strs)) + for i, anno_str in enumerate(anno_strs): + anno_dict = json.loads(anno_str) + parsed_data_info = self.parse_data_info(anno_dict) + data_list.append(parsed_data_info) + prog_bar.update() + if not self.extra_ann_exist and get_rank() == 0: + # TODO: support file client + try: + dump(self.extra_anns, self.extra_ann_file, file_format='json') + except: # noqa + warnings.warn( + 'Cache files can not be saved automatically! To speed up' + 'loading the dataset, please manually generate the cache' + ' file by file tools/misc/get_crowdhuman_id_hw.py') + + print_log( + f'\nsave extra_ann_file in {self.data_root}', + level=logging.INFO) + + del self.extra_anns + print_log('\nDone', level=logging.INFO) + return data_list + + def parse_data_info(self, raw_data_info: dict) -> Union[dict, List[dict]]: + """Parse raw annotation to target format. + + Args: + raw_data_info (dict): Raw data information load from ``ann_file`` + + Returns: + Union[dict, List[dict]]: Parsed annotation. + """ + data_info = {} + img_path = osp.join(self.data_prefix['img'], + f"{raw_data_info['ID']}.jpg") + data_info['img_path'] = img_path + data_info['img_id'] = raw_data_info['ID'] + + if not self.extra_ann_exist: + img_bytes = get(img_path, backend_args=self.backend_args) + img = mmcv.imfrombytes(img_bytes, backend='cv2') + data_info['height'], data_info['width'] = img.shape[:2] + self.extra_anns[raw_data_info['ID']] = img.shape[:2] + del img, img_bytes + else: + data_info['height'], data_info['width'] = self.extra_anns[ + raw_data_info['ID']] + + instances = [] + for i, ann in enumerate(raw_data_info['gtboxes']): + instance = {} + if ann['tag'] not in self.metainfo['classes']: + instance['bbox_label'] = -1 + instance['ignore_flag'] = 1 + else: + instance['bbox_label'] = self.metainfo['classes'].index( + ann['tag']) + instance['ignore_flag'] = 0 + if 'extra' in ann: + if 'ignore' in ann['extra']: + if ann['extra']['ignore'] != 0: + instance['bbox_label'] = -1 + instance['ignore_flag'] = 1 + + x1, y1, w, h = ann['fbox'] + bbox = [x1, y1, x1 + w, y1 + h] + instance['bbox'] = bbox + + # Record the full bbox(fbox), head bbox(hbox) and visible + # bbox(vbox) as additional information. If you need to use + # this information, you just need to design the pipeline + # instead of overriding the CrowdHumanDataset. + instance['fbox'] = bbox + hbox = ann['hbox'] + instance['hbox'] = [ + hbox[0], hbox[1], hbox[0] + hbox[2], hbox[1] + hbox[3] + ] + vbox = ann['vbox'] + instance['vbox'] = [ + vbox[0], vbox[1], vbox[0] + vbox[2], vbox[1] + vbox[3] + ] + + instances.append(instance) + + data_info['instances'] = instances + return data_info diff --git a/mmdetection/mmdet/datasets/dataset_wrappers.py b/mmdetection/mmdet/datasets/dataset_wrappers.py new file mode 100644 index 00000000..d4e26e07 --- /dev/null +++ b/mmdetection/mmdet/datasets/dataset_wrappers.py @@ -0,0 +1,260 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import collections +import copy +from typing import List, Sequence, Union + +from mmengine.dataset import BaseDataset +from mmengine.dataset import ConcatDataset as MMENGINE_ConcatDataset +from mmengine.dataset import force_full_init + +from mmdet.registry import DATASETS, TRANSFORMS + + +@DATASETS.register_module() +class MultiImageMixDataset: + """A wrapper of multiple images mixed dataset. + + Suitable for training on multiple images mixed data augmentation like + mosaic and mixup. For the augmentation pipeline of mixed image data, + the `get_indexes` method needs to be provided to obtain the image + indexes, and you can set `skip_flags` to change the pipeline running + process. At the same time, we provide the `dynamic_scale` parameter + to dynamically change the output image size. + + Args: + dataset (:obj:`CustomDataset`): The dataset to be mixed. + pipeline (Sequence[dict]): Sequence of transform object or + config dict to be composed. + dynamic_scale (tuple[int], optional): The image scale can be changed + dynamically. Default to None. It is deprecated. + skip_type_keys (list[str], optional): Sequence of type string to + be skip pipeline. Default to None. + max_refetch (int): The maximum number of retry iterations for getting + valid results from the pipeline. If the number of iterations is + greater than `max_refetch`, but results is still None, then the + iteration is terminated and raise the error. Default: 15. + """ + + def __init__(self, + dataset: Union[BaseDataset, dict], + pipeline: Sequence[str], + skip_type_keys: Union[Sequence[str], None] = None, + max_refetch: int = 15, + lazy_init: bool = False) -> None: + assert isinstance(pipeline, collections.abc.Sequence) + if skip_type_keys is not None: + assert all([ + isinstance(skip_type_key, str) + for skip_type_key in skip_type_keys + ]) + self._skip_type_keys = skip_type_keys + + self.pipeline = [] + self.pipeline_types = [] + for transform in pipeline: + if isinstance(transform, dict): + self.pipeline_types.append(transform['type']) + transform = TRANSFORMS.build(transform) + self.pipeline.append(transform) + else: + raise TypeError('pipeline must be a dict') + + self.dataset: BaseDataset + if isinstance(dataset, dict): + self.dataset = DATASETS.build(dataset) + elif isinstance(dataset, BaseDataset): + self.dataset = dataset + else: + raise TypeError( + 'elements in datasets sequence should be config or ' + f'`BaseDataset` instance, but got {type(dataset)}') + + self._metainfo = self.dataset.metainfo + if hasattr(self.dataset, 'flag'): + self.flag = self.dataset.flag + self.num_samples = len(self.dataset) + self.max_refetch = max_refetch + + self._fully_initialized = False + if not lazy_init: + self.full_init() + + @property + def metainfo(self) -> dict: + """Get the meta information of the multi-image-mixed dataset. + + Returns: + dict: The meta information of multi-image-mixed dataset. + """ + return copy.deepcopy(self._metainfo) + + def full_init(self): + """Loop to ``full_init`` each dataset.""" + if self._fully_initialized: + return + + self.dataset.full_init() + self._ori_len = len(self.dataset) + self._fully_initialized = True + + @force_full_init + def get_data_info(self, idx: int) -> dict: + """Get annotation by index. + + Args: + idx (int): Global index of ``ConcatDataset``. + + Returns: + dict: The idx-th annotation of the datasets. + """ + return self.dataset.get_data_info(idx) + + @force_full_init + def __len__(self): + return self.num_samples + + def __getitem__(self, idx): + results = copy.deepcopy(self.dataset[idx]) + for (transform, transform_type) in zip(self.pipeline, + self.pipeline_types): + if self._skip_type_keys is not None and \ + transform_type in self._skip_type_keys: + continue + + if hasattr(transform, 'get_indexes'): + for i in range(self.max_refetch): + # Make sure the results passed the loading pipeline + # of the original dataset is not None. + indexes = transform.get_indexes(self.dataset) + if not isinstance(indexes, collections.abc.Sequence): + indexes = [indexes] + mix_results = [ + copy.deepcopy(self.dataset[index]) for index in indexes + ] + if None not in mix_results: + results['mix_results'] = mix_results + break + else: + raise RuntimeError( + 'The loading pipeline of the original dataset' + ' always return None. Please check the correctness ' + 'of the dataset and its pipeline.') + + for i in range(self.max_refetch): + # To confirm the results passed the training pipeline + # of the wrapper is not None. + updated_results = transform(copy.deepcopy(results)) + if updated_results is not None: + results = updated_results + break + else: + raise RuntimeError( + 'The training pipeline of the dataset wrapper' + ' always return None.Please check the correctness ' + 'of the dataset and its pipeline.') + + if 'mix_results' in results: + results.pop('mix_results') + + return results + + def update_skip_type_keys(self, skip_type_keys): + """Update skip_type_keys. It is called by an external hook. + + Args: + skip_type_keys (list[str], optional): Sequence of type + string to be skip pipeline. + """ + assert all([ + isinstance(skip_type_key, str) for skip_type_key in skip_type_keys + ]) + self._skip_type_keys = skip_type_keys + + +@DATASETS.register_module() +class ConcatDataset(MMENGINE_ConcatDataset): + """A wrapper of concatenated dataset. + + Same as ``torch.utils.data.dataset.ConcatDataset``, support + lazy_init and get_dataset_source. + + Note: + ``ConcatDataset`` should not inherit from ``BaseDataset`` since + ``get_subset`` and ``get_subset_`` could produce ambiguous meaning + sub-dataset which conflicts with original dataset. If you want to use + a sub-dataset of ``ConcatDataset``, you should set ``indices`` + arguments for wrapped dataset which inherit from ``BaseDataset``. + + Args: + datasets (Sequence[BaseDataset] or Sequence[dict]): A list of datasets + which will be concatenated. + lazy_init (bool, optional): Whether to load annotation during + instantiation. Defaults to False. + ignore_keys (List[str] or str): Ignore the keys that can be + unequal in `dataset.metainfo`. Defaults to None. + `New in version 0.3.0.` + """ + + def __init__(self, + datasets: Sequence[Union[BaseDataset, dict]], + lazy_init: bool = False, + ignore_keys: Union[str, List[str], None] = None): + self.datasets: List[BaseDataset] = [] + for i, dataset in enumerate(datasets): + if isinstance(dataset, dict): + self.datasets.append(DATASETS.build(dataset)) + elif isinstance(dataset, BaseDataset): + self.datasets.append(dataset) + else: + raise TypeError( + 'elements in datasets sequence should be config or ' + f'`BaseDataset` instance, but got {type(dataset)}') + if ignore_keys is None: + self.ignore_keys = [] + elif isinstance(ignore_keys, str): + self.ignore_keys = [ignore_keys] + elif isinstance(ignore_keys, list): + self.ignore_keys = ignore_keys + else: + raise TypeError('ignore_keys should be a list or str, ' + f'but got {type(ignore_keys)}') + + meta_keys: set = set() + for dataset in self.datasets: + meta_keys |= dataset.metainfo.keys() + # if the metainfo of multiple datasets are the same, use metainfo + # of the first dataset, else the metainfo is a list with metainfo + # of all the datasets + is_all_same = True + self._metainfo_first = self.datasets[0].metainfo + for i, dataset in enumerate(self.datasets, 1): + for key in meta_keys: + if key in self.ignore_keys: + continue + if key not in dataset.metainfo: + is_all_same = False + break + if self._metainfo_first[key] != dataset.metainfo[key]: + is_all_same = False + break + + if is_all_same: + self._metainfo = self.datasets[0].metainfo + else: + self._metainfo = [dataset.metainfo for dataset in self.datasets] + + self._fully_initialized = False + if not lazy_init: + self.full_init() + + if is_all_same: + self._metainfo.update( + dict(cumulative_sizes=self.cumulative_sizes)) + else: + for i, dataset in enumerate(self.datasets): + self._metainfo[i].update( + dict(cumulative_sizes=self.cumulative_sizes)) + + def get_dataset_source(self, idx: int) -> int: + dataset_idx, _ = self._get_ori_dataset_idx(idx) + return dataset_idx diff --git a/mmdetection/mmdet/datasets/deepfashion.py b/mmdetection/mmdet/datasets/deepfashion.py new file mode 100644 index 00000000..f853fc63 --- /dev/null +++ b/mmdetection/mmdet/datasets/deepfashion.py @@ -0,0 +1,19 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import DATASETS +from .coco import CocoDataset + + +@DATASETS.register_module() +class DeepFashionDataset(CocoDataset): + """Dataset for DeepFashion.""" + + METAINFO = { + 'classes': ('top', 'skirt', 'leggings', 'dress', 'outer', 'pants', + 'bag', 'neckwear', 'headwear', 'eyeglass', 'belt', + 'footwear', 'hair', 'skin', 'face'), + # palette is a list of color tuples, which is used for visualization. + 'palette': [(0, 192, 64), (0, 64, 96), (128, 192, 192), (0, 64, 64), + (0, 192, 224), (0, 192, 192), (128, 192, 64), (0, 192, 96), + (128, 32, 192), (0, 0, 224), (0, 0, 64), (0, 160, 192), + (128, 0, 96), (128, 0, 192), (0, 32, 192)] + } diff --git a/mmdetection/mmdet/datasets/dod.py b/mmdetection/mmdet/datasets/dod.py new file mode 100644 index 00000000..152d32aa --- /dev/null +++ b/mmdetection/mmdet/datasets/dod.py @@ -0,0 +1,78 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from typing import List, Optional + +import numpy as np + +from mmdet.registry import DATASETS +from .base_det_dataset import BaseDetDataset + +try: + from d_cube import D3 +except ImportError: + D3 = None +from .api_wrappers import COCO + + +@DATASETS.register_module() +class DODDataset(BaseDetDataset): + + def __init__(self, + *args, + data_root: Optional[str] = '', + data_prefix: dict = dict(img_path=''), + **kwargs) -> None: + if D3 is None: + raise ImportError( + 'Please install d3 by `pip install ddd-dataset`.') + pkl_anno_path = osp.join(data_root, data_prefix['anno']) + self.img_root = osp.join(data_root, data_prefix['img']) + self.d3 = D3(self.img_root, pkl_anno_path) + + sent_infos = self.d3.load_sents() + classes = tuple([sent_info['raw_sent'] for sent_info in sent_infos]) + super().__init__( + *args, + data_root=data_root, + data_prefix=data_prefix, + metainfo={'classes': classes}, + **kwargs) + + def load_data_list(self) -> List[dict]: + coco = COCO(self.ann_file) + data_list = [] + img_ids = self.d3.get_img_ids() + for img_id in img_ids: + data_info = {} + + img_info = self.d3.load_imgs(img_id)[0] + file_name = img_info['file_name'] + img_path = osp.join(self.img_root, file_name) + data_info['img_path'] = img_path + data_info['img_id'] = img_id + data_info['height'] = img_info['height'] + data_info['width'] = img_info['width'] + + group_ids = self.d3.get_group_ids(img_ids=[img_id]) + sent_ids = self.d3.get_sent_ids(group_ids=group_ids) + sent_list = self.d3.load_sents(sent_ids=sent_ids) + text_list = [sent['raw_sent'] for sent in sent_list] + ann_ids = coco.get_ann_ids(img_ids=[img_id]) + anno = coco.load_anns(ann_ids) + + data_info['text'] = text_list + data_info['sent_ids'] = np.array([s for s in sent_ids]) + data_info['custom_entities'] = True + + instances = [] + for i, ann in enumerate(anno): + instance = {} + x1, y1, w, h = ann['bbox'] + bbox = [x1, y1, x1 + w, y1 + h] + instance['ignore_flag'] = 0 + instance['bbox'] = bbox + instance['bbox_label'] = ann['category_id'] - 1 + instances.append(instance) + data_info['instances'] = instances + data_list.append(data_info) + return data_list diff --git a/mmdetection/mmdet/datasets/dsdl.py b/mmdetection/mmdet/datasets/dsdl.py new file mode 100644 index 00000000..75570a2a --- /dev/null +++ b/mmdetection/mmdet/datasets/dsdl.py @@ -0,0 +1,192 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +from typing import List + +from mmdet.registry import DATASETS +from .base_det_dataset import BaseDetDataset + +try: + from dsdl.dataset import DSDLDataset +except ImportError: + DSDLDataset = None + + +@DATASETS.register_module() +class DSDLDetDataset(BaseDetDataset): + """Dataset for dsdl detection. + + Args: + with_bbox(bool): Load bbox or not, defaults to be True. + with_polygon(bool): Load polygon or not, defaults to be False. + with_mask(bool): Load seg map mask or not, defaults to be False. + with_imagelevel_label(bool): Load image level label or not, + defaults to be False. + with_hierarchy(bool): Load hierarchy information or not, + defaults to be False. + specific_key_path(dict): Path of specific key which can not + be loaded by it's field name. + pre_transform(dict): pre-transform functions before loading. + """ + + METAINFO = {} + + def __init__(self, + with_bbox: bool = True, + with_polygon: bool = False, + with_mask: bool = False, + with_imagelevel_label: bool = False, + with_hierarchy: bool = False, + specific_key_path: dict = {}, + pre_transform: dict = {}, + **kwargs) -> None: + + if DSDLDataset is None: + raise RuntimeError( + 'Package dsdl is not installed. Please run "pip install dsdl".' + ) + + self.with_hierarchy = with_hierarchy + self.specific_key_path = specific_key_path + + loc_config = dict(type='LocalFileReader', working_dir='') + if kwargs.get('data_root'): + kwargs['ann_file'] = os.path.join(kwargs['data_root'], + kwargs['ann_file']) + self.required_fields = ['Image', 'ImageShape', 'Label', 'ignore_flag'] + if with_bbox: + self.required_fields.append('Bbox') + if with_polygon: + self.required_fields.append('Polygon') + if with_mask: + self.required_fields.append('LabelMap') + if with_imagelevel_label: + self.required_fields.append('image_level_labels') + assert 'image_level_labels' in specific_key_path.keys( + ), '`image_level_labels` not specified in `specific_key_path` !' + + self.extra_keys = [ + key for key in self.specific_key_path.keys() + if key not in self.required_fields + ] + + self.dsdldataset = DSDLDataset( + dsdl_yaml=kwargs['ann_file'], + location_config=loc_config, + required_fields=self.required_fields, + specific_key_path=specific_key_path, + transform=pre_transform, + ) + + BaseDetDataset.__init__(self, **kwargs) + + def load_data_list(self) -> List[dict]: + """Load data info from an dsdl yaml file named as ``self.ann_file`` + + Returns: + List[dict]: A list of data info. + """ + if self.with_hierarchy: + # get classes_names and relation_matrix + classes_names, relation_matrix = \ + self.dsdldataset.class_dom.get_hierarchy_info() + self._metainfo['classes'] = tuple(classes_names) + self._metainfo['RELATION_MATRIX'] = relation_matrix + + else: + self._metainfo['classes'] = tuple(self.dsdldataset.class_names) + + data_list = [] + + for i, data in enumerate(self.dsdldataset): + # basic image info, including image id, path and size. + datainfo = dict( + img_id=i, + img_path=os.path.join(self.data_prefix['img_path'], + data['Image'][0].location), + width=data['ImageShape'][0].width, + height=data['ImageShape'][0].height, + ) + + # get image label info + if 'image_level_labels' in data.keys(): + if self.with_hierarchy: + # get leaf node name when using hierarchy classes + datainfo['image_level_labels'] = [ + self._metainfo['classes'].index(i.leaf_node_name) + for i in data['image_level_labels'] + ] + else: + datainfo['image_level_labels'] = [ + self._metainfo['classes'].index(i.name) + for i in data['image_level_labels'] + ] + + # get semantic segmentation info + if 'LabelMap' in data.keys(): + datainfo['seg_map_path'] = data['LabelMap'] + + # load instance info + instances = [] + if 'Bbox' in data.keys(): + for idx in range(len(data['Bbox'])): + bbox = data['Bbox'][idx] + if self.with_hierarchy: + # get leaf node name when using hierarchy classes + label = data['Label'][idx].leaf_node_name + label_index = self._metainfo['classes'].index(label) + else: + label = data['Label'][idx].name + label_index = self._metainfo['classes'].index(label) + + instance = {} + instance['bbox'] = bbox.xyxy + instance['bbox_label'] = label_index + + if 'ignore_flag' in data.keys(): + # get ignore flag + instance['ignore_flag'] = data['ignore_flag'][idx] + else: + instance['ignore_flag'] = 0 + + if 'Polygon' in data.keys(): + # get polygon info + polygon = data['Polygon'][idx] + instance['mask'] = polygon.openmmlabformat + + for key in self.extra_keys: + # load extra instance info + instance[key] = data[key][idx] + + instances.append(instance) + + datainfo['instances'] = instances + # append a standard sample in data list + if len(datainfo['instances']) > 0: + data_list.append(datainfo) + + return data_list + + def filter_data(self) -> List[dict]: + """Filter annotations according to filter_cfg. + + Returns: + List[dict]: Filtered results. + """ + if self.test_mode: + return self.data_list + + filter_empty_gt = self.filter_cfg.get('filter_empty_gt', False) \ + if self.filter_cfg is not None else False + min_size = self.filter_cfg.get('min_size', 0) \ + if self.filter_cfg is not None else 0 + + valid_data_list = [] + for i, data_info in enumerate(self.data_list): + width = data_info['width'] + height = data_info['height'] + if filter_empty_gt and len(data_info['instances']) == 0: + continue + if min(width, height) >= min_size: + valid_data_list.append(data_info) + + return valid_data_list diff --git a/mmdetection/mmdet/datasets/flickr30k.py b/mmdetection/mmdet/datasets/flickr30k.py new file mode 100644 index 00000000..0c76a41b --- /dev/null +++ b/mmdetection/mmdet/datasets/flickr30k.py @@ -0,0 +1,81 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from typing import List + +from pycocotools.coco import COCO + +from mmdet.registry import DATASETS +from .base_det_dataset import BaseDetDataset + + +def convert_phrase_ids(phrase_ids: list) -> list: + unique_elements = sorted(set(phrase_ids)) + element_to_new_label = { + element: label + for label, element in enumerate(unique_elements) + } + phrase_ids = [element_to_new_label[element] for element in phrase_ids] + return phrase_ids + + +@DATASETS.register_module() +class Flickr30kDataset(BaseDetDataset): + """Flickr30K Dataset.""" + + def load_data_list(self) -> List[dict]: + + self.coco = COCO(self.ann_file) + + self.ids = sorted(list(self.coco.imgs.keys())) + + data_list = [] + for img_id in self.ids: + if isinstance(img_id, str): + ann_ids = self.coco.getAnnIds(imgIds=[img_id], iscrowd=None) + else: + ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=None) + + coco_img = self.coco.loadImgs(img_id)[0] + + caption = coco_img['caption'] + file_name = coco_img['file_name'] + img_path = osp.join(self.data_prefix['img'], file_name) + width = coco_img['width'] + height = coco_img['height'] + tokens_positive = coco_img['tokens_positive_eval'] + phrases = [caption[i[0][0]:i[0][1]] for i in tokens_positive] + phrase_ids = [] + + instances = [] + annos = self.coco.loadAnns(ann_ids) + for anno in annos: + instance = { + 'bbox': [ + anno['bbox'][0], anno['bbox'][1], + anno['bbox'][0] + anno['bbox'][2], + anno['bbox'][1] + anno['bbox'][3] + ], + 'bbox_label': + anno['category_id'], + 'ignore_flag': + anno['iscrowd'] + } + phrase_ids.append(anno['phrase_ids']) + instances.append(instance) + + phrase_ids = convert_phrase_ids(phrase_ids) + + data_list.append( + dict( + img_path=img_path, + img_id=img_id, + height=height, + width=width, + instances=instances, + text=caption, + phrase_ids=phrase_ids, + tokens_positive=tokens_positive, + phrases=phrases, + )) + + return data_list diff --git a/mmdetection/mmdet/datasets/isaid.py b/mmdetection/mmdet/datasets/isaid.py new file mode 100644 index 00000000..87067d84 --- /dev/null +++ b/mmdetection/mmdet/datasets/isaid.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import DATASETS +from .coco import CocoDataset + + +@DATASETS.register_module() +class iSAIDDataset(CocoDataset): + """Dataset for iSAID instance segmentation. + + iSAID: A Large-scale Dataset for Instance Segmentation + in Aerial Images. + + For more detail, please refer to "projects/iSAID/README.md" + """ + + METAINFO = dict( + classes=('background', 'ship', 'store_tank', 'baseball_diamond', + 'tennis_court', 'basketball_court', 'Ground_Track_Field', + 'Bridge', 'Large_Vehicle', 'Small_Vehicle', 'Helicopter', + 'Swimming_pool', 'Roundabout', 'Soccer_ball_field', 'plane', + 'Harbor'), + palette=[(0, 0, 0), (0, 0, 63), (0, 63, 63), (0, 63, 0), (0, 63, 127), + (0, 63, 191), (0, 63, 255), (0, 127, 63), (0, 127, 127), + (0, 0, 127), (0, 0, 191), (0, 0, 255), (0, 191, 127), + (0, 127, 191), (0, 127, 255), (0, 100, 155)]) diff --git a/mmdetection/mmdet/datasets/lvis.py b/mmdetection/mmdet/datasets/lvis.py new file mode 100644 index 00000000..b9629f5d --- /dev/null +++ b/mmdetection/mmdet/datasets/lvis.py @@ -0,0 +1,638 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import warnings +from typing import List + +from mmengine.fileio import get_local_path + +from mmdet.registry import DATASETS +from .coco import CocoDataset + + +@DATASETS.register_module() +class LVISV05Dataset(CocoDataset): + """LVIS v0.5 dataset for detection.""" + + METAINFO = { + 'classes': + ('acorn', 'aerosol_can', 'air_conditioner', 'airplane', 'alarm_clock', + 'alcohol', 'alligator', 'almond', 'ambulance', 'amplifier', 'anklet', + 'antenna', 'apple', 'apple_juice', 'applesauce', 'apricot', 'apron', + 'aquarium', 'armband', 'armchair', 'armoire', 'armor', 'artichoke', + 'trash_can', 'ashtray', 'asparagus', 'atomizer', 'avocado', 'award', + 'awning', 'ax', 'baby_buggy', 'basketball_backboard', 'backpack', + 'handbag', 'suitcase', 'bagel', 'bagpipe', 'baguet', 'bait', 'ball', + 'ballet_skirt', 'balloon', 'bamboo', 'banana', 'Band_Aid', 'bandage', + 'bandanna', 'banjo', 'banner', 'barbell', 'barge', 'barrel', + 'barrette', 'barrow', 'baseball_base', 'baseball', 'baseball_bat', + 'baseball_cap', 'baseball_glove', 'basket', 'basketball_hoop', + 'basketball', 'bass_horn', 'bat_(animal)', 'bath_mat', 'bath_towel', + 'bathrobe', 'bathtub', 'batter_(food)', 'battery', 'beachball', + 'bead', 'beaker', 'bean_curd', 'beanbag', 'beanie', 'bear', 'bed', + 'bedspread', 'cow', 'beef_(food)', 'beeper', 'beer_bottle', + 'beer_can', 'beetle', 'bell', 'bell_pepper', 'belt', 'belt_buckle', + 'bench', 'beret', 'bib', 'Bible', 'bicycle', 'visor', 'binder', + 'binoculars', 'bird', 'birdfeeder', 'birdbath', 'birdcage', + 'birdhouse', 'birthday_cake', 'birthday_card', 'biscuit_(bread)', + 'pirate_flag', 'black_sheep', 'blackboard', 'blanket', 'blazer', + 'blender', 'blimp', 'blinker', 'blueberry', 'boar', 'gameboard', + 'boat', 'bobbin', 'bobby_pin', 'boiled_egg', 'bolo_tie', 'deadbolt', + 'bolt', 'bonnet', 'book', 'book_bag', 'bookcase', 'booklet', + 'bookmark', 'boom_microphone', 'boot', 'bottle', 'bottle_opener', + 'bouquet', 'bow_(weapon)', 'bow_(decorative_ribbons)', 'bow-tie', + 'bowl', 'pipe_bowl', 'bowler_hat', 'bowling_ball', 'bowling_pin', + 'boxing_glove', 'suspenders', 'bracelet', 'brass_plaque', 'brassiere', + 'bread-bin', 'breechcloth', 'bridal_gown', 'briefcase', + 'bristle_brush', 'broccoli', 'broach', 'broom', 'brownie', + 'brussels_sprouts', 'bubble_gum', 'bucket', 'horse_buggy', 'bull', + 'bulldog', 'bulldozer', 'bullet_train', 'bulletin_board', + 'bulletproof_vest', 'bullhorn', 'corned_beef', 'bun', 'bunk_bed', + 'buoy', 'burrito', 'bus_(vehicle)', 'business_card', 'butcher_knife', + 'butter', 'butterfly', 'button', 'cab_(taxi)', 'cabana', 'cabin_car', + 'cabinet', 'locker', 'cake', 'calculator', 'calendar', 'calf', + 'camcorder', 'camel', 'camera', 'camera_lens', 'camper_(vehicle)', + 'can', 'can_opener', 'candelabrum', 'candle', 'candle_holder', + 'candy_bar', 'candy_cane', 'walking_cane', 'canister', 'cannon', + 'canoe', 'cantaloup', 'canteen', 'cap_(headwear)', 'bottle_cap', + 'cape', 'cappuccino', 'car_(automobile)', 'railcar_(part_of_a_train)', + 'elevator_car', 'car_battery', 'identity_card', 'card', 'cardigan', + 'cargo_ship', 'carnation', 'horse_carriage', 'carrot', 'tote_bag', + 'cart', 'carton', 'cash_register', 'casserole', 'cassette', 'cast', + 'cat', 'cauliflower', 'caviar', 'cayenne_(spice)', 'CD_player', + 'celery', 'cellular_telephone', 'chain_mail', 'chair', + 'chaise_longue', 'champagne', 'chandelier', 'chap', 'checkbook', + 'checkerboard', 'cherry', 'chessboard', + 'chest_of_drawers_(furniture)', 'chicken_(animal)', 'chicken_wire', + 'chickpea', 'Chihuahua', 'chili_(vegetable)', 'chime', 'chinaware', + 'crisp_(potato_chip)', 'poker_chip', 'chocolate_bar', + 'chocolate_cake', 'chocolate_milk', 'chocolate_mousse', 'choker', + 'chopping_board', 'chopstick', 'Christmas_tree', 'slide', 'cider', + 'cigar_box', 'cigarette', 'cigarette_case', 'cistern', 'clarinet', + 'clasp', 'cleansing_agent', 'clementine', 'clip', 'clipboard', + 'clock', 'clock_tower', 'clothes_hamper', 'clothespin', 'clutch_bag', + 'coaster', 'coat', 'coat_hanger', 'coatrack', 'cock', 'coconut', + 'coffee_filter', 'coffee_maker', 'coffee_table', 'coffeepot', 'coil', + 'coin', 'colander', 'coleslaw', 'coloring_material', + 'combination_lock', 'pacifier', 'comic_book', 'computer_keyboard', + 'concrete_mixer', 'cone', 'control', 'convertible_(automobile)', + 'sofa_bed', 'cookie', 'cookie_jar', 'cooking_utensil', + 'cooler_(for_food)', 'cork_(bottle_plug)', 'corkboard', 'corkscrew', + 'edible_corn', 'cornbread', 'cornet', 'cornice', 'cornmeal', 'corset', + 'romaine_lettuce', 'costume', 'cougar', 'coverall', 'cowbell', + 'cowboy_hat', 'crab_(animal)', 'cracker', 'crape', 'crate', 'crayon', + 'cream_pitcher', 'credit_card', 'crescent_roll', 'crib', 'crock_pot', + 'crossbar', 'crouton', 'crow', 'crown', 'crucifix', 'cruise_ship', + 'police_cruiser', 'crumb', 'crutch', 'cub_(animal)', 'cube', + 'cucumber', 'cufflink', 'cup', 'trophy_cup', 'cupcake', 'hair_curler', + 'curling_iron', 'curtain', 'cushion', 'custard', 'cutting_tool', + 'cylinder', 'cymbal', 'dachshund', 'dagger', 'dartboard', + 'date_(fruit)', 'deck_chair', 'deer', 'dental_floss', 'desk', + 'detergent', 'diaper', 'diary', 'die', 'dinghy', 'dining_table', + 'tux', 'dish', 'dish_antenna', 'dishrag', 'dishtowel', 'dishwasher', + 'dishwasher_detergent', 'diskette', 'dispenser', 'Dixie_cup', 'dog', + 'dog_collar', 'doll', 'dollar', 'dolphin', 'domestic_ass', 'eye_mask', + 'doorbell', 'doorknob', 'doormat', 'doughnut', 'dove', 'dragonfly', + 'drawer', 'underdrawers', 'dress', 'dress_hat', 'dress_suit', + 'dresser', 'drill', 'drinking_fountain', 'drone', 'dropper', + 'drum_(musical_instrument)', 'drumstick', 'duck', 'duckling', + 'duct_tape', 'duffel_bag', 'dumbbell', 'dumpster', 'dustpan', + 'Dutch_oven', 'eagle', 'earphone', 'earplug', 'earring', 'easel', + 'eclair', 'eel', 'egg', 'egg_roll', 'egg_yolk', 'eggbeater', + 'eggplant', 'electric_chair', 'refrigerator', 'elephant', 'elk', + 'envelope', 'eraser', 'escargot', 'eyepatch', 'falcon', 'fan', + 'faucet', 'fedora', 'ferret', 'Ferris_wheel', 'ferry', 'fig_(fruit)', + 'fighter_jet', 'figurine', 'file_cabinet', 'file_(tool)', + 'fire_alarm', 'fire_engine', 'fire_extinguisher', 'fire_hose', + 'fireplace', 'fireplug', 'fish', 'fish_(food)', 'fishbowl', + 'fishing_boat', 'fishing_rod', 'flag', 'flagpole', 'flamingo', + 'flannel', 'flash', 'flashlight', 'fleece', 'flip-flop_(sandal)', + 'flipper_(footwear)', 'flower_arrangement', 'flute_glass', 'foal', + 'folding_chair', 'food_processor', 'football_(American)', + 'football_helmet', 'footstool', 'fork', 'forklift', 'freight_car', + 'French_toast', 'freshener', 'frisbee', 'frog', 'fruit_juice', + 'fruit_salad', 'frying_pan', 'fudge', 'funnel', 'futon', 'gag', + 'garbage', 'garbage_truck', 'garden_hose', 'gargle', 'gargoyle', + 'garlic', 'gasmask', 'gazelle', 'gelatin', 'gemstone', 'giant_panda', + 'gift_wrap', 'ginger', 'giraffe', 'cincture', + 'glass_(drink_container)', 'globe', 'glove', 'goat', 'goggles', + 'goldfish', 'golf_club', 'golfcart', 'gondola_(boat)', 'goose', + 'gorilla', 'gourd', 'surgical_gown', 'grape', 'grasshopper', 'grater', + 'gravestone', 'gravy_boat', 'green_bean', 'green_onion', 'griddle', + 'grillroom', 'grinder_(tool)', 'grits', 'grizzly', 'grocery_bag', + 'guacamole', 'guitar', 'gull', 'gun', 'hair_spray', 'hairbrush', + 'hairnet', 'hairpin', 'ham', 'hamburger', 'hammer', 'hammock', + 'hamper', 'hamster', 'hair_dryer', 'hand_glass', 'hand_towel', + 'handcart', 'handcuff', 'handkerchief', 'handle', 'handsaw', + 'hardback_book', 'harmonium', 'hat', 'hatbox', 'hatch', 'veil', + 'headband', 'headboard', 'headlight', 'headscarf', 'headset', + 'headstall_(for_horses)', 'hearing_aid', 'heart', 'heater', + 'helicopter', 'helmet', 'heron', 'highchair', 'hinge', 'hippopotamus', + 'hockey_stick', 'hog', 'home_plate_(baseball)', 'honey', 'fume_hood', + 'hook', 'horse', 'hose', 'hot-air_balloon', 'hotplate', 'hot_sauce', + 'hourglass', 'houseboat', 'hummingbird', 'hummus', 'polar_bear', + 'icecream', 'popsicle', 'ice_maker', 'ice_pack', 'ice_skate', + 'ice_tea', 'igniter', 'incense', 'inhaler', 'iPod', + 'iron_(for_clothing)', 'ironing_board', 'jacket', 'jam', 'jean', + 'jeep', 'jelly_bean', 'jersey', 'jet_plane', 'jewelry', 'joystick', + 'jumpsuit', 'kayak', 'keg', 'kennel', 'kettle', 'key', 'keycard', + 'kilt', 'kimono', 'kitchen_sink', 'kitchen_table', 'kite', 'kitten', + 'kiwi_fruit', 'knee_pad', 'knife', 'knight_(chess_piece)', + 'knitting_needle', 'knob', 'knocker_(on_a_door)', 'koala', 'lab_coat', + 'ladder', 'ladle', 'ladybug', 'lamb_(animal)', 'lamb-chop', 'lamp', + 'lamppost', 'lampshade', 'lantern', 'lanyard', 'laptop_computer', + 'lasagna', 'latch', 'lawn_mower', 'leather', 'legging_(clothing)', + 'Lego', 'lemon', 'lemonade', 'lettuce', 'license_plate', 'life_buoy', + 'life_jacket', 'lightbulb', 'lightning_rod', 'lime', 'limousine', + 'linen_paper', 'lion', 'lip_balm', 'lipstick', 'liquor', 'lizard', + 'Loafer_(type_of_shoe)', 'log', 'lollipop', 'lotion', + 'speaker_(stereo_equipment)', 'loveseat', 'machine_gun', 'magazine', + 'magnet', 'mail_slot', 'mailbox_(at_home)', 'mallet', 'mammoth', + 'mandarin_orange', 'manger', 'manhole', 'map', 'marker', 'martini', + 'mascot', 'mashed_potato', 'masher', 'mask', 'mast', + 'mat_(gym_equipment)', 'matchbox', 'mattress', 'measuring_cup', + 'measuring_stick', 'meatball', 'medicine', 'melon', 'microphone', + 'microscope', 'microwave_oven', 'milestone', 'milk', 'minivan', + 'mint_candy', 'mirror', 'mitten', 'mixer_(kitchen_tool)', 'money', + 'monitor_(computer_equipment) computer_monitor', 'monkey', 'motor', + 'motor_scooter', 'motor_vehicle', 'motorboat', 'motorcycle', + 'mound_(baseball)', 'mouse_(animal_rodent)', + 'mouse_(computer_equipment)', 'mousepad', 'muffin', 'mug', 'mushroom', + 'music_stool', 'musical_instrument', 'nailfile', 'nameplate', + 'napkin', 'neckerchief', 'necklace', 'necktie', 'needle', 'nest', + 'newsstand', 'nightshirt', 'nosebag_(for_animals)', + 'noseband_(for_animals)', 'notebook', 'notepad', 'nut', 'nutcracker', + 'oar', 'octopus_(food)', 'octopus_(animal)', 'oil_lamp', 'olive_oil', + 'omelet', 'onion', 'orange_(fruit)', 'orange_juice', 'oregano', + 'ostrich', 'ottoman', 'overalls_(clothing)', 'owl', 'packet', + 'inkpad', 'pad', 'paddle', 'padlock', 'paintbox', 'paintbrush', + 'painting', 'pajamas', 'palette', 'pan_(for_cooking)', + 'pan_(metal_container)', 'pancake', 'pantyhose', 'papaya', + 'paperclip', 'paper_plate', 'paper_towel', 'paperback_book', + 'paperweight', 'parachute', 'parakeet', 'parasail_(sports)', + 'parchment', 'parka', 'parking_meter', 'parrot', + 'passenger_car_(part_of_a_train)', 'passenger_ship', 'passport', + 'pastry', 'patty_(food)', 'pea_(food)', 'peach', 'peanut_butter', + 'pear', 'peeler_(tool_for_fruit_and_vegetables)', 'pegboard', + 'pelican', 'pen', 'pencil', 'pencil_box', 'pencil_sharpener', + 'pendulum', 'penguin', 'pennant', 'penny_(coin)', 'pepper', + 'pepper_mill', 'perfume', 'persimmon', 'baby', 'pet', 'petfood', + 'pew_(church_bench)', 'phonebook', 'phonograph_record', 'piano', + 'pickle', 'pickup_truck', 'pie', 'pigeon', 'piggy_bank', 'pillow', + 'pin_(non_jewelry)', 'pineapple', 'pinecone', 'ping-pong_ball', + 'pinwheel', 'tobacco_pipe', 'pipe', 'pistol', 'pita_(bread)', + 'pitcher_(vessel_for_liquid)', 'pitchfork', 'pizza', 'place_mat', + 'plate', 'platter', 'playing_card', 'playpen', 'pliers', + 'plow_(farm_equipment)', 'pocket_watch', 'pocketknife', + 'poker_(fire_stirring_tool)', 'pole', 'police_van', 'polo_shirt', + 'poncho', 'pony', 'pool_table', 'pop_(soda)', 'portrait', + 'postbox_(public)', 'postcard', 'poster', 'pot', 'flowerpot', + 'potato', 'potholder', 'pottery', 'pouch', 'power_shovel', 'prawn', + 'printer', 'projectile_(weapon)', 'projector', 'propeller', 'prune', + 'pudding', 'puffer_(fish)', 'puffin', 'pug-dog', 'pumpkin', 'puncher', + 'puppet', 'puppy', 'quesadilla', 'quiche', 'quilt', 'rabbit', + 'race_car', 'racket', 'radar', 'radiator', 'radio_receiver', 'radish', + 'raft', 'rag_doll', 'raincoat', 'ram_(animal)', 'raspberry', 'rat', + 'razorblade', 'reamer_(juicer)', 'rearview_mirror', 'receipt', + 'recliner', 'record_player', 'red_cabbage', 'reflector', + 'remote_control', 'rhinoceros', 'rib_(food)', 'rifle', 'ring', + 'river_boat', 'road_map', 'robe', 'rocking_chair', 'roller_skate', + 'Rollerblade', 'rolling_pin', 'root_beer', + 'router_(computer_equipment)', 'rubber_band', 'runner_(carpet)', + 'plastic_bag', 'saddle_(on_an_animal)', 'saddle_blanket', 'saddlebag', + 'safety_pin', 'sail', 'salad', 'salad_plate', 'salami', + 'salmon_(fish)', 'salmon_(food)', 'salsa', 'saltshaker', + 'sandal_(type_of_shoe)', 'sandwich', 'satchel', 'saucepan', 'saucer', + 'sausage', 'sawhorse', 'saxophone', 'scale_(measuring_instrument)', + 'scarecrow', 'scarf', 'school_bus', 'scissors', 'scoreboard', + 'scrambled_eggs', 'scraper', 'scratcher', 'screwdriver', + 'scrubbing_brush', 'sculpture', 'seabird', 'seahorse', 'seaplane', + 'seashell', 'seedling', 'serving_dish', 'sewing_machine', 'shaker', + 'shampoo', 'shark', 'sharpener', 'Sharpie', 'shaver_(electric)', + 'shaving_cream', 'shawl', 'shears', 'sheep', 'shepherd_dog', + 'sherbert', 'shield', 'shirt', 'shoe', 'shopping_bag', + 'shopping_cart', 'short_pants', 'shot_glass', 'shoulder_bag', + 'shovel', 'shower_head', 'shower_curtain', 'shredder_(for_paper)', + 'sieve', 'signboard', 'silo', 'sink', 'skateboard', 'skewer', 'ski', + 'ski_boot', 'ski_parka', 'ski_pole', 'skirt', 'sled', 'sleeping_bag', + 'sling_(bandage)', 'slipper_(footwear)', 'smoothie', 'snake', + 'snowboard', 'snowman', 'snowmobile', 'soap', 'soccer_ball', 'sock', + 'soda_fountain', 'carbonated_water', 'sofa', 'softball', + 'solar_array', 'sombrero', 'soup', 'soup_bowl', 'soupspoon', + 'sour_cream', 'soya_milk', 'space_shuttle', 'sparkler_(fireworks)', + 'spatula', 'spear', 'spectacles', 'spice_rack', 'spider', 'sponge', + 'spoon', 'sportswear', 'spotlight', 'squirrel', + 'stapler_(stapling_machine)', 'starfish', 'statue_(sculpture)', + 'steak_(food)', 'steak_knife', 'steamer_(kitchen_appliance)', + 'steering_wheel', 'stencil', 'stepladder', 'step_stool', + 'stereo_(sound_system)', 'stew', 'stirrer', 'stirrup', + 'stockings_(leg_wear)', 'stool', 'stop_sign', 'brake_light', 'stove', + 'strainer', 'strap', 'straw_(for_drinking)', 'strawberry', + 'street_sign', 'streetlight', 'string_cheese', 'stylus', 'subwoofer', + 'sugar_bowl', 'sugarcane_(plant)', 'suit_(clothing)', 'sunflower', + 'sunglasses', 'sunhat', 'sunscreen', 'surfboard', 'sushi', 'mop', + 'sweat_pants', 'sweatband', 'sweater', 'sweatshirt', 'sweet_potato', + 'swimsuit', 'sword', 'syringe', 'Tabasco_sauce', 'table-tennis_table', + 'table', 'table_lamp', 'tablecloth', 'tachometer', 'taco', 'tag', + 'taillight', 'tambourine', 'army_tank', 'tank_(storage_vessel)', + 'tank_top_(clothing)', 'tape_(sticky_cloth_or_paper)', 'tape_measure', + 'tapestry', 'tarp', 'tartan', 'tassel', 'tea_bag', 'teacup', + 'teakettle', 'teapot', 'teddy_bear', 'telephone', 'telephone_booth', + 'telephone_pole', 'telephoto_lens', 'television_camera', + 'television_set', 'tennis_ball', 'tennis_racket', 'tequila', + 'thermometer', 'thermos_bottle', 'thermostat', 'thimble', 'thread', + 'thumbtack', 'tiara', 'tiger', 'tights_(clothing)', 'timer', + 'tinfoil', 'tinsel', 'tissue_paper', 'toast_(food)', 'toaster', + 'toaster_oven', 'toilet', 'toilet_tissue', 'tomato', 'tongs', + 'toolbox', 'toothbrush', 'toothpaste', 'toothpick', 'cover', + 'tortilla', 'tow_truck', 'towel', 'towel_rack', 'toy', + 'tractor_(farm_equipment)', 'traffic_light', 'dirt_bike', + 'trailer_truck', 'train_(railroad_vehicle)', 'trampoline', 'tray', + 'tree_house', 'trench_coat', 'triangle_(musical_instrument)', + 'tricycle', 'tripod', 'trousers', 'truck', 'truffle_(chocolate)', + 'trunk', 'vat', 'turban', 'turkey_(bird)', 'turkey_(food)', 'turnip', + 'turtle', 'turtleneck_(clothing)', 'typewriter', 'umbrella', + 'underwear', 'unicycle', 'urinal', 'urn', 'vacuum_cleaner', 'valve', + 'vase', 'vending_machine', 'vent', 'videotape', 'vinegar', 'violin', + 'vodka', 'volleyball', 'vulture', 'waffle', 'waffle_iron', 'wagon', + 'wagon_wheel', 'walking_stick', 'wall_clock', 'wall_socket', 'wallet', + 'walrus', 'wardrobe', 'wasabi', 'automatic_washer', 'watch', + 'water_bottle', 'water_cooler', 'water_faucet', 'water_filter', + 'water_heater', 'water_jug', 'water_gun', 'water_scooter', + 'water_ski', 'water_tower', 'watering_can', 'watermelon', + 'weathervane', 'webcam', 'wedding_cake', 'wedding_ring', 'wet_suit', + 'wheel', 'wheelchair', 'whipped_cream', 'whiskey', 'whistle', 'wick', + 'wig', 'wind_chime', 'windmill', 'window_box_(for_plants)', + 'windshield_wiper', 'windsock', 'wine_bottle', 'wine_bucket', + 'wineglass', 'wing_chair', 'blinder_(for_horses)', 'wok', 'wolf', + 'wooden_spoon', 'wreath', 'wrench', 'wristband', 'wristlet', 'yacht', + 'yak', 'yogurt', 'yoke_(animal_equipment)', 'zebra', 'zucchini'), + 'palette': + None + } + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ # noqa: E501 + try: + import lvis + if getattr(lvis, '__version__', '0') >= '10.5.3': + warnings.warn( + 'mmlvis is deprecated, please install official lvis-api by "pip install git+https://github.com/lvis-dataset/lvis-api.git"', # noqa: E501 + UserWarning) + from lvis import LVIS + except ImportError: + raise ImportError( + 'Package lvis is not installed. Please run "pip install git+https://github.com/lvis-dataset/lvis-api.git".' # noqa: E501 + ) + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + self.lvis = LVIS(local_path) + self.cat_ids = self.lvis.get_cat_ids() + self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)} + self.cat_img_map = copy.deepcopy(self.lvis.cat_img_map) + + img_ids = self.lvis.get_img_ids() + data_list = [] + total_ann_ids = [] + for img_id in img_ids: + raw_img_info = self.lvis.load_imgs([img_id])[0] + raw_img_info['img_id'] = img_id + if raw_img_info['file_name'].startswith('COCO'): + # Convert form the COCO 2014 file naming convention of + # COCO_[train/val/test]2014_000000000000.jpg to the 2017 + # naming convention of 000000000000.jpg + # (LVIS v1 will fix this naming issue) + raw_img_info['file_name'] = raw_img_info['file_name'][-16:] + ann_ids = self.lvis.get_ann_ids(img_ids=[img_id]) + raw_ann_info = self.lvis.load_anns(ann_ids) + total_ann_ids.extend(ann_ids) + + parsed_data_info = self.parse_data_info({ + 'raw_ann_info': + raw_ann_info, + 'raw_img_info': + raw_img_info + }) + data_list.append(parsed_data_info) + if self.ANN_ID_UNIQUE: + assert len(set(total_ann_ids)) == len( + total_ann_ids + ), f"Annotation ids in '{self.ann_file}' are not unique!" + + del self.lvis + + return data_list + + +LVISDataset = LVISV05Dataset +DATASETS.register_module(name='LVISDataset', module=LVISDataset) + + +@DATASETS.register_module() +class LVISV1Dataset(LVISDataset): + """LVIS v1 dataset for detection.""" + + METAINFO = { + 'classes': + ('aerosol_can', 'air_conditioner', 'airplane', 'alarm_clock', + 'alcohol', 'alligator', 'almond', 'ambulance', 'amplifier', 'anklet', + 'antenna', 'apple', 'applesauce', 'apricot', 'apron', 'aquarium', + 'arctic_(type_of_shoe)', 'armband', 'armchair', 'armoire', 'armor', + 'artichoke', 'trash_can', 'ashtray', 'asparagus', 'atomizer', + 'avocado', 'award', 'awning', 'ax', 'baboon', 'baby_buggy', + 'basketball_backboard', 'backpack', 'handbag', 'suitcase', 'bagel', + 'bagpipe', 'baguet', 'bait', 'ball', 'ballet_skirt', 'balloon', + 'bamboo', 'banana', 'Band_Aid', 'bandage', 'bandanna', 'banjo', + 'banner', 'barbell', 'barge', 'barrel', 'barrette', 'barrow', + 'baseball_base', 'baseball', 'baseball_bat', 'baseball_cap', + 'baseball_glove', 'basket', 'basketball', 'bass_horn', 'bat_(animal)', + 'bath_mat', 'bath_towel', 'bathrobe', 'bathtub', 'batter_(food)', + 'battery', 'beachball', 'bead', 'bean_curd', 'beanbag', 'beanie', + 'bear', 'bed', 'bedpan', 'bedspread', 'cow', 'beef_(food)', 'beeper', + 'beer_bottle', 'beer_can', 'beetle', 'bell', 'bell_pepper', 'belt', + 'belt_buckle', 'bench', 'beret', 'bib', 'Bible', 'bicycle', 'visor', + 'billboard', 'binder', 'binoculars', 'bird', 'birdfeeder', 'birdbath', + 'birdcage', 'birdhouse', 'birthday_cake', 'birthday_card', + 'pirate_flag', 'black_sheep', 'blackberry', 'blackboard', 'blanket', + 'blazer', 'blender', 'blimp', 'blinker', 'blouse', 'blueberry', + 'gameboard', 'boat', 'bob', 'bobbin', 'bobby_pin', 'boiled_egg', + 'bolo_tie', 'deadbolt', 'bolt', 'bonnet', 'book', 'bookcase', + 'booklet', 'bookmark', 'boom_microphone', 'boot', 'bottle', + 'bottle_opener', 'bouquet', 'bow_(weapon)', + 'bow_(decorative_ribbons)', 'bow-tie', 'bowl', 'pipe_bowl', + 'bowler_hat', 'bowling_ball', 'box', 'boxing_glove', 'suspenders', + 'bracelet', 'brass_plaque', 'brassiere', 'bread-bin', 'bread', + 'breechcloth', 'bridal_gown', 'briefcase', 'broccoli', 'broach', + 'broom', 'brownie', 'brussels_sprouts', 'bubble_gum', 'bucket', + 'horse_buggy', 'bull', 'bulldog', 'bulldozer', 'bullet_train', + 'bulletin_board', 'bulletproof_vest', 'bullhorn', 'bun', 'bunk_bed', + 'buoy', 'burrito', 'bus_(vehicle)', 'business_card', 'butter', + 'butterfly', 'button', 'cab_(taxi)', 'cabana', 'cabin_car', 'cabinet', + 'locker', 'cake', 'calculator', 'calendar', 'calf', 'camcorder', + 'camel', 'camera', 'camera_lens', 'camper_(vehicle)', 'can', + 'can_opener', 'candle', 'candle_holder', 'candy_bar', 'candy_cane', + 'walking_cane', 'canister', 'canoe', 'cantaloup', 'canteen', + 'cap_(headwear)', 'bottle_cap', 'cape', 'cappuccino', + 'car_(automobile)', 'railcar_(part_of_a_train)', 'elevator_car', + 'car_battery', 'identity_card', 'card', 'cardigan', 'cargo_ship', + 'carnation', 'horse_carriage', 'carrot', 'tote_bag', 'cart', 'carton', + 'cash_register', 'casserole', 'cassette', 'cast', 'cat', + 'cauliflower', 'cayenne_(spice)', 'CD_player', 'celery', + 'cellular_telephone', 'chain_mail', 'chair', 'chaise_longue', + 'chalice', 'chandelier', 'chap', 'checkbook', 'checkerboard', + 'cherry', 'chessboard', 'chicken_(animal)', 'chickpea', + 'chili_(vegetable)', 'chime', 'chinaware', 'crisp_(potato_chip)', + 'poker_chip', 'chocolate_bar', 'chocolate_cake', 'chocolate_milk', + 'chocolate_mousse', 'choker', 'chopping_board', 'chopstick', + 'Christmas_tree', 'slide', 'cider', 'cigar_box', 'cigarette', + 'cigarette_case', 'cistern', 'clarinet', 'clasp', 'cleansing_agent', + 'cleat_(for_securing_rope)', 'clementine', 'clip', 'clipboard', + 'clippers_(for_plants)', 'cloak', 'clock', 'clock_tower', + 'clothes_hamper', 'clothespin', 'clutch_bag', 'coaster', 'coat', + 'coat_hanger', 'coatrack', 'cock', 'cockroach', 'cocoa_(beverage)', + 'coconut', 'coffee_maker', 'coffee_table', 'coffeepot', 'coil', + 'coin', 'colander', 'coleslaw', 'coloring_material', + 'combination_lock', 'pacifier', 'comic_book', 'compass', + 'computer_keyboard', 'condiment', 'cone', 'control', + 'convertible_(automobile)', 'sofa_bed', 'cooker', 'cookie', + 'cooking_utensil', 'cooler_(for_food)', 'cork_(bottle_plug)', + 'corkboard', 'corkscrew', 'edible_corn', 'cornbread', 'cornet', + 'cornice', 'cornmeal', 'corset', 'costume', 'cougar', 'coverall', + 'cowbell', 'cowboy_hat', 'crab_(animal)', 'crabmeat', 'cracker', + 'crape', 'crate', 'crayon', 'cream_pitcher', 'crescent_roll', 'crib', + 'crock_pot', 'crossbar', 'crouton', 'crow', 'crowbar', 'crown', + 'crucifix', 'cruise_ship', 'police_cruiser', 'crumb', 'crutch', + 'cub_(animal)', 'cube', 'cucumber', 'cufflink', 'cup', 'trophy_cup', + 'cupboard', 'cupcake', 'hair_curler', 'curling_iron', 'curtain', + 'cushion', 'cylinder', 'cymbal', 'dagger', 'dalmatian', 'dartboard', + 'date_(fruit)', 'deck_chair', 'deer', 'dental_floss', 'desk', + 'detergent', 'diaper', 'diary', 'die', 'dinghy', 'dining_table', + 'tux', 'dish', 'dish_antenna', 'dishrag', 'dishtowel', 'dishwasher', + 'dishwasher_detergent', 'dispenser', 'diving_board', 'Dixie_cup', + 'dog', 'dog_collar', 'doll', 'dollar', 'dollhouse', 'dolphin', + 'domestic_ass', 'doorknob', 'doormat', 'doughnut', 'dove', + 'dragonfly', 'drawer', 'underdrawers', 'dress', 'dress_hat', + 'dress_suit', 'dresser', 'drill', 'drone', 'dropper', + 'drum_(musical_instrument)', 'drumstick', 'duck', 'duckling', + 'duct_tape', 'duffel_bag', 'dumbbell', 'dumpster', 'dustpan', 'eagle', + 'earphone', 'earplug', 'earring', 'easel', 'eclair', 'eel', 'egg', + 'egg_roll', 'egg_yolk', 'eggbeater', 'eggplant', 'electric_chair', + 'refrigerator', 'elephant', 'elk', 'envelope', 'eraser', 'escargot', + 'eyepatch', 'falcon', 'fan', 'faucet', 'fedora', 'ferret', + 'Ferris_wheel', 'ferry', 'fig_(fruit)', 'fighter_jet', 'figurine', + 'file_cabinet', 'file_(tool)', 'fire_alarm', 'fire_engine', + 'fire_extinguisher', 'fire_hose', 'fireplace', 'fireplug', + 'first-aid_kit', 'fish', 'fish_(food)', 'fishbowl', 'fishing_rod', + 'flag', 'flagpole', 'flamingo', 'flannel', 'flap', 'flash', + 'flashlight', 'fleece', 'flip-flop_(sandal)', 'flipper_(footwear)', + 'flower_arrangement', 'flute_glass', 'foal', 'folding_chair', + 'food_processor', 'football_(American)', 'football_helmet', + 'footstool', 'fork', 'forklift', 'freight_car', 'French_toast', + 'freshener', 'frisbee', 'frog', 'fruit_juice', 'frying_pan', 'fudge', + 'funnel', 'futon', 'gag', 'garbage', 'garbage_truck', 'garden_hose', + 'gargle', 'gargoyle', 'garlic', 'gasmask', 'gazelle', 'gelatin', + 'gemstone', 'generator', 'giant_panda', 'gift_wrap', 'ginger', + 'giraffe', 'cincture', 'glass_(drink_container)', 'globe', 'glove', + 'goat', 'goggles', 'goldfish', 'golf_club', 'golfcart', + 'gondola_(boat)', 'goose', 'gorilla', 'gourd', 'grape', 'grater', + 'gravestone', 'gravy_boat', 'green_bean', 'green_onion', 'griddle', + 'grill', 'grits', 'grizzly', 'grocery_bag', 'guitar', 'gull', 'gun', + 'hairbrush', 'hairnet', 'hairpin', 'halter_top', 'ham', 'hamburger', + 'hammer', 'hammock', 'hamper', 'hamster', 'hair_dryer', 'hand_glass', + 'hand_towel', 'handcart', 'handcuff', 'handkerchief', 'handle', + 'handsaw', 'hardback_book', 'harmonium', 'hat', 'hatbox', 'veil', + 'headband', 'headboard', 'headlight', 'headscarf', 'headset', + 'headstall_(for_horses)', 'heart', 'heater', 'helicopter', 'helmet', + 'heron', 'highchair', 'hinge', 'hippopotamus', 'hockey_stick', 'hog', + 'home_plate_(baseball)', 'honey', 'fume_hood', 'hook', 'hookah', + 'hornet', 'horse', 'hose', 'hot-air_balloon', 'hotplate', 'hot_sauce', + 'hourglass', 'houseboat', 'hummingbird', 'hummus', 'polar_bear', + 'icecream', 'popsicle', 'ice_maker', 'ice_pack', 'ice_skate', + 'igniter', 'inhaler', 'iPod', 'iron_(for_clothing)', 'ironing_board', + 'jacket', 'jam', 'jar', 'jean', 'jeep', 'jelly_bean', 'jersey', + 'jet_plane', 'jewel', 'jewelry', 'joystick', 'jumpsuit', 'kayak', + 'keg', 'kennel', 'kettle', 'key', 'keycard', 'kilt', 'kimono', + 'kitchen_sink', 'kitchen_table', 'kite', 'kitten', 'kiwi_fruit', + 'knee_pad', 'knife', 'knitting_needle', 'knob', 'knocker_(on_a_door)', + 'koala', 'lab_coat', 'ladder', 'ladle', 'ladybug', 'lamb_(animal)', + 'lamb-chop', 'lamp', 'lamppost', 'lampshade', 'lantern', 'lanyard', + 'laptop_computer', 'lasagna', 'latch', 'lawn_mower', 'leather', + 'legging_(clothing)', 'Lego', 'legume', 'lemon', 'lemonade', + 'lettuce', 'license_plate', 'life_buoy', 'life_jacket', 'lightbulb', + 'lightning_rod', 'lime', 'limousine', 'lion', 'lip_balm', 'liquor', + 'lizard', 'log', 'lollipop', 'speaker_(stereo_equipment)', 'loveseat', + 'machine_gun', 'magazine', 'magnet', 'mail_slot', 'mailbox_(at_home)', + 'mallard', 'mallet', 'mammoth', 'manatee', 'mandarin_orange', + 'manger', 'manhole', 'map', 'marker', 'martini', 'mascot', + 'mashed_potato', 'masher', 'mask', 'mast', 'mat_(gym_equipment)', + 'matchbox', 'mattress', 'measuring_cup', 'measuring_stick', + 'meatball', 'medicine', 'melon', 'microphone', 'microscope', + 'microwave_oven', 'milestone', 'milk', 'milk_can', 'milkshake', + 'minivan', 'mint_candy', 'mirror', 'mitten', 'mixer_(kitchen_tool)', + 'money', 'monitor_(computer_equipment) computer_monitor', 'monkey', + 'motor', 'motor_scooter', 'motor_vehicle', 'motorcycle', + 'mound_(baseball)', 'mouse_(computer_equipment)', 'mousepad', + 'muffin', 'mug', 'mushroom', 'music_stool', 'musical_instrument', + 'nailfile', 'napkin', 'neckerchief', 'necklace', 'necktie', 'needle', + 'nest', 'newspaper', 'newsstand', 'nightshirt', + 'nosebag_(for_animals)', 'noseband_(for_animals)', 'notebook', + 'notepad', 'nut', 'nutcracker', 'oar', 'octopus_(food)', + 'octopus_(animal)', 'oil_lamp', 'olive_oil', 'omelet', 'onion', + 'orange_(fruit)', 'orange_juice', 'ostrich', 'ottoman', 'oven', + 'overalls_(clothing)', 'owl', 'packet', 'inkpad', 'pad', 'paddle', + 'padlock', 'paintbrush', 'painting', 'pajamas', 'palette', + 'pan_(for_cooking)', 'pan_(metal_container)', 'pancake', 'pantyhose', + 'papaya', 'paper_plate', 'paper_towel', 'paperback_book', + 'paperweight', 'parachute', 'parakeet', 'parasail_(sports)', + 'parasol', 'parchment', 'parka', 'parking_meter', 'parrot', + 'passenger_car_(part_of_a_train)', 'passenger_ship', 'passport', + 'pastry', 'patty_(food)', 'pea_(food)', 'peach', 'peanut_butter', + 'pear', 'peeler_(tool_for_fruit_and_vegetables)', 'wooden_leg', + 'pegboard', 'pelican', 'pen', 'pencil', 'pencil_box', + 'pencil_sharpener', 'pendulum', 'penguin', 'pennant', 'penny_(coin)', + 'pepper', 'pepper_mill', 'perfume', 'persimmon', 'person', 'pet', + 'pew_(church_bench)', 'phonebook', 'phonograph_record', 'piano', + 'pickle', 'pickup_truck', 'pie', 'pigeon', 'piggy_bank', 'pillow', + 'pin_(non_jewelry)', 'pineapple', 'pinecone', 'ping-pong_ball', + 'pinwheel', 'tobacco_pipe', 'pipe', 'pistol', 'pita_(bread)', + 'pitcher_(vessel_for_liquid)', 'pitchfork', 'pizza', 'place_mat', + 'plate', 'platter', 'playpen', 'pliers', 'plow_(farm_equipment)', + 'plume', 'pocket_watch', 'pocketknife', 'poker_(fire_stirring_tool)', + 'pole', 'polo_shirt', 'poncho', 'pony', 'pool_table', 'pop_(soda)', + 'postbox_(public)', 'postcard', 'poster', 'pot', 'flowerpot', + 'potato', 'potholder', 'pottery', 'pouch', 'power_shovel', 'prawn', + 'pretzel', 'printer', 'projectile_(weapon)', 'projector', 'propeller', + 'prune', 'pudding', 'puffer_(fish)', 'puffin', 'pug-dog', 'pumpkin', + 'puncher', 'puppet', 'puppy', 'quesadilla', 'quiche', 'quilt', + 'rabbit', 'race_car', 'racket', 'radar', 'radiator', 'radio_receiver', + 'radish', 'raft', 'rag_doll', 'raincoat', 'ram_(animal)', 'raspberry', + 'rat', 'razorblade', 'reamer_(juicer)', 'rearview_mirror', 'receipt', + 'recliner', 'record_player', 'reflector', 'remote_control', + 'rhinoceros', 'rib_(food)', 'rifle', 'ring', 'river_boat', 'road_map', + 'robe', 'rocking_chair', 'rodent', 'roller_skate', 'Rollerblade', + 'rolling_pin', 'root_beer', 'router_(computer_equipment)', + 'rubber_band', 'runner_(carpet)', 'plastic_bag', + 'saddle_(on_an_animal)', 'saddle_blanket', 'saddlebag', 'safety_pin', + 'sail', 'salad', 'salad_plate', 'salami', 'salmon_(fish)', + 'salmon_(food)', 'salsa', 'saltshaker', 'sandal_(type_of_shoe)', + 'sandwich', 'satchel', 'saucepan', 'saucer', 'sausage', 'sawhorse', + 'saxophone', 'scale_(measuring_instrument)', 'scarecrow', 'scarf', + 'school_bus', 'scissors', 'scoreboard', 'scraper', 'screwdriver', + 'scrubbing_brush', 'sculpture', 'seabird', 'seahorse', 'seaplane', + 'seashell', 'sewing_machine', 'shaker', 'shampoo', 'shark', + 'sharpener', 'Sharpie', 'shaver_(electric)', 'shaving_cream', 'shawl', + 'shears', 'sheep', 'shepherd_dog', 'sherbert', 'shield', 'shirt', + 'shoe', 'shopping_bag', 'shopping_cart', 'short_pants', 'shot_glass', + 'shoulder_bag', 'shovel', 'shower_head', 'shower_cap', + 'shower_curtain', 'shredder_(for_paper)', 'signboard', 'silo', 'sink', + 'skateboard', 'skewer', 'ski', 'ski_boot', 'ski_parka', 'ski_pole', + 'skirt', 'skullcap', 'sled', 'sleeping_bag', 'sling_(bandage)', + 'slipper_(footwear)', 'smoothie', 'snake', 'snowboard', 'snowman', + 'snowmobile', 'soap', 'soccer_ball', 'sock', 'sofa', 'softball', + 'solar_array', 'sombrero', 'soup', 'soup_bowl', 'soupspoon', + 'sour_cream', 'soya_milk', 'space_shuttle', 'sparkler_(fireworks)', + 'spatula', 'spear', 'spectacles', 'spice_rack', 'spider', 'crawfish', + 'sponge', 'spoon', 'sportswear', 'spotlight', 'squid_(food)', + 'squirrel', 'stagecoach', 'stapler_(stapling_machine)', 'starfish', + 'statue_(sculpture)', 'steak_(food)', 'steak_knife', 'steering_wheel', + 'stepladder', 'step_stool', 'stereo_(sound_system)', 'stew', + 'stirrer', 'stirrup', 'stool', 'stop_sign', 'brake_light', 'stove', + 'strainer', 'strap', 'straw_(for_drinking)', 'strawberry', + 'street_sign', 'streetlight', 'string_cheese', 'stylus', 'subwoofer', + 'sugar_bowl', 'sugarcane_(plant)', 'suit_(clothing)', 'sunflower', + 'sunglasses', 'sunhat', 'surfboard', 'sushi', 'mop', 'sweat_pants', + 'sweatband', 'sweater', 'sweatshirt', 'sweet_potato', 'swimsuit', + 'sword', 'syringe', 'Tabasco_sauce', 'table-tennis_table', 'table', + 'table_lamp', 'tablecloth', 'tachometer', 'taco', 'tag', 'taillight', + 'tambourine', 'army_tank', 'tank_(storage_vessel)', + 'tank_top_(clothing)', 'tape_(sticky_cloth_or_paper)', 'tape_measure', + 'tapestry', 'tarp', 'tartan', 'tassel', 'tea_bag', 'teacup', + 'teakettle', 'teapot', 'teddy_bear', 'telephone', 'telephone_booth', + 'telephone_pole', 'telephoto_lens', 'television_camera', + 'television_set', 'tennis_ball', 'tennis_racket', 'tequila', + 'thermometer', 'thermos_bottle', 'thermostat', 'thimble', 'thread', + 'thumbtack', 'tiara', 'tiger', 'tights_(clothing)', 'timer', + 'tinfoil', 'tinsel', 'tissue_paper', 'toast_(food)', 'toaster', + 'toaster_oven', 'toilet', 'toilet_tissue', 'tomato', 'tongs', + 'toolbox', 'toothbrush', 'toothpaste', 'toothpick', 'cover', + 'tortilla', 'tow_truck', 'towel', 'towel_rack', 'toy', + 'tractor_(farm_equipment)', 'traffic_light', 'dirt_bike', + 'trailer_truck', 'train_(railroad_vehicle)', 'trampoline', 'tray', + 'trench_coat', 'triangle_(musical_instrument)', 'tricycle', 'tripod', + 'trousers', 'truck', 'truffle_(chocolate)', 'trunk', 'vat', 'turban', + 'turkey_(food)', 'turnip', 'turtle', 'turtleneck_(clothing)', + 'typewriter', 'umbrella', 'underwear', 'unicycle', 'urinal', 'urn', + 'vacuum_cleaner', 'vase', 'vending_machine', 'vent', 'vest', + 'videotape', 'vinegar', 'violin', 'vodka', 'volleyball', 'vulture', + 'waffle', 'waffle_iron', 'wagon', 'wagon_wheel', 'walking_stick', + 'wall_clock', 'wall_socket', 'wallet', 'walrus', 'wardrobe', + 'washbasin', 'automatic_washer', 'watch', 'water_bottle', + 'water_cooler', 'water_faucet', 'water_heater', 'water_jug', + 'water_gun', 'water_scooter', 'water_ski', 'water_tower', + 'watering_can', 'watermelon', 'weathervane', 'webcam', 'wedding_cake', + 'wedding_ring', 'wet_suit', 'wheel', 'wheelchair', 'whipped_cream', + 'whistle', 'wig', 'wind_chime', 'windmill', 'window_box_(for_plants)', + 'windshield_wiper', 'windsock', 'wine_bottle', 'wine_bucket', + 'wineglass', 'blinder_(for_horses)', 'wok', 'wolf', 'wooden_spoon', + 'wreath', 'wrench', 'wristband', 'wristlet', 'yacht', 'yogurt', + 'yoke_(animal_equipment)', 'zebra', 'zucchini'), + 'palette': + None + } + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ # noqa: E501 + try: + import lvis + if getattr(lvis, '__version__', '0') >= '10.5.3': + warnings.warn( + 'mmlvis is deprecated, please install official lvis-api by "pip install git+https://github.com/lvis-dataset/lvis-api.git"', # noqa: E501 + UserWarning) + from lvis import LVIS + except ImportError: + raise ImportError( + 'Package lvis is not installed. Please run "pip install git+https://github.com/lvis-dataset/lvis-api.git".' # noqa: E501 + ) + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + self.lvis = LVIS(local_path) + self.cat_ids = self.lvis.get_cat_ids() + self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)} + self.cat_img_map = copy.deepcopy(self.lvis.cat_img_map) + + img_ids = self.lvis.get_img_ids() + data_list = [] + total_ann_ids = [] + for img_id in img_ids: + raw_img_info = self.lvis.load_imgs([img_id])[0] + raw_img_info['img_id'] = img_id + # coco_url is used in LVISv1 instead of file_name + # e.g. http://images.cocodataset.org/train2017/000000391895.jpg + # train/val split in specified in url + raw_img_info['file_name'] = raw_img_info['coco_url'].replace( + 'http://images.cocodataset.org/', '') + ann_ids = self.lvis.get_ann_ids(img_ids=[img_id]) + raw_ann_info = self.lvis.load_anns(ann_ids) + total_ann_ids.extend(ann_ids) + parsed_data_info = self.parse_data_info({ + 'raw_ann_info': + raw_ann_info, + 'raw_img_info': + raw_img_info + }) + data_list.append(parsed_data_info) + if self.ANN_ID_UNIQUE: + assert len(set(total_ann_ids)) == len( + total_ann_ids + ), f"Annotation ids in '{self.ann_file}' are not unique!" + + del self.lvis + + return data_list diff --git a/mmdetection/mmdet/datasets/mdetr_style_refcoco.py b/mmdetection/mmdet/datasets/mdetr_style_refcoco.py new file mode 100644 index 00000000..cc56dec4 --- /dev/null +++ b/mmdetection/mmdet/datasets/mdetr_style_refcoco.py @@ -0,0 +1,57 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from typing import List + +from mmengine.fileio import get_local_path + +from mmdet.datasets import BaseDetDataset +from mmdet.registry import DATASETS +from .api_wrappers import COCO + + +@DATASETS.register_module() +class MDETRStyleRefCocoDataset(BaseDetDataset): + """RefCOCO dataset. + + Only support evaluation now. + """ + + def load_data_list(self) -> List[dict]: + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + coco = COCO(local_path) + + img_ids = coco.get_img_ids() + + data_infos = [] + for img_id in img_ids: + raw_img_info = coco.load_imgs([img_id])[0] + ann_ids = coco.get_ann_ids(img_ids=[img_id]) + raw_ann_info = coco.load_anns(ann_ids) + + data_info = {} + img_path = osp.join(self.data_prefix['img'], + raw_img_info['file_name']) + data_info['img_path'] = img_path + data_info['img_id'] = img_id + data_info['height'] = raw_img_info['height'] + data_info['width'] = raw_img_info['width'] + data_info['dataset_mode'] = raw_img_info['dataset_name'] + + data_info['text'] = raw_img_info['caption'] + data_info['custom_entities'] = False + data_info['tokens_positive'] = -1 + + instances = [] + for i, ann in enumerate(raw_ann_info): + instance = {} + x1, y1, w, h = ann['bbox'] + bbox = [x1, y1, x1 + w, y1 + h] + instance['bbox'] = bbox + instance['bbox_label'] = ann['category_id'] + instance['ignore_flag'] = 0 + instances.append(instance) + + data_info['instances'] = instances + data_infos.append(data_info) + return data_infos diff --git a/mmdetection/mmdet/datasets/mot_challenge_dataset.py b/mmdetection/mmdet/datasets/mot_challenge_dataset.py new file mode 100644 index 00000000..ffbdc48e --- /dev/null +++ b/mmdetection/mmdet/datasets/mot_challenge_dataset.py @@ -0,0 +1,88 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from typing import List, Union + +from mmdet.registry import DATASETS +from .base_video_dataset import BaseVideoDataset + + +@DATASETS.register_module() +class MOTChallengeDataset(BaseVideoDataset): + """Dataset for MOTChallenge. + + Args: + visibility_thr (float, optional): The minimum visibility + for the objects during training. Default to -1. + """ + + METAINFO = { + 'classes': + ('pedestrian', 'person_on_vehicle', 'car', 'bicycle', 'motorbike', + 'non_mot_vehicle', 'static_person', 'distractor', 'occluder', + 'occluder_on_ground', 'occluder_full', 'reflection', 'crowd') + } + + def __init__(self, visibility_thr: float = -1, *args, **kwargs): + self.visibility_thr = visibility_thr + super().__init__(*args, **kwargs) + + def parse_data_info(self, raw_data_info: dict) -> Union[dict, List[dict]]: + """Parse raw annotation to target format. The difference between this + function and the one in ``BaseVideoDataset`` is that the parsing here + adds ``visibility`` and ``mot_conf``. + + Args: + raw_data_info (dict): Raw data information load from ``ann_file`` + + Returns: + Union[dict, List[dict]]: Parsed annotation. + """ + img_info = raw_data_info['raw_img_info'] + ann_info = raw_data_info['raw_ann_info'] + data_info = {} + + data_info.update(img_info) + if self.data_prefix.get('img_path', None) is not None: + img_path = osp.join(self.data_prefix['img_path'], + img_info['file_name']) + else: + img_path = img_info['file_name'] + data_info['img_path'] = img_path + + instances = [] + for i, ann in enumerate(ann_info): + instance = {} + + if (not self.test_mode) and (ann['visibility'] < + self.visibility_thr): + continue + if ann.get('ignore', False): + continue + x1, y1, w, h = ann['bbox'] + inter_w = max(0, min(x1 + w, img_info['width']) - max(x1, 0)) + inter_h = max(0, min(y1 + h, img_info['height']) - max(y1, 0)) + if inter_w * inter_h == 0: + continue + if ann['area'] <= 0 or w < 1 or h < 1: + continue + if ann['category_id'] not in self.cat_ids: + continue + bbox = [x1, y1, x1 + w, y1 + h] + + if ann.get('iscrowd', False): + instance['ignore_flag'] = 1 + else: + instance['ignore_flag'] = 0 + instance['bbox'] = bbox + instance['bbox_label'] = self.cat2label[ann['category_id']] + instance['instance_id'] = ann['instance_id'] + instance['category_id'] = ann['category_id'] + instance['mot_conf'] = ann['mot_conf'] + instance['visibility'] = ann['visibility'] + if len(instance) > 0: + instances.append(instance) + if not self.test_mode: + assert len(instances) > 0, f'No valid instances found in ' \ + f'image {data_info["img_path"]}!' + data_info['instances'] = instances + return data_info diff --git a/mmdetection/mmdet/datasets/objects365.py b/mmdetection/mmdet/datasets/objects365.py new file mode 100644 index 00000000..e99869bf --- /dev/null +++ b/mmdetection/mmdet/datasets/objects365.py @@ -0,0 +1,284 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +from typing import List + +from mmengine.fileio import get_local_path + +from mmdet.registry import DATASETS +from .api_wrappers import COCO +from .coco import CocoDataset + +# images exist in annotations but not in image folder. +objv2_ignore_list = [ + osp.join('patch16', 'objects365_v2_00908726.jpg'), + osp.join('patch6', 'objects365_v1_00320532.jpg'), + osp.join('patch6', 'objects365_v1_00320534.jpg'), +] + + +@DATASETS.register_module() +class Objects365V1Dataset(CocoDataset): + """Objects365 v1 dataset for detection.""" + + METAINFO = { + 'classes': + ('person', 'sneakers', 'chair', 'hat', 'lamp', 'bottle', + 'cabinet/shelf', 'cup', 'car', 'glasses', 'picture/frame', 'desk', + 'handbag', 'street lights', 'book', 'plate', 'helmet', + 'leather shoes', 'pillow', 'glove', 'potted plant', 'bracelet', + 'flower', 'tv', 'storage box', 'vase', 'bench', 'wine glass', 'boots', + 'bowl', 'dining table', 'umbrella', 'boat', 'flag', 'speaker', + 'trash bin/can', 'stool', 'backpack', 'couch', 'belt', 'carpet', + 'basket', 'towel/napkin', 'slippers', 'barrel/bucket', 'coffee table', + 'suv', 'toy', 'tie', 'bed', 'traffic light', 'pen/pencil', + 'microphone', 'sandals', 'canned', 'necklace', 'mirror', 'faucet', + 'bicycle', 'bread', 'high heels', 'ring', 'van', 'watch', 'sink', + 'horse', 'fish', 'apple', 'camera', 'candle', 'teddy bear', 'cake', + 'motorcycle', 'wild bird', 'laptop', 'knife', 'traffic sign', + 'cell phone', 'paddle', 'truck', 'cow', 'power outlet', 'clock', + 'drum', 'fork', 'bus', 'hanger', 'nightstand', 'pot/pan', 'sheep', + 'guitar', 'traffic cone', 'tea pot', 'keyboard', 'tripod', 'hockey', + 'fan', 'dog', 'spoon', 'blackboard/whiteboard', 'balloon', + 'air conditioner', 'cymbal', 'mouse', 'telephone', 'pickup truck', + 'orange', 'banana', 'airplane', 'luggage', 'skis', 'soccer', + 'trolley', 'oven', 'remote', 'baseball glove', 'paper towel', + 'refrigerator', 'train', 'tomato', 'machinery vehicle', 'tent', + 'shampoo/shower gel', 'head phone', 'lantern', 'donut', + 'cleaning products', 'sailboat', 'tangerine', 'pizza', 'kite', + 'computer box', 'elephant', 'toiletries', 'gas stove', 'broccoli', + 'toilet', 'stroller', 'shovel', 'baseball bat', 'microwave', + 'skateboard', 'surfboard', 'surveillance camera', 'gun', 'life saver', + 'cat', 'lemon', 'liquid soap', 'zebra', 'duck', 'sports car', + 'giraffe', 'pumpkin', 'piano', 'stop sign', 'radiator', 'converter', + 'tissue ', 'carrot', 'washing machine', 'vent', 'cookies', + 'cutting/chopping board', 'tennis racket', 'candy', + 'skating and skiing shoes', 'scissors', 'folder', 'baseball', + 'strawberry', 'bow tie', 'pigeon', 'pepper', 'coffee machine', + 'bathtub', 'snowboard', 'suitcase', 'grapes', 'ladder', 'pear', + 'american football', 'basketball', 'potato', 'paint brush', 'printer', + 'billiards', 'fire hydrant', 'goose', 'projector', 'sausage', + 'fire extinguisher', 'extension cord', 'facial mask', 'tennis ball', + 'chopsticks', 'electronic stove and gas stove', 'pie', 'frisbee', + 'kettle', 'hamburger', 'golf club', 'cucumber', 'clutch', 'blender', + 'tong', 'slide', 'hot dog', 'toothbrush', 'facial cleanser', 'mango', + 'deer', 'egg', 'violin', 'marker', 'ship', 'chicken', 'onion', + 'ice cream', 'tape', 'wheelchair', 'plum', 'bar soap', 'scale', + 'watermelon', 'cabbage', 'router/modem', 'golf ball', 'pine apple', + 'crane', 'fire truck', 'peach', 'cello', 'notepaper', 'tricycle', + 'toaster', 'helicopter', 'green beans', 'brush', 'carriage', 'cigar', + 'earphone', 'penguin', 'hurdle', 'swing', 'radio', 'CD', + 'parking meter', 'swan', 'garlic', 'french fries', 'horn', 'avocado', + 'saxophone', 'trumpet', 'sandwich', 'cue', 'kiwi fruit', 'bear', + 'fishing rod', 'cherry', 'tablet', 'green vegetables', 'nuts', 'corn', + 'key', 'screwdriver', 'globe', 'broom', 'pliers', 'volleyball', + 'hammer', 'eggplant', 'trophy', 'dates', 'board eraser', 'rice', + 'tape measure/ruler', 'dumbbell', 'hamimelon', 'stapler', 'camel', + 'lettuce', 'goldfish', 'meat balls', 'medal', 'toothpaste', + 'antelope', 'shrimp', 'rickshaw', 'trombone', 'pomegranate', + 'coconut', 'jellyfish', 'mushroom', 'calculator', 'treadmill', + 'butterfly', 'egg tart', 'cheese', 'pig', 'pomelo', 'race car', + 'rice cooker', 'tuba', 'crosswalk sign', 'papaya', 'hair drier', + 'green onion', 'chips', 'dolphin', 'sushi', 'urinal', 'donkey', + 'electric drill', 'spring rolls', 'tortoise/turtle', 'parrot', + 'flute', 'measuring cup', 'shark', 'steak', 'poker card', + 'binoculars', 'llama', 'radish', 'noodles', 'yak', 'mop', 'crab', + 'microscope', 'barbell', 'bread/bun', 'baozi', 'lion', 'red cabbage', + 'polar bear', 'lighter', 'seal', 'mangosteen', 'comb', 'eraser', + 'pitaya', 'scallop', 'pencil case', 'saw', 'table tennis paddle', + 'okra', 'starfish', 'eagle', 'monkey', 'durian', 'game board', + 'rabbit', 'french horn', 'ambulance', 'asparagus', 'hoverboard', + 'pasta', 'target', 'hotair balloon', 'chainsaw', 'lobster', 'iron', + 'flashlight'), + 'palette': + None + } + + COCOAPI = COCO + # ann_id is unique in coco dataset. + ANN_ID_UNIQUE = True + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ # noqa: E501 + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + self.coco = self.COCOAPI(local_path) + + # 'categories' list in objects365_train.json and objects365_val.json + # is inconsistent, need sort list(or dict) before get cat_ids. + cats = self.coco.cats + sorted_cats = {i: cats[i] for i in sorted(cats)} + self.coco.cats = sorted_cats + categories = self.coco.dataset['categories'] + sorted_categories = sorted(categories, key=lambda i: i['id']) + self.coco.dataset['categories'] = sorted_categories + # The order of returned `cat_ids` will not + # change with the order of the `classes` + self.cat_ids = self.coco.get_cat_ids( + cat_names=self.metainfo['classes']) + self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)} + self.cat_img_map = copy.deepcopy(self.coco.cat_img_map) + + img_ids = self.coco.get_img_ids() + data_list = [] + total_ann_ids = [] + for img_id in img_ids: + raw_img_info = self.coco.load_imgs([img_id])[0] + raw_img_info['img_id'] = img_id + + ann_ids = self.coco.get_ann_ids(img_ids=[img_id]) + raw_ann_info = self.coco.load_anns(ann_ids) + total_ann_ids.extend(ann_ids) + + parsed_data_info = self.parse_data_info({ + 'raw_ann_info': + raw_ann_info, + 'raw_img_info': + raw_img_info + }) + data_list.append(parsed_data_info) + if self.ANN_ID_UNIQUE: + assert len(set(total_ann_ids)) == len( + total_ann_ids + ), f"Annotation ids in '{self.ann_file}' are not unique!" + + del self.coco + + return data_list + + +@DATASETS.register_module() +class Objects365V2Dataset(CocoDataset): + """Objects365 v2 dataset for detection.""" + METAINFO = { + 'classes': + ('Person', 'Sneakers', 'Chair', 'Other Shoes', 'Hat', 'Car', 'Lamp', + 'Glasses', 'Bottle', 'Desk', 'Cup', 'Street Lights', 'Cabinet/shelf', + 'Handbag/Satchel', 'Bracelet', 'Plate', 'Picture/Frame', 'Helmet', + 'Book', 'Gloves', 'Storage box', 'Boat', 'Leather Shoes', 'Flower', + 'Bench', 'Potted Plant', 'Bowl/Basin', 'Flag', 'Pillow', 'Boots', + 'Vase', 'Microphone', 'Necklace', 'Ring', 'SUV', 'Wine Glass', 'Belt', + 'Moniter/TV', 'Backpack', 'Umbrella', 'Traffic Light', 'Speaker', + 'Watch', 'Tie', 'Trash bin Can', 'Slippers', 'Bicycle', 'Stool', + 'Barrel/bucket', 'Van', 'Couch', 'Sandals', 'Bakset', 'Drum', + 'Pen/Pencil', 'Bus', 'Wild Bird', 'High Heels', 'Motorcycle', + 'Guitar', 'Carpet', 'Cell Phone', 'Bread', 'Camera', 'Canned', + 'Truck', 'Traffic cone', 'Cymbal', 'Lifesaver', 'Towel', + 'Stuffed Toy', 'Candle', 'Sailboat', 'Laptop', 'Awning', 'Bed', + 'Faucet', 'Tent', 'Horse', 'Mirror', 'Power outlet', 'Sink', 'Apple', + 'Air Conditioner', 'Knife', 'Hockey Stick', 'Paddle', 'Pickup Truck', + 'Fork', 'Traffic Sign', 'Ballon', 'Tripod', 'Dog', 'Spoon', 'Clock', + 'Pot', 'Cow', 'Cake', 'Dinning Table', 'Sheep', 'Hanger', + 'Blackboard/Whiteboard', 'Napkin', 'Other Fish', 'Orange/Tangerine', + 'Toiletry', 'Keyboard', 'Tomato', 'Lantern', 'Machinery Vehicle', + 'Fan', 'Green Vegetables', 'Banana', 'Baseball Glove', 'Airplane', + 'Mouse', 'Train', 'Pumpkin', 'Soccer', 'Skiboard', 'Luggage', + 'Nightstand', 'Tea pot', 'Telephone', 'Trolley', 'Head Phone', + 'Sports Car', 'Stop Sign', 'Dessert', 'Scooter', 'Stroller', 'Crane', + 'Remote', 'Refrigerator', 'Oven', 'Lemon', 'Duck', 'Baseball Bat', + 'Surveillance Camera', 'Cat', 'Jug', 'Broccoli', 'Piano', 'Pizza', + 'Elephant', 'Skateboard', 'Surfboard', 'Gun', + 'Skating and Skiing shoes', 'Gas stove', 'Donut', 'Bow Tie', 'Carrot', + 'Toilet', 'Kite', 'Strawberry', 'Other Balls', 'Shovel', 'Pepper', + 'Computer Box', 'Toilet Paper', 'Cleaning Products', 'Chopsticks', + 'Microwave', 'Pigeon', 'Baseball', 'Cutting/chopping Board', + 'Coffee Table', 'Side Table', 'Scissors', 'Marker', 'Pie', 'Ladder', + 'Snowboard', 'Cookies', 'Radiator', 'Fire Hydrant', 'Basketball', + 'Zebra', 'Grape', 'Giraffe', 'Potato', 'Sausage', 'Tricycle', + 'Violin', 'Egg', 'Fire Extinguisher', 'Candy', 'Fire Truck', + 'Billards', 'Converter', 'Bathtub', 'Wheelchair', 'Golf Club', + 'Briefcase', 'Cucumber', 'Cigar/Cigarette ', 'Paint Brush', 'Pear', + 'Heavy Truck', 'Hamburger', 'Extractor', 'Extention Cord', 'Tong', + 'Tennis Racket', 'Folder', 'American Football', 'earphone', 'Mask', + 'Kettle', 'Tennis', 'Ship', 'Swing', 'Coffee Machine', 'Slide', + 'Carriage', 'Onion', 'Green beans', 'Projector', 'Frisbee', + 'Washing Machine/Drying Machine', 'Chicken', 'Printer', 'Watermelon', + 'Saxophone', 'Tissue', 'Toothbrush', 'Ice cream', 'Hotair ballon', + 'Cello', 'French Fries', 'Scale', 'Trophy', 'Cabbage', 'Hot dog', + 'Blender', 'Peach', 'Rice', 'Wallet/Purse', 'Volleyball', 'Deer', + 'Goose', 'Tape', 'Tablet', 'Cosmetics', 'Trumpet', 'Pineapple', + 'Golf Ball', 'Ambulance', 'Parking meter', 'Mango', 'Key', 'Hurdle', + 'Fishing Rod', 'Medal', 'Flute', 'Brush', 'Penguin', 'Megaphone', + 'Corn', 'Lettuce', 'Garlic', 'Swan', 'Helicopter', 'Green Onion', + 'Sandwich', 'Nuts', 'Speed Limit Sign', 'Induction Cooker', 'Broom', + 'Trombone', 'Plum', 'Rickshaw', 'Goldfish', 'Kiwi fruit', + 'Router/modem', 'Poker Card', 'Toaster', 'Shrimp', 'Sushi', 'Cheese', + 'Notepaper', 'Cherry', 'Pliers', 'CD', 'Pasta', 'Hammer', 'Cue', + 'Avocado', 'Hamimelon', 'Flask', 'Mushroon', 'Screwdriver', 'Soap', + 'Recorder', 'Bear', 'Eggplant', 'Board Eraser', 'Coconut', + 'Tape Measur/ Ruler', 'Pig', 'Showerhead', 'Globe', 'Chips', 'Steak', + 'Crosswalk Sign', 'Stapler', 'Campel', 'Formula 1 ', 'Pomegranate', + 'Dishwasher', 'Crab', 'Hoverboard', 'Meat ball', 'Rice Cooker', + 'Tuba', 'Calculator', 'Papaya', 'Antelope', 'Parrot', 'Seal', + 'Buttefly', 'Dumbbell', 'Donkey', 'Lion', 'Urinal', 'Dolphin', + 'Electric Drill', 'Hair Dryer', 'Egg tart', 'Jellyfish', 'Treadmill', + 'Lighter', 'Grapefruit', 'Game board', 'Mop', 'Radish', 'Baozi', + 'Target', 'French', 'Spring Rolls', 'Monkey', 'Rabbit', 'Pencil Case', + 'Yak', 'Red Cabbage', 'Binoculars', 'Asparagus', 'Barbell', 'Scallop', + 'Noddles', 'Comb', 'Dumpling', 'Oyster', 'Table Teniis paddle', + 'Cosmetics Brush/Eyeliner Pencil', 'Chainsaw', 'Eraser', 'Lobster', + 'Durian', 'Okra', 'Lipstick', 'Cosmetics Mirror', 'Curling', + 'Table Tennis '), + 'palette': + None + } + + COCOAPI = COCO + # ann_id is unique in coco dataset. + ANN_ID_UNIQUE = True + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ # noqa: E501 + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + self.coco = self.COCOAPI(local_path) + # The order of returned `cat_ids` will not + # change with the order of the `classes` + self.cat_ids = self.coco.get_cat_ids( + cat_names=self.metainfo['classes']) + self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)} + self.cat_img_map = copy.deepcopy(self.coco.cat_img_map) + + img_ids = self.coco.get_img_ids() + data_list = [] + total_ann_ids = [] + for img_id in img_ids: + raw_img_info = self.coco.load_imgs([img_id])[0] + raw_img_info['img_id'] = img_id + + ann_ids = self.coco.get_ann_ids(img_ids=[img_id]) + raw_ann_info = self.coco.load_anns(ann_ids) + total_ann_ids.extend(ann_ids) + + # file_name should be `patchX/xxx.jpg` + file_name = osp.join( + osp.split(osp.split(raw_img_info['file_name'])[0])[-1], + osp.split(raw_img_info['file_name'])[-1]) + + if file_name in objv2_ignore_list: + continue + + raw_img_info['file_name'] = file_name + parsed_data_info = self.parse_data_info({ + 'raw_ann_info': + raw_ann_info, + 'raw_img_info': + raw_img_info + }) + data_list.append(parsed_data_info) + if self.ANN_ID_UNIQUE: + assert len(set(total_ann_ids)) == len( + total_ann_ids + ), f"Annotation ids in '{self.ann_file}' are not unique!" + + del self.coco + + return data_list diff --git a/mmdetection/mmdet/datasets/odvg.py b/mmdetection/mmdet/datasets/odvg.py new file mode 100644 index 00000000..c73865f2 --- /dev/null +++ b/mmdetection/mmdet/datasets/odvg.py @@ -0,0 +1,106 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +import os.path as osp +from typing import List, Optional + +from mmengine.fileio import get_local_path + +from mmdet.registry import DATASETS +from .base_det_dataset import BaseDetDataset + + +@DATASETS.register_module() +class ODVGDataset(BaseDetDataset): + """object detection and visual grounding dataset.""" + + def __init__(self, + *args, + data_root: str = '', + label_map_file: Optional[str] = None, + need_text: bool = True, + **kwargs) -> None: + self.dataset_mode = 'VG' + self.need_text = need_text + if label_map_file: + label_map_file = osp.join(data_root, label_map_file) + with open(label_map_file, 'r') as file: + self.label_map = json.load(file) + self.dataset_mode = 'OD' + super().__init__(*args, data_root=data_root, **kwargs) + assert self.return_classes is True + + def load_data_list(self) -> List[dict]: + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + with open(local_path, 'r') as f: + data_list = [json.loads(line) for line in f] + + out_data_list = [] + for data in data_list: + data_info = {} + img_path = osp.join(self.data_prefix['img'], data['filename']) + data_info['img_path'] = img_path + data_info['height'] = data['height'] + data_info['width'] = data['width'] + if self.dataset_mode == 'OD': + if self.need_text: + data_info['text'] = self.label_map + anno = data.get('detection', {}) + instances = [obj for obj in anno.get('instances', [])] + bboxes = [obj['bbox'] for obj in instances] + bbox_labels = [str(obj['label']) for obj in instances] + + instances = [] + for bbox, label in zip(bboxes, bbox_labels): + instance = {} + x1, y1, x2, y2 = bbox + inter_w = max(0, min(x2, data['width']) - max(x1, 0)) + inter_h = max(0, min(y2, data['height']) - max(y1, 0)) + if inter_w * inter_h == 0: + continue + if (x2 - x1) < 1 or (y2 - y1) < 1: + continue + instance['ignore_flag'] = 0 + instance['bbox'] = bbox + instance['bbox_label'] = int(label) + instances.append(instance) + data_info['instances'] = instances + data_info['dataset_mode'] = self.dataset_mode + out_data_list.append(data_info) + else: + anno = data['grounding'] + data_info['text'] = anno['caption'] + regions = anno['regions'] + + instances = [] + phrases = {} + for i, region in enumerate(regions): + bbox = region['bbox'] + phrase = region['phrase'] + tokens_positive = region['tokens_positive'] + if not isinstance(bbox[0], list): + bbox = [bbox] + for box in bbox: + instance = {} + x1, y1, x2, y2 = box + inter_w = max(0, min(x2, data['width']) - max(x1, 0)) + inter_h = max(0, min(y2, data['height']) - max(y1, 0)) + if inter_w * inter_h == 0: + continue + if (x2 - x1) < 1 or (y2 - y1) < 1: + continue + instance['ignore_flag'] = 0 + instance['bbox'] = box + instance['bbox_label'] = i + phrases[i] = { + 'phrase': phrase, + 'tokens_positive': tokens_positive + } + instances.append(instance) + data_info['instances'] = instances + data_info['phrases'] = phrases + data_info['dataset_mode'] = self.dataset_mode + out_data_list.append(data_info) + + del data_list + return out_data_list diff --git a/mmdetection/mmdet/datasets/openimages.py b/mmdetection/mmdet/datasets/openimages.py new file mode 100644 index 00000000..a3c6c8ec --- /dev/null +++ b/mmdetection/mmdet/datasets/openimages.py @@ -0,0 +1,484 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import csv +import os.path as osp +from collections import defaultdict +from typing import Dict, List, Optional + +import numpy as np +from mmengine.fileio import get_local_path, load +from mmengine.utils import is_abs + +from mmdet.registry import DATASETS +from .base_det_dataset import BaseDetDataset + + +@DATASETS.register_module() +class OpenImagesDataset(BaseDetDataset): + """Open Images dataset for detection. + + Args: + ann_file (str): Annotation file path. + label_file (str): File path of the label description file that + maps the classes names in MID format to their short + descriptions. + meta_file (str): File path to get image metas. + hierarchy_file (str): The file path of the class hierarchy. + image_level_ann_file (str): Human-verified image level annotation, + which is used in evaluation. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + """ + + METAINFO: dict = dict(dataset_type='oid_v6') + + def __init__(self, + label_file: str, + meta_file: str, + hierarchy_file: str, + image_level_ann_file: Optional[str] = None, + **kwargs) -> None: + self.label_file = label_file + self.meta_file = meta_file + self.hierarchy_file = hierarchy_file + self.image_level_ann_file = image_level_ann_file + super().__init__(**kwargs) + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ + classes_names, label_id_mapping = self._parse_label_file( + self.label_file) + self._metainfo['classes'] = classes_names + self.label_id_mapping = label_id_mapping + + if self.image_level_ann_file is not None: + img_level_anns = self._parse_img_level_ann( + self.image_level_ann_file) + else: + img_level_anns = None + + # OpenImagesMetric can get the relation matrix from the dataset meta + relation_matrix = self._get_relation_matrix(self.hierarchy_file) + self._metainfo['RELATION_MATRIX'] = relation_matrix + + data_list = [] + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + with open(local_path, 'r') as f: + reader = csv.reader(f) + last_img_id = None + instances = [] + for i, line in enumerate(reader): + if i == 0: + continue + img_id = line[0] + if last_img_id is None: + last_img_id = img_id + label_id = line[2] + assert label_id in self.label_id_mapping + label = int(self.label_id_mapping[label_id]) + bbox = [ + float(line[4]), # xmin + float(line[6]), # ymin + float(line[5]), # xmax + float(line[7]) # ymax + ] + is_occluded = True if int(line[8]) == 1 else False + is_truncated = True if int(line[9]) == 1 else False + is_group_of = True if int(line[10]) == 1 else False + is_depiction = True if int(line[11]) == 1 else False + is_inside = True if int(line[12]) == 1 else False + + instance = dict( + bbox=bbox, + bbox_label=label, + ignore_flag=0, + is_occluded=is_occluded, + is_truncated=is_truncated, + is_group_of=is_group_of, + is_depiction=is_depiction, + is_inside=is_inside) + last_img_path = osp.join(self.data_prefix['img'], + f'{last_img_id}.jpg') + if img_id != last_img_id: + # switch to a new image, record previous image's data. + data_info = dict( + img_path=last_img_path, + img_id=last_img_id, + instances=instances, + ) + data_list.append(data_info) + instances = [] + instances.append(instance) + last_img_id = img_id + data_list.append( + dict( + img_path=last_img_path, + img_id=last_img_id, + instances=instances, + )) + + # add image metas to data list + img_metas = load( + self.meta_file, file_format='pkl', backend_args=self.backend_args) + assert len(img_metas) == len(data_list) + for i, meta in enumerate(img_metas): + img_id = data_list[i]['img_id'] + assert f'{img_id}.jpg' == osp.split(meta['filename'])[-1] + h, w = meta['ori_shape'][:2] + data_list[i]['height'] = h + data_list[i]['width'] = w + # denormalize bboxes + for j in range(len(data_list[i]['instances'])): + data_list[i]['instances'][j]['bbox'][0] *= w + data_list[i]['instances'][j]['bbox'][2] *= w + data_list[i]['instances'][j]['bbox'][1] *= h + data_list[i]['instances'][j]['bbox'][3] *= h + # add image-level annotation + if img_level_anns is not None: + img_labels = [] + confidences = [] + img_ann_list = img_level_anns.get(img_id, []) + for ann in img_ann_list: + img_labels.append(int(ann['image_level_label'])) + confidences.append(float(ann['confidence'])) + data_list[i]['image_level_labels'] = np.array( + img_labels, dtype=np.int64) + data_list[i]['confidences'] = np.array( + confidences, dtype=np.float32) + return data_list + + def _parse_label_file(self, label_file: str) -> tuple: + """Get classes name and index mapping from cls-label-description file. + + Args: + label_file (str): File path of the label description file that + maps the classes names in MID format to their short + descriptions. + + Returns: + tuple: Class name of OpenImages. + """ + + index_list = [] + classes_names = [] + with get_local_path( + label_file, backend_args=self.backend_args) as local_path: + with open(local_path, 'r') as f: + reader = csv.reader(f) + for line in reader: + # self.cat2label[line[0]] = line[1] + classes_names.append(line[1]) + index_list.append(line[0]) + index_mapping = {index: i for i, index in enumerate(index_list)} + return classes_names, index_mapping + + def _parse_img_level_ann(self, + img_level_ann_file: str) -> Dict[str, List[dict]]: + """Parse image level annotations from csv style ann_file. + + Args: + img_level_ann_file (str): CSV style image level annotation + file path. + + Returns: + Dict[str, List[dict]]: Annotations where item of the defaultdict + indicates an image, each of which has (n) dicts. + Keys of dicts are: + + - `image_level_label` (int): Label id. + - `confidence` (float): Labels that are human-verified to be + present in an image have confidence = 1 (positive labels). + Labels that are human-verified to be absent from an image + have confidence = 0 (negative labels). Machine-generated + labels have fractional confidences, generally >= 0.5. + The higher the confidence, the smaller the chance for + the label to be a false positive. + """ + + item_lists = defaultdict(list) + with get_local_path( + img_level_ann_file, + backend_args=self.backend_args) as local_path: + with open(local_path, 'r') as f: + reader = csv.reader(f) + for i, line in enumerate(reader): + if i == 0: + continue + img_id = line[0] + item_lists[img_id].append( + dict( + image_level_label=int( + self.label_id_mapping[line[2]]), + confidence=float(line[3]))) + return item_lists + + def _get_relation_matrix(self, hierarchy_file: str) -> np.ndarray: + """Get the matrix of class hierarchy from the hierarchy file. Hierarchy + for 600 classes can be found at https://storage.googleapis.com/openimag + es/2018_04/bbox_labels_600_hierarchy_visualizer/circle.html. + + Args: + hierarchy_file (str): File path to the hierarchy for classes. + + Returns: + np.ndarray: The matrix of the corresponding relationship between + the parent class and the child class, of shape + (class_num, class_num). + """ # noqa + + hierarchy = load( + hierarchy_file, file_format='json', backend_args=self.backend_args) + class_num = len(self._metainfo['classes']) + relation_matrix = np.eye(class_num, class_num) + relation_matrix = self._convert_hierarchy_tree(hierarchy, + relation_matrix) + return relation_matrix + + def _convert_hierarchy_tree(self, + hierarchy_map: dict, + relation_matrix: np.ndarray, + parents: list = [], + get_all_parents: bool = True) -> np.ndarray: + """Get matrix of the corresponding relationship between the parent + class and the child class. + + Args: + hierarchy_map (dict): Including label name and corresponding + subcategory. Keys of dicts are: + + - `LabeName` (str): Name of the label. + - `Subcategory` (dict | list): Corresponding subcategory(ies). + relation_matrix (ndarray): The matrix of the corresponding + relationship between the parent class and the child class, + of shape (class_num, class_num). + parents (list): Corresponding parent class. + get_all_parents (bool): Whether get all parent names. + Default: True + + Returns: + ndarray: The matrix of the corresponding relationship between + the parent class and the child class, of shape + (class_num, class_num). + """ + + if 'Subcategory' in hierarchy_map: + for node in hierarchy_map['Subcategory']: + if 'LabelName' in node: + children_name = node['LabelName'] + children_index = self.label_id_mapping[children_name] + children = [children_index] + else: + continue + if len(parents) > 0: + for parent_index in parents: + if get_all_parents: + children.append(parent_index) + relation_matrix[children_index, parent_index] = 1 + relation_matrix = self._convert_hierarchy_tree( + node, relation_matrix, parents=children) + return relation_matrix + + def _join_prefix(self): + """Join ``self.data_root`` with annotation path.""" + super()._join_prefix() + if not is_abs(self.label_file) and self.label_file: + self.label_file = osp.join(self.data_root, self.label_file) + if not is_abs(self.meta_file) and self.meta_file: + self.meta_file = osp.join(self.data_root, self.meta_file) + if not is_abs(self.hierarchy_file) and self.hierarchy_file: + self.hierarchy_file = osp.join(self.data_root, self.hierarchy_file) + if self.image_level_ann_file and not is_abs(self.image_level_ann_file): + self.image_level_ann_file = osp.join(self.data_root, + self.image_level_ann_file) + + +@DATASETS.register_module() +class OpenImagesChallengeDataset(OpenImagesDataset): + """Open Images Challenge dataset for detection. + + Args: + ann_file (str): Open Images Challenge box annotation in txt format. + """ + + METAINFO: dict = dict(dataset_type='oid_challenge') + + def __init__(self, ann_file: str, **kwargs) -> None: + if not ann_file.endswith('txt'): + raise TypeError('The annotation file of Open Images Challenge ' + 'should be a txt file.') + + super().__init__(ann_file=ann_file, **kwargs) + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ + classes_names, label_id_mapping = self._parse_label_file( + self.label_file) + self._metainfo['classes'] = classes_names + self.label_id_mapping = label_id_mapping + + if self.image_level_ann_file is not None: + img_level_anns = self._parse_img_level_ann( + self.image_level_ann_file) + else: + img_level_anns = None + + # OpenImagesMetric can get the relation matrix from the dataset meta + relation_matrix = self._get_relation_matrix(self.hierarchy_file) + self._metainfo['RELATION_MATRIX'] = relation_matrix + + data_list = [] + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + with open(local_path, 'r') as f: + lines = f.readlines() + i = 0 + while i < len(lines): + instances = [] + filename = lines[i].rstrip() + i += 2 + img_gt_size = int(lines[i]) + i += 1 + for j in range(img_gt_size): + sp = lines[i + j].split() + instances.append( + dict( + bbox=[ + float(sp[1]), + float(sp[2]), + float(sp[3]), + float(sp[4]) + ], + bbox_label=int(sp[0]) - 1, # labels begin from 1 + ignore_flag=0, + is_group_ofs=True if int(sp[5]) == 1 else False)) + i += img_gt_size + data_list.append( + dict( + img_path=osp.join(self.data_prefix['img'], filename), + instances=instances, + )) + + # add image metas to data list + img_metas = load( + self.meta_file, file_format='pkl', backend_args=self.backend_args) + assert len(img_metas) == len(data_list) + for i, meta in enumerate(img_metas): + img_id = osp.split(data_list[i]['img_path'])[-1][:-4] + assert img_id == osp.split(meta['filename'])[-1][:-4] + h, w = meta['ori_shape'][:2] + data_list[i]['height'] = h + data_list[i]['width'] = w + data_list[i]['img_id'] = img_id + # denormalize bboxes + for j in range(len(data_list[i]['instances'])): + data_list[i]['instances'][j]['bbox'][0] *= w + data_list[i]['instances'][j]['bbox'][2] *= w + data_list[i]['instances'][j]['bbox'][1] *= h + data_list[i]['instances'][j]['bbox'][3] *= h + # add image-level annotation + if img_level_anns is not None: + img_labels = [] + confidences = [] + img_ann_list = img_level_anns.get(img_id, []) + for ann in img_ann_list: + img_labels.append(int(ann['image_level_label'])) + confidences.append(float(ann['confidence'])) + data_list[i]['image_level_labels'] = np.array( + img_labels, dtype=np.int64) + data_list[i]['confidences'] = np.array( + confidences, dtype=np.float32) + return data_list + + def _parse_label_file(self, label_file: str) -> tuple: + """Get classes name and index mapping from cls-label-description file. + + Args: + label_file (str): File path of the label description file that + maps the classes names in MID format to their short + descriptions. + + Returns: + tuple: Class name of OpenImages. + """ + label_list = [] + id_list = [] + index_mapping = {} + with get_local_path( + label_file, backend_args=self.backend_args) as local_path: + with open(local_path, 'r') as f: + reader = csv.reader(f) + for line in reader: + label_name = line[0] + label_id = int(line[2]) + label_list.append(line[1]) + id_list.append(label_id) + index_mapping[label_name] = label_id - 1 + indexes = np.argsort(id_list) + classes_names = [] + for index in indexes: + classes_names.append(label_list[index]) + return classes_names, index_mapping + + def _parse_img_level_ann(self, image_level_ann_file): + """Parse image level annotations from csv style ann_file. + + Args: + image_level_ann_file (str): CSV style image level annotation + file path. + + Returns: + defaultdict[list[dict]]: Annotations where item of the defaultdict + indicates an image, each of which has (n) dicts. + Keys of dicts are: + + - `image_level_label` (int): of shape 1. + - `confidence` (float): of shape 1. + """ + + item_lists = defaultdict(list) + with get_local_path( + image_level_ann_file, + backend_args=self.backend_args) as local_path: + with open(local_path, 'r') as f: + reader = csv.reader(f) + i = -1 + for line in reader: + i += 1 + if i == 0: + continue + else: + img_id = line[0] + label_id = line[1] + assert label_id in self.label_id_mapping + image_level_label = int( + self.label_id_mapping[label_id]) + confidence = float(line[2]) + item_lists[img_id].append( + dict( + image_level_label=image_level_label, + confidence=confidence)) + return item_lists + + def _get_relation_matrix(self, hierarchy_file: str) -> np.ndarray: + """Get the matrix of class hierarchy from the hierarchy file. + + Args: + hierarchy_file (str): File path to the hierarchy for classes. + + Returns: + np.ndarray: The matrix of the corresponding + relationship between the parent class and the child class, + of shape (class_num, class_num). + """ + with get_local_path( + hierarchy_file, backend_args=self.backend_args) as local_path: + class_label_tree = np.load(local_path, allow_pickle=True) + return class_label_tree[1:, 1:] diff --git a/mmdetection/mmdet/datasets/refcoco.py b/mmdetection/mmdet/datasets/refcoco.py new file mode 100644 index 00000000..0dae75fd --- /dev/null +++ b/mmdetection/mmdet/datasets/refcoco.py @@ -0,0 +1,163 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import collections +import os.path as osp +import random +from typing import Dict, List + +import mmengine +from mmengine.dataset import BaseDataset + +from mmdet.registry import DATASETS + + +@DATASETS.register_module() +class RefCocoDataset(BaseDataset): + """RefCOCO dataset. + + The `Refcoco` and `Refcoco+` dataset is based on + `ReferItGame: Referring to Objects in Photographs of Natural Scenes + `_. + + The `Refcocog` dataset is based on + `Generation and Comprehension of Unambiguous Object Descriptions + `_. + + Args: + ann_file (str): Annotation file path. + data_root (str): The root directory for ``data_prefix`` and + ``ann_file``. Defaults to ''. + data_prefix (str): Prefix for training data. + split_file (str): Split file path. + split (str): Split name. Defaults to 'train'. + text_mode (str): Text mode. Defaults to 'random'. + **kwargs: Other keyword arguments in :class:`BaseDataset`. + """ + + def __init__(self, + data_root: str, + ann_file: str, + split_file: str, + data_prefix: Dict, + split: str = 'train', + text_mode: str = 'random', + **kwargs): + self.split_file = split_file + self.split = split + + assert text_mode in ['original', 'random', 'concat', 'select_first'] + self.text_mode = text_mode + super().__init__( + data_root=data_root, + data_prefix=data_prefix, + ann_file=ann_file, + **kwargs, + ) + + def _join_prefix(self): + if not mmengine.is_abs(self.split_file) and self.split_file: + self.split_file = osp.join(self.data_root, self.split_file) + + return super()._join_prefix() + + def _init_refs(self): + """Initialize the refs for RefCOCO.""" + anns, imgs = {}, {} + for ann in self.instances['annotations']: + anns[ann['id']] = ann + for img in self.instances['images']: + imgs[img['id']] = img + + refs, ref_to_ann = {}, {} + for ref in self.splits: + # ids + ref_id = ref['ref_id'] + ann_id = ref['ann_id'] + # add mapping related to ref + refs[ref_id] = ref + ref_to_ann[ref_id] = anns[ann_id] + + self.refs = refs + self.ref_to_ann = ref_to_ann + + def load_data_list(self) -> List[dict]: + """Load data list.""" + self.splits = mmengine.load(self.split_file, file_format='pkl') + self.instances = mmengine.load(self.ann_file, file_format='json') + self._init_refs() + img_prefix = self.data_prefix['img_path'] + + ref_ids = [ + ref['ref_id'] for ref in self.splits if ref['split'] == self.split + ] + full_anno = [] + for ref_id in ref_ids: + ref = self.refs[ref_id] + ann = self.ref_to_ann[ref_id] + ann.update(ref) + full_anno.append(ann) + + image_id_list = [] + final_anno = {} + for anno in full_anno: + image_id_list.append(anno['image_id']) + final_anno[anno['ann_id']] = anno + annotations = [value for key, value in final_anno.items()] + + coco_train_id = [] + image_annot = {} + for i in range(len(self.instances['images'])): + coco_train_id.append(self.instances['images'][i]['id']) + image_annot[self.instances['images'][i] + ['id']] = self.instances['images'][i] + + images = [] + for image_id in list(set(image_id_list)): + images += [image_annot[image_id]] + + data_list = [] + + grounding_dict = collections.defaultdict(list) + for anno in annotations: + image_id = int(anno['image_id']) + grounding_dict[image_id].append(anno) + + join_path = mmengine.fileio.get_file_backend(img_prefix).join_path + for image in images: + img_id = image['id'] + instances = [] + sentences = [] + for grounding_anno in grounding_dict[img_id]: + texts = [x['raw'].lower() for x in grounding_anno['sentences']] + # random select one text + if self.text_mode == 'random': + idx = random.randint(0, len(texts) - 1) + text = [texts[idx]] + # concat all texts + elif self.text_mode == 'concat': + text = [''.join(texts)] + # select the first text + elif self.text_mode == 'select_first': + text = [texts[0]] + # use all texts + elif self.text_mode == 'original': + text = texts + else: + raise ValueError(f'Invalid text mode "{self.text_mode}".') + ins = [{ + 'mask': grounding_anno['segmentation'], + 'ignore_flag': 0 + }] * len(text) + instances.extend(ins) + sentences.extend(text) + data_info = { + 'img_path': join_path(img_prefix, image['file_name']), + 'img_id': img_id, + 'instances': instances, + 'text': sentences + } + data_list.append(data_info) + + if len(data_list) == 0: + raise ValueError(f'No sample in split "{self.split}".') + + return data_list diff --git a/mmdetection/mmdet/datasets/reid_dataset.py b/mmdetection/mmdet/datasets/reid_dataset.py new file mode 100644 index 00000000..1eed3ee4 --- /dev/null +++ b/mmdetection/mmdet/datasets/reid_dataset.py @@ -0,0 +1,127 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +from collections import defaultdict +from typing import Any, Dict, List + +import numpy as np +from mmengine.dataset import BaseDataset +from mmengine.utils import check_file_exist + +from mmdet.registry import DATASETS + + +@DATASETS.register_module() +class ReIDDataset(BaseDataset): + """Dataset for ReID. + + Args: + triplet_sampler (dict, optional): The sampler for hard mining + triplet loss. Defaults to None. + keys: num_ids (int): The number of person ids. + ins_per_id (int): The number of image for each person. + """ + + def __init__(self, triplet_sampler: dict = None, *args, **kwargs): + self.triplet_sampler = triplet_sampler + super().__init__(*args, **kwargs) + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ''self.ann_file''. + + Returns: + list[dict]: A list of annotation. + """ + assert isinstance(self.ann_file, str) + check_file_exist(self.ann_file) + data_list = [] + with open(self.ann_file) as f: + samples = [x.strip().split(' ') for x in f.readlines()] + for filename, gt_label in samples: + info = dict(img_prefix=self.data_prefix) + if self.data_prefix['img_path'] is not None: + info['img_path'] = osp.join(self.data_prefix['img_path'], + filename) + else: + info['img_path'] = filename + info['gt_label'] = np.array(gt_label, dtype=np.int64) + data_list.append(info) + self._parse_ann_info(data_list) + return data_list + + def _parse_ann_info(self, data_list: List[dict]): + """Parse person id annotations.""" + index_tmp_dic = defaultdict(list) # pid->[idx1,...,idxN] + self.index_dic = dict() # pid->array([idx1,...,idxN]) + for idx, info in enumerate(data_list): + pid = info['gt_label'] + index_tmp_dic[int(pid)].append(idx) + for pid, idxs in index_tmp_dic.items(): + self.index_dic[pid] = np.asarray(idxs, dtype=np.int64) + self.pids = np.asarray(list(self.index_dic.keys()), dtype=np.int64) + + def prepare_data(self, idx: int) -> Any: + """Get data processed by ''self.pipeline''. + + Args: + idx (int): The index of ''data_info'' + + Returns: + Any: Depends on ''self.pipeline'' + """ + data_info = self.get_data_info(idx) + if self.triplet_sampler is not None: + img_info = self.triplet_sampling(data_info['gt_label'], + **self.triplet_sampler) + data_info = copy.deepcopy(img_info) # triplet -> list + else: + data_info = copy.deepcopy(data_info) # no triplet -> dict + return self.pipeline(data_info) + + def triplet_sampling(self, + pos_pid, + num_ids: int = 8, + ins_per_id: int = 4) -> Dict: + """Triplet sampler for hard mining triplet loss. First, for one + pos_pid, random sample ins_per_id images with same person id. + + Then, random sample num_ids - 1 images for each negative id. + Finally, random sample ins_per_id images for each negative id. + + Args: + pos_pid (ndarray): The person id of the anchor. + num_ids (int): The number of person ids. + ins_per_id (int): The number of images for each person. + + Returns: + Dict: Annotation information of num_ids X ins_per_id images. + """ + assert len(self.pids) >= num_ids, \ + 'The number of person ids in the training set must ' \ + 'be greater than the number of person ids in the sample.' + + pos_idxs = self.index_dic[int( + pos_pid)] # all positive idxs for pos_pid + idxs_list = [] + # select positive samplers + idxs_list.extend(pos_idxs[np.random.choice( + pos_idxs.shape[0], ins_per_id, replace=True)]) + # select negative ids + neg_pids = np.random.choice( + [i for i, _ in enumerate(self.pids) if i != pos_pid], + num_ids - 1, + replace=False) + # select negative samplers for each negative id + for neg_pid in neg_pids: + neg_idxs = self.index_dic[neg_pid] + idxs_list.extend(neg_idxs[np.random.choice( + neg_idxs.shape[0], ins_per_id, replace=True)]) + # return the final triplet batch + triplet_img_infos = [] + for idx in idxs_list: + triplet_img_infos.append(copy.deepcopy(self.get_data_info(idx))) + # Collect data_list scatters (list of dict -> dict of list) + out = dict() + for key in triplet_img_infos[0].keys(): + out[key] = [_info[key] for _info in triplet_img_infos] + return out diff --git a/mmdetection/mmdet/datasets/samplers/__init__.py b/mmdetection/mmdet/datasets/samplers/__init__.py new file mode 100644 index 00000000..9ea0e4cb --- /dev/null +++ b/mmdetection/mmdet/datasets/samplers/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .batch_sampler import (AspectRatioBatchSampler, + MultiDataAspectRatioBatchSampler, + TrackAspectRatioBatchSampler) +from .class_aware_sampler import ClassAwareSampler +from .custom_sample_size_sampler import CustomSampleSizeSampler +from .multi_data_sampler import MultiDataSampler +from .multi_source_sampler import GroupMultiSourceSampler, MultiSourceSampler +from .track_img_sampler import TrackImgSampler + +__all__ = [ + 'ClassAwareSampler', 'AspectRatioBatchSampler', 'MultiSourceSampler', + 'GroupMultiSourceSampler', 'TrackImgSampler', + 'TrackAspectRatioBatchSampler', 'MultiDataSampler', + 'MultiDataAspectRatioBatchSampler', 'CustomSampleSizeSampler' +] diff --git a/mmdetection/mmdet/datasets/samplers/batch_sampler.py b/mmdetection/mmdet/datasets/samplers/batch_sampler.py new file mode 100644 index 00000000..c17789c4 --- /dev/null +++ b/mmdetection/mmdet/datasets/samplers/batch_sampler.py @@ -0,0 +1,193 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Sequence + +from torch.utils.data import BatchSampler, Sampler + +from mmdet.datasets.samplers.track_img_sampler import TrackImgSampler +from mmdet.registry import DATA_SAMPLERS + + +# TODO: maybe replace with a data_loader wrapper +@DATA_SAMPLERS.register_module() +class AspectRatioBatchSampler(BatchSampler): + """A sampler wrapper for grouping images with similar aspect ratio (< 1 or. + + >= 1) into a same batch. + + Args: + sampler (Sampler): Base sampler. + batch_size (int): Size of mini-batch. + drop_last (bool): If ``True``, the sampler will drop the last batch if + its size would be less than ``batch_size``. + """ + + def __init__(self, + sampler: Sampler, + batch_size: int, + drop_last: bool = False) -> None: + if not isinstance(sampler, Sampler): + raise TypeError('sampler should be an instance of ``Sampler``, ' + f'but got {sampler}') + if not isinstance(batch_size, int) or batch_size <= 0: + raise ValueError('batch_size should be a positive integer value, ' + f'but got batch_size={batch_size}') + self.sampler = sampler + self.batch_size = batch_size + self.drop_last = drop_last + # two groups for w < h and w >= h + self._aspect_ratio_buckets = [[] for _ in range(2)] + + def __iter__(self) -> Sequence[int]: + for idx in self.sampler: + data_info = self.sampler.dataset.get_data_info(idx) + width, height = data_info['width'], data_info['height'] + bucket_id = 0 if width < height else 1 + bucket = self._aspect_ratio_buckets[bucket_id] + bucket.append(idx) + # yield a batch of indices in the same aspect ratio group + if len(bucket) == self.batch_size: + yield bucket[:] + del bucket[:] + + # yield the rest data and reset the bucket + left_data = self._aspect_ratio_buckets[0] + self._aspect_ratio_buckets[ + 1] + self._aspect_ratio_buckets = [[] for _ in range(2)] + while len(left_data) > 0: + if len(left_data) <= self.batch_size: + if not self.drop_last: + yield left_data[:] + left_data = [] + else: + yield left_data[:self.batch_size] + left_data = left_data[self.batch_size:] + + def __len__(self) -> int: + if self.drop_last: + return len(self.sampler) // self.batch_size + else: + return (len(self.sampler) + self.batch_size - 1) // self.batch_size + + +@DATA_SAMPLERS.register_module() +class TrackAspectRatioBatchSampler(AspectRatioBatchSampler): + """A sampler wrapper for grouping images with similar aspect ratio (< 1 or. + + >= 1) into a same batch. + + Args: + sampler (Sampler): Base sampler. + batch_size (int): Size of mini-batch. + drop_last (bool): If ``True``, the sampler will drop the last batch if + its size would be less than ``batch_size``. + """ + + def __iter__(self) -> Sequence[int]: + for idx in self.sampler: + # hard code to solve TrackImgSampler + if isinstance(self.sampler, TrackImgSampler): + video_idx, _ = idx + else: + video_idx = idx + # video_idx + data_info = self.sampler.dataset.get_data_info(video_idx) + # data_info {video_id, images, video_length} + img_data_info = data_info['images'][0] + width, height = img_data_info['width'], img_data_info['height'] + bucket_id = 0 if width < height else 1 + bucket = self._aspect_ratio_buckets[bucket_id] + bucket.append(idx) + # yield a batch of indices in the same aspect ratio group + if len(bucket) == self.batch_size: + yield bucket[:] + del bucket[:] + + # yield the rest data and reset the bucket + left_data = self._aspect_ratio_buckets[0] + self._aspect_ratio_buckets[ + 1] + self._aspect_ratio_buckets = [[] for _ in range(2)] + while len(left_data) > 0: + if len(left_data) <= self.batch_size: + if not self.drop_last: + yield left_data[:] + left_data = [] + else: + yield left_data[:self.batch_size] + left_data = left_data[self.batch_size:] + + +@DATA_SAMPLERS.register_module() +class MultiDataAspectRatioBatchSampler(BatchSampler): + """A sampler wrapper for grouping images with similar aspect ratio (< 1 or. + + >= 1) into a same batch for multi-source datasets. + + Args: + sampler (Sampler): Base sampler. + batch_size (Sequence(int)): Size of mini-batch for multi-source + datasets. + num_datasets(int): Number of multi-source datasets. + drop_last (bool): If ``True``, the sampler will drop the last batch if + its size would be less than ``batch_size``. + """ + + def __init__(self, + sampler: Sampler, + batch_size: Sequence[int], + num_datasets: int, + drop_last: bool = True) -> None: + if not isinstance(sampler, Sampler): + raise TypeError('sampler should be an instance of ``Sampler``, ' + f'but got {sampler}') + self.sampler = sampler + self.batch_size = batch_size + self.num_datasets = num_datasets + self.drop_last = drop_last + # two groups for w < h and w >= h for each dataset --> 2 * num_datasets + self._buckets = [[] for _ in range(2 * self.num_datasets)] + + def __iter__(self) -> Sequence[int]: + for idx in self.sampler: + data_info = self.sampler.dataset.get_data_info(idx) + width, height = data_info['width'], data_info['height'] + dataset_source_idx = self.sampler.dataset.get_dataset_source(idx) + aspect_ratio_bucket_id = 0 if width < height else 1 + bucket_id = dataset_source_idx * 2 + aspect_ratio_bucket_id + bucket = self._buckets[bucket_id] + bucket.append(idx) + # yield a batch of indices in the same aspect ratio group + if len(bucket) == self.batch_size[dataset_source_idx]: + yield bucket[:] + del bucket[:] + + # yield the rest data and reset the bucket + for i in range(self.num_datasets): + left_data = self._buckets[i * 2 + 0] + self._buckets[i * 2 + 1] + while len(left_data) > 0: + if len(left_data) <= self.batch_size[i]: + if not self.drop_last: + yield left_data[:] + left_data = [] + else: + yield left_data[:self.batch_size[i]] + left_data = left_data[self.batch_size[i]:] + + self._buckets = [[] for _ in range(2 * self.num_datasets)] + + def __len__(self) -> int: + sizes = [0 for _ in range(self.num_datasets)] + for idx in self.sampler: + dataset_source_idx = self.sampler.dataset.get_dataset_source(idx) + sizes[dataset_source_idx] += 1 + + if self.drop_last: + lens = 0 + for i in range(self.num_datasets): + lens += sizes[i] // self.batch_size[i] + return lens + else: + lens = 0 + for i in range(self.num_datasets): + lens += (sizes[i] + self.batch_size[i] - + 1) // self.batch_size[i] + return lens diff --git a/mmdetection/mmdet/datasets/samplers/class_aware_sampler.py b/mmdetection/mmdet/datasets/samplers/class_aware_sampler.py new file mode 100644 index 00000000..6ca2f9b3 --- /dev/null +++ b/mmdetection/mmdet/datasets/samplers/class_aware_sampler.py @@ -0,0 +1,192 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Dict, Iterator, Optional, Union + +import numpy as np +import torch +from mmengine.dataset import BaseDataset +from mmengine.dist import get_dist_info, sync_random_seed +from torch.utils.data import Sampler + +from mmdet.registry import DATA_SAMPLERS + + +@DATA_SAMPLERS.register_module() +class ClassAwareSampler(Sampler): + r"""Sampler that restricts data loading to the label of the dataset. + + A class-aware sampling strategy to effectively tackle the + non-uniform class distribution. The length of the training data is + consistent with source data. Simple improvements based on `Relay + Backpropagation for Effective Learning of Deep Convolutional + Neural Networks `_ + + The implementation logic is referred to + https://github.com/Sense-X/TSD/blob/master/mmdet/datasets/samplers/distributed_classaware_sampler.py + + Args: + dataset: Dataset used for sampling. + seed (int, optional): random seed used to shuffle the sampler. + This number should be identical across all + processes in the distributed group. Defaults to None. + num_sample_class (int): The number of samples taken from each + per-label list. Defaults to 1. + """ + + def __init__(self, + dataset: BaseDataset, + seed: Optional[int] = None, + num_sample_class: int = 1) -> None: + rank, world_size = get_dist_info() + self.rank = rank + self.world_size = world_size + + self.dataset = dataset + self.epoch = 0 + # Must be the same across all workers. If None, will use a + # random seed shared among workers + # (require synchronization among all workers) + if seed is None: + seed = sync_random_seed() + self.seed = seed + + # The number of samples taken from each per-label list + assert num_sample_class > 0 and isinstance(num_sample_class, int) + self.num_sample_class = num_sample_class + # Get per-label image list from dataset + self.cat_dict = self.get_cat2imgs() + + self.num_samples = int(math.ceil(len(self.dataset) * 1.0 / world_size)) + self.total_size = self.num_samples * self.world_size + + # get number of images containing each category + self.num_cat_imgs = [len(x) for x in self.cat_dict.values()] + # filter labels without images + self.valid_cat_inds = [ + i for i, length in enumerate(self.num_cat_imgs) if length != 0 + ] + self.num_classes = len(self.valid_cat_inds) + + def get_cat2imgs(self) -> Dict[int, list]: + """Get a dict with class as key and img_ids as values. + + Returns: + dict[int, list]: A dict of per-label image list, + the item of the dict indicates a label index, + corresponds to the image index that contains the label. + """ + classes = self.dataset.metainfo.get('classes', None) + if classes is None: + raise ValueError('dataset metainfo must contain `classes`') + # sort the label index + cat2imgs = {i: [] for i in range(len(classes))} + for i in range(len(self.dataset)): + cat_ids = set(self.dataset.get_cat_ids(i)) + for cat in cat_ids: + cat2imgs[cat].append(i) + return cat2imgs + + def __iter__(self) -> Iterator[int]: + # deterministically shuffle based on epoch + g = torch.Generator() + g.manual_seed(self.epoch + self.seed) + + # initialize label list + label_iter_list = RandomCycleIter(self.valid_cat_inds, generator=g) + # initialize each per-label image list + data_iter_dict = dict() + for i in self.valid_cat_inds: + data_iter_dict[i] = RandomCycleIter(self.cat_dict[i], generator=g) + + def gen_cat_img_inds(cls_list, data_dict, num_sample_cls): + """Traverse the categories and extract `num_sample_cls` image + indexes of the corresponding categories one by one.""" + id_indices = [] + for _ in range(len(cls_list)): + cls_idx = next(cls_list) + for _ in range(num_sample_cls): + id = next(data_dict[cls_idx]) + id_indices.append(id) + return id_indices + + # deterministically shuffle based on epoch + num_bins = int( + math.ceil(self.total_size * 1.0 / self.num_classes / + self.num_sample_class)) + indices = [] + for i in range(num_bins): + indices += gen_cat_img_inds(label_iter_list, data_iter_dict, + self.num_sample_class) + + # fix extra samples to make it evenly divisible + if len(indices) >= self.total_size: + indices = indices[:self.total_size] + else: + indices += indices[:(self.total_size - len(indices))] + assert len(indices) == self.total_size + + # subsample + offset = self.num_samples * self.rank + indices = indices[offset:offset + self.num_samples] + assert len(indices) == self.num_samples + + return iter(indices) + + def __len__(self) -> int: + """The number of samples in this rank.""" + return self.num_samples + + def set_epoch(self, epoch: int) -> None: + """Sets the epoch for this sampler. + + When :attr:`shuffle=True`, this ensures all replicas use a different + random ordering for each epoch. Otherwise, the next iteration of this + sampler will yield the same ordering. + + Args: + epoch (int): Epoch number. + """ + self.epoch = epoch + + +class RandomCycleIter: + """Shuffle the list and do it again after the list have traversed. + + The implementation logic is referred to + https://github.com/wutong16/DistributionBalancedLoss/blob/master/mllt/datasets/loader/sampler.py + + Example: + >>> label_list = [0, 1, 2, 4, 5] + >>> g = torch.Generator() + >>> g.manual_seed(0) + >>> label_iter_list = RandomCycleIter(label_list, generator=g) + >>> index = next(label_iter_list) + Args: + data (list or ndarray): The data that needs to be shuffled. + generator: An torch.Generator object, which is used in setting the seed + for generating random numbers. + """ # noqa: W605 + + def __init__(self, + data: Union[list, np.ndarray], + generator: torch.Generator = None) -> None: + self.data = data + self.length = len(data) + self.index = torch.randperm(self.length, generator=generator).numpy() + self.i = 0 + self.generator = generator + + def __iter__(self) -> Iterator: + return self + + def __len__(self) -> int: + return len(self.data) + + def __next__(self): + if self.i == self.length: + self.index = torch.randperm( + self.length, generator=self.generator).numpy() + self.i = 0 + idx = self.data[self.index[self.i]] + self.i += 1 + return idx diff --git a/mmdetection/mmdet/datasets/samplers/custom_sample_size_sampler.py b/mmdetection/mmdet/datasets/samplers/custom_sample_size_sampler.py new file mode 100644 index 00000000..6bedf6c6 --- /dev/null +++ b/mmdetection/mmdet/datasets/samplers/custom_sample_size_sampler.py @@ -0,0 +1,111 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Iterator, Optional, Sequence, Sized + +import torch +from mmengine.dist import get_dist_info, sync_random_seed +from torch.utils.data import Sampler + +from mmdet.registry import DATA_SAMPLERS +from .class_aware_sampler import RandomCycleIter + + +@DATA_SAMPLERS.register_module() +class CustomSampleSizeSampler(Sampler): + + def __init__(self, + dataset: Sized, + dataset_size: Sequence[int], + ratio_mode: bool = False, + seed: Optional[int] = None, + round_up: bool = True) -> None: + assert len(dataset.datasets) == len(dataset_size) + rank, world_size = get_dist_info() + self.rank = rank + self.world_size = world_size + + self.dataset = dataset + if seed is None: + seed = sync_random_seed() + self.seed = seed + self.epoch = 0 + self.round_up = round_up + + total_size = 0 + total_size_fake = 0 + self.dataset_index = [] + self.dataset_cycle_iter = [] + new_dataset_size = [] + for dataset, size in zip(dataset.datasets, dataset_size): + self.dataset_index.append( + list(range(total_size_fake, + len(dataset) + total_size_fake))) + total_size_fake += len(dataset) + if size == -1: + total_size += len(dataset) + self.dataset_cycle_iter.append(None) + new_dataset_size.append(-1) + else: + if ratio_mode: + size = int(size * len(dataset)) + assert size <= len( + dataset + ), f'dataset size {size} is larger than ' \ + f'dataset length {len(dataset)}' + total_size += size + new_dataset_size.append(size) + + g = torch.Generator() + g.manual_seed(self.seed) + self.dataset_cycle_iter.append( + RandomCycleIter(self.dataset_index[-1], generator=g)) + self.dataset_size = new_dataset_size + + if self.round_up: + self.num_samples = math.ceil(total_size / world_size) + self.total_size = self.num_samples * self.world_size + else: + self.num_samples = math.ceil((total_size - rank) / world_size) + self.total_size = total_size + + def __iter__(self) -> Iterator[int]: + """Iterate the indices.""" + # deterministically shuffle based on epoch and seed + g = torch.Generator() + g.manual_seed(self.seed + self.epoch) + + out_index = [] + for data_size, data_index, cycle_iter in zip(self.dataset_size, + self.dataset_index, + self.dataset_cycle_iter): + if data_size == -1: + out_index += data_index + else: + index = [next(cycle_iter) for _ in range(data_size)] + out_index += index + + index = torch.randperm(len(out_index), generator=g).numpy().tolist() + indices = [out_index[i] for i in index] + + if self.round_up: + indices = ( + indices * + int(self.total_size / len(indices) + 1))[:self.total_size] + indices = indices[self.rank:self.total_size:self.world_size] + return iter(indices) + + def __len__(self) -> int: + """The number of samples in this rank.""" + return self.num_samples + + def set_epoch(self, epoch: int) -> None: + """Sets the epoch for this sampler. + + When :attr:`shuffle=True`, this ensures all replicas use a different + random ordering for each epoch. Otherwise, the next iteration of this + sampler will yield the same ordering. + + Args: + epoch (int): Epoch number. + """ + self.epoch = epoch diff --git a/mmdetection/mmdet/datasets/samplers/multi_data_sampler.py b/mmdetection/mmdet/datasets/samplers/multi_data_sampler.py new file mode 100644 index 00000000..c3a4b60d --- /dev/null +++ b/mmdetection/mmdet/datasets/samplers/multi_data_sampler.py @@ -0,0 +1,110 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Iterator, Optional, Sequence, Sized + +import torch +from mmengine.dist import get_dist_info, sync_random_seed +from mmengine.registry import DATA_SAMPLERS +from torch.utils.data import Sampler + + +@DATA_SAMPLERS.register_module() +class MultiDataSampler(Sampler): + """The default data sampler for both distributed and non-distributed + environment. + + It has several differences from the PyTorch ``DistributedSampler`` as + below: + + 1. This sampler supports non-distributed environment. + + 2. The round up behaviors are a little different. + + - If ``round_up=True``, this sampler will add extra samples to make the + number of samples is evenly divisible by the world size. And + this behavior is the same as the ``DistributedSampler`` with + ``drop_last=False``. + - If ``round_up=False``, this sampler won't remove or add any samples + while the ``DistributedSampler`` with ``drop_last=True`` will remove + tail samples. + + Args: + dataset (Sized): The dataset. + dataset_ratio (Sequence(int)) The ratios of different datasets. + seed (int, optional): Random seed used to shuffle the sampler if + :attr:`shuffle=True`. This number should be identical across all + processes in the distributed group. Defaults to None. + round_up (bool): Whether to add extra samples to make the number of + samples evenly divisible by the world size. Defaults to True. + """ + + def __init__(self, + dataset: Sized, + dataset_ratio: Sequence[int], + seed: Optional[int] = None, + round_up: bool = True) -> None: + rank, world_size = get_dist_info() + self.rank = rank + self.world_size = world_size + + self.dataset = dataset + self.dataset_ratio = dataset_ratio + + if seed is None: + seed = sync_random_seed() + self.seed = seed + self.epoch = 0 + self.round_up = round_up + + if self.round_up: + self.num_samples = math.ceil(len(self.dataset) / world_size) + self.total_size = self.num_samples * self.world_size + else: + self.num_samples = math.ceil( + (len(self.dataset) - rank) / world_size) + self.total_size = len(self.dataset) + + self.sizes = [len(dataset) for dataset in self.dataset.datasets] + + dataset_weight = [ + torch.ones(s) * max(self.sizes) / s * r / sum(self.dataset_ratio) + for i, (r, s) in enumerate(zip(self.dataset_ratio, self.sizes)) + ] + self.weights = torch.cat(dataset_weight) + + def __iter__(self) -> Iterator[int]: + """Iterate the indices.""" + # deterministically shuffle based on epoch and seed + g = torch.Generator() + g.manual_seed(self.seed + self.epoch) + + indices = torch.multinomial( + self.weights, len(self.weights), generator=g, + replacement=True).tolist() + + # add extra samples to make it evenly divisible + if self.round_up: + indices = ( + indices * + int(self.total_size / len(indices) + 1))[:self.total_size] + + # subsample + indices = indices[self.rank:self.total_size:self.world_size] + + return iter(indices) + + def __len__(self) -> int: + """The number of samples in this rank.""" + return self.num_samples + + def set_epoch(self, epoch: int) -> None: + """Sets the epoch for this sampler. + + When :attr:`shuffle=True`, this ensures all replicas use a different + random ordering for each epoch. Otherwise, the next iteration of this + sampler will yield the same ordering. + + Args: + epoch (int): Epoch number. + """ + self.epoch = epoch diff --git a/mmdetection/mmdet/datasets/samplers/multi_source_sampler.py b/mmdetection/mmdet/datasets/samplers/multi_source_sampler.py new file mode 100644 index 00000000..6efcde35 --- /dev/null +++ b/mmdetection/mmdet/datasets/samplers/multi_source_sampler.py @@ -0,0 +1,214 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import itertools +from typing import Iterator, List, Optional, Sized, Union + +import numpy as np +import torch +from mmengine.dataset import BaseDataset +from mmengine.dist import get_dist_info, sync_random_seed +from torch.utils.data import Sampler + +from mmdet.registry import DATA_SAMPLERS + + +@DATA_SAMPLERS.register_module() +class MultiSourceSampler(Sampler): + r"""Multi-Source Infinite Sampler. + + According to the sampling ratio, sample data from different + datasets to form batches. + + Args: + dataset (Sized): The dataset. + batch_size (int): Size of mini-batch. + source_ratio (list[int | float]): The sampling ratio of different + source datasets in a mini-batch. + shuffle (bool): Whether shuffle the dataset or not. Defaults to True. + seed (int, optional): Random seed. If None, set a random seed. + Defaults to None. + + Examples: + >>> dataset_type = 'ConcatDataset' + >>> sub_dataset_type = 'CocoDataset' + >>> data_root = 'data/coco/' + >>> sup_ann = '../coco_semi_annos/instances_train2017.1@10.json' + >>> unsup_ann = '../coco_semi_annos/' \ + >>> 'instances_train2017.1@10-unlabeled.json' + >>> dataset = dict(type=dataset_type, + >>> datasets=[ + >>> dict( + >>> type=sub_dataset_type, + >>> data_root=data_root, + >>> ann_file=sup_ann, + >>> data_prefix=dict(img='train2017/'), + >>> filter_cfg=dict(filter_empty_gt=True, min_size=32), + >>> pipeline=sup_pipeline), + >>> dict( + >>> type=sub_dataset_type, + >>> data_root=data_root, + >>> ann_file=unsup_ann, + >>> data_prefix=dict(img='train2017/'), + >>> filter_cfg=dict(filter_empty_gt=True, min_size=32), + >>> pipeline=unsup_pipeline), + >>> ]) + >>> train_dataloader = dict( + >>> batch_size=5, + >>> num_workers=5, + >>> persistent_workers=True, + >>> sampler=dict(type='MultiSourceSampler', + >>> batch_size=5, source_ratio=[1, 4]), + >>> batch_sampler=None, + >>> dataset=dataset) + """ + + def __init__(self, + dataset: Sized, + batch_size: int, + source_ratio: List[Union[int, float]], + shuffle: bool = True, + seed: Optional[int] = None) -> None: + + assert hasattr(dataset, 'cumulative_sizes'),\ + f'The dataset must be ConcatDataset, but get {dataset}' + assert isinstance(batch_size, int) and batch_size > 0, \ + 'batch_size must be a positive integer value, ' \ + f'but got batch_size={batch_size}' + assert isinstance(source_ratio, list), \ + f'source_ratio must be a list, but got source_ratio={source_ratio}' + assert len(source_ratio) == len(dataset.cumulative_sizes), \ + 'The length of source_ratio must be equal to ' \ + f'the number of datasets, but got source_ratio={source_ratio}' + + rank, world_size = get_dist_info() + self.rank = rank + self.world_size = world_size + + self.dataset = dataset + self.cumulative_sizes = [0] + dataset.cumulative_sizes + self.batch_size = batch_size + self.source_ratio = source_ratio + + self.num_per_source = [ + int(batch_size * sr / sum(source_ratio)) for sr in source_ratio + ] + self.num_per_source[0] = batch_size - sum(self.num_per_source[1:]) + + assert sum(self.num_per_source) == batch_size, \ + 'The sum of num_per_source must be equal to ' \ + f'batch_size, but get {self.num_per_source}' + + self.seed = sync_random_seed() if seed is None else seed + self.shuffle = shuffle + self.source2inds = { + source: self._indices_of_rank(len(ds)) + for source, ds in enumerate(dataset.datasets) + } + + def _infinite_indices(self, sample_size: int) -> Iterator[int]: + """Infinitely yield a sequence of indices.""" + g = torch.Generator() + g.manual_seed(self.seed) + while True: + if self.shuffle: + yield from torch.randperm(sample_size, generator=g).tolist() + else: + yield from torch.arange(sample_size).tolist() + + def _indices_of_rank(self, sample_size: int) -> Iterator[int]: + """Slice the infinite indices by rank.""" + yield from itertools.islice( + self._infinite_indices(sample_size), self.rank, None, + self.world_size) + + def __iter__(self) -> Iterator[int]: + batch_buffer = [] + while True: + for source, num in enumerate(self.num_per_source): + batch_buffer_per_source = [] + for idx in self.source2inds[source]: + idx += self.cumulative_sizes[source] + batch_buffer_per_source.append(idx) + if len(batch_buffer_per_source) == num: + batch_buffer += batch_buffer_per_source + break + yield from batch_buffer + batch_buffer = [] + + def __len__(self) -> int: + return len(self.dataset) + + def set_epoch(self, epoch: int) -> None: + """Not supported in `epoch-based runner.""" + pass + + +@DATA_SAMPLERS.register_module() +class GroupMultiSourceSampler(MultiSourceSampler): + r"""Group Multi-Source Infinite Sampler. + + According to the sampling ratio, sample data from different + datasets but the same group to form batches. + + Args: + dataset (Sized): The dataset. + batch_size (int): Size of mini-batch. + source_ratio (list[int | float]): The sampling ratio of different + source datasets in a mini-batch. + shuffle (bool): Whether shuffle the dataset or not. Defaults to True. + seed (int, optional): Random seed. If None, set a random seed. + Defaults to None. + """ + + def __init__(self, + dataset: BaseDataset, + batch_size: int, + source_ratio: List[Union[int, float]], + shuffle: bool = True, + seed: Optional[int] = None) -> None: + super().__init__( + dataset=dataset, + batch_size=batch_size, + source_ratio=source_ratio, + shuffle=shuffle, + seed=seed) + + self._get_source_group_info() + self.group_source2inds = [{ + source: + self._indices_of_rank(self.group2size_per_source[source][group]) + for source in range(len(dataset.datasets)) + } for group in range(len(self.group_ratio))] + + def _get_source_group_info(self) -> None: + self.group2size_per_source = [{0: 0, 1: 0}, {0: 0, 1: 0}] + self.group2inds_per_source = [{0: [], 1: []}, {0: [], 1: []}] + for source, dataset in enumerate(self.dataset.datasets): + for idx in range(len(dataset)): + data_info = dataset.get_data_info(idx) + width, height = data_info['width'], data_info['height'] + group = 0 if width < height else 1 + self.group2size_per_source[source][group] += 1 + self.group2inds_per_source[source][group].append(idx) + + self.group_sizes = np.zeros(2, dtype=np.int64) + for group2size in self.group2size_per_source: + for group, size in group2size.items(): + self.group_sizes[group] += size + self.group_ratio = self.group_sizes / sum(self.group_sizes) + + def __iter__(self) -> Iterator[int]: + batch_buffer = [] + while True: + group = np.random.choice( + list(range(len(self.group_ratio))), p=self.group_ratio) + for source, num in enumerate(self.num_per_source): + batch_buffer_per_source = [] + for idx in self.group_source2inds[group][source]: + idx = self.group2inds_per_source[source][group][ + idx] + self.cumulative_sizes[source] + batch_buffer_per_source.append(idx) + if len(batch_buffer_per_source) == num: + batch_buffer += batch_buffer_per_source + break + yield from batch_buffer + batch_buffer = [] diff --git a/mmdetection/mmdet/datasets/samplers/track_img_sampler.py b/mmdetection/mmdet/datasets/samplers/track_img_sampler.py new file mode 100644 index 00000000..d7db629f --- /dev/null +++ b/mmdetection/mmdet/datasets/samplers/track_img_sampler.py @@ -0,0 +1,146 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +import random +from typing import Iterator, Optional, Sized + +import numpy as np +from mmengine.dataset import ClassBalancedDataset, ConcatDataset +from mmengine.dist import get_dist_info, sync_random_seed +from torch.utils.data import Sampler + +from mmdet.registry import DATA_SAMPLERS +from ..base_video_dataset import BaseVideoDataset + + +@DATA_SAMPLERS.register_module() +class TrackImgSampler(Sampler): + """Sampler that providing image-level sampling outputs for video datasets + in tracking tasks. It could be both used in both distributed and + non-distributed environment. + If using the default sampler in pytorch, the subsequent data receiver will + get one video, which is not desired in some cases: + (Take a non-distributed environment as an example) + 1. In test mode, we want only one image is fed into the data pipeline. This + is in consideration of memory usage since feeding the whole video commonly + requires a large amount of memory (>=20G on MOTChallenge17 dataset), which + is not available in some machines. + 2. In training mode, we may want to make sure all the images in one video + are randomly sampled once in one epoch and this can not be guaranteed in + the default sampler in pytorch. + + Args: + dataset (Sized): Dataset used for sampling. + seed (int, optional): random seed used to shuffle the sampler. This + number should be identical across all processes in the distributed + group. Defaults to None. + """ + + def __init__( + self, + dataset: Sized, + seed: Optional[int] = None, + ) -> None: + rank, world_size = get_dist_info() + self.rank = rank + self.world_size = world_size + self.epoch = 0 + if seed is None: + self.seed = sync_random_seed() + else: + self.seed = seed + + self.dataset = dataset + self.indices = [] + # Hard code here to handle different dataset wrapper + if isinstance(self.dataset, ConcatDataset): + cat_datasets = self.dataset.datasets + assert isinstance( + cat_datasets[0], BaseVideoDataset + ), f'expected BaseVideoDataset, but got {type(cat_datasets[0])}' + self.test_mode = cat_datasets[0].test_mode + assert not self.test_mode, "'ConcatDataset' should not exist in " + 'test mode' + for dataset in cat_datasets: + num_videos = len(dataset) + for video_ind in range(num_videos): + self.indices.extend([ + (video_ind, frame_ind) for frame_ind in range( + dataset.get_len_per_video(video_ind)) + ]) + elif isinstance(self.dataset, ClassBalancedDataset): + ori_dataset = self.dataset.dataset + assert isinstance( + ori_dataset, BaseVideoDataset + ), f'expected BaseVideoDataset, but got {type(ori_dataset)}' + self.test_mode = ori_dataset.test_mode + assert not self.test_mode, "'ClassBalancedDataset' should not " + 'exist in test mode' + video_indices = self.dataset.repeat_indices + for index in video_indices: + self.indices.extend([(index, frame_ind) for frame_ind in range( + ori_dataset.get_len_per_video(index))]) + else: + assert isinstance( + self.dataset, BaseVideoDataset + ), 'TrackImgSampler is only supported in BaseVideoDataset or ' + 'dataset wrapper: ClassBalancedDataset and ConcatDataset, but ' + f'got {type(self.dataset)} ' + self.test_mode = self.dataset.test_mode + num_videos = len(self.dataset) + + if self.test_mode: + # in test mode, the images belong to the same video must be put + # on the same device. + if num_videos < self.world_size: + raise ValueError(f'only {num_videos} videos loaded,' + f'but {self.world_size} gpus were given.') + chunks = np.array_split( + list(range(num_videos)), self.world_size) + for videos_inds in chunks: + indices_chunk = [] + for video_ind in videos_inds: + indices_chunk.extend([ + (video_ind, frame_ind) for frame_ind in range( + self.dataset.get_len_per_video(video_ind)) + ]) + self.indices.append(indices_chunk) + else: + for video_ind in range(num_videos): + self.indices.extend([ + (video_ind, frame_ind) for frame_ind in range( + self.dataset.get_len_per_video(video_ind)) + ]) + + if self.test_mode: + self.num_samples = len(self.indices[self.rank]) + self.total_size = sum( + [len(index_list) for index_list in self.indices]) + else: + self.num_samples = int( + math.ceil(len(self.indices) * 1.0 / self.world_size)) + self.total_size = self.num_samples * self.world_size + + def __iter__(self) -> Iterator: + if self.test_mode: + # in test mode, the order of frames can not be shuffled. + indices = self.indices[self.rank] + else: + # deterministically shuffle based on epoch + rng = random.Random(self.epoch + self.seed) + indices = rng.sample(self.indices, len(self.indices)) + + # add extra samples to make it evenly divisible + indices += indices[:(self.total_size - len(indices))] + assert len(indices) == self.total_size + + # subsample + indices = indices[self.rank:self.total_size:self.world_size] + assert len(indices) == self.num_samples + + return iter(indices) + + def __len__(self): + return self.num_samples + + def set_epoch(self, epoch): + self.epoch = epoch diff --git a/mmdetection/mmdet/datasets/transforms/__init__.py b/mmdetection/mmdet/datasets/transforms/__init__.py new file mode 100644 index 00000000..ab3478fe --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/__init__.py @@ -0,0 +1,45 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .augment_wrappers import AutoAugment, RandAugment +from .colorspace import (AutoContrast, Brightness, Color, ColorTransform, + Contrast, Equalize, Invert, Posterize, Sharpness, + Solarize, SolarizeAdd) +from .formatting import (ImageToTensor, PackDetInputs, PackReIDInputs, + PackTrackInputs, ToTensor, Transpose) +from .frame_sampling import BaseFrameSample, UniformRefFrameSample +from .geometric import (GeomTransform, Rotate, ShearX, ShearY, TranslateX, + TranslateY) +from .instaboost import InstaBoost +from .loading import (FilterAnnotations, InferencerLoader, LoadAnnotations, + LoadEmptyAnnotations, LoadImageFromNDArray, + LoadMultiChannelImageFromFiles, LoadPanopticAnnotations, + LoadProposals, LoadTrackAnnotations) +from .text_transformers import LoadTextAnnotations, RandomSamplingNegPos +from .transformers_glip import GTBoxSubOne_GLIP, RandomFlip_GLIP +from .transforms import (Albu, CachedMixUp, CachedMosaic, CopyPaste, CutOut, + Expand, FixScaleResize, FixShapeResize, + MinIoURandomCrop, MixUp, Mosaic, Pad, + PhotoMetricDistortion, RandomAffine, + RandomCenterCropPad, RandomCrop, RandomErasing, + RandomFlip, RandomShift, Resize, ResizeShortestEdge, + SegRescale, YOLOXHSVRandomAug) +from .wrappers import MultiBranch, ProposalBroadcaster, RandomOrder + +__all__ = [ + 'PackDetInputs', 'ToTensor', 'ImageToTensor', 'Transpose', + 'LoadImageFromNDArray', 'LoadAnnotations', 'LoadPanopticAnnotations', + 'LoadMultiChannelImageFromFiles', 'LoadProposals', 'Resize', 'RandomFlip', + 'RandomCrop', 'SegRescale', 'MinIoURandomCrop', 'Expand', + 'PhotoMetricDistortion', 'Albu', 'InstaBoost', 'RandomCenterCropPad', + 'AutoAugment', 'CutOut', 'ShearX', 'ShearY', 'Rotate', 'Color', 'Equalize', + 'Brightness', 'Contrast', 'TranslateX', 'TranslateY', 'RandomShift', + 'Mosaic', 'MixUp', 'RandomAffine', 'YOLOXHSVRandomAug', 'CopyPaste', + 'FilterAnnotations', 'Pad', 'GeomTransform', 'ColorTransform', + 'RandAugment', 'Sharpness', 'Solarize', 'SolarizeAdd', 'Posterize', + 'AutoContrast', 'Invert', 'MultiBranch', 'RandomErasing', + 'LoadEmptyAnnotations', 'RandomOrder', 'CachedMosaic', 'CachedMixUp', + 'FixShapeResize', 'ProposalBroadcaster', 'InferencerLoader', + 'LoadTrackAnnotations', 'BaseFrameSample', 'UniformRefFrameSample', + 'PackTrackInputs', 'PackReIDInputs', 'FixScaleResize', + 'ResizeShortestEdge', 'GTBoxSubOne_GLIP', 'RandomFlip_GLIP', + 'RandomSamplingNegPos', 'LoadTextAnnotations' +] diff --git a/mmdetection/mmdet/datasets/transforms/augment_wrappers.py b/mmdetection/mmdet/datasets/transforms/augment_wrappers.py new file mode 100644 index 00000000..19fae6ef --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/augment_wrappers.py @@ -0,0 +1,264 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Union + +import numpy as np +from mmcv.transforms import RandomChoice +from mmcv.transforms.utils import cache_randomness +from mmengine.config import ConfigDict + +from mmdet.registry import TRANSFORMS + +# AutoAugment uses reinforcement learning to search for +# some widely useful data augmentation strategies, +# here we provide AUTOAUG_POLICIES_V0. +# For AUTOAUG_POLICIES_V0, each tuple is an augmentation +# operation of the form (operation, probability, magnitude). +# Each element in policies is a policy that will be applied +# sequentially on the image. + +# RandAugment defines a data augmentation search space, RANDAUG_SPACE, +# sampling 1~3 data augmentations each time, and +# setting the magnitude of each data augmentation randomly, +# which will be applied sequentially on the image. + +_MAX_LEVEL = 10 + +AUTOAUG_POLICIES_V0 = [ + [('Equalize', 0.8, 1), ('ShearY', 0.8, 4)], + [('Color', 0.4, 9), ('Equalize', 0.6, 3)], + [('Color', 0.4, 1), ('Rotate', 0.6, 8)], + [('Solarize', 0.8, 3), ('Equalize', 0.4, 7)], + [('Solarize', 0.4, 2), ('Solarize', 0.6, 2)], + [('Color', 0.2, 0), ('Equalize', 0.8, 8)], + [('Equalize', 0.4, 8), ('SolarizeAdd', 0.8, 3)], + [('ShearX', 0.2, 9), ('Rotate', 0.6, 8)], + [('Color', 0.6, 1), ('Equalize', 1.0, 2)], + [('Invert', 0.4, 9), ('Rotate', 0.6, 0)], + [('Equalize', 1.0, 9), ('ShearY', 0.6, 3)], + [('Color', 0.4, 7), ('Equalize', 0.6, 0)], + [('Posterize', 0.4, 6), ('AutoContrast', 0.4, 7)], + [('Solarize', 0.6, 8), ('Color', 0.6, 9)], + [('Solarize', 0.2, 4), ('Rotate', 0.8, 9)], + [('Rotate', 1.0, 7), ('TranslateY', 0.8, 9)], + [('ShearX', 0.0, 0), ('Solarize', 0.8, 4)], + [('ShearY', 0.8, 0), ('Color', 0.6, 4)], + [('Color', 1.0, 0), ('Rotate', 0.6, 2)], + [('Equalize', 0.8, 4), ('Equalize', 0.0, 8)], + [('Equalize', 1.0, 4), ('AutoContrast', 0.6, 2)], + [('ShearY', 0.4, 7), ('SolarizeAdd', 0.6, 7)], + [('Posterize', 0.8, 2), ('Solarize', 0.6, 10)], + [('Solarize', 0.6, 8), ('Equalize', 0.6, 1)], + [('Color', 0.8, 6), ('Rotate', 0.4, 5)], +] + + +def policies_v0(): + """Autoaugment policies that was used in AutoAugment Paper.""" + policies = list() + for policy_args in AUTOAUG_POLICIES_V0: + policy = list() + for args in policy_args: + policy.append(dict(type=args[0], prob=args[1], level=args[2])) + policies.append(policy) + return policies + + +RANDAUG_SPACE = [[dict(type='AutoContrast')], [dict(type='Equalize')], + [dict(type='Invert')], [dict(type='Rotate')], + [dict(type='Posterize')], [dict(type='Solarize')], + [dict(type='SolarizeAdd')], [dict(type='Color')], + [dict(type='Contrast')], [dict(type='Brightness')], + [dict(type='Sharpness')], [dict(type='ShearX')], + [dict(type='ShearY')], [dict(type='TranslateX')], + [dict(type='TranslateY')]] + + +def level_to_mag(level: Optional[int], min_mag: float, + max_mag: float) -> float: + """Map from level to magnitude.""" + if level is None: + return round(np.random.rand() * (max_mag - min_mag) + min_mag, 1) + else: + return round(level / _MAX_LEVEL * (max_mag - min_mag) + min_mag, 1) + + +@TRANSFORMS.register_module() +class AutoAugment(RandomChoice): + """Auto augmentation. + + This data augmentation is proposed in `AutoAugment: Learning + Augmentation Policies from Data `_ + and in `Learning Data Augmentation Strategies for Object Detection + `_. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_ignore_flags (bool) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes + - gt_bboxes_labels + - gt_masks + - gt_ignore_flags + - gt_seg_map + + Added Keys: + + - homography_matrix + + Args: + policies (List[List[Union[dict, ConfigDict]]]): + The policies of auto augmentation.Each policy in ``policies`` + is a specific augmentation policy, and is composed by several + augmentations. When AutoAugment is called, a random policy in + ``policies`` will be selected to augment images. + Defaults to policy_v0(). + prob (list[float], optional): The probabilities associated + with each policy. The length should be equal to the policy + number and the sum should be 1. If not given, a uniform + distribution will be assumed. Defaults to None. + + Examples: + >>> policies = [ + >>> [ + >>> dict(type='Sharpness', prob=0.0, level=8), + >>> dict(type='ShearX', prob=0.4, level=0,) + >>> ], + >>> [ + >>> dict(type='Rotate', prob=0.6, level=10), + >>> dict(type='Color', prob=1.0, level=6) + >>> ] + >>> ] + >>> augmentation = AutoAugment(policies) + >>> img = np.ones(100, 100, 3) + >>> gt_bboxes = np.ones(10, 4) + >>> results = dict(img=img, gt_bboxes=gt_bboxes) + >>> results = augmentation(results) + """ + + def __init__(self, + policies: List[List[Union[dict, ConfigDict]]] = policies_v0(), + prob: Optional[List[float]] = None) -> None: + assert isinstance(policies, list) and len(policies) > 0, \ + 'Policies must be a non-empty list.' + for policy in policies: + assert isinstance(policy, list) and len(policy) > 0, \ + 'Each policy in policies must be a non-empty list.' + for augment in policy: + assert isinstance(augment, dict) and 'type' in augment, \ + 'Each specific augmentation must be a dict with key' \ + ' "type".' + super().__init__(transforms=policies, prob=prob) + self.policies = policies + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(policies={self.policies}, ' \ + f'prob={self.prob})' + + +@TRANSFORMS.register_module() +class RandAugment(RandomChoice): + """Rand augmentation. + + This data augmentation is proposed in `RandAugment: + Practical automated data augmentation with a reduced + search space `_. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_ignore_flags (bool) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes + - gt_bboxes_labels + - gt_masks + - gt_ignore_flags + - gt_seg_map + + Added Keys: + + - homography_matrix + + Args: + aug_space (List[List[Union[dict, ConfigDict]]]): The augmentation space + of rand augmentation. Each augmentation transform in ``aug_space`` + is a specific transform, and is composed by several augmentations. + When RandAugment is called, a random transform in ``aug_space`` + will be selected to augment images. Defaults to aug_space. + aug_num (int): Number of augmentation to apply equentially. + Defaults to 2. + prob (list[float], optional): The probabilities associated with + each augmentation. The length should be equal to the + augmentation space and the sum should be 1. If not given, + a uniform distribution will be assumed. Defaults to None. + + Examples: + >>> aug_space = [ + >>> dict(type='Sharpness'), + >>> dict(type='ShearX'), + >>> dict(type='Color'), + >>> ], + >>> augmentation = RandAugment(aug_space) + >>> img = np.ones(100, 100, 3) + >>> gt_bboxes = np.ones(10, 4) + >>> results = dict(img=img, gt_bboxes=gt_bboxes) + >>> results = augmentation(results) + """ + + def __init__(self, + aug_space: List[Union[dict, ConfigDict]] = RANDAUG_SPACE, + aug_num: int = 2, + prob: Optional[List[float]] = None) -> None: + assert isinstance(aug_space, list) and len(aug_space) > 0, \ + 'Augmentation space must be a non-empty list.' + for aug in aug_space: + assert isinstance(aug, list) and len(aug) == 1, \ + 'Each augmentation in aug_space must be a list.' + for transform in aug: + assert isinstance(transform, dict) and 'type' in transform, \ + 'Each specific transform must be a dict with key' \ + ' "type".' + super().__init__(transforms=aug_space, prob=prob) + self.aug_space = aug_space + self.aug_num = aug_num + + @cache_randomness + def random_pipeline_index(self): + indices = np.arange(len(self.transforms)) + return np.random.choice( + indices, self.aug_num, p=self.prob, replace=False) + + def transform(self, results: dict) -> dict: + """Transform function to use RandAugment. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with RandAugment. + """ + for idx in self.random_pipeline_index(): + results = self.transforms[idx](results) + return results + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(' \ + f'aug_space={self.aug_space}, '\ + f'aug_num={self.aug_num}, ' \ + f'prob={self.prob})' diff --git a/mmdetection/mmdet/datasets/transforms/colorspace.py b/mmdetection/mmdet/datasets/transforms/colorspace.py new file mode 100644 index 00000000..e0ba2e97 --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/colorspace.py @@ -0,0 +1,493 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Optional + +import mmcv +import numpy as np +from mmcv.transforms import BaseTransform +from mmcv.transforms.utils import cache_randomness + +from mmdet.registry import TRANSFORMS +from .augment_wrappers import _MAX_LEVEL, level_to_mag + + +@TRANSFORMS.register_module() +class ColorTransform(BaseTransform): + """Base class for color transformations. All color transformations need to + inherit from this base class. ``ColorTransform`` unifies the class + attributes and class functions of color transformations (Color, Brightness, + Contrast, Sharpness, Solarize, SolarizeAdd, Equalize, AutoContrast, Invert, + and Posterize), and only distort color channels, without impacting the + locations of the instances. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing the geometric + transformation and should be in range [0, 1]. Defaults to 1.0. + level (int, optional): The level should be in range [0, _MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum magnitude for color transformation. + Defaults to 0.1. + max_mag (float): The maximum magnitude for color transformation. + Defaults to 1.9. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.1, + max_mag: float = 1.9) -> None: + assert 0 <= prob <= 1.0, f'The probability of the transformation ' \ + f'should be in range [0,1], got {prob}.' + assert level is None or isinstance(level, int), \ + f'The level should be None or type int, got {type(level)}.' + assert level is None or 0 <= level <= _MAX_LEVEL, \ + f'The level should be in range [0,{_MAX_LEVEL}], got {level}.' + assert isinstance(min_mag, float), \ + f'min_mag should be type float, got {type(min_mag)}.' + assert isinstance(max_mag, float), \ + f'max_mag should be type float, got {type(max_mag)}.' + assert min_mag <= max_mag, \ + f'min_mag should smaller than max_mag, ' \ + f'got min_mag={min_mag} and max_mag={max_mag}' + self.prob = prob + self.level = level + self.min_mag = min_mag + self.max_mag = max_mag + + def _transform_img(self, results: dict, mag: float) -> None: + """Transform the image.""" + pass + + @cache_randomness + def _random_disable(self): + """Randomly disable the transform.""" + return np.random.rand() > self.prob + + @cache_randomness + def _get_mag(self): + """Get the magnitude of the transform.""" + return level_to_mag(self.level, self.min_mag, self.max_mag) + + def transform(self, results: dict) -> dict: + """Transform function for images. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Transformed results. + """ + + if self._random_disable(): + return results + mag = self._get_mag() + self._transform_img(results, mag) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, ' + repr_str += f'level={self.level}, ' + repr_str += f'min_mag={self.min_mag}, ' + repr_str += f'max_mag={self.max_mag})' + return repr_str + + +@TRANSFORMS.register_module() +class Color(ColorTransform): + """Adjust the color balance of the image, in a manner similar to the + controls on a colour TV set. A magnitude=0 gives a black & white image, + whereas magnitude=1 gives the original image. The bboxes, masks and + segmentations are not modified. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing Color transformation. + Defaults to 1.0. + level (int, optional): Should be in range [0,_MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum magnitude for Color transformation. + Defaults to 0.1. + max_mag (float): The maximum magnitude for Color transformation. + Defaults to 1.9. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.1, + max_mag: float = 1.9) -> None: + assert 0. <= min_mag <= 2.0, \ + f'min_mag for Color should be in range [0,2], got {min_mag}.' + assert 0. <= max_mag <= 2.0, \ + f'max_mag for Color should be in range [0,2], got {max_mag}.' + super().__init__( + prob=prob, level=level, min_mag=min_mag, max_mag=max_mag) + + def _transform_img(self, results: dict, mag: float) -> None: + """Apply Color transformation to image.""" + # NOTE defaultly the image should be BGR format + img = results['img'] + results['img'] = mmcv.adjust_color(img, mag).astype(img.dtype) + + +@TRANSFORMS.register_module() +class Brightness(ColorTransform): + """Adjust the brightness of the image. A magnitude=0 gives a black image, + whereas magnitude=1 gives the original image. The bboxes, masks and + segmentations are not modified. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing Brightness transformation. + Defaults to 1.0. + level (int, optional): Should be in range [0,_MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum magnitude for Brightness transformation. + Defaults to 0.1. + max_mag (float): The maximum magnitude for Brightness transformation. + Defaults to 1.9. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.1, + max_mag: float = 1.9) -> None: + assert 0. <= min_mag <= 2.0, \ + f'min_mag for Brightness should be in range [0,2], got {min_mag}.' + assert 0. <= max_mag <= 2.0, \ + f'max_mag for Brightness should be in range [0,2], got {max_mag}.' + super().__init__( + prob=prob, level=level, min_mag=min_mag, max_mag=max_mag) + + def _transform_img(self, results: dict, mag: float) -> None: + """Adjust the brightness of image.""" + img = results['img'] + results['img'] = mmcv.adjust_brightness(img, mag).astype(img.dtype) + + +@TRANSFORMS.register_module() +class Contrast(ColorTransform): + """Control the contrast of the image. A magnitude=0 gives a gray image, + whereas magnitude=1 gives the original imageThe bboxes, masks and + segmentations are not modified. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing Contrast transformation. + Defaults to 1.0. + level (int, optional): Should be in range [0,_MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum magnitude for Contrast transformation. + Defaults to 0.1. + max_mag (float): The maximum magnitude for Contrast transformation. + Defaults to 1.9. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.1, + max_mag: float = 1.9) -> None: + assert 0. <= min_mag <= 2.0, \ + f'min_mag for Contrast should be in range [0,2], got {min_mag}.' + assert 0. <= max_mag <= 2.0, \ + f'max_mag for Contrast should be in range [0,2], got {max_mag}.' + super().__init__( + prob=prob, level=level, min_mag=min_mag, max_mag=max_mag) + + def _transform_img(self, results: dict, mag: float) -> None: + """Adjust the image contrast.""" + img = results['img'] + results['img'] = mmcv.adjust_contrast(img, mag).astype(img.dtype) + + +@TRANSFORMS.register_module() +class Sharpness(ColorTransform): + """Adjust images sharpness. A positive magnitude would enhance the + sharpness and a negative magnitude would make the image blurry. A + magnitude=0 gives the origin img. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing Sharpness transformation. + Defaults to 1.0. + level (int, optional): Should be in range [0,_MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum magnitude for Sharpness transformation. + Defaults to 0.1. + max_mag (float): The maximum magnitude for Sharpness transformation. + Defaults to 1.9. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.1, + max_mag: float = 1.9) -> None: + assert 0. <= min_mag <= 2.0, \ + f'min_mag for Sharpness should be in range [0,2], got {min_mag}.' + assert 0. <= max_mag <= 2.0, \ + f'max_mag for Sharpness should be in range [0,2], got {max_mag}.' + super().__init__( + prob=prob, level=level, min_mag=min_mag, max_mag=max_mag) + + def _transform_img(self, results: dict, mag: float) -> None: + """Adjust the image sharpness.""" + img = results['img'] + results['img'] = mmcv.adjust_sharpness(img, mag).astype(img.dtype) + + +@TRANSFORMS.register_module() +class Solarize(ColorTransform): + """Solarize images (Invert all pixels above a threshold value of + magnitude.). + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing Solarize transformation. + Defaults to 1.0. + level (int, optional): Should be in range [0,_MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum magnitude for Solarize transformation. + Defaults to 0.0. + max_mag (float): The maximum magnitude for Solarize transformation. + Defaults to 256.0. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.0, + max_mag: float = 256.0) -> None: + assert 0. <= min_mag <= 256.0, f'min_mag for Solarize should be ' \ + f'in range [0, 256], got {min_mag}.' + assert 0. <= max_mag <= 256.0, f'max_mag for Solarize should be ' \ + f'in range [0, 256], got {max_mag}.' + super().__init__( + prob=prob, level=level, min_mag=min_mag, max_mag=max_mag) + + def _transform_img(self, results: dict, mag: float) -> None: + """Invert all pixel values above magnitude.""" + img = results['img'] + results['img'] = mmcv.solarize(img, mag).astype(img.dtype) + + +@TRANSFORMS.register_module() +class SolarizeAdd(ColorTransform): + """SolarizeAdd images. For each pixel in the image that is less than 128, + add an additional amount to it decided by the magnitude. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing SolarizeAdd + transformation. Defaults to 1.0. + level (int, optional): Should be in range [0,_MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum magnitude for SolarizeAdd transformation. + Defaults to 0.0. + max_mag (float): The maximum magnitude for SolarizeAdd transformation. + Defaults to 110.0. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.0, + max_mag: float = 110.0) -> None: + assert 0. <= min_mag <= 110.0, f'min_mag for SolarizeAdd should be ' \ + f'in range [0, 110], got {min_mag}.' + assert 0. <= max_mag <= 110.0, f'max_mag for SolarizeAdd should be ' \ + f'in range [0, 110], got {max_mag}.' + super().__init__( + prob=prob, level=level, min_mag=min_mag, max_mag=max_mag) + + def _transform_img(self, results: dict, mag: float) -> None: + """SolarizeAdd the image.""" + img = results['img'] + img_solarized = np.where(img < 128, np.minimum(img + mag, 255), img) + results['img'] = img_solarized.astype(img.dtype) + + +@TRANSFORMS.register_module() +class Posterize(ColorTransform): + """Posterize images (reduce the number of bits for each color channel). + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing Posterize + transformation. Defaults to 1.0. + level (int, optional): Should be in range [0,_MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum magnitude for Posterize transformation. + Defaults to 0.0. + max_mag (float): The maximum magnitude for Posterize transformation. + Defaults to 4.0. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.0, + max_mag: float = 4.0) -> None: + assert 0. <= min_mag <= 8.0, f'min_mag for Posterize should be ' \ + f'in range [0, 8], got {min_mag}.' + assert 0. <= max_mag <= 8.0, f'max_mag for Posterize should be ' \ + f'in range [0, 8], got {max_mag}.' + super().__init__( + prob=prob, level=level, min_mag=min_mag, max_mag=max_mag) + + def _transform_img(self, results: dict, mag: float) -> None: + """Posterize the image.""" + img = results['img'] + results['img'] = mmcv.posterize(img, math.ceil(mag)).astype(img.dtype) + + +@TRANSFORMS.register_module() +class Equalize(ColorTransform): + """Equalize the image histogram. The bboxes, masks and segmentations are + not modified. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing Equalize transformation. + Defaults to 1.0. + level (int, optional): No use for Equalize transformation. + Defaults to None. + min_mag (float): No use for Equalize transformation. Defaults to 0.1. + max_mag (float): No use for Equalize transformation. Defaults to 1.9. + """ + + def _transform_img(self, results: dict, mag: float) -> None: + """Equalizes the histogram of one image.""" + img = results['img'] + results['img'] = mmcv.imequalize(img).astype(img.dtype) + + +@TRANSFORMS.register_module() +class AutoContrast(ColorTransform): + """Auto adjust image contrast. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing AutoContrast should + be in range [0, 1]. Defaults to 1.0. + level (int, optional): No use for AutoContrast transformation. + Defaults to None. + min_mag (float): No use for AutoContrast transformation. + Defaults to 0.1. + max_mag (float): No use for AutoContrast transformation. + Defaults to 1.9. + """ + + def _transform_img(self, results: dict, mag: float) -> None: + """Auto adjust image contrast.""" + img = results['img'] + results['img'] = mmcv.auto_contrast(img).astype(img.dtype) + + +@TRANSFORMS.register_module() +class Invert(ColorTransform): + """Invert images. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + prob (float): The probability for performing invert therefore should + be in range [0, 1]. Defaults to 1.0. + level (int, optional): No use for Invert transformation. + Defaults to None. + min_mag (float): No use for Invert transformation. Defaults to 0.1. + max_mag (float): No use for Invert transformation. Defaults to 1.9. + """ + + def _transform_img(self, results: dict, mag: float) -> None: + """Invert the image.""" + img = results['img'] + results['img'] = mmcv.iminvert(img).astype(img.dtype) diff --git a/mmdetection/mmdet/datasets/transforms/formatting.py b/mmdetection/mmdet/datasets/transforms/formatting.py new file mode 100644 index 00000000..05263807 --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/formatting.py @@ -0,0 +1,512 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence + +import numpy as np +from mmcv.transforms import to_tensor +from mmcv.transforms.base import BaseTransform +from mmengine.structures import InstanceData, PixelData + +from mmdet.registry import TRANSFORMS +from mmdet.structures import DetDataSample, ReIDDataSample, TrackDataSample +from mmdet.structures.bbox import BaseBoxes + + +@TRANSFORMS.register_module() +class PackDetInputs(BaseTransform): + """Pack the inputs data for the detection / semantic segmentation / + panoptic segmentation. + + The ``img_meta`` item is always populated. The contents of the + ``img_meta`` dictionary depends on ``meta_keys``. By default this includes: + + - ``img_id``: id of the image + + - ``img_path``: path to the image file + + - ``ori_shape``: original shape of the image as a tuple (h, w) + + - ``img_shape``: shape of the image input to the network as a tuple \ + (h, w). Note that images may be zero padded on the \ + bottom/right if the batch tensor is larger than this shape. + + - ``scale_factor``: a float indicating the preprocessing scale + + - ``flip``: a boolean indicating if image flip transform was used + + - ``flip_direction``: the flipping direction + + Args: + meta_keys (Sequence[str], optional): Meta keys to be converted to + ``mmcv.DataContainer`` and collected in ``data[img_metas]``. + Default: ``('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction')`` + """ + mapping_table = { + 'gt_bboxes': 'bboxes', + 'gt_bboxes_labels': 'labels', + 'gt_masks': 'masks' + } + + def __init__(self, + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'flip', 'flip_direction')): + self.meta_keys = meta_keys + + def transform(self, results: dict) -> dict: + """Method to pack the input data. + + Args: + results (dict): Result dict from the data pipeline. + + Returns: + dict: + + - 'inputs' (obj:`torch.Tensor`): The forward data of models. + - 'data_sample' (obj:`DetDataSample`): The annotation info of the + sample. + """ + packed_results = dict() + if 'img' in results: + img = results['img'] + if len(img.shape) < 3: + img = np.expand_dims(img, -1) + # To improve the computational speed by by 3-5 times, apply: + # If image is not contiguous, use + # `numpy.transpose()` followed by `numpy.ascontiguousarray()` + # If image is already contiguous, use + # `torch.permute()` followed by `torch.contiguous()` + # Refer to https://github.com/open-mmlab/mmdetection/pull/9533 + # for more details + if not img.flags.c_contiguous: + img = np.ascontiguousarray(img.transpose(2, 0, 1)) + img = to_tensor(img) + else: + img = to_tensor(img).permute(2, 0, 1).contiguous() + + packed_results['inputs'] = img + + if 'gt_ignore_flags' in results: + valid_idx = np.where(results['gt_ignore_flags'] == 0)[0] + ignore_idx = np.where(results['gt_ignore_flags'] == 1)[0] + + data_sample = DetDataSample() + instance_data = InstanceData() + ignore_instance_data = InstanceData() + + for key in self.mapping_table.keys(): + if key not in results: + continue + if key == 'gt_masks' or isinstance(results[key], BaseBoxes): + if 'gt_ignore_flags' in results: + instance_data[ + self.mapping_table[key]] = results[key][valid_idx] + ignore_instance_data[ + self.mapping_table[key]] = results[key][ignore_idx] + else: + instance_data[self.mapping_table[key]] = results[key] + else: + if 'gt_ignore_flags' in results: + instance_data[self.mapping_table[key]] = to_tensor( + results[key][valid_idx]) + ignore_instance_data[self.mapping_table[key]] = to_tensor( + results[key][ignore_idx]) + else: + instance_data[self.mapping_table[key]] = to_tensor( + results[key]) + data_sample.gt_instances = instance_data + data_sample.ignored_instances = ignore_instance_data + + if 'proposals' in results: + proposals = InstanceData( + bboxes=to_tensor(results['proposals']), + scores=to_tensor(results['proposals_scores'])) + data_sample.proposals = proposals + + if 'gt_seg_map' in results: + gt_sem_seg_data = dict( + sem_seg=to_tensor(results['gt_seg_map'][None, ...].copy())) + gt_sem_seg_data = PixelData(**gt_sem_seg_data) + if 'ignore_index' in results: + metainfo = dict(ignore_index=results['ignore_index']) + gt_sem_seg_data.set_metainfo(metainfo) + data_sample.gt_sem_seg = gt_sem_seg_data + + img_meta = {} + for key in self.meta_keys: + if key in results: + img_meta[key] = results[key] + data_sample.set_metainfo(img_meta) + packed_results['data_samples'] = data_sample + + return packed_results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(meta_keys={self.meta_keys})' + return repr_str + + +@TRANSFORMS.register_module() +class ToTensor: + """Convert some results to :obj:`torch.Tensor` by given keys. + + Args: + keys (Sequence[str]): Keys that need to be converted to Tensor. + """ + + def __init__(self, keys): + self.keys = keys + + def __call__(self, results): + """Call function to convert data in results to :obj:`torch.Tensor`. + + Args: + results (dict): Result dict contains the data to convert. + + Returns: + dict: The result dict contains the data converted + to :obj:`torch.Tensor`. + """ + for key in self.keys: + results[key] = to_tensor(results[key]) + return results + + def __repr__(self): + return self.__class__.__name__ + f'(keys={self.keys})' + + +@TRANSFORMS.register_module() +class ImageToTensor: + """Convert image to :obj:`torch.Tensor` by given keys. + + The dimension order of input image is (H, W, C). The pipeline will convert + it to (C, H, W). If only 2 dimension (H, W) is given, the output would be + (1, H, W). + + Args: + keys (Sequence[str]): Key of images to be converted to Tensor. + """ + + def __init__(self, keys): + self.keys = keys + + def __call__(self, results): + """Call function to convert image in results to :obj:`torch.Tensor` and + transpose the channel order. + + Args: + results (dict): Result dict contains the image data to convert. + + Returns: + dict: The result dict contains the image converted + to :obj:`torch.Tensor` and permuted to (C, H, W) order. + """ + for key in self.keys: + img = results[key] + if len(img.shape) < 3: + img = np.expand_dims(img, -1) + results[key] = to_tensor(img).permute(2, 0, 1).contiguous() + + return results + + def __repr__(self): + return self.__class__.__name__ + f'(keys={self.keys})' + + +@TRANSFORMS.register_module() +class Transpose: + """Transpose some results by given keys. + + Args: + keys (Sequence[str]): Keys of results to be transposed. + order (Sequence[int]): Order of transpose. + """ + + def __init__(self, keys, order): + self.keys = keys + self.order = order + + def __call__(self, results): + """Call function to transpose the channel order of data in results. + + Args: + results (dict): Result dict contains the data to transpose. + + Returns: + dict: The result dict contains the data transposed to \ + ``self.order``. + """ + for key in self.keys: + results[key] = results[key].transpose(self.order) + return results + + def __repr__(self): + return self.__class__.__name__ + \ + f'(keys={self.keys}, order={self.order})' + + +@TRANSFORMS.register_module() +class WrapFieldsToLists: + """Wrap fields of the data dictionary into lists for evaluation. + + This class can be used as a last step of a test or validation + pipeline for single image evaluation or inference. + + Example: + >>> test_pipeline = [ + >>> dict(type='LoadImageFromFile'), + >>> dict(type='Normalize', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + to_rgb=True), + >>> dict(type='Pad', size_divisor=32), + >>> dict(type='ImageToTensor', keys=['img']), + >>> dict(type='Collect', keys=['img']), + >>> dict(type='WrapFieldsToLists') + >>> ] + """ + + def __call__(self, results): + """Call function to wrap fields into lists. + + Args: + results (dict): Result dict contains the data to wrap. + + Returns: + dict: The result dict where value of ``self.keys`` are wrapped \ + into list. + """ + + # Wrap dict fields into lists + for key, val in results.items(): + results[key] = [val] + return results + + def __repr__(self): + return f'{self.__class__.__name__}()' + + +@TRANSFORMS.register_module() +class PackTrackInputs(BaseTransform): + """Pack the inputs data for the multi object tracking and video instance + segmentation. All the information of images are packed to ``inputs``. All + the information except images are packed to ``data_samples``. In order to + get the original annotaiton and meta info, we add `instances` key into meta + keys. + + Args: + meta_keys (Sequence[str]): Meta keys to be collected in + ``data_sample.metainfo``. Defaults to None. + default_meta_keys (tuple): Default meta keys. Defaults to ('img_id', + 'img_path', 'ori_shape', 'img_shape', 'scale_factor', + 'flip', 'flip_direction', 'frame_id', 'is_video_data', + 'video_id', 'video_length', 'instances'). + """ + mapping_table = { + 'gt_bboxes': 'bboxes', + 'gt_bboxes_labels': 'labels', + 'gt_masks': 'masks', + 'gt_instances_ids': 'instances_ids' + } + + def __init__(self, + meta_keys: Optional[dict] = None, + default_meta_keys: tuple = ('img_id', 'img_path', 'ori_shape', + 'img_shape', 'scale_factor', + 'flip', 'flip_direction', + 'frame_id', 'video_id', + 'video_length', + 'ori_video_length', 'instances')): + self.meta_keys = default_meta_keys + if meta_keys is not None: + if isinstance(meta_keys, str): + meta_keys = (meta_keys, ) + else: + assert isinstance(meta_keys, tuple), \ + 'meta_keys must be str or tuple' + self.meta_keys += meta_keys + + def transform(self, results: dict) -> dict: + """Method to pack the input data. + Args: + results (dict): Result dict from the data pipeline. + Returns: + dict: + - 'inputs' (dict[Tensor]): The forward data of models. + - 'data_samples' (obj:`TrackDataSample`): The annotation info of + the samples. + """ + packed_results = dict() + packed_results['inputs'] = dict() + + # 1. Pack images + if 'img' in results: + imgs = results['img'] + imgs = np.stack(imgs, axis=0) + imgs = imgs.transpose(0, 3, 1, 2) + packed_results['inputs'] = to_tensor(imgs) + + # 2. Pack InstanceData + if 'gt_ignore_flags' in results: + gt_ignore_flags_list = results['gt_ignore_flags'] + valid_idx_list, ignore_idx_list = [], [] + for gt_ignore_flags in gt_ignore_flags_list: + valid_idx = np.where(gt_ignore_flags == 0)[0] + ignore_idx = np.where(gt_ignore_flags == 1)[0] + valid_idx_list.append(valid_idx) + ignore_idx_list.append(ignore_idx) + + assert 'img_id' in results, "'img_id' must contained in the results " + 'for counting the number of images' + + num_imgs = len(results['img_id']) + instance_data_list = [InstanceData() for _ in range(num_imgs)] + ignore_instance_data_list = [InstanceData() for _ in range(num_imgs)] + + for key in self.mapping_table.keys(): + if key not in results: + continue + if key == 'gt_masks': + mapped_key = self.mapping_table[key] + gt_masks_list = results[key] + if 'gt_ignore_flags' in results: + for i, gt_mask in enumerate(gt_masks_list): + valid_idx, ignore_idx = valid_idx_list[ + i], ignore_idx_list[i] + instance_data_list[i][mapped_key] = gt_mask[valid_idx] + ignore_instance_data_list[i][mapped_key] = gt_mask[ + ignore_idx] + + else: + for i, gt_mask in enumerate(gt_masks_list): + instance_data_list[i][mapped_key] = gt_mask + + else: + anns_list = results[key] + if 'gt_ignore_flags' in results: + for i, ann in enumerate(anns_list): + valid_idx, ignore_idx = valid_idx_list[ + i], ignore_idx_list[i] + instance_data_list[i][ + self.mapping_table[key]] = to_tensor( + ann[valid_idx]) + ignore_instance_data_list[i][ + self.mapping_table[key]] = to_tensor( + ann[ignore_idx]) + else: + for i, ann in enumerate(anns_list): + instance_data_list[i][ + self.mapping_table[key]] = to_tensor(ann) + + det_data_samples_list = [] + for i in range(num_imgs): + det_data_sample = DetDataSample() + det_data_sample.gt_instances = instance_data_list[i] + det_data_sample.ignored_instances = ignore_instance_data_list[i] + det_data_samples_list.append(det_data_sample) + + # 3. Pack metainfo + for key in self.meta_keys: + if key not in results: + continue + img_metas_list = results[key] + for i, img_meta in enumerate(img_metas_list): + det_data_samples_list[i].set_metainfo({f'{key}': img_meta}) + + track_data_sample = TrackDataSample() + track_data_sample.video_data_samples = det_data_samples_list + if 'key_frame_flags' in results: + key_frame_flags = np.asarray(results['key_frame_flags']) + key_frames_inds = np.where(key_frame_flags)[0].tolist() + ref_frames_inds = np.where(~key_frame_flags)[0].tolist() + track_data_sample.set_metainfo( + dict(key_frames_inds=key_frames_inds)) + track_data_sample.set_metainfo( + dict(ref_frames_inds=ref_frames_inds)) + + packed_results['data_samples'] = track_data_sample + return packed_results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'meta_keys={self.meta_keys}, ' + repr_str += f'default_meta_keys={self.default_meta_keys})' + return repr_str + + +@TRANSFORMS.register_module() +class PackReIDInputs(BaseTransform): + """Pack the inputs data for the ReID. The ``meta_info`` item is always + populated. The contents of the ``meta_info`` dictionary depends on + ``meta_keys``. By default this includes: + + - ``img_path``: path to the image file. + - ``ori_shape``: original shape of the image as a tuple (H, W). + - ``img_shape``: shape of the image input to the network as a tuple + (H, W). Note that images may be zero padded on the bottom/right + if the batch tensor is larger than this shape. + - ``scale``: scale of the image as a tuple (W, H). + - ``scale_factor``: a float indicating the pre-processing scale. + - ``flip``: a boolean indicating if image flip transform was used. + - ``flip_direction``: the flipping direction. + Args: + meta_keys (Sequence[str], optional): The meta keys to saved in the + ``metainfo`` of the packed ``data_sample``. + """ + default_meta_keys = ('img_path', 'ori_shape', 'img_shape', 'scale', + 'scale_factor') + + def __init__(self, meta_keys: Sequence[str] = ()) -> None: + self.meta_keys = self.default_meta_keys + if meta_keys is not None: + if isinstance(meta_keys, str): + meta_keys = (meta_keys, ) + else: + assert isinstance(meta_keys, tuple), \ + 'meta_keys must be str or tuple.' + self.meta_keys += meta_keys + + def transform(self, results: dict) -> dict: + """Method to pack the input data. + Args: + results (dict): Result dict from the data pipeline. + Returns: + dict: + - 'inputs' (dict[Tensor]): The forward data of models. + - 'data_samples' (obj:`ReIDDataSample`): The meta info of the + sample. + """ + packed_results = dict(inputs=dict(), data_samples=None) + assert 'img' in results, 'Missing the key ``img``.' + _type = type(results['img']) + label = results['gt_label'] + + if _type == list: + img = results['img'] + label = np.stack(label, axis=0) # (N,) + assert all([type(v) == _type for v in results.values()]), \ + 'All items in the results must have the same type.' + else: + img = [results['img']] + + img = np.stack(img, axis=3) # (H, W, C, N) + img = img.transpose(3, 2, 0, 1) # (N, C, H, W) + img = np.ascontiguousarray(img) + + packed_results['inputs'] = to_tensor(img) + + data_sample = ReIDDataSample() + data_sample.set_gt_label(label) + + meta_info = dict() + for key in self.meta_keys: + meta_info[key] = results[key] + data_sample.set_metainfo(meta_info) + packed_results['data_samples'] = data_sample + + return packed_results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(meta_keys={self.meta_keys})' + return repr_str diff --git a/mmdetection/mmdet/datasets/transforms/frame_sampling.py b/mmdetection/mmdet/datasets/transforms/frame_sampling.py new file mode 100644 index 00000000..a91f1e78 --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/frame_sampling.py @@ -0,0 +1,177 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import random +from collections import defaultdict +from typing import Dict, List, Optional, Union + +from mmcv.transforms import BaseTransform + +from mmdet.registry import TRANSFORMS + + +@TRANSFORMS.register_module() +class BaseFrameSample(BaseTransform): + """Directly get the key frame, no reference frames. + + Args: + collect_video_keys (list[str]): The keys of video info to be + collected. + """ + + def __init__(self, + collect_video_keys: List[str] = ['video_id', 'video_length']): + self.collect_video_keys = collect_video_keys + + def prepare_data(self, video_infos: dict, + sampled_inds: List[int]) -> Dict[str, List]: + """Prepare data for the subsequent pipeline. + + Args: + video_infos (dict): The whole video information. + sampled_inds (list[int]): The sampled frame indices. + + Returns: + dict: The processed data information. + """ + frames_anns = video_infos['images'] + final_data_info = defaultdict(list) + # for data in frames_anns: + for index in sampled_inds: + data = frames_anns[index] + # copy the info in video-level into img-level + for key in self.collect_video_keys: + if key == 'video_length': + data['ori_video_length'] = video_infos[key] + data['video_length'] = len(sampled_inds) + else: + data[key] = video_infos[key] + # Collate data_list (list of dict to dict of list) + for key, value in data.items(): + final_data_info[key].append(value) + + return final_data_info + + def transform(self, video_infos: dict) -> Optional[Dict[str, List]]: + """Transform the video information. + + Args: + video_infos (dict): The whole video information. + + Returns: + dict: The data information of the key frames. + """ + if 'key_frame_id' in video_infos: + key_frame_id = video_infos['key_frame_id'] + assert isinstance(video_infos['key_frame_id'], int) + else: + key_frame_id = random.sample( + list(range(video_infos['video_length'])), 1)[0] + results = self.prepare_data(video_infos, [key_frame_id]) + + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(collect_video_keys={self.collect_video_keys})' + return repr_str + + +@TRANSFORMS.register_module() +class UniformRefFrameSample(BaseFrameSample): + """Uniformly sample reference frames. + + Args: + num_ref_imgs (int): Number of reference frames to be sampled. + frame_range (int | list[int]): Range of frames to be sampled around + key frame. If int, the range is [-frame_range, frame_range]. + Defaults to 10. + filter_key_img (bool): Whether to filter the key frame when + sampling reference frames. Defaults to True. + collect_video_keys (list[str]): The keys of video info to be + collected. + """ + + def __init__(self, + num_ref_imgs: int = 1, + frame_range: Union[int, List[int]] = 10, + filter_key_img: bool = True, + collect_video_keys: List[str] = ['video_id', 'video_length']): + self.num_ref_imgs = num_ref_imgs + self.filter_key_img = filter_key_img + if isinstance(frame_range, int): + assert frame_range >= 0, 'frame_range can not be a negative value.' + frame_range = [-frame_range, frame_range] + elif isinstance(frame_range, list): + assert len(frame_range) == 2, 'The length must be 2.' + assert frame_range[0] <= 0 and frame_range[1] >= 0 + for i in frame_range: + assert isinstance(i, int), 'Each element must be int.' + else: + raise TypeError('The type of frame_range must be int or list.') + self.frame_range = frame_range + super().__init__(collect_video_keys=collect_video_keys) + + def sampling_frames(self, video_length: int, key_frame_id: int): + """Sampling frames. + + Args: + video_length (int): The length of the video. + key_frame_id (int): The key frame id. + + Returns: + list[int]: The sampled frame indices. + """ + if video_length > 1: + left = max(0, key_frame_id + self.frame_range[0]) + right = min(key_frame_id + self.frame_range[1], video_length - 1) + frame_ids = list(range(0, video_length)) + + valid_ids = frame_ids[left:right + 1] + if self.filter_key_img and key_frame_id in valid_ids: + valid_ids.remove(key_frame_id) + assert len( + valid_ids + ) > 0, 'After filtering key frame, there are no valid frames' + if len(valid_ids) < self.num_ref_imgs: + valid_ids = valid_ids * self.num_ref_imgs + ref_frame_ids = random.sample(valid_ids, self.num_ref_imgs) + else: + ref_frame_ids = [key_frame_id] * self.num_ref_imgs + + sampled_frames_ids = [key_frame_id] + ref_frame_ids + sampled_frames_ids = sorted(sampled_frames_ids) + + key_frames_ind = sampled_frames_ids.index(key_frame_id) + key_frame_flags = [False] * len(sampled_frames_ids) + key_frame_flags[key_frames_ind] = True + return sampled_frames_ids, key_frame_flags + + def transform(self, video_infos: dict) -> Optional[Dict[str, List]]: + """Transform the video information. + + Args: + video_infos (dict): The whole video information. + + Returns: + dict: The data information of the sampled frames. + """ + if 'key_frame_id' in video_infos: + key_frame_id = video_infos['key_frame_id'] + assert isinstance(video_infos['key_frame_id'], int) + else: + key_frame_id = random.sample( + list(range(video_infos['video_length'])), 1)[0] + + (sampled_frames_ids, key_frame_flags) = self.sampling_frames( + video_infos['video_length'], key_frame_id=key_frame_id) + results = self.prepare_data(video_infos, sampled_frames_ids) + results['key_frame_flags'] = key_frame_flags + + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(num_ref_imgs={self.num_ref_imgs}, ' + repr_str += f'frame_range={self.frame_range}, ' + repr_str += f'filter_key_img={self.filter_key_img}, ' + repr_str += f'collect_video_keys={self.collect_video_keys})' + return repr_str diff --git a/mmdetection/mmdet/datasets/transforms/geometric.py b/mmdetection/mmdet/datasets/transforms/geometric.py new file mode 100644 index 00000000..d2cd6be2 --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/geometric.py @@ -0,0 +1,754 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +from typing import Optional, Union + +import cv2 +import mmcv +import numpy as np +from mmcv.transforms import BaseTransform +from mmcv.transforms.utils import cache_randomness + +from mmdet.registry import TRANSFORMS +from mmdet.structures.bbox import autocast_box_type +from .augment_wrappers import _MAX_LEVEL, level_to_mag + + +@TRANSFORMS.register_module() +class GeomTransform(BaseTransform): + """Base class for geometric transformations. All geometric transformations + need to inherit from this base class. ``GeomTransform`` unifies the class + attributes and class functions of geometric transformations (ShearX, + ShearY, Rotate, TranslateX, and TranslateY), and records the homography + matrix. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - gt_bboxes + - gt_masks + - gt_seg_map + + Added Keys: + + - homography_matrix + + Args: + prob (float): The probability for performing the geometric + transformation and should be in range [0, 1]. Defaults to 1.0. + level (int, optional): The level should be in range [0, _MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum magnitude for geometric transformation. + Defaults to 0.0. + max_mag (float): The maximum magnitude for geometric transformation. + Defaults to 1.0. + reversal_prob (float): The probability that reverses the geometric + transformation magnitude. Should be in range [0,1]. + Defaults to 0.5. + img_border_value (int | float | tuple): The filled values for + image border. If float, the same fill value will be used for + all the three channels of image. If tuple, it should be 3 elements. + Defaults to 128. + mask_border_value (int): The fill value used for masks. Defaults to 0. + seg_ignore_label (int): The fill value used for segmentation map. + Note this value must equals ``ignore_label`` in ``semantic_head`` + of the corresponding config. Defaults to 255. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. Defaults + to 'bilinear'. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.0, + max_mag: float = 1.0, + reversal_prob: float = 0.5, + img_border_value: Union[int, float, tuple] = 128, + mask_border_value: int = 0, + seg_ignore_label: int = 255, + interpolation: str = 'bilinear') -> None: + assert 0 <= prob <= 1.0, f'The probability of the transformation ' \ + f'should be in range [0,1], got {prob}.' + assert level is None or isinstance(level, int), \ + f'The level should be None or type int, got {type(level)}.' + assert level is None or 0 <= level <= _MAX_LEVEL, \ + f'The level should be in range [0,{_MAX_LEVEL}], got {level}.' + assert isinstance(min_mag, float), \ + f'min_mag should be type float, got {type(min_mag)}.' + assert isinstance(max_mag, float), \ + f'max_mag should be type float, got {type(max_mag)}.' + assert min_mag <= max_mag, \ + f'min_mag should smaller than max_mag, ' \ + f'got min_mag={min_mag} and max_mag={max_mag}' + assert isinstance(reversal_prob, float), \ + f'reversal_prob should be type float, got {type(max_mag)}.' + assert 0 <= reversal_prob <= 1.0, \ + f'The reversal probability of the transformation magnitude ' \ + f'should be type float, got {type(reversal_prob)}.' + if isinstance(img_border_value, (float, int)): + img_border_value = tuple([float(img_border_value)] * 3) + elif isinstance(img_border_value, tuple): + assert len(img_border_value) == 3, \ + f'img_border_value as tuple must have 3 elements, ' \ + f'got {len(img_border_value)}.' + img_border_value = tuple([float(val) for val in img_border_value]) + else: + raise ValueError( + 'img_border_value must be float or tuple with 3 elements.') + assert np.all([0 <= val <= 255 for val in img_border_value]), 'all ' \ + 'elements of img_border_value should between range [0,255].' \ + f'got {img_border_value}.' + self.prob = prob + self.level = level + self.min_mag = min_mag + self.max_mag = max_mag + self.reversal_prob = reversal_prob + self.img_border_value = img_border_value + self.mask_border_value = mask_border_value + self.seg_ignore_label = seg_ignore_label + self.interpolation = interpolation + + def _transform_img(self, results: dict, mag: float) -> None: + """Transform the image.""" + pass + + def _transform_masks(self, results: dict, mag: float) -> None: + """Transform the masks.""" + pass + + def _transform_seg(self, results: dict, mag: float) -> None: + """Transform the segmentation map.""" + pass + + def _get_homography_matrix(self, results: dict, mag: float) -> np.ndarray: + """Get the homography matrix for the geometric transformation.""" + return np.eye(3, dtype=np.float32) + + def _transform_bboxes(self, results: dict, mag: float) -> None: + """Transform the bboxes.""" + results['gt_bboxes'].project_(self.homography_matrix) + results['gt_bboxes'].clip_(results['img_shape']) + + def _record_homography_matrix(self, results: dict) -> None: + """Record the homography matrix for the geometric transformation.""" + if results.get('homography_matrix', None) is None: + results['homography_matrix'] = self.homography_matrix + else: + results['homography_matrix'] = self.homography_matrix @ results[ + 'homography_matrix'] + + @cache_randomness + def _random_disable(self): + """Randomly disable the transform.""" + return np.random.rand() > self.prob + + @cache_randomness + def _get_mag(self): + """Get the magnitude of the transform.""" + mag = level_to_mag(self.level, self.min_mag, self.max_mag) + return -mag if np.random.rand() > self.reversal_prob else mag + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Transform function for images, bounding boxes, masks and semantic + segmentation map. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Transformed results. + """ + + if self._random_disable(): + return results + mag = self._get_mag() + self.homography_matrix = self._get_homography_matrix(results, mag) + self._record_homography_matrix(results) + self._transform_img(results, mag) + if results.get('gt_bboxes', None) is not None: + self._transform_bboxes(results, mag) + if results.get('gt_masks', None) is not None: + self._transform_masks(results, mag) + if results.get('gt_seg_map', None) is not None: + self._transform_seg(results, mag) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, ' + repr_str += f'level={self.level}, ' + repr_str += f'min_mag={self.min_mag}, ' + repr_str += f'max_mag={self.max_mag}, ' + repr_str += f'reversal_prob={self.reversal_prob}, ' + repr_str += f'img_border_value={self.img_border_value}, ' + repr_str += f'mask_border_value={self.mask_border_value}, ' + repr_str += f'seg_ignore_label={self.seg_ignore_label}, ' + repr_str += f'interpolation={self.interpolation})' + return repr_str + + +@TRANSFORMS.register_module() +class ShearX(GeomTransform): + """Shear the images, bboxes, masks and segmentation map horizontally. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - gt_bboxes + - gt_masks + - gt_seg_map + + Added Keys: + + - homography_matrix + + Args: + prob (float): The probability for performing Shear and should be in + range [0, 1]. Defaults to 1.0. + level (int, optional): The level should be in range [0, _MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum angle for the horizontal shear. + Defaults to 0.0. + max_mag (float): The maximum angle for the horizontal shear. + Defaults to 30.0. + reversal_prob (float): The probability that reverses the horizontal + shear magnitude. Should be in range [0,1]. Defaults to 0.5. + img_border_value (int | float | tuple): The filled values for + image border. If float, the same fill value will be used for + all the three channels of image. If tuple, it should be 3 elements. + Defaults to 128. + mask_border_value (int): The fill value used for masks. Defaults to 0. + seg_ignore_label (int): The fill value used for segmentation map. + Note this value must equals ``ignore_label`` in ``semantic_head`` + of the corresponding config. Defaults to 255. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. Defaults + to 'bilinear'. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.0, + max_mag: float = 30.0, + reversal_prob: float = 0.5, + img_border_value: Union[int, float, tuple] = 128, + mask_border_value: int = 0, + seg_ignore_label: int = 255, + interpolation: str = 'bilinear') -> None: + assert 0. <= min_mag <= 90., \ + f'min_mag angle for ShearX should be ' \ + f'in range [0, 90], got {min_mag}.' + assert 0. <= max_mag <= 90., \ + f'max_mag angle for ShearX should be ' \ + f'in range [0, 90], got {max_mag}.' + super().__init__( + prob=prob, + level=level, + min_mag=min_mag, + max_mag=max_mag, + reversal_prob=reversal_prob, + img_border_value=img_border_value, + mask_border_value=mask_border_value, + seg_ignore_label=seg_ignore_label, + interpolation=interpolation) + + @cache_randomness + def _get_mag(self): + """Get the magnitude of the transform.""" + mag = level_to_mag(self.level, self.min_mag, self.max_mag) + mag = np.tan(mag * np.pi / 180) + return -mag if np.random.rand() > self.reversal_prob else mag + + def _get_homography_matrix(self, results: dict, mag: float) -> np.ndarray: + """Get the homography matrix for ShearX.""" + return np.array([[1, mag, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32) + + def _transform_img(self, results: dict, mag: float) -> None: + """Shear the image horizontally.""" + results['img'] = mmcv.imshear( + results['img'], + mag, + direction='horizontal', + border_value=self.img_border_value, + interpolation=self.interpolation) + + def _transform_masks(self, results: dict, mag: float) -> None: + """Shear the masks horizontally.""" + results['gt_masks'] = results['gt_masks'].shear( + results['img_shape'], + mag, + direction='horizontal', + border_value=self.mask_border_value, + interpolation=self.interpolation) + + def _transform_seg(self, results: dict, mag: float) -> None: + """Shear the segmentation map horizontally.""" + results['gt_seg_map'] = mmcv.imshear( + results['gt_seg_map'], + mag, + direction='horizontal', + border_value=self.seg_ignore_label, + interpolation='nearest') + + +@TRANSFORMS.register_module() +class ShearY(GeomTransform): + """Shear the images, bboxes, masks and segmentation map vertically. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - gt_bboxes + - gt_masks + - gt_seg_map + + Added Keys: + + - homography_matrix + + Args: + prob (float): The probability for performing ShearY and should be in + range [0, 1]. Defaults to 1.0. + level (int, optional): The level should be in range [0,_MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum angle for the vertical shear. + Defaults to 0.0. + max_mag (float): The maximum angle for the vertical shear. + Defaults to 30.0. + reversal_prob (float): The probability that reverses the vertical + shear magnitude. Should be in range [0,1]. Defaults to 0.5. + img_border_value (int | float | tuple): The filled values for + image border. If float, the same fill value will be used for + all the three channels of image. If tuple, it should be 3 elements. + Defaults to 128. + mask_border_value (int): The fill value used for masks. Defaults to 0. + seg_ignore_label (int): The fill value used for segmentation map. + Note this value must equals ``ignore_label`` in ``semantic_head`` + of the corresponding config. Defaults to 255. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. Defaults + to 'bilinear'. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.0, + max_mag: float = 30., + reversal_prob: float = 0.5, + img_border_value: Union[int, float, tuple] = 128, + mask_border_value: int = 0, + seg_ignore_label: int = 255, + interpolation: str = 'bilinear') -> None: + assert 0. <= min_mag <= 90., \ + f'min_mag angle for ShearY should be ' \ + f'in range [0, 90], got {min_mag}.' + assert 0. <= max_mag <= 90., \ + f'max_mag angle for ShearY should be ' \ + f'in range [0, 90], got {max_mag}.' + super().__init__( + prob=prob, + level=level, + min_mag=min_mag, + max_mag=max_mag, + reversal_prob=reversal_prob, + img_border_value=img_border_value, + mask_border_value=mask_border_value, + seg_ignore_label=seg_ignore_label, + interpolation=interpolation) + + @cache_randomness + def _get_mag(self): + """Get the magnitude of the transform.""" + mag = level_to_mag(self.level, self.min_mag, self.max_mag) + mag = np.tan(mag * np.pi / 180) + return -mag if np.random.rand() > self.reversal_prob else mag + + def _get_homography_matrix(self, results: dict, mag: float) -> np.ndarray: + """Get the homography matrix for ShearY.""" + return np.array([[1, 0, 0], [mag, 1, 0], [0, 0, 1]], dtype=np.float32) + + def _transform_img(self, results: dict, mag: float) -> None: + """Shear the image vertically.""" + results['img'] = mmcv.imshear( + results['img'], + mag, + direction='vertical', + border_value=self.img_border_value, + interpolation=self.interpolation) + + def _transform_masks(self, results: dict, mag: float) -> None: + """Shear the masks vertically.""" + results['gt_masks'] = results['gt_masks'].shear( + results['img_shape'], + mag, + direction='vertical', + border_value=self.mask_border_value, + interpolation=self.interpolation) + + def _transform_seg(self, results: dict, mag: float) -> None: + """Shear the segmentation map vertically.""" + results['gt_seg_map'] = mmcv.imshear( + results['gt_seg_map'], + mag, + direction='vertical', + border_value=self.seg_ignore_label, + interpolation='nearest') + + +@TRANSFORMS.register_module() +class Rotate(GeomTransform): + """Rotate the images, bboxes, masks and segmentation map. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - gt_bboxes + - gt_masks + - gt_seg_map + + Added Keys: + + - homography_matrix + + Args: + prob (float): The probability for perform transformation and + should be in range 0 to 1. Defaults to 1.0. + level (int, optional): The level should be in range [0, _MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The maximum angle for rotation. + Defaults to 0.0. + max_mag (float): The maximum angle for rotation. + Defaults to 30.0. + reversal_prob (float): The probability that reverses the rotation + magnitude. Should be in range [0,1]. Defaults to 0.5. + img_border_value (int | float | tuple): The filled values for + image border. If float, the same fill value will be used for + all the three channels of image. If tuple, it should be 3 elements. + Defaults to 128. + mask_border_value (int): The fill value used for masks. Defaults to 0. + seg_ignore_label (int): The fill value used for segmentation map. + Note this value must equals ``ignore_label`` in ``semantic_head`` + of the corresponding config. Defaults to 255. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. Defaults + to 'bilinear'. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.0, + max_mag: float = 30.0, + reversal_prob: float = 0.5, + img_border_value: Union[int, float, tuple] = 128, + mask_border_value: int = 0, + seg_ignore_label: int = 255, + interpolation: str = 'bilinear') -> None: + assert 0. <= min_mag <= 180., \ + f'min_mag for Rotate should be in range [0,180], got {min_mag}.' + assert 0. <= max_mag <= 180., \ + f'max_mag for Rotate should be in range [0,180], got {max_mag}.' + super().__init__( + prob=prob, + level=level, + min_mag=min_mag, + max_mag=max_mag, + reversal_prob=reversal_prob, + img_border_value=img_border_value, + mask_border_value=mask_border_value, + seg_ignore_label=seg_ignore_label, + interpolation=interpolation) + + def _get_homography_matrix(self, results: dict, mag: float) -> np.ndarray: + """Get the homography matrix for Rotate.""" + img_shape = results['img_shape'] + center = ((img_shape[1] - 1) * 0.5, (img_shape[0] - 1) * 0.5) + cv2_rotation_matrix = cv2.getRotationMatrix2D(center, -mag, 1.0) + return np.concatenate( + [cv2_rotation_matrix, + np.array([0, 0, 1]).reshape((1, 3))]).astype(np.float32) + + def _transform_img(self, results: dict, mag: float) -> None: + """Rotate the image.""" + results['img'] = mmcv.imrotate( + results['img'], + mag, + border_value=self.img_border_value, + interpolation=self.interpolation) + + def _transform_masks(self, results: dict, mag: float) -> None: + """Rotate the masks.""" + results['gt_masks'] = results['gt_masks'].rotate( + results['img_shape'], + mag, + border_value=self.mask_border_value, + interpolation=self.interpolation) + + def _transform_seg(self, results: dict, mag: float) -> None: + """Rotate the segmentation map.""" + results['gt_seg_map'] = mmcv.imrotate( + results['gt_seg_map'], + mag, + border_value=self.seg_ignore_label, + interpolation='nearest') + + +@TRANSFORMS.register_module() +class TranslateX(GeomTransform): + """Translate the images, bboxes, masks and segmentation map horizontally. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - gt_bboxes + - gt_masks + - gt_seg_map + + Added Keys: + + - homography_matrix + + Args: + prob (float): The probability for perform transformation and + should be in range 0 to 1. Defaults to 1.0. + level (int, optional): The level should be in range [0, _MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum pixel's offset ratio for horizontal + translation. Defaults to 0.0. + max_mag (float): The maximum pixel's offset ratio for horizontal + translation. Defaults to 0.1. + reversal_prob (float): The probability that reverses the horizontal + translation magnitude. Should be in range [0,1]. Defaults to 0.5. + img_border_value (int | float | tuple): The filled values for + image border. If float, the same fill value will be used for + all the three channels of image. If tuple, it should be 3 elements. + Defaults to 128. + mask_border_value (int): The fill value used for masks. Defaults to 0. + seg_ignore_label (int): The fill value used for segmentation map. + Note this value must equals ``ignore_label`` in ``semantic_head`` + of the corresponding config. Defaults to 255. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. Defaults + to 'bilinear'. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.0, + max_mag: float = 0.1, + reversal_prob: float = 0.5, + img_border_value: Union[int, float, tuple] = 128, + mask_border_value: int = 0, + seg_ignore_label: int = 255, + interpolation: str = 'bilinear') -> None: + assert 0. <= min_mag <= 1., \ + f'min_mag ratio for TranslateX should be ' \ + f'in range [0, 1], got {min_mag}.' + assert 0. <= max_mag <= 1., \ + f'max_mag ratio for TranslateX should be ' \ + f'in range [0, 1], got {max_mag}.' + super().__init__( + prob=prob, + level=level, + min_mag=min_mag, + max_mag=max_mag, + reversal_prob=reversal_prob, + img_border_value=img_border_value, + mask_border_value=mask_border_value, + seg_ignore_label=seg_ignore_label, + interpolation=interpolation) + + def _get_homography_matrix(self, results: dict, mag: float) -> np.ndarray: + """Get the homography matrix for TranslateX.""" + mag = int(results['img_shape'][1] * mag) + return np.array([[1, 0, mag], [0, 1, 0], [0, 0, 1]], dtype=np.float32) + + def _transform_img(self, results: dict, mag: float) -> None: + """Translate the image horizontally.""" + mag = int(results['img_shape'][1] * mag) + results['img'] = mmcv.imtranslate( + results['img'], + mag, + direction='horizontal', + border_value=self.img_border_value, + interpolation=self.interpolation) + + def _transform_masks(self, results: dict, mag: float) -> None: + """Translate the masks horizontally.""" + mag = int(results['img_shape'][1] * mag) + results['gt_masks'] = results['gt_masks'].translate( + results['img_shape'], + mag, + direction='horizontal', + border_value=self.mask_border_value, + interpolation=self.interpolation) + + def _transform_seg(self, results: dict, mag: float) -> None: + """Translate the segmentation map horizontally.""" + mag = int(results['img_shape'][1] * mag) + results['gt_seg_map'] = mmcv.imtranslate( + results['gt_seg_map'], + mag, + direction='horizontal', + border_value=self.seg_ignore_label, + interpolation='nearest') + + +@TRANSFORMS.register_module() +class TranslateY(GeomTransform): + """Translate the images, bboxes, masks and segmentation map vertically. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - gt_bboxes + - gt_masks + - gt_seg_map + + Added Keys: + + - homography_matrix + + Args: + prob (float): The probability for perform transformation and + should be in range 0 to 1. Defaults to 1.0. + level (int, optional): The level should be in range [0, _MAX_LEVEL]. + If level is None, it will generate from [0, _MAX_LEVEL] randomly. + Defaults to None. + min_mag (float): The minimum pixel's offset ratio for vertical + translation. Defaults to 0.0. + max_mag (float): The maximum pixel's offset ratio for vertical + translation. Defaults to 0.1. + reversal_prob (float): The probability that reverses the vertical + translation magnitude. Should be in range [0,1]. Defaults to 0.5. + img_border_value (int | float | tuple): The filled values for + image border. If float, the same fill value will be used for + all the three channels of image. If tuple, it should be 3 elements. + Defaults to 128. + mask_border_value (int): The fill value used for masks. Defaults to 0. + seg_ignore_label (int): The fill value used for segmentation map. + Note this value must equals ``ignore_label`` in ``semantic_head`` + of the corresponding config. Defaults to 255. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. Defaults + to 'bilinear'. + """ + + def __init__(self, + prob: float = 1.0, + level: Optional[int] = None, + min_mag: float = 0.0, + max_mag: float = 0.1, + reversal_prob: float = 0.5, + img_border_value: Union[int, float, tuple] = 128, + mask_border_value: int = 0, + seg_ignore_label: int = 255, + interpolation: str = 'bilinear') -> None: + assert 0. <= min_mag <= 1., \ + f'min_mag ratio for TranslateY should be ' \ + f'in range [0,1], got {min_mag}.' + assert 0. <= max_mag <= 1., \ + f'max_mag ratio for TranslateY should be ' \ + f'in range [0,1], got {max_mag}.' + super().__init__( + prob=prob, + level=level, + min_mag=min_mag, + max_mag=max_mag, + reversal_prob=reversal_prob, + img_border_value=img_border_value, + mask_border_value=mask_border_value, + seg_ignore_label=seg_ignore_label, + interpolation=interpolation) + + def _get_homography_matrix(self, results: dict, mag: float) -> np.ndarray: + """Get the homography matrix for TranslateY.""" + mag = int(results['img_shape'][0] * mag) + return np.array([[1, 0, 0], [0, 1, mag], [0, 0, 1]], dtype=np.float32) + + def _transform_img(self, results: dict, mag: float) -> None: + """Translate the image vertically.""" + mag = int(results['img_shape'][0] * mag) + results['img'] = mmcv.imtranslate( + results['img'], + mag, + direction='vertical', + border_value=self.img_border_value, + interpolation=self.interpolation) + + def _transform_masks(self, results: dict, mag: float) -> None: + """Translate masks vertically.""" + mag = int(results['img_shape'][0] * mag) + results['gt_masks'] = results['gt_masks'].translate( + results['img_shape'], + mag, + direction='vertical', + border_value=self.mask_border_value, + interpolation=self.interpolation) + + def _transform_seg(self, results: dict, mag: float) -> None: + """Translate segmentation map vertically.""" + mag = int(results['img_shape'][0] * mag) + results['gt_seg_map'] = mmcv.imtranslate( + results['gt_seg_map'], + mag, + direction='vertical', + border_value=self.seg_ignore_label, + interpolation='nearest') diff --git a/mmdetection/mmdet/datasets/transforms/instaboost.py b/mmdetection/mmdet/datasets/transforms/instaboost.py new file mode 100644 index 00000000..30dc1603 --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/instaboost.py @@ -0,0 +1,150 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import numpy as np +from mmcv.transforms import BaseTransform + +from mmdet.registry import TRANSFORMS + + +@TRANSFORMS.register_module() +class InstaBoost(BaseTransform): + r"""Data augmentation method in `InstaBoost: Boosting Instance + Segmentation Via Probability Map Guided Copy-Pasting + `_. + + Refer to https://github.com/GothicAi/Instaboost for implementation details. + + + Required Keys: + + - img (np.uint8) + - instances + + Modified Keys: + + - img (np.uint8) + - instances + + Args: + action_candidate (tuple): Action candidates. "normal", "horizontal", \ + "vertical", "skip" are supported. Defaults to ('normal', \ + 'horizontal', 'skip'). + action_prob (tuple): Corresponding action probabilities. Should be \ + the same length as action_candidate. Defaults to (1, 0, 0). + scale (tuple): (min scale, max scale). Defaults to (0.8, 1.2). + dx (int): The maximum x-axis shift will be (instance width) / dx. + Defaults to 15. + dy (int): The maximum y-axis shift will be (instance height) / dy. + Defaults to 15. + theta (tuple): (min rotation degree, max rotation degree). \ + Defaults to (-1, 1). + color_prob (float): Probability of images for color augmentation. + Defaults to 0.5. + hflag (bool): Whether to use heatmap guided. Defaults to False. + aug_ratio (float): Probability of applying this transformation. \ + Defaults to 0.5. + """ + + def __init__(self, + action_candidate: tuple = ('normal', 'horizontal', 'skip'), + action_prob: tuple = (1, 0, 0), + scale: tuple = (0.8, 1.2), + dx: int = 15, + dy: int = 15, + theta: tuple = (-1, 1), + color_prob: float = 0.5, + hflag: bool = False, + aug_ratio: float = 0.5) -> None: + + import matplotlib + import matplotlib.pyplot as plt + default_backend = plt.get_backend() + + try: + import instaboostfast as instaboost + except ImportError: + raise ImportError( + 'Please run "pip install instaboostfast" ' + 'to install instaboostfast first for instaboost augmentation.') + + # instaboost will modify the default backend + # and cause visualization to fail. + matplotlib.use(default_backend) + + self.cfg = instaboost.InstaBoostConfig(action_candidate, action_prob, + scale, dx, dy, theta, + color_prob, hflag) + self.aug_ratio = aug_ratio + + def _load_anns(self, results: dict) -> Tuple[list, list]: + """Convert raw anns to instaboost expected input format.""" + anns = [] + ignore_anns = [] + for instance in results['instances']: + label = instance['bbox_label'] + bbox = instance['bbox'] + mask = instance['mask'] + x1, y1, x2, y2 = bbox + # assert (x2 - x1) >= 1 and (y2 - y1) >= 1 + bbox = [x1, y1, x2 - x1, y2 - y1] + + if instance['ignore_flag'] == 0: + anns.append({ + 'category_id': label, + 'segmentation': mask, + 'bbox': bbox + }) + else: + # Ignore instances without data augmentation + ignore_anns.append(instance) + return anns, ignore_anns + + def _parse_anns(self, results: dict, anns: list, ignore_anns: list, + img: np.ndarray) -> dict: + """Restore the result of instaboost processing to the original anns + format.""" + instances = [] + for ann in anns: + x1, y1, w, h = ann['bbox'] + # TODO: more essential bug need to be fixed in instaboost + if w <= 0 or h <= 0: + continue + bbox = [x1, y1, x1 + w, y1 + h] + instances.append( + dict( + bbox=bbox, + bbox_label=ann['category_id'], + mask=ann['segmentation'], + ignore_flag=0)) + + instances.extend(ignore_anns) + results['img'] = img + results['instances'] = instances + return results + + def transform(self, results) -> dict: + """The transform function.""" + img = results['img'] + ori_type = img.dtype + if 'instances' not in results or len(results['instances']) == 0: + return results + + anns, ignore_anns = self._load_anns(results) + if np.random.choice([0, 1], p=[1 - self.aug_ratio, self.aug_ratio]): + try: + import instaboostfast as instaboost + except ImportError: + raise ImportError('Please run "pip install instaboostfast" ' + 'to install instaboostfast first.') + anns, img = instaboost.get_new_data( + anns, img.astype(np.uint8), self.cfg, background=None) + + results = self._parse_anns(results, anns, ignore_anns, + img.astype(ori_type)) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(aug_ratio={self.aug_ratio})' + return repr_str diff --git a/mmdetection/mmdet/datasets/transforms/loading.py b/mmdetection/mmdet/datasets/transforms/loading.py new file mode 100644 index 00000000..722d4b0e --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/loading.py @@ -0,0 +1,1074 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, Union + +import mmcv +import numpy as np +import pycocotools.mask as maskUtils +import torch +from mmcv.transforms import BaseTransform +from mmcv.transforms import LoadAnnotations as MMCV_LoadAnnotations +from mmcv.transforms import LoadImageFromFile +from mmengine.fileio import get +from mmengine.structures import BaseDataElement + +from mmdet.registry import TRANSFORMS +from mmdet.structures.bbox import get_box_type +from mmdet.structures.bbox.box_type import autocast_box_type +from mmdet.structures.mask import BitmapMasks, PolygonMasks + + +@TRANSFORMS.register_module() +class LoadImageFromNDArray(LoadImageFromFile): + """Load an image from ``results['img']``. + + Similar with :obj:`LoadImageFromFile`, but the image has been loaded as + :obj:`np.ndarray` in ``results['img']``. Can be used when loading image + from webcam. + + Required Keys: + + - img + + Modified Keys: + + - img + - img_path + - img_shape + - ori_shape + + Args: + to_float32 (bool): Whether to convert the loaded image to a float32 + numpy array. If set to False, the loaded image is an uint8 array. + Defaults to False. + """ + + def transform(self, results: dict) -> dict: + """Transform function to add image meta information. + + Args: + results (dict): Result dict with Webcam read image in + ``results['img']``. + + Returns: + dict: The dict contains loaded image and meta information. + """ + + img = results['img'] + if self.to_float32: + img = img.astype(np.float32) + + results['img_path'] = None + results['img'] = img + results['img_shape'] = img.shape[:2] + results['ori_shape'] = img.shape[:2] + return results + + +@TRANSFORMS.register_module() +class LoadMultiChannelImageFromFiles(BaseTransform): + """Load multi-channel images from a list of separate channel files. + + Required Keys: + + - img_path + + Modified Keys: + + - img + - img_shape + - ori_shape + + Args: + to_float32 (bool): Whether to convert the loaded image to a float32 + numpy array. If set to False, the loaded image is an uint8 array. + Defaults to False. + color_type (str): The flag argument for :func:``mmcv.imfrombytes``. + Defaults to 'unchanged'. + imdecode_backend (str): The image decoding backend type. The backend + argument for :func:``mmcv.imfrombytes``. + See :func:``mmcv.imfrombytes`` for details. + Defaults to 'cv2'. + file_client_args (dict): Arguments to instantiate the + corresponding backend in mmdet <= 3.0.0rc6. Defaults to None. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend in mmdet >= 3.0.0rc7. Defaults to None. + """ + + def __init__( + self, + to_float32: bool = False, + color_type: str = 'unchanged', + imdecode_backend: str = 'cv2', + file_client_args: dict = None, + backend_args: dict = None, + ) -> None: + self.to_float32 = to_float32 + self.color_type = color_type + self.imdecode_backend = imdecode_backend + self.backend_args = backend_args + if file_client_args is not None: + raise RuntimeError( + 'The `file_client_args` is deprecated, ' + 'please use `backend_args` instead, please refer to' + 'https://github.com/open-mmlab/mmdetection/blob/main/configs/_base_/datasets/coco_detection.py' # noqa: E501 + ) + + def transform(self, results: dict) -> dict: + """Transform functions to load multiple images and get images meta + information. + + Args: + results (dict): Result dict from :obj:`mmdet.CustomDataset`. + + Returns: + dict: The dict contains loaded images and meta information. + """ + + assert isinstance(results['img_path'], list) + img = [] + for name in results['img_path']: + img_bytes = get(name, backend_args=self.backend_args) + img.append( + mmcv.imfrombytes( + img_bytes, + flag=self.color_type, + backend=self.imdecode_backend)) + img = np.stack(img, axis=-1) + if self.to_float32: + img = img.astype(np.float32) + + results['img'] = img + results['img_shape'] = img.shape[:2] + results['ori_shape'] = img.shape[:2] + return results + + def __repr__(self): + repr_str = (f'{self.__class__.__name__}(' + f'to_float32={self.to_float32}, ' + f"color_type='{self.color_type}', " + f"imdecode_backend='{self.imdecode_backend}', " + f'backend_args={self.backend_args})') + return repr_str + + +@TRANSFORMS.register_module() +class LoadAnnotations(MMCV_LoadAnnotations): + """Load and process the ``instances`` and ``seg_map`` annotation provided + by dataset. + + The annotation format is as the following: + + .. code-block:: python + + { + 'instances': + [ + { + # List of 4 numbers representing the bounding box of the + # instance, in (x1, y1, x2, y2) order. + 'bbox': [x1, y1, x2, y2], + + # Label of image classification. + 'bbox_label': 1, + + # Used in instance/panoptic segmentation. The segmentation mask + # of the instance or the information of segments. + # 1. If list[list[float]], it represents a list of polygons, + # one for each connected component of the object. Each + # list[float] is one simple polygon in the format of + # [x1, y1, ..., xn, yn] (n >= 3). The Xs and Ys are absolute + # coordinates in unit of pixels. + # 2. If dict, it represents the per-pixel segmentation mask in + # COCO's compressed RLE format. The dict should have keys + # “size” and “counts”. Can be loaded by pycocotools + 'mask': list[list[float]] or dict, + + } + ] + # Filename of semantic or panoptic segmentation ground truth file. + 'seg_map_path': 'a/b/c' + } + + After this module, the annotation has been changed to the format below: + + .. code-block:: python + + { + # In (x1, y1, x2, y2) order, float type. N is the number of bboxes + # in an image + 'gt_bboxes': BaseBoxes(N, 4) + # In int type. + 'gt_bboxes_labels': np.ndarray(N, ) + # In built-in class + 'gt_masks': PolygonMasks (H, W) or BitmapMasks (H, W) + # In uint8 type. + 'gt_seg_map': np.ndarray (H, W) + # in (x, y, v) order, float type. + } + + Required Keys: + + - height + - width + - instances + + - bbox (optional) + - bbox_label + - mask (optional) + - ignore_flag + + - seg_map_path (optional) + + Added Keys: + + - gt_bboxes (BaseBoxes[torch.float32]) + - gt_bboxes_labels (np.int64) + - gt_masks (BitmapMasks | PolygonMasks) + - gt_seg_map (np.uint8) + - gt_ignore_flags (bool) + + Args: + with_bbox (bool): Whether to parse and load the bbox annotation. + Defaults to True. + with_label (bool): Whether to parse and load the label annotation. + Defaults to True. + with_mask (bool): Whether to parse and load the mask annotation. + Default: False. + with_seg (bool): Whether to parse and load the semantic segmentation + annotation. Defaults to False. + poly2mask (bool): Whether to convert mask to bitmap. Default: True. + box_type (str): The box type used to wrap the bboxes. If ``box_type`` + is None, gt_bboxes will keep being np.ndarray. Defaults to 'hbox'. + reduce_zero_label (bool): Whether reduce all label value + by 1. Usually used for datasets where 0 is background label. + Defaults to False. + ignore_index (int): The label index to be ignored. + Valid only if reduce_zero_label is true. Defaults is 255. + imdecode_backend (str): The image decoding backend type. The backend + argument for :func:``mmcv.imfrombytes``. + See :fun:``mmcv.imfrombytes`` for details. + Defaults to 'cv2'. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + """ + + def __init__( + self, + with_mask: bool = False, + poly2mask: bool = True, + box_type: str = 'hbox', + # use for semseg + reduce_zero_label: bool = False, + ignore_index: int = 255, + **kwargs) -> None: + super(LoadAnnotations, self).__init__(**kwargs) + self.with_mask = with_mask + self.poly2mask = poly2mask + self.box_type = box_type + self.reduce_zero_label = reduce_zero_label + self.ignore_index = ignore_index + + def _load_bboxes(self, results: dict) -> None: + """Private function to load bounding box annotations. + + Args: + results (dict): Result dict from :obj:``mmengine.BaseDataset``. + Returns: + dict: The dict contains loaded bounding box annotations. + """ + gt_bboxes = [] + gt_ignore_flags = [] + for instance in results.get('instances', []): + gt_bboxes.append(instance['bbox']) + gt_ignore_flags.append(instance['ignore_flag']) + if self.box_type is None: + results['gt_bboxes'] = np.array( + gt_bboxes, dtype=np.float32).reshape((-1, 4)) + else: + _, box_type_cls = get_box_type(self.box_type) + results['gt_bboxes'] = box_type_cls(gt_bboxes, dtype=torch.float32) + results['gt_ignore_flags'] = np.array(gt_ignore_flags, dtype=bool) + + def _load_labels(self, results: dict) -> None: + """Private function to load label annotations. + + Args: + results (dict): Result dict from :obj:``mmengine.BaseDataset``. + + Returns: + dict: The dict contains loaded label annotations. + """ + gt_bboxes_labels = [] + for instance in results.get('instances', []): + gt_bboxes_labels.append(instance['bbox_label']) + # TODO: Inconsistent with mmcv, consider how to deal with it later. + results['gt_bboxes_labels'] = np.array( + gt_bboxes_labels, dtype=np.int64) + + def _poly2mask(self, mask_ann: Union[list, dict], img_h: int, + img_w: int) -> np.ndarray: + """Private function to convert masks represented with polygon to + bitmaps. + + Args: + mask_ann (list | dict): Polygon mask annotation input. + img_h (int): The height of output mask. + img_w (int): The width of output mask. + + Returns: + np.ndarray: The decode bitmap mask of shape (img_h, img_w). + """ + + if isinstance(mask_ann, list): + # polygon -- a single object might consist of multiple parts + # we merge all parts into one mask rle code + rles = maskUtils.frPyObjects(mask_ann, img_h, img_w) + rle = maskUtils.merge(rles) + elif isinstance(mask_ann['counts'], list): + # uncompressed RLE + rle = maskUtils.frPyObjects(mask_ann, img_h, img_w) + else: + # rle + rle = mask_ann + mask = maskUtils.decode(rle) + return mask + + def _process_masks(self, results: dict) -> list: + """Process gt_masks and filter invalid polygons. + + Args: + results (dict): Result dict from :obj:``mmengine.BaseDataset``. + + Returns: + list: Processed gt_masks. + """ + gt_masks = [] + gt_ignore_flags = [] + for instance in results.get('instances', []): + gt_mask = instance['mask'] + # If the annotation of segmentation mask is invalid, + # ignore the whole instance. + if isinstance(gt_mask, list): + gt_mask = [ + np.array(polygon) for polygon in gt_mask + if len(polygon) % 2 == 0 and len(polygon) >= 6 + ] + if len(gt_mask) == 0: + # ignore this instance and set gt_mask to a fake mask + instance['ignore_flag'] = 1 + gt_mask = [np.zeros(6)] + elif not self.poly2mask: + # `PolygonMasks` requires a ploygon of format List[np.array], + # other formats are invalid. + instance['ignore_flag'] = 1 + gt_mask = [np.zeros(6)] + elif isinstance(gt_mask, dict) and \ + not (gt_mask.get('counts') is not None and + gt_mask.get('size') is not None and + isinstance(gt_mask['counts'], (list, str))): + # if gt_mask is a dict, it should include `counts` and `size`, + # so that `BitmapMasks` can uncompressed RLE + instance['ignore_flag'] = 1 + gt_mask = [np.zeros(6)] + gt_masks.append(gt_mask) + # re-process gt_ignore_flags + gt_ignore_flags.append(instance['ignore_flag']) + results['gt_ignore_flags'] = np.array(gt_ignore_flags, dtype=bool) + return gt_masks + + def _load_masks(self, results: dict) -> None: + """Private function to load mask annotations. + + Args: + results (dict): Result dict from :obj:``mmengine.BaseDataset``. + """ + h, w = results['ori_shape'] + gt_masks = self._process_masks(results) + if self.poly2mask: + gt_masks = BitmapMasks( + [self._poly2mask(mask, h, w) for mask in gt_masks], h, w) + else: + # fake polygon masks will be ignored in `PackDetInputs` + gt_masks = PolygonMasks([mask for mask in gt_masks], h, w) + results['gt_masks'] = gt_masks + + def _load_seg_map(self, results: dict) -> None: + """Private function to load semantic segmentation annotations. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded semantic segmentation annotations. + """ + if results.get('seg_map_path', None) is None: + return + + img_bytes = get( + results['seg_map_path'], backend_args=self.backend_args) + gt_semantic_seg = mmcv.imfrombytes( + img_bytes, flag='unchanged', + backend=self.imdecode_backend).squeeze() + + if self.reduce_zero_label: + # avoid using underflow conversion + gt_semantic_seg[gt_semantic_seg == 0] = self.ignore_index + gt_semantic_seg = gt_semantic_seg - 1 + gt_semantic_seg[gt_semantic_seg == self.ignore_index - + 1] = self.ignore_index + + # modify if custom classes + if results.get('label_map', None) is not None: + # Add deep copy to solve bug of repeatedly + # replace `gt_semantic_seg`, which is reported in + # https://github.com/open-mmlab/mmsegmentation/pull/1445/ + gt_semantic_seg_copy = gt_semantic_seg.copy() + for old_id, new_id in results['label_map'].items(): + gt_semantic_seg[gt_semantic_seg_copy == old_id] = new_id + results['gt_seg_map'] = gt_semantic_seg + results['ignore_index'] = self.ignore_index + + def transform(self, results: dict) -> dict: + """Function to load multiple types annotations. + + Args: + results (dict): Result dict from :obj:``mmengine.BaseDataset``. + + Returns: + dict: The dict contains loaded bounding box, label and + semantic segmentation. + """ + + if self.with_bbox: + self._load_bboxes(results) + if self.with_label: + self._load_labels(results) + if self.with_mask: + self._load_masks(results) + if self.with_seg: + self._load_seg_map(results) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(with_bbox={self.with_bbox}, ' + repr_str += f'with_label={self.with_label}, ' + repr_str += f'with_mask={self.with_mask}, ' + repr_str += f'with_seg={self.with_seg}, ' + repr_str += f'poly2mask={self.poly2mask}, ' + repr_str += f"imdecode_backend='{self.imdecode_backend}', " + repr_str += f'backend_args={self.backend_args})' + return repr_str + + +@TRANSFORMS.register_module() +class LoadPanopticAnnotations(LoadAnnotations): + """Load multiple types of panoptic annotations. + + The annotation format is as the following: + + .. code-block:: python + + { + 'instances': + [ + { + # List of 4 numbers representing the bounding box of the + # instance, in (x1, y1, x2, y2) order. + 'bbox': [x1, y1, x2, y2], + + # Label of image classification. + 'bbox_label': 1, + }, + ... + ] + 'segments_info': + [ + { + # id = cls_id + instance_id * INSTANCE_OFFSET + 'id': int, + + # Contiguous category id defined in dataset. + 'category': int + + # Thing flag. + 'is_thing': bool + }, + ... + ] + + # Filename of semantic or panoptic segmentation ground truth file. + 'seg_map_path': 'a/b/c' + } + + After this module, the annotation has been changed to the format below: + + .. code-block:: python + + { + # In (x1, y1, x2, y2) order, float type. N is the number of bboxes + # in an image + 'gt_bboxes': BaseBoxes(N, 4) + # In int type. + 'gt_bboxes_labels': np.ndarray(N, ) + # In built-in class + 'gt_masks': PolygonMasks (H, W) or BitmapMasks (H, W) + # In uint8 type. + 'gt_seg_map': np.ndarray (H, W) + # in (x, y, v) order, float type. + } + + Required Keys: + + - height + - width + - instances + - bbox + - bbox_label + - ignore_flag + - segments_info + - id + - category + - is_thing + - seg_map_path + + Added Keys: + + - gt_bboxes (BaseBoxes[torch.float32]) + - gt_bboxes_labels (np.int64) + - gt_masks (BitmapMasks | PolygonMasks) + - gt_seg_map (np.uint8) + - gt_ignore_flags (bool) + + Args: + with_bbox (bool): Whether to parse and load the bbox annotation. + Defaults to True. + with_label (bool): Whether to parse and load the label annotation. + Defaults to True. + with_mask (bool): Whether to parse and load the mask annotation. + Defaults to True. + with_seg (bool): Whether to parse and load the semantic segmentation + annotation. Defaults to False. + box_type (str): The box mode used to wrap the bboxes. + imdecode_backend (str): The image decoding backend type. The backend + argument for :func:``mmcv.imfrombytes``. + See :fun:``mmcv.imfrombytes`` for details. + Defaults to 'cv2'. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend in mmdet >= 3.0.0rc7. Defaults to None. + """ + + def __init__(self, + with_bbox: bool = True, + with_label: bool = True, + with_mask: bool = True, + with_seg: bool = True, + box_type: str = 'hbox', + imdecode_backend: str = 'cv2', + backend_args: dict = None) -> None: + try: + from panopticapi import utils + except ImportError: + raise ImportError( + 'panopticapi is not installed, please install it by: ' + 'pip install git+https://github.com/cocodataset/' + 'panopticapi.git.') + self.rgb2id = utils.rgb2id + + super(LoadPanopticAnnotations, self).__init__( + with_bbox=with_bbox, + with_label=with_label, + with_mask=with_mask, + with_seg=with_seg, + with_keypoints=False, + box_type=box_type, + imdecode_backend=imdecode_backend, + backend_args=backend_args) + + def _load_masks_and_semantic_segs(self, results: dict) -> None: + """Private function to load mask and semantic segmentation annotations. + + In gt_semantic_seg, the foreground label is from ``0`` to + ``num_things - 1``, the background label is from ``num_things`` to + ``num_things + num_stuff - 1``, 255 means the ignored label (``VOID``). + + Args: + results (dict): Result dict from :obj:``mmdet.CustomDataset``. + """ + # seg_map_path is None, when inference on the dataset without gts. + if results.get('seg_map_path', None) is None: + return + + img_bytes = get( + results['seg_map_path'], backend_args=self.backend_args) + pan_png = mmcv.imfrombytes( + img_bytes, flag='color', channel_order='rgb').squeeze() + pan_png = self.rgb2id(pan_png) + + gt_masks = [] + gt_seg = np.zeros_like(pan_png) + 255 # 255 as ignore + + for segment_info in results['segments_info']: + mask = (pan_png == segment_info['id']) + gt_seg = np.where(mask, segment_info['category'], gt_seg) + + # The legal thing masks + if segment_info.get('is_thing'): + gt_masks.append(mask.astype(np.uint8)) + + if self.with_mask: + h, w = results['ori_shape'] + gt_masks = BitmapMasks(gt_masks, h, w) + results['gt_masks'] = gt_masks + + if self.with_seg: + results['gt_seg_map'] = gt_seg + + def transform(self, results: dict) -> dict: + """Function to load multiple types panoptic annotations. + + Args: + results (dict): Result dict from :obj:``mmdet.CustomDataset``. + + Returns: + dict: The dict contains loaded bounding box, label, mask and + semantic segmentation annotations. + """ + + if self.with_bbox: + self._load_bboxes(results) + if self.with_label: + self._load_labels(results) + if self.with_mask or self.with_seg: + # The tasks completed by '_load_masks' and '_load_semantic_segs' + # in LoadAnnotations are merged to one function. + self._load_masks_and_semantic_segs(results) + + return results + + +@TRANSFORMS.register_module() +class LoadProposals(BaseTransform): + """Load proposal pipeline. + + Required Keys: + + - proposals + + Modified Keys: + + - proposals + + Args: + num_max_proposals (int, optional): Maximum number of proposals to load. + If not specified, all proposals will be loaded. + """ + + def __init__(self, num_max_proposals: Optional[int] = None) -> None: + self.num_max_proposals = num_max_proposals + + def transform(self, results: dict) -> dict: + """Transform function to load proposals from file. + + Args: + results (dict): Result dict from :obj:`mmdet.CustomDataset`. + + Returns: + dict: The dict contains loaded proposal annotations. + """ + + proposals = results['proposals'] + # the type of proposals should be `dict` or `InstanceData` + assert isinstance(proposals, dict) \ + or isinstance(proposals, BaseDataElement) + bboxes = proposals['bboxes'].astype(np.float32) + assert bboxes.shape[1] == 4, \ + f'Proposals should have shapes (n, 4), but found {bboxes.shape}' + + if 'scores' in proposals: + scores = proposals['scores'].astype(np.float32) + assert bboxes.shape[0] == scores.shape[0] + else: + scores = np.zeros(bboxes.shape[0], dtype=np.float32) + + if self.num_max_proposals is not None: + # proposals should sort by scores during dumping the proposals + bboxes = bboxes[:self.num_max_proposals] + scores = scores[:self.num_max_proposals] + + if len(bboxes) == 0: + bboxes = np.zeros((0, 4), dtype=np.float32) + scores = np.zeros(0, dtype=np.float32) + + results['proposals'] = bboxes + results['proposals_scores'] = scores + return results + + def __repr__(self): + return self.__class__.__name__ + \ + f'(num_max_proposals={self.num_max_proposals})' + + +@TRANSFORMS.register_module() +class FilterAnnotations(BaseTransform): + """Filter invalid annotations. + + Required Keys: + + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_ignore_flags (bool) (optional) + + Modified Keys: + + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_masks (optional) + - gt_ignore_flags (optional) + + Args: + min_gt_bbox_wh (tuple[float]): Minimum width and height of ground truth + boxes. Default: (1., 1.) + min_gt_mask_area (int): Minimum foreground area of ground truth masks. + Default: 1 + by_box (bool): Filter instances with bounding boxes not meeting the + min_gt_bbox_wh threshold. Default: True + by_mask (bool): Filter instances with masks not meeting + min_gt_mask_area threshold. Default: False + keep_empty (bool): Whether to return None when it + becomes an empty bbox after filtering. Defaults to True. + """ + + def __init__(self, + min_gt_bbox_wh: Tuple[int, int] = (1, 1), + min_gt_mask_area: int = 1, + by_box: bool = True, + by_mask: bool = False, + keep_empty: bool = True) -> None: + # TODO: add more filter options + assert by_box or by_mask + self.min_gt_bbox_wh = min_gt_bbox_wh + self.min_gt_mask_area = min_gt_mask_area + self.by_box = by_box + self.by_mask = by_mask + self.keep_empty = keep_empty + + @autocast_box_type() + def transform(self, results: dict) -> Union[dict, None]: + """Transform function to filter annotations. + + Args: + results (dict): Result dict. + + Returns: + dict: Updated result dict. + """ + assert 'gt_bboxes' in results + gt_bboxes = results['gt_bboxes'] + if gt_bboxes.shape[0] == 0: + return results + + tests = [] + if self.by_box: + tests.append( + ((gt_bboxes.widths > self.min_gt_bbox_wh[0]) & + (gt_bboxes.heights > self.min_gt_bbox_wh[1])).numpy()) + if self.by_mask: + assert 'gt_masks' in results + gt_masks = results['gt_masks'] + tests.append(gt_masks.areas >= self.min_gt_mask_area) + + keep = tests[0] + for t in tests[1:]: + keep = keep & t + + if not keep.any(): + if self.keep_empty: + return None + + keys = ('gt_bboxes', 'gt_bboxes_labels', 'gt_masks', 'gt_ignore_flags') + for key in keys: + if key in results: + results[key] = results[key][keep] + + return results + + def __repr__(self): + return self.__class__.__name__ + \ + f'(min_gt_bbox_wh={self.min_gt_bbox_wh}, ' \ + f'keep_empty={self.keep_empty})' + + +@TRANSFORMS.register_module() +class LoadEmptyAnnotations(BaseTransform): + """Load Empty Annotations for unlabeled images. + + Added Keys: + - gt_bboxes (np.float32) + - gt_bboxes_labels (np.int64) + - gt_masks (BitmapMasks | PolygonMasks) + - gt_seg_map (np.uint8) + - gt_ignore_flags (bool) + + Args: + with_bbox (bool): Whether to load the pseudo bbox annotation. + Defaults to True. + with_label (bool): Whether to load the pseudo label annotation. + Defaults to True. + with_mask (bool): Whether to load the pseudo mask annotation. + Default: False. + with_seg (bool): Whether to load the pseudo semantic segmentation + annotation. Defaults to False. + seg_ignore_label (int): The fill value used for segmentation map. + Note this value must equals ``ignore_label`` in ``semantic_head`` + of the corresponding config. Defaults to 255. + """ + + def __init__(self, + with_bbox: bool = True, + with_label: bool = True, + with_mask: bool = False, + with_seg: bool = False, + seg_ignore_label: int = 255) -> None: + self.with_bbox = with_bbox + self.with_label = with_label + self.with_mask = with_mask + self.with_seg = with_seg + self.seg_ignore_label = seg_ignore_label + + def transform(self, results: dict) -> dict: + """Transform function to load empty annotations. + + Args: + results (dict): Result dict. + Returns: + dict: Updated result dict. + """ + if self.with_bbox: + results['gt_bboxes'] = np.zeros((0, 4), dtype=np.float32) + results['gt_ignore_flags'] = np.zeros((0, ), dtype=bool) + if self.with_label: + results['gt_bboxes_labels'] = np.zeros((0, ), dtype=np.int64) + if self.with_mask: + # TODO: support PolygonMasks + h, w = results['img_shape'] + gt_masks = np.zeros((0, h, w), dtype=np.uint8) + results['gt_masks'] = BitmapMasks(gt_masks, h, w) + if self.with_seg: + h, w = results['img_shape'] + results['gt_seg_map'] = self.seg_ignore_label * np.ones( + (h, w), dtype=np.uint8) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(with_bbox={self.with_bbox}, ' + repr_str += f'with_label={self.with_label}, ' + repr_str += f'with_mask={self.with_mask}, ' + repr_str += f'with_seg={self.with_seg}, ' + repr_str += f'seg_ignore_label={self.seg_ignore_label})' + return repr_str + + +@TRANSFORMS.register_module() +class InferencerLoader(BaseTransform): + """Load an image from ``results['img']``. + + Similar with :obj:`LoadImageFromFile`, but the image has been loaded as + :obj:`np.ndarray` in ``results['img']``. Can be used when loading image + from webcam. + + Required Keys: + + - img + + Modified Keys: + + - img + - img_path + - img_shape + - ori_shape + + Args: + to_float32 (bool): Whether to convert the loaded image to a float32 + numpy array. If set to False, the loaded image is an uint8 array. + Defaults to False. + """ + + def __init__(self, **kwargs) -> None: + super().__init__() + self.from_file = TRANSFORMS.build( + dict(type='LoadImageFromFile', **kwargs)) + self.from_ndarray = TRANSFORMS.build( + dict(type='mmdet.LoadImageFromNDArray', **kwargs)) + + def transform(self, results: Union[str, np.ndarray, dict]) -> dict: + """Transform function to add image meta information. + + Args: + results (str, np.ndarray or dict): The result. + + Returns: + dict: The dict contains loaded image and meta information. + """ + if isinstance(results, str): + inputs = dict(img_path=results) + elif isinstance(results, np.ndarray): + inputs = dict(img=results) + elif isinstance(results, dict): + inputs = results + else: + raise NotImplementedError + + if 'img' in inputs: + return self.from_ndarray(inputs) + return self.from_file(inputs) + + +@TRANSFORMS.register_module() +class LoadTrackAnnotations(LoadAnnotations): + """Load and process the ``instances`` and ``seg_map`` annotation provided + by dataset. It must load ``instances_ids`` which is only used in the + tracking tasks. The annotation format is as the following: + + .. code-block:: python + { + 'instances': + [ + { + # List of 4 numbers representing the bounding box of the + # instance, in (x1, y1, x2, y2) order. + 'bbox': [x1, y1, x2, y2], + # Label of image classification. + 'bbox_label': 1, + # Used in tracking. + # Id of instances. + 'instance_id': 100, + # Used in instance/panoptic segmentation. The segmentation mask + # of the instance or the information of segments. + # 1. If list[list[float]], it represents a list of polygons, + # one for each connected component of the object. Each + # list[float] is one simple polygon in the format of + # [x1, y1, ..., xn, yn] (n >= 3). The Xs and Ys are absolute + # coordinates in unit of pixels. + # 2. If dict, it represents the per-pixel segmentation mask in + # COCO's compressed RLE format. The dict should have keys + # “size” and “counts”. Can be loaded by pycocotools + 'mask': list[list[float]] or dict, + } + ] + # Filename of semantic or panoptic segmentation ground truth file. + 'seg_map_path': 'a/b/c' + } + + After this module, the annotation has been changed to the format below: + .. code-block:: python + { + # In (x1, y1, x2, y2) order, float type. N is the number of bboxes + # in an image + 'gt_bboxes': np.ndarray(N, 4) + # In int type. + 'gt_bboxes_labels': np.ndarray(N, ) + # In built-in class + 'gt_masks': PolygonMasks (H, W) or BitmapMasks (H, W) + # In uint8 type. + 'gt_seg_map': np.ndarray (H, W) + # in (x, y, v) order, float type. + } + + Required Keys: + + - height (optional) + - width (optional) + - instances + - bbox (optional) + - bbox_label + - instance_id (optional) + - mask (optional) + - ignore_flag (optional) + - seg_map_path (optional) + + Added Keys: + + - gt_bboxes (np.float32) + - gt_bboxes_labels (np.int32) + - gt_instances_ids (np.int32) + - gt_masks (BitmapMasks | PolygonMasks) + - gt_seg_map (np.uint8) + - gt_ignore_flags (np.bool) + """ + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + + def _load_bboxes(self, results: dict) -> None: + """Private function to load bounding box annotations. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded bounding box annotations. + """ + gt_bboxes = [] + gt_ignore_flags = [] + # TODO: use bbox_type + for instance in results['instances']: + # The datasets which are only format in evaluation don't have + # groundtruth boxes. + if 'bbox' in instance: + gt_bboxes.append(instance['bbox']) + if 'ignore_flag' in instance: + gt_ignore_flags.append(instance['ignore_flag']) + + # TODO: check this case + if len(gt_bboxes) != len(gt_ignore_flags): + # There may be no ``gt_ignore_flags`` in some cases, we treat them + # as all False in order to keep the length of ``gt_bboxes`` and + # ``gt_ignore_flags`` the same + gt_ignore_flags = [False] * len(gt_bboxes) + + results['gt_bboxes'] = np.array( + gt_bboxes, dtype=np.float32).reshape(-1, 4) + results['gt_ignore_flags'] = np.array(gt_ignore_flags, dtype=bool) + + def _load_instances_ids(self, results: dict) -> None: + """Private function to load instances id annotations. + + Args: + results (dict): Result dict from :obj :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict containing instances id annotations. + """ + gt_instances_ids = [] + for instance in results['instances']: + gt_instances_ids.append(instance['instance_id']) + results['gt_instances_ids'] = np.array( + gt_instances_ids, dtype=np.int32) + + def transform(self, results: dict) -> dict: + """Function to load multiple types annotations. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded bounding box, label, instances id + and semantic segmentation and keypoints annotations. + """ + results = super().transform(results) + self._load_instances_ids(results) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(with_bbox={self.with_bbox}, ' + repr_str += f'with_label={self.with_label}, ' + repr_str += f'with_mask={self.with_mask}, ' + repr_str += f'with_seg={self.with_seg}, ' + repr_str += f'poly2mask={self.poly2mask}, ' + repr_str += f"imdecode_backend='{self.imdecode_backend}', " + repr_str += f'file_client_args={self.file_client_args})' + return repr_str diff --git a/mmdetection/mmdet/datasets/transforms/text_transformers.py b/mmdetection/mmdet/datasets/transforms/text_transformers.py new file mode 100644 index 00000000..12a0e57d --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/text_transformers.py @@ -0,0 +1,255 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json + +from mmcv.transforms import BaseTransform + +from mmdet.registry import TRANSFORMS +from mmdet.structures.bbox import BaseBoxes + +try: + from transformers import AutoTokenizer + from transformers import BertModel as HFBertModel +except ImportError: + AutoTokenizer = None + HFBertModel = None + +import random +import re + +import numpy as np + + +def clean_name(name): + name = re.sub(r'\(.*\)', '', name) + name = re.sub(r'_', ' ', name) + name = re.sub(r' ', ' ', name) + name = name.lower() + return name + + +def check_for_positive_overflow(gt_bboxes, gt_labels, text, tokenizer, + max_tokens): + # Check if we have too many positive labels + # generate a caption by appending the positive labels + positive_label_list = np.unique(gt_labels).tolist() + # random shuffule so we can sample different annotations + # at different epochs + random.shuffle(positive_label_list) + + kept_lables = [] + length = 0 + + for index, label in enumerate(positive_label_list): + + label_text = clean_name(text[str(label)]) + '. ' + + tokenized = tokenizer.tokenize(label_text) + + length += len(tokenized) + + if length > max_tokens: + break + else: + kept_lables.append(label) + + keep_box_index = [] + keep_gt_labels = [] + for i in range(len(gt_labels)): + if gt_labels[i] in kept_lables: + keep_box_index.append(i) + keep_gt_labels.append(gt_labels[i]) + + return gt_bboxes[keep_box_index], np.array( + keep_gt_labels, dtype=np.long), length + + +def generate_senetence_given_labels(positive_label_list, negative_label_list, + text): + label_to_positions = {} + + label_list = negative_label_list + positive_label_list + + random.shuffle(label_list) + + pheso_caption = '' + + label_remap_dict = {} + for index, label in enumerate(label_list): + + start_index = len(pheso_caption) + + pheso_caption += clean_name(text[str(label)]) + + end_index = len(pheso_caption) + + if label in positive_label_list: + label_to_positions[index] = [[start_index, end_index]] + label_remap_dict[int(label)] = index + + # if index != len(label_list) - 1: + # pheso_caption += '. ' + pheso_caption += '. ' + + return label_to_positions, pheso_caption, label_remap_dict + + +@TRANSFORMS.register_module() +class RandomSamplingNegPos(BaseTransform): + + def __init__(self, + tokenizer_name, + num_sample_negative=85, + max_tokens=256, + full_sampling_prob=0.5, + label_map_file=None): + if AutoTokenizer is None: + raise RuntimeError( + 'transformers is not installed, please install it by: ' + 'pip install transformers.') + + self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_name) + self.num_sample_negative = num_sample_negative + self.full_sampling_prob = full_sampling_prob + self.max_tokens = max_tokens + self.label_map = None + if label_map_file: + with open(label_map_file, 'r') as file: + self.label_map = json.load(file) + + def transform(self, results: dict) -> dict: + if 'phrases' in results: + return self.vg_aug(results) + else: + return self.od_aug(results) + + def vg_aug(self, results): + gt_bboxes = results['gt_bboxes'] + if isinstance(gt_bboxes, BaseBoxes): + gt_bboxes = gt_bboxes.tensor + gt_labels = results['gt_bboxes_labels'] + text = results['text'].lower().strip() + if not text.endswith('.'): + text = text + '. ' + + phrases = results['phrases'] + # TODO: add neg + positive_label_list = np.unique(gt_labels).tolist() + label_to_positions = {} + for label in positive_label_list: + label_to_positions[label] = phrases[label]['tokens_positive'] + + results['gt_bboxes'] = gt_bboxes + results['gt_bboxes_labels'] = gt_labels + + results['text'] = text + results['tokens_positive'] = label_to_positions + return results + + def od_aug(self, results): + gt_bboxes = results['gt_bboxes'] + if isinstance(gt_bboxes, BaseBoxes): + gt_bboxes = gt_bboxes.tensor + gt_labels = results['gt_bboxes_labels'] + + if 'text' not in results: + assert self.label_map is not None + text = self.label_map + else: + text = results['text'] + + original_box_num = len(gt_labels) + # If the category name is in the format of 'a/b' (in object365), + # we randomly select one of them. + for key, value in text.items(): + if '/' in value: + text[key] = random.choice(value.split('/')).strip() + + gt_bboxes, gt_labels, positive_caption_length = \ + check_for_positive_overflow(gt_bboxes, gt_labels, + text, self.tokenizer, self.max_tokens) + + if len(gt_bboxes) < original_box_num: + print('WARNING: removed {} boxes due to positive caption overflow'. + format(original_box_num - len(gt_bboxes))) + + valid_negative_indexes = list(text.keys()) + + positive_label_list = np.unique(gt_labels).tolist() + full_negative = self.num_sample_negative + + if full_negative > len(valid_negative_indexes): + full_negative = len(valid_negative_indexes) + + outer_prob = random.random() + + if outer_prob < self.full_sampling_prob: + # c. probability_full: add both all positive and all negatives + num_negatives = full_negative + else: + if random.random() < 1.0: + num_negatives = np.random.choice(max(1, full_negative)) + 1 + else: + num_negatives = full_negative + + # Keep some negatives + negative_label_list = set() + if num_negatives != -1: + if num_negatives > len(valid_negative_indexes): + num_negatives = len(valid_negative_indexes) + + for i in np.random.choice( + valid_negative_indexes, size=num_negatives, replace=False): + if int(i) not in positive_label_list: + negative_label_list.add(i) + + random.shuffle(positive_label_list) + + negative_label_list = list(negative_label_list) + random.shuffle(negative_label_list) + + negative_max_length = self.max_tokens - positive_caption_length + screened_negative_label_list = [] + + for negative_label in negative_label_list: + label_text = clean_name(text[str(negative_label)]) + '. ' + + tokenized = self.tokenizer.tokenize(label_text) + + negative_max_length -= len(tokenized) + + if negative_max_length > 0: + screened_negative_label_list.append(negative_label) + else: + break + negative_label_list = screened_negative_label_list + label_to_positions, pheso_caption, label_remap_dict = \ + generate_senetence_given_labels(positive_label_list, + negative_label_list, text) + + # label remap + if len(gt_labels) > 0: + gt_labels = np.vectorize(lambda x: label_remap_dict[x])(gt_labels) + + results['gt_bboxes'] = gt_bboxes + results['gt_bboxes_labels'] = gt_labels + + results['text'] = pheso_caption + results['tokens_positive'] = label_to_positions + + return results + + +@TRANSFORMS.register_module() +class LoadTextAnnotations(BaseTransform): + + def transform(self, results: dict) -> dict: + if 'phrases' in results: + tokens_positive = [ + phrase['tokens_positive'] + for phrase in results['phrases'].values() + ] + results['tokens_positive'] = tokens_positive + else: + text = results['text'] + results['text'] = list(text.values()) + return results diff --git a/mmdetection/mmdet/datasets/transforms/transformers_glip.py b/mmdetection/mmdet/datasets/transforms/transformers_glip.py new file mode 100644 index 00000000..60c4f87d --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/transformers_glip.py @@ -0,0 +1,66 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmcv +import numpy as np +from mmcv.transforms import BaseTransform + +from mmdet.registry import TRANSFORMS +from mmdet.structures.bbox import HorizontalBoxes, autocast_box_type +from .transforms import RandomFlip + + +@TRANSFORMS.register_module() +class GTBoxSubOne_GLIP(BaseTransform): + """Subtract 1 from the x2 and y2 coordinates of the gt_bboxes.""" + + def transform(self, results: dict) -> dict: + if 'gt_bboxes' in results: + gt_bboxes = results['gt_bboxes'] + if isinstance(gt_bboxes, np.ndarray): + gt_bboxes[:, 2:] -= 1 + results['gt_bboxes'] = gt_bboxes + elif isinstance(gt_bboxes, HorizontalBoxes): + gt_bboxes = results['gt_bboxes'].tensor + gt_bboxes[:, 2:] -= 1 + results['gt_bboxes'] = HorizontalBoxes(gt_bboxes) + else: + raise NotImplementedError + return results + + +@TRANSFORMS.register_module() +class RandomFlip_GLIP(RandomFlip): + """Flip the image & bboxes & masks & segs horizontally or vertically. + + When using horizontal flipping, the corresponding bbox x-coordinate needs + to be additionally subtracted by one. + """ + + @autocast_box_type() + def _flip(self, results: dict) -> None: + """Flip images, bounding boxes, and semantic segmentation map.""" + # flip image + results['img'] = mmcv.imflip( + results['img'], direction=results['flip_direction']) + + img_shape = results['img'].shape[:2] + + # flip bboxes + if results.get('gt_bboxes', None) is not None: + results['gt_bboxes'].flip_(img_shape, results['flip_direction']) + # Only change this line + if results['flip_direction'] == 'horizontal': + results['gt_bboxes'].translate_([-1, 0]) + + # TODO: check it + # flip masks + if results.get('gt_masks', None) is not None: + results['gt_masks'] = results['gt_masks'].flip( + results['flip_direction']) + + # flip segs + if results.get('gt_seg_map', None) is not None: + results['gt_seg_map'] = mmcv.imflip( + results['gt_seg_map'], direction=results['flip_direction']) + + # record homography matrix for flip + self._record_homography_matrix(results) diff --git a/mmdetection/mmdet/datasets/transforms/transforms.py b/mmdetection/mmdet/datasets/transforms/transforms.py new file mode 100644 index 00000000..c50b987d --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/transforms.py @@ -0,0 +1,3856 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import inspect +import math +import warnings +from typing import List, Optional, Sequence, Tuple, Union + +import cv2 +import mmcv +import numpy as np +from mmcv.image import imresize +from mmcv.image.geometric import _scale_size +from mmcv.transforms import BaseTransform +from mmcv.transforms import Pad as MMCV_Pad +from mmcv.transforms import RandomFlip as MMCV_RandomFlip +from mmcv.transforms import Resize as MMCV_Resize +from mmcv.transforms.utils import avoid_cache_randomness, cache_randomness +from mmengine.dataset import BaseDataset +from mmengine.utils import is_str +from numpy import random + +from mmdet.registry import TRANSFORMS +from mmdet.structures.bbox import HorizontalBoxes, autocast_box_type +from mmdet.structures.mask import BitmapMasks, PolygonMasks +from mmdet.utils import log_img_scale + +try: + from imagecorruptions import corrupt +except ImportError: + corrupt = None + +try: + import albumentations + from albumentations import Compose +except ImportError: + albumentations = None + Compose = None + +Number = Union[int, float] + + +def _fixed_scale_size( + size: Tuple[int, int], + scale: Union[float, int, tuple], +) -> Tuple[int, int]: + """Rescale a size by a ratio. + + Args: + size (tuple[int]): (w, h). + scale (float | tuple(float)): Scaling factor. + + Returns: + tuple[int]: scaled size. + """ + if isinstance(scale, (float, int)): + scale = (scale, scale) + w, h = size + # don't need o.5 offset + return int(w * float(scale[0])), int(h * float(scale[1])) + + +def rescale_size(old_size: tuple, + scale: Union[float, int, tuple], + return_scale: bool = False) -> tuple: + """Calculate the new size to be rescaled to. + + Args: + old_size (tuple[int]): The old size (w, h) of image. + scale (float | tuple[int]): The scaling factor or maximum size. + If it is a float number, then the image will be rescaled by this + factor, else if it is a tuple of 2 integers, then the image will + be rescaled as large as possible within the scale. + return_scale (bool): Whether to return the scaling factor besides the + rescaled image size. + + Returns: + tuple[int]: The new rescaled image size. + """ + w, h = old_size + if isinstance(scale, (float, int)): + if scale <= 0: + raise ValueError(f'Invalid scale {scale}, must be positive.') + scale_factor = scale + elif isinstance(scale, tuple): + max_long_edge = max(scale) + max_short_edge = min(scale) + scale_factor = min(max_long_edge / max(h, w), + max_short_edge / min(h, w)) + else: + raise TypeError( + f'Scale must be a number or tuple of int, but got {type(scale)}') + # only change this + new_size = _fixed_scale_size((w, h), scale_factor) + + if return_scale: + return new_size, scale_factor + else: + return new_size + + +def imrescale( + img: np.ndarray, + scale: Union[float, Tuple[int, int]], + return_scale: bool = False, + interpolation: str = 'bilinear', + backend: Optional[str] = None +) -> Union[np.ndarray, Tuple[np.ndarray, float]]: + """Resize image while keeping the aspect ratio. + + Args: + img (ndarray): The input image. + scale (float | tuple[int]): The scaling factor or maximum size. + If it is a float number, then the image will be rescaled by this + factor, else if it is a tuple of 2 integers, then the image will + be rescaled as large as possible within the scale. + return_scale (bool): Whether to return the scaling factor besides the + rescaled image. + interpolation (str): Same as :func:`resize`. + backend (str | None): Same as :func:`resize`. + + Returns: + ndarray: The rescaled image. + """ + h, w = img.shape[:2] + new_size, scale_factor = rescale_size((w, h), scale, return_scale=True) + rescaled_img = imresize( + img, new_size, interpolation=interpolation, backend=backend) + if return_scale: + return rescaled_img, scale_factor + else: + return rescaled_img + + +@TRANSFORMS.register_module() +class Resize(MMCV_Resize): + """Resize images & bbox & seg. + + This transform resizes the input image according to ``scale`` or + ``scale_factor``. Bboxes, masks, and seg map are then resized + with the same scale factor. + if ``scale`` and ``scale_factor`` are both set, it will use ``scale`` to + resize. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes + - gt_masks + - gt_seg_map + + + Added Keys: + + - scale + - scale_factor + - keep_ratio + - homography_matrix + + Args: + scale (int or tuple): Images scales for resizing. Defaults to None + scale_factor (float or tuple[float]): Scale factors for resizing. + Defaults to None. + keep_ratio (bool): Whether to keep the aspect ratio when resizing the + image. Defaults to False. + clip_object_border (bool): Whether to clip the objects + outside the border of the image. In some dataset like MOT17, the gt + bboxes are allowed to cross the border of images. Therefore, we + don't need to clip the gt bboxes in these cases. Defaults to True. + backend (str): Image resize backend, choices are 'cv2' and 'pillow'. + These two backends generates slightly different results. Defaults + to 'cv2'. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. Defaults + to 'bilinear'. + """ + + def _resize_masks(self, results: dict) -> None: + """Resize masks with ``results['scale']``""" + if results.get('gt_masks', None) is not None: + if self.keep_ratio: + results['gt_masks'] = results['gt_masks'].rescale( + results['scale']) + else: + results['gt_masks'] = results['gt_masks'].resize( + results['img_shape']) + + def _resize_bboxes(self, results: dict) -> None: + """Resize bounding boxes with ``results['scale_factor']``.""" + if results.get('gt_bboxes', None) is not None: + results['gt_bboxes'].rescale_(results['scale_factor']) + if self.clip_object_border: + results['gt_bboxes'].clip_(results['img_shape']) + + def _record_homography_matrix(self, results: dict) -> None: + """Record the homography matrix for the Resize.""" + w_scale, h_scale = results['scale_factor'] + homography_matrix = np.array( + [[w_scale, 0, 0], [0, h_scale, 0], [0, 0, 1]], dtype=np.float32) + if results.get('homography_matrix', None) is None: + results['homography_matrix'] = homography_matrix + else: + results['homography_matrix'] = homography_matrix @ results[ + 'homography_matrix'] + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Transform function to resize images, bounding boxes and semantic + segmentation map. + + Args: + results (dict): Result dict from loading pipeline. + Returns: + dict: Resized results, 'img', 'gt_bboxes', 'gt_seg_map', + 'scale', 'scale_factor', 'height', 'width', and 'keep_ratio' keys + are updated in result dict. + """ + if self.scale: + results['scale'] = self.scale + else: + img_shape = results['img'].shape[:2] + results['scale'] = _scale_size(img_shape[::-1], self.scale_factor) + self._resize_img(results) + self._resize_bboxes(results) + self._resize_masks(results) + self._resize_seg(results) + self._record_homography_matrix(results) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(scale={self.scale}, ' + repr_str += f'scale_factor={self.scale_factor}, ' + repr_str += f'keep_ratio={self.keep_ratio}, ' + repr_str += f'clip_object_border={self.clip_object_border}), ' + repr_str += f'backend={self.backend}), ' + repr_str += f'interpolation={self.interpolation})' + return repr_str + + +@TRANSFORMS.register_module() +class FixScaleResize(Resize): + """Compared to Resize, FixScaleResize fixes the scaling issue when + `keep_ratio=true`.""" + + def _resize_img(self, results): + """Resize images with ``results['scale']``.""" + if results.get('img', None) is not None: + if self.keep_ratio: + img, scale_factor = imrescale( + results['img'], + results['scale'], + interpolation=self.interpolation, + return_scale=True, + backend=self.backend) + new_h, new_w = img.shape[:2] + h, w = results['img'].shape[:2] + w_scale = new_w / w + h_scale = new_h / h + else: + img, w_scale, h_scale = mmcv.imresize( + results['img'], + results['scale'], + interpolation=self.interpolation, + return_scale=True, + backend=self.backend) + results['img'] = img + results['img_shape'] = img.shape[:2] + results['scale_factor'] = (w_scale, h_scale) + results['keep_ratio'] = self.keep_ratio + + +@TRANSFORMS.register_module() +class ResizeShortestEdge(BaseTransform): + """Resize the image and mask while keeping the aspect ratio unchanged. + + Modified from https://github.com/facebookresearch/detectron2/blob/main/detectron2/data/transforms/augmentation_impl.py#L130 # noqa:E501 + + This transform attempts to scale the shorter edge to the given + `scale`, as long as the longer edge does not exceed `max_size`. + If `max_size` is reached, then downscale so that the longer + edge does not exceed `max_size`. + + Required Keys: + - img + - gt_seg_map (optional) + Modified Keys: + - img + - img_shape + - gt_seg_map (optional)) + Added Keys: + - scale + - scale_factor + - keep_ratio + + Args: + scale (Union[int, Tuple[int, int]]): The target short edge length. + If it's tuple, will select the min value as the short edge length. + max_size (int): The maximum allowed longest edge length. + """ + + def __init__(self, + scale: Union[int, Tuple[int, int]], + max_size: Optional[int] = None, + resize_type: str = 'Resize', + **resize_kwargs) -> None: + super().__init__() + self.scale = scale + self.max_size = max_size + + self.resize_cfg = dict(type=resize_type, **resize_kwargs) + self.resize = TRANSFORMS.build({'scale': 0, **self.resize_cfg}) + + def _get_output_shape( + self, img: np.ndarray, + short_edge_length: Union[int, Tuple[int, int]]) -> Tuple[int, int]: + """Compute the target image shape with the given `short_edge_length`. + + Args: + img (np.ndarray): The input image. + short_edge_length (Union[int, Tuple[int, int]]): The target short + edge length. If it's tuple, will select the min value as the + short edge length. + """ + h, w = img.shape[:2] + if isinstance(short_edge_length, int): + size = short_edge_length * 1.0 + elif isinstance(short_edge_length, tuple): + size = min(short_edge_length) * 1.0 + scale = size / min(h, w) + if h < w: + new_h, new_w = size, scale * w + else: + new_h, new_w = scale * h, size + + if self.max_size and max(new_h, new_w) > self.max_size: + scale = self.max_size * 1.0 / max(new_h, new_w) + new_h *= scale + new_w *= scale + + new_h = int(new_h + 0.5) + new_w = int(new_w + 0.5) + return new_w, new_h + + def transform(self, results: dict) -> dict: + self.resize.scale = self._get_output_shape(results['img'], self.scale) + return self.resize(results) + + +@TRANSFORMS.register_module() +class FixShapeResize(Resize): + """Resize images & bbox & seg to the specified size. + + This transform resizes the input image according to ``width`` and + ``height``. Bboxes, masks, and seg map are then resized + with the same parameters. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes + - gt_masks + - gt_seg_map + + + Added Keys: + + - scale + - scale_factor + - keep_ratio + - homography_matrix + + Args: + width (int): width for resizing. + height (int): height for resizing. + Defaults to None. + pad_val (Number | dict[str, Number], optional): Padding value for if + the pad_mode is "constant". If it is a single number, the value + to pad the image is the number and to pad the semantic + segmentation map is 255. If it is a dict, it should have the + following keys: + + - img: The value to pad the image. + - seg: The value to pad the semantic segmentation map. + Defaults to dict(img=0, seg=255). + keep_ratio (bool): Whether to keep the aspect ratio when resizing the + image. Defaults to False. + clip_object_border (bool): Whether to clip the objects + outside the border of the image. In some dataset like MOT17, the gt + bboxes are allowed to cross the border of images. Therefore, we + don't need to clip the gt bboxes in these cases. Defaults to True. + backend (str): Image resize backend, choices are 'cv2' and 'pillow'. + These two backends generates slightly different results. Defaults + to 'cv2'. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. Defaults + to 'bilinear'. + """ + + def __init__(self, + width: int, + height: int, + pad_val: Union[Number, dict] = dict(img=0, seg=255), + keep_ratio: bool = False, + clip_object_border: bool = True, + backend: str = 'cv2', + interpolation: str = 'bilinear') -> None: + assert width is not None and height is not None, ( + '`width` and' + '`height` can not be `None`') + + self.width = width + self.height = height + self.scale = (width, height) + + self.backend = backend + self.interpolation = interpolation + self.keep_ratio = keep_ratio + self.clip_object_border = clip_object_border + + if keep_ratio is True: + # padding to the fixed size when keep_ratio=True + self.pad_transform = Pad(size=self.scale, pad_val=pad_val) + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Transform function to resize images, bounding boxes and semantic + segmentation map. + + Args: + results (dict): Result dict from loading pipeline. + Returns: + dict: Resized results, 'img', 'gt_bboxes', 'gt_seg_map', + 'scale', 'scale_factor', 'height', 'width', and 'keep_ratio' keys + are updated in result dict. + """ + img = results['img'] + h, w = img.shape[:2] + if self.keep_ratio: + scale_factor = min(self.width / w, self.height / h) + results['scale_factor'] = (scale_factor, scale_factor) + real_w, real_h = int(w * float(scale_factor) + + 0.5), int(h * float(scale_factor) + 0.5) + img, scale_factor = mmcv.imrescale( + results['img'], (real_w, real_h), + interpolation=self.interpolation, + return_scale=True, + backend=self.backend) + # the w_scale and h_scale has minor difference + # a real fix should be done in the mmcv.imrescale in the future + results['img'] = img + results['img_shape'] = img.shape[:2] + results['keep_ratio'] = self.keep_ratio + results['scale'] = (real_w, real_h) + else: + results['scale'] = (self.width, self.height) + results['scale_factor'] = (self.width / w, self.height / h) + super()._resize_img(results) + + self._resize_bboxes(results) + self._resize_masks(results) + self._resize_seg(results) + self._record_homography_matrix(results) + if self.keep_ratio: + self.pad_transform(results) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(width={self.width}, height={self.height}, ' + repr_str += f'keep_ratio={self.keep_ratio}, ' + repr_str += f'clip_object_border={self.clip_object_border}), ' + repr_str += f'backend={self.backend}), ' + repr_str += f'interpolation={self.interpolation})' + return repr_str + + +@TRANSFORMS.register_module() +class RandomFlip(MMCV_RandomFlip): + """Flip the image & bbox & mask & segmentation map. Added or Updated keys: + flip, flip_direction, img, gt_bboxes, and gt_seg_map. There are 3 flip + modes: + + - ``prob`` is float, ``direction`` is string: the image will be + ``direction``ly flipped with probability of ``prob`` . + E.g., ``prob=0.5``, ``direction='horizontal'``, + then image will be horizontally flipped with probability of 0.5. + - ``prob`` is float, ``direction`` is list of string: the image will + be ``direction[i]``ly flipped with probability of + ``prob/len(direction)``. + E.g., ``prob=0.5``, ``direction=['horizontal', 'vertical']``, + then image will be horizontally flipped with probability of 0.25, + vertically with probability of 0.25. + - ``prob`` is list of float, ``direction`` is list of string: + given ``len(prob) == len(direction)``, the image will + be ``direction[i]``ly flipped with probability of ``prob[i]``. + E.g., ``prob=[0.3, 0.5]``, ``direction=['horizontal', + 'vertical']``, then image will be horizontally flipped with + probability of 0.3, vertically with probability of 0.5. + + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - gt_bboxes + - gt_masks + - gt_seg_map + + Added Keys: + + - flip + - flip_direction + - homography_matrix + + + Args: + prob (float | list[float], optional): The flipping probability. + Defaults to None. + direction(str | list[str]): The flipping direction. Options + If input is a list, the length must equal ``prob``. Each + element in ``prob`` indicates the flip probability of + corresponding direction. Defaults to 'horizontal'. + """ + + def _record_homography_matrix(self, results: dict) -> None: + """Record the homography matrix for the RandomFlip.""" + cur_dir = results['flip_direction'] + h, w = results['img'].shape[:2] + + if cur_dir == 'horizontal': + homography_matrix = np.array([[-1, 0, w], [0, 1, 0], [0, 0, 1]], + dtype=np.float32) + elif cur_dir == 'vertical': + homography_matrix = np.array([[1, 0, 0], [0, -1, h], [0, 0, 1]], + dtype=np.float32) + elif cur_dir == 'diagonal': + homography_matrix = np.array([[-1, 0, w], [0, -1, h], [0, 0, 1]], + dtype=np.float32) + else: + homography_matrix = np.eye(3, dtype=np.float32) + + if results.get('homography_matrix', None) is None: + results['homography_matrix'] = homography_matrix + else: + results['homography_matrix'] = homography_matrix @ results[ + 'homography_matrix'] + + @autocast_box_type() + def _flip(self, results: dict) -> None: + """Flip images, bounding boxes, and semantic segmentation map.""" + # flip image + results['img'] = mmcv.imflip( + results['img'], direction=results['flip_direction']) + + img_shape = results['img'].shape[:2] + + # flip bboxes + if results.get('gt_bboxes', None) is not None: + results['gt_bboxes'].flip_(img_shape, results['flip_direction']) + + # flip masks + if results.get('gt_masks', None) is not None: + results['gt_masks'] = results['gt_masks'].flip( + results['flip_direction']) + + # flip segs + if results.get('gt_seg_map', None) is not None: + results['gt_seg_map'] = mmcv.imflip( + results['gt_seg_map'], direction=results['flip_direction']) + + # record homography matrix for flip + self._record_homography_matrix(results) + + +@TRANSFORMS.register_module() +class RandomShift(BaseTransform): + """Shift the image and box given shift pixels and probability. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) + - gt_bboxes_labels (np.int64) + - gt_ignore_flags (bool) (optional) + + Modified Keys: + + - img + - gt_bboxes + - gt_bboxes_labels + - gt_ignore_flags (bool) (optional) + + Args: + prob (float): Probability of shifts. Defaults to 0.5. + max_shift_px (int): The max pixels for shifting. Defaults to 32. + filter_thr_px (int): The width and height threshold for filtering. + The bbox and the rest of the targets below the width and + height threshold will be filtered. Defaults to 1. + """ + + def __init__(self, + prob: float = 0.5, + max_shift_px: int = 32, + filter_thr_px: int = 1) -> None: + assert 0 <= prob <= 1 + assert max_shift_px >= 0 + self.prob = prob + self.max_shift_px = max_shift_px + self.filter_thr_px = int(filter_thr_px) + + @cache_randomness + def _random_prob(self) -> float: + return random.uniform(0, 1) + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Transform function to random shift images, bounding boxes. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Shift results. + """ + if self._random_prob() < self.prob: + img_shape = results['img'].shape[:2] + + random_shift_x = random.randint(-self.max_shift_px, + self.max_shift_px) + random_shift_y = random.randint(-self.max_shift_px, + self.max_shift_px) + new_x = max(0, random_shift_x) + ori_x = max(0, -random_shift_x) + new_y = max(0, random_shift_y) + ori_y = max(0, -random_shift_y) + + # TODO: support mask and semantic segmentation maps. + bboxes = results['gt_bboxes'].clone() + bboxes.translate_([random_shift_x, random_shift_y]) + + # clip border + bboxes.clip_(img_shape) + + # remove invalid bboxes + valid_inds = (bboxes.widths > self.filter_thr_px).numpy() & ( + bboxes.heights > self.filter_thr_px).numpy() + # If the shift does not contain any gt-bbox area, skip this + # image. + if not valid_inds.any(): + return results + bboxes = bboxes[valid_inds] + results['gt_bboxes'] = bboxes + results['gt_bboxes_labels'] = results['gt_bboxes_labels'][ + valid_inds] + + if results.get('gt_ignore_flags', None) is not None: + results['gt_ignore_flags'] = \ + results['gt_ignore_flags'][valid_inds] + + # shift img + img = results['img'] + new_img = np.zeros_like(img) + img_h, img_w = img.shape[:2] + new_h = img_h - np.abs(random_shift_y) + new_w = img_w - np.abs(random_shift_x) + new_img[new_y:new_y + new_h, new_x:new_x + new_w] \ + = img[ori_y:ori_y + new_h, ori_x:ori_x + new_w] + results['img'] = new_img + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, ' + repr_str += f'max_shift_px={self.max_shift_px}, ' + repr_str += f'filter_thr_px={self.filter_thr_px})' + return repr_str + + +@TRANSFORMS.register_module() +class Pad(MMCV_Pad): + """Pad the image & segmentation map. + + There are three padding modes: (1) pad to a fixed size and (2) pad to the + minimum size that is divisible by some number. and (3)pad to square. Also, + pad to square and pad to the minimum size can be used as the same time. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - img_shape + - gt_masks + - gt_seg_map + + Added Keys: + + - pad_shape + - pad_fixed_size + - pad_size_divisor + + Args: + size (tuple, optional): Fixed padding size. + Expected padding shape (width, height). Defaults to None. + size_divisor (int, optional): The divisor of padded size. Defaults to + None. + pad_to_square (bool): Whether to pad the image into a square. + Currently only used for YOLOX. Defaults to False. + pad_val (Number | dict[str, Number], optional) - Padding value for if + the pad_mode is "constant". If it is a single number, the value + to pad the image is the number and to pad the semantic + segmentation map is 255. If it is a dict, it should have the + following keys: + + - img: The value to pad the image. + - seg: The value to pad the semantic segmentation map. + Defaults to dict(img=0, seg=255). + padding_mode (str): Type of padding. Should be: constant, edge, + reflect or symmetric. Defaults to 'constant'. + + - constant: pads with a constant value, this value is specified + with pad_val. + - edge: pads with the last value at the edge of the image. + - reflect: pads with reflection of image without repeating the last + value on the edge. For example, padding [1, 2, 3, 4] with 2 + elements on both sides in reflect mode will result in + [3, 2, 1, 2, 3, 4, 3, 2]. + - symmetric: pads with reflection of image repeating the last value + on the edge. For example, padding [1, 2, 3, 4] with 2 elements on + both sides in symmetric mode will result in + [2, 1, 1, 2, 3, 4, 4, 3] + """ + + def _pad_masks(self, results: dict) -> None: + """Pad masks according to ``results['pad_shape']``.""" + if results.get('gt_masks', None) is not None: + pad_val = self.pad_val.get('masks', 0) + pad_shape = results['pad_shape'][:2] + results['gt_masks'] = results['gt_masks'].pad( + pad_shape, pad_val=pad_val) + + def transform(self, results: dict) -> dict: + """Call function to pad images, masks, semantic segmentation maps. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Updated result dict. + """ + self._pad_img(results) + self._pad_seg(results) + self._pad_masks(results) + return results + + +@TRANSFORMS.register_module() +class RandomCrop(BaseTransform): + """Random crop the image & bboxes & masks. + + The absolute ``crop_size`` is sampled based on ``crop_type`` and + ``image_size``, then the cropped results are generated. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_ignore_flags (bool) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_masks (optional) + - gt_ignore_flags (optional) + - gt_seg_map (optional) + - gt_instances_ids (options, only used in MOT/VIS) + + Added Keys: + + - homography_matrix + + Args: + crop_size (tuple): The relative ratio or absolute pixels of + (width, height). + crop_type (str, optional): One of "relative_range", "relative", + "absolute", "absolute_range". "relative" randomly crops + (h * crop_size[0], w * crop_size[1]) part from an input of size + (h, w). "relative_range" uniformly samples relative crop size from + range [crop_size[0], 1] and [crop_size[1], 1] for height and width + respectively. "absolute" crops from an input with absolute size + (crop_size[0], crop_size[1]). "absolute_range" uniformly samples + crop_h in range [crop_size[0], min(h, crop_size[1])] and crop_w + in range [crop_size[0], min(w, crop_size[1])]. + Defaults to "absolute". + allow_negative_crop (bool, optional): Whether to allow a crop that does + not contain any bbox area. Defaults to False. + recompute_bbox (bool, optional): Whether to re-compute the boxes based + on cropped instance masks. Defaults to False. + bbox_clip_border (bool, optional): Whether clip the objects outside + the border of the image. Defaults to True. + + Note: + - If the image is smaller than the absolute crop size, return the + original image. + - The keys for bboxes, labels and masks must be aligned. That is, + ``gt_bboxes`` corresponds to ``gt_labels`` and ``gt_masks``, and + ``gt_bboxes_ignore`` corresponds to ``gt_labels_ignore`` and + ``gt_masks_ignore``. + - If the crop does not contain any gt-bbox region and + ``allow_negative_crop`` is set to False, skip this image. + """ + + def __init__(self, + crop_size: tuple, + crop_type: str = 'absolute', + allow_negative_crop: bool = False, + recompute_bbox: bool = False, + bbox_clip_border: bool = True) -> None: + if crop_type not in [ + 'relative_range', 'relative', 'absolute', 'absolute_range' + ]: + raise ValueError(f'Invalid crop_type {crop_type}.') + if crop_type in ['absolute', 'absolute_range']: + assert crop_size[0] > 0 and crop_size[1] > 0 + assert isinstance(crop_size[0], int) and isinstance( + crop_size[1], int) + if crop_type == 'absolute_range': + assert crop_size[0] <= crop_size[1] + else: + assert 0 < crop_size[0] <= 1 and 0 < crop_size[1] <= 1 + self.crop_size = crop_size + self.crop_type = crop_type + self.allow_negative_crop = allow_negative_crop + self.bbox_clip_border = bbox_clip_border + self.recompute_bbox = recompute_bbox + + def _crop_data(self, results: dict, crop_size: Tuple[int, int], + allow_negative_crop: bool) -> Union[dict, None]: + """Function to randomly crop images, bounding boxes, masks, semantic + segmentation maps. + + Args: + results (dict): Result dict from loading pipeline. + crop_size (Tuple[int, int]): Expected absolute size after + cropping, (h, w). + allow_negative_crop (bool): Whether to allow a crop that does not + contain any bbox area. + + Returns: + results (Union[dict, None]): Randomly cropped results, 'img_shape' + key in result dict is updated according to crop size. None will + be returned when there is no valid bbox after cropping. + """ + assert crop_size[0] > 0 and crop_size[1] > 0 + img = results['img'] + margin_h = max(img.shape[0] - crop_size[0], 0) + margin_w = max(img.shape[1] - crop_size[1], 0) + offset_h, offset_w = self._rand_offset((margin_h, margin_w)) + crop_y1, crop_y2 = offset_h, offset_h + crop_size[0] + crop_x1, crop_x2 = offset_w, offset_w + crop_size[1] + + # Record the homography matrix for the RandomCrop + homography_matrix = np.array( + [[1, 0, -offset_w], [0, 1, -offset_h], [0, 0, 1]], + dtype=np.float32) + if results.get('homography_matrix', None) is None: + results['homography_matrix'] = homography_matrix + else: + results['homography_matrix'] = homography_matrix @ results[ + 'homography_matrix'] + + # crop the image + img = img[crop_y1:crop_y2, crop_x1:crop_x2, ...] + img_shape = img.shape + results['img'] = img + results['img_shape'] = img_shape[:2] + + # crop bboxes accordingly and clip to the image boundary + if results.get('gt_bboxes', None) is not None: + bboxes = results['gt_bboxes'] + bboxes.translate_([-offset_w, -offset_h]) + if self.bbox_clip_border: + bboxes.clip_(img_shape[:2]) + valid_inds = bboxes.is_inside(img_shape[:2]).numpy() + # If the crop does not contain any gt-bbox area and + # allow_negative_crop is False, skip this image. + if (not valid_inds.any() and not allow_negative_crop): + return None + + results['gt_bboxes'] = bboxes[valid_inds] + + if results.get('gt_ignore_flags', None) is not None: + results['gt_ignore_flags'] = \ + results['gt_ignore_flags'][valid_inds] + + if results.get('gt_bboxes_labels', None) is not None: + results['gt_bboxes_labels'] = \ + results['gt_bboxes_labels'][valid_inds] + + if results.get('gt_masks', None) is not None: + results['gt_masks'] = results['gt_masks'][ + valid_inds.nonzero()[0]].crop( + np.asarray([crop_x1, crop_y1, crop_x2, crop_y2])) + if self.recompute_bbox: + results['gt_bboxes'] = results['gt_masks'].get_bboxes( + type(results['gt_bboxes'])) + + # We should remove the instance ids corresponding to invalid boxes. + if results.get('gt_instances_ids', None) is not None: + results['gt_instances_ids'] = \ + results['gt_instances_ids'][valid_inds] + + # crop semantic seg + if results.get('gt_seg_map', None) is not None: + results['gt_seg_map'] = results['gt_seg_map'][crop_y1:crop_y2, + crop_x1:crop_x2] + + return results + + @cache_randomness + def _rand_offset(self, margin: Tuple[int, int]) -> Tuple[int, int]: + """Randomly generate crop offset. + + Args: + margin (Tuple[int, int]): The upper bound for the offset generated + randomly. + + Returns: + Tuple[int, int]: The random offset for the crop. + """ + margin_h, margin_w = margin + offset_h = np.random.randint(0, margin_h + 1) + offset_w = np.random.randint(0, margin_w + 1) + + return offset_h, offset_w + + @cache_randomness + def _get_crop_size(self, image_size: Tuple[int, int]) -> Tuple[int, int]: + """Randomly generates the absolute crop size based on `crop_type` and + `image_size`. + + Args: + image_size (Tuple[int, int]): (h, w). + + Returns: + crop_size (Tuple[int, int]): (crop_h, crop_w) in absolute pixels. + """ + h, w = image_size + if self.crop_type == 'absolute': + return min(self.crop_size[1], h), min(self.crop_size[0], w) + elif self.crop_type == 'absolute_range': + crop_h = np.random.randint( + min(h, self.crop_size[0]), + min(h, self.crop_size[1]) + 1) + crop_w = np.random.randint( + min(w, self.crop_size[0]), + min(w, self.crop_size[1]) + 1) + return crop_h, crop_w + elif self.crop_type == 'relative': + crop_w, crop_h = self.crop_size + return int(h * crop_h + 0.5), int(w * crop_w + 0.5) + else: + # 'relative_range' + crop_size = np.asarray(self.crop_size, dtype=np.float32) + crop_h, crop_w = crop_size + np.random.rand(2) * (1 - crop_size) + return int(h * crop_h + 0.5), int(w * crop_w + 0.5) + + @autocast_box_type() + def transform(self, results: dict) -> Union[dict, None]: + """Transform function to randomly crop images, bounding boxes, masks, + semantic segmentation maps. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + results (Union[dict, None]): Randomly cropped results, 'img_shape' + key in result dict is updated according to crop size. None will + be returned when there is no valid bbox after cropping. + """ + image_size = results['img'].shape[:2] + crop_size = self._get_crop_size(image_size) + results = self._crop_data(results, crop_size, self.allow_negative_crop) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(crop_size={self.crop_size}, ' + repr_str += f'crop_type={self.crop_type}, ' + repr_str += f'allow_negative_crop={self.allow_negative_crop}, ' + repr_str += f'recompute_bbox={self.recompute_bbox}, ' + repr_str += f'bbox_clip_border={self.bbox_clip_border})' + return repr_str + + +@TRANSFORMS.register_module() +class SegRescale(BaseTransform): + """Rescale semantic segmentation maps. + + This transform rescale the ``gt_seg_map`` according to ``scale_factor``. + + Required Keys: + + - gt_seg_map + + Modified Keys: + + - gt_seg_map + + Args: + scale_factor (float): The scale factor of the final output. Defaults + to 1. + backend (str): Image rescale backend, choices are 'cv2' and 'pillow'. + These two backends generates slightly different results. Defaults + to 'cv2'. + """ + + def __init__(self, scale_factor: float = 1, backend: str = 'cv2') -> None: + self.scale_factor = scale_factor + self.backend = backend + + def transform(self, results: dict) -> dict: + """Transform function to scale the semantic segmentation map. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with semantic segmentation map scaled. + """ + if self.scale_factor != 1: + results['gt_seg_map'] = mmcv.imrescale( + results['gt_seg_map'], + self.scale_factor, + interpolation='nearest', + backend=self.backend) + + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(scale_factor={self.scale_factor}, ' + repr_str += f'backend={self.backend})' + return repr_str + + +@TRANSFORMS.register_module() +class PhotoMetricDistortion(BaseTransform): + """Apply photometric distortion to image sequentially, every transformation + is applied with a probability of 0.5. The position of random contrast is in + second or second to last. + + 1. random brightness + 2. random contrast (mode 0) + 3. convert color from BGR to HSV + 4. random saturation + 5. random hue + 6. convert color from HSV to BGR + 7. random contrast (mode 1) + 8. randomly swap channels + + Required Keys: + + - img (np.uint8) + + Modified Keys: + + - img (np.float32) + + Args: + brightness_delta (int): delta of brightness. + contrast_range (sequence): range of contrast. + saturation_range (sequence): range of saturation. + hue_delta (int): delta of hue. + """ + + def __init__(self, + brightness_delta: int = 32, + contrast_range: Sequence[Number] = (0.5, 1.5), + saturation_range: Sequence[Number] = (0.5, 1.5), + hue_delta: int = 18) -> None: + self.brightness_delta = brightness_delta + self.contrast_lower, self.contrast_upper = contrast_range + self.saturation_lower, self.saturation_upper = saturation_range + self.hue_delta = hue_delta + + @cache_randomness + def _random_flags(self) -> Sequence[Number]: + mode = random.randint(2) + brightness_flag = random.randint(2) + contrast_flag = random.randint(2) + saturation_flag = random.randint(2) + hue_flag = random.randint(2) + swap_flag = random.randint(2) + delta_value = random.uniform(-self.brightness_delta, + self.brightness_delta) + alpha_value = random.uniform(self.contrast_lower, self.contrast_upper) + saturation_value = random.uniform(self.saturation_lower, + self.saturation_upper) + hue_value = random.uniform(-self.hue_delta, self.hue_delta) + swap_value = random.permutation(3) + + return (mode, brightness_flag, contrast_flag, saturation_flag, + hue_flag, swap_flag, delta_value, alpha_value, + saturation_value, hue_value, swap_value) + + def transform(self, results: dict) -> dict: + """Transform function to perform photometric distortion on images. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with images distorted. + """ + assert 'img' in results, '`img` is not found in results' + img = results['img'] + img = img.astype(np.float32) + + (mode, brightness_flag, contrast_flag, saturation_flag, hue_flag, + swap_flag, delta_value, alpha_value, saturation_value, hue_value, + swap_value) = self._random_flags() + + # random brightness + if brightness_flag: + img += delta_value + + # mode == 0 --> do random contrast first + # mode == 1 --> do random contrast last + if mode == 1: + if contrast_flag: + img *= alpha_value + + # convert color from BGR to HSV + img = mmcv.bgr2hsv(img) + + # random saturation + if saturation_flag: + img[..., 1] *= saturation_value + # For image(type=float32), after convert bgr to hsv by opencv, + # valid saturation value range is [0, 1] + if saturation_value > 1: + img[..., 1] = img[..., 1].clip(0, 1) + + # random hue + if hue_flag: + img[..., 0] += hue_value + img[..., 0][img[..., 0] > 360] -= 360 + img[..., 0][img[..., 0] < 0] += 360 + + # convert color from HSV to BGR + img = mmcv.hsv2bgr(img) + + # random contrast + if mode == 0: + if contrast_flag: + img *= alpha_value + + # randomly swap channels + if swap_flag: + img = img[..., swap_value] + + results['img'] = img + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(brightness_delta={self.brightness_delta}, ' + repr_str += 'contrast_range=' + repr_str += f'{(self.contrast_lower, self.contrast_upper)}, ' + repr_str += 'saturation_range=' + repr_str += f'{(self.saturation_lower, self.saturation_upper)}, ' + repr_str += f'hue_delta={self.hue_delta})' + return repr_str + + +@TRANSFORMS.register_module() +class Expand(BaseTransform): + """Random expand the image & bboxes & masks & segmentation map. + + Randomly place the original image on a canvas of ``ratio`` x original image + size filled with mean values. The ratio is in the range of ratio_range. + + Required Keys: + + - img + - img_shape + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes + - gt_masks + - gt_seg_map + + + Args: + mean (sequence): mean value of dataset. + to_rgb (bool): if need to convert the order of mean to align with RGB. + ratio_range (sequence)): range of expand ratio. + seg_ignore_label (int): label of ignore segmentation map. + prob (float): probability of applying this transformation + """ + + def __init__(self, + mean: Sequence[Number] = (0, 0, 0), + to_rgb: bool = True, + ratio_range: Sequence[Number] = (1, 4), + seg_ignore_label: int = None, + prob: float = 0.5) -> None: + self.to_rgb = to_rgb + self.ratio_range = ratio_range + if to_rgb: + self.mean = mean[::-1] + else: + self.mean = mean + self.min_ratio, self.max_ratio = ratio_range + self.seg_ignore_label = seg_ignore_label + self.prob = prob + + @cache_randomness + def _random_prob(self) -> float: + return random.uniform(0, 1) + + @cache_randomness + def _random_ratio(self) -> float: + return random.uniform(self.min_ratio, self.max_ratio) + + @cache_randomness + def _random_left_top(self, ratio: float, h: int, + w: int) -> Tuple[int, int]: + left = int(random.uniform(0, w * ratio - w)) + top = int(random.uniform(0, h * ratio - h)) + return left, top + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Transform function to expand images, bounding boxes, masks, + segmentation map. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with images, bounding boxes, masks, segmentation + map expanded. + """ + if self._random_prob() > self.prob: + return results + assert 'img' in results, '`img` is not found in results' + img = results['img'] + h, w, c = img.shape + ratio = self._random_ratio() + # speedup expand when meets large image + if np.all(self.mean == self.mean[0]): + expand_img = np.empty((int(h * ratio), int(w * ratio), c), + img.dtype) + expand_img.fill(self.mean[0]) + else: + expand_img = np.full((int(h * ratio), int(w * ratio), c), + self.mean, + dtype=img.dtype) + left, top = self._random_left_top(ratio, h, w) + expand_img[top:top + h, left:left + w] = img + results['img'] = expand_img + results['img_shape'] = expand_img.shape[:2] + + # expand bboxes + if results.get('gt_bboxes', None) is not None: + results['gt_bboxes'].translate_([left, top]) + + # expand masks + if results.get('gt_masks', None) is not None: + results['gt_masks'] = results['gt_masks'].expand( + int(h * ratio), int(w * ratio), top, left) + + # expand segmentation map + if results.get('gt_seg_map', None) is not None: + gt_seg = results['gt_seg_map'] + expand_gt_seg = np.full((int(h * ratio), int(w * ratio)), + self.seg_ignore_label, + dtype=gt_seg.dtype) + expand_gt_seg[top:top + h, left:left + w] = gt_seg + results['gt_seg_map'] = expand_gt_seg + + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(mean={self.mean}, to_rgb={self.to_rgb}, ' + repr_str += f'ratio_range={self.ratio_range}, ' + repr_str += f'seg_ignore_label={self.seg_ignore_label}, ' + repr_str += f'prob={self.prob})' + return repr_str + + +@TRANSFORMS.register_module() +class MinIoURandomCrop(BaseTransform): + """Random crop the image & bboxes & masks & segmentation map, the cropped + patches have minimum IoU requirement with original image & bboxes & masks. + + & segmentation map, the IoU threshold is randomly selected from min_ious. + + + Required Keys: + + - img + - img_shape + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - gt_ignore_flags (bool) (optional) + - gt_seg_map (np.uint8) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes + - gt_bboxes_labels + - gt_masks + - gt_ignore_flags + - gt_seg_map + + + Args: + min_ious (Sequence[float]): minimum IoU threshold for all intersections + with bounding boxes. + min_crop_size (float): minimum crop's size (i.e. h,w := a*h, a*w, + where a >= min_crop_size). + bbox_clip_border (bool, optional): Whether clip the objects outside + the border of the image. Defaults to True. + """ + + def __init__(self, + min_ious: Sequence[float] = (0.1, 0.3, 0.5, 0.7, 0.9), + min_crop_size: float = 0.3, + bbox_clip_border: bool = True) -> None: + + self.min_ious = min_ious + self.sample_mode = (1, *min_ious, 0) + self.min_crop_size = min_crop_size + self.bbox_clip_border = bbox_clip_border + + @cache_randomness + def _random_mode(self) -> Number: + return random.choice(self.sample_mode) + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Transform function to crop images and bounding boxes with minimum + IoU constraint. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with images and bounding boxes cropped, \ + 'img_shape' key is updated. + """ + assert 'img' in results, '`img` is not found in results' + assert 'gt_bboxes' in results, '`gt_bboxes` is not found in results' + img = results['img'] + boxes = results['gt_bboxes'] + h, w, c = img.shape + while True: + mode = self._random_mode() + self.mode = mode + if mode == 1: + return results + + min_iou = self.mode + for i in range(50): + new_w = random.uniform(self.min_crop_size * w, w) + new_h = random.uniform(self.min_crop_size * h, h) + + # h / w in [0.5, 2] + if new_h / new_w < 0.5 or new_h / new_w > 2: + continue + + left = random.uniform(w - new_w) + top = random.uniform(h - new_h) + + patch = np.array( + (int(left), int(top), int(left + new_w), int(top + new_h))) + # Line or point crop is not allowed + if patch[2] == patch[0] or patch[3] == patch[1]: + continue + overlaps = boxes.overlaps( + HorizontalBoxes(patch.reshape(-1, 4).astype(np.float32)), + boxes).numpy().reshape(-1) + if len(overlaps) > 0 and overlaps.min() < min_iou: + continue + + # center of boxes should inside the crop img + # only adjust boxes and instance masks when the gt is not empty + if len(overlaps) > 0: + # adjust boxes + def is_center_of_bboxes_in_patch(boxes, patch): + centers = boxes.centers.numpy() + mask = ((centers[:, 0] > patch[0]) * + (centers[:, 1] > patch[1]) * + (centers[:, 0] < patch[2]) * + (centers[:, 1] < patch[3])) + return mask + + mask = is_center_of_bboxes_in_patch(boxes, patch) + if not mask.any(): + continue + if results.get('gt_bboxes', None) is not None: + boxes = results['gt_bboxes'] + mask = is_center_of_bboxes_in_patch(boxes, patch) + boxes = boxes[mask] + boxes.translate_([-patch[0], -patch[1]]) + if self.bbox_clip_border: + boxes.clip_( + [patch[3] - patch[1], patch[2] - patch[0]]) + results['gt_bboxes'] = boxes + + # ignore_flags + if results.get('gt_ignore_flags', None) is not None: + results['gt_ignore_flags'] = \ + results['gt_ignore_flags'][mask] + + # labels + if results.get('gt_bboxes_labels', None) is not None: + results['gt_bboxes_labels'] = results[ + 'gt_bboxes_labels'][mask] + + # mask fields + if results.get('gt_masks', None) is not None: + results['gt_masks'] = results['gt_masks'][ + mask.nonzero()[0]].crop(patch) + # adjust the img no matter whether the gt is empty before crop + img = img[patch[1]:patch[3], patch[0]:patch[2]] + results['img'] = img + results['img_shape'] = img.shape[:2] + + # seg fields + if results.get('gt_seg_map', None) is not None: + results['gt_seg_map'] = results['gt_seg_map'][ + patch[1]:patch[3], patch[0]:patch[2]] + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(min_ious={self.min_ious}, ' + repr_str += f'min_crop_size={self.min_crop_size}, ' + repr_str += f'bbox_clip_border={self.bbox_clip_border})' + return repr_str + + +@TRANSFORMS.register_module() +class Corrupt(BaseTransform): + """Corruption augmentation. + + Corruption transforms implemented based on + `imagecorruptions `_. + + Required Keys: + + - img (np.uint8) + + + Modified Keys: + + - img (np.uint8) + + + Args: + corruption (str): Corruption name. + severity (int): The severity of corruption. Defaults to 1. + """ + + def __init__(self, corruption: str, severity: int = 1) -> None: + self.corruption = corruption + self.severity = severity + + def transform(self, results: dict) -> dict: + """Call function to corrupt image. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with images corrupted. + """ + + if corrupt is None: + raise RuntimeError('imagecorruptions is not installed') + results['img'] = corrupt( + results['img'].astype(np.uint8), + corruption_name=self.corruption, + severity=self.severity) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(corruption={self.corruption}, ' + repr_str += f'severity={self.severity})' + return repr_str + + +@TRANSFORMS.register_module() +@avoid_cache_randomness +class Albu(BaseTransform): + """Albumentation augmentation. + + Adds custom transformations from Albumentations library. + Please, visit `https://albumentations.readthedocs.io` + to get more information. + + Required Keys: + + - img (np.uint8) + - gt_bboxes (HorizontalBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + + Modified Keys: + + - img (np.uint8) + - gt_bboxes (HorizontalBoxes[torch.float32]) (optional) + - gt_masks (BitmapMasks | PolygonMasks) (optional) + - img_shape (tuple) + + An example of ``transforms`` is as followed: + + .. code-block:: + + [ + dict( + type='ShiftScaleRotate', + shift_limit=0.0625, + scale_limit=0.0, + rotate_limit=0, + interpolation=1, + p=0.5), + dict( + type='RandomBrightnessContrast', + brightness_limit=[0.1, 0.3], + contrast_limit=[0.1, 0.3], + p=0.2), + dict(type='ChannelShuffle', p=0.1), + dict( + type='OneOf', + transforms=[ + dict(type='Blur', blur_limit=3, p=1.0), + dict(type='MedianBlur', blur_limit=3, p=1.0) + ], + p=0.1), + ] + + Args: + transforms (list[dict]): A list of albu transformations + bbox_params (dict, optional): Bbox_params for albumentation `Compose` + keymap (dict, optional): Contains + {'input key':'albumentation-style key'} + skip_img_without_anno (bool): Whether to skip the image if no ann left + after aug. Defaults to False. + """ + + def __init__(self, + transforms: List[dict], + bbox_params: Optional[dict] = None, + keymap: Optional[dict] = None, + skip_img_without_anno: bool = False) -> None: + if Compose is None: + raise RuntimeError('albumentations is not installed') + + # Args will be modified later, copying it will be safer + transforms = copy.deepcopy(transforms) + if bbox_params is not None: + bbox_params = copy.deepcopy(bbox_params) + if keymap is not None: + keymap = copy.deepcopy(keymap) + self.transforms = transforms + self.filter_lost_elements = False + self.skip_img_without_anno = skip_img_without_anno + + # A simple workaround to remove masks without boxes + if (isinstance(bbox_params, dict) and 'label_fields' in bbox_params + and 'filter_lost_elements' in bbox_params): + self.filter_lost_elements = True + self.origin_label_fields = bbox_params['label_fields'] + bbox_params['label_fields'] = ['idx_mapper'] + del bbox_params['filter_lost_elements'] + + self.bbox_params = ( + self.albu_builder(bbox_params) if bbox_params else None) + self.aug = Compose([self.albu_builder(t) for t in self.transforms], + bbox_params=self.bbox_params) + + if not keymap: + self.keymap_to_albu = { + 'img': 'image', + 'gt_masks': 'masks', + 'gt_bboxes': 'bboxes' + } + else: + self.keymap_to_albu = keymap + self.keymap_back = {v: k for k, v in self.keymap_to_albu.items()} + + def albu_builder(self, cfg: dict) -> albumentations: + """Import a module from albumentations. + + It inherits some of :func:`build_from_cfg` logic. + + Args: + cfg (dict): Config dict. It should at least contain the key "type". + + Returns: + obj: The constructed object. + """ + + assert isinstance(cfg, dict) and 'type' in cfg + args = cfg.copy() + obj_type = args.pop('type') + if is_str(obj_type): + if albumentations is None: + raise RuntimeError('albumentations is not installed') + obj_cls = getattr(albumentations, obj_type) + elif inspect.isclass(obj_type): + obj_cls = obj_type + else: + raise TypeError( + f'type must be a str or valid type, but got {type(obj_type)}') + + if 'transforms' in args: + args['transforms'] = [ + self.albu_builder(transform) + for transform in args['transforms'] + ] + + return obj_cls(**args) + + @staticmethod + def mapper(d: dict, keymap: dict) -> dict: + """Dictionary mapper. Renames keys according to keymap provided. + + Args: + d (dict): old dict + keymap (dict): {'old_key':'new_key'} + Returns: + dict: new dict. + """ + updated_dict = {} + for k, v in zip(d.keys(), d.values()): + new_k = keymap.get(k, k) + updated_dict[new_k] = d[k] + return updated_dict + + @autocast_box_type() + def transform(self, results: dict) -> Union[dict, None]: + """Transform function of Albu.""" + # TODO: gt_seg_map is not currently supported + # dict to albumentations format + results = self.mapper(results, self.keymap_to_albu) + results, ori_masks = self._preprocess_results(results) + results = self.aug(**results) + results = self._postprocess_results(results, ori_masks) + if results is None: + return None + # back to the original format + results = self.mapper(results, self.keymap_back) + results['img_shape'] = results['img'].shape[:2] + return results + + def _preprocess_results(self, results: dict) -> tuple: + """Pre-processing results to facilitate the use of Albu.""" + if 'bboxes' in results: + # to list of boxes + if not isinstance(results['bboxes'], HorizontalBoxes): + raise NotImplementedError( + 'Albu only supports horizontal boxes now') + bboxes = results['bboxes'].numpy() + results['bboxes'] = [x for x in bboxes] + # add pseudo-field for filtration + if self.filter_lost_elements: + results['idx_mapper'] = np.arange(len(results['bboxes'])) + + # TODO: Support mask structure in albu + ori_masks = None + if 'masks' in results: + if isinstance(results['masks'], PolygonMasks): + raise NotImplementedError( + 'Albu only supports BitMap masks now') + ori_masks = results['masks'] + if albumentations.__version__ < '0.5': + results['masks'] = results['masks'].masks + else: + results['masks'] = [mask for mask in results['masks'].masks] + + return results, ori_masks + + def _postprocess_results( + self, + results: dict, + ori_masks: Optional[Union[BitmapMasks, + PolygonMasks]] = None) -> dict: + """Post-processing Albu output.""" + # albumentations may return np.array or list on different versions + if 'gt_bboxes_labels' in results and isinstance( + results['gt_bboxes_labels'], list): + results['gt_bboxes_labels'] = np.array( + results['gt_bboxes_labels'], dtype=np.int64) + if 'gt_ignore_flags' in results and isinstance( + results['gt_ignore_flags'], list): + results['gt_ignore_flags'] = np.array( + results['gt_ignore_flags'], dtype=bool) + + if 'bboxes' in results: + if isinstance(results['bboxes'], list): + results['bboxes'] = np.array( + results['bboxes'], dtype=np.float32) + results['bboxes'] = results['bboxes'].reshape(-1, 4) + results['bboxes'] = HorizontalBoxes(results['bboxes']) + + # filter label_fields + if self.filter_lost_elements: + + for label in self.origin_label_fields: + results[label] = np.array( + [results[label][i] for i in results['idx_mapper']]) + if 'masks' in results: + assert ori_masks is not None + results['masks'] = np.array( + [results['masks'][i] for i in results['idx_mapper']]) + results['masks'] = ori_masks.__class__( + results['masks'], + results['masks'][0].shape[0], + results['masks'][0].shape[1], + ) + if (not len(results['idx_mapper']) + and self.skip_img_without_anno): + return None + elif 'masks' in results: + results['masks'] = ori_masks.__class__(results['masks'], + ori_masks.height, + ori_masks.width) + + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + f'(transforms={self.transforms})' + return repr_str + + +@TRANSFORMS.register_module() +@avoid_cache_randomness +class RandomCenterCropPad(BaseTransform): + """Random center crop and random around padding for CornerNet. + + This operation generates randomly cropped image from the original image and + pads it simultaneously. Different from :class:`RandomCrop`, the output + shape may not equal to ``crop_size`` strictly. We choose a random value + from ``ratios`` and the output shape could be larger or smaller than + ``crop_size``. The padding operation is also different from :class:`Pad`, + here we use around padding instead of right-bottom padding. + + The relation between output image (padding image) and original image: + + .. code:: text + + output image + + +----------------------------+ + | padded area | + +------|----------------------------|----------+ + | | cropped area | | + | | +---------------+ | | + | | | . center | | | original image + | | | range | | | + | | +---------------+ | | + +------|----------------------------|----------+ + | padded area | + +----------------------------+ + + There are 5 main areas in the figure: + + - output image: output image of this operation, also called padding + image in following instruction. + - original image: input image of this operation. + - padded area: non-intersect area of output image and original image. + - cropped area: the overlap of output image and original image. + - center range: a smaller area where random center chosen from. + center range is computed by ``border`` and original image's shape + to avoid our random center is too close to original image's border. + + Also this operation act differently in train and test mode, the summary + pipeline is listed below. + + Train pipeline: + + 1. Choose a ``random_ratio`` from ``ratios``, the shape of padding image + will be ``random_ratio * crop_size``. + 2. Choose a ``random_center`` in center range. + 3. Generate padding image with center matches the ``random_center``. + 4. Initialize the padding image with pixel value equals to ``mean``. + 5. Copy the cropped area to padding image. + 6. Refine annotations. + + Test pipeline: + + 1. Compute output shape according to ``test_pad_mode``. + 2. Generate padding image with center matches the original image + center. + 3. Initialize the padding image with pixel value equals to ``mean``. + 4. Copy the ``cropped area`` to padding image. + + Required Keys: + + - img (np.float32) + - img_shape (tuple) + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + + Modified Keys: + + - img (np.float32) + - img_shape (tuple) + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + + Args: + crop_size (tuple, optional): expected size after crop, final size will + computed according to ratio. Requires (width, height) + in train mode, and None in test mode. + ratios (tuple, optional): random select a ratio from tuple and crop + image to (crop_size[0] * ratio) * (crop_size[1] * ratio). + Only available in train mode. Defaults to (0.9, 1.0, 1.1). + border (int, optional): max distance from center select area to image + border. Only available in train mode. Defaults to 128. + mean (sequence, optional): Mean values of 3 channels. + std (sequence, optional): Std values of 3 channels. + to_rgb (bool, optional): Whether to convert the image from BGR to RGB. + test_mode (bool): whether involve random variables in transform. + In train mode, crop_size is fixed, center coords and ratio is + random selected from predefined lists. In test mode, crop_size + is image's original shape, center coords and ratio is fixed. + Defaults to False. + test_pad_mode (tuple, optional): padding method and padding shape + value, only available in test mode. Default is using + 'logical_or' with 127 as padding shape value. + + - 'logical_or': final_shape = input_shape | padding_shape_value + - 'size_divisor': final_shape = int( + ceil(input_shape / padding_shape_value) * padding_shape_value) + + Defaults to ('logical_or', 127). + test_pad_add_pix (int): Extra padding pixel in test mode. + Defaults to 0. + bbox_clip_border (bool): Whether clip the objects outside + the border of the image. Defaults to True. + """ + + def __init__(self, + crop_size: Optional[tuple] = None, + ratios: Optional[tuple] = (0.9, 1.0, 1.1), + border: Optional[int] = 128, + mean: Optional[Sequence] = None, + std: Optional[Sequence] = None, + to_rgb: Optional[bool] = None, + test_mode: bool = False, + test_pad_mode: Optional[tuple] = ('logical_or', 127), + test_pad_add_pix: int = 0, + bbox_clip_border: bool = True) -> None: + if test_mode: + assert crop_size is None, 'crop_size must be None in test mode' + assert ratios is None, 'ratios must be None in test mode' + assert border is None, 'border must be None in test mode' + assert isinstance(test_pad_mode, (list, tuple)) + assert test_pad_mode[0] in ['logical_or', 'size_divisor'] + else: + assert isinstance(crop_size, (list, tuple)) + assert crop_size[0] > 0 and crop_size[1] > 0, ( + 'crop_size must > 0 in train mode') + assert isinstance(ratios, (list, tuple)) + assert test_pad_mode is None, ( + 'test_pad_mode must be None in train mode') + + self.crop_size = crop_size + self.ratios = ratios + self.border = border + # We do not set default value to mean, std and to_rgb because these + # hyper-parameters are easy to forget but could affect the performance. + # Please use the same setting as Normalize for performance assurance. + assert mean is not None and std is not None and to_rgb is not None + self.to_rgb = to_rgb + self.input_mean = mean + self.input_std = std + if to_rgb: + self.mean = mean[::-1] + self.std = std[::-1] + else: + self.mean = mean + self.std = std + self.test_mode = test_mode + self.test_pad_mode = test_pad_mode + self.test_pad_add_pix = test_pad_add_pix + self.bbox_clip_border = bbox_clip_border + + def _get_border(self, border, size): + """Get final border for the target size. + + This function generates a ``final_border`` according to image's shape. + The area between ``final_border`` and ``size - final_border`` is the + ``center range``. We randomly choose center from the ``center range`` + to avoid our random center is too close to original image's border. + Also ``center range`` should be larger than 0. + + Args: + border (int): The initial border, default is 128. + size (int): The width or height of original image. + Returns: + int: The final border. + """ + k = 2 * border / size + i = pow(2, np.ceil(np.log2(np.ceil(k))) + (k == int(k))) + return border // i + + def _filter_boxes(self, patch, boxes): + """Check whether the center of each box is in the patch. + + Args: + patch (list[int]): The cropped area, [left, top, right, bottom]. + boxes (numpy array, (N x 4)): Ground truth boxes. + + Returns: + mask (numpy array, (N,)): Each box is inside or outside the patch. + """ + center = boxes.centers.numpy() + mask = (center[:, 0] > patch[0]) * (center[:, 1] > patch[1]) * ( + center[:, 0] < patch[2]) * ( + center[:, 1] < patch[3]) + return mask + + def _crop_image_and_paste(self, image, center, size): + """Crop image with a given center and size, then paste the cropped + image to a blank image with two centers align. + + This function is equivalent to generating a blank image with ``size`` + as its shape. Then cover it on the original image with two centers ( + the center of blank image and the random center of original image) + aligned. The overlap area is paste from the original image and the + outside area is filled with ``mean pixel``. + + Args: + image (np array, H x W x C): Original image. + center (list[int]): Target crop center coord. + size (list[int]): Target crop size. [target_h, target_w] + + Returns: + cropped_img (np array, target_h x target_w x C): Cropped image. + border (np array, 4): The distance of four border of + ``cropped_img`` to the original image area, [top, bottom, + left, right] + patch (list[int]): The cropped area, [left, top, right, bottom]. + """ + center_y, center_x = center + target_h, target_w = size + img_h, img_w, img_c = image.shape + + x0 = max(0, center_x - target_w // 2) + x1 = min(center_x + target_w // 2, img_w) + y0 = max(0, center_y - target_h // 2) + y1 = min(center_y + target_h // 2, img_h) + patch = np.array((int(x0), int(y0), int(x1), int(y1))) + + left, right = center_x - x0, x1 - center_x + top, bottom = center_y - y0, y1 - center_y + + cropped_center_y, cropped_center_x = target_h // 2, target_w // 2 + cropped_img = np.zeros((target_h, target_w, img_c), dtype=image.dtype) + for i in range(img_c): + cropped_img[:, :, i] += self.mean[i] + y_slice = slice(cropped_center_y - top, cropped_center_y + bottom) + x_slice = slice(cropped_center_x - left, cropped_center_x + right) + cropped_img[y_slice, x_slice, :] = image[y0:y1, x0:x1, :] + + border = np.array([ + cropped_center_y - top, cropped_center_y + bottom, + cropped_center_x - left, cropped_center_x + right + ], + dtype=np.float32) + + return cropped_img, border, patch + + def _train_aug(self, results): + """Random crop and around padding the original image. + + Args: + results (dict): Image infomations in the augment pipeline. + + Returns: + results (dict): The updated dict. + """ + img = results['img'] + h, w, c = img.shape + gt_bboxes = results['gt_bboxes'] + while True: + scale = random.choice(self.ratios) + new_h = int(self.crop_size[1] * scale) + new_w = int(self.crop_size[0] * scale) + h_border = self._get_border(self.border, h) + w_border = self._get_border(self.border, w) + + for i in range(50): + center_x = random.randint(low=w_border, high=w - w_border) + center_y = random.randint(low=h_border, high=h - h_border) + + cropped_img, border, patch = self._crop_image_and_paste( + img, [center_y, center_x], [new_h, new_w]) + + if len(gt_bboxes) == 0: + results['img'] = cropped_img + results['img_shape'] = cropped_img.shape[:2] + return results + + # if image do not have valid bbox, any crop patch is valid. + mask = self._filter_boxes(patch, gt_bboxes) + if not mask.any(): + continue + + results['img'] = cropped_img + results['img_shape'] = cropped_img.shape[:2] + + x0, y0, x1, y1 = patch + + left_w, top_h = center_x - x0, center_y - y0 + cropped_center_x, cropped_center_y = new_w // 2, new_h // 2 + + # crop bboxes accordingly and clip to the image boundary + gt_bboxes = gt_bboxes[mask] + gt_bboxes.translate_([ + cropped_center_x - left_w - x0, + cropped_center_y - top_h - y0 + ]) + if self.bbox_clip_border: + gt_bboxes.clip_([new_h, new_w]) + keep = gt_bboxes.is_inside([new_h, new_w]).numpy() + gt_bboxes = gt_bboxes[keep] + + results['gt_bboxes'] = gt_bboxes + + # ignore_flags + if results.get('gt_ignore_flags', None) is not None: + gt_ignore_flags = results['gt_ignore_flags'][mask] + results['gt_ignore_flags'] = \ + gt_ignore_flags[keep] + + # labels + if results.get('gt_bboxes_labels', None) is not None: + gt_labels = results['gt_bboxes_labels'][mask] + results['gt_bboxes_labels'] = gt_labels[keep] + + if 'gt_masks' in results or 'gt_seg_map' in results: + raise NotImplementedError( + 'RandomCenterCropPad only supports bbox.') + + return results + + def _test_aug(self, results): + """Around padding the original image without cropping. + + The padding mode and value are from ``test_pad_mode``. + + Args: + results (dict): Image infomations in the augment pipeline. + + Returns: + results (dict): The updated dict. + """ + img = results['img'] + h, w, c = img.shape + if self.test_pad_mode[0] in ['logical_or']: + # self.test_pad_add_pix is only used for centernet + target_h = (h | self.test_pad_mode[1]) + self.test_pad_add_pix + target_w = (w | self.test_pad_mode[1]) + self.test_pad_add_pix + elif self.test_pad_mode[0] in ['size_divisor']: + divisor = self.test_pad_mode[1] + target_h = int(np.ceil(h / divisor)) * divisor + target_w = int(np.ceil(w / divisor)) * divisor + else: + raise NotImplementedError( + 'RandomCenterCropPad only support two testing pad mode:' + 'logical-or and size_divisor.') + + cropped_img, border, _ = self._crop_image_and_paste( + img, [h // 2, w // 2], [target_h, target_w]) + results['img'] = cropped_img + results['img_shape'] = cropped_img.shape[:2] + results['border'] = border + return results + + @autocast_box_type() + def transform(self, results: dict) -> dict: + img = results['img'] + assert img.dtype == np.float32, ( + 'RandomCenterCropPad needs the input image of dtype np.float32,' + ' please set "to_float32=True" in "LoadImageFromFile" pipeline') + h, w, c = img.shape + assert c == len(self.mean) + if self.test_mode: + return self._test_aug(results) + else: + return self._train_aug(results) + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(crop_size={self.crop_size}, ' + repr_str += f'ratios={self.ratios}, ' + repr_str += f'border={self.border}, ' + repr_str += f'mean={self.input_mean}, ' + repr_str += f'std={self.input_std}, ' + repr_str += f'to_rgb={self.to_rgb}, ' + repr_str += f'test_mode={self.test_mode}, ' + repr_str += f'test_pad_mode={self.test_pad_mode}, ' + repr_str += f'bbox_clip_border={self.bbox_clip_border})' + return repr_str + + +@TRANSFORMS.register_module() +class CutOut(BaseTransform): + """CutOut operation. + + Randomly drop some regions of image used in + `Cutout `_. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + n_holes (int or tuple[int, int]): Number of regions to be dropped. + If it is given as a list, number of holes will be randomly + selected from the closed interval [``n_holes[0]``, ``n_holes[1]``]. + cutout_shape (tuple[int, int] or list[tuple[int, int]], optional): + The candidate shape of dropped regions. It can be + ``tuple[int, int]`` to use a fixed cutout shape, or + ``list[tuple[int, int]]`` to randomly choose shape + from the list. Defaults to None. + cutout_ratio (tuple[float, float] or list[tuple[float, float]], + optional): The candidate ratio of dropped regions. It can be + ``tuple[float, float]`` to use a fixed ratio or + ``list[tuple[float, float]]`` to randomly choose ratio + from the list. Please note that ``cutout_shape`` and + ``cutout_ratio`` cannot be both given at the same time. + Defaults to None. + fill_in (tuple[float, float, float] or tuple[int, int, int]): The value + of pixel to fill in the dropped regions. Defaults to (0, 0, 0). + """ + + def __init__( + self, + n_holes: Union[int, Tuple[int, int]], + cutout_shape: Optional[Union[Tuple[int, int], + List[Tuple[int, int]]]] = None, + cutout_ratio: Optional[Union[Tuple[float, float], + List[Tuple[float, float]]]] = None, + fill_in: Union[Tuple[float, float, float], Tuple[int, int, + int]] = (0, 0, 0) + ) -> None: + + assert (cutout_shape is None) ^ (cutout_ratio is None), \ + 'Either cutout_shape or cutout_ratio should be specified.' + assert (isinstance(cutout_shape, (list, tuple)) + or isinstance(cutout_ratio, (list, tuple))) + if isinstance(n_holes, tuple): + assert len(n_holes) == 2 and 0 <= n_holes[0] < n_holes[1] + else: + n_holes = (n_holes, n_holes) + self.n_holes = n_holes + self.fill_in = fill_in + self.with_ratio = cutout_ratio is not None + self.candidates = cutout_ratio if self.with_ratio else cutout_shape + if not isinstance(self.candidates, list): + self.candidates = [self.candidates] + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Call function to drop some regions of image.""" + h, w, c = results['img'].shape + n_holes = np.random.randint(self.n_holes[0], self.n_holes[1] + 1) + for _ in range(n_holes): + x1 = np.random.randint(0, w) + y1 = np.random.randint(0, h) + index = np.random.randint(0, len(self.candidates)) + if not self.with_ratio: + cutout_w, cutout_h = self.candidates[index] + else: + cutout_w = int(self.candidates[index][0] * w) + cutout_h = int(self.candidates[index][1] * h) + + x2 = np.clip(x1 + cutout_w, 0, w) + y2 = np.clip(y1 + cutout_h, 0, h) + results['img'][y1:y2, x1:x2, :] = self.fill_in + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(n_holes={self.n_holes}, ' + repr_str += (f'cutout_ratio={self.candidates}, ' if self.with_ratio + else f'cutout_shape={self.candidates}, ') + repr_str += f'fill_in={self.fill_in})' + return repr_str + + +@TRANSFORMS.register_module() +class Mosaic(BaseTransform): + """Mosaic augmentation. + + Given 4 images, mosaic transform combines them into + one output image. The output image is composed of the parts from each sub- + image. + + .. code:: text + + mosaic transform + center_x + +------------------------------+ + | pad | pad | + | +-----------+ | + | | | | + | | image1 |--------+ | + | | | | | + | | | image2 | | + center_y |----+-------------+-----------| + | | cropped | | + |pad | image3 | image4 | + | | | | + +----|-------------+-----------+ + | | + +-------------+ + + The mosaic transform steps are as follows: + + 1. Choose the mosaic center as the intersections of 4 images + 2. Get the left top image according to the index, and randomly + sample another 3 images from the custom dataset. + 3. Sub image will be cropped if image is larger than mosaic patch + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + - mix_results (List[dict]) + + Modified Keys: + + - img + - img_shape + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_ignore_flags (optional) + + Args: + img_scale (Sequence[int]): Image size before mosaic pipeline of single + image. The shape order should be (width, height). + Defaults to (640, 640). + center_ratio_range (Sequence[float]): Center ratio range of mosaic + output. Defaults to (0.5, 1.5). + bbox_clip_border (bool, optional): Whether to clip the objects outside + the border of the image. In some dataset like MOT17, the gt bboxes + are allowed to cross the border of images. Therefore, we don't + need to clip the gt bboxes in these cases. Defaults to True. + pad_val (int): Pad value. Defaults to 114. + prob (float): Probability of applying this transformation. + Defaults to 1.0. + """ + + def __init__(self, + img_scale: Tuple[int, int] = (640, 640), + center_ratio_range: Tuple[float, float] = (0.5, 1.5), + bbox_clip_border: bool = True, + pad_val: float = 114.0, + prob: float = 1.0) -> None: + assert isinstance(img_scale, tuple) + assert 0 <= prob <= 1.0, 'The probability should be in range [0,1]. ' \ + f'got {prob}.' + + log_img_scale(img_scale, skip_square=True, shape_order='wh') + self.img_scale = img_scale + self.center_ratio_range = center_ratio_range + self.bbox_clip_border = bbox_clip_border + self.pad_val = pad_val + self.prob = prob + + @cache_randomness + def get_indexes(self, dataset: BaseDataset) -> int: + """Call function to collect indexes. + + Args: + dataset (:obj:`MultiImageMixDataset`): The dataset. + + Returns: + list: indexes. + """ + + indexes = [random.randint(0, len(dataset)) for _ in range(3)] + return indexes + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Mosaic transform function. + + Args: + results (dict): Result dict. + + Returns: + dict: Updated result dict. + """ + if random.uniform(0, 1) > self.prob: + return results + + assert 'mix_results' in results + mosaic_bboxes = [] + mosaic_bboxes_labels = [] + mosaic_ignore_flags = [] + if len(results['img'].shape) == 3: + mosaic_img = np.full( + (int(self.img_scale[1] * 2), int(self.img_scale[0] * 2), 3), + self.pad_val, + dtype=results['img'].dtype) + else: + mosaic_img = np.full( + (int(self.img_scale[1] * 2), int(self.img_scale[0] * 2)), + self.pad_val, + dtype=results['img'].dtype) + + # mosaic center x, y + center_x = int( + random.uniform(*self.center_ratio_range) * self.img_scale[0]) + center_y = int( + random.uniform(*self.center_ratio_range) * self.img_scale[1]) + center_position = (center_x, center_y) + + loc_strs = ('top_left', 'top_right', 'bottom_left', 'bottom_right') + for i, loc in enumerate(loc_strs): + if loc == 'top_left': + results_patch = copy.deepcopy(results) + else: + results_patch = copy.deepcopy(results['mix_results'][i - 1]) + + img_i = results_patch['img'] + h_i, w_i = img_i.shape[:2] + # keep_ratio resize + scale_ratio_i = min(self.img_scale[1] / h_i, + self.img_scale[0] / w_i) + img_i = mmcv.imresize( + img_i, (int(w_i * scale_ratio_i), int(h_i * scale_ratio_i))) + + # compute the combine parameters + paste_coord, crop_coord = self._mosaic_combine( + loc, center_position, img_i.shape[:2][::-1]) + x1_p, y1_p, x2_p, y2_p = paste_coord + x1_c, y1_c, x2_c, y2_c = crop_coord + + # crop and paste image + mosaic_img[y1_p:y2_p, x1_p:x2_p] = img_i[y1_c:y2_c, x1_c:x2_c] + + # adjust coordinate + gt_bboxes_i = results_patch['gt_bboxes'] + gt_bboxes_labels_i = results_patch['gt_bboxes_labels'] + gt_ignore_flags_i = results_patch['gt_ignore_flags'] + + padw = x1_p - x1_c + padh = y1_p - y1_c + gt_bboxes_i.rescale_([scale_ratio_i, scale_ratio_i]) + gt_bboxes_i.translate_([padw, padh]) + mosaic_bboxes.append(gt_bboxes_i) + mosaic_bboxes_labels.append(gt_bboxes_labels_i) + mosaic_ignore_flags.append(gt_ignore_flags_i) + + mosaic_bboxes = mosaic_bboxes[0].cat(mosaic_bboxes, 0) + mosaic_bboxes_labels = np.concatenate(mosaic_bboxes_labels, 0) + mosaic_ignore_flags = np.concatenate(mosaic_ignore_flags, 0) + + if self.bbox_clip_border: + mosaic_bboxes.clip_([2 * self.img_scale[1], 2 * self.img_scale[0]]) + # remove outside bboxes + inside_inds = mosaic_bboxes.is_inside( + [2 * self.img_scale[1], 2 * self.img_scale[0]]).numpy() + mosaic_bboxes = mosaic_bboxes[inside_inds] + mosaic_bboxes_labels = mosaic_bboxes_labels[inside_inds] + mosaic_ignore_flags = mosaic_ignore_flags[inside_inds] + + results['img'] = mosaic_img + results['img_shape'] = mosaic_img.shape[:2] + results['gt_bboxes'] = mosaic_bboxes + results['gt_bboxes_labels'] = mosaic_bboxes_labels + results['gt_ignore_flags'] = mosaic_ignore_flags + return results + + def _mosaic_combine( + self, loc: str, center_position_xy: Sequence[float], + img_shape_wh: Sequence[int]) -> Tuple[Tuple[int], Tuple[int]]: + """Calculate global coordinate of mosaic image and local coordinate of + cropped sub-image. + + Args: + loc (str): Index for the sub-image, loc in ('top_left', + 'top_right', 'bottom_left', 'bottom_right'). + center_position_xy (Sequence[float]): Mixing center for 4 images, + (x, y). + img_shape_wh (Sequence[int]): Width and height of sub-image + + Returns: + tuple[tuple[float]]: Corresponding coordinate of pasting and + cropping + - paste_coord (tuple): paste corner coordinate in mosaic image. + - crop_coord (tuple): crop corner coordinate in mosaic image. + """ + assert loc in ('top_left', 'top_right', 'bottom_left', 'bottom_right') + if loc == 'top_left': + # index0 to top left part of image + x1, y1, x2, y2 = max(center_position_xy[0] - img_shape_wh[0], 0), \ + max(center_position_xy[1] - img_shape_wh[1], 0), \ + center_position_xy[0], \ + center_position_xy[1] + crop_coord = img_shape_wh[0] - (x2 - x1), img_shape_wh[1] - ( + y2 - y1), img_shape_wh[0], img_shape_wh[1] + + elif loc == 'top_right': + # index1 to top right part of image + x1, y1, x2, y2 = center_position_xy[0], \ + max(center_position_xy[1] - img_shape_wh[1], 0), \ + min(center_position_xy[0] + img_shape_wh[0], + self.img_scale[0] * 2), \ + center_position_xy[1] + crop_coord = 0, img_shape_wh[1] - (y2 - y1), min( + img_shape_wh[0], x2 - x1), img_shape_wh[1] + + elif loc == 'bottom_left': + # index2 to bottom left part of image + x1, y1, x2, y2 = max(center_position_xy[0] - img_shape_wh[0], 0), \ + center_position_xy[1], \ + center_position_xy[0], \ + min(self.img_scale[1] * 2, center_position_xy[1] + + img_shape_wh[1]) + crop_coord = img_shape_wh[0] - (x2 - x1), 0, img_shape_wh[0], min( + y2 - y1, img_shape_wh[1]) + + else: + # index3 to bottom right part of image + x1, y1, x2, y2 = center_position_xy[0], \ + center_position_xy[1], \ + min(center_position_xy[0] + img_shape_wh[0], + self.img_scale[0] * 2), \ + min(self.img_scale[1] * 2, center_position_xy[1] + + img_shape_wh[1]) + crop_coord = 0, 0, min(img_shape_wh[0], + x2 - x1), min(y2 - y1, img_shape_wh[1]) + + paste_coord = x1, y1, x2, y2 + return paste_coord, crop_coord + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(img_scale={self.img_scale}, ' + repr_str += f'center_ratio_range={self.center_ratio_range}, ' + repr_str += f'pad_val={self.pad_val}, ' + repr_str += f'prob={self.prob})' + return repr_str + + +@TRANSFORMS.register_module() +class MixUp(BaseTransform): + """MixUp data augmentation. + + .. code:: text + + mixup transform + +------------------------------+ + | mixup image | | + | +--------|--------+ | + | | | | | + |---------------+ | | + | | | | + | | image | | + | | | | + | | | | + | |-----------------+ | + | pad | + +------------------------------+ + + The mixup transform steps are as follows: + + 1. Another random image is picked by dataset and embedded in + the top left patch(after padding and resizing) + 2. The target of mixup transform is the weighted average of mixup + image and origin image. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + - mix_results (List[dict]) + + + Modified Keys: + + - img + - img_shape + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_ignore_flags (optional) + + + Args: + img_scale (Sequence[int]): Image output size after mixup pipeline. + The shape order should be (width, height). Defaults to (640, 640). + ratio_range (Sequence[float]): Scale ratio of mixup image. + Defaults to (0.5, 1.5). + flip_ratio (float): Horizontal flip ratio of mixup image. + Defaults to 0.5. + pad_val (int): Pad value. Defaults to 114. + max_iters (int): The maximum number of iterations. If the number of + iterations is greater than `max_iters`, but gt_bbox is still + empty, then the iteration is terminated. Defaults to 15. + bbox_clip_border (bool, optional): Whether to clip the objects outside + the border of the image. In some dataset like MOT17, the gt bboxes + are allowed to cross the border of images. Therefore, we don't + need to clip the gt bboxes in these cases. Defaults to True. + """ + + def __init__(self, + img_scale: Tuple[int, int] = (640, 640), + ratio_range: Tuple[float, float] = (0.5, 1.5), + flip_ratio: float = 0.5, + pad_val: float = 114.0, + max_iters: int = 15, + bbox_clip_border: bool = True) -> None: + assert isinstance(img_scale, tuple) + log_img_scale(img_scale, skip_square=True, shape_order='wh') + self.dynamic_scale = img_scale + self.ratio_range = ratio_range + self.flip_ratio = flip_ratio + self.pad_val = pad_val + self.max_iters = max_iters + self.bbox_clip_border = bbox_clip_border + + @cache_randomness + def get_indexes(self, dataset: BaseDataset) -> int: + """Call function to collect indexes. + + Args: + dataset (:obj:`MultiImageMixDataset`): The dataset. + + Returns: + list: indexes. + """ + + for i in range(self.max_iters): + index = random.randint(0, len(dataset)) + gt_bboxes_i = dataset[index]['gt_bboxes'] + if len(gt_bboxes_i) != 0: + break + + return index + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """MixUp transform function. + + Args: + results (dict): Result dict. + + Returns: + dict: Updated result dict. + """ + + assert 'mix_results' in results + assert len( + results['mix_results']) == 1, 'MixUp only support 2 images now !' + + if results['mix_results'][0]['gt_bboxes'].shape[0] == 0: + # empty bbox + return results + + retrieve_results = results['mix_results'][0] + retrieve_img = retrieve_results['img'] + + jit_factor = random.uniform(*self.ratio_range) + is_flip = random.uniform(0, 1) > self.flip_ratio + + if len(retrieve_img.shape) == 3: + out_img = np.ones( + (self.dynamic_scale[1], self.dynamic_scale[0], 3), + dtype=retrieve_img.dtype) * self.pad_val + else: + out_img = np.ones( + self.dynamic_scale[::-1], + dtype=retrieve_img.dtype) * self.pad_val + + # 1. keep_ratio resize + scale_ratio = min(self.dynamic_scale[1] / retrieve_img.shape[0], + self.dynamic_scale[0] / retrieve_img.shape[1]) + retrieve_img = mmcv.imresize( + retrieve_img, (int(retrieve_img.shape[1] * scale_ratio), + int(retrieve_img.shape[0] * scale_ratio))) + + # 2. paste + out_img[:retrieve_img.shape[0], :retrieve_img.shape[1]] = retrieve_img + + # 3. scale jit + scale_ratio *= jit_factor + out_img = mmcv.imresize(out_img, (int(out_img.shape[1] * jit_factor), + int(out_img.shape[0] * jit_factor))) + + # 4. flip + if is_flip: + out_img = out_img[:, ::-1, :] + + # 5. random crop + ori_img = results['img'] + origin_h, origin_w = out_img.shape[:2] + target_h, target_w = ori_img.shape[:2] + padded_img = np.ones((max(origin_h, target_h), max( + origin_w, target_w), 3)) * self.pad_val + padded_img = padded_img.astype(np.uint8) + padded_img[:origin_h, :origin_w] = out_img + + x_offset, y_offset = 0, 0 + if padded_img.shape[0] > target_h: + y_offset = random.randint(0, padded_img.shape[0] - target_h) + if padded_img.shape[1] > target_w: + x_offset = random.randint(0, padded_img.shape[1] - target_w) + padded_cropped_img = padded_img[y_offset:y_offset + target_h, + x_offset:x_offset + target_w] + + # 6. adjust bbox + retrieve_gt_bboxes = retrieve_results['gt_bboxes'] + retrieve_gt_bboxes.rescale_([scale_ratio, scale_ratio]) + if self.bbox_clip_border: + retrieve_gt_bboxes.clip_([origin_h, origin_w]) + + if is_flip: + retrieve_gt_bboxes.flip_([origin_h, origin_w], + direction='horizontal') + + # 7. filter + cp_retrieve_gt_bboxes = retrieve_gt_bboxes.clone() + cp_retrieve_gt_bboxes.translate_([-x_offset, -y_offset]) + if self.bbox_clip_border: + cp_retrieve_gt_bboxes.clip_([target_h, target_w]) + + # 8. mix up + ori_img = ori_img.astype(np.float32) + mixup_img = 0.5 * ori_img + 0.5 * padded_cropped_img.astype(np.float32) + + retrieve_gt_bboxes_labels = retrieve_results['gt_bboxes_labels'] + retrieve_gt_ignore_flags = retrieve_results['gt_ignore_flags'] + + mixup_gt_bboxes = cp_retrieve_gt_bboxes.cat( + (results['gt_bboxes'], cp_retrieve_gt_bboxes), dim=0) + mixup_gt_bboxes_labels = np.concatenate( + (results['gt_bboxes_labels'], retrieve_gt_bboxes_labels), axis=0) + mixup_gt_ignore_flags = np.concatenate( + (results['gt_ignore_flags'], retrieve_gt_ignore_flags), axis=0) + + # remove outside bbox + inside_inds = mixup_gt_bboxes.is_inside([target_h, target_w]).numpy() + mixup_gt_bboxes = mixup_gt_bboxes[inside_inds] + mixup_gt_bboxes_labels = mixup_gt_bboxes_labels[inside_inds] + mixup_gt_ignore_flags = mixup_gt_ignore_flags[inside_inds] + + results['img'] = mixup_img.astype(np.uint8) + results['img_shape'] = mixup_img.shape[:2] + results['gt_bboxes'] = mixup_gt_bboxes + results['gt_bboxes_labels'] = mixup_gt_bboxes_labels + results['gt_ignore_flags'] = mixup_gt_ignore_flags + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(dynamic_scale={self.dynamic_scale}, ' + repr_str += f'ratio_range={self.ratio_range}, ' + repr_str += f'flip_ratio={self.flip_ratio}, ' + repr_str += f'pad_val={self.pad_val}, ' + repr_str += f'max_iters={self.max_iters}, ' + repr_str += f'bbox_clip_border={self.bbox_clip_border})' + return repr_str + + +@TRANSFORMS.register_module() +class RandomAffine(BaseTransform): + """Random affine transform data augmentation. + + This operation randomly generates affine transform matrix which including + rotation, translation, shear and scaling transforms. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_ignore_flags (optional) + + Args: + max_rotate_degree (float): Maximum degrees of rotation transform. + Defaults to 10. + max_translate_ratio (float): Maximum ratio of translation. + Defaults to 0.1. + scaling_ratio_range (tuple[float]): Min and max ratio of + scaling transform. Defaults to (0.5, 1.5). + max_shear_degree (float): Maximum degrees of shear + transform. Defaults to 2. + border (tuple[int]): Distance from width and height sides of input + image to adjust output shape. Only used in mosaic dataset. + Defaults to (0, 0). + border_val (tuple[int]): Border padding values of 3 channels. + Defaults to (114, 114, 114). + bbox_clip_border (bool, optional): Whether to clip the objects outside + the border of the image. In some dataset like MOT17, the gt bboxes + are allowed to cross the border of images. Therefore, we don't + need to clip the gt bboxes in these cases. Defaults to True. + """ + + def __init__(self, + max_rotate_degree: float = 10.0, + max_translate_ratio: float = 0.1, + scaling_ratio_range: Tuple[float, float] = (0.5, 1.5), + max_shear_degree: float = 2.0, + border: Tuple[int, int] = (0, 0), + border_val: Tuple[int, int, int] = (114, 114, 114), + bbox_clip_border: bool = True) -> None: + assert 0 <= max_translate_ratio <= 1 + assert scaling_ratio_range[0] <= scaling_ratio_range[1] + assert scaling_ratio_range[0] > 0 + self.max_rotate_degree = max_rotate_degree + self.max_translate_ratio = max_translate_ratio + self.scaling_ratio_range = scaling_ratio_range + self.max_shear_degree = max_shear_degree + self.border = border + self.border_val = border_val + self.bbox_clip_border = bbox_clip_border + + @cache_randomness + def _get_random_homography_matrix(self, height, width): + # Rotation + rotation_degree = random.uniform(-self.max_rotate_degree, + self.max_rotate_degree) + rotation_matrix = self._get_rotation_matrix(rotation_degree) + + # Scaling + scaling_ratio = random.uniform(self.scaling_ratio_range[0], + self.scaling_ratio_range[1]) + scaling_matrix = self._get_scaling_matrix(scaling_ratio) + + # Shear + x_degree = random.uniform(-self.max_shear_degree, + self.max_shear_degree) + y_degree = random.uniform(-self.max_shear_degree, + self.max_shear_degree) + shear_matrix = self._get_shear_matrix(x_degree, y_degree) + + # Translation + trans_x = random.uniform(-self.max_translate_ratio, + self.max_translate_ratio) * width + trans_y = random.uniform(-self.max_translate_ratio, + self.max_translate_ratio) * height + translate_matrix = self._get_translation_matrix(trans_x, trans_y) + + warp_matrix = ( + translate_matrix @ shear_matrix @ rotation_matrix @ scaling_matrix) + return warp_matrix + + @autocast_box_type() + def transform(self, results: dict) -> dict: + img = results['img'] + height = img.shape[0] + self.border[1] * 2 + width = img.shape[1] + self.border[0] * 2 + + warp_matrix = self._get_random_homography_matrix(height, width) + + img = cv2.warpPerspective( + img, + warp_matrix, + dsize=(width, height), + borderValue=self.border_val) + results['img'] = img + results['img_shape'] = img.shape[:2] + + bboxes = results['gt_bboxes'] + num_bboxes = len(bboxes) + if num_bboxes: + bboxes.project_(warp_matrix) + if self.bbox_clip_border: + bboxes.clip_([height, width]) + # remove outside bbox + valid_index = bboxes.is_inside([height, width]).numpy() + results['gt_bboxes'] = bboxes[valid_index] + results['gt_bboxes_labels'] = results['gt_bboxes_labels'][ + valid_index] + results['gt_ignore_flags'] = results['gt_ignore_flags'][ + valid_index] + + if 'gt_masks' in results: + raise NotImplementedError('RandomAffine only supports bbox.') + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(max_rotate_degree={self.max_rotate_degree}, ' + repr_str += f'max_translate_ratio={self.max_translate_ratio}, ' + repr_str += f'scaling_ratio_range={self.scaling_ratio_range}, ' + repr_str += f'max_shear_degree={self.max_shear_degree}, ' + repr_str += f'border={self.border}, ' + repr_str += f'border_val={self.border_val}, ' + repr_str += f'bbox_clip_border={self.bbox_clip_border})' + return repr_str + + @staticmethod + def _get_rotation_matrix(rotate_degrees: float) -> np.ndarray: + radian = math.radians(rotate_degrees) + rotation_matrix = np.array( + [[np.cos(radian), -np.sin(radian), 0.], + [np.sin(radian), np.cos(radian), 0.], [0., 0., 1.]], + dtype=np.float32) + return rotation_matrix + + @staticmethod + def _get_scaling_matrix(scale_ratio: float) -> np.ndarray: + scaling_matrix = np.array( + [[scale_ratio, 0., 0.], [0., scale_ratio, 0.], [0., 0., 1.]], + dtype=np.float32) + return scaling_matrix + + @staticmethod + def _get_shear_matrix(x_shear_degrees: float, + y_shear_degrees: float) -> np.ndarray: + x_radian = math.radians(x_shear_degrees) + y_radian = math.radians(y_shear_degrees) + shear_matrix = np.array([[1, np.tan(x_radian), 0.], + [np.tan(y_radian), 1, 0.], [0., 0., 1.]], + dtype=np.float32) + return shear_matrix + + @staticmethod + def _get_translation_matrix(x: float, y: float) -> np.ndarray: + translation_matrix = np.array([[1, 0., x], [0., 1, y], [0., 0., 1.]], + dtype=np.float32) + return translation_matrix + + +@TRANSFORMS.register_module() +class YOLOXHSVRandomAug(BaseTransform): + """Apply HSV augmentation to image sequentially. It is referenced from + https://github.com/Megvii- + BaseDetection/YOLOX/blob/main/yolox/data/data_augment.py#L21. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + hue_delta (int): delta of hue. Defaults to 5. + saturation_delta (int): delta of saturation. Defaults to 30. + value_delta (int): delat of value. Defaults to 30. + """ + + def __init__(self, + hue_delta: int = 5, + saturation_delta: int = 30, + value_delta: int = 30) -> None: + self.hue_delta = hue_delta + self.saturation_delta = saturation_delta + self.value_delta = value_delta + + @cache_randomness + def _get_hsv_gains(self): + hsv_gains = np.random.uniform(-1, 1, 3) * [ + self.hue_delta, self.saturation_delta, self.value_delta + ] + # random selection of h, s, v + hsv_gains *= np.random.randint(0, 2, 3) + # prevent overflow + hsv_gains = hsv_gains.astype(np.int16) + return hsv_gains + + def transform(self, results: dict) -> dict: + img = results['img'] + hsv_gains = self._get_hsv_gains() + img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV).astype(np.int16) + + img_hsv[..., 0] = (img_hsv[..., 0] + hsv_gains[0]) % 180 + img_hsv[..., 1] = np.clip(img_hsv[..., 1] + hsv_gains[1], 0, 255) + img_hsv[..., 2] = np.clip(img_hsv[..., 2] + hsv_gains[2], 0, 255) + cv2.cvtColor(img_hsv.astype(img.dtype), cv2.COLOR_HSV2BGR, dst=img) + + results['img'] = img + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(hue_delta={self.hue_delta}, ' + repr_str += f'saturation_delta={self.saturation_delta}, ' + repr_str += f'value_delta={self.value_delta})' + return repr_str + + +@TRANSFORMS.register_module() +class CopyPaste(BaseTransform): + """Simple Copy-Paste is a Strong Data Augmentation Method for Instance + Segmentation The simple copy-paste transform steps are as follows: + + 1. The destination image is already resized with aspect ratio kept, + cropped and padded. + 2. Randomly select a source image, which is also already resized + with aspect ratio kept, cropped and padded in a similar way + as the destination image. + 3. Randomly select some objects from the source image. + 4. Paste these source objects to the destination image directly, + due to the source and destination image have the same size. + 5. Update object masks of the destination image, for some origin objects + may be occluded. + 6. Generate bboxes from the updated destination masks and + filter some objects which are totally occluded, and adjust bboxes + which are partly occluded. + 7. Append selected source bboxes, masks, and labels. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + - gt_masks (BitmapMasks) (optional) + + Modified Keys: + + - img + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_ignore_flags (optional) + - gt_masks (optional) + + Args: + max_num_pasted (int): The maximum number of pasted objects. + Defaults to 100. + bbox_occluded_thr (int): The threshold of occluded bbox. + Defaults to 10. + mask_occluded_thr (int): The threshold of occluded mask. + Defaults to 300. + selected (bool): Whether select objects or not. If select is False, + all objects of the source image will be pasted to the + destination image. + Defaults to True. + paste_by_box (bool): Whether use boxes as masks when masks are not + available. + Defaults to False. + """ + + def __init__( + self, + max_num_pasted: int = 100, + bbox_occluded_thr: int = 10, + mask_occluded_thr: int = 300, + selected: bool = True, + paste_by_box: bool = False, + ) -> None: + self.max_num_pasted = max_num_pasted + self.bbox_occluded_thr = bbox_occluded_thr + self.mask_occluded_thr = mask_occluded_thr + self.selected = selected + self.paste_by_box = paste_by_box + + @cache_randomness + def get_indexes(self, dataset: BaseDataset) -> int: + """Call function to collect indexes.s. + + Args: + dataset (:obj:`MultiImageMixDataset`): The dataset. + Returns: + list: Indexes. + """ + return random.randint(0, len(dataset)) + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Transform function to make a copy-paste of image. + + Args: + results (dict): Result dict. + Returns: + dict: Result dict with copy-paste transformed. + """ + + assert 'mix_results' in results + num_images = len(results['mix_results']) + assert num_images == 1, \ + f'CopyPaste only supports processing 2 images, got {num_images}' + if self.selected: + selected_results = self._select_object(results['mix_results'][0]) + else: + selected_results = results['mix_results'][0] + return self._copy_paste(results, selected_results) + + @cache_randomness + def _get_selected_inds(self, num_bboxes: int) -> np.ndarray: + max_num_pasted = min(num_bboxes + 1, self.max_num_pasted) + num_pasted = np.random.randint(0, max_num_pasted) + return np.random.choice(num_bboxes, size=num_pasted, replace=False) + + def get_gt_masks(self, results: dict) -> BitmapMasks: + """Get gt_masks originally or generated based on bboxes. + + If gt_masks is not contained in results, + it will be generated based on gt_bboxes. + Args: + results (dict): Result dict. + Returns: + BitmapMasks: gt_masks, originally or generated based on bboxes. + """ + if results.get('gt_masks', None) is not None: + if self.paste_by_box: + warnings.warn('gt_masks is already contained in results, ' + 'so paste_by_box is disabled.') + return results['gt_masks'] + else: + if not self.paste_by_box: + raise RuntimeError('results does not contain masks.') + return results['gt_bboxes'].create_masks(results['img'].shape[:2]) + + def _select_object(self, results: dict) -> dict: + """Select some objects from the source results.""" + bboxes = results['gt_bboxes'] + labels = results['gt_bboxes_labels'] + masks = self.get_gt_masks(results) + ignore_flags = results['gt_ignore_flags'] + + selected_inds = self._get_selected_inds(bboxes.shape[0]) + + selected_bboxes = bboxes[selected_inds] + selected_labels = labels[selected_inds] + selected_masks = masks[selected_inds] + selected_ignore_flags = ignore_flags[selected_inds] + + results['gt_bboxes'] = selected_bboxes + results['gt_bboxes_labels'] = selected_labels + results['gt_masks'] = selected_masks + results['gt_ignore_flags'] = selected_ignore_flags + return results + + def _copy_paste(self, dst_results: dict, src_results: dict) -> dict: + """CopyPaste transform function. + + Args: + dst_results (dict): Result dict of the destination image. + src_results (dict): Result dict of the source image. + Returns: + dict: Updated result dict. + """ + dst_img = dst_results['img'] + dst_bboxes = dst_results['gt_bboxes'] + dst_labels = dst_results['gt_bboxes_labels'] + dst_masks = self.get_gt_masks(dst_results) + dst_ignore_flags = dst_results['gt_ignore_flags'] + + src_img = src_results['img'] + src_bboxes = src_results['gt_bboxes'] + src_labels = src_results['gt_bboxes_labels'] + src_masks = src_results['gt_masks'] + src_ignore_flags = src_results['gt_ignore_flags'] + + if len(src_bboxes) == 0: + return dst_results + + # update masks and generate bboxes from updated masks + composed_mask = np.where(np.any(src_masks.masks, axis=0), 1, 0) + updated_dst_masks = self._get_updated_masks(dst_masks, composed_mask) + updated_dst_bboxes = updated_dst_masks.get_bboxes(type(dst_bboxes)) + assert len(updated_dst_bboxes) == len(updated_dst_masks) + + # filter totally occluded objects + l1_distance = (updated_dst_bboxes.tensor - dst_bboxes.tensor).abs() + bboxes_inds = (l1_distance <= self.bbox_occluded_thr).all( + dim=-1).numpy() + masks_inds = updated_dst_masks.masks.sum( + axis=(1, 2)) > self.mask_occluded_thr + valid_inds = bboxes_inds | masks_inds + + # Paste source objects to destination image directly + img = dst_img * (1 - composed_mask[..., np.newaxis] + ) + src_img * composed_mask[..., np.newaxis] + bboxes = src_bboxes.cat([updated_dst_bboxes[valid_inds], src_bboxes]) + labels = np.concatenate([dst_labels[valid_inds], src_labels]) + masks = np.concatenate( + [updated_dst_masks.masks[valid_inds], src_masks.masks]) + ignore_flags = np.concatenate( + [dst_ignore_flags[valid_inds], src_ignore_flags]) + + dst_results['img'] = img + dst_results['gt_bboxes'] = bboxes + dst_results['gt_bboxes_labels'] = labels + dst_results['gt_masks'] = BitmapMasks(masks, masks.shape[1], + masks.shape[2]) + dst_results['gt_ignore_flags'] = ignore_flags + + return dst_results + + def _get_updated_masks(self, masks: BitmapMasks, + composed_mask: np.ndarray) -> BitmapMasks: + """Update masks with composed mask.""" + assert masks.masks.shape[-2:] == composed_mask.shape[-2:], \ + 'Cannot compare two arrays of different size' + masks.masks = np.where(composed_mask, 0, masks.masks) + return masks + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(max_num_pasted={self.max_num_pasted}, ' + repr_str += f'bbox_occluded_thr={self.bbox_occluded_thr}, ' + repr_str += f'mask_occluded_thr={self.mask_occluded_thr}, ' + repr_str += f'selected={self.selected}), ' + repr_str += f'paste_by_box={self.paste_by_box})' + return repr_str + + +@TRANSFORMS.register_module() +class RandomErasing(BaseTransform): + """RandomErasing operation. + + Random Erasing randomly selects a rectangle region + in an image and erases its pixels with random values. + `RandomErasing `_. + + Required Keys: + + - img + - gt_bboxes (HorizontalBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + - gt_masks (BitmapMasks) (optional) + + Modified Keys: + - img + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_ignore_flags (optional) + - gt_masks (optional) + + Args: + n_patches (int or tuple[int, int]): Number of regions to be dropped. + If it is given as a tuple, number of patches will be randomly + selected from the closed interval [``n_patches[0]``, + ``n_patches[1]``]. + ratio (float or tuple[float, float]): The ratio of erased regions. + It can be ``float`` to use a fixed ratio or ``tuple[float, float]`` + to randomly choose ratio from the interval. + squared (bool): Whether to erase square region. Defaults to True. + bbox_erased_thr (float): The threshold for the maximum area proportion + of the bbox to be erased. When the proportion of the area where the + bbox is erased is greater than the threshold, the bbox will be + removed. Defaults to 0.9. + img_border_value (int or float or tuple): The filled values for + image border. If float, the same fill value will be used for + all the three channels of image. If tuple, it should be 3 elements. + Defaults to 128. + mask_border_value (int): The fill value used for masks. Defaults to 0. + seg_ignore_label (int): The fill value used for segmentation map. + Note this value must equals ``ignore_label`` in ``semantic_head`` + of the corresponding config. Defaults to 255. + """ + + def __init__( + self, + n_patches: Union[int, Tuple[int, int]], + ratio: Union[float, Tuple[float, float]], + squared: bool = True, + bbox_erased_thr: float = 0.9, + img_border_value: Union[int, float, tuple] = 128, + mask_border_value: int = 0, + seg_ignore_label: int = 255, + ) -> None: + if isinstance(n_patches, tuple): + assert len(n_patches) == 2 and 0 <= n_patches[0] < n_patches[1] + else: + n_patches = (n_patches, n_patches) + if isinstance(ratio, tuple): + assert len(ratio) == 2 and 0 <= ratio[0] < ratio[1] <= 1 + else: + ratio = (ratio, ratio) + + self.n_patches = n_patches + self.ratio = ratio + self.squared = squared + self.bbox_erased_thr = bbox_erased_thr + self.img_border_value = img_border_value + self.mask_border_value = mask_border_value + self.seg_ignore_label = seg_ignore_label + + @cache_randomness + def _get_patches(self, img_shape: Tuple[int, int]) -> List[list]: + """Get patches for random erasing.""" + patches = [] + n_patches = np.random.randint(self.n_patches[0], self.n_patches[1] + 1) + for _ in range(n_patches): + if self.squared: + ratio = np.random.random() * (self.ratio[1] - + self.ratio[0]) + self.ratio[0] + ratio = (ratio, ratio) + else: + ratio = (np.random.random() * (self.ratio[1] - self.ratio[0]) + + self.ratio[0], np.random.random() * + (self.ratio[1] - self.ratio[0]) + self.ratio[0]) + ph, pw = int(img_shape[0] * ratio[0]), int(img_shape[1] * ratio[1]) + px1, py1 = np.random.randint(0, + img_shape[1] - pw), np.random.randint( + 0, img_shape[0] - ph) + px2, py2 = px1 + pw, py1 + ph + patches.append([px1, py1, px2, py2]) + return np.array(patches) + + def _transform_img(self, results: dict, patches: List[list]) -> None: + """Random erasing the image.""" + for patch in patches: + px1, py1, px2, py2 = patch + results['img'][py1:py2, px1:px2, :] = self.img_border_value + + def _transform_bboxes(self, results: dict, patches: List[list]) -> None: + """Random erasing the bboxes.""" + bboxes = results['gt_bboxes'] + # TODO: unify the logic by using operators in BaseBoxes. + assert isinstance(bboxes, HorizontalBoxes) + bboxes = bboxes.numpy() + left_top = np.maximum(bboxes[:, None, :2], patches[:, :2]) + right_bottom = np.minimum(bboxes[:, None, 2:], patches[:, 2:]) + wh = np.maximum(right_bottom - left_top, 0) + inter_areas = wh[:, :, 0] * wh[:, :, 1] + bbox_areas = (bboxes[:, 2] - bboxes[:, 0]) * ( + bboxes[:, 3] - bboxes[:, 1]) + bboxes_erased_ratio = inter_areas.sum(-1) / (bbox_areas + 1e-7) + valid_inds = bboxes_erased_ratio < self.bbox_erased_thr + results['gt_bboxes'] = HorizontalBoxes(bboxes[valid_inds]) + results['gt_bboxes_labels'] = results['gt_bboxes_labels'][valid_inds] + results['gt_ignore_flags'] = results['gt_ignore_flags'][valid_inds] + if results.get('gt_masks', None) is not None: + results['gt_masks'] = results['gt_masks'][valid_inds] + + def _transform_masks(self, results: dict, patches: List[list]) -> None: + """Random erasing the masks.""" + for patch in patches: + px1, py1, px2, py2 = patch + results['gt_masks'].masks[:, py1:py2, + px1:px2] = self.mask_border_value + + def _transform_seg(self, results: dict, patches: List[list]) -> None: + """Random erasing the segmentation map.""" + for patch in patches: + px1, py1, px2, py2 = patch + results['gt_seg_map'][py1:py2, px1:px2] = self.seg_ignore_label + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Transform function to erase some regions of image.""" + patches = self._get_patches(results['img_shape']) + self._transform_img(results, patches) + if results.get('gt_bboxes', None) is not None: + self._transform_bboxes(results, patches) + if results.get('gt_masks', None) is not None: + self._transform_masks(results, patches) + if results.get('gt_seg_map', None) is not None: + self._transform_seg(results, patches) + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(n_patches={self.n_patches}, ' + repr_str += f'ratio={self.ratio}, ' + repr_str += f'squared={self.squared}, ' + repr_str += f'bbox_erased_thr={self.bbox_erased_thr}, ' + repr_str += f'img_border_value={self.img_border_value}, ' + repr_str += f'mask_border_value={self.mask_border_value}, ' + repr_str += f'seg_ignore_label={self.seg_ignore_label})' + return repr_str + + +@TRANSFORMS.register_module() +class CachedMosaic(Mosaic): + """Cached mosaic augmentation. + + Cached mosaic transform will random select images from the cache + and combine them into one output image. + + .. code:: text + + mosaic transform + center_x + +------------------------------+ + | pad | pad | + | +-----------+ | + | | | | + | | image1 |--------+ | + | | | | | + | | | image2 | | + center_y |----+-------------+-----------| + | | cropped | | + |pad | image3 | image4 | + | | | | + +----|-------------+-----------+ + | | + +-------------+ + + The cached mosaic transform steps are as follows: + + 1. Append the results from the last transform into the cache. + 2. Choose the mosaic center as the intersections of 4 images + 3. Get the left top image according to the index, and randomly + sample another 3 images from the result cache. + 4. Sub image will be cropped if image is larger than mosaic patch + + Required Keys: + + - img + - gt_bboxes (np.float32) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_ignore_flags (optional) + + Args: + img_scale (Sequence[int]): Image size before mosaic pipeline of single + image. The shape order should be (width, height). + Defaults to (640, 640). + center_ratio_range (Sequence[float]): Center ratio range of mosaic + output. Defaults to (0.5, 1.5). + bbox_clip_border (bool, optional): Whether to clip the objects outside + the border of the image. In some dataset like MOT17, the gt bboxes + are allowed to cross the border of images. Therefore, we don't + need to clip the gt bboxes in these cases. Defaults to True. + pad_val (int): Pad value. Defaults to 114. + prob (float): Probability of applying this transformation. + Defaults to 1.0. + max_cached_images (int): The maximum length of the cache. The larger + the cache, the stronger the randomness of this transform. As a + rule of thumb, providing 10 caches for each image suffices for + randomness. Defaults to 40. + random_pop (bool): Whether to randomly pop a result from the cache + when the cache is full. If set to False, use FIFO popping method. + Defaults to True. + """ + + def __init__(self, + *args, + max_cached_images: int = 40, + random_pop: bool = True, + **kwargs) -> None: + super().__init__(*args, **kwargs) + self.results_cache = [] + self.random_pop = random_pop + assert max_cached_images >= 4, 'The length of cache must >= 4, ' \ + f'but got {max_cached_images}.' + self.max_cached_images = max_cached_images + + @cache_randomness + def get_indexes(self, cache: list) -> list: + """Call function to collect indexes. + + Args: + cache (list): The results cache. + + Returns: + list: indexes. + """ + + indexes = [random.randint(0, len(cache) - 1) for _ in range(3)] + return indexes + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """Mosaic transform function. + + Args: + results (dict): Result dict. + + Returns: + dict: Updated result dict. + """ + # cache and pop images + self.results_cache.append(copy.deepcopy(results)) + if len(self.results_cache) > self.max_cached_images: + if self.random_pop: + index = random.randint(0, len(self.results_cache) - 1) + else: + index = 0 + self.results_cache.pop(index) + + if len(self.results_cache) <= 4: + return results + + if random.uniform(0, 1) > self.prob: + return results + indices = self.get_indexes(self.results_cache) + mix_results = [copy.deepcopy(self.results_cache[i]) for i in indices] + + # TODO: refactor mosaic to reuse these code. + mosaic_bboxes = [] + mosaic_bboxes_labels = [] + mosaic_ignore_flags = [] + mosaic_masks = [] + with_mask = True if 'gt_masks' in results else False + + if len(results['img'].shape) == 3: + mosaic_img = np.full( + (int(self.img_scale[1] * 2), int(self.img_scale[0] * 2), 3), + self.pad_val, + dtype=results['img'].dtype) + else: + mosaic_img = np.full( + (int(self.img_scale[1] * 2), int(self.img_scale[0] * 2)), + self.pad_val, + dtype=results['img'].dtype) + + # mosaic center x, y + center_x = int( + random.uniform(*self.center_ratio_range) * self.img_scale[0]) + center_y = int( + random.uniform(*self.center_ratio_range) * self.img_scale[1]) + center_position = (center_x, center_y) + + loc_strs = ('top_left', 'top_right', 'bottom_left', 'bottom_right') + for i, loc in enumerate(loc_strs): + if loc == 'top_left': + results_patch = copy.deepcopy(results) + else: + results_patch = copy.deepcopy(mix_results[i - 1]) + + img_i = results_patch['img'] + h_i, w_i = img_i.shape[:2] + # keep_ratio resize + scale_ratio_i = min(self.img_scale[1] / h_i, + self.img_scale[0] / w_i) + img_i = mmcv.imresize( + img_i, (int(w_i * scale_ratio_i), int(h_i * scale_ratio_i))) + + # compute the combine parameters + paste_coord, crop_coord = self._mosaic_combine( + loc, center_position, img_i.shape[:2][::-1]) + x1_p, y1_p, x2_p, y2_p = paste_coord + x1_c, y1_c, x2_c, y2_c = crop_coord + + # crop and paste image + mosaic_img[y1_p:y2_p, x1_p:x2_p] = img_i[y1_c:y2_c, x1_c:x2_c] + + # adjust coordinate + gt_bboxes_i = results_patch['gt_bboxes'] + gt_bboxes_labels_i = results_patch['gt_bboxes_labels'] + gt_ignore_flags_i = results_patch['gt_ignore_flags'] + + padw = x1_p - x1_c + padh = y1_p - y1_c + gt_bboxes_i.rescale_([scale_ratio_i, scale_ratio_i]) + gt_bboxes_i.translate_([padw, padh]) + mosaic_bboxes.append(gt_bboxes_i) + mosaic_bboxes_labels.append(gt_bboxes_labels_i) + mosaic_ignore_flags.append(gt_ignore_flags_i) + if with_mask and results_patch.get('gt_masks', None) is not None: + gt_masks_i = results_patch['gt_masks'] + gt_masks_i = gt_masks_i.rescale(float(scale_ratio_i)) + gt_masks_i = gt_masks_i.translate( + out_shape=(int(self.img_scale[0] * 2), + int(self.img_scale[1] * 2)), + offset=padw, + direction='horizontal') + gt_masks_i = gt_masks_i.translate( + out_shape=(int(self.img_scale[0] * 2), + int(self.img_scale[1] * 2)), + offset=padh, + direction='vertical') + mosaic_masks.append(gt_masks_i) + + mosaic_bboxes = mosaic_bboxes[0].cat(mosaic_bboxes, 0) + mosaic_bboxes_labels = np.concatenate(mosaic_bboxes_labels, 0) + mosaic_ignore_flags = np.concatenate(mosaic_ignore_flags, 0) + + if self.bbox_clip_border: + mosaic_bboxes.clip_([2 * self.img_scale[1], 2 * self.img_scale[0]]) + # remove outside bboxes + inside_inds = mosaic_bboxes.is_inside( + [2 * self.img_scale[1], 2 * self.img_scale[0]]).numpy() + mosaic_bboxes = mosaic_bboxes[inside_inds] + mosaic_bboxes_labels = mosaic_bboxes_labels[inside_inds] + mosaic_ignore_flags = mosaic_ignore_flags[inside_inds] + + results['img'] = mosaic_img + results['img_shape'] = mosaic_img.shape[:2] + results['gt_bboxes'] = mosaic_bboxes + results['gt_bboxes_labels'] = mosaic_bboxes_labels + results['gt_ignore_flags'] = mosaic_ignore_flags + + if with_mask: + mosaic_masks = mosaic_masks[0].cat(mosaic_masks) + results['gt_masks'] = mosaic_masks[inside_inds] + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(img_scale={self.img_scale}, ' + repr_str += f'center_ratio_range={self.center_ratio_range}, ' + repr_str += f'pad_val={self.pad_val}, ' + repr_str += f'prob={self.prob}, ' + repr_str += f'max_cached_images={self.max_cached_images}, ' + repr_str += f'random_pop={self.random_pop})' + return repr_str + + +@TRANSFORMS.register_module() +class CachedMixUp(BaseTransform): + """Cached mixup data augmentation. + + .. code:: text + + mixup transform + +------------------------------+ + | mixup image | | + | +--------|--------+ | + | | | | | + |---------------+ | | + | | | | + | | image | | + | | | | + | | | | + | |-----------------+ | + | pad | + +------------------------------+ + + The cached mixup transform steps are as follows: + + 1. Append the results from the last transform into the cache. + 2. Another random image is picked from the cache and embedded in + the top left patch(after padding and resizing) + 3. The target of mixup transform is the weighted average of mixup + image and origin image. + + Required Keys: + + - img + - gt_bboxes (np.float32) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + - mix_results (List[dict]) + + + Modified Keys: + + - img + - img_shape + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_ignore_flags (optional) + + + Args: + img_scale (Sequence[int]): Image output size after mixup pipeline. + The shape order should be (width, height). Defaults to (640, 640). + ratio_range (Sequence[float]): Scale ratio of mixup image. + Defaults to (0.5, 1.5). + flip_ratio (float): Horizontal flip ratio of mixup image. + Defaults to 0.5. + pad_val (int): Pad value. Defaults to 114. + max_iters (int): The maximum number of iterations. If the number of + iterations is greater than `max_iters`, but gt_bbox is still + empty, then the iteration is terminated. Defaults to 15. + bbox_clip_border (bool, optional): Whether to clip the objects outside + the border of the image. In some dataset like MOT17, the gt bboxes + are allowed to cross the border of images. Therefore, we don't + need to clip the gt bboxes in these cases. Defaults to True. + max_cached_images (int): The maximum length of the cache. The larger + the cache, the stronger the randomness of this transform. As a + rule of thumb, providing 10 caches for each image suffices for + randomness. Defaults to 20. + random_pop (bool): Whether to randomly pop a result from the cache + when the cache is full. If set to False, use FIFO popping method. + Defaults to True. + prob (float): Probability of applying this transformation. + Defaults to 1.0. + """ + + def __init__(self, + img_scale: Tuple[int, int] = (640, 640), + ratio_range: Tuple[float, float] = (0.5, 1.5), + flip_ratio: float = 0.5, + pad_val: float = 114.0, + max_iters: int = 15, + bbox_clip_border: bool = True, + max_cached_images: int = 20, + random_pop: bool = True, + prob: float = 1.0) -> None: + assert isinstance(img_scale, tuple) + assert max_cached_images >= 2, 'The length of cache must >= 2, ' \ + f'but got {max_cached_images}.' + assert 0 <= prob <= 1.0, 'The probability should be in range [0,1]. ' \ + f'got {prob}.' + self.dynamic_scale = img_scale + self.ratio_range = ratio_range + self.flip_ratio = flip_ratio + self.pad_val = pad_val + self.max_iters = max_iters + self.bbox_clip_border = bbox_clip_border + self.results_cache = [] + + self.max_cached_images = max_cached_images + self.random_pop = random_pop + self.prob = prob + + @cache_randomness + def get_indexes(self, cache: list) -> int: + """Call function to collect indexes. + + Args: + cache (list): The result cache. + + Returns: + int: index. + """ + + for i in range(self.max_iters): + index = random.randint(0, len(cache) - 1) + gt_bboxes_i = cache[index]['gt_bboxes'] + if len(gt_bboxes_i) != 0: + break + return index + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """MixUp transform function. + + Args: + results (dict): Result dict. + + Returns: + dict: Updated result dict. + """ + # cache and pop images + self.results_cache.append(copy.deepcopy(results)) + if len(self.results_cache) > self.max_cached_images: + if self.random_pop: + index = random.randint(0, len(self.results_cache) - 1) + else: + index = 0 + self.results_cache.pop(index) + + if len(self.results_cache) <= 1: + return results + + if random.uniform(0, 1) > self.prob: + return results + + index = self.get_indexes(self.results_cache) + retrieve_results = copy.deepcopy(self.results_cache[index]) + + # TODO: refactor mixup to reuse these code. + if retrieve_results['gt_bboxes'].shape[0] == 0: + # empty bbox + return results + + retrieve_img = retrieve_results['img'] + with_mask = True if 'gt_masks' in results else False + + jit_factor = random.uniform(*self.ratio_range) + is_flip = random.uniform(0, 1) > self.flip_ratio + + if len(retrieve_img.shape) == 3: + out_img = np.ones( + (self.dynamic_scale[1], self.dynamic_scale[0], 3), + dtype=retrieve_img.dtype) * self.pad_val + else: + out_img = np.ones( + self.dynamic_scale[::-1], + dtype=retrieve_img.dtype) * self.pad_val + + # 1. keep_ratio resize + scale_ratio = min(self.dynamic_scale[1] / retrieve_img.shape[0], + self.dynamic_scale[0] / retrieve_img.shape[1]) + retrieve_img = mmcv.imresize( + retrieve_img, (int(retrieve_img.shape[1] * scale_ratio), + int(retrieve_img.shape[0] * scale_ratio))) + + # 2. paste + out_img[:retrieve_img.shape[0], :retrieve_img.shape[1]] = retrieve_img + + # 3. scale jit + scale_ratio *= jit_factor + out_img = mmcv.imresize(out_img, (int(out_img.shape[1] * jit_factor), + int(out_img.shape[0] * jit_factor))) + + # 4. flip + if is_flip: + out_img = out_img[:, ::-1, :] + + # 5. random crop + ori_img = results['img'] + origin_h, origin_w = out_img.shape[:2] + target_h, target_w = ori_img.shape[:2] + padded_img = np.ones((max(origin_h, target_h), max( + origin_w, target_w), 3)) * self.pad_val + padded_img = padded_img.astype(np.uint8) + padded_img[:origin_h, :origin_w] = out_img + + x_offset, y_offset = 0, 0 + if padded_img.shape[0] > target_h: + y_offset = random.randint(0, padded_img.shape[0] - target_h) + if padded_img.shape[1] > target_w: + x_offset = random.randint(0, padded_img.shape[1] - target_w) + padded_cropped_img = padded_img[y_offset:y_offset + target_h, + x_offset:x_offset + target_w] + + # 6. adjust bbox + retrieve_gt_bboxes = retrieve_results['gt_bboxes'] + retrieve_gt_bboxes.rescale_([scale_ratio, scale_ratio]) + if with_mask: + retrieve_gt_masks = retrieve_results['gt_masks'].rescale( + scale_ratio) + + if self.bbox_clip_border: + retrieve_gt_bboxes.clip_([origin_h, origin_w]) + + if is_flip: + retrieve_gt_bboxes.flip_([origin_h, origin_w], + direction='horizontal') + if with_mask: + retrieve_gt_masks = retrieve_gt_masks.flip() + + # 7. filter + cp_retrieve_gt_bboxes = retrieve_gt_bboxes.clone() + cp_retrieve_gt_bboxes.translate_([-x_offset, -y_offset]) + if with_mask: + retrieve_gt_masks = retrieve_gt_masks.translate( + out_shape=(target_h, target_w), + offset=-x_offset, + direction='horizontal') + retrieve_gt_masks = retrieve_gt_masks.translate( + out_shape=(target_h, target_w), + offset=-y_offset, + direction='vertical') + + if self.bbox_clip_border: + cp_retrieve_gt_bboxes.clip_([target_h, target_w]) + + # 8. mix up + ori_img = ori_img.astype(np.float32) + mixup_img = 0.5 * ori_img + 0.5 * padded_cropped_img.astype(np.float32) + + retrieve_gt_bboxes_labels = retrieve_results['gt_bboxes_labels'] + retrieve_gt_ignore_flags = retrieve_results['gt_ignore_flags'] + + mixup_gt_bboxes = cp_retrieve_gt_bboxes.cat( + (results['gt_bboxes'], cp_retrieve_gt_bboxes), dim=0) + mixup_gt_bboxes_labels = np.concatenate( + (results['gt_bboxes_labels'], retrieve_gt_bboxes_labels), axis=0) + mixup_gt_ignore_flags = np.concatenate( + (results['gt_ignore_flags'], retrieve_gt_ignore_flags), axis=0) + if with_mask: + mixup_gt_masks = retrieve_gt_masks.cat( + [results['gt_masks'], retrieve_gt_masks]) + + # remove outside bbox + inside_inds = mixup_gt_bboxes.is_inside([target_h, target_w]).numpy() + mixup_gt_bboxes = mixup_gt_bboxes[inside_inds] + mixup_gt_bboxes_labels = mixup_gt_bboxes_labels[inside_inds] + mixup_gt_ignore_flags = mixup_gt_ignore_flags[inside_inds] + if with_mask: + mixup_gt_masks = mixup_gt_masks[inside_inds] + + results['img'] = mixup_img.astype(np.uint8) + results['img_shape'] = mixup_img.shape[:2] + results['gt_bboxes'] = mixup_gt_bboxes + results['gt_bboxes_labels'] = mixup_gt_bboxes_labels + results['gt_ignore_flags'] = mixup_gt_ignore_flags + if with_mask: + results['gt_masks'] = mixup_gt_masks + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(dynamic_scale={self.dynamic_scale}, ' + repr_str += f'ratio_range={self.ratio_range}, ' + repr_str += f'flip_ratio={self.flip_ratio}, ' + repr_str += f'pad_val={self.pad_val}, ' + repr_str += f'max_iters={self.max_iters}, ' + repr_str += f'bbox_clip_border={self.bbox_clip_border}, ' + repr_str += f'max_cached_images={self.max_cached_images}, ' + repr_str += f'random_pop={self.random_pop}, ' + repr_str += f'prob={self.prob})' + return repr_str diff --git a/mmdetection/mmdet/datasets/transforms/wrappers.py b/mmdetection/mmdet/datasets/transforms/wrappers.py new file mode 100644 index 00000000..3a17711c --- /dev/null +++ b/mmdetection/mmdet/datasets/transforms/wrappers.py @@ -0,0 +1,277 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import Callable, Dict, List, Optional, Union + +import numpy as np +from mmcv.transforms import BaseTransform, Compose +from mmcv.transforms.utils import cache_random_params, cache_randomness + +from mmdet.registry import TRANSFORMS + + +@TRANSFORMS.register_module() +class MultiBranch(BaseTransform): + r"""Multiple branch pipeline wrapper. + + Generate multiple data-augmented versions of the same image. + `MultiBranch` needs to specify the branch names of all + pipelines of the dataset, perform corresponding data augmentation + for the current branch, and return None for other branches, + which ensures the consistency of return format across + different samples. + + Args: + branch_field (list): List of branch names. + branch_pipelines (dict): Dict of different pipeline configs + to be composed. + + Examples: + >>> branch_field = ['sup', 'unsup_teacher', 'unsup_student'] + >>> sup_pipeline = [ + >>> dict(type='LoadImageFromFile'), + >>> dict(type='LoadAnnotations', with_bbox=True), + >>> dict(type='Resize', scale=(1333, 800), keep_ratio=True), + >>> dict(type='RandomFlip', prob=0.5), + >>> dict( + >>> type='MultiBranch', + >>> branch_field=branch_field, + >>> sup=dict(type='PackDetInputs')) + >>> ] + >>> weak_pipeline = [ + >>> dict(type='LoadImageFromFile'), + >>> dict(type='LoadAnnotations', with_bbox=True), + >>> dict(type='Resize', scale=(1333, 800), keep_ratio=True), + >>> dict(type='RandomFlip', prob=0.0), + >>> dict( + >>> type='MultiBranch', + >>> branch_field=branch_field, + >>> sup=dict(type='PackDetInputs')) + >>> ] + >>> strong_pipeline = [ + >>> dict(type='LoadImageFromFile'), + >>> dict(type='LoadAnnotations', with_bbox=True), + >>> dict(type='Resize', scale=(1333, 800), keep_ratio=True), + >>> dict(type='RandomFlip', prob=1.0), + >>> dict( + >>> type='MultiBranch', + >>> branch_field=branch_field, + >>> sup=dict(type='PackDetInputs')) + >>> ] + >>> unsup_pipeline = [ + >>> dict(type='LoadImageFromFile'), + >>> dict(type='LoadEmptyAnnotations'), + >>> dict( + >>> type='MultiBranch', + >>> branch_field=branch_field, + >>> unsup_teacher=weak_pipeline, + >>> unsup_student=strong_pipeline) + >>> ] + >>> from mmcv.transforms import Compose + >>> sup_branch = Compose(sup_pipeline) + >>> unsup_branch = Compose(unsup_pipeline) + >>> print(sup_branch) + >>> Compose( + >>> LoadImageFromFile(ignore_empty=False, to_float32=False, color_type='color', imdecode_backend='cv2') # noqa + >>> LoadAnnotations(with_bbox=True, with_label=True, with_mask=False, with_seg=False, poly2mask=True, imdecode_backend='cv2') # noqa + >>> Resize(scale=(1333, 800), scale_factor=None, keep_ratio=True, clip_object_border=True), backend=cv2), interpolation=bilinear) # noqa + >>> RandomFlip(prob=0.5, direction=horizontal) + >>> MultiBranch(branch_pipelines=['sup']) + >>> ) + >>> print(unsup_branch) + >>> Compose( + >>> LoadImageFromFile(ignore_empty=False, to_float32=False, color_type='color', imdecode_backend='cv2') # noqa + >>> LoadEmptyAnnotations(with_bbox=True, with_label=True, with_mask=False, with_seg=False, seg_ignore_label=255) # noqa + >>> MultiBranch(branch_pipelines=['unsup_teacher', 'unsup_student']) + >>> ) + """ + + def __init__(self, branch_field: List[str], + **branch_pipelines: dict) -> None: + self.branch_field = branch_field + self.branch_pipelines = { + branch: Compose(pipeline) + for branch, pipeline in branch_pipelines.items() + } + + def transform(self, results: dict) -> dict: + """Transform function to apply transforms sequentially. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: + + - 'inputs' (Dict[str, obj:`torch.Tensor`]): The forward data of + models from different branches. + - 'data_sample' (Dict[str,obj:`DetDataSample`]): The annotation + info of the sample from different branches. + """ + + multi_results = {} + for branch in self.branch_field: + multi_results[branch] = {'inputs': None, 'data_samples': None} + for branch, pipeline in self.branch_pipelines.items(): + branch_results = pipeline(copy.deepcopy(results)) + # If one branch pipeline returns None, + # it will sample another data from dataset. + if branch_results is None: + return None + multi_results[branch] = branch_results + + format_results = {} + for branch, results in multi_results.items(): + for key in results.keys(): + if format_results.get(key, None) is None: + format_results[key] = {branch: results[key]} + else: + format_results[key][branch] = results[key] + return format_results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(branch_pipelines={list(self.branch_pipelines.keys())})' + return repr_str + + +@TRANSFORMS.register_module() +class RandomOrder(Compose): + """Shuffle the transform Sequence.""" + + @cache_randomness + def _random_permutation(self): + return np.random.permutation(len(self.transforms)) + + def transform(self, results: Dict) -> Optional[Dict]: + """Transform function to apply transforms in random order. + + Args: + results (dict): A result dict contains the results to transform. + + Returns: + dict or None: Transformed results. + """ + inds = self._random_permutation() + for idx in inds: + t = self.transforms[idx] + results = t(results) + if results is None: + return None + return results + + def __repr__(self): + """Compute the string representation.""" + format_string = self.__class__.__name__ + '(' + for t in self.transforms: + format_string += f'{t.__class__.__name__}, ' + format_string += ')' + return format_string + + +@TRANSFORMS.register_module() +class ProposalBroadcaster(BaseTransform): + """A transform wrapper to apply the wrapped transforms to process both + `gt_bboxes` and `proposals` without adding any codes. It will do the + following steps: + + 1. Scatter the broadcasting targets to a list of inputs of the wrapped + transforms. The type of the list should be list[dict, dict], which + the first is the original inputs, the second is the processing + results that `gt_bboxes` being rewritten by the `proposals`. + 2. Apply ``self.transforms``, with same random parameters, which is + sharing with a context manager. The type of the outputs is a + list[dict, dict]. + 3. Gather the outputs, update the `proposals` in the first item of + the outputs with the `gt_bboxes` in the second . + + Args: + transforms (list, optional): Sequence of transform + object or config dict to be wrapped. Defaults to []. + + Note: The `TransformBroadcaster` in MMCV can achieve the same operation as + `ProposalBroadcaster`, but need to set more complex parameters. + + Examples: + >>> pipeline = [ + >>> dict(type='LoadImageFromFile'), + >>> dict(type='LoadProposals', num_max_proposals=2000), + >>> dict(type='LoadAnnotations', with_bbox=True), + >>> dict( + >>> type='ProposalBroadcaster', + >>> transforms=[ + >>> dict(type='Resize', scale=(1333, 800), + >>> keep_ratio=True), + >>> dict(type='RandomFlip', prob=0.5), + >>> ]), + >>> dict(type='PackDetInputs')] + """ + + def __init__(self, transforms: List[Union[dict, Callable]] = []) -> None: + self.transforms = Compose(transforms) + + def transform(self, results: dict) -> dict: + """Apply wrapped transform functions to process both `gt_bboxes` and + `proposals`. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Updated result dict. + """ + assert results.get('proposals', None) is not None, \ + '`proposals` should be in the results, please delete ' \ + '`ProposalBroadcaster` in your configs, or check whether ' \ + 'you have load proposals successfully.' + + inputs = self._process_input(results) + outputs = self._apply_transforms(inputs) + outputs = self._process_output(outputs) + return outputs + + def _process_input(self, data: dict) -> list: + """Scatter the broadcasting targets to a list of inputs of the wrapped + transforms. + + Args: + data (dict): The original input data. + + Returns: + list[dict]: A list of input data. + """ + cp_data = copy.deepcopy(data) + cp_data['gt_bboxes'] = cp_data['proposals'] + scatters = [data, cp_data] + return scatters + + def _apply_transforms(self, inputs: list) -> list: + """Apply ``self.transforms``. + + Args: + inputs (list[dict, dict]): list of input data. + + Returns: + list[dict]: The output of the wrapped pipeline. + """ + assert len(inputs) == 2 + ctx = cache_random_params + with ctx(self.transforms): + output_scatters = [self.transforms(_input) for _input in inputs] + return output_scatters + + def _process_output(self, output_scatters: list) -> dict: + """Gathering and renaming data items. + + Args: + output_scatters (list[dict, dict]): The output of the wrapped + pipeline. + + Returns: + dict: Updated result dict. + """ + assert isinstance(output_scatters, list) and \ + isinstance(output_scatters[0], dict) and \ + len(output_scatters) == 2 + outputs = output_scatters[0] + outputs['proposals'] = output_scatters[1]['gt_bboxes'] + return outputs diff --git a/mmdetection/mmdet/datasets/utils.py b/mmdetection/mmdet/datasets/utils.py new file mode 100644 index 00000000..d794eb4b --- /dev/null +++ b/mmdetection/mmdet/datasets/utils.py @@ -0,0 +1,48 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +from mmcv.transforms import LoadImageFromFile + +from mmdet.datasets.transforms import LoadAnnotations, LoadPanopticAnnotations +from mmdet.registry import TRANSFORMS + + +def get_loading_pipeline(pipeline): + """Only keep loading image and annotations related configuration. + + Args: + pipeline (list[dict]): Data pipeline configs. + + Returns: + list[dict]: The new pipeline list with only keep + loading image and annotations related configuration. + + Examples: + >>> pipelines = [ + ... dict(type='LoadImageFromFile'), + ... dict(type='LoadAnnotations', with_bbox=True), + ... dict(type='Resize', img_scale=(1333, 800), keep_ratio=True), + ... dict(type='RandomFlip', flip_ratio=0.5), + ... dict(type='Normalize', **img_norm_cfg), + ... dict(type='Pad', size_divisor=32), + ... dict(type='DefaultFormatBundle'), + ... dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']) + ... ] + >>> expected_pipelines = [ + ... dict(type='LoadImageFromFile'), + ... dict(type='LoadAnnotations', with_bbox=True) + ... ] + >>> assert expected_pipelines ==\ + ... get_loading_pipeline(pipelines) + """ + loading_pipeline_cfg = [] + for cfg in pipeline: + obj_cls = TRANSFORMS.get(cfg['type']) + # TODO:use more elegant way to distinguish loading modules + if obj_cls is not None and obj_cls in (LoadImageFromFile, + LoadAnnotations, + LoadPanopticAnnotations): + loading_pipeline_cfg.append(cfg) + assert len(loading_pipeline_cfg) == 2, \ + 'The data pipeline in your config file must include ' \ + 'loading image and annotations related pipeline.' + return loading_pipeline_cfg diff --git a/mmdetection/mmdet/datasets/v3det.py b/mmdetection/mmdet/datasets/v3det.py new file mode 100644 index 00000000..25bfe3bc --- /dev/null +++ b/mmdetection/mmdet/datasets/v3det.py @@ -0,0 +1,32 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path +from typing import Optional + +import mmengine + +from mmdet.registry import DATASETS +from .coco import CocoDataset + + +@DATASETS.register_module() +class V3DetDataset(CocoDataset): + """Dataset for V3Det.""" + + METAINFO = { + 'classes': None, + 'palette': None, + } + + def __init__( + self, + *args, + metainfo: Optional[dict] = None, + data_root: str = '', + label_file='annotations/category_name_13204_v3det_2023_v1.txt', # noqa + **kwargs) -> None: + class_names = tuple( + mmengine.list_from_file(os.path.join(data_root, label_file))) + if metainfo is None: + metainfo = {'classes': class_names} + super().__init__( + *args, data_root=data_root, metainfo=metainfo, **kwargs) diff --git a/mmdetection/mmdet/datasets/voc.py b/mmdetection/mmdet/datasets/voc.py new file mode 100644 index 00000000..65e73f2f --- /dev/null +++ b/mmdetection/mmdet/datasets/voc.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import DATASETS +from .xml_style import XMLDataset + + +@DATASETS.register_module() +class VOCDataset(XMLDataset): + """Dataset for PASCAL VOC.""" + + METAINFO = { + 'classes': + ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', + 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', + 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'), + # palette is a list of color tuples, which is used for visualization. + 'palette': [(106, 0, 228), (119, 11, 32), (165, 42, 42), (0, 0, 192), + (197, 226, 255), (0, 60, 100), (0, 0, 142), (255, 77, 255), + (153, 69, 1), (120, 166, 157), (0, 182, 199), + (0, 226, 252), (182, 182, 255), (0, 0, 230), (220, 20, 60), + (163, 255, 0), (0, 82, 0), (3, 95, 161), (0, 80, 100), + (183, 130, 88)] + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + if 'VOC2007' in self.sub_data_root: + self._metainfo['dataset_type'] = 'VOC2007' + elif 'VOC2012' in self.sub_data_root: + self._metainfo['dataset_type'] = 'VOC2012' + else: + self._metainfo['dataset_type'] = None diff --git a/mmdetection/mmdet/datasets/wider_face.py b/mmdetection/mmdet/datasets/wider_face.py new file mode 100644 index 00000000..62c7fff8 --- /dev/null +++ b/mmdetection/mmdet/datasets/wider_face.py @@ -0,0 +1,90 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import xml.etree.ElementTree as ET + +from mmengine.dist import is_main_process +from mmengine.fileio import get_local_path, list_from_file +from mmengine.utils import ProgressBar + +from mmdet.registry import DATASETS +from mmdet.utils.typing_utils import List, Union +from .xml_style import XMLDataset + + +@DATASETS.register_module() +class WIDERFaceDataset(XMLDataset): + """Reader for the WIDER Face dataset in PASCAL VOC format. + + Conversion scripts can be found in + https://github.com/sovrasov/wider-face-pascal-voc-annotations + """ + METAINFO = {'classes': ('face', ), 'palette': [(0, 255, 0)]} + + def load_data_list(self) -> List[dict]: + """Load annotation from XML style ann_file. + + Returns: + list[dict]: Annotation info from XML file. + """ + assert self._metainfo.get('classes', None) is not None, \ + 'classes in `XMLDataset` can not be None.' + self.cat2label = { + cat: i + for i, cat in enumerate(self._metainfo['classes']) + } + + data_list = [] + img_ids = list_from_file(self.ann_file, backend_args=self.backend_args) + + # loading process takes around 10 mins + if is_main_process(): + prog_bar = ProgressBar(len(img_ids)) + + for img_id in img_ids: + raw_img_info = {} + raw_img_info['img_id'] = img_id + raw_img_info['file_name'] = f'{img_id}.jpg' + parsed_data_info = self.parse_data_info(raw_img_info) + data_list.append(parsed_data_info) + + if is_main_process(): + prog_bar.update() + return data_list + + def parse_data_info(self, img_info: dict) -> Union[dict, List[dict]]: + """Parse raw annotation to target format. + + Args: + img_info (dict): Raw image information, usually it includes + `img_id`, `file_name`, and `xml_path`. + + Returns: + Union[dict, List[dict]]: Parsed annotation. + """ + data_info = {} + img_id = img_info['img_id'] + xml_path = osp.join(self.data_prefix['img'], 'Annotations', + f'{img_id}.xml') + data_info['img_id'] = img_id + data_info['xml_path'] = xml_path + + # deal with xml file + with get_local_path( + xml_path, backend_args=self.backend_args) as local_path: + raw_ann_info = ET.parse(local_path) + root = raw_ann_info.getroot() + size = root.find('size') + width = int(size.find('width').text) + height = int(size.find('height').text) + folder = root.find('folder').text + img_path = osp.join(self.data_prefix['img'], folder, + img_info['file_name']) + data_info['img_path'] = img_path + + data_info['height'] = height + data_info['width'] = width + + # Coordinates are in range [0, width - 1 or height - 1] + data_info['instances'] = self._parse_instance_info( + raw_ann_info, minus_one=False) + return data_info diff --git a/mmdetection/mmdet/datasets/xml_style.py b/mmdetection/mmdet/datasets/xml_style.py new file mode 100644 index 00000000..06045ea0 --- /dev/null +++ b/mmdetection/mmdet/datasets/xml_style.py @@ -0,0 +1,186 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import xml.etree.ElementTree as ET +from typing import List, Optional, Union + +import mmcv +from mmengine.fileio import get, get_local_path, list_from_file + +from mmdet.registry import DATASETS +from .base_det_dataset import BaseDetDataset + + +@DATASETS.register_module() +class XMLDataset(BaseDetDataset): + """XML dataset for detection. + + Args: + img_subdir (str): Subdir where images are stored. Default: JPEGImages. + ann_subdir (str): Subdir where annotations are. Default: Annotations. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + """ + + def __init__(self, + img_subdir: str = 'JPEGImages', + ann_subdir: str = 'Annotations', + **kwargs) -> None: + self.img_subdir = img_subdir + self.ann_subdir = ann_subdir + super().__init__(**kwargs) + + @property + def sub_data_root(self) -> str: + """Return the sub data root.""" + return self.data_prefix.get('sub_data_root', '') + + def load_data_list(self) -> List[dict]: + """Load annotation from XML style ann_file. + + Returns: + list[dict]: Annotation info from XML file. + """ + assert self._metainfo.get('classes', None) is not None, \ + '`classes` in `XMLDataset` can not be None.' + self.cat2label = { + cat: i + for i, cat in enumerate(self._metainfo['classes']) + } + + data_list = [] + img_ids = list_from_file(self.ann_file, backend_args=self.backend_args) + for img_id in img_ids: + file_name = osp.join(self.img_subdir, f'{img_id}.jpg') + xml_path = osp.join(self.sub_data_root, self.ann_subdir, + f'{img_id}.xml') + + raw_img_info = {} + raw_img_info['img_id'] = img_id + raw_img_info['file_name'] = file_name + raw_img_info['xml_path'] = xml_path + + parsed_data_info = self.parse_data_info(raw_img_info) + data_list.append(parsed_data_info) + return data_list + + @property + def bbox_min_size(self) -> Optional[int]: + """Return the minimum size of bounding boxes in the images.""" + if self.filter_cfg is not None: + return self.filter_cfg.get('bbox_min_size', None) + else: + return None + + def parse_data_info(self, img_info: dict) -> Union[dict, List[dict]]: + """Parse raw annotation to target format. + + Args: + img_info (dict): Raw image information, usually it includes + `img_id`, `file_name`, and `xml_path`. + + Returns: + Union[dict, List[dict]]: Parsed annotation. + """ + data_info = {} + img_path = osp.join(self.sub_data_root, img_info['file_name']) + data_info['img_path'] = img_path + data_info['img_id'] = img_info['img_id'] + data_info['xml_path'] = img_info['xml_path'] + + # deal with xml file + with get_local_path( + img_info['xml_path'], + backend_args=self.backend_args) as local_path: + raw_ann_info = ET.parse(local_path) + root = raw_ann_info.getroot() + size = root.find('size') + if size is not None: + width = int(size.find('width').text) + height = int(size.find('height').text) + else: + img_bytes = get(img_path, backend_args=self.backend_args) + img = mmcv.imfrombytes(img_bytes, backend='cv2') + height, width = img.shape[:2] + del img, img_bytes + + data_info['height'] = height + data_info['width'] = width + + data_info['instances'] = self._parse_instance_info( + raw_ann_info, minus_one=True) + + return data_info + + def _parse_instance_info(self, + raw_ann_info: ET, + minus_one: bool = True) -> List[dict]: + """parse instance information. + + Args: + raw_ann_info (ElementTree): ElementTree object. + minus_one (bool): Whether to subtract 1 from the coordinates. + Defaults to True. + + Returns: + List[dict]: List of instances. + """ + instances = [] + for obj in raw_ann_info.findall('object'): + instance = {} + name = obj.find('name').text + if name not in self._metainfo['classes']: + continue + difficult = obj.find('difficult') + difficult = 0 if difficult is None else int(difficult.text) + bnd_box = obj.find('bndbox') + bbox = [ + int(float(bnd_box.find('xmin').text)), + int(float(bnd_box.find('ymin').text)), + int(float(bnd_box.find('xmax').text)), + int(float(bnd_box.find('ymax').text)) + ] + + # VOC needs to subtract 1 from the coordinates + if minus_one: + bbox = [x - 1 for x in bbox] + + ignore = False + if self.bbox_min_size is not None: + assert not self.test_mode + w = bbox[2] - bbox[0] + h = bbox[3] - bbox[1] + if w < self.bbox_min_size or h < self.bbox_min_size: + ignore = True + if difficult or ignore: + instance['ignore_flag'] = 1 + else: + instance['ignore_flag'] = 0 + instance['bbox'] = bbox + instance['bbox_label'] = self.cat2label[name] + instances.append(instance) + return instances + + def filter_data(self) -> List[dict]: + """Filter annotations according to filter_cfg. + + Returns: + List[dict]: Filtered results. + """ + if self.test_mode: + return self.data_list + + filter_empty_gt = self.filter_cfg.get('filter_empty_gt', False) \ + if self.filter_cfg is not None else False + min_size = self.filter_cfg.get('min_size', 0) \ + if self.filter_cfg is not None else 0 + + valid_data_infos = [] + for i, data_info in enumerate(self.data_list): + width = data_info['width'] + height = data_info['height'] + if filter_empty_gt and len(data_info['instances']) == 0: + continue + if min(width, height) >= min_size: + valid_data_infos.append(data_info) + + return valid_data_infos diff --git a/mmdetection/mmdet/datasets/youtube_vis_dataset.py b/mmdetection/mmdet/datasets/youtube_vis_dataset.py new file mode 100644 index 00000000..38c3d390 --- /dev/null +++ b/mmdetection/mmdet/datasets/youtube_vis_dataset.py @@ -0,0 +1,52 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import DATASETS +from .base_video_dataset import BaseVideoDataset + + +@DATASETS.register_module() +class YouTubeVISDataset(BaseVideoDataset): + """YouTube VIS dataset for video instance segmentation. + + Args: + dataset_version (str): Select dataset year version. + """ + + def __init__(self, dataset_version: str, *args, **kwargs): + self.set_dataset_classes(dataset_version) + super().__init__(*args, **kwargs) + + @classmethod + def set_dataset_classes(cls, dataset_version: str) -> None: + """Pass the category of the corresponding year to metainfo. + + Args: + dataset_version (str): Select dataset year version. + """ + classes_2019_version = ('person', 'giant_panda', 'lizard', 'parrot', + 'skateboard', 'sedan', 'ape', 'dog', 'snake', + 'monkey', 'hand', 'rabbit', 'duck', 'cat', + 'cow', 'fish', 'train', 'horse', 'turtle', + 'bear', 'motorbike', 'giraffe', 'leopard', + 'fox', 'deer', 'owl', 'surfboard', 'airplane', + 'truck', 'zebra', 'tiger', 'elephant', + 'snowboard', 'boat', 'shark', 'mouse', 'frog', + 'eagle', 'earless_seal', 'tennis_racket') + + classes_2021_version = ('airplane', 'bear', 'bird', 'boat', 'car', + 'cat', 'cow', 'deer', 'dog', 'duck', + 'earless_seal', 'elephant', 'fish', + 'flying_disc', 'fox', 'frog', 'giant_panda', + 'giraffe', 'horse', 'leopard', 'lizard', + 'monkey', 'motorbike', 'mouse', 'parrot', + 'person', 'rabbit', 'shark', 'skateboard', + 'snake', 'snowboard', 'squirrel', 'surfboard', + 'tennis_racket', 'tiger', 'train', 'truck', + 'turtle', 'whale', 'zebra') + + if dataset_version == '2019': + cls.METAINFO = dict(classes=classes_2019_version) + elif dataset_version == '2021': + cls.METAINFO = dict(classes=classes_2021_version) + else: + raise NotImplementedError('Not supported YouTubeVIS dataset' + f'version: {dataset_version}') diff --git a/mmdetection/mmdet/engine/__init__.py b/mmdetection/mmdet/engine/__init__.py new file mode 100644 index 00000000..c91ace6f --- /dev/null +++ b/mmdetection/mmdet/engine/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .hooks import * # noqa: F401, F403 +from .optimizers import * # noqa: F401, F403 +from .runner import * # noqa: F401, F403 +from .schedulers import * # noqa: F401, F403 diff --git a/mmdetection/mmdet/engine/hooks/__init__.py b/mmdetection/mmdet/engine/hooks/__init__.py new file mode 100644 index 00000000..889fa557 --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/__init__.py @@ -0,0 +1,21 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .checkloss_hook import CheckInvalidLossHook +from .mean_teacher_hook import MeanTeacherHook +from .memory_profiler_hook import MemoryProfilerHook +from .num_class_check_hook import NumClassCheckHook +from .pipeline_switch_hook import PipelineSwitchHook +from .set_epoch_info_hook import SetEpochInfoHook +from .sync_norm_hook import SyncNormHook +from .utils import trigger_visualization_hook +from .visualization_hook import (DetVisualizationHook, + GroundingVisualizationHook, + TrackVisualizationHook) +from .yolox_mode_switch_hook import YOLOXModeSwitchHook + +__all__ = [ + 'YOLOXModeSwitchHook', 'SyncNormHook', 'CheckInvalidLossHook', + 'SetEpochInfoHook', 'MemoryProfilerHook', 'DetVisualizationHook', + 'NumClassCheckHook', 'MeanTeacherHook', 'trigger_visualization_hook', + 'PipelineSwitchHook', 'TrackVisualizationHook', + 'GroundingVisualizationHook' +] diff --git a/mmdetection/mmdet/engine/hooks/checkloss_hook.py b/mmdetection/mmdet/engine/hooks/checkloss_hook.py new file mode 100644 index 00000000..3ebfcd5d --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/checkloss_hook.py @@ -0,0 +1,42 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +from mmengine.hooks import Hook +from mmengine.runner import Runner + +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class CheckInvalidLossHook(Hook): + """Check invalid loss hook. + + This hook will regularly check whether the loss is valid + during training. + + Args: + interval (int): Checking interval (every k iterations). + Default: 50. + """ + + def __init__(self, interval: int = 50) -> None: + self.interval = interval + + def after_train_iter(self, + runner: Runner, + batch_idx: int, + data_batch: Optional[dict] = None, + outputs: Optional[dict] = None) -> None: + """Regularly check whether the loss is valid every n iterations. + + Args: + runner (:obj:`Runner`): The runner of the training process. + batch_idx (int): The index of the current batch in the train loop. + data_batch (dict, Optional): Data from dataloader. + Defaults to None. + outputs (dict, Optional): Outputs from model. Defaults to None. + """ + if self.every_n_train_iters(runner, self.interval): + assert torch.isfinite(outputs['loss']), \ + runner.logger.info('loss become infinite or NaN!') diff --git a/mmdetection/mmdet/engine/hooks/mean_teacher_hook.py b/mmdetection/mmdet/engine/hooks/mean_teacher_hook.py new file mode 100644 index 00000000..b924c0a5 --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/mean_teacher_hook.py @@ -0,0 +1,87 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch.nn as nn +from mmengine.hooks import Hook +from mmengine.model import is_model_wrapper +from mmengine.runner import Runner + +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class MeanTeacherHook(Hook): + """Mean Teacher Hook. + + Mean Teacher is an efficient semi-supervised learning method in + `Mean Teacher `_. + This method requires two models with exactly the same structure, + as the student model and the teacher model, respectively. + The student model updates the parameters through gradient descent, + and the teacher model updates the parameters through + exponential moving average of the student model. + Compared with the student model, the teacher model + is smoother and accumulates more knowledge. + + Args: + momentum (float): The momentum used for updating teacher's parameter. + Teacher's parameter are updated with the formula: + `teacher = (1-momentum) * teacher + momentum * student`. + Defaults to 0.001. + interval (int): Update teacher's parameter every interval iteration. + Defaults to 1. + skip_buffers (bool): Whether to skip the model buffers, such as + batchnorm running stats (running_mean, running_var), it does not + perform the ema operation. Default to True. + """ + + def __init__(self, + momentum: float = 0.001, + interval: int = 1, + skip_buffer=True) -> None: + assert 0 < momentum < 1 + self.momentum = momentum + self.interval = interval + self.skip_buffers = skip_buffer + + def before_train(self, runner: Runner) -> None: + """To check that teacher model and student model exist.""" + model = runner.model + if is_model_wrapper(model): + model = model.module + assert hasattr(model, 'teacher') + assert hasattr(model, 'student') + # only do it at initial stage + if runner.iter == 0: + self.momentum_update(model, 1) + + def after_train_iter(self, + runner: Runner, + batch_idx: int, + data_batch: Optional[dict] = None, + outputs: Optional[dict] = None) -> None: + """Update teacher's parameter every self.interval iterations.""" + if (runner.iter + 1) % self.interval != 0: + return + model = runner.model + if is_model_wrapper(model): + model = model.module + self.momentum_update(model, self.momentum) + + def momentum_update(self, model: nn.Module, momentum: float) -> None: + """Compute the moving average of the parameters using exponential + moving average.""" + if self.skip_buffers: + for (src_name, src_parm), (dst_name, dst_parm) in zip( + model.student.named_parameters(), + model.teacher.named_parameters()): + dst_parm.data.mul_(1 - momentum).add_( + src_parm.data, alpha=momentum) + else: + for (src_parm, + dst_parm) in zip(model.student.state_dict().values(), + model.teacher.state_dict().values()): + # exclude num_tracking + if dst_parm.dtype.is_floating_point: + dst_parm.data.mul_(1 - momentum).add_( + src_parm.data, alpha=momentum) diff --git a/mmdetection/mmdet/engine/hooks/memory_profiler_hook.py b/mmdetection/mmdet/engine/hooks/memory_profiler_hook.py new file mode 100644 index 00000000..3dcdcae0 --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/memory_profiler_hook.py @@ -0,0 +1,121 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence + +from mmengine.hooks import Hook +from mmengine.runner import Runner + +from mmdet.registry import HOOKS +from mmdet.structures import DetDataSample + + +@HOOKS.register_module() +class MemoryProfilerHook(Hook): + """Memory profiler hook recording memory information including virtual + memory, swap memory, and the memory of the current process. + + Args: + interval (int): Checking interval (every k iterations). + Default: 50. + """ + + def __init__(self, interval: int = 50) -> None: + try: + from psutil import swap_memory, virtual_memory + self._swap_memory = swap_memory + self._virtual_memory = virtual_memory + except ImportError: + raise ImportError('psutil is not installed, please install it by: ' + 'pip install psutil') + + try: + from memory_profiler import memory_usage + self._memory_usage = memory_usage + except ImportError: + raise ImportError( + 'memory_profiler is not installed, please install it by: ' + 'pip install memory_profiler') + + self.interval = interval + + def _record_memory_information(self, runner: Runner) -> None: + """Regularly record memory information. + + Args: + runner (:obj:`Runner`): The runner of the training or evaluation + process. + """ + # in Byte + virtual_memory = self._virtual_memory() + swap_memory = self._swap_memory() + # in MB + process_memory = self._memory_usage()[0] + factor = 1024 * 1024 + runner.logger.info( + 'Memory information ' + 'available_memory: ' + f'{round(virtual_memory.available / factor)} MB, ' + 'used_memory: ' + f'{round(virtual_memory.used / factor)} MB, ' + f'memory_utilization: {virtual_memory.percent} %, ' + 'available_swap_memory: ' + f'{round((swap_memory.total - swap_memory.used) / factor)}' + ' MB, ' + f'used_swap_memory: {round(swap_memory.used / factor)} MB, ' + f'swap_memory_utilization: {swap_memory.percent} %, ' + 'current_process_memory: ' + f'{round(process_memory)} MB') + + def after_train_iter(self, + runner: Runner, + batch_idx: int, + data_batch: Optional[dict] = None, + outputs: Optional[dict] = None) -> None: + """Regularly record memory information. + + Args: + runner (:obj:`Runner`): The runner of the training process. + batch_idx (int): The index of the current batch in the train loop. + data_batch (dict, optional): Data from dataloader. + Defaults to None. + outputs (dict, optional): Outputs from model. Defaults to None. + """ + if self.every_n_inner_iters(batch_idx, self.interval): + self._record_memory_information(runner) + + def after_val_iter( + self, + runner: Runner, + batch_idx: int, + data_batch: Optional[dict] = None, + outputs: Optional[Sequence[DetDataSample]] = None) -> None: + """Regularly record memory information. + + Args: + runner (:obj:`Runner`): The runner of the validation process. + batch_idx (int): The index of the current batch in the val loop. + data_batch (dict, optional): Data from dataloader. + Defaults to None. + outputs (Sequence[:obj:`DetDataSample`], optional): + Outputs from model. Defaults to None. + """ + if self.every_n_inner_iters(batch_idx, self.interval): + self._record_memory_information(runner) + + def after_test_iter( + self, + runner: Runner, + batch_idx: int, + data_batch: Optional[dict] = None, + outputs: Optional[Sequence[DetDataSample]] = None) -> None: + """Regularly record memory information. + + Args: + runner (:obj:`Runner`): The runner of the testing process. + batch_idx (int): The index of the current batch in the test loop. + data_batch (dict, optional): Data from dataloader. + Defaults to None. + outputs (Sequence[:obj:`DetDataSample`], optional): + Outputs from model. Defaults to None. + """ + if self.every_n_inner_iters(batch_idx, self.interval): + self._record_memory_information(runner) diff --git a/mmdetection/mmdet/engine/hooks/num_class_check_hook.py b/mmdetection/mmdet/engine/hooks/num_class_check_hook.py new file mode 100644 index 00000000..6588473a --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/num_class_check_hook.py @@ -0,0 +1,68 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.cnn import VGG +from mmengine.hooks import Hook +from mmengine.runner import Runner + +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class NumClassCheckHook(Hook): + """Check whether the `num_classes` in head matches the length of `classes` + in `dataset.metainfo`.""" + + def _check_head(self, runner: Runner, mode: str) -> None: + """Check whether the `num_classes` in head matches the length of + `classes` in `dataset.metainfo`. + + Args: + runner (:obj:`Runner`): The runner of the training or evaluation + process. + """ + assert mode in ['train', 'val'] + model = runner.model + dataset = runner.train_dataloader.dataset if mode == 'train' else \ + runner.val_dataloader.dataset + if dataset.metainfo.get('classes', None) is None: + runner.logger.warning( + f'Please set `classes` ' + f'in the {dataset.__class__.__name__} `metainfo` and' + f'check if it is consistent with the `num_classes` ' + f'of head') + else: + classes = dataset.metainfo['classes'] + assert type(classes) is not str, \ + (f'`classes` in {dataset.__class__.__name__}' + f'should be a tuple of str.' + f'Add comma if number of classes is 1 as ' + f'classes = ({classes},)') + from mmdet.models.roi_heads.mask_heads import FusedSemanticHead + for name, module in model.named_modules(): + if hasattr(module, 'num_classes') and not name.endswith( + 'rpn_head') and not isinstance( + module, (VGG, FusedSemanticHead)): + assert module.num_classes == len(classes), \ + (f'The `num_classes` ({module.num_classes}) in ' + f'{module.__class__.__name__} of ' + f'{model.__class__.__name__} does not matches ' + f'the length of `classes` ' + f'{len(classes)}) in ' + f'{dataset.__class__.__name__}') + + def before_train_epoch(self, runner: Runner) -> None: + """Check whether the training dataset is compatible with head. + + Args: + runner (:obj:`Runner`): The runner of the training or evaluation + process. + """ + self._check_head(runner, 'train') + + def before_val_epoch(self, runner: Runner) -> None: + """Check whether the dataset in val epoch is compatible with head. + + Args: + runner (:obj:`Runner`): The runner of the training or evaluation + process. + """ + self._check_head(runner, 'val') diff --git a/mmdetection/mmdet/engine/hooks/pipeline_switch_hook.py b/mmdetection/mmdet/engine/hooks/pipeline_switch_hook.py new file mode 100644 index 00000000..a5abd897 --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/pipeline_switch_hook.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms import Compose +from mmengine.hooks import Hook + +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class PipelineSwitchHook(Hook): + """Switch data pipeline at switch_epoch. + + Args: + switch_epoch (int): switch pipeline at this epoch. + switch_pipeline (list[dict]): the pipeline to switch to. + """ + + def __init__(self, switch_epoch, switch_pipeline): + self.switch_epoch = switch_epoch + self.switch_pipeline = switch_pipeline + self._restart_dataloader = False + self._has_switched = False + + def before_train_epoch(self, runner): + """switch pipeline.""" + epoch = runner.epoch + train_loader = runner.train_dataloader + if epoch >= self.switch_epoch and not self._has_switched: + runner.logger.info('Switch pipeline now!') + # The dataset pipeline cannot be updated when persistent_workers + # is True, so we need to force the dataloader's multi-process + # restart. This is a very hacky approach. + train_loader.dataset.pipeline = Compose(self.switch_pipeline) + if hasattr(train_loader, 'persistent_workers' + ) and train_loader.persistent_workers is True: + train_loader._DataLoader__initialized = False + train_loader._iterator = None + self._restart_dataloader = True + self._has_switched = True + else: + # Once the restart is complete, we need to restore + # the initialization flag. + if self._restart_dataloader: + train_loader._DataLoader__initialized = True diff --git a/mmdetection/mmdet/engine/hooks/set_epoch_info_hook.py b/mmdetection/mmdet/engine/hooks/set_epoch_info_hook.py new file mode 100644 index 00000000..183f3167 --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/set_epoch_info_hook.py @@ -0,0 +1,17 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import Hook +from mmengine.model.wrappers import is_model_wrapper + +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class SetEpochInfoHook(Hook): + """Set runner's epoch information to the model.""" + + def before_train_epoch(self, runner): + epoch = runner.epoch + model = runner.model + if is_model_wrapper(model): + model = model.module + model.set_epoch(epoch) diff --git a/mmdetection/mmdet/engine/hooks/sync_norm_hook.py b/mmdetection/mmdet/engine/hooks/sync_norm_hook.py new file mode 100644 index 00000000..a1734380 --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/sync_norm_hook.py @@ -0,0 +1,37 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections import OrderedDict + +from mmengine.dist import get_dist_info +from mmengine.hooks import Hook +from torch import nn + +from mmdet.registry import HOOKS +from mmdet.utils import all_reduce_dict + + +def get_norm_states(module: nn.Module) -> OrderedDict: + """Get the state_dict of batch norms in the module.""" + async_norm_states = OrderedDict() + for name, child in module.named_modules(): + if isinstance(child, nn.modules.batchnorm._NormBase): + for k, v in child.state_dict().items(): + async_norm_states['.'.join([name, k])] = v + return async_norm_states + + +@HOOKS.register_module() +class SyncNormHook(Hook): + """Synchronize Norm states before validation, currently used in YOLOX.""" + + def before_val_epoch(self, runner): + """Synchronizing norm.""" + module = runner.model + _, world_size = get_dist_info() + if world_size == 1: + return + norm_states = get_norm_states(module) + if len(norm_states) == 0: + return + # TODO: use `all_reduce_dict` in mmengine + norm_states = all_reduce_dict(norm_states, op='mean') + module.load_state_dict(norm_states, strict=False) diff --git a/mmdetection/mmdet/engine/hooks/utils.py b/mmdetection/mmdet/engine/hooks/utils.py new file mode 100644 index 00000000..d267cfe7 --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/utils.py @@ -0,0 +1,19 @@ +# Copyright (c) OpenMMLab. All rights reserved. +def trigger_visualization_hook(cfg, args): + default_hooks = cfg.default_hooks + if 'visualization' in default_hooks: + visualization_hook = default_hooks['visualization'] + # Turn on visualization + visualization_hook['draw'] = True + if args.show: + visualization_hook['show'] = True + visualization_hook['wait_time'] = args.wait_time + if args.show_dir: + visualization_hook['test_out_dir'] = args.show_dir + else: + raise RuntimeError( + 'VisualizationHook must be included in default_hooks.' + 'refer to usage ' + '"visualization=dict(type=\'VisualizationHook\')"') + + return cfg diff --git a/mmdetection/mmdet/engine/hooks/visualization_hook.py b/mmdetection/mmdet/engine/hooks/visualization_hook.py new file mode 100644 index 00000000..3408186b --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/visualization_hook.py @@ -0,0 +1,515 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import warnings +from typing import Optional, Sequence + +import mmcv +import numpy as np +from mmengine.fileio import get +from mmengine.hooks import Hook +from mmengine.runner import Runner +from mmengine.utils import mkdir_or_exist +from mmengine.visualization import Visualizer + +from mmdet.datasets.samplers import TrackImgSampler +from mmdet.registry import HOOKS +from mmdet.structures import DetDataSample, TrackDataSample +from mmdet.structures.bbox import BaseBoxes +from mmdet.visualization.palette import _get_adaptive_scales + + +@HOOKS.register_module() +class DetVisualizationHook(Hook): + """Detection Visualization Hook. Used to visualize validation and testing + process prediction results. + + In the testing phase: + + 1. If ``show`` is True, it means that only the prediction results are + visualized without storing data, so ``vis_backends`` needs to + be excluded. + 2. If ``test_out_dir`` is specified, it means that the prediction results + need to be saved to ``test_out_dir``. In order to avoid vis_backends + also storing data, so ``vis_backends`` needs to be excluded. + 3. ``vis_backends`` takes effect if the user does not specify ``show`` + and `test_out_dir``. You can set ``vis_backends`` to WandbVisBackend or + TensorboardVisBackend to store the prediction result in Wandb or + Tensorboard. + + Args: + draw (bool): whether to draw prediction results. If it is False, + it means that no drawing will be done. Defaults to False. + interval (int): The interval of visualization. Defaults to 50. + score_thr (float): The threshold to visualize the bboxes + and masks. Defaults to 0.3. + show (bool): Whether to display the drawn image. Default to False. + wait_time (float): The interval of show (s). Defaults to 0. + test_out_dir (str, optional): directory where painted images + will be saved in testing process. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + """ + + def __init__(self, + draw: bool = False, + interval: int = 50, + score_thr: float = 0.3, + show: bool = False, + wait_time: float = 0., + test_out_dir: Optional[str] = None, + backend_args: dict = None): + self._visualizer: Visualizer = Visualizer.get_current_instance() + self.interval = interval + self.score_thr = score_thr + self.show = show + if self.show: + # No need to think about vis backends. + self._visualizer._vis_backends = {} + warnings.warn('The show is True, it means that only ' + 'the prediction results are visualized ' + 'without storing data, so vis_backends ' + 'needs to be excluded.') + + self.wait_time = wait_time + self.backend_args = backend_args + self.draw = draw + self.test_out_dir = test_out_dir + self._test_index = 0 + + def after_val_iter(self, runner: Runner, batch_idx: int, data_batch: dict, + outputs: Sequence[DetDataSample]) -> None: + """Run after every ``self.interval`` validation iterations. + + Args: + runner (:obj:`Runner`): The runner of the validation process. + batch_idx (int): The index of the current batch in the val loop. + data_batch (dict): Data from dataloader. + outputs (Sequence[:obj:`DetDataSample`]]): A batch of data samples + that contain annotations and predictions. + """ + if self.draw is False: + return + + # There is no guarantee that the same batch of images + # is visualized for each evaluation. + total_curr_iter = runner.iter + batch_idx + + # Visualize only the first data + img_path = outputs[0].img_path + img_bytes = get(img_path, backend_args=self.backend_args) + img = mmcv.imfrombytes(img_bytes, channel_order='rgb') + + if total_curr_iter % self.interval == 0: + self._visualizer.add_datasample( + osp.basename(img_path) if self.show else 'val_img', + img, + data_sample=outputs[0], + show=self.show, + wait_time=self.wait_time, + pred_score_thr=self.score_thr, + step=total_curr_iter) + + def after_test_iter(self, runner: Runner, batch_idx: int, data_batch: dict, + outputs: Sequence[DetDataSample]) -> None: + """Run after every testing iterations. + + Args: + runner (:obj:`Runner`): The runner of the testing process. + batch_idx (int): The index of the current batch in the val loop. + data_batch (dict): Data from dataloader. + outputs (Sequence[:obj:`DetDataSample`]): A batch of data samples + that contain annotations and predictions. + """ + if self.draw is False: + return + + if self.test_out_dir is not None: + self.test_out_dir = osp.join(runner.work_dir, runner.timestamp, + self.test_out_dir) + mkdir_or_exist(self.test_out_dir) + + for data_sample in outputs: + self._test_index += 1 + + img_path = data_sample.img_path + img_bytes = get(img_path, backend_args=self.backend_args) + img = mmcv.imfrombytes(img_bytes, channel_order='rgb') + + out_file = None + if self.test_out_dir is not None: + out_file = osp.basename(img_path) + out_file = osp.join(self.test_out_dir, out_file) + + self._visualizer.add_datasample( + osp.basename(img_path) if self.show else 'test_img', + img, + data_sample=data_sample, + show=self.show, + wait_time=self.wait_time, + pred_score_thr=self.score_thr, + out_file=out_file, + step=self._test_index) + + +@HOOKS.register_module() +class TrackVisualizationHook(Hook): + """Tracking Visualization Hook. Used to visualize validation and testing + process prediction results. + + In the testing phase: + + 1. If ``show`` is True, it means that only the prediction results are + visualized without storing data, so ``vis_backends`` needs to + be excluded. + 2. If ``test_out_dir`` is specified, it means that the prediction results + need to be saved to ``test_out_dir``. In order to avoid vis_backends + also storing data, so ``vis_backends`` needs to be excluded. + 3. ``vis_backends`` takes effect if the user does not specify ``show`` + and `test_out_dir``. You can set ``vis_backends`` to WandbVisBackend or + TensorboardVisBackend to store the prediction result in Wandb or + Tensorboard. + + Args: + draw (bool): whether to draw prediction results. If it is False, + it means that no drawing will be done. Defaults to False. + frame_interval (int): The interval of visualization. Defaults to 30. + score_thr (float): The threshold to visualize the bboxes + and masks. Defaults to 0.3. + show (bool): Whether to display the drawn image. Default to False. + wait_time (float): The interval of show (s). Defaults to 0. + test_out_dir (str, optional): directory where painted images + will be saved in testing process. + backend_args (dict): Arguments to instantiate a file client. + Defaults to ``None``. + """ + + def __init__(self, + draw: bool = False, + frame_interval: int = 30, + score_thr: float = 0.3, + show: bool = False, + wait_time: float = 0., + test_out_dir: Optional[str] = None, + backend_args: dict = None) -> None: + self._visualizer: Visualizer = Visualizer.get_current_instance() + self.frame_interval = frame_interval + self.score_thr = score_thr + self.show = show + if self.show: + # No need to think about vis backends. + self._visualizer._vis_backends = {} + warnings.warn('The show is True, it means that only ' + 'the prediction results are visualized ' + 'without storing data, so vis_backends ' + 'needs to be excluded.') + + self.wait_time = wait_time + self.backend_args = backend_args + self.draw = draw + self.test_out_dir = test_out_dir + self.image_idx = 0 + + def after_val_iter(self, runner: Runner, batch_idx: int, data_batch: dict, + outputs: Sequence[TrackDataSample]) -> None: + """Run after every ``self.interval`` validation iteration. + + Args: + runner (:obj:`Runner`): The runner of the validation process. + batch_idx (int): The index of the current batch in the val loop. + data_batch (dict): Data from dataloader. + outputs (Sequence[:obj:`TrackDataSample`]): Outputs from model. + """ + if self.draw is False: + return + + assert len(outputs) == 1, \ + 'only batch_size=1 is supported while validating.' + + sampler = runner.val_dataloader.sampler + if isinstance(sampler, TrackImgSampler): + if self.every_n_inner_iters(batch_idx, self.frame_interval): + total_curr_iter = runner.iter + batch_idx + track_data_sample = outputs[0] + self.visualize_single_image(track_data_sample[0], + total_curr_iter) + else: + # video visualization DefaultSampler + if self.every_n_inner_iters(batch_idx, 1): + track_data_sample = outputs[0] + video_length = len(track_data_sample) + + for frame_id in range(video_length): + if frame_id % self.frame_interval == 0: + total_curr_iter = runner.iter + self.image_idx + \ + frame_id + img_data_sample = track_data_sample[frame_id] + self.visualize_single_image(img_data_sample, + total_curr_iter) + self.image_idx = self.image_idx + video_length + + def after_test_iter(self, runner: Runner, batch_idx: int, data_batch: dict, + outputs: Sequence[TrackDataSample]) -> None: + """Run after every testing iteration. + + Args: + runner (:obj:`Runner`): The runner of the testing process. + batch_idx (int): The index of the current batch in the test loop. + data_batch (dict): Data from dataloader. + outputs (Sequence[:obj:`TrackDataSample`]): Outputs from model. + """ + if self.draw is False: + return + + assert len(outputs) == 1, \ + 'only batch_size=1 is supported while testing.' + + if self.test_out_dir is not None: + self.test_out_dir = osp.join(runner.work_dir, runner.timestamp, + self.test_out_dir) + mkdir_or_exist(self.test_out_dir) + + sampler = runner.test_dataloader.sampler + if isinstance(sampler, TrackImgSampler): + if self.every_n_inner_iters(batch_idx, self.frame_interval): + track_data_sample = outputs[0] + self.visualize_single_image(track_data_sample[0], batch_idx) + else: + # video visualization DefaultSampler + if self.every_n_inner_iters(batch_idx, 1): + track_data_sample = outputs[0] + video_length = len(track_data_sample) + + for frame_id in range(video_length): + if frame_id % self.frame_interval == 0: + img_data_sample = track_data_sample[frame_id] + self.visualize_single_image(img_data_sample, + self.image_idx + frame_id) + self.image_idx = self.image_idx + video_length + + def visualize_single_image(self, img_data_sample: DetDataSample, + step: int) -> None: + """ + Args: + img_data_sample (DetDataSample): single image output. + step (int): The index of the current image. + """ + img_path = img_data_sample.img_path + img_bytes = get(img_path, backend_args=self.backend_args) + img = mmcv.imfrombytes(img_bytes, channel_order='rgb') + + out_file = None + if self.test_out_dir is not None: + video_name = img_path.split('/')[-3] + mkdir_or_exist(osp.join(self.test_out_dir, video_name)) + out_file = osp.join(self.test_out_dir, video_name, + osp.basename(img_path)) + + self._visualizer.add_datasample( + osp.basename(img_path) if self.show else 'test_img', + img, + data_sample=img_data_sample, + show=self.show, + wait_time=self.wait_time, + pred_score_thr=self.score_thr, + out_file=out_file, + step=step) + + +def draw_all_character(visualizer, characters, w): + start_index = 2 + y_index = 5 + for char in characters: + if isinstance(char, str): + visualizer.draw_texts( + str(char), + positions=np.array([start_index, y_index]), + colors=(0, 0, 0), + font_families='monospace') + start_index += len(char) * 8 + else: + visualizer.draw_texts( + str(char[0]), + positions=np.array([start_index, y_index]), + colors=char[1], + font_families='monospace') + start_index += len(char[0]) * 8 + + if start_index > w - 10: + start_index = 2 + y_index += 15 + + drawn_text = visualizer.get_image() + return drawn_text + + +@HOOKS.register_module() +class GroundingVisualizationHook(DetVisualizationHook): + + def after_test_iter(self, runner: Runner, batch_idx: int, data_batch: dict, + outputs: Sequence[DetDataSample]) -> None: + """Run after every testing iterations. + + Args: + runner (:obj:`Runner`): The runner of the testing process. + batch_idx (int): The index of the current batch in the val loop. + data_batch (dict): Data from dataloader. + outputs (Sequence[:obj:`DetDataSample`]): A batch of data samples + that contain annotations and predictions. + """ + if self.draw is False: + return + + if self.test_out_dir is not None: + self.test_out_dir = osp.join(runner.work_dir, runner.timestamp, + self.test_out_dir) + mkdir_or_exist(self.test_out_dir) + + for data_sample in outputs: + data_sample = data_sample.cpu() + + self._test_index += 1 + + img_path = data_sample.img_path + img_bytes = get(img_path, backend_args=self.backend_args) + img = mmcv.imfrombytes(img_bytes, channel_order='rgb') + + out_file = None + if self.test_out_dir is not None: + out_file = osp.basename(img_path) + out_file = osp.join(self.test_out_dir, out_file) + + text = data_sample.text + if isinstance(text, str): # VG + gt_instances = data_sample.gt_instances + tokens_positive = data_sample.tokens_positive + if 'phrase_ids' in data_sample: + # flickr30k + gt_labels = data_sample.phrase_ids + else: + gt_labels = gt_instances.labels + gt_bboxes = gt_instances.get('bboxes', None) + if gt_bboxes is not None and isinstance(gt_bboxes, BaseBoxes): + gt_instances.bboxes = gt_bboxes.tensor + print(gt_labels, tokens_positive, gt_bboxes, img_path) + pred_instances = data_sample.pred_instances + pred_instances = pred_instances[ + pred_instances.scores > self.score_thr] + pred_labels = pred_instances.labels + pred_bboxes = pred_instances.bboxes + pred_scores = pred_instances.scores + + max_label = 0 + if len(gt_labels) > 0: + max_label = max(gt_labels) + if len(pred_labels) > 0: + max_label = max(max(pred_labels), max_label) + + max_label = int(max(max_label, 0)) + palette = np.random.randint(0, 256, size=(max_label + 1, 3)) + bbox_palette = [tuple(c) for c in palette] + # bbox_palette = get_palette('random', max_label + 1) + if len(gt_labels) >= len(pred_labels): + colors = [bbox_palette[label] for label in gt_labels] + else: + colors = [bbox_palette[label] for label in pred_labels] + + self._visualizer.set_image(img) + + for label, bbox, color in zip(gt_labels, gt_bboxes, colors): + self._visualizer.draw_bboxes( + bbox, edge_colors=color, face_colors=color, alpha=0.3) + self._visualizer.draw_bboxes( + bbox, edge_colors=color, alpha=1) + + drawn_img = self._visualizer.get_image() + + new_image = np.ones( + (100, img.shape[1], 3), dtype=np.uint8) * 255 + self._visualizer.set_image(new_image) + + if tokens_positive == -1: # REC + gt_tokens_positive = [[]] + else: # Phrase Grounding + gt_tokens_positive = [ + tokens_positive[label] for label in gt_labels + ] + split_by_character = [char for char in text] + characters = [] + start_index = 0 + end_index = 0 + for w in split_by_character: + end_index += len(w) + is_find = False + for i, positive in enumerate(gt_tokens_positive): + for p in positive: + if start_index >= p[0] and end_index <= p[1]: + characters.append([w, colors[i]]) + is_find = True + break + if is_find: + break + if not is_find: + characters.append([w, (0, 0, 0)]) + start_index = end_index + + drawn_text = draw_all_character(self._visualizer, characters, + img.shape[1]) + drawn_gt_img = np.concatenate((drawn_img, drawn_text), axis=0) + + self._visualizer.set_image(img) + + for label, bbox, color in zip(pred_labels, pred_bboxes, + colors): + self._visualizer.draw_bboxes( + bbox, edge_colors=color, face_colors=color, alpha=0.3) + self._visualizer.draw_bboxes( + bbox, edge_colors=color, alpha=1) + print(pred_labels, pred_bboxes, pred_scores, colors) + areas = (pred_bboxes[:, 3] - pred_bboxes[:, 1]) * ( + pred_bboxes[:, 2] - pred_bboxes[:, 0]) + scales = _get_adaptive_scales(areas) + score = [str(round(s.item(), 2)) for s in pred_scores] + font_sizes = [int(13 * scales[i]) for i in range(len(scales))] + self._visualizer.draw_texts( + score, + pred_bboxes[:, :2].int(), + colors=(255, 255, 255), + font_sizes=font_sizes, + bboxes=[{ + 'facecolor': 'black', + 'alpha': 0.8, + 'pad': 0.7, + 'edgecolor': 'none' + }] * len(pred_bboxes)) + + drawn_img = self._visualizer.get_image() + + new_image = np.ones( + (100, img.shape[1], 3), dtype=np.uint8) * 255 + self._visualizer.set_image(new_image) + drawn_text = draw_all_character(self._visualizer, characters, + img.shape[1]) + drawn_pred_img = np.concatenate((drawn_img, drawn_text), + axis=0) + drawn_img = np.concatenate((drawn_gt_img, drawn_pred_img), + axis=1) + + if self.show: + self._visualizer.show( + drawn_img, + win_name=osp.basename(img_path), + wait_time=self.wait_time) + if out_file is not None: + mmcv.imwrite(drawn_img[..., ::-1], out_file) + else: + self.add_image('test_img', drawn_img, self._test_index) + else: # OD + self._visualizer.add_datasample( + osp.basename(img_path) if self.show else 'test_img', + img, + data_sample=data_sample, + show=self.show, + wait_time=self.wait_time, + pred_score_thr=self.score_thr, + out_file=out_file, + step=self._test_index) diff --git a/mmdetection/mmdet/engine/hooks/yolox_mode_switch_hook.py b/mmdetection/mmdet/engine/hooks/yolox_mode_switch_hook.py new file mode 100644 index 00000000..05a2c690 --- /dev/null +++ b/mmdetection/mmdet/engine/hooks/yolox_mode_switch_hook.py @@ -0,0 +1,66 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Sequence + +from mmengine.hooks import Hook +from mmengine.model import is_model_wrapper + +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class YOLOXModeSwitchHook(Hook): + """Switch the mode of YOLOX during training. + + This hook turns off the mosaic and mixup data augmentation and switches + to use L1 loss in bbox_head. + + Args: + num_last_epochs (int): The number of latter epochs in the end of the + training to close the data augmentation and switch to L1 loss. + Defaults to 15. + skip_type_keys (Sequence[str], optional): Sequence of type string to be + skip pipeline. Defaults to ('Mosaic', 'RandomAffine', 'MixUp'). + """ + + def __init__( + self, + num_last_epochs: int = 15, + skip_type_keys: Sequence[str] = ('Mosaic', 'RandomAffine', 'MixUp') + ) -> None: + self.num_last_epochs = num_last_epochs + self.skip_type_keys = skip_type_keys + self._restart_dataloader = False + self._has_switched = False + + def before_train_epoch(self, runner) -> None: + """Close mosaic and mixup augmentation and switches to use L1 loss.""" + epoch = runner.epoch + train_loader = runner.train_dataloader + model = runner.model + # TODO: refactor after mmengine using model wrapper + if is_model_wrapper(model): + model = model.module + epoch_to_be_switched = ((epoch + 1) >= + runner.max_epochs - self.num_last_epochs) + if epoch_to_be_switched and not self._has_switched: + runner.logger.info('No mosaic and mixup aug now!') + # The dataset pipeline cannot be updated when persistent_workers + # is True, so we need to force the dataloader's multi-process + # restart. This is a very hacky approach. + train_loader.dataset.update_skip_type_keys(self.skip_type_keys) + if hasattr(train_loader, 'persistent_workers' + ) and train_loader.persistent_workers is True: + train_loader._DataLoader__initialized = False + train_loader._iterator = None + self._restart_dataloader = True + runner.logger.info('Add additional L1 loss now!') + if hasattr(model, 'detector'): + model.detector.bbox_head.use_l1 = True + else: + model.bbox_head.use_l1 = True + self._has_switched = True + else: + # Once the restart is complete, we need to restore + # the initialization flag. + if self._restart_dataloader: + train_loader._DataLoader__initialized = True diff --git a/mmdetection/mmdet/engine/optimizers/__init__.py b/mmdetection/mmdet/engine/optimizers/__init__.py new file mode 100644 index 00000000..83db069e --- /dev/null +++ b/mmdetection/mmdet/engine/optimizers/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .layer_decay_optimizer_constructor import \ + LearningRateDecayOptimizerConstructor + +__all__ = ['LearningRateDecayOptimizerConstructor'] diff --git a/mmdetection/mmdet/engine/optimizers/layer_decay_optimizer_constructor.py b/mmdetection/mmdet/engine/optimizers/layer_decay_optimizer_constructor.py new file mode 100644 index 00000000..73028a0a --- /dev/null +++ b/mmdetection/mmdet/engine/optimizers/layer_decay_optimizer_constructor.py @@ -0,0 +1,158 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +from typing import List + +import torch.nn as nn +from mmengine.dist import get_dist_info +from mmengine.logging import MMLogger +from mmengine.optim import DefaultOptimWrapperConstructor + +from mmdet.registry import OPTIM_WRAPPER_CONSTRUCTORS + + +def get_layer_id_for_convnext(var_name, max_layer_id): + """Get the layer id to set the different learning rates in ``layer_wise`` + decay_type. + + Args: + var_name (str): The key of the model. + max_layer_id (int): Maximum layer id. + + Returns: + int: The id number corresponding to different learning rate in + ``LearningRateDecayOptimizerConstructor``. + """ + + if var_name in ('backbone.cls_token', 'backbone.mask_token', + 'backbone.pos_embed'): + return 0 + elif var_name.startswith('backbone.downsample_layers'): + stage_id = int(var_name.split('.')[2]) + if stage_id == 0: + layer_id = 0 + elif stage_id == 1: + layer_id = 2 + elif stage_id == 2: + layer_id = 3 + elif stage_id == 3: + layer_id = max_layer_id + return layer_id + elif var_name.startswith('backbone.stages'): + stage_id = int(var_name.split('.')[2]) + block_id = int(var_name.split('.')[3]) + if stage_id == 0: + layer_id = 1 + elif stage_id == 1: + layer_id = 2 + elif stage_id == 2: + layer_id = 3 + block_id // 3 + elif stage_id == 3: + layer_id = max_layer_id + return layer_id + else: + return max_layer_id + 1 + + +def get_stage_id_for_convnext(var_name, max_stage_id): + """Get the stage id to set the different learning rates in ``stage_wise`` + decay_type. + + Args: + var_name (str): The key of the model. + max_stage_id (int): Maximum stage id. + + Returns: + int: The id number corresponding to different learning rate in + ``LearningRateDecayOptimizerConstructor``. + """ + + if var_name in ('backbone.cls_token', 'backbone.mask_token', + 'backbone.pos_embed'): + return 0 + elif var_name.startswith('backbone.downsample_layers'): + return 0 + elif var_name.startswith('backbone.stages'): + stage_id = int(var_name.split('.')[2]) + return stage_id + 1 + else: + return max_stage_id - 1 + + +@OPTIM_WRAPPER_CONSTRUCTORS.register_module() +class LearningRateDecayOptimizerConstructor(DefaultOptimWrapperConstructor): + # Different learning rates are set for different layers of backbone. + # Note: Currently, this optimizer constructor is built for ConvNeXt. + + def add_params(self, params: List[dict], module: nn.Module, + **kwargs) -> None: + """Add all parameters of module to the params list. + + The parameters of the given module will be added to the list of param + groups, with specific rules defined by paramwise_cfg. + + Args: + params (list[dict]): A list of param groups, it will be modified + in place. + module (nn.Module): The module to be added. + """ + logger = MMLogger.get_current_instance() + + parameter_groups = {} + logger.info(f'self.paramwise_cfg is {self.paramwise_cfg}') + num_layers = self.paramwise_cfg.get('num_layers') + 2 + decay_rate = self.paramwise_cfg.get('decay_rate') + decay_type = self.paramwise_cfg.get('decay_type', 'layer_wise') + logger.info('Build LearningRateDecayOptimizerConstructor ' + f'{decay_type} {decay_rate} - {num_layers}') + weight_decay = self.base_wd + for name, param in module.named_parameters(): + if not param.requires_grad: + continue # frozen weights + if len(param.shape) == 1 or name.endswith('.bias') or name in ( + 'pos_embed', 'cls_token'): + group_name = 'no_decay' + this_weight_decay = 0. + else: + group_name = 'decay' + this_weight_decay = weight_decay + if 'layer_wise' in decay_type: + if 'ConvNeXt' in module.backbone.__class__.__name__: + layer_id = get_layer_id_for_convnext( + name, self.paramwise_cfg.get('num_layers')) + logger.info(f'set param {name} as id {layer_id}') + else: + raise NotImplementedError() + elif decay_type == 'stage_wise': + if 'ConvNeXt' in module.backbone.__class__.__name__: + layer_id = get_stage_id_for_convnext(name, num_layers) + logger.info(f'set param {name} as id {layer_id}') + else: + raise NotImplementedError() + group_name = f'layer_{layer_id}_{group_name}' + + if group_name not in parameter_groups: + scale = decay_rate**(num_layers - layer_id - 1) + + parameter_groups[group_name] = { + 'weight_decay': this_weight_decay, + 'params': [], + 'param_names': [], + 'lr_scale': scale, + 'group_name': group_name, + 'lr': scale * self.base_lr, + } + + parameter_groups[group_name]['params'].append(param) + parameter_groups[group_name]['param_names'].append(name) + rank, _ = get_dist_info() + if rank == 0: + to_display = {} + for key in parameter_groups: + to_display[key] = { + 'param_names': parameter_groups[key]['param_names'], + 'lr_scale': parameter_groups[key]['lr_scale'], + 'lr': parameter_groups[key]['lr'], + 'weight_decay': parameter_groups[key]['weight_decay'], + } + logger.info(f'Param groups = {json.dumps(to_display, indent=2)}') + params.extend(parameter_groups.values()) diff --git a/mmdetection/mmdet/engine/runner/__init__.py b/mmdetection/mmdet/engine/runner/__init__.py new file mode 100644 index 00000000..e8bcce44 --- /dev/null +++ b/mmdetection/mmdet/engine/runner/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .loops import TeacherStudentValLoop + +__all__ = ['TeacherStudentValLoop'] diff --git a/mmdetection/mmdet/engine/runner/loops.py b/mmdetection/mmdet/engine/runner/loops.py new file mode 100644 index 00000000..afe53afa --- /dev/null +++ b/mmdetection/mmdet/engine/runner/loops.py @@ -0,0 +1,38 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.model import is_model_wrapper +from mmengine.runner import ValLoop + +from mmdet.registry import LOOPS + + +@LOOPS.register_module() +class TeacherStudentValLoop(ValLoop): + """Loop for validation of model teacher and student.""" + + def run(self): + """Launch validation for model teacher and student.""" + self.runner.call_hook('before_val') + self.runner.call_hook('before_val_epoch') + self.runner.model.eval() + + model = self.runner.model + if is_model_wrapper(model): + model = model.module + assert hasattr(model, 'teacher') + assert hasattr(model, 'student') + + predict_on = model.semi_test_cfg.get('predict_on', None) + multi_metrics = dict() + for _predict_on in ['teacher', 'student']: + model.semi_test_cfg['predict_on'] = _predict_on + for idx, data_batch in enumerate(self.dataloader): + self.run_iter(idx, data_batch) + # compute metrics + metrics = self.evaluator.evaluate(len(self.dataloader.dataset)) + multi_metrics.update( + {'/'.join((_predict_on, k)): v + for k, v in metrics.items()}) + model.semi_test_cfg['predict_on'] = predict_on + + self.runner.call_hook('after_val_epoch', metrics=multi_metrics) + self.runner.call_hook('after_val') diff --git a/mmdetection/mmdet/engine/schedulers/__init__.py b/mmdetection/mmdet/engine/schedulers/__init__.py new file mode 100644 index 00000000..01261646 --- /dev/null +++ b/mmdetection/mmdet/engine/schedulers/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .quadratic_warmup import (QuadraticWarmupLR, QuadraticWarmupMomentum, + QuadraticWarmupParamScheduler) + +__all__ = [ + 'QuadraticWarmupParamScheduler', 'QuadraticWarmupMomentum', + 'QuadraticWarmupLR' +] diff --git a/mmdetection/mmdet/engine/schedulers/quadratic_warmup.py b/mmdetection/mmdet/engine/schedulers/quadratic_warmup.py new file mode 100644 index 00000000..639b4785 --- /dev/null +++ b/mmdetection/mmdet/engine/schedulers/quadratic_warmup.py @@ -0,0 +1,131 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.optim.scheduler.lr_scheduler import LRSchedulerMixin +from mmengine.optim.scheduler.momentum_scheduler import MomentumSchedulerMixin +from mmengine.optim.scheduler.param_scheduler import INF, _ParamScheduler +from torch.optim import Optimizer + +from mmdet.registry import PARAM_SCHEDULERS + + +@PARAM_SCHEDULERS.register_module() +class QuadraticWarmupParamScheduler(_ParamScheduler): + r"""Warm up the parameter value of each parameter group by quadratic + formula: + + .. math:: + + X_{t} = X_{t-1} + \frac{2t+1}{{(end-begin)}^{2}} \times X_{base} + + Args: + optimizer (Optimizer): Wrapped optimizer. + param_name (str): Name of the parameter to be adjusted, such as + ``lr``, ``momentum``. + begin (int): Step at which to start updating the parameters. + Defaults to 0. + end (int): Step at which to stop updating the parameters. + Defaults to INF. + last_step (int): The index of last step. Used for resume without + state dict. Defaults to -1. + by_epoch (bool): Whether the scheduled parameters are updated by + epochs. Defaults to True. + verbose (bool): Whether to print the value for each update. + Defaults to False. + """ + + def __init__(self, + optimizer: Optimizer, + param_name: str, + begin: int = 0, + end: int = INF, + last_step: int = -1, + by_epoch: bool = True, + verbose: bool = False): + if end >= INF: + raise ValueError('``end`` must be less than infinity,' + 'Please set ``end`` parameter of ' + '``QuadraticWarmupScheduler`` as the ' + 'number of warmup end.') + self.total_iters = end - begin + super().__init__( + optimizer=optimizer, + param_name=param_name, + begin=begin, + end=end, + last_step=last_step, + by_epoch=by_epoch, + verbose=verbose) + + @classmethod + def build_iter_from_epoch(cls, + *args, + begin=0, + end=INF, + by_epoch=True, + epoch_length=None, + **kwargs): + """Build an iter-based instance of this scheduler from an epoch-based + config.""" + assert by_epoch, 'Only epoch-based kwargs whose `by_epoch=True` can ' \ + 'be converted to iter-based.' + assert epoch_length is not None and epoch_length > 0, \ + f'`epoch_length` must be a positive integer, ' \ + f'but got {epoch_length}.' + by_epoch = False + begin = begin * epoch_length + if end != INF: + end = end * epoch_length + return cls(*args, begin=begin, end=end, by_epoch=by_epoch, **kwargs) + + def _get_value(self): + """Compute value using chainable form of the scheduler.""" + if self.last_step == 0: + return [ + base_value * (2 * self.last_step + 1) / self.total_iters**2 + for base_value in self.base_values + ] + + return [ + group[self.param_name] + base_value * + (2 * self.last_step + 1) / self.total_iters**2 + for base_value, group in zip(self.base_values, + self.optimizer.param_groups) + ] + + +@PARAM_SCHEDULERS.register_module() +class QuadraticWarmupLR(LRSchedulerMixin, QuadraticWarmupParamScheduler): + """Warm up the learning rate of each parameter group by quadratic formula. + + Args: + optimizer (Optimizer): Wrapped optimizer. + begin (int): Step at which to start updating the parameters. + Defaults to 0. + end (int): Step at which to stop updating the parameters. + Defaults to INF. + last_step (int): The index of last step. Used for resume without + state dict. Defaults to -1. + by_epoch (bool): Whether the scheduled parameters are updated by + epochs. Defaults to True. + verbose (bool): Whether to print the value for each update. + Defaults to False. + """ + + +@PARAM_SCHEDULERS.register_module() +class QuadraticWarmupMomentum(MomentumSchedulerMixin, + QuadraticWarmupParamScheduler): + """Warm up the momentum value of each parameter group by quadratic formula. + + Args: + optimizer (Optimizer): Wrapped optimizer. + begin (int): Step at which to start updating the parameters. + Defaults to 0. + end (int): Step at which to stop updating the parameters. + Defaults to INF. + last_step (int): The index of last step. Used for resume without + state dict. Defaults to -1. + by_epoch (bool): Whether the scheduled parameters are updated by + epochs. Defaults to True. + verbose (bool): Whether to print the value for each update. + Defaults to False. + """ diff --git a/mmdetection/mmdet/evaluation/__init__.py b/mmdetection/mmdet/evaluation/__init__.py new file mode 100644 index 00000000..126dea09 --- /dev/null +++ b/mmdetection/mmdet/evaluation/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .evaluator import * # noqa: F401,F403 +from .functional import * # noqa: F401,F403 +from .metrics import * # noqa: F401,F403 diff --git a/mmdetection/mmdet/evaluation/evaluator/__init__.py b/mmdetection/mmdet/evaluation/evaluator/__init__.py new file mode 100644 index 00000000..6b13fe99 --- /dev/null +++ b/mmdetection/mmdet/evaluation/evaluator/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .multi_datasets_evaluator import MultiDatasetsEvaluator + +__all__ = ['MultiDatasetsEvaluator'] diff --git a/mmdetection/mmdet/evaluation/evaluator/multi_datasets_evaluator.py b/mmdetection/mmdet/evaluation/evaluator/multi_datasets_evaluator.py new file mode 100644 index 00000000..5cff1cf2 --- /dev/null +++ b/mmdetection/mmdet/evaluation/evaluator/multi_datasets_evaluator.py @@ -0,0 +1,111 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from collections import OrderedDict +from typing import Sequence, Union + +from mmengine.dist import (broadcast_object_list, collect_results, + is_main_process) +from mmengine.evaluator import BaseMetric, Evaluator +from mmengine.evaluator.metric import _to_cpu +from mmengine.registry import EVALUATOR + +from mmdet.utils import ConfigType + + +@EVALUATOR.register_module() +class MultiDatasetsEvaluator(Evaluator): + """Wrapper class to compose class: `ConcatDataset` and multiple + :class:`BaseMetric` instances. + The metrics will be evaluated on each dataset slice separately. The name of + the each metric is the concatenation of the dataset prefix, the metric + prefix and the key of metric - e.g. + `dataset_prefix/metric_prefix/accuracy`. + + Args: + metrics (dict or BaseMetric or Sequence): The config of metrics. + dataset_prefixes (Sequence[str]): The prefix of each dataset. The + length of this sequence should be the same as the length of the + datasets. + """ + + def __init__(self, metrics: Union[ConfigType, BaseMetric, Sequence], + dataset_prefixes: Sequence[str]) -> None: + super().__init__(metrics) + self.dataset_prefixes = dataset_prefixes + self._setups = False + + def _get_cumulative_sizes(self): + # ConcatDataset have a property `cumulative_sizes` + if isinstance(self.dataset_meta, Sequence): + dataset_slices = self.dataset_meta[0]['cumulative_sizes'] + if not self._setups: + self._setups = True + for dataset_meta, metric in zip(self.dataset_meta, + self.metrics): + metric.dataset_meta = dataset_meta + else: + dataset_slices = self.dataset_meta['cumulative_sizes'] + return dataset_slices + + def evaluate(self, size: int) -> dict: + """Invoke ``evaluate`` method of each metric and collect the metrics + dictionary. + + Args: + size (int): Length of the entire validation dataset. When batch + size > 1, the dataloader may pad some data samples to make + sure all ranks have the same length of dataset slice. The + ``collect_results`` function will drop the padded data based on + this size. + + Returns: + dict: Evaluation results of all metrics. The keys are the names + of the metrics, and the values are corresponding results. + """ + metrics_results = OrderedDict() + dataset_slices = self._get_cumulative_sizes() + assert len(dataset_slices) == len(self.dataset_prefixes) + + for dataset_prefix, start, end, metric in zip( + self.dataset_prefixes, [0] + dataset_slices[:-1], + dataset_slices, self.metrics): + if len(metric.results) == 0: + warnings.warn( + f'{metric.__class__.__name__} got empty `self.results`.' + 'Please ensure that the processed results are properly ' + 'added into `self.results` in `process` method.') + + results = collect_results(metric.results, size, + metric.collect_device) + + if is_main_process(): + # cast all tensors in results list to cpu + results = _to_cpu(results) + _metrics = metric.compute_metrics( + results[start:end]) # type: ignore + + if metric.prefix: + final_prefix = '/'.join((dataset_prefix, metric.prefix)) + else: + final_prefix = dataset_prefix + print(f'================{final_prefix}================') + metric_results = { + '/'.join((final_prefix, k)): v + for k, v in _metrics.items() + } + + # Check metric name conflicts + for name in metric_results.keys(): + if name in metrics_results: + raise ValueError( + 'There are multiple evaluation results with ' + f'the same metric name {name}. Please make ' + 'sure all metrics have different prefixes.') + metrics_results.update(metric_results) + metric.results.clear() + if is_main_process(): + metrics_results = [metrics_results] + else: + metrics_results = [None] # type: ignore + broadcast_object_list(metrics_results) + return metrics_results[0] diff --git a/mmdetection/mmdet/evaluation/functional/__init__.py b/mmdetection/mmdet/evaluation/functional/__init__.py new file mode 100644 index 00000000..96d58ebd --- /dev/null +++ b/mmdetection/mmdet/evaluation/functional/__init__.py @@ -0,0 +1,26 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .bbox_overlaps import bbox_overlaps +from .cityscapes_utils import evaluateImgLists +from .class_names import (cityscapes_classes, coco_classes, + coco_panoptic_classes, dataset_aliases, get_classes, + imagenet_det_classes, imagenet_vid_classes, + objects365v1_classes, objects365v2_classes, + oid_challenge_classes, oid_v6_classes, voc_classes) +from .mean_ap import average_precision, eval_map, print_map_summary +from .panoptic_utils import (INSTANCE_OFFSET, pq_compute_multi_core, + pq_compute_single_core) +from .recall import (eval_recalls, plot_iou_recall, plot_num_recall, + print_recall_summary) +from .ytvis import YTVIS +from .ytviseval import YTVISeval + +__all__ = [ + 'voc_classes', 'imagenet_det_classes', 'imagenet_vid_classes', + 'coco_classes', 'cityscapes_classes', 'dataset_aliases', 'get_classes', + 'average_precision', 'eval_map', 'print_map_summary', 'eval_recalls', + 'print_recall_summary', 'plot_num_recall', 'plot_iou_recall', + 'oid_v6_classes', 'oid_challenge_classes', 'INSTANCE_OFFSET', + 'pq_compute_single_core', 'pq_compute_multi_core', 'bbox_overlaps', + 'objects365v1_classes', 'objects365v2_classes', 'coco_panoptic_classes', + 'evaluateImgLists', 'YTVIS', 'YTVISeval' +] diff --git a/mmdetection/mmdet/evaluation/functional/bbox_overlaps.py b/mmdetection/mmdet/evaluation/functional/bbox_overlaps.py new file mode 100644 index 00000000..5d6eb82f --- /dev/null +++ b/mmdetection/mmdet/evaluation/functional/bbox_overlaps.py @@ -0,0 +1,65 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np + + +def bbox_overlaps(bboxes1, + bboxes2, + mode='iou', + eps=1e-6, + use_legacy_coordinate=False): + """Calculate the ious between each bbox of bboxes1 and bboxes2. + + Args: + bboxes1 (ndarray): Shape (n, 4) + bboxes2 (ndarray): Shape (k, 4) + mode (str): IOU (intersection over union) or IOF (intersection + over foreground) + use_legacy_coordinate (bool): Whether to use coordinate system in + mmdet v1.x. which means width, height should be + calculated as 'x2 - x1 + 1` and 'y2 - y1 + 1' respectively. + Note when function is used in `VOCDataset`, it should be + True to align with the official implementation + `http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCdevkit_18-May-2011.tar` + Default: False. + + Returns: + ious (ndarray): Shape (n, k) + """ + + assert mode in ['iou', 'iof'] + if not use_legacy_coordinate: + extra_length = 0. + else: + extra_length = 1. + bboxes1 = bboxes1.astype(np.float32) + bboxes2 = bboxes2.astype(np.float32) + rows = bboxes1.shape[0] + cols = bboxes2.shape[0] + ious = np.zeros((rows, cols), dtype=np.float32) + if rows * cols == 0: + return ious + exchange = False + if bboxes1.shape[0] > bboxes2.shape[0]: + bboxes1, bboxes2 = bboxes2, bboxes1 + ious = np.zeros((cols, rows), dtype=np.float32) + exchange = True + area1 = (bboxes1[:, 2] - bboxes1[:, 0] + extra_length) * ( + bboxes1[:, 3] - bboxes1[:, 1] + extra_length) + area2 = (bboxes2[:, 2] - bboxes2[:, 0] + extra_length) * ( + bboxes2[:, 3] - bboxes2[:, 1] + extra_length) + for i in range(bboxes1.shape[0]): + x_start = np.maximum(bboxes1[i, 0], bboxes2[:, 0]) + y_start = np.maximum(bboxes1[i, 1], bboxes2[:, 1]) + x_end = np.minimum(bboxes1[i, 2], bboxes2[:, 2]) + y_end = np.minimum(bboxes1[i, 3], bboxes2[:, 3]) + overlap = np.maximum(x_end - x_start + extra_length, 0) * np.maximum( + y_end - y_start + extra_length, 0) + if mode == 'iou': + union = area1[i] + area2 - overlap + else: + union = area1[i] if not exchange else area2 + union = np.maximum(union, eps) + ious[i, :] = overlap / union + if exchange: + ious = ious.T + return ious diff --git a/mmdetection/mmdet/evaluation/functional/cityscapes_utils.py b/mmdetection/mmdet/evaluation/functional/cityscapes_utils.py new file mode 100644 index 00000000..5ced3680 --- /dev/null +++ b/mmdetection/mmdet/evaluation/functional/cityscapes_utils.py @@ -0,0 +1,302 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Copyright (c) https://github.com/mcordts/cityscapesScripts +# A wrapper of `cityscapesscripts` which supports loading groundtruth +# image from `backend_args`. +import json +import os +import sys +from pathlib import Path +from typing import Optional, Union + +import mmcv +import numpy as np +from mmengine.fileio import get + +try: + import cityscapesscripts.evaluation.evalInstanceLevelSemanticLabeling as CSEval # noqa: E501 + from cityscapesscripts.evaluation.evalInstanceLevelSemanticLabeling import \ + CArgs # noqa: E501 + from cityscapesscripts.evaluation.instance import Instance + from cityscapesscripts.helpers.csHelpers import (id2label, labels, + writeDict2JSON) + HAS_CITYSCAPESAPI = True +except ImportError: + CArgs = object + HAS_CITYSCAPESAPI = False + + +def evaluateImgLists(prediction_list: list, + groundtruth_list: list, + args: CArgs, + backend_args: Optional[dict] = None, + dump_matches: bool = False) -> dict: + """A wrapper of obj:``cityscapesscripts.evaluation. + + evalInstanceLevelSemanticLabeling.evaluateImgLists``. Support loading + groundtruth image from file backend. + Args: + prediction_list (list): A list of prediction txt file. + groundtruth_list (list): A list of groundtruth image file. + args (CArgs): A global object setting in + obj:``cityscapesscripts.evaluation. + evalInstanceLevelSemanticLabeling`` + backend_args (dict, optional): Arguments to instantiate the + preifx of uri corresponding backend. Defaults to None. + dump_matches (bool): whether dump matches.json. Defaults to False. + Returns: + dict: The computed metric. + """ + if not HAS_CITYSCAPESAPI: + raise RuntimeError('Failed to import `cityscapesscripts`.' + 'Please try to install official ' + 'cityscapesscripts by ' + '"pip install cityscapesscripts"') + # determine labels of interest + CSEval.setInstanceLabels(args) + # get dictionary of all ground truth instances + gt_instances = getGtInstances( + groundtruth_list, args, backend_args=backend_args) + # match predictions and ground truth + matches = matchGtWithPreds(prediction_list, groundtruth_list, gt_instances, + args, backend_args) + if dump_matches: + CSEval.writeDict2JSON(matches, 'matches.json') + # evaluate matches + apScores = CSEval.evaluateMatches(matches, args) + # averages + avgDict = CSEval.computeAverages(apScores, args) + # result dict + resDict = CSEval.prepareJSONDataForResults(avgDict, apScores, args) + if args.JSONOutput: + # create output folder if necessary + path = os.path.dirname(args.exportFile) + CSEval.ensurePath(path) + # Write APs to JSON + CSEval.writeDict2JSON(resDict, args.exportFile) + + CSEval.printResults(avgDict, args) + + return resDict + + +def matchGtWithPreds(prediction_list: list, + groundtruth_list: list, + gt_instances: dict, + args: CArgs, + backend_args=None): + """A wrapper of obj:``cityscapesscripts.evaluation. + + evalInstanceLevelSemanticLabeling.matchGtWithPreds``. Support loading + groundtruth image from file backend. + Args: + prediction_list (list): A list of prediction txt file. + groundtruth_list (list): A list of groundtruth image file. + gt_instances (dict): Groundtruth dict. + args (CArgs): A global object setting in + obj:``cityscapesscripts.evaluation. + evalInstanceLevelSemanticLabeling`` + backend_args (dict, optional): Arguments to instantiate the + preifx of uri corresponding backend. Defaults to None. + Returns: + dict: The processed prediction and groundtruth result. + """ + if not HAS_CITYSCAPESAPI: + raise RuntimeError('Failed to import `cityscapesscripts`.' + 'Please try to install official ' + 'cityscapesscripts by ' + '"pip install cityscapesscripts"') + matches: dict = dict() + if not args.quiet: + print(f'Matching {len(prediction_list)} pairs of images...') + + count = 0 + for (pred, gt) in zip(prediction_list, groundtruth_list): + # Read input files + gt_image = readGTImage(gt, backend_args) + pred_info = readPredInfo(pred) + # Get and filter ground truth instances + unfiltered_instances = gt_instances[gt] + cur_gt_instances_orig = CSEval.filterGtInstances( + unfiltered_instances, args) + + # Try to assign all predictions + (cur_gt_instances, + cur_pred_instances) = CSEval.assignGt2Preds(cur_gt_instances_orig, + gt_image, pred_info, args) + + # append to global dict + matches[gt] = {} + matches[gt]['groundTruth'] = cur_gt_instances + matches[gt]['prediction'] = cur_pred_instances + + count += 1 + if not args.quiet: + print(f'\rImages Processed: {count}', end=' ') + sys.stdout.flush() + + if not args.quiet: + print('') + + return matches + + +def readGTImage(image_file: Union[str, Path], + backend_args: Optional[dict] = None) -> np.ndarray: + """Read an image from path. + + Same as obj:``cityscapesscripts.evaluation. + evalInstanceLevelSemanticLabeling.readGTImage``, but support loading + groundtruth image from file backend. + Args: + image_file (str or Path): Either a str or pathlib.Path. + backend_args (dict, optional): Instantiates the corresponding file + backend. It may contain `backend` key to specify the file + backend. If it contains, the file backend corresponding to this + value will be used and initialized with the remaining values, + otherwise the corresponding file backend will be selected + based on the prefix of the file path. Defaults to None. + Returns: + np.ndarray: The groundtruth image. + """ + img_bytes = get(image_file, backend_args=backend_args) + img = mmcv.imfrombytes(img_bytes, flag='unchanged', backend='pillow') + return img + + +def readPredInfo(prediction_file: str) -> dict: + """A wrapper of obj:``cityscapesscripts.evaluation. + + evalInstanceLevelSemanticLabeling.readPredInfo``. + Args: + prediction_file (str): The prediction txt file. + Returns: + dict: The processed prediction results. + """ + if not HAS_CITYSCAPESAPI: + raise RuntimeError('Failed to import `cityscapesscripts`.' + 'Please try to install official ' + 'cityscapesscripts by ' + '"pip install cityscapesscripts"') + printError = CSEval.printError + + predInfo = {} + if (not os.path.isfile(prediction_file)): + printError(f"Infofile '{prediction_file}' " + 'for the predictions not found.') + with open(prediction_file) as f: + for line in f: + splittedLine = line.split(' ') + if len(splittedLine) != 3: + printError('Invalid prediction file. Expected content: ' + 'relPathPrediction1 labelIDPrediction1 ' + 'confidencePrediction1') + if os.path.isabs(splittedLine[0]): + printError('Invalid prediction file. First entry in each ' + 'line must be a relative path.') + + filename = os.path.join( + os.path.dirname(prediction_file), splittedLine[0]) + + imageInfo = {} + imageInfo['labelID'] = int(float(splittedLine[1])) + imageInfo['conf'] = float(splittedLine[2]) # type: ignore + predInfo[filename] = imageInfo + + return predInfo + + +def getGtInstances(groundtruth_list: list, + args: CArgs, + backend_args: Optional[dict] = None) -> dict: + """A wrapper of obj:``cityscapesscripts.evaluation. + + evalInstanceLevelSemanticLabeling.getGtInstances``. Support loading + groundtruth image from file backend. + Args: + groundtruth_list (list): A list of groundtruth image file. + args (CArgs): A global object setting in + obj:``cityscapesscripts.evaluation. + evalInstanceLevelSemanticLabeling`` + backend_args (dict, optional): Arguments to instantiate the + preifx of uri corresponding backend. Defaults to None. + Returns: + dict: The computed metric. + """ + if not HAS_CITYSCAPESAPI: + raise RuntimeError('Failed to import `cityscapesscripts`.' + 'Please try to install official ' + 'cityscapesscripts by ' + '"pip install cityscapesscripts"') + # if there is a global statistics json, then load it + if (os.path.isfile(args.gtInstancesFile)): + if not args.quiet: + print('Loading ground truth instances from JSON.') + with open(args.gtInstancesFile) as json_file: + gt_instances = json.load(json_file) + # otherwise create it + else: + if (not args.quiet): + print('Creating ground truth instances from png files.') + gt_instances = instances2dict( + groundtruth_list, args, backend_args=backend_args) + writeDict2JSON(gt_instances, args.gtInstancesFile) + + return gt_instances + + +def instances2dict(image_list: list, + args: CArgs, + backend_args: Optional[dict] = None) -> dict: + """A wrapper of obj:``cityscapesscripts.evaluation. + + evalInstanceLevelSemanticLabeling.instances2dict``. Support loading + groundtruth image from file backend. + Args: + image_list (list): A list of image file. + args (CArgs): A global object setting in + obj:``cityscapesscripts.evaluation. + evalInstanceLevelSemanticLabeling`` + backend_args (dict, optional): Arguments to instantiate the + preifx of uri corresponding backend. Defaults to None. + Returns: + dict: The processed groundtruth results. + """ + if not HAS_CITYSCAPESAPI: + raise RuntimeError('Failed to import `cityscapesscripts`.' + 'Please try to install official ' + 'cityscapesscripts by ' + '"pip install cityscapesscripts"') + imgCount = 0 + instanceDict = {} + + if not isinstance(image_list, list): + image_list = [image_list] + + if not args.quiet: + print(f'Processing {len(image_list)} images...') + + for image_name in image_list: + # Load image + img_bytes = get(image_name, backend_args=backend_args) + imgNp = mmcv.imfrombytes(img_bytes, flag='unchanged', backend='pillow') + + # Initialize label categories + instances: dict = {} + for label in labels: + instances[label.name] = [] + + # Loop through all instance ids in instance image + for instanceId in np.unique(imgNp): + instanceObj = Instance(imgNp, instanceId) + + instances[id2label[instanceObj.labelID].name].append( + instanceObj.toDict()) + + instanceDict[image_name] = instances + imgCount += 1 + + if not args.quiet: + print(f'\rImages Processed: {imgCount}', end=' ') + sys.stdout.flush() + + return instanceDict diff --git a/mmdetection/mmdet/evaluation/functional/class_names.py b/mmdetection/mmdet/evaluation/functional/class_names.py new file mode 100644 index 00000000..623a89cf --- /dev/null +++ b/mmdetection/mmdet/evaluation/functional/class_names.py @@ -0,0 +1,762 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.utils import is_str + + +def wider_face_classes() -> list: + """Class names of WIDERFace.""" + return ['face'] + + +def voc_classes() -> list: + """Class names of PASCAL VOC.""" + return [ + 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', + 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', + 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor' + ] + + +def imagenet_det_classes() -> list: + """Class names of ImageNet Det.""" + return [ + 'accordion', 'airplane', 'ant', 'antelope', 'apple', 'armadillo', + 'artichoke', 'axe', 'baby_bed', 'backpack', 'bagel', 'balance_beam', + 'banana', 'band_aid', 'banjo', 'baseball', 'basketball', 'bathing_cap', + 'beaker', 'bear', 'bee', 'bell_pepper', 'bench', 'bicycle', 'binder', + 'bird', 'bookshelf', 'bow_tie', 'bow', 'bowl', 'brassiere', 'burrito', + 'bus', 'butterfly', 'camel', 'can_opener', 'car', 'cart', 'cattle', + 'cello', 'centipede', 'chain_saw', 'chair', 'chime', 'cocktail_shaker', + 'coffee_maker', 'computer_keyboard', 'computer_mouse', 'corkscrew', + 'cream', 'croquet_ball', 'crutch', 'cucumber', 'cup_or_mug', 'diaper', + 'digital_clock', 'dishwasher', 'dog', 'domestic_cat', 'dragonfly', + 'drum', 'dumbbell', 'electric_fan', 'elephant', 'face_powder', 'fig', + 'filing_cabinet', 'flower_pot', 'flute', 'fox', 'french_horn', 'frog', + 'frying_pan', 'giant_panda', 'goldfish', 'golf_ball', 'golfcart', + 'guacamole', 'guitar', 'hair_dryer', 'hair_spray', 'hamburger', + 'hammer', 'hamster', 'harmonica', 'harp', 'hat_with_a_wide_brim', + 'head_cabbage', 'helmet', 'hippopotamus', 'horizontal_bar', 'horse', + 'hotdog', 'iPod', 'isopod', 'jellyfish', 'koala_bear', 'ladle', + 'ladybug', 'lamp', 'laptop', 'lemon', 'lion', 'lipstick', 'lizard', + 'lobster', 'maillot', 'maraca', 'microphone', 'microwave', 'milk_can', + 'miniskirt', 'monkey', 'motorcycle', 'mushroom', 'nail', 'neck_brace', + 'oboe', 'orange', 'otter', 'pencil_box', 'pencil_sharpener', 'perfume', + 'person', 'piano', 'pineapple', 'ping-pong_ball', 'pitcher', 'pizza', + 'plastic_bag', 'plate_rack', 'pomegranate', 'popsicle', 'porcupine', + 'power_drill', 'pretzel', 'printer', 'puck', 'punching_bag', 'purse', + 'rabbit', 'racket', 'ray', 'red_panda', 'refrigerator', + 'remote_control', 'rubber_eraser', 'rugby_ball', 'ruler', + 'salt_or_pepper_shaker', 'saxophone', 'scorpion', 'screwdriver', + 'seal', 'sheep', 'ski', 'skunk', 'snail', 'snake', 'snowmobile', + 'snowplow', 'soap_dispenser', 'soccer_ball', 'sofa', 'spatula', + 'squirrel', 'starfish', 'stethoscope', 'stove', 'strainer', + 'strawberry', 'stretcher', 'sunglasses', 'swimming_trunks', 'swine', + 'syringe', 'table', 'tape_player', 'tennis_ball', 'tick', 'tie', + 'tiger', 'toaster', 'traffic_light', 'train', 'trombone', 'trumpet', + 'turtle', 'tv_or_monitor', 'unicycle', 'vacuum', 'violin', + 'volleyball', 'waffle_iron', 'washer', 'water_bottle', 'watercraft', + 'whale', 'wine_bottle', 'zebra' + ] + + +def imagenet_vid_classes() -> list: + """Class names of ImageNet VID.""" + return [ + 'airplane', 'antelope', 'bear', 'bicycle', 'bird', 'bus', 'car', + 'cattle', 'dog', 'domestic_cat', 'elephant', 'fox', 'giant_panda', + 'hamster', 'horse', 'lion', 'lizard', 'monkey', 'motorcycle', 'rabbit', + 'red_panda', 'sheep', 'snake', 'squirrel', 'tiger', 'train', 'turtle', + 'watercraft', 'whale', 'zebra' + ] + + +def coco_classes() -> list: + """Class names of COCO.""" + return [ + 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'traffic_light', 'fire_hydrant', 'stop_sign', + 'parking_meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', + 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', + 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', + 'sports_ball', 'kite', 'baseball_bat', 'baseball_glove', 'skateboard', + 'surfboard', 'tennis_racket', 'bottle', 'wine_glass', 'cup', 'fork', + 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', + 'broccoli', 'carrot', 'hot_dog', 'pizza', 'donut', 'cake', 'chair', + 'couch', 'potted_plant', 'bed', 'dining_table', 'toilet', 'tv', + 'laptop', 'mouse', 'remote', 'keyboard', 'cell_phone', 'microwave', + 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy_bear', 'hair_drier', 'toothbrush' + ] + + +def coco_panoptic_classes() -> list: + """Class names of COCO panoptic.""" + return [ + 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', + 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', + 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', + 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', + 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', + 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', + 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', + 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', + 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', + 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy bear', 'hair drier', 'toothbrush', 'banner', + 'blanket', 'bridge', 'cardboard', 'counter', 'curtain', 'door-stuff', + 'floor-wood', 'flower', 'fruit', 'gravel', 'house', 'light', + 'mirror-stuff', 'net', 'pillow', 'platform', 'playingfield', + 'railroad', 'river', 'road', 'roof', 'sand', 'sea', 'shelf', 'snow', + 'stairs', 'tent', 'towel', 'wall-brick', 'wall-stone', 'wall-tile', + 'wall-wood', 'water-other', 'window-blind', 'window-other', + 'tree-merged', 'fence-merged', 'ceiling-merged', 'sky-other-merged', + 'cabinet-merged', 'table-merged', 'floor-other-merged', + 'pavement-merged', 'mountain-merged', 'grass-merged', 'dirt-merged', + 'paper-merged', 'food-other-merged', 'building-other-merged', + 'rock-merged', 'wall-other-merged', 'rug-merged' + ] + + +def cityscapes_classes() -> list: + """Class names of Cityscapes.""" + return [ + 'person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle' + ] + + +def oid_challenge_classes() -> list: + """Class names of Open Images Challenge.""" + return [ + 'Footwear', 'Jeans', 'House', 'Tree', 'Woman', 'Man', 'Land vehicle', + 'Person', 'Wheel', 'Bus', 'Human face', 'Bird', 'Dress', 'Girl', + 'Vehicle', 'Building', 'Cat', 'Car', 'Belt', 'Elephant', 'Dessert', + 'Butterfly', 'Train', 'Guitar', 'Poster', 'Book', 'Boy', 'Bee', + 'Flower', 'Window', 'Hat', 'Human head', 'Dog', 'Human arm', 'Drink', + 'Human mouth', 'Human hair', 'Human nose', 'Human hand', 'Table', + 'Marine invertebrates', 'Fish', 'Sculpture', 'Rose', 'Street light', + 'Glasses', 'Fountain', 'Skyscraper', 'Swimwear', 'Brassiere', 'Drum', + 'Duck', 'Countertop', 'Furniture', 'Ball', 'Human leg', 'Boat', + 'Balloon', 'Bicycle helmet', 'Goggles', 'Door', 'Human eye', 'Shirt', + 'Toy', 'Teddy bear', 'Pasta', 'Tomato', 'Human ear', + 'Vehicle registration plate', 'Microphone', 'Musical keyboard', + 'Tower', 'Houseplant', 'Flowerpot', 'Fruit', 'Vegetable', + 'Musical instrument', 'Suit', 'Motorcycle', 'Bagel', 'French fries', + 'Hamburger', 'Chair', 'Salt and pepper shakers', 'Snail', 'Airplane', + 'Horse', 'Laptop', 'Computer keyboard', 'Football helmet', 'Cocktail', + 'Juice', 'Tie', 'Computer monitor', 'Human beard', 'Bottle', + 'Saxophone', 'Lemon', 'Mouse', 'Sock', 'Cowboy hat', 'Sun hat', + 'Football', 'Porch', 'Sunglasses', 'Lobster', 'Crab', 'Picture frame', + 'Van', 'Crocodile', 'Surfboard', 'Shorts', 'Helicopter', 'Helmet', + 'Sports uniform', 'Taxi', 'Swan', 'Goose', 'Coat', 'Jacket', 'Handbag', + 'Flag', 'Skateboard', 'Television', 'Tire', 'Spoon', 'Palm tree', + 'Stairs', 'Salad', 'Castle', 'Oven', 'Microwave oven', 'Wine', + 'Ceiling fan', 'Mechanical fan', 'Cattle', 'Truck', 'Box', 'Ambulance', + 'Desk', 'Wine glass', 'Reptile', 'Tank', 'Traffic light', 'Billboard', + 'Tent', 'Insect', 'Spider', 'Treadmill', 'Cupboard', 'Shelf', + 'Seat belt', 'Human foot', 'Bicycle', 'Bicycle wheel', 'Couch', + 'Bookcase', 'Fedora', 'Backpack', 'Bench', 'Oyster', + 'Moths and butterflies', 'Lavender', 'Waffle', 'Fork', 'Animal', + 'Accordion', 'Mobile phone', 'Plate', 'Coffee cup', 'Saucer', + 'Platter', 'Dagger', 'Knife', 'Bull', 'Tortoise', 'Sea turtle', 'Deer', + 'Weapon', 'Apple', 'Ski', 'Taco', 'Traffic sign', 'Beer', 'Necklace', + 'Sunflower', 'Piano', 'Organ', 'Harpsichord', 'Bed', 'Cabinetry', + 'Nightstand', 'Curtain', 'Chest of drawers', 'Drawer', 'Parrot', + 'Sandal', 'High heels', 'Tableware', 'Cart', 'Mushroom', 'Kite', + 'Missile', 'Seafood', 'Camera', 'Paper towel', 'Toilet paper', + 'Sombrero', 'Radish', 'Lighthouse', 'Segway', 'Pig', 'Watercraft', + 'Golf cart', 'studio couch', 'Dolphin', 'Whale', 'Earrings', 'Otter', + 'Sea lion', 'Whiteboard', 'Monkey', 'Gondola', 'Zebra', + 'Baseball glove', 'Scarf', 'Adhesive tape', 'Trousers', 'Scoreboard', + 'Lily', 'Carnivore', 'Power plugs and sockets', 'Office building', + 'Sandwich', 'Swimming pool', 'Headphones', 'Tin can', 'Crown', 'Doll', + 'Cake', 'Frog', 'Beetle', 'Ant', 'Gas stove', 'Canoe', 'Falcon', + 'Blue jay', 'Egg', 'Fire hydrant', 'Raccoon', 'Muffin', 'Wall clock', + 'Coffee', 'Mug', 'Tea', 'Bear', 'Waste container', 'Home appliance', + 'Candle', 'Lion', 'Mirror', 'Starfish', 'Marine mammal', 'Wheelchair', + 'Umbrella', 'Alpaca', 'Violin', 'Cello', 'Brown bear', 'Canary', 'Bat', + 'Ruler', 'Plastic bag', 'Penguin', 'Watermelon', 'Harbor seal', 'Pen', + 'Pumpkin', 'Harp', 'Kitchen appliance', 'Roller skates', 'Bust', + 'Coffee table', 'Tennis ball', 'Tennis racket', 'Ladder', 'Boot', + 'Bowl', 'Stop sign', 'Volleyball', 'Eagle', 'Paddle', 'Chicken', + 'Skull', 'Lamp', 'Beehive', 'Maple', 'Sink', 'Goldfish', 'Tripod', + 'Coconut', 'Bidet', 'Tap', 'Bathroom cabinet', 'Toilet', + 'Filing cabinet', 'Pretzel', 'Table tennis racket', 'Bronze sculpture', + 'Rocket', 'Mouse', 'Hamster', 'Lizard', 'Lifejacket', 'Goat', + 'Washing machine', 'Trumpet', 'Horn', 'Trombone', 'Sheep', + 'Tablet computer', 'Pillow', 'Kitchen & dining room table', + 'Parachute', 'Raven', 'Glove', 'Loveseat', 'Christmas tree', + 'Shellfish', 'Rifle', 'Shotgun', 'Sushi', 'Sparrow', 'Bread', + 'Toaster', 'Watch', 'Asparagus', 'Artichoke', 'Suitcase', 'Antelope', + 'Broccoli', 'Ice cream', 'Racket', 'Banana', 'Cookie', 'Cucumber', + 'Dragonfly', 'Lynx', 'Caterpillar', 'Light bulb', 'Office supplies', + 'Miniskirt', 'Skirt', 'Fireplace', 'Potato', 'Light switch', + 'Croissant', 'Cabbage', 'Ladybug', 'Handgun', 'Luggage and bags', + 'Window blind', 'Snowboard', 'Baseball bat', 'Digital clock', + 'Serving tray', 'Infant bed', 'Sofa bed', 'Guacamole', 'Fox', 'Pizza', + 'Snowplow', 'Jet ski', 'Refrigerator', 'Lantern', 'Convenience store', + 'Sword', 'Rugby ball', 'Owl', 'Ostrich', 'Pancake', 'Strawberry', + 'Carrot', 'Tart', 'Dice', 'Turkey', 'Rabbit', 'Invertebrate', 'Vase', + 'Stool', 'Swim cap', 'Shower', 'Clock', 'Jellyfish', 'Aircraft', + 'Chopsticks', 'Orange', 'Snake', 'Sewing machine', 'Kangaroo', 'Mixer', + 'Food processor', 'Shrimp', 'Towel', 'Porcupine', 'Jaguar', 'Cannon', + 'Limousine', 'Mule', 'Squirrel', 'Kitchen knife', 'Tiara', 'Tiger', + 'Bow and arrow', 'Candy', 'Rhinoceros', 'Shark', 'Cricket ball', + 'Doughnut', 'Plumbing fixture', 'Camel', 'Polar bear', 'Coin', + 'Printer', 'Blender', 'Giraffe', 'Billiard table', 'Kettle', + 'Dinosaur', 'Pineapple', 'Zucchini', 'Jug', 'Barge', 'Teapot', + 'Golf ball', 'Binoculars', 'Scissors', 'Hot dog', 'Door handle', + 'Seahorse', 'Bathtub', 'Leopard', 'Centipede', 'Grapefruit', 'Snowman', + 'Cheetah', 'Alarm clock', 'Grape', 'Wrench', 'Wok', 'Bell pepper', + 'Cake stand', 'Barrel', 'Woodpecker', 'Flute', 'Corded phone', + 'Willow', 'Punching bag', 'Pomegranate', 'Telephone', 'Pear', + 'Common fig', 'Bench', 'Wood-burning stove', 'Burrito', 'Nail', + 'Turtle', 'Submarine sandwich', 'Drinking straw', 'Peach', 'Popcorn', + 'Frying pan', 'Picnic basket', 'Honeycomb', 'Envelope', 'Mango', + 'Cutting board', 'Pitcher', 'Stationary bicycle', 'Dumbbell', + 'Personal care', 'Dog bed', 'Snowmobile', 'Oboe', 'Briefcase', + 'Squash', 'Tick', 'Slow cooker', 'Coffeemaker', 'Measuring cup', + 'Crutch', 'Stretcher', 'Screwdriver', 'Flashlight', 'Spatula', + 'Pressure cooker', 'Ring binder', 'Beaker', 'Torch', 'Winter melon' + ] + + +def oid_v6_classes() -> list: + """Class names of Open Images V6.""" + return [ + 'Tortoise', 'Container', 'Magpie', 'Sea turtle', 'Football', + 'Ambulance', 'Ladder', 'Toothbrush', 'Syringe', 'Sink', 'Toy', + 'Organ (Musical Instrument)', 'Cassette deck', 'Apple', 'Human eye', + 'Cosmetics', 'Paddle', 'Snowman', 'Beer', 'Chopsticks', 'Human beard', + 'Bird', 'Parking meter', 'Traffic light', 'Croissant', 'Cucumber', + 'Radish', 'Towel', 'Doll', 'Skull', 'Washing machine', 'Glove', 'Tick', + 'Belt', 'Sunglasses', 'Banjo', 'Cart', 'Ball', 'Backpack', 'Bicycle', + 'Home appliance', 'Centipede', 'Boat', 'Surfboard', 'Boot', + 'Headphones', 'Hot dog', 'Shorts', 'Fast food', 'Bus', 'Boy', + 'Screwdriver', 'Bicycle wheel', 'Barge', 'Laptop', 'Miniskirt', + 'Drill (Tool)', 'Dress', 'Bear', 'Waffle', 'Pancake', 'Brown bear', + 'Woodpecker', 'Blue jay', 'Pretzel', 'Bagel', 'Tower', 'Teapot', + 'Person', 'Bow and arrow', 'Swimwear', 'Beehive', 'Brassiere', 'Bee', + 'Bat (Animal)', 'Starfish', 'Popcorn', 'Burrito', 'Chainsaw', + 'Balloon', 'Wrench', 'Tent', 'Vehicle registration plate', 'Lantern', + 'Toaster', 'Flashlight', 'Billboard', 'Tiara', 'Limousine', 'Necklace', + 'Carnivore', 'Scissors', 'Stairs', 'Computer keyboard', 'Printer', + 'Traffic sign', 'Chair', 'Shirt', 'Poster', 'Cheese', 'Sock', + 'Fire hydrant', 'Land vehicle', 'Earrings', 'Tie', 'Watercraft', + 'Cabinetry', 'Suitcase', 'Muffin', 'Bidet', 'Snack', 'Snowmobile', + 'Clock', 'Medical equipment', 'Cattle', 'Cello', 'Jet ski', 'Camel', + 'Coat', 'Suit', 'Desk', 'Cat', 'Bronze sculpture', 'Juice', 'Gondola', + 'Beetle', 'Cannon', 'Computer mouse', 'Cookie', 'Office building', + 'Fountain', 'Coin', 'Calculator', 'Cocktail', 'Computer monitor', + 'Box', 'Stapler', 'Christmas tree', 'Cowboy hat', 'Hiking equipment', + 'Studio couch', 'Drum', 'Dessert', 'Wine rack', 'Drink', 'Zucchini', + 'Ladle', 'Human mouth', 'Dairy Product', 'Dice', 'Oven', 'Dinosaur', + 'Ratchet (Device)', 'Couch', 'Cricket ball', 'Winter melon', 'Spatula', + 'Whiteboard', 'Pencil sharpener', 'Door', 'Hat', 'Shower', 'Eraser', + 'Fedora', 'Guacamole', 'Dagger', 'Scarf', 'Dolphin', 'Sombrero', + 'Tin can', 'Mug', 'Tap', 'Harbor seal', 'Stretcher', 'Can opener', + 'Goggles', 'Human body', 'Roller skates', 'Coffee cup', + 'Cutting board', 'Blender', 'Plumbing fixture', 'Stop sign', + 'Office supplies', 'Volleyball (Ball)', 'Vase', 'Slow cooker', + 'Wardrobe', 'Coffee', 'Whisk', 'Paper towel', 'Personal care', 'Food', + 'Sun hat', 'Tree house', 'Flying disc', 'Skirt', 'Gas stove', + 'Salt and pepper shakers', 'Mechanical fan', 'Face powder', 'Fax', + 'Fruit', 'French fries', 'Nightstand', 'Barrel', 'Kite', 'Tart', + 'Treadmill', 'Fox', 'Flag', 'French horn', 'Window blind', + 'Human foot', 'Golf cart', 'Jacket', 'Egg (Food)', 'Street light', + 'Guitar', 'Pillow', 'Human leg', 'Isopod', 'Grape', 'Human ear', + 'Power plugs and sockets', 'Panda', 'Giraffe', 'Woman', 'Door handle', + 'Rhinoceros', 'Bathtub', 'Goldfish', 'Houseplant', 'Goat', + 'Baseball bat', 'Baseball glove', 'Mixing bowl', + 'Marine invertebrates', 'Kitchen utensil', 'Light switch', 'House', + 'Horse', 'Stationary bicycle', 'Hammer', 'Ceiling fan', 'Sofa bed', + 'Adhesive tape', 'Harp', 'Sandal', 'Bicycle helmet', 'Saucer', + 'Harpsichord', 'Human hair', 'Heater', 'Harmonica', 'Hamster', + 'Curtain', 'Bed', 'Kettle', 'Fireplace', 'Scale', 'Drinking straw', + 'Insect', 'Hair dryer', 'Kitchenware', 'Indoor rower', 'Invertebrate', + 'Food processor', 'Bookcase', 'Refrigerator', 'Wood-burning stove', + 'Punching bag', 'Common fig', 'Cocktail shaker', 'Jaguar (Animal)', + 'Golf ball', 'Fashion accessory', 'Alarm clock', 'Filing cabinet', + 'Artichoke', 'Table', 'Tableware', 'Kangaroo', 'Koala', 'Knife', + 'Bottle', 'Bottle opener', 'Lynx', 'Lavender (Plant)', 'Lighthouse', + 'Dumbbell', 'Human head', 'Bowl', 'Humidifier', 'Porch', 'Lizard', + 'Billiard table', 'Mammal', 'Mouse', 'Motorcycle', + 'Musical instrument', 'Swim cap', 'Frying pan', 'Snowplow', + 'Bathroom cabinet', 'Missile', 'Bust', 'Man', 'Waffle iron', 'Milk', + 'Ring binder', 'Plate', 'Mobile phone', 'Baked goods', 'Mushroom', + 'Crutch', 'Pitcher (Container)', 'Mirror', 'Personal flotation device', + 'Table tennis racket', 'Pencil case', 'Musical keyboard', 'Scoreboard', + 'Briefcase', 'Kitchen knife', 'Nail (Construction)', 'Tennis ball', + 'Plastic bag', 'Oboe', 'Chest of drawers', 'Ostrich', 'Piano', 'Girl', + 'Plant', 'Potato', 'Hair spray', 'Sports equipment', 'Pasta', + 'Penguin', 'Pumpkin', 'Pear', 'Infant bed', 'Polar bear', 'Mixer', + 'Cupboard', 'Jacuzzi', 'Pizza', 'Digital clock', 'Pig', 'Reptile', + 'Rifle', 'Lipstick', 'Skateboard', 'Raven', 'High heels', 'Red panda', + 'Rose', 'Rabbit', 'Sculpture', 'Saxophone', 'Shotgun', 'Seafood', + 'Submarine sandwich', 'Snowboard', 'Sword', 'Picture frame', 'Sushi', + 'Loveseat', 'Ski', 'Squirrel', 'Tripod', 'Stethoscope', 'Submarine', + 'Scorpion', 'Segway', 'Training bench', 'Snake', 'Coffee table', + 'Skyscraper', 'Sheep', 'Television', 'Trombone', 'Tea', 'Tank', 'Taco', + 'Telephone', 'Torch', 'Tiger', 'Strawberry', 'Trumpet', 'Tree', + 'Tomato', 'Train', 'Tool', 'Picnic basket', 'Cooking spray', + 'Trousers', 'Bowling equipment', 'Football helmet', 'Truck', + 'Measuring cup', 'Coffeemaker', 'Violin', 'Vehicle', 'Handbag', + 'Paper cutter', 'Wine', 'Weapon', 'Wheel', 'Worm', 'Wok', 'Whale', + 'Zebra', 'Auto part', 'Jug', 'Pizza cutter', 'Cream', 'Monkey', 'Lion', + 'Bread', 'Platter', 'Chicken', 'Eagle', 'Helicopter', 'Owl', 'Duck', + 'Turtle', 'Hippopotamus', 'Crocodile', 'Toilet', 'Toilet paper', + 'Squid', 'Clothing', 'Footwear', 'Lemon', 'Spider', 'Deer', 'Frog', + 'Banana', 'Rocket', 'Wine glass', 'Countertop', 'Tablet computer', + 'Waste container', 'Swimming pool', 'Dog', 'Book', 'Elephant', 'Shark', + 'Candle', 'Leopard', 'Axe', 'Hand dryer', 'Soap dispenser', + 'Porcupine', 'Flower', 'Canary', 'Cheetah', 'Palm tree', 'Hamburger', + 'Maple', 'Building', 'Fish', 'Lobster', 'Garden Asparagus', + 'Furniture', 'Hedgehog', 'Airplane', 'Spoon', 'Otter', 'Bull', + 'Oyster', 'Horizontal bar', 'Convenience store', 'Bomb', 'Bench', + 'Ice cream', 'Caterpillar', 'Butterfly', 'Parachute', 'Orange', + 'Antelope', 'Beaker', 'Moths and butterflies', 'Window', 'Closet', + 'Castle', 'Jellyfish', 'Goose', 'Mule', 'Swan', 'Peach', 'Coconut', + 'Seat belt', 'Raccoon', 'Chisel', 'Fork', 'Lamp', 'Camera', + 'Squash (Plant)', 'Racket', 'Human face', 'Human arm', 'Vegetable', + 'Diaper', 'Unicycle', 'Falcon', 'Chime', 'Snail', 'Shellfish', + 'Cabbage', 'Carrot', 'Mango', 'Jeans', 'Flowerpot', 'Pineapple', + 'Drawer', 'Stool', 'Envelope', 'Cake', 'Dragonfly', 'Common sunflower', + 'Microwave oven', 'Honeycomb', 'Marine mammal', 'Sea lion', 'Ladybug', + 'Shelf', 'Watch', 'Candy', 'Salad', 'Parrot', 'Handgun', 'Sparrow', + 'Van', 'Grinder', 'Spice rack', 'Light bulb', 'Corded phone', + 'Sports uniform', 'Tennis racket', 'Wall clock', 'Serving tray', + 'Kitchen & dining room table', 'Dog bed', 'Cake stand', + 'Cat furniture', 'Bathroom accessory', 'Facial tissue holder', + 'Pressure cooker', 'Kitchen appliance', 'Tire', 'Ruler', + 'Luggage and bags', 'Microphone', 'Broccoli', 'Umbrella', 'Pastry', + 'Grapefruit', 'Band-aid', 'Animal', 'Bell pepper', 'Turkey', 'Lily', + 'Pomegranate', 'Doughnut', 'Glasses', 'Human nose', 'Pen', 'Ant', + 'Car', 'Aircraft', 'Human hand', 'Skunk', 'Teddy bear', 'Watermelon', + 'Cantaloupe', 'Dishwasher', 'Flute', 'Balance beam', 'Sandwich', + 'Shrimp', 'Sewing machine', 'Binoculars', 'Rays and skates', 'Ipod', + 'Accordion', 'Willow', 'Crab', 'Crown', 'Seahorse', 'Perfume', + 'Alpaca', 'Taxi', 'Canoe', 'Remote control', 'Wheelchair', + 'Rugby ball', 'Armadillo', 'Maracas', 'Helmet' + ] + + +def objects365v1_classes() -> list: + """Class names of Objects365 V1.""" + return [ + 'person', 'sneakers', 'chair', 'hat', 'lamp', 'bottle', + 'cabinet/shelf', 'cup', 'car', 'glasses', 'picture/frame', 'desk', + 'handbag', 'street lights', 'book', 'plate', 'helmet', 'leather shoes', + 'pillow', 'glove', 'potted plant', 'bracelet', 'flower', 'tv', + 'storage box', 'vase', 'bench', 'wine glass', 'boots', 'bowl', + 'dining table', 'umbrella', 'boat', 'flag', 'speaker', 'trash bin/can', + 'stool', 'backpack', 'couch', 'belt', 'carpet', 'basket', + 'towel/napkin', 'slippers', 'barrel/bucket', 'coffee table', 'suv', + 'toy', 'tie', 'bed', 'traffic light', 'pen/pencil', 'microphone', + 'sandals', 'canned', 'necklace', 'mirror', 'faucet', 'bicycle', + 'bread', 'high heels', 'ring', 'van', 'watch', 'sink', 'horse', 'fish', + 'apple', 'camera', 'candle', 'teddy bear', 'cake', 'motorcycle', + 'wild bird', 'laptop', 'knife', 'traffic sign', 'cell phone', 'paddle', + 'truck', 'cow', 'power outlet', 'clock', 'drum', 'fork', 'bus', + 'hanger', 'nightstand', 'pot/pan', 'sheep', 'guitar', 'traffic cone', + 'tea pot', 'keyboard', 'tripod', 'hockey', 'fan', 'dog', 'spoon', + 'blackboard/whiteboard', 'balloon', 'air conditioner', 'cymbal', + 'mouse', 'telephone', 'pickup truck', 'orange', 'banana', 'airplane', + 'luggage', 'skis', 'soccer', 'trolley', 'oven', 'remote', + 'baseball glove', 'paper towel', 'refrigerator', 'train', 'tomato', + 'machinery vehicle', 'tent', 'shampoo/shower gel', 'head phone', + 'lantern', 'donut', 'cleaning products', 'sailboat', 'tangerine', + 'pizza', 'kite', 'computer box', 'elephant', 'toiletries', 'gas stove', + 'broccoli', 'toilet', 'stroller', 'shovel', 'baseball bat', + 'microwave', 'skateboard', 'surfboard', 'surveillance camera', 'gun', + 'life saver', 'cat', 'lemon', 'liquid soap', 'zebra', 'duck', + 'sports car', 'giraffe', 'pumpkin', 'piano', 'stop sign', 'radiator', + 'converter', 'tissue ', 'carrot', 'washing machine', 'vent', 'cookies', + 'cutting/chopping board', 'tennis racket', 'candy', + 'skating and skiing shoes', 'scissors', 'folder', 'baseball', + 'strawberry', 'bow tie', 'pigeon', 'pepper', 'coffee machine', + 'bathtub', 'snowboard', 'suitcase', 'grapes', 'ladder', 'pear', + 'american football', 'basketball', 'potato', 'paint brush', 'printer', + 'billiards', 'fire hydrant', 'goose', 'projector', 'sausage', + 'fire extinguisher', 'extension cord', 'facial mask', 'tennis ball', + 'chopsticks', 'electronic stove and gas stove', 'pie', 'frisbee', + 'kettle', 'hamburger', 'golf club', 'cucumber', 'clutch', 'blender', + 'tong', 'slide', 'hot dog', 'toothbrush', 'facial cleanser', 'mango', + 'deer', 'egg', 'violin', 'marker', 'ship', 'chicken', 'onion', + 'ice cream', 'tape', 'wheelchair', 'plum', 'bar soap', 'scale', + 'watermelon', 'cabbage', 'router/modem', 'golf ball', 'pine apple', + 'crane', 'fire truck', 'peach', 'cello', 'notepaper', 'tricycle', + 'toaster', 'helicopter', 'green beans', 'brush', 'carriage', 'cigar', + 'earphone', 'penguin', 'hurdle', 'swing', 'radio', 'CD', + 'parking meter', 'swan', 'garlic', 'french fries', 'horn', 'avocado', + 'saxophone', 'trumpet', 'sandwich', 'cue', 'kiwi fruit', 'bear', + 'fishing rod', 'cherry', 'tablet', 'green vegetables', 'nuts', 'corn', + 'key', 'screwdriver', 'globe', 'broom', 'pliers', 'volleyball', + 'hammer', 'eggplant', 'trophy', 'dates', 'board eraser', 'rice', + 'tape measure/ruler', 'dumbbell', 'hamimelon', 'stapler', 'camel', + 'lettuce', 'goldfish', 'meat balls', 'medal', 'toothpaste', 'antelope', + 'shrimp', 'rickshaw', 'trombone', 'pomegranate', 'coconut', + 'jellyfish', 'mushroom', 'calculator', 'treadmill', 'butterfly', + 'egg tart', 'cheese', 'pig', 'pomelo', 'race car', 'rice cooker', + 'tuba', 'crosswalk sign', 'papaya', 'hair drier', 'green onion', + 'chips', 'dolphin', 'sushi', 'urinal', 'donkey', 'electric drill', + 'spring rolls', 'tortoise/turtle', 'parrot', 'flute', 'measuring cup', + 'shark', 'steak', 'poker card', 'binoculars', 'llama', 'radish', + 'noodles', 'yak', 'mop', 'crab', 'microscope', 'barbell', 'bread/bun', + 'baozi', 'lion', 'red cabbage', 'polar bear', 'lighter', 'seal', + 'mangosteen', 'comb', 'eraser', 'pitaya', 'scallop', 'pencil case', + 'saw', 'table tennis paddle', 'okra', 'starfish', 'eagle', 'monkey', + 'durian', 'game board', 'rabbit', 'french horn', 'ambulance', + 'asparagus', 'hoverboard', 'pasta', 'target', 'hotair balloon', + 'chainsaw', 'lobster', 'iron', 'flashlight' + ] + + +def objects365v2_classes() -> list: + """Class names of Objects365 V2.""" + return [ + 'Person', 'Sneakers', 'Chair', 'Other Shoes', 'Hat', 'Car', 'Lamp', + 'Glasses', 'Bottle', 'Desk', 'Cup', 'Street Lights', 'Cabinet/shelf', + 'Handbag/Satchel', 'Bracelet', 'Plate', 'Picture/Frame', 'Helmet', + 'Book', 'Gloves', 'Storage box', 'Boat', 'Leather Shoes', 'Flower', + 'Bench', 'Potted Plant', 'Bowl/Basin', 'Flag', 'Pillow', 'Boots', + 'Vase', 'Microphone', 'Necklace', 'Ring', 'SUV', 'Wine Glass', 'Belt', + 'Moniter/TV', 'Backpack', 'Umbrella', 'Traffic Light', 'Speaker', + 'Watch', 'Tie', 'Trash bin Can', 'Slippers', 'Bicycle', 'Stool', + 'Barrel/bucket', 'Van', 'Couch', 'Sandals', 'Bakset', 'Drum', + 'Pen/Pencil', 'Bus', 'Wild Bird', 'High Heels', 'Motorcycle', 'Guitar', + 'Carpet', 'Cell Phone', 'Bread', 'Camera', 'Canned', 'Truck', + 'Traffic cone', 'Cymbal', 'Lifesaver', 'Towel', 'Stuffed Toy', + 'Candle', 'Sailboat', 'Laptop', 'Awning', 'Bed', 'Faucet', 'Tent', + 'Horse', 'Mirror', 'Power outlet', 'Sink', 'Apple', 'Air Conditioner', + 'Knife', 'Hockey Stick', 'Paddle', 'Pickup Truck', 'Fork', + 'Traffic Sign', 'Ballon', 'Tripod', 'Dog', 'Spoon', 'Clock', 'Pot', + 'Cow', 'Cake', 'Dinning Table', 'Sheep', 'Hanger', + 'Blackboard/Whiteboard', 'Napkin', 'Other Fish', 'Orange/Tangerine', + 'Toiletry', 'Keyboard', 'Tomato', 'Lantern', 'Machinery Vehicle', + 'Fan', 'Green Vegetables', 'Banana', 'Baseball Glove', 'Airplane', + 'Mouse', 'Train', 'Pumpkin', 'Soccer', 'Skiboard', 'Luggage', + 'Nightstand', 'Tea pot', 'Telephone', 'Trolley', 'Head Phone', + 'Sports Car', 'Stop Sign', 'Dessert', 'Scooter', 'Stroller', 'Crane', + 'Remote', 'Refrigerator', 'Oven', 'Lemon', 'Duck', 'Baseball Bat', + 'Surveillance Camera', 'Cat', 'Jug', 'Broccoli', 'Piano', 'Pizza', + 'Elephant', 'Skateboard', 'Surfboard', 'Gun', + 'Skating and Skiing shoes', 'Gas stove', 'Donut', 'Bow Tie', 'Carrot', + 'Toilet', 'Kite', 'Strawberry', 'Other Balls', 'Shovel', 'Pepper', + 'Computer Box', 'Toilet Paper', 'Cleaning Products', 'Chopsticks', + 'Microwave', 'Pigeon', 'Baseball', 'Cutting/chopping Board', + 'Coffee Table', 'Side Table', 'Scissors', 'Marker', 'Pie', 'Ladder', + 'Snowboard', 'Cookies', 'Radiator', 'Fire Hydrant', 'Basketball', + 'Zebra', 'Grape', 'Giraffe', 'Potato', 'Sausage', 'Tricycle', 'Violin', + 'Egg', 'Fire Extinguisher', 'Candy', 'Fire Truck', 'Billards', + 'Converter', 'Bathtub', 'Wheelchair', 'Golf Club', 'Briefcase', + 'Cucumber', 'Cigar/Cigarette ', 'Paint Brush', 'Pear', 'Heavy Truck', + 'Hamburger', 'Extractor', 'Extention Cord', 'Tong', 'Tennis Racket', + 'Folder', 'American Football', 'earphone', 'Mask', 'Kettle', 'Tennis', + 'Ship', 'Swing', 'Coffee Machine', 'Slide', 'Carriage', 'Onion', + 'Green beans', 'Projector', 'Frisbee', + 'Washing Machine/Drying Machine', 'Chicken', 'Printer', 'Watermelon', + 'Saxophone', 'Tissue', 'Toothbrush', 'Ice cream', 'Hotair ballon', + 'Cello', 'French Fries', 'Scale', 'Trophy', 'Cabbage', 'Hot dog', + 'Blender', 'Peach', 'Rice', 'Wallet/Purse', 'Volleyball', 'Deer', + 'Goose', 'Tape', 'Tablet', 'Cosmetics', 'Trumpet', 'Pineapple', + 'Golf Ball', 'Ambulance', 'Parking meter', 'Mango', 'Key', 'Hurdle', + 'Fishing Rod', 'Medal', 'Flute', 'Brush', 'Penguin', 'Megaphone', + 'Corn', 'Lettuce', 'Garlic', 'Swan', 'Helicopter', 'Green Onion', + 'Sandwich', 'Nuts', 'Speed Limit Sign', 'Induction Cooker', 'Broom', + 'Trombone', 'Plum', 'Rickshaw', 'Goldfish', 'Kiwi fruit', + 'Router/modem', 'Poker Card', 'Toaster', 'Shrimp', 'Sushi', 'Cheese', + 'Notepaper', 'Cherry', 'Pliers', 'CD', 'Pasta', 'Hammer', 'Cue', + 'Avocado', 'Hamimelon', 'Flask', 'Mushroon', 'Screwdriver', 'Soap', + 'Recorder', 'Bear', 'Eggplant', 'Board Eraser', 'Coconut', + 'Tape Measur/ Ruler', 'Pig', 'Showerhead', 'Globe', 'Chips', 'Steak', + 'Crosswalk Sign', 'Stapler', 'Campel', 'Formula 1 ', 'Pomegranate', + 'Dishwasher', 'Crab', 'Hoverboard', 'Meat ball', 'Rice Cooker', 'Tuba', + 'Calculator', 'Papaya', 'Antelope', 'Parrot', 'Seal', 'Buttefly', + 'Dumbbell', 'Donkey', 'Lion', 'Urinal', 'Dolphin', 'Electric Drill', + 'Hair Dryer', 'Egg tart', 'Jellyfish', 'Treadmill', 'Lighter', + 'Grapefruit', 'Game board', 'Mop', 'Radish', 'Baozi', 'Target', + 'French', 'Spring Rolls', 'Monkey', 'Rabbit', 'Pencil Case', 'Yak', + 'Red Cabbage', 'Binoculars', 'Asparagus', 'Barbell', 'Scallop', + 'Noddles', 'Comb', 'Dumpling', 'Oyster', 'Table Teniis paddle', + 'Cosmetics Brush/Eyeliner Pencil', 'Chainsaw', 'Eraser', 'Lobster', + 'Durian', 'Okra', 'Lipstick', 'Cosmetics Mirror', 'Curling', + 'Table Tennis ' + ] + + +def lvis_classes() -> list: + """Class names of LVIS.""" + return [ + 'aerosol_can', 'air_conditioner', 'airplane', 'alarm_clock', 'alcohol', + 'alligator', 'almond', 'ambulance', 'amplifier', 'anklet', 'antenna', + 'apple', 'applesauce', 'apricot', 'apron', 'aquarium', + 'arctic_(type_of_shoe)', 'armband', 'armchair', 'armoire', 'armor', + 'artichoke', 'trash_can', 'ashtray', 'asparagus', 'atomizer', + 'avocado', 'award', 'awning', 'ax', 'baboon', 'baby_buggy', + 'basketball_backboard', 'backpack', 'handbag', 'suitcase', 'bagel', + 'bagpipe', 'baguet', 'bait', 'ball', 'ballet_skirt', 'balloon', + 'bamboo', 'banana', 'Band_Aid', 'bandage', 'bandanna', 'banjo', + 'banner', 'barbell', 'barge', 'barrel', 'barrette', 'barrow', + 'baseball_base', 'baseball', 'baseball_bat', 'baseball_cap', + 'baseball_glove', 'basket', 'basketball', 'bass_horn', 'bat_(animal)', + 'bath_mat', 'bath_towel', 'bathrobe', 'bathtub', 'batter_(food)', + 'battery', 'beachball', 'bead', 'bean_curd', 'beanbag', 'beanie', + 'bear', 'bed', 'bedpan', 'bedspread', 'cow', 'beef_(food)', 'beeper', + 'beer_bottle', 'beer_can', 'beetle', 'bell', 'bell_pepper', 'belt', + 'belt_buckle', 'bench', 'beret', 'bib', 'Bible', 'bicycle', 'visor', + 'billboard', 'binder', 'binoculars', 'bird', 'birdfeeder', 'birdbath', + 'birdcage', 'birdhouse', 'birthday_cake', 'birthday_card', + 'pirate_flag', 'black_sheep', 'blackberry', 'blackboard', 'blanket', + 'blazer', 'blender', 'blimp', 'blinker', 'blouse', 'blueberry', + 'gameboard', 'boat', 'bob', 'bobbin', 'bobby_pin', 'boiled_egg', + 'bolo_tie', 'deadbolt', 'bolt', 'bonnet', 'book', 'bookcase', + 'booklet', 'bookmark', 'boom_microphone', 'boot', 'bottle', + 'bottle_opener', 'bouquet', 'bow_(weapon)', 'bow_(decorative_ribbons)', + 'bow-tie', 'bowl', 'pipe_bowl', 'bowler_hat', 'bowling_ball', 'box', + 'boxing_glove', 'suspenders', 'bracelet', 'brass_plaque', 'brassiere', + 'bread-bin', 'bread', 'breechcloth', 'bridal_gown', 'briefcase', + 'broccoli', 'broach', 'broom', 'brownie', 'brussels_sprouts', + 'bubble_gum', 'bucket', 'horse_buggy', 'bull', 'bulldog', 'bulldozer', + 'bullet_train', 'bulletin_board', 'bulletproof_vest', 'bullhorn', + 'bun', 'bunk_bed', 'buoy', 'burrito', 'bus_(vehicle)', 'business_card', + 'butter', 'butterfly', 'button', 'cab_(taxi)', 'cabana', 'cabin_car', + 'cabinet', 'locker', 'cake', 'calculator', 'calendar', 'calf', + 'camcorder', 'camel', 'camera', 'camera_lens', 'camper_(vehicle)', + 'can', 'can_opener', 'candle', 'candle_holder', 'candy_bar', + 'candy_cane', 'walking_cane', 'canister', 'canoe', 'cantaloup', + 'canteen', 'cap_(headwear)', 'bottle_cap', 'cape', 'cappuccino', + 'car_(automobile)', 'railcar_(part_of_a_train)', 'elevator_car', + 'car_battery', 'identity_card', 'card', 'cardigan', 'cargo_ship', + 'carnation', 'horse_carriage', 'carrot', 'tote_bag', 'cart', 'carton', + 'cash_register', 'casserole', 'cassette', 'cast', 'cat', 'cauliflower', + 'cayenne_(spice)', 'CD_player', 'celery', 'cellular_telephone', + 'chain_mail', 'chair', 'chaise_longue', 'chalice', 'chandelier', + 'chap', 'checkbook', 'checkerboard', 'cherry', 'chessboard', + 'chicken_(animal)', 'chickpea', 'chili_(vegetable)', 'chime', + 'chinaware', 'crisp_(potato_chip)', 'poker_chip', 'chocolate_bar', + 'chocolate_cake', 'chocolate_milk', 'chocolate_mousse', 'choker', + 'chopping_board', 'chopstick', 'Christmas_tree', 'slide', 'cider', + 'cigar_box', 'cigarette', 'cigarette_case', 'cistern', 'clarinet', + 'clasp', 'cleansing_agent', 'cleat_(for_securing_rope)', 'clementine', + 'clip', 'clipboard', 'clippers_(for_plants)', 'cloak', 'clock', + 'clock_tower', 'clothes_hamper', 'clothespin', 'clutch_bag', 'coaster', + 'coat', 'coat_hanger', 'coatrack', 'cock', 'cockroach', + 'cocoa_(beverage)', 'coconut', 'coffee_maker', 'coffee_table', + 'coffeepot', 'coil', 'coin', 'colander', 'coleslaw', + 'coloring_material', 'combination_lock', 'pacifier', 'comic_book', + 'compass', 'computer_keyboard', 'condiment', 'cone', 'control', + 'convertible_(automobile)', 'sofa_bed', 'cooker', 'cookie', + 'cooking_utensil', 'cooler_(for_food)', 'cork_(bottle_plug)', + 'corkboard', 'corkscrew', 'edible_corn', 'cornbread', 'cornet', + 'cornice', 'cornmeal', 'corset', 'costume', 'cougar', 'coverall', + 'cowbell', 'cowboy_hat', 'crab_(animal)', 'crabmeat', 'cracker', + 'crape', 'crate', 'crayon', 'cream_pitcher', 'crescent_roll', 'crib', + 'crock_pot', 'crossbar', 'crouton', 'crow', 'crowbar', 'crown', + 'crucifix', 'cruise_ship', 'police_cruiser', 'crumb', 'crutch', + 'cub_(animal)', 'cube', 'cucumber', 'cufflink', 'cup', 'trophy_cup', + 'cupboard', 'cupcake', 'hair_curler', 'curling_iron', 'curtain', + 'cushion', 'cylinder', 'cymbal', 'dagger', 'dalmatian', 'dartboard', + 'date_(fruit)', 'deck_chair', 'deer', 'dental_floss', 'desk', + 'detergent', 'diaper', 'diary', 'die', 'dinghy', 'dining_table', 'tux', + 'dish', 'dish_antenna', 'dishrag', 'dishtowel', 'dishwasher', + 'dishwasher_detergent', 'dispenser', 'diving_board', 'Dixie_cup', + 'dog', 'dog_collar', 'doll', 'dollar', 'dollhouse', 'dolphin', + 'domestic_ass', 'doorknob', 'doormat', 'doughnut', 'dove', 'dragonfly', + 'drawer', 'underdrawers', 'dress', 'dress_hat', 'dress_suit', + 'dresser', 'drill', 'drone', 'dropper', 'drum_(musical_instrument)', + 'drumstick', 'duck', 'duckling', 'duct_tape', 'duffel_bag', 'dumbbell', + 'dumpster', 'dustpan', 'eagle', 'earphone', 'earplug', 'earring', + 'easel', 'eclair', 'eel', 'egg', 'egg_roll', 'egg_yolk', 'eggbeater', + 'eggplant', 'electric_chair', 'refrigerator', 'elephant', 'elk', + 'envelope', 'eraser', 'escargot', 'eyepatch', 'falcon', 'fan', + 'faucet', 'fedora', 'ferret', 'Ferris_wheel', 'ferry', 'fig_(fruit)', + 'fighter_jet', 'figurine', 'file_cabinet', 'file_(tool)', 'fire_alarm', + 'fire_engine', 'fire_extinguisher', 'fire_hose', 'fireplace', + 'fireplug', 'first-aid_kit', 'fish', 'fish_(food)', 'fishbowl', + 'fishing_rod', 'flag', 'flagpole', 'flamingo', 'flannel', 'flap', + 'flash', 'flashlight', 'fleece', 'flip-flop_(sandal)', + 'flipper_(footwear)', 'flower_arrangement', 'flute_glass', 'foal', + 'folding_chair', 'food_processor', 'football_(American)', + 'football_helmet', 'footstool', 'fork', 'forklift', 'freight_car', + 'French_toast', 'freshener', 'frisbee', 'frog', 'fruit_juice', + 'frying_pan', 'fudge', 'funnel', 'futon', 'gag', 'garbage', + 'garbage_truck', 'garden_hose', 'gargle', 'gargoyle', 'garlic', + 'gasmask', 'gazelle', 'gelatin', 'gemstone', 'generator', + 'giant_panda', 'gift_wrap', 'ginger', 'giraffe', 'cincture', + 'glass_(drink_container)', 'globe', 'glove', 'goat', 'goggles', + 'goldfish', 'golf_club', 'golfcart', 'gondola_(boat)', 'goose', + 'gorilla', 'gourd', 'grape', 'grater', 'gravestone', 'gravy_boat', + 'green_bean', 'green_onion', 'griddle', 'grill', 'grits', 'grizzly', + 'grocery_bag', 'guitar', 'gull', 'gun', 'hairbrush', 'hairnet', + 'hairpin', 'halter_top', 'ham', 'hamburger', 'hammer', 'hammock', + 'hamper', 'hamster', 'hair_dryer', 'hand_glass', 'hand_towel', + 'handcart', 'handcuff', 'handkerchief', 'handle', 'handsaw', + 'hardback_book', 'harmonium', 'hat', 'hatbox', 'veil', 'headband', + 'headboard', 'headlight', 'headscarf', 'headset', + 'headstall_(for_horses)', 'heart', 'heater', 'helicopter', 'helmet', + 'heron', 'highchair', 'hinge', 'hippopotamus', 'hockey_stick', 'hog', + 'home_plate_(baseball)', 'honey', 'fume_hood', 'hook', 'hookah', + 'hornet', 'horse', 'hose', 'hot-air_balloon', 'hotplate', 'hot_sauce', + 'hourglass', 'houseboat', 'hummingbird', 'hummus', 'polar_bear', + 'icecream', 'popsicle', 'ice_maker', 'ice_pack', 'ice_skate', + 'igniter', 'inhaler', 'iPod', 'iron_(for_clothing)', 'ironing_board', + 'jacket', 'jam', 'jar', 'jean', 'jeep', 'jelly_bean', 'jersey', + 'jet_plane', 'jewel', 'jewelry', 'joystick', 'jumpsuit', 'kayak', + 'keg', 'kennel', 'kettle', 'key', 'keycard', 'kilt', 'kimono', + 'kitchen_sink', 'kitchen_table', 'kite', 'kitten', 'kiwi_fruit', + 'knee_pad', 'knife', 'knitting_needle', 'knob', 'knocker_(on_a_door)', + 'koala', 'lab_coat', 'ladder', 'ladle', 'ladybug', 'lamb_(animal)', + 'lamb-chop', 'lamp', 'lamppost', 'lampshade', 'lantern', 'lanyard', + 'laptop_computer', 'lasagna', 'latch', 'lawn_mower', 'leather', + 'legging_(clothing)', 'Lego', 'legume', 'lemon', 'lemonade', 'lettuce', + 'license_plate', 'life_buoy', 'life_jacket', 'lightbulb', + 'lightning_rod', 'lime', 'limousine', 'lion', 'lip_balm', 'liquor', + 'lizard', 'log', 'lollipop', 'speaker_(stereo_equipment)', 'loveseat', + 'machine_gun', 'magazine', 'magnet', 'mail_slot', 'mailbox_(at_home)', + 'mallard', 'mallet', 'mammoth', 'manatee', 'mandarin_orange', 'manger', + 'manhole', 'map', 'marker', 'martini', 'mascot', 'mashed_potato', + 'masher', 'mask', 'mast', 'mat_(gym_equipment)', 'matchbox', + 'mattress', 'measuring_cup', 'measuring_stick', 'meatball', 'medicine', + 'melon', 'microphone', 'microscope', 'microwave_oven', 'milestone', + 'milk', 'milk_can', 'milkshake', 'minivan', 'mint_candy', 'mirror', + 'mitten', 'mixer_(kitchen_tool)', 'money', + 'monitor_(computer_equipment) computer_monitor', 'monkey', 'motor', + 'motor_scooter', 'motor_vehicle', 'motorcycle', 'mound_(baseball)', + 'mouse_(computer_equipment)', 'mousepad', 'muffin', 'mug', 'mushroom', + 'music_stool', 'musical_instrument', 'nailfile', 'napkin', + 'neckerchief', 'necklace', 'necktie', 'needle', 'nest', 'newspaper', + 'newsstand', 'nightshirt', 'nosebag_(for_animals)', + 'noseband_(for_animals)', 'notebook', 'notepad', 'nut', 'nutcracker', + 'oar', 'octopus_(food)', 'octopus_(animal)', 'oil_lamp', 'olive_oil', + 'omelet', 'onion', 'orange_(fruit)', 'orange_juice', 'ostrich', + 'ottoman', 'oven', 'overalls_(clothing)', 'owl', 'packet', 'inkpad', + 'pad', 'paddle', 'padlock', 'paintbrush', 'painting', 'pajamas', + 'palette', 'pan_(for_cooking)', 'pan_(metal_container)', 'pancake', + 'pantyhose', 'papaya', 'paper_plate', 'paper_towel', 'paperback_book', + 'paperweight', 'parachute', 'parakeet', 'parasail_(sports)', 'parasol', + 'parchment', 'parka', 'parking_meter', 'parrot', + 'passenger_car_(part_of_a_train)', 'passenger_ship', 'passport', + 'pastry', 'patty_(food)', 'pea_(food)', 'peach', 'peanut_butter', + 'pear', 'peeler_(tool_for_fruit_and_vegetables)', 'wooden_leg', + 'pegboard', 'pelican', 'pen', 'pencil', 'pencil_box', + 'pencil_sharpener', 'pendulum', 'penguin', 'pennant', 'penny_(coin)', + 'pepper', 'pepper_mill', 'perfume', 'persimmon', 'person', 'pet', + 'pew_(church_bench)', 'phonebook', 'phonograph_record', 'piano', + 'pickle', 'pickup_truck', 'pie', 'pigeon', 'piggy_bank', 'pillow', + 'pin_(non_jewelry)', 'pineapple', 'pinecone', 'ping-pong_ball', + 'pinwheel', 'tobacco_pipe', 'pipe', 'pistol', 'pita_(bread)', + 'pitcher_(vessel_for_liquid)', 'pitchfork', 'pizza', 'place_mat', + 'plate', 'platter', 'playpen', 'pliers', 'plow_(farm_equipment)', + 'plume', 'pocket_watch', 'pocketknife', 'poker_(fire_stirring_tool)', + 'pole', 'polo_shirt', 'poncho', 'pony', 'pool_table', 'pop_(soda)', + 'postbox_(public)', 'postcard', 'poster', 'pot', 'flowerpot', 'potato', + 'potholder', 'pottery', 'pouch', 'power_shovel', 'prawn', 'pretzel', + 'printer', 'projectile_(weapon)', 'projector', 'propeller', 'prune', + 'pudding', 'puffer_(fish)', 'puffin', 'pug-dog', 'pumpkin', 'puncher', + 'puppet', 'puppy', 'quesadilla', 'quiche', 'quilt', 'rabbit', + 'race_car', 'racket', 'radar', 'radiator', 'radio_receiver', 'radish', + 'raft', 'rag_doll', 'raincoat', 'ram_(animal)', 'raspberry', 'rat', + 'razorblade', 'reamer_(juicer)', 'rearview_mirror', 'receipt', + 'recliner', 'record_player', 'reflector', 'remote_control', + 'rhinoceros', 'rib_(food)', 'rifle', 'ring', 'river_boat', 'road_map', + 'robe', 'rocking_chair', 'rodent', 'roller_skate', 'Rollerblade', + 'rolling_pin', 'root_beer', 'router_(computer_equipment)', + 'rubber_band', 'runner_(carpet)', 'plastic_bag', + 'saddle_(on_an_animal)', 'saddle_blanket', 'saddlebag', 'safety_pin', + 'sail', 'salad', 'salad_plate', 'salami', 'salmon_(fish)', + 'salmon_(food)', 'salsa', 'saltshaker', 'sandal_(type_of_shoe)', + 'sandwich', 'satchel', 'saucepan', 'saucer', 'sausage', 'sawhorse', + 'saxophone', 'scale_(measuring_instrument)', 'scarecrow', 'scarf', + 'school_bus', 'scissors', 'scoreboard', 'scraper', 'screwdriver', + 'scrubbing_brush', 'sculpture', 'seabird', 'seahorse', 'seaplane', + 'seashell', 'sewing_machine', 'shaker', 'shampoo', 'shark', + 'sharpener', 'Sharpie', 'shaver_(electric)', 'shaving_cream', 'shawl', + 'shears', 'sheep', 'shepherd_dog', 'sherbert', 'shield', 'shirt', + 'shoe', 'shopping_bag', 'shopping_cart', 'short_pants', 'shot_glass', + 'shoulder_bag', 'shovel', 'shower_head', 'shower_cap', + 'shower_curtain', 'shredder_(for_paper)', 'signboard', 'silo', 'sink', + 'skateboard', 'skewer', 'ski', 'ski_boot', 'ski_parka', 'ski_pole', + 'skirt', 'skullcap', 'sled', 'sleeping_bag', 'sling_(bandage)', + 'slipper_(footwear)', 'smoothie', 'snake', 'snowboard', 'snowman', + 'snowmobile', 'soap', 'soccer_ball', 'sock', 'sofa', 'softball', + 'solar_array', 'sombrero', 'soup', 'soup_bowl', 'soupspoon', + 'sour_cream', 'soya_milk', 'space_shuttle', 'sparkler_(fireworks)', + 'spatula', 'spear', 'spectacles', 'spice_rack', 'spider', 'crawfish', + 'sponge', 'spoon', 'sportswear', 'spotlight', 'squid_(food)', + 'squirrel', 'stagecoach', 'stapler_(stapling_machine)', 'starfish', + 'statue_(sculpture)', 'steak_(food)', 'steak_knife', 'steering_wheel', + 'stepladder', 'step_stool', 'stereo_(sound_system)', 'stew', 'stirrer', + 'stirrup', 'stool', 'stop_sign', 'brake_light', 'stove', 'strainer', + 'strap', 'straw_(for_drinking)', 'strawberry', 'street_sign', + 'streetlight', 'string_cheese', 'stylus', 'subwoofer', 'sugar_bowl', + 'sugarcane_(plant)', 'suit_(clothing)', 'sunflower', 'sunglasses', + 'sunhat', 'surfboard', 'sushi', 'mop', 'sweat_pants', 'sweatband', + 'sweater', 'sweatshirt', 'sweet_potato', 'swimsuit', 'sword', + 'syringe', 'Tabasco_sauce', 'table-tennis_table', 'table', + 'table_lamp', 'tablecloth', 'tachometer', 'taco', 'tag', 'taillight', + 'tambourine', 'army_tank', 'tank_(storage_vessel)', + 'tank_top_(clothing)', 'tape_(sticky_cloth_or_paper)', 'tape_measure', + 'tapestry', 'tarp', 'tartan', 'tassel', 'tea_bag', 'teacup', + 'teakettle', 'teapot', 'teddy_bear', 'telephone', 'telephone_booth', + 'telephone_pole', 'telephoto_lens', 'television_camera', + 'television_set', 'tennis_ball', 'tennis_racket', 'tequila', + 'thermometer', 'thermos_bottle', 'thermostat', 'thimble', 'thread', + 'thumbtack', 'tiara', 'tiger', 'tights_(clothing)', 'timer', 'tinfoil', + 'tinsel', 'tissue_paper', 'toast_(food)', 'toaster', 'toaster_oven', + 'toilet', 'toilet_tissue', 'tomato', 'tongs', 'toolbox', 'toothbrush', + 'toothpaste', 'toothpick', 'cover', 'tortilla', 'tow_truck', 'towel', + 'towel_rack', 'toy', 'tractor_(farm_equipment)', 'traffic_light', + 'dirt_bike', 'trailer_truck', 'train_(railroad_vehicle)', 'trampoline', + 'tray', 'trench_coat', 'triangle_(musical_instrument)', 'tricycle', + 'tripod', 'trousers', 'truck', 'truffle_(chocolate)', 'trunk', 'vat', + 'turban', 'turkey_(food)', 'turnip', 'turtle', 'turtleneck_(clothing)', + 'typewriter', 'umbrella', 'underwear', 'unicycle', 'urinal', 'urn', + 'vacuum_cleaner', 'vase', 'vending_machine', 'vent', 'vest', + 'videotape', 'vinegar', 'violin', 'vodka', 'volleyball', 'vulture', + 'waffle', 'waffle_iron', 'wagon', 'wagon_wheel', 'walking_stick', + 'wall_clock', 'wall_socket', 'wallet', 'walrus', 'wardrobe', + 'washbasin', 'automatic_washer', 'watch', 'water_bottle', + 'water_cooler', 'water_faucet', 'water_heater', 'water_jug', + 'water_gun', 'water_scooter', 'water_ski', 'water_tower', + 'watering_can', 'watermelon', 'weathervane', 'webcam', 'wedding_cake', + 'wedding_ring', 'wet_suit', 'wheel', 'wheelchair', 'whipped_cream', + 'whistle', 'wig', 'wind_chime', 'windmill', 'window_box_(for_plants)', + 'windshield_wiper', 'windsock', 'wine_bottle', 'wine_bucket', + 'wineglass', 'blinder_(for_horses)', 'wok', 'wolf', 'wooden_spoon', + 'wreath', 'wrench', 'wristband', 'wristlet', 'yacht', 'yogurt', + 'yoke_(animal_equipment)', 'zebra', 'zucchini' + ] + + +dataset_aliases = { + 'voc': ['voc', 'pascal_voc', 'voc07', 'voc12'], + 'imagenet_det': ['det', 'imagenet_det', 'ilsvrc_det'], + 'imagenet_vid': ['vid', 'imagenet_vid', 'ilsvrc_vid'], + 'coco': ['coco', 'mscoco', 'ms_coco'], + 'coco_panoptic': ['coco_panoptic', 'panoptic'], + 'wider_face': ['WIDERFaceDataset', 'wider_face', 'WIDERFace'], + 'cityscapes': ['cityscapes'], + 'oid_challenge': ['oid_challenge', 'openimages_challenge'], + 'oid_v6': ['oid_v6', 'openimages_v6'], + 'objects365v1': ['objects365v1', 'obj365v1'], + 'objects365v2': ['objects365v2', 'obj365v2'], + 'lvis': ['lvis', 'lvis_v1'], +} + + +def get_classes(dataset) -> list: + """Get class names of a dataset.""" + alias2name = {} + for name, aliases in dataset_aliases.items(): + for alias in aliases: + alias2name[alias] = name + + if is_str(dataset): + if dataset in alias2name: + labels = eval(alias2name[dataset] + '_classes()') + else: + raise ValueError(f'Unrecognized dataset: {dataset}') + else: + raise TypeError(f'dataset must a str, but got {type(dataset)}') + return labels diff --git a/mmdetection/mmdet/evaluation/functional/mean_ap.py b/mmdetection/mmdet/evaluation/functional/mean_ap.py new file mode 100644 index 00000000..989972a4 --- /dev/null +++ b/mmdetection/mmdet/evaluation/functional/mean_ap.py @@ -0,0 +1,792 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from multiprocessing import Pool + +import numpy as np +from mmengine.logging import print_log +from mmengine.utils import is_str +from terminaltables import AsciiTable + +from .bbox_overlaps import bbox_overlaps +from .class_names import get_classes + + +def average_precision(recalls, precisions, mode='area'): + """Calculate average precision (for single or multiple scales). + + Args: + recalls (ndarray): shape (num_scales, num_dets) or (num_dets, ) + precisions (ndarray): shape (num_scales, num_dets) or (num_dets, ) + mode (str): 'area' or '11points', 'area' means calculating the area + under precision-recall curve, '11points' means calculating + the average precision of recalls at [0, 0.1, ..., 1] + + Returns: + float or ndarray: calculated average precision + """ + no_scale = False + if recalls.ndim == 1: + no_scale = True + recalls = recalls[np.newaxis, :] + precisions = precisions[np.newaxis, :] + assert recalls.shape == precisions.shape and recalls.ndim == 2 + num_scales = recalls.shape[0] + ap = np.zeros(num_scales, dtype=np.float32) + if mode == 'area': + zeros = np.zeros((num_scales, 1), dtype=recalls.dtype) + ones = np.ones((num_scales, 1), dtype=recalls.dtype) + mrec = np.hstack((zeros, recalls, ones)) + mpre = np.hstack((zeros, precisions, zeros)) + for i in range(mpre.shape[1] - 1, 0, -1): + mpre[:, i - 1] = np.maximum(mpre[:, i - 1], mpre[:, i]) + for i in range(num_scales): + ind = np.where(mrec[i, 1:] != mrec[i, :-1])[0] + ap[i] = np.sum( + (mrec[i, ind + 1] - mrec[i, ind]) * mpre[i, ind + 1]) + elif mode == '11points': + for i in range(num_scales): + for thr in np.arange(0, 1 + 1e-3, 0.1): + precs = precisions[i, recalls[i, :] >= thr] + prec = precs.max() if precs.size > 0 else 0 + ap[i] += prec + ap /= 11 + else: + raise ValueError( + 'Unrecognized mode, only "area" and "11points" are supported') + if no_scale: + ap = ap[0] + return ap + + +def tpfp_imagenet(det_bboxes, + gt_bboxes, + gt_bboxes_ignore=None, + default_iou_thr=0.5, + area_ranges=None, + use_legacy_coordinate=False, + **kwargs): + """Check if detected bboxes are true positive or false positive. + + Args: + det_bbox (ndarray): Detected bboxes of this image, of shape (m, 5). + gt_bboxes (ndarray): GT bboxes of this image, of shape (n, 4). + gt_bboxes_ignore (ndarray): Ignored gt bboxes of this image, + of shape (k, 4). Defaults to None + default_iou_thr (float): IoU threshold to be considered as matched for + medium and large bboxes (small ones have special rules). + Defaults to 0.5. + area_ranges (list[tuple] | None): Range of bbox areas to be evaluated, + in the format [(min1, max1), (min2, max2), ...]. Defaults to None. + use_legacy_coordinate (bool): Whether to use coordinate system in + mmdet v1.x. which means width, height should be + calculated as 'x2 - x1 + 1` and 'y2 - y1 + 1' respectively. + Defaults to False. + + Returns: + tuple[np.ndarray]: (tp, fp) whose elements are 0 and 1. The shape of + each array is (num_scales, m). + """ + + if not use_legacy_coordinate: + extra_length = 0. + else: + extra_length = 1. + + # an indicator of ignored gts + gt_ignore_inds = np.concatenate( + (np.zeros(gt_bboxes.shape[0], + dtype=bool), np.ones(gt_bboxes_ignore.shape[0], dtype=bool))) + # stack gt_bboxes and gt_bboxes_ignore for convenience + gt_bboxes = np.vstack((gt_bboxes, gt_bboxes_ignore)) + + num_dets = det_bboxes.shape[0] + num_gts = gt_bboxes.shape[0] + if area_ranges is None: + area_ranges = [(None, None)] + num_scales = len(area_ranges) + # tp and fp are of shape (num_scales, num_gts), each row is tp or fp + # of a certain scale. + tp = np.zeros((num_scales, num_dets), dtype=np.float32) + fp = np.zeros((num_scales, num_dets), dtype=np.float32) + if gt_bboxes.shape[0] == 0: + if area_ranges == [(None, None)]: + fp[...] = 1 + else: + det_areas = ( + det_bboxes[:, 2] - det_bboxes[:, 0] + extra_length) * ( + det_bboxes[:, 3] - det_bboxes[:, 1] + extra_length) + for i, (min_area, max_area) in enumerate(area_ranges): + fp[i, (det_areas >= min_area) & (det_areas < max_area)] = 1 + return tp, fp + ious = bbox_overlaps( + det_bboxes, gt_bboxes - 1, use_legacy_coordinate=use_legacy_coordinate) + gt_w = gt_bboxes[:, 2] - gt_bboxes[:, 0] + extra_length + gt_h = gt_bboxes[:, 3] - gt_bboxes[:, 1] + extra_length + iou_thrs = np.minimum((gt_w * gt_h) / ((gt_w + 10.0) * (gt_h + 10.0)), + default_iou_thr) + # sort all detections by scores in descending order + sort_inds = np.argsort(-det_bboxes[:, -1]) + for k, (min_area, max_area) in enumerate(area_ranges): + gt_covered = np.zeros(num_gts, dtype=bool) + # if no area range is specified, gt_area_ignore is all False + if min_area is None: + gt_area_ignore = np.zeros_like(gt_ignore_inds, dtype=bool) + else: + gt_areas = gt_w * gt_h + gt_area_ignore = (gt_areas < min_area) | (gt_areas >= max_area) + for i in sort_inds: + max_iou = -1 + matched_gt = -1 + # find best overlapped available gt + for j in range(num_gts): + # different from PASCAL VOC: allow finding other gts if the + # best overlapped ones are already matched by other det bboxes + if gt_covered[j]: + continue + elif ious[i, j] >= iou_thrs[j] and ious[i, j] > max_iou: + max_iou = ious[i, j] + matched_gt = j + # there are 4 cases for a det bbox: + # 1. it matches a gt, tp = 1, fp = 0 + # 2. it matches an ignored gt, tp = 0, fp = 0 + # 3. it matches no gt and within area range, tp = 0, fp = 1 + # 4. it matches no gt but is beyond area range, tp = 0, fp = 0 + if matched_gt >= 0: + gt_covered[matched_gt] = 1 + if not (gt_ignore_inds[matched_gt] + or gt_area_ignore[matched_gt]): + tp[k, i] = 1 + elif min_area is None: + fp[k, i] = 1 + else: + bbox = det_bboxes[i, :4] + area = (bbox[2] - bbox[0] + extra_length) * ( + bbox[3] - bbox[1] + extra_length) + if area >= min_area and area < max_area: + fp[k, i] = 1 + return tp, fp + + +def tpfp_default(det_bboxes, + gt_bboxes, + gt_bboxes_ignore=None, + iou_thr=0.5, + area_ranges=None, + use_legacy_coordinate=False, + **kwargs): + """Check if detected bboxes are true positive or false positive. + + Args: + det_bbox (ndarray): Detected bboxes of this image, of shape (m, 5). + gt_bboxes (ndarray): GT bboxes of this image, of shape (n, 4). + gt_bboxes_ignore (ndarray): Ignored gt bboxes of this image, + of shape (k, 4). Defaults to None + iou_thr (float): IoU threshold to be considered as matched. + Defaults to 0.5. + area_ranges (list[tuple] | None): Range of bbox areas to be + evaluated, in the format [(min1, max1), (min2, max2), ...]. + Defaults to None. + use_legacy_coordinate (bool): Whether to use coordinate system in + mmdet v1.x. which means width, height should be + calculated as 'x2 - x1 + 1` and 'y2 - y1 + 1' respectively. + Defaults to False. + + Returns: + tuple[np.ndarray]: (tp, fp) whose elements are 0 and 1. The shape of + each array is (num_scales, m). + """ + + if not use_legacy_coordinate: + extra_length = 0. + else: + extra_length = 1. + + # an indicator of ignored gts + gt_ignore_inds = np.concatenate( + (np.zeros(gt_bboxes.shape[0], + dtype=bool), np.ones(gt_bboxes_ignore.shape[0], dtype=bool))) + # stack gt_bboxes and gt_bboxes_ignore for convenience + gt_bboxes = np.vstack((gt_bboxes, gt_bboxes_ignore)) + + num_dets = det_bboxes.shape[0] + num_gts = gt_bboxes.shape[0] + if area_ranges is None: + area_ranges = [(None, None)] + num_scales = len(area_ranges) + # tp and fp are of shape (num_scales, num_gts), each row is tp or fp of + # a certain scale + tp = np.zeros((num_scales, num_dets), dtype=np.float32) + fp = np.zeros((num_scales, num_dets), dtype=np.float32) + + # if there is no gt bboxes in this image, then all det bboxes + # within area range are false positives + if gt_bboxes.shape[0] == 0: + if area_ranges == [(None, None)]: + fp[...] = 1 + else: + det_areas = ( + det_bboxes[:, 2] - det_bboxes[:, 0] + extra_length) * ( + det_bboxes[:, 3] - det_bboxes[:, 1] + extra_length) + for i, (min_area, max_area) in enumerate(area_ranges): + fp[i, (det_areas >= min_area) & (det_areas < max_area)] = 1 + return tp, fp + + ious = bbox_overlaps( + det_bboxes, gt_bboxes, use_legacy_coordinate=use_legacy_coordinate) + # for each det, the max iou with all gts + ious_max = ious.max(axis=1) + # for each det, which gt overlaps most with it + ious_argmax = ious.argmax(axis=1) + # sort all dets in descending order by scores + sort_inds = np.argsort(-det_bboxes[:, -1]) + for k, (min_area, max_area) in enumerate(area_ranges): + gt_covered = np.zeros(num_gts, dtype=bool) + # if no area range is specified, gt_area_ignore is all False + if min_area is None: + gt_area_ignore = np.zeros_like(gt_ignore_inds, dtype=bool) + else: + gt_areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0] + extra_length) * ( + gt_bboxes[:, 3] - gt_bboxes[:, 1] + extra_length) + gt_area_ignore = (gt_areas < min_area) | (gt_areas >= max_area) + for i in sort_inds: + if ious_max[i] >= iou_thr: + matched_gt = ious_argmax[i] + if not (gt_ignore_inds[matched_gt] + or gt_area_ignore[matched_gt]): + if not gt_covered[matched_gt]: + gt_covered[matched_gt] = True + tp[k, i] = 1 + else: + fp[k, i] = 1 + # otherwise ignore this detected bbox, tp = 0, fp = 0 + elif min_area is None: + fp[k, i] = 1 + else: + bbox = det_bboxes[i, :4] + area = (bbox[2] - bbox[0] + extra_length) * ( + bbox[3] - bbox[1] + extra_length) + if area >= min_area and area < max_area: + fp[k, i] = 1 + return tp, fp + + +def tpfp_openimages(det_bboxes, + gt_bboxes, + gt_bboxes_ignore=None, + iou_thr=0.5, + area_ranges=None, + use_legacy_coordinate=False, + gt_bboxes_group_of=None, + use_group_of=True, + ioa_thr=0.5, + **kwargs): + """Check if detected bboxes are true positive or false positive. + + Args: + det_bbox (ndarray): Detected bboxes of this image, of shape (m, 5). + gt_bboxes (ndarray): GT bboxes of this image, of shape (n, 4). + gt_bboxes_ignore (ndarray): Ignored gt bboxes of this image, + of shape (k, 4). Defaults to None + iou_thr (float): IoU threshold to be considered as matched. + Defaults to 0.5. + area_ranges (list[tuple] | None): Range of bbox areas to be + evaluated, in the format [(min1, max1), (min2, max2), ...]. + Defaults to None. + use_legacy_coordinate (bool): Whether to use coordinate system in + mmdet v1.x. which means width, height should be + calculated as 'x2 - x1 + 1` and 'y2 - y1 + 1' respectively. + Defaults to False. + gt_bboxes_group_of (ndarray): GT group_of of this image, of shape + (k, 1). Defaults to None + use_group_of (bool): Whether to use group of when calculate TP and FP, + which only used in OpenImages evaluation. Defaults to True. + ioa_thr (float | None): IoA threshold to be considered as matched, + which only used in OpenImages evaluation. Defaults to 0.5. + + Returns: + tuple[np.ndarray]: Returns a tuple (tp, fp, det_bboxes), where + (tp, fp) whose elements are 0 and 1. The shape of each array is + (num_scales, m). (det_bboxes) whose will filter those are not + matched by group of gts when processing Open Images evaluation. + The shape is (num_scales, m). + """ + + if not use_legacy_coordinate: + extra_length = 0. + else: + extra_length = 1. + + # an indicator of ignored gts + gt_ignore_inds = np.concatenate( + (np.zeros(gt_bboxes.shape[0], + dtype=bool), np.ones(gt_bboxes_ignore.shape[0], dtype=bool))) + # stack gt_bboxes and gt_bboxes_ignore for convenience + gt_bboxes = np.vstack((gt_bboxes, gt_bboxes_ignore)) + + num_dets = det_bboxes.shape[0] + num_gts = gt_bboxes.shape[0] + if area_ranges is None: + area_ranges = [(None, None)] + num_scales = len(area_ranges) + # tp and fp are of shape (num_scales, num_gts), each row is tp or fp of + # a certain scale + tp = np.zeros((num_scales, num_dets), dtype=np.float32) + fp = np.zeros((num_scales, num_dets), dtype=np.float32) + + # if there is no gt bboxes in this image, then all det bboxes + # within area range are false positives + if gt_bboxes.shape[0] == 0: + if area_ranges == [(None, None)]: + fp[...] = 1 + else: + det_areas = ( + det_bboxes[:, 2] - det_bboxes[:, 0] + extra_length) * ( + det_bboxes[:, 3] - det_bboxes[:, 1] + extra_length) + for i, (min_area, max_area) in enumerate(area_ranges): + fp[i, (det_areas >= min_area) & (det_areas < max_area)] = 1 + return tp, fp, det_bboxes + + if gt_bboxes_group_of is not None and use_group_of: + # if handle group-of boxes, divided gt boxes into two parts: + # non-group-of and group-of.Then calculate ious and ioas through + # non-group-of group-of gts respectively. This only used in + # OpenImages evaluation. + assert gt_bboxes_group_of.shape[0] == gt_bboxes.shape[0] + non_group_gt_bboxes = gt_bboxes[~gt_bboxes_group_of] + group_gt_bboxes = gt_bboxes[gt_bboxes_group_of] + num_gts_group = group_gt_bboxes.shape[0] + ious = bbox_overlaps(det_bboxes, non_group_gt_bboxes) + ioas = bbox_overlaps(det_bboxes, group_gt_bboxes, mode='iof') + else: + # if not consider group-of boxes, only calculate ious through gt boxes + ious = bbox_overlaps( + det_bboxes, gt_bboxes, use_legacy_coordinate=use_legacy_coordinate) + ioas = None + + if ious.shape[1] > 0: + # for each det, the max iou with all gts + ious_max = ious.max(axis=1) + # for each det, which gt overlaps most with it + ious_argmax = ious.argmax(axis=1) + # sort all dets in descending order by scores + sort_inds = np.argsort(-det_bboxes[:, -1]) + for k, (min_area, max_area) in enumerate(area_ranges): + gt_covered = np.zeros(num_gts, dtype=bool) + # if no area range is specified, gt_area_ignore is all False + if min_area is None: + gt_area_ignore = np.zeros_like(gt_ignore_inds, dtype=bool) + else: + gt_areas = ( + gt_bboxes[:, 2] - gt_bboxes[:, 0] + extra_length) * ( + gt_bboxes[:, 3] - gt_bboxes[:, 1] + extra_length) + gt_area_ignore = (gt_areas < min_area) | (gt_areas >= max_area) + for i in sort_inds: + if ious_max[i] >= iou_thr: + matched_gt = ious_argmax[i] + if not (gt_ignore_inds[matched_gt] + or gt_area_ignore[matched_gt]): + if not gt_covered[matched_gt]: + gt_covered[matched_gt] = True + tp[k, i] = 1 + else: + fp[k, i] = 1 + # otherwise ignore this detected bbox, tp = 0, fp = 0 + elif min_area is None: + fp[k, i] = 1 + else: + bbox = det_bboxes[i, :4] + area = (bbox[2] - bbox[0] + extra_length) * ( + bbox[3] - bbox[1] + extra_length) + if area >= min_area and area < max_area: + fp[k, i] = 1 + else: + # if there is no no-group-of gt bboxes in this image, + # then all det bboxes within area range are false positives. + # Only used in OpenImages evaluation. + if area_ranges == [(None, None)]: + fp[...] = 1 + else: + det_areas = ( + det_bboxes[:, 2] - det_bboxes[:, 0] + extra_length) * ( + det_bboxes[:, 3] - det_bboxes[:, 1] + extra_length) + for i, (min_area, max_area) in enumerate(area_ranges): + fp[i, (det_areas >= min_area) & (det_areas < max_area)] = 1 + + if ioas is None or ioas.shape[1] <= 0: + return tp, fp, det_bboxes + else: + # The evaluation of group-of TP and FP are done in two stages: + # 1. All detections are first matched to non group-of boxes; true + # positives are determined. + # 2. Detections that are determined as false positives are matched + # against group-of boxes and calculated group-of TP and FP. + # Only used in OpenImages evaluation. + det_bboxes_group = np.zeros( + (num_scales, ioas.shape[1], det_bboxes.shape[1]), dtype=float) + match_group_of = np.zeros((num_scales, num_dets), dtype=bool) + tp_group = np.zeros((num_scales, num_gts_group), dtype=np.float32) + ioas_max = ioas.max(axis=1) + # for each det, which gt overlaps most with it + ioas_argmax = ioas.argmax(axis=1) + # sort all dets in descending order by scores + sort_inds = np.argsort(-det_bboxes[:, -1]) + for k, (min_area, max_area) in enumerate(area_ranges): + box_is_covered = tp[k] + # if no area range is specified, gt_area_ignore is all False + if min_area is None: + gt_area_ignore = np.zeros_like(gt_ignore_inds, dtype=bool) + else: + gt_areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0]) * ( + gt_bboxes[:, 3] - gt_bboxes[:, 1]) + gt_area_ignore = (gt_areas < min_area) | (gt_areas >= max_area) + for i in sort_inds: + matched_gt = ioas_argmax[i] + if not box_is_covered[i]: + if ioas_max[i] >= ioa_thr: + if not (gt_ignore_inds[matched_gt] + or gt_area_ignore[matched_gt]): + if not tp_group[k, matched_gt]: + tp_group[k, matched_gt] = 1 + match_group_of[k, i] = True + else: + match_group_of[k, i] = True + + if det_bboxes_group[k, matched_gt, -1] < \ + det_bboxes[i, -1]: + det_bboxes_group[k, matched_gt] = \ + det_bboxes[i] + + fp_group = (tp_group <= 0).astype(float) + tps = [] + fps = [] + # concatenate tp, fp, and det-boxes which not matched group of + # gt boxes and tp_group, fp_group, and det_bboxes_group which + # matched group of boxes respectively. + for i in range(num_scales): + tps.append( + np.concatenate((tp[i][~match_group_of[i]], tp_group[i]))) + fps.append( + np.concatenate((fp[i][~match_group_of[i]], fp_group[i]))) + det_bboxes = np.concatenate( + (det_bboxes[~match_group_of[i]], det_bboxes_group[i])) + + tp = np.vstack(tps) + fp = np.vstack(fps) + return tp, fp, det_bboxes + + +def get_cls_results(det_results, annotations, class_id): + """Get det results and gt information of a certain class. + + Args: + det_results (list[list]): Same as `eval_map()`. + annotations (list[dict]): Same as `eval_map()`. + class_id (int): ID of a specific class. + + Returns: + tuple[list[np.ndarray]]: detected bboxes, gt bboxes, ignored gt bboxes + """ + cls_dets = [img_res[class_id] for img_res in det_results] + cls_gts = [] + cls_gts_ignore = [] + for ann in annotations: + gt_inds = ann['labels'] == class_id + cls_gts.append(ann['bboxes'][gt_inds, :]) + + if ann.get('labels_ignore', None) is not None: + ignore_inds = ann['labels_ignore'] == class_id + cls_gts_ignore.append(ann['bboxes_ignore'][ignore_inds, :]) + else: + cls_gts_ignore.append(np.empty((0, 4), dtype=np.float32)) + + return cls_dets, cls_gts, cls_gts_ignore + + +def get_cls_group_ofs(annotations, class_id): + """Get `gt_group_of` of a certain class, which is used in Open Images. + + Args: + annotations (list[dict]): Same as `eval_map()`. + class_id (int): ID of a specific class. + + Returns: + list[np.ndarray]: `gt_group_of` of a certain class. + """ + gt_group_ofs = [] + for ann in annotations: + gt_inds = ann['labels'] == class_id + if ann.get('gt_is_group_ofs', None) is not None: + gt_group_ofs.append(ann['gt_is_group_ofs'][gt_inds]) + else: + gt_group_ofs.append(np.empty((0, 1), dtype=bool)) + + return gt_group_ofs + + +def eval_map(det_results, + annotations, + scale_ranges=None, + iou_thr=0.5, + ioa_thr=None, + dataset=None, + logger=None, + tpfp_fn=None, + nproc=4, + use_legacy_coordinate=False, + use_group_of=False, + eval_mode='area'): + """Evaluate mAP of a dataset. + + Args: + det_results (list[list]): [[cls1_det, cls2_det, ...], ...]. + The outer list indicates images, and the inner list indicates + per-class detected bboxes. + annotations (list[dict]): Ground truth annotations where each item of + the list indicates an image. Keys of annotations are: + + - `bboxes`: numpy array of shape (n, 4) + - `labels`: numpy array of shape (n, ) + - `bboxes_ignore` (optional): numpy array of shape (k, 4) + - `labels_ignore` (optional): numpy array of shape (k, ) + scale_ranges (list[tuple] | None): Range of scales to be evaluated, + in the format [(min1, max1), (min2, max2), ...]. A range of + (32, 64) means the area range between (32**2, 64**2). + Defaults to None. + iou_thr (float): IoU threshold to be considered as matched. + Defaults to 0.5. + ioa_thr (float | None): IoA threshold to be considered as matched, + which only used in OpenImages evaluation. Defaults to None. + dataset (list[str] | str | None): Dataset name or dataset classes, + there are minor differences in metrics for different datasets, e.g. + "voc", "imagenet_det", etc. Defaults to None. + logger (logging.Logger | str | None): The way to print the mAP + summary. See `mmengine.logging.print_log()` for details. + Defaults to None. + tpfp_fn (callable | None): The function used to determine true/ + false positives. If None, :func:`tpfp_default` is used as default + unless dataset is 'det' or 'vid' (:func:`tpfp_imagenet` in this + case). If it is given as a function, then this function is used + to evaluate tp & fp. Default None. + nproc (int): Processes used for computing TP and FP. + Defaults to 4. + use_legacy_coordinate (bool): Whether to use coordinate system in + mmdet v1.x. which means width, height should be + calculated as 'x2 - x1 + 1` and 'y2 - y1 + 1' respectively. + Defaults to False. + use_group_of (bool): Whether to use group of when calculate TP and FP, + which only used in OpenImages evaluation. Defaults to False. + eval_mode (str): 'area' or '11points', 'area' means calculating the + area under precision-recall curve, '11points' means calculating + the average precision of recalls at [0, 0.1, ..., 1], + PASCAL VOC2007 uses `11points` as default evaluate mode, while + others are 'area'. Defaults to 'area'. + + Returns: + tuple: (mAP, [dict, dict, ...]) + """ + assert len(det_results) == len(annotations) + assert eval_mode in ['area', '11points'], \ + f'Unrecognized {eval_mode} mode, only "area" and "11points" ' \ + 'are supported' + if not use_legacy_coordinate: + extra_length = 0. + else: + extra_length = 1. + + num_imgs = len(det_results) + num_scales = len(scale_ranges) if scale_ranges is not None else 1 + num_classes = len(det_results[0]) # positive class num + area_ranges = ([(rg[0]**2, rg[1]**2) for rg in scale_ranges] + if scale_ranges is not None else None) + + # There is no need to use multi processes to process + # when num_imgs = 1 . + if num_imgs > 1: + assert nproc > 0, 'nproc must be at least one.' + nproc = min(nproc, num_imgs) + pool = Pool(nproc) + + eval_results = [] + for i in range(num_classes): + # get gt and det bboxes of this class + cls_dets, cls_gts, cls_gts_ignore = get_cls_results( + det_results, annotations, i) + # choose proper function according to datasets to compute tp and fp + if tpfp_fn is None: + if dataset in ['det', 'vid']: + tpfp_fn = tpfp_imagenet + elif dataset in ['oid_challenge', 'oid_v6'] \ + or use_group_of is True: + tpfp_fn = tpfp_openimages + else: + tpfp_fn = tpfp_default + if not callable(tpfp_fn): + raise ValueError( + f'tpfp_fn has to be a function or None, but got {tpfp_fn}') + + if num_imgs > 1: + # compute tp and fp for each image with multiple processes + args = [] + if use_group_of: + # used in Open Images Dataset evaluation + gt_group_ofs = get_cls_group_ofs(annotations, i) + args.append(gt_group_ofs) + args.append([use_group_of for _ in range(num_imgs)]) + if ioa_thr is not None: + args.append([ioa_thr for _ in range(num_imgs)]) + + tpfp = pool.starmap( + tpfp_fn, + zip(cls_dets, cls_gts, cls_gts_ignore, + [iou_thr for _ in range(num_imgs)], + [area_ranges for _ in range(num_imgs)], + [use_legacy_coordinate for _ in range(num_imgs)], *args)) + else: + tpfp = tpfp_fn( + cls_dets[0], + cls_gts[0], + cls_gts_ignore[0], + iou_thr, + area_ranges, + use_legacy_coordinate, + gt_bboxes_group_of=(get_cls_group_ofs(annotations, i)[0] + if use_group_of else None), + use_group_of=use_group_of, + ioa_thr=ioa_thr) + tpfp = [tpfp] + + if use_group_of: + tp, fp, cls_dets = tuple(zip(*tpfp)) + else: + tp, fp = tuple(zip(*tpfp)) + # calculate gt number of each scale + # ignored gts or gts beyond the specific scale are not counted + num_gts = np.zeros(num_scales, dtype=int) + for j, bbox in enumerate(cls_gts): + if area_ranges is None: + num_gts[0] += bbox.shape[0] + else: + gt_areas = (bbox[:, 2] - bbox[:, 0] + extra_length) * ( + bbox[:, 3] - bbox[:, 1] + extra_length) + for k, (min_area, max_area) in enumerate(area_ranges): + num_gts[k] += np.sum((gt_areas >= min_area) + & (gt_areas < max_area)) + # sort all det bboxes by score, also sort tp and fp + cls_dets = np.vstack(cls_dets) + num_dets = cls_dets.shape[0] + sort_inds = np.argsort(-cls_dets[:, -1]) + tp = np.hstack(tp)[:, sort_inds] + fp = np.hstack(fp)[:, sort_inds] + # calculate recall and precision with tp and fp + tp = np.cumsum(tp, axis=1) + fp = np.cumsum(fp, axis=1) + eps = np.finfo(np.float32).eps + recalls = tp / np.maximum(num_gts[:, np.newaxis], eps) + precisions = tp / np.maximum((tp + fp), eps) + # calculate AP + if scale_ranges is None: + recalls = recalls[0, :] + precisions = precisions[0, :] + num_gts = num_gts.item() + ap = average_precision(recalls, precisions, eval_mode) + eval_results.append({ + 'num_gts': num_gts, + 'num_dets': num_dets, + 'recall': recalls, + 'precision': precisions, + 'ap': ap + }) + + if num_imgs > 1: + pool.close() + + if scale_ranges is not None: + # shape (num_classes, num_scales) + all_ap = np.vstack([cls_result['ap'] for cls_result in eval_results]) + all_num_gts = np.vstack( + [cls_result['num_gts'] for cls_result in eval_results]) + mean_ap = [] + for i in range(num_scales): + if np.any(all_num_gts[:, i] > 0): + mean_ap.append(all_ap[all_num_gts[:, i] > 0, i].mean()) + else: + mean_ap.append(0.0) + else: + aps = [] + for cls_result in eval_results: + if cls_result['num_gts'] > 0: + aps.append(cls_result['ap']) + mean_ap = np.array(aps).mean().item() if aps else 0.0 + + print_map_summary( + mean_ap, eval_results, dataset, area_ranges, logger=logger) + + return mean_ap, eval_results + + +def print_map_summary(mean_ap, + results, + dataset=None, + scale_ranges=None, + logger=None): + """Print mAP and results of each class. + + A table will be printed to show the gts/dets/recall/AP of each class and + the mAP. + + Args: + mean_ap (float): Calculated from `eval_map()`. + results (list[dict]): Calculated from `eval_map()`. + dataset (list[str] | str | None): Dataset name or dataset classes. + scale_ranges (list[tuple] | None): Range of scales to be evaluated. + logger (logging.Logger | str | None): The way to print the mAP + summary. See `mmengine.logging.print_log()` for details. + Defaults to None. + """ + + if logger == 'silent': + return + + if isinstance(results[0]['ap'], np.ndarray): + num_scales = len(results[0]['ap']) + else: + num_scales = 1 + + if scale_ranges is not None: + assert len(scale_ranges) == num_scales + + num_classes = len(results) + + recalls = np.zeros((num_scales, num_classes), dtype=np.float32) + aps = np.zeros((num_scales, num_classes), dtype=np.float32) + num_gts = np.zeros((num_scales, num_classes), dtype=int) + for i, cls_result in enumerate(results): + if cls_result['recall'].size > 0: + recalls[:, i] = np.array(cls_result['recall'], ndmin=2)[:, -1] + aps[:, i] = cls_result['ap'] + num_gts[:, i] = cls_result['num_gts'] + + if dataset is None: + label_names = [str(i) for i in range(num_classes)] + elif is_str(dataset): + label_names = get_classes(dataset) + else: + label_names = dataset + + if not isinstance(mean_ap, list): + mean_ap = [mean_ap] + + header = ['class', 'gts', 'dets', 'recall', 'ap'] + for i in range(num_scales): + if scale_ranges is not None: + print_log(f'Scale range {scale_ranges[i]}', logger=logger) + table_data = [header] + for j in range(num_classes): + row_data = [ + label_names[j], num_gts[i, j], results[j]['num_dets'], + f'{recalls[i, j]:.3f}', f'{aps[i, j]:.3f}' + ] + table_data.append(row_data) + table_data.append(['mAP', '', '', '', f'{mean_ap[i]:.3f}']) + table = AsciiTable(table_data) + table.inner_footing_row_border = True + print_log('\n' + table.table, logger=logger) diff --git a/mmdetection/mmdet/evaluation/functional/panoptic_utils.py b/mmdetection/mmdet/evaluation/functional/panoptic_utils.py new file mode 100644 index 00000000..6faa8ed5 --- /dev/null +++ b/mmdetection/mmdet/evaluation/functional/panoptic_utils.py @@ -0,0 +1,228 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Copyright (c) 2018, Alexander Kirillov +# This file supports `backend_args` for `panopticapi`, +# the source code is copied from `panopticapi`, +# only the way to load the gt images is modified. +import multiprocessing +import os + +import mmcv +import numpy as np +from mmengine.fileio import get + +# A custom value to distinguish instance ID and category ID; need to +# be greater than the number of categories. +# For a pixel in the panoptic result map: +# pan_id = ins_id * INSTANCE_OFFSET + cat_id +INSTANCE_OFFSET = 1000 + +try: + from panopticapi.evaluation import OFFSET, VOID, PQStat + from panopticapi.utils import rgb2id +except ImportError: + PQStat = None + rgb2id = None + VOID = 0 + OFFSET = 256 * 256 * 256 + + +def pq_compute_single_core(proc_id, + annotation_set, + gt_folder, + pred_folder, + categories, + backend_args=None, + print_log=False): + """The single core function to evaluate the metric of Panoptic + Segmentation. + + Same as the function with the same name in `panopticapi`. Only the function + to load the images is changed to use the file client. + + Args: + proc_id (int): The id of the mini process. + gt_folder (str): The path of the ground truth images. + pred_folder (str): The path of the prediction images. + categories (str): The categories of the dataset. + backend_args (object): The Backend of the dataset. If None, + the backend will be set to `local`. + print_log (bool): Whether to print the log. Defaults to False. + """ + if PQStat is None: + raise RuntimeError( + 'panopticapi is not installed, please install it by: ' + 'pip install git+https://github.com/cocodataset/' + 'panopticapi.git.') + + pq_stat = PQStat() + + idx = 0 + for gt_ann, pred_ann in annotation_set: + if print_log and idx % 100 == 0: + print('Core: {}, {} from {} images processed'.format( + proc_id, idx, len(annotation_set))) + idx += 1 + # The gt images can be on the local disk or `ceph`, so we use + # backend here. + img_bytes = get( + os.path.join(gt_folder, gt_ann['file_name']), + backend_args=backend_args) + pan_gt = mmcv.imfrombytes(img_bytes, flag='color', channel_order='rgb') + pan_gt = rgb2id(pan_gt) + + # The predictions can only be on the local dist now. + pan_pred = mmcv.imread( + os.path.join(pred_folder, pred_ann['file_name']), + flag='color', + channel_order='rgb') + pan_pred = rgb2id(pan_pred) + + gt_segms = {el['id']: el for el in gt_ann['segments_info']} + pred_segms = {el['id']: el for el in pred_ann['segments_info']} + + # predicted segments area calculation + prediction sanity checks + pred_labels_set = set(el['id'] for el in pred_ann['segments_info']) + labels, labels_cnt = np.unique(pan_pred, return_counts=True) + for label, label_cnt in zip(labels, labels_cnt): + if label not in pred_segms: + if label == VOID: + continue + raise KeyError( + 'In the image with ID {} segment with ID {} is ' + 'presented in PNG and not presented in JSON.'.format( + gt_ann['image_id'], label)) + pred_segms[label]['area'] = label_cnt + pred_labels_set.remove(label) + if pred_segms[label]['category_id'] not in categories: + raise KeyError( + 'In the image with ID {} segment with ID {} has ' + 'unknown category_id {}.'.format( + gt_ann['image_id'], label, + pred_segms[label]['category_id'])) + if len(pred_labels_set) != 0: + raise KeyError( + 'In the image with ID {} the following segment IDs {} ' + 'are presented in JSON and not presented in PNG.'.format( + gt_ann['image_id'], list(pred_labels_set))) + + # confusion matrix calculation + pan_gt_pred = pan_gt.astype(np.uint64) * OFFSET + pan_pred.astype( + np.uint64) + gt_pred_map = {} + labels, labels_cnt = np.unique(pan_gt_pred, return_counts=True) + for label, intersection in zip(labels, labels_cnt): + gt_id = label // OFFSET + pred_id = label % OFFSET + gt_pred_map[(gt_id, pred_id)] = intersection + + # count all matched pairs + gt_matched = set() + pred_matched = set() + for label_tuple, intersection in gt_pred_map.items(): + gt_label, pred_label = label_tuple + if gt_label not in gt_segms: + continue + if pred_label not in pred_segms: + continue + if gt_segms[gt_label]['iscrowd'] == 1: + continue + if gt_segms[gt_label]['category_id'] != pred_segms[pred_label][ + 'category_id']: + continue + + union = pred_segms[pred_label]['area'] + gt_segms[gt_label][ + 'area'] - intersection - gt_pred_map.get((VOID, pred_label), 0) + iou = intersection / union + if iou > 0.5: + pq_stat[gt_segms[gt_label]['category_id']].tp += 1 + pq_stat[gt_segms[gt_label]['category_id']].iou += iou + gt_matched.add(gt_label) + pred_matched.add(pred_label) + + # count false positives + crowd_labels_dict = {} + for gt_label, gt_info in gt_segms.items(): + if gt_label in gt_matched: + continue + # crowd segments are ignored + if gt_info['iscrowd'] == 1: + crowd_labels_dict[gt_info['category_id']] = gt_label + continue + pq_stat[gt_info['category_id']].fn += 1 + + # count false positives + for pred_label, pred_info in pred_segms.items(): + if pred_label in pred_matched: + continue + # intersection of the segment with VOID + intersection = gt_pred_map.get((VOID, pred_label), 0) + # plus intersection with corresponding CROWD region if it exists + if pred_info['category_id'] in crowd_labels_dict: + intersection += gt_pred_map.get( + (crowd_labels_dict[pred_info['category_id']], pred_label), + 0) + # predicted segment is ignored if more than half of + # the segment correspond to VOID and CROWD regions + if intersection / pred_info['area'] > 0.5: + continue + pq_stat[pred_info['category_id']].fp += 1 + + if print_log: + print('Core: {}, all {} images processed'.format( + proc_id, len(annotation_set))) + return pq_stat + + +def pq_compute_multi_core(matched_annotations_list, + gt_folder, + pred_folder, + categories, + backend_args=None, + nproc=32): + """Evaluate the metrics of Panoptic Segmentation with multithreading. + + Same as the function with the same name in `panopticapi`. + + Args: + matched_annotations_list (list): The matched annotation list. Each + element is a tuple of annotations of the same image with the + format (gt_anns, pred_anns). + gt_folder (str): The path of the ground truth images. + pred_folder (str): The path of the prediction images. + categories (str): The categories of the dataset. + backend_args (object): The file client of the dataset. If None, + the backend will be set to `local`. + nproc (int): Number of processes for panoptic quality computing. + Defaults to 32. When `nproc` exceeds the number of cpu cores, + the number of cpu cores is used. + """ + if PQStat is None: + raise RuntimeError( + 'panopticapi is not installed, please install it by: ' + 'pip install git+https://github.com/cocodataset/' + 'panopticapi.git.') + + cpu_num = min(nproc, multiprocessing.cpu_count()) + + annotations_split = np.array_split(matched_annotations_list, cpu_num) + print('Number of cores: {}, images per core: {}'.format( + cpu_num, len(annotations_split[0]))) + workers = multiprocessing.Pool(processes=cpu_num) + processes = [] + for proc_id, annotation_set in enumerate(annotations_split): + p = workers.apply_async(pq_compute_single_core, + (proc_id, annotation_set, gt_folder, + pred_folder, categories, backend_args)) + processes.append(p) + + # Close the process pool, otherwise it will lead to memory + # leaking problems. + workers.close() + workers.join() + + pq_stat = PQStat() + for p in processes: + pq_stat += p.get() + + return pq_stat diff --git a/mmdetection/mmdet/evaluation/functional/recall.py b/mmdetection/mmdet/evaluation/functional/recall.py new file mode 100644 index 00000000..4bce2bf3 --- /dev/null +++ b/mmdetection/mmdet/evaluation/functional/recall.py @@ -0,0 +1,199 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections.abc import Sequence + +import numpy as np +from mmengine.logging import print_log +from terminaltables import AsciiTable + +from .bbox_overlaps import bbox_overlaps + + +def _recalls(all_ious, proposal_nums, thrs): + + img_num = all_ious.shape[0] + total_gt_num = sum([ious.shape[0] for ious in all_ious]) + + _ious = np.zeros((proposal_nums.size, total_gt_num), dtype=np.float32) + for k, proposal_num in enumerate(proposal_nums): + tmp_ious = np.zeros(0) + for i in range(img_num): + ious = all_ious[i][:, :proposal_num].copy() + gt_ious = np.zeros((ious.shape[0])) + if ious.size == 0: + tmp_ious = np.hstack((tmp_ious, gt_ious)) + continue + for j in range(ious.shape[0]): + gt_max_overlaps = ious.argmax(axis=1) + max_ious = ious[np.arange(0, ious.shape[0]), gt_max_overlaps] + gt_idx = max_ious.argmax() + gt_ious[j] = max_ious[gt_idx] + box_idx = gt_max_overlaps[gt_idx] + ious[gt_idx, :] = -1 + ious[:, box_idx] = -1 + tmp_ious = np.hstack((tmp_ious, gt_ious)) + _ious[k, :] = tmp_ious + + _ious = np.fliplr(np.sort(_ious, axis=1)) + recalls = np.zeros((proposal_nums.size, thrs.size)) + for i, thr in enumerate(thrs): + recalls[:, i] = (_ious >= thr).sum(axis=1) / float(total_gt_num) + + return recalls + + +def set_recall_param(proposal_nums, iou_thrs): + """Check proposal_nums and iou_thrs and set correct format.""" + if isinstance(proposal_nums, Sequence): + _proposal_nums = np.array(proposal_nums) + elif isinstance(proposal_nums, int): + _proposal_nums = np.array([proposal_nums]) + else: + _proposal_nums = proposal_nums + + if iou_thrs is None: + _iou_thrs = np.array([0.5]) + elif isinstance(iou_thrs, Sequence): + _iou_thrs = np.array(iou_thrs) + elif isinstance(iou_thrs, float): + _iou_thrs = np.array([iou_thrs]) + else: + _iou_thrs = iou_thrs + + return _proposal_nums, _iou_thrs + + +def eval_recalls(gts, + proposals, + proposal_nums=None, + iou_thrs=0.5, + logger=None, + use_legacy_coordinate=False): + """Calculate recalls. + + Args: + gts (list[ndarray]): a list of arrays of shape (n, 4) + proposals (list[ndarray]): a list of arrays of shape (k, 4) or (k, 5) + proposal_nums (int | Sequence[int]): Top N proposals to be evaluated. + iou_thrs (float | Sequence[float]): IoU thresholds. Default: 0.5. + logger (logging.Logger | str | None): The way to print the recall + summary. See `mmengine.logging.print_log()` for details. + Default: None. + use_legacy_coordinate (bool): Whether use coordinate system + in mmdet v1.x. "1" was added to both height and width + which means w, h should be + computed as 'x2 - x1 + 1` and 'y2 - y1 + 1'. Default: False. + + + Returns: + ndarray: recalls of different ious and proposal nums + """ + + img_num = len(gts) + assert img_num == len(proposals) + proposal_nums, iou_thrs = set_recall_param(proposal_nums, iou_thrs) + all_ious = [] + for i in range(img_num): + if proposals[i].ndim == 2 and proposals[i].shape[1] == 5: + scores = proposals[i][:, 4] + sort_idx = np.argsort(scores)[::-1] + img_proposal = proposals[i][sort_idx, :] + else: + img_proposal = proposals[i] + prop_num = min(img_proposal.shape[0], proposal_nums[-1]) + if gts[i] is None or gts[i].shape[0] == 0: + ious = np.zeros((0, img_proposal.shape[0]), dtype=np.float32) + else: + ious = bbox_overlaps( + gts[i], + img_proposal[:prop_num, :4], + use_legacy_coordinate=use_legacy_coordinate) + all_ious.append(ious) + all_ious = np.array(all_ious) + recalls = _recalls(all_ious, proposal_nums, iou_thrs) + + print_recall_summary(recalls, proposal_nums, iou_thrs, logger=logger) + return recalls + + +def print_recall_summary(recalls, + proposal_nums, + iou_thrs, + row_idxs=None, + col_idxs=None, + logger=None): + """Print recalls in a table. + + Args: + recalls (ndarray): calculated from `bbox_recalls` + proposal_nums (ndarray or list): top N proposals + iou_thrs (ndarray or list): iou thresholds + row_idxs (ndarray): which rows(proposal nums) to print + col_idxs (ndarray): which cols(iou thresholds) to print + logger (logging.Logger | str | None): The way to print the recall + summary. See `mmengine.logging.print_log()` for details. + Default: None. + """ + proposal_nums = np.array(proposal_nums, dtype=np.int32) + iou_thrs = np.array(iou_thrs) + if row_idxs is None: + row_idxs = np.arange(proposal_nums.size) + if col_idxs is None: + col_idxs = np.arange(iou_thrs.size) + row_header = [''] + iou_thrs[col_idxs].tolist() + table_data = [row_header] + for i, num in enumerate(proposal_nums[row_idxs]): + row = [f'{val:.3f}' for val in recalls[row_idxs[i], col_idxs].tolist()] + row.insert(0, num) + table_data.append(row) + table = AsciiTable(table_data) + print_log('\n' + table.table, logger=logger) + + +def plot_num_recall(recalls, proposal_nums): + """Plot Proposal_num-Recalls curve. + + Args: + recalls(ndarray or list): shape (k,) + proposal_nums(ndarray or list): same shape as `recalls` + """ + if isinstance(proposal_nums, np.ndarray): + _proposal_nums = proposal_nums.tolist() + else: + _proposal_nums = proposal_nums + if isinstance(recalls, np.ndarray): + _recalls = recalls.tolist() + else: + _recalls = recalls + + import matplotlib.pyplot as plt + f = plt.figure() + plt.plot([0] + _proposal_nums, [0] + _recalls) + plt.xlabel('Proposal num') + plt.ylabel('Recall') + plt.axis([0, proposal_nums.max(), 0, 1]) + f.show() + + +def plot_iou_recall(recalls, iou_thrs): + """Plot IoU-Recalls curve. + + Args: + recalls(ndarray or list): shape (k,) + iou_thrs(ndarray or list): same shape as `recalls` + """ + if isinstance(iou_thrs, np.ndarray): + _iou_thrs = iou_thrs.tolist() + else: + _iou_thrs = iou_thrs + if isinstance(recalls, np.ndarray): + _recalls = recalls.tolist() + else: + _recalls = recalls + + import matplotlib.pyplot as plt + f = plt.figure() + plt.plot(_iou_thrs + [1.0], _recalls + [0.]) + plt.xlabel('IoU') + plt.ylabel('Recall') + plt.axis([iou_thrs.min(), 1, 0, 1]) + f.show() diff --git a/mmdetection/mmdet/evaluation/functional/ytvis.py b/mmdetection/mmdet/evaluation/functional/ytvis.py new file mode 100644 index 00000000..c65a7e9b --- /dev/null +++ b/mmdetection/mmdet/evaluation/functional/ytvis.py @@ -0,0 +1,305 @@ +# Copyright (c) Github URL +# Copied from +# https://github.com/youtubevos/cocoapi/blob/master/PythonAPI/pycocotools/ytvos.py +__author__ = 'ychfan' +# Interface for accessing the YouTubeVIS dataset. + +# The following API functions are defined: +# YTVIS - YTVIS api class that loads YouTubeVIS annotation file +# and prepare data structures. +# decodeMask - Decode binary mask M encoded via run-length encoding. +# encodeMask - Encode binary mask M using run-length encoding. +# getAnnIds - Get ann ids that satisfy given filter conditions. +# getCatIds - Get cat ids that satisfy given filter conditions. +# getImgIds - Get img ids that satisfy given filter conditions. +# loadAnns - Load anns with the specified ids. +# loadCats - Load cats with the specified ids. +# loadImgs - Load imgs with the specified ids. +# annToMask - Convert segmentation in an annotation to binary mask. +# loadRes - Load algorithm results and create API for accessing them. + +# Microsoft COCO Toolbox. version 2.0 +# Data, paper, and tutorials available at: http://mscoco.org/ +# Code written by Piotr Dollar and Tsung-Yi Lin, 2014. +# Licensed under the Simplified BSD License [see bsd.txt] + +import copy +import itertools +import json +import sys +import time +from collections import defaultdict + +import numpy as np +from pycocotools import mask as maskUtils + +PYTHON_VERSION = sys.version_info[0] + + +def _isArrayLike(obj): + return hasattr(obj, '__iter__') and hasattr(obj, '__len__') + + +class YTVIS: + + def __init__(self, annotation_file=None): + """Constructor of Microsoft COCO helper class for reading and + visualizing annotations. + + :param annotation_file (str | dict): location of annotation file or + dict results. + :param image_folder (str): location to the folder that hosts images. + :return: + """ + # load dataset + self.dataset, self.anns, self.cats, self.vids = dict(), dict(), dict( + ), dict() + self.vidToAnns, self.catToVids = defaultdict(list), defaultdict(list) + if annotation_file is not None: + print('loading annotations into memory...') + tic = time.time() + if type(annotation_file) == str: + dataset = json.load(open(annotation_file, 'r')) + else: + dataset = annotation_file + assert type( + dataset + ) == dict, 'annotation file format {} not supported'.format( + type(dataset)) + print('Done (t={:0.2f}s)'.format(time.time() - tic)) + self.dataset = dataset + self.createIndex() + + def createIndex(self): + # create index + print('creating index...') + anns, cats, vids = {}, {}, {} + vidToAnns, catToVids = defaultdict(list), defaultdict(list) + if 'annotations' in self.dataset: + for ann in self.dataset['annotations']: + vidToAnns[ann['video_id']].append(ann) + anns[ann['id']] = ann + + if 'videos' in self.dataset: + for vid in self.dataset['videos']: + vids[vid['id']] = vid + + if 'categories' in self.dataset: + for cat in self.dataset['categories']: + cats[cat['id']] = cat + + if 'annotations' in self.dataset and 'categories' in self.dataset: + for ann in self.dataset['annotations']: + catToVids[ann['category_id']].append(ann['video_id']) + + print('index created!') + + # create class members + self.anns = anns + self.vidToAnns = vidToAnns + self.catToVids = catToVids + self.vids = vids + self.cats = cats + + def getAnnIds(self, vidIds=[], catIds=[], areaRng=[], iscrowd=None): + """Get ann ids that satisfy given filter conditions. default skips that + filter. + + :param vidIds (int array) : get anns for given vids + catIds (int array) : get anns for given cats + areaRng (float array) : get anns for given area range + iscrowd (boolean) : get anns for given crowd label + :return: ids (int array) : integer array of ann ids + """ + vidIds = vidIds if _isArrayLike(vidIds) else [vidIds] + catIds = catIds if _isArrayLike(catIds) else [catIds] + + if len(vidIds) == len(catIds) == len(areaRng) == 0: + anns = self.dataset['annotations'] + else: + if not len(vidIds) == 0: + lists = [ + self.vidToAnns[vidId] for vidId in vidIds + if vidId in self.vidToAnns + ] + anns = list(itertools.chain.from_iterable(lists)) + else: + anns = self.dataset['annotations'] + anns = anns if len(catIds) == 0 else [ + ann for ann in anns if ann['category_id'] in catIds + ] + anns = anns if len(areaRng) == 0 else [ + ann for ann in anns if ann['avg_area'] > areaRng[0] + and ann['avg_area'] < areaRng[1] + ] + if iscrowd is not None: + ids = [ann['id'] for ann in anns if ann['iscrowd'] == iscrowd] + else: + ids = [ann['id'] for ann in anns] + return ids + + def getCatIds(self, catNms=[], supNms=[], catIds=[]): + """filtering parameters. default skips that filter. + + :param catNms (str array) : get cats for given cat names + :param supNms (str array) : get cats for given supercategory names + :param catIds (int array) : get cats for given cat ids + :return: ids (int array) : integer array of cat ids + """ + catNms = catNms if _isArrayLike(catNms) else [catNms] + supNms = supNms if _isArrayLike(supNms) else [supNms] + catIds = catIds if _isArrayLike(catIds) else [catIds] + + if len(catNms) == len(supNms) == len(catIds) == 0: + cats = self.dataset['categories'] + else: + cats = self.dataset['categories'] + cats = cats if len(catNms) == 0 else [ + cat for cat in cats if cat['name'] in catNms + ] + cats = cats if len(supNms) == 0 else [ + cat for cat in cats if cat['supercategory'] in supNms + ] + cats = cats if len(catIds) == 0 else [ + cat for cat in cats if cat['id'] in catIds + ] + ids = [cat['id'] for cat in cats] + return ids + + def getVidIds(self, vidIds=[], catIds=[]): + """Get vid ids that satisfy given filter conditions. + + :param vidIds (int array) : get vids for given ids + :param catIds (int array) : get vids with all given cats + :return: ids (int array) : integer array of vid ids + """ + vidIds = vidIds if _isArrayLike(vidIds) else [vidIds] + catIds = catIds if _isArrayLike(catIds) else [catIds] + + if len(vidIds) == len(catIds) == 0: + ids = self.vids.keys() + else: + ids = set(vidIds) + for i, catId in enumerate(catIds): + if i == 0 and len(ids) == 0: + ids = set(self.catToVids[catId]) + else: + ids &= set(self.catToVids[catId]) + return list(ids) + + def loadAnns(self, ids=[]): + """Load anns with the specified ids. + + :param ids (int array) : integer ids specifying anns + :return: anns (object array) : loaded ann objects + """ + if _isArrayLike(ids): + return [self.anns[id] for id in ids] + elif type(ids) == int: + return [self.anns[ids]] + + def loadCats(self, ids=[]): + """Load cats with the specified ids. + + :param ids (int array) : integer ids specifying cats + :return: cats (object array) : loaded cat objects + """ + if _isArrayLike(ids): + return [self.cats[id] for id in ids] + elif type(ids) == int: + return [self.cats[ids]] + + def loadVids(self, ids=[]): + """Load anns with the specified ids. + + :param ids (int array) : integer ids specifying vid + :return: vids (object array) : loaded vid objects + """ + if _isArrayLike(ids): + return [self.vids[id] for id in ids] + elif type(ids) == int: + return [self.vids[ids]] + + def loadRes(self, resFile): + """Load result file and return a result api object. + + :param resFile (str) : file name of result file + :return: res (obj) : result api object + """ + res = YTVIS() + res.dataset['videos'] = [img for img in self.dataset['videos']] + + print('Loading and preparing results...') + tic = time.time() + if type(resFile) == str or (PYTHON_VERSION == 2 + and type(resFile) == str): + anns = json.load(open(resFile)) + elif type(resFile) == np.ndarray: + anns = self.loadNumpyAnnotations(resFile) + else: + anns = resFile + assert type(anns) == list, 'results in not an array of objects' + annsVidIds = [ann['video_id'] for ann in anns] + assert set(annsVidIds) == (set(annsVidIds) & set(self.getVidIds())), \ + 'Results do not correspond to current coco set' + if 'segmentations' in anns[0]: + res.dataset['categories'] = copy.deepcopy( + self.dataset['categories']) + for id, ann in enumerate(anns): + ann['areas'] = [] + if 'bboxes' not in ann: + ann['bboxes'] = [] + for seg in ann['segmentations']: + # now only support compressed RLE format + # as segmentation results + if seg: + ann['areas'].append(maskUtils.area(seg)) + if len(ann['bboxes']) < len(ann['areas']): + ann['bboxes'].append(maskUtils.toBbox(seg)) + else: + ann['areas'].append(None) + if len(ann['bboxes']) < len(ann['areas']): + ann['bboxes'].append(None) + ann['id'] = id + 1 + l_ori = [a for a in ann['areas'] if a] + if len(l_ori) == 0: + ann['avg_area'] = 0 + else: + ann['avg_area'] = np.array(l_ori).mean() + ann['iscrowd'] = 0 + print('DONE (t={:0.2f}s)'.format(time.time() - tic)) + + res.dataset['annotations'] = anns + res.createIndex() + return res + + def annToRLE(self, ann, frameId): + """Convert annotation which can be polygons, uncompressed RLE to RLE. + + :return: binary mask (numpy 2D array) + """ + t = self.vids[ann['video_id']] + h, w = t['height'], t['width'] + segm = ann['segmentations'][frameId] + if type(segm) == list: + # polygon -- a single object might consist of multiple parts + # we merge all parts into one mask rle code + rles = maskUtils.frPyObjects(segm, h, w) + rle = maskUtils.merge(rles) + elif type(segm['counts']) == list: + # uncompressed RLE + rle = maskUtils.frPyObjects(segm, h, w) + else: + # rle + rle = segm + return rle + + def annToMask(self, ann, frameId): + """Convert annotation which can be polygons, uncompressed RLE, or RLE + to binary mask. + + :return: binary mask (numpy 2D array) + """ + rle = self.annToRLE(ann, frameId) + m = maskUtils.decode(rle) + return m diff --git a/mmdetection/mmdet/evaluation/functional/ytviseval.py b/mmdetection/mmdet/evaluation/functional/ytviseval.py new file mode 100644 index 00000000..fdaf110d --- /dev/null +++ b/mmdetection/mmdet/evaluation/functional/ytviseval.py @@ -0,0 +1,623 @@ +# Copyright (c) Github URL +# Copied from +# https://github.com/youtubevos/cocoapi/blob/master/PythonAPI/pycocotools/ytvoseval.py +__author__ = 'ychfan' + +import copy +import datetime +import time +from collections import defaultdict + +import numpy as np +from pycocotools import mask as maskUtils + + +class YTVISeval: + # Interface for evaluating video instance segmentation on + # the YouTubeVIS dataset. + # + # The usage for YTVISeval is as follows: + # cocoGt=..., cocoDt=... # load dataset and results + # E = YTVISeval(cocoGt,cocoDt); # initialize YTVISeval object + # E.params.recThrs = ...; # set parameters as desired + # E.evaluate(); # run per image evaluation + # E.accumulate(); # accumulate per image results + # E.summarize(); # display summary metrics of results + # For example usage see evalDemo.m and http://mscoco.org/. + # + # The evaluation parameters are as follows (defaults in brackets): + # imgIds - [all] N img ids to use for evaluation + # catIds - [all] K cat ids to use for evaluation + # iouThrs - [.5:.05:.95] T=10 IoU thresholds for evaluation + # recThrs - [0:.01:1] R=101 recall thresholds for evaluation + # areaRng - [...] A=4 object area ranges for evaluation + # maxDets - [1 10 100] M=3 thresholds on max detections per image + # iouType - ['segm'] set iouType to 'segm', 'bbox' or 'keypoints' + # iouType replaced the now DEPRECATED useSegm parameter. + # useCats - [1] if true use category labels for evaluation + # Note: if useCats=0 category labels are ignored as in proposal scoring. + # Note: multiple areaRngs [Ax2] and maxDets [Mx1] can be specified. + # + # evaluate(): evaluates detections on every image and every category and + # concats the results into the "evalImgs" with fields: + # dtIds - [1xD] id for each of the D detections (dt) + # gtIds - [1xG] id for each of the G ground truths (gt) + # dtMatches - [TxD] matching gt id at each IoU or 0 + # gtMatches - [TxG] matching dt id at each IoU or 0 + # dtScores - [1xD] confidence of each dt + # gtIgnore - [1xG] ignore flag for each gt + # dtIgnore - [TxD] ignore flag for each dt at each IoU + # + # accumulate(): accumulates the per-image, per-category evaluation + # results in "evalImgs" into the dictionary "eval" with fields: + # params - parameters used for evaluation + # date - date evaluation was performed + # counts - [T,R,K,A,M] parameter dimensions (see above) + # precision - [TxRxKxAxM] precision for every evaluation setting + # recall - [TxKxAxM] max recall for every evaluation setting + # Note: precision and recall==-1 for settings with no gt objects. + # + # See also coco, mask, pycocoDemo, pycocoEvalDemo + # + # Microsoft COCO Toolbox. version 2.0 + # Data, paper, and tutorials available at: http://mscoco.org/ + # Code written by Piotr Dollar and Tsung-Yi Lin, 2015. + # Licensed under the Simplified BSD License [see coco/license.txt] + def __init__(self, cocoGt=None, cocoDt=None, iouType='segm'): + """Initialize CocoEval using coco APIs for gt and dt. + + :param cocoGt: coco object with ground truth annotations + :param cocoDt: coco object with detection results + :return: None + """ + if not iouType: + print('iouType not specified. use default iouType segm') + self.cocoGt = cocoGt # ground truth COCO API + self.cocoDt = cocoDt # detections COCO API + self.params = {} # evaluation parameters + self.evalVids = defaultdict( + list) # per-image per-category evaluation results [KxAxI] elements + self.eval = {} # accumulated evaluation results + self._gts = defaultdict(list) # gt for evaluation + self._dts = defaultdict(list) # dt for evaluation + self.params = Params(iouType=iouType) # parameters + self._paramsEval = {} # parameters for evaluation + self.stats = [] # result summarization + self.ious = {} # ious between all gts and dts + if cocoGt is not None: + self.params.vidIds = sorted(cocoGt.getVidIds()) + self.params.catIds = sorted(cocoGt.getCatIds()) + + def _prepare(self): + ''' + Prepare ._gts and ._dts for evaluation based on params + :return: None + ''' + + def _toMask(anns, coco): + # modify ann['segmentation'] by reference + for ann in anns: + for i, a in enumerate(ann['segmentations']): + if a: + rle = coco.annToRLE(ann, i) + ann['segmentations'][i] = rle + l_ori = [a for a in ann['areas'] if a] + if len(l_ori) == 0: + ann['avg_area'] = 0 + else: + ann['avg_area'] = np.array(l_ori).mean() + + p = self.params + if p.useCats: + gts = self.cocoGt.loadAnns( + self.cocoGt.getAnnIds(vidIds=p.vidIds, catIds=p.catIds)) + dts = self.cocoDt.loadAnns( + self.cocoDt.getAnnIds(vidIds=p.vidIds, catIds=p.catIds)) + else: + gts = self.cocoGt.loadAnns(self.cocoGt.getAnnIds(vidIds=p.vidIds)) + dts = self.cocoDt.loadAnns(self.cocoDt.getAnnIds(vidIds=p.vidIds)) + + # convert ground truth to mask if iouType == 'segm' + if p.iouType == 'segm': + _toMask(gts, self.cocoGt) + _toMask(dts, self.cocoDt) + # set ignore flag + for gt in gts: + gt['ignore'] = gt['ignore'] if 'ignore' in gt else 0 + gt['ignore'] = 'iscrowd' in gt and gt['iscrowd'] + if p.iouType == 'keypoints': + gt['ignore'] = (gt['num_keypoints'] == 0) or gt['ignore'] + self._gts = defaultdict(list) # gt for evaluation + self._dts = defaultdict(list) # dt for evaluation + for gt in gts: + self._gts[gt['video_id'], gt['category_id']].append(gt) + for dt in dts: + self._dts[dt['video_id'], dt['category_id']].append(dt) + self.evalVids = defaultdict( + list) # per-image per-category evaluation results + self.eval = {} # accumulated evaluation results + + def evaluate(self): + ''' + Run per image evaluation on given images and store + results (a list of dict) in self.evalVids + :return: None + ''' + tic = time.time() + print('Running per image evaluation...') + p = self.params + # add backward compatibility if useSegm is specified in params + if p.useSegm is not None: + p.iouType = 'segm' if p.useSegm == 1 else 'bbox' + print('useSegm (deprecated) is not None. Running {} evaluation'. + format(p.iouType)) + print('Evaluate annotation type *{}*'.format(p.iouType)) + p.vidIds = list(np.unique(p.vidIds)) + if p.useCats: + p.catIds = list(np.unique(p.catIds)) + p.maxDets = sorted(p.maxDets) + self.params = p + + self._prepare() + # loop through images, area range, max detection number + catIds = p.catIds if p.useCats else [-1] + + if p.iouType == 'segm' or p.iouType == 'bbox': + computeIoU = self.computeIoU + elif p.iouType == 'keypoints': + computeIoU = self.computeOks + self.ious = {(vidId, catId): computeIoU(vidId, catId) + for vidId in p.vidIds for catId in catIds} + + evaluateVid = self.evaluateVid + maxDet = p.maxDets[-1] + + self.evalImgs = [ + evaluateVid(vidId, catId, areaRng, maxDet) for catId in catIds + for areaRng in p.areaRng for vidId in p.vidIds + ] + self._paramsEval = copy.deepcopy(self.params) + toc = time.time() + print('DONE (t={:0.2f}s).'.format(toc - tic)) + + def computeIoU(self, vidId, catId): + p = self.params + if p.useCats: + gt = self._gts[vidId, catId] + dt = self._dts[vidId, catId] + else: + gt = [_ for cId in p.catIds for _ in self._gts[vidId, cId]] + dt = [_ for cId in p.catIds for _ in self._dts[vidId, cId]] + if len(gt) == 0 and len(dt) == 0: + return [] + inds = np.argsort([-d['score'] for d in dt], kind='mergesort') + dt = [dt[i] for i in inds] + if len(dt) > p.maxDets[-1]: + dt = dt[0:p.maxDets[-1]] + + if p.iouType == 'segm': + g = [g['segmentations'] for g in gt] + d = [d['segmentations'] for d in dt] + elif p.iouType == 'bbox': + g = [g['bboxes'] for g in gt] + d = [d['bboxes'] for d in dt] + else: + raise Exception('unknown iouType for iou computation') + + # compute iou between each dt and gt region + + def iou_seq(d_seq, g_seq): + i = .0 + u = .0 + for d, g in zip(d_seq, g_seq): + if d and g: + i += maskUtils.area(maskUtils.merge([d, g], True)) + u += maskUtils.area(maskUtils.merge([d, g], False)) + elif not d and g: + u += maskUtils.area(g) + elif d and not g: + u += maskUtils.area(d) + if not u > .0: + print('Mask sizes in video {} and category {} may not match!'. + format(vidId, catId)) + iou = i / u if u > .0 else .0 + return iou + + ious = np.zeros([len(d), len(g)]) + for i, j in np.ndindex(ious.shape): + ious[i, j] = iou_seq(d[i], g[j]) + + return ious + + def computeOks(self, imgId, catId): + p = self.params + + gts = self._gts[imgId, catId] + dts = self._dts[imgId, catId] + inds = np.argsort([-d['score'] for d in dts], kind='mergesort') + dts = [dts[i] for i in inds] + if len(dts) > p.maxDets[-1]: + dts = dts[0:p.maxDets[-1]] + # if len(gts) == 0 and len(dts) == 0: + if len(gts) == 0 or len(dts) == 0: + return [] + ious = np.zeros((len(dts), len(gts))) + sigmas = np.array([ + .26, .25, .25, .35, .35, .79, .79, .72, .72, .62, .62, 1.07, 1.07, + .87, .87, .89, .89 + ]) / 10.0 + vars = (sigmas * 2)**2 + k = len(sigmas) + # compute oks between each detection and ground truth object + for j, gt in enumerate(gts): + # create bounds for ignore regions(double the gt bbox) + g = np.array(gt['keypoints']) + xg = g[0::3] + yg = g[1::3] + vg = g[2::3] + k1 = np.count_nonzero(vg > 0) + bb = gt['bbox'] + x0 = bb[0] - bb[2] + x1 = bb[0] + bb[2] * 2 + y0 = bb[1] - bb[3] + y1 = bb[1] + bb[3] * 2 + for i, dt in enumerate(dts): + d = np.array(dt['keypoints']) + xd = d[0::3] + yd = d[1::3] + if k1 > 0: + # measure the per-keypoint distance if keypoints visible + dx = xd - xg + dy = yd - yg + else: + # measure minimum distance to keypoints + z = np.zeros((k)) + dx = np.max((z, x0 - xd), axis=0) + np.max( + (z, xd - x1), axis=0) + dy = np.max((z, y0 - yd), axis=0) + np.max( + (z, yd - y1), axis=0) + e = (dx**2 + dy**2) / vars / (gt['avg_area'] + + np.spacing(1)) / 2 + if k1 > 0: + e = e[vg > 0] + ious[i, j] = np.sum(np.exp(-e)) / e.shape[0] + return ious + + def evaluateVid(self, vidId, catId, aRng, maxDet): + ''' + perform evaluation for single category and image + :return: dict (single image results) + ''' + p = self.params + if p.useCats: + gt = self._gts[vidId, catId] + dt = self._dts[vidId, catId] + else: + gt = [_ for cId in p.catIds for _ in self._gts[vidId, cId]] + dt = [_ for cId in p.catIds for _ in self._dts[vidId, cId]] + if len(gt) == 0 and len(dt) == 0: + return None + + for g in gt: + if g['ignore'] or (g['avg_area'] < aRng[0] + or g['avg_area'] > aRng[1]): + g['_ignore'] = 1 + else: + g['_ignore'] = 0 + + # sort dt highest score first, sort gt ignore last + gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort') + gt = [gt[i] for i in gtind] + dtind = np.argsort([-d['score'] for d in dt], kind='mergesort') + dt = [dt[i] for i in dtind[0:maxDet]] + iscrowd = [int(o['iscrowd']) for o in gt] + # load computed ious + ious = self.ious[vidId, catId][:, gtind] if len( + self.ious[vidId, catId]) > 0 else self.ious[vidId, catId] + + T = len(p.iouThrs) + G = len(gt) + D = len(dt) + gtm = np.zeros((T, G)) + dtm = np.zeros((T, D)) + gtIg = np.array([g['_ignore'] for g in gt]) + dtIg = np.zeros((T, D)) + if not len(ious) == 0: + for tind, t in enumerate(p.iouThrs): + for dind, d in enumerate(dt): + # information about best match so far (m=-1 -> unmatched) + iou = min([t, 1 - 1e-10]) + m = -1 + for gind, g in enumerate(gt): + # if this gt already matched, and not a crowd, continue + if gtm[tind, gind] > 0 and not iscrowd[gind]: + continue + # if dt matched to reg gt, and on ignore gt, stop + if m > -1 and gtIg[m] == 0 and gtIg[gind] == 1: + break + # continue to next gt unless better match made + if ious[dind, gind] < iou: + continue + # if match successful and best so far, + # store appropriately + iou = ious[dind, gind] + m = gind + # if match made store id of match for both dt and gt + if m == -1: + continue + dtIg[tind, dind] = gtIg[m] + dtm[tind, dind] = gt[m]['id'] + gtm[tind, m] = d['id'] + # set unmatched detections outside of area range to ignore + a = np.array([ + d['avg_area'] < aRng[0] or d['avg_area'] > aRng[1] for d in dt + ]).reshape((1, len(dt))) + dtIg = np.logical_or(dtIg, np.logical_and(dtm == 0, np.repeat(a, T, + 0))) + # store results for given image and category + return { + 'video_id': vidId, + 'category_id': catId, + 'aRng': aRng, + 'maxDet': maxDet, + 'dtIds': [d['id'] for d in dt], + 'gtIds': [g['id'] for g in gt], + 'dtMatches': dtm, + 'gtMatches': gtm, + 'dtScores': [d['score'] for d in dt], + 'gtIgnore': gtIg, + 'dtIgnore': dtIg, + } + + def accumulate(self, p=None): + """Accumulate per image evaluation results and store the result in + self.eval. + + :param p: input params for evaluation + :return: None + """ + print('Accumulating evaluation results...') + tic = time.time() + if not self.evalImgs: + print('Please run evaluate() first') + # allows input customized parameters + if p is None: + p = self.params + p.catIds = p.catIds if p.useCats == 1 else [-1] + T = len(p.iouThrs) + R = len(p.recThrs) + K = len(p.catIds) if p.useCats else 1 + A = len(p.areaRng) + M = len(p.maxDets) + precision = -np.ones( + (T, R, K, A, M)) # -1 for the precision of absent categories + recall = -np.ones((T, K, A, M)) + scores = -np.ones((T, R, K, A, M)) + + # create dictionary for future indexing + _pe = self._paramsEval + catIds = _pe.catIds if _pe.useCats else [-1] + setK = set(catIds) + setA = set(map(tuple, _pe.areaRng)) + setM = set(_pe.maxDets) + setI = set(_pe.vidIds) + # get inds to evaluate + k_list = [n for n, k in enumerate(p.catIds) if k in setK] + m_list = [m for n, m in enumerate(p.maxDets) if m in setM] + a_list = [ + n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) + if a in setA + ] + i_list = [n for n, i in enumerate(p.vidIds) if i in setI] + I0 = len(_pe.vidIds) + A0 = len(_pe.areaRng) + # retrieve E at each category, area range, and max number of detections + for k, k0 in enumerate(k_list): + Nk = k0 * A0 * I0 + for a, a0 in enumerate(a_list): + Na = a0 * I0 + for m, maxDet in enumerate(m_list): + E = [self.evalImgs[Nk + Na + i] for i in i_list] + E = [e for e in E if e is not None] + if len(E) == 0: + continue + dtScores = np.concatenate( + [e['dtScores'][0:maxDet] for e in E]) + + inds = np.argsort(-dtScores, kind='mergesort') + dtScoresSorted = dtScores[inds] + + dtm = np.concatenate( + [e['dtMatches'][:, 0:maxDet] for e in E], axis=1)[:, + inds] + dtIg = np.concatenate( + [e['dtIgnore'][:, 0:maxDet] for e in E], axis=1)[:, + inds] + gtIg = np.concatenate([e['gtIgnore'] for e in E]) + npig = np.count_nonzero(gtIg == 0) + if npig == 0: + continue + tps = np.logical_and(dtm, np.logical_not(dtIg)) + fps = np.logical_and( + np.logical_not(dtm), np.logical_not(dtIg)) + + tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float) + fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float) + for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)): + tp = np.array(tp) + fp = np.array(fp) + nd_ori = len(tp) + rc = tp / npig + pr = tp / (fp + tp + np.spacing(1)) + q = np.zeros((R, )) + ss = np.zeros((R, )) + + if nd_ori: + recall[t, k, a, m] = rc[-1] + else: + recall[t, k, a, m] = 0 + + # use python array gets significant speed improvement + pr = pr.tolist() + q = q.tolist() + + for i in range(nd_ori - 1, 0, -1): + if pr[i] > pr[i - 1]: + pr[i - 1] = pr[i] + + inds = np.searchsorted(rc, p.recThrs, side='left') + try: + for ri, pi in enumerate(inds): + q[ri] = pr[pi] + ss[ri] = dtScoresSorted[pi] + except Exception: + pass + precision[t, :, k, a, m] = np.array(q) + scores[t, :, k, a, m] = np.array(ss) + self.eval = { + 'params': p, + 'counts': [T, R, K, A, M], + 'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'precision': precision, + 'recall': recall, + 'scores': scores, + } + toc = time.time() + print('DONE (t={:0.2f}s).'.format(toc - tic)) + + def summarize(self): + """Compute and display summary metrics for evaluation results. + + Note this function can *only* be applied on the default parameter + setting + """ + + def _summarize(ap=1, iouThr=None, areaRng='all', maxDets=100): + p = self.params + iStr = ' {:<18} {} @[ IoU={:<9} | area={:>6s} | ' \ + 'maxDets={:>3d} ] = {:0.3f}' + titleStr = 'Average Precision' if ap == 1 else 'Average Recall' + typeStr = '(AP)' if ap == 1 else '(AR)' + iouStr = '{:0.2f}:{:0.2f}'.format(p.iouThrs[0], p.iouThrs[-1]) \ + if iouThr is None else '{:0.2f}'.format(iouThr) + + aind = [ + i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng + ] + mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets] + if ap == 1: + # dimension of precision: [TxRxKxAxM] + s = self.eval['precision'] + # IoU + if iouThr is not None: + t = np.where(iouThr == p.iouThrs)[0] + s = s[t] + s = s[:, :, :, aind, mind] + else: + # dimension of recall: [TxKxAxM] + s = self.eval['recall'] + if iouThr is not None: + t = np.where(iouThr == p.iouThrs)[0] + s = s[t] + s = s[:, :, aind, mind] + if len(s[s > -1]) == 0: + mean_s = -1 + else: + mean_s = np.mean(s[s > -1]) + print( + iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, + mean_s)) + return mean_s + + def _summarizeDets(): + stats = np.zeros((12, )) + stats[0] = _summarize(1) + stats[1] = _summarize(1, iouThr=.5, maxDets=self.params.maxDets[2]) + stats[2] = _summarize( + 1, iouThr=.75, maxDets=self.params.maxDets[2]) + stats[3] = _summarize( + 1, areaRng='small', maxDets=self.params.maxDets[2]) + stats[4] = _summarize( + 1, areaRng='medium', maxDets=self.params.maxDets[2]) + stats[5] = _summarize( + 1, areaRng='large', maxDets=self.params.maxDets[2]) + stats[6] = _summarize(0, maxDets=self.params.maxDets[0]) + stats[7] = _summarize(0, maxDets=self.params.maxDets[1]) + stats[8] = _summarize(0, maxDets=self.params.maxDets[2]) + stats[9] = _summarize( + 0, areaRng='small', maxDets=self.params.maxDets[2]) + stats[10] = _summarize( + 0, areaRng='medium', maxDets=self.params.maxDets[2]) + stats[11] = _summarize( + 0, areaRng='large', maxDets=self.params.maxDets[2]) + return stats + + def _summarizeKps(): + stats = np.zeros((10, )) + stats[0] = _summarize(1, maxDets=20) + stats[1] = _summarize(1, maxDets=20, iouThr=.5) + stats[2] = _summarize(1, maxDets=20, iouThr=.75) + stats[3] = _summarize(1, maxDets=20, areaRng='medium') + stats[4] = _summarize(1, maxDets=20, areaRng='large') + stats[5] = _summarize(0, maxDets=20) + stats[6] = _summarize(0, maxDets=20, iouThr=.5) + stats[7] = _summarize(0, maxDets=20, iouThr=.75) + stats[8] = _summarize(0, maxDets=20, areaRng='medium') + stats[9] = _summarize(0, maxDets=20, areaRng='large') + return stats + + if not self.eval: + raise Exception('Please run accumulate() first') + iouType = self.params.iouType + if iouType == 'segm' or iouType == 'bbox': + summarize = _summarizeDets + elif iouType == 'keypoints': + summarize = _summarizeKps + self.stats = summarize() + + def __str__(self): + self.summarize() + + +class Params: + """Params for coco evaluation api.""" + + def setDetParams(self): + self.vidIds = [] + self.catIds = [] + # np.arange causes trouble. the data point on arange + # is slightly larger than the true value + self.iouThrs = np.linspace( + .5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True) + self.recThrs = np.linspace( + .0, 1.00, int(np.round((1.00 - .0) / .01)) + 1, endpoint=True) + self.maxDets = [1, 10, 100] + self.areaRng = [[0**2, 1e5**2], [0**2, 128**2], [128**2, 256**2], + [256**2, 1e5**2]] + self.areaRngLbl = ['all', 'small', 'medium', 'large'] + self.useCats = 1 + + def setKpParams(self): + self.vidIds = [] + self.catIds = [] + # np.arange causes trouble. the data point on arange + # is slightly larger than the true value + self.iouThrs = np.linspace( + .5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True) + self.recThrs = np.linspace( + .0, 1.00, int(np.round((1.00 - .0) / .01)) + 1, endpoint=True) + self.maxDets = [20] + self.areaRng = [[0**2, 1e5**2], [32**2, 96**2], [96**2, 1e5**2]] + self.areaRngLbl = ['all', 'medium', 'large'] + self.useCats = 1 + + def __init__(self, iouType='segm'): + if iouType == 'segm' or iouType == 'bbox': + self.setDetParams() + elif iouType == 'keypoints': + self.setKpParams() + else: + raise Exception('iouType not supported') + self.iouType = iouType + # useSegm is deprecated + self.useSegm = None diff --git a/mmdetection/mmdet/evaluation/metrics/__init__.py b/mmdetection/mmdet/evaluation/metrics/__init__.py new file mode 100644 index 00000000..f91f1c06 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/__init__.py @@ -0,0 +1,36 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_video_metric import BaseVideoMetric +from .cityscapes_metric import CityScapesMetric +from .coco_caption_metric import COCOCaptionMetric +from .coco_metric import CocoMetric +from .coco_occluded_metric import CocoOccludedSeparatedMetric +from .coco_panoptic_metric import CocoPanopticMetric +from .coco_video_metric import CocoVideoMetric +from .crowdhuman_metric import CrowdHumanMetric +from .dod_metric import DODCocoMetric +from .dump_det_results import DumpDetResults +from .dump_odvg_results import DumpODVGResults +from .dump_proposals_metric import DumpProposals +from .flickr30k_metric import Flickr30kMetric +from .grefcoco_metric import gRefCOCOMetric +from .lvis_metric import LVISMetric +from .mot_challenge_metric import MOTChallengeMetric +from .openimages_metric import OpenImagesMetric +from .ov_coco_metric import OVCocoMetric +from .refexp_metric import RefExpMetric +from .refseg_metric import RefSegMetric +from .reid_metric import ReIDMetrics +from .semseg_metric import SemSegMetric +from .voc_metric import VOCMetric +from .youtube_vis_metric import YouTubeVISMetric +from.custom_metric import CustomResultDumping + +__all__ = [ + 'CityScapesMetric', 'CocoMetric', 'CocoPanopticMetric', 'OpenImagesMetric', + 'VOCMetric', 'LVISMetric', 'CrowdHumanMetric', 'DumpProposals', + 'CocoOccludedSeparatedMetric', 'DumpDetResults', 'BaseVideoMetric', + 'MOTChallengeMetric', 'CocoVideoMetric', 'ReIDMetrics', 'YouTubeVISMetric', + 'COCOCaptionMetric', 'SemSegMetric', 'RefSegMetric', 'RefExpMetric', + 'gRefCOCOMetric', 'DODCocoMetric', 'DumpODVGResults', 'Flickr30kMetric', + 'OVCocoMetric', 'CustomResultDumping' +] diff --git a/mmdetection/mmdet/evaluation/metrics/base_video_metric.py b/mmdetection/mmdet/evaluation/metrics/base_video_metric.py new file mode 100644 index 00000000..90c7cdcb --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/base_video_metric.py @@ -0,0 +1,173 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import pickle +import shutil +import tempfile +import warnings +from typing import Optional, Sequence + +import torch +from mmengine.dist import (barrier, broadcast, broadcast_object_list, + get_dist_info, is_main_process) +from mmengine.evaluator import BaseMetric +from mmengine.utils import mkdir_or_exist + + +class BaseVideoMetric(BaseMetric): + """Base class for a metric in video task. + + The metric first processes each batch of data_samples and predictions, + and appends the processed results to the results list. Then it + collects all results together from all ranks if distributed training + is used. Finally, it computes the metrics of the entire dataset. + + A subclass of class:`BaseVideoMetric` should assign a meaningful value + to the class attribute `default_prefix`. See the argument `prefix` for + details. + """ + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. + + The processed results should be stored in ``self.results``, which will + be used to compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for track_data_sample in data_samples: + video_data_samples = track_data_sample['video_data_samples'] + ori_video_len = video_data_samples[0].ori_video_length + if ori_video_len == len(video_data_samples): + # video process + self.process_video(video_data_samples) + else: + # image process + self.process_image(video_data_samples, ori_video_len) + + def evaluate(self, size: int = 1) -> dict: + """Evaluate the model performance of the whole dataset after processing + all batches. + + Args: + size (int): Length of the entire validation dataset. + + Returns: + dict: Evaluation metrics dict on the val dataset. The keys are the + names of the metrics, and the values are corresponding results. + """ + if len(self.results) == 0: + warnings.warn( + f'{self.__class__.__name__} got empty `self.results`. Please ' + 'ensure that the processed results are properly added into ' + '`self.results` in `process` method.') + + results = collect_tracking_results(self.results, self.collect_device) + + if is_main_process(): + _metrics = self.compute_metrics(results) # type: ignore + # Add prefix to metric names + if self.prefix: + _metrics = { + '/'.join((self.prefix, k)): v + for k, v in _metrics.items() + } + metrics = [_metrics] + else: + metrics = [None] # type: ignore + + broadcast_object_list(metrics) + + # reset the results list + self.results.clear() + return metrics[0] + + +def collect_tracking_results(results: list, + device: str = 'cpu', + tmpdir: Optional[str] = None) -> Optional[list]: + """Collected results in distributed environments. different from the + function mmengine.dist.collect_results, tracking compute metrics don't use + paramenter size, which means length of the entire validation dataset. + because it's equal to video num, but compute metrics need image num. + + Args: + results (list): Result list containing result parts to be + collected. Each item of ``result_part`` should be a picklable + object. + device (str): Device name. Optional values are 'cpu' and 'gpu'. + tmpdir (str | None): Temporal directory for collected results to + store. If set to None, it will create a temporal directory for it. + ``tmpdir`` should be None when device is 'gpu'. Defaults to None. + + Returns: + list or None: The collected results. + """ + if device not in ['gpu', 'cpu']: + raise NotImplementedError( + f"device must be 'cpu' or 'gpu', but got {device}") + + if device == 'gpu': + assert tmpdir is None, 'tmpdir should be None when device is "gpu"' + raise NotImplementedError('GPU collecting has not been supported yet') + else: + return collect_tracking_results_cpu(results, tmpdir) + + +def collect_tracking_results_cpu(result_part: list, + tmpdir: Optional[str] = None + ) -> Optional[list]: + """Collect results on cpu mode. + + Saves the results on different gpus to 'tmpdir' and collects them by the + rank 0 worker. + + Args: + result_part (list): The part of prediction results. + tmpdir (str): Path of directory to save the temporary results from + different gpus under cpu mode. If is None, use `tempfile.mkdtemp()` + to make a temporary path. Defaults to None. + + Returns: + list or None: The collected results. + """ + rank, world_size = get_dist_info() + if world_size == 1: + return result_part + + # create a tmp dir if it is not specified + if tmpdir is None: + MAX_LEN = 512 + # 32 is whitespace + dir_tensor = torch.full((MAX_LEN, ), 32, dtype=torch.uint8) + if rank == 0: + mkdir_or_exist('.dist_test') + tmpdir = tempfile.mkdtemp(dir='.dist_test') + tmpdir = torch.tensor( + bytearray(tmpdir.encode()), dtype=torch.uint8) + dir_tensor[:len(tmpdir)] = tmpdir + broadcast(dir_tensor, 0) + tmpdir = dir_tensor.numpy().tobytes().decode().rstrip() + else: + mkdir_or_exist(tmpdir) + + # dump the part result to the dir + with open(osp.join(tmpdir, f'part_{rank}.pkl'), 'wb') as f: # type: ignore + pickle.dump(result_part, f, protocol=2) + + barrier() + + # collect all parts + if rank != 0: + return None + else: + # load results of all parts from tmp dir + part_list = [] + for i in range(world_size): + path = osp.join(tmpdir, f'part_{i}.pkl') # type: ignore + with open(path, 'rb') as f: + part_list.extend(pickle.load(f)) + shutil.rmtree(tmpdir) + return part_list diff --git a/mmdetection/mmdet/evaluation/metrics/cityscapes_metric.py b/mmdetection/mmdet/evaluation/metrics/cityscapes_metric.py new file mode 100644 index 00000000..e5cdc179 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/cityscapes_metric.py @@ -0,0 +1,205 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import os.path as osp +import shutil +import tempfile +from collections import OrderedDict +from typing import Dict, Optional, Sequence + +import mmcv +import numpy as np +from mmengine.dist import is_main_process +from mmengine.evaluator import BaseMetric +from mmengine.logging import MMLogger + +from mmdet.registry import METRICS + +try: + import cityscapesscripts.evaluation.evalInstanceLevelSemanticLabeling as CSEval # noqa: E501 + import cityscapesscripts.helpers.labels as CSLabels + + from mmdet.evaluation.functional import evaluateImgLists + HAS_CITYSCAPESAPI = True +except ImportError: + HAS_CITYSCAPESAPI = False + + +@METRICS.register_module() +class CityScapesMetric(BaseMetric): + """CityScapes metric for instance segmentation. + + Args: + outfile_prefix (str): The prefix of txt and png files. The txt and + png file will be save in a directory whose path is + "outfile_prefix.results/". + seg_prefix (str, optional): Path to the directory which contains the + cityscapes instance segmentation masks. It's necessary when + training and validation. It could be None when infer on test + dataset. Defaults to None. + format_only (bool): Format the output results without perform + evaluation. It is useful when you want to format the result + to a specific format and submit it to the test server. + Defaults to False. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + dump_matches (bool): Whether dump matches.json file during evaluating. + Defaults to False. + file_client_args (dict, optional): Arguments to instantiate the + corresponding backend in mmdet <= 3.0.0rc6. Defaults to None. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + """ + default_prefix: Optional[str] = 'cityscapes' + + def __init__(self, + outfile_prefix: str, + seg_prefix: Optional[str] = None, + format_only: bool = False, + collect_device: str = 'cpu', + prefix: Optional[str] = None, + dump_matches: bool = False, + file_client_args: dict = None, + backend_args: dict = None) -> None: + + if not HAS_CITYSCAPESAPI: + raise RuntimeError('Failed to import `cityscapesscripts`.' + 'Please try to install official ' + 'cityscapesscripts by ' + '"pip install cityscapesscripts"') + super().__init__(collect_device=collect_device, prefix=prefix) + + self.tmp_dir = None + self.format_only = format_only + if self.format_only: + assert outfile_prefix is not None, 'outfile_prefix must be not' + 'None when format_only is True, otherwise the result files will' + 'be saved to a temp directory which will be cleaned up at the end.' + else: + assert seg_prefix is not None, '`seg_prefix` is necessary when ' + 'computing the CityScapes metrics' + + if outfile_prefix is None: + self.tmp_dir = tempfile.TemporaryDirectory() + self.outfile_prefix = osp.join(self.tmp_dir.name, 'results') + else: + # the directory to save predicted panoptic segmentation mask + self.outfile_prefix = osp.join(outfile_prefix, 'results') # type: ignore # yapf: disable # noqa: E501 + + dir_name = osp.expanduser(self.outfile_prefix) + + if osp.exists(dir_name) and is_main_process(): + logger: MMLogger = MMLogger.get_current_instance() + logger.info('remove previous results.') + shutil.rmtree(dir_name) + os.makedirs(dir_name, exist_ok=True) + + self.backend_args = backend_args + if file_client_args is not None: + raise RuntimeError( + 'The `file_client_args` is deprecated, ' + 'please use `backend_args` instead, please refer to' + 'https://github.com/open-mmlab/mmdetection/blob/main/configs/_base_/datasets/coco_detection.py' # noqa: E501 + ) + + self.seg_prefix = seg_prefix + self.dump_matches = dump_matches + + def __del__(self) -> None: + """Clean up the results if necessary.""" + if self.tmp_dir is not None: + self.tmp_dir.cleanup() + + # TODO: data_batch is no longer needed, consider adjusting the + # parameter position + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + # parse pred + result = dict() + pred = data_sample['pred_instances'] + filename = data_sample['img_path'] + basename = osp.splitext(osp.basename(filename))[0] + pred_txt = osp.join(self.outfile_prefix, basename + '_pred.txt') + result['pred_txt'] = pred_txt + labels = pred['labels'].cpu().numpy() + masks = pred['masks'].cpu().numpy().astype(np.uint8) + if 'mask_scores' in pred: + # some detectors use different scores for bbox and mask + mask_scores = pred['mask_scores'].cpu().numpy() + else: + mask_scores = pred['scores'].cpu().numpy() + + with open(pred_txt, 'w') as f: + for i, (label, mask, mask_score) in enumerate( + zip(labels, masks, mask_scores)): + class_name = self.dataset_meta['classes'][label] + class_id = CSLabels.name2label[class_name].id + png_filename = osp.join( + self.outfile_prefix, + basename + f'_{i}_{class_name}.png') + mmcv.imwrite(mask, png_filename) + f.write(f'{osp.basename(png_filename)} ' + f'{class_id} {mask_score}\n') + + # parse gt + gt = dict() + img_path = filename.replace('leftImg8bit.png', + 'gtFine_instanceIds.png') + gt['file_name'] = img_path.replace('leftImg8bit', 'gtFine') + + self.results.append((gt, result)) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + + if self.format_only: + logger.info( + f'results are saved to {osp.dirname(self.outfile_prefix)}') + return OrderedDict() + logger.info('starts to compute metric') + + gts, preds = zip(*results) + # set global states in cityscapes evaluation API + gt_instances_file = osp.join(self.outfile_prefix, 'gtInstances.json') # type: ignore # yapf: disable # noqa: E501 + # split gt and prediction list + gts, preds = zip(*results) + CSEval.args.JSONOutput = False + CSEval.args.colorized = False + CSEval.args.gtInstancesFile = gt_instances_file + + groundTruthImgList = [gt['file_name'] for gt in gts] + predictionImgList = [pred['pred_txt'] for pred in preds] + CSEval_results = evaluateImgLists( + predictionImgList, + groundTruthImgList, + CSEval.args, + self.backend_args, + dump_matches=self.dump_matches)['averages'] + + eval_results = OrderedDict() + eval_results['mAP'] = CSEval_results['allAp'] + eval_results['AP@50'] = CSEval_results['allAp50%'] + + return eval_results diff --git a/mmdetection/mmdet/evaluation/metrics/coco_caption_metric.py b/mmdetection/mmdet/evaluation/metrics/coco_caption_metric.py new file mode 100644 index 00000000..d8c73501 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/coco_caption_metric.py @@ -0,0 +1,135 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +import os +import tempfile +from typing import List, Optional + +from mmengine.evaluator import BaseMetric +from mmengine.utils import track_iter_progress +from pycocotools.coco import COCO + +from mmdet.registry import METRICS + +try: + from pycocoevalcap.eval import COCOEvalCap +except ImportError: + COCOEvalCap = None + + +@METRICS.register_module() +class COCOCaptionMetric(BaseMetric): + """Coco Caption evaluation wrapper. + + Save the generated captions and transform into coco format. + Calling COCO API for caption metrics. + + Args: + ann_file (str): the path for the COCO format caption ground truth + json file, load for evaluations. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Should be modified according to the + `retrieval_type` for unambiguous results. Defaults to TR. + """ + + def __init__(self, + ann_file: str, + collect_device: str = 'cpu', + prefix: Optional[str] = None): + if COCOEvalCap is None: + raise RuntimeError( + 'COCOEvalCap is not installed, please install it by: ' + 'pip install pycocoevalcap') + + super().__init__(collect_device=collect_device, prefix=prefix) + self.ann_file = ann_file + + def process(self, data_batch, data_samples): + """Process one batch of data samples. + + The processed results should be stored in ``self.results``, which will + be used to computed the metrics when all batches have been processed. + + Args: + data_batch: A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of outputs from the model. + """ + + for data_sample in data_samples: + result = dict() + + result['caption'] = data_sample['pred_caption'] + result['image_id'] = int(data_sample['img_id']) + + # Save the result to `self.results`. + self.results.append(result) + + def compute_metrics(self, results: List): + """Compute the metrics from processed results. + + Args: + results (dict): The processed results of each batch. + + Returns: + Dict: The computed metrics. The keys are the names of the metrics, + and the values are corresponding results. + """ + # NOTICE: don't access `self.results` from the method. + + with tempfile.TemporaryDirectory() as temp_dir: + + eval_result_file = save_result( + result=results, + result_dir=temp_dir, + filename='caption_pred', + remove_duplicate='image_id', + ) + + coco_val = coco_caption_eval(eval_result_file, self.ann_file) + + return coco_val + + +def save_result(result, result_dir, filename, remove_duplicate=''): + """Saving predictions as json file for evaluation.""" + # combine results from all processes + if remove_duplicate: + result_new = [] + id_list = [] + for res in track_iter_progress(result): + if res[remove_duplicate] not in id_list: + id_list.append(res[remove_duplicate]) + result_new.append(res) + result = result_new + + final_result_file_url = os.path.join(result_dir, '%s.json' % filename) + print(f'result file saved to {final_result_file_url}') + json.dump(result, open(final_result_file_url, 'w')) + + return final_result_file_url + + +def coco_caption_eval(results_file, ann_file): + """Evaluation between gt json and prediction json files.""" + # create coco object and coco_result object + coco = COCO(ann_file) + coco_result = coco.loadRes(results_file) + + # create coco_eval object by taking coco and coco_result + coco_eval = COCOEvalCap(coco, coco_result) + + # make sure the image ids are the same + coco_eval.params['image_id'] = coco_result.getImgIds() + + # This will take some times at the first run + coco_eval.evaluate() + + # print output evaluation scores + for metric, score in coco_eval.eval.items(): + print(f'{metric}: {score:.3f}') + + return coco_eval.eval diff --git a/mmdetection/mmdet/evaluation/metrics/coco_metric.py b/mmdetection/mmdet/evaluation/metrics/coco_metric.py new file mode 100644 index 00000000..cfdc66e0 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/coco_metric.py @@ -0,0 +1,597 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import datetime +import itertools +import os.path as osp +import tempfile +from collections import OrderedDict +from typing import Dict, List, Optional, Sequence, Union + +import numpy as np +import torch +from mmengine.evaluator import BaseMetric +from mmengine.fileio import dump, get_local_path, load +from mmengine.logging import MMLogger +from terminaltables import AsciiTable + +from mmdet.datasets.api_wrappers import COCO, COCOeval, COCOevalMP +from mmdet.registry import METRICS +from mmdet.structures.mask import encode_mask_results +from ..functional import eval_recalls + + +@METRICS.register_module() +class CocoMetric(BaseMetric): + """COCO evaluation metric. + + Evaluate AR, AP, and mAP for detection tasks including proposal/box + detection and instance segmentation. Please refer to + https://cocodataset.org/#detection-eval for more details. + + Args: + ann_file (str, optional): Path to the coco format annotation file. + If not specified, ground truth annotations from the dataset will + be converted to coco format. Defaults to None. + metric (str | List[str]): Metrics to be evaluated. Valid metrics + include 'bbox', 'segm', 'proposal', and 'proposal_fast'. + Defaults to 'bbox'. + classwise (bool): Whether to evaluate the metric class-wise. + Defaults to False. + proposal_nums (Sequence[int]): Numbers of proposals to be evaluated. + Defaults to (100, 300, 1000). + iou_thrs (float | List[float], optional): IoU threshold to compute AP + and AR. If not specified, IoUs from 0.5 to 0.95 will be used. + Defaults to None. + metric_items (List[str], optional): Metric result names to be + recorded in the evaluation result. Defaults to None. + format_only (bool): Format the output results without perform + evaluation. It is useful when you want to format the result + to a specific format and submit it to the test server. + Defaults to False. + outfile_prefix (str, optional): The prefix of json files. It includes + the file path and the prefix of filename, e.g., "a/b/prefix". + If not specified, a temp file will be created. Defaults to None. + file_client_args (dict, optional): Arguments to instantiate the + corresponding backend in mmdet <= 3.0.0rc6. Defaults to None. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + sort_categories (bool): Whether sort categories in annotations. Only + used for `Objects365V1Dataset`. Defaults to False. + use_mp_eval (bool): Whether to use mul-processing evaluation + """ + default_prefix: Optional[str] = 'coco' + + def __init__(self, + ann_file: Optional[str] = None, + metric: Union[str, List[str]] = 'bbox', + classwise: bool = False, + proposal_nums: Sequence[int] = (100, 300, 1000), + iou_thrs: Optional[Union[float, Sequence[float]]] = None, + metric_items: Optional[Sequence[str]] = None, + format_only: bool = False, + outfile_prefix: Optional[str] = None, + file_client_args: dict = None, + backend_args: dict = None, + collect_device: str = 'cpu', + prefix: Optional[str] = None, + sort_categories: bool = False, + use_mp_eval: bool = False) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + # coco evaluation metrics + self.metrics = metric if isinstance(metric, list) else [metric] + allowed_metrics = ['bbox', 'segm', 'proposal', 'proposal_fast'] + for metric in self.metrics: + if metric not in allowed_metrics: + raise KeyError( + "metric should be one of 'bbox', 'segm', 'proposal', " + f"'proposal_fast', but got {metric}.") + + # do class wise evaluation, default False + self.classwise = classwise + # whether to use multi processing evaluation, default False + self.use_mp_eval = use_mp_eval + + # proposal_nums used to compute recall or precision. + self.proposal_nums = list(proposal_nums) + + # iou_thrs used to compute recall or precision. + if iou_thrs is None: + iou_thrs = np.linspace( + .5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True) + self.iou_thrs = iou_thrs + self.metric_items = metric_items + self.format_only = format_only + if self.format_only: + assert outfile_prefix is not None, 'outfile_prefix must be not' + 'None when format_only is True, otherwise the result files will' + 'be saved to a temp directory which will be cleaned up at the end.' + + self.outfile_prefix = outfile_prefix + + self.backend_args = backend_args + if file_client_args is not None: + raise RuntimeError( + 'The `file_client_args` is deprecated, ' + 'please use `backend_args` instead, please refer to' + 'https://github.com/open-mmlab/mmdetection/blob/main/configs/_base_/datasets/coco_detection.py' # noqa: E501 + ) + + # if ann_file is not specified, + # initialize coco api with the converted dataset + if ann_file is not None: + with get_local_path( + ann_file, backend_args=self.backend_args) as local_path: + self._coco_api = COCO(local_path) + if sort_categories: + # 'categories' list in objects365_train.json and + # objects365_val.json is inconsistent, need sort + # list(or dict) before get cat_ids. + cats = self._coco_api.cats + sorted_cats = {i: cats[i] for i in sorted(cats)} + self._coco_api.cats = sorted_cats + categories = self._coco_api.dataset['categories'] + sorted_categories = sorted( + categories, key=lambda i: i['id']) + self._coco_api.dataset['categories'] = sorted_categories + else: + self._coco_api = None + + # handle dataset lazy init + self.cat_ids = None + self.img_ids = None + + def fast_eval_recall(self, + results: List[dict], + proposal_nums: Sequence[int], + iou_thrs: Sequence[float], + logger: Optional[MMLogger] = None) -> np.ndarray: + """Evaluate proposal recall with COCO's fast_eval_recall. + + Args: + results (List[dict]): Results of the dataset. + proposal_nums (Sequence[int]): Proposal numbers used for + evaluation. + iou_thrs (Sequence[float]): IoU thresholds used for evaluation. + logger (MMLogger, optional): Logger used for logging the recall + summary. + Returns: + np.ndarray: Averaged recall results. + """ + gt_bboxes = [] + pred_bboxes = [result['bboxes'] for result in results] + for i in range(len(self.img_ids)): + ann_ids = self._coco_api.get_ann_ids(img_ids=self.img_ids[i]) + ann_info = self._coco_api.load_anns(ann_ids) + if len(ann_info) == 0: + gt_bboxes.append(np.zeros((0, 4))) + continue + bboxes = [] + for ann in ann_info: + if ann.get('ignore', False) or ann['iscrowd']: + continue + x1, y1, w, h = ann['bbox'] + bboxes.append([x1, y1, x1 + w, y1 + h]) + bboxes = np.array(bboxes, dtype=np.float32) + if bboxes.shape[0] == 0: + bboxes = np.zeros((0, 4)) + gt_bboxes.append(bboxes) + + recalls = eval_recalls( + gt_bboxes, pred_bboxes, proposal_nums, iou_thrs, logger=logger) + ar = recalls.mean(axis=1) + return ar + + def xyxy2xywh(self, bbox: np.ndarray) -> list: + """Convert ``xyxy`` style bounding boxes to ``xywh`` style for COCO + evaluation. + + Args: + bbox (numpy.ndarray): The bounding boxes, shape (4, ), in + ``xyxy`` order. + + Returns: + list[float]: The converted bounding boxes, in ``xywh`` order. + """ + + _bbox: List = bbox.tolist() + return [ + _bbox[0], + _bbox[1], + _bbox[2] - _bbox[0], + _bbox[3] - _bbox[1], + ] + + def results2json(self, results: Sequence[dict], + outfile_prefix: str) -> dict: + """Dump the detection results to a COCO style json file. + + There are 3 types of results: proposals, bbox predictions, mask + predictions, and they have different data types. This method will + automatically recognize the type, and dump them to json files. + + Args: + results (Sequence[dict]): Testing results of the + dataset. + outfile_prefix (str): The filename prefix of the json files. If the + prefix is "somepath/xxx", the json files will be named + "somepath/xxx.bbox.json", "somepath/xxx.segm.json", + "somepath/xxx.proposal.json". + + Returns: + dict: Possible keys are "bbox", "segm", "proposal", and + values are corresponding filenames. + """ + bbox_json_results = [] + segm_json_results = [] if 'masks' in results[0] else None + for idx, result in enumerate(results): + image_id = result.get('img_id', idx) + labels = result['labels'] + bboxes = result['bboxes'] + scores = result['scores'] + # bbox results + for i, label in enumerate(labels): + data = dict() + data['image_id'] = image_id + data['bbox'] = self.xyxy2xywh(bboxes[i]) + data['score'] = float(scores[i]) + data['category_id'] = self.cat_ids[label] + bbox_json_results.append(data) + + if segm_json_results is None: + continue + + # segm results + masks = result['masks'] + mask_scores = result.get('mask_scores', scores) + for i, label in enumerate(labels): + data = dict() + data['image_id'] = image_id + data['bbox'] = self.xyxy2xywh(bboxes[i]) + data['score'] = float(mask_scores[i]) + data['category_id'] = self.cat_ids[label] + if isinstance(masks[i]['counts'], bytes): + masks[i]['counts'] = masks[i]['counts'].decode() + data['segmentation'] = masks[i] + segm_json_results.append(data) + + result_files = dict() + result_files['bbox'] = f'{outfile_prefix}.bbox.json' + result_files['proposal'] = f'{outfile_prefix}.bbox.json' + dump(bbox_json_results, result_files['bbox']) + + if segm_json_results is not None: + result_files['segm'] = f'{outfile_prefix}.segm.json' + dump(segm_json_results, result_files['segm']) + + return result_files + + def gt_to_coco_json(self, gt_dicts: Sequence[dict], + outfile_prefix: str) -> str: + """Convert ground truth to coco format json file. + + Args: + gt_dicts (Sequence[dict]): Ground truth of the dataset. + outfile_prefix (str): The filename prefix of the json files. If the + prefix is "somepath/xxx", the json file will be named + "somepath/xxx.gt.json". + Returns: + str: The filename of the json file. + """ + categories = [ + dict(id=id, name=name) + for id, name in enumerate(self.dataset_meta['classes']) + ] + image_infos = [] + annotations = [] + + for idx, gt_dict in enumerate(gt_dicts): + img_id = gt_dict.get('img_id', idx) + image_info = dict( + id=img_id, + width=gt_dict['width'], + height=gt_dict['height'], + file_name='') + image_infos.append(image_info) + for ann in gt_dict['anns']: + label = ann['bbox_label'] + bbox = ann['bbox'] + coco_bbox = [ + bbox[0], + bbox[1], + bbox[2] - bbox[0], + bbox[3] - bbox[1], + ] + + annotation = dict( + id=len(annotations) + + 1, # coco api requires id starts with 1 + image_id=img_id, + bbox=coco_bbox, + iscrowd=ann.get('ignore_flag', 0), + category_id=int(label), + area=coco_bbox[2] * coco_bbox[3]) + if ann.get('mask', None): + mask = ann['mask'] + # area = mask_util.area(mask) + if isinstance(mask, dict) and isinstance( + mask['counts'], bytes): + mask['counts'] = mask['counts'].decode() + annotation['segmentation'] = mask + # annotation['area'] = float(area) + annotations.append(annotation) + + info = dict( + date_created=str(datetime.datetime.now()), + description='Coco json file converted by mmdet CocoMetric.') + coco_json = dict( + info=info, + images=image_infos, + categories=categories, + licenses=None, + ) + if len(annotations) > 0: + coco_json['annotations'] = annotations + converted_json_path = f'{outfile_prefix}.gt.json' + dump(coco_json, converted_json_path) + return converted_json_path + + # TODO: data_batch is no longer needed, consider adjusting the + # parameter position + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + result = dict() + pred = data_sample['pred_instances'] + result['img_id'] = data_sample['img_id'] + result['bboxes'] = pred['bboxes'].cpu().numpy() + result['scores'] = pred['scores'].cpu().numpy() + result['labels'] = pred['labels'].cpu().numpy() + # encode mask to RLE + if 'masks' in pred: + result['masks'] = encode_mask_results( + pred['masks'].detach().cpu().numpy()) if isinstance( + pred['masks'], torch.Tensor) else pred['masks'] + # some detectors use different scores for bbox and mask + if 'mask_scores' in pred: + result['mask_scores'] = pred['mask_scores'].cpu().numpy() + + # parse gt + gt = dict() + gt['width'] = data_sample['ori_shape'][1] + gt['height'] = data_sample['ori_shape'][0] + gt['img_id'] = data_sample['img_id'] + if self._coco_api is None: + # TODO: Need to refactor to support LoadAnnotations + assert 'instances' in data_sample, \ + 'ground truth is required for evaluation when ' \ + '`ann_file` is not provided' + gt['anns'] = data_sample['instances'] + # add converted result to the results list + self.results.append((gt, result)) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + + # split gt and prediction list + gts, preds = zip(*results) + + tmp_dir = None + if self.outfile_prefix is None: + tmp_dir = tempfile.TemporaryDirectory() + outfile_prefix = osp.join(tmp_dir.name, 'results') + else: + outfile_prefix = self.outfile_prefix + + if self._coco_api is None: + # use converted gt json file to initialize coco api + logger.info('Converting ground truth to coco format...') + coco_json_path = self.gt_to_coco_json( + gt_dicts=gts, outfile_prefix=outfile_prefix) + self._coco_api = COCO(coco_json_path) + + # handle lazy init + if self.cat_ids is None: + self.cat_ids = self._coco_api.get_cat_ids( + cat_names=self.dataset_meta['classes']) + if self.img_ids is None: + self.img_ids = self._coco_api.get_img_ids() + + # convert predictions to coco format and dump to json file + result_files = self.results2json(preds, outfile_prefix) + + eval_results = OrderedDict() + if self.format_only: + logger.info('results are saved in ' + f'{osp.dirname(outfile_prefix)}') + return eval_results + + for metric in self.metrics: + logger.info(f'Evaluating {metric}...') + + # TODO: May refactor fast_eval_recall to an independent metric? + # fast eval recall + if metric == 'proposal_fast': + ar = self.fast_eval_recall( + preds, self.proposal_nums, self.iou_thrs, logger=logger) + log_msg = [] + for i, num in enumerate(self.proposal_nums): + eval_results[f'AR@{num}'] = ar[i] + log_msg.append(f'\nAR@{num}\t{ar[i]:.4f}') + log_msg = ''.join(log_msg) + logger.info(log_msg) + continue + + # evaluate proposal, bbox and segm + iou_type = 'bbox' if metric == 'proposal' else metric + if metric not in result_files: + raise KeyError(f'{metric} is not in results') + try: + predictions = load(result_files[metric]) + if iou_type == 'segm': + # Refer to https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/coco.py#L331 # noqa + # When evaluating mask AP, if the results contain bbox, + # cocoapi will use the box area instead of the mask area + # for calculating the instance area. Though the overall AP + # is not affected, this leads to different + # small/medium/large mask AP results. + for x in predictions: + x.pop('bbox') + coco_dt = self._coco_api.loadRes(predictions) + + except IndexError: + logger.error( + 'The testing results of the whole dataset is empty.') + break + + if self.use_mp_eval: + coco_eval = COCOevalMP(self._coco_api, coco_dt, iou_type) + else: + coco_eval = COCOeval(self._coco_api, coco_dt, iou_type) + + coco_eval.params.catIds = self.cat_ids + coco_eval.params.imgIds = self.img_ids + coco_eval.params.maxDets = list(self.proposal_nums) + coco_eval.params.iouThrs = self.iou_thrs + + # mapping of cocoEval.stats + coco_metric_names = { + 'mAP': 0, + 'mAP_50': 1, + 'mAP_75': 2, + 'mAP_s': 3, + 'mAP_m': 4, + 'mAP_l': 5, + 'AR@100': 6, + 'AR@300': 7, + 'AR@1000': 8, + 'AR_s@1000': 9, + 'AR_m@1000': 10, + 'AR_l@1000': 11 + } + metric_items = self.metric_items + if metric_items is not None: + for metric_item in metric_items: + if metric_item not in coco_metric_names: + raise KeyError( + f'metric item "{metric_item}" is not supported') + + if metric == 'proposal': + coco_eval.params.useCats = 0 + coco_eval.evaluate() + coco_eval.accumulate() + coco_eval.summarize() + if metric_items is None: + metric_items = [ + 'AR@100', 'AR@300', 'AR@1000', 'AR_s@1000', + 'AR_m@1000', 'AR_l@1000' + ] + + for item in metric_items: + val = float( + f'{coco_eval.stats[coco_metric_names[item]]:.3f}') + eval_results[item] = val + else: + coco_eval.evaluate() + coco_eval.accumulate() + coco_eval.summarize() + if self.classwise: # Compute per-category AP + # Compute per-category AP + # from https://github.com/facebookresearch/detectron2/ + precisions = coco_eval.eval['precision'] + # precision: (iou, recall, cls, area range, max dets) + assert len(self.cat_ids) == precisions.shape[2] + + results_per_category = [] + for idx, cat_id in enumerate(self.cat_ids): + t = [] + # area range index 0: all area ranges + # max dets index -1: typically 100 per image + nm = self._coco_api.loadCats(cat_id)[0] + precision = precisions[:, :, idx, 0, -1] + precision = precision[precision > -1] + if precision.size: + ap = np.mean(precision) + else: + ap = float('nan') + t.append(f'{nm["name"]}') + t.append(f'{round(ap, 3)}') + eval_results[f'{nm["name"]}_precision'] = round(ap, 3) + + # indexes of IoU @50 and @75 + for iou in [0, 5]: + precision = precisions[iou, :, idx, 0, -1] + precision = precision[precision > -1] + if precision.size: + ap = np.mean(precision) + else: + ap = float('nan') + t.append(f'{round(ap, 3)}') + + # indexes of area of small, median and large + for area in [1, 2, 3]: + precision = precisions[:, :, idx, area, -1] + precision = precision[precision > -1] + if precision.size: + ap = np.mean(precision) + else: + ap = float('nan') + t.append(f'{round(ap, 3)}') + results_per_category.append(tuple(t)) + + num_columns = len(results_per_category[0]) + results_flatten = list( + itertools.chain(*results_per_category)) + headers = [ + 'category', 'mAP', 'mAP_50', 'mAP_75', 'mAP_s', + 'mAP_m', 'mAP_l' + ] + results_2d = itertools.zip_longest(*[ + results_flatten[i::num_columns] + for i in range(num_columns) + ]) + table_data = [headers] + table_data += [result for result in results_2d] + table = AsciiTable(table_data) + logger.info('\n' + table.table) + + if metric_items is None: + metric_items = [ + 'mAP', 'mAP_50', 'mAP_75', 'mAP_s', 'mAP_m', 'mAP_l' + ] + + for metric_item in metric_items: + key = f'{metric}_{metric_item}' + val = coco_eval.stats[coco_metric_names[metric_item]] + eval_results[key] = float(f'{round(val, 3)}') + + ap = coco_eval.stats[:6] + logger.info(f'{metric}_mAP_copypaste: {ap[0]:.3f} ' + f'{ap[1]:.3f} {ap[2]:.3f} {ap[3]:.3f} ' + f'{ap[4]:.3f} {ap[5]:.3f}') + + if tmp_dir is not None: + tmp_dir.cleanup() + return eval_results diff --git a/mmdetection/mmdet/evaluation/metrics/coco_occluded_metric.py b/mmdetection/mmdet/evaluation/metrics/coco_occluded_metric.py new file mode 100644 index 00000000..81235a04 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/coco_occluded_metric.py @@ -0,0 +1,204 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Union + +import mmengine +import numpy as np +from mmengine.fileio import load +from mmengine.logging import print_log +from pycocotools import mask as coco_mask +from terminaltables import AsciiTable + +from mmdet.registry import METRICS +from .coco_metric import CocoMetric + + +@METRICS.register_module() +class CocoOccludedSeparatedMetric(CocoMetric): + """Metric of separated and occluded masks which presented in paper `A Tri- + Layer Plugin to Improve Occluded Detection. + + `_. + + Separated COCO and Occluded COCO are automatically generated subsets of + COCO val dataset, collecting separated objects and partially occluded + objects for a large variety of categories. In this way, we define + occlusion into two major categories: separated and partially occluded. + + - Separation: target object segmentation mask is separated into distinct + regions by the occluder. + - Partial Occlusion: target object is partially occluded but the + segmentation mask is connected. + + These two new scalable real-image datasets are to benchmark a model's + capability to detect occluded objects of 80 common categories. + + Please cite the paper if you use this dataset: + + @article{zhan2022triocc, + title={A Tri-Layer Plugin to Improve Occluded Detection}, + author={Zhan, Guanqi and Xie, Weidi and Zisserman, Andrew}, + journal={British Machine Vision Conference}, + year={2022} + } + + Args: + occluded_ann (str): Path to the occluded coco annotation file. + separated_ann (str): Path to the separated coco annotation file. + score_thr (float): Score threshold of the detection masks. + Defaults to 0.3. + iou_thr (float): IoU threshold for the recall calculation. + Defaults to 0.75. + metric (str | List[str]): Metrics to be evaluated. Valid metrics + include 'bbox', 'segm', 'proposal', and 'proposal_fast'. + Defaults to 'bbox'. + """ + default_prefix: Optional[str] = 'coco' + + def __init__( + self, + *args, + occluded_ann: + str = 'https://www.robots.ox.ac.uk/~vgg/research/tpod/datasets/occluded_coco.pkl', # noqa + separated_ann: + str = 'https://www.robots.ox.ac.uk/~vgg/research/tpod/datasets/separated_coco.pkl', # noqa + score_thr: float = 0.3, + iou_thr: float = 0.75, + metric: Union[str, List[str]] = ['bbox', 'segm'], + **kwargs) -> None: + super().__init__(*args, metric=metric, **kwargs) + self.occluded_ann = load(occluded_ann) + self.separated_ann = load(separated_ann) + self.score_thr = score_thr + self.iou_thr = iou_thr + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. + """ + coco_metric_res = super().compute_metrics(results) + eval_res = self.evaluate_occluded_separated(results) + coco_metric_res.update(eval_res) + return coco_metric_res + + def evaluate_occluded_separated(self, results: List[tuple]) -> dict: + """Compute the recall of occluded and separated masks. + + Args: + results (list[tuple]): Testing results of the dataset. + + Returns: + dict[str, float]: The recall of occluded and separated masks. + """ + dict_det = {} + print_log('processing detection results...') + prog_bar = mmengine.ProgressBar(len(results)) + for i in range(len(results)): + gt, dt = results[i] + img_id = dt['img_id'] + cur_img_name = self._coco_api.imgs[img_id]['file_name'] + if cur_img_name not in dict_det.keys(): + dict_det[cur_img_name] = [] + + for bbox, score, label, mask in zip(dt['bboxes'], dt['scores'], + dt['labels'], dt['masks']): + cur_binary_mask = coco_mask.decode(mask) + dict_det[cur_img_name].append([ + score, self.dataset_meta['classes'][label], + cur_binary_mask, bbox + ]) + dict_det[cur_img_name].sort( + key=lambda x: (-x[0], x[3][0], x[3][1]) + ) # rank by confidence from high to low, avoid same confidence + prog_bar.update() + print_log('\ncomputing occluded mask recall...', logger='current') + occluded_correct_num, occluded_recall = self.compute_recall( + dict_det, gt_ann=self.occluded_ann, is_occ=True) + print_log( + f'\nCOCO occluded mask recall: {occluded_recall:.2f}%', + logger='current') + print_log( + f'COCO occluded mask success num: {occluded_correct_num}', + logger='current') + print_log('computing separated mask recall...', logger='current') + separated_correct_num, separated_recall = self.compute_recall( + dict_det, gt_ann=self.separated_ann, is_occ=False) + print_log( + f'\nCOCO separated mask recall: {separated_recall:.2f}%', + logger='current') + print_log( + f'COCO separated mask success num: {separated_correct_num}', + logger='current') + table_data = [ + ['mask type', 'recall', 'num correct'], + ['occluded', f'{occluded_recall:.2f}%', occluded_correct_num], + ['separated', f'{separated_recall:.2f}%', separated_correct_num] + ] + table = AsciiTable(table_data) + print_log('\n' + table.table, logger='current') + return dict( + occluded_recall=occluded_recall, separated_recall=separated_recall) + + def compute_recall(self, + result_dict: dict, + gt_ann: list, + is_occ: bool = True) -> tuple: + """Compute the recall of occluded or separated masks. + + Args: + result_dict (dict): Processed mask results. + gt_ann (list): Occluded or separated coco annotations. + is_occ (bool): Whether the annotation is occluded mask. + Defaults to True. + Returns: + tuple: number of correct masks and the recall. + """ + correct = 0 + prog_bar = mmengine.ProgressBar(len(gt_ann)) + for iter_i in range(len(gt_ann)): + cur_item = gt_ann[iter_i] + cur_img_name = cur_item[0] + cur_gt_bbox = cur_item[3] + if is_occ: + cur_gt_bbox = [ + cur_gt_bbox[0], cur_gt_bbox[1], + cur_gt_bbox[0] + cur_gt_bbox[2], + cur_gt_bbox[1] + cur_gt_bbox[3] + ] + cur_gt_class = cur_item[1] + cur_gt_mask = coco_mask.decode(cur_item[4]) + + assert cur_img_name in result_dict.keys() + cur_detections = result_dict[cur_img_name] + + correct_flag = False + for i in range(len(cur_detections)): + cur_det_confidence = cur_detections[i][0] + if cur_det_confidence < self.score_thr: + break + cur_det_class = cur_detections[i][1] + if cur_det_class != cur_gt_class: + continue + cur_det_mask = cur_detections[i][2] + cur_iou = self.mask_iou(cur_det_mask, cur_gt_mask) + if cur_iou >= self.iou_thr: + correct_flag = True + break + if correct_flag: + correct += 1 + prog_bar.update() + recall = correct / len(gt_ann) * 100 + return correct, recall + + def mask_iou(self, mask1: np.ndarray, mask2: np.ndarray) -> np.ndarray: + """Compute IoU between two masks.""" + mask1_area = np.count_nonzero(mask1 == 1) + mask2_area = np.count_nonzero(mask2 == 1) + intersection = np.count_nonzero(np.logical_and(mask1 == 1, mask2 == 1)) + iou = intersection / (mask1_area + mask2_area - intersection) + return iou diff --git a/mmdetection/mmdet/evaluation/metrics/coco_panoptic_metric.py b/mmdetection/mmdet/evaluation/metrics/coco_panoptic_metric.py new file mode 100644 index 00000000..f86be916 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/coco_panoptic_metric.py @@ -0,0 +1,618 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import datetime +import itertools +import os.path as osp +import tempfile +from typing import Dict, Optional, Sequence, Tuple, Union + +import mmcv +import numpy as np +from mmengine.evaluator import BaseMetric +from mmengine.fileio import dump, get_local_path, load +from mmengine.logging import MMLogger, print_log +from terminaltables import AsciiTable + +from mmdet.datasets.api_wrappers import COCOPanoptic +from mmdet.registry import METRICS +from ..functional import (INSTANCE_OFFSET, pq_compute_multi_core, + pq_compute_single_core) + +try: + import panopticapi + from panopticapi.evaluation import VOID, PQStat + from panopticapi.utils import id2rgb, rgb2id +except ImportError: + panopticapi = None + id2rgb = None + rgb2id = None + VOID = None + PQStat = None + + +@METRICS.register_module() +class CocoPanopticMetric(BaseMetric): + """COCO panoptic segmentation evaluation metric. + + Evaluate PQ, SQ RQ for panoptic segmentation tasks. Please refer to + https://cocodataset.org/#panoptic-eval for more details. + + Args: + ann_file (str, optional): Path to the coco format annotation file. + If not specified, ground truth annotations from the dataset will + be converted to coco format. Defaults to None. + seg_prefix (str, optional): Path to the directory which contains the + coco panoptic segmentation mask. It should be specified when + evaluate. Defaults to None. + classwise (bool): Whether to evaluate the metric class-wise. + Defaults to False. + outfile_prefix (str, optional): The prefix of json files. It includes + the file path and the prefix of filename, e.g., "a/b/prefix". + If not specified, a temp file will be created. + It should be specified when format_only is True. Defaults to None. + format_only (bool): Format the output results without perform + evaluation. It is useful when you want to format the result + to a specific format and submit it to the test server. + Defaults to False. + nproc (int): Number of processes for panoptic quality computing. + Defaults to 32. When ``nproc`` exceeds the number of cpu cores, + the number of cpu cores is used. + file_client_args (dict, optional): Arguments to instantiate the + corresponding backend in mmdet <= 3.0.0rc6. Defaults to None. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + """ + default_prefix: Optional[str] = 'coco_panoptic' + + def __init__(self, + ann_file: Optional[str] = None, + seg_prefix: Optional[str] = None, + classwise: bool = False, + format_only: bool = False, + outfile_prefix: Optional[str] = None, + nproc: int = 32, + file_client_args: dict = None, + backend_args: dict = None, + collect_device: str = 'cpu', + prefix: Optional[str] = None) -> None: + if panopticapi is None: + raise RuntimeError( + 'panopticapi is not installed, please install it by: ' + 'pip install git+https://github.com/cocodataset/' + 'panopticapi.git.') + + super().__init__(collect_device=collect_device, prefix=prefix) + self.classwise = classwise + self.format_only = format_only + if self.format_only: + assert outfile_prefix is not None, 'outfile_prefix must be not' + 'None when format_only is True, otherwise the result files will' + 'be saved to a temp directory which will be cleaned up at the end.' + + self.tmp_dir = None + # outfile_prefix should be a prefix of a path which points to a shared + # storage when train or test with multi nodes. + self.outfile_prefix = outfile_prefix + if outfile_prefix is None: + self.tmp_dir = tempfile.TemporaryDirectory() + self.outfile_prefix = osp.join(self.tmp_dir.name, 'results') + # the directory to save predicted panoptic segmentation mask + self.seg_out_dir = f'{self.outfile_prefix}.panoptic' + self.nproc = nproc + self.seg_prefix = seg_prefix + + self.cat_ids = None + self.cat2label = None + + self.backend_args = backend_args + if file_client_args is not None: + raise RuntimeError( + 'The `file_client_args` is deprecated, ' + 'please use `backend_args` instead, please refer to' + 'https://github.com/open-mmlab/mmdetection/blob/main/configs/_base_/datasets/coco_detection.py' # noqa: E501 + ) + + if ann_file: + with get_local_path( + ann_file, backend_args=self.backend_args) as local_path: + self._coco_api = COCOPanoptic(local_path) + self.categories = self._coco_api.cats + else: + self._coco_api = None + self.categories = None + + def __del__(self) -> None: + """Clean up.""" + if self.tmp_dir is not None: + self.tmp_dir.cleanup() + + def gt_to_coco_json(self, gt_dicts: Sequence[dict], + outfile_prefix: str) -> Tuple[str, str]: + """Convert ground truth to coco panoptic segmentation format json file. + + Args: + gt_dicts (Sequence[dict]): Ground truth of the dataset. + outfile_prefix (str): The filename prefix of the json file. If the + prefix is "somepath/xxx", the json file will be named + "somepath/xxx.gt.json". + + Returns: + Tuple[str, str]: The filename of the json file and the name of the\ + directory which contains panoptic segmentation masks. + """ + assert len(gt_dicts) > 0, 'gt_dicts is empty.' + gt_folder = osp.dirname(gt_dicts[0]['seg_map_path']) + converted_json_path = f'{outfile_prefix}.gt.json' + + categories = [] + for id, name in enumerate(self.dataset_meta['classes']): + isthing = 1 if name in self.dataset_meta['thing_classes'] else 0 + categories.append({'id': id, 'name': name, 'isthing': isthing}) + + image_infos = [] + annotations = [] + for gt_dict in gt_dicts: + img_id = gt_dict['image_id'] + image_info = { + 'id': img_id, + 'width': gt_dict['width'], + 'height': gt_dict['height'], + 'file_name': osp.split(gt_dict['seg_map_path'])[-1] + } + image_infos.append(image_info) + + pan_png = mmcv.imread(gt_dict['seg_map_path']).squeeze() + pan_png = pan_png[:, :, ::-1] + pan_png = rgb2id(pan_png) + segments_info = [] + for segment_info in gt_dict['segments_info']: + id = segment_info['id'] + label = segment_info['category'] + mask = pan_png == id + isthing = categories[label]['isthing'] + if isthing: + iscrowd = 1 if not segment_info['is_thing'] else 0 + else: + iscrowd = 0 + + new_segment_info = { + 'id': id, + 'category_id': label, + 'isthing': isthing, + 'iscrowd': iscrowd, + 'area': mask.sum() + } + segments_info.append(new_segment_info) + + segm_file = image_info['file_name'].replace('.jpg', '.png') + annotation = dict( + image_id=img_id, + segments_info=segments_info, + file_name=segm_file) + annotations.append(annotation) + pan_png = id2rgb(pan_png) + + info = dict( + date_created=str(datetime.datetime.now()), + description='Coco json file converted by mmdet CocoPanopticMetric.' + ) + coco_json = dict( + info=info, + images=image_infos, + categories=categories, + licenses=None, + ) + if len(annotations) > 0: + coco_json['annotations'] = annotations + dump(coco_json, converted_json_path) + return converted_json_path, gt_folder + + def result2json(self, results: Sequence[dict], + outfile_prefix: str) -> Tuple[str, str]: + """Dump the panoptic results to a COCO style json file and a directory. + + Args: + results (Sequence[dict]): Testing results of the dataset. + outfile_prefix (str): The filename prefix of the json files and the + directory. + + Returns: + Tuple[str, str]: The json file and the directory which contains \ + panoptic segmentation masks. The filename of the json is + "somepath/xxx.panoptic.json" and name of the directory is + "somepath/xxx.panoptic". + """ + label2cat = dict((v, k) for (k, v) in self.cat2label.items()) + pred_annotations = [] + for idx in range(len(results)): + result = results[idx] + for segment_info in result['segments_info']: + sem_label = segment_info['category_id'] + # convert sem_label to json label + cat_id = label2cat[sem_label] + segment_info['category_id'] = label2cat[sem_label] + is_thing = self.categories[cat_id]['isthing'] + segment_info['isthing'] = is_thing + pred_annotations.append(result) + pan_json_results = dict(annotations=pred_annotations) + json_filename = f'{outfile_prefix}.panoptic.json' + dump(pan_json_results, json_filename) + return json_filename, ( + self.seg_out_dir + if self.tmp_dir is None else tempfile.gettempdir()) + + def _parse_predictions(self, + pred: dict, + img_id: int, + segm_file: str, + label2cat=None) -> dict: + """Parse panoptic segmentation predictions. + + Args: + pred (dict): Panoptic segmentation predictions. + img_id (int): Image id. + segm_file (str): Segmentation file name. + label2cat (dict): Mapping from label to category id. + Defaults to None. + + Returns: + dict: Parsed predictions. + """ + result = dict() + result['img_id'] = img_id + # shape (1, H, W) -> (H, W) + pan = pred['pred_panoptic_seg']['sem_seg'].cpu().numpy()[0] + ignore_index = pred['pred_panoptic_seg'].get( + 'ignore_index', len(self.dataset_meta['classes'])) + pan_labels = np.unique(pan) + segments_info = [] + for pan_label in pan_labels: + sem_label = pan_label % INSTANCE_OFFSET + # We reserve the length of dataset_meta['classes'] + # and ignore_index for VOID label + if sem_label == len( + self.dataset_meta['classes']) or sem_label == ignore_index: + continue + mask = pan == pan_label + area = mask.sum() + segments_info.append({ + 'id': + int(pan_label), + # when ann_file provided, sem_label should be cat_id, otherwise + # sem_label should be a continuous id, not the cat_id + # defined in dataset + 'category_id': + label2cat[sem_label] if label2cat else sem_label, + 'area': + int(area) + }) + # evaluation script uses 0 for VOID label. + pan[pan % INSTANCE_OFFSET == len(self.dataset_meta['classes'])] = VOID + pan[pan % INSTANCE_OFFSET == ignore_index] = VOID + + pan = id2rgb(pan).astype(np.uint8) + mmcv.imwrite(pan[:, :, ::-1], osp.join(self.seg_out_dir, segm_file)) + result = { + 'image_id': img_id, + 'segments_info': segments_info, + 'file_name': segm_file + } + + return result + + def _compute_batch_pq_stats(self, data_samples: Sequence[dict]): + """Process gts and predictions when ``outfile_prefix`` is not set, gts + are from dataset or a json file which is defined by ``ann_file``. + + Intermediate results, ``pq_stats``, are computed here and put into + ``self.results``. + """ + if self._coco_api is None: + categories = dict() + for id, name in enumerate(self.dataset_meta['classes']): + isthing = 1 if name in self.dataset_meta['thing_classes']\ + else 0 + categories[id] = {'id': id, 'name': name, 'isthing': isthing} + label2cat = None + else: + categories = self.categories + cat_ids = self._coco_api.get_cat_ids( + cat_names=self.dataset_meta['classes']) + label2cat = {i: cat_id for i, cat_id in enumerate(cat_ids)} + + for data_sample in data_samples: + # parse pred + img_id = data_sample['img_id'] + segm_file = osp.basename(data_sample['img_path']).replace( + '.jpg', '.png') + result = self._parse_predictions( + pred=data_sample, + img_id=img_id, + segm_file=segm_file, + label2cat=label2cat) + + # parse gt + gt = dict() + gt['image_id'] = img_id + gt['width'] = data_sample['ori_shape'][1] + gt['height'] = data_sample['ori_shape'][0] + gt['file_name'] = segm_file + + if self._coco_api is None: + # get segments_info from data_sample + seg_map_path = osp.join(self.seg_prefix, segm_file) + pan_png = mmcv.imread(seg_map_path).squeeze() + pan_png = pan_png[:, :, ::-1] + pan_png = rgb2id(pan_png) + segments_info = [] + + for segment_info in data_sample['segments_info']: + id = segment_info['id'] + label = segment_info['category'] + mask = pan_png == id + isthing = categories[label]['isthing'] + if isthing: + iscrowd = 1 if not segment_info['is_thing'] else 0 + else: + iscrowd = 0 + + new_segment_info = { + 'id': id, + 'category_id': label, + 'isthing': isthing, + 'iscrowd': iscrowd, + 'area': mask.sum() + } + segments_info.append(new_segment_info) + else: + # get segments_info from annotation file + segments_info = self._coco_api.imgToAnns[img_id] + + gt['segments_info'] = segments_info + + pq_stats = pq_compute_single_core( + proc_id=0, + annotation_set=[(gt, result)], + gt_folder=self.seg_prefix, + pred_folder=self.seg_out_dir, + categories=categories, + backend_args=self.backend_args) + + self.results.append(pq_stats) + + def _process_gt_and_predictions(self, data_samples: Sequence[dict]): + """Process gts and predictions when ``outfile_prefix`` is set. + + The predictions will be saved to directory specified by + ``outfile_predfix``. The matched pair (gt, result) will be put into + ``self.results``. + """ + for data_sample in data_samples: + # parse pred + img_id = data_sample['img_id'] + segm_file = osp.basename(data_sample['img_path']).replace( + '.jpg', '.png') + result = self._parse_predictions( + pred=data_sample, img_id=img_id, segm_file=segm_file) + + # parse gt + gt = dict() + gt['image_id'] = img_id + gt['width'] = data_sample['ori_shape'][1] + gt['height'] = data_sample['ori_shape'][0] + + if self._coco_api is None: + # get segments_info from dataset + gt['segments_info'] = data_sample['segments_info'] + gt['seg_map_path'] = data_sample['seg_map_path'] + + self.results.append((gt, result)) + + # TODO: data_batch is no longer needed, consider adjusting the + # parameter position + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + # If ``self.tmp_dir`` is none, it will save gt and predictions to + # self.results, otherwise, it will compute pq_stats here. + if self.tmp_dir is None: + self._process_gt_and_predictions(data_samples) + else: + self._compute_batch_pq_stats(data_samples) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. There + are two cases: + + - When ``outfile_prefix`` is not provided, the elements in + results are pq_stats which can be summed directly to get PQ. + - When ``outfile_prefix`` is provided, the elements in + results are tuples like (gt, pred). + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + + if self.tmp_dir is None: + # do evaluation after collect all the results + + # split gt and prediction list + gts, preds = zip(*results) + + if self._coco_api is None: + # use converted gt json file to initialize coco api + logger.info('Converting ground truth to coco format...') + coco_json_path, gt_folder = self.gt_to_coco_json( + gt_dicts=gts, outfile_prefix=self.outfile_prefix) + self._coco_api = COCOPanoptic(coco_json_path) + else: + gt_folder = self.seg_prefix + + self.cat_ids = self._coco_api.get_cat_ids( + cat_names=self.dataset_meta['classes']) + self.cat2label = { + cat_id: i + for i, cat_id in enumerate(self.cat_ids) + } + self.img_ids = self._coco_api.get_img_ids() + self.categories = self._coco_api.cats + + # convert predictions to coco format and dump to json file + json_filename, pred_folder = self.result2json( + results=preds, outfile_prefix=self.outfile_prefix) + + if self.format_only: + logger.info('results are saved in ' + f'{osp.dirname(self.outfile_prefix)}') + return dict() + + imgs = self._coco_api.imgs + gt_json = self._coco_api.img_ann_map + gt_json = [{ + 'image_id': k, + 'segments_info': v, + 'file_name': imgs[k]['segm_file'] + } for k, v in gt_json.items()] + pred_json = load(json_filename) + pred_json = dict( + (el['image_id'], el) for el in pred_json['annotations']) + + # match the gt_anns and pred_anns in the same image + matched_annotations_list = [] + for gt_ann in gt_json: + img_id = gt_ann['image_id'] + if img_id not in pred_json.keys(): + raise Exception('no prediction for the image' + ' with id: {}'.format(img_id)) + matched_annotations_list.append((gt_ann, pred_json[img_id])) + + pq_stat = pq_compute_multi_core( + matched_annotations_list, + gt_folder, + pred_folder, + self.categories, + backend_args=self.backend_args, + nproc=self.nproc) + + else: + # aggregate the results generated in process + if self._coco_api is None: + categories = dict() + for id, name in enumerate(self.dataset_meta['classes']): + isthing = 1 if name in self.dataset_meta[ + 'thing_classes'] else 0 + categories[id] = { + 'id': id, + 'name': name, + 'isthing': isthing + } + self.categories = categories + + pq_stat = PQStat() + for result in results: + pq_stat += result + + metrics = [('All', None), ('Things', True), ('Stuff', False)] + pq_results = {} + + for name, isthing in metrics: + pq_results[name], classwise_results = pq_stat.pq_average( + self.categories, isthing=isthing) + if name == 'All': + pq_results['classwise'] = classwise_results + + classwise_results = None + if self.classwise: + classwise_results = { + k: v + for k, v in zip(self.dataset_meta['classes'], + pq_results['classwise'].values()) + } + + print_panoptic_table(pq_results, classwise_results, logger=logger) + results = parse_pq_results(pq_results) + + return results + + +def parse_pq_results(pq_results: dict) -> dict: + """Parse the Panoptic Quality results. + + Args: + pq_results (dict): Panoptic Quality results. + + Returns: + dict: Panoptic Quality results parsed. + """ + result = dict() + result['PQ'] = 100 * pq_results['All']['pq'] + result['SQ'] = 100 * pq_results['All']['sq'] + result['RQ'] = 100 * pq_results['All']['rq'] + result['PQ_th'] = 100 * pq_results['Things']['pq'] + result['SQ_th'] = 100 * pq_results['Things']['sq'] + result['RQ_th'] = 100 * pq_results['Things']['rq'] + result['PQ_st'] = 100 * pq_results['Stuff']['pq'] + result['SQ_st'] = 100 * pq_results['Stuff']['sq'] + result['RQ_st'] = 100 * pq_results['Stuff']['rq'] + return result + + +def print_panoptic_table( + pq_results: dict, + classwise_results: Optional[dict] = None, + logger: Optional[Union['MMLogger', str]] = None) -> None: + """Print the panoptic evaluation results table. + + Args: + pq_results(dict): The Panoptic Quality results. + classwise_results(dict, optional): The classwise Panoptic Quality. + results. The keys are class names and the values are metrics. + Defaults to None. + logger (:obj:`MMLogger` | str, optional): Logger used for printing + related information during evaluation. Default: None. + """ + + headers = ['', 'PQ', 'SQ', 'RQ', 'categories'] + data = [headers] + for name in ['All', 'Things', 'Stuff']: + numbers = [ + f'{(pq_results[name][k] * 100):0.3f}' for k in ['pq', 'sq', 'rq'] + ] + row = [name] + numbers + [pq_results[name]['n']] + data.append(row) + table = AsciiTable(data) + print_log('Panoptic Evaluation Results:\n' + table.table, logger=logger) + + if classwise_results is not None: + class_metrics = [(name, ) + tuple(f'{(metrics[k] * 100):0.3f}' + for k in ['pq', 'sq', 'rq']) + for name, metrics in classwise_results.items()] + num_columns = min(8, len(class_metrics) * 4) + results_flatten = list(itertools.chain(*class_metrics)) + headers = ['category', 'PQ', 'SQ', 'RQ'] * (num_columns // 4) + results_2d = itertools.zip_longest( + *[results_flatten[i::num_columns] for i in range(num_columns)]) + data = [headers] + data += [result for result in results_2d] + table = AsciiTable(data) + print_log( + 'Classwise Panoptic Evaluation Results:\n' + table.table, + logger=logger) diff --git a/mmdetection/mmdet/evaluation/metrics/coco_video_metric.py b/mmdetection/mmdet/evaluation/metrics/coco_video_metric.py new file mode 100644 index 00000000..b5c75d02 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/coco_video_metric.py @@ -0,0 +1,80 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import Sequence + +from mmengine.dist import broadcast_object_list, is_main_process + +from mmdet.registry import METRICS +from .base_video_metric import collect_tracking_results +from .coco_metric import CocoMetric + + +@METRICS.register_module() +class CocoVideoMetric(CocoMetric): + """COCO evaluation metric. + + Evaluate AR, AP, and mAP for detection tasks including proposal/box + detection and instance segmentation. Please refer to + https://cocodataset.org/#detection-eval for more details. + """ + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. + + The processed results should be stored in ``self.results``, which will + be used to compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for track_data_sample in data_samples: + video_data_samples = track_data_sample['video_data_samples'] + ori_video_len = video_data_samples[0].ori_video_length + video_len = len(video_data_samples) + if ori_video_len == video_len: + # video process + for frame_id in range(video_len): + img_data_sample = video_data_samples[frame_id].to_dict() + super().process(None, [img_data_sample]) + else: + # image process + img_data_sample = video_data_samples[0].to_dict() + super().process(None, [img_data_sample]) + + def evaluate(self, size: int = 1) -> dict: + """Evaluate the model performance of the whole dataset after processing + all batches. + + Args: + size (int): Length of the entire validation dataset. + Returns: + dict: Evaluation metrics dict on the val dataset. The keys are the + names of the metrics, and the values are corresponding results. + """ + if len(self.results) == 0: + warnings.warn( + f'{self.__class__.__name__} got empty `self.results`. Please ' + 'ensure that the processed results are properly added into ' + '`self.results` in `process` method.') + + results = collect_tracking_results(self.results, self.collect_device) + + if is_main_process(): + _metrics = self.compute_metrics(results) # type: ignore + # Add prefix to metric names + if self.prefix: + _metrics = { + '/'.join((self.prefix, k)): v + for k, v in _metrics.items() + } + metrics = [_metrics] + else: + metrics = [None] # type: ignore + + broadcast_object_list(metrics) + + # reset the results list + self.results.clear() + return metrics[0] diff --git a/mmdetection/mmdet/evaluation/metrics/crowdhuman_metric.py b/mmdetection/mmdet/evaluation/metrics/crowdhuman_metric.py new file mode 100644 index 00000000..50ac210a --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/crowdhuman_metric.py @@ -0,0 +1,824 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import json +import os.path as osp +import tempfile +from collections import OrderedDict +from multiprocessing import Process, Queue +from typing import Dict, List, Optional, Sequence, Union + +import numpy as np +from mmengine.evaluator import BaseMetric +from mmengine.fileio import dump, get_text, load +from mmengine.logging import MMLogger +from scipy.sparse import csr_matrix +from scipy.sparse.csgraph import maximum_bipartite_matching + +from mmdet.evaluation.functional.bbox_overlaps import bbox_overlaps +from mmdet.registry import METRICS + +PERSON_CLASSES = ['background', 'person'] + + +@METRICS.register_module() +class CrowdHumanMetric(BaseMetric): + """CrowdHuman evaluation metric. + + Evaluate Average Precision (AP), Miss Rate (MR) and Jaccard Index (JI) + for detection tasks. + + Args: + ann_file (str): Path to the annotation file. + metric (str | List[str]): Metrics to be evaluated. Valid metrics + include 'AP', 'MR' and 'JI'. Defaults to 'AP'. + format_only (bool): Format the output results without perform + evaluation. It is useful when you want to format the result + to a specific format and submit it to the test server. + Defaults to False. + outfile_prefix (str, optional): The prefix of json files. It includes + the file path and the prefix of filename, e.g., "a/b/prefix". + If not specified, a temp file will be created. Defaults to None. + file_client_args (dict, optional): Arguments to instantiate the + corresponding backend in mmdet <= 3.0.0rc6. Defaults to None. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + eval_mode (int): Select the mode of evaluate. Valid mode include + 0(just body box), 1(just head box) and 2(both of them). + Defaults to 0. + iou_thres (float): IoU threshold. Defaults to 0.5. + compare_matching_method (str, optional): Matching method to compare + the detection results with the ground_truth when compute 'AP' + and 'MR'.Valid method include VOC and None(CALTECH). Default to + None. + mr_ref (str): Different parameter selection to calculate MR. Valid + ref include CALTECH_-2 and CALTECH_-4. Defaults to CALTECH_-2. + num_ji_process (int): The number of processes to evaluation JI. + Defaults to 10. + """ + default_prefix: Optional[str] = 'crowd_human' + + def __init__(self, + ann_file: str, + metric: Union[str, List[str]] = ['AP', 'MR', 'JI'], + format_only: bool = False, + outfile_prefix: Optional[str] = None, + file_client_args: dict = None, + backend_args: dict = None, + collect_device: str = 'cpu', + prefix: Optional[str] = None, + eval_mode: int = 0, + iou_thres: float = 0.5, + compare_matching_method: Optional[str] = None, + mr_ref: str = 'CALTECH_-2', + num_ji_process: int = 10) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + + self.ann_file = ann_file + # crowdhuman evaluation metrics + self.metrics = metric if isinstance(metric, list) else [metric] + allowed_metrics = ['MR', 'AP', 'JI'] + for metric in self.metrics: + if metric not in allowed_metrics: + raise KeyError(f"metric should be one of 'MR', 'AP', 'JI'," + f'but got {metric}.') + + self.format_only = format_only + if self.format_only: + assert outfile_prefix is not None, 'outfile_prefix must be not' + 'None when format_only is True, otherwise the result files will' + 'be saved to a temp directory which will be cleaned up at the end.' + self.outfile_prefix = outfile_prefix + self.backend_args = backend_args + if file_client_args is not None: + raise RuntimeError( + 'The `file_client_args` is deprecated, ' + 'please use `backend_args` instead, please refer to' + 'https://github.com/open-mmlab/mmdetection/blob/main/configs/_base_/datasets/coco_detection.py' # noqa: E501 + ) + + assert eval_mode in [0, 1, 2], \ + "Unknown eval mode. mr_ref should be one of '0', '1', '2'." + assert compare_matching_method is None or \ + compare_matching_method == 'VOC', \ + 'The alternative compare_matching_method is VOC.' \ + 'This parameter defaults to CALTECH(None)' + assert mr_ref == 'CALTECH_-2' or mr_ref == 'CALTECH_-4', \ + "mr_ref should be one of 'CALTECH_-2', 'CALTECH_-4'." + self.eval_mode = eval_mode + self.iou_thres = iou_thres + self.compare_matching_method = compare_matching_method + self.mr_ref = mr_ref + self.num_ji_process = num_ji_process + + @staticmethod + def results2json(results: Sequence[dict], outfile_prefix: str) -> str: + """Dump the detection results to a json file.""" + result_file_path = f'{outfile_prefix}.json' + bbox_json_results = [] + for i, result in enumerate(results): + ann, pred = result + dump_dict = dict() + dump_dict['ID'] = ann['ID'] + dump_dict['width'] = ann['width'] + dump_dict['height'] = ann['height'] + dtboxes = [] + bboxes = pred.tolist() + for _, single_bbox in enumerate(bboxes): + temp_dict = dict() + x1, y1, x2, y2, score = single_bbox + temp_dict['box'] = [x1, y1, x2 - x1, y2 - y1] + temp_dict['score'] = score + temp_dict['tag'] = 1 + dtboxes.append(temp_dict) + dump_dict['dtboxes'] = dtboxes + bbox_json_results.append(dump_dict) + dump(bbox_json_results, result_file_path) + return result_file_path + + def process(self, data_batch: Sequence[dict], + data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + ann = dict() + ann['ID'] = data_sample['img_id'] + ann['width'] = data_sample['ori_shape'][1] + ann['height'] = data_sample['ori_shape'][0] + pred_bboxes = data_sample['pred_instances']['bboxes'].cpu().numpy() + pred_scores = data_sample['pred_instances']['scores'].cpu().numpy() + + pred_bbox_scores = np.hstack( + [pred_bboxes, pred_scores.reshape((-1, 1))]) + + self.results.append((ann, pred_bbox_scores)) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + eval_results(Dict[str, float]): The computed metrics. + The keys are the names of the metrics, and the values + are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + + tmp_dir = None + if self.outfile_prefix is None: + tmp_dir = tempfile.TemporaryDirectory() + outfile_prefix = osp.join(tmp_dir.name, 'result') + else: + outfile_prefix = self.outfile_prefix + + # convert predictions to coco format and dump to json file + result_file = self.results2json(results, outfile_prefix) + eval_results = OrderedDict() + if self.format_only: + logger.info(f'results are saved in {osp.dirname(outfile_prefix)}') + return eval_results + + # load evaluation samples + eval_samples = self.load_eval_samples(result_file) + + if 'AP' in self.metrics or 'MR' in self.metrics: + score_list = self.compare(eval_samples) + gt_num = sum([eval_samples[i].gt_num for i in eval_samples]) + ign_num = sum([eval_samples[i].ign_num for i in eval_samples]) + gt_num = gt_num - ign_num + img_num = len(eval_samples) + + for metric in self.metrics: + logger.info(f'Evaluating {metric}...') + if metric == 'AP': + AP = self.eval_ap(score_list, gt_num, img_num) + eval_results['mAP'] = float(f'{round(AP, 4)}') + if metric == 'MR': + MR = self.eval_mr(score_list, gt_num, img_num) + eval_results['mMR'] = float(f'{round(MR, 4)}') + if metric == 'JI': + JI = self.eval_ji(eval_samples) + eval_results['JI'] = float(f'{round(JI, 4)}') + if tmp_dir is not None: + tmp_dir.cleanup() + + return eval_results + + def load_eval_samples(self, result_file): + """Load data from annotations file and detection results. + + Args: + result_file (str): The file path of the saved detection results. + + Returns: + Dict[Image]: The detection result packaged by Image + """ + gt_str = get_text( + self.ann_file, backend_args=self.backend_args).strip().split('\n') + gt_records = [json.loads(line) for line in gt_str] + + pred_records = load(result_file, backend_args=self.backend_args) + eval_samples = dict() + for gt_record, pred_record in zip(gt_records, pred_records): + assert gt_record['ID'] == pred_record['ID'], \ + 'please set val_dataloader.sampler.shuffle=False and try again' + eval_samples[pred_record['ID']] = Image(self.eval_mode) + eval_samples[pred_record['ID']].load(gt_record, 'box', None, + PERSON_CLASSES, True) + eval_samples[pred_record['ID']].load(pred_record, 'box', None, + PERSON_CLASSES, False) + eval_samples[pred_record['ID']].clip_all_boader() + return eval_samples + + def compare(self, samples): + """Match the detection results with the ground_truth. + + Args: + samples (dict[Image]): The detection result packaged by Image. + + Returns: + score_list(list[tuple[ndarray, int, str]]): Matching result. + a list of tuples (dtbox, label, imgID) in the descending + sort of dtbox.score. + """ + score_list = list() + for id in samples: + if self.compare_matching_method == 'VOC': + result = samples[id].compare_voc(self.iou_thres) + else: + result = samples[id].compare_caltech(self.iou_thres) + score_list.extend(result) + # In the descending sort of dtbox score. + score_list.sort(key=lambda x: x[0][-1], reverse=True) + return score_list + + @staticmethod + def eval_ap(score_list, gt_num, img_num): + """Evaluate by average precision. + + Args: + score_list(list[tuple[ndarray, int, str]]): Matching result. + a list of tuples (dtbox, label, imgID) in the descending + sort of dtbox.score. + gt_num(int): The number of gt boxes in the entire dataset. + img_num(int): The number of images in the entire dataset. + + Returns: + ap(float): result of average precision. + """ + + # calculate general ap score + def _calculate_map(_recall, _precision): + assert len(_recall) == len(_precision) + area = 0 + for k in range(1, len(_recall)): + delta_h = (_precision[k - 1] + _precision[k]) / 2 + delta_w = _recall[k] - _recall[k - 1] + area += delta_w * delta_h + return area + + tp, fp = 0.0, 0.0 + rpX, rpY = list(), list() + + fpn = [] + recalln = [] + thr = [] + fppi = [] + for i, item in enumerate(score_list): + if item[1] == 1: + tp += 1.0 + elif item[1] == 0: + fp += 1.0 + fn = gt_num - tp + recall = tp / (tp + fn) + precision = tp / (tp + fp) + rpX.append(recall) + rpY.append(precision) + fpn.append(fp) + recalln.append(tp) + thr.append(item[0][-1]) + fppi.append(fp / img_num) + + ap = _calculate_map(rpX, rpY) + return ap + + def eval_mr(self, score_list, gt_num, img_num): + """Evaluate by Caltech-style log-average miss rate. + + Args: + score_list(list[tuple[ndarray, int, str]]): Matching result. + a list of tuples (dtbox, label, imgID) in the descending + sort of dtbox.score. + gt_num(int): The number of gt boxes in the entire dataset. + img_num(int): The number of image in the entire dataset. + + Returns: + mr(float): result of miss rate. + """ + + # find greater_than + def _find_gt(lst, target): + for idx, _item in enumerate(lst): + if _item >= target: + return idx + return len(lst) - 1 + + if self.mr_ref == 'CALTECH_-2': + # CALTECH_MRREF_2: anchor points (from 10^-2 to 1) as in + # P.Dollar's paper + ref = [ + 0.0100, 0.0178, 0.03160, 0.0562, 0.1000, 0.1778, 0.3162, + 0.5623, 1.000 + ] + else: + # CALTECH_MRREF_4: anchor points (from 10^-4 to 1) as in + # S.Zhang's paper + ref = [ + 0.0001, 0.0003, 0.00100, 0.0032, 0.0100, 0.0316, 0.1000, + 0.3162, 1.000 + ] + + tp, fp = 0.0, 0.0 + fppiX, fppiY = list(), list() + for i, item in enumerate(score_list): + if item[1] == 1: + tp += 1.0 + elif item[1] == 0: + fp += 1.0 + + fn = gt_num - tp + recall = tp / (tp + fn) + missrate = 1.0 - recall + fppi = fp / img_num + fppiX.append(fppi) + fppiY.append(missrate) + + score = list() + for pos in ref: + argmin = _find_gt(fppiX, pos) + if argmin >= 0: + score.append(fppiY[argmin]) + score = np.array(score) + mr = np.exp(np.log(score).mean()) + return mr + + def eval_ji(self, samples): + """Evaluate by JI using multi_process. + + Args: + samples(Dict[str, Image]): The detection result packaged by Image. + + Returns: + ji(float): result of jaccard index. + """ + import math + res_line = [] + res_ji = [] + for i in range(10): + score_thr = 1e-1 * i + total = len(samples) + stride = math.ceil(total / self.num_ji_process) + result_queue = Queue(10000) + results, procs = [], [] + records = list(samples.items()) + for i in range(self.num_ji_process): + start = i * stride + end = np.min([start + stride, total]) + sample_data = dict(records[start:end]) + p = Process( + target=self.compute_ji_with_ignore, + args=(result_queue, sample_data, score_thr)) + p.start() + procs.append(p) + for i in range(total): + t = result_queue.get() + results.append(t) + for p in procs: + p.join() + line, mean_ratio = self.gather(results) + line = 'score_thr:{:.1f}, {}'.format(score_thr, line) + res_line.append(line) + res_ji.append(mean_ratio) + return max(res_ji) + + def compute_ji_with_ignore(self, result_queue, dt_result, score_thr): + """Compute JI with ignore. + + Args: + result_queue(Queue): The Queue for save compute result when + multi_process. + dt_result(dict[Image]): Detection result packaged by Image. + score_thr(float): The threshold of detection score. + Returns: + dict: compute result. + """ + for ID, record in dt_result.items(): + gt_boxes = record.gt_boxes + dt_boxes = record.dt_boxes + keep = dt_boxes[:, -1] > score_thr + dt_boxes = dt_boxes[keep][:, :-1] + + gt_tag = np.array(gt_boxes[:, -1] != -1) + matches = self.compute_ji_matching(dt_boxes, gt_boxes[gt_tag, :4]) + # get the unmatched_indices + matched_indices = np.array([j for (j, _) in matches]) + unmatched_indices = list( + set(np.arange(dt_boxes.shape[0])) - set(matched_indices)) + num_ignore_dt = self.get_ignores(dt_boxes[unmatched_indices], + gt_boxes[~gt_tag, :4]) + matched_indices = np.array([j for (_, j) in matches]) + unmatched_indices = list( + set(np.arange(gt_boxes[gt_tag].shape[0])) - + set(matched_indices)) + num_ignore_gt = self.get_ignores( + gt_boxes[gt_tag][unmatched_indices], gt_boxes[~gt_tag, :4]) + # compute results + eps = 1e-6 + k = len(matches) + m = gt_tag.sum() - num_ignore_gt + n = dt_boxes.shape[0] - num_ignore_dt + ratio = k / (m + n - k + eps) + recall = k / (m + eps) + cover = k / (n + eps) + noise = 1 - cover + result_dict = dict( + ratio=ratio, + recall=recall, + cover=cover, + noise=noise, + k=k, + m=m, + n=n) + result_queue.put_nowait(result_dict) + + @staticmethod + def gather(results): + """Integrate test results.""" + assert len(results) + img_num = 0 + for result in results: + if result['n'] != 0 or result['m'] != 0: + img_num += 1 + mean_ratio = np.sum([rb['ratio'] for rb in results]) / img_num + valids = np.sum([rb['k'] for rb in results]) + total = np.sum([rb['n'] for rb in results]) + gtn = np.sum([rb['m'] for rb in results]) + line = 'mean_ratio:{:.4f}, valids:{}, total:{}, gtn:{}'\ + .format(mean_ratio, valids, total, gtn) + return line, mean_ratio + + def compute_ji_matching(self, dt_boxes, gt_boxes): + """Match the annotation box for each detection box. + + Args: + dt_boxes(ndarray): Detection boxes. + gt_boxes(ndarray): Ground_truth boxes. + + Returns: + matches_(list[tuple[int, int]]): Match result. + """ + assert dt_boxes.shape[-1] > 3 and gt_boxes.shape[-1] > 3 + if dt_boxes.shape[0] < 1 or gt_boxes.shape[0] < 1: + return list() + + ious = bbox_overlaps(dt_boxes, gt_boxes, mode='iou') + input_ = copy.deepcopy(ious) + input_[input_ < self.iou_thres] = 0 + match_scipy = maximum_bipartite_matching( + csr_matrix(input_), perm_type='column') + matches_ = [] + for i in range(len(match_scipy)): + if match_scipy[i] != -1: + matches_.append((i, int(match_scipy[i]))) + return matches_ + + def get_ignores(self, dt_boxes, gt_boxes): + """Get the number of ignore bboxes.""" + if gt_boxes.size: + ioas = bbox_overlaps(dt_boxes, gt_boxes, mode='iof') + ioas = np.max(ioas, axis=1) + rows = np.where(ioas > self.iou_thres)[0] + return len(rows) + else: + return 0 + + +class Image(object): + """Data structure for evaluation of CrowdHuman. + + Note: + This implementation is modified from https://github.com/Purkialo/ + CrowdDet/blob/master/lib/evaluate/APMRToolkits/image.py + + Args: + mode (int): Select the mode of evaluate. Valid mode include + 0(just body box), 1(just head box) and 2(both of them). + Defaults to 0. + """ + + def __init__(self, mode): + self.ID = None + self.width = None + self.height = None + self.dt_boxes = None + self.gt_boxes = None + self.eval_mode = mode + + self.ign_num = None + self.gt_num = None + self.dt_num = None + + def load(self, record, body_key, head_key, class_names, gt_flag): + """Loading information for evaluation. + + Args: + record (dict): Label information or test results. + The format might look something like this: + { + 'ID': '273271,c9db000d5146c15', + 'gtboxes': [ + {'fbox': [72, 202, 163, 503], 'tag': 'person', ...}, + {'fbox': [199, 180, 144, 499], 'tag': 'person', ...}, + ... + ] + } + or: + { + 'ID': '273271,c9db000d5146c15', + 'width': 800, + 'height': 1067, + 'dtboxes': [ + { + 'box': [306.22, 205.95, 164.05, 394.04], + 'score': 0.99, + 'tag': 1 + }, + { + 'box': [403.60, 178.66, 157.15, 421.33], + 'score': 0.99, + 'tag': 1 + }, + ... + ] + } + body_key (str, None): key of detection body box. + Valid when loading detection results and self.eval_mode!=1. + head_key (str, None): key of detection head box. + Valid when loading detection results and self.eval_mode!=0. + class_names (list[str]):class names of data set. + Defaults to ['background', 'person']. + gt_flag (bool): Indicate whether record is ground truth + or predicting the outcome. + """ + if 'ID' in record and self.ID is None: + self.ID = record['ID'] + if 'width' in record and self.width is None: + self.width = record['width'] + if 'height' in record and self.height is None: + self.height = record['height'] + if gt_flag: + self.gt_num = len(record['gtboxes']) + body_bbox, head_bbox = self.load_gt_boxes(record, 'gtboxes', + class_names) + if self.eval_mode == 0: + self.gt_boxes = body_bbox + self.ign_num = (body_bbox[:, -1] == -1).sum() + elif self.eval_mode == 1: + self.gt_boxes = head_bbox + self.ign_num = (head_bbox[:, -1] == -1).sum() + else: + gt_tag = np.array([ + body_bbox[i, -1] != -1 and head_bbox[i, -1] != -1 + for i in range(len(body_bbox)) + ]) + self.ign_num = (gt_tag == 0).sum() + self.gt_boxes = np.hstack( + (body_bbox[:, :-1], head_bbox[:, :-1], + gt_tag.reshape(-1, 1))) + + if not gt_flag: + self.dt_num = len(record['dtboxes']) + if self.eval_mode == 0: + self.dt_boxes = self.load_det_boxes(record, 'dtboxes', + body_key, 'score') + elif self.eval_mode == 1: + self.dt_boxes = self.load_det_boxes(record, 'dtboxes', + head_key, 'score') + else: + body_dtboxes = self.load_det_boxes(record, 'dtboxes', body_key, + 'score') + head_dtboxes = self.load_det_boxes(record, 'dtboxes', head_key, + 'score') + self.dt_boxes = np.hstack((body_dtboxes, head_dtboxes)) + + @staticmethod + def load_gt_boxes(dict_input, key_name, class_names): + """load ground_truth and transform [x, y, w, h] to [x1, y1, x2, y2]""" + assert key_name in dict_input + if len(dict_input[key_name]) < 1: + return np.empty([0, 5]) + head_bbox = [] + body_bbox = [] + for rb in dict_input[key_name]: + if rb['tag'] in class_names: + body_tag = class_names.index(rb['tag']) + head_tag = copy.deepcopy(body_tag) + else: + body_tag = -1 + head_tag = -1 + if 'extra' in rb: + if 'ignore' in rb['extra']: + if rb['extra']['ignore'] != 0: + body_tag = -1 + head_tag = -1 + if 'head_attr' in rb: + if 'ignore' in rb['head_attr']: + if rb['head_attr']['ignore'] != 0: + head_tag = -1 + head_bbox.append(np.hstack((rb['hbox'], head_tag))) + body_bbox.append(np.hstack((rb['fbox'], body_tag))) + head_bbox = np.array(head_bbox) + head_bbox[:, 2:4] += head_bbox[:, :2] + body_bbox = np.array(body_bbox) + body_bbox[:, 2:4] += body_bbox[:, :2] + return body_bbox, head_bbox + + @staticmethod + def load_det_boxes(dict_input, key_name, key_box, key_score, key_tag=None): + """load detection boxes.""" + assert key_name in dict_input + if len(dict_input[key_name]) < 1: + return np.empty([0, 5]) + else: + assert key_box in dict_input[key_name][0] + if key_score: + assert key_score in dict_input[key_name][0] + if key_tag: + assert key_tag in dict_input[key_name][0] + if key_score: + if key_tag: + bboxes = np.vstack([ + np.hstack((rb[key_box], rb[key_score], rb[key_tag])) + for rb in dict_input[key_name] + ]) + else: + bboxes = np.vstack([ + np.hstack((rb[key_box], rb[key_score])) + for rb in dict_input[key_name] + ]) + else: + if key_tag: + bboxes = np.vstack([ + np.hstack((rb[key_box], rb[key_tag])) + for rb in dict_input[key_name] + ]) + else: + bboxes = np.vstack( + [rb[key_box] for rb in dict_input[key_name]]) + bboxes[:, 2:4] += bboxes[:, :2] + return bboxes + + def clip_all_boader(self): + """Make sure boxes are within the image range.""" + + def _clip_boundary(boxes, height, width): + assert boxes.shape[-1] >= 4 + boxes[:, 0] = np.minimum(np.maximum(boxes[:, 0], 0), width - 1) + boxes[:, 1] = np.minimum(np.maximum(boxes[:, 1], 0), height - 1) + boxes[:, 2] = np.maximum(np.minimum(boxes[:, 2], width), 0) + boxes[:, 3] = np.maximum(np.minimum(boxes[:, 3], height), 0) + return boxes + + assert self.dt_boxes.shape[-1] >= 4 + assert self.gt_boxes.shape[-1] >= 4 + assert self.width is not None and self.height is not None + if self.eval_mode == 2: + self.dt_boxes[:, :4] = _clip_boundary(self.dt_boxes[:, :4], + self.height, self.width) + self.gt_boxes[:, :4] = _clip_boundary(self.gt_boxes[:, :4], + self.height, self.width) + self.dt_boxes[:, 4:8] = _clip_boundary(self.dt_boxes[:, 4:8], + self.height, self.width) + self.gt_boxes[:, 4:8] = _clip_boundary(self.gt_boxes[:, 4:8], + self.height, self.width) + else: + self.dt_boxes = _clip_boundary(self.dt_boxes, self.height, + self.width) + self.gt_boxes = _clip_boundary(self.gt_boxes, self.height, + self.width) + + def compare_voc(self, thres): + """Match the detection results with the ground_truth by VOC. + + Args: + thres (float): IOU threshold. + + Returns: + score_list(list[tuple[ndarray, int, str]]): Matching result. + a list of tuples (dtbox, label, imgID) in the descending + sort of dtbox.score. + """ + if self.dt_boxes is None: + return list() + dtboxes = self.dt_boxes + gtboxes = self.gt_boxes if self.gt_boxes is not None else list() + dtboxes.sort(key=lambda x: x.score, reverse=True) + gtboxes.sort(key=lambda x: x.ign) + + score_list = list() + for i, dt in enumerate(dtboxes): + maxpos = -1 + maxiou = thres + + for j, gt in enumerate(gtboxes): + overlap = dt.iou(gt) + if overlap > maxiou: + maxiou = overlap + maxpos = j + + if maxpos >= 0: + if gtboxes[maxpos].ign == 0: + gtboxes[maxpos].matched = 1 + dtboxes[i].matched = 1 + score_list.append((dt, self.ID)) + else: + dtboxes[i].matched = -1 + else: + dtboxes[i].matched = 0 + score_list.append((dt, self.ID)) + return score_list + + def compare_caltech(self, thres): + """Match the detection results with the ground_truth by Caltech + matching strategy. + + Args: + thres (float): IOU threshold. + + Returns: + score_list(list[tuple[ndarray, int, str]]): Matching result. + a list of tuples (dtbox, label, imgID) in the descending + sort of dtbox.score. + """ + if self.dt_boxes is None or self.gt_boxes is None: + return list() + + dtboxes = self.dt_boxes if self.dt_boxes is not None else list() + gtboxes = self.gt_boxes if self.gt_boxes is not None else list() + dt_matched = np.zeros(dtboxes.shape[0]) + gt_matched = np.zeros(gtboxes.shape[0]) + + dtboxes = np.array(sorted(dtboxes, key=lambda x: x[-1], reverse=True)) + gtboxes = np.array(sorted(gtboxes, key=lambda x: x[-1], reverse=True)) + if len(dtboxes): + overlap_iou = bbox_overlaps(dtboxes, gtboxes, mode='iou') + overlap_ioa = bbox_overlaps(dtboxes, gtboxes, mode='iof') + else: + return list() + + score_list = list() + for i, dt in enumerate(dtboxes): + maxpos = -1 + maxiou = thres + for j, gt in enumerate(gtboxes): + if gt_matched[j] == 1: + continue + if gt[-1] > 0: + overlap = overlap_iou[i][j] + if overlap > maxiou: + maxiou = overlap + maxpos = j + else: + if maxpos >= 0: + break + else: + overlap = overlap_ioa[i][j] + if overlap > thres: + maxiou = overlap + maxpos = j + if maxpos >= 0: + if gtboxes[maxpos, -1] > 0: + gt_matched[maxpos] = 1 + dt_matched[i] = 1 + score_list.append((dt, 1, self.ID)) + else: + dt_matched[i] = -1 + else: + dt_matched[i] = 0 + score_list.append((dt, 0, self.ID)) + return score_list diff --git a/mmdetection/mmdet/evaluation/metrics/custom_metric.py b/mmdetection/mmdet/evaluation/metrics/custom_metric.py new file mode 100644 index 00000000..ad13bbce --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/custom_metric.py @@ -0,0 +1,110 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Any, Optional, Sequence + +from mmcv.ops import batched_nms +from mmengine.evaluator import BaseMetric +from mmengine.logging import print_log + +from mmdet.registry import METRICS + +import os +import pandas as pd +from tqdm.auto import tqdm as tqdm_auto + +try: + import jsonlines +except ImportError: + jsonlines = None + + +@METRICS.register_module() +class CustomResultDumping(BaseMetric): + default_prefix: Optional[str] = 'pl_odvg' + + def __init__(self, + outfile_path, + img_prefix: str, + classes: list, + collect_device: str = 'cpu', + prefix: Optional[str] = None) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + self.outfile_path = outfile_path + self.img_prefix = img_prefix + self.classes=classes + + if not os.path.exists(outfile_path): + os.makedirs(outfile_path) + print(f"Created directories for the path: {outfile_path}") + else: + print(f"Path already exists: {outfile_path}") + + if jsonlines is None: + raise ImportError('Please run "pip install jsonlines" to install ' + 'this package.') + + def process(self, data_batch: Any, data_samples: Sequence[dict]) -> None: + for data_sample in data_samples: + result = {} + + filename = data_sample['img_path'] + filename = filename.replace(self.img_prefix, '') + if filename.startswith('/'): + filename = filename[1:] + result['filename'] = filename + + height = data_sample['ori_shape'][0] + width = data_sample['ori_shape'][1] + result['height'] = height + result['width'] = width + + pred_instances = data_sample['pred_instances'] + + pred_instances['bboxes'] = pred_instances['bboxes'] + pred_instances['scores'] = pred_instances['scores'] + pred_instances['labels'] = pred_instances['labels'] + + + result['pred_instances'] = pred_instances + self.results.append(result) + + + def compute_metrics(self, results: list) -> dict: + prediction_strings = [] + file_names = [] + + for result in tqdm_auto(results, desc="making csv file"): + prediction_string = '' + file_name = result['filename'] + if 'pred_instances' in result: + pred_instances = result["pred_instances"] + if pred_instances['bboxes'].numel() > 0: + bboxes = pred_instances['bboxes'].cpu().numpy() + scores = pred_instances['scores'].cpu().numpy() + labels = pred_instances['labels'].cpu().numpy() + + # 클래스별로 결과 정리 + class_results = [[] for _ in range(len(self.classes))] + for label, score, bbox in zip(labels, scores, bboxes): + class_results[label].append((score, bbox)) + + # Pascal VOC 형식으로 문자열 생성 + for class_id, class_result in enumerate(class_results): + if class_result: # 해당 클래스의 결과가 있는 경우만 + for score, bbox in class_result: + prediction_string += f"{class_id} {score:.4f} {bbox[0]:.2f} {bbox[1]:.2f} {bbox[2]:.2f} {bbox[3]:.2f} " + + prediction_strings.append(prediction_string) + file_names.append(file_name) + + # submission 파일 생성 + submission = pd.DataFrame() + submission['PredictionString'] = prediction_strings + submission['image_id'] = file_names + submission.to_csv(os.path.join(self.outfile_path, 'submission.csv'), index=None) + + + print(submission.head()) + print_log( + f'Results has been saved to {self.outfile_path}.', + logger='current') + return {} diff --git a/mmdetection/mmdet/evaluation/metrics/dod_metric.py b/mmdetection/mmdet/evaluation/metrics/dod_metric.py new file mode 100644 index 00000000..b47d0721 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/dod_metric.py @@ -0,0 +1,169 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections import defaultdict +from typing import List, Optional, Sequence + +import numpy as np +from mmengine.evaluator import BaseMetric +from mmengine.fileio import get_local_path +from mmengine.logging import MMLogger + +from mmdet.datasets.api_wrappers import COCO, COCOeval +from mmdet.registry import METRICS + + +@METRICS.register_module() +class DODCocoMetric(BaseMetric): + + default_prefix: Optional[str] = 'dod' + + def __init__(self, + ann_file: Optional[str] = None, + collect_device: str = 'cpu', + outfile_prefix: Optional[str] = None, + backend_args: dict = None, + prefix: Optional[str] = None) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + self.outfile_prefix = outfile_prefix + with get_local_path(ann_file, backend_args=backend_args) as local_path: + self._coco_api = COCO(local_path) + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + for data_sample in data_samples: + result = dict() + pred = data_sample['pred_instances'] + result['img_id'] = data_sample['img_id'] + result['bboxes'] = pred['bboxes'].cpu().numpy() + result['scores'] = pred['scores'].cpu().numpy() + + result['labels'] = pred['labels'].cpu().numpy() + result['labels'] = data_sample['sent_ids'][result['labels']] + self.results.append(result) + + def xyxy2xywh(self, bbox: np.ndarray) -> list: + """Convert ``xyxy`` style bounding boxes to ``xywh`` style for COCO + evaluation. + + Args: + bbox (numpy.ndarray): The bounding boxes, shape (4, ), in + ``xyxy`` order. + + Returns: + list[float]: The converted bounding boxes, in ``xywh`` order. + """ + + _bbox: List = bbox.tolist() + return [ + _bbox[0], + _bbox[1], + _bbox[2] - _bbox[0], + _bbox[3] - _bbox[1], + ] + + def results2json(self, results: Sequence[dict]) -> list: + """Dump the detection results to a COCO style json file. + + There are 3 types of results: proposals, bbox predictions, mask + predictions, and they have different data types. This method will + automatically recognize the type, and dump them to json files. + + Args: + results (Sequence[dict]): Testing results of the + dataset. + + Returns: + dict: Possible keys are "bbox", "segm", "proposal", and + values are corresponding filenames. + """ + bbox_json_results = [] + for idx, result in enumerate(results): + image_id = result.get('img_id', idx) + labels = result['labels'] + bboxes = result['bboxes'] + scores = result['scores'] + for i, label in enumerate(labels): + data = dict() + data['image_id'] = image_id + data['bbox'] = self.xyxy2xywh(bboxes[i]) + data['score'] = float(scores[i]) + data['category_id'] = label + bbox_json_results.append(data) + return bbox_json_results + + def compute_metrics(self, results: list) -> dict: + logger: MMLogger = MMLogger.get_current_instance() + result_files = self.results2json(results) + d3_res = self._coco_api.loadRes(result_files) + cocoEval = COCOeval(self._coco_api, d3_res, 'bbox') + cocoEval.evaluate() + cocoEval.accumulate() + cocoEval.summarize() + + aps = cocoEval.eval['precision'][:, :, :, 0, -1] + category_ids = self._coco_api.getCatIds() + category_names = [ + cat['name'] for cat in self._coco_api.loadCats(category_ids) + ] + + aps_lens = defaultdict(list) + counter_lens = defaultdict(int) + for i in range(len(category_names)): + ap = aps[:, :, i] + ap_value = ap[ap > -1].mean() + if not np.isnan(ap_value): + len_ref = len(category_names[i].split(' ')) + aps_lens[len_ref].append(ap_value) + counter_lens[len_ref] += 1 + + ap_sum_short = sum([sum(aps_lens[i]) for i in range(0, 4)]) + ap_sum_mid = sum([sum(aps_lens[i]) for i in range(4, 7)]) + ap_sum_long = sum([sum(aps_lens[i]) for i in range(7, 10)]) + ap_sum_very_long = sum([ + sum(aps_lens[i]) for i in range(10, + max(counter_lens.keys()) + 1) + ]) + c_sum_short = sum([counter_lens[i] for i in range(1, 4)]) + c_sum_mid = sum([counter_lens[i] for i in range(4, 7)]) + c_sum_long = sum([counter_lens[i] for i in range(7, 10)]) + c_sum_very_long = sum( + [counter_lens[i] for i in range(10, + max(counter_lens.keys()) + 1)]) + map_short = ap_sum_short / c_sum_short + map_mid = ap_sum_mid / c_sum_mid + map_long = ap_sum_long / c_sum_long + map_very_long = ap_sum_very_long / c_sum_very_long + + coco_metric_names = { + 'mAP': 0, + 'mAP_50': 1, + 'mAP_75': 2, + 'mAP_s': 3, + 'mAP_m': 4, + 'mAP_l': 5, + 'AR@100': 6, + 'AR@300': 7, + 'AR@1000': 8, + 'AR_s@1000': 9, + 'AR_m@1000': 10, + 'AR_l@1000': 11 + } + metric_items = ['mAP', 'mAP_50', 'mAP_75', 'mAP_s', 'mAP_m', 'mAP_l'] + + eval_results = {} + for metric_item in metric_items: + key = f'{metric_item}' + val = cocoEval.stats[coco_metric_names[metric_item]] + eval_results[key] = float(f'{round(val, 3)}') + + ap = cocoEval.stats[:6] + logger.info(f'mAP_copypaste: {ap[0]:.3f} ' + f'{ap[1]:.3f} {ap[2]:.3f} {ap[3]:.3f} ' + f'{ap[4]:.3f} {ap[5]:.3f}') + + logger.info(f'mAP over reference length: short - {map_short:.4f}, ' + f'mid - {map_mid:.4f}, long - {map_long:.4f}, ' + f'very long - {map_very_long:.4f}') + eval_results['mAP_short'] = float(f'{round(map_short, 3)}') + eval_results['mAP_mid'] = float(f'{round(map_mid, 3)}') + eval_results['mAP_long'] = float(f'{round(map_long, 3)}') + eval_results['mAP_very_long'] = float(f'{round(map_very_long, 3)}') + return eval_results diff --git a/mmdetection/mmdet/evaluation/metrics/dump_det_results.py b/mmdetection/mmdet/evaluation/metrics/dump_det_results.py new file mode 100644 index 00000000..f3071d19 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/dump_det_results.py @@ -0,0 +1,47 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import Sequence + +from mmengine.evaluator import DumpResults +from mmengine.evaluator.metric import _to_cpu + +from mmdet.registry import METRICS +from mmdet.structures.mask import encode_mask_results + + +@METRICS.register_module() +class DumpDetResults(DumpResults): + """Dump model predictions to a pickle file for offline evaluation. + + Different from `DumpResults` in MMEngine, it compresses instance + segmentation masks into RLE format. + + Args: + out_file_path (str): Path of the dumped file. Must end with '.pkl' + or '.pickle'. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + """ + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """transfer tensors in predictions to CPU.""" + data_samples = _to_cpu(data_samples) + for data_sample in data_samples: + # remove gt + data_sample.pop('gt_instances', None) + data_sample.pop('ignored_instances', None) + data_sample.pop('gt_panoptic_seg', None) + + if 'pred_instances' in data_sample: + pred = data_sample['pred_instances'] + # encode mask to RLE + if 'masks' in pred: + pred['masks'] = encode_mask_results(pred['masks'].numpy()) + if 'pred_panoptic_seg' in data_sample: + warnings.warn( + 'Panoptic segmentation map will not be compressed. ' + 'The dumped file will be extremely large! ' + 'Suggest using `CocoPanopticMetric` to save the coco ' + 'format json and segmentation png files directly.') + self.results.extend(data_samples) diff --git a/mmdetection/mmdet/evaluation/metrics/dump_odvg_results.py b/mmdetection/mmdet/evaluation/metrics/dump_odvg_results.py new file mode 100644 index 00000000..a1446b05 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/dump_odvg_results.py @@ -0,0 +1,138 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Any, Optional, Sequence + +from mmcv.ops import batched_nms +from mmengine.evaluator import BaseMetric +from mmengine.logging import print_log + +from mmdet.registry import METRICS + +try: + import jsonlines +except ImportError: + jsonlines = None + + +@METRICS.register_module() +class DumpODVGResults(BaseMetric): + default_prefix: Optional[str] = 'pl_odvg' + + def __init__(self, + outfile_path, + img_prefix: str, + score_thr: float = 0.1, + collect_device: str = 'cpu', + nms_thr: float = 0.5, + prefix: Optional[str] = None) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + self.outfile_path = outfile_path + self.score_thr = score_thr + self.img_prefix = img_prefix + self.nms_thr = nms_thr + + if jsonlines is None: + raise ImportError('Please run "pip install jsonlines" to install ' + 'this package.') + + def process(self, data_batch: Any, data_samples: Sequence[dict]) -> None: + for data_sample in data_samples: + result = {} + + filename = data_sample['img_path'] + filename = filename.replace(self.img_prefix, '') + if filename.startswith('/'): + filename = filename[1:] + result['filename'] = filename + + height = data_sample['ori_shape'][0] + width = data_sample['ori_shape'][1] + result['height'] = height + result['width'] = width + + pred_instances = data_sample['pred_instances'] + + bboxes = pred_instances['bboxes'].cpu() + scores = pred_instances['scores'].cpu() + labels = pred_instances['labels'].cpu() + + bboxes = bboxes[scores > self.score_thr] + labels = labels[scores > self.score_thr] + scores = scores[scores > self.score_thr] + + if 'tokens_positive' in data_sample: + task = 'vg' + else: + task = 'od' + + if task == 'od': + classes_name = data_sample['text'] + result['detection'] = {} + + if len(bboxes) > 0: + det_bboxes, keep = batched_nms( + bboxes, scores, labels, + dict(type='nms', iou_threshold=self.nms_thr)) + _scores = det_bboxes[:, -1] + _bboxes = det_bboxes[:, :-1] + _labels = labels[keep] + + instances = [] + _bboxes = _bboxes.numpy().tolist() + _scores = _scores.numpy().tolist() + _labels = _labels.numpy().tolist() + for bbox, score, label in zip(_bboxes, _scores, _labels): + round_bbox = [round(b, 2) for b in bbox] + round_score = round(score, 2) + instances.append({ + 'bbox': round_bbox, + 'score': round_score, + 'label': label, + 'category': classes_name[label] + }) + result['detection']['instances'] = instances + else: + result['detection']['instances'] = [] + self.results.append(result) + else: + caption = data_sample['text'] + result['grounding'] = {} + result['grounding']['caption'] = caption + + tokens_positive = data_sample['tokens_positive'] + + region_list = [] + for label, positive in enumerate(tokens_positive): + phrase = [caption[pos[0]:pos[1]] for pos in positive] + + _bboxes = bboxes[labels == label] + _scores = scores[labels == label] + det_bboxes, _ = batched_nms( + _bboxes, + _scores, + None, + dict(type='nms', iou_threshold=self.nms_thr), + class_agnostic=True) + _scores = det_bboxes[:, -1].numpy().tolist() + _bboxes = det_bboxes[:, :-1].numpy().tolist() + + round_bboxes = [] + for bbox in _bboxes: + round_bboxes.append([round(b, 2) for b in bbox]) + _scores = [[round(s, 2) for s in _scores]] + region = { + 'phrase': phrase, + 'bbox': round_bboxes, + 'score': _scores, + 'tokens_positive': positive + } + region_list.append(region) + result['grounding']['regions'] = region_list + self.results.append(result) + + def compute_metrics(self, results: list) -> dict: + with jsonlines.open(self.outfile_path, mode='w') as writer: + writer.write_all(results) + print_log( + f'Results has been saved to {self.outfile_path}.', + logger='current') + return {} diff --git a/mmdetection/mmdet/evaluation/metrics/dump_proposals_metric.py b/mmdetection/mmdet/evaluation/metrics/dump_proposals_metric.py new file mode 100644 index 00000000..9e9c5365 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/dump_proposals_metric.py @@ -0,0 +1,119 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import os.path as osp +from typing import Optional, Sequence + +from mmengine.dist import is_main_process +from mmengine.evaluator import BaseMetric +from mmengine.fileio import dump +from mmengine.logging import MMLogger +from mmengine.structures import InstanceData + +from mmdet.registry import METRICS + + +@METRICS.register_module() +class DumpProposals(BaseMetric): + """Dump proposals pseudo metric. + + Args: + output_dir (str): The root directory for ``proposals_file``. + Defaults to ''. + proposals_file (str): Proposals file path. Defaults to 'proposals.pkl'. + num_max_proposals (int, optional): Maximum number of proposals to dump. + If not specified, all proposals will be dumped. + file_client_args (dict, optional): Arguments to instantiate the + corresponding backend in mmdet <= 3.0.0rc6. Defaults to None. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + """ + + default_prefix: Optional[str] = 'dump_proposals' + + def __init__(self, + output_dir: str = '', + proposals_file: str = 'proposals.pkl', + num_max_proposals: Optional[int] = None, + file_client_args: dict = None, + backend_args: dict = None, + collect_device: str = 'cpu', + prefix: Optional[str] = None) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + self.num_max_proposals = num_max_proposals + # TODO: update after mmengine finish refactor fileio. + self.backend_args = backend_args + if file_client_args is not None: + raise RuntimeError( + 'The `file_client_args` is deprecated, ' + 'please use `backend_args` instead, please refer to' + 'https://github.com/open-mmlab/mmdetection/blob/main/configs/_base_/datasets/coco_detection.py' # noqa: E501 + ) + self.output_dir = output_dir + assert proposals_file.endswith(('.pkl', '.pickle')), \ + 'The output file must be a pkl file.' + + self.proposals_file = os.path.join(self.output_dir, proposals_file) + if is_main_process(): + os.makedirs(self.output_dir, exist_ok=True) + + def process(self, data_batch: Sequence[dict], + data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + pred = data_sample['pred_instances'] + # `bboxes` is sorted by `scores` + ranked_scores, rank_inds = pred['scores'].sort(descending=True) + ranked_bboxes = pred['bboxes'][rank_inds, :] + + ranked_bboxes = ranked_bboxes.cpu().numpy() + ranked_scores = ranked_scores.cpu().numpy() + + pred_instance = InstanceData() + pred_instance.bboxes = ranked_bboxes + pred_instance.scores = ranked_scores + if self.num_max_proposals is not None: + pred_instance = pred_instance[:self.num_max_proposals] + + img_path = data_sample['img_path'] + # `file_name` is the key to obtain the proposals from the + # `proposals_list`. + file_name = osp.join( + osp.split(osp.split(img_path)[0])[-1], + osp.split(img_path)[-1]) + result = {file_name: pred_instance} + self.results.append(result) + + def compute_metrics(self, results: list) -> dict: + """Dump the processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + dict: An empty dict. + """ + logger: MMLogger = MMLogger.get_current_instance() + dump_results = {} + for result in results: + dump_results.update(result) + dump( + dump_results, + file=self.proposals_file, + backend_args=self.backend_args) + logger.info(f'Results are saved at {self.proposals_file}') + return {} diff --git a/mmdetection/mmdet/evaluation/metrics/flickr30k_metric.py b/mmdetection/mmdet/evaluation/metrics/flickr30k_metric.py new file mode 100644 index 00000000..f8b64bfd --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/flickr30k_metric.py @@ -0,0 +1,165 @@ +# Copyright (c) OpenMMLab. All rights reserved +from collections import defaultdict +from typing import Dict, List, Optional, Sequence + +import numpy as np +from mmengine.evaluator import BaseMetric +from mmengine.logging import MMLogger + +from mmdet.registry import METRICS +from ..functional import bbox_overlaps + + +class RecallTracker: + """Utility class to track recall@k for various k, split by categories.""" + + def __init__(self, topk: Sequence[int]): + """ + Parameters: + - topk : tuple of ints corresponding to the recalls being + tracked (eg, recall@1, recall@10, ...) + """ + + self.total_byk_bycat: Dict[int, Dict[str, int]] = { + k: defaultdict(int) + for k in topk + } + self.positives_byk_bycat: Dict[int, Dict[str, int]] = { + k: defaultdict(int) + for k in topk + } + + def add_positive(self, k: int, category: str): + """Log a positive hit @k for given category.""" + if k not in self.total_byk_bycat: + raise RuntimeError(f'{k} is not a valid recall threshold') + self.total_byk_bycat[k][category] += 1 + self.positives_byk_bycat[k][category] += 1 + + def add_negative(self, k: int, category: str): + """Log a negative hit @k for given category.""" + if k not in self.total_byk_bycat: + raise RuntimeError(f'{k} is not a valid recall threshold') + self.total_byk_bycat[k][category] += 1 + + def report(self) -> Dict[str, Dict[str, float]]: + """Return a condensed report of the results as a dict of dict. + + report[k][cat] is the recall@k for the given category + """ + report: Dict[str, Dict[str, float]] = {} + for k in self.total_byk_bycat: + assert k in self.positives_byk_bycat + report[str(k)] = { + cat: + self.positives_byk_bycat[k][cat] / self.total_byk_bycat[k][cat] + for cat in self.total_byk_bycat[k] + } + return report + + +@METRICS.register_module() +class Flickr30kMetric(BaseMetric): + """Phrase Grounding Metric.""" + + def __init__( + self, + topk: Sequence[int] = (1, 5, 10, -1), + iou_thrs: float = 0.5, + merge_boxes: bool = False, + collect_device: str = 'cpu', + prefix: Optional[str] = None, + ) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + + self.iou_thrs = iou_thrs + self.topk = topk + self.merge = merge_boxes + + def merge_boxes(self, boxes: List[List[int]]) -> List[List[int]]: + """Return the boxes corresponding to the smallest enclosing box + containing all the provided boxes The boxes are expected in [x1, y1, + x2, y2] format.""" + if len(boxes) == 1: + return boxes + + np_boxes = np.asarray(boxes) + + return [[ + np.boxes[:, 0].min(), np_boxes[:, 1].min(), np_boxes[:, 2].max(), + np_boxes[:, 3].max() + ]] + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. + + The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + pred = data_sample['pred_instances'] + gt = data_sample['gt_instances']['bboxes'] + gt_label = data_sample['phrase_ids'] + phrases = data_sample['phrases'] + assert len(gt) == len(gt_label) + + self.results.append((pred, gt, gt_label, phrases)) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + + pred_list, gt_list, gt_label_list, phrase_list = zip(*results) + + recall_tracker = RecallTracker(self.topk) + + for pred, gt_boxes, gt_labels, phrases in zip(pred_list, gt_list, + gt_label_list, + phrase_list): + pred_boxes = pred['bboxes'].cpu().numpy() + pred_labels = pred['labels'].cpu().numpy() + for i, phrase in enumerate(phrases): + cur_index = pred_labels == i + cur_boxes = pred_boxes[cur_index] + tar_index = [ + index for index, value in enumerate(gt_labels) + if value == i + ] + tar_boxes = gt_boxes[tar_index] + if self.merge: + tar_boxes = self.merge_boxes(tar_boxes) + if len(cur_boxes) == 0: + cur_boxes = [[0., 0., 0., 0.]] + ious = bbox_overlaps( + np.asarray(cur_boxes), np.asarray(tar_boxes)) + for k in self.topk: + if k == -1: + maxi = ious.max() + else: + assert k > 0 + maxi = ious[:k].max() + if maxi >= self.iou_thrs: + recall_tracker.add_positive(k, 'all') + # TODO: do not support class-wise evaluation yet + # for phrase_type in phrase['phrase_type']: + # recall_tracker.add_positive(k, phrase_type) + else: + recall_tracker.add_negative(k, 'all') + # for phrase_type in phrase['phrase_type']: + # recall_tracker.add_negative(k, phrase_type) + + results = recall_tracker.report() + logger.info(results) + return results diff --git a/mmdetection/mmdet/evaluation/metrics/grefcoco_metric.py b/mmdetection/mmdet/evaluation/metrics/grefcoco_metric.py new file mode 100644 index 00000000..55cc638c --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/grefcoco_metric.py @@ -0,0 +1,122 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, Optional, Sequence + +import numpy as np +import torch +from mmengine.evaluator import BaseMetric +from mmengine.fileio import get_local_path +from mmengine.logging import MMLogger + +from mmdet.datasets.api_wrappers import COCO +from mmdet.registry import METRICS +from ..functional import bbox_overlaps + + +# refer from https://github.com/henghuiding/gRefCOCO/blob/main/mdetr/datasets/refexp.py # noqa +@METRICS.register_module() +class gRefCOCOMetric(BaseMetric): + default_prefix: Optional[str] = 'grefcoco' + + def __init__(self, + ann_file: Optional[str] = None, + metric: str = 'bbox', + iou_thrs: float = 0.5, + thresh_score: float = 0.7, + thresh_f1: float = 1.0, + **kwargs) -> None: + super().__init__(**kwargs) + self.metric = metric + self.iou_thrs = iou_thrs + self.thresh_score = thresh_score + self.thresh_f1 = thresh_f1 + + with get_local_path(ann_file) as local_path: + self.coco = COCO(local_path) + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + for data_sample in data_samples: + result = dict() + pred = data_sample['pred_instances'] + result['img_id'] = data_sample['img_id'] + result['bboxes'] = pred['bboxes'].cpu() + result['scores'] = pred['scores'].cpu() + self.results.append(result) + + def compute_metrics(self, results: list) -> Dict[str, float]: + logger: MMLogger = MMLogger.get_current_instance() + + correct_image = 0 + num_image = 0 + nt = {'TP': 0, 'TN': 0, 'FP': 0, 'FN': 0} + + for result in results: + img_id = result['img_id'] + TP = 0 + + ann_ids = self.coco.getAnnIds(imgIds=img_id) + target = self.coco.loadAnns(ann_ids[0]) + + converted_bbox_all = [] + no_target_flag = False + for one_target in target: + if one_target['category_id'] == -1: + no_target_flag = True + target_bbox = one_target['bbox'] + converted_bbox = [ + target_bbox[0], + target_bbox[1], + target_bbox[2] + target_bbox[0], + target_bbox[3] + target_bbox[1], + ] + converted_bbox_all.append( + np.array(converted_bbox).reshape(-1, 4)) + gt_bbox_all = np.concatenate(converted_bbox_all, axis=0) + + idx = result['scores'] >= self.thresh_score + filtered_boxes = result['bboxes'][idx] + + iou = bbox_overlaps(filtered_boxes.numpy(), gt_bbox_all) + iou = torch.from_numpy(iou) + + num_prediction = filtered_boxes.shape[0] + num_gt = gt_bbox_all.shape[0] + if no_target_flag: + if num_prediction >= 1: + nt['FN'] += 1 + else: + nt['TP'] += 1 + if num_prediction >= 1: + f_1 = 0. + else: + f_1 = 1.0 + else: + if num_prediction >= 1: + nt['TN'] += 1 + else: + nt['FP'] += 1 + for i in range(min(num_prediction, num_gt)): + top_value, top_index = torch.topk(iou.flatten(0, 1), 1) + if top_value < self.iou_thrs: + break + else: + top_index_x = top_index // num_gt + top_index_y = top_index % num_gt + TP += 1 + iou[top_index_x[0], :] = 0.0 + iou[:, top_index_y[0]] = 0.0 + FP = num_prediction - TP + FN = num_gt - TP + f_1 = 2 * TP / (2 * TP + FP + FN) + + if f_1 >= self.thresh_f1: + correct_image += 1 + num_image += 1 + + score = correct_image / max(num_image, 1) + results = { + 'F1_score': score, + 'T_acc': nt['TN'] / (nt['TN'] + nt['FP']), + 'N_acc': nt['TP'] / (nt['TP'] + nt['FN']) + } + logger.info(results) + return results diff --git a/mmdetection/mmdet/evaluation/metrics/lvis_metric.py b/mmdetection/mmdet/evaluation/metrics/lvis_metric.py new file mode 100644 index 00000000..a861c6ee --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/lvis_metric.py @@ -0,0 +1,534 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import itertools +import logging +import os.path as osp +import tempfile +import warnings +from collections import OrderedDict, defaultdict +from typing import Dict, List, Optional, Sequence, Union + +import numpy as np +import torch +from mmengine.dist import (all_gather_object, broadcast_object_list, + is_main_process) +from mmengine.evaluator import BaseMetric +from mmengine.evaluator.metric import _to_cpu +from mmengine.fileio import get_local_path +from mmengine.logging import MMLogger, print_log +from terminaltables import AsciiTable + +from mmdet.registry import METRICS +from mmdet.structures.mask import encode_mask_results +from ..functional import eval_recalls +from .coco_metric import CocoMetric + +try: + import lvis + + if getattr(lvis, '__version__', '0') >= '10.5.3': + warnings.warn( + 'mmlvis is deprecated, please install official lvis-api by "pip install git+https://github.com/lvis-dataset/lvis-api.git"', # noqa: E501 + UserWarning) + from lvis import LVIS, LVISEval, LVISResults +except ImportError: + lvis = None + LVISEval = None + LVISResults = None + + +@METRICS.register_module() +class LVISMetric(CocoMetric): + """LVIS evaluation metric. + + Args: + ann_file (str, optional): Path to the coco format annotation file. + If not specified, ground truth annotations from the dataset will + be converted to coco format. Defaults to None. + metric (str | List[str]): Metrics to be evaluated. Valid metrics + include 'bbox', 'segm', 'proposal', and 'proposal_fast'. + Defaults to 'bbox'. + classwise (bool): Whether to evaluate the metric class-wise. + Defaults to False. + proposal_nums (Sequence[int]): Numbers of proposals to be evaluated. + Defaults to (100, 300, 1000). + iou_thrs (float | List[float], optional): IoU threshold to compute AP + and AR. If not specified, IoUs from 0.5 to 0.95 will be used. + Defaults to None. + metric_items (List[str], optional): Metric result names to be + recorded in the evaluation result. Defaults to None. + format_only (bool): Format the output results without perform + evaluation. It is useful when you want to format the result + to a specific format and submit it to the test server. + Defaults to False. + outfile_prefix (str, optional): The prefix of json files. It includes + the file path and the prefix of filename, e.g., "a/b/prefix". + If not specified, a temp file will be created. Defaults to None. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + file_client_args (dict, optional): Arguments to instantiate the + corresponding backend in mmdet <= 3.0.0rc6. Defaults to None. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + """ + + default_prefix: Optional[str] = 'lvis' + + def __init__(self, + ann_file: Optional[str] = None, + metric: Union[str, List[str]] = 'bbox', + classwise: bool = False, + proposal_nums: Sequence[int] = (100, 300, 1000), + iou_thrs: Optional[Union[float, Sequence[float]]] = None, + metric_items: Optional[Sequence[str]] = None, + format_only: bool = False, + outfile_prefix: Optional[str] = None, + collect_device: str = 'cpu', + prefix: Optional[str] = None, + file_client_args: dict = None, + backend_args: dict = None) -> None: + if lvis is None: + raise RuntimeError( + 'Package lvis is not installed. Please run "pip install ' + 'git+https://github.com/lvis-dataset/lvis-api.git".') + super().__init__(collect_device=collect_device, prefix=prefix) + # coco evaluation metrics + self.metrics = metric if isinstance(metric, list) else [metric] + allowed_metrics = ['bbox', 'segm', 'proposal', 'proposal_fast'] + for metric in self.metrics: + if metric not in allowed_metrics: + raise KeyError( + "metric should be one of 'bbox', 'segm', 'proposal', " + f"'proposal_fast', but got {metric}.") + + # do class wise evaluation, default False + self.classwise = classwise + + # proposal_nums used to compute recall or precision. + self.proposal_nums = list(proposal_nums) + + # iou_thrs used to compute recall or precision. + if iou_thrs is None: + iou_thrs = np.linspace( + .5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True) + self.iou_thrs = iou_thrs + self.metric_items = metric_items + self.format_only = format_only + if self.format_only: + assert outfile_prefix is not None, 'outfile_prefix must be not' + 'None when format_only is True, otherwise the result files will' + 'be saved to a temp directory which will be cleaned up at the end.' + + self.outfile_prefix = outfile_prefix + self.backend_args = backend_args + if file_client_args is not None: + raise RuntimeError( + 'The `file_client_args` is deprecated, ' + 'please use `backend_args` instead, please refer to' + 'https://github.com/open-mmlab/mmdetection/blob/main/configs/_base_/datasets/coco_detection.py' # noqa: E501 + ) + + # if ann_file is not specified, + # initialize lvis api with the converted dataset + if ann_file is not None: + with get_local_path( + ann_file, backend_args=self.backend_args) as local_path: + self._lvis_api = LVIS(local_path) + else: + self._lvis_api = None + + # handle dataset lazy init + self.cat_ids = None + self.img_ids = None + + def fast_eval_recall(self, + results: List[dict], + proposal_nums: Sequence[int], + iou_thrs: Sequence[float], + logger: Optional[MMLogger] = None) -> np.ndarray: + """Evaluate proposal recall with LVIS's fast_eval_recall. + + Args: + results (List[dict]): Results of the dataset. + proposal_nums (Sequence[int]): Proposal numbers used for + evaluation. + iou_thrs (Sequence[float]): IoU thresholds used for evaluation. + logger (MMLogger, optional): Logger used for logging the recall + summary. + Returns: + np.ndarray: Averaged recall results. + """ + gt_bboxes = [] + pred_bboxes = [result['bboxes'] for result in results] + for i in range(len(self.img_ids)): + ann_ids = self._lvis_api.get_ann_ids(img_ids=[self.img_ids[i]]) + ann_info = self._lvis_api.load_anns(ann_ids) + if len(ann_info) == 0: + gt_bboxes.append(np.zeros((0, 4))) + continue + bboxes = [] + for ann in ann_info: + x1, y1, w, h = ann['bbox'] + bboxes.append([x1, y1, x1 + w, y1 + h]) + bboxes = np.array(bboxes, dtype=np.float32) + if bboxes.shape[0] == 0: + bboxes = np.zeros((0, 4)) + gt_bboxes.append(bboxes) + + recalls = eval_recalls( + gt_bboxes, pred_bboxes, proposal_nums, iou_thrs, logger=logger) + ar = recalls.mean(axis=1) + return ar + + # TODO: data_batch is no longer needed, consider adjusting the + # parameter position + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + result = dict() + pred = data_sample['pred_instances'] + result['img_id'] = data_sample['img_id'] + result['bboxes'] = pred['bboxes'].cpu().numpy() + result['scores'] = pred['scores'].cpu().numpy() + result['labels'] = pred['labels'].cpu().numpy() + # encode mask to RLE + if 'masks' in pred: + result['masks'] = encode_mask_results( + pred['masks'].detach().cpu().numpy()) + # some detectors use different scores for bbox and mask + if 'mask_scores' in pred: + result['mask_scores'] = pred['mask_scores'].cpu().numpy() + + # parse gt + gt = dict() + gt['width'] = data_sample['ori_shape'][1] + gt['height'] = data_sample['ori_shape'][0] + gt['img_id'] = data_sample['img_id'] + if self._lvis_api is None: + # TODO: Need to refactor to support LoadAnnotations + assert 'instances' in data_sample, \ + 'ground truth is required for evaluation when ' \ + '`ann_file` is not provided' + gt['anns'] = data_sample['instances'] + # add converted result to the results list + self.results.append((gt, result)) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + + # split gt and prediction list + gts, preds = zip(*results) + + tmp_dir = None + if self.outfile_prefix is None: + tmp_dir = tempfile.TemporaryDirectory() + outfile_prefix = osp.join(tmp_dir.name, 'results') + else: + outfile_prefix = self.outfile_prefix + + if self._lvis_api is None: + # use converted gt json file to initialize coco api + logger.info('Converting ground truth to coco format...') + coco_json_path = self.gt_to_coco_json( + gt_dicts=gts, outfile_prefix=outfile_prefix) + self._lvis_api = LVIS(coco_json_path) + + # handle lazy init + if self.cat_ids is None: + self.cat_ids = self._lvis_api.get_cat_ids() + if self.img_ids is None: + self.img_ids = self._lvis_api.get_img_ids() + + # convert predictions to coco format and dump to json file + result_files = self.results2json(preds, outfile_prefix) + + eval_results = OrderedDict() + if self.format_only: + logger.info('results are saved in ' + f'{osp.dirname(outfile_prefix)}') + return eval_results + + lvis_gt = self._lvis_api + + for metric in self.metrics: + logger.info(f'Evaluating {metric}...') + + # TODO: May refactor fast_eval_recall to an independent metric? + # fast eval recall + if metric == 'proposal_fast': + ar = self.fast_eval_recall( + preds, self.proposal_nums, self.iou_thrs, logger=logger) + log_msg = [] + for i, num in enumerate(self.proposal_nums): + eval_results[f'AR@{num}'] = ar[i] + log_msg.append(f'\nAR@{num}\t{ar[i]:.4f}') + log_msg = ''.join(log_msg) + logger.info(log_msg) + continue + + try: + lvis_dt = LVISResults(lvis_gt, result_files[metric]) + except IndexError: + logger.info( + 'The testing results of the whole dataset is empty.') + break + + iou_type = 'bbox' if metric == 'proposal' else metric + lvis_eval = LVISEval(lvis_gt, lvis_dt, iou_type) + lvis_eval.params.imgIds = self.img_ids + metric_items = self.metric_items + if metric == 'proposal': + lvis_eval.params.useCats = 0 + lvis_eval.params.maxDets = list(self.proposal_nums) + lvis_eval.evaluate() + lvis_eval.accumulate() + lvis_eval.summarize() + if metric_items is None: + metric_items = ['AR@300', 'ARs@300', 'ARm@300', 'ARl@300'] + for k, v in lvis_eval.get_results().items(): + if k in metric_items: + val = float('{:.3f}'.format(float(v))) + eval_results[k] = val + + else: + lvis_eval.evaluate() + lvis_eval.accumulate() + lvis_eval.summarize() + lvis_results = lvis_eval.get_results() + if self.classwise: # Compute per-category AP + # Compute per-category AP + # from https://github.com/facebookresearch/detectron2/ + precisions = lvis_eval.eval['precision'] + # precision: (iou, recall, cls, area range, max dets) + assert len(self.cat_ids) == precisions.shape[2] + + results_per_category = [] + for idx, catId in enumerate(self.cat_ids): + # area range index 0: all area ranges + # max dets index -1: typically 100 per image + # the dimensions of precisions are + # [num_thrs, num_recalls, num_cats, num_area_rngs] + nm = self._lvis_api.load_cats([catId])[0] + precision = precisions[:, :, idx, 0] + precision = precision[precision > -1] + if precision.size: + ap = np.mean(precision) + else: + ap = float('nan') + results_per_category.append( + (f'{nm["name"]}', f'{float(ap):0.3f}')) + eval_results[f'{nm["name"]}_precision'] = round(ap, 3) + + num_columns = min(6, len(results_per_category) * 2) + results_flatten = list( + itertools.chain(*results_per_category)) + headers = ['category', 'AP'] * (num_columns // 2) + results_2d = itertools.zip_longest(*[ + results_flatten[i::num_columns] + for i in range(num_columns) + ]) + table_data = [headers] + table_data += [result for result in results_2d] + table = AsciiTable(table_data) + logger.info('\n' + table.table) + + if metric_items is None: + metric_items = [ + 'AP', 'AP50', 'AP75', 'APs', 'APm', 'APl', 'APr', + 'APc', 'APf' + ] + + for k, v in lvis_results.items(): + if k in metric_items: + key = '{}_{}'.format(metric, k) + val = float('{:.3f}'.format(float(v))) + eval_results[key] = val + + lvis_eval.print_results() + if tmp_dir is not None: + tmp_dir.cleanup() + return eval_results + + +def _merge_lists(listA, listB, maxN, key): + result = [] + indA, indB = 0, 0 + while (indA < len(listA) or indB < len(listB)) and len(result) < maxN: + if (indB < len(listB)) and (indA >= len(listA) + or key(listA[indA]) < key(listB[indB])): + result.append(listB[indB]) + indB += 1 + else: + result.append(listA[indA]) + indA += 1 + return result + + +@METRICS.register_module() +class LVISFixedAPMetric(BaseMetric): + default_prefix: Optional[str] = 'lvis_fixed_ap' + + def __init__(self, + ann_file: str, + topk: int = 10000, + format_only: bool = False, + outfile_prefix: Optional[str] = None, + collect_device: str = 'cpu', + prefix: Optional[str] = None, + backend_args: dict = None) -> None: + + if lvis is None: + raise RuntimeError( + 'Package lvis is not installed. Please run "pip install ' + 'git+https://github.com/lvis-dataset/lvis-api.git".') + super().__init__(collect_device=collect_device, prefix=prefix) + + self.format_only = format_only + if self.format_only: + assert outfile_prefix is not None, 'outfile_prefix must be not' + 'None when format_only is True, otherwise the result files will' + 'be saved to a temp directory which will be cleaned up at the end.' + + self.outfile_prefix = outfile_prefix + self.backend_args = backend_args + + with get_local_path( + ann_file, backend_args=self.backend_args) as local_path: + self._lvis_api = LVIS(local_path) + + self.cat_ids = self._lvis_api.get_cat_ids() + + self.results = {} + self.topk = topk + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + cur_results = [] + for data_sample in data_samples: + pred = data_sample['pred_instances'] + xmin, ymin, xmax, ymax = pred['bboxes'].cpu().unbind(1) + boxes = torch.stack((xmin, ymin, xmax - xmin, ymax - ymin), + dim=1).tolist() + + scores = pred['scores'].cpu().numpy() + labels = pred['labels'].cpu().numpy() + + if len(boxes) == 0: + continue + + cur_results.extend([{ + 'image_id': data_sample['img_id'], + 'category_id': self.cat_ids[labels[k]], + 'bbox': box, + 'score': scores[k], + } for k, box in enumerate(boxes)]) + + by_cat = defaultdict(list) + for ann in cur_results: + by_cat[ann['category_id']].append(ann) + + for cat, cat_anns in by_cat.items(): + if cat not in self.results: + self.results[cat] = [] + + cur = sorted( + cat_anns, key=lambda x: x['score'], reverse=True)[:self.topk] + self.results[cat] = _merge_lists( + self.results[cat], cur, self.topk, key=lambda x: x['score']) + + def compute_metrics(self, results: dict) -> dict: + logger: MMLogger = MMLogger.get_current_instance() + + new_results = [] + + missing_dets_cats = set() + for cat, cat_anns in results.items(): + if len(cat_anns) < self.topk: + missing_dets_cats.add(cat) + new_results.extend( + sorted(cat_anns, key=lambda x: x['score'], + reverse=True)[:self.topk]) + + if missing_dets_cats: + logger.info( + f'\n===\n' + f'{len(missing_dets_cats)} classes had less than {self.topk} ' + f'detections!\n Outputting {self.topk} detections for each ' + f'class will improve AP further.\n ===') + + new_results = LVISResults(self._lvis_api, new_results, max_dets=-1) + lvis_eval = LVISEval(self._lvis_api, new_results, iou_type='bbox') + params = lvis_eval.params + params.max_dets = -1 # No limit on detections per image. + lvis_eval.run() + lvis_eval.print_results() + metrics = { + k: v + for k, v in lvis_eval.results.items() if k.startswith('AP') + } + logger.info(f'mAP_copypaste: {metrics}') + return metrics + + def evaluate(self, size: int) -> dict: + if len(self.results) == 0: + print_log( + f'{self.__class__.__name__} got empty `self.results`. Please ' + 'ensure that the processed results are properly added into ' + '`self.results` in `process` method.', + logger='current', + level=logging.WARNING) + + all_cats = all_gather_object(self.results) + results = defaultdict(list) + for cats in all_cats: + for cat, cat_anns in cats.items(): + results[cat].extend(cat_anns) + + if is_main_process(): + # cast all tensors in results list to cpu + results = _to_cpu(results) + _metrics = self.compute_metrics(results) # type: ignore + # Add prefix to metric names + if self.prefix: + _metrics = { + '/'.join((self.prefix, k)): v + for k, v in _metrics.items() + } + metrics = [_metrics] + else: + metrics = [None] # type: ignore + + broadcast_object_list(metrics) + + # reset the results + self.results = {} + return metrics[0] diff --git a/mmdetection/mmdet/evaluation/metrics/mot_challenge_metric.py b/mmdetection/mmdet/evaluation/metrics/mot_challenge_metric.py new file mode 100644 index 00000000..a5513c44 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/mot_challenge_metric.py @@ -0,0 +1,443 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import os.path as osp +import shutil +import tempfile +from collections import defaultdict +from typing import List, Optional, Union + +import numpy as np +import torch + +try: + import trackeval +except ImportError: + trackeval = None +from mmengine.dist import (all_gather_object, barrier, broadcast, + broadcast_object_list, get_dist_info, + is_main_process) +from mmengine.logging import MMLogger + +from mmdet.registry import METRICS, TASK_UTILS +from .base_video_metric import BaseVideoMetric + + +def get_tmpdir() -> str: + """return the same tmpdir for all processes.""" + rank, world_size = get_dist_info() + MAX_LEN = 512 + # 32 is whitespace + dir_tensor = torch.full((MAX_LEN, ), 32, dtype=torch.uint8) + if rank == 0: + tmpdir = tempfile.mkdtemp() + tmpdir = torch.tensor(bytearray(tmpdir.encode()), dtype=torch.uint8) + dir_tensor[:len(tmpdir)] = tmpdir + broadcast(dir_tensor, 0) + tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip() + return tmpdir + + +@METRICS.register_module() +class MOTChallengeMetric(BaseVideoMetric): + """Evaluation metrics for MOT Challenge. + + Args: + metric (str | list[str]): Metrics to be evaluated. Options are + 'HOTA', 'CLEAR', 'Identity'. + Defaults to ['HOTA', 'CLEAR', 'Identity']. + outfile_prefix (str, optional): Path to save the formatted results. + Defaults to None. + track_iou_thr (float): IoU threshold for tracking evaluation. + Defaults to 0.5. + benchmark (str): Benchmark to be evaluated. Defaults to 'MOT17'. + format_only (bool): If True, only formatting the results to the + official format and not performing evaluation. Defaults to False. + postprocess_tracklet_cfg (List[dict], optional): configs for tracklets + postprocessing methods. `InterpolateTracklets` is supported. + Defaults to [] + - InterpolateTracklets: + - min_num_frames (int, optional): The minimum length of a + track that will be interpolated. Defaults to 5. + - max_num_frames (int, optional): The maximum disconnected + length in a track. Defaults to 20. + - use_gsi (bool, optional): Whether to use the GSI (Gaussian- + smoothed interpolation) method. Defaults to False. + - smooth_tau (int, optional): smoothing parameter in GSI. + Defaults to 10. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Default: None + Returns: + """ + TRACKER = 'default-tracker' + allowed_metrics = ['HOTA', 'CLEAR', 'Identity'] + allowed_benchmarks = ['MOT15', 'MOT16', 'MOT17', 'MOT20', 'DanceTrack'] + default_prefix: Optional[str] = 'motchallenge-metric' + + def __init__(self, + metric: Union[str, List[str]] = ['HOTA', 'CLEAR', 'Identity'], + outfile_prefix: Optional[str] = None, + track_iou_thr: float = 0.5, + benchmark: str = 'MOT17', + format_only: bool = False, + use_postprocess: bool = False, + postprocess_tracklet_cfg: Optional[List[dict]] = [], + collect_device: str = 'cpu', + prefix: Optional[str] = None) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + if trackeval is None: + raise RuntimeError( + 'trackeval is not installed,' + 'please install it by: pip install' + 'git+https://github.com/JonathonLuiten/TrackEval.git' + 'trackeval need low version numpy, please install it' + 'by: pip install -U numpy==1.23.5') + if isinstance(metric, list): + metrics = metric + elif isinstance(metric, str): + metrics = [metric] + else: + raise TypeError('metric must be a list or a str.') + for metric in metrics: + if metric not in self.allowed_metrics: + raise KeyError(f'metric {metric} is not supported.') + self.metrics = metrics + self.format_only = format_only + if self.format_only: + assert outfile_prefix is not None, 'outfile_prefix must be not' + 'None when format_only is True, otherwise the result files will' + 'be saved to a temp directory which will be cleaned up at the end.' + self.use_postprocess = use_postprocess + self.postprocess_tracklet_cfg = postprocess_tracklet_cfg.copy() + self.postprocess_tracklet_methods = [ + TASK_UTILS.build(cfg) for cfg in self.postprocess_tracklet_cfg + ] + assert benchmark in self.allowed_benchmarks + self.benchmark = benchmark + self.track_iou_thr = track_iou_thr + self.tmp_dir = tempfile.TemporaryDirectory() + self.tmp_dir.name = get_tmpdir() + self.seq_info = defaultdict( + lambda: dict(seq_length=-1, gt_tracks=[], pred_tracks=[])) + self.gt_dir = self._get_gt_dir() + self.pred_dir = self._get_pred_dir(outfile_prefix) + self.seqmap = osp.join(self.pred_dir, 'videoseq.txt') + with open(self.seqmap, 'w') as f: + f.write('name\n') + + def __del__(self): + # To avoid tmpdir being cleaned up too early, because in multiple + # consecutive ValLoops, the value of `self.tmp_dir.name` is unchanged, + # and calling `tmp_dir.cleanup()` in compute_metrics will cause errors. + self.tmp_dir.cleanup() + + def _get_pred_dir(self, outfile_prefix): + """Get directory to save the prediction results.""" + logger: MMLogger = MMLogger.get_current_instance() + + if outfile_prefix is None: + outfile_prefix = self.tmp_dir.name + else: + if osp.exists(outfile_prefix) and is_main_process(): + logger.info('remove previous results.') + shutil.rmtree(outfile_prefix) + pred_dir = osp.join(outfile_prefix, self.TRACKER) + os.makedirs(pred_dir, exist_ok=True) + return pred_dir + + def _get_gt_dir(self): + """Get directory to save the gt files.""" + output_dir = osp.join(self.tmp_dir.name, 'gt') + os.makedirs(output_dir, exist_ok=True) + return output_dir + + def transform_gt_and_pred(self, img_data_sample, video, frame_id): + + video = img_data_sample['img_path'].split(os.sep)[-3] + # load gts + if 'instances' in img_data_sample: + gt_instances = img_data_sample['instances'] + gt_tracks = [ + np.array([ + frame_id + 1, gt_instances[i]['instance_id'], + gt_instances[i]['bbox'][0], gt_instances[i]['bbox'][1], + gt_instances[i]['bbox'][2] - gt_instances[i]['bbox'][0], + gt_instances[i]['bbox'][3] - gt_instances[i]['bbox'][1], + gt_instances[i]['mot_conf'], + gt_instances[i]['category_id'], + gt_instances[i]['visibility'] + ]) for i in range(len(gt_instances)) + ] + self.seq_info[video]['gt_tracks'].extend(gt_tracks) + + # load predictions + assert 'pred_track_instances' in img_data_sample + if self.use_postprocess: + pred_instances = img_data_sample['pred_track_instances'] + pred_tracks = [ + pred_instances['bboxes'][i] + for i in range(len(pred_instances['bboxes'])) + ] + else: + pred_instances = img_data_sample['pred_track_instances'] + pred_tracks = [ + np.array([ + frame_id + 1, pred_instances['instances_id'][i].cpu(), + pred_instances['bboxes'][i][0].cpu(), + pred_instances['bboxes'][i][1].cpu(), + (pred_instances['bboxes'][i][2] - + pred_instances['bboxes'][i][0]).cpu(), + (pred_instances['bboxes'][i][3] - + pred_instances['bboxes'][i][1]).cpu(), + pred_instances['scores'][i].cpu() + ]) for i in range(len(pred_instances['instances_id'])) + ] + self.seq_info[video]['pred_tracks'].extend(pred_tracks) + + def process_image(self, data_samples, video_len): + + img_data_sample = data_samples[0].to_dict() + video = img_data_sample['img_path'].split(os.sep)[-3] + frame_id = img_data_sample['frame_id'] + if self.seq_info[video]['seq_length'] == -1: + self.seq_info[video]['seq_length'] = video_len + self.transform_gt_and_pred(img_data_sample, video, frame_id) + + if frame_id == video_len - 1: + # postprocessing + if self.postprocess_tracklet_cfg: + info = self.seq_info[video] + pred_tracks = np.array(info['pred_tracks']) + for postprocess_tracklet_methods in \ + self.postprocess_tracklet_methods: + pred_tracks = postprocess_tracklet_methods\ + .forward(pred_tracks) + info['pred_tracks'] = pred_tracks + self._save_one_video_gts_preds(video) + + def process_video(self, data_samples): + + video_len = len(data_samples) + for frame_id in range(video_len): + img_data_sample = data_samples[frame_id].to_dict() + # load basic info + video = img_data_sample['img_path'].split(os.sep)[-3] + if self.seq_info[video]['seq_length'] == -1: + self.seq_info[video]['seq_length'] = video_len + self.transform_gt_and_pred(img_data_sample, video, frame_id) + + if self.postprocess_tracklet_cfg: + info = self.seq_info[video] + pred_tracks = np.array(info['pred_tracks']) + for postprocess_tracklet_methods in \ + self.postprocess_tracklet_methods: + pred_tracks = postprocess_tracklet_methods \ + .forward(pred_tracks) + info['pred_tracks'] = pred_tracks + self._save_one_video_gts_preds(video) + + def _save_one_video_gts_preds(self, seq: str) -> None: + """Save the gt and prediction results.""" + info = self.seq_info[seq] + # save predictions + pred_file = osp.join(self.pred_dir, seq + '.txt') + + pred_tracks = np.array(info['pred_tracks']) + + with open(pred_file, 'wt') as f: + for tracks in pred_tracks: + line = '%d,%d,%.3f,%.3f,%.3f,%.3f,%.3f,-1,-1,-1\n' % ( + tracks[0], tracks[1], tracks[2], tracks[3], tracks[4], + tracks[5], tracks[6]) + f.writelines(line) + + info['pred_tracks'] = [] + # save gts + if info['gt_tracks']: + gt_file = osp.join(self.gt_dir, seq + '.txt') + with open(gt_file, 'wt') as f: + for tracks in info['gt_tracks']: + line = '%d,%d,%d,%d,%d,%d,%d,%d,%.5f\n' % ( + tracks[0], tracks[1], tracks[2], tracks[3], tracks[4], + tracks[5], tracks[6], tracks[7], tracks[8]) + f.writelines(line) + info['gt_tracks'].clear() + # save seq info + with open(self.seqmap, 'a') as f: + f.write(seq + '\n') + f.close() + + def compute_metrics(self, results: list = None) -> dict: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + Defaults to None. + + Returns: + dict: The computed metrics. The keys are the names of the metrics, + and the values are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + + # NOTICE: don't access `self.results` from the method. + eval_results = dict() + + if self.format_only: + return eval_results + + eval_config = trackeval.Evaluator.get_default_eval_config() + + # need to split out the tracker name + # caused by the implementation of TrackEval + pred_dir_tmp = self.pred_dir.rsplit(osp.sep, 1)[0] + dataset_config = self.get_dataset_cfg(self.gt_dir, pred_dir_tmp) + + evaluator = trackeval.Evaluator(eval_config) + dataset = [trackeval.datasets.MotChallenge2DBox(dataset_config)] + metrics = [ + getattr(trackeval.metrics, + metric)(dict(METRICS=[metric], THRESHOLD=0.5)) + for metric in self.metrics + ] + output_res, _ = evaluator.evaluate(dataset, metrics) + output_res = output_res['MotChallenge2DBox'][ + self.TRACKER]['COMBINED_SEQ']['pedestrian'] + + if 'HOTA' in self.metrics: + logger.info('Evaluating HOTA Metrics...') + eval_results['HOTA'] = np.average(output_res['HOTA']['HOTA']) + eval_results['AssA'] = np.average(output_res['HOTA']['AssA']) + eval_results['DetA'] = np.average(output_res['HOTA']['DetA']) + + if 'CLEAR' in self.metrics: + logger.info('Evaluating CLEAR Metrics...') + eval_results['MOTA'] = np.average(output_res['CLEAR']['MOTA']) + eval_results['MOTP'] = np.average(output_res['CLEAR']['MOTP']) + eval_results['IDSW'] = np.average(output_res['CLEAR']['IDSW']) + eval_results['TP'] = np.average(output_res['CLEAR']['CLR_TP']) + eval_results['FP'] = np.average(output_res['CLEAR']['CLR_FP']) + eval_results['FN'] = np.average(output_res['CLEAR']['CLR_FN']) + eval_results['Frag'] = np.average(output_res['CLEAR']['Frag']) + eval_results['MT'] = np.average(output_res['CLEAR']['MT']) + eval_results['ML'] = np.average(output_res['CLEAR']['ML']) + + if 'Identity' in self.metrics: + logger.info('Evaluating Identity Metrics...') + eval_results['IDF1'] = np.average(output_res['Identity']['IDF1']) + eval_results['IDTP'] = np.average(output_res['Identity']['IDTP']) + eval_results['IDFN'] = np.average(output_res['Identity']['IDFN']) + eval_results['IDFP'] = np.average(output_res['Identity']['IDFP']) + eval_results['IDP'] = np.average(output_res['Identity']['IDP']) + eval_results['IDR'] = np.average(output_res['Identity']['IDR']) + + return eval_results + + def evaluate(self, size: int = 1) -> dict: + """Evaluate the model performance of the whole dataset after processing + all batches. + + Args: + size (int): Length of the entire validation dataset. + Defaults to None. + + Returns: + dict: Evaluation metrics dict on the val dataset. The keys are the + names of the metrics, and the values are corresponding results. + """ + # wait for all processes to complete prediction. + barrier() + + # gather seq_info and convert the list of dict to a dict. + # convert self.seq_info to dict first to make it picklable. + gathered_seq_info = all_gather_object(dict(self.seq_info)) + all_seq_info = dict() + for _seq_info in gathered_seq_info: + all_seq_info.update(_seq_info) + self.seq_info = all_seq_info + + if is_main_process(): + _metrics = self.compute_metrics() # type: ignore + # Add prefix to metric names + if self.prefix: + _metrics = { + '/'.join((self.prefix, k)): v + for k, v in _metrics.items() + } + metrics = [_metrics] + else: + metrics = [None] # type: ignore + + broadcast_object_list(metrics) + + # reset the results list + self.results.clear() + return metrics[0] + + def get_dataset_cfg(self, gt_folder: str, tracker_folder: str): + """Get default configs for trackeval.datasets.MotChallenge2DBox. + + Args: + gt_folder (str): the name of the GT folder + tracker_folder (str): the name of the tracker folder + + Returns: + Dataset Configs for MotChallenge2DBox. + """ + dataset_config = dict( + # Location of GT data + GT_FOLDER=gt_folder, + # Trackers location + TRACKERS_FOLDER=tracker_folder, + # Where to save eval results + # (if None, same as TRACKERS_FOLDER) + OUTPUT_FOLDER=None, + # Use self.TRACKER as the default tracker + TRACKERS_TO_EVAL=[self.TRACKER], + # Option values: ['pedestrian'] + CLASSES_TO_EVAL=['pedestrian'], + # Option Values: 'MOT15', 'MOT16', 'MOT17', 'MOT20', 'DanceTrack' + BENCHMARK=self.benchmark, + # Option Values: 'train', 'test' + SPLIT_TO_EVAL='val' if self.benchmark == 'DanceTrack' else 'train', + # Whether tracker input files are zipped + INPUT_AS_ZIP=False, + # Whether to print current config + PRINT_CONFIG=True, + # Whether to perform preprocessing + # (never done for MOT15) + DO_PREPROC=False if self.benchmark == 'MOT15' else True, + # Tracker files are in + # TRACKER_FOLDER/tracker_name/TRACKER_SUB_FOLDER + TRACKER_SUB_FOLDER='', + # Output files are saved in + # OUTPUT_FOLDER/tracker_name/OUTPUT_SUB_FOLDER + OUTPUT_SUB_FOLDER='', + # Names of trackers to display + # (if None: TRACKERS_TO_EVAL) + TRACKER_DISPLAY_NAMES=None, + # Where seqmaps are found + # (if None: GT_FOLDER/seqmaps) + SEQMAP_FOLDER=None, + # Directly specify seqmap file + # (if none use seqmap_folder/benchmark-split_to_eval) + SEQMAP_FILE=self.seqmap, + # If not None, specify sequences to eval + # and their number of timesteps + SEQ_INFO={ + seq: info['seq_length'] + for seq, info in self.seq_info.items() + }, + # '{gt_folder}/{seq}.txt' + GT_LOC_FORMAT='{gt_folder}/{seq}.txt', + # If False, data is in GT_FOLDER/BENCHMARK-SPLIT_TO_EVAL/ and in + # TRACKERS_FOLDER/BENCHMARK-SPLIT_TO_EVAL/tracker/ + # If True, the middle 'benchmark-split' folder is skipped for both. + SKIP_SPLIT_FOL=True, + ) + + return dataset_config diff --git a/mmdetection/mmdet/evaluation/metrics/openimages_metric.py b/mmdetection/mmdet/evaluation/metrics/openimages_metric.py new file mode 100644 index 00000000..d75c59e0 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/openimages_metric.py @@ -0,0 +1,237 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from collections import OrderedDict +from typing import List, Optional, Sequence, Union + +import numpy as np +from mmengine.evaluator import BaseMetric +from mmengine.logging import MMLogger, print_log + +from mmdet.registry import METRICS +from ..functional import eval_map + + +@METRICS.register_module() +class OpenImagesMetric(BaseMetric): + """OpenImages evaluation metric. + + Evaluate detection mAP for OpenImages. Please refer to + https://storage.googleapis.com/openimages/web/evaluation.html for more + details. + + Args: + iou_thrs (float or List[float]): IoU threshold. Defaults to 0.5. + ioa_thrs (float or List[float]): IoA threshold. Defaults to 0.5. + scale_ranges (List[tuple], optional): Scale ranges for evaluating + mAP. If not specified, all bounding boxes would be included in + evaluation. Defaults to None + use_group_of (bool): Whether consider group of groud truth bboxes + during evaluating. Defaults to True. + get_supercategory (bool): Whether to get parent class of the + current class. Default: True. + filter_labels (bool): Whether filter unannotated classes. + Default: True. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + """ + default_prefix: Optional[str] = 'openimages' + + def __init__(self, + iou_thrs: Union[float, List[float]] = 0.5, + ioa_thrs: Union[float, List[float]] = 0.5, + scale_ranges: Optional[List[tuple]] = None, + use_group_of: bool = True, + get_supercategory: bool = True, + filter_labels: bool = True, + collect_device: str = 'cpu', + prefix: Optional[str] = None) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + self.iou_thrs = [iou_thrs] if isinstance(iou_thrs, float) else iou_thrs + self.ioa_thrs = [ioa_thrs] if (isinstance(ioa_thrs, float) + or ioa_thrs is None) else ioa_thrs + assert isinstance(self.iou_thrs, list) and isinstance( + self.ioa_thrs, list) + assert len(self.iou_thrs) == len(self.ioa_thrs) + + self.scale_ranges = scale_ranges + self.use_group_of = use_group_of + self.get_supercategory = get_supercategory + self.filter_labels = filter_labels + + def _get_supercategory_ann(self, instances: List[dict]) -> List[dict]: + """Get parent classes's annotation of the corresponding class. + + Args: + instances (List[dict]): A list of annotations of the instances. + + Returns: + List[dict]: Annotations extended with super-category. + """ + supercat_instances = [] + relation_matrix = self.dataset_meta['RELATION_MATRIX'] + for instance in instances: + labels = np.where(relation_matrix[instance['bbox_label']])[0] + for label in labels: + if label == instance['bbox_label']: + continue + new_instance = copy.deepcopy(instance) + new_instance['bbox_label'] = label + supercat_instances.append(new_instance) + return supercat_instances + + def _process_predictions(self, pred_bboxes: np.ndarray, + pred_scores: np.ndarray, pred_labels: np.ndarray, + gt_instances: list, + image_level_labels: np.ndarray) -> tuple: + """Process results of the corresponding class of the detection bboxes. + + Note: It will choose to do the following two processing according to + the parameters: + + 1. Whether to add parent classes of the corresponding class of the + detection bboxes. + + 2. Whether to ignore the classes that unannotated on that image. + + Args: + pred_bboxes (np.ndarray): bboxes predicted by the model + pred_scores (np.ndarray): scores predicted by the model + pred_labels (np.ndarray): labels predicted by the model + gt_instances (list): ground truth annotations + image_level_labels (np.ndarray): human-verified image level labels + + Returns: + tuple: Processed bboxes, scores, and labels. + """ + processed_bboxes = copy.deepcopy(pred_bboxes) + processed_scores = copy.deepcopy(pred_scores) + processed_labels = copy.deepcopy(pred_labels) + gt_labels = np.array([ins['bbox_label'] for ins in gt_instances], + dtype=np.int64) + if image_level_labels is not None: + allowed_classes = np.unique( + np.append(gt_labels, image_level_labels)) + else: + allowed_classes = np.unique(gt_labels) + relation_matrix = self.dataset_meta['RELATION_MATRIX'] + pred_classes = np.unique(pred_labels) + for pred_class in pred_classes: + classes = np.where(relation_matrix[pred_class])[0] + for cls in classes: + if (cls in allowed_classes and cls != pred_class + and self.get_supercategory): + # add super-supercategory preds + index = np.where(pred_labels == pred_class)[0] + processed_scores = np.concatenate( + [processed_scores, pred_scores[index]]) + processed_bboxes = np.concatenate( + [processed_bboxes, pred_bboxes[index]]) + extend_labels = np.full(index.shape, cls, dtype=np.int64) + processed_labels = np.concatenate( + [processed_labels, extend_labels]) + elif cls not in allowed_classes and self.filter_labels: + # remove unannotated preds + index = np.where(processed_labels != cls)[0] + processed_scores = processed_scores[index] + processed_bboxes = processed_bboxes[index] + processed_labels = processed_labels[index] + return processed_bboxes, processed_scores, processed_labels + + # TODO: data_batch is no longer needed, consider adjusting the + # parameter position + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + gt = copy.deepcopy(data_sample) + # add super-category instances + # TODO: Need to refactor to support LoadAnnotations + instances = gt['instances'] + if self.get_supercategory: + supercat_instances = self._get_supercategory_ann(instances) + instances.extend(supercat_instances) + gt_labels = [] + gt_bboxes = [] + is_group_ofs = [] + for ins in instances: + gt_labels.append(ins['bbox_label']) + gt_bboxes.append(ins['bbox']) + is_group_ofs.append(ins['is_group_of']) + ann = dict( + labels=np.array(gt_labels, dtype=np.int64), + bboxes=np.array(gt_bboxes, dtype=np.float32).reshape((-1, 4)), + gt_is_group_ofs=np.array(is_group_ofs, dtype=bool)) + + image_level_labels = gt.get('image_level_labels', None) + pred = data_sample['pred_instances'] + pred_bboxes = pred['bboxes'].cpu().numpy() + pred_scores = pred['scores'].cpu().numpy() + pred_labels = pred['labels'].cpu().numpy() + + pred_bboxes, pred_scores, pred_labels = self._process_predictions( + pred_bboxes, pred_scores, pred_labels, instances, + image_level_labels) + + dets = [] + for label in range(len(self.dataset_meta['classes'])): + index = np.where(pred_labels == label)[0] + pred_bbox_scores = np.hstack( + [pred_bboxes[index], pred_scores[index].reshape((-1, 1))]) + dets.append(pred_bbox_scores) + self.results.append((ann, dets)) + + def compute_metrics(self, results: list) -> dict: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + dict: The computed metrics. The keys are the names of the metrics, + and the values are corresponding results. + """ + logger = MMLogger.get_current_instance() + gts, preds = zip(*results) + eval_results = OrderedDict() + # get dataset type + dataset_type = self.dataset_meta.get('dataset_type') + if dataset_type not in ['oid_challenge', 'oid_v6']: + dataset_type = 'oid_v6' + print_log( + 'Cannot infer dataset type from the length of the' + ' classes. Set `oid_v6` as dataset type.', + logger='current') + mean_aps = [] + for i, (iou_thr, + ioa_thr) in enumerate(zip(self.iou_thrs, self.ioa_thrs)): + if self.use_group_of: + assert ioa_thr is not None, 'ioa_thr must have value when' \ + ' using group_of in evaluation.' + print_log(f'\n{"-" * 15}iou_thr, ioa_thr: {iou_thr}, {ioa_thr}' + f'{"-" * 15}') + mean_ap, _ = eval_map( + preds, + gts, + scale_ranges=self.scale_ranges, + iou_thr=iou_thr, + ioa_thr=ioa_thr, + dataset=dataset_type, + logger=logger, + use_group_of=self.use_group_of) + + mean_aps.append(mean_ap) + eval_results[f'AP{int(iou_thr * 100):02d}'] = round(mean_ap, 3) + eval_results['mAP'] = sum(mean_aps) / len(mean_aps) + return eval_results diff --git a/mmdetection/mmdet/evaluation/metrics/ov_coco_metric.py b/mmdetection/mmdet/evaluation/metrics/ov_coco_metric.py new file mode 100644 index 00000000..08cb9025 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/ov_coco_metric.py @@ -0,0 +1,266 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import itertools +import os.path as osp +import tempfile +from collections import OrderedDict +from typing import Dict + +import numpy as np +from mmengine.fileio import load +from mmengine.logging import MMLogger +from terminaltables import AsciiTable + +from mmdet.datasets.api_wrappers import COCO, COCOeval, COCOevalMP +from mmdet.registry import METRICS +from .coco_metric import CocoMetric + + +@METRICS.register_module() +class OVCocoMetric(CocoMetric): + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + + # split gt and prediction list + gts, preds = zip(*results) + + tmp_dir = None + if self.outfile_prefix is None: + tmp_dir = tempfile.TemporaryDirectory() + outfile_prefix = osp.join(tmp_dir.name, 'results') + else: + outfile_prefix = self.outfile_prefix + + if self._coco_api is None: + # use converted gt json file to initialize coco api + logger.info('Converting ground truth to coco format...') + coco_json_path = self.gt_to_coco_json( + gt_dicts=gts, outfile_prefix=outfile_prefix) + self._coco_api = COCO(coco_json_path) + + # handle lazy init + if self.cat_ids is None: + self.cat_ids = self._coco_api.get_cat_ids( + cat_names=self.dataset_meta['classes']) + self.base_cat_ids = self._coco_api.get_cat_ids( + cat_names=self.dataset_meta['base_classes']) + self.novel_cat_ids = self._coco_api.get_cat_ids( + cat_names=self.dataset_meta['novel_classes']) + + if self.img_ids is None: + self.img_ids = self._coco_api.get_img_ids() + + # convert predictions to coco format and dump to json file + result_files = self.results2json(preds, outfile_prefix) + + eval_results = OrderedDict() + if self.format_only: + logger.info('results are saved in ' + f'{osp.dirname(outfile_prefix)}') + return eval_results + + for metric in self.metrics: + logger.info(f'Evaluating {metric}...') + + # TODO: May refactor fast_eval_recall to an independent metric? + # fast eval recall + if metric == 'proposal_fast': + ar = self.fast_eval_recall( + preds, self.proposal_nums, self.iou_thrs, logger=logger) + log_msg = [] + for i, num in enumerate(self.proposal_nums): + eval_results[f'AR@{num}'] = ar[i] + log_msg.append(f'\nAR@{num}\t{ar[i]:.4f}') + log_msg = ''.join(log_msg) + logger.info(log_msg) + continue + + # evaluate proposal, bbox and segm + iou_type = 'bbox' if metric == 'proposal' else metric + if metric not in result_files: + raise KeyError(f'{metric} is not in results') + try: + predictions = load(result_files[metric]) + if iou_type == 'segm': + # Refer to https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/coco.py#L331 # noqa + # When evaluating mask AP, if the results contain bbox, + # cocoapi will use the box area instead of the mask area + # for calculating the instance area. Though the overall AP + # is not affected, this leads to different + # small/medium/large mask AP results. + for x in predictions: + x.pop('bbox') + coco_dt = self._coco_api.loadRes(predictions) + + except IndexError: + logger.error( + 'The testing results of the whole dataset is empty.') + break + + if self.use_mp_eval: + coco_eval = COCOevalMP(self._coco_api, coco_dt, iou_type) + else: + coco_eval = COCOeval(self._coco_api, coco_dt, iou_type) + + coco_eval.params.catIds = self.cat_ids + coco_eval.params.imgIds = self.img_ids + coco_eval.params.maxDets = list(self.proposal_nums) + coco_eval.params.iouThrs = self.iou_thrs + + # mapping of cocoEval.stats + coco_metric_names = { + 'mAP': 0, + 'mAP_50': 1, + 'mAP_75': 2, + 'mAP_s': 3, + 'mAP_m': 4, + 'mAP_l': 5, + 'AR@100': 6, + 'AR@300': 7, + 'AR@1000': 8, + 'AR_s@1000': 9, + 'AR_m@1000': 10, + 'AR_l@1000': 11 + } + metric_items = self.metric_items + if metric_items is not None: + for metric_item in metric_items: + if metric_item not in coco_metric_names: + raise KeyError( + f'metric item "{metric_item}" is not supported') + + if metric == 'proposal': + coco_eval.params.useCats = 0 + coco_eval.evaluate() + coco_eval.accumulate() + coco_eval.summarize() + if metric_items is None: + metric_items = [ + 'AR@100', 'AR@300', 'AR@1000', 'AR_s@1000', + 'AR_m@1000', 'AR_l@1000' + ] + + for item in metric_items: + val = float( + f'{coco_eval.stats[coco_metric_names[item]]:.3f}') + eval_results[item] = val + else: + coco_eval.evaluate() + coco_eval.accumulate() + coco_eval.summarize() + if self.classwise: # Compute per-category AP + # Compute per-category AP + # from https://github.com/facebookresearch/detectron2/ + precisions = coco_eval.eval['precision'] + # precision: (iou, recall, cls, area range, max dets) + assert len(self.cat_ids) == precisions.shape[2] + + results_per_category = [] + for idx, cat_id in enumerate(self.cat_ids): + t = [] + # area range index 0: all area ranges + # max dets index -1: typically 100 per image + nm = self._coco_api.loadCats(cat_id)[0] + precision = precisions[:, :, idx, 0, -1] + precision = precision[precision > -1] + if precision.size: + ap = np.mean(precision) + else: + ap = float('nan') + t.append(f'{nm["name"]}') + t.append(f'{round(ap, 3)}') + eval_results[f'{nm["name"]}_precision'] = round(ap, 3) + + # indexes of IoU @50 and @75 + for iou in [0, 5]: + precision = precisions[iou, :, idx, 0, -1] + precision = precision[precision > -1] + if precision.size: + ap = np.mean(precision) + else: + ap = float('nan') + t.append(f'{round(ap, 3)}') + + # indexes of area of small, median and large + for area in [1, 2, 3]: + precision = precisions[:, :, idx, area, -1] + precision = precision[precision > -1] + if precision.size: + ap = np.mean(precision) + else: + ap = float('nan') + t.append(f'{round(ap, 3)}') + results_per_category.append(tuple(t)) + + num_columns = len(results_per_category[0]) + results_flatten = list( + itertools.chain(*results_per_category)) + headers = [ + 'category', 'mAP', 'mAP_50', 'mAP_75', 'mAP_s', + 'mAP_m', 'mAP_l' + ] + results_2d = itertools.zip_longest(*[ + results_flatten[i::num_columns] + for i in range(num_columns) + ]) + table_data = [headers] + table_data += [result for result in results_2d] + table = AsciiTable(table_data) + logger.info('\n' + table.table) + + # ------------get novel_ap50 and base_ap50--------- + precisions = coco_eval.eval['precision'] + assert len(self.cat_ids) == precisions.shape[2] + base_inds, novel_inds = [], [] + + for idx, catId in enumerate(self.cat_ids): + if catId in self.base_cat_ids: + base_inds.append(idx) + if catId in self.novel_cat_ids: + novel_inds.append(idx) + + base_ap = precisions[:, :, base_inds, 0, -1] + novel_ap = precisions[:, :, novel_inds, 0, -1] + base_ap50 = precisions[0, :, base_inds, 0, -1] + novel_ap50 = precisions[0, :, novel_inds, 0, -1] + + eval_results['base_ap'] = np.mean( + base_ap[base_ap > -1]) if len( + base_ap[base_ap > -1]) else -1 + eval_results['novel_ap'] = np.mean( + novel_ap[novel_ap > -1]) if len( + novel_ap[novel_ap > -1]) else -1 + eval_results['base_ap50'] = np.mean( + base_ap50[base_ap50 > -1]) if len( + base_ap50[base_ap50 > -1]) else -1 + eval_results['novel_ap50'] = np.mean( + novel_ap50[novel_ap50 > -1]) if len( + novel_ap50[novel_ap50 > -1]) else -1 + # ------------get novel_ap50 and base_ap50--------- + if metric_items is None: + metric_items = [ + 'mAP', 'mAP_50', 'mAP_75', 'mAP_s', 'mAP_m', 'mAP_l' + ] + + for metric_item in metric_items: + key = f'{metric}_{metric_item}' + val = coco_eval.stats[coco_metric_names[metric_item]] + eval_results[key] = float(f'{round(val, 3)}') + + ap = coco_eval.stats[:6] + logger.info(f'{metric}_mAP_copypaste: {ap[0]:.3f} ' + f'{ap[1]:.3f} {ap[2]:.3f} {ap[3]:.3f} ' + f'{ap[4]:.3f} {ap[5]:.3f}') + + if tmp_dir is not None: + tmp_dir.cleanup() + return eval_results diff --git a/mmdetection/mmdet/evaluation/metrics/refexp_metric.py b/mmdetection/mmdet/evaluation/metrics/refexp_metric.py new file mode 100644 index 00000000..8bcdf162 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/refexp_metric.py @@ -0,0 +1,100 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, Optional, Sequence + +import numpy as np +from mmengine.evaluator import BaseMetric +from mmengine.fileio import get_local_path +from mmengine.logging import MMLogger + +from mmdet.datasets.api_wrappers import COCO +from mmdet.registry import METRICS +from ..functional import bbox_overlaps + + +@METRICS.register_module() +class RefExpMetric(BaseMetric): + default_prefix: Optional[str] = 'refexp' + + def __init__(self, + ann_file: Optional[str] = None, + metric: str = 'bbox', + topk=(1, 5, 10), + iou_thrs: float = 0.5, + **kwargs) -> None: + super().__init__(**kwargs) + self.metric = metric + self.topk = topk + self.iou_thrs = iou_thrs + + with get_local_path(ann_file) as local_path: + self.coco = COCO(local_path) + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + for data_sample in data_samples: + result = dict() + pred = data_sample['pred_instances'] + result['img_id'] = data_sample['img_id'] + result['bboxes'] = pred['bboxes'].cpu().numpy() + result['scores'] = pred['scores'].cpu().numpy() + self.results.append(result) + + def compute_metrics(self, results: list) -> Dict[str, float]: + logger: MMLogger = MMLogger.get_current_instance() + + dataset2score = { + 'refcoco': {k: 0.0 + for k in self.topk}, + 'refcoco+': {k: 0.0 + for k in self.topk}, + 'refcocog': {k: 0.0 + for k in self.topk}, + } + dataset2count = {'refcoco': 0.0, 'refcoco+': 0.0, 'refcocog': 0.0} + + for result in results: + img_id = result['img_id'] + + ann_ids = self.coco.getAnnIds(imgIds=img_id) + assert len(ann_ids) == 1 + img_info = self.coco.loadImgs(img_id)[0] + target = self.coco.loadAnns(ann_ids[0]) + + target_bbox = target[0]['bbox'] + converted_bbox = [ + target_bbox[0], + target_bbox[1], + target_bbox[2] + target_bbox[0], + target_bbox[3] + target_bbox[1], + ] + iou = bbox_overlaps(result['bboxes'], + np.array(converted_bbox).reshape(-1, 4)) + for k in self.topk: + if max(iou[:k]) >= self.iou_thrs: + dataset2score[img_info['dataset_name']][k] += 1.0 + dataset2count[img_info['dataset_name']] += 1.0 + + for key, value in dataset2score.items(): + for k in self.topk: + try: + value[k] /= dataset2count[key] + except Exception as e: + print(e) + + results = {} + mean_precision = 0.0 + for key, value in dataset2score.items(): + results[key] = sorted([v for k, v in value.items()]) + mean_precision += sum(results[key]) + logger.info( + f' Dataset: {key} - Precision @ 1, 5, 10: {results[key]}') + + # `mean_precision` key is used for saving the best checkpoint + out_results = {'mean_precision': mean_precision / 9.0} + + for i, k in enumerate(self.topk): + out_results[f'refcoco_precision@{k}'] = results['refcoco'][i] + for i, k in enumerate(self.topk): + out_results[f'refcoco+_precision@{k}'] = results['refcoco+'][i] + for i, k in enumerate(self.topk): + out_results[f'refcocog_precision@{k}'] = results['refcocog'][i] + return out_results diff --git a/mmdetection/mmdet/evaluation/metrics/refseg_metric.py b/mmdetection/mmdet/evaluation/metrics/refseg_metric.py new file mode 100644 index 00000000..0faee070 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/refseg_metric.py @@ -0,0 +1,63 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Sequence + +import torch +from mmengine.evaluator import BaseMetric + +from mmdet.registry import METRICS + + +@METRICS.register_module() +class RefSegMetric(BaseMetric): + """Referring Expression Segmentation Metric.""" + + def __init__(self, metric: Sequence = ('cIoU', 'mIoU'), **kwargs): + super().__init__(**kwargs) + assert set(metric).issubset(['cIoU', 'mIoU']), \ + f'Only support cIoU and mIoU, but got {metric}' + assert len(metric) > 0, 'metrics should not be empty' + self.metrics = metric + + def compute_iou(self, pred_seg: torch.Tensor, + gt_seg: torch.Tensor) -> tuple: + overlap = pred_seg & gt_seg + union = pred_seg | gt_seg + return overlap, union + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data and data_samples. + + The processed results should be stored in ``self.results``, which will + be used to compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of outputs from the model. + """ + for data_sample in data_samples: + pred_label = data_sample['pred_instances']['masks'].bool() + label = data_sample['gt_masks'].to_tensor( + pred_label.dtype, pred_label.device).bool() + # calculate iou + overlap, union = self.compute_iou(pred_label, label) + + bs = len(pred_label) + iou = overlap.reshape(bs, -1).sum(-1) * 1.0 / union.reshape( + bs, -1).sum(-1) + iou = torch.nan_to_num_(iou, nan=0.0) + self.results.append((overlap.sum(), union.sum(), iou.sum(), bs)) + + def compute_metrics(self, results: list) -> dict: + results = tuple(zip(*results)) + assert len(results) == 4 + cum_i = sum(results[0]) + cum_u = sum(results[1]) + iou = sum(results[2]) + seg_total = sum(results[3]) + + metrics = {} + if 'cIoU' in self.metrics: + metrics['cIoU'] = cum_i * 100 / cum_u + if 'mIoU' in self.metrics: + metrics['mIoU'] = iou * 100 / seg_total + return metrics diff --git a/mmdetection/mmdet/evaluation/metrics/reid_metric.py b/mmdetection/mmdet/evaluation/metrics/reid_metric.py new file mode 100644 index 00000000..d74df143 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/reid_metric.py @@ -0,0 +1,138 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence, Union + +import numpy as np +import torch +from mmengine.evaluator import BaseMetric + +from mmdet.registry import METRICS + + +@METRICS.register_module() +class ReIDMetrics(BaseMetric): + """mAP and CMC evaluation metrics for the ReID task. + + Args: + metric (str | list[str]): Metrics to be evaluated. + Default value is `mAP`. + metric_options: (dict, optional): Options for calculating metrics. + Allowed keys are 'rank_list' and 'max_rank'. Defaults to None. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Default: None + """ + allowed_metrics = ['mAP', 'CMC'] + default_prefix: Optional[str] = 'reid-metric' + + def __init__(self, + metric: Union[str, Sequence[str]] = 'mAP', + metric_options: Optional[dict] = None, + collect_device: str = 'cpu', + prefix: Optional[str] = None) -> None: + super().__init__(collect_device, prefix) + + if isinstance(metric, list): + metrics = metric + elif isinstance(metric, str): + metrics = [metric] + else: + raise TypeError('metric must be a list or a str.') + for metric in metrics: + if metric not in self.allowed_metrics: + raise KeyError(f'metric {metric} is not supported.') + self.metrics = metrics + + self.metric_options = metric_options or dict( + rank_list=[1, 5, 10, 20], max_rank=20) + for rank in self.metric_options['rank_list']: + assert 1 <= rank <= self.metric_options['max_rank'] + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. + + The processed results should be stored in ``self.results``, which will + be used to compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + pred_feature = data_sample['pred_feature'] + assert isinstance(pred_feature, torch.Tensor) + gt_label = data_sample.get('gt_label', data_sample['gt_label']) + assert isinstance(gt_label['label'], torch.Tensor) + result = dict( + pred_feature=pred_feature.data.cpu(), + gt_label=gt_label['label'].cpu()) + self.results.append(result) + + def compute_metrics(self, results: list) -> dict: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + dict: The computed metrics. The keys are the names of the metrics, + and the values are corresponding results. + """ + # NOTICE: don't access `self.results` from the method. + metrics = {} + + pids = torch.cat([result['gt_label'] for result in results]).numpy() + features = torch.stack([result['pred_feature'] for result in results]) + + n, c = features.size() + mat = torch.pow(features, 2).sum(dim=1, keepdim=True).expand(n, n) + distmat = mat + mat.t() + distmat.addmm_(features, features.t(), beta=1, alpha=-2) + distmat = distmat.numpy() + + indices = np.argsort(distmat, axis=1) + matches = (pids[indices] == pids[:, np.newaxis]).astype(np.int32) + + all_cmc = [] + all_AP = [] + num_valid_q = 0. + for q_idx in range(n): + # remove self + raw_cmc = matches[q_idx][1:] + if not np.any(raw_cmc): + # this condition is true when query identity + # does not appear in gallery + continue + + cmc = raw_cmc.cumsum() + cmc[cmc > 1] = 1 + + all_cmc.append(cmc[:self.metric_options['max_rank']]) + num_valid_q += 1. + + # compute average precision + num_rel = raw_cmc.sum() + tmp_cmc = raw_cmc.cumsum() + tmp_cmc = [x / (i + 1.) for i, x in enumerate(tmp_cmc)] + tmp_cmc = np.asarray(tmp_cmc) * raw_cmc + AP = tmp_cmc.sum() / num_rel + all_AP.append(AP) + + assert num_valid_q > 0, \ + 'Error: all query identities do not appear in gallery' + + all_cmc = np.asarray(all_cmc) + all_cmc = all_cmc.sum(0) / num_valid_q + mAP = np.mean(all_AP) + + if 'mAP' in self.metrics: + metrics['mAP'] = np.around(mAP, decimals=3) + if 'CMC' in self.metrics: + for rank in self.metric_options['rank_list']: + metrics[f'R{rank}'] = np.around(all_cmc[rank - 1], decimals=3) + + return metrics diff --git a/mmdetection/mmdet/evaluation/metrics/semseg_metric.py b/mmdetection/mmdet/evaluation/metrics/semseg_metric.py new file mode 100644 index 00000000..3215f678 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/semseg_metric.py @@ -0,0 +1,279 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from collections import OrderedDict +from typing import Dict, Optional, Sequence, Union + +import numpy as np +import torch +from mmcv import imwrite +from mmengine.dist import is_main_process +from mmengine.evaluator import BaseMetric +from mmengine.logging import MMLogger, print_log +from mmengine.utils import mkdir_or_exist +from PIL import Image + +try: + from prettytable import PrettyTable +except ImportError: + PrettyTable = None + +from mmdet.registry import METRICS + + +@METRICS.register_module() +class SemSegMetric(BaseMetric): + """mIoU evaluation metric. + + Args: + iou_metrics (list[str] | str): Metrics to be calculated, the options + includes 'mIoU', 'mDice' and 'mFscore'. + beta (int): Determines the weight of recall in the combined score. + Default: 1. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + output_dir (str): The directory for output prediction. Defaults to + None. + format_only (bool): Only format result for results commit without + perform evaluation. It is useful when you want to save the result + to a specific format and submit it to the test server. + Defaults to False. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + """ + + def __init__(self, + iou_metrics: Sequence[str] = ['mIoU'], + beta: int = 1, + collect_device: str = 'cpu', + output_dir: Optional[str] = None, + format_only: bool = False, + backend_args: dict = None, + prefix: Optional[str] = None) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + + if isinstance(iou_metrics, str): + iou_metrics = [iou_metrics] + if not set(iou_metrics).issubset(set(['mIoU', 'mDice', 'mFscore'])): + raise KeyError(f'metrics {iou_metrics} is not supported. ' + f'Only supports mIoU/mDice/mFscore.') + self.metrics = iou_metrics + self.beta = beta + self.output_dir = output_dir + if self.output_dir and is_main_process(): + mkdir_or_exist(self.output_dir) + self.format_only = format_only + self.backend_args = backend_args + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data and data_samples. + + The processed results should be stored in ``self.results``, which will + be used to compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of outputs from the model. + """ + num_classes = len(self.dataset_meta['classes']) + for data_sample in data_samples: + pred_label = data_sample['pred_sem_seg']['sem_seg'].squeeze() + # format_only always for test dataset without ground truth + if not self.format_only: + label = data_sample['gt_sem_seg']['sem_seg'].squeeze().to( + pred_label) + ignore_index = data_sample['pred_sem_seg'].get( + 'ignore_index', 255) + self.results.append( + self._compute_pred_stats(pred_label, label, num_classes, + ignore_index)) + + # format_result + if self.output_dir is not None: + basename = osp.splitext(osp.basename( + data_sample['img_path']))[0] + png_filename = osp.abspath( + osp.join(self.output_dir, f'{basename}.png')) + output_mask = pred_label.cpu().numpy() + output = Image.fromarray(output_mask.astype(np.uint8)) + imwrite(output, png_filename, backend_args=self.backend_args) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. The key + mainly includes aAcc, mIoU, mAcc, mDice, mFscore, mPrecision, + mRecall. + """ + logger: MMLogger = MMLogger.get_current_instance() + if self.format_only: + logger.info(f'results are saved to {osp.dirname(self.output_dir)}') + return OrderedDict() + + ret_metrics = self.get_return_metrics(results) + + # summary table + ret_metrics_summary = OrderedDict({ + ret_metric: np.round(np.nanmean(ret_metric_value) * 100, 2) + for ret_metric, ret_metric_value in ret_metrics.items() + }) + metrics = dict() + for key, val in ret_metrics_summary.items(): + if key == 'aAcc': + metrics[key] = val + else: + metrics['m' + key] = val + + print_semantic_table(ret_metrics, self.dataset_meta['classes'], logger) + + return metrics + + def _compute_pred_stats(self, pred_label: torch.tensor, + label: torch.tensor, num_classes: int, + ignore_index: int): + """Parse semantic segmentation predictions. + + Args: + pred_label (torch.tensor): Prediction segmentation map + or predict result filename. The shape is (H, W). + label (torch.tensor): Ground truth segmentation map + or label filename. The shape is (H, W). + num_classes (int): Number of categories. + + Returns: + torch.Tensor: The intersection of prediction and ground truth + histogram on all classes. + torch.Tensor: The union of prediction and ground truth histogram on + all classes. + torch.Tensor: The prediction histogram on all classes. + torch.Tensor: The ground truth histogram on all classes. + """ + assert pred_label.shape == label.shape + mask = label != ignore_index + label, pred_label = label[mask], pred_label[mask] + + intersect = pred_label[pred_label == label] + area_intersect = torch.histc( + intersect.float(), bins=num_classes, min=0, max=num_classes - 1) + area_pred_label = torch.histc( + pred_label.float(), bins=num_classes, min=0, max=num_classes - 1) + area_label = torch.histc( + label.float(), bins=num_classes, min=0, max=num_classes - 1) + area_union = area_pred_label + area_label - area_intersect + result = dict( + area_intersect=area_intersect, + area_union=area_union, + area_pred_label=area_pred_label, + area_label=area_label) + return result + + def get_return_metrics(self, results: list) -> dict: + """Calculate evaluation metrics. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, np.ndarray]: per category evaluation metrics, + shape (num_classes, ). + """ + + def f_score(precision, recall, beta=1): + """calculate the f-score value. + + Args: + precision (float | torch.Tensor): The precision value. + recall (float | torch.Tensor): The recall value. + beta (int): Determines the weight of recall in the combined + score. Default: 1. + + Returns: + [torch.tensor]: The f-score value. + """ + score = (1 + beta**2) * (precision * recall) / ( + (beta**2 * precision) + recall) + return score + + total_area_intersect = sum([r['area_intersect'] for r in results]) + total_area_union = sum([r['area_union'] for r in results]) + total_area_pred_label = sum([r['area_pred_label'] for r in results]) + total_area_label = sum([r['area_label'] for r in results]) + + all_acc = total_area_intersect / total_area_label + ret_metrics = OrderedDict({'aAcc': all_acc}) + for metric in self.metrics: + if metric == 'mIoU': + iou = total_area_intersect / total_area_union + acc = total_area_intersect / total_area_label + ret_metrics['IoU'] = iou + ret_metrics['Acc'] = acc + elif metric == 'mDice': + dice = 2 * total_area_intersect / ( + total_area_pred_label + total_area_label) + acc = total_area_intersect / total_area_label + ret_metrics['Dice'] = dice + ret_metrics['Acc'] = acc + elif metric == 'mFscore': + precision = total_area_intersect / total_area_pred_label + recall = total_area_intersect / total_area_label + f_value = torch.tensor([ + f_score(x[0], x[1], self.beta) + for x in zip(precision, recall) + ]) + ret_metrics['Fscore'] = f_value + ret_metrics['Precision'] = precision + ret_metrics['Recall'] = recall + + ret_metrics = { + metric: value.cpu().numpy() + for metric, value in ret_metrics.items() + } + + return ret_metrics + + +def print_semantic_table( + results: dict, + class_names: list, + logger: Optional[Union['MMLogger', str]] = None) -> None: + """Print semantic segmentation evaluation results table. + + Args: + results (dict): The evaluation results. + class_names (list): Class names. + logger (MMLogger | str, optional): Logger used for printing. + Default: None. + """ + # each class table + results.pop('aAcc', None) + ret_metrics_class = OrderedDict({ + ret_metric: np.round(ret_metric_value * 100, 2) + for ret_metric, ret_metric_value in results.items() + }) + + print_log('per class results:', logger) + if PrettyTable: + class_table_data = PrettyTable() + ret_metrics_class.update({'Class': class_names}) + ret_metrics_class.move_to_end('Class', last=False) + for key, val in ret_metrics_class.items(): + class_table_data.add_column(key, val) + print_log('\n' + class_table_data.get_string(), logger=logger) + else: + logger.warning( + '`prettytable` is not installed, for better table format, ' + 'please consider installing it with "pip install prettytable"') + print_result = {} + for class_name, iou, acc in zip(class_names, ret_metrics_class['IoU'], + ret_metrics_class['Acc']): + print_result[class_name] = {'IoU': iou, 'Acc': acc} + print_log(print_result, logger) diff --git a/mmdetection/mmdet/evaluation/metrics/voc_metric.py b/mmdetection/mmdet/evaluation/metrics/voc_metric.py new file mode 100644 index 00000000..32d8c075 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/voc_metric.py @@ -0,0 +1,176 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import warnings +from collections import OrderedDict +from typing import List, Optional, Sequence, Union + +import numpy as np +from mmengine.evaluator import BaseMetric +from mmengine.logging import MMLogger + +from mmdet.registry import METRICS +from ..functional import eval_map, eval_recalls + + +@METRICS.register_module() +class VOCMetric(BaseMetric): + """Pascal VOC evaluation metric. + + Args: + iou_thrs (float or List[float]): IoU threshold. Defaults to 0.5. + scale_ranges (List[tuple], optional): Scale ranges for evaluating + mAP. If not specified, all bounding boxes would be included in + evaluation. Defaults to None. + metric (str | list[str]): Metrics to be evaluated. Options are + 'mAP', 'recall'. If is list, the first setting in the list will + be used to evaluate metric. + proposal_nums (Sequence[int]): Proposal number used for evaluating + recalls, such as recall@100, recall@1000. + Default: (100, 300, 1000). + eval_mode (str): 'area' or '11points', 'area' means calculating the + area under precision-recall curve, '11points' means calculating + the average precision of recalls at [0, 0.1, ..., 1]. + The PASCAL VOC2007 defaults to use '11points', while PASCAL + VOC2012 defaults to use 'area'. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + """ + + default_prefix: Optional[str] = 'pascal_voc' + + def __init__(self, + iou_thrs: Union[float, List[float]] = 0.5, + scale_ranges: Optional[List[tuple]] = None, + metric: Union[str, List[str]] = 'mAP', + proposal_nums: Sequence[int] = (100, 300, 1000), + eval_mode: str = '11points', + collect_device: str = 'cpu', + prefix: Optional[str] = None) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + self.iou_thrs = [iou_thrs] if isinstance(iou_thrs, float) \ + else iou_thrs + self.scale_ranges = scale_ranges + # voc evaluation metrics + if not isinstance(metric, str): + assert len(metric) == 1 + metric = metric[0] + allowed_metrics = ['recall', 'mAP'] + if metric not in allowed_metrics: + raise KeyError( + f"metric should be one of 'recall', 'mAP', but got {metric}.") + self.metric = metric + self.proposal_nums = proposal_nums + assert eval_mode in ['area', '11points'], \ + 'Unrecognized mode, only "area" and "11points" are supported' + self.eval_mode = eval_mode + + # TODO: data_batch is no longer needed, consider adjusting the + # parameter position + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + gt = copy.deepcopy(data_sample) + # TODO: Need to refactor to support LoadAnnotations + gt_instances = gt['gt_instances'] + gt_ignore_instances = gt['ignored_instances'] + ann = dict( + labels=gt_instances['labels'].cpu().numpy(), + bboxes=gt_instances['bboxes'].cpu().numpy(), + bboxes_ignore=gt_ignore_instances['bboxes'].cpu().numpy(), + labels_ignore=gt_ignore_instances['labels'].cpu().numpy()) + + pred = data_sample['pred_instances'] + pred_bboxes = pred['bboxes'].cpu().numpy() + pred_scores = pred['scores'].cpu().numpy() + pred_labels = pred['labels'].cpu().numpy() + + dets = [] + for label in range(len(self.dataset_meta['classes'])): + index = np.where(pred_labels == label)[0] + pred_bbox_scores = np.hstack( + [pred_bboxes[index], pred_scores[index].reshape((-1, 1))]) + dets.append(pred_bbox_scores) + + self.results.append((ann, dets)) + + def compute_metrics(self, results: list) -> dict: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + dict: The computed metrics. The keys are the names of the metrics, + and the values are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + gts, preds = zip(*results) + eval_results = OrderedDict() + if self.metric == 'mAP': + assert isinstance(self.iou_thrs, list) + dataset_type = self.dataset_meta.get('dataset_type') + if dataset_type in ['VOC2007', 'VOC2012']: + dataset_name = 'voc' + if dataset_type == 'VOC2007' and self.eval_mode != '11points': + warnings.warn('Pascal VOC2007 uses `11points` as default ' + 'evaluate mode, but you are using ' + f'{self.eval_mode}.') + elif dataset_type == 'VOC2012' and self.eval_mode != 'area': + warnings.warn('Pascal VOC2012 uses `area` as default ' + 'evaluate mode, but you are using ' + f'{self.eval_mode}.') + else: + dataset_name = self.dataset_meta['classes'] + + mean_aps = [] + for iou_thr in self.iou_thrs: + logger.info(f'\n{"-" * 15}iou_thr: {iou_thr}{"-" * 15}') + # Follow the official implementation, + # http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCdevkit_18-May-2011.tar + # we should use the legacy coordinate system in mmdet 1.x, + # which means w, h should be computed as 'x2 - x1 + 1` and + # `y2 - y1 + 1` + mean_ap, _ = eval_map( + preds, + gts, + scale_ranges=self.scale_ranges, + iou_thr=iou_thr, + dataset=dataset_name, + logger=logger, + eval_mode=self.eval_mode, + use_legacy_coordinate=True) + mean_aps.append(mean_ap) + eval_results[f'AP{int(iou_thr * 100):02d}'] = round(mean_ap, 3) + eval_results['mAP'] = sum(mean_aps) / len(mean_aps) + eval_results.move_to_end('mAP', last=False) + elif self.metric == 'recall': + gt_bboxes = [gt['bboxes'] for gt in gts] + pr_bboxes = [pred[0] for pred in preds] + recalls = eval_recalls( + gt_bboxes, + pr_bboxes, + self.proposal_nums, + self.iou_thrs, + logger=logger, + use_legacy_coordinate=True) + for i, num in enumerate(self.proposal_nums): + for j, iou_thr in enumerate(self.iou_thrs): + eval_results[f'recall@{num}@{iou_thr}'] = recalls[i, j] + if recalls.shape[1] > 1: + ar = recalls.mean(axis=1) + for i, num in enumerate(self.proposal_nums): + eval_results[f'AR@{num}'] = ar[i] + return eval_results diff --git a/mmdetection/mmdet/evaluation/metrics/youtube_vis_metric.py b/mmdetection/mmdet/evaluation/metrics/youtube_vis_metric.py new file mode 100644 index 00000000..5abc77a5 --- /dev/null +++ b/mmdetection/mmdet/evaluation/metrics/youtube_vis_metric.py @@ -0,0 +1,426 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import tempfile +import warnings +import zipfile +from collections import OrderedDict, defaultdict +from typing import Dict, List, Optional, Sequence, Tuple, Union + +import mmengine +import numpy as np +from mmengine.dist import (all_gather_object, barrier, broadcast_object_list, + is_main_process) +from mmengine.logging import MMLogger + +from mmdet.registry import METRICS +from mmdet.structures.mask import encode_mask_results +from ..functional import YTVIS, YTVISeval +from .base_video_metric import BaseVideoMetric, collect_tracking_results + + +@METRICS.register_module() +class YouTubeVISMetric(BaseVideoMetric): + """mAP evaluation metrics for the VIS task. + + Args: + metric (str | list[str]): Metrics to be evaluated. + Default value is `youtube_vis_ap`. + metric_items (List[str], optional): Metric result names to be + recorded in the evaluation result. Defaults to None. + outfile_prefix (str | None): The prefix of json files. It includes + the file path and the prefix of filename, e.g., "a/b/prefix". + If not specified, a temp file will be created. Defaults to None. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonyms metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Default: None + format_only (bool): If True, only formatting the results to the + official format and not performing evaluation. Defaults to False. + """ + + default_prefix: Optional[str] = 'youtube_vis' + + def __init__(self, + metric: Union[str, List[str]] = 'youtube_vis_ap', + metric_items: Optional[Sequence[str]] = None, + outfile_prefix: Optional[str] = None, + collect_device: str = 'cpu', + prefix: Optional[str] = None, + format_only: bool = False) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + # vis evaluation metrics + self.metrics = metric if isinstance(metric, list) else [metric] + self.format_only = format_only + allowed_metrics = ['youtube_vis_ap'] + for metric in self.metrics: + if metric not in allowed_metrics: + raise KeyError( + f"metric should be 'youtube_vis_ap', but got {metric}.") + + self.metric_items = metric_items + self.outfile_prefix = outfile_prefix + self.per_video_res = [] + self.categories = [] + self._vis_meta_info = defaultdict(list) # record video and image infos + + def process_video(self, data_samples): + + video_length = len(data_samples) + for frame_id in range(video_length): + result = dict() + img_data_sample = data_samples[frame_id].to_dict() + pred = img_data_sample['pred_track_instances'] + video_id = img_data_sample['video_id'] + + result['img_id'] = img_data_sample['img_id'] + result['bboxes'] = pred['bboxes'].cpu().numpy() + result['scores'] = pred['scores'].cpu().numpy() + result['labels'] = pred['labels'].cpu().numpy() + result['instances_id'] = pred['instances_id'].cpu().numpy() + # encode mask to RLE + assert 'masks' in pred, \ + 'masks must exist in YouTube-VIS metric' + result['masks'] = encode_mask_results( + pred['masks'].detach().cpu().numpy()) + + # parse gt + gt = dict() + gt['width'] = img_data_sample['ori_shape'][1] + gt['height'] = img_data_sample['ori_shape'][0] + gt['img_id'] = img_data_sample['img_id'] + gt['frame_id'] = frame_id + gt['video_id'] = video_id + gt['video_length'] = video_length + + if 'instances' in img_data_sample: + gt['anns'] = img_data_sample['instances'] + else: + gt['anns'] = dict() + self.per_video_res.append((result, gt)) + + preds, gts = zip(*self.per_video_res) + # format the results + # we must format gts first to update self._vis_meta_info + gt_results = self._format_one_video_gts(gts) + pred_results = self._format_one_video_preds(preds) + self.per_video_res.clear() + # add converted result to the results list + self.results.append((pred_results, gt_results)) + + def compute_metrics(self, results: List) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (List): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. + """ + # split gt and prediction list + tmp_pred_results, tmp_gt_results = zip(*results) + gt_results = self.format_gts(tmp_gt_results) + pred_results = self.format_preds(tmp_pred_results) + + if self.format_only: + self.save_pred_results(pred_results) + return dict() + + ytvis = YTVIS(gt_results) + + ytvis_dets = ytvis.loadRes(pred_results) + vid_ids = ytvis.getVidIds() + + iou_type = metric = 'segm' + eval_results = OrderedDict() + ytvisEval = YTVISeval(ytvis, ytvis_dets, iou_type) + ytvisEval.params.vidIds = vid_ids + ytvisEval.evaluate() + ytvisEval.accumulate() + ytvisEval.summarize() + + coco_metric_names = { + 'mAP': 0, + 'mAP_50': 1, + 'mAP_75': 2, + 'mAP_s': 3, + 'mAP_m': 4, + 'mAP_l': 5, + 'AR@1': 6, + 'AR@10': 7, + 'AR@100': 8, + 'AR_s@100': 9, + 'AR_m@100': 10, + 'AR_l@100': 11 + } + metric_items = self.metric_items + if metric_items is not None: + for metric_item in metric_items: + if metric_item not in coco_metric_names: + raise KeyError( + f'metric item "{metric_item}" is not supported') + + if metric_items is None: + metric_items = [ + 'mAP', 'mAP_50', 'mAP_75', 'mAP_s', 'mAP_m', 'mAP_l' + ] + for metric_item in metric_items: + key = f'{metric}_{metric_item}' + val = float( + f'{ytvisEval.stats[coco_metric_names[metric_item]]:.3f}') + eval_results[key] = val + + return eval_results + + def format_gts(self, gts: Tuple[List]) -> dict: + """Gather all ground-truth from self.results.""" + self.categories = [ + dict(id=id + 1, name=name) + for id, name in enumerate(self.dataset_meta['classes']) + ] + gt_results = dict( + categories=self.categories, + videos=self._vis_meta_info['videos'], + annotations=[]) + for gt_result in gts: + gt_results['annotations'].extend(gt_result) + return gt_results + + def format_preds(self, preds: Tuple[List]) -> List: + """Gather all predictions from self.results.""" + pred_results = [] + for pred_result in preds: + pred_results.extend(pred_result) + return pred_results + + def _format_one_video_preds(self, pred_dicts: Tuple[dict]) -> List: + """Convert the annotation to the format of YouTube-VIS. + + This operation is to make it easier to use the official eval API. + + Args: + pred_dicts (Tuple[dict]): Prediction of the dataset. + + Returns: + List: The formatted predictions. + """ + # Collate preds scatters (tuple of dict to dict of list) + preds = defaultdict(list) + for pred in pred_dicts: + for key in pred.keys(): + preds[key].append(pred[key]) + + img_infos = self._vis_meta_info['images'] + vid_infos = self._vis_meta_info['videos'] + inds = [i for i, _ in enumerate(img_infos) if _['frame_id'] == 0] + inds.append(len(img_infos)) + json_results = [] + video_id = vid_infos[-1]['id'] + # collect data for each instances in a video. + collect_data = dict() + for frame_id, (masks, scores, labels, ids) in enumerate( + zip(preds['masks'], preds['scores'], preds['labels'], + preds['instances_id'])): + + assert len(masks) == len(labels) + for j, id in enumerate(ids): + if id not in collect_data: + collect_data[id] = dict( + category_ids=[], scores=[], segmentations=dict()) + collect_data[id]['category_ids'].append(labels[j]) + collect_data[id]['scores'].append(scores[j]) + if isinstance(masks[j]['counts'], bytes): + masks[j]['counts'] = masks[j]['counts'].decode() + collect_data[id]['segmentations'][frame_id] = masks[j] + + # transform the collected data into official format + for id, id_data in collect_data.items(): + output = dict() + output['video_id'] = video_id + output['score'] = np.array(id_data['scores']).mean().item() + # majority voting for sequence category + output['category_id'] = np.bincount( + np.array(id_data['category_ids'])).argmax().item() + 1 + output['segmentations'] = [] + for frame_id in range(inds[-1] - inds[-2]): + if frame_id in id_data['segmentations']: + output['segmentations'].append( + id_data['segmentations'][frame_id]) + else: + output['segmentations'].append(None) + json_results.append(output) + + return json_results + + def _format_one_video_gts(self, gt_dicts: Tuple[dict]) -> List: + """Convert the annotation to the format of YouTube-VIS. + + This operation is to make it easier to use the official eval API. + + Args: + gt_dicts (Tuple[dict]): Ground truth of the dataset. + + Returns: + list: The formatted gts. + """ + video_infos = [] + image_infos = [] + instance_infos = defaultdict(list) + len_videos = dict() # mapping from instance_id to video_length + vis_anns = [] + + # get video infos + for gt_dict in gt_dicts: + frame_id = gt_dict['frame_id'] + video_id = gt_dict['video_id'] + img_id = gt_dict['img_id'] + image_info = dict( + id=img_id, + width=gt_dict['width'], + height=gt_dict['height'], + frame_id=frame_id, + file_name='') + image_infos.append(image_info) + if frame_id == 0: + video_info = dict( + id=video_id, + width=gt_dict['width'], + height=gt_dict['height'], + file_name='') + video_infos.append(video_info) + + for ann in gt_dict['anns']: + label = ann['bbox_label'] + bbox = ann['bbox'] + instance_id = ann['instance_id'] + # update video length + len_videos[instance_id] = gt_dict['video_length'] + coco_bbox = [ + bbox[0], + bbox[1], + bbox[2] - bbox[0], + bbox[3] - bbox[1], + ] + + annotation = dict( + video_id=video_id, + frame_id=frame_id, + bbox=coco_bbox, + instance_id=instance_id, + iscrowd=ann.get('ignore_flag', 0), + category_id=int(label) + 1, + area=coco_bbox[2] * coco_bbox[3]) + if ann.get('mask', None): + mask = ann['mask'] + # area = mask_util.area(mask) + if isinstance(mask, dict) and isinstance( + mask['counts'], bytes): + mask['counts'] = mask['counts'].decode() + annotation['segmentation'] = mask + + instance_infos[instance_id].append(annotation) + + # update vis meta info + self._vis_meta_info['images'].extend(image_infos) + self._vis_meta_info['videos'].extend(video_infos) + + for instance_id, ann_infos in instance_infos.items(): + cur_video_len = len_videos[instance_id] + segm = [None] * cur_video_len + bbox = [None] * cur_video_len + area = [None] * cur_video_len + # In the official format, no instances are represented by + # 'None', however, only images with instances are recorded + # in the current annotations, so we need to use 'None' to + # initialize these lists. + for ann_info in ann_infos: + frame_id = ann_info['frame_id'] + segm[frame_id] = ann_info['segmentation'] + bbox[frame_id] = ann_info['bbox'] + area[frame_id] = ann_info['area'] + instance = dict( + category_id=ann_infos[0]['category_id'], + segmentations=segm, + bboxes=bbox, + video_id=ann_infos[0]['video_id'], + areas=area, + id=instance_id, + iscrowd=ann_infos[0]['iscrowd']) + vis_anns.append(instance) + return vis_anns + + def save_pred_results(self, pred_results: List) -> None: + """Save the results to a zip file (standard format for YouTube-VIS + Challenge). + + Args: + pred_results (list): Testing results of the + dataset. + """ + logger: MMLogger = MMLogger.get_current_instance() + if self.outfile_prefix is None: + tmp_dir = tempfile.TemporaryDirectory() + outfile_prefix = osp.join(tmp_dir.name, 'results') + else: + outfile_prefix = self.outfile_prefix + mmengine.dump(pred_results, f'{outfile_prefix}.json') + # zip the json file in order to submit to the test server. + zip_file_name = f'{outfile_prefix}.submission_file.zip' + zf = zipfile.ZipFile(zip_file_name, 'w', zipfile.ZIP_DEFLATED) + logger.info(f"zip the 'results.json' into '{zip_file_name}', " + 'please submmit the zip file to the test server') + zf.write(f'{outfile_prefix}.json', 'results.json') + zf.close() + + def evaluate(self, size: int) -> dict: + """Evaluate the model performance of the whole dataset after processing + all batches. + + Args: + size (int): Length of the entire validation dataset. + + Returns: + dict: Evaluation metrics dict on the val dataset. The keys are the + names of the metrics, and the values are corresponding results. + """ + # wait for all processes to complete prediction. + barrier() + + if len(self.results) == 0: + warnings.warn( + f'{self.__class__.__name__} got empty `self.results`. Please ' + 'ensure that the processed results are properly added into ' + '`self.results` in `process` method.') + + results = collect_tracking_results(self.results, self.collect_device) + + # gather seq_info + gathered_seq_info = all_gather_object(self._vis_meta_info['videos']) + all_seq_info = [] + for _seq_info in gathered_seq_info: + all_seq_info.extend(_seq_info) + # update self._vis_meta_info + self._vis_meta_info = dict(videos=all_seq_info) + + if is_main_process(): + _metrics = self.compute_metrics(results) # type: ignore + # Add prefix to metric names + if self.prefix: + _metrics = { + '/'.join((self.prefix, k)): v + for k, v in _metrics.items() + } + metrics = [_metrics] + else: + metrics = [None] # type: ignore + + broadcast_object_list(metrics) + + # reset the results list + self.results.clear() + # reset the vis_meta_info + self._vis_meta_info.clear() + return metrics[0] diff --git a/mmdetection/mmdet/models/__init__.py b/mmdetection/mmdet/models/__init__.py new file mode 100644 index 00000000..c0a0d5e8 --- /dev/null +++ b/mmdetection/mmdet/models/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .backbones import * # noqa: F401,F403 +from .data_preprocessors import * # noqa: F401,F403 +from .dense_heads import * # noqa: F401,F403 +from .detectors import * # noqa: F401,F403 +from .language_models import * # noqa: F401,F403 +from .layers import * # noqa: F401,F403 +from .losses import * # noqa: F401,F403 +from .mot import * # noqa: F401,F403 +from .necks import * # noqa: F401,F403 +from .reid import * # noqa: F401,F403 +from .roi_heads import * # noqa: F401,F403 +from .seg_heads import * # noqa: F401,F403 +from .task_modules import * # noqa: F401,F403 +from .test_time_augs import * # noqa: F401,F403 +from .trackers import * # noqa: F401,F403 +from .tracking_heads import * # noqa: F401,F403 +from .vis import * # noqa: F401,F403 diff --git a/mmdetection/mmdet/models/backbones/__init__.py b/mmdetection/mmdet/models/backbones/__init__.py new file mode 100644 index 00000000..e16ff85f --- /dev/null +++ b/mmdetection/mmdet/models/backbones/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .csp_darknet import CSPDarknet +from .cspnext import CSPNeXt +from .darknet import Darknet +from .detectors_resnet import DetectoRS_ResNet +from .detectors_resnext import DetectoRS_ResNeXt +from .efficientnet import EfficientNet +from .hourglass import HourglassNet +from .hrnet import HRNet +from .mobilenet_v2 import MobileNetV2 +from .pvt import PyramidVisionTransformer, PyramidVisionTransformerV2 +from .regnet import RegNet +from .res2net import Res2Net +from .resnest import ResNeSt +from .resnet import ResNet, ResNetV1d +from .resnext import ResNeXt +from .ssd_vgg import SSDVGG +from .swin import SwinTransformer +from .trident_resnet import TridentResNet + +__all__ = [ + 'RegNet', 'ResNet', 'ResNetV1d', 'ResNeXt', 'SSDVGG', 'HRNet', + 'MobileNetV2', 'Res2Net', 'HourglassNet', 'DetectoRS_ResNet', + 'DetectoRS_ResNeXt', 'Darknet', 'ResNeSt', 'TridentResNet', 'CSPDarknet', + 'SwinTransformer', 'PyramidVisionTransformer', + 'PyramidVisionTransformerV2', 'EfficientNet', 'CSPNeXt' +] diff --git a/mmdetection/mmdet/models/backbones/csp_darknet.py b/mmdetection/mmdet/models/backbones/csp_darknet.py new file mode 100644 index 00000000..a890b486 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/csp_darknet.py @@ -0,0 +1,286 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmengine.model import BaseModule +from torch.nn.modules.batchnorm import _BatchNorm + +from mmdet.registry import MODELS +from ..layers import CSPLayer + + +class Focus(nn.Module): + """Focus width and height information into channel space. + + Args: + in_channels (int): The input channels of this Module. + out_channels (int): The output channels of this Module. + kernel_size (int): The kernel size of the convolution. Default: 1 + stride (int): The stride of the convolution. Default: 1 + conv_cfg (dict): Config dict for convolution layer. Default: None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN', momentum=0.03, eps=0.001). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='Swish'). + """ + + def __init__(self, + in_channels, + out_channels, + kernel_size=1, + stride=1, + conv_cfg=None, + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish')): + super().__init__() + self.conv = ConvModule( + in_channels * 4, + out_channels, + kernel_size, + stride, + padding=(kernel_size - 1) // 2, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + # shape of x (b,c,w,h) -> y(b,4c,w/2,h/2) + patch_top_left = x[..., ::2, ::2] + patch_top_right = x[..., ::2, 1::2] + patch_bot_left = x[..., 1::2, ::2] + patch_bot_right = x[..., 1::2, 1::2] + x = torch.cat( + ( + patch_top_left, + patch_bot_left, + patch_top_right, + patch_bot_right, + ), + dim=1, + ) + return self.conv(x) + + +class SPPBottleneck(BaseModule): + """Spatial pyramid pooling layer used in YOLOv3-SPP. + + Args: + in_channels (int): The input channels of this Module. + out_channels (int): The output channels of this Module. + kernel_sizes (tuple[int]): Sequential of kernel sizes of pooling + layers. Default: (5, 9, 13). + conv_cfg (dict): Config dict for convolution layer. Default: None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='Swish'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + kernel_sizes=(5, 9, 13), + conv_cfg=None, + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish'), + init_cfg=None): + super().__init__(init_cfg) + mid_channels = in_channels // 2 + self.conv1 = ConvModule( + in_channels, + mid_channels, + 1, + stride=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.poolings = nn.ModuleList([ + nn.MaxPool2d(kernel_size=ks, stride=1, padding=ks // 2) + for ks in kernel_sizes + ]) + conv2_channels = mid_channels * (len(kernel_sizes) + 1) + self.conv2 = ConvModule( + conv2_channels, + out_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + x = self.conv1(x) + with torch.cuda.amp.autocast(enabled=False): + x = torch.cat( + [x] + [pooling(x) for pooling in self.poolings], dim=1) + x = self.conv2(x) + return x + + +@MODELS.register_module() +class CSPDarknet(BaseModule): + """CSP-Darknet backbone used in YOLOv5 and YOLOX. + + Args: + arch (str): Architecture of CSP-Darknet, from {P5, P6}. + Default: P5. + deepen_factor (float): Depth multiplier, multiply number of + blocks in CSP layer by this amount. Default: 1.0. + widen_factor (float): Width multiplier, multiply number of + channels in each layer by this amount. Default: 1.0. + out_indices (Sequence[int]): Output from which stages. + Default: (2, 3, 4). + frozen_stages (int): Stages to be frozen (stop grad and set eval + mode). -1 means not freezing any parameters. Default: -1. + use_depthwise (bool): Whether to use depthwise separable convolution. + Default: False. + arch_ovewrite(list): Overwrite default arch settings. Default: None. + spp_kernal_sizes: (tuple[int]): Sequential of kernel sizes of SPP + layers. Default: (5, 9, 13). + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: dict(type='BN', requires_grad=True). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='LeakyReLU', negative_slope=0.1). + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Example: + >>> from mmdet.models import CSPDarknet + >>> import torch + >>> self = CSPDarknet(depth=53) + >>> self.eval() + >>> inputs = torch.rand(1, 3, 416, 416) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + ... + (1, 256, 52, 52) + (1, 512, 26, 26) + (1, 1024, 13, 13) + """ + # From left to right: + # in_channels, out_channels, num_blocks, add_identity, use_spp + arch_settings = { + 'P5': [[64, 128, 3, True, False], [128, 256, 9, True, False], + [256, 512, 9, True, False], [512, 1024, 3, False, True]], + 'P6': [[64, 128, 3, True, False], [128, 256, 9, True, False], + [256, 512, 9, True, False], [512, 768, 3, True, False], + [768, 1024, 3, False, True]] + } + + def __init__(self, + arch='P5', + deepen_factor=1.0, + widen_factor=1.0, + out_indices=(2, 3, 4), + frozen_stages=-1, + use_depthwise=False, + arch_ovewrite=None, + spp_kernal_sizes=(5, 9, 13), + conv_cfg=None, + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish'), + norm_eval=False, + init_cfg=dict( + type='Kaiming', + layer='Conv2d', + a=math.sqrt(5), + distribution='uniform', + mode='fan_in', + nonlinearity='leaky_relu')): + super().__init__(init_cfg) + arch_setting = self.arch_settings[arch] + if arch_ovewrite: + arch_setting = arch_ovewrite + assert set(out_indices).issubset( + i for i in range(len(arch_setting) + 1)) + if frozen_stages not in range(-1, len(arch_setting) + 1): + raise ValueError('frozen_stages must be in range(-1, ' + 'len(arch_setting) + 1). But received ' + f'{frozen_stages}') + + self.out_indices = out_indices + self.frozen_stages = frozen_stages + self.use_depthwise = use_depthwise + self.norm_eval = norm_eval + conv = DepthwiseSeparableConvModule if use_depthwise else ConvModule + + self.stem = Focus( + 3, + int(arch_setting[0][0] * widen_factor), + kernel_size=3, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.layers = ['stem'] + + for i, (in_channels, out_channels, num_blocks, add_identity, + use_spp) in enumerate(arch_setting): + in_channels = int(in_channels * widen_factor) + out_channels = int(out_channels * widen_factor) + num_blocks = max(round(num_blocks * deepen_factor), 1) + stage = [] + conv_layer = conv( + in_channels, + out_channels, + 3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + stage.append(conv_layer) + if use_spp: + spp = SPPBottleneck( + out_channels, + out_channels, + kernel_sizes=spp_kernal_sizes, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + stage.append(spp) + csp_layer = CSPLayer( + out_channels, + out_channels, + num_blocks=num_blocks, + add_identity=add_identity, + use_depthwise=use_depthwise, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + stage.append(csp_layer) + self.add_module(f'stage{i + 1}', nn.Sequential(*stage)) + self.layers.append(f'stage{i + 1}') + + def _freeze_stages(self): + if self.frozen_stages >= 0: + for i in range(self.frozen_stages + 1): + m = getattr(self, self.layers[i]) + m.eval() + for param in m.parameters(): + param.requires_grad = False + + def train(self, mode=True): + super(CSPDarknet, self).train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + if isinstance(m, _BatchNorm): + m.eval() + + def forward(self, x): + outs = [] + for i, layer_name in enumerate(self.layers): + layer = getattr(self, layer_name) + x = layer(x) + if i in self.out_indices: + outs.append(x) + return tuple(outs) diff --git a/mmdetection/mmdet/models/backbones/cspnext.py b/mmdetection/mmdet/models/backbones/cspnext.py new file mode 100644 index 00000000..269725a7 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/cspnext.py @@ -0,0 +1,195 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Sequence, Tuple + +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmengine.model import BaseModule +from torch import Tensor +from torch.nn.modules.batchnorm import _BatchNorm + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from ..layers import CSPLayer +from .csp_darknet import SPPBottleneck + + +@MODELS.register_module() +class CSPNeXt(BaseModule): + """CSPNeXt backbone used in RTMDet. + + Args: + arch (str): Architecture of CSPNeXt, from {P5, P6}. + Defaults to P5. + expand_ratio (float): Ratio to adjust the number of channels of the + hidden layer. Defaults to 0.5. + deepen_factor (float): Depth multiplier, multiply number of + blocks in CSP layer by this amount. Defaults to 1.0. + widen_factor (float): Width multiplier, multiply number of + channels in each layer by this amount. Defaults to 1.0. + out_indices (Sequence[int]): Output from which stages. + Defaults to (2, 3, 4). + frozen_stages (int): Stages to be frozen (stop grad and set eval + mode). -1 means not freezing any parameters. Defaults to -1. + use_depthwise (bool): Whether to use depthwise separable convolution. + Defaults to False. + arch_ovewrite (list): Overwrite default arch settings. + Defaults to None. + spp_kernel_sizes: (tuple[int]): Sequential of kernel sizes of SPP + layers. Defaults to (5, 9, 13). + channel_attention (bool): Whether to add channel attention in each + stage. Defaults to True. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict): Dictionary to construct and + config norm layer. Defaults to dict(type='BN', requires_grad=True). + act_cfg (:obj:`ConfigDict` or dict): Config dict for activation layer. + Defaults to dict(type='SiLU'). + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`]): Initialization config dict. + """ + # From left to right: + # in_channels, out_channels, num_blocks, add_identity, use_spp + arch_settings = { + 'P5': [[64, 128, 3, True, False], [128, 256, 6, True, False], + [256, 512, 6, True, False], [512, 1024, 3, False, True]], + 'P6': [[64, 128, 3, True, False], [128, 256, 6, True, False], + [256, 512, 6, True, False], [512, 768, 3, True, False], + [768, 1024, 3, False, True]] + } + + def __init__( + self, + arch: str = 'P5', + deepen_factor: float = 1.0, + widen_factor: float = 1.0, + out_indices: Sequence[int] = (2, 3, 4), + frozen_stages: int = -1, + use_depthwise: bool = False, + expand_ratio: float = 0.5, + arch_ovewrite: dict = None, + spp_kernel_sizes: Sequence[int] = (5, 9, 13), + channel_attention: bool = True, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN', momentum=0.03, eps=0.001), + act_cfg: ConfigType = dict(type='SiLU'), + norm_eval: bool = False, + init_cfg: OptMultiConfig = dict( + type='Kaiming', + layer='Conv2d', + a=math.sqrt(5), + distribution='uniform', + mode='fan_in', + nonlinearity='leaky_relu') + ) -> None: + super().__init__(init_cfg=init_cfg) + arch_setting = self.arch_settings[arch] + if arch_ovewrite: + arch_setting = arch_ovewrite + assert set(out_indices).issubset( + i for i in range(len(arch_setting) + 1)) + if frozen_stages not in range(-1, len(arch_setting) + 1): + raise ValueError('frozen_stages must be in range(-1, ' + 'len(arch_setting) + 1). But received ' + f'{frozen_stages}') + + self.out_indices = out_indices + self.frozen_stages = frozen_stages + self.use_depthwise = use_depthwise + self.norm_eval = norm_eval + conv = DepthwiseSeparableConvModule if use_depthwise else ConvModule + self.stem = nn.Sequential( + ConvModule( + 3, + int(arch_setting[0][0] * widen_factor // 2), + 3, + padding=1, + stride=2, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + int(arch_setting[0][0] * widen_factor // 2), + int(arch_setting[0][0] * widen_factor // 2), + 3, + padding=1, + stride=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + int(arch_setting[0][0] * widen_factor // 2), + int(arch_setting[0][0] * widen_factor), + 3, + padding=1, + stride=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.layers = ['stem'] + + for i, (in_channels, out_channels, num_blocks, add_identity, + use_spp) in enumerate(arch_setting): + in_channels = int(in_channels * widen_factor) + out_channels = int(out_channels * widen_factor) + num_blocks = max(round(num_blocks * deepen_factor), 1) + stage = [] + conv_layer = conv( + in_channels, + out_channels, + 3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + stage.append(conv_layer) + if use_spp: + spp = SPPBottleneck( + out_channels, + out_channels, + kernel_sizes=spp_kernel_sizes, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + stage.append(spp) + csp_layer = CSPLayer( + out_channels, + out_channels, + num_blocks=num_blocks, + add_identity=add_identity, + use_depthwise=use_depthwise, + use_cspnext_block=True, + expand_ratio=expand_ratio, + channel_attention=channel_attention, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + stage.append(csp_layer) + self.add_module(f'stage{i + 1}', nn.Sequential(*stage)) + self.layers.append(f'stage{i + 1}') + + def _freeze_stages(self) -> None: + if self.frozen_stages >= 0: + for i in range(self.frozen_stages + 1): + m = getattr(self, self.layers[i]) + m.eval() + for param in m.parameters(): + param.requires_grad = False + + def train(self, mode=True) -> None: + super().train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + if isinstance(m, _BatchNorm): + m.eval() + + def forward(self, x: Tuple[Tensor, ...]) -> Tuple[Tensor, ...]: + outs = [] + for i, layer_name in enumerate(self.layers): + layer = getattr(self, layer_name) + x = layer(x) + if i in self.out_indices: + outs.append(x) + return tuple(outs) diff --git a/mmdetection/mmdet/models/backbones/darknet.py b/mmdetection/mmdet/models/backbones/darknet.py new file mode 100644 index 00000000..1d44da1e --- /dev/null +++ b/mmdetection/mmdet/models/backbones/darknet.py @@ -0,0 +1,213 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Copyright (c) 2019 Western Digital Corporation or its affiliates. + +import warnings + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch.nn.modules.batchnorm import _BatchNorm + +from mmdet.registry import MODELS + + +class ResBlock(BaseModule): + """The basic residual block used in Darknet. Each ResBlock consists of two + ConvModules and the input is added to the final output. Each ConvModule is + composed of Conv, BN, and LeakyReLU. In YoloV3 paper, the first convLayer + has half of the number of the filters as much as the second convLayer. The + first convLayer has filter size of 1x1 and the second one has the filter + size of 3x3. + + Args: + in_channels (int): The input channels. Must be even. + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: dict(type='BN', requires_grad=True) + act_cfg (dict): Config dict for activation layer. + Default: dict(type='LeakyReLU', negative_slope=0.1). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + in_channels, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='LeakyReLU', negative_slope=0.1), + init_cfg=None): + super(ResBlock, self).__init__(init_cfg) + assert in_channels % 2 == 0 # ensure the in_channels is even + half_in_channels = in_channels // 2 + + # shortcut + cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + + self.conv1 = ConvModule(in_channels, half_in_channels, 1, **cfg) + self.conv2 = ConvModule( + half_in_channels, in_channels, 3, padding=1, **cfg) + + def forward(self, x): + residual = x + out = self.conv1(x) + out = self.conv2(out) + out = out + residual + + return out + + +@MODELS.register_module() +class Darknet(BaseModule): + """Darknet backbone. + + Args: + depth (int): Depth of Darknet. Currently only support 53. + out_indices (Sequence[int]): Output from which stages. + frozen_stages (int): Stages to be frozen (stop grad and set eval mode). + -1 means not freezing any parameters. Default: -1. + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: dict(type='BN', requires_grad=True) + act_cfg (dict): Config dict for activation layer. + Default: dict(type='LeakyReLU', negative_slope=0.1). + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + + Example: + >>> from mmdet.models import Darknet + >>> import torch + >>> self = Darknet(depth=53) + >>> self.eval() + >>> inputs = torch.rand(1, 3, 416, 416) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + ... + (1, 256, 52, 52) + (1, 512, 26, 26) + (1, 1024, 13, 13) + """ + + # Dict(depth: (layers, channels)) + arch_settings = { + 53: ((1, 2, 8, 8, 4), ((32, 64), (64, 128), (128, 256), (256, 512), + (512, 1024))) + } + + def __init__(self, + depth=53, + out_indices=(3, 4, 5), + frozen_stages=-1, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='LeakyReLU', negative_slope=0.1), + norm_eval=True, + pretrained=None, + init_cfg=None): + super(Darknet, self).__init__(init_cfg) + if depth not in self.arch_settings: + raise KeyError(f'invalid depth {depth} for darknet') + + self.depth = depth + self.out_indices = out_indices + self.frozen_stages = frozen_stages + self.layers, self.channels = self.arch_settings[depth] + + cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + + self.conv1 = ConvModule(3, 32, 3, padding=1, **cfg) + + self.cr_blocks = ['conv1'] + for i, n_layers in enumerate(self.layers): + layer_name = f'conv_res_block{i + 1}' + in_c, out_c = self.channels[i] + self.add_module( + layer_name, + self.make_conv_res_block(in_c, out_c, n_layers, **cfg)) + self.cr_blocks.append(layer_name) + + self.norm_eval = norm_eval + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + else: + raise TypeError('pretrained must be a str or None') + + def forward(self, x): + outs = [] + for i, layer_name in enumerate(self.cr_blocks): + cr_block = getattr(self, layer_name) + x = cr_block(x) + if i in self.out_indices: + outs.append(x) + + return tuple(outs) + + def _freeze_stages(self): + if self.frozen_stages >= 0: + for i in range(self.frozen_stages): + m = getattr(self, self.cr_blocks[i]) + m.eval() + for param in m.parameters(): + param.requires_grad = False + + def train(self, mode=True): + super(Darknet, self).train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + if isinstance(m, _BatchNorm): + m.eval() + + @staticmethod + def make_conv_res_block(in_channels, + out_channels, + res_repeat, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='LeakyReLU', + negative_slope=0.1)): + """In Darknet backbone, ConvLayer is usually followed by ResBlock. This + function will make that. The Conv layers always have 3x3 filters with + stride=2. The number of the filters in Conv layer is the same as the + out channels of the ResBlock. + + Args: + in_channels (int): The number of input channels. + out_channels (int): The number of output channels. + res_repeat (int): The number of ResBlocks. + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: dict(type='BN', requires_grad=True) + act_cfg (dict): Config dict for activation layer. + Default: dict(type='LeakyReLU', negative_slope=0.1). + """ + + cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + + model = nn.Sequential() + model.add_module( + 'conv', + ConvModule( + in_channels, out_channels, 3, stride=2, padding=1, **cfg)) + for idx in range(res_repeat): + model.add_module('res{}'.format(idx), + ResBlock(out_channels, **cfg)) + return model diff --git a/mmdetection/mmdet/models/backbones/detectors_resnet.py b/mmdetection/mmdet/models/backbones/detectors_resnet.py new file mode 100644 index 00000000..f33424fc --- /dev/null +++ b/mmdetection/mmdet/models/backbones/detectors_resnet.py @@ -0,0 +1,353 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn import build_conv_layer, build_norm_layer +from mmengine.logging import MMLogger +from mmengine.model import Sequential, constant_init, kaiming_init +from mmengine.runner.checkpoint import load_checkpoint +from torch.nn.modules.batchnorm import _BatchNorm + +from mmdet.registry import MODELS +from .resnet import BasicBlock +from .resnet import Bottleneck as _Bottleneck +from .resnet import ResNet + + +class Bottleneck(_Bottleneck): + r"""Bottleneck for the ResNet backbone in `DetectoRS + `_. + + This bottleneck allows the users to specify whether to use + SAC (Switchable Atrous Convolution) and RFP (Recursive Feature Pyramid). + + Args: + inplanes (int): The number of input channels. + planes (int): The number of output channels before expansion. + rfp_inplanes (int, optional): The number of channels from RFP. + Default: None. If specified, an additional conv layer will be + added for ``rfp_feat``. Otherwise, the structure is the same as + base class. + sac (dict, optional): Dictionary to construct SAC. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + expansion = 4 + + def __init__(self, + inplanes, + planes, + rfp_inplanes=None, + sac=None, + init_cfg=None, + **kwargs): + super(Bottleneck, self).__init__( + inplanes, planes, init_cfg=init_cfg, **kwargs) + + assert sac is None or isinstance(sac, dict) + self.sac = sac + self.with_sac = sac is not None + if self.with_sac: + self.conv2 = build_conv_layer( + self.sac, + planes, + planes, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + bias=False) + + self.rfp_inplanes = rfp_inplanes + if self.rfp_inplanes: + self.rfp_conv = build_conv_layer( + None, + self.rfp_inplanes, + planes * self.expansion, + 1, + stride=1, + bias=True) + if init_cfg is None: + self.init_cfg = dict( + type='Constant', val=0, override=dict(name='rfp_conv')) + + def rfp_forward(self, x, rfp_feat): + """The forward function that also takes the RFP features as input.""" + + def _inner_forward(x): + identity = x + + out = self.conv1(x) + out = self.norm1(out) + out = self.relu(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv1_plugin_names) + + out = self.conv2(out) + out = self.norm2(out) + out = self.relu(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv2_plugin_names) + + out = self.conv3(out) + out = self.norm3(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv3_plugin_names) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + if self.rfp_inplanes: + rfp_feat = self.rfp_conv(rfp_feat) + out = out + rfp_feat + + out = self.relu(out) + + return out + + +class ResLayer(Sequential): + """ResLayer to build ResNet style backbone for RPF in detectoRS. + + The difference between this module and base class is that we pass + ``rfp_inplanes`` to the first block. + + Args: + block (nn.Module): block used to build ResLayer. + inplanes (int): inplanes of block. + planes (int): planes of block. + num_blocks (int): number of blocks. + stride (int): stride of the first block. Default: 1 + avg_down (bool): Use AvgPool instead of stride conv when + downsampling in the bottleneck. Default: False + conv_cfg (dict): dictionary to construct and config conv layer. + Default: None + norm_cfg (dict): dictionary to construct and config norm layer. + Default: dict(type='BN') + downsample_first (bool): Downsample at the first block or last block. + False for Hourglass, True for ResNet. Default: True + rfp_inplanes (int, optional): The number of channels from RFP. + Default: None. If specified, an additional conv layer will be + added for ``rfp_feat``. Otherwise, the structure is the same as + base class. + """ + + def __init__(self, + block, + inplanes, + planes, + num_blocks, + stride=1, + avg_down=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + downsample_first=True, + rfp_inplanes=None, + **kwargs): + self.block = block + assert downsample_first, f'downsample_first={downsample_first} is ' \ + 'not supported in DetectoRS' + + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = [] + conv_stride = stride + if avg_down and stride != 1: + conv_stride = 1 + downsample.append( + nn.AvgPool2d( + kernel_size=stride, + stride=stride, + ceil_mode=True, + count_include_pad=False)) + downsample.extend([ + build_conv_layer( + conv_cfg, + inplanes, + planes * block.expansion, + kernel_size=1, + stride=conv_stride, + bias=False), + build_norm_layer(norm_cfg, planes * block.expansion)[1] + ]) + downsample = nn.Sequential(*downsample) + + layers = [] + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=stride, + downsample=downsample, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + rfp_inplanes=rfp_inplanes, + **kwargs)) + inplanes = planes * block.expansion + for _ in range(1, num_blocks): + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + **kwargs)) + + super(ResLayer, self).__init__(*layers) + + +@MODELS.register_module() +class DetectoRS_ResNet(ResNet): + """ResNet backbone for DetectoRS. + + Args: + sac (dict, optional): Dictionary to construct SAC (Switchable Atrous + Convolution). Default: None. + stage_with_sac (list): Which stage to use sac. Default: (False, False, + False, False). + rfp_inplanes (int, optional): The number of channels from RFP. + Default: None. If specified, an additional conv layer will be + added for ``rfp_feat``. Otherwise, the structure is the same as + base class. + output_img (bool): If ``True``, the input image will be inserted into + the starting position of output. Default: False. + """ + + arch_settings = { + 50: (Bottleneck, (3, 4, 6, 3)), + 101: (Bottleneck, (3, 4, 23, 3)), + 152: (Bottleneck, (3, 8, 36, 3)) + } + + def __init__(self, + sac=None, + stage_with_sac=(False, False, False, False), + rfp_inplanes=None, + output_img=False, + pretrained=None, + init_cfg=None, + **kwargs): + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + self.pretrained = pretrained + if init_cfg is not None: + assert isinstance(init_cfg, dict), \ + f'init_cfg must be a dict, but got {type(init_cfg)}' + if 'type' in init_cfg: + assert init_cfg.get('type') == 'Pretrained', \ + 'Only can initialize module by loading a pretrained model' + else: + raise KeyError('`init_cfg` must contain the key "type"') + self.pretrained = init_cfg.get('checkpoint') + self.sac = sac + self.stage_with_sac = stage_with_sac + self.rfp_inplanes = rfp_inplanes + self.output_img = output_img + super(DetectoRS_ResNet, self).__init__(**kwargs) + + self.inplanes = self.stem_channels + self.res_layers = [] + for i, num_blocks in enumerate(self.stage_blocks): + stride = self.strides[i] + dilation = self.dilations[i] + dcn = self.dcn if self.stage_with_dcn[i] else None + sac = self.sac if self.stage_with_sac[i] else None + if self.plugins is not None: + stage_plugins = self.make_stage_plugins(self.plugins, i) + else: + stage_plugins = None + planes = self.base_channels * 2**i + res_layer = self.make_res_layer( + block=self.block, + inplanes=self.inplanes, + planes=planes, + num_blocks=num_blocks, + stride=stride, + dilation=dilation, + style=self.style, + avg_down=self.avg_down, + with_cp=self.with_cp, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + dcn=dcn, + sac=sac, + rfp_inplanes=rfp_inplanes if i > 0 else None, + plugins=stage_plugins) + self.inplanes = planes * self.block.expansion + layer_name = f'layer{i + 1}' + self.add_module(layer_name, res_layer) + self.res_layers.append(layer_name) + + self._freeze_stages() + + # In order to be properly initialized by RFP + def init_weights(self): + # Calling this method will cause parameter initialization exception + # super(DetectoRS_ResNet, self).init_weights() + + if isinstance(self.pretrained, str): + logger = MMLogger.get_current_instance() + load_checkpoint(self, self.pretrained, strict=False, logger=logger) + elif self.pretrained is None: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + kaiming_init(m) + elif isinstance(m, (_BatchNorm, nn.GroupNorm)): + constant_init(m, 1) + + if self.dcn is not None: + for m in self.modules(): + if isinstance(m, Bottleneck) and hasattr( + m.conv2, 'conv_offset'): + constant_init(m.conv2.conv_offset, 0) + + if self.zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + constant_init(m.norm3, 0) + elif isinstance(m, BasicBlock): + constant_init(m.norm2, 0) + else: + raise TypeError('pretrained must be a str or None') + + def make_res_layer(self, **kwargs): + """Pack all blocks in a stage into a ``ResLayer`` for DetectoRS.""" + return ResLayer(**kwargs) + + def forward(self, x): + """Forward function.""" + outs = list(super(DetectoRS_ResNet, self).forward(x)) + if self.output_img: + outs.insert(0, x) + return tuple(outs) + + def rfp_forward(self, x, rfp_feats): + """Forward function for RFP.""" + if self.deep_stem: + x = self.stem(x) + else: + x = self.conv1(x) + x = self.norm1(x) + x = self.relu(x) + x = self.maxpool(x) + outs = [] + for i, layer_name in enumerate(self.res_layers): + res_layer = getattr(self, layer_name) + rfp_feat = rfp_feats[i] if i > 0 else None + for layer in res_layer: + x = layer.rfp_forward(x, rfp_feat) + if i in self.out_indices: + outs.append(x) + return tuple(outs) diff --git a/mmdetection/mmdet/models/backbones/detectors_resnext.py b/mmdetection/mmdet/models/backbones/detectors_resnext.py new file mode 100644 index 00000000..4bbd6315 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/detectors_resnext.py @@ -0,0 +1,123 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +from mmcv.cnn import build_conv_layer, build_norm_layer + +from mmdet.registry import MODELS +from .detectors_resnet import Bottleneck as _Bottleneck +from .detectors_resnet import DetectoRS_ResNet + + +class Bottleneck(_Bottleneck): + expansion = 4 + + def __init__(self, + inplanes, + planes, + groups=1, + base_width=4, + base_channels=64, + **kwargs): + """Bottleneck block for ResNeXt. + + If style is "pytorch", the stride-two layer is the 3x3 conv layer, if + it is "caffe", the stride-two layer is the first 1x1 conv layer. + """ + super(Bottleneck, self).__init__(inplanes, planes, **kwargs) + + if groups == 1: + width = self.planes + else: + width = math.floor(self.planes * + (base_width / base_channels)) * groups + + self.norm1_name, norm1 = build_norm_layer( + self.norm_cfg, width, postfix=1) + self.norm2_name, norm2 = build_norm_layer( + self.norm_cfg, width, postfix=2) + self.norm3_name, norm3 = build_norm_layer( + self.norm_cfg, self.planes * self.expansion, postfix=3) + + self.conv1 = build_conv_layer( + self.conv_cfg, + self.inplanes, + width, + kernel_size=1, + stride=self.conv1_stride, + bias=False) + self.add_module(self.norm1_name, norm1) + fallback_on_stride = False + self.with_modulated_dcn = False + if self.with_dcn: + fallback_on_stride = self.dcn.pop('fallback_on_stride', False) + if self.with_sac: + self.conv2 = build_conv_layer( + self.sac, + width, + width, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + groups=groups, + bias=False) + elif not self.with_dcn or fallback_on_stride: + self.conv2 = build_conv_layer( + self.conv_cfg, + width, + width, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + groups=groups, + bias=False) + else: + assert self.conv_cfg is None, 'conv_cfg must be None for DCN' + self.conv2 = build_conv_layer( + self.dcn, + width, + width, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + groups=groups, + bias=False) + + self.add_module(self.norm2_name, norm2) + self.conv3 = build_conv_layer( + self.conv_cfg, + width, + self.planes * self.expansion, + kernel_size=1, + bias=False) + self.add_module(self.norm3_name, norm3) + + +@MODELS.register_module() +class DetectoRS_ResNeXt(DetectoRS_ResNet): + """ResNeXt backbone for DetectoRS. + + Args: + groups (int): The number of groups in ResNeXt. + base_width (int): The base width of ResNeXt. + """ + + arch_settings = { + 50: (Bottleneck, (3, 4, 6, 3)), + 101: (Bottleneck, (3, 4, 23, 3)), + 152: (Bottleneck, (3, 8, 36, 3)) + } + + def __init__(self, groups=1, base_width=4, **kwargs): + self.groups = groups + self.base_width = base_width + super(DetectoRS_ResNeXt, self).__init__(**kwargs) + + def make_res_layer(self, **kwargs): + return super().make_res_layer( + groups=self.groups, + base_width=self.base_width, + base_channels=self.base_channels, + **kwargs) diff --git a/mmdetection/mmdet/models/backbones/efficientnet.py b/mmdetection/mmdet/models/backbones/efficientnet.py new file mode 100644 index 00000000..8484afe2 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/efficientnet.py @@ -0,0 +1,418 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import math +from functools import partial + +import torch +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn.bricks import ConvModule, DropPath +from mmengine.model import BaseModule, Sequential + +from mmdet.registry import MODELS +from ..layers import InvertedResidual, SELayer +from ..utils import make_divisible + + +class EdgeResidual(BaseModule): + """Edge Residual Block. + + Args: + in_channels (int): The input channels of this module. + out_channels (int): The output channels of this module. + mid_channels (int): The input channels of the second convolution. + kernel_size (int): The kernel size of the first convolution. + Defaults to 3. + stride (int): The stride of the first convolution. Defaults to 1. + se_cfg (dict, optional): Config dict for se layer. Defaults to None, + which means no se layer. + with_residual (bool): Use residual connection. Defaults to True. + conv_cfg (dict, optional): Config dict for convolution layer. + Defaults to None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Defaults to ``dict(type='BN')``. + act_cfg (dict): Config dict for activation layer. + Defaults to ``dict(type='ReLU')``. + drop_path_rate (float): stochastic depth rate. Defaults to 0. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Defaults to False. + init_cfg (dict | list[dict], optional): Initialization config dict. + """ + + def __init__(self, + in_channels, + out_channels, + mid_channels, + kernel_size=3, + stride=1, + se_cfg=None, + with_residual=True, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + drop_path_rate=0., + with_cp=False, + init_cfg=None, + **kwargs): + super(EdgeResidual, self).__init__(init_cfg=init_cfg) + assert stride in [1, 2] + self.with_cp = with_cp + self.drop_path = DropPath( + drop_path_rate) if drop_path_rate > 0 else nn.Identity() + self.with_se = se_cfg is not None + self.with_residual = ( + stride == 1 and in_channels == out_channels and with_residual) + + if self.with_se: + assert isinstance(se_cfg, dict) + + self.conv1 = ConvModule( + in_channels=in_channels, + out_channels=mid_channels, + kernel_size=kernel_size, + stride=1, + padding=kernel_size // 2, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + if self.with_se: + self.se = SELayer(**se_cfg) + + self.conv2 = ConvModule( + in_channels=mid_channels, + out_channels=out_channels, + kernel_size=1, + stride=stride, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + + def forward(self, x): + + def _inner_forward(x): + out = x + out = self.conv1(out) + + if self.with_se: + out = self.se(out) + + out = self.conv2(out) + + if self.with_residual: + return x + self.drop_path(out) + else: + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + return out + + +def model_scaling(layer_setting, arch_setting): + """Scaling operation to the layer's parameters according to the + arch_setting.""" + # scale width + new_layer_setting = copy.deepcopy(layer_setting) + for layer_cfg in new_layer_setting: + for block_cfg in layer_cfg: + block_cfg[1] = make_divisible(block_cfg[1] * arch_setting[0], 8) + + # scale depth + split_layer_setting = [new_layer_setting[0]] + for layer_cfg in new_layer_setting[1:-1]: + tmp_index = [0] + for i in range(len(layer_cfg) - 1): + if layer_cfg[i + 1][1] != layer_cfg[i][1]: + tmp_index.append(i + 1) + tmp_index.append(len(layer_cfg)) + for i in range(len(tmp_index) - 1): + split_layer_setting.append(layer_cfg[tmp_index[i]:tmp_index[i + + 1]]) + split_layer_setting.append(new_layer_setting[-1]) + + num_of_layers = [len(layer_cfg) for layer_cfg in split_layer_setting[1:-1]] + new_layers = [ + int(math.ceil(arch_setting[1] * num)) for num in num_of_layers + ] + + merge_layer_setting = [split_layer_setting[0]] + for i, layer_cfg in enumerate(split_layer_setting[1:-1]): + if new_layers[i] <= num_of_layers[i]: + tmp_layer_cfg = layer_cfg[:new_layers[i]] + else: + tmp_layer_cfg = copy.deepcopy(layer_cfg) + [layer_cfg[-1]] * ( + new_layers[i] - num_of_layers[i]) + if tmp_layer_cfg[0][3] == 1 and i != 0: + merge_layer_setting[-1] += tmp_layer_cfg.copy() + else: + merge_layer_setting.append(tmp_layer_cfg.copy()) + merge_layer_setting.append(split_layer_setting[-1]) + + return merge_layer_setting + + +@MODELS.register_module() +class EfficientNet(BaseModule): + """EfficientNet backbone. + + Args: + arch (str): Architecture of efficientnet. Defaults to b0. + out_indices (Sequence[int]): Output from which stages. + Defaults to (6, ). + frozen_stages (int): Stages to be frozen (all param fixed). + Defaults to 0, which means not freezing any parameters. + conv_cfg (dict): Config dict for convolution layer. + Defaults to None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Defaults to dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Defaults to dict(type='Swish'). + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Defaults to False. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Defaults to False. + """ + + # Parameters to build layers. + # 'b' represents the architecture of normal EfficientNet family includes + # 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8'. + # 'e' represents the architecture of EfficientNet-EdgeTPU including 'es', + # 'em', 'el'. + # 6 parameters are needed to construct a layer, From left to right: + # - kernel_size: The kernel size of the block + # - out_channel: The number of out_channels of the block + # - se_ratio: The sequeeze ratio of SELayer. + # - stride: The stride of the block + # - expand_ratio: The expand_ratio of the mid_channels + # - block_type: -1: Not a block, 0: InvertedResidual, 1: EdgeResidual + layer_settings = { + 'b': [[[3, 32, 0, 2, 0, -1]], + [[3, 16, 4, 1, 1, 0]], + [[3, 24, 4, 2, 6, 0], + [3, 24, 4, 1, 6, 0]], + [[5, 40, 4, 2, 6, 0], + [5, 40, 4, 1, 6, 0]], + [[3, 80, 4, 2, 6, 0], + [3, 80, 4, 1, 6, 0], + [3, 80, 4, 1, 6, 0], + [5, 112, 4, 1, 6, 0], + [5, 112, 4, 1, 6, 0], + [5, 112, 4, 1, 6, 0]], + [[5, 192, 4, 2, 6, 0], + [5, 192, 4, 1, 6, 0], + [5, 192, 4, 1, 6, 0], + [5, 192, 4, 1, 6, 0], + [3, 320, 4, 1, 6, 0]], + [[1, 1280, 0, 1, 0, -1]] + ], + 'e': [[[3, 32, 0, 2, 0, -1]], + [[3, 24, 0, 1, 3, 1]], + [[3, 32, 0, 2, 8, 1], + [3, 32, 0, 1, 8, 1]], + [[3, 48, 0, 2, 8, 1], + [3, 48, 0, 1, 8, 1], + [3, 48, 0, 1, 8, 1], + [3, 48, 0, 1, 8, 1]], + [[5, 96, 0, 2, 8, 0], + [5, 96, 0, 1, 8, 0], + [5, 96, 0, 1, 8, 0], + [5, 96, 0, 1, 8, 0], + [5, 96, 0, 1, 8, 0], + [5, 144, 0, 1, 8, 0], + [5, 144, 0, 1, 8, 0], + [5, 144, 0, 1, 8, 0], + [5, 144, 0, 1, 8, 0]], + [[5, 192, 0, 2, 8, 0], + [5, 192, 0, 1, 8, 0]], + [[1, 1280, 0, 1, 0, -1]] + ] + } # yapf: disable + + # Parameters to build different kinds of architecture. + # From left to right: scaling factor for width, scaling factor for depth, + # resolution. + arch_settings = { + 'b0': (1.0, 1.0, 224), + 'b1': (1.0, 1.1, 240), + 'b2': (1.1, 1.2, 260), + 'b3': (1.2, 1.4, 300), + 'b4': (1.4, 1.8, 380), + 'b5': (1.6, 2.2, 456), + 'b6': (1.8, 2.6, 528), + 'b7': (2.0, 3.1, 600), + 'b8': (2.2, 3.6, 672), + 'es': (1.0, 1.0, 224), + 'em': (1.0, 1.1, 240), + 'el': (1.2, 1.4, 300) + } + + def __init__(self, + arch='b0', + drop_path_rate=0., + out_indices=(6, ), + frozen_stages=0, + conv_cfg=dict(type='Conv2dAdaptivePadding'), + norm_cfg=dict(type='BN', eps=1e-3), + act_cfg=dict(type='Swish'), + norm_eval=False, + with_cp=False, + init_cfg=[ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + layer=['_BatchNorm', 'GroupNorm'], + val=1) + ]): + super(EfficientNet, self).__init__(init_cfg) + assert arch in self.arch_settings, \ + f'"{arch}" is not one of the arch_settings ' \ + f'({", ".join(self.arch_settings.keys())})' + self.arch_setting = self.arch_settings[arch] + self.layer_setting = self.layer_settings[arch[:1]] + for index in out_indices: + if index not in range(0, len(self.layer_setting)): + raise ValueError('the item in out_indices must in ' + f'range(0, {len(self.layer_setting)}). ' + f'But received {index}') + + if frozen_stages not in range(len(self.layer_setting) + 1): + raise ValueError('frozen_stages must be in range(0, ' + f'{len(self.layer_setting) + 1}). ' + f'But received {frozen_stages}') + self.drop_path_rate = drop_path_rate + self.out_indices = out_indices + self.frozen_stages = frozen_stages + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.norm_eval = norm_eval + self.with_cp = with_cp + + self.layer_setting = model_scaling(self.layer_setting, + self.arch_setting) + block_cfg_0 = self.layer_setting[0][0] + block_cfg_last = self.layer_setting[-1][0] + self.in_channels = make_divisible(block_cfg_0[1], 8) + self.out_channels = block_cfg_last[1] + self.layers = nn.ModuleList() + self.layers.append( + ConvModule( + in_channels=3, + out_channels=self.in_channels, + kernel_size=block_cfg_0[0], + stride=block_cfg_0[3], + padding=block_cfg_0[0] // 2, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + self.make_layer() + # Avoid building unused layers in mmdetection. + if len(self.layers) < max(self.out_indices) + 1: + self.layers.append( + ConvModule( + in_channels=self.in_channels, + out_channels=self.out_channels, + kernel_size=block_cfg_last[0], + stride=block_cfg_last[3], + padding=block_cfg_last[0] // 2, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + + def make_layer(self): + # Without the first and the final conv block. + layer_setting = self.layer_setting[1:-1] + + total_num_blocks = sum([len(x) for x in layer_setting]) + block_idx = 0 + dpr = [ + x.item() + for x in torch.linspace(0, self.drop_path_rate, total_num_blocks) + ] # stochastic depth decay rule + + for i, layer_cfg in enumerate(layer_setting): + # Avoid building unused layers in mmdetection. + if i > max(self.out_indices) - 1: + break + layer = [] + for i, block_cfg in enumerate(layer_cfg): + (kernel_size, out_channels, se_ratio, stride, expand_ratio, + block_type) = block_cfg + + mid_channels = int(self.in_channels * expand_ratio) + out_channels = make_divisible(out_channels, 8) + if se_ratio <= 0: + se_cfg = None + else: + # In mmdetection, the `divisor` is deleted to align + # the logic of SELayer with mmpretrain. + se_cfg = dict( + channels=mid_channels, + ratio=expand_ratio * se_ratio, + act_cfg=(self.act_cfg, dict(type='Sigmoid'))) + if block_type == 1: # edge tpu + if i > 0 and expand_ratio == 3: + with_residual = False + expand_ratio = 4 + else: + with_residual = True + mid_channels = int(self.in_channels * expand_ratio) + if se_cfg is not None: + # In mmdetection, the `divisor` is deleted to align + # the logic of SELayer with mmpretrain. + se_cfg = dict( + channels=mid_channels, + ratio=se_ratio * expand_ratio, + act_cfg=(self.act_cfg, dict(type='Sigmoid'))) + block = partial(EdgeResidual, with_residual=with_residual) + else: + block = InvertedResidual + layer.append( + block( + in_channels=self.in_channels, + out_channels=out_channels, + mid_channels=mid_channels, + kernel_size=kernel_size, + stride=stride, + se_cfg=se_cfg, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + drop_path_rate=dpr[block_idx], + with_cp=self.with_cp, + # In mmdetection, `with_expand_conv` is set to align + # the logic of InvertedResidual with mmpretrain. + with_expand_conv=(mid_channels != self.in_channels))) + self.in_channels = out_channels + block_idx += 1 + self.layers.append(Sequential(*layer)) + + def forward(self, x): + outs = [] + for i, layer in enumerate(self.layers): + x = layer(x) + if i in self.out_indices: + outs.append(x) + + return tuple(outs) + + def _freeze_stages(self): + for i in range(self.frozen_stages): + m = self.layers[i] + m.eval() + for param in m.parameters(): + param.requires_grad = False + + def train(self, mode=True): + super(EfficientNet, self).train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + if isinstance(m, nn.BatchNorm2d): + m.eval() diff --git a/mmdetection/mmdet/models/backbones/hourglass.py b/mmdetection/mmdet/models/backbones/hourglass.py new file mode 100644 index 00000000..bb58799f --- /dev/null +++ b/mmdetection/mmdet/models/backbones/hourglass.py @@ -0,0 +1,225 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Sequence + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptMultiConfig +from ..layers import ResLayer +from .resnet import BasicBlock + + +class HourglassModule(BaseModule): + """Hourglass Module for HourglassNet backbone. + + Generate module recursively and use BasicBlock as the base unit. + + Args: + depth (int): Depth of current HourglassModule. + stage_channels (list[int]): Feature channels of sub-modules in current + and follow-up HourglassModule. + stage_blocks (list[int]): Number of sub-modules stacked in current and + follow-up HourglassModule. + norm_cfg (ConfigType): Dictionary to construct and config norm layer. + Defaults to `dict(type='BN', requires_grad=True)` + upsample_cfg (ConfigType): Config dict for interpolate layer. + Defaults to `dict(mode='nearest')` + init_cfg (dict or ConfigDict, optional): the config to control the + initialization. + """ + + def __init__(self, + depth: int, + stage_channels: List[int], + stage_blocks: List[int], + norm_cfg: ConfigType = dict(type='BN', requires_grad=True), + upsample_cfg: ConfigType = dict(mode='nearest'), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg) + + self.depth = depth + + cur_block = stage_blocks[0] + next_block = stage_blocks[1] + + cur_channel = stage_channels[0] + next_channel = stage_channels[1] + + self.up1 = ResLayer( + BasicBlock, cur_channel, cur_channel, cur_block, norm_cfg=norm_cfg) + + self.low1 = ResLayer( + BasicBlock, + cur_channel, + next_channel, + cur_block, + stride=2, + norm_cfg=norm_cfg) + + if self.depth > 1: + self.low2 = HourglassModule(depth - 1, stage_channels[1:], + stage_blocks[1:]) + else: + self.low2 = ResLayer( + BasicBlock, + next_channel, + next_channel, + next_block, + norm_cfg=norm_cfg) + + self.low3 = ResLayer( + BasicBlock, + next_channel, + cur_channel, + cur_block, + norm_cfg=norm_cfg, + downsample_first=False) + + self.up2 = F.interpolate + self.upsample_cfg = upsample_cfg + + def forward(self, x: torch.Tensor) -> nn.Module: + """Forward function.""" + up1 = self.up1(x) + low1 = self.low1(x) + low2 = self.low2(low1) + low3 = self.low3(low2) + # Fixing `scale factor` (e.g. 2) is common for upsampling, but + # in some cases the spatial size is mismatched and error will arise. + if 'scale_factor' in self.upsample_cfg: + up2 = self.up2(low3, **self.upsample_cfg) + else: + shape = up1.shape[2:] + up2 = self.up2(low3, size=shape, **self.upsample_cfg) + return up1 + up2 + + +@MODELS.register_module() +class HourglassNet(BaseModule): + """HourglassNet backbone. + + Stacked Hourglass Networks for Human Pose Estimation. + More details can be found in the `paper + `_ . + + Args: + downsample_times (int): Downsample times in a HourglassModule. + num_stacks (int): Number of HourglassModule modules stacked, + 1 for Hourglass-52, 2 for Hourglass-104. + stage_channels (Sequence[int]): Feature channel of each sub-module in a + HourglassModule. + stage_blocks (Sequence[int]): Number of sub-modules stacked in a + HourglassModule. + feat_channel (int): Feature channel of conv after a HourglassModule. + norm_cfg (norm_cfg): Dictionary to construct and config norm layer. + init_cfg (dict or ConfigDict, optional): the config to control the + initialization. + + Example: + >>> from mmdet.models import HourglassNet + >>> import torch + >>> self = HourglassNet() + >>> self.eval() + >>> inputs = torch.rand(1, 3, 511, 511) + >>> level_outputs = self.forward(inputs) + >>> for level_output in level_outputs: + ... print(tuple(level_output.shape)) + (1, 256, 128, 128) + (1, 256, 128, 128) + """ + + def __init__(self, + downsample_times: int = 5, + num_stacks: int = 2, + stage_channels: Sequence = (256, 256, 384, 384, 384, 512), + stage_blocks: Sequence = (2, 2, 2, 2, 2, 4), + feat_channel: int = 256, + norm_cfg: ConfigType = dict(type='BN', requires_grad=True), + init_cfg: OptMultiConfig = None) -> None: + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + super().__init__(init_cfg) + + self.num_stacks = num_stacks + assert self.num_stacks >= 1 + assert len(stage_channels) == len(stage_blocks) + assert len(stage_channels) > downsample_times + + cur_channel = stage_channels[0] + + self.stem = nn.Sequential( + ConvModule( + 3, cur_channel // 2, 7, padding=3, stride=2, + norm_cfg=norm_cfg), + ResLayer( + BasicBlock, + cur_channel // 2, + cur_channel, + 1, + stride=2, + norm_cfg=norm_cfg)) + + self.hourglass_modules = nn.ModuleList([ + HourglassModule(downsample_times, stage_channels, stage_blocks) + for _ in range(num_stacks) + ]) + + self.inters = ResLayer( + BasicBlock, + cur_channel, + cur_channel, + num_stacks - 1, + norm_cfg=norm_cfg) + + self.conv1x1s = nn.ModuleList([ + ConvModule( + cur_channel, cur_channel, 1, norm_cfg=norm_cfg, act_cfg=None) + for _ in range(num_stacks - 1) + ]) + + self.out_convs = nn.ModuleList([ + ConvModule( + cur_channel, feat_channel, 3, padding=1, norm_cfg=norm_cfg) + for _ in range(num_stacks) + ]) + + self.remap_convs = nn.ModuleList([ + ConvModule( + feat_channel, cur_channel, 1, norm_cfg=norm_cfg, act_cfg=None) + for _ in range(num_stacks - 1) + ]) + + self.relu = nn.ReLU(inplace=True) + + def init_weights(self) -> None: + """Init module weights.""" + # Training Centripetal Model needs to reset parameters for Conv2d + super().init_weights() + for m in self.modules(): + if isinstance(m, nn.Conv2d): + m.reset_parameters() + + def forward(self, x: torch.Tensor) -> List[torch.Tensor]: + """Forward function.""" + inter_feat = self.stem(x) + out_feats = [] + + for ind in range(self.num_stacks): + single_hourglass = self.hourglass_modules[ind] + out_conv = self.out_convs[ind] + + hourglass_feat = single_hourglass(inter_feat) + out_feat = out_conv(hourglass_feat) + out_feats.append(out_feat) + + if ind < self.num_stacks - 1: + inter_feat = self.conv1x1s[ind]( + inter_feat) + self.remap_convs[ind]( + out_feat) + inter_feat = self.inters[ind](self.relu(inter_feat)) + + return out_feats diff --git a/mmdetection/mmdet/models/backbones/hrnet.py b/mmdetection/mmdet/models/backbones/hrnet.py new file mode 100644 index 00000000..77bd3cc7 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/hrnet.py @@ -0,0 +1,589 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +from mmcv.cnn import build_conv_layer, build_norm_layer +from mmengine.model import BaseModule, ModuleList, Sequential +from torch.nn.modules.batchnorm import _BatchNorm + +from mmdet.registry import MODELS +from .resnet import BasicBlock, Bottleneck + + +class HRModule(BaseModule): + """High-Resolution Module for HRNet. + + In this module, every branch has 4 BasicBlocks/Bottlenecks. Fusion/Exchange + is in this module. + """ + + def __init__(self, + num_branches, + blocks, + num_blocks, + in_channels, + num_channels, + multiscale_output=True, + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + block_init_cfg=None, + init_cfg=None): + super(HRModule, self).__init__(init_cfg) + self.block_init_cfg = block_init_cfg + self._check_branches(num_branches, num_blocks, in_channels, + num_channels) + + self.in_channels = in_channels + self.num_branches = num_branches + + self.multiscale_output = multiscale_output + self.norm_cfg = norm_cfg + self.conv_cfg = conv_cfg + self.with_cp = with_cp + self.branches = self._make_branches(num_branches, blocks, num_blocks, + num_channels) + self.fuse_layers = self._make_fuse_layers() + self.relu = nn.ReLU(inplace=False) + + def _check_branches(self, num_branches, num_blocks, in_channels, + num_channels): + if num_branches != len(num_blocks): + error_msg = f'NUM_BRANCHES({num_branches}) ' \ + f'!= NUM_BLOCKS({len(num_blocks)})' + raise ValueError(error_msg) + + if num_branches != len(num_channels): + error_msg = f'NUM_BRANCHES({num_branches}) ' \ + f'!= NUM_CHANNELS({len(num_channels)})' + raise ValueError(error_msg) + + if num_branches != len(in_channels): + error_msg = f'NUM_BRANCHES({num_branches}) ' \ + f'!= NUM_INCHANNELS({len(in_channels)})' + raise ValueError(error_msg) + + def _make_one_branch(self, + branch_index, + block, + num_blocks, + num_channels, + stride=1): + downsample = None + if stride != 1 or \ + self.in_channels[branch_index] != \ + num_channels[branch_index] * block.expansion: + downsample = nn.Sequential( + build_conv_layer( + self.conv_cfg, + self.in_channels[branch_index], + num_channels[branch_index] * block.expansion, + kernel_size=1, + stride=stride, + bias=False), + build_norm_layer(self.norm_cfg, num_channels[branch_index] * + block.expansion)[1]) + + layers = [] + layers.append( + block( + self.in_channels[branch_index], + num_channels[branch_index], + stride, + downsample=downsample, + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + init_cfg=self.block_init_cfg)) + self.in_channels[branch_index] = \ + num_channels[branch_index] * block.expansion + for i in range(1, num_blocks[branch_index]): + layers.append( + block( + self.in_channels[branch_index], + num_channels[branch_index], + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + init_cfg=self.block_init_cfg)) + + return Sequential(*layers) + + def _make_branches(self, num_branches, block, num_blocks, num_channels): + branches = [] + + for i in range(num_branches): + branches.append( + self._make_one_branch(i, block, num_blocks, num_channels)) + + return ModuleList(branches) + + def _make_fuse_layers(self): + if self.num_branches == 1: + return None + + num_branches = self.num_branches + in_channels = self.in_channels + fuse_layers = [] + num_out_branches = num_branches if self.multiscale_output else 1 + for i in range(num_out_branches): + fuse_layer = [] + for j in range(num_branches): + if j > i: + fuse_layer.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels[j], + in_channels[i], + kernel_size=1, + stride=1, + padding=0, + bias=False), + build_norm_layer(self.norm_cfg, in_channels[i])[1], + nn.Upsample( + scale_factor=2**(j - i), mode='nearest'))) + elif j == i: + fuse_layer.append(None) + else: + conv_downsamples = [] + for k in range(i - j): + if k == i - j - 1: + conv_downsamples.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels[j], + in_channels[i], + kernel_size=3, + stride=2, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, + in_channels[i])[1])) + else: + conv_downsamples.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels[j], + in_channels[j], + kernel_size=3, + stride=2, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, + in_channels[j])[1], + nn.ReLU(inplace=False))) + fuse_layer.append(nn.Sequential(*conv_downsamples)) + fuse_layers.append(nn.ModuleList(fuse_layer)) + + return nn.ModuleList(fuse_layers) + + def forward(self, x): + """Forward function.""" + if self.num_branches == 1: + return [self.branches[0](x[0])] + + for i in range(self.num_branches): + x[i] = self.branches[i](x[i]) + + x_fuse = [] + for i in range(len(self.fuse_layers)): + y = 0 + for j in range(self.num_branches): + if i == j: + y += x[j] + else: + y += self.fuse_layers[i][j](x[j]) + x_fuse.append(self.relu(y)) + return x_fuse + + +@MODELS.register_module() +class HRNet(BaseModule): + """HRNet backbone. + + `High-Resolution Representations for Labeling Pixels and Regions + arXiv: `_. + + Args: + extra (dict): Detailed configuration for each stage of HRNet. + There must be 4 stages, the configuration for each stage must have + 5 keys: + + - num_modules(int): The number of HRModule in this stage. + - num_branches(int): The number of branches in the HRModule. + - block(str): The type of convolution block. + - num_blocks(tuple): The number of blocks in each branch. + The length must be equal to num_branches. + - num_channels(tuple): The number of channels in each branch. + The length must be equal to num_branches. + in_channels (int): Number of input image channels. Default: 3. + conv_cfg (dict): Dictionary to construct and config conv layer. + norm_cfg (dict): Dictionary to construct and config norm layer. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: True. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + zero_init_residual (bool): Whether to use zero init for last norm layer + in resblocks to let them behave as identity. Default: False. + multiscale_output (bool): Whether to output multi-level features + produced by multiple branches. If False, only the first level + feature will be output. Default: True. + pretrained (str, optional): Model pretrained path. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + + Example: + >>> from mmdet.models import HRNet + >>> import torch + >>> extra = dict( + >>> stage1=dict( + >>> num_modules=1, + >>> num_branches=1, + >>> block='BOTTLENECK', + >>> num_blocks=(4, ), + >>> num_channels=(64, )), + >>> stage2=dict( + >>> num_modules=1, + >>> num_branches=2, + >>> block='BASIC', + >>> num_blocks=(4, 4), + >>> num_channels=(32, 64)), + >>> stage3=dict( + >>> num_modules=4, + >>> num_branches=3, + >>> block='BASIC', + >>> num_blocks=(4, 4, 4), + >>> num_channels=(32, 64, 128)), + >>> stage4=dict( + >>> num_modules=3, + >>> num_branches=4, + >>> block='BASIC', + >>> num_blocks=(4, 4, 4, 4), + >>> num_channels=(32, 64, 128, 256))) + >>> self = HRNet(extra, in_channels=1) + >>> self.eval() + >>> inputs = torch.rand(1, 1, 32, 32) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + (1, 32, 8, 8) + (1, 64, 4, 4) + (1, 128, 2, 2) + (1, 256, 1, 1) + """ + + blocks_dict = {'BASIC': BasicBlock, 'BOTTLENECK': Bottleneck} + + def __init__(self, + extra, + in_channels=3, + conv_cfg=None, + norm_cfg=dict(type='BN'), + norm_eval=True, + with_cp=False, + zero_init_residual=False, + multiscale_output=True, + pretrained=None, + init_cfg=None): + super(HRNet, self).__init__(init_cfg) + + self.pretrained = pretrained + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + else: + raise TypeError('pretrained must be a str or None') + + # Assert configurations of 4 stages are in extra + assert 'stage1' in extra and 'stage2' in extra \ + and 'stage3' in extra and 'stage4' in extra + # Assert whether the length of `num_blocks` and `num_channels` are + # equal to `num_branches` + for i in range(4): + cfg = extra[f'stage{i + 1}'] + assert len(cfg['num_blocks']) == cfg['num_branches'] and \ + len(cfg['num_channels']) == cfg['num_branches'] + + self.extra = extra + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.norm_eval = norm_eval + self.with_cp = with_cp + self.zero_init_residual = zero_init_residual + + # stem net + self.norm1_name, norm1 = build_norm_layer(self.norm_cfg, 64, postfix=1) + self.norm2_name, norm2 = build_norm_layer(self.norm_cfg, 64, postfix=2) + + self.conv1 = build_conv_layer( + self.conv_cfg, + in_channels, + 64, + kernel_size=3, + stride=2, + padding=1, + bias=False) + + self.add_module(self.norm1_name, norm1) + self.conv2 = build_conv_layer( + self.conv_cfg, + 64, + 64, + kernel_size=3, + stride=2, + padding=1, + bias=False) + + self.add_module(self.norm2_name, norm2) + self.relu = nn.ReLU(inplace=True) + + # stage 1 + self.stage1_cfg = self.extra['stage1'] + num_channels = self.stage1_cfg['num_channels'][0] + block_type = self.stage1_cfg['block'] + num_blocks = self.stage1_cfg['num_blocks'][0] + + block = self.blocks_dict[block_type] + stage1_out_channels = num_channels * block.expansion + self.layer1 = self._make_layer(block, 64, num_channels, num_blocks) + + # stage 2 + self.stage2_cfg = self.extra['stage2'] + num_channels = self.stage2_cfg['num_channels'] + block_type = self.stage2_cfg['block'] + + block = self.blocks_dict[block_type] + num_channels = [channel * block.expansion for channel in num_channels] + self.transition1 = self._make_transition_layer([stage1_out_channels], + num_channels) + self.stage2, pre_stage_channels = self._make_stage( + self.stage2_cfg, num_channels) + + # stage 3 + self.stage3_cfg = self.extra['stage3'] + num_channels = self.stage3_cfg['num_channels'] + block_type = self.stage3_cfg['block'] + + block = self.blocks_dict[block_type] + num_channels = [channel * block.expansion for channel in num_channels] + self.transition2 = self._make_transition_layer(pre_stage_channels, + num_channels) + self.stage3, pre_stage_channels = self._make_stage( + self.stage3_cfg, num_channels) + + # stage 4 + self.stage4_cfg = self.extra['stage4'] + num_channels = self.stage4_cfg['num_channels'] + block_type = self.stage4_cfg['block'] + + block = self.blocks_dict[block_type] + num_channels = [channel * block.expansion for channel in num_channels] + self.transition3 = self._make_transition_layer(pre_stage_channels, + num_channels) + self.stage4, pre_stage_channels = self._make_stage( + self.stage4_cfg, num_channels, multiscale_output=multiscale_output) + + @property + def norm1(self): + """nn.Module: the normalization layer named "norm1" """ + return getattr(self, self.norm1_name) + + @property + def norm2(self): + """nn.Module: the normalization layer named "norm2" """ + return getattr(self, self.norm2_name) + + def _make_transition_layer(self, num_channels_pre_layer, + num_channels_cur_layer): + num_branches_cur = len(num_channels_cur_layer) + num_branches_pre = len(num_channels_pre_layer) + + transition_layers = [] + for i in range(num_branches_cur): + if i < num_branches_pre: + if num_channels_cur_layer[i] != num_channels_pre_layer[i]: + transition_layers.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + num_channels_pre_layer[i], + num_channels_cur_layer[i], + kernel_size=3, + stride=1, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, + num_channels_cur_layer[i])[1], + nn.ReLU(inplace=True))) + else: + transition_layers.append(None) + else: + conv_downsamples = [] + for j in range(i + 1 - num_branches_pre): + in_channels = num_channels_pre_layer[-1] + out_channels = num_channels_cur_layer[i] \ + if j == i - num_branches_pre else in_channels + conv_downsamples.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels, + out_channels, + kernel_size=3, + stride=2, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, out_channels)[1], + nn.ReLU(inplace=True))) + transition_layers.append(nn.Sequential(*conv_downsamples)) + + return nn.ModuleList(transition_layers) + + def _make_layer(self, block, inplanes, planes, blocks, stride=1): + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = nn.Sequential( + build_conv_layer( + self.conv_cfg, + inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias=False), + build_norm_layer(self.norm_cfg, planes * block.expansion)[1]) + + layers = [] + block_init_cfg = None + if self.pretrained is None and not hasattr( + self, 'init_cfg') and self.zero_init_residual: + if block is BasicBlock: + block_init_cfg = dict( + type='Constant', val=0, override=dict(name='norm2')) + elif block is Bottleneck: + block_init_cfg = dict( + type='Constant', val=0, override=dict(name='norm3')) + layers.append( + block( + inplanes, + planes, + stride, + downsample=downsample, + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + init_cfg=block_init_cfg, + )) + inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append( + block( + inplanes, + planes, + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + init_cfg=block_init_cfg)) + + return Sequential(*layers) + + def _make_stage(self, layer_config, in_channels, multiscale_output=True): + num_modules = layer_config['num_modules'] + num_branches = layer_config['num_branches'] + num_blocks = layer_config['num_blocks'] + num_channels = layer_config['num_channels'] + block = self.blocks_dict[layer_config['block']] + + hr_modules = [] + block_init_cfg = None + if self.pretrained is None and not hasattr( + self, 'init_cfg') and self.zero_init_residual: + if block is BasicBlock: + block_init_cfg = dict( + type='Constant', val=0, override=dict(name='norm2')) + elif block is Bottleneck: + block_init_cfg = dict( + type='Constant', val=0, override=dict(name='norm3')) + + for i in range(num_modules): + # multi_scale_output is only used for the last module + if not multiscale_output and i == num_modules - 1: + reset_multiscale_output = False + else: + reset_multiscale_output = True + + hr_modules.append( + HRModule( + num_branches, + block, + num_blocks, + in_channels, + num_channels, + reset_multiscale_output, + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + block_init_cfg=block_init_cfg)) + + return Sequential(*hr_modules), in_channels + + def forward(self, x): + """Forward function.""" + x = self.conv1(x) + x = self.norm1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.norm2(x) + x = self.relu(x) + x = self.layer1(x) + + x_list = [] + for i in range(self.stage2_cfg['num_branches']): + if self.transition1[i] is not None: + x_list.append(self.transition1[i](x)) + else: + x_list.append(x) + y_list = self.stage2(x_list) + + x_list = [] + for i in range(self.stage3_cfg['num_branches']): + if self.transition2[i] is not None: + x_list.append(self.transition2[i](y_list[-1])) + else: + x_list.append(y_list[i]) + y_list = self.stage3(x_list) + + x_list = [] + for i in range(self.stage4_cfg['num_branches']): + if self.transition3[i] is not None: + x_list.append(self.transition3[i](y_list[-1])) + else: + x_list.append(y_list[i]) + y_list = self.stage4(x_list) + + return y_list + + def train(self, mode=True): + """Convert the model into training mode will keeping the normalization + layer freezed.""" + super(HRNet, self).train(mode) + if mode and self.norm_eval: + for m in self.modules(): + # trick: eval have effect on BatchNorm only + if isinstance(m, _BatchNorm): + m.eval() diff --git a/mmdetection/mmdet/models/backbones/mobilenet_v2.py b/mmdetection/mmdet/models/backbones/mobilenet_v2.py new file mode 100644 index 00000000..a4fd0519 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/mobilenet_v2.py @@ -0,0 +1,198 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch.nn.modules.batchnorm import _BatchNorm + +from mmdet.registry import MODELS +from ..layers import InvertedResidual +from ..utils import make_divisible + + +@MODELS.register_module() +class MobileNetV2(BaseModule): + """MobileNetV2 backbone. + + Args: + widen_factor (float): Width multiplier, multiply number of + channels in each layer by this amount. Default: 1.0. + out_indices (Sequence[int], optional): Output from which stages. + Default: (1, 2, 4, 7). + frozen_stages (int): Stages to be frozen (all param fixed). + Default: -1, which means not freezing any parameters. + conv_cfg (dict, optional): Config dict for convolution layer. + Default: None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU6'). + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + # Parameters to build layers. 4 parameters are needed to construct a + # layer, from left to right: expand_ratio, channel, num_blocks, stride. + arch_settings = [[1, 16, 1, 1], [6, 24, 2, 2], [6, 32, 3, 2], + [6, 64, 4, 2], [6, 96, 3, 1], [6, 160, 3, 2], + [6, 320, 1, 1]] + + def __init__(self, + widen_factor=1., + out_indices=(1, 2, 4, 7), + frozen_stages=-1, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU6'), + norm_eval=False, + with_cp=False, + pretrained=None, + init_cfg=None): + super(MobileNetV2, self).__init__(init_cfg) + + self.pretrained = pretrained + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + else: + raise TypeError('pretrained must be a str or None') + + self.widen_factor = widen_factor + self.out_indices = out_indices + if not set(out_indices).issubset(set(range(0, 8))): + raise ValueError('out_indices must be a subset of range' + f'(0, 8). But received {out_indices}') + + if frozen_stages not in range(-1, 8): + raise ValueError('frozen_stages must be in range(-1, 8). ' + f'But received {frozen_stages}') + self.out_indices = out_indices + self.frozen_stages = frozen_stages + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.norm_eval = norm_eval + self.with_cp = with_cp + + self.in_channels = make_divisible(32 * widen_factor, 8) + + self.conv1 = ConvModule( + in_channels=3, + out_channels=self.in_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + self.layers = [] + + for i, layer_cfg in enumerate(self.arch_settings): + expand_ratio, channel, num_blocks, stride = layer_cfg + out_channels = make_divisible(channel * widen_factor, 8) + inverted_res_layer = self.make_layer( + out_channels=out_channels, + num_blocks=num_blocks, + stride=stride, + expand_ratio=expand_ratio) + layer_name = f'layer{i + 1}' + self.add_module(layer_name, inverted_res_layer) + self.layers.append(layer_name) + + if widen_factor > 1.0: + self.out_channel = int(1280 * widen_factor) + else: + self.out_channel = 1280 + + layer = ConvModule( + in_channels=self.in_channels, + out_channels=self.out_channel, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.add_module('conv2', layer) + self.layers.append('conv2') + + def make_layer(self, out_channels, num_blocks, stride, expand_ratio): + """Stack InvertedResidual blocks to build a layer for MobileNetV2. + + Args: + out_channels (int): out_channels of block. + num_blocks (int): number of blocks. + stride (int): stride of the first block. Default: 1 + expand_ratio (int): Expand the number of channels of the + hidden layer in InvertedResidual by this ratio. Default: 6. + """ + layers = [] + for i in range(num_blocks): + if i >= 1: + stride = 1 + layers.append( + InvertedResidual( + self.in_channels, + out_channels, + mid_channels=int(round(self.in_channels * expand_ratio)), + stride=stride, + with_expand_conv=expand_ratio != 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + with_cp=self.with_cp)) + self.in_channels = out_channels + + return nn.Sequential(*layers) + + def _freeze_stages(self): + if self.frozen_stages >= 0: + for param in self.conv1.parameters(): + param.requires_grad = False + for i in range(1, self.frozen_stages + 1): + layer = getattr(self, f'layer{i}') + layer.eval() + for param in layer.parameters(): + param.requires_grad = False + + def forward(self, x): + """Forward function.""" + x = self.conv1(x) + outs = [] + for i, layer_name in enumerate(self.layers): + layer = getattr(self, layer_name) + x = layer(x) + if i in self.out_indices: + outs.append(x) + return tuple(outs) + + def train(self, mode=True): + """Convert the model into training mode while keep normalization layer + frozen.""" + super(MobileNetV2, self).train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + # trick: eval have effect on BatchNorm only + if isinstance(m, _BatchNorm): + m.eval() diff --git a/mmdetection/mmdet/models/backbones/pvt.py b/mmdetection/mmdet/models/backbones/pvt.py new file mode 100644 index 00000000..8b250f63 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/pvt.py @@ -0,0 +1,665 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +import warnings +from collections import OrderedDict + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Conv2d, build_activation_layer, build_norm_layer +from mmcv.cnn.bricks.drop import build_dropout +from mmcv.cnn.bricks.transformer import MultiheadAttention +from mmengine.logging import MMLogger +from mmengine.model import (BaseModule, ModuleList, Sequential, constant_init, + normal_init, trunc_normal_init) +from mmengine.model.weight_init import trunc_normal_ +from mmengine.runner.checkpoint import CheckpointLoader, load_state_dict +from torch.nn.modules.utils import _pair as to_2tuple + +from mmdet.registry import MODELS +from ..layers import PatchEmbed, nchw_to_nlc, nlc_to_nchw + + +class MixFFN(BaseModule): + """An implementation of MixFFN of PVT. + + The differences between MixFFN & FFN: + 1. Use 1X1 Conv to replace Linear layer. + 2. Introduce 3X3 Depth-wise Conv to encode positional information. + + Args: + embed_dims (int): The feature dimension. Same as + `MultiheadAttention`. + feedforward_channels (int): The hidden dimension of FFNs. + act_cfg (dict, optional): The activation config for FFNs. + Default: dict(type='GELU'). + ffn_drop (float, optional): Probability of an element to be + zeroed in FFN. Default 0.0. + dropout_layer (obj:`ConfigDict`): The dropout_layer used + when adding the shortcut. + Default: None. + use_conv (bool): If True, add 3x3 DWConv between two Linear layers. + Defaults: False. + init_cfg (obj:`mmengine.ConfigDict`): The Config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + feedforward_channels, + act_cfg=dict(type='GELU'), + ffn_drop=0., + dropout_layer=None, + use_conv=False, + init_cfg=None): + super(MixFFN, self).__init__(init_cfg=init_cfg) + + self.embed_dims = embed_dims + self.feedforward_channels = feedforward_channels + self.act_cfg = act_cfg + activate = build_activation_layer(act_cfg) + + in_channels = embed_dims + fc1 = Conv2d( + in_channels=in_channels, + out_channels=feedforward_channels, + kernel_size=1, + stride=1, + bias=True) + if use_conv: + # 3x3 depth wise conv to provide positional encode information + dw_conv = Conv2d( + in_channels=feedforward_channels, + out_channels=feedforward_channels, + kernel_size=3, + stride=1, + padding=(3 - 1) // 2, + bias=True, + groups=feedforward_channels) + fc2 = Conv2d( + in_channels=feedforward_channels, + out_channels=in_channels, + kernel_size=1, + stride=1, + bias=True) + drop = nn.Dropout(ffn_drop) + layers = [fc1, activate, drop, fc2, drop] + if use_conv: + layers.insert(1, dw_conv) + self.layers = Sequential(*layers) + self.dropout_layer = build_dropout( + dropout_layer) if dropout_layer else torch.nn.Identity() + + def forward(self, x, hw_shape, identity=None): + out = nlc_to_nchw(x, hw_shape) + out = self.layers(out) + out = nchw_to_nlc(out) + if identity is None: + identity = x + return identity + self.dropout_layer(out) + + +class SpatialReductionAttention(MultiheadAttention): + """An implementation of Spatial Reduction Attention of PVT. + + This module is modified from MultiheadAttention which is a module from + mmcv.cnn.bricks.transformer. + + Args: + embed_dims (int): The embedding dimension. + num_heads (int): Parallel attention heads. + attn_drop (float): A Dropout layer on attn_output_weights. + Default: 0.0. + proj_drop (float): A Dropout layer after `nn.MultiheadAttention`. + Default: 0.0. + dropout_layer (obj:`ConfigDict`): The dropout_layer used + when adding the shortcut. Default: None. + batch_first (bool): Key, Query and Value are shape of + (batch, n, embed_dim) + or (n, batch, embed_dim). Default: False. + qkv_bias (bool): enable bias for qkv if True. Default: True. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + sr_ratio (int): The ratio of spatial reduction of Spatial Reduction + Attention of PVT. Default: 1. + init_cfg (obj:`mmengine.ConfigDict`): The Config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + attn_drop=0., + proj_drop=0., + dropout_layer=None, + batch_first=True, + qkv_bias=True, + norm_cfg=dict(type='LN'), + sr_ratio=1, + init_cfg=None): + super().__init__( + embed_dims, + num_heads, + attn_drop, + proj_drop, + batch_first=batch_first, + dropout_layer=dropout_layer, + bias=qkv_bias, + init_cfg=init_cfg) + + self.sr_ratio = sr_ratio + if sr_ratio > 1: + self.sr = Conv2d( + in_channels=embed_dims, + out_channels=embed_dims, + kernel_size=sr_ratio, + stride=sr_ratio) + # The ret[0] of build_norm_layer is norm name. + self.norm = build_norm_layer(norm_cfg, embed_dims)[1] + + # handle the BC-breaking from https://github.com/open-mmlab/mmcv/pull/1418 # noqa + from mmdet import digit_version, mmcv_version + if mmcv_version < digit_version('1.3.17'): + warnings.warn('The legacy version of forward function in' + 'SpatialReductionAttention is deprecated in' + 'mmcv>=1.3.17 and will no longer support in the' + 'future. Please upgrade your mmcv.') + self.forward = self.legacy_forward + + def forward(self, x, hw_shape, identity=None): + + x_q = x + if self.sr_ratio > 1: + x_kv = nlc_to_nchw(x, hw_shape) + x_kv = self.sr(x_kv) + x_kv = nchw_to_nlc(x_kv) + x_kv = self.norm(x_kv) + else: + x_kv = x + + if identity is None: + identity = x_q + + # Because the dataflow('key', 'query', 'value') of + # ``torch.nn.MultiheadAttention`` is (num_queries, batch, + # embed_dims), We should adjust the shape of dataflow from + # batch_first (batch, num_queries, embed_dims) to num_queries_first + # (num_queries ,batch, embed_dims), and recover ``attn_output`` + # from num_queries_first to batch_first. + if self.batch_first: + x_q = x_q.transpose(0, 1) + x_kv = x_kv.transpose(0, 1) + + out = self.attn(query=x_q, key=x_kv, value=x_kv)[0] + + if self.batch_first: + out = out.transpose(0, 1) + + return identity + self.dropout_layer(self.proj_drop(out)) + + def legacy_forward(self, x, hw_shape, identity=None): + """multi head attention forward in mmcv version < 1.3.17.""" + x_q = x + if self.sr_ratio > 1: + x_kv = nlc_to_nchw(x, hw_shape) + x_kv = self.sr(x_kv) + x_kv = nchw_to_nlc(x_kv) + x_kv = self.norm(x_kv) + else: + x_kv = x + + if identity is None: + identity = x_q + + out = self.attn(query=x_q, key=x_kv, value=x_kv)[0] + + return identity + self.dropout_layer(self.proj_drop(out)) + + +class PVTEncoderLayer(BaseModule): + """Implements one encoder layer in PVT. + + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + drop_rate (float): Probability of an element to be zeroed. + after the feed forward layer. Default: 0.0. + attn_drop_rate (float): The drop out rate for attention layer. + Default: 0.0. + drop_path_rate (float): stochastic depth rate. Default: 0.0. + qkv_bias (bool): enable bias for qkv if True. + Default: True. + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + sr_ratio (int): The ratio of spatial reduction of Spatial Reduction + Attention of PVT. Default: 1. + use_conv_ffn (bool): If True, use Convolutional FFN to replace FFN. + Default: False. + init_cfg (dict, optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + qkv_bias=True, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + sr_ratio=1, + use_conv_ffn=False, + init_cfg=None): + super(PVTEncoderLayer, self).__init__(init_cfg=init_cfg) + + # The ret[0] of build_norm_layer is norm name. + self.norm1 = build_norm_layer(norm_cfg, embed_dims)[1] + + self.attn = SpatialReductionAttention( + embed_dims=embed_dims, + num_heads=num_heads, + attn_drop=attn_drop_rate, + proj_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + qkv_bias=qkv_bias, + norm_cfg=norm_cfg, + sr_ratio=sr_ratio) + + # The ret[0] of build_norm_layer is norm name. + self.norm2 = build_norm_layer(norm_cfg, embed_dims)[1] + + self.ffn = MixFFN( + embed_dims=embed_dims, + feedforward_channels=feedforward_channels, + ffn_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + use_conv=use_conv_ffn, + act_cfg=act_cfg) + + def forward(self, x, hw_shape): + x = self.attn(self.norm1(x), hw_shape, identity=x) + x = self.ffn(self.norm2(x), hw_shape, identity=x) + + return x + + +class AbsolutePositionEmbedding(BaseModule): + """An implementation of the absolute position embedding in PVT. + + Args: + pos_shape (int): The shape of the absolute position embedding. + pos_dim (int): The dimension of the absolute position embedding. + drop_rate (float): Probability of an element to be zeroed. + Default: 0.0. + """ + + def __init__(self, pos_shape, pos_dim, drop_rate=0., init_cfg=None): + super().__init__(init_cfg=init_cfg) + + if isinstance(pos_shape, int): + pos_shape = to_2tuple(pos_shape) + elif isinstance(pos_shape, tuple): + if len(pos_shape) == 1: + pos_shape = to_2tuple(pos_shape[0]) + assert len(pos_shape) == 2, \ + f'The size of image should have length 1 or 2, ' \ + f'but got {len(pos_shape)}' + self.pos_shape = pos_shape + self.pos_dim = pos_dim + + self.pos_embed = nn.Parameter( + torch.zeros(1, pos_shape[0] * pos_shape[1], pos_dim)) + self.drop = nn.Dropout(p=drop_rate) + + def init_weights(self): + trunc_normal_(self.pos_embed, std=0.02) + + def resize_pos_embed(self, pos_embed, input_shape, mode='bilinear'): + """Resize pos_embed weights. + + Resize pos_embed using bilinear interpolate method. + + Args: + pos_embed (torch.Tensor): Position embedding weights. + input_shape (tuple): Tuple for (downsampled input image height, + downsampled input image width). + mode (str): Algorithm used for upsampling: + ``'nearest'`` | ``'linear'`` | ``'bilinear'`` | ``'bicubic'`` | + ``'trilinear'``. Default: ``'bilinear'``. + + Return: + torch.Tensor: The resized pos_embed of shape [B, L_new, C]. + """ + assert pos_embed.ndim == 3, 'shape of pos_embed must be [B, L, C]' + pos_h, pos_w = self.pos_shape + pos_embed_weight = pos_embed[:, (-1 * pos_h * pos_w):] + pos_embed_weight = pos_embed_weight.reshape( + 1, pos_h, pos_w, self.pos_dim).permute(0, 3, 1, 2).contiguous() + pos_embed_weight = F.interpolate( + pos_embed_weight, size=input_shape, mode=mode) + pos_embed_weight = torch.flatten(pos_embed_weight, + 2).transpose(1, 2).contiguous() + pos_embed = pos_embed_weight + + return pos_embed + + def forward(self, x, hw_shape, mode='bilinear'): + pos_embed = self.resize_pos_embed(self.pos_embed, hw_shape, mode) + return self.drop(x + pos_embed) + + +@MODELS.register_module() +class PyramidVisionTransformer(BaseModule): + """Pyramid Vision Transformer (PVT) + + Implementation of `Pyramid Vision Transformer: A Versatile Backbone for + Dense Prediction without Convolutions + `_. + + Args: + pretrain_img_size (int | tuple[int]): The size of input image when + pretrain. Defaults: 224. + in_channels (int): Number of input channels. Default: 3. + embed_dims (int): Embedding dimension. Default: 64. + num_stags (int): The num of stages. Default: 4. + num_layers (Sequence[int]): The layer number of each transformer encode + layer. Default: [3, 4, 6, 3]. + num_heads (Sequence[int]): The attention heads of each transformer + encode layer. Default: [1, 2, 5, 8]. + patch_sizes (Sequence[int]): The patch_size of each patch embedding. + Default: [4, 2, 2, 2]. + strides (Sequence[int]): The stride of each patch embedding. + Default: [4, 2, 2, 2]. + paddings (Sequence[int]): The padding of each patch embedding. + Default: [0, 0, 0, 0]. + sr_ratios (Sequence[int]): The spatial reduction rate of each + transformer encode layer. Default: [8, 4, 2, 1]. + out_indices (Sequence[int] | int): Output from which stages. + Default: (0, 1, 2, 3). + mlp_ratios (Sequence[int]): The ratio of the mlp hidden dim to the + embedding dim of each transformer encode layer. + Default: [8, 8, 4, 4]. + qkv_bias (bool): Enable bias for qkv if True. Default: True. + drop_rate (float): Probability of an element to be zeroed. + Default 0.0. + attn_drop_rate (float): The drop out rate for attention layer. + Default 0.0. + drop_path_rate (float): stochastic depth rate. Default 0.1. + use_abs_pos_embed (bool): If True, add absolute position embedding to + the patch embedding. Defaults: True. + use_conv_ffn (bool): If True, use Convolutional FFN to replace FFN. + Default: False. + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + pretrained (str, optional): model pretrained path. Default: None. + convert_weights (bool): The flag indicates whether the + pre-trained model is from the original repo. We may need + to convert some keys to make it compatible. + Default: True. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + pretrain_img_size=224, + in_channels=3, + embed_dims=64, + num_stages=4, + num_layers=[3, 4, 6, 3], + num_heads=[1, 2, 5, 8], + patch_sizes=[4, 2, 2, 2], + strides=[4, 2, 2, 2], + paddings=[0, 0, 0, 0], + sr_ratios=[8, 4, 2, 1], + out_indices=(0, 1, 2, 3), + mlp_ratios=[8, 8, 4, 4], + qkv_bias=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.1, + use_abs_pos_embed=True, + norm_after_stage=False, + use_conv_ffn=False, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN', eps=1e-6), + pretrained=None, + convert_weights=True, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.convert_weights = convert_weights + if isinstance(pretrain_img_size, int): + pretrain_img_size = to_2tuple(pretrain_img_size) + elif isinstance(pretrain_img_size, tuple): + if len(pretrain_img_size) == 1: + pretrain_img_size = to_2tuple(pretrain_img_size[0]) + assert len(pretrain_img_size) == 2, \ + f'The size of image should have length 1 or 2, ' \ + f'but got {len(pretrain_img_size)}' + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be setting at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + self.init_cfg = init_cfg + else: + raise TypeError('pretrained must be a str or None') + + self.embed_dims = embed_dims + + self.num_stages = num_stages + self.num_layers = num_layers + self.num_heads = num_heads + self.patch_sizes = patch_sizes + self.strides = strides + self.sr_ratios = sr_ratios + assert num_stages == len(num_layers) == len(num_heads) \ + == len(patch_sizes) == len(strides) == len(sr_ratios) + + self.out_indices = out_indices + assert max(out_indices) < self.num_stages + self.pretrained = pretrained + + # transformer encoder + dpr = [ + x.item() + for x in torch.linspace(0, drop_path_rate, sum(num_layers)) + ] # stochastic num_layer decay rule + + cur = 0 + self.layers = ModuleList() + for i, num_layer in enumerate(num_layers): + embed_dims_i = embed_dims * num_heads[i] + patch_embed = PatchEmbed( + in_channels=in_channels, + embed_dims=embed_dims_i, + kernel_size=patch_sizes[i], + stride=strides[i], + padding=paddings[i], + bias=True, + norm_cfg=norm_cfg) + + layers = ModuleList() + if use_abs_pos_embed: + pos_shape = pretrain_img_size // np.prod(patch_sizes[:i + 1]) + pos_embed = AbsolutePositionEmbedding( + pos_shape=pos_shape, + pos_dim=embed_dims_i, + drop_rate=drop_rate) + layers.append(pos_embed) + layers.extend([ + PVTEncoderLayer( + embed_dims=embed_dims_i, + num_heads=num_heads[i], + feedforward_channels=mlp_ratios[i] * embed_dims_i, + drop_rate=drop_rate, + attn_drop_rate=attn_drop_rate, + drop_path_rate=dpr[cur + idx], + qkv_bias=qkv_bias, + act_cfg=act_cfg, + norm_cfg=norm_cfg, + sr_ratio=sr_ratios[i], + use_conv_ffn=use_conv_ffn) for idx in range(num_layer) + ]) + in_channels = embed_dims_i + # The ret[0] of build_norm_layer is norm name. + if norm_after_stage: + norm = build_norm_layer(norm_cfg, embed_dims_i)[1] + else: + norm = nn.Identity() + self.layers.append(ModuleList([patch_embed, layers, norm])) + cur += num_layer + + def init_weights(self): + logger = MMLogger.get_current_instance() + if self.init_cfg is None: + logger.warn(f'No pre-trained weights for ' + f'{self.__class__.__name__}, ' + f'training start from scratch') + for m in self.modules(): + if isinstance(m, nn.Linear): + trunc_normal_init(m, std=.02, bias=0.) + elif isinstance(m, nn.LayerNorm): + constant_init(m, 1.0) + elif isinstance(m, nn.Conv2d): + fan_out = m.kernel_size[0] * m.kernel_size[ + 1] * m.out_channels + fan_out //= m.groups + normal_init(m, 0, math.sqrt(2.0 / fan_out)) + elif isinstance(m, AbsolutePositionEmbedding): + m.init_weights() + else: + assert 'checkpoint' in self.init_cfg, f'Only support ' \ + f'specify `Pretrained` in ' \ + f'`init_cfg` in ' \ + f'{self.__class__.__name__} ' + checkpoint = CheckpointLoader.load_checkpoint( + self.init_cfg.checkpoint, logger=logger, map_location='cpu') + logger.warn(f'Load pre-trained model for ' + f'{self.__class__.__name__} from original repo') + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + elif 'model' in checkpoint: + state_dict = checkpoint['model'] + else: + state_dict = checkpoint + if self.convert_weights: + # Because pvt backbones are not supported by mmpretrain, + # so we need to convert pre-trained weights to match this + # implementation. + state_dict = pvt_convert(state_dict) + load_state_dict(self, state_dict, strict=False, logger=logger) + + def forward(self, x): + outs = [] + + for i, layer in enumerate(self.layers): + x, hw_shape = layer[0](x) + + for block in layer[1]: + x = block(x, hw_shape) + x = layer[2](x) + x = nlc_to_nchw(x, hw_shape) + if i in self.out_indices: + outs.append(x) + + return outs + + +@MODELS.register_module() +class PyramidVisionTransformerV2(PyramidVisionTransformer): + """Implementation of `PVTv2: Improved Baselines with Pyramid Vision + Transformer `_.""" + + def __init__(self, **kwargs): + super(PyramidVisionTransformerV2, self).__init__( + patch_sizes=[7, 3, 3, 3], + paddings=[3, 1, 1, 1], + use_abs_pos_embed=False, + norm_after_stage=True, + use_conv_ffn=True, + **kwargs) + + +def pvt_convert(ckpt): + new_ckpt = OrderedDict() + # Process the concat between q linear weights and kv linear weights + use_abs_pos_embed = False + use_conv_ffn = False + for k in ckpt.keys(): + if k.startswith('pos_embed'): + use_abs_pos_embed = True + if k.find('dwconv') >= 0: + use_conv_ffn = True + for k, v in ckpt.items(): + if k.startswith('head'): + continue + if k.startswith('norm.'): + continue + if k.startswith('cls_token'): + continue + if k.startswith('pos_embed'): + stage_i = int(k.replace('pos_embed', '')) + new_k = k.replace(f'pos_embed{stage_i}', + f'layers.{stage_i - 1}.1.0.pos_embed') + if stage_i == 4 and v.size(1) == 50: # 1 (cls token) + 7 * 7 + new_v = v[:, 1:, :] # remove cls token + else: + new_v = v + elif k.startswith('patch_embed'): + stage_i = int(k.split('.')[0].replace('patch_embed', '')) + new_k = k.replace(f'patch_embed{stage_i}', + f'layers.{stage_i - 1}.0') + new_v = v + if 'proj.' in new_k: + new_k = new_k.replace('proj.', 'projection.') + elif k.startswith('block'): + stage_i = int(k.split('.')[0].replace('block', '')) + layer_i = int(k.split('.')[1]) + new_layer_i = layer_i + use_abs_pos_embed + new_k = k.replace(f'block{stage_i}.{layer_i}', + f'layers.{stage_i - 1}.1.{new_layer_i}') + new_v = v + if 'attn.q.' in new_k: + sub_item_k = k.replace('q.', 'kv.') + new_k = new_k.replace('q.', 'attn.in_proj_') + new_v = torch.cat([v, ckpt[sub_item_k]], dim=0) + elif 'attn.kv.' in new_k: + continue + elif 'attn.proj.' in new_k: + new_k = new_k.replace('proj.', 'attn.out_proj.') + elif 'attn.sr.' in new_k: + new_k = new_k.replace('sr.', 'sr.') + elif 'mlp.' in new_k: + string = f'{new_k}-' + new_k = new_k.replace('mlp.', 'ffn.layers.') + if 'fc1.weight' in new_k or 'fc2.weight' in new_k: + new_v = v.reshape((*v.shape, 1, 1)) + new_k = new_k.replace('fc1.', '0.') + new_k = new_k.replace('dwconv.dwconv.', '1.') + if use_conv_ffn: + new_k = new_k.replace('fc2.', '4.') + else: + new_k = new_k.replace('fc2.', '3.') + string += f'{new_k} {v.shape}-{new_v.shape}' + elif k.startswith('norm'): + stage_i = int(k[4]) + new_k = k.replace(f'norm{stage_i}', f'layers.{stage_i - 1}.2') + new_v = v + else: + new_k = k + new_v = v + new_ckpt[new_k] = new_v + + return new_ckpt diff --git a/mmdetection/mmdet/models/backbones/regnet.py b/mmdetection/mmdet/models/backbones/regnet.py new file mode 100644 index 00000000..55d3ce07 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/regnet.py @@ -0,0 +1,356 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import numpy as np +import torch.nn as nn +from mmcv.cnn import build_conv_layer, build_norm_layer + +from mmdet.registry import MODELS +from .resnet import ResNet +from .resnext import Bottleneck + + +@MODELS.register_module() +class RegNet(ResNet): + """RegNet backbone. + + More details can be found in `paper `_ . + + Args: + arch (dict): The parameter of RegNets. + + - w0 (int): initial width + - wa (float): slope of width + - wm (float): quantization parameter to quantize the width + - depth (int): depth of the backbone + - group_w (int): width of group + - bot_mul (float): bottleneck ratio, i.e. expansion of bottleneck. + strides (Sequence[int]): Strides of the first block of each stage. + base_channels (int): Base channels after stem layer. + in_channels (int): Number of input image channels. Default: 3. + dilations (Sequence[int]): Dilation of each stage. + out_indices (Sequence[int]): Output from which stages. + style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two + layer is the 3x3 conv layer, otherwise the stride-two layer is + the first 1x1 conv layer. + frozen_stages (int): Stages to be frozen (all param fixed). -1 means + not freezing any parameters. + norm_cfg (dict): dictionary to construct and config norm layer. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. + zero_init_residual (bool): whether to use zero init for last norm layer + in resblocks to let them behave as identity. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + + Example: + >>> from mmdet.models import RegNet + >>> import torch + >>> self = RegNet( + arch=dict( + w0=88, + wa=26.31, + wm=2.25, + group_w=48, + depth=25, + bot_mul=1.0)) + >>> self.eval() + >>> inputs = torch.rand(1, 3, 32, 32) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + (1, 96, 8, 8) + (1, 192, 4, 4) + (1, 432, 2, 2) + (1, 1008, 1, 1) + """ + arch_settings = { + 'regnetx_400mf': + dict(w0=24, wa=24.48, wm=2.54, group_w=16, depth=22, bot_mul=1.0), + 'regnetx_800mf': + dict(w0=56, wa=35.73, wm=2.28, group_w=16, depth=16, bot_mul=1.0), + 'regnetx_1.6gf': + dict(w0=80, wa=34.01, wm=2.25, group_w=24, depth=18, bot_mul=1.0), + 'regnetx_3.2gf': + dict(w0=88, wa=26.31, wm=2.25, group_w=48, depth=25, bot_mul=1.0), + 'regnetx_4.0gf': + dict(w0=96, wa=38.65, wm=2.43, group_w=40, depth=23, bot_mul=1.0), + 'regnetx_6.4gf': + dict(w0=184, wa=60.83, wm=2.07, group_w=56, depth=17, bot_mul=1.0), + 'regnetx_8.0gf': + dict(w0=80, wa=49.56, wm=2.88, group_w=120, depth=23, bot_mul=1.0), + 'regnetx_12gf': + dict(w0=168, wa=73.36, wm=2.37, group_w=112, depth=19, bot_mul=1.0), + } + + def __init__(self, + arch, + in_channels=3, + stem_channels=32, + base_channels=32, + strides=(2, 2, 2, 2), + dilations=(1, 1, 1, 1), + out_indices=(0, 1, 2, 3), + style='pytorch', + deep_stem=False, + avg_down=False, + frozen_stages=-1, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + dcn=None, + stage_with_dcn=(False, False, False, False), + plugins=None, + with_cp=False, + zero_init_residual=True, + pretrained=None, + init_cfg=None): + super(ResNet, self).__init__(init_cfg) + + # Generate RegNet parameters first + if isinstance(arch, str): + assert arch in self.arch_settings, \ + f'"arch": "{arch}" is not one of the' \ + ' arch_settings' + arch = self.arch_settings[arch] + elif not isinstance(arch, dict): + raise ValueError('Expect "arch" to be either a string ' + f'or a dict, got {type(arch)}') + + widths, num_stages = self.generate_regnet( + arch['w0'], + arch['wa'], + arch['wm'], + arch['depth'], + ) + # Convert to per stage format + stage_widths, stage_blocks = self.get_stages_from_blocks(widths) + # Generate group widths and bot muls + group_widths = [arch['group_w'] for _ in range(num_stages)] + self.bottleneck_ratio = [arch['bot_mul'] for _ in range(num_stages)] + # Adjust the compatibility of stage_widths and group_widths + stage_widths, group_widths = self.adjust_width_group( + stage_widths, self.bottleneck_ratio, group_widths) + + # Group params by stage + self.stage_widths = stage_widths + self.group_widths = group_widths + self.depth = sum(stage_blocks) + self.stem_channels = stem_channels + self.base_channels = base_channels + self.num_stages = num_stages + assert num_stages >= 1 and num_stages <= 4 + self.strides = strides + self.dilations = dilations + assert len(strides) == len(dilations) == num_stages + self.out_indices = out_indices + assert max(out_indices) < num_stages + self.style = style + self.deep_stem = deep_stem + self.avg_down = avg_down + self.frozen_stages = frozen_stages + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.with_cp = with_cp + self.norm_eval = norm_eval + self.dcn = dcn + self.stage_with_dcn = stage_with_dcn + if dcn is not None: + assert len(stage_with_dcn) == num_stages + self.plugins = plugins + self.zero_init_residual = zero_init_residual + self.block = Bottleneck + expansion_bak = self.block.expansion + self.block.expansion = 1 + self.stage_blocks = stage_blocks[:num_stages] + + self._make_stem_layer(in_channels, stem_channels) + + block_init_cfg = None + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + if self.zero_init_residual: + block_init_cfg = dict( + type='Constant', val=0, override=dict(name='norm3')) + else: + raise TypeError('pretrained must be a str or None') + + self.inplanes = stem_channels + self.res_layers = [] + for i, num_blocks in enumerate(self.stage_blocks): + stride = self.strides[i] + dilation = self.dilations[i] + group_width = self.group_widths[i] + width = int(round(self.stage_widths[i] * self.bottleneck_ratio[i])) + stage_groups = width // group_width + + dcn = self.dcn if self.stage_with_dcn[i] else None + if self.plugins is not None: + stage_plugins = self.make_stage_plugins(self.plugins, i) + else: + stage_plugins = None + + res_layer = self.make_res_layer( + block=self.block, + inplanes=self.inplanes, + planes=self.stage_widths[i], + num_blocks=num_blocks, + stride=stride, + dilation=dilation, + style=self.style, + avg_down=self.avg_down, + with_cp=self.with_cp, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + dcn=dcn, + plugins=stage_plugins, + groups=stage_groups, + base_width=group_width, + base_channels=self.stage_widths[i], + init_cfg=block_init_cfg) + self.inplanes = self.stage_widths[i] + layer_name = f'layer{i + 1}' + self.add_module(layer_name, res_layer) + self.res_layers.append(layer_name) + + self._freeze_stages() + + self.feat_dim = stage_widths[-1] + self.block.expansion = expansion_bak + + def _make_stem_layer(self, in_channels, base_channels): + self.conv1 = build_conv_layer( + self.conv_cfg, + in_channels, + base_channels, + kernel_size=3, + stride=2, + padding=1, + bias=False) + self.norm1_name, norm1 = build_norm_layer( + self.norm_cfg, base_channels, postfix=1) + self.add_module(self.norm1_name, norm1) + self.relu = nn.ReLU(inplace=True) + + def generate_regnet(self, + initial_width, + width_slope, + width_parameter, + depth, + divisor=8): + """Generates per block width from RegNet parameters. + + Args: + initial_width ([int]): Initial width of the backbone + width_slope ([float]): Slope of the quantized linear function + width_parameter ([int]): Parameter used to quantize the width. + depth ([int]): Depth of the backbone. + divisor (int, optional): The divisor of channels. Defaults to 8. + + Returns: + list, int: return a list of widths of each stage and the number \ + of stages + """ + assert width_slope >= 0 + assert initial_width > 0 + assert width_parameter > 1 + assert initial_width % divisor == 0 + widths_cont = np.arange(depth) * width_slope + initial_width + ks = np.round( + np.log(widths_cont / initial_width) / np.log(width_parameter)) + widths = initial_width * np.power(width_parameter, ks) + widths = np.round(np.divide(widths, divisor)) * divisor + num_stages = len(np.unique(widths)) + widths, widths_cont = widths.astype(int).tolist(), widths_cont.tolist() + return widths, num_stages + + @staticmethod + def quantize_float(number, divisor): + """Converts a float to closest non-zero int divisible by divisor. + + Args: + number (int): Original number to be quantized. + divisor (int): Divisor used to quantize the number. + + Returns: + int: quantized number that is divisible by devisor. + """ + return int(round(number / divisor) * divisor) + + def adjust_width_group(self, widths, bottleneck_ratio, groups): + """Adjusts the compatibility of widths and groups. + + Args: + widths (list[int]): Width of each stage. + bottleneck_ratio (float): Bottleneck ratio. + groups (int): number of groups in each stage + + Returns: + tuple(list): The adjusted widths and groups of each stage. + """ + bottleneck_width = [ + int(w * b) for w, b in zip(widths, bottleneck_ratio) + ] + groups = [min(g, w_bot) for g, w_bot in zip(groups, bottleneck_width)] + bottleneck_width = [ + self.quantize_float(w_bot, g) + for w_bot, g in zip(bottleneck_width, groups) + ] + widths = [ + int(w_bot / b) + for w_bot, b in zip(bottleneck_width, bottleneck_ratio) + ] + return widths, groups + + def get_stages_from_blocks(self, widths): + """Gets widths/stage_blocks of network at each stage. + + Args: + widths (list[int]): Width in each stage. + + Returns: + tuple(list): width and depth of each stage + """ + width_diff = [ + width != width_prev + for width, width_prev in zip(widths + [0], [0] + widths) + ] + stage_widths = [ + width for width, diff in zip(widths, width_diff[:-1]) if diff + ] + stage_blocks = np.diff([ + depth for depth, diff in zip(range(len(width_diff)), width_diff) + if diff + ]).tolist() + return stage_widths, stage_blocks + + def forward(self, x): + """Forward function.""" + x = self.conv1(x) + x = self.norm1(x) + x = self.relu(x) + + outs = [] + for i, layer_name in enumerate(self.res_layers): + res_layer = getattr(self, layer_name) + x = res_layer(x) + if i in self.out_indices: + outs.append(x) + return tuple(outs) diff --git a/mmdetection/mmdet/models/backbones/res2net.py b/mmdetection/mmdet/models/backbones/res2net.py new file mode 100644 index 00000000..958fc884 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/res2net.py @@ -0,0 +1,327 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn import build_conv_layer, build_norm_layer +from mmengine.model import Sequential + +from mmdet.registry import MODELS +from .resnet import Bottleneck as _Bottleneck +from .resnet import ResNet + + +class Bottle2neck(_Bottleneck): + expansion = 4 + + def __init__(self, + inplanes, + planes, + scales=4, + base_width=26, + base_channels=64, + stage_type='normal', + **kwargs): + """Bottle2neck block for Res2Net. + + If style is "pytorch", the stride-two layer is the 3x3 conv layer, if + it is "caffe", the stride-two layer is the first 1x1 conv layer. + """ + super(Bottle2neck, self).__init__(inplanes, planes, **kwargs) + assert scales > 1, 'Res2Net degenerates to ResNet when scales = 1.' + width = int(math.floor(self.planes * (base_width / base_channels))) + + self.norm1_name, norm1 = build_norm_layer( + self.norm_cfg, width * scales, postfix=1) + self.norm3_name, norm3 = build_norm_layer( + self.norm_cfg, self.planes * self.expansion, postfix=3) + + self.conv1 = build_conv_layer( + self.conv_cfg, + self.inplanes, + width * scales, + kernel_size=1, + stride=self.conv1_stride, + bias=False) + self.add_module(self.norm1_name, norm1) + + if stage_type == 'stage' and self.conv2_stride != 1: + self.pool = nn.AvgPool2d( + kernel_size=3, stride=self.conv2_stride, padding=1) + convs = [] + bns = [] + + fallback_on_stride = False + if self.with_dcn: + fallback_on_stride = self.dcn.pop('fallback_on_stride', False) + if not self.with_dcn or fallback_on_stride: + for i in range(scales - 1): + convs.append( + build_conv_layer( + self.conv_cfg, + width, + width, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + bias=False)) + bns.append( + build_norm_layer(self.norm_cfg, width, postfix=i + 1)[1]) + self.convs = nn.ModuleList(convs) + self.bns = nn.ModuleList(bns) + else: + assert self.conv_cfg is None, 'conv_cfg must be None for DCN' + for i in range(scales - 1): + convs.append( + build_conv_layer( + self.dcn, + width, + width, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + bias=False)) + bns.append( + build_norm_layer(self.norm_cfg, width, postfix=i + 1)[1]) + self.convs = nn.ModuleList(convs) + self.bns = nn.ModuleList(bns) + + self.conv3 = build_conv_layer( + self.conv_cfg, + width * scales, + self.planes * self.expansion, + kernel_size=1, + bias=False) + self.add_module(self.norm3_name, norm3) + + self.stage_type = stage_type + self.scales = scales + self.width = width + delattr(self, 'conv2') + delattr(self, self.norm2_name) + + def forward(self, x): + """Forward function.""" + + def _inner_forward(x): + identity = x + + out = self.conv1(x) + out = self.norm1(out) + out = self.relu(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv1_plugin_names) + + spx = torch.split(out, self.width, 1) + sp = self.convs[0](spx[0].contiguous()) + sp = self.relu(self.bns[0](sp)) + out = sp + for i in range(1, self.scales - 1): + if self.stage_type == 'stage': + sp = spx[i] + else: + sp = sp + spx[i] + sp = self.convs[i](sp.contiguous()) + sp = self.relu(self.bns[i](sp)) + out = torch.cat((out, sp), 1) + + if self.stage_type == 'normal' or self.conv2_stride == 1: + out = torch.cat((out, spx[self.scales - 1]), 1) + elif self.stage_type == 'stage': + out = torch.cat((out, self.pool(spx[self.scales - 1])), 1) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv2_plugin_names) + + out = self.conv3(out) + out = self.norm3(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv3_plugin_names) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + out = self.relu(out) + + return out + + +class Res2Layer(Sequential): + """Res2Layer to build Res2Net style backbone. + + Args: + block (nn.Module): block used to build ResLayer. + inplanes (int): inplanes of block. + planes (int): planes of block. + num_blocks (int): number of blocks. + stride (int): stride of the first block. Default: 1 + avg_down (bool): Use AvgPool instead of stride conv when + downsampling in the bottle2neck. Default: False + conv_cfg (dict): dictionary to construct and config conv layer. + Default: None + norm_cfg (dict): dictionary to construct and config norm layer. + Default: dict(type='BN') + scales (int): Scales used in Res2Net. Default: 4 + base_width (int): Basic width of each scale. Default: 26 + """ + + def __init__(self, + block, + inplanes, + planes, + num_blocks, + stride=1, + avg_down=True, + conv_cfg=None, + norm_cfg=dict(type='BN'), + scales=4, + base_width=26, + **kwargs): + self.block = block + + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.AvgPool2d( + kernel_size=stride, + stride=stride, + ceil_mode=True, + count_include_pad=False), + build_conv_layer( + conv_cfg, + inplanes, + planes * block.expansion, + kernel_size=1, + stride=1, + bias=False), + build_norm_layer(norm_cfg, planes * block.expansion)[1], + ) + + layers = [] + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=stride, + downsample=downsample, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + scales=scales, + base_width=base_width, + stage_type='stage', + **kwargs)) + inplanes = planes * block.expansion + for i in range(1, num_blocks): + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + scales=scales, + base_width=base_width, + **kwargs)) + super(Res2Layer, self).__init__(*layers) + + +@MODELS.register_module() +class Res2Net(ResNet): + """Res2Net backbone. + + Args: + scales (int): Scales used in Res2Net. Default: 4 + base_width (int): Basic width of each scale. Default: 26 + depth (int): Depth of res2net, from {50, 101, 152}. + in_channels (int): Number of input image channels. Default: 3. + num_stages (int): Res2net stages. Default: 4. + strides (Sequence[int]): Strides of the first block of each stage. + dilations (Sequence[int]): Dilation of each stage. + out_indices (Sequence[int]): Output from which stages. + style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two + layer is the 3x3 conv layer, otherwise the stride-two layer is + the first 1x1 conv layer. + deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv + avg_down (bool): Use AvgPool instead of stride conv when + downsampling in the bottle2neck. + frozen_stages (int): Stages to be frozen (stop grad and set eval mode). + -1 means not freezing any parameters. + norm_cfg (dict): Dictionary to construct and config norm layer. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. + plugins (list[dict]): List of plugins for stages, each dict contains: + + - cfg (dict, required): Cfg dict to build plugin. + - position (str, required): Position inside block to insert + plugin, options are 'after_conv1', 'after_conv2', 'after_conv3'. + - stages (tuple[bool], optional): Stages to apply plugin, length + should be same as 'num_stages'. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. + zero_init_residual (bool): Whether to use zero init for last norm layer + in resblocks to let them behave as identity. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + + Example: + >>> from mmdet.models import Res2Net + >>> import torch + >>> self = Res2Net(depth=50, scales=4, base_width=26) + >>> self.eval() + >>> inputs = torch.rand(1, 3, 32, 32) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + (1, 256, 8, 8) + (1, 512, 4, 4) + (1, 1024, 2, 2) + (1, 2048, 1, 1) + """ + + arch_settings = { + 50: (Bottle2neck, (3, 4, 6, 3)), + 101: (Bottle2neck, (3, 4, 23, 3)), + 152: (Bottle2neck, (3, 8, 36, 3)) + } + + def __init__(self, + scales=4, + base_width=26, + style='pytorch', + deep_stem=True, + avg_down=True, + pretrained=None, + init_cfg=None, + **kwargs): + self.scales = scales + self.base_width = base_width + super(Res2Net, self).__init__( + style='pytorch', + deep_stem=True, + avg_down=True, + pretrained=pretrained, + init_cfg=init_cfg, + **kwargs) + + def make_res_layer(self, **kwargs): + return Res2Layer( + scales=self.scales, + base_width=self.base_width, + base_channels=self.base_channels, + **kwargs) diff --git a/mmdetection/mmdet/models/backbones/resnest.py b/mmdetection/mmdet/models/backbones/resnest.py new file mode 100644 index 00000000..d4466c4c --- /dev/null +++ b/mmdetection/mmdet/models/backbones/resnest.py @@ -0,0 +1,322 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as cp +from mmcv.cnn import build_conv_layer, build_norm_layer +from mmengine.model import BaseModule + +from mmdet.registry import MODELS +from ..layers import ResLayer +from .resnet import Bottleneck as _Bottleneck +from .resnet import ResNetV1d + + +class RSoftmax(nn.Module): + """Radix Softmax module in ``SplitAttentionConv2d``. + + Args: + radix (int): Radix of input. + groups (int): Groups of input. + """ + + def __init__(self, radix, groups): + super().__init__() + self.radix = radix + self.groups = groups + + def forward(self, x): + batch = x.size(0) + if self.radix > 1: + x = x.view(batch, self.groups, self.radix, -1).transpose(1, 2) + x = F.softmax(x, dim=1) + x = x.reshape(batch, -1) + else: + x = torch.sigmoid(x) + return x + + +class SplitAttentionConv2d(BaseModule): + """Split-Attention Conv2d in ResNeSt. + + Args: + in_channels (int): Number of channels in the input feature map. + channels (int): Number of intermediate channels. + kernel_size (int | tuple[int]): Size of the convolution kernel. + stride (int | tuple[int]): Stride of the convolution. + padding (int | tuple[int]): Zero-padding added to both sides of + dilation (int | tuple[int]): Spacing between kernel elements. + groups (int): Number of blocked connections from input channels to + output channels. + groups (int): Same as nn.Conv2d. + radix (int): Radix of SpltAtConv2d. Default: 2 + reduction_factor (int): Reduction factor of inter_channels. Default: 4. + conv_cfg (dict): Config dict for convolution layer. Default: None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. Default: None. + dcn (dict): Config dict for DCN. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + in_channels, + channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + radix=2, + reduction_factor=4, + conv_cfg=None, + norm_cfg=dict(type='BN'), + dcn=None, + init_cfg=None): + super(SplitAttentionConv2d, self).__init__(init_cfg) + inter_channels = max(in_channels * radix // reduction_factor, 32) + self.radix = radix + self.groups = groups + self.channels = channels + self.with_dcn = dcn is not None + self.dcn = dcn + fallback_on_stride = False + if self.with_dcn: + fallback_on_stride = self.dcn.pop('fallback_on_stride', False) + if self.with_dcn and not fallback_on_stride: + assert conv_cfg is None, 'conv_cfg must be None for DCN' + conv_cfg = dcn + self.conv = build_conv_layer( + conv_cfg, + in_channels, + channels * radix, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups * radix, + bias=False) + # To be consistent with original implementation, starting from 0 + self.norm0_name, norm0 = build_norm_layer( + norm_cfg, channels * radix, postfix=0) + self.add_module(self.norm0_name, norm0) + self.relu = nn.ReLU(inplace=True) + self.fc1 = build_conv_layer( + None, channels, inter_channels, 1, groups=self.groups) + self.norm1_name, norm1 = build_norm_layer( + norm_cfg, inter_channels, postfix=1) + self.add_module(self.norm1_name, norm1) + self.fc2 = build_conv_layer( + None, inter_channels, channels * radix, 1, groups=self.groups) + self.rsoftmax = RSoftmax(radix, groups) + + @property + def norm0(self): + """nn.Module: the normalization layer named "norm0" """ + return getattr(self, self.norm0_name) + + @property + def norm1(self): + """nn.Module: the normalization layer named "norm1" """ + return getattr(self, self.norm1_name) + + def forward(self, x): + x = self.conv(x) + x = self.norm0(x) + x = self.relu(x) + + batch, rchannel = x.shape[:2] + batch = x.size(0) + if self.radix > 1: + splits = x.view(batch, self.radix, -1, *x.shape[2:]) + gap = splits.sum(dim=1) + else: + gap = x + gap = F.adaptive_avg_pool2d(gap, 1) + gap = self.fc1(gap) + + gap = self.norm1(gap) + gap = self.relu(gap) + + atten = self.fc2(gap) + atten = self.rsoftmax(atten).view(batch, -1, 1, 1) + + if self.radix > 1: + attens = atten.view(batch, self.radix, -1, *atten.shape[2:]) + out = torch.sum(attens * splits, dim=1) + else: + out = atten * x + return out.contiguous() + + +class Bottleneck(_Bottleneck): + """Bottleneck block for ResNeSt. + + Args: + inplane (int): Input planes of this block. + planes (int): Middle planes of this block. + groups (int): Groups of conv2. + base_width (int): Base of width in terms of base channels. Default: 4. + base_channels (int): Base of channels for calculating width. + Default: 64. + radix (int): Radix of SpltAtConv2d. Default: 2 + reduction_factor (int): Reduction factor of inter_channels in + SplitAttentionConv2d. Default: 4. + avg_down_stride (bool): Whether to use average pool for stride in + Bottleneck. Default: True. + kwargs (dict): Key word arguments for base class. + """ + expansion = 4 + + def __init__(self, + inplanes, + planes, + groups=1, + base_width=4, + base_channels=64, + radix=2, + reduction_factor=4, + avg_down_stride=True, + **kwargs): + """Bottleneck block for ResNeSt.""" + super(Bottleneck, self).__init__(inplanes, planes, **kwargs) + + if groups == 1: + width = self.planes + else: + width = math.floor(self.planes * + (base_width / base_channels)) * groups + + self.avg_down_stride = avg_down_stride and self.conv2_stride > 1 + + self.norm1_name, norm1 = build_norm_layer( + self.norm_cfg, width, postfix=1) + self.norm3_name, norm3 = build_norm_layer( + self.norm_cfg, self.planes * self.expansion, postfix=3) + + self.conv1 = build_conv_layer( + self.conv_cfg, + self.inplanes, + width, + kernel_size=1, + stride=self.conv1_stride, + bias=False) + self.add_module(self.norm1_name, norm1) + self.with_modulated_dcn = False + self.conv2 = SplitAttentionConv2d( + width, + width, + kernel_size=3, + stride=1 if self.avg_down_stride else self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + groups=groups, + radix=radix, + reduction_factor=reduction_factor, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + dcn=self.dcn) + delattr(self, self.norm2_name) + + if self.avg_down_stride: + self.avd_layer = nn.AvgPool2d(3, self.conv2_stride, padding=1) + + self.conv3 = build_conv_layer( + self.conv_cfg, + width, + self.planes * self.expansion, + kernel_size=1, + bias=False) + self.add_module(self.norm3_name, norm3) + + def forward(self, x): + + def _inner_forward(x): + identity = x + + out = self.conv1(x) + out = self.norm1(out) + out = self.relu(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv1_plugin_names) + + out = self.conv2(out) + + if self.avg_down_stride: + out = self.avd_layer(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv2_plugin_names) + + out = self.conv3(out) + out = self.norm3(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv3_plugin_names) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + out = self.relu(out) + + return out + + +@MODELS.register_module() +class ResNeSt(ResNetV1d): + """ResNeSt backbone. + + Args: + groups (int): Number of groups of Bottleneck. Default: 1 + base_width (int): Base width of Bottleneck. Default: 4 + radix (int): Radix of SplitAttentionConv2d. Default: 2 + reduction_factor (int): Reduction factor of inter_channels in + SplitAttentionConv2d. Default: 4. + avg_down_stride (bool): Whether to use average pool for stride in + Bottleneck. Default: True. + kwargs (dict): Keyword arguments for ResNet. + """ + + arch_settings = { + 50: (Bottleneck, (3, 4, 6, 3)), + 101: (Bottleneck, (3, 4, 23, 3)), + 152: (Bottleneck, (3, 8, 36, 3)), + 200: (Bottleneck, (3, 24, 36, 3)) + } + + def __init__(self, + groups=1, + base_width=4, + radix=2, + reduction_factor=4, + avg_down_stride=True, + **kwargs): + self.groups = groups + self.base_width = base_width + self.radix = radix + self.reduction_factor = reduction_factor + self.avg_down_stride = avg_down_stride + super(ResNeSt, self).__init__(**kwargs) + + def make_res_layer(self, **kwargs): + """Pack all blocks in a stage into a ``ResLayer``.""" + return ResLayer( + groups=self.groups, + base_width=self.base_width, + base_channels=self.base_channels, + radix=self.radix, + reduction_factor=self.reduction_factor, + avg_down_stride=self.avg_down_stride, + **kwargs) diff --git a/mmdetection/mmdet/models/backbones/resnet.py b/mmdetection/mmdet/models/backbones/resnet.py new file mode 100644 index 00000000..1d6f48f9 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/resnet.py @@ -0,0 +1,672 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn import build_conv_layer, build_norm_layer, build_plugin_layer +from mmengine.model import BaseModule +from torch.nn.modules.batchnorm import _BatchNorm + +from mmdet.registry import MODELS +from ..layers import ResLayer + + +class BasicBlock(BaseModule): + expansion = 1 + + def __init__(self, + inplanes, + planes, + stride=1, + dilation=1, + downsample=None, + style='pytorch', + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + dcn=None, + plugins=None, + init_cfg=None): + super(BasicBlock, self).__init__(init_cfg) + assert dcn is None, 'Not implemented yet.' + assert plugins is None, 'Not implemented yet.' + + self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1) + self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2) + + self.conv1 = build_conv_layer( + conv_cfg, + inplanes, + planes, + 3, + stride=stride, + padding=dilation, + dilation=dilation, + bias=False) + self.add_module(self.norm1_name, norm1) + self.conv2 = build_conv_layer( + conv_cfg, planes, planes, 3, padding=1, bias=False) + self.add_module(self.norm2_name, norm2) + + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + self.dilation = dilation + self.with_cp = with_cp + + @property + def norm1(self): + """nn.Module: normalization layer after the first convolution layer""" + return getattr(self, self.norm1_name) + + @property + def norm2(self): + """nn.Module: normalization layer after the second convolution layer""" + return getattr(self, self.norm2_name) + + def forward(self, x): + """Forward function.""" + + def _inner_forward(x): + identity = x + + out = self.conv1(x) + out = self.norm1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.norm2(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + out = self.relu(out) + + return out + + +class Bottleneck(BaseModule): + expansion = 4 + + def __init__(self, + inplanes, + planes, + stride=1, + dilation=1, + downsample=None, + style='pytorch', + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + dcn=None, + plugins=None, + init_cfg=None): + """Bottleneck block for ResNet. + + If style is "pytorch", the stride-two layer is the 3x3 conv layer, if + it is "caffe", the stride-two layer is the first 1x1 conv layer. + """ + super(Bottleneck, self).__init__(init_cfg) + assert style in ['pytorch', 'caffe'] + assert dcn is None or isinstance(dcn, dict) + assert plugins is None or isinstance(plugins, list) + if plugins is not None: + allowed_position = ['after_conv1', 'after_conv2', 'after_conv3'] + assert all(p['position'] in allowed_position for p in plugins) + + self.inplanes = inplanes + self.planes = planes + self.stride = stride + self.dilation = dilation + self.style = style + self.with_cp = with_cp + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.dcn = dcn + self.with_dcn = dcn is not None + self.plugins = plugins + self.with_plugins = plugins is not None + + if self.with_plugins: + # collect plugins for conv1/conv2/conv3 + self.after_conv1_plugins = [ + plugin['cfg'] for plugin in plugins + if plugin['position'] == 'after_conv1' + ] + self.after_conv2_plugins = [ + plugin['cfg'] for plugin in plugins + if plugin['position'] == 'after_conv2' + ] + self.after_conv3_plugins = [ + plugin['cfg'] for plugin in plugins + if plugin['position'] == 'after_conv3' + ] + + if self.style == 'pytorch': + self.conv1_stride = 1 + self.conv2_stride = stride + else: + self.conv1_stride = stride + self.conv2_stride = 1 + + self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1) + self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2) + self.norm3_name, norm3 = build_norm_layer( + norm_cfg, planes * self.expansion, postfix=3) + + self.conv1 = build_conv_layer( + conv_cfg, + inplanes, + planes, + kernel_size=1, + stride=self.conv1_stride, + bias=False) + self.add_module(self.norm1_name, norm1) + fallback_on_stride = False + if self.with_dcn: + fallback_on_stride = dcn.pop('fallback_on_stride', False) + if not self.with_dcn or fallback_on_stride: + self.conv2 = build_conv_layer( + conv_cfg, + planes, + planes, + kernel_size=3, + stride=self.conv2_stride, + padding=dilation, + dilation=dilation, + bias=False) + else: + assert self.conv_cfg is None, 'conv_cfg must be None for DCN' + self.conv2 = build_conv_layer( + dcn, + planes, + planes, + kernel_size=3, + stride=self.conv2_stride, + padding=dilation, + dilation=dilation, + bias=False) + + self.add_module(self.norm2_name, norm2) + self.conv3 = build_conv_layer( + conv_cfg, + planes, + planes * self.expansion, + kernel_size=1, + bias=False) + self.add_module(self.norm3_name, norm3) + + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + + if self.with_plugins: + self.after_conv1_plugin_names = self.make_block_plugins( + planes, self.after_conv1_plugins) + self.after_conv2_plugin_names = self.make_block_plugins( + planes, self.after_conv2_plugins) + self.after_conv3_plugin_names = self.make_block_plugins( + planes * self.expansion, self.after_conv3_plugins) + + def make_block_plugins(self, in_channels, plugins): + """make plugins for block. + + Args: + in_channels (int): Input channels of plugin. + plugins (list[dict]): List of plugins cfg to build. + + Returns: + list[str]: List of the names of plugin. + """ + assert isinstance(plugins, list) + plugin_names = [] + for plugin in plugins: + plugin = plugin.copy() + name, layer = build_plugin_layer( + plugin, + in_channels=in_channels, + postfix=plugin.pop('postfix', '')) + assert not hasattr(self, name), f'duplicate plugin {name}' + self.add_module(name, layer) + plugin_names.append(name) + return plugin_names + + def forward_plugin(self, x, plugin_names): + out = x + for name in plugin_names: + out = getattr(self, name)(out) + return out + + @property + def norm1(self): + """nn.Module: normalization layer after the first convolution layer""" + return getattr(self, self.norm1_name) + + @property + def norm2(self): + """nn.Module: normalization layer after the second convolution layer""" + return getattr(self, self.norm2_name) + + @property + def norm3(self): + """nn.Module: normalization layer after the third convolution layer""" + return getattr(self, self.norm3_name) + + def forward(self, x): + """Forward function.""" + + def _inner_forward(x): + identity = x + out = self.conv1(x) + out = self.norm1(out) + out = self.relu(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv1_plugin_names) + + out = self.conv2(out) + out = self.norm2(out) + out = self.relu(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv2_plugin_names) + + out = self.conv3(out) + out = self.norm3(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv3_plugin_names) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + out = self.relu(out) + + return out + + +@MODELS.register_module() +class ResNet(BaseModule): + """ResNet backbone. + + Args: + depth (int): Depth of resnet, from {18, 34, 50, 101, 152}. + stem_channels (int | None): Number of stem channels. If not specified, + it will be the same as `base_channels`. Default: None. + base_channels (int): Number of base channels of res layer. Default: 64. + in_channels (int): Number of input image channels. Default: 3. + num_stages (int): Resnet stages. Default: 4. + strides (Sequence[int]): Strides of the first block of each stage. + dilations (Sequence[int]): Dilation of each stage. + out_indices (Sequence[int]): Output from which stages. + style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two + layer is the 3x3 conv layer, otherwise the stride-two layer is + the first 1x1 conv layer. + deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv + avg_down (bool): Use AvgPool instead of stride conv when + downsampling in the bottleneck. + frozen_stages (int): Stages to be frozen (stop grad and set eval mode). + -1 means not freezing any parameters. + norm_cfg (dict): Dictionary to construct and config norm layer. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. + plugins (list[dict]): List of plugins for stages, each dict contains: + + - cfg (dict, required): Cfg dict to build plugin. + - position (str, required): Position inside block to insert + plugin, options are 'after_conv1', 'after_conv2', 'after_conv3'. + - stages (tuple[bool], optional): Stages to apply plugin, length + should be same as 'num_stages'. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. + zero_init_residual (bool): Whether to use zero init for last norm layer + in resblocks to let them behave as identity. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + + Example: + >>> from mmdet.models import ResNet + >>> import torch + >>> self = ResNet(depth=18) + >>> self.eval() + >>> inputs = torch.rand(1, 3, 32, 32) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + (1, 64, 8, 8) + (1, 128, 4, 4) + (1, 256, 2, 2) + (1, 512, 1, 1) + """ + + arch_settings = { + 18: (BasicBlock, (2, 2, 2, 2)), + 34: (BasicBlock, (3, 4, 6, 3)), + 50: (Bottleneck, (3, 4, 6, 3)), + 101: (Bottleneck, (3, 4, 23, 3)), + 152: (Bottleneck, (3, 8, 36, 3)) + } + + def __init__(self, + depth, + in_channels=3, + stem_channels=None, + base_channels=64, + num_stages=4, + strides=(1, 2, 2, 2), + dilations=(1, 1, 1, 1), + out_indices=(0, 1, 2, 3), + style='pytorch', + deep_stem=False, + avg_down=False, + frozen_stages=-1, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + dcn=None, + stage_with_dcn=(False, False, False, False), + plugins=None, + with_cp=False, + zero_init_residual=True, + pretrained=None, + init_cfg=None): + super(ResNet, self).__init__(init_cfg) + self.zero_init_residual = zero_init_residual + if depth not in self.arch_settings: + raise KeyError(f'invalid depth {depth} for resnet') + + block_init_cfg = None + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + block = self.arch_settings[depth][0] + if self.zero_init_residual: + if block is BasicBlock: + block_init_cfg = dict( + type='Constant', + val=0, + override=dict(name='norm2')) + elif block is Bottleneck: + block_init_cfg = dict( + type='Constant', + val=0, + override=dict(name='norm3')) + else: + raise TypeError('pretrained must be a str or None') + + self.depth = depth + if stem_channels is None: + stem_channels = base_channels + self.stem_channels = stem_channels + self.base_channels = base_channels + self.num_stages = num_stages + assert num_stages >= 1 and num_stages <= 4 + self.strides = strides + self.dilations = dilations + assert len(strides) == len(dilations) == num_stages + self.out_indices = out_indices + assert max(out_indices) < num_stages + self.style = style + self.deep_stem = deep_stem + self.avg_down = avg_down + self.frozen_stages = frozen_stages + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.with_cp = with_cp + self.norm_eval = norm_eval + self.dcn = dcn + self.stage_with_dcn = stage_with_dcn + if dcn is not None: + assert len(stage_with_dcn) == num_stages + self.plugins = plugins + self.block, stage_blocks = self.arch_settings[depth] + self.stage_blocks = stage_blocks[:num_stages] + self.inplanes = stem_channels + + self._make_stem_layer(in_channels, stem_channels) + + self.res_layers = [] + for i, num_blocks in enumerate(self.stage_blocks): + stride = strides[i] + dilation = dilations[i] + dcn = self.dcn if self.stage_with_dcn[i] else None + if plugins is not None: + stage_plugins = self.make_stage_plugins(plugins, i) + else: + stage_plugins = None + planes = base_channels * 2**i + res_layer = self.make_res_layer( + block=self.block, + inplanes=self.inplanes, + planes=planes, + num_blocks=num_blocks, + stride=stride, + dilation=dilation, + style=self.style, + avg_down=self.avg_down, + with_cp=with_cp, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + dcn=dcn, + plugins=stage_plugins, + init_cfg=block_init_cfg) + self.inplanes = planes * self.block.expansion + layer_name = f'layer{i + 1}' + self.add_module(layer_name, res_layer) + self.res_layers.append(layer_name) + + self._freeze_stages() + + self.feat_dim = self.block.expansion * base_channels * 2**( + len(self.stage_blocks) - 1) + + def make_stage_plugins(self, plugins, stage_idx): + """Make plugins for ResNet ``stage_idx`` th stage. + + Currently we support to insert ``context_block``, + ``empirical_attention_block``, ``nonlocal_block`` into the backbone + like ResNet/ResNeXt. They could be inserted after conv1/conv2/conv3 of + Bottleneck. + + An example of plugins format could be: + + Examples: + >>> plugins=[ + ... dict(cfg=dict(type='xxx', arg1='xxx'), + ... stages=(False, True, True, True), + ... position='after_conv2'), + ... dict(cfg=dict(type='yyy'), + ... stages=(True, True, True, True), + ... position='after_conv3'), + ... dict(cfg=dict(type='zzz', postfix='1'), + ... stages=(True, True, True, True), + ... position='after_conv3'), + ... dict(cfg=dict(type='zzz', postfix='2'), + ... stages=(True, True, True, True), + ... position='after_conv3') + ... ] + >>> self = ResNet(depth=18) + >>> stage_plugins = self.make_stage_plugins(plugins, 0) + >>> assert len(stage_plugins) == 3 + + Suppose ``stage_idx=0``, the structure of blocks in the stage would be: + + .. code-block:: none + + conv1-> conv2->conv3->yyy->zzz1->zzz2 + + Suppose 'stage_idx=1', the structure of blocks in the stage would be: + + .. code-block:: none + + conv1-> conv2->xxx->conv3->yyy->zzz1->zzz2 + + If stages is missing, the plugin would be applied to all stages. + + Args: + plugins (list[dict]): List of plugins cfg to build. The postfix is + required if multiple same type plugins are inserted. + stage_idx (int): Index of stage to build + + Returns: + list[dict]: Plugins for current stage + """ + stage_plugins = [] + for plugin in plugins: + plugin = plugin.copy() + stages = plugin.pop('stages', None) + assert stages is None or len(stages) == self.num_stages + # whether to insert plugin into current stage + if stages is None or stages[stage_idx]: + stage_plugins.append(plugin) + + return stage_plugins + + def make_res_layer(self, **kwargs): + """Pack all blocks in a stage into a ``ResLayer``.""" + return ResLayer(**kwargs) + + @property + def norm1(self): + """nn.Module: the normalization layer named "norm1" """ + return getattr(self, self.norm1_name) + + def _make_stem_layer(self, in_channels, stem_channels): + if self.deep_stem: + self.stem = nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels, + stem_channels // 2, + kernel_size=3, + stride=2, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, stem_channels // 2)[1], + nn.ReLU(inplace=True), + build_conv_layer( + self.conv_cfg, + stem_channels // 2, + stem_channels // 2, + kernel_size=3, + stride=1, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, stem_channels // 2)[1], + nn.ReLU(inplace=True), + build_conv_layer( + self.conv_cfg, + stem_channels // 2, + stem_channels, + kernel_size=3, + stride=1, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, stem_channels)[1], + nn.ReLU(inplace=True)) + else: + self.conv1 = build_conv_layer( + self.conv_cfg, + in_channels, + stem_channels, + kernel_size=7, + stride=2, + padding=3, + bias=False) + self.norm1_name, norm1 = build_norm_layer( + self.norm_cfg, stem_channels, postfix=1) + self.add_module(self.norm1_name, norm1) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + + def _freeze_stages(self): + if self.frozen_stages >= 0: + if self.deep_stem: + self.stem.eval() + for param in self.stem.parameters(): + param.requires_grad = False + else: + self.norm1.eval() + for m in [self.conv1, self.norm1]: + for param in m.parameters(): + param.requires_grad = False + + for i in range(1, self.frozen_stages + 1): + m = getattr(self, f'layer{i}') + m.eval() + for param in m.parameters(): + param.requires_grad = False + + def forward(self, x): + """Forward function.""" + if self.deep_stem: + x = self.stem(x) + else: + x = self.conv1(x) + x = self.norm1(x) + x = self.relu(x) + x = self.maxpool(x) + outs = [] + for i, layer_name in enumerate(self.res_layers): + res_layer = getattr(self, layer_name) + x = res_layer(x) + if i in self.out_indices: + outs.append(x) + return tuple(outs) + + def train(self, mode=True): + """Convert the model into training mode while keep normalization layer + freezed.""" + super(ResNet, self).train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + # trick: eval have effect on BatchNorm only + if isinstance(m, _BatchNorm): + m.eval() + + +@MODELS.register_module() +class ResNetV1d(ResNet): + r"""ResNetV1d variant described in `Bag of Tricks + `_. + + Compared with default ResNet(ResNetV1b), ResNetV1d replaces the 7x7 conv in + the input stem with three 3x3 convs. And in the downsampling block, a 2x2 + avg_pool with stride 2 is added before conv, whose stride is changed to 1. + """ + + def __init__(self, **kwargs): + super(ResNetV1d, self).__init__( + deep_stem=True, avg_down=True, **kwargs) diff --git a/mmdetection/mmdet/models/backbones/resnext.py b/mmdetection/mmdet/models/backbones/resnext.py new file mode 100644 index 00000000..df3d79e0 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/resnext.py @@ -0,0 +1,154 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +from mmcv.cnn import build_conv_layer, build_norm_layer + +from mmdet.registry import MODELS +from ..layers import ResLayer +from .resnet import Bottleneck as _Bottleneck +from .resnet import ResNet + + +class Bottleneck(_Bottleneck): + expansion = 4 + + def __init__(self, + inplanes, + planes, + groups=1, + base_width=4, + base_channels=64, + **kwargs): + """Bottleneck block for ResNeXt. + + If style is "pytorch", the stride-two layer is the 3x3 conv layer, if + it is "caffe", the stride-two layer is the first 1x1 conv layer. + """ + super(Bottleneck, self).__init__(inplanes, planes, **kwargs) + + if groups == 1: + width = self.planes + else: + width = math.floor(self.planes * + (base_width / base_channels)) * groups + + self.norm1_name, norm1 = build_norm_layer( + self.norm_cfg, width, postfix=1) + self.norm2_name, norm2 = build_norm_layer( + self.norm_cfg, width, postfix=2) + self.norm3_name, norm3 = build_norm_layer( + self.norm_cfg, self.planes * self.expansion, postfix=3) + + self.conv1 = build_conv_layer( + self.conv_cfg, + self.inplanes, + width, + kernel_size=1, + stride=self.conv1_stride, + bias=False) + self.add_module(self.norm1_name, norm1) + fallback_on_stride = False + self.with_modulated_dcn = False + if self.with_dcn: + fallback_on_stride = self.dcn.pop('fallback_on_stride', False) + if not self.with_dcn or fallback_on_stride: + self.conv2 = build_conv_layer( + self.conv_cfg, + width, + width, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + groups=groups, + bias=False) + else: + assert self.conv_cfg is None, 'conv_cfg must be None for DCN' + self.conv2 = build_conv_layer( + self.dcn, + width, + width, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + groups=groups, + bias=False) + + self.add_module(self.norm2_name, norm2) + self.conv3 = build_conv_layer( + self.conv_cfg, + width, + self.planes * self.expansion, + kernel_size=1, + bias=False) + self.add_module(self.norm3_name, norm3) + + if self.with_plugins: + self._del_block_plugins(self.after_conv1_plugin_names + + self.after_conv2_plugin_names + + self.after_conv3_plugin_names) + self.after_conv1_plugin_names = self.make_block_plugins( + width, self.after_conv1_plugins) + self.after_conv2_plugin_names = self.make_block_plugins( + width, self.after_conv2_plugins) + self.after_conv3_plugin_names = self.make_block_plugins( + self.planes * self.expansion, self.after_conv3_plugins) + + def _del_block_plugins(self, plugin_names): + """delete plugins for block if exist. + + Args: + plugin_names (list[str]): List of plugins name to delete. + """ + assert isinstance(plugin_names, list) + for plugin_name in plugin_names: + del self._modules[plugin_name] + + +@MODELS.register_module() +class ResNeXt(ResNet): + """ResNeXt backbone. + + Args: + depth (int): Depth of resnet, from {18, 34, 50, 101, 152}. + in_channels (int): Number of input image channels. Default: 3. + num_stages (int): Resnet stages. Default: 4. + groups (int): Group of resnext. + base_width (int): Base width of resnext. + strides (Sequence[int]): Strides of the first block of each stage. + dilations (Sequence[int]): Dilation of each stage. + out_indices (Sequence[int]): Output from which stages. + style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two + layer is the 3x3 conv layer, otherwise the stride-two layer is + the first 1x1 conv layer. + frozen_stages (int): Stages to be frozen (all param fixed). -1 means + not freezing any parameters. + norm_cfg (dict): dictionary to construct and config norm layer. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. + zero_init_residual (bool): whether to use zero init for last norm layer + in resblocks to let them behave as identity. + """ + + arch_settings = { + 50: (Bottleneck, (3, 4, 6, 3)), + 101: (Bottleneck, (3, 4, 23, 3)), + 152: (Bottleneck, (3, 8, 36, 3)) + } + + def __init__(self, groups=1, base_width=4, **kwargs): + self.groups = groups + self.base_width = base_width + super(ResNeXt, self).__init__(**kwargs) + + def make_res_layer(self, **kwargs): + """Pack all blocks in a stage into a ``ResLayer``""" + return ResLayer( + groups=self.groups, + base_width=self.base_width, + base_channels=self.base_channels, + **kwargs) diff --git a/mmdetection/mmdet/models/backbones/ssd_vgg.py b/mmdetection/mmdet/models/backbones/ssd_vgg.py new file mode 100644 index 00000000..843e82e2 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/ssd_vgg.py @@ -0,0 +1,128 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +from mmcv.cnn import VGG +from mmengine.model import BaseModule + +from mmdet.registry import MODELS +from ..necks import ssd_neck + + +@MODELS.register_module() +class SSDVGG(VGG, BaseModule): + """VGG Backbone network for single-shot-detection. + + Args: + depth (int): Depth of vgg, from {11, 13, 16, 19}. + with_last_pool (bool): Whether to add a pooling layer at the last + of the model + ceil_mode (bool): When True, will use `ceil` instead of `floor` + to compute the output shape. + out_indices (Sequence[int]): Output from which stages. + out_feature_indices (Sequence[int]): Output from which feature map. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + input_size (int, optional): Deprecated argumment. + Width and height of input, from {300, 512}. + l2_norm_scale (float, optional) : Deprecated argumment. + L2 normalization layer init scale. + + Example: + >>> self = SSDVGG(input_size=300, depth=11) + >>> self.eval() + >>> inputs = torch.rand(1, 3, 300, 300) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + (1, 1024, 19, 19) + (1, 512, 10, 10) + (1, 256, 5, 5) + (1, 256, 3, 3) + (1, 256, 1, 1) + """ + extra_setting = { + 300: (256, 'S', 512, 128, 'S', 256, 128, 256, 128, 256), + 512: (256, 'S', 512, 128, 'S', 256, 128, 'S', 256, 128, 'S', 256, 128), + } + + def __init__(self, + depth, + with_last_pool=False, + ceil_mode=True, + out_indices=(3, 4), + out_feature_indices=(22, 34), + pretrained=None, + init_cfg=None, + input_size=None, + l2_norm_scale=None): + # TODO: in_channels for mmcv.VGG + super(SSDVGG, self).__init__( + depth, + with_last_pool=with_last_pool, + ceil_mode=ceil_mode, + out_indices=out_indices) + + self.features.add_module( + str(len(self.features)), + nn.MaxPool2d(kernel_size=3, stride=1, padding=1)) + self.features.add_module( + str(len(self.features)), + nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)) + self.features.add_module( + str(len(self.features)), nn.ReLU(inplace=True)) + self.features.add_module( + str(len(self.features)), nn.Conv2d(1024, 1024, kernel_size=1)) + self.features.add_module( + str(len(self.features)), nn.ReLU(inplace=True)) + self.out_feature_indices = out_feature_indices + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + + if init_cfg is not None: + self.init_cfg = init_cfg + elif isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict(type='Constant', val=1, layer='BatchNorm2d'), + dict(type='Normal', std=0.01, layer='Linear'), + ] + else: + raise TypeError('pretrained must be a str or None') + + if input_size is not None: + warnings.warn('DeprecationWarning: input_size is deprecated') + if l2_norm_scale is not None: + warnings.warn('DeprecationWarning: l2_norm_scale in VGG is ' + 'deprecated, it has been moved to SSDNeck.') + + def init_weights(self, pretrained=None): + super(VGG, self).init_weights() + + def forward(self, x): + """Forward function.""" + outs = [] + for i, layer in enumerate(self.features): + x = layer(x) + if i in self.out_feature_indices: + outs.append(x) + + if len(outs) == 1: + return outs[0] + else: + return tuple(outs) + + +class L2Norm(ssd_neck.L2Norm): + + def __init__(self, **kwargs): + super(L2Norm, self).__init__(**kwargs) + warnings.warn('DeprecationWarning: L2Norm in ssd_vgg.py ' + 'is deprecated, please use L2Norm in ' + 'mmdet/models/necks/ssd_neck.py instead') diff --git a/mmdetection/mmdet/models/backbones/swin.py b/mmdetection/mmdet/models/backbones/swin.py new file mode 100644 index 00000000..062190fa --- /dev/null +++ b/mmdetection/mmdet/models/backbones/swin.py @@ -0,0 +1,819 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from collections import OrderedDict +from copy import deepcopy + +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as cp +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import FFN, build_dropout +from mmengine.logging import MMLogger +from mmengine.model import BaseModule, ModuleList +from mmengine.model.weight_init import (constant_init, trunc_normal_, + trunc_normal_init) +from mmengine.runner.checkpoint import CheckpointLoader +from mmengine.utils import to_2tuple + +from mmdet.registry import MODELS +from ..layers import PatchEmbed, PatchMerging + + +class WindowMSA(BaseModule): + """Window based multi-head self-attention (W-MSA) module with relative + position bias. + + Args: + embed_dims (int): Number of input channels. + num_heads (int): Number of attention heads. + window_size (tuple[int]): The height and width of the window. + qkv_bias (bool, optional): If True, add a learnable bias to q, k, v. + Default: True. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + attn_drop_rate (float, optional): Dropout ratio of attention weight. + Default: 0.0 + proj_drop_rate (float, optional): Dropout ratio of output. Default: 0. + init_cfg (dict | None, optional): The Config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + window_size, + qkv_bias=True, + qk_scale=None, + attn_drop_rate=0., + proj_drop_rate=0., + init_cfg=None): + + super().__init__() + self.embed_dims = embed_dims + self.window_size = window_size # Wh, Ww + self.num_heads = num_heads + head_embed_dims = embed_dims // num_heads + self.scale = qk_scale or head_embed_dims**-0.5 + self.init_cfg = init_cfg + + # define a parameter table of relative position bias + self.relative_position_bias_table = nn.Parameter( + torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1), + num_heads)) # 2*Wh-1 * 2*Ww-1, nH + + # About 2x faster than original impl + Wh, Ww = self.window_size + rel_index_coords = self.double_step_seq(2 * Ww - 1, Wh, 1, Ww) + rel_position_index = rel_index_coords + rel_index_coords.T + rel_position_index = rel_position_index.flip(1).contiguous() + self.register_buffer('relative_position_index', rel_position_index) + + self.qkv = nn.Linear(embed_dims, embed_dims * 3, bias=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop_rate) + self.proj = nn.Linear(embed_dims, embed_dims) + self.proj_drop = nn.Dropout(proj_drop_rate) + + self.softmax = nn.Softmax(dim=-1) + + def init_weights(self): + trunc_normal_(self.relative_position_bias_table, std=0.02) + + def forward(self, x, mask=None): + """ + Args: + + x (tensor): input features with shape of (num_windows*B, N, C) + mask (tensor | None, Optional): mask with shape of (num_windows, + Wh*Ww, Wh*Ww), value should be between (-inf, 0]. + """ + B, N, C = x.shape + qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, + C // self.num_heads).permute(2, 0, 3, 1, 4) + # make torchscript happy (cannot use tensor as tuple) + q, k, v = qkv[0], qkv[1], qkv[2] + + q = q * self.scale + attn = (q @ k.transpose(-2, -1)) + + relative_position_bias = self.relative_position_bias_table[ + self.relative_position_index.view(-1)].view( + self.window_size[0] * self.window_size[1], + self.window_size[0] * self.window_size[1], + -1) # Wh*Ww,Wh*Ww,nH + relative_position_bias = relative_position_bias.permute( + 2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww + attn = attn + relative_position_bias.unsqueeze(0) + + if mask is not None: + nW = mask.shape[0] + attn = attn.view(B // nW, nW, self.num_heads, N, + N) + mask.unsqueeze(1).unsqueeze(0) + attn = attn.view(-1, self.num_heads, N, N) + attn = self.softmax(attn) + + attn = self.attn_drop(attn) + + x = (attn @ v).transpose(1, 2).reshape(B, N, C) + x = self.proj(x) + x = self.proj_drop(x) + return x + + @staticmethod + def double_step_seq(step1, len1, step2, len2): + seq1 = torch.arange(0, step1 * len1, step1) + seq2 = torch.arange(0, step2 * len2, step2) + return (seq1[:, None] + seq2[None, :]).reshape(1, -1) + + +class ShiftWindowMSA(BaseModule): + """Shifted Window Multihead Self-Attention Module. + + Args: + embed_dims (int): Number of input channels. + num_heads (int): Number of attention heads. + window_size (int): The height and width of the window. + shift_size (int, optional): The shift step of each window towards + right-bottom. If zero, act as regular window-msa. Defaults to 0. + qkv_bias (bool, optional): If True, add a learnable bias to q, k, v. + Default: True + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Defaults: None. + attn_drop_rate (float, optional): Dropout ratio of attention weight. + Defaults: 0. + proj_drop_rate (float, optional): Dropout ratio of output. + Defaults: 0. + dropout_layer (dict, optional): The dropout_layer used before output. + Defaults: dict(type='DropPath', drop_prob=0.). + init_cfg (dict, optional): The extra config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + window_size, + shift_size=0, + qkv_bias=True, + qk_scale=None, + attn_drop_rate=0, + proj_drop_rate=0, + dropout_layer=dict(type='DropPath', drop_prob=0.), + init_cfg=None): + super().__init__(init_cfg) + + self.window_size = window_size + self.shift_size = shift_size + assert 0 <= self.shift_size < self.window_size + + self.w_msa = WindowMSA( + embed_dims=embed_dims, + num_heads=num_heads, + window_size=to_2tuple(window_size), + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop_rate=attn_drop_rate, + proj_drop_rate=proj_drop_rate, + init_cfg=None) + + self.drop = build_dropout(dropout_layer) + + def forward(self, query, hw_shape): + B, L, C = query.shape + H, W = hw_shape + assert L == H * W, 'input feature has wrong size' + query = query.view(B, H, W, C) + + # pad feature maps to multiples of window size + pad_r = (self.window_size - W % self.window_size) % self.window_size + pad_b = (self.window_size - H % self.window_size) % self.window_size + query = F.pad(query, (0, 0, 0, pad_r, 0, pad_b)) + H_pad, W_pad = query.shape[1], query.shape[2] + + # cyclic shift + if self.shift_size > 0: + shifted_query = torch.roll( + query, + shifts=(-self.shift_size, -self.shift_size), + dims=(1, 2)) + + # calculate attention mask for SW-MSA + img_mask = torch.zeros((1, H_pad, W_pad, 1), device=query.device) + h_slices = (slice(0, -self.window_size), + slice(-self.window_size, + -self.shift_size), slice(-self.shift_size, None)) + w_slices = (slice(0, -self.window_size), + slice(-self.window_size, + -self.shift_size), slice(-self.shift_size, None)) + cnt = 0 + for h in h_slices: + for w in w_slices: + img_mask[:, h, w, :] = cnt + cnt += 1 + + # nW, window_size, window_size, 1 + mask_windows = self.window_partition(img_mask) + mask_windows = mask_windows.view( + -1, self.window_size * self.window_size) + attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) + attn_mask = attn_mask.masked_fill(attn_mask != 0, + float(-100.0)).masked_fill( + attn_mask == 0, float(0.0)) + else: + shifted_query = query + attn_mask = None + + # nW*B, window_size, window_size, C + query_windows = self.window_partition(shifted_query) + # nW*B, window_size*window_size, C + query_windows = query_windows.view(-1, self.window_size**2, C) + + # W-MSA/SW-MSA (nW*B, window_size*window_size, C) + attn_windows = self.w_msa(query_windows, mask=attn_mask) + + # merge windows + attn_windows = attn_windows.view(-1, self.window_size, + self.window_size, C) + + # B H' W' C + shifted_x = self.window_reverse(attn_windows, H_pad, W_pad) + # reverse cyclic shift + if self.shift_size > 0: + x = torch.roll( + shifted_x, + shifts=(self.shift_size, self.shift_size), + dims=(1, 2)) + else: + x = shifted_x + + if pad_r > 0 or pad_b: + x = x[:, :H, :W, :].contiguous() + + x = x.view(B, H * W, C) + + x = self.drop(x) + return x + + def window_reverse(self, windows, H, W): + """ + Args: + windows: (num_windows*B, window_size, window_size, C) + H (int): Height of image + W (int): Width of image + Returns: + x: (B, H, W, C) + """ + window_size = self.window_size + B = int(windows.shape[0] / (H * W / window_size / window_size)) + x = windows.view(B, H // window_size, W // window_size, window_size, + window_size, -1) + x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1) + return x + + def window_partition(self, x): + """ + Args: + x: (B, H, W, C) + Returns: + windows: (num_windows*B, window_size, window_size, C) + """ + B, H, W, C = x.shape + window_size = self.window_size + x = x.view(B, H // window_size, window_size, W // window_size, + window_size, C) + windows = x.permute(0, 1, 3, 2, 4, 5).contiguous() + windows = windows.view(-1, window_size, window_size, C) + return windows + + +class SwinBlock(BaseModule): + """" + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + window_size (int, optional): The local window scale. Default: 7. + shift (bool, optional): whether to shift window or not. Default False. + qkv_bias (bool, optional): enable bias for qkv if True. Default: True. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + drop_rate (float, optional): Dropout rate. Default: 0. + attn_drop_rate (float, optional): Attention dropout rate. Default: 0. + drop_path_rate (float, optional): Stochastic depth rate. Default: 0. + act_cfg (dict, optional): The config dict of activation function. + Default: dict(type='GELU'). + norm_cfg (dict, optional): The config dict of normalization. + Default: dict(type='LN'). + with_cp (bool, optional): Use checkpoint or not. Using checkpoint + will save some memory while slowing down the training speed. + Default: False. + init_cfg (dict | list | None, optional): The init config. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + window_size=7, + shift=False, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + with_cp=False, + init_cfg=None): + + super(SwinBlock, self).__init__() + + self.init_cfg = init_cfg + self.with_cp = with_cp + + self.norm1 = build_norm_layer(norm_cfg, embed_dims)[1] + self.attn = ShiftWindowMSA( + embed_dims=embed_dims, + num_heads=num_heads, + window_size=window_size, + shift_size=window_size // 2 if shift else 0, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop_rate=attn_drop_rate, + proj_drop_rate=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + init_cfg=None) + + self.norm2 = build_norm_layer(norm_cfg, embed_dims)[1] + self.ffn = FFN( + embed_dims=embed_dims, + feedforward_channels=feedforward_channels, + num_fcs=2, + ffn_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + act_cfg=act_cfg, + add_identity=True, + init_cfg=None) + + def forward(self, x, hw_shape): + + def _inner_forward(x): + identity = x + x = self.norm1(x) + x = self.attn(x, hw_shape) + + x = x + identity + + identity = x + x = self.norm2(x) + x = self.ffn(x, identity=identity) + + return x + + if self.with_cp and x.requires_grad: + x = cp.checkpoint(_inner_forward, x) + else: + x = _inner_forward(x) + + return x + + +class SwinBlockSequence(BaseModule): + """Implements one stage in Swin Transformer. + + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + depth (int): The number of blocks in this stage. + window_size (int, optional): The local window scale. Default: 7. + qkv_bias (bool, optional): enable bias for qkv if True. Default: True. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + drop_rate (float, optional): Dropout rate. Default: 0. + attn_drop_rate (float, optional): Attention dropout rate. Default: 0. + drop_path_rate (float | list[float], optional): Stochastic depth + rate. Default: 0. + downsample (BaseModule | None, optional): The downsample operation + module. Default: None. + act_cfg (dict, optional): The config dict of activation function. + Default: dict(type='GELU'). + norm_cfg (dict, optional): The config dict of normalization. + Default: dict(type='LN'). + with_cp (bool, optional): Use checkpoint or not. Using checkpoint + will save some memory while slowing down the training speed. + Default: False. + init_cfg (dict | list | None, optional): The init config. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + depth, + window_size=7, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + downsample=None, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + with_cp=False, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + if isinstance(drop_path_rate, list): + drop_path_rates = drop_path_rate + assert len(drop_path_rates) == depth + else: + drop_path_rates = [deepcopy(drop_path_rate) for _ in range(depth)] + + self.blocks = ModuleList() + for i in range(depth): + block = SwinBlock( + embed_dims=embed_dims, + num_heads=num_heads, + feedforward_channels=feedforward_channels, + window_size=window_size, + shift=False if i % 2 == 0 else True, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop_rate=drop_rate, + attn_drop_rate=attn_drop_rate, + drop_path_rate=drop_path_rates[i], + act_cfg=act_cfg, + norm_cfg=norm_cfg, + with_cp=with_cp, + init_cfg=None) + self.blocks.append(block) + + self.downsample = downsample + + def forward(self, x, hw_shape): + for block in self.blocks: + x = block(x, hw_shape) + + if self.downsample: + x_down, down_hw_shape = self.downsample(x, hw_shape) + return x_down, down_hw_shape, x, hw_shape + else: + return x, hw_shape, x, hw_shape + + +@MODELS.register_module() +class SwinTransformer(BaseModule): + """ Swin Transformer + A PyTorch implement of : `Swin Transformer: + Hierarchical Vision Transformer using Shifted Windows` - + https://arxiv.org/abs/2103.14030 + + Inspiration from + https://github.com/microsoft/Swin-Transformer + + Args: + pretrain_img_size (int | tuple[int]): The size of input image when + pretrain. Defaults: 224. + in_channels (int): The num of input channels. + Defaults: 3. + embed_dims (int): The feature dimension. Default: 96. + patch_size (int | tuple[int]): Patch size. Default: 4. + window_size (int): Window size. Default: 7. + mlp_ratio (int): Ratio of mlp hidden dim to embedding dim. + Default: 4. + depths (tuple[int]): Depths of each Swin Transformer stage. + Default: (2, 2, 6, 2). + num_heads (tuple[int]): Parallel attention heads of each Swin + Transformer stage. Default: (3, 6, 12, 24). + strides (tuple[int]): The patch merging or patch embedding stride of + each Swin Transformer stage. (In swin, we set kernel size equal to + stride.) Default: (4, 2, 2, 2). + out_indices (tuple[int]): Output from which stages. + Default: (0, 1, 2, 3). + qkv_bias (bool, optional): If True, add a learnable bias to query, key, + value. Default: True + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + patch_norm (bool): If add a norm layer for patch embed and patch + merging. Default: True. + drop_rate (float): Dropout rate. Defaults: 0. + attn_drop_rate (float): Attention dropout rate. Default: 0. + drop_path_rate (float): Stochastic depth rate. Defaults: 0.1. + use_abs_pos_embed (bool): If True, add absolute position embedding to + the patch embedding. Defaults: False. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer at + output of backone. Defaults: dict(type='LN'). + with_cp (bool, optional): Use checkpoint or not. Using checkpoint + will save some memory while slowing down the training speed. + Default: False. + pretrained (str, optional): model pretrained path. Default: None. + convert_weights (bool): The flag indicates whether the + pre-trained model is from the original repo. We may need + to convert some keys to make it compatible. + Default: False. + frozen_stages (int): Stages to be frozen (stop grad and set eval mode). + Default: -1 (-1 means not freezing any parameters). + init_cfg (dict, optional): The Config for initialization. + Defaults to None. + """ + + def __init__(self, + pretrain_img_size=224, + in_channels=3, + embed_dims=96, + patch_size=4, + window_size=7, + mlp_ratio=4, + depths=(2, 2, 6, 2), + num_heads=(3, 6, 12, 24), + strides=(4, 2, 2, 2), + out_indices=(0, 1, 2, 3), + qkv_bias=True, + qk_scale=None, + patch_norm=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.1, + use_abs_pos_embed=False, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + with_cp=False, + pretrained=None, + convert_weights=False, + frozen_stages=-1, + init_cfg=None): + self.convert_weights = convert_weights + self.frozen_stages = frozen_stages + if isinstance(pretrain_img_size, int): + pretrain_img_size = to_2tuple(pretrain_img_size) + elif isinstance(pretrain_img_size, tuple): + if len(pretrain_img_size) == 1: + pretrain_img_size = to_2tuple(pretrain_img_size[0]) + assert len(pretrain_img_size) == 2, \ + f'The size of image should have length 1 or 2, ' \ + f'but got {len(pretrain_img_size)}' + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + self.init_cfg = init_cfg + else: + raise TypeError('pretrained must be a str or None') + + super(SwinTransformer, self).__init__(init_cfg=init_cfg) + + num_layers = len(depths) + self.out_indices = out_indices + self.use_abs_pos_embed = use_abs_pos_embed + + assert strides[0] == patch_size, 'Use non-overlapping patch embed.' + + self.patch_embed = PatchEmbed( + in_channels=in_channels, + embed_dims=embed_dims, + conv_type='Conv2d', + kernel_size=patch_size, + stride=strides[0], + norm_cfg=norm_cfg if patch_norm else None, + init_cfg=None) + + if self.use_abs_pos_embed: + patch_row = pretrain_img_size[0] // patch_size + patch_col = pretrain_img_size[1] // patch_size + num_patches = patch_row * patch_col + self.absolute_pos_embed = nn.Parameter( + torch.zeros((1, num_patches, embed_dims))) + + self.drop_after_pos = nn.Dropout(p=drop_rate) + + # set stochastic depth decay rule + total_depth = sum(depths) + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, total_depth) + ] + + self.stages = ModuleList() + in_channels = embed_dims + for i in range(num_layers): + if i < num_layers - 1: + downsample = PatchMerging( + in_channels=in_channels, + out_channels=2 * in_channels, + stride=strides[i + 1], + norm_cfg=norm_cfg if patch_norm else None, + init_cfg=None) + else: + downsample = None + + stage = SwinBlockSequence( + embed_dims=in_channels, + num_heads=num_heads[i], + feedforward_channels=mlp_ratio * in_channels, + depth=depths[i], + window_size=window_size, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop_rate=drop_rate, + attn_drop_rate=attn_drop_rate, + drop_path_rate=dpr[sum(depths[:i]):sum(depths[:i + 1])], + downsample=downsample, + act_cfg=act_cfg, + norm_cfg=norm_cfg, + with_cp=with_cp, + init_cfg=None) + self.stages.append(stage) + if downsample: + in_channels = downsample.out_channels + + self.num_features = [int(embed_dims * 2**i) for i in range(num_layers)] + # Add a norm layer for each output + for i in out_indices: + layer = build_norm_layer(norm_cfg, self.num_features[i])[1] + layer_name = f'norm{i}' + self.add_module(layer_name, layer) + + def train(self, mode=True): + """Convert the model into training mode while keep layers freezed.""" + super(SwinTransformer, self).train(mode) + self._freeze_stages() + + def _freeze_stages(self): + if self.frozen_stages >= 0: + self.patch_embed.eval() + for param in self.patch_embed.parameters(): + param.requires_grad = False + if self.use_abs_pos_embed: + self.absolute_pos_embed.requires_grad = False + self.drop_after_pos.eval() + + for i in range(1, self.frozen_stages + 1): + + if (i - 1) in self.out_indices: + norm_layer = getattr(self, f'norm{i-1}') + norm_layer.eval() + for param in norm_layer.parameters(): + param.requires_grad = False + + m = self.stages[i - 1] + m.eval() + for param in m.parameters(): + param.requires_grad = False + + def init_weights(self): + logger = MMLogger.get_current_instance() + if self.init_cfg is None: + logger.warn(f'No pre-trained weights for ' + f'{self.__class__.__name__}, ' + f'training start from scratch') + if self.use_abs_pos_embed: + trunc_normal_(self.absolute_pos_embed, std=0.02) + for m in self.modules(): + if isinstance(m, nn.Linear): + trunc_normal_init(m, std=.02, bias=0.) + elif isinstance(m, nn.LayerNorm): + constant_init(m, 1.0) + else: + assert 'checkpoint' in self.init_cfg, f'Only support ' \ + f'specify `Pretrained` in ' \ + f'`init_cfg` in ' \ + f'{self.__class__.__name__} ' + ckpt = CheckpointLoader.load_checkpoint( + self.init_cfg.checkpoint, logger=logger, map_location='cpu') + if 'state_dict' in ckpt: + _state_dict = ckpt['state_dict'] + elif 'model' in ckpt: + _state_dict = ckpt['model'] + else: + _state_dict = ckpt + if self.convert_weights: + # supported loading weight from original repo, + _state_dict = swin_converter(_state_dict) + + state_dict = OrderedDict() + for k, v in _state_dict.items(): + if k.startswith('backbone.'): + state_dict[k[9:]] = v + + # strip prefix of state_dict + if list(state_dict.keys())[0].startswith('module.'): + state_dict = {k[7:]: v for k, v in state_dict.items()} + + # reshape absolute position embedding + if state_dict.get('absolute_pos_embed') is not None: + absolute_pos_embed = state_dict['absolute_pos_embed'] + N1, L, C1 = absolute_pos_embed.size() + N2, C2, H, W = self.absolute_pos_embed.size() + if N1 != N2 or C1 != C2 or L != H * W: + logger.warning('Error in loading absolute_pos_embed, pass') + else: + state_dict['absolute_pos_embed'] = absolute_pos_embed.view( + N2, H, W, C2).permute(0, 3, 1, 2).contiguous() + + # interpolate position bias table if needed + relative_position_bias_table_keys = [ + k for k in state_dict.keys() + if 'relative_position_bias_table' in k + ] + for table_key in relative_position_bias_table_keys: + table_pretrained = state_dict[table_key] + table_current = self.state_dict()[table_key] + L1, nH1 = table_pretrained.size() + L2, nH2 = table_current.size() + if nH1 != nH2: + logger.warning(f'Error in loading {table_key}, pass') + elif L1 != L2: + S1 = int(L1**0.5) + S2 = int(L2**0.5) + table_pretrained_resized = F.interpolate( + table_pretrained.permute(1, 0).reshape(1, nH1, S1, S1), + size=(S2, S2), + mode='bicubic') + state_dict[table_key] = table_pretrained_resized.view( + nH2, L2).permute(1, 0).contiguous() + + # load state_dict + self.load_state_dict(state_dict, False) + + def forward(self, x): + x, hw_shape = self.patch_embed(x) + + if self.use_abs_pos_embed: + x = x + self.absolute_pos_embed + x = self.drop_after_pos(x) + + outs = [] + for i, stage in enumerate(self.stages): + x, hw_shape, out, out_hw_shape = stage(x, hw_shape) + if i in self.out_indices: + norm_layer = getattr(self, f'norm{i}') + out = norm_layer(out) + out = out.view(-1, *out_hw_shape, + self.num_features[i]).permute(0, 3, 1, + 2).contiguous() + outs.append(out) + + return outs + + +def swin_converter(ckpt): + + new_ckpt = OrderedDict() + + def correct_unfold_reduction_order(x): + out_channel, in_channel = x.shape + x = x.reshape(out_channel, 4, in_channel // 4) + x = x[:, [0, 2, 1, 3], :].transpose(1, + 2).reshape(out_channel, in_channel) + return x + + def correct_unfold_norm_order(x): + in_channel = x.shape[0] + x = x.reshape(4, in_channel // 4) + x = x[[0, 2, 1, 3], :].transpose(0, 1).reshape(in_channel) + return x + + for k, v in ckpt.items(): + if k.startswith('head'): + continue + elif k.startswith('layers'): + new_v = v + if 'attn.' in k: + new_k = k.replace('attn.', 'attn.w_msa.') + elif 'mlp.' in k: + if 'mlp.fc1.' in k: + new_k = k.replace('mlp.fc1.', 'ffn.layers.0.0.') + elif 'mlp.fc2.' in k: + new_k = k.replace('mlp.fc2.', 'ffn.layers.1.') + else: + new_k = k.replace('mlp.', 'ffn.') + elif 'downsample' in k: + new_k = k + if 'reduction.' in k: + new_v = correct_unfold_reduction_order(v) + elif 'norm.' in k: + new_v = correct_unfold_norm_order(v) + else: + new_k = k + new_k = new_k.replace('layers', 'stages', 1) + elif k.startswith('patch_embed'): + new_v = v + if 'proj' in k: + new_k = k.replace('proj', 'projection') + else: + new_k = k + else: + new_v = v + new_k = k + + new_ckpt['backbone.' + new_k] = new_v + + return new_ckpt diff --git a/mmdetection/mmdet/models/backbones/trident_resnet.py b/mmdetection/mmdet/models/backbones/trident_resnet.py new file mode 100644 index 00000000..22c76354 --- /dev/null +++ b/mmdetection/mmdet/models/backbones/trident_resnet.py @@ -0,0 +1,298 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as cp +from mmcv.cnn import build_conv_layer, build_norm_layer +from mmengine.model import BaseModule +from torch.nn.modules.utils import _pair + +from mmdet.models.backbones.resnet import Bottleneck, ResNet +from mmdet.registry import MODELS + + +class TridentConv(BaseModule): + """Trident Convolution Module. + + Args: + in_channels (int): Number of channels in input. + out_channels (int): Number of channels in output. + kernel_size (int): Size of convolution kernel. + stride (int, optional): Convolution stride. Default: 1. + trident_dilations (tuple[int, int, int], optional): Dilations of + different trident branch. Default: (1, 2, 3). + test_branch_idx (int, optional): In inference, all 3 branches will + be used if `test_branch_idx==-1`, otherwise only branch with + index `test_branch_idx` will be used. Default: 1. + bias (bool, optional): Whether to use bias in convolution or not. + Default: False. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + trident_dilations=(1, 2, 3), + test_branch_idx=1, + bias=False, + init_cfg=None): + super(TridentConv, self).__init__(init_cfg) + self.num_branch = len(trident_dilations) + self.with_bias = bias + self.test_branch_idx = test_branch_idx + self.stride = _pair(stride) + self.kernel_size = _pair(kernel_size) + self.paddings = _pair(trident_dilations) + self.dilations = trident_dilations + self.in_channels = in_channels + self.out_channels = out_channels + self.bias = bias + + self.weight = nn.Parameter( + torch.Tensor(out_channels, in_channels, *self.kernel_size)) + if bias: + self.bias = nn.Parameter(torch.Tensor(out_channels)) + else: + self.bias = None + + def extra_repr(self): + tmpstr = f'in_channels={self.in_channels}' + tmpstr += f', out_channels={self.out_channels}' + tmpstr += f', kernel_size={self.kernel_size}' + tmpstr += f', num_branch={self.num_branch}' + tmpstr += f', test_branch_idx={self.test_branch_idx}' + tmpstr += f', stride={self.stride}' + tmpstr += f', paddings={self.paddings}' + tmpstr += f', dilations={self.dilations}' + tmpstr += f', bias={self.bias}' + return tmpstr + + def forward(self, inputs): + if self.training or self.test_branch_idx == -1: + outputs = [ + F.conv2d(input, self.weight, self.bias, self.stride, padding, + dilation) for input, dilation, padding in zip( + inputs, self.dilations, self.paddings) + ] + else: + assert len(inputs) == 1 + outputs = [ + F.conv2d(inputs[0], self.weight, self.bias, self.stride, + self.paddings[self.test_branch_idx], + self.dilations[self.test_branch_idx]) + ] + + return outputs + + +# Since TridentNet is defined over ResNet50 and ResNet101, here we +# only support TridentBottleneckBlock. +class TridentBottleneck(Bottleneck): + """BottleBlock for TridentResNet. + + Args: + trident_dilations (tuple[int, int, int]): Dilations of different + trident branch. + test_branch_idx (int): In inference, all 3 branches will be used + if `test_branch_idx==-1`, otherwise only branch with index + `test_branch_idx` will be used. + concat_output (bool): Whether to concat the output list to a Tensor. + `True` only in the last Block. + """ + + def __init__(self, trident_dilations, test_branch_idx, concat_output, + **kwargs): + + super(TridentBottleneck, self).__init__(**kwargs) + self.trident_dilations = trident_dilations + self.num_branch = len(trident_dilations) + self.concat_output = concat_output + self.test_branch_idx = test_branch_idx + self.conv2 = TridentConv( + self.planes, + self.planes, + kernel_size=3, + stride=self.conv2_stride, + bias=False, + trident_dilations=self.trident_dilations, + test_branch_idx=test_branch_idx, + init_cfg=dict( + type='Kaiming', + distribution='uniform', + mode='fan_in', + override=dict(name='conv2'))) + + def forward(self, x): + + def _inner_forward(x): + num_branch = ( + self.num_branch + if self.training or self.test_branch_idx == -1 else 1) + identity = x + if not isinstance(x, list): + x = (x, ) * num_branch + identity = x + if self.downsample is not None: + identity = [self.downsample(b) for b in x] + + out = [self.conv1(b) for b in x] + out = [self.norm1(b) for b in out] + out = [self.relu(b) for b in out] + + if self.with_plugins: + for k in range(len(out)): + out[k] = self.forward_plugin(out[k], + self.after_conv1_plugin_names) + + out = self.conv2(out) + out = [self.norm2(b) for b in out] + out = [self.relu(b) for b in out] + if self.with_plugins: + for k in range(len(out)): + out[k] = self.forward_plugin(out[k], + self.after_conv2_plugin_names) + + out = [self.conv3(b) for b in out] + out = [self.norm3(b) for b in out] + + if self.with_plugins: + for k in range(len(out)): + out[k] = self.forward_plugin(out[k], + self.after_conv3_plugin_names) + + out = [ + out_b + identity_b for out_b, identity_b in zip(out, identity) + ] + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + out = [self.relu(b) for b in out] + if self.concat_output: + out = torch.cat(out, dim=0) + return out + + +def make_trident_res_layer(block, + inplanes, + planes, + num_blocks, + stride=1, + trident_dilations=(1, 2, 3), + style='pytorch', + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + dcn=None, + plugins=None, + test_branch_idx=-1): + """Build Trident Res Layers.""" + + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = [] + conv_stride = stride + downsample.extend([ + build_conv_layer( + conv_cfg, + inplanes, + planes * block.expansion, + kernel_size=1, + stride=conv_stride, + bias=False), + build_norm_layer(norm_cfg, planes * block.expansion)[1] + ]) + downsample = nn.Sequential(*downsample) + + layers = [] + for i in range(num_blocks): + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=stride if i == 0 else 1, + trident_dilations=trident_dilations, + downsample=downsample if i == 0 else None, + style=style, + with_cp=with_cp, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + dcn=dcn, + plugins=plugins, + test_branch_idx=test_branch_idx, + concat_output=True if i == num_blocks - 1 else False)) + inplanes = planes * block.expansion + return nn.Sequential(*layers) + + +@MODELS.register_module() +class TridentResNet(ResNet): + """The stem layer, stage 1 and stage 2 in Trident ResNet are identical to + ResNet, while in stage 3, Trident BottleBlock is utilized to replace the + normal BottleBlock to yield trident output. Different branch shares the + convolution weight but uses different dilations to achieve multi-scale + output. + + / stage3(b0) \ + x - stem - stage1 - stage2 - stage3(b1) - output + \ stage3(b2) / + + Args: + depth (int): Depth of resnet, from {50, 101, 152}. + num_branch (int): Number of branches in TridentNet. + test_branch_idx (int): In inference, all 3 branches will be used + if `test_branch_idx==-1`, otherwise only branch with index + `test_branch_idx` will be used. + trident_dilations (tuple[int]): Dilations of different trident branch. + len(trident_dilations) should be equal to num_branch. + """ # noqa + + def __init__(self, depth, num_branch, test_branch_idx, trident_dilations, + **kwargs): + + assert num_branch == len(trident_dilations) + assert depth in (50, 101, 152) + super(TridentResNet, self).__init__(depth, **kwargs) + assert self.num_stages == 3 + self.test_branch_idx = test_branch_idx + self.num_branch = num_branch + + last_stage_idx = self.num_stages - 1 + stride = self.strides[last_stage_idx] + dilation = trident_dilations + dcn = self.dcn if self.stage_with_dcn[last_stage_idx] else None + if self.plugins is not None: + stage_plugins = self.make_stage_plugins(self.plugins, + last_stage_idx) + else: + stage_plugins = None + planes = self.base_channels * 2**last_stage_idx + res_layer = make_trident_res_layer( + TridentBottleneck, + inplanes=(self.block.expansion * self.base_channels * + 2**(last_stage_idx - 1)), + planes=planes, + num_blocks=self.stage_blocks[last_stage_idx], + stride=stride, + trident_dilations=dilation, + style=self.style, + with_cp=self.with_cp, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + dcn=dcn, + plugins=stage_plugins, + test_branch_idx=self.test_branch_idx) + + layer_name = f'layer{last_stage_idx + 1}' + + self.__setattr__(layer_name, res_layer) + self.res_layers.pop(last_stage_idx) + self.res_layers.insert(last_stage_idx, layer_name) + + self._freeze_stages() diff --git a/mmdetection/mmdet/models/data_preprocessors/__init__.py b/mmdetection/mmdet/models/data_preprocessors/__init__.py new file mode 100644 index 00000000..201a1da6 --- /dev/null +++ b/mmdetection/mmdet/models/data_preprocessors/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .data_preprocessor import (BatchFixedSizePad, BatchResize, + BatchSyncRandomResize, BoxInstDataPreprocessor, + DetDataPreprocessor, + MultiBranchDataPreprocessor) +from .reid_data_preprocessor import ReIDDataPreprocessor +from .track_data_preprocessor import TrackDataPreprocessor + +__all__ = [ + 'DetDataPreprocessor', 'BatchSyncRandomResize', 'BatchFixedSizePad', + 'MultiBranchDataPreprocessor', 'BatchResize', 'BoxInstDataPreprocessor', + 'TrackDataPreprocessor', 'ReIDDataPreprocessor' +] diff --git a/mmdetection/mmdet/models/data_preprocessors/data_preprocessor.py b/mmdetection/mmdet/models/data_preprocessors/data_preprocessor.py new file mode 100644 index 00000000..55b5c35b --- /dev/null +++ b/mmdetection/mmdet/models/data_preprocessors/data_preprocessor.py @@ -0,0 +1,793 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import random +from numbers import Number +from typing import List, Optional, Sequence, Tuple, Union + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.dist import barrier, broadcast, get_dist_info +from mmengine.logging import MessageHub +from mmengine.model import BaseDataPreprocessor, ImgDataPreprocessor +from mmengine.structures import PixelData +from mmengine.utils import is_seq_of +from torch import Tensor + +from mmdet.models.utils import unfold_wo_center +from mmdet.models.utils.misc import samplelist_boxtype2tensor +from mmdet.registry import MODELS +from mmdet.structures import DetDataSample +from mmdet.structures.mask import BitmapMasks +from mmdet.utils import ConfigType + +try: + import skimage +except ImportError: + skimage = None + + +@MODELS.register_module() +class DetDataPreprocessor(ImgDataPreprocessor): + """Image pre-processor for detection tasks. + + Comparing with the :class:`mmengine.ImgDataPreprocessor`, + + 1. It supports batch augmentations. + 2. It will additionally append batch_input_shape and pad_shape + to data_samples considering the object detection task. + + It provides the data pre-processing as follows + + - Collate and move data to the target device. + - Pad inputs to the maximum size of current batch with defined + ``pad_value``. The padding size can be divisible by a defined + ``pad_size_divisor`` + - Stack inputs to batch_inputs. + - Convert inputs from bgr to rgb if the shape of input is (3, H, W). + - Normalize image with defined std and mean. + - Do batch augmentations during training. + + Args: + mean (Sequence[Number], optional): The pixel mean of R, G, B channels. + Defaults to None. + std (Sequence[Number], optional): The pixel standard deviation of + R, G, B channels. Defaults to None. + pad_size_divisor (int): The size of padded image should be + divisible by ``pad_size_divisor``. Defaults to 1. + pad_value (Number): The padded pixel value. Defaults to 0. + pad_mask (bool): Whether to pad instance masks. Defaults to False. + mask_pad_value (int): The padded pixel value for instance masks. + Defaults to 0. + pad_seg (bool): Whether to pad semantic segmentation maps. + Defaults to False. + seg_pad_value (int): The padded pixel value for semantic + segmentation maps. Defaults to 255. + bgr_to_rgb (bool): whether to convert image from BGR to RGB. + Defaults to False. + rgb_to_bgr (bool): whether to convert image from RGB to RGB. + Defaults to False. + boxtype2tensor (bool): Whether to convert the ``BaseBoxes`` type of + bboxes data to ``Tensor`` type. Defaults to True. + non_blocking (bool): Whether block current process + when transferring data to device. Defaults to False. + batch_augments (list[dict], optional): Batch-level augmentations + """ + + def __init__(self, + mean: Sequence[Number] = None, + std: Sequence[Number] = None, + pad_size_divisor: int = 1, + pad_value: Union[float, int] = 0, + pad_mask: bool = False, + mask_pad_value: int = 0, + pad_seg: bool = False, + seg_pad_value: int = 255, + bgr_to_rgb: bool = False, + rgb_to_bgr: bool = False, + boxtype2tensor: bool = True, + non_blocking: Optional[bool] = False, + batch_augments: Optional[List[dict]] = None): + super().__init__( + mean=mean, + std=std, + pad_size_divisor=pad_size_divisor, + pad_value=pad_value, + bgr_to_rgb=bgr_to_rgb, + rgb_to_bgr=rgb_to_bgr, + non_blocking=non_blocking) + if batch_augments is not None: + self.batch_augments = nn.ModuleList( + [MODELS.build(aug) for aug in batch_augments]) + else: + self.batch_augments = None + self.pad_mask = pad_mask + self.mask_pad_value = mask_pad_value + self.pad_seg = pad_seg + self.seg_pad_value = seg_pad_value + self.boxtype2tensor = boxtype2tensor + + def forward(self, data: dict, training: bool = False) -> dict: + """Perform normalization,padding and bgr2rgb conversion based on + ``BaseDataPreprocessor``. + + Args: + data (dict): Data sampled from dataloader. + training (bool): Whether to enable training time augmentation. + + Returns: + dict: Data in the same format as the model input. + """ + batch_pad_shape = self._get_pad_shape(data) + data = super().forward(data=data, training=training) + inputs, data_samples = data['inputs'], data['data_samples'] + + if data_samples is not None: + # NOTE the batched image size information may be useful, e.g. + # in DETR, this is needed for the construction of masks, which is + # then used for the transformer_head. + batch_input_shape = tuple(inputs[0].size()[-2:]) + for data_sample, pad_shape in zip(data_samples, batch_pad_shape): + data_sample.set_metainfo({ + 'batch_input_shape': batch_input_shape, + 'pad_shape': pad_shape + }) + + if self.boxtype2tensor: + samplelist_boxtype2tensor(data_samples) + + if self.pad_mask and training: + self.pad_gt_masks(data_samples) + + if self.pad_seg and training: + self.pad_gt_sem_seg(data_samples) + + if training and self.batch_augments is not None: + for batch_aug in self.batch_augments: + inputs, data_samples = batch_aug(inputs, data_samples) + + return {'inputs': inputs, 'data_samples': data_samples} + + def _get_pad_shape(self, data: dict) -> List[tuple]: + """Get the pad_shape of each image based on data and + pad_size_divisor.""" + _batch_inputs = data['inputs'] + # Process data with `pseudo_collate`. + if is_seq_of(_batch_inputs, torch.Tensor): + batch_pad_shape = [] + for ori_input in _batch_inputs: + pad_h = int( + np.ceil(ori_input.shape[1] / + self.pad_size_divisor)) * self.pad_size_divisor + pad_w = int( + np.ceil(ori_input.shape[2] / + self.pad_size_divisor)) * self.pad_size_divisor + batch_pad_shape.append((pad_h, pad_w)) + # Process data with `default_collate`. + elif isinstance(_batch_inputs, torch.Tensor): + assert _batch_inputs.dim() == 4, ( + 'The input of `ImgDataPreprocessor` should be a NCHW tensor ' + 'or a list of tensor, but got a tensor with shape: ' + f'{_batch_inputs.shape}') + pad_h = int( + np.ceil(_batch_inputs.shape[2] / + self.pad_size_divisor)) * self.pad_size_divisor + pad_w = int( + np.ceil(_batch_inputs.shape[3] / + self.pad_size_divisor)) * self.pad_size_divisor + batch_pad_shape = [(pad_h, pad_w)] * _batch_inputs.shape[0] + else: + raise TypeError('Output of `cast_data` should be a dict ' + 'or a tuple with inputs and data_samples, but got' + f'{type(data)}: {data}') + return batch_pad_shape + + def pad_gt_masks(self, + batch_data_samples: Sequence[DetDataSample]) -> None: + """Pad gt_masks to shape of batch_input_shape.""" + if 'masks' in batch_data_samples[0].gt_instances: + for data_samples in batch_data_samples: + masks = data_samples.gt_instances.masks + data_samples.gt_instances.masks = masks.pad( + data_samples.batch_input_shape, + pad_val=self.mask_pad_value) + + def pad_gt_sem_seg(self, + batch_data_samples: Sequence[DetDataSample]) -> None: + """Pad gt_sem_seg to shape of batch_input_shape.""" + if 'gt_sem_seg' in batch_data_samples[0]: + for data_samples in batch_data_samples: + gt_sem_seg = data_samples.gt_sem_seg.sem_seg + h, w = gt_sem_seg.shape[-2:] + pad_h, pad_w = data_samples.batch_input_shape + gt_sem_seg = F.pad( + gt_sem_seg, + pad=(0, max(pad_w - w, 0), 0, max(pad_h - h, 0)), + mode='constant', + value=self.seg_pad_value) + data_samples.gt_sem_seg = PixelData(sem_seg=gt_sem_seg) + + +@MODELS.register_module() +class BatchSyncRandomResize(nn.Module): + """Batch random resize which synchronizes the random size across ranks. + + Args: + random_size_range (tuple): The multi-scale random range during + multi-scale training. + interval (int): The iter interval of change + image size. Defaults to 10. + size_divisor (int): Image size divisible factor. + Defaults to 32. + """ + + def __init__(self, + random_size_range: Tuple[int, int], + interval: int = 10, + size_divisor: int = 32) -> None: + super().__init__() + self.rank, self.world_size = get_dist_info() + self._input_size = None + self._random_size_range = (round(random_size_range[0] / size_divisor), + round(random_size_range[1] / size_divisor)) + self._interval = interval + self._size_divisor = size_divisor + + def forward( + self, inputs: Tensor, data_samples: List[DetDataSample] + ) -> Tuple[Tensor, List[DetDataSample]]: + """resize a batch of images and bboxes to shape ``self._input_size``""" + h, w = inputs.shape[-2:] + if self._input_size is None: + self._input_size = (h, w) + scale_y = self._input_size[0] / h + scale_x = self._input_size[1] / w + if scale_x != 1 or scale_y != 1: + inputs = F.interpolate( + inputs, + size=self._input_size, + mode='bilinear', + align_corners=False) + for data_sample in data_samples: + img_shape = (int(data_sample.img_shape[0] * scale_y), + int(data_sample.img_shape[1] * scale_x)) + pad_shape = (int(data_sample.pad_shape[0] * scale_y), + int(data_sample.pad_shape[1] * scale_x)) + data_sample.set_metainfo({ + 'img_shape': img_shape, + 'pad_shape': pad_shape, + 'batch_input_shape': self._input_size + }) + data_sample.gt_instances.bboxes[ + ..., + 0::2] = data_sample.gt_instances.bboxes[..., + 0::2] * scale_x + data_sample.gt_instances.bboxes[ + ..., + 1::2] = data_sample.gt_instances.bboxes[..., + 1::2] * scale_y + if 'ignored_instances' in data_sample: + data_sample.ignored_instances.bboxes[ + ..., 0::2] = data_sample.ignored_instances.bboxes[ + ..., 0::2] * scale_x + data_sample.ignored_instances.bboxes[ + ..., 1::2] = data_sample.ignored_instances.bboxes[ + ..., 1::2] * scale_y + message_hub = MessageHub.get_current_instance() + if (message_hub.get_info('iter') + 1) % self._interval == 0: + self._input_size = self._get_random_size( + aspect_ratio=float(w / h), device=inputs.device) + return inputs, data_samples + + def _get_random_size(self, aspect_ratio: float, + device: torch.device) -> Tuple[int, int]: + """Randomly generate a shape in ``_random_size_range`` and broadcast to + all ranks.""" + tensor = torch.LongTensor(2).to(device) + if self.rank == 0: + size = random.randint(*self._random_size_range) + size = (self._size_divisor * size, + self._size_divisor * int(aspect_ratio * size)) + tensor[0] = size[0] + tensor[1] = size[1] + barrier() + broadcast(tensor, 0) + input_size = (tensor[0].item(), tensor[1].item()) + return input_size + + +@MODELS.register_module() +class BatchFixedSizePad(nn.Module): + """Fixed size padding for batch images. + + Args: + size (Tuple[int, int]): Fixed padding size. Expected padding + shape (h, w). Defaults to None. + img_pad_value (int): The padded pixel value for images. + Defaults to 0. + pad_mask (bool): Whether to pad instance masks. Defaults to False. + mask_pad_value (int): The padded pixel value for instance masks. + Defaults to 0. + pad_seg (bool): Whether to pad semantic segmentation maps. + Defaults to False. + seg_pad_value (int): The padded pixel value for semantic + segmentation maps. Defaults to 255. + """ + + def __init__(self, + size: Tuple[int, int], + img_pad_value: int = 0, + pad_mask: bool = False, + mask_pad_value: int = 0, + pad_seg: bool = False, + seg_pad_value: int = 255) -> None: + super().__init__() + self.size = size + self.pad_mask = pad_mask + self.pad_seg = pad_seg + self.img_pad_value = img_pad_value + self.mask_pad_value = mask_pad_value + self.seg_pad_value = seg_pad_value + + def forward( + self, + inputs: Tensor, + data_samples: Optional[List[dict]] = None + ) -> Tuple[Tensor, Optional[List[dict]]]: + """Pad image, instance masks, segmantic segmentation maps.""" + src_h, src_w = inputs.shape[-2:] + dst_h, dst_w = self.size + + if src_h >= dst_h and src_w >= dst_w: + return inputs, data_samples + + inputs = F.pad( + inputs, + pad=(0, max(0, dst_w - src_w), 0, max(0, dst_h - src_h)), + mode='constant', + value=self.img_pad_value) + + if data_samples is not None: + # update batch_input_shape + for data_sample in data_samples: + data_sample.set_metainfo({ + 'batch_input_shape': (dst_h, dst_w), + 'pad_shape': (dst_h, dst_w) + }) + + if self.pad_mask: + for data_sample in data_samples: + masks = data_sample.gt_instances.masks + data_sample.gt_instances.masks = masks.pad( + (dst_h, dst_w), pad_val=self.mask_pad_value) + + if self.pad_seg: + for data_sample in data_samples: + gt_sem_seg = data_sample.gt_sem_seg.sem_seg + h, w = gt_sem_seg.shape[-2:] + gt_sem_seg = F.pad( + gt_sem_seg, + pad=(0, max(0, dst_w - w), 0, max(0, dst_h - h)), + mode='constant', + value=self.seg_pad_value) + data_sample.gt_sem_seg = PixelData(sem_seg=gt_sem_seg) + + return inputs, data_samples + + +@MODELS.register_module() +class MultiBranchDataPreprocessor(BaseDataPreprocessor): + """DataPreprocessor wrapper for multi-branch data. + + Take semi-supervised object detection as an example, assume that + the ratio of labeled data and unlabeled data in a batch is 1:2, + `sup` indicates the branch where the labeled data is augmented, + `unsup_teacher` and `unsup_student` indicate the branches where + the unlabeled data is augmented by different pipeline. + + The input format of multi-branch data is shown as below : + + .. code-block:: none + { + 'inputs': + { + 'sup': [Tensor, None, None], + 'unsup_teacher': [None, Tensor, Tensor], + 'unsup_student': [None, Tensor, Tensor], + }, + 'data_sample': + { + 'sup': [DetDataSample, None, None], + 'unsup_teacher': [None, DetDataSample, DetDataSample], + 'unsup_student': [NOne, DetDataSample, DetDataSample], + } + } + + The format of multi-branch data + after filtering None is shown as below : + + .. code-block:: none + { + 'inputs': + { + 'sup': [Tensor], + 'unsup_teacher': [Tensor, Tensor], + 'unsup_student': [Tensor, Tensor], + }, + 'data_sample': + { + 'sup': [DetDataSample], + 'unsup_teacher': [DetDataSample, DetDataSample], + 'unsup_student': [DetDataSample, DetDataSample], + } + } + + In order to reuse `DetDataPreprocessor` for the data + from different branches, the format of multi-branch data + grouped by branch is as below : + + .. code-block:: none + { + 'sup': + { + 'inputs': [Tensor] + 'data_sample': [DetDataSample, DetDataSample] + }, + 'unsup_teacher': + { + 'inputs': [Tensor, Tensor] + 'data_sample': [DetDataSample, DetDataSample] + }, + 'unsup_student': + { + 'inputs': [Tensor, Tensor] + 'data_sample': [DetDataSample, DetDataSample] + }, + } + + After preprocessing data from different branches, + the multi-branch data needs to be reformatted as: + + .. code-block:: none + { + 'inputs': + { + 'sup': [Tensor], + 'unsup_teacher': [Tensor, Tensor], + 'unsup_student': [Tensor, Tensor], + }, + 'data_sample': + { + 'sup': [DetDataSample], + 'unsup_teacher': [DetDataSample, DetDataSample], + 'unsup_student': [DetDataSample, DetDataSample], + } + } + + Args: + data_preprocessor (:obj:`ConfigDict` or dict): Config of + :class:`DetDataPreprocessor` to process the input data. + """ + + def __init__(self, data_preprocessor: ConfigType) -> None: + super().__init__() + self.data_preprocessor = MODELS.build(data_preprocessor) + + def forward(self, data: dict, training: bool = False) -> dict: + """Perform normalization,padding and bgr2rgb conversion based on + ``BaseDataPreprocessor`` for multi-branch data. + + Args: + data (dict): Data sampled from dataloader. + training (bool): Whether to enable training time augmentation. + + Returns: + dict: + + - 'inputs' (Dict[str, obj:`torch.Tensor`]): The forward data of + models from different branches. + - 'data_sample' (Dict[str, obj:`DetDataSample`]): The annotation + info of the sample from different branches. + """ + + if training is False: + return self.data_preprocessor(data, training) + + # Filter out branches with a value of None + for key in data.keys(): + for branch in data[key].keys(): + data[key][branch] = list( + filter(lambda x: x is not None, data[key][branch])) + + # Group data by branch + multi_branch_data = {} + for key in data.keys(): + for branch in data[key].keys(): + if multi_branch_data.get(branch, None) is None: + multi_branch_data[branch] = {key: data[key][branch]} + elif multi_branch_data[branch].get(key, None) is None: + multi_branch_data[branch][key] = data[key][branch] + else: + multi_branch_data[branch][key].append(data[key][branch]) + + # Preprocess data from different branches + for branch, _data in multi_branch_data.items(): + multi_branch_data[branch] = self.data_preprocessor(_data, training) + + # Format data by inputs and data_samples + format_data = {} + for branch in multi_branch_data.keys(): + for key in multi_branch_data[branch].keys(): + if format_data.get(key, None) is None: + format_data[key] = {branch: multi_branch_data[branch][key]} + elif format_data[key].get(branch, None) is None: + format_data[key][branch] = multi_branch_data[branch][key] + else: + format_data[key][branch].append( + multi_branch_data[branch][key]) + + return format_data + + @property + def device(self): + return self.data_preprocessor.device + + def to(self, device: Optional[Union[int, torch.device]], *args, + **kwargs) -> nn.Module: + """Overrides this method to set the :attr:`device` + + Args: + device (int or torch.device, optional): The desired device of the + parameters and buffers in this module. + + Returns: + nn.Module: The model itself. + """ + + return self.data_preprocessor.to(device, *args, **kwargs) + + def cuda(self, *args, **kwargs) -> nn.Module: + """Overrides this method to set the :attr:`device` + + Returns: + nn.Module: The model itself. + """ + + return self.data_preprocessor.cuda(*args, **kwargs) + + def cpu(self, *args, **kwargs) -> nn.Module: + """Overrides this method to set the :attr:`device` + + Returns: + nn.Module: The model itself. + """ + + return self.data_preprocessor.cpu(*args, **kwargs) + + +@MODELS.register_module() +class BatchResize(nn.Module): + """Batch resize during training. This implementation is modified from + https://github.com/Purkialo/CrowdDet/blob/master/lib/data/CrowdHuman.py. + + It provides the data pre-processing as follows: + - A batch of all images will pad to a uniform size and stack them into + a torch.Tensor by `DetDataPreprocessor`. + - `BatchFixShapeResize` resize all images to the target size. + - Padding images to make sure the size of image can be divisible by + ``pad_size_divisor``. + + Args: + scale (tuple): Images scales for resizing. + pad_size_divisor (int): Image size divisible factor. + Defaults to 1. + pad_value (Number): The padded pixel value. Defaults to 0. + """ + + def __init__( + self, + scale: tuple, + pad_size_divisor: int = 1, + pad_value: Union[float, int] = 0, + ) -> None: + super().__init__() + self.min_size = min(scale) + self.max_size = max(scale) + self.pad_size_divisor = pad_size_divisor + self.pad_value = pad_value + + def forward( + self, inputs: Tensor, data_samples: List[DetDataSample] + ) -> Tuple[Tensor, List[DetDataSample]]: + """resize a batch of images and bboxes.""" + + batch_height, batch_width = inputs.shape[-2:] + target_height, target_width, scale = self.get_target_size( + batch_height, batch_width) + + inputs = F.interpolate( + inputs, + size=(target_height, target_width), + mode='bilinear', + align_corners=False) + + inputs = self.get_padded_tensor(inputs, self.pad_value) + + if data_samples is not None: + batch_input_shape = tuple(inputs.size()[-2:]) + for data_sample in data_samples: + img_shape = [ + int(scale * _) for _ in list(data_sample.img_shape) + ] + data_sample.set_metainfo({ + 'img_shape': tuple(img_shape), + 'batch_input_shape': batch_input_shape, + 'pad_shape': batch_input_shape, + 'scale_factor': (scale, scale) + }) + + data_sample.gt_instances.bboxes *= scale + data_sample.ignored_instances.bboxes *= scale + + return inputs, data_samples + + def get_target_size(self, height: int, + width: int) -> Tuple[int, int, float]: + """Get the target size of a batch of images based on data and scale.""" + im_size_min = np.min([height, width]) + im_size_max = np.max([height, width]) + scale = self.min_size / im_size_min + if scale * im_size_max > self.max_size: + scale = self.max_size / im_size_max + target_height, target_width = int(round(height * scale)), int( + round(width * scale)) + return target_height, target_width, scale + + def get_padded_tensor(self, tensor: Tensor, pad_value: int) -> Tensor: + """Pad images according to pad_size_divisor.""" + assert tensor.ndim == 4 + target_height, target_width = tensor.shape[-2], tensor.shape[-1] + divisor = self.pad_size_divisor + padded_height = (target_height + divisor - 1) // divisor * divisor + padded_width = (target_width + divisor - 1) // divisor * divisor + padded_tensor = torch.ones([ + tensor.shape[0], tensor.shape[1], padded_height, padded_width + ]) * pad_value + padded_tensor = padded_tensor.type_as(tensor) + padded_tensor[:, :, :target_height, :target_width] = tensor + return padded_tensor + + +@MODELS.register_module() +class BoxInstDataPreprocessor(DetDataPreprocessor): + """Pseudo mask pre-processor for BoxInst. + + Comparing with the :class:`mmdet.DetDataPreprocessor`, + + 1. It generates masks using box annotations. + 2. It computes the images color similarity in LAB color space. + + Args: + mask_stride (int): The mask output stride in boxinst. Defaults to 4. + pairwise_size (int): The size of neighborhood for each pixel. + Defaults to 3. + pairwise_dilation (int): The dilation of neighborhood for each pixel. + Defaults to 2. + pairwise_color_thresh (float): The thresh of image color similarity. + Defaults to 0.3. + bottom_pixels_removed (int): The length of removed pixels in bottom. + It is caused by the annotation error in coco dataset. + Defaults to 10. + """ + + def __init__(self, + *arg, + mask_stride: int = 4, + pairwise_size: int = 3, + pairwise_dilation: int = 2, + pairwise_color_thresh: float = 0.3, + bottom_pixels_removed: int = 10, + **kwargs) -> None: + super().__init__(*arg, **kwargs) + self.mask_stride = mask_stride + self.pairwise_size = pairwise_size + self.pairwise_dilation = pairwise_dilation + self.pairwise_color_thresh = pairwise_color_thresh + self.bottom_pixels_removed = bottom_pixels_removed + + if skimage is None: + raise RuntimeError('skimage is not installed,\ + please install it by: pip install scikit-image') + + def get_images_color_similarity(self, inputs: Tensor, + image_masks: Tensor) -> Tensor: + """Compute the image color similarity in LAB color space.""" + assert inputs.dim() == 4 + assert inputs.size(0) == 1 + + unfolded_images = unfold_wo_center( + inputs, + kernel_size=self.pairwise_size, + dilation=self.pairwise_dilation) + diff = inputs[:, :, None] - unfolded_images + similarity = torch.exp(-torch.norm(diff, dim=1) * 0.5) + + unfolded_weights = unfold_wo_center( + image_masks[None, None], + kernel_size=self.pairwise_size, + dilation=self.pairwise_dilation) + unfolded_weights = torch.max(unfolded_weights, dim=1)[0] + + return similarity * unfolded_weights + + def forward(self, data: dict, training: bool = False) -> dict: + """Get pseudo mask labels using color similarity.""" + det_data = super().forward(data, training) + inputs, data_samples = det_data['inputs'], det_data['data_samples'] + + if training: + # get image masks and remove bottom pixels + b_img_h, b_img_w = data_samples[0].batch_input_shape + img_masks = [] + for i in range(inputs.shape[0]): + img_h, img_w = data_samples[i].img_shape + img_mask = inputs.new_ones((img_h, img_w)) + pixels_removed = int(self.bottom_pixels_removed * + float(img_h) / float(b_img_h)) + if pixels_removed > 0: + img_mask[-pixels_removed:, :] = 0 + pad_w = b_img_w - img_w + pad_h = b_img_h - img_h + img_mask = F.pad(img_mask, (0, pad_w, 0, pad_h), 'constant', + 0.) + img_masks.append(img_mask) + img_masks = torch.stack(img_masks, dim=0) + start = int(self.mask_stride // 2) + img_masks = img_masks[:, start::self.mask_stride, + start::self.mask_stride] + + # Get origin rgb image for color similarity + ori_imgs = inputs * self.std + self.mean + downsampled_imgs = F.avg_pool2d( + ori_imgs.float(), + kernel_size=self.mask_stride, + stride=self.mask_stride, + padding=0) + + # Compute color similarity for pseudo mask generation + for im_i, data_sample in enumerate(data_samples): + # TODO: Support rgb2lab in mmengine? + images_lab = skimage.color.rgb2lab( + downsampled_imgs[im_i].byte().permute(1, 2, + 0).cpu().numpy()) + images_lab = torch.as_tensor( + images_lab, device=ori_imgs.device, dtype=torch.float32) + images_lab = images_lab.permute(2, 0, 1)[None] + images_color_similarity = self.get_images_color_similarity( + images_lab, img_masks[im_i]) + pairwise_mask = (images_color_similarity >= + self.pairwise_color_thresh).float() + + per_im_bboxes = data_sample.gt_instances.bboxes + if per_im_bboxes.shape[0] > 0: + per_im_masks = [] + for per_box in per_im_bboxes: + mask_full = torch.zeros((b_img_h, b_img_w), + device=self.device).float() + mask_full[int(per_box[1]):int(per_box[3] + 1), + int(per_box[0]):int(per_box[2] + 1)] = 1.0 + per_im_masks.append(mask_full) + per_im_masks = torch.stack(per_im_masks, dim=0) + pairwise_masks = torch.cat( + [pairwise_mask for _ in range(per_im_bboxes.shape[0])], + dim=0) + else: + per_im_masks = torch.zeros((0, b_img_h, b_img_w)) + pairwise_masks = torch.zeros( + (0, self.pairwise_size**2 - 1, b_img_h, b_img_w)) + + # TODO: Support BitmapMasks with tensor? + data_sample.gt_instances.masks = BitmapMasks( + per_im_masks.cpu().numpy(), b_img_h, b_img_w) + data_sample.gt_instances.pairwise_masks = pairwise_masks + return {'inputs': inputs, 'data_samples': data_samples} diff --git a/mmdetection/mmdet/models/data_preprocessors/reid_data_preprocessor.py b/mmdetection/mmdet/models/data_preprocessors/reid_data_preprocessor.py new file mode 100644 index 00000000..3d0a1d45 --- /dev/null +++ b/mmdetection/mmdet/models/data_preprocessors/reid_data_preprocessor.py @@ -0,0 +1,216 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from numbers import Number +from typing import Optional, Sequence + +import torch +import torch.nn.functional as F +from mmengine.model import BaseDataPreprocessor, stack_batch + +from mmdet.registry import MODELS + +try: + import mmpretrain + from mmpretrain.models.utils.batch_augments import RandomBatchAugment + from mmpretrain.structures import (batch_label_to_onehot, cat_batch_labels, + tensor_split) +except ImportError: + mmpretrain = None + + +def stack_batch_scores(elements, device=None): + """Stack the ``score`` of a batch of :obj:`LabelData` to a tensor. + + Args: + elements (List[LabelData]): A batch of :obj`LabelData`. + device (torch.device, optional): The output device of the batch label. + Defaults to None. + Returns: + torch.Tensor: The stacked score tensor. + """ + item = elements[0] + if 'score' not in item._data_fields: + return None + + batch_score = torch.stack([element.score for element in elements]) + if device is not None: + batch_score = batch_score.to(device) + return batch_score + + +@MODELS.register_module() +class ReIDDataPreprocessor(BaseDataPreprocessor): + """Image pre-processor for classification tasks. + + Comparing with the :class:`mmengine.model.ImgDataPreprocessor`, + + 1. It won't do normalization if ``mean`` is not specified. + 2. It does normalization and color space conversion after stacking batch. + 3. It supports batch augmentations like mixup and cutmix. + + It provides the data pre-processing as follows + + - Collate and move data to the target device. + - Pad inputs to the maximum size of current batch with defined + ``pad_value``. The padding size can be divisible by a defined + ``pad_size_divisor`` + - Stack inputs to batch_inputs. + - Convert inputs from bgr to rgb if the shape of input is (3, H, W). + - Normalize image with defined std and mean. + - Do batch augmentations like Mixup and Cutmix during training. + + Args: + mean (Sequence[Number], optional): The pixel mean of R, G, B channels. + Defaults to None. + std (Sequence[Number], optional): The pixel standard deviation of + R, G, B channels. Defaults to None. + pad_size_divisor (int): The size of padded image should be + divisible by ``pad_size_divisor``. Defaults to 1. + pad_value (Number): The padded pixel value. Defaults to 0. + to_rgb (bool): whether to convert image from BGR to RGB. + Defaults to False. + to_onehot (bool): Whether to generate one-hot format gt-labels and set + to data samples. Defaults to False. + num_classes (int, optional): The number of classes. Defaults to None. + batch_augments (dict, optional): The batch augmentations settings, + including "augments" and "probs". For more details, see + :class:`mmpretrain.models.RandomBatchAugment`. + """ + + def __init__(self, + mean: Sequence[Number] = None, + std: Sequence[Number] = None, + pad_size_divisor: int = 1, + pad_value: Number = 0, + to_rgb: bool = False, + to_onehot: bool = False, + num_classes: Optional[int] = None, + batch_augments: Optional[dict] = None): + if mmpretrain is None: + raise RuntimeError('Please run "pip install openmim" and ' + 'run "mim install mmpretrain" to ' + 'install mmpretrain first.') + super().__init__() + self.pad_size_divisor = pad_size_divisor + self.pad_value = pad_value + self.to_rgb = to_rgb + self.to_onehot = to_onehot + self.num_classes = num_classes + + if mean is not None: + assert std is not None, 'To enable the normalization in ' \ + 'preprocessing, please specify both `mean` and `std`.' + # Enable the normalization in preprocessing. + self._enable_normalize = True + self.register_buffer('mean', + torch.tensor(mean).view(-1, 1, 1), False) + self.register_buffer('std', + torch.tensor(std).view(-1, 1, 1), False) + else: + self._enable_normalize = False + + if batch_augments is not None: + self.batch_augments = RandomBatchAugment(**batch_augments) + if not self.to_onehot: + from mmengine.logging import MMLogger + MMLogger.get_current_instance().info( + 'Because batch augmentations are enabled, the data ' + 'preprocessor automatically enables the `to_onehot` ' + 'option to generate one-hot format labels.') + self.to_onehot = True + else: + self.batch_augments = None + + def forward(self, data: dict, training: bool = False) -> dict: + """Perform normalization, padding, bgr2rgb conversion and batch + augmentation based on ``BaseDataPreprocessor``. + + Args: + data (dict): data sampled from dataloader. + training (bool): Whether to enable training time augmentation. + + Returns: + dict: Data in the same format as the model input. + """ + inputs = self.cast_data(data['inputs']) + + if isinstance(inputs, torch.Tensor): + # The branch if use `default_collate` as the collate_fn in the + # dataloader. + + # ------ To RGB ------ + if self.to_rgb and inputs.size(1) == 3: + inputs = inputs.flip(1) + + # -- Normalization --- + inputs = inputs.float() + if self._enable_normalize: + inputs = (inputs - self.mean) / self.std + + # ------ Padding ----- + if self.pad_size_divisor > 1: + h, w = inputs.shape[-2:] + + target_h = math.ceil( + h / self.pad_size_divisor) * self.pad_size_divisor + target_w = math.ceil( + w / self.pad_size_divisor) * self.pad_size_divisor + pad_h = target_h - h + pad_w = target_w - w + inputs = F.pad(inputs, (0, pad_w, 0, pad_h), 'constant', + self.pad_value) + else: + # The branch if use `pseudo_collate` as the collate_fn in the + # dataloader. + + processed_inputs = [] + for input_ in inputs: + # ------ To RGB ------ + if self.to_rgb and input_.size(0) == 3: + input_ = input_.flip(0) + + # -- Normalization --- + input_ = input_.float() + if self._enable_normalize: + input_ = (input_ - self.mean) / self.std + + processed_inputs.append(input_) + # Combine padding and stack + inputs = stack_batch(processed_inputs, self.pad_size_divisor, + self.pad_value) + + data_samples = data.get('data_samples', None) + sample_item = data_samples[0] if data_samples is not None else None + if 'gt_label' in sample_item: + gt_labels = [sample.gt_label for sample in data_samples] + gt_labels_tensor = [gt_label.label for gt_label in gt_labels] + batch_label, label_indices = cat_batch_labels(gt_labels_tensor) + batch_label = batch_label.to(self.device) + + batch_score = stack_batch_scores(gt_labels, device=self.device) + if batch_score is None and self.to_onehot: + assert batch_label is not None, \ + 'Cannot generate onehot format labels because no labels.' + num_classes = self.num_classes or data_samples[0].get( + 'num_classes') + assert num_classes is not None, \ + 'Cannot generate one-hot format labels because not set ' \ + '`num_classes` in `data_preprocessor`.' + batch_score = batch_label_to_onehot(batch_label, label_indices, + num_classes) + + # ----- Batch Augmentations ---- + if training and self.batch_augments is not None: + inputs, batch_score = self.batch_augments(inputs, batch_score) + + # ----- scatter labels and scores to data samples --- + if batch_label is not None: + for sample, label in zip( + data_samples, tensor_split(batch_label, + label_indices)): + sample.set_gt_label(label) + if batch_score is not None: + for sample, score in zip(data_samples, batch_score): + sample.set_gt_score(score) + + return {'inputs': inputs, 'data_samples': data_samples} diff --git a/mmdetection/mmdet/models/data_preprocessors/track_data_preprocessor.py b/mmdetection/mmdet/models/data_preprocessors/track_data_preprocessor.py new file mode 100644 index 00000000..40a65b8e --- /dev/null +++ b/mmdetection/mmdet/models/data_preprocessors/track_data_preprocessor.py @@ -0,0 +1,266 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Sequence, Union + +import numpy as np +import torch +import torch.nn.functional as F +from mmengine.model.utils import stack_batch + +from mmdet.models.utils.misc import samplelist_boxtype2tensor +from mmdet.registry import MODELS +from mmdet.structures import TrackDataSample +from mmdet.structures.mask import BitmapMasks +from .data_preprocessor import DetDataPreprocessor + + +@MODELS.register_module() +class TrackDataPreprocessor(DetDataPreprocessor): + """Image pre-processor for tracking tasks. + + Accepts the data sampled by the dataloader, and preprocesses + it into the format of the model input. ``TrackDataPreprocessor`` + provides the tracking data pre-processing as follows: + + - Collate and move data to the target device. + - Pad inputs to the maximum size of current batch with defined + ``pad_value``. The padding size can be divisible by a defined + ``pad_size_divisor`` + - Stack inputs to inputs. + - Convert inputs from bgr to rgb if the shape of input is (1, 3, H, W). + - Normalize image with defined std and mean. + - Do batch augmentations during training. + - Record the information of ``batch_input_shape`` and ``pad_shape``. + + Args: + mean (Sequence[Number], optional): The pixel mean of R, G, B + channels. Defaults to None. + std (Sequence[Number], optional): The pixel standard deviation of + R, G, B channels. Defaults to None. + pad_size_divisor (int): The size of padded image should be + divisible by ``pad_size_divisor``. Defaults to 1. + pad_value (Number): The padded pixel value. Defaults to 0. + pad_mask (bool): Whether to pad instance masks. Defaults to False. + mask_pad_value (int): The padded pixel value for instance masks. + Defaults to 0. + bgr_to_rgb (bool): whether to convert image from BGR to RGB. + Defaults to False. + rgb_to_bgr (bool): whether to convert image from RGB to RGB. + Defaults to False. + use_det_processor: (bool): whether to use DetDataPreprocessor + in training phrase. This is mainly for some tracking models + fed into one image rather than a group of image in training. + Defaults to False. + . boxtype2tensor (bool): Whether to convert the ``BaseBoxes`` type of + bboxes data to ``Tensor`` type. Defaults to True. + batch_augments (list[dict], optional): Batch-level augmentations + """ + + def __init__(self, + mean: Optional[Sequence[Union[float, int]]] = None, + std: Optional[Sequence[Union[float, int]]] = None, + use_det_processor: bool = False, + **kwargs): + super().__init__(mean=mean, std=std, **kwargs) + self.use_det_processor = use_det_processor + if mean is not None and not self.use_det_processor: + # overwrite the ``register_bufffer`` in ``ImgDataPreprocessor`` + # since the shape of ``mean`` and ``std`` in tracking tasks must be + # (T, C, H, W), which T is the temporal length of the video. + self.register_buffer('mean', + torch.tensor(mean).view(1, -1, 1, 1), False) + self.register_buffer('std', + torch.tensor(std).view(1, -1, 1, 1), False) + + def forward(self, data: dict, training: bool = False) -> Dict: + """Perform normalization,padding and bgr2rgb conversion based on + ``TrackDataPreprocessor``. + + Args: + data (dict): data sampled from dataloader. + training (bool): Whether to enable training time augmentation. + + Returns: + Tuple[Dict[str, List[torch.Tensor]], OptSampleList]: Data in the + same format as the model input. + """ + if self.use_det_processor and training: + batch_pad_shape = self._get_pad_shape(data) + else: + batch_pad_shape = self._get_track_pad_shape(data) + + data = self.cast_data(data) + imgs, data_samples = data['inputs'], data['data_samples'] + + if self.use_det_processor and training: + assert imgs[0].dim() == 3, \ + 'Only support the 3 dims when use detpreprocessor in training' + if self._channel_conversion: + imgs = [_img[[2, 1, 0], ...] for _img in imgs] + # Convert to `float` + imgs = [_img.float() for _img in imgs] + if self._enable_normalize: + imgs = [(_img - self.mean) / self.std for _img in imgs] + inputs = stack_batch(imgs, self.pad_size_divisor, self.pad_value) + else: + assert imgs[0].dim() == 4, \ + 'Only support the 4 dims when use trackprocessor in training' + # The shape of imgs[0] is (T, C, H, W). + channel = imgs[0].size(1) + if self._channel_conversion and channel == 3: + imgs = [_img[:, [2, 1, 0], ...] for _img in imgs] + # change to `float` + imgs = [_img.float() for _img in imgs] + if self._enable_normalize: + imgs = [(_img - self.mean) / self.std for _img in imgs] + inputs = stack_track_batch(imgs, self.pad_size_divisor, + self.pad_value) + + if data_samples is not None: + # NOTE the batched image size information may be useful, e.g. + # in DETR, this is needed for the construction of masks, which is + # then used for the transformer_head. + batch_input_shape = tuple(inputs.size()[-2:]) + if self.use_det_processor and training: + for data_sample, pad_shape in zip(data_samples, + batch_pad_shape): + data_sample.set_metainfo({ + 'batch_input_shape': batch_input_shape, + 'pad_shape': pad_shape + }) + if self.boxtype2tensor: + samplelist_boxtype2tensor(data_samples) + if self.pad_mask: + self.pad_gt_masks(data_samples) + else: + for track_data_sample, pad_shapes in zip( + data_samples, batch_pad_shape): + for i in range(len(track_data_sample)): + det_data_sample = track_data_sample[i] + det_data_sample.set_metainfo({ + 'batch_input_shape': batch_input_shape, + 'pad_shape': pad_shapes[i] + }) + if self.pad_mask and training: + self.pad_track_gt_masks(data_samples) + + if training and self.batch_augments is not None: + for batch_aug in self.batch_augments: + if self.use_det_processor and training: + inputs, data_samples = batch_aug(inputs, data_samples) + else: + # we only support T==1 when using batch augments. + # Only yolox need batch_aug, and yolox can only process + # (N, C, H, W) shape. + # The shape of `inputs` is (N, T, C, H, W), hence, we use + # inputs[:, 0] to change the shape to (N, C, H, W). + assert inputs.size(1) == 1 and len( + data_samples[0] + ) == 1, 'Only support the number of sequence images equals to 1 when using batch augment.' # noqa: E501 + det_data_samples = [ + track_data_sample[0] + for track_data_sample in data_samples + ] + aug_inputs, aug_det_samples = batch_aug( + inputs[:, 0], det_data_samples) + inputs = aug_inputs.unsqueeze(1) + for track_data_sample, det_sample in zip( + data_samples, aug_det_samples): + track_data_sample.video_data_samples = [det_sample] + + # Note: inputs may contain large number of frames, so we must make + # sure that the mmeory is contiguous for stable forward + inputs = inputs.contiguous() + + return dict(inputs=inputs, data_samples=data_samples) + + def _get_track_pad_shape(self, data: dict) -> Dict[str, List]: + """Get the pad_shape of each image based on data and pad_size_divisor. + + Args: + data (dict): Data sampled from dataloader. + + Returns: + Dict[str, List]: The shape of padding. + """ + batch_pad_shape = dict() + batch_pad_shape = [] + for imgs in data['inputs']: + # The sequence images in one sample among a batch have the same + # original shape + pad_h = int(np.ceil(imgs.shape[-2] / + self.pad_size_divisor)) * self.pad_size_divisor + pad_w = int(np.ceil(imgs.shape[-1] / + self.pad_size_divisor)) * self.pad_size_divisor + pad_shapes = [(pad_h, pad_w)] * imgs.size(0) + batch_pad_shape.append(pad_shapes) + return batch_pad_shape + + def pad_track_gt_masks(self, + data_samples: Sequence[TrackDataSample]) -> None: + """Pad gt_masks to shape of batch_input_shape.""" + if 'masks' in data_samples[0][0].get('gt_instances', None): + for track_data_sample in data_samples: + for i in range(len(track_data_sample)): + det_data_sample = track_data_sample[i] + masks = det_data_sample.gt_instances.masks + # TODO: whether to use BitmapMasks + assert isinstance(masks, BitmapMasks) + batch_input_shape = det_data_sample.batch_input_shape + det_data_sample.gt_instances.masks = masks.pad( + batch_input_shape, pad_val=self.mask_pad_value) + + +def stack_track_batch(tensors: List[torch.Tensor], + pad_size_divisor: int = 0, + pad_value: Union[int, float] = 0) -> torch.Tensor: + """Stack multiple tensors to form a batch and pad the images to the max + shape use the right bottom padding mode in these images. If + ``pad_size_divisor > 0``, add padding to ensure the common height and width + is divisible by ``pad_size_divisor``. The difference between this function + and ``stack_batch`` in MMEngine is that this function can process batch + sequence images with shape (N, T, C, H, W). + + Args: + tensors (List[Tensor]): The input multiple tensors. each is a + TCHW 4D-tensor. T denotes the number of key/reference frames. + pad_size_divisor (int): If ``pad_size_divisor > 0``, add padding + to ensure the common height and width is divisible by + ``pad_size_divisor``. This depends on the model, and many + models need a divisibility of 32. Defaults to 0 + pad_value (int, float): The padding value. Defaults to 0 + + Returns: + Tensor: The NTCHW 5D-tensor. N denotes the batch size. + """ + assert isinstance(tensors, list), \ + f'Expected input type to be list, but got {type(tensors)}' + assert len(set([tensor.ndim for tensor in tensors])) == 1, \ + f'Expected the dimensions of all tensors must be the same, ' \ + f'but got {[tensor.ndim for tensor in tensors]}' + assert tensors[0].ndim == 4, f'Expected tensor dimension to be 4, ' \ + f'but got {tensors[0].ndim}' + assert len(set([tensor.shape[0] for tensor in tensors])) == 1, \ + f'Expected the channels of all tensors must be the same, ' \ + f'but got {[tensor.shape[0] for tensor in tensors]}' + + tensor_sizes = [(tensor.shape[-2], tensor.shape[-1]) for tensor in tensors] + max_size = np.stack(tensor_sizes).max(0) + + if pad_size_divisor > 1: + # the last two dims are H,W, both subject to divisibility requirement + max_size = ( + max_size + + (pad_size_divisor - 1)) // pad_size_divisor * pad_size_divisor + + padded_samples = [] + for tensor in tensors: + padding_size = [ + 0, max_size[-1] - tensor.shape[-1], 0, + max_size[-2] - tensor.shape[-2] + ] + if sum(padding_size) == 0: + padded_samples.append(tensor) + else: + padded_samples.append(F.pad(tensor, padding_size, value=pad_value)) + + return torch.stack(padded_samples, dim=0) diff --git a/mmdetection/mmdet/models/dense_heads/__init__.py b/mmdetection/mmdet/models/dense_heads/__init__.py new file mode 100644 index 00000000..c9b55ec2 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/__init__.py @@ -0,0 +1,72 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .anchor_free_head import AnchorFreeHead +from .anchor_head import AnchorHead +from .atss_head import ATSSHead +from .atss_vlfusion_head import ATSSVLFusionHead +from .autoassign_head import AutoAssignHead +from .boxinst_head import BoxInstBboxHead, BoxInstMaskHead +from .cascade_rpn_head import CascadeRPNHead, StageCascadeRPNHead +from .centernet_head import CenterNetHead +from .centernet_update_head import CenterNetUpdateHead +from .centripetal_head import CentripetalHead +from .condinst_head import CondInstBboxHead, CondInstMaskHead +from .conditional_detr_head import ConditionalDETRHead +from .corner_head import CornerHead +from .dab_detr_head import DABDETRHead +from .ddod_head import DDODHead +from .ddq_detr_head import DDQDETRHead +from .deformable_detr_head import DeformableDETRHead +from .detr_head import DETRHead +from .dino_head import DINOHead +from .embedding_rpn_head import EmbeddingRPNHead +from .fcos_head import FCOSHead +from .fovea_head import FoveaHead +from .free_anchor_retina_head import FreeAnchorRetinaHead +from .fsaf_head import FSAFHead +from .ga_retina_head import GARetinaHead +from .ga_rpn_head import GARPNHead +from .gfl_head import GFLHead +from .grounding_dino_head import GroundingDINOHead +from .guided_anchor_head import FeatureAdaption, GuidedAnchorHead +from .lad_head import LADHead +from .ld_head import LDHead +from .mask2former_head import Mask2FormerHead +from .maskformer_head import MaskFormerHead +from .nasfcos_head import NASFCOSHead +from .paa_head import PAAHead +from .pisa_retinanet_head import PISARetinaHead +from .pisa_ssd_head import PISASSDHead +from .reppoints_head import RepPointsHead +from .retina_head import RetinaHead +from .retina_sepbn_head import RetinaSepBNHead +from .rpn_head import RPNHead +from .rtmdet_head import RTMDetHead, RTMDetSepBNHead +from .rtmdet_ins_head import RTMDetInsHead, RTMDetInsSepBNHead +from .sabl_retina_head import SABLRetinaHead +from .solo_head import DecoupledSOLOHead, DecoupledSOLOLightHead, SOLOHead +from .solov2_head import SOLOV2Head +from .ssd_head import SSDHead +from .tood_head import TOODHead +from .vfnet_head import VFNetHead +from .yolact_head import YOLACTHead, YOLACTProtonet +from .yolo_head import YOLOV3Head +from .yolof_head import YOLOFHead +from .yolox_head import YOLOXHead + +__all__ = [ + 'AnchorFreeHead', 'AnchorHead', 'GuidedAnchorHead', 'FeatureAdaption', + 'RPNHead', 'GARPNHead', 'RetinaHead', 'RetinaSepBNHead', 'GARetinaHead', + 'SSDHead', 'FCOSHead', 'RepPointsHead', 'FoveaHead', + 'FreeAnchorRetinaHead', 'ATSSHead', 'FSAFHead', 'NASFCOSHead', + 'PISARetinaHead', 'PISASSDHead', 'GFLHead', 'CornerHead', 'YOLACTHead', + 'YOLACTProtonet', 'YOLOV3Head', 'PAAHead', 'SABLRetinaHead', + 'CentripetalHead', 'VFNetHead', 'StageCascadeRPNHead', 'CascadeRPNHead', + 'EmbeddingRPNHead', 'LDHead', 'AutoAssignHead', 'DETRHead', 'YOLOFHead', + 'DeformableDETRHead', 'CenterNetHead', 'YOLOXHead', 'SOLOHead', + 'DecoupledSOLOHead', 'DecoupledSOLOLightHead', 'SOLOV2Head', 'LADHead', + 'TOODHead', 'MaskFormerHead', 'Mask2FormerHead', 'DDODHead', + 'CenterNetUpdateHead', 'RTMDetHead', 'RTMDetSepBNHead', 'CondInstBboxHead', + 'CondInstMaskHead', 'RTMDetInsHead', 'RTMDetInsSepBNHead', + 'BoxInstBboxHead', 'BoxInstMaskHead', 'ConditionalDETRHead', 'DINOHead', + 'ATSSVLFusionHead', 'DABDETRHead', 'DDQDETRHead', 'GroundingDINOHead' +] diff --git a/mmdetection/mmdet/models/dense_heads/anchor_free_head.py b/mmdetection/mmdet/models/dense_heads/anchor_free_head.py new file mode 100644 index 00000000..90a9b362 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/anchor_free_head.py @@ -0,0 +1,317 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import abstractmethod +from typing import Any, List, Sequence, Tuple, Union + +import torch.nn as nn +from mmcv.cnn import ConvModule +from numpy import ndarray +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, OptConfigType, + OptInstanceList) +from ..task_modules.prior_generators import MlvlPointGenerator +from ..utils import multi_apply +from .base_dense_head import BaseDenseHead + +StrideType = Union[Sequence[int], Sequence[Tuple[int, int]]] + + +@MODELS.register_module() +class AnchorFreeHead(BaseDenseHead): + """Anchor-free head (FCOS, Fovea, RepPoints, etc.). + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + feat_channels (int): Number of hidden channels. Used in child classes. + stacked_convs (int): Number of stacking convs of the head. + strides (Sequence[int] or Sequence[Tuple[int, int]]): Downsample + factor of each feature map. + dcn_on_last_conv (bool): If true, use dcn in the last layer of + towers. Defaults to False. + conv_bias (bool or str): If specified as `auto`, it will be decided by + the norm_cfg. Bias of conv will be set as True if `norm_cfg` is + None, otherwise False. Default: "auto". + loss_cls (:obj:`ConfigDict` or dict): Config of classification loss. + loss_bbox (:obj:`ConfigDict` or dict): Config of localization loss. + bbox_coder (:obj:`ConfigDict` or dict): Config of bbox coder. Defaults + 'DistancePointBBoxCoder'. + conv_cfg (:obj:`ConfigDict` or dict, Optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict, Optional): Config dict for + normalization layer. Defaults to None. + train_cfg (:obj:`ConfigDict` or dict, Optional): Training config of + anchor-free head. + test_cfg (:obj:`ConfigDict` or dict, Optional): Testing config of + anchor-free head. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. + """ # noqa: W605 + + _version = 1 + + def __init__( + self, + num_classes: int, + in_channels: int, + feat_channels: int = 256, + stacked_convs: int = 4, + strides: StrideType = (4, 8, 16, 32, 64), + dcn_on_last_conv: bool = False, + conv_bias: Union[bool, str] = 'auto', + loss_cls: ConfigType = dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox: ConfigType = dict(type='IoULoss', loss_weight=1.0), + bbox_coder: ConfigType = dict(type='DistancePointBBoxCoder'), + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', name='conv_cls', std=0.01, bias_prob=0.01)) + ) -> None: + super().__init__(init_cfg=init_cfg) + self.num_classes = num_classes + self.use_sigmoid_cls = loss_cls.get('use_sigmoid', False) + if self.use_sigmoid_cls: + self.cls_out_channels = num_classes + else: + self.cls_out_channels = num_classes + 1 + self.in_channels = in_channels + self.feat_channels = feat_channels + self.stacked_convs = stacked_convs + self.strides = strides + self.dcn_on_last_conv = dcn_on_last_conv + assert conv_bias == 'auto' or isinstance(conv_bias, bool) + self.conv_bias = conv_bias + self.loss_cls = MODELS.build(loss_cls) + self.loss_bbox = MODELS.build(loss_bbox) + self.bbox_coder = TASK_UTILS.build(bbox_coder) + + self.prior_generator = MlvlPointGenerator(strides) + + # In order to keep a more general interface and be consistent with + # anchor_head. We can think of point like one anchor + self.num_base_priors = self.prior_generator.num_base_priors[0] + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.fp16_enabled = False + + self._init_layers() + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self._init_cls_convs() + self._init_reg_convs() + self._init_predictor() + + def _init_cls_convs(self) -> None: + """Initialize classification conv layers of the head.""" + self.cls_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + if self.dcn_on_last_conv and i == self.stacked_convs - 1: + conv_cfg = dict(type='DCNv2') + else: + conv_cfg = self.conv_cfg + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=self.norm_cfg, + bias=self.conv_bias)) + + def _init_reg_convs(self) -> None: + """Initialize bbox regression conv layers of the head.""" + self.reg_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + if self.dcn_on_last_conv and i == self.stacked_convs - 1: + conv_cfg = dict(type='DCNv2') + else: + conv_cfg = self.conv_cfg + self.reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=self.norm_cfg, + bias=self.conv_bias)) + + def _init_predictor(self) -> None: + """Initialize predictor layers of the head.""" + self.conv_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + self.conv_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1) + + def _load_from_state_dict(self, state_dict: dict, prefix: str, + local_metadata: dict, strict: bool, + missing_keys: Union[List[str], str], + unexpected_keys: Union[List[str], str], + error_msgs: Union[List[str], str]) -> None: + """Hack some keys of the model state dict so that can load checkpoints + of previous version.""" + version = local_metadata.get('version', None) + if version is None: + # the key is different in early versions + # for example, 'fcos_cls' become 'conv_cls' now + bbox_head_keys = [ + k for k in state_dict.keys() if k.startswith(prefix) + ] + ori_predictor_keys = [] + new_predictor_keys = [] + # e.g. 'fcos_cls' or 'fcos_reg' + for key in bbox_head_keys: + ori_predictor_keys.append(key) + key = key.split('.') + if len(key) < 2: + conv_name = None + elif key[1].endswith('cls'): + conv_name = 'conv_cls' + elif key[1].endswith('reg'): + conv_name = 'conv_reg' + elif key[1].endswith('centerness'): + conv_name = 'conv_centerness' + else: + conv_name = None + if conv_name is not None: + key[1] = conv_name + new_predictor_keys.append('.'.join(key)) + else: + ori_predictor_keys.pop(-1) + for i in range(len(new_predictor_keys)): + state_dict[new_predictor_keys[i]] = state_dict.pop( + ori_predictor_keys[i]) + super()._load_from_state_dict(state_dict, prefix, local_metadata, + strict, missing_keys, unexpected_keys, + error_msgs) + + def forward(self, x: Tuple[Tensor]) -> Tuple[List[Tensor], List[Tensor]]: + """Forward features from the upstream network. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually contain classification scores and bbox predictions. + + - cls_scores (list[Tensor]): Box scores for each scale level, \ + each is a 4D-tensor, the channel number is \ + num_points * num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for each scale \ + level, each is a 4D-tensor, the channel number is num_points * 4. + """ + return multi_apply(self.forward_single, x)[:2] + + def forward_single(self, x: Tensor) -> Tuple[Tensor, ...]: + """Forward features of a single scale level. + + Args: + x (Tensor): FPN feature maps of the specified stride. + + Returns: + tuple: Scores for each class, bbox predictions, features + after classification and regression conv layers, some + models needs these features like FCOS. + """ + cls_feat = x + reg_feat = x + + for cls_layer in self.cls_convs: + cls_feat = cls_layer(cls_feat) + cls_score = self.conv_cls(cls_feat) + + for reg_layer in self.reg_convs: + reg_feat = reg_layer(reg_feat) + bbox_pred = self.conv_reg(reg_feat) + return cls_score, bbox_pred, cls_feat, reg_feat + + @abstractmethod + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level, + each is a 4D-tensor, the channel number is + num_points * num_classes. + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level, each is a 4D-tensor, the channel number is + num_points * 4. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + """ + + raise NotImplementedError + + @abstractmethod + def get_targets(self, points: List[Tensor], + batch_gt_instances: InstanceList) -> Any: + """Compute regression, classification and centerness targets for points + in multiple images. + + Args: + points (list[Tensor]): Points of each fpn level, each has shape + (num_points, 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + """ + raise NotImplementedError + + # TODO refactor aug_test + def aug_test(self, + aug_batch_feats: List[Tensor], + aug_batch_img_metas: List[List[Tensor]], + rescale: bool = False) -> List[ndarray]: + """Test function with test time augmentation. + + Args: + aug_batch_feats (list[Tensor]): the outer list indicates test-time + augmentations and inner Tensor should have a shape NxCxHxW, + which contains features for all images in the batch. + aug_batch_img_metas (list[list[dict]]): the outer list indicates + test-time augs (multiscale, flip, etc.) and the inner list + indicates images in a batch. each dict has image information. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[ndarray]: bbox results of each class + """ + return self.aug_test_bboxes( + aug_batch_feats, aug_batch_img_metas, rescale=rescale) diff --git a/mmdetection/mmdet/models/dense_heads/anchor_head.py b/mmdetection/mmdet/models/dense_heads/anchor_head.py new file mode 100644 index 00000000..4578caca --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/anchor_head.py @@ -0,0 +1,530 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import List, Optional, Tuple, Union + +import torch +import torch.nn as nn +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures.bbox import BaseBoxes, cat_boxes, get_box_tensor +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList, OptMultiConfig) +from ..task_modules.prior_generators import (AnchorGenerator, + anchor_inside_flags) +from ..task_modules.samplers import PseudoSampler +from ..utils import images_to_levels, multi_apply, unmap +from .base_dense_head import BaseDenseHead + + +@MODELS.register_module() +class AnchorHead(BaseDenseHead): + """Anchor-based head (RPN, RetinaNet, SSD, etc.). + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + feat_channels (int): Number of hidden channels. Used in child classes. + anchor_generator (dict): Config dict for anchor generator + bbox_coder (dict): Config of bounding box coder. + reg_decoded_bbox (bool): If true, the regression loss would be + applied directly on decoded bounding boxes, converting both + the predicted boxes and regression targets to absolute + coordinates format. Default False. It should be `True` when + using `IoULoss`, `GIoULoss`, or `DIoULoss` in the bbox head. + loss_cls (dict): Config of classification loss. + loss_bbox (dict): Config of localization loss. + train_cfg (dict): Training config of anchor head. + test_cfg (dict): Testing config of anchor head. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ # noqa: W605 + + def __init__( + self, + num_classes: int, + in_channels: int, + feat_channels: int = 256, + anchor_generator: ConfigType = dict( + type='AnchorGenerator', + scales=[8, 16, 32], + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + bbox_coder: ConfigType = dict( + type='DeltaXYWHBBoxCoder', + clip_border=True, + target_means=(.0, .0, .0, .0), + target_stds=(1.0, 1.0, 1.0, 1.0)), + reg_decoded_bbox: bool = False, + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox: ConfigType = dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = dict( + type='Normal', layer='Conv2d', std=0.01) + ) -> None: + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.num_classes = num_classes + self.feat_channels = feat_channels + self.use_sigmoid_cls = loss_cls.get('use_sigmoid', False) + if self.use_sigmoid_cls: + self.cls_out_channels = num_classes + else: + self.cls_out_channels = num_classes + 1 + + if self.cls_out_channels <= 0: + raise ValueError(f'num_classes={num_classes} is too small') + self.reg_decoded_bbox = reg_decoded_bbox + + self.bbox_coder = TASK_UTILS.build(bbox_coder) + self.loss_cls = MODELS.build(loss_cls) + self.loss_bbox = MODELS.build(loss_bbox) + self.train_cfg = train_cfg + self.test_cfg = test_cfg + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + if train_cfg.get('sampler', None) is not None: + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], default_args=dict(context=self)) + else: + self.sampler = PseudoSampler(context=self) + + self.fp16_enabled = False + + self.prior_generator = TASK_UTILS.build(anchor_generator) + + # Usually the numbers of anchors for each level are the same + # except SSD detectors. So it is an int in the most dense + # heads but a list of int in SSDHead + self.num_base_priors = self.prior_generator.num_base_priors[0] + self._init_layers() + + @property + def num_anchors(self) -> int: + warnings.warn('DeprecationWarning: `num_anchors` is deprecated, ' + 'for consistency or also use ' + '`num_base_priors` instead') + return self.prior_generator.num_base_priors[0] + + @property + def anchor_generator(self) -> AnchorGenerator: + warnings.warn('DeprecationWarning: anchor_generator is deprecated, ' + 'please use "prior_generator" instead') + return self.prior_generator + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.conv_cls = nn.Conv2d(self.in_channels, + self.num_base_priors * self.cls_out_channels, + 1) + reg_dim = self.bbox_coder.encode_size + self.conv_reg = nn.Conv2d(self.in_channels, + self.num_base_priors * reg_dim, 1) + + def forward_single(self, x: Tensor) -> Tuple[Tensor, Tensor]: + """Forward feature of a single scale level. + + Args: + x (Tensor): Features of a single scale level. + + Returns: + tuple: + cls_score (Tensor): Cls scores for a single scale level \ + the channels number is num_base_priors * num_classes. + bbox_pred (Tensor): Box energies / deltas for a single scale \ + level, the channels number is num_base_priors * 4. + """ + cls_score = self.conv_cls(x) + bbox_pred = self.conv_reg(x) + return cls_score, bbox_pred + + def forward(self, x: Tuple[Tensor]) -> Tuple[List[Tensor]]: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of classification scores and bbox prediction. + + - cls_scores (list[Tensor]): Classification scores for all \ + scale levels, each is a 4D-tensor, the channels number \ + is num_base_priors * num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for all \ + scale levels, each is a 4D-tensor, the channels number \ + is num_base_priors * 4. + """ + return multi_apply(self.forward_single, x) + + def get_anchors(self, + featmap_sizes: List[tuple], + batch_img_metas: List[dict], + device: Union[torch.device, str] = 'cuda') \ + -> Tuple[List[List[Tensor]], List[List[Tensor]]]: + """Get anchors according to feature map sizes. + + Args: + featmap_sizes (list[tuple]): Multi-level feature map sizes. + batch_img_metas (list[dict]): Image meta info. + device (torch.device | str): Device for returned tensors. + Defaults to cuda. + + Returns: + tuple: + + - anchor_list (list[list[Tensor]]): Anchors of each image. + - valid_flag_list (list[list[Tensor]]): Valid flags of each + image. + """ + num_imgs = len(batch_img_metas) + + # since feature map sizes of all images are the same, we only compute + # anchors for one time + multi_level_anchors = self.prior_generator.grid_priors( + featmap_sizes, device=device) + anchor_list = [multi_level_anchors for _ in range(num_imgs)] + + # for each image, we compute valid flags of multi level anchors + valid_flag_list = [] + for img_id, img_meta in enumerate(batch_img_metas): + multi_level_flags = self.prior_generator.valid_flags( + featmap_sizes, img_meta['pad_shape'], device) + valid_flag_list.append(multi_level_flags) + + return anchor_list, valid_flag_list + + def _get_targets_single(self, + flat_anchors: Union[Tensor, BaseBoxes], + valid_flags: Tensor, + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression and classification targets for anchors in a + single image. + + Args: + flat_anchors (Tensor or :obj:`BaseBoxes`): Multi-level anchors + of the image, which are concatenated into a single tensor + or box type of shape (num_anchors, 4) + valid_flags (Tensor): Multi level valid flags of the image, + which are concatenated into a single tensor of + shape (num_anchors, ). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + + Returns: + tuple: + + - labels (Tensor): Labels of each level. + - label_weights (Tensor): Label weights of each level. + - bbox_targets (Tensor): BBox targets of each level. + - bbox_weights (Tensor): BBox weights of each level. + - pos_inds (Tensor): positive samples indexes. + - neg_inds (Tensor): negative samples indexes. + - sampling_result (:obj:`SamplingResult`): Sampling results. + """ + inside_flags = anchor_inside_flags(flat_anchors, valid_flags, + img_meta['img_shape'][:2], + self.train_cfg['allowed_border']) + if not inside_flags.any(): + raise ValueError( + 'There is no valid anchor inside the image boundary. Please ' + 'check the image size and anchor sizes, or set ' + '``allowed_border`` to -1 to skip the condition.') + # assign gt and sample anchors + anchors = flat_anchors[inside_flags] + + pred_instances = InstanceData(priors=anchors) + assign_result = self.assigner.assign(pred_instances, gt_instances, + gt_instances_ignore) + # No sampling is required except for RPN and + # Guided Anchoring algorithms + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_valid_anchors = anchors.shape[0] + target_dim = gt_instances.bboxes.size(-1) if self.reg_decoded_bbox \ + else self.bbox_coder.encode_size + bbox_targets = anchors.new_zeros(num_valid_anchors, target_dim) + bbox_weights = anchors.new_zeros(num_valid_anchors, target_dim) + + # TODO: Considering saving memory, is it necessary to be long? + labels = anchors.new_full((num_valid_anchors, ), + self.num_classes, + dtype=torch.long) + label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + # `bbox_coder.encode` accepts tensor or box type inputs and generates + # tensor targets. If regressing decoded boxes, the code will convert + # box type `pos_bbox_targets` to tensor. + if len(pos_inds) > 0: + if not self.reg_decoded_bbox: + pos_bbox_targets = self.bbox_coder.encode( + sampling_result.pos_priors, sampling_result.pos_gt_bboxes) + else: + pos_bbox_targets = sampling_result.pos_gt_bboxes + pos_bbox_targets = get_box_tensor(pos_bbox_targets) + bbox_targets[pos_inds, :] = pos_bbox_targets + bbox_weights[pos_inds, :] = 1.0 + + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_anchors.size(0) + labels = unmap( + labels, num_total_anchors, inside_flags, + fill=self.num_classes) # fill bg label + label_weights = unmap(label_weights, num_total_anchors, + inside_flags) + bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags) + bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags) + + return (labels, label_weights, bbox_targets, bbox_weights, pos_inds, + neg_inds, sampling_result) + + def get_targets(self, + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs: bool = True, + return_sampling_results: bool = False) -> tuple: + """Compute regression and classification targets for anchors in + multiple images. + + Args: + anchor_list (list[list[Tensor]]): Multi level anchors of each + image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_anchors, 4). + valid_flag_list (list[list[Tensor]]): Multi level valid flags of + each image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_anchors, ) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + return_sampling_results (bool): Whether to return the sampling + results. Defaults to False. + + Returns: + tuple: Usually returns a tuple containing learning targets. + + - labels_list (list[Tensor]): Labels of each level. + - label_weights_list (list[Tensor]): Label weights of each + level. + - bbox_targets_list (list[Tensor]): BBox targets of each level. + - bbox_weights_list (list[Tensor]): BBox weights of each level. + - avg_factor (int): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + `PseudoSampler`, `avg_factor` is usually equal to the number + of positive priors. + + additional_returns: This function enables user-defined returns from + `self._get_targets_single`. These returns are currently refined + to properties at each feature map (i.e. having HxW dimension). + The results will be concatenated after the end + """ + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + # concat all level anchors to a single tensor + concat_anchor_list = [] + concat_valid_flag_list = [] + for i in range(num_imgs): + assert len(anchor_list[i]) == len(valid_flag_list[i]) + concat_anchor_list.append(cat_boxes(anchor_list[i])) + concat_valid_flag_list.append(torch.cat(valid_flag_list[i])) + + # compute targets for each image + results = multi_apply( + self._get_targets_single, + concat_anchor_list, + concat_valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + (all_labels, all_label_weights, all_bbox_targets, all_bbox_weights, + pos_inds_list, neg_inds_list, sampling_results_list) = results[:7] + rest_results = list(results[7:]) # user-added return values + # Get `avg_factor` of all images, which calculate in `SamplingResult`. + # When using sampling method, avg_factor is usually the sum of + # positive and negative priors. When using `PseudoSampler`, + # `avg_factor` is usually equal to the number of positive priors. + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + # update `_raw_positive_infos`, which will be used when calling + # `get_positive_infos`. + self._raw_positive_infos.update(sampling_results=sampling_results_list) + # split targets to a list w.r.t. multiple levels + labels_list = images_to_levels(all_labels, num_level_anchors) + label_weights_list = images_to_levels(all_label_weights, + num_level_anchors) + bbox_targets_list = images_to_levels(all_bbox_targets, + num_level_anchors) + bbox_weights_list = images_to_levels(all_bbox_weights, + num_level_anchors) + res = (labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor) + if return_sampling_results: + res = res + (sampling_results_list, ) + for i, r in enumerate(rest_results): # user-added return values + rest_results[i] = images_to_levels(r, num_level_anchors) + + return res + tuple(rest_results) + + def loss_by_feat_single(self, cls_score: Tensor, bbox_pred: Tensor, + anchors: Tensor, labels: Tensor, + label_weights: Tensor, bbox_targets: Tensor, + bbox_weights: Tensor, avg_factor: int) -> tuple: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + cls_score (Tensor): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W). + bbox_pred (Tensor): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + anchors (Tensor): Box reference for each scale level with shape + (N, num_total_anchors, 4). + labels (Tensor): Labels of each anchors with shape + (N, num_total_anchors). + label_weights (Tensor): Label weights of each anchor with shape + (N, num_total_anchors) + bbox_targets (Tensor): BBox regression targets of each anchor + weight shape (N, num_total_anchors, 4). + bbox_weights (Tensor): BBox regression loss weights of each anchor + with shape (N, num_total_anchors, 4). + avg_factor (int): Average factor that is used to average the loss. + + Returns: + tuple: loss components. + """ + # classification loss + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + cls_score = cls_score.permute(0, 2, 3, + 1).reshape(-1, self.cls_out_channels) + loss_cls = self.loss_cls( + cls_score, labels, label_weights, avg_factor=avg_factor) + # regression loss + target_dim = bbox_targets.size(-1) + bbox_targets = bbox_targets.reshape(-1, target_dim) + bbox_weights = bbox_weights.reshape(-1, target_dim) + bbox_pred = bbox_pred.permute(0, 2, 3, + 1).reshape(-1, + self.bbox_coder.encode_size) + if self.reg_decoded_bbox: + # When the regression loss (e.g. `IouLoss`, `GIouLoss`) + # is applied directly on the decoded bounding boxes, it + # decodes the already encoded coordinates to absolute format. + anchors = anchors.reshape(-1, anchors.size(-1)) + bbox_pred = self.bbox_coder.decode(anchors, bbox_pred) + bbox_pred = get_box_tensor(bbox_pred) + loss_bbox = self.loss_bbox( + bbox_pred, bbox_targets, bbox_weights, avg_factor=avg_factor) + return loss_cls, loss_bbox + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + has shape (N, num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + avg_factor) = cls_reg_targets + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + # concat all level anchors and flags to a single tensor + concat_anchor_list = [] + for i in range(len(anchor_list)): + concat_anchor_list.append(cat_boxes(anchor_list[i])) + all_anchor_list = images_to_levels(concat_anchor_list, + num_level_anchors) + + losses_cls, losses_bbox = multi_apply( + self.loss_by_feat_single, + cls_scores, + bbox_preds, + all_anchor_list, + labels_list, + label_weights_list, + bbox_targets_list, + bbox_weights_list, + avg_factor=avg_factor) + return dict(loss_cls=losses_cls, loss_bbox=losses_bbox) diff --git a/mmdetection/mmdet/models/dense_heads/atss_head.py b/mmdetection/mmdet/models/dense_heads/atss_head.py new file mode 100644 index 00000000..2ce71b3e --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/atss_head.py @@ -0,0 +1,524 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Sequence, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, Scale +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, OptConfigType, + OptInstanceList, reduce_mean) +from ..task_modules.prior_generators import anchor_inside_flags +from ..utils import images_to_levels, multi_apply, unmap +from .anchor_head import AnchorHead + + +@MODELS.register_module() +class ATSSHead(AnchorHead): + """Detection Head of `ATSS `_. + + ATSS head structure is similar with FCOS, however ATSS use anchor boxes + and assign label by Adaptive Training Sample Selection instead max-iou. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + pred_kernel_size (int): Kernel size of ``nn.Conv2d`` + stacked_convs (int): Number of stacking convs of the head. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict): Config dict for normalization + layer. Defaults to ``dict(type='GN', num_groups=32, + requires_grad=True)``. + reg_decoded_bbox (bool): If true, the regression loss would be + applied directly on decoded bounding boxes, converting both + the predicted boxes and regression targets to absolute + coordinates format. Defaults to False. It should be `True` when + using `IoULoss`, `GIoULoss`, or `DIoULoss` in the bbox head. + loss_centerness (:obj:`ConfigDict` or dict): Config of centerness loss. + Defaults to ``dict(type='CrossEntropyLoss', use_sigmoid=True, + loss_weight=1.0)``. + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`]): Initialization config dict. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + pred_kernel_size: int = 3, + stacked_convs: int = 4, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict( + type='GN', num_groups=32, requires_grad=True), + reg_decoded_bbox: bool = True, + loss_centerness: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0), + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', + name='atss_cls', + std=0.01, + bias_prob=0.01)), + **kwargs) -> None: + self.pred_kernel_size = pred_kernel_size + self.stacked_convs = stacked_convs + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + reg_decoded_bbox=reg_decoded_bbox, + init_cfg=init_cfg, + **kwargs) + + self.sampling = False + self.loss_centerness = MODELS.build(loss_centerness) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.relu = nn.ReLU(inplace=True) + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + pred_pad_size = self.pred_kernel_size // 2 + self.atss_cls = nn.Conv2d( + self.feat_channels, + self.num_anchors * self.cls_out_channels, + self.pred_kernel_size, + padding=pred_pad_size) + self.atss_reg = nn.Conv2d( + self.feat_channels, + self.num_base_priors * 4, + self.pred_kernel_size, + padding=pred_pad_size) + self.atss_centerness = nn.Conv2d( + self.feat_channels, + self.num_base_priors * 1, + self.pred_kernel_size, + padding=pred_pad_size) + self.scales = nn.ModuleList( + [Scale(1.0) for _ in self.prior_generator.strides]) + + def forward(self, x: Tuple[Tensor]) -> Tuple[List[Tensor]]: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually a tuple of classification scores and bbox prediction + cls_scores (list[Tensor]): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_anchors * num_classes. + bbox_preds (list[Tensor]): Box energies / deltas for all scale + levels, each is a 4D-tensor, the channels number is + num_anchors * 4. + """ + return multi_apply(self.forward_single, x, self.scales) + + def forward_single(self, x: Tensor, scale: Scale) -> Sequence[Tensor]: + """Forward feature of a single scale level. + + Args: + x (Tensor): Features of a single scale level. + scale (:obj: `mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + + Returns: + tuple: + cls_score (Tensor): Cls scores for a single scale level + the channels number is num_anchors * num_classes. + bbox_pred (Tensor): Box energies / deltas for a single scale + level, the channels number is num_anchors * 4. + centerness (Tensor): Centerness for a single scale level, the + channel number is (N, num_anchors * 1, H, W). + """ + cls_feat = x + reg_feat = x + for cls_conv in self.cls_convs: + cls_feat = cls_conv(cls_feat) + for reg_conv in self.reg_convs: + reg_feat = reg_conv(reg_feat) + cls_score = self.atss_cls(cls_feat) + # we just follow atss, not apply exp in bbox_pred + bbox_pred = scale(self.atss_reg(reg_feat)).float() + centerness = self.atss_centerness(reg_feat) + return cls_score, bbox_pred, centerness + + def loss_by_feat_single(self, anchors: Tensor, cls_score: Tensor, + bbox_pred: Tensor, centerness: Tensor, + labels: Tensor, label_weights: Tensor, + bbox_targets: Tensor, avg_factor: float) -> dict: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + cls_score (Tensor): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W). + bbox_pred (Tensor): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + anchors (Tensor): Box reference for each scale level with shape + (N, num_total_anchors, 4). + labels (Tensor): Labels of each anchors with shape + (N, num_total_anchors). + label_weights (Tensor): Label weights of each anchor with shape + (N, num_total_anchors) + bbox_targets (Tensor): BBox regression targets of each anchor with + shape (N, num_total_anchors, 4). + avg_factor (float): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + `PseudoSampler`, `avg_factor` is usually equal to the number + of positive priors. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + + anchors = anchors.reshape(-1, 4) + cls_score = cls_score.permute(0, 2, 3, 1).reshape( + -1, self.cls_out_channels).contiguous() + bbox_pred = bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4) + centerness = centerness.permute(0, 2, 3, 1).reshape(-1) + bbox_targets = bbox_targets.reshape(-1, 4) + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + + # classification loss + loss_cls = self.loss_cls( + cls_score, labels, label_weights, avg_factor=avg_factor) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().squeeze(1) + + if len(pos_inds) > 0: + pos_bbox_targets = bbox_targets[pos_inds] + pos_bbox_pred = bbox_pred[pos_inds] + pos_anchors = anchors[pos_inds] + pos_centerness = centerness[pos_inds] + + centerness_targets = self.centerness_target( + pos_anchors, pos_bbox_targets) + pos_decode_bbox_pred = self.bbox_coder.decode( + pos_anchors, pos_bbox_pred) + + # regression loss + loss_bbox = self.loss_bbox( + pos_decode_bbox_pred, + pos_bbox_targets, + weight=centerness_targets, + avg_factor=1.0) + + # centerness loss + loss_centerness = self.loss_centerness( + pos_centerness, centerness_targets, avg_factor=avg_factor) + + else: + loss_bbox = bbox_pred.sum() * 0 + loss_centerness = centerness.sum() * 0 + centerness_targets = bbox_targets.new_tensor(0.) + + return loss_cls, loss_bbox, loss_centerness, centerness_targets.sum() + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + centernesses: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + centernesses (list[Tensor]): Centerness for each scale + level with shape (N, num_anchors * 1, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in bbox_preds] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + + (anchor_list, labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor) = cls_reg_targets + avg_factor = reduce_mean( + torch.tensor(avg_factor, dtype=torch.float, device=device)).item() + + losses_cls, losses_bbox, loss_centerness, \ + bbox_avg_factor = multi_apply( + self.loss_by_feat_single, + anchor_list, + cls_scores, + bbox_preds, + centernesses, + labels_list, + label_weights_list, + bbox_targets_list, + avg_factor=avg_factor) + + bbox_avg_factor = sum(bbox_avg_factor) + bbox_avg_factor = reduce_mean(bbox_avg_factor).clamp_(min=1).item() + losses_bbox = list(map(lambda x: x / bbox_avg_factor, losses_bbox)) + return dict( + loss_cls=losses_cls, + loss_bbox=losses_bbox, + loss_centerness=loss_centerness) + + def centerness_target(self, anchors: Tensor, gts: Tensor) -> Tensor: + """Calculate the centerness between anchors and gts. + + Only calculate pos centerness targets, otherwise there may be nan. + + Args: + anchors (Tensor): Anchors with shape (N, 4), "xyxy" format. + gts (Tensor): Ground truth bboxes with shape (N, 4), "xyxy" format. + + Returns: + Tensor: Centerness between anchors and gts. + """ + anchors_cx = (anchors[:, 2] + anchors[:, 0]) / 2 + anchors_cy = (anchors[:, 3] + anchors[:, 1]) / 2 + l_ = anchors_cx - gts[:, 0] + t_ = anchors_cy - gts[:, 1] + r_ = gts[:, 2] - anchors_cx + b_ = gts[:, 3] - anchors_cy + + left_right = torch.stack([l_, r_], dim=1) + top_bottom = torch.stack([t_, b_], dim=1) + centerness = torch.sqrt( + (left_right.min(dim=-1)[0] / left_right.max(dim=-1)[0]) * + (top_bottom.min(dim=-1)[0] / top_bottom.max(dim=-1)[0])) + assert not torch.isnan(centerness).any() + return centerness + + def get_targets(self, + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs: bool = True) -> tuple: + """Get targets for ATSS head. + + This method is almost the same as `AnchorHead.get_targets()`. Besides + returning the targets as the parent method does, it also returns the + anchors as the first element of the returned tuple. + """ + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + num_level_anchors_list = [num_level_anchors] * num_imgs + + # concat all level anchors and flags to a single tensor + for i in range(num_imgs): + assert len(anchor_list[i]) == len(valid_flag_list[i]) + anchor_list[i] = torch.cat(anchor_list[i]) + valid_flag_list[i] = torch.cat(valid_flag_list[i]) + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + (all_anchors, all_labels, all_label_weights, all_bbox_targets, + all_bbox_weights, pos_inds_list, neg_inds_list, + sampling_results_list) = multi_apply( + self._get_targets_single, + anchor_list, + valid_flag_list, + num_level_anchors_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + # Get `avg_factor` of all images, which calculate in `SamplingResult`. + # When using sampling method, avg_factor is usually the sum of + # positive and negative priors. When using `PseudoSampler`, + # `avg_factor` is usually equal to the number of positive priors. + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + # split targets to a list w.r.t. multiple levels + anchors_list = images_to_levels(all_anchors, num_level_anchors) + labels_list = images_to_levels(all_labels, num_level_anchors) + label_weights_list = images_to_levels(all_label_weights, + num_level_anchors) + bbox_targets_list = images_to_levels(all_bbox_targets, + num_level_anchors) + bbox_weights_list = images_to_levels(all_bbox_weights, + num_level_anchors) + return (anchors_list, labels_list, label_weights_list, + bbox_targets_list, bbox_weights_list, avg_factor) + + def _get_targets_single(self, + flat_anchors: Tensor, + valid_flags: Tensor, + num_level_anchors: List[int], + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression, classification targets for anchors in a single + image. + + Args: + flat_anchors (Tensor): Multi-level anchors of the image, which are + concatenated into a single tensor of shape (num_anchors ,4) + valid_flags (Tensor): Multi level valid flags of the image, + which are concatenated into a single tensor of + shape (num_anchors,). + num_level_anchors (List[int]): Number of anchors of each scale + level. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. + + Returns: + tuple: N is the number of total anchors in the image. + labels (Tensor): Labels of all anchors in the image with shape + (N,). + label_weights (Tensor): Label weights of all anchor in the + image with shape (N,). + bbox_targets (Tensor): BBox targets of all anchors in the + image with shape (N, 4). + bbox_weights (Tensor): BBox weights of all anchors in the + image with shape (N, 4) + pos_inds (Tensor): Indices of positive anchor with shape + (num_pos,). + neg_inds (Tensor): Indices of negative anchor with shape + (num_neg,). + sampling_result (:obj:`SamplingResult`): Sampling results. + """ + inside_flags = anchor_inside_flags(flat_anchors, valid_flags, + img_meta['img_shape'][:2], + self.train_cfg['allowed_border']) + if not inside_flags.any(): + raise ValueError( + 'There is no valid anchor inside the image boundary. Please ' + 'check the image size and anchor sizes, or set ' + '``allowed_border`` to -1 to skip the condition.') + # assign gt and sample anchors + anchors = flat_anchors[inside_flags, :] + + num_level_anchors_inside = self.get_num_level_anchors_inside( + num_level_anchors, inside_flags) + pred_instances = InstanceData(priors=anchors) + assign_result = self.assigner.assign(pred_instances, + num_level_anchors_inside, + gt_instances, gt_instances_ignore) + + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_valid_anchors = anchors.shape[0] + bbox_targets = torch.zeros_like(anchors) + bbox_weights = torch.zeros_like(anchors) + labels = anchors.new_full((num_valid_anchors, ), + self.num_classes, + dtype=torch.long) + label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + if self.reg_decoded_bbox: + pos_bbox_targets = sampling_result.pos_gt_bboxes + else: + pos_bbox_targets = self.bbox_coder.encode( + sampling_result.pos_priors, sampling_result.pos_gt_bboxes) + + bbox_targets[pos_inds, :] = pos_bbox_targets + bbox_weights[pos_inds, :] = 1.0 + + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_anchors.size(0) + anchors = unmap(anchors, num_total_anchors, inside_flags) + labels = unmap( + labels, num_total_anchors, inside_flags, fill=self.num_classes) + label_weights = unmap(label_weights, num_total_anchors, + inside_flags) + bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags) + bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags) + + return (anchors, labels, label_weights, bbox_targets, bbox_weights, + pos_inds, neg_inds, sampling_result) + + def get_num_level_anchors_inside(self, num_level_anchors, inside_flags): + """Get the number of valid anchors in every level.""" + + split_inside_flags = torch.split(inside_flags, num_level_anchors) + num_level_anchors_inside = [ + int(flags.sum()) for flags in split_inside_flags + ] + return num_level_anchors_inside diff --git a/mmdetection/mmdet/models/dense_heads/atss_vlfusion_head.py b/mmdetection/mmdet/models/dense_heads/atss_vlfusion_head.py new file mode 100644 index 00000000..c5cd28b4 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/atss_vlfusion_head.py @@ -0,0 +1,949 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import math +from typing import Callable, List, Optional, Sequence, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Scale +from mmcv.ops.modulated_deform_conv import ModulatedDeformConv2d +from mmengine.config import ConfigDict +from mmengine.model import BaseModel +from mmengine.structures import InstanceData +from torch import Tensor + +try: + from transformers import BertConfig +except ImportError: + BertConfig = None + +from mmdet.registry import MODELS +from mmdet.structures.bbox import cat_boxes +from mmdet.utils import InstanceList, OptInstanceList, reduce_mean +from ..utils import (BertEncoderLayer, VLFuse, filter_scores_and_topk, + permute_and_flatten, select_single_mlvl, + unpack_gt_instances) +from ..utils.vlfuse_helper import MAX_CLAMP_VALUE +from .atss_head import ATSSHead + + +def convert_grounding_to_cls_scores(logits: Tensor, + positive_maps: List[dict]) -> Tensor: + """Convert logits to class scores.""" + assert len(positive_maps) == logits.shape[0] # batch size + + scores = torch.zeros(logits.shape[0], logits.shape[1], + len(positive_maps[0])).to(logits.device) + if positive_maps is not None: + if all(x == positive_maps[0] for x in positive_maps): + # only need to compute once + positive_map = positive_maps[0] + for label_j in positive_map: + scores[:, :, label_j - + 1] = logits[:, :, + torch.LongTensor(positive_map[label_j] + )].mean(-1) + else: + for i, positive_map in enumerate(positive_maps): + for label_j in positive_map: + scores[i, :, label_j - 1] = logits[ + i, :, torch.LongTensor(positive_map[label_j])].mean(-1) + return scores + + +class Conv3x3Norm(nn.Module): + """Conv3x3 and norm.""" + + def __init__(self, + in_channels: int, + out_channels: int, + stride: int, + groups: int = 1, + use_dcn: bool = False, + norm_type: Optional[Union[Sequence, str]] = None): + super().__init__() + + if use_dcn: + self.conv = ModulatedDeformConv2d( + in_channels, + out_channels, + kernel_size=3, + stride=stride, + padding=1, + groups=groups) + else: + self.conv = nn.Conv2d( + in_channels, + out_channels, + kernel_size=3, + stride=stride, + padding=1, + groups=groups) + + if isinstance(norm_type, Sequence): + assert len(norm_type) == 2 + assert norm_type[0] == 'gn' + gn_group = norm_type[1] + norm_type = norm_type[0] + + if norm_type == 'bn': + bn_op = nn.BatchNorm2d(out_channels) + elif norm_type == 'gn': + bn_op = nn.GroupNorm( + num_groups=gn_group, num_channels=out_channels) + if norm_type is not None: + self.bn = bn_op + else: + self.bn = None + + def forward(self, x, **kwargs): + x = self.conv(x, **kwargs) + if self.bn: + x = self.bn(x) + return x + + +class DyReLU(nn.Module): + """Dynamic ReLU.""" + + def __init__(self, + in_channels: int, + out_channels: int, + expand_ratio: int = 4): + super().__init__() + self.avg_pool = nn.AdaptiveAvgPool2d(1) + self.expand_ratio = expand_ratio + self.out_channels = out_channels + + self.fc = nn.Sequential( + nn.Linear(in_channels, in_channels // expand_ratio), + nn.ReLU(inplace=True), + nn.Linear(in_channels // expand_ratio, + out_channels * self.expand_ratio), + nn.Hardsigmoid(inplace=True)) + + def forward(self, x) -> Tensor: + x_out = x + b, c, h, w = x.size() + x = self.avg_pool(x).view(b, c) + x = self.fc(x).view(b, -1, 1, 1) + + a1, b1, a2, b2 = torch.split(x, self.out_channels, dim=1) + a1 = (a1 - 0.5) * 2 + 1.0 + a2 = (a2 - 0.5) * 2 + b1 = b1 - 0.5 + b2 = b2 - 0.5 + out = torch.max(x_out * a1 + b1, x_out * a2 + b2) + return out + + +class DyConv(nn.Module): + """Dynamic Convolution.""" + + def __init__(self, + conv_func: Callable, + in_channels: int, + out_channels: int, + use_dyfuse: bool = True, + use_dyrelu: bool = False, + use_dcn: bool = False): + super().__init__() + + self.dyconvs = nn.ModuleList() + self.dyconvs.append(conv_func(in_channels, out_channels, 1)) + self.dyconvs.append(conv_func(in_channels, out_channels, 1)) + self.dyconvs.append(conv_func(in_channels, out_channels, 2)) + + if use_dyfuse: + self.attnconv = nn.Sequential( + nn.AdaptiveAvgPool2d(1), + nn.Conv2d(in_channels, 1, kernel_size=1), + nn.ReLU(inplace=True)) + self.h_sigmoid = nn.Hardsigmoid(inplace=True) + else: + self.attnconv = None + + if use_dyrelu: + self.relu = DyReLU(in_channels, out_channels) + else: + self.relu = nn.ReLU() + + if use_dcn: + self.offset = nn.Conv2d( + in_channels, 27, kernel_size=3, stride=1, padding=1) + else: + self.offset = None + + self.init_weights() + + def init_weights(self): + for m in self.dyconvs.modules(): + if isinstance(m, nn.Conv2d): + nn.init.normal_(m.weight.data, 0, 0.01) + if m.bias is not None: + m.bias.data.zero_() + if self.attnconv is not None: + for m in self.attnconv.modules(): + if isinstance(m, nn.Conv2d): + nn.init.normal_(m.weight.data, 0, 0.01) + if m.bias is not None: + m.bias.data.zero_() + + def forward(self, inputs: dict) -> dict: + visual_feats = inputs['visual'] + + out_vis_feats = [] + for level, feature in enumerate(visual_feats): + + offset_conv_args = {} + if self.offset is not None: + offset_mask = self.offset(feature) + offset = offset_mask[:, :18, :, :] + mask = offset_mask[:, 18:, :, :].sigmoid() + offset_conv_args = dict(offset=offset, mask=mask) + + temp_feats = [self.dyconvs[1](feature, **offset_conv_args)] + + if level > 0: + temp_feats.append(self.dyconvs[2](visual_feats[level - 1], + **offset_conv_args)) + if level < len(visual_feats) - 1: + temp_feats.append( + F.upsample_bilinear( + self.dyconvs[0](visual_feats[level + 1], + **offset_conv_args), + size=[feature.size(2), + feature.size(3)])) + mean_feats = torch.mean( + torch.stack(temp_feats), dim=0, keepdim=False) + + if self.attnconv is not None: + attn_feat = [] + res_feat = [] + for feat in temp_feats: + res_feat.append(feat) + attn_feat.append(self.attnconv(feat)) + + res_feat = torch.stack(res_feat) + spa_pyr_attn = self.h_sigmoid(torch.stack(attn_feat)) + + mean_feats = torch.mean( + res_feat * spa_pyr_attn, dim=0, keepdim=False) + + out_vis_feats.append(mean_feats) + + out_vis_feats = [self.relu(item) for item in out_vis_feats] + + features_dict = {'visual': out_vis_feats, 'lang': inputs['lang']} + + return features_dict + + +class VLFusionModule(BaseModel): + """Visual-lang Fusion Module.""" + + def __init__(self, + in_channels: int, + feat_channels: int, + num_base_priors: int, + early_fuse: bool = False, + num_dyhead_blocks: int = 6, + lang_model_name: str = 'bert-base-uncased', + use_dyrelu: bool = True, + use_dyfuse: bool = True, + use_dcn: bool = True, + use_checkpoint: bool = False, + **kwargs) -> None: + super().__init__(**kwargs) + if BertConfig is None: + raise RuntimeError( + 'transformers is not installed, please install it by: ' + 'pip install transformers.') + self.in_channels = in_channels + self.feat_channels = feat_channels + self.num_base_priors = num_base_priors + self.early_fuse = early_fuse + self.num_dyhead_blocks = num_dyhead_blocks + self.use_dyrelu = use_dyrelu + self.use_dyfuse = use_dyfuse + self.use_dcn = use_dcn + self.use_checkpoint = use_checkpoint + + self.lang_cfg = BertConfig.from_pretrained(lang_model_name) + self.lang_dim = self.lang_cfg.hidden_size + self._init_layers() + + def _init_layers(self) -> None: + """Initialize layers of the model.""" + bias_value = -math.log((1 - 0.01) / 0.01) + + dyhead_tower = [] + for i in range(self.num_dyhead_blocks): + if self.early_fuse: + # cross-modality fusion + dyhead_tower.append(VLFuse(use_checkpoint=self.use_checkpoint)) + # lang branch + dyhead_tower.append( + BertEncoderLayer( + self.lang_cfg, + clamp_min_for_underflow=True, + clamp_max_for_overflow=True)) + + # vision branch + dyhead_tower.append( + DyConv( + lambda i, o, s: Conv3x3Norm( + i, o, s, use_dcn=self.use_dcn, norm_type=['gn', 16]), + self.in_channels if i == 0 else self.feat_channels, + self.feat_channels, + use_dyrelu=(self.use_dyrelu + and self.in_channels == self.feat_channels) + if i == 0 else self.use_dyrelu, + use_dyfuse=(self.use_dyfuse + and self.in_channels == self.feat_channels) + if i == 0 else self.use_dyfuse, + use_dcn=(self.use_dcn + and self.in_channels == self.feat_channels) + if i == 0 else self.use_dcn, + )) + + self.add_module('dyhead_tower', nn.Sequential(*dyhead_tower)) + + self.bbox_pred = nn.Conv2d( + self.feat_channels, self.num_base_priors * 4, kernel_size=1) + self.centerness = nn.Conv2d( + self.feat_channels, self.num_base_priors * 1, kernel_size=1) + self.dot_product_projection_text = nn.Linear( + self.lang_dim, + self.num_base_priors * self.feat_channels, + bias=True) + self.log_scale = nn.Parameter(torch.Tensor([0.0]), requires_grad=True) + self.bias_lang = nn.Parameter( + torch.zeros(self.lang_dim), requires_grad=True) + self.bias0 = nn.Parameter( + torch.Tensor([bias_value]), requires_grad=True) + self.scales = nn.ModuleList([Scale(1.0) for _ in range(5)]) + + def forward(self, visual_feats: Tuple[Tensor], + language_feats: dict) -> Tuple: + feat_inputs = {'visual': visual_feats, 'lang': language_feats} + dyhead_tower = self.dyhead_tower(feat_inputs) + + if self.early_fuse: + embedding = dyhead_tower['lang']['hidden'] + else: + embedding = language_feats['embedded'] + + embedding = F.normalize(embedding, p=2, dim=-1) + dot_product_proj_tokens = self.dot_product_projection_text(embedding / + 2.0) + dot_product_proj_tokens_bias = torch.matmul( + embedding, self.bias_lang) + self.bias0 + + bbox_preds = [] + centerness = [] + cls_logits = [] + + for i, feature in enumerate(visual_feats): + visual = dyhead_tower['visual'][i] + B, C, H, W = visual.shape + + bbox_pred = self.scales[i](self.bbox_pred(visual)) + bbox_preds.append(bbox_pred) + centerness.append(self.centerness(visual)) + + dot_product_proj_queries = permute_and_flatten( + visual, B, self.num_base_priors, C, H, W) + + bias = dot_product_proj_tokens_bias.unsqueeze(1).repeat( + 1, self.num_base_priors, 1) + dot_product_logit = ( + torch.matmul(dot_product_proj_queries, + dot_product_proj_tokens.transpose(-1, -2)) / + self.log_scale.exp()) + bias + dot_product_logit = torch.clamp( + dot_product_logit, max=MAX_CLAMP_VALUE) + dot_product_logit = torch.clamp( + dot_product_logit, min=-MAX_CLAMP_VALUE) + cls_logits.append(dot_product_logit) + + return bbox_preds, centerness, cls_logits + + +@MODELS.register_module() +class ATSSVLFusionHead(ATSSHead): + """ATSS head with visual-language fusion module. + + Args: + early_fuse (bool): Whether to fuse visual and language features + Defaults to False. + use_checkpoint (bool): Whether to use checkpoint. Defaults to False. + num_dyhead_blocks (int): Number of dynamic head blocks. Defaults to 6. + lang_model_name (str): Name of the language model. + Defaults to 'bert-base-uncased'. + """ + + def __init__(self, + *args, + early_fuse: bool = False, + use_checkpoint: bool = False, + num_dyhead_blocks: int = 6, + lang_model_name: str = 'bert-base-uncased', + init_cfg=None, + **kwargs): + super().__init__(*args, **kwargs, init_cfg=init_cfg) + self.head = VLFusionModule( + in_channels=self.in_channels, + feat_channels=self.feat_channels, + num_base_priors=self.num_base_priors, + early_fuse=early_fuse, + use_checkpoint=use_checkpoint, + num_dyhead_blocks=num_dyhead_blocks, + lang_model_name=lang_model_name) + self.text_masks = None + + def _init_layers(self) -> None: + """No need to initialize the ATSS head layer.""" + pass + + def forward(self, visual_feats: Tuple[Tensor], + language_feats: dict) -> Tuple[Tensor]: + """Forward function.""" + bbox_preds, centerness, cls_logits = self.head(visual_feats, + language_feats) + return cls_logits, bbox_preds, centerness + + def loss(self, visual_feats: Tuple[Tensor], language_feats: dict, + batch_data_samples): + outputs = unpack_gt_instances(batch_data_samples) + (batch_gt_instances, batch_gt_instances_ignore, + batch_img_metas) = outputs + + outs = self(visual_feats, language_feats) + self.text_masks = language_feats['masks'] + loss_inputs = outs + (batch_gt_instances, batch_img_metas, + batch_gt_instances_ignore) + losses = self.loss_by_feat(*loss_inputs) + return losses + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + centernesses: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + centernesses (list[Tensor]): Centerness for each scale + level with shape (N, num_anchors * 1, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in bbox_preds] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + + (anchor_list, labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor) = cls_reg_targets + avg_factor = reduce_mean( + torch.tensor(avg_factor, dtype=torch.float, device=device)).item() + + anchors = torch.cat(anchor_list, dim=1) + labels = torch.cat(labels_list, dim=1) + label_weights = torch.cat(label_weights_list, dim=1) + bbox_targets = torch.cat(bbox_targets_list, dim=1) + cls_scores = torch.cat(cls_scores, dim=1) + + centernesses_ = [] + bbox_preds_ = [] + for bbox_pred, centerness in zip(bbox_preds, centernesses): + centernesses_.append( + centerness.permute(0, 2, 3, + 1).reshape(cls_scores.size(0), -1, 1)) + bbox_preds_.append( + bbox_pred.permute(0, 2, 3, + 1).reshape(cls_scores.size(0), -1, 4)) + bbox_preds = torch.cat(bbox_preds_, dim=1) + centernesses = torch.cat(centernesses_, dim=1) + + losses_cls, losses_bbox, loss_centerness, bbox_avg_factor = \ + self._loss_by_feat( + anchors, + cls_scores, + bbox_preds, + centernesses, + labels, + label_weights, + bbox_targets, + avg_factor=avg_factor) + + bbox_avg_factor = reduce_mean(bbox_avg_factor).clamp_(min=1).item() + losses_bbox = losses_bbox / bbox_avg_factor + return dict( + loss_cls=losses_cls, + loss_bbox=losses_bbox, + loss_centerness=loss_centerness) + + def _loss_by_feat(self, anchors: Tensor, cls_score: Tensor, + bbox_pred: Tensor, centerness: Tensor, labels: Tensor, + label_weights: Tensor, bbox_targets: Tensor, + avg_factor: float) -> dict: + """Calculate the loss of all scale level based on the features + extracted by the detection head. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + + anchors = anchors.reshape(-1, 4) + + # ===== this change ===== + pos_inds = (labels.sum(-1) > 0).reshape(-1) + + # Loss is not computed for the padded regions of the text. + assert (self.text_masks.dim() == 2) + text_mask = (self.text_masks > 0).unsqueeze(1) + text_mask = text_mask.repeat(1, cls_score.size(1), 1) + cls_score = torch.masked_select(cls_score, text_mask).contiguous() + labels = torch.masked_select(labels, text_mask) + label_weights = label_weights[..., + None].repeat(1, 1, text_mask.size(-1)) + label_weights = torch.masked_select(label_weights, text_mask) + + bbox_pred = bbox_pred.reshape(-1, 4) + centerness = centerness.reshape(-1) + bbox_targets = bbox_targets.reshape(-1, 4) + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + + # classification loss + loss_cls = self.loss_cls( + cls_score, labels, label_weights, avg_factor=avg_factor) + + if pos_inds.sum() > 0: + pos_bbox_targets = bbox_targets[pos_inds] + pos_bbox_pred = bbox_pred[pos_inds] + pos_anchors = anchors[pos_inds] + pos_centerness = centerness[pos_inds] + + centerness_targets = self.centerness_target( + pos_anchors, pos_bbox_targets) + + if torch.isnan(centerness_targets).any(): + print('=====Centerness includes NaN=====') + mask = ~torch.isnan(centerness_targets) + centerness_targets = centerness_targets[mask] + pos_centerness = pos_centerness[mask] + pos_anchors = pos_anchors[mask] + pos_bbox_targets = pos_bbox_targets[mask] + pos_bbox_pred = pos_bbox_pred[mask] + + if pos_bbox_targets.shape[0] == 0: + loss_bbox = bbox_pred.sum() * 0 + loss_centerness = centerness.sum() * 0 + centerness_targets = bbox_targets.new_tensor(0.) + return loss_cls, loss_bbox, loss_centerness, \ + centerness_targets.sum() + + # The decoding process takes the offset into consideration. + pos_anchors[:, 2:] += 1 + pos_decode_bbox_pred = self.bbox_coder.decode( + pos_anchors, pos_bbox_pred) + + # regression loss + loss_bbox = self.loss_bbox( + pos_decode_bbox_pred, + pos_bbox_targets, + weight=centerness_targets, + avg_factor=1.0) + + # centerness loss + loss_centerness = self.loss_centerness( + pos_centerness, centerness_targets, avg_factor=avg_factor) + else: + loss_bbox = bbox_pred.sum() * 0 + loss_centerness = centerness.sum() * 0 + centerness_targets = bbox_targets.new_tensor(0.) + + return loss_cls, loss_bbox, loss_centerness, centerness_targets.sum() + + def _get_targets_single(self, + flat_anchors: Tensor, + valid_flags: Tensor, + num_level_anchors: List[int], + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression, classification targets for anchors in a single + image. + + Args: + flat_anchors (Tensor): Multi-level anchors of the image, which are + concatenated into a single tensor of shape (num_anchors ,4) + valid_flags (Tensor): Multi level valid flags of the image, + which are concatenated into a single tensor of + shape (num_anchors,). + num_level_anchors (List[int]): Number of anchors of each scale + level. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. + + Returns: + tuple: N is the number of total anchors in the image. + labels (Tensor): Labels of all anchors in the image with shape + (N,). + label_weights (Tensor): Label weights of all anchor in the + image with shape (N,). + bbox_targets (Tensor): BBox targets of all anchors in the + image with shape (N, 4). + bbox_weights (Tensor): BBox weights of all anchors in the + image with shape (N, 4) + pos_inds (Tensor): Indices of positive anchor with shape + (num_pos,). + neg_inds (Tensor): Indices of negative anchor with shape + (num_neg,). + sampling_result (:obj:`SamplingResult`): Sampling results. + """ + anchors = flat_anchors + # Align the official implementation + anchors[:, 2:] -= 1 + + num_level_anchors_inside = num_level_anchors + pred_instances = InstanceData(priors=anchors) + assign_result = self.assigner.assign(pred_instances, + num_level_anchors_inside, + gt_instances, gt_instances_ignore) + + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_valid_anchors = anchors.shape[0] + bbox_targets = torch.zeros_like(anchors) + bbox_weights = torch.zeros_like(anchors) + + # ===== this change ===== + labels = anchors.new_full((num_valid_anchors, self.feat_channels), + 0, + dtype=torch.float32) + label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float) + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + if self.reg_decoded_bbox: + pos_bbox_targets = sampling_result.pos_gt_bboxes + else: + pos_bbox_targets = self.bbox_coder.encode( + sampling_result.pos_priors, sampling_result.pos_gt_bboxes) + + bbox_targets[pos_inds, :] = pos_bbox_targets + bbox_weights[pos_inds, :] = 1.0 + + # ===== this change ===== + labels[pos_inds] = gt_instances.positive_maps[ + sampling_result.pos_assigned_gt_inds] + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + return (anchors, labels, label_weights, bbox_targets, bbox_weights, + pos_inds, neg_inds, sampling_result) + + def centerness_target(self, anchors: Tensor, gts: Tensor) -> Tensor: + """Calculate the centerness between anchors and gts. + + Only calculate pos centerness targets, otherwise there may be nan. + + Args: + anchors (Tensor): Anchors with shape (N, 4), "xyxy" format. + gts (Tensor): Ground truth bboxes with shape (N, 4), "xyxy" format. + + Returns: + Tensor: Centerness between anchors and gts. + """ + anchors_cx = (anchors[:, 2] + anchors[:, 0]) / 2 + anchors_cy = (anchors[:, 3] + anchors[:, 1]) / 2 + l_ = anchors_cx - gts[:, 0] + t_ = anchors_cy - gts[:, 1] + r_ = gts[:, 2] - anchors_cx + b_ = gts[:, 3] - anchors_cy + + left_right = torch.stack([l_, r_], dim=1) + top_bottom = torch.stack([t_, b_], dim=1) + centerness = torch.sqrt( + (left_right.min(dim=-1)[0] / left_right.max(dim=-1)[0]) * + (top_bottom.min(dim=-1)[0] / top_bottom.max(dim=-1)[0])) + # assert not torch.isnan(centerness).any() + return centerness + + def predict(self, + visual_feats: Tuple[Tensor], + language_feats: dict, + batch_data_samples, + rescale: bool = True): + """Perform forward propagation of the detection head and predict + detection results on the features of the upstream network. + + Args: + visual_feats (tuple[Tensor]): Multi-level visual features from the + upstream network, each is a 4D-tensor. + language_feats (dict): Language features from the upstream network. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + batch_token_positive_maps = [ + data_samples.token_positive_map + for data_samples in batch_data_samples + ] + outs = self(visual_feats, language_feats) + + predictions = self.predict_by_feat( + *outs, + batch_img_metas=batch_img_metas, + batch_token_positive_maps=batch_token_positive_maps, + rescale=rescale) + return predictions + + def predict_by_feat(self, + cls_logits: List[Tensor], + bbox_preds: List[Tensor], + score_factors: List[Tensor], + batch_img_metas: Optional[List[dict]] = None, + batch_token_positive_maps: Optional[List[dict]] = None, + cfg: Optional[ConfigDict] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Note: When score_factors is not None, the cls_scores are + usually multiplied by it then obtain the real score used in NMS, + such as CenterNess in FCOS, IoU branch in ATSS. + + Args: + cls_logits (list[Tensor]): Classification scores for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * 4, H, W). + score_factors (list[Tensor], optional): Score factor for + all scale level, each is a 4D-tensor, has shape + (batch_size, num_priors * 1, H, W). Defaults to None. + batch_img_metas (list[dict], Optional): Batch image meta info. + Defaults to None. + batch_token_positive_maps (list[dict], Optional): Batch token + positive map. Defaults to None. + cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(bbox_preds) == len(score_factors) + num_levels = len(bbox_preds) + + featmap_sizes = [bbox_preds[i].shape[-2:] for i in range(num_levels)] + mlvl_priors = self.prior_generator.grid_priors( + featmap_sizes, + dtype=bbox_preds[0].dtype, + device=bbox_preds[0].device) + + result_list = [] + + for img_id in range(len(batch_img_metas)): + img_meta = batch_img_metas[img_id] + token_positive_maps = batch_token_positive_maps[img_id] + bbox_pred_list = select_single_mlvl( + bbox_preds, img_id, detach=True) + score_factor_list = select_single_mlvl( + score_factors, img_id, detach=True) + cls_logit_list = select_single_mlvl( + cls_logits, img_id, detach=True) + + results = self._predict_by_feat_single( + bbox_pred_list=bbox_pred_list, + score_factor_list=score_factor_list, + cls_logit_list=cls_logit_list, + mlvl_priors=mlvl_priors, + token_positive_maps=token_positive_maps, + img_meta=img_meta, + cfg=cfg, + rescale=rescale, + with_nms=with_nms) + result_list.append(results) + return result_list + + def _predict_by_feat_single(self, + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + cls_logit_list: List[Tensor], + mlvl_priors: List[Tensor], + token_positive_maps: dict, + img_meta: dict, + cfg: ConfigDict, + rescale: bool = True, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image, each item has shape + (num_priors * 1, H, W). + cls_logit_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid. In all + anchor-based methods, it has shape (num_priors, 4). In + all anchor-free methods, it has shape (num_priors, 2) + when `with_stride=True`, otherwise it still has shape + (num_priors, 4). + token_positive_maps (dict): Token positive map. + img_meta (dict): Image meta info. + cfg (mmengine.Config): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + score_thr = cfg.get('score_thr', 0) + + mlvl_bbox_preds = [] + mlvl_valid_priors = [] + mlvl_scores = [] + mlvl_labels = [] + + for level_idx, (bbox_pred, score_factor, cls_logit, priors) in \ + enumerate(zip(bbox_pred_list, + score_factor_list, cls_logit_list, mlvl_priors)): + bbox_pred = bbox_pred.permute(1, 2, 0).reshape( + -1, self.bbox_coder.encode_size) + score_factor = score_factor.permute(1, 2, 0).reshape(-1).sigmoid() + + scores = convert_grounding_to_cls_scores( + logits=cls_logit.sigmoid()[None], + positive_maps=[token_positive_maps])[0] + + results = filter_scores_and_topk( + scores, score_thr, nms_pre, + dict(bbox_pred=bbox_pred, priors=priors)) + + scores, labels, keep_idxs, filtered_results = results + + bbox_pred = filtered_results['bbox_pred'] + priors = filtered_results['priors'] + score_factor = score_factor[keep_idxs] + scores = torch.sqrt(scores * score_factor) + + mlvl_bbox_preds.append(bbox_pred) + mlvl_valid_priors.append(priors) + mlvl_scores.append(scores) + mlvl_labels.append(labels) + + bbox_pred = torch.cat(mlvl_bbox_preds) + priors = cat_boxes(mlvl_valid_priors) + bboxes = self.bbox_coder.decode(priors, bbox_pred, max_shape=img_shape) + + results = InstanceData() + results.bboxes = bboxes + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + + predictions = self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + + if len(predictions) > 0: + # Note: GLIP adopts a very strange bbox decoder logic, + # and if 1 is not added here, it will not align with + # the official mAP. + predictions.bboxes[:, 2:] = predictions.bboxes[:, 2:] + 1 + return predictions diff --git a/mmdetection/mmdet/models/dense_heads/autoassign_head.py b/mmdetection/mmdet/models/dense_heads/autoassign_head.py new file mode 100644 index 00000000..a2b30ff0 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/autoassign_head.py @@ -0,0 +1,524 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Sequence, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Scale +from mmengine.model import bias_init_with_prob, normal_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures.bbox import bbox_overlaps +from mmdet.utils import InstanceList, OptInstanceList, reduce_mean +from ..task_modules.prior_generators import MlvlPointGenerator +from ..utils import levels_to_images, multi_apply +from .fcos_head import FCOSHead + +EPS = 1e-12 + + +class CenterPrior(nn.Module): + """Center Weighting module to adjust the category-specific prior + distributions. + + Args: + force_topk (bool): When no point falls into gt_bbox, forcibly + select the k points closest to the center to calculate + the center prior. Defaults to False. + topk (int): The number of points used to calculate the + center prior when no point falls in gt_bbox. Only work when + force_topk if True. Defaults to 9. + num_classes (int): The class number of dataset. Defaults to 80. + strides (Sequence[int]): The stride of each input feature map. + Defaults to (8, 16, 32, 64, 128). + """ + + def __init__( + self, + force_topk: bool = False, + topk: int = 9, + num_classes: int = 80, + strides: Sequence[int] = (8, 16, 32, 64, 128) + ) -> None: + super().__init__() + self.mean = nn.Parameter(torch.zeros(num_classes, 2)) + self.sigma = nn.Parameter(torch.ones(num_classes, 2)) + self.strides = strides + self.force_topk = force_topk + self.topk = topk + + def forward(self, anchor_points_list: List[Tensor], + gt_instances: InstanceData, + inside_gt_bbox_mask: Tensor) -> Tuple[Tensor, Tensor]: + """Get the center prior of each point on the feature map for each + instance. + + Args: + anchor_points_list (list[Tensor]): list of coordinate + of points on feature map. Each with shape + (num_points, 2). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + inside_gt_bbox_mask (Tensor): Tensor of bool type, + with shape of (num_points, num_gt), each + value is used to mark whether this point falls + within a certain gt. + + Returns: + tuple[Tensor, Tensor]: + + - center_prior_weights(Tensor): Float tensor with shape of \ + (num_points, num_gt). Each value represents the center \ + weighting coefficient. + - inside_gt_bbox_mask (Tensor): Tensor of bool type, with shape \ + of (num_points, num_gt), each value is used to mark whether this \ + point falls within a certain gt or is the topk nearest points for \ + a specific gt_bbox. + """ + gt_bboxes = gt_instances.bboxes + labels = gt_instances.labels + + inside_gt_bbox_mask = inside_gt_bbox_mask.clone() + num_gts = len(labels) + num_points = sum([len(item) for item in anchor_points_list]) + if num_gts == 0: + return gt_bboxes.new_zeros(num_points, + num_gts), inside_gt_bbox_mask + center_prior_list = [] + for slvl_points, stride in zip(anchor_points_list, self.strides): + # slvl_points: points from single level in FPN, has shape (h*w, 2) + # single_level_points has shape (h*w, num_gt, 2) + single_level_points = slvl_points[:, None, :].expand( + (slvl_points.size(0), len(gt_bboxes), 2)) + gt_center_x = ((gt_bboxes[:, 0] + gt_bboxes[:, 2]) / 2) + gt_center_y = ((gt_bboxes[:, 1] + gt_bboxes[:, 3]) / 2) + gt_center = torch.stack((gt_center_x, gt_center_y), dim=1) + gt_center = gt_center[None] + # instance_center has shape (1, num_gt, 2) + instance_center = self.mean[labels][None] + # instance_sigma has shape (1, num_gt, 2) + instance_sigma = self.sigma[labels][None] + # distance has shape (num_points, num_gt, 2) + distance = (((single_level_points - gt_center) / float(stride) - + instance_center)**2) + center_prior = torch.exp(-distance / + (2 * instance_sigma**2)).prod(dim=-1) + center_prior_list.append(center_prior) + center_prior_weights = torch.cat(center_prior_list, dim=0) + + if self.force_topk: + gt_inds_no_points_inside = torch.nonzero( + inside_gt_bbox_mask.sum(0) == 0).reshape(-1) + if gt_inds_no_points_inside.numel(): + topk_center_index = \ + center_prior_weights[:, gt_inds_no_points_inside].topk( + self.topk, + dim=0)[1] + temp_mask = inside_gt_bbox_mask[:, gt_inds_no_points_inside] + inside_gt_bbox_mask[:, gt_inds_no_points_inside] = \ + torch.scatter(temp_mask, + dim=0, + index=topk_center_index, + src=torch.ones_like( + topk_center_index, + dtype=torch.bool)) + + center_prior_weights[~inside_gt_bbox_mask] = 0 + return center_prior_weights, inside_gt_bbox_mask + + +@MODELS.register_module() +class AutoAssignHead(FCOSHead): + """AutoAssignHead head used in AutoAssign. + + More details can be found in the `paper + `_ . + + Args: + force_topk (bool): Used in center prior initialization to + handle extremely small gt. Default is False. + topk (int): The number of points used to calculate the + center prior when no point falls in gt_bbox. Only work when + force_topk if True. Defaults to 9. + pos_loss_weight (float): The loss weight of positive loss + and with default value 0.25. + neg_loss_weight (float): The loss weight of negative loss + and with default value 0.75. + center_loss_weight (float): The loss weight of center prior + loss and with default value 0.75. + """ + + def __init__(self, + *args, + force_topk: bool = False, + topk: int = 9, + pos_loss_weight: float = 0.25, + neg_loss_weight: float = 0.75, + center_loss_weight: float = 0.75, + **kwargs) -> None: + super().__init__(*args, conv_bias=True, **kwargs) + self.center_prior = CenterPrior( + force_topk=force_topk, + topk=topk, + num_classes=self.num_classes, + strides=self.strides) + self.pos_loss_weight = pos_loss_weight + self.neg_loss_weight = neg_loss_weight + self.center_loss_weight = center_loss_weight + self.prior_generator = MlvlPointGenerator(self.strides, offset=0) + + def init_weights(self) -> None: + """Initialize weights of the head. + + In particular, we have special initialization for classified conv's and + regression conv's bias + """ + + super(AutoAssignHead, self).init_weights() + bias_cls = bias_init_with_prob(0.02) + normal_init(self.conv_cls, std=0.01, bias=bias_cls) + normal_init(self.conv_reg, std=0.01, bias=4.0) + + def forward_single(self, x: Tensor, scale: Scale, + stride: int) -> Tuple[Tensor, Tensor, Tensor]: + """Forward features of a single scale level. + + Args: + x (Tensor): FPN feature maps of the specified stride. + scale (:obj:`mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + stride (int): The corresponding stride for feature maps, only + used to normalize the bbox prediction when self.norm_on_bbox + is True. + + Returns: + tuple[Tensor, Tensor, Tensor]: scores for each class, bbox + predictions and centerness predictions of input feature maps. + """ + cls_score, bbox_pred, cls_feat, reg_feat = super( + FCOSHead, self).forward_single(x) + centerness = self.conv_centerness(reg_feat) + # scale the bbox_pred of different level + # float to avoid overflow when enabling FP16 + bbox_pred = scale(bbox_pred).float() + # bbox_pred needed for gradient computation has been modified + # by F.relu(bbox_pred) when run with PyTorch 1.10. So replace + # F.relu(bbox_pred) with bbox_pred.clamp(min=0) + bbox_pred = bbox_pred.clamp(min=0) + bbox_pred *= stride + return cls_score, bbox_pred, centerness + + def get_pos_loss_single(self, cls_score: Tensor, objectness: Tensor, + reg_loss: Tensor, gt_instances: InstanceData, + center_prior_weights: Tensor) -> Tuple[Tensor]: + """Calculate the positive loss of all points in gt_bboxes. + + Args: + cls_score (Tensor): All category scores for each point on + the feature map. The shape is (num_points, num_class). + objectness (Tensor): Foreground probability of all points, + has shape (num_points, 1). + reg_loss (Tensor): The regression loss of each gt_bbox and each + prediction box, has shape of (num_points, num_gt). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + center_prior_weights (Tensor): Float tensor with shape + of (num_points, num_gt). Each value represents + the center weighting coefficient. + + Returns: + tuple[Tensor]: + + - pos_loss (Tensor): The positive loss of all points in the \ + gt_bboxes. + """ + gt_labels = gt_instances.labels + # p_loc: localization confidence + p_loc = torch.exp(-reg_loss) + # p_cls: classification confidence + p_cls = (cls_score * objectness)[:, gt_labels] + # p_pos: joint confidence indicator + p_pos = p_cls * p_loc + + # 3 is a hyper-parameter to control the contributions of high and + # low confidence locations towards positive losses. + confidence_weight = torch.exp(p_pos * 3) + p_pos_weight = (confidence_weight * center_prior_weights) / ( + (confidence_weight * center_prior_weights).sum( + 0, keepdim=True)).clamp(min=EPS) + reweighted_p_pos = (p_pos * p_pos_weight).sum(0) + pos_loss = F.binary_cross_entropy( + reweighted_p_pos, + torch.ones_like(reweighted_p_pos), + reduction='none') + pos_loss = pos_loss.sum() * self.pos_loss_weight + return pos_loss, + + def get_neg_loss_single(self, cls_score: Tensor, objectness: Tensor, + gt_instances: InstanceData, ious: Tensor, + inside_gt_bbox_mask: Tensor) -> Tuple[Tensor]: + """Calculate the negative loss of all points in feature map. + + Args: + cls_score (Tensor): All category scores for each point on + the feature map. The shape is (num_points, num_class). + objectness (Tensor): Foreground probability of all points + and is shape of (num_points, 1). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + ious (Tensor): Float tensor with shape of (num_points, num_gt). + Each value represent the iou of pred_bbox and gt_bboxes. + inside_gt_bbox_mask (Tensor): Tensor of bool type, + with shape of (num_points, num_gt), each + value is used to mark whether this point falls + within a certain gt. + + Returns: + tuple[Tensor]: + + - neg_loss (Tensor): The negative loss of all points in the \ + feature map. + """ + gt_labels = gt_instances.labels + num_gts = len(gt_labels) + joint_conf = (cls_score * objectness) + p_neg_weight = torch.ones_like(joint_conf) + if num_gts > 0: + # the order of dinmension would affect the value of + # p_neg_weight, we strictly follow the original + # implementation. + inside_gt_bbox_mask = inside_gt_bbox_mask.permute(1, 0) + ious = ious.permute(1, 0) + + foreground_idxs = torch.nonzero(inside_gt_bbox_mask, as_tuple=True) + temp_weight = (1 / (1 - ious[foreground_idxs]).clamp_(EPS)) + + def normalize(x): + return (x - x.min() + EPS) / (x.max() - x.min() + EPS) + + for instance_idx in range(num_gts): + idxs = foreground_idxs[0] == instance_idx + if idxs.any(): + temp_weight[idxs] = normalize(temp_weight[idxs]) + + p_neg_weight[foreground_idxs[1], + gt_labels[foreground_idxs[0]]] = 1 - temp_weight + + logits = (joint_conf * p_neg_weight) + neg_loss = ( + logits**2 * F.binary_cross_entropy( + logits, torch.zeros_like(logits), reduction='none')) + neg_loss = neg_loss.sum() * self.neg_loss_weight + return neg_loss, + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + objectnesses: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level, + each is a 4D-tensor, the channel number is + num_points * num_classes. + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level, each is a 4D-tensor, the channel number is + num_points * 4. + objectnesses (list[Tensor]): objectness for each scale level, each + is a 4D-tensor, the channel number is num_points * 1. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + + assert len(cls_scores) == len(bbox_preds) == len(objectnesses) + all_num_gt = sum([len(item) for item in batch_gt_instances]) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + all_level_points = self.prior_generator.grid_priors( + featmap_sizes, + dtype=bbox_preds[0].dtype, + device=bbox_preds[0].device) + inside_gt_bbox_mask_list, bbox_targets_list = self.get_targets( + all_level_points, batch_gt_instances) + + center_prior_weight_list = [] + temp_inside_gt_bbox_mask_list = [] + for gt_instances, inside_gt_bbox_mask in zip(batch_gt_instances, + inside_gt_bbox_mask_list): + center_prior_weight, inside_gt_bbox_mask = \ + self.center_prior(all_level_points, gt_instances, + inside_gt_bbox_mask) + center_prior_weight_list.append(center_prior_weight) + temp_inside_gt_bbox_mask_list.append(inside_gt_bbox_mask) + inside_gt_bbox_mask_list = temp_inside_gt_bbox_mask_list + mlvl_points = torch.cat(all_level_points, dim=0) + bbox_preds = levels_to_images(bbox_preds) + cls_scores = levels_to_images(cls_scores) + objectnesses = levels_to_images(objectnesses) + + reg_loss_list = [] + ious_list = [] + num_points = len(mlvl_points) + + for bbox_pred, encoded_targets, inside_gt_bbox_mask in zip( + bbox_preds, bbox_targets_list, inside_gt_bbox_mask_list): + temp_num_gt = encoded_targets.size(1) + expand_mlvl_points = mlvl_points[:, None, :].expand( + num_points, temp_num_gt, 2).reshape(-1, 2) + encoded_targets = encoded_targets.reshape(-1, 4) + expand_bbox_pred = bbox_pred[:, None, :].expand( + num_points, temp_num_gt, 4).reshape(-1, 4) + decoded_bbox_preds = self.bbox_coder.decode( + expand_mlvl_points, expand_bbox_pred) + decoded_target_preds = self.bbox_coder.decode( + expand_mlvl_points, encoded_targets) + with torch.no_grad(): + ious = bbox_overlaps( + decoded_bbox_preds, decoded_target_preds, is_aligned=True) + ious = ious.reshape(num_points, temp_num_gt) + if temp_num_gt: + ious = ious.max( + dim=-1, keepdim=True).values.repeat(1, temp_num_gt) + else: + ious = ious.new_zeros(num_points, temp_num_gt) + ious[~inside_gt_bbox_mask] = 0 + ious_list.append(ious) + loss_bbox = self.loss_bbox( + decoded_bbox_preds, + decoded_target_preds, + weight=None, + reduction_override='none') + reg_loss_list.append(loss_bbox.reshape(num_points, temp_num_gt)) + + cls_scores = [item.sigmoid() for item in cls_scores] + objectnesses = [item.sigmoid() for item in objectnesses] + pos_loss_list, = multi_apply(self.get_pos_loss_single, cls_scores, + objectnesses, reg_loss_list, + batch_gt_instances, + center_prior_weight_list) + pos_avg_factor = reduce_mean( + bbox_pred.new_tensor(all_num_gt)).clamp_(min=1) + pos_loss = sum(pos_loss_list) / pos_avg_factor + + neg_loss_list, = multi_apply(self.get_neg_loss_single, cls_scores, + objectnesses, batch_gt_instances, + ious_list, inside_gt_bbox_mask_list) + neg_avg_factor = sum(item.data.sum() + for item in center_prior_weight_list) + neg_avg_factor = reduce_mean(neg_avg_factor).clamp_(min=1) + neg_loss = sum(neg_loss_list) / neg_avg_factor + + center_loss = [] + for i in range(len(batch_img_metas)): + + if inside_gt_bbox_mask_list[i].any(): + center_loss.append( + len(batch_gt_instances[i]) / + center_prior_weight_list[i].sum().clamp_(min=EPS)) + # when width or height of gt_bbox is smaller than stride of p3 + else: + center_loss.append(center_prior_weight_list[i].sum() * 0) + + center_loss = torch.stack(center_loss).mean() * self.center_loss_weight + + # avoid dead lock in DDP + if all_num_gt == 0: + pos_loss = bbox_preds[0].sum() * 0 + dummy_center_prior_loss = self.center_prior.mean.sum( + ) * 0 + self.center_prior.sigma.sum() * 0 + center_loss = objectnesses[0].sum() * 0 + dummy_center_prior_loss + + loss = dict( + loss_pos=pos_loss, loss_neg=neg_loss, loss_center=center_loss) + + return loss + + def get_targets( + self, points: List[Tensor], batch_gt_instances: InstanceList + ) -> Tuple[List[Tensor], List[Tensor]]: + """Compute regression targets and each point inside or outside gt_bbox + in multiple images. + + Args: + points (list[Tensor]): Points of all fpn level, each has shape + (num_points, 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + + Returns: + tuple(list[Tensor], list[Tensor]): + + - inside_gt_bbox_mask_list (list[Tensor]): Each Tensor is with \ + bool type and shape of (num_points, num_gt), each value is used \ + to mark whether this point falls within a certain gt. + - concat_lvl_bbox_targets (list[Tensor]): BBox targets of each \ + level. Each tensor has shape (num_points, num_gt, 4). + """ + + concat_points = torch.cat(points, dim=0) + # the number of points per img, per lvl + inside_gt_bbox_mask_list, bbox_targets_list = multi_apply( + self._get_targets_single, batch_gt_instances, points=concat_points) + return inside_gt_bbox_mask_list, bbox_targets_list + + def _get_targets_single(self, gt_instances: InstanceData, + points: Tensor) -> Tuple[Tensor, Tensor]: + """Compute regression targets and each point inside or outside gt_bbox + for a single image. + + Args: + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + points (Tensor): Points of all fpn level, has shape + (num_points, 2). + + Returns: + tuple[Tensor, Tensor]: Containing the following Tensors: + + - inside_gt_bbox_mask (Tensor): Bool tensor with shape \ + (num_points, num_gt), each value is used to mark whether this \ + point falls within a certain gt. + - bbox_targets (Tensor): BBox targets of each points with each \ + gt_bboxes, has shape (num_points, num_gt, 4). + """ + gt_bboxes = gt_instances.bboxes + num_points = points.size(0) + num_gts = gt_bboxes.size(0) + gt_bboxes = gt_bboxes[None].expand(num_points, num_gts, 4) + xs, ys = points[:, 0], points[:, 1] + xs = xs[:, None] + ys = ys[:, None] + left = xs - gt_bboxes[..., 0] + right = gt_bboxes[..., 2] - xs + top = ys - gt_bboxes[..., 1] + bottom = gt_bboxes[..., 3] - ys + bbox_targets = torch.stack((left, top, right, bottom), -1) + if num_gts: + inside_gt_bbox_mask = bbox_targets.min(-1)[0] > 0 + else: + inside_gt_bbox_mask = bbox_targets.new_zeros((num_points, num_gts), + dtype=torch.bool) + + return inside_gt_bbox_mask, bbox_targets diff --git a/mmdetection/mmdet/models/dense_heads/base_dense_head.py b/mmdetection/mmdet/models/dense_heads/base_dense_head.py new file mode 100644 index 00000000..d0a4469e --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/base_dense_head.py @@ -0,0 +1,583 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from abc import ABCMeta, abstractmethod +from inspect import signature +from typing import List, Optional, Tuple + +import torch +from mmcv.ops import batched_nms +from mmengine.config import ConfigDict +from mmengine.model import BaseModule, constant_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.structures import SampleList +from mmdet.structures.bbox import (cat_boxes, get_box_tensor, get_box_wh, + scale_boxes) +from mmdet.utils import InstanceList, OptMultiConfig +from ..test_time_augs import merge_aug_results +from ..utils import (filter_scores_and_topk, select_single_mlvl, + unpack_gt_instances) + + +class BaseDenseHead(BaseModule, metaclass=ABCMeta): + """Base class for DenseHeads. + + 1. The ``init_weights`` method is used to initialize densehead's + model parameters. After detector initialization, ``init_weights`` + is triggered when ``detector.init_weights()`` is called externally. + + 2. The ``loss`` method is used to calculate the loss of densehead, + which includes two steps: (1) the densehead model performs forward + propagation to obtain the feature maps (2) The ``loss_by_feat`` method + is called based on the feature maps to calculate the loss. + + .. code:: text + + loss(): forward() -> loss_by_feat() + + 3. The ``predict`` method is used to predict detection results, + which includes two steps: (1) the densehead model performs forward + propagation to obtain the feature maps (2) The ``predict_by_feat`` method + is called based on the feature maps to predict detection results including + post-processing. + + .. code:: text + + predict(): forward() -> predict_by_feat() + + 4. The ``loss_and_predict`` method is used to return loss and detection + results at the same time. It will call densehead's ``forward``, + ``loss_by_feat`` and ``predict_by_feat`` methods in order. If one-stage is + used as RPN, the densehead needs to return both losses and predictions. + This predictions is used as the proposal of roihead. + + .. code:: text + + loss_and_predict(): forward() -> loss_by_feat() -> predict_by_feat() + """ + + def __init__(self, init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + # `_raw_positive_infos` will be used in `get_positive_infos`, which + # can get positive information. + self._raw_positive_infos = dict() + + def init_weights(self) -> None: + """Initialize the weights.""" + super().init_weights() + # avoid init_cfg overwrite the initialization of `conv_offset` + for m in self.modules(): + # DeformConv2dPack, ModulatedDeformConv2dPack + if hasattr(m, 'conv_offset'): + constant_init(m.conv_offset, 0) + + def get_positive_infos(self) -> InstanceList: + """Get positive information from sampling results. + + Returns: + list[:obj:`InstanceData`]: Positive information of each image, + usually including positive bboxes, positive labels, positive + priors, etc. + """ + if len(self._raw_positive_infos) == 0: + return None + + sampling_results = self._raw_positive_infos.get( + 'sampling_results', None) + assert sampling_results is not None + positive_infos = [] + for sampling_result in enumerate(sampling_results): + pos_info = InstanceData() + pos_info.bboxes = sampling_result.pos_gt_bboxes + pos_info.labels = sampling_result.pos_gt_labels + pos_info.priors = sampling_result.pos_priors + pos_info.pos_assigned_gt_inds = \ + sampling_result.pos_assigned_gt_inds + pos_info.pos_inds = sampling_result.pos_inds + positive_infos.append(pos_info) + return positive_infos + + def loss(self, x: Tuple[Tensor], batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the features of the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + outs = self(x) + + outputs = unpack_gt_instances(batch_data_samples) + (batch_gt_instances, batch_gt_instances_ignore, + batch_img_metas) = outputs + + loss_inputs = outs + (batch_gt_instances, batch_img_metas, + batch_gt_instances_ignore) + losses = self.loss_by_feat(*loss_inputs) + return losses + + @abstractmethod + def loss_by_feat(self, **kwargs) -> dict: + """Calculate the loss based on the features extracted by the detection + head.""" + pass + + def loss_and_predict( + self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + proposal_cfg: Optional[ConfigDict] = None + ) -> Tuple[dict, InstanceList]: + """Perform forward propagation of the head, then calculate loss and + predictions from the features and data samples. + + Args: + x (tuple[Tensor]): Features from FPN. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + proposal_cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + + Returns: + tuple: the return value is a tuple contains: + + - losses: (dict[str, Tensor]): A dictionary of loss components. + - predictions (list[:obj:`InstanceData`]): Detection + results of each image after the post process. + """ + outputs = unpack_gt_instances(batch_data_samples) + (batch_gt_instances, batch_gt_instances_ignore, + batch_img_metas) = outputs + + outs = self(x) + + loss_inputs = outs + (batch_gt_instances, batch_img_metas, + batch_gt_instances_ignore) + losses = self.loss_by_feat(*loss_inputs) + + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas, cfg=proposal_cfg) + return losses, predictions + + def predict(self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the detection head and predict + detection results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + outs = self(x) + + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas, rescale=rescale) + return predictions + + def predict_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + score_factors: Optional[List[Tensor]] = None, + batch_img_metas: Optional[List[dict]] = None, + cfg: Optional[ConfigDict] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Note: When score_factors is not None, the cls_scores are + usually multiplied by it then obtain the real score used in NMS, + such as CenterNess in FCOS, IoU branch in ATSS. + + Args: + cls_scores (list[Tensor]): Classification scores for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * 4, H, W). + score_factors (list[Tensor], optional): Score factor for + all scale level, each is a 4D-tensor, has shape + (batch_size, num_priors * 1, H, W). Defaults to None. + batch_img_metas (list[dict], Optional): Batch image meta info. + Defaults to None. + cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_scores) == len(bbox_preds) + + if score_factors is None: + # e.g. Retina, FreeAnchor, Foveabox, etc. + with_score_factors = False + else: + # e.g. FCOS, PAA, ATSS, AutoAssign, etc. + with_score_factors = True + assert len(cls_scores) == len(score_factors) + + num_levels = len(cls_scores) + + featmap_sizes = [cls_scores[i].shape[-2:] for i in range(num_levels)] + mlvl_priors = self.prior_generator.grid_priors( + featmap_sizes, + dtype=cls_scores[0].dtype, + device=cls_scores[0].device) + + result_list = [] + + for img_id in range(len(batch_img_metas)): + img_meta = batch_img_metas[img_id] + cls_score_list = select_single_mlvl( + cls_scores, img_id, detach=True) + bbox_pred_list = select_single_mlvl( + bbox_preds, img_id, detach=True) + if with_score_factors: + score_factor_list = select_single_mlvl( + score_factors, img_id, detach=True) + else: + score_factor_list = [None for _ in range(num_levels)] + + results = self._predict_by_feat_single( + cls_score_list=cls_score_list, + bbox_pred_list=bbox_pred_list, + score_factor_list=score_factor_list, + mlvl_priors=mlvl_priors, + img_meta=img_meta, + cfg=cfg, + rescale=rescale, + with_nms=with_nms) + result_list.append(results) + return result_list + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image, each item has shape + (num_priors * 1, H, W). + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid. In all + anchor-based methods, it has shape (num_priors, 4). In + all anchor-free methods, it has shape (num_priors, 2) + when `with_stride=True`, otherwise it still has shape + (num_priors, 4). + img_meta (dict): Image meta info. + cfg (mmengine.Config): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + if score_factor_list[0] is None: + # e.g. Retina, FreeAnchor, etc. + with_score_factors = False + else: + # e.g. FCOS, PAA, ATSS, etc. + with_score_factors = True + + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bbox_preds = [] + mlvl_valid_priors = [] + mlvl_scores = [] + mlvl_labels = [] + if with_score_factors: + mlvl_score_factors = [] + else: + mlvl_score_factors = None + for level_idx, (cls_score, bbox_pred, score_factor, priors) in \ + enumerate(zip(cls_score_list, bbox_pred_list, + score_factor_list, mlvl_priors)): + + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + + dim = self.bbox_coder.encode_size + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, dim) + if with_score_factors: + score_factor = score_factor.permute(1, 2, + 0).reshape(-1).sigmoid() + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + + # the `custom_cls_channels` parameter is derived from + # CrossEntropyCustomLoss and FocalCustomLoss, and is currently used + # in v3det. + if getattr(self.loss_cls, 'custom_cls_channels', False): + scores = self.loss_cls.get_activation(cls_score) + elif self.use_sigmoid_cls: + scores = cls_score.sigmoid() + else: + # remind that we set FG labels to [0, num_class-1] + # since mmdet v2.0 + # BG cat_id: num_class + scores = cls_score.softmax(-1)[:, :-1] + + # After https://github.com/open-mmlab/mmdetection/pull/6268/, + # this operation keeps fewer bboxes under the same `nms_pre`. + # There is no difference in performance for most models. If you + # find a slight drop in performance, you can set a larger + # `nms_pre` than before. + score_thr = cfg.get('score_thr', 0) + + results = filter_scores_and_topk( + scores, score_thr, nms_pre, + dict(bbox_pred=bbox_pred, priors=priors)) + scores, labels, keep_idxs, filtered_results = results + + bbox_pred = filtered_results['bbox_pred'] + priors = filtered_results['priors'] + + if with_score_factors: + score_factor = score_factor[keep_idxs] + + mlvl_bbox_preds.append(bbox_pred) + mlvl_valid_priors.append(priors) + mlvl_scores.append(scores) + mlvl_labels.append(labels) + + if with_score_factors: + mlvl_score_factors.append(score_factor) + + bbox_pred = torch.cat(mlvl_bbox_preds) + priors = cat_boxes(mlvl_valid_priors) + bboxes = self.bbox_coder.decode(priors, bbox_pred, max_shape=img_shape) + + results = InstanceData() + results.bboxes = bboxes + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + if with_score_factors: + results.score_factors = torch.cat(mlvl_score_factors) + + return self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + + def _bbox_post_process(self, + results: InstanceData, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True, + img_meta: Optional[dict] = None) -> InstanceData: + """bbox post-processing method. + + The boxes would be rescaled to the original image scale and do + the nms operation. Usually `with_nms` is False is used for aug test. + + Args: + results (:obj:`InstaceData`): Detection instance results, + each item has shape (num_bboxes, ). + cfg (ConfigDict): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Default to False. + with_nms (bool): If True, do nms before return boxes. + Default to True. + img_meta (dict, optional): Image meta info. Defaults to None. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + if rescale: + assert img_meta.get('scale_factor') is not None + scale_factor = [1 / s for s in img_meta['scale_factor']] + results.bboxes = scale_boxes(results.bboxes, scale_factor) + + if hasattr(results, 'score_factors'): + # TODO: Add sqrt operation in order to be consistent with + # the paper. + score_factors = results.pop('score_factors') + results.scores = results.scores * score_factors + + # filter small size bboxes + if cfg.get('min_bbox_size', -1) >= 0: + w, h = get_box_wh(results.bboxes) + valid_mask = (w > cfg.min_bbox_size) & (h > cfg.min_bbox_size) + if not valid_mask.all(): + results = results[valid_mask] + + # TODO: deal with `with_nms` and `nms_cfg=None` in test_cfg + if with_nms and results.bboxes.numel() > 0: + bboxes = get_box_tensor(results.bboxes) + det_bboxes, keep_idxs = batched_nms(bboxes, results.scores, + results.labels, cfg.nms) + results = results[keep_idxs] + # some nms would reweight the score, such as softnms + results.scores = det_bboxes[:, -1] + results = results[:cfg.max_per_img] + + return results + + def aug_test(self, + aug_batch_feats, + aug_batch_img_metas, + rescale=False, + with_ori_nms=False, + **kwargs): + """Test function with test time augmentation. + + Args: + aug_batch_feats (list[tuple[Tensor]]): The outer list + indicates test-time augmentations and inner tuple + indicate the multi-level feats from + FPN, each Tensor should have a shape (B, C, H, W), + aug_batch_img_metas (list[list[dict]]): Meta information + of images under the different test-time augs + (multiscale, flip, etc.). The outer list indicate + the + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + with_ori_nms (bool): Whether execute the nms in original head. + Defaults to False. It will be `True` when the head is + adopted as `rpn_head`. + + Returns: + list(obj:`InstanceData`): Detection results of the + input images. Each item usually contains\ + following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance,) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances,). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + # TODO: remove this for detr and deformdetr + sig_of_get_results = signature(self.get_results) + get_results_args = [ + p.name for p in sig_of_get_results.parameters.values() + ] + get_results_single_sig = signature(self._get_results_single) + get_results_single_sig_args = [ + p.name for p in get_results_single_sig.parameters.values() + ] + assert ('with_nms' in get_results_args) and \ + ('with_nms' in get_results_single_sig_args), \ + f'{self.__class__.__name__}' \ + 'does not support test-time augmentation ' + + num_imgs = len(aug_batch_img_metas[0]) + aug_batch_results = [] + for x, img_metas in zip(aug_batch_feats, aug_batch_img_metas): + outs = self.forward(x) + batch_instance_results = self.get_results( + *outs, + img_metas=img_metas, + cfg=self.test_cfg, + rescale=False, + with_nms=with_ori_nms, + **kwargs) + aug_batch_results.append(batch_instance_results) + + # after merging, bboxes will be rescaled to the original image + batch_results = merge_aug_results(aug_batch_results, + aug_batch_img_metas) + + final_results = [] + for img_id in range(num_imgs): + results = batch_results[img_id] + det_bboxes, keep_idxs = batched_nms(results.bboxes, results.scores, + results.labels, + self.test_cfg.nms) + results = results[keep_idxs] + # some nms operation may reweight the score such as softnms + results.scores = det_bboxes[:, -1] + results = results[:self.test_cfg.max_per_img] + if rescale: + # all results have been mapped to the original scale + # in `merge_aug_results`, so just pass + pass + else: + # map to the first aug image scale + scale_factor = results.bboxes.new_tensor( + aug_batch_img_metas[0][img_id]['scale_factor']) + results.bboxes = \ + results.bboxes * scale_factor + + final_results.append(results) + + return final_results diff --git a/mmdetection/mmdet/models/dense_heads/base_mask_head.py b/mmdetection/mmdet/models/dense_heads/base_mask_head.py new file mode 100644 index 00000000..7183d782 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/base_mask_head.py @@ -0,0 +1,128 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import List, Tuple, Union + +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.structures import SampleList +from mmdet.utils import InstanceList, OptInstanceList, OptMultiConfig +from ..utils import unpack_gt_instances + + +class BaseMaskHead(BaseModule, metaclass=ABCMeta): + """Base class for mask heads used in One-Stage Instance Segmentation.""" + + def __init__(self, init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + + @abstractmethod + def loss_by_feat(self, *args, **kwargs): + """Calculate the loss based on the features extracted by the mask + head.""" + pass + + @abstractmethod + def predict_by_feat(self, *args, **kwargs): + """Transform a batch of output features extracted from the head into + mask results.""" + pass + + def loss(self, + x: Union[List[Tensor], Tuple[Tensor]], + batch_data_samples: SampleList, + positive_infos: OptInstanceList = None, + **kwargs) -> dict: + """Perform forward propagation and loss calculation of the mask head on + the features of the upstream network. + + Args: + x (list[Tensor] | tuple[Tensor]): Features from FPN. + Each has a shape (B, C, H, W). + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + positive_infos (list[:obj:`InstanceData`], optional): Information + of positive samples. Used when the label assignment is + done outside the MaskHead, e.g., BboxHead in + YOLACT or CondInst, etc. When the label assignment is done in + MaskHead, it would be None, like SOLO or SOLOv2. All values + in it should have shape (num_positive_samples, *). + + + Returns: + dict: A dictionary of loss components. + """ + if positive_infos is None: + outs = self(x) + else: + outs = self(x, positive_infos) + + assert isinstance(outs, tuple), 'Forward results should be a tuple, ' \ + 'even if only one item is returned' + + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, batch_img_metas \ + = outputs + for gt_instances, img_metas in zip(batch_gt_instances, + batch_img_metas): + img_shape = img_metas['batch_input_shape'] + gt_masks = gt_instances.masks.pad(img_shape) + gt_instances.masks = gt_masks + + losses = self.loss_by_feat( + *outs, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas, + positive_infos=positive_infos, + batch_gt_instances_ignore=batch_gt_instances_ignore, + **kwargs) + return losses + + def predict(self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + rescale: bool = False, + results_list: OptInstanceList = None, + **kwargs) -> InstanceList: + """Test function without test-time augmentation. + + Args: + x (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + results_list (list[obj:`InstanceData`], optional): Detection + results of each image after the post process. Only exist + if there is a `bbox_head`, like `YOLACT`, `CondInst`, etc. + + Returns: + list[obj:`InstanceData`]: Instance segmentation + results of each image after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance,) + - labels (Tensor): Has a shape (num_instances,). + - masks (Tensor): Processed mask results, has a + shape (num_instances, h, w). + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + if results_list is None: + outs = self(x) + else: + outs = self(x, results_list) + + results_list = self.predict_by_feat( + *outs, + batch_img_metas=batch_img_metas, + rescale=rescale, + results_list=results_list, + **kwargs) + + return results_list diff --git a/mmdetection/mmdet/models/dense_heads/boxinst_head.py b/mmdetection/mmdet/models/dense_heads/boxinst_head.py new file mode 100644 index 00000000..7d6e8f77 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/boxinst_head.py @@ -0,0 +1,252 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn.functional as F +from mmengine import MessageHub +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import InstanceList +from ..utils.misc import unfold_wo_center +from .condinst_head import CondInstBboxHead, CondInstMaskHead + + +@MODELS.register_module() +class BoxInstBboxHead(CondInstBboxHead): + """BoxInst box head used in https://arxiv.org/abs/2012.02310.""" + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + +@MODELS.register_module() +class BoxInstMaskHead(CondInstMaskHead): + """BoxInst mask head used in https://arxiv.org/abs/2012.02310. + + This head outputs the mask for BoxInst. + + Args: + pairwise_size (dict): The size of neighborhood for each pixel. + Defaults to 3. + pairwise_dilation (int): The dilation of neighborhood for each pixel. + Defaults to 2. + warmup_iters (int): Warmup iterations for pair-wise loss. + Defaults to 10000. + """ + + def __init__(self, + *arg, + pairwise_size: int = 3, + pairwise_dilation: int = 2, + warmup_iters: int = 10000, + **kwargs) -> None: + self.pairwise_size = pairwise_size + self.pairwise_dilation = pairwise_dilation + self.warmup_iters = warmup_iters + super().__init__(*arg, **kwargs) + + def get_pairwise_affinity(self, mask_logits: Tensor) -> Tensor: + """Compute the pairwise affinity for each pixel.""" + log_fg_prob = F.logsigmoid(mask_logits).unsqueeze(1) + log_bg_prob = F.logsigmoid(-mask_logits).unsqueeze(1) + + log_fg_prob_unfold = unfold_wo_center( + log_fg_prob, + kernel_size=self.pairwise_size, + dilation=self.pairwise_dilation) + log_bg_prob_unfold = unfold_wo_center( + log_bg_prob, + kernel_size=self.pairwise_size, + dilation=self.pairwise_dilation) + + # the probability of making the same prediction: + # p_i * p_j + (1 - p_i) * (1 - p_j) + # we compute the the probability in log space + # to avoid numerical instability + log_same_fg_prob = log_fg_prob[:, :, None] + log_fg_prob_unfold + log_same_bg_prob = log_bg_prob[:, :, None] + log_bg_prob_unfold + + # TODO: Figure out the difference between it and directly sum + max_ = torch.max(log_same_fg_prob, log_same_bg_prob) + log_same_prob = torch.log( + torch.exp(log_same_fg_prob - max_) + + torch.exp(log_same_bg_prob - max_)) + max_ + + return -log_same_prob[:, 0] + + def loss_by_feat(self, mask_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], positive_infos: InstanceList, + **kwargs) -> dict: + """Calculate the loss based on the features extracted by the mask head. + + Args: + mask_preds (list[Tensor]): List of predicted masks, each has + shape (num_classes, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``masks``, + and ``labels`` attributes. + batch_img_metas (list[dict]): Meta information of multiple images. + positive_infos (List[:obj:``InstanceData``]): Information of + positive samples of each image that are assigned in detection + head. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert positive_infos is not None, \ + 'positive_infos should not be None in `BoxInstMaskHead`' + losses = dict() + + loss_mask_project = 0. + loss_mask_pairwise = 0. + num_imgs = len(mask_preds) + total_pos = 0. + avg_fatcor = 0. + + for idx in range(num_imgs): + (mask_pred, pos_mask_targets, pos_pairwise_masks, num_pos) = \ + self._get_targets_single( + mask_preds[idx], batch_gt_instances[idx], + positive_infos[idx]) + # mask loss + total_pos += num_pos + if num_pos == 0 or pos_mask_targets is None: + loss_project = mask_pred.new_zeros(1).mean() + loss_pairwise = mask_pred.new_zeros(1).mean() + avg_fatcor += 0. + else: + # compute the project term + loss_project_x = self.loss_mask( + mask_pred.max(dim=1, keepdim=True)[0], + pos_mask_targets.max(dim=1, keepdim=True)[0], + reduction_override='none').sum() + loss_project_y = self.loss_mask( + mask_pred.max(dim=2, keepdim=True)[0], + pos_mask_targets.max(dim=2, keepdim=True)[0], + reduction_override='none').sum() + loss_project = loss_project_x + loss_project_y + # compute the pairwise term + pairwise_affinity = self.get_pairwise_affinity(mask_pred) + avg_fatcor += pos_pairwise_masks.sum().clamp(min=1.0) + loss_pairwise = (pairwise_affinity * pos_pairwise_masks).sum() + + loss_mask_project += loss_project + loss_mask_pairwise += loss_pairwise + + if total_pos == 0: + total_pos += 1 # avoid nan + if avg_fatcor == 0: + avg_fatcor += 1 # avoid nan + loss_mask_project = loss_mask_project / total_pos + loss_mask_pairwise = loss_mask_pairwise / avg_fatcor + message_hub = MessageHub.get_current_instance() + iter = message_hub.get_info('iter') + warmup_factor = min(iter / float(self.warmup_iters), 1.0) + loss_mask_pairwise *= warmup_factor + + losses.update( + loss_mask_project=loss_mask_project, + loss_mask_pairwise=loss_mask_pairwise) + return losses + + def _get_targets_single(self, mask_preds: Tensor, + gt_instances: InstanceData, + positive_info: InstanceData): + """Compute targets for predictions of single image. + + Args: + mask_preds (Tensor): Predicted prototypes with shape + (num_classes, H, W). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes``, ``labels``, + and ``masks`` attributes. + positive_info (:obj:`InstanceData`): Information of positive + samples that are assigned in detection head. It usually + contains following keys. + + - pos_assigned_gt_inds (Tensor): Assigner GT indexes of + positive proposals, has shape (num_pos, ) + - pos_inds (Tensor): Positive index of image, has + shape (num_pos, ). + - param_pred (Tensor): Positive param preditions + with shape (num_pos, num_params). + + Returns: + tuple: Usually returns a tuple containing learning targets. + + - mask_preds (Tensor): Positive predicted mask with shape + (num_pos, mask_h, mask_w). + - pos_mask_targets (Tensor): Positive mask targets with shape + (num_pos, mask_h, mask_w). + - pos_pairwise_masks (Tensor): Positive pairwise masks with + shape: (num_pos, num_neighborhood, mask_h, mask_w). + - num_pos (int): Positive numbers. + """ + gt_bboxes = gt_instances.bboxes + device = gt_bboxes.device + # Note that gt_masks are generated by full box + # from BoxInstDataPreprocessor + gt_masks = gt_instances.masks.to_tensor( + dtype=torch.bool, device=device).float() + # Note that pairwise_masks are generated by image color similarity + # from BoxInstDataPreprocessor + pairwise_masks = gt_instances.pairwise_masks + pairwise_masks = pairwise_masks.to(device=device) + + # process with mask targets + pos_assigned_gt_inds = positive_info.get('pos_assigned_gt_inds') + scores = positive_info.get('scores') + centernesses = positive_info.get('centernesses') + num_pos = pos_assigned_gt_inds.size(0) + + if gt_masks.size(0) == 0 or num_pos == 0: + return mask_preds, None, None, 0 + # Since we're producing (near) full image masks, + # it'd take too much vram to backprop on every single mask. + # Thus we select only a subset. + if (self.max_masks_to_train != -1) and \ + (num_pos > self.max_masks_to_train): + perm = torch.randperm(num_pos) + select = perm[:self.max_masks_to_train] + mask_preds = mask_preds[select] + pos_assigned_gt_inds = pos_assigned_gt_inds[select] + num_pos = self.max_masks_to_train + elif self.topk_masks_per_img != -1: + unique_gt_inds = pos_assigned_gt_inds.unique() + num_inst_per_gt = max( + int(self.topk_masks_per_img / len(unique_gt_inds)), 1) + + keep_mask_preds = [] + keep_pos_assigned_gt_inds = [] + for gt_ind in unique_gt_inds: + per_inst_pos_inds = (pos_assigned_gt_inds == gt_ind) + mask_preds_per_inst = mask_preds[per_inst_pos_inds] + gt_inds_per_inst = pos_assigned_gt_inds[per_inst_pos_inds] + if sum(per_inst_pos_inds) > num_inst_per_gt: + per_inst_scores = scores[per_inst_pos_inds].sigmoid().max( + dim=1)[0] + per_inst_centerness = centernesses[ + per_inst_pos_inds].sigmoid().reshape(-1, ) + select = (per_inst_scores * per_inst_centerness).topk( + k=num_inst_per_gt, dim=0)[1] + mask_preds_per_inst = mask_preds_per_inst[select] + gt_inds_per_inst = gt_inds_per_inst[select] + keep_mask_preds.append(mask_preds_per_inst) + keep_pos_assigned_gt_inds.append(gt_inds_per_inst) + mask_preds = torch.cat(keep_mask_preds) + pos_assigned_gt_inds = torch.cat(keep_pos_assigned_gt_inds) + num_pos = pos_assigned_gt_inds.size(0) + + # Follow the origin implement + start = int(self.mask_out_stride // 2) + gt_masks = gt_masks[:, start::self.mask_out_stride, + start::self.mask_out_stride] + gt_masks = gt_masks.gt(0.5).float() + pos_mask_targets = gt_masks[pos_assigned_gt_inds] + pos_pairwise_masks = pairwise_masks[pos_assigned_gt_inds] + pos_pairwise_masks = pos_pairwise_masks * pos_mask_targets.unsqueeze(1) + + return (mask_preds, pos_mask_targets, pos_pairwise_masks, num_pos) diff --git a/mmdetection/mmdet/models/dense_heads/cascade_rpn_head.py b/mmdetection/mmdet/models/dense_heads/cascade_rpn_head.py new file mode 100644 index 00000000..a8686cc2 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/cascade_rpn_head.py @@ -0,0 +1,1110 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from __future__ import division +import copy +from typing import Dict, List, Optional, Tuple, Union + +import torch +import torch.nn as nn +from mmcv.ops import DeformConv2d +from mmengine.config import ConfigDict +from mmengine.model import BaseModule, ModuleList +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import SampleList +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, + OptInstanceList, OptMultiConfig) +from ..task_modules.assigners import RegionAssigner +from ..task_modules.samplers import PseudoSampler +from ..utils import (images_to_levels, multi_apply, select_single_mlvl, + unpack_gt_instances) +from .base_dense_head import BaseDenseHead +from .rpn_head import RPNHead + + +class AdaptiveConv(BaseModule): + """AdaptiveConv used to adapt the sampling location with the anchors. + + Args: + in_channels (int): Number of channels in the input image. + out_channels (int): Number of channels produced by the convolution. + kernel_size (int or tuple[int]): Size of the conv kernel. + Defaults to 3. + stride (int or tuple[int]): Stride of the convolution. Defaults to 1. + padding (int or tuple[int]): Zero-padding added to both sides of + the input. Defaults to 1. + dilation (int or tuple[int]): Spacing between kernel elements. + Defaults to 3. + groups (int): Number of blocked connections from input channels to + output channels. Defaults to 1. + bias (bool): If set True, adds a learnable bias to the output. + Defaults to False. + adapt_type (str): Type of adaptive conv, can be either ``offset`` + (arbitrary anchors) or 'dilation' (uniform anchor). + Defaults to 'dilation'. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or \ + list[dict]): Initialization config dict. + """ + + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: Union[int, Tuple[int]] = 3, + stride: Union[int, Tuple[int]] = 1, + padding: Union[int, Tuple[int]] = 1, + dilation: Union[int, Tuple[int]] = 3, + groups: int = 1, + bias: bool = False, + adapt_type: str = 'dilation', + init_cfg: MultiConfig = dict( + type='Normal', std=0.01, override=dict(name='conv')) + ) -> None: + super().__init__(init_cfg=init_cfg) + assert adapt_type in ['offset', 'dilation'] + self.adapt_type = adapt_type + + assert kernel_size == 3, 'Adaptive conv only supports kernels 3' + if self.adapt_type == 'offset': + assert stride == 1 and padding == 1 and groups == 1, \ + 'Adaptive conv offset mode only supports padding: {1}, ' \ + f'stride: {1}, groups: {1}' + self.conv = DeformConv2d( + in_channels, + out_channels, + kernel_size, + padding=padding, + stride=stride, + groups=groups, + bias=bias) + else: + self.conv = nn.Conv2d( + in_channels, + out_channels, + kernel_size, + padding=dilation, + dilation=dilation) + + def forward(self, x: Tensor, offset: Tensor) -> Tensor: + """Forward function.""" + if self.adapt_type == 'offset': + N, _, H, W = x.shape + assert offset is not None + assert H * W == offset.shape[1] + # reshape [N, NA, 18] to (N, 18, H, W) + offset = offset.permute(0, 2, 1).reshape(N, -1, H, W) + offset = offset.contiguous() + x = self.conv(x, offset) + else: + assert offset is None + x = self.conv(x) + return x + + +@MODELS.register_module() +class StageCascadeRPNHead(RPNHead): + """Stage of CascadeRPNHead. + + Args: + in_channels (int): Number of channels in the input feature map. + anchor_generator (:obj:`ConfigDict` or dict): anchor generator config. + adapt_cfg (:obj:`ConfigDict` or dict): adaptation config. + bridged_feature (bool): whether update rpn feature. Defaults to False. + with_cls (bool): whether use classification branch. Defaults to True. + init_cfg :obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + in_channels: int, + anchor_generator: ConfigType = dict( + type='AnchorGenerator', + scales=[8], + ratios=[1.0], + strides=[4, 8, 16, 32, 64]), + adapt_cfg: ConfigType = dict(type='dilation', dilation=3), + bridged_feature: bool = False, + with_cls: bool = True, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + self.with_cls = with_cls + self.anchor_strides = anchor_generator['strides'] + self.anchor_scales = anchor_generator['scales'] + self.bridged_feature = bridged_feature + self.adapt_cfg = adapt_cfg + super().__init__( + in_channels=in_channels, + anchor_generator=anchor_generator, + init_cfg=init_cfg, + **kwargs) + + # override sampling and sampler + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + # use PseudoSampler when sampling is False + if self.train_cfg.get('sampler', None) is not None: + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], default_args=dict(context=self)) + else: + self.sampler = PseudoSampler(context=self) + + if init_cfg is None: + self.init_cfg = dict( + type='Normal', std=0.01, override=[dict(name='rpn_reg')]) + if self.with_cls: + self.init_cfg['override'].append(dict(name='rpn_cls')) + + def _init_layers(self) -> None: + """Init layers of a CascadeRPN stage.""" + adapt_cfg = copy.deepcopy(self.adapt_cfg) + adapt_cfg['adapt_type'] = adapt_cfg.pop('type') + self.rpn_conv = AdaptiveConv(self.in_channels, self.feat_channels, + **adapt_cfg) + if self.with_cls: + self.rpn_cls = nn.Conv2d(self.feat_channels, + self.num_anchors * self.cls_out_channels, + 1) + self.rpn_reg = nn.Conv2d(self.feat_channels, self.num_anchors * 4, 1) + self.relu = nn.ReLU(inplace=True) + + def forward_single(self, x: Tensor, offset: Tensor) -> Tuple[Tensor]: + """Forward function of single scale.""" + bridged_x = x + x = self.relu(self.rpn_conv(x, offset)) + if self.bridged_feature: + bridged_x = x # update feature + cls_score = self.rpn_cls(x) if self.with_cls else None + bbox_pred = self.rpn_reg(x) + return bridged_x, cls_score, bbox_pred + + def forward( + self, + feats: List[Tensor], + offset_list: Optional[List[Tensor]] = None) -> Tuple[List[Tensor]]: + """Forward function.""" + if offset_list is None: + offset_list = [None for _ in range(len(feats))] + return multi_apply(self.forward_single, feats, offset_list) + + def _region_targets_single(self, flat_anchors: Tensor, valid_flags: Tensor, + gt_instances: InstanceData, img_meta: dict, + gt_instances_ignore: InstanceData, + featmap_sizes: List[Tuple[int, int]], + num_level_anchors: List[int]) -> tuple: + """Get anchor targets based on region for single level. + + Args: + flat_anchors (Tensor): Multi-level anchors of the image, which are + concatenated into a single tensor of shape (num_anchors, 4) + valid_flags (Tensor): Multi level valid flags of the image, + which are concatenated into a single tensor of + shape (num_anchors, ). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + featmap_sizes (list[Tuple[int, int]]): Feature map size each level. + num_level_anchors (list[int]): The number of anchors in each level. + + Returns: + tuple: + + - labels (Tensor): Labels of each level. + - label_weights (Tensor): Label weights of each level. + - bbox_targets (Tensor): BBox targets of each level. + - bbox_weights (Tensor): BBox weights of each level. + - pos_inds (Tensor): positive samples indexes. + - neg_inds (Tensor): negative samples indexes. + - sampling_result (:obj:`SamplingResult`): Sampling results. + """ + pred_instances = InstanceData() + pred_instances.priors = flat_anchors + pred_instances.valid_flags = valid_flags + + assign_result = self.assigner.assign( + pred_instances, + gt_instances, + img_meta, + featmap_sizes, + num_level_anchors, + self.anchor_scales[0], + self.anchor_strides, + gt_instances_ignore=gt_instances_ignore, + allowed_border=self.train_cfg['allowed_border']) + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_anchors = flat_anchors.shape[0] + bbox_targets = torch.zeros_like(flat_anchors) + bbox_weights = torch.zeros_like(flat_anchors) + labels = flat_anchors.new_zeros(num_anchors, dtype=torch.long) + label_weights = flat_anchors.new_zeros(num_anchors, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + if not self.reg_decoded_bbox: + pos_bbox_targets = self.bbox_coder.encode( + sampling_result.pos_bboxes, sampling_result.pos_gt_bboxes) + else: + pos_bbox_targets = sampling_result.pos_gt_bboxes + bbox_targets[pos_inds, :] = pos_bbox_targets + bbox_weights[pos_inds, :] = 1.0 + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + return (labels, label_weights, bbox_targets, bbox_weights, pos_inds, + neg_inds, sampling_result) + + def region_targets( + self, + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + featmap_sizes: List[Tuple[int, int]], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + return_sampling_results: bool = False, + ) -> tuple: + """Compute regression and classification targets for anchors when using + RegionAssigner. + + Args: + anchor_list (list[list[Tensor]]): Multi level anchors of each + image. + valid_flag_list (list[list[Tensor]]): Multi level valid flags of + each image. + featmap_sizes (list[Tuple[int, int]]): Feature map size each level. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + tuple: + + - labels_list (list[Tensor]): Labels of each level. + - label_weights_list (list[Tensor]): Label weights of each + level. + - bbox_targets_list (list[Tensor]): BBox targets of each level. + - bbox_weights_list (list[Tensor]): BBox weights of each level. + - avg_factor (int): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + ``PseudoSampler``, ``avg_factor`` is usually equal to the + number of positive priors. + """ + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + # concat all level anchors to a single tensor + concat_anchor_list = [] + concat_valid_flag_list = [] + for i in range(num_imgs): + assert len(anchor_list[i]) == len(valid_flag_list[i]) + concat_anchor_list.append(torch.cat(anchor_list[i])) + concat_valid_flag_list.append(torch.cat(valid_flag_list[i])) + + # compute targets for each image + (all_labels, all_label_weights, all_bbox_targets, all_bbox_weights, + pos_inds_list, neg_inds_list, sampling_results_list) = multi_apply( + self._region_targets_single, + concat_anchor_list, + concat_valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + featmap_sizes=featmap_sizes, + num_level_anchors=num_level_anchors) + # no valid anchors + if any([labels is None for labels in all_labels]): + return None + # sampled anchors of all images + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + # split targets to a list w.r.t. multiple levels + labels_list = images_to_levels(all_labels, num_level_anchors) + label_weights_list = images_to_levels(all_label_weights, + num_level_anchors) + bbox_targets_list = images_to_levels(all_bbox_targets, + num_level_anchors) + bbox_weights_list = images_to_levels(all_bbox_weights, + num_level_anchors) + res = (labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor) + if return_sampling_results: + res = res + (sampling_results_list, ) + return res + + def get_targets( + self, + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + featmap_sizes: List[Tuple[int, int]], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + return_sampling_results: bool = False, + ) -> tuple: + """Compute regression and classification targets for anchors. + + Args: + anchor_list (list[list[Tensor]]): Multi level anchors of each + image. + valid_flag_list (list[list[Tensor]]): Multi level valid flags of + each image. + featmap_sizes (list[Tuple[int, int]]): Feature map size each level. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + return_sampling_results (bool): Whether to return the sampling + results. Defaults to False. + + Returns: + tuple: + + - labels_list (list[Tensor]): Labels of each level. + - label_weights_list (list[Tensor]): Label weights of each + level. + - bbox_targets_list (list[Tensor]): BBox targets of each level. + - bbox_weights_list (list[Tensor]): BBox weights of each level. + - avg_factor (int): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + ``PseudoSampler``, ``avg_factor`` is usually equal to the + number of positive priors. + """ + if isinstance(self.assigner, RegionAssigner): + cls_reg_targets = self.region_targets( + anchor_list, + valid_flag_list, + featmap_sizes, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + return_sampling_results=return_sampling_results) + else: + cls_reg_targets = super().get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + return_sampling_results=return_sampling_results) + return cls_reg_targets + + def anchor_offset(self, anchor_list: List[List[Tensor]], + anchor_strides: List[int], + featmap_sizes: List[Tuple[int, int]]) -> List[Tensor]: + """ Get offset for deformable conv based on anchor shape + NOTE: currently support deformable kernel_size=3 and dilation=1 + + Args: + anchor_list (list[list[tensor])): [NI, NLVL, NA, 4] list of + multi-level anchors + anchor_strides (list[int]): anchor stride of each level + + Returns: + list[tensor]: offset of DeformConv kernel with shapes of + [NLVL, NA, 2, 18]. + """ + + def _shape_offset(anchors, stride, ks=3, dilation=1): + # currently support kernel_size=3 and dilation=1 + assert ks == 3 and dilation == 1 + pad = (ks - 1) // 2 + idx = torch.arange(-pad, pad + 1, dtype=dtype, device=device) + yy, xx = torch.meshgrid(idx, idx) # return order matters + xx = xx.reshape(-1) + yy = yy.reshape(-1) + w = (anchors[:, 2] - anchors[:, 0]) / stride + h = (anchors[:, 3] - anchors[:, 1]) / stride + w = w / (ks - 1) - dilation + h = h / (ks - 1) - dilation + offset_x = w[:, None] * xx # (NA, ks**2) + offset_y = h[:, None] * yy # (NA, ks**2) + return offset_x, offset_y + + def _ctr_offset(anchors, stride, featmap_size): + feat_h, feat_w = featmap_size + assert len(anchors) == feat_h * feat_w + + x = (anchors[:, 0] + anchors[:, 2]) * 0.5 + y = (anchors[:, 1] + anchors[:, 3]) * 0.5 + # compute centers on feature map + x = x / stride + y = y / stride + # compute predefine centers + xx = torch.arange(0, feat_w, device=anchors.device) + yy = torch.arange(0, feat_h, device=anchors.device) + yy, xx = torch.meshgrid(yy, xx) + xx = xx.reshape(-1).type_as(x) + yy = yy.reshape(-1).type_as(y) + + offset_x = x - xx # (NA, ) + offset_y = y - yy # (NA, ) + return offset_x, offset_y + + num_imgs = len(anchor_list) + num_lvls = len(anchor_list[0]) + dtype = anchor_list[0][0].dtype + device = anchor_list[0][0].device + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + + offset_list = [] + for i in range(num_imgs): + mlvl_offset = [] + for lvl in range(num_lvls): + c_offset_x, c_offset_y = _ctr_offset(anchor_list[i][lvl], + anchor_strides[lvl], + featmap_sizes[lvl]) + s_offset_x, s_offset_y = _shape_offset(anchor_list[i][lvl], + anchor_strides[lvl]) + + # offset = ctr_offset + shape_offset + offset_x = s_offset_x + c_offset_x[:, None] + offset_y = s_offset_y + c_offset_y[:, None] + + # offset order (y0, x0, y1, x2, .., y8, x8, y9, x9) + offset = torch.stack([offset_y, offset_x], dim=-1) + offset = offset.reshape(offset.size(0), -1) # [NA, 2*ks**2] + mlvl_offset.append(offset) + offset_list.append(torch.cat(mlvl_offset)) # [totalNA, 2*ks**2] + offset_list = images_to_levels(offset_list, num_level_anchors) + return offset_list + + def loss_by_feat_single(self, cls_score: Tensor, bbox_pred: Tensor, + anchors: Tensor, labels: Tensor, + label_weights: Tensor, bbox_targets: Tensor, + bbox_weights: Tensor, avg_factor: int) -> tuple: + """Loss function on single scale.""" + # classification loss + if self.with_cls: + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + cls_score = cls_score.permute(0, 2, 3, + 1).reshape(-1, self.cls_out_channels) + loss_cls = self.loss_cls( + cls_score, labels, label_weights, avg_factor=avg_factor) + # regression loss + bbox_targets = bbox_targets.reshape(-1, 4) + bbox_weights = bbox_weights.reshape(-1, 4) + bbox_pred = bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4) + if self.reg_decoded_bbox: + # When the regression loss (e.g. `IouLoss`, `GIouLoss`) + # is applied directly on the decoded bounding boxes, it + # decodes the already encoded coordinates to absolute format. + anchors = anchors.reshape(-1, 4) + bbox_pred = self.bbox_coder.decode(anchors, bbox_pred) + loss_reg = self.loss_bbox( + bbox_pred, bbox_targets, bbox_weights, avg_factor=avg_factor) + if self.with_cls: + return loss_cls, loss_reg + return None, loss_reg + + def loss_by_feat( + self, + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Compute losses of the head. + + Args: + anchor_list (list[list[Tensor]]): Multi level anchors of each + image. + valid_flag_list (list[list[Tensor]]): Multi level valid flags of + each image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_anchors, ) + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in bbox_preds] + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + featmap_sizes, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + return_sampling_results=True) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + avg_factor, sampling_results_list) = cls_reg_targets + if not sampling_results_list[0].avg_factor_with_neg: + # 200 is hard-coded average factor, + # which follows guided anchoring. + avg_factor = sum([label.numel() for label in labels_list]) / 200.0 + + # change per image, per level anchor_list to per_level, per_image + mlvl_anchor_list = list(zip(*anchor_list)) + # concat mlvl_anchor_list + mlvl_anchor_list = [ + torch.cat(anchors, dim=0) for anchors in mlvl_anchor_list + ] + + losses = multi_apply( + self.loss_by_feat_single, + cls_scores, + bbox_preds, + mlvl_anchor_list, + labels_list, + label_weights_list, + bbox_targets_list, + bbox_weights_list, + avg_factor=avg_factor) + if self.with_cls: + return dict(loss_rpn_cls=losses[0], loss_rpn_reg=losses[1]) + return dict(loss_rpn_reg=losses[1]) + + def predict_by_feat(self, + anchor_list: List[List[Tensor]], + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_img_metas: List[dict], + cfg: Optional[ConfigDict] = None, + rescale: bool = False) -> InstanceList: + """Get proposal predict. Overriding to enable input ``anchor_list`` + from outside. + + Args: + anchor_list (list[list[Tensor]]): Multi level anchors of each + image. + cls_scores (list[Tensor]): Classification scores for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * 4, H, W). + batch_img_metas (list[dict], Optional): Image meta info. + cfg (:obj:`ConfigDict`, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_scores) == len(bbox_preds) + + result_list = [] + for img_id in range(len(batch_img_metas)): + cls_score_list = select_single_mlvl(cls_scores, img_id) + bbox_pred_list = select_single_mlvl(bbox_preds, img_id) + proposals = self._predict_by_feat_single( + cls_scores=cls_score_list, + bbox_preds=bbox_pred_list, + mlvl_anchors=anchor_list[img_id], + img_meta=batch_img_metas[img_id], + cfg=cfg, + rescale=rescale) + result_list.append(proposals) + return result_list + + def _predict_by_feat_single(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + mlvl_anchors: List[Tensor], + img_meta: dict, + cfg: ConfigDict, + rescale: bool = False) -> InstanceData: + """Transform outputs of a single image into bbox predictions. + + Args: + cls_scores (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has + shape (num_anchors * 4, H, W). + mlvl_anchors (list[Tensor]): Box reference from all scale + levels of a single image, each item has shape + (num_total_anchors, 4). + img_shape (tuple[int]): Shape of the input image, + (height, width, 3). + scale_factor (ndarray): Scale factor of the image arange as + (w_scale, h_scale, w_scale, h_scale). + cfg (:obj:`ConfigDict`): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + # bboxes from different level should be independent during NMS, + # level_ids are used as labels for batched NMS to separate them + level_ids = [] + mlvl_scores = [] + mlvl_bbox_preds = [] + mlvl_valid_anchors = [] + nms_pre = cfg.get('nms_pre', -1) + for idx in range(len(cls_scores)): + rpn_cls_score = cls_scores[idx] + rpn_bbox_pred = bbox_preds[idx] + assert rpn_cls_score.size()[-2:] == rpn_bbox_pred.size()[-2:] + rpn_cls_score = rpn_cls_score.permute(1, 2, 0) + if self.use_sigmoid_cls: + rpn_cls_score = rpn_cls_score.reshape(-1) + scores = rpn_cls_score.sigmoid() + else: + rpn_cls_score = rpn_cls_score.reshape(-1, 2) + # We set FG labels to [0, num_class-1] and BG label to + # num_class in RPN head since mmdet v2.5, which is unified to + # be consistent with other head since mmdet v2.0. In mmdet v2.0 + # to v2.4 we keep BG label as 0 and FG label as 1 in rpn head. + scores = rpn_cls_score.softmax(dim=1)[:, 0] + rpn_bbox_pred = rpn_bbox_pred.permute(1, 2, 0).reshape(-1, 4) + anchors = mlvl_anchors[idx] + + if 0 < nms_pre < scores.shape[0]: + # sort is faster than topk + # _, topk_inds = scores.topk(cfg.nms_pre) + ranked_scores, rank_inds = scores.sort(descending=True) + topk_inds = rank_inds[:nms_pre] + scores = ranked_scores[:nms_pre] + rpn_bbox_pred = rpn_bbox_pred[topk_inds, :] + anchors = anchors[topk_inds, :] + mlvl_scores.append(scores) + mlvl_bbox_preds.append(rpn_bbox_pred) + mlvl_valid_anchors.append(anchors) + level_ids.append( + scores.new_full((scores.size(0), ), idx, dtype=torch.long)) + + anchors = torch.cat(mlvl_valid_anchors) + rpn_bbox_pred = torch.cat(mlvl_bbox_preds) + bboxes = self.bbox_coder.decode( + anchors, rpn_bbox_pred, max_shape=img_meta['img_shape']) + + proposals = InstanceData() + proposals.bboxes = bboxes + proposals.scores = torch.cat(mlvl_scores) + proposals.level_ids = torch.cat(level_ids) + + return self._bbox_post_process( + results=proposals, cfg=cfg, rescale=rescale, img_meta=img_meta) + + def refine_bboxes(self, anchor_list: List[List[Tensor]], + bbox_preds: List[Tensor], + img_metas: List[dict]) -> List[List[Tensor]]: + """Refine bboxes through stages.""" + num_levels = len(bbox_preds) + new_anchor_list = [] + for img_id in range(len(img_metas)): + mlvl_anchors = [] + for i in range(num_levels): + bbox_pred = bbox_preds[i][img_id].detach() + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4) + img_shape = img_metas[img_id]['img_shape'] + bboxes = self.bbox_coder.decode(anchor_list[img_id][i], + bbox_pred, img_shape) + mlvl_anchors.append(bboxes) + new_anchor_list.append(mlvl_anchors) + return new_anchor_list + + def loss(self, x: Tuple[Tensor], batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the features of the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, _, batch_img_metas = outputs + + featmap_sizes = [featmap.size()[-2:] for featmap in x] + device = x[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + if self.adapt_cfg['type'] == 'offset': + offset_list = self.anchor_offset(anchor_list, self.anchor_strides, + featmap_sizes) + else: + offset_list = None + + x, cls_score, bbox_pred = self(x, offset_list) + rpn_loss_inputs = (anchor_list, valid_flag_list, cls_score, bbox_pred, + batch_gt_instances, batch_img_metas) + losses = self.loss_by_feat(*rpn_loss_inputs) + + return losses + + def loss_and_predict( + self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + proposal_cfg: Optional[ConfigDict] = None, + ) -> Tuple[dict, InstanceList]: + """Perform forward propagation of the head, then calculate loss and + predictions from the features and data samples. + + Args: + x (tuple[Tensor]): Features from FPN. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + proposal_cfg (:obj`ConfigDict`, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + + Returns: + tuple: the return value is a tuple contains: + + - losses: (dict[str, Tensor]): A dictionary of loss components. + - predictions (list[:obj:`InstanceData`]): Detection + results of each image after the post process. + """ + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, _, batch_img_metas = outputs + + featmap_sizes = [featmap.size()[-2:] for featmap in x] + device = x[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + if self.adapt_cfg['type'] == 'offset': + offset_list = self.anchor_offset(anchor_list, self.anchor_strides, + featmap_sizes) + else: + offset_list = None + + x, cls_score, bbox_pred = self(x, offset_list) + rpn_loss_inputs = (anchor_list, valid_flag_list, cls_score, bbox_pred, + batch_gt_instances, batch_img_metas) + losses = self.loss_by_feat(*rpn_loss_inputs) + + predictions = self.predict_by_feat( + anchor_list, + cls_score, + bbox_pred, + batch_img_metas=batch_img_metas, + cfg=proposal_cfg) + return losses, predictions + + def predict(self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the detection head and predict + detection results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + featmap_sizes = [featmap.size()[-2:] for featmap in x] + device = x[0].device + anchor_list, _ = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + if self.adapt_cfg['type'] == 'offset': + offset_list = self.anchor_offset(anchor_list, self.anchor_strides, + featmap_sizes) + else: + offset_list = None + + x, cls_score, bbox_pred = self(x, offset_list) + predictions = self.stages[-1].predict_by_feat( + anchor_list, + cls_score, + bbox_pred, + batch_img_metas=batch_img_metas, + rescale=rescale) + return predictions + + +@MODELS.register_module() +class CascadeRPNHead(BaseDenseHead): + """The CascadeRPNHead will predict more accurate region proposals, which is + required for two-stage detectors (such as Fast/Faster R-CNN). CascadeRPN + consists of a sequence of RPNStage to progressively improve the accuracy of + the detected proposals. + + More details can be found in ``https://arxiv.org/abs/1909.06720``. + + Args: + num_stages (int): number of CascadeRPN stages. + stages (list[:obj:`ConfigDict` or dict]): list of configs to build + the stages. + train_cfg (list[:obj:`ConfigDict` or dict]): list of configs at + training time each stage. + test_cfg (:obj:`ConfigDict` or dict): config at testing time. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or \ + list[dict]): Initialization config dict. + """ + + def __init__(self, + num_classes: int, + num_stages: int, + stages: List[ConfigType], + train_cfg: List[ConfigType], + test_cfg: ConfigType, + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + assert num_classes == 1, 'Only support num_classes == 1' + assert num_stages == len(stages) + self.num_stages = num_stages + # Be careful! Pretrained weights cannot be loaded when use + # nn.ModuleList + self.stages = ModuleList() + for i in range(len(stages)): + train_cfg_i = train_cfg[i] if train_cfg is not None else None + stages[i].update(train_cfg=train_cfg_i) + stages[i].update(test_cfg=test_cfg) + self.stages.append(MODELS.build(stages[i])) + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + def loss_by_feat(self): + """loss_by_feat() is implemented in StageCascadeRPNHead.""" + pass + + def predict_by_feat(self): + """predict_by_feat() is implemented in StageCascadeRPNHead.""" + pass + + def loss(self, x: Tuple[Tensor], batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the features of the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, _, batch_img_metas = outputs + + featmap_sizes = [featmap.size()[-2:] for featmap in x] + device = x[0].device + anchor_list, valid_flag_list = self.stages[0].get_anchors( + featmap_sizes, batch_img_metas, device=device) + + losses = dict() + + for i in range(self.num_stages): + stage = self.stages[i] + + if stage.adapt_cfg['type'] == 'offset': + offset_list = stage.anchor_offset(anchor_list, + stage.anchor_strides, + featmap_sizes) + else: + offset_list = None + x, cls_score, bbox_pred = stage(x, offset_list) + rpn_loss_inputs = (anchor_list, valid_flag_list, cls_score, + bbox_pred, batch_gt_instances, batch_img_metas) + stage_loss = stage.loss_by_feat(*rpn_loss_inputs) + for name, value in stage_loss.items(): + losses['s{}.{}'.format(i, name)] = value + + # refine boxes + if i < self.num_stages - 1: + anchor_list = stage.refine_bboxes(anchor_list, bbox_pred, + batch_img_metas) + + return losses + + def loss_and_predict( + self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + proposal_cfg: Optional[ConfigDict] = None, + ) -> Tuple[dict, InstanceList]: + """Perform forward propagation of the head, then calculate loss and + predictions from the features and data samples. + + Args: + x (tuple[Tensor]): Features from FPN. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + proposal_cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + + Returns: + tuple: the return value is a tuple contains: + + - losses: (dict[str, Tensor]): A dictionary of loss components. + - predictions (list[:obj:`InstanceData`]): Detection + results of each image after the post process. + """ + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, _, batch_img_metas = outputs + + featmap_sizes = [featmap.size()[-2:] for featmap in x] + device = x[0].device + anchor_list, valid_flag_list = self.stages[0].get_anchors( + featmap_sizes, batch_img_metas, device=device) + + losses = dict() + + for i in range(self.num_stages): + stage = self.stages[i] + + if stage.adapt_cfg['type'] == 'offset': + offset_list = stage.anchor_offset(anchor_list, + stage.anchor_strides, + featmap_sizes) + else: + offset_list = None + x, cls_score, bbox_pred = stage(x, offset_list) + rpn_loss_inputs = (anchor_list, valid_flag_list, cls_score, + bbox_pred, batch_gt_instances, batch_img_metas) + stage_loss = stage.loss_by_feat(*rpn_loss_inputs) + for name, value in stage_loss.items(): + losses['s{}.{}'.format(i, name)] = value + + # refine boxes + if i < self.num_stages - 1: + anchor_list = stage.refine_bboxes(anchor_list, bbox_pred, + batch_img_metas) + + predictions = self.stages[-1].predict_by_feat( + anchor_list, + cls_score, + bbox_pred, + batch_img_metas=batch_img_metas, + cfg=proposal_cfg) + return losses, predictions + + def predict(self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the detection head and predict + detection results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + featmap_sizes = [featmap.size()[-2:] for featmap in x] + device = x[0].device + anchor_list, _ = self.stages[0].get_anchors( + featmap_sizes, batch_img_metas, device=device) + + for i in range(self.num_stages): + stage = self.stages[i] + if stage.adapt_cfg['type'] == 'offset': + offset_list = stage.anchor_offset(anchor_list, + stage.anchor_strides, + featmap_sizes) + else: + offset_list = None + x, cls_score, bbox_pred = stage(x, offset_list) + if i < self.num_stages - 1: + anchor_list = stage.refine_bboxes(anchor_list, bbox_pred, + batch_img_metas) + + predictions = self.stages[-1].predict_by_feat( + anchor_list, + cls_score, + bbox_pred, + batch_img_metas=batch_img_metas, + rescale=rescale) + return predictions diff --git a/mmdetection/mmdet/models/dense_heads/centernet_head.py b/mmdetection/mmdet/models/dense_heads/centernet_head.py new file mode 100644 index 00000000..09f3e599 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/centernet_head.py @@ -0,0 +1,447 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +from mmcv.ops import batched_nms +from mmengine.config import ConfigDict +from mmengine.model import bias_init_with_prob, normal_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList, OptMultiConfig) +from ..utils import (gaussian_radius, gen_gaussian_target, get_local_maximum, + get_topk_from_heatmap, multi_apply, + transpose_and_gather_feat) +from .base_dense_head import BaseDenseHead + + +@MODELS.register_module() +class CenterNetHead(BaseDenseHead): + """Objects as Points Head. CenterHead use center_point to indicate object's + position. Paper link + + Args: + in_channels (int): Number of channel in the input feature map. + feat_channels (int): Number of channel in the intermediate feature map. + num_classes (int): Number of categories excluding the background + category. + loss_center_heatmap (:obj:`ConfigDict` or dict): Config of center + heatmap loss. Defaults to + dict(type='GaussianFocalLoss', loss_weight=1.0) + loss_wh (:obj:`ConfigDict` or dict): Config of wh loss. Defaults to + dict(type='L1Loss', loss_weight=0.1). + loss_offset (:obj:`ConfigDict` or dict): Config of offset loss. + Defaults to dict(type='L1Loss', loss_weight=1.0). + train_cfg (:obj:`ConfigDict` or dict, optional): Training config. + Useless in CenterNet, but we keep this variable for + SingleStageDetector. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config + of CenterNet. + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`], optional): Initialization + config dict. + """ + + def __init__(self, + in_channels: int, + feat_channels: int, + num_classes: int, + loss_center_heatmap: ConfigType = dict( + type='GaussianFocalLoss', loss_weight=1.0), + loss_wh: ConfigType = dict(type='L1Loss', loss_weight=0.1), + loss_offset: ConfigType = dict( + type='L1Loss', loss_weight=1.0), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + self.num_classes = num_classes + self.heatmap_head = self._build_head(in_channels, feat_channels, + num_classes) + self.wh_head = self._build_head(in_channels, feat_channels, 2) + self.offset_head = self._build_head(in_channels, feat_channels, 2) + + self.loss_center_heatmap = MODELS.build(loss_center_heatmap) + self.loss_wh = MODELS.build(loss_wh) + self.loss_offset = MODELS.build(loss_offset) + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + self.fp16_enabled = False + + def _build_head(self, in_channels: int, feat_channels: int, + out_channels: int) -> nn.Sequential: + """Build head for each branch.""" + layer = nn.Sequential( + nn.Conv2d(in_channels, feat_channels, kernel_size=3, padding=1), + nn.ReLU(inplace=True), + nn.Conv2d(feat_channels, out_channels, kernel_size=1)) + return layer + + def init_weights(self) -> None: + """Initialize weights of the head.""" + bias_init = bias_init_with_prob(0.1) + self.heatmap_head[-1].bias.data.fill_(bias_init) + for head in [self.wh_head, self.offset_head]: + for m in head.modules(): + if isinstance(m, nn.Conv2d): + normal_init(m, std=0.001) + + def forward(self, x: Tuple[Tensor, ...]) -> Tuple[List[Tensor]]: + """Forward features. Notice CenterNet head does not use FPN. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + center_heatmap_preds (list[Tensor]): center predict heatmaps for + all levels, the channels number is num_classes. + wh_preds (list[Tensor]): wh predicts for all levels, the channels + number is 2. + offset_preds (list[Tensor]): offset predicts for all levels, the + channels number is 2. + """ + return multi_apply(self.forward_single, x) + + def forward_single(self, x: Tensor) -> Tuple[Tensor, ...]: + """Forward feature of a single level. + + Args: + x (Tensor): Feature of a single level. + + Returns: + center_heatmap_pred (Tensor): center predict heatmaps, the + channels number is num_classes. + wh_pred (Tensor): wh predicts, the channels number is 2. + offset_pred (Tensor): offset predicts, the channels number is 2. + """ + center_heatmap_pred = self.heatmap_head(x).sigmoid() + wh_pred = self.wh_head(x) + offset_pred = self.offset_head(x) + return center_heatmap_pred, wh_pred, offset_pred + + def loss_by_feat( + self, + center_heatmap_preds: List[Tensor], + wh_preds: List[Tensor], + offset_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Compute losses of the head. + + Args: + center_heatmap_preds (list[Tensor]): center predict heatmaps for + all levels with shape (B, num_classes, H, W). + wh_preds (list[Tensor]): wh predicts for all levels with + shape (B, 2, H, W). + offset_preds (list[Tensor]): offset predicts for all levels + with shape (B, 2, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: which has components below: + - loss_center_heatmap (Tensor): loss of center heatmap. + - loss_wh (Tensor): loss of hw heatmap + - loss_offset (Tensor): loss of offset heatmap. + """ + assert len(center_heatmap_preds) == len(wh_preds) == len( + offset_preds) == 1 + center_heatmap_pred = center_heatmap_preds[0] + wh_pred = wh_preds[0] + offset_pred = offset_preds[0] + + gt_bboxes = [ + gt_instances.bboxes for gt_instances in batch_gt_instances + ] + gt_labels = [ + gt_instances.labels for gt_instances in batch_gt_instances + ] + img_shape = batch_img_metas[0]['batch_input_shape'] + target_result, avg_factor = self.get_targets(gt_bboxes, gt_labels, + center_heatmap_pred.shape, + img_shape) + + center_heatmap_target = target_result['center_heatmap_target'] + wh_target = target_result['wh_target'] + offset_target = target_result['offset_target'] + wh_offset_target_weight = target_result['wh_offset_target_weight'] + + # Since the channel of wh_target and offset_target is 2, the avg_factor + # of loss_center_heatmap is always 1/2 of loss_wh and loss_offset. + loss_center_heatmap = self.loss_center_heatmap( + center_heatmap_pred, center_heatmap_target, avg_factor=avg_factor) + loss_wh = self.loss_wh( + wh_pred, + wh_target, + wh_offset_target_weight, + avg_factor=avg_factor * 2) + loss_offset = self.loss_offset( + offset_pred, + offset_target, + wh_offset_target_weight, + avg_factor=avg_factor * 2) + return dict( + loss_center_heatmap=loss_center_heatmap, + loss_wh=loss_wh, + loss_offset=loss_offset) + + def get_targets(self, gt_bboxes: List[Tensor], gt_labels: List[Tensor], + feat_shape: tuple, img_shape: tuple) -> Tuple[dict, int]: + """Compute regression and classification targets in multiple images. + + Args: + gt_bboxes (list[Tensor]): Ground truth bboxes for each image with + shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format. + gt_labels (list[Tensor]): class indices corresponding to each box. + feat_shape (tuple): feature map shape with value [B, _, H, W] + img_shape (tuple): image shape. + + Returns: + tuple[dict, float]: The float value is mean avg_factor, the dict + has components below: + - center_heatmap_target (Tensor): targets of center heatmap, \ + shape (B, num_classes, H, W). + - wh_target (Tensor): targets of wh predict, shape \ + (B, 2, H, W). + - offset_target (Tensor): targets of offset predict, shape \ + (B, 2, H, W). + - wh_offset_target_weight (Tensor): weights of wh and offset \ + predict, shape (B, 2, H, W). + """ + img_h, img_w = img_shape[:2] + bs, _, feat_h, feat_w = feat_shape + + width_ratio = float(feat_w / img_w) + height_ratio = float(feat_h / img_h) + + center_heatmap_target = gt_bboxes[-1].new_zeros( + [bs, self.num_classes, feat_h, feat_w]) + wh_target = gt_bboxes[-1].new_zeros([bs, 2, feat_h, feat_w]) + offset_target = gt_bboxes[-1].new_zeros([bs, 2, feat_h, feat_w]) + wh_offset_target_weight = gt_bboxes[-1].new_zeros( + [bs, 2, feat_h, feat_w]) + + for batch_id in range(bs): + gt_bbox = gt_bboxes[batch_id] + gt_label = gt_labels[batch_id] + center_x = (gt_bbox[:, [0]] + gt_bbox[:, [2]]) * width_ratio / 2 + center_y = (gt_bbox[:, [1]] + gt_bbox[:, [3]]) * height_ratio / 2 + gt_centers = torch.cat((center_x, center_y), dim=1) + + for j, ct in enumerate(gt_centers): + ctx_int, cty_int = ct.int() + ctx, cty = ct + scale_box_h = (gt_bbox[j][3] - gt_bbox[j][1]) * height_ratio + scale_box_w = (gt_bbox[j][2] - gt_bbox[j][0]) * width_ratio + radius = gaussian_radius([scale_box_h, scale_box_w], + min_overlap=0.3) + radius = max(0, int(radius)) + ind = gt_label[j] + gen_gaussian_target(center_heatmap_target[batch_id, ind], + [ctx_int, cty_int], radius) + + wh_target[batch_id, 0, cty_int, ctx_int] = scale_box_w + wh_target[batch_id, 1, cty_int, ctx_int] = scale_box_h + + offset_target[batch_id, 0, cty_int, ctx_int] = ctx - ctx_int + offset_target[batch_id, 1, cty_int, ctx_int] = cty - cty_int + + wh_offset_target_weight[batch_id, :, cty_int, ctx_int] = 1 + + avg_factor = max(1, center_heatmap_target.eq(1).sum()) + target_result = dict( + center_heatmap_target=center_heatmap_target, + wh_target=wh_target, + offset_target=offset_target, + wh_offset_target_weight=wh_offset_target_weight) + return target_result, avg_factor + + def predict_by_feat(self, + center_heatmap_preds: List[Tensor], + wh_preds: List[Tensor], + offset_preds: List[Tensor], + batch_img_metas: Optional[List[dict]] = None, + rescale: bool = True, + with_nms: bool = False) -> InstanceList: + """Transform network output for a batch into bbox predictions. + + Args: + center_heatmap_preds (list[Tensor]): Center predict heatmaps for + all levels with shape (B, num_classes, H, W). + wh_preds (list[Tensor]): WH predicts for all levels with + shape (B, 2, H, W). + offset_preds (list[Tensor]): Offset predicts for all levels + with shape (B, 2, H, W). + batch_img_metas (list[dict], optional): Batch image meta info. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to True. + with_nms (bool): If True, do nms before return boxes. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Instance segmentation + results of each image after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(center_heatmap_preds) == len(wh_preds) == len( + offset_preds) == 1 + result_list = [] + for img_id in range(len(batch_img_metas)): + result_list.append( + self._predict_by_feat_single( + center_heatmap_preds[0][img_id:img_id + 1, ...], + wh_preds[0][img_id:img_id + 1, ...], + offset_preds[0][img_id:img_id + 1, ...], + batch_img_metas[img_id], + rescale=rescale, + with_nms=with_nms)) + return result_list + + def _predict_by_feat_single(self, + center_heatmap_pred: Tensor, + wh_pred: Tensor, + offset_pred: Tensor, + img_meta: dict, + rescale: bool = True, + with_nms: bool = False) -> InstanceData: + """Transform outputs of a single image into bbox results. + + Args: + center_heatmap_pred (Tensor): Center heatmap for current level with + shape (1, num_classes, H, W). + wh_pred (Tensor): WH heatmap for current level with shape + (1, num_classes, H, W). + offset_pred (Tensor): Offset for current level with shape + (1, corner_offset_channels, H, W). + img_meta (dict): Meta information of current image, e.g., + image size, scaling factor, etc. + rescale (bool): If True, return boxes in original image space. + Defaults to True. + with_nms (bool): If True, do nms before return boxes. + Defaults to False. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + batch_det_bboxes, batch_labels = self._decode_heatmap( + center_heatmap_pred, + wh_pred, + offset_pred, + img_meta['batch_input_shape'], + k=self.test_cfg.topk, + kernel=self.test_cfg.local_maximum_kernel) + + det_bboxes = batch_det_bboxes.view([-1, 5]) + det_labels = batch_labels.view(-1) + + batch_border = det_bboxes.new_tensor(img_meta['border'])[..., + [2, 0, 2, 0]] + det_bboxes[..., :4] -= batch_border + + if rescale and 'scale_factor' in img_meta: + det_bboxes[..., :4] /= det_bboxes.new_tensor( + img_meta['scale_factor']).repeat((1, 2)) + + if with_nms: + det_bboxes, det_labels = self._bboxes_nms(det_bboxes, det_labels, + self.test_cfg) + results = InstanceData() + results.bboxes = det_bboxes[..., :4] + results.scores = det_bboxes[..., 4] + results.labels = det_labels + return results + + def _decode_heatmap(self, + center_heatmap_pred: Tensor, + wh_pred: Tensor, + offset_pred: Tensor, + img_shape: tuple, + k: int = 100, + kernel: int = 3) -> Tuple[Tensor, Tensor]: + """Transform outputs into detections raw bbox prediction. + + Args: + center_heatmap_pred (Tensor): center predict heatmap, + shape (B, num_classes, H, W). + wh_pred (Tensor): wh predict, shape (B, 2, H, W). + offset_pred (Tensor): offset predict, shape (B, 2, H, W). + img_shape (tuple): image shape in hw format. + k (int): Get top k center keypoints from heatmap. Defaults to 100. + kernel (int): Max pooling kernel for extract local maximum pixels. + Defaults to 3. + + Returns: + tuple[Tensor]: Decoded output of CenterNetHead, containing + the following Tensors: + + - batch_bboxes (Tensor): Coords of each box with shape (B, k, 5) + - batch_topk_labels (Tensor): Categories of each box with \ + shape (B, k) + """ + height, width = center_heatmap_pred.shape[2:] + inp_h, inp_w = img_shape + + center_heatmap_pred = get_local_maximum( + center_heatmap_pred, kernel=kernel) + + *batch_dets, topk_ys, topk_xs = get_topk_from_heatmap( + center_heatmap_pred, k=k) + batch_scores, batch_index, batch_topk_labels = batch_dets + + wh = transpose_and_gather_feat(wh_pred, batch_index) + offset = transpose_and_gather_feat(offset_pred, batch_index) + topk_xs = topk_xs + offset[..., 0] + topk_ys = topk_ys + offset[..., 1] + tl_x = (topk_xs - wh[..., 0] / 2) * (inp_w / width) + tl_y = (topk_ys - wh[..., 1] / 2) * (inp_h / height) + br_x = (topk_xs + wh[..., 0] / 2) * (inp_w / width) + br_y = (topk_ys + wh[..., 1] / 2) * (inp_h / height) + + batch_bboxes = torch.stack([tl_x, tl_y, br_x, br_y], dim=2) + batch_bboxes = torch.cat((batch_bboxes, batch_scores[..., None]), + dim=-1) + return batch_bboxes, batch_topk_labels + + def _bboxes_nms(self, bboxes: Tensor, labels: Tensor, + cfg: ConfigDict) -> Tuple[Tensor, Tensor]: + """bboxes nms.""" + if labels.numel() > 0: + max_num = cfg.max_per_img + bboxes, keep = batched_nms(bboxes[:, :4], bboxes[:, + -1].contiguous(), + labels, cfg.nms) + if max_num > 0: + bboxes = bboxes[:max_num] + labels = labels[keep][:max_num] + + return bboxes, labels diff --git a/mmdetection/mmdet/models/dense_heads/centernet_update_head.py b/mmdetection/mmdet/models/dense_heads/centernet_update_head.py new file mode 100644 index 00000000..00cfcb89 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/centernet_update_head.py @@ -0,0 +1,624 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Sequence, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import Scale +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures.bbox import bbox2distance +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList, reduce_mean) +from ..utils import multi_apply +from .anchor_free_head import AnchorFreeHead + +INF = 1000000000 +RangeType = Sequence[Tuple[int, int]] + + +def _transpose(tensor_list: List[Tensor], + num_point_list: list) -> List[Tensor]: + """This function is used to transpose image first tensors to level first + ones.""" + for img_idx in range(len(tensor_list)): + tensor_list[img_idx] = torch.split( + tensor_list[img_idx], num_point_list, dim=0) + + tensors_level_first = [] + for targets_per_level in zip(*tensor_list): + tensors_level_first.append(torch.cat(targets_per_level, dim=0)) + return tensors_level_first + + +@MODELS.register_module() +class CenterNetUpdateHead(AnchorFreeHead): + """CenterNetUpdateHead is an improved version of CenterNet in CenterNet2. + Paper link ``_. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channel in the input feature map. + regress_ranges (Sequence[Tuple[int, int]]): Regress range of multiple + level points. + hm_min_radius (int): Heatmap target minimum radius of cls branch. + Defaults to 4. + hm_min_overlap (float): Heatmap target minimum overlap of cls branch. + Defaults to 0.8. + more_pos_thresh (float): The filtering threshold when the cls branch + adds more positive samples. Defaults to 0.2. + more_pos_topk (int): The maximum number of additional positive samples + added to each gt. Defaults to 9. + soft_weight_on_reg (bool): Whether to use the soft target of the + cls branch as the soft weight of the bbox branch. + Defaults to False. + loss_cls (:obj:`ConfigDict` or dict): Config of cls loss. Defaults to + dict(type='GaussianFocalLoss', loss_weight=1.0) + loss_bbox (:obj:`ConfigDict` or dict): Config of bbox loss. Defaults to + dict(type='GIoULoss', loss_weight=2.0). + norm_cfg (:obj:`ConfigDict` or dict, optional): dictionary to construct + and config norm layer. Defaults to + ``norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)``. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config. + Unused in CenterNet. Reserved for compatibility with + SingleStageDetector. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config + of CenterNet. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + regress_ranges: RangeType = ((0, 80), (64, 160), (128, 320), + (256, 640), (512, INF)), + hm_min_radius: int = 4, + hm_min_overlap: float = 0.8, + more_pos_thresh: float = 0.2, + more_pos_topk: int = 9, + soft_weight_on_reg: bool = False, + loss_cls: ConfigType = dict( + type='GaussianFocalLoss', + pos_weight=0.25, + neg_weight=0.75, + loss_weight=1.0), + loss_bbox: ConfigType = dict( + type='GIoULoss', loss_weight=2.0), + norm_cfg: OptConfigType = dict( + type='GN', num_groups=32, requires_grad=True), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + **kwargs) -> None: + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + loss_cls=loss_cls, + loss_bbox=loss_bbox, + norm_cfg=norm_cfg, + train_cfg=train_cfg, + test_cfg=test_cfg, + **kwargs) + self.soft_weight_on_reg = soft_weight_on_reg + self.hm_min_radius = hm_min_radius + self.more_pos_thresh = more_pos_thresh + self.more_pos_topk = more_pos_topk + self.delta = (1 - hm_min_overlap) / (1 + hm_min_overlap) + self.sigmoid_clamp = 0.0001 + + # GaussianFocalLoss must be sigmoid mode + self.use_sigmoid_cls = True + self.cls_out_channels = num_classes + + self.regress_ranges = regress_ranges + self.scales = nn.ModuleList([Scale(1.0) for _ in self.strides]) + + def _init_predictor(self) -> None: + """Initialize predictor layers of the head.""" + self.conv_cls = nn.Conv2d( + self.feat_channels, self.num_classes, 3, padding=1) + self.conv_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1) + + def forward(self, x: Tuple[Tensor]) -> Tuple[List[Tensor], List[Tensor]]: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of each level outputs. + + - cls_scores (list[Tensor]): Box scores for each scale level, \ + each is a 4D-tensor, the channel number is num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for each \ + scale level, each is a 4D-tensor, the channel number is 4. + """ + return multi_apply(self.forward_single, x, self.scales, self.strides) + + def forward_single(self, x: Tensor, scale: Scale, + stride: int) -> Tuple[Tensor, Tensor]: + """Forward features of a single scale level. + + Args: + x (Tensor): FPN feature maps of the specified stride. + scale (:obj:`mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + stride (int): The corresponding stride for feature maps. + + Returns: + tuple: scores for each class, bbox predictions of + input feature maps. + """ + cls_score, bbox_pred, _, _ = super().forward_single(x) + # scale the bbox_pred of different level + # float to avoid overflow when enabling FP16 + bbox_pred = scale(bbox_pred).float() + # bbox_pred needed for gradient computation has been modified + # by F.relu(bbox_pred) when run with PyTorch 1.10. So replace + # F.relu(bbox_pred) with bbox_pred.clamp(min=0) + bbox_pred = bbox_pred.clamp(min=0) + if not self.training: + bbox_pred *= stride + return cls_score, bbox_pred + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level, + each is a 4D-tensor, the channel number is num_classes. + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level, each is a 4D-tensor, the channel number is 4. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + num_imgs = cls_scores[0].size(0) + assert len(cls_scores) == len(bbox_preds) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + all_level_points = self.prior_generator.grid_priors( + featmap_sizes, + dtype=bbox_preds[0].dtype, + device=bbox_preds[0].device) + + # 1 flatten outputs + flatten_cls_scores = [ + cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels) + for cls_score in cls_scores + ] + flatten_bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4) + for bbox_pred in bbox_preds + ] + flatten_cls_scores = torch.cat(flatten_cls_scores) + flatten_bbox_preds = torch.cat(flatten_bbox_preds) + + # repeat points to align with bbox_preds + flatten_points = torch.cat( + [points.repeat(num_imgs, 1) for points in all_level_points]) + + assert (torch.isfinite(flatten_bbox_preds).all().item()) + + # 2 calc reg and cls branch targets + cls_targets, bbox_targets = self.get_targets(all_level_points, + batch_gt_instances) + + # 3 add more pos index for cls branch + featmap_sizes = flatten_points.new_tensor(featmap_sizes) + pos_inds, cls_labels = self.add_cls_pos_inds(flatten_points, + flatten_bbox_preds, + featmap_sizes, + batch_gt_instances) + + # 4 calc cls loss + if pos_inds is None: + # num_gts=0 + num_pos_cls = bbox_preds[0].new_tensor(0, dtype=torch.float) + else: + num_pos_cls = bbox_preds[0].new_tensor( + len(pos_inds), dtype=torch.float) + num_pos_cls = max(reduce_mean(num_pos_cls), 1.0) + flatten_cls_scores = flatten_cls_scores.sigmoid().clamp( + min=self.sigmoid_clamp, max=1 - self.sigmoid_clamp) + cls_loss = self.loss_cls( + flatten_cls_scores, + cls_targets, + pos_inds=pos_inds, + pos_labels=cls_labels, + avg_factor=num_pos_cls) + + # 5 calc reg loss + pos_bbox_inds = torch.nonzero( + bbox_targets.max(dim=1)[0] >= 0).squeeze(1) + pos_bbox_preds = flatten_bbox_preds[pos_bbox_inds] + pos_bbox_targets = bbox_targets[pos_bbox_inds] + + bbox_weight_map = cls_targets.max(dim=1)[0] + bbox_weight_map = bbox_weight_map[pos_bbox_inds] + bbox_weight_map = bbox_weight_map if self.soft_weight_on_reg \ + else torch.ones_like(bbox_weight_map) + num_pos_bbox = max(reduce_mean(bbox_weight_map.sum()), 1.0) + + if len(pos_bbox_inds) > 0: + pos_points = flatten_points[pos_bbox_inds] + pos_decoded_bbox_preds = self.bbox_coder.decode( + pos_points, pos_bbox_preds) + pos_decoded_target_preds = self.bbox_coder.decode( + pos_points, pos_bbox_targets) + bbox_loss = self.loss_bbox( + pos_decoded_bbox_preds, + pos_decoded_target_preds, + weight=bbox_weight_map, + avg_factor=num_pos_bbox) + else: + bbox_loss = flatten_bbox_preds.sum() * 0 + + return dict(loss_cls=cls_loss, loss_bbox=bbox_loss) + + def get_targets( + self, + points: List[Tensor], + batch_gt_instances: InstanceList, + ) -> Tuple[Tensor, Tensor]: + """Compute classification and bbox targets for points in multiple + images. + + Args: + points (list[Tensor]): Points of each fpn level, each has shape + (num_points, 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + + Returns: + tuple: Targets of each level. + + - concat_lvl_labels (Tensor): Labels of all level and batch. + - concat_lvl_bbox_targets (Tensor): BBox targets of all \ + level and batch. + """ + assert len(points) == len(self.regress_ranges) + + num_levels = len(points) + # the number of points per img, per lvl + num_points = [center.size(0) for center in points] + + # expand regress ranges to align with points + expanded_regress_ranges = [ + points[i].new_tensor(self.regress_ranges[i])[None].expand_as( + points[i]) for i in range(num_levels) + ] + # concat all levels points and regress ranges + concat_regress_ranges = torch.cat(expanded_regress_ranges, dim=0) + concat_points = torch.cat(points, dim=0) + concat_strides = torch.cat([ + concat_points.new_ones(num_points[i]) * self.strides[i] + for i in range(num_levels) + ]) + + # get labels and bbox_targets of each image + cls_targets_list, bbox_targets_list = multi_apply( + self._get_targets_single, + batch_gt_instances, + points=concat_points, + regress_ranges=concat_regress_ranges, + strides=concat_strides) + + bbox_targets_list = _transpose(bbox_targets_list, num_points) + cls_targets_list = _transpose(cls_targets_list, num_points) + concat_lvl_bbox_targets = torch.cat(bbox_targets_list, 0) + concat_lvl_cls_targets = torch.cat(cls_targets_list, dim=0) + return concat_lvl_cls_targets, concat_lvl_bbox_targets + + def _get_targets_single(self, gt_instances: InstanceData, points: Tensor, + regress_ranges: Tensor, + strides: Tensor) -> Tuple[Tensor, Tensor]: + """Compute classification and bbox targets for a single image.""" + num_points = points.size(0) + num_gts = len(gt_instances) + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + + if num_gts == 0: + return gt_labels.new_full((num_points, + self.num_classes), + self.num_classes), \ + gt_bboxes.new_full((num_points, 4), -1) + + # Calculate the regression tblr target corresponding to all points + points = points[:, None].expand(num_points, num_gts, 2) + gt_bboxes = gt_bboxes[None].expand(num_points, num_gts, 4) + strides = strides[:, None, None].expand(num_points, num_gts, 2) + + bbox_target = bbox2distance(points, gt_bboxes) # M x N x 4 + + # condition1: inside a gt bbox + inside_gt_bbox_mask = bbox_target.min(dim=2)[0] > 0 # M x N + + # condition2: Calculate the nearest points from + # the upper, lower, left and right ranges from + # the center of the gt bbox + centers = ((gt_bboxes[..., [0, 1]] + gt_bboxes[..., [2, 3]]) / 2) + centers_discret = ((centers / strides).int() * strides).float() + \ + strides / 2 + + centers_discret_dist = points - centers_discret + dist_x = centers_discret_dist[..., 0].abs() + dist_y = centers_discret_dist[..., 1].abs() + inside_gt_center3x3_mask = (dist_x <= strides[..., 0]) & \ + (dist_y <= strides[..., 0]) + + # condition3: limit the regression range for each location + bbox_target_wh = bbox_target[..., :2] + bbox_target[..., 2:] + crit = (bbox_target_wh**2).sum(dim=2)**0.5 / 2 + inside_fpn_level_mask = (crit >= regress_ranges[:, [0]]) & \ + (crit <= regress_ranges[:, [1]]) + bbox_target_mask = inside_gt_bbox_mask & \ + inside_gt_center3x3_mask & \ + inside_fpn_level_mask + + # Calculate the distance weight map + gt_center_peak_mask = ((centers_discret_dist**2).sum(dim=2) == 0) + weighted_dist = ((points - centers)**2).sum(dim=2) # M x N + weighted_dist[gt_center_peak_mask] = 0 + + areas = (gt_bboxes[..., 2] - gt_bboxes[..., 0]) * ( + gt_bboxes[..., 3] - gt_bboxes[..., 1]) + radius = self.delta**2 * 2 * areas + radius = torch.clamp(radius, min=self.hm_min_radius**2) + weighted_dist = weighted_dist / radius + + # Calculate bbox_target + bbox_weighted_dist = weighted_dist.clone() + bbox_weighted_dist[bbox_target_mask == 0] = INF * 1.0 + min_dist, min_inds = bbox_weighted_dist.min(dim=1) + bbox_target = bbox_target[range(len(bbox_target)), + min_inds] # M x N x 4 --> M x 4 + bbox_target[min_dist == INF] = -INF + + # Convert to feature map scale + bbox_target /= strides[:, 0, :].repeat(1, 2) + + # Calculate cls_target + cls_target = self._create_heatmaps_from_dist(weighted_dist, gt_labels) + + return cls_target, bbox_target + + @torch.no_grad() + def add_cls_pos_inds( + self, flatten_points: Tensor, flatten_bbox_preds: Tensor, + featmap_sizes: Tensor, batch_gt_instances: InstanceList + ) -> Tuple[Optional[Tensor], Optional[Tensor]]: + """Provide additional adaptive positive samples to the classification + branch. + + Args: + flatten_points (Tensor): The point after flatten, including + batch image and all levels. The shape is (N, 2). + flatten_bbox_preds (Tensor): The bbox predicts after flatten, + including batch image and all levels. The shape is (N, 4). + featmap_sizes (Tensor): Feature map size of all layers. + The shape is (5, 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + + Returns: + tuple: + + - pos_inds (Tensor): Adaptively selected positive sample index. + - cls_labels (Tensor): Corresponding positive class label. + """ + outputs = self._get_center3x3_region_index_targets( + batch_gt_instances, featmap_sizes) + cls_labels, fpn_level_masks, center3x3_inds, \ + center3x3_bbox_targets, center3x3_masks = outputs + + num_gts, total_level, K = cls_labels.shape[0], len( + self.strides), center3x3_masks.shape[-1] + + if num_gts == 0: + return None, None + + # The out-of-bounds index is forcibly set to 0 + # to prevent loss calculation errors + center3x3_inds[center3x3_masks == 0] = 0 + reg_pred_center3x3 = flatten_bbox_preds[center3x3_inds] + center3x3_points = flatten_points[center3x3_inds].view(-1, 2) + + center3x3_bbox_targets_expand = center3x3_bbox_targets.view( + -1, 4).clamp(min=0) + + pos_decoded_bbox_preds = self.bbox_coder.decode( + center3x3_points, reg_pred_center3x3.view(-1, 4)) + pos_decoded_target_preds = self.bbox_coder.decode( + center3x3_points, center3x3_bbox_targets_expand) + center3x3_bbox_loss = self.loss_bbox( + pos_decoded_bbox_preds, + pos_decoded_target_preds, + None, + reduction_override='none').view(num_gts, total_level, + K) / self.loss_bbox.loss_weight + + # Invalid index Loss set to infinity + center3x3_bbox_loss[center3x3_masks == 0] = INF + + # 4 is the center point of the sampled 9 points, the center point + # of gt bbox after discretization. + # The center point of gt bbox after discretization + # must be a positive sample, so we force its loss to be set to 0. + center3x3_bbox_loss.view(-1, K)[fpn_level_masks.view(-1), 4] = 0 + center3x3_bbox_loss = center3x3_bbox_loss.view(num_gts, -1) + + loss_thr = torch.kthvalue( + center3x3_bbox_loss, self.more_pos_topk, dim=1)[0] + + loss_thr[loss_thr > self.more_pos_thresh] = self.more_pos_thresh + new_pos = center3x3_bbox_loss < loss_thr.view(num_gts, 1) + pos_inds = center3x3_inds.view(num_gts, -1)[new_pos] + cls_labels = cls_labels.view(num_gts, + 1).expand(num_gts, + total_level * K)[new_pos] + return pos_inds, cls_labels + + def _create_heatmaps_from_dist(self, weighted_dist: Tensor, + cls_labels: Tensor) -> Tensor: + """Generate heatmaps of classification branch based on weighted + distance map.""" + heatmaps = weighted_dist.new_zeros( + (weighted_dist.shape[0], self.num_classes)) + for c in range(self.num_classes): + inds = (cls_labels == c) # N + if inds.int().sum() == 0: + continue + heatmaps[:, c] = torch.exp(-weighted_dist[:, inds].min(dim=1)[0]) + zeros = heatmaps[:, c] < 1e-4 + heatmaps[zeros, c] = 0 + return heatmaps + + def _get_center3x3_region_index_targets(self, + bacth_gt_instances: InstanceList, + shapes_per_level: Tensor) -> tuple: + """Get the center (and the 3x3 region near center) locations and target + of each objects.""" + cls_labels = [] + inside_fpn_level_masks = [] + center3x3_inds = [] + center3x3_masks = [] + center3x3_bbox_targets = [] + + total_levels = len(self.strides) + batch = len(bacth_gt_instances) + + shapes_per_level = shapes_per_level.long() + area_per_level = (shapes_per_level[:, 0] * shapes_per_level[:, 1]) + + # Select a total of 9 positions of 3x3 in the center of the gt bbox + # as candidate positive samples + K = 9 + dx = shapes_per_level.new_tensor([-1, 0, 1, -1, 0, 1, -1, 0, + 1]).view(1, 1, K) + dy = shapes_per_level.new_tensor([-1, -1, -1, 0, 0, 0, 1, 1, + 1]).view(1, 1, K) + + regress_ranges = shapes_per_level.new_tensor(self.regress_ranges).view( + len(self.regress_ranges), 2) # L x 2 + strides = shapes_per_level.new_tensor(self.strides) + + start_coord_pre_level = [] + _start = 0 + for level in range(total_levels): + start_coord_pre_level.append(_start) + _start = _start + batch * area_per_level[level] + start_coord_pre_level = shapes_per_level.new_tensor( + start_coord_pre_level).view(1, total_levels, 1) + area_per_level = area_per_level.view(1, total_levels, 1) + + for im_i in range(batch): + gt_instance = bacth_gt_instances[im_i] + gt_bboxes = gt_instance.bboxes + gt_labels = gt_instance.labels + num_gts = gt_bboxes.shape[0] + if num_gts == 0: + continue + + cls_labels.append(gt_labels) + + gt_bboxes = gt_bboxes[:, None].expand(num_gts, total_levels, 4) + expanded_strides = strides[None, :, + None].expand(num_gts, total_levels, 2) + expanded_regress_ranges = regress_ranges[None].expand( + num_gts, total_levels, 2) + expanded_shapes_per_level = shapes_per_level[None].expand( + num_gts, total_levels, 2) + + # calc reg_target + centers = ((gt_bboxes[..., [0, 1]] + gt_bboxes[..., [2, 3]]) / 2) + centers_inds = (centers / expanded_strides).long() + centers_discret = centers_inds * expanded_strides \ + + expanded_strides // 2 + + bbox_target = bbox2distance(centers_discret, + gt_bboxes) # M x N x 4 + + # calc inside_fpn_level_mask + bbox_target_wh = bbox_target[..., :2] + bbox_target[..., 2:] + crit = (bbox_target_wh**2).sum(dim=2)**0.5 / 2 + inside_fpn_level_mask = \ + (crit >= expanded_regress_ranges[..., 0]) & \ + (crit <= expanded_regress_ranges[..., 1]) + + inside_gt_bbox_mask = bbox_target.min(dim=2)[0] >= 0 + inside_fpn_level_mask = inside_gt_bbox_mask & inside_fpn_level_mask + inside_fpn_level_masks.append(inside_fpn_level_mask) + + # calc center3x3_ind and mask + expand_ws = expanded_shapes_per_level[..., 1:2].expand( + num_gts, total_levels, K) + expand_hs = expanded_shapes_per_level[..., 0:1].expand( + num_gts, total_levels, K) + centers_inds_x = centers_inds[..., 0:1] + centers_inds_y = centers_inds[..., 1:2] + + center3x3_idx = start_coord_pre_level + \ + im_i * area_per_level + \ + (centers_inds_y + dy) * expand_ws + \ + (centers_inds_x + dx) + center3x3_mask = \ + ((centers_inds_y + dy) < expand_hs) & \ + ((centers_inds_y + dy) >= 0) & \ + ((centers_inds_x + dx) < expand_ws) & \ + ((centers_inds_x + dx) >= 0) + + # recalc center3x3 region reg target + bbox_target = bbox_target / expanded_strides.repeat(1, 1, 2) + center3x3_bbox_target = bbox_target[..., None, :].expand( + num_gts, total_levels, K, 4).clone() + center3x3_bbox_target[..., 0] += dx + center3x3_bbox_target[..., 1] += dy + center3x3_bbox_target[..., 2] -= dx + center3x3_bbox_target[..., 3] -= dy + # update center3x3_mask + center3x3_mask = center3x3_mask & ( + center3x3_bbox_target.min(dim=3)[0] >= 0) # n x L x K + + center3x3_inds.append(center3x3_idx) + center3x3_masks.append(center3x3_mask) + center3x3_bbox_targets.append(center3x3_bbox_target) + + if len(inside_fpn_level_masks) > 0: + cls_labels = torch.cat(cls_labels, dim=0) + inside_fpn_level_masks = torch.cat(inside_fpn_level_masks, dim=0) + center3x3_inds = torch.cat(center3x3_inds, dim=0).long() + center3x3_bbox_targets = torch.cat(center3x3_bbox_targets, dim=0) + center3x3_masks = torch.cat(center3x3_masks, dim=0) + else: + cls_labels = shapes_per_level.new_zeros(0).long() + inside_fpn_level_masks = shapes_per_level.new_zeros( + (0, total_levels)).bool() + center3x3_inds = shapes_per_level.new_zeros( + (0, total_levels, K)).long() + center3x3_bbox_targets = shapes_per_level.new_zeros( + (0, total_levels, K, 4)).float() + center3x3_masks = shapes_per_level.new_zeros( + (0, total_levels, K)).bool() + return cls_labels, inside_fpn_level_masks, center3x3_inds, \ + center3x3_bbox_targets, center3x3_masks diff --git a/mmdetection/mmdet/models/dense_heads/centripetal_head.py b/mmdetection/mmdet/models/dense_heads/centripetal_head.py new file mode 100644 index 00000000..18f6601f --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/centripetal_head.py @@ -0,0 +1,459 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmcv.ops import DeformConv2d +from mmengine.model import normal_init +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import (ConfigType, InstanceList, OptInstanceList, + OptMultiConfig) +from ..utils import multi_apply +from .corner_head import CornerHead + + +@MODELS.register_module() +class CentripetalHead(CornerHead): + """Head of CentripetalNet: Pursuing High-quality Keypoint Pairs for Object + Detection. + + CentripetalHead inherits from :class:`CornerHead`. It removes the + embedding branch and adds guiding shift and centripetal shift branches. + More details can be found in the `paper + `_ . + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + num_feat_levels (int): Levels of feature from the previous module. + 2 for HourglassNet-104 and 1 for HourglassNet-52. HourglassNet-104 + outputs the final feature and intermediate supervision feature and + HourglassNet-52 only outputs the final feature. Defaults to 2. + corner_emb_channels (int): Channel of embedding vector. Defaults to 1. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config. + Useless in CornerHead, but we keep this variable for + SingleStageDetector. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + CornerHead. + loss_heatmap (:obj:`ConfigDict` or dict): Config of corner heatmap + loss. Defaults to GaussianFocalLoss. + loss_embedding (:obj:`ConfigDict` or dict): Config of corner embedding + loss. Defaults to AssociativeEmbeddingLoss. + loss_offset (:obj:`ConfigDict` or dict): Config of corner offset loss. + Defaults to SmoothL1Loss. + loss_guiding_shift (:obj:`ConfigDict` or dict): Config of + guiding shift loss. Defaults to SmoothL1Loss. + loss_centripetal_shift (:obj:`ConfigDict` or dict): Config of + centripetal shift loss. Defaults to SmoothL1Loss. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. + """ + + def __init__(self, + *args, + centripetal_shift_channels: int = 2, + guiding_shift_channels: int = 2, + feat_adaption_conv_kernel: int = 3, + loss_guiding_shift: ConfigType = dict( + type='SmoothL1Loss', beta=1.0, loss_weight=0.05), + loss_centripetal_shift: ConfigType = dict( + type='SmoothL1Loss', beta=1.0, loss_weight=1), + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + assert centripetal_shift_channels == 2, ( + 'CentripetalHead only support centripetal_shift_channels == 2') + self.centripetal_shift_channels = centripetal_shift_channels + assert guiding_shift_channels == 2, ( + 'CentripetalHead only support guiding_shift_channels == 2') + self.guiding_shift_channels = guiding_shift_channels + self.feat_adaption_conv_kernel = feat_adaption_conv_kernel + super().__init__(*args, init_cfg=init_cfg, **kwargs) + self.loss_guiding_shift = MODELS.build(loss_guiding_shift) + self.loss_centripetal_shift = MODELS.build(loss_centripetal_shift) + + def _init_centripetal_layers(self) -> None: + """Initialize centripetal layers. + + Including feature adaption deform convs (feat_adaption), deform offset + prediction convs (dcn_off), guiding shift (guiding_shift) and + centripetal shift ( centripetal_shift). Each branch has two parts: + prefix `tl_` for top-left and `br_` for bottom-right. + """ + self.tl_feat_adaption = nn.ModuleList() + self.br_feat_adaption = nn.ModuleList() + self.tl_dcn_offset = nn.ModuleList() + self.br_dcn_offset = nn.ModuleList() + self.tl_guiding_shift = nn.ModuleList() + self.br_guiding_shift = nn.ModuleList() + self.tl_centripetal_shift = nn.ModuleList() + self.br_centripetal_shift = nn.ModuleList() + + for _ in range(self.num_feat_levels): + self.tl_feat_adaption.append( + DeformConv2d(self.in_channels, self.in_channels, + self.feat_adaption_conv_kernel, 1, 1)) + self.br_feat_adaption.append( + DeformConv2d(self.in_channels, self.in_channels, + self.feat_adaption_conv_kernel, 1, 1)) + + self.tl_guiding_shift.append( + self._make_layers( + out_channels=self.guiding_shift_channels, + in_channels=self.in_channels)) + self.br_guiding_shift.append( + self._make_layers( + out_channels=self.guiding_shift_channels, + in_channels=self.in_channels)) + + self.tl_dcn_offset.append( + ConvModule( + self.guiding_shift_channels, + self.feat_adaption_conv_kernel**2 * + self.guiding_shift_channels, + 1, + bias=False, + act_cfg=None)) + self.br_dcn_offset.append( + ConvModule( + self.guiding_shift_channels, + self.feat_adaption_conv_kernel**2 * + self.guiding_shift_channels, + 1, + bias=False, + act_cfg=None)) + + self.tl_centripetal_shift.append( + self._make_layers( + out_channels=self.centripetal_shift_channels, + in_channels=self.in_channels)) + self.br_centripetal_shift.append( + self._make_layers( + out_channels=self.centripetal_shift_channels, + in_channels=self.in_channels)) + + def _init_layers(self) -> None: + """Initialize layers for CentripetalHead. + + Including two parts: CornerHead layers and CentripetalHead layers + """ + super()._init_layers() # using _init_layers in CornerHead + self._init_centripetal_layers() + + def init_weights(self) -> None: + super().init_weights() + for i in range(self.num_feat_levels): + normal_init(self.tl_feat_adaption[i], std=0.01) + normal_init(self.br_feat_adaption[i], std=0.01) + normal_init(self.tl_dcn_offset[i].conv, std=0.1) + normal_init(self.br_dcn_offset[i].conv, std=0.1) + _ = [x.conv.reset_parameters() for x in self.tl_guiding_shift[i]] + _ = [x.conv.reset_parameters() for x in self.br_guiding_shift[i]] + _ = [ + x.conv.reset_parameters() for x in self.tl_centripetal_shift[i] + ] + _ = [ + x.conv.reset_parameters() for x in self.br_centripetal_shift[i] + ] + + def forward_single(self, x: Tensor, lvl_ind: int) -> List[Tensor]: + """Forward feature of a single level. + + Args: + x (Tensor): Feature of a single level. + lvl_ind (int): Level index of current feature. + + Returns: + tuple[Tensor]: A tuple of CentripetalHead's output for current + feature level. Containing the following Tensors: + + - tl_heat (Tensor): Predicted top-left corner heatmap. + - br_heat (Tensor): Predicted bottom-right corner heatmap. + - tl_off (Tensor): Predicted top-left offset heatmap. + - br_off (Tensor): Predicted bottom-right offset heatmap. + - tl_guiding_shift (Tensor): Predicted top-left guiding shift + heatmap. + - br_guiding_shift (Tensor): Predicted bottom-right guiding + shift heatmap. + - tl_centripetal_shift (Tensor): Predicted top-left centripetal + shift heatmap. + - br_centripetal_shift (Tensor): Predicted bottom-right + centripetal shift heatmap. + """ + tl_heat, br_heat, _, _, tl_off, br_off, tl_pool, br_pool = super( + ).forward_single( + x, lvl_ind, return_pool=True) + + tl_guiding_shift = self.tl_guiding_shift[lvl_ind](tl_pool) + br_guiding_shift = self.br_guiding_shift[lvl_ind](br_pool) + + tl_dcn_offset = self.tl_dcn_offset[lvl_ind](tl_guiding_shift.detach()) + br_dcn_offset = self.br_dcn_offset[lvl_ind](br_guiding_shift.detach()) + + tl_feat_adaption = self.tl_feat_adaption[lvl_ind](tl_pool, + tl_dcn_offset) + br_feat_adaption = self.br_feat_adaption[lvl_ind](br_pool, + br_dcn_offset) + + tl_centripetal_shift = self.tl_centripetal_shift[lvl_ind]( + tl_feat_adaption) + br_centripetal_shift = self.br_centripetal_shift[lvl_ind]( + br_feat_adaption) + + result_list = [ + tl_heat, br_heat, tl_off, br_off, tl_guiding_shift, + br_guiding_shift, tl_centripetal_shift, br_centripetal_shift + ] + return result_list + + def loss_by_feat( + self, + tl_heats: List[Tensor], + br_heats: List[Tensor], + tl_offs: List[Tensor], + br_offs: List[Tensor], + tl_guiding_shifts: List[Tensor], + br_guiding_shifts: List[Tensor], + tl_centripetal_shifts: List[Tensor], + br_centripetal_shifts: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + tl_heats (list[Tensor]): Top-left corner heatmaps for each level + with shape (N, num_classes, H, W). + br_heats (list[Tensor]): Bottom-right corner heatmaps for each + level with shape (N, num_classes, H, W). + tl_offs (list[Tensor]): Top-left corner offsets for each level + with shape (N, corner_offset_channels, H, W). + br_offs (list[Tensor]): Bottom-right corner offsets for each level + with shape (N, corner_offset_channels, H, W). + tl_guiding_shifts (list[Tensor]): Top-left guiding shifts for each + level with shape (N, guiding_shift_channels, H, W). + br_guiding_shifts (list[Tensor]): Bottom-right guiding shifts for + each level with shape (N, guiding_shift_channels, H, W). + tl_centripetal_shifts (list[Tensor]): Top-left centripetal shifts + for each level with shape (N, centripetal_shift_channels, H, + W). + br_centripetal_shifts (list[Tensor]): Bottom-right centripetal + shifts for each level with shape (N, + centripetal_shift_channels, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Specify which bounding boxes can be ignored when computing + the loss. + + Returns: + dict[str, Tensor]: A dictionary of loss components. Containing the + following losses: + + - det_loss (list[Tensor]): Corner keypoint losses of all + feature levels. + - off_loss (list[Tensor]): Corner offset losses of all feature + levels. + - guiding_loss (list[Tensor]): Guiding shift losses of all + feature levels. + - centripetal_loss (list[Tensor]): Centripetal shift losses of + all feature levels. + """ + gt_bboxes = [ + gt_instances.bboxes for gt_instances in batch_gt_instances + ] + gt_labels = [ + gt_instances.labels for gt_instances in batch_gt_instances + ] + + targets = self.get_targets( + gt_bboxes, + gt_labels, + tl_heats[-1].shape, + batch_img_metas[0]['batch_input_shape'], + with_corner_emb=self.with_corner_emb, + with_guiding_shift=True, + with_centripetal_shift=True) + mlvl_targets = [targets for _ in range(self.num_feat_levels)] + [det_losses, off_losses, guiding_losses, centripetal_losses + ] = multi_apply(self.loss_by_feat_single, tl_heats, br_heats, tl_offs, + br_offs, tl_guiding_shifts, br_guiding_shifts, + tl_centripetal_shifts, br_centripetal_shifts, + mlvl_targets) + loss_dict = dict( + det_loss=det_losses, + off_loss=off_losses, + guiding_loss=guiding_losses, + centripetal_loss=centripetal_losses) + return loss_dict + + def loss_by_feat_single(self, tl_hmp: Tensor, br_hmp: Tensor, + tl_off: Tensor, br_off: Tensor, + tl_guiding_shift: Tensor, br_guiding_shift: Tensor, + tl_centripetal_shift: Tensor, + br_centripetal_shift: Tensor, + targets: dict) -> Tuple[Tensor, ...]: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + tl_hmp (Tensor): Top-left corner heatmap for current level with + shape (N, num_classes, H, W). + br_hmp (Tensor): Bottom-right corner heatmap for current level with + shape (N, num_classes, H, W). + tl_off (Tensor): Top-left corner offset for current level with + shape (N, corner_offset_channels, H, W). + br_off (Tensor): Bottom-right corner offset for current level with + shape (N, corner_offset_channels, H, W). + tl_guiding_shift (Tensor): Top-left guiding shift for current level + with shape (N, guiding_shift_channels, H, W). + br_guiding_shift (Tensor): Bottom-right guiding shift for current + level with shape (N, guiding_shift_channels, H, W). + tl_centripetal_shift (Tensor): Top-left centripetal shift for + current level with shape (N, centripetal_shift_channels, H, W). + br_centripetal_shift (Tensor): Bottom-right centripetal shift for + current level with shape (N, centripetal_shift_channels, H, W). + targets (dict): Corner target generated by `get_targets`. + + Returns: + tuple[torch.Tensor]: Losses of the head's different branches + containing the following losses: + + - det_loss (Tensor): Corner keypoint loss. + - off_loss (Tensor): Corner offset loss. + - guiding_loss (Tensor): Guiding shift loss. + - centripetal_loss (Tensor): Centripetal shift loss. + """ + targets['corner_embedding'] = None + + det_loss, _, _, off_loss = super().loss_by_feat_single( + tl_hmp, br_hmp, None, None, tl_off, br_off, targets) + + gt_tl_guiding_shift = targets['topleft_guiding_shift'] + gt_br_guiding_shift = targets['bottomright_guiding_shift'] + gt_tl_centripetal_shift = targets['topleft_centripetal_shift'] + gt_br_centripetal_shift = targets['bottomright_centripetal_shift'] + + gt_tl_heatmap = targets['topleft_heatmap'] + gt_br_heatmap = targets['bottomright_heatmap'] + # We only compute the offset loss at the real corner position. + # The value of real corner would be 1 in heatmap ground truth. + # The mask is computed in class agnostic mode and its shape is + # batch * 1 * width * height. + tl_mask = gt_tl_heatmap.eq(1).sum(1).gt(0).unsqueeze(1).type_as( + gt_tl_heatmap) + br_mask = gt_br_heatmap.eq(1).sum(1).gt(0).unsqueeze(1).type_as( + gt_br_heatmap) + + # Guiding shift loss + tl_guiding_loss = self.loss_guiding_shift( + tl_guiding_shift, + gt_tl_guiding_shift, + tl_mask, + avg_factor=tl_mask.sum()) + br_guiding_loss = self.loss_guiding_shift( + br_guiding_shift, + gt_br_guiding_shift, + br_mask, + avg_factor=br_mask.sum()) + guiding_loss = (tl_guiding_loss + br_guiding_loss) / 2.0 + # Centripetal shift loss + tl_centripetal_loss = self.loss_centripetal_shift( + tl_centripetal_shift, + gt_tl_centripetal_shift, + tl_mask, + avg_factor=tl_mask.sum()) + br_centripetal_loss = self.loss_centripetal_shift( + br_centripetal_shift, + gt_br_centripetal_shift, + br_mask, + avg_factor=br_mask.sum()) + centripetal_loss = (tl_centripetal_loss + br_centripetal_loss) / 2.0 + + return det_loss, off_loss, guiding_loss, centripetal_loss + + def predict_by_feat(self, + tl_heats: List[Tensor], + br_heats: List[Tensor], + tl_offs: List[Tensor], + br_offs: List[Tensor], + tl_guiding_shifts: List[Tensor], + br_guiding_shifts: List[Tensor], + tl_centripetal_shifts: List[Tensor], + br_centripetal_shifts: List[Tensor], + batch_img_metas: Optional[List[dict]] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Args: + tl_heats (list[Tensor]): Top-left corner heatmaps for each level + with shape (N, num_classes, H, W). + br_heats (list[Tensor]): Bottom-right corner heatmaps for each + level with shape (N, num_classes, H, W). + tl_offs (list[Tensor]): Top-left corner offsets for each level + with shape (N, corner_offset_channels, H, W). + br_offs (list[Tensor]): Bottom-right corner offsets for each level + with shape (N, corner_offset_channels, H, W). + tl_guiding_shifts (list[Tensor]): Top-left guiding shifts for each + level with shape (N, guiding_shift_channels, H, W). Useless in + this function, we keep this arg because it's the raw output + from CentripetalHead. + br_guiding_shifts (list[Tensor]): Bottom-right guiding shifts for + each level with shape (N, guiding_shift_channels, H, W). + Useless in this function, we keep this arg because it's the + raw output from CentripetalHead. + tl_centripetal_shifts (list[Tensor]): Top-left centripetal shifts + for each level with shape (N, centripetal_shift_channels, H, + W). + br_centripetal_shifts (list[Tensor]): Bottom-right centripetal + shifts for each level with shape (N, + centripetal_shift_channels, H, W). + batch_img_metas (list[dict], optional): Batch image meta info. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert tl_heats[-1].shape[0] == br_heats[-1].shape[0] == len( + batch_img_metas) + result_list = [] + for img_id in range(len(batch_img_metas)): + result_list.append( + self._predict_by_feat_single( + tl_heats[-1][img_id:img_id + 1, :], + br_heats[-1][img_id:img_id + 1, :], + tl_offs[-1][img_id:img_id + 1, :], + br_offs[-1][img_id:img_id + 1, :], + batch_img_metas[img_id], + tl_emb=None, + br_emb=None, + tl_centripetal_shift=tl_centripetal_shifts[-1][ + img_id:img_id + 1, :], + br_centripetal_shift=br_centripetal_shifts[-1][ + img_id:img_id + 1, :], + rescale=rescale, + with_nms=with_nms)) + + return result_list diff --git a/mmdetection/mmdet/models/dense_heads/condinst_head.py b/mmdetection/mmdet/models/dense_heads/condinst_head.py new file mode 100644 index 00000000..35a25e63 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/condinst_head.py @@ -0,0 +1,1226 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import Dict, List, Optional, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, Scale +from mmengine.config import ConfigDict +from mmengine.model import BaseModule, kaiming_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures.bbox import cat_boxes +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, OptConfigType, + OptInstanceList, reduce_mean) +from ..task_modules.prior_generators import MlvlPointGenerator +from ..utils import (aligned_bilinear, filter_scores_and_topk, multi_apply, + relative_coordinate_maps, select_single_mlvl) +from ..utils.misc import empty_instances +from .base_mask_head import BaseMaskHead +from .fcos_head import FCOSHead + +INF = 1e8 + + +@MODELS.register_module() +class CondInstBboxHead(FCOSHead): + """CondInst box head used in https://arxiv.org/abs/1904.02689. + + Note that CondInst Bbox Head is a extension of FCOS head. + Two differences are described as follows: + + 1. CondInst box head predicts a set of params for each instance. + 2. CondInst box head return the pos_gt_inds and pos_inds. + + Args: + num_params (int): Number of params for instance segmentation. + """ + + def __init__(self, *args, num_params: int = 169, **kwargs) -> None: + self.num_params = num_params + super().__init__(*args, **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + super()._init_layers() + self.controller = nn.Conv2d( + self.feat_channels, self.num_params, 3, padding=1) + + def forward_single(self, x: Tensor, scale: Scale, + stride: int) -> Tuple[Tensor, Tensor, Tensor, Tensor]: + """Forward features of a single scale level. + + Args: + x (Tensor): FPN feature maps of the specified stride. + scale (:obj:`mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + stride (int): The corresponding stride for feature maps, only + used to normalize the bbox prediction when self.norm_on_bbox + is True. + + Returns: + tuple: scores for each class, bbox predictions, centerness + predictions and param predictions of input feature maps. + """ + cls_score, bbox_pred, cls_feat, reg_feat = \ + super(FCOSHead, self).forward_single(x) + if self.centerness_on_reg: + centerness = self.conv_centerness(reg_feat) + else: + centerness = self.conv_centerness(cls_feat) + # scale the bbox_pred of different level + # float to avoid overflow when enabling FP16 + bbox_pred = scale(bbox_pred).float() + if self.norm_on_bbox: + # bbox_pred needed for gradient computation has been modified + # by F.relu(bbox_pred) when run with PyTorch 1.10. So replace + # F.relu(bbox_pred) with bbox_pred.clamp(min=0) + bbox_pred = bbox_pred.clamp(min=0) + if not self.training: + bbox_pred *= stride + else: + bbox_pred = bbox_pred.exp() + param_pred = self.controller(reg_feat) + return cls_score, bbox_pred, centerness, param_pred + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + centernesses: List[Tensor], + param_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level, + each is a 4D-tensor, the channel number is + num_points * num_classes. + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level, each is a 4D-tensor, the channel number is + num_points * 4. + centernesses (list[Tensor]): centerness for each scale level, each + is a 4D-tensor, the channel number is num_points * 1. + param_preds (List[Tensor]): param_pred for each scale level, each + is a 4D-tensor, the channel number is num_params. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert len(cls_scores) == len(bbox_preds) == len(centernesses) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + # Need stride for rel coord compute + all_level_points_strides = self.prior_generator.grid_priors( + featmap_sizes, + dtype=bbox_preds[0].dtype, + device=bbox_preds[0].device, + with_stride=True) + all_level_points = [i[:, :2] for i in all_level_points_strides] + all_level_strides = [i[:, 2] for i in all_level_points_strides] + labels, bbox_targets, pos_inds_list, pos_gt_inds_list = \ + self.get_targets(all_level_points, batch_gt_instances) + + num_imgs = cls_scores[0].size(0) + # flatten cls_scores, bbox_preds and centerness + flatten_cls_scores = [ + cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels) + for cls_score in cls_scores + ] + flatten_bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4) + for bbox_pred in bbox_preds + ] + flatten_centerness = [ + centerness.permute(0, 2, 3, 1).reshape(-1) + for centerness in centernesses + ] + flatten_cls_scores = torch.cat(flatten_cls_scores) + flatten_bbox_preds = torch.cat(flatten_bbox_preds) + flatten_centerness = torch.cat(flatten_centerness) + flatten_labels = torch.cat(labels) + flatten_bbox_targets = torch.cat(bbox_targets) + # repeat points to align with bbox_preds + flatten_points = torch.cat( + [points.repeat(num_imgs, 1) for points in all_level_points]) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = self.num_classes + pos_inds = ((flatten_labels >= 0) + & (flatten_labels < bg_class_ind)).nonzero().reshape(-1) + num_pos = torch.tensor( + len(pos_inds), dtype=torch.float, device=bbox_preds[0].device) + num_pos = max(reduce_mean(num_pos), 1.0) + loss_cls = self.loss_cls( + flatten_cls_scores, flatten_labels, avg_factor=num_pos) + + pos_bbox_preds = flatten_bbox_preds[pos_inds] + pos_centerness = flatten_centerness[pos_inds] + pos_bbox_targets = flatten_bbox_targets[pos_inds] + pos_centerness_targets = self.centerness_target(pos_bbox_targets) + # centerness weighted iou loss + centerness_denorm = max( + reduce_mean(pos_centerness_targets.sum().detach()), 1e-6) + + if len(pos_inds) > 0: + pos_points = flatten_points[pos_inds] + pos_decoded_bbox_preds = self.bbox_coder.decode( + pos_points, pos_bbox_preds) + pos_decoded_target_preds = self.bbox_coder.decode( + pos_points, pos_bbox_targets) + loss_bbox = self.loss_bbox( + pos_decoded_bbox_preds, + pos_decoded_target_preds, + weight=pos_centerness_targets, + avg_factor=centerness_denorm) + loss_centerness = self.loss_centerness( + pos_centerness, pos_centerness_targets, avg_factor=num_pos) + else: + loss_bbox = pos_bbox_preds.sum() + loss_centerness = pos_centerness.sum() + + self._raw_positive_infos.update(cls_scores=cls_scores) + self._raw_positive_infos.update(centernesses=centernesses) + self._raw_positive_infos.update(param_preds=param_preds) + self._raw_positive_infos.update(all_level_points=all_level_points) + self._raw_positive_infos.update(all_level_strides=all_level_strides) + self._raw_positive_infos.update(pos_gt_inds_list=pos_gt_inds_list) + self._raw_positive_infos.update(pos_inds_list=pos_inds_list) + + return dict( + loss_cls=loss_cls, + loss_bbox=loss_bbox, + loss_centerness=loss_centerness) + + def get_targets( + self, points: List[Tensor], batch_gt_instances: InstanceList + ) -> Tuple[List[Tensor], List[Tensor], List[Tensor], List[Tensor]]: + """Compute regression, classification and centerness targets for points + in multiple images. + + Args: + points (list[Tensor]): Points of each fpn level, each has shape + (num_points, 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + + Returns: + tuple: Targets of each level. + + - concat_lvl_labels (list[Tensor]): Labels of each level. + - concat_lvl_bbox_targets (list[Tensor]): BBox targets of each \ + level. + - pos_inds_list (list[Tensor]): pos_inds of each image. + - pos_gt_inds_list (List[Tensor]): pos_gt_inds of each image. + """ + assert len(points) == len(self.regress_ranges) + num_levels = len(points) + # expand regress ranges to align with points + expanded_regress_ranges = [ + points[i].new_tensor(self.regress_ranges[i])[None].expand_as( + points[i]) for i in range(num_levels) + ] + # concat all levels points and regress ranges + concat_regress_ranges = torch.cat(expanded_regress_ranges, dim=0) + concat_points = torch.cat(points, dim=0) + + # the number of points per img, per lvl + num_points = [center.size(0) for center in points] + + # get labels and bbox_targets of each image + labels_list, bbox_targets_list, pos_inds_list, pos_gt_inds_list = \ + multi_apply( + self._get_targets_single, + batch_gt_instances, + points=concat_points, + regress_ranges=concat_regress_ranges, + num_points_per_lvl=num_points) + + # split to per img, per level + labels_list = [labels.split(num_points, 0) for labels in labels_list] + bbox_targets_list = [ + bbox_targets.split(num_points, 0) + for bbox_targets in bbox_targets_list + ] + + # concat per level image + concat_lvl_labels = [] + concat_lvl_bbox_targets = [] + for i in range(num_levels): + concat_lvl_labels.append( + torch.cat([labels[i] for labels in labels_list])) + bbox_targets = torch.cat( + [bbox_targets[i] for bbox_targets in bbox_targets_list]) + if self.norm_on_bbox: + bbox_targets = bbox_targets / self.strides[i] + concat_lvl_bbox_targets.append(bbox_targets) + return (concat_lvl_labels, concat_lvl_bbox_targets, pos_inds_list, + pos_gt_inds_list) + + def _get_targets_single( + self, gt_instances: InstanceData, points: Tensor, + regress_ranges: Tensor, num_points_per_lvl: List[int] + ) -> Tuple[Tensor, Tensor, Tensor, Tensor]: + """Compute regression and classification targets for a single image.""" + num_points = points.size(0) + num_gts = len(gt_instances) + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + gt_masks = gt_instances.get('masks', None) + + if num_gts == 0: + return gt_labels.new_full((num_points,), self.num_classes), \ + gt_bboxes.new_zeros((num_points, 4)), \ + gt_bboxes.new_zeros((0,), dtype=torch.int64), \ + gt_bboxes.new_zeros((0,), dtype=torch.int64) + + areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0]) * ( + gt_bboxes[:, 3] - gt_bboxes[:, 1]) + # TODO: figure out why these two are different + # areas = areas[None].expand(num_points, num_gts) + areas = areas[None].repeat(num_points, 1) + regress_ranges = regress_ranges[:, None, :].expand( + num_points, num_gts, 2) + gt_bboxes = gt_bboxes[None].expand(num_points, num_gts, 4) + xs, ys = points[:, 0], points[:, 1] + xs = xs[:, None].expand(num_points, num_gts) + ys = ys[:, None].expand(num_points, num_gts) + + left = xs - gt_bboxes[..., 0] + right = gt_bboxes[..., 2] - xs + top = ys - gt_bboxes[..., 1] + bottom = gt_bboxes[..., 3] - ys + bbox_targets = torch.stack((left, top, right, bottom), -1) + + if self.center_sampling: + # condition1: inside a `center bbox` + radius = self.center_sample_radius + # if gt_mask not None, use gt mask's centroid to determine + # the center region rather than gt_bbox center + if gt_masks is None: + center_xs = (gt_bboxes[..., 0] + gt_bboxes[..., 2]) / 2 + center_ys = (gt_bboxes[..., 1] + gt_bboxes[..., 3]) / 2 + else: + h, w = gt_masks.height, gt_masks.width + masks = gt_masks.to_tensor( + dtype=torch.bool, device=gt_bboxes.device) + yys = torch.arange( + 0, h, dtype=torch.float32, device=masks.device) + xxs = torch.arange( + 0, w, dtype=torch.float32, device=masks.device) + # m00/m10/m01 represent the moments of a contour + # centroid is computed by m00/m10 and m00/m01 + m00 = masks.sum(dim=-1).sum(dim=-1).clamp(min=1e-6) + m10 = (masks * xxs).sum(dim=-1).sum(dim=-1) + m01 = (masks * yys[:, None]).sum(dim=-1).sum(dim=-1) + center_xs = m10 / m00 + center_ys = m01 / m00 + + center_xs = center_xs[None].expand(num_points, num_gts) + center_ys = center_ys[None].expand(num_points, num_gts) + center_gts = torch.zeros_like(gt_bboxes) + stride = center_xs.new_zeros(center_xs.shape) + + # project the points on current lvl back to the `original` sizes + lvl_begin = 0 + for lvl_idx, num_points_lvl in enumerate(num_points_per_lvl): + lvl_end = lvl_begin + num_points_lvl + stride[lvl_begin:lvl_end] = self.strides[lvl_idx] * radius + lvl_begin = lvl_end + + x_mins = center_xs - stride + y_mins = center_ys - stride + x_maxs = center_xs + stride + y_maxs = center_ys + stride + center_gts[..., 0] = torch.where(x_mins > gt_bboxes[..., 0], + x_mins, gt_bboxes[..., 0]) + center_gts[..., 1] = torch.where(y_mins > gt_bboxes[..., 1], + y_mins, gt_bboxes[..., 1]) + center_gts[..., 2] = torch.where(x_maxs > gt_bboxes[..., 2], + gt_bboxes[..., 2], x_maxs) + center_gts[..., 3] = torch.where(y_maxs > gt_bboxes[..., 3], + gt_bboxes[..., 3], y_maxs) + + cb_dist_left = xs - center_gts[..., 0] + cb_dist_right = center_gts[..., 2] - xs + cb_dist_top = ys - center_gts[..., 1] + cb_dist_bottom = center_gts[..., 3] - ys + center_bbox = torch.stack( + (cb_dist_left, cb_dist_top, cb_dist_right, cb_dist_bottom), -1) + inside_gt_bbox_mask = center_bbox.min(-1)[0] > 0 + else: + # condition1: inside a gt bbox + inside_gt_bbox_mask = bbox_targets.min(-1)[0] > 0 + + # condition2: limit the regression range for each location + max_regress_distance = bbox_targets.max(-1)[0] + inside_regress_range = ( + (max_regress_distance >= regress_ranges[..., 0]) + & (max_regress_distance <= regress_ranges[..., 1])) + + # if there are still more than one objects for a location, + # we choose the one with minimal area + areas[inside_gt_bbox_mask == 0] = INF + areas[inside_regress_range == 0] = INF + min_area, min_area_inds = areas.min(dim=1) + + labels = gt_labels[min_area_inds] + labels[min_area == INF] = self.num_classes # set as BG + bbox_targets = bbox_targets[range(num_points), min_area_inds] + + # return pos_inds & pos_gt_inds + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().reshape(-1) + pos_gt_inds = min_area_inds[labels < self.num_classes] + return labels, bbox_targets, pos_inds, pos_gt_inds + + def get_positive_infos(self) -> InstanceList: + """Get positive information from sampling results. + + Returns: + list[:obj:`InstanceData`]: Positive information of each image, + usually including positive bboxes, positive labels, positive + priors, etc. + """ + assert len(self._raw_positive_infos) > 0 + + pos_gt_inds_list = self._raw_positive_infos['pos_gt_inds_list'] + pos_inds_list = self._raw_positive_infos['pos_inds_list'] + num_imgs = len(pos_gt_inds_list) + + cls_score_list = [] + centerness_list = [] + param_pred_list = [] + point_list = [] + stride_list = [] + for cls_score_per_lvl, centerness_per_lvl, param_pred_per_lvl,\ + point_per_lvl, stride_per_lvl in \ + zip(self._raw_positive_infos['cls_scores'], + self._raw_positive_infos['centernesses'], + self._raw_positive_infos['param_preds'], + self._raw_positive_infos['all_level_points'], + self._raw_positive_infos['all_level_strides']): + cls_score_per_lvl = \ + cls_score_per_lvl.permute( + 0, 2, 3, 1).reshape(num_imgs, -1, self.num_classes) + centerness_per_lvl = \ + centerness_per_lvl.permute( + 0, 2, 3, 1).reshape(num_imgs, -1, 1) + param_pred_per_lvl = \ + param_pred_per_lvl.permute( + 0, 2, 3, 1).reshape(num_imgs, -1, self.num_params) + point_per_lvl = point_per_lvl.unsqueeze(0).repeat(num_imgs, 1, 1) + stride_per_lvl = stride_per_lvl.unsqueeze(0).repeat(num_imgs, 1) + + cls_score_list.append(cls_score_per_lvl) + centerness_list.append(centerness_per_lvl) + param_pred_list.append(param_pred_per_lvl) + point_list.append(point_per_lvl) + stride_list.append(stride_per_lvl) + cls_scores = torch.cat(cls_score_list, dim=1) + centernesses = torch.cat(centerness_list, dim=1) + param_preds = torch.cat(param_pred_list, dim=1) + all_points = torch.cat(point_list, dim=1) + all_strides = torch.cat(stride_list, dim=1) + + positive_infos = [] + for i, (pos_gt_inds, + pos_inds) in enumerate(zip(pos_gt_inds_list, pos_inds_list)): + pos_info = InstanceData() + pos_info.points = all_points[i][pos_inds] + pos_info.strides = all_strides[i][pos_inds] + pos_info.scores = cls_scores[i][pos_inds] + pos_info.centernesses = centernesses[i][pos_inds] + pos_info.param_preds = param_preds[i][pos_inds] + pos_info.pos_assigned_gt_inds = pos_gt_inds + pos_info.pos_inds = pos_inds + positive_infos.append(pos_info) + return positive_infos + + def predict_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + score_factors: Optional[List[Tensor]] = None, + param_preds: Optional[List[Tensor]] = None, + batch_img_metas: Optional[List[dict]] = None, + cfg: Optional[ConfigDict] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Note: When score_factors is not None, the cls_scores are + usually multiplied by it then obtain the real score used in NMS, + such as CenterNess in FCOS, IoU branch in ATSS. + + Args: + cls_scores (list[Tensor]): Classification scores for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * 4, H, W). + score_factors (list[Tensor], optional): Score factor for + all scale level, each is a 4D-tensor, has shape + (batch_size, num_priors * 1, H, W). Defaults to None. + param_preds (list[Tensor], optional): Params for all scale + level, each is a 4D-tensor, has shape + (batch_size, num_priors * num_params, H, W) + batch_img_metas (list[dict], Optional): Batch image meta info. + Defaults to None. + cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_scores) == len(bbox_preds) + + if score_factors is None: + # e.g. Retina, FreeAnchor, Foveabox, etc. + with_score_factors = False + else: + # e.g. FCOS, PAA, ATSS, AutoAssign, etc. + with_score_factors = True + assert len(cls_scores) == len(score_factors) + + num_levels = len(cls_scores) + + featmap_sizes = [cls_scores[i].shape[-2:] for i in range(num_levels)] + all_level_points_strides = self.prior_generator.grid_priors( + featmap_sizes, + dtype=bbox_preds[0].dtype, + device=bbox_preds[0].device, + with_stride=True) + all_level_points = [i[:, :2] for i in all_level_points_strides] + all_level_strides = [i[:, 2] for i in all_level_points_strides] + + result_list = [] + + for img_id in range(len(batch_img_metas)): + img_meta = batch_img_metas[img_id] + cls_score_list = select_single_mlvl( + cls_scores, img_id, detach=True) + bbox_pred_list = select_single_mlvl( + bbox_preds, img_id, detach=True) + if with_score_factors: + score_factor_list = select_single_mlvl( + score_factors, img_id, detach=True) + else: + score_factor_list = [None for _ in range(num_levels)] + param_pred_list = select_single_mlvl( + param_preds, img_id, detach=True) + + results = self._predict_by_feat_single( + cls_score_list=cls_score_list, + bbox_pred_list=bbox_pred_list, + score_factor_list=score_factor_list, + param_pred_list=param_pred_list, + mlvl_points=all_level_points, + mlvl_strides=all_level_strides, + img_meta=img_meta, + cfg=cfg, + rescale=rescale, + with_nms=with_nms) + result_list.append(results) + return result_list + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + param_pred_list: List[Tensor], + mlvl_points: List[Tensor], + mlvl_strides: List[Tensor], + img_meta: dict, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image, each item has shape + (num_priors * 1, H, W). + param_pred_list (List[Tensor]): Param predition from all scale + levels of a single image, each item has shape + (num_priors * num_params, H, W). + mlvl_points (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid. + It has shape (num_priors, 2) + mlvl_strides (List[Tensor]): Each element in the list is + the stride of a single level in feature pyramid. + It has shape (num_priors, 1) + img_meta (dict): Image meta info. + cfg (mmengine.Config): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + if score_factor_list[0] is None: + # e.g. Retina, FreeAnchor, etc. + with_score_factors = False + else: + # e.g. FCOS, PAA, ATSS, etc. + with_score_factors = True + + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bbox_preds = [] + mlvl_param_preds = [] + mlvl_valid_points = [] + mlvl_valid_strides = [] + mlvl_scores = [] + mlvl_labels = [] + if with_score_factors: + mlvl_score_factors = [] + else: + mlvl_score_factors = None + for level_idx, (cls_score, bbox_pred, score_factor, + param_pred, points, strides) in \ + enumerate(zip(cls_score_list, bbox_pred_list, + score_factor_list, param_pred_list, + mlvl_points, mlvl_strides)): + + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + + dim = self.bbox_coder.encode_size + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, dim) + if with_score_factors: + score_factor = score_factor.permute(1, 2, + 0).reshape(-1).sigmoid() + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + if self.use_sigmoid_cls: + scores = cls_score.sigmoid() + else: + # remind that we set FG labels to [0, num_class-1] + # since mmdet v2.0 + # BG cat_id: num_class + scores = cls_score.softmax(-1)[:, :-1] + + param_pred = param_pred.permute(1, 2, + 0).reshape(-1, self.num_params) + + # After https://github.com/open-mmlab/mmdetection/pull/6268/, + # this operation keeps fewer bboxes under the same `nms_pre`. + # There is no difference in performance for most models. If you + # find a slight drop in performance, you can set a larger + # `nms_pre` than before. + score_thr = cfg.get('score_thr', 0) + + results = filter_scores_and_topk( + scores, score_thr, nms_pre, + dict( + bbox_pred=bbox_pred, + param_pred=param_pred, + points=points, + strides=strides)) + scores, labels, keep_idxs, filtered_results = results + + bbox_pred = filtered_results['bbox_pred'] + param_pred = filtered_results['param_pred'] + points = filtered_results['points'] + strides = filtered_results['strides'] + + if with_score_factors: + score_factor = score_factor[keep_idxs] + + mlvl_bbox_preds.append(bbox_pred) + mlvl_param_preds.append(param_pred) + mlvl_valid_points.append(points) + mlvl_valid_strides.append(strides) + mlvl_scores.append(scores) + mlvl_labels.append(labels) + + if with_score_factors: + mlvl_score_factors.append(score_factor) + + bbox_pred = torch.cat(mlvl_bbox_preds) + priors = cat_boxes(mlvl_valid_points) + bboxes = self.bbox_coder.decode(priors, bbox_pred, max_shape=img_shape) + + results = InstanceData() + results.bboxes = bboxes + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + results.param_preds = torch.cat(mlvl_param_preds) + results.points = torch.cat(mlvl_valid_points) + results.strides = torch.cat(mlvl_valid_strides) + if with_score_factors: + results.score_factors = torch.cat(mlvl_score_factors) + + return self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + + +class MaskFeatModule(BaseModule): + """CondInst mask feature map branch used in \ + https://arxiv.org/abs/1904.02689. + + Args: + in_channels (int): Number of channels in the input feature map. + feat_channels (int): Number of hidden channels of the mask feature + map branch. + start_level (int): The starting feature map level from RPN that + will be used to predict the mask feature map. + end_level (int): The ending feature map level from rpn that + will be used to predict the mask feature map. + out_channels (int): Number of output channels of the mask feature + map branch. This is the channel count of the mask + feature map that to be dynamically convolved with the predicted + kernel. + mask_stride (int): Downsample factor of the mask feature map output. + Defaults to 4. + num_stacked_convs (int): Number of convs in mask feature branch. + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Config dict for normalization layer. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + in_channels: int, + feat_channels: int, + start_level: int, + end_level: int, + out_channels: int, + mask_stride: int = 4, + num_stacked_convs: int = 4, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + init_cfg: MultiConfig = [ + dict(type='Normal', layer='Conv2d', std=0.01) + ], + **kwargs) -> None: + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.feat_channels = feat_channels + self.start_level = start_level + self.end_level = end_level + self.mask_stride = mask_stride + self.num_stacked_convs = num_stacked_convs + assert start_level >= 0 and end_level >= start_level + self.out_channels = out_channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self._init_layers() + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.convs_all_levels = nn.ModuleList() + for i in range(self.start_level, self.end_level + 1): + convs_per_level = nn.Sequential() + convs_per_level.add_module( + f'conv{i}', + ConvModule( + self.in_channels, + self.feat_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + inplace=False, + bias=False)) + self.convs_all_levels.append(convs_per_level) + + conv_branch = [] + for _ in range(self.num_stacked_convs): + conv_branch.append( + ConvModule( + self.feat_channels, + self.feat_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + bias=False)) + self.conv_branch = nn.Sequential(*conv_branch) + + self.conv_pred = nn.Conv2d( + self.feat_channels, self.out_channels, 1, stride=1) + + def init_weights(self) -> None: + """Initialize weights of the head.""" + super().init_weights() + kaiming_init(self.convs_all_levels, a=1, distribution='uniform') + kaiming_init(self.conv_branch, a=1, distribution='uniform') + kaiming_init(self.conv_pred, a=1, distribution='uniform') + + def forward(self, x: Tuple[Tensor]) -> Tensor: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + Tensor: The predicted mask feature map. + """ + inputs = x[self.start_level:self.end_level + 1] + assert len(inputs) == (self.end_level - self.start_level + 1) + feature_add_all_level = self.convs_all_levels[0](inputs[0]) + target_h, target_w = feature_add_all_level.size()[2:] + for i in range(1, len(inputs)): + input_p = inputs[i] + x_p = self.convs_all_levels[i](input_p) + h, w = x_p.size()[2:] + factor_h = target_h // h + factor_w = target_w // w + assert factor_h == factor_w + feature_per_level = aligned_bilinear(x_p, factor_h) + feature_add_all_level = feature_add_all_level + \ + feature_per_level + + feature_add_all_level = self.conv_branch(feature_add_all_level) + feature_pred = self.conv_pred(feature_add_all_level) + return feature_pred + + +@MODELS.register_module() +class CondInstMaskHead(BaseMaskHead): + """CondInst mask head used in https://arxiv.org/abs/1904.02689. + + This head outputs the mask for CondInst. + + Args: + mask_feature_head (dict): Config of CondInstMaskFeatHead. + num_layers (int): Number of dynamic conv layers. + feat_channels (int): Number of channels in the dynamic conv. + mask_out_stride (int): The stride of the mask feat. + size_of_interest (int): The size of the region used in rel coord. + max_masks_to_train (int): Maximum number of masks to train for + each image. + loss_segm (:obj:`ConfigDict` or dict, optional): Config of + segmentation loss. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config + of head. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + head. + """ + + def __init__(self, + mask_feature_head: ConfigType, + num_layers: int = 3, + feat_channels: int = 8, + mask_out_stride: int = 4, + size_of_interest: int = 8, + max_masks_to_train: int = -1, + topk_masks_per_img: int = -1, + loss_mask: ConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None) -> None: + super().__init__() + self.mask_feature_head = MaskFeatModule(**mask_feature_head) + self.mask_feat_stride = self.mask_feature_head.mask_stride + self.in_channels = self.mask_feature_head.out_channels + self.num_layers = num_layers + self.feat_channels = feat_channels + self.size_of_interest = size_of_interest + self.mask_out_stride = mask_out_stride + self.max_masks_to_train = max_masks_to_train + self.topk_masks_per_img = topk_masks_per_img + self.prior_generator = MlvlPointGenerator([self.mask_feat_stride]) + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + self.loss_mask = MODELS.build(loss_mask) + self._init_layers() + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + weight_nums, bias_nums = [], [] + for i in range(self.num_layers): + if i == 0: + weight_nums.append((self.in_channels + 2) * self.feat_channels) + bias_nums.append(self.feat_channels) + elif i == self.num_layers - 1: + weight_nums.append(self.feat_channels * 1) + bias_nums.append(1) + else: + weight_nums.append(self.feat_channels * self.feat_channels) + bias_nums.append(self.feat_channels) + + self.weight_nums = weight_nums + self.bias_nums = bias_nums + self.num_params = sum(weight_nums) + sum(bias_nums) + + def parse_dynamic_params( + self, params: Tensor) -> Tuple[List[Tensor], List[Tensor]]: + """parse the dynamic params for dynamic conv.""" + num_insts = params.size(0) + params_splits = list( + torch.split_with_sizes( + params, self.weight_nums + self.bias_nums, dim=1)) + weight_splits = params_splits[:self.num_layers] + bias_splits = params_splits[self.num_layers:] + for i in range(self.num_layers): + if i < self.num_layers - 1: + weight_splits[i] = weight_splits[i].reshape( + num_insts * self.in_channels, -1, 1, 1) + bias_splits[i] = bias_splits[i].reshape(num_insts * + self.in_channels) + else: + # out_channels x in_channels x 1 x 1 + weight_splits[i] = weight_splits[i].reshape( + num_insts * 1, -1, 1, 1) + bias_splits[i] = bias_splits[i].reshape(num_insts) + + return weight_splits, bias_splits + + def dynamic_conv_forward(self, features: Tensor, weights: List[Tensor], + biases: List[Tensor], num_insts: int) -> Tensor: + """dynamic forward, each layer follow a relu.""" + n_layers = len(weights) + x = features + for i, (w, b) in enumerate(zip(weights, biases)): + x = F.conv2d(x, w, bias=b, stride=1, padding=0, groups=num_insts) + if i < n_layers - 1: + x = F.relu(x) + return x + + def forward(self, x: tuple, positive_infos: InstanceList) -> tuple: + """Forward feature from the upstream network to get prototypes and + linearly combine the prototypes, using masks coefficients, into + instance masks. Finally, crop the instance masks with given bboxes. + + Args: + x (Tuple[Tensor]): Feature from the upstream network, which is + a 4D-tensor. + positive_infos (List[:obj:``InstanceData``]): Positive information + that calculate from detect head. + + Returns: + tuple: Predicted instance segmentation masks + """ + mask_feats = self.mask_feature_head(x) + return multi_apply(self.forward_single, mask_feats, positive_infos) + + def forward_single(self, mask_feat: Tensor, + positive_info: InstanceData) -> Tensor: + """Forward features of a each image.""" + pos_param_preds = positive_info.get('param_preds') + pos_points = positive_info.get('points') + pos_strides = positive_info.get('strides') + + num_inst = pos_param_preds.shape[0] + mask_feat = mask_feat[None].repeat(num_inst, 1, 1, 1) + _, _, H, W = mask_feat.size() + if num_inst == 0: + return (pos_param_preds.new_zeros((0, 1, H, W)), ) + + locations = self.prior_generator.single_level_grid_priors( + mask_feat.size()[2:], 0, device=mask_feat.device) + + rel_coords = relative_coordinate_maps(locations, pos_points, + pos_strides, + self.size_of_interest, + mask_feat.size()[2:]) + mask_head_inputs = torch.cat([rel_coords, mask_feat], dim=1) + mask_head_inputs = mask_head_inputs.reshape(1, -1, H, W) + + weights, biases = self.parse_dynamic_params(pos_param_preds) + mask_preds = self.dynamic_conv_forward(mask_head_inputs, weights, + biases, num_inst) + mask_preds = mask_preds.reshape(-1, H, W) + mask_preds = aligned_bilinear( + mask_preds.unsqueeze(0), + int(self.mask_feat_stride / self.mask_out_stride)).squeeze(0) + + return (mask_preds, ) + + def loss_by_feat(self, mask_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], positive_infos: InstanceList, + **kwargs) -> dict: + """Calculate the loss based on the features extracted by the mask head. + + Args: + mask_preds (list[Tensor]): List of predicted masks, each has + shape (num_classes, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``masks``, + and ``labels`` attributes. + batch_img_metas (list[dict]): Meta information of multiple images. + positive_infos (List[:obj:``InstanceData``]): Information of + positive samples of each image that are assigned in detection + head. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert positive_infos is not None, \ + 'positive_infos should not be None in `CondInstMaskHead`' + losses = dict() + + loss_mask = 0. + num_imgs = len(mask_preds) + total_pos = 0 + + for idx in range(num_imgs): + (mask_pred, pos_mask_targets, num_pos) = \ + self._get_targets_single( + mask_preds[idx], batch_gt_instances[idx], + positive_infos[idx]) + # mask loss + total_pos += num_pos + if num_pos == 0 or pos_mask_targets is None: + loss = mask_pred.new_zeros(1).mean() + else: + loss = self.loss_mask( + mask_pred, pos_mask_targets, + reduction_override='none').sum() + loss_mask += loss + + if total_pos == 0: + total_pos += 1 # avoid nan + loss_mask = loss_mask / total_pos + losses.update(loss_mask=loss_mask) + return losses + + def _get_targets_single(self, mask_preds: Tensor, + gt_instances: InstanceData, + positive_info: InstanceData): + """Compute targets for predictions of single image. + + Args: + mask_preds (Tensor): Predicted prototypes with shape + (num_classes, H, W). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes``, ``labels``, + and ``masks`` attributes. + positive_info (:obj:`InstanceData`): Information of positive + samples that are assigned in detection head. It usually + contains following keys. + + - pos_assigned_gt_inds (Tensor): Assigner GT indexes of + positive proposals, has shape (num_pos, ) + - pos_inds (Tensor): Positive index of image, has + shape (num_pos, ). + - param_pred (Tensor): Positive param preditions + with shape (num_pos, num_params). + + Returns: + tuple: Usually returns a tuple containing learning targets. + + - mask_preds (Tensor): Positive predicted mask with shape + (num_pos, mask_h, mask_w). + - pos_mask_targets (Tensor): Positive mask targets with shape + (num_pos, mask_h, mask_w). + - num_pos (int): Positive numbers. + """ + gt_bboxes = gt_instances.bboxes + device = gt_bboxes.device + gt_masks = gt_instances.masks.to_tensor( + dtype=torch.bool, device=device).float() + + # process with mask targets + pos_assigned_gt_inds = positive_info.get('pos_assigned_gt_inds') + scores = positive_info.get('scores') + centernesses = positive_info.get('centernesses') + num_pos = pos_assigned_gt_inds.size(0) + + if gt_masks.size(0) == 0 or num_pos == 0: + return mask_preds, None, 0 + # Since we're producing (near) full image masks, + # it'd take too much vram to backprop on every single mask. + # Thus we select only a subset. + if (self.max_masks_to_train != -1) and \ + (num_pos > self.max_masks_to_train): + perm = torch.randperm(num_pos) + select = perm[:self.max_masks_to_train] + mask_preds = mask_preds[select] + pos_assigned_gt_inds = pos_assigned_gt_inds[select] + num_pos = self.max_masks_to_train + elif self.topk_masks_per_img != -1: + unique_gt_inds = pos_assigned_gt_inds.unique() + num_inst_per_gt = max( + int(self.topk_masks_per_img / len(unique_gt_inds)), 1) + + keep_mask_preds = [] + keep_pos_assigned_gt_inds = [] + for gt_ind in unique_gt_inds: + per_inst_pos_inds = (pos_assigned_gt_inds == gt_ind) + mask_preds_per_inst = mask_preds[per_inst_pos_inds] + gt_inds_per_inst = pos_assigned_gt_inds[per_inst_pos_inds] + if sum(per_inst_pos_inds) > num_inst_per_gt: + per_inst_scores = scores[per_inst_pos_inds].sigmoid().max( + dim=1)[0] + per_inst_centerness = centernesses[ + per_inst_pos_inds].sigmoid().reshape(-1, ) + select = (per_inst_scores * per_inst_centerness).topk( + k=num_inst_per_gt, dim=0)[1] + mask_preds_per_inst = mask_preds_per_inst[select] + gt_inds_per_inst = gt_inds_per_inst[select] + keep_mask_preds.append(mask_preds_per_inst) + keep_pos_assigned_gt_inds.append(gt_inds_per_inst) + mask_preds = torch.cat(keep_mask_preds) + pos_assigned_gt_inds = torch.cat(keep_pos_assigned_gt_inds) + num_pos = pos_assigned_gt_inds.size(0) + + # Follow the origin implement + start = int(self.mask_out_stride // 2) + gt_masks = gt_masks[:, start::self.mask_out_stride, + start::self.mask_out_stride] + gt_masks = gt_masks.gt(0.5).float() + pos_mask_targets = gt_masks[pos_assigned_gt_inds] + + return (mask_preds, pos_mask_targets, num_pos) + + def predict_by_feat(self, + mask_preds: List[Tensor], + results_list: InstanceList, + batch_img_metas: List[dict], + rescale: bool = True, + **kwargs) -> InstanceList: + """Transform a batch of output features extracted from the head into + mask results. + + Args: + mask_preds (list[Tensor]): Predicted prototypes with shape + (num_classes, H, W). + results_list (List[:obj:``InstanceData``]): BBoxHead results. + batch_img_metas (list[dict]): Meta information of all images. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Processed results of multiple + images.Each :obj:`InstanceData` usually contains + following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + assert len(mask_preds) == len(results_list) == len(batch_img_metas) + + for img_id in range(len(batch_img_metas)): + img_meta = batch_img_metas[img_id] + results = results_list[img_id] + bboxes = results.bboxes + mask_pred = mask_preds[img_id] + if bboxes.shape[0] == 0 or mask_pred.shape[0] == 0: + results_list[img_id] = empty_instances( + [img_meta], + bboxes.device, + task_type='mask', + instance_results=[results])[0] + else: + im_mask = self._predict_by_feat_single( + mask_preds=mask_pred, + bboxes=bboxes, + img_meta=img_meta, + rescale=rescale) + results.masks = im_mask + return results_list + + def _predict_by_feat_single(self, + mask_preds: Tensor, + bboxes: Tensor, + img_meta: dict, + rescale: bool, + cfg: OptConfigType = None): + """Transform a single image's features extracted from the head into + mask results. + + Args: + mask_preds (Tensor): Predicted prototypes, has shape [H, W, N]. + img_meta (dict): Meta information of each image, e.g., + image size, scaling factor, etc. + rescale (bool): If rescale is False, then returned masks will + fit the scale of imgs[0]. + cfg (dict, optional): Config used in test phase. + Defaults to None. + + Returns: + :obj:`InstanceData`: Processed results of single image. + it usually contains following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + cfg = self.test_cfg if cfg is None else cfg + scale_factor = bboxes.new_tensor(img_meta['scale_factor']).repeat( + (1, 2)) + img_h, img_w = img_meta['img_shape'][:2] + ori_h, ori_w = img_meta['ori_shape'][:2] + + mask_preds = mask_preds.sigmoid().unsqueeze(0) + mask_preds = aligned_bilinear(mask_preds, self.mask_out_stride) + mask_preds = mask_preds[:, :, :img_h, :img_w] + if rescale: # in-placed rescale the bboxes + scale_factor = bboxes.new_tensor(img_meta['scale_factor']).repeat( + (1, 2)) + bboxes /= scale_factor + + masks = F.interpolate( + mask_preds, (ori_h, ori_w), + mode='bilinear', + align_corners=False).squeeze(0) > cfg.mask_thr + else: + masks = mask_preds.squeeze(0) > cfg.mask_thr + + return masks diff --git a/mmdetection/mmdet/models/dense_heads/conditional_detr_head.py b/mmdetection/mmdet/models/dense_heads/conditional_detr_head.py new file mode 100644 index 00000000..cc2df2c2 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/conditional_detr_head.py @@ -0,0 +1,168 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch +import torch.nn as nn +from mmengine.model import bias_init_with_prob +from torch import Tensor + +from mmdet.models.layers.transformer import inverse_sigmoid +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import InstanceList +from .detr_head import DETRHead + + +@MODELS.register_module() +class ConditionalDETRHead(DETRHead): + """Head of Conditional DETR. Conditional DETR: Conditional DETR for Fast + Training Convergence. More details can be found in the `paper. + + `_ . + """ + + def init_weights(self): + """Initialize weights of the transformer head.""" + super().init_weights() + # The initialization below for transformer head is very + # important as we use Focal_loss for loss_cls + if self.loss_cls.use_sigmoid: + bias_init = bias_init_with_prob(0.01) + nn.init.constant_(self.fc_cls.bias, bias_init) + + def forward(self, hidden_states: Tensor, + references: Tensor) -> Tuple[Tensor, Tensor]: + """"Forward function. + + Args: + hidden_states (Tensor): Features from transformer decoder. If + `return_intermediate_dec` is True output has shape + (num_decoder_layers, bs, num_queries, dim), else has shape (1, + bs, num_queries, dim) which only contains the last layer + outputs. + references (Tensor): References from transformer decoder, has + shape (bs, num_queries, 2). + Returns: + tuple[Tensor]: results of head containing the following tensor. + + - layers_cls_scores (Tensor): Outputs from the classification head, + shape (num_decoder_layers, bs, num_queries, cls_out_channels). + Note cls_out_channels should include background. + - layers_bbox_preds (Tensor): Sigmoid outputs from the regression + head with normalized coordinate format (cx, cy, w, h), has shape + (num_decoder_layers, bs, num_queries, 4). + """ + + references_unsigmoid = inverse_sigmoid(references) + layers_bbox_preds = [] + for layer_id in range(hidden_states.shape[0]): + tmp_reg_preds = self.fc_reg( + self.activate(self.reg_ffn(hidden_states[layer_id]))) + tmp_reg_preds[..., :2] += references_unsigmoid + outputs_coord = tmp_reg_preds.sigmoid() + layers_bbox_preds.append(outputs_coord) + layers_bbox_preds = torch.stack(layers_bbox_preds) + + layers_cls_scores = self.fc_cls(hidden_states) + return layers_cls_scores, layers_bbox_preds + + def loss(self, hidden_states: Tensor, references: Tensor, + batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the features of the upstream network. + + Args: + hidden_states (Tensor): Features from the transformer decoder, has + shape (num_decoder_layers, bs, num_queries, dim). + references (Tensor): References from the transformer decoder, has + shape (num_decoder_layers, bs, num_queries, 2). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + outs = self(hidden_states, references) + loss_inputs = outs + (batch_gt_instances, batch_img_metas) + losses = self.loss_by_feat(*loss_inputs) + return losses + + def loss_and_predict( + self, hidden_states: Tensor, references: Tensor, + batch_data_samples: SampleList) -> Tuple[dict, InstanceList]: + """Perform forward propagation of the head, then calculate loss and + predictions from the features and data samples. Over-write because + img_metas are needed as inputs for bbox_head. + + Args: + hidden_states (Tensor): Features from the transformer decoder, has + shape (num_decoder_layers, bs, num_queries, dim). + references (Tensor): References from the transformer decoder, has + shape (num_decoder_layers, bs, num_queries, 2). + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns: + tuple: The return value is a tuple contains: + + - losses: (dict[str, Tensor]): A dictionary of loss components. + - predictions (list[:obj:`InstanceData`]): Detection + results of each image after the post process. + """ + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + outs = self(hidden_states, references) + loss_inputs = outs + (batch_gt_instances, batch_img_metas) + losses = self.loss_by_feat(*loss_inputs) + + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas) + return losses, predictions + + def predict(self, + hidden_states: Tensor, + references: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> InstanceList: + """Perform forward propagation of the detection head and predict + detection results on the features of the upstream network. Over-write + because img_metas are needed as inputs for bbox_head. + + Args: + hidden_states (Tensor): Features from the transformer decoder, has + shape (num_decoder_layers, bs, num_queries, dim). + references (Tensor): References from the transformer decoder, has + shape (num_decoder_layers, bs, num_queries, 2). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): Whether to rescale the results. + Defaults to True. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + last_layer_hidden_state = hidden_states[-1].unsqueeze(0) + outs = self(last_layer_hidden_state, references) + + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas, rescale=rescale) + + return predictions diff --git a/mmdetection/mmdet/models/dense_heads/corner_head.py b/mmdetection/mmdet/models/dense_heads/corner_head.py new file mode 100644 index 00000000..0cec71d5 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/corner_head.py @@ -0,0 +1,1084 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from logging import warning +from math import ceil, log +from typing import List, Optional, Sequence, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmcv.ops import CornerPool, batched_nms +from mmengine.config import ConfigDict +from mmengine.model import BaseModule, bias_init_with_prob +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList, OptMultiConfig) +from ..utils import (gather_feat, gaussian_radius, gen_gaussian_target, + get_local_maximum, get_topk_from_heatmap, multi_apply, + transpose_and_gather_feat) +from .base_dense_head import BaseDenseHead + + +class BiCornerPool(BaseModule): + """Bidirectional Corner Pooling Module (TopLeft, BottomRight, etc.) + + Args: + in_channels (int): Input channels of module. + directions (list[str]): Directions of two CornerPools. + out_channels (int): Output channels of module. + feat_channels (int): Feature channels of module. + norm_cfg (:obj:`ConfigDict` or dict): Dictionary to construct + and config norm layer. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to + control the initialization. + """ + + def __init__(self, + in_channels: int, + directions: List[int], + feat_channels: int = 128, + out_channels: int = 128, + norm_cfg: ConfigType = dict(type='BN', requires_grad=True), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg) + self.direction1_conv = ConvModule( + in_channels, feat_channels, 3, padding=1, norm_cfg=norm_cfg) + self.direction2_conv = ConvModule( + in_channels, feat_channels, 3, padding=1, norm_cfg=norm_cfg) + + self.aftpool_conv = ConvModule( + feat_channels, + out_channels, + 3, + padding=1, + norm_cfg=norm_cfg, + act_cfg=None) + + self.conv1 = ConvModule( + in_channels, out_channels, 1, norm_cfg=norm_cfg, act_cfg=None) + self.conv2 = ConvModule( + in_channels, out_channels, 3, padding=1, norm_cfg=norm_cfg) + + self.direction1_pool = CornerPool(directions[0]) + self.direction2_pool = CornerPool(directions[1]) + self.relu = nn.ReLU(inplace=True) + + def forward(self, x: Tensor) -> Tensor: + """Forward features from the upstream network. + + Args: + x (tensor): Input feature of BiCornerPool. + + Returns: + conv2 (tensor): Output feature of BiCornerPool. + """ + direction1_conv = self.direction1_conv(x) + direction2_conv = self.direction2_conv(x) + direction1_feat = self.direction1_pool(direction1_conv) + direction2_feat = self.direction2_pool(direction2_conv) + aftpool_conv = self.aftpool_conv(direction1_feat + direction2_feat) + conv1 = self.conv1(x) + relu = self.relu(aftpool_conv + conv1) + conv2 = self.conv2(relu) + return conv2 + + +@MODELS.register_module() +class CornerHead(BaseDenseHead): + """Head of CornerNet: Detecting Objects as Paired Keypoints. + + Code is modified from the `official github repo + `_ . + + More details can be found in the `paper + `_ . + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + num_feat_levels (int): Levels of feature from the previous module. + 2 for HourglassNet-104 and 1 for HourglassNet-52. Because + HourglassNet-104 outputs the final feature and intermediate + supervision feature and HourglassNet-52 only outputs the final + feature. Defaults to 2. + corner_emb_channels (int): Channel of embedding vector. Defaults to 1. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config. + Useless in CornerHead, but we keep this variable for + SingleStageDetector. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + CornerHead. + loss_heatmap (:obj:`ConfigDict` or dict): Config of corner heatmap + loss. Defaults to GaussianFocalLoss. + loss_embedding (:obj:`ConfigDict` or dict): Config of corner embedding + loss. Defaults to AssociativeEmbeddingLoss. + loss_offset (:obj:`ConfigDict` or dict): Config of corner offset loss. + Defaults to SmoothL1Loss. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + num_feat_levels: int = 2, + corner_emb_channels: int = 1, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + loss_heatmap: ConfigType = dict( + type='GaussianFocalLoss', + alpha=2.0, + gamma=4.0, + loss_weight=1), + loss_embedding: ConfigType = dict( + type='AssociativeEmbeddingLoss', + pull_weight=0.25, + push_weight=0.25), + loss_offset: ConfigType = dict( + type='SmoothL1Loss', beta=1.0, loss_weight=1), + init_cfg: OptMultiConfig = None) -> None: + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + super().__init__(init_cfg=init_cfg) + self.num_classes = num_classes + self.in_channels = in_channels + self.corner_emb_channels = corner_emb_channels + self.with_corner_emb = self.corner_emb_channels > 0 + self.corner_offset_channels = 2 + self.num_feat_levels = num_feat_levels + self.loss_heatmap = MODELS.build( + loss_heatmap) if loss_heatmap is not None else None + self.loss_embedding = MODELS.build( + loss_embedding) if loss_embedding is not None else None + self.loss_offset = MODELS.build( + loss_offset) if loss_offset is not None else None + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + self._init_layers() + + def _make_layers(self, + out_channels: int, + in_channels: int = 256, + feat_channels: int = 256) -> nn.Sequential: + """Initialize conv sequential for CornerHead.""" + return nn.Sequential( + ConvModule(in_channels, feat_channels, 3, padding=1), + ConvModule( + feat_channels, out_channels, 1, norm_cfg=None, act_cfg=None)) + + def _init_corner_kpt_layers(self) -> None: + """Initialize corner keypoint layers. + + Including corner heatmap branch and corner offset branch. Each branch + has two parts: prefix `tl_` for top-left and `br_` for bottom-right. + """ + self.tl_pool, self.br_pool = nn.ModuleList(), nn.ModuleList() + self.tl_heat, self.br_heat = nn.ModuleList(), nn.ModuleList() + self.tl_off, self.br_off = nn.ModuleList(), nn.ModuleList() + + for _ in range(self.num_feat_levels): + self.tl_pool.append( + BiCornerPool( + self.in_channels, ['top', 'left'], + out_channels=self.in_channels)) + self.br_pool.append( + BiCornerPool( + self.in_channels, ['bottom', 'right'], + out_channels=self.in_channels)) + + self.tl_heat.append( + self._make_layers( + out_channels=self.num_classes, + in_channels=self.in_channels)) + self.br_heat.append( + self._make_layers( + out_channels=self.num_classes, + in_channels=self.in_channels)) + + self.tl_off.append( + self._make_layers( + out_channels=self.corner_offset_channels, + in_channels=self.in_channels)) + self.br_off.append( + self._make_layers( + out_channels=self.corner_offset_channels, + in_channels=self.in_channels)) + + def _init_corner_emb_layers(self) -> None: + """Initialize corner embedding layers. + + Only include corner embedding branch with two parts: prefix `tl_` for + top-left and `br_` for bottom-right. + """ + self.tl_emb, self.br_emb = nn.ModuleList(), nn.ModuleList() + + for _ in range(self.num_feat_levels): + self.tl_emb.append( + self._make_layers( + out_channels=self.corner_emb_channels, + in_channels=self.in_channels)) + self.br_emb.append( + self._make_layers( + out_channels=self.corner_emb_channels, + in_channels=self.in_channels)) + + def _init_layers(self) -> None: + """Initialize layers for CornerHead. + + Including two parts: corner keypoint layers and corner embedding layers + """ + self._init_corner_kpt_layers() + if self.with_corner_emb: + self._init_corner_emb_layers() + + def init_weights(self) -> None: + super().init_weights() + bias_init = bias_init_with_prob(0.1) + for i in range(self.num_feat_levels): + # The initialization of parameters are different between + # nn.Conv2d and ConvModule. Our experiments show that + # using the original initialization of nn.Conv2d increases + # the final mAP by about 0.2% + self.tl_heat[i][-1].conv.reset_parameters() + self.tl_heat[i][-1].conv.bias.data.fill_(bias_init) + self.br_heat[i][-1].conv.reset_parameters() + self.br_heat[i][-1].conv.bias.data.fill_(bias_init) + self.tl_off[i][-1].conv.reset_parameters() + self.br_off[i][-1].conv.reset_parameters() + if self.with_corner_emb: + self.tl_emb[i][-1].conv.reset_parameters() + self.br_emb[i][-1].conv.reset_parameters() + + def forward(self, feats: Tuple[Tensor]) -> tuple: + """Forward features from the upstream network. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually a tuple of corner heatmaps, offset heatmaps and + embedding heatmaps. + - tl_heats (list[Tensor]): Top-left corner heatmaps for all + levels, each is a 4D-tensor, the channels number is + num_classes. + - br_heats (list[Tensor]): Bottom-right corner heatmaps for all + levels, each is a 4D-tensor, the channels number is + num_classes. + - tl_embs (list[Tensor] | list[None]): Top-left embedding + heatmaps for all levels, each is a 4D-tensor or None. + If not None, the channels number is corner_emb_channels. + - br_embs (list[Tensor] | list[None]): Bottom-right embedding + heatmaps for all levels, each is a 4D-tensor or None. + If not None, the channels number is corner_emb_channels. + - tl_offs (list[Tensor]): Top-left offset heatmaps for all + levels, each is a 4D-tensor. The channels number is + corner_offset_channels. + - br_offs (list[Tensor]): Bottom-right offset heatmaps for all + levels, each is a 4D-tensor. The channels number is + corner_offset_channels. + """ + lvl_ind = list(range(self.num_feat_levels)) + return multi_apply(self.forward_single, feats, lvl_ind) + + def forward_single(self, + x: Tensor, + lvl_ind: int, + return_pool: bool = False) -> List[Tensor]: + """Forward feature of a single level. + + Args: + x (Tensor): Feature of a single level. + lvl_ind (int): Level index of current feature. + return_pool (bool): Return corner pool feature or not. + Defaults to False. + + Returns: + tuple[Tensor]: A tuple of CornerHead's output for current feature + level. Containing the following Tensors: + + - tl_heat (Tensor): Predicted top-left corner heatmap. + - br_heat (Tensor): Predicted bottom-right corner heatmap. + - tl_emb (Tensor | None): Predicted top-left embedding heatmap. + None for `self.with_corner_emb == False`. + - br_emb (Tensor | None): Predicted bottom-right embedding + heatmap. None for `self.with_corner_emb == False`. + - tl_off (Tensor): Predicted top-left offset heatmap. + - br_off (Tensor): Predicted bottom-right offset heatmap. + - tl_pool (Tensor): Top-left corner pool feature. Not must + have. + - br_pool (Tensor): Bottom-right corner pool feature. Not must + have. + """ + tl_pool = self.tl_pool[lvl_ind](x) + tl_heat = self.tl_heat[lvl_ind](tl_pool) + br_pool = self.br_pool[lvl_ind](x) + br_heat = self.br_heat[lvl_ind](br_pool) + + tl_emb, br_emb = None, None + if self.with_corner_emb: + tl_emb = self.tl_emb[lvl_ind](tl_pool) + br_emb = self.br_emb[lvl_ind](br_pool) + + tl_off = self.tl_off[lvl_ind](tl_pool) + br_off = self.br_off[lvl_ind](br_pool) + + result_list = [tl_heat, br_heat, tl_emb, br_emb, tl_off, br_off] + if return_pool: + result_list.append(tl_pool) + result_list.append(br_pool) + + return result_list + + def get_targets(self, + gt_bboxes: List[Tensor], + gt_labels: List[Tensor], + feat_shape: Sequence[int], + img_shape: Sequence[int], + with_corner_emb: bool = False, + with_guiding_shift: bool = False, + with_centripetal_shift: bool = False) -> dict: + """Generate corner targets. + + Including corner heatmap, corner offset. + + Optional: corner embedding, corner guiding shift, centripetal shift. + + For CornerNet, we generate corner heatmap, corner offset and corner + embedding from this function. + + For CentripetalNet, we generate corner heatmap, corner offset, guiding + shift and centripetal shift from this function. + + Args: + gt_bboxes (list[Tensor]): Ground truth bboxes of each image, each + has shape (num_gt, 4). + gt_labels (list[Tensor]): Ground truth labels of each box, each has + shape (num_gt, ). + feat_shape (Sequence[int]): Shape of output feature, + [batch, channel, height, width]. + img_shape (Sequence[int]): Shape of input image, + [height, width, channel]. + with_corner_emb (bool): Generate corner embedding target or not. + Defaults to False. + with_guiding_shift (bool): Generate guiding shift target or not. + Defaults to False. + with_centripetal_shift (bool): Generate centripetal shift target or + not. Defaults to False. + + Returns: + dict: Ground truth of corner heatmap, corner offset, corner + embedding, guiding shift and centripetal shift. Containing the + following keys: + + - topleft_heatmap (Tensor): Ground truth top-left corner + heatmap. + - bottomright_heatmap (Tensor): Ground truth bottom-right + corner heatmap. + - topleft_offset (Tensor): Ground truth top-left corner offset. + - bottomright_offset (Tensor): Ground truth bottom-right corner + offset. + - corner_embedding (list[list[list[int]]]): Ground truth corner + embedding. Not must have. + - topleft_guiding_shift (Tensor): Ground truth top-left corner + guiding shift. Not must have. + - bottomright_guiding_shift (Tensor): Ground truth bottom-right + corner guiding shift. Not must have. + - topleft_centripetal_shift (Tensor): Ground truth top-left + corner centripetal shift. Not must have. + - bottomright_centripetal_shift (Tensor): Ground truth + bottom-right corner centripetal shift. Not must have. + """ + batch_size, _, height, width = feat_shape + img_h, img_w = img_shape[:2] + + width_ratio = float(width / img_w) + height_ratio = float(height / img_h) + + gt_tl_heatmap = gt_bboxes[-1].new_zeros( + [batch_size, self.num_classes, height, width]) + gt_br_heatmap = gt_bboxes[-1].new_zeros( + [batch_size, self.num_classes, height, width]) + gt_tl_offset = gt_bboxes[-1].new_zeros([batch_size, 2, height, width]) + gt_br_offset = gt_bboxes[-1].new_zeros([batch_size, 2, height, width]) + + if with_corner_emb: + match = [] + + # Guiding shift is a kind of offset, from center to corner + if with_guiding_shift: + gt_tl_guiding_shift = gt_bboxes[-1].new_zeros( + [batch_size, 2, height, width]) + gt_br_guiding_shift = gt_bboxes[-1].new_zeros( + [batch_size, 2, height, width]) + # Centripetal shift is also a kind of offset, from center to corner + # and normalized by log. + if with_centripetal_shift: + gt_tl_centripetal_shift = gt_bboxes[-1].new_zeros( + [batch_size, 2, height, width]) + gt_br_centripetal_shift = gt_bboxes[-1].new_zeros( + [batch_size, 2, height, width]) + + for batch_id in range(batch_size): + # Ground truth of corner embedding per image is a list of coord set + corner_match = [] + for box_id in range(len(gt_labels[batch_id])): + left, top, right, bottom = gt_bboxes[batch_id][box_id] + center_x = (left + right) / 2.0 + center_y = (top + bottom) / 2.0 + label = gt_labels[batch_id][box_id] + + # Use coords in the feature level to generate ground truth + scale_left = left * width_ratio + scale_right = right * width_ratio + scale_top = top * height_ratio + scale_bottom = bottom * height_ratio + scale_center_x = center_x * width_ratio + scale_center_y = center_y * height_ratio + + # Int coords on feature map/ground truth tensor + left_idx = int(min(scale_left, width - 1)) + right_idx = int(min(scale_right, width - 1)) + top_idx = int(min(scale_top, height - 1)) + bottom_idx = int(min(scale_bottom, height - 1)) + + # Generate gaussian heatmap + scale_box_width = ceil(scale_right - scale_left) + scale_box_height = ceil(scale_bottom - scale_top) + radius = gaussian_radius((scale_box_height, scale_box_width), + min_overlap=0.3) + radius = max(0, int(radius)) + gt_tl_heatmap[batch_id, label] = gen_gaussian_target( + gt_tl_heatmap[batch_id, label], [left_idx, top_idx], + radius) + gt_br_heatmap[batch_id, label] = gen_gaussian_target( + gt_br_heatmap[batch_id, label], [right_idx, bottom_idx], + radius) + + # Generate corner offset + left_offset = scale_left - left_idx + top_offset = scale_top - top_idx + right_offset = scale_right - right_idx + bottom_offset = scale_bottom - bottom_idx + gt_tl_offset[batch_id, 0, top_idx, left_idx] = left_offset + gt_tl_offset[batch_id, 1, top_idx, left_idx] = top_offset + gt_br_offset[batch_id, 0, bottom_idx, right_idx] = right_offset + gt_br_offset[batch_id, 1, bottom_idx, + right_idx] = bottom_offset + + # Generate corner embedding + if with_corner_emb: + corner_match.append([[top_idx, left_idx], + [bottom_idx, right_idx]]) + # Generate guiding shift + if with_guiding_shift: + gt_tl_guiding_shift[batch_id, 0, top_idx, + left_idx] = scale_center_x - left_idx + gt_tl_guiding_shift[batch_id, 1, top_idx, + left_idx] = scale_center_y - top_idx + gt_br_guiding_shift[batch_id, 0, bottom_idx, + right_idx] = right_idx - scale_center_x + gt_br_guiding_shift[ + batch_id, 1, bottom_idx, + right_idx] = bottom_idx - scale_center_y + # Generate centripetal shift + if with_centripetal_shift: + gt_tl_centripetal_shift[batch_id, 0, top_idx, + left_idx] = log(scale_center_x - + scale_left) + gt_tl_centripetal_shift[batch_id, 1, top_idx, + left_idx] = log(scale_center_y - + scale_top) + gt_br_centripetal_shift[batch_id, 0, bottom_idx, + right_idx] = log(scale_right - + scale_center_x) + gt_br_centripetal_shift[batch_id, 1, bottom_idx, + right_idx] = log(scale_bottom - + scale_center_y) + + if with_corner_emb: + match.append(corner_match) + + target_result = dict( + topleft_heatmap=gt_tl_heatmap, + topleft_offset=gt_tl_offset, + bottomright_heatmap=gt_br_heatmap, + bottomright_offset=gt_br_offset) + + if with_corner_emb: + target_result.update(corner_embedding=match) + if with_guiding_shift: + target_result.update( + topleft_guiding_shift=gt_tl_guiding_shift, + bottomright_guiding_shift=gt_br_guiding_shift) + if with_centripetal_shift: + target_result.update( + topleft_centripetal_shift=gt_tl_centripetal_shift, + bottomright_centripetal_shift=gt_br_centripetal_shift) + + return target_result + + def loss_by_feat( + self, + tl_heats: List[Tensor], + br_heats: List[Tensor], + tl_embs: List[Tensor], + br_embs: List[Tensor], + tl_offs: List[Tensor], + br_offs: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + tl_heats (list[Tensor]): Top-left corner heatmaps for each level + with shape (N, num_classes, H, W). + br_heats (list[Tensor]): Bottom-right corner heatmaps for each + level with shape (N, num_classes, H, W). + tl_embs (list[Tensor]): Top-left corner embeddings for each level + with shape (N, corner_emb_channels, H, W). + br_embs (list[Tensor]): Bottom-right corner embeddings for each + level with shape (N, corner_emb_channels, H, W). + tl_offs (list[Tensor]): Top-left corner offsets for each level + with shape (N, corner_offset_channels, H, W). + br_offs (list[Tensor]): Bottom-right corner offsets for each level + with shape (N, corner_offset_channels, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Specify which bounding boxes can be ignored when computing + the loss. + + Returns: + dict[str, Tensor]: A dictionary of loss components. Containing the + following losses: + + - det_loss (list[Tensor]): Corner keypoint losses of all + feature levels. + - pull_loss (list[Tensor]): Part one of AssociativeEmbedding + losses of all feature levels. + - push_loss (list[Tensor]): Part two of AssociativeEmbedding + losses of all feature levels. + - off_loss (list[Tensor]): Corner offset losses of all feature + levels. + """ + gt_bboxes = [ + gt_instances.bboxes for gt_instances in batch_gt_instances + ] + gt_labels = [ + gt_instances.labels for gt_instances in batch_gt_instances + ] + + targets = self.get_targets( + gt_bboxes, + gt_labels, + tl_heats[-1].shape, + batch_img_metas[0]['batch_input_shape'], + with_corner_emb=self.with_corner_emb) + mlvl_targets = [targets for _ in range(self.num_feat_levels)] + det_losses, pull_losses, push_losses, off_losses = multi_apply( + self.loss_by_feat_single, tl_heats, br_heats, tl_embs, br_embs, + tl_offs, br_offs, mlvl_targets) + loss_dict = dict(det_loss=det_losses, off_loss=off_losses) + if self.with_corner_emb: + loss_dict.update(pull_loss=pull_losses, push_loss=push_losses) + return loss_dict + + def loss_by_feat_single(self, tl_hmp: Tensor, br_hmp: Tensor, + tl_emb: Optional[Tensor], br_emb: Optional[Tensor], + tl_off: Tensor, br_off: Tensor, + targets: dict) -> Tuple[Tensor, ...]: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + tl_hmp (Tensor): Top-left corner heatmap for current level with + shape (N, num_classes, H, W). + br_hmp (Tensor): Bottom-right corner heatmap for current level with + shape (N, num_classes, H, W). + tl_emb (Tensor, optional): Top-left corner embedding for current + level with shape (N, corner_emb_channels, H, W). + br_emb (Tensor, optional): Bottom-right corner embedding for + current level with shape (N, corner_emb_channels, H, W). + tl_off (Tensor): Top-left corner offset for current level with + shape (N, corner_offset_channels, H, W). + br_off (Tensor): Bottom-right corner offset for current level with + shape (N, corner_offset_channels, H, W). + targets (dict): Corner target generated by `get_targets`. + + Returns: + tuple[torch.Tensor]: Losses of the head's different branches + containing the following losses: + + - det_loss (Tensor): Corner keypoint loss. + - pull_loss (Tensor): Part one of AssociativeEmbedding loss. + - push_loss (Tensor): Part two of AssociativeEmbedding loss. + - off_loss (Tensor): Corner offset loss. + """ + gt_tl_hmp = targets['topleft_heatmap'] + gt_br_hmp = targets['bottomright_heatmap'] + gt_tl_off = targets['topleft_offset'] + gt_br_off = targets['bottomright_offset'] + gt_embedding = targets['corner_embedding'] + + # Detection loss + tl_det_loss = self.loss_heatmap( + tl_hmp.sigmoid(), + gt_tl_hmp, + avg_factor=max(1, + gt_tl_hmp.eq(1).sum())) + br_det_loss = self.loss_heatmap( + br_hmp.sigmoid(), + gt_br_hmp, + avg_factor=max(1, + gt_br_hmp.eq(1).sum())) + det_loss = (tl_det_loss + br_det_loss) / 2.0 + + # AssociativeEmbedding loss + if self.with_corner_emb and self.loss_embedding is not None: + pull_loss, push_loss = self.loss_embedding(tl_emb, br_emb, + gt_embedding) + else: + pull_loss, push_loss = None, None + + # Offset loss + # We only compute the offset loss at the real corner position. + # The value of real corner would be 1 in heatmap ground truth. + # The mask is computed in class agnostic mode and its shape is + # batch * 1 * width * height. + tl_off_mask = gt_tl_hmp.eq(1).sum(1).gt(0).unsqueeze(1).type_as( + gt_tl_hmp) + br_off_mask = gt_br_hmp.eq(1).sum(1).gt(0).unsqueeze(1).type_as( + gt_br_hmp) + tl_off_loss = self.loss_offset( + tl_off, + gt_tl_off, + tl_off_mask, + avg_factor=max(1, tl_off_mask.sum())) + br_off_loss = self.loss_offset( + br_off, + gt_br_off, + br_off_mask, + avg_factor=max(1, br_off_mask.sum())) + + off_loss = (tl_off_loss + br_off_loss) / 2.0 + + return det_loss, pull_loss, push_loss, off_loss + + def predict_by_feat(self, + tl_heats: List[Tensor], + br_heats: List[Tensor], + tl_embs: List[Tensor], + br_embs: List[Tensor], + tl_offs: List[Tensor], + br_offs: List[Tensor], + batch_img_metas: Optional[List[dict]] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Args: + tl_heats (list[Tensor]): Top-left corner heatmaps for each level + with shape (N, num_classes, H, W). + br_heats (list[Tensor]): Bottom-right corner heatmaps for each + level with shape (N, num_classes, H, W). + tl_embs (list[Tensor]): Top-left corner embeddings for each level + with shape (N, corner_emb_channels, H, W). + br_embs (list[Tensor]): Bottom-right corner embeddings for each + level with shape (N, corner_emb_channels, H, W). + tl_offs (list[Tensor]): Top-left corner offsets for each level + with shape (N, corner_offset_channels, H, W). + br_offs (list[Tensor]): Bottom-right corner offsets for each level + with shape (N, corner_offset_channels, H, W). + batch_img_metas (list[dict], optional): Batch image meta info. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert tl_heats[-1].shape[0] == br_heats[-1].shape[0] == len( + batch_img_metas) + result_list = [] + for img_id in range(len(batch_img_metas)): + result_list.append( + self._predict_by_feat_single( + tl_heats[-1][img_id:img_id + 1, :], + br_heats[-1][img_id:img_id + 1, :], + tl_offs[-1][img_id:img_id + 1, :], + br_offs[-1][img_id:img_id + 1, :], + batch_img_metas[img_id], + tl_emb=tl_embs[-1][img_id:img_id + 1, :], + br_emb=br_embs[-1][img_id:img_id + 1, :], + rescale=rescale, + with_nms=with_nms)) + + return result_list + + def _predict_by_feat_single(self, + tl_heat: Tensor, + br_heat: Tensor, + tl_off: Tensor, + br_off: Tensor, + img_meta: dict, + tl_emb: Optional[Tensor] = None, + br_emb: Optional[Tensor] = None, + tl_centripetal_shift: Optional[Tensor] = None, + br_centripetal_shift: Optional[Tensor] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + tl_heat (Tensor): Top-left corner heatmap for current level with + shape (N, num_classes, H, W). + br_heat (Tensor): Bottom-right corner heatmap for current level + with shape (N, num_classes, H, W). + tl_off (Tensor): Top-left corner offset for current level with + shape (N, corner_offset_channels, H, W). + br_off (Tensor): Bottom-right corner offset for current level with + shape (N, corner_offset_channels, H, W). + img_meta (dict): Meta information of current image, e.g., + image size, scaling factor, etc. + tl_emb (Tensor): Top-left corner embedding for current level with + shape (N, corner_emb_channels, H, W). + br_emb (Tensor): Bottom-right corner embedding for current level + with shape (N, corner_emb_channels, H, W). + tl_centripetal_shift: Top-left corner's centripetal shift for + current level with shape (N, 2, H, W). + br_centripetal_shift: Bottom-right corner's centripetal shift for + current level with shape (N, 2, H, W). + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + if isinstance(img_meta, (list, tuple)): + img_meta = img_meta[0] + + batch_bboxes, batch_scores, batch_clses = self._decode_heatmap( + tl_heat=tl_heat.sigmoid(), + br_heat=br_heat.sigmoid(), + tl_off=tl_off, + br_off=br_off, + tl_emb=tl_emb, + br_emb=br_emb, + tl_centripetal_shift=tl_centripetal_shift, + br_centripetal_shift=br_centripetal_shift, + img_meta=img_meta, + k=self.test_cfg.corner_topk, + kernel=self.test_cfg.local_maximum_kernel, + distance_threshold=self.test_cfg.distance_threshold) + + if rescale and 'scale_factor' in img_meta: + batch_bboxes /= batch_bboxes.new_tensor( + img_meta['scale_factor']).repeat((1, 2)) + + bboxes = batch_bboxes.view([-1, 4]) + scores = batch_scores.view(-1) + clses = batch_clses.view(-1) + + det_bboxes = torch.cat([bboxes, scores.unsqueeze(-1)], -1) + keepinds = (det_bboxes[:, -1] > -0.1) + det_bboxes = det_bboxes[keepinds] + det_labels = clses[keepinds] + + if with_nms: + det_bboxes, det_labels = self._bboxes_nms(det_bboxes, det_labels, + self.test_cfg) + + results = InstanceData() + results.bboxes = det_bboxes[..., :4] + results.scores = det_bboxes[..., 4] + results.labels = det_labels + return results + + def _bboxes_nms(self, bboxes: Tensor, labels: Tensor, + cfg: ConfigDict) -> Tuple[Tensor, Tensor]: + """bboxes nms.""" + if 'nms_cfg' in cfg: + warning.warn('nms_cfg in test_cfg will be deprecated. ' + 'Please rename it as nms') + if 'nms' not in cfg: + cfg.nms = cfg.nms_cfg + + if labels.numel() > 0: + max_num = cfg.max_per_img + bboxes, keep = batched_nms(bboxes[:, :4], bboxes[:, + -1].contiguous(), + labels, cfg.nms) + if max_num > 0: + bboxes = bboxes[:max_num] + labels = labels[keep][:max_num] + + return bboxes, labels + + def _decode_heatmap(self, + tl_heat: Tensor, + br_heat: Tensor, + tl_off: Tensor, + br_off: Tensor, + tl_emb: Optional[Tensor] = None, + br_emb: Optional[Tensor] = None, + tl_centripetal_shift: Optional[Tensor] = None, + br_centripetal_shift: Optional[Tensor] = None, + img_meta: Optional[dict] = None, + k: int = 100, + kernel: int = 3, + distance_threshold: float = 0.5, + num_dets: int = 1000) -> Tuple[Tensor, Tensor, Tensor]: + """Transform outputs into detections raw bbox prediction. + + Args: + tl_heat (Tensor): Top-left corner heatmap for current level with + shape (N, num_classes, H, W). + br_heat (Tensor): Bottom-right corner heatmap for current level + with shape (N, num_classes, H, W). + tl_off (Tensor): Top-left corner offset for current level with + shape (N, corner_offset_channels, H, W). + br_off (Tensor): Bottom-right corner offset for current level with + shape (N, corner_offset_channels, H, W). + tl_emb (Tensor, Optional): Top-left corner embedding for current + level with shape (N, corner_emb_channels, H, W). + br_emb (Tensor, Optional): Bottom-right corner embedding for + current level with shape (N, corner_emb_channels, H, W). + tl_centripetal_shift (Tensor, Optional): Top-left centripetal shift + for current level with shape (N, 2, H, W). + br_centripetal_shift (Tensor, Optional): Bottom-right centripetal + shift for current level with shape (N, 2, H, W). + img_meta (dict): Meta information of current image, e.g., + image size, scaling factor, etc. + k (int): Get top k corner keypoints from heatmap. + kernel (int): Max pooling kernel for extract local maximum pixels. + distance_threshold (float): Distance threshold. Top-left and + bottom-right corner keypoints with feature distance less than + the threshold will be regarded as keypoints from same object. + num_dets (int): Num of raw boxes before doing nms. + + Returns: + tuple[torch.Tensor]: Decoded output of CornerHead, containing the + following Tensors: + + - bboxes (Tensor): Coords of each box. + - scores (Tensor): Scores of each box. + - clses (Tensor): Categories of each box. + """ + with_embedding = tl_emb is not None and br_emb is not None + with_centripetal_shift = ( + tl_centripetal_shift is not None + and br_centripetal_shift is not None) + assert with_embedding + with_centripetal_shift == 1 + batch, _, height, width = tl_heat.size() + if torch.onnx.is_in_onnx_export(): + inp_h, inp_w = img_meta['pad_shape_for_onnx'][:2] + else: + inp_h, inp_w = img_meta['batch_input_shape'][:2] + + # perform nms on heatmaps + tl_heat = get_local_maximum(tl_heat, kernel=kernel) + br_heat = get_local_maximum(br_heat, kernel=kernel) + + tl_scores, tl_inds, tl_clses, tl_ys, tl_xs = get_topk_from_heatmap( + tl_heat, k=k) + br_scores, br_inds, br_clses, br_ys, br_xs = get_topk_from_heatmap( + br_heat, k=k) + + # We use repeat instead of expand here because expand is a + # shallow-copy function. Thus it could cause unexpected testing result + # sometimes. Using expand will decrease about 10% mAP during testing + # compared to repeat. + tl_ys = tl_ys.view(batch, k, 1).repeat(1, 1, k) + tl_xs = tl_xs.view(batch, k, 1).repeat(1, 1, k) + br_ys = br_ys.view(batch, 1, k).repeat(1, k, 1) + br_xs = br_xs.view(batch, 1, k).repeat(1, k, 1) + + tl_off = transpose_and_gather_feat(tl_off, tl_inds) + tl_off = tl_off.view(batch, k, 1, 2) + br_off = transpose_and_gather_feat(br_off, br_inds) + br_off = br_off.view(batch, 1, k, 2) + + tl_xs = tl_xs + tl_off[..., 0] + tl_ys = tl_ys + tl_off[..., 1] + br_xs = br_xs + br_off[..., 0] + br_ys = br_ys + br_off[..., 1] + + if with_centripetal_shift: + tl_centripetal_shift = transpose_and_gather_feat( + tl_centripetal_shift, tl_inds).view(batch, k, 1, 2).exp() + br_centripetal_shift = transpose_and_gather_feat( + br_centripetal_shift, br_inds).view(batch, 1, k, 2).exp() + + tl_ctxs = tl_xs + tl_centripetal_shift[..., 0] + tl_ctys = tl_ys + tl_centripetal_shift[..., 1] + br_ctxs = br_xs - br_centripetal_shift[..., 0] + br_ctys = br_ys - br_centripetal_shift[..., 1] + + # all possible boxes based on top k corners (ignoring class) + tl_xs *= (inp_w / width) + tl_ys *= (inp_h / height) + br_xs *= (inp_w / width) + br_ys *= (inp_h / height) + + if with_centripetal_shift: + tl_ctxs *= (inp_w / width) + tl_ctys *= (inp_h / height) + br_ctxs *= (inp_w / width) + br_ctys *= (inp_h / height) + + x_off, y_off = 0, 0 # no crop + if not torch.onnx.is_in_onnx_export(): + # since `RandomCenterCropPad` is done on CPU with numpy and it's + # not dynamic traceable when exporting to ONNX, thus 'border' + # does not appears as key in 'img_meta'. As a tmp solution, + # we move this 'border' handle part to the postprocess after + # finished exporting to ONNX, which is handle in + # `mmdet/core/export/model_wrappers.py`. Though difference between + # pytorch and exported onnx model, it might be ignored since + # comparable performance is achieved between them (e.g. 40.4 vs + # 40.6 on COCO val2017, for CornerNet without test-time flip) + if 'border' in img_meta: + x_off = img_meta['border'][2] + y_off = img_meta['border'][0] + + tl_xs -= x_off + tl_ys -= y_off + br_xs -= x_off + br_ys -= y_off + + zeros = tl_xs.new_zeros(*tl_xs.size()) + tl_xs = torch.where(tl_xs > 0.0, tl_xs, zeros) + tl_ys = torch.where(tl_ys > 0.0, tl_ys, zeros) + br_xs = torch.where(br_xs > 0.0, br_xs, zeros) + br_ys = torch.where(br_ys > 0.0, br_ys, zeros) + + bboxes = torch.stack((tl_xs, tl_ys, br_xs, br_ys), dim=3) + area_bboxes = ((br_xs - tl_xs) * (br_ys - tl_ys)).abs() + + if with_centripetal_shift: + tl_ctxs -= x_off + tl_ctys -= y_off + br_ctxs -= x_off + br_ctys -= y_off + + tl_ctxs *= tl_ctxs.gt(0.0).type_as(tl_ctxs) + tl_ctys *= tl_ctys.gt(0.0).type_as(tl_ctys) + br_ctxs *= br_ctxs.gt(0.0).type_as(br_ctxs) + br_ctys *= br_ctys.gt(0.0).type_as(br_ctys) + + ct_bboxes = torch.stack((tl_ctxs, tl_ctys, br_ctxs, br_ctys), + dim=3) + area_ct_bboxes = ((br_ctxs - tl_ctxs) * (br_ctys - tl_ctys)).abs() + + rcentral = torch.zeros_like(ct_bboxes) + # magic nums from paper section 4.1 + mu = torch.ones_like(area_bboxes) / 2.4 + mu[area_bboxes > 3500] = 1 / 2.1 # large bbox have smaller mu + + bboxes_center_x = (bboxes[..., 0] + bboxes[..., 2]) / 2 + bboxes_center_y = (bboxes[..., 1] + bboxes[..., 3]) / 2 + rcentral[..., 0] = bboxes_center_x - mu * (bboxes[..., 2] - + bboxes[..., 0]) / 2 + rcentral[..., 1] = bboxes_center_y - mu * (bboxes[..., 3] - + bboxes[..., 1]) / 2 + rcentral[..., 2] = bboxes_center_x + mu * (bboxes[..., 2] - + bboxes[..., 0]) / 2 + rcentral[..., 3] = bboxes_center_y + mu * (bboxes[..., 3] - + bboxes[..., 1]) / 2 + area_rcentral = ((rcentral[..., 2] - rcentral[..., 0]) * + (rcentral[..., 3] - rcentral[..., 1])).abs() + dists = area_ct_bboxes / area_rcentral + + tl_ctx_inds = (ct_bboxes[..., 0] <= rcentral[..., 0]) | ( + ct_bboxes[..., 0] >= rcentral[..., 2]) + tl_cty_inds = (ct_bboxes[..., 1] <= rcentral[..., 1]) | ( + ct_bboxes[..., 1] >= rcentral[..., 3]) + br_ctx_inds = (ct_bboxes[..., 2] <= rcentral[..., 0]) | ( + ct_bboxes[..., 2] >= rcentral[..., 2]) + br_cty_inds = (ct_bboxes[..., 3] <= rcentral[..., 1]) | ( + ct_bboxes[..., 3] >= rcentral[..., 3]) + + if with_embedding: + tl_emb = transpose_and_gather_feat(tl_emb, tl_inds) + tl_emb = tl_emb.view(batch, k, 1) + br_emb = transpose_and_gather_feat(br_emb, br_inds) + br_emb = br_emb.view(batch, 1, k) + dists = torch.abs(tl_emb - br_emb) + + tl_scores = tl_scores.view(batch, k, 1).repeat(1, 1, k) + br_scores = br_scores.view(batch, 1, k).repeat(1, k, 1) + + scores = (tl_scores + br_scores) / 2 # scores for all possible boxes + + # tl and br should have same class + tl_clses = tl_clses.view(batch, k, 1).repeat(1, 1, k) + br_clses = br_clses.view(batch, 1, k).repeat(1, k, 1) + cls_inds = (tl_clses != br_clses) + + # reject boxes based on distances + dist_inds = dists > distance_threshold + + # reject boxes based on widths and heights + width_inds = (br_xs <= tl_xs) + height_inds = (br_ys <= tl_ys) + + # No use `scores[cls_inds]`, instead we use `torch.where` here. + # Since only 1-D indices with type 'tensor(bool)' are supported + # when exporting to ONNX, any other bool indices with more dimensions + # (e.g. 2-D bool tensor) as input parameter in node is invalid + negative_scores = -1 * torch.ones_like(scores) + scores = torch.where(cls_inds, negative_scores, scores) + scores = torch.where(width_inds, negative_scores, scores) + scores = torch.where(height_inds, negative_scores, scores) + scores = torch.where(dist_inds, negative_scores, scores) + + if with_centripetal_shift: + scores[tl_ctx_inds] = -1 + scores[tl_cty_inds] = -1 + scores[br_ctx_inds] = -1 + scores[br_cty_inds] = -1 + + scores = scores.view(batch, -1) + scores, inds = torch.topk(scores, num_dets) + scores = scores.unsqueeze(2) + + bboxes = bboxes.view(batch, -1, 4) + bboxes = gather_feat(bboxes, inds) + + clses = tl_clses.contiguous().view(batch, -1, 1) + clses = gather_feat(clses, inds) + + return bboxes, scores, clses diff --git a/mmdetection/mmdet/models/dense_heads/dab_detr_head.py b/mmdetection/mmdet/models/dense_heads/dab_detr_head.py new file mode 100644 index 00000000..892833ff --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/dab_detr_head.py @@ -0,0 +1,106 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch.nn as nn +from mmcv.cnn import Linear +from mmengine.model import bias_init_with_prob, constant_init +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import InstanceList +from ..layers import MLP, inverse_sigmoid +from .conditional_detr_head import ConditionalDETRHead + + +@MODELS.register_module() +class DABDETRHead(ConditionalDETRHead): + """Head of DAB-DETR. DAB-DETR: Dynamic Anchor Boxes are Better Queries for + DETR. + + More details can be found in the `paper + `_ . + """ + + def _init_layers(self) -> None: + """Initialize layers of the transformer head.""" + # cls branch + self.fc_cls = Linear(self.embed_dims, self.cls_out_channels) + # reg branch + self.fc_reg = MLP(self.embed_dims, self.embed_dims, 4, 3) + + def init_weights(self) -> None: + """initialize weights.""" + if self.loss_cls.use_sigmoid: + bias_init = bias_init_with_prob(0.01) + nn.init.constant_(self.fc_cls.bias, bias_init) + constant_init(self.fc_reg.layers[-1], 0., bias=0.) + + def forward(self, hidden_states: Tensor, + references: Tensor) -> Tuple[Tensor, Tensor]: + """"Forward function. + + Args: + hidden_states (Tensor): Features from transformer decoder. If + `return_intermediate_dec` is True output has shape + (num_decoder_layers, bs, num_queries, dim), else has shape (1, + bs, num_queries, dim) which only contains the last layer + outputs. + references (Tensor): References from transformer decoder. If + `return_intermediate_dec` is True output has shape + (num_decoder_layers, bs, num_queries, 2/4), else has shape (1, + bs, num_queries, 2/4) + which only contains the last layer reference. + Returns: + tuple[Tensor]: results of head containing the following tensor. + + - layers_cls_scores (Tensor): Outputs from the classification head, + shape (num_decoder_layers, bs, num_queries, cls_out_channels). + Note cls_out_channels should include background. + - layers_bbox_preds (Tensor): Sigmoid outputs from the regression + head with normalized coordinate format (cx, cy, w, h), has shape + (num_decoder_layers, bs, num_queries, 4). + """ + layers_cls_scores = self.fc_cls(hidden_states) + references_before_sigmoid = inverse_sigmoid(references, eps=1e-3) + tmp_reg_preds = self.fc_reg(hidden_states) + tmp_reg_preds[..., :references_before_sigmoid. + size(-1)] += references_before_sigmoid + layers_bbox_preds = tmp_reg_preds.sigmoid() + return layers_cls_scores, layers_bbox_preds + + def predict(self, + hidden_states: Tensor, + references: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> InstanceList: + """Perform forward propagation of the detection head and predict + detection results on the features of the upstream network. Over-write + because img_metas are needed as inputs for bbox_head. + + Args: + hidden_states (Tensor): Feature from the transformer decoder, has + shape (num_decoder_layers, bs, num_queries, dim). + references (Tensor): references from the transformer decoder, has + shape (num_decoder_layers, bs, num_queries, 2/4). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): Whether to rescale the results. + Defaults to True. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + last_layer_hidden_state = hidden_states[-1].unsqueeze(0) + last_layer_reference = references[-1].unsqueeze(0) + outs = self(last_layer_hidden_state, last_layer_reference) + + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas, rescale=rescale) + return predictions diff --git a/mmdetection/mmdet/models/dense_heads/ddod_head.py b/mmdetection/mmdet/models/dense_heads/ddod_head.py new file mode 100644 index 00000000..64e91ff0 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/ddod_head.py @@ -0,0 +1,794 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Sequence, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, Scale +from mmengine.model import bias_init_with_prob, normal_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures.bbox import bbox_overlaps +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList, reduce_mean) +from ..task_modules.prior_generators import anchor_inside_flags +from ..utils import images_to_levels, multi_apply, unmap +from .anchor_head import AnchorHead + +EPS = 1e-12 + + +@MODELS.register_module() +class DDODHead(AnchorHead): + """Detection Head of `DDOD `_. + + DDOD head decomposes conjunctions lying in most current one-stage + detectors via label assignment disentanglement, spatial feature + disentanglement, and pyramid supervision disentanglement. + + Args: + num_classes (int): Number of categories excluding the + background category. + in_channels (int): Number of channels in the input feature map. + stacked_convs (int): The number of stacked Conv. Defaults to 4. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + use_dcn (bool): Use dcn, Same as ATSS when False. Defaults to True. + norm_cfg (:obj:`ConfigDict` or dict): Normal config of ddod head. + Defaults to dict(type='GN', num_groups=32, requires_grad=True). + loss_iou (:obj:`ConfigDict` or dict): Config of IoU loss. Defaults to + dict(type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0). + """ + + def __init__(self, + num_classes: int, + in_channels: int, + stacked_convs: int = 4, + conv_cfg: OptConfigType = None, + use_dcn: bool = True, + norm_cfg: ConfigType = dict( + type='GN', num_groups=32, requires_grad=True), + loss_iou: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0), + **kwargs) -> None: + self.stacked_convs = stacked_convs + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.use_dcn = use_dcn + super().__init__(num_classes, in_channels, **kwargs) + + if self.train_cfg: + self.cls_assigner = TASK_UTILS.build(self.train_cfg['assigner']) + self.reg_assigner = TASK_UTILS.build( + self.train_cfg['reg_assigner']) + self.loss_iou = MODELS.build(loss_iou) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.relu = nn.ReLU(inplace=True) + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=dict(type='DCN', deform_groups=1) + if i == 0 and self.use_dcn else self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=dict(type='DCN', deform_groups=1) + if i == 0 and self.use_dcn else self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.atss_cls = nn.Conv2d( + self.feat_channels, + self.num_base_priors * self.cls_out_channels, + 3, + padding=1) + self.atss_reg = nn.Conv2d( + self.feat_channels, self.num_base_priors * 4, 3, padding=1) + self.atss_iou = nn.Conv2d( + self.feat_channels, self.num_base_priors * 1, 3, padding=1) + self.scales = nn.ModuleList( + [Scale(1.0) for _ in self.prior_generator.strides]) + + # we use the global list in loss + self.cls_num_pos_samples_per_level = [ + 0. for _ in range(len(self.prior_generator.strides)) + ] + self.reg_num_pos_samples_per_level = [ + 0. for _ in range(len(self.prior_generator.strides)) + ] + + def init_weights(self) -> None: + """Initialize weights of the head.""" + for m in self.cls_convs: + normal_init(m.conv, std=0.01) + for m in self.reg_convs: + normal_init(m.conv, std=0.01) + normal_init(self.atss_reg, std=0.01) + normal_init(self.atss_iou, std=0.01) + bias_cls = bias_init_with_prob(0.01) + normal_init(self.atss_cls, std=0.01, bias=bias_cls) + + def forward(self, x: Tuple[Tensor]) -> Tuple[List[Tensor]]: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of classification scores, bbox predictions, + and iou predictions. + + - cls_scores (list[Tensor]): Classification scores for all \ + scale levels, each is a 4D-tensor, the channels number is \ + num_base_priors * num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for all \ + scale levels, each is a 4D-tensor, the channels number is \ + num_base_priors * 4. + - iou_preds (list[Tensor]): IoU scores for all scale levels, \ + each is a 4D-tensor, the channels number is num_base_priors * 1. + """ + return multi_apply(self.forward_single, x, self.scales) + + def forward_single(self, x: Tensor, scale: Scale) -> Sequence[Tensor]: + """Forward feature of a single scale level. + + Args: + x (Tensor): Features of a single scale level. + scale (:obj: `mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + + Returns: + tuple: + + - cls_score (Tensor): Cls scores for a single scale level \ + the channels number is num_base_priors * num_classes. + - bbox_pred (Tensor): Box energies / deltas for a single \ + scale level, the channels number is num_base_priors * 4. + - iou_pred (Tensor): Iou for a single scale level, the \ + channel number is (N, num_base_priors * 1, H, W). + """ + cls_feat = x + reg_feat = x + for cls_conv in self.cls_convs: + cls_feat = cls_conv(cls_feat) + for reg_conv in self.reg_convs: + reg_feat = reg_conv(reg_feat) + cls_score = self.atss_cls(cls_feat) + # we just follow atss, not apply exp in bbox_pred + bbox_pred = scale(self.atss_reg(reg_feat)).float() + iou_pred = self.atss_iou(reg_feat) + return cls_score, bbox_pred, iou_pred + + def loss_cls_by_feat_single(self, cls_score: Tensor, labels: Tensor, + label_weights: Tensor, + reweight_factor: List[float], + avg_factor: float) -> Tuple[Tensor]: + """Compute cls loss of a single scale level. + + Args: + cls_score (Tensor): Box scores for each scale level + Has shape (N, num_base_priors * num_classes, H, W). + labels (Tensor): Labels of each anchors with shape + (N, num_total_anchors). + label_weights (Tensor): Label weights of each anchor with shape + (N, num_total_anchors) + reweight_factor (List[float]): Reweight factor for cls and reg + loss. + avg_factor (float): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + `PseudoSampler`, `avg_factor` is usually equal to the number + of positive priors. + + Returns: + Tuple[Tensor]: A tuple of loss components. + """ + cls_score = cls_score.permute(0, 2, 3, 1).reshape( + -1, self.cls_out_channels).contiguous() + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + loss_cls = self.loss_cls( + cls_score, labels, label_weights, avg_factor=avg_factor) + return reweight_factor * loss_cls, + + def loss_reg_by_feat_single(self, anchors: Tensor, bbox_pred: Tensor, + iou_pred: Tensor, labels, + label_weights: Tensor, bbox_targets: Tensor, + bbox_weights: Tensor, + reweight_factor: List[float], + avg_factor: float) -> Tuple[Tensor, Tensor]: + """Compute reg loss of a single scale level based on the features + extracted by the detection head. + + Args: + anchors (Tensor): Box reference for each scale level with shape + (N, num_total_anchors, 4). + bbox_pred (Tensor): Box energies / deltas for each scale + level with shape (N, num_base_priors * 4, H, W). + iou_pred (Tensor): Iou for a single scale level, the + channel number is (N, num_base_priors * 1, H, W). + labels (Tensor): Labels of each anchors with shape + (N, num_total_anchors). + label_weights (Tensor): Label weights of each anchor with shape + (N, num_total_anchors) + bbox_targets (Tensor): BBox regression targets of each anchor with + shape (N, num_total_anchors, 4). + bbox_weights (Tensor): BBox weights of all anchors in the + image with shape (N, 4) + reweight_factor (List[float]): Reweight factor for cls and reg + loss. + avg_factor (float): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + `PseudoSampler`, `avg_factor` is usually equal to the number + of positive priors. + Returns: + Tuple[Tensor, Tensor]: A tuple of loss components. + """ + anchors = anchors.reshape(-1, 4) + bbox_pred = bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4) + iou_pred = iou_pred.permute(0, 2, 3, 1).reshape(-1, ) + bbox_targets = bbox_targets.reshape(-1, 4) + bbox_weights = bbox_weights.reshape(-1, 4) + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + + iou_targets = label_weights.new_zeros(labels.shape) + iou_weights = label_weights.new_zeros(labels.shape) + iou_weights[(bbox_weights.sum(axis=1) > 0).nonzero( + as_tuple=False)] = 1. + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & + (labels < bg_class_ind)).nonzero(as_tuple=False).squeeze(1) + + if len(pos_inds) > 0: + pos_bbox_targets = bbox_targets[pos_inds] + pos_bbox_pred = bbox_pred[pos_inds] + pos_anchors = anchors[pos_inds] + + pos_decode_bbox_pred = self.bbox_coder.decode( + pos_anchors, pos_bbox_pred) + pos_decode_bbox_targets = self.bbox_coder.decode( + pos_anchors, pos_bbox_targets) + + # regression loss + loss_bbox = self.loss_bbox( + pos_decode_bbox_pred, + pos_decode_bbox_targets, + avg_factor=avg_factor) + + iou_targets[pos_inds] = bbox_overlaps( + pos_decode_bbox_pred.detach(), + pos_decode_bbox_targets, + is_aligned=True) + loss_iou = self.loss_iou( + iou_pred, iou_targets, iou_weights, avg_factor=avg_factor) + else: + loss_bbox = bbox_pred.sum() * 0 + loss_iou = iou_pred.sum() * 0 + + return reweight_factor * loss_bbox, reweight_factor * loss_iou + + def calc_reweight_factor(self, labels_list: List[Tensor]) -> List[float]: + """Compute reweight_factor for regression and classification loss.""" + # get pos samples for each level + bg_class_ind = self.num_classes + for ii, each_level_label in enumerate(labels_list): + pos_inds = ((each_level_label >= 0) & + (each_level_label < bg_class_ind)).nonzero( + as_tuple=False).squeeze(1) + self.cls_num_pos_samples_per_level[ii] += len(pos_inds) + # get reweight factor from 1 ~ 2 with bilinear interpolation + min_pos_samples = min(self.cls_num_pos_samples_per_level) + max_pos_samples = max(self.cls_num_pos_samples_per_level) + interval = 1. / (max_pos_samples - min_pos_samples + 1e-10) + reweight_factor_per_level = [] + for pos_samples in self.cls_num_pos_samples_per_level: + factor = 2. - (pos_samples - min_pos_samples) * interval + reweight_factor_per_level.append(factor) + return reweight_factor_per_level + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + iou_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_base_priors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_base_priors * 4, H, W) + iou_preds (list[Tensor]): Score factor for all scale level, + each is a 4D-tensor, has shape (batch_size, 1, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + # calculate common vars for cls and reg assigners at once + targets_com = self.process_predictions_and_anchors( + anchor_list, valid_flag_list, cls_scores, bbox_preds, + batch_img_metas, batch_gt_instances_ignore) + (anchor_list, valid_flag_list, num_level_anchors_list, cls_score_list, + bbox_pred_list, batch_gt_instances_ignore) = targets_com + + # classification branch assigner + cls_targets = self.get_cls_targets( + anchor_list, + valid_flag_list, + num_level_anchors_list, + cls_score_list, + bbox_pred_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + + (cls_anchor_list, labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor) = cls_targets + + avg_factor = reduce_mean( + torch.tensor(avg_factor, dtype=torch.float, device=device)).item() + avg_factor = max(avg_factor, 1.0) + + reweight_factor_per_level = self.calc_reweight_factor(labels_list) + + cls_losses_cls, = multi_apply( + self.loss_cls_by_feat_single, + cls_scores, + labels_list, + label_weights_list, + reweight_factor_per_level, + avg_factor=avg_factor) + + # regression branch assigner + reg_targets = self.get_reg_targets( + anchor_list, + valid_flag_list, + num_level_anchors_list, + cls_score_list, + bbox_pred_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + + (reg_anchor_list, labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor) = reg_targets + + avg_factor = reduce_mean( + torch.tensor(avg_factor, dtype=torch.float, device=device)).item() + avg_factor = max(avg_factor, 1.0) + + reweight_factor_per_level = self.calc_reweight_factor(labels_list) + + reg_losses_bbox, reg_losses_iou = multi_apply( + self.loss_reg_by_feat_single, + reg_anchor_list, + bbox_preds, + iou_preds, + labels_list, + label_weights_list, + bbox_targets_list, + bbox_weights_list, + reweight_factor_per_level, + avg_factor=avg_factor) + + return dict( + loss_cls=cls_losses_cls, + loss_bbox=reg_losses_bbox, + loss_iou=reg_losses_iou) + + def process_predictions_and_anchors( + self, + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> tuple: + """Compute common vars for regression and classification targets. + + Args: + anchor_list (List[List[Tensor]]): anchors of each image. + valid_flag_list (List[List[Tensor]]): Valid flags of each image. + cls_scores (List[Tensor]): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * num_classes. + bbox_preds (list[Tensor]): Box energies / deltas for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * 4. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Return: + tuple[Tensor]: A tuple of common loss vars. + """ + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + num_level_anchors_list = [num_level_anchors] * num_imgs + + anchor_list_ = [] + valid_flag_list_ = [] + # concat all level anchors and flags to a single tensor + for i in range(num_imgs): + assert len(anchor_list[i]) == len(valid_flag_list[i]) + anchor_list_.append(torch.cat(anchor_list[i])) + valid_flag_list_.append(torch.cat(valid_flag_list[i])) + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None for _ in range(num_imgs)] + + num_levels = len(cls_scores) + cls_score_list = [] + bbox_pred_list = [] + + mlvl_cls_score_list = [ + cls_score.permute(0, 2, 3, 1).reshape( + num_imgs, -1, self.num_base_priors * self.cls_out_channels) + for cls_score in cls_scores + ] + mlvl_bbox_pred_list = [ + bbox_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, + self.num_base_priors * 4) + for bbox_pred in bbox_preds + ] + + for i in range(num_imgs): + mlvl_cls_tensor_list = [ + mlvl_cls_score_list[j][i] for j in range(num_levels) + ] + mlvl_bbox_tensor_list = [ + mlvl_bbox_pred_list[j][i] for j in range(num_levels) + ] + cat_mlvl_cls_score = torch.cat(mlvl_cls_tensor_list, dim=0) + cat_mlvl_bbox_pred = torch.cat(mlvl_bbox_tensor_list, dim=0) + cls_score_list.append(cat_mlvl_cls_score) + bbox_pred_list.append(cat_mlvl_bbox_pred) + return (anchor_list_, valid_flag_list_, num_level_anchors_list, + cls_score_list, bbox_pred_list, batch_gt_instances_ignore) + + def get_cls_targets(self, + anchor_list: List[Tensor], + valid_flag_list: List[Tensor], + num_level_anchors_list: List[int], + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs: bool = True) -> tuple: + """Get cls targets for DDOD head. + + This method is almost the same as `AnchorHead.get_targets()`. + Besides returning the targets as the parent method does, + it also returns the anchors as the first element of the + returned tuple. + + Args: + anchor_list (list[Tensor]): anchors of each image. + valid_flag_list (list[Tensor]): Valid flags of each image. + num_level_anchors_list (list[Tensor]): Number of anchors of each + scale level of all image. + cls_score_list (list[Tensor]): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * num_classes. + bbox_pred_list (list[Tensor]): Box energies / deltas for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * 4. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. + + Return: + tuple[Tensor]: A tuple of cls targets components. + """ + (all_anchors, all_labels, all_label_weights, all_bbox_targets, + all_bbox_weights, pos_inds_list, neg_inds_list, + sampling_results_list) = multi_apply( + self._get_targets_single, + anchor_list, + valid_flag_list, + cls_score_list, + bbox_pred_list, + num_level_anchors_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs, + is_cls_assigner=True) + # Get `avg_factor` of all images, which calculate in `SamplingResult`. + # When using sampling method, avg_factor is usually the sum of + # positive and negative priors. When using `PseudoSampler`, + # `avg_factor` is usually equal to the number of positive priors. + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + # split targets to a list w.r.t. multiple levels + anchors_list = images_to_levels(all_anchors, num_level_anchors_list[0]) + labels_list = images_to_levels(all_labels, num_level_anchors_list[0]) + label_weights_list = images_to_levels(all_label_weights, + num_level_anchors_list[0]) + bbox_targets_list = images_to_levels(all_bbox_targets, + num_level_anchors_list[0]) + bbox_weights_list = images_to_levels(all_bbox_weights, + num_level_anchors_list[0]) + return (anchors_list, labels_list, label_weights_list, + bbox_targets_list, bbox_weights_list, avg_factor) + + def get_reg_targets(self, + anchor_list: List[Tensor], + valid_flag_list: List[Tensor], + num_level_anchors_list: List[int], + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs: bool = True) -> tuple: + """Get reg targets for DDOD head. + + This method is almost the same as `AnchorHead.get_targets()` when + is_cls_assigner is False. Besides returning the targets as the parent + method does, it also returns the anchors as the first element of the + returned tuple. + + Args: + anchor_list (list[Tensor]): anchors of each image. + valid_flag_list (list[Tensor]): Valid flags of each image. + num_level_anchors_list (list[Tensor]): Number of anchors of each + scale level of all image. + cls_score_list (list[Tensor]): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * num_classes. + bbox_pred_list (list[Tensor]): Box energies / deltas for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * 4. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. + + Return: + tuple[Tensor]: A tuple of reg targets components. + """ + (all_anchors, all_labels, all_label_weights, all_bbox_targets, + all_bbox_weights, pos_inds_list, neg_inds_list, + sampling_results_list) = multi_apply( + self._get_targets_single, + anchor_list, + valid_flag_list, + cls_score_list, + bbox_pred_list, + num_level_anchors_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs, + is_cls_assigner=False) + # Get `avg_factor` of all images, which calculate in `SamplingResult`. + # When using sampling method, avg_factor is usually the sum of + # positive and negative priors. When using `PseudoSampler`, + # `avg_factor` is usually equal to the number of positive priors. + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + # split targets to a list w.r.t. multiple levels + anchors_list = images_to_levels(all_anchors, num_level_anchors_list[0]) + labels_list = images_to_levels(all_labels, num_level_anchors_list[0]) + label_weights_list = images_to_levels(all_label_weights, + num_level_anchors_list[0]) + bbox_targets_list = images_to_levels(all_bbox_targets, + num_level_anchors_list[0]) + bbox_weights_list = images_to_levels(all_bbox_weights, + num_level_anchors_list[0]) + return (anchors_list, labels_list, label_weights_list, + bbox_targets_list, bbox_weights_list, avg_factor) + + def _get_targets_single(self, + flat_anchors: Tensor, + valid_flags: Tensor, + cls_scores: Tensor, + bbox_preds: Tensor, + num_level_anchors: List[int], + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True, + is_cls_assigner: bool = True) -> tuple: + """Compute regression, classification targets for anchors in a single + image. + + Args: + flat_anchors (Tensor): Multi-level anchors of the image, + which are concatenated into a single tensor of shape + (num_base_priors, 4). + valid_flags (Tensor): Multi level valid flags of the image, + which are concatenated into a single tensor of + shape (num_base_priors,). + cls_scores (Tensor): Classification scores for all scale + levels of the image. + bbox_preds (Tensor): Box energies / deltas for all scale + levels of the image. + num_level_anchors (List[int]): Number of anchors of each + scale level. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + is_cls_assigner (bool): Classification or regression. + Defaults to True. + + Returns: + tuple: N is the number of total anchors in the image. + - anchors (Tensor): all anchors in the image with shape (N, 4). + - labels (Tensor): Labels of all anchors in the image with \ + shape (N, ). + - label_weights (Tensor): Label weights of all anchor in the \ + image with shape (N, ). + - bbox_targets (Tensor): BBox targets of all anchors in the \ + image with shape (N, 4). + - bbox_weights (Tensor): BBox weights of all anchors in the \ + image with shape (N, 4) + - pos_inds (Tensor): Indices of positive anchor with shape \ + (num_pos, ). + - neg_inds (Tensor): Indices of negative anchor with shape \ + (num_neg, ). + - sampling_result (:obj:`SamplingResult`): Sampling results. + """ + inside_flags = anchor_inside_flags(flat_anchors, valid_flags, + img_meta['img_shape'][:2], + self.train_cfg['allowed_border']) + if not inside_flags.any(): + raise ValueError( + 'There is no valid anchor inside the image boundary. Please ' + 'check the image size and anchor sizes, or set ' + '``allowed_border`` to -1 to skip the condition.') + # assign gt and sample anchors + anchors = flat_anchors[inside_flags, :] + + num_level_anchors_inside = self.get_num_level_anchors_inside( + num_level_anchors, inside_flags) + bbox_preds_valid = bbox_preds[inside_flags, :] + cls_scores_valid = cls_scores[inside_flags, :] + + assigner = self.cls_assigner if is_cls_assigner else self.reg_assigner + + # decode prediction out of assigner + bbox_preds_valid = self.bbox_coder.decode(anchors, bbox_preds_valid) + pred_instances = InstanceData( + priors=anchors, bboxes=bbox_preds_valid, scores=cls_scores_valid) + + assign_result = assigner.assign( + pred_instances=pred_instances, + num_level_priors=num_level_anchors_inside, + gt_instances=gt_instances, + gt_instances_ignore=gt_instances_ignore) + sampling_result = self.sampler.sample( + assign_result=assign_result, + pred_instances=pred_instances, + gt_instances=gt_instances) + + num_valid_anchors = anchors.shape[0] + bbox_targets = torch.zeros_like(anchors) + bbox_weights = torch.zeros_like(anchors) + labels = anchors.new_full((num_valid_anchors, ), + self.num_classes, + dtype=torch.long) + label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + pos_bbox_targets = self.bbox_coder.encode( + sampling_result.pos_bboxes, sampling_result.pos_gt_bboxes) + bbox_targets[pos_inds, :] = pos_bbox_targets + bbox_weights[pos_inds, :] = 1.0 + + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_anchors.size(0) + anchors = unmap(anchors, num_total_anchors, inside_flags) + labels = unmap( + labels, num_total_anchors, inside_flags, fill=self.num_classes) + label_weights = unmap(label_weights, num_total_anchors, + inside_flags) + bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags) + bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags) + + return (anchors, labels, label_weights, bbox_targets, bbox_weights, + pos_inds, neg_inds, sampling_result) + + def get_num_level_anchors_inside(self, num_level_anchors: List[int], + inside_flags: Tensor) -> List[int]: + """Get the anchors of each scale level inside. + + Args: + num_level_anchors (list[int]): Number of anchors of each + scale level. + inside_flags (Tensor): Multi level inside flags of the image, + which are concatenated into a single tensor of + shape (num_base_priors,). + + Returns: + list[int]: Number of anchors of each scale level inside. + """ + split_inside_flags = torch.split(inside_flags, num_level_anchors) + num_level_anchors_inside = [ + int(flags.sum()) for flags in split_inside_flags + ] + return num_level_anchors_inside diff --git a/mmdetection/mmdet/models/dense_heads/ddq_detr_head.py b/mmdetection/mmdet/models/dense_heads/ddq_detr_head.py new file mode 100644 index 00000000..0580653a --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/ddq_detr_head.py @@ -0,0 +1,550 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import Dict, List, Tuple + +import torch +from mmengine.model import bias_init_with_prob, constant_init +from torch import Tensor, nn + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox_cxcywh_to_xyxy +from mmdet.utils import InstanceList, OptInstanceList, reduce_mean +from ..layers import inverse_sigmoid +from ..losses import DDQAuxLoss +from ..utils import multi_apply +from .dino_head import DINOHead + + +@MODELS.register_module() +class DDQDETRHead(DINOHead): + r"""Head of DDQDETR: Dense Distinct Query for + End-to-End Object Detection. + + Code is modified from the `official github repo + `_. + + More details can be found in the `paper + `_ . + + Args: + aux_num_pos (int): Number of positive targets assigned to a + perdicted object. Defaults to 4. + """ + + def __init__(self, *args, aux_num_pos=4, **kwargs): + super(DDQDETRHead, self).__init__(*args, **kwargs) + self.aux_loss_for_dense = DDQAuxLoss( + train_cfg=dict( + assigner=dict(type='TopkHungarianAssigner', topk=aux_num_pos), + alpha=1, + beta=6)) + + def _init_layers(self) -> None: + """Initialize classification branch and regression branch of aux head + for dense queries.""" + super(DDQDETRHead, self)._init_layers() + # If decoder `num_layers` = 6 and `as_two_stage` = True, then: + # 1) 6 main heads are required for + # each decoder output of distinct queries. + # 2) 1 main head is required for `output_memory` of distinct queries. + # 3) 1 aux head is required for `output_memory` of dense queries, + # which is done by code below this comment. + # So 8 heads are required in sum. + # aux head for dense queries on encoder feature map + self.cls_branches.append(copy.deepcopy(self.cls_branches[-1])) + self.reg_branches.append(copy.deepcopy(self.reg_branches[-1])) + + # If decoder `num_layers` = 6 and `as_two_stage` = True, then: + # 6 aux heads are required for each decoder output of dense queries. + # So 8 + 6 = 14 heads and heads are requires in sum. + # self.num_pred_layer is 7 + # aux head for dense queries in decoder + self.aux_cls_branches = nn.ModuleList([ + copy.deepcopy(self.cls_branches[-1]) + for _ in range(self.num_pred_layer - 1) + ]) + self.aux_reg_branches = nn.ModuleList([ + copy.deepcopy(self.reg_branches[-1]) + for _ in range(self.num_pred_layer - 1) + ]) + + def init_weights(self) -> None: + """Initialize weights of the Deformable DETR head.""" + bias_init = bias_init_with_prob(0.01) + for m in self.cls_branches: + nn.init.constant_(m.bias, bias_init) + for m in self.aux_cls_branches: + nn.init.constant_(m.bias, bias_init) + for m in self.reg_branches: + constant_init(m[-1], 0, bias=0) + for m in self.reg_branches: + nn.init.constant_(m[-1].bias.data[2:], 0.0) + + for m in self.aux_reg_branches: + constant_init(m[-1], 0, bias=0) + + for m in self.aux_reg_branches: + nn.init.constant_(m[-1].bias.data[2:], 0.0) + + def forward(self, hidden_states: Tensor, + references: List[Tensor]) -> Tuple[Tensor]: + """Forward function. + + Args: + hidden_states (Tensor): Hidden states output from each decoder + layer, has shape (num_decoder_layers, bs, num_queries_total, + dim), where `num_queries_total` is the sum of + `num_denoising_queries`, `num_queries` and `num_dense_queries` + when `self.training` is `True`, else `num_queries`. + references (list[Tensor]): List of the reference from the decoder. + The first reference is the `init_reference` (initial) and the + other num_decoder_layers(6) references are `inter_references` + (intermediate). Each reference has shape (bs, + num_queries_total, 4) with the last dimension arranged as + (cx, cy, w, h). + + Returns: + tuple[Tensor]: results of head containing the following tensors. + + - all_layers_outputs_classes (Tensor): Outputs from the + classification head, has shape (num_decoder_layers, bs, + num_queries_total, cls_out_channels). + - all_layers_outputs_coords (Tensor): Sigmoid outputs from the + regression head with normalized coordinate format (cx, cy, w, + h), has shape (num_decoder_layers, bs, num_queries_total, 4) + with the last dimension arranged as (cx, cy, w, h). + """ + all_layers_outputs_classes = [] + all_layers_outputs_coords = [] + if self.training: + num_dense = self.cache_dict['num_dense_queries'] + for layer_id in range(hidden_states.shape[0]): + reference = inverse_sigmoid(references[layer_id]) + hidden_state = hidden_states[layer_id] + if self.training: + dense_hidden_state = hidden_state[:, -num_dense:] + hidden_state = hidden_state[:, :-num_dense] + + outputs_class = self.cls_branches[layer_id](hidden_state) + tmp_reg_preds = self.reg_branches[layer_id](hidden_state) + if self.training: + dense_outputs_class = self.aux_cls_branches[layer_id]( + dense_hidden_state) + dense_tmp_reg_preds = self.aux_reg_branches[layer_id]( + dense_hidden_state) + outputs_class = torch.cat([outputs_class, dense_outputs_class], + dim=1) + tmp_reg_preds = torch.cat([tmp_reg_preds, dense_tmp_reg_preds], + dim=1) + + if reference.shape[-1] == 4: + tmp_reg_preds += reference + else: + assert reference.shape[-1] == 2 + tmp_reg_preds[..., :2] += reference + outputs_coord = tmp_reg_preds.sigmoid() + all_layers_outputs_classes.append(outputs_class) + all_layers_outputs_coords.append(outputs_coord) + + all_layers_outputs_classes = torch.stack(all_layers_outputs_classes) + all_layers_outputs_coords = torch.stack(all_layers_outputs_coords) + + return all_layers_outputs_classes, all_layers_outputs_coords + + def loss(self, + hidden_states: Tensor, + references: List[Tensor], + enc_outputs_class: Tensor, + enc_outputs_coord: Tensor, + batch_data_samples: SampleList, + dn_meta: Dict[str, int], + aux_enc_outputs_class=None, + aux_enc_outputs_coord=None) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the queries of the upstream network. + + Args: + hidden_states (Tensor): Hidden states output from each decoder + layer, has shape (num_decoder_layers, bs, num_queries_total, + dim), where `num_queries_total` is the sum of + `num_denoising_queries`, `num_queries` and `num_dense_queries` + when `self.training` is `True`, else `num_queries`. + references (list[Tensor]): List of the reference from the decoder. + The first reference is the `init_reference` (initial) and the + other num_decoder_layers(6) references are `inter_references` + (intermediate). Each reference has shape (bs, + num_queries_total, 4) with the last dimension arranged as + (cx, cy, w, h). + enc_outputs_class (Tensor): The top k classification score of + each point on encoder feature map, has shape (bs, num_queries, + cls_out_channels). + enc_outputs_coord (Tensor): The proposal generated from points + with top k score, has shape (bs, num_queries, 4) with the + last dimension arranged as (cx, cy, w, h). + batch_data_samples (list[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + aux_enc_outputs_class (Tensor): The `dense_topk` classification + score of each point on encoder feature map, has shape (bs, + num_dense_queries, cls_out_channels). + It is `None` when `self.training` is `False`. + aux_enc_outputs_coord (Tensor): The proposal generated from points + with `dense_topk` score, has shape (bs, num_dense_queries, 4) + with the last dimension arranged as (cx, cy, w, h). + It is `None` when `self.training` is `False`. + + Returns: + dict: A dictionary of loss components. + """ + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + outs = self(hidden_states, references) + loss_inputs = outs + (enc_outputs_class, enc_outputs_coord, + batch_gt_instances, batch_img_metas, dn_meta) + losses = self.loss_by_feat(*loss_inputs) + + aux_enc_outputs_coord = bbox_cxcywh_to_xyxy(aux_enc_outputs_coord) + aux_enc_outputs_coord_list = [] + for img_id in range(len(aux_enc_outputs_coord)): + det_bboxes = aux_enc_outputs_coord[img_id] + img_shape = batch_img_metas[img_id]['img_shape'] + det_bboxes[:, 0::2] = det_bboxes[:, 0::2] * img_shape[1] + det_bboxes[:, 1::2] = det_bboxes[:, 1::2] * img_shape[0] + aux_enc_outputs_coord_list.append(det_bboxes) + aux_enc_outputs_coord = torch.stack(aux_enc_outputs_coord_list) + aux_loss = self.aux_loss_for_dense.loss( + aux_enc_outputs_class.sigmoid(), aux_enc_outputs_coord, + [item.bboxes for item in batch_gt_instances], + [item.labels for item in batch_gt_instances], batch_img_metas) + for k, v in aux_loss.items(): + losses[f'aux_enc_{k}'] = v + + return losses + + def loss_by_feat( + self, + all_layers_cls_scores: Tensor, + all_layers_bbox_preds: Tensor, + enc_cls_scores: Tensor, + enc_bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + dn_meta: Dict[str, int], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Loss function. + + Args: + all_layers_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, + num_queries_total, cls_out_channels). + all_layers_bbox_preds (Tensor): Bbox coordinates of all decoder + layers. Each has shape (num_decoder_layers, bs, + num_queries_total, 4) with normalized coordinate format + (cx, cy, w, h). + enc_cls_scores (Tensor): The top k score of each point on + encoder feature map, has shape (bs, num_queries, + cls_out_channels). + enc_bbox_preds (Tensor): The proposal generated from points + with top k score, has shape (bs, num_queries, 4) with the + last dimension arranged as (cx, cy, w, h). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, + e.g., image size, scaling factor, etc. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + (all_layers_matching_cls_scores, all_layers_matching_bbox_preds, + all_layers_denoising_cls_scores, all_layers_denoising_bbox_preds) = \ + self.split_outputs( + all_layers_cls_scores, all_layers_bbox_preds, dn_meta) + + num_dense_queries = dn_meta['num_dense_queries'] + num_layer = all_layers_matching_bbox_preds.size(0) + dense_all_layers_matching_cls_scores = all_layers_matching_cls_scores[:, :, # noqa: E501 + -num_dense_queries:] # noqa: E501 + dense_all_layers_matching_bbox_preds = all_layers_matching_bbox_preds[:, :, # noqa: E501 + -num_dense_queries:] # noqa: E501 + + all_layers_matching_cls_scores = all_layers_matching_cls_scores[:, :, : # noqa: E501 + -num_dense_queries] # noqa: E501 + all_layers_matching_bbox_preds = all_layers_matching_bbox_preds[:, :, : # noqa: E501 + -num_dense_queries] # noqa: E501 + + loss_dict = self.loss_for_distinct_queries( + all_layers_matching_cls_scores, all_layers_matching_bbox_preds, + batch_gt_instances, batch_img_metas, batch_gt_instances_ignore) + + if enc_cls_scores is not None: + + enc_loss_cls, enc_losses_bbox, enc_losses_iou = \ + self.loss_by_feat_single( + enc_cls_scores, enc_bbox_preds, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas) + loss_dict['enc_loss_cls'] = enc_loss_cls + loss_dict['enc_loss_bbox'] = enc_losses_bbox + loss_dict['enc_loss_iou'] = enc_losses_iou + + if all_layers_denoising_cls_scores is not None: + dn_losses_cls, dn_losses_bbox, dn_losses_iou = self.loss_dn( + all_layers_denoising_cls_scores, + all_layers_denoising_bbox_preds, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas, + dn_meta=dn_meta) + loss_dict['dn_loss_cls'] = dn_losses_cls[-1] + loss_dict['dn_loss_bbox'] = dn_losses_bbox[-1] + loss_dict['dn_loss_iou'] = dn_losses_iou[-1] + for num_dec_layer, (loss_cls_i, loss_bbox_i, loss_iou_i) in \ + enumerate(zip(dn_losses_cls[:-1], dn_losses_bbox[:-1], + dn_losses_iou[:-1])): + loss_dict[f'd{num_dec_layer}.dn_loss_cls'] = loss_cls_i + loss_dict[f'd{num_dec_layer}.dn_loss_bbox'] = loss_bbox_i + loss_dict[f'd{num_dec_layer}.dn_loss_iou'] = loss_iou_i + + for l_id in range(num_layer): + cls_scores = dense_all_layers_matching_cls_scores[l_id].sigmoid() + bbox_preds = dense_all_layers_matching_bbox_preds[l_id] + + bbox_preds = bbox_cxcywh_to_xyxy(bbox_preds) + bbox_preds_list = [] + for img_id in range(len(bbox_preds)): + det_bboxes = bbox_preds[img_id] + img_shape = batch_img_metas[img_id]['img_shape'] + det_bboxes[:, 0::2] = det_bboxes[:, 0::2] * img_shape[1] + det_bboxes[:, 1::2] = det_bboxes[:, 1::2] * img_shape[0] + bbox_preds_list.append(det_bboxes) + bbox_preds = torch.stack(bbox_preds_list) + aux_loss = self.aux_loss_for_dense.loss( + cls_scores, bbox_preds, + [item.bboxes for item in batch_gt_instances], + [item.labels for item in batch_gt_instances], batch_img_metas) + for k, v in aux_loss.items(): + loss_dict[f'{l_id}_aux_{k}'] = v + + return loss_dict + + def loss_for_distinct_queries( + self, + all_layers_cls_scores: Tensor, + all_layers_bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Calculate the loss of distinct queries, that is, excluding denoising + and dense queries. Only select the distinct queries in decoder for + loss. + + Args: + all_layers_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, + num_queries, cls_out_channels). + all_layers_bbox_preds (Tensor): Bbox coordinates of all decoder + layers. It has shape (num_decoder_layers, bs, + num_queries, 4) with the last dimension arranged as + (cx, cy, w, h). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, + e.g., image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert batch_gt_instances_ignore is None, \ + f'{self.__class__.__name__} only supports ' \ + 'for batch_gt_instances_ignore setting to None.' + + losses_cls, losses_bbox, losses_iou = multi_apply( + self._loss_for_distinct_queries_single, + all_layers_cls_scores, + all_layers_bbox_preds, + [i for i in range(len(all_layers_bbox_preds))], + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas) + + loss_dict = dict() + # loss from the last decoder layer + loss_dict['loss_cls'] = losses_cls[-1] + loss_dict['loss_bbox'] = losses_bbox[-1] + loss_dict['loss_iou'] = losses_iou[-1] + # loss from other decoder layers + num_dec_layer = 0 + for loss_cls_i, loss_bbox_i, loss_iou_i in \ + zip(losses_cls[:-1], losses_bbox[:-1], losses_iou[:-1]): + loss_dict[f'd{num_dec_layer}.loss_cls'] = loss_cls_i + loss_dict[f'd{num_dec_layer}.loss_bbox'] = loss_bbox_i + loss_dict[f'd{num_dec_layer}.loss_iou'] = loss_iou_i + num_dec_layer += 1 + return loss_dict + + def _loss_for_distinct_queries_single(self, cls_scores, bbox_preds, l_id, + batch_gt_instances, batch_img_metas): + """Calculate the loss for outputs from a single decoder layer of + distinct queries, that is, excluding denoising and dense queries. Only + select the distinct queries in decoder for loss. + + Args: + cls_scores (Tensor): Classification scores of a single + decoder layer, has shape (bs, num_queries, cls_out_channels). + bbox_preds (Tensor): Bbox coordinates of a single decoder + layer. It has shape (bs, num_queries, 4) with the last + dimension arranged as (cx, cy, w, h). + l_id (int): Decoder layer index for these outputs. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, + e.g., image size, scaling factor, etc. + + Returns: + Tuple[Tensor]: A tuple including `loss_cls`, `loss_box` and + `loss_iou`. + """ + num_imgs = cls_scores.size(0) + if 0 < l_id: + batch_mask = [ + self.cache_dict['distinct_query_mask'][l_id - 1][ + img_id * self.cache_dict['num_heads']][0] + for img_id in range(num_imgs) + ] + else: + batch_mask = [ + torch.ones(len(cls_scores[i]), + device=cls_scores.device).bool() + for i in range(num_imgs) + ] + # only select the distinct queries in decoder for loss + cls_scores_list = [ + cls_scores[i][batch_mask[i]] for i in range(num_imgs) + ] + bbox_preds_list = [ + bbox_preds[i][batch_mask[i]] for i in range(num_imgs) + ] + cls_scores = torch.cat(cls_scores_list) + + cls_reg_targets = self.get_targets(cls_scores_list, bbox_preds_list, + batch_gt_instances, batch_img_metas) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + num_total_pos, num_total_neg) = cls_reg_targets + labels = torch.cat(labels_list, 0) + label_weights = torch.cat(label_weights_list, 0) + bbox_targets = torch.cat(bbox_targets_list, 0) + bbox_weights = torch.cat(bbox_weights_list, 0) + + # classification loss + cls_scores = cls_scores.reshape(-1, self.cls_out_channels) + # construct weighted avg_factor to match with the official DETR repo + cls_avg_factor = num_total_pos * 1.0 + \ + num_total_neg * self.bg_cls_weight + if self.sync_cls_avg_factor: + cls_avg_factor = reduce_mean( + cls_scores.new_tensor([cls_avg_factor])) + cls_avg_factor = max(cls_avg_factor, 1) + + loss_cls = self.loss_cls( + cls_scores, labels, label_weights, avg_factor=cls_avg_factor) + + # Compute the average number of gt boxes across all gpus, for + # normalization purposes + num_total_pos = loss_cls.new_tensor([num_total_pos]) + num_total_pos = torch.clamp(reduce_mean(num_total_pos), min=1).item() + + # construct factors used for rescale bboxes + factors = [] + for img_meta, bbox_pred in zip(batch_img_metas, bbox_preds_list): + img_h, img_w, = img_meta['img_shape'] + factor = bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0).repeat( + bbox_pred.size(0), 1) + factors.append(factor) + factors = torch.cat(factors, 0) + + # DETR regress the relative position of boxes (cxcywh) in the image, + # thus the learning target is normalized by the image size. So here + # we need to re-scale them for calculating IoU loss + bbox_preds = torch.cat(bbox_preds_list) + bbox_preds = bbox_preds.reshape(-1, 4) + bboxes = bbox_cxcywh_to_xyxy(bbox_preds) * factors + bboxes_gt = bbox_cxcywh_to_xyxy(bbox_targets) * factors + + # regression IoU loss, defaultly GIoU loss + loss_iou = self.loss_iou( + bboxes, bboxes_gt, bbox_weights, avg_factor=num_total_pos) + + # regression L1 loss + loss_bbox = self.loss_bbox( + bbox_preds, bbox_targets, bbox_weights, avg_factor=num_total_pos) + return loss_cls, loss_bbox, loss_iou + + def predict_by_feat(self, + layer_cls_scores: Tensor, + layer_bbox_preds: Tensor, + batch_img_metas: List[dict], + rescale: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Args: + layer_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, + num_queries, cls_out_channels). + layer_bbox_preds (Tensor): Bbox coordinates of all decoder layers. + Each has shape (num_decoder_layers, bs, num_queries, 4) + with normalized coordinate format (cx, cy, w, h). + batch_img_metas (list[dict]): Meta information of each image. + rescale (bool, optional): If `True`, return boxes in original + image space. Default `False`. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + cls_scores = layer_cls_scores[-1] + bbox_preds = layer_bbox_preds[-1] + + num_imgs = cls_scores.size(0) + # -1 is last layer input query mask + + batch_mask = [ + self.cache_dict['distinct_query_mask'][-1][ + img_id * self.cache_dict['num_heads']][0] + for img_id in range(num_imgs) + ] + + result_list = [] + for img_id in range(len(batch_img_metas)): + cls_score = cls_scores[img_id][batch_mask[img_id]] + bbox_pred = bbox_preds[img_id][batch_mask[img_id]] + img_meta = batch_img_metas[img_id] + results = self._predict_by_feat_single(cls_score, bbox_pred, + img_meta, rescale) + result_list.append(results) + return result_list diff --git a/mmdetection/mmdet/models/dense_heads/deformable_detr_head.py b/mmdetection/mmdet/models/dense_heads/deformable_detr_head.py new file mode 100644 index 00000000..adedd4aa --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/deformable_detr_head.py @@ -0,0 +1,329 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import Dict, List, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import Linear +from mmengine.model import bias_init_with_prob, constant_init +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import InstanceList, OptInstanceList +from ..layers import inverse_sigmoid +from .detr_head import DETRHead + + +@MODELS.register_module() +class DeformableDETRHead(DETRHead): + r"""Head of DeformDETR: Deformable DETR: Deformable Transformers for + End-to-End Object Detection. + + Code is modified from the `official github repo + `_. + + More details can be found in the `paper + `_ . + + Args: + share_pred_layer (bool): Whether to share parameters for all the + prediction layers. Defaults to `False`. + num_pred_layer (int): The number of the prediction layers. + Defaults to 6. + as_two_stage (bool, optional): Whether to generate the proposal + from the outputs of encoder. Defaults to `False`. + """ + + def __init__(self, + *args, + share_pred_layer: bool = False, + num_pred_layer: int = 6, + as_two_stage: bool = False, + **kwargs) -> None: + self.share_pred_layer = share_pred_layer + self.num_pred_layer = num_pred_layer + self.as_two_stage = as_two_stage + + super().__init__(*args, **kwargs) + + def _init_layers(self) -> None: + """Initialize classification branch and regression branch of head.""" + fc_cls = Linear(self.embed_dims, self.cls_out_channels) + reg_branch = [] + for _ in range(self.num_reg_fcs): + reg_branch.append(Linear(self.embed_dims, self.embed_dims)) + reg_branch.append(nn.ReLU()) + reg_branch.append(Linear(self.embed_dims, 4)) + reg_branch = nn.Sequential(*reg_branch) + + if self.share_pred_layer: + self.cls_branches = nn.ModuleList( + [fc_cls for _ in range(self.num_pred_layer)]) + self.reg_branches = nn.ModuleList( + [reg_branch for _ in range(self.num_pred_layer)]) + else: + self.cls_branches = nn.ModuleList( + [copy.deepcopy(fc_cls) for _ in range(self.num_pred_layer)]) + self.reg_branches = nn.ModuleList([ + copy.deepcopy(reg_branch) for _ in range(self.num_pred_layer) + ]) + + def init_weights(self) -> None: + """Initialize weights of the Deformable DETR head.""" + if self.loss_cls.use_sigmoid: + bias_init = bias_init_with_prob(0.01) + for m in self.cls_branches: + if hasattr(m, 'bias') and m.bias is not None: + nn.init.constant_(m.bias, bias_init) + for m in self.reg_branches: + constant_init(m[-1], 0, bias=0) + nn.init.constant_(self.reg_branches[0][-1].bias.data[2:], -2.0) + if self.as_two_stage: + for m in self.reg_branches: + nn.init.constant_(m[-1].bias.data[2:], 0.0) + + def forward(self, hidden_states: Tensor, + references: List[Tensor]) -> Tuple[Tensor, Tensor]: + """Forward function. + + Args: + hidden_states (Tensor): Hidden states output from each decoder + layer, has shape (num_decoder_layers, bs, num_queries, dim). + references (list[Tensor]): List of the reference from the decoder. + The first reference is the `init_reference` (initial) and the + other num_decoder_layers(6) references are `inter_references` + (intermediate). The `init_reference` has shape (bs, + num_queries, 4) when `as_two_stage` of the detector is `True`, + otherwise (bs, num_queries, 2). Each `inter_reference` has + shape (bs, num_queries, 4) when `with_box_refine` of the + detector is `True`, otherwise (bs, num_queries, 2). The + coordinates are arranged as (cx, cy) when the last dimension is + 2, and (cx, cy, w, h) when it is 4. + + Returns: + tuple[Tensor]: results of head containing the following tensor. + + - all_layers_outputs_classes (Tensor): Outputs from the + classification head, has shape (num_decoder_layers, bs, + num_queries, cls_out_channels). + - all_layers_outputs_coords (Tensor): Sigmoid outputs from the + regression head with normalized coordinate format (cx, cy, w, + h), has shape (num_decoder_layers, bs, num_queries, 4) with the + last dimension arranged as (cx, cy, w, h). + """ + all_layers_outputs_classes = [] + all_layers_outputs_coords = [] + + for layer_id in range(hidden_states.shape[0]): + reference = inverse_sigmoid(references[layer_id]) + # NOTE The last reference will not be used. + hidden_state = hidden_states[layer_id] + outputs_class = self.cls_branches[layer_id](hidden_state) + tmp_reg_preds = self.reg_branches[layer_id](hidden_state) + if reference.shape[-1] == 4: + # When `layer` is 0 and `as_two_stage` of the detector + # is `True`, or when `layer` is greater than 0 and + # `with_box_refine` of the detector is `True`. + tmp_reg_preds += reference + else: + # When `layer` is 0 and `as_two_stage` of the detector + # is `False`, or when `layer` is greater than 0 and + # `with_box_refine` of the detector is `False`. + assert reference.shape[-1] == 2 + tmp_reg_preds[..., :2] += reference + outputs_coord = tmp_reg_preds.sigmoid() + all_layers_outputs_classes.append(outputs_class) + all_layers_outputs_coords.append(outputs_coord) + + all_layers_outputs_classes = torch.stack(all_layers_outputs_classes) + all_layers_outputs_coords = torch.stack(all_layers_outputs_coords) + + return all_layers_outputs_classes, all_layers_outputs_coords + + def loss(self, hidden_states: Tensor, references: List[Tensor], + enc_outputs_class: Tensor, enc_outputs_coord: Tensor, + batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the queries of the upstream network. + + Args: + hidden_states (Tensor): Hidden states output from each decoder + layer, has shape (num_decoder_layers, num_queries, bs, dim). + references (list[Tensor]): List of the reference from the decoder. + The first reference is the `init_reference` (initial) and the + other num_decoder_layers(6) references are `inter_references` + (intermediate). The `init_reference` has shape (bs, + num_queries, 4) when `as_two_stage` of the detector is `True`, + otherwise (bs, num_queries, 2). Each `inter_reference` has + shape (bs, num_queries, 4) when `with_box_refine` of the + detector is `True`, otherwise (bs, num_queries, 2). The + coordinates are arranged as (cx, cy) when the last dimension is + 2, and (cx, cy, w, h) when it is 4. + enc_outputs_class (Tensor): The score of each point on encode + feature map, has shape (bs, num_feat_points, cls_out_channels). + Only when `as_two_stage` is `True` it would be passed in, + otherwise it would be `None`. + enc_outputs_coord (Tensor): The proposal generate from the encode + feature map, has shape (bs, num_feat_points, 4) with the last + dimension arranged as (cx, cy, w, h). Only when `as_two_stage` + is `True` it would be passed in, otherwise it would be `None`. + batch_data_samples (list[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + outs = self(hidden_states, references) + loss_inputs = outs + (enc_outputs_class, enc_outputs_coord, + batch_gt_instances, batch_img_metas) + losses = self.loss_by_feat(*loss_inputs) + return losses + + def loss_by_feat( + self, + all_layers_cls_scores: Tensor, + all_layers_bbox_preds: Tensor, + enc_cls_scores: Tensor, + enc_bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Loss function. + + Args: + all_layers_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, num_queries, + cls_out_channels). + all_layers_bbox_preds (Tensor): Regression outputs of all decoder + layers. Each is a 4D-tensor with normalized coordinate format + (cx, cy, w, h) and has shape (num_decoder_layers, bs, + num_queries, 4) with the last dimension arranged as + (cx, cy, w, h). + enc_cls_scores (Tensor): The score of each point on encode + feature map, has shape (bs, num_feat_points, cls_out_channels). + Only when `as_two_stage` is `True` it would be passes in, + otherwise, it would be `None`. + enc_bbox_preds (Tensor): The proposal generate from the encode + feature map, has shape (bs, num_feat_points, 4) with the last + dimension arranged as (cx, cy, w, h). Only when `as_two_stage` + is `True` it would be passed in, otherwise it would be `None`. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + loss_dict = super().loss_by_feat(all_layers_cls_scores, + all_layers_bbox_preds, + batch_gt_instances, batch_img_metas, + batch_gt_instances_ignore) + + # loss of proposal generated from encode feature map. + if enc_cls_scores is not None: + proposal_gt_instances = copy.deepcopy(batch_gt_instances) + for i in range(len(proposal_gt_instances)): + proposal_gt_instances[i].labels = torch.zeros_like( + proposal_gt_instances[i].labels) + enc_loss_cls, enc_losses_bbox, enc_losses_iou = \ + self.loss_by_feat_single( + enc_cls_scores, enc_bbox_preds, + batch_gt_instances=proposal_gt_instances, + batch_img_metas=batch_img_metas) + loss_dict['enc_loss_cls'] = enc_loss_cls + loss_dict['enc_loss_bbox'] = enc_losses_bbox + loss_dict['enc_loss_iou'] = enc_losses_iou + return loss_dict + + def predict(self, + hidden_states: Tensor, + references: List[Tensor], + batch_data_samples: SampleList, + rescale: bool = True) -> InstanceList: + """Perform forward propagation and loss calculation of the detection + head on the queries of the upstream network. + + Args: + hidden_states (Tensor): Hidden states output from each decoder + layer, has shape (num_decoder_layers, num_queries, bs, dim). + references (list[Tensor]): List of the reference from the decoder. + The first reference is the `init_reference` (initial) and the + other num_decoder_layers(6) references are `inter_references` + (intermediate). The `init_reference` has shape (bs, + num_queries, 4) when `as_two_stage` of the detector is `True`, + otherwise (bs, num_queries, 2). Each `inter_reference` has + shape (bs, num_queries, 4) when `with_box_refine` of the + detector is `True`, otherwise (bs, num_queries, 2). The + coordinates are arranged as (cx, cy) when the last dimension is + 2, and (cx, cy, w, h) when it is 4. + batch_data_samples (list[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): If `True`, return boxes in original + image space. Defaults to `True`. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + outs = self(hidden_states, references) + + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas, rescale=rescale) + return predictions + + def predict_by_feat(self, + all_layers_cls_scores: Tensor, + all_layers_bbox_preds: Tensor, + batch_img_metas: List[Dict], + rescale: bool = False) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Args: + all_layers_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, num_queries, + cls_out_channels). + all_layers_bbox_preds (Tensor): Regression outputs of all decoder + layers. Each is a 4D-tensor with normalized coordinate format + (cx, cy, w, h) and shape (num_decoder_layers, bs, num_queries, + 4) with the last dimension arranged as (cx, cy, w, h). + batch_img_metas (list[dict]): Meta information of each image. + rescale (bool, optional): If `True`, return boxes in original + image space. Default `False`. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + cls_scores = all_layers_cls_scores[-1] + bbox_preds = all_layers_bbox_preds[-1] + + result_list = [] + for img_id in range(len(batch_img_metas)): + cls_score = cls_scores[img_id] + bbox_pred = bbox_preds[img_id] + img_meta = batch_img_metas[img_id] + results = self._predict_by_feat_single(cls_score, bbox_pred, + img_meta, rescale) + result_list.append(results) + return result_list diff --git a/mmdetection/mmdet/models/dense_heads/dense_test_mixins.py b/mmdetection/mmdet/models/dense_heads/dense_test_mixins.py new file mode 100644 index 00000000..a7526d48 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/dense_test_mixins.py @@ -0,0 +1,215 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import sys +import warnings +from inspect import signature + +import torch +from mmcv.ops import batched_nms +from mmengine.structures import InstanceData + +from mmdet.structures.bbox import bbox_mapping_back +from ..test_time_augs import merge_aug_proposals + +if sys.version_info >= (3, 7): + from mmdet.utils.contextmanagers import completed + + +class BBoxTestMixin(object): + """Mixin class for testing det bboxes via DenseHead.""" + + def simple_test_bboxes(self, feats, img_metas, rescale=False): + """Test det bboxes without test-time augmentation, can be applied in + DenseHead except for ``RPNHead`` and its variants, e.g., ``GARPNHead``, + etc. + + Args: + feats (tuple[torch.Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + img_metas (list[dict]): List of image information. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[obj:`InstanceData`]: Detection results of each + image after the post process. \ + Each item usually contains following keys. \ + + - scores (Tensor): Classification scores, has a shape + (num_instance,) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances,). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + warnings.warn('You are calling `simple_test_bboxes` in ' + '`dense_test_mixins`, but the `dense_test_mixins`' + 'will be deprecated soon. Please use ' + '`simple_test` instead.') + outs = self.forward(feats) + results_list = self.get_results( + *outs, img_metas=img_metas, rescale=rescale) + return results_list + + def aug_test_bboxes(self, feats, img_metas, rescale=False): + """Test det bboxes with test time augmentation, can be applied in + DenseHead except for ``RPNHead`` and its variants, e.g., ``GARPNHead``, + etc. + + Args: + feats (list[Tensor]): the outer list indicates test-time + augmentations and inner Tensor should have a shape NxCxHxW, + which contains features for all images in the batch. + img_metas (list[list[dict]]): the outer list indicates test-time + augs (multiscale, flip, etc.) and the inner list indicates + images in a batch. each dict has image information. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple. + The first item is ``bboxes`` with shape (n, 5), + where 5 represent (tl_x, tl_y, br_x, br_y, score). + The shape of the second tensor in the tuple is ``labels`` + with shape (n,). The length of list should always be 1. + """ + + warnings.warn('You are calling `aug_test_bboxes` in ' + '`dense_test_mixins`, but the `dense_test_mixins`' + 'will be deprecated soon. Please use ' + '`aug_test` instead.') + # check with_nms argument + gb_sig = signature(self.get_results) + gb_args = [p.name for p in gb_sig.parameters.values()] + gbs_sig = signature(self._get_results_single) + gbs_args = [p.name for p in gbs_sig.parameters.values()] + assert ('with_nms' in gb_args) and ('with_nms' in gbs_args), \ + f'{self.__class__.__name__}' \ + ' does not support test-time augmentation' + + aug_bboxes = [] + aug_scores = [] + aug_labels = [] + for x, img_meta in zip(feats, img_metas): + # only one image in the batch + outs = self.forward(x) + bbox_outputs = self.get_results( + *outs, + img_metas=img_meta, + cfg=self.test_cfg, + rescale=False, + with_nms=False)[0] + aug_bboxes.append(bbox_outputs.bboxes) + aug_scores.append(bbox_outputs.scores) + if len(bbox_outputs) >= 3: + aug_labels.append(bbox_outputs.labels) + + # after merging, bboxes will be rescaled to the original image size + merged_bboxes, merged_scores = self.merge_aug_bboxes( + aug_bboxes, aug_scores, img_metas) + merged_labels = torch.cat(aug_labels, dim=0) if aug_labels else None + + if merged_bboxes.numel() == 0: + det_bboxes = torch.cat([merged_bboxes, merged_scores[:, None]], -1) + return [ + (det_bboxes, merged_labels), + ] + + det_bboxes, keep_idxs = batched_nms(merged_bboxes, merged_scores, + merged_labels, self.test_cfg.nms) + det_bboxes = det_bboxes[:self.test_cfg.max_per_img] + det_labels = merged_labels[keep_idxs][:self.test_cfg.max_per_img] + + if rescale: + _det_bboxes = det_bboxes + else: + _det_bboxes = det_bboxes.clone() + _det_bboxes[:, :4] *= det_bboxes.new_tensor( + img_metas[0][0]['scale_factor']) + + results = InstanceData() + results.bboxes = _det_bboxes[:, :4] + results.scores = _det_bboxes[:, 4] + results.labels = det_labels + return [results] + + def aug_test_rpn(self, feats, img_metas): + """Test with augmentation for only for ``RPNHead`` and its variants, + e.g., ``GARPNHead``, etc. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + img_metas (list[dict]): Meta info of each image. + + Returns: + list[Tensor]: Proposals of each image, each item has shape (n, 5), + where 5 represent (tl_x, tl_y, br_x, br_y, score). + """ + samples_per_gpu = len(img_metas[0]) + aug_proposals = [[] for _ in range(samples_per_gpu)] + for x, img_meta in zip(feats, img_metas): + results_list = self.simple_test_rpn(x, img_meta) + for i, results in enumerate(results_list): + proposals = torch.cat( + [results.bboxes, results.scores[:, None]], dim=-1) + aug_proposals[i].append(proposals) + # reorganize the order of 'img_metas' to match the dimensions + # of 'aug_proposals' + aug_img_metas = [] + for i in range(samples_per_gpu): + aug_img_meta = [] + for j in range(len(img_metas)): + aug_img_meta.append(img_metas[j][i]) + aug_img_metas.append(aug_img_meta) + # after merging, proposals will be rescaled to the original image size + + merged_proposals = [] + for proposals, aug_img_meta in zip(aug_proposals, aug_img_metas): + merged_proposal = merge_aug_proposals(proposals, aug_img_meta, + self.test_cfg) + results = InstanceData() + results.bboxes = merged_proposal[:, :4] + results.scores = merged_proposal[:, 4] + merged_proposals.append(results) + return merged_proposals + + if sys.version_info >= (3, 7): + + async def async_simple_test_rpn(self, x, img_metas): + sleep_interval = self.test_cfg.pop('async_sleep_interval', 0.025) + async with completed( + __name__, 'rpn_head_forward', + sleep_interval=sleep_interval): + rpn_outs = self(x) + + proposal_list = self.get_results(*rpn_outs, img_metas=img_metas) + return proposal_list + + def merge_aug_bboxes(self, aug_bboxes, aug_scores, img_metas): + """Merge augmented detection bboxes and scores. + + Args: + aug_bboxes (list[Tensor]): shape (n, 4*#class) + aug_scores (list[Tensor] or None): shape (n, #class) + img_shapes (list[Tensor]): shape (3, ). + + Returns: + tuple[Tensor]: ``bboxes`` with shape (n,4), where + 4 represent (tl_x, tl_y, br_x, br_y) + and ``scores`` with shape (n,). + """ + recovered_bboxes = [] + for bboxes, img_info in zip(aug_bboxes, img_metas): + img_shape = img_info[0]['img_shape'] + scale_factor = img_info[0]['scale_factor'] + flip = img_info[0]['flip'] + flip_direction = img_info[0]['flip_direction'] + bboxes = bbox_mapping_back(bboxes, img_shape, scale_factor, flip, + flip_direction) + recovered_bboxes.append(bboxes) + bboxes = torch.cat(recovered_bboxes, dim=0) + if aug_scores is None: + return bboxes + else: + scores = torch.cat(aug_scores, dim=0) + return bboxes, scores diff --git a/mmdetection/mmdet/models/dense_heads/detr_head.py b/mmdetection/mmdet/models/dense_heads/detr_head.py new file mode 100644 index 00000000..9daeb474 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/detr_head.py @@ -0,0 +1,634 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Linear +from mmcv.cnn.bricks.transformer import FFN +from mmengine.model import BaseModule +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import SampleList +from mmdet.structures.bbox import (bbox_cxcywh_to_xyxy, bbox_overlaps, + bbox_xyxy_to_cxcywh) +from mmdet.utils import (ConfigType, InstanceList, OptInstanceList, + OptMultiConfig, reduce_mean) +from ..losses import QualityFocalLoss +from ..utils import multi_apply + + +@MODELS.register_module() +class DETRHead(BaseModule): + r"""Head of DETR. DETR:End-to-End Object Detection with Transformers. + + More details can be found in the `paper + `_ . + + Args: + num_classes (int): Number of categories excluding the background. + embed_dims (int): The dims of Transformer embedding. + num_reg_fcs (int): Number of fully-connected layers used in `FFN`, + which is then used for the regression head. Defaults to 2. + sync_cls_avg_factor (bool): Whether to sync the `avg_factor` of + all ranks. Default to `False`. + loss_cls (:obj:`ConfigDict` or dict): Config of the classification + loss. Defaults to `CrossEntropyLoss`. + loss_bbox (:obj:`ConfigDict` or dict): Config of the regression bbox + loss. Defaults to `L1Loss`. + loss_iou (:obj:`ConfigDict` or dict): Config of the regression iou + loss. Defaults to `GIoULoss`. + train_cfg (:obj:`ConfigDict` or dict): Training config of transformer + head. + test_cfg (:obj:`ConfigDict` or dict): Testing config of transformer + head. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + _version = 2 + + def __init__( + self, + num_classes: int, + embed_dims: int = 256, + num_reg_fcs: int = 2, + sync_cls_avg_factor: bool = False, + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', + bg_cls_weight=0.1, + use_sigmoid=False, + loss_weight=1.0, + class_weight=1.0), + loss_bbox: ConfigType = dict(type='L1Loss', loss_weight=5.0), + loss_iou: ConfigType = dict(type='GIoULoss', loss_weight=2.0), + train_cfg: ConfigType = dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='ClassificationCost', weight=1.), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg: ConfigType = dict(max_per_img=100), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + self.bg_cls_weight = 0 + self.sync_cls_avg_factor = sync_cls_avg_factor + class_weight = loss_cls.get('class_weight', None) + if class_weight is not None and (self.__class__ is DETRHead): + assert isinstance(class_weight, float), 'Expected ' \ + 'class_weight to have type float. Found ' \ + f'{type(class_weight)}.' + # NOTE following the official DETR repo, bg_cls_weight means + # relative classification weight of the no-object class. + bg_cls_weight = loss_cls.get('bg_cls_weight', class_weight) + assert isinstance(bg_cls_weight, float), 'Expected ' \ + 'bg_cls_weight to have type float. Found ' \ + f'{type(bg_cls_weight)}.' + class_weight = torch.ones(num_classes + 1) * class_weight + # set background class as the last indice + class_weight[num_classes] = bg_cls_weight + loss_cls.update({'class_weight': class_weight}) + if 'bg_cls_weight' in loss_cls: + loss_cls.pop('bg_cls_weight') + self.bg_cls_weight = bg_cls_weight + + if train_cfg: + assert 'assigner' in train_cfg, 'assigner should be provided ' \ + 'when train_cfg is set.' + assigner = train_cfg['assigner'] + self.assigner = TASK_UTILS.build(assigner) + if train_cfg.get('sampler', None) is not None: + raise RuntimeError('DETR do not build sampler.') + self.num_classes = num_classes + self.embed_dims = embed_dims + self.num_reg_fcs = num_reg_fcs + self.train_cfg = train_cfg + self.test_cfg = test_cfg + self.loss_cls = MODELS.build(loss_cls) + self.loss_bbox = MODELS.build(loss_bbox) + self.loss_iou = MODELS.build(loss_iou) + + if self.loss_cls.use_sigmoid: + self.cls_out_channels = num_classes + else: + self.cls_out_channels = num_classes + 1 + + self._init_layers() + + def _init_layers(self) -> None: + """Initialize layers of the transformer head.""" + # cls branch + self.fc_cls = Linear(self.embed_dims, self.cls_out_channels) + # reg branch + self.activate = nn.ReLU() + self.reg_ffn = FFN( + self.embed_dims, + self.embed_dims, + self.num_reg_fcs, + dict(type='ReLU', inplace=True), + dropout=0.0, + add_residual=False) + # NOTE the activations of reg_branch here is the same as + # those in transformer, but they are actually different + # in DAB-DETR (prelu in transformer and relu in reg_branch) + self.fc_reg = Linear(self.embed_dims, 4) + + def forward(self, hidden_states: Tensor) -> Tuple[Tensor]: + """"Forward function. + + Args: + hidden_states (Tensor): Features from transformer decoder. If + `return_intermediate_dec` in detr.py is True output has shape + (num_decoder_layers, bs, num_queries, dim), else has shape + (1, bs, num_queries, dim) which only contains the last layer + outputs. + Returns: + tuple[Tensor]: results of head containing the following tensor. + + - layers_cls_scores (Tensor): Outputs from the classification head, + shape (num_decoder_layers, bs, num_queries, cls_out_channels). + Note cls_out_channels should include background. + - layers_bbox_preds (Tensor): Sigmoid outputs from the regression + head with normalized coordinate format (cx, cy, w, h), has shape + (num_decoder_layers, bs, num_queries, 4). + """ + layers_cls_scores = self.fc_cls(hidden_states) + layers_bbox_preds = self.fc_reg( + self.activate(self.reg_ffn(hidden_states))).sigmoid() + return layers_cls_scores, layers_bbox_preds + + def loss(self, hidden_states: Tensor, + batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the features of the upstream network. + + Args: + hidden_states (Tensor): Feature from the transformer decoder, has + shape (num_decoder_layers, bs, num_queries, cls_out_channels) + or (num_decoder_layers, num_queries, bs, cls_out_channels). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + outs = self(hidden_states) + loss_inputs = outs + (batch_gt_instances, batch_img_metas) + losses = self.loss_by_feat(*loss_inputs) + return losses + + def loss_by_feat( + self, + all_layers_cls_scores: Tensor, + all_layers_bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """"Loss function. + + Only outputs from the last feature level are used for computing + losses by default. + + Args: + all_layers_cls_scores (Tensor): Classification outputs + of each decoder layers. Each is a 4D-tensor, has shape + (num_decoder_layers, bs, num_queries, cls_out_channels). + all_layers_bbox_preds (Tensor): Sigmoid regression + outputs of each decoder layers. Each is a 4D-tensor with + normalized coordinate format (cx, cy, w, h) and shape + (num_decoder_layers, bs, num_queries, 4). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert batch_gt_instances_ignore is None, \ + f'{self.__class__.__name__} only supports ' \ + 'for batch_gt_instances_ignore setting to None.' + + losses_cls, losses_bbox, losses_iou = multi_apply( + self.loss_by_feat_single, + all_layers_cls_scores, + all_layers_bbox_preds, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas) + + loss_dict = dict() + # loss from the last decoder layer + loss_dict['loss_cls'] = losses_cls[-1] + loss_dict['loss_bbox'] = losses_bbox[-1] + loss_dict['loss_iou'] = losses_iou[-1] + # loss from other decoder layers + num_dec_layer = 0 + for loss_cls_i, loss_bbox_i, loss_iou_i in \ + zip(losses_cls[:-1], losses_bbox[:-1], losses_iou[:-1]): + loss_dict[f'd{num_dec_layer}.loss_cls'] = loss_cls_i + loss_dict[f'd{num_dec_layer}.loss_bbox'] = loss_bbox_i + loss_dict[f'd{num_dec_layer}.loss_iou'] = loss_iou_i + num_dec_layer += 1 + return loss_dict + + def loss_by_feat_single(self, cls_scores: Tensor, bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict]) -> Tuple[Tensor]: + """Loss function for outputs from a single decoder layer of a single + feature level. + + Args: + cls_scores (Tensor): Box score logits from a single decoder layer + for all images, has shape (bs, num_queries, cls_out_channels). + bbox_preds (Tensor): Sigmoid outputs from a single decoder layer + for all images, with normalized coordinate (cx, cy, w, h) and + shape (bs, num_queries, 4). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + + Returns: + Tuple[Tensor]: A tuple including `loss_cls`, `loss_box` and + `loss_iou`. + """ + num_imgs = cls_scores.size(0) + cls_scores_list = [cls_scores[i] for i in range(num_imgs)] + bbox_preds_list = [bbox_preds[i] for i in range(num_imgs)] + cls_reg_targets = self.get_targets(cls_scores_list, bbox_preds_list, + batch_gt_instances, batch_img_metas) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + num_total_pos, num_total_neg) = cls_reg_targets + labels = torch.cat(labels_list, 0) + label_weights = torch.cat(label_weights_list, 0) + bbox_targets = torch.cat(bbox_targets_list, 0) + bbox_weights = torch.cat(bbox_weights_list, 0) + + # classification loss + cls_scores = cls_scores.reshape(-1, self.cls_out_channels) + # construct weighted avg_factor to match with the official DETR repo + cls_avg_factor = num_total_pos * 1.0 + \ + num_total_neg * self.bg_cls_weight + if self.sync_cls_avg_factor: + cls_avg_factor = reduce_mean( + cls_scores.new_tensor([cls_avg_factor])) + cls_avg_factor = max(cls_avg_factor, 1) + + if isinstance(self.loss_cls, QualityFocalLoss): + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().squeeze(1) + scores = label_weights.new_zeros(labels.shape) + pos_bbox_targets = bbox_targets[pos_inds] + pos_decode_bbox_targets = bbox_cxcywh_to_xyxy(pos_bbox_targets) + pos_bbox_pred = bbox_preds.reshape(-1, 4)[pos_inds] + pos_decode_bbox_pred = bbox_cxcywh_to_xyxy(pos_bbox_pred) + scores[pos_inds] = bbox_overlaps( + pos_decode_bbox_pred.detach(), + pos_decode_bbox_targets, + is_aligned=True) + loss_cls = self.loss_cls( + cls_scores, (labels, scores), + label_weights, + avg_factor=cls_avg_factor) + else: + loss_cls = self.loss_cls( + cls_scores, labels, label_weights, avg_factor=cls_avg_factor) + + # Compute the average number of gt boxes across all gpus, for + # normalization purposes + num_total_pos = loss_cls.new_tensor([num_total_pos]) + num_total_pos = torch.clamp(reduce_mean(num_total_pos), min=1).item() + + # construct factors used for rescale bboxes + factors = [] + for img_meta, bbox_pred in zip(batch_img_metas, bbox_preds): + img_h, img_w, = img_meta['img_shape'] + factor = bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0).repeat( + bbox_pred.size(0), 1) + factors.append(factor) + factors = torch.cat(factors, 0) + + # DETR regress the relative position of boxes (cxcywh) in the image, + # thus the learning target is normalized by the image size. So here + # we need to re-scale them for calculating IoU loss + bbox_preds = bbox_preds.reshape(-1, 4) + bboxes = bbox_cxcywh_to_xyxy(bbox_preds) * factors + bboxes_gt = bbox_cxcywh_to_xyxy(bbox_targets) * factors + + # regression IoU loss, defaultly GIoU loss + loss_iou = self.loss_iou( + bboxes, bboxes_gt, bbox_weights, avg_factor=num_total_pos) + + # regression L1 loss + loss_bbox = self.loss_bbox( + bbox_preds, bbox_targets, bbox_weights, avg_factor=num_total_pos) + return loss_cls, loss_bbox, loss_iou + + def get_targets(self, cls_scores_list: List[Tensor], + bbox_preds_list: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict]) -> tuple: + """Compute regression and classification targets for a batch image. + + Outputs from a single decoder layer of a single feature level are used. + + Args: + cls_scores_list (list[Tensor]): Box score logits from a single + decoder layer for each image, has shape [num_queries, + cls_out_channels]. + bbox_preds_list (list[Tensor]): Sigmoid outputs from a single + decoder layer for each image, with normalized coordinate + (cx, cy, w, h) and shape [num_queries, 4]. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + + Returns: + tuple: a tuple containing the following targets. + + - labels_list (list[Tensor]): Labels for all images. + - label_weights_list (list[Tensor]): Label weights for all images. + - bbox_targets_list (list[Tensor]): BBox targets for all images. + - bbox_weights_list (list[Tensor]): BBox weights for all images. + - num_total_pos (int): Number of positive samples in all images. + - num_total_neg (int): Number of negative samples in all images. + """ + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + pos_inds_list, + neg_inds_list) = multi_apply(self._get_targets_single, + cls_scores_list, bbox_preds_list, + batch_gt_instances, batch_img_metas) + num_total_pos = sum((inds.numel() for inds in pos_inds_list)) + num_total_neg = sum((inds.numel() for inds in neg_inds_list)) + return (labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, num_total_pos, num_total_neg) + + def _get_targets_single(self, cls_score: Tensor, bbox_pred: Tensor, + gt_instances: InstanceData, + img_meta: dict) -> tuple: + """Compute regression and classification targets for one image. + + Outputs from a single decoder layer of a single feature level are used. + + Args: + cls_score (Tensor): Box score logits from a single decoder layer + for one image. Shape [num_queries, cls_out_channels]. + bbox_pred (Tensor): Sigmoid outputs from a single decoder layer + for one image, with normalized coordinate (cx, cy, w, h) and + shape [num_queries, 4]. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for one image. + + Returns: + tuple[Tensor]: a tuple containing the following for one image. + + - labels (Tensor): Labels of each image. + - label_weights (Tensor]): Label weights of each image. + - bbox_targets (Tensor): BBox targets of each image. + - bbox_weights (Tensor): BBox weights of each image. + - pos_inds (Tensor): Sampled positive indices for each image. + - neg_inds (Tensor): Sampled negative indices for each image. + """ + img_h, img_w = img_meta['img_shape'] + factor = bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0) + num_bboxes = bbox_pred.size(0) + # convert bbox_pred from xywh, normalized to xyxy, unnormalized + bbox_pred = bbox_cxcywh_to_xyxy(bbox_pred) + bbox_pred = bbox_pred * factor + + pred_instances = InstanceData(scores=cls_score, bboxes=bbox_pred) + # assigner and sampler + assign_result = self.assigner.assign( + pred_instances=pred_instances, + gt_instances=gt_instances, + img_meta=img_meta) + + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + pos_inds = torch.nonzero( + assign_result.gt_inds > 0, as_tuple=False).squeeze(-1).unique() + neg_inds = torch.nonzero( + assign_result.gt_inds == 0, as_tuple=False).squeeze(-1).unique() + pos_assigned_gt_inds = assign_result.gt_inds[pos_inds] - 1 + pos_gt_bboxes = gt_bboxes[pos_assigned_gt_inds.long(), :] + + # label targets + labels = gt_bboxes.new_full((num_bboxes, ), + self.num_classes, + dtype=torch.long) + labels[pos_inds] = gt_labels[pos_assigned_gt_inds] + label_weights = gt_bboxes.new_ones(num_bboxes) + + # bbox targets + bbox_targets = torch.zeros_like(bbox_pred, dtype=gt_bboxes.dtype) + bbox_weights = torch.zeros_like(bbox_pred, dtype=gt_bboxes.dtype) + bbox_weights[pos_inds] = 1.0 + + # DETR regress the relative position of boxes (cxcywh) in the image. + # Thus the learning target should be normalized by the image size, also + # the box format should be converted from defaultly x1y1x2y2 to cxcywh. + pos_gt_bboxes_normalized = pos_gt_bboxes / factor + pos_gt_bboxes_targets = bbox_xyxy_to_cxcywh(pos_gt_bboxes_normalized) + bbox_targets[pos_inds] = pos_gt_bboxes_targets + return (labels, label_weights, bbox_targets, bbox_weights, pos_inds, + neg_inds) + + def loss_and_predict( + self, hidden_states: Tuple[Tensor], + batch_data_samples: SampleList) -> Tuple[dict, InstanceList]: + """Perform forward propagation of the head, then calculate loss and + predictions from the features and data samples. Over-write because + img_metas are needed as inputs for bbox_head. + + Args: + hidden_states (tuple[Tensor]): Feature from the transformer + decoder, has shape (num_decoder_layers, bs, num_queries, dim). + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns: + tuple: the return value is a tuple contains: + + - losses: (dict[str, Tensor]): A dictionary of loss components. + - predictions (list[:obj:`InstanceData`]): Detection + results of each image after the post process. + """ + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + outs = self(hidden_states) + loss_inputs = outs + (batch_gt_instances, batch_img_metas) + losses = self.loss_by_feat(*loss_inputs) + + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas) + return losses, predictions + + def predict(self, + hidden_states: Tuple[Tensor], + batch_data_samples: SampleList, + rescale: bool = True) -> InstanceList: + """Perform forward propagation of the detection head and predict + detection results on the features of the upstream network. Over-write + because img_metas are needed as inputs for bbox_head. + + Args: + hidden_states (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): Whether to rescale the results. + Defaults to True. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + last_layer_hidden_state = hidden_states[-1].unsqueeze(0) + outs = self(last_layer_hidden_state) + + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas, rescale=rescale) + + return predictions + + def predict_by_feat(self, + layer_cls_scores: Tensor, + layer_bbox_preds: Tensor, + batch_img_metas: List[dict], + rescale: bool = True) -> InstanceList: + """Transform network outputs for a batch into bbox predictions. + + Args: + layer_cls_scores (Tensor): Classification outputs of the last or + all decoder layer. Each is a 4D-tensor, has shape + (num_decoder_layers, bs, num_queries, cls_out_channels). + layer_bbox_preds (Tensor): Sigmoid regression outputs of the last + or all decoder layer. Each is a 4D-tensor with normalized + coordinate format (cx, cy, w, h) and shape + (num_decoder_layers, bs, num_queries, 4). + batch_img_metas (list[dict]): Meta information of each image. + rescale (bool, optional): If `True`, return boxes in original + image space. Defaults to `True`. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + # NOTE only using outputs from the last feature level, + # and only the outputs from the last decoder layer is used. + cls_scores = layer_cls_scores[-1] + bbox_preds = layer_bbox_preds[-1] + + result_list = [] + for img_id in range(len(batch_img_metas)): + cls_score = cls_scores[img_id] + bbox_pred = bbox_preds[img_id] + img_meta = batch_img_metas[img_id] + results = self._predict_by_feat_single(cls_score, bbox_pred, + img_meta, rescale) + result_list.append(results) + return result_list + + def _predict_by_feat_single(self, + cls_score: Tensor, + bbox_pred: Tensor, + img_meta: dict, + rescale: bool = True) -> InstanceData: + """Transform outputs from the last decoder layer into bbox predictions + for each image. + + Args: + cls_score (Tensor): Box score logits from the last decoder layer + for each image. Shape [num_queries, cls_out_channels]. + bbox_pred (Tensor): Sigmoid outputs from the last decoder layer + for each image, with coordinate format (cx, cy, w, h) and + shape [num_queries, 4]. + img_meta (dict): Image meta info. + rescale (bool): If True, return boxes in original image + space. Default True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_score) == len(bbox_pred) # num_queries + max_per_img = self.test_cfg.get('max_per_img', len(cls_score)) + img_shape = img_meta['img_shape'] + # exclude background + if self.loss_cls.use_sigmoid: + cls_score = cls_score.sigmoid() + scores, indexes = cls_score.view(-1).topk(max_per_img) + det_labels = indexes % self.num_classes + bbox_index = indexes // self.num_classes + bbox_pred = bbox_pred[bbox_index] + else: + scores, det_labels = F.softmax(cls_score, dim=-1)[..., :-1].max(-1) + scores, bbox_index = scores.topk(max_per_img) + bbox_pred = bbox_pred[bbox_index] + det_labels = det_labels[bbox_index] + + det_bboxes = bbox_cxcywh_to_xyxy(bbox_pred) + det_bboxes[:, 0::2] = det_bboxes[:, 0::2] * img_shape[1] + det_bboxes[:, 1::2] = det_bboxes[:, 1::2] * img_shape[0] + det_bboxes[:, 0::2].clamp_(min=0, max=img_shape[1]) + det_bboxes[:, 1::2].clamp_(min=0, max=img_shape[0]) + if rescale: + assert img_meta.get('scale_factor') is not None + det_bboxes /= det_bboxes.new_tensor( + img_meta['scale_factor']).repeat((1, 2)) + + results = InstanceData() + results.bboxes = det_bboxes + results.scores = scores + results.labels = det_labels + return results diff --git a/mmdetection/mmdet/models/dense_heads/dino_head.py b/mmdetection/mmdet/models/dense_heads/dino_head.py new file mode 100644 index 00000000..54f46d14 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/dino_head.py @@ -0,0 +1,479 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Tuple + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import (bbox_cxcywh_to_xyxy, bbox_overlaps, + bbox_xyxy_to_cxcywh) +from mmdet.utils import InstanceList, OptInstanceList, reduce_mean +from ..losses import QualityFocalLoss +from ..utils import multi_apply +from .deformable_detr_head import DeformableDETRHead + + +@MODELS.register_module() +class DINOHead(DeformableDETRHead): + r"""Head of the DINO: DETR with Improved DeNoising Anchor Boxes + for End-to-End Object Detection + + Code is modified from the `official github repo + `_. + + More details can be found in the `paper + `_ . + """ + + def loss(self, hidden_states: Tensor, references: List[Tensor], + enc_outputs_class: Tensor, enc_outputs_coord: Tensor, + batch_data_samples: SampleList, dn_meta: Dict[str, int]) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the queries of the upstream network. + + Args: + hidden_states (Tensor): Hidden states output from each decoder + layer, has shape (num_decoder_layers, bs, num_queries_total, + dim), where `num_queries_total` is the sum of + `num_denoising_queries` and `num_matching_queries` when + `self.training` is `True`, else `num_matching_queries`. + references (list[Tensor]): List of the reference from the decoder. + The first reference is the `init_reference` (initial) and the + other num_decoder_layers(6) references are `inter_references` + (intermediate). The `init_reference` has shape (bs, + num_queries_total, 4) and each `inter_reference` has shape + (bs, num_queries, 4) with the last dimension arranged as + (cx, cy, w, h). + enc_outputs_class (Tensor): The score of each point on encode + feature map, has shape (bs, num_feat_points, cls_out_channels). + enc_outputs_coord (Tensor): The proposal generate from the + encode feature map, has shape (bs, num_feat_points, 4) with the + last dimension arranged as (cx, cy, w, h). + batch_data_samples (list[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + dict: A dictionary of loss components. + """ + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + outs = self(hidden_states, references) + loss_inputs = outs + (enc_outputs_class, enc_outputs_coord, + batch_gt_instances, batch_img_metas, dn_meta) + losses = self.loss_by_feat(*loss_inputs) + return losses + + def loss_by_feat( + self, + all_layers_cls_scores: Tensor, + all_layers_bbox_preds: Tensor, + enc_cls_scores: Tensor, + enc_bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + dn_meta: Dict[str, int], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Loss function. + + Args: + all_layers_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, + num_queries_total, cls_out_channels), where + `num_queries_total` is the sum of `num_denoising_queries` + and `num_matching_queries`. + all_layers_bbox_preds (Tensor): Regression outputs of all decoder + layers. Each is a 4D-tensor with normalized coordinate format + (cx, cy, w, h) and has shape (num_decoder_layers, bs, + num_queries_total, 4). + enc_cls_scores (Tensor): The score of each point on encode + feature map, has shape (bs, num_feat_points, cls_out_channels). + enc_bbox_preds (Tensor): The proposal generate from the encode + feature map, has shape (bs, num_feat_points, 4) with the last + dimension arranged as (cx, cy, w, h). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + # extract denoising and matching part of outputs + (all_layers_matching_cls_scores, all_layers_matching_bbox_preds, + all_layers_denoising_cls_scores, all_layers_denoising_bbox_preds) = \ + self.split_outputs( + all_layers_cls_scores, all_layers_bbox_preds, dn_meta) + + loss_dict = super(DeformableDETRHead, self).loss_by_feat( + all_layers_matching_cls_scores, all_layers_matching_bbox_preds, + batch_gt_instances, batch_img_metas, batch_gt_instances_ignore) + # NOTE DETRHead.loss_by_feat but not DeformableDETRHead.loss_by_feat + # is called, because the encoder loss calculations are different + # between DINO and DeformableDETR. + + # loss of proposal generated from encode feature map. + if enc_cls_scores is not None: + # NOTE The enc_loss calculation of the DINO is + # different from that of Deformable DETR. + enc_loss_cls, enc_losses_bbox, enc_losses_iou = \ + self.loss_by_feat_single( + enc_cls_scores, enc_bbox_preds, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas) + loss_dict['enc_loss_cls'] = enc_loss_cls + loss_dict['enc_loss_bbox'] = enc_losses_bbox + loss_dict['enc_loss_iou'] = enc_losses_iou + + if all_layers_denoising_cls_scores is not None: + # calculate denoising loss from all decoder layers + dn_losses_cls, dn_losses_bbox, dn_losses_iou = self.loss_dn( + all_layers_denoising_cls_scores, + all_layers_denoising_bbox_preds, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas, + dn_meta=dn_meta) + # collate denoising loss + loss_dict['dn_loss_cls'] = dn_losses_cls[-1] + loss_dict['dn_loss_bbox'] = dn_losses_bbox[-1] + loss_dict['dn_loss_iou'] = dn_losses_iou[-1] + for num_dec_layer, (loss_cls_i, loss_bbox_i, loss_iou_i) in \ + enumerate(zip(dn_losses_cls[:-1], dn_losses_bbox[:-1], + dn_losses_iou[:-1])): + loss_dict[f'd{num_dec_layer}.dn_loss_cls'] = loss_cls_i + loss_dict[f'd{num_dec_layer}.dn_loss_bbox'] = loss_bbox_i + loss_dict[f'd{num_dec_layer}.dn_loss_iou'] = loss_iou_i + return loss_dict + + def loss_dn(self, all_layers_denoising_cls_scores: Tensor, + all_layers_denoising_bbox_preds: Tensor, + batch_gt_instances: InstanceList, batch_img_metas: List[dict], + dn_meta: Dict[str, int]) -> Tuple[List[Tensor]]: + """Calculate denoising loss. + + Args: + all_layers_denoising_cls_scores (Tensor): Classification scores of + all decoder layers in denoising part, has shape ( + num_decoder_layers, bs, num_denoising_queries, + cls_out_channels). + all_layers_denoising_bbox_preds (Tensor): Regression outputs of all + decoder layers in denoising part. Each is a 4D-tensor with + normalized coordinate format (cx, cy, w, h) and has shape + (num_decoder_layers, bs, num_denoising_queries, 4). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + Tuple[List[Tensor]]: The loss_dn_cls, loss_dn_bbox, and loss_dn_iou + of each decoder layers. + """ + return multi_apply( + self._loss_dn_single, + all_layers_denoising_cls_scores, + all_layers_denoising_bbox_preds, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas, + dn_meta=dn_meta) + + def _loss_dn_single(self, dn_cls_scores: Tensor, dn_bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + dn_meta: Dict[str, int]) -> Tuple[Tensor]: + """Denoising loss for outputs from a single decoder layer. + + Args: + dn_cls_scores (Tensor): Classification scores of a single decoder + layer in denoising part, has shape (bs, num_denoising_queries, + cls_out_channels). + dn_bbox_preds (Tensor): Regression outputs of a single decoder + layer in denoising part. Each is a 4D-tensor with normalized + coordinate format (cx, cy, w, h) and has shape + (bs, num_denoising_queries, 4). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + Tuple[Tensor]: A tuple including `loss_cls`, `loss_box` and + `loss_iou`. + """ + cls_reg_targets = self.get_dn_targets(batch_gt_instances, + batch_img_metas, dn_meta) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + num_total_pos, num_total_neg) = cls_reg_targets + labels = torch.cat(labels_list, 0) + label_weights = torch.cat(label_weights_list, 0) + bbox_targets = torch.cat(bbox_targets_list, 0) + bbox_weights = torch.cat(bbox_weights_list, 0) + + # classification loss + cls_scores = dn_cls_scores.reshape(-1, self.cls_out_channels) + # construct weighted avg_factor to match with the official DETR repo + cls_avg_factor = \ + num_total_pos * 1.0 + num_total_neg * self.bg_cls_weight + if self.sync_cls_avg_factor: + cls_avg_factor = reduce_mean( + cls_scores.new_tensor([cls_avg_factor])) + cls_avg_factor = max(cls_avg_factor, 1) + + if len(cls_scores) > 0: + if isinstance(self.loss_cls, QualityFocalLoss): + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().squeeze(1) + scores = label_weights.new_zeros(labels.shape) + pos_bbox_targets = bbox_targets[pos_inds] + pos_decode_bbox_targets = bbox_cxcywh_to_xyxy(pos_bbox_targets) + pos_bbox_pred = dn_bbox_preds.reshape(-1, 4)[pos_inds] + pos_decode_bbox_pred = bbox_cxcywh_to_xyxy(pos_bbox_pred) + scores[pos_inds] = bbox_overlaps( + pos_decode_bbox_pred.detach(), + pos_decode_bbox_targets, + is_aligned=True) + loss_cls = self.loss_cls( + cls_scores, (labels, scores), + weight=label_weights, + avg_factor=cls_avg_factor) + else: + loss_cls = self.loss_cls( + cls_scores, + labels, + label_weights, + avg_factor=cls_avg_factor) + else: + loss_cls = torch.zeros( + 1, dtype=cls_scores.dtype, device=cls_scores.device) + + # Compute the average number of gt boxes across all gpus, for + # normalization purposes + num_total_pos = loss_cls.new_tensor([num_total_pos]) + num_total_pos = torch.clamp(reduce_mean(num_total_pos), min=1).item() + + # construct factors used for rescale bboxes + factors = [] + for img_meta, bbox_pred in zip(batch_img_metas, dn_bbox_preds): + img_h, img_w = img_meta['img_shape'] + factor = bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0).repeat( + bbox_pred.size(0), 1) + factors.append(factor) + factors = torch.cat(factors) + + # DETR regress the relative position of boxes (cxcywh) in the image, + # thus the learning target is normalized by the image size. So here + # we need to re-scale them for calculating IoU loss + bbox_preds = dn_bbox_preds.reshape(-1, 4) + bboxes = bbox_cxcywh_to_xyxy(bbox_preds) * factors + bboxes_gt = bbox_cxcywh_to_xyxy(bbox_targets) * factors + + # regression IoU loss, defaultly GIoU loss + loss_iou = self.loss_iou( + bboxes, bboxes_gt, bbox_weights, avg_factor=num_total_pos) + + # regression L1 loss + loss_bbox = self.loss_bbox( + bbox_preds, bbox_targets, bbox_weights, avg_factor=num_total_pos) + return loss_cls, loss_bbox, loss_iou + + def get_dn_targets(self, batch_gt_instances: InstanceList, + batch_img_metas: dict, dn_meta: Dict[str, + int]) -> tuple: + """Get targets in denoising part for a batch of images. + + Args: + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + tuple: a tuple containing the following targets. + + - labels_list (list[Tensor]): Labels for all images. + - label_weights_list (list[Tensor]): Label weights for all images. + - bbox_targets_list (list[Tensor]): BBox targets for all images. + - bbox_weights_list (list[Tensor]): BBox weights for all images. + - num_total_pos (int): Number of positive samples in all images. + - num_total_neg (int): Number of negative samples in all images. + """ + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + pos_inds_list, neg_inds_list) = multi_apply( + self._get_dn_targets_single, + batch_gt_instances, + batch_img_metas, + dn_meta=dn_meta) + num_total_pos = sum((inds.numel() for inds in pos_inds_list)) + num_total_neg = sum((inds.numel() for inds in neg_inds_list)) + return (labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, num_total_pos, num_total_neg) + + def _get_dn_targets_single(self, gt_instances: InstanceData, + img_meta: dict, dn_meta: Dict[str, + int]) -> tuple: + """Get targets in denoising part for one image. + + Args: + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for one image. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + tuple[Tensor]: a tuple containing the following for one image. + + - labels (Tensor): Labels of each image. + - label_weights (Tensor]): Label weights of each image. + - bbox_targets (Tensor): BBox targets of each image. + - bbox_weights (Tensor): BBox weights of each image. + - pos_inds (Tensor): Sampled positive indices for each image. + - neg_inds (Tensor): Sampled negative indices for each image. + """ + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + num_groups = dn_meta['num_denoising_groups'] + num_denoising_queries = dn_meta['num_denoising_queries'] + num_queries_each_group = int(num_denoising_queries / num_groups) + device = gt_bboxes.device + + if len(gt_labels) > 0: + t = torch.arange(len(gt_labels), dtype=torch.long, device=device) + t = t.unsqueeze(0).repeat(num_groups, 1) + pos_assigned_gt_inds = t.flatten() + pos_inds = torch.arange( + num_groups, dtype=torch.long, device=device) + pos_inds = pos_inds.unsqueeze(1) * num_queries_each_group + t + pos_inds = pos_inds.flatten() + else: + pos_inds = pos_assigned_gt_inds = \ + gt_bboxes.new_tensor([], dtype=torch.long) + + neg_inds = pos_inds + num_queries_each_group // 2 + + # label targets + labels = gt_bboxes.new_full((num_denoising_queries, ), + self.num_classes, + dtype=torch.long) + labels[pos_inds] = gt_labels[pos_assigned_gt_inds] + label_weights = gt_bboxes.new_ones(num_denoising_queries) + + # bbox targets + bbox_targets = torch.zeros(num_denoising_queries, 4, device=device) + bbox_weights = torch.zeros(num_denoising_queries, 4, device=device) + bbox_weights[pos_inds] = 1.0 + img_h, img_w = img_meta['img_shape'] + + # DETR regress the relative position of boxes (cxcywh) in the image. + # Thus the learning target should be normalized by the image size, also + # the box format should be converted from defaultly x1y1x2y2 to cxcywh. + factor = gt_bboxes.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0) + gt_bboxes_normalized = gt_bboxes / factor + gt_bboxes_targets = bbox_xyxy_to_cxcywh(gt_bboxes_normalized) + bbox_targets[pos_inds] = gt_bboxes_targets.repeat([num_groups, 1]) + + return (labels, label_weights, bbox_targets, bbox_weights, pos_inds, + neg_inds) + + @staticmethod + def split_outputs(all_layers_cls_scores: Tensor, + all_layers_bbox_preds: Tensor, + dn_meta: Dict[str, int]) -> Tuple[Tensor]: + """Split outputs of the denoising part and the matching part. + + For the total outputs of `num_queries_total` length, the former + `num_denoising_queries` outputs are from denoising queries, and + the rest `num_matching_queries` ones are from matching queries, + where `num_queries_total` is the sum of `num_denoising_queries` and + `num_matching_queries`. + + Args: + all_layers_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, + num_queries_total, cls_out_channels). + all_layers_bbox_preds (Tensor): Regression outputs of all decoder + layers. Each is a 4D-tensor with normalized coordinate format + (cx, cy, w, h) and has shape (num_decoder_layers, bs, + num_queries_total, 4). + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. + + Returns: + Tuple[Tensor]: a tuple containing the following outputs. + + - all_layers_matching_cls_scores (Tensor): Classification scores + of all decoder layers in matching part, has shape + (num_decoder_layers, bs, num_matching_queries, cls_out_channels). + - all_layers_matching_bbox_preds (Tensor): Regression outputs of + all decoder layers in matching part. Each is a 4D-tensor with + normalized coordinate format (cx, cy, w, h) and has shape + (num_decoder_layers, bs, num_matching_queries, 4). + - all_layers_denoising_cls_scores (Tensor): Classification scores + of all decoder layers in denoising part, has shape + (num_decoder_layers, bs, num_denoising_queries, + cls_out_channels). + - all_layers_denoising_bbox_preds (Tensor): Regression outputs of + all decoder layers in denoising part. Each is a 4D-tensor with + normalized coordinate format (cx, cy, w, h) and has shape + (num_decoder_layers, bs, num_denoising_queries, 4). + """ + num_denoising_queries = dn_meta['num_denoising_queries'] + if dn_meta is not None: + all_layers_denoising_cls_scores = \ + all_layers_cls_scores[:, :, : num_denoising_queries, :] + all_layers_denoising_bbox_preds = \ + all_layers_bbox_preds[:, :, : num_denoising_queries, :] + all_layers_matching_cls_scores = \ + all_layers_cls_scores[:, :, num_denoising_queries:, :] + all_layers_matching_bbox_preds = \ + all_layers_bbox_preds[:, :, num_denoising_queries:, :] + else: + all_layers_denoising_cls_scores = None + all_layers_denoising_bbox_preds = None + all_layers_matching_cls_scores = all_layers_cls_scores + all_layers_matching_bbox_preds = all_layers_bbox_preds + return (all_layers_matching_cls_scores, all_layers_matching_bbox_preds, + all_layers_denoising_cls_scores, + all_layers_denoising_bbox_preds) diff --git a/mmdetection/mmdet/models/dense_heads/embedding_rpn_head.py b/mmdetection/mmdet/models/dense_heads/embedding_rpn_head.py new file mode 100644 index 00000000..97e84fa8 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/embedding_rpn_head.py @@ -0,0 +1,132 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn as nn +from mmengine.model import BaseModule +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures.bbox import bbox_cxcywh_to_xyxy +from mmdet.structures.det_data_sample import SampleList +from mmdet.utils import InstanceList, OptConfigType + + +@MODELS.register_module() +class EmbeddingRPNHead(BaseModule): + """RPNHead in the `Sparse R-CNN `_ . + + Unlike traditional RPNHead, this module does not need FPN input, but just + decode `init_proposal_bboxes` and expand the first dimension of + `init_proposal_bboxes` and `init_proposal_features` to the batch_size. + + Args: + num_proposals (int): Number of init_proposals. Defaults to 100. + proposal_feature_channel (int): Channel number of + init_proposal_feature. Defaults to 256. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. Defaults to None. + """ + + def __init__(self, + num_proposals: int = 100, + proposal_feature_channel: int = 256, + init_cfg: OptConfigType = None, + **kwargs) -> None: + # `**kwargs` is necessary to avoid some potential error. + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + super().__init__(init_cfg=init_cfg) + self.num_proposals = num_proposals + self.proposal_feature_channel = proposal_feature_channel + self._init_layers() + + def _init_layers(self) -> None: + """Initialize a sparse set of proposal boxes and proposal features.""" + self.init_proposal_bboxes = nn.Embedding(self.num_proposals, 4) + self.init_proposal_features = nn.Embedding( + self.num_proposals, self.proposal_feature_channel) + + def init_weights(self) -> None: + """Initialize the init_proposal_bboxes as normalized. + + [c_x, c_y, w, h], and we initialize it to the size of the entire + image. + """ + super().init_weights() + nn.init.constant_(self.init_proposal_bboxes.weight[:, :2], 0.5) + nn.init.constant_(self.init_proposal_bboxes.weight[:, 2:], 1) + + def _decode_init_proposals(self, x: List[Tensor], + batch_data_samples: SampleList) -> InstanceList: + """Decode init_proposal_bboxes according to the size of images and + expand dimension of init_proposal_features to batch_size. + + Args: + x (list[Tensor]): List of FPN features. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + List[:obj:`InstanceData`:] Detection results of each image. + Each item usually contains following keys. + + - proposals: Decoded proposal bboxes, + has shape (num_proposals, 4). + - features: init_proposal_features, expanded proposal + features, has shape + (num_proposals, proposal_feature_channel). + - imgs_whwh: Tensor with shape + (num_proposals, 4), the dimension means + [img_width, img_height, img_width, img_height]. + """ + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + + proposals = self.init_proposal_bboxes.weight.clone() + proposals = bbox_cxcywh_to_xyxy(proposals) + imgs_whwh = [] + for meta in batch_img_metas: + h, w = meta['img_shape'][:2] + imgs_whwh.append(x[0].new_tensor([[w, h, w, h]])) + imgs_whwh = torch.cat(imgs_whwh, dim=0) + imgs_whwh = imgs_whwh[:, None, :] + proposals = proposals * imgs_whwh + + rpn_results_list = [] + for idx in range(len(batch_img_metas)): + rpn_results = InstanceData() + rpn_results.bboxes = proposals[idx] + rpn_results.imgs_whwh = imgs_whwh[idx].repeat( + self.num_proposals, 1) + rpn_results.features = self.init_proposal_features.weight.clone() + rpn_results_list.append(rpn_results) + return rpn_results_list + + def loss(self, *args, **kwargs): + """Perform forward propagation and loss calculation of the detection + head on the features of the upstream network.""" + raise NotImplementedError( + 'EmbeddingRPNHead does not have `loss`, please use ' + '`predict` or `loss_and_predict` instead.') + + def predict(self, x: List[Tensor], batch_data_samples: SampleList, + **kwargs) -> InstanceList: + """Perform forward propagation of the detection head and predict + detection results on the features of the upstream network.""" + # `**kwargs` is necessary to avoid some potential error. + return self._decode_init_proposals( + x=x, batch_data_samples=batch_data_samples) + + def loss_and_predict(self, x: List[Tensor], batch_data_samples: SampleList, + **kwargs) -> tuple: + """Perform forward propagation of the head, then calculate loss and + predictions from the features and data samples.""" + # `**kwargs` is necessary to avoid some potential error. + predictions = self._decode_init_proposals( + x=x, batch_data_samples=batch_data_samples) + + return dict(), predictions diff --git a/mmdetection/mmdet/models/dense_heads/fcos_head.py b/mmdetection/mmdet/models/dense_heads/fcos_head.py new file mode 100644 index 00000000..ba4d4640 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/fcos_head.py @@ -0,0 +1,476 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import Scale +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.layers import NormedConv2d +from mmdet.registry import MODELS +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, + OptInstanceList, RangeType, reduce_mean) +from ..utils import multi_apply +from .anchor_free_head import AnchorFreeHead + +INF = 1e8 + + +@MODELS.register_module() +class FCOSHead(AnchorFreeHead): + """Anchor-free head used in `FCOS `_. + + The FCOS head does not use anchor boxes. Instead bounding boxes are + predicted at each pixel and a centerness measure is used to suppress + low-quality predictions. + Here norm_on_bbox, centerness_on_reg, dcn_on_last_conv are training + tricks used in official repo, which will bring remarkable mAP gains + of up to 4.9. Please see https://github.com/tianzhi0549/FCOS for + more detail. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + strides (Sequence[int] or Sequence[Tuple[int, int]]): Strides of points + in multiple feature levels. Defaults to (4, 8, 16, 32, 64). + regress_ranges (Sequence[Tuple[int, int]]): Regress range of multiple + level points. + center_sampling (bool): If true, use center sampling. + Defaults to False. + center_sample_radius (float): Radius of center sampling. + Defaults to 1.5. + norm_on_bbox (bool): If true, normalize the regression targets with + FPN strides. Defaults to False. + centerness_on_reg (bool): If true, position centerness on the + regress branch. Please refer to https://github.com/tianzhi0549/FCOS/issues/89#issuecomment-516877042. + Defaults to False. + conv_bias (bool or str): If specified as `auto`, it will be decided by + the norm_cfg. Bias of conv will be set as True if `norm_cfg` is + None, otherwise False. Defaults to "auto". + loss_cls (:obj:`ConfigDict` or dict): Config of classification loss. + loss_bbox (:obj:`ConfigDict` or dict): Config of localization loss. + loss_centerness (:obj:`ConfigDict`, or dict): Config of centerness + loss. + norm_cfg (:obj:`ConfigDict` or dict): dictionary to construct and + config norm layer. Defaults to + ``norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)``. + cls_predictor_cfg (:obj:`ConfigDict` or dict): dictionary to construct and + config conv_cls. Defaults to None. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. + + Example: + >>> self = FCOSHead(11, 7) + >>> feats = [torch.rand(1, 7, s, s) for s in [4, 8, 16, 32, 64]] + >>> cls_score, bbox_pred, centerness = self.forward(feats) + >>> assert len(cls_score) == len(self.scales) + """ # noqa: E501 + + def __init__(self, + num_classes: int, + in_channels: int, + regress_ranges: RangeType = ((-1, 64), (64, 128), (128, 256), + (256, 512), (512, INF)), + center_sampling: bool = False, + center_sample_radius: float = 1.5, + norm_on_bbox: bool = False, + centerness_on_reg: bool = False, + loss_cls: ConfigType = dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox: ConfigType = dict(type='IoULoss', loss_weight=1.0), + loss_centerness: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0), + norm_cfg: ConfigType = dict( + type='GN', num_groups=32, requires_grad=True), + cls_predictor_cfg=None, + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', + name='conv_cls', + std=0.01, + bias_prob=0.01)), + **kwargs) -> None: + self.regress_ranges = regress_ranges + self.center_sampling = center_sampling + self.center_sample_radius = center_sample_radius + self.norm_on_bbox = norm_on_bbox + self.centerness_on_reg = centerness_on_reg + self.cls_predictor_cfg = cls_predictor_cfg + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + loss_cls=loss_cls, + loss_bbox=loss_bbox, + norm_cfg=norm_cfg, + init_cfg=init_cfg, + **kwargs) + self.loss_centerness = MODELS.build(loss_centerness) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + super()._init_layers() + self.conv_centerness = nn.Conv2d(self.feat_channels, 1, 3, padding=1) + self.scales = nn.ModuleList([Scale(1.0) for _ in self.strides]) + if self.cls_predictor_cfg is not None: + self.cls_predictor_cfg.pop('type') + self.conv_cls = NormedConv2d( + self.feat_channels, + self.cls_out_channels, + 1, + padding=0, + **self.cls_predictor_cfg) + + def forward( + self, x: Tuple[Tensor] + ) -> Tuple[List[Tensor], List[Tensor], List[Tensor]]: + """Forward features from the upstream network. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of each level outputs. + + - cls_scores (list[Tensor]): Box scores for each scale level, \ + each is a 4D-tensor, the channel number is \ + num_points * num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for each \ + scale level, each is a 4D-tensor, the channel number is \ + num_points * 4. + - centernesses (list[Tensor]): centerness for each scale level, \ + each is a 4D-tensor, the channel number is num_points * 1. + """ + return multi_apply(self.forward_single, x, self.scales, self.strides) + + def forward_single(self, x: Tensor, scale: Scale, + stride: int) -> Tuple[Tensor, Tensor, Tensor]: + """Forward features of a single scale level. + + Args: + x (Tensor): FPN feature maps of the specified stride. + scale (:obj:`mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + stride (int): The corresponding stride for feature maps, only + used to normalize the bbox prediction when self.norm_on_bbox + is True. + + Returns: + tuple: scores for each class, bbox predictions and centerness + predictions of input feature maps. + """ + cls_score, bbox_pred, cls_feat, reg_feat = super().forward_single(x) + if self.centerness_on_reg: + centerness = self.conv_centerness(reg_feat) + else: + centerness = self.conv_centerness(cls_feat) + # scale the bbox_pred of different level + # float to avoid overflow when enabling FP16 + bbox_pred = scale(bbox_pred).float() + if self.norm_on_bbox: + # bbox_pred needed for gradient computation has been modified + # by F.relu(bbox_pred) when run with PyTorch 1.10. So replace + # F.relu(bbox_pred) with bbox_pred.clamp(min=0) + bbox_pred = bbox_pred.clamp(min=0) + if not self.training: + bbox_pred *= stride + else: + bbox_pred = bbox_pred.exp() + return cls_score, bbox_pred, centerness + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + centernesses: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level, + each is a 4D-tensor, the channel number is + num_points * num_classes. + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level, each is a 4D-tensor, the channel number is + num_points * 4. + centernesses (list[Tensor]): centerness for each scale level, each + is a 4D-tensor, the channel number is num_points * 1. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert len(cls_scores) == len(bbox_preds) == len(centernesses) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + all_level_points = self.prior_generator.grid_priors( + featmap_sizes, + dtype=bbox_preds[0].dtype, + device=bbox_preds[0].device) + labels, bbox_targets = self.get_targets(all_level_points, + batch_gt_instances) + + num_imgs = cls_scores[0].size(0) + # flatten cls_scores, bbox_preds and centerness + flatten_cls_scores = [ + cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels) + for cls_score in cls_scores + ] + flatten_bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4) + for bbox_pred in bbox_preds + ] + flatten_centerness = [ + centerness.permute(0, 2, 3, 1).reshape(-1) + for centerness in centernesses + ] + flatten_cls_scores = torch.cat(flatten_cls_scores) + flatten_bbox_preds = torch.cat(flatten_bbox_preds) + flatten_centerness = torch.cat(flatten_centerness) + flatten_labels = torch.cat(labels) + flatten_bbox_targets = torch.cat(bbox_targets) + # repeat points to align with bbox_preds + flatten_points = torch.cat( + [points.repeat(num_imgs, 1) for points in all_level_points]) + + losses = dict() + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = self.num_classes + pos_inds = ((flatten_labels >= 0) + & (flatten_labels < bg_class_ind)).nonzero().reshape(-1) + num_pos = torch.tensor( + len(pos_inds), dtype=torch.float, device=bbox_preds[0].device) + num_pos = max(reduce_mean(num_pos), 1.0) + loss_cls = self.loss_cls( + flatten_cls_scores, flatten_labels, avg_factor=num_pos) + + if getattr(self.loss_cls, 'custom_accuracy', False): + acc = self.loss_cls.get_accuracy(flatten_cls_scores, + flatten_labels) + losses.update(acc) + + pos_bbox_preds = flatten_bbox_preds[pos_inds] + pos_centerness = flatten_centerness[pos_inds] + pos_bbox_targets = flatten_bbox_targets[pos_inds] + pos_centerness_targets = self.centerness_target(pos_bbox_targets) + # centerness weighted iou loss + centerness_denorm = max( + reduce_mean(pos_centerness_targets.sum().detach()), 1e-6) + + if len(pos_inds) > 0: + pos_points = flatten_points[pos_inds] + pos_decoded_bbox_preds = self.bbox_coder.decode( + pos_points, pos_bbox_preds) + pos_decoded_target_preds = self.bbox_coder.decode( + pos_points, pos_bbox_targets) + loss_bbox = self.loss_bbox( + pos_decoded_bbox_preds, + pos_decoded_target_preds, + weight=pos_centerness_targets, + avg_factor=centerness_denorm) + loss_centerness = self.loss_centerness( + pos_centerness, pos_centerness_targets, avg_factor=num_pos) + else: + loss_bbox = pos_bbox_preds.sum() + loss_centerness = pos_centerness.sum() + + losses['loss_cls'] = loss_cls + losses['loss_bbox'] = loss_bbox + losses['loss_centerness'] = loss_centerness + + return losses + + def get_targets( + self, points: List[Tensor], batch_gt_instances: InstanceList + ) -> Tuple[List[Tensor], List[Tensor]]: + """Compute regression, classification and centerness targets for points + in multiple images. + + Args: + points (list[Tensor]): Points of each fpn level, each has shape + (num_points, 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + + Returns: + tuple: Targets of each level. + + - concat_lvl_labels (list[Tensor]): Labels of each level. + - concat_lvl_bbox_targets (list[Tensor]): BBox targets of each \ + level. + """ + assert len(points) == len(self.regress_ranges) + num_levels = len(points) + # expand regress ranges to align with points + expanded_regress_ranges = [ + points[i].new_tensor(self.regress_ranges[i])[None].expand_as( + points[i]) for i in range(num_levels) + ] + # concat all levels points and regress ranges + concat_regress_ranges = torch.cat(expanded_regress_ranges, dim=0) + concat_points = torch.cat(points, dim=0) + + # the number of points per img, per lvl + num_points = [center.size(0) for center in points] + + # get labels and bbox_targets of each image + labels_list, bbox_targets_list = multi_apply( + self._get_targets_single, + batch_gt_instances, + points=concat_points, + regress_ranges=concat_regress_ranges, + num_points_per_lvl=num_points) + + # split to per img, per level + labels_list = [labels.split(num_points, 0) for labels in labels_list] + bbox_targets_list = [ + bbox_targets.split(num_points, 0) + for bbox_targets in bbox_targets_list + ] + + # concat per level image + concat_lvl_labels = [] + concat_lvl_bbox_targets = [] + for i in range(num_levels): + concat_lvl_labels.append( + torch.cat([labels[i] for labels in labels_list])) + bbox_targets = torch.cat( + [bbox_targets[i] for bbox_targets in bbox_targets_list]) + if self.norm_on_bbox: + bbox_targets = bbox_targets / self.strides[i] + concat_lvl_bbox_targets.append(bbox_targets) + return concat_lvl_labels, concat_lvl_bbox_targets + + def _get_targets_single( + self, gt_instances: InstanceData, points: Tensor, + regress_ranges: Tensor, + num_points_per_lvl: List[int]) -> Tuple[Tensor, Tensor]: + """Compute regression and classification targets for a single image.""" + num_points = points.size(0) + num_gts = len(gt_instances) + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + + if num_gts == 0: + return gt_labels.new_full((num_points,), self.num_classes), \ + gt_bboxes.new_zeros((num_points, 4)) + + areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0]) * ( + gt_bboxes[:, 3] - gt_bboxes[:, 1]) + # TODO: figure out why these two are different + # areas = areas[None].expand(num_points, num_gts) + areas = areas[None].repeat(num_points, 1) + regress_ranges = regress_ranges[:, None, :].expand( + num_points, num_gts, 2) + gt_bboxes = gt_bboxes[None].expand(num_points, num_gts, 4) + xs, ys = points[:, 0], points[:, 1] + xs = xs[:, None].expand(num_points, num_gts) + ys = ys[:, None].expand(num_points, num_gts) + + left = xs - gt_bboxes[..., 0] + right = gt_bboxes[..., 2] - xs + top = ys - gt_bboxes[..., 1] + bottom = gt_bboxes[..., 3] - ys + bbox_targets = torch.stack((left, top, right, bottom), -1) + + if self.center_sampling: + # condition1: inside a `center bbox` + radius = self.center_sample_radius + center_xs = (gt_bboxes[..., 0] + gt_bboxes[..., 2]) / 2 + center_ys = (gt_bboxes[..., 1] + gt_bboxes[..., 3]) / 2 + center_gts = torch.zeros_like(gt_bboxes) + stride = center_xs.new_zeros(center_xs.shape) + + # project the points on current lvl back to the `original` sizes + lvl_begin = 0 + for lvl_idx, num_points_lvl in enumerate(num_points_per_lvl): + lvl_end = lvl_begin + num_points_lvl + stride[lvl_begin:lvl_end] = self.strides[lvl_idx] * radius + lvl_begin = lvl_end + + x_mins = center_xs - stride + y_mins = center_ys - stride + x_maxs = center_xs + stride + y_maxs = center_ys + stride + center_gts[..., 0] = torch.where(x_mins > gt_bboxes[..., 0], + x_mins, gt_bboxes[..., 0]) + center_gts[..., 1] = torch.where(y_mins > gt_bboxes[..., 1], + y_mins, gt_bboxes[..., 1]) + center_gts[..., 2] = torch.where(x_maxs > gt_bboxes[..., 2], + gt_bboxes[..., 2], x_maxs) + center_gts[..., 3] = torch.where(y_maxs > gt_bboxes[..., 3], + gt_bboxes[..., 3], y_maxs) + + cb_dist_left = xs - center_gts[..., 0] + cb_dist_right = center_gts[..., 2] - xs + cb_dist_top = ys - center_gts[..., 1] + cb_dist_bottom = center_gts[..., 3] - ys + center_bbox = torch.stack( + (cb_dist_left, cb_dist_top, cb_dist_right, cb_dist_bottom), -1) + inside_gt_bbox_mask = center_bbox.min(-1)[0] > 0 + else: + # condition1: inside a gt bbox + inside_gt_bbox_mask = bbox_targets.min(-1)[0] > 0 + + # condition2: limit the regression range for each location + max_regress_distance = bbox_targets.max(-1)[0] + inside_regress_range = ( + (max_regress_distance >= regress_ranges[..., 0]) + & (max_regress_distance <= regress_ranges[..., 1])) + + # if there are still more than one objects for a location, + # we choose the one with minimal area + areas[inside_gt_bbox_mask == 0] = INF + areas[inside_regress_range == 0] = INF + min_area, min_area_inds = areas.min(dim=1) + + labels = gt_labels[min_area_inds] + labels[min_area == INF] = self.num_classes # set as BG + bbox_targets = bbox_targets[range(num_points), min_area_inds] + + return labels, bbox_targets + + def centerness_target(self, pos_bbox_targets: Tensor) -> Tensor: + """Compute centerness targets. + + Args: + pos_bbox_targets (Tensor): BBox targets of positive bboxes in shape + (num_pos, 4) + + Returns: + Tensor: Centerness target. + """ + # only calculate pos centerness targets, otherwise there may be nan + left_right = pos_bbox_targets[:, [0, 2]] + top_bottom = pos_bbox_targets[:, [1, 3]] + if len(left_right) == 0: + centerness_targets = left_right[..., 0] + else: + centerness_targets = ( + left_right.min(dim=-1)[0] / left_right.max(dim=-1)[0]) * ( + top_bottom.min(dim=-1)[0] / top_bottom.max(dim=-1)[0]) + return torch.sqrt(centerness_targets) diff --git a/mmdetection/mmdet/models/dense_heads/fovea_head.py b/mmdetection/mmdet/models/dense_heads/fovea_head.py new file mode 100644 index 00000000..89353dea --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/fovea_head.py @@ -0,0 +1,509 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmcv.ops import DeformConv2d +from mmengine.config import ConfigDict +from mmengine.model import BaseModule +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import InstanceList, OptInstanceList, OptMultiConfig +from ..utils import filter_scores_and_topk, multi_apply +from .anchor_free_head import AnchorFreeHead + +INF = 1e8 + + +class FeatureAlign(BaseModule): + """Feature Align Module. + + Feature Align Module is implemented based on DCN v1. + It uses anchor shape prediction rather than feature map to + predict offsets of deform conv layer. + + Args: + in_channels (int): Number of channels in the input feature map. + out_channels (int): Number of channels in the output feature map. + kernel_size (int): Size of the convolution kernel. + ``norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)``. + deform_groups: (int): Group number of DCN in + FeatureAdaption module. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. + """ + + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: int = 3, + deform_groups: int = 4, + init_cfg: OptMultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.1, + override=dict(type='Normal', name='conv_adaption', std=0.01)) + ) -> None: + super().__init__(init_cfg=init_cfg) + offset_channels = kernel_size * kernel_size * 2 + self.conv_offset = nn.Conv2d( + 4, deform_groups * offset_channels, 1, bias=False) + self.conv_adaption = DeformConv2d( + in_channels, + out_channels, + kernel_size=kernel_size, + padding=(kernel_size - 1) // 2, + deform_groups=deform_groups) + self.relu = nn.ReLU(inplace=True) + + def forward(self, x: Tensor, shape: Tensor) -> Tensor: + """Forward function of feature align module. + + Args: + x (Tensor): Features from the upstream network. + shape (Tensor): Exponential of bbox predictions. + + Returns: + x (Tensor): The aligned features. + """ + offset = self.conv_offset(shape) + x = self.relu(self.conv_adaption(x, offset)) + return x + + +@MODELS.register_module() +class FoveaHead(AnchorFreeHead): + """Detection Head of `FoveaBox: Beyond Anchor-based Object Detector. + + `_. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + base_edge_list (list[int]): List of edges. + scale_ranges (list[tuple]): Range of scales. + sigma (float): Super parameter of ``FoveaHead``. + with_deform (bool): Whether use deform conv. + deform_groups (int): Deformable conv group size. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + base_edge_list: List[int] = (16, 32, 64, 128, 256), + scale_ranges: List[tuple] = ((8, 32), (16, 64), (32, 128), + (64, 256), (128, 512)), + sigma: float = 0.4, + with_deform: bool = False, + deform_groups: int = 4, + init_cfg: OptMultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', + name='conv_cls', + std=0.01, + bias_prob=0.01)), + **kwargs) -> None: + self.base_edge_list = base_edge_list + self.scale_ranges = scale_ranges + self.sigma = sigma + self.with_deform = with_deform + self.deform_groups = deform_groups + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + init_cfg=init_cfg, + **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + # box branch + super()._init_reg_convs() + self.conv_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1) + + # cls branch + if not self.with_deform: + super()._init_cls_convs() + self.conv_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + else: + self.cls_convs = nn.ModuleList() + self.cls_convs.append( + ConvModule( + self.feat_channels, (self.feat_channels * 4), + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + bias=self.norm_cfg is None)) + self.cls_convs.append( + ConvModule((self.feat_channels * 4), (self.feat_channels * 4), + 1, + stride=1, + padding=0, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + bias=self.norm_cfg is None)) + self.feature_adaption = FeatureAlign( + self.feat_channels, + self.feat_channels, + kernel_size=3, + deform_groups=self.deform_groups) + self.conv_cls = nn.Conv2d( + int(self.feat_channels * 4), + self.cls_out_channels, + 3, + padding=1) + + def forward_single(self, x: Tensor) -> Tuple[Tensor, Tensor]: + """Forward features of a single scale level. + + Args: + x (Tensor): FPN feature maps of the specified stride. + + Returns: + tuple: scores for each class and bbox predictions of input + feature maps. + """ + cls_feat = x + reg_feat = x + for reg_layer in self.reg_convs: + reg_feat = reg_layer(reg_feat) + bbox_pred = self.conv_reg(reg_feat) + if self.with_deform: + cls_feat = self.feature_adaption(cls_feat, bbox_pred.exp()) + for cls_layer in self.cls_convs: + cls_feat = cls_layer(cls_feat) + cls_score = self.conv_cls(cls_feat) + return cls_score, bbox_pred + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level, + each is a 4D-tensor, the channel number is + num_priors * num_classes. + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level, each is a 4D-tensor, the channel number is + num_priors * 4. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert len(cls_scores) == len(bbox_preds) + + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + priors = self.prior_generator.grid_priors( + featmap_sizes, + dtype=bbox_preds[0].dtype, + device=bbox_preds[0].device) + num_imgs = cls_scores[0].size(0) + flatten_cls_scores = [ + cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels) + for cls_score in cls_scores + ] + flatten_bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4) + for bbox_pred in bbox_preds + ] + flatten_cls_scores = torch.cat(flatten_cls_scores) + flatten_bbox_preds = torch.cat(flatten_bbox_preds) + flatten_labels, flatten_bbox_targets = self.get_targets( + batch_gt_instances, featmap_sizes, priors) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + pos_inds = ((flatten_labels >= 0) + & (flatten_labels < self.num_classes)).nonzero().view(-1) + num_pos = len(pos_inds) + + loss_cls = self.loss_cls( + flatten_cls_scores, flatten_labels, avg_factor=num_pos + num_imgs) + if num_pos > 0: + pos_bbox_preds = flatten_bbox_preds[pos_inds] + pos_bbox_targets = flatten_bbox_targets[pos_inds] + pos_weights = pos_bbox_targets.new_ones(pos_bbox_targets.size()) + loss_bbox = self.loss_bbox( + pos_bbox_preds, + pos_bbox_targets, + pos_weights, + avg_factor=num_pos) + else: + loss_bbox = torch.tensor( + 0, + dtype=flatten_bbox_preds.dtype, + device=flatten_bbox_preds.device) + return dict(loss_cls=loss_cls, loss_bbox=loss_bbox) + + def get_targets( + self, batch_gt_instances: InstanceList, featmap_sizes: List[tuple], + priors_list: List[Tensor]) -> Tuple[List[Tensor], List[Tensor]]: + """Compute regression and classification for priors in multiple images. + + Args: + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + featmap_sizes (list[tuple]): Size tuple of feature maps. + priors_list (list[Tensor]): Priors list of each fpn level, each has + shape (num_priors, 2). + + Returns: + tuple: Targets of each level. + + - flatten_labels (list[Tensor]): Labels of each level. + - flatten_bbox_targets (list[Tensor]): BBox targets of each + level. + """ + label_list, bbox_target_list = multi_apply( + self._get_targets_single, + batch_gt_instances, + featmap_size_list=featmap_sizes, + priors_list=priors_list) + flatten_labels = [ + torch.cat([ + labels_level_img.flatten() for labels_level_img in labels_level + ]) for labels_level in zip(*label_list) + ] + flatten_bbox_targets = [ + torch.cat([ + bbox_targets_level_img.reshape(-1, 4) + for bbox_targets_level_img in bbox_targets_level + ]) for bbox_targets_level in zip(*bbox_target_list) + ] + flatten_labels = torch.cat(flatten_labels) + flatten_bbox_targets = torch.cat(flatten_bbox_targets) + return flatten_labels, flatten_bbox_targets + + def _get_targets_single(self, + gt_instances: InstanceData, + featmap_size_list: List[tuple] = None, + priors_list: List[Tensor] = None) -> tuple: + """Compute regression and classification targets for a single image. + + Args: + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + featmap_size_list (list[tuple]): Size tuple of feature maps. + priors_list (list[Tensor]): Priors of each fpn level, each has + shape (num_priors, 2). + + Returns: + tuple: + + - label_list (list[Tensor]): Labels of all anchors in the image. + - box_target_list (list[Tensor]): BBox targets of all anchors in + the image. + """ + gt_bboxes_raw = gt_instances.bboxes + gt_labels_raw = gt_instances.labels + gt_areas = torch.sqrt((gt_bboxes_raw[:, 2] - gt_bboxes_raw[:, 0]) * + (gt_bboxes_raw[:, 3] - gt_bboxes_raw[:, 1])) + label_list = [] + bbox_target_list = [] + # for each pyramid, find the cls and box target + for base_len, (lower_bound, upper_bound), stride, featmap_size, \ + priors in zip(self.base_edge_list, self.scale_ranges, + self.strides, featmap_size_list, priors_list): + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + priors = priors.view(*featmap_size, 2) + x, y = priors[..., 0], priors[..., 1] + labels = gt_labels_raw.new_full(featmap_size, self.num_classes) + bbox_targets = gt_bboxes_raw.new_ones(featmap_size[0], + featmap_size[1], 4) + # scale assignment + hit_indices = ((gt_areas >= lower_bound) & + (gt_areas <= upper_bound)).nonzero().flatten() + if len(hit_indices) == 0: + label_list.append(labels) + bbox_target_list.append(torch.log(bbox_targets)) + continue + _, hit_index_order = torch.sort(-gt_areas[hit_indices]) + hit_indices = hit_indices[hit_index_order] + gt_bboxes = gt_bboxes_raw[hit_indices, :] / stride + gt_labels = gt_labels_raw[hit_indices] + half_w = 0.5 * (gt_bboxes[:, 2] - gt_bboxes[:, 0]) + half_h = 0.5 * (gt_bboxes[:, 3] - gt_bboxes[:, 1]) + # valid fovea area: left, right, top, down + pos_left = torch.ceil( + gt_bboxes[:, 0] + (1 - self.sigma) * half_w - 0.5).long(). \ + clamp(0, featmap_size[1] - 1) + pos_right = torch.floor( + gt_bboxes[:, 0] + (1 + self.sigma) * half_w - 0.5).long(). \ + clamp(0, featmap_size[1] - 1) + pos_top = torch.ceil( + gt_bboxes[:, 1] + (1 - self.sigma) * half_h - 0.5).long(). \ + clamp(0, featmap_size[0] - 1) + pos_down = torch.floor( + gt_bboxes[:, 1] + (1 + self.sigma) * half_h - 0.5).long(). \ + clamp(0, featmap_size[0] - 1) + for px1, py1, px2, py2, label, (gt_x1, gt_y1, gt_x2, gt_y2) in \ + zip(pos_left, pos_top, pos_right, pos_down, gt_labels, + gt_bboxes_raw[hit_indices, :]): + labels[py1:py2 + 1, px1:px2 + 1] = label + bbox_targets[py1:py2 + 1, px1:px2 + 1, 0] = \ + (x[py1:py2 + 1, px1:px2 + 1] - gt_x1) / base_len + bbox_targets[py1:py2 + 1, px1:px2 + 1, 1] = \ + (y[py1:py2 + 1, px1:px2 + 1] - gt_y1) / base_len + bbox_targets[py1:py2 + 1, px1:px2 + 1, 2] = \ + (gt_x2 - x[py1:py2 + 1, px1:px2 + 1]) / base_len + bbox_targets[py1:py2 + 1, px1:px2 + 1, 3] = \ + (gt_y2 - y[py1:py2 + 1, px1:px2 + 1]) / base_len + bbox_targets = bbox_targets.clamp(min=1. / 16, max=16.) + label_list.append(labels) + bbox_target_list.append(torch.log(bbox_targets)) + return label_list, bbox_target_list + + # Same as base_dense_head/_predict_by_feat_single except self._bbox_decode + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: Optional[ConfigDict] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image, each item has shape + (num_priors * 1, H, W). + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid, has shape + (num_priors, 2). + img_meta (dict): Image meta info. + cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + cfg = self.test_cfg if cfg is None else cfg + assert len(cls_score_list) == len(bbox_pred_list) + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bboxes = [] + mlvl_scores = [] + mlvl_labels = [] + for level_idx, (cls_score, bbox_pred, stride, base_len, priors) in \ + enumerate(zip(cls_score_list, bbox_pred_list, self.strides, + self.base_edge_list, mlvl_priors)): + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4) + + scores = cls_score.permute(1, 2, 0).reshape( + -1, self.cls_out_channels).sigmoid() + + # After https://github.com/open-mmlab/mmdetection/pull/6268/, + # this operation keeps fewer bboxes under the same `nms_pre`. + # There is no difference in performance for most models. If you + # find a slight drop in performance, you can set a larger + # `nms_pre` than before. + results = filter_scores_and_topk( + scores, cfg.score_thr, nms_pre, + dict(bbox_pred=bbox_pred, priors=priors)) + scores, labels, _, filtered_results = results + + bbox_pred = filtered_results['bbox_pred'] + priors = filtered_results['priors'] + + bboxes = self._bbox_decode(priors, bbox_pred, base_len, img_shape) + + mlvl_bboxes.append(bboxes) + mlvl_scores.append(scores) + mlvl_labels.append(labels) + + results = InstanceData() + results.bboxes = torch.cat(mlvl_bboxes) + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + + return self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + + def _bbox_decode(self, priors: Tensor, bbox_pred: Tensor, base_len: int, + max_shape: int) -> Tensor: + """Function to decode bbox. + + Args: + priors (Tensor): Center proiors of an image, has shape + (num_instances, 2). + bbox_preds (Tensor): Box energies / deltas for all instances, + has shape (batch_size, num_instances, 4). + base_len (int): The base length. + max_shape (int): The max shape of bbox. + + Returns: + Tensor: Decoded bboxes in (tl_x, tl_y, br_x, br_y) format. Has + shape (batch_size, num_instances, 4). + """ + bbox_pred = bbox_pred.exp() + + y = priors[:, 1] + x = priors[:, 0] + x1 = (x - base_len * bbox_pred[:, 0]). \ + clamp(min=0, max=max_shape[1] - 1) + y1 = (y - base_len * bbox_pred[:, 1]). \ + clamp(min=0, max=max_shape[0] - 1) + x2 = (x + base_len * bbox_pred[:, 2]). \ + clamp(min=0, max=max_shape[1] - 1) + y2 = (y + base_len * bbox_pred[:, 3]). \ + clamp(min=0, max=max_shape[0] - 1) + decoded_bboxes = torch.stack([x1, y1, x2, y2], -1) + return decoded_bboxes diff --git a/mmdetection/mmdet/models/dense_heads/free_anchor_retina_head.py b/mmdetection/mmdet/models/dense_heads/free_anchor_retina_head.py new file mode 100644 index 00000000..df6fb920 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/free_anchor_retina_head.py @@ -0,0 +1,312 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn.functional as F +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures.bbox import bbox_overlaps +from mmdet.utils import InstanceList, OptConfigType, OptInstanceList +from ..utils import multi_apply +from .retina_head import RetinaHead + +EPS = 1e-12 + + +@MODELS.register_module() +class FreeAnchorRetinaHead(RetinaHead): + """FreeAnchor RetinaHead used in https://arxiv.org/abs/1909.02466. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + stacked_convs (int): Number of conv layers in cls and reg tower. + Defaults to 4. + conv_cfg (:obj:`ConfigDict` or dict, optional): dictionary to + construct and config conv layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict, optional): dictionary to + construct and config norm layer. Defaults to + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True). + pre_anchor_topk (int): Number of boxes that be token in each bag. + Defaults to 50 + bbox_thr (float): The threshold of the saturated linear function. + It is usually the same with the IoU threshold used in NMS. + Defaults to 0.6. + gamma (float): Gamma parameter in focal loss. Defaults to 2.0. + alpha (float): Alpha parameter in focal loss. Defaults to 0.5. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + stacked_convs: int = 4, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + pre_anchor_topk: int = 50, + bbox_thr: float = 0.6, + gamma: float = 2.0, + alpha: float = 0.5, + **kwargs) -> None: + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + stacked_convs=stacked_convs, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + **kwargs) + + self.pre_anchor_topk = pre_anchor_topk + self.bbox_thr = bbox_thr + self.gamma = gamma + self.alpha = alpha + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + has shape (N, num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + + anchor_list, _ = self.get_anchors( + featmap_sizes=featmap_sizes, + batch_img_metas=batch_img_metas, + device=device) + concat_anchor_list = [torch.cat(anchor) for anchor in anchor_list] + + # concatenate each level + cls_scores = [ + cls.permute(0, 2, 3, + 1).reshape(cls.size(0), -1, self.cls_out_channels) + for cls in cls_scores + ] + bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(bbox_pred.size(0), -1, 4) + for bbox_pred in bbox_preds + ] + cls_scores = torch.cat(cls_scores, dim=1) + cls_probs = torch.sigmoid(cls_scores) + bbox_preds = torch.cat(bbox_preds, dim=1) + + box_probs, positive_losses, num_pos_list = multi_apply( + self.positive_loss_single, cls_probs, bbox_preds, + concat_anchor_list, batch_gt_instances) + + num_pos = sum(num_pos_list) + positive_loss = torch.cat(positive_losses).sum() / max(1, num_pos) + + # box_prob: P{a_{j} \in A_{+}} + box_probs = torch.stack(box_probs, dim=0) + + # negative_loss: + # \sum_{j}{ FL((1 - P{a_{j} \in A_{+}}) * (1 - P_{j}^{bg})) } / n||B|| + negative_loss = self.negative_bag_loss(cls_probs, box_probs).sum() / \ + max(1, num_pos * self.pre_anchor_topk) + + # avoid the absence of gradients in regression subnet + # when no ground-truth in a batch + if num_pos == 0: + positive_loss = bbox_preds.sum() * 0 + + losses = { + 'positive_bag_loss': positive_loss, + 'negative_bag_loss': negative_loss + } + return losses + + def positive_loss_single(self, cls_prob: Tensor, bbox_pred: Tensor, + flat_anchors: Tensor, + gt_instances: InstanceData) -> tuple: + """Compute positive loss. + + Args: + cls_prob (Tensor): Classification probability of shape + (num_anchors, num_classes). + bbox_pred (Tensor): Box probability of shape (num_anchors, 4). + flat_anchors (Tensor): Multi-level anchors of the image, which are + concatenated into a single tensor of shape (num_anchors, 4) + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + + Returns: + tuple: + + - box_prob (Tensor): Box probability of shape (num_anchors, 4). + - positive_loss (Tensor): Positive loss of shape (num_pos, ). + - num_pos (int): positive samples indexes. + """ + + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + with torch.no_grad(): + if len(gt_bboxes) == 0: + image_box_prob = torch.zeros( + flat_anchors.size(0), + self.cls_out_channels).type_as(bbox_pred) + else: + # box_localization: a_{j}^{loc}, shape: [j, 4] + pred_boxes = self.bbox_coder.decode(flat_anchors, bbox_pred) + + # object_box_iou: IoU_{ij}^{loc}, shape: [i, j] + object_box_iou = bbox_overlaps(gt_bboxes, pred_boxes) + + # object_box_prob: P{a_{j} -> b_{i}}, shape: [i, j] + t1 = self.bbox_thr + t2 = object_box_iou.max( + dim=1, keepdim=True).values.clamp(min=t1 + 1e-12) + object_box_prob = ((object_box_iou - t1) / (t2 - t1)).clamp( + min=0, max=1) + + # object_cls_box_prob: P{a_{j} -> b_{i}}, shape: [i, c, j] + num_obj = gt_labels.size(0) + indices = torch.stack( + [torch.arange(num_obj).type_as(gt_labels), gt_labels], + dim=0) + object_cls_box_prob = torch.sparse_coo_tensor( + indices, object_box_prob) + + # image_box_iou: P{a_{j} \in A_{+}}, shape: [c, j] + """ + from "start" to "end" implement: + image_box_iou = torch.sparse.max(object_cls_box_prob, + dim=0).t() + + """ + # start + box_cls_prob = torch.sparse.sum( + object_cls_box_prob, dim=0).to_dense() + + indices = torch.nonzero(box_cls_prob, as_tuple=False).t_() + if indices.numel() == 0: + image_box_prob = torch.zeros( + flat_anchors.size(0), + self.cls_out_channels).type_as(object_box_prob) + else: + nonzero_box_prob = torch.where( + (gt_labels.unsqueeze(dim=-1) == indices[0]), + object_box_prob[:, indices[1]], + torch.tensor( + [0]).type_as(object_box_prob)).max(dim=0).values + + # upmap to shape [j, c] + image_box_prob = torch.sparse_coo_tensor( + indices.flip([0]), + nonzero_box_prob, + size=(flat_anchors.size(0), + self.cls_out_channels)).to_dense() + # end + box_prob = image_box_prob + + # construct bags for objects + match_quality_matrix = bbox_overlaps(gt_bboxes, flat_anchors) + _, matched = torch.topk( + match_quality_matrix, self.pre_anchor_topk, dim=1, sorted=False) + del match_quality_matrix + + # matched_cls_prob: P_{ij}^{cls} + matched_cls_prob = torch.gather( + cls_prob[matched], 2, + gt_labels.view(-1, 1, 1).repeat(1, self.pre_anchor_topk, + 1)).squeeze(2) + + # matched_box_prob: P_{ij}^{loc} + matched_anchors = flat_anchors[matched] + matched_object_targets = self.bbox_coder.encode( + matched_anchors, + gt_bboxes.unsqueeze(dim=1).expand_as(matched_anchors)) + loss_bbox = self.loss_bbox( + bbox_pred[matched], + matched_object_targets, + reduction_override='none').sum(-1) + matched_box_prob = torch.exp(-loss_bbox) + + # positive_losses: {-log( Mean-max(P_{ij}^{cls} * P_{ij}^{loc}) )} + num_pos = len(gt_bboxes) + positive_loss = self.positive_bag_loss(matched_cls_prob, + matched_box_prob) + + return box_prob, positive_loss, num_pos + + def positive_bag_loss(self, matched_cls_prob: Tensor, + matched_box_prob: Tensor) -> Tensor: + """Compute positive bag loss. + + :math:`-log( Mean-max(P_{ij}^{cls} * P_{ij}^{loc}) )`. + + :math:`P_{ij}^{cls}`: matched_cls_prob, classification probability of matched samples. + + :math:`P_{ij}^{loc}`: matched_box_prob, box probability of matched samples. + + Args: + matched_cls_prob (Tensor): Classification probability of matched + samples in shape (num_gt, pre_anchor_topk). + matched_box_prob (Tensor): BBox probability of matched samples, + in shape (num_gt, pre_anchor_topk). + + Returns: + Tensor: Positive bag loss in shape (num_gt,). + """ # noqa: E501, W605 + # bag_prob = Mean-max(matched_prob) + matched_prob = matched_cls_prob * matched_box_prob + weight = 1 / torch.clamp(1 - matched_prob, 1e-12, None) + weight /= weight.sum(dim=1).unsqueeze(dim=-1) + bag_prob = (weight * matched_prob).sum(dim=1) + # positive_bag_loss = -self.alpha * log(bag_prob) + return self.alpha * F.binary_cross_entropy( + bag_prob, torch.ones_like(bag_prob), reduction='none') + + def negative_bag_loss(self, cls_prob: Tensor, box_prob: Tensor) -> Tensor: + """Compute negative bag loss. + + :math:`FL((1 - P_{a_{j} \in A_{+}}) * (1 - P_{j}^{bg}))`. + + :math:`P_{a_{j} \in A_{+}}`: Box_probability of matched samples. + + :math:`P_{j}^{bg}`: Classification probability of negative samples. + + Args: + cls_prob (Tensor): Classification probability, in shape + (num_img, num_anchors, num_classes). + box_prob (Tensor): Box probability, in shape + (num_img, num_anchors, num_classes). + + Returns: + Tensor: Negative bag loss in shape (num_img, num_anchors, + num_classes). + """ # noqa: E501, W605 + prob = cls_prob * (1 - box_prob) + # There are some cases when neg_prob = 0. + # This will cause the neg_prob.log() to be inf without clamp. + prob = prob.clamp(min=EPS, max=1 - EPS) + negative_bag_loss = prob**self.gamma * F.binary_cross_entropy( + prob, torch.zeros_like(prob), reduction='none') + return (1 - self.alpha) * negative_bag_loss diff --git a/mmdetection/mmdet/models/dense_heads/fsaf_head.py b/mmdetection/mmdet/models/dense_heads/fsaf_head.py new file mode 100644 index 00000000..0a01c487 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/fsaf_head.py @@ -0,0 +1,458 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Tuple + +import numpy as np +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import InstanceList, OptInstanceList, OptMultiConfig +from ..losses.accuracy import accuracy +from ..losses.utils import weight_reduce_loss +from ..task_modules.prior_generators import anchor_inside_flags +from ..utils import images_to_levels, multi_apply, unmap +from .retina_head import RetinaHead + + +@MODELS.register_module() +class FSAFHead(RetinaHead): + """Anchor-free head used in `FSAF `_. + + The head contains two subnetworks. The first classifies anchor boxes and + the second regresses deltas for the anchors (num_anchors is 1 for anchor- + free methods) + + Args: + *args: Same as its base class in :class:`RetinaHead` + score_threshold (float, optional): The score_threshold to calculate + positive recall. If given, prediction scores lower than this value + is counted as incorrect prediction. Defaults to None. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. + **kwargs: Same as its base class in :class:`RetinaHead` + + Example: + >>> import torch + >>> self = FSAFHead(11, 7) + >>> x = torch.rand(1, 7, 32, 32) + >>> cls_score, bbox_pred = self.forward_single(x) + >>> # Each anchor predicts a score for each class except background + >>> cls_per_anchor = cls_score.shape[1] / self.num_anchors + >>> box_per_anchor = bbox_pred.shape[1] / self.num_anchors + >>> assert cls_per_anchor == self.num_classes + >>> assert box_per_anchor == 4 + """ + + def __init__(self, + *args, + score_threshold: Optional[float] = None, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + # The positive bias in self.retina_reg conv is to prevent predicted \ + # bbox with 0 area + if init_cfg is None: + init_cfg = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=[ + dict( + type='Normal', + name='retina_cls', + std=0.01, + bias_prob=0.01), + dict( + type='Normal', name='retina_reg', std=0.01, bias=0.25) + ]) + super().__init__(*args, init_cfg=init_cfg, **kwargs) + self.score_threshold = score_threshold + + def forward_single(self, x: Tensor) -> Tuple[Tensor, Tensor]: + """Forward feature map of a single scale level. + + Args: + x (Tensor): Feature map of a single scale level. + + Returns: + tuple[Tensor, Tensor]: + + - cls_score (Tensor): Box scores for each scale level Has \ + shape (N, num_points * num_classes, H, W). + - bbox_pred (Tensor): Box energies / deltas for each scale \ + level with shape (N, num_points * 4, H, W). + """ + cls_score, bbox_pred = super().forward_single(x) + # relu: TBLR encoder only accepts positive bbox_pred + return cls_score, self.relu(bbox_pred) + + def _get_targets_single(self, + flat_anchors: Tensor, + valid_flags: Tensor, + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression and classification targets for anchors in a + single image. + + Most of the codes are the same with the base class :obj: `AnchorHead`, + except that it also collects and returns the matched gt index in the + image (from 0 to num_gt-1). If the anchor bbox is not matched to any + gt, the corresponding value in pos_gt_inds is -1. + + Args: + flat_anchors (Tensor): Multi-level anchors of the image, which are + concatenated into a single tensor of shape (num_anchors, 4) + valid_flags (Tensor): Multi level valid flags of the image, + which are concatenated into a single tensor of + shape (num_anchors, ). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + """ + inside_flags = anchor_inside_flags(flat_anchors, valid_flags, + img_meta['img_shape'][:2], + self.train_cfg['allowed_border']) + if not inside_flags.any(): + raise ValueError( + 'There is no valid anchor inside the image boundary. Please ' + 'check the image size and anchor sizes, or set ' + '``allowed_border`` to -1 to skip the condition.') + # Assign gt and sample anchors + anchors = flat_anchors[inside_flags.type(torch.bool), :] + + pred_instances = InstanceData(priors=anchors) + assign_result = self.assigner.assign(pred_instances, gt_instances, + gt_instances_ignore) + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_valid_anchors = anchors.shape[0] + bbox_targets = torch.zeros_like(anchors) + bbox_weights = torch.zeros_like(anchors) + labels = anchors.new_full((num_valid_anchors, ), + self.num_classes, + dtype=torch.long) + label_weights = anchors.new_zeros( + (num_valid_anchors, self.cls_out_channels), dtype=torch.float) + pos_gt_inds = anchors.new_full((num_valid_anchors, ), + -1, + dtype=torch.long) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + + if len(pos_inds) > 0: + if not self.reg_decoded_bbox: + pos_bbox_targets = self.bbox_coder.encode( + sampling_result.pos_bboxes, sampling_result.pos_gt_bboxes) + else: + # When the regression loss (e.g. `IouLoss`, `GIouLoss`) + # is applied directly on the decoded bounding boxes, both + # the predicted boxes and regression targets should be with + # absolute coordinate format. + pos_bbox_targets = sampling_result.pos_gt_bboxes + bbox_targets[pos_inds, :] = pos_bbox_targets + bbox_weights[pos_inds, :] = 1.0 + # The assigned gt_index for each anchor. (0-based) + pos_gt_inds[pos_inds] = sampling_result.pos_assigned_gt_inds + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + # shadowed_labels is a tensor composed of tuples + # (anchor_inds, class_label) that indicate those anchors lying in the + # outer region of a gt or overlapped by another gt with a smaller + # area. + # + # Therefore, only the shadowed labels are ignored for loss calculation. + # the key `shadowed_labels` is defined in :obj:`CenterRegionAssigner` + shadowed_labels = assign_result.get_extra_property('shadowed_labels') + if shadowed_labels is not None and shadowed_labels.numel(): + if len(shadowed_labels.shape) == 2: + idx_, label_ = shadowed_labels[:, 0], shadowed_labels[:, 1] + assert (labels[idx_] != label_).all(), \ + 'One label cannot be both positive and ignored' + label_weights[idx_, label_] = 0 + else: + label_weights[shadowed_labels] = 0 + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_anchors.size(0) + labels = unmap( + labels, num_total_anchors, inside_flags, + fill=self.num_classes) # fill bg label + label_weights = unmap(label_weights, num_total_anchors, + inside_flags) + bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags) + bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags) + pos_gt_inds = unmap( + pos_gt_inds, num_total_anchors, inside_flags, fill=-1) + + return (labels, label_weights, bbox_targets, bbox_weights, pos_inds, + neg_inds, sampling_result, pos_gt_inds) + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Compute loss of the head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_points * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_points * 4, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + for i in range(len(bbox_preds)): # loop over fpn level + # avoid 0 area of the predicted bbox + bbox_preds[i] = bbox_preds[i].clamp(min=1e-4) + # TODO: It may directly use the base-class loss function. + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + batch_size = len(batch_img_metas) + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + return_sampling_results=True) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + avg_factor, sampling_results_list, + pos_assigned_gt_inds_list) = cls_reg_targets + + num_gts = np.array(list(map(len, batch_gt_instances))) + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + # concat all level anchors and flags to a single tensor + concat_anchor_list = [] + for i in range(len(anchor_list)): + concat_anchor_list.append(torch.cat(anchor_list[i])) + all_anchor_list = images_to_levels(concat_anchor_list, + num_level_anchors) + losses_cls, losses_bbox = multi_apply( + self.loss_by_feat_single, + cls_scores, + bbox_preds, + all_anchor_list, + labels_list, + label_weights_list, + bbox_targets_list, + bbox_weights_list, + avg_factor=avg_factor) + + # `pos_assigned_gt_inds_list` (length: fpn_levels) stores the assigned + # gt index of each anchor bbox in each fpn level. + cum_num_gts = list(np.cumsum(num_gts)) # length of batch_size + for i, assign in enumerate(pos_assigned_gt_inds_list): + # loop over fpn levels + for j in range(1, batch_size): + # loop over batch size + # Convert gt indices in each img to those in the batch + assign[j][assign[j] >= 0] += int(cum_num_gts[j - 1]) + pos_assigned_gt_inds_list[i] = assign.flatten() + labels_list[i] = labels_list[i].flatten() + num_gts = num_gts.sum() # total number of gt in the batch + # The unique label index of each gt in the batch + label_sequence = torch.arange(num_gts, device=device) + # Collect the average loss of each gt in each level + with torch.no_grad(): + loss_levels, = multi_apply( + self.collect_loss_level_single, + losses_cls, + losses_bbox, + pos_assigned_gt_inds_list, + labels_seq=label_sequence) + # Shape: (fpn_levels, num_gts). Loss of each gt at each fpn level + loss_levels = torch.stack(loss_levels, dim=0) + # Locate the best fpn level for loss back-propagation + if loss_levels.numel() == 0: # zero gt + argmin = loss_levels.new_empty((num_gts, ), dtype=torch.long) + else: + _, argmin = loss_levels.min(dim=0) + + # Reweight the loss of each (anchor, label) pair, so that only those + # at the best gt level are back-propagated. + losses_cls, losses_bbox, pos_inds = multi_apply( + self.reweight_loss_single, + losses_cls, + losses_bbox, + pos_assigned_gt_inds_list, + labels_list, + list(range(len(losses_cls))), + min_levels=argmin) + num_pos = torch.cat(pos_inds, 0).sum().float() + pos_recall = self.calculate_pos_recall(cls_scores, labels_list, + pos_inds) + + if num_pos == 0: # No gt + num_total_neg = sum( + [results.num_neg for results in sampling_results_list]) + avg_factor = num_pos + num_total_neg + else: + avg_factor = num_pos + for i in range(len(losses_cls)): + losses_cls[i] /= avg_factor + losses_bbox[i] /= avg_factor + return dict( + loss_cls=losses_cls, + loss_bbox=losses_bbox, + num_pos=num_pos / batch_size, + pos_recall=pos_recall) + + def calculate_pos_recall(self, cls_scores: List[Tensor], + labels_list: List[Tensor], + pos_inds: List[Tensor]) -> Tensor: + """Calculate positive recall with score threshold. + + Args: + cls_scores (list[Tensor]): Classification scores at all fpn levels. + Each tensor is in shape (N, num_classes * num_anchors, H, W) + labels_list (list[Tensor]): The label that each anchor is assigned + to. Shape (N * H * W * num_anchors, ) + pos_inds (list[Tensor]): List of bool tensors indicating whether + the anchor is assigned to a positive label. + Shape (N * H * W * num_anchors, ) + + Returns: + Tensor: A single float number indicating the positive recall. + """ + with torch.no_grad(): + num_class = self.num_classes + scores = [ + cls.permute(0, 2, 3, 1).reshape(-1, num_class)[pos] + for cls, pos in zip(cls_scores, pos_inds) + ] + labels = [ + label.reshape(-1)[pos] + for label, pos in zip(labels_list, pos_inds) + ] + scores = torch.cat(scores, dim=0) + labels = torch.cat(labels, dim=0) + if self.use_sigmoid_cls: + scores = scores.sigmoid() + else: + scores = scores.softmax(dim=1) + + return accuracy(scores, labels, thresh=self.score_threshold) + + def collect_loss_level_single(self, cls_loss: Tensor, reg_loss: Tensor, + assigned_gt_inds: Tensor, + labels_seq: Tensor) -> Tensor: + """Get the average loss in each FPN level w.r.t. each gt label. + + Args: + cls_loss (Tensor): Classification loss of each feature map pixel, + shape (num_anchor, num_class) + reg_loss (Tensor): Regression loss of each feature map pixel, + shape (num_anchor, 4) + assigned_gt_inds (Tensor): It indicates which gt the prior is + assigned to (0-based, -1: no assignment). shape (num_anchor), + labels_seq: The rank of labels. shape (num_gt) + + Returns: + Tensor: shape (num_gt), average loss of each gt in this level + """ + if len(reg_loss.shape) == 2: # iou loss has shape (num_prior, 4) + reg_loss = reg_loss.sum(dim=-1) # sum loss in tblr dims + if len(cls_loss.shape) == 2: + cls_loss = cls_loss.sum(dim=-1) # sum loss in class dims + loss = cls_loss + reg_loss + assert loss.size(0) == assigned_gt_inds.size(0) + # Default loss value is 1e6 for a layer where no anchor is positive + # to ensure it will not be chosen to back-propagate gradient + losses_ = loss.new_full(labels_seq.shape, 1e6) + for i, l in enumerate(labels_seq): + match = assigned_gt_inds == l + if match.any(): + losses_[i] = loss[match].mean() + return losses_, + + def reweight_loss_single(self, cls_loss: Tensor, reg_loss: Tensor, + assigned_gt_inds: Tensor, labels: Tensor, + level: int, min_levels: Tensor) -> tuple: + """Reweight loss values at each level. + + Reassign loss values at each level by masking those where the + pre-calculated loss is too large. Then return the reduced losses. + + Args: + cls_loss (Tensor): Element-wise classification loss. + Shape: (num_anchors, num_classes) + reg_loss (Tensor): Element-wise regression loss. + Shape: (num_anchors, 4) + assigned_gt_inds (Tensor): The gt indices that each anchor bbox + is assigned to. -1 denotes a negative anchor, otherwise it is the + gt index (0-based). Shape: (num_anchors, ), + labels (Tensor): Label assigned to anchors. Shape: (num_anchors, ). + level (int): The current level index in the pyramid + (0-4 for RetinaNet) + min_levels (Tensor): The best-matching level for each gt. + Shape: (num_gts, ), + + Returns: + tuple: + + - cls_loss: Reduced corrected classification loss. Scalar. + - reg_loss: Reduced corrected regression loss. Scalar. + - pos_flags (Tensor): Corrected bool tensor indicating the \ + final positive anchors. Shape: (num_anchors, ). + """ + loc_weight = torch.ones_like(reg_loss) + cls_weight = torch.ones_like(cls_loss) + pos_flags = assigned_gt_inds >= 0 # positive pixel flag + pos_indices = torch.nonzero(pos_flags, as_tuple=False).flatten() + + if pos_flags.any(): # pos pixels exist + pos_assigned_gt_inds = assigned_gt_inds[pos_flags] + zeroing_indices = (min_levels[pos_assigned_gt_inds] != level) + neg_indices = pos_indices[zeroing_indices] + + if neg_indices.numel(): + pos_flags[neg_indices] = 0 + loc_weight[neg_indices] = 0 + # Only the weight corresponding to the label is + # zeroed out if not selected + zeroing_labels = labels[neg_indices] + assert (zeroing_labels >= 0).all() + cls_weight[neg_indices, zeroing_labels] = 0 + + # Weighted loss for both cls and reg loss + cls_loss = weight_reduce_loss(cls_loss, cls_weight, reduction='sum') + reg_loss = weight_reduce_loss(reg_loss, loc_weight, reduction='sum') + + return cls_loss, reg_loss, pos_flags diff --git a/mmdetection/mmdet/models/dense_heads/ga_retina_head.py b/mmdetection/mmdet/models/dense_heads/ga_retina_head.py new file mode 100644 index 00000000..569910b3 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/ga_retina_head.py @@ -0,0 +1,120 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmcv.ops import MaskedConv2d +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType, OptMultiConfig +from .guided_anchor_head import FeatureAdaption, GuidedAnchorHead + + +@MODELS.register_module() +class GARetinaHead(GuidedAnchorHead): + """Guided-Anchor-based RetinaNet head.""" + + def __init__(self, + num_classes: int, + in_channels: int, + stacked_convs: int = 4, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + if init_cfg is None: + init_cfg = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=[ + dict( + type='Normal', + name='conv_loc', + std=0.01, + bias_prob=0.01), + dict( + type='Normal', + name='retina_cls', + std=0.01, + bias_prob=0.01) + ]) + self.stacked_convs = stacked_convs + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + init_cfg=init_cfg, + **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.relu = nn.ReLU(inplace=True) + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + + self.conv_loc = nn.Conv2d(self.feat_channels, 1, 1) + num_anchors = self.square_anchor_generator.num_base_priors[0] + self.conv_shape = nn.Conv2d(self.feat_channels, num_anchors * 2, 1) + self.feature_adaption_cls = FeatureAdaption( + self.feat_channels, + self.feat_channels, + kernel_size=3, + deform_groups=self.deform_groups) + self.feature_adaption_reg = FeatureAdaption( + self.feat_channels, + self.feat_channels, + kernel_size=3, + deform_groups=self.deform_groups) + self.retina_cls = MaskedConv2d( + self.feat_channels, + self.num_base_priors * self.cls_out_channels, + 3, + padding=1) + self.retina_reg = MaskedConv2d( + self.feat_channels, self.num_base_priors * 4, 3, padding=1) + + def forward_single(self, x: Tensor) -> Tuple[Tensor]: + """Forward feature map of a single scale level.""" + cls_feat = x + reg_feat = x + for cls_conv in self.cls_convs: + cls_feat = cls_conv(cls_feat) + for reg_conv in self.reg_convs: + reg_feat = reg_conv(reg_feat) + + loc_pred = self.conv_loc(cls_feat) + shape_pred = self.conv_shape(reg_feat) + + cls_feat = self.feature_adaption_cls(cls_feat, shape_pred) + reg_feat = self.feature_adaption_reg(reg_feat, shape_pred) + + if not self.training: + mask = loc_pred.sigmoid()[0] >= self.loc_filter_thr + else: + mask = None + cls_score = self.retina_cls(cls_feat, mask) + bbox_pred = self.retina_reg(reg_feat, mask) + return cls_score, bbox_pred, shape_pred, loc_pred diff --git a/mmdetection/mmdet/models/dense_heads/ga_rpn_head.py b/mmdetection/mmdet/models/dense_heads/ga_rpn_head.py new file mode 100644 index 00000000..96144631 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/ga_rpn_head.py @@ -0,0 +1,222 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import List, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.ops import nms +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, InstanceList, MultiConfig, OptInstanceList +from .guided_anchor_head import GuidedAnchorHead + + +@MODELS.register_module() +class GARPNHead(GuidedAnchorHead): + """Guided-Anchor-based RPN head.""" + + def __init__(self, + in_channels: int, + num_classes: int = 1, + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', + name='conv_loc', + std=0.01, + bias_prob=0.01)), + **kwargs) -> None: + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + init_cfg=init_cfg, + **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.rpn_conv = nn.Conv2d( + self.in_channels, self.feat_channels, 3, padding=1) + super(GARPNHead, self)._init_layers() + + def forward_single(self, x: Tensor) -> Tuple[Tensor]: + """Forward feature of a single scale level.""" + + x = self.rpn_conv(x) + x = F.relu(x, inplace=True) + (cls_score, bbox_pred, shape_pred, + loc_pred) = super().forward_single(x) + return cls_score, bbox_pred, shape_pred, loc_pred + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + shape_preds: List[Tensor], + loc_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + has shape (N, num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + shape_preds (list[Tensor]): shape predictions for each scale + level with shape (N, 1, H, W). + loc_preds (list[Tensor]): location predictions for each scale + level with shape (N, num_anchors * 2, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: A dictionary of loss components. + """ + losses = super().loss_by_feat( + cls_scores, + bbox_preds, + shape_preds, + loc_preds, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + return dict( + loss_rpn_cls=losses['loss_cls'], + loss_rpn_bbox=losses['loss_bbox'], + loss_anchor_shape=losses['loss_shape'], + loss_anchor_loc=losses['loss_loc']) + + def _predict_by_feat_single(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + mlvl_anchors: List[Tensor], + mlvl_masks: List[Tensor], + img_meta: dict, + cfg: ConfigType, + rescale: bool = False) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_scores (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + mlvl_anchors (list[Tensor]): Each element in the list is + the anchors of a single level in feature pyramid. it has + shape (num_priors, 4). + mlvl_masks (list[Tensor]): Each element in the list is location + masks of a single level. + img_meta (dict): Image meta info. + cfg (:obj:`ConfigDict` or dict): Test / postprocessing + configuration, if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), the last + dimension 4 arrange as (x1, y1, x2, y2). + """ + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + assert cfg.nms.get('type', 'nms') == 'nms', 'GARPNHead only support ' \ + 'naive nms.' + + mlvl_proposals = [] + for idx in range(len(cls_scores)): + rpn_cls_score = cls_scores[idx] + rpn_bbox_pred = bbox_preds[idx] + anchors = mlvl_anchors[idx] + mask = mlvl_masks[idx] + assert rpn_cls_score.size()[-2:] == rpn_bbox_pred.size()[-2:] + # if no location is kept, end. + if mask.sum() == 0: + continue + rpn_cls_score = rpn_cls_score.permute(1, 2, 0) + if self.use_sigmoid_cls: + rpn_cls_score = rpn_cls_score.reshape(-1) + scores = rpn_cls_score.sigmoid() + else: + rpn_cls_score = rpn_cls_score.reshape(-1, 2) + # remind that we set FG labels to [0, num_class-1] + # since mmdet v2.0 + # BG cat_id: num_class + scores = rpn_cls_score.softmax(dim=1)[:, :-1] + # filter scores, bbox_pred w.r.t. mask. + # anchors are filtered in get_anchors() beforehand. + scores = scores[mask] + rpn_bbox_pred = rpn_bbox_pred.permute(1, 2, 0).reshape(-1, + 4)[mask, :] + if scores.dim() == 0: + rpn_bbox_pred = rpn_bbox_pred.unsqueeze(0) + anchors = anchors.unsqueeze(0) + scores = scores.unsqueeze(0) + # filter anchors, bbox_pred, scores w.r.t. scores + if cfg.nms_pre > 0 and scores.shape[0] > cfg.nms_pre: + _, topk_inds = scores.topk(cfg.nms_pre) + rpn_bbox_pred = rpn_bbox_pred[topk_inds, :] + anchors = anchors[topk_inds, :] + scores = scores[topk_inds] + # get proposals w.r.t. anchors and rpn_bbox_pred + proposals = self.bbox_coder.decode( + anchors, rpn_bbox_pred, max_shape=img_meta['img_shape']) + # filter out too small bboxes + if cfg.min_bbox_size >= 0: + w = proposals[:, 2] - proposals[:, 0] + h = proposals[:, 3] - proposals[:, 1] + valid_mask = (w > cfg.min_bbox_size) & (h > cfg.min_bbox_size) + if not valid_mask.all(): + proposals = proposals[valid_mask] + scores = scores[valid_mask] + + # NMS in current level + proposals, _ = nms(proposals, scores, cfg.nms.iou_threshold) + proposals = proposals[:cfg.nms_post, :] + mlvl_proposals.append(proposals) + proposals = torch.cat(mlvl_proposals, 0) + if cfg.get('nms_across_levels', False): + # NMS across multi levels + proposals, _ = nms(proposals[:, :4], proposals[:, -1], + cfg.nms.iou_threshold) + proposals = proposals[:cfg.max_per_img, :] + else: + scores = proposals[:, 4] + num = min(cfg.max_per_img, proposals.shape[0]) + _, topk_inds = scores.topk(num) + proposals = proposals[topk_inds, :] + + bboxes = proposals[:, :-1] + scores = proposals[:, -1] + if rescale: + assert img_meta.get('scale_factor') is not None + bboxes /= bboxes.new_tensor(img_meta['scale_factor']).repeat( + (1, 2)) + + results = InstanceData() + results.bboxes = bboxes + results.scores = scores + results.labels = scores.new_zeros(scores.size(0), dtype=torch.long) + return results diff --git a/mmdetection/mmdet/models/dense_heads/gfl_head.py b/mmdetection/mmdet/models/dense_heads/gfl_head.py new file mode 100644 index 00000000..be43d9b4 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/gfl_head.py @@ -0,0 +1,667 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Sequence, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, Scale +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures.bbox import bbox_overlaps +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, OptConfigType, + OptInstanceList, reduce_mean) +from ..task_modules.prior_generators import anchor_inside_flags +from ..task_modules.samplers import PseudoSampler +from ..utils import (filter_scores_and_topk, images_to_levels, multi_apply, + unmap) +from .anchor_head import AnchorHead + + +class Integral(nn.Module): + """A fixed layer for calculating integral result from distribution. + + This layer calculates the target location by :math: ``sum{P(y_i) * y_i}``, + P(y_i) denotes the softmax vector that represents the discrete distribution + y_i denotes the discrete set, usually {0, 1, 2, ..., reg_max} + + Args: + reg_max (int): The maximal value of the discrete set. Defaults to 16. + You may want to reset it according to your new dataset or related + settings. + """ + + def __init__(self, reg_max: int = 16) -> None: + super().__init__() + self.reg_max = reg_max + self.register_buffer('project', + torch.linspace(0, self.reg_max, self.reg_max + 1)) + + def forward(self, x: Tensor) -> Tensor: + """Forward feature from the regression head to get integral result of + bounding box location. + + Args: + x (Tensor): Features of the regression head, shape (N, 4*(n+1)), + n is self.reg_max. + + Returns: + x (Tensor): Integral result of box locations, i.e., distance + offsets from the box center in four directions, shape (N, 4). + """ + x = F.softmax(x.reshape(-1, self.reg_max + 1), dim=1) + x = F.linear(x, self.project.type_as(x)).reshape(-1, 4) + return x + + +@MODELS.register_module() +class GFLHead(AnchorHead): + """Generalized Focal Loss: Learning Qualified and Distributed Bounding + Boxes for Dense Object Detection. + + GFL head structure is similar with ATSS, however GFL uses + 1) joint representation for classification and localization quality, and + 2) flexible General distribution for bounding box locations, + which are supervised by + Quality Focal Loss (QFL) and Distribution Focal Loss (DFL), respectively + + https://arxiv.org/abs/2006.04388 + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + stacked_convs (int): Number of conv layers in cls and reg tower. + Defaults to 4. + conv_cfg (:obj:`ConfigDict` or dict, optional): dictionary to construct + and config conv layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict): dictionary to construct and + config norm layer. Default: dict(type='GN', num_groups=32, + requires_grad=True). + loss_qfl (:obj:`ConfigDict` or dict): Config of Quality Focal Loss + (QFL). + bbox_coder (:obj:`ConfigDict` or dict): Config of bbox coder. Defaults + to 'DistancePointBBoxCoder'. + reg_max (int): Max value of integral set :math: ``{0, ..., reg_max}`` + in QFL setting. Defaults to 16. + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`]): Initialization config dict. + Example: + >>> self = GFLHead(11, 7) + >>> feats = [torch.rand(1, 7, s, s) for s in [4, 8, 16, 32, 64]] + >>> cls_quality_score, bbox_pred = self.forward(feats) + >>> assert len(cls_quality_score) == len(self.scales) + """ + + def __init__(self, + num_classes: int, + in_channels: int, + stacked_convs: int = 4, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict( + type='GN', num_groups=32, requires_grad=True), + loss_dfl: ConfigType = dict( + type='DistributionFocalLoss', loss_weight=0.25), + bbox_coder: ConfigType = dict(type='DistancePointBBoxCoder'), + reg_max: int = 16, + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', + name='gfl_cls', + std=0.01, + bias_prob=0.01)), + **kwargs) -> None: + self.stacked_convs = stacked_convs + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.reg_max = reg_max + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + bbox_coder=bbox_coder, + init_cfg=init_cfg, + **kwargs) + + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + if self.train_cfg.get('sampler', None) is not None: + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], default_args=dict(context=self)) + else: + self.sampler = PseudoSampler(context=self) + + self.integral = Integral(self.reg_max) + self.loss_dfl = MODELS.build(loss_dfl) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.relu = nn.ReLU() + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + assert self.num_anchors == 1, 'anchor free version' + self.gfl_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + self.gfl_reg = nn.Conv2d( + self.feat_channels, 4 * (self.reg_max + 1), 3, padding=1) + self.scales = nn.ModuleList( + [Scale(1.0) for _ in self.prior_generator.strides]) + + def forward(self, x: Tuple[Tensor]) -> Tuple[List[Tensor]]: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually a tuple of classification scores and bbox prediction + + - cls_scores (list[Tensor]): Classification and quality (IoU) + joint scores for all scale levels, each is a 4D-tensor, + the channel number is num_classes. + - bbox_preds (list[Tensor]): Box distribution logits for all + scale levels, each is a 4D-tensor, the channel number is + 4*(n+1), n is max value of integral set. + """ + return multi_apply(self.forward_single, x, self.scales) + + def forward_single(self, x: Tensor, scale: Scale) -> Sequence[Tensor]: + """Forward feature of a single scale level. + + Args: + x (Tensor): Features of a single scale level. + scale (:obj: `mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + + Returns: + tuple: + + - cls_score (Tensor): Cls and quality joint scores for a single + scale level the channel number is num_classes. + - bbox_pred (Tensor): Box distribution logits for a single scale + level, the channel number is 4*(n+1), n is max value of + integral set. + """ + cls_feat = x + reg_feat = x + for cls_conv in self.cls_convs: + cls_feat = cls_conv(cls_feat) + for reg_conv in self.reg_convs: + reg_feat = reg_conv(reg_feat) + cls_score = self.gfl_cls(cls_feat) + bbox_pred = scale(self.gfl_reg(reg_feat)).float() + return cls_score, bbox_pred + + def anchor_center(self, anchors: Tensor) -> Tensor: + """Get anchor centers from anchors. + + Args: + anchors (Tensor): Anchor list with shape (N, 4), ``xyxy`` format. + + Returns: + Tensor: Anchor centers with shape (N, 2), ``xy`` format. + """ + anchors_cx = (anchors[..., 2] + anchors[..., 0]) / 2 + anchors_cy = (anchors[..., 3] + anchors[..., 1]) / 2 + return torch.stack([anchors_cx, anchors_cy], dim=-1) + + def loss_by_feat_single(self, anchors: Tensor, cls_score: Tensor, + bbox_pred: Tensor, labels: Tensor, + label_weights: Tensor, bbox_targets: Tensor, + stride: Tuple[int], avg_factor: int) -> dict: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + anchors (Tensor): Box reference for each scale level with shape + (N, num_total_anchors, 4). + cls_score (Tensor): Cls and quality joint scores for each scale + level has shape (N, num_classes, H, W). + bbox_pred (Tensor): Box distribution logits for each scale + level with shape (N, 4*(n+1), H, W), n is max value of integral + set. + labels (Tensor): Labels of each anchors with shape + (N, num_total_anchors). + label_weights (Tensor): Label weights of each anchor with shape + (N, num_total_anchors) + bbox_targets (Tensor): BBox regression targets of each anchor with + shape (N, num_total_anchors, 4). + stride (Tuple[int]): Stride in this scale level. + avg_factor (int): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + `PseudoSampler`, `avg_factor` is usually equal to the number + of positive priors. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert stride[0] == stride[1], 'h stride is not equal to w stride!' + anchors = anchors.reshape(-1, 4) + cls_score = cls_score.permute(0, 2, 3, + 1).reshape(-1, self.cls_out_channels) + bbox_pred = bbox_pred.permute(0, 2, 3, + 1).reshape(-1, 4 * (self.reg_max + 1)) + bbox_targets = bbox_targets.reshape(-1, 4) + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().squeeze(1) + score = label_weights.new_zeros(labels.shape) + + if len(pos_inds) > 0: + pos_bbox_targets = bbox_targets[pos_inds] + pos_bbox_pred = bbox_pred[pos_inds] + pos_anchors = anchors[pos_inds] + pos_anchor_centers = self.anchor_center(pos_anchors) / stride[0] + + weight_targets = cls_score.detach().sigmoid() + weight_targets = weight_targets.max(dim=1)[0][pos_inds] + pos_bbox_pred_corners = self.integral(pos_bbox_pred) + pos_decode_bbox_pred = self.bbox_coder.decode( + pos_anchor_centers, pos_bbox_pred_corners) + pos_decode_bbox_targets = pos_bbox_targets / stride[0] + score[pos_inds] = bbox_overlaps( + pos_decode_bbox_pred.detach(), + pos_decode_bbox_targets, + is_aligned=True) + pred_corners = pos_bbox_pred.reshape(-1, self.reg_max + 1) + target_corners = self.bbox_coder.encode(pos_anchor_centers, + pos_decode_bbox_targets, + self.reg_max).reshape(-1) + + # regression loss + loss_bbox = self.loss_bbox( + pos_decode_bbox_pred, + pos_decode_bbox_targets, + weight=weight_targets, + avg_factor=1.0) + + # dfl loss + loss_dfl = self.loss_dfl( + pred_corners, + target_corners, + weight=weight_targets[:, None].expand(-1, 4).reshape(-1), + avg_factor=4.0) + else: + loss_bbox = bbox_pred.sum() * 0 + loss_dfl = bbox_pred.sum() * 0 + weight_targets = bbox_pred.new_tensor(0) + + # cls (qfl) loss + loss_cls = self.loss_cls( + cls_score, (labels, score), + weight=label_weights, + avg_factor=avg_factor) + + return loss_cls, loss_bbox, loss_dfl, weight_targets.sum() + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Cls and quality scores for each scale + level has shape (N, num_classes, H, W). + bbox_preds (list[Tensor]): Box distribution logits for each scale + level with shape (N, 4*(n+1), H, W), n is max value of integral + set. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + + (anchor_list, labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor) = cls_reg_targets + + avg_factor = reduce_mean( + torch.tensor(avg_factor, dtype=torch.float, device=device)).item() + + losses_cls, losses_bbox, losses_dfl,\ + avg_factor = multi_apply( + self.loss_by_feat_single, + anchor_list, + cls_scores, + bbox_preds, + labels_list, + label_weights_list, + bbox_targets_list, + self.prior_generator.strides, + avg_factor=avg_factor) + + avg_factor = sum(avg_factor) + avg_factor = reduce_mean(avg_factor).clamp_(min=1).item() + losses_bbox = list(map(lambda x: x / avg_factor, losses_bbox)) + losses_dfl = list(map(lambda x: x / avg_factor, losses_dfl)) + return dict( + loss_cls=losses_cls, loss_bbox=losses_bbox, loss_dfl=losses_dfl) + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image. GFL head does not need this value. + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid, has shape + (num_priors, 4). + img_meta (dict): Image meta info. + cfg (:obj: `ConfigDict`): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + tuple[Tensor]: Results of detected bboxes and labels. If with_nms + is False and mlvl_score_factor is None, return mlvl_bboxes and + mlvl_scores, else return mlvl_bboxes, mlvl_scores and + mlvl_score_factor. Usually with_nms is False is used for aug + test. If with_nms is True, then return the following format + + - det_bboxes (Tensor): Predicted bboxes with shape + [num_bboxes, 5], where the first 4 columns are bounding + box positions (tl_x, tl_y, br_x, br_y) and the 5-th + column are scores between 0 and 1. + - det_labels (Tensor): Predicted labels of the corresponding + box with shape [num_bboxes]. + """ + cfg = self.test_cfg if cfg is None else cfg + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bboxes = [] + mlvl_scores = [] + mlvl_labels = [] + for level_idx, (cls_score, bbox_pred, stride, priors) in enumerate( + zip(cls_score_list, bbox_pred_list, + self.prior_generator.strides, mlvl_priors)): + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + assert stride[0] == stride[1] + + bbox_pred = bbox_pred.permute(1, 2, 0) + bbox_pred = self.integral(bbox_pred) * stride[0] + + scores = cls_score.permute(1, 2, 0).reshape( + -1, self.cls_out_channels).sigmoid() + + # After https://github.com/open-mmlab/mmdetection/pull/6268/, + # this operation keeps fewer bboxes under the same `nms_pre`. + # There is no difference in performance for most models. If you + # find a slight drop in performance, you can set a larger + # `nms_pre` than before. + results = filter_scores_and_topk( + scores, cfg.score_thr, nms_pre, + dict(bbox_pred=bbox_pred, priors=priors)) + scores, labels, _, filtered_results = results + + bbox_pred = filtered_results['bbox_pred'] + priors = filtered_results['priors'] + + bboxes = self.bbox_coder.decode( + self.anchor_center(priors), bbox_pred, max_shape=img_shape) + mlvl_bboxes.append(bboxes) + mlvl_scores.append(scores) + mlvl_labels.append(labels) + + results = InstanceData() + results.bboxes = torch.cat(mlvl_bboxes) + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + + return self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + + def get_targets(self, + anchor_list: List[Tensor], + valid_flag_list: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs=True) -> tuple: + """Get targets for GFL head. + + This method is almost the same as `AnchorHead.get_targets()`. Besides + returning the targets as the parent method does, it also returns the + anchors as the first element of the returned tuple. + """ + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + num_level_anchors_list = [num_level_anchors] * num_imgs + + # concat all level anchors and flags to a single tensor + for i in range(num_imgs): + assert len(anchor_list[i]) == len(valid_flag_list[i]) + anchor_list[i] = torch.cat(anchor_list[i]) + valid_flag_list[i] = torch.cat(valid_flag_list[i]) + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + (all_anchors, all_labels, all_label_weights, all_bbox_targets, + all_bbox_weights, pos_inds_list, neg_inds_list, + sampling_results_list) = multi_apply( + self._get_targets_single, + anchor_list, + valid_flag_list, + num_level_anchors_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + # Get `avg_factor` of all images, which calculate in `SamplingResult`. + # When using sampling method, avg_factor is usually the sum of + # positive and negative priors. When using `PseudoSampler`, + # `avg_factor` is usually equal to the number of positive priors. + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + # split targets to a list w.r.t. multiple levels + anchors_list = images_to_levels(all_anchors, num_level_anchors) + labels_list = images_to_levels(all_labels, num_level_anchors) + label_weights_list = images_to_levels(all_label_weights, + num_level_anchors) + bbox_targets_list = images_to_levels(all_bbox_targets, + num_level_anchors) + bbox_weights_list = images_to_levels(all_bbox_weights, + num_level_anchors) + return (anchors_list, labels_list, label_weights_list, + bbox_targets_list, bbox_weights_list, avg_factor) + + def _get_targets_single(self, + flat_anchors: Tensor, + valid_flags: Tensor, + num_level_anchors: List[int], + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression, classification targets for anchors in a single + image. + + Args: + flat_anchors (Tensor): Multi-level anchors of the image, which are + concatenated into a single tensor of shape (num_anchors, 4) + valid_flags (Tensor): Multi level valid flags of the image, + which are concatenated into a single tensor of + shape (num_anchors,). + num_level_anchors (list[int]): Number of anchors of each scale + level. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + + Returns: + tuple: N is the number of total anchors in the image. + + - anchors (Tensor): All anchors in the image with shape (N, 4). + - labels (Tensor): Labels of all anchors in the image with + shape (N,). + - label_weights (Tensor): Label weights of all anchor in the + image with shape (N,). + - bbox_targets (Tensor): BBox targets of all anchors in the + image with shape (N, 4). + - bbox_weights (Tensor): BBox weights of all anchors in the + image with shape (N, 4). + - pos_inds (Tensor): Indices of positive anchor with shape + (num_pos,). + - neg_inds (Tensor): Indices of negative anchor with shape + (num_neg,). + - sampling_result (:obj:`SamplingResult`): Sampling results. + """ + inside_flags = anchor_inside_flags(flat_anchors, valid_flags, + img_meta['img_shape'][:2], + self.train_cfg['allowed_border']) + if not inside_flags.any(): + raise ValueError( + 'There is no valid anchor inside the image boundary. Please ' + 'check the image size and anchor sizes, or set ' + '``allowed_border`` to -1 to skip the condition.') + # assign gt and sample anchors + anchors = flat_anchors[inside_flags, :] + num_level_anchors_inside = self.get_num_level_anchors_inside( + num_level_anchors, inside_flags) + pred_instances = InstanceData(priors=anchors) + assign_result = self.assigner.assign( + pred_instances=pred_instances, + num_level_priors=num_level_anchors_inside, + gt_instances=gt_instances, + gt_instances_ignore=gt_instances_ignore) + + sampling_result = self.sampler.sample( + assign_result=assign_result, + pred_instances=pred_instances, + gt_instances=gt_instances) + + num_valid_anchors = anchors.shape[0] + bbox_targets = torch.zeros_like(anchors) + bbox_weights = torch.zeros_like(anchors) + labels = anchors.new_full((num_valid_anchors, ), + self.num_classes, + dtype=torch.long) + label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + pos_bbox_targets = sampling_result.pos_gt_bboxes + bbox_targets[pos_inds, :] = pos_bbox_targets + bbox_weights[pos_inds, :] = 1.0 + + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_anchors.size(0) + anchors = unmap(anchors, num_total_anchors, inside_flags) + labels = unmap( + labels, num_total_anchors, inside_flags, fill=self.num_classes) + label_weights = unmap(label_weights, num_total_anchors, + inside_flags) + bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags) + bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags) + + return (anchors, labels, label_weights, bbox_targets, bbox_weights, + pos_inds, neg_inds, sampling_result) + + def get_num_level_anchors_inside(self, num_level_anchors: List[int], + inside_flags: Tensor) -> List[int]: + """Get the number of valid anchors in every level.""" + + split_inside_flags = torch.split(inside_flags, num_level_anchors) + num_level_anchors_inside = [ + int(flags.sum()) for flags in split_inside_flags + ] + return num_level_anchors_inside diff --git a/mmdetection/mmdet/models/dense_heads/grounding_dino_head.py b/mmdetection/mmdet/models/dense_heads/grounding_dino_head.py new file mode 100644 index 00000000..80883225 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/grounding_dino_head.py @@ -0,0 +1,774 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import math +from typing import Dict, List, Optional, Tuple, Union + +import torch +import torch.nn as nn +from mmcv.cnn import Linear +from mmengine.model import constant_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.losses import QualityFocalLoss +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox_cxcywh_to_xyxy, bbox_xyxy_to_cxcywh +from mmdet.utils import InstanceList, reduce_mean +from ..layers import inverse_sigmoid +from .atss_vlfusion_head import convert_grounding_to_cls_scores +from .dino_head import DINOHead + + +class ContrastiveEmbed(nn.Module): + """text visual ContrastiveEmbed layer. + + Args: + max_text_len (int, optional): Maximum length of text. + log_scale (Optional[Union[str, float]]): The initial value of a + learnable parameter to multiply with the similarity + matrix to normalize the output. Defaults to 0.0. + - If set to 'auto', the similarity matrix will be normalized by + a fixed value ``sqrt(d_c)`` where ``d_c`` is the channel number. + - If set to 'none' or ``None``, there is no normalization applied. + - If set to a float number, the similarity matrix will be multiplied + by ``exp(log_scale)``, where ``log_scale`` is learnable. + bias (bool, optional): Whether to add bias to the output. + If set to ``True``, a learnable bias that is initialized as -4.6 + will be added to the output. Useful when training from scratch. + Defaults to False. + """ + + def __init__(self, + max_text_len: int = 256, + log_scale: Optional[Union[str, float]] = None, + bias: bool = False): + super().__init__() + self.max_text_len = max_text_len + self.log_scale = log_scale + if isinstance(log_scale, float): + self.log_scale = nn.Parameter( + torch.Tensor([float(log_scale)]), requires_grad=True) + elif log_scale not in ['auto', 'none', None]: + raise ValueError(f'log_scale should be one of ' + f'"auto", "none", None, but got {log_scale}') + + self.bias = None + if bias: + bias_value = -math.log((1 - 0.01) / 0.01) + self.bias = nn.Parameter( + torch.Tensor([bias_value]), requires_grad=True) + + def forward(self, visual_feat: Tensor, text_feat: Tensor, + text_token_mask: Tensor) -> Tensor: + """Forward function. + + Args: + visual_feat (Tensor): Visual features. + text_feat (Tensor): Text features. + text_token_mask (Tensor): A mask used for text feats. + + Returns: + Tensor: Classification score. + """ + res = visual_feat @ text_feat.transpose(-1, -2) + if isinstance(self.log_scale, nn.Parameter): + res = res * self.log_scale.exp() + elif self.log_scale == 'auto': + # NOTE: similar to the normalizer in self-attention + res = res / math.sqrt(visual_feat.shape[-1]) + if self.bias is not None: + res = res + self.bias + res.masked_fill_(~text_token_mask[:, None, :], float('-inf')) + + new_res = torch.full((*res.shape[:-1], self.max_text_len), + float('-inf'), + device=res.device) + new_res[..., :res.shape[-1]] = res + + return new_res + + +@MODELS.register_module() +class GroundingDINOHead(DINOHead): + """Head of the Grounding DINO: Marrying DINO with Grounded Pre-Training for + Open-Set Object Detection. + + Args: + contrastive_cfg (dict, optional): Contrastive config that contains + keys like ``max_text_len``. Defaults to dict(max_text_len=256). + """ + + def __init__(self, contrastive_cfg=dict(max_text_len=256), **kwargs): + self.contrastive_cfg = contrastive_cfg + self.max_text_len = contrastive_cfg.get('max_text_len', 256) + super().__init__(**kwargs) + + def _init_layers(self) -> None: + """Initialize classification branch and regression branch of head.""" + fc_cls = ContrastiveEmbed(**self.contrastive_cfg) + reg_branch = [] + for _ in range(self.num_reg_fcs): + reg_branch.append(Linear(self.embed_dims, self.embed_dims)) + reg_branch.append(nn.ReLU()) + reg_branch.append(Linear(self.embed_dims, 4)) + reg_branch = nn.Sequential(*reg_branch) + + # NOTE: due to the fc_cls is a contrastive embedding and don't + # have any trainable parameters,we do not need to copy it. + if self.share_pred_layer: + self.cls_branches = nn.ModuleList( + [fc_cls for _ in range(self.num_pred_layer)]) + self.reg_branches = nn.ModuleList( + [reg_branch for _ in range(self.num_pred_layer)]) + else: + self.cls_branches = nn.ModuleList( + [copy.deepcopy(fc_cls) for _ in range(self.num_pred_layer)]) + self.reg_branches = nn.ModuleList([ + copy.deepcopy(reg_branch) for _ in range(self.num_pred_layer) + ]) + + def init_weights(self) -> None: + """Initialize weights of the Deformable DETR head.""" + for m in self.reg_branches: + constant_init(m[-1], 0, bias=0) + nn.init.constant_(self.reg_branches[0][-1].bias.data[2:], -2.0) + if self.as_two_stage: + for m in self.reg_branches: + nn.init.constant_(m[-1].bias.data[2:], 0.0) + + def _get_targets_single(self, cls_score: Tensor, bbox_pred: Tensor, + gt_instances: InstanceData, + img_meta: dict) -> tuple: + """Compute regression and classification targets for one image. + + Outputs from a single decoder layer of a single feature level are used. + + Args: + cls_score (Tensor): Box score logits from a single decoder layer + for one image. Shape [num_queries, cls_out_channels]. + bbox_pred (Tensor): Sigmoid outputs from a single decoder layer + for one image, with normalized coordinate (cx, cy, w, h) and + shape [num_queries, 4]. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for one image. + + Returns: + tuple[Tensor]: a tuple containing the following for one image. + + - labels (Tensor): Labels of each image. + - label_weights (Tensor]): Label weights of each image. + - bbox_targets (Tensor): BBox targets of each image. + - bbox_weights (Tensor): BBox weights of each image. + - pos_inds (Tensor): Sampled positive indices for each image. + - neg_inds (Tensor): Sampled negative indices for each image. + """ + img_h, img_w = img_meta['img_shape'] + factor = bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0) + num_bboxes = bbox_pred.size(0) + # convert bbox_pred from xywh, normalized to xyxy, unnormalized + bbox_pred = bbox_cxcywh_to_xyxy(bbox_pred) + bbox_pred = bbox_pred * factor + + pred_instances = InstanceData(scores=cls_score, bboxes=bbox_pred) + # assigner and sampler + assign_result = self.assigner.assign( + pred_instances=pred_instances, + gt_instances=gt_instances, + img_meta=img_meta) + gt_bboxes = gt_instances.bboxes + + pos_inds = torch.nonzero( + assign_result.gt_inds > 0, as_tuple=False).squeeze(-1).unique() + neg_inds = torch.nonzero( + assign_result.gt_inds == 0, as_tuple=False).squeeze(-1).unique() + pos_assigned_gt_inds = assign_result.gt_inds[pos_inds] - 1 + pos_gt_bboxes = gt_bboxes[pos_assigned_gt_inds.long(), :] + + # Major changes. The labels are 0-1 binary labels for each bbox + # and text tokens. + labels = gt_bboxes.new_full((num_bboxes, self.max_text_len), + 0, + dtype=torch.float32) + labels[pos_inds] = gt_instances.positive_maps[pos_assigned_gt_inds] + label_weights = gt_bboxes.new_ones(num_bboxes) + + # bbox targets + bbox_targets = torch.zeros_like(bbox_pred, dtype=gt_bboxes.dtype) + bbox_weights = torch.zeros_like(bbox_pred, dtype=gt_bboxes.dtype) + bbox_weights[pos_inds] = 1.0 + + # DETR regress the relative position of boxes (cxcywh) in the image. + # Thus the learning target should be normalized by the image size, also + # the box format should be converted from defaultly x1y1x2y2 to cxcywh. + pos_gt_bboxes_normalized = pos_gt_bboxes / factor + pos_gt_bboxes_targets = bbox_xyxy_to_cxcywh(pos_gt_bboxes_normalized) + bbox_targets[pos_inds] = pos_gt_bboxes_targets + return (labels, label_weights, bbox_targets, bbox_weights, pos_inds, + neg_inds) + + def forward( + self, + hidden_states: Tensor, + references: List[Tensor], + memory_text: Tensor, + text_token_mask: Tensor, + ) -> Tuple[Tensor]: + """Forward function. + + Args: + hidden_states (Tensor): Hidden states output from each decoder + layer, has shape (num_decoder_layers, bs, num_queries, dim). + references (List[Tensor]): List of the reference from the decoder. + The first reference is the `init_reference` (initial) and the + other num_decoder_layers(6) references are `inter_references` + (intermediate). The `init_reference` has shape (bs, + num_queries, 4) when `as_two_stage` of the detector is `True`, + otherwise (bs, num_queries, 2). Each `inter_reference` has + shape (bs, num_queries, 4) when `with_box_refine` of the + detector is `True`, otherwise (bs, num_queries, 2). The + coordinates are arranged as (cx, cy) when the last dimension is + 2, and (cx, cy, w, h) when it is 4. + memory_text (Tensor): Memory text. It has shape (bs, len_text, + text_embed_dims). + text_token_mask (Tensor): Text token mask. It has shape (bs, + len_text). + + Returns: + tuple[Tensor]: results of head containing the following tensor. + + - all_layers_outputs_classes (Tensor): Outputs from the + classification head, has shape (num_decoder_layers, bs, + num_queries, cls_out_channels). + - all_layers_outputs_coords (Tensor): Sigmoid outputs from the + regression head with normalized coordinate format (cx, cy, w, + h), has shape (num_decoder_layers, bs, num_queries, 4) with the + last dimension arranged as (cx, cy, w, h). + """ + all_layers_outputs_classes = [] + all_layers_outputs_coords = [] + + for layer_id in range(hidden_states.shape[0]): + reference = inverse_sigmoid(references[layer_id]) + # NOTE The last reference will not be used. + hidden_state = hidden_states[layer_id] + outputs_class = self.cls_branches[layer_id](hidden_state, + memory_text, + text_token_mask) + tmp_reg_preds = self.reg_branches[layer_id](hidden_state) + if reference.shape[-1] == 4: + # When `layer` is 0 and `as_two_stage` of the detector + # is `True`, or when `layer` is greater than 0 and + # `with_box_refine` of the detector is `True`. + tmp_reg_preds += reference + else: + # When `layer` is 0 and `as_two_stage` of the detector + # is `False`, or when `layer` is greater than 0 and + # `with_box_refine` of the detector is `False`. + assert reference.shape[-1] == 2 + tmp_reg_preds[..., :2] += reference + outputs_coord = tmp_reg_preds.sigmoid() + all_layers_outputs_classes.append(outputs_class) + all_layers_outputs_coords.append(outputs_coord) + + all_layers_outputs_classes = torch.stack(all_layers_outputs_classes) + all_layers_outputs_coords = torch.stack(all_layers_outputs_coords) + + return all_layers_outputs_classes, all_layers_outputs_coords + + def predict(self, + hidden_states: Tensor, + references: List[Tensor], + memory_text: Tensor, + text_token_mask: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> InstanceList: + """Perform forward propagation and loss calculation of the detection + head on the queries of the upstream network. + + Args: + hidden_states (Tensor): Hidden states output from each decoder + layer, has shape (num_decoder_layers, num_queries, bs, dim). + references (List[Tensor]): List of the reference from the decoder. + The first reference is the `init_reference` (initial) and the + other num_decoder_layers(6) references are `inter_references` + (intermediate). The `init_reference` has shape (bs, + num_queries, 4) when `as_two_stage` of the detector is `True`, + otherwise (bs, num_queries, 2). Each `inter_reference` has + shape (bs, num_queries, 4) when `with_box_refine` of the + detector is `True`, otherwise (bs, num_queries, 2). The + coordinates are arranged as (cx, cy) when the last dimension is + 2, and (cx, cy, w, h) when it is 4. + memory_text (Tensor): Memory text. It has shape (bs, len_text, + text_embed_dims). + text_token_mask (Tensor): Text token mask. It has shape (bs, + len_text). + batch_data_samples (SampleList): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): If `True`, return boxes in original + image space. Defaults to `True`. + + Returns: + InstanceList: Detection results of each image + after the post process. + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + batch_token_positive_maps = [ + data_samples.token_positive_map + for data_samples in batch_data_samples + ] + + outs = self(hidden_states, references, memory_text, text_token_mask) + + predictions = self.predict_by_feat( + *outs, + batch_img_metas=batch_img_metas, + batch_token_positive_maps=batch_token_positive_maps, + rescale=rescale) + return predictions + + def predict_by_feat(self, + all_layers_cls_scores: Tensor, + all_layers_bbox_preds: Tensor, + batch_img_metas: List[Dict], + batch_token_positive_maps: Optional[List[dict]] = None, + rescale: bool = False) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Args: + all_layers_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, num_queries, + cls_out_channels). + all_layers_bbox_preds (Tensor): Regression outputs of all decoder + layers. Each is a 4D-tensor with normalized coordinate format + (cx, cy, w, h) and shape (num_decoder_layers, bs, num_queries, + 4) with the last dimension arranged as (cx, cy, w, h). + batch_img_metas (List[Dict]): _description_ + batch_token_positive_maps (list[dict], Optional): Batch token + positive map. Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + cls_scores = all_layers_cls_scores[-1] + bbox_preds = all_layers_bbox_preds[-1] + result_list = [] + for img_id in range(len(batch_img_metas)): + cls_score = cls_scores[img_id] + bbox_pred = bbox_preds[img_id] + img_meta = batch_img_metas[img_id] + token_positive_maps = batch_token_positive_maps[img_id] + results = self._predict_by_feat_single(cls_score, bbox_pred, + token_positive_maps, + img_meta, rescale) + result_list.append(results) + return result_list + + def _predict_by_feat_single(self, + cls_score: Tensor, + bbox_pred: Tensor, + token_positive_maps: dict, + img_meta: dict, + rescale: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score (Tensor): Box score logits from the last decoder layer + for each image. Shape [num_queries, cls_out_channels]. + bbox_pred (Tensor): Sigmoid outputs from the last decoder layer + for each image, with coordinate format (cx, cy, w, h) and + shape [num_queries, 4]. + token_positive_maps (dict): Token positive map. + img_meta (dict): Image meta info. + rescale (bool, optional): If True, return boxes in original image + space. Default True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_score) == len(bbox_pred) # num_queries + max_per_img = self.test_cfg.get('max_per_img', len(cls_score)) + img_shape = img_meta['img_shape'] + + if token_positive_maps is not None: + cls_score = convert_grounding_to_cls_scores( + logits=cls_score.sigmoid()[None], + positive_maps=[token_positive_maps])[0] + scores, indexes = cls_score.view(-1).topk(max_per_img) + num_classes = cls_score.shape[-1] + det_labels = indexes % num_classes + bbox_index = indexes // num_classes + bbox_pred = bbox_pred[bbox_index] + else: + cls_score = cls_score.sigmoid() + scores, _ = cls_score.max(-1) + scores, indexes = scores.topk(max_per_img) + bbox_pred = bbox_pred[indexes] + det_labels = scores.new_zeros(scores.shape, dtype=torch.long) + + det_bboxes = bbox_cxcywh_to_xyxy(bbox_pred) + det_bboxes[:, 0::2] = det_bboxes[:, 0::2] * img_shape[1] + det_bboxes[:, 1::2] = det_bboxes[:, 1::2] * img_shape[0] + det_bboxes[:, 0::2].clamp_(min=0, max=img_shape[1]) + det_bboxes[:, 1::2].clamp_(min=0, max=img_shape[0]) + if rescale: + assert img_meta.get('scale_factor') is not None + det_bboxes /= det_bboxes.new_tensor( + img_meta['scale_factor']).repeat((1, 2)) + results = InstanceData() + results.bboxes = det_bboxes + results.scores = scores + results.labels = det_labels + return results + + def loss(self, hidden_states: Tensor, references: List[Tensor], + memory_text: Tensor, text_token_mask: Tensor, + enc_outputs_class: Tensor, enc_outputs_coord: Tensor, + batch_data_samples: SampleList, dn_meta: Dict[str, int]) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the queries of the upstream network. + + Args: + hidden_states (Tensor): Hidden states output from each decoder + layer, has shape (num_decoder_layers, bs, num_queries_total, + dim), where `num_queries_total` is the sum of + `num_denoising_queries` and `num_matching_queries` when + `self.training` is `True`, else `num_matching_queries`. + references (list[Tensor]): List of the reference from the decoder. + The first reference is the `init_reference` (initial) and the + other num_decoder_layers(6) references are `inter_references` + (intermediate). The `init_reference` has shape (bs, + num_queries_total, 4) and each `inter_reference` has shape + (bs, num_queries, 4) with the last dimension arranged as + (cx, cy, w, h). + memory_text (Tensor): Memory text. It has shape (bs, len_text, + text_embed_dims). + enc_outputs_class (Tensor): The score of each point on encode + feature map, has shape (bs, num_feat_points, cls_out_channels). + enc_outputs_coord (Tensor): The proposal generate from the + encode feature map, has shape (bs, num_feat_points, 4) with the + last dimension arranged as (cx, cy, w, h). + batch_data_samples (list[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + dict: A dictionary of loss components. + """ + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + outs = self(hidden_states, references, memory_text, text_token_mask) + self.text_masks = text_token_mask + loss_inputs = outs + (enc_outputs_class, enc_outputs_coord, + batch_gt_instances, batch_img_metas, dn_meta) + losses = self.loss_by_feat(*loss_inputs) + return losses + + def loss_by_feat_single(self, cls_scores: Tensor, bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict]) -> Tuple[Tensor]: + """Loss function for outputs from a single decoder layer of a single + feature level. + + Args: + cls_scores (Tensor): Box score logits from a single decoder layer + for all images, has shape (bs, num_queries, cls_out_channels). + bbox_preds (Tensor): Sigmoid outputs from a single decoder layer + for all images, with normalized coordinate (cx, cy, w, h) and + shape (bs, num_queries, 4). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + + Returns: + Tuple[Tensor]: A tuple including `loss_cls`, `loss_box` and + `loss_iou`. + """ + num_imgs = cls_scores.size(0) + cls_scores_list = [cls_scores[i] for i in range(num_imgs)] + bbox_preds_list = [bbox_preds[i] for i in range(num_imgs)] + with torch.no_grad(): + cls_reg_targets = self.get_targets(cls_scores_list, + bbox_preds_list, + batch_gt_instances, + batch_img_metas) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + num_total_pos, num_total_neg) = cls_reg_targets + labels = torch.stack(labels_list, 0) + label_weights = torch.stack(label_weights_list, 0) + bbox_targets = torch.cat(bbox_targets_list, 0) + bbox_weights = torch.cat(bbox_weights_list, 0) + + # ===== this change ===== + # Loss is not computed for the padded regions of the text. + assert (self.text_masks.dim() == 2) + text_masks = self.text_masks.new_zeros( + (self.text_masks.size(0), self.max_text_len)) + text_masks[:, :self.text_masks.size(1)] = self.text_masks + text_mask = (text_masks > 0).unsqueeze(1) + text_mask = text_mask.repeat(1, cls_scores.size(1), 1) + cls_scores = torch.masked_select(cls_scores, text_mask).contiguous() + + labels = torch.masked_select(labels, text_mask) + label_weights = label_weights[..., + None].repeat(1, 1, text_mask.size(-1)) + label_weights = torch.masked_select(label_weights, text_mask) + + # classification loss + # construct weighted avg_factor to match with the official DETR repo + cls_avg_factor = num_total_pos * 1.0 + \ + num_total_neg * self.bg_cls_weight + if self.sync_cls_avg_factor: + cls_avg_factor = reduce_mean( + cls_scores.new_tensor([cls_avg_factor])) + cls_avg_factor = max(cls_avg_factor, 1) + + if isinstance(self.loss_cls, QualityFocalLoss): + raise NotImplementedError( + 'QualityFocalLoss for GroundingDINOHead is not supported yet.') + else: + loss_cls = self.loss_cls( + cls_scores, labels, label_weights, avg_factor=cls_avg_factor) + + # Compute the average number of gt boxes across all gpus, for + # normalization purposes + num_total_pos = loss_cls.new_tensor([num_total_pos]) + num_total_pos = torch.clamp(reduce_mean(num_total_pos), min=1).item() + + # construct factors used for rescale bboxes + factors = [] + for img_meta, bbox_pred in zip(batch_img_metas, bbox_preds): + img_h, img_w, = img_meta['img_shape'] + factor = bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0).repeat( + bbox_pred.size(0), 1) + factors.append(factor) + factors = torch.cat(factors, 0) + + # DETR regress the relative position of boxes (cxcywh) in the image, + # thus the learning target is normalized by the image size. So here + # we need to re-scale them for calculating IoU loss + bbox_preds = bbox_preds.reshape(-1, 4) + bboxes = bbox_cxcywh_to_xyxy(bbox_preds) * factors + bboxes_gt = bbox_cxcywh_to_xyxy(bbox_targets) * factors + + # regression IoU loss, defaultly GIoU loss + loss_iou = self.loss_iou( + bboxes, bboxes_gt, bbox_weights, avg_factor=num_total_pos) + + # regression L1 loss + loss_bbox = self.loss_bbox( + bbox_preds, bbox_targets, bbox_weights, avg_factor=num_total_pos) + return loss_cls, loss_bbox, loss_iou + + def _loss_dn_single(self, dn_cls_scores: Tensor, dn_bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + dn_meta: Dict[str, int]) -> Tuple[Tensor]: + """Denoising loss for outputs from a single decoder layer. + + Args: + dn_cls_scores (Tensor): Classification scores of a single decoder + layer in denoising part, has shape (bs, num_denoising_queries, + cls_out_channels). + dn_bbox_preds (Tensor): Regression outputs of a single decoder + layer in denoising part. Each is a 4D-tensor with normalized + coordinate format (cx, cy, w, h) and has shape + (bs, num_denoising_queries, 4). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + Tuple[Tensor]: A tuple including `loss_cls`, `loss_box` and + `loss_iou`. + """ + cls_reg_targets = self.get_dn_targets(batch_gt_instances, + batch_img_metas, dn_meta) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + num_total_pos, num_total_neg) = cls_reg_targets + labels = torch.stack(labels_list, 0) + label_weights = torch.stack(label_weights_list, 0) + bbox_targets = torch.cat(bbox_targets_list, 0) + bbox_weights = torch.cat(bbox_weights_list, 0) + # ===== this change ===== + # Loss is not computed for the padded regions of the text. + assert (self.text_masks.dim() == 2) + text_masks = self.text_masks.new_zeros( + (self.text_masks.size(0), self.max_text_len)) + text_masks[:, :self.text_masks.size(1)] = self.text_masks + text_mask = (text_masks > 0).unsqueeze(1) + text_mask = text_mask.repeat(1, dn_cls_scores.size(1), 1) + cls_scores = torch.masked_select(dn_cls_scores, text_mask).contiguous() + labels = torch.masked_select(labels, text_mask) + label_weights = label_weights[..., + None].repeat(1, 1, text_mask.size(-1)) + label_weights = torch.masked_select(label_weights, text_mask) + # ======================= + + # classification loss + # construct weighted avg_factor to match with the official DETR repo + cls_avg_factor = \ + num_total_pos * 1.0 + num_total_neg * self.bg_cls_weight + if self.sync_cls_avg_factor: + cls_avg_factor = reduce_mean( + cls_scores.new_tensor([cls_avg_factor])) + cls_avg_factor = max(cls_avg_factor, 1) + + if len(cls_scores) > 0: + if isinstance(self.loss_cls, QualityFocalLoss): + raise NotImplementedError('QualityFocalLoss is not supported') + else: + loss_cls = self.loss_cls( + cls_scores, + labels, + label_weights, + avg_factor=cls_avg_factor) + else: + loss_cls = torch.zeros( + 1, dtype=cls_scores.dtype, device=cls_scores.device) + + # Compute the average number of gt boxes across all gpus, for + # normalization purposes + num_total_pos = loss_cls.new_tensor([num_total_pos]) + num_total_pos = torch.clamp(reduce_mean(num_total_pos), min=1).item() + + # construct factors used for rescale bboxes + factors = [] + for img_meta, bbox_pred in zip(batch_img_metas, dn_bbox_preds): + img_h, img_w = img_meta['img_shape'] + factor = bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0).repeat( + bbox_pred.size(0), 1) + factors.append(factor) + factors = torch.cat(factors) + + # DETR regress the relative position of boxes (cxcywh) in the image, + # thus the learning target is normalized by the image size. So here + # we need to re-scale them for calculating IoU loss + bbox_preds = dn_bbox_preds.reshape(-1, 4) + bboxes = bbox_cxcywh_to_xyxy(bbox_preds) * factors + bboxes_gt = bbox_cxcywh_to_xyxy(bbox_targets) * factors + + # regression IoU loss, defaultly GIoU loss + loss_iou = self.loss_iou( + bboxes, bboxes_gt, bbox_weights, avg_factor=num_total_pos) + + # regression L1 loss + loss_bbox = self.loss_bbox( + bbox_preds, bbox_targets, bbox_weights, avg_factor=num_total_pos) + return loss_cls, loss_bbox, loss_iou + + def _get_dn_targets_single(self, gt_instances: InstanceData, + img_meta: dict, dn_meta: Dict[str, + int]) -> tuple: + """Get targets in denoising part for one image. + + Args: + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for one image. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + tuple[Tensor]: a tuple containing the following for one image. + + - labels (Tensor): Labels of each image. + - label_weights (Tensor]): Label weights of each image. + - bbox_targets (Tensor): BBox targets of each image. + - bbox_weights (Tensor): BBox weights of each image. + - pos_inds (Tensor): Sampled positive indices for each image. + - neg_inds (Tensor): Sampled negative indices for each image. + """ + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + num_groups = dn_meta['num_denoising_groups'] + num_denoising_queries = dn_meta['num_denoising_queries'] + num_queries_each_group = int(num_denoising_queries / num_groups) + device = gt_bboxes.device + + if len(gt_labels) > 0: + t = torch.arange(len(gt_labels), dtype=torch.long, device=device) + t = t.unsqueeze(0).repeat(num_groups, 1) + pos_assigned_gt_inds = t.flatten() + pos_inds = torch.arange( + num_groups, dtype=torch.long, device=device) + pos_inds = pos_inds.unsqueeze(1) * num_queries_each_group + t + pos_inds = pos_inds.flatten() + else: + pos_inds = pos_assigned_gt_inds = \ + gt_bboxes.new_tensor([], dtype=torch.long) + + neg_inds = pos_inds + num_queries_each_group // 2 + # label targets + # this change + labels = gt_bboxes.new_full((num_denoising_queries, self.max_text_len), + 0, + dtype=torch.float32) + labels[pos_inds] = gt_instances.positive_maps[pos_assigned_gt_inds] + label_weights = gt_bboxes.new_ones(num_denoising_queries) + + # bbox targets + bbox_targets = torch.zeros(num_denoising_queries, 4, device=device) + bbox_weights = torch.zeros(num_denoising_queries, 4, device=device) + bbox_weights[pos_inds] = 1.0 + img_h, img_w = img_meta['img_shape'] + + # DETR regress the relative position of boxes (cxcywh) in the image. + # Thus the learning target should be normalized by the image size, also + # the box format should be converted from defaultly x1y1x2y2 to cxcywh. + factor = gt_bboxes.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0) + gt_bboxes_normalized = gt_bboxes / factor + gt_bboxes_targets = bbox_xyxy_to_cxcywh(gt_bboxes_normalized) + bbox_targets[pos_inds] = gt_bboxes_targets.repeat([num_groups, 1]) + + return (labels, label_weights, bbox_targets, bbox_weights, pos_inds, + neg_inds) diff --git a/mmdetection/mmdet/models/dense_heads/guided_anchor_head.py b/mmdetection/mmdet/models/dense_heads/guided_anchor_head.py new file mode 100644 index 00000000..59f6dd33 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/guided_anchor_head.py @@ -0,0 +1,994 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +from mmcv.ops import DeformConv2d, MaskedConv2d +from mmengine.model import BaseModule +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, OptConfigType, + OptInstanceList) +from ..layers import multiclass_nms +from ..task_modules.prior_generators import anchor_inside_flags, calc_region +from ..task_modules.samplers import PseudoSampler +from ..utils import images_to_levels, multi_apply, unmap +from .anchor_head import AnchorHead + + +class FeatureAdaption(BaseModule): + """Feature Adaption Module. + + Feature Adaption Module is implemented based on DCN v1. + It uses anchor shape prediction rather than feature map to + predict offsets of deform conv layer. + + Args: + in_channels (int): Number of channels in the input feature map. + out_channels (int): Number of channels in the output feature map. + kernel_size (int): Deformable conv kernel size. Defaults to 3. + deform_groups (int): Deformable conv group size. Defaults to 4. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or \ + list[dict], optional): Initialization config dict. + """ + + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: int = 3, + deform_groups: int = 4, + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.1, + override=dict(type='Normal', name='conv_adaption', std=0.01)) + ) -> None: + super().__init__(init_cfg=init_cfg) + offset_channels = kernel_size * kernel_size * 2 + self.conv_offset = nn.Conv2d( + 2, deform_groups * offset_channels, 1, bias=False) + self.conv_adaption = DeformConv2d( + in_channels, + out_channels, + kernel_size=kernel_size, + padding=(kernel_size - 1) // 2, + deform_groups=deform_groups) + self.relu = nn.ReLU(inplace=True) + + def forward(self, x: Tensor, shape: Tensor) -> Tensor: + offset = self.conv_offset(shape.detach()) + x = self.relu(self.conv_adaption(x, offset)) + return x + + +@MODELS.register_module() +class GuidedAnchorHead(AnchorHead): + """Guided-Anchor-based head (GA-RPN, GA-RetinaNet, etc.). + + This GuidedAnchorHead will predict high-quality feature guided + anchors and locations where anchors will be kept in inference. + There are mainly 3 categories of bounding-boxes. + + - Sampled 9 pairs for target assignment. (approxes) + - The square boxes where the predicted anchors are based on. (squares) + - Guided anchors. + + Please refer to https://arxiv.org/abs/1901.03278 for more details. + + Args: + num_classes (int): Number of classes. + in_channels (int): Number of channels in the input feature map. + feat_channels (int): Number of hidden channels. Defaults to 256. + approx_anchor_generator (:obj:`ConfigDict` or dict): Config dict + for approx generator + square_anchor_generator (:obj:`ConfigDict` or dict): Config dict + for square generator + anchor_coder (:obj:`ConfigDict` or dict): Config dict for anchor coder + bbox_coder (:obj:`ConfigDict` or dict): Config dict for bbox coder + reg_decoded_bbox (bool): If true, the regression loss would be + applied directly on decoded bounding boxes, converting both + the predicted boxes and regression targets to absolute + coordinates format. Defaults to False. It should be `True` when + using `IoULoss`, `GIoULoss`, or `DIoULoss` in the bbox head. + deform_groups: (int): Group number of DCN in FeatureAdaption module. + Defaults to 4. + loc_filter_thr (float): Threshold to filter out unconcerned regions. + Defaults to 0.01. + loss_loc (:obj:`ConfigDict` or dict): Config of location loss. + loss_shape (:obj:`ConfigDict` or dict): Config of anchor shape loss. + loss_cls (:obj:`ConfigDict` or dict): Config of classification loss. + loss_bbox (:obj:`ConfigDict` or dict): Config of bbox regression loss. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or \ + list[dict], optional): Initialization config dict. + """ + + def __init__( + self, + num_classes: int, + in_channels: int, + feat_channels: int = 256, + approx_anchor_generator: ConfigType = dict( + type='AnchorGenerator', + octave_base_scale=8, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64]), + square_anchor_generator: ConfigType = dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[8], + strides=[4, 8, 16, 32, 64]), + anchor_coder: ConfigType = dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + bbox_coder: ConfigType = dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + reg_decoded_bbox: bool = False, + deform_groups: int = 4, + loc_filter_thr: float = 0.01, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + loss_loc: ConfigType = dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_shape: ConfigType = dict( + type='BoundedIoULoss', beta=0.2, loss_weight=1.0), + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + loss_bbox: ConfigType = dict( + type='SmoothL1Loss', beta=1.0, loss_weight=1.0), + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', name='conv_loc', std=0.01, lbias_prob=0.01)) + ) -> None: + super(AnchorHead, self).__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.num_classes = num_classes + self.feat_channels = feat_channels + self.deform_groups = deform_groups + self.loc_filter_thr = loc_filter_thr + + # build approx_anchor_generator and square_anchor_generator + assert (approx_anchor_generator['octave_base_scale'] == + square_anchor_generator['scales'][0]) + assert (approx_anchor_generator['strides'] == + square_anchor_generator['strides']) + self.approx_anchor_generator = TASK_UTILS.build( + approx_anchor_generator) + self.square_anchor_generator = TASK_UTILS.build( + square_anchor_generator) + self.approxs_per_octave = self.approx_anchor_generator \ + .num_base_priors[0] + + self.reg_decoded_bbox = reg_decoded_bbox + + # one anchor per location + self.num_base_priors = self.square_anchor_generator.num_base_priors[0] + + self.use_sigmoid_cls = loss_cls.get('use_sigmoid', False) + self.loc_focal_loss = loss_loc['type'] in ['FocalLoss'] + if self.use_sigmoid_cls: + self.cls_out_channels = self.num_classes + else: + self.cls_out_channels = self.num_classes + 1 + + # build bbox_coder + self.anchor_coder = TASK_UTILS.build(anchor_coder) + self.bbox_coder = TASK_UTILS.build(bbox_coder) + + # build losses + self.loss_loc = MODELS.build(loss_loc) + self.loss_shape = MODELS.build(loss_shape) + self.loss_cls = MODELS.build(loss_cls) + self.loss_bbox = MODELS.build(loss_bbox) + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + # use PseudoSampler when no sampler in train_cfg + if train_cfg.get('sampler', None) is not None: + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], default_args=dict(context=self)) + else: + self.sampler = PseudoSampler() + + self.ga_assigner = TASK_UTILS.build(self.train_cfg['ga_assigner']) + if train_cfg.get('ga_sampler', None) is not None: + self.ga_sampler = TASK_UTILS.build( + self.train_cfg['ga_sampler'], + default_args=dict(context=self)) + else: + self.ga_sampler = PseudoSampler() + + self._init_layers() + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.relu = nn.ReLU(inplace=True) + self.conv_loc = nn.Conv2d(self.in_channels, 1, 1) + self.conv_shape = nn.Conv2d(self.in_channels, self.num_base_priors * 2, + 1) + self.feature_adaption = FeatureAdaption( + self.in_channels, + self.feat_channels, + kernel_size=3, + deform_groups=self.deform_groups) + self.conv_cls = MaskedConv2d( + self.feat_channels, self.num_base_priors * self.cls_out_channels, + 1) + self.conv_reg = MaskedConv2d(self.feat_channels, + self.num_base_priors * 4, 1) + + def forward_single(self, x: Tensor) -> Tuple[Tensor]: + """Forward feature of a single scale level.""" + loc_pred = self.conv_loc(x) + shape_pred = self.conv_shape(x) + x = self.feature_adaption(x, shape_pred) + # masked conv is only used during inference for speed-up + if not self.training: + mask = loc_pred.sigmoid()[0] >= self.loc_filter_thr + else: + mask = None + cls_score = self.conv_cls(x, mask) + bbox_pred = self.conv_reg(x, mask) + return cls_score, bbox_pred, shape_pred, loc_pred + + def forward(self, x: List[Tensor]) -> Tuple[List[Tensor]]: + """Forward features from the upstream network.""" + return multi_apply(self.forward_single, x) + + def get_sampled_approxs(self, + featmap_sizes: List[Tuple[int, int]], + batch_img_metas: List[dict], + device: str = 'cuda') -> tuple: + """Get sampled approxs and inside flags according to feature map sizes. + + Args: + featmap_sizes (list[tuple]): Multi-level feature map sizes. + batch_img_metas (list[dict]): Image meta info. + device (str): device for returned tensors + + Returns: + tuple: approxes of each image, inside flags of each image + """ + num_imgs = len(batch_img_metas) + + # since feature map sizes of all images are the same, we only compute + # approxes for one time + multi_level_approxs = self.approx_anchor_generator.grid_priors( + featmap_sizes, device=device) + approxs_list = [multi_level_approxs for _ in range(num_imgs)] + + # for each image, we compute inside flags of multi level approxes + inside_flag_list = [] + for img_id, img_meta in enumerate(batch_img_metas): + multi_level_flags = [] + multi_level_approxs = approxs_list[img_id] + + # obtain valid flags for each approx first + multi_level_approx_flags = self.approx_anchor_generator \ + .valid_flags(featmap_sizes, + img_meta['pad_shape'], + device=device) + + for i, flags in enumerate(multi_level_approx_flags): + approxs = multi_level_approxs[i] + inside_flags_list = [] + for j in range(self.approxs_per_octave): + split_valid_flags = flags[j::self.approxs_per_octave] + split_approxs = approxs[j::self.approxs_per_octave, :] + inside_flags = anchor_inside_flags( + split_approxs, split_valid_flags, + img_meta['img_shape'][:2], + self.train_cfg['allowed_border']) + inside_flags_list.append(inside_flags) + # inside_flag for a position is true if any anchor in this + # position is true + inside_flags = ( + torch.stack(inside_flags_list, 0).sum(dim=0) > 0) + multi_level_flags.append(inside_flags) + inside_flag_list.append(multi_level_flags) + return approxs_list, inside_flag_list + + def get_anchors(self, + featmap_sizes: List[Tuple[int, int]], + shape_preds: List[Tensor], + loc_preds: List[Tensor], + batch_img_metas: List[dict], + use_loc_filter: bool = False, + device: str = 'cuda') -> tuple: + """Get squares according to feature map sizes and guided anchors. + + Args: + featmap_sizes (list[tuple]): Multi-level feature map sizes. + shape_preds (list[tensor]): Multi-level shape predictions. + loc_preds (list[tensor]): Multi-level location predictions. + batch_img_metas (list[dict]): Image meta info. + use_loc_filter (bool): Use loc filter or not. Defaults to False + device (str): device for returned tensors. + Defaults to `cuda`. + + Returns: + tuple: square approxs of each image, guided anchors of each image, + loc masks of each image. + """ + num_imgs = len(batch_img_metas) + num_levels = len(featmap_sizes) + + # since feature map sizes of all images are the same, we only compute + # squares for one time + multi_level_squares = self.square_anchor_generator.grid_priors( + featmap_sizes, device=device) + squares_list = [multi_level_squares for _ in range(num_imgs)] + + # for each image, we compute multi level guided anchors + guided_anchors_list = [] + loc_mask_list = [] + for img_id, img_meta in enumerate(batch_img_metas): + multi_level_guided_anchors = [] + multi_level_loc_mask = [] + for i in range(num_levels): + squares = squares_list[img_id][i] + shape_pred = shape_preds[i][img_id] + loc_pred = loc_preds[i][img_id] + guided_anchors, loc_mask = self._get_guided_anchors_single( + squares, + shape_pred, + loc_pred, + use_loc_filter=use_loc_filter) + multi_level_guided_anchors.append(guided_anchors) + multi_level_loc_mask.append(loc_mask) + guided_anchors_list.append(multi_level_guided_anchors) + loc_mask_list.append(multi_level_loc_mask) + return squares_list, guided_anchors_list, loc_mask_list + + def _get_guided_anchors_single( + self, + squares: Tensor, + shape_pred: Tensor, + loc_pred: Tensor, + use_loc_filter: bool = False) -> Tuple[Tensor]: + """Get guided anchors and loc masks for a single level. + + Args: + squares (tensor): Squares of a single level. + shape_pred (tensor): Shape predictions of a single level. + loc_pred (tensor): Loc predictions of a single level. + use_loc_filter (list[tensor]): Use loc filter or not. + Defaults to False. + + Returns: + tuple: guided anchors, location masks + """ + # calculate location filtering mask + loc_pred = loc_pred.sigmoid().detach() + if use_loc_filter: + loc_mask = loc_pred >= self.loc_filter_thr + else: + loc_mask = loc_pred >= 0.0 + mask = loc_mask.permute(1, 2, 0).expand(-1, -1, self.num_base_priors) + mask = mask.contiguous().view(-1) + # calculate guided anchors + squares = squares[mask] + anchor_deltas = shape_pred.permute(1, 2, 0).contiguous().view( + -1, 2).detach()[mask] + bbox_deltas = anchor_deltas.new_full(squares.size(), 0) + bbox_deltas[:, 2:] = anchor_deltas + guided_anchors = self.anchor_coder.decode( + squares, bbox_deltas, wh_ratio_clip=1e-6) + return guided_anchors, mask + + def ga_loc_targets(self, batch_gt_instances: InstanceList, + featmap_sizes: List[Tuple[int, int]]) -> tuple: + """Compute location targets for guided anchoring. + + Each feature map is divided into positive, negative and ignore regions. + - positive regions: target 1, weight 1 + - ignore regions: target 0, weight 0 + - negative regions: target 0, weight 0.1 + + Args: + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + featmap_sizes (list[tuple]): Multi level sizes of each feature + maps. + + Returns: + tuple: Returns a tuple containing location targets. + """ + anchor_scale = self.approx_anchor_generator.octave_base_scale + anchor_strides = self.approx_anchor_generator.strides + # Currently only supports same stride in x and y direction. + for stride in anchor_strides: + assert (stride[0] == stride[1]) + anchor_strides = [stride[0] for stride in anchor_strides] + + center_ratio = self.train_cfg['center_ratio'] + ignore_ratio = self.train_cfg['ignore_ratio'] + img_per_gpu = len(batch_gt_instances) + num_lvls = len(featmap_sizes) + r1 = (1 - center_ratio) / 2 + r2 = (1 - ignore_ratio) / 2 + all_loc_targets = [] + all_loc_weights = [] + all_ignore_map = [] + for lvl_id in range(num_lvls): + h, w = featmap_sizes[lvl_id] + loc_targets = torch.zeros( + img_per_gpu, + 1, + h, + w, + device=batch_gt_instances[0].bboxes.device, + dtype=torch.float32) + loc_weights = torch.full_like(loc_targets, -1) + ignore_map = torch.zeros_like(loc_targets) + all_loc_targets.append(loc_targets) + all_loc_weights.append(loc_weights) + all_ignore_map.append(ignore_map) + for img_id in range(img_per_gpu): + gt_bboxes = batch_gt_instances[img_id].bboxes + scale = torch.sqrt((gt_bboxes[:, 2] - gt_bboxes[:, 0]) * + (gt_bboxes[:, 3] - gt_bboxes[:, 1])) + min_anchor_size = scale.new_full( + (1, ), float(anchor_scale * anchor_strides[0])) + # assign gt bboxes to different feature levels w.r.t. their scales + target_lvls = torch.floor( + torch.log2(scale) - torch.log2(min_anchor_size) + 0.5) + target_lvls = target_lvls.clamp(min=0, max=num_lvls - 1).long() + for gt_id in range(gt_bboxes.size(0)): + lvl = target_lvls[gt_id].item() + # rescaled to corresponding feature map + gt_ = gt_bboxes[gt_id, :4] / anchor_strides[lvl] + # calculate ignore regions + ignore_x1, ignore_y1, ignore_x2, ignore_y2 = calc_region( + gt_, r2, featmap_sizes[lvl]) + # calculate positive (center) regions + ctr_x1, ctr_y1, ctr_x2, ctr_y2 = calc_region( + gt_, r1, featmap_sizes[lvl]) + all_loc_targets[lvl][img_id, 0, ctr_y1:ctr_y2 + 1, + ctr_x1:ctr_x2 + 1] = 1 + all_loc_weights[lvl][img_id, 0, ignore_y1:ignore_y2 + 1, + ignore_x1:ignore_x2 + 1] = 0 + all_loc_weights[lvl][img_id, 0, ctr_y1:ctr_y2 + 1, + ctr_x1:ctr_x2 + 1] = 1 + # calculate ignore map on nearby low level feature + if lvl > 0: + d_lvl = lvl - 1 + # rescaled to corresponding feature map + gt_ = gt_bboxes[gt_id, :4] / anchor_strides[d_lvl] + ignore_x1, ignore_y1, ignore_x2, ignore_y2 = calc_region( + gt_, r2, featmap_sizes[d_lvl]) + all_ignore_map[d_lvl][img_id, 0, ignore_y1:ignore_y2 + 1, + ignore_x1:ignore_x2 + 1] = 1 + # calculate ignore map on nearby high level feature + if lvl < num_lvls - 1: + u_lvl = lvl + 1 + # rescaled to corresponding feature map + gt_ = gt_bboxes[gt_id, :4] / anchor_strides[u_lvl] + ignore_x1, ignore_y1, ignore_x2, ignore_y2 = calc_region( + gt_, r2, featmap_sizes[u_lvl]) + all_ignore_map[u_lvl][img_id, 0, ignore_y1:ignore_y2 + 1, + ignore_x1:ignore_x2 + 1] = 1 + for lvl_id in range(num_lvls): + # ignore negative regions w.r.t. ignore map + all_loc_weights[lvl_id][(all_loc_weights[lvl_id] < 0) + & (all_ignore_map[lvl_id] > 0)] = 0 + # set negative regions with weight 0.1 + all_loc_weights[lvl_id][all_loc_weights[lvl_id] < 0] = 0.1 + # loc average factor to balance loss + loc_avg_factor = sum( + [t.size(0) * t.size(-1) * t.size(-2) + for t in all_loc_targets]) / 200 + return all_loc_targets, all_loc_weights, loc_avg_factor + + def _ga_shape_target_single(self, + flat_approxs: Tensor, + inside_flags: Tensor, + flat_squares: Tensor, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData], + img_meta: dict, + unmap_outputs: bool = True) -> tuple: + """Compute guided anchoring targets. + + This function returns sampled anchors and gt bboxes directly + rather than calculates regression targets. + + Args: + flat_approxs (Tensor): flat approxs of a single image, + shape (n, 4) + inside_flags (Tensor): inside flags of a single image, + shape (n, ). + flat_squares (Tensor): flat squares of a single image, + shape (approxs_per_octave * n, 4) + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + img_meta (dict): Meta info of a single image. + unmap_outputs (bool): unmap outputs or not. + + Returns: + tuple: Returns a tuple containing shape targets of each image. + """ + if not inside_flags.any(): + raise ValueError( + 'There is no valid anchor inside the image boundary. Please ' + 'check the image size and anchor sizes, or set ' + '``allowed_border`` to -1 to skip the condition.') + # assign gt and sample anchors + num_square = flat_squares.size(0) + approxs = flat_approxs.view(num_square, self.approxs_per_octave, 4) + approxs = approxs[inside_flags, ...] + squares = flat_squares[inside_flags, :] + + pred_instances = InstanceData() + pred_instances.priors = squares + pred_instances.approxs = approxs + + assign_result = self.ga_assigner.assign( + pred_instances=pred_instances, + gt_instances=gt_instances, + gt_instances_ignore=gt_instances_ignore) + sampling_result = self.ga_sampler.sample( + assign_result=assign_result, + pred_instances=pred_instances, + gt_instances=gt_instances) + + bbox_anchors = torch.zeros_like(squares) + bbox_gts = torch.zeros_like(squares) + bbox_weights = torch.zeros_like(squares) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + bbox_anchors[pos_inds, :] = sampling_result.pos_bboxes + bbox_gts[pos_inds, :] = sampling_result.pos_gt_bboxes + bbox_weights[pos_inds, :] = 1.0 + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_squares.size(0) + bbox_anchors = unmap(bbox_anchors, num_total_anchors, inside_flags) + bbox_gts = unmap(bbox_gts, num_total_anchors, inside_flags) + bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags) + + return (bbox_anchors, bbox_gts, bbox_weights, pos_inds, neg_inds, + sampling_result) + + def ga_shape_targets(self, + approx_list: List[List[Tensor]], + inside_flag_list: List[List[Tensor]], + square_list: List[List[Tensor]], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs: bool = True) -> tuple: + """Compute guided anchoring targets. + + Args: + approx_list (list[list[Tensor]]): Multi level approxs of each + image. + inside_flag_list (list[list[Tensor]]): Multi level inside flags + of each image. + square_list (list[list[Tensor]]): Multi level squares of each + image. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): unmap outputs or not. Defaults to None. + + Returns: + tuple: Returns a tuple containing shape targets. + """ + num_imgs = len(batch_img_metas) + assert len(approx_list) == len(inside_flag_list) == len( + square_list) == num_imgs + # anchor number of multi levels + num_level_squares = [squares.size(0) for squares in square_list[0]] + # concat all level anchors and flags to a single tensor + inside_flag_flat_list = [] + approx_flat_list = [] + square_flat_list = [] + for i in range(num_imgs): + assert len(square_list[i]) == len(inside_flag_list[i]) + inside_flag_flat_list.append(torch.cat(inside_flag_list[i])) + approx_flat_list.append(torch.cat(approx_list[i])) + square_flat_list.append(torch.cat(square_list[i])) + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None for _ in range(num_imgs)] + (all_bbox_anchors, all_bbox_gts, all_bbox_weights, pos_inds_list, + neg_inds_list, sampling_results_list) = multi_apply( + self._ga_shape_target_single, + approx_flat_list, + inside_flag_flat_list, + square_flat_list, + batch_gt_instances, + batch_gt_instances_ignore, + batch_img_metas, + unmap_outputs=unmap_outputs) + # sampled anchors of all images + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + # split targets to a list w.r.t. multiple levels + bbox_anchors_list = images_to_levels(all_bbox_anchors, + num_level_squares) + bbox_gts_list = images_to_levels(all_bbox_gts, num_level_squares) + bbox_weights_list = images_to_levels(all_bbox_weights, + num_level_squares) + return (bbox_anchors_list, bbox_gts_list, bbox_weights_list, + avg_factor) + + def loss_shape_single(self, shape_pred: Tensor, bbox_anchors: Tensor, + bbox_gts: Tensor, anchor_weights: Tensor, + avg_factor: int) -> Tensor: + """Compute shape loss in single level.""" + shape_pred = shape_pred.permute(0, 2, 3, 1).contiguous().view(-1, 2) + bbox_anchors = bbox_anchors.contiguous().view(-1, 4) + bbox_gts = bbox_gts.contiguous().view(-1, 4) + anchor_weights = anchor_weights.contiguous().view(-1, 4) + bbox_deltas = bbox_anchors.new_full(bbox_anchors.size(), 0) + bbox_deltas[:, 2:] += shape_pred + # filter out negative samples to speed-up weighted_bounded_iou_loss + inds = torch.nonzero( + anchor_weights[:, 0] > 0, as_tuple=False).squeeze(1) + bbox_deltas_ = bbox_deltas[inds] + bbox_anchors_ = bbox_anchors[inds] + bbox_gts_ = bbox_gts[inds] + anchor_weights_ = anchor_weights[inds] + pred_anchors_ = self.anchor_coder.decode( + bbox_anchors_, bbox_deltas_, wh_ratio_clip=1e-6) + loss_shape = self.loss_shape( + pred_anchors_, bbox_gts_, anchor_weights_, avg_factor=avg_factor) + return loss_shape + + def loss_loc_single(self, loc_pred: Tensor, loc_target: Tensor, + loc_weight: Tensor, avg_factor: float) -> Tensor: + """Compute location loss in single level.""" + loss_loc = self.loss_loc( + loc_pred.reshape(-1, 1), + loc_target.reshape(-1).long(), + loc_weight.reshape(-1), + avg_factor=avg_factor) + return loss_loc + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + shape_preds: List[Tensor], + loc_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + has shape (N, num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + shape_preds (list[Tensor]): shape predictions for each scale + level with shape (N, 1, H, W). + loc_preds (list[Tensor]): location predictions for each scale + level with shape (N, num_anchors * 2, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: A dictionary of loss components. + """ + + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.approx_anchor_generator.num_levels + + device = cls_scores[0].device + + # get loc targets + loc_targets, loc_weights, loc_avg_factor = self.ga_loc_targets( + batch_gt_instances, featmap_sizes) + + # get sampled approxes + approxs_list, inside_flag_list = self.get_sampled_approxs( + featmap_sizes, batch_img_metas, device=device) + # get squares and guided anchors + squares_list, guided_anchors_list, _ = self.get_anchors( + featmap_sizes, + shape_preds, + loc_preds, + batch_img_metas, + device=device) + + # get shape targets + shape_targets = self.ga_shape_targets(approxs_list, inside_flag_list, + squares_list, batch_gt_instances, + batch_img_metas) + (bbox_anchors_list, bbox_gts_list, anchor_weights_list, + ga_avg_factor) = shape_targets + + # get anchor targets + cls_reg_targets = self.get_targets( + guided_anchors_list, + inside_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + avg_factor) = cls_reg_targets + + # anchor number of multi levels + num_level_anchors = [ + anchors.size(0) for anchors in guided_anchors_list[0] + ] + # concat all level anchors to a single tensor + concat_anchor_list = [] + for i in range(len(guided_anchors_list)): + concat_anchor_list.append(torch.cat(guided_anchors_list[i])) + all_anchor_list = images_to_levels(concat_anchor_list, + num_level_anchors) + + # get classification and bbox regression losses + losses_cls, losses_bbox = multi_apply( + self.loss_by_feat_single, + cls_scores, + bbox_preds, + all_anchor_list, + labels_list, + label_weights_list, + bbox_targets_list, + bbox_weights_list, + avg_factor=avg_factor) + + # get anchor location loss + losses_loc = [] + for i in range(len(loc_preds)): + loss_loc = self.loss_loc_single( + loc_preds[i], + loc_targets[i], + loc_weights[i], + avg_factor=loc_avg_factor) + losses_loc.append(loss_loc) + + # get anchor shape loss + losses_shape = [] + for i in range(len(shape_preds)): + loss_shape = self.loss_shape_single( + shape_preds[i], + bbox_anchors_list[i], + bbox_gts_list[i], + anchor_weights_list[i], + avg_factor=ga_avg_factor) + losses_shape.append(loss_shape) + + return dict( + loss_cls=losses_cls, + loss_bbox=losses_bbox, + loss_shape=losses_shape, + loss_loc=losses_loc) + + def predict_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + shape_preds: List[Tensor], + loc_preds: List[Tensor], + batch_img_metas: List[dict], + cfg: OptConfigType = None, + rescale: bool = False) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Args: + cls_scores (list[Tensor]): Classification scores for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * 4, H, W). + shape_preds (list[Tensor]): shape predictions for each scale + level with shape (N, 1, H, W). + loc_preds (list[Tensor]): location predictions for each scale + level with shape (N, num_anchors * 2, H, W). + batch_img_metas (list[dict], Optional): Batch image meta info. + Defaults to None. + cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), the last + dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_scores) == len(bbox_preds) == len(shape_preds) == len( + loc_preds) + num_levels = len(cls_scores) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + device = cls_scores[0].device + # get guided anchors + _, guided_anchors, loc_masks = self.get_anchors( + featmap_sizes, + shape_preds, + loc_preds, + batch_img_metas, + use_loc_filter=not self.training, + device=device) + result_list = [] + for img_id in range(len(batch_img_metas)): + cls_score_list = [ + cls_scores[i][img_id].detach() for i in range(num_levels) + ] + bbox_pred_list = [ + bbox_preds[i][img_id].detach() for i in range(num_levels) + ] + guided_anchor_list = [ + guided_anchors[img_id][i].detach() for i in range(num_levels) + ] + loc_mask_list = [ + loc_masks[img_id][i].detach() for i in range(num_levels) + ] + proposals = self._predict_by_feat_single( + cls_scores=cls_score_list, + bbox_preds=bbox_pred_list, + mlvl_anchors=guided_anchor_list, + mlvl_masks=loc_mask_list, + img_meta=batch_img_metas[img_id], + cfg=cfg, + rescale=rescale) + result_list.append(proposals) + return result_list + + def _predict_by_feat_single(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + mlvl_anchors: List[Tensor], + mlvl_masks: List[Tensor], + img_meta: dict, + cfg: ConfigType, + rescale: bool = False) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_scores (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + mlvl_anchors (list[Tensor]): Each element in the list is + the anchors of a single level in feature pyramid. it has + shape (num_priors, 4). + mlvl_masks (list[Tensor]): Each element in the list is location + masks of a single level. + img_meta (dict): Image meta info. + cfg (:obj:`ConfigDict` or dict): Test / postprocessing + configuration, if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), the last + dimension 4 arrange as (x1, y1, x2, y2). + """ + cfg = self.test_cfg if cfg is None else cfg + assert len(cls_scores) == len(bbox_preds) == len(mlvl_anchors) + mlvl_bbox_preds = [] + mlvl_valid_anchors = [] + mlvl_scores = [] + for cls_score, bbox_pred, anchors, mask in zip(cls_scores, bbox_preds, + mlvl_anchors, + mlvl_masks): + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + # if no location is kept, end. + if mask.sum() == 0: + continue + # reshape scores and bbox_pred + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + if self.use_sigmoid_cls: + scores = cls_score.sigmoid() + else: + scores = cls_score.softmax(-1) + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4) + # filter scores, bbox_pred w.r.t. mask. + # anchors are filtered in get_anchors() beforehand. + scores = scores[mask, :] + bbox_pred = bbox_pred[mask, :] + if scores.dim() == 0: + anchors = anchors.unsqueeze(0) + scores = scores.unsqueeze(0) + bbox_pred = bbox_pred.unsqueeze(0) + # filter anchors, bbox_pred, scores w.r.t. scores + nms_pre = cfg.get('nms_pre', -1) + if nms_pre > 0 and scores.shape[0] > nms_pre: + if self.use_sigmoid_cls: + max_scores, _ = scores.max(dim=1) + else: + # remind that we set FG labels to [0, num_class-1] + # since mmdet v2.0 + # BG cat_id: num_class + max_scores, _ = scores[:, :-1].max(dim=1) + _, topk_inds = max_scores.topk(nms_pre) + anchors = anchors[topk_inds, :] + bbox_pred = bbox_pred[topk_inds, :] + scores = scores[topk_inds, :] + + mlvl_bbox_preds.append(bbox_pred) + mlvl_valid_anchors.append(anchors) + mlvl_scores.append(scores) + + mlvl_bbox_preds = torch.cat(mlvl_bbox_preds) + mlvl_anchors = torch.cat(mlvl_valid_anchors) + mlvl_scores = torch.cat(mlvl_scores) + mlvl_bboxes = self.bbox_coder.decode( + mlvl_anchors, mlvl_bbox_preds, max_shape=img_meta['img_shape']) + + if rescale: + assert img_meta.get('scale_factor') is not None + mlvl_bboxes /= mlvl_bboxes.new_tensor( + img_meta['scale_factor']).repeat((1, 2)) + + if self.use_sigmoid_cls: + # Add a dummy background class to the backend when using sigmoid + # remind that we set FG labels to [0, num_class-1] since mmdet v2.0 + # BG cat_id: num_class + padding = mlvl_scores.new_zeros(mlvl_scores.shape[0], 1) + mlvl_scores = torch.cat([mlvl_scores, padding], dim=1) + # multi class NMS + det_bboxes, det_labels = multiclass_nms(mlvl_bboxes, mlvl_scores, + cfg.score_thr, cfg.nms, + cfg.max_per_img) + + results = InstanceData() + results.bboxes = det_bboxes[:, :-1] + results.scores = det_bboxes[:, -1] + results.labels = det_labels + return results diff --git a/mmdetection/mmdet/models/dense_heads/lad_head.py b/mmdetection/mmdet/models/dense_heads/lad_head.py new file mode 100644 index 00000000..d1218e1f --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/lad_head.py @@ -0,0 +1,226 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox_overlaps +from mmdet.utils import InstanceList, OptInstanceList +from ..utils import levels_to_images, multi_apply, unpack_gt_instances +from .paa_head import PAAHead + + +@MODELS.register_module() +class LADHead(PAAHead): + """Label Assignment Head from the paper: `Improving Object Detection by + Label Assignment Distillation `_""" + + def get_label_assignment( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + iou_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> tuple: + """Get label assignment (from teacher). + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + iou_preds (list[Tensor]): iou_preds for each scale + level with shape (N, num_anchors * 1, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + tuple: Returns a tuple containing label assignment variables. + + - labels (Tensor): Labels of all anchors, each with + shape (num_anchors,). + - labels_weight (Tensor): Label weights of all anchor. + each with shape (num_anchors,). + - bboxes_target (Tensor): BBox targets of all anchors. + each with shape (num_anchors, 4). + - bboxes_weight (Tensor): BBox weights of all anchors. + each with shape (num_anchors, 4). + - pos_inds_flatten (Tensor): Contains all index of positive + sample in all anchor. + - pos_anchors (Tensor): Positive anchors. + - num_pos (int): Number of positive anchors. + """ + + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + ) + (labels, labels_weight, bboxes_target, bboxes_weight, pos_inds, + pos_gt_index) = cls_reg_targets + cls_scores = levels_to_images(cls_scores) + cls_scores = [ + item.reshape(-1, self.cls_out_channels) for item in cls_scores + ] + bbox_preds = levels_to_images(bbox_preds) + bbox_preds = [item.reshape(-1, 4) for item in bbox_preds] + pos_losses_list, = multi_apply(self.get_pos_loss, anchor_list, + cls_scores, bbox_preds, labels, + labels_weight, bboxes_target, + bboxes_weight, pos_inds) + + with torch.no_grad(): + reassign_labels, reassign_label_weight, \ + reassign_bbox_weights, num_pos = multi_apply( + self.paa_reassign, + pos_losses_list, + labels, + labels_weight, + bboxes_weight, + pos_inds, + pos_gt_index, + anchor_list) + num_pos = sum(num_pos) + # convert all tensor list to a flatten tensor + labels = torch.cat(reassign_labels, 0).view(-1) + flatten_anchors = torch.cat( + [torch.cat(item, 0) for item in anchor_list]) + labels_weight = torch.cat(reassign_label_weight, 0).view(-1) + bboxes_target = torch.cat(bboxes_target, + 0).view(-1, bboxes_target[0].size(-1)) + + pos_inds_flatten = ((labels >= 0) + & + (labels < self.num_classes)).nonzero().reshape(-1) + + if num_pos: + pos_anchors = flatten_anchors[pos_inds_flatten] + else: + pos_anchors = None + + label_assignment_results = (labels, labels_weight, bboxes_target, + bboxes_weight, pos_inds_flatten, + pos_anchors, num_pos) + return label_assignment_results + + def loss(self, x: List[Tensor], label_assignment_results: tuple, + batch_data_samples: SampleList) -> dict: + """Forward train with the available label assignment (student receives + from teacher). + + Args: + x (list[Tensor]): Features from FPN. + label_assignment_results (tuple): As the outputs defined in the + function `self.get_label_assignment`. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + losses: (dict[str, Tensor]): A dictionary of loss components. + """ + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, batch_img_metas \ + = outputs + + outs = self(x) + loss_inputs = outs + (batch_gt_instances, batch_img_metas) + losses = self.loss_by_feat( + *loss_inputs, + batch_gt_instances_ignore=batch_gt_instances_ignore, + label_assignment_results=label_assignment_results) + return losses + + def loss_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + iou_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + label_assignment_results: Optional[tuple] = None) -> dict: + """Compute losses of the head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + iou_preds (list[Tensor]): iou_preds for each scale + level with shape (N, num_anchors * 1, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + label_assignment_results (tuple, optional): As the outputs defined + in the function `self.get_ + label_assignment`. + + Returns: + dict[str, Tensor]: A dictionary of loss gmm_assignment. + """ + + (labels, labels_weight, bboxes_target, bboxes_weight, pos_inds_flatten, + pos_anchors, num_pos) = label_assignment_results + + cls_scores = levels_to_images(cls_scores) + cls_scores = [ + item.reshape(-1, self.cls_out_channels) for item in cls_scores + ] + bbox_preds = levels_to_images(bbox_preds) + bbox_preds = [item.reshape(-1, 4) for item in bbox_preds] + iou_preds = levels_to_images(iou_preds) + iou_preds = [item.reshape(-1, 1) for item in iou_preds] + + # convert all tensor list to a flatten tensor + cls_scores = torch.cat(cls_scores, 0).view(-1, cls_scores[0].size(-1)) + bbox_preds = torch.cat(bbox_preds, 0).view(-1, bbox_preds[0].size(-1)) + iou_preds = torch.cat(iou_preds, 0).view(-1, iou_preds[0].size(-1)) + + losses_cls = self.loss_cls( + cls_scores, + labels, + labels_weight, + avg_factor=max(num_pos, len(batch_img_metas))) # avoid num_pos=0 + if num_pos: + pos_bbox_pred = self.bbox_coder.decode( + pos_anchors, bbox_preds[pos_inds_flatten]) + pos_bbox_target = bboxes_target[pos_inds_flatten] + iou_target = bbox_overlaps( + pos_bbox_pred.detach(), pos_bbox_target, is_aligned=True) + losses_iou = self.loss_centerness( + iou_preds[pos_inds_flatten], + iou_target.unsqueeze(-1), + avg_factor=num_pos) + losses_bbox = self.loss_bbox( + pos_bbox_pred, pos_bbox_target, avg_factor=num_pos) + + else: + losses_iou = iou_preds.sum() * 0 + losses_bbox = bbox_preds.sum() * 0 + + return dict( + loss_cls=losses_cls, loss_bbox=losses_bbox, loss_iou=losses_iou) diff --git a/mmdetection/mmdet/models/dense_heads/ld_head.py b/mmdetection/mmdet/models/dense_heads/ld_head.py new file mode 100644 index 00000000..2558fac9 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/ld_head.py @@ -0,0 +1,257 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox_overlaps +from mmdet.utils import ConfigType, InstanceList, OptInstanceList, reduce_mean +from ..utils import multi_apply, unpack_gt_instances +from .gfl_head import GFLHead + + +@MODELS.register_module() +class LDHead(GFLHead): + """Localization distillation Head. (Short description) + + It utilizes the learned bbox distributions to transfer the localization + dark knowledge from teacher to student. Original paper: `Localization + Distillation for Object Detection. `_ + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + loss_ld (:obj:`ConfigDict` or dict): Config of Localization + Distillation Loss (LD), T is the temperature for distillation. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + loss_ld: ConfigType = dict( + type='LocalizationDistillationLoss', + loss_weight=0.25, + T=10), + **kwargs) -> dict: + + super().__init__( + num_classes=num_classes, in_channels=in_channels, **kwargs) + self.loss_ld = MODELS.build(loss_ld) + + def loss_by_feat_single(self, anchors: Tensor, cls_score: Tensor, + bbox_pred: Tensor, labels: Tensor, + label_weights: Tensor, bbox_targets: Tensor, + stride: Tuple[int], soft_targets: Tensor, + avg_factor: int): + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + anchors (Tensor): Box reference for each scale level with shape + (N, num_total_anchors, 4). + cls_score (Tensor): Cls and quality joint scores for each scale + level has shape (N, num_classes, H, W). + bbox_pred (Tensor): Box distribution logits for each scale + level with shape (N, 4*(n+1), H, W), n is max value of integral + set. + labels (Tensor): Labels of each anchors with shape + (N, num_total_anchors). + label_weights (Tensor): Label weights of each anchor with shape + (N, num_total_anchors) + bbox_targets (Tensor): BBox regression targets of each anchor with + shape (N, num_total_anchors, 4). + stride (tuple): Stride in this scale level. + soft_targets (Tensor): Soft BBox regression targets. + avg_factor (int): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + `PseudoSampler`, `avg_factor` is usually equal to the number + of positive priors. + + Returns: + dict[tuple, Tensor]: Loss components and weight targets. + """ + assert stride[0] == stride[1], 'h stride is not equal to w stride!' + anchors = anchors.reshape(-1, 4) + cls_score = cls_score.permute(0, 2, 3, + 1).reshape(-1, self.cls_out_channels) + bbox_pred = bbox_pred.permute(0, 2, 3, + 1).reshape(-1, 4 * (self.reg_max + 1)) + soft_targets = soft_targets.permute(0, 2, 3, + 1).reshape(-1, + 4 * (self.reg_max + 1)) + + bbox_targets = bbox_targets.reshape(-1, 4) + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().squeeze(1) + score = label_weights.new_zeros(labels.shape) + + if len(pos_inds) > 0: + pos_bbox_targets = bbox_targets[pos_inds] + pos_bbox_pred = bbox_pred[pos_inds] + pos_anchors = anchors[pos_inds] + pos_anchor_centers = self.anchor_center(pos_anchors) / stride[0] + + weight_targets = cls_score.detach().sigmoid() + weight_targets = weight_targets.max(dim=1)[0][pos_inds] + pos_bbox_pred_corners = self.integral(pos_bbox_pred) + pos_decode_bbox_pred = self.bbox_coder.decode( + pos_anchor_centers, pos_bbox_pred_corners) + pos_decode_bbox_targets = pos_bbox_targets / stride[0] + score[pos_inds] = bbox_overlaps( + pos_decode_bbox_pred.detach(), + pos_decode_bbox_targets, + is_aligned=True) + pred_corners = pos_bbox_pred.reshape(-1, self.reg_max + 1) + pos_soft_targets = soft_targets[pos_inds] + soft_corners = pos_soft_targets.reshape(-1, self.reg_max + 1) + + target_corners = self.bbox_coder.encode(pos_anchor_centers, + pos_decode_bbox_targets, + self.reg_max).reshape(-1) + + # regression loss + loss_bbox = self.loss_bbox( + pos_decode_bbox_pred, + pos_decode_bbox_targets, + weight=weight_targets, + avg_factor=1.0) + + # dfl loss + loss_dfl = self.loss_dfl( + pred_corners, + target_corners, + weight=weight_targets[:, None].expand(-1, 4).reshape(-1), + avg_factor=4.0) + + # ld loss + loss_ld = self.loss_ld( + pred_corners, + soft_corners, + weight=weight_targets[:, None].expand(-1, 4).reshape(-1), + avg_factor=4.0) + + else: + loss_ld = bbox_pred.sum() * 0 + loss_bbox = bbox_pred.sum() * 0 + loss_dfl = bbox_pred.sum() * 0 + weight_targets = bbox_pred.new_tensor(0) + + # cls (qfl) loss + loss_cls = self.loss_cls( + cls_score, (labels, score), + weight=label_weights, + avg_factor=avg_factor) + + return loss_cls, loss_bbox, loss_dfl, loss_ld, weight_targets.sum() + + def loss(self, x: List[Tensor], out_teacher: Tuple[Tensor], + batch_data_samples: SampleList) -> dict: + """ + Args: + x (list[Tensor]): Features from FPN. + out_teacher (tuple[Tensor]): The output of teacher. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + tuple[dict, list]: The loss components and proposals of each image. + + - losses (dict[str, Tensor]): A dictionary of loss components. + - proposal_list (list[Tensor]): Proposals of each image. + """ + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, batch_img_metas \ + = outputs + + outs = self(x) + soft_targets = out_teacher[1] + loss_inputs = outs + (batch_gt_instances, batch_img_metas, + soft_targets) + losses = self.loss_by_feat( + *loss_inputs, batch_gt_instances_ignore=batch_gt_instances_ignore) + + return losses + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + soft_targets: List[Tensor], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Compute losses of the head. + + Args: + cls_scores (list[Tensor]): Cls and quality scores for each scale + level has shape (N, num_classes, H, W). + bbox_preds (list[Tensor]): Box distribution logits for each scale + level with shape (N, 4*(n+1), H, W), n is max value of integral + set. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + soft_targets (list[Tensor]): Soft BBox regression targets. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + + (anchor_list, labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor) = cls_reg_targets + + avg_factor = reduce_mean( + torch.tensor(avg_factor, dtype=torch.float, device=device)).item() + + losses_cls, losses_bbox, losses_dfl, losses_ld, \ + avg_factor = multi_apply( + self.loss_by_feat_single, + anchor_list, + cls_scores, + bbox_preds, + labels_list, + label_weights_list, + bbox_targets_list, + self.prior_generator.strides, + soft_targets, + avg_factor=avg_factor) + + avg_factor = sum(avg_factor) + 1e-6 + avg_factor = reduce_mean(avg_factor).item() + losses_bbox = [x / avg_factor for x in losses_bbox] + losses_dfl = [x / avg_factor for x in losses_dfl] + return dict( + loss_cls=losses_cls, + loss_bbox=losses_bbox, + loss_dfl=losses_dfl, + loss_ld=losses_ld) diff --git a/mmdetection/mmdet/models/dense_heads/mask2former_head.py b/mmdetection/mmdet/models/dense_heads/mask2former_head.py new file mode 100644 index 00000000..12d47c65 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/mask2former_head.py @@ -0,0 +1,459 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import List, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Conv2d +from mmcv.ops import point_sample +from mmengine.model import ModuleList, caffe2_xavier_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig, reduce_mean +from ..layers import Mask2FormerTransformerDecoder, SinePositionalEncoding +from ..utils import get_uncertain_point_coords_with_randomness +from .anchor_free_head import AnchorFreeHead +from .maskformer_head import MaskFormerHead + + +@MODELS.register_module() +class Mask2FormerHead(MaskFormerHead): + """Implements the Mask2Former head. + + See `Masked-attention Mask Transformer for Universal Image + Segmentation `_ for details. + + Args: + in_channels (list[int]): Number of channels in the input feature map. + feat_channels (int): Number of channels for features. + out_channels (int): Number of channels for output. + num_things_classes (int): Number of things. + num_stuff_classes (int): Number of stuff. + num_queries (int): Number of query in Transformer decoder. + pixel_decoder (:obj:`ConfigDict` or dict): Config for pixel + decoder. Defaults to None. + enforce_decoder_input_project (bool, optional): Whether to add + a layer to change the embed_dim of tranformer encoder in + pixel decoder to the embed_dim of transformer decoder. + Defaults to False. + transformer_decoder (:obj:`ConfigDict` or dict): Config for + transformer decoder. Defaults to None. + positional_encoding (:obj:`ConfigDict` or dict): Config for + transformer decoder position encoding. Defaults to + dict(num_feats=128, normalize=True). + loss_cls (:obj:`ConfigDict` or dict): Config of the classification + loss. Defaults to None. + loss_mask (:obj:`ConfigDict` or dict): Config of the mask loss. + Defaults to None. + loss_dice (:obj:`ConfigDict` or dict): Config of the dice loss. + Defaults to None. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config of + Mask2Former head. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + Mask2Former head. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. Defaults to None. + """ + + def __init__(self, + in_channels: List[int], + feat_channels: int, + out_channels: int, + num_things_classes: int = 80, + num_stuff_classes: int = 53, + num_queries: int = 100, + num_transformer_feat_level: int = 3, + pixel_decoder: ConfigType = ..., + enforce_decoder_input_project: bool = False, + transformer_decoder: ConfigType = ..., + positional_encoding: ConfigType = dict( + num_feats=128, normalize=True), + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=2.0, + reduction='mean', + class_weight=[1.0] * 133 + [0.1]), + loss_mask: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='mean', + loss_weight=5.0), + loss_dice: ConfigType = dict( + type='DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=5.0), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + super(AnchorFreeHead, self).__init__(init_cfg=init_cfg) + self.num_things_classes = num_things_classes + self.num_stuff_classes = num_stuff_classes + self.num_classes = self.num_things_classes + self.num_stuff_classes + self.num_queries = num_queries + self.num_transformer_feat_level = num_transformer_feat_level + self.num_heads = transformer_decoder.layer_cfg.cross_attn_cfg.num_heads + self.num_transformer_decoder_layers = transformer_decoder.num_layers + assert pixel_decoder.encoder.layer_cfg. \ + self_attn_cfg.num_levels == num_transformer_feat_level + pixel_decoder_ = copy.deepcopy(pixel_decoder) + pixel_decoder_.update( + in_channels=in_channels, + feat_channels=feat_channels, + out_channels=out_channels) + self.pixel_decoder = MODELS.build(pixel_decoder_) + self.transformer_decoder = Mask2FormerTransformerDecoder( + **transformer_decoder) + self.decoder_embed_dims = self.transformer_decoder.embed_dims + + self.decoder_input_projs = ModuleList() + # from low resolution to high resolution + for _ in range(num_transformer_feat_level): + if (self.decoder_embed_dims != feat_channels + or enforce_decoder_input_project): + self.decoder_input_projs.append( + Conv2d( + feat_channels, self.decoder_embed_dims, kernel_size=1)) + else: + self.decoder_input_projs.append(nn.Identity()) + self.decoder_positional_encoding = SinePositionalEncoding( + **positional_encoding) + self.query_embed = nn.Embedding(self.num_queries, feat_channels) + self.query_feat = nn.Embedding(self.num_queries, feat_channels) + # from low resolution to high resolution + self.level_embed = nn.Embedding(self.num_transformer_feat_level, + feat_channels) + + self.cls_embed = nn.Linear(feat_channels, self.num_classes + 1) + self.mask_embed = nn.Sequential( + nn.Linear(feat_channels, feat_channels), nn.ReLU(inplace=True), + nn.Linear(feat_channels, feat_channels), nn.ReLU(inplace=True), + nn.Linear(feat_channels, out_channels)) + + self.test_cfg = test_cfg + self.train_cfg = train_cfg + if train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], default_args=dict(context=self)) + self.num_points = self.train_cfg.get('num_points', 12544) + self.oversample_ratio = self.train_cfg.get('oversample_ratio', 3.0) + self.importance_sample_ratio = self.train_cfg.get( + 'importance_sample_ratio', 0.75) + + self.class_weight = loss_cls.class_weight + self.loss_cls = MODELS.build(loss_cls) + self.loss_mask = MODELS.build(loss_mask) + self.loss_dice = MODELS.build(loss_dice) + + def init_weights(self) -> None: + for m in self.decoder_input_projs: + if isinstance(m, Conv2d): + caffe2_xavier_init(m, bias=0) + + self.pixel_decoder.init_weights() + + for p in self.transformer_decoder.parameters(): + if p.dim() > 1: + nn.init.xavier_normal_(p) + + def _get_targets_single(self, cls_score: Tensor, mask_pred: Tensor, + gt_instances: InstanceData, + img_meta: dict) -> Tuple[Tensor]: + """Compute classification and mask targets for one image. + + Args: + cls_score (Tensor): Mask score logits from a single decoder layer + for one image. Shape (num_queries, cls_out_channels). + mask_pred (Tensor): Mask logits for a single decoder layer for one + image. Shape (num_queries, h, w). + gt_instances (:obj:`InstanceData`): It contains ``labels`` and + ``masks``. + img_meta (dict): Image informtation. + + Returns: + tuple[Tensor]: A tuple containing the following for one image. + + - labels (Tensor): Labels of each image. \ + shape (num_queries, ). + - label_weights (Tensor): Label weights of each image. \ + shape (num_queries, ). + - mask_targets (Tensor): Mask targets of each image. \ + shape (num_queries, h, w). + - mask_weights (Tensor): Mask weights of each image. \ + shape (num_queries, ). + - pos_inds (Tensor): Sampled positive indices for each \ + image. + - neg_inds (Tensor): Sampled negative indices for each \ + image. + - sampling_result (:obj:`SamplingResult`): Sampling results. + """ + gt_labels = gt_instances.labels + gt_masks = gt_instances.masks + # sample points + num_queries = cls_score.shape[0] + num_gts = gt_labels.shape[0] + + point_coords = torch.rand((1, self.num_points, 2), + device=cls_score.device) + # shape (num_queries, num_points) + mask_points_pred = point_sample( + mask_pred.unsqueeze(1), point_coords.repeat(num_queries, 1, + 1)).squeeze(1) + # shape (num_gts, num_points) + gt_points_masks = point_sample( + gt_masks.unsqueeze(1).float(), point_coords.repeat(num_gts, 1, + 1)).squeeze(1) + + sampled_gt_instances = InstanceData( + labels=gt_labels, masks=gt_points_masks) + sampled_pred_instances = InstanceData( + scores=cls_score, masks=mask_points_pred) + # assign and sample + assign_result = self.assigner.assign( + pred_instances=sampled_pred_instances, + gt_instances=sampled_gt_instances, + img_meta=img_meta) + pred_instances = InstanceData(scores=cls_score, masks=mask_pred) + sampling_result = self.sampler.sample( + assign_result=assign_result, + pred_instances=pred_instances, + gt_instances=gt_instances) + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + + # label target + labels = gt_labels.new_full((self.num_queries, ), + self.num_classes, + dtype=torch.long) + labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds] + label_weights = gt_labels.new_ones((self.num_queries, )) + + # mask target + mask_targets = gt_masks[sampling_result.pos_assigned_gt_inds] + mask_weights = mask_pred.new_zeros((self.num_queries, )) + mask_weights[pos_inds] = 1.0 + + return (labels, label_weights, mask_targets, mask_weights, pos_inds, + neg_inds, sampling_result) + + def _loss_by_feat_single(self, cls_scores: Tensor, mask_preds: Tensor, + batch_gt_instances: List[InstanceData], + batch_img_metas: List[dict]) -> Tuple[Tensor]: + """Loss function for outputs from a single decoder layer. + + Args: + cls_scores (Tensor): Mask score logits from a single decoder layer + for all images. Shape (batch_size, num_queries, + cls_out_channels). Note `cls_out_channels` should includes + background. + mask_preds (Tensor): Mask logits for a pixel decoder for all + images. Shape (batch_size, num_queries, h, w). + batch_gt_instances (list[obj:`InstanceData`]): each contains + ``labels`` and ``masks``. + batch_img_metas (list[dict]): List of image meta information. + + Returns: + tuple[Tensor]: Loss components for outputs from a single \ + decoder layer. + """ + num_imgs = cls_scores.size(0) + cls_scores_list = [cls_scores[i] for i in range(num_imgs)] + mask_preds_list = [mask_preds[i] for i in range(num_imgs)] + (labels_list, label_weights_list, mask_targets_list, mask_weights_list, + avg_factor) = self.get_targets(cls_scores_list, mask_preds_list, + batch_gt_instances, batch_img_metas) + # shape (batch_size, num_queries) + labels = torch.stack(labels_list, dim=0) + # shape (batch_size, num_queries) + label_weights = torch.stack(label_weights_list, dim=0) + # shape (num_total_gts, h, w) + mask_targets = torch.cat(mask_targets_list, dim=0) + # shape (batch_size, num_queries) + mask_weights = torch.stack(mask_weights_list, dim=0) + + # classfication loss + # shape (batch_size * num_queries, ) + cls_scores = cls_scores.flatten(0, 1) + labels = labels.flatten(0, 1) + label_weights = label_weights.flatten(0, 1) + + class_weight = cls_scores.new_tensor(self.class_weight) + loss_cls = self.loss_cls( + cls_scores, + labels, + label_weights, + avg_factor=class_weight[labels].sum()) + + num_total_masks = reduce_mean(cls_scores.new_tensor([avg_factor])) + num_total_masks = max(num_total_masks, 1) + + # extract positive ones + # shape (batch_size, num_queries, h, w) -> (num_total_gts, h, w) + mask_preds = mask_preds[mask_weights > 0] + + if mask_targets.shape[0] == 0: + # zero match + loss_dice = mask_preds.sum() + loss_mask = mask_preds.sum() + return loss_cls, loss_mask, loss_dice + + with torch.no_grad(): + points_coords = get_uncertain_point_coords_with_randomness( + mask_preds.unsqueeze(1), None, self.num_points, + self.oversample_ratio, self.importance_sample_ratio) + # shape (num_total_gts, h, w) -> (num_total_gts, num_points) + mask_point_targets = point_sample( + mask_targets.unsqueeze(1).float(), points_coords).squeeze(1) + # shape (num_queries, h, w) -> (num_queries, num_points) + mask_point_preds = point_sample( + mask_preds.unsqueeze(1), points_coords).squeeze(1) + + # dice loss + loss_dice = self.loss_dice( + mask_point_preds, mask_point_targets, avg_factor=num_total_masks) + + # mask loss + # shape (num_queries, num_points) -> (num_queries * num_points, ) + mask_point_preds = mask_point_preds.reshape(-1) + # shape (num_total_gts, num_points) -> (num_total_gts * num_points, ) + mask_point_targets = mask_point_targets.reshape(-1) + loss_mask = self.loss_mask( + mask_point_preds, + mask_point_targets, + avg_factor=num_total_masks * self.num_points) + + return loss_cls, loss_mask, loss_dice + + def _forward_head(self, decoder_out: Tensor, mask_feature: Tensor, + attn_mask_target_size: Tuple[int, int]) -> Tuple[Tensor]: + """Forward for head part which is called after every decoder layer. + + Args: + decoder_out (Tensor): in shape (batch_size, num_queries, c). + mask_feature (Tensor): in shape (batch_size, c, h, w). + attn_mask_target_size (tuple[int, int]): target attention + mask size. + + Returns: + tuple: A tuple contain three elements. + + - cls_pred (Tensor): Classification scores in shape \ + (batch_size, num_queries, cls_out_channels). \ + Note `cls_out_channels` should includes background. + - mask_pred (Tensor): Mask scores in shape \ + (batch_size, num_queries,h, w). + - attn_mask (Tensor): Attention mask in shape \ + (batch_size * num_heads, num_queries, h, w). + """ + decoder_out = self.transformer_decoder.post_norm(decoder_out) + # shape (num_queries, batch_size, c) + cls_pred = self.cls_embed(decoder_out) + # shape (num_queries, batch_size, c) + mask_embed = self.mask_embed(decoder_out) + # shape (num_queries, batch_size, h, w) + mask_pred = torch.einsum('bqc,bchw->bqhw', mask_embed, mask_feature) + attn_mask = F.interpolate( + mask_pred, + attn_mask_target_size, + mode='bilinear', + align_corners=False) + # shape (num_queries, batch_size, h, w) -> + # (batch_size * num_head, num_queries, h, w) + attn_mask = attn_mask.flatten(2).unsqueeze(1).repeat( + (1, self.num_heads, 1, 1)).flatten(0, 1) + attn_mask = attn_mask.sigmoid() < 0.5 + attn_mask = attn_mask.detach() + + return cls_pred, mask_pred, attn_mask + + def forward(self, x: List[Tensor], + batch_data_samples: SampleList) -> Tuple[List[Tensor]]: + """Forward function. + + Args: + x (list[Tensor]): Multi scale Features from the + upstream network, each is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + tuple[list[Tensor]]: A tuple contains two elements. + + - cls_pred_list (list[Tensor)]: Classification logits \ + for each decoder layer. Each is a 3D-tensor with shape \ + (batch_size, num_queries, cls_out_channels). \ + Note `cls_out_channels` should includes background. + - mask_pred_list (list[Tensor]): Mask logits for each \ + decoder layer. Each with shape (batch_size, num_queries, \ + h, w). + """ + batch_size = x[0].shape[0] + mask_features, multi_scale_memorys = self.pixel_decoder(x) + # multi_scale_memorys (from low resolution to high resolution) + decoder_inputs = [] + decoder_positional_encodings = [] + for i in range(self.num_transformer_feat_level): + decoder_input = self.decoder_input_projs[i](multi_scale_memorys[i]) + # shape (batch_size, c, h, w) -> (batch_size, h*w, c) + decoder_input = decoder_input.flatten(2).permute(0, 2, 1) + level_embed = self.level_embed.weight[i].view(1, 1, -1) + decoder_input = decoder_input + level_embed + # shape (batch_size, c, h, w) -> (batch_size, h*w, c) + mask = decoder_input.new_zeros( + (batch_size, ) + multi_scale_memorys[i].shape[-2:], + dtype=torch.bool) + decoder_positional_encoding = self.decoder_positional_encoding( + mask) + decoder_positional_encoding = decoder_positional_encoding.flatten( + 2).permute(0, 2, 1) + decoder_inputs.append(decoder_input) + decoder_positional_encodings.append(decoder_positional_encoding) + # shape (num_queries, c) -> (batch_size, num_queries, c) + query_feat = self.query_feat.weight.unsqueeze(0).repeat( + (batch_size, 1, 1)) + query_embed = self.query_embed.weight.unsqueeze(0).repeat( + (batch_size, 1, 1)) + + cls_pred_list = [] + mask_pred_list = [] + cls_pred, mask_pred, attn_mask = self._forward_head( + query_feat, mask_features, multi_scale_memorys[0].shape[-2:]) + cls_pred_list.append(cls_pred) + mask_pred_list.append(mask_pred) + + for i in range(self.num_transformer_decoder_layers): + level_idx = i % self.num_transformer_feat_level + # if a mask is all True(all background), then set it all False. + mask_sum = (attn_mask.sum(-1) != attn_mask.shape[-1]).unsqueeze(-1) + attn_mask = attn_mask & mask_sum + # cross_attn + self_attn + layer = self.transformer_decoder.layers[i] + query_feat = layer( + query=query_feat, + key=decoder_inputs[level_idx], + value=decoder_inputs[level_idx], + query_pos=query_embed, + key_pos=decoder_positional_encodings[level_idx], + cross_attn_mask=attn_mask, + query_key_padding_mask=None, + # here we do not apply masking on padded region + key_padding_mask=None) + cls_pred, mask_pred, attn_mask = self._forward_head( + query_feat, mask_features, multi_scale_memorys[ + (i + 1) % self.num_transformer_feat_level].shape[-2:]) + + cls_pred_list.append(cls_pred) + mask_pred_list.append(mask_pred) + + return cls_pred_list, mask_pred_list diff --git a/mmdetection/mmdet/models/dense_heads/maskformer_head.py b/mmdetection/mmdet/models/dense_heads/maskformer_head.py new file mode 100644 index 00000000..24c0655e --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/maskformer_head.py @@ -0,0 +1,601 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Conv2d +from mmengine.model import caffe2_xavier_init +from mmengine.structures import InstanceData, PixelData +from torch import Tensor + +from mmdet.models.layers.pixel_decoder import PixelDecoder +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import SampleList +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptMultiConfig, reduce_mean) +from ..layers import DetrTransformerDecoder, SinePositionalEncoding +from ..utils import multi_apply, preprocess_panoptic_gt +from .anchor_free_head import AnchorFreeHead + + +@MODELS.register_module() +class MaskFormerHead(AnchorFreeHead): + """Implements the MaskFormer head. + + See `Per-Pixel Classification is Not All You Need for Semantic + Segmentation `_ for details. + + Args: + in_channels (list[int]): Number of channels in the input feature map. + feat_channels (int): Number of channels for feature. + out_channels (int): Number of channels for output. + num_things_classes (int): Number of things. + num_stuff_classes (int): Number of stuff. + num_queries (int): Number of query in Transformer. + pixel_decoder (:obj:`ConfigDict` or dict): Config for pixel + decoder. + enforce_decoder_input_project (bool): Whether to add a layer + to change the embed_dim of transformer encoder in pixel decoder to + the embed_dim of transformer decoder. Defaults to False. + transformer_decoder (:obj:`ConfigDict` or dict): Config for + transformer decoder. + positional_encoding (:obj:`ConfigDict` or dict): Config for + transformer decoder position encoding. + loss_cls (:obj:`ConfigDict` or dict): Config of the classification + loss. Defaults to `CrossEntropyLoss`. + loss_mask (:obj:`ConfigDict` or dict): Config of the mask loss. + Defaults to `FocalLoss`. + loss_dice (:obj:`ConfigDict` or dict): Config of the dice loss. + Defaults to `DiceLoss`. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config of + MaskFormer head. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + MaskFormer head. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. Defaults to None. + """ + + def __init__(self, + in_channels: List[int], + feat_channels: int, + out_channels: int, + num_things_classes: int = 80, + num_stuff_classes: int = 53, + num_queries: int = 100, + pixel_decoder: ConfigType = ..., + enforce_decoder_input_project: bool = False, + transformer_decoder: ConfigType = ..., + positional_encoding: ConfigType = dict( + num_feats=128, normalize=True), + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + class_weight=[1.0] * 133 + [0.1]), + loss_mask: ConfigType = dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=20.0), + loss_dice: ConfigType = dict( + type='DiceLoss', + use_sigmoid=True, + activate=True, + naive_dice=True, + loss_weight=1.0), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + super(AnchorFreeHead, self).__init__(init_cfg=init_cfg) + self.num_things_classes = num_things_classes + self.num_stuff_classes = num_stuff_classes + self.num_classes = self.num_things_classes + self.num_stuff_classes + self.num_queries = num_queries + + pixel_decoder.update( + in_channels=in_channels, + feat_channels=feat_channels, + out_channels=out_channels) + self.pixel_decoder = MODELS.build(pixel_decoder) + self.transformer_decoder = DetrTransformerDecoder( + **transformer_decoder) + self.decoder_embed_dims = self.transformer_decoder.embed_dims + if type(self.pixel_decoder) == PixelDecoder and ( + self.decoder_embed_dims != in_channels[-1] + or enforce_decoder_input_project): + self.decoder_input_proj = Conv2d( + in_channels[-1], self.decoder_embed_dims, kernel_size=1) + else: + self.decoder_input_proj = nn.Identity() + self.decoder_pe = SinePositionalEncoding(**positional_encoding) + self.query_embed = nn.Embedding(self.num_queries, out_channels) + + self.cls_embed = nn.Linear(feat_channels, self.num_classes + 1) + self.mask_embed = nn.Sequential( + nn.Linear(feat_channels, feat_channels), nn.ReLU(inplace=True), + nn.Linear(feat_channels, feat_channels), nn.ReLU(inplace=True), + nn.Linear(feat_channels, out_channels)) + + self.test_cfg = test_cfg + self.train_cfg = train_cfg + if train_cfg: + self.assigner = TASK_UTILS.build(train_cfg['assigner']) + self.sampler = TASK_UTILS.build( + train_cfg['sampler'], default_args=dict(context=self)) + + self.class_weight = loss_cls.class_weight + self.loss_cls = MODELS.build(loss_cls) + self.loss_mask = MODELS.build(loss_mask) + self.loss_dice = MODELS.build(loss_dice) + + def init_weights(self) -> None: + if isinstance(self.decoder_input_proj, Conv2d): + caffe2_xavier_init(self.decoder_input_proj, bias=0) + + self.pixel_decoder.init_weights() + + for p in self.transformer_decoder.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + + def preprocess_gt( + self, batch_gt_instances: InstanceList, + batch_gt_semantic_segs: List[Optional[PixelData]]) -> InstanceList: + """Preprocess the ground truth for all images. + + Args: + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``labels``, each is + ground truth labels of each bbox, with shape (num_gts, ) + and ``masks``, each is ground truth masks of each instances + of a image, shape (num_gts, h, w). + gt_semantic_seg (list[Optional[PixelData]]): Ground truth of + semantic segmentation, each with the shape (1, h, w). + [0, num_thing_class - 1] means things, + [num_thing_class, num_class-1] means stuff, + 255 means VOID. It's None when training instance segmentation. + + Returns: + list[obj:`InstanceData`]: each contains the following keys + + - labels (Tensor): Ground truth class indices\ + for a image, with shape (n, ), n is the sum of\ + number of stuff type and number of instance in a image. + - masks (Tensor): Ground truth mask for a\ + image, with shape (n, h, w). + """ + num_things_list = [self.num_things_classes] * len(batch_gt_instances) + num_stuff_list = [self.num_stuff_classes] * len(batch_gt_instances) + gt_labels_list = [ + gt_instances['labels'] for gt_instances in batch_gt_instances + ] + gt_masks_list = [ + gt_instances['masks'] for gt_instances in batch_gt_instances + ] + gt_semantic_segs = [ + None if gt_semantic_seg is None else gt_semantic_seg.sem_seg + for gt_semantic_seg in batch_gt_semantic_segs + ] + targets = multi_apply(preprocess_panoptic_gt, gt_labels_list, + gt_masks_list, gt_semantic_segs, num_things_list, + num_stuff_list) + labels, masks = targets + batch_gt_instances = [ + InstanceData(labels=label, masks=mask) + for label, mask in zip(labels, masks) + ] + return batch_gt_instances + + def get_targets( + self, + cls_scores_list: List[Tensor], + mask_preds_list: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + return_sampling_results: bool = False + ) -> Tuple[List[Union[Tensor, int]]]: + """Compute classification and mask targets for all images for a decoder + layer. + + Args: + cls_scores_list (list[Tensor]): Mask score logits from a single + decoder layer for all images. Each with shape (num_queries, + cls_out_channels). + mask_preds_list (list[Tensor]): Mask logits from a single decoder + layer for all images. Each with shape (num_queries, h, w). + batch_gt_instances (list[obj:`InstanceData`]): each contains + ``labels`` and ``masks``. + batch_img_metas (list[dict]): List of image meta information. + return_sampling_results (bool): Whether to return the sampling + results. Defaults to False. + + Returns: + tuple: a tuple containing the following targets. + + - labels_list (list[Tensor]): Labels of all images.\ + Each with shape (num_queries, ). + - label_weights_list (list[Tensor]): Label weights\ + of all images. Each with shape (num_queries, ). + - mask_targets_list (list[Tensor]): Mask targets of\ + all images. Each with shape (num_queries, h, w). + - mask_weights_list (list[Tensor]): Mask weights of\ + all images. Each with shape (num_queries, ). + - avg_factor (int): Average factor that is used to average\ + the loss. When using sampling method, avg_factor is + usually the sum of positive and negative priors. When + using `MaskPseudoSampler`, `avg_factor` is usually equal + to the number of positive priors. + + additional_returns: This function enables user-defined returns from + `self._get_targets_single`. These returns are currently refined + to properties at each feature map (i.e. having HxW dimension). + The results will be concatenated after the end. + """ + results = multi_apply(self._get_targets_single, cls_scores_list, + mask_preds_list, batch_gt_instances, + batch_img_metas) + (labels_list, label_weights_list, mask_targets_list, mask_weights_list, + pos_inds_list, neg_inds_list, sampling_results_list) = results[:7] + rest_results = list(results[7:]) + + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + + res = (labels_list, label_weights_list, mask_targets_list, + mask_weights_list, avg_factor) + if return_sampling_results: + res = res + (sampling_results_list) + + return res + tuple(rest_results) + + def _get_targets_single(self, cls_score: Tensor, mask_pred: Tensor, + gt_instances: InstanceData, + img_meta: dict) -> Tuple[Tensor]: + """Compute classification and mask targets for one image. + + Args: + cls_score (Tensor): Mask score logits from a single decoder layer + for one image. Shape (num_queries, cls_out_channels). + mask_pred (Tensor): Mask logits for a single decoder layer for one + image. Shape (num_queries, h, w). + gt_instances (:obj:`InstanceData`): It contains ``labels`` and + ``masks``. + img_meta (dict): Image informtation. + + Returns: + tuple: a tuple containing the following for one image. + + - labels (Tensor): Labels of each image. + shape (num_queries, ). + - label_weights (Tensor): Label weights of each image. + shape (num_queries, ). + - mask_targets (Tensor): Mask targets of each image. + shape (num_queries, h, w). + - mask_weights (Tensor): Mask weights of each image. + shape (num_queries, ). + - pos_inds (Tensor): Sampled positive indices for each image. + - neg_inds (Tensor): Sampled negative indices for each image. + - sampling_result (:obj:`SamplingResult`): Sampling results. + """ + gt_masks = gt_instances.masks + gt_labels = gt_instances.labels + + target_shape = mask_pred.shape[-2:] + if gt_masks.shape[0] > 0: + gt_masks_downsampled = F.interpolate( + gt_masks.unsqueeze(1).float(), target_shape, + mode='nearest').squeeze(1).long() + else: + gt_masks_downsampled = gt_masks + + pred_instances = InstanceData(scores=cls_score, masks=mask_pred) + downsampled_gt_instances = InstanceData( + labels=gt_labels, masks=gt_masks_downsampled) + # assign and sample + assign_result = self.assigner.assign( + pred_instances=pred_instances, + gt_instances=downsampled_gt_instances, + img_meta=img_meta) + sampling_result = self.sampler.sample( + assign_result=assign_result, + pred_instances=pred_instances, + gt_instances=gt_instances) + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + + # label target + labels = gt_labels.new_full((self.num_queries, ), + self.num_classes, + dtype=torch.long) + labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds] + label_weights = gt_labels.new_ones(self.num_queries) + + # mask target + mask_targets = gt_masks[sampling_result.pos_assigned_gt_inds] + mask_weights = mask_pred.new_zeros((self.num_queries, )) + mask_weights[pos_inds] = 1.0 + + return (labels, label_weights, mask_targets, mask_weights, pos_inds, + neg_inds, sampling_result) + + def loss_by_feat(self, all_cls_scores: Tensor, all_mask_preds: Tensor, + batch_gt_instances: List[InstanceData], + batch_img_metas: List[dict]) -> Dict[str, Tensor]: + """Loss function. + + Args: + all_cls_scores (Tensor): Classification scores for all decoder + layers with shape (num_decoder, batch_size, num_queries, + cls_out_channels). Note `cls_out_channels` should includes + background. + all_mask_preds (Tensor): Mask scores for all decoder layers with + shape (num_decoder, batch_size, num_queries, h, w). + batch_gt_instances (list[obj:`InstanceData`]): each contains + ``labels`` and ``masks``. + batch_img_metas (list[dict]): List of image meta information. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + num_dec_layers = len(all_cls_scores) + batch_gt_instances_list = [ + batch_gt_instances for _ in range(num_dec_layers) + ] + img_metas_list = [batch_img_metas for _ in range(num_dec_layers)] + losses_cls, losses_mask, losses_dice = multi_apply( + self._loss_by_feat_single, all_cls_scores, all_mask_preds, + batch_gt_instances_list, img_metas_list) + + loss_dict = dict() + # loss from the last decoder layer + loss_dict['loss_cls'] = losses_cls[-1] + loss_dict['loss_mask'] = losses_mask[-1] + loss_dict['loss_dice'] = losses_dice[-1] + # loss from other decoder layers + num_dec_layer = 0 + for loss_cls_i, loss_mask_i, loss_dice_i in zip( + losses_cls[:-1], losses_mask[:-1], losses_dice[:-1]): + loss_dict[f'd{num_dec_layer}.loss_cls'] = loss_cls_i + loss_dict[f'd{num_dec_layer}.loss_mask'] = loss_mask_i + loss_dict[f'd{num_dec_layer}.loss_dice'] = loss_dice_i + num_dec_layer += 1 + return loss_dict + + def _loss_by_feat_single(self, cls_scores: Tensor, mask_preds: Tensor, + batch_gt_instances: List[InstanceData], + batch_img_metas: List[dict]) -> Tuple[Tensor]: + """Loss function for outputs from a single decoder layer. + + Args: + cls_scores (Tensor): Mask score logits from a single decoder layer + for all images. Shape (batch_size, num_queries, + cls_out_channels). Note `cls_out_channels` should includes + background. + mask_preds (Tensor): Mask logits for a pixel decoder for all + images. Shape (batch_size, num_queries, h, w). + batch_gt_instances (list[obj:`InstanceData`]): each contains + ``labels`` and ``masks``. + batch_img_metas (list[dict]): List of image meta information. + + Returns: + tuple[Tensor]: Loss components for outputs from a single decoder\ + layer. + """ + num_imgs = cls_scores.size(0) + cls_scores_list = [cls_scores[i] for i in range(num_imgs)] + mask_preds_list = [mask_preds[i] for i in range(num_imgs)] + + (labels_list, label_weights_list, mask_targets_list, mask_weights_list, + avg_factor) = self.get_targets(cls_scores_list, mask_preds_list, + batch_gt_instances, batch_img_metas) + # shape (batch_size, num_queries) + labels = torch.stack(labels_list, dim=0) + # shape (batch_size, num_queries) + label_weights = torch.stack(label_weights_list, dim=0) + # shape (num_total_gts, h, w) + mask_targets = torch.cat(mask_targets_list, dim=0) + # shape (batch_size, num_queries) + mask_weights = torch.stack(mask_weights_list, dim=0) + + # classfication loss + # shape (batch_size * num_queries, ) + cls_scores = cls_scores.flatten(0, 1) + labels = labels.flatten(0, 1) + label_weights = label_weights.flatten(0, 1) + + class_weight = cls_scores.new_tensor(self.class_weight) + loss_cls = self.loss_cls( + cls_scores, + labels, + label_weights, + avg_factor=class_weight[labels].sum()) + + num_total_masks = reduce_mean(cls_scores.new_tensor([avg_factor])) + num_total_masks = max(num_total_masks, 1) + + # extract positive ones + # shape (batch_size, num_queries, h, w) -> (num_total_gts, h, w) + mask_preds = mask_preds[mask_weights > 0] + target_shape = mask_targets.shape[-2:] + + if mask_targets.shape[0] == 0: + # zero match + loss_dice = mask_preds.sum() + loss_mask = mask_preds.sum() + return loss_cls, loss_mask, loss_dice + + # upsample to shape of target + # shape (num_total_gts, h, w) + mask_preds = F.interpolate( + mask_preds.unsqueeze(1), + target_shape, + mode='bilinear', + align_corners=False).squeeze(1) + + # dice loss + loss_dice = self.loss_dice( + mask_preds, mask_targets, avg_factor=num_total_masks) + + # mask loss + # FocalLoss support input of shape (n, num_class) + h, w = mask_preds.shape[-2:] + # shape (num_total_gts, h, w) -> (num_total_gts * h * w, 1) + mask_preds = mask_preds.reshape(-1, 1) + # shape (num_total_gts, h, w) -> (num_total_gts * h * w) + mask_targets = mask_targets.reshape(-1) + # target is (1 - mask_targets) !!! + loss_mask = self.loss_mask( + mask_preds, 1 - mask_targets, avg_factor=num_total_masks * h * w) + + return loss_cls, loss_mask, loss_dice + + def forward(self, x: Tuple[Tensor], + batch_data_samples: SampleList) -> Tuple[Tensor]: + """Forward function. + + Args: + x (tuple[Tensor]): Features from the upstream network, each + is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + tuple[Tensor]: a tuple contains two elements. + + - all_cls_scores (Tensor): Classification scores for each\ + scale level. Each is a 4D-tensor with shape\ + (num_decoder, batch_size, num_queries, cls_out_channels).\ + Note `cls_out_channels` should includes background. + - all_mask_preds (Tensor): Mask scores for each decoder\ + layer. Each with shape (num_decoder, batch_size,\ + num_queries, h, w). + """ + batch_img_metas = [ + data_sample.metainfo for data_sample in batch_data_samples + ] + batch_size = x[0].shape[0] + input_img_h, input_img_w = batch_img_metas[0]['batch_input_shape'] + padding_mask = x[-1].new_ones((batch_size, input_img_h, input_img_w), + dtype=torch.float32) + for i in range(batch_size): + img_h, img_w = batch_img_metas[i]['img_shape'] + padding_mask[i, :img_h, :img_w] = 0 + padding_mask = F.interpolate( + padding_mask.unsqueeze(1), size=x[-1].shape[-2:], + mode='nearest').to(torch.bool).squeeze(1) + # when backbone is swin, memory is output of last stage of swin. + # when backbone is r50, memory is output of tranformer encoder. + mask_features, memory = self.pixel_decoder(x, batch_img_metas) + pos_embed = self.decoder_pe(padding_mask) + memory = self.decoder_input_proj(memory) + # shape (batch_size, c, h, w) -> (batch_size, h*w, c) + memory = memory.flatten(2).permute(0, 2, 1) + pos_embed = pos_embed.flatten(2).permute(0, 2, 1) + # shape (batch_size, h * w) + padding_mask = padding_mask.flatten(1) + # shape = (num_queries, embed_dims) + query_embed = self.query_embed.weight + # shape = (batch_size, num_queries, embed_dims) + query_embed = query_embed.unsqueeze(0).repeat(batch_size, 1, 1) + target = torch.zeros_like(query_embed) + # shape (num_decoder, num_queries, batch_size, embed_dims) + out_dec = self.transformer_decoder( + query=target, + key=memory, + value=memory, + query_pos=query_embed, + key_pos=pos_embed, + key_padding_mask=padding_mask) + + # cls_scores + all_cls_scores = self.cls_embed(out_dec) + + # mask_preds + mask_embed = self.mask_embed(out_dec) + all_mask_preds = torch.einsum('lbqc,bchw->lbqhw', mask_embed, + mask_features) + + return all_cls_scores, all_mask_preds + + def loss( + self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + ) -> Dict[str, Tensor]: + """Perform forward propagation and loss calculation of the panoptic + head on the features of the upstream network. + + Args: + x (tuple[Tensor]): Multi-level features from the upstream + network, each is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + batch_img_metas = [] + batch_gt_instances = [] + batch_gt_semantic_segs = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + if 'gt_sem_seg' in data_sample: + batch_gt_semantic_segs.append(data_sample.gt_sem_seg) + else: + batch_gt_semantic_segs.append(None) + + # forward + all_cls_scores, all_mask_preds = self(x, batch_data_samples) + + # preprocess ground truth + batch_gt_instances = self.preprocess_gt(batch_gt_instances, + batch_gt_semantic_segs) + + # loss + losses = self.loss_by_feat(all_cls_scores, all_mask_preds, + batch_gt_instances, batch_img_metas) + + return losses + + def predict(self, x: Tuple[Tensor], + batch_data_samples: SampleList) -> Tuple[Tensor]: + """Test without augmentaton. + + Args: + x (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + tuple[Tensor]: A tuple contains two tensors. + + - mask_cls_results (Tensor): Mask classification logits,\ + shape (batch_size, num_queries, cls_out_channels). + Note `cls_out_channels` should includes background. + - mask_pred_results (Tensor): Mask logits, shape \ + (batch_size, num_queries, h, w). + """ + batch_img_metas = [ + data_sample.metainfo for data_sample in batch_data_samples + ] + all_cls_scores, all_mask_preds = self(x, batch_data_samples) + mask_cls_results = all_cls_scores[-1] + mask_pred_results = all_mask_preds[-1] + + # upsample masks + img_shape = batch_img_metas[0]['batch_input_shape'] + mask_pred_results = F.interpolate( + mask_pred_results, + size=(img_shape[0], img_shape[1]), + mode='bilinear', + align_corners=False) + + return mask_cls_results, mask_pred_results diff --git a/mmdetection/mmdet/models/dense_heads/nasfcos_head.py b/mmdetection/mmdet/models/dense_heads/nasfcos_head.py new file mode 100644 index 00000000..14ee62a7 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/nasfcos_head.py @@ -0,0 +1,114 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy + +import torch.nn as nn +from mmcv.cnn import ConvModule, Scale + +from mmdet.models.dense_heads.fcos_head import FCOSHead +from mmdet.registry import MODELS +from mmdet.utils import OptMultiConfig + + +@MODELS.register_module() +class NASFCOSHead(FCOSHead): + """Anchor-free head used in `NASFCOS `_. + + It is quite similar with FCOS head, except for the searched structure of + classification branch and bbox regression branch, where a structure of + "dconv3x3, conv3x3, dconv3x3, conv1x1" is utilized instead. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + strides (Sequence[int] or Sequence[Tuple[int, int]]): Strides of points + in multiple feature levels. Defaults to (4, 8, 16, 32, 64). + regress_ranges (Sequence[Tuple[int, int]]): Regress range of multiple + level points. + center_sampling (bool): If true, use center sampling. + Defaults to False. + center_sample_radius (float): Radius of center sampling. + Defaults to 1.5. + norm_on_bbox (bool): If true, normalize the regression targets with + FPN strides. Defaults to False. + centerness_on_reg (bool): If true, position centerness on the + regress branch. Please refer to https://github.com/tianzhi0549/FCOS/issues/89#issuecomment-516877042. + Defaults to False. + conv_bias (bool or str): If specified as `auto`, it will be decided by + the norm_cfg. Bias of conv will be set as True if `norm_cfg` is + None, otherwise False. Defaults to "auto". + loss_cls (:obj:`ConfigDict` or dict): Config of classification loss. + loss_bbox (:obj:`ConfigDict` or dict): Config of localization loss. + loss_centerness (:obj:`ConfigDict`, or dict): Config of centerness + loss. + norm_cfg (:obj:`ConfigDict` or dict): dictionary to construct and + config norm layer. Defaults to + ``norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)``. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], opitonal): Initialization config dict. + """ # noqa: E501 + + def __init__(self, + *args, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + if init_cfg is None: + init_cfg = [ + dict(type='Caffe2Xavier', layer=['ConvModule', 'Conv2d']), + dict( + type='Normal', + std=0.01, + override=[ + dict(name='conv_reg'), + dict(name='conv_centerness'), + dict( + name='conv_cls', + type='Normal', + std=0.01, + bias_prob=0.01) + ]), + ] + super().__init__(*args, init_cfg=init_cfg, **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + dconv3x3_config = dict( + type='DCNv2', + kernel_size=3, + use_bias=True, + deform_groups=2, + padding=1) + conv3x3_config = dict(type='Conv', kernel_size=3, padding=1) + conv1x1_config = dict(type='Conv', kernel_size=1) + + self.arch_config = [ + dconv3x3_config, conv3x3_config, dconv3x3_config, conv1x1_config + ] + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + for i, op_ in enumerate(self.arch_config): + op = copy.deepcopy(op_) + chn = self.in_channels if i == 0 else self.feat_channels + assert isinstance(op, dict) + use_bias = op.pop('use_bias', False) + padding = op.pop('padding', 0) + kernel_size = op.pop('kernel_size') + module = ConvModule( + chn, + self.feat_channels, + kernel_size, + stride=1, + padding=padding, + norm_cfg=self.norm_cfg, + bias=use_bias, + conv_cfg=op) + + self.cls_convs.append(copy.deepcopy(module)) + self.reg_convs.append(copy.deepcopy(module)) + + self.conv_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + self.conv_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1) + self.conv_centerness = nn.Conv2d(self.feat_channels, 1, 3, padding=1) + + self.scales = nn.ModuleList([Scale(1.0) for _ in self.strides]) diff --git a/mmdetection/mmdet/models/dense_heads/paa_head.py b/mmdetection/mmdet/models/dense_heads/paa_head.py new file mode 100644 index 00000000..3c1f453d --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/paa_head.py @@ -0,0 +1,730 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import numpy as np +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures.bbox import bbox_overlaps +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList) +from ..layers import multiclass_nms +from ..utils import levels_to_images, multi_apply +from . import ATSSHead + +EPS = 1e-12 +try: + import sklearn.mixture as skm +except ImportError: + skm = None + + +@MODELS.register_module() +class PAAHead(ATSSHead): + """Head of PAAAssignment: Probabilistic Anchor Assignment with IoU + Prediction for Object Detection. + + Code is modified from the `official github repo + `_. + + More details can be found in the `paper + `_ . + + Args: + topk (int): Select topk samples with smallest loss in + each level. + score_voting (bool): Whether to use score voting in post-process. + covariance_type : String describing the type of covariance parameters + to be used in :class:`sklearn.mixture.GaussianMixture`. + It must be one of: + + - 'full': each component has its own general covariance matrix + - 'tied': all components share the same general covariance matrix + - 'diag': each component has its own diagonal covariance matrix + - 'spherical': each component has its own single variance + Default: 'diag'. From 'full' to 'spherical', the gmm fitting + process is faster yet the performance could be influenced. For most + cases, 'diag' should be a good choice. + """ + + def __init__(self, + *args, + topk: int = 9, + score_voting: bool = True, + covariance_type: str = 'diag', + **kwargs): + # topk used in paa reassign process + self.topk = topk + self.with_score_voting = score_voting + self.covariance_type = covariance_type + super().__init__(*args, **kwargs) + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + iou_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + iou_preds (list[Tensor]): iou_preds for each scale + level with shape (N, num_anchors * 1, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss gmm_assignment. + """ + + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + ) + (labels, labels_weight, bboxes_target, bboxes_weight, pos_inds, + pos_gt_index) = cls_reg_targets + cls_scores = levels_to_images(cls_scores) + cls_scores = [ + item.reshape(-1, self.cls_out_channels) for item in cls_scores + ] + bbox_preds = levels_to_images(bbox_preds) + bbox_preds = [item.reshape(-1, 4) for item in bbox_preds] + iou_preds = levels_to_images(iou_preds) + iou_preds = [item.reshape(-1, 1) for item in iou_preds] + pos_losses_list, = multi_apply(self.get_pos_loss, anchor_list, + cls_scores, bbox_preds, labels, + labels_weight, bboxes_target, + bboxes_weight, pos_inds) + + with torch.no_grad(): + reassign_labels, reassign_label_weight, \ + reassign_bbox_weights, num_pos = multi_apply( + self.paa_reassign, + pos_losses_list, + labels, + labels_weight, + bboxes_weight, + pos_inds, + pos_gt_index, + anchor_list) + num_pos = sum(num_pos) + # convert all tensor list to a flatten tensor + cls_scores = torch.cat(cls_scores, 0).view(-1, cls_scores[0].size(-1)) + bbox_preds = torch.cat(bbox_preds, 0).view(-1, bbox_preds[0].size(-1)) + iou_preds = torch.cat(iou_preds, 0).view(-1, iou_preds[0].size(-1)) + labels = torch.cat(reassign_labels, 0).view(-1) + flatten_anchors = torch.cat( + [torch.cat(item, 0) for item in anchor_list]) + labels_weight = torch.cat(reassign_label_weight, 0).view(-1) + bboxes_target = torch.cat(bboxes_target, + 0).view(-1, bboxes_target[0].size(-1)) + + pos_inds_flatten = ((labels >= 0) + & + (labels < self.num_classes)).nonzero().reshape(-1) + + losses_cls = self.loss_cls( + cls_scores, + labels, + labels_weight, + avg_factor=max(num_pos, len(batch_img_metas))) # avoid num_pos=0 + if num_pos: + pos_bbox_pred = self.bbox_coder.decode( + flatten_anchors[pos_inds_flatten], + bbox_preds[pos_inds_flatten]) + pos_bbox_target = bboxes_target[pos_inds_flatten] + iou_target = bbox_overlaps( + pos_bbox_pred.detach(), pos_bbox_target, is_aligned=True) + losses_iou = self.loss_centerness( + iou_preds[pos_inds_flatten], + iou_target.unsqueeze(-1), + avg_factor=num_pos) + losses_bbox = self.loss_bbox( + pos_bbox_pred, + pos_bbox_target, + iou_target.clamp(min=EPS), + avg_factor=iou_target.sum()) + else: + losses_iou = iou_preds.sum() * 0 + losses_bbox = bbox_preds.sum() * 0 + + return dict( + loss_cls=losses_cls, loss_bbox=losses_bbox, loss_iou=losses_iou) + + def get_pos_loss(self, anchors: List[Tensor], cls_score: Tensor, + bbox_pred: Tensor, label: Tensor, label_weight: Tensor, + bbox_target: dict, bbox_weight: Tensor, + pos_inds: Tensor) -> Tensor: + """Calculate loss of all potential positive samples obtained from first + match process. + + Args: + anchors (list[Tensor]): Anchors of each scale. + cls_score (Tensor): Box scores of single image with shape + (num_anchors, num_classes) + bbox_pred (Tensor): Box energies / deltas of single image + with shape (num_anchors, 4) + label (Tensor): classification target of each anchor with + shape (num_anchors,) + label_weight (Tensor): Classification loss weight of each + anchor with shape (num_anchors). + bbox_target (dict): Regression target of each anchor with + shape (num_anchors, 4). + bbox_weight (Tensor): Bbox weight of each anchor with shape + (num_anchors, 4). + pos_inds (Tensor): Index of all positive samples got from + first assign process. + + Returns: + Tensor: Losses of all positive samples in single image. + """ + if not len(pos_inds): + return cls_score.new([]), + anchors_all_level = torch.cat(anchors, 0) + pos_scores = cls_score[pos_inds] + pos_bbox_pred = bbox_pred[pos_inds] + pos_label = label[pos_inds] + pos_label_weight = label_weight[pos_inds] + pos_bbox_target = bbox_target[pos_inds] + pos_bbox_weight = bbox_weight[pos_inds] + pos_anchors = anchors_all_level[pos_inds] + pos_bbox_pred = self.bbox_coder.decode(pos_anchors, pos_bbox_pred) + + # to keep loss dimension + loss_cls = self.loss_cls( + pos_scores, + pos_label, + pos_label_weight, + avg_factor=1.0, + reduction_override='none') + + loss_bbox = self.loss_bbox( + pos_bbox_pred, + pos_bbox_target, + pos_bbox_weight, + avg_factor=1.0, # keep same loss weight before reassign + reduction_override='none') + + loss_cls = loss_cls.sum(-1) + pos_loss = loss_bbox + loss_cls + return pos_loss, + + def paa_reassign(self, pos_losses: Tensor, label: Tensor, + label_weight: Tensor, bbox_weight: Tensor, + pos_inds: Tensor, pos_gt_inds: Tensor, + anchors: List[Tensor]) -> tuple: + """Fit loss to GMM distribution and separate positive, ignore, negative + samples again with GMM model. + + Args: + pos_losses (Tensor): Losses of all positive samples in + single image. + label (Tensor): classification target of each anchor with + shape (num_anchors,) + label_weight (Tensor): Classification loss weight of each + anchor with shape (num_anchors). + bbox_weight (Tensor): Bbox weight of each anchor with shape + (num_anchors, 4). + pos_inds (Tensor): Index of all positive samples got from + first assign process. + pos_gt_inds (Tensor): Gt_index of all positive samples got + from first assign process. + anchors (list[Tensor]): Anchors of each scale. + + Returns: + tuple: Usually returns a tuple containing learning targets. + + - label (Tensor): classification target of each anchor after + paa assign, with shape (num_anchors,) + - label_weight (Tensor): Classification loss weight of each + anchor after paa assign, with shape (num_anchors). + - bbox_weight (Tensor): Bbox weight of each anchor with shape + (num_anchors, 4). + - num_pos (int): The number of positive samples after paa + assign. + """ + if not len(pos_inds): + return label, label_weight, bbox_weight, 0 + label = label.clone() + label_weight = label_weight.clone() + bbox_weight = bbox_weight.clone() + num_gt = pos_gt_inds.max() + 1 + num_level = len(anchors) + num_anchors_each_level = [item.size(0) for item in anchors] + num_anchors_each_level.insert(0, 0) + inds_level_interval = np.cumsum(num_anchors_each_level) + pos_level_mask = [] + for i in range(num_level): + mask = (pos_inds >= inds_level_interval[i]) & ( + pos_inds < inds_level_interval[i + 1]) + pos_level_mask.append(mask) + pos_inds_after_paa = [label.new_tensor([])] + ignore_inds_after_paa = [label.new_tensor([])] + for gt_ind in range(num_gt): + pos_inds_gmm = [] + pos_loss_gmm = [] + gt_mask = pos_gt_inds == gt_ind + for level in range(num_level): + level_mask = pos_level_mask[level] + level_gt_mask = level_mask & gt_mask + value, topk_inds = pos_losses[level_gt_mask].topk( + min(level_gt_mask.sum(), self.topk), largest=False) + pos_inds_gmm.append(pos_inds[level_gt_mask][topk_inds]) + pos_loss_gmm.append(value) + pos_inds_gmm = torch.cat(pos_inds_gmm) + pos_loss_gmm = torch.cat(pos_loss_gmm) + # fix gmm need at least two sample + if len(pos_inds_gmm) < 2: + continue + device = pos_inds_gmm.device + pos_loss_gmm, sort_inds = pos_loss_gmm.sort() + pos_inds_gmm = pos_inds_gmm[sort_inds] + pos_loss_gmm = pos_loss_gmm.view(-1, 1).cpu().numpy() + min_loss, max_loss = pos_loss_gmm.min(), pos_loss_gmm.max() + means_init = np.array([min_loss, max_loss]).reshape(2, 1) + weights_init = np.array([0.5, 0.5]) + precisions_init = np.array([1.0, 1.0]).reshape(2, 1, 1) # full + if self.covariance_type == 'spherical': + precisions_init = precisions_init.reshape(2) + elif self.covariance_type == 'diag': + precisions_init = precisions_init.reshape(2, 1) + elif self.covariance_type == 'tied': + precisions_init = np.array([[1.0]]) + if skm is None: + raise ImportError('Please run "pip install sklearn" ' + 'to install sklearn first.') + gmm = skm.GaussianMixture( + 2, + weights_init=weights_init, + means_init=means_init, + precisions_init=precisions_init, + covariance_type=self.covariance_type) + gmm.fit(pos_loss_gmm) + gmm_assignment = gmm.predict(pos_loss_gmm) + scores = gmm.score_samples(pos_loss_gmm) + gmm_assignment = torch.from_numpy(gmm_assignment).to(device) + scores = torch.from_numpy(scores).to(device) + + pos_inds_temp, ignore_inds_temp = self.gmm_separation_scheme( + gmm_assignment, scores, pos_inds_gmm) + pos_inds_after_paa.append(pos_inds_temp) + ignore_inds_after_paa.append(ignore_inds_temp) + + pos_inds_after_paa = torch.cat(pos_inds_after_paa) + ignore_inds_after_paa = torch.cat(ignore_inds_after_paa) + reassign_mask = (pos_inds.unsqueeze(1) != pos_inds_after_paa).all(1) + reassign_ids = pos_inds[reassign_mask] + label[reassign_ids] = self.num_classes + label_weight[ignore_inds_after_paa] = 0 + bbox_weight[reassign_ids] = 0 + num_pos = len(pos_inds_after_paa) + return label, label_weight, bbox_weight, num_pos + + def gmm_separation_scheme(self, gmm_assignment: Tensor, scores: Tensor, + pos_inds_gmm: Tensor) -> Tuple[Tensor, Tensor]: + """A general separation scheme for gmm model. + + It separates a GMM distribution of candidate samples into three + parts, 0 1 and uncertain areas, and you can implement other + separation schemes by rewriting this function. + + Args: + gmm_assignment (Tensor): The prediction of GMM which is of shape + (num_samples,). The 0/1 value indicates the distribution + that each sample comes from. + scores (Tensor): The probability of sample coming from the + fit GMM distribution. The tensor is of shape (num_samples,). + pos_inds_gmm (Tensor): All the indexes of samples which are used + to fit GMM model. The tensor is of shape (num_samples,) + + Returns: + tuple[Tensor, Tensor]: The indices of positive and ignored samples. + + - pos_inds_temp (Tensor): Indices of positive samples. + - ignore_inds_temp (Tensor): Indices of ignore samples. + """ + # The implementation is (c) in Fig.3 in origin paper instead of (b). + # You can refer to issues such as + # https://github.com/kkhoot/PAA/issues/8 and + # https://github.com/kkhoot/PAA/issues/9. + fgs = gmm_assignment == 0 + pos_inds_temp = fgs.new_tensor([], dtype=torch.long) + ignore_inds_temp = fgs.new_tensor([], dtype=torch.long) + if fgs.nonzero().numel(): + _, pos_thr_ind = scores[fgs].topk(1) + pos_inds_temp = pos_inds_gmm[fgs][:pos_thr_ind + 1] + ignore_inds_temp = pos_inds_gmm.new_tensor([]) + return pos_inds_temp, ignore_inds_temp + + def get_targets(self, + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs: bool = True) -> tuple: + """Get targets for PAA head. + + This method is almost the same as `AnchorHead.get_targets()`. We direct + return the results from _get_targets_single instead map it to levels + by images_to_levels function. + + Args: + anchor_list (list[list[Tensor]]): Multi level anchors of each + image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_anchors, 4). + valid_flag_list (list[list[Tensor]]): Multi level valid flags of + each image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_anchors, ) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + + Returns: + tuple: Usually returns a tuple containing learning targets. + + - labels (list[Tensor]): Labels of all anchors, each with + shape (num_anchors,). + - label_weights (list[Tensor]): Label weights of all anchor. + each with shape (num_anchors,). + - bbox_targets (list[Tensor]): BBox targets of all anchors. + each with shape (num_anchors, 4). + - bbox_weights (list[Tensor]): BBox weights of all anchors. + each with shape (num_anchors, 4). + - pos_inds (list[Tensor]): Contains all index of positive + sample in all anchor. + - gt_inds (list[Tensor]): Contains all gt_index of positive + sample in all anchor. + """ + + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + concat_anchor_list = [] + concat_valid_flag_list = [] + for i in range(num_imgs): + assert len(anchor_list[i]) == len(valid_flag_list[i]) + concat_anchor_list.append(torch.cat(anchor_list[i])) + concat_valid_flag_list.append(torch.cat(valid_flag_list[i])) + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + results = multi_apply( + self._get_targets_single, + concat_anchor_list, + concat_valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + + (labels, label_weights, bbox_targets, bbox_weights, valid_pos_inds, + valid_neg_inds, sampling_result) = results + + # Due to valid flag of anchors, we have to calculate the real pos_inds + # in origin anchor set. + pos_inds = [] + for i, single_labels in enumerate(labels): + pos_mask = (0 <= single_labels) & ( + single_labels < self.num_classes) + pos_inds.append(pos_mask.nonzero().view(-1)) + + gt_inds = [item.pos_assigned_gt_inds for item in sampling_result] + return (labels, label_weights, bbox_targets, bbox_weights, pos_inds, + gt_inds) + + def _get_targets_single(self, + flat_anchors: Tensor, + valid_flags: Tensor, + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression and classification targets for anchors in a + single image. + + This method is same as `AnchorHead._get_targets_single()`. + """ + assert unmap_outputs, 'We must map outputs back to the original' \ + 'set of anchors in PAAhead' + return super(ATSSHead, self)._get_targets_single( + flat_anchors, + valid_flags, + gt_instances, + img_meta, + gt_instances_ignore, + unmap_outputs=True) + + def predict_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + score_factors: Optional[List[Tensor]] = None, + batch_img_metas: Optional[List[dict]] = None, + cfg: OptConfigType = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + This method is same as `BaseDenseHead.get_results()`. + """ + assert with_nms, 'PAA only supports "with_nms=True" now and it ' \ + 'means PAAHead does not support ' \ + 'test-time augmentation' + return super().predict_by_feat( + cls_scores=cls_scores, + bbox_preds=bbox_preds, + score_factors=score_factors, + batch_img_metas=batch_img_metas, + cfg=cfg, + rescale=rescale, + with_nms=with_nms) + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: OptConfigType = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factors from all scale + levels of a single image, each item has shape + (num_priors * 1, H, W). + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid, has shape + (num_priors, 4). + img_meta (dict): Image meta info. + cfg (:obj:`ConfigDict` or dict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Default: False. + with_nms (bool): If True, do nms before return boxes. + Default: True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + cfg = self.test_cfg if cfg is None else cfg + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bboxes = [] + mlvl_scores = [] + mlvl_score_factors = [] + for level_idx, (cls_score, bbox_pred, score_factor, priors) in \ + enumerate(zip(cls_score_list, bbox_pred_list, + score_factor_list, mlvl_priors)): + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + + scores = cls_score.permute(1, 2, 0).reshape( + -1, self.cls_out_channels).sigmoid() + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4) + score_factor = score_factor.permute(1, 2, 0).reshape(-1).sigmoid() + + if 0 < nms_pre < scores.shape[0]: + max_scores, _ = (scores * + score_factor[:, None]).sqrt().max(dim=1) + _, topk_inds = max_scores.topk(nms_pre) + priors = priors[topk_inds, :] + bbox_pred = bbox_pred[topk_inds, :] + scores = scores[topk_inds, :] + score_factor = score_factor[topk_inds] + + bboxes = self.bbox_coder.decode( + priors, bbox_pred, max_shape=img_shape) + mlvl_bboxes.append(bboxes) + mlvl_scores.append(scores) + mlvl_score_factors.append(score_factor) + + results = InstanceData() + results.bboxes = torch.cat(mlvl_bboxes) + results.scores = torch.cat(mlvl_scores) + results.score_factors = torch.cat(mlvl_score_factors) + + return self._bbox_post_process(results, cfg, rescale, with_nms, + img_meta) + + def _bbox_post_process(self, + results: InstanceData, + cfg: ConfigType, + rescale: bool = False, + with_nms: bool = True, + img_meta: Optional[dict] = None): + """bbox post-processing method. + + The boxes would be rescaled to the original image scale and do + the nms operation. Usually with_nms is False is used for aug test. + + Args: + results (:obj:`InstaceData`): Detection instance results, + each item has shape (num_bboxes, ). + cfg (:obj:`ConfigDict` or dict): Test / postprocessing + configuration, if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Default: False. + with_nms (bool): If True, do nms before return boxes. + Default: True. + img_meta (dict, optional): Image meta info. Defaults to None. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + if rescale: + results.bboxes /= results.bboxes.new_tensor( + img_meta['scale_factor']).repeat((1, 2)) + # Add a dummy background class to the backend when using sigmoid + # remind that we set FG labels to [0, num_class-1] since mmdet v2.0 + # BG cat_id: num_class + padding = results.scores.new_zeros(results.scores.shape[0], 1) + mlvl_scores = torch.cat([results.scores, padding], dim=1) + + mlvl_nms_scores = (mlvl_scores * results.score_factors[:, None]).sqrt() + det_bboxes, det_labels = multiclass_nms( + results.bboxes, + mlvl_nms_scores, + cfg.score_thr, + cfg.nms, + cfg.max_per_img, + score_factors=None) + if self.with_score_voting and len(det_bboxes) > 0: + det_bboxes, det_labels = self.score_voting(det_bboxes, det_labels, + results.bboxes, + mlvl_nms_scores, + cfg.score_thr) + nms_results = InstanceData() + nms_results.bboxes = det_bboxes[:, :-1] + nms_results.scores = det_bboxes[:, -1] + nms_results.labels = det_labels + return nms_results + + def score_voting(self, det_bboxes: Tensor, det_labels: Tensor, + mlvl_bboxes: Tensor, mlvl_nms_scores: Tensor, + score_thr: float) -> Tuple[Tensor, Tensor]: + """Implementation of score voting method works on each remaining boxes + after NMS procedure. + + Args: + det_bboxes (Tensor): Remaining boxes after NMS procedure, + with shape (k, 5), each dimension means + (x1, y1, x2, y2, score). + det_labels (Tensor): The label of remaining boxes, with shape + (k, 1),Labels are 0-based. + mlvl_bboxes (Tensor): All boxes before the NMS procedure, + with shape (num_anchors,4). + mlvl_nms_scores (Tensor): The scores of all boxes which is used + in the NMS procedure, with shape (num_anchors, num_class) + score_thr (float): The score threshold of bboxes. + + Returns: + tuple: Usually returns a tuple containing voting results. + + - det_bboxes_voted (Tensor): Remaining boxes after + score voting procedure, with shape (k, 5), each + dimension means (x1, y1, x2, y2, score). + - det_labels_voted (Tensor): Label of remaining bboxes + after voting, with shape (num_anchors,). + """ + candidate_mask = mlvl_nms_scores > score_thr + candidate_mask_nonzeros = candidate_mask.nonzero(as_tuple=False) + candidate_inds = candidate_mask_nonzeros[:, 0] + candidate_labels = candidate_mask_nonzeros[:, 1] + candidate_bboxes = mlvl_bboxes[candidate_inds] + candidate_scores = mlvl_nms_scores[candidate_mask] + det_bboxes_voted = [] + det_labels_voted = [] + for cls in range(self.cls_out_channels): + candidate_cls_mask = candidate_labels == cls + if not candidate_cls_mask.any(): + continue + candidate_cls_scores = candidate_scores[candidate_cls_mask] + candidate_cls_bboxes = candidate_bboxes[candidate_cls_mask] + det_cls_mask = det_labels == cls + det_cls_bboxes = det_bboxes[det_cls_mask].view( + -1, det_bboxes.size(-1)) + det_candidate_ious = bbox_overlaps(det_cls_bboxes[:, :4], + candidate_cls_bboxes) + for det_ind in range(len(det_cls_bboxes)): + single_det_ious = det_candidate_ious[det_ind] + pos_ious_mask = single_det_ious > 0.01 + pos_ious = single_det_ious[pos_ious_mask] + pos_bboxes = candidate_cls_bboxes[pos_ious_mask] + pos_scores = candidate_cls_scores[pos_ious_mask] + pis = (torch.exp(-(1 - pos_ious)**2 / 0.025) * + pos_scores)[:, None] + voted_box = torch.sum( + pis * pos_bboxes, dim=0) / torch.sum( + pis, dim=0) + voted_score = det_cls_bboxes[det_ind][-1:][None, :] + det_bboxes_voted.append( + torch.cat((voted_box[None, :], voted_score), dim=1)) + det_labels_voted.append(cls) + + det_bboxes_voted = torch.cat(det_bboxes_voted, dim=0) + det_labels_voted = det_labels.new_tensor(det_labels_voted) + return det_bboxes_voted, det_labels_voted diff --git a/mmdetection/mmdet/models/dense_heads/pisa_retinanet_head.py b/mmdetection/mmdet/models/dense_heads/pisa_retinanet_head.py new file mode 100644 index 00000000..85fd54f5 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/pisa_retinanet_head.py @@ -0,0 +1,154 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import InstanceList, OptInstanceList +from ..losses import carl_loss, isr_p +from ..utils import images_to_levels +from .retina_head import RetinaHead + + +@MODELS.register_module() +class PISARetinaHead(RetinaHead): + """PISA Retinanet Head. + + The head owns the same structure with Retinanet Head, but differs in two + aspects: + 1. Importance-based Sample Reweighting Positive (ISR-P) is applied to + change the positive loss weights. + 2. Classification-aware regression loss is adopted as a third loss. + """ + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Compute losses of the head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: Loss dict, comprise classification loss, regression loss and + carl loss. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + label_channels = self.cls_out_channels if self.use_sigmoid_cls else 1 + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + return_sampling_results=True) + if cls_reg_targets is None: + return None + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + avg_factor, sampling_results_list) = cls_reg_targets + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + # concat all level anchors and flags to a single tensor + concat_anchor_list = [] + for i in range(len(anchor_list)): + concat_anchor_list.append(torch.cat(anchor_list[i])) + all_anchor_list = images_to_levels(concat_anchor_list, + num_level_anchors) + + num_imgs = len(batch_img_metas) + flatten_cls_scores = [ + cls_score.permute(0, 2, 3, 1).reshape(num_imgs, -1, label_channels) + for cls_score in cls_scores + ] + flatten_cls_scores = torch.cat( + flatten_cls_scores, dim=1).reshape(-1, + flatten_cls_scores[0].size(-1)) + flatten_bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, 4) + for bbox_pred in bbox_preds + ] + flatten_bbox_preds = torch.cat( + flatten_bbox_preds, dim=1).view(-1, flatten_bbox_preds[0].size(-1)) + flatten_labels = torch.cat(labels_list, dim=1).reshape(-1) + flatten_label_weights = torch.cat( + label_weights_list, dim=1).reshape(-1) + flatten_anchors = torch.cat(all_anchor_list, dim=1).reshape(-1, 4) + flatten_bbox_targets = torch.cat( + bbox_targets_list, dim=1).reshape(-1, 4) + flatten_bbox_weights = torch.cat( + bbox_weights_list, dim=1).reshape(-1, 4) + + # Apply ISR-P + isr_cfg = self.train_cfg.get('isr', None) + if isr_cfg is not None: + all_targets = (flatten_labels, flatten_label_weights, + flatten_bbox_targets, flatten_bbox_weights) + with torch.no_grad(): + all_targets = isr_p( + flatten_cls_scores, + flatten_bbox_preds, + all_targets, + flatten_anchors, + sampling_results_list, + bbox_coder=self.bbox_coder, + loss_cls=self.loss_cls, + num_class=self.num_classes, + **self.train_cfg['isr']) + (flatten_labels, flatten_label_weights, flatten_bbox_targets, + flatten_bbox_weights) = all_targets + + # For convenience we compute loss once instead separating by fpn level, + # so that we don't need to separate the weights by level again. + # The result should be the same + losses_cls = self.loss_cls( + flatten_cls_scores, + flatten_labels, + flatten_label_weights, + avg_factor=avg_factor) + losses_bbox = self.loss_bbox( + flatten_bbox_preds, + flatten_bbox_targets, + flatten_bbox_weights, + avg_factor=avg_factor) + loss_dict = dict(loss_cls=losses_cls, loss_bbox=losses_bbox) + + # CARL Loss + carl_cfg = self.train_cfg.get('carl', None) + if carl_cfg is not None: + loss_carl = carl_loss( + flatten_cls_scores, + flatten_labels, + flatten_bbox_preds, + flatten_bbox_targets, + self.loss_bbox, + **self.train_cfg['carl'], + avg_factor=avg_factor, + sigmoid=True, + num_class=self.num_classes) + loss_dict.update(loss_carl) + + return loss_dict diff --git a/mmdetection/mmdet/models/dense_heads/pisa_ssd_head.py b/mmdetection/mmdet/models/dense_heads/pisa_ssd_head.py new file mode 100644 index 00000000..ec09cb40 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/pisa_ssd_head.py @@ -0,0 +1,182 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Union + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import InstanceList, OptInstanceList +from ..losses import CrossEntropyLoss, SmoothL1Loss, carl_loss, isr_p +from ..utils import multi_apply +from .ssd_head import SSDHead + + +# TODO: add loss evaluator for SSD +@MODELS.register_module() +class PISASSDHead(SSDHead): + """Implementation of `PISA SSD head `_ + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (Sequence[int]): Number of channels in the input feature + map. + stacked_convs (int): Number of conv layers in cls and reg tower. + Defaults to 0. + feat_channels (int): Number of hidden channels when stacked_convs + > 0. Defaults to 256. + use_depthwise (bool): Whether to use DepthwiseSeparableConv. + Defaults to False. + conv_cfg (:obj:`ConfigDict` or dict, Optional): Dictionary to construct + and config conv layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict, Optional): Dictionary to construct + and config norm layer. Defaults to None. + act_cfg (:obj:`ConfigDict` or dict, Optional): Dictionary to construct + and config activation layer. Defaults to None. + anchor_generator (:obj:`ConfigDict` or dict): Config dict for anchor + generator. + bbox_coder (:obj:`ConfigDict` or dict): Config of bounding box coder. + reg_decoded_bbox (bool): If true, the regression loss would be + applied directly on decoded bounding boxes, converting both + the predicted boxes and regression targets to absolute + coordinates format. Defaults to False. It should be `True` when + using `IoULoss`, `GIoULoss`, or `DIoULoss` in the bbox head. + train_cfg (:obj:`ConfigDict` or dict, Optional): Training config of + anchor head. + test_cfg (:obj:`ConfigDict` or dict, Optional): Testing config of + anchor head. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], Optional): Initialization config dict. + """ # noqa: W605 + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Union[List[Tensor], Tensor]]: + """Compute losses of the head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Union[List[Tensor], Tensor]]: A dictionary of loss + components. the dict has components below: + + - loss_cls (list[Tensor]): A list containing each feature map \ + classification loss. + - loss_bbox (list[Tensor]): A list containing each feature map \ + regression loss. + - loss_carl (Tensor): The loss of CARL. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + unmap_outputs=False, + return_sampling_results=True) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + avg_factor, sampling_results_list) = cls_reg_targets + + num_images = len(batch_img_metas) + all_cls_scores = torch.cat([ + s.permute(0, 2, 3, 1).reshape( + num_images, -1, self.cls_out_channels) for s in cls_scores + ], 1) + all_labels = torch.cat(labels_list, -1).view(num_images, -1) + all_label_weights = torch.cat(label_weights_list, + -1).view(num_images, -1) + all_bbox_preds = torch.cat([ + b.permute(0, 2, 3, 1).reshape(num_images, -1, 4) + for b in bbox_preds + ], -2) + all_bbox_targets = torch.cat(bbox_targets_list, + -2).view(num_images, -1, 4) + all_bbox_weights = torch.cat(bbox_weights_list, + -2).view(num_images, -1, 4) + + # concat all level anchors to a single tensor + all_anchors = [] + for i in range(num_images): + all_anchors.append(torch.cat(anchor_list[i])) + + isr_cfg = self.train_cfg.get('isr', None) + all_targets = (all_labels.view(-1), all_label_weights.view(-1), + all_bbox_targets.view(-1, + 4), all_bbox_weights.view(-1, 4)) + # apply ISR-P + if isr_cfg is not None: + all_targets = isr_p( + all_cls_scores.view(-1, all_cls_scores.size(-1)), + all_bbox_preds.view(-1, 4), + all_targets, + torch.cat(all_anchors), + sampling_results_list, + loss_cls=CrossEntropyLoss(), + bbox_coder=self.bbox_coder, + **self.train_cfg['isr'], + num_class=self.num_classes) + (new_labels, new_label_weights, new_bbox_targets, + new_bbox_weights) = all_targets + all_labels = new_labels.view(all_labels.shape) + all_label_weights = new_label_weights.view(all_label_weights.shape) + all_bbox_targets = new_bbox_targets.view(all_bbox_targets.shape) + all_bbox_weights = new_bbox_weights.view(all_bbox_weights.shape) + + # add CARL loss + carl_loss_cfg = self.train_cfg.get('carl', None) + if carl_loss_cfg is not None: + loss_carl = carl_loss( + all_cls_scores.view(-1, all_cls_scores.size(-1)), + all_targets[0], + all_bbox_preds.view(-1, 4), + all_targets[2], + SmoothL1Loss(beta=1.), + **self.train_cfg['carl'], + avg_factor=avg_factor, + num_class=self.num_classes) + + # check NaN and Inf + assert torch.isfinite(all_cls_scores).all().item(), \ + 'classification scores become infinite or NaN!' + assert torch.isfinite(all_bbox_preds).all().item(), \ + 'bbox predications become infinite or NaN!' + + losses_cls, losses_bbox = multi_apply( + self.loss_by_feat_single, + all_cls_scores, + all_bbox_preds, + all_anchors, + all_labels, + all_label_weights, + all_bbox_targets, + all_bbox_weights, + avg_factor=avg_factor) + loss_dict = dict(loss_cls=losses_cls, loss_bbox=losses_bbox) + if carl_loss_cfg is not None: + loss_dict.update(loss_carl) + return loss_dict diff --git a/mmdetection/mmdet/models/dense_heads/reppoints_head.py b/mmdetection/mmdet/models/dense_heads/reppoints_head.py new file mode 100644 index 00000000..22f3e340 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/reppoints_head.py @@ -0,0 +1,885 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Sequence, Tuple + +import numpy as np +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmcv.ops import DeformConv2d +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.utils import ConfigType, InstanceList, MultiConfig, OptInstanceList +from ..task_modules.prior_generators import MlvlPointGenerator +from ..task_modules.samplers import PseudoSampler +from ..utils import (filter_scores_and_topk, images_to_levels, multi_apply, + unmap) +from .anchor_free_head import AnchorFreeHead + + +@MODELS.register_module() +class RepPointsHead(AnchorFreeHead): + """RepPoint head. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + point_feat_channels (int): Number of channels of points features. + num_points (int): Number of points. + gradient_mul (float): The multiplier to gradients from + points refinement and recognition. + point_strides (Sequence[int]): points strides. + point_base_scale (int): bbox scale for assigning labels. + loss_cls (:obj:`ConfigDict` or dict): Config of classification loss. + loss_bbox_init (:obj:`ConfigDict` or dict): Config of initial points + loss. + loss_bbox_refine (:obj:`ConfigDict` or dict): Config of points loss in + refinement. + use_grid_points (bool): If we use bounding box representation, the + reppoints is represented as grid points on the bounding box. + center_init (bool): Whether to use center point assignment. + transform_method (str): The methods to transform RepPoints to bbox. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. + """ # noqa: W605 + + def __init__(self, + num_classes: int, + in_channels: int, + point_feat_channels: int = 256, + num_points: int = 9, + gradient_mul: float = 0.1, + point_strides: Sequence[int] = [8, 16, 32, 64, 128], + point_base_scale: int = 4, + loss_cls: ConfigType = dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox_init: ConfigType = dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=0.5), + loss_bbox_refine: ConfigType = dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0), + use_grid_points: bool = False, + center_init: bool = True, + transform_method: str = 'moment', + moment_mul: float = 0.01, + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', + name='reppoints_cls_out', + std=0.01, + bias_prob=0.01)), + **kwargs) -> None: + self.num_points = num_points + self.point_feat_channels = point_feat_channels + self.use_grid_points = use_grid_points + self.center_init = center_init + + # we use deform conv to extract points features + self.dcn_kernel = int(np.sqrt(num_points)) + self.dcn_pad = int((self.dcn_kernel - 1) / 2) + assert self.dcn_kernel * self.dcn_kernel == num_points, \ + 'The points number should be a square number.' + assert self.dcn_kernel % 2 == 1, \ + 'The points number should be an odd square number.' + dcn_base = np.arange(-self.dcn_pad, + self.dcn_pad + 1).astype(np.float64) + dcn_base_y = np.repeat(dcn_base, self.dcn_kernel) + dcn_base_x = np.tile(dcn_base, self.dcn_kernel) + dcn_base_offset = np.stack([dcn_base_y, dcn_base_x], axis=1).reshape( + (-1)) + self.dcn_base_offset = torch.tensor(dcn_base_offset).view(1, -1, 1, 1) + + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + loss_cls=loss_cls, + init_cfg=init_cfg, + **kwargs) + + self.gradient_mul = gradient_mul + self.point_base_scale = point_base_scale + self.point_strides = point_strides + self.prior_generator = MlvlPointGenerator( + self.point_strides, offset=0.) + + if self.train_cfg: + self.init_assigner = TASK_UTILS.build( + self.train_cfg['init']['assigner']) + self.refine_assigner = TASK_UTILS.build( + self.train_cfg['refine']['assigner']) + + if self.train_cfg.get('sampler', None) is not None: + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], default_args=dict(context=self)) + else: + self.sampler = PseudoSampler(context=self) + + self.transform_method = transform_method + if self.transform_method == 'moment': + self.moment_transfer = nn.Parameter( + data=torch.zeros(2), requires_grad=True) + self.moment_mul = moment_mul + + self.use_sigmoid_cls = loss_cls.get('use_sigmoid', False) + if self.use_sigmoid_cls: + self.cls_out_channels = self.num_classes + else: + self.cls_out_channels = self.num_classes + 1 + self.loss_bbox_init = MODELS.build(loss_bbox_init) + self.loss_bbox_refine = MODELS.build(loss_bbox_refine) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.relu = nn.ReLU(inplace=True) + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + pts_out_dim = 4 if self.use_grid_points else 2 * self.num_points + self.reppoints_cls_conv = DeformConv2d(self.feat_channels, + self.point_feat_channels, + self.dcn_kernel, 1, + self.dcn_pad) + self.reppoints_cls_out = nn.Conv2d(self.point_feat_channels, + self.cls_out_channels, 1, 1, 0) + self.reppoints_pts_init_conv = nn.Conv2d(self.feat_channels, + self.point_feat_channels, 3, + 1, 1) + self.reppoints_pts_init_out = nn.Conv2d(self.point_feat_channels, + pts_out_dim, 1, 1, 0) + self.reppoints_pts_refine_conv = DeformConv2d(self.feat_channels, + self.point_feat_channels, + self.dcn_kernel, 1, + self.dcn_pad) + self.reppoints_pts_refine_out = nn.Conv2d(self.point_feat_channels, + pts_out_dim, 1, 1, 0) + + def points2bbox(self, pts: Tensor, y_first: bool = True) -> Tensor: + """Converting the points set into bounding box. + + Args: + pts (Tensor): the input points sets (fields), each points + set (fields) is represented as 2n scalar. + y_first (bool): if y_first=True, the point set is + represented as [y1, x1, y2, x2 ... yn, xn], otherwise + the point set is represented as + [x1, y1, x2, y2 ... xn, yn]. Defaults to True. + + Returns: + Tensor: each points set is converting to a bbox [x1, y1, x2, y2]. + """ + pts_reshape = pts.view(pts.shape[0], -1, 2, *pts.shape[2:]) + pts_y = pts_reshape[:, :, 0, ...] if y_first else pts_reshape[:, :, 1, + ...] + pts_x = pts_reshape[:, :, 1, ...] if y_first else pts_reshape[:, :, 0, + ...] + if self.transform_method == 'minmax': + bbox_left = pts_x.min(dim=1, keepdim=True)[0] + bbox_right = pts_x.max(dim=1, keepdim=True)[0] + bbox_up = pts_y.min(dim=1, keepdim=True)[0] + bbox_bottom = pts_y.max(dim=1, keepdim=True)[0] + bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom], + dim=1) + elif self.transform_method == 'partial_minmax': + pts_y = pts_y[:, :4, ...] + pts_x = pts_x[:, :4, ...] + bbox_left = pts_x.min(dim=1, keepdim=True)[0] + bbox_right = pts_x.max(dim=1, keepdim=True)[0] + bbox_up = pts_y.min(dim=1, keepdim=True)[0] + bbox_bottom = pts_y.max(dim=1, keepdim=True)[0] + bbox = torch.cat([bbox_left, bbox_up, bbox_right, bbox_bottom], + dim=1) + elif self.transform_method == 'moment': + pts_y_mean = pts_y.mean(dim=1, keepdim=True) + pts_x_mean = pts_x.mean(dim=1, keepdim=True) + pts_y_std = torch.std(pts_y - pts_y_mean, dim=1, keepdim=True) + pts_x_std = torch.std(pts_x - pts_x_mean, dim=1, keepdim=True) + moment_transfer = (self.moment_transfer * self.moment_mul) + ( + self.moment_transfer.detach() * (1 - self.moment_mul)) + moment_width_transfer = moment_transfer[0] + moment_height_transfer = moment_transfer[1] + half_width = pts_x_std * torch.exp(moment_width_transfer) + half_height = pts_y_std * torch.exp(moment_height_transfer) + bbox = torch.cat([ + pts_x_mean - half_width, pts_y_mean - half_height, + pts_x_mean + half_width, pts_y_mean + half_height + ], + dim=1) + else: + raise NotImplementedError + return bbox + + def gen_grid_from_reg(self, reg: Tensor, + previous_boxes: Tensor) -> Tuple[Tensor]: + """Base on the previous bboxes and regression values, we compute the + regressed bboxes and generate the grids on the bboxes. + + Args: + reg (Tensor): the regression value to previous bboxes. + previous_boxes (Tensor): previous bboxes. + + Returns: + Tuple[Tensor]: generate grids on the regressed bboxes. + """ + b, _, h, w = reg.shape + bxy = (previous_boxes[:, :2, ...] + previous_boxes[:, 2:, ...]) / 2. + bwh = (previous_boxes[:, 2:, ...] - + previous_boxes[:, :2, ...]).clamp(min=1e-6) + grid_topleft = bxy + bwh * reg[:, :2, ...] - 0.5 * bwh * torch.exp( + reg[:, 2:, ...]) + grid_wh = bwh * torch.exp(reg[:, 2:, ...]) + grid_left = grid_topleft[:, [0], ...] + grid_top = grid_topleft[:, [1], ...] + grid_width = grid_wh[:, [0], ...] + grid_height = grid_wh[:, [1], ...] + intervel = torch.linspace(0., 1., self.dcn_kernel).view( + 1, self.dcn_kernel, 1, 1).type_as(reg) + grid_x = grid_left + grid_width * intervel + grid_x = grid_x.unsqueeze(1).repeat(1, self.dcn_kernel, 1, 1, 1) + grid_x = grid_x.view(b, -1, h, w) + grid_y = grid_top + grid_height * intervel + grid_y = grid_y.unsqueeze(2).repeat(1, 1, self.dcn_kernel, 1, 1) + grid_y = grid_y.view(b, -1, h, w) + grid_yx = torch.stack([grid_y, grid_x], dim=2) + grid_yx = grid_yx.view(b, -1, h, w) + regressed_bbox = torch.cat([ + grid_left, grid_top, grid_left + grid_width, grid_top + grid_height + ], 1) + return grid_yx, regressed_bbox + + def forward(self, feats: Tuple[Tensor]) -> Tuple[Tensor]: + return multi_apply(self.forward_single, feats) + + def forward_single(self, x: Tensor) -> Tuple[Tensor]: + """Forward feature map of a single FPN level.""" + dcn_base_offset = self.dcn_base_offset.type_as(x) + # If we use center_init, the initial reppoints is from center points. + # If we use bounding bbox representation, the initial reppoints is + # from regular grid placed on a pre-defined bbox. + if self.use_grid_points or not self.center_init: + scale = self.point_base_scale / 2 + points_init = dcn_base_offset / dcn_base_offset.max() * scale + bbox_init = x.new_tensor([-scale, -scale, scale, + scale]).view(1, 4, 1, 1) + else: + points_init = 0 + cls_feat = x + pts_feat = x + for cls_conv in self.cls_convs: + cls_feat = cls_conv(cls_feat) + for reg_conv in self.reg_convs: + pts_feat = reg_conv(pts_feat) + # initialize reppoints + pts_out_init = self.reppoints_pts_init_out( + self.relu(self.reppoints_pts_init_conv(pts_feat))) + if self.use_grid_points: + pts_out_init, bbox_out_init = self.gen_grid_from_reg( + pts_out_init, bbox_init.detach()) + else: + pts_out_init = pts_out_init + points_init + # refine and classify reppoints + pts_out_init_grad_mul = (1 - self.gradient_mul) * pts_out_init.detach( + ) + self.gradient_mul * pts_out_init + dcn_offset = pts_out_init_grad_mul - dcn_base_offset + cls_out = self.reppoints_cls_out( + self.relu(self.reppoints_cls_conv(cls_feat, dcn_offset))) + pts_out_refine = self.reppoints_pts_refine_out( + self.relu(self.reppoints_pts_refine_conv(pts_feat, dcn_offset))) + if self.use_grid_points: + pts_out_refine, bbox_out_refine = self.gen_grid_from_reg( + pts_out_refine, bbox_out_init.detach()) + else: + pts_out_refine = pts_out_refine + pts_out_init.detach() + + if self.training: + return cls_out, pts_out_init, pts_out_refine + else: + return cls_out, self.points2bbox(pts_out_refine) + + def get_points(self, featmap_sizes: List[Tuple[int]], + batch_img_metas: List[dict], device: str) -> tuple: + """Get points according to feature map sizes. + + Args: + featmap_sizes (list[tuple]): Multi-level feature map sizes. + batch_img_metas (list[dict]): Image meta info. + + Returns: + tuple: points of each image, valid flags of each image + """ + num_imgs = len(batch_img_metas) + + # since feature map sizes of all images are the same, we only compute + # points center for one time + multi_level_points = self.prior_generator.grid_priors( + featmap_sizes, device=device, with_stride=True) + points_list = [[point.clone() for point in multi_level_points] + for _ in range(num_imgs)] + + # for each image, we compute valid flags of multi level grids + valid_flag_list = [] + for img_id, img_meta in enumerate(batch_img_metas): + multi_level_flags = self.prior_generator.valid_flags( + featmap_sizes, img_meta['pad_shape'], device=device) + valid_flag_list.append(multi_level_flags) + + return points_list, valid_flag_list + + def centers_to_bboxes(self, point_list: List[Tensor]) -> List[Tensor]: + """Get bboxes according to center points. + + Only used in :class:`MaxIoUAssigner`. + """ + bbox_list = [] + for i_img, point in enumerate(point_list): + bbox = [] + for i_lvl in range(len(self.point_strides)): + scale = self.point_base_scale * self.point_strides[i_lvl] * 0.5 + bbox_shift = torch.Tensor([-scale, -scale, scale, + scale]).view(1, 4).type_as(point[0]) + bbox_center = torch.cat( + [point[i_lvl][:, :2], point[i_lvl][:, :2]], dim=1) + bbox.append(bbox_center + bbox_shift) + bbox_list.append(bbox) + return bbox_list + + def offset_to_pts(self, center_list: List[Tensor], + pred_list: List[Tensor]) -> List[Tensor]: + """Change from point offset to point coordinate.""" + pts_list = [] + for i_lvl in range(len(self.point_strides)): + pts_lvl = [] + for i_img in range(len(center_list)): + pts_center = center_list[i_img][i_lvl][:, :2].repeat( + 1, self.num_points) + pts_shift = pred_list[i_lvl][i_img] + yx_pts_shift = pts_shift.permute(1, 2, 0).view( + -1, 2 * self.num_points) + y_pts_shift = yx_pts_shift[..., 0::2] + x_pts_shift = yx_pts_shift[..., 1::2] + xy_pts_shift = torch.stack([x_pts_shift, y_pts_shift], -1) + xy_pts_shift = xy_pts_shift.view(*yx_pts_shift.shape[:-1], -1) + pts = xy_pts_shift * self.point_strides[i_lvl] + pts_center + pts_lvl.append(pts) + pts_lvl = torch.stack(pts_lvl, 0) + pts_list.append(pts_lvl) + return pts_list + + def _get_targets_single(self, + flat_proposals: Tensor, + valid_flags: Tensor, + gt_instances: InstanceData, + gt_instances_ignore: InstanceData, + stage: str = 'init', + unmap_outputs: bool = True) -> tuple: + """Compute corresponding GT box and classification targets for + proposals. + + Args: + flat_proposals (Tensor): Multi level points of a image. + valid_flags (Tensor): Multi level valid flags of a image. + gt_instances (InstanceData): It usually includes ``bboxes`` and + ``labels`` attributes. + gt_instances_ignore (InstanceData): It includes ``bboxes`` + attribute data that is ignored during training and testing. + stage (str): 'init' or 'refine'. Generate target for + init stage or refine stage. Defaults to 'init'. + unmap_outputs (bool): Whether to map outputs back to + the original set of anchors. Defaults to True. + + Returns: + tuple: + + - labels (Tensor): Labels of each level. + - label_weights (Tensor): Label weights of each level. + - bbox_targets (Tensor): BBox targets of each level. + - bbox_weights (Tensor): BBox weights of each level. + - pos_inds (Tensor): positive samples indexes. + - neg_inds (Tensor): negative samples indexes. + - sampling_result (:obj:`SamplingResult`): Sampling results. + """ + inside_flags = valid_flags + if not inside_flags.any(): + raise ValueError( + 'There is no valid proposal inside the image boundary. Please ' + 'check the image size.') + # assign gt and sample proposals + proposals = flat_proposals[inside_flags, :] + pred_instances = InstanceData(priors=proposals) + + if stage == 'init': + assigner = self.init_assigner + pos_weight = self.train_cfg['init']['pos_weight'] + else: + assigner = self.refine_assigner + pos_weight = self.train_cfg['refine']['pos_weight'] + + assign_result = assigner.assign(pred_instances, gt_instances, + gt_instances_ignore) + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_valid_proposals = proposals.shape[0] + bbox_gt = proposals.new_zeros([num_valid_proposals, 4]) + pos_proposals = torch.zeros_like(proposals) + proposals_weights = proposals.new_zeros([num_valid_proposals, 4]) + labels = proposals.new_full((num_valid_proposals, ), + self.num_classes, + dtype=torch.long) + label_weights = proposals.new_zeros( + num_valid_proposals, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + bbox_gt[pos_inds, :] = sampling_result.pos_gt_bboxes + pos_proposals[pos_inds, :] = proposals[pos_inds, :] + proposals_weights[pos_inds, :] = 1.0 + + labels[pos_inds] = sampling_result.pos_gt_labels + if pos_weight <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = pos_weight + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + # map up to original set of proposals + if unmap_outputs: + num_total_proposals = flat_proposals.size(0) + labels = unmap( + labels, + num_total_proposals, + inside_flags, + fill=self.num_classes) # fill bg label + label_weights = unmap(label_weights, num_total_proposals, + inside_flags) + bbox_gt = unmap(bbox_gt, num_total_proposals, inside_flags) + pos_proposals = unmap(pos_proposals, num_total_proposals, + inside_flags) + proposals_weights = unmap(proposals_weights, num_total_proposals, + inside_flags) + + return (labels, label_weights, bbox_gt, pos_proposals, + proposals_weights, pos_inds, neg_inds, sampling_result) + + def get_targets(self, + proposals_list: List[Tensor], + valid_flag_list: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + stage: str = 'init', + unmap_outputs: bool = True, + return_sampling_results: bool = False) -> tuple: + """Compute corresponding GT box and classification targets for + proposals. + + Args: + proposals_list (list[Tensor]): Multi level points/bboxes of each + image. + valid_flag_list (list[Tensor]): Multi level valid flags of each + image. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + stage (str): 'init' or 'refine'. Generate target for init stage or + refine stage. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. + return_sampling_results (bool): Whether to return the sampling + results. Defaults to False. + + Returns: + tuple: + + - labels_list (list[Tensor]): Labels of each level. + - label_weights_list (list[Tensor]): Label weights of each + level. + - bbox_gt_list (list[Tensor]): Ground truth bbox of each level. + - proposals_list (list[Tensor]): Proposals(points/bboxes) of + each level. + - proposal_weights_list (list[Tensor]): Proposal weights of + each level. + - avg_factor (int): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + `PseudoSampler`, `avg_factor` is usually equal to the number + of positive priors. + """ + assert stage in ['init', 'refine'] + num_imgs = len(batch_img_metas) + assert len(proposals_list) == len(valid_flag_list) == num_imgs + + # points number of multi levels + num_level_proposals = [points.size(0) for points in proposals_list[0]] + + # concat all level points and flags to a single tensor + for i in range(num_imgs): + assert len(proposals_list[i]) == len(valid_flag_list[i]) + proposals_list[i] = torch.cat(proposals_list[i]) + valid_flag_list[i] = torch.cat(valid_flag_list[i]) + + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + + (all_labels, all_label_weights, all_bbox_gt, all_proposals, + all_proposal_weights, pos_inds_list, neg_inds_list, + sampling_results_list) = multi_apply( + self._get_targets_single, + proposals_list, + valid_flag_list, + batch_gt_instances, + batch_gt_instances_ignore, + stage=stage, + unmap_outputs=unmap_outputs) + + # sampled points of all images + avg_refactor = sum( + [results.avg_factor for results in sampling_results_list]) + labels_list = images_to_levels(all_labels, num_level_proposals) + label_weights_list = images_to_levels(all_label_weights, + num_level_proposals) + bbox_gt_list = images_to_levels(all_bbox_gt, num_level_proposals) + proposals_list = images_to_levels(all_proposals, num_level_proposals) + proposal_weights_list = images_to_levels(all_proposal_weights, + num_level_proposals) + res = (labels_list, label_weights_list, bbox_gt_list, proposals_list, + proposal_weights_list, avg_refactor) + if return_sampling_results: + res = res + (sampling_results_list, ) + + return res + + def loss_by_feat_single(self, cls_score: Tensor, pts_pred_init: Tensor, + pts_pred_refine: Tensor, labels: Tensor, + label_weights, bbox_gt_init: Tensor, + bbox_weights_init: Tensor, bbox_gt_refine: Tensor, + bbox_weights_refine: Tensor, stride: int, + avg_factor_init: int, + avg_factor_refine: int) -> Tuple[Tensor]: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + cls_score (Tensor): Box scores for each scale level + Has shape (N, num_classes, h_i, w_i). + pts_pred_init (Tensor): Points of shape + (batch_size, h_i * w_i, num_points * 2). + pts_pred_refine (Tensor): Points refined of shape + (batch_size, h_i * w_i, num_points * 2). + labels (Tensor): Ground truth class indices with shape + (batch_size, h_i * w_i). + label_weights (Tensor): Label weights of shape + (batch_size, h_i * w_i). + bbox_gt_init (Tensor): BBox regression targets in the init stage + of shape (batch_size, h_i * w_i, 4). + bbox_weights_init (Tensor): BBox regression loss weights in the + init stage of shape (batch_size, h_i * w_i, 4). + bbox_gt_refine (Tensor): BBox regression targets in the refine + stage of shape (batch_size, h_i * w_i, 4). + bbox_weights_refine (Tensor): BBox regression loss weights in the + refine stage of shape (batch_size, h_i * w_i, 4). + stride (int): Point stride. + avg_factor_init (int): Average factor that is used to average + the loss in the init stage. + avg_factor_refine (int): Average factor that is used to average + the loss in the refine stage. + + Returns: + Tuple[Tensor]: loss components. + """ + # classification loss + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + cls_score = cls_score.permute(0, 2, 3, + 1).reshape(-1, self.cls_out_channels) + cls_score = cls_score.contiguous() + loss_cls = self.loss_cls( + cls_score, labels, label_weights, avg_factor=avg_factor_refine) + + # points loss + bbox_gt_init = bbox_gt_init.reshape(-1, 4) + bbox_weights_init = bbox_weights_init.reshape(-1, 4) + bbox_pred_init = self.points2bbox( + pts_pred_init.reshape(-1, 2 * self.num_points), y_first=False) + bbox_gt_refine = bbox_gt_refine.reshape(-1, 4) + bbox_weights_refine = bbox_weights_refine.reshape(-1, 4) + bbox_pred_refine = self.points2bbox( + pts_pred_refine.reshape(-1, 2 * self.num_points), y_first=False) + normalize_term = self.point_base_scale * stride + loss_pts_init = self.loss_bbox_init( + bbox_pred_init / normalize_term, + bbox_gt_init / normalize_term, + bbox_weights_init, + avg_factor=avg_factor_init) + loss_pts_refine = self.loss_bbox_refine( + bbox_pred_refine / normalize_term, + bbox_gt_refine / normalize_term, + bbox_weights_refine, + avg_factor=avg_factor_refine) + return loss_cls, loss_pts_init, loss_pts_refine + + def loss_by_feat( + self, + cls_scores: List[Tensor], + pts_preds_init: List[Tensor], + pts_preds_refine: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level, + each is a 4D-tensor, of shape (batch_size, num_classes, h, w). + pts_preds_init (list[Tensor]): Points for each scale level, each is + a 3D-tensor, of shape (batch_size, h_i * w_i, num_points * 2). + pts_preds_refine (list[Tensor]): Points refined for each scale + level, each is a 3D-tensor, of shape + (batch_size, h_i * w_i, num_points * 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + device = cls_scores[0].device + + # target for initial stage + center_list, valid_flag_list = self.get_points(featmap_sizes, + batch_img_metas, device) + pts_coordinate_preds_init = self.offset_to_pts(center_list, + pts_preds_init) + if self.train_cfg['init']['assigner']['type'] == 'PointAssigner': + # Assign target for center list + candidate_list = center_list + else: + # transform center list to bbox list and + # assign target for bbox list + bbox_list = self.centers_to_bboxes(center_list) + candidate_list = bbox_list + cls_reg_targets_init = self.get_targets( + proposals_list=candidate_list, + valid_flag_list=valid_flag_list, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + stage='init', + return_sampling_results=False) + (*_, bbox_gt_list_init, candidate_list_init, bbox_weights_list_init, + avg_factor_init) = cls_reg_targets_init + + # target for refinement stage + center_list, valid_flag_list = self.get_points(featmap_sizes, + batch_img_metas, device) + pts_coordinate_preds_refine = self.offset_to_pts( + center_list, pts_preds_refine) + bbox_list = [] + for i_img, center in enumerate(center_list): + bbox = [] + for i_lvl in range(len(pts_preds_refine)): + bbox_preds_init = self.points2bbox( + pts_preds_init[i_lvl].detach()) + bbox_shift = bbox_preds_init * self.point_strides[i_lvl] + bbox_center = torch.cat( + [center[i_lvl][:, :2], center[i_lvl][:, :2]], dim=1) + bbox.append(bbox_center + + bbox_shift[i_img].permute(1, 2, 0).reshape(-1, 4)) + bbox_list.append(bbox) + cls_reg_targets_refine = self.get_targets( + proposals_list=bbox_list, + valid_flag_list=valid_flag_list, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + stage='refine', + return_sampling_results=False) + (labels_list, label_weights_list, bbox_gt_list_refine, + candidate_list_refine, bbox_weights_list_refine, + avg_factor_refine) = cls_reg_targets_refine + + # compute loss + losses_cls, losses_pts_init, losses_pts_refine = multi_apply( + self.loss_by_feat_single, + cls_scores, + pts_coordinate_preds_init, + pts_coordinate_preds_refine, + labels_list, + label_weights_list, + bbox_gt_list_init, + bbox_weights_list_init, + bbox_gt_list_refine, + bbox_weights_list_refine, + self.point_strides, + avg_factor_init=avg_factor_init, + avg_factor_refine=avg_factor_refine) + loss_dict_all = { + 'loss_cls': losses_cls, + 'loss_pts_init': losses_pts_init, + 'loss_pts_refine': losses_pts_refine + } + return loss_dict_all + + # Same as base_dense_head/_get_bboxes_single except self._bbox_decode + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform outputs of a single image into bbox predictions. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image. RepPoints head does not need + this value. + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid, has shape + (num_priors, 2). + img_meta (dict): Image meta info. + cfg (:obj:`ConfigDict`): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + cfg = self.test_cfg if cfg is None else cfg + assert len(cls_score_list) == len(bbox_pred_list) + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bboxes = [] + mlvl_scores = [] + mlvl_labels = [] + for level_idx, (cls_score, bbox_pred, priors) in enumerate( + zip(cls_score_list, bbox_pred_list, mlvl_priors)): + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4) + + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + if self.use_sigmoid_cls: + scores = cls_score.sigmoid() + else: + scores = cls_score.softmax(-1)[:, :-1] + + # After https://github.com/open-mmlab/mmdetection/pull/6268/, + # this operation keeps fewer bboxes under the same `nms_pre`. + # There is no difference in performance for most models. If you + # find a slight drop in performance, you can set a larger + # `nms_pre` than before. + results = filter_scores_and_topk( + scores, cfg.score_thr, nms_pre, + dict(bbox_pred=bbox_pred, priors=priors)) + scores, labels, _, filtered_results = results + + bbox_pred = filtered_results['bbox_pred'] + priors = filtered_results['priors'] + + bboxes = self._bbox_decode(priors, bbox_pred, + self.point_strides[level_idx], + img_shape) + + mlvl_bboxes.append(bboxes) + mlvl_scores.append(scores) + mlvl_labels.append(labels) + + results = InstanceData() + results.bboxes = torch.cat(mlvl_bboxes) + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + + return self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + + def _bbox_decode(self, points: Tensor, bbox_pred: Tensor, stride: int, + max_shape: Tuple[int, int]) -> Tensor: + """Decode the prediction to bounding box. + + Args: + points (Tensor): shape (h_i * w_i, 2). + bbox_pred (Tensor): shape (h_i * w_i, 4). + stride (int): Stride for bbox_pred in different level. + max_shape (Tuple[int, int]): image shape. + + Returns: + Tensor: Bounding boxes decoded. + """ + bbox_pos_center = torch.cat([points[:, :2], points[:, :2]], dim=1) + bboxes = bbox_pred * stride + bbox_pos_center + x1 = bboxes[:, 0].clamp(min=0, max=max_shape[1]) + y1 = bboxes[:, 1].clamp(min=0, max=max_shape[0]) + x2 = bboxes[:, 2].clamp(min=0, max=max_shape[1]) + y2 = bboxes[:, 3].clamp(min=0, max=max_shape[0]) + decoded_bboxes = torch.stack([x1, y1, x2, y2], dim=-1) + return decoded_bboxes diff --git a/mmdetection/mmdet/models/dense_heads/retina_head.py b/mmdetection/mmdet/models/dense_heads/retina_head.py new file mode 100644 index 00000000..be3ae74d --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/retina_head.py @@ -0,0 +1,120 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmdet.registry import MODELS +from .anchor_head import AnchorHead + + +@MODELS.register_module() +class RetinaHead(AnchorHead): + r"""An anchor-based head used in `RetinaNet + `_. + + The head contains two subnetworks. The first classifies anchor boxes and + the second regresses deltas for the anchors. + + Example: + >>> import torch + >>> self = RetinaHead(11, 7) + >>> x = torch.rand(1, 7, 32, 32) + >>> cls_score, bbox_pred = self.forward_single(x) + >>> # Each anchor predicts a score for each class except background + >>> cls_per_anchor = cls_score.shape[1] / self.num_anchors + >>> box_per_anchor = bbox_pred.shape[1] / self.num_anchors + >>> assert cls_per_anchor == (self.num_classes) + >>> assert box_per_anchor == 4 + """ + + def __init__(self, + num_classes, + in_channels, + stacked_convs=4, + conv_cfg=None, + norm_cfg=None, + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + init_cfg=dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', + name='retina_cls', + std=0.01, + bias_prob=0.01)), + **kwargs): + assert stacked_convs >= 0, \ + '`stacked_convs` must be non-negative integers, ' \ + f'but got {stacked_convs} instead.' + self.stacked_convs = stacked_convs + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + super(RetinaHead, self).__init__( + num_classes, + in_channels, + anchor_generator=anchor_generator, + init_cfg=init_cfg, + **kwargs) + + def _init_layers(self): + """Initialize layers of the head.""" + self.relu = nn.ReLU(inplace=True) + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + in_channels = self.in_channels + for i in range(self.stacked_convs): + self.cls_convs.append( + ConvModule( + in_channels, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.reg_convs.append( + ConvModule( + in_channels, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + in_channels = self.feat_channels + self.retina_cls = nn.Conv2d( + in_channels, + self.num_base_priors * self.cls_out_channels, + 3, + padding=1) + reg_dim = self.bbox_coder.encode_size + self.retina_reg = nn.Conv2d( + in_channels, self.num_base_priors * reg_dim, 3, padding=1) + + def forward_single(self, x): + """Forward feature of a single scale level. + + Args: + x (Tensor): Features of a single scale level. + + Returns: + tuple: + cls_score (Tensor): Cls scores for a single scale level + the channels number is num_anchors * num_classes. + bbox_pred (Tensor): Box energies / deltas for a single scale + level, the channels number is num_anchors * 4. + """ + cls_feat = x + reg_feat = x + for cls_conv in self.cls_convs: + cls_feat = cls_conv(cls_feat) + for reg_conv in self.reg_convs: + reg_feat = reg_conv(reg_feat) + cls_score = self.retina_cls(cls_feat) + bbox_pred = self.retina_reg(reg_feat) + return cls_score, bbox_pred diff --git a/mmdetection/mmdet/models/dense_heads/retina_sepbn_head.py b/mmdetection/mmdet/models/dense_heads/retina_sepbn_head.py new file mode 100644 index 00000000..681a3998 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/retina_sepbn_head.py @@ -0,0 +1,127 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import bias_init_with_prob, normal_init +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType, OptMultiConfig +from .anchor_head import AnchorHead + + +@MODELS.register_module() +class RetinaSepBNHead(AnchorHead): + """"RetinaHead with separate BN. + + In RetinaHead, conv/norm layers are shared across different FPN levels, + while in RetinaSepBNHead, conv layers are shared across different FPN + levels, but BN layers are separated. + """ + + def __init__(self, + num_classes: int, + num_ins: int, + in_channels: int, + stacked_convs: int = 4, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + self.stacked_convs = stacked_convs + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.num_ins = num_ins + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + init_cfg=init_cfg, + **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.relu = nn.ReLU(inplace=True) + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + for i in range(self.num_ins): + cls_convs = nn.ModuleList() + reg_convs = nn.ModuleList() + for j in range(self.stacked_convs): + chn = self.in_channels if j == 0 else self.feat_channels + cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.cls_convs.append(cls_convs) + self.reg_convs.append(reg_convs) + for i in range(self.stacked_convs): + for j in range(1, self.num_ins): + self.cls_convs[j][i].conv = self.cls_convs[0][i].conv + self.reg_convs[j][i].conv = self.reg_convs[0][i].conv + self.retina_cls = nn.Conv2d( + self.feat_channels, + self.num_base_priors * self.cls_out_channels, + 3, + padding=1) + self.retina_reg = nn.Conv2d( + self.feat_channels, self.num_base_priors * 4, 3, padding=1) + + def init_weights(self) -> None: + """Initialize weights of the head.""" + super().init_weights() + for m in self.cls_convs[0]: + normal_init(m.conv, std=0.01) + for m in self.reg_convs[0]: + normal_init(m.conv, std=0.01) + bias_cls = bias_init_with_prob(0.01) + normal_init(self.retina_cls, std=0.01, bias=bias_cls) + normal_init(self.retina_reg, std=0.01) + + def forward(self, feats: Tuple[Tensor]) -> tuple: + """Forward features from the upstream network. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually a tuple of classification scores and bbox prediction + + - cls_scores (list[Tensor]): Classification scores for all + scale levels, each is a 4D-tensor, the channels number is + num_anchors * num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for all + scale levels, each is a 4D-tensor, the channels number is + num_anchors * 4. + """ + cls_scores = [] + bbox_preds = [] + for i, x in enumerate(feats): + cls_feat = feats[i] + reg_feat = feats[i] + for cls_conv in self.cls_convs[i]: + cls_feat = cls_conv(cls_feat) + for reg_conv in self.reg_convs[i]: + reg_feat = reg_conv(reg_feat) + cls_score = self.retina_cls(cls_feat) + bbox_pred = self.retina_reg(reg_feat) + cls_scores.append(cls_score) + bbox_preds.append(bbox_pred) + return cls_scores, bbox_preds diff --git a/mmdetection/mmdet/models/dense_heads/rpn_head.py b/mmdetection/mmdet/models/dense_heads/rpn_head.py new file mode 100644 index 00000000..6b544009 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/rpn_head.py @@ -0,0 +1,302 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmcv.ops import batched_nms +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures.bbox import (cat_boxes, empty_box_as, get_box_tensor, + get_box_wh, scale_boxes) +from mmdet.utils import InstanceList, MultiConfig, OptInstanceList +from .anchor_head import AnchorHead + + +@MODELS.register_module() +class RPNHead(AnchorHead): + """Implementation of RPN head. + + Args: + in_channels (int): Number of channels in the input feature map. + num_classes (int): Number of categories excluding the background + category. Defaults to 1. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or \ + list[dict]): Initialization config dict. + num_convs (int): Number of convolution layers in the head. + Defaults to 1. + """ # noqa: W605 + + def __init__(self, + in_channels: int, + num_classes: int = 1, + init_cfg: MultiConfig = dict( + type='Normal', layer='Conv2d', std=0.01), + num_convs: int = 1, + **kwargs) -> None: + self.num_convs = num_convs + assert num_classes == 1 + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + init_cfg=init_cfg, + **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + if self.num_convs > 1: + rpn_convs = [] + for i in range(self.num_convs): + if i == 0: + in_channels = self.in_channels + else: + in_channels = self.feat_channels + # use ``inplace=False`` to avoid error: one of the variables + # needed for gradient computation has been modified by an + # inplace operation. + rpn_convs.append( + ConvModule( + in_channels, + self.feat_channels, + 3, + padding=1, + inplace=False)) + self.rpn_conv = nn.Sequential(*rpn_convs) + else: + self.rpn_conv = nn.Conv2d( + self.in_channels, self.feat_channels, 3, padding=1) + self.rpn_cls = nn.Conv2d(self.feat_channels, + self.num_base_priors * self.cls_out_channels, + 1) + reg_dim = self.bbox_coder.encode_size + self.rpn_reg = nn.Conv2d(self.feat_channels, + self.num_base_priors * reg_dim, 1) + + def forward_single(self, x: Tensor) -> Tuple[Tensor, Tensor]: + """Forward feature of a single scale level. + + Args: + x (Tensor): Features of a single scale level. + + Returns: + tuple: + cls_score (Tensor): Cls scores for a single scale level \ + the channels number is num_base_priors * num_classes. + bbox_pred (Tensor): Box energies / deltas for a single scale \ + level, the channels number is num_base_priors * 4. + """ + x = self.rpn_conv(x) + x = F.relu(x) + rpn_cls_score = self.rpn_cls(x) + rpn_bbox_pred = self.rpn_reg(x) + return rpn_cls_score, rpn_bbox_pred + + def loss_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) \ + -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level, + has shape (N, num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + batch_gt_instances (list[obj:InstanceData]): Batch of gt_instance. + It usually includes ``bboxes`` and ``labels`` attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[obj:InstanceData], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + losses = super().loss_by_feat( + cls_scores, + bbox_preds, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + return dict( + loss_rpn_cls=losses['loss_cls'], loss_rpn_bbox=losses['loss_bbox']) + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Be compatible with + BaseDenseHead. Not used in RPNHead. + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid. In all + anchor-based methods, it has shape (num_priors, 4). In + all anchor-free methods, it has shape (num_priors, 2) + when `with_stride=True`, otherwise it still has shape + (num_priors, 4). + img_meta (dict): Image meta info. + cfg (ConfigDict, optional): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bbox_preds = [] + mlvl_valid_priors = [] + mlvl_scores = [] + level_ids = [] + for level_idx, (cls_score, bbox_pred, priors) in \ + enumerate(zip(cls_score_list, bbox_pred_list, + mlvl_priors)): + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + + reg_dim = self.bbox_coder.encode_size + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, reg_dim) + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + if self.use_sigmoid_cls: + scores = cls_score.sigmoid() + else: + # remind that we set FG labels to [0] since mmdet v2.0 + # BG cat_id: 1 + scores = cls_score.softmax(-1)[:, :-1] + + scores = torch.squeeze(scores) + if 0 < nms_pre < scores.shape[0]: + # sort is faster than topk + # _, topk_inds = scores.topk(cfg.nms_pre) + ranked_scores, rank_inds = scores.sort(descending=True) + topk_inds = rank_inds[:nms_pre] + scores = ranked_scores[:nms_pre] + bbox_pred = bbox_pred[topk_inds, :] + priors = priors[topk_inds] + + mlvl_bbox_preds.append(bbox_pred) + mlvl_valid_priors.append(priors) + mlvl_scores.append(scores) + + # use level id to implement the separate level nms + level_ids.append( + scores.new_full((scores.size(0), ), + level_idx, + dtype=torch.long)) + + bbox_pred = torch.cat(mlvl_bbox_preds) + priors = cat_boxes(mlvl_valid_priors) + bboxes = self.bbox_coder.decode(priors, bbox_pred, max_shape=img_shape) + + results = InstanceData() + results.bboxes = bboxes + results.scores = torch.cat(mlvl_scores) + results.level_ids = torch.cat(level_ids) + + return self._bbox_post_process( + results=results, cfg=cfg, rescale=rescale, img_meta=img_meta) + + def _bbox_post_process(self, + results: InstanceData, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True, + img_meta: Optional[dict] = None) -> InstanceData: + """bbox post-processing method. + + The boxes would be rescaled to the original image scale and do + the nms operation. + + Args: + results (:obj:`InstaceData`): Detection instance results, + each item has shape (num_bboxes, ). + cfg (ConfigDict): Test / postprocessing configuration. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Default to True. + img_meta (dict, optional): Image meta info. Defaults to None. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert with_nms, '`with_nms` must be True in RPNHead' + if rescale: + assert img_meta.get('scale_factor') is not None + scale_factor = [1 / s for s in img_meta['scale_factor']] + results.bboxes = scale_boxes(results.bboxes, scale_factor) + + # filter small size bboxes + if cfg.get('min_bbox_size', -1) >= 0: + w, h = get_box_wh(results.bboxes) + valid_mask = (w > cfg.min_bbox_size) & (h > cfg.min_bbox_size) + if not valid_mask.all(): + results = results[valid_mask] + + if results.bboxes.numel() > 0: + bboxes = get_box_tensor(results.bboxes) + det_bboxes, keep_idxs = batched_nms(bboxes, results.scores, + results.level_ids, cfg.nms) + results = results[keep_idxs] + # some nms would reweight the score, such as softnms + results.scores = det_bboxes[:, -1] + results = results[:cfg.max_per_img] + # TODO: This would unreasonably show the 0th class label + # in visualization + results.labels = results.scores.new_zeros( + len(results), dtype=torch.long) + del results.level_ids + else: + # To avoid some potential error + results_ = InstanceData() + results_.bboxes = empty_box_as(results.bboxes) + results_.scores = results.scores.new_zeros(0) + results_.labels = results.scores.new_zeros(0) + results = results_ + return results diff --git a/mmdetection/mmdet/models/dense_heads/rtmdet_head.py b/mmdetection/mmdet/models/dense_heads/rtmdet_head.py new file mode 100644 index 00000000..ae0ee6d2 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/rtmdet_head.py @@ -0,0 +1,692 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple, Union + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule, Scale, is_norm +from mmengine.model import bias_init_with_prob, constant_init, normal_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures.bbox import distance2bbox +from mmdet.utils import ConfigType, InstanceList, OptInstanceList, reduce_mean +from ..layers.transformer import inverse_sigmoid +from ..task_modules import anchor_inside_flags +from ..utils import (images_to_levels, multi_apply, sigmoid_geometric_mean, + unmap) +from .atss_head import ATSSHead + + +@MODELS.register_module() +class RTMDetHead(ATSSHead): + """Detection Head of RTMDet. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + with_objectness (bool): Whether to add an objectness branch. + Defaults to True. + act_cfg (:obj:`ConfigDict` or dict): Config dict for activation layer. + Default: dict(type='ReLU') + """ + + def __init__(self, + num_classes: int, + in_channels: int, + with_objectness: bool = True, + act_cfg: ConfigType = dict(type='ReLU'), + **kwargs) -> None: + self.act_cfg = act_cfg + self.with_objectness = with_objectness + super().__init__(num_classes, in_channels, **kwargs) + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + + def _init_layers(self): + """Initialize layers of the head.""" + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + self.reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + pred_pad_size = self.pred_kernel_size // 2 + self.rtm_cls = nn.Conv2d( + self.feat_channels, + self.num_base_priors * self.cls_out_channels, + self.pred_kernel_size, + padding=pred_pad_size) + self.rtm_reg = nn.Conv2d( + self.feat_channels, + self.num_base_priors * 4, + self.pred_kernel_size, + padding=pred_pad_size) + if self.with_objectness: + self.rtm_obj = nn.Conv2d( + self.feat_channels, + 1, + self.pred_kernel_size, + padding=pred_pad_size) + + self.scales = nn.ModuleList( + [Scale(1.0) for _ in self.prior_generator.strides]) + + def init_weights(self) -> None: + """Initialize weights of the head.""" + for m in self.modules(): + if isinstance(m, nn.Conv2d): + normal_init(m, mean=0, std=0.01) + if is_norm(m): + constant_init(m, 1) + bias_cls = bias_init_with_prob(0.01) + normal_init(self.rtm_cls, std=0.01, bias=bias_cls) + normal_init(self.rtm_reg, std=0.01) + if self.with_objectness: + normal_init(self.rtm_obj, std=0.01, bias=bias_cls) + + def forward(self, feats: Tuple[Tensor, ...]) -> tuple: + """Forward features from the upstream network. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually a tuple of classification scores and bbox prediction + - cls_scores (list[Tensor]): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * 4. + """ + + cls_scores = [] + bbox_preds = [] + for idx, (x, scale, stride) in enumerate( + zip(feats, self.scales, self.prior_generator.strides)): + cls_feat = x + reg_feat = x + + for cls_layer in self.cls_convs: + cls_feat = cls_layer(cls_feat) + cls_score = self.rtm_cls(cls_feat) + + for reg_layer in self.reg_convs: + reg_feat = reg_layer(reg_feat) + + if self.with_objectness: + objectness = self.rtm_obj(reg_feat) + cls_score = inverse_sigmoid( + sigmoid_geometric_mean(cls_score, objectness)) + + reg_dist = scale(self.rtm_reg(reg_feat).exp()).float() * stride[0] + + cls_scores.append(cls_score) + bbox_preds.append(reg_dist) + return tuple(cls_scores), tuple(bbox_preds) + + def loss_by_feat_single(self, cls_score: Tensor, bbox_pred: Tensor, + labels: Tensor, label_weights: Tensor, + bbox_targets: Tensor, assign_metrics: Tensor, + stride: List[int]): + """Compute loss of a single scale level. + + Args: + cls_score (Tensor): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W). + bbox_pred (Tensor): Decoded bboxes for each scale + level with shape (N, num_anchors * 4, H, W). + labels (Tensor): Labels of each anchors with shape + (N, num_total_anchors). + label_weights (Tensor): Label weights of each anchor with shape + (N, num_total_anchors). + bbox_targets (Tensor): BBox regression targets of each anchor with + shape (N, num_total_anchors, 4). + assign_metrics (Tensor): Assign metrics with shape + (N, num_total_anchors). + stride (List[int]): Downsample stride of the feature map. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert stride[0] == stride[1], 'h stride is not equal to w stride!' + cls_score = cls_score.permute(0, 2, 3, 1).reshape( + -1, self.cls_out_channels).contiguous() + bbox_pred = bbox_pred.reshape(-1, 4) + bbox_targets = bbox_targets.reshape(-1, 4) + labels = labels.reshape(-1) + assign_metrics = assign_metrics.reshape(-1) + label_weights = label_weights.reshape(-1) + targets = (labels, assign_metrics) + + loss_cls = self.loss_cls( + cls_score, targets, label_weights, avg_factor=1.0) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().squeeze(1) + + if len(pos_inds) > 0: + pos_bbox_targets = bbox_targets[pos_inds] + pos_bbox_pred = bbox_pred[pos_inds] + + pos_decode_bbox_pred = pos_bbox_pred + pos_decode_bbox_targets = pos_bbox_targets + + # regression loss + pos_bbox_weight = assign_metrics[pos_inds] + + loss_bbox = self.loss_bbox( + pos_decode_bbox_pred, + pos_decode_bbox_targets, + weight=pos_bbox_weight, + avg_factor=1.0) + else: + loss_bbox = bbox_pred.sum() * 0 + pos_bbox_weight = bbox_targets.new_tensor(0.) + + return loss_cls, loss_bbox, assign_metrics.sum(), pos_bbox_weight.sum() + + def loss_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None): + """Compute losses of the head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Decoded box for each scale + level with shape (N, num_anchors * 4, H, W) in + [tl_x, tl_y, br_x, br_y] format. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + num_imgs = len(batch_img_metas) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + flatten_cls_scores = torch.cat([ + cls_score.permute(0, 2, 3, 1).reshape(num_imgs, -1, + self.cls_out_channels) + for cls_score in cls_scores + ], 1) + decoded_bboxes = [] + for anchor, bbox_pred in zip(anchor_list[0], bbox_preds): + anchor = anchor.reshape(-1, 4) + bbox_pred = bbox_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, 4) + bbox_pred = distance2bbox(anchor, bbox_pred) + decoded_bboxes.append(bbox_pred) + + flatten_bboxes = torch.cat(decoded_bboxes, 1) + + cls_reg_targets = self.get_targets( + flatten_cls_scores, + flatten_bboxes, + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + (anchor_list, labels_list, label_weights_list, bbox_targets_list, + assign_metrics_list, sampling_results_list) = cls_reg_targets + + losses_cls, losses_bbox,\ + cls_avg_factors, bbox_avg_factors = multi_apply( + self.loss_by_feat_single, + cls_scores, + decoded_bboxes, + labels_list, + label_weights_list, + bbox_targets_list, + assign_metrics_list, + self.prior_generator.strides) + + cls_avg_factor = reduce_mean(sum(cls_avg_factors)).clamp_(min=1).item() + losses_cls = list(map(lambda x: x / cls_avg_factor, losses_cls)) + + bbox_avg_factor = reduce_mean( + sum(bbox_avg_factors)).clamp_(min=1).item() + losses_bbox = list(map(lambda x: x / bbox_avg_factor, losses_bbox)) + return dict(loss_cls=losses_cls, loss_bbox=losses_bbox) + + def get_targets(self, + cls_scores: Tensor, + bbox_preds: Tensor, + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs=True): + """Compute regression and classification targets for anchors in + multiple images. + + Args: + cls_scores (Tensor): Classification predictions of images, + a 3D-Tensor with shape [num_imgs, num_priors, num_classes]. + bbox_preds (Tensor): Decoded bboxes predictions of one image, + a 3D-Tensor with shape [num_imgs, num_priors, 4] in [tl_x, + tl_y, br_x, br_y] format. + anchor_list (list[list[Tensor]]): Multi level anchors of each + image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_anchors, 4). + valid_flag_list (list[list[Tensor]]): Multi level valid flags of + each image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_anchors, ) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + + Returns: + tuple: a tuple containing learning targets. + + - anchors_list (list[list[Tensor]]): Anchors of each level. + - labels_list (list[Tensor]): Labels of each level. + - label_weights_list (list[Tensor]): Label weights of each + level. + - bbox_targets_list (list[Tensor]): BBox targets of each level. + - assign_metrics_list (list[Tensor]): alignment metrics of each + level. + """ + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + + # concat all level anchors and flags to a single tensor + for i in range(num_imgs): + assert len(anchor_list[i]) == len(valid_flag_list[i]) + anchor_list[i] = torch.cat(anchor_list[i]) + valid_flag_list[i] = torch.cat(valid_flag_list[i]) + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + # anchor_list: list(b * [-1, 4]) + (all_anchors, all_labels, all_label_weights, all_bbox_targets, + all_assign_metrics, sampling_results_list) = multi_apply( + self._get_targets_single, + cls_scores.detach(), + bbox_preds.detach(), + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + # no valid anchors + if any([labels is None for labels in all_labels]): + return None + + # split targets to a list w.r.t. multiple levels + anchors_list = images_to_levels(all_anchors, num_level_anchors) + labels_list = images_to_levels(all_labels, num_level_anchors) + label_weights_list = images_to_levels(all_label_weights, + num_level_anchors) + bbox_targets_list = images_to_levels(all_bbox_targets, + num_level_anchors) + assign_metrics_list = images_to_levels(all_assign_metrics, + num_level_anchors) + + return (anchors_list, labels_list, label_weights_list, + bbox_targets_list, assign_metrics_list, sampling_results_list) + + def _get_targets_single(self, + cls_scores: Tensor, + bbox_preds: Tensor, + flat_anchors: Tensor, + valid_flags: Tensor, + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs=True): + """Compute regression, classification targets for anchors in a single + image. + + Args: + cls_scores (list(Tensor)): Box scores for each image. + bbox_preds (list(Tensor)): Box energies / deltas for each image. + flat_anchors (Tensor): Multi-level anchors of the image, which are + concatenated into a single tensor of shape (num_anchors ,4) + valid_flags (Tensor): Multi level valid flags of the image, + which are concatenated into a single tensor of + shape (num_anchors,). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + + Returns: + tuple: N is the number of total anchors in the image. + + - anchors (Tensor): All anchors in the image with shape (N, 4). + - labels (Tensor): Labels of all anchors in the image with shape + (N,). + - label_weights (Tensor): Label weights of all anchor in the + image with shape (N,). + - bbox_targets (Tensor): BBox targets of all anchors in the + image with shape (N, 4). + - norm_alignment_metrics (Tensor): Normalized alignment metrics + of all priors in the image with shape (N,). + """ + inside_flags = anchor_inside_flags(flat_anchors, valid_flags, + img_meta['img_shape'][:2], + self.train_cfg['allowed_border']) + if not inside_flags.any(): + return (None, ) * 7 + # assign gt and sample anchors + anchors = flat_anchors[inside_flags, :] + + pred_instances = InstanceData( + scores=cls_scores[inside_flags, :], + bboxes=bbox_preds[inside_flags, :], + priors=anchors) + + assign_result = self.assigner.assign(pred_instances, gt_instances, + gt_instances_ignore) + + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_valid_anchors = anchors.shape[0] + bbox_targets = torch.zeros_like(anchors) + labels = anchors.new_full((num_valid_anchors, ), + self.num_classes, + dtype=torch.long) + label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float) + assign_metrics = anchors.new_zeros( + num_valid_anchors, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + # point-based + pos_bbox_targets = sampling_result.pos_gt_bboxes + bbox_targets[pos_inds, :] = pos_bbox_targets + + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + class_assigned_gt_inds = torch.unique( + sampling_result.pos_assigned_gt_inds) + for gt_inds in class_assigned_gt_inds: + gt_class_inds = pos_inds[sampling_result.pos_assigned_gt_inds == + gt_inds] + assign_metrics[gt_class_inds] = assign_result.max_overlaps[ + gt_class_inds] + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_anchors.size(0) + anchors = unmap(anchors, num_total_anchors, inside_flags) + labels = unmap( + labels, num_total_anchors, inside_flags, fill=self.num_classes) + label_weights = unmap(label_weights, num_total_anchors, + inside_flags) + bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags) + assign_metrics = unmap(assign_metrics, num_total_anchors, + inside_flags) + return (anchors, labels, label_weights, bbox_targets, assign_metrics, + sampling_result) + + def get_anchors(self, + featmap_sizes: List[tuple], + batch_img_metas: List[dict], + device: Union[torch.device, str] = 'cuda') \ + -> Tuple[List[List[Tensor]], List[List[Tensor]]]: + """Get anchors according to feature map sizes. + + Args: + featmap_sizes (list[tuple]): Multi-level feature map sizes. + batch_img_metas (list[dict]): Image meta info. + device (torch.device or str): Device for returned tensors. + Defaults to cuda. + + Returns: + tuple: + + - anchor_list (list[list[Tensor]]): Anchors of each image. + - valid_flag_list (list[list[Tensor]]): Valid flags of each + image. + """ + num_imgs = len(batch_img_metas) + + # since feature map sizes of all images are the same, we only compute + # anchors for one time + multi_level_anchors = self.prior_generator.grid_priors( + featmap_sizes, device=device, with_stride=True) + anchor_list = [multi_level_anchors for _ in range(num_imgs)] + + # for each image, we compute valid flags of multi level anchors + valid_flag_list = [] + for img_id, img_meta in enumerate(batch_img_metas): + multi_level_flags = self.prior_generator.valid_flags( + featmap_sizes, img_meta['pad_shape'], device) + valid_flag_list.append(multi_level_flags) + return anchor_list, valid_flag_list + + +@MODELS.register_module() +class RTMDetSepBNHead(RTMDetHead): + """RTMDetHead with separated BN layers and shared conv layers. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + share_conv (bool): Whether to share conv layers between stages. + Defaults to True. + use_depthwise (bool): Whether to use depthwise separable convolution in + head. Defaults to False. + norm_cfg (:obj:`ConfigDict` or dict)): Config dict for normalization + layer. Defaults to dict(type='BN', momentum=0.03, eps=0.001). + act_cfg (:obj:`ConfigDict` or dict)): Config dict for activation layer. + Defaults to dict(type='SiLU'). + pred_kernel_size (int): Kernel size of prediction layer. Defaults to 1. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + share_conv: bool = True, + use_depthwise: bool = False, + norm_cfg: ConfigType = dict( + type='BN', momentum=0.03, eps=0.001), + act_cfg: ConfigType = dict(type='SiLU'), + pred_kernel_size: int = 1, + exp_on_reg=False, + **kwargs) -> None: + self.share_conv = share_conv + self.exp_on_reg = exp_on_reg + self.use_depthwise = use_depthwise + super().__init__( + num_classes, + in_channels, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + pred_kernel_size=pred_kernel_size, + **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + conv = DepthwiseSeparableConvModule \ + if self.use_depthwise else ConvModule + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + + self.rtm_cls = nn.ModuleList() + self.rtm_reg = nn.ModuleList() + if self.with_objectness: + self.rtm_obj = nn.ModuleList() + for n in range(len(self.prior_generator.strides)): + cls_convs = nn.ModuleList() + reg_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + cls_convs.append( + conv( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + reg_convs.append( + conv( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + self.cls_convs.append(cls_convs) + self.reg_convs.append(reg_convs) + + self.rtm_cls.append( + nn.Conv2d( + self.feat_channels, + self.num_base_priors * self.cls_out_channels, + self.pred_kernel_size, + padding=self.pred_kernel_size // 2)) + self.rtm_reg.append( + nn.Conv2d( + self.feat_channels, + self.num_base_priors * 4, + self.pred_kernel_size, + padding=self.pred_kernel_size // 2)) + if self.with_objectness: + self.rtm_obj.append( + nn.Conv2d( + self.feat_channels, + 1, + self.pred_kernel_size, + padding=self.pred_kernel_size // 2)) + + if self.share_conv: + for n in range(len(self.prior_generator.strides)): + for i in range(self.stacked_convs): + self.cls_convs[n][i].conv = self.cls_convs[0][i].conv + self.reg_convs[n][i].conv = self.reg_convs[0][i].conv + + def init_weights(self) -> None: + """Initialize weights of the head.""" + for m in self.modules(): + if isinstance(m, nn.Conv2d): + normal_init(m, mean=0, std=0.01) + if is_norm(m): + constant_init(m, 1) + bias_cls = bias_init_with_prob(0.01) + for rtm_cls, rtm_reg in zip(self.rtm_cls, self.rtm_reg): + normal_init(rtm_cls, std=0.01, bias=bias_cls) + normal_init(rtm_reg, std=0.01) + if self.with_objectness: + for rtm_obj in self.rtm_obj: + normal_init(rtm_obj, std=0.01, bias=bias_cls) + + def forward(self, feats: Tuple[Tensor, ...]) -> tuple: + """Forward features from the upstream network. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually a tuple of classification scores and bbox prediction + + - cls_scores (tuple[Tensor]): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_anchors * num_classes. + - bbox_preds (tuple[Tensor]): Box energies / deltas for all scale + levels, each is a 4D-tensor, the channels number is + num_anchors * 4. + """ + + cls_scores = [] + bbox_preds = [] + for idx, (x, stride) in enumerate( + zip(feats, self.prior_generator.strides)): + cls_feat = x + reg_feat = x + + for cls_layer in self.cls_convs[idx]: + cls_feat = cls_layer(cls_feat) + cls_score = self.rtm_cls[idx](cls_feat) + + for reg_layer in self.reg_convs[idx]: + reg_feat = reg_layer(reg_feat) + + if self.with_objectness: + objectness = self.rtm_obj[idx](reg_feat) + cls_score = inverse_sigmoid( + sigmoid_geometric_mean(cls_score, objectness)) + if self.exp_on_reg: + reg_dist = self.rtm_reg[idx](reg_feat).exp() * stride[0] + else: + reg_dist = self.rtm_reg[idx](reg_feat) * stride[0] + cls_scores.append(cls_score) + bbox_preds.append(reg_dist) + return tuple(cls_scores), tuple(bbox_preds) diff --git a/mmdetection/mmdet/models/dense_heads/rtmdet_ins_head.py b/mmdetection/mmdet/models/dense_heads/rtmdet_ins_head.py new file mode 100644 index 00000000..261a57fe --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/rtmdet_ins_head.py @@ -0,0 +1,1034 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import math +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, is_norm +from mmcv.ops import batched_nms +from mmengine.model import (BaseModule, bias_init_with_prob, constant_init, + normal_init) +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.layers.transformer import inverse_sigmoid +from mmdet.models.utils import (filter_scores_and_topk, multi_apply, + select_single_mlvl, sigmoid_geometric_mean) +from mmdet.registry import MODELS +from mmdet.structures.bbox import (cat_boxes, distance2bbox, get_box_tensor, + get_box_wh, scale_boxes) +from mmdet.utils import ConfigType, InstanceList, OptInstanceList, reduce_mean +from .rtmdet_head import RTMDetHead + + +@MODELS.register_module() +class RTMDetInsHead(RTMDetHead): + """Detection Head of RTMDet-Ins. + + Args: + num_prototypes (int): Number of mask prototype features extracted + from the mask head. Defaults to 8. + dyconv_channels (int): Channel of the dynamic conv layers. + Defaults to 8. + num_dyconvs (int): Number of the dynamic convolution layers. + Defaults to 3. + mask_loss_stride (int): Down sample stride of the masks for loss + computation. Defaults to 4. + loss_mask (:obj:`ConfigDict` or dict): Config dict for mask loss. + """ + + def __init__(self, + *args, + num_prototypes: int = 8, + dyconv_channels: int = 8, + num_dyconvs: int = 3, + mask_loss_stride: int = 4, + loss_mask=dict( + type='DiceLoss', + loss_weight=2.0, + eps=5e-6, + reduction='mean'), + **kwargs) -> None: + self.num_prototypes = num_prototypes + self.num_dyconvs = num_dyconvs + self.dyconv_channels = dyconv_channels + self.mask_loss_stride = mask_loss_stride + super().__init__(*args, **kwargs) + self.loss_mask = MODELS.build(loss_mask) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + super()._init_layers() + # a branch to predict kernels of dynamic convs + self.kernel_convs = nn.ModuleList() + # calculate num dynamic parameters + weight_nums, bias_nums = [], [] + for i in range(self.num_dyconvs): + if i == 0: + weight_nums.append( + # mask prototype and coordinate features + (self.num_prototypes + 2) * self.dyconv_channels) + bias_nums.append(self.dyconv_channels * 1) + elif i == self.num_dyconvs - 1: + weight_nums.append(self.dyconv_channels * 1) + bias_nums.append(1) + else: + weight_nums.append(self.dyconv_channels * self.dyconv_channels) + bias_nums.append(self.dyconv_channels * 1) + self.weight_nums = weight_nums + self.bias_nums = bias_nums + self.num_gen_params = sum(weight_nums) + sum(bias_nums) + + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + self.kernel_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + pred_pad_size = self.pred_kernel_size // 2 + self.rtm_kernel = nn.Conv2d( + self.feat_channels, + self.num_gen_params, + self.pred_kernel_size, + padding=pred_pad_size) + self.mask_head = MaskFeatModule( + in_channels=self.in_channels, + feat_channels=self.feat_channels, + stacked_convs=4, + num_levels=len(self.prior_generator.strides), + num_prototypes=self.num_prototypes, + act_cfg=self.act_cfg, + norm_cfg=self.norm_cfg) + + def forward(self, feats: Tuple[Tensor, ...]) -> tuple: + """Forward features from the upstream network. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually a tuple of classification scores and bbox prediction + - cls_scores (list[Tensor]): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * 4. + - kernel_preds (list[Tensor]): Dynamic conv kernels for all scale + levels, each is a 4D-tensor, the channels number is + num_gen_params. + - mask_feat (Tensor): Output feature of the mask head. Each is a + 4D-tensor, the channels number is num_prototypes. + """ + mask_feat = self.mask_head(feats) + + cls_scores = [] + bbox_preds = [] + kernel_preds = [] + for idx, (x, scale, stride) in enumerate( + zip(feats, self.scales, self.prior_generator.strides)): + cls_feat = x + reg_feat = x + kernel_feat = x + + for cls_layer in self.cls_convs: + cls_feat = cls_layer(cls_feat) + cls_score = self.rtm_cls(cls_feat) + + for kernel_layer in self.kernel_convs: + kernel_feat = kernel_layer(kernel_feat) + kernel_pred = self.rtm_kernel(kernel_feat) + + for reg_layer in self.reg_convs: + reg_feat = reg_layer(reg_feat) + + if self.with_objectness: + objectness = self.rtm_obj(reg_feat) + cls_score = inverse_sigmoid( + sigmoid_geometric_mean(cls_score, objectness)) + + reg_dist = scale(self.rtm_reg(reg_feat)) * stride[0] + + cls_scores.append(cls_score) + bbox_preds.append(reg_dist) + kernel_preds.append(kernel_pred) + return tuple(cls_scores), tuple(bbox_preds), tuple( + kernel_preds), mask_feat + + def predict_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + kernel_preds: List[Tensor], + mask_feat: Tensor, + score_factors: Optional[List[Tensor]] = None, + batch_img_metas: Optional[List[dict]] = None, + cfg: Optional[ConfigType] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Note: When score_factors is not None, the cls_scores are + usually multiplied by it then obtain the real score used in NMS, + such as CenterNess in FCOS, IoU branch in ATSS. + + Args: + cls_scores (list[Tensor]): Classification scores for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * 4, H, W). + kernel_preds (list[Tensor]): Kernel predictions of dynamic + convs for all scale levels, each is a 4D-tensor, has shape + (batch_size, num_params, H, W). + mask_feat (Tensor): Mask prototype features extracted from the + mask head, has shape (batch_size, num_prototypes, H, W). + score_factors (list[Tensor], optional): Score factor for + all scale level, each is a 4D-tensor, has shape + (batch_size, num_priors * 1, H, W). Defaults to None. + batch_img_metas (list[dict], Optional): Batch image meta info. + Defaults to None. + cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, h, w). + """ + assert len(cls_scores) == len(bbox_preds) + + if score_factors is None: + # e.g. Retina, FreeAnchor, Foveabox, etc. + with_score_factors = False + else: + # e.g. FCOS, PAA, ATSS, AutoAssign, etc. + with_score_factors = True + assert len(cls_scores) == len(score_factors) + + num_levels = len(cls_scores) + + featmap_sizes = [cls_scores[i].shape[-2:] for i in range(num_levels)] + mlvl_priors = self.prior_generator.grid_priors( + featmap_sizes, + dtype=cls_scores[0].dtype, + device=cls_scores[0].device, + with_stride=True) + + result_list = [] + + for img_id in range(len(batch_img_metas)): + img_meta = batch_img_metas[img_id] + cls_score_list = select_single_mlvl( + cls_scores, img_id, detach=True) + bbox_pred_list = select_single_mlvl( + bbox_preds, img_id, detach=True) + kernel_pred_list = select_single_mlvl( + kernel_preds, img_id, detach=True) + if with_score_factors: + score_factor_list = select_single_mlvl( + score_factors, img_id, detach=True) + else: + score_factor_list = [None for _ in range(num_levels)] + + results = self._predict_by_feat_single( + cls_score_list=cls_score_list, + bbox_pred_list=bbox_pred_list, + kernel_pred_list=kernel_pred_list, + mask_feat=mask_feat[img_id], + score_factor_list=score_factor_list, + mlvl_priors=mlvl_priors, + img_meta=img_meta, + cfg=cfg, + rescale=rescale, + with_nms=with_nms) + result_list.append(results) + return result_list + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + kernel_pred_list: List[Tensor], + mask_feat: Tensor, + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: ConfigType, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox and mask results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + kernel_preds (list[Tensor]): Kernel predictions of dynamic + convs for all scale levels of a single image, each is a + 4D-tensor, has shape (num_params, H, W). + mask_feat (Tensor): Mask prototype features of a single image + extracted from the mask head, has shape (num_prototypes, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image, each item has shape + (num_priors * 1, H, W). + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid. In all + anchor-based methods, it has shape (num_priors, 4). In + all anchor-free methods, it has shape (num_priors, 2) + when `with_stride=True`, otherwise it still has shape + (num_priors, 4). + img_meta (dict): Image meta info. + cfg (mmengine.Config): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, h, w). + """ + if score_factor_list[0] is None: + # e.g. Retina, FreeAnchor, etc. + with_score_factors = False + else: + # e.g. FCOS, PAA, ATSS, etc. + with_score_factors = True + + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bbox_preds = [] + mlvl_kernels = [] + mlvl_valid_priors = [] + mlvl_scores = [] + mlvl_labels = [] + if with_score_factors: + mlvl_score_factors = [] + else: + mlvl_score_factors = None + + for level_idx, (cls_score, bbox_pred, kernel_pred, + score_factor, priors) in \ + enumerate(zip(cls_score_list, bbox_pred_list, kernel_pred_list, + score_factor_list, mlvl_priors)): + + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + + dim = self.bbox_coder.encode_size + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, dim) + if with_score_factors: + score_factor = score_factor.permute(1, 2, + 0).reshape(-1).sigmoid() + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + kernel_pred = kernel_pred.permute(1, 2, 0).reshape( + -1, self.num_gen_params) + if self.use_sigmoid_cls: + scores = cls_score.sigmoid() + else: + # remind that we set FG labels to [0, num_class-1] + # since mmdet v2.0 + # BG cat_id: num_class + scores = cls_score.softmax(-1)[:, :-1] + + # After https://github.com/open-mmlab/mmdetection/pull/6268/, + # this operation keeps fewer bboxes under the same `nms_pre`. + # There is no difference in performance for most models. If you + # find a slight drop in performance, you can set a larger + # `nms_pre` than before. + score_thr = cfg.get('score_thr', 0) + + results = filter_scores_and_topk( + scores, score_thr, nms_pre, + dict( + bbox_pred=bbox_pred, + priors=priors, + kernel_pred=kernel_pred)) + scores, labels, keep_idxs, filtered_results = results + + bbox_pred = filtered_results['bbox_pred'] + priors = filtered_results['priors'] + kernel_pred = filtered_results['kernel_pred'] + + if with_score_factors: + score_factor = score_factor[keep_idxs] + + mlvl_bbox_preds.append(bbox_pred) + mlvl_valid_priors.append(priors) + mlvl_scores.append(scores) + mlvl_labels.append(labels) + mlvl_kernels.append(kernel_pred) + + if with_score_factors: + mlvl_score_factors.append(score_factor) + + bbox_pred = torch.cat(mlvl_bbox_preds) + priors = cat_boxes(mlvl_valid_priors) + bboxes = self.bbox_coder.decode( + priors[..., :2], bbox_pred, max_shape=img_shape) + + results = InstanceData() + results.bboxes = bboxes + results.priors = priors + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + results.kernels = torch.cat(mlvl_kernels) + if with_score_factors: + results.score_factors = torch.cat(mlvl_score_factors) + + return self._bbox_mask_post_process( + results=results, + mask_feat=mask_feat, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + + def _bbox_mask_post_process( + self, + results: InstanceData, + mask_feat, + cfg: ConfigType, + rescale: bool = False, + with_nms: bool = True, + img_meta: Optional[dict] = None) -> InstanceData: + """bbox and mask post-processing method. + + The boxes would be rescaled to the original image scale and do + the nms operation. Usually `with_nms` is False is used for aug test. + + Args: + results (:obj:`InstaceData`): Detection instance results, + each item has shape (num_bboxes, ). + cfg (ConfigDict): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Default to False. + with_nms (bool): If True, do nms before return boxes. + Default to True. + img_meta (dict, optional): Image meta info. Defaults to None. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, h, w). + """ + stride = self.prior_generator.strides[0][0] + if rescale: + assert img_meta.get('scale_factor') is not None + scale_factor = [1 / s for s in img_meta['scale_factor']] + results.bboxes = scale_boxes(results.bboxes, scale_factor) + + if hasattr(results, 'score_factors'): + # TODO: Add sqrt operation in order to be consistent with + # the paper. + score_factors = results.pop('score_factors') + results.scores = results.scores * score_factors + + # filter small size bboxes + if cfg.get('min_bbox_size', -1) >= 0: + w, h = get_box_wh(results.bboxes) + valid_mask = (w > cfg.min_bbox_size) & (h > cfg.min_bbox_size) + if not valid_mask.all(): + results = results[valid_mask] + + # TODO: deal with `with_nms` and `nms_cfg=None` in test_cfg + assert with_nms, 'with_nms must be True for RTMDet-Ins' + if results.bboxes.numel() > 0: + bboxes = get_box_tensor(results.bboxes) + det_bboxes, keep_idxs = batched_nms(bboxes, results.scores, + results.labels, cfg.nms) + results = results[keep_idxs] + # some nms would reweight the score, such as softnms + results.scores = det_bboxes[:, -1] + results = results[:cfg.max_per_img] + + # process masks + mask_logits = self._mask_predict_by_feat_single( + mask_feat, results.kernels, results.priors) + + mask_logits = F.interpolate( + mask_logits.unsqueeze(0), scale_factor=stride, mode='bilinear') + if rescale: + ori_h, ori_w = img_meta['ori_shape'][:2] + mask_logits = F.interpolate( + mask_logits, + size=[ + math.ceil(mask_logits.shape[-2] * scale_factor[0]), + math.ceil(mask_logits.shape[-1] * scale_factor[1]) + ], + mode='bilinear', + align_corners=False)[..., :ori_h, :ori_w] + masks = mask_logits.sigmoid().squeeze(0) + masks = masks > cfg.mask_thr_binary + results.masks = masks + else: + h, w = img_meta['ori_shape'][:2] if rescale else img_meta[ + 'img_shape'][:2] + results.masks = torch.zeros( + size=(results.bboxes.shape[0], h, w), + dtype=torch.bool, + device=results.bboxes.device) + + return results + + def parse_dynamic_params(self, flatten_kernels: Tensor) -> tuple: + """split kernel head prediction to conv weight and bias.""" + n_inst = flatten_kernels.size(0) + n_layers = len(self.weight_nums) + params_splits = list( + torch.split_with_sizes( + flatten_kernels, self.weight_nums + self.bias_nums, dim=1)) + weight_splits = params_splits[:n_layers] + bias_splits = params_splits[n_layers:] + for i in range(n_layers): + if i < n_layers - 1: + weight_splits[i] = weight_splits[i].reshape( + n_inst * self.dyconv_channels, -1, 1, 1) + bias_splits[i] = bias_splits[i].reshape(n_inst * + self.dyconv_channels) + else: + weight_splits[i] = weight_splits[i].reshape(n_inst, -1, 1, 1) + bias_splits[i] = bias_splits[i].reshape(n_inst) + + return weight_splits, bias_splits + + def _mask_predict_by_feat_single(self, mask_feat: Tensor, kernels: Tensor, + priors: Tensor) -> Tensor: + """Generate mask logits from mask features with dynamic convs. + + Args: + mask_feat (Tensor): Mask prototype features. + Has shape (num_prototypes, H, W). + kernels (Tensor): Kernel parameters for each instance. + Has shape (num_instance, num_params) + priors (Tensor): Center priors for each instance. + Has shape (num_instance, 4). + Returns: + Tensor: Instance segmentation masks for each instance. + Has shape (num_instance, H, W). + """ + num_inst = priors.shape[0] + h, w = mask_feat.size()[-2:] + if num_inst < 1: + return torch.empty( + size=(num_inst, h, w), + dtype=mask_feat.dtype, + device=mask_feat.device) + if len(mask_feat.shape) < 4: + mask_feat.unsqueeze(0) + + coord = self.prior_generator.single_level_grid_priors( + (h, w), level_idx=0, device=mask_feat.device).reshape(1, -1, 2) + num_inst = priors.shape[0] + points = priors[:, :2].reshape(-1, 1, 2) + strides = priors[:, 2:].reshape(-1, 1, 2) + relative_coord = (points - coord).permute(0, 2, 1) / ( + strides[..., 0].reshape(-1, 1, 1) * 8) + relative_coord = relative_coord.reshape(num_inst, 2, h, w) + + mask_feat = torch.cat( + [relative_coord, + mask_feat.repeat(num_inst, 1, 1, 1)], dim=1) + weights, biases = self.parse_dynamic_params(kernels) + + n_layers = len(weights) + x = mask_feat.reshape(1, -1, h, w) + for i, (weight, bias) in enumerate(zip(weights, biases)): + x = F.conv2d( + x, weight, bias=bias, stride=1, padding=0, groups=num_inst) + if i < n_layers - 1: + x = F.relu(x) + x = x.reshape(num_inst, h, w) + return x + + def loss_mask_by_feat(self, mask_feats: Tensor, flatten_kernels: Tensor, + sampling_results_list: list, + batch_gt_instances: InstanceList) -> Tensor: + """Compute instance segmentation loss. + + Args: + mask_feats (list[Tensor]): Mask prototype features extracted from + the mask head. Has shape (N, num_prototypes, H, W) + flatten_kernels (list[Tensor]): Kernels of the dynamic conv layers. + Has shape (N, num_instances, num_params) + sampling_results_list (list[:obj:`SamplingResults`]) Batch of + assignment results. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + + Returns: + Tensor: The mask loss tensor. + """ + batch_pos_mask_logits = [] + pos_gt_masks = [] + for idx, (mask_feat, kernels, sampling_results, + gt_instances) in enumerate( + zip(mask_feats, flatten_kernels, sampling_results_list, + batch_gt_instances)): + pos_priors = sampling_results.pos_priors + pos_inds = sampling_results.pos_inds + pos_kernels = kernels[pos_inds] # n_pos, num_gen_params + pos_mask_logits = self._mask_predict_by_feat_single( + mask_feat, pos_kernels, pos_priors) + if gt_instances.masks.numel() == 0: + gt_masks = torch.empty_like(gt_instances.masks) + else: + gt_masks = gt_instances.masks[ + sampling_results.pos_assigned_gt_inds, :] + batch_pos_mask_logits.append(pos_mask_logits) + pos_gt_masks.append(gt_masks) + + pos_gt_masks = torch.cat(pos_gt_masks, 0) + batch_pos_mask_logits = torch.cat(batch_pos_mask_logits, 0) + + # avg_factor + num_pos = batch_pos_mask_logits.shape[0] + num_pos = reduce_mean(mask_feats.new_tensor([num_pos + ])).clamp_(min=1).item() + + if batch_pos_mask_logits.shape[0] == 0: + return mask_feats.sum() * 0 + + scale = self.prior_generator.strides[0][0] // self.mask_loss_stride + # upsample pred masks + batch_pos_mask_logits = F.interpolate( + batch_pos_mask_logits.unsqueeze(0), + scale_factor=scale, + mode='bilinear', + align_corners=False).squeeze(0) + # downsample gt masks + pos_gt_masks = pos_gt_masks[:, self.mask_loss_stride // + 2::self.mask_loss_stride, + self.mask_loss_stride // + 2::self.mask_loss_stride] + + loss_mask = self.loss_mask( + batch_pos_mask_logits, + pos_gt_masks, + weight=None, + avg_factor=num_pos) + + return loss_mask + + def loss_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + kernel_preds: List[Tensor], + mask_feat: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None): + """Compute losses of the head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Decoded box for each scale + level with shape (N, num_anchors * 4, H, W) in + [tl_x, tl_y, br_x, br_y] format. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + num_imgs = len(batch_img_metas) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + flatten_cls_scores = torch.cat([ + cls_score.permute(0, 2, 3, 1).reshape(num_imgs, -1, + self.cls_out_channels) + for cls_score in cls_scores + ], 1) + flatten_kernels = torch.cat([ + kernel_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, + self.num_gen_params) + for kernel_pred in kernel_preds + ], 1) + decoded_bboxes = [] + for anchor, bbox_pred in zip(anchor_list[0], bbox_preds): + anchor = anchor.reshape(-1, 4) + bbox_pred = bbox_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, 4) + bbox_pred = distance2bbox(anchor, bbox_pred) + decoded_bboxes.append(bbox_pred) + + flatten_bboxes = torch.cat(decoded_bboxes, 1) + for gt_instances in batch_gt_instances: + gt_instances.masks = gt_instances.masks.to_tensor( + dtype=torch.bool, device=device) + + cls_reg_targets = self.get_targets( + flatten_cls_scores, + flatten_bboxes, + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + (anchor_list, labels_list, label_weights_list, bbox_targets_list, + assign_metrics_list, sampling_results_list) = cls_reg_targets + + losses_cls, losses_bbox,\ + cls_avg_factors, bbox_avg_factors = multi_apply( + self.loss_by_feat_single, + cls_scores, + decoded_bboxes, + labels_list, + label_weights_list, + bbox_targets_list, + assign_metrics_list, + self.prior_generator.strides) + + cls_avg_factor = reduce_mean(sum(cls_avg_factors)).clamp_(min=1).item() + losses_cls = list(map(lambda x: x / cls_avg_factor, losses_cls)) + + bbox_avg_factor = reduce_mean( + sum(bbox_avg_factors)).clamp_(min=1).item() + losses_bbox = list(map(lambda x: x / bbox_avg_factor, losses_bbox)) + + loss_mask = self.loss_mask_by_feat(mask_feat, flatten_kernels, + sampling_results_list, + batch_gt_instances) + loss = dict( + loss_cls=losses_cls, loss_bbox=losses_bbox, loss_mask=loss_mask) + return loss + + +class MaskFeatModule(BaseModule): + """Mask feature head used in RTMDet-Ins. + + Args: + in_channels (int): Number of channels in the input feature map. + feat_channels (int): Number of hidden channels of the mask feature + map branch. + num_levels (int): The starting feature map level from RPN that + will be used to predict the mask feature map. + num_prototypes (int): Number of output channel of the mask feature + map branch. This is the channel count of the mask + feature map that to be dynamically convolved with the predicted + kernel. + stacked_convs (int): Number of convs in mask feature branch. + act_cfg (:obj:`ConfigDict` or dict): Config dict for activation layer. + Default: dict(type='ReLU', inplace=True) + norm_cfg (dict): Config dict for normalization layer. Default: None. + """ + + def __init__( + self, + in_channels: int, + feat_channels: int = 256, + stacked_convs: int = 4, + num_levels: int = 3, + num_prototypes: int = 8, + act_cfg: ConfigType = dict(type='ReLU', inplace=True), + norm_cfg: ConfigType = dict(type='BN') + ) -> None: + super().__init__(init_cfg=None) + self.num_levels = num_levels + self.fusion_conv = nn.Conv2d(num_levels * in_channels, in_channels, 1) + convs = [] + for i in range(stacked_convs): + in_c = in_channels if i == 0 else feat_channels + convs.append( + ConvModule( + in_c, + feat_channels, + 3, + padding=1, + act_cfg=act_cfg, + norm_cfg=norm_cfg)) + self.stacked_convs = nn.Sequential(*convs) + self.projection = nn.Conv2d( + feat_channels, num_prototypes, kernel_size=1) + + def forward(self, features: Tuple[Tensor, ...]) -> Tensor: + # multi-level feature fusion + fusion_feats = [features[0]] + size = features[0].shape[-2:] + for i in range(1, self.num_levels): + f = F.interpolate(features[i], size=size, mode='bilinear') + fusion_feats.append(f) + fusion_feats = torch.cat(fusion_feats, dim=1) + fusion_feats = self.fusion_conv(fusion_feats) + # pred mask feats + mask_features = self.stacked_convs(fusion_feats) + mask_features = self.projection(mask_features) + return mask_features + + +@MODELS.register_module() +class RTMDetInsSepBNHead(RTMDetInsHead): + """Detection Head of RTMDet-Ins with sep-bn layers. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + share_conv (bool): Whether to share conv layers between stages. + Defaults to True. + norm_cfg (:obj:`ConfigDict` or dict)): Config dict for normalization + layer. Defaults to dict(type='BN'). + act_cfg (:obj:`ConfigDict` or dict)): Config dict for activation layer. + Defaults to dict(type='SiLU', inplace=True). + pred_kernel_size (int): Kernel size of prediction layer. Defaults to 1. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + share_conv: bool = True, + with_objectness: bool = False, + norm_cfg: ConfigType = dict(type='BN', requires_grad=True), + act_cfg: ConfigType = dict(type='SiLU', inplace=True), + pred_kernel_size: int = 1, + **kwargs) -> None: + self.share_conv = share_conv + super().__init__( + num_classes, + in_channels, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + pred_kernel_size=pred_kernel_size, + with_objectness=with_objectness, + **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + self.kernel_convs = nn.ModuleList() + + self.rtm_cls = nn.ModuleList() + self.rtm_reg = nn.ModuleList() + self.rtm_kernel = nn.ModuleList() + self.rtm_obj = nn.ModuleList() + + # calculate num dynamic parameters + weight_nums, bias_nums = [], [] + for i in range(self.num_dyconvs): + if i == 0: + weight_nums.append( + (self.num_prototypes + 2) * self.dyconv_channels) + bias_nums.append(self.dyconv_channels) + elif i == self.num_dyconvs - 1: + weight_nums.append(self.dyconv_channels) + bias_nums.append(1) + else: + weight_nums.append(self.dyconv_channels * self.dyconv_channels) + bias_nums.append(self.dyconv_channels) + self.weight_nums = weight_nums + self.bias_nums = bias_nums + self.num_gen_params = sum(weight_nums) + sum(bias_nums) + pred_pad_size = self.pred_kernel_size // 2 + + for n in range(len(self.prior_generator.strides)): + cls_convs = nn.ModuleList() + reg_convs = nn.ModuleList() + kernel_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + kernel_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + self.cls_convs.append(cls_convs) + self.reg_convs.append(cls_convs) + self.kernel_convs.append(kernel_convs) + + self.rtm_cls.append( + nn.Conv2d( + self.feat_channels, + self.num_base_priors * self.cls_out_channels, + self.pred_kernel_size, + padding=pred_pad_size)) + self.rtm_reg.append( + nn.Conv2d( + self.feat_channels, + self.num_base_priors * 4, + self.pred_kernel_size, + padding=pred_pad_size)) + self.rtm_kernel.append( + nn.Conv2d( + self.feat_channels, + self.num_gen_params, + self.pred_kernel_size, + padding=pred_pad_size)) + if self.with_objectness: + self.rtm_obj.append( + nn.Conv2d( + self.feat_channels, + 1, + self.pred_kernel_size, + padding=pred_pad_size)) + + if self.share_conv: + for n in range(len(self.prior_generator.strides)): + for i in range(self.stacked_convs): + self.cls_convs[n][i].conv = self.cls_convs[0][i].conv + self.reg_convs[n][i].conv = self.reg_convs[0][i].conv + + self.mask_head = MaskFeatModule( + in_channels=self.in_channels, + feat_channels=self.feat_channels, + stacked_convs=4, + num_levels=len(self.prior_generator.strides), + num_prototypes=self.num_prototypes, + act_cfg=self.act_cfg, + norm_cfg=self.norm_cfg) + + def init_weights(self) -> None: + """Initialize weights of the head.""" + for m in self.modules(): + if isinstance(m, nn.Conv2d): + normal_init(m, mean=0, std=0.01) + if is_norm(m): + constant_init(m, 1) + bias_cls = bias_init_with_prob(0.01) + for rtm_cls, rtm_reg, rtm_kernel in zip(self.rtm_cls, self.rtm_reg, + self.rtm_kernel): + normal_init(rtm_cls, std=0.01, bias=bias_cls) + normal_init(rtm_reg, std=0.01, bias=1) + if self.with_objectness: + for rtm_obj in self.rtm_obj: + normal_init(rtm_obj, std=0.01, bias=bias_cls) + + def forward(self, feats: Tuple[Tensor, ...]) -> tuple: + """Forward features from the upstream network. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually a tuple of classification scores and bbox prediction + - cls_scores (list[Tensor]): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * 4. + - kernel_preds (list[Tensor]): Dynamic conv kernels for all scale + levels, each is a 4D-tensor, the channels number is + num_gen_params. + - mask_feat (Tensor): Output feature of the mask head. Each is a + 4D-tensor, the channels number is num_prototypes. + """ + mask_feat = self.mask_head(feats) + + cls_scores = [] + bbox_preds = [] + kernel_preds = [] + for idx, (x, stride) in enumerate( + zip(feats, self.prior_generator.strides)): + cls_feat = x + reg_feat = x + kernel_feat = x + + for cls_layer in self.cls_convs[idx]: + cls_feat = cls_layer(cls_feat) + cls_score = self.rtm_cls[idx](cls_feat) + + for kernel_layer in self.kernel_convs[idx]: + kernel_feat = kernel_layer(kernel_feat) + kernel_pred = self.rtm_kernel[idx](kernel_feat) + + for reg_layer in self.reg_convs[idx]: + reg_feat = reg_layer(reg_feat) + + if self.with_objectness: + objectness = self.rtm_obj[idx](reg_feat) + cls_score = inverse_sigmoid( + sigmoid_geometric_mean(cls_score, objectness)) + + reg_dist = F.relu(self.rtm_reg[idx](reg_feat)) * stride[0] + + cls_scores.append(cls_score) + bbox_preds.append(reg_dist) + kernel_preds.append(kernel_pred) + return tuple(cls_scores), tuple(bbox_preds), tuple( + kernel_preds), mask_feat diff --git a/mmdetection/mmdet/models/dense_heads/sabl_retina_head.py b/mmdetection/mmdet/models/dense_heads/sabl_retina_head.py new file mode 100644 index 00000000..8cd1b71c --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/sabl_retina_head.py @@ -0,0 +1,706 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple, Union + +import numpy as np +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, OptConfigType, + OptInstanceList) +from ..task_modules.samplers import PseudoSampler +from ..utils import (filter_scores_and_topk, images_to_levels, multi_apply, + unmap) +from .base_dense_head import BaseDenseHead +from .guided_anchor_head import GuidedAnchorHead + + +@MODELS.register_module() +class SABLRetinaHead(BaseDenseHead): + """Side-Aware Boundary Localization (SABL) for RetinaNet. + + The anchor generation, assigning and sampling in SABLRetinaHead + are the same as GuidedAnchorHead for guided anchoring. + + Please refer to https://arxiv.org/abs/1912.04260 for more details. + + Args: + num_classes (int): Number of classes. + in_channels (int): Number of channels in the input feature map. + stacked_convs (int): Number of Convs for classification and + regression branches. Defaults to 4. + feat_channels (int): Number of hidden channels. Defaults to 256. + approx_anchor_generator (:obj:`ConfigType` or dict): Config dict for + approx generator. + square_anchor_generator (:obj:`ConfigDict` or dict): Config dict for + square generator. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + ConvModule. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + Norm Layer. Defaults to None. + bbox_coder (:obj:`ConfigDict` or dict): Config dict for bbox coder. + reg_decoded_bbox (bool): If true, the regression loss would be + applied directly on decoded bounding boxes, converting both + the predicted boxes and regression targets to absolute + coordinates format. Default False. It should be ``True`` when + using ``IoULoss``, ``GIoULoss``, or ``DIoULoss`` in the bbox head. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config of + SABLRetinaHead. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + SABLRetinaHead. + loss_cls (:obj:`ConfigDict` or dict): Config of classification loss. + loss_bbox_cls (:obj:`ConfigDict` or dict): Config of classification + loss for bbox branch. + loss_bbox_reg (:obj:`ConfigDict` or dict): Config of regression loss + for bbox branch. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. + """ + + def __init__( + self, + num_classes: int, + in_channels: int, + stacked_convs: int = 4, + feat_channels: int = 256, + approx_anchor_generator: ConfigType = dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + square_anchor_generator: ConfigType = dict( + type='AnchorGenerator', + ratios=[1.0], + scales=[4], + strides=[8, 16, 32, 64, 128]), + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + bbox_coder: ConfigType = dict( + type='BucketingBBoxCoder', num_buckets=14, scale_factor=3.0), + reg_decoded_bbox: bool = False, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + loss_cls: ConfigType = dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + loss_bbox_cls: ConfigType = dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.5), + loss_bbox_reg: ConfigType = dict( + type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.5), + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', name='retina_cls', std=0.01, bias_prob=0.01)) + ) -> None: + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.num_classes = num_classes + self.feat_channels = feat_channels + self.num_buckets = bbox_coder['num_buckets'] + self.side_num = int(np.ceil(self.num_buckets / 2)) + + assert (approx_anchor_generator['octave_base_scale'] == + square_anchor_generator['scales'][0]) + assert (approx_anchor_generator['strides'] == + square_anchor_generator['strides']) + + self.approx_anchor_generator = TASK_UTILS.build( + approx_anchor_generator) + self.square_anchor_generator = TASK_UTILS.build( + square_anchor_generator) + self.approxs_per_octave = ( + self.approx_anchor_generator.num_base_priors[0]) + + # one anchor per location + self.num_base_priors = self.square_anchor_generator.num_base_priors[0] + + self.stacked_convs = stacked_convs + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + + self.reg_decoded_bbox = reg_decoded_bbox + + self.use_sigmoid_cls = loss_cls.get('use_sigmoid', False) + if self.use_sigmoid_cls: + self.cls_out_channels = num_classes + else: + self.cls_out_channels = num_classes + 1 + + self.bbox_coder = TASK_UTILS.build(bbox_coder) + self.loss_cls = MODELS.build(loss_cls) + self.loss_bbox_cls = MODELS.build(loss_bbox_cls) + self.loss_bbox_reg = MODELS.build(loss_bbox_reg) + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + # use PseudoSampler when sampling is False + if 'sampler' in self.train_cfg: + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], default_args=dict(context=self)) + else: + self.sampler = PseudoSampler(context=self) + + self._init_layers() + + def _init_layers(self) -> None: + self.relu = nn.ReLU(inplace=True) + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.reg_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.retina_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + self.retina_bbox_reg = nn.Conv2d( + self.feat_channels, self.side_num * 4, 3, padding=1) + self.retina_bbox_cls = nn.Conv2d( + self.feat_channels, self.side_num * 4, 3, padding=1) + + def forward_single(self, x: Tensor) -> Tuple[Tensor, Tensor]: + cls_feat = x + reg_feat = x + for cls_conv in self.cls_convs: + cls_feat = cls_conv(cls_feat) + for reg_conv in self.reg_convs: + reg_feat = reg_conv(reg_feat) + cls_score = self.retina_cls(cls_feat) + bbox_cls_pred = self.retina_bbox_cls(reg_feat) + bbox_reg_pred = self.retina_bbox_reg(reg_feat) + bbox_pred = (bbox_cls_pred, bbox_reg_pred) + return cls_score, bbox_pred + + def forward(self, feats: List[Tensor]) -> Tuple[List[Tensor]]: + return multi_apply(self.forward_single, feats) + + def get_anchors( + self, + featmap_sizes: List[tuple], + img_metas: List[dict], + device: Union[torch.device, str] = 'cuda' + ) -> Tuple[List[List[Tensor]], List[List[Tensor]]]: + """Get squares according to feature map sizes and guided anchors. + + Args: + featmap_sizes (list[tuple]): Multi-level feature map sizes. + img_metas (list[dict]): Image meta info. + device (torch.device | str): device for returned tensors + + Returns: + tuple: square approxs of each image + """ + num_imgs = len(img_metas) + + # since feature map sizes of all images are the same, we only compute + # squares for one time + multi_level_squares = self.square_anchor_generator.grid_priors( + featmap_sizes, device=device) + squares_list = [multi_level_squares for _ in range(num_imgs)] + + return squares_list + + def get_targets(self, + approx_list: List[List[Tensor]], + inside_flag_list: List[List[Tensor]], + square_list: List[List[Tensor]], + batch_gt_instances: InstanceList, + batch_img_metas, + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs=True) -> tuple: + """Compute bucketing targets. + + Args: + approx_list (list[list[Tensor]]): Multi level approxs of each + image. + inside_flag_list (list[list[Tensor]]): Multi level inside flags of + each image. + square_list (list[list[Tensor]]): Multi level squares of each + image. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + + Returns: + tuple: Returns a tuple containing learning targets. + + - labels_list (list[Tensor]): Labels of each level. + - label_weights_list (list[Tensor]): Label weights of each level. + - bbox_cls_targets_list (list[Tensor]): BBox cls targets of \ + each level. + - bbox_cls_weights_list (list[Tensor]): BBox cls weights of \ + each level. + - bbox_reg_targets_list (list[Tensor]): BBox reg targets of \ + each level. + - bbox_reg_weights_list (list[Tensor]): BBox reg weights of \ + each level. + - num_total_pos (int): Number of positive samples in all images. + - num_total_neg (int): Number of negative samples in all images. + """ + num_imgs = len(batch_img_metas) + assert len(approx_list) == len(inside_flag_list) == len( + square_list) == num_imgs + # anchor number of multi levels + num_level_squares = [squares.size(0) for squares in square_list[0]] + # concat all level anchors and flags to a single tensor + inside_flag_flat_list = [] + approx_flat_list = [] + square_flat_list = [] + for i in range(num_imgs): + assert len(square_list[i]) == len(inside_flag_list[i]) + inside_flag_flat_list.append(torch.cat(inside_flag_list[i])) + approx_flat_list.append(torch.cat(approx_list[i])) + square_flat_list.append(torch.cat(square_list[i])) + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None for _ in range(num_imgs)] + (all_labels, all_label_weights, all_bbox_cls_targets, + all_bbox_cls_weights, all_bbox_reg_targets, all_bbox_reg_weights, + pos_inds_list, neg_inds_list, sampling_results_list) = multi_apply( + self._get_targets_single, + approx_flat_list, + inside_flag_flat_list, + square_flat_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + + # sampled anchors of all images + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + # split targets to a list w.r.t. multiple levels + labels_list = images_to_levels(all_labels, num_level_squares) + label_weights_list = images_to_levels(all_label_weights, + num_level_squares) + bbox_cls_targets_list = images_to_levels(all_bbox_cls_targets, + num_level_squares) + bbox_cls_weights_list = images_to_levels(all_bbox_cls_weights, + num_level_squares) + bbox_reg_targets_list = images_to_levels(all_bbox_reg_targets, + num_level_squares) + bbox_reg_weights_list = images_to_levels(all_bbox_reg_weights, + num_level_squares) + return (labels_list, label_weights_list, bbox_cls_targets_list, + bbox_cls_weights_list, bbox_reg_targets_list, + bbox_reg_weights_list, avg_factor) + + def _get_targets_single(self, + flat_approxs: Tensor, + inside_flags: Tensor, + flat_squares: Tensor, + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression and classification targets for anchors in a + single image. + + Args: + flat_approxs (Tensor): flat approxs of a single image, + shape (n, 4) + inside_flags (Tensor): inside flags of a single image, + shape (n, ). + flat_squares (Tensor): flat squares of a single image, + shape (approxs_per_octave * n, 4) + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. Defaults to True. + + Returns: + tuple: + + - labels_list (Tensor): Labels in a single image. + - label_weights (Tensor): Label weights in a single image. + - bbox_cls_targets (Tensor): BBox cls targets in a single image. + - bbox_cls_weights (Tensor): BBox cls weights in a single image. + - bbox_reg_targets (Tensor): BBox reg targets in a single image. + - bbox_reg_weights (Tensor): BBox reg weights in a single image. + - num_total_pos (int): Number of positive samples in a single \ + image. + - num_total_neg (int): Number of negative samples in a single \ + image. + - sampling_result (:obj:`SamplingResult`): Sampling result object. + """ + if not inside_flags.any(): + raise ValueError( + 'There is no valid anchor inside the image boundary. Please ' + 'check the image size and anchor sizes, or set ' + '``allowed_border`` to -1 to skip the condition.') + # assign gt and sample anchors + num_square = flat_squares.size(0) + approxs = flat_approxs.view(num_square, self.approxs_per_octave, 4) + approxs = approxs[inside_flags, ...] + squares = flat_squares[inside_flags, :] + + pred_instances = InstanceData() + pred_instances.priors = squares + pred_instances.approxs = approxs + assign_result = self.assigner.assign(pred_instances, gt_instances, + gt_instances_ignore) + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_valid_squares = squares.shape[0] + bbox_cls_targets = squares.new_zeros( + (num_valid_squares, self.side_num * 4)) + bbox_cls_weights = squares.new_zeros( + (num_valid_squares, self.side_num * 4)) + bbox_reg_targets = squares.new_zeros( + (num_valid_squares, self.side_num * 4)) + bbox_reg_weights = squares.new_zeros( + (num_valid_squares, self.side_num * 4)) + labels = squares.new_full((num_valid_squares, ), + self.num_classes, + dtype=torch.long) + label_weights = squares.new_zeros(num_valid_squares, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + (pos_bbox_reg_targets, pos_bbox_reg_weights, pos_bbox_cls_targets, + pos_bbox_cls_weights) = self.bbox_coder.encode( + sampling_result.pos_bboxes, sampling_result.pos_gt_bboxes) + + bbox_cls_targets[pos_inds, :] = pos_bbox_cls_targets + bbox_reg_targets[pos_inds, :] = pos_bbox_reg_targets + bbox_cls_weights[pos_inds, :] = pos_bbox_cls_weights + bbox_reg_weights[pos_inds, :] = pos_bbox_reg_weights + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_squares.size(0) + labels = unmap( + labels, num_total_anchors, inside_flags, fill=self.num_classes) + label_weights = unmap(label_weights, num_total_anchors, + inside_flags) + bbox_cls_targets = unmap(bbox_cls_targets, num_total_anchors, + inside_flags) + bbox_cls_weights = unmap(bbox_cls_weights, num_total_anchors, + inside_flags) + bbox_reg_targets = unmap(bbox_reg_targets, num_total_anchors, + inside_flags) + bbox_reg_weights = unmap(bbox_reg_weights, num_total_anchors, + inside_flags) + return (labels, label_weights, bbox_cls_targets, bbox_cls_weights, + bbox_reg_targets, bbox_reg_weights, pos_inds, neg_inds, + sampling_result) + + def loss_by_feat_single(self, cls_score: Tensor, bbox_pred: Tensor, + labels: Tensor, label_weights: Tensor, + bbox_cls_targets: Tensor, bbox_cls_weights: Tensor, + bbox_reg_targets: Tensor, bbox_reg_weights: Tensor, + avg_factor: float) -> Tuple[Tensor]: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + cls_score (Tensor): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W). + bbox_pred (Tensor): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + labels (Tensor): Labels in a single image. + label_weights (Tensor): Label weights in a single level. + bbox_cls_targets (Tensor): BBox cls targets in a single level. + bbox_cls_weights (Tensor): BBox cls weights in a single level. + bbox_reg_targets (Tensor): BBox reg targets in a single level. + bbox_reg_weights (Tensor): BBox reg weights in a single level. + avg_factor (int): Average factor that is used to average the loss. + + Returns: + tuple: loss components. + """ + # classification loss + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + cls_score = cls_score.permute(0, 2, 3, + 1).reshape(-1, self.cls_out_channels) + loss_cls = self.loss_cls( + cls_score, labels, label_weights, avg_factor=avg_factor) + # regression loss + bbox_cls_targets = bbox_cls_targets.reshape(-1, self.side_num * 4) + bbox_cls_weights = bbox_cls_weights.reshape(-1, self.side_num * 4) + bbox_reg_targets = bbox_reg_targets.reshape(-1, self.side_num * 4) + bbox_reg_weights = bbox_reg_weights.reshape(-1, self.side_num * 4) + (bbox_cls_pred, bbox_reg_pred) = bbox_pred + bbox_cls_pred = bbox_cls_pred.permute(0, 2, 3, 1).reshape( + -1, self.side_num * 4) + bbox_reg_pred = bbox_reg_pred.permute(0, 2, 3, 1).reshape( + -1, self.side_num * 4) + loss_bbox_cls = self.loss_bbox_cls( + bbox_cls_pred, + bbox_cls_targets.long(), + bbox_cls_weights, + avg_factor=avg_factor * 4 * self.side_num) + loss_bbox_reg = self.loss_bbox_reg( + bbox_reg_pred, + bbox_reg_targets, + bbox_reg_weights, + avg_factor=avg_factor * 4 * self.bbox_coder.offset_topk) + return loss_cls, loss_bbox_cls, loss_bbox_reg + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + has shape (N, num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.approx_anchor_generator.num_levels + + device = cls_scores[0].device + + # get sampled approxes + approxs_list, inside_flag_list = GuidedAnchorHead.get_sampled_approxs( + self, featmap_sizes, batch_img_metas, device=device) + + square_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + cls_reg_targets = self.get_targets( + approxs_list, + inside_flag_list, + square_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + (labels_list, label_weights_list, bbox_cls_targets_list, + bbox_cls_weights_list, bbox_reg_targets_list, bbox_reg_weights_list, + avg_factor) = cls_reg_targets + + losses_cls, losses_bbox_cls, losses_bbox_reg = multi_apply( + self.loss_by_feat_single, + cls_scores, + bbox_preds, + labels_list, + label_weights_list, + bbox_cls_targets_list, + bbox_cls_weights_list, + bbox_reg_targets_list, + bbox_reg_weights_list, + avg_factor=avg_factor) + return dict( + loss_cls=losses_cls, + loss_bbox_cls=losses_bbox_cls, + loss_bbox_reg=losses_bbox_reg) + + def predict_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_img_metas: List[dict], + cfg: Optional[ConfigDict] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Note: When score_factors is not None, the cls_scores are + usually multiplied by it then obtain the real score used in NMS, + such as CenterNess in FCOS, IoU branch in ATSS. + + Args: + cls_scores (list[Tensor]): Classification scores for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * 4, H, W). + batch_img_metas (list[dict], Optional): Batch image meta info. + cfg (:obj:`ConfigDict`, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_scores) == len(bbox_preds) + num_levels = len(cls_scores) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + + device = cls_scores[0].device + mlvl_anchors = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + result_list = [] + for img_id in range(len(batch_img_metas)): + cls_score_list = [ + cls_scores[i][img_id].detach() for i in range(num_levels) + ] + bbox_cls_pred_list = [ + bbox_preds[i][0][img_id].detach() for i in range(num_levels) + ] + bbox_reg_pred_list = [ + bbox_preds[i][1][img_id].detach() for i in range(num_levels) + ] + proposals = self._predict_by_feat_single( + cls_scores=cls_score_list, + bbox_cls_preds=bbox_cls_pred_list, + bbox_reg_preds=bbox_reg_pred_list, + mlvl_anchors=mlvl_anchors[img_id], + img_meta=batch_img_metas[img_id], + cfg=cfg, + rescale=rescale, + with_nms=with_nms) + result_list.append(proposals) + return result_list + + def _predict_by_feat_single(self, + cls_scores: List[Tensor], + bbox_cls_preds: List[Tensor], + bbox_reg_preds: List[Tensor], + mlvl_anchors: List[Tensor], + img_meta: dict, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + cfg = self.test_cfg if cfg is None else cfg + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bboxes = [] + mlvl_scores = [] + mlvl_confids = [] + mlvl_labels = [] + assert len(cls_scores) == len(bbox_cls_preds) == len( + bbox_reg_preds) == len(mlvl_anchors) + for cls_score, bbox_cls_pred, bbox_reg_pred, anchors in zip( + cls_scores, bbox_cls_preds, bbox_reg_preds, mlvl_anchors): + assert cls_score.size()[-2:] == bbox_cls_pred.size( + )[-2:] == bbox_reg_pred.size()[-2::] + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + if self.use_sigmoid_cls: + scores = cls_score.sigmoid() + else: + scores = cls_score.softmax(-1)[:, :-1] + bbox_cls_pred = bbox_cls_pred.permute(1, 2, 0).reshape( + -1, self.side_num * 4) + bbox_reg_pred = bbox_reg_pred.permute(1, 2, 0).reshape( + -1, self.side_num * 4) + + # After https://github.com/open-mmlab/mmdetection/pull/6268/, + # this operation keeps fewer bboxes under the same `nms_pre`. + # There is no difference in performance for most models. If you + # find a slight drop in performance, you can set a larger + # `nms_pre` than before. + results = filter_scores_and_topk( + scores, cfg.score_thr, nms_pre, + dict( + anchors=anchors, + bbox_cls_pred=bbox_cls_pred, + bbox_reg_pred=bbox_reg_pred)) + scores, labels, _, filtered_results = results + + anchors = filtered_results['anchors'] + bbox_cls_pred = filtered_results['bbox_cls_pred'] + bbox_reg_pred = filtered_results['bbox_reg_pred'] + + bbox_preds = [ + bbox_cls_pred.contiguous(), + bbox_reg_pred.contiguous() + ] + bboxes, confids = self.bbox_coder.decode( + anchors.contiguous(), + bbox_preds, + max_shape=img_meta['img_shape']) + + mlvl_bboxes.append(bboxes) + mlvl_scores.append(scores) + mlvl_confids.append(confids) + mlvl_labels.append(labels) + + results = InstanceData() + results.bboxes = torch.cat(mlvl_bboxes) + results.scores = torch.cat(mlvl_scores) + results.score_factors = torch.cat(mlvl_confids) + results.labels = torch.cat(mlvl_labels) + + return self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) diff --git a/mmdetection/mmdet/models/dense_heads/solo_head.py b/mmdetection/mmdet/models/dense_heads/solo_head.py new file mode 100644 index 00000000..8cf33845 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/solo_head.py @@ -0,0 +1,1263 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import mmcv +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.utils.misc import floordiv +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, InstanceList, MultiConfig, OptConfigType +from ..layers import mask_matrix_nms +from ..utils import center_of_mass, generate_coordinate, multi_apply +from .base_mask_head import BaseMaskHead + + +@MODELS.register_module() +class SOLOHead(BaseMaskHead): + """SOLO mask head used in `SOLO: Segmenting Objects by Locations. + + `_ + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + feat_channels (int): Number of hidden channels. Used in child classes. + Defaults to 256. + stacked_convs (int): Number of stacking convs of the head. + Defaults to 4. + strides (tuple): Downsample factor of each feature map. + scale_ranges (tuple[tuple[int, int]]): Area range of multiple + level masks, in the format [(min1, max1), (min2, max2), ...]. + A range of (16, 64) means the area range between (16, 64). + pos_scale (float): Constant scale factor to control the center region. + num_grids (list[int]): Divided image into a uniform grids, each + feature map has a different grid value. The number of output + channels is grid ** 2. Defaults to [40, 36, 24, 16, 12]. + cls_down_index (int): The index of downsample operation in + classification branch. Defaults to 0. + loss_mask (dict): Config of mask loss. + loss_cls (dict): Config of classification loss. + norm_cfg (dict): Dictionary to construct and config norm layer. + Defaults to norm_cfg=dict(type='GN', num_groups=32, + requires_grad=True). + train_cfg (dict): Training config of head. + test_cfg (dict): Testing config of head. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__( + self, + num_classes: int, + in_channels: int, + feat_channels: int = 256, + stacked_convs: int = 4, + strides: tuple = (4, 8, 16, 32, 64), + scale_ranges: tuple = ((8, 32), (16, 64), (32, 128), (64, 256), (128, + 512)), + pos_scale: float = 0.2, + num_grids: list = [40, 36, 24, 16, 12], + cls_down_index: int = 0, + loss_mask: ConfigType = dict( + type='DiceLoss', use_sigmoid=True, loss_weight=3.0), + loss_cls: ConfigType = dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + norm_cfg: ConfigType = dict( + type='GN', num_groups=32, requires_grad=True), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: MultiConfig = [ + dict(type='Normal', layer='Conv2d', std=0.01), + dict( + type='Normal', + std=0.01, + bias_prob=0.01, + override=dict(name='conv_mask_list')), + dict( + type='Normal', + std=0.01, + bias_prob=0.01, + override=dict(name='conv_cls')) + ] + ) -> None: + super().__init__(init_cfg=init_cfg) + self.num_classes = num_classes + self.cls_out_channels = self.num_classes + self.in_channels = in_channels + self.feat_channels = feat_channels + self.stacked_convs = stacked_convs + self.strides = strides + self.num_grids = num_grids + # number of FPN feats + self.num_levels = len(strides) + assert self.num_levels == len(scale_ranges) == len(num_grids) + self.scale_ranges = scale_ranges + self.pos_scale = pos_scale + + self.cls_down_index = cls_down_index + self.loss_cls = MODELS.build(loss_cls) + self.loss_mask = MODELS.build(loss_mask) + self.norm_cfg = norm_cfg + self.init_cfg = init_cfg + self.train_cfg = train_cfg + self.test_cfg = test_cfg + self._init_layers() + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.mask_convs = nn.ModuleList() + self.cls_convs = nn.ModuleList() + for i in range(self.stacked_convs): + chn = self.in_channels + 2 if i == 0 else self.feat_channels + self.mask_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + norm_cfg=self.norm_cfg)) + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + norm_cfg=self.norm_cfg)) + self.conv_mask_list = nn.ModuleList() + for num_grid in self.num_grids: + self.conv_mask_list.append( + nn.Conv2d(self.feat_channels, num_grid**2, 1)) + + self.conv_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + + def resize_feats(self, x: Tuple[Tensor]) -> List[Tensor]: + """Downsample the first feat and upsample last feat in feats. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + list[Tensor]: Features after resizing, each is a 4D-tensor. + """ + out = [] + for i in range(len(x)): + if i == 0: + out.append( + F.interpolate(x[0], scale_factor=0.5, mode='bilinear')) + elif i == len(x) - 1: + out.append( + F.interpolate( + x[i], size=x[i - 1].shape[-2:], mode='bilinear')) + else: + out.append(x[i]) + return out + + def forward(self, x: Tuple[Tensor]) -> tuple: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of classification scores and mask prediction. + + - mlvl_mask_preds (list[Tensor]): Multi-level mask prediction. + Each element in the list has shape + (batch_size, num_grids**2 ,h ,w). + - mlvl_cls_preds (list[Tensor]): Multi-level scores. + Each element in the list has shape + (batch_size, num_classes, num_grids ,num_grids). + """ + assert len(x) == self.num_levels + feats = self.resize_feats(x) + mlvl_mask_preds = [] + mlvl_cls_preds = [] + for i in range(self.num_levels): + x = feats[i] + mask_feat = x + cls_feat = x + # generate and concat the coordinate + coord_feat = generate_coordinate(mask_feat.size(), + mask_feat.device) + mask_feat = torch.cat([mask_feat, coord_feat], 1) + + for mask_layer in (self.mask_convs): + mask_feat = mask_layer(mask_feat) + + mask_feat = F.interpolate( + mask_feat, scale_factor=2, mode='bilinear') + mask_preds = self.conv_mask_list[i](mask_feat) + + # cls branch + for j, cls_layer in enumerate(self.cls_convs): + if j == self.cls_down_index: + num_grid = self.num_grids[i] + cls_feat = F.interpolate( + cls_feat, size=num_grid, mode='bilinear') + cls_feat = cls_layer(cls_feat) + + cls_pred = self.conv_cls(cls_feat) + + if not self.training: + feat_wh = feats[0].size()[-2:] + upsampled_size = (feat_wh[0] * 2, feat_wh[1] * 2) + mask_preds = F.interpolate( + mask_preds.sigmoid(), size=upsampled_size, mode='bilinear') + cls_pred = cls_pred.sigmoid() + # get local maximum + local_max = F.max_pool2d(cls_pred, 2, stride=1, padding=1) + keep_mask = local_max[:, :, :-1, :-1] == cls_pred + cls_pred = cls_pred * keep_mask + + mlvl_mask_preds.append(mask_preds) + mlvl_cls_preds.append(cls_pred) + return mlvl_mask_preds, mlvl_cls_preds + + def loss_by_feat(self, mlvl_mask_preds: List[Tensor], + mlvl_cls_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], **kwargs) -> dict: + """Calculate the loss based on the features extracted by the mask head. + + Args: + mlvl_mask_preds (list[Tensor]): Multi-level mask prediction. + Each element in the list has shape + (batch_size, num_grids**2 ,h ,w). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``masks``, + and ``labels`` attributes. + batch_img_metas (list[dict]): Meta information of multiple images. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + num_levels = self.num_levels + num_imgs = len(batch_img_metas) + + featmap_sizes = [featmap.size()[-2:] for featmap in mlvl_mask_preds] + + # `BoolTensor` in `pos_masks` represent + # whether the corresponding point is + # positive + pos_mask_targets, labels, pos_masks = multi_apply( + self._get_targets_single, + batch_gt_instances, + featmap_sizes=featmap_sizes) + + # change from the outside list meaning multi images + # to the outside list meaning multi levels + mlvl_pos_mask_targets = [[] for _ in range(num_levels)] + mlvl_pos_mask_preds = [[] for _ in range(num_levels)] + mlvl_pos_masks = [[] for _ in range(num_levels)] + mlvl_labels = [[] for _ in range(num_levels)] + for img_id in range(num_imgs): + assert num_levels == len(pos_mask_targets[img_id]) + for lvl in range(num_levels): + mlvl_pos_mask_targets[lvl].append( + pos_mask_targets[img_id][lvl]) + mlvl_pos_mask_preds[lvl].append( + mlvl_mask_preds[lvl][img_id, pos_masks[img_id][lvl], ...]) + mlvl_pos_masks[lvl].append(pos_masks[img_id][lvl].flatten()) + mlvl_labels[lvl].append(labels[img_id][lvl].flatten()) + + # cat multiple image + temp_mlvl_cls_preds = [] + for lvl in range(num_levels): + mlvl_pos_mask_targets[lvl] = torch.cat( + mlvl_pos_mask_targets[lvl], dim=0) + mlvl_pos_mask_preds[lvl] = torch.cat( + mlvl_pos_mask_preds[lvl], dim=0) + mlvl_pos_masks[lvl] = torch.cat(mlvl_pos_masks[lvl], dim=0) + mlvl_labels[lvl] = torch.cat(mlvl_labels[lvl], dim=0) + temp_mlvl_cls_preds.append(mlvl_cls_preds[lvl].permute( + 0, 2, 3, 1).reshape(-1, self.cls_out_channels)) + + num_pos = sum(item.sum() for item in mlvl_pos_masks) + # dice loss + loss_mask = [] + for pred, target in zip(mlvl_pos_mask_preds, mlvl_pos_mask_targets): + if pred.size()[0] == 0: + loss_mask.append(pred.sum().unsqueeze(0)) + continue + loss_mask.append( + self.loss_mask(pred, target, reduction_override='none')) + if num_pos > 0: + loss_mask = torch.cat(loss_mask).sum() / num_pos + else: + loss_mask = torch.cat(loss_mask).mean() + + flatten_labels = torch.cat(mlvl_labels) + flatten_cls_preds = torch.cat(temp_mlvl_cls_preds) + loss_cls = self.loss_cls( + flatten_cls_preds, flatten_labels, avg_factor=num_pos + 1) + return dict(loss_mask=loss_mask, loss_cls=loss_cls) + + def _get_targets_single(self, + gt_instances: InstanceData, + featmap_sizes: Optional[list] = None) -> tuple: + """Compute targets for predictions of single image. + + Args: + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes``, ``labels``, + and ``masks`` attributes. + featmap_sizes (list[:obj:`torch.size`]): Size of each + feature map from feature pyramid, each element + means (feat_h, feat_w). Defaults to None. + + Returns: + Tuple: Usually returns a tuple containing targets for predictions. + + - mlvl_pos_mask_targets (list[Tensor]): Each element represent + the binary mask targets for positive points in this + level, has shape (num_pos, out_h, out_w). + - mlvl_labels (list[Tensor]): Each element is + classification labels for all + points in this level, has shape + (num_grid, num_grid). + - mlvl_pos_masks (list[Tensor]): Each element is + a `BoolTensor` to represent whether the + corresponding point in single level + is positive, has shape (num_grid **2). + """ + gt_labels = gt_instances.labels + device = gt_labels.device + + gt_bboxes = gt_instances.bboxes + gt_areas = torch.sqrt((gt_bboxes[:, 2] - gt_bboxes[:, 0]) * + (gt_bboxes[:, 3] - gt_bboxes[:, 1])) + + gt_masks = gt_instances.masks.to_tensor( + dtype=torch.bool, device=device) + + mlvl_pos_mask_targets = [] + mlvl_labels = [] + mlvl_pos_masks = [] + for (lower_bound, upper_bound), stride, featmap_size, num_grid \ + in zip(self.scale_ranges, self.strides, + featmap_sizes, self.num_grids): + + mask_target = torch.zeros( + [num_grid**2, featmap_size[0], featmap_size[1]], + dtype=torch.uint8, + device=device) + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + labels = torch.zeros([num_grid, num_grid], + dtype=torch.int64, + device=device) + self.num_classes + pos_mask = torch.zeros([num_grid**2], + dtype=torch.bool, + device=device) + + gt_inds = ((gt_areas >= lower_bound) & + (gt_areas <= upper_bound)).nonzero().flatten() + if len(gt_inds) == 0: + mlvl_pos_mask_targets.append( + mask_target.new_zeros(0, featmap_size[0], featmap_size[1])) + mlvl_labels.append(labels) + mlvl_pos_masks.append(pos_mask) + continue + hit_gt_bboxes = gt_bboxes[gt_inds] + hit_gt_labels = gt_labels[gt_inds] + hit_gt_masks = gt_masks[gt_inds, ...] + + pos_w_ranges = 0.5 * (hit_gt_bboxes[:, 2] - + hit_gt_bboxes[:, 0]) * self.pos_scale + pos_h_ranges = 0.5 * (hit_gt_bboxes[:, 3] - + hit_gt_bboxes[:, 1]) * self.pos_scale + + # Make sure hit_gt_masks has a value + valid_mask_flags = hit_gt_masks.sum(dim=-1).sum(dim=-1) > 0 + output_stride = stride / 2 + + for gt_mask, gt_label, pos_h_range, pos_w_range, \ + valid_mask_flag in \ + zip(hit_gt_masks, hit_gt_labels, pos_h_ranges, + pos_w_ranges, valid_mask_flags): + if not valid_mask_flag: + continue + upsampled_size = (featmap_sizes[0][0] * 4, + featmap_sizes[0][1] * 4) + center_h, center_w = center_of_mass(gt_mask) + + coord_w = int( + floordiv((center_w / upsampled_size[1]), (1. / num_grid), + rounding_mode='trunc')) + coord_h = int( + floordiv((center_h / upsampled_size[0]), (1. / num_grid), + rounding_mode='trunc')) + + # left, top, right, down + top_box = max( + 0, + int( + floordiv( + (center_h - pos_h_range) / upsampled_size[0], + (1. / num_grid), + rounding_mode='trunc'))) + down_box = min( + num_grid - 1, + int( + floordiv( + (center_h + pos_h_range) / upsampled_size[0], + (1. / num_grid), + rounding_mode='trunc'))) + left_box = max( + 0, + int( + floordiv( + (center_w - pos_w_range) / upsampled_size[1], + (1. / num_grid), + rounding_mode='trunc'))) + right_box = min( + num_grid - 1, + int( + floordiv( + (center_w + pos_w_range) / upsampled_size[1], + (1. / num_grid), + rounding_mode='trunc'))) + + top = max(top_box, coord_h - 1) + down = min(down_box, coord_h + 1) + left = max(coord_w - 1, left_box) + right = min(right_box, coord_w + 1) + + labels[top:(down + 1), left:(right + 1)] = gt_label + # ins + gt_mask = np.uint8(gt_mask.cpu().numpy()) + # Follow the original implementation, F.interpolate is + # different from cv2 and opencv + gt_mask = mmcv.imrescale(gt_mask, scale=1. / output_stride) + gt_mask = torch.from_numpy(gt_mask).to(device=device) + + for i in range(top, down + 1): + for j in range(left, right + 1): + index = int(i * num_grid + j) + mask_target[index, :gt_mask.shape[0], :gt_mask. + shape[1]] = gt_mask + pos_mask[index] = True + mlvl_pos_mask_targets.append(mask_target[pos_mask]) + mlvl_labels.append(labels) + mlvl_pos_masks.append(pos_mask) + return mlvl_pos_mask_targets, mlvl_labels, mlvl_pos_masks + + def predict_by_feat(self, mlvl_mask_preds: List[Tensor], + mlvl_cls_scores: List[Tensor], + batch_img_metas: List[dict], **kwargs) -> InstanceList: + """Transform a batch of output features extracted from the head into + mask results. + + Args: + mlvl_mask_preds (list[Tensor]): Multi-level mask prediction. + Each element in the list has shape + (batch_size, num_grids**2 ,h ,w). + mlvl_cls_scores (list[Tensor]): Multi-level scores. Each element + in the list has shape + (batch_size, num_classes, num_grids ,num_grids). + batch_img_metas (list[dict]): Meta information of all images. + + Returns: + list[:obj:`InstanceData`]: Processed results of multiple + images.Each :obj:`InstanceData` usually contains + following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + mlvl_cls_scores = [ + item.permute(0, 2, 3, 1) for item in mlvl_cls_scores + ] + assert len(mlvl_mask_preds) == len(mlvl_cls_scores) + num_levels = len(mlvl_cls_scores) + + results_list = [] + for img_id in range(len(batch_img_metas)): + cls_pred_list = [ + mlvl_cls_scores[lvl][img_id].view(-1, self.cls_out_channels) + for lvl in range(num_levels) + ] + mask_pred_list = [ + mlvl_mask_preds[lvl][img_id] for lvl in range(num_levels) + ] + + cls_pred_list = torch.cat(cls_pred_list, dim=0) + mask_pred_list = torch.cat(mask_pred_list, dim=0) + img_meta = batch_img_metas[img_id] + + results = self._predict_by_feat_single( + cls_pred_list, mask_pred_list, img_meta=img_meta) + results_list.append(results) + + return results_list + + def _predict_by_feat_single(self, + cls_scores: Tensor, + mask_preds: Tensor, + img_meta: dict, + cfg: OptConfigType = None) -> InstanceData: + """Transform a single image's features extracted from the head into + mask results. + + Args: + cls_scores (Tensor): Classification score of all points + in single image, has shape (num_points, num_classes). + mask_preds (Tensor): Mask prediction of all points in + single image, has shape (num_points, feat_h, feat_w). + img_meta (dict): Meta information of corresponding image. + cfg (dict, optional): Config used in test phase. + Defaults to None. + + Returns: + :obj:`InstanceData`: Processed results of single image. + it usually contains following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + + def empty_results(cls_scores, ori_shape): + """Generate a empty results.""" + results = InstanceData() + results.scores = cls_scores.new_ones(0) + results.masks = cls_scores.new_zeros(0, *ori_shape) + results.labels = cls_scores.new_ones(0) + results.bboxes = cls_scores.new_zeros(0, 4) + return results + + cfg = self.test_cfg if cfg is None else cfg + assert len(cls_scores) == len(mask_preds) + + featmap_size = mask_preds.size()[-2:] + + h, w = img_meta['img_shape'][:2] + upsampled_size = (featmap_size[0] * 4, featmap_size[1] * 4) + + score_mask = (cls_scores > cfg.score_thr) + cls_scores = cls_scores[score_mask] + if len(cls_scores) == 0: + return empty_results(cls_scores, img_meta['ori_shape'][:2]) + + inds = score_mask.nonzero() + cls_labels = inds[:, 1] + + # Filter the mask mask with an area is smaller than + # stride of corresponding feature level + lvl_interval = cls_labels.new_tensor(self.num_grids).pow(2).cumsum(0) + strides = cls_scores.new_ones(lvl_interval[-1]) + strides[:lvl_interval[0]] *= self.strides[0] + for lvl in range(1, self.num_levels): + strides[lvl_interval[lvl - + 1]:lvl_interval[lvl]] *= self.strides[lvl] + strides = strides[inds[:, 0]] + mask_preds = mask_preds[inds[:, 0]] + + masks = mask_preds > cfg.mask_thr + sum_masks = masks.sum((1, 2)).float() + keep = sum_masks > strides + if keep.sum() == 0: + return empty_results(cls_scores, img_meta['ori_shape'][:2]) + masks = masks[keep] + mask_preds = mask_preds[keep] + sum_masks = sum_masks[keep] + cls_scores = cls_scores[keep] + cls_labels = cls_labels[keep] + + # maskness. + mask_scores = (mask_preds * masks).sum((1, 2)) / sum_masks + cls_scores *= mask_scores + + scores, labels, _, keep_inds = mask_matrix_nms( + masks, + cls_labels, + cls_scores, + mask_area=sum_masks, + nms_pre=cfg.nms_pre, + max_num=cfg.max_per_img, + kernel=cfg.kernel, + sigma=cfg.sigma, + filter_thr=cfg.filter_thr) + # mask_matrix_nms may return an empty Tensor + if len(keep_inds) == 0: + return empty_results(cls_scores, img_meta['ori_shape'][:2]) + mask_preds = mask_preds[keep_inds] + mask_preds = F.interpolate( + mask_preds.unsqueeze(0), size=upsampled_size, + mode='bilinear')[:, :, :h, :w] + mask_preds = F.interpolate( + mask_preds, size=img_meta['ori_shape'][:2], + mode='bilinear').squeeze(0) + masks = mask_preds > cfg.mask_thr + + results = InstanceData() + results.masks = masks + results.labels = labels + results.scores = scores + # create an empty bbox in InstanceData to avoid bugs when + # calculating metrics. + results.bboxes = results.scores.new_zeros(len(scores), 4) + return results + + +@MODELS.register_module() +class DecoupledSOLOHead(SOLOHead): + """Decoupled SOLO mask head used in `SOLO: Segmenting Objects by Locations. + + `_ + + Args: + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + *args, + init_cfg: MultiConfig = [ + dict(type='Normal', layer='Conv2d', std=0.01), + dict( + type='Normal', + std=0.01, + bias_prob=0.01, + override=dict(name='conv_mask_list_x')), + dict( + type='Normal', + std=0.01, + bias_prob=0.01, + override=dict(name='conv_mask_list_y')), + dict( + type='Normal', + std=0.01, + bias_prob=0.01, + override=dict(name='conv_cls')) + ], + **kwargs) -> None: + super().__init__(*args, init_cfg=init_cfg, **kwargs) + + def _init_layers(self) -> None: + self.mask_convs_x = nn.ModuleList() + self.mask_convs_y = nn.ModuleList() + self.cls_convs = nn.ModuleList() + + for i in range(self.stacked_convs): + chn = self.in_channels + 1 if i == 0 else self.feat_channels + self.mask_convs_x.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + norm_cfg=self.norm_cfg)) + self.mask_convs_y.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + norm_cfg=self.norm_cfg)) + + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + norm_cfg=self.norm_cfg)) + + self.conv_mask_list_x = nn.ModuleList() + self.conv_mask_list_y = nn.ModuleList() + for num_grid in self.num_grids: + self.conv_mask_list_x.append( + nn.Conv2d(self.feat_channels, num_grid, 3, padding=1)) + self.conv_mask_list_y.append( + nn.Conv2d(self.feat_channels, num_grid, 3, padding=1)) + self.conv_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + + def forward(self, x: Tuple[Tensor]) -> Tuple: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of classification scores and mask prediction. + + - mlvl_mask_preds_x (list[Tensor]): Multi-level mask prediction + from x branch. Each element in the list has shape + (batch_size, num_grids ,h ,w). + - mlvl_mask_preds_y (list[Tensor]): Multi-level mask prediction + from y branch. Each element in the list has shape + (batch_size, num_grids ,h ,w). + - mlvl_cls_preds (list[Tensor]): Multi-level scores. + Each element in the list has shape + (batch_size, num_classes, num_grids ,num_grids). + """ + assert len(x) == self.num_levels + feats = self.resize_feats(x) + mask_preds_x = [] + mask_preds_y = [] + cls_preds = [] + for i in range(self.num_levels): + x = feats[i] + mask_feat = x + cls_feat = x + # generate and concat the coordinate + coord_feat = generate_coordinate(mask_feat.size(), + mask_feat.device) + mask_feat_x = torch.cat([mask_feat, coord_feat[:, 0:1, ...]], 1) + mask_feat_y = torch.cat([mask_feat, coord_feat[:, 1:2, ...]], 1) + + for mask_layer_x, mask_layer_y in \ + zip(self.mask_convs_x, self.mask_convs_y): + mask_feat_x = mask_layer_x(mask_feat_x) + mask_feat_y = mask_layer_y(mask_feat_y) + + mask_feat_x = F.interpolate( + mask_feat_x, scale_factor=2, mode='bilinear') + mask_feat_y = F.interpolate( + mask_feat_y, scale_factor=2, mode='bilinear') + + mask_pred_x = self.conv_mask_list_x[i](mask_feat_x) + mask_pred_y = self.conv_mask_list_y[i](mask_feat_y) + + # cls branch + for j, cls_layer in enumerate(self.cls_convs): + if j == self.cls_down_index: + num_grid = self.num_grids[i] + cls_feat = F.interpolate( + cls_feat, size=num_grid, mode='bilinear') + cls_feat = cls_layer(cls_feat) + + cls_pred = self.conv_cls(cls_feat) + + if not self.training: + feat_wh = feats[0].size()[-2:] + upsampled_size = (feat_wh[0] * 2, feat_wh[1] * 2) + mask_pred_x = F.interpolate( + mask_pred_x.sigmoid(), + size=upsampled_size, + mode='bilinear') + mask_pred_y = F.interpolate( + mask_pred_y.sigmoid(), + size=upsampled_size, + mode='bilinear') + cls_pred = cls_pred.sigmoid() + # get local maximum + local_max = F.max_pool2d(cls_pred, 2, stride=1, padding=1) + keep_mask = local_max[:, :, :-1, :-1] == cls_pred + cls_pred = cls_pred * keep_mask + + mask_preds_x.append(mask_pred_x) + mask_preds_y.append(mask_pred_y) + cls_preds.append(cls_pred) + return mask_preds_x, mask_preds_y, cls_preds + + def loss_by_feat(self, mlvl_mask_preds_x: List[Tensor], + mlvl_mask_preds_y: List[Tensor], + mlvl_cls_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], **kwargs) -> dict: + """Calculate the loss based on the features extracted by the mask head. + + Args: + mlvl_mask_preds_x (list[Tensor]): Multi-level mask prediction + from x branch. Each element in the list has shape + (batch_size, num_grids ,h ,w). + mlvl_mask_preds_y (list[Tensor]): Multi-level mask prediction + from y branch. Each element in the list has shape + (batch_size, num_grids ,h ,w). + mlvl_cls_preds (list[Tensor]): Multi-level scores. Each element + in the list has shape + (batch_size, num_classes, num_grids ,num_grids). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``masks``, + and ``labels`` attributes. + batch_img_metas (list[dict]): Meta information of multiple images. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + num_levels = self.num_levels + num_imgs = len(batch_img_metas) + featmap_sizes = [featmap.size()[-2:] for featmap in mlvl_mask_preds_x] + + pos_mask_targets, labels, xy_pos_indexes = multi_apply( + self._get_targets_single, + batch_gt_instances, + featmap_sizes=featmap_sizes) + + # change from the outside list meaning multi images + # to the outside list meaning multi levels + mlvl_pos_mask_targets = [[] for _ in range(num_levels)] + mlvl_pos_mask_preds_x = [[] for _ in range(num_levels)] + mlvl_pos_mask_preds_y = [[] for _ in range(num_levels)] + mlvl_labels = [[] for _ in range(num_levels)] + for img_id in range(num_imgs): + + for lvl in range(num_levels): + mlvl_pos_mask_targets[lvl].append( + pos_mask_targets[img_id][lvl]) + mlvl_pos_mask_preds_x[lvl].append( + mlvl_mask_preds_x[lvl][img_id, + xy_pos_indexes[img_id][lvl][:, 1]]) + mlvl_pos_mask_preds_y[lvl].append( + mlvl_mask_preds_y[lvl][img_id, + xy_pos_indexes[img_id][lvl][:, 0]]) + mlvl_labels[lvl].append(labels[img_id][lvl].flatten()) + + # cat multiple image + temp_mlvl_cls_preds = [] + for lvl in range(num_levels): + mlvl_pos_mask_targets[lvl] = torch.cat( + mlvl_pos_mask_targets[lvl], dim=0) + mlvl_pos_mask_preds_x[lvl] = torch.cat( + mlvl_pos_mask_preds_x[lvl], dim=0) + mlvl_pos_mask_preds_y[lvl] = torch.cat( + mlvl_pos_mask_preds_y[lvl], dim=0) + mlvl_labels[lvl] = torch.cat(mlvl_labels[lvl], dim=0) + temp_mlvl_cls_preds.append(mlvl_cls_preds[lvl].permute( + 0, 2, 3, 1).reshape(-1, self.cls_out_channels)) + + num_pos = 0. + # dice loss + loss_mask = [] + for pred_x, pred_y, target in \ + zip(mlvl_pos_mask_preds_x, + mlvl_pos_mask_preds_y, mlvl_pos_mask_targets): + num_masks = pred_x.size(0) + if num_masks == 0: + # make sure can get grad + loss_mask.append((pred_x.sum() + pred_y.sum()).unsqueeze(0)) + continue + num_pos += num_masks + pred_mask = pred_y.sigmoid() * pred_x.sigmoid() + loss_mask.append( + self.loss_mask(pred_mask, target, reduction_override='none')) + if num_pos > 0: + loss_mask = torch.cat(loss_mask).sum() / num_pos + else: + loss_mask = torch.cat(loss_mask).mean() + + # cate + flatten_labels = torch.cat(mlvl_labels) + flatten_cls_preds = torch.cat(temp_mlvl_cls_preds) + + loss_cls = self.loss_cls( + flatten_cls_preds, flatten_labels, avg_factor=num_pos + 1) + return dict(loss_mask=loss_mask, loss_cls=loss_cls) + + def _get_targets_single(self, + gt_instances: InstanceData, + featmap_sizes: Optional[list] = None) -> tuple: + """Compute targets for predictions of single image. + + Args: + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes``, ``labels``, + and ``masks`` attributes. + featmap_sizes (list[:obj:`torch.size`]): Size of each + feature map from feature pyramid, each element + means (feat_h, feat_w). Defaults to None. + + Returns: + Tuple: Usually returns a tuple containing targets for predictions. + + - mlvl_pos_mask_targets (list[Tensor]): Each element represent + the binary mask targets for positive points in this + level, has shape (num_pos, out_h, out_w). + - mlvl_labels (list[Tensor]): Each element is + classification labels for all + points in this level, has shape + (num_grid, num_grid). + - mlvl_xy_pos_indexes (list[Tensor]): Each element + in the list contains the index of positive samples in + corresponding level, has shape (num_pos, 2), last + dimension 2 present (index_x, index_y). + """ + mlvl_pos_mask_targets, mlvl_labels, mlvl_pos_masks = \ + super()._get_targets_single(gt_instances, + featmap_sizes=featmap_sizes) + + mlvl_xy_pos_indexes = [(item - self.num_classes).nonzero() + for item in mlvl_labels] + + return mlvl_pos_mask_targets, mlvl_labels, mlvl_xy_pos_indexes + + def predict_by_feat(self, mlvl_mask_preds_x: List[Tensor], + mlvl_mask_preds_y: List[Tensor], + mlvl_cls_scores: List[Tensor], + batch_img_metas: List[dict], **kwargs) -> InstanceList: + """Transform a batch of output features extracted from the head into + mask results. + + Args: + mlvl_mask_preds_x (list[Tensor]): Multi-level mask prediction + from x branch. Each element in the list has shape + (batch_size, num_grids ,h ,w). + mlvl_mask_preds_y (list[Tensor]): Multi-level mask prediction + from y branch. Each element in the list has shape + (batch_size, num_grids ,h ,w). + mlvl_cls_scores (list[Tensor]): Multi-level scores. Each element + in the list has shape + (batch_size, num_classes ,num_grids ,num_grids). + batch_img_metas (list[dict]): Meta information of all images. + + Returns: + list[:obj:`InstanceData`]: Processed results of multiple + images.Each :obj:`InstanceData` usually contains + following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + mlvl_cls_scores = [ + item.permute(0, 2, 3, 1) for item in mlvl_cls_scores + ] + assert len(mlvl_mask_preds_x) == len(mlvl_cls_scores) + num_levels = len(mlvl_cls_scores) + + results_list = [] + for img_id in range(len(batch_img_metas)): + cls_pred_list = [ + mlvl_cls_scores[i][img_id].view( + -1, self.cls_out_channels).detach() + for i in range(num_levels) + ] + mask_pred_list_x = [ + mlvl_mask_preds_x[i][img_id] for i in range(num_levels) + ] + mask_pred_list_y = [ + mlvl_mask_preds_y[i][img_id] for i in range(num_levels) + ] + + cls_pred_list = torch.cat(cls_pred_list, dim=0) + mask_pred_list_x = torch.cat(mask_pred_list_x, dim=0) + mask_pred_list_y = torch.cat(mask_pred_list_y, dim=0) + img_meta = batch_img_metas[img_id] + + results = self._predict_by_feat_single( + cls_pred_list, + mask_pred_list_x, + mask_pred_list_y, + img_meta=img_meta) + results_list.append(results) + return results_list + + def _predict_by_feat_single(self, + cls_scores: Tensor, + mask_preds_x: Tensor, + mask_preds_y: Tensor, + img_meta: dict, + cfg: OptConfigType = None) -> InstanceData: + """Transform a single image's features extracted from the head into + mask results. + + Args: + cls_scores (Tensor): Classification score of all points + in single image, has shape (num_points, num_classes). + mask_preds_x (Tensor): Mask prediction of x branch of + all points in single image, has shape + (sum_num_grids, feat_h, feat_w). + mask_preds_y (Tensor): Mask prediction of y branch of + all points in single image, has shape + (sum_num_grids, feat_h, feat_w). + img_meta (dict): Meta information of corresponding image. + cfg (dict): Config used in test phase. + + Returns: + :obj:`InstanceData`: Processed results of single image. + it usually contains following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + + def empty_results(cls_scores, ori_shape): + """Generate a empty results.""" + results = InstanceData() + results.scores = cls_scores.new_ones(0) + results.masks = cls_scores.new_zeros(0, *ori_shape) + results.labels = cls_scores.new_ones(0) + results.bboxes = cls_scores.new_zeros(0, 4) + return results + + cfg = self.test_cfg if cfg is None else cfg + + featmap_size = mask_preds_x.size()[-2:] + + h, w = img_meta['img_shape'][:2] + upsampled_size = (featmap_size[0] * 4, featmap_size[1] * 4) + + score_mask = (cls_scores > cfg.score_thr) + cls_scores = cls_scores[score_mask] + inds = score_mask.nonzero() + lvl_interval = inds.new_tensor(self.num_grids).pow(2).cumsum(0) + num_all_points = lvl_interval[-1] + lvl_start_index = inds.new_ones(num_all_points) + num_grids = inds.new_ones(num_all_points) + seg_size = inds.new_tensor(self.num_grids).cumsum(0) + mask_lvl_start_index = inds.new_ones(num_all_points) + strides = inds.new_ones(num_all_points) + + lvl_start_index[:lvl_interval[0]] *= 0 + mask_lvl_start_index[:lvl_interval[0]] *= 0 + num_grids[:lvl_interval[0]] *= self.num_grids[0] + strides[:lvl_interval[0]] *= self.strides[0] + + for lvl in range(1, self.num_levels): + lvl_start_index[lvl_interval[lvl - 1]:lvl_interval[lvl]] *= \ + lvl_interval[lvl - 1] + mask_lvl_start_index[lvl_interval[lvl - 1]:lvl_interval[lvl]] *= \ + seg_size[lvl - 1] + num_grids[lvl_interval[lvl - 1]:lvl_interval[lvl]] *= \ + self.num_grids[lvl] + strides[lvl_interval[lvl - 1]:lvl_interval[lvl]] *= \ + self.strides[lvl] + + lvl_start_index = lvl_start_index[inds[:, 0]] + mask_lvl_start_index = mask_lvl_start_index[inds[:, 0]] + num_grids = num_grids[inds[:, 0]] + strides = strides[inds[:, 0]] + + y_lvl_offset = (inds[:, 0] - lvl_start_index) // num_grids + x_lvl_offset = (inds[:, 0] - lvl_start_index) % num_grids + y_inds = mask_lvl_start_index + y_lvl_offset + x_inds = mask_lvl_start_index + x_lvl_offset + + cls_labels = inds[:, 1] + mask_preds = mask_preds_x[x_inds, ...] * mask_preds_y[y_inds, ...] + + masks = mask_preds > cfg.mask_thr + sum_masks = masks.sum((1, 2)).float() + keep = sum_masks > strides + if keep.sum() == 0: + return empty_results(cls_scores, img_meta['ori_shape'][:2]) + + masks = masks[keep] + mask_preds = mask_preds[keep] + sum_masks = sum_masks[keep] + cls_scores = cls_scores[keep] + cls_labels = cls_labels[keep] + + # maskness. + mask_scores = (mask_preds * masks).sum((1, 2)) / sum_masks + cls_scores *= mask_scores + + scores, labels, _, keep_inds = mask_matrix_nms( + masks, + cls_labels, + cls_scores, + mask_area=sum_masks, + nms_pre=cfg.nms_pre, + max_num=cfg.max_per_img, + kernel=cfg.kernel, + sigma=cfg.sigma, + filter_thr=cfg.filter_thr) + # mask_matrix_nms may return an empty Tensor + if len(keep_inds) == 0: + return empty_results(cls_scores, img_meta['ori_shape'][:2]) + mask_preds = mask_preds[keep_inds] + mask_preds = F.interpolate( + mask_preds.unsqueeze(0), size=upsampled_size, + mode='bilinear')[:, :, :h, :w] + mask_preds = F.interpolate( + mask_preds, size=img_meta['ori_shape'][:2], + mode='bilinear').squeeze(0) + masks = mask_preds > cfg.mask_thr + + results = InstanceData() + results.masks = masks + results.labels = labels + results.scores = scores + # create an empty bbox in InstanceData to avoid bugs when + # calculating metrics. + results.bboxes = results.scores.new_zeros(len(scores), 4) + + return results + + +@MODELS.register_module() +class DecoupledSOLOLightHead(DecoupledSOLOHead): + """Decoupled Light SOLO mask head used in `SOLO: Segmenting Objects by + Locations `_ + + Args: + with_dcn (bool): Whether use dcn in mask_convs and cls_convs, + Defaults to False. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + *args, + dcn_cfg: OptConfigType = None, + init_cfg: MultiConfig = [ + dict(type='Normal', layer='Conv2d', std=0.01), + dict( + type='Normal', + std=0.01, + bias_prob=0.01, + override=dict(name='conv_mask_list_x')), + dict( + type='Normal', + std=0.01, + bias_prob=0.01, + override=dict(name='conv_mask_list_y')), + dict( + type='Normal', + std=0.01, + bias_prob=0.01, + override=dict(name='conv_cls')) + ], + **kwargs) -> None: + assert dcn_cfg is None or isinstance(dcn_cfg, dict) + self.dcn_cfg = dcn_cfg + super().__init__(*args, init_cfg=init_cfg, **kwargs) + + def _init_layers(self) -> None: + self.mask_convs = nn.ModuleList() + self.cls_convs = nn.ModuleList() + + for i in range(self.stacked_convs): + if self.dcn_cfg is not None \ + and i == self.stacked_convs - 1: + conv_cfg = self.dcn_cfg + else: + conv_cfg = None + + chn = self.in_channels + 2 if i == 0 else self.feat_channels + self.mask_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=self.norm_cfg)) + + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=self.norm_cfg)) + + self.conv_mask_list_x = nn.ModuleList() + self.conv_mask_list_y = nn.ModuleList() + for num_grid in self.num_grids: + self.conv_mask_list_x.append( + nn.Conv2d(self.feat_channels, num_grid, 3, padding=1)) + self.conv_mask_list_y.append( + nn.Conv2d(self.feat_channels, num_grid, 3, padding=1)) + self.conv_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + + def forward(self, x: Tuple[Tensor]) -> Tuple: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of classification scores and mask prediction. + + - mlvl_mask_preds_x (list[Tensor]): Multi-level mask prediction + from x branch. Each element in the list has shape + (batch_size, num_grids ,h ,w). + - mlvl_mask_preds_y (list[Tensor]): Multi-level mask prediction + from y branch. Each element in the list has shape + (batch_size, num_grids ,h ,w). + - mlvl_cls_preds (list[Tensor]): Multi-level scores. + Each element in the list has shape + (batch_size, num_classes, num_grids ,num_grids). + """ + assert len(x) == self.num_levels + feats = self.resize_feats(x) + mask_preds_x = [] + mask_preds_y = [] + cls_preds = [] + for i in range(self.num_levels): + x = feats[i] + mask_feat = x + cls_feat = x + # generate and concat the coordinate + coord_feat = generate_coordinate(mask_feat.size(), + mask_feat.device) + mask_feat = torch.cat([mask_feat, coord_feat], 1) + + for mask_layer in self.mask_convs: + mask_feat = mask_layer(mask_feat) + + mask_feat = F.interpolate( + mask_feat, scale_factor=2, mode='bilinear') + + mask_pred_x = self.conv_mask_list_x[i](mask_feat) + mask_pred_y = self.conv_mask_list_y[i](mask_feat) + + # cls branch + for j, cls_layer in enumerate(self.cls_convs): + if j == self.cls_down_index: + num_grid = self.num_grids[i] + cls_feat = F.interpolate( + cls_feat, size=num_grid, mode='bilinear') + cls_feat = cls_layer(cls_feat) + + cls_pred = self.conv_cls(cls_feat) + + if not self.training: + feat_wh = feats[0].size()[-2:] + upsampled_size = (feat_wh[0] * 2, feat_wh[1] * 2) + mask_pred_x = F.interpolate( + mask_pred_x.sigmoid(), + size=upsampled_size, + mode='bilinear') + mask_pred_y = F.interpolate( + mask_pred_y.sigmoid(), + size=upsampled_size, + mode='bilinear') + cls_pred = cls_pred.sigmoid() + # get local maximum + local_max = F.max_pool2d(cls_pred, 2, stride=1, padding=1) + keep_mask = local_max[:, :, :-1, :-1] == cls_pred + cls_pred = cls_pred * keep_mask + + mask_preds_x.append(mask_pred_x) + mask_preds_y.append(mask_pred_y) + cls_preds.append(cls_pred) + return mask_preds_x, mask_preds_y, cls_preds diff --git a/mmdetection/mmdet/models/dense_heads/solov2_head.py b/mmdetection/mmdet/models/dense_heads/solov2_head.py new file mode 100644 index 00000000..35b9df0c --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/solov2_head.py @@ -0,0 +1,799 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import List, Optional, Tuple + +import mmcv +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.utils.misc import floordiv +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, InstanceList, MultiConfig, OptConfigType +from ..layers import mask_matrix_nms +from ..utils import center_of_mass, generate_coordinate, multi_apply +from .solo_head import SOLOHead + + +class MaskFeatModule(BaseModule): + """SOLOv2 mask feature map branch used in `SOLOv2: Dynamic and Fast + Instance Segmentation. `_ + + Args: + in_channels (int): Number of channels in the input feature map. + feat_channels (int): Number of hidden channels of the mask feature + map branch. + start_level (int): The starting feature map level from RPN that + will be used to predict the mask feature map. + end_level (int): The ending feature map level from rpn that + will be used to predict the mask feature map. + out_channels (int): Number of output channels of the mask feature + map branch. This is the channel count of the mask + feature map that to be dynamically convolved with the predicted + kernel. + mask_stride (int): Downsample factor of the mask feature map output. + Defaults to 4. + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Config dict for normalization layer. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__( + self, + in_channels: int, + feat_channels: int, + start_level: int, + end_level: int, + out_channels: int, + mask_stride: int = 4, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + init_cfg: MultiConfig = [ + dict(type='Normal', layer='Conv2d', std=0.01) + ] + ) -> None: + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.feat_channels = feat_channels + self.start_level = start_level + self.end_level = end_level + self.mask_stride = mask_stride + assert start_level >= 0 and end_level >= start_level + self.out_channels = out_channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self._init_layers() + self.fp16_enabled = False + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.convs_all_levels = nn.ModuleList() + for i in range(self.start_level, self.end_level + 1): + convs_per_level = nn.Sequential() + if i == 0: + convs_per_level.add_module( + f'conv{i}', + ConvModule( + self.in_channels, + self.feat_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + inplace=False)) + self.convs_all_levels.append(convs_per_level) + continue + + for j in range(i): + if j == 0: + if i == self.end_level: + chn = self.in_channels + 2 + else: + chn = self.in_channels + convs_per_level.add_module( + f'conv{j}', + ConvModule( + chn, + self.feat_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + inplace=False)) + convs_per_level.add_module( + f'upsample{j}', + nn.Upsample( + scale_factor=2, + mode='bilinear', + align_corners=False)) + continue + + convs_per_level.add_module( + f'conv{j}', + ConvModule( + self.feat_channels, + self.feat_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + inplace=False)) + convs_per_level.add_module( + f'upsample{j}', + nn.Upsample( + scale_factor=2, mode='bilinear', align_corners=False)) + + self.convs_all_levels.append(convs_per_level) + + self.conv_pred = ConvModule( + self.feat_channels, + self.out_channels, + 1, + padding=0, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg) + + def forward(self, x: Tuple[Tensor]) -> Tensor: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + Tensor: The predicted mask feature map. + """ + inputs = x[self.start_level:self.end_level + 1] + assert len(inputs) == (self.end_level - self.start_level + 1) + feature_add_all_level = self.convs_all_levels[0](inputs[0]) + for i in range(1, len(inputs)): + input_p = inputs[i] + if i == len(inputs) - 1: + coord_feat = generate_coordinate(input_p.size(), + input_p.device) + input_p = torch.cat([input_p, coord_feat], 1) + + feature_add_all_level = feature_add_all_level + \ + self.convs_all_levels[i](input_p) + + feature_pred = self.conv_pred(feature_add_all_level) + return feature_pred + + +@MODELS.register_module() +class SOLOV2Head(SOLOHead): + """SOLOv2 mask head used in `SOLOv2: Dynamic and Fast Instance + Segmentation. `_ + + Args: + mask_feature_head (dict): Config of SOLOv2MaskFeatHead. + dynamic_conv_size (int): Dynamic Conv kernel size. Defaults to 1. + dcn_cfg (dict): Dcn conv configurations in kernel_convs and cls_conv. + Defaults to None. + dcn_apply_to_all_conv (bool): Whether to use dcn in every layer of + kernel_convs and cls_convs, or only the last layer. It shall be set + `True` for the normal version of SOLOv2 and `False` for the + light-weight version. Defaults to True. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + *args, + mask_feature_head: ConfigType, + dynamic_conv_size: int = 1, + dcn_cfg: OptConfigType = None, + dcn_apply_to_all_conv: bool = True, + init_cfg: MultiConfig = [ + dict(type='Normal', layer='Conv2d', std=0.01), + dict( + type='Normal', + std=0.01, + bias_prob=0.01, + override=dict(name='conv_cls')) + ], + **kwargs) -> None: + assert dcn_cfg is None or isinstance(dcn_cfg, dict) + self.dcn_cfg = dcn_cfg + self.with_dcn = dcn_cfg is not None + self.dcn_apply_to_all_conv = dcn_apply_to_all_conv + self.dynamic_conv_size = dynamic_conv_size + mask_out_channels = mask_feature_head.get('out_channels') + self.kernel_out_channels = \ + mask_out_channels * self.dynamic_conv_size * self.dynamic_conv_size + + super().__init__(*args, init_cfg=init_cfg, **kwargs) + + # update the in_channels of mask_feature_head + if mask_feature_head.get('in_channels', None) is not None: + if mask_feature_head.in_channels != self.in_channels: + warnings.warn('The `in_channels` of SOLOv2MaskFeatHead and ' + 'SOLOv2Head should be same, changing ' + 'mask_feature_head.in_channels to ' + f'{self.in_channels}') + mask_feature_head.update(in_channels=self.in_channels) + else: + mask_feature_head.update(in_channels=self.in_channels) + + self.mask_feature_head = MaskFeatModule(**mask_feature_head) + self.mask_stride = self.mask_feature_head.mask_stride + self.fp16_enabled = False + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.cls_convs = nn.ModuleList() + self.kernel_convs = nn.ModuleList() + conv_cfg = None + for i in range(self.stacked_convs): + if self.with_dcn: + if self.dcn_apply_to_all_conv: + conv_cfg = self.dcn_cfg + elif i == self.stacked_convs - 1: + # light head + conv_cfg = self.dcn_cfg + + chn = self.in_channels + 2 if i == 0 else self.feat_channels + self.kernel_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=self.norm_cfg, + bias=self.norm_cfg is None)) + + chn = self.in_channels if i == 0 else self.feat_channels + self.cls_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=self.norm_cfg, + bias=self.norm_cfg is None)) + + self.conv_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + + self.conv_kernel = nn.Conv2d( + self.feat_channels, self.kernel_out_channels, 3, padding=1) + + def forward(self, x): + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of classification scores, mask prediction, + and mask features. + + - mlvl_kernel_preds (list[Tensor]): Multi-level dynamic kernel + prediction. The kernel is used to generate instance + segmentation masks by dynamic convolution. Each element in + the list has shape + (batch_size, kernel_out_channels, num_grids, num_grids). + - mlvl_cls_preds (list[Tensor]): Multi-level scores. Each + element in the list has shape + (batch_size, num_classes, num_grids, num_grids). + - mask_feats (Tensor): Unified mask feature map used to + generate instance segmentation masks by dynamic convolution. + Has shape (batch_size, mask_out_channels, h, w). + """ + assert len(x) == self.num_levels + mask_feats = self.mask_feature_head(x) + ins_kernel_feats = self.resize_feats(x) + mlvl_kernel_preds = [] + mlvl_cls_preds = [] + for i in range(self.num_levels): + ins_kernel_feat = ins_kernel_feats[i] + # ins branch + # concat coord + coord_feat = generate_coordinate(ins_kernel_feat.size(), + ins_kernel_feat.device) + ins_kernel_feat = torch.cat([ins_kernel_feat, coord_feat], 1) + + # kernel branch + kernel_feat = ins_kernel_feat + kernel_feat = F.interpolate( + kernel_feat, + size=self.num_grids[i], + mode='bilinear', + align_corners=False) + + cate_feat = kernel_feat[:, :-2, :, :] + + kernel_feat = kernel_feat.contiguous() + for i, kernel_conv in enumerate(self.kernel_convs): + kernel_feat = kernel_conv(kernel_feat) + kernel_pred = self.conv_kernel(kernel_feat) + + # cate branch + cate_feat = cate_feat.contiguous() + for i, cls_conv in enumerate(self.cls_convs): + cate_feat = cls_conv(cate_feat) + cate_pred = self.conv_cls(cate_feat) + + mlvl_kernel_preds.append(kernel_pred) + mlvl_cls_preds.append(cate_pred) + + return mlvl_kernel_preds, mlvl_cls_preds, mask_feats + + def _get_targets_single(self, + gt_instances: InstanceData, + featmap_sizes: Optional[list] = None) -> tuple: + """Compute targets for predictions of single image. + + Args: + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes``, ``labels``, + and ``masks`` attributes. + featmap_sizes (list[:obj:`torch.size`]): Size of each + feature map from feature pyramid, each element + means (feat_h, feat_w). Defaults to None. + + Returns: + Tuple: Usually returns a tuple containing targets for predictions. + + - mlvl_pos_mask_targets (list[Tensor]): Each element represent + the binary mask targets for positive points in this + level, has shape (num_pos, out_h, out_w). + - mlvl_labels (list[Tensor]): Each element is + classification labels for all + points in this level, has shape + (num_grid, num_grid). + - mlvl_pos_masks (list[Tensor]): Each element is + a `BoolTensor` to represent whether the + corresponding point in single level + is positive, has shape (num_grid **2). + - mlvl_pos_indexes (list[list]): Each element + in the list contains the positive index in + corresponding level, has shape (num_pos). + """ + gt_labels = gt_instances.labels + device = gt_labels.device + + gt_bboxes = gt_instances.bboxes + gt_areas = torch.sqrt((gt_bboxes[:, 2] - gt_bboxes[:, 0]) * + (gt_bboxes[:, 3] - gt_bboxes[:, 1])) + gt_masks = gt_instances.masks.to_tensor( + dtype=torch.bool, device=device) + + mlvl_pos_mask_targets = [] + mlvl_pos_indexes = [] + mlvl_labels = [] + mlvl_pos_masks = [] + for (lower_bound, upper_bound), num_grid \ + in zip(self.scale_ranges, self.num_grids): + mask_target = [] + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + pos_index = [] + labels = torch.zeros([num_grid, num_grid], + dtype=torch.int64, + device=device) + self.num_classes + pos_mask = torch.zeros([num_grid**2], + dtype=torch.bool, + device=device) + + gt_inds = ((gt_areas >= lower_bound) & + (gt_areas <= upper_bound)).nonzero().flatten() + if len(gt_inds) == 0: + mlvl_pos_mask_targets.append( + torch.zeros([0, featmap_sizes[0], featmap_sizes[1]], + dtype=torch.uint8, + device=device)) + mlvl_labels.append(labels) + mlvl_pos_masks.append(pos_mask) + mlvl_pos_indexes.append([]) + continue + hit_gt_bboxes = gt_bboxes[gt_inds] + hit_gt_labels = gt_labels[gt_inds] + hit_gt_masks = gt_masks[gt_inds, ...] + + pos_w_ranges = 0.5 * (hit_gt_bboxes[:, 2] - + hit_gt_bboxes[:, 0]) * self.pos_scale + pos_h_ranges = 0.5 * (hit_gt_bboxes[:, 3] - + hit_gt_bboxes[:, 1]) * self.pos_scale + + # Make sure hit_gt_masks has a value + valid_mask_flags = hit_gt_masks.sum(dim=-1).sum(dim=-1) > 0 + + for gt_mask, gt_label, pos_h_range, pos_w_range, \ + valid_mask_flag in \ + zip(hit_gt_masks, hit_gt_labels, pos_h_ranges, + pos_w_ranges, valid_mask_flags): + if not valid_mask_flag: + continue + upsampled_size = (featmap_sizes[0] * self.mask_stride, + featmap_sizes[1] * self.mask_stride) + center_h, center_w = center_of_mass(gt_mask) + + coord_w = int( + floordiv((center_w / upsampled_size[1]), (1. / num_grid), + rounding_mode='trunc')) + coord_h = int( + floordiv((center_h / upsampled_size[0]), (1. / num_grid), + rounding_mode='trunc')) + + # left, top, right, down + top_box = max( + 0, + int( + floordiv( + (center_h - pos_h_range) / upsampled_size[0], + (1. / num_grid), + rounding_mode='trunc'))) + down_box = min( + num_grid - 1, + int( + floordiv( + (center_h + pos_h_range) / upsampled_size[0], + (1. / num_grid), + rounding_mode='trunc'))) + left_box = max( + 0, + int( + floordiv( + (center_w - pos_w_range) / upsampled_size[1], + (1. / num_grid), + rounding_mode='trunc'))) + right_box = min( + num_grid - 1, + int( + floordiv( + (center_w + pos_w_range) / upsampled_size[1], + (1. / num_grid), + rounding_mode='trunc'))) + + top = max(top_box, coord_h - 1) + down = min(down_box, coord_h + 1) + left = max(coord_w - 1, left_box) + right = min(right_box, coord_w + 1) + + labels[top:(down + 1), left:(right + 1)] = gt_label + # ins + gt_mask = np.uint8(gt_mask.cpu().numpy()) + # Follow the original implementation, F.interpolate is + # different from cv2 and opencv + gt_mask = mmcv.imrescale(gt_mask, scale=1. / self.mask_stride) + gt_mask = torch.from_numpy(gt_mask).to(device=device) + + for i in range(top, down + 1): + for j in range(left, right + 1): + index = int(i * num_grid + j) + this_mask_target = torch.zeros( + [featmap_sizes[0], featmap_sizes[1]], + dtype=torch.uint8, + device=device) + this_mask_target[:gt_mask.shape[0], :gt_mask. + shape[1]] = gt_mask + mask_target.append(this_mask_target) + pos_mask[index] = True + pos_index.append(index) + if len(mask_target) == 0: + mask_target = torch.zeros( + [0, featmap_sizes[0], featmap_sizes[1]], + dtype=torch.uint8, + device=device) + else: + mask_target = torch.stack(mask_target, 0) + mlvl_pos_mask_targets.append(mask_target) + mlvl_labels.append(labels) + mlvl_pos_masks.append(pos_mask) + mlvl_pos_indexes.append(pos_index) + return (mlvl_pos_mask_targets, mlvl_labels, mlvl_pos_masks, + mlvl_pos_indexes) + + def loss_by_feat(self, mlvl_kernel_preds: List[Tensor], + mlvl_cls_preds: List[Tensor], mask_feats: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], **kwargs) -> dict: + """Calculate the loss based on the features extracted by the mask head. + + Args: + mlvl_kernel_preds (list[Tensor]): Multi-level dynamic kernel + prediction. The kernel is used to generate instance + segmentation masks by dynamic convolution. Each element in the + list has shape + (batch_size, kernel_out_channels, num_grids, num_grids). + mlvl_cls_preds (list[Tensor]): Multi-level scores. Each element + in the list has shape + (batch_size, num_classes, num_grids, num_grids). + mask_feats (Tensor): Unified mask feature map used to generate + instance segmentation masks by dynamic convolution. Has shape + (batch_size, mask_out_channels, h, w). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``masks``, + and ``labels`` attributes. + batch_img_metas (list[dict]): Meta information of multiple images. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + featmap_sizes = mask_feats.size()[-2:] + + pos_mask_targets, labels, pos_masks, pos_indexes = multi_apply( + self._get_targets_single, + batch_gt_instances, + featmap_sizes=featmap_sizes) + + mlvl_mask_targets = [ + torch.cat(lvl_mask_targets, 0) + for lvl_mask_targets in zip(*pos_mask_targets) + ] + + mlvl_pos_kernel_preds = [] + for lvl_kernel_preds, lvl_pos_indexes in zip(mlvl_kernel_preds, + zip(*pos_indexes)): + lvl_pos_kernel_preds = [] + for img_lvl_kernel_preds, img_lvl_pos_indexes in zip( + lvl_kernel_preds, lvl_pos_indexes): + img_lvl_pos_kernel_preds = img_lvl_kernel_preds.view( + img_lvl_kernel_preds.shape[0], -1)[:, img_lvl_pos_indexes] + lvl_pos_kernel_preds.append(img_lvl_pos_kernel_preds) + mlvl_pos_kernel_preds.append(lvl_pos_kernel_preds) + + # make multilevel mlvl_mask_pred + mlvl_mask_preds = [] + for lvl_pos_kernel_preds in mlvl_pos_kernel_preds: + lvl_mask_preds = [] + for img_id, img_lvl_pos_kernel_pred in enumerate( + lvl_pos_kernel_preds): + if img_lvl_pos_kernel_pred.size()[-1] == 0: + continue + img_mask_feats = mask_feats[[img_id]] + h, w = img_mask_feats.shape[-2:] + num_kernel = img_lvl_pos_kernel_pred.shape[1] + img_lvl_mask_pred = F.conv2d( + img_mask_feats, + img_lvl_pos_kernel_pred.permute(1, 0).view( + num_kernel, -1, self.dynamic_conv_size, + self.dynamic_conv_size), + stride=1).view(-1, h, w) + lvl_mask_preds.append(img_lvl_mask_pred) + if len(lvl_mask_preds) == 0: + lvl_mask_preds = None + else: + lvl_mask_preds = torch.cat(lvl_mask_preds, 0) + mlvl_mask_preds.append(lvl_mask_preds) + # dice loss + num_pos = 0 + for img_pos_masks in pos_masks: + for lvl_img_pos_masks in img_pos_masks: + # Fix `Tensor` object has no attribute `count_nonzero()` + # in PyTorch 1.6, the type of `lvl_img_pos_masks` + # should be `torch.bool`. + num_pos += lvl_img_pos_masks.nonzero().numel() + loss_mask = [] + for lvl_mask_preds, lvl_mask_targets in zip(mlvl_mask_preds, + mlvl_mask_targets): + if lvl_mask_preds is None: + continue + loss_mask.append( + self.loss_mask( + lvl_mask_preds, + lvl_mask_targets, + reduction_override='none')) + if num_pos > 0: + loss_mask = torch.cat(loss_mask).sum() / num_pos + else: + loss_mask = mask_feats.sum() * 0 + + # cate + flatten_labels = [ + torch.cat( + [img_lvl_labels.flatten() for img_lvl_labels in lvl_labels]) + for lvl_labels in zip(*labels) + ] + flatten_labels = torch.cat(flatten_labels) + + flatten_cls_preds = [ + lvl_cls_preds.permute(0, 2, 3, 1).reshape(-1, self.num_classes) + for lvl_cls_preds in mlvl_cls_preds + ] + flatten_cls_preds = torch.cat(flatten_cls_preds) + + loss_cls = self.loss_cls( + flatten_cls_preds, flatten_labels, avg_factor=num_pos + 1) + return dict(loss_mask=loss_mask, loss_cls=loss_cls) + + def predict_by_feat(self, mlvl_kernel_preds: List[Tensor], + mlvl_cls_scores: List[Tensor], mask_feats: Tensor, + batch_img_metas: List[dict], **kwargs) -> InstanceList: + """Transform a batch of output features extracted from the head into + mask results. + + Args: + mlvl_kernel_preds (list[Tensor]): Multi-level dynamic kernel + prediction. The kernel is used to generate instance + segmentation masks by dynamic convolution. Each element in the + list has shape + (batch_size, kernel_out_channels, num_grids, num_grids). + mlvl_cls_scores (list[Tensor]): Multi-level scores. Each element + in the list has shape + (batch_size, num_classes, num_grids, num_grids). + mask_feats (Tensor): Unified mask feature map used to generate + instance segmentation masks by dynamic convolution. Has shape + (batch_size, mask_out_channels, h, w). + batch_img_metas (list[dict]): Meta information of all images. + + Returns: + list[:obj:`InstanceData`]: Processed results of multiple + images.Each :obj:`InstanceData` usually contains + following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + num_levels = len(mlvl_cls_scores) + assert len(mlvl_kernel_preds) == len(mlvl_cls_scores) + + for lvl in range(num_levels): + cls_scores = mlvl_cls_scores[lvl] + cls_scores = cls_scores.sigmoid() + local_max = F.max_pool2d(cls_scores, 2, stride=1, padding=1) + keep_mask = local_max[:, :, :-1, :-1] == cls_scores + cls_scores = cls_scores * keep_mask + mlvl_cls_scores[lvl] = cls_scores.permute(0, 2, 3, 1) + + result_list = [] + for img_id in range(len(batch_img_metas)): + img_cls_pred = [ + mlvl_cls_scores[lvl][img_id].view(-1, self.cls_out_channels) + for lvl in range(num_levels) + ] + img_mask_feats = mask_feats[[img_id]] + img_kernel_pred = [ + mlvl_kernel_preds[lvl][img_id].permute(1, 2, 0).view( + -1, self.kernel_out_channels) for lvl in range(num_levels) + ] + img_cls_pred = torch.cat(img_cls_pred, dim=0) + img_kernel_pred = torch.cat(img_kernel_pred, dim=0) + result = self._predict_by_feat_single( + img_kernel_pred, + img_cls_pred, + img_mask_feats, + img_meta=batch_img_metas[img_id]) + result_list.append(result) + return result_list + + def _predict_by_feat_single(self, + kernel_preds: Tensor, + cls_scores: Tensor, + mask_feats: Tensor, + img_meta: dict, + cfg: OptConfigType = None) -> InstanceData: + """Transform a single image's features extracted from the head into + mask results. + + Args: + kernel_preds (Tensor): Dynamic kernel prediction of all points + in single image, has shape + (num_points, kernel_out_channels). + cls_scores (Tensor): Classification score of all points + in single image, has shape (num_points, num_classes). + mask_feats (Tensor): Mask prediction of all points in + single image, has shape (num_points, feat_h, feat_w). + img_meta (dict): Meta information of corresponding image. + cfg (dict, optional): Config used in test phase. + Defaults to None. + + Returns: + :obj:`InstanceData`: Processed results of single image. + it usually contains following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + + def empty_results(cls_scores, ori_shape): + """Generate a empty results.""" + results = InstanceData() + results.scores = cls_scores.new_ones(0) + results.masks = cls_scores.new_zeros(0, *ori_shape) + results.labels = cls_scores.new_ones(0) + results.bboxes = cls_scores.new_zeros(0, 4) + return results + + cfg = self.test_cfg if cfg is None else cfg + assert len(kernel_preds) == len(cls_scores) + + featmap_size = mask_feats.size()[-2:] + + # overall info + h, w = img_meta['img_shape'][:2] + upsampled_size = (featmap_size[0] * self.mask_stride, + featmap_size[1] * self.mask_stride) + + # process. + score_mask = (cls_scores > cfg.score_thr) + cls_scores = cls_scores[score_mask] + if len(cls_scores) == 0: + return empty_results(cls_scores, img_meta['ori_shape'][:2]) + + # cate_labels & kernel_preds + inds = score_mask.nonzero() + cls_labels = inds[:, 1] + kernel_preds = kernel_preds[inds[:, 0]] + + # trans vector. + lvl_interval = cls_labels.new_tensor(self.num_grids).pow(2).cumsum(0) + strides = kernel_preds.new_ones(lvl_interval[-1]) + + strides[:lvl_interval[0]] *= self.strides[0] + for lvl in range(1, self.num_levels): + strides[lvl_interval[lvl - + 1]:lvl_interval[lvl]] *= self.strides[lvl] + strides = strides[inds[:, 0]] + + # mask encoding. + kernel_preds = kernel_preds.view( + kernel_preds.size(0), -1, self.dynamic_conv_size, + self.dynamic_conv_size) + mask_preds = F.conv2d( + mask_feats, kernel_preds, stride=1).squeeze(0).sigmoid() + # mask. + masks = mask_preds > cfg.mask_thr + sum_masks = masks.sum((1, 2)).float() + keep = sum_masks > strides + if keep.sum() == 0: + return empty_results(cls_scores, img_meta['ori_shape'][:2]) + masks = masks[keep] + mask_preds = mask_preds[keep] + sum_masks = sum_masks[keep] + cls_scores = cls_scores[keep] + cls_labels = cls_labels[keep] + + # maskness. + mask_scores = (mask_preds * masks).sum((1, 2)) / sum_masks + cls_scores *= mask_scores + + scores, labels, _, keep_inds = mask_matrix_nms( + masks, + cls_labels, + cls_scores, + mask_area=sum_masks, + nms_pre=cfg.nms_pre, + max_num=cfg.max_per_img, + kernel=cfg.kernel, + sigma=cfg.sigma, + filter_thr=cfg.filter_thr) + if len(keep_inds) == 0: + return empty_results(cls_scores, img_meta['ori_shape'][:2]) + mask_preds = mask_preds[keep_inds] + mask_preds = F.interpolate( + mask_preds.unsqueeze(0), + size=upsampled_size, + mode='bilinear', + align_corners=False)[:, :, :h, :w] + mask_preds = F.interpolate( + mask_preds, + size=img_meta['ori_shape'][:2], + mode='bilinear', + align_corners=False).squeeze(0) + masks = mask_preds > cfg.mask_thr + + results = InstanceData() + results.masks = masks + results.labels = labels + results.scores = scores + # create an empty bbox in InstanceData to avoid bugs when + # calculating metrics. + results.bboxes = results.scores.new_zeros(len(scores), 4) + + return results diff --git a/mmdetection/mmdet/models/dense_heads/ssd_head.py b/mmdetection/mmdet/models/dense_heads/ssd_head.py new file mode 100644 index 00000000..950df291 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/ssd_head.py @@ -0,0 +1,362 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Sequence, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.utils import ConfigType, InstanceList, MultiConfig, OptInstanceList +from ..losses import smooth_l1_loss +from ..task_modules.samplers import PseudoSampler +from ..utils import multi_apply +from .anchor_head import AnchorHead + + +# TODO: add loss evaluator for SSD +@MODELS.register_module() +class SSDHead(AnchorHead): + """Implementation of `SSD head `_ + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (Sequence[int]): Number of channels in the input feature + map. + stacked_convs (int): Number of conv layers in cls and reg tower. + Defaults to 0. + feat_channels (int): Number of hidden channels when stacked_convs + > 0. Defaults to 256. + use_depthwise (bool): Whether to use DepthwiseSeparableConv. + Defaults to False. + conv_cfg (:obj:`ConfigDict` or dict, Optional): Dictionary to construct + and config conv layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict, Optional): Dictionary to construct + and config norm layer. Defaults to None. + act_cfg (:obj:`ConfigDict` or dict, Optional): Dictionary to construct + and config activation layer. Defaults to None. + anchor_generator (:obj:`ConfigDict` or dict): Config dict for anchor + generator. + bbox_coder (:obj:`ConfigDict` or dict): Config of bounding box coder. + reg_decoded_bbox (bool): If true, the regression loss would be + applied directly on decoded bounding boxes, converting both + the predicted boxes and regression targets to absolute + coordinates format. Defaults to False. It should be `True` when + using `IoULoss`, `GIoULoss`, or `DIoULoss` in the bbox head. + train_cfg (:obj:`ConfigDict` or dict, Optional): Training config of + anchor head. + test_cfg (:obj:`ConfigDict` or dict, Optional): Testing config of + anchor head. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], Optional): Initialization config dict. + """ # noqa: W605 + + def __init__( + self, + num_classes: int = 80, + in_channels: Sequence[int] = (512, 1024, 512, 256, 256, 256), + stacked_convs: int = 0, + feat_channels: int = 256, + use_depthwise: bool = False, + conv_cfg: Optional[ConfigType] = None, + norm_cfg: Optional[ConfigType] = None, + act_cfg: Optional[ConfigType] = None, + anchor_generator: ConfigType = dict( + type='SSDAnchorGenerator', + scale_major=False, + input_size=300, + strides=[8, 16, 32, 64, 100, 300], + ratios=([2], [2, 3], [2, 3], [2, 3], [2], [2]), + basesize_ratio_range=(0.1, 0.9)), + bbox_coder: ConfigType = dict( + type='DeltaXYWHBBoxCoder', + clip_border=True, + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0], + ), + reg_decoded_bbox: bool = False, + train_cfg: Optional[ConfigType] = None, + test_cfg: Optional[ConfigType] = None, + init_cfg: MultiConfig = dict( + type='Xavier', layer='Conv2d', distribution='uniform', bias=0) + ) -> None: + super(AnchorHead, self).__init__(init_cfg=init_cfg) + self.num_classes = num_classes + self.in_channels = in_channels + self.stacked_convs = stacked_convs + self.feat_channels = feat_channels + self.use_depthwise = use_depthwise + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + self.cls_out_channels = num_classes + 1 # add background class + self.prior_generator = TASK_UTILS.build(anchor_generator) + + # Usually the numbers of anchors for each level are the same + # except SSD detectors. So it is an int in the most dense + # heads but a list of int in SSDHead + self.num_base_priors = self.prior_generator.num_base_priors + + self._init_layers() + + self.bbox_coder = TASK_UTILS.build(bbox_coder) + self.reg_decoded_bbox = reg_decoded_bbox + self.use_sigmoid_cls = False + self.cls_focal_loss = False + self.train_cfg = train_cfg + self.test_cfg = test_cfg + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + if self.train_cfg.get('sampler', None) is not None: + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], default_args=dict(context=self)) + else: + self.sampler = PseudoSampler(context=self) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.cls_convs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + # TODO: Use registry to choose ConvModule type + conv = DepthwiseSeparableConvModule \ + if self.use_depthwise else ConvModule + + for channel, num_base_priors in zip(self.in_channels, + self.num_base_priors): + cls_layers = [] + reg_layers = [] + in_channel = channel + # build stacked conv tower, not used in default ssd + for i in range(self.stacked_convs): + cls_layers.append( + conv( + in_channel, + self.feat_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + reg_layers.append( + conv( + in_channel, + self.feat_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + in_channel = self.feat_channels + # SSD-Lite head + if self.use_depthwise: + cls_layers.append( + ConvModule( + in_channel, + in_channel, + 3, + padding=1, + groups=in_channel, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + reg_layers.append( + ConvModule( + in_channel, + in_channel, + 3, + padding=1, + groups=in_channel, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + cls_layers.append( + nn.Conv2d( + in_channel, + num_base_priors * self.cls_out_channels, + kernel_size=1 if self.use_depthwise else 3, + padding=0 if self.use_depthwise else 1)) + reg_layers.append( + nn.Conv2d( + in_channel, + num_base_priors * 4, + kernel_size=1 if self.use_depthwise else 3, + padding=0 if self.use_depthwise else 1)) + self.cls_convs.append(nn.Sequential(*cls_layers)) + self.reg_convs.append(nn.Sequential(*reg_layers)) + + def forward(self, x: Tuple[Tensor]) -> Tuple[List[Tensor], List[Tensor]]: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple[list[Tensor], list[Tensor]]: A tuple of cls_scores list and + bbox_preds list. + + - cls_scores (list[Tensor]): Classification scores for all scale \ + levels, each is a 4D-tensor, the channels number is \ + num_anchors * num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for all scale \ + levels, each is a 4D-tensor, the channels number is \ + num_anchors * 4. + """ + cls_scores = [] + bbox_preds = [] + for feat, reg_conv, cls_conv in zip(x, self.reg_convs, self.cls_convs): + cls_scores.append(cls_conv(feat)) + bbox_preds.append(reg_conv(feat)) + return cls_scores, bbox_preds + + def loss_by_feat_single(self, cls_score: Tensor, bbox_pred: Tensor, + anchor: Tensor, labels: Tensor, + label_weights: Tensor, bbox_targets: Tensor, + bbox_weights: Tensor, + avg_factor: int) -> Tuple[Tensor, Tensor]: + """Compute loss of a single image. + + Args: + cls_score (Tensor): Box scores for eachimage + Has shape (num_total_anchors, num_classes). + bbox_pred (Tensor): Box energies / deltas for each image + level with shape (num_total_anchors, 4). + anchors (Tensor): Box reference for each scale level with shape + (num_total_anchors, 4). + labels (Tensor): Labels of each anchors with shape + (num_total_anchors,). + label_weights (Tensor): Label weights of each anchor with shape + (num_total_anchors,) + bbox_targets (Tensor): BBox regression targets of each anchor with + shape (num_total_anchors, 4). + bbox_weights (Tensor): BBox regression loss weights of each anchor + with shape (num_total_anchors, 4). + avg_factor (int): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + `PseudoSampler`, `avg_factor` is usually equal to the number + of positive priors. + + Returns: + Tuple[Tensor, Tensor]: A tuple of cls loss and bbox loss of one + feature map. + """ + + loss_cls_all = F.cross_entropy( + cls_score, labels, reduction='none') * label_weights + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + pos_inds = ((labels >= 0) & (labels < self.num_classes)).nonzero( + as_tuple=False).reshape(-1) + neg_inds = (labels == self.num_classes).nonzero( + as_tuple=False).view(-1) + + num_pos_samples = pos_inds.size(0) + num_neg_samples = self.train_cfg['neg_pos_ratio'] * num_pos_samples + if num_neg_samples > neg_inds.size(0): + num_neg_samples = neg_inds.size(0) + topk_loss_cls_neg, _ = loss_cls_all[neg_inds].topk(num_neg_samples) + loss_cls_pos = loss_cls_all[pos_inds].sum() + loss_cls_neg = topk_loss_cls_neg.sum() + loss_cls = (loss_cls_pos + loss_cls_neg) / avg_factor + + if self.reg_decoded_bbox: + # When the regression loss (e.g. `IouLoss`, `GIouLoss`) + # is applied directly on the decoded bounding boxes, it + # decodes the already encoded coordinates to absolute format. + bbox_pred = self.bbox_coder.decode(anchor, bbox_pred) + + loss_bbox = smooth_l1_loss( + bbox_pred, + bbox_targets, + bbox_weights, + beta=self.train_cfg['smoothl1_beta'], + avg_factor=avg_factor) + return loss_cls[None], loss_bbox + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, List[Tensor]]: + """Compute losses of the head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, list[Tensor]]: A dictionary of loss components. the dict + has components below: + + - loss_cls (list[Tensor]): A list containing each feature map \ + classification loss. + - loss_bbox (list[Tensor]): A list containing each feature map \ + regression loss. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + unmap_outputs=True) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + avg_factor) = cls_reg_targets + + num_images = len(batch_img_metas) + all_cls_scores = torch.cat([ + s.permute(0, 2, 3, 1).reshape( + num_images, -1, self.cls_out_channels) for s in cls_scores + ], 1) + all_labels = torch.cat(labels_list, -1).view(num_images, -1) + all_label_weights = torch.cat(label_weights_list, + -1).view(num_images, -1) + all_bbox_preds = torch.cat([ + b.permute(0, 2, 3, 1).reshape(num_images, -1, 4) + for b in bbox_preds + ], -2) + all_bbox_targets = torch.cat(bbox_targets_list, + -2).view(num_images, -1, 4) + all_bbox_weights = torch.cat(bbox_weights_list, + -2).view(num_images, -1, 4) + + # concat all level anchors to a single tensor + all_anchors = [] + for i in range(num_images): + all_anchors.append(torch.cat(anchor_list[i])) + + losses_cls, losses_bbox = multi_apply( + self.loss_by_feat_single, + all_cls_scores, + all_bbox_preds, + all_anchors, + all_labels, + all_label_weights, + all_bbox_targets, + all_bbox_weights, + avg_factor=avg_factor) + return dict(loss_cls=losses_cls, loss_bbox=losses_bbox) diff --git a/mmdetection/mmdet/models/dense_heads/tood_head.py b/mmdetection/mmdet/models/dense_heads/tood_head.py new file mode 100644 index 00000000..8c59598d --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/tood_head.py @@ -0,0 +1,805 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, Scale +from mmcv.ops import deform_conv2d +from mmengine import MessageHub +from mmengine.config import ConfigDict +from mmengine.model import bias_init_with_prob, normal_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures.bbox import distance2bbox +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList, reduce_mean) +from ..task_modules.prior_generators import anchor_inside_flags +from ..utils import (filter_scores_and_topk, images_to_levels, multi_apply, + sigmoid_geometric_mean, unmap) +from .atss_head import ATSSHead + + +class TaskDecomposition(nn.Module): + """Task decomposition module in task-aligned predictor of TOOD. + + Args: + feat_channels (int): Number of feature channels in TOOD head. + stacked_convs (int): Number of conv layers in TOOD head. + la_down_rate (int): Downsample rate of layer attention. + Defaults to 8. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + normalization layer. Defaults to None. + """ + + def __init__(self, + feat_channels: int, + stacked_convs: int, + la_down_rate: int = 8, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None) -> None: + super().__init__() + self.feat_channels = feat_channels + self.stacked_convs = stacked_convs + self.in_channels = self.feat_channels * self.stacked_convs + self.norm_cfg = norm_cfg + self.layer_attention = nn.Sequential( + nn.Conv2d(self.in_channels, self.in_channels // la_down_rate, 1), + nn.ReLU(inplace=True), + nn.Conv2d( + self.in_channels // la_down_rate, + self.stacked_convs, + 1, + padding=0), nn.Sigmoid()) + + self.reduction_conv = ConvModule( + self.in_channels, + self.feat_channels, + 1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + bias=norm_cfg is None) + + def init_weights(self) -> None: + """Initialize the parameters.""" + for m in self.layer_attention.modules(): + if isinstance(m, nn.Conv2d): + normal_init(m, std=0.001) + normal_init(self.reduction_conv.conv, std=0.01) + + def forward(self, + feat: Tensor, + avg_feat: Optional[Tensor] = None) -> Tensor: + """Forward function of task decomposition module.""" + b, c, h, w = feat.shape + if avg_feat is None: + avg_feat = F.adaptive_avg_pool2d(feat, (1, 1)) + weight = self.layer_attention(avg_feat) + + # here we first compute the product between layer attention weight and + # conv weight, and then compute the convolution between new conv weight + # and feature map, in order to save memory and FLOPs. + conv_weight = weight.reshape( + b, 1, self.stacked_convs, + 1) * self.reduction_conv.conv.weight.reshape( + 1, self.feat_channels, self.stacked_convs, self.feat_channels) + conv_weight = conv_weight.reshape(b, self.feat_channels, + self.in_channels) + feat = feat.reshape(b, self.in_channels, h * w) + feat = torch.bmm(conv_weight, feat).reshape(b, self.feat_channels, h, + w) + if self.norm_cfg is not None: + feat = self.reduction_conv.norm(feat) + feat = self.reduction_conv.activate(feat) + + return feat + + +@MODELS.register_module() +class TOODHead(ATSSHead): + """TOODHead used in `TOOD: Task-aligned One-stage Object Detection. + + `_. + + TOOD uses Task-aligned head (T-head) and is optimized by Task Alignment + Learning (TAL). + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + num_dcn (int): Number of deformable convolution in the head. + Defaults to 0. + anchor_type (str): If set to ``anchor_free``, the head will use centers + to regress bboxes. If set to ``anchor_based``, the head will + regress bboxes based on anchors. Defaults to ``anchor_free``. + initial_loss_cls (:obj:`ConfigDict` or dict): Config of initial loss. + + Example: + >>> self = TOODHead(11, 7) + >>> feats = [torch.rand(1, 7, s, s) for s in [4, 8, 16, 32, 64]] + >>> cls_score, bbox_pred = self.forward(feats) + >>> assert len(cls_score) == len(self.scales) + """ + + def __init__(self, + num_classes: int, + in_channels: int, + num_dcn: int = 0, + anchor_type: str = 'anchor_free', + initial_loss_cls: ConfigType = dict( + type='FocalLoss', + use_sigmoid=True, + activated=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + **kwargs) -> None: + assert anchor_type in ['anchor_free', 'anchor_based'] + self.num_dcn = num_dcn + self.anchor_type = anchor_type + super().__init__( + num_classes=num_classes, in_channels=in_channels, **kwargs) + + if self.train_cfg: + self.initial_epoch = self.train_cfg['initial_epoch'] + self.initial_assigner = TASK_UTILS.build( + self.train_cfg['initial_assigner']) + self.initial_loss_cls = MODELS.build(initial_loss_cls) + self.assigner = self.initial_assigner + self.alignment_assigner = TASK_UTILS.build( + self.train_cfg['assigner']) + self.alpha = self.train_cfg['alpha'] + self.beta = self.train_cfg['beta'] + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.relu = nn.ReLU(inplace=True) + self.inter_convs = nn.ModuleList() + for i in range(self.stacked_convs): + if i < self.num_dcn: + conv_cfg = dict(type='DCNv2', deform_groups=4) + else: + conv_cfg = self.conv_cfg + chn = self.in_channels if i == 0 else self.feat_channels + self.inter_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=self.norm_cfg)) + + self.cls_decomp = TaskDecomposition(self.feat_channels, + self.stacked_convs, + self.stacked_convs * 8, + self.conv_cfg, self.norm_cfg) + self.reg_decomp = TaskDecomposition(self.feat_channels, + self.stacked_convs, + self.stacked_convs * 8, + self.conv_cfg, self.norm_cfg) + + self.tood_cls = nn.Conv2d( + self.feat_channels, + self.num_base_priors * self.cls_out_channels, + 3, + padding=1) + self.tood_reg = nn.Conv2d( + self.feat_channels, self.num_base_priors * 4, 3, padding=1) + + self.cls_prob_module = nn.Sequential( + nn.Conv2d(self.feat_channels * self.stacked_convs, + self.feat_channels // 4, 1), nn.ReLU(inplace=True), + nn.Conv2d(self.feat_channels // 4, 1, 3, padding=1)) + self.reg_offset_module = nn.Sequential( + nn.Conv2d(self.feat_channels * self.stacked_convs, + self.feat_channels // 4, 1), nn.ReLU(inplace=True), + nn.Conv2d(self.feat_channels // 4, 4 * 2, 3, padding=1)) + + self.scales = nn.ModuleList( + [Scale(1.0) for _ in self.prior_generator.strides]) + + def init_weights(self) -> None: + """Initialize weights of the head.""" + bias_cls = bias_init_with_prob(0.01) + for m in self.inter_convs: + normal_init(m.conv, std=0.01) + for m in self.cls_prob_module: + if isinstance(m, nn.Conv2d): + normal_init(m, std=0.01) + for m in self.reg_offset_module: + if isinstance(m, nn.Conv2d): + normal_init(m, std=0.001) + normal_init(self.cls_prob_module[-1], std=0.01, bias=bias_cls) + + self.cls_decomp.init_weights() + self.reg_decomp.init_weights() + + normal_init(self.tood_cls, std=0.01, bias=bias_cls) + normal_init(self.tood_reg, std=0.01) + + def forward(self, feats: Tuple[Tensor]) -> Tuple[List[Tensor]]: + """Forward features from the upstream network. + + Args: + feats (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: Usually a tuple of classification scores and bbox prediction + cls_scores (list[Tensor]): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_anchors * num_classes. + bbox_preds (list[Tensor]): Decoded box for all scale levels, + each is a 4D-tensor, the channels number is + num_anchors * 4. In [tl_x, tl_y, br_x, br_y] format. + """ + cls_scores = [] + bbox_preds = [] + for idx, (x, scale, stride) in enumerate( + zip(feats, self.scales, self.prior_generator.strides)): + b, c, h, w = x.shape + anchor = self.prior_generator.single_level_grid_priors( + (h, w), idx, device=x.device) + anchor = torch.cat([anchor for _ in range(b)]) + # extract task interactive features + inter_feats = [] + for inter_conv in self.inter_convs: + x = inter_conv(x) + inter_feats.append(x) + feat = torch.cat(inter_feats, 1) + + # task decomposition + avg_feat = F.adaptive_avg_pool2d(feat, (1, 1)) + cls_feat = self.cls_decomp(feat, avg_feat) + reg_feat = self.reg_decomp(feat, avg_feat) + + # cls prediction and alignment + cls_logits = self.tood_cls(cls_feat) + cls_prob = self.cls_prob_module(feat) + cls_score = sigmoid_geometric_mean(cls_logits, cls_prob) + + # reg prediction and alignment + if self.anchor_type == 'anchor_free': + reg_dist = scale(self.tood_reg(reg_feat).exp()).float() + reg_dist = reg_dist.permute(0, 2, 3, 1).reshape(-1, 4) + reg_bbox = distance2bbox( + self.anchor_center(anchor) / stride[0], + reg_dist).reshape(b, h, w, 4).permute(0, 3, 1, + 2) # (b, c, h, w) + elif self.anchor_type == 'anchor_based': + reg_dist = scale(self.tood_reg(reg_feat)).float() + reg_dist = reg_dist.permute(0, 2, 3, 1).reshape(-1, 4) + reg_bbox = self.bbox_coder.decode(anchor, reg_dist).reshape( + b, h, w, 4).permute(0, 3, 1, 2) / stride[0] + else: + raise NotImplementedError( + f'Unknown anchor type: {self.anchor_type}.' + f'Please use `anchor_free` or `anchor_based`.') + reg_offset = self.reg_offset_module(feat) + bbox_pred = self.deform_sampling(reg_bbox.contiguous(), + reg_offset.contiguous()) + + # After deform_sampling, some boxes will become invalid (The + # left-top point is at the right or bottom of the right-bottom + # point), which will make the GIoULoss negative. + invalid_bbox_idx = (bbox_pred[:, [0]] > bbox_pred[:, [2]]) | \ + (bbox_pred[:, [1]] > bbox_pred[:, [3]]) + invalid_bbox_idx = invalid_bbox_idx.expand_as(bbox_pred) + bbox_pred = torch.where(invalid_bbox_idx, reg_bbox, bbox_pred) + + cls_scores.append(cls_score) + bbox_preds.append(bbox_pred) + return tuple(cls_scores), tuple(bbox_preds) + + def deform_sampling(self, feat: Tensor, offset: Tensor) -> Tensor: + """Sampling the feature x according to offset. + + Args: + feat (Tensor): Feature + offset (Tensor): Spatial offset for feature sampling + """ + # it is an equivalent implementation of bilinear interpolation + b, c, h, w = feat.shape + weight = feat.new_ones(c, 1, 1, 1) + y = deform_conv2d(feat, offset, weight, 1, 0, 1, c, c) + return y + + def anchor_center(self, anchors: Tensor) -> Tensor: + """Get anchor centers from anchors. + + Args: + anchors (Tensor): Anchor list with shape (N, 4), "xyxy" format. + + Returns: + Tensor: Anchor centers with shape (N, 2), "xy" format. + """ + anchors_cx = (anchors[:, 2] + anchors[:, 0]) / 2 + anchors_cy = (anchors[:, 3] + anchors[:, 1]) / 2 + return torch.stack([anchors_cx, anchors_cy], dim=-1) + + def loss_by_feat_single(self, anchors: Tensor, cls_score: Tensor, + bbox_pred: Tensor, labels: Tensor, + label_weights: Tensor, bbox_targets: Tensor, + alignment_metrics: Tensor, + stride: Tuple[int, int]) -> dict: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + anchors (Tensor): Box reference for each scale level with shape + (N, num_total_anchors, 4). + cls_score (Tensor): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W). + bbox_pred (Tensor): Decoded bboxes for each scale + level with shape (N, num_anchors * 4, H, W). + labels (Tensor): Labels of each anchors with shape + (N, num_total_anchors). + label_weights (Tensor): Label weights of each anchor with shape + (N, num_total_anchors). + bbox_targets (Tensor): BBox regression targets of each anchor with + shape (N, num_total_anchors, 4). + alignment_metrics (Tensor): Alignment metrics with shape + (N, num_total_anchors). + stride (Tuple[int, int]): Downsample stride of the feature map. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert stride[0] == stride[1], 'h stride is not equal to w stride!' + anchors = anchors.reshape(-1, 4) + cls_score = cls_score.permute(0, 2, 3, 1).reshape( + -1, self.cls_out_channels).contiguous() + bbox_pred = bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4) + bbox_targets = bbox_targets.reshape(-1, 4) + labels = labels.reshape(-1) + alignment_metrics = alignment_metrics.reshape(-1) + label_weights = label_weights.reshape(-1) + targets = labels if self.epoch < self.initial_epoch else ( + labels, alignment_metrics) + cls_loss_func = self.initial_loss_cls \ + if self.epoch < self.initial_epoch else self.loss_cls + + loss_cls = cls_loss_func( + cls_score, targets, label_weights, avg_factor=1.0) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().squeeze(1) + + if len(pos_inds) > 0: + pos_bbox_targets = bbox_targets[pos_inds] + pos_bbox_pred = bbox_pred[pos_inds] + pos_anchors = anchors[pos_inds] + + pos_decode_bbox_pred = pos_bbox_pred + pos_decode_bbox_targets = pos_bbox_targets / stride[0] + + # regression loss + pos_bbox_weight = self.centerness_target( + pos_anchors, pos_bbox_targets + ) if self.epoch < self.initial_epoch else alignment_metrics[ + pos_inds] + + loss_bbox = self.loss_bbox( + pos_decode_bbox_pred, + pos_decode_bbox_targets, + weight=pos_bbox_weight, + avg_factor=1.0) + else: + loss_bbox = bbox_pred.sum() * 0 + pos_bbox_weight = bbox_targets.new_tensor(0.) + + return loss_cls, loss_bbox, alignment_metrics.sum( + ), pos_bbox_weight.sum() + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Decoded box for each scale + level with shape (N, num_anchors * 4, H, W) in + [tl_x, tl_y, br_x, br_y] format. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + num_imgs = len(batch_img_metas) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + flatten_cls_scores = torch.cat([ + cls_score.permute(0, 2, 3, 1).reshape(num_imgs, -1, + self.cls_out_channels) + for cls_score in cls_scores + ], 1) + flatten_bbox_preds = torch.cat([ + bbox_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, 4) * stride[0] + for bbox_pred, stride in zip(bbox_preds, + self.prior_generator.strides) + ], 1) + + cls_reg_targets = self.get_targets( + flatten_cls_scores, + flatten_bbox_preds, + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + (anchor_list, labels_list, label_weights_list, bbox_targets_list, + alignment_metrics_list) = cls_reg_targets + + losses_cls, losses_bbox, \ + cls_avg_factors, bbox_avg_factors = multi_apply( + self.loss_by_feat_single, + anchor_list, + cls_scores, + bbox_preds, + labels_list, + label_weights_list, + bbox_targets_list, + alignment_metrics_list, + self.prior_generator.strides) + + cls_avg_factor = reduce_mean(sum(cls_avg_factors)).clamp_(min=1).item() + losses_cls = list(map(lambda x: x / cls_avg_factor, losses_cls)) + + bbox_avg_factor = reduce_mean( + sum(bbox_avg_factors)).clamp_(min=1).item() + losses_bbox = list(map(lambda x: x / bbox_avg_factor, losses_bbox)) + return dict(loss_cls=losses_cls, loss_bbox=losses_bbox) + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: Optional[ConfigDict] = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image, each item has shape + (num_priors * 1, H, W). + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid. In all + anchor-based methods, it has shape (num_priors, 4). In + all anchor-free methods, it has shape (num_priors, 2) + when `with_stride=True`, otherwise it still has shape + (num_priors, 4). + img_meta (dict): Image meta info. + cfg (:obj:`ConfigDict`, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + tuple[Tensor]: Results of detected bboxes and labels. If with_nms + is False and mlvl_score_factor is None, return mlvl_bboxes and + mlvl_scores, else return mlvl_bboxes, mlvl_scores and + mlvl_score_factor. Usually with_nms is False is used for aug + test. If with_nms is True, then return the following format + + - det_bboxes (Tensor): Predicted bboxes with shape \ + [num_bboxes, 5], where the first 4 columns are bounding \ + box positions (tl_x, tl_y, br_x, br_y) and the 5-th \ + column are scores between 0 and 1. + - det_labels (Tensor): Predicted labels of the corresponding \ + box with shape [num_bboxes]. + """ + + cfg = self.test_cfg if cfg is None else cfg + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bboxes = [] + mlvl_scores = [] + mlvl_labels = [] + for cls_score, bbox_pred, priors, stride in zip( + cls_score_list, bbox_pred_list, mlvl_priors, + self.prior_generator.strides): + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4) * stride[0] + scores = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + + # After https://github.com/open-mmlab/mmdetection/pull/6268/, + # this operation keeps fewer bboxes under the same `nms_pre`. + # There is no difference in performance for most models. If you + # find a slight drop in performance, you can set a larger + # `nms_pre` than before. + results = filter_scores_and_topk( + scores, cfg.score_thr, nms_pre, + dict(bbox_pred=bbox_pred, priors=priors)) + scores, labels, keep_idxs, filtered_results = results + + bboxes = filtered_results['bbox_pred'] + + mlvl_bboxes.append(bboxes) + mlvl_scores.append(scores) + mlvl_labels.append(labels) + + results = InstanceData() + results.bboxes = torch.cat(mlvl_bboxes) + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + + return self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + + def get_targets(self, + cls_scores: List[List[Tensor]], + bbox_preds: List[List[Tensor]], + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression and classification targets for anchors in + multiple images. + + Args: + cls_scores (list[list[Tensor]]): Classification predictions of + images, a 3D-Tensor with shape [num_imgs, num_priors, + num_classes]. + bbox_preds (list[list[Tensor]]): Decoded bboxes predictions of one + image, a 3D-Tensor with shape [num_imgs, num_priors, 4] in + [tl_x, tl_y, br_x, br_y] format. + anchor_list (list[list[Tensor]]): Multi level anchors of each + image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_anchors, 4). + valid_flag_list (list[list[Tensor]]): Multi level valid flags of + each image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_anchors, ) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. + + Returns: + tuple: a tuple containing learning targets. + + - anchors_list (list[list[Tensor]]): Anchors of each level. + - labels_list (list[Tensor]): Labels of each level. + - label_weights_list (list[Tensor]): Label weights of each + level. + - bbox_targets_list (list[Tensor]): BBox targets of each level. + - norm_alignment_metrics_list (list[Tensor]): Normalized + alignment metrics of each level. + """ + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + num_level_anchors_list = [num_level_anchors] * num_imgs + + # concat all level anchors and flags to a single tensor + for i in range(num_imgs): + assert len(anchor_list[i]) == len(valid_flag_list[i]) + anchor_list[i] = torch.cat(anchor_list[i]) + valid_flag_list[i] = torch.cat(valid_flag_list[i]) + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + # anchor_list: list(b * [-1, 4]) + + # get epoch information from message hub + message_hub = MessageHub.get_current_instance() + self.epoch = message_hub.get_info('epoch') + + if self.epoch < self.initial_epoch: + (all_anchors, all_labels, all_label_weights, all_bbox_targets, + all_bbox_weights, pos_inds_list, neg_inds_list, + sampling_result) = multi_apply( + super()._get_targets_single, + anchor_list, + valid_flag_list, + num_level_anchors_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + all_assign_metrics = [ + weight[..., 0] for weight in all_bbox_weights + ] + else: + (all_anchors, all_labels, all_label_weights, all_bbox_targets, + all_assign_metrics) = multi_apply( + self._get_targets_single, + cls_scores, + bbox_preds, + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + + # split targets to a list w.r.t. multiple levels + anchors_list = images_to_levels(all_anchors, num_level_anchors) + labels_list = images_to_levels(all_labels, num_level_anchors) + label_weights_list = images_to_levels(all_label_weights, + num_level_anchors) + bbox_targets_list = images_to_levels(all_bbox_targets, + num_level_anchors) + norm_alignment_metrics_list = images_to_levels(all_assign_metrics, + num_level_anchors) + + return (anchors_list, labels_list, label_weights_list, + bbox_targets_list, norm_alignment_metrics_list) + + def _get_targets_single(self, + cls_scores: Tensor, + bbox_preds: Tensor, + flat_anchors: Tensor, + valid_flags: Tensor, + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression, classification targets for anchors in a single + image. + + Args: + cls_scores (Tensor): Box scores for each image. + bbox_preds (Tensor): Box energies / deltas for each image. + flat_anchors (Tensor): Multi-level anchors of the image, which are + concatenated into a single tensor of shape (num_anchors ,4) + valid_flags (Tensor): Multi level valid flags of the image, + which are concatenated into a single tensor of + shape (num_anchors,). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. + + Returns: + tuple: N is the number of total anchors in the image. + anchors (Tensor): All anchors in the image with shape (N, 4). + labels (Tensor): Labels of all anchors in the image with shape + (N,). + label_weights (Tensor): Label weights of all anchor in the + image with shape (N,). + bbox_targets (Tensor): BBox targets of all anchors in the + image with shape (N, 4). + norm_alignment_metrics (Tensor): Normalized alignment metrics + of all priors in the image with shape (N,). + """ + inside_flags = anchor_inside_flags(flat_anchors, valid_flags, + img_meta['img_shape'][:2], + self.train_cfg['allowed_border']) + if not inside_flags.any(): + raise ValueError( + 'There is no valid anchor inside the image boundary. Please ' + 'check the image size and anchor sizes, or set ' + '``allowed_border`` to -1 to skip the condition.') + # assign gt and sample anchors + anchors = flat_anchors[inside_flags, :] + pred_instances = InstanceData( + priors=anchors, + scores=cls_scores[inside_flags, :], + bboxes=bbox_preds[inside_flags, :]) + assign_result = self.alignment_assigner.assign(pred_instances, + gt_instances, + gt_instances_ignore, + self.alpha, self.beta) + assign_ious = assign_result.max_overlaps + assign_metrics = assign_result.assign_metrics + + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_valid_anchors = anchors.shape[0] + bbox_targets = torch.zeros_like(anchors) + labels = anchors.new_full((num_valid_anchors, ), + self.num_classes, + dtype=torch.long) + label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float) + norm_alignment_metrics = anchors.new_zeros( + num_valid_anchors, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + # point-based + pos_bbox_targets = sampling_result.pos_gt_bboxes + bbox_targets[pos_inds, :] = pos_bbox_targets + + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + class_assigned_gt_inds = torch.unique( + sampling_result.pos_assigned_gt_inds) + for gt_inds in class_assigned_gt_inds: + gt_class_inds = pos_inds[sampling_result.pos_assigned_gt_inds == + gt_inds] + pos_alignment_metrics = assign_metrics[gt_class_inds] + pos_ious = assign_ious[gt_class_inds] + pos_norm_alignment_metrics = pos_alignment_metrics / ( + pos_alignment_metrics.max() + 10e-8) * pos_ious.max() + norm_alignment_metrics[gt_class_inds] = pos_norm_alignment_metrics + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_anchors.size(0) + anchors = unmap(anchors, num_total_anchors, inside_flags) + labels = unmap( + labels, num_total_anchors, inside_flags, fill=self.num_classes) + label_weights = unmap(label_weights, num_total_anchors, + inside_flags) + bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags) + norm_alignment_metrics = unmap(norm_alignment_metrics, + num_total_anchors, inside_flags) + return (anchors, labels, label_weights, bbox_targets, + norm_alignment_metrics) diff --git a/mmdetection/mmdet/models/dense_heads/vfnet_head.py b/mmdetection/mmdet/models/dense_heads/vfnet_head.py new file mode 100644 index 00000000..430b06d0 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/vfnet_head.py @@ -0,0 +1,722 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple, Union + +import numpy as np +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, Scale +from mmcv.ops import DeformConv2d +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures.bbox import bbox_overlaps +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, + OptInstanceList, RangeType, reduce_mean) +from ..task_modules.prior_generators import MlvlPointGenerator +from ..task_modules.samplers import PseudoSampler +from ..utils import multi_apply +from .atss_head import ATSSHead +from .fcos_head import FCOSHead + +INF = 1e8 + + +@MODELS.register_module() +class VFNetHead(ATSSHead, FCOSHead): + """Head of `VarifocalNet (VFNet): An IoU-aware Dense Object + Detector.`_. + + The VFNet predicts IoU-aware classification scores which mix the + object presence confidence and object localization accuracy as the + detection score. It is built on the FCOS architecture and uses ATSS + for defining positive/negative training examples. The VFNet is trained + with Varifocal Loss and empolys star-shaped deformable convolution to + extract features for a bbox. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + regress_ranges (Sequence[Tuple[int, int]]): Regress range of multiple + level points. + center_sampling (bool): If true, use center sampling. Defaults to False. + center_sample_radius (float): Radius of center sampling. Defaults to 1.5. + sync_num_pos (bool): If true, synchronize the number of positive + examples across GPUs. Defaults to True + gradient_mul (float): The multiplier to gradients from bbox refinement + and recognition. Defaults to 0.1. + bbox_norm_type (str): The bbox normalization type, 'reg_denom' or + 'stride'. Defaults to reg_denom + loss_cls_fl (:obj:`ConfigDict` or dict): Config of focal loss. + use_vfl (bool): If true, use varifocal loss for training. + Defaults to True. + loss_cls (:obj:`ConfigDict` or dict): Config of varifocal loss. + loss_bbox (:obj:`ConfigDict` or dict): Config of localization loss, + GIoU Loss. + loss_bbox (:obj:`ConfigDict` or dict): Config of localization + refinement loss, GIoU Loss. + norm_cfg (:obj:`ConfigDict` or dict): dictionary to construct and + config norm layer. Defaults to norm_cfg=dict(type='GN', + num_groups=32, requires_grad=True). + use_atss (bool): If true, use ATSS to define positive/negative + examples. Defaults to True. + anchor_generator (:obj:`ConfigDict` or dict): Config of anchor + generator for ATSS. + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`]): Initialization config dict. + + Example: + >>> self = VFNetHead(11, 7) + >>> feats = [torch.rand(1, 7, s, s) for s in [4, 8, 16, 32, 64]] + >>> cls_score, bbox_pred, bbox_pred_refine= self.forward(feats) + >>> assert len(cls_score) == len(self.scales) + """ # noqa: E501 + + def __init__(self, + num_classes: int, + in_channels: int, + regress_ranges: RangeType = ((-1, 64), (64, 128), (128, 256), + (256, 512), (512, INF)), + center_sampling: bool = False, + center_sample_radius: float = 1.5, + sync_num_pos: bool = True, + gradient_mul: float = 0.1, + bbox_norm_type: str = 'reg_denom', + loss_cls_fl: ConfigType = dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), + use_vfl: bool = True, + loss_cls: ConfigType = dict( + type='VarifocalLoss', + use_sigmoid=True, + alpha=0.75, + gamma=2.0, + iou_weighted=True, + loss_weight=1.0), + loss_bbox: ConfigType = dict( + type='GIoULoss', loss_weight=1.5), + loss_bbox_refine: ConfigType = dict( + type='GIoULoss', loss_weight=2.0), + norm_cfg: ConfigType = dict( + type='GN', num_groups=32, requires_grad=True), + use_atss: bool = True, + reg_decoded_bbox: bool = True, + anchor_generator: ConfigType = dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + center_offset=0.0, + strides=[8, 16, 32, 64, 128]), + init_cfg: MultiConfig = dict( + type='Normal', + layer='Conv2d', + std=0.01, + override=dict( + type='Normal', + name='vfnet_cls', + std=0.01, + bias_prob=0.01)), + **kwargs) -> None: + # dcn base offsets, adapted from reppoints_head.py + self.num_dconv_points = 9 + self.dcn_kernel = int(np.sqrt(self.num_dconv_points)) + self.dcn_pad = int((self.dcn_kernel - 1) / 2) + dcn_base = np.arange(-self.dcn_pad, + self.dcn_pad + 1).astype(np.float64) + dcn_base_y = np.repeat(dcn_base, self.dcn_kernel) + dcn_base_x = np.tile(dcn_base, self.dcn_kernel) + dcn_base_offset = np.stack([dcn_base_y, dcn_base_x], axis=1).reshape( + (-1)) + self.dcn_base_offset = torch.tensor(dcn_base_offset).view(1, -1, 1, 1) + + super(FCOSHead, self).__init__( + num_classes=num_classes, + in_channels=in_channels, + norm_cfg=norm_cfg, + init_cfg=init_cfg, + **kwargs) + self.regress_ranges = regress_ranges + self.reg_denoms = [ + regress_range[-1] for regress_range in regress_ranges + ] + self.reg_denoms[-1] = self.reg_denoms[-2] * 2 + self.center_sampling = center_sampling + self.center_sample_radius = center_sample_radius + self.sync_num_pos = sync_num_pos + self.bbox_norm_type = bbox_norm_type + self.gradient_mul = gradient_mul + self.use_vfl = use_vfl + if self.use_vfl: + self.loss_cls = MODELS.build(loss_cls) + else: + self.loss_cls = MODELS.build(loss_cls_fl) + self.loss_bbox = MODELS.build(loss_bbox) + self.loss_bbox_refine = MODELS.build(loss_bbox_refine) + + # for getting ATSS targets + self.use_atss = use_atss + self.reg_decoded_bbox = reg_decoded_bbox + self.use_sigmoid_cls = loss_cls.get('use_sigmoid', False) + + self.anchor_center_offset = anchor_generator['center_offset'] + + self.num_base_priors = self.prior_generator.num_base_priors[0] + + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + if self.train_cfg.get('sampler', None) is not None: + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], default_args=dict(context=self)) + else: + self.sampler = PseudoSampler() + # only be used in `get_atss_targets` when `use_atss` is True + self.atss_prior_generator = TASK_UTILS.build(anchor_generator) + + self.fcos_prior_generator = MlvlPointGenerator( + anchor_generator['strides'], + self.anchor_center_offset if self.use_atss else 0.5) + + # In order to reuse the `get_bboxes` in `BaseDenseHead. + # Only be used in testing phase. + self.prior_generator = self.fcos_prior_generator + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + super(FCOSHead, self)._init_cls_convs() + super(FCOSHead, self)._init_reg_convs() + self.relu = nn.ReLU() + self.vfnet_reg_conv = ConvModule( + self.feat_channels, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + bias=self.conv_bias) + self.vfnet_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1) + self.scales = nn.ModuleList([Scale(1.0) for _ in self.strides]) + + self.vfnet_reg_refine_dconv = DeformConv2d( + self.feat_channels, + self.feat_channels, + self.dcn_kernel, + 1, + padding=self.dcn_pad) + self.vfnet_reg_refine = nn.Conv2d(self.feat_channels, 4, 3, padding=1) + self.scales_refine = nn.ModuleList([Scale(1.0) for _ in self.strides]) + + self.vfnet_cls_dconv = DeformConv2d( + self.feat_channels, + self.feat_channels, + self.dcn_kernel, + 1, + padding=self.dcn_pad) + self.vfnet_cls = nn.Conv2d( + self.feat_channels, self.cls_out_channels, 3, padding=1) + + def forward(self, x: Tuple[Tensor]) -> Tuple[List[Tensor]]: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: + + - cls_scores (list[Tensor]): Box iou-aware scores for each scale + level, each is a 4D-tensor, the channel number is + num_points * num_classes. + - bbox_preds (list[Tensor]): Box offsets for each + scale level, each is a 4D-tensor, the channel number is + num_points * 4. + - bbox_preds_refine (list[Tensor]): Refined Box offsets for + each scale level, each is a 4D-tensor, the channel + number is num_points * 4. + """ + return multi_apply(self.forward_single, x, self.scales, + self.scales_refine, self.strides, self.reg_denoms) + + def forward_single(self, x: Tensor, scale: Scale, scale_refine: Scale, + stride: int, reg_denom: int) -> tuple: + """Forward features of a single scale level. + + Args: + x (Tensor): FPN feature maps of the specified stride. + scale (:obj: `mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + scale_refine (:obj: `mmcv.cnn.Scale`): Learnable scale module to + resize the refined bbox prediction. + stride (int): The corresponding stride for feature maps, + used to normalize the bbox prediction when + bbox_norm_type = 'stride'. + reg_denom (int): The corresponding regression range for feature + maps, only used to normalize the bbox prediction when + bbox_norm_type = 'reg_denom'. + + Returns: + tuple: iou-aware cls scores for each box, bbox predictions and + refined bbox predictions of input feature maps. + """ + cls_feat = x + reg_feat = x + + for cls_layer in self.cls_convs: + cls_feat = cls_layer(cls_feat) + + for reg_layer in self.reg_convs: + reg_feat = reg_layer(reg_feat) + + # predict the bbox_pred of different level + reg_feat_init = self.vfnet_reg_conv(reg_feat) + if self.bbox_norm_type == 'reg_denom': + bbox_pred = scale( + self.vfnet_reg(reg_feat_init)).float().exp() * reg_denom + elif self.bbox_norm_type == 'stride': + bbox_pred = scale( + self.vfnet_reg(reg_feat_init)).float().exp() * stride + else: + raise NotImplementedError + + # compute star deformable convolution offsets + # converting dcn_offset to reg_feat.dtype thus VFNet can be + # trained with FP16 + dcn_offset = self.star_dcn_offset(bbox_pred, self.gradient_mul, + stride).to(reg_feat.dtype) + + # refine the bbox_pred + reg_feat = self.relu(self.vfnet_reg_refine_dconv(reg_feat, dcn_offset)) + bbox_pred_refine = scale_refine( + self.vfnet_reg_refine(reg_feat)).float().exp() + bbox_pred_refine = bbox_pred_refine * bbox_pred.detach() + + # predict the iou-aware cls score + cls_feat = self.relu(self.vfnet_cls_dconv(cls_feat, dcn_offset)) + cls_score = self.vfnet_cls(cls_feat) + + if self.training: + return cls_score, bbox_pred, bbox_pred_refine + else: + return cls_score, bbox_pred_refine + + def star_dcn_offset(self, bbox_pred: Tensor, gradient_mul: float, + stride: int) -> Tensor: + """Compute the star deformable conv offsets. + + Args: + bbox_pred (Tensor): Predicted bbox distance offsets (l, r, t, b). + gradient_mul (float): Gradient multiplier. + stride (int): The corresponding stride for feature maps, + used to project the bbox onto the feature map. + + Returns: + Tensor: The offsets for deformable convolution. + """ + dcn_base_offset = self.dcn_base_offset.type_as(bbox_pred) + bbox_pred_grad_mul = (1 - gradient_mul) * bbox_pred.detach() + \ + gradient_mul * bbox_pred + # map to the feature map scale + bbox_pred_grad_mul = bbox_pred_grad_mul / stride + N, C, H, W = bbox_pred.size() + + x1 = bbox_pred_grad_mul[:, 0, :, :] + y1 = bbox_pred_grad_mul[:, 1, :, :] + x2 = bbox_pred_grad_mul[:, 2, :, :] + y2 = bbox_pred_grad_mul[:, 3, :, :] + bbox_pred_grad_mul_offset = bbox_pred.new_zeros( + N, 2 * self.num_dconv_points, H, W) + bbox_pred_grad_mul_offset[:, 0, :, :] = -1.0 * y1 # -y1 + bbox_pred_grad_mul_offset[:, 1, :, :] = -1.0 * x1 # -x1 + bbox_pred_grad_mul_offset[:, 2, :, :] = -1.0 * y1 # -y1 + bbox_pred_grad_mul_offset[:, 4, :, :] = -1.0 * y1 # -y1 + bbox_pred_grad_mul_offset[:, 5, :, :] = x2 # x2 + bbox_pred_grad_mul_offset[:, 7, :, :] = -1.0 * x1 # -x1 + bbox_pred_grad_mul_offset[:, 11, :, :] = x2 # x2 + bbox_pred_grad_mul_offset[:, 12, :, :] = y2 # y2 + bbox_pred_grad_mul_offset[:, 13, :, :] = -1.0 * x1 # -x1 + bbox_pred_grad_mul_offset[:, 14, :, :] = y2 # y2 + bbox_pred_grad_mul_offset[:, 16, :, :] = y2 # y2 + bbox_pred_grad_mul_offset[:, 17, :, :] = x2 # x2 + dcn_offset = bbox_pred_grad_mul_offset - dcn_base_offset + + return dcn_offset + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + bbox_preds_refine: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Compute loss of the head. + + Args: + cls_scores (list[Tensor]): Box iou-aware scores for each scale + level, each is a 4D-tensor, the channel number is + num_points * num_classes. + bbox_preds (list[Tensor]): Box offsets for each + scale level, each is a 4D-tensor, the channel number is + num_points * 4. + bbox_preds_refine (list[Tensor]): Refined Box offsets for + each scale level, each is a 4D-tensor, the channel + number is num_points * 4. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert len(cls_scores) == len(bbox_preds) == len(bbox_preds_refine) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + all_level_points = self.fcos_prior_generator.grid_priors( + featmap_sizes, bbox_preds[0].dtype, bbox_preds[0].device) + labels, label_weights, bbox_targets, bbox_weights = self.get_targets( + cls_scores, + all_level_points, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + + num_imgs = cls_scores[0].size(0) + # flatten cls_scores, bbox_preds and bbox_preds_refine + flatten_cls_scores = [ + cls_score.permute(0, 2, 3, + 1).reshape(-1, + self.cls_out_channels).contiguous() + for cls_score in cls_scores + ] + flatten_bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4).contiguous() + for bbox_pred in bbox_preds + ] + flatten_bbox_preds_refine = [ + bbox_pred_refine.permute(0, 2, 3, 1).reshape(-1, 4).contiguous() + for bbox_pred_refine in bbox_preds_refine + ] + flatten_cls_scores = torch.cat(flatten_cls_scores) + flatten_bbox_preds = torch.cat(flatten_bbox_preds) + flatten_bbox_preds_refine = torch.cat(flatten_bbox_preds_refine) + flatten_labels = torch.cat(labels) + flatten_bbox_targets = torch.cat(bbox_targets) + # repeat points to align with bbox_preds + flatten_points = torch.cat( + [points.repeat(num_imgs, 1) for points in all_level_points]) + + # FG cat_id: [0, num_classes - 1], BG cat_id: num_classes + bg_class_ind = self.num_classes + pos_inds = torch.where( + ((flatten_labels >= 0) & (flatten_labels < bg_class_ind)) > 0)[0] + num_pos = len(pos_inds) + + pos_bbox_preds = flatten_bbox_preds[pos_inds] + pos_bbox_preds_refine = flatten_bbox_preds_refine[pos_inds] + pos_labels = flatten_labels[pos_inds] + + # sync num_pos across all gpus + if self.sync_num_pos: + num_pos_avg_per_gpu = reduce_mean( + pos_inds.new_tensor(num_pos).float()).item() + num_pos_avg_per_gpu = max(num_pos_avg_per_gpu, 1.0) + else: + num_pos_avg_per_gpu = num_pos + + pos_bbox_targets = flatten_bbox_targets[pos_inds] + pos_points = flatten_points[pos_inds] + + pos_decoded_bbox_preds = self.bbox_coder.decode( + pos_points, pos_bbox_preds) + pos_decoded_target_preds = self.bbox_coder.decode( + pos_points, pos_bbox_targets) + iou_targets_ini = bbox_overlaps( + pos_decoded_bbox_preds, + pos_decoded_target_preds.detach(), + is_aligned=True).clamp(min=1e-6) + bbox_weights_ini = iou_targets_ini.clone().detach() + bbox_avg_factor_ini = reduce_mean( + bbox_weights_ini.sum()).clamp_(min=1).item() + + pos_decoded_bbox_preds_refine = \ + self.bbox_coder.decode(pos_points, pos_bbox_preds_refine) + iou_targets_rf = bbox_overlaps( + pos_decoded_bbox_preds_refine, + pos_decoded_target_preds.detach(), + is_aligned=True).clamp(min=1e-6) + bbox_weights_rf = iou_targets_rf.clone().detach() + bbox_avg_factor_rf = reduce_mean( + bbox_weights_rf.sum()).clamp_(min=1).item() + + if num_pos > 0: + loss_bbox = self.loss_bbox( + pos_decoded_bbox_preds, + pos_decoded_target_preds.detach(), + weight=bbox_weights_ini, + avg_factor=bbox_avg_factor_ini) + + loss_bbox_refine = self.loss_bbox_refine( + pos_decoded_bbox_preds_refine, + pos_decoded_target_preds.detach(), + weight=bbox_weights_rf, + avg_factor=bbox_avg_factor_rf) + + # build IoU-aware cls_score targets + if self.use_vfl: + pos_ious = iou_targets_rf.clone().detach() + cls_iou_targets = torch.zeros_like(flatten_cls_scores) + cls_iou_targets[pos_inds, pos_labels] = pos_ious + else: + loss_bbox = pos_bbox_preds.sum() * 0 + loss_bbox_refine = pos_bbox_preds_refine.sum() * 0 + if self.use_vfl: + cls_iou_targets = torch.zeros_like(flatten_cls_scores) + + if self.use_vfl: + loss_cls = self.loss_cls( + flatten_cls_scores, + cls_iou_targets, + avg_factor=num_pos_avg_per_gpu) + else: + loss_cls = self.loss_cls( + flatten_cls_scores, + flatten_labels, + weight=label_weights, + avg_factor=num_pos_avg_per_gpu) + + return dict( + loss_cls=loss_cls, + loss_bbox=loss_bbox, + loss_bbox_rf=loss_bbox_refine) + + def get_targets( + self, + cls_scores: List[Tensor], + mlvl_points: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> tuple: + """A wrapper for computing ATSS and FCOS targets for points in multiple + images. + + Args: + cls_scores (list[Tensor]): Box iou-aware scores for each scale + level with shape (N, num_points * num_classes, H, W). + mlvl_points (list[Tensor]): Points of each fpn level, each has + shape (num_points, 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + tuple: + + - labels_list (list[Tensor]): Labels of each level. + - label_weights (Tensor/None): Label weights of all levels. + - bbox_targets_list (list[Tensor]): Regression targets of each + level, (l, t, r, b). + - bbox_weights (Tensor/None): Bbox weights of all levels. + """ + if self.use_atss: + return self.get_atss_targets(cls_scores, mlvl_points, + batch_gt_instances, batch_img_metas, + batch_gt_instances_ignore) + else: + self.norm_on_bbox = False + return self.get_fcos_targets(mlvl_points, batch_gt_instances) + + def _get_targets_single(self, *args, **kwargs): + """Avoid ambiguity in multiple inheritance.""" + if self.use_atss: + return ATSSHead._get_targets_single(self, *args, **kwargs) + else: + return FCOSHead._get_targets_single(self, *args, **kwargs) + + def get_fcos_targets(self, points: List[Tensor], + batch_gt_instances: InstanceList) -> tuple: + """Compute FCOS regression and classification targets for points in + multiple images. + + Args: + points (list[Tensor]): Points of each fpn level, each has shape + (num_points, 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + + Returns: + tuple: + + - labels (list[Tensor]): Labels of each level. + - label_weights: None, to be compatible with ATSS targets. + - bbox_targets (list[Tensor]): BBox targets of each level. + - bbox_weights: None, to be compatible with ATSS targets. + """ + labels, bbox_targets = FCOSHead.get_targets(self, points, + batch_gt_instances) + label_weights = None + bbox_weights = None + return labels, label_weights, bbox_targets, bbox_weights + + def get_anchors(self, + featmap_sizes: List[Tuple], + batch_img_metas: List[dict], + device: str = 'cuda') -> tuple: + """Get anchors according to feature map sizes. + + Args: + featmap_sizes (list[tuple]): Multi-level feature map sizes. + batch_img_metas (list[dict]): Image meta info. + device (str): Device for returned tensors + + Returns: + tuple: + + - anchor_list (list[Tensor]): Anchors of each image. + - valid_flag_list (list[Tensor]): Valid flags of each image. + """ + num_imgs = len(batch_img_metas) + + # since feature map sizes of all images are the same, we only compute + # anchors for one time + multi_level_anchors = self.atss_prior_generator.grid_priors( + featmap_sizes, device=device) + anchor_list = [multi_level_anchors for _ in range(num_imgs)] + + # for each image, we compute valid flags of multi level anchors + valid_flag_list = [] + for img_id, img_meta in enumerate(batch_img_metas): + multi_level_flags = self.atss_prior_generator.valid_flags( + featmap_sizes, img_meta['pad_shape'], device=device) + valid_flag_list.append(multi_level_flags) + + return anchor_list, valid_flag_list + + def get_atss_targets( + self, + cls_scores: List[Tensor], + mlvl_points: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> tuple: + """A wrapper for computing ATSS targets for points in multiple images. + + Args: + cls_scores (list[Tensor]): Box iou-aware scores for each scale + level with shape (N, num_points * num_classes, H, W). + mlvl_points (list[Tensor]): Points of each fpn level, each has + shape (num_points, 2). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + tuple: + + - labels_list (list[Tensor]): Labels of each level. + - label_weights (Tensor): Label weights of all levels. + - bbox_targets_list (list[Tensor]): Regression targets of each + level, (l, t, r, b). + - bbox_weights (Tensor): Bbox weights of all levels. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len( + featmap_sizes + ) == self.atss_prior_generator.num_levels == \ + self.fcos_prior_generator.num_levels + + device = cls_scores[0].device + + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + cls_reg_targets = ATSSHead.get_targets( + self, + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=True) + + (anchor_list, labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor) = cls_reg_targets + + bbox_targets_list = [ + bbox_targets.reshape(-1, 4) for bbox_targets in bbox_targets_list + ] + + num_imgs = len(batch_img_metas) + # transform bbox_targets (x1, y1, x2, y2) into (l, t, r, b) format + bbox_targets_list = self.transform_bbox_targets( + bbox_targets_list, mlvl_points, num_imgs) + + labels_list = [labels.reshape(-1) for labels in labels_list] + label_weights_list = [ + label_weights.reshape(-1) for label_weights in label_weights_list + ] + bbox_weights_list = [ + bbox_weights.reshape(-1) for bbox_weights in bbox_weights_list + ] + label_weights = torch.cat(label_weights_list) + bbox_weights = torch.cat(bbox_weights_list) + return labels_list, label_weights, bbox_targets_list, bbox_weights + + def transform_bbox_targets(self, decoded_bboxes: List[Tensor], + mlvl_points: List[Tensor], + num_imgs: int) -> List[Tensor]: + """Transform bbox_targets (x1, y1, x2, y2) into (l, t, r, b) format. + + Args: + decoded_bboxes (list[Tensor]): Regression targets of each level, + in the form of (x1, y1, x2, y2). + mlvl_points (list[Tensor]): Points of each fpn level, each has + shape (num_points, 2). + num_imgs (int): the number of images in a batch. + + Returns: + bbox_targets (list[Tensor]): Regression targets of each level in + the form of (l, t, r, b). + """ + # TODO: Re-implemented in Class PointCoder + assert len(decoded_bboxes) == len(mlvl_points) + num_levels = len(decoded_bboxes) + mlvl_points = [points.repeat(num_imgs, 1) for points in mlvl_points] + bbox_targets = [] + for i in range(num_levels): + bbox_target = self.bbox_coder.encode(mlvl_points[i], + decoded_bboxes[i]) + bbox_targets.append(bbox_target) + + return bbox_targets + + def _load_from_state_dict(self, state_dict: dict, prefix: str, + local_metadata: dict, strict: bool, + missing_keys: Union[List[str], str], + unexpected_keys: Union[List[str], str], + error_msgs: Union[List[str], str]) -> None: + """Override the method in the parent class to avoid changing para's + name.""" + pass diff --git a/mmdetection/mmdet/models/dense_heads/yolact_head.py b/mmdetection/mmdet/models/dense_heads/yolact_head.py new file mode 100644 index 00000000..3390c136 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/yolact_head.py @@ -0,0 +1,1193 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import List, Optional + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule, ModuleList +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList, OptMultiConfig) +from ..layers import fast_nms +from ..utils import images_to_levels, multi_apply, select_single_mlvl +from ..utils.misc import empty_instances +from .anchor_head import AnchorHead +from .base_mask_head import BaseMaskHead + + +@MODELS.register_module() +class YOLACTHead(AnchorHead): + """YOLACT box head used in https://arxiv.org/abs/1904.02689. + + Note that YOLACT head is a light version of RetinaNet head. + Four differences are described as follows: + + 1. YOLACT box head has three-times fewer anchors. + 2. YOLACT box head shares the convs for box and cls branches. + 3. YOLACT box head uses OHEM instead of Focal loss. + 4. YOLACT box head predicts a set of mask coefficients for each box. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + anchor_generator (:obj:`ConfigDict` or dict): Config dict for + anchor generator + loss_cls (:obj:`ConfigDict` or dict): Config of classification loss. + loss_bbox (:obj:`ConfigDict` or dict): Config of localization loss. + num_head_convs (int): Number of the conv layers shared by + box and cls branches. + num_protos (int): Number of the mask coefficients. + use_ohem (bool): If true, ``loss_single_OHEM`` will be used for + cls loss calculation. If false, ``loss_single`` will be used. + conv_cfg (:obj:`ConfigDict` or dict, optional): Dictionary to + construct and config conv layer. + norm_cfg (:obj:`ConfigDict` or dict, optional): Dictionary to + construct and config norm layer. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + anchor_generator: ConfigType = dict( + type='AnchorGenerator', + octave_base_scale=3, + scales_per_octave=1, + ratios=[0.5, 1.0, 2.0], + strides=[8, 16, 32, 64, 128]), + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=False, + reduction='none', + loss_weight=1.0), + loss_bbox: ConfigType = dict( + type='SmoothL1Loss', beta=1.0, loss_weight=1.5), + num_head_convs: int = 1, + num_protos: int = 32, + use_ohem: bool = True, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = dict( + type='Xavier', + distribution='uniform', + bias=0, + layer='Conv2d'), + **kwargs) -> None: + self.num_head_convs = num_head_convs + self.num_protos = num_protos + self.use_ohem = use_ohem + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + loss_cls=loss_cls, + loss_bbox=loss_bbox, + anchor_generator=anchor_generator, + init_cfg=init_cfg, + **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.relu = nn.ReLU(inplace=True) + self.head_convs = ModuleList() + for i in range(self.num_head_convs): + chn = self.in_channels if i == 0 else self.feat_channels + self.head_convs.append( + ConvModule( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.conv_cls = nn.Conv2d( + self.feat_channels, + self.num_base_priors * self.cls_out_channels, + 3, + padding=1) + self.conv_reg = nn.Conv2d( + self.feat_channels, self.num_base_priors * 4, 3, padding=1) + self.conv_coeff = nn.Conv2d( + self.feat_channels, + self.num_base_priors * self.num_protos, + 3, + padding=1) + + def forward_single(self, x: Tensor) -> tuple: + """Forward feature of a single scale level. + + Args: + x (Tensor): Features of a single scale level. + + Returns: + tuple: + + - cls_score (Tensor): Cls scores for a single scale level + the channels number is num_anchors * num_classes. + - bbox_pred (Tensor): Box energies / deltas for a single scale + level, the channels number is num_anchors * 4. + - coeff_pred (Tensor): Mask coefficients for a single scale + level, the channels number is num_anchors * num_protos. + """ + for head_conv in self.head_convs: + x = head_conv(x) + cls_score = self.conv_cls(x) + bbox_pred = self.conv_reg(x) + coeff_pred = self.conv_coeff(x).tanh() + return cls_score, bbox_pred, coeff_pred + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + coeff_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the bbox head. + + When ``self.use_ohem == True``, it functions like ``SSDHead.loss``, + otherwise, it follows ``AnchorHead.loss``. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + has shape (N, num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + coeff_preds (list[Tensor]): Mask coefficients for each scale + level with shape (N, num_anchors * num_protos, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore, + unmap_outputs=not self.use_ohem, + return_sampling_results=True) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + avg_factor, sampling_results) = cls_reg_targets + + if self.use_ohem: + num_images = len(batch_img_metas) + all_cls_scores = torch.cat([ + s.permute(0, 2, 3, 1).reshape( + num_images, -1, self.cls_out_channels) for s in cls_scores + ], 1) + all_labels = torch.cat(labels_list, -1).view(num_images, -1) + all_label_weights = torch.cat(label_weights_list, + -1).view(num_images, -1) + all_bbox_preds = torch.cat([ + b.permute(0, 2, 3, 1).reshape(num_images, -1, 4) + for b in bbox_preds + ], -2) + all_bbox_targets = torch.cat(bbox_targets_list, + -2).view(num_images, -1, 4) + all_bbox_weights = torch.cat(bbox_weights_list, + -2).view(num_images, -1, 4) + + # concat all level anchors to a single tensor + all_anchors = [] + for i in range(num_images): + all_anchors.append(torch.cat(anchor_list[i])) + + # check NaN and Inf + assert torch.isfinite(all_cls_scores).all().item(), \ + 'classification scores become infinite or NaN!' + assert torch.isfinite(all_bbox_preds).all().item(), \ + 'bbox predications become infinite or NaN!' + + losses_cls, losses_bbox = multi_apply( + self.OHEMloss_by_feat_single, + all_cls_scores, + all_bbox_preds, + all_anchors, + all_labels, + all_label_weights, + all_bbox_targets, + all_bbox_weights, + avg_factor=avg_factor) + else: + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + # concat all level anchors and flags to a single tensor + concat_anchor_list = [] + for i in range(len(anchor_list)): + concat_anchor_list.append(torch.cat(anchor_list[i])) + all_anchor_list = images_to_levels(concat_anchor_list, + num_level_anchors) + losses_cls, losses_bbox = multi_apply( + self.loss_by_feat_single, + cls_scores, + bbox_preds, + all_anchor_list, + labels_list, + label_weights_list, + bbox_targets_list, + bbox_weights_list, + avg_factor=avg_factor) + losses = dict(loss_cls=losses_cls, loss_bbox=losses_bbox) + # update `_raw_positive_infos`, which will be used when calling + # `get_positive_infos`. + self._raw_positive_infos.update(coeff_preds=coeff_preds) + return losses + + def OHEMloss_by_feat_single(self, cls_score: Tensor, bbox_pred: Tensor, + anchors: Tensor, labels: Tensor, + label_weights: Tensor, bbox_targets: Tensor, + bbox_weights: Tensor, + avg_factor: int) -> tuple: + """Compute loss of a single image. Similar to + func:``SSDHead.loss_by_feat_single`` + + Args: + cls_score (Tensor): Box scores for eachimage + Has shape (num_total_anchors, num_classes). + bbox_pred (Tensor): Box energies / deltas for each image + level with shape (num_total_anchors, 4). + anchors (Tensor): Box reference for each scale level with shape + (num_total_anchors, 4). + labels (Tensor): Labels of each anchors with shape + (num_total_anchors,). + label_weights (Tensor): Label weights of each anchor with shape + (num_total_anchors,) + bbox_targets (Tensor): BBox regression targets of each anchor with + shape (num_total_anchors, 4). + bbox_weights (Tensor): BBox regression loss weights of each anchor + with shape (num_total_anchors, 4). + avg_factor (int): Average factor that is used to average + the loss. When using sampling method, avg_factor is usually + the sum of positive and negative priors. When using + `PseudoSampler`, `avg_factor` is usually equal to the number + of positive priors. + + Returns: + Tuple[Tensor, Tensor]: A tuple of cls loss and bbox loss of one + feature map. + """ + + loss_cls_all = self.loss_cls(cls_score, labels, label_weights) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + pos_inds = ((labels >= 0) & (labels < self.num_classes)).nonzero( + as_tuple=False).reshape(-1) + neg_inds = (labels == self.num_classes).nonzero( + as_tuple=False).view(-1) + + num_pos_samples = pos_inds.size(0) + if num_pos_samples == 0: + num_neg_samples = neg_inds.size(0) + else: + num_neg_samples = self.train_cfg['neg_pos_ratio'] * \ + num_pos_samples + if num_neg_samples > neg_inds.size(0): + num_neg_samples = neg_inds.size(0) + topk_loss_cls_neg, _ = loss_cls_all[neg_inds].topk(num_neg_samples) + loss_cls_pos = loss_cls_all[pos_inds].sum() + loss_cls_neg = topk_loss_cls_neg.sum() + loss_cls = (loss_cls_pos + loss_cls_neg) / avg_factor + if self.reg_decoded_bbox: + # When the regression loss (e.g. `IouLoss`, `GIouLoss`) + # is applied directly on the decoded bounding boxes, it + # decodes the already encoded coordinates to absolute format. + bbox_pred = self.bbox_coder.decode(anchors, bbox_pred) + loss_bbox = self.loss_bbox( + bbox_pred, bbox_targets, bbox_weights, avg_factor=avg_factor) + return loss_cls[None], loss_bbox + + def get_positive_infos(self) -> InstanceList: + """Get positive information from sampling results. + + Returns: + list[:obj:`InstanceData`]: Positive Information of each image, + usually including positive bboxes, positive labels, positive + priors, positive coeffs, etc. + """ + assert len(self._raw_positive_infos) > 0 + sampling_results = self._raw_positive_infos['sampling_results'] + num_imgs = len(sampling_results) + + coeff_pred_list = [] + for coeff_pred_per_level in self._raw_positive_infos['coeff_preds']: + coeff_pred_per_level = \ + coeff_pred_per_level.permute( + 0, 2, 3, 1).reshape(num_imgs, -1, self.num_protos) + coeff_pred_list.append(coeff_pred_per_level) + coeff_preds = torch.cat(coeff_pred_list, dim=1) + + pos_info_list = [] + for idx, sampling_result in enumerate(sampling_results): + pos_info = InstanceData() + coeff_preds_single = coeff_preds[idx] + pos_info.pos_assigned_gt_inds = \ + sampling_result.pos_assigned_gt_inds + pos_info.pos_inds = sampling_result.pos_inds + pos_info.coeffs = coeff_preds_single[sampling_result.pos_inds] + pos_info.bboxes = sampling_result.pos_gt_bboxes + pos_info_list.append(pos_info) + return pos_info_list + + def predict_by_feat(self, + cls_scores, + bbox_preds, + coeff_preds, + batch_img_metas, + cfg=None, + rescale=True, + **kwargs): + """Similar to func:``AnchorHead.get_bboxes``, but additionally + processes coeff_preds. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + with shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + coeff_preds (list[Tensor]): Mask coefficients for each scale + level with shape (N, num_anchors * num_protos, H, W) + batch_img_metas (list[dict]): Batch image meta info. + cfg (:obj:`Config` | None): Test / postprocessing configuration, + if None, test_cfg would be used + rescale (bool): If True, return boxes in original image space. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - coeffs (Tensor): the predicted mask coefficients of + instance inside the corresponding box has a shape + (n, num_protos). + """ + assert len(cls_scores) == len(bbox_preds) + num_levels = len(cls_scores) + + device = cls_scores[0].device + featmap_sizes = [cls_scores[i].shape[-2:] for i in range(num_levels)] + mlvl_priors = self.prior_generator.grid_priors( + featmap_sizes, device=device) + + result_list = [] + for img_id in range(len(batch_img_metas)): + img_meta = batch_img_metas[img_id] + cls_score_list = select_single_mlvl(cls_scores, img_id) + bbox_pred_list = select_single_mlvl(bbox_preds, img_id) + coeff_pred_list = select_single_mlvl(coeff_preds, img_id) + results = self._predict_by_feat_single( + cls_score_list=cls_score_list, + bbox_pred_list=bbox_pred_list, + coeff_preds_list=coeff_pred_list, + mlvl_priors=mlvl_priors, + img_meta=img_meta, + cfg=cfg, + rescale=rescale) + result_list.append(results) + return result_list + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + coeff_preds_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: ConfigType, + rescale: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. Similar to func:``AnchorHead._predict_by_feat_single``, + but additionally processes coeff_preds_list and uses fast NMS instead + of traditional NMS. + + Args: + cls_score_list (list[Tensor]): Box scores for a single scale level + Has shape (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas for a single + scale level with shape (num_priors * 4, H, W). + coeff_preds_list (list[Tensor]): Mask coefficients for a single + scale level with shape (num_priors * num_protos, H, W). + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid, + has shape (num_priors, 4). + img_meta (dict): Image meta info. + cfg (mmengine.Config): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - coeffs (Tensor): the predicted mask coefficients of + instance inside the corresponding box has a shape + (n, num_protos). + """ + assert len(cls_score_list) == len(bbox_pred_list) == len(mlvl_priors) + + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + img_shape = img_meta['img_shape'] + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bbox_preds = [] + mlvl_valid_priors = [] + mlvl_scores = [] + mlvl_coeffs = [] + for cls_score, bbox_pred, coeff_pred, priors in \ + zip(cls_score_list, bbox_pred_list, + coeff_preds_list, mlvl_priors): + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + if self.use_sigmoid_cls: + scores = cls_score.sigmoid() + else: + scores = cls_score.softmax(-1) + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, 4) + coeff_pred = coeff_pred.permute(1, 2, + 0).reshape(-1, self.num_protos) + + if 0 < nms_pre < scores.shape[0]: + # Get maximum scores for foreground classes. + if self.use_sigmoid_cls: + max_scores, _ = scores.max(dim=1) + else: + # remind that we set FG labels to [0, num_class-1] + # since mmdet v2.0 + # BG cat_id: num_class + max_scores, _ = scores[:, :-1].max(dim=1) + _, topk_inds = max_scores.topk(nms_pre) + priors = priors[topk_inds, :] + bbox_pred = bbox_pred[topk_inds, :] + scores = scores[topk_inds, :] + coeff_pred = coeff_pred[topk_inds, :] + + mlvl_bbox_preds.append(bbox_pred) + mlvl_valid_priors.append(priors) + mlvl_scores.append(scores) + mlvl_coeffs.append(coeff_pred) + + bbox_pred = torch.cat(mlvl_bbox_preds) + priors = torch.cat(mlvl_valid_priors) + multi_bboxes = self.bbox_coder.decode( + priors, bbox_pred, max_shape=img_shape) + + multi_scores = torch.cat(mlvl_scores) + multi_coeffs = torch.cat(mlvl_coeffs) + + return self._bbox_post_process( + multi_bboxes=multi_bboxes, + multi_scores=multi_scores, + multi_coeffs=multi_coeffs, + cfg=cfg, + rescale=rescale, + img_meta=img_meta) + + def _bbox_post_process(self, + multi_bboxes: Tensor, + multi_scores: Tensor, + multi_coeffs: Tensor, + cfg: ConfigType, + rescale: bool = False, + img_meta: Optional[dict] = None, + **kwargs) -> InstanceData: + """bbox post-processing method. + + The boxes would be rescaled to the original image scale and do + the nms operation. Usually `with_nms` is False is used for aug test. + + Args: + multi_bboxes (Tensor): Predicted bbox that concat all levels. + multi_scores (Tensor): Bbox scores that concat all levels. + multi_coeffs (Tensor): Mask coefficients that concat all levels. + cfg (ConfigDict): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Default to False. + img_meta (dict, optional): Image meta info. Defaults to None. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - coeffs (Tensor): the predicted mask coefficients of + instance inside the corresponding box has a shape + (n, num_protos). + """ + if rescale: + assert img_meta.get('scale_factor') is not None + multi_bboxes /= multi_bboxes.new_tensor( + img_meta['scale_factor']).repeat((1, 2)) + # mlvl_bboxes /= mlvl_bboxes.new_tensor(scale_factor) + + if self.use_sigmoid_cls: + # Add a dummy background class to the backend when using sigmoid + # remind that we set FG labels to [0, num_class-1] since mmdet v2.0 + # BG cat_id: num_class + + padding = multi_scores.new_zeros(multi_scores.shape[0], 1) + multi_scores = torch.cat([multi_scores, padding], dim=1) + det_bboxes, det_labels, det_coeffs = fast_nms( + multi_bboxes, multi_scores, multi_coeffs, cfg.score_thr, + cfg.iou_thr, cfg.top_k, cfg.max_per_img) + results = InstanceData() + results.bboxes = det_bboxes[:, :4] + results.scores = det_bboxes[:, -1] + results.labels = det_labels + results.coeffs = det_coeffs + return results + + +@MODELS.register_module() +class YOLACTProtonet(BaseMaskHead): + """YOLACT mask head used in https://arxiv.org/abs/1904.02689. + + This head outputs the mask prototypes for YOLACT. + + Args: + in_channels (int): Number of channels in the input feature map. + proto_channels (tuple[int]): Output channels of protonet convs. + proto_kernel_sizes (tuple[int]): Kernel sizes of protonet convs. + include_last_relu (bool): If keep the last relu of protonet. + num_protos (int): Number of prototypes. + num_classes (int): Number of categories excluding the background + category. + loss_mask_weight (float): Reweight the mask loss by this factor. + max_masks_to_train (int): Maximum number of masks to train for + each image. + with_seg_branch (bool): Whether to apply a semantic segmentation + branch and calculate loss during training to increase + performance with no speed penalty. Defaults to True. + loss_segm (:obj:`ConfigDict` or dict, optional): Config of + semantic segmentation loss. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config + of head. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + head. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + """ + + def __init__( + self, + num_classes: int, + in_channels: int = 256, + proto_channels: tuple = (256, 256, 256, None, 256, 32), + proto_kernel_sizes: tuple = (3, 3, 3, -2, 3, 1), + include_last_relu: bool = True, + num_protos: int = 32, + loss_mask_weight: float = 1.0, + max_masks_to_train: int = 100, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + with_seg_branch: bool = True, + loss_segm: ConfigType = dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), + init_cfg=dict( + type='Xavier', + distribution='uniform', + override=dict(name='protonet')) + ) -> None: + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.proto_channels = proto_channels + self.proto_kernel_sizes = proto_kernel_sizes + self.include_last_relu = include_last_relu + + # Segmentation branch + self.with_seg_branch = with_seg_branch + self.segm_branch = SegmentationModule( + num_classes=num_classes, in_channels=in_channels) \ + if with_seg_branch else None + self.loss_segm = MODELS.build(loss_segm) if with_seg_branch else None + + self.loss_mask_weight = loss_mask_weight + self.num_protos = num_protos + self.num_classes = num_classes + self.max_masks_to_train = max_masks_to_train + self.train_cfg = train_cfg + self.test_cfg = test_cfg + self._init_layers() + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + # Possible patterns: + # ( 256, 3) -> conv + # ( 256,-2) -> deconv + # (None,-2) -> bilinear interpolate + in_channels = self.in_channels + protonets = ModuleList() + for num_channels, kernel_size in zip(self.proto_channels, + self.proto_kernel_sizes): + if kernel_size > 0: + layer = nn.Conv2d( + in_channels, + num_channels, + kernel_size, + padding=kernel_size // 2) + else: + if num_channels is None: + layer = InterpolateModule( + scale_factor=-kernel_size, + mode='bilinear', + align_corners=False) + else: + layer = nn.ConvTranspose2d( + in_channels, + num_channels, + -kernel_size, + padding=kernel_size // 2) + protonets.append(layer) + protonets.append(nn.ReLU(inplace=True)) + in_channels = num_channels if num_channels is not None \ + else in_channels + if not self.include_last_relu: + protonets = protonets[:-1] + self.protonet = nn.Sequential(*protonets) + + def forward(self, x: tuple, positive_infos: InstanceList) -> tuple: + """Forward feature from the upstream network to get prototypes and + linearly combine the prototypes, using masks coefficients, into + instance masks. Finally, crop the instance masks with given bboxes. + + Args: + x (Tuple[Tensor]): Feature from the upstream network, which is + a 4D-tensor. + positive_infos (List[:obj:``InstanceData``]): Positive information + that calculate from detect head. + + Returns: + tuple: Predicted instance segmentation masks and + semantic segmentation map. + """ + # YOLACT used single feature map to get segmentation masks + single_x = x[0] + + # YOLACT segmentation branch, if not training or segmentation branch + # is None, will not process the forward function. + if self.segm_branch is not None and self.training: + segm_preds = self.segm_branch(single_x) + else: + segm_preds = None + # YOLACT mask head + prototypes = self.protonet(single_x) + prototypes = prototypes.permute(0, 2, 3, 1).contiguous() + + num_imgs = single_x.size(0) + + mask_pred_list = [] + for idx in range(num_imgs): + cur_prototypes = prototypes[idx] + pos_coeffs = positive_infos[idx].coeffs + + # Linearly combine the prototypes with the mask coefficients + mask_preds = cur_prototypes @ pos_coeffs.t() + mask_preds = torch.sigmoid(mask_preds) + mask_pred_list.append(mask_preds) + return mask_pred_list, segm_preds + + def loss_by_feat(self, mask_preds: List[Tensor], segm_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], positive_infos: InstanceList, + **kwargs) -> dict: + """Calculate the loss based on the features extracted by the mask head. + + Args: + mask_preds (list[Tensor]): List of predicted prototypes, each has + shape (num_classes, H, W). + segm_preds (Tensor): Predicted semantic segmentation map with + shape (N, num_classes, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``masks``, + and ``labels`` attributes. + batch_img_metas (list[dict]): Meta information of multiple images. + positive_infos (List[:obj:``InstanceData``]): Information of + positive samples of each image that are assigned in detection + head. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + assert positive_infos is not None, \ + 'positive_infos should not be None in `YOLACTProtonet`' + losses = dict() + + # crop + croped_mask_pred = self.crop_mask_preds(mask_preds, batch_img_metas, + positive_infos) + + loss_mask = [] + loss_segm = [] + num_imgs, _, mask_h, mask_w = segm_preds.size() + assert num_imgs == len(croped_mask_pred) + segm_avg_factor = num_imgs * mask_h * mask_w + total_pos = 0 + + if self.segm_branch is not None: + assert segm_preds is not None + + for idx in range(num_imgs): + img_meta = batch_img_metas[idx] + + (mask_preds, pos_mask_targets, segm_targets, num_pos, + gt_bboxes_for_reweight) = self._get_targets_single( + croped_mask_pred[idx], segm_preds[idx], + batch_gt_instances[idx], positive_infos[idx]) + + # segmentation loss + if self.with_seg_branch: + if segm_targets is None: + loss = segm_preds[idx].sum() * 0. + else: + loss = self.loss_segm( + segm_preds[idx], + segm_targets, + avg_factor=segm_avg_factor) + loss_segm.append(loss) + # mask loss + total_pos += num_pos + if num_pos == 0 or pos_mask_targets is None: + loss = mask_preds.sum() * 0. + else: + mask_preds = torch.clamp(mask_preds, 0, 1) + loss = F.binary_cross_entropy( + mask_preds, pos_mask_targets, + reduction='none') * self.loss_mask_weight + + h, w = img_meta['img_shape'][:2] + gt_bboxes_width = (gt_bboxes_for_reweight[:, 2] - + gt_bboxes_for_reweight[:, 0]) / w + gt_bboxes_height = (gt_bboxes_for_reweight[:, 3] - + gt_bboxes_for_reweight[:, 1]) / h + loss = loss.mean(dim=(1, + 2)) / gt_bboxes_width / gt_bboxes_height + loss = torch.sum(loss) + loss_mask.append(loss) + + if total_pos == 0: + total_pos += 1 # avoid nan + loss_mask = [x / total_pos for x in loss_mask] + + losses.update(loss_mask=loss_mask) + if self.with_seg_branch: + losses.update(loss_segm=loss_segm) + + return losses + + def _get_targets_single(self, mask_preds: Tensor, segm_pred: Tensor, + gt_instances: InstanceData, + positive_info: InstanceData): + """Compute targets for predictions of single image. + + Args: + mask_preds (Tensor): Predicted prototypes with shape + (num_classes, H, W). + segm_pred (Tensor): Predicted semantic segmentation map + with shape (num_classes, H, W). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes``, ``labels``, + and ``masks`` attributes. + positive_info (:obj:`InstanceData`): Information of positive + samples that are assigned in detection head. It usually + contains following keys. + + - pos_assigned_gt_inds (Tensor): Assigner GT indexes of + positive proposals, has shape (num_pos, ) + - pos_inds (Tensor): Positive index of image, has + shape (num_pos, ). + - coeffs (Tensor): Positive mask coefficients + with shape (num_pos, num_protos). + - bboxes (Tensor): Positive bboxes with shape + (num_pos, 4) + + Returns: + tuple: Usually returns a tuple containing learning targets. + + - mask_preds (Tensor): Positive predicted mask with shape + (num_pos, mask_h, mask_w). + - pos_mask_targets (Tensor): Positive mask targets with shape + (num_pos, mask_h, mask_w). + - segm_targets (Tensor): Semantic segmentation targets with shape + (num_classes, segm_h, segm_w). + - num_pos (int): Positive numbers. + - gt_bboxes_for_reweight (Tensor): GT bboxes that match to the + positive priors has shape (num_pos, 4). + """ + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + device = gt_bboxes.device + gt_masks = gt_instances.masks.to_tensor( + dtype=torch.bool, device=device).float() + if gt_masks.size(0) == 0: + return mask_preds, None, None, 0, None + + # process with semantic segmentation targets + if segm_pred is not None: + num_classes, segm_h, segm_w = segm_pred.size() + with torch.no_grad(): + downsampled_masks = F.interpolate( + gt_masks.unsqueeze(0), (segm_h, segm_w), + mode='bilinear', + align_corners=False).squeeze(0) + downsampled_masks = downsampled_masks.gt(0.5).float() + segm_targets = torch.zeros_like(segm_pred, requires_grad=False) + for obj_idx in range(downsampled_masks.size(0)): + segm_targets[gt_labels[obj_idx] - 1] = torch.max( + segm_targets[gt_labels[obj_idx] - 1], + downsampled_masks[obj_idx]) + else: + segm_targets = None + # process with mask targets + pos_assigned_gt_inds = positive_info.pos_assigned_gt_inds + num_pos = pos_assigned_gt_inds.size(0) + # Since we're producing (near) full image masks, + # it'd take too much vram to backprop on every single mask. + # Thus we select only a subset. + if num_pos > self.max_masks_to_train: + perm = torch.randperm(num_pos) + select = perm[:self.max_masks_to_train] + mask_preds = mask_preds[select] + pos_assigned_gt_inds = pos_assigned_gt_inds[select] + num_pos = self.max_masks_to_train + + gt_bboxes_for_reweight = gt_bboxes[pos_assigned_gt_inds] + + mask_h, mask_w = mask_preds.shape[-2:] + gt_masks = F.interpolate( + gt_masks.unsqueeze(0), (mask_h, mask_w), + mode='bilinear', + align_corners=False).squeeze(0) + gt_masks = gt_masks.gt(0.5).float() + pos_mask_targets = gt_masks[pos_assigned_gt_inds] + + return (mask_preds, pos_mask_targets, segm_targets, num_pos, + gt_bboxes_for_reweight) + + def crop_mask_preds(self, mask_preds: List[Tensor], + batch_img_metas: List[dict], + positive_infos: InstanceList) -> list: + """Crop predicted masks by zeroing out everything not in the predicted + bbox. + + Args: + mask_preds (list[Tensor]): Predicted prototypes with shape + (num_classes, H, W). + batch_img_metas (list[dict]): Meta information of multiple images. + positive_infos (List[:obj:``InstanceData``]): Positive + information that calculate from detect head. + + Returns: + list: The cropped masks. + """ + croped_mask_preds = [] + for img_meta, mask_preds, cur_info in zip(batch_img_metas, mask_preds, + positive_infos): + bboxes_for_cropping = copy.deepcopy(cur_info.bboxes) + h, w = img_meta['img_shape'][:2] + bboxes_for_cropping[:, 0::2] /= w + bboxes_for_cropping[:, 1::2] /= h + mask_preds = self.crop_single(mask_preds, bboxes_for_cropping) + mask_preds = mask_preds.permute(2, 0, 1).contiguous() + croped_mask_preds.append(mask_preds) + return croped_mask_preds + + def crop_single(self, + masks: Tensor, + boxes: Tensor, + padding: int = 1) -> Tensor: + """Crop single predicted masks by zeroing out everything not in the + predicted bbox. + + Args: + masks (Tensor): Predicted prototypes, has shape [H, W, N]. + boxes (Tensor): Bbox coords in relative point form with + shape [N, 4]. + padding (int): Image padding size. + + Return: + Tensor: The cropped masks. + """ + h, w, n = masks.size() + x1, x2 = self.sanitize_coordinates( + boxes[:, 0], boxes[:, 2], w, padding, cast=False) + y1, y2 = self.sanitize_coordinates( + boxes[:, 1], boxes[:, 3], h, padding, cast=False) + + rows = torch.arange( + w, device=masks.device, dtype=x1.dtype).view(1, -1, + 1).expand(h, w, n) + cols = torch.arange( + h, device=masks.device, dtype=x1.dtype).view(-1, 1, + 1).expand(h, w, n) + + masks_left = rows >= x1.view(1, 1, -1) + masks_right = rows < x2.view(1, 1, -1) + masks_up = cols >= y1.view(1, 1, -1) + masks_down = cols < y2.view(1, 1, -1) + + crop_mask = masks_left * masks_right * masks_up * masks_down + + return masks * crop_mask.float() + + def sanitize_coordinates(self, + x1: Tensor, + x2: Tensor, + img_size: int, + padding: int = 0, + cast: bool = True) -> tuple: + """Sanitizes the input coordinates so that x1 < x2, x1 != x2, x1 >= 0, + and x2 <= image_size. Also converts from relative to absolute + coordinates and casts the results to long tensors. + + Warning: this does things in-place behind the scenes so + copy if necessary. + + Args: + x1 (Tensor): shape (N, ). + x2 (Tensor): shape (N, ). + img_size (int): Size of the input image. + padding (int): x1 >= padding, x2 <= image_size-padding. + cast (bool): If cast is false, the result won't be cast to longs. + + Returns: + tuple: + + - x1 (Tensor): Sanitized _x1. + - x2 (Tensor): Sanitized _x2. + """ + x1 = x1 * img_size + x2 = x2 * img_size + if cast: + x1 = x1.long() + x2 = x2.long() + x1 = torch.min(x1, x2) + x2 = torch.max(x1, x2) + x1 = torch.clamp(x1 - padding, min=0) + x2 = torch.clamp(x2 + padding, max=img_size) + return x1, x2 + + def predict_by_feat(self, + mask_preds: List[Tensor], + segm_preds: Tensor, + results_list: InstanceList, + batch_img_metas: List[dict], + rescale: bool = True, + **kwargs) -> InstanceList: + """Transform a batch of output features extracted from the head into + mask results. + + Args: + mask_preds (list[Tensor]): Predicted prototypes with shape + (num_classes, H, W). + results_list (List[:obj:``InstanceData``]): BBoxHead results. + batch_img_metas (list[dict]): Meta information of all images. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Processed results of multiple + images.Each :obj:`InstanceData` usually contains + following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + assert len(mask_preds) == len(results_list) == len(batch_img_metas) + + croped_mask_pred = self.crop_mask_preds(mask_preds, batch_img_metas, + results_list) + + for img_id in range(len(batch_img_metas)): + img_meta = batch_img_metas[img_id] + results = results_list[img_id] + bboxes = results.bboxes + mask_preds = croped_mask_pred[img_id] + if bboxes.shape[0] == 0 or mask_preds.shape[0] == 0: + results_list[img_id] = empty_instances( + [img_meta], + bboxes.device, + task_type='mask', + instance_results=[results])[0] + else: + im_mask = self._predict_by_feat_single( + mask_preds=croped_mask_pred[img_id], + bboxes=bboxes, + img_meta=img_meta, + rescale=rescale) + results.masks = im_mask + return results_list + + def _predict_by_feat_single(self, + mask_preds: Tensor, + bboxes: Tensor, + img_meta: dict, + rescale: bool, + cfg: OptConfigType = None): + """Transform a single image's features extracted from the head into + mask results. + + Args: + mask_preds (Tensor): Predicted prototypes, has shape [H, W, N]. + bboxes (Tensor): Bbox coords in relative point form with + shape [N, 4]. + img_meta (dict): Meta information of each image, e.g., + image size, scaling factor, etc. + rescale (bool): If rescale is False, then returned masks will + fit the scale of imgs[0]. + cfg (dict, optional): Config used in test phase. + Defaults to None. + + Returns: + :obj:`InstanceData`: Processed results of single image. + it usually contains following keys. + + - scores (Tensor): Classification scores, has shape + (num_instance,). + - labels (Tensor): Has shape (num_instances,). + - masks (Tensor): Processed mask results, has + shape (num_instances, h, w). + """ + cfg = self.test_cfg if cfg is None else cfg + scale_factor = bboxes.new_tensor(img_meta['scale_factor']).repeat( + (1, 2)) + img_h, img_w = img_meta['ori_shape'][:2] + if rescale: # in-placed rescale the bboxes + scale_factor = bboxes.new_tensor(img_meta['scale_factor']).repeat( + (1, 2)) + bboxes /= scale_factor + else: + w_scale, h_scale = scale_factor[0, 0], scale_factor[0, 1] + img_h = np.round(img_h * h_scale.item()).astype(np.int32) + img_w = np.round(img_w * w_scale.item()).astype(np.int32) + + masks = F.interpolate( + mask_preds.unsqueeze(0), (img_h, img_w), + mode='bilinear', + align_corners=False).squeeze(0) > cfg.mask_thr + + if cfg.mask_thr_binary < 0: + # for visualization and debugging + masks = (masks * 255).to(dtype=torch.uint8) + + return masks + + +class SegmentationModule(BaseModule): + """YOLACT segmentation branch used in `_ + + In mmdet v2.x `segm_loss` is calculated in YOLACTSegmHead, while in + mmdet v3.x `SegmentationModule` is used to obtain the predicted semantic + segmentation map and `segm_loss` is calculated in YOLACTProtonet. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__( + self, + num_classes: int, + in_channels: int = 256, + init_cfg: ConfigType = dict( + type='Xavier', + distribution='uniform', + override=dict(name='segm_conv')) + ) -> None: + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.num_classes = num_classes + self._init_layers() + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.segm_conv = nn.Conv2d( + self.in_channels, self.num_classes, kernel_size=1) + + def forward(self, x: Tensor) -> Tensor: + """Forward feature from the upstream network. + + Args: + x (Tensor): Feature from the upstream network, which is + a 4D-tensor. + + Returns: + Tensor: Predicted semantic segmentation map with shape + (N, num_classes, H, W). + """ + return self.segm_conv(x) + + +class InterpolateModule(BaseModule): + """This is a module version of F.interpolate. + + Any arguments you give it just get passed along for the ride. + """ + + def __init__(self, *args, init_cfg=None, **kwargs) -> None: + super().__init__(init_cfg=init_cfg) + self.args = args + self.kwargs = kwargs + + def forward(self, x: Tensor) -> Tensor: + """Forward features from the upstream network. + + Args: + x (Tensor): Feature from the upstream network, which is + a 4D-tensor. + + Returns: + Tensor: A 4D-tensor feature map. + """ + return F.interpolate(x, *self.args, **self.kwargs) diff --git a/mmdetection/mmdet/models/dense_heads/yolo_head.py b/mmdetection/mmdet/models/dense_heads/yolo_head.py new file mode 100644 index 00000000..0f63afbb --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/yolo_head.py @@ -0,0 +1,527 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Copyright (c) 2019 Western Digital Corporation or its affiliates. + +import copy +import warnings +from typing import List, Optional, Sequence, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, is_norm +from mmengine.model import bias_init_with_prob, constant_init, normal_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList) +from ..task_modules.samplers import PseudoSampler +from ..utils import filter_scores_and_topk, images_to_levels, multi_apply +from .base_dense_head import BaseDenseHead + + +@MODELS.register_module() +class YOLOV3Head(BaseDenseHead): + """YOLOV3Head Paper link: https://arxiv.org/abs/1804.02767. + + Args: + num_classes (int): The number of object classes (w/o background) + in_channels (Sequence[int]): Number of input channels per scale. + out_channels (Sequence[int]): The number of output channels per scale + before the final 1x1 layer. Default: (1024, 512, 256). + anchor_generator (:obj:`ConfigDict` or dict): Config dict for anchor + generator. + bbox_coder (:obj:`ConfigDict` or dict): Config of bounding box coder. + featmap_strides (Sequence[int]): The stride of each scale. + Should be in descending order. Defaults to (32, 16, 8). + one_hot_smoother (float): Set a non-zero value to enable label-smooth + Defaults to 0. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict): Dictionary to construct and + config norm layer. Defaults to dict(type='BN', requires_grad=True). + act_cfg (:obj:`ConfigDict` or dict): Config dict for activation layer. + Defaults to dict(type='LeakyReLU', negative_slope=0.1). + loss_cls (:obj:`ConfigDict` or dict): Config of classification loss. + loss_conf (:obj:`ConfigDict` or dict): Config of confidence loss. + loss_xy (:obj:`ConfigDict` or dict): Config of xy coordinate loss. + loss_wh (:obj:`ConfigDict` or dict): Config of wh coordinate loss. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config of + YOLOV3 head. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + YOLOV3 head. Defaults to None. + """ + + def __init__(self, + num_classes: int, + in_channels: Sequence[int], + out_channels: Sequence[int] = (1024, 512, 256), + anchor_generator: ConfigType = dict( + type='YOLOAnchorGenerator', + base_sizes=[[(116, 90), (156, 198), (373, 326)], + [(30, 61), (62, 45), (59, 119)], + [(10, 13), (16, 30), (33, 23)]], + strides=[32, 16, 8]), + bbox_coder: ConfigType = dict(type='YOLOBBoxCoder'), + featmap_strides: Sequence[int] = (32, 16, 8), + one_hot_smoother: float = 0., + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN', requires_grad=True), + act_cfg: ConfigType = dict( + type='LeakyReLU', negative_slope=0.1), + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0), + loss_conf: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0), + loss_xy: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0), + loss_wh: ConfigType = dict(type='MSELoss', loss_weight=1.0), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None) -> None: + super().__init__(init_cfg=None) + # Check params + assert (len(in_channels) == len(out_channels) == len(featmap_strides)) + + self.num_classes = num_classes + self.in_channels = in_channels + self.out_channels = out_channels + self.featmap_strides = featmap_strides + self.train_cfg = train_cfg + self.test_cfg = test_cfg + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + if train_cfg.get('sampler', None) is not None: + self.sampler = TASK_UTILS.build( + self.train_cfg['sampler'], context=self) + else: + self.sampler = PseudoSampler() + + self.one_hot_smoother = one_hot_smoother + + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + self.bbox_coder = TASK_UTILS.build(bbox_coder) + + self.prior_generator = TASK_UTILS.build(anchor_generator) + + self.loss_cls = MODELS.build(loss_cls) + self.loss_conf = MODELS.build(loss_conf) + self.loss_xy = MODELS.build(loss_xy) + self.loss_wh = MODELS.build(loss_wh) + + self.num_base_priors = self.prior_generator.num_base_priors[0] + assert len( + self.prior_generator.num_base_priors) == len(featmap_strides) + self._init_layers() + + @property + def num_levels(self) -> int: + """int: number of feature map levels""" + return len(self.featmap_strides) + + @property + def num_attrib(self) -> int: + """int: number of attributes in pred_map, bboxes (4) + + objectness (1) + num_classes""" + + return 5 + self.num_classes + + def _init_layers(self) -> None: + """initialize conv layers in YOLOv3 head.""" + self.convs_bridge = nn.ModuleList() + self.convs_pred = nn.ModuleList() + for i in range(self.num_levels): + conv_bridge = ConvModule( + self.in_channels[i], + self.out_channels[i], + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + conv_pred = nn.Conv2d(self.out_channels[i], + self.num_base_priors * self.num_attrib, 1) + + self.convs_bridge.append(conv_bridge) + self.convs_pred.append(conv_pred) + + def init_weights(self) -> None: + """initialize weights.""" + for m in self.modules(): + if isinstance(m, nn.Conv2d): + normal_init(m, mean=0, std=0.01) + if is_norm(m): + constant_init(m, 1) + + # Use prior in model initialization to improve stability + for conv_pred, stride in zip(self.convs_pred, self.featmap_strides): + bias = conv_pred.bias.reshape(self.num_base_priors, -1) + # init objectness with prior of 8 objects per feature map + # refer to https://github.com/ultralytics/yolov3 + nn.init.constant_(bias.data[:, 4], + bias_init_with_prob(8 / (608 / stride)**2)) + nn.init.constant_(bias.data[:, 5:], bias_init_with_prob(0.01)) + + def forward(self, x: Tuple[Tensor, ...]) -> tuple: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple[Tensor]: A tuple of multi-level predication map, each is a + 4D-tensor of shape (batch_size, 5+num_classes, height, width). + """ + + assert len(x) == self.num_levels + pred_maps = [] + for i in range(self.num_levels): + feat = x[i] + feat = self.convs_bridge[i](feat) + pred_map = self.convs_pred[i](feat) + pred_maps.append(pred_map) + + return tuple(pred_maps), + + def predict_by_feat(self, + pred_maps: Sequence[Tensor], + batch_img_metas: Optional[List[dict]], + cfg: OptConfigType = None, + rescale: bool = False, + with_nms: bool = True) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. It has been accelerated since PR #5991. + + Args: + pred_maps (Sequence[Tensor]): Raw predictions for a batch of + images. + batch_img_metas (list[dict], Optional): Batch image meta info. + Defaults to None. + cfg (:obj:`ConfigDict` or dict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(pred_maps) == self.num_levels + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + + num_imgs = len(batch_img_metas) + featmap_sizes = [pred_map.shape[-2:] for pred_map in pred_maps] + + mlvl_anchors = self.prior_generator.grid_priors( + featmap_sizes, device=pred_maps[0].device) + flatten_preds = [] + flatten_strides = [] + for pred, stride in zip(pred_maps, self.featmap_strides): + pred = pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, + self.num_attrib) + pred[..., :2].sigmoid_() + flatten_preds.append(pred) + flatten_strides.append( + pred.new_tensor(stride).expand(pred.size(1))) + + flatten_preds = torch.cat(flatten_preds, dim=1) + flatten_bbox_preds = flatten_preds[..., :4] + flatten_objectness = flatten_preds[..., 4].sigmoid() + flatten_cls_scores = flatten_preds[..., 5:].sigmoid() + flatten_anchors = torch.cat(mlvl_anchors) + flatten_strides = torch.cat(flatten_strides) + flatten_bboxes = self.bbox_coder.decode(flatten_anchors, + flatten_bbox_preds, + flatten_strides.unsqueeze(-1)) + results_list = [] + for (bboxes, scores, objectness, + img_meta) in zip(flatten_bboxes, flatten_cls_scores, + flatten_objectness, batch_img_metas): + # Filtering out all predictions with conf < conf_thr + conf_thr = cfg.get('conf_thr', -1) + if conf_thr > 0: + conf_inds = objectness >= conf_thr + bboxes = bboxes[conf_inds, :] + scores = scores[conf_inds, :] + objectness = objectness[conf_inds] + + score_thr = cfg.get('score_thr', 0) + nms_pre = cfg.get('nms_pre', -1) + scores, labels, keep_idxs, _ = filter_scores_and_topk( + scores, score_thr, nms_pre) + + results = InstanceData( + scores=scores, + labels=labels, + bboxes=bboxes[keep_idxs], + score_factors=objectness[keep_idxs], + ) + results = self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + results_list.append(results) + return results_list + + def loss_by_feat( + self, + pred_maps: Sequence[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + pred_maps (list[Tensor]): Prediction map for each scale level, + shape (N, num_anchors * num_attrib, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: A dictionary of loss components. + """ + num_imgs = len(batch_img_metas) + device = pred_maps[0][0].device + + featmap_sizes = [ + pred_maps[i].shape[-2:] for i in range(self.num_levels) + ] + mlvl_anchors = self.prior_generator.grid_priors( + featmap_sizes, device=device) + anchor_list = [mlvl_anchors for _ in range(num_imgs)] + + responsible_flag_list = [] + for img_id in range(num_imgs): + responsible_flag_list.append( + self.responsible_flags(featmap_sizes, + batch_gt_instances[img_id].bboxes, + device)) + + target_maps_list, neg_maps_list = self.get_targets( + anchor_list, responsible_flag_list, batch_gt_instances) + + losses_cls, losses_conf, losses_xy, losses_wh = multi_apply( + self.loss_by_feat_single, pred_maps, target_maps_list, + neg_maps_list) + + return dict( + loss_cls=losses_cls, + loss_conf=losses_conf, + loss_xy=losses_xy, + loss_wh=losses_wh) + + def loss_by_feat_single(self, pred_map: Tensor, target_map: Tensor, + neg_map: Tensor) -> tuple: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + pred_map (Tensor): Raw predictions for a single level. + target_map (Tensor): The Ground-Truth target for a single level. + neg_map (Tensor): The negative masks for a single level. + + Returns: + tuple: + loss_cls (Tensor): Classification loss. + loss_conf (Tensor): Confidence loss. + loss_xy (Tensor): Regression loss of x, y coordinate. + loss_wh (Tensor): Regression loss of w, h coordinate. + """ + + num_imgs = len(pred_map) + pred_map = pred_map.permute(0, 2, 3, + 1).reshape(num_imgs, -1, self.num_attrib) + neg_mask = neg_map.float() + pos_mask = target_map[..., 4] + pos_and_neg_mask = neg_mask + pos_mask + pos_mask = pos_mask.unsqueeze(dim=-1) + if torch.max(pos_and_neg_mask) > 1.: + warnings.warn('There is overlap between pos and neg sample.') + pos_and_neg_mask = pos_and_neg_mask.clamp(min=0., max=1.) + + pred_xy = pred_map[..., :2] + pred_wh = pred_map[..., 2:4] + pred_conf = pred_map[..., 4] + pred_label = pred_map[..., 5:] + + target_xy = target_map[..., :2] + target_wh = target_map[..., 2:4] + target_conf = target_map[..., 4] + target_label = target_map[..., 5:] + + loss_cls = self.loss_cls(pred_label, target_label, weight=pos_mask) + loss_conf = self.loss_conf( + pred_conf, target_conf, weight=pos_and_neg_mask) + loss_xy = self.loss_xy(pred_xy, target_xy, weight=pos_mask) + loss_wh = self.loss_wh(pred_wh, target_wh, weight=pos_mask) + + return loss_cls, loss_conf, loss_xy, loss_wh + + def get_targets(self, anchor_list: List[List[Tensor]], + responsible_flag_list: List[List[Tensor]], + batch_gt_instances: List[InstanceData]) -> tuple: + """Compute target maps for anchors in multiple images. + + Args: + anchor_list (list[list[Tensor]]): Multi level anchors of each + image. The outer list indicates images, and the inner list + corresponds to feature levels of the image. Each element of + the inner list is a tensor of shape (num_total_anchors, 4). + responsible_flag_list (list[list[Tensor]]): Multi level responsible + flags of each image. Each element is a tensor of shape + (num_total_anchors, ) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + + Returns: + tuple: Usually returns a tuple containing learning targets. + - target_map_list (list[Tensor]): Target map of each level. + - neg_map_list (list[Tensor]): Negative map of each level. + """ + num_imgs = len(anchor_list) + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + + results = multi_apply(self._get_targets_single, anchor_list, + responsible_flag_list, batch_gt_instances) + + all_target_maps, all_neg_maps = results + assert num_imgs == len(all_target_maps) == len(all_neg_maps) + target_maps_list = images_to_levels(all_target_maps, num_level_anchors) + neg_maps_list = images_to_levels(all_neg_maps, num_level_anchors) + + return target_maps_list, neg_maps_list + + def _get_targets_single(self, anchors: List[Tensor], + responsible_flags: List[Tensor], + gt_instances: InstanceData) -> tuple: + """Generate matching bounding box prior and converted GT. + + Args: + anchors (List[Tensor]): Multi-level anchors of the image. + responsible_flags (List[Tensor]): Multi-level responsible flags of + anchors + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + + Returns: + tuple: + target_map (Tensor): Predication target map of each + scale level, shape (num_total_anchors, + 5+num_classes) + neg_map (Tensor): Negative map of each scale level, + shape (num_total_anchors,) + """ + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + anchor_strides = [] + for i in range(len(anchors)): + anchor_strides.append( + torch.tensor(self.featmap_strides[i], + device=gt_bboxes.device).repeat(len(anchors[i]))) + concat_anchors = torch.cat(anchors) + concat_responsible_flags = torch.cat(responsible_flags) + + anchor_strides = torch.cat(anchor_strides) + assert len(anchor_strides) == len(concat_anchors) == \ + len(concat_responsible_flags) + pred_instances = InstanceData( + priors=concat_anchors, responsible_flags=concat_responsible_flags) + + assign_result = self.assigner.assign(pred_instances, gt_instances) + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + target_map = concat_anchors.new_zeros( + concat_anchors.size(0), self.num_attrib) + + target_map[sampling_result.pos_inds, :4] = self.bbox_coder.encode( + sampling_result.pos_priors, sampling_result.pos_gt_bboxes, + anchor_strides[sampling_result.pos_inds]) + + target_map[sampling_result.pos_inds, 4] = 1 + + gt_labels_one_hot = F.one_hot( + gt_labels, num_classes=self.num_classes).float() + if self.one_hot_smoother != 0: # label smooth + gt_labels_one_hot = gt_labels_one_hot * ( + 1 - self.one_hot_smoother + ) + self.one_hot_smoother / self.num_classes + target_map[sampling_result.pos_inds, 5:] = gt_labels_one_hot[ + sampling_result.pos_assigned_gt_inds] + + neg_map = concat_anchors.new_zeros( + concat_anchors.size(0), dtype=torch.uint8) + neg_map[sampling_result.neg_inds] = 1 + + return target_map, neg_map + + def responsible_flags(self, featmap_sizes: List[tuple], gt_bboxes: Tensor, + device: str) -> List[Tensor]: + """Generate responsible anchor flags of grid cells in multiple scales. + + Args: + featmap_sizes (List[tuple]): List of feature map sizes in multiple + feature levels. + gt_bboxes (Tensor): Ground truth boxes, shape (n, 4). + device (str): Device where the anchors will be put on. + + Return: + List[Tensor]: responsible flags of anchors in multiple level + """ + assert self.num_levels == len(featmap_sizes) + multi_level_responsible_flags = [] + for i in range(self.num_levels): + anchor_stride = self.prior_generator.strides[i] + feat_h, feat_w = featmap_sizes[i] + gt_cx = ((gt_bboxes[:, 0] + gt_bboxes[:, 2]) * 0.5).to(device) + gt_cy = ((gt_bboxes[:, 1] + gt_bboxes[:, 3]) * 0.5).to(device) + gt_grid_x = torch.floor(gt_cx / anchor_stride[0]).long() + gt_grid_y = torch.floor(gt_cy / anchor_stride[1]).long() + # row major indexing + gt_bboxes_grid_idx = gt_grid_y * feat_w + gt_grid_x + + responsible_grid = torch.zeros( + feat_h * feat_w, dtype=torch.uint8, device=device) + responsible_grid[gt_bboxes_grid_idx] = 1 + + responsible_grid = responsible_grid[:, None].expand( + responsible_grid.size(0), + self.prior_generator.num_base_priors[i]).contiguous().view(-1) + + multi_level_responsible_flags.append(responsible_grid) + return multi_level_responsible_flags diff --git a/mmdetection/mmdet/models/dense_heads/yolof_head.py b/mmdetection/mmdet/models/dense_heads/yolof_head.py new file mode 100644 index 00000000..b5e5e6b7 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/yolof_head.py @@ -0,0 +1,399 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, is_norm +from mmengine.model import bias_init_with_prob, constant_init, normal_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, InstanceList, OptInstanceList, reduce_mean +from ..task_modules.prior_generators import anchor_inside_flags +from ..utils import levels_to_images, multi_apply, unmap +from .anchor_head import AnchorHead + +INF = 1e8 + + +@MODELS.register_module() +class YOLOFHead(AnchorHead): + """Detection Head of `YOLOF `_ + + Args: + num_classes (int): The number of object classes (w/o background) + in_channels (list[int]): The number of input channels per scale. + cls_num_convs (int): The number of convolutions of cls branch. + Defaults to 2. + reg_num_convs (int): The number of convolutions of reg branch. + Defaults to 4. + norm_cfg (:obj:`ConfigDict` or dict): Config dict for normalization + layer. Defaults to ``dict(type='BN', requires_grad=True)``. + """ + + def __init__(self, + num_classes: int, + in_channels: List[int], + num_cls_convs: int = 2, + num_reg_convs: int = 4, + norm_cfg: ConfigType = dict(type='BN', requires_grad=True), + **kwargs) -> None: + self.num_cls_convs = num_cls_convs + self.num_reg_convs = num_reg_convs + self.norm_cfg = norm_cfg + super().__init__( + num_classes=num_classes, in_channels=in_channels, **kwargs) + + def _init_layers(self) -> None: + cls_subnet = [] + bbox_subnet = [] + for i in range(self.num_cls_convs): + cls_subnet.append( + ConvModule( + self.in_channels, + self.in_channels, + kernel_size=3, + padding=1, + norm_cfg=self.norm_cfg)) + for i in range(self.num_reg_convs): + bbox_subnet.append( + ConvModule( + self.in_channels, + self.in_channels, + kernel_size=3, + padding=1, + norm_cfg=self.norm_cfg)) + self.cls_subnet = nn.Sequential(*cls_subnet) + self.bbox_subnet = nn.Sequential(*bbox_subnet) + self.cls_score = nn.Conv2d( + self.in_channels, + self.num_base_priors * self.num_classes, + kernel_size=3, + stride=1, + padding=1) + self.bbox_pred = nn.Conv2d( + self.in_channels, + self.num_base_priors * 4, + kernel_size=3, + stride=1, + padding=1) + self.object_pred = nn.Conv2d( + self.in_channels, + self.num_base_priors, + kernel_size=3, + stride=1, + padding=1) + + def init_weights(self) -> None: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + normal_init(m, mean=0, std=0.01) + if is_norm(m): + constant_init(m, 1) + + # Use prior in model initialization to improve stability + bias_cls = bias_init_with_prob(0.01) + torch.nn.init.constant_(self.cls_score.bias, bias_cls) + + def forward_single(self, x: Tensor) -> Tuple[Tensor, Tensor]: + """Forward feature of a single scale level. + + Args: + x (Tensor): Features of a single scale level. + + Returns: + tuple: + normalized_cls_score (Tensor): Normalized Cls scores for a \ + single scale level, the channels number is \ + num_base_priors * num_classes. + bbox_reg (Tensor): Box energies / deltas for a single scale \ + level, the channels number is num_base_priors * 4. + """ + cls_score = self.cls_score(self.cls_subnet(x)) + N, _, H, W = cls_score.shape + cls_score = cls_score.view(N, -1, self.num_classes, H, W) + + reg_feat = self.bbox_subnet(x) + bbox_reg = self.bbox_pred(reg_feat) + objectness = self.object_pred(reg_feat) + + # implicit objectness + objectness = objectness.view(N, -1, 1, H, W) + normalized_cls_score = cls_score + objectness - torch.log( + 1. + torch.clamp(cls_score.exp(), max=INF) + + torch.clamp(objectness.exp(), max=INF)) + normalized_cls_score = normalized_cls_score.view(N, -1, H, W) + return normalized_cls_score, bbox_reg + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + has shape (N, num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: A dictionary of loss components. + """ + assert len(cls_scores) == 1 + assert self.prior_generator.num_levels == 1 + + device = cls_scores[0].device + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + # The output level is always 1 + anchor_list = [anchors[0] for anchors in anchor_list] + valid_flag_list = [valid_flags[0] for valid_flags in valid_flag_list] + + cls_scores_list = levels_to_images(cls_scores) + bbox_preds_list = levels_to_images(bbox_preds) + + cls_reg_targets = self.get_targets( + cls_scores_list, + bbox_preds_list, + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + if cls_reg_targets is None: + return None + (batch_labels, batch_label_weights, avg_factor, batch_bbox_weights, + batch_pos_predicted_boxes, batch_target_boxes) = cls_reg_targets + + flatten_labels = batch_labels.reshape(-1) + batch_label_weights = batch_label_weights.reshape(-1) + cls_score = cls_scores[0].permute(0, 2, 3, + 1).reshape(-1, self.cls_out_channels) + + avg_factor = reduce_mean( + torch.tensor(avg_factor, dtype=torch.float, device=device)).item() + + # classification loss + loss_cls = self.loss_cls( + cls_score, + flatten_labels, + batch_label_weights, + avg_factor=avg_factor) + + # regression loss + if batch_pos_predicted_boxes.shape[0] == 0: + # no pos sample + loss_bbox = batch_pos_predicted_boxes.sum() * 0 + else: + loss_bbox = self.loss_bbox( + batch_pos_predicted_boxes, + batch_target_boxes, + batch_bbox_weights.float(), + avg_factor=avg_factor) + + return dict(loss_cls=loss_cls, loss_bbox=loss_bbox) + + def get_targets(self, + cls_scores_list: List[Tensor], + bbox_preds_list: List[Tensor], + anchor_list: List[Tensor], + valid_flag_list: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs: bool = True): + """Compute regression and classification targets for anchors in + multiple images. + + Args: + cls_scores_list (list[Tensor]): Classification scores of + each image. each is a 4D-tensor, the shape is + (h * w, num_anchors * num_classes). + bbox_preds_list (list[Tensor]): Bbox preds of each image. + each is a 4D-tensor, the shape is (h * w, num_anchors * 4). + anchor_list (list[Tensor]): Anchors of each image. Each element of + is a tensor of shape (h * w * num_anchors, 4). + valid_flag_list (list[Tensor]): Valid flags of each image. Each + element of is a tensor of shape (h * w * num_anchors, ) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. + + Returns: + tuple: Usually returns a tuple containing learning targets. + + - batch_labels (Tensor): Label of all images. Each element \ + of is a tensor of shape (batch, h * w * num_anchors) + - batch_label_weights (Tensor): Label weights of all images \ + of is a tensor of shape (batch, h * w * num_anchors) + - num_total_pos (int): Number of positive samples in all \ + images. + - num_total_neg (int): Number of negative samples in all \ + images. + additional_returns: This function enables user-defined returns from + `self._get_targets_single`. These returns are currently refined + to properties at each feature map (i.e. having HxW dimension). + The results will be concatenated after the end + """ + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + results = multi_apply( + self._get_targets_single, + bbox_preds_list, + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + (all_labels, all_label_weights, pos_inds, neg_inds, + sampling_results_list) = results[:5] + # Get `avg_factor` of all images, which calculate in `SamplingResult`. + # When using sampling method, avg_factor is usually the sum of + # positive and negative priors. When using `PseudoSampler`, + # `avg_factor` is usually equal to the number of positive priors. + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + rest_results = list(results[5:]) # user-added return values + + batch_labels = torch.stack(all_labels, 0) + batch_label_weights = torch.stack(all_label_weights, 0) + + res = (batch_labels, batch_label_weights, avg_factor) + for i, rests in enumerate(rest_results): # user-added return values + rest_results[i] = torch.cat(rests, 0) + + return res + tuple(rest_results) + + def _get_targets_single(self, + bbox_preds: Tensor, + flat_anchors: Tensor, + valid_flags: Tensor, + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None, + unmap_outputs: bool = True) -> tuple: + """Compute regression and classification targets for anchors in a + single image. + + Args: + bbox_preds (Tensor): Bbox prediction of the image, which + shape is (h * w ,4) + flat_anchors (Tensor): Anchors of the image, which shape is + (h * w * num_anchors ,4) + valid_flags (Tensor): Valid flags of the image, which shape is + (h * w * num_anchors,). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + unmap_outputs (bool): Whether to map outputs back to the original + set of anchors. + + Returns: + tuple: + labels (Tensor): Labels of image, which shape is + (h * w * num_anchors, ). + label_weights (Tensor): Label weights of image, which shape is + (h * w * num_anchors, ). + pos_inds (Tensor): Pos index of image. + neg_inds (Tensor): Neg index of image. + sampling_result (obj:`SamplingResult`): Sampling result. + pos_bbox_weights (Tensor): The Weight of using to calculate + the bbox branch loss, which shape is (num, ). + pos_predicted_boxes (Tensor): boxes predicted value of + using to calculate the bbox branch loss, which shape is + (num, 4). + pos_target_boxes (Tensor): boxes target value of + using to calculate the bbox branch loss, which shape is + (num, 4). + """ + inside_flags = anchor_inside_flags(flat_anchors, valid_flags, + img_meta['img_shape'][:2], + self.train_cfg['allowed_border']) + if not inside_flags.any(): + raise ValueError( + 'There is no valid anchor inside the image boundary. Please ' + 'check the image size and anchor sizes, or set ' + '``allowed_border`` to -1 to skip the condition.') + + # assign gt and sample anchors + anchors = flat_anchors[inside_flags, :] + bbox_preds = bbox_preds.reshape(-1, 4) + bbox_preds = bbox_preds[inside_flags, :] + + # decoded bbox + decoder_bbox_preds = self.bbox_coder.decode(anchors, bbox_preds) + pred_instances = InstanceData( + priors=anchors, decoder_priors=decoder_bbox_preds) + assign_result = self.assigner.assign(pred_instances, gt_instances, + gt_instances_ignore) + + pos_bbox_weights = assign_result.get_extra_property('pos_idx') + pos_predicted_boxes = assign_result.get_extra_property( + 'pos_predicted_boxes') + pos_target_boxes = assign_result.get_extra_property('target_boxes') + + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + num_valid_anchors = anchors.shape[0] + labels = anchors.new_full((num_valid_anchors, ), + self.num_classes, + dtype=torch.long) + label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + labels[pos_inds] = sampling_result.pos_gt_labels + if self.train_cfg['pos_weight'] <= 0: + label_weights[pos_inds] = 1.0 + else: + label_weights[pos_inds] = self.train_cfg['pos_weight'] + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + # map up to original set of anchors + if unmap_outputs: + num_total_anchors = flat_anchors.size(0) + labels = unmap( + labels, num_total_anchors, inside_flags, + fill=self.num_classes) # fill bg label + label_weights = unmap(label_weights, num_total_anchors, + inside_flags) + + return (labels, label_weights, pos_inds, neg_inds, sampling_result, + pos_bbox_weights, pos_predicted_boxes, pos_target_boxes) diff --git a/mmdetection/mmdet/models/dense_heads/yolox_head.py b/mmdetection/mmdet/models/dense_heads/yolox_head.py new file mode 100644 index 00000000..00fe1e42 --- /dev/null +++ b/mmdetection/mmdet/models/dense_heads/yolox_head.py @@ -0,0 +1,618 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import List, Optional, Sequence, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmcv.ops.nms import batched_nms +from mmengine.config import ConfigDict +from mmengine.model import bias_init_with_prob +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures.bbox import bbox_xyxy_to_cxcywh +from mmdet.utils import (ConfigType, OptConfigType, OptInstanceList, + OptMultiConfig, reduce_mean) +from ..task_modules.prior_generators import MlvlPointGenerator +from ..task_modules.samplers import PseudoSampler +from ..utils import multi_apply +from .base_dense_head import BaseDenseHead + + +@MODELS.register_module() +class YOLOXHead(BaseDenseHead): + """YOLOXHead head used in `YOLOX `_. + + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channels in the input feature map. + feat_channels (int): Number of hidden channels in stacking convs. + Defaults to 256 + stacked_convs (int): Number of stacking convs of the head. + Defaults to (8, 16, 32). + strides (Sequence[int]): Downsample factor of each feature map. + Defaults to None. + use_depthwise (bool): Whether to depthwise separable convolution in + blocks. Defaults to False. + dcn_on_last_conv (bool): If true, use dcn in the last layer of + towers. Defaults to False. + conv_bias (bool or str): If specified as `auto`, it will be decided by + the norm_cfg. Bias of conv will be set as True if `norm_cfg` is + None, otherwise False. Defaults to "auto". + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict): Config dict for normalization + layer. Defaults to dict(type='BN', momentum=0.03, eps=0.001). + act_cfg (:obj:`ConfigDict` or dict): Config dict for activation layer. + Defaults to None. + loss_cls (:obj:`ConfigDict` or dict): Config of classification loss. + loss_bbox (:obj:`ConfigDict` or dict): Config of localization loss. + loss_obj (:obj:`ConfigDict` or dict): Config of objectness loss. + loss_l1 (:obj:`ConfigDict` or dict): Config of L1 loss. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config of + anchor head. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + anchor head. Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__( + self, + num_classes: int, + in_channels: int, + feat_channels: int = 256, + stacked_convs: int = 2, + strides: Sequence[int] = (8, 16, 32), + use_depthwise: bool = False, + dcn_on_last_conv: bool = False, + conv_bias: Union[bool, str] = 'auto', + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN', momentum=0.03, eps=0.001), + act_cfg: ConfigType = dict(type='Swish'), + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='sum', + loss_weight=1.0), + loss_bbox: ConfigType = dict( + type='IoULoss', + mode='square', + eps=1e-16, + reduction='sum', + loss_weight=5.0), + loss_obj: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='sum', + loss_weight=1.0), + loss_l1: ConfigType = dict( + type='L1Loss', reduction='sum', loss_weight=1.0), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = dict( + type='Kaiming', + layer='Conv2d', + a=math.sqrt(5), + distribution='uniform', + mode='fan_in', + nonlinearity='leaky_relu') + ) -> None: + + super().__init__(init_cfg=init_cfg) + self.num_classes = num_classes + self.cls_out_channels = num_classes + self.in_channels = in_channels + self.feat_channels = feat_channels + self.stacked_convs = stacked_convs + self.strides = strides + self.use_depthwise = use_depthwise + self.dcn_on_last_conv = dcn_on_last_conv + assert conv_bias == 'auto' or isinstance(conv_bias, bool) + self.conv_bias = conv_bias + self.use_sigmoid_cls = True + + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + self.loss_cls: nn.Module = MODELS.build(loss_cls) + self.loss_bbox: nn.Module = MODELS.build(loss_bbox) + self.loss_obj: nn.Module = MODELS.build(loss_obj) + + self.use_l1 = False # This flag will be modified by hooks. + self.loss_l1: nn.Module = MODELS.build(loss_l1) + + self.prior_generator = MlvlPointGenerator(strides, offset=0) + + self.test_cfg = test_cfg + self.train_cfg = train_cfg + + if self.train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + # YOLOX does not support sampling + self.sampler = PseudoSampler() + + self._init_layers() + + def _init_layers(self) -> None: + """Initialize heads for all level feature maps.""" + self.multi_level_cls_convs = nn.ModuleList() + self.multi_level_reg_convs = nn.ModuleList() + self.multi_level_conv_cls = nn.ModuleList() + self.multi_level_conv_reg = nn.ModuleList() + self.multi_level_conv_obj = nn.ModuleList() + for _ in self.strides: + self.multi_level_cls_convs.append(self._build_stacked_convs()) + self.multi_level_reg_convs.append(self._build_stacked_convs()) + conv_cls, conv_reg, conv_obj = self._build_predictor() + self.multi_level_conv_cls.append(conv_cls) + self.multi_level_conv_reg.append(conv_reg) + self.multi_level_conv_obj.append(conv_obj) + + def _build_stacked_convs(self) -> nn.Sequential: + """Initialize conv layers of a single level head.""" + conv = DepthwiseSeparableConvModule \ + if self.use_depthwise else ConvModule + stacked_convs = [] + for i in range(self.stacked_convs): + chn = self.in_channels if i == 0 else self.feat_channels + if self.dcn_on_last_conv and i == self.stacked_convs - 1: + conv_cfg = dict(type='DCNv2') + else: + conv_cfg = self.conv_cfg + stacked_convs.append( + conv( + chn, + self.feat_channels, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + bias=self.conv_bias)) + return nn.Sequential(*stacked_convs) + + def _build_predictor(self) -> Tuple[nn.Module, nn.Module, nn.Module]: + """Initialize predictor layers of a single level head.""" + conv_cls = nn.Conv2d(self.feat_channels, self.cls_out_channels, 1) + conv_reg = nn.Conv2d(self.feat_channels, 4, 1) + conv_obj = nn.Conv2d(self.feat_channels, 1, 1) + return conv_cls, conv_reg, conv_obj + + def init_weights(self) -> None: + """Initialize weights of the head.""" + super(YOLOXHead, self).init_weights() + # Use prior in model initialization to improve stability + bias_init = bias_init_with_prob(0.01) + for conv_cls, conv_obj in zip(self.multi_level_conv_cls, + self.multi_level_conv_obj): + conv_cls.bias.data.fill_(bias_init) + conv_obj.bias.data.fill_(bias_init) + + def forward_single(self, x: Tensor, cls_convs: nn.Module, + reg_convs: nn.Module, conv_cls: nn.Module, + conv_reg: nn.Module, + conv_obj: nn.Module) -> Tuple[Tensor, Tensor, Tensor]: + """Forward feature of a single scale level.""" + + cls_feat = cls_convs(x) + reg_feat = reg_convs(x) + + cls_score = conv_cls(cls_feat) + bbox_pred = conv_reg(reg_feat) + objectness = conv_obj(reg_feat) + + return cls_score, bbox_pred, objectness + + def forward(self, x: Tuple[Tensor]) -> Tuple[List]: + """Forward features from the upstream network. + + Args: + x (Tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + Returns: + Tuple[List]: A tuple of multi-level classification scores, bbox + predictions, and objectnesses. + """ + + return multi_apply(self.forward_single, x, self.multi_level_cls_convs, + self.multi_level_reg_convs, + self.multi_level_conv_cls, + self.multi_level_conv_reg, + self.multi_level_conv_obj) + + def predict_by_feat(self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + objectnesses: Optional[List[Tensor]], + batch_img_metas: Optional[List[dict]] = None, + cfg: Optional[ConfigDict] = None, + rescale: bool = False, + with_nms: bool = True) -> List[InstanceData]: + """Transform a batch of output features extracted by the head into + bbox results. + Args: + cls_scores (list[Tensor]): Classification scores for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for all + scale levels, each is a 4D-tensor, has shape + (batch_size, num_priors * 4, H, W). + objectnesses (list[Tensor], Optional): Score factor for + all scale level, each is a 4D-tensor, has shape + (batch_size, 1, H, W). + batch_img_metas (list[dict], Optional): Batch image meta info. + Defaults to None. + cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + list[:obj:`InstanceData`]: Object detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_scores) == len(bbox_preds) == len(objectnesses) + cfg = self.test_cfg if cfg is None else cfg + + num_imgs = len(batch_img_metas) + featmap_sizes = [cls_score.shape[2:] for cls_score in cls_scores] + mlvl_priors = self.prior_generator.grid_priors( + featmap_sizes, + dtype=cls_scores[0].dtype, + device=cls_scores[0].device, + with_stride=True) + + # flatten cls_scores, bbox_preds and objectness + flatten_cls_scores = [ + cls_score.permute(0, 2, 3, 1).reshape(num_imgs, -1, + self.cls_out_channels) + for cls_score in cls_scores + ] + flatten_bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, 4) + for bbox_pred in bbox_preds + ] + flatten_objectness = [ + objectness.permute(0, 2, 3, 1).reshape(num_imgs, -1) + for objectness in objectnesses + ] + + flatten_cls_scores = torch.cat(flatten_cls_scores, dim=1).sigmoid() + flatten_bbox_preds = torch.cat(flatten_bbox_preds, dim=1) + flatten_objectness = torch.cat(flatten_objectness, dim=1).sigmoid() + flatten_priors = torch.cat(mlvl_priors) + + flatten_bboxes = self._bbox_decode(flatten_priors, flatten_bbox_preds) + + result_list = [] + for img_id, img_meta in enumerate(batch_img_metas): + max_scores, labels = torch.max(flatten_cls_scores[img_id], 1) + valid_mask = flatten_objectness[ + img_id] * max_scores >= cfg.score_thr + results = InstanceData( + bboxes=flatten_bboxes[img_id][valid_mask], + scores=max_scores[valid_mask] * + flatten_objectness[img_id][valid_mask], + labels=labels[valid_mask]) + + result_list.append( + self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta)) + + return result_list + + def _bbox_decode(self, priors: Tensor, bbox_preds: Tensor) -> Tensor: + """Decode regression results (delta_x, delta_x, w, h) to bboxes (tl_x, + tl_y, br_x, br_y). + + Args: + priors (Tensor): Center proiors of an image, has shape + (num_instances, 2). + bbox_preds (Tensor): Box energies / deltas for all instances, + has shape (batch_size, num_instances, 4). + + Returns: + Tensor: Decoded bboxes in (tl_x, tl_y, br_x, br_y) format. Has + shape (batch_size, num_instances, 4). + """ + xys = (bbox_preds[..., :2] * priors[:, 2:]) + priors[:, :2] + whs = bbox_preds[..., 2:].exp() * priors[:, 2:] + + tl_x = (xys[..., 0] - whs[..., 0] / 2) + tl_y = (xys[..., 1] - whs[..., 1] / 2) + br_x = (xys[..., 0] + whs[..., 0] / 2) + br_y = (xys[..., 1] + whs[..., 1] / 2) + + decoded_bboxes = torch.stack([tl_x, tl_y, br_x, br_y], -1) + return decoded_bboxes + + def _bbox_post_process(self, + results: InstanceData, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True, + img_meta: Optional[dict] = None) -> InstanceData: + """bbox post-processing method. + + The boxes would be rescaled to the original image scale and do + the nms operation. Usually `with_nms` is False is used for aug test. + + Args: + results (:obj:`InstaceData`): Detection instance results, + each item has shape (num_bboxes, ). + cfg (mmengine.Config): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Default to False. + with_nms (bool): If True, do nms before return boxes. + Default to True. + img_meta (dict, optional): Image meta info. Defaults to None. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + + if rescale: + assert img_meta.get('scale_factor') is not None + results.bboxes /= results.bboxes.new_tensor( + img_meta['scale_factor']).repeat((1, 2)) + + if with_nms and results.bboxes.numel() > 0: + det_bboxes, keep_idxs = batched_nms(results.bboxes, results.scores, + results.labels, cfg.nms) + results = results[keep_idxs] + # some nms would reweight the score, such as softnms + results.scores = det_bboxes[:, -1] + return results + + def loss_by_feat( + self, + cls_scores: Sequence[Tensor], + bbox_preds: Sequence[Tensor], + objectnesses: Sequence[Tensor], + batch_gt_instances: Sequence[InstanceData], + batch_img_metas: Sequence[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (Sequence[Tensor]): Box scores for each scale level, + each is a 4D-tensor, the channel number is + num_priors * num_classes. + bbox_preds (Sequence[Tensor]): Box energies / deltas for each scale + level, each is a 4D-tensor, the channel number is + num_priors * 4. + objectnesses (Sequence[Tensor]): Score factor for + all scale level, each is a 4D-tensor, has shape + (batch_size, 1, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + Returns: + dict[str, Tensor]: A dictionary of losses. + """ + num_imgs = len(batch_img_metas) + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + + featmap_sizes = [cls_score.shape[2:] for cls_score in cls_scores] + mlvl_priors = self.prior_generator.grid_priors( + featmap_sizes, + dtype=cls_scores[0].dtype, + device=cls_scores[0].device, + with_stride=True) + + flatten_cls_preds = [ + cls_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, + self.cls_out_channels) + for cls_pred in cls_scores + ] + flatten_bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, 4) + for bbox_pred in bbox_preds + ] + flatten_objectness = [ + objectness.permute(0, 2, 3, 1).reshape(num_imgs, -1) + for objectness in objectnesses + ] + + flatten_cls_preds = torch.cat(flatten_cls_preds, dim=1) + flatten_bbox_preds = torch.cat(flatten_bbox_preds, dim=1) + flatten_objectness = torch.cat(flatten_objectness, dim=1) + flatten_priors = torch.cat(mlvl_priors) + flatten_bboxes = self._bbox_decode(flatten_priors, flatten_bbox_preds) + + (pos_masks, cls_targets, obj_targets, bbox_targets, l1_targets, + num_fg_imgs) = multi_apply( + self._get_targets_single, + flatten_priors.unsqueeze(0).repeat(num_imgs, 1, 1), + flatten_cls_preds.detach(), flatten_bboxes.detach(), + flatten_objectness.detach(), batch_gt_instances, batch_img_metas, + batch_gt_instances_ignore) + + # The experimental results show that 'reduce_mean' can improve + # performance on the COCO dataset. + num_pos = torch.tensor( + sum(num_fg_imgs), + dtype=torch.float, + device=flatten_cls_preds.device) + num_total_samples = max(reduce_mean(num_pos), 1.0) + + pos_masks = torch.cat(pos_masks, 0) + cls_targets = torch.cat(cls_targets, 0) + obj_targets = torch.cat(obj_targets, 0) + bbox_targets = torch.cat(bbox_targets, 0) + if self.use_l1: + l1_targets = torch.cat(l1_targets, 0) + + loss_obj = self.loss_obj(flatten_objectness.view(-1, 1), + obj_targets) / num_total_samples + if num_pos > 0: + loss_cls = self.loss_cls( + flatten_cls_preds.view(-1, self.num_classes)[pos_masks], + cls_targets) / num_total_samples + loss_bbox = self.loss_bbox( + flatten_bboxes.view(-1, 4)[pos_masks], + bbox_targets) / num_total_samples + else: + # Avoid cls and reg branch not participating in the gradient + # propagation when there is no ground-truth in the images. + # For more details, please refer to + # https://github.com/open-mmlab/mmdetection/issues/7298 + loss_cls = flatten_cls_preds.sum() * 0 + loss_bbox = flatten_bboxes.sum() * 0 + + loss_dict = dict( + loss_cls=loss_cls, loss_bbox=loss_bbox, loss_obj=loss_obj) + + if self.use_l1: + if num_pos > 0: + loss_l1 = self.loss_l1( + flatten_bbox_preds.view(-1, 4)[pos_masks], + l1_targets) / num_total_samples + else: + # Avoid cls and reg branch not participating in the gradient + # propagation when there is no ground-truth in the images. + # For more details, please refer to + # https://github.com/open-mmlab/mmdetection/issues/7298 + loss_l1 = flatten_bbox_preds.sum() * 0 + loss_dict.update(loss_l1=loss_l1) + + return loss_dict + + @torch.no_grad() + def _get_targets_single( + self, + priors: Tensor, + cls_preds: Tensor, + decoded_bboxes: Tensor, + objectness: Tensor, + gt_instances: InstanceData, + img_meta: dict, + gt_instances_ignore: Optional[InstanceData] = None) -> tuple: + """Compute classification, regression, and objectness targets for + priors in a single image. + + Args: + priors (Tensor): All priors of one image, a 2D-Tensor with shape + [num_priors, 4] in [cx, xy, stride_w, stride_y] format. + cls_preds (Tensor): Classification predictions of one image, + a 2D-Tensor with shape [num_priors, num_classes] + decoded_bboxes (Tensor): Decoded bboxes predictions of one image, + a 2D-Tensor with shape [num_priors, 4] in [tl_x, tl_y, + br_x, br_y] format. + objectness (Tensor): Objectness predictions of one image, + a 1D-Tensor with shape [num_priors] + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for current image. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + Returns: + tuple: + foreground_mask (list[Tensor]): Binary mask of foreground + targets. + cls_target (list[Tensor]): Classification targets of an image. + obj_target (list[Tensor]): Objectness targets of an image. + bbox_target (list[Tensor]): BBox targets of an image. + l1_target (int): BBox L1 targets of an image. + num_pos_per_img (int): Number of positive samples in an image. + """ + + num_priors = priors.size(0) + num_gts = len(gt_instances) + # No target + if num_gts == 0: + cls_target = cls_preds.new_zeros((0, self.num_classes)) + bbox_target = cls_preds.new_zeros((0, 4)) + l1_target = cls_preds.new_zeros((0, 4)) + obj_target = cls_preds.new_zeros((num_priors, 1)) + foreground_mask = cls_preds.new_zeros(num_priors).bool() + return (foreground_mask, cls_target, obj_target, bbox_target, + l1_target, 0) + + # YOLOX uses center priors with 0.5 offset to assign targets, + # but use center priors without offset to regress bboxes. + offset_priors = torch.cat( + [priors[:, :2] + priors[:, 2:] * 0.5, priors[:, 2:]], dim=-1) + + scores = cls_preds.sigmoid() * objectness.unsqueeze(1).sigmoid() + pred_instances = InstanceData( + bboxes=decoded_bboxes, scores=scores.sqrt_(), priors=offset_priors) + assign_result = self.assigner.assign( + pred_instances=pred_instances, + gt_instances=gt_instances, + gt_instances_ignore=gt_instances_ignore) + + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + pos_inds = sampling_result.pos_inds + num_pos_per_img = pos_inds.size(0) + + pos_ious = assign_result.max_overlaps[pos_inds] + # IOU aware classification score + cls_target = F.one_hot(sampling_result.pos_gt_labels, + self.num_classes) * pos_ious.unsqueeze(-1) + obj_target = torch.zeros_like(objectness).unsqueeze(-1) + obj_target[pos_inds] = 1 + bbox_target = sampling_result.pos_gt_bboxes + l1_target = cls_preds.new_zeros((num_pos_per_img, 4)) + if self.use_l1: + l1_target = self._get_l1_target(l1_target, bbox_target, + priors[pos_inds]) + foreground_mask = torch.zeros_like(objectness).to(torch.bool) + foreground_mask[pos_inds] = 1 + return (foreground_mask, cls_target, obj_target, bbox_target, + l1_target, num_pos_per_img) + + def _get_l1_target(self, + l1_target: Tensor, + gt_bboxes: Tensor, + priors: Tensor, + eps: float = 1e-8) -> Tensor: + """Convert gt bboxes to center offset and log width height.""" + gt_cxcywh = bbox_xyxy_to_cxcywh(gt_bboxes) + l1_target[:, :2] = (gt_cxcywh[:, :2] - priors[:, :2]) / priors[:, 2:] + l1_target[:, 2:] = torch.log(gt_cxcywh[:, 2:] / priors[:, 2:] + eps) + return l1_target diff --git a/mmdetection/mmdet/models/detectors/__init__.py b/mmdetection/mmdet/models/detectors/__init__.py new file mode 100644 index 00000000..e5a06d28 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/__init__.py @@ -0,0 +1,75 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .atss import ATSS +from .autoassign import AutoAssign +from .base import BaseDetector +from .base_detr import DetectionTransformer +from .boxinst import BoxInst +from .cascade_rcnn import CascadeRCNN +from .centernet import CenterNet +from .condinst import CondInst +from .conditional_detr import ConditionalDETR +from .cornernet import CornerNet +from .crowddet import CrowdDet +from .d2_wrapper import Detectron2Wrapper +from .dab_detr import DABDETR +from .ddod import DDOD +from .ddq_detr import DDQDETR +from .deformable_detr import DeformableDETR +from .detr import DETR +from .dino import DINO +from .fast_rcnn import FastRCNN +from .faster_rcnn import FasterRCNN +from .fcos import FCOS +from .fovea import FOVEA +from .fsaf import FSAF +from .gfl import GFL +from .glip import GLIP +from .grid_rcnn import GridRCNN +from .grounding_dino import GroundingDINO +from .htc import HybridTaskCascade +from .kd_one_stage import KnowledgeDistillationSingleStageDetector +from .lad import LAD +from .mask2former import Mask2Former +from .mask_rcnn import MaskRCNN +from .mask_scoring_rcnn import MaskScoringRCNN +from .maskformer import MaskFormer +from .nasfcos import NASFCOS +from .paa import PAA +from .panoptic_fpn import PanopticFPN +from .panoptic_two_stage_segmentor import TwoStagePanopticSegmentor +from .point_rend import PointRend +from .queryinst import QueryInst +from .reppoints_detector import RepPointsDetector +from .retinanet import RetinaNet +from .rpn import RPN +from .rtmdet import RTMDet +from .scnet import SCNet +from .semi_base import SemiBaseDetector +from .single_stage import SingleStageDetector +from .soft_teacher import SoftTeacher +from .solo import SOLO +from .solov2 import SOLOv2 +from .sparse_rcnn import SparseRCNN +from .tood import TOOD +from .trident_faster_rcnn import TridentFasterRCNN +from .two_stage import TwoStageDetector +from .vfnet import VFNet +from .yolact import YOLACT +from .yolo import YOLOV3 +from .yolof import YOLOF +from .yolox import YOLOX + +__all__ = [ + 'ATSS', 'BaseDetector', 'SingleStageDetector', 'TwoStageDetector', 'RPN', + 'KnowledgeDistillationSingleStageDetector', 'FastRCNN', 'FasterRCNN', + 'MaskRCNN', 'CascadeRCNN', 'HybridTaskCascade', 'RetinaNet', 'FCOS', + 'GridRCNN', 'MaskScoringRCNN', 'RepPointsDetector', 'FOVEA', 'FSAF', + 'NASFCOS', 'PointRend', 'GFL', 'CornerNet', 'PAA', 'YOLOV3', 'YOLACT', + 'VFNet', 'DETR', 'TridentFasterRCNN', 'SparseRCNN', 'SCNet', 'SOLO', + 'SOLOv2', 'DeformableDETR', 'AutoAssign', 'YOLOF', 'CenterNet', 'YOLOX', + 'TwoStagePanopticSegmentor', 'PanopticFPN', 'QueryInst', 'LAD', 'TOOD', + 'MaskFormer', 'DDOD', 'Mask2Former', 'SemiBaseDetector', 'SoftTeacher', + 'RTMDet', 'Detectron2Wrapper', 'CrowdDet', 'CondInst', 'BoxInst', + 'DetectionTransformer', 'ConditionalDETR', 'DINO', 'DABDETR', 'GLIP', + 'DDQDETR', 'GroundingDINO' +] diff --git a/mmdetection/mmdet/models/detectors/atss.py b/mmdetection/mmdet/models/detectors/atss.py new file mode 100644 index 00000000..0bfcc728 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/atss.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class ATSS(SingleStageDetector): + """Implementation of `ATSS `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of ATSS. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of ATSS. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/autoassign.py b/mmdetection/mmdet/models/detectors/autoassign.py new file mode 100644 index 00000000..a0b3570f --- /dev/null +++ b/mmdetection/mmdet/models/detectors/autoassign.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class AutoAssign(SingleStageDetector): + """Implementation of `AutoAssign: Differentiable Label Assignment for Dense + Object Detection `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone config. + neck (:obj:`ConfigDict` or dict): The neck config. + bbox_head (:obj:`ConfigDict` or dict): The bbox head config. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of AutoAssign. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of AutoAssign. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/base.py b/mmdetection/mmdet/models/detectors/base.py new file mode 100644 index 00000000..1a193b0c --- /dev/null +++ b/mmdetection/mmdet/models/detectors/base.py @@ -0,0 +1,156 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import Dict, List, Tuple, Union + +import torch +from mmengine.model import BaseModel +from torch import Tensor + +from mmdet.structures import DetDataSample, OptSampleList, SampleList +from mmdet.utils import InstanceList, OptConfigType, OptMultiConfig +from ..utils import samplelist_boxtype2tensor + +ForwardResults = Union[Dict[str, torch.Tensor], List[DetDataSample], + Tuple[torch.Tensor], torch.Tensor] + + +class BaseDetector(BaseModel, metaclass=ABCMeta): + """Base class for detectors. + + Args: + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`BaseDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + init_cfg (dict or ConfigDict, optional): the config to control the + initialization. Defaults to None. + """ + + def __init__(self, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + + @property + def with_neck(self) -> bool: + """bool: whether the detector has a neck""" + return hasattr(self, 'neck') and self.neck is not None + + # TODO: these properties need to be carefully handled + # for both single stage & two stage detectors + @property + def with_shared_head(self) -> bool: + """bool: whether the detector has a shared head in the RoI Head""" + return hasattr(self, 'roi_head') and self.roi_head.with_shared_head + + @property + def with_bbox(self) -> bool: + """bool: whether the detector has a bbox head""" + return ((hasattr(self, 'roi_head') and self.roi_head.with_bbox) + or (hasattr(self, 'bbox_head') and self.bbox_head is not None)) + + @property + def with_mask(self) -> bool: + """bool: whether the detector has a mask head""" + return ((hasattr(self, 'roi_head') and self.roi_head.with_mask) + or (hasattr(self, 'mask_head') and self.mask_head is not None)) + + def forward(self, + inputs: torch.Tensor, + data_samples: OptSampleList = None, + mode: str = 'tensor') -> ForwardResults: + """The unified entry for a forward process in both training and test. + + The method should accept three modes: "tensor", "predict" and "loss": + + - "tensor": Forward the whole network and return tensor or tuple of + tensor without any post-processing, same as a common nn.Module. + - "predict": Forward and return the predictions, which are fully + processed to a list of :obj:`DetDataSample`. + - "loss": Forward and return a dict of losses according to the given + inputs and data samples. + + Note that this method doesn't handle either back propagation or + parameter update, which are supposed to be done in :meth:`train_step`. + + Args: + inputs (torch.Tensor): The input tensor with shape + (N, C, ...) in general. + data_samples (list[:obj:`DetDataSample`], optional): A batch of + data samples that contain annotations and predictions. + Defaults to None. + mode (str): Return what kind of value. Defaults to 'tensor'. + + Returns: + The return type depends on ``mode``. + + - If ``mode="tensor"``, return a tensor or a tuple of tensor. + - If ``mode="predict"``, return a list of :obj:`DetDataSample`. + - If ``mode="loss"``, return a dict of tensor. + """ + if mode == 'loss': + return self.loss(inputs, data_samples) + elif mode == 'predict': + return self.predict(inputs, data_samples) + elif mode == 'tensor': + return self._forward(inputs, data_samples) + else: + raise RuntimeError(f'Invalid mode "{mode}". ' + 'Only supports loss, predict and tensor mode') + + @abstractmethod + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> Union[dict, tuple]: + """Calculate losses from a batch of inputs and data samples.""" + pass + + @abstractmethod + def predict(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing.""" + pass + + @abstractmethod + def _forward(self, + batch_inputs: Tensor, + batch_data_samples: OptSampleList = None): + """Network forward process. + + Usually includes backbone, neck and head forward without any post- + processing. + """ + pass + + @abstractmethod + def extract_feat(self, batch_inputs: Tensor): + """Extract features from images.""" + pass + + def add_pred_to_datasample(self, data_samples: SampleList, + results_list: InstanceList) -> SampleList: + """Add predictions to `DetDataSample`. + + Args: + data_samples (list[:obj:`DetDataSample`], optional): A batch of + data samples that contain annotations and predictions. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + + Returns: + list[:obj:`DetDataSample`]: Detection results of the + input images. Each DetDataSample usually contain + 'pred_instances'. And the ``pred_instances`` usually + contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + for data_sample, pred_instances in zip(data_samples, results_list): + data_sample.pred_instances = pred_instances + samplelist_boxtype2tensor(data_samples) + return data_samples diff --git a/mmdetection/mmdet/models/detectors/base_detr.py b/mmdetection/mmdet/models/detectors/base_detr.py new file mode 100644 index 00000000..88f00ec7 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/base_detr.py @@ -0,0 +1,332 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import Dict, List, Tuple, Union + +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList, SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .base import BaseDetector + + +@MODELS.register_module() +class DetectionTransformer(BaseDetector, metaclass=ABCMeta): + r"""Base class for Detection Transformer. + + In Detection Transformer, an encoder is used to process output features of + neck, then several queries interact with the encoder features using a + decoder and do the regression and classification with the bounding box + head. + + Args: + backbone (:obj:`ConfigDict` or dict): Config of the backbone. + neck (:obj:`ConfigDict` or dict, optional): Config of the neck. + Defaults to None. + encoder (:obj:`ConfigDict` or dict, optional): Config of the + Transformer encoder. Defaults to None. + decoder (:obj:`ConfigDict` or dict, optional): Config of the + Transformer decoder. Defaults to None. + bbox_head (:obj:`ConfigDict` or dict, optional): Config for the + bounding box head module. Defaults to None. + positional_encoding (:obj:`ConfigDict` or dict, optional): Config + of the positional encoding module. Defaults to None. + num_queries (int, optional): Number of decoder query in Transformer. + Defaults to 100. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config of + the bounding box head module. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + the bounding box head module. Defaults to None. + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`BaseDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + encoder: OptConfigType = None, + decoder: OptConfigType = None, + bbox_head: OptConfigType = None, + positional_encoding: OptConfigType = None, + num_queries: int = 100, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + # process args + bbox_head.update(train_cfg=train_cfg) + bbox_head.update(test_cfg=test_cfg) + self.train_cfg = train_cfg + self.test_cfg = test_cfg + self.encoder = encoder + self.decoder = decoder + self.positional_encoding = positional_encoding + self.num_queries = num_queries + + # init model layers + self.backbone = MODELS.build(backbone) + if neck is not None: + self.neck = MODELS.build(neck) + self.bbox_head = MODELS.build(bbox_head) + self._init_layers() + + @abstractmethod + def _init_layers(self) -> None: + """Initialize layers except for backbone, neck and bbox_head.""" + pass + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> Union[dict, list]: + """Calculate losses from a batch of inputs and data samples. + + Args: + batch_inputs (Tensor): Input images of shape (bs, dim, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components + """ + img_feats = self.extract_feat(batch_inputs) + head_inputs_dict = self.forward_transformer(img_feats, + batch_data_samples) + losses = self.bbox_head.loss( + **head_inputs_dict, batch_data_samples=batch_data_samples) + + return losses + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + batch_inputs (Tensor): Inputs, has shape (bs, dim, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to True. + + Returns: + list[:obj:`DetDataSample`]: Detection results of the input images. + Each DetDataSample usually contain 'pred_instances'. And the + `pred_instances` usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + img_feats = self.extract_feat(batch_inputs) + head_inputs_dict = self.forward_transformer(img_feats, + batch_data_samples) + results_list = self.bbox_head.predict( + **head_inputs_dict, + rescale=rescale, + batch_data_samples=batch_data_samples) + batch_data_samples = self.add_pred_to_datasample( + batch_data_samples, results_list) + return batch_data_samples + + def _forward( + self, + batch_inputs: Tensor, + batch_data_samples: OptSampleList = None) -> Tuple[List[Tensor]]: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + batch_inputs (Tensor): Inputs, has shape (bs, dim, H, W). + batch_data_samples (List[:obj:`DetDataSample`], optional): The + batch data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + Defaults to None. + + Returns: + tuple[Tensor]: A tuple of features from ``bbox_head`` forward. + """ + img_feats = self.extract_feat(batch_inputs) + head_inputs_dict = self.forward_transformer(img_feats, + batch_data_samples) + results = self.bbox_head.forward(**head_inputs_dict) + return results + + def forward_transformer(self, + img_feats: Tuple[Tensor], + batch_data_samples: OptSampleList = None) -> Dict: + """Forward process of Transformer, which includes four steps: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder'. We + summarized the parameters flow of the existing DETR-like detector, + which can be illustrated as follow: + + .. code:: text + + img_feats & batch_data_samples + | + V + +-----------------+ + | pre_transformer | + +-----------------+ + | | + | V + | +-----------------+ + | | forward_encoder | + | +-----------------+ + | | + | V + | +---------------+ + | | pre_decoder | + | +---------------+ + | | | + V V | + +-----------------+ | + | forward_decoder | | + +-----------------+ | + | | + V V + head_inputs_dict + + Args: + img_feats (tuple[Tensor]): Tuple of feature maps from neck. Each + feature map has shape (bs, dim, H, W). + batch_data_samples (list[:obj:`DetDataSample`], optional): The + batch data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + Defaults to None. + + Returns: + dict: The dictionary of bbox_head function inputs, which always + includes the `hidden_states` of the decoder output and may contain + `references` including the initial and intermediate references. + """ + encoder_inputs_dict, decoder_inputs_dict = self.pre_transformer( + img_feats, batch_data_samples) + + encoder_outputs_dict = self.forward_encoder(**encoder_inputs_dict) + + tmp_dec_in, head_inputs_dict = self.pre_decoder(**encoder_outputs_dict) + decoder_inputs_dict.update(tmp_dec_in) + + decoder_outputs_dict = self.forward_decoder(**decoder_inputs_dict) + head_inputs_dict.update(decoder_outputs_dict) + return head_inputs_dict + + def extract_feat(self, batch_inputs: Tensor) -> Tuple[Tensor]: + """Extract features. + + Args: + batch_inputs (Tensor): Image tensor, has shape (bs, dim, H, W). + + Returns: + tuple[Tensor]: Tuple of feature maps from neck. Each feature map + has shape (bs, dim, H, W). + """ + x = self.backbone(batch_inputs) + if self.with_neck: + x = self.neck(x) + return x + + @abstractmethod + def pre_transformer( + self, + img_feats: Tuple[Tensor], + batch_data_samples: OptSampleList = None) -> Tuple[Dict, Dict]: + """Process image features before feeding them to the transformer. + + Args: + img_feats (tuple[Tensor]): Tuple of feature maps from neck. Each + feature map has shape (bs, dim, H, W). + batch_data_samples (list[:obj:`DetDataSample`], optional): The + batch data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + Defaults to None. + + Returns: + tuple[dict, dict]: The first dict contains the inputs of encoder + and the second dict contains the inputs of decoder. + + - encoder_inputs_dict (dict): The keyword args dictionary of + `self.forward_encoder()`, which includes 'feat', 'feat_mask', + 'feat_pos', and other algorithm-specific arguments. + - decoder_inputs_dict (dict): The keyword args dictionary of + `self.forward_decoder()`, which includes 'memory_mask', and + other algorithm-specific arguments. + """ + pass + + @abstractmethod + def forward_encoder(self, feat: Tensor, feat_mask: Tensor, + feat_pos: Tensor, **kwargs) -> Dict: + """Forward with Transformer encoder. + + Args: + feat (Tensor): Sequential features, has shape (bs, num_feat_points, + dim). + feat_mask (Tensor): ByteTensor, the padding mask of the features, + has shape (bs, num_feat_points). + feat_pos (Tensor): The positional embeddings of the features, has + shape (bs, num_feat_points, dim). + + Returns: + dict: The dictionary of encoder outputs, which includes the + `memory` of the encoder output and other algorithm-specific + arguments. + """ + pass + + @abstractmethod + def pre_decoder(self, memory: Tensor, **kwargs) -> Tuple[Dict, Dict]: + """Prepare intermediate variables before entering Transformer decoder, + such as `query`, `query_pos`, and `reference_points`. + + Args: + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + + Returns: + tuple[dict, dict]: The first dict contains the inputs of decoder + and the second dict contains the inputs of the bbox_head function. + + - decoder_inputs_dict (dict): The keyword dictionary args of + `self.forward_decoder()`, which includes 'query', 'query_pos', + 'memory', and other algorithm-specific arguments. + - head_inputs_dict (dict): The keyword dictionary args of the + bbox_head functions, which is usually empty, or includes + `enc_outputs_class` and `enc_outputs_class` when the detector + support 'two stage' or 'query selection' strategies. + """ + pass + + @abstractmethod + def forward_decoder(self, query: Tensor, query_pos: Tensor, memory: Tensor, + **kwargs) -> Dict: + """Forward with Transformer decoder. + + Args: + query (Tensor): The queries of decoder inputs, has shape + (bs, num_queries, dim). + query_pos (Tensor): The positional queries of decoder inputs, + has shape (bs, num_queries, dim). + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + + Returns: + dict: The dictionary of decoder outputs, which includes the + `hidden_states` of the decoder output, `references` including + the initial and intermediate reference_points, and other + algorithm-specific arguments. + """ + pass diff --git a/mmdetection/mmdet/models/detectors/boxinst.py b/mmdetection/mmdet/models/detectors/boxinst.py new file mode 100644 index 00000000..ca6b0bdd --- /dev/null +++ b/mmdetection/mmdet/models/detectors/boxinst.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage_instance_seg import SingleStageInstanceSegmentor + + +@MODELS.register_module() +class BoxInst(SingleStageInstanceSegmentor): + """Implementation of `BoxInst `_""" + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + mask_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + mask_head=mask_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/cascade_rcnn.py b/mmdetection/mmdet/models/detectors/cascade_rcnn.py new file mode 100644 index 00000000..ecf733ff --- /dev/null +++ b/mmdetection/mmdet/models/detectors/cascade_rcnn.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class CascadeRCNN(TwoStageDetector): + r"""Implementation of `Cascade R-CNN: Delving into High Quality Object + Detection `_""" + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + rpn_head: OptConfigType = None, + roi_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/centernet.py b/mmdetection/mmdet/models/detectors/centernet.py new file mode 100644 index 00000000..9c6622d6 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/centernet.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class CenterNet(SingleStageDetector): + """Implementation of CenterNet(Objects as Points) + + . + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/condinst.py b/mmdetection/mmdet/models/detectors/condinst.py new file mode 100644 index 00000000..ed2dc99e --- /dev/null +++ b/mmdetection/mmdet/models/detectors/condinst.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage_instance_seg import SingleStageInstanceSegmentor + + +@MODELS.register_module() +class CondInst(SingleStageInstanceSegmentor): + """Implementation of `CondInst `_""" + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + mask_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + mask_head=mask_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/conditional_detr.py b/mmdetection/mmdet/models/detectors/conditional_detr.py new file mode 100644 index 00000000..d57868e6 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/conditional_detr.py @@ -0,0 +1,74 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict + +import torch.nn as nn +from torch import Tensor + +from mmdet.registry import MODELS +from ..layers import (ConditionalDetrTransformerDecoder, + DetrTransformerEncoder, SinePositionalEncoding) +from .detr import DETR + + +@MODELS.register_module() +class ConditionalDETR(DETR): + r"""Implementation of `Conditional DETR for Fast Training Convergence. + + `_. + + Code is modified from the `official github repo + `_. + """ + + def _init_layers(self) -> None: + """Initialize layers except for backbone, neck and bbox_head.""" + self.positional_encoding = SinePositionalEncoding( + **self.positional_encoding) + self.encoder = DetrTransformerEncoder(**self.encoder) + self.decoder = ConditionalDetrTransformerDecoder(**self.decoder) + self.embed_dims = self.encoder.embed_dims + # NOTE The embed_dims is typically passed from the inside out. + # For example in DETR, The embed_dims is passed as + # self_attn -> the first encoder layer -> encoder -> detector. + self.query_embedding = nn.Embedding(self.num_queries, self.embed_dims) + + num_feats = self.positional_encoding.num_feats + assert num_feats * 2 == self.embed_dims, \ + f'embed_dims should be exactly 2 times of num_feats. ' \ + f'Found {self.embed_dims} and {num_feats}.' + + def forward_decoder(self, query: Tensor, query_pos: Tensor, memory: Tensor, + memory_mask: Tensor, memory_pos: Tensor) -> Dict: + """Forward with Transformer decoder. + + Args: + query (Tensor): The queries of decoder inputs, has shape + (bs, num_queries, dim). + query_pos (Tensor): The positional queries of decoder inputs, + has shape (bs, num_queries, dim). + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + memory_mask (Tensor): ByteTensor, the padding mask of the memory, + has shape (bs, num_feat_points). + memory_pos (Tensor): The positional embeddings of memory, has + shape (bs, num_feat_points, dim). + + Returns: + dict: The dictionary of decoder outputs, which includes the + `hidden_states` and `references` of the decoder output. + + - hidden_states (Tensor): Has shape + (num_decoder_layers, bs, num_queries, dim) + - references (Tensor): Has shape + (bs, num_queries, 2) + """ + + hidden_states, references = self.decoder( + query=query, + key=memory, + query_pos=query_pos, + key_pos=memory_pos, + key_padding_mask=memory_mask) + head_inputs_dict = dict( + hidden_states=hidden_states, references=references) + return head_inputs_dict diff --git a/mmdetection/mmdet/models/detectors/cornernet.py b/mmdetection/mmdet/models/detectors/cornernet.py new file mode 100644 index 00000000..946af4db --- /dev/null +++ b/mmdetection/mmdet/models/detectors/cornernet.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class CornerNet(SingleStageDetector): + """CornerNet. + + This detector is the implementation of the paper `CornerNet: Detecting + Objects as Paired Keypoints `_ . + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/crowddet.py b/mmdetection/mmdet/models/detectors/crowddet.py new file mode 100644 index 00000000..4f43bc08 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/crowddet.py @@ -0,0 +1,45 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class CrowdDet(TwoStageDetector): + """Implementation of `CrowdDet `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone config. + rpn_head (:obj:`ConfigDict` or dict): The rpn config. + roi_head (:obj:`ConfigDict` or dict): The roi config. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of FCOS. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of FCOS. Defaults to None. + neck (:obj:`ConfigDict` or dict): The neck config. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + rpn_head: ConfigType, + roi_head: ConfigType, + train_cfg: ConfigType, + test_cfg: ConfigType, + neck: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + init_cfg=init_cfg, + data_preprocessor=data_preprocessor) diff --git a/mmdetection/mmdet/models/detectors/d2_wrapper.py b/mmdetection/mmdet/models/detectors/d2_wrapper.py new file mode 100644 index 00000000..3a2daa41 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/d2_wrapper.py @@ -0,0 +1,291 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Union + +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import BaseBoxes +from mmdet.structures.mask import BitmapMasks, PolygonMasks +from mmdet.utils import ConfigType +from .base import BaseDetector + +try: + import detectron2 + from detectron2.config import get_cfg + from detectron2.modeling import build_model + from detectron2.structures.masks import BitMasks as D2_BitMasks + from detectron2.structures.masks import PolygonMasks as D2_PolygonMasks + from detectron2.utils.events import EventStorage +except ImportError: + detectron2 = None + + +def _to_cfgnode_list(cfg: ConfigType, + config_list: list = [], + father_name: str = 'MODEL') -> tuple: + """Convert the key and value of mmengine.ConfigDict into a list. + + Args: + cfg (ConfigDict): The detectron2 model config. + config_list (list): A list contains the key and value of ConfigDict. + Defaults to []. + father_name (str): The father name add before the key. + Defaults to "MODEL". + + Returns: + tuple: + + - config_list: A list contains the key and value of ConfigDict. + - father_name (str): The father name add before the key. + Defaults to "MODEL". + """ + for key, value in cfg.items(): + name = f'{father_name}.{key.upper()}' + if isinstance(value, ConfigDict) or isinstance(value, dict): + config_list, fater_name = \ + _to_cfgnode_list(value, config_list, name) + else: + config_list.append(name) + config_list.append(value) + + return config_list, father_name + + +def convert_d2_pred_to_datasample(data_samples: SampleList, + d2_results_list: list) -> SampleList: + """Convert the Detectron2's result to DetDataSample. + + Args: + data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + d2_results_list (list): The list of the results of Detectron2's model. + + Returns: + list[:obj:`DetDataSample`]: Detection results of the + input images. Each DetDataSample usually contain + 'pred_instances'. And the ``pred_instances`` usually + contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(data_samples) == len(d2_results_list) + for data_sample, d2_results in zip(data_samples, d2_results_list): + d2_instance = d2_results['instances'] + + results = InstanceData() + results.bboxes = d2_instance.pred_boxes.tensor + results.scores = d2_instance.scores + results.labels = d2_instance.pred_classes + + if d2_instance.has('pred_masks'): + results.masks = d2_instance.pred_masks + data_sample.pred_instances = results + + return data_samples + + +@MODELS.register_module() +class Detectron2Wrapper(BaseDetector): + """Wrapper of a Detectron2 model. Input/output formats of this class follow + MMDetection's convention, so a Detectron2 model can be trained and + evaluated in MMDetection. + + Args: + detector (:obj:`ConfigDict` or dict): The module config of + Detectron2. + bgr_to_rgb (bool): whether to convert image from BGR to RGB. + Defaults to False. + rgb_to_bgr (bool): whether to convert image from RGB to BGR. + Defaults to False. + """ + + def __init__(self, + detector: ConfigType, + bgr_to_rgb: bool = False, + rgb_to_bgr: bool = False) -> None: + if detectron2 is None: + raise ImportError('Please install Detectron2 first') + assert not (bgr_to_rgb and rgb_to_bgr), ( + '`bgr2rgb` and `rgb2bgr` cannot be set to True at the same time') + super().__init__() + self._channel_conversion = rgb_to_bgr or bgr_to_rgb + cfgnode_list, _ = _to_cfgnode_list(detector) + self.cfg = get_cfg() + self.cfg.merge_from_list(cfgnode_list) + self.d2_model = build_model(self.cfg) + self.storage = EventStorage() + + def init_weights(self) -> None: + """Initialization Backbone. + + NOTE: The initialization of other layers are in Detectron2, + if users want to change the initialization way, please + change the code in Detectron2. + """ + from detectron2.checkpoint import DetectionCheckpointer + checkpointer = DetectionCheckpointer(model=self.d2_model) + checkpointer.load(self.cfg.MODEL.WEIGHTS, checkpointables=[]) + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> Union[dict, tuple]: + """Calculate losses from a batch of inputs and data samples. + + The inputs will first convert to the Detectron2 type and feed into + D2 models. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + d2_batched_inputs = self._convert_to_d2_inputs( + batch_inputs=batch_inputs, + batch_data_samples=batch_data_samples, + training=True) + + with self.storage as storage: # noqa + losses = self.d2_model(d2_batched_inputs) + # storage contains some training information, such as cls_accuracy. + # you can use storage.latest() to get the detail information + return losses + + def predict(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + The inputs will first convert to the Detectron2 type and feed into + D2 models. And the results will convert back to the MMDet type. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + + Returns: + list[:obj:`DetDataSample`]: Detection results of the + input images. Each DetDataSample usually contain + 'pred_instances'. And the ``pred_instances`` usually + contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + d2_batched_inputs = self._convert_to_d2_inputs( + batch_inputs=batch_inputs, + batch_data_samples=batch_data_samples, + training=False) + # results in detectron2 has already rescale + d2_results_list = self.d2_model(d2_batched_inputs) + batch_data_samples = convert_d2_pred_to_datasample( + data_samples=batch_data_samples, d2_results_list=d2_results_list) + + return batch_data_samples + + def _forward(self, *args, **kwargs): + """Network forward process. + + Usually includes backbone, neck and head forward without any post- + processing. + """ + raise NotImplementedError( + f'`_forward` is not implemented in {self.__class__.__name__}') + + def extract_feat(self, *args, **kwargs): + """Extract features from images. + + `extract_feat` will not be used in obj:``Detectron2Wrapper``. + """ + pass + + def _convert_to_d2_inputs(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + training=True) -> list: + """Convert inputs type to support Detectron2's model. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + training (bool): Whether to enable training time processing. + + Returns: + list[dict]: A list of dict, which will be fed into Detectron2's + model. And the dict usually contains following keys. + + - image (Tensor): Image in (C, H, W) format. + - instances (Instances): GT Instance. + - height (int): the output height resolution of the model + - width (int): the output width resolution of the model + """ + from detectron2.data.detection_utils import filter_empty_instances + from detectron2.structures import Boxes, Instances + + batched_d2_inputs = [] + for image, data_samples in zip(batch_inputs, batch_data_samples): + d2_inputs = dict() + # deal with metainfo + meta_info = data_samples.metainfo + d2_inputs['file_name'] = meta_info['img_path'] + d2_inputs['height'], d2_inputs['width'] = meta_info['ori_shape'] + d2_inputs['image_id'] = meta_info['img_id'] + # deal with image + if self._channel_conversion: + image = image[[2, 1, 0], ...] + d2_inputs['image'] = image + # deal with gt_instances + gt_instances = data_samples.gt_instances + d2_instances = Instances(meta_info['img_shape']) + + gt_boxes = gt_instances.bboxes + # TODO: use mmdet.structures.box.get_box_tensor after PR 8658 + # has merged + if isinstance(gt_boxes, BaseBoxes): + gt_boxes = gt_boxes.tensor + d2_instances.gt_boxes = Boxes(gt_boxes) + + d2_instances.gt_classes = gt_instances.labels + if gt_instances.get('masks', None) is not None: + gt_masks = gt_instances.masks + if isinstance(gt_masks, PolygonMasks): + d2_instances.gt_masks = D2_PolygonMasks(gt_masks.masks) + elif isinstance(gt_masks, BitmapMasks): + d2_instances.gt_masks = D2_BitMasks(gt_masks.masks) + else: + raise TypeError('The type of `gt_mask` can be ' + '`PolygonMasks` or `BitMasks`, but get ' + f'{type(gt_masks)}.') + # convert to cpu and convert back to cuda to avoid + # some potential error + if training: + device = gt_boxes.device + d2_instances = filter_empty_instances( + d2_instances.to('cpu')).to(device) + d2_inputs['instances'] = d2_instances + batched_d2_inputs.append(d2_inputs) + + return batched_d2_inputs diff --git a/mmdetection/mmdet/models/detectors/dab_detr.py b/mmdetection/mmdet/models/detectors/dab_detr.py new file mode 100644 index 00000000..b61301cf --- /dev/null +++ b/mmdetection/mmdet/models/detectors/dab_detr.py @@ -0,0 +1,139 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, Tuple + +from mmengine.model import uniform_init +from torch import Tensor, nn + +from mmdet.registry import MODELS +from ..layers import SinePositionalEncoding +from ..layers.transformer import (DABDetrTransformerDecoder, + DABDetrTransformerEncoder, inverse_sigmoid) +from .detr import DETR + + +@MODELS.register_module() +class DABDETR(DETR): + r"""Implementation of `DAB-DETR: + Dynamic Anchor Boxes are Better Queries for DETR. + + `_. + + Code is modified from the `official github repo + `_. + + Args: + with_random_refpoints (bool): Whether to randomly initialize query + embeddings and not update them during training. + Defaults to False. + num_patterns (int): Inspired by Anchor-DETR. Defaults to 0. + """ + + def __init__(self, + *args, + with_random_refpoints: bool = False, + num_patterns: int = 0, + **kwargs) -> None: + self.with_random_refpoints = with_random_refpoints + assert isinstance(num_patterns, int), \ + f'num_patterns should be int but {num_patterns}.' + self.num_patterns = num_patterns + + super().__init__(*args, **kwargs) + + def _init_layers(self) -> None: + """Initialize layers except for backbone, neck and bbox_head.""" + self.positional_encoding = SinePositionalEncoding( + **self.positional_encoding) + self.encoder = DABDetrTransformerEncoder(**self.encoder) + self.decoder = DABDetrTransformerDecoder(**self.decoder) + self.embed_dims = self.encoder.embed_dims + self.query_dim = self.decoder.query_dim + self.query_embedding = nn.Embedding(self.num_queries, self.query_dim) + if self.num_patterns > 0: + self.patterns = nn.Embedding(self.num_patterns, self.embed_dims) + + num_feats = self.positional_encoding.num_feats + assert num_feats * 2 == self.embed_dims, \ + f'embed_dims should be exactly 2 times of num_feats. ' \ + f'Found {self.embed_dims} and {num_feats}.' + + def init_weights(self) -> None: + """Initialize weights for Transformer and other components.""" + super(DABDETR, self).init_weights() + if self.with_random_refpoints: + uniform_init(self.query_embedding) + self.query_embedding.weight.data[:, :2] = \ + inverse_sigmoid(self.query_embedding.weight.data[:, :2]) + self.query_embedding.weight.data[:, :2].requires_grad = False + + def pre_decoder(self, memory: Tensor) -> Tuple[Dict, Dict]: + """Prepare intermediate variables before entering Transformer decoder, + such as `query`, `query_pos`. + + Args: + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + + Returns: + tuple[dict, dict]: The first dict contains the inputs of decoder + and the second dict contains the inputs of the bbox_head function. + + - decoder_inputs_dict (dict): The keyword args dictionary of + `self.forward_decoder()`, which includes 'query', 'query_pos', + 'memory' and 'reg_branches'. + - head_inputs_dict (dict): The keyword args dictionary of the + bbox_head functions, which is usually empty, or includes + `enc_outputs_class` and `enc_outputs_class` when the detector + support 'two stage' or 'query selection' strategies. + """ + batch_size = memory.size(0) + query_pos = self.query_embedding.weight + query_pos = query_pos.unsqueeze(0).repeat(batch_size, 1, 1) + if self.num_patterns == 0: + query = query_pos.new_zeros(batch_size, self.num_queries, + self.embed_dims) + else: + query = self.patterns.weight[:, None, None, :]\ + .repeat(1, self.num_queries, batch_size, 1)\ + .view(-1, batch_size, self.embed_dims)\ + .permute(1, 0, 2) + query_pos = query_pos.repeat(1, self.num_patterns, 1) + + decoder_inputs_dict = dict( + query_pos=query_pos, query=query, memory=memory) + head_inputs_dict = dict() + return decoder_inputs_dict, head_inputs_dict + + def forward_decoder(self, query: Tensor, query_pos: Tensor, memory: Tensor, + memory_mask: Tensor, memory_pos: Tensor) -> Dict: + """Forward with Transformer decoder. + + Args: + query (Tensor): The queries of decoder inputs, has shape + (bs, num_queries, dim). + query_pos (Tensor): The positional queries of decoder inputs, + has shape (bs, num_queries, dim). + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + memory_mask (Tensor): ByteTensor, the padding mask of the memory, + has shape (bs, num_feat_points). + memory_pos (Tensor): The positional embeddings of memory, has + shape (bs, num_feat_points, dim). + + Returns: + dict: The dictionary of decoder outputs, which includes the + `hidden_states` and `references` of the decoder output. + """ + + hidden_states, references = self.decoder( + query=query, + key=memory, + query_pos=query_pos, + key_pos=memory_pos, + key_padding_mask=memory_mask, + reg_branches=self.bbox_head. + fc_reg # iterative refinement for anchor boxes + ) + head_inputs_dict = dict( + hidden_states=hidden_states, references=references) + return head_inputs_dict diff --git a/mmdetection/mmdet/models/detectors/ddod.py b/mmdetection/mmdet/models/detectors/ddod.py new file mode 100644 index 00000000..3503a40c --- /dev/null +++ b/mmdetection/mmdet/models/detectors/ddod.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class DDOD(SingleStageDetector): + """Implementation of `DDOD `_. + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of ATSS. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of ATSS. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/ddq_detr.py b/mmdetection/mmdet/models/detectors/ddq_detr.py new file mode 100644 index 00000000..57d4959d --- /dev/null +++ b/mmdetection/mmdet/models/detectors/ddq_detr.py @@ -0,0 +1,274 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, Tuple + +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmcv.ops import MultiScaleDeformableAttention, batched_nms +from torch import Tensor, nn +from torch.nn.init import normal_ + +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList +from mmdet.structures.bbox import bbox_cxcywh_to_xyxy +from mmdet.utils import OptConfigType +from ..layers import DDQTransformerDecoder +from ..utils import align_tensor +from .deformable_detr import DeformableDETR +from .dino import DINO + + +@MODELS.register_module() +class DDQDETR(DINO): + r"""Implementation of `Dense Distinct Query for + End-to-End Object Detection `_ + + Code is modified from the `official github repo + `_. + + Args: + dense_topk_ratio (float): Ratio of num_dense queries to num_queries. + Defaults to 1.5. + dqs_cfg (:obj:`ConfigDict` or dict, optional): Config of + Distinct Queries Selection. Defaults to nms with + `iou_threshold` = 0.8. + """ + + def __init__(self, + *args, + dense_topk_ratio: float = 1.5, + dqs_cfg: OptConfigType = dict(type='nms', iou_threshold=0.8), + **kwargs): + self.dense_topk_ratio = dense_topk_ratio + self.decoder_cfg = kwargs['decoder'] + self.dqs_cfg = dqs_cfg + super().__init__(*args, **kwargs) + + # a share dict in all moduls + # pass some intermediate results and config parameters + cache_dict = dict() + for m in self.modules(): + m.cache_dict = cache_dict + # first element is the start index of matching queries + # second element is the number of matching queries + self.cache_dict['dis_query_info'] = [0, 0] + + # mask for distinct queries in each decoder layer + self.cache_dict['distinct_query_mask'] = [] + # pass to decoder do the dqs + self.cache_dict['cls_branches'] = self.bbox_head.cls_branches + # Used to construct the attention mask after dqs + self.cache_dict['num_heads'] = self.encoder.layers[ + 0].self_attn.num_heads + # pass to decoder to do the dqs + self.cache_dict['dqs_cfg'] = self.dqs_cfg + + def _init_layers(self) -> None: + """Initialize layers except for backbone, neck and bbox_head.""" + super(DDQDETR, self)._init_layers() + self.decoder = DDQTransformerDecoder(**self.decoder_cfg) + self.query_embedding = None + self.query_map = nn.Linear(self.embed_dims, self.embed_dims) + + def init_weights(self) -> None: + """Initialize weights for Transformer and other components.""" + super(DeformableDETR, self).init_weights() + for coder in self.encoder, self.decoder: + for p in coder.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + for m in self.modules(): + if isinstance(m, MultiScaleDeformableAttention): + m.init_weights() + nn.init.xavier_uniform_(self.memory_trans_fc.weight) + normal_(self.level_embed) + + def pre_decoder( + self, + memory: Tensor, + memory_mask: Tensor, + spatial_shapes: Tensor, + batch_data_samples: OptSampleList = None, + ) -> Tuple[Dict]: + """Prepare intermediate variables before entering Transformer decoder, + such as `query`, `memory`, and `reference_points`. + + Args: + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + memory_mask (Tensor): ByteTensor, the padding mask of the memory, + has shape (bs, num_feat_points). Will only be used when + `as_two_stage` is `True`. + spatial_shapes (Tensor): Spatial shapes of features in all levels. + With shape (num_levels, 2), last dimension represents (h, w). + Will only be used when `as_two_stage` is `True`. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + Defaults to None. + + Returns: + tuple[dict]: The decoder_inputs_dict and head_inputs_dict. + + - decoder_inputs_dict (dict): The keyword dictionary args of + `self.forward_decoder()`, which includes 'query', 'memory', + `reference_points`, and `dn_mask`. The reference points of + decoder input here are 4D boxes, although it has `points` + in its name. + - head_inputs_dict (dict): The keyword dictionary args of the + bbox_head functions, which includes `topk_score`, `topk_coords`, + `dense_topk_score`, `dense_topk_coords`, + and `dn_meta`, when `self.training` is `True`, else is empty. + """ + bs, _, c = memory.shape + output_memory, output_proposals = self.gen_encoder_output_proposals( + memory, memory_mask, spatial_shapes) + enc_outputs_class = self.bbox_head.cls_branches[ + self.decoder.num_layers]( + output_memory) + enc_outputs_coord_unact = self.bbox_head.reg_branches[ + self.decoder.num_layers](output_memory) + output_proposals + + if self.training: + # aux dense branch particularly in DDQ DETR, which doesn't exist + # in DINO. + # -1 is the aux head for the encoder + dense_enc_outputs_class = self.bbox_head.cls_branches[-1]( + output_memory) + dense_enc_outputs_coord_unact = self.bbox_head.reg_branches[-1]( + output_memory) + output_proposals + + topk = self.num_queries + dense_topk = int(topk * self.dense_topk_ratio) + + proposals = enc_outputs_coord_unact.sigmoid() + proposals = bbox_cxcywh_to_xyxy(proposals) + scores = enc_outputs_class.max(-1)[0].sigmoid() + + if self.training: + # aux dense branch particularly in DDQ DETR, which doesn't exist + # in DINO. + dense_proposals = dense_enc_outputs_coord_unact.sigmoid() + dense_proposals = bbox_cxcywh_to_xyxy(dense_proposals) + dense_scores = dense_enc_outputs_class.max(-1)[0].sigmoid() + + num_imgs = len(scores) + topk_score = [] + topk_coords_unact = [] + # Distinct query. + query = [] + + dense_topk_score = [] + dense_topk_coords_unact = [] + dense_query = [] + + for img_id in range(num_imgs): + single_proposals = proposals[img_id] + single_scores = scores[img_id] + + # `batched_nms` of class scores and bbox coordinations is used + # particularly by DDQ DETR for region proposal generation, + # instead of `topk` of class scores by DINO. + _, keep_idxs = batched_nms( + single_proposals, single_scores, + torch.ones(len(single_scores), device=single_scores.device), + self.cache_dict['dqs_cfg']) + + if self.training: + # aux dense branch particularly in DDQ DETR, which doesn't + # exist in DINO. + dense_single_proposals = dense_proposals[img_id] + dense_single_scores = dense_scores[img_id] + # sort according the score + # Only sort by classification score, neither nms nor topk is + # required. So input parameter `nms_cfg` = None. + _, dense_keep_idxs = batched_nms( + dense_single_proposals, dense_single_scores, + torch.ones( + len(dense_single_scores), + device=dense_single_scores.device), None) + + dense_topk_score.append(dense_enc_outputs_class[img_id] + [dense_keep_idxs][:dense_topk]) + dense_topk_coords_unact.append( + dense_enc_outputs_coord_unact[img_id][dense_keep_idxs] + [:dense_topk]) + + topk_score.append(enc_outputs_class[img_id][keep_idxs][:topk]) + + # Instead of initializing the content part with transformed + # coordinates in Deformable DETR, we fuse the feature map + # embedding of distinct positions as the content part, which + # makes the initial queries more distinct. + topk_coords_unact.append( + enc_outputs_coord_unact[img_id][keep_idxs][:topk]) + + map_memory = self.query_map(memory[img_id].detach()) + query.append(map_memory[keep_idxs][:topk]) + if self.training: + # aux dense branch particularly in DDQ DETR, which doesn't + # exist in DINO. + dense_query.append(map_memory[dense_keep_idxs][:dense_topk]) + + topk_score = align_tensor(topk_score, topk) + topk_coords_unact = align_tensor(topk_coords_unact, topk) + query = align_tensor(query, topk) + if self.training: + dense_topk_score = align_tensor(dense_topk_score) + dense_topk_coords_unact = align_tensor(dense_topk_coords_unact) + + dense_query = align_tensor(dense_query) + num_dense_queries = dense_query.size(1) + if self.training: + query = torch.cat([query, dense_query], dim=1) + topk_coords_unact = torch.cat( + [topk_coords_unact, dense_topk_coords_unact], dim=1) + + topk_coords = topk_coords_unact.sigmoid() + if self.training: + dense_topk_coords = topk_coords[:, -num_dense_queries:] + topk_coords = topk_coords[:, :-num_dense_queries] + + topk_coords_unact = topk_coords_unact.detach() + + if self.training: + dn_label_query, dn_bbox_query, dn_mask, dn_meta = \ + self.dn_query_generator(batch_data_samples) + query = torch.cat([dn_label_query, query], dim=1) + reference_points = torch.cat([dn_bbox_query, topk_coords_unact], + dim=1) + + # Update `dn_mask` to add mask for dense queries. + ori_size = dn_mask.size(-1) + new_size = dn_mask.size(-1) + num_dense_queries + new_dn_mask = dn_mask.new_ones((new_size, new_size)).bool() + dense_mask = torch.zeros(num_dense_queries, + num_dense_queries).bool() + self.cache_dict['dis_query_info'] = [dn_label_query.size(1), topk] + + new_dn_mask[ori_size:, ori_size:] = dense_mask + new_dn_mask[:ori_size, :ori_size] = dn_mask + dn_meta['num_dense_queries'] = num_dense_queries + dn_mask = new_dn_mask + self.cache_dict['num_dense_queries'] = num_dense_queries + self.decoder.aux_reg_branches = self.bbox_head.aux_reg_branches + + else: + self.cache_dict['dis_query_info'] = [0, topk] + reference_points = topk_coords_unact + dn_mask, dn_meta = None, None + + reference_points = reference_points.sigmoid() + + decoder_inputs_dict = dict( + query=query, + memory=memory, + reference_points=reference_points, + dn_mask=dn_mask) + head_inputs_dict = dict( + enc_outputs_class=topk_score, + enc_outputs_coord=topk_coords, + aux_enc_outputs_class=dense_topk_score, + aux_enc_outputs_coord=dense_topk_coords, + dn_meta=dn_meta) if self.training else dict() + + return decoder_inputs_dict, head_inputs_dict diff --git a/mmdetection/mmdet/models/detectors/deformable_detr.py b/mmdetection/mmdet/models/detectors/deformable_detr.py new file mode 100644 index 00000000..0eb5cd2f --- /dev/null +++ b/mmdetection/mmdet/models/detectors/deformable_detr.py @@ -0,0 +1,572 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Dict, Tuple + +import torch +import torch.nn.functional as F +from mmcv.cnn.bricks.transformer import MultiScaleDeformableAttention +from mmengine.model import xavier_init +from torch import Tensor, nn +from torch.nn.init import normal_ + +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList +from mmdet.utils import OptConfigType +from ..layers import (DeformableDetrTransformerDecoder, + DeformableDetrTransformerEncoder, SinePositionalEncoding) +from .base_detr import DetectionTransformer + + +@MODELS.register_module() +class DeformableDETR(DetectionTransformer): + r"""Implementation of `Deformable DETR: Deformable Transformers for + End-to-End Object Detection `_ + + Code is modified from the `official github repo + `_. + + Args: + decoder (:obj:`ConfigDict` or dict, optional): Config of the + Transformer decoder. Defaults to None. + bbox_head (:obj:`ConfigDict` or dict, optional): Config for the + bounding box head module. Defaults to None. + with_box_refine (bool, optional): Whether to refine the references + in the decoder. Defaults to `False`. + as_two_stage (bool, optional): Whether to generate the proposal + from the outputs of encoder. Defaults to `False`. + num_feature_levels (int, optional): Number of feature levels. + Defaults to 4. + """ + + def __init__(self, + *args, + decoder: OptConfigType = None, + bbox_head: OptConfigType = None, + with_box_refine: bool = False, + as_two_stage: bool = False, + num_feature_levels: int = 4, + **kwargs) -> None: + self.with_box_refine = with_box_refine + self.as_two_stage = as_two_stage + self.num_feature_levels = num_feature_levels + + if bbox_head is not None: + assert 'share_pred_layer' not in bbox_head and \ + 'num_pred_layer' not in bbox_head and \ + 'as_two_stage' not in bbox_head, \ + 'The two keyword args `share_pred_layer`, `num_pred_layer`, ' \ + 'and `as_two_stage are set in `detector.__init__()`, users ' \ + 'should not set them in `bbox_head` config.' + # The last prediction layer is used to generate proposal + # from encode feature map when `as_two_stage` is `True`. + # And all the prediction layers should share parameters + # when `with_box_refine` is `True`. + bbox_head['share_pred_layer'] = not with_box_refine + bbox_head['num_pred_layer'] = (decoder['num_layers'] + 1) \ + if self.as_two_stage else decoder['num_layers'] + bbox_head['as_two_stage'] = as_two_stage + + super().__init__(*args, decoder=decoder, bbox_head=bbox_head, **kwargs) + + def _init_layers(self) -> None: + """Initialize layers except for backbone, neck and bbox_head.""" + self.positional_encoding = SinePositionalEncoding( + **self.positional_encoding) + self.encoder = DeformableDetrTransformerEncoder(**self.encoder) + self.decoder = DeformableDetrTransformerDecoder(**self.decoder) + self.embed_dims = self.encoder.embed_dims + if not self.as_two_stage: + self.query_embedding = nn.Embedding(self.num_queries, + self.embed_dims * 2) + # NOTE The query_embedding will be split into query and query_pos + # in self.pre_decoder, hence, the embed_dims are doubled. + + num_feats = self.positional_encoding.num_feats + assert num_feats * 2 == self.embed_dims, \ + 'embed_dims should be exactly 2 times of num_feats. ' \ + f'Found {self.embed_dims} and {num_feats}.' + + self.level_embed = nn.Parameter( + torch.Tensor(self.num_feature_levels, self.embed_dims)) + + if self.as_two_stage: + self.memory_trans_fc = nn.Linear(self.embed_dims, self.embed_dims) + self.memory_trans_norm = nn.LayerNorm(self.embed_dims) + self.pos_trans_fc = nn.Linear(self.embed_dims * 2, + self.embed_dims * 2) + self.pos_trans_norm = nn.LayerNorm(self.embed_dims * 2) + else: + self.reference_points_fc = nn.Linear(self.embed_dims, 2) + + def init_weights(self) -> None: + """Initialize weights for Transformer and other components.""" + super().init_weights() + for coder in self.encoder, self.decoder: + for p in coder.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + for m in self.modules(): + if isinstance(m, MultiScaleDeformableAttention): + m.init_weights() + if self.as_two_stage: + nn.init.xavier_uniform_(self.memory_trans_fc.weight) + nn.init.xavier_uniform_(self.pos_trans_fc.weight) + else: + xavier_init( + self.reference_points_fc, distribution='uniform', bias=0.) + normal_(self.level_embed) + + def pre_transformer( + self, + mlvl_feats: Tuple[Tensor], + batch_data_samples: OptSampleList = None) -> Tuple[Dict]: + """Process image features before feeding them to the transformer. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + + Args: + mlvl_feats (tuple[Tensor]): Multi-level features that may have + different resolutions, output from neck. Each feature has + shape (bs, dim, h_lvl, w_lvl), where 'lvl' means 'layer'. + batch_data_samples (list[:obj:`DetDataSample`], optional): The + batch data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + Defaults to None. + + Returns: + tuple[dict]: The first dict contains the inputs of encoder and the + second dict contains the inputs of decoder. + + - encoder_inputs_dict (dict): The keyword args dictionary of + `self.forward_encoder()`, which includes 'feat', 'feat_mask', + and 'feat_pos'. + - decoder_inputs_dict (dict): The keyword args dictionary of + `self.forward_decoder()`, which includes 'memory_mask'. + """ + batch_size = mlvl_feats[0].size(0) + + # construct binary masks for the transformer. + assert batch_data_samples is not None + batch_input_shape = batch_data_samples[0].batch_input_shape + input_img_h, input_img_w = batch_input_shape + img_shape_list = [sample.img_shape for sample in batch_data_samples] + same_shape_flag = all([ + s[0] == input_img_h and s[1] == input_img_w for s in img_shape_list + ]) + # support torch2onnx without feeding masks + if torch.onnx.is_in_onnx_export() or same_shape_flag: + mlvl_masks = [] + mlvl_pos_embeds = [] + for feat in mlvl_feats: + mlvl_masks.append(None) + mlvl_pos_embeds.append( + self.positional_encoding(None, input=feat)) + else: + masks = mlvl_feats[0].new_ones( + (batch_size, input_img_h, input_img_w)) + for img_id in range(batch_size): + img_h, img_w = img_shape_list[img_id] + masks[img_id, :img_h, :img_w] = 0 + # NOTE following the official DETR repo, non-zero + # values representing ignored positions, while + # zero values means valid positions. + + mlvl_masks = [] + mlvl_pos_embeds = [] + for feat in mlvl_feats: + mlvl_masks.append( + F.interpolate(masks[None], size=feat.shape[-2:]).to( + torch.bool).squeeze(0)) + mlvl_pos_embeds.append( + self.positional_encoding(mlvl_masks[-1])) + + feat_flatten = [] + lvl_pos_embed_flatten = [] + mask_flatten = [] + spatial_shapes = [] + for lvl, (feat, mask, pos_embed) in enumerate( + zip(mlvl_feats, mlvl_masks, mlvl_pos_embeds)): + batch_size, c, h, w = feat.shape + spatial_shape = torch._shape_as_tensor(feat)[2:].to(feat.device) + # [bs, c, h_lvl, w_lvl] -> [bs, h_lvl*w_lvl, c] + feat = feat.view(batch_size, c, -1).permute(0, 2, 1) + pos_embed = pos_embed.view(batch_size, c, -1).permute(0, 2, 1) + lvl_pos_embed = pos_embed + self.level_embed[lvl].view(1, 1, -1) + # [bs, h_lvl, w_lvl] -> [bs, h_lvl*w_lvl] + if mask is not None: + mask = mask.flatten(1) + + feat_flatten.append(feat) + lvl_pos_embed_flatten.append(lvl_pos_embed) + mask_flatten.append(mask) + spatial_shapes.append(spatial_shape) + + # (bs, num_feat_points, dim) + feat_flatten = torch.cat(feat_flatten, 1) + lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1) + # (bs, num_feat_points), where num_feat_points = sum_lvl(h_lvl*w_lvl) + if mask_flatten[0] is not None: + mask_flatten = torch.cat(mask_flatten, 1) + else: + mask_flatten = None + + # (num_level, 2) + spatial_shapes = torch.cat(spatial_shapes).view(-1, 2) + level_start_index = torch.cat(( + spatial_shapes.new_zeros((1, )), # (num_level) + spatial_shapes.prod(1).cumsum(0)[:-1])) + if mlvl_masks[0] is not None: + valid_ratios = torch.stack( # (bs, num_level, 2) + [self.get_valid_ratio(m) for m in mlvl_masks], 1) + else: + valid_ratios = mlvl_feats[0].new_ones(batch_size, len(mlvl_feats), + 2) + + encoder_inputs_dict = dict( + feat=feat_flatten, + feat_mask=mask_flatten, + feat_pos=lvl_pos_embed_flatten, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios) + decoder_inputs_dict = dict( + memory_mask=mask_flatten, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios) + return encoder_inputs_dict, decoder_inputs_dict + + def forward_encoder(self, feat: Tensor, feat_mask: Tensor, + feat_pos: Tensor, spatial_shapes: Tensor, + level_start_index: Tensor, + valid_ratios: Tensor) -> Dict: + """Forward with Transformer encoder. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + + Args: + feat (Tensor): Sequential features, has shape (bs, num_feat_points, + dim). + feat_mask (Tensor): ByteTensor, the padding mask of the features, + has shape (bs, num_feat_points). + feat_pos (Tensor): The positional embeddings of the features, has + shape (bs, num_feat_points, dim). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape (num_levels, ) and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + + Returns: + dict: The dictionary of encoder outputs, which includes the + `memory` of the encoder output. + """ + memory = self.encoder( + query=feat, + query_pos=feat_pos, + key_padding_mask=feat_mask, # for self_attn + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios) + encoder_outputs_dict = dict( + memory=memory, + memory_mask=feat_mask, + spatial_shapes=spatial_shapes) + return encoder_outputs_dict + + def pre_decoder(self, memory: Tensor, memory_mask: Tensor, + spatial_shapes: Tensor) -> Tuple[Dict, Dict]: + """Prepare intermediate variables before entering Transformer decoder, + such as `query`, `query_pos`, and `reference_points`. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + + Args: + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + memory_mask (Tensor): ByteTensor, the padding mask of the memory, + has shape (bs, num_feat_points). It will only be used when + `as_two_stage` is `True`. + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + It will only be used when `as_two_stage` is `True`. + + Returns: + tuple[dict, dict]: The decoder_inputs_dict and head_inputs_dict. + + - decoder_inputs_dict (dict): The keyword dictionary args of + `self.forward_decoder()`, which includes 'query', 'query_pos', + 'memory', and `reference_points`. The reference_points of + decoder input here are 4D boxes when `as_two_stage` is `True`, + otherwise 2D points, although it has `points` in its name. + The reference_points in encoder is always 2D points. + - head_inputs_dict (dict): The keyword dictionary args of the + bbox_head functions, which includes `enc_outputs_class` and + `enc_outputs_coord`. They are both `None` when 'as_two_stage' + is `False`. The dict is empty when `self.training` is `False`. + """ + batch_size, _, c = memory.shape + if self.as_two_stage: + output_memory, output_proposals = \ + self.gen_encoder_output_proposals( + memory, memory_mask, spatial_shapes) + enc_outputs_class = self.bbox_head.cls_branches[ + self.decoder.num_layers]( + output_memory) + enc_outputs_coord_unact = self.bbox_head.reg_branches[ + self.decoder.num_layers](output_memory) + output_proposals + enc_outputs_coord = enc_outputs_coord_unact.sigmoid() + # We only use the first channel in enc_outputs_class as foreground, + # the other (num_classes - 1) channels are actually not used. + # Its targets are set to be 0s, which indicates the first + # class (foreground) because we use [0, num_classes - 1] to + # indicate class labels, background class is indicated by + # num_classes (similar convention in RPN). + # See https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/dense_heads/deformable_detr_head.py#L241 # noqa + # This follows the official implementation of Deformable DETR. + topk_proposals = torch.topk( + enc_outputs_class[..., 0], self.num_queries, dim=1)[1] + topk_coords_unact = torch.gather( + enc_outputs_coord_unact, 1, + topk_proposals.unsqueeze(-1).repeat(1, 1, 4)) + topk_coords_unact = topk_coords_unact.detach() + reference_points = topk_coords_unact.sigmoid() + pos_trans_out = self.pos_trans_fc( + self.get_proposal_pos_embed(topk_coords_unact)) + pos_trans_out = self.pos_trans_norm(pos_trans_out) + query_pos, query = torch.split(pos_trans_out, c, dim=2) + else: + enc_outputs_class, enc_outputs_coord = None, None + query_embed = self.query_embedding.weight + query_pos, query = torch.split(query_embed, c, dim=1) + query_pos = query_pos.unsqueeze(0).expand(batch_size, -1, -1) + query = query.unsqueeze(0).expand(batch_size, -1, -1) + reference_points = self.reference_points_fc(query_pos).sigmoid() + + decoder_inputs_dict = dict( + query=query, + query_pos=query_pos, + memory=memory, + reference_points=reference_points) + head_inputs_dict = dict( + enc_outputs_class=enc_outputs_class, + enc_outputs_coord=enc_outputs_coord) if self.training else dict() + return decoder_inputs_dict, head_inputs_dict + + def forward_decoder(self, query: Tensor, query_pos: Tensor, memory: Tensor, + memory_mask: Tensor, reference_points: Tensor, + spatial_shapes: Tensor, level_start_index: Tensor, + valid_ratios: Tensor) -> Dict: + """Forward with Transformer decoder. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + + Args: + query (Tensor): The queries of decoder inputs, has shape + (bs, num_queries, dim). + query_pos (Tensor): The positional queries of decoder inputs, + has shape (bs, num_queries, dim). + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + memory_mask (Tensor): ByteTensor, the padding mask of the memory, + has shape (bs, num_feat_points). + reference_points (Tensor): The initial reference, has shape + (bs, num_queries, 4) with the last dimension arranged as + (cx, cy, w, h) when `as_two_stage` is `True`, otherwise has + shape (bs, num_queries, 2) with the last dimension arranged as + (cx, cy). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape (num_levels, ) and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + + Returns: + dict: The dictionary of decoder outputs, which includes the + `hidden_states` of the decoder output and `references` including + the initial and intermediate reference_points. + """ + inter_states, inter_references = self.decoder( + query=query, + value=memory, + query_pos=query_pos, + key_padding_mask=memory_mask, # for cross_attn + reference_points=reference_points, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reg_branches=self.bbox_head.reg_branches + if self.with_box_refine else None) + references = [reference_points, *inter_references] + decoder_outputs_dict = dict( + hidden_states=inter_states, references=references) + return decoder_outputs_dict + + @staticmethod + def get_valid_ratio(mask: Tensor) -> Tensor: + """Get the valid radios of feature map in a level. + + .. code:: text + + |---> valid_W <---| + ---+-----------------+-----+--- + A | | | A + | | | | | + | | | | | + valid_H | | | | + | | | | H + | | | | | + V | | | | + ---+-----------------+ | | + | | V + +-----------------------+--- + |---------> W <---------| + + The valid_ratios are defined as: + r_h = valid_H / H, r_w = valid_W / W + They are the factors to re-normalize the relative coordinates of the + image to the relative coordinates of the current level feature map. + + Args: + mask (Tensor): Binary mask of a feature map, has shape (bs, H, W). + + Returns: + Tensor: valid ratios [r_w, r_h] of a feature map, has shape (1, 2). + """ + _, H, W = mask.shape + valid_H = torch.sum(~mask[:, :, 0], 1) + valid_W = torch.sum(~mask[:, 0, :], 1) + valid_ratio_h = valid_H.float() / H + valid_ratio_w = valid_W.float() / W + valid_ratio = torch.stack([valid_ratio_w, valid_ratio_h], -1) + return valid_ratio + + def gen_encoder_output_proposals( + self, memory: Tensor, memory_mask: Tensor, + spatial_shapes: Tensor) -> Tuple[Tensor, Tensor]: + """Generate proposals from encoded memory. The function will only be + used when `as_two_stage` is `True`. + + Args: + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + memory_mask (Tensor): ByteTensor, the padding mask of the memory, + has shape (bs, num_feat_points). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + + Returns: + tuple: A tuple of transformed memory and proposals. + + - output_memory (Tensor): The transformed memory for obtaining + top-k proposals, has shape (bs, num_feat_points, dim). + - output_proposals (Tensor): The inverse-normalized proposal, has + shape (batch_size, num_keys, 4) with the last dimension arranged + as (cx, cy, w, h). + """ + + bs = memory.size(0) + proposals = [] + _cur = 0 # start index in the sequence of the current level + for lvl, HW in enumerate(spatial_shapes): + H, W = HW + + if memory_mask is not None: + mask_flatten_ = memory_mask[:, _cur:(_cur + H * W)].view( + bs, H, W, 1) + valid_H = torch.sum(~mask_flatten_[:, :, 0, 0], + 1).unsqueeze(-1) + valid_W = torch.sum(~mask_flatten_[:, 0, :, 0], + 1).unsqueeze(-1) + scale = torch.cat([valid_W, valid_H], 1).view(bs, 1, 1, 2) + else: + if not isinstance(HW, torch.Tensor): + HW = memory.new_tensor(HW) + scale = HW.unsqueeze(0).flip(dims=[0, 1]).view(1, 1, 1, 2) + grid_y, grid_x = torch.meshgrid( + torch.linspace( + 0, H - 1, H, dtype=torch.float32, device=memory.device), + torch.linspace( + 0, W - 1, W, dtype=torch.float32, device=memory.device)) + grid = torch.cat([grid_x.unsqueeze(-1), grid_y.unsqueeze(-1)], -1) + grid = (grid.unsqueeze(0).expand(bs, -1, -1, -1) + 0.5) / scale + wh = torch.ones_like(grid) * 0.05 * (2.0**lvl) + proposal = torch.cat((grid, wh), -1).view(bs, -1, 4) + proposals.append(proposal) + _cur += (H * W) + output_proposals = torch.cat(proposals, 1) + # do not use `all` to make it exportable to onnx + output_proposals_valid = ( + (output_proposals > 0.01) & (output_proposals < 0.99)).sum( + -1, keepdim=True) == output_proposals.shape[-1] + # inverse_sigmoid + output_proposals = torch.log(output_proposals / (1 - output_proposals)) + if memory_mask is not None: + output_proposals = output_proposals.masked_fill( + memory_mask.unsqueeze(-1), float('inf')) + output_proposals = output_proposals.masked_fill( + ~output_proposals_valid, float('inf')) + + output_memory = memory + if memory_mask is not None: + output_memory = output_memory.masked_fill( + memory_mask.unsqueeze(-1), float(0)) + output_memory = output_memory.masked_fill(~output_proposals_valid, + float(0)) + output_memory = self.memory_trans_fc(output_memory) + output_memory = self.memory_trans_norm(output_memory) + # [bs, sum(hw), 2] + return output_memory, output_proposals + + @staticmethod + def get_proposal_pos_embed(proposals: Tensor, + num_pos_feats: int = 128, + temperature: int = 10000) -> Tensor: + """Get the position embedding of the proposal. + + Args: + proposals (Tensor): Not normalized proposals, has shape + (bs, num_queries, 4) with the last dimension arranged as + (cx, cy, w, h). + num_pos_feats (int, optional): The feature dimension for each + position along x, y, w, and h-axis. Note the final returned + dimension for each position is 4 times of num_pos_feats. + Default to 128. + temperature (int, optional): The temperature used for scaling the + position embedding. Defaults to 10000. + + Returns: + Tensor: The position embedding of proposal, has shape + (bs, num_queries, num_pos_feats * 4), with the last dimension + arranged as (cx, cy, w, h) + """ + scale = 2 * math.pi + dim_t = torch.arange( + num_pos_feats, dtype=torch.float32, device=proposals.device) + dim_t = temperature**(2 * (dim_t // 2) / num_pos_feats) + # N, L, 4 + proposals = proposals.sigmoid() * scale + # N, L, 4, 128 + pos = proposals[:, :, :, None] / dim_t + # N, L, 4, 64, 2 + pos = torch.stack((pos[:, :, :, 0::2].sin(), pos[:, :, :, 1::2].cos()), + dim=4).flatten(2) + return pos diff --git a/mmdetection/mmdet/models/detectors/detr.py b/mmdetection/mmdet/models/detectors/detr.py new file mode 100644 index 00000000..7895e9ec --- /dev/null +++ b/mmdetection/mmdet/models/detectors/detr.py @@ -0,0 +1,225 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, Tuple + +import torch +import torch.nn.functional as F +from torch import Tensor, nn + +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList +from ..layers import (DetrTransformerDecoder, DetrTransformerEncoder, + SinePositionalEncoding) +from .base_detr import DetectionTransformer + + +@MODELS.register_module() +class DETR(DetectionTransformer): + r"""Implementation of `DETR: End-to-End Object Detection with Transformers. + + `_. + + Code is modified from the `official github repo + `_. + """ + + def _init_layers(self) -> None: + """Initialize layers except for backbone, neck and bbox_head.""" + self.positional_encoding = SinePositionalEncoding( + **self.positional_encoding) + self.encoder = DetrTransformerEncoder(**self.encoder) + self.decoder = DetrTransformerDecoder(**self.decoder) + self.embed_dims = self.encoder.embed_dims + # NOTE The embed_dims is typically passed from the inside out. + # For example in DETR, The embed_dims is passed as + # self_attn -> the first encoder layer -> encoder -> detector. + self.query_embedding = nn.Embedding(self.num_queries, self.embed_dims) + + num_feats = self.positional_encoding.num_feats + assert num_feats * 2 == self.embed_dims, \ + 'embed_dims should be exactly 2 times of num_feats. ' \ + f'Found {self.embed_dims} and {num_feats}.' + + def init_weights(self) -> None: + """Initialize weights for Transformer and other components.""" + super().init_weights() + for coder in self.encoder, self.decoder: + for p in coder.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + + def pre_transformer( + self, + img_feats: Tuple[Tensor], + batch_data_samples: OptSampleList = None) -> Tuple[Dict, Dict]: + """Prepare the inputs of the Transformer. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + + Args: + img_feats (Tuple[Tensor]): Tuple of features output from the neck, + has shape (bs, c, h, w). + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such as + `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + Defaults to None. + + Returns: + tuple[dict, dict]: The first dict contains the inputs of encoder + and the second dict contains the inputs of decoder. + + - encoder_inputs_dict (dict): The keyword args dictionary of + `self.forward_encoder()`, which includes 'feat', 'feat_mask', + and 'feat_pos'. + - decoder_inputs_dict (dict): The keyword args dictionary of + `self.forward_decoder()`, which includes 'memory_mask', + and 'memory_pos'. + """ + + feat = img_feats[-1] # NOTE img_feats contains only one feature. + batch_size, feat_dim, _, _ = feat.shape + # construct binary masks which for the transformer. + assert batch_data_samples is not None + batch_input_shape = batch_data_samples[0].batch_input_shape + input_img_h, input_img_w = batch_input_shape + img_shape_list = [sample.img_shape for sample in batch_data_samples] + same_shape_flag = all([ + s[0] == input_img_h and s[1] == input_img_w for s in img_shape_list + ]) + if torch.onnx.is_in_onnx_export() or same_shape_flag: + masks = None + # [batch_size, embed_dim, h, w] + pos_embed = self.positional_encoding(masks, input=feat) + else: + masks = feat.new_ones((batch_size, input_img_h, input_img_w)) + for img_id in range(batch_size): + img_h, img_w = img_shape_list[img_id] + masks[img_id, :img_h, :img_w] = 0 + # NOTE following the official DETR repo, non-zero values represent + # ignored positions, while zero values mean valid positions. + + masks = F.interpolate( + masks.unsqueeze(1), + size=feat.shape[-2:]).to(torch.bool).squeeze(1) + # [batch_size, embed_dim, h, w] + pos_embed = self.positional_encoding(masks) + + # use `view` instead of `flatten` for dynamically exporting to ONNX + # [bs, c, h, w] -> [bs, h*w, c] + feat = feat.view(batch_size, feat_dim, -1).permute(0, 2, 1) + pos_embed = pos_embed.view(batch_size, feat_dim, -1).permute(0, 2, 1) + # [bs, h, w] -> [bs, h*w] + if masks is not None: + masks = masks.view(batch_size, -1) + + # prepare transformer_inputs_dict + encoder_inputs_dict = dict( + feat=feat, feat_mask=masks, feat_pos=pos_embed) + decoder_inputs_dict = dict(memory_mask=masks, memory_pos=pos_embed) + return encoder_inputs_dict, decoder_inputs_dict + + def forward_encoder(self, feat: Tensor, feat_mask: Tensor, + feat_pos: Tensor) -> Dict: + """Forward with Transformer encoder. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + + Args: + feat (Tensor): Sequential features, has shape (bs, num_feat_points, + dim). + feat_mask (Tensor): ByteTensor, the padding mask of the features, + has shape (bs, num_feat_points). + feat_pos (Tensor): The positional embeddings of the features, has + shape (bs, num_feat_points, dim). + + Returns: + dict: The dictionary of encoder outputs, which includes the + `memory` of the encoder output. + """ + memory = self.encoder( + query=feat, query_pos=feat_pos, + key_padding_mask=feat_mask) # for self_attn + encoder_outputs_dict = dict(memory=memory) + return encoder_outputs_dict + + def pre_decoder(self, memory: Tensor) -> Tuple[Dict, Dict]: + """Prepare intermediate variables before entering Transformer decoder, + such as `query`, `query_pos`. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + + Args: + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + + Returns: + tuple[dict, dict]: The first dict contains the inputs of decoder + and the second dict contains the inputs of the bbox_head function. + + - decoder_inputs_dict (dict): The keyword args dictionary of + `self.forward_decoder()`, which includes 'query', 'query_pos', + 'memory'. + - head_inputs_dict (dict): The keyword args dictionary of the + bbox_head functions, which is usually empty, or includes + `enc_outputs_class` and `enc_outputs_class` when the detector + support 'two stage' or 'query selection' strategies. + """ + + batch_size = memory.size(0) # (bs, num_feat_points, dim) + query_pos = self.query_embedding.weight + # (num_queries, dim) -> (bs, num_queries, dim) + query_pos = query_pos.unsqueeze(0).repeat(batch_size, 1, 1) + query = torch.zeros_like(query_pos) + + decoder_inputs_dict = dict( + query_pos=query_pos, query=query, memory=memory) + head_inputs_dict = dict() + return decoder_inputs_dict, head_inputs_dict + + def forward_decoder(self, query: Tensor, query_pos: Tensor, memory: Tensor, + memory_mask: Tensor, memory_pos: Tensor) -> Dict: + """Forward with Transformer decoder. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + + Args: + query (Tensor): The queries of decoder inputs, has shape + (bs, num_queries, dim). + query_pos (Tensor): The positional queries of decoder inputs, + has shape (bs, num_queries, dim). + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + memory_mask (Tensor): ByteTensor, the padding mask of the memory, + has shape (bs, num_feat_points). + memory_pos (Tensor): The positional embeddings of memory, has + shape (bs, num_feat_points, dim). + + Returns: + dict: The dictionary of decoder outputs, which includes the + `hidden_states` of the decoder output. + + - hidden_states (Tensor): Has shape + (num_decoder_layers, bs, num_queries, dim) + """ + + hidden_states = self.decoder( + query=query, + key=memory, + value=memory, + query_pos=query_pos, + key_pos=memory_pos, + key_padding_mask=memory_mask) # for cross_attn + + head_inputs_dict = dict(hidden_states=hidden_states) + return head_inputs_dict diff --git a/mmdetection/mmdet/models/detectors/dino.py b/mmdetection/mmdet/models/detectors/dino.py new file mode 100644 index 00000000..ade47f53 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/dino.py @@ -0,0 +1,287 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, Optional, Tuple + +import torch +from torch import Tensor, nn +from torch.nn.init import normal_ + +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList +from mmdet.utils import OptConfigType +from ..layers import (CdnQueryGenerator, DeformableDetrTransformerEncoder, + DinoTransformerDecoder, SinePositionalEncoding) +from .deformable_detr import DeformableDETR, MultiScaleDeformableAttention + + +@MODELS.register_module() +class DINO(DeformableDETR): + r"""Implementation of `DINO: DETR with Improved DeNoising Anchor Boxes + for End-to-End Object Detection `_ + + Code is modified from the `official github repo + `_. + + Args: + dn_cfg (:obj:`ConfigDict` or dict, optional): Config of denoising + query generator. Defaults to `None`. + """ + + def __init__(self, *args, dn_cfg: OptConfigType = None, **kwargs) -> None: + super().__init__(*args, **kwargs) + assert self.as_two_stage, 'as_two_stage must be True for DINO' + assert self.with_box_refine, 'with_box_refine must be True for DINO' + + if dn_cfg is not None: + assert 'num_classes' not in dn_cfg and \ + 'num_queries' not in dn_cfg and \ + 'hidden_dim' not in dn_cfg, \ + 'The three keyword args `num_classes`, `embed_dims`, and ' \ + '`num_matching_queries` are set in `detector.__init__()`, ' \ + 'users should not set them in `dn_cfg` config.' + dn_cfg['num_classes'] = self.bbox_head.num_classes + dn_cfg['embed_dims'] = self.embed_dims + dn_cfg['num_matching_queries'] = self.num_queries + self.dn_query_generator = CdnQueryGenerator(**dn_cfg) + + def _init_layers(self) -> None: + """Initialize layers except for backbone, neck and bbox_head.""" + self.positional_encoding = SinePositionalEncoding( + **self.positional_encoding) + self.encoder = DeformableDetrTransformerEncoder(**self.encoder) + self.decoder = DinoTransformerDecoder(**self.decoder) + self.embed_dims = self.encoder.embed_dims + self.query_embedding = nn.Embedding(self.num_queries, self.embed_dims) + # NOTE In DINO, the query_embedding only contains content + # queries, while in Deformable DETR, the query_embedding + # contains both content and spatial queries, and in DETR, + # it only contains spatial queries. + + num_feats = self.positional_encoding.num_feats + assert num_feats * 2 == self.embed_dims, \ + f'embed_dims should be exactly 2 times of num_feats. ' \ + f'Found {self.embed_dims} and {num_feats}.' + + self.level_embed = nn.Parameter( + torch.Tensor(self.num_feature_levels, self.embed_dims)) + self.memory_trans_fc = nn.Linear(self.embed_dims, self.embed_dims) + self.memory_trans_norm = nn.LayerNorm(self.embed_dims) + + def init_weights(self) -> None: + """Initialize weights for Transformer and other components.""" + super(DeformableDETR, self).init_weights() + for coder in self.encoder, self.decoder: + for p in coder.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + for m in self.modules(): + if isinstance(m, MultiScaleDeformableAttention): + m.init_weights() + nn.init.xavier_uniform_(self.memory_trans_fc.weight) + nn.init.xavier_uniform_(self.query_embedding.weight) + normal_(self.level_embed) + + def forward_transformer( + self, + img_feats: Tuple[Tensor], + batch_data_samples: OptSampleList = None, + ) -> Dict: + """Forward process of Transformer. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + The difference is that the ground truth in `batch_data_samples` is + required for the `pre_decoder` to prepare the query of DINO. + Additionally, DINO inherits the `pre_transformer` method and the + `forward_encoder` method of DeformableDETR. More details about the + two methods can be found in `mmdet/detector/deformable_detr.py`. + + Args: + img_feats (tuple[Tensor]): Tuple of feature maps from neck. Each + feature map has shape (bs, dim, H, W). + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + Defaults to None. + + Returns: + dict: The dictionary of bbox_head function inputs, which always + includes the `hidden_states` of the decoder output and may contain + `references` including the initial and intermediate references. + """ + encoder_inputs_dict, decoder_inputs_dict = self.pre_transformer( + img_feats, batch_data_samples) + + encoder_outputs_dict = self.forward_encoder(**encoder_inputs_dict) + + tmp_dec_in, head_inputs_dict = self.pre_decoder( + **encoder_outputs_dict, batch_data_samples=batch_data_samples) + decoder_inputs_dict.update(tmp_dec_in) + + decoder_outputs_dict = self.forward_decoder(**decoder_inputs_dict) + head_inputs_dict.update(decoder_outputs_dict) + return head_inputs_dict + + def pre_decoder( + self, + memory: Tensor, + memory_mask: Tensor, + spatial_shapes: Tensor, + batch_data_samples: OptSampleList = None, + ) -> Tuple[Dict]: + """Prepare intermediate variables before entering Transformer decoder, + such as `query`, `query_pos`, and `reference_points`. + + Args: + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + memory_mask (Tensor): ByteTensor, the padding mask of the memory, + has shape (bs, num_feat_points). Will only be used when + `as_two_stage` is `True`. + spatial_shapes (Tensor): Spatial shapes of features in all levels. + With shape (num_levels, 2), last dimension represents (h, w). + Will only be used when `as_two_stage` is `True`. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + Defaults to None. + + Returns: + tuple[dict]: The decoder_inputs_dict and head_inputs_dict. + + - decoder_inputs_dict (dict): The keyword dictionary args of + `self.forward_decoder()`, which includes 'query', 'memory', + `reference_points`, and `dn_mask`. The reference points of + decoder input here are 4D boxes, although it has `points` + in its name. + - head_inputs_dict (dict): The keyword dictionary args of the + bbox_head functions, which includes `topk_score`, `topk_coords`, + and `dn_meta` when `self.training` is `True`, else is empty. + """ + bs, _, c = memory.shape + cls_out_features = self.bbox_head.cls_branches[ + self.decoder.num_layers].out_features + + output_memory, output_proposals = self.gen_encoder_output_proposals( + memory, memory_mask, spatial_shapes) + enc_outputs_class = self.bbox_head.cls_branches[ + self.decoder.num_layers]( + output_memory) + enc_outputs_coord_unact = self.bbox_head.reg_branches[ + self.decoder.num_layers](output_memory) + output_proposals + + # NOTE The DINO selects top-k proposals according to scores of + # multi-class classification, while DeformDETR, where the input + # is `enc_outputs_class[..., 0]` selects according to scores of + # binary classification. + topk_indices = torch.topk( + enc_outputs_class.max(-1)[0], k=self.num_queries, dim=1)[1] + topk_score = torch.gather( + enc_outputs_class, 1, + topk_indices.unsqueeze(-1).repeat(1, 1, cls_out_features)) + topk_coords_unact = torch.gather( + enc_outputs_coord_unact, 1, + topk_indices.unsqueeze(-1).repeat(1, 1, 4)) + topk_coords = topk_coords_unact.sigmoid() + topk_coords_unact = topk_coords_unact.detach() + + query = self.query_embedding.weight[:, None, :] + query = query.repeat(1, bs, 1).transpose(0, 1) + if self.training: + dn_label_query, dn_bbox_query, dn_mask, dn_meta = \ + self.dn_query_generator(batch_data_samples) + query = torch.cat([dn_label_query, query], dim=1) + reference_points = torch.cat([dn_bbox_query, topk_coords_unact], + dim=1) + else: + reference_points = topk_coords_unact + dn_mask, dn_meta = None, None + reference_points = reference_points.sigmoid() + + decoder_inputs_dict = dict( + query=query, + memory=memory, + reference_points=reference_points, + dn_mask=dn_mask) + # NOTE DINO calculates encoder losses on scores and coordinates + # of selected top-k encoder queries, while DeformDETR is of all + # encoder queries. + head_inputs_dict = dict( + enc_outputs_class=topk_score, + enc_outputs_coord=topk_coords, + dn_meta=dn_meta) if self.training else dict() + return decoder_inputs_dict, head_inputs_dict + + def forward_decoder(self, + query: Tensor, + memory: Tensor, + memory_mask: Tensor, + reference_points: Tensor, + spatial_shapes: Tensor, + level_start_index: Tensor, + valid_ratios: Tensor, + dn_mask: Optional[Tensor] = None, + **kwargs) -> Dict: + """Forward with Transformer decoder. + + The forward procedure of the transformer is defined as: + 'pre_transformer' -> 'encoder' -> 'pre_decoder' -> 'decoder' + More details can be found at `TransformerDetector.forward_transformer` + in `mmdet/detector/base_detr.py`. + + Args: + query (Tensor): The queries of decoder inputs, has shape + (bs, num_queries_total, dim), where `num_queries_total` is the + sum of `num_denoising_queries` and `num_matching_queries` when + `self.training` is `True`, else `num_matching_queries`. + memory (Tensor): The output embeddings of the Transformer encoder, + has shape (bs, num_feat_points, dim). + memory_mask (Tensor): ByteTensor, the padding mask of the memory, + has shape (bs, num_feat_points). + reference_points (Tensor): The initial reference, has shape + (bs, num_queries_total, 4) with the last dimension arranged as + (cx, cy, w, h). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape (num_levels, ) and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + dn_mask (Tensor, optional): The attention mask to prevent + information leakage from different denoising groups and + matching parts, will be used as `self_attn_mask` of the + `self.decoder`, has shape (num_queries_total, + num_queries_total). + It is `None` when `self.training` is `False`. + + Returns: + dict: The dictionary of decoder outputs, which includes the + `hidden_states` of the decoder output and `references` including + the initial and intermediate reference_points. + """ + inter_states, references = self.decoder( + query=query, + value=memory, + key_padding_mask=memory_mask, + self_attn_mask=dn_mask, + reference_points=reference_points, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reg_branches=self.bbox_head.reg_branches, + **kwargs) + + if len(query) == self.num_queries: + # NOTE: This is to make sure label_embeding can be involved to + # produce loss even if there is no denoising query (no ground truth + # target in this GPU), otherwise, this will raise runtime error in + # distributed training. + inter_states[0] += \ + self.dn_query_generator.label_embedding.weight[0, 0] * 0.0 + + decoder_outputs_dict = dict( + hidden_states=inter_states, references=list(references)) + return decoder_outputs_dict diff --git a/mmdetection/mmdet/models/detectors/fast_rcnn.py b/mmdetection/mmdet/models/detectors/fast_rcnn.py new file mode 100644 index 00000000..5b39050f --- /dev/null +++ b/mmdetection/mmdet/models/detectors/fast_rcnn.py @@ -0,0 +1,26 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class FastRCNN(TwoStageDetector): + """Implementation of `Fast R-CNN `_""" + + def __init__(self, + backbone: ConfigType, + roi_head: ConfigType, + train_cfg: ConfigType, + test_cfg: ConfigType, + neck: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + init_cfg=init_cfg, + data_preprocessor=data_preprocessor) diff --git a/mmdetection/mmdet/models/detectors/faster_rcnn.py b/mmdetection/mmdet/models/detectors/faster_rcnn.py new file mode 100644 index 00000000..36109e32 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/faster_rcnn.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class FasterRCNN(TwoStageDetector): + """Implementation of `Faster R-CNN `_""" + + def __init__(self, + backbone: ConfigType, + rpn_head: ConfigType, + roi_head: ConfigType, + train_cfg: ConfigType, + test_cfg: ConfigType, + neck: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + init_cfg=init_cfg, + data_preprocessor=data_preprocessor) diff --git a/mmdetection/mmdet/models/detectors/fcos.py b/mmdetection/mmdet/models/detectors/fcos.py new file mode 100644 index 00000000..c6280593 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/fcos.py @@ -0,0 +1,42 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class FCOS(SingleStageDetector): + """Implementation of `FCOS `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone config. + neck (:obj:`ConfigDict` or dict): The neck config. + bbox_head (:obj:`ConfigDict` or dict): The bbox head config. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of FCOS. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of FCOS. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/fovea.py b/mmdetection/mmdet/models/detectors/fovea.py new file mode 100644 index 00000000..5e4f21ca --- /dev/null +++ b/mmdetection/mmdet/models/detectors/fovea.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class FOVEA(SingleStageDetector): + """Implementation of `FoveaBox `_ + Args: + backbone (:obj:`ConfigDict` or dict): The backbone config. + neck (:obj:`ConfigDict` or dict): The neck config. + bbox_head (:obj:`ConfigDict` or dict): The bbox head config. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of FOVEA. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of FOVEA. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/fsaf.py b/mmdetection/mmdet/models/detectors/fsaf.py new file mode 100644 index 00000000..01b40273 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/fsaf.py @@ -0,0 +1,26 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class FSAF(SingleStageDetector): + """Implementation of `FSAF `_""" + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/gfl.py b/mmdetection/mmdet/models/detectors/gfl.py new file mode 100644 index 00000000..c26821af --- /dev/null +++ b/mmdetection/mmdet/models/detectors/gfl.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class GFL(SingleStageDetector): + """Implementation of `GFL `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of GFL. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of GFL. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/glip.py b/mmdetection/mmdet/models/detectors/glip.py new file mode 100644 index 00000000..45cfe7d3 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/glip.py @@ -0,0 +1,590 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import re +import warnings +from typing import Optional, Tuple, Union + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +def find_noun_phrases(caption: str) -> list: + """Find noun phrases in a caption using nltk. + Args: + caption (str): The caption to analyze. + + Returns: + list: List of noun phrases found in the caption. + + Examples: + >>> caption = 'There is two cat and a remote in the picture' + >>> find_noun_phrases(caption) # ['cat', 'a remote', 'the picture'] + """ + try: + import nltk + nltk.download('punkt', download_dir='~/nltk_data') + nltk.download('averaged_perceptron_tagger', download_dir='~/nltk_data') + except ImportError: + raise RuntimeError('nltk is not installed, please install it by: ' + 'pip install nltk.') + + caption = caption.lower() + tokens = nltk.word_tokenize(caption) + pos_tags = nltk.pos_tag(tokens) + + grammar = 'NP: {
    ?*+}' + cp = nltk.RegexpParser(grammar) + result = cp.parse(pos_tags) + + noun_phrases = [] + for subtree in result.subtrees(): + if subtree.label() == 'NP': + noun_phrases.append(' '.join(t[0] for t in subtree.leaves())) + + return noun_phrases + + +def remove_punctuation(text: str) -> str: + """Remove punctuation from a text. + Args: + text (str): The input text. + + Returns: + str: The text with punctuation removed. + """ + punctuation = [ + '|', ':', ';', '@', '(', ')', '[', ']', '{', '}', '^', '\'', '\"', '’', + '`', '?', '$', '%', '#', '!', '&', '*', '+', ',', '.' + ] + for p in punctuation: + text = text.replace(p, '') + return text.strip() + + +def run_ner(caption: str) -> Tuple[list, list]: + """Run NER on a caption and return the tokens and noun phrases. + Args: + caption (str): The input caption. + + Returns: + Tuple[List, List]: A tuple containing the tokens and noun phrases. + - tokens_positive (List): A list of token positions. + - noun_phrases (List): A list of noun phrases. + """ + noun_phrases = find_noun_phrases(caption) + noun_phrases = [remove_punctuation(phrase) for phrase in noun_phrases] + noun_phrases = [phrase for phrase in noun_phrases if phrase != ''] + print('noun_phrases:', noun_phrases) + relevant_phrases = noun_phrases + labels = noun_phrases + + tokens_positive = [] + for entity, label in zip(relevant_phrases, labels): + try: + # search all occurrences and mark them as different entities + # TODO: Not Robust + for m in re.finditer(entity, caption.lower()): + tokens_positive.append([[m.start(), m.end()]]) + except Exception: + print('noun entities:', noun_phrases) + print('entity:', entity) + print('caption:', caption.lower()) + return tokens_positive, noun_phrases + + +def create_positive_map(tokenized, + tokens_positive: list, + max_num_entities: int = 256) -> Tensor: + """construct a map such that positive_map[i,j] = True + if box i is associated to token j + + Args: + tokenized: The tokenized input. + tokens_positive (list): A list of token ranges + associated with positive boxes. + max_num_entities (int, optional): The maximum number of entities. + Defaults to 256. + + Returns: + torch.Tensor: The positive map. + + Raises: + Exception: If an error occurs during token-to-char mapping. + """ + positive_map = torch.zeros((len(tokens_positive), max_num_entities), + dtype=torch.float) + + for j, tok_list in enumerate(tokens_positive): + for (beg, end) in tok_list: + try: + beg_pos = tokenized.char_to_token(beg) + end_pos = tokenized.char_to_token(end - 1) + except Exception as e: + print('beg:', beg, 'end:', end) + print('token_positive:', tokens_positive) + raise e + if beg_pos is None: + try: + beg_pos = tokenized.char_to_token(beg + 1) + if beg_pos is None: + beg_pos = tokenized.char_to_token(beg + 2) + except Exception: + beg_pos = None + if end_pos is None: + try: + end_pos = tokenized.char_to_token(end - 2) + if end_pos is None: + end_pos = tokenized.char_to_token(end - 3) + except Exception: + end_pos = None + if beg_pos is None or end_pos is None: + continue + + assert beg_pos is not None and end_pos is not None + positive_map[j, beg_pos:end_pos + 1].fill_(1) + return positive_map / (positive_map.sum(-1)[:, None] + 1e-6) + + +def create_positive_map_label_to_token(positive_map: Tensor, + plus: int = 0) -> dict: + """Create a dictionary mapping the label to the token. + Args: + positive_map (Tensor): The positive map tensor. + plus (int, optional): Value added to the label for indexing. + Defaults to 0. + + Returns: + dict: The dictionary mapping the label to the token. + """ + positive_map_label_to_token = {} + for i in range(len(positive_map)): + positive_map_label_to_token[i + plus] = torch.nonzero( + positive_map[i], as_tuple=True)[0].tolist() + return positive_map_label_to_token + + +def clean_label_name(name: str) -> str: + name = re.sub(r'\(.*\)', '', name) + name = re.sub(r'_', ' ', name) + name = re.sub(r' ', ' ', name) + return name + + +def chunks(lst: list, n: int) -> list: + """Yield successive n-sized chunks from lst.""" + all_ = [] + for i in range(0, len(lst), n): + data_index = lst[i:i + n] + all_.append(data_index) + counter = 0 + for i in all_: + counter += len(i) + assert (counter == len(lst)) + + return all_ + + +@MODELS.register_module() +class GLIP(SingleStageDetector): + """Implementation of `GLIP `_ + Args: + backbone (:obj:`ConfigDict` or dict): The backbone config. + neck (:obj:`ConfigDict` or dict): The neck config. + bbox_head (:obj:`ConfigDict` or dict): The bbox head config. + language_model (:obj:`ConfigDict` or dict): The language model config. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of GLIP. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of GLIP. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + language_model: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) + self.language_model = MODELS.build(language_model) + + self._special_tokens = '. ' + + def to_enhance_text_prompts(self, original_caption, enhanced_text_prompts): + caption_string = '' + tokens_positive = [] + for idx, word in enumerate(original_caption): + if word in enhanced_text_prompts: + enhanced_text_dict = enhanced_text_prompts[word] + if 'prefix' in enhanced_text_dict: + caption_string += enhanced_text_dict['prefix'] + start_i = len(caption_string) + if 'name' in enhanced_text_dict: + caption_string += enhanced_text_dict['name'] + else: + caption_string += word + end_i = len(caption_string) + tokens_positive.append([[start_i, end_i]]) + + if 'suffix' in enhanced_text_dict: + caption_string += enhanced_text_dict['suffix'] + else: + tokens_positive.append( + [[len(caption_string), + len(caption_string) + len(word)]]) + caption_string += word + + if idx != len(original_caption) - 1: + caption_string += self._special_tokens + return caption_string, tokens_positive + + def to_plain_text_prompts(self, original_caption): + caption_string = '' + tokens_positive = [] + for idx, word in enumerate(original_caption): + tokens_positive.append( + [[len(caption_string), + len(caption_string) + len(word)]]) + caption_string += word + if idx != len(original_caption) - 1: + caption_string += self._special_tokens + return caption_string, tokens_positive + + def get_tokens_and_prompts( + self, + original_caption: Union[str, list, tuple], + custom_entities: bool = False, + enhanced_text_prompts: Optional[ConfigType] = None + ) -> Tuple[dict, str, list, list]: + """Get the tokens positive and prompts for the caption.""" + if isinstance(original_caption, (list, tuple)) or custom_entities: + if custom_entities and isinstance(original_caption, str): + original_caption = original_caption.strip(self._special_tokens) + original_caption = original_caption.split(self._special_tokens) + original_caption = list( + filter(lambda x: len(x) > 0, original_caption)) + + original_caption = [clean_label_name(i) for i in original_caption] + + if custom_entities and enhanced_text_prompts is not None: + caption_string, tokens_positive = self.to_enhance_text_prompts( + original_caption, enhanced_text_prompts) + else: + caption_string, tokens_positive = self.to_plain_text_prompts( + original_caption) + + tokenized = self.language_model.tokenizer([caption_string], + return_tensors='pt') + entities = original_caption + else: + original_caption = original_caption.strip(self._special_tokens) + tokenized = self.language_model.tokenizer([original_caption], + return_tensors='pt') + tokens_positive, noun_phrases = run_ner(original_caption) + entities = noun_phrases + caption_string = original_caption + + return tokenized, caption_string, tokens_positive, entities + + def get_positive_map(self, tokenized, tokens_positive): + positive_map = create_positive_map(tokenized, tokens_positive) + positive_map_label_to_token = create_positive_map_label_to_token( + positive_map, plus=1) + return positive_map_label_to_token, positive_map + + def get_tokens_positive_and_prompts( + self, + original_caption: Union[str, list, tuple], + custom_entities: bool = False, + enhanced_text_prompt: Optional[ConfigType] = None, + tokens_positive: Optional[list] = None, + ) -> Tuple[dict, str, Tensor, list]: + if tokens_positive is not None: + if tokens_positive == -1: + if not original_caption.endswith('.'): + original_caption = original_caption + self._special_tokens + return None, original_caption, None, original_caption + else: + if not original_caption.endswith('.'): + original_caption = original_caption + self._special_tokens + tokenized = self.language_model.tokenizer([original_caption], + return_tensors='pt') + positive_map_label_to_token, positive_map = \ + self.get_positive_map(tokenized, tokens_positive) + + entities = [] + for token_positive in tokens_positive: + instance_entities = [] + for t in token_positive: + instance_entities.append(original_caption[t[0]:t[1]]) + entities.append(' / '.join(instance_entities)) + return positive_map_label_to_token, original_caption, \ + positive_map, entities + + chunked_size = self.test_cfg.get('chunked_size', -1) + if not self.training and chunked_size > 0: + assert isinstance(original_caption, + (list, tuple)) or custom_entities is True + all_output = self.get_tokens_positive_and_prompts_chunked( + original_caption, enhanced_text_prompt) + positive_map_label_to_token, \ + caption_string, \ + positive_map, \ + entities = all_output + else: + tokenized, caption_string, tokens_positive, entities = \ + self.get_tokens_and_prompts( + original_caption, custom_entities, enhanced_text_prompt) + positive_map_label_to_token, positive_map = self.get_positive_map( + tokenized, tokens_positive) + if tokenized.input_ids.shape[1] > self.language_model.max_tokens: + warnings.warn('Inputting a text that is too long will result ' + 'in poor prediction performance. ' + 'Please reduce the text length.') + return positive_map_label_to_token, caption_string, \ + positive_map, entities + + def get_tokens_positive_and_prompts_chunked( + self, + original_caption: Union[list, tuple], + enhanced_text_prompts: Optional[ConfigType] = None): + chunked_size = self.test_cfg.get('chunked_size', -1) + original_caption = [clean_label_name(i) for i in original_caption] + + original_caption_chunked = chunks(original_caption, chunked_size) + ids_chunked = chunks( + list(range(1, + len(original_caption) + 1)), chunked_size) + + positive_map_label_to_token_chunked = [] + caption_string_chunked = [] + positive_map_chunked = [] + entities_chunked = [] + + for i in range(len(ids_chunked)): + if enhanced_text_prompts is not None: + caption_string, tokens_positive = self.to_enhance_text_prompts( + original_caption_chunked[i], enhanced_text_prompts) + else: + caption_string, tokens_positive = self.to_plain_text_prompts( + original_caption_chunked[i]) + tokenized = self.language_model.tokenizer([caption_string], + return_tensors='pt') + if tokenized.input_ids.shape[1] > self.language_model.max_tokens: + warnings.warn('Inputting a text that is too long will result ' + 'in poor prediction performance. ' + 'Please reduce the --chunked-size.') + positive_map_label_to_token, positive_map = self.get_positive_map( + tokenized, tokens_positive) + + caption_string_chunked.append(caption_string) + positive_map_label_to_token_chunked.append( + positive_map_label_to_token) + positive_map_chunked.append(positive_map) + entities_chunked.append(original_caption_chunked[i]) + + return positive_map_label_to_token_chunked, \ + caption_string_chunked, \ + positive_map_chunked, \ + entities_chunked + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> Union[dict, list]: + # TODO: Only open vocabulary tasks are supported for training now. + text_prompts = [ + data_samples.text for data_samples in batch_data_samples + ] + + gt_labels = [ + data_samples.gt_instances.labels + for data_samples in batch_data_samples + ] + + new_text_prompts = [] + positive_maps = [] + if len(set(text_prompts)) == 1: + # All the text prompts are the same, + # so there is no need to calculate them multiple times. + tokenized, caption_string, tokens_positive, _ = \ + self.get_tokens_and_prompts( + text_prompts[0], True) + new_text_prompts = [caption_string] * len(batch_inputs) + for gt_label in gt_labels: + new_tokens_positive = [ + tokens_positive[label] for label in gt_label + ] + _, positive_map = self.get_positive_map( + tokenized, new_tokens_positive) + positive_maps.append(positive_map) + else: + for text_prompt, gt_label in zip(text_prompts, gt_labels): + tokenized, caption_string, tokens_positive, _ = \ + self.get_tokens_and_prompts( + text_prompt, True) + new_tokens_positive = [ + tokens_positive[label] for label in gt_label + ] + _, positive_map = self.get_positive_map( + tokenized, new_tokens_positive) + positive_maps.append(positive_map) + new_text_prompts.append(caption_string) + + language_dict_features = self.language_model(new_text_prompts) + for i, data_samples in enumerate(batch_data_samples): + # .bool().float() is very important + positive_map = positive_maps[i].to( + batch_inputs.device).bool().float() + data_samples.gt_instances.positive_maps = positive_map + + visual_features = self.extract_feat(batch_inputs) + + losses = self.bbox_head.loss(visual_features, language_dict_features, + batch_data_samples) + return losses + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to True. + + Returns: + list[:obj:`DetDataSample`]: Detection results of the + input images. Each DetDataSample usually contain + 'pred_instances'. And the ``pred_instances`` usually + contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - label_names (List[str]): Label names of bboxes. + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + text_prompts = [] + enhanced_text_prompts = [] + tokens_positives = [] + for data_samples in batch_data_samples: + text_prompts.append(data_samples.text) + if 'caption_prompt' in data_samples: + enhanced_text_prompts.append(data_samples.caption_prompt) + else: + enhanced_text_prompts.append(None) + tokens_positives.append(data_samples.get('tokens_positive', None)) + + if 'custom_entities' in batch_data_samples[0]: + # Assuming that the `custom_entities` flag + # inside a batch is always the same. For single image inference + custom_entities = batch_data_samples[0].custom_entities + else: + custom_entities = False + + if len(set(text_prompts)) == 1: + # All the text prompts are the same, + # so there is no need to calculate them multiple times. + _positive_maps_and_prompts = [ + self.get_tokens_positive_and_prompts( + text_prompts[0], custom_entities, enhanced_text_prompts[0], + tokens_positives[0]) + ] * len(batch_inputs) + else: + _positive_maps_and_prompts = [ + self.get_tokens_positive_and_prompts(text_prompt, + custom_entities, + enhanced_text_prompt, + tokens_positive) + for text_prompt, enhanced_text_prompt, tokens_positive in zip( + text_prompts, enhanced_text_prompts, tokens_positives) + ] + + token_positive_maps, text_prompts, _, entities = zip( + *_positive_maps_and_prompts) + + visual_features = self.extract_feat(batch_inputs) + + if isinstance(text_prompts[0], list): + # chunked text prompts, only bs=1 is supported + assert len(batch_inputs) == 1 + count = 0 + results_list = [] + + entities = [[item for lst in entities[0] for item in lst]] + + for b in range(len(text_prompts[0])): + text_prompts_once = [text_prompts[0][b]] + token_positive_maps_once = token_positive_maps[0][b] + language_dict_features = self.language_model(text_prompts_once) + batch_data_samples[ + 0].token_positive_map = token_positive_maps_once + + pred_instances = self.bbox_head.predict( + copy.deepcopy(visual_features), + language_dict_features, + batch_data_samples, + rescale=rescale)[0] + + if len(pred_instances) > 0: + pred_instances.labels += count + count += len(token_positive_maps_once) + results_list.append(pred_instances) + results_list = [results_list[0].cat(results_list)] + else: + language_dict_features = self.language_model(list(text_prompts)) + + for i, data_samples in enumerate(batch_data_samples): + data_samples.token_positive_map = token_positive_maps[i] + + results_list = self.bbox_head.predict( + visual_features, + language_dict_features, + batch_data_samples, + rescale=rescale) + + for data_sample, pred_instances, entity in zip(batch_data_samples, + results_list, entities): + if len(pred_instances) > 0: + label_names = [] + for labels in pred_instances.labels: + if labels >= len(entity): + warnings.warn( + 'The unexpected output indicates an issue with ' + 'named entity recognition. You can try ' + 'setting custom_entities=True and running ' + 'again to see if it helps.') + label_names.append('unobject') + else: + label_names.append(entity[labels]) + # for visualization + pred_instances.label_names = label_names + data_sample.pred_instances = pred_instances + return batch_data_samples diff --git a/mmdetection/mmdet/models/detectors/grid_rcnn.py b/mmdetection/mmdet/models/detectors/grid_rcnn.py new file mode 100644 index 00000000..7bcb5b03 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/grid_rcnn.py @@ -0,0 +1,33 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class GridRCNN(TwoStageDetector): + """Grid R-CNN. + + This detector is the implementation of: + - Grid R-CNN (https://arxiv.org/abs/1811.12030) + - Grid R-CNN Plus: Faster and Better (https://arxiv.org/abs/1906.05688) + """ + + def __init__(self, + backbone: ConfigType, + rpn_head: ConfigType, + roi_head: ConfigType, + train_cfg: ConfigType, + test_cfg: ConfigType, + neck: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/grounding_dino.py b/mmdetection/mmdet/models/detectors/grounding_dino.py new file mode 100644 index 00000000..b1ab7c2d --- /dev/null +++ b/mmdetection/mmdet/models/detectors/grounding_dino.py @@ -0,0 +1,621 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import re +import warnings +from typing import Dict, Optional, Tuple, Union + +import torch +import torch.nn as nn +from mmengine.runner.amp import autocast +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList, SampleList +from mmdet.utils import ConfigType +from ..layers import SinePositionalEncoding +from ..layers.transformer.grounding_dino_layers import ( + GroundingDinoTransformerDecoder, GroundingDinoTransformerEncoder) +from .dino import DINO +from .glip import (create_positive_map, create_positive_map_label_to_token, + run_ner) + + +def clean_label_name(name: str) -> str: + name = re.sub(r'\(.*\)', '', name) + name = re.sub(r'_', ' ', name) + name = re.sub(r' ', ' ', name) + return name + + +def chunks(lst: list, n: int) -> list: + """Yield successive n-sized chunks from lst.""" + all_ = [] + for i in range(0, len(lst), n): + data_index = lst[i:i + n] + all_.append(data_index) + counter = 0 + for i in all_: + counter += len(i) + assert (counter == len(lst)) + + return all_ + + +@MODELS.register_module() +class GroundingDINO(DINO): + """Implementation of `Grounding DINO: Marrying DINO with Grounded Pre- + Training for Open-Set Object Detection. + + `_ + + Code is modified from the `official github repo + `_. + """ + + def __init__(self, + language_model, + *args, + use_autocast=False, + **kwargs) -> None: + + self.language_model_cfg = language_model + self._special_tokens = '. ' + self.use_autocast = use_autocast + super().__init__(*args, **kwargs) + + def _init_layers(self) -> None: + """Initialize layers except for backbone, neck and bbox_head.""" + self.positional_encoding = SinePositionalEncoding( + **self.positional_encoding) + self.encoder = GroundingDinoTransformerEncoder(**self.encoder) + self.decoder = GroundingDinoTransformerDecoder(**self.decoder) + self.embed_dims = self.encoder.embed_dims + self.query_embedding = nn.Embedding(self.num_queries, self.embed_dims) + num_feats = self.positional_encoding.num_feats + assert num_feats * 2 == self.embed_dims, \ + f'embed_dims should be exactly 2 times of num_feats. ' \ + f'Found {self.embed_dims} and {num_feats}.' + + self.level_embed = nn.Parameter( + torch.Tensor(self.num_feature_levels, self.embed_dims)) + self.memory_trans_fc = nn.Linear(self.embed_dims, self.embed_dims) + self.memory_trans_norm = nn.LayerNorm(self.embed_dims) + + # text modules + self.language_model = MODELS.build(self.language_model_cfg) + self.text_feat_map = nn.Linear( + self.language_model.language_backbone.body.language_dim, + self.embed_dims, + bias=True) + + def init_weights(self) -> None: + """Initialize weights for Transformer and other components.""" + super().init_weights() + nn.init.constant_(self.text_feat_map.bias.data, 0) + nn.init.xavier_uniform_(self.text_feat_map.weight.data) + + def to_enhance_text_prompts(self, original_caption, enhanced_text_prompts): + caption_string = '' + tokens_positive = [] + for idx, word in enumerate(original_caption): + if word in enhanced_text_prompts: + enhanced_text_dict = enhanced_text_prompts[word] + if 'prefix' in enhanced_text_dict: + caption_string += enhanced_text_dict['prefix'] + start_i = len(caption_string) + if 'name' in enhanced_text_dict: + caption_string += enhanced_text_dict['name'] + else: + caption_string += word + end_i = len(caption_string) + tokens_positive.append([[start_i, end_i]]) + + if 'suffix' in enhanced_text_dict: + caption_string += enhanced_text_dict['suffix'] + else: + tokens_positive.append( + [[len(caption_string), + len(caption_string) + len(word)]]) + caption_string += word + caption_string += self._special_tokens + return caption_string, tokens_positive + + def to_plain_text_prompts(self, original_caption): + caption_string = '' + tokens_positive = [] + for idx, word in enumerate(original_caption): + tokens_positive.append( + [[len(caption_string), + len(caption_string) + len(word)]]) + caption_string += word + caption_string += self._special_tokens + return caption_string, tokens_positive + + def get_tokens_and_prompts( + self, + original_caption: Union[str, list, tuple], + custom_entities: bool = False, + enhanced_text_prompts: Optional[ConfigType] = None + ) -> Tuple[dict, str, list]: + """Get the tokens positive and prompts for the caption.""" + if isinstance(original_caption, (list, tuple)) or custom_entities: + if custom_entities and isinstance(original_caption, str): + original_caption = original_caption.strip(self._special_tokens) + original_caption = original_caption.split(self._special_tokens) + original_caption = list( + filter(lambda x: len(x) > 0, original_caption)) + + original_caption = [clean_label_name(i) for i in original_caption] + + if custom_entities and enhanced_text_prompts is not None: + caption_string, tokens_positive = self.to_enhance_text_prompts( + original_caption, enhanced_text_prompts) + else: + caption_string, tokens_positive = self.to_plain_text_prompts( + original_caption) + + # NOTE: Tokenizer in Grounding DINO is different from + # that in GLIP. The tokenizer in GLIP will pad the + # caption_string to max_length, while the tokenizer + # in Grounding DINO will not. + tokenized = self.language_model.tokenizer( + [caption_string], + padding='max_length' + if self.language_model.pad_to_max else 'longest', + return_tensors='pt') + entities = original_caption + else: + if not original_caption.endswith('.'): + original_caption = original_caption + self._special_tokens + # NOTE: Tokenizer in Grounding DINO is different from + # that in GLIP. The tokenizer in GLIP will pad the + # caption_string to max_length, while the tokenizer + # in Grounding DINO will not. + tokenized = self.language_model.tokenizer( + [original_caption], + padding='max_length' + if self.language_model.pad_to_max else 'longest', + return_tensors='pt') + tokens_positive, noun_phrases = run_ner(original_caption) + entities = noun_phrases + caption_string = original_caption + + return tokenized, caption_string, tokens_positive, entities + + def get_positive_map(self, tokenized, tokens_positive): + positive_map = create_positive_map( + tokenized, + tokens_positive, + max_num_entities=self.bbox_head.cls_branches[ + self.decoder.num_layers].max_text_len) + positive_map_label_to_token = create_positive_map_label_to_token( + positive_map, plus=1) + return positive_map_label_to_token, positive_map + + def get_tokens_positive_and_prompts( + self, + original_caption: Union[str, list, tuple], + custom_entities: bool = False, + enhanced_text_prompt: Optional[ConfigType] = None, + tokens_positive: Optional[list] = None, + ) -> Tuple[dict, str, Tensor, list]: + """Get the tokens positive and prompts for the caption. + + Args: + original_caption (str): The original caption, e.g. 'bench . car .' + custom_entities (bool, optional): Whether to use custom entities. + If ``True``, the ``original_caption`` should be a list of + strings, each of which is a word. Defaults to False. + + Returns: + Tuple[dict, str, dict, str]: The dict is a mapping from each entity + id, which is numbered from 1, to its positive token id. + The str represents the prompts. + """ + if tokens_positive is not None: + if tokens_positive == -1: + if not original_caption.endswith('.'): + original_caption = original_caption + self._special_tokens + return None, original_caption, None, original_caption + else: + if not original_caption.endswith('.'): + original_caption = original_caption + self._special_tokens + tokenized = self.language_model.tokenizer( + [original_caption], + padding='max_length' + if self.language_model.pad_to_max else 'longest', + return_tensors='pt') + positive_map_label_to_token, positive_map = \ + self.get_positive_map(tokenized, tokens_positive) + + entities = [] + for token_positive in tokens_positive: + instance_entities = [] + for t in token_positive: + instance_entities.append(original_caption[t[0]:t[1]]) + entities.append(' / '.join(instance_entities)) + return positive_map_label_to_token, original_caption, \ + positive_map, entities + + chunked_size = self.test_cfg.get('chunked_size', -1) + if not self.training and chunked_size > 0: + assert isinstance(original_caption, + (list, tuple)) or custom_entities is True + all_output = self.get_tokens_positive_and_prompts_chunked( + original_caption, enhanced_text_prompt) + positive_map_label_to_token, \ + caption_string, \ + positive_map, \ + entities = all_output + else: + tokenized, caption_string, tokens_positive, entities = \ + self.get_tokens_and_prompts( + original_caption, custom_entities, enhanced_text_prompt) + positive_map_label_to_token, positive_map = self.get_positive_map( + tokenized, tokens_positive) + return positive_map_label_to_token, caption_string, \ + positive_map, entities + + def get_tokens_positive_and_prompts_chunked( + self, + original_caption: Union[list, tuple], + enhanced_text_prompts: Optional[ConfigType] = None): + chunked_size = self.test_cfg.get('chunked_size', -1) + original_caption = [clean_label_name(i) for i in original_caption] + + original_caption_chunked = chunks(original_caption, chunked_size) + ids_chunked = chunks( + list(range(1, + len(original_caption) + 1)), chunked_size) + + positive_map_label_to_token_chunked = [] + caption_string_chunked = [] + positive_map_chunked = [] + entities_chunked = [] + + for i in range(len(ids_chunked)): + if enhanced_text_prompts is not None: + caption_string, tokens_positive = self.to_enhance_text_prompts( + original_caption_chunked[i], enhanced_text_prompts) + else: + caption_string, tokens_positive = self.to_plain_text_prompts( + original_caption_chunked[i]) + tokenized = self.language_model.tokenizer([caption_string], + return_tensors='pt') + if tokenized.input_ids.shape[1] > self.language_model.max_tokens: + warnings.warn('Inputting a text that is too long will result ' + 'in poor prediction performance. ' + 'Please reduce the --chunked-size.') + positive_map_label_to_token, positive_map = self.get_positive_map( + tokenized, tokens_positive) + + caption_string_chunked.append(caption_string) + positive_map_label_to_token_chunked.append( + positive_map_label_to_token) + positive_map_chunked.append(positive_map) + entities_chunked.append(original_caption_chunked[i]) + + return positive_map_label_to_token_chunked, \ + caption_string_chunked, \ + positive_map_chunked, \ + entities_chunked + + def forward_transformer( + self, + img_feats: Tuple[Tensor], + text_dict: Dict, + batch_data_samples: OptSampleList = None, + ) -> Dict: + encoder_inputs_dict, decoder_inputs_dict = self.pre_transformer( + img_feats, batch_data_samples) + + encoder_outputs_dict = self.forward_encoder( + **encoder_inputs_dict, text_dict=text_dict) + + tmp_dec_in, head_inputs_dict = self.pre_decoder( + **encoder_outputs_dict, batch_data_samples=batch_data_samples) + decoder_inputs_dict.update(tmp_dec_in) + + decoder_outputs_dict = self.forward_decoder(**decoder_inputs_dict) + head_inputs_dict.update(decoder_outputs_dict) + return head_inputs_dict + + def forward_encoder(self, feat: Tensor, feat_mask: Tensor, + feat_pos: Tensor, spatial_shapes: Tensor, + level_start_index: Tensor, valid_ratios: Tensor, + text_dict: Dict) -> Dict: + text_token_mask = text_dict['text_token_mask'] + memory, memory_text = self.encoder( + query=feat, + query_pos=feat_pos, + key_padding_mask=feat_mask, # for self_attn + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + # for text encoder + memory_text=text_dict['embedded'], + text_attention_mask=~text_token_mask, + position_ids=text_dict['position_ids'], + text_self_attention_masks=text_dict['masks']) + encoder_outputs_dict = dict( + memory=memory, + memory_mask=feat_mask, + spatial_shapes=spatial_shapes, + memory_text=memory_text, + text_token_mask=text_token_mask) + return encoder_outputs_dict + + def pre_decoder( + self, + memory: Tensor, + memory_mask: Tensor, + spatial_shapes: Tensor, + memory_text: Tensor, + text_token_mask: Tensor, + batch_data_samples: OptSampleList = None, + ) -> Tuple[Dict]: + bs, _, c = memory.shape + + output_memory, output_proposals = self.gen_encoder_output_proposals( + memory, memory_mask, spatial_shapes) + + enc_outputs_class = self.bbox_head.cls_branches[ + self.decoder.num_layers](output_memory, memory_text, + text_token_mask) + cls_out_features = self.bbox_head.cls_branches[ + self.decoder.num_layers].max_text_len + enc_outputs_coord_unact = self.bbox_head.reg_branches[ + self.decoder.num_layers](output_memory) + output_proposals + + # NOTE The DINO selects top-k proposals according to scores of + # multi-class classification, while DeformDETR, where the input + # is `enc_outputs_class[..., 0]` selects according to scores of + # binary classification. + topk_indices = torch.topk( + enc_outputs_class.max(-1)[0], k=self.num_queries, dim=1)[1] + + topk_score = torch.gather( + enc_outputs_class, 1, + topk_indices.unsqueeze(-1).repeat(1, 1, cls_out_features)) + topk_coords_unact = torch.gather( + enc_outputs_coord_unact, 1, + topk_indices.unsqueeze(-1).repeat(1, 1, 4)) + topk_coords = topk_coords_unact.sigmoid() + topk_coords_unact = topk_coords_unact.detach() + + query = self.query_embedding.weight[:, None, :] + query = query.repeat(1, bs, 1).transpose(0, 1) + if self.training: + dn_label_query, dn_bbox_query, dn_mask, dn_meta = \ + self.dn_query_generator(batch_data_samples) + query = torch.cat([dn_label_query, query], dim=1) + reference_points = torch.cat([dn_bbox_query, topk_coords_unact], + dim=1) + else: + reference_points = topk_coords_unact + dn_mask, dn_meta = None, None + reference_points = reference_points.sigmoid() + + decoder_inputs_dict = dict( + query=query, + memory=memory, + reference_points=reference_points, + dn_mask=dn_mask, + memory_text=memory_text, + text_attention_mask=~text_token_mask, + ) + # NOTE DINO calculates encoder losses on scores and coordinates + # of selected top-k encoder queries, while DeformDETR is of all + # encoder queries. + head_inputs_dict = dict( + enc_outputs_class=topk_score, + enc_outputs_coord=topk_coords, + dn_meta=dn_meta) if self.training else dict() + # append text_feats to head_inputs_dict + head_inputs_dict['memory_text'] = memory_text + head_inputs_dict['text_token_mask'] = text_token_mask + return decoder_inputs_dict, head_inputs_dict + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> Union[dict, list]: + text_prompts = [ + data_samples.text for data_samples in batch_data_samples + ] + + gt_labels = [ + data_samples.gt_instances.labels + for data_samples in batch_data_samples + ] + + if 'tokens_positive' in batch_data_samples[0]: + tokens_positive = [ + data_samples.tokens_positive + for data_samples in batch_data_samples + ] + positive_maps = [] + for token_positive, text_prompt, gt_label in zip( + tokens_positive, text_prompts, gt_labels): + tokenized = self.language_model.tokenizer( + [text_prompt], + padding='max_length' + if self.language_model.pad_to_max else 'longest', + return_tensors='pt') + new_tokens_positive = [ + token_positive[label.item()] for label in gt_label + ] + _, positive_map = self.get_positive_map( + tokenized, new_tokens_positive) + positive_maps.append(positive_map) + new_text_prompts = text_prompts + else: + new_text_prompts = [] + positive_maps = [] + if len(set(text_prompts)) == 1: + # All the text prompts are the same, + # so there is no need to calculate them multiple times. + tokenized, caption_string, tokens_positive, _ = \ + self.get_tokens_and_prompts( + text_prompts[0], True) + new_text_prompts = [caption_string] * len(batch_inputs) + for gt_label in gt_labels: + new_tokens_positive = [ + tokens_positive[label] for label in gt_label + ] + _, positive_map = self.get_positive_map( + tokenized, new_tokens_positive) + positive_maps.append(positive_map) + else: + for text_prompt, gt_label in zip(text_prompts, gt_labels): + tokenized, caption_string, tokens_positive, _ = \ + self.get_tokens_and_prompts( + text_prompt, True) + new_tokens_positive = [ + tokens_positive[label] for label in gt_label + ] + _, positive_map = self.get_positive_map( + tokenized, new_tokens_positive) + positive_maps.append(positive_map) + new_text_prompts.append(caption_string) + + text_dict = self.language_model(new_text_prompts) + if self.text_feat_map is not None: + text_dict['embedded'] = self.text_feat_map(text_dict['embedded']) + + for i, data_samples in enumerate(batch_data_samples): + positive_map = positive_maps[i].to( + batch_inputs.device).bool().float() + text_token_mask = text_dict['text_token_mask'][i] + data_samples.gt_instances.positive_maps = positive_map + data_samples.gt_instances.text_token_mask = \ + text_token_mask.unsqueeze(0).repeat( + len(positive_map), 1) + if self.use_autocast: + with autocast(enabled=True): + visual_features = self.extract_feat(batch_inputs) + else: + visual_features = self.extract_feat(batch_inputs) + head_inputs_dict = self.forward_transformer(visual_features, text_dict, + batch_data_samples) + + losses = self.bbox_head.loss( + **head_inputs_dict, batch_data_samples=batch_data_samples) + return losses + + def predict(self, batch_inputs, batch_data_samples, rescale: bool = True): + text_prompts = [] + enhanced_text_prompts = [] + tokens_positives = [] + for data_samples in batch_data_samples: + text_prompts.append(data_samples.text) + if 'caption_prompt' in data_samples: + enhanced_text_prompts.append(data_samples.caption_prompt) + else: + enhanced_text_prompts.append(None) + tokens_positives.append(data_samples.get('tokens_positive', None)) + + if 'custom_entities' in batch_data_samples[0]: + # Assuming that the `custom_entities` flag + # inside a batch is always the same. For single image inference + custom_entities = batch_data_samples[0].custom_entities + else: + custom_entities = False + if len(text_prompts) == 1: + # All the text prompts are the same, + # so there is no need to calculate them multiple times. + _positive_maps_and_prompts = [ + self.get_tokens_positive_and_prompts( + text_prompts[0], custom_entities, enhanced_text_prompts[0], + tokens_positives[0]) + ] * len(batch_inputs) + else: + _positive_maps_and_prompts = [ + self.get_tokens_positive_and_prompts(text_prompt, + custom_entities, + enhanced_text_prompt, + tokens_positive) + for text_prompt, enhanced_text_prompt, tokens_positive in zip( + text_prompts, enhanced_text_prompts, tokens_positives) + ] + token_positive_maps, text_prompts, _, entities = zip( + *_positive_maps_and_prompts) + + # image feature extraction + visual_feats = self.extract_feat(batch_inputs) + + if isinstance(text_prompts[0], list): + # chunked text prompts, only bs=1 is supported + assert len(batch_inputs) == 1 + count = 0 + results_list = [] + + entities = [[item for lst in entities[0] for item in lst]] + + for b in range(len(text_prompts[0])): + text_prompts_once = [text_prompts[0][b]] + token_positive_maps_once = token_positive_maps[0][b] + text_dict = self.language_model(text_prompts_once) + # text feature map layer + if self.text_feat_map is not None: + text_dict['embedded'] = self.text_feat_map( + text_dict['embedded']) + + batch_data_samples[ + 0].token_positive_map = token_positive_maps_once + + head_inputs_dict = self.forward_transformer( + copy.deepcopy(visual_feats), text_dict, batch_data_samples) + pred_instances = self.bbox_head.predict( + **head_inputs_dict, + rescale=rescale, + batch_data_samples=batch_data_samples)[0] + + if len(pred_instances) > 0: + pred_instances.labels += count + count += len(token_positive_maps_once) + results_list.append(pred_instances) + results_list = [results_list[0].cat(results_list)] + is_rec_tasks = [False] * len(results_list) + else: + # extract text feats + text_dict = self.language_model(list(text_prompts)) + # text feature map layer + if self.text_feat_map is not None: + text_dict['embedded'] = self.text_feat_map( + text_dict['embedded']) + + is_rec_tasks = [] + for i, data_samples in enumerate(batch_data_samples): + if token_positive_maps[i] is not None: + is_rec_tasks.append(False) + else: + is_rec_tasks.append(True) + data_samples.token_positive_map = token_positive_maps[i] + + head_inputs_dict = self.forward_transformer( + visual_feats, text_dict, batch_data_samples) + results_list = self.bbox_head.predict( + **head_inputs_dict, + rescale=rescale, + batch_data_samples=batch_data_samples) + + for data_sample, pred_instances, entity, is_rec_task in zip( + batch_data_samples, results_list, entities, is_rec_tasks): + if len(pred_instances) > 0: + label_names = [] + for labels in pred_instances.labels: + if is_rec_task: + label_names.append(entity) + continue + if labels >= len(entity): + warnings.warn( + 'The unexpected output indicates an issue with ' + 'named entity recognition. You can try ' + 'setting custom_entities=True and running ' + 'again to see if it helps.') + label_names.append('unobject') + else: + label_names.append(entity[labels]) + # for visualization + pred_instances.label_names = label_names + data_sample.pred_instances = pred_instances + return batch_data_samples diff --git a/mmdetection/mmdet/models/detectors/htc.py b/mmdetection/mmdet/models/detectors/htc.py new file mode 100644 index 00000000..22a2aa88 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/htc.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from .cascade_rcnn import CascadeRCNN + + +@MODELS.register_module() +class HybridTaskCascade(CascadeRCNN): + """Implementation of `HTC `_""" + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + + @property + def with_semantic(self) -> bool: + """bool: whether the detector has a semantic head""" + return self.roi_head.with_semantic diff --git a/mmdetection/mmdet/models/detectors/kd_one_stage.py b/mmdetection/mmdet/models/detectors/kd_one_stage.py new file mode 100644 index 00000000..8a4a1bb5 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/kd_one_stage.py @@ -0,0 +1,122 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from pathlib import Path +from typing import Any, Optional, Union + +import torch +import torch.nn as nn +from mmengine.config import Config +from mmengine.runner import load_checkpoint +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class KnowledgeDistillationSingleStageDetector(SingleStageDetector): + r"""Implementation of `Distilling the Knowledge in a Neural Network. + `_. + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + teacher_config (:obj:`ConfigDict` | dict | str | Path): Config file + path or the config object of teacher model. + teacher_ckpt (str, optional): Checkpoint path of teacher model. + If left as None, the model will not load any weights. + Defaults to True. + eval_teacher (bool): Set the train mode for teacher. + Defaults to True. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of ATSS. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of ATSS. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + """ + + def __init__( + self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + teacher_config: Union[ConfigType, str, Path], + teacher_ckpt: Optional[str] = None, + eval_teacher: bool = True, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + ) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor) + self.eval_teacher = eval_teacher + # Build teacher model + if isinstance(teacher_config, (str, Path)): + teacher_config = Config.fromfile(teacher_config) + self.teacher_model = MODELS.build(teacher_config['model']) + if teacher_ckpt is not None: + load_checkpoint( + self.teacher_model, teacher_ckpt, map_location='cpu') + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> dict: + """ + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + x = self.extract_feat(batch_inputs) + with torch.no_grad(): + teacher_x = self.teacher_model.extract_feat(batch_inputs) + out_teacher = self.teacher_model.bbox_head(teacher_x) + losses = self.bbox_head.loss(x, out_teacher, batch_data_samples) + return losses + + def cuda(self, device: Optional[str] = None) -> nn.Module: + """Since teacher_model is registered as a plain object, it is necessary + to put the teacher model to cuda when calling ``cuda`` function.""" + self.teacher_model.cuda(device=device) + return super().cuda(device=device) + + def to(self, device: Optional[str] = None) -> nn.Module: + """Since teacher_model is registered as a plain object, it is necessary + to put the teacher model to other device when calling ``to`` + function.""" + self.teacher_model.to(device=device) + return super().to(device=device) + + def train(self, mode: bool = True) -> None: + """Set the same train mode for teacher and student model.""" + if self.eval_teacher: + self.teacher_model.train(False) + else: + self.teacher_model.train(mode) + super().train(mode) + + def __setattr__(self, name: str, value: Any) -> None: + """Set attribute, i.e. self.name = value + + This reloading prevent the teacher model from being registered as a + nn.Module. The teacher module is registered as a plain object, so that + the teacher parameters will not show up when calling + ``self.parameters``, ``self.modules``, ``self.children`` methods. + """ + if name == 'teacher_model': + object.__setattr__(self, name, value) + else: + super().__setattr__(name, value) diff --git a/mmdetection/mmdet/models/detectors/lad.py b/mmdetection/mmdet/models/detectors/lad.py new file mode 100644 index 00000000..008f8987 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/lad.py @@ -0,0 +1,93 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +import torch.nn as nn +from mmengine.runner import load_checkpoint +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType +from ..utils.misc import unpack_gt_instances +from .kd_one_stage import KnowledgeDistillationSingleStageDetector + + +@MODELS.register_module() +class LAD(KnowledgeDistillationSingleStageDetector): + """Implementation of `LAD `_.""" + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + teacher_backbone: ConfigType, + teacher_neck: ConfigType, + teacher_bbox_head: ConfigType, + teacher_ckpt: Optional[str] = None, + eval_teacher: bool = True, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None) -> None: + super(KnowledgeDistillationSingleStageDetector, self).__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor) + self.eval_teacher = eval_teacher + self.teacher_model = nn.Module() + self.teacher_model.backbone = MODELS.build(teacher_backbone) + if teacher_neck is not None: + self.teacher_model.neck = MODELS.build(teacher_neck) + teacher_bbox_head.update(train_cfg=train_cfg) + teacher_bbox_head.update(test_cfg=test_cfg) + self.teacher_model.bbox_head = MODELS.build(teacher_bbox_head) + if teacher_ckpt is not None: + load_checkpoint( + self.teacher_model, teacher_ckpt, map_location='cpu') + + @property + def with_teacher_neck(self) -> bool: + """bool: whether the detector has a teacher_neck""" + return hasattr(self.teacher_model, 'neck') and \ + self.teacher_model.neck is not None + + def extract_teacher_feat(self, batch_inputs: Tensor) -> Tensor: + """Directly extract teacher features from the backbone+neck.""" + x = self.teacher_model.backbone(batch_inputs) + if self.with_teacher_neck: + x = self.teacher_model.neck(x) + return x + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> dict: + """ + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, batch_img_metas \ + = outputs + # get label assignment from the teacher + with torch.no_grad(): + x_teacher = self.extract_teacher_feat(batch_inputs) + outs_teacher = self.teacher_model.bbox_head(x_teacher) + label_assignment_results = \ + self.teacher_model.bbox_head.get_label_assignment( + *outs_teacher, batch_gt_instances, batch_img_metas, + batch_gt_instances_ignore) + + # the student use the label assignment from the teacher to learn + x = self.extract_feat(batch_inputs) + losses = self.bbox_head.loss(x, label_assignment_results, + batch_data_samples) + return losses diff --git a/mmdetection/mmdet/models/detectors/mask2former.py b/mmdetection/mmdet/models/detectors/mask2former.py new file mode 100644 index 00000000..4f38ef44 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/mask2former.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .maskformer import MaskFormer + + +@MODELS.register_module() +class Mask2Former(MaskFormer): + r"""Implementation of `Masked-attention Mask + Transformer for Universal Image Segmentation + `_.""" + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + panoptic_head: OptConfigType = None, + panoptic_fusion_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__( + backbone=backbone, + neck=neck, + panoptic_head=panoptic_head, + panoptic_fusion_head=panoptic_fusion_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/mask_rcnn.py b/mmdetection/mmdet/models/detectors/mask_rcnn.py new file mode 100644 index 00000000..880ee1e8 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/mask_rcnn.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import ConfigDict + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class MaskRCNN(TwoStageDetector): + """Implementation of `Mask R-CNN `_""" + + def __init__(self, + backbone: ConfigDict, + rpn_head: ConfigDict, + roi_head: ConfigDict, + train_cfg: ConfigDict, + test_cfg: ConfigDict, + neck: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + init_cfg=init_cfg, + data_preprocessor=data_preprocessor) diff --git a/mmdetection/mmdet/models/detectors/mask_scoring_rcnn.py b/mmdetection/mmdet/models/detectors/mask_scoring_rcnn.py new file mode 100644 index 00000000..e09d3a10 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/mask_scoring_rcnn.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class MaskScoringRCNN(TwoStageDetector): + """Mask Scoring RCNN. + + https://arxiv.org/abs/1903.00241 + """ + + def __init__(self, + backbone: ConfigType, + rpn_head: ConfigType, + roi_head: ConfigType, + train_cfg: ConfigType, + test_cfg: ConfigType, + neck: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/maskformer.py b/mmdetection/mmdet/models/detectors/maskformer.py new file mode 100644 index 00000000..7493c00e --- /dev/null +++ b/mmdetection/mmdet/models/detectors/maskformer.py @@ -0,0 +1,170 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Tuple + +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class MaskFormer(SingleStageDetector): + r"""Implementation of `Per-Pixel Classification is + NOT All You Need for Semantic Segmentation + `_.""" + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + panoptic_head: OptConfigType = None, + panoptic_fusion_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super(SingleStageDetector, self).__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + self.backbone = MODELS.build(backbone) + if neck is not None: + self.neck = MODELS.build(neck) + + panoptic_head_ = panoptic_head.deepcopy() + panoptic_head_.update(train_cfg=train_cfg) + panoptic_head_.update(test_cfg=test_cfg) + self.panoptic_head = MODELS.build(panoptic_head_) + + panoptic_fusion_head_ = panoptic_fusion_head.deepcopy() + panoptic_fusion_head_.update(test_cfg=test_cfg) + self.panoptic_fusion_head = MODELS.build(panoptic_fusion_head_) + + self.num_things_classes = self.panoptic_head.num_things_classes + self.num_stuff_classes = self.panoptic_head.num_stuff_classes + self.num_classes = self.panoptic_head.num_classes + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> Dict[str, Tensor]: + """ + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + x = self.extract_feat(batch_inputs) + losses = self.panoptic_head.loss(x, batch_data_samples) + return losses + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to True. + + Returns: + list[:obj:`DetDataSample`]: Detection results of the + input images. Each DetDataSample usually contain + 'pred_instances' and `pred_panoptic_seg`. And the + ``pred_instances`` usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + + And the ``pred_panoptic_seg`` contains the following key + + - sem_seg (Tensor): panoptic segmentation mask, has a + shape (1, h, w). + """ + feats = self.extract_feat(batch_inputs) + mask_cls_results, mask_pred_results = self.panoptic_head.predict( + feats, batch_data_samples) + results_list = self.panoptic_fusion_head.predict( + mask_cls_results, + mask_pred_results, + batch_data_samples, + rescale=rescale) + results = self.add_pred_to_datasample(batch_data_samples, results_list) + + return results + + def add_pred_to_datasample(self, data_samples: SampleList, + results_list: List[dict]) -> SampleList: + """Add predictions to `DetDataSample`. + + Args: + data_samples (list[:obj:`DetDataSample`], optional): A batch of + data samples that contain annotations and predictions. + results_list (List[dict]): Instance segmentation, segmantic + segmentation and panoptic segmentation results. + + Returns: + list[:obj:`DetDataSample`]: Detection results of the + input images. Each DetDataSample usually contain + 'pred_instances' and `pred_panoptic_seg`. And the + ``pred_instances`` usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + + And the ``pred_panoptic_seg`` contains the following key + + - sem_seg (Tensor): panoptic segmentation mask, has a + shape (1, h, w). + """ + for data_sample, pred_results in zip(data_samples, results_list): + if 'pan_results' in pred_results: + data_sample.pred_panoptic_seg = pred_results['pan_results'] + + if 'ins_results' in pred_results: + data_sample.pred_instances = pred_results['ins_results'] + + assert 'sem_results' not in pred_results, 'segmantic ' \ + 'segmentation results are not supported yet.' + + return data_samples + + def _forward(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> Tuple[List[Tensor]]: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + tuple[List[Tensor]]: A tuple of features from ``panoptic_head`` + forward. + """ + feats = self.extract_feat(batch_inputs) + results = self.panoptic_head.forward(feats, batch_data_samples) + return results diff --git a/mmdetection/mmdet/models/detectors/nasfcos.py b/mmdetection/mmdet/models/detectors/nasfcos.py new file mode 100644 index 00000000..da2b911b --- /dev/null +++ b/mmdetection/mmdet/models/detectors/nasfcos.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class NASFCOS(SingleStageDetector): + """Implementation of `NAS-FCOS: Fast Neural Architecture Search for Object + Detection. `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone config. + neck (:obj:`ConfigDict` or dict): The neck config. + bbox_head (:obj:`ConfigDict` or dict): The bbox head config. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of NASFCOS. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of NASFCOS. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/paa.py b/mmdetection/mmdet/models/detectors/paa.py new file mode 100644 index 00000000..094306b2 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/paa.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class PAA(SingleStageDetector): + """Implementation of `PAA `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of PAA. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of PAA. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/panoptic_fpn.py b/mmdetection/mmdet/models/detectors/panoptic_fpn.py new file mode 100644 index 00000000..ae63ccc3 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/panoptic_fpn.py @@ -0,0 +1,35 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .panoptic_two_stage_segmentor import TwoStagePanopticSegmentor + + +@MODELS.register_module() +class PanopticFPN(TwoStagePanopticSegmentor): + r"""Implementation of `Panoptic feature pyramid + networks `_""" + + def __init__( + self, + backbone: ConfigType, + neck: OptConfigType = None, + rpn_head: OptConfigType = None, + roi_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None, + # for panoptic segmentation + semantic_head: OptConfigType = None, + panoptic_fusion_head: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg, + semantic_head=semantic_head, + panoptic_fusion_head=panoptic_fusion_head) diff --git a/mmdetection/mmdet/models/detectors/panoptic_two_stage_segmentor.py b/mmdetection/mmdet/models/detectors/panoptic_two_stage_segmentor.py new file mode 100644 index 00000000..879edbe1 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/panoptic_two_stage_segmentor.py @@ -0,0 +1,234 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import List + +import torch +from mmengine.structures import PixelData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class TwoStagePanopticSegmentor(TwoStageDetector): + """Base class of Two-stage Panoptic Segmentor. + + As well as the components in TwoStageDetector, Panoptic Segmentor has extra + semantic_head and panoptic_fusion_head. + """ + + def __init__( + self, + backbone: ConfigType, + neck: OptConfigType = None, + rpn_head: OptConfigType = None, + roi_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None, + # for panoptic segmentation + semantic_head: OptConfigType = None, + panoptic_fusion_head: OptConfigType = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) + + if semantic_head is not None: + self.semantic_head = MODELS.build(semantic_head) + + if panoptic_fusion_head is not None: + panoptic_cfg = test_cfg.panoptic if test_cfg is not None else None + panoptic_fusion_head_ = panoptic_fusion_head.deepcopy() + panoptic_fusion_head_.update(test_cfg=panoptic_cfg) + self.panoptic_fusion_head = MODELS.build(panoptic_fusion_head_) + + self.num_things_classes = self.panoptic_fusion_head.\ + num_things_classes + self.num_stuff_classes = self.panoptic_fusion_head.\ + num_stuff_classes + self.num_classes = self.panoptic_fusion_head.num_classes + + @property + def with_semantic_head(self) -> bool: + """bool: whether the detector has semantic head""" + return hasattr(self, + 'semantic_head') and self.semantic_head is not None + + @property + def with_panoptic_fusion_head(self) -> bool: + """bool: whether the detector has panoptic fusion head""" + return hasattr(self, 'panoptic_fusion_head') and \ + self.panoptic_fusion_head is not None + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> dict: + """ + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + x = self.extract_feat(batch_inputs) + + losses = dict() + + # RPN forward and loss + if self.with_rpn: + proposal_cfg = self.train_cfg.get('rpn_proposal', + self.test_cfg.rpn) + rpn_data_samples = copy.deepcopy(batch_data_samples) + # set cat_id of gt_labels to 0 in RPN + for data_sample in rpn_data_samples: + data_sample.gt_instances.labels = \ + torch.zeros_like(data_sample.gt_instances.labels) + + rpn_losses, rpn_results_list = self.rpn_head.loss_and_predict( + x, rpn_data_samples, proposal_cfg=proposal_cfg) + # avoid get same name with roi_head loss + keys = rpn_losses.keys() + for key in list(keys): + if 'loss' in key and 'rpn' not in key: + rpn_losses[f'rpn_{key}'] = rpn_losses.pop(key) + losses.update(rpn_losses) + else: + # TODO: Not support currently, should have a check at Fast R-CNN + assert batch_data_samples[0].get('proposals', None) is not None + # use pre-defined proposals in InstanceData for the second stage + # to extract ROI features. + rpn_results_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + + roi_losses = self.roi_head.loss(x, rpn_results_list, + batch_data_samples) + losses.update(roi_losses) + + semantic_loss = self.semantic_head.loss(x, batch_data_samples) + losses.update(semantic_loss) + + return losses + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to True. + + Returns: + List[:obj:`DetDataSample`]: Return the packed panoptic segmentation + results of input images. Each DetDataSample usually contains + 'pred_panoptic_seg'. And the 'pred_panoptic_seg' has a key + ``sem_seg``, which is a tensor of shape (1, h, w). + """ + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + x = self.extract_feat(batch_inputs) + + # If there are no pre-defined proposals, use RPN to get proposals + if batch_data_samples[0].get('proposals', None) is None: + rpn_results_list = self.rpn_head.predict( + x, batch_data_samples, rescale=False) + else: + rpn_results_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + + results_list = self.roi_head.predict( + x, rpn_results_list, batch_data_samples, rescale=rescale) + + seg_preds = self.semantic_head.predict(x, batch_img_metas, rescale) + + results_list = self.panoptic_fusion_head.predict( + results_list, seg_preds) + + batch_data_samples = self.add_pred_to_datasample( + batch_data_samples, results_list) + return batch_data_samples + + # TODO the code has not been verified and needs to be refactored later. + def _forward(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + + Returns: + tuple: A tuple of features from ``rpn_head``, ``roi_head`` and + ``semantic_head`` forward. + """ + results = () + x = self.extract_feat(batch_inputs) + rpn_outs = self.rpn_head.forward(x) + results = results + (rpn_outs) + + # If there are no pre-defined proposals, use RPN to get proposals + if batch_data_samples[0].get('proposals', None) is None: + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + rpn_results_list = self.rpn_head.predict_by_feat( + *rpn_outs, batch_img_metas=batch_img_metas, rescale=False) + else: + # TODO: Not checked currently. + rpn_results_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + + # roi_head + roi_outs = self.roi_head(x, rpn_results_list) + results = results + (roi_outs) + + # semantic_head + sem_outs = self.semantic_head.forward(x) + results = results + (sem_outs['seg_preds'], ) + + return results + + def add_pred_to_datasample(self, data_samples: SampleList, + results_list: List[PixelData]) -> SampleList: + """Add predictions to `DetDataSample`. + + Args: + data_samples (list[:obj:`DetDataSample`]): The + annotation data of every samples. + results_list (List[PixelData]): Panoptic segmentation results of + each image. + + Returns: + List[:obj:`DetDataSample`]: Return the packed panoptic segmentation + results of input images. Each DetDataSample usually contains + 'pred_panoptic_seg'. And the 'pred_panoptic_seg' has a key + ``sem_seg``, which is a tensor of shape (1, h, w). + """ + + for data_sample, pred_panoptic_seg in zip(data_samples, results_list): + data_sample.pred_panoptic_seg = pred_panoptic_seg + return data_samples diff --git a/mmdetection/mmdet/models/detectors/point_rend.py b/mmdetection/mmdet/models/detectors/point_rend.py new file mode 100644 index 00000000..5062ac0c --- /dev/null +++ b/mmdetection/mmdet/models/detectors/point_rend.py @@ -0,0 +1,35 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.config import ConfigDict + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class PointRend(TwoStageDetector): + """PointRend: Image Segmentation as Rendering + + This detector is the implementation of + `PointRend `_. + + """ + + def __init__(self, + backbone: ConfigDict, + rpn_head: ConfigDict, + roi_head: ConfigDict, + train_cfg: ConfigDict, + test_cfg: ConfigDict, + neck: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + init_cfg=init_cfg, + data_preprocessor=data_preprocessor) diff --git a/mmdetection/mmdet/models/detectors/queryinst.py b/mmdetection/mmdet/models/detectors/queryinst.py new file mode 100644 index 00000000..400ce20c --- /dev/null +++ b/mmdetection/mmdet/models/detectors/queryinst.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .sparse_rcnn import SparseRCNN + + +@MODELS.register_module() +class QueryInst(SparseRCNN): + r"""Implementation of + `Instances as Queries `_""" + + def __init__(self, + backbone: ConfigType, + rpn_head: ConfigType, + roi_head: ConfigType, + train_cfg: ConfigType, + test_cfg: ConfigType, + neck: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/reppoints_detector.py b/mmdetection/mmdet/models/detectors/reppoints_detector.py new file mode 100644 index 00000000..d86cec2e --- /dev/null +++ b/mmdetection/mmdet/models/detectors/reppoints_detector.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class RepPointsDetector(SingleStageDetector): + """RepPoints: Point Set Representation for Object Detection. + + This detector is the implementation of: + - RepPoints detector (https://arxiv.org/pdf/1904.11490) + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/retinanet.py b/mmdetection/mmdet/models/detectors/retinanet.py new file mode 100644 index 00000000..03e3cb20 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/retinanet.py @@ -0,0 +1,26 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class RetinaNet(SingleStageDetector): + """Implementation of `RetinaNet `_""" + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/rpn.py b/mmdetection/mmdet/models/detectors/rpn.py new file mode 100644 index 00000000..72fe8521 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/rpn.py @@ -0,0 +1,81 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import warnings + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class RPN(SingleStageDetector): + """Implementation of Region Proposal Network. + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone config. + neck (:obj:`ConfigDict` or dict): The neck config. + bbox_head (:obj:`ConfigDict` or dict): The bbox head config. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + rpn_head: ConfigType, + train_cfg: ConfigType, + test_cfg: ConfigType, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + super(SingleStageDetector, self).__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + self.backbone = MODELS.build(backbone) + self.neck = MODELS.build(neck) if neck is not None else None + rpn_train_cfg = train_cfg['rpn'] if train_cfg is not None else None + rpn_head_num_classes = rpn_head.get('num_classes', 1) + if rpn_head_num_classes != 1: + warnings.warn('The `num_classes` should be 1 in RPN, but get ' + f'{rpn_head_num_classes}, please set ' + 'rpn_head.num_classes = 1 in your config file.') + rpn_head.update(num_classes=1) + rpn_head.update(train_cfg=rpn_train_cfg) + rpn_head.update(test_cfg=test_cfg['rpn']) + self.bbox_head = MODELS.build(rpn_head) + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + x = self.extract_feat(batch_inputs) + + # set cat_id of gt_labels to 0 in RPN + rpn_data_samples = copy.deepcopy(batch_data_samples) + for data_sample in rpn_data_samples: + data_sample.gt_instances.labels = \ + torch.zeros_like(data_sample.gt_instances.labels) + + losses = self.bbox_head.loss(x, rpn_data_samples) + return losses diff --git a/mmdetection/mmdet/models/detectors/rtmdet.py b/mmdetection/mmdet/models/detectors/rtmdet.py new file mode 100644 index 00000000..b43e053f --- /dev/null +++ b/mmdetection/mmdet/models/detectors/rtmdet.py @@ -0,0 +1,52 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmengine.dist import get_world_size +from mmengine.logging import print_log + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class RTMDet(SingleStageDetector): + """Implementation of RTMDet. + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of ATSS. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of ATSS. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + use_syncbn (bool): Whether to use SyncBatchNorm. Defaults to True. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None, + use_syncbn: bool = True) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) + + # TODO: Waiting for mmengine support + if use_syncbn and get_world_size() > 1: + torch.nn.SyncBatchNorm.convert_sync_batchnorm(self) + print_log('Using SyncBatchNorm()', 'current') diff --git a/mmdetection/mmdet/models/detectors/scnet.py b/mmdetection/mmdet/models/detectors/scnet.py new file mode 100644 index 00000000..606a0203 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/scnet.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from .cascade_rcnn import CascadeRCNN + + +@MODELS.register_module() +class SCNet(CascadeRCNN): + """Implementation of `SCNet `_""" + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) diff --git a/mmdetection/mmdet/models/detectors/semi_base.py b/mmdetection/mmdet/models/detectors/semi_base.py new file mode 100644 index 00000000..f3f0c8c0 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/semi_base.py @@ -0,0 +1,266 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import Dict, List, Optional, Tuple, Union + +import torch +import torch.nn as nn +from torch import Tensor + +from mmdet.models.utils import (filter_gt_instances, rename_loss_dict, + reweight_loss_dict) +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox_project +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .base import BaseDetector + + +@MODELS.register_module() +class SemiBaseDetector(BaseDetector): + """Base class for semi-supervised detectors. + + Semi-supervised detectors typically consisting of a teacher model + updated by exponential moving average and a student model updated + by gradient descent. + + Args: + detector (:obj:`ConfigDict` or dict): The detector config. + semi_train_cfg (:obj:`ConfigDict` or dict, optional): + The semi-supervised training config. + semi_test_cfg (:obj:`ConfigDict` or dict, optional): + The semi-supervised testing config. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + detector: ConfigType, + semi_train_cfg: OptConfigType = None, + semi_test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + self.student = MODELS.build(detector) + self.teacher = MODELS.build(detector) + self.semi_train_cfg = semi_train_cfg + self.semi_test_cfg = semi_test_cfg + if self.semi_train_cfg.get('freeze_teacher', True) is True: + self.freeze(self.teacher) + + @staticmethod + def freeze(model: nn.Module): + """Freeze the model.""" + model.eval() + for param in model.parameters(): + param.requires_grad = False + + def loss(self, multi_batch_inputs: Dict[str, Tensor], + multi_batch_data_samples: Dict[str, SampleList]) -> dict: + """Calculate losses from multi-branch inputs and data samples. + + Args: + multi_batch_inputs (Dict[str, Tensor]): The dict of multi-branch + input images, each value with shape (N, C, H, W). + Each value should usually be mean centered and std scaled. + multi_batch_data_samples (Dict[str, List[:obj:`DetDataSample`]]): + The dict of multi-branch data samples. + + Returns: + dict: A dictionary of loss components + """ + losses = dict() + losses.update(**self.loss_by_gt_instances( + multi_batch_inputs['sup'], multi_batch_data_samples['sup'])) + + origin_pseudo_data_samples, batch_info = self.get_pseudo_instances( + multi_batch_inputs['unsup_teacher'], + multi_batch_data_samples['unsup_teacher']) + multi_batch_data_samples[ + 'unsup_student'] = self.project_pseudo_instances( + origin_pseudo_data_samples, + multi_batch_data_samples['unsup_student']) + losses.update(**self.loss_by_pseudo_instances( + multi_batch_inputs['unsup_student'], + multi_batch_data_samples['unsup_student'], batch_info)) + return losses + + def loss_by_gt_instances(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and ground-truth data + samples. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components + """ + + losses = self.student.loss(batch_inputs, batch_data_samples) + sup_weight = self.semi_train_cfg.get('sup_weight', 1.) + return rename_loss_dict('sup_', reweight_loss_dict(losses, sup_weight)) + + def loss_by_pseudo_instances(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + batch_info: Optional[dict] = None) -> dict: + """Calculate losses from a batch of inputs and pseudo data samples. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`, + which are `pseudo_instance` or `pseudo_panoptic_seg` + or `pseudo_sem_seg` in fact. + batch_info (dict): Batch information of teacher model + forward propagation process. Defaults to None. + + Returns: + dict: A dictionary of loss components + """ + batch_data_samples = filter_gt_instances( + batch_data_samples, score_thr=self.semi_train_cfg.cls_pseudo_thr) + losses = self.student.loss(batch_inputs, batch_data_samples) + pseudo_instances_num = sum([ + len(data_samples.gt_instances) + for data_samples in batch_data_samples + ]) + unsup_weight = self.semi_train_cfg.get( + 'unsup_weight', 1.) if pseudo_instances_num > 0 else 0. + return rename_loss_dict('unsup_', + reweight_loss_dict(losses, unsup_weight)) + + @torch.no_grad() + def get_pseudo_instances( + self, batch_inputs: Tensor, batch_data_samples: SampleList + ) -> Tuple[SampleList, Optional[dict]]: + """Get pseudo instances from teacher model.""" + self.teacher.eval() + results_list = self.teacher.predict( + batch_inputs, batch_data_samples, rescale=False) + batch_info = {} + for data_samples, results in zip(batch_data_samples, results_list): + data_samples.gt_instances = results.pred_instances + data_samples.gt_instances.bboxes = bbox_project( + data_samples.gt_instances.bboxes, + torch.from_numpy(data_samples.homography_matrix).inverse().to( + self.data_preprocessor.device), data_samples.ori_shape) + return batch_data_samples, batch_info + + def project_pseudo_instances(self, batch_pseudo_instances: SampleList, + batch_data_samples: SampleList) -> SampleList: + """Project pseudo instances.""" + for pseudo_instances, data_samples in zip(batch_pseudo_instances, + batch_data_samples): + data_samples.gt_instances = copy.deepcopy( + pseudo_instances.gt_instances) + data_samples.gt_instances.bboxes = bbox_project( + data_samples.gt_instances.bboxes, + torch.tensor(data_samples.homography_matrix).to( + self.data_preprocessor.device), data_samples.img_shape) + wh_thr = self.semi_train_cfg.get('min_pseudo_bbox_wh', (1e-2, 1e-2)) + return filter_gt_instances(batch_data_samples, wh_thr=wh_thr) + + def predict(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to True. + + Returns: + list[:obj:`DetDataSample`]: Return the detection results of the + input images. The returns value is DetDataSample, + which usually contain 'pred_instances'. And the + ``pred_instances`` usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + if self.semi_test_cfg.get('predict_on', 'teacher') == 'teacher': + return self.teacher( + batch_inputs, batch_data_samples, mode='predict') + else: + return self.student( + batch_inputs, batch_data_samples, mode='predict') + + def _forward(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> SampleList: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + + Returns: + tuple: A tuple of features from ``rpn_head`` and ``roi_head`` + forward. + """ + if self.semi_test_cfg.get('forward_on', 'teacher') == 'teacher': + return self.teacher( + batch_inputs, batch_data_samples, mode='tensor') + else: + return self.student( + batch_inputs, batch_data_samples, mode='tensor') + + def extract_feat(self, batch_inputs: Tensor) -> Tuple[Tensor]: + """Extract features. + + Args: + batch_inputs (Tensor): Image tensor with shape (N, C, H ,W). + + Returns: + tuple[Tensor]: Multi-level features that may have + different resolutions. + """ + if self.semi_test_cfg.get('extract_feat_on', 'teacher') == 'teacher': + return self.teacher.extract_feat(batch_inputs) + else: + return self.student.extract_feat(batch_inputs) + + def _load_from_state_dict(self, state_dict: dict, prefix: str, + local_metadata: dict, strict: bool, + missing_keys: Union[List[str], str], + unexpected_keys: Union[List[str], str], + error_msgs: Union[List[str], str]) -> None: + """Add teacher and student prefixes to model parameter names.""" + if not any([ + 'student' in key or 'teacher' in key + for key in state_dict.keys() + ]): + keys = list(state_dict.keys()) + state_dict.update({'teacher.' + k: state_dict[k] for k in keys}) + state_dict.update({'student.' + k: state_dict[k] for k in keys}) + for k in keys: + state_dict.pop(k) + return super()._load_from_state_dict( + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ) diff --git a/mmdetection/mmdet/models/detectors/single_stage.py b/mmdetection/mmdet/models/detectors/single_stage.py new file mode 100644 index 00000000..06c07408 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/single_stage.py @@ -0,0 +1,149 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple, Union + +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList, SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .base import BaseDetector + + +@MODELS.register_module() +class SingleStageDetector(BaseDetector): + """Base class for single-stage detectors. + + Single-stage detectors directly and densely predict bounding boxes on the + output features of the backbone+neck. + """ + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + bbox_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + self.backbone = MODELS.build(backbone) + if neck is not None: + self.neck = MODELS.build(neck) + bbox_head.update(train_cfg=train_cfg) + bbox_head.update(test_cfg=test_cfg) + self.bbox_head = MODELS.build(bbox_head) + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + def _load_from_state_dict(self, state_dict: dict, prefix: str, + local_metadata: dict, strict: bool, + missing_keys: Union[List[str], str], + unexpected_keys: Union[List[str], str], + error_msgs: Union[List[str], str]) -> None: + """Exchange bbox_head key to rpn_head key when loading two-stage + weights into single-stage model.""" + bbox_head_prefix = prefix + '.bbox_head' if prefix else 'bbox_head' + bbox_head_keys = [ + k for k in state_dict.keys() if k.startswith(bbox_head_prefix) + ] + rpn_head_prefix = prefix + '.rpn_head' if prefix else 'rpn_head' + rpn_head_keys = [ + k for k in state_dict.keys() if k.startswith(rpn_head_prefix) + ] + if len(bbox_head_keys) == 0 and len(rpn_head_keys) != 0: + for rpn_head_key in rpn_head_keys: + bbox_head_key = bbox_head_prefix + \ + rpn_head_key[len(rpn_head_prefix):] + state_dict[bbox_head_key] = state_dict.pop(rpn_head_key) + super()._load_from_state_dict(state_dict, prefix, local_metadata, + strict, missing_keys, unexpected_keys, + error_msgs) + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> Union[dict, list]: + """Calculate losses from a batch of inputs and data samples. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + x = self.extract_feat(batch_inputs) + losses = self.bbox_head.loss(x, batch_data_samples) + return losses + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to True. + + Returns: + list[:obj:`DetDataSample`]: Detection results of the + input images. Each DetDataSample usually contain + 'pred_instances'. And the ``pred_instances`` usually + contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + x = self.extract_feat(batch_inputs) + results_list = self.bbox_head.predict( + x, batch_data_samples, rescale=rescale) + batch_data_samples = self.add_pred_to_datasample( + batch_data_samples, results_list) + return batch_data_samples + + def _forward( + self, + batch_inputs: Tensor, + batch_data_samples: OptSampleList = None) -> Tuple[List[Tensor]]: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns: + tuple[list]: A tuple of features from ``bbox_head`` forward. + """ + x = self.extract_feat(batch_inputs) + results = self.bbox_head.forward(x) + return results + + def extract_feat(self, batch_inputs: Tensor) -> Tuple[Tensor]: + """Extract features. + + Args: + batch_inputs (Tensor): Image tensor with shape (N, C, H ,W). + + Returns: + tuple[Tensor]: Multi-level features that may have + different resolutions. + """ + x = self.backbone(batch_inputs) + if self.with_neck: + x = self.neck(x) + return x diff --git a/mmdetection/mmdet/models/detectors/single_stage_instance_seg.py b/mmdetection/mmdet/models/detectors/single_stage_instance_seg.py new file mode 100644 index 00000000..acb5f0d2 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/single_stage_instance_seg.py @@ -0,0 +1,180 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import Tuple + +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList, SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .base import BaseDetector + +INF = 1e8 + + +@MODELS.register_module() +class SingleStageInstanceSegmentor(BaseDetector): + """Base class for single-stage instance segmentors.""" + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + bbox_head: OptConfigType = None, + mask_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + self.backbone = MODELS.build(backbone) + if neck is not None: + self.neck = MODELS.build(neck) + else: + self.neck = None + if bbox_head is not None: + bbox_head.update(train_cfg=copy.deepcopy(train_cfg)) + bbox_head.update(test_cfg=copy.deepcopy(test_cfg)) + self.bbox_head = MODELS.build(bbox_head) + else: + self.bbox_head = None + + assert mask_head, f'`mask_head` must ' \ + f'be implemented in {self.__class__.__name__}' + mask_head.update(train_cfg=copy.deepcopy(train_cfg)) + mask_head.update(test_cfg=copy.deepcopy(test_cfg)) + self.mask_head = MODELS.build(mask_head) + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + def extract_feat(self, batch_inputs: Tensor) -> Tuple[Tensor]: + """Extract features. + + Args: + batch_inputs (Tensor): Image tensor with shape (N, C, H ,W). + + Returns: + tuple[Tensor]: Multi-level features that may have different + resolutions. + """ + x = self.backbone(batch_inputs) + if self.with_neck: + x = self.neck(x) + return x + + def _forward(self, + batch_inputs: Tensor, + batch_data_samples: OptSampleList = None, + **kwargs) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + + Returns: + tuple: A tuple of features from ``bbox_head`` forward. + """ + outs = () + # backbone + x = self.extract_feat(batch_inputs) + # bbox_head + positive_infos = None + if self.with_bbox: + assert batch_data_samples is not None + bbox_outs = self.bbox_head.forward(x) + outs = outs + (bbox_outs, ) + # It is necessary to use `bbox_head.loss` to update + # `_raw_positive_infos` which will be used in `get_positive_infos` + # positive_infos will be used in the following mask head. + _ = self.bbox_head.loss(x, batch_data_samples, **kwargs) + positive_infos = self.bbox_head.get_positive_infos() + # mask_head + if positive_infos is None: + mask_outs = self.mask_head.forward(x) + else: + mask_outs = self.mask_head.forward(x, positive_infos) + outs = outs + (mask_outs, ) + return outs + + def loss(self, batch_inputs: Tensor, batch_data_samples: SampleList, + **kwargs) -> dict: + """ + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + x = self.extract_feat(batch_inputs) + losses = dict() + + positive_infos = None + # CondInst and YOLACT have bbox_head + if self.with_bbox: + bbox_losses = self.bbox_head.loss(x, batch_data_samples, **kwargs) + losses.update(bbox_losses) + # get positive information from bbox head, which will be used + # in the following mask head. + positive_infos = self.bbox_head.get_positive_infos() + + mask_loss = self.mask_head.loss( + x, batch_data_samples, positive_infos=positive_infos, **kwargs) + # avoid loss override + assert not set(mask_loss.keys()) & set(losses.keys()) + + losses.update(mask_loss) + return losses + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True, + **kwargs) -> SampleList: + """Perform forward propagation of the mask head and predict mask + results on the features of the upstream network. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to False. + + Returns: + list[:obj:`DetDataSample`]: Detection results of the + input images. Each DetDataSample usually contain + 'pred_instances'. And the ``pred_instances`` usually + contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + x = self.extract_feat(batch_inputs) + if self.with_bbox: + # the bbox branch does not need to be scaled to the original + # image scale, because the mask branch will scale both bbox + # and mask at the same time. + bbox_rescale = rescale if not self.with_mask else False + results_list = self.bbox_head.predict( + x, batch_data_samples, rescale=bbox_rescale) + else: + results_list = None + + results_list = self.mask_head.predict( + x, batch_data_samples, rescale=rescale, results_list=results_list) + + batch_data_samples = self.add_pred_to_datasample( + batch_data_samples, results_list) + return batch_data_samples diff --git a/mmdetection/mmdet/models/detectors/soft_teacher.py b/mmdetection/mmdet/models/detectors/soft_teacher.py new file mode 100644 index 00000000..80853f1d --- /dev/null +++ b/mmdetection/mmdet/models/detectors/soft_teacher.py @@ -0,0 +1,378 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import List, Optional, Tuple + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.utils import (filter_gt_instances, rename_loss_dict, + reweight_loss_dict) +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi, bbox_project +from mmdet.utils import ConfigType, InstanceList, OptConfigType, OptMultiConfig +from ..utils.misc import unpack_gt_instances +from .semi_base import SemiBaseDetector + + +@MODELS.register_module() +class SoftTeacher(SemiBaseDetector): + r"""Implementation of `End-to-End Semi-Supervised Object Detection + with Soft Teacher `_ + + Args: + detector (:obj:`ConfigDict` or dict): The detector config. + semi_train_cfg (:obj:`ConfigDict` or dict, optional): + The semi-supervised training config. + semi_test_cfg (:obj:`ConfigDict` or dict, optional): + The semi-supervised testing config. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + detector: ConfigType, + semi_train_cfg: OptConfigType = None, + semi_test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + detector=detector, + semi_train_cfg=semi_train_cfg, + semi_test_cfg=semi_test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) + + def loss_by_pseudo_instances(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + batch_info: Optional[dict] = None) -> dict: + """Calculate losses from a batch of inputs and pseudo data samples. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`, + which are `pseudo_instance` or `pseudo_panoptic_seg` + or `pseudo_sem_seg` in fact. + batch_info (dict): Batch information of teacher model + forward propagation process. Defaults to None. + + Returns: + dict: A dictionary of loss components + """ + + x = self.student.extract_feat(batch_inputs) + + losses = {} + rpn_losses, rpn_results_list = self.rpn_loss_by_pseudo_instances( + x, batch_data_samples) + losses.update(**rpn_losses) + losses.update(**self.rcnn_cls_loss_by_pseudo_instances( + x, rpn_results_list, batch_data_samples, batch_info)) + losses.update(**self.rcnn_reg_loss_by_pseudo_instances( + x, rpn_results_list, batch_data_samples)) + unsup_weight = self.semi_train_cfg.get('unsup_weight', 1.) + return rename_loss_dict('unsup_', + reweight_loss_dict(losses, unsup_weight)) + + @torch.no_grad() + def get_pseudo_instances( + self, batch_inputs: Tensor, batch_data_samples: SampleList + ) -> Tuple[SampleList, Optional[dict]]: + """Get pseudo instances from teacher model.""" + assert self.teacher.with_bbox, 'Bbox head must be implemented.' + x = self.teacher.extract_feat(batch_inputs) + + # If there are no pre-defined proposals, use RPN to get proposals + if batch_data_samples[0].get('proposals', None) is None: + rpn_results_list = self.teacher.rpn_head.predict( + x, batch_data_samples, rescale=False) + else: + rpn_results_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + + results_list = self.teacher.roi_head.predict( + x, rpn_results_list, batch_data_samples, rescale=False) + + for data_samples, results in zip(batch_data_samples, results_list): + data_samples.gt_instances = results + + batch_data_samples = filter_gt_instances( + batch_data_samples, + score_thr=self.semi_train_cfg.pseudo_label_initial_score_thr) + + reg_uncs_list = self.compute_uncertainty_with_aug( + x, batch_data_samples) + + for data_samples, reg_uncs in zip(batch_data_samples, reg_uncs_list): + data_samples.gt_instances['reg_uncs'] = reg_uncs + data_samples.gt_instances.bboxes = bbox_project( + data_samples.gt_instances.bboxes, + torch.from_numpy(data_samples.homography_matrix).inverse().to( + self.data_preprocessor.device), data_samples.ori_shape) + + batch_info = { + 'feat': x, + 'img_shape': [], + 'homography_matrix': [], + 'metainfo': [] + } + for data_samples in batch_data_samples: + batch_info['img_shape'].append(data_samples.img_shape) + batch_info['homography_matrix'].append( + torch.from_numpy(data_samples.homography_matrix).to( + self.data_preprocessor.device)) + batch_info['metainfo'].append(data_samples.metainfo) + return batch_data_samples, batch_info + + def rpn_loss_by_pseudo_instances(self, x: Tuple[Tensor], + batch_data_samples: SampleList) -> dict: + """Calculate rpn loss from a batch of inputs and pseudo data samples. + + Args: + x (tuple[Tensor]): Features from FPN. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`, + which are `pseudo_instance` or `pseudo_panoptic_seg` + or `pseudo_sem_seg` in fact. + Returns: + dict: A dictionary of rpn loss components + """ + + rpn_data_samples = copy.deepcopy(batch_data_samples) + rpn_data_samples = filter_gt_instances( + rpn_data_samples, score_thr=self.semi_train_cfg.rpn_pseudo_thr) + proposal_cfg = self.student.train_cfg.get('rpn_proposal', + self.student.test_cfg.rpn) + # set cat_id of gt_labels to 0 in RPN + for data_sample in rpn_data_samples: + data_sample.gt_instances.labels = \ + torch.zeros_like(data_sample.gt_instances.labels) + + rpn_losses, rpn_results_list = self.student.rpn_head.loss_and_predict( + x, rpn_data_samples, proposal_cfg=proposal_cfg) + for key in rpn_losses.keys(): + if 'loss' in key and 'rpn' not in key: + rpn_losses[f'rpn_{key}'] = rpn_losses.pop(key) + return rpn_losses, rpn_results_list + + def rcnn_cls_loss_by_pseudo_instances(self, x: Tuple[Tensor], + unsup_rpn_results_list: InstanceList, + batch_data_samples: SampleList, + batch_info: dict) -> dict: + """Calculate classification loss from a batch of inputs and pseudo data + samples. + + Args: + x (tuple[Tensor]): List of multi-level img features. + unsup_rpn_results_list (list[:obj:`InstanceData`]): + List of region proposals. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`, + which are `pseudo_instance` or `pseudo_panoptic_seg` + or `pseudo_sem_seg` in fact. + batch_info (dict): Batch information of teacher model + forward propagation process. + + Returns: + dict[str, Tensor]: A dictionary of rcnn + classification loss components + """ + rpn_results_list = copy.deepcopy(unsup_rpn_results_list) + cls_data_samples = copy.deepcopy(batch_data_samples) + cls_data_samples = filter_gt_instances( + cls_data_samples, score_thr=self.semi_train_cfg.cls_pseudo_thr) + + outputs = unpack_gt_instances(cls_data_samples) + batch_gt_instances, batch_gt_instances_ignore, _ = outputs + + # assign gts and sample proposals + num_imgs = len(cls_data_samples) + sampling_results = [] + for i in range(num_imgs): + # rename rpn_results.bboxes to rpn_results.priors + rpn_results = rpn_results_list[i] + rpn_results.priors = rpn_results.pop('bboxes') + assign_result = self.student.roi_head.bbox_assigner.assign( + rpn_results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = self.student.roi_head.bbox_sampler.sample( + assign_result, + rpn_results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + sampling_results.append(sampling_result) + + selected_bboxes = [res.priors for res in sampling_results] + rois = bbox2roi(selected_bboxes) + bbox_results = self.student.roi_head._bbox_forward(x, rois) + # cls_reg_targets is a tuple of labels, label_weights, + # and bbox_targets, bbox_weights + cls_reg_targets = self.student.roi_head.bbox_head.get_targets( + sampling_results, self.student.train_cfg.rcnn) + + selected_results_list = [] + for bboxes, data_samples, teacher_matrix, teacher_img_shape in zip( + selected_bboxes, batch_data_samples, + batch_info['homography_matrix'], batch_info['img_shape']): + student_matrix = torch.tensor( + data_samples.homography_matrix, device=teacher_matrix.device) + homography_matrix = teacher_matrix @ student_matrix.inverse() + projected_bboxes = bbox_project(bboxes, homography_matrix, + teacher_img_shape) + selected_results_list.append(InstanceData(bboxes=projected_bboxes)) + + with torch.no_grad(): + results_list = self.teacher.roi_head.predict_bbox( + batch_info['feat'], + batch_info['metainfo'], + selected_results_list, + rcnn_test_cfg=None, + rescale=False) + bg_score = torch.cat( + [results.scores[:, -1] for results in results_list]) + # cls_reg_targets[0] is labels + neg_inds = cls_reg_targets[ + 0] == self.student.roi_head.bbox_head.num_classes + # cls_reg_targets[1] is label_weights + cls_reg_targets[1][neg_inds] = bg_score[neg_inds].detach() + + losses = self.student.roi_head.bbox_head.loss( + bbox_results['cls_score'], bbox_results['bbox_pred'], rois, + *cls_reg_targets) + # cls_reg_targets[1] is label_weights + losses['loss_cls'] = losses['loss_cls'] * len( + cls_reg_targets[1]) / max(sum(cls_reg_targets[1]), 1.0) + return losses + + def rcnn_reg_loss_by_pseudo_instances( + self, x: Tuple[Tensor], unsup_rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> dict: + """Calculate rcnn regression loss from a batch of inputs and pseudo + data samples. + + Args: + x (tuple[Tensor]): List of multi-level img features. + unsup_rpn_results_list (list[:obj:`InstanceData`]): + List of region proposals. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`, + which are `pseudo_instance` or `pseudo_panoptic_seg` + or `pseudo_sem_seg` in fact. + + Returns: + dict[str, Tensor]: A dictionary of rcnn + regression loss components + """ + rpn_results_list = copy.deepcopy(unsup_rpn_results_list) + reg_data_samples = copy.deepcopy(batch_data_samples) + for data_samples in reg_data_samples: + if data_samples.gt_instances.bboxes.shape[0] > 0: + data_samples.gt_instances = data_samples.gt_instances[ + data_samples.gt_instances.reg_uncs < + self.semi_train_cfg.reg_pseudo_thr] + roi_losses = self.student.roi_head.loss(x, rpn_results_list, + reg_data_samples) + return {'loss_bbox': roi_losses['loss_bbox']} + + def compute_uncertainty_with_aug( + self, x: Tuple[Tensor], + batch_data_samples: SampleList) -> List[Tensor]: + """Compute uncertainty with augmented bboxes. + + Args: + x (tuple[Tensor]): List of multi-level img features. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`, + which are `pseudo_instance` or `pseudo_panoptic_seg` + or `pseudo_sem_seg` in fact. + + Returns: + list[Tensor]: A list of uncertainty for pseudo bboxes. + """ + auged_results_list = self.aug_box(batch_data_samples, + self.semi_train_cfg.jitter_times, + self.semi_train_cfg.jitter_scale) + # flatten + auged_results_list = [ + InstanceData(bboxes=auged.reshape(-1, auged.shape[-1])) + for auged in auged_results_list + ] + + self.teacher.roi_head.test_cfg = None + results_list = self.teacher.roi_head.predict( + x, auged_results_list, batch_data_samples, rescale=False) + self.teacher.roi_head.test_cfg = self.teacher.test_cfg.rcnn + + reg_channel = max( + [results.bboxes.shape[-1] for results in results_list]) // 4 + bboxes = [ + results.bboxes.reshape(self.semi_train_cfg.jitter_times, -1, + results.bboxes.shape[-1]) + if results.bboxes.numel() > 0 else results.bboxes.new_zeros( + self.semi_train_cfg.jitter_times, 0, 4 * reg_channel).float() + for results in results_list + ] + + box_unc = [bbox.std(dim=0) for bbox in bboxes] + bboxes = [bbox.mean(dim=0) for bbox in bboxes] + labels = [ + data_samples.gt_instances.labels + for data_samples in batch_data_samples + ] + if reg_channel != 1: + bboxes = [ + bbox.reshape(bbox.shape[0], reg_channel, + 4)[torch.arange(bbox.shape[0]), label] + for bbox, label in zip(bboxes, labels) + ] + box_unc = [ + unc.reshape(unc.shape[0], reg_channel, + 4)[torch.arange(unc.shape[0]), label] + for unc, label in zip(box_unc, labels) + ] + + box_shape = [(bbox[:, 2:4] - bbox[:, :2]).clamp(min=1.0) + for bbox in bboxes] + box_unc = [ + torch.mean( + unc / wh[:, None, :].expand(-1, 2, 2).reshape(-1, 4), dim=-1) + if wh.numel() > 0 else unc for unc, wh in zip(box_unc, box_shape) + ] + return box_unc + + @staticmethod + def aug_box(batch_data_samples, times, frac): + """Augment bboxes with jitter.""" + + def _aug_single(box): + box_scale = box[:, 2:4] - box[:, :2] + box_scale = ( + box_scale.clamp(min=1)[:, None, :].expand(-1, 2, + 2).reshape(-1, 4)) + aug_scale = box_scale * frac # [n,4] + + offset = ( + torch.randn(times, box.shape[0], 4, device=box.device) * + aug_scale[None, ...]) + new_box = box.clone()[None, ...].expand(times, box.shape[0], + -1) + offset + return new_box + + return [ + _aug_single(data_samples.gt_instances.bboxes) + for data_samples in batch_data_samples + ] diff --git a/mmdetection/mmdet/models/detectors/solo.py b/mmdetection/mmdet/models/detectors/solo.py new file mode 100644 index 00000000..6bf47ba2 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/solo.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage_instance_seg import SingleStageInstanceSegmentor + + +@MODELS.register_module() +class SOLO(SingleStageInstanceSegmentor): + """`SOLO: Segmenting Objects by Locations + `_ + + """ + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + bbox_head: OptConfigType = None, + mask_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + mask_head=mask_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/solov2.py b/mmdetection/mmdet/models/detectors/solov2.py new file mode 100644 index 00000000..1eefe4c5 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/solov2.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage_instance_seg import SingleStageInstanceSegmentor + + +@MODELS.register_module() +class SOLOv2(SingleStageInstanceSegmentor): + """`SOLOv2: Dynamic and Fast Instance Segmentation + `_ + + """ + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + bbox_head: OptConfigType = None, + mask_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + mask_head=mask_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/sparse_rcnn.py b/mmdetection/mmdet/models/detectors/sparse_rcnn.py new file mode 100644 index 00000000..75442a69 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/sparse_rcnn.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .two_stage import TwoStageDetector + + +@MODELS.register_module() +class SparseRCNN(TwoStageDetector): + r"""Implementation of `Sparse R-CNN: End-to-End Object Detection with + Learnable Proposals `_""" + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + rpn_head: OptConfigType = None, + roi_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) + assert self.with_rpn, 'Sparse R-CNN and QueryInst ' \ + 'do not support external proposals' diff --git a/mmdetection/mmdet/models/detectors/tood.py b/mmdetection/mmdet/models/detectors/tood.py new file mode 100644 index 00000000..38720482 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/tood.py @@ -0,0 +1,42 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class TOOD(SingleStageDetector): + r"""Implementation of `TOOD: Task-aligned One-stage Object Detection. + `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of TOOD. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of TOOD. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/trident_faster_rcnn.py b/mmdetection/mmdet/models/detectors/trident_faster_rcnn.py new file mode 100644 index 00000000..4244925b --- /dev/null +++ b/mmdetection/mmdet/models/detectors/trident_faster_rcnn.py @@ -0,0 +1,81 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .faster_rcnn import FasterRCNN + + +@MODELS.register_module() +class TridentFasterRCNN(FasterRCNN): + """Implementation of `TridentNet `_""" + + def __init__(self, + backbone: ConfigType, + rpn_head: ConfigType, + roi_head: ConfigType, + train_cfg: ConfigType, + test_cfg: ConfigType, + neck: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + + super().__init__( + backbone=backbone, + neck=neck, + rpn_head=rpn_head, + roi_head=roi_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) + assert self.backbone.num_branch == self.roi_head.num_branch + assert self.backbone.test_branch_idx == self.roi_head.test_branch_idx + self.num_branch = self.backbone.num_branch + self.test_branch_idx = self.backbone.test_branch_idx + + def _forward(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> tuple: + """copy the ``batch_data_samples`` to fit multi-branch.""" + num_branch = self.num_branch \ + if self.training or self.test_branch_idx == -1 else 1 + trident_data_samples = batch_data_samples * num_branch + return super()._forward( + batch_inputs=batch_inputs, batch_data_samples=trident_data_samples) + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> dict: + """copy the ``batch_data_samples`` to fit multi-branch.""" + num_branch = self.num_branch \ + if self.training or self.test_branch_idx == -1 else 1 + trident_data_samples = batch_data_samples * num_branch + return super().loss( + batch_inputs=batch_inputs, batch_data_samples=trident_data_samples) + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> SampleList: + """copy the ``batch_data_samples`` to fit multi-branch.""" + num_branch = self.num_branch \ + if self.training or self.test_branch_idx == -1 else 1 + trident_data_samples = batch_data_samples * num_branch + return super().predict( + batch_inputs=batch_inputs, + batch_data_samples=trident_data_samples, + rescale=rescale) + + # TODO need to refactor + def aug_test(self, imgs, img_metas, rescale=False): + """Test with augmentations. + + If rescale is False, then returned bboxes and masks will fit the scale + of imgs[0]. + """ + x = self.extract_feats(imgs) + num_branch = (self.num_branch if self.test_branch_idx == -1 else 1) + trident_img_metas = [img_metas * num_branch for img_metas in img_metas] + proposal_list = self.rpn_head.aug_test_rpn(x, trident_img_metas) + return self.roi_head.aug_test( + x, proposal_list, img_metas, rescale=rescale) diff --git a/mmdetection/mmdet/models/detectors/two_stage.py b/mmdetection/mmdet/models/detectors/two_stage.py new file mode 100644 index 00000000..4e83df9e --- /dev/null +++ b/mmdetection/mmdet/models/detectors/two_stage.py @@ -0,0 +1,243 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import warnings +from typing import List, Tuple, Union + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .base import BaseDetector + + +@MODELS.register_module() +class TwoStageDetector(BaseDetector): + """Base class for two-stage detectors. + + Two-stage detectors typically consisting of a region proposal network and a + task-specific regression head. + """ + + def __init__(self, + backbone: ConfigType, + neck: OptConfigType = None, + rpn_head: OptConfigType = None, + roi_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + self.backbone = MODELS.build(backbone) + + if neck is not None: + self.neck = MODELS.build(neck) + + if rpn_head is not None: + rpn_train_cfg = train_cfg.rpn if train_cfg is not None else None + rpn_head_ = rpn_head.copy() + rpn_head_.update(train_cfg=rpn_train_cfg, test_cfg=test_cfg.rpn) + rpn_head_num_classes = rpn_head_.get('num_classes', None) + if rpn_head_num_classes is None: + rpn_head_.update(num_classes=1) + else: + if rpn_head_num_classes != 1: + warnings.warn( + 'The `num_classes` should be 1 in RPN, but get ' + f'{rpn_head_num_classes}, please set ' + 'rpn_head.num_classes = 1 in your config file.') + rpn_head_.update(num_classes=1) + self.rpn_head = MODELS.build(rpn_head_) + + if roi_head is not None: + # update train and test cfg here for now + # TODO: refactor assigner & sampler + rcnn_train_cfg = train_cfg.rcnn if train_cfg is not None else None + roi_head.update(train_cfg=rcnn_train_cfg) + roi_head.update(test_cfg=test_cfg.rcnn) + self.roi_head = MODELS.build(roi_head) + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + def _load_from_state_dict(self, state_dict: dict, prefix: str, + local_metadata: dict, strict: bool, + missing_keys: Union[List[str], str], + unexpected_keys: Union[List[str], str], + error_msgs: Union[List[str], str]) -> None: + """Exchange bbox_head key to rpn_head key when loading single-stage + weights into two-stage model.""" + bbox_head_prefix = prefix + '.bbox_head' if prefix else 'bbox_head' + bbox_head_keys = [ + k for k in state_dict.keys() if k.startswith(bbox_head_prefix) + ] + rpn_head_prefix = prefix + '.rpn_head' if prefix else 'rpn_head' + rpn_head_keys = [ + k for k in state_dict.keys() if k.startswith(rpn_head_prefix) + ] + if len(bbox_head_keys) != 0 and len(rpn_head_keys) == 0: + for bbox_head_key in bbox_head_keys: + rpn_head_key = rpn_head_prefix + \ + bbox_head_key[len(bbox_head_prefix):] + state_dict[rpn_head_key] = state_dict.pop(bbox_head_key) + super()._load_from_state_dict(state_dict, prefix, local_metadata, + strict, missing_keys, unexpected_keys, + error_msgs) + + @property + def with_rpn(self) -> bool: + """bool: whether the detector has RPN""" + return hasattr(self, 'rpn_head') and self.rpn_head is not None + + @property + def with_roi_head(self) -> bool: + """bool: whether the detector has a RoI head""" + return hasattr(self, 'roi_head') and self.roi_head is not None + + def extract_feat(self, batch_inputs: Tensor) -> Tuple[Tensor]: + """Extract features. + + Args: + batch_inputs (Tensor): Image tensor with shape (N, C, H ,W). + + Returns: + tuple[Tensor]: Multi-level features that may have + different resolutions. + """ + x = self.backbone(batch_inputs) + if self.with_neck: + x = self.neck(x) + return x + + def _forward(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns: + tuple: A tuple of features from ``rpn_head`` and ``roi_head`` + forward. + """ + results = () + x = self.extract_feat(batch_inputs) + + if self.with_rpn: + rpn_results_list = self.rpn_head.predict( + x, batch_data_samples, rescale=False) + else: + assert batch_data_samples[0].get('proposals', None) is not None + rpn_results_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + roi_outs = self.roi_head.forward(x, rpn_results_list, + batch_data_samples) + results = results + (roi_outs, ) + return results + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components + """ + x = self.extract_feat(batch_inputs) + + losses = dict() + + # RPN forward and loss + if self.with_rpn: + proposal_cfg = self.train_cfg.get('rpn_proposal', + self.test_cfg.rpn) + rpn_data_samples = copy.deepcopy(batch_data_samples) + # set cat_id of gt_labels to 0 in RPN + for data_sample in rpn_data_samples: + data_sample.gt_instances.labels = \ + torch.zeros_like(data_sample.gt_instances.labels) + + rpn_losses, rpn_results_list = self.rpn_head.loss_and_predict( + x, rpn_data_samples, proposal_cfg=proposal_cfg) + # avoid get same name with roi_head loss + keys = rpn_losses.keys() + for key in list(keys): + if 'loss' in key and 'rpn' not in key: + rpn_losses[f'rpn_{key}'] = rpn_losses.pop(key) + losses.update(rpn_losses) + else: + assert batch_data_samples[0].get('proposals', None) is not None + # use pre-defined proposals in InstanceData for the second stage + # to extract ROI features. + rpn_results_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + + roi_losses = self.roi_head.loss(x, rpn_results_list, + batch_data_samples) + losses.update(roi_losses) + + return losses + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to True. + + Returns: + list[:obj:`DetDataSample`]: Return the detection results of the + input images. The returns value is DetDataSample, + which usually contain 'pred_instances'. And the + ``pred_instances`` usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + + assert self.with_bbox, 'Bbox head must be implemented.' + x = self.extract_feat(batch_inputs) + + # If there are no pre-defined proposals, use RPN to get proposals + if batch_data_samples[0].get('proposals', None) is None: + rpn_results_list = self.rpn_head.predict( + x, batch_data_samples, rescale=False) + else: + rpn_results_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + + results_list = self.roi_head.predict( + x, rpn_results_list, batch_data_samples, rescale=rescale) + + batch_data_samples = self.add_pred_to_datasample( + batch_data_samples, results_list) + return batch_data_samples diff --git a/mmdetection/mmdet/models/detectors/vfnet.py b/mmdetection/mmdet/models/detectors/vfnet.py new file mode 100644 index 00000000..a695513f --- /dev/null +++ b/mmdetection/mmdet/models/detectors/vfnet.py @@ -0,0 +1,42 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class VFNet(SingleStageDetector): + """Implementation of `VarifocalNet + (VFNet).`_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of VFNet. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of VFNet. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/yolact.py b/mmdetection/mmdet/models/detectors/yolact.py new file mode 100644 index 00000000..f15fb7b7 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/yolact.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage_instance_seg import SingleStageInstanceSegmentor + + +@MODELS.register_module() +class YOLACT(SingleStageInstanceSegmentor): + """Implementation of `YOLACT `_""" + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + mask_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + mask_head=mask_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/yolo.py b/mmdetection/mmdet/models/detectors/yolo.py new file mode 100644 index 00000000..5cb9a9cd --- /dev/null +++ b/mmdetection/mmdet/models/detectors/yolo.py @@ -0,0 +1,45 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Copyright (c) 2019 Western Digital Corporation or its affiliates. + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class YOLOV3(SingleStageDetector): + r"""Implementation of `Yolov3: An incremental improvement + `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of YOLOX. Default: None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of YOLOX. Default: None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): + Model preprocessing config for processing the input data. + it usually includes ``to_rgb``, ``pad_size_divisor``, + ``pad_value``, ``mean`` and ``std``. Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/yolof.py b/mmdetection/mmdet/models/detectors/yolof.py new file mode 100644 index 00000000..c6d98b91 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/yolof.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class YOLOF(SingleStageDetector): + r"""Implementation of `You Only Look One-level Feature + `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone module. + neck (:obj:`ConfigDict` or dict): The neck module. + bbox_head (:obj:`ConfigDict` or dict): The bbox head module. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of YOLOF. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of YOLOF. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): + Model preprocessing config for processing the input data. + it usually includes ``to_rgb``, ``pad_size_divisor``, + ``pad_value``, ``mean`` and ``std``. Defaults to None. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/detectors/yolox.py b/mmdetection/mmdet/models/detectors/yolox.py new file mode 100644 index 00000000..df9190c9 --- /dev/null +++ b/mmdetection/mmdet/models/detectors/yolox.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .single_stage import SingleStageDetector + + +@MODELS.register_module() +class YOLOX(SingleStageDetector): + r"""Implementation of `YOLOX: Exceeding YOLO Series in 2021 + `_ + + Args: + backbone (:obj:`ConfigDict` or dict): The backbone config. + neck (:obj:`ConfigDict` or dict): The neck config. + bbox_head (:obj:`ConfigDict` or dict): The bbox head config. + train_cfg (:obj:`ConfigDict` or dict, optional): The training config + of YOLOX. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): The testing config + of YOLOX. Defaults to None. + data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of + :class:`DetDataPreprocessor` to process the input data. + Defaults to None. + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/mmdet/models/language_models/__init__.py b/mmdetection/mmdet/models/language_models/__init__.py new file mode 100644 index 00000000..70f1a22c --- /dev/null +++ b/mmdetection/mmdet/models/language_models/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .bert import BertModel + +__all__ = ['BertModel'] diff --git a/mmdetection/mmdet/models/language_models/bert.py b/mmdetection/mmdet/models/language_models/bert.py new file mode 100644 index 00000000..efb0f46b --- /dev/null +++ b/mmdetection/mmdet/models/language_models/bert.py @@ -0,0 +1,231 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections import OrderedDict +from typing import Sequence + +import torch +from mmengine.model import BaseModel +from torch import nn + +try: + from transformers import AutoTokenizer, BertConfig + from transformers import BertModel as HFBertModel +except ImportError: + AutoTokenizer = None + HFBertModel = None + +from mmdet.registry import MODELS + + +def generate_masks_with_special_tokens_and_transfer_map( + tokenized, special_tokens_list): + """Generate attention mask between each pair of special tokens. + + Only token pairs in between two special tokens are attended to + and thus the attention mask for these pairs is positive. + + Args: + input_ids (torch.Tensor): input ids. Shape: [bs, num_token] + special_tokens_mask (list): special tokens mask. + + Returns: + Tuple(Tensor, Tensor): + - attention_mask is the attention mask between each tokens. + Only token pairs in between two special tokens are positive. + Shape: [bs, num_token, num_token]. + - position_ids is the position id of tokens within each valid sentence. + The id starts from 0 whenenver a special token is encountered. + Shape: [bs, num_token] + """ + input_ids = tokenized['input_ids'] + bs, num_token = input_ids.shape + # special_tokens_mask: + # bs, num_token. 1 for special tokens. 0 for normal tokens + special_tokens_mask = torch.zeros((bs, num_token), + device=input_ids.device).bool() + + for special_token in special_tokens_list: + special_tokens_mask |= input_ids == special_token + + # idxs: each row is a list of indices of special tokens + idxs = torch.nonzero(special_tokens_mask) + + # generate attention mask and positional ids + attention_mask = ( + torch.eye(num_token, + device=input_ids.device).bool().unsqueeze(0).repeat( + bs, 1, 1)) + position_ids = torch.zeros((bs, num_token), device=input_ids.device) + previous_col = 0 + for i in range(idxs.shape[0]): + row, col = idxs[i] + if (col == 0) or (col == num_token - 1): + attention_mask[row, col, col] = True + position_ids[row, col] = 0 + else: + attention_mask[row, previous_col + 1:col + 1, + previous_col + 1:col + 1] = True + position_ids[row, previous_col + 1:col + 1] = torch.arange( + 0, col - previous_col, device=input_ids.device) + previous_col = col + + return attention_mask, position_ids.to(torch.long) + + +@MODELS.register_module() +class BertModel(BaseModel): + """BERT model for language embedding only encoder. + + Args: + name (str, optional): name of the pretrained BERT model from + HuggingFace. Defaults to bert-base-uncased. + max_tokens (int, optional): maximum number of tokens to be + used for BERT. Defaults to 256. + pad_to_max (bool, optional): whether to pad the tokens to max_tokens. + Defaults to True. + use_sub_sentence_represent (bool, optional): whether to use sub + sentence represent introduced in `Grounding DINO + `. Defaults to False. + special_tokens_list (list, optional): special tokens used to split + subsentence. It cannot be None when `use_sub_sentence_represent` + is True. Defaults to None. + add_pooling_layer (bool, optional): whether to adding pooling + layer in bert encoder. Defaults to False. + num_layers_of_embedded (int, optional): number of layers of + the embedded model. Defaults to 1. + use_checkpoint (bool, optional): whether to use gradient checkpointing. + Defaults to False. + """ + + def __init__(self, + name: str = 'bert-base-uncased', + max_tokens: int = 256, + pad_to_max: bool = True, + use_sub_sentence_represent: bool = False, + special_tokens_list: list = None, + add_pooling_layer: bool = False, + num_layers_of_embedded: int = 1, + use_checkpoint: bool = False, + **kwargs) -> None: + + super().__init__(**kwargs) + self.max_tokens = max_tokens + self.pad_to_max = pad_to_max + + if AutoTokenizer is None: + raise RuntimeError( + 'transformers is not installed, please install it by: ' + 'pip install transformers.') + + self.tokenizer = AutoTokenizer.from_pretrained(name) + self.language_backbone = nn.Sequential( + OrderedDict([('body', + BertEncoder( + name, + add_pooling_layer=add_pooling_layer, + num_layers_of_embedded=num_layers_of_embedded, + use_checkpoint=use_checkpoint))])) + + self.use_sub_sentence_represent = use_sub_sentence_represent + if self.use_sub_sentence_represent: + assert special_tokens_list is not None, \ + 'special_tokens should not be None \ + if use_sub_sentence_represent is True' + + self.special_tokens = self.tokenizer.convert_tokens_to_ids( + special_tokens_list) + + def forward(self, captions: Sequence[str], **kwargs) -> dict: + """Forward function.""" + device = next(self.language_backbone.parameters()).device + tokenized = self.tokenizer.batch_encode_plus( + captions, + max_length=self.max_tokens, + padding='max_length' if self.pad_to_max else 'longest', + return_special_tokens_mask=True, + return_tensors='pt', + truncation=True).to(device) + input_ids = tokenized.input_ids + if self.use_sub_sentence_represent: + attention_mask, position_ids = \ + generate_masks_with_special_tokens_and_transfer_map( + tokenized, self.special_tokens) + token_type_ids = tokenized['token_type_ids'] + + else: + attention_mask = tokenized.attention_mask + position_ids = None + token_type_ids = None + + tokenizer_input = { + 'input_ids': input_ids, + 'attention_mask': attention_mask, + 'position_ids': position_ids, + 'token_type_ids': token_type_ids + } + language_dict_features = self.language_backbone(tokenizer_input) + if self.use_sub_sentence_represent: + language_dict_features['position_ids'] = position_ids + language_dict_features[ + 'text_token_mask'] = tokenized.attention_mask.bool() + return language_dict_features + + +class BertEncoder(nn.Module): + """BERT encoder for language embedding. + + Args: + name (str): name of the pretrained BERT model from HuggingFace. + Defaults to bert-base-uncased. + add_pooling_layer (bool): whether to add a pooling layer. + num_layers_of_embedded (int): number of layers of the embedded model. + Defaults to 1. + use_checkpoint (bool): whether to use gradient checkpointing. + Defaults to False. + """ + + def __init__(self, + name: str, + add_pooling_layer: bool = False, + num_layers_of_embedded: int = 1, + use_checkpoint: bool = False): + super().__init__() + if BertConfig is None: + raise RuntimeError( + 'transformers is not installed, please install it by: ' + 'pip install transformers.') + config = BertConfig.from_pretrained(name) + config.gradient_checkpointing = use_checkpoint + # only encoder + self.model = HFBertModel.from_pretrained( + name, add_pooling_layer=add_pooling_layer, config=config) + self.language_dim = config.hidden_size + self.num_layers_of_embedded = num_layers_of_embedded + + def forward(self, x) -> dict: + mask = x['attention_mask'] + + outputs = self.model( + input_ids=x['input_ids'], + attention_mask=mask, + position_ids=x['position_ids'], + token_type_ids=x['token_type_ids'], + output_hidden_states=True, + ) + + # outputs has 13 layers, 1 input layer and 12 hidden layers + encoded_layers = outputs.hidden_states[1:] + features = torch.stack(encoded_layers[-self.num_layers_of_embedded:], + 1).mean(1) + # language embedding has shape [len(phrase), seq_len, language_dim] + features = features / self.num_layers_of_embedded + if mask.dim() == 2: + embedded = features * mask.unsqueeze(-1).float() + else: + embedded = features + + results = { + 'embedded': embedded, + 'masks': mask, + 'hidden': encoded_layers[-1] + } + return results diff --git a/mmdetection/mmdet/models/layers/__init__.py b/mmdetection/mmdet/models/layers/__init__.py new file mode 100644 index 00000000..e3c41f64 --- /dev/null +++ b/mmdetection/mmdet/models/layers/__init__.py @@ -0,0 +1,65 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .activations import SiLU +from .bbox_nms import fast_nms, multiclass_nms +from .brick_wrappers import (AdaptiveAvgPool2d, FrozenBatchNorm2d, + adaptive_avg_pool2d) +from .conv_upsample import ConvUpsample +from .csp_layer import CSPLayer +from .dropblock import DropBlock +from .ema import ExpMomentumEMA +from .inverted_residual import InvertedResidual +from .matrix_nms import mask_matrix_nms +from .msdeformattn_pixel_decoder import MSDeformAttnPixelDecoder +from .normed_predictor import NormedConv2d, NormedLinear +from .pixel_decoder import PixelDecoder, TransformerEncoderPixelDecoder +from .positional_encoding import (LearnedPositionalEncoding, + SinePositionalEncoding, + SinePositionalEncoding3D) +from .res_layer import ResLayer, SimplifiedBasicBlock +from .se_layer import ChannelAttention, DyReLU, SELayer +# yapf: disable +from .transformer import (MLP, AdaptivePadding, CdnQueryGenerator, + ConditionalAttention, + ConditionalDetrTransformerDecoder, + ConditionalDetrTransformerDecoderLayer, + DABDetrTransformerDecoder, + DABDetrTransformerDecoderLayer, + DABDetrTransformerEncoder, DDQTransformerDecoder, + DeformableDetrTransformerDecoder, + DeformableDetrTransformerDecoderLayer, + DeformableDetrTransformerEncoder, + DeformableDetrTransformerEncoderLayer, + DetrTransformerDecoder, DetrTransformerDecoderLayer, + DetrTransformerEncoder, DetrTransformerEncoderLayer, + DinoTransformerDecoder, DynamicConv, + Mask2FormerTransformerDecoder, + Mask2FormerTransformerDecoderLayer, + Mask2FormerTransformerEncoder, PatchEmbed, + PatchMerging, coordinate_to_encoding, + inverse_sigmoid, nchw_to_nlc, nlc_to_nchw) + +# yapf: enable + +__all__ = [ + 'fast_nms', 'multiclass_nms', 'mask_matrix_nms', 'DropBlock', + 'PixelDecoder', 'TransformerEncoderPixelDecoder', + 'MSDeformAttnPixelDecoder', 'ResLayer', 'PatchMerging', + 'SinePositionalEncoding', 'LearnedPositionalEncoding', 'DynamicConv', + 'SimplifiedBasicBlock', 'NormedLinear', 'NormedConv2d', 'InvertedResidual', + 'SELayer', 'ConvUpsample', 'CSPLayer', 'adaptive_avg_pool2d', + 'AdaptiveAvgPool2d', 'PatchEmbed', 'nchw_to_nlc', 'nlc_to_nchw', 'DyReLU', + 'ExpMomentumEMA', 'inverse_sigmoid', 'ChannelAttention', 'SiLU', 'MLP', + 'DetrTransformerEncoderLayer', 'DetrTransformerDecoderLayer', + 'DetrTransformerEncoder', 'DetrTransformerDecoder', + 'DeformableDetrTransformerEncoder', 'DeformableDetrTransformerDecoder', + 'DeformableDetrTransformerEncoderLayer', + 'DeformableDetrTransformerDecoderLayer', 'AdaptivePadding', + 'coordinate_to_encoding', 'ConditionalAttention', + 'DABDetrTransformerDecoderLayer', 'DABDetrTransformerDecoder', + 'DABDetrTransformerEncoder', 'DDQTransformerDecoder', + 'ConditionalDetrTransformerDecoder', + 'ConditionalDetrTransformerDecoderLayer', 'DinoTransformerDecoder', + 'CdnQueryGenerator', 'Mask2FormerTransformerEncoder', + 'Mask2FormerTransformerDecoderLayer', 'Mask2FormerTransformerDecoder', + 'SinePositionalEncoding3D', 'FrozenBatchNorm2d' +] diff --git a/mmdetection/mmdet/models/layers/activations.py b/mmdetection/mmdet/models/layers/activations.py new file mode 100644 index 00000000..9e73ef42 --- /dev/null +++ b/mmdetection/mmdet/models/layers/activations.py @@ -0,0 +1,22 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmengine.utils import digit_version + +from mmdet.registry import MODELS + +if digit_version(torch.__version__) >= digit_version('1.7.0'): + from torch.nn import SiLU +else: + + class SiLU(nn.Module): + """Sigmoid Weighted Liner Unit.""" + + def __init__(self, inplace=True): + super().__init__() + + def forward(self, inputs) -> torch.Tensor: + return inputs * torch.sigmoid(inputs) + + +MODELS.register_module(module=SiLU, name='SiLU') diff --git a/mmdetection/mmdet/models/layers/bbox_nms.py b/mmdetection/mmdet/models/layers/bbox_nms.py new file mode 100644 index 00000000..fd67a45f --- /dev/null +++ b/mmdetection/mmdet/models/layers/bbox_nms.py @@ -0,0 +1,184 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, Union + +import torch +from mmcv.ops.nms import batched_nms +from torch import Tensor + +from mmdet.structures.bbox import bbox_overlaps +from mmdet.utils import ConfigType + + +def multiclass_nms( + multi_bboxes: Tensor, + multi_scores: Tensor, + score_thr: float, + nms_cfg: ConfigType, + max_num: int = -1, + score_factors: Optional[Tensor] = None, + return_inds: bool = False, + box_dim: int = 4 +) -> Union[Tuple[Tensor, Tensor, Tensor], Tuple[Tensor, Tensor]]: + """NMS for multi-class bboxes. + + Args: + multi_bboxes (Tensor): shape (n, #class*4) or (n, 4) + multi_scores (Tensor): shape (n, #class), where the last column + contains scores of the background class, but this will be ignored. + score_thr (float): bbox threshold, bboxes with scores lower than it + will not be considered. + nms_cfg (Union[:obj:`ConfigDict`, dict]): a dict that contains + the arguments of nms operations. + max_num (int, optional): if there are more than max_num bboxes after + NMS, only top max_num will be kept. Default to -1. + score_factors (Tensor, optional): The factors multiplied to scores + before applying NMS. Default to None. + return_inds (bool, optional): Whether return the indices of kept + bboxes. Default to False. + box_dim (int): The dimension of boxes. Defaults to 4. + + Returns: + Union[Tuple[Tensor, Tensor, Tensor], Tuple[Tensor, Tensor]]: + (dets, labels, indices (optional)), tensors of shape (k, 5), + (k), and (k). Dets are boxes with scores. Labels are 0-based. + """ + num_classes = multi_scores.size(1) - 1 + # exclude background category + if multi_bboxes.shape[1] > box_dim: + bboxes = multi_bboxes.view(multi_scores.size(0), -1, box_dim) + else: + bboxes = multi_bboxes[:, None].expand( + multi_scores.size(0), num_classes, box_dim) + + scores = multi_scores[:, :-1] + + labels = torch.arange(num_classes, dtype=torch.long, device=scores.device) + labels = labels.view(1, -1).expand_as(scores) + + bboxes = bboxes.reshape(-1, box_dim) + scores = scores.reshape(-1) + labels = labels.reshape(-1) + + if not torch.onnx.is_in_onnx_export(): + # NonZero not supported in TensorRT + # remove low scoring boxes + valid_mask = scores > score_thr + # multiply score_factor after threshold to preserve more bboxes, improve + # mAP by 1% for YOLOv3 + if score_factors is not None: + # expand the shape to match original shape of score + score_factors = score_factors.view(-1, 1).expand( + multi_scores.size(0), num_classes) + score_factors = score_factors.reshape(-1) + scores = scores * score_factors + + if not torch.onnx.is_in_onnx_export(): + # NonZero not supported in TensorRT + inds = valid_mask.nonzero(as_tuple=False).squeeze(1) + bboxes, scores, labels = bboxes[inds], scores[inds], labels[inds] + else: + # TensorRT NMS plugin has invalid output filled with -1 + # add dummy data to make detection output correct. + bboxes = torch.cat([bboxes, bboxes.new_zeros(1, box_dim)], dim=0) + scores = torch.cat([scores, scores.new_zeros(1)], dim=0) + labels = torch.cat([labels, labels.new_zeros(1)], dim=0) + + if bboxes.numel() == 0: + if torch.onnx.is_in_onnx_export(): + raise RuntimeError('[ONNX Error] Can not record NMS ' + 'as it has not been executed this time') + dets = torch.cat([bboxes, scores[:, None]], -1) + if return_inds: + return dets, labels, inds + else: + return dets, labels + + dets, keep = batched_nms(bboxes, scores, labels, nms_cfg) + + if max_num > 0: + dets = dets[:max_num] + keep = keep[:max_num] + + if return_inds: + return dets, labels[keep], inds[keep] + else: + return dets, labels[keep] + + +def fast_nms( + multi_bboxes: Tensor, + multi_scores: Tensor, + multi_coeffs: Tensor, + score_thr: float, + iou_thr: float, + top_k: int, + max_num: int = -1 +) -> Union[Tuple[Tensor, Tensor, Tensor], Tuple[Tensor, Tensor]]: + """Fast NMS in `YOLACT `_. + + Fast NMS allows already-removed detections to suppress other detections so + that every instance can be decided to be kept or discarded in parallel, + which is not possible in traditional NMS. This relaxation allows us to + implement Fast NMS entirely in standard GPU-accelerated matrix operations. + + Args: + multi_bboxes (Tensor): shape (n, #class*4) or (n, 4) + multi_scores (Tensor): shape (n, #class+1), where the last column + contains scores of the background class, but this will be ignored. + multi_coeffs (Tensor): shape (n, #class*coeffs_dim). + score_thr (float): bbox threshold, bboxes with scores lower than it + will not be considered. + iou_thr (float): IoU threshold to be considered as conflicted. + top_k (int): if there are more than top_k bboxes before NMS, + only top top_k will be kept. + max_num (int): if there are more than max_num bboxes after NMS, + only top max_num will be kept. If -1, keep all the bboxes. + Default: -1. + + Returns: + Union[Tuple[Tensor, Tensor, Tensor], Tuple[Tensor, Tensor]]: + (dets, labels, coefficients), tensors of shape (k, 5), (k, 1), + and (k, coeffs_dim). Dets are boxes with scores. + Labels are 0-based. + """ + + scores = multi_scores[:, :-1].t() # [#class, n] + scores, idx = scores.sort(1, descending=True) + + idx = idx[:, :top_k].contiguous() + scores = scores[:, :top_k] # [#class, topk] + num_classes, num_dets = idx.size() + boxes = multi_bboxes[idx.view(-1), :].view(num_classes, num_dets, 4) + coeffs = multi_coeffs[idx.view(-1), :].view(num_classes, num_dets, -1) + + iou = bbox_overlaps(boxes, boxes) # [#class, topk, topk] + iou.triu_(diagonal=1) + iou_max, _ = iou.max(dim=1) + + # Now just filter out the ones higher than the threshold + keep = iou_max <= iou_thr + + # Second thresholding introduces 0.2 mAP gain at negligible time cost + keep *= scores > score_thr + + # Assign each kept detection to its corresponding class + classes = torch.arange( + num_classes, device=boxes.device)[:, None].expand_as(keep) + classes = classes[keep] + + boxes = boxes[keep] + coeffs = coeffs[keep] + scores = scores[keep] + + # Only keep the top max_num highest scores across all classes + scores, idx = scores.sort(0, descending=True) + if max_num > 0: + idx = idx[:max_num] + scores = scores[:max_num] + + classes = classes[idx] + boxes = boxes[idx] + coeffs = coeffs[idx] + + cls_dets = torch.cat([boxes, scores[:, None]], dim=1) + return cls_dets, classes, coeffs diff --git a/mmdetection/mmdet/models/layers/brick_wrappers.py b/mmdetection/mmdet/models/layers/brick_wrappers.py new file mode 100644 index 00000000..5ecb8499 --- /dev/null +++ b/mmdetection/mmdet/models/layers/brick_wrappers.py @@ -0,0 +1,138 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn.bricks.wrappers import NewEmptyTensorOp, obsolete_torch_version + +from mmdet.registry import MODELS + +if torch.__version__ == 'parrots': + TORCH_VERSION = torch.__version__ +else: + # torch.__version__ could be 1.3.1+cu92, we only need the first two + # for comparison + TORCH_VERSION = tuple(int(x) for x in torch.__version__.split('.')[:2]) + + +def adaptive_avg_pool2d(input, output_size): + """Handle empty batch dimension to adaptive_avg_pool2d. + + Args: + input (tensor): 4D tensor. + output_size (int, tuple[int,int]): the target output size. + """ + if input.numel() == 0 and obsolete_torch_version(TORCH_VERSION, (1, 9)): + if isinstance(output_size, int): + output_size = [output_size, output_size] + output_size = [*input.shape[:2], *output_size] + empty = NewEmptyTensorOp.apply(input, output_size) + return empty + else: + return F.adaptive_avg_pool2d(input, output_size) + + +class AdaptiveAvgPool2d(nn.AdaptiveAvgPool2d): + """Handle empty batch dimension to AdaptiveAvgPool2d.""" + + def forward(self, x): + # PyTorch 1.9 does not support empty tensor inference yet + if x.numel() == 0 and obsolete_torch_version(TORCH_VERSION, (1, 9)): + output_size = self.output_size + if isinstance(output_size, int): + output_size = [output_size, output_size] + else: + output_size = [ + v if v is not None else d + for v, d in zip(output_size, + x.size()[-2:]) + ] + output_size = [*x.shape[:2], *output_size] + empty = NewEmptyTensorOp.apply(x, output_size) + return empty + + return super().forward(x) + + +# Modified from +# https://github.com/facebookresearch/detectron2/blob/main/detectron2/layers/batch_norm.py#L13 # noqa +@MODELS.register_module('FrozenBN') +class FrozenBatchNorm2d(nn.Module): + """BatchNorm2d where the batch statistics and the affine parameters are + fixed. + + It contains non-trainable buffers called + "weight" and "bias", "running_mean", "running_var", + initialized to perform identity transformation. + Args: + num_features (int): :math:`C` from an expected input of size + :math:`(N, C, H, W)`. + eps (float): a value added to the denominator for numerical stability. + Default: 1e-5 + """ + + def __init__(self, num_features, eps=1e-5, **kwargs): + super().__init__() + self.num_features = num_features + self.eps = eps + self.register_buffer('weight', torch.ones(num_features)) + self.register_buffer('bias', torch.zeros(num_features)) + self.register_buffer('running_mean', torch.zeros(num_features)) + self.register_buffer('running_var', torch.ones(num_features) - eps) + + def forward(self, x): + if x.requires_grad: + # When gradients are needed, F.batch_norm will use extra memory + # because its backward op computes gradients for weight/bias + # as well. + scale = self.weight * (self.running_var + self.eps).rsqrt() + bias = self.bias - self.running_mean * scale + scale = scale.reshape(1, -1, 1, 1) + bias = bias.reshape(1, -1, 1, 1) + out_dtype = x.dtype # may be half + return x * scale.to(out_dtype) + bias.to(out_dtype) + else: + # When gradients are not needed, F.batch_norm is a single fused op + # and provide more optimization opportunities. + return F.batch_norm( + x, + self.running_mean, + self.running_var, + self.weight, + self.bias, + training=False, + eps=self.eps, + ) + + def __repr__(self): + return 'FrozenBatchNorm2d(num_features={}, eps={})'.format( + self.num_features, self.eps) + + @classmethod + def convert_frozen_batchnorm(cls, module): + """Convert all BatchNorm/SyncBatchNorm in module into FrozenBatchNorm. + + Args: + module (torch.nn.Module): + Returns: + If module is BatchNorm/SyncBatchNorm, returns a new module. + Otherwise, in-place convert module and return it. + Similar to convert_sync_batchnorm in + https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/batchnorm.py + """ + bn_module = nn.modules.batchnorm + bn_module = (bn_module.BatchNorm2d, bn_module.SyncBatchNorm) + res = module + if isinstance(module, bn_module): + res = cls(module.num_features) + if module.affine: + res.weight.data = module.weight.data.clone().detach() + res.bias.data = module.bias.data.clone().detach() + res.running_mean.data = module.running_mean.data + res.running_var.data = module.running_var.data + res.eps = module.eps + else: + for name, child in module.named_children(): + new_child = cls.convert_frozen_batchnorm(child) + if new_child is not child: + res.add_module(name, new_child) + return res diff --git a/mmdetection/mmdet/models/layers/conv_upsample.py b/mmdetection/mmdet/models/layers/conv_upsample.py new file mode 100644 index 00000000..32505875 --- /dev/null +++ b/mmdetection/mmdet/models/layers/conv_upsample.py @@ -0,0 +1,67 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule, ModuleList + + +class ConvUpsample(BaseModule): + """ConvUpsample performs 2x upsampling after Conv. + + There are several `ConvModule` layers. In the first few layers, upsampling + will be applied after each layer of convolution. The number of upsampling + must be no more than the number of ConvModule layers. + + Args: + in_channels (int): Number of channels in the input feature map. + inner_channels (int): Number of channels produced by the convolution. + num_layers (int): Number of convolution layers. + num_upsample (int | optional): Number of upsampling layer. Must be no + more than num_layers. Upsampling will be applied after the first + ``num_upsample`` layers of convolution. Default: ``num_layers``. + conv_cfg (dict): Config dict for convolution layer. Default: None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. Default: None. + init_cfg (dict): Config dict for initialization. Default: None. + kwargs (key word augments): Other augments used in ConvModule. + """ + + def __init__(self, + in_channels, + inner_channels, + num_layers=1, + num_upsample=None, + conv_cfg=None, + norm_cfg=None, + init_cfg=None, + **kwargs): + super(ConvUpsample, self).__init__(init_cfg) + if num_upsample is None: + num_upsample = num_layers + assert num_upsample <= num_layers, \ + f'num_upsample({num_upsample})must be no more than ' \ + f'num_layers({num_layers})' + self.num_layers = num_layers + self.num_upsample = num_upsample + self.conv = ModuleList() + for i in range(num_layers): + self.conv.append( + ConvModule( + in_channels, + inner_channels, + 3, + padding=1, + stride=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + **kwargs)) + in_channels = inner_channels + + def forward(self, x): + num_upsample = self.num_upsample + for i in range(self.num_layers): + x = self.conv[i](x) + if num_upsample > 0: + num_upsample -= 1 + x = F.interpolate( + x, scale_factor=2, mode='bilinear', align_corners=False) + return x diff --git a/mmdetection/mmdet/models/layers/csp_layer.py b/mmdetection/mmdet/models/layers/csp_layer.py new file mode 100644 index 00000000..c8b547b8 --- /dev/null +++ b/mmdetection/mmdet/models/layers/csp_layer.py @@ -0,0 +1,246 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from .se_layer import ChannelAttention + + +class DarknetBottleneck(BaseModule): + """The basic bottleneck block used in Darknet. + + Each ResBlock consists of two ConvModules and the input is added to the + final output. Each ConvModule is composed of Conv, BN, and LeakyReLU. + The first convLayer has filter size of 1x1 and the second one has the + filter size of 3x3. + + Args: + in_channels (int): The input channels of this Module. + out_channels (int): The output channels of this Module. + expansion (float): The kernel size of the convolution. + Defaults to 0.5. + add_identity (bool): Whether to add identity to the out. + Defaults to True. + use_depthwise (bool): Whether to use depthwise separable convolution. + Defaults to False. + conv_cfg (dict): Config dict for convolution layer. Defaults to None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Defaults to dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Defaults to dict(type='Swish'). + """ + + def __init__(self, + in_channels: int, + out_channels: int, + expansion: float = 0.5, + add_identity: bool = True, + use_depthwise: bool = False, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict( + type='BN', momentum=0.03, eps=0.001), + act_cfg: ConfigType = dict(type='Swish'), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + hidden_channels = int(out_channels * expansion) + conv = DepthwiseSeparableConvModule if use_depthwise else ConvModule + self.conv1 = ConvModule( + in_channels, + hidden_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.conv2 = conv( + hidden_channels, + out_channels, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.add_identity = \ + add_identity and in_channels == out_channels + + def forward(self, x: Tensor) -> Tensor: + """Forward function.""" + identity = x + out = self.conv1(x) + out = self.conv2(out) + + if self.add_identity: + return out + identity + else: + return out + + +class CSPNeXtBlock(BaseModule): + """The basic bottleneck block used in CSPNeXt. + + Args: + in_channels (int): The input channels of this Module. + out_channels (int): The output channels of this Module. + expansion (float): Expand ratio of the hidden channel. Defaults to 0.5. + add_identity (bool): Whether to add identity to the out. Only works + when in_channels == out_channels. Defaults to True. + use_depthwise (bool): Whether to use depthwise separable convolution. + Defaults to False. + kernel_size (int): The kernel size of the second convolution layer. + Defaults to 5. + conv_cfg (dict): Config dict for convolution layer. Defaults to None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Defaults to dict(type='BN', momentum=0.03, eps=0.001). + act_cfg (dict): Config dict for activation layer. + Defaults to dict(type='SiLU'). + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + in_channels: int, + out_channels: int, + expansion: float = 0.5, + add_identity: bool = True, + use_depthwise: bool = False, + kernel_size: int = 5, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict( + type='BN', momentum=0.03, eps=0.001), + act_cfg: ConfigType = dict(type='SiLU'), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + hidden_channels = int(out_channels * expansion) + conv = DepthwiseSeparableConvModule if use_depthwise else ConvModule + self.conv1 = conv( + in_channels, + hidden_channels, + 3, + stride=1, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.conv2 = DepthwiseSeparableConvModule( + hidden_channels, + out_channels, + kernel_size, + stride=1, + padding=kernel_size // 2, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.add_identity = \ + add_identity and in_channels == out_channels + + def forward(self, x: Tensor) -> Tensor: + """Forward function.""" + identity = x + out = self.conv1(x) + out = self.conv2(out) + + if self.add_identity: + return out + identity + else: + return out + + +class CSPLayer(BaseModule): + """Cross Stage Partial Layer. + + Args: + in_channels (int): The input channels of the CSP layer. + out_channels (int): The output channels of the CSP layer. + expand_ratio (float): Ratio to adjust the number of channels of the + hidden layer. Defaults to 0.5. + num_blocks (int): Number of blocks. Defaults to 1. + add_identity (bool): Whether to add identity in blocks. + Defaults to True. + use_cspnext_block (bool): Whether to use CSPNeXt block. + Defaults to False. + use_depthwise (bool): Whether to use depthwise separable convolution in + blocks. Defaults to False. + channel_attention (bool): Whether to add channel attention in each + stage. Defaults to True. + conv_cfg (dict, optional): Config dict for convolution layer. + Defaults to None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Defaults to dict(type='BN') + act_cfg (dict): Config dict for activation layer. + Defaults to dict(type='Swish') + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + in_channels: int, + out_channels: int, + expand_ratio: float = 0.5, + num_blocks: int = 1, + add_identity: bool = True, + use_depthwise: bool = False, + use_cspnext_block: bool = False, + channel_attention: bool = False, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict( + type='BN', momentum=0.03, eps=0.001), + act_cfg: ConfigType = dict(type='Swish'), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + block = CSPNeXtBlock if use_cspnext_block else DarknetBottleneck + mid_channels = int(out_channels * expand_ratio) + self.channel_attention = channel_attention + self.main_conv = ConvModule( + in_channels, + mid_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.short_conv = ConvModule( + in_channels, + mid_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.final_conv = ConvModule( + 2 * mid_channels, + out_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + self.blocks = nn.Sequential(*[ + block( + mid_channels, + mid_channels, + 1.0, + add_identity, + use_depthwise, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) for _ in range(num_blocks) + ]) + if channel_attention: + self.attention = ChannelAttention(2 * mid_channels) + + def forward(self, x: Tensor) -> Tensor: + """Forward function.""" + x_short = self.short_conv(x) + + x_main = self.main_conv(x) + x_main = self.blocks(x_main) + + x_final = torch.cat((x_main, x_short), dim=1) + + if self.channel_attention: + x_final = self.attention(x_final) + return self.final_conv(x_final) diff --git a/mmdetection/mmdet/models/layers/dropblock.py b/mmdetection/mmdet/models/layers/dropblock.py new file mode 100644 index 00000000..7938199b --- /dev/null +++ b/mmdetection/mmdet/models/layers/dropblock.py @@ -0,0 +1,86 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from mmdet.registry import MODELS + +eps = 1e-6 + + +@MODELS.register_module() +class DropBlock(nn.Module): + """Randomly drop some regions of feature maps. + + Please refer to the method proposed in `DropBlock + `_ for details. + + Args: + drop_prob (float): The probability of dropping each block. + block_size (int): The size of dropped blocks. + warmup_iters (int): The drop probability will linearly increase + from `0` to `drop_prob` during the first `warmup_iters` iterations. + Default: 2000. + """ + + def __init__(self, drop_prob, block_size, warmup_iters=2000, **kwargs): + super(DropBlock, self).__init__() + assert block_size % 2 == 1 + assert 0 < drop_prob <= 1 + assert warmup_iters >= 0 + self.drop_prob = drop_prob + self.block_size = block_size + self.warmup_iters = warmup_iters + self.iter_cnt = 0 + + def forward(self, x): + """ + Args: + x (Tensor): Input feature map on which some areas will be randomly + dropped. + + Returns: + Tensor: The tensor after DropBlock layer. + """ + if not self.training: + return x + self.iter_cnt += 1 + N, C, H, W = list(x.shape) + gamma = self._compute_gamma((H, W)) + mask_shape = (N, C, H - self.block_size + 1, W - self.block_size + 1) + mask = torch.bernoulli(torch.full(mask_shape, gamma, device=x.device)) + + mask = F.pad(mask, [self.block_size // 2] * 4, value=0) + mask = F.max_pool2d( + input=mask, + stride=(1, 1), + kernel_size=(self.block_size, self.block_size), + padding=self.block_size // 2) + mask = 1 - mask + x = x * mask * mask.numel() / (eps + mask.sum()) + return x + + def _compute_gamma(self, feat_size): + """Compute the value of gamma according to paper. gamma is the + parameter of bernoulli distribution, which controls the number of + features to drop. + + gamma = (drop_prob * fm_area) / (drop_area * keep_area) + + Args: + feat_size (tuple[int, int]): The height and width of feature map. + + Returns: + float: The value of gamma. + """ + gamma = (self.drop_prob * feat_size[0] * feat_size[1]) + gamma /= ((feat_size[0] - self.block_size + 1) * + (feat_size[1] - self.block_size + 1)) + gamma /= (self.block_size**2) + factor = (1.0 if self.iter_cnt > self.warmup_iters else self.iter_cnt / + self.warmup_iters) + return gamma * factor + + def extra_repr(self): + return (f'drop_prob={self.drop_prob}, block_size={self.block_size}, ' + f'warmup_iters={self.warmup_iters}') diff --git a/mmdetection/mmdet/models/layers/ema.py b/mmdetection/mmdet/models/layers/ema.py new file mode 100644 index 00000000..73a0ca67 --- /dev/null +++ b/mmdetection/mmdet/models/layers/ema.py @@ -0,0 +1,66 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Optional + +import torch +import torch.nn as nn +from mmengine.model import ExponentialMovingAverage +from torch import Tensor + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class ExpMomentumEMA(ExponentialMovingAverage): + """Exponential moving average (EMA) with exponential momentum strategy, + which is used in YOLOX. + + Args: + model (nn.Module): The model to be averaged. + momentum (float): The momentum used for updating ema parameter. + Ema's parameter are updated with the formula: + `averaged_param = (1-momentum) * averaged_param + momentum * + source_param`. Defaults to 0.0002. + gamma (int): Use a larger momentum early in training and gradually + annealing to a smaller value to update the ema model smoothly. The + momentum is calculated as + `(1 - momentum) * exp(-(1 + steps) / gamma) + momentum`. + Defaults to 2000. + interval (int): Interval between two updates. Defaults to 1. + device (torch.device, optional): If provided, the averaged model will + be stored on the :attr:`device`. Defaults to None. + update_buffers (bool): if True, it will compute running averages for + both the parameters and the buffers of the model. Defaults to + False. + """ + + def __init__(self, + model: nn.Module, + momentum: float = 0.0002, + gamma: int = 2000, + interval=1, + device: Optional[torch.device] = None, + update_buffers: bool = False) -> None: + super().__init__( + model=model, + momentum=momentum, + interval=interval, + device=device, + update_buffers=update_buffers) + assert gamma > 0, f'gamma must be greater than 0, but got {gamma}' + self.gamma = gamma + + def avg_func(self, averaged_param: Tensor, source_param: Tensor, + steps: int) -> None: + """Compute the moving average of the parameters using the exponential + momentum strategy. + + Args: + averaged_param (Tensor): The averaged parameters. + source_param (Tensor): The source parameters. + steps (int): The number of times the parameters have been + updated. + """ + momentum = (1 - self.momentum) * math.exp( + -float(1 + steps) / self.gamma) + self.momentum + averaged_param.lerp_(source_param, momentum) diff --git a/mmdetection/mmdet/models/layers/inverted_residual.py b/mmdetection/mmdet/models/layers/inverted_residual.py new file mode 100644 index 00000000..a174ccc8 --- /dev/null +++ b/mmdetection/mmdet/models/layers/inverted_residual.py @@ -0,0 +1,130 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn import ConvModule +from mmcv.cnn.bricks import DropPath +from mmengine.model import BaseModule + +from .se_layer import SELayer + + +class InvertedResidual(BaseModule): + """Inverted Residual Block. + + Args: + in_channels (int): The input channels of this Module. + out_channels (int): The output channels of this Module. + mid_channels (int): The input channels of the depthwise convolution. + kernel_size (int): The kernel size of the depthwise convolution. + Default: 3. + stride (int): The stride of the depthwise convolution. Default: 1. + se_cfg (dict): Config dict for se layer. Default: None, which means no + se layer. + with_expand_conv (bool): Use expand conv or not. If set False, + mid_channels must be the same with in_channels. + Default: True. + conv_cfg (dict): Config dict for convolution layer. Default: None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU'). + drop_path_rate (float): stochastic depth rate. Defaults to 0. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + + Returns: + Tensor: The output tensor. + """ + + def __init__(self, + in_channels, + out_channels, + mid_channels, + kernel_size=3, + stride=1, + se_cfg=None, + with_expand_conv=True, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + drop_path_rate=0., + with_cp=False, + init_cfg=None): + super(InvertedResidual, self).__init__(init_cfg) + self.with_res_shortcut = (stride == 1 and in_channels == out_channels) + assert stride in [1, 2], f'stride must in [1, 2]. ' \ + f'But received {stride}.' + self.with_cp = with_cp + self.drop_path = DropPath( + drop_path_rate) if drop_path_rate > 0 else nn.Identity() + self.with_se = se_cfg is not None + self.with_expand_conv = with_expand_conv + + if self.with_se: + assert isinstance(se_cfg, dict) + if not self.with_expand_conv: + assert mid_channels == in_channels + + if self.with_expand_conv: + self.expand_conv = ConvModule( + in_channels=in_channels, + out_channels=mid_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.depthwise_conv = ConvModule( + in_channels=mid_channels, + out_channels=mid_channels, + kernel_size=kernel_size, + stride=stride, + padding=kernel_size // 2, + groups=mid_channels, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + if self.with_se: + self.se = SELayer(**se_cfg) + + self.linear_conv = ConvModule( + in_channels=mid_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + + def forward(self, x): + + def _inner_forward(x): + out = x + + if self.with_expand_conv: + out = self.expand_conv(out) + + out = self.depthwise_conv(out) + + if self.with_se: + out = self.se(out) + + out = self.linear_conv(out) + + if self.with_res_shortcut: + return x + self.drop_path(out) + else: + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + return out diff --git a/mmdetection/mmdet/models/layers/matrix_nms.py b/mmdetection/mmdet/models/layers/matrix_nms.py new file mode 100644 index 00000000..9dc8c4f7 --- /dev/null +++ b/mmdetection/mmdet/models/layers/matrix_nms.py @@ -0,0 +1,121 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + + +def mask_matrix_nms(masks, + labels, + scores, + filter_thr=-1, + nms_pre=-1, + max_num=-1, + kernel='gaussian', + sigma=2.0, + mask_area=None): + """Matrix NMS for multi-class masks. + + Args: + masks (Tensor): Has shape (num_instances, h, w) + labels (Tensor): Labels of corresponding masks, + has shape (num_instances,). + scores (Tensor): Mask scores of corresponding masks, + has shape (num_instances). + filter_thr (float): Score threshold to filter the masks + after matrix nms. Default: -1, which means do not + use filter_thr. + nms_pre (int): The max number of instances to do the matrix nms. + Default: -1, which means do not use nms_pre. + max_num (int, optional): If there are more than max_num masks after + matrix, only top max_num will be kept. Default: -1, which means + do not use max_num. + kernel (str): 'linear' or 'gaussian'. + sigma (float): std in gaussian method. + mask_area (Tensor): The sum of seg_masks. + + Returns: + tuple(Tensor): Processed mask results. + + - scores (Tensor): Updated scores, has shape (n,). + - labels (Tensor): Remained labels, has shape (n,). + - masks (Tensor): Remained masks, has shape (n, w, h). + - keep_inds (Tensor): The indices number of + the remaining mask in the input mask, has shape (n,). + """ + assert len(labels) == len(masks) == len(scores) + if len(labels) == 0: + return scores.new_zeros(0), labels.new_zeros(0), masks.new_zeros( + 0, *masks.shape[-2:]), labels.new_zeros(0) + if mask_area is None: + mask_area = masks.sum((1, 2)).float() + else: + assert len(masks) == len(mask_area) + + # sort and keep top nms_pre + scores, sort_inds = torch.sort(scores, descending=True) + + keep_inds = sort_inds + if nms_pre > 0 and len(sort_inds) > nms_pre: + sort_inds = sort_inds[:nms_pre] + keep_inds = keep_inds[:nms_pre] + scores = scores[:nms_pre] + masks = masks[sort_inds] + mask_area = mask_area[sort_inds] + labels = labels[sort_inds] + + num_masks = len(labels) + flatten_masks = masks.reshape(num_masks, -1).float() + # inter. + inter_matrix = torch.mm(flatten_masks, flatten_masks.transpose(1, 0)) + expanded_mask_area = mask_area.expand(num_masks, num_masks) + # Upper triangle iou matrix. + iou_matrix = (inter_matrix / + (expanded_mask_area + expanded_mask_area.transpose(1, 0) - + inter_matrix)).triu(diagonal=1) + # label_specific matrix. + expanded_labels = labels.expand(num_masks, num_masks) + # Upper triangle label matrix. + label_matrix = (expanded_labels == expanded_labels.transpose( + 1, 0)).triu(diagonal=1) + + # IoU compensation + compensate_iou, _ = (iou_matrix * label_matrix).max(0) + compensate_iou = compensate_iou.expand(num_masks, + num_masks).transpose(1, 0) + + # IoU decay + decay_iou = iou_matrix * label_matrix + + # Calculate the decay_coefficient + if kernel == 'gaussian': + decay_matrix = torch.exp(-1 * sigma * (decay_iou**2)) + compensate_matrix = torch.exp(-1 * sigma * (compensate_iou**2)) + decay_coefficient, _ = (decay_matrix / compensate_matrix).min(0) + elif kernel == 'linear': + decay_matrix = (1 - decay_iou) / (1 - compensate_iou) + decay_coefficient, _ = decay_matrix.min(0) + else: + raise NotImplementedError( + f'{kernel} kernel is not supported in matrix nms!') + # update the score. + scores = scores * decay_coefficient + + if filter_thr > 0: + keep = scores >= filter_thr + keep_inds = keep_inds[keep] + if not keep.any(): + return scores.new_zeros(0), labels.new_zeros(0), masks.new_zeros( + 0, *masks.shape[-2:]), labels.new_zeros(0) + masks = masks[keep] + scores = scores[keep] + labels = labels[keep] + + # sort and keep top max_num + scores, sort_inds = torch.sort(scores, descending=True) + keep_inds = keep_inds[sort_inds] + if max_num > 0 and len(sort_inds) > max_num: + sort_inds = sort_inds[:max_num] + keep_inds = keep_inds[:max_num] + scores = scores[:max_num] + masks = masks[sort_inds] + labels = labels[sort_inds] + + return scores, labels, masks, keep_inds diff --git a/mmdetection/mmdet/models/layers/msdeformattn_pixel_decoder.py b/mmdetection/mmdet/models/layers/msdeformattn_pixel_decoder.py new file mode 100644 index 00000000..a67dc3c4 --- /dev/null +++ b/mmdetection/mmdet/models/layers/msdeformattn_pixel_decoder.py @@ -0,0 +1,246 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Conv2d, ConvModule +from mmcv.cnn.bricks.transformer import MultiScaleDeformableAttention +from mmengine.model import (BaseModule, ModuleList, caffe2_xavier_init, + normal_init, xavier_init) +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptMultiConfig +from ..task_modules.prior_generators import MlvlPointGenerator +from .positional_encoding import SinePositionalEncoding +from .transformer import Mask2FormerTransformerEncoder + + +@MODELS.register_module() +class MSDeformAttnPixelDecoder(BaseModule): + """Pixel decoder with multi-scale deformable attention. + + Args: + in_channels (list[int] | tuple[int]): Number of channels in the + input feature maps. + strides (list[int] | tuple[int]): Output strides of feature from + backbone. + feat_channels (int): Number of channels for feature. + out_channels (int): Number of channels for output. + num_outs (int): Number of output scales. + norm_cfg (:obj:`ConfigDict` or dict): Config for normalization. + Defaults to dict(type='GN', num_groups=32). + act_cfg (:obj:`ConfigDict` or dict): Config for activation. + Defaults to dict(type='ReLU'). + encoder (:obj:`ConfigDict` or dict): Config for transformer + encoder. Defaults to None. + positional_encoding (:obj:`ConfigDict` or dict): Config for + transformer encoder position encoding. Defaults to + dict(num_feats=128, normalize=True). + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. Defaults to None. + """ + + def __init__(self, + in_channels: Union[List[int], + Tuple[int]] = [256, 512, 1024, 2048], + strides: Union[List[int], Tuple[int]] = [4, 8, 16, 32], + feat_channels: int = 256, + out_channels: int = 256, + num_outs: int = 3, + norm_cfg: ConfigType = dict(type='GN', num_groups=32), + act_cfg: ConfigType = dict(type='ReLU'), + encoder: ConfigType = None, + positional_encoding: ConfigType = dict( + num_feats=128, normalize=True), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + self.strides = strides + self.num_input_levels = len(in_channels) + self.num_encoder_levels = \ + encoder.layer_cfg.self_attn_cfg.num_levels + assert self.num_encoder_levels >= 1, \ + 'num_levels in attn_cfgs must be at least one' + input_conv_list = [] + # from top to down (low to high resolution) + for i in range(self.num_input_levels - 1, + self.num_input_levels - self.num_encoder_levels - 1, + -1): + input_conv = ConvModule( + in_channels[i], + feat_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=None, + bias=True) + input_conv_list.append(input_conv) + self.input_convs = ModuleList(input_conv_list) + + self.encoder = Mask2FormerTransformerEncoder(**encoder) + self.postional_encoding = SinePositionalEncoding(**positional_encoding) + # high resolution to low resolution + self.level_encoding = nn.Embedding(self.num_encoder_levels, + feat_channels) + + # fpn-like structure + self.lateral_convs = ModuleList() + self.output_convs = ModuleList() + self.use_bias = norm_cfg is None + # from top to down (low to high resolution) + # fpn for the rest features that didn't pass in encoder + for i in range(self.num_input_levels - self.num_encoder_levels - 1, -1, + -1): + lateral_conv = ConvModule( + in_channels[i], + feat_channels, + kernel_size=1, + bias=self.use_bias, + norm_cfg=norm_cfg, + act_cfg=None) + output_conv = ConvModule( + feat_channels, + feat_channels, + kernel_size=3, + stride=1, + padding=1, + bias=self.use_bias, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.lateral_convs.append(lateral_conv) + self.output_convs.append(output_conv) + + self.mask_feature = Conv2d( + feat_channels, out_channels, kernel_size=1, stride=1, padding=0) + + self.num_outs = num_outs + self.point_generator = MlvlPointGenerator(strides) + + def init_weights(self) -> None: + """Initialize weights.""" + for i in range(0, self.num_encoder_levels): + xavier_init( + self.input_convs[i].conv, + gain=1, + bias=0, + distribution='uniform') + + for i in range(0, self.num_input_levels - self.num_encoder_levels): + caffe2_xavier_init(self.lateral_convs[i].conv, bias=0) + caffe2_xavier_init(self.output_convs[i].conv, bias=0) + + caffe2_xavier_init(self.mask_feature, bias=0) + + normal_init(self.level_encoding, mean=0, std=1) + for p in self.encoder.parameters(): + if p.dim() > 1: + nn.init.xavier_normal_(p) + + # init_weights defined in MultiScaleDeformableAttention + for m in self.encoder.layers.modules(): + if isinstance(m, MultiScaleDeformableAttention): + m.init_weights() + + def forward(self, feats: List[Tensor]) -> Tuple[Tensor, Tensor]: + """ + Args: + feats (list[Tensor]): Feature maps of each level. Each has + shape of (batch_size, c, h, w). + + Returns: + tuple: A tuple containing the following: + + - mask_feature (Tensor): shape (batch_size, c, h, w). + - multi_scale_features (list[Tensor]): Multi scale \ + features, each in shape (batch_size, c, h, w). + """ + # generate padding mask for each level, for each image + batch_size = feats[0].shape[0] + encoder_input_list = [] + padding_mask_list = [] + level_positional_encoding_list = [] + spatial_shapes = [] + reference_points_list = [] + for i in range(self.num_encoder_levels): + level_idx = self.num_input_levels - i - 1 + feat = feats[level_idx] + feat_projected = self.input_convs[i](feat) + feat_hw = torch._shape_as_tensor(feat)[2:].to(feat.device) + + # no padding + padding_mask_resized = feat.new_zeros( + (batch_size, ) + feat.shape[-2:], dtype=torch.bool) + pos_embed = self.postional_encoding(padding_mask_resized) + level_embed = self.level_encoding.weight[i] + level_pos_embed = level_embed.view(1, -1, 1, 1) + pos_embed + # (h_i * w_i, 2) + reference_points = self.point_generator.single_level_grid_priors( + feat.shape[-2:], level_idx, device=feat.device) + # normalize + feat_wh = feat_hw.unsqueeze(0).flip(dims=[0, 1]) + factor = feat_wh * self.strides[level_idx] + reference_points = reference_points / factor + + # shape (batch_size, c, h_i, w_i) -> (h_i * w_i, batch_size, c) + feat_projected = feat_projected.flatten(2).permute(0, 2, 1) + level_pos_embed = level_pos_embed.flatten(2).permute(0, 2, 1) + padding_mask_resized = padding_mask_resized.flatten(1) + + encoder_input_list.append(feat_projected) + padding_mask_list.append(padding_mask_resized) + level_positional_encoding_list.append(level_pos_embed) + spatial_shapes.append(feat_hw) + reference_points_list.append(reference_points) + # shape (batch_size, total_num_queries), + # total_num_queries=sum([., h_i * w_i,.]) + padding_masks = torch.cat(padding_mask_list, dim=1) + # shape (total_num_queries, batch_size, c) + encoder_inputs = torch.cat(encoder_input_list, dim=1) + level_positional_encodings = torch.cat( + level_positional_encoding_list, dim=1) + # shape (num_encoder_levels, 2), from low + # resolution to high resolution + num_queries_per_level = [e[0] * e[1] for e in spatial_shapes] + spatial_shapes = torch.cat(spatial_shapes).view(-1, 2) + # shape (0, h_0*w_0, h_0*w_0+h_1*w_1, ...) + level_start_index = torch.cat((spatial_shapes.new_zeros( + (1, )), spatial_shapes.prod(1).cumsum(0)[:-1])) + reference_points = torch.cat(reference_points_list, dim=0) + reference_points = reference_points[None, :, None].repeat( + batch_size, 1, self.num_encoder_levels, 1) + valid_radios = reference_points.new_ones( + (batch_size, self.num_encoder_levels, 2)) + # shape (num_total_queries, batch_size, c) + memory = self.encoder( + query=encoder_inputs, + query_pos=level_positional_encodings, + key_padding_mask=padding_masks, + spatial_shapes=spatial_shapes, + reference_points=reference_points, + level_start_index=level_start_index, + valid_ratios=valid_radios) + # (batch_size, c, num_total_queries) + memory = memory.permute(0, 2, 1) + + # from low resolution to high resolution + outs = torch.split(memory, num_queries_per_level, dim=-1) + outs = [ + x.reshape(batch_size, -1, spatial_shapes[i][0], + spatial_shapes[i][1]) for i, x in enumerate(outs) + ] + + for i in range(self.num_input_levels - self.num_encoder_levels - 1, -1, + -1): + x = feats[i] + cur_feat = self.lateral_convs[i](x) + y = cur_feat + F.interpolate( + outs[-1], + size=cur_feat.shape[-2:], + mode='bilinear', + align_corners=False) + y = self.output_convs[i](y) + outs.append(y) + multi_scale_features = outs[:self.num_outs] + + mask_feature = self.mask_feature(outs[-1]) + return mask_feature, multi_scale_features diff --git a/mmdetection/mmdet/models/layers/normed_predictor.py b/mmdetection/mmdet/models/layers/normed_predictor.py new file mode 100644 index 00000000..592194b1 --- /dev/null +++ b/mmdetection/mmdet/models/layers/normed_predictor.py @@ -0,0 +1,99 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.utils import digit_version +from torch import Tensor + +from mmdet.registry import MODELS + +MODELS.register_module('Linear', module=nn.Linear) + + +@MODELS.register_module(name='NormedLinear') +class NormedLinear(nn.Linear): + """Normalized Linear Layer. + + Args: + tempeature (float, optional): Tempeature term. Defaults to 20. + power (int, optional): Power term. Defaults to 1.0. + eps (float, optional): The minimal value of divisor to + keep numerical stability. Defaults to 1e-6. + """ + + def __init__(self, + *args, + tempearture: float = 20, + power: int = 1.0, + eps: float = 1e-6, + **kwargs) -> None: + super().__init__(*args, **kwargs) + self.tempearture = tempearture + self.power = power + self.eps = eps + self.init_weights() + + def init_weights(self) -> None: + """Initialize the weights.""" + nn.init.normal_(self.weight, mean=0, std=0.01) + if self.bias is not None: + nn.init.constant_(self.bias, 0) + + def forward(self, x: Tensor) -> Tensor: + """Forward function for `NormedLinear`.""" + weight_ = self.weight / ( + self.weight.norm(dim=1, keepdim=True).pow(self.power) + self.eps) + x_ = x / (x.norm(dim=1, keepdim=True).pow(self.power) + self.eps) + x_ = x_ * self.tempearture + + return F.linear(x_, weight_, self.bias) + + +@MODELS.register_module(name='NormedConv2d') +class NormedConv2d(nn.Conv2d): + """Normalized Conv2d Layer. + + Args: + tempeature (float, optional): Tempeature term. Defaults to 20. + power (int, optional): Power term. Defaults to 1.0. + eps (float, optional): The minimal value of divisor to + keep numerical stability. Defaults to 1e-6. + norm_over_kernel (bool, optional): Normalize over kernel. + Defaults to False. + """ + + def __init__(self, + *args, + tempearture: float = 20, + power: int = 1.0, + eps: float = 1e-6, + norm_over_kernel: bool = False, + **kwargs) -> None: + super().__init__(*args, **kwargs) + self.tempearture = tempearture + self.power = power + self.norm_over_kernel = norm_over_kernel + self.eps = eps + + def forward(self, x: Tensor) -> Tensor: + """Forward function for `NormedConv2d`.""" + if not self.norm_over_kernel: + weight_ = self.weight / ( + self.weight.norm(dim=1, keepdim=True).pow(self.power) + + self.eps) + else: + weight_ = self.weight / ( + self.weight.view(self.weight.size(0), -1).norm( + dim=1, keepdim=True).pow(self.power)[..., None, None] + + self.eps) + x_ = x / (x.norm(dim=1, keepdim=True).pow(self.power) + self.eps) + x_ = x_ * self.tempearture + + if hasattr(self, 'conv2d_forward'): + x_ = self.conv2d_forward(x_, weight_) + else: + if digit_version(torch.__version__) >= digit_version('1.8'): + x_ = self._conv_forward(x_, weight_, self.bias) + else: + x_ = self._conv_forward(x_, weight_) + return x_ diff --git a/mmdetection/mmdet/models/layers/pixel_decoder.py b/mmdetection/mmdet/models/layers/pixel_decoder.py new file mode 100644 index 00000000..fb614340 --- /dev/null +++ b/mmdetection/mmdet/models/layers/pixel_decoder.py @@ -0,0 +1,249 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Conv2d, ConvModule +from mmengine.model import BaseModule, ModuleList, caffe2_xavier_init +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptMultiConfig +from .positional_encoding import SinePositionalEncoding +from .transformer import DetrTransformerEncoder + + +@MODELS.register_module() +class PixelDecoder(BaseModule): + """Pixel decoder with a structure like fpn. + + Args: + in_channels (list[int] | tuple[int]): Number of channels in the + input feature maps. + feat_channels (int): Number channels for feature. + out_channels (int): Number channels for output. + norm_cfg (:obj:`ConfigDict` or dict): Config for normalization. + Defaults to dict(type='GN', num_groups=32). + act_cfg (:obj:`ConfigDict` or dict): Config for activation. + Defaults to dict(type='ReLU'). + encoder (:obj:`ConfigDict` or dict): Config for transorformer + encoder.Defaults to None. + positional_encoding (:obj:`ConfigDict` or dict): Config for + transformer encoder position encoding. Defaults to + dict(type='SinePositionalEncoding', num_feats=128, + normalize=True). + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. Defaults to None. + """ + + def __init__(self, + in_channels: Union[List[int], Tuple[int]], + feat_channels: int, + out_channels: int, + norm_cfg: ConfigType = dict(type='GN', num_groups=32), + act_cfg: ConfigType = dict(type='ReLU'), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.num_inputs = len(in_channels) + self.lateral_convs = ModuleList() + self.output_convs = ModuleList() + self.use_bias = norm_cfg is None + for i in range(0, self.num_inputs - 1): + lateral_conv = ConvModule( + in_channels[i], + feat_channels, + kernel_size=1, + bias=self.use_bias, + norm_cfg=norm_cfg, + act_cfg=None) + output_conv = ConvModule( + feat_channels, + feat_channels, + kernel_size=3, + stride=1, + padding=1, + bias=self.use_bias, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.lateral_convs.append(lateral_conv) + self.output_convs.append(output_conv) + + self.last_feat_conv = ConvModule( + in_channels[-1], + feat_channels, + kernel_size=3, + padding=1, + stride=1, + bias=self.use_bias, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.mask_feature = Conv2d( + feat_channels, out_channels, kernel_size=3, stride=1, padding=1) + + def init_weights(self) -> None: + """Initialize weights.""" + for i in range(0, self.num_inputs - 2): + caffe2_xavier_init(self.lateral_convs[i].conv, bias=0) + caffe2_xavier_init(self.output_convs[i].conv, bias=0) + + caffe2_xavier_init(self.mask_feature, bias=0) + caffe2_xavier_init(self.last_feat_conv, bias=0) + + def forward(self, feats: List[Tensor], + batch_img_metas: List[dict]) -> Tuple[Tensor, Tensor]: + """ + Args: + feats (list[Tensor]): Feature maps of each level. Each has + shape of (batch_size, c, h, w). + batch_img_metas (list[dict]): List of image information. + Pass in for creating more accurate padding mask. Not + used here. + + Returns: + tuple[Tensor, Tensor]: a tuple containing the following: + + - mask_feature (Tensor): Shape (batch_size, c, h, w). + - memory (Tensor): Output of last stage of backbone.\ + Shape (batch_size, c, h, w). + """ + y = self.last_feat_conv(feats[-1]) + for i in range(self.num_inputs - 2, -1, -1): + x = feats[i] + cur_feat = self.lateral_convs[i](x) + y = cur_feat + \ + F.interpolate(y, size=cur_feat.shape[-2:], mode='nearest') + y = self.output_convs[i](y) + + mask_feature = self.mask_feature(y) + memory = feats[-1] + return mask_feature, memory + + +@MODELS.register_module() +class TransformerEncoderPixelDecoder(PixelDecoder): + """Pixel decoder with transormer encoder inside. + + Args: + in_channels (list[int] | tuple[int]): Number of channels in the + input feature maps. + feat_channels (int): Number channels for feature. + out_channels (int): Number channels for output. + norm_cfg (:obj:`ConfigDict` or dict): Config for normalization. + Defaults to dict(type='GN', num_groups=32). + act_cfg (:obj:`ConfigDict` or dict): Config for activation. + Defaults to dict(type='ReLU'). + encoder (:obj:`ConfigDict` or dict): Config for transformer encoder. + Defaults to None. + positional_encoding (:obj:`ConfigDict` or dict): Config for + transformer encoder position encoding. Defaults to + dict(num_feats=128, normalize=True). + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. Defaults to None. + """ + + def __init__(self, + in_channels: Union[List[int], Tuple[int]], + feat_channels: int, + out_channels: int, + norm_cfg: ConfigType = dict(type='GN', num_groups=32), + act_cfg: ConfigType = dict(type='ReLU'), + encoder: ConfigType = None, + positional_encoding: ConfigType = dict( + num_feats=128, normalize=True), + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + in_channels=in_channels, + feat_channels=feat_channels, + out_channels=out_channels, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + init_cfg=init_cfg) + self.last_feat_conv = None + + self.encoder = DetrTransformerEncoder(**encoder) + self.encoder_embed_dims = self.encoder.embed_dims + assert self.encoder_embed_dims == feat_channels, 'embed_dims({}) of ' \ + 'tranformer encoder must equal to feat_channels({})'.format( + feat_channels, self.encoder_embed_dims) + self.positional_encoding = SinePositionalEncoding( + **positional_encoding) + self.encoder_in_proj = Conv2d( + in_channels[-1], feat_channels, kernel_size=1) + self.encoder_out_proj = ConvModule( + feat_channels, + feat_channels, + kernel_size=3, + stride=1, + padding=1, + bias=self.use_bias, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def init_weights(self) -> None: + """Initialize weights.""" + for i in range(0, self.num_inputs - 2): + caffe2_xavier_init(self.lateral_convs[i].conv, bias=0) + caffe2_xavier_init(self.output_convs[i].conv, bias=0) + + caffe2_xavier_init(self.mask_feature, bias=0) + caffe2_xavier_init(self.encoder_in_proj, bias=0) + caffe2_xavier_init(self.encoder_out_proj.conv, bias=0) + + for p in self.encoder.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + + def forward(self, feats: List[Tensor], + batch_img_metas: List[dict]) -> Tuple[Tensor, Tensor]: + """ + Args: + feats (list[Tensor]): Feature maps of each level. Each has + shape of (batch_size, c, h, w). + batch_img_metas (list[dict]): List of image information. Pass in + for creating more accurate padding mask. + + Returns: + tuple: a tuple containing the following: + + - mask_feature (Tensor): shape (batch_size, c, h, w). + - memory (Tensor): shape (batch_size, c, h, w). + """ + feat_last = feats[-1] + bs, c, h, w = feat_last.shape + input_img_h, input_img_w = batch_img_metas[0]['batch_input_shape'] + padding_mask = feat_last.new_ones((bs, input_img_h, input_img_w), + dtype=torch.float32) + for i in range(bs): + img_h, img_w = batch_img_metas[i]['img_shape'] + padding_mask[i, :img_h, :img_w] = 0 + padding_mask = F.interpolate( + padding_mask.unsqueeze(1), + size=feat_last.shape[-2:], + mode='nearest').to(torch.bool).squeeze(1) + + pos_embed = self.positional_encoding(padding_mask) + feat_last = self.encoder_in_proj(feat_last) + # (batch_size, c, h, w) -> (batch_size, num_queries, c) + feat_last = feat_last.flatten(2).permute(0, 2, 1) + pos_embed = pos_embed.flatten(2).permute(0, 2, 1) + # (batch_size, h, w) -> (batch_size, h*w) + padding_mask = padding_mask.flatten(1) + memory = self.encoder( + query=feat_last, + query_pos=pos_embed, + key_padding_mask=padding_mask) + # (batch_size, num_queries, c) -> (batch_size, c, h, w) + memory = memory.permute(0, 2, 1).view(bs, self.encoder_embed_dims, h, + w) + y = self.encoder_out_proj(memory) + for i in range(self.num_inputs - 2, -1, -1): + x = feats[i] + cur_feat = self.lateral_convs[i](x) + y = cur_feat + \ + F.interpolate(y, size=cur_feat.shape[-2:], mode='nearest') + y = self.output_convs[i](y) + + mask_feature = self.mask_feature(y) + return mask_feature, memory diff --git a/mmdetection/mmdet/models/layers/positional_encoding.py b/mmdetection/mmdet/models/layers/positional_encoding.py new file mode 100644 index 00000000..87080d81 --- /dev/null +++ b/mmdetection/mmdet/models/layers/positional_encoding.py @@ -0,0 +1,269 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Optional + +import torch +import torch.nn as nn +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import MultiConfig, OptMultiConfig + + +@MODELS.register_module() +class SinePositionalEncoding(BaseModule): + """Position encoding with sine and cosine functions. + + See `End-to-End Object Detection with Transformers + `_ for details. + + Args: + num_feats (int): The feature dimension for each position + along x-axis or y-axis. Note the final returned dimension + for each position is 2 times of this value. + temperature (int, optional): The temperature used for scaling + the position embedding. Defaults to 10000. + normalize (bool, optional): Whether to normalize the position + embedding. Defaults to False. + scale (float, optional): A scale factor that scales the position + embedding. The scale will be used only when `normalize` is True. + Defaults to 2*pi. + eps (float, optional): A value added to the denominator for + numerical stability. Defaults to 1e-6. + offset (float): offset add to embed when do the normalization. + Defaults to 0. + init_cfg (dict or list[dict], optional): Initialization config dict. + Defaults to None + """ + + def __init__(self, + num_feats: int, + temperature: int = 10000, + normalize: bool = False, + scale: float = 2 * math.pi, + eps: float = 1e-6, + offset: float = 0., + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + if normalize: + assert isinstance(scale, (float, int)), 'when normalize is set,' \ + 'scale should be provided and in float or int type, ' \ + f'found {type(scale)}' + self.num_feats = num_feats + self.temperature = temperature + self.normalize = normalize + self.scale = scale + self.eps = eps + self.offset = offset + + def forward(self, mask: Tensor, input: Optional[Tensor] = None) -> Tensor: + """Forward function for `SinePositionalEncoding`. + + Args: + mask (Tensor): ByteTensor mask. Non-zero values representing + ignored positions, while zero values means valid positions + for this image. Shape [bs, h, w]. + input (Tensor, optional): Input image/feature Tensor. + Shape [bs, c, h, w] + + Returns: + pos (Tensor): Returned position embedding with shape + [bs, num_feats*2, h, w]. + """ + assert not (mask is None and input is None) + + if mask is not None: + B, H, W = mask.size() + device = mask.device + # For convenience of exporting to ONNX, + # it's required to convert + # `masks` from bool to int. + mask = mask.to(torch.int) + not_mask = 1 - mask # logical_not + y_embed = not_mask.cumsum(1, dtype=torch.float32) + x_embed = not_mask.cumsum(2, dtype=torch.float32) + else: + # single image or batch image with no padding + B, _, H, W = input.shape + device = input.device + x_embed = torch.arange( + 1, W + 1, dtype=torch.float32, device=device) + x_embed = x_embed.view(1, 1, -1).repeat(B, H, 1) + y_embed = torch.arange( + 1, H + 1, dtype=torch.float32, device=device) + y_embed = y_embed.view(1, -1, 1).repeat(B, 1, W) + if self.normalize: + y_embed = (y_embed + self.offset) / \ + (y_embed[:, -1:, :] + self.eps) * self.scale + x_embed = (x_embed + self.offset) / \ + (x_embed[:, :, -1:] + self.eps) * self.scale + dim_t = torch.arange( + self.num_feats, dtype=torch.float32, device=device) + dim_t = self.temperature**(2 * (dim_t // 2) / self.num_feats) + pos_x = x_embed[:, :, :, None] / dim_t + pos_y = y_embed[:, :, :, None] / dim_t + # use `view` instead of `flatten` for dynamically exporting to ONNX + + pos_x = torch.stack( + (pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), + dim=4).view(B, H, W, -1) + pos_y = torch.stack( + (pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), + dim=4).view(B, H, W, -1) + pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2) + return pos + + def __repr__(self) -> str: + """str: a string that describes the module""" + repr_str = self.__class__.__name__ + repr_str += f'(num_feats={self.num_feats}, ' + repr_str += f'temperature={self.temperature}, ' + repr_str += f'normalize={self.normalize}, ' + repr_str += f'scale={self.scale}, ' + repr_str += f'eps={self.eps})' + return repr_str + + +@MODELS.register_module() +class LearnedPositionalEncoding(BaseModule): + """Position embedding with learnable embedding weights. + + Args: + num_feats (int): The feature dimension for each position + along x-axis or y-axis. The final returned dimension for + each position is 2 times of this value. + row_num_embed (int, optional): The dictionary size of row embeddings. + Defaults to 50. + col_num_embed (int, optional): The dictionary size of col embeddings. + Defaults to 50. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__( + self, + num_feats: int, + row_num_embed: int = 50, + col_num_embed: int = 50, + init_cfg: MultiConfig = dict(type='Uniform', layer='Embedding') + ) -> None: + super().__init__(init_cfg=init_cfg) + self.row_embed = nn.Embedding(row_num_embed, num_feats) + self.col_embed = nn.Embedding(col_num_embed, num_feats) + self.num_feats = num_feats + self.row_num_embed = row_num_embed + self.col_num_embed = col_num_embed + + def forward(self, mask: Tensor) -> Tensor: + """Forward function for `LearnedPositionalEncoding`. + + Args: + mask (Tensor): ByteTensor mask. Non-zero values representing + ignored positions, while zero values means valid positions + for this image. Shape [bs, h, w]. + + Returns: + pos (Tensor): Returned position embedding with shape + [bs, num_feats*2, h, w]. + """ + h, w = mask.shape[-2:] + x = torch.arange(w, device=mask.device) + y = torch.arange(h, device=mask.device) + x_embed = self.col_embed(x) + y_embed = self.row_embed(y) + pos = torch.cat( + (x_embed.unsqueeze(0).repeat(h, 1, 1), y_embed.unsqueeze(1).repeat( + 1, w, 1)), + dim=-1).permute(2, 0, + 1).unsqueeze(0).repeat(mask.shape[0], 1, 1, 1) + return pos + + def __repr__(self) -> str: + """str: a string that describes the module""" + repr_str = self.__class__.__name__ + repr_str += f'(num_feats={self.num_feats}, ' + repr_str += f'row_num_embed={self.row_num_embed}, ' + repr_str += f'col_num_embed={self.col_num_embed})' + return repr_str + + +@MODELS.register_module() +class SinePositionalEncoding3D(SinePositionalEncoding): + """Position encoding with sine and cosine functions. + + See `End-to-End Object Detection with Transformers + `_ for details. + + Args: + num_feats (int): The feature dimension for each position + along x-axis or y-axis. Note the final returned dimension + for each position is 2 times of this value. + temperature (int, optional): The temperature used for scaling + the position embedding. Defaults to 10000. + normalize (bool, optional): Whether to normalize the position + embedding. Defaults to False. + scale (float, optional): A scale factor that scales the position + embedding. The scale will be used only when `normalize` is True. + Defaults to 2*pi. + eps (float, optional): A value added to the denominator for + numerical stability. Defaults to 1e-6. + offset (float): offset add to embed when do the normalization. + Defaults to 0. + init_cfg (dict or list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def forward(self, mask: Tensor) -> Tensor: + """Forward function for `SinePositionalEncoding3D`. + + Args: + mask (Tensor): ByteTensor mask. Non-zero values representing + ignored positions, while zero values means valid positions + for this image. Shape [bs, t, h, w]. + + Returns: + pos (Tensor): Returned position embedding with shape + [bs, num_feats*2, h, w]. + """ + assert mask.dim() == 4,\ + f'{mask.shape} should be a 4-dimensional Tensor,' \ + f' got {mask.dim()}-dimensional Tensor instead ' + # For convenience of exporting to ONNX, it's required to convert + # `masks` from bool to int. + mask = mask.to(torch.int) + not_mask = 1 - mask # logical_not + z_embed = not_mask.cumsum(1, dtype=torch.float32) + y_embed = not_mask.cumsum(2, dtype=torch.float32) + x_embed = not_mask.cumsum(3, dtype=torch.float32) + if self.normalize: + z_embed = (z_embed + self.offset) / \ + (z_embed[:, -1:, :, :] + self.eps) * self.scale + y_embed = (y_embed + self.offset) / \ + (y_embed[:, :, -1:, :] + self.eps) * self.scale + x_embed = (x_embed + self.offset) / \ + (x_embed[:, :, :, -1:] + self.eps) * self.scale + dim_t = torch.arange( + self.num_feats, dtype=torch.float32, device=mask.device) + dim_t = self.temperature**(2 * (dim_t // 2) / self.num_feats) + + dim_t_z = torch.arange((self.num_feats * 2), + dtype=torch.float32, + device=mask.device) + dim_t_z = self.temperature**(2 * (dim_t_z // 2) / (self.num_feats * 2)) + + pos_x = x_embed[:, :, :, :, None] / dim_t + pos_y = y_embed[:, :, :, :, None] / dim_t + pos_z = z_embed[:, :, :, :, None] / dim_t_z + # use `view` instead of `flatten` for dynamically exporting to ONNX + B, T, H, W = mask.size() + pos_x = torch.stack( + (pos_x[:, :, :, :, 0::2].sin(), pos_x[:, :, :, :, 1::2].cos()), + dim=5).view(B, T, H, W, -1) + pos_y = torch.stack( + (pos_y[:, :, :, :, 0::2].sin(), pos_y[:, :, :, :, 1::2].cos()), + dim=5).view(B, T, H, W, -1) + pos_z = torch.stack( + (pos_z[:, :, :, :, 0::2].sin(), pos_z[:, :, :, :, 1::2].cos()), + dim=5).view(B, T, H, W, -1) + pos = (torch.cat((pos_y, pos_x), dim=4) + pos_z).permute(0, 1, 4, 2, 3) + return pos diff --git a/mmdetection/mmdet/models/layers/res_layer.py b/mmdetection/mmdet/models/layers/res_layer.py new file mode 100644 index 00000000..ff24d3e8 --- /dev/null +++ b/mmdetection/mmdet/models/layers/res_layer.py @@ -0,0 +1,195 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +from mmcv.cnn import build_conv_layer, build_norm_layer +from mmengine.model import BaseModule, Sequential +from torch import Tensor +from torch import nn as nn + +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig + + +class ResLayer(Sequential): + """ResLayer to build ResNet style backbone. + + Args: + block (nn.Module): block used to build ResLayer. + inplanes (int): inplanes of block. + planes (int): planes of block. + num_blocks (int): number of blocks. + stride (int): stride of the first block. Defaults to 1 + avg_down (bool): Use AvgPool instead of stride conv when + downsampling in the bottleneck. Defaults to False + conv_cfg (dict): dictionary to construct and config conv layer. + Defaults to None + norm_cfg (dict): dictionary to construct and config norm layer. + Defaults to dict(type='BN') + downsample_first (bool): Downsample at the first block or last block. + False for Hourglass, True for ResNet. Defaults to True + """ + + def __init__(self, + block: BaseModule, + inplanes: int, + planes: int, + num_blocks: int, + stride: int = 1, + avg_down: bool = False, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN'), + downsample_first: bool = True, + **kwargs) -> None: + self.block = block + + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = [] + conv_stride = stride + if avg_down: + conv_stride = 1 + downsample.append( + nn.AvgPool2d( + kernel_size=stride, + stride=stride, + ceil_mode=True, + count_include_pad=False)) + downsample.extend([ + build_conv_layer( + conv_cfg, + inplanes, + planes * block.expansion, + kernel_size=1, + stride=conv_stride, + bias=False), + build_norm_layer(norm_cfg, planes * block.expansion)[1] + ]) + downsample = nn.Sequential(*downsample) + + layers = [] + if downsample_first: + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=stride, + downsample=downsample, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + **kwargs)) + inplanes = planes * block.expansion + for _ in range(1, num_blocks): + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + **kwargs)) + + else: # downsample_first=False is for HourglassModule + for _ in range(num_blocks - 1): + layers.append( + block( + inplanes=inplanes, + planes=inplanes, + stride=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + **kwargs)) + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=stride, + downsample=downsample, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + **kwargs)) + super().__init__(*layers) + + +class SimplifiedBasicBlock(BaseModule): + """Simplified version of original basic residual block. This is used in + `SCNet `_. + + - Norm layer is now optional + - Last ReLU in forward function is removed + """ + expansion = 1 + + def __init__(self, + inplanes: int, + planes: int, + stride: int = 1, + dilation: int = 1, + downsample: Optional[Sequential] = None, + style: ConfigType = 'pytorch', + with_cp: bool = False, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN'), + dcn: OptConfigType = None, + plugins: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + assert dcn is None, 'Not implemented yet.' + assert plugins is None, 'Not implemented yet.' + assert not with_cp, 'Not implemented yet.' + self.with_norm = norm_cfg is not None + with_bias = True if norm_cfg is None else False + self.conv1 = build_conv_layer( + conv_cfg, + inplanes, + planes, + 3, + stride=stride, + padding=dilation, + dilation=dilation, + bias=with_bias) + if self.with_norm: + self.norm1_name, norm1 = build_norm_layer( + norm_cfg, planes, postfix=1) + self.add_module(self.norm1_name, norm1) + self.conv2 = build_conv_layer( + conv_cfg, planes, planes, 3, padding=1, bias=with_bias) + if self.with_norm: + self.norm2_name, norm2 = build_norm_layer( + norm_cfg, planes, postfix=2) + self.add_module(self.norm2_name, norm2) + + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + self.dilation = dilation + self.with_cp = with_cp + + @property + def norm1(self) -> Optional[BaseModule]: + """nn.Module: normalization layer after the first convolution layer""" + return getattr(self, self.norm1_name) if self.with_norm else None + + @property + def norm2(self) -> Optional[BaseModule]: + """nn.Module: normalization layer after the second convolution layer""" + return getattr(self, self.norm2_name) if self.with_norm else None + + def forward(self, x: Tensor) -> Tensor: + """Forward function for SimplifiedBasicBlock.""" + + identity = x + + out = self.conv1(x) + if self.with_norm: + out = self.norm1(out) + out = self.relu(out) + + out = self.conv2(out) + if self.with_norm: + out = self.norm2(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + + return out diff --git a/mmdetection/mmdet/models/layers/se_layer.py b/mmdetection/mmdet/models/layers/se_layer.py new file mode 100644 index 00000000..5598daba --- /dev/null +++ b/mmdetection/mmdet/models/layers/se_layer.py @@ -0,0 +1,162 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from mmengine.utils import digit_version, is_tuple_of +from torch import Tensor + +from mmdet.utils import MultiConfig, OptConfigType, OptMultiConfig + + +class SELayer(BaseModule): + """Squeeze-and-Excitation Module. + + Args: + channels (int): The input (and output) channels of the SE layer. + ratio (int): Squeeze ratio in SELayer, the intermediate channel will be + ``int(channels/ratio)``. Defaults to 16. + conv_cfg (None or dict): Config dict for convolution layer. + Defaults to None, which means using conv2d. + act_cfg (dict or Sequence[dict]): Config dict for activation layer. + If act_cfg is a dict, two activation layers will be configurated + by this dict. If act_cfg is a sequence of dicts, the first + activation layer will be configurated by the first dict and the + second activation layer will be configurated by the second dict. + Defaults to (dict(type='ReLU'), dict(type='Sigmoid')) + init_cfg (dict or list[dict], optional): Initialization config dict. + Defaults to None + """ + + def __init__(self, + channels: int, + ratio: int = 16, + conv_cfg: OptConfigType = None, + act_cfg: MultiConfig = (dict(type='ReLU'), + dict(type='Sigmoid')), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + if isinstance(act_cfg, dict): + act_cfg = (act_cfg, act_cfg) + assert len(act_cfg) == 2 + assert is_tuple_of(act_cfg, dict) + self.global_avgpool = nn.AdaptiveAvgPool2d(1) + self.conv1 = ConvModule( + in_channels=channels, + out_channels=int(channels / ratio), + kernel_size=1, + stride=1, + conv_cfg=conv_cfg, + act_cfg=act_cfg[0]) + self.conv2 = ConvModule( + in_channels=int(channels / ratio), + out_channels=channels, + kernel_size=1, + stride=1, + conv_cfg=conv_cfg, + act_cfg=act_cfg[1]) + + def forward(self, x: Tensor) -> Tensor: + """Forward function for SELayer.""" + out = self.global_avgpool(x) + out = self.conv1(out) + out = self.conv2(out) + return x * out + + +class DyReLU(BaseModule): + """Dynamic ReLU (DyReLU) module. + + See `Dynamic ReLU `_ for details. + Current implementation is specialized for task-aware attention in DyHead. + HSigmoid arguments in default act_cfg follow DyHead official code. + https://github.com/microsoft/DynamicHead/blob/master/dyhead/dyrelu.py + + Args: + channels (int): The input (and output) channels of DyReLU module. + ratio (int): Squeeze ratio in Squeeze-and-Excitation-like module, + the intermediate channel will be ``int(channels/ratio)``. + Defaults to 4. + conv_cfg (None or dict): Config dict for convolution layer. + Defaults to None, which means using conv2d. + act_cfg (dict or Sequence[dict]): Config dict for activation layer. + If act_cfg is a dict, two activation layers will be configurated + by this dict. If act_cfg is a sequence of dicts, the first + activation layer will be configurated by the first dict and the + second activation layer will be configurated by the second dict. + Defaults to (dict(type='ReLU'), dict(type='HSigmoid', bias=3.0, + divisor=6.0)) + init_cfg (dict or list[dict], optional): Initialization config dict. + Defaults to None + """ + + def __init__(self, + channels: int, + ratio: int = 4, + conv_cfg: OptConfigType = None, + act_cfg: MultiConfig = (dict(type='ReLU'), + dict( + type='HSigmoid', + bias=3.0, + divisor=6.0)), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + if isinstance(act_cfg, dict): + act_cfg = (act_cfg, act_cfg) + assert len(act_cfg) == 2 + assert is_tuple_of(act_cfg, dict) + self.channels = channels + self.expansion = 4 # for a1, b1, a2, b2 + self.global_avgpool = nn.AdaptiveAvgPool2d(1) + self.conv1 = ConvModule( + in_channels=channels, + out_channels=int(channels / ratio), + kernel_size=1, + stride=1, + conv_cfg=conv_cfg, + act_cfg=act_cfg[0]) + self.conv2 = ConvModule( + in_channels=int(channels / ratio), + out_channels=channels * self.expansion, + kernel_size=1, + stride=1, + conv_cfg=conv_cfg, + act_cfg=act_cfg[1]) + + def forward(self, x: Tensor) -> Tensor: + """Forward function.""" + coeffs = self.global_avgpool(x) + coeffs = self.conv1(coeffs) + coeffs = self.conv2(coeffs) - 0.5 # value range: [-0.5, 0.5] + a1, b1, a2, b2 = torch.split(coeffs, self.channels, dim=1) + a1 = a1 * 2.0 + 1.0 # [-1.0, 1.0] + 1.0 + a2 = a2 * 2.0 # [-1.0, 1.0] + out = torch.max(x * a1 + b1, x * a2 + b2) + return out + + +class ChannelAttention(BaseModule): + """Channel attention Module. + + Args: + channels (int): The input (and output) channels of the attention layer. + init_cfg (dict or list[dict], optional): Initialization config dict. + Defaults to None + """ + + def __init__(self, channels: int, init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + self.global_avgpool = nn.AdaptiveAvgPool2d(1) + self.fc = nn.Conv2d(channels, channels, 1, 1, 0, bias=True) + if digit_version(torch.__version__) < (1, 7, 0): + self.act = nn.Hardsigmoid() + else: + self.act = nn.Hardsigmoid(inplace=True) + + def forward(self, x: Tensor) -> Tensor: + """Forward function for ChannelAttention.""" + with torch.cuda.amp.autocast(enabled=False): + out = self.global_avgpool(x) + out = self.fc(out) + out = self.act(out) + return x * out diff --git a/mmdetection/mmdet/models/layers/transformer/__init__.py b/mmdetection/mmdet/models/layers/transformer/__init__.py new file mode 100644 index 00000000..839d9364 --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/__init__.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .conditional_detr_layers import (ConditionalDetrTransformerDecoder, + ConditionalDetrTransformerDecoderLayer) +from .dab_detr_layers import (DABDetrTransformerDecoder, + DABDetrTransformerDecoderLayer, + DABDetrTransformerEncoder) +from .ddq_detr_layers import DDQTransformerDecoder +from .deformable_detr_layers import (DeformableDetrTransformerDecoder, + DeformableDetrTransformerDecoderLayer, + DeformableDetrTransformerEncoder, + DeformableDetrTransformerEncoderLayer) +from .detr_layers import (DetrTransformerDecoder, DetrTransformerDecoderLayer, + DetrTransformerEncoder, DetrTransformerEncoderLayer) +from .dino_layers import CdnQueryGenerator, DinoTransformerDecoder +from .grounding_dino_layers import (GroundingDinoTransformerDecoder, + GroundingDinoTransformerDecoderLayer, + GroundingDinoTransformerEncoder) +from .mask2former_layers import (Mask2FormerTransformerDecoder, + Mask2FormerTransformerDecoderLayer, + Mask2FormerTransformerEncoder) +from .utils import (MLP, AdaptivePadding, ConditionalAttention, DynamicConv, + PatchEmbed, PatchMerging, coordinate_to_encoding, + inverse_sigmoid, nchw_to_nlc, nlc_to_nchw) + +__all__ = [ + 'nlc_to_nchw', 'nchw_to_nlc', 'AdaptivePadding', 'PatchEmbed', + 'PatchMerging', 'inverse_sigmoid', 'DynamicConv', 'MLP', + 'DetrTransformerEncoder', 'DetrTransformerDecoder', + 'DetrTransformerEncoderLayer', 'DetrTransformerDecoderLayer', + 'DeformableDetrTransformerEncoder', 'DeformableDetrTransformerDecoder', + 'DeformableDetrTransformerEncoderLayer', + 'DeformableDetrTransformerDecoderLayer', 'coordinate_to_encoding', + 'ConditionalAttention', 'DABDetrTransformerDecoderLayer', + 'DABDetrTransformerDecoder', 'DABDetrTransformerEncoder', + 'DDQTransformerDecoder', 'ConditionalDetrTransformerDecoder', + 'ConditionalDetrTransformerDecoderLayer', 'DinoTransformerDecoder', + 'CdnQueryGenerator', 'Mask2FormerTransformerEncoder', + 'Mask2FormerTransformerDecoderLayer', 'Mask2FormerTransformerDecoder', + 'GroundingDinoTransformerDecoderLayer', 'GroundingDinoTransformerEncoder', + 'GroundingDinoTransformerDecoder' +] diff --git a/mmdetection/mmdet/models/layers/transformer/conditional_detr_layers.py b/mmdetection/mmdet/models/layers/transformer/conditional_detr_layers.py new file mode 100644 index 00000000..6db12a13 --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/conditional_detr_layers.py @@ -0,0 +1,170 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import FFN +from torch import Tensor +from torch.nn import ModuleList + +from .detr_layers import DetrTransformerDecoder, DetrTransformerDecoderLayer +from .utils import MLP, ConditionalAttention, coordinate_to_encoding + + +class ConditionalDetrTransformerDecoder(DetrTransformerDecoder): + """Decoder of Conditional DETR.""" + + def _init_layers(self) -> None: + """Initialize decoder layers and other layers.""" + self.layers = ModuleList([ + ConditionalDetrTransformerDecoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + self.embed_dims = self.layers[0].embed_dims + self.post_norm = build_norm_layer(self.post_norm_cfg, + self.embed_dims)[1] + # conditional detr affline + self.query_scale = MLP(self.embed_dims, self.embed_dims, + self.embed_dims, 2) + self.ref_point_head = MLP(self.embed_dims, self.embed_dims, 2, 2) + # we have substitute 'qpos_proj' with 'qpos_sine_proj' except for + # the first decoder layer), so 'qpos_proj' should be deleted + # in other layers. + for layer_id in range(self.num_layers - 1): + self.layers[layer_id + 1].cross_attn.qpos_proj = None + + def forward(self, + query: Tensor, + key: Tensor = None, + query_pos: Tensor = None, + key_pos: Tensor = None, + key_padding_mask: Tensor = None): + """Forward function of decoder. + + Args: + query (Tensor): The input query with shape + (bs, num_queries, dim). + key (Tensor): The input key with shape (bs, num_keys, dim) If + `None`, the `query` will be used. Defaults to `None`. + query_pos (Tensor): The positional encoding for `query`, with the + same shape as `query`. If not `None`, it will be added to + `query` before forward function. Defaults to `None`. + key_pos (Tensor): The positional encoding for `key`, with the + same shape as `key`. If not `None`, it will be added to + `key` before forward function. If `None`, and `query_pos` + has the same shape as `key`, then `query_pos` will be used + as `key_pos`. Defaults to `None`. + key_padding_mask (Tensor): ByteTensor with shape (bs, num_keys). + Defaults to `None`. + Returns: + List[Tensor]: forwarded results with shape (num_decoder_layers, + bs, num_queries, dim) if `return_intermediate` is True, otherwise + with shape (1, bs, num_queries, dim). References with shape + (bs, num_queries, 2). + """ + reference_unsigmoid = self.ref_point_head( + query_pos) # [bs, num_queries, 2] + reference = reference_unsigmoid.sigmoid() + reference_xy = reference[..., :2] + intermediate = [] + for layer_id, layer in enumerate(self.layers): + if layer_id == 0: + pos_transformation = 1 + else: + pos_transformation = self.query_scale(query) + # get sine embedding for the query reference + ref_sine_embed = coordinate_to_encoding(coord_tensor=reference_xy) + # apply transformation + ref_sine_embed = ref_sine_embed * pos_transformation + query = layer( + query, + key=key, + query_pos=query_pos, + key_pos=key_pos, + key_padding_mask=key_padding_mask, + ref_sine_embed=ref_sine_embed, + is_first=(layer_id == 0)) + if self.return_intermediate: + intermediate.append(self.post_norm(query)) + + if self.return_intermediate: + return torch.stack(intermediate), reference + + query = self.post_norm(query) + return query.unsqueeze(0), reference + + +class ConditionalDetrTransformerDecoderLayer(DetrTransformerDecoderLayer): + """Implements decoder layer in Conditional DETR transformer.""" + + def _init_layers(self): + """Initialize self-attention, cross-attention, FFN, and + normalization.""" + self.self_attn = ConditionalAttention(**self.self_attn_cfg) + self.cross_attn = ConditionalAttention(**self.cross_attn_cfg) + self.embed_dims = self.self_attn.embed_dims + self.ffn = FFN(**self.ffn_cfg) + norms_list = [ + build_norm_layer(self.norm_cfg, self.embed_dims)[1] + for _ in range(3) + ] + self.norms = ModuleList(norms_list) + + def forward(self, + query: Tensor, + key: Tensor = None, + query_pos: Tensor = None, + key_pos: Tensor = None, + self_attn_masks: Tensor = None, + cross_attn_masks: Tensor = None, + key_padding_mask: Tensor = None, + ref_sine_embed: Tensor = None, + is_first: bool = False): + """ + Args: + query (Tensor): The input query, has shape (bs, num_queries, dim) + key (Tensor, optional): The input key, has shape (bs, num_keys, + dim). If `None`, the `query` will be used. Defaults to `None`. + query_pos (Tensor, optional): The positional encoding for `query`, + has the same shape as `query`. If not `None`, it will be + added to `query` before forward function. Defaults to `None`. + ref_sine_embed (Tensor): The positional encoding for query in + cross attention, with the same shape as `x`. Defaults to None. + key_pos (Tensor, optional): The positional encoding for `key`, has + the same shape as `key`. If not None, it will be added to + `key` before forward function. If None, and `query_pos` has + the same shape as `key`, then `query_pos` will be used for + `key_pos`. Defaults to None. + self_attn_masks (Tensor, optional): ByteTensor mask, has shape + (num_queries, num_keys), Same in `nn.MultiheadAttention. + forward`. Defaults to None. + cross_attn_masks (Tensor, optional): ByteTensor mask, has shape + (num_queries, num_keys), Same in `nn.MultiheadAttention. + forward`. Defaults to None. + key_padding_mask (Tensor, optional): ByteTensor, has shape + (bs, num_keys). Defaults to None. + is_first (bool): A indicator to tell whether the current layer + is the first layer of the decoder. Defaults to False. + + Returns: + Tensor: Forwarded results, has shape (bs, num_queries, dim). + """ + query = self.self_attn( + query=query, + key=query, + query_pos=query_pos, + key_pos=query_pos, + attn_mask=self_attn_masks) + query = self.norms[0](query) + query = self.cross_attn( + query=query, + key=key, + query_pos=query_pos, + key_pos=key_pos, + attn_mask=cross_attn_masks, + key_padding_mask=key_padding_mask, + ref_sine_embed=ref_sine_embed, + is_first=is_first) + query = self.norms[1](query) + query = self.ffn(query) + query = self.norms[2](query) + + return query diff --git a/mmdetection/mmdet/models/layers/transformer/dab_detr_layers.py b/mmdetection/mmdet/models/layers/transformer/dab_detr_layers.py new file mode 100644 index 00000000..b8a6e772 --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/dab_detr_layers.py @@ -0,0 +1,298 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn as nn +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import FFN +from mmengine.model import ModuleList +from torch import Tensor + +from .detr_layers import (DetrTransformerDecoder, DetrTransformerDecoderLayer, + DetrTransformerEncoder, DetrTransformerEncoderLayer) +from .utils import (MLP, ConditionalAttention, coordinate_to_encoding, + inverse_sigmoid) + + +class DABDetrTransformerDecoderLayer(DetrTransformerDecoderLayer): + """Implements decoder layer in DAB-DETR transformer.""" + + def _init_layers(self): + """Initialize self-attention, cross-attention, FFN, normalization and + others.""" + self.self_attn = ConditionalAttention(**self.self_attn_cfg) + self.cross_attn = ConditionalAttention(**self.cross_attn_cfg) + self.embed_dims = self.self_attn.embed_dims + self.ffn = FFN(**self.ffn_cfg) + norms_list = [ + build_norm_layer(self.norm_cfg, self.embed_dims)[1] + for _ in range(3) + ] + self.norms = ModuleList(norms_list) + self.keep_query_pos = self.cross_attn.keep_query_pos + + def forward(self, + query: Tensor, + key: Tensor, + query_pos: Tensor, + key_pos: Tensor, + ref_sine_embed: Tensor = None, + self_attn_masks: Tensor = None, + cross_attn_masks: Tensor = None, + key_padding_mask: Tensor = None, + is_first: bool = False, + **kwargs) -> Tensor: + """ + Args: + query (Tensor): The input query with shape [bs, num_queries, + dim]. + key (Tensor): The key tensor with shape [bs, num_keys, + dim]. + query_pos (Tensor): The positional encoding for query in self + attention, with the same shape as `x`. + key_pos (Tensor): The positional encoding for `key`, with the + same shape as `key`. + ref_sine_embed (Tensor): The positional encoding for query in + cross attention, with the same shape as `x`. + Defaults to None. + self_attn_masks (Tensor): ByteTensor mask with shape [num_queries, + num_keys]. Same in `nn.MultiheadAttention.forward`. + Defaults to None. + cross_attn_masks (Tensor): ByteTensor mask with shape [num_queries, + num_keys]. Same in `nn.MultiheadAttention.forward`. + Defaults to None. + key_padding_mask (Tensor): ByteTensor with shape [bs, num_keys]. + Defaults to None. + is_first (bool): A indicator to tell whether the current layer + is the first layer of the decoder. + Defaults to False. + + Returns: + Tensor: forwarded results with shape + [bs, num_queries, dim]. + """ + + query = self.self_attn( + query=query, + key=query, + query_pos=query_pos, + key_pos=query_pos, + attn_mask=self_attn_masks, + **kwargs) + query = self.norms[0](query) + query = self.cross_attn( + query=query, + key=key, + query_pos=query_pos, + key_pos=key_pos, + ref_sine_embed=ref_sine_embed, + attn_mask=cross_attn_masks, + key_padding_mask=key_padding_mask, + is_first=is_first, + **kwargs) + query = self.norms[1](query) + query = self.ffn(query) + query = self.norms[2](query) + + return query + + +class DABDetrTransformerDecoder(DetrTransformerDecoder): + """Decoder of DAB-DETR. + + Args: + query_dim (int): The last dimension of query pos, + 4 for anchor format, 2 for point format. + Defaults to 4. + query_scale_type (str): Type of transformation applied + to content query. Defaults to `cond_elewise`. + with_modulated_hw_attn (bool): Whether to inject h&w info + during cross conditional attention. Defaults to True. + """ + + def __init__(self, + *args, + query_dim: int = 4, + query_scale_type: str = 'cond_elewise', + with_modulated_hw_attn: bool = True, + **kwargs): + + self.query_dim = query_dim + self.query_scale_type = query_scale_type + self.with_modulated_hw_attn = with_modulated_hw_attn + + super().__init__(*args, **kwargs) + + def _init_layers(self): + """Initialize decoder layers and other layers.""" + assert self.query_dim in [2, 4], \ + f'{"dab-detr only supports anchor prior or reference point prior"}' + assert self.query_scale_type in [ + 'cond_elewise', 'cond_scalar', 'fix_elewise' + ] + + self.layers = ModuleList([ + DABDetrTransformerDecoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + + embed_dims = self.layers[0].embed_dims + self.embed_dims = embed_dims + + self.post_norm = build_norm_layer(self.post_norm_cfg, embed_dims)[1] + if self.query_scale_type == 'cond_elewise': + self.query_scale = MLP(embed_dims, embed_dims, embed_dims, 2) + elif self.query_scale_type == 'cond_scalar': + self.query_scale = MLP(embed_dims, embed_dims, 1, 2) + elif self.query_scale_type == 'fix_elewise': + self.query_scale = nn.Embedding(self.num_layers, embed_dims) + else: + raise NotImplementedError('Unknown query_scale_type: {}'.format( + self.query_scale_type)) + + self.ref_point_head = MLP(self.query_dim // 2 * embed_dims, embed_dims, + embed_dims, 2) + + if self.with_modulated_hw_attn and self.query_dim == 4: + self.ref_anchor_head = MLP(embed_dims, embed_dims, 2, 2) + + self.keep_query_pos = self.layers[0].keep_query_pos + if not self.keep_query_pos: + for layer_id in range(self.num_layers - 1): + self.layers[layer_id + 1].cross_attn.qpos_proj = None + + def forward(self, + query: Tensor, + key: Tensor, + query_pos: Tensor, + key_pos: Tensor, + reg_branches: nn.Module, + key_padding_mask: Tensor = None, + **kwargs) -> List[Tensor]: + """Forward function of decoder. + + Args: + query (Tensor): The input query with shape (bs, num_queries, dim). + key (Tensor): The input key with shape (bs, num_keys, dim). + query_pos (Tensor): The positional encoding for `query`, with the + same shape as `query`. + key_pos (Tensor): The positional encoding for `key`, with the + same shape as `key`. + reg_branches (nn.Module): The regression branch for dynamically + updating references in each layer. + key_padding_mask (Tensor): ByteTensor with shape (bs, num_keys). + Defaults to `None`. + + Returns: + List[Tensor]: forwarded results with shape (num_decoder_layers, + bs, num_queries, dim) if `return_intermediate` is True, otherwise + with shape (1, bs, num_queries, dim). references with shape + (num_decoder_layers, bs, num_queries, 2/4). + """ + output = query + unsigmoid_references = query_pos + + reference_points = unsigmoid_references.sigmoid() + intermediate_reference_points = [reference_points] + + intermediate = [] + for layer_id, layer in enumerate(self.layers): + obj_center = reference_points[..., :self.query_dim] + ref_sine_embed = coordinate_to_encoding( + coord_tensor=obj_center, num_feats=self.embed_dims // 2) + query_pos = self.ref_point_head( + ref_sine_embed) # [bs, nq, 2c] -> [bs, nq, c] + # For the first decoder layer, do not apply transformation + if self.query_scale_type != 'fix_elewise': + if layer_id == 0: + pos_transformation = 1 + else: + pos_transformation = self.query_scale(output) + else: + pos_transformation = self.query_scale.weight[layer_id] + # apply transformation + ref_sine_embed = ref_sine_embed[ + ..., :self.embed_dims] * pos_transformation + # modulated height and weight attention + if self.with_modulated_hw_attn: + assert obj_center.size(-1) == 4 + ref_hw = self.ref_anchor_head(output).sigmoid() + ref_sine_embed[..., self.embed_dims // 2:] *= \ + (ref_hw[..., 0] / obj_center[..., 2]).unsqueeze(-1) + ref_sine_embed[..., : self.embed_dims // 2] *= \ + (ref_hw[..., 1] / obj_center[..., 3]).unsqueeze(-1) + + output = layer( + output, + key, + query_pos=query_pos, + ref_sine_embed=ref_sine_embed, + key_pos=key_pos, + key_padding_mask=key_padding_mask, + is_first=(layer_id == 0), + **kwargs) + # iter update + tmp_reg_preds = reg_branches(output) + tmp_reg_preds[..., :self.query_dim] += inverse_sigmoid( + reference_points) + new_reference_points = tmp_reg_preds[ + ..., :self.query_dim].sigmoid() + if layer_id != self.num_layers - 1: + intermediate_reference_points.append(new_reference_points) + reference_points = new_reference_points.detach() + + if self.return_intermediate: + intermediate.append(self.post_norm(output)) + + output = self.post_norm(output) + + if self.return_intermediate: + return [ + torch.stack(intermediate), + torch.stack(intermediate_reference_points), + ] + else: + return [ + output.unsqueeze(0), + torch.stack(intermediate_reference_points) + ] + + +class DABDetrTransformerEncoder(DetrTransformerEncoder): + """Encoder of DAB-DETR.""" + + def _init_layers(self): + """Initialize encoder layers.""" + self.layers = ModuleList([ + DetrTransformerEncoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + embed_dims = self.layers[0].embed_dims + self.embed_dims = embed_dims + self.query_scale = MLP(embed_dims, embed_dims, embed_dims, 2) + + def forward(self, query: Tensor, query_pos: Tensor, + key_padding_mask: Tensor, **kwargs): + """Forward function of encoder. + + Args: + query (Tensor): Input queries of encoder, has shape + (bs, num_queries, dim). + query_pos (Tensor): The positional embeddings of the queries, has + shape (bs, num_feat_points, dim). + key_padding_mask (Tensor): ByteTensor, the key padding mask + of the queries, has shape (bs, num_feat_points). + + Returns: + Tensor: With shape (num_queries, bs, dim). + """ + + for layer in self.layers: + pos_scales = self.query_scale(query) + query = layer( + query, + query_pos=query_pos * pos_scales, + key_padding_mask=key_padding_mask, + **kwargs) + + return query diff --git a/mmdetection/mmdet/models/layers/transformer/ddq_detr_layers.py b/mmdetection/mmdet/models/layers/transformer/ddq_detr_layers.py new file mode 100644 index 00000000..57664c7e --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/ddq_detr_layers.py @@ -0,0 +1,223 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy + +import torch +from mmcv.ops import batched_nms +from torch import Tensor, nn + +from mmdet.structures.bbox import bbox_cxcywh_to_xyxy +from .deformable_detr_layers import DeformableDetrTransformerDecoder +from .utils import MLP, coordinate_to_encoding, inverse_sigmoid + + +class DDQTransformerDecoder(DeformableDetrTransformerDecoder): + """Transformer decoder of DDQ.""" + + def _init_layers(self) -> None: + """Initialize encoder layers.""" + super()._init_layers() + self.ref_point_head = MLP(self.embed_dims * 2, self.embed_dims, + self.embed_dims, 2) + self.norm = nn.LayerNorm(self.embed_dims) + + def select_distinct_queries(self, reference_points: Tensor, query: Tensor, + self_attn_mask: Tensor, layer_index): + """Get updated `self_attn_mask` for distinct queries selection, it is + used in self attention layers of decoder. + + Args: + reference_points (Tensor): The input reference of decoder, + has shape (bs, num_queries, 4) with the last dimension + arranged as (cx, cy, w, h). + query (Tensor): The input query of decoder, has shape + (bs, num_queries, dims). + self_attn_mask (Tensor): The input self attention mask of + last decoder layer, has shape (bs, num_queries_total, + num_queries_total). + layer_index (int): Last decoder layer index, used to get + classification score of last layer output, for + distinct queries selection. + + Returns: + Tensor: `self_attn_mask` used in self attention layers + of decoder, has shape (bs, num_queries_total, + num_queries_total). + """ + num_imgs = len(reference_points) + dis_start, num_dis = self.cache_dict['dis_query_info'] + # shape of self_attn_mask + # (batch⋅num_heads, num_queries, embed_dims) + dis_mask = self_attn_mask[:, dis_start:dis_start + num_dis, + dis_start:dis_start + num_dis] + # cls_branches from DDQDETRHead + scores = self.cache_dict['cls_branches'][layer_index]( + query[:, dis_start:dis_start + num_dis]).sigmoid().max(-1).values + proposals = reference_points[:, dis_start:dis_start + num_dis] + proposals = bbox_cxcywh_to_xyxy(proposals) + + attn_mask_list = [] + for img_id in range(num_imgs): + single_proposals = proposals[img_id] + single_scores = scores[img_id] + attn_mask = ~dis_mask[img_id * self.cache_dict['num_heads']][0] + # distinct query inds in this layer + ori_index = attn_mask.nonzero().view(-1) + _, keep_idxs = batched_nms(single_proposals[ori_index], + single_scores[ori_index], + torch.ones(len(ori_index)), + self.cache_dict['dqs_cfg']) + + real_keep_index = ori_index[keep_idxs] + + attn_mask = torch.ones_like(dis_mask[0]).bool() + # such a attn_mask give best result + # If it requires to keep index i, then all cells in row or column + # i should be kept in `attn_mask` . For example, if + # `real_keep_index` = [1, 4], and `attn_mask` size = [8, 8], + # then all cells at rows or columns [1, 4] should be kept, and + # all the other cells should be masked out. So the value of + # `attn_mask` should be: + # + # target\source 0 1 2 3 4 5 6 7 + # 0 [ 0 1 0 0 1 0 0 0 ] + # 1 [ 1 1 1 1 1 1 1 1 ] + # 2 [ 0 1 0 0 1 0 0 0 ] + # 3 [ 0 1 0 0 1 0 0 0 ] + # 4 [ 1 1 1 1 1 1 1 1 ] + # 5 [ 0 1 0 0 1 0 0 0 ] + # 6 [ 0 1 0 0 1 0 0 0 ] + # 7 [ 0 1 0 0 1 0 0 0 ] + attn_mask[real_keep_index] = False + attn_mask[:, real_keep_index] = False + + attn_mask = attn_mask[None].repeat(self.cache_dict['num_heads'], 1, + 1) + attn_mask_list.append(attn_mask) + attn_mask = torch.cat(attn_mask_list) + self_attn_mask = copy.deepcopy(self_attn_mask) + self_attn_mask[:, dis_start:dis_start + num_dis, + dis_start:dis_start + num_dis] = attn_mask + # will be used in loss and inference + self.cache_dict['distinct_query_mask'].append(~attn_mask) + return self_attn_mask + + def forward(self, query: Tensor, value: Tensor, key_padding_mask: Tensor, + self_attn_mask: Tensor, reference_points: Tensor, + spatial_shapes: Tensor, level_start_index: Tensor, + valid_ratios: Tensor, reg_branches: nn.ModuleList, + **kwargs) -> Tensor: + """Forward function of Transformer decoder. + + Args: + query (Tensor): The input query, has shape (bs, num_queries, + dims). + value (Tensor): The input values, has shape (bs, num_value, dim). + key_padding_mask (Tensor): The `key_padding_mask` of `cross_attn` + input. ByteTensor, has shape (bs, num_value). + self_attn_mask (Tensor): The attention mask to prevent information + leakage from different denoising groups, distinct queries and + dense queries, has shape (num_queries_total, + num_queries_total). It will be updated for distinct queries + selection in this forward function. It is `None` when + `self.training` is `False`. + reference_points (Tensor): The initial reference, has shape + (bs, num_queries, 4) with the last dimension arranged as + (cx, cy, w, h). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape (num_levels, ) and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + reg_branches: (obj:`nn.ModuleList`): Used for refining the + regression results. + + Returns: + tuple[Tensor]: Output queries and references of Transformer + decoder + + - query (Tensor): Output embeddings of the last decoder, has + shape (bs, num_queries, embed_dims) when `return_intermediate` + is `False`. Otherwise, Intermediate output embeddings of all + decoder layers, has shape (num_decoder_layers, bs, num_queries, + embed_dims). + - reference_points (Tensor): The reference of the last decoder + layer, has shape (bs, num_queries, 4) when `return_intermediate` + is `False`. Otherwise, Intermediate references of all decoder + layers, has shape (1 + num_decoder_layers, bs, num_queries, 4). + The coordinates are arranged as (cx, cy, w, h). + """ + intermediate = [] + intermediate_reference_points = [reference_points] + self.cache_dict['distinct_query_mask'] = [] + if self_attn_mask is None: + self_attn_mask = torch.zeros((query.size(1), query.size(1)), + device=query.device).bool() + # shape is (batch*number_heads, num_queries, num_queries) + self_attn_mask = self_attn_mask[None].repeat( + len(query) * self.cache_dict['num_heads'], 1, 1) + for layer_index, layer in enumerate(self.layers): + if reference_points.shape[-1] == 4: + reference_points_input = \ + reference_points[:, :, None] * torch.cat( + [valid_ratios, valid_ratios], -1)[:, None] + else: + assert reference_points.shape[-1] == 2 + reference_points_input = \ + reference_points[:, :, None] * valid_ratios[:, None] + + query_sine_embed = coordinate_to_encoding( + reference_points_input[:, :, 0, :], + num_feats=self.embed_dims // 2) + query_pos = self.ref_point_head(query_sine_embed) + + query = layer( + query, + query_pos=query_pos, + value=value, + key_padding_mask=key_padding_mask, + self_attn_mask=self_attn_mask, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reference_points=reference_points_input, + **kwargs) + + if not self.training: + tmp = reg_branches[layer_index](query) + assert reference_points.shape[-1] == 4 + new_reference_points = tmp + inverse_sigmoid( + reference_points, eps=1e-3) + new_reference_points = new_reference_points.sigmoid() + reference_points = new_reference_points.detach() + if layer_index < (len(self.layers) - 1): + self_attn_mask = self.select_distinct_queries( + reference_points, query, self_attn_mask, layer_index) + + else: + num_dense = self.cache_dict['num_dense_queries'] + tmp = reg_branches[layer_index](query[:, :-num_dense]) + tmp_dense = self.aux_reg_branches[layer_index]( + query[:, -num_dense:]) + + tmp = torch.cat([tmp, tmp_dense], dim=1) + assert reference_points.shape[-1] == 4 + new_reference_points = tmp + inverse_sigmoid( + reference_points, eps=1e-3) + new_reference_points = new_reference_points.sigmoid() + reference_points = new_reference_points.detach() + if layer_index < (len(self.layers) - 1): + self_attn_mask = self.select_distinct_queries( + reference_points, query, self_attn_mask, layer_index) + + if self.return_intermediate: + intermediate.append(self.norm(query)) + intermediate_reference_points.append(new_reference_points) + + if self.return_intermediate: + return torch.stack(intermediate), torch.stack( + intermediate_reference_points) + + return query, reference_points diff --git a/mmdetection/mmdet/models/layers/transformer/deformable_detr_layers.py b/mmdetection/mmdet/models/layers/transformer/deformable_detr_layers.py new file mode 100644 index 00000000..da6325d6 --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/deformable_detr_layers.py @@ -0,0 +1,265 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, Union + +import torch +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import FFN, MultiheadAttention +from mmcv.ops import MultiScaleDeformableAttention +from mmengine.model import ModuleList +from torch import Tensor, nn + +from .detr_layers import (DetrTransformerDecoder, DetrTransformerDecoderLayer, + DetrTransformerEncoder, DetrTransformerEncoderLayer) +from .utils import inverse_sigmoid + +try: + from fairscale.nn.checkpoint import checkpoint_wrapper +except Exception: + checkpoint_wrapper = None + + +class DeformableDetrTransformerEncoder(DetrTransformerEncoder): + """Transformer encoder of Deformable DETR.""" + + def _init_layers(self) -> None: + """Initialize encoder layers.""" + self.layers = ModuleList([ + DeformableDetrTransformerEncoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + + if self.num_cp > 0: + if checkpoint_wrapper is None: + raise NotImplementedError( + 'If you want to reduce GPU memory usage, \ + please install fairscale by executing the \ + following command: pip install fairscale.') + for i in range(self.num_cp): + self.layers[i] = checkpoint_wrapper(self.layers[i]) + + self.embed_dims = self.layers[0].embed_dims + + def forward(self, query: Tensor, query_pos: Tensor, + key_padding_mask: Tensor, spatial_shapes: Tensor, + level_start_index: Tensor, valid_ratios: Tensor, + **kwargs) -> Tensor: + """Forward function of Transformer encoder. + + Args: + query (Tensor): The input query, has shape (bs, num_queries, dim). + query_pos (Tensor): The positional encoding for query, has shape + (bs, num_queries, dim). + key_padding_mask (Tensor): The `key_padding_mask` of `self_attn` + input. ByteTensor, has shape (bs, num_queries). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape (num_levels, ) and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + + Returns: + Tensor: Output queries of Transformer encoder, which is also + called 'encoder output embeddings' or 'memory', has shape + (bs, num_queries, dim) + """ + reference_points = self.get_encoder_reference_points( + spatial_shapes, valid_ratios, device=query.device) + for layer in self.layers: + query = layer( + query=query, + query_pos=query_pos, + key_padding_mask=key_padding_mask, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reference_points=reference_points, + **kwargs) + return query + + @staticmethod + def get_encoder_reference_points( + spatial_shapes: Tensor, valid_ratios: Tensor, + device: Union[torch.device, str]) -> Tensor: + """Get the reference points used in encoder. + + Args: + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + device (obj:`device` or str): The device acquired by the + `reference_points`. + + Returns: + Tensor: Reference points used in decoder, has shape (bs, length, + num_levels, 2). + """ + + reference_points_list = [] + for lvl, (H, W) in enumerate(spatial_shapes): + ref_y, ref_x = torch.meshgrid( + torch.linspace( + 0.5, H - 0.5, H, dtype=torch.float32, device=device), + torch.linspace( + 0.5, W - 0.5, W, dtype=torch.float32, device=device)) + ref_y = ref_y.reshape(-1)[None] / ( + valid_ratios[:, None, lvl, 1] * H) + ref_x = ref_x.reshape(-1)[None] / ( + valid_ratios[:, None, lvl, 0] * W) + ref = torch.stack((ref_x, ref_y), -1) + reference_points_list.append(ref) + reference_points = torch.cat(reference_points_list, 1) + # [bs, sum(hw), num_level, 2] + reference_points = reference_points[:, :, None] * valid_ratios[:, None] + return reference_points + + +class DeformableDetrTransformerDecoder(DetrTransformerDecoder): + """Transformer Decoder of Deformable DETR.""" + + def _init_layers(self) -> None: + """Initialize decoder layers.""" + self.layers = ModuleList([ + DeformableDetrTransformerDecoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + self.embed_dims = self.layers[0].embed_dims + if self.post_norm_cfg is not None: + raise ValueError('There is not post_norm in ' + f'{self._get_name()}') + + def forward(self, + query: Tensor, + query_pos: Tensor, + value: Tensor, + key_padding_mask: Tensor, + reference_points: Tensor, + spatial_shapes: Tensor, + level_start_index: Tensor, + valid_ratios: Tensor, + reg_branches: Optional[nn.Module] = None, + **kwargs) -> Tuple[Tensor]: + """Forward function of Transformer decoder. + + Args: + query (Tensor): The input queries, has shape (bs, num_queries, + dim). + query_pos (Tensor): The input positional query, has shape + (bs, num_queries, dim). It will be added to `query` before + forward function. + value (Tensor): The input values, has shape (bs, num_value, dim). + key_padding_mask (Tensor): The `key_padding_mask` of `cross_attn` + input. ByteTensor, has shape (bs, num_value). + reference_points (Tensor): The initial reference, has shape + (bs, num_queries, 4) with the last dimension arranged as + (cx, cy, w, h) when `as_two_stage` is `True`, otherwise has + shape (bs, num_queries, 2) with the last dimension arranged + as (cx, cy). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape (num_levels, ) and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + reg_branches: (obj:`nn.ModuleList`, optional): Used for refining + the regression results. Only would be passed when + `with_box_refine` is `True`, otherwise would be `None`. + + Returns: + tuple[Tensor]: Outputs of Deformable Transformer Decoder. + + - output (Tensor): Output embeddings of the last decoder, has + shape (num_queries, bs, embed_dims) when `return_intermediate` + is `False`. Otherwise, Intermediate output embeddings of all + decoder layers, has shape (num_decoder_layers, num_queries, bs, + embed_dims). + - reference_points (Tensor): The reference of the last decoder + layer, has shape (bs, num_queries, 4) when `return_intermediate` + is `False`. Otherwise, Intermediate references of all decoder + layers, has shape (num_decoder_layers, bs, num_queries, 4). The + coordinates are arranged as (cx, cy, w, h) + """ + output = query + intermediate = [] + intermediate_reference_points = [] + for layer_id, layer in enumerate(self.layers): + if reference_points.shape[-1] == 4: + reference_points_input = \ + reference_points[:, :, None] * \ + torch.cat([valid_ratios, valid_ratios], -1)[:, None] + else: + assert reference_points.shape[-1] == 2 + reference_points_input = \ + reference_points[:, :, None] * \ + valid_ratios[:, None] + output = layer( + output, + query_pos=query_pos, + value=value, + key_padding_mask=key_padding_mask, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reference_points=reference_points_input, + **kwargs) + + if reg_branches is not None: + tmp_reg_preds = reg_branches[layer_id](output) + if reference_points.shape[-1] == 4: + new_reference_points = tmp_reg_preds + inverse_sigmoid( + reference_points) + new_reference_points = new_reference_points.sigmoid() + else: + assert reference_points.shape[-1] == 2 + new_reference_points = tmp_reg_preds + new_reference_points[..., :2] = tmp_reg_preds[ + ..., :2] + inverse_sigmoid(reference_points) + new_reference_points = new_reference_points.sigmoid() + reference_points = new_reference_points.detach() + + if self.return_intermediate: + intermediate.append(output) + intermediate_reference_points.append(reference_points) + + if self.return_intermediate: + return torch.stack(intermediate), torch.stack( + intermediate_reference_points) + + return output, reference_points + + +class DeformableDetrTransformerEncoderLayer(DetrTransformerEncoderLayer): + """Encoder layer of Deformable DETR.""" + + def _init_layers(self) -> None: + """Initialize self_attn, ffn, and norms.""" + self.self_attn = MultiScaleDeformableAttention(**self.self_attn_cfg) + self.embed_dims = self.self_attn.embed_dims + self.ffn = FFN(**self.ffn_cfg) + norms_list = [ + build_norm_layer(self.norm_cfg, self.embed_dims)[1] + for _ in range(2) + ] + self.norms = ModuleList(norms_list) + + +class DeformableDetrTransformerDecoderLayer(DetrTransformerDecoderLayer): + """Decoder layer of Deformable DETR.""" + + def _init_layers(self) -> None: + """Initialize self_attn, cross-attn, ffn, and norms.""" + self.self_attn = MultiheadAttention(**self.self_attn_cfg) + self.cross_attn = MultiScaleDeformableAttention(**self.cross_attn_cfg) + self.embed_dims = self.self_attn.embed_dims + self.ffn = FFN(**self.ffn_cfg) + norms_list = [ + build_norm_layer(self.norm_cfg, self.embed_dims)[1] + for _ in range(3) + ] + self.norms = ModuleList(norms_list) diff --git a/mmdetection/mmdet/models/layers/transformer/detr_layers.py b/mmdetection/mmdet/models/layers/transformer/detr_layers.py new file mode 100644 index 00000000..6a83dd2f --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/detr_layers.py @@ -0,0 +1,374 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Union + +import torch +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import FFN, MultiheadAttention +from mmengine import ConfigDict +from mmengine.model import BaseModule, ModuleList +from torch import Tensor + +from mmdet.utils import ConfigType, OptConfigType + +try: + from fairscale.nn.checkpoint import checkpoint_wrapper +except Exception: + checkpoint_wrapper = None + + +class DetrTransformerEncoder(BaseModule): + """Encoder of DETR. + + Args: + num_layers (int): Number of encoder layers. + layer_cfg (:obj:`ConfigDict` or dict): the config of each encoder + layer. All the layers will share the same config. + num_cp (int): Number of checkpointing blocks in encoder layer. + Default to -1. + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + num_layers: int, + layer_cfg: ConfigType, + num_cp: int = -1, + init_cfg: OptConfigType = None) -> None: + + super().__init__(init_cfg=init_cfg) + self.num_layers = num_layers + self.layer_cfg = layer_cfg + self.num_cp = num_cp + assert self.num_cp <= self.num_layers + self._init_layers() + + def _init_layers(self) -> None: + """Initialize encoder layers.""" + self.layers = ModuleList([ + DetrTransformerEncoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + + if self.num_cp > 0: + if checkpoint_wrapper is None: + raise NotImplementedError( + 'If you want to reduce GPU memory usage, \ + please install fairscale by executing the \ + following command: pip install fairscale.') + for i in range(self.num_cp): + self.layers[i] = checkpoint_wrapper(self.layers[i]) + + self.embed_dims = self.layers[0].embed_dims + + def forward(self, query: Tensor, query_pos: Tensor, + key_padding_mask: Tensor, **kwargs) -> Tensor: + """Forward function of encoder. + + Args: + query (Tensor): Input queries of encoder, has shape + (bs, num_queries, dim). + query_pos (Tensor): The positional embeddings of the queries, has + shape (bs, num_queries, dim). + key_padding_mask (Tensor): The `key_padding_mask` of `self_attn` + input. ByteTensor, has shape (bs, num_queries). + + Returns: + Tensor: Has shape (bs, num_queries, dim) if `batch_first` is + `True`, otherwise (num_queries, bs, dim). + """ + for layer in self.layers: + query = layer(query, query_pos, key_padding_mask, **kwargs) + return query + + +class DetrTransformerDecoder(BaseModule): + """Decoder of DETR. + + Args: + num_layers (int): Number of decoder layers. + layer_cfg (:obj:`ConfigDict` or dict): the config of each encoder + layer. All the layers will share the same config. + post_norm_cfg (:obj:`ConfigDict` or dict, optional): Config of the + post normalization layer. Defaults to `LN`. + return_intermediate (bool, optional): Whether to return outputs of + intermediate layers. Defaults to `True`, + init_cfg (:obj:`ConfigDict` or dict, optional): the config to control + the initialization. Defaults to None. + """ + + def __init__(self, + num_layers: int, + layer_cfg: ConfigType, + post_norm_cfg: OptConfigType = dict(type='LN'), + return_intermediate: bool = True, + init_cfg: Union[dict, ConfigDict] = None) -> None: + super().__init__(init_cfg=init_cfg) + self.layer_cfg = layer_cfg + self.num_layers = num_layers + self.post_norm_cfg = post_norm_cfg + self.return_intermediate = return_intermediate + self._init_layers() + + def _init_layers(self) -> None: + """Initialize decoder layers.""" + self.layers = ModuleList([ + DetrTransformerDecoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + self.embed_dims = self.layers[0].embed_dims + self.post_norm = build_norm_layer(self.post_norm_cfg, + self.embed_dims)[1] + + def forward(self, query: Tensor, key: Tensor, value: Tensor, + query_pos: Tensor, key_pos: Tensor, key_padding_mask: Tensor, + **kwargs) -> Tensor: + """Forward function of decoder + Args: + query (Tensor): The input query, has shape (bs, num_queries, dim). + key (Tensor): The input key, has shape (bs, num_keys, dim). + value (Tensor): The input value with the same shape as `key`. + query_pos (Tensor): The positional encoding for `query`, with the + same shape as `query`. + key_pos (Tensor): The positional encoding for `key`, with the + same shape as `key`. + key_padding_mask (Tensor): The `key_padding_mask` of `cross_attn` + input. ByteTensor, has shape (bs, num_value). + + Returns: + Tensor: The forwarded results will have shape + (num_decoder_layers, bs, num_queries, dim) if + `return_intermediate` is `True` else (1, bs, num_queries, dim). + """ + intermediate = [] + for layer in self.layers: + query = layer( + query, + key=key, + value=value, + query_pos=query_pos, + key_pos=key_pos, + key_padding_mask=key_padding_mask, + **kwargs) + if self.return_intermediate: + intermediate.append(self.post_norm(query)) + query = self.post_norm(query) + + if self.return_intermediate: + return torch.stack(intermediate) + + return query.unsqueeze(0) + + +class DetrTransformerEncoderLayer(BaseModule): + """Implements encoder layer in DETR transformer. + + Args: + self_attn_cfg (:obj:`ConfigDict` or dict, optional): Config for self + attention. + ffn_cfg (:obj:`ConfigDict` or dict, optional): Config for FFN. + norm_cfg (:obj:`ConfigDict` or dict, optional): Config for + normalization layers. All the layers will share the same + config. Defaults to `LN`. + init_cfg (:obj:`ConfigDict` or dict, optional): Config to control + the initialization. Defaults to None. + """ + + def __init__(self, + self_attn_cfg: OptConfigType = dict( + embed_dims=256, num_heads=8, dropout=0.0), + ffn_cfg: OptConfigType = dict( + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0., + act_cfg=dict(type='ReLU', inplace=True)), + norm_cfg: OptConfigType = dict(type='LN'), + init_cfg: OptConfigType = None) -> None: + + super().__init__(init_cfg=init_cfg) + + self.self_attn_cfg = self_attn_cfg + if 'batch_first' not in self.self_attn_cfg: + self.self_attn_cfg['batch_first'] = True + else: + assert self.self_attn_cfg['batch_first'] is True, 'First \ + dimension of all DETRs in mmdet is `batch`, \ + please set `batch_first` flag.' + + self.ffn_cfg = ffn_cfg + self.norm_cfg = norm_cfg + self._init_layers() + + def _init_layers(self) -> None: + """Initialize self-attention, FFN, and normalization.""" + self.self_attn = MultiheadAttention(**self.self_attn_cfg) + self.embed_dims = self.self_attn.embed_dims + self.ffn = FFN(**self.ffn_cfg) + norms_list = [ + build_norm_layer(self.norm_cfg, self.embed_dims)[1] + for _ in range(2) + ] + self.norms = ModuleList(norms_list) + + def forward(self, query: Tensor, query_pos: Tensor, + key_padding_mask: Tensor, **kwargs) -> Tensor: + """Forward function of an encoder layer. + + Args: + query (Tensor): The input query, has shape (bs, num_queries, dim). + query_pos (Tensor): The positional encoding for query, with + the same shape as `query`. + key_padding_mask (Tensor): The `key_padding_mask` of `self_attn` + input. ByteTensor. has shape (bs, num_queries). + Returns: + Tensor: forwarded results, has shape (bs, num_queries, dim). + """ + query = self.self_attn( + query=query, + key=query, + value=query, + query_pos=query_pos, + key_pos=query_pos, + key_padding_mask=key_padding_mask, + **kwargs) + query = self.norms[0](query) + query = self.ffn(query) + query = self.norms[1](query) + + return query + + +class DetrTransformerDecoderLayer(BaseModule): + """Implements decoder layer in DETR transformer. + + Args: + self_attn_cfg (:obj:`ConfigDict` or dict, optional): Config for self + attention. + cross_attn_cfg (:obj:`ConfigDict` or dict, optional): Config for cross + attention. + ffn_cfg (:obj:`ConfigDict` or dict, optional): Config for FFN. + norm_cfg (:obj:`ConfigDict` or dict, optional): Config for + normalization layers. All the layers will share the same + config. Defaults to `LN`. + init_cfg (:obj:`ConfigDict` or dict, optional): Config to control + the initialization. Defaults to None. + """ + + def __init__(self, + self_attn_cfg: OptConfigType = dict( + embed_dims=256, + num_heads=8, + dropout=0.0, + batch_first=True), + cross_attn_cfg: OptConfigType = dict( + embed_dims=256, + num_heads=8, + dropout=0.0, + batch_first=True), + ffn_cfg: OptConfigType = dict( + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0., + act_cfg=dict(type='ReLU', inplace=True), + ), + norm_cfg: OptConfigType = dict(type='LN'), + init_cfg: OptConfigType = None) -> None: + + super().__init__(init_cfg=init_cfg) + + self.self_attn_cfg = self_attn_cfg + self.cross_attn_cfg = cross_attn_cfg + if 'batch_first' not in self.self_attn_cfg: + self.self_attn_cfg['batch_first'] = True + else: + assert self.self_attn_cfg['batch_first'] is True, 'First \ + dimension of all DETRs in mmdet is `batch`, \ + please set `batch_first` flag.' + + if 'batch_first' not in self.cross_attn_cfg: + self.cross_attn_cfg['batch_first'] = True + else: + assert self.cross_attn_cfg['batch_first'] is True, 'First \ + dimension of all DETRs in mmdet is `batch`, \ + please set `batch_first` flag.' + + self.ffn_cfg = ffn_cfg + self.norm_cfg = norm_cfg + self._init_layers() + + def _init_layers(self) -> None: + """Initialize self-attention, FFN, and normalization.""" + self.self_attn = MultiheadAttention(**self.self_attn_cfg) + self.cross_attn = MultiheadAttention(**self.cross_attn_cfg) + self.embed_dims = self.self_attn.embed_dims + self.ffn = FFN(**self.ffn_cfg) + norms_list = [ + build_norm_layer(self.norm_cfg, self.embed_dims)[1] + for _ in range(3) + ] + self.norms = ModuleList(norms_list) + + def forward(self, + query: Tensor, + key: Tensor = None, + value: Tensor = None, + query_pos: Tensor = None, + key_pos: Tensor = None, + self_attn_mask: Tensor = None, + cross_attn_mask: Tensor = None, + key_padding_mask: Tensor = None, + **kwargs) -> Tensor: + """ + Args: + query (Tensor): The input query, has shape (bs, num_queries, dim). + key (Tensor, optional): The input key, has shape (bs, num_keys, + dim). If `None`, the `query` will be used. Defaults to `None`. + value (Tensor, optional): The input value, has the same shape as + `key`, as in `nn.MultiheadAttention.forward`. If `None`, the + `key` will be used. Defaults to `None`. + query_pos (Tensor, optional): The positional encoding for `query`, + has the same shape as `query`. If not `None`, it will be added + to `query` before forward function. Defaults to `None`. + key_pos (Tensor, optional): The positional encoding for `key`, has + the same shape as `key`. If not `None`, it will be added to + `key` before forward function. If None, and `query_pos` has the + same shape as `key`, then `query_pos` will be used for + `key_pos`. Defaults to None. + self_attn_mask (Tensor, optional): ByteTensor mask, has shape + (num_queries, num_keys), as in `nn.MultiheadAttention.forward`. + Defaults to None. + cross_attn_mask (Tensor, optional): ByteTensor mask, has shape + (num_queries, num_keys), as in `nn.MultiheadAttention.forward`. + Defaults to None. + key_padding_mask (Tensor, optional): The `key_padding_mask` of + `self_attn` input. ByteTensor, has shape (bs, num_value). + Defaults to None. + + Returns: + Tensor: forwarded results, has shape (bs, num_queries, dim). + """ + + query = self.self_attn( + query=query, + key=query, + value=query, + query_pos=query_pos, + key_pos=query_pos, + attn_mask=self_attn_mask, + **kwargs) + query = self.norms[0](query) + query = self.cross_attn( + query=query, + key=key, + value=value, + query_pos=query_pos, + key_pos=key_pos, + attn_mask=cross_attn_mask, + key_padding_mask=key_padding_mask, + **kwargs) + query = self.norms[1](query) + query = self.ffn(query) + query = self.norms[2](query) + + return query diff --git a/mmdetection/mmdet/models/layers/transformer/dino_layers.py b/mmdetection/mmdet/models/layers/transformer/dino_layers.py new file mode 100644 index 00000000..64610d0a --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/dino_layers.py @@ -0,0 +1,562 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import Tuple, Union + +import torch +from mmengine.model import BaseModule +from torch import Tensor, nn + +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox_xyxy_to_cxcywh +from mmdet.utils import OptConfigType +from .deformable_detr_layers import DeformableDetrTransformerDecoder +from .utils import MLP, coordinate_to_encoding, inverse_sigmoid + + +class DinoTransformerDecoder(DeformableDetrTransformerDecoder): + """Transformer decoder of DINO.""" + + def _init_layers(self) -> None: + """Initialize decoder layers.""" + super()._init_layers() + self.ref_point_head = MLP(self.embed_dims * 2, self.embed_dims, + self.embed_dims, 2) + self.norm = nn.LayerNorm(self.embed_dims) + + def forward(self, query: Tensor, value: Tensor, key_padding_mask: Tensor, + self_attn_mask: Tensor, reference_points: Tensor, + spatial_shapes: Tensor, level_start_index: Tensor, + valid_ratios: Tensor, reg_branches: nn.ModuleList, + **kwargs) -> Tuple[Tensor]: + """Forward function of Transformer decoder. + + Args: + query (Tensor): The input query, has shape (num_queries, bs, dim). + value (Tensor): The input values, has shape (num_value, bs, dim). + key_padding_mask (Tensor): The `key_padding_mask` of `self_attn` + input. ByteTensor, has shape (num_queries, bs). + self_attn_mask (Tensor): The attention mask to prevent information + leakage from different denoising groups and matching parts, has + shape (num_queries_total, num_queries_total). It is `None` when + `self.training` is `False`. + reference_points (Tensor): The initial reference, has shape + (bs, num_queries, 4) with the last dimension arranged as + (cx, cy, w, h). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape (num_levels, ) and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + reg_branches: (obj:`nn.ModuleList`): Used for refining the + regression results. + + Returns: + tuple[Tensor]: Output queries and references of Transformer + decoder + + - query (Tensor): Output embeddings of the last decoder, has + shape (num_queries, bs, embed_dims) when `return_intermediate` + is `False`. Otherwise, Intermediate output embeddings of all + decoder layers, has shape (num_decoder_layers, num_queries, bs, + embed_dims). + - reference_points (Tensor): The reference of the last decoder + layer, has shape (bs, num_queries, 4) when `return_intermediate` + is `False`. Otherwise, Intermediate references of all decoder + layers, has shape (num_decoder_layers, bs, num_queries, 4). The + coordinates are arranged as (cx, cy, w, h) + """ + intermediate = [] + intermediate_reference_points = [reference_points] + for lid, layer in enumerate(self.layers): + if reference_points.shape[-1] == 4: + reference_points_input = \ + reference_points[:, :, None] * torch.cat( + [valid_ratios, valid_ratios], -1)[:, None] + else: + assert reference_points.shape[-1] == 2 + reference_points_input = \ + reference_points[:, :, None] * valid_ratios[:, None] + + query_sine_embed = coordinate_to_encoding( + reference_points_input[:, :, 0, :]) + query_pos = self.ref_point_head(query_sine_embed) + + query = layer( + query, + query_pos=query_pos, + value=value, + key_padding_mask=key_padding_mask, + self_attn_mask=self_attn_mask, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reference_points=reference_points_input, + **kwargs) + + if reg_branches is not None: + tmp = reg_branches[lid](query) + assert reference_points.shape[-1] == 4 + new_reference_points = tmp + inverse_sigmoid( + reference_points, eps=1e-3) + new_reference_points = new_reference_points.sigmoid() + reference_points = new_reference_points.detach() + + if self.return_intermediate: + intermediate.append(self.norm(query)) + intermediate_reference_points.append(new_reference_points) + # NOTE this is for the "Look Forward Twice" module, + # in the DeformDETR, reference_points was appended. + + if self.return_intermediate: + return torch.stack(intermediate), torch.stack( + intermediate_reference_points) + + return query, reference_points + + +class CdnQueryGenerator(BaseModule): + """Implement query generator of the Contrastive denoising (CDN) proposed in + `DINO: DETR with Improved DeNoising Anchor Boxes for End-to-End Object + Detection `_ + + Code is modified from the `official github repo + `_. + + Args: + num_classes (int): Number of object classes. + embed_dims (int): The embedding dimensions of the generated queries. + num_matching_queries (int): The queries number of the matching part. + Used for generating dn_mask. + label_noise_scale (float): The scale of label noise, defaults to 0.5. + box_noise_scale (float): The scale of box noise, defaults to 1.0. + group_cfg (:obj:`ConfigDict` or dict, optional): The config of the + denoising queries grouping, includes `dynamic`, `num_dn_queries`, + and `num_groups`. Two grouping strategies, 'static dn groups' and + 'dynamic dn groups', are supported. When `dynamic` is `False`, + the `num_groups` should be set, and the number of denoising query + groups will always be `num_groups`. When `dynamic` is `True`, the + `num_dn_queries` should be set, and the group number will be + dynamic to ensure that the denoising queries number will not exceed + `num_dn_queries` to prevent large fluctuations of memory. Defaults + to `None`. + """ + + def __init__(self, + num_classes: int, + embed_dims: int, + num_matching_queries: int, + label_noise_scale: float = 0.5, + box_noise_scale: float = 1.0, + group_cfg: OptConfigType = None) -> None: + super().__init__() + self.num_classes = num_classes + self.embed_dims = embed_dims + self.num_matching_queries = num_matching_queries + self.label_noise_scale = label_noise_scale + self.box_noise_scale = box_noise_scale + + # prepare grouping strategy + group_cfg = {} if group_cfg is None else group_cfg + self.dynamic_dn_groups = group_cfg.get('dynamic', True) + if self.dynamic_dn_groups: + if 'num_dn_queries' not in group_cfg: + warnings.warn("'num_dn_queries' should be set when using " + 'dynamic dn groups, use 100 as default.') + self.num_dn_queries = group_cfg.get('num_dn_queries', 100) + assert isinstance(self.num_dn_queries, int), \ + f'Expected the num_dn_queries to have type int, but got ' \ + f'{self.num_dn_queries}({type(self.num_dn_queries)}). ' + else: + assert 'num_groups' in group_cfg, \ + 'num_groups should be set when using static dn groups' + self.num_groups = group_cfg['num_groups'] + assert isinstance(self.num_groups, int), \ + f'Expected the num_groups to have type int, but got ' \ + f'{self.num_groups}({type(self.num_groups)}). ' + + # NOTE The original repo of DINO set the num_embeddings 92 for coco, + # 91 (0~90) of which represents target classes and the 92 (91) + # indicates `Unknown` class. However, the embedding of `unknown` class + # is not used in the original DINO. + # TODO: num_classes + 1 or num_classes ? + self.label_embedding = nn.Embedding(self.num_classes, self.embed_dims) + + def __call__(self, batch_data_samples: SampleList) -> tuple: + """Generate contrastive denoising (cdn) queries with ground truth. + + Descriptions of the Number Values in code and comments: + - num_target_total: the total target number of the input batch + samples. + - max_num_target: the max target number of the input batch samples. + - num_noisy_targets: the total targets number after adding noise, + i.e., num_target_total * num_groups * 2. + - num_denoising_queries: the length of the output batched queries, + i.e., max_num_target * num_groups * 2. + + NOTE The format of input bboxes in batch_data_samples is unnormalized + (x, y, x, y), and the output bbox queries are embedded by normalized + (cx, cy, w, h) format bboxes going through inverse_sigmoid. + + Args: + batch_data_samples (list[:obj:`DetDataSample`]): List of the batch + data samples, each includes `gt_instance` which has attributes + `bboxes` and `labels`. The `bboxes` has unnormalized coordinate + format (x, y, x, y). + + Returns: + tuple: The outputs of the dn query generator. + + - dn_label_query (Tensor): The output content queries for denoising + part, has shape (bs, num_denoising_queries, dim), where + `num_denoising_queries = max_num_target * num_groups * 2`. + - dn_bbox_query (Tensor): The output reference bboxes as positions + of queries for denoising part, which are embedded by normalized + (cx, cy, w, h) format bboxes going through inverse_sigmoid, has + shape (bs, num_denoising_queries, 4) with the last dimension + arranged as (cx, cy, w, h). + - attn_mask (Tensor): The attention mask to prevent information + leakage from different denoising groups and matching parts, + will be used as `self_attn_mask` of the `decoder`, has shape + (num_queries_total, num_queries_total), where `num_queries_total` + is the sum of `num_denoising_queries` and `num_matching_queries`. + - dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + """ + # normalize bbox and collate ground truth (gt) + gt_labels_list = [] + gt_bboxes_list = [] + for sample in batch_data_samples: + img_h, img_w = sample.img_shape + bboxes = sample.gt_instances.bboxes + factor = bboxes.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0) + bboxes_normalized = bboxes / factor + gt_bboxes_list.append(bboxes_normalized) + gt_labels_list.append(sample.gt_instances.labels) + gt_labels = torch.cat(gt_labels_list) # (num_target_total, 4) + gt_bboxes = torch.cat(gt_bboxes_list) + + num_target_list = [len(bboxes) for bboxes in gt_bboxes_list] + max_num_target = max(num_target_list) + num_groups = self.get_num_groups(max_num_target) + + dn_label_query = self.generate_dn_label_query(gt_labels, num_groups) + dn_bbox_query = self.generate_dn_bbox_query(gt_bboxes, num_groups) + + # The `batch_idx` saves the batch index of the corresponding sample + # for each target, has shape (num_target_total). + batch_idx = torch.cat([ + torch.full_like(t.long(), i) for i, t in enumerate(gt_labels_list) + ]) + dn_label_query, dn_bbox_query = self.collate_dn_queries( + dn_label_query, dn_bbox_query, batch_idx, len(batch_data_samples), + num_groups) + + attn_mask = self.generate_dn_mask( + max_num_target, num_groups, device=dn_label_query.device) + + dn_meta = dict( + num_denoising_queries=int(max_num_target * 2 * num_groups), + num_denoising_groups=num_groups) + + return dn_label_query, dn_bbox_query, attn_mask, dn_meta + + def get_num_groups(self, max_num_target: int = None) -> int: + """Calculate denoising query groups number. + + Two grouping strategies, 'static dn groups' and 'dynamic dn groups', + are supported. When `self.dynamic_dn_groups` is `False`, the number + of denoising query groups will always be `self.num_groups`. When + `self.dynamic_dn_groups` is `True`, the group number will be dynamic, + ensuring the denoising queries number will not exceed + `self.num_dn_queries` to prevent large fluctuations of memory. + + NOTE The `num_group` is shared for different samples in a batch. When + the target numbers in the samples varies, the denoising queries of the + samples containing fewer targets are padded to the max length. + + Args: + max_num_target (int, optional): The max target number of the batch + samples. It will only be used when `self.dynamic_dn_groups` is + `True`. Defaults to `None`. + + Returns: + int: The denoising group number of the current batch. + """ + if self.dynamic_dn_groups: + assert max_num_target is not None, \ + 'group_queries should be provided when using ' \ + 'dynamic dn groups' + if max_num_target == 0: + num_groups = 1 + else: + num_groups = self.num_dn_queries // max_num_target + else: + num_groups = self.num_groups + if num_groups < 1: + num_groups = 1 + return int(num_groups) + + def generate_dn_label_query(self, gt_labels: Tensor, + num_groups: int) -> Tensor: + """Generate noisy labels and their query embeddings. + + The strategy for generating noisy labels is: Randomly choose labels of + `self.label_noise_scale * 0.5` proportion and override each of them + with a random object category label. + + NOTE Not add noise to all labels. Besides, the `self.label_noise_scale + * 0.5` arg is the ratio of the chosen positions, which is higher than + the actual proportion of noisy labels, because the labels to override + may be correct. And the gap becomes larger as the number of target + categories decreases. The users should notice this and modify the scale + arg or the corresponding logic according to specific dataset. + + Args: + gt_labels (Tensor): The concatenated gt labels of all samples + in the batch, has shape (num_target_total, ) where + `num_target_total = sum(num_target_list)`. + num_groups (int): The number of denoising query groups. + + Returns: + Tensor: The query embeddings of noisy labels, has shape + (num_noisy_targets, embed_dims), where `num_noisy_targets = + num_target_total * num_groups * 2`. + """ + assert self.label_noise_scale > 0 + gt_labels_expand = gt_labels.repeat(2 * num_groups, + 1).view(-1) # Note `* 2` # noqa + p = torch.rand_like(gt_labels_expand.float()) + chosen_indice = torch.nonzero(p < (self.label_noise_scale * 0.5)).view( + -1) # Note `* 0.5` + new_labels = torch.randint_like(chosen_indice, 0, self.num_classes) + noisy_labels_expand = gt_labels_expand.scatter(0, chosen_indice, + new_labels) + dn_label_query = self.label_embedding(noisy_labels_expand) + return dn_label_query + + def generate_dn_bbox_query(self, gt_bboxes: Tensor, + num_groups: int) -> Tensor: + """Generate noisy bboxes and their query embeddings. + + The strategy for generating noisy bboxes is as follow: + + .. code:: text + + +--------------------+ + | negative | + | +----------+ | + | | positive | | + | | +-----|----+------------+ + | | | | | | + | +----+-----+ | | + | | | | + +---------+----------+ | + | | + | gt bbox | + | | + | +---------+----------+ + | | | | + | | +----+-----+ | + | | | | | | + +-------------|--- +----+ | | + | | positive | | + | +----------+ | + | negative | + +--------------------+ + + The random noise is added to the top-left and down-right point + positions, hence, normalized (x, y, x, y) format of bboxes are + required. The noisy bboxes of positive queries have the points + both within the inner square, while those of negative queries + have the points both between the inner and outer squares. + + Besides, the length of outer square is twice as long as that of + the inner square, i.e., self.box_noise_scale * w_or_h / 2. + NOTE The noise is added to all the bboxes. Moreover, there is still + unconsidered case when one point is within the positive square and + the others is between the inner and outer squares. + + Args: + gt_bboxes (Tensor): The concatenated gt bboxes of all samples + in the batch, has shape (num_target_total, 4) with the last + dimension arranged as (cx, cy, w, h) where + `num_target_total = sum(num_target_list)`. + num_groups (int): The number of denoising query groups. + + Returns: + Tensor: The output noisy bboxes, which are embedded by normalized + (cx, cy, w, h) format bboxes going through inverse_sigmoid, has + shape (num_noisy_targets, 4) with the last dimension arranged as + (cx, cy, w, h), where + `num_noisy_targets = num_target_total * num_groups * 2`. + """ + assert self.box_noise_scale > 0 + device = gt_bboxes.device + + # expand gt_bboxes as groups + gt_bboxes_expand = gt_bboxes.repeat(2 * num_groups, 1) # xyxy + + # obtain index of negative queries in gt_bboxes_expand + positive_idx = torch.arange( + len(gt_bboxes), dtype=torch.long, device=device) + positive_idx = positive_idx.unsqueeze(0).repeat(num_groups, 1) + positive_idx += 2 * len(gt_bboxes) * torch.arange( + num_groups, dtype=torch.long, device=device)[:, None] + positive_idx = positive_idx.flatten() + negative_idx = positive_idx + len(gt_bboxes) + + # determine the sign of each element in the random part of the added + # noise to be positive or negative randomly. + rand_sign = torch.randint_like( + gt_bboxes_expand, low=0, high=2, + dtype=torch.float32) * 2.0 - 1.0 # [low, high), 1 or -1, randomly + + # calculate the random part of the added noise + rand_part = torch.rand_like(gt_bboxes_expand) # [0, 1) + rand_part[negative_idx] += 1.0 # pos: [0, 1); neg: [1, 2) + rand_part *= rand_sign # pos: (-1, 1); neg: (-2, -1] U [1, 2) + + # add noise to the bboxes + bboxes_whwh = bbox_xyxy_to_cxcywh(gt_bboxes_expand)[:, 2:].repeat(1, 2) + noisy_bboxes_expand = gt_bboxes_expand + torch.mul( + rand_part, bboxes_whwh) * self.box_noise_scale / 2 # xyxy + noisy_bboxes_expand = noisy_bboxes_expand.clamp(min=0.0, max=1.0) + noisy_bboxes_expand = bbox_xyxy_to_cxcywh(noisy_bboxes_expand) + + dn_bbox_query = inverse_sigmoid(noisy_bboxes_expand, eps=1e-3) + return dn_bbox_query + + def collate_dn_queries(self, input_label_query: Tensor, + input_bbox_query: Tensor, batch_idx: Tensor, + batch_size: int, num_groups: int) -> Tuple[Tensor]: + """Collate generated queries to obtain batched dn queries. + + The strategy for query collation is as follow: + + .. code:: text + + input_queries (num_target_total, query_dim) + P_A1 P_B1 P_B2 N_A1 N_B1 N_B2 P'A1 P'B1 P'B2 N'A1 N'B1 N'B2 + |________ group1 ________| |________ group2 ________| + | + V + P_A1 Pad0 N_A1 Pad0 P'A1 Pad0 N'A1 Pad0 + P_B1 P_B2 N_B1 N_B2 P'B1 P'B2 N'B1 N'B2 + |____ group1 ____| |____ group2 ____| + batched_queries (batch_size, max_num_target, query_dim) + + where query_dim is 4 for bbox and self.embed_dims for label. + Notation: _-group 1; '-group 2; + A-Sample1(has 1 target); B-sample2(has 2 targets) + + Args: + input_label_query (Tensor): The generated label queries of all + targets, has shape (num_target_total, embed_dims) where + `num_target_total = sum(num_target_list)`. + input_bbox_query (Tensor): The generated bbox queries of all + targets, has shape (num_target_total, 4) with the last + dimension arranged as (cx, cy, w, h). + batch_idx (Tensor): The batch index of the corresponding sample + for each target, has shape (num_target_total). + batch_size (int): The size of the input batch. + num_groups (int): The number of denoising query groups. + + Returns: + tuple[Tensor]: Output batched label and bbox queries. + - batched_label_query (Tensor): The output batched label queries, + has shape (batch_size, max_num_target, embed_dims). + - batched_bbox_query (Tensor): The output batched bbox queries, + has shape (batch_size, max_num_target, 4) with the last dimension + arranged as (cx, cy, w, h). + """ + device = input_label_query.device + num_target_list = [ + torch.sum(batch_idx == idx) for idx in range(batch_size) + ] + max_num_target = max(num_target_list) + num_denoising_queries = int(max_num_target * 2 * num_groups) + + map_query_index = torch.cat([ + torch.arange(num_target, device=device) + for num_target in num_target_list + ]) + map_query_index = torch.cat([ + map_query_index + max_num_target * i for i in range(2 * num_groups) + ]).long() + batch_idx_expand = batch_idx.repeat(2 * num_groups, 1).view(-1) + mapper = (batch_idx_expand, map_query_index) + + batched_label_query = torch.zeros( + batch_size, num_denoising_queries, self.embed_dims, device=device) + batched_bbox_query = torch.zeros( + batch_size, num_denoising_queries, 4, device=device) + + batched_label_query[mapper] = input_label_query + batched_bbox_query[mapper] = input_bbox_query + return batched_label_query, batched_bbox_query + + def generate_dn_mask(self, max_num_target: int, num_groups: int, + device: Union[torch.device, str]) -> Tensor: + """Generate attention mask to prevent information leakage from + different denoising groups and matching parts. + + .. code:: text + + 0 0 0 0 1 1 1 1 0 0 0 0 0 + 0 0 0 0 1 1 1 1 0 0 0 0 0 + 0 0 0 0 1 1 1 1 0 0 0 0 0 + 0 0 0 0 1 1 1 1 0 0 0 0 0 + 1 1 1 1 0 0 0 0 0 0 0 0 0 + 1 1 1 1 0 0 0 0 0 0 0 0 0 + 1 1 1 1 0 0 0 0 0 0 0 0 0 + 1 1 1 1 0 0 0 0 0 0 0 0 0 + 1 1 1 1 1 1 1 1 0 0 0 0 0 + 1 1 1 1 1 1 1 1 0 0 0 0 0 + 1 1 1 1 1 1 1 1 0 0 0 0 0 + 1 1 1 1 1 1 1 1 0 0 0 0 0 + 1 1 1 1 1 1 1 1 0 0 0 0 0 + max_num_target |_| |_________| num_matching_queries + |_____________| num_denoising_queries + + 1 -> True (Masked), means 'can not see'. + 0 -> False (UnMasked), means 'can see'. + + Args: + max_num_target (int): The max target number of the input batch + samples. + num_groups (int): The number of denoising query groups. + device (obj:`device` or str): The device of generated mask. + + Returns: + Tensor: The attention mask to prevent information leakage from + different denoising groups and matching parts, will be used as + `self_attn_mask` of the `decoder`, has shape (num_queries_total, + num_queries_total), where `num_queries_total` is the sum of + `num_denoising_queries` and `num_matching_queries`. + """ + num_denoising_queries = int(max_num_target * 2 * num_groups) + num_queries_total = num_denoising_queries + self.num_matching_queries + attn_mask = torch.zeros( + num_queries_total, + num_queries_total, + device=device, + dtype=torch.bool) + # Make the matching part cannot see the denoising groups + attn_mask[num_denoising_queries:, :num_denoising_queries] = True + # Make the denoising groups cannot see each other + for i in range(num_groups): + # Mask rows of one group per step. + row_scope = slice(max_num_target * 2 * i, + max_num_target * 2 * (i + 1)) + left_scope = slice(max_num_target * 2 * i) + right_scope = slice(max_num_target * 2 * (i + 1), + num_denoising_queries) + attn_mask[row_scope, right_scope] = True + attn_mask[row_scope, left_scope] = True + return attn_mask diff --git a/mmdetection/mmdet/models/layers/transformer/grounding_dino_layers.py b/mmdetection/mmdet/models/layers/transformer/grounding_dino_layers.py new file mode 100644 index 00000000..3c285768 --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/grounding_dino_layers.py @@ -0,0 +1,270 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import FFN, MultiheadAttention +from mmcv.ops import MultiScaleDeformableAttention +from mmengine.model import ModuleList +from torch import Tensor + +from mmdet.models.utils.vlfuse_helper import SingleScaleBiAttentionBlock +from mmdet.utils import ConfigType, OptConfigType +from .deformable_detr_layers import (DeformableDetrTransformerDecoderLayer, + DeformableDetrTransformerEncoder, + DeformableDetrTransformerEncoderLayer) +from .detr_layers import DetrTransformerEncoderLayer +from .dino_layers import DinoTransformerDecoder +from .utils import MLP, get_text_sine_pos_embed + +try: + from fairscale.nn.checkpoint import checkpoint_wrapper +except Exception: + checkpoint_wrapper = None + + +class GroundingDinoTransformerDecoderLayer( + DeformableDetrTransformerDecoderLayer): + + def __init__(self, + cross_attn_text_cfg: OptConfigType = dict( + embed_dims=256, + num_heads=8, + dropout=0.0, + batch_first=True), + **kwargs) -> None: + """Decoder layer of Deformable DETR.""" + self.cross_attn_text_cfg = cross_attn_text_cfg + if 'batch_first' not in self.cross_attn_text_cfg: + self.cross_attn_text_cfg['batch_first'] = True + super().__init__(**kwargs) + + def _init_layers(self) -> None: + """Initialize self_attn, cross-attn, ffn, and norms.""" + self.self_attn = MultiheadAttention(**self.self_attn_cfg) + self.cross_attn_text = MultiheadAttention(**self.cross_attn_text_cfg) + self.cross_attn = MultiScaleDeformableAttention(**self.cross_attn_cfg) + self.embed_dims = self.self_attn.embed_dims + self.ffn = FFN(**self.ffn_cfg) + norms_list = [ + build_norm_layer(self.norm_cfg, self.embed_dims)[1] + for _ in range(4) + ] + self.norms = ModuleList(norms_list) + + def forward(self, + query: Tensor, + key: Tensor = None, + value: Tensor = None, + query_pos: Tensor = None, + key_pos: Tensor = None, + self_attn_mask: Tensor = None, + cross_attn_mask: Tensor = None, + key_padding_mask: Tensor = None, + memory_text: Tensor = None, + text_attention_mask: Tensor = None, + **kwargs) -> Tensor: + """Implements decoder layer in Grounding DINO transformer. + + Args: + query (Tensor): The input query, has shape (bs, num_queries, dim). + key (Tensor, optional): The input key, has shape (bs, num_keys, + dim). If `None`, the `query` will be used. Defaults to `None`. + value (Tensor, optional): The input value, has the same shape as + `key`, as in `nn.MultiheadAttention.forward`. If `None`, the + `key` will be used. Defaults to `None`. + query_pos (Tensor, optional): The positional encoding for `query`, + has the same shape as `query`. If not `None`, it will be added + to `query` before forward function. Defaults to `None`. + key_pos (Tensor, optional): The positional encoding for `key`, has + the same shape as `key`. If not `None`, it will be added to + `key` before forward function. If None, and `query_pos` has the + same shape as `key`, then `query_pos` will be used for + `key_pos`. Defaults to None. + self_attn_mask (Tensor, optional): ByteTensor mask, has shape + (num_queries, num_keys), as in `nn.MultiheadAttention.forward`. + Defaults to None. + cross_attn_mask (Tensor, optional): ByteTensor mask, has shape + (num_queries, num_keys), as in `nn.MultiheadAttention.forward`. + Defaults to None. + key_padding_mask (Tensor, optional): The `key_padding_mask` of + `self_attn` input. ByteTensor, has shape (bs, num_value). + Defaults to None. + memory_text (Tensor): Memory text. It has shape (bs, len_text, + text_embed_dims). + text_attention_mask (Tensor): Text token mask. It has shape (bs, + len_text). + + Returns: + Tensor: forwarded results, has shape (bs, num_queries, dim). + """ + # self attention + query = self.self_attn( + query=query, + key=query, + value=query, + query_pos=query_pos, + key_pos=query_pos, + attn_mask=self_attn_mask, + **kwargs) + query = self.norms[0](query) + # cross attention between query and text + query = self.cross_attn_text( + query=query, + query_pos=query_pos, + key=memory_text, + value=memory_text, + key_padding_mask=text_attention_mask) + query = self.norms[1](query) + # cross attention between query and image + query = self.cross_attn( + query=query, + key=key, + value=value, + query_pos=query_pos, + key_pos=key_pos, + attn_mask=cross_attn_mask, + key_padding_mask=key_padding_mask, + **kwargs) + query = self.norms[2](query) + query = self.ffn(query) + query = self.norms[3](query) + + return query + + +class GroundingDinoTransformerEncoder(DeformableDetrTransformerEncoder): + + def __init__(self, text_layer_cfg: ConfigType, + fusion_layer_cfg: ConfigType, **kwargs) -> None: + self.text_layer_cfg = text_layer_cfg + self.fusion_layer_cfg = fusion_layer_cfg + super().__init__(**kwargs) + + def _init_layers(self) -> None: + """Initialize encoder layers.""" + self.layers = ModuleList([ + DeformableDetrTransformerEncoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + self.text_layers = ModuleList([ + DetrTransformerEncoderLayer(**self.text_layer_cfg) + for _ in range(self.num_layers) + ]) + self.fusion_layers = ModuleList([ + SingleScaleBiAttentionBlock(**self.fusion_layer_cfg) + for _ in range(self.num_layers) + ]) + self.embed_dims = self.layers[0].embed_dims + if self.num_cp > 0: + if checkpoint_wrapper is None: + raise NotImplementedError( + 'If you want to reduce GPU memory usage, \ + please install fairscale by executing the \ + following command: pip install fairscale.') + for i in range(self.num_cp): + self.layers[i] = checkpoint_wrapper(self.layers[i]) + self.fusion_layers[i] = checkpoint_wrapper( + self.fusion_layers[i]) + + def forward(self, + query: Tensor, + query_pos: Tensor, + key_padding_mask: Tensor, + spatial_shapes: Tensor, + level_start_index: Tensor, + valid_ratios: Tensor, + memory_text: Tensor = None, + text_attention_mask: Tensor = None, + pos_text: Tensor = None, + text_self_attention_masks: Tensor = None, + position_ids: Tensor = None): + """Forward function of Transformer encoder. + + Args: + query (Tensor): The input query, has shape (bs, num_queries, dim). + query_pos (Tensor): The positional encoding for query, has shape + (bs, num_queries, dim). + key_padding_mask (Tensor): The `key_padding_mask` of `self_attn` + input. ByteTensor, has shape (bs, num_queries). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape (num_levels, ) and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + memory_text (Tensor, optional): Memory text. It has shape (bs, + len_text, text_embed_dims). + text_attention_mask (Tensor, optional): Text token mask. It has + shape (bs,len_text). + pos_text (Tensor, optional): The positional encoding for text. + Defaults to None. + text_self_attention_masks (Tensor, optional): Text self attention + mask. Defaults to None. + position_ids (Tensor, optional): Text position ids. + Defaults to None. + """ + output = query + reference_points = self.get_encoder_reference_points( + spatial_shapes, valid_ratios, device=query.device) + if self.text_layers: + # generate pos_text + bs, n_text, _ = memory_text.shape + if pos_text is None and position_ids is None: + pos_text = ( + torch.arange(n_text, + device=memory_text.device).float().unsqueeze( + 0).unsqueeze(-1).repeat(bs, 1, 1)) + pos_text = get_text_sine_pos_embed( + pos_text, num_pos_feats=256, exchange_xy=False) + if position_ids is not None: + pos_text = get_text_sine_pos_embed( + position_ids[..., None], + num_pos_feats=256, + exchange_xy=False) + + # main process + for layer_id, layer in enumerate(self.layers): + if self.fusion_layers: + output, memory_text = self.fusion_layers[layer_id]( + visual_feature=output, + lang_feature=memory_text, + attention_mask_v=key_padding_mask, + attention_mask_l=text_attention_mask, + ) + if self.text_layers: + text_num_heads = self.text_layers[ + layer_id].self_attn_cfg.num_heads + memory_text = self.text_layers[layer_id]( + query=memory_text, + query_pos=(pos_text if pos_text is not None else None), + attn_mask=~text_self_attention_masks.repeat( + text_num_heads, 1, 1), # note we use ~ for mask here + key_padding_mask=None, + ) + output = layer( + query=output, + query_pos=query_pos, + reference_points=reference_points, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + key_padding_mask=key_padding_mask) + return output, memory_text + + +class GroundingDinoTransformerDecoder(DinoTransformerDecoder): + + def _init_layers(self) -> None: + """Initialize decoder layers.""" + self.layers = ModuleList([ + GroundingDinoTransformerDecoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + self.embed_dims = self.layers[0].embed_dims + if self.post_norm_cfg is not None: + raise ValueError('There is not post_norm in ' + f'{self._get_name()}') + self.ref_point_head = MLP(self.embed_dims * 2, self.embed_dims, + self.embed_dims, 2) + self.norm = nn.LayerNorm(self.embed_dims) diff --git a/mmdetection/mmdet/models/layers/transformer/mask2former_layers.py b/mmdetection/mmdet/models/layers/transformer/mask2former_layers.py new file mode 100644 index 00000000..dcc604e2 --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/mask2former_layers.py @@ -0,0 +1,135 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.cnn import build_norm_layer +from mmengine.model import ModuleList +from torch import Tensor + +from .deformable_detr_layers import DeformableDetrTransformerEncoder +from .detr_layers import DetrTransformerDecoder, DetrTransformerDecoderLayer + + +class Mask2FormerTransformerEncoder(DeformableDetrTransformerEncoder): + """Encoder in PixelDecoder of Mask2Former.""" + + def forward(self, query: Tensor, query_pos: Tensor, + key_padding_mask: Tensor, spatial_shapes: Tensor, + level_start_index: Tensor, valid_ratios: Tensor, + reference_points: Tensor, **kwargs) -> Tensor: + """Forward function of Transformer encoder. + + Args: + query (Tensor): The input query, has shape (bs, num_queries, dim). + query_pos (Tensor): The positional encoding for query, has shape + (bs, num_queries, dim). If not None, it will be added to the + `query` before forward function. Defaults to None. + key_padding_mask (Tensor): The `key_padding_mask` of `self_attn` + input. ByteTensor, has shape (bs, num_queries). + spatial_shapes (Tensor): Spatial shapes of features in all levels, + has shape (num_levels, 2), last dimension represents (h, w). + level_start_index (Tensor): The start index of each level. + A tensor has shape (num_levels, ) and can be represented + as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...]. + valid_ratios (Tensor): The ratios of the valid width and the valid + height relative to the width and the height of features in all + levels, has shape (bs, num_levels, 2). + reference_points (Tensor): The initial reference, has shape + (bs, num_queries, 2) with the last dimension arranged + as (cx, cy). + + Returns: + Tensor: Output queries of Transformer encoder, which is also + called 'encoder output embeddings' or 'memory', has shape + (bs, num_queries, dim) + """ + for layer in self.layers: + query = layer( + query=query, + query_pos=query_pos, + key_padding_mask=key_padding_mask, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reference_points=reference_points, + **kwargs) + return query + + +class Mask2FormerTransformerDecoder(DetrTransformerDecoder): + """Decoder of Mask2Former.""" + + def _init_layers(self) -> None: + """Initialize decoder layers.""" + self.layers = ModuleList([ + Mask2FormerTransformerDecoderLayer(**self.layer_cfg) + for _ in range(self.num_layers) + ]) + self.embed_dims = self.layers[0].embed_dims + self.post_norm = build_norm_layer(self.post_norm_cfg, + self.embed_dims)[1] + + +class Mask2FormerTransformerDecoderLayer(DetrTransformerDecoderLayer): + """Implements decoder layer in Mask2Former transformer.""" + + def forward(self, + query: Tensor, + key: Tensor = None, + value: Tensor = None, + query_pos: Tensor = None, + key_pos: Tensor = None, + self_attn_mask: Tensor = None, + cross_attn_mask: Tensor = None, + key_padding_mask: Tensor = None, + **kwargs) -> Tensor: + """ + Args: + query (Tensor): The input query, has shape (bs, num_queries, dim). + key (Tensor, optional): The input key, has shape (bs, num_keys, + dim). If `None`, the `query` will be used. Defaults to `None`. + value (Tensor, optional): The input value, has the same shape as + `key`, as in `nn.MultiheadAttention.forward`. If `None`, the + `key` will be used. Defaults to `None`. + query_pos (Tensor, optional): The positional encoding for `query`, + has the same shape as `query`. If not `None`, it will be added + to `query` before forward function. Defaults to `None`. + key_pos (Tensor, optional): The positional encoding for `key`, has + the same shape as `key`. If not `None`, it will be added to + `key` before forward function. If None, and `query_pos` has the + same shape as `key`, then `query_pos` will be used for + `key_pos`. Defaults to None. + self_attn_mask (Tensor, optional): ByteTensor mask, has shape + (num_queries, num_keys), as in `nn.MultiheadAttention.forward`. + Defaults to None. + cross_attn_mask (Tensor, optional): ByteTensor mask, has shape + (num_queries, num_keys), as in `nn.MultiheadAttention.forward`. + Defaults to None. + key_padding_mask (Tensor, optional): The `key_padding_mask` of + `self_attn` input. ByteTensor, has shape (bs, num_value). + Defaults to None. + + Returns: + Tensor: forwarded results, has shape (bs, num_queries, dim). + """ + + query = self.cross_attn( + query=query, + key=key, + value=value, + query_pos=query_pos, + key_pos=key_pos, + attn_mask=cross_attn_mask, + key_padding_mask=key_padding_mask, + **kwargs) + query = self.norms[0](query) + query = self.self_attn( + query=query, + key=query, + value=query, + query_pos=query_pos, + key_pos=query_pos, + attn_mask=self_attn_mask, + **kwargs) + query = self.norms[1](query) + query = self.ffn(query) + query = self.norms[2](query) + + return query diff --git a/mmdetection/mmdet/models/layers/transformer/utils.py b/mmdetection/mmdet/models/layers/transformer/utils.py new file mode 100644 index 00000000..6e43a172 --- /dev/null +++ b/mmdetection/mmdet/models/layers/transformer/utils.py @@ -0,0 +1,915 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +import warnings +from typing import Optional, Sequence, Tuple, Union + +import torch +import torch.nn.functional as F +from mmcv.cnn import (Linear, build_activation_layer, build_conv_layer, + build_norm_layer) +from mmcv.cnn.bricks.drop import Dropout +from mmengine.model import BaseModule, ModuleList +from mmengine.utils import to_2tuple +from torch import Tensor, nn + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType, OptMultiConfig + + +def nlc_to_nchw(x: Tensor, hw_shape: Sequence[int]) -> Tensor: + """Convert [N, L, C] shape tensor to [N, C, H, W] shape tensor. + + Args: + x (Tensor): The input tensor of shape [N, L, C] before conversion. + hw_shape (Sequence[int]): The height and width of output feature map. + + Returns: + Tensor: The output tensor of shape [N, C, H, W] after conversion. + """ + H, W = hw_shape + assert len(x.shape) == 3 + B, L, C = x.shape + assert L == H * W, 'The seq_len does not match H, W' + return x.transpose(1, 2).reshape(B, C, H, W).contiguous() + + +def nchw_to_nlc(x): + """Flatten [N, C, H, W] shape tensor to [N, L, C] shape tensor. + + Args: + x (Tensor): The input tensor of shape [N, C, H, W] before conversion. + + Returns: + Tensor: The output tensor of shape [N, L, C] after conversion. + """ + assert len(x.shape) == 4 + return x.flatten(2).transpose(1, 2).contiguous() + + +def coordinate_to_encoding(coord_tensor: Tensor, + num_feats: int = 128, + temperature: int = 10000, + scale: float = 2 * math.pi): + """Convert coordinate tensor to positional encoding. + + Args: + coord_tensor (Tensor): Coordinate tensor to be converted to + positional encoding. With the last dimension as 2 or 4. + num_feats (int, optional): The feature dimension for each position + along x-axis or y-axis. Note the final returned dimension + for each position is 2 times of this value. Defaults to 128. + temperature (int, optional): The temperature used for scaling + the position embedding. Defaults to 10000. + scale (float, optional): A scale factor that scales the position + embedding. The scale will be used only when `normalize` is True. + Defaults to 2*pi. + Returns: + Tensor: Returned encoded positional tensor. + """ + dim_t = torch.arange( + num_feats, dtype=torch.float32, device=coord_tensor.device) + dim_t = temperature**(2 * (dim_t // 2) / num_feats) + x_embed = coord_tensor[..., 0] * scale + y_embed = coord_tensor[..., 1] * scale + pos_x = x_embed[..., None] / dim_t + pos_y = y_embed[..., None] / dim_t + pos_x = torch.stack((pos_x[..., 0::2].sin(), pos_x[..., 1::2].cos()), + dim=-1).flatten(2) + pos_y = torch.stack((pos_y[..., 0::2].sin(), pos_y[..., 1::2].cos()), + dim=-1).flatten(2) + if coord_tensor.size(-1) == 2: + pos = torch.cat((pos_y, pos_x), dim=-1) + elif coord_tensor.size(-1) == 4: + w_embed = coord_tensor[..., 2] * scale + pos_w = w_embed[..., None] / dim_t + pos_w = torch.stack((pos_w[..., 0::2].sin(), pos_w[..., 1::2].cos()), + dim=-1).flatten(2) + + h_embed = coord_tensor[..., 3] * scale + pos_h = h_embed[..., None] / dim_t + pos_h = torch.stack((pos_h[..., 0::2].sin(), pos_h[..., 1::2].cos()), + dim=-1).flatten(2) + + pos = torch.cat((pos_y, pos_x, pos_w, pos_h), dim=-1) + else: + raise ValueError('Unknown pos_tensor shape(-1):{}'.format( + coord_tensor.size(-1))) + return pos + + +def inverse_sigmoid(x: Tensor, eps: float = 1e-5) -> Tensor: + """Inverse function of sigmoid. + + Args: + x (Tensor): The tensor to do the inverse. + eps (float): EPS avoid numerical overflow. Defaults 1e-5. + Returns: + Tensor: The x has passed the inverse function of sigmoid, has the same + shape with input. + """ + x = x.clamp(min=0, max=1) + x1 = x.clamp(min=eps) + x2 = (1 - x).clamp(min=eps) + return torch.log(x1 / x2) + + +class AdaptivePadding(nn.Module): + """Applies padding to input (if needed) so that input can get fully covered + by filter you specified. It support two modes "same" and "corner". The + "same" mode is same with "SAME" padding mode in TensorFlow, pad zero around + input. The "corner" mode would pad zero to bottom right. + + Args: + kernel_size (int | tuple): Size of the kernel: + stride (int | tuple): Stride of the filter. Default: 1: + dilation (int | tuple): Spacing between kernel elements. + Default: 1 + padding (str): Support "same" and "corner", "corner" mode + would pad zero to bottom right, and "same" mode would + pad zero around input. Default: "corner". + Example: + >>> kernel_size = 16 + >>> stride = 16 + >>> dilation = 1 + >>> input = torch.rand(1, 1, 15, 17) + >>> adap_pad = AdaptivePadding( + >>> kernel_size=kernel_size, + >>> stride=stride, + >>> dilation=dilation, + >>> padding="corner") + >>> out = adap_pad(input) + >>> assert (out.shape[2], out.shape[3]) == (16, 32) + >>> input = torch.rand(1, 1, 16, 17) + >>> out = adap_pad(input) + >>> assert (out.shape[2], out.shape[3]) == (16, 32) + """ + + def __init__(self, kernel_size=1, stride=1, dilation=1, padding='corner'): + + super(AdaptivePadding, self).__init__() + + assert padding in ('same', 'corner') + + kernel_size = to_2tuple(kernel_size) + stride = to_2tuple(stride) + padding = to_2tuple(padding) + dilation = to_2tuple(dilation) + + self.padding = padding + self.kernel_size = kernel_size + self.stride = stride + self.dilation = dilation + + def get_pad_shape(self, input_shape): + input_h, input_w = input_shape + kernel_h, kernel_w = self.kernel_size + stride_h, stride_w = self.stride + output_h = math.ceil(input_h / stride_h) + output_w = math.ceil(input_w / stride_w) + pad_h = max((output_h - 1) * stride_h + + (kernel_h - 1) * self.dilation[0] + 1 - input_h, 0) + pad_w = max((output_w - 1) * stride_w + + (kernel_w - 1) * self.dilation[1] + 1 - input_w, 0) + return pad_h, pad_w + + def forward(self, x): + pad_h, pad_w = self.get_pad_shape(x.size()[-2:]) + if pad_h > 0 or pad_w > 0: + if self.padding == 'corner': + x = F.pad(x, [0, pad_w, 0, pad_h]) + elif self.padding == 'same': + x = F.pad(x, [ + pad_w // 2, pad_w - pad_w // 2, pad_h // 2, + pad_h - pad_h // 2 + ]) + return x + + +class PatchEmbed(BaseModule): + """Image to Patch Embedding. + + We use a conv layer to implement PatchEmbed. + + Args: + in_channels (int): The num of input channels. Default: 3 + embed_dims (int): The dimensions of embedding. Default: 768 + conv_type (str): The config dict for embedding + conv layer type selection. Default: "Conv2d. + kernel_size (int): The kernel_size of embedding conv. Default: 16. + stride (int): The slide stride of embedding conv. + Default: None (Would be set as `kernel_size`). + padding (int | tuple | string ): The padding length of + embedding conv. When it is a string, it means the mode + of adaptive padding, support "same" and "corner" now. + Default: "corner". + dilation (int): The dilation rate of embedding conv. Default: 1. + bias (bool): Bias of embed conv. Default: True. + norm_cfg (dict, optional): Config dict for normalization layer. + Default: None. + input_size (int | tuple | None): The size of input, which will be + used to calculate the out size. Only work when `dynamic_size` + is False. Default: None. + init_cfg (`mmengine.ConfigDict`, optional): The Config for + initialization. Default: None. + """ + + def __init__(self, + in_channels: int = 3, + embed_dims: int = 768, + conv_type: str = 'Conv2d', + kernel_size: int = 16, + stride: int = 16, + padding: Union[int, tuple, str] = 'corner', + dilation: int = 1, + bias: bool = True, + norm_cfg: OptConfigType = None, + input_size: Union[int, tuple] = None, + init_cfg: OptConfigType = None) -> None: + super(PatchEmbed, self).__init__(init_cfg=init_cfg) + + self.embed_dims = embed_dims + if stride is None: + stride = kernel_size + + kernel_size = to_2tuple(kernel_size) + stride = to_2tuple(stride) + dilation = to_2tuple(dilation) + + if isinstance(padding, str): + self.adap_padding = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + # disable the padding of conv + padding = 0 + else: + self.adap_padding = None + padding = to_2tuple(padding) + + self.projection = build_conv_layer( + dict(type=conv_type), + in_channels=in_channels, + out_channels=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + if norm_cfg is not None: + self.norm = build_norm_layer(norm_cfg, embed_dims)[1] + else: + self.norm = None + + if input_size: + input_size = to_2tuple(input_size) + # `init_out_size` would be used outside to + # calculate the num_patches + # when `use_abs_pos_embed` outside + self.init_input_size = input_size + if self.adap_padding: + pad_h, pad_w = self.adap_padding.get_pad_shape(input_size) + input_h, input_w = input_size + input_h = input_h + pad_h + input_w = input_w + pad_w + input_size = (input_h, input_w) + + # https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html + h_out = (input_size[0] + 2 * padding[0] - dilation[0] * + (kernel_size[0] - 1) - 1) // stride[0] + 1 + w_out = (input_size[1] + 2 * padding[1] - dilation[1] * + (kernel_size[1] - 1) - 1) // stride[1] + 1 + self.init_out_size = (h_out, w_out) + else: + self.init_input_size = None + self.init_out_size = None + + def forward(self, x: Tensor) -> Tuple[Tensor, Tuple[int]]: + """ + Args: + x (Tensor): Has shape (B, C, H, W). In most case, C is 3. + + Returns: + tuple: Contains merged results and its spatial shape. + + - x (Tensor): Has shape (B, out_h * out_w, embed_dims) + - out_size (tuple[int]): Spatial shape of x, arrange as + (out_h, out_w). + """ + + if self.adap_padding: + x = self.adap_padding(x) + + x = self.projection(x) + out_size = (x.shape[2], x.shape[3]) + x = x.flatten(2).transpose(1, 2) + if self.norm is not None: + x = self.norm(x) + return x, out_size + + +class PatchMerging(BaseModule): + """Merge patch feature map. + + This layer groups feature map by kernel_size, and applies norm and linear + layers to the grouped feature map. Our implementation uses `nn.Unfold` to + merge patch, which is about 25% faster than original implementation. + Instead, we need to modify pretrained models for compatibility. + + Args: + in_channels (int): The num of input channels. + to gets fully covered by filter and stride you specified.. + Default: True. + out_channels (int): The num of output channels. + kernel_size (int | tuple, optional): the kernel size in the unfold + layer. Defaults to 2. + stride (int | tuple, optional): the stride of the sliding blocks in the + unfold layer. Default: None. (Would be set as `kernel_size`) + padding (int | tuple | string ): The padding length of + embedding conv. When it is a string, it means the mode + of adaptive padding, support "same" and "corner" now. + Default: "corner". + dilation (int | tuple, optional): dilation parameter in the unfold + layer. Default: 1. + bias (bool, optional): Whether to add bias in linear layer or not. + Defaults: False. + norm_cfg (dict, optional): Config dict for normalization layer. + Default: dict(type='LN'). + init_cfg (dict, optional): The extra config for initialization. + Default: None. + """ + + def __init__(self, + in_channels: int, + out_channels: int, + kernel_size: Optional[Union[int, tuple]] = 2, + stride: Optional[Union[int, tuple]] = None, + padding: Union[int, tuple, str] = 'corner', + dilation: Optional[Union[int, tuple]] = 1, + bias: Optional[bool] = False, + norm_cfg: OptConfigType = dict(type='LN'), + init_cfg: OptConfigType = None) -> None: + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + if stride: + stride = stride + else: + stride = kernel_size + + kernel_size = to_2tuple(kernel_size) + stride = to_2tuple(stride) + dilation = to_2tuple(dilation) + + if isinstance(padding, str): + self.adap_padding = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + # disable the padding of unfold + padding = 0 + else: + self.adap_padding = None + + padding = to_2tuple(padding) + self.sampler = nn.Unfold( + kernel_size=kernel_size, + dilation=dilation, + padding=padding, + stride=stride) + + sample_dim = kernel_size[0] * kernel_size[1] * in_channels + + if norm_cfg is not None: + self.norm = build_norm_layer(norm_cfg, sample_dim)[1] + else: + self.norm = None + + self.reduction = nn.Linear(sample_dim, out_channels, bias=bias) + + def forward(self, x: Tensor, + input_size: Tuple[int]) -> Tuple[Tensor, Tuple[int]]: + """ + Args: + x (Tensor): Has shape (B, H*W, C_in). + input_size (tuple[int]): The spatial shape of x, arrange as (H, W). + Default: None. + + Returns: + tuple: Contains merged results and its spatial shape. + + - x (Tensor): Has shape (B, Merged_H * Merged_W, C_out) + - out_size (tuple[int]): Spatial shape of x, arrange as + (Merged_H, Merged_W). + """ + B, L, C = x.shape + assert isinstance(input_size, Sequence), f'Expect ' \ + f'input_size is ' \ + f'`Sequence` ' \ + f'but get {input_size}' + + H, W = input_size + assert L == H * W, 'input feature has wrong size' + + x = x.view(B, H, W, C).permute([0, 3, 1, 2]) # B, C, H, W + # Use nn.Unfold to merge patch. About 25% faster than original method, + # but need to modify pretrained model for compatibility + + if self.adap_padding: + x = self.adap_padding(x) + H, W = x.shape[-2:] + + x = self.sampler(x) + # if kernel_size=2 and stride=2, x should has shape (B, 4*C, H/2*W/2) + + out_h = (H + 2 * self.sampler.padding[0] - self.sampler.dilation[0] * + (self.sampler.kernel_size[0] - 1) - + 1) // self.sampler.stride[0] + 1 + out_w = (W + 2 * self.sampler.padding[1] - self.sampler.dilation[1] * + (self.sampler.kernel_size[1] - 1) - + 1) // self.sampler.stride[1] + 1 + + output_size = (out_h, out_w) + x = x.transpose(1, 2) # B, H/2*W/2, 4*C + x = self.norm(x) if self.norm else x + x = self.reduction(x) + return x, output_size + + +class ConditionalAttention(BaseModule): + """A wrapper of conditional attention, dropout and residual connection. + + Args: + embed_dims (int): The embedding dimension. + num_heads (int): Parallel attention heads. + attn_drop (float): A Dropout layer on attn_output_weights. + Default: 0.0. + proj_drop: A Dropout layer after `nn.MultiheadAttention`. + Default: 0.0. + cross_attn (bool): Whether the attention module is for cross attention. + Default: False + keep_query_pos (bool): Whether to transform query_pos before cross + attention. + Default: False. + batch_first (bool): When it is True, Key, Query and Value are shape of + (batch, n, embed_dim), otherwise (n, batch, embed_dim). + Default: True. + init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims: int, + num_heads: int, + attn_drop: float = 0., + proj_drop: float = 0., + cross_attn: bool = False, + keep_query_pos: bool = False, + batch_first: bool = True, + init_cfg: OptMultiConfig = None): + super().__init__(init_cfg=init_cfg) + + assert batch_first is True, 'Set `batch_first`\ + to False is NOT supported in ConditionalAttention. \ + First dimension of all DETRs in mmdet is `batch`, \ + please set `batch_first` to True.' + + self.cross_attn = cross_attn + self.keep_query_pos = keep_query_pos + self.embed_dims = embed_dims + self.num_heads = num_heads + self.attn_drop = Dropout(attn_drop) + self.proj_drop = Dropout(proj_drop) + + self._init_layers() + + def _init_layers(self): + """Initialize layers for qkv projection.""" + embed_dims = self.embed_dims + self.qcontent_proj = Linear(embed_dims, embed_dims) + self.qpos_proj = Linear(embed_dims, embed_dims) + self.kcontent_proj = Linear(embed_dims, embed_dims) + self.kpos_proj = Linear(embed_dims, embed_dims) + self.v_proj = Linear(embed_dims, embed_dims) + if self.cross_attn: + self.qpos_sine_proj = Linear(embed_dims, embed_dims) + self.out_proj = Linear(embed_dims, embed_dims) + + nn.init.constant_(self.out_proj.bias, 0.) + + def forward_attn(self, + query: Tensor, + key: Tensor, + value: Tensor, + attn_mask: Tensor = None, + key_padding_mask: Tensor = None) -> Tuple[Tensor]: + """Forward process for `ConditionalAttention`. + + Args: + query (Tensor): The input query with shape [bs, num_queries, + embed_dims]. + key (Tensor): The key tensor with shape [bs, num_keys, + embed_dims]. + If None, the `query` will be used. Defaults to None. + value (Tensor): The value tensor with same shape as `key`. + Same in `nn.MultiheadAttention.forward`. Defaults to None. + If None, the `key` will be used. + attn_mask (Tensor): ByteTensor mask with shape [num_queries, + num_keys]. Same in `nn.MultiheadAttention.forward`. + Defaults to None. + key_padding_mask (Tensor): ByteTensor with shape [bs, num_keys]. + Defaults to None. + Returns: + Tuple[Tensor]: Attention outputs of shape :math:`(N, L, E)`, + where :math:`N` is the batch size, :math:`L` is the target + sequence length , and :math:`E` is the embedding dimension + `embed_dim`. Attention weights per head of shape :math:` + (num_heads, L, S)`. where :math:`N` is batch size, :math:`L` + is target sequence length, and :math:`S` is the source sequence + length. + """ + assert key.size(1) == value.size(1), \ + f'{"key, value must have the same sequence length"}' + assert query.size(0) == key.size(0) == value.size(0), \ + f'{"batch size must be equal for query, key, value"}' + assert query.size(2) == key.size(2), \ + f'{"q_dims, k_dims must be equal"}' + assert value.size(2) == self.embed_dims, \ + f'{"v_dims must be equal to embed_dims"}' + + bs, tgt_len, hidden_dims = query.size() + _, src_len, _ = key.size() + head_dims = hidden_dims // self.num_heads + v_head_dims = self.embed_dims // self.num_heads + assert head_dims * self.num_heads == hidden_dims, \ + f'{"hidden_dims must be divisible by num_heads"}' + scaling = float(head_dims)**-0.5 + + q = query * scaling + k = key + v = value + + if attn_mask is not None: + assert attn_mask.dtype == torch.float32 or \ + attn_mask.dtype == torch.float64 or \ + attn_mask.dtype == torch.float16 or \ + attn_mask.dtype == torch.uint8 or \ + attn_mask.dtype == torch.bool, \ + 'Only float, byte, and bool types are supported for \ + attn_mask' + + if attn_mask.dtype == torch.uint8: + warnings.warn('Byte tensor for attn_mask is deprecated.\ + Use bool tensor instead.') + attn_mask = attn_mask.to(torch.bool) + if attn_mask.dim() == 2: + attn_mask = attn_mask.unsqueeze(0) + if list(attn_mask.size()) != [1, query.size(1), key.size(1)]: + raise RuntimeError( + 'The size of the 2D attn_mask is not correct.') + elif attn_mask.dim() == 3: + if list(attn_mask.size()) != [ + bs * self.num_heads, + query.size(1), + key.size(1) + ]: + raise RuntimeError( + 'The size of the 3D attn_mask is not correct.') + else: + raise RuntimeError( + "attn_mask's dimension {} is not supported".format( + attn_mask.dim())) + # attn_mask's dim is 3 now. + + if key_padding_mask is not None and key_padding_mask.dtype == int: + key_padding_mask = key_padding_mask.to(torch.bool) + + q = q.contiguous().view(bs, tgt_len, self.num_heads, + head_dims).permute(0, 2, 1, 3).flatten(0, 1) + if k is not None: + k = k.contiguous().view(bs, src_len, self.num_heads, + head_dims).permute(0, 2, 1, + 3).flatten(0, 1) + if v is not None: + v = v.contiguous().view(bs, src_len, self.num_heads, + v_head_dims).permute(0, 2, 1, + 3).flatten(0, 1) + + if key_padding_mask is not None: + assert key_padding_mask.size(0) == bs + assert key_padding_mask.size(1) == src_len + + attn_output_weights = torch.bmm(q, k.transpose(1, 2)) + assert list(attn_output_weights.size()) == [ + bs * self.num_heads, tgt_len, src_len + ] + + if attn_mask is not None: + if attn_mask.dtype == torch.bool: + attn_output_weights.masked_fill_(attn_mask, float('-inf')) + else: + attn_output_weights += attn_mask + + if key_padding_mask is not None: + attn_output_weights = attn_output_weights.view( + bs, self.num_heads, tgt_len, src_len) + attn_output_weights = attn_output_weights.masked_fill( + key_padding_mask.unsqueeze(1).unsqueeze(2), + float('-inf'), + ) + attn_output_weights = attn_output_weights.view( + bs * self.num_heads, tgt_len, src_len) + + attn_output_weights = F.softmax( + attn_output_weights - + attn_output_weights.max(dim=-1, keepdim=True)[0], + dim=-1) + attn_output_weights = self.attn_drop(attn_output_weights) + + attn_output = torch.bmm(attn_output_weights, v) + assert list( + attn_output.size()) == [bs * self.num_heads, tgt_len, v_head_dims] + attn_output = attn_output.view(bs, self.num_heads, tgt_len, + v_head_dims).permute(0, 2, 1, + 3).flatten(2) + attn_output = self.out_proj(attn_output) + + # average attention weights over heads + attn_output_weights = attn_output_weights.view(bs, self.num_heads, + tgt_len, src_len) + return attn_output, attn_output_weights.sum(dim=1) / self.num_heads + + def forward(self, + query: Tensor, + key: Tensor, + query_pos: Tensor = None, + ref_sine_embed: Tensor = None, + key_pos: Tensor = None, + attn_mask: Tensor = None, + key_padding_mask: Tensor = None, + is_first: bool = False) -> Tensor: + """Forward function for `ConditionalAttention`. + Args: + query (Tensor): The input query with shape [bs, num_queries, + embed_dims]. + key (Tensor): The key tensor with shape [bs, num_keys, + embed_dims]. + If None, the `query` will be used. Defaults to None. + query_pos (Tensor): The positional encoding for query in self + attention, with the same shape as `x`. If not None, it will + be added to `x` before forward function. + Defaults to None. + query_sine_embed (Tensor): The positional encoding for query in + cross attention, with the same shape as `x`. If not None, it + will be added to `x` before forward function. + Defaults to None. + key_pos (Tensor): The positional encoding for `key`, with the + same shape as `key`. Defaults to None. If not None, it will + be added to `key` before forward function. If None, and + `query_pos` has the same shape as `key`, then `query_pos` + will be used for `key_pos`. Defaults to None. + attn_mask (Tensor): ByteTensor mask with shape [num_queries, + num_keys]. Same in `nn.MultiheadAttention.forward`. + Defaults to None. + key_padding_mask (Tensor): ByteTensor with shape [bs, num_keys]. + Defaults to None. + is_first (bool): A indicator to tell whether the current layer + is the first layer of the decoder. + Defaults to False. + Returns: + Tensor: forwarded results with shape + [bs, num_queries, embed_dims]. + """ + + if self.cross_attn: + q_content = self.qcontent_proj(query) + k_content = self.kcontent_proj(key) + v = self.v_proj(key) + + bs, nq, c = q_content.size() + _, hw, _ = k_content.size() + + k_pos = self.kpos_proj(key_pos) + if is_first or self.keep_query_pos: + q_pos = self.qpos_proj(query_pos) + q = q_content + q_pos + k = k_content + k_pos + else: + q = q_content + k = k_content + q = q.view(bs, nq, self.num_heads, c // self.num_heads) + query_sine_embed = self.qpos_sine_proj(ref_sine_embed) + query_sine_embed = query_sine_embed.view(bs, nq, self.num_heads, + c // self.num_heads) + q = torch.cat([q, query_sine_embed], dim=3).view(bs, nq, 2 * c) + k = k.view(bs, hw, self.num_heads, c // self.num_heads) + k_pos = k_pos.view(bs, hw, self.num_heads, c // self.num_heads) + k = torch.cat([k, k_pos], dim=3).view(bs, hw, 2 * c) + ca_output = self.forward_attn( + query=q, + key=k, + value=v, + attn_mask=attn_mask, + key_padding_mask=key_padding_mask)[0] + query = query + self.proj_drop(ca_output) + else: + q_content = self.qcontent_proj(query) + q_pos = self.qpos_proj(query_pos) + k_content = self.kcontent_proj(query) + k_pos = self.kpos_proj(query_pos) + v = self.v_proj(query) + q = q_content if q_pos is None else q_content + q_pos + k = k_content if k_pos is None else k_content + k_pos + sa_output = self.forward_attn( + query=q, + key=k, + value=v, + attn_mask=attn_mask, + key_padding_mask=key_padding_mask)[0] + query = query + self.proj_drop(sa_output) + + return query + + +class MLP(BaseModule): + """Very simple multi-layer perceptron (also called FFN) with relu. Mostly + used in DETR series detectors. + + Args: + input_dim (int): Feature dim of the input tensor. + hidden_dim (int): Feature dim of the hidden layer. + output_dim (int): Feature dim of the output tensor. + num_layers (int): Number of FFN layers. As the last + layer of MLP only contains FFN (Linear). + """ + + def __init__(self, input_dim: int, hidden_dim: int, output_dim: int, + num_layers: int) -> None: + super().__init__() + self.num_layers = num_layers + h = [hidden_dim] * (num_layers - 1) + self.layers = ModuleList( + Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim])) + + def forward(self, x: Tensor) -> Tensor: + """Forward function of MLP. + + Args: + x (Tensor): The input feature, has shape + (num_queries, bs, input_dim). + Returns: + Tensor: The output feature, has shape + (num_queries, bs, output_dim). + """ + for i, layer in enumerate(self.layers): + x = F.relu(layer(x)) if i < self.num_layers - 1 else layer(x) + return x + + +@MODELS.register_module() +class DynamicConv(BaseModule): + """Implements Dynamic Convolution. + + This module generate parameters for each sample and + use bmm to implement 1*1 convolution. Code is modified + from the `official github repo `_ . + + Args: + in_channels (int): The input feature channel. + Defaults to 256. + feat_channels (int): The inner feature channel. + Defaults to 64. + out_channels (int, optional): The output feature channel. + When not specified, it will be set to `in_channels` + by default + input_feat_shape (int): The shape of input feature. + Defaults to 7. + with_proj (bool): Project two-dimentional feature to + one-dimentional feature. Default to True. + act_cfg (dict): The activation config for DynamicConv. + norm_cfg (dict): Config dict for normalization layer. Default + layer normalization. + init_cfg (obj:`mmengine.ConfigDict`): The Config for initialization. + Default: None. + """ + + def __init__(self, + in_channels: int = 256, + feat_channels: int = 64, + out_channels: Optional[int] = None, + input_feat_shape: int = 7, + with_proj: bool = True, + act_cfg: OptConfigType = dict(type='ReLU', inplace=True), + norm_cfg: OptConfigType = dict(type='LN'), + init_cfg: OptConfigType = None) -> None: + super(DynamicConv, self).__init__(init_cfg) + self.in_channels = in_channels + self.feat_channels = feat_channels + self.out_channels_raw = out_channels + self.input_feat_shape = input_feat_shape + self.with_proj = with_proj + self.act_cfg = act_cfg + self.norm_cfg = norm_cfg + self.out_channels = out_channels if out_channels else in_channels + + self.num_params_in = self.in_channels * self.feat_channels + self.num_params_out = self.out_channels * self.feat_channels + self.dynamic_layer = nn.Linear( + self.in_channels, self.num_params_in + self.num_params_out) + + self.norm_in = build_norm_layer(norm_cfg, self.feat_channels)[1] + self.norm_out = build_norm_layer(norm_cfg, self.out_channels)[1] + + self.activation = build_activation_layer(act_cfg) + + num_output = self.out_channels * input_feat_shape**2 + if self.with_proj: + self.fc_layer = nn.Linear(num_output, self.out_channels) + self.fc_norm = build_norm_layer(norm_cfg, self.out_channels)[1] + + def forward(self, param_feature: Tensor, input_feature: Tensor) -> Tensor: + """Forward function for `DynamicConv`. + + Args: + param_feature (Tensor): The feature can be used + to generate the parameter, has shape + (num_all_proposals, in_channels). + input_feature (Tensor): Feature that + interact with parameters, has shape + (num_all_proposals, in_channels, H, W). + + Returns: + Tensor: The output feature has shape + (num_all_proposals, out_channels). + """ + input_feature = input_feature.flatten(2).permute(2, 0, 1) + + input_feature = input_feature.permute(1, 0, 2) + parameters = self.dynamic_layer(param_feature) + + param_in = parameters[:, :self.num_params_in].view( + -1, self.in_channels, self.feat_channels) + param_out = parameters[:, -self.num_params_out:].view( + -1, self.feat_channels, self.out_channels) + + # input_feature has shape (num_all_proposals, H*W, in_channels) + # param_in has shape (num_all_proposals, in_channels, feat_channels) + # feature has shape (num_all_proposals, H*W, feat_channels) + features = torch.bmm(input_feature, param_in) + features = self.norm_in(features) + features = self.activation(features) + + # param_out has shape (batch_size, feat_channels, out_channels) + features = torch.bmm(features, param_out) + features = self.norm_out(features) + features = self.activation(features) + + if self.with_proj: + features = features.flatten(1) + features = self.fc_layer(features) + features = self.fc_norm(features) + features = self.activation(features) + + return features + + +def get_text_sine_pos_embed( + pos_tensor: torch.Tensor, + num_pos_feats: int = 128, + temperature: int = 10000, + exchange_xy: bool = True, +): + """generate sine position embedding from a position tensor + Args: + pos_tensor (torch.Tensor): shape: [..., n]. + num_pos_feats (int): projected shape for each float in the tensor. + temperature (int): temperature in the sine/cosine function. + exchange_xy (bool, optional): exchange pos x and pos y. For example, + input tensor is [x,y], the results will be [pos(y), pos(x)]. + Defaults to True. + Returns: + pos_embed (torch.Tensor): shape: [..., n*num_pos_feats]. + """ + scale = 2 * math.pi + dim_t = torch.arange( + num_pos_feats, dtype=torch.float32, device=pos_tensor.device) + dim_t = temperature**(2 * torch.div(dim_t, 2, rounding_mode='floor') / + num_pos_feats) + + def sine_func(x: torch.Tensor): + sin_x = x * scale / dim_t + sin_x = torch.stack((sin_x[..., 0::2].sin(), sin_x[..., 1::2].cos()), + dim=3).flatten(2) + return sin_x + + pos_res = [ + sine_func(x) + for x in pos_tensor.split([1] * pos_tensor.shape[-1], dim=-1) + ] + if exchange_xy: + pos_res[0], pos_res[1] = pos_res[1], pos_res[0] + pos_res = torch.cat(pos_res, dim=-1) + return pos_res diff --git a/mmdetection/mmdet/models/losses/__init__.py b/mmdetection/mmdet/models/losses/__init__.py new file mode 100644 index 00000000..7c57a3a9 --- /dev/null +++ b/mmdetection/mmdet/models/losses/__init__.py @@ -0,0 +1,42 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .accuracy import Accuracy, accuracy +from .ae_loss import AssociativeEmbeddingLoss +from .balanced_l1_loss import BalancedL1Loss, balanced_l1_loss +from .cross_entropy_loss import (CrossEntropyCustomLoss, CrossEntropyLoss, + binary_cross_entropy, cross_entropy, + mask_cross_entropy) +from .ddq_detr_aux_loss import DDQAuxLoss +from .dice_loss import DiceLoss +from .eqlv2_loss import EQLV2Loss +from .focal_loss import FocalCustomLoss, FocalLoss, sigmoid_focal_loss +from .gaussian_focal_loss import GaussianFocalLoss +from .gfocal_loss import DistributionFocalLoss, QualityFocalLoss +from .ghm_loss import GHMC, GHMR +from .iou_loss import (BoundedIoULoss, CIoULoss, DIoULoss, EIoULoss, GIoULoss, + IoULoss, SIoULoss, bounded_iou_loss, iou_loss) +from .kd_loss import KnowledgeDistillationKLDivLoss +from .l2_loss import L2Loss +from .margin_loss import MarginL2Loss +from .mse_loss import MSELoss, mse_loss +from .multipos_cross_entropy_loss import MultiPosCrossEntropyLoss +from .pisa_loss import carl_loss, isr_p +from .seesaw_loss import SeesawLoss +from .smooth_l1_loss import L1Loss, SmoothL1Loss, l1_loss, smooth_l1_loss +from .triplet_loss import TripletLoss +from .utils import reduce_loss, weight_reduce_loss, weighted_loss +from .varifocal_loss import VarifocalLoss + +__all__ = [ + 'accuracy', 'Accuracy', 'cross_entropy', 'binary_cross_entropy', + 'mask_cross_entropy', 'CrossEntropyLoss', 'sigmoid_focal_loss', + 'FocalLoss', 'smooth_l1_loss', 'SmoothL1Loss', 'balanced_l1_loss', + 'BalancedL1Loss', 'mse_loss', 'MSELoss', 'iou_loss', 'bounded_iou_loss', + 'IoULoss', 'BoundedIoULoss', 'GIoULoss', 'DIoULoss', 'CIoULoss', + 'EIoULoss', 'SIoULoss', 'GHMC', 'GHMR', 'reduce_loss', + 'weight_reduce_loss', 'weighted_loss', 'L1Loss', 'l1_loss', 'isr_p', + 'carl_loss', 'AssociativeEmbeddingLoss', 'GaussianFocalLoss', + 'QualityFocalLoss', 'DistributionFocalLoss', 'VarifocalLoss', + 'KnowledgeDistillationKLDivLoss', 'SeesawLoss', 'DiceLoss', 'EQLV2Loss', + 'MarginL2Loss', 'MultiPosCrossEntropyLoss', 'L2Loss', 'TripletLoss', + 'DDQAuxLoss', 'CrossEntropyCustomLoss', 'FocalCustomLoss' +] diff --git a/mmdetection/mmdet/models/losses/accuracy.py b/mmdetection/mmdet/models/losses/accuracy.py new file mode 100644 index 00000000..d68484e1 --- /dev/null +++ b/mmdetection/mmdet/models/losses/accuracy.py @@ -0,0 +1,77 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn + + +def accuracy(pred, target, topk=1, thresh=None): + """Calculate accuracy according to the prediction and target. + + Args: + pred (torch.Tensor): The model prediction, shape (N, num_class) + target (torch.Tensor): The target of each prediction, shape (N, ) + topk (int | tuple[int], optional): If the predictions in ``topk`` + matches the target, the predictions will be regarded as + correct ones. Defaults to 1. + thresh (float, optional): If not None, predictions with scores under + this threshold are considered incorrect. Default to None. + + Returns: + float | tuple[float]: If the input ``topk`` is a single integer, + the function will return a single float as accuracy. If + ``topk`` is a tuple containing multiple integers, the + function will return a tuple containing accuracies of + each ``topk`` number. + """ + assert isinstance(topk, (int, tuple)) + if isinstance(topk, int): + topk = (topk, ) + return_single = True + else: + return_single = False + + maxk = max(topk) + if pred.size(0) == 0: + accu = [pred.new_tensor(0.) for i in range(len(topk))] + return accu[0] if return_single else accu + assert pred.ndim == 2 and target.ndim == 1 + assert pred.size(0) == target.size(0) + assert maxk <= pred.size(1), \ + f'maxk {maxk} exceeds pred dimension {pred.size(1)}' + pred_value, pred_label = pred.topk(maxk, dim=1) + pred_label = pred_label.t() # transpose to shape (maxk, N) + correct = pred_label.eq(target.view(1, -1).expand_as(pred_label)) + if thresh is not None: + # Only prediction values larger than thresh are counted as correct + correct = correct & (pred_value > thresh).t() + res = [] + for k in topk: + correct_k = correct[:k].reshape(-1).float().sum(0, keepdim=True) + res.append(correct_k.mul_(100.0 / pred.size(0))) + return res[0] if return_single else res + + +class Accuracy(nn.Module): + + def __init__(self, topk=(1, ), thresh=None): + """Module to calculate the accuracy. + + Args: + topk (tuple, optional): The criterion used to calculate the + accuracy. Defaults to (1,). + thresh (float, optional): If not None, predictions with scores + under this threshold are considered incorrect. Default to None. + """ + super().__init__() + self.topk = topk + self.thresh = thresh + + def forward(self, pred, target): + """Forward function to calculate accuracy. + + Args: + pred (torch.Tensor): Prediction of models. + target (torch.Tensor): Target for each prediction. + + Returns: + tuple[float]: The accuracies under different topk criterions. + """ + return accuracy(pred, target, self.topk, self.thresh) diff --git a/mmdetection/mmdet/models/losses/ae_loss.py b/mmdetection/mmdet/models/losses/ae_loss.py new file mode 100644 index 00000000..2aa7d696 --- /dev/null +++ b/mmdetection/mmdet/models/losses/ae_loss.py @@ -0,0 +1,101 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from mmdet.registry import MODELS + + +def ae_loss_per_image(tl_preds, br_preds, match): + """Associative Embedding Loss in one image. + + Associative Embedding Loss including two parts: pull loss and push loss. + Pull loss makes embedding vectors from same object closer to each other. + Push loss distinguish embedding vector from different objects, and makes + the gap between them is large enough. + + During computing, usually there are 3 cases: + - no object in image: both pull loss and push loss will be 0. + - one object in image: push loss will be 0 and pull loss is computed + by the two corner of the only object. + - more than one objects in image: pull loss is computed by corner pairs + from each object, push loss is computed by each object with all + other objects. We use confusion matrix with 0 in diagonal to + compute the push loss. + + Args: + tl_preds (tensor): Embedding feature map of left-top corner. + br_preds (tensor): Embedding feature map of bottim-right corner. + match (list): Downsampled coordinates pair of each ground truth box. + """ + + tl_list, br_list, me_list = [], [], [] + if len(match) == 0: # no object in image + pull_loss = tl_preds.sum() * 0. + push_loss = tl_preds.sum() * 0. + else: + for m in match: + [tl_y, tl_x], [br_y, br_x] = m + tl_e = tl_preds[:, tl_y, tl_x].view(-1, 1) + br_e = br_preds[:, br_y, br_x].view(-1, 1) + tl_list.append(tl_e) + br_list.append(br_e) + me_list.append((tl_e + br_e) / 2.0) + + tl_list = torch.cat(tl_list) + br_list = torch.cat(br_list) + me_list = torch.cat(me_list) + + assert tl_list.size() == br_list.size() + + # N is object number in image, M is dimension of embedding vector + N, M = tl_list.size() + + pull_loss = (tl_list - me_list).pow(2) + (br_list - me_list).pow(2) + pull_loss = pull_loss.sum() / N + + margin = 1 # exp setting of CornerNet, details in section 3.3 of paper + + # confusion matrix of push loss + conf_mat = me_list.expand((N, N, M)).permute(1, 0, 2) - me_list + conf_weight = 1 - torch.eye(N).type_as(me_list) + conf_mat = conf_weight * (margin - conf_mat.sum(-1).abs()) + + if N > 1: # more than one object in current image + push_loss = F.relu(conf_mat).sum() / (N * (N - 1)) + else: + push_loss = tl_preds.sum() * 0. + + return pull_loss, push_loss + + +@MODELS.register_module() +class AssociativeEmbeddingLoss(nn.Module): + """Associative Embedding Loss. + + More details can be found in + `Associative Embedding `_ and + `CornerNet `_ . + Code is modified from `kp_utils.py `_ # noqa: E501 + + Args: + pull_weight (float): Loss weight for corners from same object. + push_weight (float): Loss weight for corners from different object. + """ + + def __init__(self, pull_weight=0.25, push_weight=0.25): + super(AssociativeEmbeddingLoss, self).__init__() + self.pull_weight = pull_weight + self.push_weight = push_weight + + def forward(self, pred, target, match): + """Forward function.""" + batch = pred.size(0) + pull_all, push_all = 0.0, 0.0 + for i in range(batch): + pull, push = ae_loss_per_image(pred[i], target[i], match[i]) + + pull_all += self.pull_weight * pull + push_all += self.push_weight * push + + return pull_all, push_all diff --git a/mmdetection/mmdet/models/losses/balanced_l1_loss.py b/mmdetection/mmdet/models/losses/balanced_l1_loss.py new file mode 100644 index 00000000..25adaab2 --- /dev/null +++ b/mmdetection/mmdet/models/losses/balanced_l1_loss.py @@ -0,0 +1,122 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np +import torch +import torch.nn as nn + +from mmdet.registry import MODELS +from .utils import weighted_loss + + +@weighted_loss +def balanced_l1_loss(pred, + target, + beta=1.0, + alpha=0.5, + gamma=1.5, + reduction='mean'): + """Calculate balanced L1 loss. + + Please see the `Libra R-CNN `_ + + Args: + pred (torch.Tensor): The prediction with shape (N, 4). + target (torch.Tensor): The learning target of the prediction with + shape (N, 4). + beta (float): The loss is a piecewise function of prediction and target + and ``beta`` serves as a threshold for the difference between the + prediction and target. Defaults to 1.0. + alpha (float): The denominator ``alpha`` in the balanced L1 loss. + Defaults to 0.5. + gamma (float): The ``gamma`` in the balanced L1 loss. + Defaults to 1.5. + reduction (str, optional): The method that reduces the loss to a + scalar. Options are "none", "mean" and "sum". + + Returns: + torch.Tensor: The calculated loss + """ + assert beta > 0 + if target.numel() == 0: + return pred.sum() * 0 + + assert pred.size() == target.size() + + diff = torch.abs(pred - target) + b = np.e**(gamma / alpha) - 1 + loss = torch.where( + diff < beta, alpha / b * + (b * diff + 1) * torch.log(b * diff / beta + 1) - alpha * diff, + gamma * diff + gamma / b - alpha * beta) + + return loss + + +@MODELS.register_module() +class BalancedL1Loss(nn.Module): + """Balanced L1 Loss. + + arXiv: https://arxiv.org/pdf/1904.02701.pdf (CVPR 2019) + + Args: + alpha (float): The denominator ``alpha`` in the balanced L1 loss. + Defaults to 0.5. + gamma (float): The ``gamma`` in the balanced L1 loss. Defaults to 1.5. + beta (float, optional): The loss is a piecewise function of prediction + and target. ``beta`` serves as a threshold for the difference + between the prediction and target. Defaults to 1.0. + reduction (str, optional): The method that reduces the loss to a + scalar. Options are "none", "mean" and "sum". + loss_weight (float, optional): The weight of the loss. Defaults to 1.0 + """ + + def __init__(self, + alpha=0.5, + gamma=1.5, + beta=1.0, + reduction='mean', + loss_weight=1.0): + super(BalancedL1Loss, self).__init__() + self.alpha = alpha + self.gamma = gamma + self.beta = beta + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None, + **kwargs): + """Forward function of loss. + + Args: + pred (torch.Tensor): The prediction with shape (N, 4). + target (torch.Tensor): The learning target of the prediction with + shape (N, 4). + weight (torch.Tensor, optional): Sample-wise loss weight with + shape (N, ). + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Options are "none", "mean" and "sum". + + Returns: + torch.Tensor: The calculated loss + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss_bbox = self.loss_weight * balanced_l1_loss( + pred, + target, + weight, + alpha=self.alpha, + gamma=self.gamma, + beta=self.beta, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss_bbox diff --git a/mmdetection/mmdet/models/losses/cross_entropy_loss.py b/mmdetection/mmdet/models/losses/cross_entropy_loss.py new file mode 100644 index 00000000..49fac774 --- /dev/null +++ b/mmdetection/mmdet/models/losses/cross_entropy_loss.py @@ -0,0 +1,401 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from mmdet.registry import MODELS +from .accuracy import accuracy +from .utils import weight_reduce_loss + + +def cross_entropy(pred, + label, + weight=None, + reduction='mean', + avg_factor=None, + class_weight=None, + ignore_index=-100, + avg_non_ignore=False): + """Calculate the CrossEntropy loss. + + Args: + pred (torch.Tensor): The prediction with shape (N, C), C is the number + of classes. + label (torch.Tensor): The learning label of the prediction. + weight (torch.Tensor, optional): Sample-wise loss weight. + reduction (str, optional): The method used to reduce the loss. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + class_weight (list[float], optional): The weight for each class. + ignore_index (int | None): The label index to be ignored. + If None, it will be set to default value. Default: -100. + avg_non_ignore (bool): The flag decides to whether the loss is + only averaged over non-ignored targets. Default: False. + + Returns: + torch.Tensor: The calculated loss + """ + # The default value of ignore_index is the same as F.cross_entropy + ignore_index = -100 if ignore_index is None else ignore_index + # element-wise losses + loss = F.cross_entropy( + pred, + label, + weight=class_weight, + reduction='none', + ignore_index=ignore_index) + + # average loss over non-ignored elements + # pytorch's official cross_entropy average loss over non-ignored elements + # refer to https://github.com/pytorch/pytorch/blob/56b43f4fec1f76953f15a627694d4bba34588969/torch/nn/functional.py#L2660 # noqa + if (avg_factor is None) and avg_non_ignore and reduction == 'mean': + avg_factor = label.numel() - (label == ignore_index).sum().item() + + # apply weights and do the reduction + if weight is not None: + weight = weight.float() + loss = weight_reduce_loss( + loss, weight=weight, reduction=reduction, avg_factor=avg_factor) + + return loss + + +def _expand_onehot_labels(labels, label_weights, label_channels, ignore_index): + """Expand onehot labels to match the size of prediction.""" + bin_labels = labels.new_full((labels.size(0), label_channels), 0) + valid_mask = (labels >= 0) & (labels != ignore_index) + inds = torch.nonzero( + valid_mask & (labels < label_channels), as_tuple=False) + + if inds.numel() > 0: + bin_labels[inds, labels[inds]] = 1 + + valid_mask = valid_mask.view(-1, 1).expand(labels.size(0), + label_channels).float() + if label_weights is None: + bin_label_weights = valid_mask + else: + bin_label_weights = label_weights.view(-1, 1).repeat(1, label_channels) + bin_label_weights *= valid_mask + + return bin_labels, bin_label_weights, valid_mask + + +def binary_cross_entropy(pred, + label, + weight=None, + reduction='mean', + avg_factor=None, + class_weight=None, + ignore_index=-100, + avg_non_ignore=False): + """Calculate the binary CrossEntropy loss. + + Args: + pred (torch.Tensor): The prediction with shape (N, 1) or (N, ). + When the shape of pred is (N, 1), label will be expanded to + one-hot format, and when the shape of pred is (N, ), label + will not be expanded to one-hot format. + label (torch.Tensor): The learning label of the prediction, + with shape (N, ). + weight (torch.Tensor, optional): Sample-wise loss weight. + reduction (str, optional): The method used to reduce the loss. + Options are "none", "mean" and "sum". + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + class_weight (list[float], optional): The weight for each class. + ignore_index (int | None): The label index to be ignored. + If None, it will be set to default value. Default: -100. + avg_non_ignore (bool): The flag decides to whether the loss is + only averaged over non-ignored targets. Default: False. + + Returns: + torch.Tensor: The calculated loss. + """ + # The default value of ignore_index is the same as F.cross_entropy + ignore_index = -100 if ignore_index is None else ignore_index + + if pred.dim() != label.dim(): + label, weight, valid_mask = _expand_onehot_labels( + label, weight, pred.size(-1), ignore_index) + else: + # should mask out the ignored elements + valid_mask = ((label >= 0) & (label != ignore_index)).float() + if weight is not None: + # The inplace writing method will have a mismatched broadcast + # shape error if the weight and valid_mask dimensions + # are inconsistent such as (B,N,1) and (B,N,C). + weight = weight * valid_mask + else: + weight = valid_mask + + # average loss over non-ignored elements + if (avg_factor is None) and avg_non_ignore and reduction == 'mean': + avg_factor = valid_mask.sum().item() + + # weighted element-wise losses + weight = weight.float() + loss = F.binary_cross_entropy_with_logits( + pred, label.float(), pos_weight=class_weight, reduction='none') + # do the reduction for the weighted loss + loss = weight_reduce_loss( + loss, weight, reduction=reduction, avg_factor=avg_factor) + + return loss + + +def mask_cross_entropy(pred, + target, + label, + reduction='mean', + avg_factor=None, + class_weight=None, + ignore_index=None, + **kwargs): + """Calculate the CrossEntropy loss for masks. + + Args: + pred (torch.Tensor): The prediction with shape (N, C, *), C is the + number of classes. The trailing * indicates arbitrary shape. + target (torch.Tensor): The learning label of the prediction. + label (torch.Tensor): ``label`` indicates the class label of the mask + corresponding object. This will be used to select the mask in the + of the class which the object belongs to when the mask prediction + if not class-agnostic. + reduction (str, optional): The method used to reduce the loss. + Options are "none", "mean" and "sum". + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + class_weight (list[float], optional): The weight for each class. + ignore_index (None): Placeholder, to be consistent with other loss. + Default: None. + + Returns: + torch.Tensor: The calculated loss + + Example: + >>> N, C = 3, 11 + >>> H, W = 2, 2 + >>> pred = torch.randn(N, C, H, W) * 1000 + >>> target = torch.rand(N, H, W) + >>> label = torch.randint(0, C, size=(N,)) + >>> reduction = 'mean' + >>> avg_factor = None + >>> class_weights = None + >>> loss = mask_cross_entropy(pred, target, label, reduction, + >>> avg_factor, class_weights) + >>> assert loss.shape == (1,) + """ + assert ignore_index is None, 'BCE loss does not support ignore_index' + # TODO: handle these two reserved arguments + assert reduction == 'mean' and avg_factor is None + num_rois = pred.size()[0] + inds = torch.arange(0, num_rois, dtype=torch.long, device=pred.device) + pred_slice = pred[inds, label].squeeze(1) + return F.binary_cross_entropy_with_logits( + pred_slice, target, weight=class_weight, reduction='mean')[None] + + +@MODELS.register_module() +class CrossEntropyLoss(nn.Module): + + def __init__(self, + use_sigmoid=False, + use_mask=False, + reduction='mean', + class_weight=None, + ignore_index=None, + loss_weight=1.0, + avg_non_ignore=False): + """CrossEntropyLoss. + + Args: + use_sigmoid (bool, optional): Whether the prediction uses sigmoid + of softmax. Defaults to False. + use_mask (bool, optional): Whether to use mask cross entropy loss. + Defaults to False. + reduction (str, optional): . Defaults to 'mean'. + Options are "none", "mean" and "sum". + class_weight (list[float], optional): Weight of each class. + Defaults to None. + ignore_index (int | None): The label index to be ignored. + Defaults to None. + loss_weight (float, optional): Weight of the loss. Defaults to 1.0. + avg_non_ignore (bool): The flag decides to whether the loss is + only averaged over non-ignored targets. Default: False. + """ + super(CrossEntropyLoss, self).__init__() + assert (use_sigmoid is False) or (use_mask is False) + self.use_sigmoid = use_sigmoid + self.use_mask = use_mask + self.reduction = reduction + self.loss_weight = loss_weight + self.class_weight = class_weight + self.ignore_index = ignore_index + self.avg_non_ignore = avg_non_ignore + if ((ignore_index is not None) and not self.avg_non_ignore + and self.reduction == 'mean'): + warnings.warn( + 'Default ``avg_non_ignore`` is False, if you would like to ' + 'ignore the certain label and average loss over non-ignore ' + 'labels, which is the same with PyTorch official ' + 'cross_entropy, set ``avg_non_ignore=True``.') + + if self.use_sigmoid: + self.cls_criterion = binary_cross_entropy + elif self.use_mask: + self.cls_criterion = mask_cross_entropy + else: + self.cls_criterion = cross_entropy + + def extra_repr(self): + """Extra repr.""" + s = f'avg_non_ignore={self.avg_non_ignore}' + return s + + def forward(self, + cls_score, + label, + weight=None, + avg_factor=None, + reduction_override=None, + ignore_index=None, + **kwargs): + """Forward function. + + Args: + cls_score (torch.Tensor): The prediction. + label (torch.Tensor): The learning label of the prediction. + weight (torch.Tensor, optional): Sample-wise loss weight. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The method used to reduce the + loss. Options are "none", "mean" and "sum". + ignore_index (int | None): The label index to be ignored. + If not None, it will override the default value. Default: None. + Returns: + torch.Tensor: The calculated loss. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if ignore_index is None: + ignore_index = self.ignore_index + + if self.class_weight is not None: + class_weight = cls_score.new_tensor( + self.class_weight, device=cls_score.device) + else: + class_weight = None + loss_cls = self.loss_weight * self.cls_criterion( + cls_score, + label, + weight, + class_weight=class_weight, + reduction=reduction, + avg_factor=avg_factor, + ignore_index=ignore_index, + avg_non_ignore=self.avg_non_ignore, + **kwargs) + return loss_cls + + +@MODELS.register_module() +class CrossEntropyCustomLoss(CrossEntropyLoss): + + def __init__(self, + use_sigmoid=False, + use_mask=False, + reduction='mean', + num_classes=-1, + class_weight=None, + ignore_index=None, + loss_weight=1.0, + avg_non_ignore=False): + """CrossEntropyCustomLoss. + + Args: + use_sigmoid (bool, optional): Whether the prediction uses sigmoid + of softmax. Defaults to False. + use_mask (bool, optional): Whether to use mask cross entropy loss. + Defaults to False. + reduction (str, optional): . Defaults to 'mean'. + Options are "none", "mean" and "sum". + num_classes (int): Number of classes to classify. + class_weight (list[float], optional): Weight of each class. + Defaults to None. + ignore_index (int | None): The label index to be ignored. + Defaults to None. + loss_weight (float, optional): Weight of the loss. Defaults to 1.0. + avg_non_ignore (bool): The flag decides to whether the loss is + only averaged over non-ignored targets. Default: False. + """ + super(CrossEntropyCustomLoss, self).__init__() + assert (use_sigmoid is False) or (use_mask is False) + self.use_sigmoid = use_sigmoid + self.use_mask = use_mask + self.reduction = reduction + self.loss_weight = loss_weight + self.class_weight = class_weight + self.ignore_index = ignore_index + self.avg_non_ignore = avg_non_ignore + if ((ignore_index is not None) and not self.avg_non_ignore + and self.reduction == 'mean'): + warnings.warn( + 'Default ``avg_non_ignore`` is False, if you would like to ' + 'ignore the certain label and average loss over non-ignore ' + 'labels, which is the same with PyTorch official ' + 'cross_entropy, set ``avg_non_ignore=True``.') + + if self.use_sigmoid: + self.cls_criterion = binary_cross_entropy + elif self.use_mask: + self.cls_criterion = mask_cross_entropy + else: + self.cls_criterion = cross_entropy + + self.num_classes = num_classes + + assert self.num_classes != -1 + + # custom output channels of the classifier + self.custom_cls_channels = True + # custom activation of cls_score + self.custom_activation = True + # custom accuracy of the classsifier + self.custom_accuracy = True + + def get_cls_channels(self, num_classes): + assert num_classes == self.num_classes + if not self.use_sigmoid: + return num_classes + 1 + else: + return num_classes + + def get_activation(self, cls_score): + + fine_cls_score = cls_score[:, :self.num_classes] + + if not self.use_sigmoid: + bg_score = cls_score[:, [-1]] + new_score = torch.cat([fine_cls_score, bg_score], dim=-1) + scores = F.softmax(new_score, dim=-1) + else: + score_classes = fine_cls_score.sigmoid() + score_neg = 1 - score_classes.sum(dim=1, keepdim=True) + score_neg = score_neg.clamp(min=0, max=1) + scores = torch.cat([score_classes, score_neg], dim=1) + + return scores + + def get_accuracy(self, cls_score, labels): + + fine_cls_score = cls_score[:, :self.num_classes] + + pos_inds = labels < self.num_classes + acc_classes = accuracy(fine_cls_score[pos_inds], labels[pos_inds]) + acc = dict() + acc['acc_classes'] = acc_classes + return acc diff --git a/mmdetection/mmdet/models/losses/ddq_detr_aux_loss.py b/mmdetection/mmdet/models/losses/ddq_detr_aux_loss.py new file mode 100644 index 00000000..41f1c716 --- /dev/null +++ b/mmdetection/mmdet/models/losses/ddq_detr_aux_loss.py @@ -0,0 +1,303 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmengine.structures import BaseDataElement + +from mmdet.models.utils import multi_apply +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.utils import reduce_mean + + +class DDQAuxLoss(nn.Module): + """DDQ auxiliary branches loss for dense queries. + + Args: + loss_cls (dict): + Configuration of classification loss function. + loss_bbox (dict): + Configuration of bbox regression loss function. + train_cfg (dict): + Configuration of gt targets assigner for each predicted bbox. + """ + + def __init__( + self, + loss_cls=dict( + type='QualityFocalLoss', + use_sigmoid=True, + activated=True, # use probability instead of logit as input + beta=2.0, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + train_cfg=dict( + assigner=dict(type='TopkHungarianAssigner', topk=8), + alpha=1, + beta=6), + ): + super(DDQAuxLoss, self).__init__() + self.train_cfg = train_cfg + self.loss_cls = MODELS.build(loss_cls) + self.loss_bbox = MODELS.build(loss_bbox) + self.assigner = TASK_UTILS.build(self.train_cfg['assigner']) + + sampler_cfg = dict(type='PseudoSampler') + self.sampler = TASK_UTILS.build(sampler_cfg) + + def loss_single(self, cls_score, bbox_pred, labels, label_weights, + bbox_targets, alignment_metrics): + """Calculate auxiliary branches loss for dense queries for one image. + + Args: + cls_score (Tensor): Predicted normalized classification + scores for one image, has shape (num_dense_queries, + cls_out_channels). + bbox_pred (Tensor): Predicted unnormalized bbox coordinates + for one image, has shape (num_dense_queries, 4) with the + last dimension arranged as (x1, y1, x2, y2). + labels (Tensor): Labels for one image. + label_weights (Tensor): Label weights for one image. + bbox_targets (Tensor): Bbox targets for one image. + alignment_metrics (Tensor): Normalized alignment metrics for one + image. + + Returns: + tuple: A tuple of loss components and loss weights. + """ + bbox_targets = bbox_targets.reshape(-1, 4) + labels = labels.reshape(-1) + alignment_metrics = alignment_metrics.reshape(-1) + label_weights = label_weights.reshape(-1) + targets = (labels, alignment_metrics) + cls_loss_func = self.loss_cls + + loss_cls = cls_loss_func( + cls_score, targets, label_weights, avg_factor=1.0) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = cls_score.size(-1) + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().squeeze(1) + + if len(pos_inds) > 0: + pos_bbox_targets = bbox_targets[pos_inds] + pos_bbox_pred = bbox_pred[pos_inds] + + pos_decode_bbox_pred = pos_bbox_pred + pos_decode_bbox_targets = pos_bbox_targets + + # regression loss + pos_bbox_weight = alignment_metrics[pos_inds] + + loss_bbox = self.loss_bbox( + pos_decode_bbox_pred, + pos_decode_bbox_targets, + weight=pos_bbox_weight, + avg_factor=1.0) + else: + loss_bbox = bbox_pred.sum() * 0 + pos_bbox_weight = bbox_targets.new_tensor(0.) + + return loss_cls, loss_bbox, alignment_metrics.sum( + ), pos_bbox_weight.sum() + + def loss(self, cls_scores, bbox_preds, gt_bboxes, gt_labels, img_metas, + **kwargs): + """Calculate auxiliary branches loss for dense queries. + + Args: + cls_scores (Tensor): Predicted normalized classification + scores, has shape (bs, num_dense_queries, + cls_out_channels). + bbox_preds (Tensor): Predicted unnormalized bbox coordinates, + has shape (bs, num_dense_queries, 4) with the last + dimension arranged as (x1, y1, x2, y2). + gt_bboxes (list[Tensor]): List of unnormalized ground truth + bboxes for each image, each has shape (num_gt, 4) with the + last dimension arranged as (x1, y1, x2, y2). + NOTE: num_gt is dynamic for each image. + gt_labels (list[Tensor]): List of ground truth classification + index for each image, each has shape (num_gt,). + NOTE: num_gt is dynamic for each image. + img_metas (list[dict]): Meta information for one image, + e.g., image size, scaling factor, etc. + + Returns: + dict: A dictionary of loss components. + """ + flatten_cls_scores = cls_scores + flatten_bbox_preds = bbox_preds + + cls_reg_targets = self.get_targets( + flatten_cls_scores, + flatten_bbox_preds, + gt_bboxes, + img_metas, + gt_labels_list=gt_labels, + ) + (labels_list, label_weights_list, bbox_targets_list, + alignment_metrics_list) = cls_reg_targets + + losses_cls, losses_bbox, \ + cls_avg_factors, bbox_avg_factors = multi_apply( + self.loss_single, + flatten_cls_scores, + flatten_bbox_preds, + labels_list, + label_weights_list, + bbox_targets_list, + alignment_metrics_list, + ) + + cls_avg_factor = reduce_mean(sum(cls_avg_factors)).clamp_(min=1).item() + losses_cls = list(map(lambda x: x / cls_avg_factor, losses_cls)) + + bbox_avg_factor = reduce_mean( + sum(bbox_avg_factors)).clamp_(min=1).item() + losses_bbox = list(map(lambda x: x / bbox_avg_factor, losses_bbox)) + return dict(aux_loss_cls=losses_cls, aux_loss_bbox=losses_bbox) + + def get_targets(self, + cls_scores, + bbox_preds, + gt_bboxes_list, + img_metas, + gt_labels_list=None, + **kwargs): + """Compute regression and classification targets for a batch images. + + Args: + cls_scores (Tensor): Predicted normalized classification + scores, has shape (bs, num_dense_queries, + cls_out_channels). + bbox_preds (Tensor): Predicted unnormalized bbox coordinates, + has shape (bs, num_dense_queries, 4) with the last + dimension arranged as (x1, y1, x2, y2). + gt_bboxes_list (List[Tensor]): List of unnormalized ground truth + bboxes for each image, each has shape (num_gt, 4) with the + last dimension arranged as (x1, y1, x2, y2). + NOTE: num_gt is dynamic for each image. + img_metas (list[dict]): Meta information for one image, + e.g., image size, scaling factor, etc. + gt_labels_list (list[Tensor]): List of ground truth classification + index for each image, each has shape (num_gt,). + NOTE: num_gt is dynamic for each image. + Default: None. + + Returns: + tuple: a tuple containing the following targets. + + - all_labels (list[Tensor]): Labels for all images. + - all_label_weights (list[Tensor]): Label weights for all images. + - all_bbox_targets (list[Tensor]): Bbox targets for all images. + - all_assign_metrics (list[Tensor]): Normalized alignment metrics + for all images. + """ + (all_labels, all_label_weights, all_bbox_targets, + all_assign_metrics) = multi_apply(self._get_target_single, cls_scores, + bbox_preds, gt_bboxes_list, + gt_labels_list, img_metas) + + return (all_labels, all_label_weights, all_bbox_targets, + all_assign_metrics) + + def _get_target_single(self, cls_scores, bbox_preds, gt_bboxes, gt_labels, + img_meta, **kwargs): + """Compute regression and classification targets for one image. + + Args: + cls_scores (Tensor): Predicted normalized classification + scores for one image, has shape (num_dense_queries, + cls_out_channels). + bbox_preds (Tensor): Predicted unnormalized bbox coordinates + for one image, has shape (num_dense_queries, 4) with the + last dimension arranged as (x1, y1, x2, y2). + gt_bboxes (Tensor): Unnormalized ground truth + bboxes for one image, has shape (num_gt, 4) with the + last dimension arranged as (x1, y1, x2, y2). + NOTE: num_gt is dynamic for each image. + gt_labels (Tensor): Ground truth classification + index for the image, has shape (num_gt,). + NOTE: num_gt is dynamic for each image. + img_meta (dict): Meta information for one image. + + Returns: + tuple[Tensor]: a tuple containing the following for one image. + + - labels (Tensor): Labels for one image. + - label_weights (Tensor): Label weights for one image. + - bbox_targets (Tensor): Bbox targets for one image. + - norm_alignment_metrics (Tensor): Normalized alignment + metrics for one image. + """ + if len(gt_labels) == 0: + num_valid_anchors = len(cls_scores) + bbox_targets = torch.zeros_like(bbox_preds) + labels = bbox_preds.new_full((num_valid_anchors, ), + cls_scores.size(-1), + dtype=torch.long) + label_weights = bbox_preds.new_zeros( + num_valid_anchors, dtype=torch.float) + norm_alignment_metrics = bbox_preds.new_zeros( + num_valid_anchors, dtype=torch.float) + return (labels, label_weights, bbox_targets, + norm_alignment_metrics) + + assign_result = self.assigner.assign(cls_scores, bbox_preds, gt_bboxes, + gt_labels, img_meta) + assign_ious = assign_result.max_overlaps + assign_metrics = assign_result.assign_metrics + + pred_instances = BaseDataElement() + gt_instances = BaseDataElement() + + pred_instances.bboxes = bbox_preds + gt_instances.bboxes = gt_bboxes + + pred_instances.priors = cls_scores + gt_instances.labels = gt_labels + + sampling_result = self.sampler.sample(assign_result, pred_instances, + gt_instances) + + num_valid_anchors = len(cls_scores) + bbox_targets = torch.zeros_like(bbox_preds) + labels = bbox_preds.new_full((num_valid_anchors, ), + cls_scores.size(-1), + dtype=torch.long) + label_weights = bbox_preds.new_zeros( + num_valid_anchors, dtype=torch.float) + norm_alignment_metrics = bbox_preds.new_zeros( + num_valid_anchors, dtype=torch.float) + + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + if len(pos_inds) > 0: + # point-based + pos_bbox_targets = sampling_result.pos_gt_bboxes + bbox_targets[pos_inds, :] = pos_bbox_targets + + if gt_labels is None: + # Only dense_heads gives gt_labels as None + # Foreground is the first class since v2.5.0 + labels[pos_inds] = 0 + else: + labels[pos_inds] = gt_labels[ + sampling_result.pos_assigned_gt_inds] + + label_weights[pos_inds] = 1.0 + + if len(neg_inds) > 0: + label_weights[neg_inds] = 1.0 + + class_assigned_gt_inds = torch.unique( + sampling_result.pos_assigned_gt_inds) + for gt_inds in class_assigned_gt_inds: + gt_class_inds = sampling_result.pos_assigned_gt_inds == gt_inds + pos_alignment_metrics = assign_metrics[gt_class_inds] + pos_ious = assign_ious[gt_class_inds] + pos_norm_alignment_metrics = pos_alignment_metrics / ( + pos_alignment_metrics.max() + 10e-8) * pos_ious.max() + norm_alignment_metrics[ + pos_inds[gt_class_inds]] = pos_norm_alignment_metrics + + return (labels, label_weights, bbox_targets, norm_alignment_metrics) diff --git a/mmdetection/mmdet/models/losses/dice_loss.py b/mmdetection/mmdet/models/losses/dice_loss.py new file mode 100644 index 00000000..1d5cac1e --- /dev/null +++ b/mmdetection/mmdet/models/losses/dice_loss.py @@ -0,0 +1,146 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn + +from mmdet.registry import MODELS +from .utils import weight_reduce_loss + + +def dice_loss(pred, + target, + weight=None, + eps=1e-3, + reduction='mean', + naive_dice=False, + avg_factor=None): + """Calculate dice loss, there are two forms of dice loss is supported: + + - the one proposed in `V-Net: Fully Convolutional Neural + Networks for Volumetric Medical Image Segmentation + `_. + - the dice loss in which the power of the number in the + denominator is the first power instead of the second + power. + + Args: + pred (torch.Tensor): The prediction, has a shape (n, *) + target (torch.Tensor): The learning label of the prediction, + shape (n, *), same shape of pred. + weight (torch.Tensor, optional): The weight of loss for each + prediction, has a shape (n,). Defaults to None. + eps (float): Avoid dividing by zero. Default: 1e-3. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. + Options are "none", "mean" and "sum". + naive_dice (bool, optional): If false, use the dice + loss defined in the V-Net paper, otherwise, use the + naive dice loss in which the power of the number in the + denominator is the first power instead of the second + power.Defaults to False. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + """ + + input = pred.flatten(1) + target = target.flatten(1).float() + + a = torch.sum(input * target, 1) + if naive_dice: + b = torch.sum(input, 1) + c = torch.sum(target, 1) + d = (2 * a + eps) / (b + c + eps) + else: + b = torch.sum(input * input, 1) + eps + c = torch.sum(target * target, 1) + eps + d = (2 * a) / (b + c) + + loss = 1 - d + if weight is not None: + assert weight.ndim == loss.ndim + assert len(weight) == len(pred) + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + + +@MODELS.register_module() +class DiceLoss(nn.Module): + + def __init__(self, + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=False, + loss_weight=1.0, + eps=1e-3): + """Compute dice loss. + + Args: + use_sigmoid (bool, optional): Whether to the prediction is + used for sigmoid or softmax. Defaults to True. + activate (bool): Whether to activate the predictions inside, + this will disable the inside sigmoid operation. + Defaults to True. + reduction (str, optional): The method used + to reduce the loss. Options are "none", + "mean" and "sum". Defaults to 'mean'. + naive_dice (bool, optional): If false, use the dice + loss defined in the V-Net paper, otherwise, use the + naive dice loss in which the power of the number in the + denominator is the first power instead of the second + power. Defaults to False. + loss_weight (float, optional): Weight of loss. Defaults to 1.0. + eps (float): Avoid dividing by zero. Defaults to 1e-3. + """ + + super(DiceLoss, self).__init__() + self.use_sigmoid = use_sigmoid + self.reduction = reduction + self.naive_dice = naive_dice + self.loss_weight = loss_weight + self.eps = eps + self.activate = activate + + def forward(self, + pred, + target, + weight=None, + reduction_override=None, + avg_factor=None): + """Forward function. + + Args: + pred (torch.Tensor): The prediction, has a shape (n, *). + target (torch.Tensor): The label of the prediction, + shape (n, *), same shape of pred. + weight (torch.Tensor, optional): The weight of loss for each + prediction, has a shape (n,). Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Options are "none", "mean" and "sum". + + Returns: + torch.Tensor: The calculated loss + """ + + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + + if self.activate: + if self.use_sigmoid: + pred = pred.sigmoid() + else: + raise NotImplementedError + + loss = self.loss_weight * dice_loss( + pred, + target, + weight, + eps=self.eps, + reduction=reduction, + naive_dice=self.naive_dice, + avg_factor=avg_factor) + + return loss diff --git a/mmdetection/mmdet/models/losses/eqlv2_loss.py b/mmdetection/mmdet/models/losses/eqlv2_loss.py new file mode 100644 index 00000000..ea1f4a9a --- /dev/null +++ b/mmdetection/mmdet/models/losses/eqlv2_loss.py @@ -0,0 +1,173 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging +from functools import partial +from typing import Optional + +import torch +import torch.distributed as dist +import torch.nn as nn +import torch.nn.functional as F +from mmengine.logging import print_log +from torch import Tensor + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class EQLV2Loss(nn.Module): + + def __init__(self, + use_sigmoid: bool = True, + reduction: str = 'mean', + class_weight: Optional[Tensor] = None, + loss_weight: float = 1.0, + num_classes: int = 1203, + use_distributed: bool = False, + mu: float = 0.8, + alpha: float = 4.0, + gamma: int = 12, + vis_grad: bool = False, + test_with_obj: bool = True) -> None: + """`Equalization Loss v2 `_ + + Args: + use_sigmoid (bool): EQLv2 uses the sigmoid function to transform + the predicted logits to an estimated probability distribution. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. + class_weight (Tensor, optional): The weight of loss for each + prediction. Defaults to None. + loss_weight (float, optional): The weight of the total EQLv2 loss. + Defaults to 1.0. + num_classes (int): 1203 for lvis v1.0, 1230 for lvis v0.5. + use_distributed (bool, float): EQLv2 will calculate the gradients + on all GPUs if there is any. Change to True if you are using + distributed training. Default to False. + mu (float, optional): Defaults to 0.8 + alpha (float, optional): A balance factor for the negative part of + EQLV2 Loss. Defaults to 4.0. + gamma (int, optional): The gamma for calculating the modulating + factor. Defaults to 12. + vis_grad (bool, optional): Default to False. + test_with_obj (bool, optional): Default to True. + + Returns: + None. + """ + super().__init__() + self.use_sigmoid = True + self.reduction = reduction + self.loss_weight = loss_weight + self.class_weight = class_weight + self.num_classes = num_classes + self.group = True + + # cfg for eqlv2 + self.vis_grad = vis_grad + self.mu = mu + self.alpha = alpha + self.gamma = gamma + self.use_distributed = use_distributed + + # initial variables + self.register_buffer('pos_grad', torch.zeros(self.num_classes)) + self.register_buffer('neg_grad', torch.zeros(self.num_classes)) + # At the beginning of training, we set a high value (eg. 100) + # for the initial gradient ratio so that the weight for pos + # gradients and neg gradients are 1. + self.register_buffer('pos_neg', torch.ones(self.num_classes) * 100) + + self.test_with_obj = test_with_obj + + def _func(x, gamma, mu): + return 1 / (1 + torch.exp(-gamma * (x - mu))) + + self.map_func = partial(_func, gamma=self.gamma, mu=self.mu) + + print_log( + f'build EQL v2, gamma: {gamma}, mu: {mu}, alpha: {alpha}', + logger='current', + level=logging.DEBUG) + + def forward(self, + cls_score: Tensor, + label: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[Tensor] = None) -> Tensor: + """`Equalization Loss v2 `_ + + Args: + cls_score (Tensor): The prediction with shape (N, C), C is the + number of classes. + label (Tensor): The ground truth label of the predicted target with + shape (N, C), C is the number of classes. + weight (Tensor, optional): The weight of loss for each prediction. + Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Options are "none", "mean" and "sum". + + Returns: + Tensor: The calculated loss + """ + self.n_i, self.n_c = cls_score.size() + self.gt_classes = label + self.pred_class_logits = cls_score + + def expand_label(pred, gt_classes): + target = pred.new_zeros(self.n_i, self.n_c) + target[torch.arange(self.n_i), gt_classes] = 1 + return target + + target = expand_label(cls_score, label) + + pos_w, neg_w = self.get_weight(cls_score) + + weight = pos_w * target + neg_w * (1 - target) + + cls_loss = F.binary_cross_entropy_with_logits( + cls_score, target, reduction='none') + cls_loss = torch.sum(cls_loss * weight) / self.n_i + + self.collect_grad(cls_score.detach(), target.detach(), weight.detach()) + + return self.loss_weight * cls_loss + + def get_channel_num(self, num_classes): + num_channel = num_classes + 1 + return num_channel + + def get_activation(self, pred): + pred = torch.sigmoid(pred) + n_i, n_c = pred.size() + bg_score = pred[:, -1].view(n_i, 1) + if self.test_with_obj: + pred[:, :-1] *= (1 - bg_score) + return pred + + def collect_grad(self, pred, target, weight): + prob = torch.sigmoid(pred) + grad = target * (prob - 1) + (1 - target) * prob + grad = torch.abs(grad) + + # do not collect grad for objectiveness branch [:-1] + pos_grad = torch.sum(grad * target * weight, dim=0)[:-1] + neg_grad = torch.sum(grad * (1 - target) * weight, dim=0)[:-1] + + if self.use_distributed: + dist.all_reduce(pos_grad) + dist.all_reduce(neg_grad) + + self.pos_grad += pos_grad + self.neg_grad += neg_grad + self.pos_neg = self.pos_grad / (self.neg_grad + 1e-10) + + def get_weight(self, pred): + neg_w = torch.cat([self.map_func(self.pos_neg), pred.new_ones(1)]) + pos_w = 1 + self.alpha * (1 - neg_w) + neg_w = neg_w.view(1, -1).expand(self.n_i, self.n_c) + pos_w = pos_w.view(1, -1).expand(self.n_i, self.n_c) + return pos_w, neg_w diff --git a/mmdetection/mmdet/models/losses/focal_loss.py b/mmdetection/mmdet/models/losses/focal_loss.py new file mode 100644 index 00000000..15bef293 --- /dev/null +++ b/mmdetection/mmdet/models/losses/focal_loss.py @@ -0,0 +1,371 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.ops import sigmoid_focal_loss as _sigmoid_focal_loss + +from mmdet.registry import MODELS +from .accuracy import accuracy +from .utils import weight_reduce_loss + + +# This method is only for debugging +def py_sigmoid_focal_loss(pred, + target, + weight=None, + gamma=2.0, + alpha=0.25, + reduction='mean', + avg_factor=None): + """PyTorch version of `Focal Loss `_. + + Args: + pred (torch.Tensor): The prediction with shape (N, C), C is the + number of classes + target (torch.Tensor): The learning label of the prediction. + weight (torch.Tensor, optional): Sample-wise loss weight. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + alpha (float, optional): A balanced form for Focal Loss. + Defaults to 0.25. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + """ + pred_sigmoid = pred.sigmoid() + target = target.type_as(pred) + # Actually, pt here denotes (1 - pt) in the Focal Loss paper + pt = (1 - pred_sigmoid) * target + pred_sigmoid * (1 - target) + # Thus it's pt.pow(gamma) rather than (1 - pt).pow(gamma) + focal_weight = (alpha * target + (1 - alpha) * + (1 - target)) * pt.pow(gamma) + loss = F.binary_cross_entropy_with_logits( + pred, target, reduction='none') * focal_weight + if weight is not None: + if weight.shape != loss.shape: + if weight.size(0) == loss.size(0): + # For most cases, weight is of shape (num_priors, ), + # which means it does not have the second axis num_class + weight = weight.view(-1, 1) + else: + # Sometimes, weight per anchor per class is also needed. e.g. + # in FSAF. But it may be flattened of shape + # (num_priors x num_class, ), while loss is still of shape + # (num_priors, num_class). + assert weight.numel() == loss.numel() + weight = weight.view(loss.size(0), -1) + assert weight.ndim == loss.ndim + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + + +def py_focal_loss_with_prob(pred, + target, + weight=None, + gamma=2.0, + alpha=0.25, + reduction='mean', + avg_factor=None): + """PyTorch version of `Focal Loss `_. + Different from `py_sigmoid_focal_loss`, this function accepts probability + as input. + + Args: + pred (torch.Tensor): The prediction probability with shape (N, C), + C is the number of classes. + target (torch.Tensor): The learning label of the prediction. + The target shape support (N,C) or (N,), (N,C) means one-hot form. + weight (torch.Tensor, optional): Sample-wise loss weight. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + alpha (float, optional): A balanced form for Focal Loss. + Defaults to 0.25. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + """ + if pred.dim() != target.dim(): + num_classes = pred.size(1) + target = F.one_hot(target, num_classes=num_classes + 1) + target = target[:, :num_classes] + + target = target.type_as(pred) + pt = (1 - pred) * target + pred * (1 - target) + focal_weight = (alpha * target + (1 - alpha) * + (1 - target)) * pt.pow(gamma) + loss = F.binary_cross_entropy( + pred, target, reduction='none') * focal_weight + if weight is not None: + if weight.shape != loss.shape: + if weight.size(0) == loss.size(0): + # For most cases, weight is of shape (num_priors, ), + # which means it does not have the second axis num_class + weight = weight.view(-1, 1) + else: + # Sometimes, weight per anchor per class is also needed. e.g. + # in FSAF. But it may be flattened of shape + # (num_priors x num_class, ), while loss is still of shape + # (num_priors, num_class). + assert weight.numel() == loss.numel() + weight = weight.view(loss.size(0), -1) + assert weight.ndim == loss.ndim + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + + +def sigmoid_focal_loss(pred, + target, + weight=None, + gamma=2.0, + alpha=0.25, + reduction='mean', + avg_factor=None): + r"""A wrapper of cuda version `Focal Loss + `_. + + Args: + pred (torch.Tensor): The prediction with shape (N, C), C is the number + of classes. + target (torch.Tensor): The learning label of the prediction. + weight (torch.Tensor, optional): Sample-wise loss weight. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + alpha (float, optional): A balanced form for Focal Loss. + Defaults to 0.25. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. Options are "none", "mean" and "sum". + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + """ + # Function.apply does not accept keyword arguments, so the decorator + # "weighted_loss" is not applicable + loss = _sigmoid_focal_loss(pred.contiguous(), target.contiguous(), gamma, + alpha, None, 'none') + if weight is not None: + if weight.shape != loss.shape: + if weight.size(0) == loss.size(0): + # For most cases, weight is of shape (num_priors, ), + # which means it does not have the second axis num_class + weight = weight.view(-1, 1) + else: + # Sometimes, weight per anchor per class is also needed. e.g. + # in FSAF. But it may be flattened of shape + # (num_priors x num_class, ), while loss is still of shape + # (num_priors, num_class). + assert weight.numel() == loss.numel() + weight = weight.view(loss.size(0), -1) + assert weight.ndim == loss.ndim + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + + +@MODELS.register_module() +class FocalLoss(nn.Module): + + def __init__(self, + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + reduction='mean', + loss_weight=1.0, + activated=False): + """`Focal Loss `_ + + Args: + use_sigmoid (bool, optional): Whether to the prediction is + used for sigmoid or softmax. Defaults to True. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + alpha (float, optional): A balanced form for Focal Loss. + Defaults to 0.25. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. Options are "none", "mean" and + "sum". + loss_weight (float, optional): Weight of loss. Defaults to 1.0. + activated (bool, optional): Whether the input is activated. + If True, it means the input has been activated and can be + treated as probabilities. Else, it should be treated as logits. + Defaults to False. + """ + super(FocalLoss, self).__init__() + assert use_sigmoid is True, 'Only sigmoid focal loss supported now.' + self.use_sigmoid = use_sigmoid + self.gamma = gamma + self.alpha = alpha + self.reduction = reduction + self.loss_weight = loss_weight + self.activated = activated + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None): + """Forward function. + + Args: + pred (torch.Tensor): The prediction. + target (torch.Tensor): The learning label of the prediction. + The target shape support (N,C) or (N,), (N,C) means + one-hot form. + weight (torch.Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Options are "none", "mean" and "sum". + + Returns: + torch.Tensor: The calculated loss + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if self.use_sigmoid: + if self.activated: + calculate_loss_func = py_focal_loss_with_prob + else: + if pred.dim() == target.dim(): + # this means that target is already in One-Hot form. + calculate_loss_func = py_sigmoid_focal_loss + elif torch.cuda.is_available() and pred.is_cuda: + calculate_loss_func = sigmoid_focal_loss + else: + num_classes = pred.size(1) + target = F.one_hot(target, num_classes=num_classes + 1) + target = target[:, :num_classes] + calculate_loss_func = py_sigmoid_focal_loss + + loss_cls = self.loss_weight * calculate_loss_func( + pred, + target, + weight, + gamma=self.gamma, + alpha=self.alpha, + reduction=reduction, + avg_factor=avg_factor) + + else: + raise NotImplementedError + return loss_cls + + +@MODELS.register_module() +class FocalCustomLoss(nn.Module): + + def __init__(self, + use_sigmoid=True, + num_classes=-1, + gamma=2.0, + alpha=0.25, + reduction='mean', + loss_weight=1.0, + activated=False): + """`Focal Loss for V3Det `_ + + Args: + use_sigmoid (bool, optional): Whether to the prediction is + used for sigmoid or softmax. Defaults to True. + num_classes (int): Number of classes to classify. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + alpha (float, optional): A balanced form for Focal Loss. + Defaults to 0.25. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. Options are "none", "mean" and + "sum". + loss_weight (float, optional): Weight of loss. Defaults to 1.0. + activated (bool, optional): Whether the input is activated. + If True, it means the input has been activated and can be + treated as probabilities. Else, it should be treated as logits. + Defaults to False. + """ + super(FocalCustomLoss, self).__init__() + assert use_sigmoid is True, 'Only sigmoid focal loss supported now.' + self.use_sigmoid = use_sigmoid + self.num_classes = num_classes + self.gamma = gamma + self.alpha = alpha + self.reduction = reduction + self.loss_weight = loss_weight + self.activated = activated + + assert self.num_classes != -1 + + # custom output channels of the classifier + self.custom_cls_channels = True + # custom activation of cls_score + self.custom_activation = True + # custom accuracy of the classsifier + self.custom_accuracy = True + + def get_cls_channels(self, num_classes): + assert num_classes == self.num_classes + return num_classes + + def get_activation(self, cls_score): + + fine_cls_score = cls_score[:, :self.num_classes] + + score_classes = fine_cls_score.sigmoid() + + return score_classes + + def get_accuracy(self, cls_score, labels): + + fine_cls_score = cls_score[:, :self.num_classes] + + pos_inds = labels < self.num_classes + acc_classes = accuracy(fine_cls_score[pos_inds], labels[pos_inds]) + acc = dict() + acc['acc_classes'] = acc_classes + return acc + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None): + """Forward function. + + Args: + pred (torch.Tensor): The prediction. + target (torch.Tensor): The learning label of the prediction. + weight (torch.Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Options are "none", "mean" and "sum". + + Returns: + torch.Tensor: The calculated loss + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if self.use_sigmoid: + + num_classes = pred.size(1) + target = F.one_hot(target, num_classes=num_classes + 1) + target = target[:, :num_classes] + calculate_loss_func = py_sigmoid_focal_loss + + loss_cls = self.loss_weight * calculate_loss_func( + pred, + target, + weight, + gamma=self.gamma, + alpha=self.alpha, + reduction=reduction, + avg_factor=avg_factor) + + else: + raise NotImplementedError + return loss_cls diff --git a/mmdetection/mmdet/models/losses/gaussian_focal_loss.py b/mmdetection/mmdet/models/losses/gaussian_focal_loss.py new file mode 100644 index 00000000..14fa8da4 --- /dev/null +++ b/mmdetection/mmdet/models/losses/gaussian_focal_loss.py @@ -0,0 +1,186 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Union + +import torch.nn as nn +from torch import Tensor + +from mmdet.registry import MODELS +from .utils import weight_reduce_loss, weighted_loss + + +@weighted_loss +def gaussian_focal_loss(pred: Tensor, + gaussian_target: Tensor, + alpha: float = 2.0, + gamma: float = 4.0, + pos_weight: float = 1.0, + neg_weight: float = 1.0) -> Tensor: + """`Focal Loss `_ for targets in gaussian + distribution. + + Args: + pred (torch.Tensor): The prediction. + gaussian_target (torch.Tensor): The learning target of the prediction + in gaussian distribution. + alpha (float, optional): A balanced form for Focal Loss. + Defaults to 2.0. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 4.0. + pos_weight(float): Positive sample loss weight. Defaults to 1.0. + neg_weight(float): Negative sample loss weight. Defaults to 1.0. + """ + eps = 1e-12 + pos_weights = gaussian_target.eq(1) + neg_weights = (1 - gaussian_target).pow(gamma) + pos_loss = -(pred + eps).log() * (1 - pred).pow(alpha) * pos_weights + neg_loss = -(1 - pred + eps).log() * pred.pow(alpha) * neg_weights + return pos_weight * pos_loss + neg_weight * neg_loss + + +def gaussian_focal_loss_with_pos_inds( + pred: Tensor, + gaussian_target: Tensor, + pos_inds: Tensor, + pos_labels: Tensor, + alpha: float = 2.0, + gamma: float = 4.0, + pos_weight: float = 1.0, + neg_weight: float = 1.0, + reduction: str = 'mean', + avg_factor: Optional[Union[int, float]] = None) -> Tensor: + """`Focal Loss `_ for targets in gaussian + distribution. + + Note: The index with a value of 1 in ``gaussian_target`` in the + ``gaussian_focal_loss`` function is a positive sample, but in + ``gaussian_focal_loss_with_pos_inds`` the positive sample is passed + in through the ``pos_inds`` parameter. + + Args: + pred (torch.Tensor): The prediction. The shape is (N, num_classes). + gaussian_target (torch.Tensor): The learning target of the prediction + in gaussian distribution. The shape is (N, num_classes). + pos_inds (torch.Tensor): The positive sample index. + The shape is (M, ). + pos_labels (torch.Tensor): The label corresponding to the positive + sample index. The shape is (M, ). + alpha (float, optional): A balanced form for Focal Loss. + Defaults to 2.0. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 4.0. + pos_weight(float): Positive sample loss weight. Defaults to 1.0. + neg_weight(float): Negative sample loss weight. Defaults to 1.0. + reduction (str): Options are "none", "mean" and "sum". + Defaults to 'mean`. + avg_factor (int, float, optional): Average factor that is used to + average the loss. Defaults to None. + """ + eps = 1e-12 + neg_weights = (1 - gaussian_target).pow(gamma) + + pos_pred_pix = pred[pos_inds] + pos_pred = pos_pred_pix.gather(1, pos_labels.unsqueeze(1)) + pos_loss = -(pos_pred + eps).log() * (1 - pos_pred).pow(alpha) + pos_loss = weight_reduce_loss(pos_loss, None, reduction, avg_factor) + + neg_loss = -(1 - pred + eps).log() * pred.pow(alpha) * neg_weights + neg_loss = weight_reduce_loss(neg_loss, None, reduction, avg_factor) + + return pos_weight * pos_loss + neg_weight * neg_loss + + +@MODELS.register_module() +class GaussianFocalLoss(nn.Module): + """GaussianFocalLoss is a variant of focal loss. + + More details can be found in the `paper + `_ + Code is modified from `kp_utils.py + `_ # noqa: E501 + Please notice that the target in GaussianFocalLoss is a gaussian heatmap, + not 0/1 binary target. + + Args: + alpha (float): Power of prediction. + gamma (float): Power of target for negative samples. + reduction (str): Options are "none", "mean" and "sum". + loss_weight (float): Loss weight of current loss. + pos_weight(float): Positive sample loss weight. Defaults to 1.0. + neg_weight(float): Negative sample loss weight. Defaults to 1.0. + """ + + def __init__(self, + alpha: float = 2.0, + gamma: float = 4.0, + reduction: str = 'mean', + loss_weight: float = 1.0, + pos_weight: float = 1.0, + neg_weight: float = 1.0) -> None: + super().__init__() + self.alpha = alpha + self.gamma = gamma + self.reduction = reduction + self.loss_weight = loss_weight + self.pos_weight = pos_weight + self.neg_weight = neg_weight + + def forward(self, + pred: Tensor, + target: Tensor, + pos_inds: Optional[Tensor] = None, + pos_labels: Optional[Tensor] = None, + weight: Optional[Tensor] = None, + avg_factor: Optional[Union[int, float]] = None, + reduction_override: Optional[str] = None) -> Tensor: + """Forward function. + + If you want to manually determine which positions are + positive samples, you can set the pos_index and pos_label + parameter. Currently, only the CenterNet update version uses + the parameter. + + Args: + pred (torch.Tensor): The prediction. The shape is (N, num_classes). + target (torch.Tensor): The learning target of the prediction + in gaussian distribution. The shape is (N, num_classes). + pos_inds (torch.Tensor): The positive sample index. + Defaults to None. + pos_labels (torch.Tensor): The label corresponding to the positive + sample index. Defaults to None. + weight (torch.Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, float, optional): Average factor that is used to + average the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if pos_inds is not None: + assert pos_labels is not None + # Only used by centernet update version + loss_reg = self.loss_weight * gaussian_focal_loss_with_pos_inds( + pred, + target, + pos_inds, + pos_labels, + alpha=self.alpha, + gamma=self.gamma, + pos_weight=self.pos_weight, + neg_weight=self.neg_weight, + reduction=reduction, + avg_factor=avg_factor) + else: + loss_reg = self.loss_weight * gaussian_focal_loss( + pred, + target, + weight, + alpha=self.alpha, + gamma=self.gamma, + pos_weight=self.pos_weight, + neg_weight=self.neg_weight, + reduction=reduction, + avg_factor=avg_factor) + return loss_reg diff --git a/mmdetection/mmdet/models/losses/gfocal_loss.py b/mmdetection/mmdet/models/losses/gfocal_loss.py new file mode 100644 index 00000000..b3a11722 --- /dev/null +++ b/mmdetection/mmdet/models/losses/gfocal_loss.py @@ -0,0 +1,295 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from functools import partial + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from mmdet.models.losses.utils import weighted_loss +from mmdet.registry import MODELS + + +@weighted_loss +def quality_focal_loss(pred, target, beta=2.0): + r"""Quality Focal Loss (QFL) is from `Generalized Focal Loss: Learning + Qualified and Distributed Bounding Boxes for Dense Object Detection + `_. + + Args: + pred (torch.Tensor): Predicted joint representation of classification + and quality (IoU) estimation with shape (N, C), C is the number of + classes. + target (tuple([torch.Tensor])): Target category label with shape (N,) + and target quality label with shape (N,). + beta (float): The beta parameter for calculating the modulating factor. + Defaults to 2.0. + + Returns: + torch.Tensor: Loss tensor with shape (N,). + """ + assert len(target) == 2, """target for QFL must be a tuple of two elements, + including category label and quality label, respectively""" + # label denotes the category id, score denotes the quality score + label, score = target + + # negatives are supervised by 0 quality score + pred_sigmoid = pred.sigmoid() + scale_factor = pred_sigmoid + zerolabel = scale_factor.new_zeros(pred.shape) + loss = F.binary_cross_entropy_with_logits( + pred, zerolabel, reduction='none') * scale_factor.pow(beta) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = pred.size(1) + pos = ((label >= 0) & (label < bg_class_ind)).nonzero().squeeze(1) + pos_label = label[pos].long() + # positives are supervised by bbox quality (IoU) score + scale_factor = score[pos] - pred_sigmoid[pos, pos_label] + loss[pos, pos_label] = F.binary_cross_entropy_with_logits( + pred[pos, pos_label], score[pos], + reduction='none') * scale_factor.abs().pow(beta) + + loss = loss.sum(dim=1, keepdim=False) + return loss + + +@weighted_loss +def quality_focal_loss_tensor_target(pred, target, beta=2.0, activated=False): + """`QualityFocal Loss `_ + Args: + pred (torch.Tensor): The prediction with shape (N, C), C is the + number of classes + target (torch.Tensor): The learning target of the iou-aware + classification score with shape (N, C), C is the number of classes. + beta (float): The beta parameter for calculating the modulating factor. + Defaults to 2.0. + activated (bool): Whether the input is activated. + If True, it means the input has been activated and can be + treated as probabilities. Else, it should be treated as logits. + Defaults to False. + """ + # pred and target should be of the same size + assert pred.size() == target.size() + if activated: + pred_sigmoid = pred + loss_function = F.binary_cross_entropy + else: + pred_sigmoid = pred.sigmoid() + loss_function = F.binary_cross_entropy_with_logits + + scale_factor = pred_sigmoid + target = target.type_as(pred) + + zerolabel = scale_factor.new_zeros(pred.shape) + loss = loss_function( + pred, zerolabel, reduction='none') * scale_factor.pow(beta) + + pos = (target != 0) + scale_factor = target[pos] - pred_sigmoid[pos] + loss[pos] = loss_function( + pred[pos], target[pos], + reduction='none') * scale_factor.abs().pow(beta) + + loss = loss.sum(dim=1, keepdim=False) + return loss + + +@weighted_loss +def quality_focal_loss_with_prob(pred, target, beta=2.0): + r"""Quality Focal Loss (QFL) is from `Generalized Focal Loss: Learning + Qualified and Distributed Bounding Boxes for Dense Object Detection + `_. + Different from `quality_focal_loss`, this function accepts probability + as input. + + Args: + pred (torch.Tensor): Predicted joint representation of classification + and quality (IoU) estimation with shape (N, C), C is the number of + classes. + target (tuple([torch.Tensor])): Target category label with shape (N,) + and target quality label with shape (N,). + beta (float): The beta parameter for calculating the modulating factor. + Defaults to 2.0. + + Returns: + torch.Tensor: Loss tensor with shape (N,). + """ + assert len(target) == 2, """target for QFL must be a tuple of two elements, + including category label and quality label, respectively""" + # label denotes the category id, score denotes the quality score + label, score = target + + # negatives are supervised by 0 quality score + pred_sigmoid = pred + scale_factor = pred_sigmoid + zerolabel = scale_factor.new_zeros(pred.shape) + loss = F.binary_cross_entropy( + pred, zerolabel, reduction='none') * scale_factor.pow(beta) + + # FG cat_id: [0, num_classes -1], BG cat_id: num_classes + bg_class_ind = pred.size(1) + pos = ((label >= 0) & (label < bg_class_ind)).nonzero().squeeze(1) + pos_label = label[pos].long() + # positives are supervised by bbox quality (IoU) score + scale_factor = score[pos] - pred_sigmoid[pos, pos_label] + loss[pos, pos_label] = F.binary_cross_entropy( + pred[pos, pos_label], score[pos], + reduction='none') * scale_factor.abs().pow(beta) + + loss = loss.sum(dim=1, keepdim=False) + return loss + + +@weighted_loss +def distribution_focal_loss(pred, label): + r"""Distribution Focal Loss (DFL) is from `Generalized Focal Loss: Learning + Qualified and Distributed Bounding Boxes for Dense Object Detection + `_. + + Args: + pred (torch.Tensor): Predicted general distribution of bounding boxes + (before softmax) with shape (N, n+1), n is the max value of the + integral set `{0, ..., n}` in paper. + label (torch.Tensor): Target distance label for bounding boxes with + shape (N,). + + Returns: + torch.Tensor: Loss tensor with shape (N,). + """ + dis_left = label.long() + dis_right = dis_left + 1 + weight_left = dis_right.float() - label + weight_right = label - dis_left.float() + loss = F.cross_entropy(pred, dis_left, reduction='none') * weight_left \ + + F.cross_entropy(pred, dis_right, reduction='none') * weight_right + return loss + + +@MODELS.register_module() +class QualityFocalLoss(nn.Module): + r"""Quality Focal Loss (QFL) is a variant of `Generalized Focal Loss: + Learning Qualified and Distributed Bounding Boxes for Dense Object + Detection `_. + + Args: + use_sigmoid (bool): Whether sigmoid operation is conducted in QFL. + Defaults to True. + beta (float): The beta parameter for calculating the modulating factor. + Defaults to 2.0. + reduction (str): Options are "none", "mean" and "sum". + loss_weight (float): Loss weight of current loss. + activated (bool, optional): Whether the input is activated. + If True, it means the input has been activated and can be + treated as probabilities. Else, it should be treated as logits. + Defaults to False. + """ + + def __init__(self, + use_sigmoid=True, + beta=2.0, + reduction='mean', + loss_weight=1.0, + activated=False): + super(QualityFocalLoss, self).__init__() + assert use_sigmoid is True, 'Only sigmoid in QFL supported now.' + self.use_sigmoid = use_sigmoid + self.beta = beta + self.reduction = reduction + self.loss_weight = loss_weight + self.activated = activated + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None): + """Forward function. + + Args: + pred (torch.Tensor): Predicted joint representation of + classification and quality (IoU) estimation with shape (N, C), + C is the number of classes. + target (Union(tuple([torch.Tensor]),Torch.Tensor)): The type is + tuple, it should be included Target category label with + shape (N,) and target quality label with shape (N,).The type + is torch.Tensor, the target should be one-hot form with + soft weights. + weight (torch.Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if self.use_sigmoid: + if self.activated: + calculate_loss_func = quality_focal_loss_with_prob + else: + calculate_loss_func = quality_focal_loss + if isinstance(target, torch.Tensor): + # the target shape with (N,C) or (N,C,...), which means + # the target is one-hot form with soft weights. + calculate_loss_func = partial( + quality_focal_loss_tensor_target, activated=self.activated) + + loss_cls = self.loss_weight * calculate_loss_func( + pred, + target, + weight, + beta=self.beta, + reduction=reduction, + avg_factor=avg_factor) + else: + raise NotImplementedError + return loss_cls + + +@MODELS.register_module() +class DistributionFocalLoss(nn.Module): + r"""Distribution Focal Loss (DFL) is a variant of `Generalized Focal Loss: + Learning Qualified and Distributed Bounding Boxes for Dense Object + Detection `_. + + Args: + reduction (str): Options are `'none'`, `'mean'` and `'sum'`. + loss_weight (float): Loss weight of current loss. + """ + + def __init__(self, reduction='mean', loss_weight=1.0): + super(DistributionFocalLoss, self).__init__() + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None): + """Forward function. + + Args: + pred (torch.Tensor): Predicted general distribution of bounding + boxes (before softmax) with shape (N, n+1), n is the max value + of the integral set `{0, ..., n}` in paper. + target (torch.Tensor): Target distance label for bounding boxes + with shape (N,). + weight (torch.Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss_cls = self.loss_weight * distribution_focal_loss( + pred, target, weight, reduction=reduction, avg_factor=avg_factor) + return loss_cls diff --git a/mmdetection/mmdet/models/losses/ghm_loss.py b/mmdetection/mmdet/models/losses/ghm_loss.py new file mode 100644 index 00000000..a874c003 --- /dev/null +++ b/mmdetection/mmdet/models/losses/ghm_loss.py @@ -0,0 +1,213 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from mmdet.registry import MODELS +from .utils import weight_reduce_loss + + +def _expand_onehot_labels(labels, label_weights, label_channels): + bin_labels = labels.new_full((labels.size(0), label_channels), 0) + inds = torch.nonzero( + (labels >= 0) & (labels < label_channels), as_tuple=False).squeeze() + if inds.numel() > 0: + bin_labels[inds, labels[inds]] = 1 + bin_label_weights = label_weights.view(-1, 1).expand( + label_weights.size(0), label_channels) + return bin_labels, bin_label_weights + + +# TODO: code refactoring to make it consistent with other losses +@MODELS.register_module() +class GHMC(nn.Module): + """GHM Classification Loss. + + Details of the theorem can be viewed in the paper + `Gradient Harmonized Single-stage Detector + `_. + + Args: + bins (int): Number of the unit regions for distribution calculation. + momentum (float): The parameter for moving average. + use_sigmoid (bool): Can only be true for BCE based loss now. + loss_weight (float): The weight of the total GHM-C loss. + reduction (str): Options are "none", "mean" and "sum". + Defaults to "mean" + """ + + def __init__(self, + bins=10, + momentum=0, + use_sigmoid=True, + loss_weight=1.0, + reduction='mean'): + super(GHMC, self).__init__() + self.bins = bins + self.momentum = momentum + edges = torch.arange(bins + 1).float() / bins + self.register_buffer('edges', edges) + self.edges[-1] += 1e-6 + if momentum > 0: + acc_sum = torch.zeros(bins) + self.register_buffer('acc_sum', acc_sum) + self.use_sigmoid = use_sigmoid + if not self.use_sigmoid: + raise NotImplementedError + self.loss_weight = loss_weight + self.reduction = reduction + + def forward(self, + pred, + target, + label_weight, + reduction_override=None, + **kwargs): + """Calculate the GHM-C loss. + + Args: + pred (float tensor of size [batch_num, class_num]): + The direct prediction of classification fc layer. + target (float tensor of size [batch_num, class_num]): + Binary class target for each sample. + label_weight (float tensor of size [batch_num, class_num]): + the value is 1 if the sample is valid and 0 if ignored. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + Returns: + The gradient harmonized loss. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + # the target should be binary class label + if pred.dim() != target.dim(): + target, label_weight = _expand_onehot_labels( + target, label_weight, pred.size(-1)) + target, label_weight = target.float(), label_weight.float() + edges = self.edges + mmt = self.momentum + weights = torch.zeros_like(pred) + + # gradient length + g = torch.abs(pred.sigmoid().detach() - target) + + valid = label_weight > 0 + tot = max(valid.float().sum().item(), 1.0) + n = 0 # n valid bins + for i in range(self.bins): + inds = (g >= edges[i]) & (g < edges[i + 1]) & valid + num_in_bin = inds.sum().item() + if num_in_bin > 0: + if mmt > 0: + self.acc_sum[i] = mmt * self.acc_sum[i] \ + + (1 - mmt) * num_in_bin + weights[inds] = tot / self.acc_sum[i] + else: + weights[inds] = tot / num_in_bin + n += 1 + if n > 0: + weights = weights / n + + loss = F.binary_cross_entropy_with_logits( + pred, target, reduction='none') + loss = weight_reduce_loss( + loss, weights, reduction=reduction, avg_factor=tot) + return loss * self.loss_weight + + +# TODO: code refactoring to make it consistent with other losses +@MODELS.register_module() +class GHMR(nn.Module): + """GHM Regression Loss. + + Details of the theorem can be viewed in the paper + `Gradient Harmonized Single-stage Detector + `_. + + Args: + mu (float): The parameter for the Authentic Smooth L1 loss. + bins (int): Number of the unit regions for distribution calculation. + momentum (float): The parameter for moving average. + loss_weight (float): The weight of the total GHM-R loss. + reduction (str): Options are "none", "mean" and "sum". + Defaults to "mean" + """ + + def __init__(self, + mu=0.02, + bins=10, + momentum=0, + loss_weight=1.0, + reduction='mean'): + super(GHMR, self).__init__() + self.mu = mu + self.bins = bins + edges = torch.arange(bins + 1).float() / bins + self.register_buffer('edges', edges) + self.edges[-1] = 1e3 + self.momentum = momentum + if momentum > 0: + acc_sum = torch.zeros(bins) + self.register_buffer('acc_sum', acc_sum) + self.loss_weight = loss_weight + self.reduction = reduction + + # TODO: support reduction parameter + def forward(self, + pred, + target, + label_weight, + avg_factor=None, + reduction_override=None): + """Calculate the GHM-R loss. + + Args: + pred (float tensor of size [batch_num, 4 (* class_num)]): + The prediction of box regression layer. Channel number can be 4 + or 4 * class_num depending on whether it is class-agnostic. + target (float tensor of size [batch_num, 4 (* class_num)]): + The target regression values with the same size of pred. + label_weight (float tensor of size [batch_num, 4 (* class_num)]): + The weight of each sample, 0 if ignored. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + Returns: + The gradient harmonized loss. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + mu = self.mu + edges = self.edges + mmt = self.momentum + + # ASL1 loss + diff = pred - target + loss = torch.sqrt(diff * diff + mu * mu) - mu + + # gradient length + g = torch.abs(diff / torch.sqrt(mu * mu + diff * diff)).detach() + weights = torch.zeros_like(g) + + valid = label_weight > 0 + tot = max(label_weight.float().sum().item(), 1.0) + n = 0 # n: valid bins + for i in range(self.bins): + inds = (g >= edges[i]) & (g < edges[i + 1]) & valid + num_in_bin = inds.sum().item() + if num_in_bin > 0: + n += 1 + if mmt > 0: + self.acc_sum[i] = mmt * self.acc_sum[i] \ + + (1 - mmt) * num_in_bin + weights[inds] = tot / self.acc_sum[i] + else: + weights[inds] = tot / num_in_bin + if n > 0: + weights /= n + loss = weight_reduce_loss( + loss, weights, reduction=reduction, avg_factor=tot) + return loss * self.loss_weight diff --git a/mmdetection/mmdet/models/losses/iou_loss.py b/mmdetection/mmdet/models/losses/iou_loss.py new file mode 100644 index 00000000..c8a2b977 --- /dev/null +++ b/mmdetection/mmdet/models/losses/iou_loss.py @@ -0,0 +1,926 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +import warnings +from typing import Optional + +import torch +import torch.nn as nn +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures.bbox import bbox_overlaps +from .utils import weighted_loss + + +@weighted_loss +def iou_loss(pred: Tensor, + target: Tensor, + linear: bool = False, + mode: str = 'log', + eps: float = 1e-6) -> Tensor: + """IoU loss. + + Computing the IoU loss between a set of predicted bboxes and target bboxes. + The loss is calculated as negative log of IoU. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): Corresponding gt bboxes, shape (n, 4). + linear (bool, optional): If True, use linear scale of loss instead of + log scale. Default: False. + mode (str): Loss scaling mode, including "linear", "square", and "log". + Default: 'log' + eps (float): Epsilon to avoid log(0). + + Return: + Tensor: Loss tensor. + """ + assert mode in ['linear', 'square', 'log'] + if linear: + mode = 'linear' + warnings.warn('DeprecationWarning: Setting "linear=True" in ' + 'iou_loss is deprecated, please use "mode=`linear`" ' + 'instead.') + # avoid fp16 overflow + if pred.dtype == torch.float16: + fp16 = True + pred = pred.to(torch.float32) + else: + fp16 = False + + ious = bbox_overlaps(pred, target, is_aligned=True).clamp(min=eps) + + if fp16: + ious = ious.to(torch.float16) + + if mode == 'linear': + loss = 1 - ious + elif mode == 'square': + loss = 1 - ious**2 + elif mode == 'log': + loss = -ious.log() + else: + raise NotImplementedError + return loss + + +@weighted_loss +def bounded_iou_loss(pred: Tensor, + target: Tensor, + beta: float = 0.2, + eps: float = 1e-3) -> Tensor: + """BIoULoss. + + This is an implementation of paper + `Improving Object Localization with Fitness NMS and Bounded IoU Loss. + `_. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): Corresponding gt bboxes, shape (n, 4). + beta (float, optional): Beta parameter in smoothl1. + eps (float, optional): Epsilon to avoid NaN values. + + Return: + Tensor: Loss tensor. + """ + pred_ctrx = (pred[:, 0] + pred[:, 2]) * 0.5 + pred_ctry = (pred[:, 1] + pred[:, 3]) * 0.5 + pred_w = pred[:, 2] - pred[:, 0] + pred_h = pred[:, 3] - pred[:, 1] + with torch.no_grad(): + target_ctrx = (target[:, 0] + target[:, 2]) * 0.5 + target_ctry = (target[:, 1] + target[:, 3]) * 0.5 + target_w = target[:, 2] - target[:, 0] + target_h = target[:, 3] - target[:, 1] + + dx = target_ctrx - pred_ctrx + dy = target_ctry - pred_ctry + + loss_dx = 1 - torch.max( + (target_w - 2 * dx.abs()) / + (target_w + 2 * dx.abs() + eps), torch.zeros_like(dx)) + loss_dy = 1 - torch.max( + (target_h - 2 * dy.abs()) / + (target_h + 2 * dy.abs() + eps), torch.zeros_like(dy)) + loss_dw = 1 - torch.min(target_w / (pred_w + eps), pred_w / + (target_w + eps)) + loss_dh = 1 - torch.min(target_h / (pred_h + eps), pred_h / + (target_h + eps)) + # view(..., -1) does not work for empty tensor + loss_comb = torch.stack([loss_dx, loss_dy, loss_dw, loss_dh], + dim=-1).flatten(1) + + loss = torch.where(loss_comb < beta, 0.5 * loss_comb * loss_comb / beta, + loss_comb - 0.5 * beta) + return loss + + +@weighted_loss +def giou_loss(pred: Tensor, target: Tensor, eps: float = 1e-7) -> Tensor: + r"""`Generalized Intersection over Union: A Metric and A Loss for Bounding + Box Regression `_. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): Corresponding gt bboxes, shape (n, 4). + eps (float): Epsilon to avoid log(0). + + Return: + Tensor: Loss tensor. + """ + # avoid fp16 overflow + if pred.dtype == torch.float16: + fp16 = True + pred = pred.to(torch.float32) + else: + fp16 = False + + gious = bbox_overlaps(pred, target, mode='giou', is_aligned=True, eps=eps) + + if fp16: + gious = gious.to(torch.float16) + + loss = 1 - gious + return loss + + +@weighted_loss +def diou_loss(pred: Tensor, target: Tensor, eps: float = 1e-7) -> Tensor: + r"""Implementation of `Distance-IoU Loss: Faster and Better + Learning for Bounding Box Regression https://arxiv.org/abs/1911.08287`_. + + Code is modified from https://github.com/Zzh-tju/DIoU. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): Corresponding gt bboxes, shape (n, 4). + eps (float): Epsilon to avoid log(0). + + Return: + Tensor: Loss tensor. + """ + # overlap + lt = torch.max(pred[:, :2], target[:, :2]) + rb = torch.min(pred[:, 2:], target[:, 2:]) + wh = (rb - lt).clamp(min=0) + overlap = wh[:, 0] * wh[:, 1] + + # union + ap = (pred[:, 2] - pred[:, 0]) * (pred[:, 3] - pred[:, 1]) + ag = (target[:, 2] - target[:, 0]) * (target[:, 3] - target[:, 1]) + union = ap + ag - overlap + eps + + # IoU + ious = overlap / union + + # enclose area + enclose_x1y1 = torch.min(pred[:, :2], target[:, :2]) + enclose_x2y2 = torch.max(pred[:, 2:], target[:, 2:]) + enclose_wh = (enclose_x2y2 - enclose_x1y1).clamp(min=0) + + cw = enclose_wh[:, 0] + ch = enclose_wh[:, 1] + + c2 = cw**2 + ch**2 + eps + + b1_x1, b1_y1 = pred[:, 0], pred[:, 1] + b1_x2, b1_y2 = pred[:, 2], pred[:, 3] + b2_x1, b2_y1 = target[:, 0], target[:, 1] + b2_x2, b2_y2 = target[:, 2], target[:, 3] + + left = ((b2_x1 + b2_x2) - (b1_x1 + b1_x2))**2 / 4 + right = ((b2_y1 + b2_y2) - (b1_y1 + b1_y2))**2 / 4 + rho2 = left + right + + # DIoU + dious = ious - rho2 / c2 + loss = 1 - dious + return loss + + +@weighted_loss +def ciou_loss(pred: Tensor, target: Tensor, eps: float = 1e-7) -> Tensor: + r"""`Implementation of paper `Enhancing Geometric Factors into + Model Learning and Inference for Object Detection and Instance + Segmentation `_. + + Code is modified from https://github.com/Zzh-tju/CIoU. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): Corresponding gt bboxes, shape (n, 4). + eps (float): Epsilon to avoid log(0). + + Return: + Tensor: Loss tensor. + """ + # overlap + lt = torch.max(pred[:, :2], target[:, :2]) + rb = torch.min(pred[:, 2:], target[:, 2:]) + wh = (rb - lt).clamp(min=0) + overlap = wh[:, 0] * wh[:, 1] + + # union + ap = (pred[:, 2] - pred[:, 0]) * (pred[:, 3] - pred[:, 1]) + ag = (target[:, 2] - target[:, 0]) * (target[:, 3] - target[:, 1]) + union = ap + ag - overlap + eps + + # IoU + ious = overlap / union + + # enclose area + enclose_x1y1 = torch.min(pred[:, :2], target[:, :2]) + enclose_x2y2 = torch.max(pred[:, 2:], target[:, 2:]) + enclose_wh = (enclose_x2y2 - enclose_x1y1).clamp(min=0) + + cw = enclose_wh[:, 0] + ch = enclose_wh[:, 1] + + c2 = cw**2 + ch**2 + eps + + b1_x1, b1_y1 = pred[:, 0], pred[:, 1] + b1_x2, b1_y2 = pred[:, 2], pred[:, 3] + b2_x1, b2_y1 = target[:, 0], target[:, 1] + b2_x2, b2_y2 = target[:, 2], target[:, 3] + + w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps + w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps + + left = ((b2_x1 + b2_x2) - (b1_x1 + b1_x2))**2 / 4 + right = ((b2_y1 + b2_y2) - (b1_y1 + b1_y2))**2 / 4 + rho2 = left + right + + factor = 4 / math.pi**2 + v = factor * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2) + + with torch.no_grad(): + alpha = (ious > 0.5).float() * v / (1 - ious + v) + + # CIoU + cious = ious - (rho2 / c2 + alpha * v) + loss = 1 - cious.clamp(min=-1.0, max=1.0) + return loss + + +@weighted_loss +def eiou_loss(pred: Tensor, + target: Tensor, + smooth_point: float = 0.1, + eps: float = 1e-7) -> Tensor: + r"""Implementation of paper `Extended-IoU Loss: A Systematic + IoU-Related Method: Beyond Simplified Regression for Better + Localization `_ + + Code is modified from https://github.com//ShiqiYu/libfacedetection.train. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): Corresponding gt bboxes, shape (n, 4). + smooth_point (float): hyperparameter, default is 0.1. + eps (float): Epsilon to avoid log(0). + + Return: + Tensor: Loss tensor. + """ + px1, py1, px2, py2 = pred[:, 0], pred[:, 1], pred[:, 2], pred[:, 3] + tx1, ty1, tx2, ty2 = target[:, 0], target[:, 1], target[:, 2], target[:, 3] + + # extent top left + ex1 = torch.min(px1, tx1) + ey1 = torch.min(py1, ty1) + + # intersection coordinates + ix1 = torch.max(px1, tx1) + iy1 = torch.max(py1, ty1) + ix2 = torch.min(px2, tx2) + iy2 = torch.min(py2, ty2) + + # extra + xmin = torch.min(ix1, ix2) + ymin = torch.min(iy1, iy2) + xmax = torch.max(ix1, ix2) + ymax = torch.max(iy1, iy2) + + # Intersection + intersection = (ix2 - ex1) * (iy2 - ey1) + (xmin - ex1) * (ymin - ey1) - ( + ix1 - ex1) * (ymax - ey1) - (xmax - ex1) * ( + iy1 - ey1) + # Union + union = (px2 - px1) * (py2 - py1) + (tx2 - tx1) * ( + ty2 - ty1) - intersection + eps + # IoU + ious = 1 - (intersection / union) + + # Smooth-EIoU + smooth_sign = (ious < smooth_point).detach().float() + loss = 0.5 * smooth_sign * (ious**2) / smooth_point + (1 - smooth_sign) * ( + ious - 0.5 * smooth_point) + return loss + + +@weighted_loss +def siou_loss(pred, target, eps=1e-7, neg_gamma=False): + r"""`Implementation of paper `SIoU Loss: More Powerful Learning + for Bounding Box Regression `_. + + Code is modified from https://github.com/meituan/YOLOv6. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): Corresponding gt bboxes, shape (n, 4). + eps (float): Eps to avoid log(0). + neg_gamma (bool): `True` follows original implementation in paper. + + Return: + Tensor: Loss tensor. + """ + # overlap + lt = torch.max(pred[:, :2], target[:, :2]) + rb = torch.min(pred[:, 2:], target[:, 2:]) + wh = (rb - lt).clamp(min=0) + overlap = wh[:, 0] * wh[:, 1] + + # union + ap = (pred[:, 2] - pred[:, 0]) * (pred[:, 3] - pred[:, 1]) + ag = (target[:, 2] - target[:, 0]) * (target[:, 3] - target[:, 1]) + union = ap + ag - overlap + eps + + # IoU + ious = overlap / union + + # enclose area + enclose_x1y1 = torch.min(pred[:, :2], target[:, :2]) + enclose_x2y2 = torch.max(pred[:, 2:], target[:, 2:]) + # modified clamp threshold zero to eps to avoid NaN + enclose_wh = (enclose_x2y2 - enclose_x1y1).clamp(min=eps) + + cw = enclose_wh[:, 0] + ch = enclose_wh[:, 1] + + b1_x1, b1_y1 = pred[:, 0], pred[:, 1] + b1_x2, b1_y2 = pred[:, 2], pred[:, 3] + b2_x1, b2_y1 = target[:, 0], target[:, 1] + b2_x2, b2_y2 = target[:, 2], target[:, 3] + + w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps + w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps + + # angle cost + s_cw = (b2_x1 + b2_x2 - b1_x1 - b1_x2) * 0.5 + eps + s_ch = (b2_y1 + b2_y2 - b1_y1 - b1_y2) * 0.5 + eps + + sigma = torch.pow(s_cw**2 + s_ch**2, 0.5) + + sin_alpha_1 = torch.abs(s_cw) / sigma + sin_alpha_2 = torch.abs(s_ch) / sigma + threshold = pow(2, 0.5) / 2 + sin_alpha = torch.where(sin_alpha_1 > threshold, sin_alpha_2, sin_alpha_1) + angle_cost = torch.cos(torch.asin(sin_alpha) * 2 - math.pi / 2) + + # distance cost + rho_x = (s_cw / cw)**2 + rho_y = (s_ch / ch)**2 + + # `neg_gamma=True` follows original implementation in paper + # but setting `neg_gamma=False` makes training more stable. + gamma = angle_cost - 2 if neg_gamma else 2 - angle_cost + distance_cost = 2 - torch.exp(gamma * rho_x) - torch.exp(gamma * rho_y) + + # shape cost + omiga_w = torch.abs(w1 - w2) / torch.max(w1, w2) + omiga_h = torch.abs(h1 - h2) / torch.max(h1, h2) + shape_cost = torch.pow(1 - torch.exp(-1 * omiga_w), 4) + torch.pow( + 1 - torch.exp(-1 * omiga_h), 4) + + # SIoU + sious = ious - 0.5 * (distance_cost + shape_cost) + loss = 1 - sious.clamp(min=-1.0, max=1.0) + return loss + + +@MODELS.register_module() +class IoULoss(nn.Module): + """IoULoss. + + Computing the IoU loss between a set of predicted bboxes and target bboxes. + + Args: + linear (bool): If True, use linear scale of loss else determined + by mode. Default: False. + eps (float): Epsilon to avoid log(0). + reduction (str): Options are "none", "mean" and "sum". + loss_weight (float): Weight of loss. + mode (str): Loss scaling mode, including "linear", "square", and "log". + Default: 'log' + """ + + def __init__(self, + linear: bool = False, + eps: float = 1e-6, + reduction: str = 'mean', + loss_weight: float = 1.0, + mode: str = 'log') -> None: + super().__init__() + assert mode in ['linear', 'square', 'log'] + if linear: + mode = 'linear' + warnings.warn('DeprecationWarning: Setting "linear=True" in ' + 'IOULoss is deprecated, please use "mode=`linear`" ' + 'instead.') + self.mode = mode + self.linear = linear + self.eps = eps + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): The learning target of the prediction, + shape (n, 4). + weight (Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. Options are "none", "mean" and "sum". + + Return: + Tensor: Loss tensor. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if (weight is not None) and (not torch.any(weight > 0)) and ( + reduction != 'none'): + if pred.dim() == weight.dim() + 1: + weight = weight.unsqueeze(1) + return (pred * weight).sum() # 0 + if weight is not None and weight.dim() > 1: + # TODO: remove this in the future + # reduce the weight of shape (n, 4) to (n,) to match the + # iou_loss of shape (n,) + assert weight.shape == pred.shape + weight = weight.mean(-1) + loss = self.loss_weight * iou_loss( + pred, + target, + weight, + mode=self.mode, + eps=self.eps, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss + + +@MODELS.register_module() +class BoundedIoULoss(nn.Module): + """BIoULoss. + + This is an implementation of paper + `Improving Object Localization with Fitness NMS and Bounded IoU Loss. + `_. + + Args: + beta (float, optional): Beta parameter in smoothl1. + eps (float, optional): Epsilon to avoid NaN values. + reduction (str): Options are "none", "mean" and "sum". + loss_weight (float): Weight of loss. + """ + + def __init__(self, + beta: float = 0.2, + eps: float = 1e-3, + reduction: str = 'mean', + loss_weight: float = 1.0) -> None: + super().__init__() + self.beta = beta + self.eps = eps + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): The learning target of the prediction, + shape (n, 4). + weight (Optional[Tensor], optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (Optional[int], optional): Average factor that is used + to average the loss. Defaults to None. + reduction_override (Optional[str], optional): The reduction method + used to override the original reduction method of the loss. + Defaults to None. Options are "none", "mean" and "sum". + + Returns: + Tensor: Loss tensor. + """ + if weight is not None and not torch.any(weight > 0): + if pred.dim() == weight.dim() + 1: + weight = weight.unsqueeze(1) + return (pred * weight).sum() # 0 + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss = self.loss_weight * bounded_iou_loss( + pred, + target, + weight, + beta=self.beta, + eps=self.eps, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss + + +@MODELS.register_module() +class GIoULoss(nn.Module): + r"""`Generalized Intersection over Union: A Metric and A Loss for Bounding + Box Regression `_. + + Args: + eps (float): Epsilon to avoid log(0). + reduction (str): Options are "none", "mean" and "sum". + loss_weight (float): Weight of loss. + """ + + def __init__(self, + eps: float = 1e-6, + reduction: str = 'mean', + loss_weight: float = 1.0) -> None: + super().__init__() + self.eps = eps + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): The learning target of the prediction, + shape (n, 4). + weight (Optional[Tensor], optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (Optional[int], optional): Average factor that is used + to average the loss. Defaults to None. + reduction_override (Optional[str], optional): The reduction method + used to override the original reduction method of the loss. + Defaults to None. Options are "none", "mean" and "sum". + + Returns: + Tensor: Loss tensor. + """ + if weight is not None and not torch.any(weight > 0): + if pred.dim() == weight.dim() + 1: + weight = weight.unsqueeze(1) + return (pred * weight).sum() # 0 + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if weight is not None and weight.dim() > 1: + # TODO: remove this in the future + # reduce the weight of shape (n, 4) to (n,) to match the + # giou_loss of shape (n,) + assert weight.shape == pred.shape + weight = weight.mean(-1) + loss = self.loss_weight * giou_loss( + pred, + target, + weight, + eps=self.eps, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss + + +@MODELS.register_module() +class DIoULoss(nn.Module): + r"""Implementation of `Distance-IoU Loss: Faster and Better + Learning for Bounding Box Regression https://arxiv.org/abs/1911.08287`_. + + Code is modified from https://github.com/Zzh-tju/DIoU. + + Args: + eps (float): Epsilon to avoid log(0). + reduction (str): Options are "none", "mean" and "sum". + loss_weight (float): Weight of loss. + """ + + def __init__(self, + eps: float = 1e-6, + reduction: str = 'mean', + loss_weight: float = 1.0) -> None: + super().__init__() + self.eps = eps + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): The learning target of the prediction, + shape (n, 4). + weight (Optional[Tensor], optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (Optional[int], optional): Average factor that is used + to average the loss. Defaults to None. + reduction_override (Optional[str], optional): The reduction method + used to override the original reduction method of the loss. + Defaults to None. Options are "none", "mean" and "sum". + + Returns: + Tensor: Loss tensor. + """ + if weight is not None and not torch.any(weight > 0): + if pred.dim() == weight.dim() + 1: + weight = weight.unsqueeze(1) + return (pred * weight).sum() # 0 + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if weight is not None and weight.dim() > 1: + # TODO: remove this in the future + # reduce the weight of shape (n, 4) to (n,) to match the + # giou_loss of shape (n,) + assert weight.shape == pred.shape + weight = weight.mean(-1) + loss = self.loss_weight * diou_loss( + pred, + target, + weight, + eps=self.eps, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss + + +@MODELS.register_module() +class CIoULoss(nn.Module): + r"""`Implementation of paper `Enhancing Geometric Factors into + Model Learning and Inference for Object Detection and Instance + Segmentation `_. + + Code is modified from https://github.com/Zzh-tju/CIoU. + + Args: + eps (float): Epsilon to avoid log(0). + reduction (str): Options are "none", "mean" and "sum". + loss_weight (float): Weight of loss. + """ + + def __init__(self, + eps: float = 1e-6, + reduction: str = 'mean', + loss_weight: float = 1.0) -> None: + super().__init__() + self.eps = eps + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): The learning target of the prediction, + shape (n, 4). + weight (Optional[Tensor], optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (Optional[int], optional): Average factor that is used + to average the loss. Defaults to None. + reduction_override (Optional[str], optional): The reduction method + used to override the original reduction method of the loss. + Defaults to None. Options are "none", "mean" and "sum". + + Returns: + Tensor: Loss tensor. + """ + if weight is not None and not torch.any(weight > 0): + if pred.dim() == weight.dim() + 1: + weight = weight.unsqueeze(1) + return (pred * weight).sum() # 0 + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if weight is not None and weight.dim() > 1: + # TODO: remove this in the future + # reduce the weight of shape (n, 4) to (n,) to match the + # giou_loss of shape (n,) + assert weight.shape == pred.shape + weight = weight.mean(-1) + loss = self.loss_weight * ciou_loss( + pred, + target, + weight, + eps=self.eps, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss + + +@MODELS.register_module() +class EIoULoss(nn.Module): + r"""Implementation of paper `Extended-IoU Loss: A Systematic + IoU-Related Method: Beyond Simplified Regression for Better + Localization `_ + + Code is modified from https://github.com//ShiqiYu/libfacedetection.train. + + Args: + eps (float): Epsilon to avoid log(0). + reduction (str): Options are "none", "mean" and "sum". + loss_weight (float): Weight of loss. + smooth_point (float): hyperparameter, default is 0.1. + """ + + def __init__(self, + eps: float = 1e-6, + reduction: str = 'mean', + loss_weight: float = 1.0, + smooth_point: float = 0.1) -> None: + super().__init__() + self.eps = eps + self.reduction = reduction + self.loss_weight = loss_weight + self.smooth_point = smooth_point + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): The learning target of the prediction, + shape (n, 4). + weight (Optional[Tensor], optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (Optional[int], optional): Average factor that is used + to average the loss. Defaults to None. + reduction_override (Optional[str], optional): The reduction method + used to override the original reduction method of the loss. + Defaults to None. Options are "none", "mean" and "sum". + + Returns: + Tensor: Loss tensor. + """ + if weight is not None and not torch.any(weight > 0): + if pred.dim() == weight.dim() + 1: + weight = weight.unsqueeze(1) + return (pred * weight).sum() # 0 + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if weight is not None and weight.dim() > 1: + assert weight.shape == pred.shape + weight = weight.mean(-1) + loss = self.loss_weight * eiou_loss( + pred, + target, + weight, + smooth_point=self.smooth_point, + eps=self.eps, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss + + +@MODELS.register_module() +class SIoULoss(nn.Module): + r"""`Implementation of paper `SIoU Loss: More Powerful Learning + for Bounding Box Regression `_. + + Code is modified from https://github.com/meituan/YOLOv6. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): Corresponding gt bboxes, shape (n, 4). + eps (float): Eps to avoid log(0). + neg_gamma (bool): `True` follows original implementation in paper. + + Return: + Tensor: Loss tensor. + """ + + def __init__(self, + eps: float = 1e-6, + reduction: str = 'mean', + loss_weight: float = 1.0, + neg_gamma: bool = False) -> None: + super().__init__() + self.eps = eps + self.reduction = reduction + self.loss_weight = loss_weight + self.neg_gamma = neg_gamma + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): Predicted bboxes of format (x1, y1, x2, y2), + shape (n, 4). + target (Tensor): The learning target of the prediction, + shape (n, 4). + weight (Optional[Tensor], optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (Optional[int], optional): Average factor that is used + to average the loss. Defaults to None. + reduction_override (Optional[str], optional): The reduction method + used to override the original reduction method of the loss. + Defaults to None. Options are "none", "mean" and "sum". + + Returns: + Tensor: Loss tensor. + """ + if weight is not None and not torch.any(weight > 0): + if pred.dim() == weight.dim() + 1: + weight = weight.unsqueeze(1) + return (pred * weight).sum() # 0 + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if weight is not None and weight.dim() > 1: + # TODO: remove this in the future + # reduce the weight of shape (n, 4) to (n,) to match the + # giou_loss of shape (n,) + assert weight.shape == pred.shape + weight = weight.mean(-1) + loss = self.loss_weight * siou_loss( + pred, + target, + weight, + eps=self.eps, + reduction=reduction, + avg_factor=avg_factor, + neg_gamma=self.neg_gamma, + **kwargs) + return loss diff --git a/mmdetection/mmdet/models/losses/kd_loss.py b/mmdetection/mmdet/models/losses/kd_loss.py new file mode 100644 index 00000000..0a7d5ef2 --- /dev/null +++ b/mmdetection/mmdet/models/losses/kd_loss.py @@ -0,0 +1,95 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch.nn as nn +import torch.nn.functional as F +from torch import Tensor + +from mmdet.registry import MODELS +from .utils import weighted_loss + + +@weighted_loss +def knowledge_distillation_kl_div_loss(pred: Tensor, + soft_label: Tensor, + T: int, + detach_target: bool = True) -> Tensor: + r"""Loss function for knowledge distilling using KL divergence. + + Args: + pred (Tensor): Predicted logits with shape (N, n + 1). + soft_label (Tensor): Target logits with shape (N, N + 1). + T (int): Temperature for distillation. + detach_target (bool): Remove soft_label from automatic differentiation + + Returns: + Tensor: Loss tensor with shape (N,). + """ + assert pred.size() == soft_label.size() + target = F.softmax(soft_label / T, dim=1) + if detach_target: + target = target.detach() + + kd_loss = F.kl_div( + F.log_softmax(pred / T, dim=1), target, reduction='none').mean(1) * ( + T * T) + + return kd_loss + + +@MODELS.register_module() +class KnowledgeDistillationKLDivLoss(nn.Module): + """Loss function for knowledge distilling using KL divergence. + + Args: + reduction (str): Options are `'none'`, `'mean'` and `'sum'`. + loss_weight (float): Loss weight of current loss. + T (int): Temperature for distillation. + """ + + def __init__(self, + reduction: str = 'mean', + loss_weight: float = 1.0, + T: int = 10) -> None: + super().__init__() + assert T >= 1 + self.reduction = reduction + self.loss_weight = loss_weight + self.T = T + + def forward(self, + pred: Tensor, + soft_label: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None) -> Tensor: + """Forward function. + + Args: + pred (Tensor): Predicted logits with shape (N, n + 1). + soft_label (Tensor): Target logits with shape (N, N + 1). + weight (Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + + Returns: + Tensor: Loss tensor. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + + reduction = ( + reduction_override if reduction_override else self.reduction) + + loss_kd = self.loss_weight * knowledge_distillation_kl_div_loss( + pred, + soft_label, + weight, + reduction=reduction, + avg_factor=avg_factor, + T=self.T) + + return loss_kd diff --git a/mmdetection/mmdet/models/losses/l2_loss.py b/mmdetection/mmdet/models/losses/l2_loss.py new file mode 100644 index 00000000..6210a300 --- /dev/null +++ b/mmdetection/mmdet/models/losses/l2_loss.py @@ -0,0 +1,139 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, Union + +import numpy as np +import torch +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from .utils import weighted_loss + + +@weighted_loss +def l2_loss(pred: Tensor, target: Tensor) -> Tensor: + """L2 loss. + + Args: + pred (torch.Tensor): The prediction. + target (torch.Tensor): The learning target of the prediction. + + Returns: + torch.Tensor: Calculated loss + """ + assert pred.size() == target.size() + loss = torch.abs(pred - target)**2 + return loss + + +@MODELS.register_module() +class L2Loss(BaseModule): + """L2 loss. + + Args: + reduction (str, optional): The method to reduce the loss. + Options are "none", "mean" and "sum". + loss_weight (float, optional): The weight of loss. + """ + + def __init__(self, + neg_pos_ub: int = -1, + pos_margin: float = -1, + neg_margin: float = -1, + hard_mining: bool = False, + reduction: str = 'mean', + loss_weight: float = 1.0): + super(L2Loss, self).__init__() + self.neg_pos_ub = neg_pos_ub + self.pos_margin = pos_margin + self.neg_margin = neg_margin + self.hard_mining = hard_mining + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[float] = None, + reduction_override: Optional[str] = None) -> Tensor: + """Forward function. + + Args: + pred (torch.Tensor): The prediction. + target (torch.Tensor): The learning target of the prediction. + weight (torch.Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (float, optional): Average factor that is used to + average the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + pred, weight, avg_factor = self.update_weight(pred, target, weight, + avg_factor) + loss_bbox = self.loss_weight * l2_loss( + pred, target, weight, reduction=reduction, avg_factor=avg_factor) + return loss_bbox + + def update_weight(self, pred: Tensor, target: Tensor, weight: Tensor, + avg_factor: float) -> Tuple[Tensor, Tensor, float]: + """Update the weight according to targets.""" + if weight is None: + weight = target.new_ones(target.size()) + + invalid_inds = weight <= 0 + target[invalid_inds] = -1 + pos_inds = target == 1 + neg_inds = target == 0 + + if self.pos_margin > 0: + pred[pos_inds] -= self.pos_margin + if self.neg_margin > 0: + pred[neg_inds] -= self.neg_margin + pred = torch.clamp(pred, min=0, max=1) + + num_pos = int((target == 1).sum()) + num_neg = int((target == 0).sum()) + if self.neg_pos_ub > 0 and num_neg / (num_pos + + 1e-6) > self.neg_pos_ub: + num_neg = num_pos * self.neg_pos_ub + neg_idx = torch.nonzero(target == 0, as_tuple=False) + + if self.hard_mining: + costs = l2_loss( + pred, target, reduction='none')[neg_idx[:, 0], + neg_idx[:, 1]].detach() + neg_idx = neg_idx[costs.topk(num_neg)[1], :] + else: + neg_idx = self.random_choice(neg_idx, num_neg) + + new_neg_inds = neg_inds.new_zeros(neg_inds.size()).bool() + new_neg_inds[neg_idx[:, 0], neg_idx[:, 1]] = True + + invalid_neg_inds = torch.logical_xor(neg_inds, new_neg_inds) + weight[invalid_neg_inds] = 0 + + avg_factor = (weight > 0).sum() + return pred, weight, avg_factor + + @staticmethod + def random_choice(gallery: Union[list, np.ndarray, Tensor], + num: int) -> np.ndarray: + """Random select some elements from the gallery. + + It seems that Pytorch's implementation is slower than numpy so we use + numpy to randperm the indices. + """ + assert len(gallery) >= num + if isinstance(gallery, list): + gallery = np.array(gallery) + cands = np.arange(len(gallery)) + np.random.shuffle(cands) + rand_inds = cands[:num] + if not isinstance(gallery, np.ndarray): + rand_inds = torch.from_numpy(rand_inds).long().to(gallery.device) + return gallery[rand_inds] diff --git a/mmdetection/mmdet/models/losses/margin_loss.py b/mmdetection/mmdet/models/losses/margin_loss.py new file mode 100644 index 00000000..0609e1db --- /dev/null +++ b/mmdetection/mmdet/models/losses/margin_loss.py @@ -0,0 +1,152 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, Union + +import numpy as np +import torch +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from .mse_loss import mse_loss + + +@MODELS.register_module() +class MarginL2Loss(BaseModule): + """L2 loss with margin. + + Args: + neg_pos_ub (int, optional): The upper bound of negative to positive + samples in hard mining. Defaults to -1. + pos_margin (float, optional): The similarity margin for positive + samples in hard mining. Defaults to -1. + neg_margin (float, optional): The similarity margin for negative + samples in hard mining. Defaults to -1. + hard_mining (bool, optional): Whether to use hard mining. Defaults to + False. + reduction (str, optional): The method to reduce the loss. + Options are "none", "mean" and "sum". Defaults to "mean". + loss_weight (float, optional): The weight of loss. Defaults to 1.0. + """ + + def __init__(self, + neg_pos_ub: int = -1, + pos_margin: float = -1, + neg_margin: float = -1, + hard_mining: bool = False, + reduction: str = 'mean', + loss_weight: float = 1.0): + super(MarginL2Loss, self).__init__() + self.neg_pos_ub = neg_pos_ub + self.pos_margin = pos_margin + self.neg_margin = neg_margin + self.hard_mining = hard_mining + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[float] = None, + reduction_override: Optional[str] = None) -> Tensor: + """Forward function. + + Args: + pred (torch.Tensor): The prediction. + target (torch.Tensor): The learning target of the prediction. + weight (torch.Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (float, optional): Average factor that is used to + average the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + pred, weight, avg_factor = self.update_weight(pred, target, weight, + avg_factor) + loss_bbox = self.loss_weight * mse_loss( + pred, + target.float(), + weight.float(), + reduction=reduction, + avg_factor=avg_factor) + return loss_bbox + + def update_weight(self, pred: Tensor, target: Tensor, weight: Tensor, + avg_factor: float) -> Tuple[Tensor, Tensor, float]: + """Update the weight according to targets. + + Args: + pred (torch.Tensor): The prediction. + target (torch.Tensor): The learning target of the prediction. + weight (torch.Tensor): The weight of loss for each prediction. + avg_factor (float): Average factor that is used to average the + loss. + + Returns: + tuple[torch.Tensor]: The updated prediction, weight and average + factor. + """ + if weight is None: + weight = target.new_ones(target.size()) + + invalid_inds = weight <= 0 + target[invalid_inds] = -1 + pos_inds = target == 1 + neg_inds = target == 0 + + if self.pos_margin > 0: + pred[pos_inds] -= self.pos_margin + if self.neg_margin > 0: + pred[neg_inds] -= self.neg_margin + pred = torch.clamp(pred, min=0, max=1) + + num_pos = int((target == 1).sum()) + num_neg = int((target == 0).sum()) + if self.neg_pos_ub > 0 and num_neg / (num_pos + + 1e-6) > self.neg_pos_ub: + num_neg = num_pos * self.neg_pos_ub + neg_idx = torch.nonzero(target == 0, as_tuple=False) + + if self.hard_mining: + costs = mse_loss( + pred, target.float(), + reduction='none')[neg_idx[:, 0], neg_idx[:, 1]].detach() + neg_idx = neg_idx[costs.topk(num_neg)[1], :] + else: + neg_idx = self.random_choice(neg_idx, num_neg) + + new_neg_inds = neg_inds.new_zeros(neg_inds.size()).bool() + new_neg_inds[neg_idx[:, 0], neg_idx[:, 1]] = True + + invalid_neg_inds = torch.logical_xor(neg_inds, new_neg_inds) + weight[invalid_neg_inds] = 0 + + avg_factor = (weight > 0).sum() + return pred, weight, avg_factor + + @staticmethod + def random_choice(gallery: Union[list, np.ndarray, Tensor], + num: int) -> np.ndarray: + """Random select some elements from the gallery. + + It seems that Pytorch's implementation is slower than numpy so we use + numpy to randperm the indices. + + Args: + gallery (list | np.ndarray | torch.Tensor): The gallery from + which to sample. + num (int): The number of elements to sample. + """ + assert len(gallery) >= num + if isinstance(gallery, list): + gallery = np.array(gallery) + cands = np.arange(len(gallery)) + np.random.shuffle(cands) + rand_inds = cands[:num] + if not isinstance(gallery, np.ndarray): + rand_inds = torch.from_numpy(rand_inds).long().to(gallery.device) + return gallery[rand_inds] diff --git a/mmdetection/mmdet/models/losses/mse_loss.py b/mmdetection/mmdet/models/losses/mse_loss.py new file mode 100644 index 00000000..6048218a --- /dev/null +++ b/mmdetection/mmdet/models/losses/mse_loss.py @@ -0,0 +1,69 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch.nn as nn +import torch.nn.functional as F +from torch import Tensor + +from mmdet.registry import MODELS +from .utils import weighted_loss + + +@weighted_loss +def mse_loss(pred: Tensor, target: Tensor) -> Tensor: + """A Wrapper of MSE loss. + Args: + pred (Tensor): The prediction. + target (Tensor): The learning target of the prediction. + + Returns: + Tensor: loss Tensor + """ + return F.mse_loss(pred, target, reduction='none') + + +@MODELS.register_module() +class MSELoss(nn.Module): + """MSELoss. + + Args: + reduction (str, optional): The method that reduces the loss to a + scalar. Options are "none", "mean" and "sum". + loss_weight (float, optional): The weight of the loss. Defaults to 1.0 + """ + + def __init__(self, + reduction: str = 'mean', + loss_weight: float = 1.0) -> None: + super().__init__() + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None) -> Tensor: + """Forward function of loss. + + Args: + pred (Tensor): The prediction. + target (Tensor): The learning target of the prediction. + weight (Tensor, optional): Weight of the loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + + Returns: + Tensor: The calculated loss. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss = self.loss_weight * mse_loss( + pred, target, weight, reduction=reduction, avg_factor=avg_factor) + return loss diff --git a/mmdetection/mmdet/models/losses/multipos_cross_entropy_loss.py b/mmdetection/mmdet/models/losses/multipos_cross_entropy_loss.py new file mode 100644 index 00000000..a7d1561e --- /dev/null +++ b/mmdetection/mmdet/models/losses/multipos_cross_entropy_loss.py @@ -0,0 +1,100 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from .utils import weight_reduce_loss + + +@MODELS.register_module() +class MultiPosCrossEntropyLoss(BaseModule): + """multi-positive targets cross entropy loss. + + Args: + reduction (str, optional): The method to reduce the loss. + Options are "none", "mean" and "sum". Defaults to "mean". + loss_weight (float, optional): The weight of loss. Defaults to 1.0. + """ + + def __init__(self, reduction: str = 'mean', loss_weight: float = 1.0): + super(MultiPosCrossEntropyLoss, self).__init__() + self.reduction = reduction + self.loss_weight = loss_weight + + def multi_pos_cross_entropy(self, + pred: Tensor, + label: Tensor, + weight: Optional[Tensor] = None, + reduction: str = 'mean', + avg_factor: Optional[float] = None) -> Tensor: + """Multi-positive targets cross entropy loss. + + Args: + pred (torch.Tensor): The prediction. + label (torch.Tensor): The assigned label of the prediction. + weight (torch.Tensor): The element-wise weight. + reduction (str): Same as built-in losses of PyTorch. + avg_factor (float): Average factor when computing + the mean of losses. + + Returns: + torch.Tensor: Calculated loss + """ + + pos_inds = (label >= 1) + neg_inds = (label == 0) + pred_pos = pred * pos_inds.float() + pred_neg = pred * neg_inds.float() + # use -inf to mask out unwanted elements. + pred_pos[neg_inds] = pred_pos[neg_inds] + float('inf') + pred_neg[pos_inds] = pred_neg[pos_inds] + float('-inf') + + _pos_expand = torch.repeat_interleave(pred_pos, pred.shape[1], dim=1) + _neg_expand = pred_neg.repeat(1, pred.shape[1]) + + x = torch.nn.functional.pad((_neg_expand - _pos_expand), (0, 1), + 'constant', 0) + loss = torch.logsumexp(x, dim=1) + + # apply weights and do the reduction + if weight is not None: + weight = weight.float() + loss = weight_reduce_loss( + loss, weight=weight, reduction=reduction, avg_factor=avg_factor) + + return loss + + def forward(self, + cls_score: Tensor, + label: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[float] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + cls_score (torch.Tensor): The classification score. + label (torch.Tensor): The assigned label of the prediction. + weight (torch.Tensor): The element-wise weight. + avg_factor (float): Average factor when computing + the mean of losses. + reduction_override (str): Same as built-in losses of PyTorch. + + Returns: + torch.Tensor: Calculated loss + """ + assert cls_score.size() == label.size() + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss_cls = self.loss_weight * self.multi_pos_cross_entropy( + cls_score, + label, + weight, + reduction=reduction, + avg_factor=avg_factor) + return loss_cls diff --git a/mmdetection/mmdet/models/losses/pisa_loss.py b/mmdetection/mmdet/models/losses/pisa_loss.py new file mode 100644 index 00000000..b192aa0d --- /dev/null +++ b/mmdetection/mmdet/models/losses/pisa_loss.py @@ -0,0 +1,187 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +from torch import Tensor + +from mmdet.structures.bbox import bbox_overlaps +from ..task_modules.coders import BaseBBoxCoder +from ..task_modules.samplers import SamplingResult + + +def isr_p(cls_score: Tensor, + bbox_pred: Tensor, + bbox_targets: Tuple[Tensor], + rois: Tensor, + sampling_results: List[SamplingResult], + loss_cls: nn.Module, + bbox_coder: BaseBBoxCoder, + k: float = 2, + bias: float = 0, + num_class: int = 80) -> tuple: + """Importance-based Sample Reweighting (ISR_P), positive part. + + Args: + cls_score (Tensor): Predicted classification scores. + bbox_pred (Tensor): Predicted bbox deltas. + bbox_targets (tuple[Tensor]): A tuple of bbox targets, the are + labels, label_weights, bbox_targets, bbox_weights, respectively. + rois (Tensor): Anchors (single_stage) in shape (n, 4) or RoIs + (two_stage) in shape (n, 5). + sampling_results (:obj:`SamplingResult`): Sampling results. + loss_cls (:obj:`nn.Module`): Classification loss func of the head. + bbox_coder (:obj:`BaseBBoxCoder`): BBox coder of the head. + k (float): Power of the non-linear mapping. Defaults to 2. + bias (float): Shift of the non-linear mapping. Defaults to 0. + num_class (int): Number of classes, defaults to 80. + + Return: + tuple([Tensor]): labels, imp_based_label_weights, bbox_targets, + bbox_target_weights + """ + + labels, label_weights, bbox_targets, bbox_weights = bbox_targets + pos_label_inds = ((labels >= 0) & + (labels < num_class)).nonzero().reshape(-1) + pos_labels = labels[pos_label_inds] + + # if no positive samples, return the original targets + num_pos = float(pos_label_inds.size(0)) + if num_pos == 0: + return labels, label_weights, bbox_targets, bbox_weights + + # merge pos_assigned_gt_inds of per image to a single tensor + gts = list() + last_max_gt = 0 + for i in range(len(sampling_results)): + gt_i = sampling_results[i].pos_assigned_gt_inds + gts.append(gt_i + last_max_gt) + if len(gt_i) != 0: + last_max_gt = gt_i.max() + 1 + gts = torch.cat(gts) + assert len(gts) == num_pos + + cls_score = cls_score.detach() + bbox_pred = bbox_pred.detach() + + # For single stage detectors, rois here indicate anchors, in shape (N, 4) + # For two stage detectors, rois are in shape (N, 5) + if rois.size(-1) == 5: + pos_rois = rois[pos_label_inds][:, 1:] + else: + pos_rois = rois[pos_label_inds] + + if bbox_pred.size(-1) > 4: + bbox_pred = bbox_pred.view(bbox_pred.size(0), -1, 4) + pos_delta_pred = bbox_pred[pos_label_inds, pos_labels].view(-1, 4) + else: + pos_delta_pred = bbox_pred[pos_label_inds].view(-1, 4) + + # compute iou of the predicted bbox and the corresponding GT + pos_delta_target = bbox_targets[pos_label_inds].view(-1, 4) + pos_bbox_pred = bbox_coder.decode(pos_rois, pos_delta_pred) + target_bbox_pred = bbox_coder.decode(pos_rois, pos_delta_target) + ious = bbox_overlaps(pos_bbox_pred, target_bbox_pred, is_aligned=True) + + pos_imp_weights = label_weights[pos_label_inds] + # Two steps to compute IoU-HLR. Samples are first sorted by IoU locally, + # then sorted again within the same-rank group + max_l_num = pos_labels.bincount().max() + for label in pos_labels.unique(): + l_inds = (pos_labels == label).nonzero().view(-1) + l_gts = gts[l_inds] + for t in l_gts.unique(): + t_inds = l_inds[l_gts == t] + t_ious = ious[t_inds] + _, t_iou_rank_idx = t_ious.sort(descending=True) + _, t_iou_rank = t_iou_rank_idx.sort() + ious[t_inds] += max_l_num - t_iou_rank.float() + l_ious = ious[l_inds] + _, l_iou_rank_idx = l_ious.sort(descending=True) + _, l_iou_rank = l_iou_rank_idx.sort() # IoU-HLR + # linearly map HLR to label weights + pos_imp_weights[l_inds] *= (max_l_num - l_iou_rank.float()) / max_l_num + + pos_imp_weights = (bias + pos_imp_weights * (1 - bias)).pow(k) + + # normalize to make the new weighted loss value equal to the original loss + pos_loss_cls = loss_cls( + cls_score[pos_label_inds], pos_labels, reduction_override='none') + if pos_loss_cls.dim() > 1: + ori_pos_loss_cls = pos_loss_cls * label_weights[pos_label_inds][:, + None] + new_pos_loss_cls = pos_loss_cls * pos_imp_weights[:, None] + else: + ori_pos_loss_cls = pos_loss_cls * label_weights[pos_label_inds] + new_pos_loss_cls = pos_loss_cls * pos_imp_weights + pos_loss_cls_ratio = ori_pos_loss_cls.sum() / new_pos_loss_cls.sum() + pos_imp_weights = pos_imp_weights * pos_loss_cls_ratio + label_weights[pos_label_inds] = pos_imp_weights + + bbox_targets = labels, label_weights, bbox_targets, bbox_weights + return bbox_targets + + +def carl_loss(cls_score: Tensor, + labels: Tensor, + bbox_pred: Tensor, + bbox_targets: Tensor, + loss_bbox: nn.Module, + k: float = 1, + bias: float = 0.2, + avg_factor: Optional[int] = None, + sigmoid: bool = False, + num_class: int = 80) -> dict: + """Classification-Aware Regression Loss (CARL). + + Args: + cls_score (Tensor): Predicted classification scores. + labels (Tensor): Targets of classification. + bbox_pred (Tensor): Predicted bbox deltas. + bbox_targets (Tensor): Target of bbox regression. + loss_bbox (func): Regression loss func of the head. + bbox_coder (obj): BBox coder of the head. + k (float): Power of the non-linear mapping. Defaults to 1. + bias (float): Shift of the non-linear mapping. Defaults to 0.2. + avg_factor (int, optional): Average factor used in regression loss. + sigmoid (bool): Activation of the classification score. + num_class (int): Number of classes, defaults to 80. + + Return: + dict: CARL loss dict. + """ + pos_label_inds = ((labels >= 0) & + (labels < num_class)).nonzero().reshape(-1) + if pos_label_inds.numel() == 0: + return dict(loss_carl=cls_score.sum()[None] * 0.) + pos_labels = labels[pos_label_inds] + + # multiply pos_cls_score with the corresponding bbox weight + # and remain gradient + if sigmoid: + pos_cls_score = cls_score.sigmoid()[pos_label_inds, pos_labels] + else: + pos_cls_score = cls_score.softmax(-1)[pos_label_inds, pos_labels] + carl_loss_weights = (bias + (1 - bias) * pos_cls_score).pow(k) + + # normalize carl_loss_weight to make its sum equal to num positive + num_pos = float(pos_cls_score.size(0)) + weight_ratio = num_pos / carl_loss_weights.sum() + carl_loss_weights *= weight_ratio + + if avg_factor is None: + avg_factor = bbox_targets.size(0) + # if is class agnostic, bbox pred is in shape (N, 4) + # otherwise, bbox pred is in shape (N, #classes, 4) + if bbox_pred.size(-1) > 4: + bbox_pred = bbox_pred.view(bbox_pred.size(0), -1, 4) + pos_bbox_preds = bbox_pred[pos_label_inds, pos_labels] + else: + pos_bbox_preds = bbox_pred[pos_label_inds] + ori_loss_reg = loss_bbox( + pos_bbox_preds, + bbox_targets[pos_label_inds], + reduction_override='none') / avg_factor + loss_carl = (ori_loss_reg * carl_loss_weights[:, None]).sum() + return dict(loss_carl=loss_carl[None]) diff --git a/mmdetection/mmdet/models/losses/seesaw_loss.py b/mmdetection/mmdet/models/losses/seesaw_loss.py new file mode 100644 index 00000000..4dec62b0 --- /dev/null +++ b/mmdetection/mmdet/models/losses/seesaw_loss.py @@ -0,0 +1,278 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, Optional, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch import Tensor + +from mmdet.registry import MODELS +from .accuracy import accuracy +from .cross_entropy_loss import cross_entropy +from .utils import weight_reduce_loss + + +def seesaw_ce_loss(cls_score: Tensor, + labels: Tensor, + label_weights: Tensor, + cum_samples: Tensor, + num_classes: int, + p: float, + q: float, + eps: float, + reduction: str = 'mean', + avg_factor: Optional[int] = None) -> Tensor: + """Calculate the Seesaw CrossEntropy loss. + + Args: + cls_score (Tensor): The prediction with shape (N, C), + C is the number of classes. + labels (Tensor): The learning label of the prediction. + label_weights (Tensor): Sample-wise loss weight. + cum_samples (Tensor): Cumulative samples for each category. + num_classes (int): The number of classes. + p (float): The ``p`` in the mitigation factor. + q (float): The ``q`` in the compenstation factor. + eps (float): The minimal value of divisor to smooth + the computation of compensation factor + reduction (str, optional): The method used to reduce the loss. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + + Returns: + Tensor: The calculated loss + """ + assert cls_score.size(-1) == num_classes + assert len(cum_samples) == num_classes + + onehot_labels = F.one_hot(labels, num_classes) + seesaw_weights = cls_score.new_ones(onehot_labels.size()) + + # mitigation factor + if p > 0: + sample_ratio_matrix = cum_samples[None, :].clamp( + min=1) / cum_samples[:, None].clamp(min=1) + index = (sample_ratio_matrix < 1.0).float() + sample_weights = sample_ratio_matrix.pow(p) * index + (1 - index) + mitigation_factor = sample_weights[labels.long(), :] + seesaw_weights = seesaw_weights * mitigation_factor + + # compensation factor + if q > 0: + scores = F.softmax(cls_score.detach(), dim=1) + self_scores = scores[ + torch.arange(0, len(scores)).to(scores.device).long(), + labels.long()] + score_matrix = scores / self_scores[:, None].clamp(min=eps) + index = (score_matrix > 1.0).float() + compensation_factor = score_matrix.pow(q) * index + (1 - index) + seesaw_weights = seesaw_weights * compensation_factor + + cls_score = cls_score + (seesaw_weights.log() * (1 - onehot_labels)) + + loss = F.cross_entropy(cls_score, labels, weight=None, reduction='none') + + if label_weights is not None: + label_weights = label_weights.float() + loss = weight_reduce_loss( + loss, weight=label_weights, reduction=reduction, avg_factor=avg_factor) + return loss + + +@MODELS.register_module() +class SeesawLoss(nn.Module): + """ + Seesaw Loss for Long-Tailed Instance Segmentation (CVPR 2021) + arXiv: https://arxiv.org/abs/2008.10032 + + Args: + use_sigmoid (bool, optional): Whether the prediction uses sigmoid + of softmax. Only False is supported. + p (float, optional): The ``p`` in the mitigation factor. + Defaults to 0.8. + q (float, optional): The ``q`` in the compenstation factor. + Defaults to 2.0. + num_classes (int, optional): The number of classes. + Default to 1203 for LVIS v1 dataset. + eps (float, optional): The minimal value of divisor to smooth + the computation of compensation factor + reduction (str, optional): The method that reduces the loss to a + scalar. Options are "none", "mean" and "sum". + loss_weight (float, optional): The weight of the loss. Defaults to 1.0 + return_dict (bool, optional): Whether return the losses as a dict. + Default to True. + """ + + def __init__(self, + use_sigmoid: bool = False, + p: float = 0.8, + q: float = 2.0, + num_classes: int = 1203, + eps: float = 1e-2, + reduction: str = 'mean', + loss_weight: float = 1.0, + return_dict: bool = True) -> None: + super().__init__() + assert not use_sigmoid + self.use_sigmoid = False + self.p = p + self.q = q + self.num_classes = num_classes + self.eps = eps + self.reduction = reduction + self.loss_weight = loss_weight + self.return_dict = return_dict + + # 0 for pos, 1 for neg + self.cls_criterion = seesaw_ce_loss + + # cumulative samples for each category + self.register_buffer( + 'cum_samples', + torch.zeros(self.num_classes + 1, dtype=torch.float)) + + # custom output channels of the classifier + self.custom_cls_channels = True + # custom activation of cls_score + self.custom_activation = True + # custom accuracy of the classsifier + self.custom_accuracy = True + + def _split_cls_score(self, cls_score: Tensor) -> Tuple[Tensor, Tensor]: + """split cls_score. + + Args: + cls_score (Tensor): The prediction with shape (N, C + 2). + + Returns: + Tuple[Tensor, Tensor]: The score for classes and objectness, + respectively + """ + # split cls_score to cls_score_classes and cls_score_objectness + assert cls_score.size(-1) == self.num_classes + 2 + cls_score_classes = cls_score[..., :-2] + cls_score_objectness = cls_score[..., -2:] + return cls_score_classes, cls_score_objectness + + def get_cls_channels(self, num_classes: int) -> int: + """Get custom classification channels. + + Args: + num_classes (int): The number of classes. + + Returns: + int: The custom classification channels. + """ + assert num_classes == self.num_classes + return num_classes + 2 + + def get_activation(self, cls_score: Tensor) -> Tensor: + """Get custom activation of cls_score. + + Args: + cls_score (Tensor): The prediction with shape (N, C + 2). + + Returns: + Tensor: The custom activation of cls_score with shape + (N, C + 1). + """ + cls_score_classes, cls_score_objectness = self._split_cls_score( + cls_score) + score_classes = F.softmax(cls_score_classes, dim=-1) + score_objectness = F.softmax(cls_score_objectness, dim=-1) + score_pos = score_objectness[..., [0]] + score_neg = score_objectness[..., [1]] + score_classes = score_classes * score_pos + scores = torch.cat([score_classes, score_neg], dim=-1) + return scores + + def get_accuracy(self, cls_score: Tensor, + labels: Tensor) -> Dict[str, Tensor]: + """Get custom accuracy w.r.t. cls_score and labels. + + Args: + cls_score (Tensor): The prediction with shape (N, C + 2). + labels (Tensor): The learning label of the prediction. + + Returns: + Dict [str, Tensor]: The accuracy for objectness and classes, + respectively. + """ + pos_inds = labels < self.num_classes + obj_labels = (labels == self.num_classes).long() + cls_score_classes, cls_score_objectness = self._split_cls_score( + cls_score) + acc_objectness = accuracy(cls_score_objectness, obj_labels) + acc_classes = accuracy(cls_score_classes[pos_inds], labels[pos_inds]) + acc = dict() + acc['acc_objectness'] = acc_objectness + acc['acc_classes'] = acc_classes + return acc + + def forward( + self, + cls_score: Tensor, + labels: Tensor, + label_weights: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None + ) -> Union[Tensor, Dict[str, Tensor]]: + """Forward function. + + Args: + cls_score (Tensor): The prediction with shape (N, C + 2). + labels (Tensor): The learning label of the prediction. + label_weights (Tensor, optional): Sample-wise loss weight. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction (str, optional): The method used to reduce the loss. + Options are "none", "mean" and "sum". + + Returns: + Tensor | Dict [str, Tensor]: + if return_dict == False: The calculated loss | + if return_dict == True: The dict of calculated losses + for objectness and classes, respectively. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + assert cls_score.size(-1) == self.num_classes + 2 + pos_inds = labels < self.num_classes + # 0 for pos, 1 for neg + obj_labels = (labels == self.num_classes).long() + + # accumulate the samples for each category + unique_labels = labels.unique() + for u_l in unique_labels: + inds_ = labels == u_l.item() + self.cum_samples[u_l] += inds_.sum() + + if label_weights is not None: + label_weights = label_weights.float() + else: + label_weights = labels.new_ones(labels.size(), dtype=torch.float) + + cls_score_classes, cls_score_objectness = self._split_cls_score( + cls_score) + # calculate loss_cls_classes (only need pos samples) + if pos_inds.sum() > 0: + loss_cls_classes = self.loss_weight * self.cls_criterion( + cls_score_classes[pos_inds], labels[pos_inds], + label_weights[pos_inds], self.cum_samples[:self.num_classes], + self.num_classes, self.p, self.q, self.eps, reduction, + avg_factor) + else: + loss_cls_classes = cls_score_classes[pos_inds].sum() + # calculate loss_cls_objectness + loss_cls_objectness = self.loss_weight * cross_entropy( + cls_score_objectness, obj_labels, label_weights, reduction, + avg_factor) + + if self.return_dict: + loss_cls = dict() + loss_cls['loss_cls_objectness'] = loss_cls_objectness + loss_cls['loss_cls_classes'] = loss_cls_classes + else: + loss_cls = loss_cls_classes + loss_cls_objectness + return loss_cls diff --git a/mmdetection/mmdet/models/losses/smooth_l1_loss.py b/mmdetection/mmdet/models/losses/smooth_l1_loss.py new file mode 100644 index 00000000..102f9780 --- /dev/null +++ b/mmdetection/mmdet/models/losses/smooth_l1_loss.py @@ -0,0 +1,165 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +import torch.nn as nn +from torch import Tensor + +from mmdet.registry import MODELS +from .utils import weighted_loss + + +@weighted_loss +def smooth_l1_loss(pred: Tensor, target: Tensor, beta: float = 1.0) -> Tensor: + """Smooth L1 loss. + + Args: + pred (Tensor): The prediction. + target (Tensor): The learning target of the prediction. + beta (float, optional): The threshold in the piecewise function. + Defaults to 1.0. + + Returns: + Tensor: Calculated loss + """ + assert beta > 0 + if target.numel() == 0: + return pred.sum() * 0 + + assert pred.size() == target.size() + diff = torch.abs(pred - target) + loss = torch.where(diff < beta, 0.5 * diff * diff / beta, + diff - 0.5 * beta) + return loss + + +@weighted_loss +def l1_loss(pred: Tensor, target: Tensor) -> Tensor: + """L1 loss. + + Args: + pred (Tensor): The prediction. + target (Tensor): The learning target of the prediction. + + Returns: + Tensor: Calculated loss + """ + if target.numel() == 0: + return pred.sum() * 0 + + assert pred.size() == target.size() + loss = torch.abs(pred - target) + return loss + + +@MODELS.register_module() +class SmoothL1Loss(nn.Module): + """Smooth L1 loss. + + Args: + beta (float, optional): The threshold in the piecewise function. + Defaults to 1.0. + reduction (str, optional): The method to reduce the loss. + Options are "none", "mean" and "sum". Defaults to "mean". + loss_weight (float, optional): The weight of loss. + """ + + def __init__(self, + beta: float = 1.0, + reduction: str = 'mean', + loss_weight: float = 1.0) -> None: + super().__init__() + self.beta = beta + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): The prediction. + target (Tensor): The learning target of the prediction. + weight (Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + + Returns: + Tensor: Calculated loss + """ + if weight is not None and not torch.any(weight > 0): + if pred.dim() == weight.dim() + 1: + weight = weight.unsqueeze(1) + return (pred * weight).sum() + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss_bbox = self.loss_weight * smooth_l1_loss( + pred, + target, + weight, + beta=self.beta, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss_bbox + + +@MODELS.register_module() +class L1Loss(nn.Module): + """L1 loss. + + Args: + reduction (str, optional): The method to reduce the loss. + Options are "none", "mean" and "sum". + loss_weight (float, optional): The weight of loss. + """ + + def __init__(self, + reduction: str = 'mean', + loss_weight: float = 1.0) -> None: + super().__init__() + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None) -> Tensor: + """Forward function. + + Args: + pred (Tensor): The prediction. + target (Tensor): The learning target of the prediction. + weight (Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + + Returns: + Tensor: Calculated loss + """ + if weight is not None and not torch.any(weight > 0): + if pred.dim() == weight.dim() + 1: + weight = weight.unsqueeze(1) + return (pred * weight).sum() + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss_bbox = self.loss_weight * l1_loss( + pred, target, weight, reduction=reduction, avg_factor=avg_factor) + return loss_bbox diff --git a/mmdetection/mmdet/models/losses/triplet_loss.py b/mmdetection/mmdet/models/losses/triplet_loss.py new file mode 100644 index 00000000..4528239b --- /dev/null +++ b/mmdetection/mmdet/models/losses/triplet_loss.py @@ -0,0 +1,88 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmengine.model import BaseModule + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class TripletLoss(BaseModule): + """Triplet loss with hard positive/negative mining. + + Reference: + Hermans et al. In Defense of the Triplet Loss for + Person Re-Identification. arXiv:1703.07737. + Imported from ``_. + Args: + margin (float, optional): Margin for triplet loss. Defaults to 0.3. + loss_weight (float, optional): Weight of the loss. Defaults to 1.0. + hard_mining (bool, optional): Whether to perform hard mining. + Defaults to True. + """ + + def __init__(self, + margin: float = 0.3, + loss_weight: float = 1.0, + hard_mining=True): + super(TripletLoss, self).__init__() + self.margin = margin + self.ranking_loss = nn.MarginRankingLoss(margin=margin) + self.loss_weight = loss_weight + self.hard_mining = hard_mining + + def hard_mining_triplet_loss_forward( + self, inputs: torch.Tensor, + targets: torch.LongTensor) -> torch.Tensor: + """ + Args: + inputs (torch.Tensor): feature matrix with shape + (batch_size, feat_dim). + targets (torch.LongTensor): ground truth labels with shape + (batch_size). + + Returns: + torch.Tensor: triplet loss with hard mining. + """ + + batch_size = inputs.size(0) + + # Compute Euclidean distance + dist = torch.pow(inputs, 2).sum( + dim=1, keepdim=True).expand(batch_size, batch_size) + dist = dist + dist.t() + dist.addmm_(inputs, inputs.t(), beta=1, alpha=-2) + dist = dist.clamp(min=1e-12).sqrt() # for numerical stability + + # For each anchor, find the furthest positive sample + # and nearest negative sample in the embedding space + mask = targets.expand(batch_size, batch_size).eq( + targets.expand(batch_size, batch_size).t()) + dist_ap, dist_an = [], [] + for i in range(batch_size): + dist_ap.append(dist[i][mask[i]].max().unsqueeze(0)) + dist_an.append(dist[i][mask[i] == 0].min().unsqueeze(0)) + dist_ap = torch.cat(dist_ap) + dist_an = torch.cat(dist_an) + + # Compute ranking hinge loss + y = torch.ones_like(dist_an) + return self.loss_weight * self.ranking_loss(dist_an, dist_ap, y) + + def forward(self, inputs: torch.Tensor, + targets: torch.LongTensor) -> torch.Tensor: + """ + Args: + inputs (torch.Tensor): feature matrix with shape + (batch_size, feat_dim). + targets (torch.LongTensor): ground truth labels with shape + (num_classes). + + Returns: + torch.Tensor: triplet loss. + """ + if self.hard_mining: + return self.hard_mining_triplet_loss_forward(inputs, targets) + else: + raise NotImplementedError() diff --git a/mmdetection/mmdet/models/losses/utils.py b/mmdetection/mmdet/models/losses/utils.py new file mode 100644 index 00000000..5e6e7859 --- /dev/null +++ b/mmdetection/mmdet/models/losses/utils.py @@ -0,0 +1,125 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import functools +from typing import Callable, Optional + +import torch +import torch.nn.functional as F +from torch import Tensor + + +def reduce_loss(loss: Tensor, reduction: str) -> Tensor: + """Reduce loss as specified. + + Args: + loss (Tensor): Elementwise loss tensor. + reduction (str): Options are "none", "mean" and "sum". + + Return: + Tensor: Reduced loss tensor. + """ + reduction_enum = F._Reduction.get_enum(reduction) + # none: 0, elementwise_mean:1, sum: 2 + if reduction_enum == 0: + return loss + elif reduction_enum == 1: + return loss.mean() + elif reduction_enum == 2: + return loss.sum() + + +def weight_reduce_loss(loss: Tensor, + weight: Optional[Tensor] = None, + reduction: str = 'mean', + avg_factor: Optional[float] = None) -> Tensor: + """Apply element-wise weight and reduce loss. + + Args: + loss (Tensor): Element-wise loss. + weight (Optional[Tensor], optional): Element-wise weights. + Defaults to None. + reduction (str, optional): Same as built-in losses of PyTorch. + Defaults to 'mean'. + avg_factor (Optional[float], optional): Average factor when + computing the mean of losses. Defaults to None. + + Returns: + Tensor: Processed loss values. + """ + # if weight is specified, apply element-wise weight + if weight is not None: + loss = loss * weight + + # if avg_factor is not specified, just reduce the loss + if avg_factor is None: + loss = reduce_loss(loss, reduction) + else: + # if reduction is mean, then average the loss by avg_factor + if reduction == 'mean': + # Avoid causing ZeroDivisionError when avg_factor is 0.0, + # i.e., all labels of an image belong to ignore index. + eps = torch.finfo(torch.float32).eps + loss = loss.sum() / (avg_factor + eps) + # if reduction is 'none', then do nothing, otherwise raise an error + elif reduction != 'none': + raise ValueError('avg_factor can not be used with reduction="sum"') + return loss + + +def weighted_loss(loss_func: Callable) -> Callable: + """Create a weighted version of a given loss function. + + To use this decorator, the loss function must have the signature like + `loss_func(pred, target, **kwargs)`. The function only needs to compute + element-wise loss without any reduction. This decorator will add weight + and reduction arguments to the function. The decorated function will have + the signature like `loss_func(pred, target, weight=None, reduction='mean', + avg_factor=None, **kwargs)`. + + :Example: + + >>> import torch + >>> @weighted_loss + >>> def l1_loss(pred, target): + >>> return (pred - target).abs() + + >>> pred = torch.Tensor([0, 2, 3]) + >>> target = torch.Tensor([1, 1, 1]) + >>> weight = torch.Tensor([1, 0, 1]) + + >>> l1_loss(pred, target) + tensor(1.3333) + >>> l1_loss(pred, target, weight) + tensor(1.) + >>> l1_loss(pred, target, reduction='none') + tensor([1., 1., 2.]) + >>> l1_loss(pred, target, weight, avg_factor=2) + tensor(1.5000) + """ + + @functools.wraps(loss_func) + def wrapper(pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + reduction: str = 'mean', + avg_factor: Optional[int] = None, + **kwargs) -> Tensor: + """ + Args: + pred (Tensor): The prediction. + target (Tensor): Target bboxes. + weight (Optional[Tensor], optional): The weight of loss for each + prediction. Defaults to None. + reduction (str, optional): Options are "none", "mean" and "sum". + Defaults to 'mean'. + avg_factor (Optional[int], optional): Average factor that is used + to average the loss. Defaults to None. + + Returns: + Tensor: Loss tensor. + """ + # get element-wise loss + loss = loss_func(pred, target, **kwargs) + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + + return wrapper diff --git a/mmdetection/mmdet/models/losses/varifocal_loss.py b/mmdetection/mmdet/models/losses/varifocal_loss.py new file mode 100644 index 00000000..58ab1673 --- /dev/null +++ b/mmdetection/mmdet/models/losses/varifocal_loss.py @@ -0,0 +1,141 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch.nn as nn +import torch.nn.functional as F +from torch import Tensor + +from mmdet.registry import MODELS +from .utils import weight_reduce_loss + + +def varifocal_loss(pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + alpha: float = 0.75, + gamma: float = 2.0, + iou_weighted: bool = True, + reduction: str = 'mean', + avg_factor: Optional[int] = None) -> Tensor: + """`Varifocal Loss `_ + + Args: + pred (Tensor): The prediction with shape (N, C), C is the + number of classes. + target (Tensor): The learning target of the iou-aware + classification score with shape (N, C), C is the number of classes. + weight (Tensor, optional): The weight of loss for each + prediction. Defaults to None. + alpha (float, optional): A balance factor for the negative part of + Varifocal Loss, which is different from the alpha of Focal Loss. + Defaults to 0.75. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + iou_weighted (bool, optional): Whether to weight the loss of the + positive example with the iou target. Defaults to True. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. Options are "none", "mean" and + "sum". + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + + Returns: + Tensor: Loss tensor. + """ + # pred and target should be of the same size + assert pred.size() == target.size() + pred_sigmoid = pred.sigmoid() + target = target.type_as(pred) + if iou_weighted: + focal_weight = target * (target > 0.0).float() + \ + alpha * (pred_sigmoid - target).abs().pow(gamma) * \ + (target <= 0.0).float() + else: + focal_weight = (target > 0.0).float() + \ + alpha * (pred_sigmoid - target).abs().pow(gamma) * \ + (target <= 0.0).float() + loss = F.binary_cross_entropy_with_logits( + pred, target, reduction='none') * focal_weight + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + + +@MODELS.register_module() +class VarifocalLoss(nn.Module): + + def __init__(self, + use_sigmoid: bool = True, + alpha: float = 0.75, + gamma: float = 2.0, + iou_weighted: bool = True, + reduction: str = 'mean', + loss_weight: float = 1.0) -> None: + """`Varifocal Loss `_ + + Args: + use_sigmoid (bool, optional): Whether the prediction is + used for sigmoid or softmax. Defaults to True. + alpha (float, optional): A balance factor for the negative part of + Varifocal Loss, which is different from the alpha of Focal + Loss. Defaults to 0.75. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + iou_weighted (bool, optional): Whether to weight the loss of the + positive examples with the iou target. Defaults to True. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. Options are "none", "mean" and + "sum". + loss_weight (float, optional): Weight of loss. Defaults to 1.0. + """ + super().__init__() + assert use_sigmoid is True, \ + 'Only sigmoid varifocal loss supported now.' + assert alpha >= 0.0 + self.use_sigmoid = use_sigmoid + self.alpha = alpha + self.gamma = gamma + self.iou_weighted = iou_weighted + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None) -> Tensor: + """Forward function. + + Args: + pred (Tensor): The prediction with shape (N, C), C is the + number of classes. + target (Tensor): The learning target of the iou-aware + classification score with shape (N, C), C is + the number of classes. + weight (Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Options are "none", "mean" and "sum". + + Returns: + Tensor: The calculated loss + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if self.use_sigmoid: + loss_cls = self.loss_weight * varifocal_loss( + pred, + target, + weight, + alpha=self.alpha, + gamma=self.gamma, + iou_weighted=self.iou_weighted, + reduction=reduction, + avg_factor=avg_factor) + else: + raise NotImplementedError + return loss_cls diff --git a/mmdetection/mmdet/models/mot/__init__.py b/mmdetection/mmdet/models/mot/__init__.py new file mode 100644 index 00000000..1bd3c8d3 --- /dev/null +++ b/mmdetection/mmdet/models/mot/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base import BaseMOTModel +from .bytetrack import ByteTrack +from .deep_sort import DeepSORT +from .ocsort import OCSORT +from .qdtrack import QDTrack +from .strongsort import StrongSORT + +__all__ = [ + 'BaseMOTModel', 'ByteTrack', 'QDTrack', 'DeepSORT', 'StrongSORT', 'OCSORT' +] diff --git a/mmdetection/mmdet/models/mot/base.py b/mmdetection/mmdet/models/mot/base.py new file mode 100644 index 00000000..99814179 --- /dev/null +++ b/mmdetection/mmdet/models/mot/base.py @@ -0,0 +1,147 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import Dict, List, Tuple, Union + +from mmengine.model import BaseModel +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import OptTrackSampleList, TrackSampleList +from mmdet.utils import OptConfigType, OptMultiConfig + + +@MODELS.register_module() +class BaseMOTModel(BaseModel, metaclass=ABCMeta): + """Base class for multiple object tracking. + + Args: + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`TrackDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + init_cfg (dict or list[dict]): Initialization config dict. + """ + + def __init__(self, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + + def freeze_module(self, module: Union[List[str], Tuple[str], str]) -> None: + """Freeze module during training.""" + if isinstance(module, str): + modules = [module] + else: + if not (isinstance(module, list) or isinstance(module, tuple)): + raise TypeError('module must be a str or a list.') + else: + modules = module + for module in modules: + m = getattr(self, module) + m.eval() + for param in m.parameters(): + param.requires_grad = False + + @property + def with_detector(self) -> bool: + """bool: whether the framework has a detector.""" + return hasattr(self, 'detector') and self.detector is not None + + @property + def with_reid(self) -> bool: + """bool: whether the framework has a reid model.""" + return hasattr(self, 'reid') and self.reid is not None + + @property + def with_motion(self) -> bool: + """bool: whether the framework has a motion model.""" + return hasattr(self, 'motion') and self.motion is not None + + @property + def with_track_head(self) -> bool: + """bool: whether the framework has a track_head.""" + return hasattr(self, 'track_head') and self.track_head is not None + + @property + def with_tracker(self) -> bool: + """bool: whether the framework has a tracker.""" + return hasattr(self, 'tracker') and self.tracker is not None + + def forward(self, + inputs: Dict[str, Tensor], + data_samples: OptTrackSampleList = None, + mode: str = 'predict', + **kwargs): + """The unified entry for a forward process in both training and test. + + The method should accept three modes: "tensor", "predict" and "loss": + + - "tensor": Forward the whole network and return tensor or tuple of + tensor without any post-processing, same as a common nn.Module. + - "predict": Forward and return the predictions, which are fully + processed to a list of :obj:`TrackDataSample`. + - "loss": Forward and return a dict of losses according to the given + inputs and data samples. + + Note that this method doesn't handle neither back propagation nor + optimizer updating, which are done in the :meth:`train_step`. + + Args: + inputs (Dict[str, Tensor]): of shape (N, T, C, H, W) + encoding input images. Typically these should be mean centered + and std scaled. The N denotes batch size. The T denotes the + number of key/reference frames. + - img (Tensor) : The key images. + - ref_img (Tensor): The reference images. + data_samples (list[:obj:`TrackDataSample`], optional): The + annotation data of every samples. Defaults to None. + mode (str): Return what kind of value. Defaults to 'predict'. + + Returns: + The return type depends on ``mode``. + + - If ``mode="tensor"``, return a tensor or a tuple of tensor. + - If ``mode="predict"``, return a list of :obj:`TrackDataSample`. + - If ``mode="loss"``, return a dict of tensor. + """ + if mode == 'loss': + return self.loss(inputs, data_samples, **kwargs) + elif mode == 'predict': + return self.predict(inputs, data_samples, **kwargs) + elif mode == 'tensor': + return self._forward(inputs, data_samples, **kwargs) + else: + raise RuntimeError(f'Invalid mode "{mode}". ' + 'Only supports loss, predict and tensor mode') + + @abstractmethod + def loss(self, inputs: Dict[str, Tensor], data_samples: TrackSampleList, + **kwargs) -> Union[dict, tuple]: + """Calculate losses from a batch of inputs and data samples.""" + pass + + @abstractmethod + def predict(self, inputs: Dict[str, Tensor], data_samples: TrackSampleList, + **kwargs) -> TrackSampleList: + """Predict results from a batch of inputs and data samples with post- + processing.""" + pass + + def _forward(self, + inputs: Dict[str, Tensor], + data_samples: OptTrackSampleList = None, + **kwargs): + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + inputs (Dict[str, Tensor]): of shape (N, T, C, H, W). + data_samples (List[:obj:`TrackDataSample`], optional): The + Data Samples. It usually includes information such as + `gt_instance`. + + Returns: + tuple[list]: A tuple of features from ``head`` forward. + """ + raise NotImplementedError( + "_forward function (namely 'tensor' mode) is not supported now") diff --git a/mmdetection/mmdet/models/mot/bytetrack.py b/mmdetection/mmdet/models/mot/bytetrack.py new file mode 100644 index 00000000..8a3bb867 --- /dev/null +++ b/mmdetection/mmdet/models/mot/bytetrack.py @@ -0,0 +1,94 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, Optional + +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList, TrackSampleList +from mmdet.utils import OptConfigType, OptMultiConfig +from .base import BaseMOTModel + + +@MODELS.register_module() +class ByteTrack(BaseMOTModel): + """ByteTrack: Multi-Object Tracking by Associating Every Detection Box. + + This multi object tracker is the implementation of `ByteTrack + `_. + + Args: + detector (dict): Configuration of detector. Defaults to None. + tracker (dict): Configuration of tracker. Defaults to None. + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`TrackDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + init_cfg (dict or list[dict]): Configuration of initialization. + Defaults to None. + """ + + def __init__(self, + detector: Optional[dict] = None, + tracker: Optional[dict] = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__(data_preprocessor, init_cfg) + + if detector is not None: + self.detector = MODELS.build(detector) + + if tracker is not None: + self.tracker = MODELS.build(tracker) + + def loss(self, inputs: Tensor, data_samples: SampleList, **kwargs) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + inputs (Tensor): of shape (N, C, H, W) encoding + input images. Typically these should be mean centered and std + scaled. The N denotes batch size + data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance`. + + Returns: + dict: A dictionary of loss components. + """ + return self.detector.loss(inputs, data_samples, **kwargs) + + def predict(self, inputs: Dict[str, Tensor], data_samples: TrackSampleList, + **kwargs) -> TrackSampleList: + """Predict results from a video and data samples with post-processing. + + Args: + inputs (Tensor): of shape (N, T, C, H, W) encoding + input images. The N denotes batch size. + The T denotes the number of frames in a video. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `video_data_samples`. + Returns: + TrackSampleList: Tracking results of the inputs. + """ + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + assert inputs.size(0) == 1, \ + 'Bytetrack inference only support ' \ + '1 batch size per gpu for now.' + + assert len(data_samples) == 1, \ + 'Bytetrack inference only support 1 batch size per gpu for now.' + + track_data_sample = data_samples[0] + video_len = len(track_data_sample) + + for frame_id in range(video_len): + img_data_sample = track_data_sample[frame_id] + single_img = inputs[:, frame_id].contiguous() + # det_results List[DetDataSample] + det_results = self.detector.predict(single_img, [img_data_sample]) + assert len(det_results) == 1, 'Batch inference is not supported.' + + pred_track_instances = self.tracker.track( + data_sample=det_results[0], **kwargs) + img_data_sample.pred_track_instances = pred_track_instances + + return [track_data_sample] diff --git a/mmdetection/mmdet/models/mot/deep_sort.py b/mmdetection/mmdet/models/mot/deep_sort.py new file mode 100644 index 00000000..70b30c7b --- /dev/null +++ b/mmdetection/mmdet/models/mot/deep_sort.py @@ -0,0 +1,110 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import TrackSampleList +from mmdet.utils import OptConfigType +from .base import BaseMOTModel + + +@MODELS.register_module() +class DeepSORT(BaseMOTModel): + """Simple online and realtime tracking with a deep association metric. + + Details can be found at `DeepSORT`_. + + Args: + detector (dict): Configuration of detector. Defaults to None. + reid (dict): Configuration of reid. Defaults to None + tracker (dict): Configuration of tracker. Defaults to None. + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`TrackDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + init_cfg (dict or list[dict]): Configuration of initialization. + Defaults to None. + """ + + def __init__(self, + detector: Optional[dict] = None, + reid: Optional[dict] = None, + tracker: Optional[dict] = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptConfigType = None): + super().__init__(data_preprocessor, init_cfg) + + if detector is not None: + self.detector = MODELS.build(detector) + + if reid is not None: + self.reid = MODELS.build(reid) + + if tracker is not None: + self.tracker = MODELS.build(tracker) + + self.preprocess_cfg = data_preprocessor + + def loss(self, inputs: Tensor, data_samples: TrackSampleList, + **kwargs) -> dict: + """Calculate losses from a batch of inputs and data samples.""" + raise NotImplementedError( + 'Please train `detector` and `reid` models firstly, then \ + inference with SORT/DeepSORT.') + + def predict(self, + inputs: Tensor, + data_samples: TrackSampleList, + rescale: bool = True, + **kwargs) -> TrackSampleList: + """Predict results from a video and data samples with post- processing. + + Args: + inputs (Tensor): of shape (N, T, C, H, W) encoding + input images. The N denotes batch size. + The T denotes the number of key frames + and reference frames. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance`. + rescale (bool, Optional): If False, then returned bboxes and masks + will fit the scale of img, otherwise, returned bboxes and masks + will fit the scale of original image shape. Defaults to True. + + Returns: + TrackSampleList: List[TrackDataSample] + Tracking results of the input videos. + Each DetDataSample usually contains ``pred_track_instances``. + """ + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + assert inputs.size(0) == 1, \ + 'SORT/DeepSORT inference only support ' \ + '1 batch size per gpu for now.' + + assert len(data_samples) == 1, \ + 'SORT/DeepSORT inference only support ' \ + '1 batch size per gpu for now.' + + track_data_sample = data_samples[0] + video_len = len(track_data_sample) + if track_data_sample[0].frame_id == 0: + self.tracker.reset() + + for frame_id in range(video_len): + img_data_sample = track_data_sample[frame_id] + single_img = inputs[:, frame_id].contiguous() + # det_results List[DetDataSample] + det_results = self.detector.predict(single_img, [img_data_sample]) + assert len(det_results) == 1, 'Batch inference is not supported.' + + pred_track_instances = self.tracker.track( + model=self, + img=single_img, + feats=None, + data_sample=det_results[0], + data_preprocessor=self.preprocess_cfg, + rescale=rescale, + **kwargs) + img_data_sample.pred_track_instances = pred_track_instances + + return [track_data_sample] diff --git a/mmdetection/mmdet/models/mot/ocsort.py b/mmdetection/mmdet/models/mot/ocsort.py new file mode 100644 index 00000000..abf4eb3b --- /dev/null +++ b/mmdetection/mmdet/models/mot/ocsort.py @@ -0,0 +1,82 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +from typing import Dict, Optional + +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import TrackSampleList +from mmdet.utils import OptConfigType, OptMultiConfig +from .base import BaseMOTModel + + +@MODELS.register_module() +class OCSORT(BaseMOTModel): + """OCOSRT: Observation-Centric SORT: Rethinking SORT for Robust + Multi-Object Tracking + + This multi object tracker is the implementation of `OC-SORT + `_. + + Args: + detector (dict): Configuration of detector. Defaults to None. + tracker (dict): Configuration of tracker. Defaults to None. + motion (dict): Configuration of motion. Defaults to None. + init_cfg (dict): Configuration of initialization. Defaults to None. + """ + + def __init__(self, + detector: Optional[dict] = None, + tracker: Optional[dict] = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__(data_preprocessor, init_cfg) + + if detector is not None: + self.detector = MODELS.build(detector) + + if tracker is not None: + self.tracker = MODELS.build(tracker) + + def loss(self, inputs: Tensor, data_samples: TrackSampleList, + **kwargs) -> dict: + """Calculate losses from a batch of inputs and data samples.""" + return self.detector.loss(inputs, data_samples, **kwargs) + + def predict(self, inputs: Dict[str, Tensor], data_samples: TrackSampleList, + **kwargs) -> TrackSampleList: + """Predict results from a video and data samples with post-processing. + + Args: + inputs (Tensor): of shape (N, T, C, H, W) encoding + input images. The N denotes batch size. + The T denotes the number of frames in a video. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `video_data_samples`. + Returns: + TrackSampleList: Tracking results of the inputs. + """ + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + assert inputs.size(0) == 1, \ + 'OCSORT inference only support ' \ + '1 batch size per gpu for now.' + + assert len(data_samples) == 1, \ + 'OCSORT inference only support 1 batch size per gpu for now.' + + track_data_sample = data_samples[0] + video_len = len(track_data_sample) + + for frame_id in range(video_len): + img_data_sample = track_data_sample[frame_id] + single_img = inputs[:, frame_id].contiguous() + # det_results List[DetDataSample] + det_results = self.detector.predict(single_img, [img_data_sample]) + assert len(det_results) == 1, 'Batch inference is not supported.' + + pred_track_instances = self.tracker.track( + data_sample=det_results[0], **kwargs) + img_data_sample.pred_track_instances = pred_track_instances + + return [track_data_sample] diff --git a/mmdetection/mmdet/models/mot/qdtrack.py b/mmdetection/mmdet/models/mot/qdtrack.py new file mode 100644 index 00000000..43d5dd60 --- /dev/null +++ b/mmdetection/mmdet/models/mot/qdtrack.py @@ -0,0 +1,186 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Union + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import TrackSampleList +from mmdet.utils import OptConfigType, OptMultiConfig +from .base import BaseMOTModel + + +@MODELS.register_module() +class QDTrack(BaseMOTModel): + """Quasi-Dense Similarity Learning for Multiple Object Tracking. + + This multi object tracker is the implementation of `QDTrack + `_. + + Args: + detector (dict): Configuration of detector. Defaults to None. + track_head (dict): Configuration of track head. Defaults to None. + tracker (dict): Configuration of tracker. Defaults to None. + freeze_detector (bool): If True, freeze the detector weights. + Defaults to False. + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`TrackDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + init_cfg (dict or list[dict]): Configuration of initialization. + Defaults to None. + """ + + def __init__(self, + detector: Optional[dict] = None, + track_head: Optional[dict] = None, + tracker: Optional[dict] = None, + freeze_detector: bool = False, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__(data_preprocessor, init_cfg) + if detector is not None: + self.detector = MODELS.build(detector) + + if track_head is not None: + self.track_head = MODELS.build(track_head) + + if tracker is not None: + self.tracker = MODELS.build(tracker) + + self.freeze_detector = freeze_detector + if self.freeze_detector: + self.freeze_module('detector') + + def predict(self, + inputs: Tensor, + data_samples: TrackSampleList, + rescale: bool = True, + **kwargs) -> TrackSampleList: + """Predict results from a video and data samples with post- processing. + + Args: + inputs (Tensor): of shape (N, T, C, H, W) encoding + input images. The N denotes batch size. + The T denotes the number of frames in a video. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `video_data_samples`. + rescale (bool, Optional): If False, then returned bboxes and masks + will fit the scale of img, otherwise, returned bboxes and masks + will fit the scale of original image shape. Defaults to True. + + Returns: + TrackSampleList: Tracking results of the inputs. + """ + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + assert inputs.size(0) == 1, \ + 'QDTrack inference only support 1 batch size per gpu for now.' + + assert len(data_samples) == 1, \ + 'QDTrack only support 1 batch size per gpu for now.' + + track_data_sample = data_samples[0] + video_len = len(track_data_sample) + if track_data_sample[0].frame_id == 0: + self.tracker.reset() + + for frame_id in range(video_len): + img_data_sample = track_data_sample[frame_id] + single_img = inputs[:, frame_id].contiguous() + x = self.detector.extract_feat(single_img) + rpn_results_list = self.detector.rpn_head.predict( + x, [img_data_sample]) + # det_results List[InstanceData] + det_results = self.detector.roi_head.predict( + x, rpn_results_list, [img_data_sample], rescale=rescale) + assert len(det_results) == 1, 'Batch inference is not supported.' + img_data_sample.pred_instances = det_results[0] + frame_pred_track_instances = self.tracker.track( + model=self, + img=single_img, + feats=x, + data_sample=img_data_sample, + **kwargs) + img_data_sample.pred_track_instances = frame_pred_track_instances + + return [track_data_sample] + + def loss(self, inputs: Tensor, data_samples: TrackSampleList, + **kwargs) -> Union[dict, tuple]: + """Calculate losses from a batch of inputs and data samples. + + Args: + inputs (Dict[str, Tensor]): of shape (N, T, C, H, W) encoding + input images. Typically these should be mean centered and std + scaled. The N denotes batch size. The T denotes the number of + frames. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `video_data_samples`. + + Returns: + dict: A dictionary of loss components. + """ + # modify the inputs shape to fit mmdet + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + assert inputs.size(1) == 2, \ + 'QDTrack can only have 1 key frame and 1 reference frame.' + + # split the data_samples into two aspects: key frames and reference + # frames + ref_data_samples, key_data_samples = [], [] + key_frame_inds, ref_frame_inds = [], [] + # set cat_id of gt_labels to 0 in RPN + for track_data_sample in data_samples: + key_frame_inds.append(track_data_sample.key_frames_inds[0]) + ref_frame_inds.append(track_data_sample.ref_frames_inds[0]) + key_data_sample = track_data_sample.get_key_frames()[0] + key_data_sample.gt_instances.labels = \ + torch.zeros_like(key_data_sample.gt_instances.labels) + key_data_samples.append(key_data_sample) + ref_data_sample = track_data_sample.get_ref_frames()[0] + ref_data_samples.append(ref_data_sample) + + key_frame_inds = torch.tensor(key_frame_inds, dtype=torch.int64) + ref_frame_inds = torch.tensor(ref_frame_inds, dtype=torch.int64) + batch_inds = torch.arange(len(inputs)) + key_imgs = inputs[batch_inds, key_frame_inds].contiguous() + ref_imgs = inputs[batch_inds, ref_frame_inds].contiguous() + + x = self.detector.extract_feat(key_imgs) + ref_x = self.detector.extract_feat(ref_imgs) + + losses = dict() + # RPN head forward and loss + assert self.detector.with_rpn, \ + 'QDTrack only support detector with RPN.' + + proposal_cfg = self.detector.train_cfg.get('rpn_proposal', + self.detector.test_cfg.rpn) + rpn_losses, rpn_results_list = self.detector.rpn_head. \ + loss_and_predict(x, + key_data_samples, + proposal_cfg=proposal_cfg, + **kwargs) + ref_rpn_results_list = self.detector.rpn_head.predict( + ref_x, ref_data_samples, **kwargs) + + # avoid get same name with roi_head loss + keys = rpn_losses.keys() + for key in keys: + if 'loss' in key and 'rpn' not in key: + rpn_losses[f'rpn_{key}'] = rpn_losses.pop(key) + losses.update(rpn_losses) + + # roi_head loss + losses_detect = self.detector.roi_head.loss(x, rpn_results_list, + key_data_samples, **kwargs) + losses.update(losses_detect) + + # tracking head loss + losses_track = self.track_head.loss(x, ref_x, rpn_results_list, + ref_rpn_results_list, data_samples, + **kwargs) + losses.update(losses_track) + + return losses diff --git a/mmdetection/mmdet/models/mot/strongsort.py b/mmdetection/mmdet/models/mot/strongsort.py new file mode 100644 index 00000000..6129bf49 --- /dev/null +++ b/mmdetection/mmdet/models/mot/strongsort.py @@ -0,0 +1,129 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import numpy as np +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import TrackSampleList +from mmdet.utils import OptConfigType +from .deep_sort import DeepSORT + + +@MODELS.register_module() +class StrongSORT(DeepSORT): + """StrongSORT: Make DeepSORT Great Again. + + Details can be found at `StrongSORT`_. + + Args: + detector (dict): Configuration of detector. Defaults to None. + reid (dict): Configuration of reid. Defaults to None + tracker (dict): Configuration of tracker. Defaults to None. + kalman (dict): Configuration of Kalman filter. Defaults to None. + cmc (dict): Configuration of camera model compensation. + Defaults to None. + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`TrackDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + init_cfg (dict or list[dict]): Configuration of initialization. + Defaults to None. + """ + + def __init__(self, + detector: Optional[dict] = None, + reid: Optional[dict] = None, + cmc: Optional[dict] = None, + tracker: Optional[dict] = None, + postprocess_model: Optional[dict] = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptConfigType = None): + super().__init__(detector, reid, tracker, data_preprocessor, init_cfg) + + if cmc is not None: + self.cmc = TASK_UTILS.build(cmc) + + if postprocess_model is not None: + self.postprocess_model = TASK_UTILS.build(postprocess_model) + + @property + def with_cmc(self): + """bool: whether the framework has a camera model compensation + model. + """ + return hasattr(self, 'cmc') and self.cmc is not None + + def predict(self, + inputs: Tensor, + data_samples: TrackSampleList, + rescale: bool = True, + **kwargs) -> TrackSampleList: + """Predict results from a video and data samples with post- processing. + + Args: + inputs (Tensor): of shape (N, T, C, H, W) encoding + input images. The N denotes batch size. + The T denotes the number of key frames + and reference frames. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance`. + rescale (bool, Optional): If False, then returned bboxes and masks + will fit the scale of img, otherwise, returned bboxes and masks + will fit the scale of original image shape. Defaults to True. + + Returns: + TrackSampleList: List[TrackDataSample] + Tracking results of the input videos. + Each DetDataSample usually contains ``pred_track_instances``. + """ + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + assert inputs.size(0) == 1, \ + 'SORT/DeepSORT inference only support ' \ + '1 batch size per gpu for now.' + + assert len(data_samples) == 1, \ + 'SORT/DeepSORT inference only support ' \ + '1 batch size per gpu for now.' + + track_data_sample = data_samples[0] + video_len = len(track_data_sample) + + video_track_instances = [] + for frame_id in range(video_len): + img_data_sample = track_data_sample[frame_id] + single_img = inputs[:, frame_id].contiguous() + # det_results List[DetDataSample] + det_results = self.detector.predict(single_img, [img_data_sample]) + assert len(det_results) == 1, 'Batch inference is not supported.' + + pred_track_instances = self.tracker.track( + model=self, + img=single_img, + data_sample=det_results[0], + data_preprocessor=self.preprocess_cfg, + rescale=rescale, + **kwargs) + for i in range(len(pred_track_instances.instances_id)): + video_track_instances.append( + np.array([ + frame_id + 1, + pred_track_instances.instances_id[i].cpu(), + pred_track_instances.bboxes[i][0].cpu(), + pred_track_instances.bboxes[i][1].cpu(), + (pred_track_instances.bboxes[i][2] - + pred_track_instances.bboxes[i][0]).cpu(), + (pred_track_instances.bboxes[i][3] - + pred_track_instances.bboxes[i][1]).cpu(), + pred_track_instances.scores[i].cpu() + ])) + video_track_instances = np.array(video_track_instances).reshape(-1, 7) + video_track_instances = self.postprocess_model.forward( + video_track_instances) + for frame_id in range(video_len): + track_data_sample[frame_id].pred_track_instances = \ + InstanceData(bboxes=video_track_instances[ + video_track_instances[:, 0] == frame_id + 1, :]) + + return [track_data_sample] diff --git a/mmdetection/mmdet/models/necks/__init__.py b/mmdetection/mmdet/models/necks/__init__.py new file mode 100644 index 00000000..343fbfef --- /dev/null +++ b/mmdetection/mmdet/models/necks/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .bfp import BFP +from .channel_mapper import ChannelMapper +from .cspnext_pafpn import CSPNeXtPAFPN +from .ct_resnet_neck import CTResNetNeck +from .dilated_encoder import DilatedEncoder +from .dyhead import DyHead +from .fpg import FPG +from .fpn import FPN +from .fpn_carafe import FPN_CARAFE +from .fpn_dropblock import FPN_DropBlock +from .hrfpn import HRFPN +from .nas_fpn import NASFPN +from .nasfcos_fpn import NASFCOS_FPN +from .pafpn import PAFPN +from .rfp import RFP +from .ssd_neck import SSDNeck +from .ssh import SSH +from .yolo_neck import YOLOV3Neck +from .yolox_pafpn import YOLOXPAFPN + +__all__ = [ + 'FPN', 'BFP', 'ChannelMapper', 'HRFPN', 'NASFPN', 'FPN_CARAFE', 'PAFPN', + 'NASFCOS_FPN', 'RFP', 'YOLOV3Neck', 'FPG', 'DilatedEncoder', + 'CTResNetNeck', 'SSDNeck', 'YOLOXPAFPN', 'DyHead', 'CSPNeXtPAFPN', 'SSH', + 'FPN_DropBlock' +] diff --git a/mmdetection/mmdet/models/necks/bfp.py b/mmdetection/mmdet/models/necks/bfp.py new file mode 100644 index 00000000..401cdb0f --- /dev/null +++ b/mmdetection/mmdet/models/necks/bfp.py @@ -0,0 +1,111 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmcv.cnn.bricks import NonLocal2d +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType, OptMultiConfig + + +@MODELS.register_module() +class BFP(BaseModule): + """BFP (Balanced Feature Pyramids) + + BFP takes multi-level features as inputs and gather them into a single one, + then refine the gathered feature and scatter the refined results to + multi-level features. This module is used in Libra R-CNN (CVPR 2019), see + the paper `Libra R-CNN: Towards Balanced Learning for Object Detection + `_ for details. + + Args: + in_channels (int): Number of input channels (feature maps of all levels + should have the same channels). + num_levels (int): Number of input feature levels. + refine_level (int): Index of integration and refine level of BSF in + multi-level features from bottom to top. + refine_type (str): Type of the refine op, currently support + [None, 'conv', 'non_local']. + conv_cfg (:obj:`ConfigDict` or dict, optional): The config dict for + convolution layers. + norm_cfg (:obj:`ConfigDict` or dict, optional): The config dict for + normalization layers. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or + dict], optional): Initialization config dict. + """ + + def __init__( + self, + in_channels: int, + num_levels: int, + refine_level: int = 2, + refine_type: str = None, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = dict( + type='Xavier', layer='Conv2d', distribution='uniform') + ) -> None: + super().__init__(init_cfg=init_cfg) + assert refine_type in [None, 'conv', 'non_local'] + + self.in_channels = in_channels + self.num_levels = num_levels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + + self.refine_level = refine_level + self.refine_type = refine_type + assert 0 <= self.refine_level < self.num_levels + + if self.refine_type == 'conv': + self.refine = ConvModule( + self.in_channels, + self.in_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg) + elif self.refine_type == 'non_local': + self.refine = NonLocal2d( + self.in_channels, + reduction=1, + use_scale=False, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg) + + def forward(self, inputs: Tuple[Tensor]) -> Tuple[Tensor]: + """Forward function.""" + assert len(inputs) == self.num_levels + + # step 1: gather multi-level features by resize and average + feats = [] + gather_size = inputs[self.refine_level].size()[2:] + for i in range(self.num_levels): + if i < self.refine_level: + gathered = F.adaptive_max_pool2d( + inputs[i], output_size=gather_size) + else: + gathered = F.interpolate( + inputs[i], size=gather_size, mode='nearest') + feats.append(gathered) + + bsf = sum(feats) / len(feats) + + # step 2: refine gathered features + if self.refine_type is not None: + bsf = self.refine(bsf) + + # step 3: scatter refined features to multi-levels by a residual path + outs = [] + for i in range(self.num_levels): + out_size = inputs[i].size()[2:] + if i < self.refine_level: + residual = F.interpolate(bsf, size=out_size, mode='nearest') + else: + residual = F.adaptive_max_pool2d(bsf, output_size=out_size) + outs.append(residual + inputs[i]) + + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/channel_mapper.py b/mmdetection/mmdet/models/necks/channel_mapper.py new file mode 100644 index 00000000..74293618 --- /dev/null +++ b/mmdetection/mmdet/models/necks/channel_mapper.py @@ -0,0 +1,112 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple, Union + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType, OptMultiConfig + + +@MODELS.register_module() +class ChannelMapper(BaseModule): + """Channel Mapper to reduce/increase channels of backbone features. + + This is used to reduce/increase channels of backbone features. + + Args: + in_channels (List[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale). + kernel_size (int, optional): kernel_size for reducing channels (used + at each scale). Default: 3. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Default: None. + norm_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + normalization layer. Default: None. + act_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + activation layer in ConvModule. Default: dict(type='ReLU'). + bias (bool | str): If specified as `auto`, it will be decided by the + norm_cfg. Bias will be set as True if `norm_cfg` is None, otherwise + False. Default: "auto". + num_outs (int, optional): Number of output feature maps. There would + be extra_convs when num_outs larger than the length of in_channels. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or dict], + optional): Initialization config dict. + Example: + >>> import torch + >>> in_channels = [2, 3, 5, 7] + >>> scales = [340, 170, 84, 43] + >>> inputs = [torch.rand(1, c, s, s) + ... for c, s in zip(in_channels, scales)] + >>> self = ChannelMapper(in_channels, 11, 3).eval() + >>> outputs = self.forward(inputs) + >>> for i in range(len(outputs)): + ... print(f'outputs[{i}].shape = {outputs[i].shape}') + outputs[0].shape = torch.Size([1, 11, 340, 340]) + outputs[1].shape = torch.Size([1, 11, 170, 170]) + outputs[2].shape = torch.Size([1, 11, 84, 84]) + outputs[3].shape = torch.Size([1, 11, 43, 43]) + """ + + def __init__( + self, + in_channels: List[int], + out_channels: int, + kernel_size: int = 3, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + act_cfg: OptConfigType = dict(type='ReLU'), + bias: Union[bool, str] = 'auto', + num_outs: int = None, + init_cfg: OptMultiConfig = dict( + type='Xavier', layer='Conv2d', distribution='uniform') + ) -> None: + super().__init__(init_cfg=init_cfg) + assert isinstance(in_channels, list) + self.extra_convs = None + if num_outs is None: + num_outs = len(in_channels) + self.convs = nn.ModuleList() + for in_channel in in_channels: + self.convs.append( + ConvModule( + in_channel, + out_channels, + kernel_size, + padding=(kernel_size - 1) // 2, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + bias=bias)) + if num_outs > len(in_channels): + self.extra_convs = nn.ModuleList() + for i in range(len(in_channels), num_outs): + if i == len(in_channels): + in_channel = in_channels[-1] + else: + in_channel = out_channels + self.extra_convs.append( + ConvModule( + in_channel, + out_channels, + 3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + bias=bias)) + + def forward(self, inputs: Tuple[Tensor]) -> Tuple[Tensor]: + """Forward function.""" + assert len(inputs) == len(self.convs) + outs = [self.convs[i](inputs[i]) for i in range(len(inputs))] + if self.extra_convs: + for i in range(len(self.extra_convs)): + if i == 0: + outs.append(self.extra_convs[0](inputs[-1])) + else: + outs.append(self.extra_convs[i](outs[-1])) + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/cspnext_pafpn.py b/mmdetection/mmdet/models/necks/cspnext_pafpn.py new file mode 100644 index 00000000..a52ba72d --- /dev/null +++ b/mmdetection/mmdet/models/necks/cspnext_pafpn.py @@ -0,0 +1,170 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Sequence, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptMultiConfig +from ..layers import CSPLayer + + +@MODELS.register_module() +class CSPNeXtPAFPN(BaseModule): + """Path Aggregation Network with CSPNeXt blocks. + + Args: + in_channels (Sequence[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale) + num_csp_blocks (int): Number of bottlenecks in CSPLayer. + Defaults to 3. + use_depthwise (bool): Whether to use depthwise separable convolution in + blocks. Defaults to False. + expand_ratio (float): Ratio to adjust the number of channels of the + hidden layer. Default: 0.5 + upsample_cfg (dict): Config dict for interpolate layer. + Default: `dict(scale_factor=2, mode='nearest')` + conv_cfg (dict, optional): Config dict for convolution layer. + Default: None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN') + act_cfg (dict): Config dict for activation layer. + Default: dict(type='Swish') + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__( + self, + in_channels: Sequence[int], + out_channels: int, + num_csp_blocks: int = 3, + use_depthwise: bool = False, + expand_ratio: float = 0.5, + upsample_cfg: ConfigType = dict(scale_factor=2, mode='nearest'), + conv_cfg: bool = None, + norm_cfg: ConfigType = dict(type='BN', momentum=0.03, eps=0.001), + act_cfg: ConfigType = dict(type='Swish'), + init_cfg: OptMultiConfig = dict( + type='Kaiming', + layer='Conv2d', + a=math.sqrt(5), + distribution='uniform', + mode='fan_in', + nonlinearity='leaky_relu') + ) -> None: + super().__init__(init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + + conv = DepthwiseSeparableConvModule if use_depthwise else ConvModule + + # build top-down blocks + self.upsample = nn.Upsample(**upsample_cfg) + self.reduce_layers = nn.ModuleList() + self.top_down_blocks = nn.ModuleList() + for idx in range(len(in_channels) - 1, 0, -1): + self.reduce_layers.append( + ConvModule( + in_channels[idx], + in_channels[idx - 1], + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.top_down_blocks.append( + CSPLayer( + in_channels[idx - 1] * 2, + in_channels[idx - 1], + num_blocks=num_csp_blocks, + add_identity=False, + use_depthwise=use_depthwise, + use_cspnext_block=True, + expand_ratio=expand_ratio, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + # build bottom-up blocks + self.downsamples = nn.ModuleList() + self.bottom_up_blocks = nn.ModuleList() + for idx in range(len(in_channels) - 1): + self.downsamples.append( + conv( + in_channels[idx], + in_channels[idx], + 3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.bottom_up_blocks.append( + CSPLayer( + in_channels[idx] * 2, + in_channels[idx + 1], + num_blocks=num_csp_blocks, + add_identity=False, + use_depthwise=use_depthwise, + use_cspnext_block=True, + expand_ratio=expand_ratio, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + self.out_convs = nn.ModuleList() + for i in range(len(in_channels)): + self.out_convs.append( + conv( + in_channels[i], + out_channels, + 3, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + def forward(self, inputs: Tuple[Tensor, ...]) -> Tuple[Tensor, ...]: + """ + Args: + inputs (tuple[Tensor]): input features. + + Returns: + tuple[Tensor]: YOLOXPAFPN features. + """ + assert len(inputs) == len(self.in_channels) + + # top-down path + inner_outs = [inputs[-1]] + for idx in range(len(self.in_channels) - 1, 0, -1): + feat_heigh = inner_outs[0] + feat_low = inputs[idx - 1] + feat_heigh = self.reduce_layers[len(self.in_channels) - 1 - idx]( + feat_heigh) + inner_outs[0] = feat_heigh + + upsample_feat = self.upsample(feat_heigh) + + inner_out = self.top_down_blocks[len(self.in_channels) - 1 - idx]( + torch.cat([upsample_feat, feat_low], 1)) + inner_outs.insert(0, inner_out) + + # bottom-up path + outs = [inner_outs[0]] + for idx in range(len(self.in_channels) - 1): + feat_low = outs[-1] + feat_height = inner_outs[idx + 1] + downsample_feat = self.downsamples[idx](feat_low) + out = self.bottom_up_blocks[idx]( + torch.cat([downsample_feat, feat_height], 1)) + outs.append(out) + + # out convs + for idx, conv in enumerate(self.out_convs): + outs[idx] = conv(outs[idx]) + + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/ct_resnet_neck.py b/mmdetection/mmdet/models/necks/ct_resnet_neck.py new file mode 100644 index 00000000..9109fe79 --- /dev/null +++ b/mmdetection/mmdet/models/necks/ct_resnet_neck.py @@ -0,0 +1,102 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Sequence, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule + +from mmdet.registry import MODELS +from mmdet.utils import OptMultiConfig + + +@MODELS.register_module() +class CTResNetNeck(BaseModule): + """The neck used in `CenterNet `_ for + object classification and box regression. + + Args: + in_channels (int): Number of input channels. + num_deconv_filters (tuple[int]): Number of filters per stage. + num_deconv_kernels (tuple[int]): Number of kernels per stage. + use_dcn (bool): If True, use DCNv2. Defaults to True. + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`], optional): Initialization + config dict. + """ + + def __init__(self, + in_channels: int, + num_deconv_filters: Tuple[int, ...], + num_deconv_kernels: Tuple[int, ...], + use_dcn: bool = True, + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + assert len(num_deconv_filters) == len(num_deconv_kernels) + self.fp16_enabled = False + self.use_dcn = use_dcn + self.in_channels = in_channels + self.deconv_layers = self._make_deconv_layer(num_deconv_filters, + num_deconv_kernels) + + def _make_deconv_layer( + self, num_deconv_filters: Tuple[int, ...], + num_deconv_kernels: Tuple[int, ...]) -> nn.Sequential: + """use deconv layers to upsample backbone's output.""" + layers = [] + for i in range(len(num_deconv_filters)): + feat_channels = num_deconv_filters[i] + conv_module = ConvModule( + self.in_channels, + feat_channels, + 3, + padding=1, + conv_cfg=dict(type='DCNv2') if self.use_dcn else None, + norm_cfg=dict(type='BN')) + layers.append(conv_module) + upsample_module = ConvModule( + feat_channels, + feat_channels, + num_deconv_kernels[i], + stride=2, + padding=1, + conv_cfg=dict(type='deconv'), + norm_cfg=dict(type='BN')) + layers.append(upsample_module) + self.in_channels = feat_channels + + return nn.Sequential(*layers) + + def init_weights(self) -> None: + """Initialize the parameters.""" + for m in self.modules(): + if isinstance(m, nn.ConvTranspose2d): + # In order to be consistent with the source code, + # reset the ConvTranspose2d initialization parameters + m.reset_parameters() + # Simulated bilinear upsampling kernel + w = m.weight.data + f = math.ceil(w.size(2) / 2) + c = (2 * f - 1 - f % 2) / (2. * f) + for i in range(w.size(2)): + for j in range(w.size(3)): + w[0, 0, i, j] = \ + (1 - math.fabs(i / f - c)) * ( + 1 - math.fabs(j / f - c)) + for c in range(1, w.size(0)): + w[c, 0, :, :] = w[0, 0, :, :] + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + # self.use_dcn is False + elif not self.use_dcn and isinstance(m, nn.Conv2d): + # In order to be consistent with the source code, + # reset the Conv2d initialization parameters + m.reset_parameters() + + def forward(self, x: Sequence[torch.Tensor]) -> Tuple[torch.Tensor]: + """model forward.""" + assert isinstance(x, (list, tuple)) + outs = self.deconv_layers(x[-1]) + return outs, diff --git a/mmdetection/mmdet/models/necks/dilated_encoder.py b/mmdetection/mmdet/models/necks/dilated_encoder.py new file mode 100644 index 00000000..e9beb3ea --- /dev/null +++ b/mmdetection/mmdet/models/necks/dilated_encoder.py @@ -0,0 +1,109 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import ConvModule, is_norm +from mmengine.model import caffe2_xavier_init, constant_init, normal_init +from torch.nn import BatchNorm2d + +from mmdet.registry import MODELS + + +class Bottleneck(nn.Module): + """Bottleneck block for DilatedEncoder used in `YOLOF. + + `. + + The Bottleneck contains three ConvLayers and one residual connection. + + Args: + in_channels (int): The number of input channels. + mid_channels (int): The number of middle output channels. + dilation (int): Dilation rate. + norm_cfg (dict): Dictionary to construct and config norm layer. + """ + + def __init__(self, + in_channels, + mid_channels, + dilation, + norm_cfg=dict(type='BN', requires_grad=True)): + super(Bottleneck, self).__init__() + self.conv1 = ConvModule( + in_channels, mid_channels, 1, norm_cfg=norm_cfg) + self.conv2 = ConvModule( + mid_channels, + mid_channels, + 3, + padding=dilation, + dilation=dilation, + norm_cfg=norm_cfg) + self.conv3 = ConvModule( + mid_channels, in_channels, 1, norm_cfg=norm_cfg) + + def forward(self, x): + identity = x + out = self.conv1(x) + out = self.conv2(out) + out = self.conv3(out) + out = out + identity + return out + + +@MODELS.register_module() +class DilatedEncoder(nn.Module): + """Dilated Encoder for YOLOF `. + + This module contains two types of components: + - the original FPN lateral convolution layer and fpn convolution layer, + which are 1x1 conv + 3x3 conv + - the dilated residual block + + Args: + in_channels (int): The number of input channels. + out_channels (int): The number of output channels. + block_mid_channels (int): The number of middle block output channels + num_residual_blocks (int): The number of residual blocks. + block_dilations (list): The list of residual blocks dilation. + """ + + def __init__(self, in_channels, out_channels, block_mid_channels, + num_residual_blocks, block_dilations): + super(DilatedEncoder, self).__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.block_mid_channels = block_mid_channels + self.num_residual_blocks = num_residual_blocks + self.block_dilations = block_dilations + self._init_layers() + + def _init_layers(self): + self.lateral_conv = nn.Conv2d( + self.in_channels, self.out_channels, kernel_size=1) + self.lateral_norm = BatchNorm2d(self.out_channels) + self.fpn_conv = nn.Conv2d( + self.out_channels, self.out_channels, kernel_size=3, padding=1) + self.fpn_norm = BatchNorm2d(self.out_channels) + encoder_blocks = [] + for i in range(self.num_residual_blocks): + dilation = self.block_dilations[i] + encoder_blocks.append( + Bottleneck( + self.out_channels, + self.block_mid_channels, + dilation=dilation)) + self.dilated_encoder_blocks = nn.Sequential(*encoder_blocks) + + def init_weights(self): + caffe2_xavier_init(self.lateral_conv) + caffe2_xavier_init(self.fpn_conv) + for m in [self.lateral_norm, self.fpn_norm]: + constant_init(m, 1) + for m in self.dilated_encoder_blocks.modules(): + if isinstance(m, nn.Conv2d): + normal_init(m, mean=0, std=0.01) + if is_norm(m): + constant_init(m, 1) + + def forward(self, feature): + out = self.lateral_norm(self.lateral_conv(feature[-1])) + out = self.fpn_norm(self.fpn_conv(out)) + return self.dilated_encoder_blocks(out), diff --git a/mmdetection/mmdet/models/necks/dyhead.py b/mmdetection/mmdet/models/necks/dyhead.py new file mode 100644 index 00000000..5f5ae0b2 --- /dev/null +++ b/mmdetection/mmdet/models/necks/dyhead.py @@ -0,0 +1,173 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import build_activation_layer, build_norm_layer +from mmcv.ops.modulated_deform_conv import ModulatedDeformConv2d +from mmengine.model import BaseModule, constant_init, normal_init + +from mmdet.registry import MODELS +from ..layers import DyReLU + +# Reference: +# https://github.com/microsoft/DynamicHead +# https://github.com/jshilong/SEPC + + +class DyDCNv2(nn.Module): + """ModulatedDeformConv2d with normalization layer used in DyHead. + + This module cannot be configured with `conv_cfg=dict(type='DCNv2')` + because DyHead calculates offset and mask from middle-level feature. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + stride (int | tuple[int], optional): Stride of the convolution. + Default: 1. + norm_cfg (dict, optional): Config dict for normalization layer. + Default: dict(type='GN', num_groups=16, requires_grad=True). + """ + + def __init__(self, + in_channels, + out_channels, + stride=1, + norm_cfg=dict(type='GN', num_groups=16, requires_grad=True)): + super().__init__() + self.with_norm = norm_cfg is not None + bias = not self.with_norm + self.conv = ModulatedDeformConv2d( + in_channels, out_channels, 3, stride=stride, padding=1, bias=bias) + if self.with_norm: + self.norm = build_norm_layer(norm_cfg, out_channels)[1] + + def forward(self, x, offset, mask): + """Forward function.""" + x = self.conv(x.contiguous(), offset, mask) + if self.with_norm: + x = self.norm(x) + return x + + +class DyHeadBlock(nn.Module): + """DyHead Block with three types of attention. + + HSigmoid arguments in default act_cfg follow official code, not paper. + https://github.com/microsoft/DynamicHead/blob/master/dyhead/dyrelu.py + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + zero_init_offset (bool, optional): Whether to use zero init for + `spatial_conv_offset`. Default: True. + act_cfg (dict, optional): Config dict for the last activation layer of + scale-aware attention. Default: dict(type='HSigmoid', bias=3.0, + divisor=6.0). + """ + + def __init__(self, + in_channels, + out_channels, + zero_init_offset=True, + act_cfg=dict(type='HSigmoid', bias=3.0, divisor=6.0)): + super().__init__() + self.zero_init_offset = zero_init_offset + # (offset_x, offset_y, mask) * kernel_size_y * kernel_size_x + self.offset_and_mask_dim = 3 * 3 * 3 + self.offset_dim = 2 * 3 * 3 + + self.spatial_conv_high = DyDCNv2(in_channels, out_channels) + self.spatial_conv_mid = DyDCNv2(in_channels, out_channels) + self.spatial_conv_low = DyDCNv2(in_channels, out_channels, stride=2) + self.spatial_conv_offset = nn.Conv2d( + in_channels, self.offset_and_mask_dim, 3, padding=1) + self.scale_attn_module = nn.Sequential( + nn.AdaptiveAvgPool2d(1), nn.Conv2d(out_channels, 1, 1), + nn.ReLU(inplace=True), build_activation_layer(act_cfg)) + self.task_attn_module = DyReLU(out_channels) + self._init_weights() + + def _init_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + normal_init(m, 0, 0.01) + if self.zero_init_offset: + constant_init(self.spatial_conv_offset, 0) + + def forward(self, x): + """Forward function.""" + outs = [] + for level in range(len(x)): + # calculate offset and mask of DCNv2 from middle-level feature + offset_and_mask = self.spatial_conv_offset(x[level]) + offset = offset_and_mask[:, :self.offset_dim, :, :] + mask = offset_and_mask[:, self.offset_dim:, :, :].sigmoid() + + mid_feat = self.spatial_conv_mid(x[level], offset, mask) + sum_feat = mid_feat * self.scale_attn_module(mid_feat) + summed_levels = 1 + if level > 0: + low_feat = self.spatial_conv_low(x[level - 1], offset, mask) + sum_feat += low_feat * self.scale_attn_module(low_feat) + summed_levels += 1 + if level < len(x) - 1: + # this upsample order is weird, but faster than natural order + # https://github.com/microsoft/DynamicHead/issues/25 + high_feat = F.interpolate( + self.spatial_conv_high(x[level + 1], offset, mask), + size=x[level].shape[-2:], + mode='bilinear', + align_corners=True) + sum_feat += high_feat * self.scale_attn_module(high_feat) + summed_levels += 1 + outs.append(self.task_attn_module(sum_feat / summed_levels)) + + return outs + + +@MODELS.register_module() +class DyHead(BaseModule): + """DyHead neck consisting of multiple DyHead Blocks. + + See `Dynamic Head: Unifying Object Detection Heads with Attentions + `_ for details. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + num_blocks (int, optional): Number of DyHead Blocks. Default: 6. + zero_init_offset (bool, optional): Whether to use zero init for + `spatial_conv_offset`. Default: True. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + num_blocks=6, + zero_init_offset=True, + init_cfg=None): + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + self.num_blocks = num_blocks + self.zero_init_offset = zero_init_offset + + dyhead_blocks = [] + for i in range(num_blocks): + in_channels = self.in_channels if i == 0 else self.out_channels + dyhead_blocks.append( + DyHeadBlock( + in_channels, + self.out_channels, + zero_init_offset=zero_init_offset)) + self.dyhead_blocks = nn.Sequential(*dyhead_blocks) + + def forward(self, inputs): + """Forward function.""" + assert isinstance(inputs, (tuple, list)) + outs = self.dyhead_blocks(inputs) + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/fpg.py b/mmdetection/mmdet/models/necks/fpg.py new file mode 100644 index 00000000..73ee799b --- /dev/null +++ b/mmdetection/mmdet/models/necks/fpg.py @@ -0,0 +1,406 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule + +from mmdet.registry import MODELS + + +class Transition(BaseModule): + """Base class for transition. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + """ + + def __init__(self, in_channels, out_channels, init_cfg=None): + super().__init__(init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + + def forward(x): + pass + + +class UpInterpolationConv(Transition): + """A transition used for up-sampling. + + Up-sample the input by interpolation then refines the feature by + a convolution layer. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + scale_factor (int): Up-sampling factor. Default: 2. + mode (int): Interpolation mode. Default: nearest. + align_corners (bool): Whether align corners when interpolation. + Default: None. + kernel_size (int): Kernel size for the conv. Default: 3. + """ + + def __init__(self, + in_channels, + out_channels, + scale_factor=2, + mode='nearest', + align_corners=None, + kernel_size=3, + init_cfg=None, + **kwargs): + super().__init__(in_channels, out_channels, init_cfg) + self.mode = mode + self.scale_factor = scale_factor + self.align_corners = align_corners + self.conv = ConvModule( + in_channels, + out_channels, + kernel_size, + padding=(kernel_size - 1) // 2, + **kwargs) + + def forward(self, x): + x = F.interpolate( + x, + scale_factor=self.scale_factor, + mode=self.mode, + align_corners=self.align_corners) + x = self.conv(x) + return x + + +class LastConv(Transition): + """A transition used for refining the output of the last stage. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + num_inputs (int): Number of inputs of the FPN features. + kernel_size (int): Kernel size for the conv. Default: 3. + """ + + def __init__(self, + in_channels, + out_channels, + num_inputs, + kernel_size=3, + init_cfg=None, + **kwargs): + super().__init__(in_channels, out_channels, init_cfg) + self.num_inputs = num_inputs + self.conv_out = ConvModule( + in_channels, + out_channels, + kernel_size, + padding=(kernel_size - 1) // 2, + **kwargs) + + def forward(self, inputs): + assert len(inputs) == self.num_inputs + return self.conv_out(inputs[-1]) + + +@MODELS.register_module() +class FPG(BaseModule): + """FPG. + + Implementation of `Feature Pyramid Grids (FPG) + `_. + This implementation only gives the basic structure stated in the paper. + But users can implement different type of transitions to fully explore the + the potential power of the structure of FPG. + + Args: + in_channels (int): Number of input channels (feature maps of all levels + should have the same channels). + out_channels (int): Number of output channels (used at each scale) + num_outs (int): Number of output scales. + stack_times (int): The number of times the pyramid architecture will + be stacked. + paths (list[str]): Specify the path order of each stack level. + Each element in the list should be either 'bu' (bottom-up) or + 'td' (top-down). + inter_channels (int): Number of inter channels. + same_up_trans (dict): Transition that goes down at the same stage. + same_down_trans (dict): Transition that goes up at the same stage. + across_lateral_trans (dict): Across-pathway same-stage + across_down_trans (dict): Across-pathway bottom-up connection. + across_up_trans (dict): Across-pathway top-down connection. + across_skip_trans (dict): Across-pathway skip connection. + output_trans (dict): Transition that trans the output of the + last stage. + start_level (int): Index of the start input backbone level used to + build the feature pyramid. Default: 0. + end_level (int): Index of the end input backbone level (exclusive) to + build the feature pyramid. Default: -1, which means the last level. + add_extra_convs (bool): It decides whether to add conv + layers on top of the original feature maps. Default to False. + If True, its actual mode is specified by `extra_convs_on_inputs`. + norm_cfg (dict): Config dict for normalization layer. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + transition_types = { + 'conv': ConvModule, + 'interpolation_conv': UpInterpolationConv, + 'last_conv': LastConv, + } + + def __init__(self, + in_channels, + out_channels, + num_outs, + stack_times, + paths, + inter_channels=None, + same_down_trans=None, + same_up_trans=dict( + type='conv', kernel_size=3, stride=2, padding=1), + across_lateral_trans=dict(type='conv', kernel_size=1), + across_down_trans=dict(type='conv', kernel_size=3), + across_up_trans=None, + across_skip_trans=dict(type='identity'), + output_trans=dict(type='last_conv', kernel_size=3), + start_level=0, + end_level=-1, + add_extra_convs=False, + norm_cfg=None, + skip_inds=None, + init_cfg=[ + dict(type='Caffe2Xavier', layer='Conv2d'), + dict( + type='Constant', + layer=[ + '_BatchNorm', '_InstanceNorm', 'GroupNorm', + 'LayerNorm' + ], + val=1.0) + ]): + super(FPG, self).__init__(init_cfg) + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + self.num_ins = len(in_channels) + self.num_outs = num_outs + if inter_channels is None: + self.inter_channels = [out_channels for _ in range(num_outs)] + elif isinstance(inter_channels, int): + self.inter_channels = [inter_channels for _ in range(num_outs)] + else: + assert isinstance(inter_channels, list) + assert len(inter_channels) == num_outs + self.inter_channels = inter_channels + self.stack_times = stack_times + self.paths = paths + assert isinstance(paths, list) and len(paths) == stack_times + for d in paths: + assert d in ('bu', 'td') + + self.same_down_trans = same_down_trans + self.same_up_trans = same_up_trans + self.across_lateral_trans = across_lateral_trans + self.across_down_trans = across_down_trans + self.across_up_trans = across_up_trans + self.output_trans = output_trans + self.across_skip_trans = across_skip_trans + + self.with_bias = norm_cfg is None + # skip inds must be specified if across skip trans is not None + if self.across_skip_trans is not None: + skip_inds is not None + self.skip_inds = skip_inds + assert len(self.skip_inds[0]) <= self.stack_times + + if end_level == -1 or end_level == self.num_ins - 1: + self.backbone_end_level = self.num_ins + assert num_outs >= self.num_ins - start_level + else: + # if end_level is not the last level, no extra level is allowed + self.backbone_end_level = end_level + 1 + assert end_level < self.num_ins + assert num_outs == end_level - start_level + 1 + self.start_level = start_level + self.end_level = end_level + self.add_extra_convs = add_extra_convs + + # build lateral 1x1 convs to reduce channels + self.lateral_convs = nn.ModuleList() + for i in range(self.start_level, self.backbone_end_level): + l_conv = nn.Conv2d(self.in_channels[i], + self.inter_channels[i - self.start_level], 1) + self.lateral_convs.append(l_conv) + + extra_levels = num_outs - self.backbone_end_level + self.start_level + self.extra_downsamples = nn.ModuleList() + for i in range(extra_levels): + if self.add_extra_convs: + fpn_idx = self.backbone_end_level - self.start_level + i + extra_conv = nn.Conv2d( + self.inter_channels[fpn_idx - 1], + self.inter_channels[fpn_idx], + 3, + stride=2, + padding=1) + self.extra_downsamples.append(extra_conv) + else: + self.extra_downsamples.append(nn.MaxPool2d(1, stride=2)) + + self.fpn_transitions = nn.ModuleList() # stack times + for s in range(self.stack_times): + stage_trans = nn.ModuleList() # num of feature levels + for i in range(self.num_outs): + # same, across_lateral, across_down, across_up + trans = nn.ModuleDict() + if s in self.skip_inds[i]: + stage_trans.append(trans) + continue + # build same-stage down trans (used in bottom-up paths) + if i == 0 or self.same_up_trans is None: + same_up_trans = None + else: + same_up_trans = self.build_trans( + self.same_up_trans, self.inter_channels[i - 1], + self.inter_channels[i]) + trans['same_up'] = same_up_trans + # build same-stage up trans (used in top-down paths) + if i == self.num_outs - 1 or self.same_down_trans is None: + same_down_trans = None + else: + same_down_trans = self.build_trans( + self.same_down_trans, self.inter_channels[i + 1], + self.inter_channels[i]) + trans['same_down'] = same_down_trans + # build across lateral trans + across_lateral_trans = self.build_trans( + self.across_lateral_trans, self.inter_channels[i], + self.inter_channels[i]) + trans['across_lateral'] = across_lateral_trans + # build across down trans + if i == self.num_outs - 1 or self.across_down_trans is None: + across_down_trans = None + else: + across_down_trans = self.build_trans( + self.across_down_trans, self.inter_channels[i + 1], + self.inter_channels[i]) + trans['across_down'] = across_down_trans + # build across up trans + if i == 0 or self.across_up_trans is None: + across_up_trans = None + else: + across_up_trans = self.build_trans( + self.across_up_trans, self.inter_channels[i - 1], + self.inter_channels[i]) + trans['across_up'] = across_up_trans + if self.across_skip_trans is None: + across_skip_trans = None + else: + across_skip_trans = self.build_trans( + self.across_skip_trans, self.inter_channels[i - 1], + self.inter_channels[i]) + trans['across_skip'] = across_skip_trans + # build across_skip trans + stage_trans.append(trans) + self.fpn_transitions.append(stage_trans) + + self.output_transition = nn.ModuleList() # output levels + for i in range(self.num_outs): + trans = self.build_trans( + self.output_trans, + self.inter_channels[i], + self.out_channels, + num_inputs=self.stack_times + 1) + self.output_transition.append(trans) + + self.relu = nn.ReLU(inplace=True) + + def build_trans(self, cfg, in_channels, out_channels, **extra_args): + cfg_ = cfg.copy() + trans_type = cfg_.pop('type') + trans_cls = self.transition_types[trans_type] + return trans_cls(in_channels, out_channels, **cfg_, **extra_args) + + def fuse(self, fuse_dict): + out = None + for item in fuse_dict.values(): + if item is not None: + if out is None: + out = item + else: + out = out + item + return out + + def forward(self, inputs): + assert len(inputs) == len(self.in_channels) + + # build all levels from original feature maps + feats = [ + lateral_conv(inputs[i + self.start_level]) + for i, lateral_conv in enumerate(self.lateral_convs) + ] + for downsample in self.extra_downsamples: + feats.append(downsample(feats[-1])) + + outs = [feats] + + for i in range(self.stack_times): + current_outs = outs[-1] + next_outs = [] + direction = self.paths[i] + for j in range(self.num_outs): + if i in self.skip_inds[j]: + next_outs.append(outs[-1][j]) + continue + # feature level + if direction == 'td': + lvl = self.num_outs - j - 1 + else: + lvl = j + # get transitions + if direction == 'td': + same_trans = self.fpn_transitions[i][lvl]['same_down'] + else: + same_trans = self.fpn_transitions[i][lvl]['same_up'] + across_lateral_trans = self.fpn_transitions[i][lvl][ + 'across_lateral'] + across_down_trans = self.fpn_transitions[i][lvl]['across_down'] + across_up_trans = self.fpn_transitions[i][lvl]['across_up'] + across_skip_trans = self.fpn_transitions[i][lvl]['across_skip'] + # init output + to_fuse = dict( + same=None, lateral=None, across_up=None, across_down=None) + # same downsample/upsample + if same_trans is not None: + to_fuse['same'] = same_trans(next_outs[-1]) + # across lateral + if across_lateral_trans is not None: + to_fuse['lateral'] = across_lateral_trans( + current_outs[lvl]) + # across downsample + if lvl > 0 and across_up_trans is not None: + to_fuse['across_up'] = across_up_trans(current_outs[lvl - + 1]) + # across upsample + if (lvl < self.num_outs - 1 and across_down_trans is not None): + to_fuse['across_down'] = across_down_trans( + current_outs[lvl + 1]) + if across_skip_trans is not None: + to_fuse['across_skip'] = across_skip_trans(outs[0][lvl]) + x = self.fuse(to_fuse) + next_outs.append(x) + + if direction == 'td': + outs.append(next_outs[::-1]) + else: + outs.append(next_outs) + + # output trans + final_outs = [] + for i in range(self.num_outs): + lvl_out_list = [] + for s in range(len(outs)): + lvl_out_list.append(outs[s][i]) + lvl_out = self.output_transition[i](lvl_out_list) + final_outs.append(lvl_out) + + return final_outs diff --git a/mmdetection/mmdet/models/necks/fpn.py b/mmdetection/mmdet/models/necks/fpn.py new file mode 100644 index 00000000..67bd8879 --- /dev/null +++ b/mmdetection/mmdet/models/necks/fpn.py @@ -0,0 +1,221 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple, Union + +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, MultiConfig, OptConfigType + + +@MODELS.register_module() +class FPN(BaseModule): + r"""Feature Pyramid Network. + + This is an implementation of paper `Feature Pyramid Networks for Object + Detection `_. + + Args: + in_channels (list[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale). + num_outs (int): Number of output scales. + start_level (int): Index of the start input backbone level used to + build the feature pyramid. Defaults to 0. + end_level (int): Index of the end input backbone level (exclusive) to + build the feature pyramid. Defaults to -1, which means the + last level. + add_extra_convs (bool | str): If bool, it decides whether to add conv + layers on top of the original feature maps. Defaults to False. + If True, it is equivalent to `add_extra_convs='on_input'`. + If str, it specifies the source feature map of the extra convs. + Only the following options are allowed + + - 'on_input': Last feat map of neck inputs (i.e. backbone feature). + - 'on_lateral': Last feature map after lateral convs. + - 'on_output': The last output feature map after fpn convs. + relu_before_extra_convs (bool): Whether to apply relu before the extra + conv. Defaults to False. + no_norm_on_lateral (bool): Whether to apply norm on lateral. + Defaults to False. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + normalization layer. Defaults to None. + act_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + activation layer in ConvModule. Defaults to None. + upsample_cfg (:obj:`ConfigDict` or dict, optional): Config dict + for interpolate layer. Defaults to dict(mode='nearest'). + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. + + Example: + >>> import torch + >>> in_channels = [2, 3, 5, 7] + >>> scales = [340, 170, 84, 43] + >>> inputs = [torch.rand(1, c, s, s) + ... for c, s in zip(in_channels, scales)] + >>> self = FPN(in_channels, 11, len(in_channels)).eval() + >>> outputs = self.forward(inputs) + >>> for i in range(len(outputs)): + ... print(f'outputs[{i}].shape = {outputs[i].shape}') + outputs[0].shape = torch.Size([1, 11, 340, 340]) + outputs[1].shape = torch.Size([1, 11, 170, 170]) + outputs[2].shape = torch.Size([1, 11, 84, 84]) + outputs[3].shape = torch.Size([1, 11, 43, 43]) + """ + + def __init__( + self, + in_channels: List[int], + out_channels: int, + num_outs: int, + start_level: int = 0, + end_level: int = -1, + add_extra_convs: Union[bool, str] = False, + relu_before_extra_convs: bool = False, + no_norm_on_lateral: bool = False, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + act_cfg: OptConfigType = None, + upsample_cfg: ConfigType = dict(mode='nearest'), + init_cfg: MultiConfig = dict( + type='Xavier', layer='Conv2d', distribution='uniform') + ) -> None: + super().__init__(init_cfg=init_cfg) + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + self.num_ins = len(in_channels) + self.num_outs = num_outs + self.relu_before_extra_convs = relu_before_extra_convs + self.no_norm_on_lateral = no_norm_on_lateral + self.fp16_enabled = False + self.upsample_cfg = upsample_cfg.copy() + + if end_level == -1 or end_level == self.num_ins - 1: + self.backbone_end_level = self.num_ins + assert num_outs >= self.num_ins - start_level + else: + # if end_level is not the last level, no extra level is allowed + self.backbone_end_level = end_level + 1 + assert end_level < self.num_ins + assert num_outs == end_level - start_level + 1 + self.start_level = start_level + self.end_level = end_level + self.add_extra_convs = add_extra_convs + assert isinstance(add_extra_convs, (str, bool)) + if isinstance(add_extra_convs, str): + # Extra_convs_source choices: 'on_input', 'on_lateral', 'on_output' + assert add_extra_convs in ('on_input', 'on_lateral', 'on_output') + elif add_extra_convs: # True + self.add_extra_convs = 'on_input' + + self.lateral_convs = nn.ModuleList() + self.fpn_convs = nn.ModuleList() + + for i in range(self.start_level, self.backbone_end_level): + l_conv = ConvModule( + in_channels[i], + out_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg if not self.no_norm_on_lateral else None, + act_cfg=act_cfg, + inplace=False) + fpn_conv = ConvModule( + out_channels, + out_channels, + 3, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + inplace=False) + + self.lateral_convs.append(l_conv) + self.fpn_convs.append(fpn_conv) + + # add extra conv layers (e.g., RetinaNet) + extra_levels = num_outs - self.backbone_end_level + self.start_level + if self.add_extra_convs and extra_levels >= 1: + for i in range(extra_levels): + if i == 0 and self.add_extra_convs == 'on_input': + in_channels = self.in_channels[self.backbone_end_level - 1] + else: + in_channels = out_channels + extra_fpn_conv = ConvModule( + in_channels, + out_channels, + 3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + inplace=False) + self.fpn_convs.append(extra_fpn_conv) + + def forward(self, inputs: Tuple[Tensor]) -> tuple: + """Forward function. + + Args: + inputs (tuple[Tensor]): Features from the upstream network, each + is a 4D-tensor. + + Returns: + tuple: Feature maps, each is a 4D-tensor. + """ + assert len(inputs) == len(self.in_channels) + + # build laterals + laterals = [ + lateral_conv(inputs[i + self.start_level]) + for i, lateral_conv in enumerate(self.lateral_convs) + ] + + # build top-down path + used_backbone_levels = len(laterals) + for i in range(used_backbone_levels - 1, 0, -1): + # In some cases, fixing `scale factor` (e.g. 2) is preferred, but + # it cannot co-exist with `size` in `F.interpolate`. + if 'scale_factor' in self.upsample_cfg: + # fix runtime error of "+=" inplace operation in PyTorch 1.10 + laterals[i - 1] = laterals[i - 1] + F.interpolate( + laterals[i], **self.upsample_cfg) + else: + prev_shape = laterals[i - 1].shape[2:] + laterals[i - 1] = laterals[i - 1] + F.interpolate( + laterals[i], size=prev_shape, **self.upsample_cfg) + + # build outputs + # part 1: from original levels + outs = [ + self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels) + ] + # part 2: add extra levels + if self.num_outs > len(outs): + # use max pool to get more levels on top of outputs + # (e.g., Faster R-CNN, Mask R-CNN) + if not self.add_extra_convs: + for i in range(self.num_outs - used_backbone_levels): + outs.append(F.max_pool2d(outs[-1], 1, stride=2)) + # add conv layers on top of original feature maps (RetinaNet) + else: + if self.add_extra_convs == 'on_input': + extra_source = inputs[self.backbone_end_level - 1] + elif self.add_extra_convs == 'on_lateral': + extra_source = laterals[-1] + elif self.add_extra_convs == 'on_output': + extra_source = outs[-1] + else: + raise NotImplementedError + outs.append(self.fpn_convs[used_backbone_levels](extra_source)) + for i in range(used_backbone_levels + 1, self.num_outs): + if self.relu_before_extra_convs: + outs.append(self.fpn_convs[i](F.relu(outs[-1]))) + else: + outs.append(self.fpn_convs[i](outs[-1])) + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/fpn_carafe.py b/mmdetection/mmdet/models/necks/fpn_carafe.py new file mode 100644 index 00000000..b393ff7c --- /dev/null +++ b/mmdetection/mmdet/models/necks/fpn_carafe.py @@ -0,0 +1,275 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import ConvModule, build_upsample_layer +from mmcv.ops.carafe import CARAFEPack +from mmengine.model import BaseModule, ModuleList, xavier_init + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class FPN_CARAFE(BaseModule): + """FPN_CARAFE is a more flexible implementation of FPN. It allows more + choice for upsample methods during the top-down pathway. + + It can reproduce the performance of ICCV 2019 paper + CARAFE: Content-Aware ReAssembly of FEatures + Please refer to https://arxiv.org/abs/1905.02188 for more details. + + Args: + in_channels (list[int]): Number of channels for each input feature map. + out_channels (int): Output channels of feature pyramids. + num_outs (int): Number of output stages. + start_level (int): Start level of feature pyramids. + (Default: 0) + end_level (int): End level of feature pyramids. + (Default: -1 indicates the last level). + norm_cfg (dict): Dictionary to construct and config norm layer. + activate (str): Type of activation function in ConvModule + (Default: None indicates w/o activation). + order (dict): Order of components in ConvModule. + upsample (str): Type of upsample layer. + upsample_cfg (dict): Dictionary to construct and config upsample layer. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + in_channels, + out_channels, + num_outs, + start_level=0, + end_level=-1, + norm_cfg=None, + act_cfg=None, + order=('conv', 'norm', 'act'), + upsample_cfg=dict( + type='carafe', + up_kernel=5, + up_group=1, + encoder_kernel=3, + encoder_dilation=1), + init_cfg=None): + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + super(FPN_CARAFE, self).__init__(init_cfg) + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + self.num_ins = len(in_channels) + self.num_outs = num_outs + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.with_bias = norm_cfg is None + self.upsample_cfg = upsample_cfg.copy() + self.upsample = self.upsample_cfg.get('type') + self.relu = nn.ReLU(inplace=False) + + self.order = order + assert order in [('conv', 'norm', 'act'), ('act', 'conv', 'norm')] + + assert self.upsample in [ + 'nearest', 'bilinear', 'deconv', 'pixel_shuffle', 'carafe', None + ] + if self.upsample in ['deconv', 'pixel_shuffle']: + assert hasattr( + self.upsample_cfg, + 'upsample_kernel') and self.upsample_cfg.upsample_kernel > 0 + self.upsample_kernel = self.upsample_cfg.pop('upsample_kernel') + + if end_level == -1 or end_level == self.num_ins - 1: + self.backbone_end_level = self.num_ins + assert num_outs >= self.num_ins - start_level + else: + # if end_level is not the last level, no extra level is allowed + self.backbone_end_level = end_level + 1 + assert end_level < self.num_ins + assert num_outs == end_level - start_level + 1 + self.start_level = start_level + self.end_level = end_level + + self.lateral_convs = ModuleList() + self.fpn_convs = ModuleList() + self.upsample_modules = ModuleList() + + for i in range(self.start_level, self.backbone_end_level): + l_conv = ConvModule( + in_channels[i], + out_channels, + 1, + norm_cfg=norm_cfg, + bias=self.with_bias, + act_cfg=act_cfg, + inplace=False, + order=self.order) + fpn_conv = ConvModule( + out_channels, + out_channels, + 3, + padding=1, + norm_cfg=self.norm_cfg, + bias=self.with_bias, + act_cfg=act_cfg, + inplace=False, + order=self.order) + if i != self.backbone_end_level - 1: + upsample_cfg_ = self.upsample_cfg.copy() + if self.upsample == 'deconv': + upsample_cfg_.update( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=self.upsample_kernel, + stride=2, + padding=(self.upsample_kernel - 1) // 2, + output_padding=(self.upsample_kernel - 1) // 2) + elif self.upsample == 'pixel_shuffle': + upsample_cfg_.update( + in_channels=out_channels, + out_channels=out_channels, + scale_factor=2, + upsample_kernel=self.upsample_kernel) + elif self.upsample == 'carafe': + upsample_cfg_.update(channels=out_channels, scale_factor=2) + else: + # suppress warnings + align_corners = (None + if self.upsample == 'nearest' else False) + upsample_cfg_.update( + scale_factor=2, + mode=self.upsample, + align_corners=align_corners) + upsample_module = build_upsample_layer(upsample_cfg_) + self.upsample_modules.append(upsample_module) + self.lateral_convs.append(l_conv) + self.fpn_convs.append(fpn_conv) + + # add extra conv layers (e.g., RetinaNet) + extra_out_levels = ( + num_outs - self.backbone_end_level + self.start_level) + if extra_out_levels >= 1: + for i in range(extra_out_levels): + in_channels = ( + self.in_channels[self.backbone_end_level - + 1] if i == 0 else out_channels) + extra_l_conv = ConvModule( + in_channels, + out_channels, + 3, + stride=2, + padding=1, + norm_cfg=norm_cfg, + bias=self.with_bias, + act_cfg=act_cfg, + inplace=False, + order=self.order) + if self.upsample == 'deconv': + upsampler_cfg_ = dict( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=self.upsample_kernel, + stride=2, + padding=(self.upsample_kernel - 1) // 2, + output_padding=(self.upsample_kernel - 1) // 2) + elif self.upsample == 'pixel_shuffle': + upsampler_cfg_ = dict( + in_channels=out_channels, + out_channels=out_channels, + scale_factor=2, + upsample_kernel=self.upsample_kernel) + elif self.upsample == 'carafe': + upsampler_cfg_ = dict( + channels=out_channels, + scale_factor=2, + **self.upsample_cfg) + else: + # suppress warnings + align_corners = (None + if self.upsample == 'nearest' else False) + upsampler_cfg_ = dict( + scale_factor=2, + mode=self.upsample, + align_corners=align_corners) + upsampler_cfg_['type'] = self.upsample + upsample_module = build_upsample_layer(upsampler_cfg_) + extra_fpn_conv = ConvModule( + out_channels, + out_channels, + 3, + padding=1, + norm_cfg=self.norm_cfg, + bias=self.with_bias, + act_cfg=act_cfg, + inplace=False, + order=self.order) + self.upsample_modules.append(upsample_module) + self.fpn_convs.append(extra_fpn_conv) + self.lateral_convs.append(extra_l_conv) + + # default init_weights for conv(msra) and norm in ConvModule + def init_weights(self): + """Initialize the weights of module.""" + super(FPN_CARAFE, self).init_weights() + for m in self.modules(): + if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)): + xavier_init(m, distribution='uniform') + for m in self.modules(): + if isinstance(m, CARAFEPack): + m.init_weights() + + def slice_as(self, src, dst): + """Slice ``src`` as ``dst`` + + Note: + ``src`` should have the same or larger size than ``dst``. + + Args: + src (torch.Tensor): Tensors to be sliced. + dst (torch.Tensor): ``src`` will be sliced to have the same + size as ``dst``. + + Returns: + torch.Tensor: Sliced tensor. + """ + assert (src.size(2) >= dst.size(2)) and (src.size(3) >= dst.size(3)) + if src.size(2) == dst.size(2) and src.size(3) == dst.size(3): + return src + else: + return src[:, :, :dst.size(2), :dst.size(3)] + + def tensor_add(self, a, b): + """Add tensors ``a`` and ``b`` that might have different sizes.""" + if a.size() == b.size(): + c = a + b + else: + c = a + self.slice_as(b, a) + return c + + def forward(self, inputs): + """Forward function.""" + assert len(inputs) == len(self.in_channels) + + # build laterals + laterals = [] + for i, lateral_conv in enumerate(self.lateral_convs): + if i <= self.backbone_end_level - self.start_level: + input = inputs[min(i + self.start_level, len(inputs) - 1)] + else: + input = laterals[-1] + lateral = lateral_conv(input) + laterals.append(lateral) + + # build top-down path + for i in range(len(laterals) - 1, 0, -1): + if self.upsample is not None: + upsample_feat = self.upsample_modules[i - 1](laterals[i]) + else: + upsample_feat = laterals[i] + laterals[i - 1] = self.tensor_add(laterals[i - 1], upsample_feat) + + # build outputs + num_conv_outs = len(self.fpn_convs) + outs = [] + for i in range(num_conv_outs): + out = self.fpn_convs[i](laterals[i]) + outs.append(out) + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/fpn_dropblock.py b/mmdetection/mmdet/models/necks/fpn_dropblock.py new file mode 100644 index 00000000..473af924 --- /dev/null +++ b/mmdetection/mmdet/models/necks/fpn_dropblock.py @@ -0,0 +1,90 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple + +import torch.nn.functional as F +from torch import Tensor + +from mmdet.registry import MODELS +from .fpn import FPN + + +@MODELS.register_module() +class FPN_DropBlock(FPN): + + def __init__(self, + *args, + plugin: Optional[dict] = dict( + type='DropBlock', + drop_prob=0.3, + block_size=3, + warmup_iters=0), + **kwargs) -> None: + super().__init__(*args, **kwargs) + self.plugin = None + if plugin is not None: + self.plugin = MODELS.build(plugin) + + def forward(self, inputs: Tuple[Tensor]) -> tuple: + """Forward function. + + Args: + inputs (tuple[Tensor]): Features from the upstream network, each + is a 4D-tensor. + + Returns: + tuple: Feature maps, each is a 4D-tensor. + """ + assert len(inputs) == len(self.in_channels) + + # build laterals + laterals = [ + lateral_conv(inputs[i + self.start_level]) + for i, lateral_conv in enumerate(self.lateral_convs) + ] + + # build top-down path + used_backbone_levels = len(laterals) + for i in range(used_backbone_levels - 1, 0, -1): + # In some cases, fixing `scale factor` (e.g. 2) is preferred, but + # it cannot co-exist with `size` in `F.interpolate`. + if 'scale_factor' in self.upsample_cfg: + # fix runtime error of "+=" inplace operation in PyTorch 1.10 + laterals[i - 1] = laterals[i - 1] + F.interpolate( + laterals[i], **self.upsample_cfg) + else: + prev_shape = laterals[i - 1].shape[2:] + laterals[i - 1] = laterals[i - 1] + F.interpolate( + laterals[i], size=prev_shape, **self.upsample_cfg) + + if self.plugin is not None: + laterals[i - 1] = self.plugin(laterals[i - 1]) + + # build outputs + # part 1: from original levels + outs = [ + self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels) + ] + # part 2: add extra levels + if self.num_outs > len(outs): + # use max pool to get more levels on top of outputs + # (e.g., Faster R-CNN, Mask R-CNN) + if not self.add_extra_convs: + for i in range(self.num_outs - used_backbone_levels): + outs.append(F.max_pool2d(outs[-1], 1, stride=2)) + # add conv layers on top of original feature maps (RetinaNet) + else: + if self.add_extra_convs == 'on_input': + extra_source = inputs[self.backbone_end_level - 1] + elif self.add_extra_convs == 'on_lateral': + extra_source = laterals[-1] + elif self.add_extra_convs == 'on_output': + extra_source = outs[-1] + else: + raise NotImplementedError + outs.append(self.fpn_convs[used_backbone_levels](extra_source)) + for i in range(used_backbone_levels + 1, self.num_outs): + if self.relu_before_extra_convs: + outs.append(self.fpn_convs[i](F.relu(outs[-1]))) + else: + outs.append(self.fpn_convs[i](outs[-1])) + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/hrfpn.py b/mmdetection/mmdet/models/necks/hrfpn.py new file mode 100644 index 00000000..d2627549 --- /dev/null +++ b/mmdetection/mmdet/models/necks/hrfpn.py @@ -0,0 +1,100 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch.utils.checkpoint import checkpoint + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class HRFPN(BaseModule): + """HRFPN (High Resolution Feature Pyramids) + + paper: `High-Resolution Representations for Labeling Pixels and Regions + `_. + + Args: + in_channels (list): number of channels for each branch. + out_channels (int): output channels of feature pyramids. + num_outs (int): number of output stages. + pooling_type (str): pooling for generating feature pyramids + from {MAX, AVG}. + conv_cfg (dict): dictionary to construct and config conv layer. + norm_cfg (dict): dictionary to construct and config norm layer. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. + stride (int): stride of 3x3 convolutional layers + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + in_channels, + out_channels, + num_outs=5, + pooling_type='AVG', + conv_cfg=None, + norm_cfg=None, + with_cp=False, + stride=1, + init_cfg=dict(type='Caffe2Xavier', layer='Conv2d')): + super(HRFPN, self).__init__(init_cfg) + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + self.num_ins = len(in_channels) + self.num_outs = num_outs + self.with_cp = with_cp + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + + self.reduction_conv = ConvModule( + sum(in_channels), + out_channels, + kernel_size=1, + conv_cfg=self.conv_cfg, + act_cfg=None) + + self.fpn_convs = nn.ModuleList() + for i in range(self.num_outs): + self.fpn_convs.append( + ConvModule( + out_channels, + out_channels, + kernel_size=3, + padding=1, + stride=stride, + conv_cfg=self.conv_cfg, + act_cfg=None)) + + if pooling_type == 'MAX': + self.pooling = F.max_pool2d + else: + self.pooling = F.avg_pool2d + + def forward(self, inputs): + """Forward function.""" + assert len(inputs) == self.num_ins + outs = [inputs[0]] + for i in range(1, self.num_ins): + outs.append( + F.interpolate(inputs[i], scale_factor=2**i, mode='bilinear')) + out = torch.cat(outs, dim=1) + if out.requires_grad and self.with_cp: + out = checkpoint(self.reduction_conv, out) + else: + out = self.reduction_conv(out) + outs = [out] + for i in range(1, self.num_outs): + outs.append(self.pooling(out, kernel_size=2**i, stride=2**i)) + outputs = [] + + for i in range(self.num_outs): + if outs[i].requires_grad and self.with_cp: + tmp_out = checkpoint(self.fpn_convs[i], outs[i]) + else: + tmp_out = self.fpn_convs[i](outs[i]) + outputs.append(tmp_out) + return tuple(outputs) diff --git a/mmdetection/mmdet/models/necks/nas_fpn.py b/mmdetection/mmdet/models/necks/nas_fpn.py new file mode 100644 index 00000000..8ec90cd6 --- /dev/null +++ b/mmdetection/mmdet/models/necks/nas_fpn.py @@ -0,0 +1,171 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmcv.ops.merge_cells import GlobalPoolingCell, SumCell +from mmengine.model import BaseModule, ModuleList +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import MultiConfig, OptConfigType + + +@MODELS.register_module() +class NASFPN(BaseModule): + """NAS-FPN. + + Implementation of `NAS-FPN: Learning Scalable Feature Pyramid Architecture + for Object Detection `_ + + Args: + in_channels (List[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale) + num_outs (int): Number of output scales. + stack_times (int): The number of times the pyramid architecture will + be stacked. + start_level (int): Index of the start input backbone level used to + build the feature pyramid. Defaults to 0. + end_level (int): Index of the end input backbone level (exclusive) to + build the feature pyramid. Defaults to -1, which means the + last level. + norm_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + normalization layer. Defaults to None. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. + """ + + def __init__( + self, + in_channels: List[int], + out_channels: int, + num_outs: int, + stack_times: int, + start_level: int = 0, + end_level: int = -1, + norm_cfg: OptConfigType = None, + init_cfg: MultiConfig = dict(type='Caffe2Xavier', layer='Conv2d') + ) -> None: + super().__init__(init_cfg=init_cfg) + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + self.num_ins = len(in_channels) # num of input feature levels + self.num_outs = num_outs # num of output feature levels + self.stack_times = stack_times + self.norm_cfg = norm_cfg + + if end_level == -1 or end_level == self.num_ins - 1: + self.backbone_end_level = self.num_ins + assert num_outs >= self.num_ins - start_level + else: + # if end_level is not the last level, no extra level is allowed + self.backbone_end_level = end_level + 1 + assert end_level < self.num_ins + assert num_outs == end_level - start_level + 1 + self.start_level = start_level + self.end_level = end_level + + # add lateral connections + self.lateral_convs = nn.ModuleList() + for i in range(self.start_level, self.backbone_end_level): + l_conv = ConvModule( + in_channels[i], + out_channels, + 1, + norm_cfg=norm_cfg, + act_cfg=None) + self.lateral_convs.append(l_conv) + + # add extra downsample layers (stride-2 pooling or conv) + extra_levels = num_outs - self.backbone_end_level + self.start_level + self.extra_downsamples = nn.ModuleList() + for i in range(extra_levels): + extra_conv = ConvModule( + out_channels, out_channels, 1, norm_cfg=norm_cfg, act_cfg=None) + self.extra_downsamples.append( + nn.Sequential(extra_conv, nn.MaxPool2d(2, 2))) + + # add NAS FPN connections + self.fpn_stages = ModuleList() + for _ in range(self.stack_times): + stage = nn.ModuleDict() + # gp(p6, p4) -> p4_1 + stage['gp_64_4'] = GlobalPoolingCell( + in_channels=out_channels, + out_channels=out_channels, + out_norm_cfg=norm_cfg) + # sum(p4_1, p4) -> p4_2 + stage['sum_44_4'] = SumCell( + in_channels=out_channels, + out_channels=out_channels, + out_norm_cfg=norm_cfg) + # sum(p4_2, p3) -> p3_out + stage['sum_43_3'] = SumCell( + in_channels=out_channels, + out_channels=out_channels, + out_norm_cfg=norm_cfg) + # sum(p3_out, p4_2) -> p4_out + stage['sum_34_4'] = SumCell( + in_channels=out_channels, + out_channels=out_channels, + out_norm_cfg=norm_cfg) + # sum(p5, gp(p4_out, p3_out)) -> p5_out + stage['gp_43_5'] = GlobalPoolingCell(with_out_conv=False) + stage['sum_55_5'] = SumCell( + in_channels=out_channels, + out_channels=out_channels, + out_norm_cfg=norm_cfg) + # sum(p7, gp(p5_out, p4_2)) -> p7_out + stage['gp_54_7'] = GlobalPoolingCell(with_out_conv=False) + stage['sum_77_7'] = SumCell( + in_channels=out_channels, + out_channels=out_channels, + out_norm_cfg=norm_cfg) + # gp(p7_out, p5_out) -> p6_out + stage['gp_75_6'] = GlobalPoolingCell( + in_channels=out_channels, + out_channels=out_channels, + out_norm_cfg=norm_cfg) + self.fpn_stages.append(stage) + + def forward(self, inputs: Tuple[Tensor]) -> tuple: + """Forward function. + + Args: + inputs (tuple[Tensor]): Features from the upstream network, each + is a 4D-tensor. + + Returns: + tuple: Feature maps, each is a 4D-tensor. + """ + # build P3-P5 + feats = [ + lateral_conv(inputs[i + self.start_level]) + for i, lateral_conv in enumerate(self.lateral_convs) + ] + # build P6-P7 on top of P5 + for downsample in self.extra_downsamples: + feats.append(downsample(feats[-1])) + + p3, p4, p5, p6, p7 = feats + + for stage in self.fpn_stages: + # gp(p6, p4) -> p4_1 + p4_1 = stage['gp_64_4'](p6, p4, out_size=p4.shape[-2:]) + # sum(p4_1, p4) -> p4_2 + p4_2 = stage['sum_44_4'](p4_1, p4, out_size=p4.shape[-2:]) + # sum(p4_2, p3) -> p3_out + p3 = stage['sum_43_3'](p4_2, p3, out_size=p3.shape[-2:]) + # sum(p3_out, p4_2) -> p4_out + p4 = stage['sum_34_4'](p3, p4_2, out_size=p4.shape[-2:]) + # sum(p5, gp(p4_out, p3_out)) -> p5_out + p5_tmp = stage['gp_43_5'](p4, p3, out_size=p5.shape[-2:]) + p5 = stage['sum_55_5'](p5, p5_tmp, out_size=p5.shape[-2:]) + # sum(p7, gp(p5_out, p4_2)) -> p7_out + p7_tmp = stage['gp_54_7'](p5, p4_2, out_size=p7.shape[-2:]) + p7 = stage['sum_77_7'](p7, p7_tmp, out_size=p7.shape[-2:]) + # gp(p7_out, p5_out) -> p6_out + p6 = stage['gp_75_6'](p7, p5, out_size=p6.shape[-2:]) + + return p3, p4, p5, p6, p7 diff --git a/mmdetection/mmdet/models/necks/nasfcos_fpn.py b/mmdetection/mmdet/models/necks/nasfcos_fpn.py new file mode 100644 index 00000000..12d0848f --- /dev/null +++ b/mmdetection/mmdet/models/necks/nasfcos_fpn.py @@ -0,0 +1,170 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmcv.ops.merge_cells import ConcatCell +from mmengine.model import BaseModule, caffe2_xavier_init + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class NASFCOS_FPN(BaseModule): + """FPN structure in NASFPN. + + Implementation of paper `NAS-FCOS: Fast Neural Architecture Search for + Object Detection `_ + + Args: + in_channels (List[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale) + num_outs (int): Number of output scales. + start_level (int): Index of the start input backbone level used to + build the feature pyramid. Default: 0. + end_level (int): Index of the end input backbone level (exclusive) to + build the feature pyramid. Default: -1, which means the last level. + add_extra_convs (bool): It decides whether to add conv + layers on top of the original feature maps. Default to False. + If True, its actual mode is specified by `extra_convs_on_inputs`. + conv_cfg (dict): dictionary to construct and config conv layer. + norm_cfg (dict): dictionary to construct and config norm layer. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + in_channels, + out_channels, + num_outs, + start_level=1, + end_level=-1, + add_extra_convs=False, + conv_cfg=None, + norm_cfg=None, + init_cfg=None): + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + super(NASFCOS_FPN, self).__init__(init_cfg) + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + self.num_ins = len(in_channels) + self.num_outs = num_outs + self.norm_cfg = norm_cfg + self.conv_cfg = conv_cfg + + if end_level == -1 or end_level == self.num_ins - 1: + self.backbone_end_level = self.num_ins + assert num_outs >= self.num_ins - start_level + else: + # if end_level is not the last level, no extra level is allowed + self.backbone_end_level = end_level + 1 + assert end_level < self.num_ins + assert num_outs == end_level - start_level + 1 + self.start_level = start_level + self.end_level = end_level + self.add_extra_convs = add_extra_convs + + self.adapt_convs = nn.ModuleList() + for i in range(self.start_level, self.backbone_end_level): + adapt_conv = ConvModule( + in_channels[i], + out_channels, + 1, + stride=1, + padding=0, + bias=False, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU', inplace=False)) + self.adapt_convs.append(adapt_conv) + + # C2 is omitted according to the paper + extra_levels = num_outs - self.backbone_end_level + self.start_level + + def build_concat_cell(with_input1_conv, with_input2_conv): + cell_conv_cfg = dict( + kernel_size=1, padding=0, bias=False, groups=out_channels) + return ConcatCell( + in_channels=out_channels, + out_channels=out_channels, + with_out_conv=True, + out_conv_cfg=cell_conv_cfg, + out_norm_cfg=dict(type='BN'), + out_conv_order=('norm', 'act', 'conv'), + with_input1_conv=with_input1_conv, + with_input2_conv=with_input2_conv, + input_conv_cfg=conv_cfg, + input_norm_cfg=norm_cfg, + upsample_mode='nearest') + + # Denote c3=f0, c4=f1, c5=f2 for convince + self.fpn = nn.ModuleDict() + self.fpn['c22_1'] = build_concat_cell(True, True) + self.fpn['c22_2'] = build_concat_cell(True, True) + self.fpn['c32'] = build_concat_cell(True, False) + self.fpn['c02'] = build_concat_cell(True, False) + self.fpn['c42'] = build_concat_cell(True, True) + self.fpn['c36'] = build_concat_cell(True, True) + self.fpn['c61'] = build_concat_cell(True, True) # f9 + self.extra_downsamples = nn.ModuleList() + for i in range(extra_levels): + extra_act_cfg = None if i == 0 \ + else dict(type='ReLU', inplace=False) + self.extra_downsamples.append( + ConvModule( + out_channels, + out_channels, + 3, + stride=2, + padding=1, + act_cfg=extra_act_cfg, + order=('act', 'norm', 'conv'))) + + def forward(self, inputs): + """Forward function.""" + feats = [ + adapt_conv(inputs[i + self.start_level]) + for i, adapt_conv in enumerate(self.adapt_convs) + ] + + for (i, module_name) in enumerate(self.fpn): + idx_1, idx_2 = int(module_name[1]), int(module_name[2]) + res = self.fpn[module_name](feats[idx_1], feats[idx_2]) + feats.append(res) + + ret = [] + for (idx, input_idx) in zip([9, 8, 7], [1, 2, 3]): # add P3, P4, P5 + feats1, feats2 = feats[idx], feats[5] + feats2_resize = F.interpolate( + feats2, + size=feats1.size()[2:], + mode='bilinear', + align_corners=False) + + feats_sum = feats1 + feats2_resize + ret.append( + F.interpolate( + feats_sum, + size=inputs[input_idx].size()[2:], + mode='bilinear', + align_corners=False)) + + for submodule in self.extra_downsamples: + ret.append(submodule(ret[-1])) + + return tuple(ret) + + def init_weights(self): + """Initialize the weights of module.""" + super(NASFCOS_FPN, self).init_weights() + for module in self.fpn.values(): + if hasattr(module, 'conv_out'): + caffe2_xavier_init(module.out_conv.conv) + + for modules in [ + self.adapt_convs.modules(), + self.extra_downsamples.modules() + ]: + for module in modules: + if isinstance(module, nn.Conv2d): + caffe2_xavier_init(module) diff --git a/mmdetection/mmdet/models/necks/pafpn.py b/mmdetection/mmdet/models/necks/pafpn.py new file mode 100644 index 00000000..557638f4 --- /dev/null +++ b/mmdetection/mmdet/models/necks/pafpn.py @@ -0,0 +1,157 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule + +from mmdet.registry import MODELS +from .fpn import FPN + + +@MODELS.register_module() +class PAFPN(FPN): + """Path Aggregation Network for Instance Segmentation. + + This is an implementation of the `PAFPN in Path Aggregation Network + `_. + + Args: + in_channels (List[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale) + num_outs (int): Number of output scales. + start_level (int): Index of the start input backbone level used to + build the feature pyramid. Default: 0. + end_level (int): Index of the end input backbone level (exclusive) to + build the feature pyramid. Default: -1, which means the last level. + add_extra_convs (bool | str): If bool, it decides whether to add conv + layers on top of the original feature maps. Default to False. + If True, it is equivalent to `add_extra_convs='on_input'`. + If str, it specifies the source feature map of the extra convs. + Only the following options are allowed + + - 'on_input': Last feat map of neck inputs (i.e. backbone feature). + - 'on_lateral': Last feature map after lateral convs. + - 'on_output': The last output feature map after fpn convs. + relu_before_extra_convs (bool): Whether to apply relu before the extra + conv. Default: False. + no_norm_on_lateral (bool): Whether to apply norm on lateral. + Default: False. + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Config dict for normalization layer. Default: None. + act_cfg (str): Config dict for activation layer in ConvModule. + Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + in_channels, + out_channels, + num_outs, + start_level=0, + end_level=-1, + add_extra_convs=False, + relu_before_extra_convs=False, + no_norm_on_lateral=False, + conv_cfg=None, + norm_cfg=None, + act_cfg=None, + init_cfg=dict( + type='Xavier', layer='Conv2d', distribution='uniform')): + super(PAFPN, self).__init__( + in_channels, + out_channels, + num_outs, + start_level, + end_level, + add_extra_convs, + relu_before_extra_convs, + no_norm_on_lateral, + conv_cfg, + norm_cfg, + act_cfg, + init_cfg=init_cfg) + # add extra bottom up pathway + self.downsample_convs = nn.ModuleList() + self.pafpn_convs = nn.ModuleList() + for i in range(self.start_level + 1, self.backbone_end_level): + d_conv = ConvModule( + out_channels, + out_channels, + 3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + inplace=False) + pafpn_conv = ConvModule( + out_channels, + out_channels, + 3, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + inplace=False) + self.downsample_convs.append(d_conv) + self.pafpn_convs.append(pafpn_conv) + + def forward(self, inputs): + """Forward function.""" + assert len(inputs) == len(self.in_channels) + + # build laterals + laterals = [ + lateral_conv(inputs[i + self.start_level]) + for i, lateral_conv in enumerate(self.lateral_convs) + ] + + # build top-down path + used_backbone_levels = len(laterals) + for i in range(used_backbone_levels - 1, 0, -1): + prev_shape = laterals[i - 1].shape[2:] + laterals[i - 1] = laterals[i - 1] + F.interpolate( + laterals[i], size=prev_shape, mode='nearest') + + # build outputs + # part 1: from original levels + inter_outs = [ + self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels) + ] + + # part 2: add bottom-up path + for i in range(0, used_backbone_levels - 1): + inter_outs[i + 1] = inter_outs[i + 1] + \ + self.downsample_convs[i](inter_outs[i]) + + outs = [] + outs.append(inter_outs[0]) + outs.extend([ + self.pafpn_convs[i - 1](inter_outs[i]) + for i in range(1, used_backbone_levels) + ]) + + # part 3: add extra levels + if self.num_outs > len(outs): + # use max pool to get more levels on top of outputs + # (e.g., Faster R-CNN, Mask R-CNN) + if not self.add_extra_convs: + for i in range(self.num_outs - used_backbone_levels): + outs.append(F.max_pool2d(outs[-1], 1, stride=2)) + # add conv layers on top of original feature maps (RetinaNet) + else: + if self.add_extra_convs == 'on_input': + orig = inputs[self.backbone_end_level - 1] + outs.append(self.fpn_convs[used_backbone_levels](orig)) + elif self.add_extra_convs == 'on_lateral': + outs.append(self.fpn_convs[used_backbone_levels]( + laterals[-1])) + elif self.add_extra_convs == 'on_output': + outs.append(self.fpn_convs[used_backbone_levels](outs[-1])) + else: + raise NotImplementedError + for i in range(used_backbone_levels + 1, self.num_outs): + if self.relu_before_extra_convs: + outs.append(self.fpn_convs[i](F.relu(outs[-1]))) + else: + outs.append(self.fpn_convs[i](outs[-1])) + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/rfp.py b/mmdetection/mmdet/models/necks/rfp.py new file mode 100644 index 00000000..7ec9b375 --- /dev/null +++ b/mmdetection/mmdet/models/necks/rfp.py @@ -0,0 +1,134 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.model import BaseModule, ModuleList, constant_init, xavier_init + +from mmdet.registry import MODELS +from .fpn import FPN + + +class ASPP(BaseModule): + """ASPP (Atrous Spatial Pyramid Pooling) + + This is an implementation of the ASPP module used in DetectoRS + (https://arxiv.org/pdf/2006.02334.pdf) + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of channels produced by this module + dilations (tuple[int]): Dilations of the four branches. + Default: (1, 3, 6, 1) + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + in_channels, + out_channels, + dilations=(1, 3, 6, 1), + init_cfg=dict(type='Kaiming', layer='Conv2d')): + super().__init__(init_cfg) + assert dilations[-1] == 1 + self.aspp = nn.ModuleList() + for dilation in dilations: + kernel_size = 3 if dilation > 1 else 1 + padding = dilation if dilation > 1 else 0 + conv = nn.Conv2d( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=1, + dilation=dilation, + padding=padding, + bias=True) + self.aspp.append(conv) + self.gap = nn.AdaptiveAvgPool2d(1) + + def forward(self, x): + avg_x = self.gap(x) + out = [] + for aspp_idx in range(len(self.aspp)): + inp = avg_x if (aspp_idx == len(self.aspp) - 1) else x + out.append(F.relu_(self.aspp[aspp_idx](inp))) + out[-1] = out[-1].expand_as(out[-2]) + out = torch.cat(out, dim=1) + return out + + +@MODELS.register_module() +class RFP(FPN): + """RFP (Recursive Feature Pyramid) + + This is an implementation of RFP in `DetectoRS + `_. Different from standard FPN, the + input of RFP should be multi level features along with origin input image + of backbone. + + Args: + rfp_steps (int): Number of unrolled steps of RFP. + rfp_backbone (dict): Configuration of the backbone for RFP. + aspp_out_channels (int): Number of output channels of ASPP module. + aspp_dilations (tuple[int]): Dilation rates of four branches. + Default: (1, 3, 6, 1) + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + rfp_steps, + rfp_backbone, + aspp_out_channels, + aspp_dilations=(1, 3, 6, 1), + init_cfg=None, + **kwargs): + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + super().__init__(init_cfg=init_cfg, **kwargs) + self.rfp_steps = rfp_steps + # Be careful! Pretrained weights cannot be loaded when use + # nn.ModuleList + self.rfp_modules = ModuleList() + for rfp_idx in range(1, rfp_steps): + rfp_module = MODELS.build(rfp_backbone) + self.rfp_modules.append(rfp_module) + self.rfp_aspp = ASPP(self.out_channels, aspp_out_channels, + aspp_dilations) + self.rfp_weight = nn.Conv2d( + self.out_channels, + 1, + kernel_size=1, + stride=1, + padding=0, + bias=True) + + def init_weights(self): + # Avoid using super().init_weights(), which may alter the default + # initialization of the modules in self.rfp_modules that have missing + # keys in the pretrained checkpoint. + for convs in [self.lateral_convs, self.fpn_convs]: + for m in convs.modules(): + if isinstance(m, nn.Conv2d): + xavier_init(m, distribution='uniform') + for rfp_idx in range(self.rfp_steps - 1): + self.rfp_modules[rfp_idx].init_weights() + constant_init(self.rfp_weight, 0) + + def forward(self, inputs): + inputs = list(inputs) + assert len(inputs) == len(self.in_channels) + 1 # +1 for input image + img = inputs.pop(0) + # FPN forward + x = super().forward(tuple(inputs)) + for rfp_idx in range(self.rfp_steps - 1): + rfp_feats = [x[0]] + list( + self.rfp_aspp(x[i]) for i in range(1, len(x))) + x_idx = self.rfp_modules[rfp_idx].rfp_forward(img, rfp_feats) + # FPN forward + x_idx = super().forward(x_idx) + x_new = [] + for ft_idx in range(len(x_idx)): + add_weight = torch.sigmoid(self.rfp_weight(x_idx[ft_idx])) + x_new.append(add_weight * x_idx[ft_idx] + + (1 - add_weight) * x[ft_idx]) + x = x_new + return x diff --git a/mmdetection/mmdet/models/necks/ssd_neck.py b/mmdetection/mmdet/models/necks/ssd_neck.py new file mode 100644 index 00000000..17ba3193 --- /dev/null +++ b/mmdetection/mmdet/models/necks/ssd_neck.py @@ -0,0 +1,129 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmengine.model import BaseModule + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class SSDNeck(BaseModule): + """Extra layers of SSD backbone to generate multi-scale feature maps. + + Args: + in_channels (Sequence[int]): Number of input channels per scale. + out_channels (Sequence[int]): Number of output channels per scale. + level_strides (Sequence[int]): Stride of 3x3 conv per level. + level_paddings (Sequence[int]): Padding size of 3x3 conv per level. + l2_norm_scale (float|None): L2 normalization layer init scale. + If None, not use L2 normalization on the first input feature. + last_kernel_size (int): Kernel size of the last conv layer. + Default: 3. + use_depthwise (bool): Whether to use DepthwiseSeparableConv. + Default: False. + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: None. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + in_channels, + out_channels, + level_strides, + level_paddings, + l2_norm_scale=20., + last_kernel_size=3, + use_depthwise=False, + conv_cfg=None, + norm_cfg=None, + act_cfg=dict(type='ReLU'), + init_cfg=[ + dict( + type='Xavier', distribution='uniform', + layer='Conv2d'), + dict(type='Constant', val=1, layer='BatchNorm2d'), + ]): + super(SSDNeck, self).__init__(init_cfg) + assert len(out_channels) > len(in_channels) + assert len(out_channels) - len(in_channels) == len(level_strides) + assert len(level_strides) == len(level_paddings) + assert in_channels == out_channels[:len(in_channels)] + + if l2_norm_scale: + self.l2_norm = L2Norm(in_channels[0], l2_norm_scale) + self.init_cfg += [ + dict( + type='Constant', + val=self.l2_norm.scale, + override=dict(name='l2_norm')) + ] + + self.extra_layers = nn.ModuleList() + extra_layer_channels = out_channels[len(in_channels):] + second_conv = DepthwiseSeparableConvModule if \ + use_depthwise else ConvModule + + for i, (out_channel, stride, padding) in enumerate( + zip(extra_layer_channels, level_strides, level_paddings)): + kernel_size = last_kernel_size \ + if i == len(extra_layer_channels) - 1 else 3 + per_lvl_convs = nn.Sequential( + ConvModule( + out_channels[len(in_channels) - 1 + i], + out_channel // 2, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + second_conv( + out_channel // 2, + out_channel, + kernel_size, + stride=stride, + padding=padding, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.extra_layers.append(per_lvl_convs) + + def forward(self, inputs): + """Forward function.""" + outs = [feat for feat in inputs] + if hasattr(self, 'l2_norm'): + outs[0] = self.l2_norm(outs[0]) + + feat = outs[-1] + for layer in self.extra_layers: + feat = layer(feat) + outs.append(feat) + return tuple(outs) + + +class L2Norm(nn.Module): + + def __init__(self, n_dims, scale=20., eps=1e-10): + """L2 normalization layer. + + Args: + n_dims (int): Number of dimensions to be normalized + scale (float, optional): Defaults to 20.. + eps (float, optional): Used to avoid division by zero. + Defaults to 1e-10. + """ + super(L2Norm, self).__init__() + self.n_dims = n_dims + self.weight = nn.Parameter(torch.Tensor(self.n_dims)) + self.eps = eps + self.scale = scale + + def forward(self, x): + """Forward function.""" + # normalization layer convert to FP32 in FP16 training + x_float = x.float() + norm = x_float.pow(2).sum(1, keepdim=True).sqrt() + self.eps + return (self.weight[None, :, None, None].float().expand_as(x_float) * + x_float / norm).type_as(x) diff --git a/mmdetection/mmdet/models/necks/ssh.py b/mmdetection/mmdet/models/necks/ssh.py new file mode 100644 index 00000000..75a65614 --- /dev/null +++ b/mmdetection/mmdet/models/necks/ssh.py @@ -0,0 +1,216 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig + + +class SSHContextModule(BaseModule): + """This is an implementation of `SSH context module` described in `SSH: + Single Stage Headless Face Detector. + + `_. + + Args: + in_channels (int): Number of input channels used at each scale. + out_channels (int): Number of output channels used at each scale. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict): Config dict for normalization + layer. Defaults to dict(type='BN'). + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + in_channels: int, + out_channels: int, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN'), + init_cfg: OptMultiConfig = None): + super().__init__(init_cfg=init_cfg) + assert out_channels % 4 == 0 + + self.in_channels = in_channels + self.out_channels = out_channels + + self.conv5x5_1 = ConvModule( + self.in_channels, + self.out_channels // 4, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + ) + + self.conv5x5_2 = ConvModule( + self.out_channels // 4, + self.out_channels // 4, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + + self.conv7x7_2 = ConvModule( + self.out_channels // 4, + self.out_channels // 4, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + ) + + self.conv7x7_3 = ConvModule( + self.out_channels // 4, + self.out_channels // 4, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None, + ) + + def forward(self, x: torch.Tensor) -> tuple: + conv5x5_1 = self.conv5x5_1(x) + conv5x5 = self.conv5x5_2(conv5x5_1) + conv7x7_2 = self.conv7x7_2(conv5x5_1) + conv7x7 = self.conv7x7_3(conv7x7_2) + + return (conv5x5, conv7x7) + + +class SSHDetModule(BaseModule): + """This is an implementation of `SSH detection module` described in `SSH: + Single Stage Headless Face Detector. + + `_. + + Args: + in_channels (int): Number of input channels used at each scale. + out_channels (int): Number of output channels used at each scale. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict): Config dict for normalization + layer. Defaults to dict(type='BN'). + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + in_channels: int, + out_channels: int, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN'), + init_cfg: OptMultiConfig = None): + super().__init__(init_cfg=init_cfg) + assert out_channels % 4 == 0 + + self.in_channels = in_channels + self.out_channels = out_channels + + self.conv3x3 = ConvModule( + self.in_channels, + self.out_channels // 2, + 3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + + self.context_module = SSHContextModule( + in_channels=self.in_channels, + out_channels=self.out_channels, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + conv3x3 = self.conv3x3(x) + conv5x5, conv7x7 = self.context_module(x) + out = torch.cat([conv3x3, conv5x5, conv7x7], dim=1) + out = F.relu(out) + + return out + + +@MODELS.register_module() +class SSH(BaseModule): + """`SSH Neck` used in `SSH: Single Stage Headless Face Detector. + + `_. + + Args: + num_scales (int): The number of scales / stages. + in_channels (list[int]): The number of input channels per scale. + out_channels (list[int]): The number of output channels per scale. + conv_cfg (:obj:`ConfigDict` or dict, optional): Config dict for + convolution layer. Defaults to None. + norm_cfg (:obj:`ConfigDict` or dict): Config dict for normalization + layer. Defaults to dict(type='BN'). + init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or + list[dict], optional): Initialization config dict. + + Example: + >>> import torch + >>> in_channels = [8, 16, 32, 64] + >>> out_channels = [16, 32, 64, 128] + >>> scales = [340, 170, 84, 43] + >>> inputs = [torch.rand(1, c, s, s) + ... for c, s in zip(in_channels, scales)] + >>> self = SSH(num_scales=4, in_channels=in_channels, + ... out_channels=out_channels) + >>> outputs = self.forward(inputs) + >>> for i in range(len(outputs)): + ... print(f'outputs[{i}].shape = {outputs[i].shape}') + outputs[0].shape = torch.Size([1, 16, 340, 340]) + outputs[1].shape = torch.Size([1, 32, 170, 170]) + outputs[2].shape = torch.Size([1, 64, 84, 84]) + outputs[3].shape = torch.Size([1, 128, 43, 43]) + """ + + def __init__(self, + num_scales: int, + in_channels: List[int], + out_channels: List[int], + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN'), + init_cfg: OptMultiConfig = dict( + type='Xavier', layer='Conv2d', distribution='uniform')): + super().__init__(init_cfg=init_cfg) + assert (num_scales == len(in_channels) == len(out_channels)) + self.num_scales = num_scales + self.in_channels = in_channels + self.out_channels = out_channels + + for idx in range(self.num_scales): + in_c, out_c = self.in_channels[idx], self.out_channels[idx] + self.add_module( + f'ssh_module{idx}', + SSHDetModule( + in_channels=in_c, + out_channels=out_c, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg)) + + def forward(self, inputs: Tuple[torch.Tensor]) -> tuple: + assert len(inputs) == self.num_scales + + outs = [] + for idx, x in enumerate(inputs): + ssh_module = getattr(self, f'ssh_module{idx}') + out = ssh_module(x) + outs.append(out) + + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/yolo_neck.py b/mmdetection/mmdet/models/necks/yolo_neck.py new file mode 100644 index 00000000..48a6b1a4 --- /dev/null +++ b/mmdetection/mmdet/models/necks/yolo_neck.py @@ -0,0 +1,145 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Copyright (c) 2019 Western Digital Corporation or its affiliates. +from typing import List, Tuple + +import torch +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig + + +class DetectionBlock(BaseModule): + """Detection block in YOLO neck. + + Let out_channels = n, the DetectionBlock contains: + Six ConvLayers, 1 Conv2D Layer and 1 YoloLayer. + The first 6 ConvLayers are formed the following way: + 1x1xn, 3x3x2n, 1x1xn, 3x3x2n, 1x1xn, 3x3x2n. + The Conv2D layer is 1x1x255. + Some block will have branch after the fifth ConvLayer. + The input channel is arbitrary (in_channels) + + Args: + in_channels (int): The number of input channels. + out_channels (int): The number of output channels. + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: dict(type='BN', requires_grad=True) + act_cfg (dict): Config dict for activation layer. + Default: dict(type='LeakyReLU', negative_slope=0.1). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + in_channels: int, + out_channels: int, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN', requires_grad=True), + act_cfg: ConfigType = dict( + type='LeakyReLU', negative_slope=0.1), + init_cfg: OptMultiConfig = None) -> None: + super(DetectionBlock, self).__init__(init_cfg) + double_out_channels = out_channels * 2 + + # shortcut + cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + self.conv1 = ConvModule(in_channels, out_channels, 1, **cfg) + self.conv2 = ConvModule( + out_channels, double_out_channels, 3, padding=1, **cfg) + self.conv3 = ConvModule(double_out_channels, out_channels, 1, **cfg) + self.conv4 = ConvModule( + out_channels, double_out_channels, 3, padding=1, **cfg) + self.conv5 = ConvModule(double_out_channels, out_channels, 1, **cfg) + + def forward(self, x: Tensor) -> Tensor: + tmp = self.conv1(x) + tmp = self.conv2(tmp) + tmp = self.conv3(tmp) + tmp = self.conv4(tmp) + out = self.conv5(tmp) + return out + + +@MODELS.register_module() +class YOLOV3Neck(BaseModule): + """The neck of YOLOV3. + + It can be treated as a simplified version of FPN. It + will take the result from Darknet backbone and do some upsampling and + concatenation. It will finally output the detection result. + + Note: + The input feats should be from top to bottom. + i.e., from high-lvl to low-lvl + But YOLOV3Neck will process them in reversed order. + i.e., from bottom (high-lvl) to top (low-lvl) + + Args: + num_scales (int): The number of scales / stages. + in_channels (List[int]): The number of input channels per scale. + out_channels (List[int]): The number of output channels per scale. + conv_cfg (dict, optional): Config dict for convolution layer. + Default: None. + norm_cfg (dict, optional): Dictionary to construct and config norm + layer. Default: dict(type='BN', requires_grad=True) + act_cfg (dict, optional): Config dict for activation layer. + Default: dict(type='LeakyReLU', negative_slope=0.1). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + num_scales: int, + in_channels: List[int], + out_channels: List[int], + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN', requires_grad=True), + act_cfg: ConfigType = dict( + type='LeakyReLU', negative_slope=0.1), + init_cfg: OptMultiConfig = None) -> None: + super(YOLOV3Neck, self).__init__(init_cfg) + assert (num_scales == len(in_channels) == len(out_channels)) + self.num_scales = num_scales + self.in_channels = in_channels + self.out_channels = out_channels + + # shortcut + cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + + # To support arbitrary scales, the code looks awful, but it works. + # Better solution is welcomed. + self.detect1 = DetectionBlock(in_channels[0], out_channels[0], **cfg) + for i in range(1, self.num_scales): + in_c, out_c = self.in_channels[i], self.out_channels[i] + inter_c = out_channels[i - 1] + self.add_module(f'conv{i}', ConvModule(inter_c, out_c, 1, **cfg)) + # in_c + out_c : High-lvl feats will be cat with low-lvl feats + self.add_module(f'detect{i+1}', + DetectionBlock(in_c + out_c, out_c, **cfg)) + + def forward(self, feats=Tuple[Tensor]) -> Tuple[Tensor]: + assert len(feats) == self.num_scales + + # processed from bottom (high-lvl) to top (low-lvl) + outs = [] + out = self.detect1(feats[-1]) + outs.append(out) + + for i, x in enumerate(reversed(feats[:-1])): + conv = getattr(self, f'conv{i+1}') + tmp = conv(out) + + # Cat with low-lvl feats + tmp = F.interpolate(tmp, scale_factor=2) + tmp = torch.cat((tmp, x), 1) + + detect = getattr(self, f'detect{i+2}') + out = detect(tmp) + outs.append(out) + + return tuple(outs) diff --git a/mmdetection/mmdet/models/necks/yolox_pafpn.py b/mmdetection/mmdet/models/necks/yolox_pafpn.py new file mode 100644 index 00000000..8ec3d12b --- /dev/null +++ b/mmdetection/mmdet/models/necks/yolox_pafpn.py @@ -0,0 +1,156 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmengine.model import BaseModule + +from mmdet.registry import MODELS +from ..layers import CSPLayer + + +@MODELS.register_module() +class YOLOXPAFPN(BaseModule): + """Path Aggregation Network used in YOLOX. + + Args: + in_channels (List[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale) + num_csp_blocks (int): Number of bottlenecks in CSPLayer. Default: 3 + use_depthwise (bool): Whether to depthwise separable convolution in + blocks. Default: False + upsample_cfg (dict): Config dict for interpolate layer. + Default: `dict(scale_factor=2, mode='nearest')` + conv_cfg (dict, optional): Config dict for convolution layer. + Default: None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN') + act_cfg (dict): Config dict for activation layer. + Default: dict(type='Swish') + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + num_csp_blocks=3, + use_depthwise=False, + upsample_cfg=dict(scale_factor=2, mode='nearest'), + conv_cfg=None, + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish'), + init_cfg=dict( + type='Kaiming', + layer='Conv2d', + a=math.sqrt(5), + distribution='uniform', + mode='fan_in', + nonlinearity='leaky_relu')): + super(YOLOXPAFPN, self).__init__(init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + + conv = DepthwiseSeparableConvModule if use_depthwise else ConvModule + + # build top-down blocks + self.upsample = nn.Upsample(**upsample_cfg) + self.reduce_layers = nn.ModuleList() + self.top_down_blocks = nn.ModuleList() + for idx in range(len(in_channels) - 1, 0, -1): + self.reduce_layers.append( + ConvModule( + in_channels[idx], + in_channels[idx - 1], + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.top_down_blocks.append( + CSPLayer( + in_channels[idx - 1] * 2, + in_channels[idx - 1], + num_blocks=num_csp_blocks, + add_identity=False, + use_depthwise=use_depthwise, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + # build bottom-up blocks + self.downsamples = nn.ModuleList() + self.bottom_up_blocks = nn.ModuleList() + for idx in range(len(in_channels) - 1): + self.downsamples.append( + conv( + in_channels[idx], + in_channels[idx], + 3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.bottom_up_blocks.append( + CSPLayer( + in_channels[idx] * 2, + in_channels[idx + 1], + num_blocks=num_csp_blocks, + add_identity=False, + use_depthwise=use_depthwise, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + self.out_convs = nn.ModuleList() + for i in range(len(in_channels)): + self.out_convs.append( + ConvModule( + in_channels[i], + out_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + def forward(self, inputs): + """ + Args: + inputs (tuple[Tensor]): input features. + + Returns: + tuple[Tensor]: YOLOXPAFPN features. + """ + assert len(inputs) == len(self.in_channels) + + # top-down path + inner_outs = [inputs[-1]] + for idx in range(len(self.in_channels) - 1, 0, -1): + feat_heigh = inner_outs[0] + feat_low = inputs[idx - 1] + feat_heigh = self.reduce_layers[len(self.in_channels) - 1 - idx]( + feat_heigh) + inner_outs[0] = feat_heigh + + upsample_feat = self.upsample(feat_heigh) + + inner_out = self.top_down_blocks[len(self.in_channels) - 1 - idx]( + torch.cat([upsample_feat, feat_low], 1)) + inner_outs.insert(0, inner_out) + + # bottom-up path + outs = [inner_outs[0]] + for idx in range(len(self.in_channels) - 1): + feat_low = outs[-1] + feat_height = inner_outs[idx + 1] + downsample_feat = self.downsamples[idx](feat_low) + out = self.bottom_up_blocks[idx]( + torch.cat([downsample_feat, feat_height], 1)) + outs.append(out) + + # out convs + for idx, conv in enumerate(self.out_convs): + outs[idx] = conv(outs[idx]) + + return tuple(outs) diff --git a/mmdetection/mmdet/models/reid/__init__.py b/mmdetection/mmdet/models/reid/__init__.py new file mode 100644 index 00000000..aca617f7 --- /dev/null +++ b/mmdetection/mmdet/models/reid/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_reid import BaseReID +from .fc_module import FcModule +from .gap import GlobalAveragePooling +from .linear_reid_head import LinearReIDHead + +__all__ = ['BaseReID', 'GlobalAveragePooling', 'LinearReIDHead', 'FcModule'] diff --git a/mmdetection/mmdet/models/reid/base_reid.py b/mmdetection/mmdet/models/reid/base_reid.py new file mode 100644 index 00000000..4c459643 --- /dev/null +++ b/mmdetection/mmdet/models/reid/base_reid.py @@ -0,0 +1,65 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional + +import torch + +try: + import mmpretrain + from mmpretrain.models.classifiers import ImageClassifier +except ImportError: + mmpretrain = None + ImageClassifier = object + +from mmdet.registry import MODELS +from mmdet.structures import ReIDDataSample + + +@MODELS.register_module() +class BaseReID(ImageClassifier): + """Base model for re-identification.""" + + def __init__(self, *args, **kwargs): + if mmpretrain is None: + raise RuntimeError('Please run "pip install openmim" and ' + 'run "mim install mmpretrain" to ' + 'install mmpretrain first.') + super().__init__(*args, **kwargs) + + def forward(self, + inputs: torch.Tensor, + data_samples: Optional[List[ReIDDataSample]] = None, + mode: str = 'tensor'): + """The unified entry for a forward process in both training and test. + + The method should accept three modes: "tensor", "predict" and "loss": + + - "tensor": Forward the whole network and return tensor or tuple of + tensor without any post-processing, same as a common nn.Module. + - "predict": Forward and return the predictions, which are fully + processed to a list of :obj:`ReIDDataSample`. + - "loss": Forward and return a dict of losses according to the given + inputs and data samples. + + Note that this method doesn't handle neither back propagation nor + optimizer updating, which are done in the :meth:`train_step`. + + Args: + inputs (torch.Tensor): The input tensor with shape + (N, C, H, W) or (N, T, C, H, W). + data_samples (List[ReIDDataSample], optional): The annotation + data of every sample. It's required if ``mode="loss"``. + Defaults to None. + mode (str): Return what kind of value. Defaults to 'tensor'. + + Returns: + The return type depends on ``mode``. + + - If ``mode="tensor"``, return a tensor or a tuple of tensor. + - If ``mode="predict"``, return a list of + :obj:`ReIDDataSample`. + - If ``mode="loss"``, return a dict of tensor. + """ + if len(inputs.size()) == 5: + assert inputs.size(0) == 1 + inputs = inputs[0] + return super().forward(inputs, data_samples, mode) diff --git a/mmdetection/mmdet/models/reid/fc_module.py b/mmdetection/mmdet/models/reid/fc_module.py new file mode 100644 index 00000000..76e7efd6 --- /dev/null +++ b/mmdetection/mmdet/models/reid/fc_module.py @@ -0,0 +1,71 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import build_activation_layer, build_norm_layer +from mmengine.model import BaseModule + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class FcModule(BaseModule): + """Fully-connected layer module. + + Args: + in_channels (int): Input channels. + out_channels (int): Ourput channels. + norm_cfg (dict, optional): Configuration of normlization method + after fc. Defaults to None. + act_cfg (dict, optional): Configuration of activation method after fc. + Defaults to dict(type='ReLU'). + inplace (bool, optional): Whether inplace the activatation module. + Defaults to True. + init_cfg (dict, optional): Initialization config dict. + Defaults to dict(type='Kaiming', layer='Linear'). + """ + + def __init__(self, + in_channels: int, + out_channels: int, + norm_cfg: dict = None, + act_cfg: dict = dict(type='ReLU'), + inplace: bool = True, + init_cfg=dict(type='Kaiming', layer='Linear')): + super(FcModule, self).__init__(init_cfg) + assert norm_cfg is None or isinstance(norm_cfg, dict) + assert act_cfg is None or isinstance(act_cfg, dict) + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.inplace = inplace + + self.with_norm = norm_cfg is not None + self.with_activation = act_cfg is not None + + self.fc = nn.Linear(in_channels, out_channels) + # build normalization layers + if self.with_norm: + self.norm_name, norm = build_norm_layer(norm_cfg, out_channels) + self.add_module(self.norm_name, norm) + + # build activation layer + if self.with_activation: + act_cfg_ = act_cfg.copy() + # nn.Tanh has no 'inplace' argument + if act_cfg_['type'] not in [ + 'Tanh', 'PReLU', 'Sigmoid', 'HSigmoid', 'Swish' + ]: + act_cfg_.setdefault('inplace', inplace) + self.activate = build_activation_layer(act_cfg_) + + @property + def norm(self): + """Normalization.""" + return getattr(self, self.norm_name) + + def forward(self, x, activate=True, norm=True): + """Model forward.""" + x = self.fc(x) + if norm and self.with_norm: + x = self.norm(x) + if activate and self.with_activation: + x = self.activate(x) + return x diff --git a/mmdetection/mmdet/models/reid/gap.py b/mmdetection/mmdet/models/reid/gap.py new file mode 100644 index 00000000..aadc25e7 --- /dev/null +++ b/mmdetection/mmdet/models/reid/gap.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmengine.model import BaseModule + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class GlobalAveragePooling(BaseModule): + """Global Average Pooling neck. + + Note that we use `view` to remove extra channel after pooling. We do not + use `squeeze` as it will also remove the batch dimension when the tensor + has a batch dimension of size 1, which can lead to unexpected errors. + """ + + def __init__(self, kernel_size=None, stride=None): + super(GlobalAveragePooling, self).__init__() + if kernel_size is None and stride is None: + self.gap = nn.AdaptiveAvgPool2d((1, 1)) + else: + self.gap = nn.AvgPool2d(kernel_size, stride) + + def forward(self, inputs): + if isinstance(inputs, tuple): + outs = tuple([self.gap(x) for x in inputs]) + outs = tuple([ + out.view(x.size(0), + torch.tensor(out.size()[1:]).prod()) + for out, x in zip(outs, inputs) + ]) + elif isinstance(inputs, torch.Tensor): + outs = self.gap(inputs) + outs = outs.view( + inputs.size(0), + torch.tensor(outs.size()[1:]).prod()) + else: + raise TypeError('neck inputs should be tuple or torch.tensor') + return outs diff --git a/mmdetection/mmdet/models/reid/linear_reid_head.py b/mmdetection/mmdet/models/reid/linear_reid_head.py new file mode 100644 index 00000000..f35aaf6c --- /dev/null +++ b/mmdetection/mmdet/models/reid/linear_reid_head.py @@ -0,0 +1,202 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import List, Optional, Tuple, Union + +import torch +import torch.nn as nn + +try: + import mmpretrain + from mmpretrain.evaluation.metrics import Accuracy +except ImportError: + mmpretrain = None + +from mmengine.model import BaseModule + +from mmdet.registry import MODELS +from mmdet.structures import ReIDDataSample +from .fc_module import FcModule + + +@MODELS.register_module() +class LinearReIDHead(BaseModule): + """Linear head for re-identification. + + Args: + num_fcs (int): Number of fcs. + in_channels (int): Number of channels in the input. + fc_channels (int): Number of channels in the fcs. + out_channels (int): Number of channels in the output. + norm_cfg (dict, optional): Configuration of normlization method + after fc. Defaults to None. + act_cfg (dict, optional): Configuration of activation method after fc. + Defaults to None. + num_classes (int, optional): Number of the identities. Default to None. + loss_cls (dict, optional): Cross entropy loss to train the ReID module. + Defaults to None. + loss_triplet (dict, optional): Triplet loss to train the ReID module. + Defaults to None. + topk (int | Tuple[int]): Top-k accuracy. Defaults to ``(1, )``. + init_cfg (dict or list[dict], optional): Initialization config dict. + Defaults to dict(type='Normal',layer='Linear', mean=0, std=0.01, + bias=0). + """ + + def __init__(self, + num_fcs: int, + in_channels: int, + fc_channels: int, + out_channels: int, + norm_cfg: Optional[dict] = None, + act_cfg: Optional[dict] = None, + num_classes: Optional[int] = None, + loss_cls: Optional[dict] = None, + loss_triplet: Optional[dict] = None, + topk: Union[int, Tuple[int]] = (1, ), + init_cfg: Union[dict, List[dict]] = dict( + type='Normal', layer='Linear', mean=0, std=0.01, bias=0)): + if mmpretrain is None: + raise RuntimeError('Please run "pip install openmim" and ' + 'run "mim install mmpretrain" to ' + 'install mmpretrain first.') + super(LinearReIDHead, self).__init__(init_cfg=init_cfg) + + assert isinstance(topk, (int, tuple)) + if isinstance(topk, int): + topk = (topk, ) + for _topk in topk: + assert _topk > 0, 'Top-k should be larger than 0' + self.topk = topk + + if loss_cls is None: + if isinstance(num_classes, int): + warnings.warn('Since cross entropy is not set, ' + 'the num_classes will be ignored.') + if loss_triplet is None: + raise ValueError('Please choose at least one loss in ' + 'triplet loss and cross entropy loss.') + elif not isinstance(num_classes, int): + raise TypeError('The num_classes must be a current number, ' + 'if there is cross entropy loss.') + self.loss_cls = MODELS.build(loss_cls) if loss_cls else None + self.loss_triplet = MODELS.build(loss_triplet) \ + if loss_triplet else None + + self.num_fcs = num_fcs + self.in_channels = in_channels + self.fc_channels = fc_channels + self.out_channels = out_channels + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.num_classes = num_classes + + self._init_layers() + + def _init_layers(self): + """Initialize fc layers.""" + self.fcs = nn.ModuleList() + for i in range(self.num_fcs): + in_channels = self.in_channels if i == 0 else self.fc_channels + self.fcs.append( + FcModule(in_channels, self.fc_channels, self.norm_cfg, + self.act_cfg)) + in_channels = self.in_channels if self.num_fcs == 0 else \ + self.fc_channels + self.fc_out = nn.Linear(in_channels, self.out_channels) + if self.loss_cls: + self.bn = nn.BatchNorm1d(self.out_channels) + self.classifier = nn.Linear(self.out_channels, self.num_classes) + + def forward(self, feats: Tuple[torch.Tensor]) -> torch.Tensor: + """The forward process.""" + # Multiple stage inputs are acceptable + # but only the last stage will be used. + feats = feats[-1] + + for m in self.fcs: + feats = m(feats) + feats = self.fc_out(feats) + return feats + + def loss(self, feats: Tuple[torch.Tensor], + data_samples: List[ReIDDataSample]) -> dict: + """Calculate losses. + + Args: + feats (tuple[Tensor]): The features extracted from the backbone. + data_samples (List[ReIDDataSample]): The annotation data of + every samples. + + Returns: + dict: a dictionary of loss components + """ + # The part can be traced by torch.fx + feats = self(feats) + + # The part can not be traced by torch.fx + losses = self.loss_by_feat(feats, data_samples) + return losses + + def loss_by_feat(self, feats: torch.Tensor, + data_samples: List[ReIDDataSample]) -> dict: + """Unpack data samples and compute loss.""" + losses = dict() + gt_label = torch.cat([i.gt_label.label for i in data_samples]) + gt_label = gt_label.to(feats.device) + + if self.loss_triplet: + losses['triplet_loss'] = self.loss_triplet(feats, gt_label) + + if self.loss_cls: + feats_bn = self.bn(feats) + cls_score = self.classifier(feats_bn) + losses['ce_loss'] = self.loss_cls(cls_score, gt_label) + acc = Accuracy.calculate(cls_score, gt_label, topk=self.topk) + losses.update( + {f'accuracy_top-{k}': a + for k, a in zip(self.topk, acc)}) + + return losses + + def predict( + self, + feats: Tuple[torch.Tensor], + data_samples: List[ReIDDataSample] = None) -> List[ReIDDataSample]: + """Inference without augmentation. + + Args: + feats (Tuple[Tensor]): The features extracted from the backbone. + Multiple stage inputs are acceptable but only the last stage + will be used. + data_samples (List[ReIDDataSample], optional): The annotation + data of every samples. If not None, set ``pred_label`` of + the input data samples. Defaults to None. + + Returns: + List[ReIDDataSample]: A list of data samples which contains the + predicted results. + """ + # The part can be traced by torch.fx + feats = self(feats) + + # The part can not be traced by torch.fx + data_samples = self.predict_by_feat(feats, data_samples) + + return data_samples + + def predict_by_feat( + self, + feats: torch.Tensor, + data_samples: List[ReIDDataSample] = None) -> List[ReIDDataSample]: + """Add prediction features to data samples.""" + if data_samples is not None: + for data_sample, feat in zip(data_samples, feats): + data_sample.pred_feature = feat + else: + data_samples = [] + for feat in feats: + data_sample = ReIDDataSample() + data_sample.pred_feature = feat + data_samples.append(data_sample) + + return data_samples diff --git a/mmdetection/mmdet/models/roi_heads/__init__.py b/mmdetection/mmdet/models/roi_heads/__init__.py new file mode 100644 index 00000000..bba5664c --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/__init__.py @@ -0,0 +1,38 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_roi_head import BaseRoIHead +from .bbox_heads import (BBoxHead, ConvFCBBoxHead, DIIHead, + DoubleConvFCBBoxHead, SABLHead, SCNetBBoxHead, + Shared2FCBBoxHead, Shared4Conv1FCBBoxHead) +from .cascade_roi_head import CascadeRoIHead +from .double_roi_head import DoubleHeadRoIHead +from .dynamic_roi_head import DynamicRoIHead +from .grid_roi_head import GridRoIHead +from .htc_roi_head import HybridTaskCascadeRoIHead +from .mask_heads import (CoarseMaskHead, FCNMaskHead, FeatureRelayHead, + FusedSemanticHead, GlobalContextHead, GridHead, + HTCMaskHead, MaskIoUHead, MaskPointHead, + SCNetMaskHead, SCNetSemanticHead) +from .mask_scoring_roi_head import MaskScoringRoIHead +from .multi_instance_roi_head import MultiInstanceRoIHead +from .pisa_roi_head import PISARoIHead +from .point_rend_roi_head import PointRendRoIHead +from .roi_extractors import (BaseRoIExtractor, GenericRoIExtractor, + SingleRoIExtractor) +from .scnet_roi_head import SCNetRoIHead +from .shared_heads import ResLayer +from .sparse_roi_head import SparseRoIHead +from .standard_roi_head import StandardRoIHead +from .trident_roi_head import TridentRoIHead + +__all__ = [ + 'BaseRoIHead', 'CascadeRoIHead', 'DoubleHeadRoIHead', 'MaskScoringRoIHead', + 'HybridTaskCascadeRoIHead', 'GridRoIHead', 'ResLayer', 'BBoxHead', + 'ConvFCBBoxHead', 'DIIHead', 'SABLHead', 'Shared2FCBBoxHead', + 'StandardRoIHead', 'Shared4Conv1FCBBoxHead', 'DoubleConvFCBBoxHead', + 'FCNMaskHead', 'HTCMaskHead', 'FusedSemanticHead', 'GridHead', + 'MaskIoUHead', 'BaseRoIExtractor', 'GenericRoIExtractor', + 'SingleRoIExtractor', 'PISARoIHead', 'PointRendRoIHead', 'MaskPointHead', + 'CoarseMaskHead', 'DynamicRoIHead', 'SparseRoIHead', 'TridentRoIHead', + 'SCNetRoIHead', 'SCNetMaskHead', 'SCNetSemanticHead', 'SCNetBBoxHead', + 'FeatureRelayHead', 'GlobalContextHead', 'MultiInstanceRoIHead' +] diff --git a/mmdetection/mmdet/models/roi_heads/base_roi_head.py b/mmdetection/mmdet/models/roi_heads/base_roi_head.py new file mode 100644 index 00000000..405f80a7 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/base_roi_head.py @@ -0,0 +1,129 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import Tuple + +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import InstanceList, OptConfigType, OptMultiConfig + + +class BaseRoIHead(BaseModule, metaclass=ABCMeta): + """Base class for RoIHeads.""" + + def __init__(self, + bbox_roi_extractor: OptMultiConfig = None, + bbox_head: OptMultiConfig = None, + mask_roi_extractor: OptMultiConfig = None, + mask_head: OptMultiConfig = None, + shared_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + self.train_cfg = train_cfg + self.test_cfg = test_cfg + if shared_head is not None: + self.shared_head = MODELS.build(shared_head) + + if bbox_head is not None: + self.init_bbox_head(bbox_roi_extractor, bbox_head) + + if mask_head is not None: + self.init_mask_head(mask_roi_extractor, mask_head) + + self.init_assigner_sampler() + + @property + def with_bbox(self) -> bool: + """bool: whether the RoI head contains a `bbox_head`""" + return hasattr(self, 'bbox_head') and self.bbox_head is not None + + @property + def with_mask(self) -> bool: + """bool: whether the RoI head contains a `mask_head`""" + return hasattr(self, 'mask_head') and self.mask_head is not None + + @property + def with_shared_head(self) -> bool: + """bool: whether the RoI head contains a `shared_head`""" + return hasattr(self, 'shared_head') and self.shared_head is not None + + @abstractmethod + def init_bbox_head(self, *args, **kwargs): + """Initialize ``bbox_head``""" + pass + + @abstractmethod + def init_mask_head(self, *args, **kwargs): + """Initialize ``mask_head``""" + pass + + @abstractmethod + def init_assigner_sampler(self, *args, **kwargs): + """Initialize assigner and sampler.""" + pass + + @abstractmethod + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList): + """Perform forward propagation and loss calculation of the roi head on + the features of the upstream network.""" + + def predict(self, + x: Tuple[Tensor], + rpn_results_list: InstanceList, + batch_data_samples: SampleList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the roi head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Features from upstream network. Each + has shape (N, C, H, W). + rpn_results_list (list[:obj:`InstanceData`]): list of region + proposals. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results to + the original image. Defaults to True. + + Returns: + list[obj:`InstanceData`]: Detection results of each image. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + assert self.with_bbox, 'Bbox head must be implemented.' + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + # TODO: nms_op in mmcv need be enhanced, the bbox result may get + # difference when not rescale in bbox_head + + # If it has the mask branch, the bbox branch does not need + # to be scaled to the original image scale, because the mask + # branch will scale both bbox and mask at the same time. + bbox_rescale = rescale if not self.with_mask else False + results_list = self.predict_bbox( + x, + batch_img_metas, + rpn_results_list, + rcnn_test_cfg=self.test_cfg, + rescale=bbox_rescale) + + if self.with_mask: + results_list = self.predict_mask( + x, batch_img_metas, results_list, rescale=rescale) + + return results_list diff --git a/mmdetection/mmdet/models/roi_heads/bbox_heads/__init__.py b/mmdetection/mmdet/models/roi_heads/bbox_heads/__init__.py new file mode 100644 index 00000000..d9e742ab --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/bbox_heads/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .bbox_head import BBoxHead +from .convfc_bbox_head import (ConvFCBBoxHead, Shared2FCBBoxHead, + Shared4Conv1FCBBoxHead) +from .dii_head import DIIHead +from .double_bbox_head import DoubleConvFCBBoxHead +from .multi_instance_bbox_head import MultiInstanceBBoxHead +from .sabl_head import SABLHead +from .scnet_bbox_head import SCNetBBoxHead + +__all__ = [ + 'BBoxHead', 'ConvFCBBoxHead', 'Shared2FCBBoxHead', + 'Shared4Conv1FCBBoxHead', 'DoubleConvFCBBoxHead', 'SABLHead', 'DIIHead', + 'SCNetBBoxHead', 'MultiInstanceBBoxHead' +] diff --git a/mmdetection/mmdet/models/roi_heads/bbox_heads/bbox_head.py b/mmdetection/mmdet/models/roi_heads/bbox_heads/bbox_head.py new file mode 100644 index 00000000..3b2e8aae --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/bbox_heads/bbox_head.py @@ -0,0 +1,708 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.config import ConfigDict +from mmengine.model import BaseModule +from mmengine.structures import InstanceData +from torch import Tensor +from torch.nn.modules.utils import _pair + +from mmdet.models.layers import multiclass_nms +from mmdet.models.losses import accuracy +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.models.utils import empty_instances, multi_apply +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures.bbox import get_box_tensor, scale_boxes +from mmdet.utils import ConfigType, InstanceList, OptMultiConfig + + +@MODELS.register_module() +class BBoxHead(BaseModule): + """Simplest RoI head, with only two fc layers for classification and + regression respectively.""" + + def __init__(self, + with_avg_pool: bool = False, + with_cls: bool = True, + with_reg: bool = True, + roi_feat_size: int = 7, + in_channels: int = 256, + num_classes: int = 80, + bbox_coder: ConfigType = dict( + type='DeltaXYWHBBoxCoder', + clip_border=True, + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + predict_box_type: str = 'hbox', + reg_class_agnostic: bool = False, + reg_decoded_bbox: bool = False, + reg_predictor_cfg: ConfigType = dict(type='Linear'), + cls_predictor_cfg: ConfigType = dict(type='Linear'), + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox: ConfigType = dict( + type='SmoothL1Loss', beta=1.0, loss_weight=1.0), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + assert with_cls or with_reg + self.with_avg_pool = with_avg_pool + self.with_cls = with_cls + self.with_reg = with_reg + self.roi_feat_size = _pair(roi_feat_size) + self.roi_feat_area = self.roi_feat_size[0] * self.roi_feat_size[1] + self.in_channels = in_channels + self.num_classes = num_classes + self.predict_box_type = predict_box_type + self.reg_class_agnostic = reg_class_agnostic + self.reg_decoded_bbox = reg_decoded_bbox + self.reg_predictor_cfg = reg_predictor_cfg + self.cls_predictor_cfg = cls_predictor_cfg + + self.bbox_coder = TASK_UTILS.build(bbox_coder) + self.loss_cls = MODELS.build(loss_cls) + self.loss_bbox = MODELS.build(loss_bbox) + + in_channels = self.in_channels + if self.with_avg_pool: + self.avg_pool = nn.AvgPool2d(self.roi_feat_size) + else: + in_channels *= self.roi_feat_area + if self.with_cls: + # need to add background class + if self.custom_cls_channels: + cls_channels = self.loss_cls.get_cls_channels(self.num_classes) + else: + cls_channels = num_classes + 1 + cls_predictor_cfg_ = self.cls_predictor_cfg.copy() + cls_predictor_cfg_.update( + in_features=in_channels, out_features=cls_channels) + self.fc_cls = MODELS.build(cls_predictor_cfg_) + if self.with_reg: + box_dim = self.bbox_coder.encode_size + out_dim_reg = box_dim if reg_class_agnostic else \ + box_dim * num_classes + reg_predictor_cfg_ = self.reg_predictor_cfg.copy() + if isinstance(reg_predictor_cfg_, (dict, ConfigDict)): + reg_predictor_cfg_.update( + in_features=in_channels, out_features=out_dim_reg) + self.fc_reg = MODELS.build(reg_predictor_cfg_) + self.debug_imgs = None + if init_cfg is None: + self.init_cfg = [] + if self.with_cls: + self.init_cfg += [ + dict( + type='Normal', std=0.01, override=dict(name='fc_cls')) + ] + if self.with_reg: + self.init_cfg += [ + dict( + type='Normal', std=0.001, override=dict(name='fc_reg')) + ] + + # TODO: Create a SeasawBBoxHead to simplified logic in BBoxHead + @property + def custom_cls_channels(self) -> bool: + """get custom_cls_channels from loss_cls.""" + return getattr(self.loss_cls, 'custom_cls_channels', False) + + # TODO: Create a SeasawBBoxHead to simplified logic in BBoxHead + @property + def custom_activation(self) -> bool: + """get custom_activation from loss_cls.""" + return getattr(self.loss_cls, 'custom_activation', False) + + # TODO: Create a SeasawBBoxHead to simplified logic in BBoxHead + @property + def custom_accuracy(self) -> bool: + """get custom_accuracy from loss_cls.""" + return getattr(self.loss_cls, 'custom_accuracy', False) + + def forward(self, x: Tuple[Tensor]) -> tuple: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of classification scores and bbox prediction. + + - cls_score (Tensor): Classification scores for all + scale levels, each is a 4D-tensor, the channels number + is num_base_priors * num_classes. + - bbox_pred (Tensor): Box energies / deltas for all + scale levels, each is a 4D-tensor, the channels number + is num_base_priors * 4. + """ + if self.with_avg_pool: + if x.numel() > 0: + x = self.avg_pool(x) + x = x.view(x.size(0), -1) + else: + # avg_pool does not support empty tensor, + # so use torch.mean instead it + x = torch.mean(x, dim=(-1, -2)) + cls_score = self.fc_cls(x) if self.with_cls else None + bbox_pred = self.fc_reg(x) if self.with_reg else None + return cls_score, bbox_pred + + def _get_targets_single(self, pos_priors: Tensor, neg_priors: Tensor, + pos_gt_bboxes: Tensor, pos_gt_labels: Tensor, + cfg: ConfigDict) -> tuple: + """Calculate the ground truth for proposals in the single image + according to the sampling results. + + Args: + pos_priors (Tensor): Contains all the positive boxes, + has shape (num_pos, 4), the last dimension 4 + represents [tl_x, tl_y, br_x, br_y]. + neg_priors (Tensor): Contains all the negative boxes, + has shape (num_neg, 4), the last dimension 4 + represents [tl_x, tl_y, br_x, br_y]. + pos_gt_bboxes (Tensor): Contains gt_boxes for + all positive samples, has shape (num_pos, 4), + the last dimension 4 + represents [tl_x, tl_y, br_x, br_y]. + pos_gt_labels (Tensor): Contains gt_labels for + all positive samples, has shape (num_pos, ). + cfg (obj:`ConfigDict`): `train_cfg` of R-CNN. + + Returns: + Tuple[Tensor]: Ground truth for proposals + in a single image. Containing the following Tensors: + + - labels(Tensor): Gt_labels for all proposals, has + shape (num_proposals,). + - label_weights(Tensor): Labels_weights for all + proposals, has shape (num_proposals,). + - bbox_targets(Tensor):Regression target for all + proposals, has shape (num_proposals, 4), the + last dimension 4 represents [tl_x, tl_y, br_x, br_y]. + - bbox_weights(Tensor):Regression weights for all + proposals, has shape (num_proposals, 4). + """ + num_pos = pos_priors.size(0) + num_neg = neg_priors.size(0) + num_samples = num_pos + num_neg + + # original implementation uses new_zeros since BG are set to be 0 + # now use empty & fill because BG cat_id = num_classes, + # FG cat_id = [0, num_classes-1] + labels = pos_priors.new_full((num_samples, ), + self.num_classes, + dtype=torch.long) + reg_dim = pos_gt_bboxes.size(-1) if self.reg_decoded_bbox \ + else self.bbox_coder.encode_size + label_weights = pos_priors.new_zeros(num_samples) + bbox_targets = pos_priors.new_zeros(num_samples, reg_dim) + bbox_weights = pos_priors.new_zeros(num_samples, reg_dim) + if num_pos > 0: + labels[:num_pos] = pos_gt_labels + pos_weight = 1.0 if cfg.pos_weight <= 0 else cfg.pos_weight + label_weights[:num_pos] = pos_weight + if not self.reg_decoded_bbox: + pos_bbox_targets = self.bbox_coder.encode( + pos_priors, pos_gt_bboxes) + else: + # When the regression loss (e.g. `IouLoss`, `GIouLoss`) + # is applied directly on the decoded bounding boxes, both + # the predicted boxes and regression targets should be with + # absolute coordinate format. + pos_bbox_targets = get_box_tensor(pos_gt_bboxes) + bbox_targets[:num_pos, :] = pos_bbox_targets + bbox_weights[:num_pos, :] = 1 + if num_neg > 0: + label_weights[-num_neg:] = 1.0 + + return labels, label_weights, bbox_targets, bbox_weights + + def get_targets(self, + sampling_results: List[SamplingResult], + rcnn_train_cfg: ConfigDict, + concat: bool = True) -> tuple: + """Calculate the ground truth for all samples in a batch according to + the sampling_results. + + Almost the same as the implementation in bbox_head, we passed + additional parameters pos_inds_list and neg_inds_list to + `_get_targets_single` function. + + Args: + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + rcnn_train_cfg (obj:ConfigDict): `train_cfg` of RCNN. + concat (bool): Whether to concatenate the results of all + the images in a single batch. + + Returns: + Tuple[Tensor]: Ground truth for proposals in a single image. + Containing the following list of Tensors: + + - labels (list[Tensor],Tensor): Gt_labels for all + proposals in a batch, each tensor in list has + shape (num_proposals,) when `concat=False`, otherwise + just a single tensor has shape (num_all_proposals,). + - label_weights (list[Tensor]): Labels_weights for + all proposals in a batch, each tensor in list has + shape (num_proposals,) when `concat=False`, otherwise + just a single tensor has shape (num_all_proposals,). + - bbox_targets (list[Tensor],Tensor): Regression target + for all proposals in a batch, each tensor in list + has shape (num_proposals, 4) when `concat=False`, + otherwise just a single tensor has shape + (num_all_proposals, 4), the last dimension 4 represents + [tl_x, tl_y, br_x, br_y]. + - bbox_weights (list[tensor],Tensor): Regression weights for + all proposals in a batch, each tensor in list has shape + (num_proposals, 4) when `concat=False`, otherwise just a + single tensor has shape (num_all_proposals, 4). + """ + pos_priors_list = [res.pos_priors for res in sampling_results] + neg_priors_list = [res.neg_priors for res in sampling_results] + pos_gt_bboxes_list = [res.pos_gt_bboxes for res in sampling_results] + pos_gt_labels_list = [res.pos_gt_labels for res in sampling_results] + labels, label_weights, bbox_targets, bbox_weights = multi_apply( + self._get_targets_single, + pos_priors_list, + neg_priors_list, + pos_gt_bboxes_list, + pos_gt_labels_list, + cfg=rcnn_train_cfg) + + if concat: + labels = torch.cat(labels, 0) + label_weights = torch.cat(label_weights, 0) + bbox_targets = torch.cat(bbox_targets, 0) + bbox_weights = torch.cat(bbox_weights, 0) + return labels, label_weights, bbox_targets, bbox_weights + + def loss_and_target(self, + cls_score: Tensor, + bbox_pred: Tensor, + rois: Tensor, + sampling_results: List[SamplingResult], + rcnn_train_cfg: ConfigDict, + concat: bool = True, + reduction_override: Optional[str] = None) -> dict: + """Calculate the loss based on the features extracted by the bbox head. + + Args: + cls_score (Tensor): Classification prediction + results of all class, has shape + (batch_size * num_proposals_single_image, num_classes) + bbox_pred (Tensor): Regression prediction results, + has shape + (batch_size * num_proposals_single_image, 4), the last + dimension 4 represents [tl_x, tl_y, br_x, br_y]. + rois (Tensor): RoIs with the shape + (batch_size * num_proposals_single_image, 5) where the first + column indicates batch id of each RoI. + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + rcnn_train_cfg (obj:ConfigDict): `train_cfg` of RCNN. + concat (bool): Whether to concatenate the results of all + the images in a single batch. Defaults to True. + reduction_override (str, optional): The reduction + method used to override the original reduction + method of the loss. Options are "none", + "mean" and "sum". Defaults to None, + + Returns: + dict: A dictionary of loss and targets components. + The targets are only used for cascade rcnn. + """ + + cls_reg_targets = self.get_targets( + sampling_results, rcnn_train_cfg, concat=concat) + losses = self.loss( + cls_score, + bbox_pred, + rois, + *cls_reg_targets, + reduction_override=reduction_override) + + # cls_reg_targets is only for cascade rcnn + return dict(loss_bbox=losses, bbox_targets=cls_reg_targets) + + def loss(self, + cls_score: Tensor, + bbox_pred: Tensor, + rois: Tensor, + labels: Tensor, + label_weights: Tensor, + bbox_targets: Tensor, + bbox_weights: Tensor, + reduction_override: Optional[str] = None) -> dict: + """Calculate the loss based on the network predictions and targets. + + Args: + cls_score (Tensor): Classification prediction + results of all class, has shape + (batch_size * num_proposals_single_image, num_classes) + bbox_pred (Tensor): Regression prediction results, + has shape + (batch_size * num_proposals_single_image, 4), the last + dimension 4 represents [tl_x, tl_y, br_x, br_y]. + rois (Tensor): RoIs with the shape + (batch_size * num_proposals_single_image, 5) where the first + column indicates batch id of each RoI. + labels (Tensor): Gt_labels for all proposals in a batch, has + shape (batch_size * num_proposals_single_image, ). + label_weights (Tensor): Labels_weights for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, ). + bbox_targets (Tensor): Regression target for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, 4), + the last dimension 4 represents [tl_x, tl_y, br_x, br_y]. + bbox_weights (Tensor): Regression weights for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, 4). + reduction_override (str, optional): The reduction + method used to override the original reduction + method of the loss. Options are "none", + "mean" and "sum". Defaults to None, + + Returns: + dict: A dictionary of loss. + """ + + losses = dict() + + if cls_score is not None: + avg_factor = max(torch.sum(label_weights > 0).float().item(), 1.) + if cls_score.numel() > 0: + loss_cls_ = self.loss_cls( + cls_score, + labels, + label_weights, + avg_factor=avg_factor, + reduction_override=reduction_override) + if isinstance(loss_cls_, dict): + losses.update(loss_cls_) + else: + losses['loss_cls'] = loss_cls_ + if self.custom_activation: + acc_ = self.loss_cls.get_accuracy(cls_score, labels) + losses.update(acc_) + else: + losses['acc'] = accuracy(cls_score, labels) + if bbox_pred is not None: + bg_class_ind = self.num_classes + # 0~self.num_classes-1 are FG, self.num_classes is BG + pos_inds = (labels >= 0) & (labels < bg_class_ind) + # do not perform bounding box regression for BG anymore. + if pos_inds.any(): + if self.reg_decoded_bbox: + # When the regression loss (e.g. `IouLoss`, + # `GIouLoss`, `DIouLoss`) is applied directly on + # the decoded bounding boxes, it decodes the + # already encoded coordinates to absolute format. + bbox_pred = self.bbox_coder.decode(rois[:, 1:], bbox_pred) + bbox_pred = get_box_tensor(bbox_pred) + if self.reg_class_agnostic: + pos_bbox_pred = bbox_pred.view( + bbox_pred.size(0), -1)[pos_inds.type(torch.bool)] + else: + pos_bbox_pred = bbox_pred.view( + bbox_pred.size(0), self.num_classes, + -1)[pos_inds.type(torch.bool), + labels[pos_inds.type(torch.bool)]] + losses['loss_bbox'] = self.loss_bbox( + pos_bbox_pred, + bbox_targets[pos_inds.type(torch.bool)], + bbox_weights[pos_inds.type(torch.bool)], + avg_factor=bbox_targets.size(0), + reduction_override=reduction_override) + else: + losses['loss_bbox'] = bbox_pred[pos_inds].sum() + + return losses + + def predict_by_feat(self, + rois: Tuple[Tensor], + cls_scores: Tuple[Tensor], + bbox_preds: Tuple[Tensor], + batch_img_metas: List[dict], + rcnn_test_cfg: Optional[ConfigDict] = None, + rescale: bool = False) -> InstanceList: + """Transform a batch of output features extracted from the head into + bbox results. + + Args: + rois (tuple[Tensor]): Tuple of boxes to be transformed. + Each has shape (num_boxes, 5). last dimension 5 arrange as + (batch_index, x1, y1, x2, y2). + cls_scores (tuple[Tensor]): Tuple of box scores, each has shape + (num_boxes, num_classes + 1). + bbox_preds (tuple[Tensor]): Tuple of box energies / deltas, each + has shape (num_boxes, num_classes * 4). + batch_img_metas (list[dict]): List of image information. + rcnn_test_cfg (obj:`ConfigDict`, optional): `test_cfg` of R-CNN. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Instance segmentation + results of each image after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_scores) == len(bbox_preds) + result_list = [] + for img_id in range(len(batch_img_metas)): + img_meta = batch_img_metas[img_id] + results = self._predict_by_feat_single( + roi=rois[img_id], + cls_score=cls_scores[img_id], + bbox_pred=bbox_preds[img_id], + img_meta=img_meta, + rescale=rescale, + rcnn_test_cfg=rcnn_test_cfg) + result_list.append(results) + + return result_list + + def _predict_by_feat_single( + self, + roi: Tensor, + cls_score: Tensor, + bbox_pred: Tensor, + img_meta: dict, + rescale: bool = False, + rcnn_test_cfg: Optional[ConfigDict] = None) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + roi (Tensor): Boxes to be transformed. Has shape (num_boxes, 5). + last dimension 5 arrange as (batch_index, x1, y1, x2, y2). + cls_score (Tensor): Box scores, has shape + (num_boxes, num_classes + 1). + bbox_pred (Tensor): Box energies / deltas. + has shape (num_boxes, num_classes * 4). + img_meta (dict): image information. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of Bbox Head. + Defaults to None + + Returns: + :obj:`InstanceData`: Detection results of each image\ + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + results = InstanceData() + if roi.shape[0] == 0: + return empty_instances([img_meta], + roi.device, + task_type='bbox', + instance_results=[results], + box_type=self.predict_box_type, + use_box_type=False, + num_classes=self.num_classes, + score_per_cls=rcnn_test_cfg is None)[0] + + # some loss (Seesaw loss..) may have custom activation + if self.custom_cls_channels: + scores = self.loss_cls.get_activation(cls_score) + else: + scores = F.softmax( + cls_score, dim=-1) if cls_score is not None else None + + img_shape = img_meta['img_shape'] + num_rois = roi.size(0) + # bbox_pred would be None in some detector when with_reg is False, + # e.g. Grid R-CNN. + if bbox_pred is not None: + num_classes = 1 if self.reg_class_agnostic else self.num_classes + roi = roi.repeat_interleave(num_classes, dim=0) + bbox_pred = bbox_pred.view(-1, self.bbox_coder.encode_size) + bboxes = self.bbox_coder.decode( + roi[..., 1:], bbox_pred, max_shape=img_shape) + else: + bboxes = roi[:, 1:].clone() + if img_shape is not None and bboxes.size(-1) == 4: + bboxes[:, [0, 2]].clamp_(min=0, max=img_shape[1]) + bboxes[:, [1, 3]].clamp_(min=0, max=img_shape[0]) + + if rescale and bboxes.size(0) > 0: + assert img_meta.get('scale_factor') is not None + scale_factor = [1 / s for s in img_meta['scale_factor']] + bboxes = scale_boxes(bboxes, scale_factor) + + # Get the inside tensor when `bboxes` is a box type + bboxes = get_box_tensor(bboxes) + box_dim = bboxes.size(-1) + bboxes = bboxes.view(num_rois, -1) + + if rcnn_test_cfg is None: + # This means that it is aug test. + # It needs to return the raw results without nms. + results.bboxes = bboxes + results.scores = scores + else: + det_bboxes, det_labels = multiclass_nms( + bboxes, + scores, + rcnn_test_cfg.score_thr, + rcnn_test_cfg.nms, + rcnn_test_cfg.max_per_img, + box_dim=box_dim) + results.bboxes = det_bboxes[:, :-1] + results.scores = det_bboxes[:, -1] + results.labels = det_labels + return results + + def refine_bboxes(self, sampling_results: Union[List[SamplingResult], + InstanceList], + bbox_results: dict, + batch_img_metas: List[dict]) -> InstanceList: + """Refine bboxes during training. + + Args: + sampling_results (List[:obj:`SamplingResult`] or + List[:obj:`InstanceData`]): Sampling results. + :obj:`SamplingResult` is the real sampling results + calculate from bbox_head, while :obj:`InstanceData` is + fake sampling results, e.g., in Sparse R-CNN or QueryInst, etc. + bbox_results (dict): Usually is a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `rois` (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + - `bbox_targets` (tuple): Ground truth for proposals in a + single image. Containing the following list of Tensors: + (labels, label_weights, bbox_targets, bbox_weights) + batch_img_metas (List[dict]): List of image information. + + Returns: + list[:obj:`InstanceData`]: Refined bboxes of each image. + + Example: + >>> # xdoctest: +REQUIRES(module:kwarray) + >>> import numpy as np + >>> from mmdet.models.task_modules.samplers. + ... sampling_result import random_boxes + >>> from mmdet.models.task_modules.samplers import SamplingResult + >>> self = BBoxHead(reg_class_agnostic=True) + >>> n_roi = 2 + >>> n_img = 4 + >>> scale = 512 + >>> rng = np.random.RandomState(0) + ... batch_img_metas = [{'img_shape': (scale, scale)} + >>> for _ in range(n_img)] + >>> sampling_results = [SamplingResult.random(rng=10) + ... for _ in range(n_img)] + >>> # Create rois in the expected format + >>> roi_boxes = random_boxes(n_roi, scale=scale, rng=rng) + >>> img_ids = torch.randint(0, n_img, (n_roi,)) + >>> img_ids = img_ids.float() + >>> rois = torch.cat([img_ids[:, None], roi_boxes], dim=1) + >>> # Create other args + >>> labels = torch.randint(0, 81, (scale,)).long() + >>> bbox_preds = random_boxes(n_roi, scale=scale, rng=rng) + >>> cls_score = torch.randn((scale, 81)) + ... # For each image, pretend random positive boxes are gts + >>> bbox_targets = (labels, None, None, None) + ... bbox_results = dict(rois=rois, bbox_pred=bbox_preds, + ... cls_score=cls_score, + ... bbox_targets=bbox_targets) + >>> bboxes_list = self.refine_bboxes(sampling_results, + ... bbox_results, + ... batch_img_metas) + >>> print(bboxes_list) + """ + pos_is_gts = [res.pos_is_gt for res in sampling_results] + # bbox_targets is a tuple + labels = bbox_results['bbox_targets'][0] + cls_scores = bbox_results['cls_score'] + rois = bbox_results['rois'] + bbox_preds = bbox_results['bbox_pred'] + if self.custom_activation: + # TODO: Create a SeasawBBoxHead to simplified logic in BBoxHead + cls_scores = self.loss_cls.get_activation(cls_scores) + if cls_scores.numel() == 0: + return None + if cls_scores.shape[-1] == self.num_classes + 1: + # remove background class + cls_scores = cls_scores[:, :-1] + elif cls_scores.shape[-1] != self.num_classes: + raise ValueError('The last dim of `cls_scores` should equal to ' + '`num_classes` or `num_classes + 1`,' + f'but got {cls_scores.shape[-1]}.') + labels = torch.where(labels == self.num_classes, cls_scores.argmax(1), + labels) + + img_ids = rois[:, 0].long().unique(sorted=True) + assert img_ids.numel() <= len(batch_img_metas) + + results_list = [] + for i in range(len(batch_img_metas)): + inds = torch.nonzero( + rois[:, 0] == i, as_tuple=False).squeeze(dim=1) + num_rois = inds.numel() + + bboxes_ = rois[inds, 1:] + label_ = labels[inds] + bbox_pred_ = bbox_preds[inds] + img_meta_ = batch_img_metas[i] + pos_is_gts_ = pos_is_gts[i] + + bboxes = self.regress_by_class(bboxes_, label_, bbox_pred_, + img_meta_) + # filter gt bboxes + pos_keep = 1 - pos_is_gts_ + keep_inds = pos_is_gts_.new_ones(num_rois) + keep_inds[:len(pos_is_gts_)] = pos_keep + results = InstanceData(bboxes=bboxes[keep_inds.type(torch.bool)]) + results_list.append(results) + + return results_list + + def regress_by_class(self, priors: Tensor, label: Tensor, + bbox_pred: Tensor, img_meta: dict) -> Tensor: + """Regress the bbox for the predicted class. Used in Cascade R-CNN. + + Args: + priors (Tensor): Priors from `rpn_head` or last stage + `bbox_head`, has shape (num_proposals, 4). + label (Tensor): Only used when `self.reg_class_agnostic` + is False, has shape (num_proposals, ). + bbox_pred (Tensor): Regression prediction of + current stage `bbox_head`. When `self.reg_class_agnostic` + is False, it has shape (n, num_classes * 4), otherwise + it has shape (n, 4). + img_meta (dict): Image meta info. + + Returns: + Tensor: Regressed bboxes, the same shape as input rois. + """ + reg_dim = self.bbox_coder.encode_size + if not self.reg_class_agnostic: + label = label * reg_dim + inds = torch.stack([label + i for i in range(reg_dim)], 1) + bbox_pred = torch.gather(bbox_pred, 1, inds) + assert bbox_pred.size()[1] == reg_dim + + max_shape = img_meta['img_shape'] + regressed_bboxes = self.bbox_coder.decode( + priors, bbox_pred, max_shape=max_shape) + return regressed_bboxes diff --git a/mmdetection/mmdet/models/roi_heads/bbox_heads/convfc_bbox_head.py b/mmdetection/mmdet/models/roi_heads/bbox_heads/convfc_bbox_head.py new file mode 100644 index 00000000..cb6aadd8 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/bbox_heads/convfc_bbox_head.py @@ -0,0 +1,249 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, Union + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.config import ConfigDict +from torch import Tensor + +from mmdet.registry import MODELS +from .bbox_head import BBoxHead + + +@MODELS.register_module() +class ConvFCBBoxHead(BBoxHead): + r"""More general bbox head, with shared conv and fc layers and two optional + separated branches. + + .. code-block:: none + + /-> cls convs -> cls fcs -> cls + shared convs -> shared fcs + \-> reg convs -> reg fcs -> reg + """ # noqa: W605 + + def __init__(self, + num_shared_convs: int = 0, + num_shared_fcs: int = 0, + num_cls_convs: int = 0, + num_cls_fcs: int = 0, + num_reg_convs: int = 0, + num_reg_fcs: int = 0, + conv_out_channels: int = 256, + fc_out_channels: int = 1024, + conv_cfg: Optional[Union[dict, ConfigDict]] = None, + norm_cfg: Optional[Union[dict, ConfigDict]] = None, + init_cfg: Optional[Union[dict, ConfigDict]] = None, + *args, + **kwargs) -> None: + super().__init__(*args, init_cfg=init_cfg, **kwargs) + assert (num_shared_convs + num_shared_fcs + num_cls_convs + + num_cls_fcs + num_reg_convs + num_reg_fcs > 0) + if num_cls_convs > 0 or num_reg_convs > 0: + assert num_shared_fcs == 0 + if not self.with_cls: + assert num_cls_convs == 0 and num_cls_fcs == 0 + if not self.with_reg: + assert num_reg_convs == 0 and num_reg_fcs == 0 + self.num_shared_convs = num_shared_convs + self.num_shared_fcs = num_shared_fcs + self.num_cls_convs = num_cls_convs + self.num_cls_fcs = num_cls_fcs + self.num_reg_convs = num_reg_convs + self.num_reg_fcs = num_reg_fcs + self.conv_out_channels = conv_out_channels + self.fc_out_channels = fc_out_channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + + # add shared convs and fcs + self.shared_convs, self.shared_fcs, last_layer_dim = \ + self._add_conv_fc_branch( + self.num_shared_convs, self.num_shared_fcs, self.in_channels, + True) + self.shared_out_channels = last_layer_dim + + # add cls specific branch + self.cls_convs, self.cls_fcs, self.cls_last_dim = \ + self._add_conv_fc_branch( + self.num_cls_convs, self.num_cls_fcs, self.shared_out_channels) + + # add reg specific branch + self.reg_convs, self.reg_fcs, self.reg_last_dim = \ + self._add_conv_fc_branch( + self.num_reg_convs, self.num_reg_fcs, self.shared_out_channels) + + if self.num_shared_fcs == 0 and not self.with_avg_pool: + if self.num_cls_fcs == 0: + self.cls_last_dim *= self.roi_feat_area + if self.num_reg_fcs == 0: + self.reg_last_dim *= self.roi_feat_area + + self.relu = nn.ReLU(inplace=True) + # reconstruct fc_cls and fc_reg since input channels are changed + if self.with_cls: + if self.custom_cls_channels: + cls_channels = self.loss_cls.get_cls_channels(self.num_classes) + else: + cls_channels = self.num_classes + 1 + cls_predictor_cfg_ = self.cls_predictor_cfg.copy() + cls_predictor_cfg_.update( + in_features=self.cls_last_dim, out_features=cls_channels) + self.fc_cls = MODELS.build(cls_predictor_cfg_) + if self.with_reg: + box_dim = self.bbox_coder.encode_size + out_dim_reg = box_dim if self.reg_class_agnostic else \ + box_dim * self.num_classes + reg_predictor_cfg_ = self.reg_predictor_cfg.copy() + if isinstance(reg_predictor_cfg_, (dict, ConfigDict)): + reg_predictor_cfg_.update( + in_features=self.reg_last_dim, out_features=out_dim_reg) + self.fc_reg = MODELS.build(reg_predictor_cfg_) + + if init_cfg is None: + # when init_cfg is None, + # It has been set to + # [[dict(type='Normal', std=0.01, override=dict(name='fc_cls'))], + # [dict(type='Normal', std=0.001, override=dict(name='fc_reg'))] + # after `super(ConvFCBBoxHead, self).__init__()` + # we only need to append additional configuration + # for `shared_fcs`, `cls_fcs` and `reg_fcs` + self.init_cfg += [ + dict( + type='Xavier', + distribution='uniform', + override=[ + dict(name='shared_fcs'), + dict(name='cls_fcs'), + dict(name='reg_fcs') + ]) + ] + + def _add_conv_fc_branch(self, + num_branch_convs: int, + num_branch_fcs: int, + in_channels: int, + is_shared: bool = False) -> tuple: + """Add shared or separable branch. + + convs -> avg pool (optional) -> fcs + """ + last_layer_dim = in_channels + # add branch specific conv layers + branch_convs = nn.ModuleList() + if num_branch_convs > 0: + for i in range(num_branch_convs): + conv_in_channels = ( + last_layer_dim if i == 0 else self.conv_out_channels) + branch_convs.append( + ConvModule( + conv_in_channels, + self.conv_out_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + last_layer_dim = self.conv_out_channels + # add branch specific fc layers + branch_fcs = nn.ModuleList() + if num_branch_fcs > 0: + # for shared branch, only consider self.with_avg_pool + # for separated branches, also consider self.num_shared_fcs + if (is_shared + or self.num_shared_fcs == 0) and not self.with_avg_pool: + last_layer_dim *= self.roi_feat_area + for i in range(num_branch_fcs): + fc_in_channels = ( + last_layer_dim if i == 0 else self.fc_out_channels) + branch_fcs.append( + nn.Linear(fc_in_channels, self.fc_out_channels)) + last_layer_dim = self.fc_out_channels + return branch_convs, branch_fcs, last_layer_dim + + def forward(self, x: Tuple[Tensor]) -> tuple: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of classification scores and bbox prediction. + + - cls_score (Tensor): Classification scores for all \ + scale levels, each is a 4D-tensor, the channels number \ + is num_base_priors * num_classes. + - bbox_pred (Tensor): Box energies / deltas for all \ + scale levels, each is a 4D-tensor, the channels number \ + is num_base_priors * 4. + """ + # shared part + if self.num_shared_convs > 0: + for conv in self.shared_convs: + x = conv(x) + + if self.num_shared_fcs > 0: + if self.with_avg_pool: + x = self.avg_pool(x) + + x = x.flatten(1) + + for fc in self.shared_fcs: + x = self.relu(fc(x)) + # separate branches + x_cls = x + x_reg = x + + for conv in self.cls_convs: + x_cls = conv(x_cls) + if x_cls.dim() > 2: + if self.with_avg_pool: + x_cls = self.avg_pool(x_cls) + x_cls = x_cls.flatten(1) + for fc in self.cls_fcs: + x_cls = self.relu(fc(x_cls)) + + for conv in self.reg_convs: + x_reg = conv(x_reg) + if x_reg.dim() > 2: + if self.with_avg_pool: + x_reg = self.avg_pool(x_reg) + x_reg = x_reg.flatten(1) + for fc in self.reg_fcs: + x_reg = self.relu(fc(x_reg)) + + cls_score = self.fc_cls(x_cls) if self.with_cls else None + bbox_pred = self.fc_reg(x_reg) if self.with_reg else None + return cls_score, bbox_pred + + +@MODELS.register_module() +class Shared2FCBBoxHead(ConvFCBBoxHead): + + def __init__(self, fc_out_channels: int = 1024, *args, **kwargs) -> None: + super().__init__( + num_shared_convs=0, + num_shared_fcs=2, + num_cls_convs=0, + num_cls_fcs=0, + num_reg_convs=0, + num_reg_fcs=0, + fc_out_channels=fc_out_channels, + *args, + **kwargs) + + +@MODELS.register_module() +class Shared4Conv1FCBBoxHead(ConvFCBBoxHead): + + def __init__(self, fc_out_channels: int = 1024, *args, **kwargs) -> None: + super().__init__( + num_shared_convs=4, + num_shared_fcs=1, + num_cls_convs=0, + num_cls_fcs=0, + num_reg_convs=0, + num_reg_fcs=0, + fc_out_channels=fc_out_channels, + *args, + **kwargs) diff --git a/mmdetection/mmdet/models/roi_heads/bbox_heads/dii_head.py b/mmdetection/mmdet/models/roi_heads/bbox_heads/dii_head.py new file mode 100644 index 00000000..ae9a31bb --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/bbox_heads/dii_head.py @@ -0,0 +1,422 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn as nn +from mmcv.cnn import build_activation_layer, build_norm_layer +from mmcv.cnn.bricks.transformer import FFN, MultiheadAttention +from mmengine.config import ConfigDict +from mmengine.model import bias_init_with_prob +from torch import Tensor + +from mmdet.models.losses import accuracy +from mmdet.models.task_modules import SamplingResult +from mmdet.models.utils import multi_apply +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, reduce_mean +from .bbox_head import BBoxHead + + +@MODELS.register_module() +class DIIHead(BBoxHead): + r"""Dynamic Instance Interactive Head for `Sparse R-CNN: End-to-End Object + Detection with Learnable Proposals `_ + + Args: + num_classes (int): Number of class in dataset. + Defaults to 80. + num_ffn_fcs (int): The number of fully-connected + layers in FFNs. Defaults to 2. + num_heads (int): The hidden dimension of FFNs. + Defaults to 8. + num_cls_fcs (int): The number of fully-connected + layers in classification subnet. Defaults to 1. + num_reg_fcs (int): The number of fully-connected + layers in regression subnet. Defaults to 3. + feedforward_channels (int): The hidden dimension + of FFNs. Defaults to 2048 + in_channels (int): Hidden_channels of MultiheadAttention. + Defaults to 256. + dropout (float): Probability of drop the channel. + Defaults to 0.0 + ffn_act_cfg (:obj:`ConfigDict` or dict): The activation config + for FFNs. + dynamic_conv_cfg (:obj:`ConfigDict` or dict): The convolution + config for DynamicConv. + loss_iou (:obj:`ConfigDict` or dict): The config for iou or + giou loss. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. Defaults to None. + """ + + def __init__(self, + num_classes: int = 80, + num_ffn_fcs: int = 2, + num_heads: int = 8, + num_cls_fcs: int = 1, + num_reg_fcs: int = 3, + feedforward_channels: int = 2048, + in_channels: int = 256, + dropout: float = 0.0, + ffn_act_cfg: ConfigType = dict(type='ReLU', inplace=True), + dynamic_conv_cfg: ConfigType = dict( + type='DynamicConv', + in_channels=256, + feat_channels=64, + out_channels=256, + input_feat_shape=7, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN')), + loss_iou: ConfigType = dict(type='GIoULoss', loss_weight=2.0), + init_cfg: OptConfigType = None, + **kwargs) -> None: + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + super().__init__( + num_classes=num_classes, + reg_decoded_bbox=True, + reg_class_agnostic=True, + init_cfg=init_cfg, + **kwargs) + self.loss_iou = MODELS.build(loss_iou) + self.in_channels = in_channels + self.fp16_enabled = False + self.attention = MultiheadAttention(in_channels, num_heads, dropout) + self.attention_norm = build_norm_layer(dict(type='LN'), in_channels)[1] + + self.instance_interactive_conv = MODELS.build(dynamic_conv_cfg) + self.instance_interactive_conv_dropout = nn.Dropout(dropout) + self.instance_interactive_conv_norm = build_norm_layer( + dict(type='LN'), in_channels)[1] + + self.ffn = FFN( + in_channels, + feedforward_channels, + num_ffn_fcs, + act_cfg=ffn_act_cfg, + dropout=dropout) + self.ffn_norm = build_norm_layer(dict(type='LN'), in_channels)[1] + + self.cls_fcs = nn.ModuleList() + for _ in range(num_cls_fcs): + self.cls_fcs.append( + nn.Linear(in_channels, in_channels, bias=False)) + self.cls_fcs.append( + build_norm_layer(dict(type='LN'), in_channels)[1]) + self.cls_fcs.append( + build_activation_layer(dict(type='ReLU', inplace=True))) + + # over load the self.fc_cls in BBoxHead + if self.loss_cls.use_sigmoid: + self.fc_cls = nn.Linear(in_channels, self.num_classes) + else: + self.fc_cls = nn.Linear(in_channels, self.num_classes + 1) + + self.reg_fcs = nn.ModuleList() + for _ in range(num_reg_fcs): + self.reg_fcs.append( + nn.Linear(in_channels, in_channels, bias=False)) + self.reg_fcs.append( + build_norm_layer(dict(type='LN'), in_channels)[1]) + self.reg_fcs.append( + build_activation_layer(dict(type='ReLU', inplace=True))) + # over load the self.fc_cls in BBoxHead + self.fc_reg = nn.Linear(in_channels, 4) + + assert self.reg_class_agnostic, 'DIIHead only ' \ + 'suppport `reg_class_agnostic=True` ' + assert self.reg_decoded_bbox, 'DIIHead only ' \ + 'suppport `reg_decoded_bbox=True`' + + def init_weights(self) -> None: + """Use xavier initialization for all weight parameter and set + classification head bias as a specific value when use focal loss.""" + super().init_weights() + for p in self.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + else: + # adopt the default initialization for + # the weight and bias of the layer norm + pass + if self.loss_cls.use_sigmoid: + bias_init = bias_init_with_prob(0.01) + nn.init.constant_(self.fc_cls.bias, bias_init) + + def forward(self, roi_feat: Tensor, proposal_feat: Tensor) -> tuple: + """Forward function of Dynamic Instance Interactive Head. + + Args: + roi_feat (Tensor): Roi-pooling features with shape + (batch_size*num_proposals, feature_dimensions, + pooling_h , pooling_w). + proposal_feat (Tensor): Intermediate feature get from + diihead in last stage, has shape + (batch_size, num_proposals, feature_dimensions) + + Returns: + tuple[Tensor]: Usually a tuple of classification scores + and bbox prediction and a intermediate feature. + + - cls_scores (Tensor): Classification scores for + all proposals, has shape + (batch_size, num_proposals, num_classes). + - bbox_preds (Tensor): Box energies / deltas for + all proposals, has shape + (batch_size, num_proposals, 4). + - obj_feat (Tensor): Object feature before classification + and regression subnet, has shape + (batch_size, num_proposal, feature_dimensions). + - attn_feats (Tensor): Intermediate feature. + """ + N, num_proposals = proposal_feat.shape[:2] + + # Self attention + proposal_feat = proposal_feat.permute(1, 0, 2) + proposal_feat = self.attention_norm(self.attention(proposal_feat)) + attn_feats = proposal_feat.permute(1, 0, 2) + + # instance interactive + proposal_feat = attn_feats.reshape(-1, self.in_channels) + proposal_feat_iic = self.instance_interactive_conv( + proposal_feat, roi_feat) + proposal_feat = proposal_feat + self.instance_interactive_conv_dropout( + proposal_feat_iic) + obj_feat = self.instance_interactive_conv_norm(proposal_feat) + + # FFN + obj_feat = self.ffn_norm(self.ffn(obj_feat)) + + cls_feat = obj_feat + reg_feat = obj_feat + + for cls_layer in self.cls_fcs: + cls_feat = cls_layer(cls_feat) + for reg_layer in self.reg_fcs: + reg_feat = reg_layer(reg_feat) + + cls_score = self.fc_cls(cls_feat).view( + N, num_proposals, self.num_classes + if self.loss_cls.use_sigmoid else self.num_classes + 1) + bbox_delta = self.fc_reg(reg_feat).view(N, num_proposals, 4) + + return cls_score, bbox_delta, obj_feat.view( + N, num_proposals, self.in_channels), attn_feats + + def loss_and_target(self, + cls_score: Tensor, + bbox_pred: Tensor, + sampling_results: List[SamplingResult], + rcnn_train_cfg: ConfigType, + imgs_whwh: Tensor, + concat: bool = True, + reduction_override: str = None) -> dict: + """Calculate the loss based on the features extracted by the DIIHead. + + Args: + cls_score (Tensor): Classification prediction + results of all class, has shape + (batch_size * num_proposals_single_image, num_classes) + bbox_pred (Tensor): Regression prediction results, has shape + (batch_size * num_proposals_single_image, 4), the last + dimension 4 represents [tl_x, tl_y, br_x, br_y]. + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + rcnn_train_cfg (obj:ConfigDict): `train_cfg` of RCNN. + imgs_whwh (Tensor): imgs_whwh (Tensor): Tensor with\ + shape (batch_size, num_proposals, 4), the last + dimension means + [img_width,img_height, img_width, img_height]. + concat (bool): Whether to concatenate the results of all + the images in a single batch. Defaults to True. + reduction_override (str, optional): The reduction + method used to override the original reduction + method of the loss. Options are "none", + "mean" and "sum". Defaults to None. + + Returns: + dict: A dictionary of loss and targets components. + The targets are only used for cascade rcnn. + """ + cls_reg_targets = self.get_targets( + sampling_results=sampling_results, + rcnn_train_cfg=rcnn_train_cfg, + concat=concat) + (labels, label_weights, bbox_targets, bbox_weights) = cls_reg_targets + + losses = dict() + bg_class_ind = self.num_classes + # note in spare rcnn num_gt == num_pos + pos_inds = (labels >= 0) & (labels < bg_class_ind) + num_pos = pos_inds.sum().float() + avg_factor = reduce_mean(num_pos) + if cls_score is not None: + if cls_score.numel() > 0: + losses['loss_cls'] = self.loss_cls( + cls_score, + labels, + label_weights, + avg_factor=avg_factor, + reduction_override=reduction_override) + losses['pos_acc'] = accuracy(cls_score[pos_inds], + labels[pos_inds]) + if bbox_pred is not None: + # 0~self.num_classes-1 are FG, self.num_classes is BG + # do not perform bounding box regression for BG anymore. + if pos_inds.any(): + pos_bbox_pred = bbox_pred.reshape(bbox_pred.size(0), + 4)[pos_inds.type(torch.bool)] + imgs_whwh = imgs_whwh.reshape(bbox_pred.size(0), + 4)[pos_inds.type(torch.bool)] + losses['loss_bbox'] = self.loss_bbox( + pos_bbox_pred / imgs_whwh, + bbox_targets[pos_inds.type(torch.bool)] / imgs_whwh, + bbox_weights[pos_inds.type(torch.bool)], + avg_factor=avg_factor) + losses['loss_iou'] = self.loss_iou( + pos_bbox_pred, + bbox_targets[pos_inds.type(torch.bool)], + bbox_weights[pos_inds.type(torch.bool)], + avg_factor=avg_factor) + else: + losses['loss_bbox'] = bbox_pred.sum() * 0 + losses['loss_iou'] = bbox_pred.sum() * 0 + return dict(loss_bbox=losses, bbox_targets=cls_reg_targets) + + def _get_targets_single(self, pos_inds: Tensor, neg_inds: Tensor, + pos_priors: Tensor, neg_priors: Tensor, + pos_gt_bboxes: Tensor, pos_gt_labels: Tensor, + cfg: ConfigDict) -> tuple: + """Calculate the ground truth for proposals in the single image + according to the sampling results. + + Almost the same as the implementation in `bbox_head`, + we add pos_inds and neg_inds to select positive and + negative samples instead of selecting the first num_pos + as positive samples. + + Args: + pos_inds (Tensor): The length is equal to the + positive sample numbers contain all index + of the positive sample in the origin proposal set. + neg_inds (Tensor): The length is equal to the + negative sample numbers contain all index + of the negative sample in the origin proposal set. + pos_priors (Tensor): Contains all the positive boxes, + has shape (num_pos, 4), the last dimension 4 + represents [tl_x, tl_y, br_x, br_y]. + neg_priors (Tensor): Contains all the negative boxes, + has shape (num_neg, 4), the last dimension 4 + represents [tl_x, tl_y, br_x, br_y]. + pos_gt_bboxes (Tensor): Contains gt_boxes for + all positive samples, has shape (num_pos, 4), + the last dimension 4 + represents [tl_x, tl_y, br_x, br_y]. + pos_gt_labels (Tensor): Contains gt_labels for + all positive samples, has shape (num_pos, ). + cfg (obj:`ConfigDict`): `train_cfg` of R-CNN. + + Returns: + Tuple[Tensor]: Ground truth for proposals in a single image. + Containing the following Tensors: + + - labels(Tensor): Gt_labels for all proposals, has + shape (num_proposals,). + - label_weights(Tensor): Labels_weights for all proposals, has + shape (num_proposals,). + - bbox_targets(Tensor):Regression target for all proposals, has + shape (num_proposals, 4), the last dimension 4 + represents [tl_x, tl_y, br_x, br_y]. + - bbox_weights(Tensor):Regression weights for all proposals, + has shape (num_proposals, 4). + """ + num_pos = pos_priors.size(0) + num_neg = neg_priors.size(0) + num_samples = num_pos + num_neg + + # original implementation uses new_zeros since BG are set to be 0 + # now use empty & fill because BG cat_id = num_classes, + # FG cat_id = [0, num_classes-1] + labels = pos_priors.new_full((num_samples, ), + self.num_classes, + dtype=torch.long) + label_weights = pos_priors.new_zeros(num_samples) + bbox_targets = pos_priors.new_zeros(num_samples, 4) + bbox_weights = pos_priors.new_zeros(num_samples, 4) + if num_pos > 0: + labels[pos_inds] = pos_gt_labels + pos_weight = 1.0 if cfg.pos_weight <= 0 else cfg.pos_weight + label_weights[pos_inds] = pos_weight + if not self.reg_decoded_bbox: + pos_bbox_targets = self.bbox_coder.encode( + pos_priors, pos_gt_bboxes) + else: + pos_bbox_targets = pos_gt_bboxes + bbox_targets[pos_inds, :] = pos_bbox_targets + bbox_weights[pos_inds, :] = 1 + if num_neg > 0: + label_weights[neg_inds] = 1.0 + + return labels, label_weights, bbox_targets, bbox_weights + + def get_targets(self, + sampling_results: List[SamplingResult], + rcnn_train_cfg: ConfigDict, + concat: bool = True) -> tuple: + """Calculate the ground truth for all samples in a batch according to + the sampling_results. + + Almost the same as the implementation in bbox_head, we passed + additional parameters pos_inds_list and neg_inds_list to + `_get_targets_single` function. + + Args: + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + rcnn_train_cfg (obj:ConfigDict): `train_cfg` of RCNN. + concat (bool): Whether to concatenate the results of all + the images in a single batch. + + Returns: + Tuple[Tensor]: Ground truth for proposals in a single image. + Containing the following list of Tensors: + + - labels (list[Tensor],Tensor): Gt_labels for all + proposals in a batch, each tensor in list has + shape (num_proposals,) when `concat=False`, otherwise just + a single tensor has shape (num_all_proposals,). + - label_weights (list[Tensor]): Labels_weights for + all proposals in a batch, each tensor in list has shape + (num_proposals,) when `concat=False`, otherwise just a + single tensor has shape (num_all_proposals,). + - bbox_targets (list[Tensor],Tensor): Regression target + for all proposals in a batch, each tensor in list has + shape (num_proposals, 4) when `concat=False`, otherwise + just a single tensor has shape (num_all_proposals, 4), + the last dimension 4 represents [tl_x, tl_y, br_x, br_y]. + - bbox_weights (list[tensor],Tensor): Regression weights for + all proposals in a batch, each tensor in list has shape + (num_proposals, 4) when `concat=False`, otherwise just a + single tensor has shape (num_all_proposals, 4). + """ + pos_inds_list = [res.pos_inds for res in sampling_results] + neg_inds_list = [res.neg_inds for res in sampling_results] + pos_priors_list = [res.pos_priors for res in sampling_results] + neg_priors_list = [res.neg_priors for res in sampling_results] + pos_gt_bboxes_list = [res.pos_gt_bboxes for res in sampling_results] + pos_gt_labels_list = [res.pos_gt_labels for res in sampling_results] + labels, label_weights, bbox_targets, bbox_weights = multi_apply( + self._get_targets_single, + pos_inds_list, + neg_inds_list, + pos_priors_list, + neg_priors_list, + pos_gt_bboxes_list, + pos_gt_labels_list, + cfg=rcnn_train_cfg) + if concat: + labels = torch.cat(labels, 0) + label_weights = torch.cat(label_weights, 0) + bbox_targets = torch.cat(bbox_targets, 0) + bbox_weights = torch.cat(bbox_weights, 0) + return labels, label_weights, bbox_targets, bbox_weights diff --git a/mmdetection/mmdet/models/roi_heads/bbox_heads/double_bbox_head.py b/mmdetection/mmdet/models/roi_heads/bbox_heads/double_bbox_head.py new file mode 100644 index 00000000..076c3584 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/bbox_heads/double_bbox_head.py @@ -0,0 +1,199 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule, ModuleList +from torch import Tensor + +from mmdet.models.backbones.resnet import Bottleneck +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, MultiConfig, OptConfigType, OptMultiConfig +from .bbox_head import BBoxHead + + +class BasicResBlock(BaseModule): + """Basic residual block. + + This block is a little different from the block in the ResNet backbone. + The kernel size of conv1 is 1 in this block while 3 in ResNet BasicBlock. + + Args: + in_channels (int): Channels of the input feature map. + out_channels (int): Channels of the output feature map. + conv_cfg (:obj:`ConfigDict` or dict, optional): The config dict + for convolution layers. + norm_cfg (:obj:`ConfigDict` or dict): The config dict for + normalization layers. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. Defaults to None + """ + + def __init__(self, + in_channels: int, + out_channels: int, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN'), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + + # main path + self.conv1 = ConvModule( + in_channels, + in_channels, + kernel_size=3, + padding=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg) + self.conv2 = ConvModule( + in_channels, + out_channels, + kernel_size=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + + # identity path + self.conv_identity = ConvModule( + in_channels, + out_channels, + kernel_size=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + + self.relu = nn.ReLU(inplace=True) + + def forward(self, x: Tensor) -> Tensor: + """Forward function.""" + identity = x + + x = self.conv1(x) + x = self.conv2(x) + + identity = self.conv_identity(identity) + out = x + identity + + out = self.relu(out) + return out + + +@MODELS.register_module() +class DoubleConvFCBBoxHead(BBoxHead): + r"""Bbox head used in Double-Head R-CNN + + .. code-block:: none + + /-> cls + /-> shared convs -> + \-> reg + roi features + /-> cls + \-> shared fc -> + \-> reg + """ # noqa: W605 + + def __init__(self, + num_convs: int = 0, + num_fcs: int = 0, + conv_out_channels: int = 1024, + fc_out_channels: int = 1024, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='BN'), + init_cfg: MultiConfig = dict( + type='Normal', + override=[ + dict(type='Normal', name='fc_cls', std=0.01), + dict(type='Normal', name='fc_reg', std=0.001), + dict( + type='Xavier', + name='fc_branch', + distribution='uniform') + ]), + **kwargs) -> None: + kwargs.setdefault('with_avg_pool', True) + super().__init__(init_cfg=init_cfg, **kwargs) + assert self.with_avg_pool + assert num_convs > 0 + assert num_fcs > 0 + self.num_convs = num_convs + self.num_fcs = num_fcs + self.conv_out_channels = conv_out_channels + self.fc_out_channels = fc_out_channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + + # increase the channel of input features + self.res_block = BasicResBlock(self.in_channels, + self.conv_out_channels) + + # add conv heads + self.conv_branch = self._add_conv_branch() + # add fc heads + self.fc_branch = self._add_fc_branch() + + out_dim_reg = 4 if self.reg_class_agnostic else 4 * self.num_classes + self.fc_reg = nn.Linear(self.conv_out_channels, out_dim_reg) + + self.fc_cls = nn.Linear(self.fc_out_channels, self.num_classes + 1) + self.relu = nn.ReLU() + + def _add_conv_branch(self) -> None: + """Add the fc branch which consists of a sequential of conv layers.""" + branch_convs = ModuleList() + for i in range(self.num_convs): + branch_convs.append( + Bottleneck( + inplanes=self.conv_out_channels, + planes=self.conv_out_channels // 4, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + return branch_convs + + def _add_fc_branch(self) -> None: + """Add the fc branch which consists of a sequential of fc layers.""" + branch_fcs = ModuleList() + for i in range(self.num_fcs): + fc_in_channels = ( + self.in_channels * + self.roi_feat_area if i == 0 else self.fc_out_channels) + branch_fcs.append(nn.Linear(fc_in_channels, self.fc_out_channels)) + return branch_fcs + + def forward(self, x_cls: Tensor, x_reg: Tensor) -> Tuple[Tensor]: + """Forward features from the upstream network. + + Args: + x_cls (Tensor): Classification features of rois + x_reg (Tensor): Regression features from the upstream network. + + Returns: + tuple: A tuple of classification scores and bbox prediction. + + - cls_score (Tensor): Classification score predictions of rois. + each roi predicts num_classes + 1 channels. + - bbox_pred (Tensor): BBox deltas predictions of rois. each roi + predicts 4 * num_classes channels. + """ + # conv head + x_conv = self.res_block(x_reg) + + for conv in self.conv_branch: + x_conv = conv(x_conv) + + if self.with_avg_pool: + x_conv = self.avg_pool(x_conv) + + x_conv = x_conv.view(x_conv.size(0), -1) + bbox_pred = self.fc_reg(x_conv) + + # fc head + x_fc = x_cls.view(x_cls.size(0), -1) + for fc in self.fc_branch: + x_fc = self.relu(fc(x_fc)) + + cls_score = self.fc_cls(x_fc) + + return cls_score, bbox_pred diff --git a/mmdetection/mmdet/models/roi_heads/bbox_heads/multi_instance_bbox_head.py b/mmdetection/mmdet/models/roi_heads/bbox_heads/multi_instance_bbox_head.py new file mode 100644 index 00000000..38e57d2e --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/bbox_heads/multi_instance_bbox_head.py @@ -0,0 +1,626 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple, Union + +import numpy as np +import torch +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor, nn + +from mmdet.models.roi_heads.bbox_heads.bbox_head import BBoxHead +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.models.utils import empty_instances +from mmdet.registry import MODELS +from mmdet.structures.bbox import bbox_overlaps + + +@MODELS.register_module() +class MultiInstanceBBoxHead(BBoxHead): + r"""Bbox head used in CrowdDet. + + .. code-block:: none + + /-> cls convs_1 -> cls fcs_1 -> cls_1 + |-- + | \-> reg convs_1 -> reg fcs_1 -> reg_1 + | + | /-> cls convs_2 -> cls fcs_2 -> cls_2 + shared convs -> shared fcs |-- + | \-> reg convs_2 -> reg fcs_2 -> reg_2 + | + | ... + | + | /-> cls convs_k -> cls fcs_k -> cls_k + |-- + \-> reg convs_k -> reg fcs_k -> reg_k + + + Args: + num_instance (int): The number of branches after shared fcs. + Defaults to 2. + with_refine (bool): Whether to use refine module. Defaults to False. + num_shared_convs (int): The number of shared convs. Defaults to 0. + num_shared_fcs (int): The number of shared fcs. Defaults to 2. + num_cls_convs (int): The number of cls convs. Defaults to 0. + num_cls_fcs (int): The number of cls fcs. Defaults to 0. + num_reg_convs (int): The number of reg convs. Defaults to 0. + num_reg_fcs (int): The number of reg fcs. Defaults to 0. + conv_out_channels (int): The number of conv out channels. + Defaults to 256. + fc_out_channels (int): The number of fc out channels. Defaults to 1024. + init_cfg (dict or list[dict], optional): Initialization config dict. + Defaults to None. + """ # noqa: W605 + + def __init__(self, + num_instance: int = 2, + with_refine: bool = False, + num_shared_convs: int = 0, + num_shared_fcs: int = 2, + num_cls_convs: int = 0, + num_cls_fcs: int = 0, + num_reg_convs: int = 0, + num_reg_fcs: int = 0, + conv_out_channels: int = 256, + fc_out_channels: int = 1024, + init_cfg: Optional[Union[dict, ConfigDict]] = None, + *args, + **kwargs) -> None: + super().__init__(*args, init_cfg=init_cfg, **kwargs) + assert (num_shared_convs + num_shared_fcs + num_cls_convs + + num_cls_fcs + num_reg_convs + num_reg_fcs > 0) + assert num_instance == 2, 'Currently only 2 instances are supported' + if num_cls_convs > 0 or num_reg_convs > 0: + assert num_shared_fcs == 0 + if not self.with_cls: + assert num_cls_convs == 0 and num_cls_fcs == 0 + if not self.with_reg: + assert num_reg_convs == 0 and num_reg_fcs == 0 + self.num_instance = num_instance + self.num_shared_convs = num_shared_convs + self.num_shared_fcs = num_shared_fcs + self.num_cls_convs = num_cls_convs + self.num_cls_fcs = num_cls_fcs + self.num_reg_convs = num_reg_convs + self.num_reg_fcs = num_reg_fcs + self.conv_out_channels = conv_out_channels + self.fc_out_channels = fc_out_channels + self.with_refine = with_refine + + # add shared convs and fcs + self.shared_convs, self.shared_fcs, last_layer_dim = \ + self._add_conv_fc_branch( + self.num_shared_convs, self.num_shared_fcs, self.in_channels, + True) + self.shared_out_channels = last_layer_dim + self.relu = nn.ReLU(inplace=True) + + if self.with_refine: + refine_model_cfg = { + 'type': 'Linear', + 'in_features': self.shared_out_channels + 20, + 'out_features': self.shared_out_channels + } + self.shared_fcs_ref = MODELS.build(refine_model_cfg) + self.fc_cls_ref = nn.ModuleList() + self.fc_reg_ref = nn.ModuleList() + + self.cls_convs = nn.ModuleList() + self.cls_fcs = nn.ModuleList() + self.reg_convs = nn.ModuleList() + self.reg_fcs = nn.ModuleList() + self.cls_last_dim = list() + self.reg_last_dim = list() + self.fc_cls = nn.ModuleList() + self.fc_reg = nn.ModuleList() + for k in range(self.num_instance): + # add cls specific branch + cls_convs, cls_fcs, cls_last_dim = self._add_conv_fc_branch( + self.num_cls_convs, self.num_cls_fcs, self.shared_out_channels) + self.cls_convs.append(cls_convs) + self.cls_fcs.append(cls_fcs) + self.cls_last_dim.append(cls_last_dim) + + # add reg specific branch + reg_convs, reg_fcs, reg_last_dim = self._add_conv_fc_branch( + self.num_reg_convs, self.num_reg_fcs, self.shared_out_channels) + self.reg_convs.append(reg_convs) + self.reg_fcs.append(reg_fcs) + self.reg_last_dim.append(reg_last_dim) + + if self.num_shared_fcs == 0 and not self.with_avg_pool: + if self.num_cls_fcs == 0: + self.cls_last_dim *= self.roi_feat_area + if self.num_reg_fcs == 0: + self.reg_last_dim *= self.roi_feat_area + + if self.with_cls: + if self.custom_cls_channels: + cls_channels = self.loss_cls.get_cls_channels( + self.num_classes) + else: + cls_channels = self.num_classes + 1 + cls_predictor_cfg_ = self.cls_predictor_cfg.copy() # deepcopy + cls_predictor_cfg_.update( + in_features=self.cls_last_dim[k], + out_features=cls_channels) + self.fc_cls.append(MODELS.build(cls_predictor_cfg_)) + if self.with_refine: + self.fc_cls_ref.append(MODELS.build(cls_predictor_cfg_)) + + if self.with_reg: + out_dim_reg = (4 if self.reg_class_agnostic else 4 * + self.num_classes) + reg_predictor_cfg_ = self.reg_predictor_cfg.copy() + reg_predictor_cfg_.update( + in_features=self.reg_last_dim[k], out_features=out_dim_reg) + self.fc_reg.append(MODELS.build(reg_predictor_cfg_)) + if self.with_refine: + self.fc_reg_ref.append(MODELS.build(reg_predictor_cfg_)) + + if init_cfg is None: + # when init_cfg is None, + # It has been set to + # [[dict(type='Normal', std=0.01, override=dict(name='fc_cls'))], + # [dict(type='Normal', std=0.001, override=dict(name='fc_reg'))] + # after `super(ConvFCBBoxHead, self).__init__()` + # we only need to append additional configuration + # for `shared_fcs`, `cls_fcs` and `reg_fcs` + self.init_cfg += [ + dict( + type='Xavier', + distribution='uniform', + override=[ + dict(name='shared_fcs'), + dict(name='cls_fcs'), + dict(name='reg_fcs') + ]) + ] + + def _add_conv_fc_branch(self, + num_branch_convs: int, + num_branch_fcs: int, + in_channels: int, + is_shared: bool = False) -> tuple: + """Add shared or separable branch. + + convs -> avg pool (optional) -> fcs + """ + last_layer_dim = in_channels + # add branch specific conv layers + branch_convs = nn.ModuleList() + if num_branch_convs > 0: + for i in range(num_branch_convs): + conv_in_channels = ( + last_layer_dim if i == 0 else self.conv_out_channels) + branch_convs.append( + ConvModule( + conv_in_channels, self.conv_out_channels, 3, + padding=1)) + last_layer_dim = self.conv_out_channels + # add branch specific fc layers + branch_fcs = nn.ModuleList() + if num_branch_fcs > 0: + # for shared branch, only consider self.with_avg_pool + # for separated branches, also consider self.num_shared_fcs + if (is_shared + or self.num_shared_fcs == 0) and not self.with_avg_pool: + last_layer_dim *= self.roi_feat_area + for i in range(num_branch_fcs): + fc_in_channels = ( + last_layer_dim if i == 0 else self.fc_out_channels) + branch_fcs.append( + nn.Linear(fc_in_channels, self.fc_out_channels)) + last_layer_dim = self.fc_out_channels + return branch_convs, branch_fcs, last_layer_dim + + def forward(self, x: Tuple[Tensor]) -> tuple: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of classification scores and bbox prediction. + + - cls_score (Tensor): Classification scores for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * num_classes. + - bbox_pred (Tensor): Box energies / deltas for all scale + levels, each is a 4D-tensor, the channels number is + num_base_priors * 4. + - cls_score_ref (Tensor): The cls_score after refine model. + - bbox_pred_ref (Tensor): The bbox_pred after refine model. + """ + # shared part + if self.num_shared_convs > 0: + for conv in self.shared_convs: + x = conv(x) + + if self.num_shared_fcs > 0: + if self.with_avg_pool: + x = self.avg_pool(x) + + x = x.flatten(1) + for fc in self.shared_fcs: + x = self.relu(fc(x)) + + x_cls = x + x_reg = x + # separate branches + cls_score = list() + bbox_pred = list() + for k in range(self.num_instance): + for conv in self.cls_convs[k]: + x_cls = conv(x_cls) + if x_cls.dim() > 2: + if self.with_avg_pool: + x_cls = self.avg_pool(x_cls) + x_cls = x_cls.flatten(1) + for fc in self.cls_fcs[k]: + x_cls = self.relu(fc(x_cls)) + + for conv in self.reg_convs[k]: + x_reg = conv(x_reg) + if x_reg.dim() > 2: + if self.with_avg_pool: + x_reg = self.avg_pool(x_reg) + x_reg = x_reg.flatten(1) + for fc in self.reg_fcs[k]: + x_reg = self.relu(fc(x_reg)) + + cls_score.append(self.fc_cls[k](x_cls) if self.with_cls else None) + bbox_pred.append(self.fc_reg[k](x_reg) if self.with_reg else None) + + if self.with_refine: + x_ref = x + cls_score_ref = list() + bbox_pred_ref = list() + for k in range(self.num_instance): + feat_ref = cls_score[k].softmax(dim=-1) + feat_ref = torch.cat((bbox_pred[k], feat_ref[:, 1][:, None]), + dim=1).repeat(1, 4) + feat_ref = torch.cat((x_ref, feat_ref), dim=1) + feat_ref = F.relu_(self.shared_fcs_ref(feat_ref)) + + cls_score_ref.append(self.fc_cls_ref[k](feat_ref)) + bbox_pred_ref.append(self.fc_reg_ref[k](feat_ref)) + + cls_score = torch.cat(cls_score, dim=1) + bbox_pred = torch.cat(bbox_pred, dim=1) + cls_score_ref = torch.cat(cls_score_ref, dim=1) + bbox_pred_ref = torch.cat(bbox_pred_ref, dim=1) + return cls_score, bbox_pred, cls_score_ref, bbox_pred_ref + + cls_score = torch.cat(cls_score, dim=1) + bbox_pred = torch.cat(bbox_pred, dim=1) + + return cls_score, bbox_pred + + def get_targets(self, + sampling_results: List[SamplingResult], + rcnn_train_cfg: ConfigDict, + concat: bool = True) -> tuple: + """Calculate the ground truth for all samples in a batch according to + the sampling_results. + + Almost the same as the implementation in bbox_head, we passed + additional parameters pos_inds_list and neg_inds_list to + `_get_targets_single` function. + + Args: + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + rcnn_train_cfg (obj:ConfigDict): `train_cfg` of RCNN. + concat (bool): Whether to concatenate the results of all + the images in a single batch. + + Returns: + Tuple[Tensor]: Ground truth for proposals in a single image. + Containing the following list of Tensors: + + - labels (list[Tensor],Tensor): Gt_labels for all proposals in a + batch, each tensor in list has shape (num_proposals,) when + `concat=False`, otherwise just a single tensor has shape + (num_all_proposals,). + - label_weights (list[Tensor]): Labels_weights for + all proposals in a batch, each tensor in list has shape + (num_proposals,) when `concat=False`, otherwise just a single + tensor has shape (num_all_proposals,). + - bbox_targets (list[Tensor],Tensor): Regression target for all + proposals in a batch, each tensor in list has shape + (num_proposals, 4) when `concat=False`, otherwise just a single + tensor has shape (num_all_proposals, 4), the last dimension 4 + represents [tl_x, tl_y, br_x, br_y]. + - bbox_weights (list[tensor],Tensor): Regression weights for + all proposals in a batch, each tensor in list has shape + (num_proposals, 4) when `concat=False`, otherwise just a + single tensor has shape (num_all_proposals, 4). + """ + labels = [] + bbox_targets = [] + bbox_weights = [] + label_weights = [] + for i in range(len(sampling_results)): + sample_bboxes = torch.cat([ + sampling_results[i].pos_gt_bboxes, + sampling_results[i].neg_gt_bboxes + ]) + sample_priors = sampling_results[i].priors + sample_priors = sample_priors.repeat(1, self.num_instance).reshape( + -1, 4) + sample_bboxes = sample_bboxes.reshape(-1, 4) + + if not self.reg_decoded_bbox: + _bbox_targets = self.bbox_coder.encode(sample_priors, + sample_bboxes) + else: + _bbox_targets = sample_priors + _bbox_targets = _bbox_targets.reshape(-1, self.num_instance * 4) + _bbox_weights = torch.ones(_bbox_targets.shape) + _labels = torch.cat([ + sampling_results[i].pos_gt_labels, + sampling_results[i].neg_gt_labels + ]) + _labels_weights = torch.ones(_labels.shape) + + bbox_targets.append(_bbox_targets) + bbox_weights.append(_bbox_weights) + labels.append(_labels) + label_weights.append(_labels_weights) + + if concat: + labels = torch.cat(labels, 0) + label_weights = torch.cat(label_weights, 0) + bbox_targets = torch.cat(bbox_targets, 0) + bbox_weights = torch.cat(bbox_weights, 0) + return labels, label_weights, bbox_targets, bbox_weights + + def loss(self, cls_score: Tensor, bbox_pred: Tensor, rois: Tensor, + labels: Tensor, label_weights: Tensor, bbox_targets: Tensor, + bbox_weights: Tensor, **kwargs) -> dict: + """Calculate the loss based on the network predictions and targets. + + Args: + cls_score (Tensor): Classification prediction results of all class, + has shape (batch_size * num_proposals_single_image, + (num_classes + 1) * k), k represents the number of prediction + boxes generated by each proposal box. + bbox_pred (Tensor): Regression prediction results, has shape + (batch_size * num_proposals_single_image, 4 * k), the last + dimension 4 represents [tl_x, tl_y, br_x, br_y]. + rois (Tensor): RoIs with the shape + (batch_size * num_proposals_single_image, 5) where the first + column indicates batch id of each RoI. + labels (Tensor): Gt_labels for all proposals in a batch, has + shape (batch_size * num_proposals_single_image, k). + label_weights (Tensor): Labels_weights for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, k). + bbox_targets (Tensor): Regression target for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, + 4 * k), the last dimension 4 represents [tl_x, tl_y, br_x, + br_y]. + bbox_weights (Tensor): Regression weights for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, + 4 * k). + + Returns: + dict: A dictionary of loss. + """ + losses = dict() + if bbox_pred.numel(): + loss_0 = self.emd_loss(bbox_pred[:, 0:4], cls_score[:, 0:2], + bbox_pred[:, 4:8], cls_score[:, 2:4], + bbox_targets, labels) + loss_1 = self.emd_loss(bbox_pred[:, 4:8], cls_score[:, 2:4], + bbox_pred[:, 0:4], cls_score[:, 0:2], + bbox_targets, labels) + loss = torch.cat([loss_0, loss_1], dim=1) + _, min_indices = loss.min(dim=1) + loss_emd = loss[torch.arange(loss.shape[0]), min_indices] + loss_emd = loss_emd.mean() + else: + loss_emd = bbox_pred.sum() + losses['loss_rcnn_emd'] = loss_emd + return losses + + def emd_loss(self, bbox_pred_0: Tensor, cls_score_0: Tensor, + bbox_pred_1: Tensor, cls_score_1: Tensor, targets: Tensor, + labels: Tensor) -> Tensor: + """Calculate the emd loss. + + Note: + This implementation is modified from https://github.com/Purkialo/ + CrowdDet/blob/master/lib/det_oprs/loss_opr.py + + Args: + bbox_pred_0 (Tensor): Part of regression prediction results, has + shape (batch_size * num_proposals_single_image, 4), the last + dimension 4 represents [tl_x, tl_y, br_x, br_y]. + cls_score_0 (Tensor): Part of classification prediction results, + has shape (batch_size * num_proposals_single_image, + (num_classes + 1)), where 1 represents the background. + bbox_pred_1 (Tensor): The other part of regression prediction + results, has shape (batch_size*num_proposals_single_image, 4). + cls_score_1 (Tensor):The other part of classification prediction + results, has shape (batch_size * num_proposals_single_image, + (num_classes + 1)). + targets (Tensor):Regression target for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, + 4 * k), the last dimension 4 represents [tl_x, tl_y, br_x, + br_y], k represents the number of prediction boxes generated + by each proposal box. + labels (Tensor): Gt_labels for all proposals in a batch, has + shape (batch_size * num_proposals_single_image, k). + + Returns: + torch.Tensor: The calculated loss. + """ + + bbox_pred = torch.cat([bbox_pred_0, bbox_pred_1], + dim=1).reshape(-1, bbox_pred_0.shape[-1]) + cls_score = torch.cat([cls_score_0, cls_score_1], + dim=1).reshape(-1, cls_score_0.shape[-1]) + targets = targets.reshape(-1, 4) + labels = labels.long().flatten() + + # masks + valid_masks = labels >= 0 + fg_masks = labels > 0 + + # multiple class + bbox_pred = bbox_pred.reshape(-1, self.num_classes, 4) + fg_gt_classes = labels[fg_masks] + bbox_pred = bbox_pred[fg_masks, fg_gt_classes - 1, :] + + # loss for regression + loss_bbox = self.loss_bbox(bbox_pred, targets[fg_masks]) + loss_bbox = loss_bbox.sum(dim=1) + + # loss for classification + labels = labels * valid_masks + loss_cls = self.loss_cls(cls_score, labels) + + loss_cls[fg_masks] = loss_cls[fg_masks] + loss_bbox + loss = loss_cls.reshape(-1, 2).sum(dim=1) + return loss.reshape(-1, 1) + + def _predict_by_feat_single( + self, + roi: Tensor, + cls_score: Tensor, + bbox_pred: Tensor, + img_meta: dict, + rescale: bool = False, + rcnn_test_cfg: Optional[ConfigDict] = None) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + roi (Tensor): Boxes to be transformed. Has shape (num_boxes, 5). + last dimension 5 arrange as (batch_index, x1, y1, x2, y2). + cls_score (Tensor): Box scores, has shape + (num_boxes, num_classes + 1). + bbox_pred (Tensor): Box energies / deltas. has shape + (num_boxes, num_classes * 4). + img_meta (dict): image information. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of Bbox Head. + Defaults to None + + Returns: + :obj:`InstanceData`: Detection results of each image. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + + cls_score = cls_score.reshape(-1, self.num_classes + 1) + bbox_pred = bbox_pred.reshape(-1, 4) + roi = roi.repeat_interleave(self.num_instance, dim=0) + + results = InstanceData() + if roi.shape[0] == 0: + return empty_instances([img_meta], + roi.device, + task_type='bbox', + instance_results=[results])[0] + + scores = cls_score.softmax(dim=-1) if cls_score is not None else None + img_shape = img_meta['img_shape'] + bboxes = self.bbox_coder.decode( + roi[..., 1:], bbox_pred, max_shape=img_shape) + + if rescale and bboxes.size(0) > 0: + assert img_meta.get('scale_factor') is not None + scale_factor = bboxes.new_tensor(img_meta['scale_factor']).repeat( + (1, 2)) + bboxes = (bboxes.view(bboxes.size(0), -1, 4) / scale_factor).view( + bboxes.size()[0], -1) + + if rcnn_test_cfg is None: + # This means that it is aug test. + # It needs to return the raw results without nms. + results.bboxes = bboxes + results.scores = scores + else: + roi_idx = np.tile( + np.arange(bboxes.shape[0] / self.num_instance)[:, None], + (1, self.num_instance)).reshape(-1, 1)[:, 0] + roi_idx = torch.from_numpy(roi_idx).to(bboxes.device).reshape( + -1, 1) + bboxes = torch.cat([bboxes, roi_idx], dim=1) + det_bboxes, det_scores = self.set_nms( + bboxes, scores[:, 1], rcnn_test_cfg.score_thr, + rcnn_test_cfg.nms['iou_threshold'], rcnn_test_cfg.max_per_img) + + results.bboxes = det_bboxes[:, :-1] + results.scores = det_scores + results.labels = torch.zeros_like(det_scores) + + return results + + @staticmethod + def set_nms(bboxes: Tensor, + scores: Tensor, + score_thr: float, + iou_threshold: float, + max_num: int = -1) -> Tuple[Tensor, Tensor]: + """NMS for multi-instance prediction. Please refer to + https://github.com/Purkialo/CrowdDet for more details. + + Args: + bboxes (Tensor): predict bboxes. + scores (Tensor): The score of each predict bbox. + score_thr (float): bbox threshold, bboxes with scores lower than it + will not be considered. + iou_threshold (float): IoU threshold to be considered as + conflicted. + max_num (int, optional): if there are more than max_num bboxes + after NMS, only top max_num will be kept. Default to -1. + + Returns: + Tuple[Tensor, Tensor]: (bboxes, scores). + """ + + bboxes = bboxes[scores > score_thr] + scores = scores[scores > score_thr] + + ordered_scores, order = scores.sort(descending=True) + ordered_bboxes = bboxes[order] + roi_idx = ordered_bboxes[:, -1] + + keep = torch.ones(len(ordered_bboxes)) == 1 + ruler = torch.arange(len(ordered_bboxes)) + + keep = keep.to(bboxes.device) + ruler = ruler.to(bboxes.device) + + while ruler.shape[0] > 0: + basement = ruler[0] + ruler = ruler[1:] + idx = roi_idx[basement] + # calculate the body overlap + basement_bbox = ordered_bboxes[:, :4][basement].reshape(-1, 4) + ruler_bbox = ordered_bboxes[:, :4][ruler].reshape(-1, 4) + overlap = bbox_overlaps(basement_bbox, ruler_bbox) + indices = torch.where(overlap > iou_threshold)[1] + loc = torch.where(roi_idx[ruler][indices] == idx) + # the mask won't change in the step + mask = keep[ruler[indices][loc]] + keep[ruler[indices]] = False + keep[ruler[indices][loc][mask]] = True + ruler[~keep[ruler]] = -1 + ruler = ruler[ruler > 0] + + keep = keep[order.sort()[1]] + return bboxes[keep][:max_num, :], scores[keep][:max_num] diff --git a/mmdetection/mmdet/models/roi_heads/bbox_heads/sabl_head.py b/mmdetection/mmdet/models/roi_heads/bbox_heads/sabl_head.py new file mode 100644 index 00000000..9a9ee6ab --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/bbox_heads/sabl_head.py @@ -0,0 +1,684 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Sequence, Tuple + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.layers import multiclass_nms +from mmdet.models.losses import accuracy +from mmdet.models.task_modules import SamplingResult +from mmdet.models.utils import multi_apply +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.utils import ConfigType, InstanceList, OptConfigType, OptMultiConfig +from .bbox_head import BBoxHead + + +@MODELS.register_module() +class SABLHead(BBoxHead): + """Side-Aware Boundary Localization (SABL) for RoI-Head. + + Side-Aware features are extracted by conv layers + with an attention mechanism. + Boundary Localization with Bucketing and Bucketing Guided Rescoring + are implemented in BucketingBBoxCoder. + + Please refer to https://arxiv.org/abs/1912.04260 for more details. + + Args: + cls_in_channels (int): Input channels of cls RoI feature. \ + Defaults to 256. + reg_in_channels (int): Input channels of reg RoI feature. \ + Defaults to 256. + roi_feat_size (int): Size of RoI features. Defaults to 7. + reg_feat_up_ratio (int): Upsample ratio of reg features. \ + Defaults to 2. + reg_pre_kernel (int): Kernel of 2D conv layers before \ + attention pooling. Defaults to 3. + reg_post_kernel (int): Kernel of 1D conv layers after \ + attention pooling. Defaults to 3. + reg_pre_num (int): Number of pre convs. Defaults to 2. + reg_post_num (int): Number of post convs. Defaults to 1. + num_classes (int): Number of classes in dataset. Defaults to 80. + cls_out_channels (int): Hidden channels in cls fcs. Defaults to 1024. + reg_offset_out_channels (int): Hidden and output channel \ + of reg offset branch. Defaults to 256. + reg_cls_out_channels (int): Hidden and output channel \ + of reg cls branch. Defaults to 256. + num_cls_fcs (int): Number of fcs for cls branch. Defaults to 1. + num_reg_fcs (int): Number of fcs for reg branch.. Defaults to 0. + reg_class_agnostic (bool): Class agnostic regression or not. \ + Defaults to True. + norm_cfg (dict): Config of norm layers. Defaults to None. + bbox_coder (dict): Config of bbox coder. Defaults 'BucketingBBoxCoder'. + loss_cls (dict): Config of classification loss. + loss_bbox_cls (dict): Config of classification loss for bbox branch. + loss_bbox_reg (dict): Config of regression loss for bbox branch. + init_cfg (dict or list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + num_classes: int, + cls_in_channels: int = 256, + reg_in_channels: int = 256, + roi_feat_size: int = 7, + reg_feat_up_ratio: int = 2, + reg_pre_kernel: int = 3, + reg_post_kernel: int = 3, + reg_pre_num: int = 2, + reg_post_num: int = 1, + cls_out_channels: int = 1024, + reg_offset_out_channels: int = 256, + reg_cls_out_channels: int = 256, + num_cls_fcs: int = 1, + num_reg_fcs: int = 0, + reg_class_agnostic: bool = True, + norm_cfg: OptConfigType = None, + bbox_coder: ConfigType = dict( + type='BucketingBBoxCoder', + num_buckets=14, + scale_factor=1.7), + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + loss_bbox_cls: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0), + loss_bbox_reg: ConfigType = dict( + type='SmoothL1Loss', beta=0.1, loss_weight=1.0), + init_cfg: OptMultiConfig = None) -> None: + super(BBoxHead, self).__init__(init_cfg=init_cfg) + self.cls_in_channels = cls_in_channels + self.reg_in_channels = reg_in_channels + self.roi_feat_size = roi_feat_size + self.reg_feat_up_ratio = int(reg_feat_up_ratio) + self.num_buckets = bbox_coder['num_buckets'] + assert self.reg_feat_up_ratio // 2 >= 1 + self.up_reg_feat_size = roi_feat_size * self.reg_feat_up_ratio + assert self.up_reg_feat_size == bbox_coder['num_buckets'] + self.reg_pre_kernel = reg_pre_kernel + self.reg_post_kernel = reg_post_kernel + self.reg_pre_num = reg_pre_num + self.reg_post_num = reg_post_num + self.num_classes = num_classes + self.cls_out_channels = cls_out_channels + self.reg_offset_out_channels = reg_offset_out_channels + self.reg_cls_out_channels = reg_cls_out_channels + self.num_cls_fcs = num_cls_fcs + self.num_reg_fcs = num_reg_fcs + self.reg_class_agnostic = reg_class_agnostic + assert self.reg_class_agnostic + self.norm_cfg = norm_cfg + + self.bbox_coder = TASK_UTILS.build(bbox_coder) + self.loss_cls = MODELS.build(loss_cls) + self.loss_bbox_cls = MODELS.build(loss_bbox_cls) + self.loss_bbox_reg = MODELS.build(loss_bbox_reg) + + self.cls_fcs = self._add_fc_branch(self.num_cls_fcs, + self.cls_in_channels, + self.roi_feat_size, + self.cls_out_channels) + + self.side_num = int(np.ceil(self.num_buckets / 2)) + + if self.reg_feat_up_ratio > 1: + self.upsample_x = nn.ConvTranspose1d( + reg_in_channels, + reg_in_channels, + self.reg_feat_up_ratio, + stride=self.reg_feat_up_ratio) + self.upsample_y = nn.ConvTranspose1d( + reg_in_channels, + reg_in_channels, + self.reg_feat_up_ratio, + stride=self.reg_feat_up_ratio) + + self.reg_pre_convs = nn.ModuleList() + for i in range(self.reg_pre_num): + reg_pre_conv = ConvModule( + reg_in_channels, + reg_in_channels, + kernel_size=reg_pre_kernel, + padding=reg_pre_kernel // 2, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU')) + self.reg_pre_convs.append(reg_pre_conv) + + self.reg_post_conv_xs = nn.ModuleList() + for i in range(self.reg_post_num): + reg_post_conv_x = ConvModule( + reg_in_channels, + reg_in_channels, + kernel_size=(1, reg_post_kernel), + padding=(0, reg_post_kernel // 2), + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU')) + self.reg_post_conv_xs.append(reg_post_conv_x) + self.reg_post_conv_ys = nn.ModuleList() + for i in range(self.reg_post_num): + reg_post_conv_y = ConvModule( + reg_in_channels, + reg_in_channels, + kernel_size=(reg_post_kernel, 1), + padding=(reg_post_kernel // 2, 0), + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU')) + self.reg_post_conv_ys.append(reg_post_conv_y) + + self.reg_conv_att_x = nn.Conv2d(reg_in_channels, 1, 1) + self.reg_conv_att_y = nn.Conv2d(reg_in_channels, 1, 1) + + self.fc_cls = nn.Linear(self.cls_out_channels, self.num_classes + 1) + self.relu = nn.ReLU(inplace=True) + + self.reg_cls_fcs = self._add_fc_branch(self.num_reg_fcs, + self.reg_in_channels, 1, + self.reg_cls_out_channels) + self.reg_offset_fcs = self._add_fc_branch(self.num_reg_fcs, + self.reg_in_channels, 1, + self.reg_offset_out_channels) + self.fc_reg_cls = nn.Linear(self.reg_cls_out_channels, 1) + self.fc_reg_offset = nn.Linear(self.reg_offset_out_channels, 1) + + if init_cfg is None: + self.init_cfg = [ + dict( + type='Xavier', + layer='Linear', + distribution='uniform', + override=[ + dict(type='Normal', name='reg_conv_att_x', std=0.01), + dict(type='Normal', name='reg_conv_att_y', std=0.01), + dict(type='Normal', name='fc_reg_cls', std=0.01), + dict(type='Normal', name='fc_cls', std=0.01), + dict(type='Normal', name='fc_reg_offset', std=0.001) + ]) + ] + if self.reg_feat_up_ratio > 1: + self.init_cfg += [ + dict( + type='Kaiming', + distribution='normal', + override=[ + dict(name='upsample_x'), + dict(name='upsample_y') + ]) + ] + + def _add_fc_branch(self, num_branch_fcs: int, in_channels: int, + roi_feat_size: int, + fc_out_channels: int) -> nn.ModuleList: + """build fc layers.""" + in_channels = in_channels * roi_feat_size * roi_feat_size + branch_fcs = nn.ModuleList() + for i in range(num_branch_fcs): + fc_in_channels = (in_channels if i == 0 else fc_out_channels) + branch_fcs.append(nn.Linear(fc_in_channels, fc_out_channels)) + return branch_fcs + + def cls_forward(self, cls_x: Tensor) -> Tensor: + """forward of classification fc layers.""" + cls_x = cls_x.view(cls_x.size(0), -1) + for fc in self.cls_fcs: + cls_x = self.relu(fc(cls_x)) + cls_score = self.fc_cls(cls_x) + return cls_score + + def attention_pool(self, reg_x: Tensor) -> tuple: + """Extract direction-specific features fx and fy with attention + methanism.""" + reg_fx = reg_x + reg_fy = reg_x + reg_fx_att = self.reg_conv_att_x(reg_fx).sigmoid() + reg_fy_att = self.reg_conv_att_y(reg_fy).sigmoid() + reg_fx_att = reg_fx_att / reg_fx_att.sum(dim=2).unsqueeze(2) + reg_fy_att = reg_fy_att / reg_fy_att.sum(dim=3).unsqueeze(3) + reg_fx = (reg_fx * reg_fx_att).sum(dim=2) + reg_fy = (reg_fy * reg_fy_att).sum(dim=3) + return reg_fx, reg_fy + + def side_aware_feature_extractor(self, reg_x: Tensor) -> tuple: + """Refine and extract side-aware features without split them.""" + for reg_pre_conv in self.reg_pre_convs: + reg_x = reg_pre_conv(reg_x) + reg_fx, reg_fy = self.attention_pool(reg_x) + + if self.reg_post_num > 0: + reg_fx = reg_fx.unsqueeze(2) + reg_fy = reg_fy.unsqueeze(3) + for i in range(self.reg_post_num): + reg_fx = self.reg_post_conv_xs[i](reg_fx) + reg_fy = self.reg_post_conv_ys[i](reg_fy) + reg_fx = reg_fx.squeeze(2) + reg_fy = reg_fy.squeeze(3) + if self.reg_feat_up_ratio > 1: + reg_fx = self.relu(self.upsample_x(reg_fx)) + reg_fy = self.relu(self.upsample_y(reg_fy)) + reg_fx = torch.transpose(reg_fx, 1, 2) + reg_fy = torch.transpose(reg_fy, 1, 2) + return reg_fx.contiguous(), reg_fy.contiguous() + + def reg_pred(self, x: Tensor, offset_fcs: nn.ModuleList, + cls_fcs: nn.ModuleList) -> tuple: + """Predict bucketing estimation (cls_pred) and fine regression (offset + pred) with side-aware features.""" + x_offset = x.view(-1, self.reg_in_channels) + x_cls = x.view(-1, self.reg_in_channels) + + for fc in offset_fcs: + x_offset = self.relu(fc(x_offset)) + for fc in cls_fcs: + x_cls = self.relu(fc(x_cls)) + offset_pred = self.fc_reg_offset(x_offset) + cls_pred = self.fc_reg_cls(x_cls) + + offset_pred = offset_pred.view(x.size(0), -1) + cls_pred = cls_pred.view(x.size(0), -1) + + return offset_pred, cls_pred + + def side_aware_split(self, feat: Tensor) -> Tensor: + """Split side-aware features aligned with orders of bucketing + targets.""" + l_end = int(np.ceil(self.up_reg_feat_size / 2)) + r_start = int(np.floor(self.up_reg_feat_size / 2)) + feat_fl = feat[:, :l_end] + feat_fr = feat[:, r_start:].flip(dims=(1, )) + feat_fl = feat_fl.contiguous() + feat_fr = feat_fr.contiguous() + feat = torch.cat([feat_fl, feat_fr], dim=-1) + return feat + + def bbox_pred_split(self, bbox_pred: tuple, + num_proposals_per_img: Sequence[int]) -> tuple: + """Split batch bbox prediction back to each image.""" + bucket_cls_preds, bucket_offset_preds = bbox_pred + bucket_cls_preds = bucket_cls_preds.split(num_proposals_per_img, 0) + bucket_offset_preds = bucket_offset_preds.split( + num_proposals_per_img, 0) + bbox_pred = tuple(zip(bucket_cls_preds, bucket_offset_preds)) + return bbox_pred + + def reg_forward(self, reg_x: Tensor) -> tuple: + """forward of regression branch.""" + outs = self.side_aware_feature_extractor(reg_x) + edge_offset_preds = [] + edge_cls_preds = [] + reg_fx = outs[0] + reg_fy = outs[1] + offset_pred_x, cls_pred_x = self.reg_pred(reg_fx, self.reg_offset_fcs, + self.reg_cls_fcs) + offset_pred_y, cls_pred_y = self.reg_pred(reg_fy, self.reg_offset_fcs, + self.reg_cls_fcs) + offset_pred_x = self.side_aware_split(offset_pred_x) + offset_pred_y = self.side_aware_split(offset_pred_y) + cls_pred_x = self.side_aware_split(cls_pred_x) + cls_pred_y = self.side_aware_split(cls_pred_y) + edge_offset_preds = torch.cat([offset_pred_x, offset_pred_y], dim=-1) + edge_cls_preds = torch.cat([cls_pred_x, cls_pred_y], dim=-1) + + return edge_cls_preds, edge_offset_preds + + def forward(self, x: Tensor) -> tuple: + """Forward features from the upstream network.""" + bbox_pred = self.reg_forward(x) + cls_score = self.cls_forward(x) + + return cls_score, bbox_pred + + def get_targets(self, + sampling_results: List[SamplingResult], + rcnn_train_cfg: ConfigDict, + concat: bool = True) -> tuple: + """Calculate the ground truth for all samples in a batch according to + the sampling_results.""" + pos_proposals = [res.pos_bboxes for res in sampling_results] + neg_proposals = [res.neg_bboxes for res in sampling_results] + pos_gt_bboxes = [res.pos_gt_bboxes for res in sampling_results] + pos_gt_labels = [res.pos_gt_labels for res in sampling_results] + cls_reg_targets = self.bucket_target( + pos_proposals, + neg_proposals, + pos_gt_bboxes, + pos_gt_labels, + rcnn_train_cfg, + concat=concat) + (labels, label_weights, bucket_cls_targets, bucket_cls_weights, + bucket_offset_targets, bucket_offset_weights) = cls_reg_targets + return (labels, label_weights, (bucket_cls_targets, + bucket_offset_targets), + (bucket_cls_weights, bucket_offset_weights)) + + def bucket_target(self, + pos_proposals_list: list, + neg_proposals_list: list, + pos_gt_bboxes_list: list, + pos_gt_labels_list: list, + rcnn_train_cfg: ConfigDict, + concat: bool = True) -> tuple: + """Compute bucketing estimation targets and fine regression targets for + a batch of images.""" + (labels, label_weights, bucket_cls_targets, bucket_cls_weights, + bucket_offset_targets, bucket_offset_weights) = multi_apply( + self._bucket_target_single, + pos_proposals_list, + neg_proposals_list, + pos_gt_bboxes_list, + pos_gt_labels_list, + cfg=rcnn_train_cfg) + + if concat: + labels = torch.cat(labels, 0) + label_weights = torch.cat(label_weights, 0) + bucket_cls_targets = torch.cat(bucket_cls_targets, 0) + bucket_cls_weights = torch.cat(bucket_cls_weights, 0) + bucket_offset_targets = torch.cat(bucket_offset_targets, 0) + bucket_offset_weights = torch.cat(bucket_offset_weights, 0) + return (labels, label_weights, bucket_cls_targets, bucket_cls_weights, + bucket_offset_targets, bucket_offset_weights) + + def _bucket_target_single(self, pos_proposals: Tensor, + neg_proposals: Tensor, pos_gt_bboxes: Tensor, + pos_gt_labels: Tensor, cfg: ConfigDict) -> tuple: + """Compute bucketing estimation targets and fine regression targets for + a single image. + + Args: + pos_proposals (Tensor): positive proposals of a single image, + Shape (n_pos, 4) + neg_proposals (Tensor): negative proposals of a single image, + Shape (n_neg, 4). + pos_gt_bboxes (Tensor): gt bboxes assigned to positive proposals + of a single image, Shape (n_pos, 4). + pos_gt_labels (Tensor): gt labels assigned to positive proposals + of a single image, Shape (n_pos, ). + cfg (dict): Config of calculating targets + + Returns: + tuple: + + - labels (Tensor): Labels in a single image. Shape (n,). + - label_weights (Tensor): Label weights in a single image. + Shape (n,) + - bucket_cls_targets (Tensor): Bucket cls targets in + a single image. Shape (n, num_buckets*2). + - bucket_cls_weights (Tensor): Bucket cls weights in + a single image. Shape (n, num_buckets*2). + - bucket_offset_targets (Tensor): Bucket offset targets + in a single image. Shape (n, num_buckets*2). + - bucket_offset_targets (Tensor): Bucket offset weights + in a single image. Shape (n, num_buckets*2). + """ + num_pos = pos_proposals.size(0) + num_neg = neg_proposals.size(0) + num_samples = num_pos + num_neg + labels = pos_gt_bboxes.new_full((num_samples, ), + self.num_classes, + dtype=torch.long) + label_weights = pos_proposals.new_zeros(num_samples) + bucket_cls_targets = pos_proposals.new_zeros(num_samples, + 4 * self.side_num) + bucket_cls_weights = pos_proposals.new_zeros(num_samples, + 4 * self.side_num) + bucket_offset_targets = pos_proposals.new_zeros( + num_samples, 4 * self.side_num) + bucket_offset_weights = pos_proposals.new_zeros( + num_samples, 4 * self.side_num) + if num_pos > 0: + labels[:num_pos] = pos_gt_labels + label_weights[:num_pos] = 1.0 + (pos_bucket_offset_targets, pos_bucket_offset_weights, + pos_bucket_cls_targets, + pos_bucket_cls_weights) = self.bbox_coder.encode( + pos_proposals, pos_gt_bboxes) + bucket_cls_targets[:num_pos, :] = pos_bucket_cls_targets + bucket_cls_weights[:num_pos, :] = pos_bucket_cls_weights + bucket_offset_targets[:num_pos, :] = pos_bucket_offset_targets + bucket_offset_weights[:num_pos, :] = pos_bucket_offset_weights + if num_neg > 0: + label_weights[-num_neg:] = 1.0 + return (labels, label_weights, bucket_cls_targets, bucket_cls_weights, + bucket_offset_targets, bucket_offset_weights) + + def loss(self, + cls_score: Tensor, + bbox_pred: Tuple[Tensor, Tensor], + rois: Tensor, + labels: Tensor, + label_weights: Tensor, + bbox_targets: Tuple[Tensor, Tensor], + bbox_weights: Tuple[Tensor, Tensor], + reduction_override: Optional[str] = None) -> dict: + """Calculate the loss based on the network predictions and targets. + + Args: + cls_score (Tensor): Classification prediction + results of all class, has shape + (batch_size * num_proposals_single_image, num_classes) + bbox_pred (Tensor): A tuple of regression prediction results + containing `bucket_cls_preds and` `bucket_offset_preds`. + rois (Tensor): RoIs with the shape + (batch_size * num_proposals_single_image, 5) where the first + column indicates batch id of each RoI. + labels (Tensor): Gt_labels for all proposals in a batch, has + shape (batch_size * num_proposals_single_image, ). + label_weights (Tensor): Labels_weights for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, ). + bbox_targets (Tuple[Tensor, Tensor]): A tuple of regression target + containing `bucket_cls_targets` and `bucket_offset_targets`. + the last dimension 4 represents [tl_x, tl_y, br_x, br_y]. + bbox_weights (Tuple[Tensor, Tensor]): A tuple of regression + weights containing `bucket_cls_weights` and + `bucket_offset_weights`. + reduction_override (str, optional): The reduction + method used to override the original reduction + method of the loss. Options are "none", + "mean" and "sum". Defaults to None, + + Returns: + dict: A dictionary of loss. + """ + losses = dict() + if cls_score is not None: + avg_factor = max(torch.sum(label_weights > 0).float().item(), 1.) + losses['loss_cls'] = self.loss_cls( + cls_score, + labels, + label_weights, + avg_factor=avg_factor, + reduction_override=reduction_override) + losses['acc'] = accuracy(cls_score, labels) + + if bbox_pred is not None: + bucket_cls_preds, bucket_offset_preds = bbox_pred + bucket_cls_targets, bucket_offset_targets = bbox_targets + bucket_cls_weights, bucket_offset_weights = bbox_weights + # edge cls + bucket_cls_preds = bucket_cls_preds.view(-1, self.side_num) + bucket_cls_targets = bucket_cls_targets.view(-1, self.side_num) + bucket_cls_weights = bucket_cls_weights.view(-1, self.side_num) + losses['loss_bbox_cls'] = self.loss_bbox_cls( + bucket_cls_preds, + bucket_cls_targets, + bucket_cls_weights, + avg_factor=bucket_cls_targets.size(0), + reduction_override=reduction_override) + + losses['loss_bbox_reg'] = self.loss_bbox_reg( + bucket_offset_preds, + bucket_offset_targets, + bucket_offset_weights, + avg_factor=bucket_offset_targets.size(0), + reduction_override=reduction_override) + + return losses + + def _predict_by_feat_single( + self, + roi: Tensor, + cls_score: Tensor, + bbox_pred: Tuple[Tensor, Tensor], + img_meta: dict, + rescale: bool = False, + rcnn_test_cfg: Optional[ConfigDict] = None) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + roi (Tensor): Boxes to be transformed. Has shape (num_boxes, 5). + last dimension 5 arrange as (batch_index, x1, y1, x2, y2). + cls_score (Tensor): Box scores, has shape + (num_boxes, num_classes + 1). + bbox_pred (Tuple[Tensor, Tensor]): Box cls preds and offset preds. + img_meta (dict): image information. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of Bbox Head. + Defaults to None + + Returns: + :obj:`InstanceData`: Detection results of each image + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + results = InstanceData() + if isinstance(cls_score, list): + cls_score = sum(cls_score) / float(len(cls_score)) + scores = F.softmax(cls_score, dim=1) if cls_score is not None else None + img_shape = img_meta['img_shape'] + if bbox_pred is not None: + bboxes, confidences = self.bbox_coder.decode( + roi[:, 1:], bbox_pred, img_shape) + else: + bboxes = roi[:, 1:].clone() + confidences = None + if img_shape is not None: + bboxes[:, [0, 2]].clamp_(min=0, max=img_shape[1] - 1) + bboxes[:, [1, 3]].clamp_(min=0, max=img_shape[0] - 1) + + if rescale and bboxes.size(0) > 0: + assert img_meta.get('scale_factor') is not None + scale_factor = bboxes.new_tensor(img_meta['scale_factor']).repeat( + (1, 2)) + bboxes = (bboxes.view(bboxes.size(0), -1, 4) / scale_factor).view( + bboxes.size()[0], -1) + + if rcnn_test_cfg is None: + results.bboxes = bboxes + results.scores = scores + else: + det_bboxes, det_labels = multiclass_nms( + bboxes, + scores, + rcnn_test_cfg.score_thr, + rcnn_test_cfg.nms, + rcnn_test_cfg.max_per_img, + score_factors=confidences) + results.bboxes = det_bboxes[:, :4] + results.scores = det_bboxes[:, -1] + results.labels = det_labels + return results + + def refine_bboxes(self, sampling_results: List[SamplingResult], + bbox_results: dict, + batch_img_metas: List[dict]) -> InstanceList: + """Refine bboxes during training. + + Args: + sampling_results (List[:obj:`SamplingResult`]): Sampling results. + bbox_results (dict): Usually is a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `rois` (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + - `bbox_targets` (tuple): Ground truth for proposals in a + single image. Containing the following list of Tensors: + (labels, label_weights, bbox_targets, bbox_weights) + batch_img_metas (List[dict]): List of image information. + + Returns: + list[:obj:`InstanceData`]: Refined bboxes of each image. + """ + pos_is_gts = [res.pos_is_gt for res in sampling_results] + # bbox_targets is a tuple + labels = bbox_results['bbox_targets'][0] + cls_scores = bbox_results['cls_score'] + rois = bbox_results['rois'] + bbox_preds = bbox_results['bbox_pred'] + + if cls_scores.numel() == 0: + return None + + labels = torch.where(labels == self.num_classes, + cls_scores[:, :-1].argmax(1), labels) + + img_ids = rois[:, 0].long().unique(sorted=True) + assert img_ids.numel() <= len(batch_img_metas) + + results_list = [] + for i in range(len(batch_img_metas)): + inds = torch.nonzero( + rois[:, 0] == i, as_tuple=False).squeeze(dim=1) + num_rois = inds.numel() + + bboxes_ = rois[inds, 1:] + label_ = labels[inds] + edge_cls_preds, edge_offset_preds = bbox_preds + edge_cls_preds_ = edge_cls_preds[inds] + edge_offset_preds_ = edge_offset_preds[inds] + bbox_pred_ = (edge_cls_preds_, edge_offset_preds_) + img_meta_ = batch_img_metas[i] + pos_is_gts_ = pos_is_gts[i] + + bboxes = self.regress_by_class(bboxes_, label_, bbox_pred_, + img_meta_) + # filter gt bboxes + pos_keep = 1 - pos_is_gts_ + keep_inds = pos_is_gts_.new_ones(num_rois) + keep_inds[:len(pos_is_gts_)] = pos_keep + results = InstanceData(bboxes=bboxes[keep_inds.type(torch.bool)]) + results_list.append(results) + + return results_list + + def regress_by_class(self, rois: Tensor, label: Tensor, bbox_pred: tuple, + img_meta: dict) -> Tensor: + """Regress the bbox for the predicted class. Used in Cascade R-CNN. + + Args: + rois (Tensor): shape (n, 4) or (n, 5) + label (Tensor): shape (n, ) + bbox_pred (Tuple[Tensor]): shape [(n, num_buckets *2), \ + (n, num_buckets *2)] + img_meta (dict): Image meta info. + + Returns: + Tensor: Regressed bboxes, the same shape as input rois. + """ + assert rois.size(1) == 4 or rois.size(1) == 5 + + if rois.size(1) == 4: + new_rois, _ = self.bbox_coder.decode(rois, bbox_pred, + img_meta['img_shape']) + else: + bboxes, _ = self.bbox_coder.decode(rois[:, 1:], bbox_pred, + img_meta['img_shape']) + new_rois = torch.cat((rois[:, [0]], bboxes), dim=1) + + return new_rois diff --git a/mmdetection/mmdet/models/roi_heads/bbox_heads/scnet_bbox_head.py b/mmdetection/mmdet/models/roi_heads/bbox_heads/scnet_bbox_head.py new file mode 100644 index 00000000..790b08fb --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/bbox_heads/scnet_bbox_head.py @@ -0,0 +1,101 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple, Union + +from torch import Tensor + +from mmdet.registry import MODELS +from .convfc_bbox_head import ConvFCBBoxHead + + +@MODELS.register_module() +class SCNetBBoxHead(ConvFCBBoxHead): + """BBox head for `SCNet `_. + + This inherits ``ConvFCBBoxHead`` with modified forward() function, allow us + to get intermediate shared feature. + """ + + def _forward_shared(self, x: Tensor) -> Tensor: + """Forward function for shared part. + + Args: + x (Tensor): Input feature. + + Returns: + Tensor: Shared feature. + """ + if self.num_shared_convs > 0: + for conv in self.shared_convs: + x = conv(x) + + if self.num_shared_fcs > 0: + if self.with_avg_pool: + x = self.avg_pool(x) + + x = x.flatten(1) + + for fc in self.shared_fcs: + x = self.relu(fc(x)) + + return x + + def _forward_cls_reg(self, x: Tensor) -> Tuple[Tensor]: + """Forward function for classification and regression parts. + + Args: + x (Tensor): Input feature. + + Returns: + tuple[Tensor]: + + - cls_score (Tensor): classification prediction. + - bbox_pred (Tensor): bbox prediction. + """ + x_cls = x + x_reg = x + + for conv in self.cls_convs: + x_cls = conv(x_cls) + if x_cls.dim() > 2: + if self.with_avg_pool: + x_cls = self.avg_pool(x_cls) + x_cls = x_cls.flatten(1) + for fc in self.cls_fcs: + x_cls = self.relu(fc(x_cls)) + + for conv in self.reg_convs: + x_reg = conv(x_reg) + if x_reg.dim() > 2: + if self.with_avg_pool: + x_reg = self.avg_pool(x_reg) + x_reg = x_reg.flatten(1) + for fc in self.reg_fcs: + x_reg = self.relu(fc(x_reg)) + + cls_score = self.fc_cls(x_cls) if self.with_cls else None + bbox_pred = self.fc_reg(x_reg) if self.with_reg else None + + return cls_score, bbox_pred + + def forward( + self, + x: Tensor, + return_shared_feat: bool = False) -> Union[Tensor, Tuple[Tensor]]: + """Forward function. + + Args: + x (Tensor): input features + return_shared_feat (bool): If True, return cls-reg-shared feature. + + Return: + out (tuple[Tensor]): contain ``cls_score`` and ``bbox_pred``, + if ``return_shared_feat`` is True, append ``x_shared`` to the + returned tuple. + """ + x_shared = self._forward_shared(x) + out = self._forward_cls_reg(x_shared) + + if return_shared_feat: + out += (x_shared, ) + + return out diff --git a/mmdetection/mmdet/models/roi_heads/cascade_roi_head.py b/mmdetection/mmdet/models/roi_heads/cascade_roi_head.py new file mode 100644 index 00000000..81db6711 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/cascade_roi_head.py @@ -0,0 +1,568 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Sequence, Tuple, Union + +import torch +import torch.nn as nn +from mmengine.model import ModuleList +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.models.test_time_augs import merge_aug_masks +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi, get_box_tensor +from mmdet.utils import (ConfigType, InstanceList, MultiConfig, OptConfigType, + OptMultiConfig) +from ..utils.misc import empty_instances, unpack_gt_instances +from .base_roi_head import BaseRoIHead + + +@MODELS.register_module() +class CascadeRoIHead(BaseRoIHead): + """Cascade roi head including one bbox head and one mask head. + + https://arxiv.org/abs/1712.00726 + """ + + def __init__(self, + num_stages: int, + stage_loss_weights: Union[List[float], Tuple[float]], + bbox_roi_extractor: OptMultiConfig = None, + bbox_head: OptMultiConfig = None, + mask_roi_extractor: OptMultiConfig = None, + mask_head: OptMultiConfig = None, + shared_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + assert bbox_roi_extractor is not None + assert bbox_head is not None + assert shared_head is None, \ + 'Shared head is not supported in Cascade RCNN anymore' + + self.num_stages = num_stages + self.stage_loss_weights = stage_loss_weights + super().__init__( + bbox_roi_extractor=bbox_roi_extractor, + bbox_head=bbox_head, + mask_roi_extractor=mask_roi_extractor, + mask_head=mask_head, + shared_head=shared_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + init_cfg=init_cfg) + + def init_bbox_head(self, bbox_roi_extractor: MultiConfig, + bbox_head: MultiConfig) -> None: + """Initialize box head and box roi extractor. + + Args: + bbox_roi_extractor (:obj:`ConfigDict`, dict or list): + Config of box roi extractor. + bbox_head (:obj:`ConfigDict`, dict or list): Config + of box in box head. + """ + self.bbox_roi_extractor = ModuleList() + self.bbox_head = ModuleList() + if not isinstance(bbox_roi_extractor, list): + bbox_roi_extractor = [ + bbox_roi_extractor for _ in range(self.num_stages) + ] + if not isinstance(bbox_head, list): + bbox_head = [bbox_head for _ in range(self.num_stages)] + assert len(bbox_roi_extractor) == len(bbox_head) == self.num_stages + for roi_extractor, head in zip(bbox_roi_extractor, bbox_head): + self.bbox_roi_extractor.append(MODELS.build(roi_extractor)) + self.bbox_head.append(MODELS.build(head)) + + def init_mask_head(self, mask_roi_extractor: MultiConfig, + mask_head: MultiConfig) -> None: + """Initialize mask head and mask roi extractor. + + Args: + mask_head (dict): Config of mask in mask head. + mask_roi_extractor (:obj:`ConfigDict`, dict or list): + Config of mask roi extractor. + """ + self.mask_head = nn.ModuleList() + if not isinstance(mask_head, list): + mask_head = [mask_head for _ in range(self.num_stages)] + assert len(mask_head) == self.num_stages + for head in mask_head: + self.mask_head.append(MODELS.build(head)) + if mask_roi_extractor is not None: + self.share_roi_extractor = False + self.mask_roi_extractor = ModuleList() + if not isinstance(mask_roi_extractor, list): + mask_roi_extractor = [ + mask_roi_extractor for _ in range(self.num_stages) + ] + assert len(mask_roi_extractor) == self.num_stages + for roi_extractor in mask_roi_extractor: + self.mask_roi_extractor.append(MODELS.build(roi_extractor)) + else: + self.share_roi_extractor = True + self.mask_roi_extractor = self.bbox_roi_extractor + + def init_assigner_sampler(self) -> None: + """Initialize assigner and sampler for each stage.""" + self.bbox_assigner = [] + self.bbox_sampler = [] + if self.train_cfg is not None: + for idx, rcnn_train_cfg in enumerate(self.train_cfg): + self.bbox_assigner.append( + TASK_UTILS.build(rcnn_train_cfg.assigner)) + self.current_stage = idx + self.bbox_sampler.append( + TASK_UTILS.build( + rcnn_train_cfg.sampler, + default_args=dict(context=self))) + + def _bbox_forward(self, stage: int, x: Tuple[Tensor], + rois: Tensor) -> dict: + """Box head forward function used in both training and testing. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + """ + bbox_roi_extractor = self.bbox_roi_extractor[stage] + bbox_head = self.bbox_head[stage] + bbox_feats = bbox_roi_extractor(x[:bbox_roi_extractor.num_inputs], + rois) + # do not support caffe_c4 model anymore + cls_score, bbox_pred = bbox_head(bbox_feats) + + bbox_results = dict( + cls_score=cls_score, bbox_pred=bbox_pred, bbox_feats=bbox_feats) + return bbox_results + + def bbox_loss(self, stage: int, x: Tuple[Tensor], + sampling_results: List[SamplingResult]) -> dict: + """Run forward function and calculate loss for box head in training. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + + Returns: + dict: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + - `rois` (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + - `bbox_targets` (tuple): Ground truth for proposals in a + single image. Containing the following list of Tensors: + (labels, label_weights, bbox_targets, bbox_weights) + """ + bbox_head = self.bbox_head[stage] + rois = bbox2roi([res.priors for res in sampling_results]) + bbox_results = self._bbox_forward(stage, x, rois) + bbox_results.update(rois=rois) + + bbox_loss_and_target = bbox_head.loss_and_target( + cls_score=bbox_results['cls_score'], + bbox_pred=bbox_results['bbox_pred'], + rois=rois, + sampling_results=sampling_results, + rcnn_train_cfg=self.train_cfg[stage]) + bbox_results.update(bbox_loss_and_target) + + return bbox_results + + def _mask_forward(self, stage: int, x: Tuple[Tensor], + rois: Tensor) -> dict: + """Mask head forward function used in both training and testing. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): Tuple of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + """ + mask_roi_extractor = self.mask_roi_extractor[stage] + mask_head = self.mask_head[stage] + mask_feats = mask_roi_extractor(x[:mask_roi_extractor.num_inputs], + rois) + # do not support caffe_c4 model anymore + mask_preds = mask_head(mask_feats) + + mask_results = dict(mask_preds=mask_preds) + return mask_results + + def mask_loss(self, stage: int, x: Tuple[Tensor], + sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList) -> dict: + """Run forward function and calculate loss for mask head in training. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): Tuple of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + - `loss_mask` (dict): A dictionary of mask loss components. + """ + pos_rois = bbox2roi([res.pos_priors for res in sampling_results]) + mask_results = self._mask_forward(stage, x, pos_rois) + + mask_head = self.mask_head[stage] + + mask_loss_and_target = mask_head.loss_and_target( + mask_preds=mask_results['mask_preds'], + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=self.train_cfg[stage]) + mask_results.update(mask_loss_and_target) + + return mask_results + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components + """ + # TODO: May add a new function in baseroihead + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, batch_img_metas \ + = outputs + + num_imgs = len(batch_data_samples) + losses = dict() + results_list = rpn_results_list + for stage in range(self.num_stages): + self.current_stage = stage + + stage_loss_weight = self.stage_loss_weights[stage] + + # assign gts and sample proposals + sampling_results = [] + if self.with_bbox or self.with_mask: + bbox_assigner = self.bbox_assigner[stage] + bbox_sampler = self.bbox_sampler[stage] + + for i in range(num_imgs): + results = results_list[i] + # rename rpn_results.bboxes to rpn_results.priors + results.priors = results.pop('bboxes') + + assign_result = bbox_assigner.assign( + results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + + sampling_result = bbox_sampler.sample( + assign_result, + results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + sampling_results.append(sampling_result) + + # bbox head forward and loss + bbox_results = self.bbox_loss(stage, x, sampling_results) + + for name, value in bbox_results['loss_bbox'].items(): + losses[f's{stage}.{name}'] = ( + value * stage_loss_weight if 'loss' in name else value) + + # mask head forward and loss + if self.with_mask: + mask_results = self.mask_loss(stage, x, sampling_results, + batch_gt_instances) + for name, value in mask_results['loss_mask'].items(): + losses[f's{stage}.{name}'] = ( + value * stage_loss_weight if 'loss' in name else value) + + # refine bboxes + if stage < self.num_stages - 1: + bbox_head = self.bbox_head[stage] + with torch.no_grad(): + results_list = bbox_head.refine_bboxes( + sampling_results, bbox_results, batch_img_metas) + # Empty proposal + if results_list is None: + break + return losses + + def predict_bbox(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + rpn_results_list: InstanceList, + rcnn_test_cfg: ConfigType, + rescale: bool = False, + **kwargs) -> InstanceList: + """Perform forward propagation of the bbox head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of R-CNN. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + proposals = [res.bboxes for res in rpn_results_list] + num_proposals_per_img = tuple(len(p) for p in proposals) + rois = bbox2roi(proposals) + + if rois.shape[0] == 0: + return empty_instances( + batch_img_metas, + rois.device, + task_type='bbox', + box_type=self.bbox_head[-1].predict_box_type, + num_classes=self.bbox_head[-1].num_classes, + score_per_cls=rcnn_test_cfg is None) + + rois, cls_scores, bbox_preds = self._refine_roi( + x=x, + rois=rois, + batch_img_metas=batch_img_metas, + num_proposals_per_img=num_proposals_per_img, + **kwargs) + + results_list = self.bbox_head[-1].predict_by_feat( + rois=rois, + cls_scores=cls_scores, + bbox_preds=bbox_preds, + batch_img_metas=batch_img_metas, + rescale=rescale, + rcnn_test_cfg=rcnn_test_cfg) + return results_list + + def predict_mask(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + results_list: List[InstanceData], + rescale: bool = False) -> List[InstanceData]: + """Perform forward propagation of the mask head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + bboxes = [res.bboxes for res in results_list] + mask_rois = bbox2roi(bboxes) + if mask_rois.shape[0] == 0: + results_list = empty_instances( + batch_img_metas, + mask_rois.device, + task_type='mask', + instance_results=results_list, + mask_thr_binary=self.test_cfg.mask_thr_binary) + return results_list + + num_mask_rois_per_img = [len(res) for res in results_list] + aug_masks = [] + for stage in range(self.num_stages): + mask_results = self._mask_forward(stage, x, mask_rois) + mask_preds = mask_results['mask_preds'] + # split batch mask prediction back to each image + mask_preds = mask_preds.split(num_mask_rois_per_img, 0) + aug_masks.append([m.sigmoid().detach() for m in mask_preds]) + + merged_masks = [] + for i in range(len(batch_img_metas)): + aug_mask = [mask[i] for mask in aug_masks] + merged_mask = merge_aug_masks(aug_mask, batch_img_metas[i]) + merged_masks.append(merged_mask) + results_list = self.mask_head[-1].predict_by_feat( + mask_preds=merged_masks, + results_list=results_list, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=self.test_cfg, + rescale=rescale, + activate_map=True) + return results_list + + def _refine_roi(self, x: Tuple[Tensor], rois: Tensor, + batch_img_metas: List[dict], + num_proposals_per_img: Sequence[int], **kwargs) -> tuple: + """Multi-stage refinement of RoI. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): shape (n, 5), [batch_ind, x1, y1, x2, y2] + batch_img_metas (list[dict]): List of image information. + num_proposals_per_img (sequence[int]): number of proposals + in each image. + + Returns: + tuple: + + - rois (Tensor): Refined RoI. + - cls_scores (list[Tensor]): Average predicted + cls score per image. + - bbox_preds (list[Tensor]): Bbox branch predictions + for the last stage of per image. + """ + # "ms" in variable names means multi-stage + ms_scores = [] + for stage in range(self.num_stages): + bbox_results = self._bbox_forward( + stage=stage, x=x, rois=rois, **kwargs) + + # split batch bbox prediction back to each image + cls_scores = bbox_results['cls_score'] + bbox_preds = bbox_results['bbox_pred'] + + rois = rois.split(num_proposals_per_img, 0) + cls_scores = cls_scores.split(num_proposals_per_img, 0) + ms_scores.append(cls_scores) + + # some detector with_reg is False, bbox_preds will be None + if bbox_preds is not None: + # TODO move this to a sabl_roi_head + # the bbox prediction of some detectors like SABL is not Tensor + if isinstance(bbox_preds, torch.Tensor): + bbox_preds = bbox_preds.split(num_proposals_per_img, 0) + else: + bbox_preds = self.bbox_head[stage].bbox_pred_split( + bbox_preds, num_proposals_per_img) + else: + bbox_preds = (None, ) * len(batch_img_metas) + + if stage < self.num_stages - 1: + bbox_head = self.bbox_head[stage] + if bbox_head.custom_activation: + cls_scores = [ + bbox_head.loss_cls.get_activation(s) + for s in cls_scores + ] + refine_rois_list = [] + for i in range(len(batch_img_metas)): + if rois[i].shape[0] > 0: + bbox_label = cls_scores[i][:, :-1].argmax(dim=1) + # Refactor `bbox_head.regress_by_class` to only accept + # box tensor without img_idx concatenated. + refined_bboxes = bbox_head.regress_by_class( + rois[i][:, 1:], bbox_label, bbox_preds[i], + batch_img_metas[i]) + refined_bboxes = get_box_tensor(refined_bboxes) + refined_rois = torch.cat( + [rois[i][:, [0]], refined_bboxes], dim=1) + refine_rois_list.append(refined_rois) + rois = torch.cat(refine_rois_list) + + # average scores of each image by stages + cls_scores = [ + sum([score[i] for score in ms_scores]) / float(len(ms_scores)) + for i in range(len(batch_img_metas)) + ] + return rois, cls_scores, bbox_preds + + def forward(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + x (List[Tensor]): Multi-level features that may have different + resolutions. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns + tuple: A tuple of features from ``bbox_head`` and ``mask_head`` + forward. + """ + results = () + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + proposals = [rpn_results.bboxes for rpn_results in rpn_results_list] + num_proposals_per_img = tuple(len(p) for p in proposals) + rois = bbox2roi(proposals) + # bbox head + if self.with_bbox: + rois, cls_scores, bbox_preds = self._refine_roi( + x, rois, batch_img_metas, num_proposals_per_img) + results = results + (cls_scores, bbox_preds) + # mask head + if self.with_mask: + aug_masks = [] + rois = torch.cat(rois) + for stage in range(self.num_stages): + mask_results = self._mask_forward(stage, x, rois) + mask_preds = mask_results['mask_preds'] + mask_preds = mask_preds.split(num_proposals_per_img, 0) + aug_masks.append([m.sigmoid().detach() for m in mask_preds]) + + merged_masks = [] + for i in range(len(batch_img_metas)): + aug_mask = [mask[i] for mask in aug_masks] + merged_mask = merge_aug_masks(aug_mask, batch_img_metas[i]) + merged_masks.append(merged_mask) + results = results + (merged_masks, ) + return results diff --git a/mmdetection/mmdet/models/roi_heads/double_roi_head.py b/mmdetection/mmdet/models/roi_heads/double_roi_head.py new file mode 100644 index 00000000..f9464ff5 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/double_roi_head.py @@ -0,0 +1,53 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +from torch import Tensor + +from mmdet.registry import MODELS +from .standard_roi_head import StandardRoIHead + + +@MODELS.register_module() +class DoubleHeadRoIHead(StandardRoIHead): + """RoI head for `Double Head RCNN `_. + + Args: + reg_roi_scale_factor (float): The scale factor to extend the rois + used to extract the regression features. + """ + + def __init__(self, reg_roi_scale_factor: float, **kwargs): + super().__init__(**kwargs) + self.reg_roi_scale_factor = reg_roi_scale_factor + + def _bbox_forward(self, x: Tuple[Tensor], rois: Tensor) -> dict: + """Box head forward function used in both training and testing. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + """ + bbox_cls_feats = self.bbox_roi_extractor( + x[:self.bbox_roi_extractor.num_inputs], rois) + bbox_reg_feats = self.bbox_roi_extractor( + x[:self.bbox_roi_extractor.num_inputs], + rois, + roi_scale_factor=self.reg_roi_scale_factor) + if self.with_shared_head: + bbox_cls_feats = self.shared_head(bbox_cls_feats) + bbox_reg_feats = self.shared_head(bbox_reg_feats) + cls_score, bbox_pred = self.bbox_head(bbox_cls_feats, bbox_reg_feats) + + bbox_results = dict( + cls_score=cls_score, + bbox_pred=bbox_pred, + bbox_feats=bbox_cls_feats) + return bbox_results diff --git a/mmdetection/mmdet/models/roi_heads/dynamic_roi_head.py b/mmdetection/mmdet/models/roi_heads/dynamic_roi_head.py new file mode 100644 index 00000000..3c7f7bd2 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/dynamic_roi_head.py @@ -0,0 +1,163 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import numpy as np +import torch +from torch import Tensor + +from mmdet.models.losses import SmoothL1Loss +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import InstanceList +from ..utils.misc import unpack_gt_instances +from .standard_roi_head import StandardRoIHead + +EPS = 1e-15 + + +@MODELS.register_module() +class DynamicRoIHead(StandardRoIHead): + """RoI head for `Dynamic R-CNN `_.""" + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + assert isinstance(self.bbox_head.loss_bbox, SmoothL1Loss) + # the IoU history of the past `update_iter_interval` iterations + self.iou_history = [] + # the beta history of the past `update_iter_interval` iterations + self.beta_history = [] + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> dict: + """Forward function for training. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, _ = outputs + + # assign gts and sample proposals + num_imgs = len(batch_data_samples) + sampling_results = [] + cur_iou = [] + for i in range(num_imgs): + # rename rpn_results.bboxes to rpn_results.priors + rpn_results = rpn_results_list[i] + rpn_results.priors = rpn_results.pop('bboxes') + + assign_result = self.bbox_assigner.assign( + rpn_results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = self.bbox_sampler.sample( + assign_result, + rpn_results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + # record the `iou_topk`-th largest IoU in an image + iou_topk = min(self.train_cfg.dynamic_rcnn.iou_topk, + len(assign_result.max_overlaps)) + ious, _ = torch.topk(assign_result.max_overlaps, iou_topk) + cur_iou.append(ious[-1].item()) + sampling_results.append(sampling_result) + # average the current IoUs over images + cur_iou = np.mean(cur_iou) + self.iou_history.append(cur_iou) + + losses = dict() + # bbox head forward and loss + if self.with_bbox: + bbox_results = self.bbox_loss(x, sampling_results) + losses.update(bbox_results['loss_bbox']) + + # mask head forward and loss + if self.with_mask: + mask_results = self.mask_loss(x, sampling_results, + bbox_results['bbox_feats'], + batch_gt_instances) + losses.update(mask_results['loss_mask']) + + # update IoU threshold and SmoothL1 beta + update_iter_interval = self.train_cfg.dynamic_rcnn.update_iter_interval + if len(self.iou_history) % update_iter_interval == 0: + new_iou_thr, new_beta = self.update_hyperparameters() + + return losses + + def bbox_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult]) -> dict: + """Perform forward propagation and loss calculation of the bbox head on + the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + """ + rois = bbox2roi([res.priors for res in sampling_results]) + bbox_results = self._bbox_forward(x, rois) + + bbox_loss_and_target = self.bbox_head.loss_and_target( + cls_score=bbox_results['cls_score'], + bbox_pred=bbox_results['bbox_pred'], + rois=rois, + sampling_results=sampling_results, + rcnn_train_cfg=self.train_cfg) + bbox_results.update(loss_bbox=bbox_loss_and_target['loss_bbox']) + + # record the `beta_topk`-th smallest target + # `bbox_targets[2]` and `bbox_targets[3]` stand for bbox_targets + # and bbox_weights, respectively + bbox_targets = bbox_loss_and_target['bbox_targets'] + pos_inds = bbox_targets[3][:, 0].nonzero().squeeze(1) + num_pos = len(pos_inds) + num_imgs = len(sampling_results) + if num_pos > 0: + cur_target = bbox_targets[2][pos_inds, :2].abs().mean(dim=1) + beta_topk = min(self.train_cfg.dynamic_rcnn.beta_topk * num_imgs, + num_pos) + cur_target = torch.kthvalue(cur_target, beta_topk)[0].item() + self.beta_history.append(cur_target) + + return bbox_results + + def update_hyperparameters(self): + """Update hyperparameters like IoU thresholds for assigner and beta for + SmoothL1 loss based on the training statistics. + + Returns: + tuple[float]: the updated ``iou_thr`` and ``beta``. + """ + new_iou_thr = max(self.train_cfg.dynamic_rcnn.initial_iou, + np.mean(self.iou_history)) + self.iou_history = [] + self.bbox_assigner.pos_iou_thr = new_iou_thr + self.bbox_assigner.neg_iou_thr = new_iou_thr + self.bbox_assigner.min_pos_iou = new_iou_thr + if (not self.beta_history) or (np.median(self.beta_history) < EPS): + # avoid 0 or too small value for new_beta + new_beta = self.bbox_head.loss_bbox.beta + else: + new_beta = min(self.train_cfg.dynamic_rcnn.initial_beta, + np.median(self.beta_history)) + self.beta_history = [] + self.bbox_head.loss_bbox.beta = new_beta + return new_iou_thr, new_beta diff --git a/mmdetection/mmdet/models/roi_heads/grid_roi_head.py b/mmdetection/mmdet/models/roi_heads/grid_roi_head.py new file mode 100644 index 00000000..9eda7f01 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/grid_roi_head.py @@ -0,0 +1,280 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList +from ..task_modules.samplers import SamplingResult +from ..utils.misc import unpack_gt_instances +from .standard_roi_head import StandardRoIHead + + +@MODELS.register_module() +class GridRoIHead(StandardRoIHead): + """Implementation of `Grid RoI Head `_ + + Args: + grid_roi_extractor (:obj:`ConfigDict` or dict): Config of + roi extractor. + grid_head (:obj:`ConfigDict` or dict): Config of grid head + """ + + def __init__(self, grid_roi_extractor: ConfigType, grid_head: ConfigType, + **kwargs) -> None: + assert grid_head is not None + super().__init__(**kwargs) + if grid_roi_extractor is not None: + self.grid_roi_extractor = MODELS.build(grid_roi_extractor) + self.share_roi_extractor = False + else: + self.share_roi_extractor = True + self.grid_roi_extractor = self.bbox_roi_extractor + self.grid_head = MODELS.build(grid_head) + + def _random_jitter(self, + sampling_results: List[SamplingResult], + batch_img_metas: List[dict], + amplitude: float = 0.15) -> List[SamplingResult]: + """Ramdom jitter positive proposals for training. + + Args: + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + batch_img_metas (list[dict]): List of image information. + amplitude (float): Amplitude of random offset. Defaults to 0.15. + + Returns: + list[obj:SamplingResult]: SamplingResults after random jittering. + """ + for sampling_result, img_meta in zip(sampling_results, + batch_img_metas): + bboxes = sampling_result.pos_priors + random_offsets = bboxes.new_empty(bboxes.shape[0], 4).uniform_( + -amplitude, amplitude) + # before jittering + cxcy = (bboxes[:, 2:4] + bboxes[:, :2]) / 2 + wh = (bboxes[:, 2:4] - bboxes[:, :2]).abs() + # after jittering + new_cxcy = cxcy + wh * random_offsets[:, :2] + new_wh = wh * (1 + random_offsets[:, 2:]) + # xywh to xyxy + new_x1y1 = (new_cxcy - new_wh / 2) + new_x2y2 = (new_cxcy + new_wh / 2) + new_bboxes = torch.cat([new_x1y1, new_x2y2], dim=1) + # clip bboxes + max_shape = img_meta['img_shape'] + if max_shape is not None: + new_bboxes[:, 0::2].clamp_(min=0, max=max_shape[1] - 1) + new_bboxes[:, 1::2].clamp_(min=0, max=max_shape[0] - 1) + + sampling_result.pos_priors = new_bboxes + return sampling_results + + # TODO: Forward is incorrect and need to refactor. + def forward(self, + x: Tuple[Tensor], + rpn_results_list: InstanceList, + batch_data_samples: SampleList = None) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + x (Tuple[Tensor]): Multi-level features that may have different + resolutions. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns + tuple: A tuple of features from ``bbox_head`` and ``mask_head`` + forward. + """ + results = () + proposals = [rpn_results.bboxes for rpn_results in rpn_results_list] + rois = bbox2roi(proposals) + # bbox head + if self.with_bbox: + bbox_results = self._bbox_forward(x, rois) + results = results + (bbox_results['cls_score'], ) + if self.bbox_head.with_reg: + results = results + (bbox_results['bbox_pred'], ) + + # grid head + grid_rois = rois[:100] + grid_feats = self.grid_roi_extractor( + x[:len(self.grid_roi_extractor.featmap_strides)], grid_rois) + if self.with_shared_head: + grid_feats = self.shared_head(grid_feats) + self.grid_head.test_mode = True + grid_preds = self.grid_head(grid_feats) + results = results + (grid_preds, ) + + # mask head + if self.with_mask: + mask_rois = rois[:100] + mask_results = self._mask_forward(x, mask_rois) + results = results + (mask_results['mask_preds'], ) + return results + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList, **kwargs) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components + """ + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + (batch_gt_instances, batch_gt_instances_ignore, + batch_img_metas) = outputs + + # assign gts and sample proposals + num_imgs = len(batch_data_samples) + sampling_results = [] + for i in range(num_imgs): + # rename rpn_results.bboxes to rpn_results.priors + rpn_results = rpn_results_list[i] + rpn_results.priors = rpn_results.pop('bboxes') + + assign_result = self.bbox_assigner.assign( + rpn_results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = self.bbox_sampler.sample( + assign_result, + rpn_results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + sampling_results.append(sampling_result) + + losses = dict() + # bbox head loss + if self.with_bbox: + bbox_results = self.bbox_loss(x, sampling_results, batch_img_metas) + losses.update(bbox_results['loss_bbox']) + + # mask head forward and loss + if self.with_mask: + mask_results = self.mask_loss(x, sampling_results, + bbox_results['bbox_feats'], + batch_gt_instances) + losses.update(mask_results['loss_mask']) + + return losses + + def bbox_loss(self, + x: Tuple[Tensor], + sampling_results: List[SamplingResult], + batch_img_metas: Optional[List[dict]] = None) -> dict: + """Perform forward propagation and loss calculation of the bbox head on + the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list[:obj:`SamplingResult`]): Sampling results. + batch_img_metas (list[dict], optional): Meta information of each + image, e.g., image size, scaling factor, etc. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + """ + assert batch_img_metas is not None + bbox_results = super().bbox_loss(x, sampling_results) + + # Grid head forward and loss + sampling_results = self._random_jitter(sampling_results, + batch_img_metas) + pos_rois = bbox2roi([res.pos_bboxes for res in sampling_results]) + + # GN in head does not support zero shape input + if pos_rois.shape[0] == 0: + return bbox_results + + grid_feats = self.grid_roi_extractor( + x[:self.grid_roi_extractor.num_inputs], pos_rois) + if self.with_shared_head: + grid_feats = self.shared_head(grid_feats) + # Accelerate training + max_sample_num_grid = self.train_cfg.get('max_num_grid', 192) + sample_idx = torch.randperm( + grid_feats.shape[0])[:min(grid_feats.shape[0], max_sample_num_grid + )] + grid_feats = grid_feats[sample_idx] + grid_pred = self.grid_head(grid_feats) + + loss_grid = self.grid_head.loss(grid_pred, sample_idx, + sampling_results, self.train_cfg) + + bbox_results['loss_bbox'].update(loss_grid) + return bbox_results + + def predict_bbox(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + rpn_results_list: InstanceList, + rcnn_test_cfg: ConfigType, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the bbox head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + rcnn_test_cfg (:obj:`ConfigDict`): `test_cfg` of R-CNN. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape \ + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), the last \ + dimension 4 arrange as (x1, y1, x2, y2). + """ + results_list = super().predict_bbox( + x, + batch_img_metas=batch_img_metas, + rpn_results_list=rpn_results_list, + rcnn_test_cfg=rcnn_test_cfg, + rescale=False) + + grid_rois = bbox2roi([res.bboxes for res in results_list]) + if grid_rois.shape[0] != 0: + grid_feats = self.grid_roi_extractor( + x[:len(self.grid_roi_extractor.featmap_strides)], grid_rois) + if self.with_shared_head: + grid_feats = self.shared_head(grid_feats) + self.grid_head.test_mode = True + grid_preds = self.grid_head(grid_feats) + results_list = self.grid_head.predict_by_feat( + grid_preds=grid_preds, + results_list=results_list, + batch_img_metas=batch_img_metas, + rescale=rescale) + + return results_list diff --git a/mmdetection/mmdet/models/roi_heads/htc_roi_head.py b/mmdetection/mmdet/models/roi_heads/htc_roi_head.py new file mode 100644 index 00000000..0fdd99dd --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/htc_roi_head.py @@ -0,0 +1,581 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Tuple + +import torch +import torch.nn.functional as F +from torch import Tensor + +from mmdet.models.test_time_augs import merge_aug_masks +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import InstanceList, OptConfigType +from ..layers import adaptive_avg_pool2d +from ..task_modules.samplers import SamplingResult +from ..utils import empty_instances, unpack_gt_instances +from .cascade_roi_head import CascadeRoIHead + + +@MODELS.register_module() +class HybridTaskCascadeRoIHead(CascadeRoIHead): + """Hybrid task cascade roi head including one bbox head and one mask head. + + https://arxiv.org/abs/1901.07518 + + Args: + num_stages (int): Number of cascade stages. + stage_loss_weights (list[float]): Loss weight for every stage. + semantic_roi_extractor (:obj:`ConfigDict` or dict, optional): + Config of semantic roi extractor. Defaults to None. + Semantic_head (:obj:`ConfigDict` or dict, optional): + Config of semantic head. Defaults to None. + interleaved (bool): Whether to interleaves the box branch and mask + branch. If True, the mask branch can take the refined bounding + box predictions. Defaults to True. + mask_info_flow (bool): Whether to turn on the mask information flow, + which means that feeding the mask features of the preceding stage + to the current stage. Defaults to True. + """ + + def __init__(self, + num_stages: int, + stage_loss_weights: List[float], + semantic_roi_extractor: OptConfigType = None, + semantic_head: OptConfigType = None, + semantic_fusion: Tuple[str] = ('bbox', 'mask'), + interleaved: bool = True, + mask_info_flow: bool = True, + **kwargs) -> None: + super().__init__( + num_stages=num_stages, + stage_loss_weights=stage_loss_weights, + **kwargs) + assert self.with_bbox + assert not self.with_shared_head # shared head is not supported + + if semantic_head is not None: + self.semantic_roi_extractor = MODELS.build(semantic_roi_extractor) + self.semantic_head = MODELS.build(semantic_head) + + self.semantic_fusion = semantic_fusion + self.interleaved = interleaved + self.mask_info_flow = mask_info_flow + + # TODO move to base_roi_head later + @property + def with_semantic(self) -> bool: + """bool: whether the head has semantic head""" + return hasattr(self, + 'semantic_head') and self.semantic_head is not None + + def _bbox_forward( + self, + stage: int, + x: Tuple[Tensor], + rois: Tensor, + semantic_feat: Optional[Tensor] = None) -> Dict[str, Tensor]: + """Box head forward function used in both training and testing. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + semantic_feat (Tensor, optional): Semantic feature. Defaults to + None. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + """ + bbox_roi_extractor = self.bbox_roi_extractor[stage] + bbox_head = self.bbox_head[stage] + bbox_feats = bbox_roi_extractor(x[:bbox_roi_extractor.num_inputs], + rois) + if self.with_semantic and 'bbox' in self.semantic_fusion: + bbox_semantic_feat = self.semantic_roi_extractor([semantic_feat], + rois) + if bbox_semantic_feat.shape[-2:] != bbox_feats.shape[-2:]: + bbox_semantic_feat = adaptive_avg_pool2d( + bbox_semantic_feat, bbox_feats.shape[-2:]) + bbox_feats += bbox_semantic_feat + cls_score, bbox_pred = bbox_head(bbox_feats) + + bbox_results = dict(cls_score=cls_score, bbox_pred=bbox_pred) + return bbox_results + + def bbox_loss(self, + stage: int, + x: Tuple[Tensor], + sampling_results: List[SamplingResult], + semantic_feat: Optional[Tensor] = None) -> dict: + """Run forward function and calculate loss for box head in training. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + semantic_feat (Tensor, optional): Semantic feature. Defaults to + None. + + Returns: + dict: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + - `rois` (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + - `bbox_targets` (tuple): Ground truth for proposals in a + single image. Containing the following list of Tensors: + (labels, label_weights, bbox_targets, bbox_weights) + """ + bbox_head = self.bbox_head[stage] + rois = bbox2roi([res.priors for res in sampling_results]) + bbox_results = self._bbox_forward( + stage, x, rois, semantic_feat=semantic_feat) + bbox_results.update(rois=rois) + + bbox_loss_and_target = bbox_head.loss_and_target( + cls_score=bbox_results['cls_score'], + bbox_pred=bbox_results['bbox_pred'], + rois=rois, + sampling_results=sampling_results, + rcnn_train_cfg=self.train_cfg[stage]) + bbox_results.update(bbox_loss_and_target) + return bbox_results + + def _mask_forward(self, + stage: int, + x: Tuple[Tensor], + rois: Tensor, + semantic_feat: Optional[Tensor] = None, + training: bool = True) -> Dict[str, Tensor]: + """Mask head forward function used only in training. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): Tuple of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + semantic_feat (Tensor, optional): Semantic feature. Defaults to + None. + training (bool): Mask Forward is different between training and + testing. If True, use the mask forward in training. + Defaults to True. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + """ + mask_roi_extractor = self.mask_roi_extractor[stage] + mask_head = self.mask_head[stage] + mask_feats = mask_roi_extractor(x[:mask_roi_extractor.num_inputs], + rois) + + # semantic feature fusion + # element-wise sum for original features and pooled semantic features + if self.with_semantic and 'mask' in self.semantic_fusion: + mask_semantic_feat = self.semantic_roi_extractor([semantic_feat], + rois) + if mask_semantic_feat.shape[-2:] != mask_feats.shape[-2:]: + mask_semantic_feat = F.adaptive_avg_pool2d( + mask_semantic_feat, mask_feats.shape[-2:]) + mask_feats = mask_feats + mask_semantic_feat + + # mask information flow + # forward all previous mask heads to obtain last_feat, and fuse it + # with the normal mask feature + if training: + if self.mask_info_flow: + last_feat = None + for i in range(stage): + last_feat = self.mask_head[i]( + mask_feats, last_feat, return_logits=False) + mask_preds = mask_head( + mask_feats, last_feat, return_feat=False) + else: + mask_preds = mask_head(mask_feats, return_feat=False) + + mask_results = dict(mask_preds=mask_preds) + else: + aug_masks = [] + last_feat = None + for i in range(self.num_stages): + mask_head = self.mask_head[i] + if self.mask_info_flow: + mask_preds, last_feat = mask_head(mask_feats, last_feat) + else: + mask_preds = mask_head(mask_feats) + aug_masks.append(mask_preds) + + mask_results = dict(mask_preds=aug_masks) + + return mask_results + + def mask_loss(self, + stage: int, + x: Tuple[Tensor], + sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList, + semantic_feat: Optional[Tensor] = None) -> dict: + """Run forward function and calculate loss for mask head in training. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): Tuple of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + semantic_feat (Tensor, optional): Semantic feature. Defaults to + None. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + - `loss_mask` (dict): A dictionary of mask loss components. + """ + pos_rois = bbox2roi([res.pos_priors for res in sampling_results]) + mask_results = self._mask_forward( + stage=stage, + x=x, + rois=pos_rois, + semantic_feat=semantic_feat, + training=True) + + mask_head = self.mask_head[stage] + mask_loss_and_target = mask_head.loss_and_target( + mask_preds=mask_results['mask_preds'], + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=self.train_cfg[stage]) + mask_results.update(mask_loss_and_target) + + return mask_results + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components + """ + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, batch_img_metas \ + = outputs + + # semantic segmentation part + # 2 outputs: segmentation prediction and embedded features + losses = dict() + if self.with_semantic: + gt_semantic_segs = [ + data_sample.gt_sem_seg.sem_seg + for data_sample in batch_data_samples + ] + gt_semantic_segs = torch.stack(gt_semantic_segs) + semantic_pred, semantic_feat = self.semantic_head(x) + loss_seg = self.semantic_head.loss(semantic_pred, gt_semantic_segs) + losses['loss_semantic_seg'] = loss_seg + else: + semantic_feat = None + + results_list = rpn_results_list + num_imgs = len(batch_img_metas) + for stage in range(self.num_stages): + self.current_stage = stage + + stage_loss_weight = self.stage_loss_weights[stage] + + # assign gts and sample proposals + sampling_results = [] + bbox_assigner = self.bbox_assigner[stage] + bbox_sampler = self.bbox_sampler[stage] + for i in range(num_imgs): + results = results_list[i] + # rename rpn_results.bboxes to rpn_results.priors + if 'bboxes' in results: + results.priors = results.pop('bboxes') + + assign_result = bbox_assigner.assign( + results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = bbox_sampler.sample( + assign_result, + results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + sampling_results.append(sampling_result) + + # bbox head forward and loss + bbox_results = self.bbox_loss( + stage=stage, + x=x, + sampling_results=sampling_results, + semantic_feat=semantic_feat) + + for name, value in bbox_results['loss_bbox'].items(): + losses[f's{stage}.{name}'] = ( + value * stage_loss_weight if 'loss' in name else value) + + # mask head forward and loss + if self.with_mask: + # interleaved execution: use regressed bboxes by the box branch + # to train the mask branch + if self.interleaved: + bbox_head = self.bbox_head[stage] + with torch.no_grad(): + results_list = bbox_head.refine_bboxes( + sampling_results, bbox_results, batch_img_metas) + # re-assign and sample 512 RoIs from 512 RoIs + sampling_results = [] + for i in range(num_imgs): + results = results_list[i] + # rename rpn_results.bboxes to rpn_results.priors + results.priors = results.pop('bboxes') + assign_result = bbox_assigner.assign( + results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = bbox_sampler.sample( + assign_result, + results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + sampling_results.append(sampling_result) + mask_results = self.mask_loss( + stage=stage, + x=x, + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + semantic_feat=semantic_feat) + for name, value in mask_results['loss_mask'].items(): + losses[f's{stage}.{name}'] = ( + value * stage_loss_weight if 'loss' in name else value) + + # refine bboxes (same as Cascade R-CNN) + if stage < self.num_stages - 1 and not self.interleaved: + bbox_head = self.bbox_head[stage] + with torch.no_grad(): + results_list = bbox_head.refine_bboxes( + sampling_results=sampling_results, + bbox_results=bbox_results, + batch_img_metas=batch_img_metas) + + return losses + + def predict(self, + x: Tuple[Tensor], + rpn_results_list: InstanceList, + batch_data_samples: SampleList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the roi head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Features from upstream network. Each + has shape (N, C, H, W). + rpn_results_list (list[:obj:`InstanceData`]): list of region + proposals. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results to + the original image. Defaults to False. + + Returns: + list[obj:`InstanceData`]: Detection results of each image. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + assert self.with_bbox, 'Bbox head must be implemented.' + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + if self.with_semantic: + _, semantic_feat = self.semantic_head(x) + else: + semantic_feat = None + + # TODO: nms_op in mmcv need be enhanced, the bbox result may get + # difference when not rescale in bbox_head + + # If it has the mask branch, the bbox branch does not need + # to be scaled to the original image scale, because the mask + # branch will scale both bbox and mask at the same time. + bbox_rescale = rescale if not self.with_mask else False + results_list = self.predict_bbox( + x=x, + semantic_feat=semantic_feat, + batch_img_metas=batch_img_metas, + rpn_results_list=rpn_results_list, + rcnn_test_cfg=self.test_cfg, + rescale=bbox_rescale) + + if self.with_mask: + results_list = self.predict_mask( + x=x, + semantic_heat=semantic_feat, + batch_img_metas=batch_img_metas, + results_list=results_list, + rescale=rescale) + + return results_list + + def predict_mask(self, + x: Tuple[Tensor], + semantic_heat: Tensor, + batch_img_metas: List[dict], + results_list: InstanceList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the mask head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + semantic_feat (Tensor): Semantic feature. + batch_img_metas (list[dict]): List of image information. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + num_imgs = len(batch_img_metas) + bboxes = [res.bboxes for res in results_list] + mask_rois = bbox2roi(bboxes) + if mask_rois.shape[0] == 0: + results_list = empty_instances( + batch_img_metas=batch_img_metas, + device=mask_rois.device, + task_type='mask', + instance_results=results_list, + mask_thr_binary=self.test_cfg.mask_thr_binary) + return results_list + + num_mask_rois_per_img = [len(res) for res in results_list] + mask_results = self._mask_forward( + stage=-1, + x=x, + rois=mask_rois, + semantic_feat=semantic_heat, + training=False) + # split batch mask prediction back to each image + aug_masks = [[ + mask.sigmoid().detach() + for mask in mask_preds.split(num_mask_rois_per_img, 0) + ] for mask_preds in mask_results['mask_preds']] + + merged_masks = [] + for i in range(num_imgs): + aug_mask = [mask[i] for mask in aug_masks] + merged_mask = merge_aug_masks(aug_mask, batch_img_metas[i]) + merged_masks.append(merged_mask) + + results_list = self.mask_head[-1].predict_by_feat( + mask_preds=merged_masks, + results_list=results_list, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=self.test_cfg, + rescale=rescale, + activate_map=True) + + return results_list + + def forward(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + x (List[Tensor]): Multi-level features that may have different + resolutions. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns + tuple: A tuple of features from ``bbox_head`` and ``mask_head`` + forward. + """ + results = () + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + num_imgs = len(batch_img_metas) + + if self.with_semantic: + _, semantic_feat = self.semantic_head(x) + else: + semantic_feat = None + + proposals = [rpn_results.bboxes for rpn_results in rpn_results_list] + num_proposals_per_img = tuple(len(p) for p in proposals) + rois = bbox2roi(proposals) + # bbox head + if self.with_bbox: + rois, cls_scores, bbox_preds = self._refine_roi( + x=x, + rois=rois, + semantic_feat=semantic_feat, + batch_img_metas=batch_img_metas, + num_proposals_per_img=num_proposals_per_img) + results = results + (cls_scores, bbox_preds) + # mask head + if self.with_mask: + rois = torch.cat(rois) + mask_results = self._mask_forward( + stage=-1, + x=x, + rois=rois, + semantic_feat=semantic_feat, + training=False) + aug_masks = [[ + mask.sigmoid().detach() + for mask in mask_preds.split(num_proposals_per_img, 0) + ] for mask_preds in mask_results['mask_preds']] + + merged_masks = [] + for i in range(num_imgs): + aug_mask = [mask[i] for mask in aug_masks] + merged_mask = merge_aug_masks(aug_mask, batch_img_metas[i]) + merged_masks.append(merged_mask) + results = results + (merged_masks, ) + return results diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/__init__.py b/mmdetection/mmdet/models/roi_heads/mask_heads/__init__.py new file mode 100644 index 00000000..48a5d422 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .coarse_mask_head import CoarseMaskHead +from .dynamic_mask_head import DynamicMaskHead +from .fcn_mask_head import FCNMaskHead +from .feature_relay_head import FeatureRelayHead +from .fused_semantic_head import FusedSemanticHead +from .global_context_head import GlobalContextHead +from .grid_head import GridHead +from .htc_mask_head import HTCMaskHead +from .mask_point_head import MaskPointHead +from .maskiou_head import MaskIoUHead +from .scnet_mask_head import SCNetMaskHead +from .scnet_semantic_head import SCNetSemanticHead + +__all__ = [ + 'FCNMaskHead', 'HTCMaskHead', 'FusedSemanticHead', 'GridHead', + 'MaskIoUHead', 'CoarseMaskHead', 'MaskPointHead', 'SCNetMaskHead', + 'SCNetSemanticHead', 'GlobalContextHead', 'FeatureRelayHead', + 'DynamicMaskHead' +] diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/coarse_mask_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/coarse_mask_head.py new file mode 100644 index 00000000..1caa9012 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/coarse_mask_head.py @@ -0,0 +1,110 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.cnn import ConvModule, Linear +from mmengine.model import ModuleList +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import MultiConfig +from .fcn_mask_head import FCNMaskHead + + +@MODELS.register_module() +class CoarseMaskHead(FCNMaskHead): + """Coarse mask head used in PointRend. + + Compared with standard ``FCNMaskHead``, ``CoarseMaskHead`` will downsample + the input feature map instead of upsample it. + + Args: + num_convs (int): Number of conv layers in the head. Defaults to 0. + num_fcs (int): Number of fc layers in the head. Defaults to 2. + fc_out_channels (int): Number of output channels of fc layer. + Defaults to 1024. + downsample_factor (int): The factor that feature map is downsampled by. + Defaults to 2. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + num_convs: int = 0, + num_fcs: int = 2, + fc_out_channels: int = 1024, + downsample_factor: int = 2, + init_cfg: MultiConfig = dict( + type='Xavier', + override=[ + dict(name='fcs'), + dict(type='Constant', val=0.001, name='fc_logits') + ]), + *arg, + **kwarg) -> None: + super().__init__( + *arg, + num_convs=num_convs, + upsample_cfg=dict(type=None), + init_cfg=None, + **kwarg) + self.init_cfg = init_cfg + self.num_fcs = num_fcs + assert self.num_fcs > 0 + self.fc_out_channels = fc_out_channels + self.downsample_factor = downsample_factor + assert self.downsample_factor >= 1 + # remove conv_logit + delattr(self, 'conv_logits') + + if downsample_factor > 1: + downsample_in_channels = ( + self.conv_out_channels + if self.num_convs > 0 else self.in_channels) + self.downsample_conv = ConvModule( + downsample_in_channels, + self.conv_out_channels, + kernel_size=downsample_factor, + stride=downsample_factor, + padding=0, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg) + else: + self.downsample_conv = None + + self.output_size = (self.roi_feat_size[0] // downsample_factor, + self.roi_feat_size[1] // downsample_factor) + self.output_area = self.output_size[0] * self.output_size[1] + + last_layer_dim = self.conv_out_channels * self.output_area + + self.fcs = ModuleList() + for i in range(num_fcs): + fc_in_channels = ( + last_layer_dim if i == 0 else self.fc_out_channels) + self.fcs.append(Linear(fc_in_channels, self.fc_out_channels)) + last_layer_dim = self.fc_out_channels + output_channels = self.num_classes * self.output_area + self.fc_logits = Linear(last_layer_dim, output_channels) + + def init_weights(self) -> None: + """Initialize weights.""" + super(FCNMaskHead, self).init_weights() + + def forward(self, x: Tensor) -> Tensor: + """Forward features from the upstream network. + + Args: + x (Tensor): Extract mask RoI features. + + Returns: + Tensor: Predicted foreground masks. + """ + for conv in self.convs: + x = conv(x) + + if self.downsample_conv is not None: + x = self.downsample_conv(x) + + x = x.flatten(1) + for fc in self.fcs: + x = self.relu(fc(x)) + mask_preds = self.fc_logits(x).view( + x.size(0), self.num_classes, *self.output_size) + return mask_preds diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/dynamic_mask_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/dynamic_mask_head.py new file mode 100644 index 00000000..f33612b1 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/dynamic_mask_head.py @@ -0,0 +1,166 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn as nn +from mmengine.config import ConfigDict +from torch import Tensor + +from mmdet.models.task_modules import SamplingResult +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, InstanceList, OptConfigType, reduce_mean +from .fcn_mask_head import FCNMaskHead + + +@MODELS.register_module() +class DynamicMaskHead(FCNMaskHead): + r"""Dynamic Mask Head for + `Instances as Queries `_ + + Args: + num_convs (int): Number of convolution layer. + Defaults to 4. + roi_feat_size (int): The output size of RoI extractor, + Defaults to 14. + in_channels (int): Input feature channels. + Defaults to 256. + conv_kernel_size (int): Kernel size of convolution layers. + Defaults to 3. + conv_out_channels (int): Output channels of convolution layers. + Defaults to 256. + num_classes (int): Number of classes. + Defaults to 80 + class_agnostic (int): Whether generate class agnostic prediction. + Defaults to False. + dropout (float): Probability of drop the channel. + Defaults to 0.0 + upsample_cfg (:obj:`ConfigDict` or dict): The config for + upsample layer. + conv_cfg (:obj:`ConfigDict` or dict, optional): The convolution + layer config. + norm_cfg (:obj:`ConfigDict` or dict, optional): The norm layer config. + dynamic_conv_cfg (:obj:`ConfigDict` or dict): The dynamic convolution + layer config. + loss_mask (:obj:`ConfigDict` or dict): The config for mask loss. + """ + + def __init__(self, + num_convs: int = 4, + roi_feat_size: int = 14, + in_channels: int = 256, + conv_kernel_size: int = 3, + conv_out_channels: int = 256, + num_classes: int = 80, + class_agnostic: bool = False, + upsample_cfg: ConfigType = dict( + type='deconv', scale_factor=2), + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + dynamic_conv_cfg: ConfigType = dict( + type='DynamicConv', + in_channels=256, + feat_channels=64, + out_channels=256, + input_feat_shape=14, + with_proj=False, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN')), + loss_mask: ConfigType = dict( + type='DiceLoss', loss_weight=8.0), + **kwargs) -> None: + super().__init__( + num_convs=num_convs, + roi_feat_size=roi_feat_size, + in_channels=in_channels, + conv_kernel_size=conv_kernel_size, + conv_out_channels=conv_out_channels, + num_classes=num_classes, + class_agnostic=class_agnostic, + upsample_cfg=upsample_cfg, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + loss_mask=loss_mask, + **kwargs) + assert class_agnostic is False, \ + 'DynamicMaskHead only support class_agnostic=False' + self.fp16_enabled = False + + self.instance_interactive_conv = MODELS.build(dynamic_conv_cfg) + + def init_weights(self) -> None: + """Use xavier initialization for all weight parameter and set + classification head bias as a specific value when use focal loss.""" + for p in self.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + nn.init.constant_(self.conv_logits.bias, 0.) + + def forward(self, roi_feat: Tensor, proposal_feat: Tensor) -> Tensor: + """Forward function of DynamicMaskHead. + + Args: + roi_feat (Tensor): Roi-pooling features with shape + (batch_size*num_proposals, feature_dimensions, + pooling_h , pooling_w). + proposal_feat (Tensor): Intermediate feature get from + diihead in last stage, has shape + (batch_size*num_proposals, feature_dimensions) + + Returns: + mask_preds (Tensor): Predicted foreground masks with shape + (batch_size*num_proposals, num_classes, pooling_h*2, pooling_w*2). + """ + + proposal_feat = proposal_feat.reshape(-1, self.in_channels) + proposal_feat_iic = self.instance_interactive_conv( + proposal_feat, roi_feat) + + x = proposal_feat_iic.permute(0, 2, 1).reshape(roi_feat.size()) + + for conv in self.convs: + x = conv(x) + if self.upsample is not None: + x = self.upsample(x) + if self.upsample_method == 'deconv': + x = self.relu(x) + mask_preds = self.conv_logits(x) + return mask_preds + + def loss_and_target(self, mask_preds: Tensor, + sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList, + rcnn_train_cfg: ConfigDict) -> dict: + """Calculate the loss based on the features extracted by the mask head. + + Args: + mask_preds (Tensor): Predicted foreground masks, has shape + (num_pos, num_classes, h, w). + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + rcnn_train_cfg (obj:ConfigDict): `train_cfg` of RCNN. + + Returns: + dict: A dictionary of loss and targets components. + """ + mask_targets = self.get_targets( + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=rcnn_train_cfg) + pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results]) + + num_pos = pos_labels.new_ones(pos_labels.size()).float().sum() + avg_factor = torch.clamp(reduce_mean(num_pos), min=1.).item() + loss = dict() + if mask_preds.size(0) == 0: + loss_mask = mask_preds.sum() + else: + loss_mask = self.loss_mask( + mask_preds[torch.arange(num_pos).long(), pos_labels, + ...].sigmoid(), + mask_targets, + avg_factor=avg_factor) + loss['loss_mask'] = loss_mask + return dict(loss_mask=loss, mask_targets=mask_targets) diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/fcn_mask_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/fcn_mask_head.py new file mode 100644 index 00000000..3a089dfa --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/fcn_mask_head.py @@ -0,0 +1,474 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, build_conv_layer, build_upsample_layer +from mmcv.ops.carafe import CARAFEPack +from mmengine.config import ConfigDict +from mmengine.model import BaseModule, ModuleList +from mmengine.structures import InstanceData +from torch import Tensor +from torch.nn.modules.utils import _pair + +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.models.utils import empty_instances +from mmdet.registry import MODELS +from mmdet.structures.mask import mask_target +from mmdet.utils import ConfigType, InstanceList, OptConfigType, OptMultiConfig + +BYTES_PER_FLOAT = 4 +# TODO: This memory limit may be too much or too little. It would be better to +# determine it based on available resources. +GPU_MEM_LIMIT = 1024**3 # 1 GB memory limit + + +@MODELS.register_module() +class FCNMaskHead(BaseModule): + + def __init__(self, + num_convs: int = 4, + roi_feat_size: int = 14, + in_channels: int = 256, + conv_kernel_size: int = 3, + conv_out_channels: int = 256, + num_classes: int = 80, + class_agnostic: int = False, + upsample_cfg: ConfigType = dict( + type='deconv', scale_factor=2), + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + predictor_cfg: ConfigType = dict(type='Conv'), + loss_mask: ConfigType = dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0), + init_cfg: OptMultiConfig = None) -> None: + assert init_cfg is None, 'To prevent abnormal initialization ' \ + 'behavior, init_cfg is not allowed to be set' + super().__init__(init_cfg=init_cfg) + self.upsample_cfg = upsample_cfg.copy() + if self.upsample_cfg['type'] not in [ + None, 'deconv', 'nearest', 'bilinear', 'carafe' + ]: + raise ValueError( + f'Invalid upsample method {self.upsample_cfg["type"]}, ' + 'accepted methods are "deconv", "nearest", "bilinear", ' + '"carafe"') + self.num_convs = num_convs + # WARN: roi_feat_size is reserved and not used + self.roi_feat_size = _pair(roi_feat_size) + self.in_channels = in_channels + self.conv_kernel_size = conv_kernel_size + self.conv_out_channels = conv_out_channels + self.upsample_method = self.upsample_cfg.get('type') + self.scale_factor = self.upsample_cfg.pop('scale_factor', None) + self.num_classes = num_classes + self.class_agnostic = class_agnostic + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.predictor_cfg = predictor_cfg + self.loss_mask = MODELS.build(loss_mask) + + self.convs = ModuleList() + for i in range(self.num_convs): + in_channels = ( + self.in_channels if i == 0 else self.conv_out_channels) + padding = (self.conv_kernel_size - 1) // 2 + self.convs.append( + ConvModule( + in_channels, + self.conv_out_channels, + self.conv_kernel_size, + padding=padding, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg)) + upsample_in_channels = ( + self.conv_out_channels if self.num_convs > 0 else in_channels) + upsample_cfg_ = self.upsample_cfg.copy() + if self.upsample_method is None: + self.upsample = None + elif self.upsample_method == 'deconv': + upsample_cfg_.update( + in_channels=upsample_in_channels, + out_channels=self.conv_out_channels, + kernel_size=self.scale_factor, + stride=self.scale_factor) + self.upsample = build_upsample_layer(upsample_cfg_) + elif self.upsample_method == 'carafe': + upsample_cfg_.update( + channels=upsample_in_channels, scale_factor=self.scale_factor) + self.upsample = build_upsample_layer(upsample_cfg_) + else: + # suppress warnings + align_corners = (None + if self.upsample_method == 'nearest' else False) + upsample_cfg_.update( + scale_factor=self.scale_factor, + mode=self.upsample_method, + align_corners=align_corners) + self.upsample = build_upsample_layer(upsample_cfg_) + + out_channels = 1 if self.class_agnostic else self.num_classes + logits_in_channel = ( + self.conv_out_channels + if self.upsample_method == 'deconv' else upsample_in_channels) + self.conv_logits = build_conv_layer(self.predictor_cfg, + logits_in_channel, out_channels, 1) + self.relu = nn.ReLU(inplace=True) + self.debug_imgs = None + + def init_weights(self) -> None: + """Initialize the weights.""" + super().init_weights() + for m in [self.upsample, self.conv_logits]: + if m is None: + continue + elif isinstance(m, CARAFEPack): + m.init_weights() + elif hasattr(m, 'weight') and hasattr(m, 'bias'): + nn.init.kaiming_normal_( + m.weight, mode='fan_out', nonlinearity='relu') + nn.init.constant_(m.bias, 0) + + def forward(self, x: Tensor) -> Tensor: + """Forward features from the upstream network. + + Args: + x (Tensor): Extract mask RoI features. + + Returns: + Tensor: Predicted foreground masks. + """ + for conv in self.convs: + x = conv(x) + if self.upsample is not None: + x = self.upsample(x) + if self.upsample_method == 'deconv': + x = self.relu(x) + mask_preds = self.conv_logits(x) + return mask_preds + + def get_targets(self, sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList, + rcnn_train_cfg: ConfigDict) -> Tensor: + """Calculate the ground truth for all samples in a batch according to + the sampling_results. + + Args: + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + rcnn_train_cfg (obj:ConfigDict): `train_cfg` of RCNN. + + Returns: + Tensor: Mask target of each positive proposals in the image. + """ + pos_proposals = [res.pos_priors for res in sampling_results] + pos_assigned_gt_inds = [ + res.pos_assigned_gt_inds for res in sampling_results + ] + gt_masks = [res.masks for res in batch_gt_instances] + mask_targets = mask_target(pos_proposals, pos_assigned_gt_inds, + gt_masks, rcnn_train_cfg) + return mask_targets + + def loss_and_target(self, mask_preds: Tensor, + sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList, + rcnn_train_cfg: ConfigDict) -> dict: + """Calculate the loss based on the features extracted by the mask head. + + Args: + mask_preds (Tensor): Predicted foreground masks, has shape + (num_pos, num_classes, h, w). + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + rcnn_train_cfg (obj:ConfigDict): `train_cfg` of RCNN. + + Returns: + dict: A dictionary of loss and targets components. + """ + mask_targets = self.get_targets( + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=rcnn_train_cfg) + + pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results]) + + loss = dict() + if mask_preds.size(0) == 0: + loss_mask = mask_preds.sum() + else: + if self.class_agnostic: + loss_mask = self.loss_mask(mask_preds, mask_targets, + torch.zeros_like(pos_labels)) + else: + loss_mask = self.loss_mask(mask_preds, mask_targets, + pos_labels) + loss['loss_mask'] = loss_mask + # TODO: which algorithm requires mask_targets? + return dict(loss_mask=loss, mask_targets=mask_targets) + + def predict_by_feat(self, + mask_preds: Tuple[Tensor], + results_list: List[InstanceData], + batch_img_metas: List[dict], + rcnn_test_cfg: ConfigDict, + rescale: bool = False, + activate_map: bool = False) -> InstanceList: + """Transform a batch of output features extracted from the head into + mask results. + + Args: + mask_preds (tuple[Tensor]): Tuple of predicted foreground masks, + each has shape (n, num_classes, h, w). + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + batch_img_metas (list[dict]): List of image information. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of Bbox Head. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + activate_map (book): Whether get results with augmentations test. + If True, the `mask_preds` will not process with sigmoid. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + assert len(mask_preds) == len(results_list) == len(batch_img_metas) + + for img_id in range(len(batch_img_metas)): + img_meta = batch_img_metas[img_id] + results = results_list[img_id] + bboxes = results.bboxes + if bboxes.shape[0] == 0: + results_list[img_id] = empty_instances( + [img_meta], + bboxes.device, + task_type='mask', + instance_results=[results], + mask_thr_binary=rcnn_test_cfg.mask_thr_binary)[0] + else: + im_mask = self._predict_by_feat_single( + mask_preds=mask_preds[img_id], + bboxes=bboxes, + labels=results.labels, + img_meta=img_meta, + rcnn_test_cfg=rcnn_test_cfg, + rescale=rescale, + activate_map=activate_map) + results.masks = im_mask + return results_list + + def _predict_by_feat_single(self, + mask_preds: Tensor, + bboxes: Tensor, + labels: Tensor, + img_meta: dict, + rcnn_test_cfg: ConfigDict, + rescale: bool = False, + activate_map: bool = False) -> Tensor: + """Get segmentation masks from mask_preds and bboxes. + + Args: + mask_preds (Tensor): Predicted foreground masks, has shape + (n, num_classes, h, w). + bboxes (Tensor): Predicted bboxes, has shape (n, 4) + labels (Tensor): Labels of bboxes, has shape (n, ) + img_meta (dict): image information. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of Bbox Head. + Defaults to None. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + activate_map (book): Whether get results with augmentations test. + If True, the `mask_preds` will not process with sigmoid. + Defaults to False. + + Returns: + Tensor: Encoded masks, has shape (n, img_w, img_h) + + Example: + >>> from mmengine.config import Config + >>> from mmdet.models.roi_heads.mask_heads.fcn_mask_head import * # NOQA + >>> N = 7 # N = number of extracted ROIs + >>> C, H, W = 11, 32, 32 + >>> # Create example instance of FCN Mask Head. + >>> self = FCNMaskHead(num_classes=C, num_convs=0) + >>> inputs = torch.rand(N, self.in_channels, H, W) + >>> mask_preds = self.forward(inputs) + >>> # Each input is associated with some bounding box + >>> bboxes = torch.Tensor([[1, 1, 42, 42 ]] * N) + >>> labels = torch.randint(0, C, size=(N,)) + >>> rcnn_test_cfg = Config({'mask_thr_binary': 0, }) + >>> ori_shape = (H * 4, W * 4) + >>> scale_factor = (1, 1) + >>> rescale = False + >>> img_meta = {'scale_factor': scale_factor, + ... 'ori_shape': ori_shape} + >>> # Encoded masks are a list for each category. + >>> encoded_masks = self._get_seg_masks_single( + ... mask_preds, bboxes, labels, + ... img_meta, rcnn_test_cfg, rescale) + >>> assert encoded_masks.size()[0] == N + >>> assert encoded_masks.size()[1:] == ori_shape + """ + scale_factor = bboxes.new_tensor(img_meta['scale_factor']).repeat( + (1, 2)) + img_h, img_w = img_meta['ori_shape'][:2] + device = bboxes.device + + if not activate_map: + mask_preds = mask_preds.sigmoid() + else: + # In AugTest, has been activated before + mask_preds = bboxes.new_tensor(mask_preds) + + if rescale: # in-placed rescale the bboxes + bboxes /= scale_factor + else: + w_scale, h_scale = scale_factor[0, 0], scale_factor[0, 1] + img_h = np.round(img_h * h_scale.item()).astype(np.int32) + img_w = np.round(img_w * w_scale.item()).astype(np.int32) + + N = len(mask_preds) + # The actual implementation split the input into chunks, + # and paste them chunk by chunk. + if device.type == 'cpu': + # CPU is most efficient when they are pasted one by one with + # skip_empty=True, so that it performs minimal number of + # operations. + num_chunks = N + else: + # GPU benefits from parallelism for larger chunks, + # but may have memory issue + # the types of img_w and img_h are np.int32, + # when the image resolution is large, + # the calculation of num_chunks will overflow. + # so we need to change the types of img_w and img_h to int. + # See https://github.com/open-mmlab/mmdetection/pull/5191 + num_chunks = int( + np.ceil(N * int(img_h) * int(img_w) * BYTES_PER_FLOAT / + GPU_MEM_LIMIT)) + assert (num_chunks <= + N), 'Default GPU_MEM_LIMIT is too small; try increasing it' + chunks = torch.chunk(torch.arange(N, device=device), num_chunks) + + threshold = rcnn_test_cfg.mask_thr_binary + im_mask = torch.zeros( + N, + img_h, + img_w, + device=device, + dtype=torch.bool if threshold >= 0 else torch.uint8) + + if not self.class_agnostic: + mask_preds = mask_preds[range(N), labels][:, None] + + for inds in chunks: + masks_chunk, spatial_inds = _do_paste_mask( + mask_preds[inds], + bboxes[inds], + img_h, + img_w, + skip_empty=device.type == 'cpu') + + if threshold >= 0: + masks_chunk = (masks_chunk >= threshold).to(dtype=torch.bool) + else: + # for visualization and debugging + masks_chunk = (masks_chunk * 255).to(dtype=torch.uint8) + + im_mask[(inds, ) + spatial_inds] = masks_chunk + return im_mask + + +def _do_paste_mask(masks: Tensor, + boxes: Tensor, + img_h: int, + img_w: int, + skip_empty: bool = True) -> tuple: + """Paste instance masks according to boxes. + + This implementation is modified from + https://github.com/facebookresearch/detectron2/ + + Args: + masks (Tensor): N, 1, H, W + boxes (Tensor): N, 4 + img_h (int): Height of the image to be pasted. + img_w (int): Width of the image to be pasted. + skip_empty (bool): Only paste masks within the region that + tightly bound all boxes, and returns the results this region only. + An important optimization for CPU. + + Returns: + tuple: (Tensor, tuple). The first item is mask tensor, the second one + is the slice object. + + If skip_empty == False, the whole image will be pasted. It will + return a mask of shape (N, img_h, img_w) and an empty tuple. + + If skip_empty == True, only area around the mask will be pasted. + A mask of shape (N, h', w') and its start and end coordinates + in the original image will be returned. + """ + # On GPU, paste all masks together (up to chunk size) + # by using the entire image to sample the masks + # Compared to pasting them one by one, + # this has more operations but is faster on COCO-scale dataset. + device = masks.device + if skip_empty: + x0_int, y0_int = torch.clamp( + boxes.min(dim=0).values.floor()[:2] - 1, + min=0).to(dtype=torch.int32) + x1_int = torch.clamp( + boxes[:, 2].max().ceil() + 1, max=img_w).to(dtype=torch.int32) + y1_int = torch.clamp( + boxes[:, 3].max().ceil() + 1, max=img_h).to(dtype=torch.int32) + else: + x0_int, y0_int = 0, 0 + x1_int, y1_int = img_w, img_h + x0, y0, x1, y1 = torch.split(boxes, 1, dim=1) # each is Nx1 + + N = masks.shape[0] + + img_y = torch.arange(y0_int, y1_int, device=device).to(torch.float32) + 0.5 + img_x = torch.arange(x0_int, x1_int, device=device).to(torch.float32) + 0.5 + img_y = (img_y - y0) / (y1 - y0) * 2 - 1 + img_x = (img_x - x0) / (x1 - x0) * 2 - 1 + # img_x, img_y have shapes (N, w), (N, h) + # IsInf op is not supported with ONNX<=1.7.0 + if not torch.onnx.is_in_onnx_export(): + if torch.isinf(img_x).any(): + inds = torch.where(torch.isinf(img_x)) + img_x[inds] = 0 + if torch.isinf(img_y).any(): + inds = torch.where(torch.isinf(img_y)) + img_y[inds] = 0 + + gx = img_x[:, None, :].expand(N, img_y.size(1), img_x.size(1)) + gy = img_y[:, :, None].expand(N, img_y.size(1), img_x.size(1)) + grid = torch.stack([gx, gy], dim=3) + + img_masks = F.grid_sample( + masks.to(dtype=torch.float32), grid, align_corners=False) + + if skip_empty: + return img_masks[:, 0], (slice(y0_int, y1_int), slice(x0_int, x1_int)) + else: + return img_masks[:, 0], () diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/feature_relay_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/feature_relay_head.py new file mode 100644 index 00000000..0c34561f --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/feature_relay_head.py @@ -0,0 +1,68 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch.nn as nn +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import MultiConfig + + +@MODELS.register_module() +class FeatureRelayHead(BaseModule): + """Feature Relay Head used in `SCNet `_. + + Args: + in_channels (int): number of input channels. Defaults to 256. + conv_out_channels (int): number of output channels before + classification layer. Defaults to 256. + roi_feat_size (int): roi feat size at box head. Default: 7. + scale_factor (int): scale factor to match roi feat size + at mask head. Defaults to 2. + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`]): Initialization config dict. Defaults to + dict(type='Kaiming', layer='Linear'). + """ + + def __init__( + self, + in_channels: int = 1024, + out_conv_channels: int = 256, + roi_feat_size: int = 7, + scale_factor: int = 2, + init_cfg: MultiConfig = dict(type='Kaiming', layer='Linear') + ) -> None: + super().__init__(init_cfg=init_cfg) + assert isinstance(roi_feat_size, int) + + self.in_channels = in_channels + self.out_conv_channels = out_conv_channels + self.roi_feat_size = roi_feat_size + self.out_channels = (roi_feat_size**2) * out_conv_channels + self.scale_factor = scale_factor + self.fp16_enabled = False + + self.fc = nn.Linear(self.in_channels, self.out_channels) + self.upsample = nn.Upsample( + scale_factor=scale_factor, mode='bilinear', align_corners=True) + + def forward(self, x: Tensor) -> Optional[Tensor]: + """Forward function. + + Args: + x (Tensor): Input feature. + + Returns: + Optional[Tensor]: Output feature. When the first dim of input is + 0, None is returned. + """ + N, _ = x.shape + if N > 0: + out_C = self.out_conv_channels + out_HW = self.roi_feat_size + x = self.fc(x) + x = x.reshape(N, out_C, out_HW, out_HW) + x = self.upsample(x) + return x + return None diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/fused_semantic_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/fused_semantic_head.py new file mode 100644 index 00000000..d20beb29 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/fused_semantic_head.py @@ -0,0 +1,144 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import Tuple + +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.config import ConfigDict +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import MultiConfig, OptConfigType + + +@MODELS.register_module() +class FusedSemanticHead(BaseModule): + r"""Multi-level fused semantic segmentation head. + + .. code-block:: none + + in_1 -> 1x1 conv --- + | + in_2 -> 1x1 conv -- | + || + in_3 -> 1x1 conv - || + ||| /-> 1x1 conv (mask prediction) + in_4 -> 1x1 conv -----> 3x3 convs (*4) + | \-> 1x1 conv (feature) + in_5 -> 1x1 conv --- + """ # noqa: W605 + + def __init__( + self, + num_ins: int, + fusion_level: int, + seg_scale_factor=1 / 8, + num_convs: int = 4, + in_channels: int = 256, + conv_out_channels: int = 256, + num_classes: int = 183, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + ignore_label: int = None, + loss_weight: float = None, + loss_seg: ConfigDict = dict( + type='CrossEntropyLoss', ignore_index=255, loss_weight=0.2), + init_cfg: MultiConfig = dict( + type='Kaiming', override=dict(name='conv_logits')) + ) -> None: + super().__init__(init_cfg=init_cfg) + self.num_ins = num_ins + self.fusion_level = fusion_level + self.seg_scale_factor = seg_scale_factor + self.num_convs = num_convs + self.in_channels = in_channels + self.conv_out_channels = conv_out_channels + self.num_classes = num_classes + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.fp16_enabled = False + + self.lateral_convs = nn.ModuleList() + for i in range(self.num_ins): + self.lateral_convs.append( + ConvModule( + self.in_channels, + self.in_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + inplace=False)) + + self.convs = nn.ModuleList() + for i in range(self.num_convs): + in_channels = self.in_channels if i == 0 else conv_out_channels + self.convs.append( + ConvModule( + in_channels, + conv_out_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + self.conv_embedding = ConvModule( + conv_out_channels, + conv_out_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg) + self.conv_logits = nn.Conv2d(conv_out_channels, self.num_classes, 1) + if ignore_label: + loss_seg['ignore_index'] = ignore_label + if loss_weight: + loss_seg['loss_weight'] = loss_weight + if ignore_label or loss_weight: + warnings.warn('``ignore_label`` and ``loss_weight`` would be ' + 'deprecated soon. Please set ``ingore_index`` and ' + '``loss_weight`` in ``loss_seg`` instead.') + self.criterion = MODELS.build(loss_seg) + + def forward(self, feats: Tuple[Tensor]) -> Tuple[Tensor]: + """Forward function. + + Args: + feats (tuple[Tensor]): Multi scale feature maps. + + Returns: + tuple[Tensor]: + + - mask_preds (Tensor): Predicted mask logits. + - x (Tensor): Fused feature. + """ + x = self.lateral_convs[self.fusion_level](feats[self.fusion_level]) + fused_size = tuple(x.shape[-2:]) + for i, feat in enumerate(feats): + if i != self.fusion_level: + feat = F.interpolate( + feat, size=fused_size, mode='bilinear', align_corners=True) + # fix runtime error of "+=" inplace operation in PyTorch 1.10 + x = x + self.lateral_convs[i](feat) + + for i in range(self.num_convs): + x = self.convs[i](x) + + mask_preds = self.conv_logits(x) + x = self.conv_embedding(x) + return mask_preds, x + + def loss(self, mask_preds: Tensor, labels: Tensor) -> Tensor: + """Loss function. + + Args: + mask_preds (Tensor): Predicted mask logits. + labels (Tensor): Ground truth. + + Returns: + Tensor: Semantic segmentation loss. + """ + labels = F.interpolate( + labels.float(), scale_factor=self.seg_scale_factor, mode='nearest') + labels = labels.squeeze(1).long() + loss_semantic_seg = self.criterion(mask_preds, labels) + return loss_semantic_seg diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/global_context_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/global_context_head.py new file mode 100644 index 00000000..cb947ea5 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/global_context_head.py @@ -0,0 +1,127 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.models.layers import ResLayer, SimplifiedBasicBlock +from mmdet.registry import MODELS +from mmdet.utils import MultiConfig, OptConfigType + + +@MODELS.register_module() +class GlobalContextHead(BaseModule): + """Global context head used in `SCNet `_. + + Args: + num_convs (int, optional): number of convolutional layer in GlbCtxHead. + Defaults to 4. + in_channels (int, optional): number of input channels. Defaults to 256. + conv_out_channels (int, optional): number of output channels before + classification layer. Defaults to 256. + num_classes (int, optional): number of classes. Defaults to 80. + loss_weight (float, optional): global context loss weight. + Defaults to 1. + conv_cfg (dict, optional): config to init conv layer. Defaults to None. + norm_cfg (dict, optional): config to init norm layer. Defaults to None. + conv_to_res (bool, optional): if True, 2 convs will be grouped into + 1 `SimplifiedBasicBlock` using a skip connection. + Defaults to False. + init_cfg (:obj:`ConfigDict` or dict or list[dict] or + list[:obj:`ConfigDict`]): Initialization config dict. Defaults to + dict(type='Normal', std=0.01, override=dict(name='fc')). + """ + + def __init__( + self, + num_convs: int = 4, + in_channels: int = 256, + conv_out_channels: int = 256, + num_classes: int = 80, + loss_weight: float = 1.0, + conv_cfg: OptConfigType = None, + norm_cfg: OptConfigType = None, + conv_to_res: bool = False, + init_cfg: MultiConfig = dict( + type='Normal', std=0.01, override=dict(name='fc')) + ) -> None: + super().__init__(init_cfg=init_cfg) + self.num_convs = num_convs + self.in_channels = in_channels + self.conv_out_channels = conv_out_channels + self.num_classes = num_classes + self.loss_weight = loss_weight + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.conv_to_res = conv_to_res + self.fp16_enabled = False + + if self.conv_to_res: + num_res_blocks = num_convs // 2 + self.convs = ResLayer( + SimplifiedBasicBlock, + in_channels, + self.conv_out_channels, + num_res_blocks, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg) + self.num_convs = num_res_blocks + else: + self.convs = nn.ModuleList() + for i in range(self.num_convs): + in_channels = self.in_channels if i == 0 else conv_out_channels + self.convs.append( + ConvModule( + in_channels, + conv_out_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + + self.pool = nn.AdaptiveAvgPool2d(1) + self.fc = nn.Linear(conv_out_channels, num_classes) + + self.criterion = nn.BCEWithLogitsLoss() + + def forward(self, feats: Tuple[Tensor]) -> Tuple[Tensor]: + """Forward function. + + Args: + feats (Tuple[Tensor]): Multi-scale feature maps. + + Returns: + Tuple[Tensor]: + + - mc_pred (Tensor): Multi-class prediction. + - x (Tensor): Global context feature. + """ + x = feats[-1] + for i in range(self.num_convs): + x = self.convs[i](x) + x = self.pool(x) + + # multi-class prediction + mc_pred = x.reshape(x.size(0), -1) + mc_pred = self.fc(mc_pred) + + return mc_pred, x + + def loss(self, pred: Tensor, labels: List[Tensor]) -> Tensor: + """Loss function. + + Args: + pred (Tensor): Logits. + labels (list[Tensor]): Grouth truths. + + Returns: + Tensor: Loss. + """ + labels = [lbl.unique() for lbl in labels] + targets = pred.new_zeros(pred.size()) + for i, label in enumerate(labels): + targets[i, label] = 1.0 + loss = self.loss_weight * self.criterion(pred, targets) + return loss diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/grid_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/grid_head.py new file mode 100644 index 00000000..d9514ae7 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/grid_head.py @@ -0,0 +1,490 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Tuple + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.config import ConfigDict +from mmengine.model import BaseModule +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, InstanceList, MultiConfig, OptConfigType + + +@MODELS.register_module() +class GridHead(BaseModule): + """Implementation of `Grid Head `_ + + Args: + grid_points (int): The number of grid points. Defaults to 9. + num_convs (int): The number of convolution layers. Defaults to 8. + roi_feat_size (int): RoI feature size. Default to 14. + in_channels (int): The channel number of inputs features. + Defaults to 256. + conv_kernel_size (int): The kernel size of convolution layers. + Defaults to 3. + point_feat_channels (int): The number of channels of each point + features. Defaults to 64. + class_agnostic (bool): Whether use class agnostic classification. + If so, the output channels of logits will be 1. Defaults to False. + loss_grid (:obj:`ConfigDict` or dict): Config of grid loss. + conv_cfg (:obj:`ConfigDict` or dict, optional) dictionary to + construct and config conv layer. + norm_cfg (:obj:`ConfigDict` or dict): dictionary to construct and + config norm layer. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. + """ + + def __init__( + self, + grid_points: int = 9, + num_convs: int = 8, + roi_feat_size: int = 14, + in_channels: int = 256, + conv_kernel_size: int = 3, + point_feat_channels: int = 64, + deconv_kernel_size: int = 4, + class_agnostic: bool = False, + loss_grid: ConfigType = dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=15), + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict(type='GN', num_groups=36), + init_cfg: MultiConfig = [ + dict(type='Kaiming', layer=['Conv2d', 'Linear']), + dict( + type='Normal', + layer='ConvTranspose2d', + std=0.001, + override=dict( + type='Normal', + name='deconv2', + std=0.001, + bias=-np.log(0.99 / 0.01))) + ] + ) -> None: + super().__init__(init_cfg=init_cfg) + self.grid_points = grid_points + self.num_convs = num_convs + self.roi_feat_size = roi_feat_size + self.in_channels = in_channels + self.conv_kernel_size = conv_kernel_size + self.point_feat_channels = point_feat_channels + self.conv_out_channels = self.point_feat_channels * self.grid_points + self.class_agnostic = class_agnostic + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + if isinstance(norm_cfg, dict) and norm_cfg['type'] == 'GN': + assert self.conv_out_channels % norm_cfg['num_groups'] == 0 + + assert self.grid_points >= 4 + self.grid_size = int(np.sqrt(self.grid_points)) + if self.grid_size * self.grid_size != self.grid_points: + raise ValueError('grid_points must be a square number') + + # the predicted heatmap is half of whole_map_size + if not isinstance(self.roi_feat_size, int): + raise ValueError('Only square RoIs are supporeted in Grid R-CNN') + self.whole_map_size = self.roi_feat_size * 4 + + # compute point-wise sub-regions + self.sub_regions = self.calc_sub_regions() + + self.convs = [] + for i in range(self.num_convs): + in_channels = ( + self.in_channels if i == 0 else self.conv_out_channels) + stride = 2 if i == 0 else 1 + padding = (self.conv_kernel_size - 1) // 2 + self.convs.append( + ConvModule( + in_channels, + self.conv_out_channels, + self.conv_kernel_size, + stride=stride, + padding=padding, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + bias=True)) + self.convs = nn.Sequential(*self.convs) + + self.deconv1 = nn.ConvTranspose2d( + self.conv_out_channels, + self.conv_out_channels, + kernel_size=deconv_kernel_size, + stride=2, + padding=(deconv_kernel_size - 2) // 2, + groups=grid_points) + self.norm1 = nn.GroupNorm(grid_points, self.conv_out_channels) + self.deconv2 = nn.ConvTranspose2d( + self.conv_out_channels, + grid_points, + kernel_size=deconv_kernel_size, + stride=2, + padding=(deconv_kernel_size - 2) // 2, + groups=grid_points) + + # find the 4-neighbor of each grid point + self.neighbor_points = [] + grid_size = self.grid_size + for i in range(grid_size): # i-th column + for j in range(grid_size): # j-th row + neighbors = [] + if i > 0: # left: (i - 1, j) + neighbors.append((i - 1) * grid_size + j) + if j > 0: # up: (i, j - 1) + neighbors.append(i * grid_size + j - 1) + if j < grid_size - 1: # down: (i, j + 1) + neighbors.append(i * grid_size + j + 1) + if i < grid_size - 1: # right: (i + 1, j) + neighbors.append((i + 1) * grid_size + j) + self.neighbor_points.append(tuple(neighbors)) + # total edges in the grid + self.num_edges = sum([len(p) for p in self.neighbor_points]) + + self.forder_trans = nn.ModuleList() # first-order feature transition + self.sorder_trans = nn.ModuleList() # second-order feature transition + for neighbors in self.neighbor_points: + fo_trans = nn.ModuleList() + so_trans = nn.ModuleList() + for _ in range(len(neighbors)): + # each transition module consists of a 5x5 depth-wise conv and + # 1x1 conv. + fo_trans.append( + nn.Sequential( + nn.Conv2d( + self.point_feat_channels, + self.point_feat_channels, + 5, + stride=1, + padding=2, + groups=self.point_feat_channels), + nn.Conv2d(self.point_feat_channels, + self.point_feat_channels, 1))) + so_trans.append( + nn.Sequential( + nn.Conv2d( + self.point_feat_channels, + self.point_feat_channels, + 5, + 1, + 2, + groups=self.point_feat_channels), + nn.Conv2d(self.point_feat_channels, + self.point_feat_channels, 1))) + self.forder_trans.append(fo_trans) + self.sorder_trans.append(so_trans) + + self.loss_grid = MODELS.build(loss_grid) + + def forward(self, x: Tensor) -> Dict[str, Tensor]: + """forward function of ``GridHead``. + + Args: + x (Tensor): RoI features, has shape + (num_rois, num_channels, roi_feat_size, roi_feat_size). + + Returns: + Dict[str, Tensor]: Return a dict including fused and unfused + heatmap. + """ + assert x.shape[-1] == x.shape[-2] == self.roi_feat_size + # RoI feature transformation, downsample 2x + x = self.convs(x) + + c = self.point_feat_channels + # first-order fusion + x_fo = [None for _ in range(self.grid_points)] + for i, points in enumerate(self.neighbor_points): + x_fo[i] = x[:, i * c:(i + 1) * c] + for j, point_idx in enumerate(points): + x_fo[i] = x_fo[i] + self.forder_trans[i][j]( + x[:, point_idx * c:(point_idx + 1) * c]) + + # second-order fusion + x_so = [None for _ in range(self.grid_points)] + for i, points in enumerate(self.neighbor_points): + x_so[i] = x[:, i * c:(i + 1) * c] + for j, point_idx in enumerate(points): + x_so[i] = x_so[i] + self.sorder_trans[i][j](x_fo[point_idx]) + + # predicted heatmap with fused features + x2 = torch.cat(x_so, dim=1) + x2 = self.deconv1(x2) + x2 = F.relu(self.norm1(x2), inplace=True) + heatmap = self.deconv2(x2) + + # predicted heatmap with original features (applicable during training) + if self.training: + x1 = x + x1 = self.deconv1(x1) + x1 = F.relu(self.norm1(x1), inplace=True) + heatmap_unfused = self.deconv2(x1) + else: + heatmap_unfused = heatmap + + return dict(fused=heatmap, unfused=heatmap_unfused) + + def calc_sub_regions(self) -> List[Tuple[float]]: + """Compute point specific representation regions. + + See `Grid R-CNN Plus `_ for details. + """ + # to make it consistent with the original implementation, half_size + # is computed as 2 * quarter_size, which is smaller + half_size = self.whole_map_size // 4 * 2 + sub_regions = [] + for i in range(self.grid_points): + x_idx = i // self.grid_size + y_idx = i % self.grid_size + if x_idx == 0: + sub_x1 = 0 + elif x_idx == self.grid_size - 1: + sub_x1 = half_size + else: + ratio = x_idx / (self.grid_size - 1) - 0.25 + sub_x1 = max(int(ratio * self.whole_map_size), 0) + + if y_idx == 0: + sub_y1 = 0 + elif y_idx == self.grid_size - 1: + sub_y1 = half_size + else: + ratio = y_idx / (self.grid_size - 1) - 0.25 + sub_y1 = max(int(ratio * self.whole_map_size), 0) + sub_regions.append( + (sub_x1, sub_y1, sub_x1 + half_size, sub_y1 + half_size)) + return sub_regions + + def get_targets(self, sampling_results: List[SamplingResult], + rcnn_train_cfg: ConfigDict) -> Tensor: + """Calculate the ground truth for all samples in a batch according to + the sampling_results.". + + Args: + sampling_results (List[:obj:`SamplingResult`]): Assign results of + all images in a batch after sampling. + rcnn_train_cfg (:obj:`ConfigDict`): `train_cfg` of RCNN. + + Returns: + Tensor: Grid heatmap targets. + """ + # mix all samples (across images) together. + pos_bboxes = torch.cat([res.pos_bboxes for res in sampling_results], + dim=0).cpu() + pos_gt_bboxes = torch.cat( + [res.pos_gt_bboxes for res in sampling_results], dim=0).cpu() + assert pos_bboxes.shape == pos_gt_bboxes.shape + + # expand pos_bboxes to 2x of original size + x1 = pos_bboxes[:, 0] - (pos_bboxes[:, 2] - pos_bboxes[:, 0]) / 2 + y1 = pos_bboxes[:, 1] - (pos_bboxes[:, 3] - pos_bboxes[:, 1]) / 2 + x2 = pos_bboxes[:, 2] + (pos_bboxes[:, 2] - pos_bboxes[:, 0]) / 2 + y2 = pos_bboxes[:, 3] + (pos_bboxes[:, 3] - pos_bboxes[:, 1]) / 2 + pos_bboxes = torch.stack([x1, y1, x2, y2], dim=-1) + pos_bbox_ws = (pos_bboxes[:, 2] - pos_bboxes[:, 0]).unsqueeze(-1) + pos_bbox_hs = (pos_bboxes[:, 3] - pos_bboxes[:, 1]).unsqueeze(-1) + + num_rois = pos_bboxes.shape[0] + map_size = self.whole_map_size + # this is not the final target shape + targets = torch.zeros((num_rois, self.grid_points, map_size, map_size), + dtype=torch.float) + + # pre-compute interpolation factors for all grid points. + # the first item is the factor of x-dim, and the second is y-dim. + # for a 9-point grid, factors are like (1, 0), (0.5, 0.5), (0, 1) + factors = [] + for j in range(self.grid_points): + x_idx = j // self.grid_size + y_idx = j % self.grid_size + factors.append((1 - x_idx / (self.grid_size - 1), + 1 - y_idx / (self.grid_size - 1))) + + radius = rcnn_train_cfg.pos_radius + radius2 = radius**2 + for i in range(num_rois): + # ignore small bboxes + if (pos_bbox_ws[i] <= self.grid_size + or pos_bbox_hs[i] <= self.grid_size): + continue + # for each grid point, mark a small circle as positive + for j in range(self.grid_points): + factor_x, factor_y = factors[j] + gridpoint_x = factor_x * pos_gt_bboxes[i, 0] + ( + 1 - factor_x) * pos_gt_bboxes[i, 2] + gridpoint_y = factor_y * pos_gt_bboxes[i, 1] + ( + 1 - factor_y) * pos_gt_bboxes[i, 3] + + cx = int((gridpoint_x - pos_bboxes[i, 0]) / pos_bbox_ws[i] * + map_size) + cy = int((gridpoint_y - pos_bboxes[i, 1]) / pos_bbox_hs[i] * + map_size) + + for x in range(cx - radius, cx + radius + 1): + for y in range(cy - radius, cy + radius + 1): + if x >= 0 and x < map_size and y >= 0 and y < map_size: + if (x - cx)**2 + (y - cy)**2 <= radius2: + targets[i, j, y, x] = 1 + # reduce the target heatmap size by a half + # proposed in Grid R-CNN Plus (https://arxiv.org/abs/1906.05688). + sub_targets = [] + for i in range(self.grid_points): + sub_x1, sub_y1, sub_x2, sub_y2 = self.sub_regions[i] + sub_targets.append(targets[:, [i], sub_y1:sub_y2, sub_x1:sub_x2]) + sub_targets = torch.cat(sub_targets, dim=1) + sub_targets = sub_targets.to(sampling_results[0].pos_bboxes.device) + return sub_targets + + def loss(self, grid_pred: Tensor, sample_idx: Tensor, + sampling_results: List[SamplingResult], + rcnn_train_cfg: ConfigDict) -> dict: + """Calculate the loss based on the features extracted by the grid head. + + Args: + grid_pred (dict[str, Tensor]): Outputs of grid_head forward. + sample_idx (Tensor): The sampling index of ``grid_pred``. + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + rcnn_train_cfg (obj:`ConfigDict`): `train_cfg` of RCNN. + + Returns: + dict: A dictionary of loss and targets components. + """ + grid_targets = self.get_targets(sampling_results, rcnn_train_cfg) + grid_targets = grid_targets[sample_idx] + + loss_fused = self.loss_grid(grid_pred['fused'], grid_targets) + loss_unfused = self.loss_grid(grid_pred['unfused'], grid_targets) + loss_grid = loss_fused + loss_unfused + return dict(loss_grid=loss_grid) + + def predict_by_feat(self, + grid_preds: Dict[str, Tensor], + results_list: List[InstanceData], + batch_img_metas: List[dict], + rescale: bool = False) -> InstanceList: + """Adjust the predicted bboxes from bbox head. + + Args: + grid_preds (dict[str, Tensor]): dictionary outputted by forward + function. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + batch_img_metas (list[dict]): List of image information. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape \ + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), the last \ + dimension 4 arrange as (x1, y1, x2, y2). + """ + num_roi_per_img = tuple(res.bboxes.size(0) for res in results_list) + grid_preds = { + k: v.split(num_roi_per_img, 0) + for k, v in grid_preds.items() + } + + for i, results in enumerate(results_list): + if len(results) != 0: + bboxes = self._predict_by_feat_single( + grid_pred=grid_preds['fused'][i], + bboxes=results.bboxes, + img_meta=batch_img_metas[i], + rescale=rescale) + results.bboxes = bboxes + return results_list + + def _predict_by_feat_single(self, + grid_pred: Tensor, + bboxes: Tensor, + img_meta: dict, + rescale: bool = False) -> Tensor: + """Adjust ``bboxes`` according to ``grid_pred``. + + Args: + grid_pred (Tensor): Grid fused heatmap. + bboxes (Tensor): Predicted bboxes, has shape (n, 4) + img_meta (dict): image information. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + Tensor: adjusted bboxes. + """ + assert bboxes.size(0) == grid_pred.size(0) + grid_pred = grid_pred.sigmoid() + + R, c, h, w = grid_pred.shape + half_size = self.whole_map_size // 4 * 2 + assert h == w == half_size + assert c == self.grid_points + + # find the point with max scores in the half-sized heatmap + grid_pred = grid_pred.view(R * c, h * w) + pred_scores, pred_position = grid_pred.max(dim=1) + xs = pred_position % w + ys = pred_position // w + + # get the position in the whole heatmap instead of half-sized heatmap + for i in range(self.grid_points): + xs[i::self.grid_points] += self.sub_regions[i][0] + ys[i::self.grid_points] += self.sub_regions[i][1] + + # reshape to (num_rois, grid_points) + pred_scores, xs, ys = tuple( + map(lambda x: x.view(R, c), [pred_scores, xs, ys])) + + # get expanded pos_bboxes + widths = (bboxes[:, 2] - bboxes[:, 0]).unsqueeze(-1) + heights = (bboxes[:, 3] - bboxes[:, 1]).unsqueeze(-1) + x1 = (bboxes[:, 0, None] - widths / 2) + y1 = (bboxes[:, 1, None] - heights / 2) + # map the grid point to the absolute coordinates + abs_xs = (xs.float() + 0.5) / w * widths + x1 + abs_ys = (ys.float() + 0.5) / h * heights + y1 + + # get the grid points indices that fall on the bbox boundaries + x1_inds = [i for i in range(self.grid_size)] + y1_inds = [i * self.grid_size for i in range(self.grid_size)] + x2_inds = [ + self.grid_points - self.grid_size + i + for i in range(self.grid_size) + ] + y2_inds = [(i + 1) * self.grid_size - 1 for i in range(self.grid_size)] + + # voting of all grid points on some boundary + bboxes_x1 = (abs_xs[:, x1_inds] * pred_scores[:, x1_inds]).sum( + dim=1, keepdim=True) / ( + pred_scores[:, x1_inds].sum(dim=1, keepdim=True)) + bboxes_y1 = (abs_ys[:, y1_inds] * pred_scores[:, y1_inds]).sum( + dim=1, keepdim=True) / ( + pred_scores[:, y1_inds].sum(dim=1, keepdim=True)) + bboxes_x2 = (abs_xs[:, x2_inds] * pred_scores[:, x2_inds]).sum( + dim=1, keepdim=True) / ( + pred_scores[:, x2_inds].sum(dim=1, keepdim=True)) + bboxes_y2 = (abs_ys[:, y2_inds] * pred_scores[:, y2_inds]).sum( + dim=1, keepdim=True) / ( + pred_scores[:, y2_inds].sum(dim=1, keepdim=True)) + + bboxes = torch.cat([bboxes_x1, bboxes_y1, bboxes_x2, bboxes_y2], dim=1) + bboxes[:, [0, 2]].clamp_(min=0, max=img_meta['img_shape'][1]) + bboxes[:, [1, 3]].clamp_(min=0, max=img_meta['img_shape'][0]) + + if rescale: + assert img_meta.get('scale_factor') is not None + bboxes /= bboxes.new_tensor(img_meta['scale_factor']).repeat( + (1, 2)) + + return bboxes diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/htc_mask_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/htc_mask_head.py new file mode 100644 index 00000000..73ac1e6e --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/htc_mask_head.py @@ -0,0 +1,65 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Union + +from mmcv.cnn import ConvModule +from torch import Tensor + +from mmdet.registry import MODELS +from .fcn_mask_head import FCNMaskHead + + +@MODELS.register_module() +class HTCMaskHead(FCNMaskHead): + """Mask head for HTC. + + Args: + with_conv_res (bool): Whether add conv layer for ``res_feat``. + Defaults to True. + """ + + def __init__(self, with_conv_res: bool = True, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.with_conv_res = with_conv_res + if self.with_conv_res: + self.conv_res = ConvModule( + self.conv_out_channels, + self.conv_out_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg) + + def forward(self, + x: Tensor, + res_feat: Optional[Tensor] = None, + return_logits: bool = True, + return_feat: bool = True) -> Union[Tensor, List[Tensor]]: + """ + Args: + x (Tensor): Feature map. + res_feat (Tensor, optional): Feature for residual connection. + Defaults to None. + return_logits (bool): Whether return mask logits. Defaults to True. + return_feat (bool): Whether return feature map. Defaults to True. + + Returns: + Union[Tensor, List[Tensor]]: The return result is one of three + results: res_feat, logits, or [logits, res_feat]. + """ + assert not (not return_logits and not return_feat) + if res_feat is not None: + assert self.with_conv_res + res_feat = self.conv_res(res_feat) + x = x + res_feat + for conv in self.convs: + x = conv(x) + res_feat = x + outs = [] + if return_logits: + x = self.upsample(x) + if self.upsample_method == 'deconv': + x = self.relu(x) + mask_preds = self.conv_logits(x) + outs.append(mask_preds) + if return_feat: + outs.append(res_feat) + return outs if len(outs) > 1 else outs[0] diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/mask_point_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/mask_point_head.py new file mode 100644 index 00000000..2084f59f --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/mask_point_head.py @@ -0,0 +1,284 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Modified from https://github.com/facebookresearch/detectron2/tree/master/projects/PointRend/point_head/point_head.py # noqa + +from typing import List, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmcv.ops import point_sample, rel_roi_point_to_rel_img_point +from mmengine.model import BaseModule +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.models.utils import (get_uncertain_point_coords_with_randomness, + get_uncertainty) +from mmdet.registry import MODELS +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList, MultiConfig, OptConfigType + + +@MODELS.register_module() +class MaskPointHead(BaseModule): + """A mask point head use in PointRend. + + ``MaskPointHead`` use shared multi-layer perceptron (equivalent to + nn.Conv1d) to predict the logit of input points. The fine-grained feature + and coarse feature will be concatenate together for predication. + + Args: + num_fcs (int): Number of fc layers in the head. Defaults to 3. + in_channels (int): Number of input channels. Defaults to 256. + fc_channels (int): Number of fc channels. Defaults to 256. + num_classes (int): Number of classes for logits. Defaults to 80. + class_agnostic (bool): Whether use class agnostic classification. + If so, the output channels of logits will be 1. Defaults to False. + coarse_pred_each_layer (bool): Whether concatenate coarse feature with + the output of each fc layer. Defaults to True. + conv_cfg (:obj:`ConfigDict` or dict): Dictionary to construct + and config conv layer. Defaults to dict(type='Conv1d')). + norm_cfg (:obj:`ConfigDict` or dict, optional): Dictionary to construct + and config norm layer. Defaults to None. + loss_point (:obj:`ConfigDict` or dict): Dictionary to construct and + config loss layer of point head. Defaults to + dict(type='CrossEntropyLoss', use_mask=True, loss_weight=1.0). + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. + """ + + def __init__( + self, + num_classes: int, + num_fcs: int = 3, + in_channels: int = 256, + fc_channels: int = 256, + class_agnostic: bool = False, + coarse_pred_each_layer: bool = True, + conv_cfg: ConfigType = dict(type='Conv1d'), + norm_cfg: OptConfigType = None, + act_cfg: ConfigType = dict(type='ReLU'), + loss_point: ConfigType = dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0), + init_cfg: MultiConfig = dict( + type='Normal', std=0.001, override=dict(name='fc_logits')) + ) -> None: + super().__init__(init_cfg=init_cfg) + self.num_fcs = num_fcs + self.in_channels = in_channels + self.fc_channels = fc_channels + self.num_classes = num_classes + self.class_agnostic = class_agnostic + self.coarse_pred_each_layer = coarse_pred_each_layer + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.loss_point = MODELS.build(loss_point) + + fc_in_channels = in_channels + num_classes + self.fcs = nn.ModuleList() + for _ in range(num_fcs): + fc = ConvModule( + fc_in_channels, + fc_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.fcs.append(fc) + fc_in_channels = fc_channels + fc_in_channels += num_classes if self.coarse_pred_each_layer else 0 + + out_channels = 1 if self.class_agnostic else self.num_classes + self.fc_logits = nn.Conv1d( + fc_in_channels, out_channels, kernel_size=1, stride=1, padding=0) + + def forward(self, fine_grained_feats: Tensor, + coarse_feats: Tensor) -> Tensor: + """Classify each point base on fine grained and coarse feats. + + Args: + fine_grained_feats (Tensor): Fine grained feature sampled from FPN, + shape (num_rois, in_channels, num_points). + coarse_feats (Tensor): Coarse feature sampled from CoarseMaskHead, + shape (num_rois, num_classes, num_points). + + Returns: + Tensor: Point classification results, + shape (num_rois, num_class, num_points). + """ + + x = torch.cat([fine_grained_feats, coarse_feats], dim=1) + for fc in self.fcs: + x = fc(x) + if self.coarse_pred_each_layer: + x = torch.cat((x, coarse_feats), dim=1) + return self.fc_logits(x) + + def get_targets(self, rois: Tensor, rel_roi_points: Tensor, + sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList, + cfg: ConfigType) -> Tensor: + """Get training targets of MaskPointHead for all images. + + Args: + rois (Tensor): Region of Interest, shape (num_rois, 5). + rel_roi_points (Tensor): Points coordinates relative to RoI, shape + (num_rois, num_points, 2). + sampling_results (:obj:`SamplingResult`): Sampling result after + sampling and assignment. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + cfg (obj:`ConfigDict` or dict): Training cfg. + + Returns: + Tensor: Point target, shape (num_rois, num_points). + """ + + num_imgs = len(sampling_results) + rois_list = [] + rel_roi_points_list = [] + for batch_ind in range(num_imgs): + inds = (rois[:, 0] == batch_ind) + rois_list.append(rois[inds]) + rel_roi_points_list.append(rel_roi_points[inds]) + pos_assigned_gt_inds_list = [ + res.pos_assigned_gt_inds for res in sampling_results + ] + cfg_list = [cfg for _ in range(num_imgs)] + + point_targets = map(self._get_targets_single, rois_list, + rel_roi_points_list, pos_assigned_gt_inds_list, + batch_gt_instances, cfg_list) + point_targets = list(point_targets) + + if len(point_targets) > 0: + point_targets = torch.cat(point_targets) + + return point_targets + + def _get_targets_single(self, rois: Tensor, rel_roi_points: Tensor, + pos_assigned_gt_inds: Tensor, + gt_instances: InstanceData, + cfg: ConfigType) -> Tensor: + """Get training target of MaskPointHead for each image.""" + num_pos = rois.size(0) + num_points = cfg.num_points + if num_pos > 0: + gt_masks_th = ( + gt_instances.masks.to_tensor(rois.dtype, + rois.device).index_select( + 0, pos_assigned_gt_inds)) + gt_masks_th = gt_masks_th.unsqueeze(1) + rel_img_points = rel_roi_point_to_rel_img_point( + rois, rel_roi_points, gt_masks_th) + point_targets = point_sample(gt_masks_th, + rel_img_points).squeeze(1) + else: + point_targets = rois.new_zeros((0, num_points)) + return point_targets + + def loss_and_target(self, point_pred: Tensor, rel_roi_points: Tensor, + sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList, + cfg: ConfigType) -> dict: + """Calculate loss for MaskPointHead. + + Args: + point_pred (Tensor): Point predication result, shape + (num_rois, num_classes, num_points). + rel_roi_points (Tensor): Points coordinates relative to RoI, shape + (num_rois, num_points, 2). + sampling_results (:obj:`SamplingResult`): Sampling result after + sampling and assignment. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + cfg (obj:`ConfigDict` or dict): Training cfg. + + Returns: + dict: a dictionary of point loss and point target. + """ + rois = bbox2roi([res.pos_bboxes for res in sampling_results]) + pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results]) + + point_target = self.get_targets(rois, rel_roi_points, sampling_results, + batch_gt_instances, cfg) + if self.class_agnostic: + loss_point = self.loss_point(point_pred, point_target, + torch.zeros_like(pos_labels)) + else: + loss_point = self.loss_point(point_pred, point_target, pos_labels) + + return dict(loss_point=loss_point, point_target=point_target) + + def get_roi_rel_points_train(self, mask_preds: Tensor, labels: Tensor, + cfg: ConfigType) -> Tensor: + """Get ``num_points`` most uncertain points with random points during + train. + + Sample points in [0, 1] x [0, 1] coordinate space based on their + uncertainty. The uncertainties are calculated for each point using + '_get_uncertainty()' function that takes point's logit prediction as + input. + + Args: + mask_preds (Tensor): A tensor of shape (num_rois, num_classes, + mask_height, mask_width) for class-specific or class-agnostic + prediction. + labels (Tensor): The ground truth class for each instance. + cfg (:obj:`ConfigDict` or dict): Training config of point head. + + Returns: + point_coords (Tensor): A tensor of shape (num_rois, num_points, 2) + that contains the coordinates sampled points. + """ + point_coords = get_uncertain_point_coords_with_randomness( + mask_preds, labels, cfg.num_points, cfg.oversample_ratio, + cfg.importance_sample_ratio) + return point_coords + + def get_roi_rel_points_test(self, mask_preds: Tensor, label_preds: Tensor, + cfg: ConfigType) -> Tuple[Tensor, Tensor]: + """Get ``num_points`` most uncertain points during test. + + Args: + mask_preds (Tensor): A tensor of shape (num_rois, num_classes, + mask_height, mask_width) for class-specific or class-agnostic + prediction. + label_preds (Tensor): The predication class for each instance. + cfg (:obj:`ConfigDict` or dict): Testing config of point head. + + Returns: + tuple: + + - point_indices (Tensor): A tensor of shape (num_rois, num_points) + that contains indices from [0, mask_height x mask_width) of the + most uncertain points. + - point_coords (Tensor): A tensor of shape (num_rois, num_points, + 2) that contains [0, 1] x [0, 1] normalized coordinates of the + most uncertain points from the [mask_height, mask_width] grid. + """ + num_points = cfg.subdivision_num_points + uncertainty_map = get_uncertainty(mask_preds, label_preds) + num_rois, _, mask_height, mask_width = uncertainty_map.shape + + # During ONNX exporting, the type of each elements of 'shape' is + # `Tensor(float)`, while it is `float` during PyTorch inference. + if isinstance(mask_height, torch.Tensor): + h_step = 1.0 / mask_height.float() + w_step = 1.0 / mask_width.float() + else: + h_step = 1.0 / mask_height + w_step = 1.0 / mask_width + # cast to int to avoid dynamic K for TopK op in ONNX + mask_size = int(mask_height * mask_width) + uncertainty_map = uncertainty_map.view(num_rois, mask_size) + num_points = min(mask_size, num_points) + point_indices = uncertainty_map.topk(num_points, dim=1)[1] + xs = w_step / 2.0 + (point_indices % mask_width).float() * w_step + ys = h_step / 2.0 + (point_indices // mask_width).float() * h_step + point_coords = torch.stack([xs, ys], dim=2) + return point_indices, point_coords diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/maskiou_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/maskiou_head.py new file mode 100644 index 00000000..8901871e --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/maskiou_head.py @@ -0,0 +1,277 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import numpy as np +import torch +import torch.nn as nn +from mmcv.cnn import Conv2d, Linear, MaxPool2d +from mmengine.config import ConfigDict +from mmengine.model import BaseModule +from mmengine.structures import InstanceData +from torch import Tensor +from torch.nn.modules.utils import _pair + +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, InstanceList, OptMultiConfig + + +@MODELS.register_module() +class MaskIoUHead(BaseModule): + """Mask IoU Head. + + This head predicts the IoU of predicted masks and corresponding gt masks. + + Args: + num_convs (int): The number of convolution layers. Defaults to 4. + num_fcs (int): The number of fully connected layers. Defaults to 2. + roi_feat_size (int): RoI feature size. Default to 14. + in_channels (int): The channel number of inputs features. + Defaults to 256. + conv_out_channels (int): The feature channels of convolution layers. + Defaults to 256. + fc_out_channels (int): The feature channels of fully connected layers. + Defaults to 1024. + num_classes (int): Number of categories excluding the background + category. Defaults to 80. + loss_iou (:obj:`ConfigDict` or dict): IoU loss. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. + """ + + def __init__( + self, + num_convs: int = 4, + num_fcs: int = 2, + roi_feat_size: int = 14, + in_channels: int = 256, + conv_out_channels: int = 256, + fc_out_channels: int = 1024, + num_classes: int = 80, + loss_iou: ConfigType = dict(type='MSELoss', loss_weight=0.5), + init_cfg: OptMultiConfig = [ + dict(type='Kaiming', override=dict(name='convs')), + dict(type='Caffe2Xavier', override=dict(name='fcs')), + dict(type='Normal', std=0.01, override=dict(name='fc_mask_iou')) + ] + ) -> None: + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.conv_out_channels = conv_out_channels + self.fc_out_channels = fc_out_channels + self.num_classes = num_classes + + self.convs = nn.ModuleList() + for i in range(num_convs): + if i == 0: + # concatenation of mask feature and mask prediction + in_channels = self.in_channels + 1 + else: + in_channels = self.conv_out_channels + stride = 2 if i == num_convs - 1 else 1 + self.convs.append( + Conv2d( + in_channels, + self.conv_out_channels, + 3, + stride=stride, + padding=1)) + + roi_feat_size = _pair(roi_feat_size) + pooled_area = (roi_feat_size[0] // 2) * (roi_feat_size[1] // 2) + self.fcs = nn.ModuleList() + for i in range(num_fcs): + in_channels = ( + self.conv_out_channels * + pooled_area if i == 0 else self.fc_out_channels) + self.fcs.append(Linear(in_channels, self.fc_out_channels)) + + self.fc_mask_iou = Linear(self.fc_out_channels, self.num_classes) + self.relu = nn.ReLU() + self.max_pool = MaxPool2d(2, 2) + self.loss_iou = MODELS.build(loss_iou) + + def forward(self, mask_feat: Tensor, mask_preds: Tensor) -> Tensor: + """Forward function. + + Args: + mask_feat (Tensor): Mask features from upstream models. + mask_preds (Tensor): Mask predictions from mask head. + + Returns: + Tensor: Mask IoU predictions. + """ + mask_preds = mask_preds.sigmoid() + mask_pred_pooled = self.max_pool(mask_preds.unsqueeze(1)) + + x = torch.cat((mask_feat, mask_pred_pooled), 1) + + for conv in self.convs: + x = self.relu(conv(x)) + x = x.flatten(1) + for fc in self.fcs: + x = self.relu(fc(x)) + mask_iou = self.fc_mask_iou(x) + return mask_iou + + def loss_and_target(self, mask_iou_pred: Tensor, mask_preds: Tensor, + mask_targets: Tensor, + sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList, + rcnn_train_cfg: ConfigDict) -> dict: + """Calculate the loss and targets of MaskIoUHead. + + Args: + mask_iou_pred (Tensor): Mask IoU predictions results, has shape + (num_pos, num_classes) + mask_preds (Tensor): Mask predictions from mask head, has shape + (num_pos, mask_size, mask_size). + mask_targets (Tensor): The ground truth masks assigned with + predictions, has shape + (num_pos, mask_size, mask_size). + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It includes ``masks`` inside. + rcnn_train_cfg (obj:`ConfigDict`): `train_cfg` of RCNN. + + Returns: + dict: A dictionary of loss and targets components. + The targets are only used for cascade rcnn. + """ + mask_iou_targets = self.get_targets( + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + mask_preds=mask_preds, + mask_targets=mask_targets, + rcnn_train_cfg=rcnn_train_cfg) + + pos_inds = mask_iou_targets > 0 + if pos_inds.sum() > 0: + loss_mask_iou = self.loss_iou(mask_iou_pred[pos_inds], + mask_iou_targets[pos_inds]) + else: + loss_mask_iou = mask_iou_pred.sum() * 0 + return dict(loss_mask_iou=loss_mask_iou) + + def get_targets(self, sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList, mask_preds: Tensor, + mask_targets: Tensor, + rcnn_train_cfg: ConfigDict) -> Tensor: + """Compute target of mask IoU. + + Mask IoU target is the IoU of the predicted mask (inside a bbox) and + the gt mask of corresponding gt mask (the whole instance). + The intersection area is computed inside the bbox, and the gt mask area + is computed with two steps, firstly we compute the gt area inside the + bbox, then divide it by the area ratio of gt area inside the bbox and + the gt area of the whole instance. + + Args: + sampling_results (list[:obj:`SamplingResult`]): sampling results. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It includes ``masks`` inside. + mask_preds (Tensor): Predicted masks of each positive proposal, + shape (num_pos, h, w). + mask_targets (Tensor): Gt mask of each positive proposal, + binary map of the shape (num_pos, h, w). + rcnn_train_cfg (obj:`ConfigDict`): Training config for R-CNN part. + + Returns: + Tensor: mask iou target (length == num positive). + """ + pos_proposals = [res.pos_priors for res in sampling_results] + pos_assigned_gt_inds = [ + res.pos_assigned_gt_inds for res in sampling_results + ] + gt_masks = [res.masks for res in batch_gt_instances] + + # compute the area ratio of gt areas inside the proposals and + # the whole instance + area_ratios = map(self._get_area_ratio, pos_proposals, + pos_assigned_gt_inds, gt_masks) + area_ratios = torch.cat(list(area_ratios)) + assert mask_targets.size(0) == area_ratios.size(0) + + mask_preds = (mask_preds > rcnn_train_cfg.mask_thr_binary).float() + mask_pred_areas = mask_preds.sum((-1, -2)) + + # mask_preds and mask_targets are binary maps + overlap_areas = (mask_preds * mask_targets).sum((-1, -2)) + + # compute the mask area of the whole instance + gt_full_areas = mask_targets.sum((-1, -2)) / (area_ratios + 1e-7) + + mask_iou_targets = overlap_areas / ( + mask_pred_areas + gt_full_areas - overlap_areas) + return mask_iou_targets + + def _get_area_ratio(self, pos_proposals: Tensor, + pos_assigned_gt_inds: Tensor, + gt_masks: InstanceData) -> Tensor: + """Compute area ratio of the gt mask inside the proposal and the gt + mask of the corresponding instance. + + Args: + pos_proposals (Tensor): Positive proposals, has shape (num_pos, 4). + pos_assigned_gt_inds (Tensor): positive proposals assigned ground + truth index. + gt_masks (BitmapMask or PolygonMask): Gt masks (the whole instance) + of each image, with the same shape of the input image. + + Returns: + Tensor: The area ratio of the gt mask inside the proposal and the + gt mask of the corresponding instance. + """ + num_pos = pos_proposals.size(0) + if num_pos > 0: + area_ratios = [] + proposals_np = pos_proposals.cpu().numpy() + pos_assigned_gt_inds = pos_assigned_gt_inds.cpu().numpy() + # compute mask areas of gt instances (batch processing for speedup) + gt_instance_mask_area = gt_masks.areas + for i in range(num_pos): + gt_mask = gt_masks[pos_assigned_gt_inds[i]] + + # crop the gt mask inside the proposal + bbox = proposals_np[i, :].astype(np.int32) + gt_mask_in_proposal = gt_mask.crop(bbox) + + ratio = gt_mask_in_proposal.areas[0] / ( + gt_instance_mask_area[pos_assigned_gt_inds[i]] + 1e-7) + area_ratios.append(ratio) + area_ratios = torch.from_numpy(np.stack(area_ratios)).float().to( + pos_proposals.device) + else: + area_ratios = pos_proposals.new_zeros((0, )) + return area_ratios + + def predict_by_feat(self, mask_iou_preds: Tuple[Tensor], + results_list: InstanceList) -> InstanceList: + """Predict the mask iou and calculate it into ``results.scores``. + + Args: + mask_iou_preds (Tensor): Mask IoU predictions results, has shape + (num_proposals, num_classes) + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + assert len(mask_iou_preds) == len(results_list) + for results, mask_iou_pred in zip(results_list, mask_iou_preds): + labels = results.labels + scores = results.scores + results.scores = scores * mask_iou_pred[range(labels.size(0)), + labels] + return results_list diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/scnet_mask_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/scnet_mask_head.py new file mode 100644 index 00000000..ffd30c33 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/scnet_mask_head.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.models.layers import ResLayer, SimplifiedBasicBlock +from mmdet.registry import MODELS +from .fcn_mask_head import FCNMaskHead + + +@MODELS.register_module() +class SCNetMaskHead(FCNMaskHead): + """Mask head for `SCNet `_. + + Args: + conv_to_res (bool, optional): if True, change the conv layers to + ``SimplifiedBasicBlock``. + """ + + def __init__(self, conv_to_res: bool = True, **kwargs) -> None: + super().__init__(**kwargs) + self.conv_to_res = conv_to_res + if conv_to_res: + assert self.conv_kernel_size == 3 + self.num_res_blocks = self.num_convs // 2 + self.convs = ResLayer( + SimplifiedBasicBlock, + self.in_channels, + self.conv_out_channels, + self.num_res_blocks, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg) diff --git a/mmdetection/mmdet/models/roi_heads/mask_heads/scnet_semantic_head.py b/mmdetection/mmdet/models/roi_heads/mask_heads/scnet_semantic_head.py new file mode 100644 index 00000000..55c5c8e4 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_heads/scnet_semantic_head.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.models.layers import ResLayer, SimplifiedBasicBlock +from mmdet.registry import MODELS +from .fused_semantic_head import FusedSemanticHead + + +@MODELS.register_module() +class SCNetSemanticHead(FusedSemanticHead): + """Mask head for `SCNet `_. + + Args: + conv_to_res (bool, optional): if True, change the conv layers to + ``SimplifiedBasicBlock``. + """ + + def __init__(self, conv_to_res: bool = True, **kwargs) -> None: + super().__init__(**kwargs) + self.conv_to_res = conv_to_res + if self.conv_to_res: + num_res_blocks = self.num_convs // 2 + self.convs = ResLayer( + SimplifiedBasicBlock, + self.in_channels, + self.conv_out_channels, + num_res_blocks, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg) + self.num_convs = num_res_blocks diff --git a/mmdetection/mmdet/models/roi_heads/mask_scoring_roi_head.py b/mmdetection/mmdet/models/roi_heads/mask_scoring_roi_head.py new file mode 100644 index 00000000..6545c0ed --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/mask_scoring_roi_head.py @@ -0,0 +1,208 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList +from ..task_modules.samplers import SamplingResult +from ..utils.misc import empty_instances +from .standard_roi_head import StandardRoIHead + + +@MODELS.register_module() +class MaskScoringRoIHead(StandardRoIHead): + """Mask Scoring RoIHead for `Mask Scoring RCNN. + + `_. + + Args: + mask_iou_head (:obj`ConfigDict`, dict): The config of mask_iou_head. + """ + + def __init__(self, mask_iou_head: ConfigType, **kwargs): + assert mask_iou_head is not None + super().__init__(**kwargs) + self.mask_iou_head = MODELS.build(mask_iou_head) + + def forward(self, + x: Tuple[Tensor], + rpn_results_list: InstanceList, + batch_data_samples: SampleList = None) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + x (List[Tensor]): Multi-level features that may have different + resolutions. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns + tuple: A tuple of features from ``bbox_head`` and ``mask_head`` + forward. + """ + results = () + proposals = [rpn_results.bboxes for rpn_results in rpn_results_list] + rois = bbox2roi(proposals) + # bbox head + if self.with_bbox: + bbox_results = self._bbox_forward(x, rois) + results = results + (bbox_results['cls_score'], + bbox_results['bbox_pred']) + # mask head + if self.with_mask: + mask_rois = rois[:100] + mask_results = self._mask_forward(x, mask_rois) + results = results + (mask_results['mask_preds'], ) + + # mask iou head + cls_score = bbox_results['cls_score'][:100] + mask_preds = mask_results['mask_preds'] + mask_feats = mask_results['mask_feats'] + _, labels = cls_score[:, :self.bbox_head.num_classes].max(dim=1) + mask_iou_preds = self.mask_iou_head( + mask_feats, mask_preds[range(labels.size(0)), labels]) + results = results + (mask_iou_preds, ) + + return results + + def mask_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult], bbox_feats, + batch_gt_instances: InstanceList) -> dict: + """Perform forward propagation and loss calculation of the mask head on + the features of the upstream network. + + Args: + x (tuple[Tensor]): Tuple of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + bbox_feats (Tensor): Extract bbox RoI features. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + - `mask_feats` (Tensor): Extract mask RoI features. + - `mask_targets` (Tensor): Mask target of each positive\ + proposals in the image. + - `loss_mask` (dict): A dictionary of mask loss components. + - `loss_mask_iou` (Tensor): mask iou loss. + """ + if not self.share_roi_extractor: + pos_rois = bbox2roi([res.pos_priors for res in sampling_results]) + mask_results = self._mask_forward(x, pos_rois) + else: + pos_inds = [] + device = bbox_feats.device + for res in sampling_results: + pos_inds.append( + torch.ones( + res.pos_priors.shape[0], + device=device, + dtype=torch.uint8)) + pos_inds.append( + torch.zeros( + res.neg_priors.shape[0], + device=device, + dtype=torch.uint8)) + pos_inds = torch.cat(pos_inds) + + mask_results = self._mask_forward( + x, pos_inds=pos_inds, bbox_feats=bbox_feats) + + mask_loss_and_target = self.mask_head.loss_and_target( + mask_preds=mask_results['mask_preds'], + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=self.train_cfg) + mask_targets = mask_loss_and_target['mask_targets'] + mask_results.update(loss_mask=mask_loss_and_target['loss_mask']) + if mask_results['loss_mask'] is None: + return mask_results + + # mask iou head forward and loss + pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results]) + pos_mask_pred = mask_results['mask_preds'][ + range(mask_results['mask_preds'].size(0)), pos_labels] + mask_iou_pred = self.mask_iou_head(mask_results['mask_feats'], + pos_mask_pred) + pos_mask_iou_pred = mask_iou_pred[range(mask_iou_pred.size(0)), + pos_labels] + + loss_mask_iou = self.mask_iou_head.loss_and_target( + pos_mask_iou_pred, pos_mask_pred, mask_targets, sampling_results, + batch_gt_instances, self.train_cfg) + mask_results['loss_mask'].update(loss_mask_iou) + return mask_results + + def predict_mask(self, + x: Tensor, + batch_img_metas: List[dict], + results_list: InstanceList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the mask head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + bboxes = [res.bboxes for res in results_list] + mask_rois = bbox2roi(bboxes) + if mask_rois.shape[0] == 0: + results_list = empty_instances( + batch_img_metas, + mask_rois.device, + task_type='mask', + instance_results=results_list, + mask_thr_binary=self.test_cfg.mask_thr_binary) + return results_list + + mask_results = self._mask_forward(x, mask_rois) + mask_preds = mask_results['mask_preds'] + mask_feats = mask_results['mask_feats'] + # get mask scores with mask iou head + labels = torch.cat([res.labels for res in results_list]) + mask_iou_preds = self.mask_iou_head( + mask_feats, mask_preds[range(labels.size(0)), labels]) + # split batch mask prediction back to each image + num_mask_rois_per_img = [len(res) for res in results_list] + mask_preds = mask_preds.split(num_mask_rois_per_img, 0) + mask_iou_preds = mask_iou_preds.split(num_mask_rois_per_img, 0) + + # TODO: Handle the case where rescale is false + results_list = self.mask_head.predict_by_feat( + mask_preds=mask_preds, + results_list=results_list, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=self.test_cfg, + rescale=rescale) + results_list = self.mask_iou_head.predict_by_feat( + mask_iou_preds=mask_iou_preds, results_list=results_list) + return results_list diff --git a/mmdetection/mmdet/models/roi_heads/multi_instance_roi_head.py b/mmdetection/mmdet/models/roi_heads/multi_instance_roi_head.py new file mode 100644 index 00000000..fee55b0a --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/multi_instance_roi_head.py @@ -0,0 +1,226 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList +from ..task_modules.samplers import SamplingResult +from ..utils import empty_instances, unpack_gt_instances +from .standard_roi_head import StandardRoIHead + + +@MODELS.register_module() +class MultiInstanceRoIHead(StandardRoIHead): + """The roi head for Multi-instance prediction.""" + + def __init__(self, num_instance: int = 2, *args, **kwargs) -> None: + self.num_instance = num_instance + super().__init__(*args, **kwargs) + + def init_bbox_head(self, bbox_roi_extractor: ConfigType, + bbox_head: ConfigType) -> None: + """Initialize box head and box roi extractor. + + Args: + bbox_roi_extractor (dict or ConfigDict): Config of box + roi extractor. + bbox_head (dict or ConfigDict): Config of box in box head. + """ + self.bbox_roi_extractor = MODELS.build(bbox_roi_extractor) + self.bbox_head = MODELS.build(bbox_head) + + def _bbox_forward(self, x: Tuple[Tensor], rois: Tensor) -> dict: + """Box head forward function used in both training and testing. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `cls_score_ref` (Tensor): The cls_score after refine model. + - `bbox_pred_ref` (Tensor): The bbox_pred after refine model. + - `bbox_feats` (Tensor): Extract bbox RoI features. + """ + # TODO: a more flexible way to decide which feature maps to use + bbox_feats = self.bbox_roi_extractor( + x[:self.bbox_roi_extractor.num_inputs], rois) + bbox_results = self.bbox_head(bbox_feats) + + if self.bbox_head.with_refine: + bbox_results = dict( + cls_score=bbox_results[0], + bbox_pred=bbox_results[1], + cls_score_ref=bbox_results[2], + bbox_pred_ref=bbox_results[3], + bbox_feats=bbox_feats) + else: + bbox_results = dict( + cls_score=bbox_results[0], + bbox_pred=bbox_results[1], + bbox_feats=bbox_feats) + + return bbox_results + + def bbox_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult]) -> dict: + """Perform forward propagation and loss calculation of the bbox head on + the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + """ + rois = bbox2roi([res.priors for res in sampling_results]) + bbox_results = self._bbox_forward(x, rois) + + # If there is a refining process, add refine loss. + if 'cls_score_ref' in bbox_results: + bbox_loss_and_target = self.bbox_head.loss_and_target( + cls_score=bbox_results['cls_score'], + bbox_pred=bbox_results['bbox_pred'], + rois=rois, + sampling_results=sampling_results, + rcnn_train_cfg=self.train_cfg) + bbox_results.update(loss_bbox=bbox_loss_and_target['loss_bbox']) + bbox_loss_and_target_ref = self.bbox_head.loss_and_target( + cls_score=bbox_results['cls_score_ref'], + bbox_pred=bbox_results['bbox_pred_ref'], + rois=rois, + sampling_results=sampling_results, + rcnn_train_cfg=self.train_cfg) + bbox_results['loss_bbox']['loss_rcnn_emd_ref'] = \ + bbox_loss_and_target_ref['loss_bbox']['loss_rcnn_emd'] + else: + bbox_loss_and_target = self.bbox_head.loss_and_target( + cls_score=bbox_results['cls_score'], + bbox_pred=bbox_results['bbox_pred'], + rois=rois, + sampling_results=sampling_results, + rcnn_train_cfg=self.train_cfg) + bbox_results.update(loss_bbox=bbox_loss_and_target['loss_bbox']) + + return bbox_results + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: List[DetDataSample]) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components + """ + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, _ = outputs + + sampling_results = [] + for i in range(len(batch_data_samples)): + # rename rpn_results.bboxes to rpn_results.priors + rpn_results = rpn_results_list[i] + rpn_results.priors = rpn_results.pop('bboxes') + + assign_result = self.bbox_assigner.assign( + rpn_results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = self.bbox_sampler.sample( + assign_result, + rpn_results, + batch_gt_instances[i], + batch_gt_instances_ignore=batch_gt_instances_ignore[i]) + sampling_results.append(sampling_result) + + losses = dict() + # bbox head loss + if self.with_bbox: + bbox_results = self.bbox_loss(x, sampling_results) + losses.update(bbox_results['loss_bbox']) + + return losses + + def predict_bbox(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + rpn_results_list: InstanceList, + rcnn_test_cfg: ConfigType, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the bbox head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of R-CNN. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + proposals = [res.bboxes for res in rpn_results_list] + rois = bbox2roi(proposals) + + if rois.shape[0] == 0: + return empty_instances( + batch_img_metas, rois.device, task_type='bbox') + + bbox_results = self._bbox_forward(x, rois) + + # split batch bbox prediction back to each image + if 'cls_score_ref' in bbox_results: + cls_scores = bbox_results['cls_score_ref'] + bbox_preds = bbox_results['bbox_pred_ref'] + else: + cls_scores = bbox_results['cls_score'] + bbox_preds = bbox_results['bbox_pred'] + num_proposals_per_img = tuple(len(p) for p in proposals) + rois = rois.split(num_proposals_per_img, 0) + cls_scores = cls_scores.split(num_proposals_per_img, 0) + + if bbox_preds is not None: + bbox_preds = bbox_preds.split(num_proposals_per_img, 0) + else: + bbox_preds = (None, ) * len(proposals) + + result_list = self.bbox_head.predict_by_feat( + rois=rois, + cls_scores=cls_scores, + bbox_preds=bbox_preds, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=rcnn_test_cfg, + rescale=rescale) + return result_list diff --git a/mmdetection/mmdet/models/roi_heads/pisa_roi_head.py b/mmdetection/mmdet/models/roi_heads/pisa_roi_head.py new file mode 100644 index 00000000..45d59879 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/pisa_roi_head.py @@ -0,0 +1,148 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +from torch import Tensor + +from mmdet.models.task_modules import SamplingResult +from mmdet.registry import MODELS +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import InstanceList +from ..losses.pisa_loss import carl_loss, isr_p +from ..utils import unpack_gt_instances +from .standard_roi_head import StandardRoIHead + + +@MODELS.register_module() +class PISARoIHead(StandardRoIHead): + r"""The RoI head for `Prime Sample Attention in Object Detection + `_.""" + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: List[DetDataSample]) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components + """ + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, _ = outputs + + # assign gts and sample proposals + num_imgs = len(batch_data_samples) + sampling_results = [] + neg_label_weights = [] + for i in range(num_imgs): + # rename rpn_results.bboxes to rpn_results.priors + rpn_results = rpn_results_list[i] + rpn_results.priors = rpn_results.pop('bboxes') + + assign_result = self.bbox_assigner.assign( + rpn_results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = self.bbox_sampler.sample( + assign_result, + rpn_results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + if isinstance(sampling_result, tuple): + sampling_result, neg_label_weight = sampling_result + sampling_results.append(sampling_result) + neg_label_weights.append(neg_label_weight) + + losses = dict() + # bbox head forward and loss + if self.with_bbox: + bbox_results = self.bbox_loss( + x, sampling_results, neg_label_weights=neg_label_weights) + losses.update(bbox_results['loss_bbox']) + + # mask head forward and loss + if self.with_mask: + mask_results = self.mask_loss(x, sampling_results, + bbox_results['bbox_feats'], + batch_gt_instances) + losses.update(mask_results['loss_mask']) + + return losses + + def bbox_loss(self, + x: Tuple[Tensor], + sampling_results: List[SamplingResult], + neg_label_weights: List[Tensor] = None) -> dict: + """Perform forward propagation and loss calculation of the bbox head on + the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + """ + rois = bbox2roi([res.priors for res in sampling_results]) + bbox_results = self._bbox_forward(x, rois) + bbox_targets = self.bbox_head.get_targets(sampling_results, + self.train_cfg) + + # neg_label_weights obtained by sampler is image-wise, mapping back to + # the corresponding location in label weights + if neg_label_weights[0] is not None: + label_weights = bbox_targets[1] + cur_num_rois = 0 + for i in range(len(sampling_results)): + num_pos = sampling_results[i].pos_inds.size(0) + num_neg = sampling_results[i].neg_inds.size(0) + label_weights[cur_num_rois + num_pos:cur_num_rois + num_pos + + num_neg] = neg_label_weights[i] + cur_num_rois += num_pos + num_neg + + cls_score = bbox_results['cls_score'] + bbox_pred = bbox_results['bbox_pred'] + + # Apply ISR-P + isr_cfg = self.train_cfg.get('isr', None) + if isr_cfg is not None: + bbox_targets = isr_p( + cls_score, + bbox_pred, + bbox_targets, + rois, + sampling_results, + self.bbox_head.loss_cls, + self.bbox_head.bbox_coder, + **isr_cfg, + num_class=self.bbox_head.num_classes) + loss_bbox = self.bbox_head.loss(cls_score, bbox_pred, rois, + *bbox_targets) + + # Add CARL Loss + carl_cfg = self.train_cfg.get('carl', None) + if carl_cfg is not None: + loss_carl = carl_loss( + cls_score, + bbox_targets[0], + bbox_pred, + bbox_targets[2], + self.bbox_head.loss_bbox, + **carl_cfg, + num_class=self.bbox_head.num_classes) + loss_bbox.update(loss_carl) + + bbox_results.update(loss_bbox=loss_bbox) + return bbox_results diff --git a/mmdetection/mmdet/models/roi_heads/point_rend_roi_head.py b/mmdetection/mmdet/models/roi_heads/point_rend_roi_head.py new file mode 100644 index 00000000..6a064154 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/point_rend_roi_head.py @@ -0,0 +1,236 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Modified from https://github.com/facebookresearch/detectron2/tree/master/projects/PointRend # noqa +from typing import List, Tuple + +import torch +import torch.nn.functional as F +from mmcv.ops import point_sample, rel_roi_point_to_rel_img_point +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList +from ..task_modules.samplers import SamplingResult +from ..utils import empty_instances +from .standard_roi_head import StandardRoIHead + + +@MODELS.register_module() +class PointRendRoIHead(StandardRoIHead): + """`PointRend `_.""" + + def __init__(self, point_head: ConfigType, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + assert self.with_bbox and self.with_mask + self.init_point_head(point_head) + + def init_point_head(self, point_head: ConfigType) -> None: + """Initialize ``point_head``""" + self.point_head = MODELS.build(point_head) + + def mask_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult], bbox_feats: Tensor, + batch_gt_instances: InstanceList) -> dict: + """Run forward function and calculate loss for mask head and point head + in training.""" + mask_results = super().mask_loss( + x=x, + sampling_results=sampling_results, + bbox_feats=bbox_feats, + batch_gt_instances=batch_gt_instances) + + mask_point_results = self._mask_point_loss( + x=x, + sampling_results=sampling_results, + mask_preds=mask_results['mask_preds'], + batch_gt_instances=batch_gt_instances) + mask_results['loss_mask'].update( + loss_point=mask_point_results['loss_point']) + + return mask_results + + def _mask_point_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult], + mask_preds: Tensor, + batch_gt_instances: InstanceList) -> dict: + """Run forward function and calculate loss for point head in + training.""" + pos_labels = torch.cat([res.pos_gt_labels for res in sampling_results]) + rel_roi_points = self.point_head.get_roi_rel_points_train( + mask_preds, pos_labels, cfg=self.train_cfg) + rois = bbox2roi([res.pos_bboxes for res in sampling_results]) + + fine_grained_point_feats = self._get_fine_grained_point_feats( + x, rois, rel_roi_points) + coarse_point_feats = point_sample(mask_preds, rel_roi_points) + mask_point_pred = self.point_head(fine_grained_point_feats, + coarse_point_feats) + + loss_and_target = self.point_head.loss_and_target( + point_pred=mask_point_pred, + rel_roi_points=rel_roi_points, + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + cfg=self.train_cfg) + + return loss_and_target + + def _mask_point_forward_test(self, x: Tuple[Tensor], rois: Tensor, + label_preds: Tensor, + mask_preds: Tensor) -> Tensor: + """Mask refining process with point head in testing. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + rois (Tensor): shape (num_rois, 5). + label_preds (Tensor): The predication class for each rois. + mask_preds (Tensor): The predication coarse masks of + shape (num_rois, num_classes, small_size, small_size). + + Returns: + Tensor: The refined masks of shape (num_rois, num_classes, + large_size, large_size). + """ + refined_mask_pred = mask_preds.clone() + for subdivision_step in range(self.test_cfg.subdivision_steps): + refined_mask_pred = F.interpolate( + refined_mask_pred, + scale_factor=self.test_cfg.scale_factor, + mode='bilinear', + align_corners=False) + # If `subdivision_num_points` is larger or equal to the + # resolution of the next step, then we can skip this step + num_rois, channels, mask_height, mask_width = \ + refined_mask_pred.shape + if (self.test_cfg.subdivision_num_points >= + self.test_cfg.scale_factor**2 * mask_height * mask_width + and + subdivision_step < self.test_cfg.subdivision_steps - 1): + continue + point_indices, rel_roi_points = \ + self.point_head.get_roi_rel_points_test( + refined_mask_pred, label_preds, cfg=self.test_cfg) + + fine_grained_point_feats = self._get_fine_grained_point_feats( + x=x, rois=rois, rel_roi_points=rel_roi_points) + coarse_point_feats = point_sample(mask_preds, rel_roi_points) + mask_point_pred = self.point_head(fine_grained_point_feats, + coarse_point_feats) + + point_indices = point_indices.unsqueeze(1).expand(-1, channels, -1) + refined_mask_pred = refined_mask_pred.reshape( + num_rois, channels, mask_height * mask_width) + refined_mask_pred = refined_mask_pred.scatter_( + 2, point_indices, mask_point_pred) + refined_mask_pred = refined_mask_pred.view(num_rois, channels, + mask_height, mask_width) + + return refined_mask_pred + + def _get_fine_grained_point_feats(self, x: Tuple[Tensor], rois: Tensor, + rel_roi_points: Tensor) -> Tensor: + """Sample fine grained feats from each level feature map and + concatenate them together. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + rois (Tensor): shape (num_rois, 5). + rel_roi_points (Tensor): A tensor of shape (num_rois, num_points, + 2) that contains [0, 1] x [0, 1] normalized coordinates of the + most uncertain points from the [mask_height, mask_width] grid. + + Returns: + Tensor: The fine grained features for each points, + has shape (num_rois, feats_channels, num_points). + """ + assert rois.shape[0] > 0, 'RoI is a empty tensor.' + num_imgs = x[0].shape[0] + fine_grained_feats = [] + for idx in range(self.mask_roi_extractor.num_inputs): + feats = x[idx] + spatial_scale = 1. / float( + self.mask_roi_extractor.featmap_strides[idx]) + point_feats = [] + for batch_ind in range(num_imgs): + # unravel batch dim + feat = feats[batch_ind].unsqueeze(0) + inds = (rois[:, 0].long() == batch_ind) + if inds.any(): + rel_img_points = rel_roi_point_to_rel_img_point( + rois=rois[inds], + rel_roi_points=rel_roi_points[inds], + img=feat.shape[2:], + spatial_scale=spatial_scale).unsqueeze(0) + point_feat = point_sample(feat, rel_img_points) + point_feat = point_feat.squeeze(0).transpose(0, 1) + point_feats.append(point_feat) + fine_grained_feats.append(torch.cat(point_feats, dim=0)) + return torch.cat(fine_grained_feats, dim=1) + + def predict_mask(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + results_list: InstanceList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the mask head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + # don't need to consider aug_test. + bboxes = [res.bboxes for res in results_list] + mask_rois = bbox2roi(bboxes) + if mask_rois.shape[0] == 0: + results_list = empty_instances( + batch_img_metas, + mask_rois.device, + task_type='mask', + instance_results=results_list, + mask_thr_binary=self.test_cfg.mask_thr_binary) + return results_list + + mask_results = self._mask_forward(x, mask_rois) + mask_preds = mask_results['mask_preds'] + # split batch mask prediction back to each image + num_mask_rois_per_img = [len(res) for res in results_list] + mask_preds = mask_preds.split(num_mask_rois_per_img, 0) + + # refine mask_preds + mask_rois = mask_rois.split(num_mask_rois_per_img, 0) + mask_preds_refined = [] + for i in range(len(batch_img_metas)): + labels = results_list[i].labels + x_i = [xx[[i]] for xx in x] + mask_rois_i = mask_rois[i] + mask_rois_i[:, 0] = 0 + mask_pred_i = self._mask_point_forward_test( + x_i, mask_rois_i, labels, mask_preds[i]) + mask_preds_refined.append(mask_pred_i) + + # TODO: Handle the case where rescale is false + results_list = self.mask_head.predict_by_feat( + mask_preds=mask_preds_refined, + results_list=results_list, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=self.test_cfg, + rescale=rescale) + return results_list diff --git a/mmdetection/mmdet/models/roi_heads/roi_extractors/__init__.py b/mmdetection/mmdet/models/roi_heads/roi_extractors/__init__.py new file mode 100644 index 00000000..0f602149 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/roi_extractors/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_roi_extractor import BaseRoIExtractor +from .generic_roi_extractor import GenericRoIExtractor +from .single_level_roi_extractor import SingleRoIExtractor + +__all__ = ['BaseRoIExtractor', 'SingleRoIExtractor', 'GenericRoIExtractor'] diff --git a/mmdetection/mmdet/models/roi_heads/roi_extractors/base_roi_extractor.py b/mmdetection/mmdet/models/roi_heads/roi_extractors/base_roi_extractor.py new file mode 100644 index 00000000..a8de0518 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/roi_extractors/base_roi_extractor.py @@ -0,0 +1,111 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +from mmcv import ops +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.utils import ConfigType, OptMultiConfig + + +class BaseRoIExtractor(BaseModule, metaclass=ABCMeta): + """Base class for RoI extractor. + + Args: + roi_layer (:obj:`ConfigDict` or dict): Specify RoI layer type and + arguments. + out_channels (int): Output channels of RoI layers. + featmap_strides (list[int]): Strides of input feature maps. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. Defaults to None. + """ + + def __init__(self, + roi_layer: ConfigType, + out_channels: int, + featmap_strides: List[int], + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + self.roi_layers = self.build_roi_layers(roi_layer, featmap_strides) + self.out_channels = out_channels + self.featmap_strides = featmap_strides + + @property + def num_inputs(self) -> int: + """int: Number of input feature maps.""" + return len(self.featmap_strides) + + def build_roi_layers(self, layer_cfg: ConfigType, + featmap_strides: List[int]) -> nn.ModuleList: + """Build RoI operator to extract feature from each level feature map. + + Args: + layer_cfg (:obj:`ConfigDict` or dict): Dictionary to construct and + config RoI layer operation. Options are modules under + ``mmcv/ops`` such as ``RoIAlign``. + featmap_strides (list[int]): The stride of input feature map w.r.t + to the original image size, which would be used to scale RoI + coordinate (original image coordinate system) to feature + coordinate system. + + Returns: + :obj:`nn.ModuleList`: The RoI extractor modules for each level + feature map. + """ + + cfg = layer_cfg.copy() + layer_type = cfg.pop('type') + if isinstance(layer_type, str): + assert hasattr(ops, layer_type) + layer_cls = getattr(ops, layer_type) + else: + layer_cls = layer_type + roi_layers = nn.ModuleList( + [layer_cls(spatial_scale=1 / s, **cfg) for s in featmap_strides]) + return roi_layers + + def roi_rescale(self, rois: Tensor, scale_factor: float) -> Tensor: + """Scale RoI coordinates by scale factor. + + Args: + rois (Tensor): RoI (Region of Interest), shape (n, 5) + scale_factor (float): Scale factor that RoI will be multiplied by. + + Returns: + Tensor: Scaled RoI. + """ + + cx = (rois[:, 1] + rois[:, 3]) * 0.5 + cy = (rois[:, 2] + rois[:, 4]) * 0.5 + w = rois[:, 3] - rois[:, 1] + h = rois[:, 4] - rois[:, 2] + new_w = w * scale_factor + new_h = h * scale_factor + x1 = cx - new_w * 0.5 + x2 = cx + new_w * 0.5 + y1 = cy - new_h * 0.5 + y2 = cy + new_h * 0.5 + new_rois = torch.stack((rois[:, 0], x1, y1, x2, y2), dim=-1) + return new_rois + + @abstractmethod + def forward(self, + feats: Tuple[Tensor], + rois: Tensor, + roi_scale_factor: Optional[float] = None) -> Tensor: + """Extractor ROI feats. + + Args: + feats (Tuple[Tensor]): Multi-scale features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + roi_scale_factor (Optional[float]): RoI scale factor. + Defaults to None. + + Returns: + Tensor: RoI feature. + """ + pass diff --git a/mmdetection/mmdet/models/roi_heads/roi_extractors/generic_roi_extractor.py b/mmdetection/mmdet/models/roi_heads/roi_extractors/generic_roi_extractor.py new file mode 100644 index 00000000..39d4c901 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/roi_extractors/generic_roi_extractor.py @@ -0,0 +1,102 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple + +from mmcv.cnn.bricks import build_plugin_layer +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType +from .base_roi_extractor import BaseRoIExtractor + + +@MODELS.register_module() +class GenericRoIExtractor(BaseRoIExtractor): + """Extract RoI features from all level feature maps levels. + + This is the implementation of `A novel Region of Interest Extraction Layer + for Instance Segmentation `_. + + Args: + aggregation (str): The method to aggregate multiple feature maps. + Options are 'sum', 'concat'. Defaults to 'sum'. + pre_cfg (:obj:`ConfigDict` or dict): Specify pre-processing modules. + Defaults to None. + post_cfg (:obj:`ConfigDict` or dict): Specify post-processing modules. + Defaults to None. + kwargs (keyword arguments): Arguments that are the same + as :class:`BaseRoIExtractor`. + """ + + def __init__(self, + aggregation: str = 'sum', + pre_cfg: OptConfigType = None, + post_cfg: OptConfigType = None, + **kwargs) -> None: + super().__init__(**kwargs) + + assert aggregation in ['sum', 'concat'] + + self.aggregation = aggregation + self.with_post = post_cfg is not None + self.with_pre = pre_cfg is not None + # build pre/post processing modules + if self.with_post: + self.post_module = build_plugin_layer(post_cfg, '_post_module')[1] + if self.with_pre: + self.pre_module = build_plugin_layer(pre_cfg, '_pre_module')[1] + + def forward(self, + feats: Tuple[Tensor], + rois: Tensor, + roi_scale_factor: Optional[float] = None) -> Tensor: + """Extractor ROI feats. + + Args: + feats (Tuple[Tensor]): Multi-scale features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + roi_scale_factor (Optional[float]): RoI scale factor. + Defaults to None. + + Returns: + Tensor: RoI feature. + """ + out_size = self.roi_layers[0].output_size + num_levels = len(feats) + roi_feats = feats[0].new_zeros( + rois.size(0), self.out_channels, *out_size) + + # some times rois is an empty tensor + if roi_feats.shape[0] == 0: + return roi_feats + + if num_levels == 1: + return self.roi_layers[0](feats[0], rois) + + if roi_scale_factor is not None: + rois = self.roi_rescale(rois, roi_scale_factor) + + # mark the starting channels for concat mode + start_channels = 0 + for i in range(num_levels): + roi_feats_t = self.roi_layers[i](feats[i], rois) + end_channels = start_channels + roi_feats_t.size(1) + if self.with_pre: + # apply pre-processing to a RoI extracted from each layer + roi_feats_t = self.pre_module(roi_feats_t) + if self.aggregation == 'sum': + # and sum them all + roi_feats += roi_feats_t + else: + # and concat them along channel dimension + roi_feats[:, start_channels:end_channels] = roi_feats_t + # update channels starting position + start_channels = end_channels + # check if concat channels match at the end + if self.aggregation == 'concat': + assert start_channels == self.out_channels + + if self.with_post: + # apply post-processing before return the result + roi_feats = self.post_module(roi_feats) + return roi_feats diff --git a/mmdetection/mmdet/models/roi_heads/roi_extractors/single_level_roi_extractor.py b/mmdetection/mmdet/models/roi_heads/roi_extractors/single_level_roi_extractor.py new file mode 100644 index 00000000..59229e0b --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/roi_extractors/single_level_roi_extractor.py @@ -0,0 +1,119 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptMultiConfig +from .base_roi_extractor import BaseRoIExtractor + + +@MODELS.register_module() +class SingleRoIExtractor(BaseRoIExtractor): + """Extract RoI features from a single level feature map. + + If there are multiple input feature levels, each RoI is mapped to a level + according to its scale. The mapping rule is proposed in + `FPN `_. + + Args: + roi_layer (:obj:`ConfigDict` or dict): Specify RoI layer type and + arguments. + out_channels (int): Output channels of RoI layers. + featmap_strides (List[int]): Strides of input feature maps. + finest_scale (int): Scale threshold of mapping to level 0. + Defaults to 56. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. Defaults to None. + """ + + def __init__(self, + roi_layer: ConfigType, + out_channels: int, + featmap_strides: List[int], + finest_scale: int = 56, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + roi_layer=roi_layer, + out_channels=out_channels, + featmap_strides=featmap_strides, + init_cfg=init_cfg) + self.finest_scale = finest_scale + + def map_roi_levels(self, rois: Tensor, num_levels: int) -> Tensor: + """Map rois to corresponding feature levels by scales. + + - scale < finest_scale * 2: level 0 + - finest_scale * 2 <= scale < finest_scale * 4: level 1 + - finest_scale * 4 <= scale < finest_scale * 8: level 2 + - scale >= finest_scale * 8: level 3 + + Args: + rois (Tensor): Input RoIs, shape (k, 5). + num_levels (int): Total level number. + + Returns: + Tensor: Level index (0-based) of each RoI, shape (k, ) + """ + scale = torch.sqrt( + (rois[:, 3] - rois[:, 1]) * (rois[:, 4] - rois[:, 2])) + target_lvls = torch.floor(torch.log2(scale / self.finest_scale + 1e-6)) + target_lvls = target_lvls.clamp(min=0, max=num_levels - 1).long() + return target_lvls + + def forward(self, + feats: Tuple[Tensor], + rois: Tensor, + roi_scale_factor: Optional[float] = None): + """Extractor ROI feats. + + Args: + feats (Tuple[Tensor]): Multi-scale features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + roi_scale_factor (Optional[float]): RoI scale factor. + Defaults to None. + + Returns: + Tensor: RoI feature. + """ + # convert fp32 to fp16 when amp is on + rois = rois.type_as(feats[0]) + out_size = self.roi_layers[0].output_size + num_levels = len(feats) + roi_feats = feats[0].new_zeros( + rois.size(0), self.out_channels, *out_size) + + # TODO: remove this when parrots supports + if torch.__version__ == 'parrots': + roi_feats.requires_grad = True + + if num_levels == 1: + if len(rois) == 0: + return roi_feats + return self.roi_layers[0](feats[0], rois) + + target_lvls = self.map_roi_levels(rois, num_levels) + + if roi_scale_factor is not None: + rois = self.roi_rescale(rois, roi_scale_factor) + + for i in range(num_levels): + mask = target_lvls == i + inds = mask.nonzero(as_tuple=False).squeeze(1) + if inds.numel() > 0: + rois_ = rois[inds] + roi_feats_t = self.roi_layers[i](feats[i], rois_) + roi_feats[inds] = roi_feats_t + else: + # Sometimes some pyramid levels will not be used for RoI + # feature extraction and this will cause an incomplete + # computation graph in one GPU, which is different from those + # in other GPUs and will cause a hanging error. + # Therefore, we add it to ensure each feature pyramid is + # included in the computation graph to avoid runtime bugs. + roi_feats += sum( + x.view(-1)[0] + for x in self.parameters()) * 0. + feats[i].sum() * 0. + return roi_feats diff --git a/mmdetection/mmdet/models/roi_heads/scnet_roi_head.py b/mmdetection/mmdet/models/roi_heads/scnet_roi_head.py new file mode 100644 index 00000000..e6d2bc19 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/scnet_roi_head.py @@ -0,0 +1,677 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +import torch.nn.functional as F +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList, OptConfigType +from ..layers import adaptive_avg_pool2d +from ..task_modules.samplers import SamplingResult +from ..utils import empty_instances, unpack_gt_instances +from .cascade_roi_head import CascadeRoIHead + + +@MODELS.register_module() +class SCNetRoIHead(CascadeRoIHead): + """RoIHead for `SCNet `_. + + Args: + num_stages (int): number of cascade stages. + stage_loss_weights (list): loss weight of cascade stages. + semantic_roi_extractor (dict): config to init semantic roi extractor. + semantic_head (dict): config to init semantic head. + feat_relay_head (dict): config to init feature_relay_head. + glbctx_head (dict): config to init global context head. + """ + + def __init__(self, + num_stages: int, + stage_loss_weights: List[float], + semantic_roi_extractor: OptConfigType = None, + semantic_head: OptConfigType = None, + feat_relay_head: OptConfigType = None, + glbctx_head: OptConfigType = None, + **kwargs) -> None: + super().__init__( + num_stages=num_stages, + stage_loss_weights=stage_loss_weights, + **kwargs) + assert self.with_bbox and self.with_mask + assert not self.with_shared_head # shared head is not supported + + if semantic_head is not None: + self.semantic_roi_extractor = MODELS.build(semantic_roi_extractor) + self.semantic_head = MODELS.build(semantic_head) + + if feat_relay_head is not None: + self.feat_relay_head = MODELS.build(feat_relay_head) + + if glbctx_head is not None: + self.glbctx_head = MODELS.build(glbctx_head) + + def init_mask_head(self, mask_roi_extractor: ConfigType, + mask_head: ConfigType) -> None: + """Initialize ``mask_head``""" + if mask_roi_extractor is not None: + self.mask_roi_extractor = MODELS.build(mask_roi_extractor) + self.mask_head = MODELS.build(mask_head) + + # TODO move to base_roi_head later + @property + def with_semantic(self) -> bool: + """bool: whether the head has semantic head""" + return hasattr(self, + 'semantic_head') and self.semantic_head is not None + + @property + def with_feat_relay(self) -> bool: + """bool: whether the head has feature relay head""" + return (hasattr(self, 'feat_relay_head') + and self.feat_relay_head is not None) + + @property + def with_glbctx(self) -> bool: + """bool: whether the head has global context head""" + return hasattr(self, 'glbctx_head') and self.glbctx_head is not None + + def _fuse_glbctx(self, roi_feats: Tensor, glbctx_feat: Tensor, + rois: Tensor) -> Tensor: + """Fuse global context feats with roi feats. + + Args: + roi_feats (Tensor): RoI features. + glbctx_feat (Tensor): Global context feature.. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + Tensor: Fused feature. + """ + assert roi_feats.size(0) == rois.size(0) + # RuntimeError: isDifferentiableType(variable.scalar_type()) + # INTERNAL ASSERT FAILED if detach() is not used when calling + # roi_head.predict(). + img_inds = torch.unique(rois[:, 0].detach().cpu(), sorted=True).long() + fused_feats = torch.zeros_like(roi_feats) + for img_id in img_inds: + inds = (rois[:, 0] == img_id.item()) + fused_feats[inds] = roi_feats[inds] + glbctx_feat[img_id] + return fused_feats + + def _slice_pos_feats(self, feats: Tensor, + sampling_results: List[SamplingResult]) -> Tensor: + """Get features from pos rois. + + Args: + feats (Tensor): Input features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + + Returns: + Tensor: Sliced features. + """ + num_rois = [res.priors.size(0) for res in sampling_results] + num_pos_rois = [res.pos_priors.size(0) for res in sampling_results] + inds = torch.zeros(sum(num_rois), dtype=torch.bool) + start = 0 + for i in range(len(num_rois)): + start = 0 if i == 0 else start + num_rois[i - 1] + stop = start + num_pos_rois[i] + inds[start:stop] = 1 + sliced_feats = feats[inds] + return sliced_feats + + def _bbox_forward(self, + stage: int, + x: Tuple[Tensor], + rois: Tensor, + semantic_feat: Optional[Tensor] = None, + glbctx_feat: Optional[Tensor] = None) -> dict: + """Box head forward function used in both training and testing. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + semantic_feat (Tensor): Semantic feature. Defaults to None. + glbctx_feat (Tensor): Global context feature. Defaults to None. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + """ + bbox_roi_extractor = self.bbox_roi_extractor[stage] + bbox_head = self.bbox_head[stage] + bbox_feats = bbox_roi_extractor(x[:bbox_roi_extractor.num_inputs], + rois) + if self.with_semantic and semantic_feat is not None: + bbox_semantic_feat = self.semantic_roi_extractor([semantic_feat], + rois) + if bbox_semantic_feat.shape[-2:] != bbox_feats.shape[-2:]: + bbox_semantic_feat = adaptive_avg_pool2d( + bbox_semantic_feat, bbox_feats.shape[-2:]) + bbox_feats += bbox_semantic_feat + if self.with_glbctx and glbctx_feat is not None: + bbox_feats = self._fuse_glbctx(bbox_feats, glbctx_feat, rois) + cls_score, bbox_pred, relayed_feat = bbox_head( + bbox_feats, return_shared_feat=True) + + bbox_results = dict( + cls_score=cls_score, + bbox_pred=bbox_pred, + relayed_feat=relayed_feat) + return bbox_results + + def _mask_forward(self, + x: Tuple[Tensor], + rois: Tensor, + semantic_feat: Optional[Tensor] = None, + glbctx_feat: Optional[Tensor] = None, + relayed_feat: Optional[Tensor] = None) -> dict: + """Mask head forward function used in both training and testing. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): Tuple of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + semantic_feat (Tensor): Semantic feature. Defaults to None. + glbctx_feat (Tensor): Global context feature. Defaults to None. + relayed_feat (Tensor): Relayed feature. Defaults to None. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + """ + mask_feats = self.mask_roi_extractor( + x[:self.mask_roi_extractor.num_inputs], rois) + if self.with_semantic and semantic_feat is not None: + mask_semantic_feat = self.semantic_roi_extractor([semantic_feat], + rois) + if mask_semantic_feat.shape[-2:] != mask_feats.shape[-2:]: + mask_semantic_feat = F.adaptive_avg_pool2d( + mask_semantic_feat, mask_feats.shape[-2:]) + mask_feats += mask_semantic_feat + if self.with_glbctx and glbctx_feat is not None: + mask_feats = self._fuse_glbctx(mask_feats, glbctx_feat, rois) + if self.with_feat_relay and relayed_feat is not None: + mask_feats = mask_feats + relayed_feat + mask_preds = self.mask_head(mask_feats) + mask_results = dict(mask_preds=mask_preds) + + return mask_results + + def bbox_loss(self, + stage: int, + x: Tuple[Tensor], + sampling_results: List[SamplingResult], + semantic_feat: Optional[Tensor] = None, + glbctx_feat: Optional[Tensor] = None) -> dict: + """Run forward function and calculate loss for box head in training. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + semantic_feat (Tensor): Semantic feature. Defaults to None. + glbctx_feat (Tensor): Global context feature. Defaults to None. + + Returns: + dict: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + - `rois` (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + - `bbox_targets` (tuple): Ground truth for proposals in a + single image. Containing the following list of Tensors: + (labels, label_weights, bbox_targets, bbox_weights) + """ + bbox_head = self.bbox_head[stage] + rois = bbox2roi([res.priors for res in sampling_results]) + bbox_results = self._bbox_forward( + stage, + x, + rois, + semantic_feat=semantic_feat, + glbctx_feat=glbctx_feat) + bbox_results.update(rois=rois) + + bbox_loss_and_target = bbox_head.loss_and_target( + cls_score=bbox_results['cls_score'], + bbox_pred=bbox_results['bbox_pred'], + rois=rois, + sampling_results=sampling_results, + rcnn_train_cfg=self.train_cfg[stage]) + + bbox_results.update(bbox_loss_and_target) + return bbox_results + + def mask_loss(self, + x: Tuple[Tensor], + sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList, + semantic_feat: Optional[Tensor] = None, + glbctx_feat: Optional[Tensor] = None, + relayed_feat: Optional[Tensor] = None) -> dict: + """Run forward function and calculate loss for mask head in training. + + Args: + x (tuple[Tensor]): Tuple of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + semantic_feat (Tensor): Semantic feature. Defaults to None. + glbctx_feat (Tensor): Global context feature. Defaults to None. + relayed_feat (Tensor): Relayed feature. Defaults to None. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + - `loss_mask` (dict): A dictionary of mask loss components. + """ + pos_rois = bbox2roi([res.pos_priors for res in sampling_results]) + mask_results = self._mask_forward( + x, + pos_rois, + semantic_feat=semantic_feat, + glbctx_feat=glbctx_feat, + relayed_feat=relayed_feat) + + mask_loss_and_target = self.mask_head.loss_and_target( + mask_preds=mask_results['mask_preds'], + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=self.train_cfg[-1]) + mask_results.update(mask_loss_and_target) + + return mask_results + + def semantic_loss(self, x: Tuple[Tensor], + batch_data_samples: SampleList) -> dict: + """Semantic segmentation loss. + + Args: + x (Tuple[Tensor]): Tuple of multi-level img features. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: Usually returns a dictionary with keys: + + - `semantic_feat` (Tensor): Semantic feature. + - `loss_seg` (dict): Semantic segmentation loss. + """ + gt_semantic_segs = [ + data_sample.gt_sem_seg.sem_seg + for data_sample in batch_data_samples + ] + gt_semantic_segs = torch.stack(gt_semantic_segs) + semantic_pred, semantic_feat = self.semantic_head(x) + loss_seg = self.semantic_head.loss(semantic_pred, gt_semantic_segs) + + semantic_results = dict(loss_seg=loss_seg, semantic_feat=semantic_feat) + + return semantic_results + + def global_context_loss(self, x: Tuple[Tensor], + batch_gt_instances: InstanceList) -> dict: + """Global context loss. + + Args: + x (Tuple[Tensor]): Tuple of multi-level img features. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + + Returns: + dict: Usually returns a dictionary with keys: + + - `glbctx_feat` (Tensor): Global context feature. + - `loss_glbctx` (dict): Global context loss. + """ + gt_labels = [ + gt_instances.labels for gt_instances in batch_gt_instances + ] + mc_pred, glbctx_feat = self.glbctx_head(x) + loss_glbctx = self.glbctx_head.loss(mc_pred, gt_labels) + global_context_results = dict( + loss_glbctx=loss_glbctx, glbctx_feat=glbctx_feat) + + return global_context_results + + def loss(self, x: Tensor, rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components + """ + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, batch_img_metas \ + = outputs + + losses = dict() + + # semantic segmentation branch + if self.with_semantic: + semantic_results = self.semantic_loss( + x=x, batch_data_samples=batch_data_samples) + losses['loss_semantic_seg'] = semantic_results['loss_seg'] + semantic_feat = semantic_results['semantic_feat'] + else: + semantic_feat = None + + # global context branch + if self.with_glbctx: + global_context_results = self.global_context_loss( + x=x, batch_gt_instances=batch_gt_instances) + losses['loss_glbctx'] = global_context_results['loss_glbctx'] + glbctx_feat = global_context_results['glbctx_feat'] + else: + glbctx_feat = None + + results_list = rpn_results_list + num_imgs = len(batch_img_metas) + for stage in range(self.num_stages): + stage_loss_weight = self.stage_loss_weights[stage] + + # assign gts and sample proposals + sampling_results = [] + bbox_assigner = self.bbox_assigner[stage] + bbox_sampler = self.bbox_sampler[stage] + for i in range(num_imgs): + results = results_list[i] + # rename rpn_results.bboxes to rpn_results.priors + results.priors = results.pop('bboxes') + + assign_result = bbox_assigner.assign( + results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = bbox_sampler.sample( + assign_result, + results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + sampling_results.append(sampling_result) + + # bbox head forward and loss + bbox_results = self.bbox_loss( + stage=stage, + x=x, + sampling_results=sampling_results, + semantic_feat=semantic_feat, + glbctx_feat=glbctx_feat) + + for name, value in bbox_results['loss_bbox'].items(): + losses[f's{stage}.{name}'] = ( + value * stage_loss_weight if 'loss' in name else value) + + # refine bboxes + if stage < self.num_stages - 1: + bbox_head = self.bbox_head[stage] + with torch.no_grad(): + results_list = bbox_head.refine_bboxes( + sampling_results=sampling_results, + bbox_results=bbox_results, + batch_img_metas=batch_img_metas) + + if self.with_feat_relay: + relayed_feat = self._slice_pos_feats(bbox_results['relayed_feat'], + sampling_results) + relayed_feat = self.feat_relay_head(relayed_feat) + else: + relayed_feat = None + + # mask head forward and loss + mask_results = self.mask_loss( + x=x, + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + semantic_feat=semantic_feat, + glbctx_feat=glbctx_feat, + relayed_feat=relayed_feat) + mask_stage_loss_weight = sum(self.stage_loss_weights) + losses['loss_mask'] = mask_stage_loss_weight * mask_results[ + 'loss_mask']['loss_mask'] + + return losses + + def predict(self, + x: Tuple[Tensor], + rpn_results_list: InstanceList, + batch_data_samples: SampleList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the roi head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Features from upstream network. Each + has shape (N, C, H, W). + rpn_results_list (list[:obj:`InstanceData`]): list of region + proposals. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results to + the original image. Defaults to False. + + Returns: + list[obj:`InstanceData`]: Detection results of each image. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + assert self.with_bbox, 'Bbox head must be implemented.' + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + if self.with_semantic: + _, semantic_feat = self.semantic_head(x) + else: + semantic_feat = None + + if self.with_glbctx: + _, glbctx_feat = self.glbctx_head(x) + else: + glbctx_feat = None + + # TODO: nms_op in mmcv need be enhanced, the bbox result may get + # difference when not rescale in bbox_head + + # If it has the mask branch, the bbox branch does not need + # to be scaled to the original image scale, because the mask + # branch will scale both bbox and mask at the same time. + bbox_rescale = rescale if not self.with_mask else False + results_list = self.predict_bbox( + x=x, + semantic_feat=semantic_feat, + glbctx_feat=glbctx_feat, + batch_img_metas=batch_img_metas, + rpn_results_list=rpn_results_list, + rcnn_test_cfg=self.test_cfg, + rescale=bbox_rescale) + + if self.with_mask: + results_list = self.predict_mask( + x=x, + semantic_heat=semantic_feat, + glbctx_feat=glbctx_feat, + batch_img_metas=batch_img_metas, + results_list=results_list, + rescale=rescale) + + return results_list + + def predict_mask(self, + x: Tuple[Tensor], + semantic_heat: Tensor, + glbctx_feat: Tensor, + batch_img_metas: List[dict], + results_list: List[InstanceData], + rescale: bool = False) -> List[InstanceData]: + """Perform forward propagation of the mask head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + semantic_feat (Tensor): Semantic feature. + glbctx_feat (Tensor): Global context feature. + batch_img_metas (list[dict]): List of image information. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + bboxes = [res.bboxes for res in results_list] + mask_rois = bbox2roi(bboxes) + if mask_rois.shape[0] == 0: + results_list = empty_instances( + batch_img_metas=batch_img_metas, + device=mask_rois.device, + task_type='mask', + instance_results=results_list, + mask_thr_binary=self.test_cfg.mask_thr_binary) + return results_list + + bboxes_results = self._bbox_forward( + stage=-1, + x=x, + rois=mask_rois, + semantic_feat=semantic_heat, + glbctx_feat=glbctx_feat) + relayed_feat = bboxes_results['relayed_feat'] + relayed_feat = self.feat_relay_head(relayed_feat) + + mask_results = self._mask_forward( + x=x, + rois=mask_rois, + semantic_feat=semantic_heat, + glbctx_feat=glbctx_feat, + relayed_feat=relayed_feat) + mask_preds = mask_results['mask_preds'] + + # split batch mask prediction back to each image + num_bbox_per_img = tuple(len(_bbox) for _bbox in bboxes) + mask_preds = mask_preds.split(num_bbox_per_img, 0) + + results_list = self.mask_head.predict_by_feat( + mask_preds=mask_preds, + results_list=results_list, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=self.test_cfg, + rescale=rescale) + + return results_list + + def forward(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + x (List[Tensor]): Multi-level features that may have different + resolutions. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns + tuple: A tuple of features from ``bbox_head`` and ``mask_head`` + forward. + """ + results = () + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + if self.with_semantic: + _, semantic_feat = self.semantic_head(x) + else: + semantic_feat = None + + if self.with_glbctx: + _, glbctx_feat = self.glbctx_head(x) + else: + glbctx_feat = None + + proposals = [rpn_results.bboxes for rpn_results in rpn_results_list] + num_proposals_per_img = tuple(len(p) for p in proposals) + rois = bbox2roi(proposals) + # bbox head + if self.with_bbox: + rois, cls_scores, bbox_preds = self._refine_roi( + x=x, + rois=rois, + semantic_feat=semantic_feat, + glbctx_feat=glbctx_feat, + batch_img_metas=batch_img_metas, + num_proposals_per_img=num_proposals_per_img) + results = results + (cls_scores, bbox_preds) + # mask head + if self.with_mask: + rois = torch.cat(rois) + bboxes_results = self._bbox_forward( + stage=-1, + x=x, + rois=rois, + semantic_feat=semantic_feat, + glbctx_feat=glbctx_feat) + relayed_feat = bboxes_results['relayed_feat'] + relayed_feat = self.feat_relay_head(relayed_feat) + mask_results = self._mask_forward( + x=x, + rois=rois, + semantic_feat=semantic_feat, + glbctx_feat=glbctx_feat, + relayed_feat=relayed_feat) + mask_preds = mask_results['mask_preds'] + mask_preds = mask_preds.split(num_proposals_per_img, 0) + results = results + (mask_preds, ) + return results diff --git a/mmdetection/mmdet/models/roi_heads/shared_heads/__init__.py b/mmdetection/mmdet/models/roi_heads/shared_heads/__init__.py new file mode 100644 index 00000000..d56636ab --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/shared_heads/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .res_layer import ResLayer + +__all__ = ['ResLayer'] diff --git a/mmdetection/mmdet/models/roi_heads/shared_heads/res_layer.py b/mmdetection/mmdet/models/roi_heads/shared_heads/res_layer.py new file mode 100644 index 00000000..d9210cb9 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/shared_heads/res_layer.py @@ -0,0 +1,79 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +from mmengine.model import BaseModule + +from mmdet.models.backbones import ResNet +from mmdet.models.layers import ResLayer as _ResLayer +from mmdet.registry import MODELS + + +@MODELS.register_module() +class ResLayer(BaseModule): + + def __init__(self, + depth, + stage=3, + stride=2, + dilation=1, + style='pytorch', + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + with_cp=False, + dcn=None, + pretrained=None, + init_cfg=None): + super(ResLayer, self).__init__(init_cfg) + + self.norm_eval = norm_eval + self.norm_cfg = norm_cfg + self.stage = stage + self.fp16_enabled = False + block, stage_blocks = ResNet.arch_settings[depth] + stage_block = stage_blocks[stage] + planes = 64 * 2**stage + inplanes = 64 * 2**(stage - 1) * block.expansion + + res_layer = _ResLayer( + block, + inplanes, + planes, + stage_block, + stride=stride, + dilation=dilation, + style=style, + with_cp=with_cp, + norm_cfg=self.norm_cfg, + dcn=dcn) + self.add_module(f'layer{stage + 1}', res_layer) + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is a deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + else: + raise TypeError('pretrained must be a str or None') + + def forward(self, x): + res_layer = getattr(self, f'layer{self.stage + 1}') + out = res_layer(x) + return out + + def train(self, mode=True): + super(ResLayer, self).train(mode) + if self.norm_eval: + for m in self.modules(): + if isinstance(m, nn.BatchNorm2d): + m.eval() diff --git a/mmdetection/mmdet/models/roi_heads/sparse_roi_head.py b/mmdetection/mmdet/models/roi_heads/sparse_roi_head.py new file mode 100644 index 00000000..19c3e1e3 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/sparse_roi_head.py @@ -0,0 +1,601 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.task_modules.samplers import PseudoSampler +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList, OptConfigType +from ..utils.misc import empty_instances, unpack_gt_instances +from .cascade_roi_head import CascadeRoIHead + + +@MODELS.register_module() +class SparseRoIHead(CascadeRoIHead): + r"""The RoIHead for `Sparse R-CNN: End-to-End Object Detection with + Learnable Proposals `_ + and `Instances as Queries `_ + + Args: + num_stages (int): Number of stage whole iterative process. + Defaults to 6. + stage_loss_weights (Tuple[float]): The loss + weight of each stage. By default all stages have + the same weight 1. + bbox_roi_extractor (:obj:`ConfigDict` or dict): Config of box + roi extractor. + mask_roi_extractor (:obj:`ConfigDict` or dict): Config of mask + roi extractor. + bbox_head (:obj:`ConfigDict` or dict): Config of box head. + mask_head (:obj:`ConfigDict` or dict): Config of mask head. + train_cfg (:obj:`ConfigDict` or dict, Optional): Configuration + information in train stage. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, Optional): Configuration + information in test stage. Defaults to None. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. Defaults to None. + """ + + def __init__(self, + num_stages: int = 6, + stage_loss_weights: Tuple[float] = (1, 1, 1, 1, 1, 1), + proposal_feature_channel: int = 256, + bbox_roi_extractor: ConfigType = dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + mask_roi_extractor: OptConfigType = None, + bbox_head: ConfigType = dict( + type='DIIHead', + num_classes=80, + num_fcs=2, + num_heads=8, + num_cls_fcs=1, + num_reg_fcs=3, + feedforward_channels=2048, + hidden_channels=256, + dropout=0.0, + roi_feat_size=7, + ffn_act_cfg=dict(type='ReLU', inplace=True)), + mask_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: OptConfigType = None) -> None: + assert bbox_roi_extractor is not None + assert bbox_head is not None + assert len(stage_loss_weights) == num_stages + self.num_stages = num_stages + self.stage_loss_weights = stage_loss_weights + self.proposal_feature_channel = proposal_feature_channel + super().__init__( + num_stages=num_stages, + stage_loss_weights=stage_loss_weights, + bbox_roi_extractor=bbox_roi_extractor, + mask_roi_extractor=mask_roi_extractor, + bbox_head=bbox_head, + mask_head=mask_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + init_cfg=init_cfg) + # train_cfg would be None when run the test.py + if train_cfg is not None: + for stage in range(num_stages): + assert isinstance(self.bbox_sampler[stage], PseudoSampler), \ + 'Sparse R-CNN and QueryInst only support `PseudoSampler`' + + def bbox_loss(self, stage: int, x: Tuple[Tensor], + results_list: InstanceList, object_feats: Tensor, + batch_img_metas: List[dict], + batch_gt_instances: InstanceList) -> dict: + """Perform forward propagation and loss calculation of the bbox head on + the features of the upstream network. + + Args: + stage (int): The current stage in iterative process. + x (tuple[Tensor]): List of multi-level img features. + results_list (List[:obj:`InstanceData`]) : List of region + proposals. + object_feats (Tensor): The object feature extracted from + the previous stage. + batch_img_metas (list[dict]): Meta information of each image. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + """ + proposal_list = [res.bboxes for res in results_list] + rois = bbox2roi(proposal_list) + bbox_results = self._bbox_forward(stage, x, rois, object_feats, + batch_img_metas) + imgs_whwh = torch.cat( + [res.imgs_whwh[None, ...] for res in results_list]) + cls_pred_list = bbox_results['detached_cls_scores'] + proposal_list = bbox_results['detached_proposals'] + + sampling_results = [] + bbox_head = self.bbox_head[stage] + for i in range(len(batch_img_metas)): + pred_instances = InstanceData() + # TODO: Enhance the logic + pred_instances.bboxes = proposal_list[i] # for assinger + pred_instances.scores = cls_pred_list[i] + pred_instances.priors = proposal_list[i] # for sampler + + assign_result = self.bbox_assigner[stage].assign( + pred_instances=pred_instances, + gt_instances=batch_gt_instances[i], + gt_instances_ignore=None, + img_meta=batch_img_metas[i]) + + sampling_result = self.bbox_sampler[stage].sample( + assign_result, pred_instances, batch_gt_instances[i]) + sampling_results.append(sampling_result) + + bbox_results.update(sampling_results=sampling_results) + + cls_score = bbox_results['cls_score'] + decoded_bboxes = bbox_results['decoded_bboxes'] + cls_score = cls_score.view(-1, cls_score.size(-1)) + decoded_bboxes = decoded_bboxes.view(-1, 4) + bbox_loss_and_target = bbox_head.loss_and_target( + cls_score, + decoded_bboxes, + sampling_results, + self.train_cfg[stage], + imgs_whwh=imgs_whwh, + concat=True) + bbox_results.update(bbox_loss_and_target) + + # propose for the new proposal_list + proposal_list = [] + for idx in range(len(batch_img_metas)): + results = InstanceData() + results.imgs_whwh = results_list[idx].imgs_whwh + results.bboxes = bbox_results['detached_proposals'][idx] + proposal_list.append(results) + bbox_results.update(results_list=proposal_list) + return bbox_results + + def _bbox_forward(self, stage: int, x: Tuple[Tensor], rois: Tensor, + object_feats: Tensor, + batch_img_metas: List[dict]) -> dict: + """Box head forward function used in both training and testing. Returns + all regression, classification results and a intermediate feature. + + Args: + stage (int): The current stage in iterative process. + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + Each dimension means (img_index, x1, y1, x2, y2). + object_feats (Tensor): The object feature extracted from + the previous stage. + batch_img_metas (list[dict]): Meta information of each image. + + Returns: + dict[str, Tensor]: a dictionary of bbox head outputs, + Containing the following results: + + - cls_score (Tensor): The score of each class, has + shape (batch_size, num_proposals, num_classes) + when use focal loss or + (batch_size, num_proposals, num_classes+1) + otherwise. + - decoded_bboxes (Tensor): The regression results + with shape (batch_size, num_proposal, 4). + The last dimension 4 represents + [tl_x, tl_y, br_x, br_y]. + - object_feats (Tensor): The object feature extracted + from current stage + - detached_cls_scores (list[Tensor]): The detached + classification results, length is batch_size, and + each tensor has shape (num_proposal, num_classes). + - detached_proposals (list[tensor]): The detached + regression results, length is batch_size, and each + tensor has shape (num_proposal, 4). The last + dimension 4 represents [tl_x, tl_y, br_x, br_y]. + """ + num_imgs = len(batch_img_metas) + bbox_roi_extractor = self.bbox_roi_extractor[stage] + bbox_head = self.bbox_head[stage] + bbox_feats = bbox_roi_extractor(x[:bbox_roi_extractor.num_inputs], + rois) + cls_score, bbox_pred, object_feats, attn_feats = bbox_head( + bbox_feats, object_feats) + + fake_bbox_results = dict( + rois=rois, + bbox_targets=(rois.new_zeros(len(rois), dtype=torch.long), None), + bbox_pred=bbox_pred.view(-1, bbox_pred.size(-1)), + cls_score=cls_score.view(-1, cls_score.size(-1))) + fake_sampling_results = [ + InstanceData(pos_is_gt=rois.new_zeros(object_feats.size(1))) + for _ in range(len(batch_img_metas)) + ] + + results_list = bbox_head.refine_bboxes( + sampling_results=fake_sampling_results, + bbox_results=fake_bbox_results, + batch_img_metas=batch_img_metas) + proposal_list = [res.bboxes for res in results_list] + bbox_results = dict( + cls_score=cls_score, + decoded_bboxes=torch.cat(proposal_list), + object_feats=object_feats, + attn_feats=attn_feats, + # detach then use it in label assign + detached_cls_scores=[ + cls_score[i].detach() for i in range(num_imgs) + ], + detached_proposals=[item.detach() for item in proposal_list]) + + return bbox_results + + def _mask_forward(self, stage: int, x: Tuple[Tensor], rois: Tensor, + attn_feats) -> dict: + """Mask head forward function used in both training and testing. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): Tuple of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + attn_feats (Tensot): Intermediate feature get from the last + diihead, has shape + (batch_size*num_proposals, feature_dimensions) + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + """ + mask_roi_extractor = self.mask_roi_extractor[stage] + mask_head = self.mask_head[stage] + mask_feats = mask_roi_extractor(x[:mask_roi_extractor.num_inputs], + rois) + # do not support caffe_c4 model anymore + mask_preds = mask_head(mask_feats, attn_feats) + + mask_results = dict(mask_preds=mask_preds) + return mask_results + + def mask_loss(self, stage: int, x: Tuple[Tensor], bbox_results: dict, + batch_gt_instances: InstanceList, + rcnn_train_cfg: ConfigDict) -> dict: + """Run forward function and calculate loss for mask head in training. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): Tuple of multi-level img features. + bbox_results (dict): Results obtained from `bbox_loss`. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + rcnn_train_cfg (obj:ConfigDict): `train_cfg` of RCNN. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + - `loss_mask` (dict): A dictionary of mask loss components. + """ + attn_feats = bbox_results['attn_feats'] + sampling_results = bbox_results['sampling_results'] + + pos_rois = bbox2roi([res.pos_priors for res in sampling_results]) + + attn_feats = torch.cat([ + feats[res.pos_inds] + for (feats, res) in zip(attn_feats, sampling_results) + ]) + mask_results = self._mask_forward(stage, x, pos_rois, attn_feats) + + mask_loss_and_target = self.mask_head[stage].loss_and_target( + mask_preds=mask_results['mask_preds'], + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=rcnn_train_cfg) + mask_results.update(mask_loss_and_target) + + return mask_results + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (List[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: a dictionary of loss components of all stage. + """ + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, batch_img_metas \ + = outputs + + object_feats = torch.cat( + [res.pop('features')[None, ...] for res in rpn_results_list]) + results_list = rpn_results_list + losses = {} + for stage in range(self.num_stages): + stage_loss_weight = self.stage_loss_weights[stage] + + # bbox head forward and loss + bbox_results = self.bbox_loss( + stage=stage, + x=x, + object_feats=object_feats, + results_list=results_list, + batch_img_metas=batch_img_metas, + batch_gt_instances=batch_gt_instances) + + for name, value in bbox_results['loss_bbox'].items(): + losses[f's{stage}.{name}'] = ( + value * stage_loss_weight if 'loss' in name else value) + + if self.with_mask: + mask_results = self.mask_loss( + stage=stage, + x=x, + bbox_results=bbox_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=self.train_cfg[stage]) + + for name, value in mask_results['loss_mask'].items(): + losses[f's{stage}.{name}'] = ( + value * stage_loss_weight if 'loss' in name else value) + + object_feats = bbox_results['object_feats'] + results_list = bbox_results['results_list'] + return losses + + def predict_bbox(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + rpn_results_list: InstanceList, + rcnn_test_cfg: ConfigType, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the bbox head and predict detection + results on the features of the upstream network. + + Args: + x(tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of R-CNN. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + proposal_list = [res.bboxes for res in rpn_results_list] + object_feats = torch.cat( + [res.pop('features')[None, ...] for res in rpn_results_list]) + if all([proposal.shape[0] == 0 for proposal in proposal_list]): + # There is no proposal in the whole batch + return empty_instances( + batch_img_metas, x[0].device, task_type='bbox') + + for stage in range(self.num_stages): + rois = bbox2roi(proposal_list) + bbox_results = self._bbox_forward(stage, x, rois, object_feats, + batch_img_metas) + object_feats = bbox_results['object_feats'] + cls_score = bbox_results['cls_score'] + proposal_list = bbox_results['detached_proposals'] + + num_classes = self.bbox_head[-1].num_classes + + if self.bbox_head[-1].loss_cls.use_sigmoid: + cls_score = cls_score.sigmoid() + else: + cls_score = cls_score.softmax(-1)[..., :-1] + + topk_inds_list = [] + results_list = [] + for img_id in range(len(batch_img_metas)): + cls_score_per_img = cls_score[img_id] + scores_per_img, topk_inds = cls_score_per_img.flatten(0, 1).topk( + self.test_cfg.max_per_img, sorted=False) + labels_per_img = topk_inds % num_classes + bboxes_per_img = proposal_list[img_id][topk_inds // num_classes] + topk_inds_list.append(topk_inds) + if rescale and bboxes_per_img.size(0) > 0: + assert batch_img_metas[img_id].get('scale_factor') is not None + scale_factor = bboxes_per_img.new_tensor( + batch_img_metas[img_id]['scale_factor']).repeat((1, 2)) + bboxes_per_img = ( + bboxes_per_img.view(bboxes_per_img.size(0), -1, 4) / + scale_factor).view(bboxes_per_img.size()[0], -1) + + results = InstanceData() + results.bboxes = bboxes_per_img + results.scores = scores_per_img + results.labels = labels_per_img + results_list.append(results) + if self.with_mask: + for img_id in range(len(batch_img_metas)): + # add positive information in InstanceData to predict + # mask results in `mask_head`. + proposals = bbox_results['detached_proposals'][img_id] + topk_inds = topk_inds_list[img_id] + attn_feats = bbox_results['attn_feats'][img_id] + + results_list[img_id].proposals = proposals + results_list[img_id].topk_inds = topk_inds + results_list[img_id].attn_feats = attn_feats + return results_list + + def predict_mask(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + results_list: InstanceList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the mask head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. Each item usually contains following keys: + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - proposal (Tensor): Bboxes predicted from bbox_head, + has a shape (num_instances, 4). + - topk_inds (Tensor): Topk indices of each image, has + shape (num_instances, ) + - attn_feats (Tensor): Intermediate feature get from the last + diihead, has shape (num_instances, feature_dimensions) + + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + proposal_list = [res.pop('proposals') for res in results_list] + topk_inds_list = [res.pop('topk_inds') for res in results_list] + attn_feats = torch.cat( + [res.pop('attn_feats')[None, ...] for res in results_list]) + + rois = bbox2roi(proposal_list) + + if rois.shape[0] == 0: + results_list = empty_instances( + batch_img_metas, + rois.device, + task_type='mask', + instance_results=results_list, + mask_thr_binary=self.test_cfg.mask_thr_binary) + return results_list + + last_stage = self.num_stages - 1 + mask_results = self._mask_forward(last_stage, x, rois, attn_feats) + + num_imgs = len(batch_img_metas) + mask_results['mask_preds'] = mask_results['mask_preds'].reshape( + num_imgs, -1, *mask_results['mask_preds'].size()[1:]) + num_classes = self.bbox_head[-1].num_classes + + mask_preds = [] + for img_id in range(num_imgs): + topk_inds = topk_inds_list[img_id] + masks_per_img = mask_results['mask_preds'][img_id].flatten( + 0, 1)[topk_inds] + masks_per_img = masks_per_img[:, None, + ...].repeat(1, num_classes, 1, 1) + mask_preds.append(masks_per_img) + results_list = self.mask_head[-1].predict_by_feat( + mask_preds, + results_list, + batch_img_metas, + rcnn_test_cfg=self.test_cfg, + rescale=rescale) + + return results_list + + # TODO: Need to refactor later + def forward(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + x (List[Tensor]): Multi-level features that may have different + resolutions. + rpn_results_list (List[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns + tuple: A tuple of features from ``bbox_head`` and ``mask_head`` + forward. + """ + outputs = unpack_gt_instances(batch_data_samples) + (batch_gt_instances, batch_gt_instances_ignore, + batch_img_metas) = outputs + + all_stage_bbox_results = [] + object_feats = torch.cat( + [res.pop('features')[None, ...] for res in rpn_results_list]) + results_list = rpn_results_list + if self.with_bbox: + for stage in range(self.num_stages): + bbox_results = self.bbox_loss( + stage=stage, + x=x, + results_list=results_list, + object_feats=object_feats, + batch_img_metas=batch_img_metas, + batch_gt_instances=batch_gt_instances) + bbox_results.pop('loss_bbox') + # torch.jit does not support obj:SamplingResult + bbox_results.pop('results_list') + bbox_res = bbox_results.copy() + bbox_res.pop('sampling_results') + all_stage_bbox_results.append((bbox_res, )) + + if self.with_mask: + attn_feats = bbox_results['attn_feats'] + sampling_results = bbox_results['sampling_results'] + + pos_rois = bbox2roi( + [res.pos_priors for res in sampling_results]) + + attn_feats = torch.cat([ + feats[res.pos_inds] + for (feats, res) in zip(attn_feats, sampling_results) + ]) + mask_results = self._mask_forward(stage, x, pos_rois, + attn_feats) + all_stage_bbox_results[-1] += (mask_results, ) + return tuple(all_stage_bbox_results) diff --git a/mmdetection/mmdet/models/roi_heads/standard_roi_head.py b/mmdetection/mmdet/models/roi_heads/standard_roi_head.py new file mode 100644 index 00000000..8d168eba --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/standard_roi_head.py @@ -0,0 +1,419 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import DetDataSample, SampleList +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import ConfigType, InstanceList +from ..task_modules.samplers import SamplingResult +from ..utils import empty_instances, unpack_gt_instances +from .base_roi_head import BaseRoIHead + + +@MODELS.register_module() +class StandardRoIHead(BaseRoIHead): + """Simplest base roi head including one bbox head and one mask head.""" + + def init_assigner_sampler(self) -> None: + """Initialize assigner and sampler.""" + self.bbox_assigner = None + self.bbox_sampler = None + if self.train_cfg: + self.bbox_assigner = TASK_UTILS.build(self.train_cfg.assigner) + self.bbox_sampler = TASK_UTILS.build( + self.train_cfg.sampler, default_args=dict(context=self)) + + def init_bbox_head(self, bbox_roi_extractor: ConfigType, + bbox_head: ConfigType) -> None: + """Initialize box head and box roi extractor. + + Args: + bbox_roi_extractor (dict or ConfigDict): Config of box + roi extractor. + bbox_head (dict or ConfigDict): Config of box in box head. + """ + self.bbox_roi_extractor = MODELS.build(bbox_roi_extractor) + self.bbox_head = MODELS.build(bbox_head) + + def init_mask_head(self, mask_roi_extractor: ConfigType, + mask_head: ConfigType) -> None: + """Initialize mask head and mask roi extractor. + + Args: + mask_roi_extractor (dict or ConfigDict): Config of mask roi + extractor. + mask_head (dict or ConfigDict): Config of mask in mask head. + """ + if mask_roi_extractor is not None: + self.mask_roi_extractor = MODELS.build(mask_roi_extractor) + self.share_roi_extractor = False + else: + self.share_roi_extractor = True + self.mask_roi_extractor = self.bbox_roi_extractor + self.mask_head = MODELS.build(mask_head) + + # TODO: Need to refactor later + def forward(self, + x: Tuple[Tensor], + rpn_results_list: InstanceList, + batch_data_samples: SampleList = None) -> tuple: + """Network forward process. Usually includes backbone, neck and head + forward without any post-processing. + + Args: + x (List[Tensor]): Multi-level features that may have different + resolutions. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + + Returns + tuple: A tuple of features from ``bbox_head`` and ``mask_head`` + forward. + """ + results = () + proposals = [rpn_results.bboxes for rpn_results in rpn_results_list] + rois = bbox2roi(proposals) + # bbox head + if self.with_bbox: + bbox_results = self._bbox_forward(x, rois) + results = results + (bbox_results['cls_score'], + bbox_results['bbox_pred']) + # mask head + if self.with_mask: + mask_rois = rois[:100] + mask_results = self._mask_forward(x, mask_rois) + results = results + (mask_results['mask_preds'], ) + return results + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: List[DetDataSample]) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components + """ + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, _ = outputs + + # assign gts and sample proposals + num_imgs = len(batch_data_samples) + sampling_results = [] + for i in range(num_imgs): + # rename rpn_results.bboxes to rpn_results.priors + rpn_results = rpn_results_list[i] + rpn_results.priors = rpn_results.pop('bboxes') + + assign_result = self.bbox_assigner.assign( + rpn_results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = self.bbox_sampler.sample( + assign_result, + rpn_results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + sampling_results.append(sampling_result) + + losses = dict() + # bbox head loss + if self.with_bbox: + bbox_results = self.bbox_loss(x, sampling_results) + losses.update(bbox_results['loss_bbox']) + + # mask head forward and loss + if self.with_mask: + mask_results = self.mask_loss(x, sampling_results, + bbox_results['bbox_feats'], + batch_gt_instances) + losses.update(mask_results['loss_mask']) + + return losses + + def _bbox_forward(self, x: Tuple[Tensor], rois: Tensor) -> dict: + """Box head forward function used in both training and testing. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + """ + # TODO: a more flexible way to decide which feature maps to use + bbox_feats = self.bbox_roi_extractor( + x[:self.bbox_roi_extractor.num_inputs], rois) + if self.with_shared_head: + bbox_feats = self.shared_head(bbox_feats) + cls_score, bbox_pred = self.bbox_head(bbox_feats) + + bbox_results = dict( + cls_score=cls_score, bbox_pred=bbox_pred, bbox_feats=bbox_feats) + return bbox_results + + def bbox_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult]) -> dict: + """Perform forward propagation and loss calculation of the bbox head on + the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + """ + rois = bbox2roi([res.priors for res in sampling_results]) + bbox_results = self._bbox_forward(x, rois) + + bbox_loss_and_target = self.bbox_head.loss_and_target( + cls_score=bbox_results['cls_score'], + bbox_pred=bbox_results['bbox_pred'], + rois=rois, + sampling_results=sampling_results, + rcnn_train_cfg=self.train_cfg) + + bbox_results.update(loss_bbox=bbox_loss_and_target['loss_bbox']) + return bbox_results + + def mask_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult], bbox_feats: Tensor, + batch_gt_instances: InstanceList) -> dict: + """Perform forward propagation and loss calculation of the mask head on + the features of the upstream network. + + Args: + x (tuple[Tensor]): Tuple of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + bbox_feats (Tensor): Extract bbox RoI features. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + - `mask_feats` (Tensor): Extract mask RoI features. + - `mask_targets` (Tensor): Mask target of each positive\ + proposals in the image. + - `loss_mask` (dict): A dictionary of mask loss components. + """ + if not self.share_roi_extractor: + pos_rois = bbox2roi([res.pos_priors for res in sampling_results]) + mask_results = self._mask_forward(x, pos_rois) + else: + pos_inds = [] + device = bbox_feats.device + for res in sampling_results: + pos_inds.append( + torch.ones( + res.pos_priors.shape[0], + device=device, + dtype=torch.uint8)) + pos_inds.append( + torch.zeros( + res.neg_priors.shape[0], + device=device, + dtype=torch.uint8)) + pos_inds = torch.cat(pos_inds) + + mask_results = self._mask_forward( + x, pos_inds=pos_inds, bbox_feats=bbox_feats) + + mask_loss_and_target = self.mask_head.loss_and_target( + mask_preds=mask_results['mask_preds'], + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=self.train_cfg) + + mask_results.update(loss_mask=mask_loss_and_target['loss_mask']) + return mask_results + + def _mask_forward(self, + x: Tuple[Tensor], + rois: Tensor = None, + pos_inds: Optional[Tensor] = None, + bbox_feats: Optional[Tensor] = None) -> dict: + """Mask head forward function used in both training and testing. + + Args: + x (tuple[Tensor]): Tuple of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + pos_inds (Tensor, optional): Indices of positive samples. + Defaults to None. + bbox_feats (Tensor): Extract bbox RoI features. Defaults to None. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + - `mask_feats` (Tensor): Extract mask RoI features. + """ + assert ((rois is not None) ^ + (pos_inds is not None and bbox_feats is not None)) + if rois is not None: + mask_feats = self.mask_roi_extractor( + x[:self.mask_roi_extractor.num_inputs], rois) + if self.with_shared_head: + mask_feats = self.shared_head(mask_feats) + else: + assert bbox_feats is not None + mask_feats = bbox_feats[pos_inds] + + mask_preds = self.mask_head(mask_feats) + mask_results = dict(mask_preds=mask_preds, mask_feats=mask_feats) + return mask_results + + def predict_bbox(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + rpn_results_list: InstanceList, + rcnn_test_cfg: ConfigType, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the bbox head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of R-CNN. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + proposals = [res.bboxes for res in rpn_results_list] + rois = bbox2roi(proposals) + + if rois.shape[0] == 0: + return empty_instances( + batch_img_metas, + rois.device, + task_type='bbox', + box_type=self.bbox_head.predict_box_type, + num_classes=self.bbox_head.num_classes, + score_per_cls=rcnn_test_cfg is None) + + bbox_results = self._bbox_forward(x, rois) + + # split batch bbox prediction back to each image + cls_scores = bbox_results['cls_score'] + bbox_preds = bbox_results['bbox_pred'] + num_proposals_per_img = tuple(len(p) for p in proposals) + rois = rois.split(num_proposals_per_img, 0) + cls_scores = cls_scores.split(num_proposals_per_img, 0) + + # some detector with_reg is False, bbox_preds will be None + if bbox_preds is not None: + # TODO move this to a sabl_roi_head + # the bbox prediction of some detectors like SABL is not Tensor + if isinstance(bbox_preds, torch.Tensor): + bbox_preds = bbox_preds.split(num_proposals_per_img, 0) + else: + bbox_preds = self.bbox_head.bbox_pred_split( + bbox_preds, num_proposals_per_img) + else: + bbox_preds = (None, ) * len(proposals) + + result_list = self.bbox_head.predict_by_feat( + rois=rois, + cls_scores=cls_scores, + bbox_preds=bbox_preds, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=rcnn_test_cfg, + rescale=rescale) + return result_list + + def predict_mask(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + results_list: InstanceList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the mask head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + # don't need to consider aug_test. + bboxes = [res.bboxes for res in results_list] + mask_rois = bbox2roi(bboxes) + if mask_rois.shape[0] == 0: + results_list = empty_instances( + batch_img_metas, + mask_rois.device, + task_type='mask', + instance_results=results_list, + mask_thr_binary=self.test_cfg.mask_thr_binary) + return results_list + + mask_results = self._mask_forward(x, mask_rois) + mask_preds = mask_results['mask_preds'] + # split batch mask prediction back to each image + num_mask_rois_per_img = [len(res) for res in results_list] + mask_preds = mask_preds.split(num_mask_rois_per_img, 0) + + # TODO: Handle the case where rescale is false + results_list = self.mask_head.predict_by_feat( + mask_preds=mask_preds, + results_list=results_list, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=self.test_cfg, + rescale=rescale) + return results_list diff --git a/mmdetection/mmdet/models/roi_heads/test_mixins.py b/mmdetection/mmdet/models/roi_heads/test_mixins.py new file mode 100644 index 00000000..94049045 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/test_mixins.py @@ -0,0 +1,171 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# TODO: delete this file after refactor +import sys + +import torch + +from mmdet.models.layers import multiclass_nms +from mmdet.models.test_time_augs import merge_aug_bboxes, merge_aug_masks +from mmdet.structures.bbox import bbox2roi, bbox_mapping + +if sys.version_info >= (3, 7): + from mmdet.utils.contextmanagers import completed + + +class BBoxTestMixin: + + if sys.version_info >= (3, 7): + # TODO: Currently not supported + async def async_test_bboxes(self, + x, + img_metas, + proposals, + rcnn_test_cfg, + rescale=False, + **kwargs): + """Asynchronized test for box head without augmentation.""" + rois = bbox2roi(proposals) + roi_feats = self.bbox_roi_extractor( + x[:len(self.bbox_roi_extractor.featmap_strides)], rois) + if self.with_shared_head: + roi_feats = self.shared_head(roi_feats) + sleep_interval = rcnn_test_cfg.get('async_sleep_interval', 0.017) + + async with completed( + __name__, 'bbox_head_forward', + sleep_interval=sleep_interval): + cls_score, bbox_pred = self.bbox_head(roi_feats) + + img_shape = img_metas[0]['img_shape'] + scale_factor = img_metas[0]['scale_factor'] + det_bboxes, det_labels = self.bbox_head.get_bboxes( + rois, + cls_score, + bbox_pred, + img_shape, + scale_factor, + rescale=rescale, + cfg=rcnn_test_cfg) + return det_bboxes, det_labels + + # TODO: Currently not supported + def aug_test_bboxes(self, feats, img_metas, rpn_results_list, + rcnn_test_cfg): + """Test det bboxes with test time augmentation.""" + aug_bboxes = [] + aug_scores = [] + for x, img_meta in zip(feats, img_metas): + # only one image in the batch + img_shape = img_meta[0]['img_shape'] + scale_factor = img_meta[0]['scale_factor'] + flip = img_meta[0]['flip'] + flip_direction = img_meta[0]['flip_direction'] + # TODO more flexible + proposals = bbox_mapping(rpn_results_list[0][:, :4], img_shape, + scale_factor, flip, flip_direction) + rois = bbox2roi([proposals]) + bbox_results = self.bbox_forward(x, rois) + bboxes, scores = self.bbox_head.get_bboxes( + rois, + bbox_results['cls_score'], + bbox_results['bbox_pred'], + img_shape, + scale_factor, + rescale=False, + cfg=None) + aug_bboxes.append(bboxes) + aug_scores.append(scores) + # after merging, bboxes will be rescaled to the original image size + merged_bboxes, merged_scores = merge_aug_bboxes( + aug_bboxes, aug_scores, img_metas, rcnn_test_cfg) + if merged_bboxes.shape[0] == 0: + # There is no proposal in the single image + det_bboxes = merged_bboxes.new_zeros(0, 5) + det_labels = merged_bboxes.new_zeros((0, ), dtype=torch.long) + else: + det_bboxes, det_labels = multiclass_nms(merged_bboxes, + merged_scores, + rcnn_test_cfg.score_thr, + rcnn_test_cfg.nms, + rcnn_test_cfg.max_per_img) + return det_bboxes, det_labels + + +class MaskTestMixin: + + if sys.version_info >= (3, 7): + # TODO: Currently not supported + async def async_test_mask(self, + x, + img_metas, + det_bboxes, + det_labels, + rescale=False, + mask_test_cfg=None): + """Asynchronized test for mask head without augmentation.""" + # image shape of the first image in the batch (only one) + ori_shape = img_metas[0]['ori_shape'] + scale_factor = img_metas[0]['scale_factor'] + if det_bboxes.shape[0] == 0: + segm_result = [[] for _ in range(self.mask_head.num_classes)] + else: + if rescale and not isinstance(scale_factor, + (float, torch.Tensor)): + scale_factor = det_bboxes.new_tensor(scale_factor) + _bboxes = ( + det_bboxes[:, :4] * + scale_factor if rescale else det_bboxes) + mask_rois = bbox2roi([_bboxes]) + mask_feats = self.mask_roi_extractor( + x[:len(self.mask_roi_extractor.featmap_strides)], + mask_rois) + + if self.with_shared_head: + mask_feats = self.shared_head(mask_feats) + if mask_test_cfg and \ + mask_test_cfg.get('async_sleep_interval'): + sleep_interval = mask_test_cfg['async_sleep_interval'] + else: + sleep_interval = 0.035 + async with completed( + __name__, + 'mask_head_forward', + sleep_interval=sleep_interval): + mask_pred = self.mask_head(mask_feats) + segm_result = self.mask_head.get_results( + mask_pred, _bboxes, det_labels, self.test_cfg, ori_shape, + scale_factor, rescale) + return segm_result + + # TODO: Currently not supported + def aug_test_mask(self, feats, img_metas, det_bboxes, det_labels): + """Test for mask head with test time augmentation.""" + if det_bboxes.shape[0] == 0: + segm_result = [[] for _ in range(self.mask_head.num_classes)] + else: + aug_masks = [] + for x, img_meta in zip(feats, img_metas): + img_shape = img_meta[0]['img_shape'] + scale_factor = img_meta[0]['scale_factor'] + flip = img_meta[0]['flip'] + flip_direction = img_meta[0]['flip_direction'] + _bboxes = bbox_mapping(det_bboxes[:, :4], img_shape, + scale_factor, flip, flip_direction) + mask_rois = bbox2roi([_bboxes]) + mask_results = self._mask_forward(x, mask_rois) + # convert to numpy array to save memory + aug_masks.append( + mask_results['mask_pred'].sigmoid().cpu().numpy()) + merged_masks = merge_aug_masks(aug_masks, img_metas, self.test_cfg) + + ori_shape = img_metas[0][0]['ori_shape'] + scale_factor = det_bboxes.new_ones(4) + segm_result = self.mask_head.get_results( + merged_masks, + det_bboxes, + det_labels, + self.test_cfg, + ori_shape, + scale_factor=scale_factor, + rescale=False) + return segm_result diff --git a/mmdetection/mmdet/models/roi_heads/trident_roi_head.py b/mmdetection/mmdet/models/roi_heads/trident_roi_head.py new file mode 100644 index 00000000..52153272 --- /dev/null +++ b/mmdetection/mmdet/models/roi_heads/trident_roi_head.py @@ -0,0 +1,112 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch +from mmcv.ops import batched_nms +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import InstanceList +from .standard_roi_head import StandardRoIHead + + +@MODELS.register_module() +class TridentRoIHead(StandardRoIHead): + """Trident roi head. + + Args: + num_branch (int): Number of branches in TridentNet. + test_branch_idx (int): In inference, all 3 branches will be used + if `test_branch_idx==-1`, otherwise only branch with index + `test_branch_idx` will be used. + """ + + def __init__(self, num_branch: int, test_branch_idx: int, + **kwargs) -> None: + self.num_branch = num_branch + self.test_branch_idx = test_branch_idx + super().__init__(**kwargs) + + def merge_trident_bboxes(self, + trident_results: InstanceList) -> InstanceData: + """Merge bbox predictions of each branch. + + Args: + trident_results (List[:obj:`InstanceData`]): A list of InstanceData + predicted from every branch. + + Returns: + :obj:`InstanceData`: merged InstanceData. + """ + bboxes = torch.cat([res.bboxes for res in trident_results]) + scores = torch.cat([res.scores for res in trident_results]) + labels = torch.cat([res.labels for res in trident_results]) + + nms_cfg = self.test_cfg['nms'] + results = InstanceData() + if bboxes.numel() == 0: + results.bboxes = bboxes + results.scores = scores + results.labels = labels + else: + det_bboxes, keep = batched_nms(bboxes, scores, labels, nms_cfg) + results.bboxes = det_bboxes[:, :-1] + results.scores = det_bboxes[:, -1] + results.labels = labels[keep] + + if self.test_cfg['max_per_img'] > 0: + results = results[:self.test_cfg['max_per_img']] + return results + + def predict(self, + x: Tuple[Tensor], + rpn_results_list: InstanceList, + batch_data_samples: SampleList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the roi head and predict detection + results on the features of the upstream network. + + - Compute prediction bbox and label per branch. + - Merge predictions of each branch according to scores of + bboxes, i.e., bboxes with higher score are kept to give + top-k prediction. + + Args: + x (tuple[Tensor]): Features from upstream network. Each + has shape (N, C, H, W). + rpn_results_list (list[:obj:`InstanceData`]): list of region + proposals. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results to + the original image. Defaults to True. + + Returns: + list[obj:`InstanceData`]: Detection results of each image. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + results_list = super().predict( + x=x, + rpn_results_list=rpn_results_list, + batch_data_samples=batch_data_samples, + rescale=rescale) + + num_branch = self.num_branch \ + if self.training or self.test_branch_idx == -1 else 1 + + merged_results_list = [] + for i in range(len(batch_data_samples) // num_branch): + merged_results_list.append( + self.merge_trident_bboxes(results_list[i * num_branch:(i + 1) * + num_branch])) + return merged_results_list diff --git a/mmdetection/mmdet/models/seg_heads/__init__.py b/mmdetection/mmdet/models/seg_heads/__init__.py new file mode 100644 index 00000000..b489a905 --- /dev/null +++ b/mmdetection/mmdet/models/seg_heads/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .panoptic_fpn_head import PanopticFPNHead # noqa: F401,F403 +from .panoptic_fusion_heads import * # noqa: F401,F403 diff --git a/mmdetection/mmdet/models/seg_heads/base_semantic_head.py b/mmdetection/mmdet/models/seg_heads/base_semantic_head.py new file mode 100644 index 00000000..1db71549 --- /dev/null +++ b/mmdetection/mmdet/models/seg_heads/base_semantic_head.py @@ -0,0 +1,113 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import Dict, List, Tuple, Union + +import torch.nn.functional as F +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptMultiConfig + + +@MODELS.register_module() +class BaseSemanticHead(BaseModule, metaclass=ABCMeta): + """Base module of Semantic Head. + + Args: + num_classes (int): the number of classes. + seg_rescale_factor (float): the rescale factor for ``gt_sem_seg``, + which equals to ``1 / output_strides``. The output_strides is + for ``seg_preds``. Defaults to 1 / 4. + init_cfg (Optional[Union[:obj:`ConfigDict`, dict]]): the initialization + config. + loss_seg (Union[:obj:`ConfigDict`, dict]): the loss of the semantic + head. + """ + + def __init__(self, + num_classes: int, + seg_rescale_factor: float = 1 / 4., + loss_seg: ConfigType = dict( + type='CrossEntropyLoss', + ignore_index=255, + loss_weight=1.0), + init_cfg: OptMultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + self.loss_seg = MODELS.build(loss_seg) + self.num_classes = num_classes + self.seg_rescale_factor = seg_rescale_factor + + @abstractmethod + def forward(self, x: Union[Tensor, Tuple[Tensor]]) -> Dict[str, Tensor]: + """Placeholder of forward function. + + Args: + x (Tensor): Feature maps. + + Returns: + Dict[str, Tensor]: A dictionary, including features + and predicted scores. Required keys: 'seg_preds' + and 'feats'. + """ + pass + + @abstractmethod + def loss(self, x: Union[Tensor, Tuple[Tensor]], + batch_data_samples: SampleList) -> Dict[str, Tensor]: + """ + Args: + x (Union[Tensor, Tuple[Tensor]]): Feature maps. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Args: + x (Tensor): Feature maps. + + Returns: + Dict[str, Tensor]: The loss of semantic head. + """ + pass + + def predict(self, + x: Union[Tensor, Tuple[Tensor]], + batch_img_metas: List[dict], + rescale: bool = False) -> List[Tensor]: + """Test without Augmentation. + + Args: + x (Union[Tensor, Tuple[Tensor]]): Feature maps. + batch_img_metas (List[dict]): List of image information. + rescale (bool): Whether to rescale the results. + Defaults to False. + + Returns: + list[Tensor]: semantic segmentation logits. + """ + seg_preds = self.forward(x)['seg_preds'] + seg_preds = F.interpolate( + seg_preds, + size=batch_img_metas[0]['batch_input_shape'], + mode='bilinear', + align_corners=False) + seg_preds = [seg_preds[i] for i in range(len(batch_img_metas))] + + if rescale: + seg_pred_list = [] + for i in range(len(batch_img_metas)): + h, w = batch_img_metas[i]['img_shape'] + seg_pred = seg_preds[i][:, :h, :w] + + h, w = batch_img_metas[i]['ori_shape'] + seg_pred = F.interpolate( + seg_pred[None], + size=(h, w), + mode='bilinear', + align_corners=False)[0] + seg_pred_list.append(seg_pred) + else: + seg_pred_list = seg_preds + + return seg_pred_list diff --git a/mmdetection/mmdet/models/seg_heads/panoptic_fpn_head.py b/mmdetection/mmdet/models/seg_heads/panoptic_fpn_head.py new file mode 100644 index 00000000..8d8b9013 --- /dev/null +++ b/mmdetection/mmdet/models/seg_heads/panoptic_fpn_head.py @@ -0,0 +1,174 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.model import ModuleList +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig +from ..layers import ConvUpsample +from ..utils import interpolate_as +from .base_semantic_head import BaseSemanticHead + + +@MODELS.register_module() +class PanopticFPNHead(BaseSemanticHead): + """PanopticFPNHead used in Panoptic FPN. + + In this head, the number of output channels is ``num_stuff_classes + + 1``, including all stuff classes and one thing class. The stuff + classes will be reset from ``0`` to ``num_stuff_classes - 1``, the + thing classes will be merged to ``num_stuff_classes``-th channel. + + Arg: + num_things_classes (int): Number of thing classes. Default: 80. + num_stuff_classes (int): Number of stuff classes. Default: 53. + in_channels (int): Number of channels in the input feature + map. + inner_channels (int): Number of channels in inner features. + start_level (int): The start level of the input features + used in PanopticFPN. + end_level (int): The end level of the used features, the + ``end_level``-th layer will not be used. + conv_cfg (Optional[Union[ConfigDict, dict]]): Dictionary to construct + and config conv layer. + norm_cfg (Union[ConfigDict, dict]): Dictionary to construct and config + norm layer. Use ``GN`` by default. + init_cfg (Optional[Union[ConfigDict, dict]]): Initialization config + dict. + loss_seg (Union[ConfigDict, dict]): the loss of the semantic head. + """ + + def __init__(self, + num_things_classes: int = 80, + num_stuff_classes: int = 53, + in_channels: int = 256, + inner_channels: int = 128, + start_level: int = 0, + end_level: int = 4, + conv_cfg: OptConfigType = None, + norm_cfg: ConfigType = dict( + type='GN', num_groups=32, requires_grad=True), + loss_seg: ConfigType = dict( + type='CrossEntropyLoss', ignore_index=-1, + loss_weight=1.0), + init_cfg: OptMultiConfig = None) -> None: + seg_rescale_factor = 1 / 2**(start_level + 2) + super().__init__( + num_classes=num_stuff_classes + 1, + seg_rescale_factor=seg_rescale_factor, + loss_seg=loss_seg, + init_cfg=init_cfg) + self.num_things_classes = num_things_classes + self.num_stuff_classes = num_stuff_classes + # Used feature layers are [start_level, end_level) + self.start_level = start_level + self.end_level = end_level + self.num_stages = end_level - start_level + self.inner_channels = inner_channels + + self.conv_upsample_layers = ModuleList() + for i in range(start_level, end_level): + self.conv_upsample_layers.append( + ConvUpsample( + in_channels, + inner_channels, + num_layers=i if i > 0 else 1, + num_upsample=i if i > 0 else 0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + )) + self.conv_logits = nn.Conv2d(inner_channels, self.num_classes, 1) + + def _set_things_to_void(self, gt_semantic_seg: Tensor) -> Tensor: + """Merge thing classes to one class. + + In PanopticFPN, the background labels will be reset from `0` to + `self.num_stuff_classes-1`, the foreground labels will be merged to + `self.num_stuff_classes`-th channel. + """ + gt_semantic_seg = gt_semantic_seg.int() + fg_mask = gt_semantic_seg < self.num_things_classes + bg_mask = (gt_semantic_seg >= self.num_things_classes) * ( + gt_semantic_seg < self.num_things_classes + self.num_stuff_classes) + + new_gt_seg = torch.clone(gt_semantic_seg) + new_gt_seg = torch.where(bg_mask, + gt_semantic_seg - self.num_things_classes, + new_gt_seg) + new_gt_seg = torch.where(fg_mask, + fg_mask.int() * self.num_stuff_classes, + new_gt_seg) + return new_gt_seg + + def loss(self, x: Union[Tensor, Tuple[Tensor]], + batch_data_samples: SampleList) -> Dict[str, Tensor]: + """ + Args: + x (Union[Tensor, Tuple[Tensor]]): Feature maps. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + Dict[str, Tensor]: The loss of semantic head. + """ + seg_preds = self(x)['seg_preds'] + gt_semantic_segs = [ + data_sample.gt_sem_seg.sem_seg + for data_sample in batch_data_samples + ] + + gt_semantic_segs = torch.stack(gt_semantic_segs) + if self.seg_rescale_factor != 1.0: + gt_semantic_segs = F.interpolate( + gt_semantic_segs.float(), + scale_factor=self.seg_rescale_factor, + mode='nearest').squeeze(1) + + # Things classes will be merged to one class in PanopticFPN. + gt_semantic_segs = self._set_things_to_void(gt_semantic_segs) + + if seg_preds.shape[-2:] != gt_semantic_segs.shape[-2:]: + seg_preds = interpolate_as(seg_preds, gt_semantic_segs) + seg_preds = seg_preds.permute((0, 2, 3, 1)) + + loss_seg = self.loss_seg( + seg_preds.reshape(-1, self.num_classes), # => [NxHxW, C] + gt_semantic_segs.reshape(-1).long()) + + return dict(loss_seg=loss_seg) + + def init_weights(self) -> None: + """Initialize weights.""" + super().init_weights() + nn.init.normal_(self.conv_logits.weight.data, 0, 0.01) + self.conv_logits.bias.data.zero_() + + def forward(self, x: Tuple[Tensor]) -> Dict[str, Tensor]: + """Forward. + + Args: + x (Tuple[Tensor]): Multi scale Feature maps. + + Returns: + dict[str, Tensor]: semantic segmentation predictions and + feature maps. + """ + # the number of subnets must be not more than + # the length of features. + assert self.num_stages <= len(x) + + feats = [] + for i, layer in enumerate(self.conv_upsample_layers): + f = layer(x[self.start_level + i]) + feats.append(f) + + seg_feats = torch.sum(torch.stack(feats, dim=0), dim=0) + seg_preds = self.conv_logits(seg_feats) + out = dict(seg_preds=seg_preds, seg_feats=seg_feats) + return out diff --git a/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/__init__.py b/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/__init__.py new file mode 100644 index 00000000..41625a61 --- /dev/null +++ b/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_panoptic_fusion_head import \ + BasePanopticFusionHead # noqa: F401,F403 +from .heuristic_fusion_head import HeuristicFusionHead # noqa: F401,F403 +from .maskformer_fusion_head import MaskFormerFusionHead # noqa: F401,F403 diff --git a/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/base_panoptic_fusion_head.py b/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/base_panoptic_fusion_head.py new file mode 100644 index 00000000..f6b20e1c --- /dev/null +++ b/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/base_panoptic_fusion_head.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod + +from mmengine.model import BaseModule + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType, OptMultiConfig + + +@MODELS.register_module() +class BasePanopticFusionHead(BaseModule, metaclass=ABCMeta): + """Base class for panoptic heads.""" + + def __init__(self, + num_things_classes: int = 80, + num_stuff_classes: int = 53, + test_cfg: OptConfigType = None, + loss_panoptic: OptConfigType = None, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + super().__init__(init_cfg=init_cfg) + self.num_things_classes = num_things_classes + self.num_stuff_classes = num_stuff_classes + self.num_classes = num_things_classes + num_stuff_classes + self.test_cfg = test_cfg + + if loss_panoptic: + self.loss_panoptic = MODELS.build(loss_panoptic) + else: + self.loss_panoptic = None + + @property + def with_loss(self) -> bool: + """bool: whether the panoptic head contains loss function.""" + return self.loss_panoptic is not None + + @abstractmethod + def loss(self, **kwargs): + """Loss function.""" + + @abstractmethod + def predict(self, **kwargs): + """Predict function.""" diff --git a/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/heuristic_fusion_head.py b/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/heuristic_fusion_head.py new file mode 100644 index 00000000..7a4a4200 --- /dev/null +++ b/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/heuristic_fusion_head.py @@ -0,0 +1,159 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +from mmengine.structures import InstanceData, PixelData +from torch import Tensor + +from mmdet.evaluation.functional import INSTANCE_OFFSET +from mmdet.registry import MODELS +from mmdet.utils import InstanceList, OptConfigType, OptMultiConfig, PixelList +from .base_panoptic_fusion_head import BasePanopticFusionHead + + +@MODELS.register_module() +class HeuristicFusionHead(BasePanopticFusionHead): + """Fusion Head with Heuristic method.""" + + def __init__(self, + num_things_classes: int = 80, + num_stuff_classes: int = 53, + test_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + super().__init__( + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes, + test_cfg=test_cfg, + loss_panoptic=None, + init_cfg=init_cfg, + **kwargs) + + def loss(self, **kwargs) -> dict: + """HeuristicFusionHead has no training loss.""" + return dict() + + def _lay_masks(self, + mask_results: InstanceData, + overlap_thr: float = 0.5) -> Tensor: + """Lay instance masks to a result map. + + Args: + mask_results (:obj:`InstanceData`): Instance segmentation results, + each contains ``bboxes``, ``labels``, ``scores`` and ``masks``. + overlap_thr (float): Threshold to determine whether two masks + overlap. default: 0.5. + + Returns: + Tensor: The result map, (H, W). + """ + bboxes = mask_results.bboxes + scores = mask_results.scores + labels = mask_results.labels + masks = mask_results.masks + + num_insts = bboxes.shape[0] + id_map = torch.zeros( + masks.shape[-2:], device=bboxes.device, dtype=torch.long) + if num_insts == 0: + return id_map, labels + + # Sort by score to use heuristic fusion + order = torch.argsort(-scores) + bboxes = bboxes[order] + labels = labels[order] + segm_masks = masks[order] + + instance_id = 1 + left_labels = [] + for idx in range(bboxes.shape[0]): + _cls = labels[idx] + _mask = segm_masks[idx] + instance_id_map = torch.ones_like( + _mask, dtype=torch.long) * instance_id + area = _mask.sum() + if area == 0: + continue + + pasted = id_map > 0 + intersect = (_mask * pasted).sum() + if (intersect / (area + 1e-5)) > overlap_thr: + continue + + _part = _mask * (~pasted) + id_map = torch.where(_part, instance_id_map, id_map) + left_labels.append(_cls) + instance_id += 1 + + if len(left_labels) > 0: + instance_labels = torch.stack(left_labels) + else: + instance_labels = bboxes.new_zeros((0, ), dtype=torch.long) + assert instance_id == (len(instance_labels) + 1) + return id_map, instance_labels + + def _predict_single(self, mask_results: InstanceData, seg_preds: Tensor, + **kwargs) -> PixelData: + """Fuse the results of instance and semantic segmentations. + + Args: + mask_results (:obj:`InstanceData`): Instance segmentation results, + each contains ``bboxes``, ``labels``, ``scores`` and ``masks``. + seg_preds (Tensor): The semantic segmentation results, + (num_stuff + 1, H, W). + + Returns: + Tensor: The panoptic segmentation result, (H, W). + """ + id_map, labels = self._lay_masks(mask_results, + self.test_cfg.mask_overlap) + + seg_results = seg_preds.argmax(dim=0) + seg_results = seg_results + self.num_things_classes + + pan_results = seg_results + instance_id = 1 + for idx in range(len(mask_results)): + _mask = id_map == (idx + 1) + if _mask.sum() == 0: + continue + _cls = labels[idx] + # simply trust detection + segment_id = _cls + instance_id * INSTANCE_OFFSET + pan_results[_mask] = segment_id + instance_id += 1 + + ids, counts = torch.unique( + pan_results % INSTANCE_OFFSET, return_counts=True) + stuff_ids = ids[ids >= self.num_things_classes] + stuff_counts = counts[ids >= self.num_things_classes] + ignore_stuff_ids = stuff_ids[ + stuff_counts < self.test_cfg.stuff_area_limit] + + assert pan_results.ndim == 2 + pan_results[(pan_results.unsqueeze(2) == ignore_stuff_ids.reshape( + 1, 1, -1)).any(dim=2)] = self.num_classes + + pan_results = PixelData(sem_seg=pan_results[None].int()) + return pan_results + + def predict(self, mask_results_list: InstanceList, + seg_preds_list: List[Tensor], **kwargs) -> PixelList: + """Predict results by fusing the results of instance and semantic + segmentations. + + Args: + mask_results_list (list[:obj:`InstanceData`]): Instance + segmentation results, each contains ``bboxes``, ``labels``, + ``scores`` and ``masks``. + seg_preds_list (Tensor): List of semantic segmentation results. + + Returns: + List[PixelData]: Panoptic segmentation result. + """ + results_list = [ + self._predict_single(mask_results_list[i], seg_preds_list[i]) + for i in range(len(mask_results_list)) + ] + + return results_list diff --git a/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/maskformer_fusion_head.py b/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/maskformer_fusion_head.py new file mode 100644 index 00000000..1b76e6b4 --- /dev/null +++ b/mmdetection/mmdet/models/seg_heads/panoptic_fusion_heads/maskformer_fusion_head.py @@ -0,0 +1,266 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn.functional as F +from mmengine.structures import InstanceData, PixelData +from torch import Tensor + +from mmdet.evaluation.functional import INSTANCE_OFFSET +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.mask import mask2bbox +from mmdet.utils import OptConfigType, OptMultiConfig +from .base_panoptic_fusion_head import BasePanopticFusionHead + + +@MODELS.register_module() +class MaskFormerFusionHead(BasePanopticFusionHead): + """MaskFormer fusion head which postprocesses results for panoptic + segmentation, instance segmentation and semantic segmentation.""" + + def __init__(self, + num_things_classes: int = 80, + num_stuff_classes: int = 53, + test_cfg: OptConfigType = None, + loss_panoptic: OptConfigType = None, + init_cfg: OptMultiConfig = None, + **kwargs): + super().__init__( + num_things_classes=num_things_classes, + num_stuff_classes=num_stuff_classes, + test_cfg=test_cfg, + loss_panoptic=loss_panoptic, + init_cfg=init_cfg, + **kwargs) + + def loss(self, **kwargs): + """MaskFormerFusionHead has no training loss.""" + return dict() + + def panoptic_postprocess(self, mask_cls: Tensor, + mask_pred: Tensor) -> PixelData: + """Panoptic segmengation inference. + + Args: + mask_cls (Tensor): Classfication outputs of shape + (num_queries, cls_out_channels) for a image. + Note `cls_out_channels` should includes + background. + mask_pred (Tensor): Mask outputs of shape + (num_queries, h, w) for a image. + + Returns: + :obj:`PixelData`: Panoptic segment result of shape \ + (h, w), each element in Tensor means: \ + ``segment_id = _cls + instance_id * INSTANCE_OFFSET``. + """ + object_mask_thr = self.test_cfg.get('object_mask_thr', 0.8) + iou_thr = self.test_cfg.get('iou_thr', 0.8) + filter_low_score = self.test_cfg.get('filter_low_score', False) + + scores, labels = F.softmax(mask_cls, dim=-1).max(-1) + mask_pred = mask_pred.sigmoid() + + keep = labels.ne(self.num_classes) & (scores > object_mask_thr) + cur_scores = scores[keep] + cur_classes = labels[keep] + cur_masks = mask_pred[keep] + + cur_prob_masks = cur_scores.view(-1, 1, 1) * cur_masks + + h, w = cur_masks.shape[-2:] + panoptic_seg = torch.full((h, w), + self.num_classes, + dtype=torch.int32, + device=cur_masks.device) + if cur_masks.shape[0] == 0: + # We didn't detect any mask :( + pass + else: + cur_mask_ids = cur_prob_masks.argmax(0) + instance_id = 1 + for k in range(cur_classes.shape[0]): + pred_class = int(cur_classes[k].item()) + isthing = pred_class < self.num_things_classes + mask = cur_mask_ids == k + mask_area = mask.sum().item() + original_area = (cur_masks[k] >= 0.5).sum().item() + + if filter_low_score: + mask = mask & (cur_masks[k] >= 0.5) + + if mask_area > 0 and original_area > 0: + if mask_area / original_area < iou_thr: + continue + + if not isthing: + # different stuff regions of same class will be + # merged here, and stuff share the instance_id 0. + panoptic_seg[mask] = pred_class + else: + panoptic_seg[mask] = ( + pred_class + instance_id * INSTANCE_OFFSET) + instance_id += 1 + + return PixelData(sem_seg=panoptic_seg[None]) + + def semantic_postprocess(self, mask_cls: Tensor, + mask_pred: Tensor) -> PixelData: + """Semantic segmengation postprocess. + + Args: + mask_cls (Tensor): Classfication outputs of shape + (num_queries, cls_out_channels) for a image. + Note `cls_out_channels` should includes + background. + mask_pred (Tensor): Mask outputs of shape + (num_queries, h, w) for a image. + + Returns: + :obj:`PixelData`: Semantic segment result. + """ + # TODO add semantic segmentation result + raise NotImplementedError + + def instance_postprocess(self, mask_cls: Tensor, + mask_pred: Tensor) -> InstanceData: + """Instance segmengation postprocess. + + Args: + mask_cls (Tensor): Classfication outputs of shape + (num_queries, cls_out_channels) for a image. + Note `cls_out_channels` should includes + background. + mask_pred (Tensor): Mask outputs of shape + (num_queries, h, w) for a image. + + Returns: + :obj:`InstanceData`: Instance segmentation results. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + max_per_image = self.test_cfg.get('max_per_image', 100) + num_queries = mask_cls.shape[0] + # shape (num_queries, num_class) + scores = F.softmax(mask_cls, dim=-1)[:, :-1] + # shape (num_queries * num_class, ) + labels = torch.arange(self.num_classes, device=mask_cls.device).\ + unsqueeze(0).repeat(num_queries, 1).flatten(0, 1) + scores_per_image, top_indices = scores.flatten(0, 1).topk( + max_per_image, sorted=False) + labels_per_image = labels[top_indices] + + query_indices = top_indices // self.num_classes + mask_pred = mask_pred[query_indices] + + # extract things + is_thing = labels_per_image < self.num_things_classes + scores_per_image = scores_per_image[is_thing] + labels_per_image = labels_per_image[is_thing] + mask_pred = mask_pred[is_thing] + + mask_pred_binary = (mask_pred > 0).float() + mask_scores_per_image = (mask_pred.sigmoid() * + mask_pred_binary).flatten(1).sum(1) / ( + mask_pred_binary.flatten(1).sum(1) + 1e-6) + det_scores = scores_per_image * mask_scores_per_image + mask_pred_binary = mask_pred_binary.bool() + bboxes = mask2bbox(mask_pred_binary) + + results = InstanceData() + results.bboxes = bboxes + results.labels = labels_per_image + results.scores = det_scores + results.masks = mask_pred_binary + return results + + def predict(self, + mask_cls_results: Tensor, + mask_pred_results: Tensor, + batch_data_samples: SampleList, + rescale: bool = False, + **kwargs) -> List[dict]: + """Test segment without test-time aumengtation. + + Only the output of last decoder layers was used. + + Args: + mask_cls_results (Tensor): Mask classification logits, + shape (batch_size, num_queries, cls_out_channels). + Note `cls_out_channels` should includes background. + mask_pred_results (Tensor): Mask logits, shape + (batch_size, num_queries, h, w). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): If True, return boxes in + original image space. Default False. + + Returns: + list[dict]: Instance segmentation \ + results and panoptic segmentation results for each \ + image. + + .. code-block:: none + + [ + { + 'pan_results': PixelData, + 'ins_results': InstanceData, + # semantic segmentation results are not supported yet + 'sem_results': PixelData + }, + ... + ] + """ + batch_img_metas = [ + data_sample.metainfo for data_sample in batch_data_samples + ] + panoptic_on = self.test_cfg.get('panoptic_on', True) + semantic_on = self.test_cfg.get('semantic_on', False) + instance_on = self.test_cfg.get('instance_on', False) + assert not semantic_on, 'segmantic segmentation '\ + 'results are not supported yet.' + + results = [] + for mask_cls_result, mask_pred_result, meta in zip( + mask_cls_results, mask_pred_results, batch_img_metas): + # remove padding + img_height, img_width = meta['img_shape'][:2] + mask_pred_result = mask_pred_result[:, :img_height, :img_width] + + if rescale: + # return result in original resolution + ori_height, ori_width = meta['ori_shape'][:2] + mask_pred_result = F.interpolate( + mask_pred_result[:, None], + size=(ori_height, ori_width), + mode='bilinear', + align_corners=False)[:, 0] + + result = dict() + if panoptic_on: + pan_results = self.panoptic_postprocess( + mask_cls_result, mask_pred_result) + result['pan_results'] = pan_results + + if instance_on: + ins_results = self.instance_postprocess( + mask_cls_result, mask_pred_result) + result['ins_results'] = ins_results + + if semantic_on: + sem_results = self.semantic_postprocess( + mask_cls_result, mask_pred_result) + result['sem_results'] = sem_results + + results.append(result) + + return results diff --git a/mmdetection/mmdet/models/task_modules/__init__.py b/mmdetection/mmdet/models/task_modules/__init__.py new file mode 100644 index 00000000..7bfd8f05 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .assigners import * # noqa: F401,F403 +from .builder import (ANCHOR_GENERATORS, BBOX_ASSIGNERS, BBOX_CODERS, + BBOX_SAMPLERS, IOU_CALCULATORS, MATCH_COSTS, + PRIOR_GENERATORS, build_anchor_generator, build_assigner, + build_bbox_coder, build_iou_calculator, build_match_cost, + build_prior_generator, build_sampler) +from .coders import * # noqa: F401,F403 +from .prior_generators import * # noqa: F401,F403 +from .samplers import * # noqa: F401,F403 +from .tracking import * # noqa: F401,F403 + +__all__ = [ + 'ANCHOR_GENERATORS', 'PRIOR_GENERATORS', 'BBOX_ASSIGNERS', 'BBOX_SAMPLERS', + 'MATCH_COSTS', 'BBOX_CODERS', 'IOU_CALCULATORS', 'build_anchor_generator', + 'build_prior_generator', 'build_assigner', 'build_sampler', + 'build_iou_calculator', 'build_match_cost', 'build_bbox_coder' +] diff --git a/mmdetection/mmdet/models/task_modules/assigners/__init__.py b/mmdetection/mmdet/models/task_modules/assigners/__init__.py new file mode 100644 index 00000000..4e564f24 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/__init__.py @@ -0,0 +1,32 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .approx_max_iou_assigner import ApproxMaxIoUAssigner +from .assign_result import AssignResult +from .atss_assigner import ATSSAssigner +from .base_assigner import BaseAssigner +from .center_region_assigner import CenterRegionAssigner +from .dynamic_soft_label_assigner import DynamicSoftLabelAssigner +from .grid_assigner import GridAssigner +from .hungarian_assigner import HungarianAssigner +from .iou2d_calculator import BboxOverlaps2D, BboxOverlaps2D_GLIP +from .match_cost import (BBoxL1Cost, BinaryFocalLossCost, ClassificationCost, + CrossEntropyLossCost, DiceCost, FocalLossCost, + IoUCost) +from .max_iou_assigner import MaxIoUAssigner +from .multi_instance_assigner import MultiInstanceAssigner +from .point_assigner import PointAssigner +from .region_assigner import RegionAssigner +from .sim_ota_assigner import SimOTAAssigner +from .task_aligned_assigner import TaskAlignedAssigner +from .topk_hungarian_assigner import TopkHungarianAssigner +from .uniform_assigner import UniformAssigner + +__all__ = [ + 'BaseAssigner', 'BinaryFocalLossCost', 'MaxIoUAssigner', + 'ApproxMaxIoUAssigner', 'AssignResult', 'PointAssigner', 'ATSSAssigner', + 'CenterRegionAssigner', 'GridAssigner', 'HungarianAssigner', + 'RegionAssigner', 'UniformAssigner', 'SimOTAAssigner', + 'TaskAlignedAssigner', 'TopkHungarianAssigner', 'BBoxL1Cost', + 'ClassificationCost', 'CrossEntropyLossCost', 'DiceCost', 'FocalLossCost', + 'IoUCost', 'BboxOverlaps2D', 'DynamicSoftLabelAssigner', + 'MultiInstanceAssigner', 'BboxOverlaps2D_GLIP' +] diff --git a/mmdetection/mmdet/models/task_modules/assigners/approx_max_iou_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/approx_max_iou_assigner.py new file mode 100644 index 00000000..471d54e5 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/approx_max_iou_assigner.py @@ -0,0 +1,162 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Union + +import torch +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData + +from mmdet.registry import TASK_UTILS +from .assign_result import AssignResult +from .max_iou_assigner import MaxIoUAssigner + + +@TASK_UTILS.register_module() +class ApproxMaxIoUAssigner(MaxIoUAssigner): + """Assign a corresponding gt bbox or background to each bbox. + + Each proposals will be assigned with an integer indicating the ground truth + index. (semi-positive index: gt label (0-based), -1: background) + + - -1: negative sample, no assigned gt + - semi-positive integer: positive sample, index (0-based) of assigned gt + + Args: + pos_iou_thr (float): IoU threshold for positive bboxes. + neg_iou_thr (float or tuple): IoU threshold for negative bboxes. + min_pos_iou (float): Minimum iou for a bbox to be considered as a + positive bbox. Positive samples can have smaller IoU than + pos_iou_thr due to the 4th step (assign max IoU sample to each gt). + gt_max_assign_all (bool): Whether to assign all bboxes with the same + highest overlap with some gt to that gt. + ignore_iof_thr (float): IoF threshold for ignoring bboxes (if + `gt_bboxes_ignore` is specified). Negative values mean not + ignoring any bboxes. + ignore_wrt_candidates (bool): Whether to compute the iof between + `bboxes` and `gt_bboxes_ignore`, or the contrary. + match_low_quality (bool): Whether to allow quality matches. This is + usually allowed for RPN and single stage detectors, but not allowed + in the second stage. + gpu_assign_thr (int): The upper bound of the number of GT for GPU + assign. When the number of gt is above this threshold, will assign + on CPU device. Negative values mean not assign on CPU. + iou_calculator (:obj:`ConfigDict` or dict): Config of overlaps + Calculator. + """ + + def __init__( + self, + pos_iou_thr: float, + neg_iou_thr: Union[float, tuple], + min_pos_iou: float = .0, + gt_max_assign_all: bool = True, + ignore_iof_thr: float = -1, + ignore_wrt_candidates: bool = True, + match_low_quality: bool = True, + gpu_assign_thr: int = -1, + iou_calculator: Union[ConfigDict, dict] = dict(type='BboxOverlaps2D') + ) -> None: + self.pos_iou_thr = pos_iou_thr + self.neg_iou_thr = neg_iou_thr + self.min_pos_iou = min_pos_iou + self.gt_max_assign_all = gt_max_assign_all + self.ignore_iof_thr = ignore_iof_thr + self.ignore_wrt_candidates = ignore_wrt_candidates + self.gpu_assign_thr = gpu_assign_thr + self.match_low_quality = match_low_quality + self.iou_calculator = TASK_UTILS.build(iou_calculator) + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs) -> AssignResult: + """Assign gt to approxs. + + This method assign a gt bbox to each group of approxs (bboxes), + each group of approxs is represent by a base approx (bbox) and + will be assigned with -1, or a semi-positive number. + background_label (-1) means negative sample, + semi-positive number is the index (0-based) of assigned gt. + The assignment is done in following steps, the order matters. + + 1. assign every bbox to background_label (-1) + 2. use the max IoU of each group of approxs to assign + 2. assign proposals whose iou with all gts < neg_iou_thr to background + 3. for each bbox, if the iou with its nearest gt >= pos_iou_thr, + assign it to that bbox + 4. for each gt bbox, assign its nearest proposals (may be more than + one) to itself + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). ``approxs`` means the + group of approxs aligned with ``priors``, has shape + (n, num_approxs, 4). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + + Returns: + :obj:`AssignResult`: The assign result. + """ + squares = pred_instances.priors + approxs = pred_instances.approxs + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + gt_bboxes_ignore = None if gt_instances_ignore is None else \ + gt_instances_ignore.get('bboxes', None) + approxs_per_octave = approxs.size(1) + + num_squares = squares.size(0) + num_gts = gt_bboxes.size(0) + + if num_squares == 0 or num_gts == 0: + # No predictions and/or truth, return empty assignment + overlaps = approxs.new(num_gts, num_squares) + assign_result = self.assign_wrt_overlaps(overlaps, gt_labels) + return assign_result + + # re-organize anchors by approxs_per_octave x num_squares + approxs = torch.transpose(approxs, 0, 1).contiguous().view(-1, 4) + assign_on_cpu = True if (self.gpu_assign_thr > 0) and ( + num_gts > self.gpu_assign_thr) else False + # compute overlap and assign gt on CPU when number of GT is large + if assign_on_cpu: + device = approxs.device + approxs = approxs.cpu() + gt_bboxes = gt_bboxes.cpu() + if gt_bboxes_ignore is not None: + gt_bboxes_ignore = gt_bboxes_ignore.cpu() + if gt_labels is not None: + gt_labels = gt_labels.cpu() + all_overlaps = self.iou_calculator(approxs, gt_bboxes) + + overlaps, _ = all_overlaps.view(approxs_per_octave, num_squares, + num_gts).max(dim=0) + overlaps = torch.transpose(overlaps, 0, 1) + + if (self.ignore_iof_thr > 0 and gt_bboxes_ignore is not None + and gt_bboxes_ignore.numel() > 0 and squares.numel() > 0): + if self.ignore_wrt_candidates: + ignore_overlaps = self.iou_calculator( + squares, gt_bboxes_ignore, mode='iof') + ignore_max_overlaps, _ = ignore_overlaps.max(dim=1) + else: + ignore_overlaps = self.iou_calculator( + gt_bboxes_ignore, squares, mode='iof') + ignore_max_overlaps, _ = ignore_overlaps.max(dim=0) + overlaps[:, ignore_max_overlaps > self.ignore_iof_thr] = -1 + + assign_result = self.assign_wrt_overlaps(overlaps, gt_labels) + if assign_on_cpu: + assign_result.gt_inds = assign_result.gt_inds.to(device) + assign_result.max_overlaps = assign_result.max_overlaps.to(device) + if assign_result.labels is not None: + assign_result.labels = assign_result.labels.to(device) + return assign_result diff --git a/mmdetection/mmdet/models/task_modules/assigners/assign_result.py b/mmdetection/mmdet/models/task_modules/assigners/assign_result.py new file mode 100644 index 00000000..56ca2c3c --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/assign_result.py @@ -0,0 +1,198 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import Tensor + +from mmdet.utils import util_mixins + + +class AssignResult(util_mixins.NiceRepr): + """Stores assignments between predicted and truth boxes. + + Attributes: + num_gts (int): the number of truth boxes considered when computing this + assignment + gt_inds (Tensor): for each predicted box indicates the 1-based + index of the assigned truth box. 0 means unassigned and -1 means + ignore. + max_overlaps (Tensor): the iou between the predicted box and its + assigned truth box. + labels (Tensor): If specified, for each predicted box + indicates the category label of the assigned truth box. + + Example: + >>> # An assign result between 4 predicted boxes and 9 true boxes + >>> # where only two boxes were assigned. + >>> num_gts = 9 + >>> max_overlaps = torch.LongTensor([0, .5, .9, 0]) + >>> gt_inds = torch.LongTensor([-1, 1, 2, 0]) + >>> labels = torch.LongTensor([0, 3, 4, 0]) + >>> self = AssignResult(num_gts, gt_inds, max_overlaps, labels) + >>> print(str(self)) # xdoctest: +IGNORE_WANT + + >>> # Force addition of gt labels (when adding gt as proposals) + >>> new_labels = torch.LongTensor([3, 4, 5]) + >>> self.add_gt_(new_labels) + >>> print(str(self)) # xdoctest: +IGNORE_WANT + + """ + + def __init__(self, num_gts: int, gt_inds: Tensor, max_overlaps: Tensor, + labels: Tensor) -> None: + self.num_gts = num_gts + self.gt_inds = gt_inds + self.max_overlaps = max_overlaps + self.labels = labels + # Interface for possible user-defined properties + self._extra_properties = {} + + @property + def num_preds(self): + """int: the number of predictions in this assignment""" + return len(self.gt_inds) + + def set_extra_property(self, key, value): + """Set user-defined new property.""" + assert key not in self.info + self._extra_properties[key] = value + + def get_extra_property(self, key): + """Get user-defined property.""" + return self._extra_properties.get(key, None) + + @property + def info(self): + """dict: a dictionary of info about the object""" + basic_info = { + 'num_gts': self.num_gts, + 'num_preds': self.num_preds, + 'gt_inds': self.gt_inds, + 'max_overlaps': self.max_overlaps, + 'labels': self.labels, + } + basic_info.update(self._extra_properties) + return basic_info + + def __nice__(self): + """str: a "nice" summary string describing this assign result""" + parts = [] + parts.append(f'num_gts={self.num_gts!r}') + if self.gt_inds is None: + parts.append(f'gt_inds={self.gt_inds!r}') + else: + parts.append(f'gt_inds.shape={tuple(self.gt_inds.shape)!r}') + if self.max_overlaps is None: + parts.append(f'max_overlaps={self.max_overlaps!r}') + else: + parts.append('max_overlaps.shape=' + f'{tuple(self.max_overlaps.shape)!r}') + if self.labels is None: + parts.append(f'labels={self.labels!r}') + else: + parts.append(f'labels.shape={tuple(self.labels.shape)!r}') + return ', '.join(parts) + + @classmethod + def random(cls, **kwargs): + """Create random AssignResult for tests or debugging. + + Args: + num_preds: number of predicted boxes + num_gts: number of true boxes + p_ignore (float): probability of a predicted box assigned to an + ignored truth + p_assigned (float): probability of a predicted box not being + assigned + p_use_label (float | bool): with labels or not + rng (None | int | numpy.random.RandomState): seed or state + + Returns: + :obj:`AssignResult`: Randomly generated assign results. + + Example: + >>> from mmdet.models.task_modules.assigners.assign_result import * # NOQA + >>> self = AssignResult.random() + >>> print(self.info) + """ + from ..samplers.sampling_result import ensure_rng + rng = ensure_rng(kwargs.get('rng', None)) + + num_gts = kwargs.get('num_gts', None) + num_preds = kwargs.get('num_preds', None) + p_ignore = kwargs.get('p_ignore', 0.3) + p_assigned = kwargs.get('p_assigned', 0.7) + num_classes = kwargs.get('num_classes', 3) + + if num_gts is None: + num_gts = rng.randint(0, 8) + if num_preds is None: + num_preds = rng.randint(0, 16) + + if num_gts == 0: + max_overlaps = torch.zeros(num_preds, dtype=torch.float32) + gt_inds = torch.zeros(num_preds, dtype=torch.int64) + labels = torch.zeros(num_preds, dtype=torch.int64) + + else: + import numpy as np + + # Create an overlap for each predicted box + max_overlaps = torch.from_numpy(rng.rand(num_preds)) + + # Construct gt_inds for each predicted box + is_assigned = torch.from_numpy(rng.rand(num_preds) < p_assigned) + # maximum number of assignments constraints + n_assigned = min(num_preds, min(num_gts, is_assigned.sum())) + + assigned_idxs = np.where(is_assigned)[0] + rng.shuffle(assigned_idxs) + assigned_idxs = assigned_idxs[0:n_assigned] + assigned_idxs.sort() + + is_assigned[:] = 0 + is_assigned[assigned_idxs] = True + + is_ignore = torch.from_numpy( + rng.rand(num_preds) < p_ignore) & is_assigned + + gt_inds = torch.zeros(num_preds, dtype=torch.int64) + + true_idxs = np.arange(num_gts) + rng.shuffle(true_idxs) + true_idxs = torch.from_numpy(true_idxs) + gt_inds[is_assigned] = true_idxs[:n_assigned].long() + + gt_inds = torch.from_numpy( + rng.randint(1, num_gts + 1, size=num_preds)) + gt_inds[is_ignore] = -1 + gt_inds[~is_assigned] = 0 + max_overlaps[~is_assigned] = 0 + + if num_classes == 0: + labels = torch.zeros(num_preds, dtype=torch.int64) + else: + labels = torch.from_numpy( + # remind that we set FG labels to [0, num_class-1] + # since mmdet v2.0 + # BG cat_id: num_class + rng.randint(0, num_classes, size=num_preds)) + labels[~is_assigned] = 0 + + self = cls(num_gts, gt_inds, max_overlaps, labels) + return self + + def add_gt_(self, gt_labels): + """Add ground truth as assigned results. + + Args: + gt_labels (torch.Tensor): Labels of gt boxes + """ + self_inds = torch.arange( + 1, len(gt_labels) + 1, dtype=torch.long, device=gt_labels.device) + self.gt_inds = torch.cat([self_inds, self.gt_inds]) + + self.max_overlaps = torch.cat( + [self.max_overlaps.new_ones(len(gt_labels)), self.max_overlaps]) + + self.labels = torch.cat([gt_labels, self.labels]) diff --git a/mmdetection/mmdet/models/task_modules/assigners/atss_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/atss_assigner.py new file mode 100644 index 00000000..2796b990 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/atss_assigner.py @@ -0,0 +1,254 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import List, Optional + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.utils import ConfigType +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + + +def bbox_center_distance(bboxes: Tensor, priors: Tensor) -> Tensor: + """Compute the center distance between bboxes and priors. + + Args: + bboxes (Tensor): Shape (n, 4) for , "xyxy" format. + priors (Tensor): Shape (n, 4) for priors, "xyxy" format. + + Returns: + Tensor: Center distances between bboxes and priors. + """ + bbox_cx = (bboxes[:, 0] + bboxes[:, 2]) / 2.0 + bbox_cy = (bboxes[:, 1] + bboxes[:, 3]) / 2.0 + bbox_points = torch.stack((bbox_cx, bbox_cy), dim=1) + + priors_cx = (priors[:, 0] + priors[:, 2]) / 2.0 + priors_cy = (priors[:, 1] + priors[:, 3]) / 2.0 + priors_points = torch.stack((priors_cx, priors_cy), dim=1) + + distances = (priors_points[:, None, :] - + bbox_points[None, :, :]).pow(2).sum(-1).sqrt() + + return distances + + +@TASK_UTILS.register_module() +class ATSSAssigner(BaseAssigner): + """Assign a corresponding gt bbox or background to each prior. + + Each proposals will be assigned with `0` or a positive integer + indicating the ground truth index. + + - 0: negative sample, no assigned gt + - positive integer: positive sample, index (1-based) of assigned gt + + If ``alpha`` is not None, it means that the dynamic cost + ATSSAssigner is adopted, which is currently only used in the DDOD. + + Args: + topk (int): number of priors selected in each level + alpha (float, optional): param of cost rate for each proposal only + in DDOD. Defaults to None. + iou_calculator (:obj:`ConfigDict` or dict): Config dict for iou + calculator. Defaults to ``dict(type='BboxOverlaps2D')`` + ignore_iof_thr (float): IoF threshold for ignoring bboxes (if + `gt_bboxes_ignore` is specified). Negative values mean not + ignoring any bboxes. Defaults to -1. + """ + + def __init__(self, + topk: int, + alpha: Optional[float] = None, + iou_calculator: ConfigType = dict(type='BboxOverlaps2D'), + ignore_iof_thr: float = -1) -> None: + self.topk = topk + self.alpha = alpha + self.iou_calculator = TASK_UTILS.build(iou_calculator) + self.ignore_iof_thr = ignore_iof_thr + + # https://github.com/sfzhang15/ATSS/blob/master/atss_core/modeling/rpn/atss/loss.py + def assign( + self, + pred_instances: InstanceData, + num_level_priors: List[int], + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None + ) -> AssignResult: + """Assign gt to priors. + + The assignment is done in following steps + + 1. compute iou between all prior (prior of all pyramid levels) and gt + 2. compute center distance between all prior and gt + 3. on each pyramid level, for each gt, select k prior whose center + are closest to the gt center, so we total select k*l prior as + candidates for each gt + 4. get corresponding iou for the these candidates, and compute the + mean and std, set mean + std as the iou threshold + 5. select these candidates whose iou are greater than or equal to + the threshold as positive + 6. limit the positive sample's center in gt + + If ``alpha`` is not None, and ``cls_scores`` and `bbox_preds` + are not None, the overlaps calculation in the first step + will also include dynamic cost, which is currently only used in + the DDOD. + + Args: + pred_instances (:obj:`InstaceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors, points, or bboxes predicted by the model, + shape(n, 4). + num_level_priors (List): Number of bboxes in each level + gt_instances (:obj:`InstaceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + gt_instances_ignore (:obj:`InstaceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + + Returns: + :obj:`AssignResult`: The assign result. + """ + gt_bboxes = gt_instances.bboxes + priors = pred_instances.priors + gt_labels = gt_instances.labels + if gt_instances_ignore is not None: + gt_bboxes_ignore = gt_instances_ignore.bboxes + else: + gt_bboxes_ignore = None + + INF = 100000000 + priors = priors[:, :4] + num_gt, num_priors = gt_bboxes.size(0), priors.size(0) + + message = 'Invalid alpha parameter because cls_scores or ' \ + 'bbox_preds are None. If you want to use the ' \ + 'cost-based ATSSAssigner, please set cls_scores, ' \ + 'bbox_preds and self.alpha at the same time. ' + + # compute iou between all bbox and gt + if self.alpha is None: + # ATSSAssigner + overlaps = self.iou_calculator(priors, gt_bboxes) + if ('scores' in pred_instances or 'bboxes' in pred_instances): + warnings.warn(message) + + else: + # Dynamic cost ATSSAssigner in DDOD + assert ('scores' in pred_instances + and 'bboxes' in pred_instances), message + cls_scores = pred_instances.scores + bbox_preds = pred_instances.bboxes + + # compute cls cost for bbox and GT + cls_cost = torch.sigmoid(cls_scores[:, gt_labels]) + + # compute iou between all bbox and gt + overlaps = self.iou_calculator(bbox_preds, gt_bboxes) + + # make sure that we are in element-wise multiplication + assert cls_cost.shape == overlaps.shape + + # overlaps is actually a cost matrix + overlaps = cls_cost**(1 - self.alpha) * overlaps**self.alpha + + # assign 0 by default + assigned_gt_inds = overlaps.new_full((num_priors, ), + 0, + dtype=torch.long) + + if num_gt == 0 or num_priors == 0: + # No ground truth or boxes, return empty assignment + max_overlaps = overlaps.new_zeros((num_priors, )) + if num_gt == 0: + # No truth, assign everything to background + assigned_gt_inds[:] = 0 + assigned_labels = overlaps.new_full((num_priors, ), + -1, + dtype=torch.long) + return AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) + + # compute center distance between all bbox and gt + distances = bbox_center_distance(gt_bboxes, priors) + + if (self.ignore_iof_thr > 0 and gt_bboxes_ignore is not None + and gt_bboxes_ignore.numel() > 0 and priors.numel() > 0): + ignore_overlaps = self.iou_calculator( + priors, gt_bboxes_ignore, mode='iof') + ignore_max_overlaps, _ = ignore_overlaps.max(dim=1) + ignore_idxs = ignore_max_overlaps > self.ignore_iof_thr + distances[ignore_idxs, :] = INF + assigned_gt_inds[ignore_idxs] = -1 + + # Selecting candidates based on the center distance + candidate_idxs = [] + start_idx = 0 + for level, priors_per_level in enumerate(num_level_priors): + # on each pyramid level, for each gt, + # select k bbox whose center are closest to the gt center + end_idx = start_idx + priors_per_level + distances_per_level = distances[start_idx:end_idx, :] + selectable_k = min(self.topk, priors_per_level) + _, topk_idxs_per_level = distances_per_level.topk( + selectable_k, dim=0, largest=False) + candidate_idxs.append(topk_idxs_per_level + start_idx) + start_idx = end_idx + candidate_idxs = torch.cat(candidate_idxs, dim=0) + + # get corresponding iou for the these candidates, and compute the + # mean and std, set mean + std as the iou threshold + candidate_overlaps = overlaps[candidate_idxs, torch.arange(num_gt)] + overlaps_mean_per_gt = candidate_overlaps.mean(0) + overlaps_std_per_gt = candidate_overlaps.std(0) + overlaps_thr_per_gt = overlaps_mean_per_gt + overlaps_std_per_gt + + is_pos = candidate_overlaps >= overlaps_thr_per_gt[None, :] + + # limit the positive sample's center in gt + for gt_idx in range(num_gt): + candidate_idxs[:, gt_idx] += gt_idx * num_priors + priors_cx = (priors[:, 0] + priors[:, 2]) / 2.0 + priors_cy = (priors[:, 1] + priors[:, 3]) / 2.0 + ep_priors_cx = priors_cx.view(1, -1).expand( + num_gt, num_priors).contiguous().view(-1) + ep_priors_cy = priors_cy.view(1, -1).expand( + num_gt, num_priors).contiguous().view(-1) + candidate_idxs = candidate_idxs.view(-1) + + # calculate the left, top, right, bottom distance between positive + # prior center and gt side + l_ = ep_priors_cx[candidate_idxs].view(-1, num_gt) - gt_bboxes[:, 0] + t_ = ep_priors_cy[candidate_idxs].view(-1, num_gt) - gt_bboxes[:, 1] + r_ = gt_bboxes[:, 2] - ep_priors_cx[candidate_idxs].view(-1, num_gt) + b_ = gt_bboxes[:, 3] - ep_priors_cy[candidate_idxs].view(-1, num_gt) + is_in_gts = torch.stack([l_, t_, r_, b_], dim=1).min(dim=1)[0] > 0.01 + + is_pos = is_pos & is_in_gts + + # if an anchor box is assigned to multiple gts, + # the one with the highest IoU will be selected. + overlaps_inf = torch.full_like(overlaps, + -INF).t().contiguous().view(-1) + index = candidate_idxs.view(-1)[is_pos.view(-1)] + overlaps_inf[index] = overlaps.t().contiguous().view(-1)[index] + overlaps_inf = overlaps_inf.view(num_gt, -1).t() + + max_overlaps, argmax_overlaps = overlaps_inf.max(dim=1) + assigned_gt_inds[ + max_overlaps != -INF] = argmax_overlaps[max_overlaps != -INF] + 1 + + assigned_labels = assigned_gt_inds.new_full((num_priors, ), -1) + pos_inds = torch.nonzero( + assigned_gt_inds > 0, as_tuple=False).squeeze() + if pos_inds.numel() > 0: + assigned_labels[pos_inds] = gt_labels[assigned_gt_inds[pos_inds] - + 1] + return AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) diff --git a/mmdetection/mmdet/models/task_modules/assigners/base_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/base_assigner.py new file mode 100644 index 00000000..b12280ad --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/base_assigner.py @@ -0,0 +1,17 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import Optional + +from mmengine.structures import InstanceData + + +class BaseAssigner(metaclass=ABCMeta): + """Base assigner that assigns boxes to ground truth boxes.""" + + @abstractmethod + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs): + """Assign boxes to either a ground truth boxes or a negative boxes.""" diff --git a/mmdetection/mmdet/models/task_modules/assigners/center_region_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/center_region_assigner.py new file mode 100644 index 00000000..11c8055c --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/center_region_assigner.py @@ -0,0 +1,366 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.utils import ConfigType +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + + +def scale_boxes(bboxes: Tensor, scale: float) -> Tensor: + """Expand an array of boxes by a given scale. + + Args: + bboxes (Tensor): Shape (m, 4) + scale (float): The scale factor of bboxes + + Returns: + Tensor: Shape (m, 4). Scaled bboxes + """ + assert bboxes.size(1) == 4 + w_half = (bboxes[:, 2] - bboxes[:, 0]) * .5 + h_half = (bboxes[:, 3] - bboxes[:, 1]) * .5 + x_c = (bboxes[:, 2] + bboxes[:, 0]) * .5 + y_c = (bboxes[:, 3] + bboxes[:, 1]) * .5 + + w_half *= scale + h_half *= scale + + boxes_scaled = torch.zeros_like(bboxes) + boxes_scaled[:, 0] = x_c - w_half + boxes_scaled[:, 2] = x_c + w_half + boxes_scaled[:, 1] = y_c - h_half + boxes_scaled[:, 3] = y_c + h_half + return boxes_scaled + + +def is_located_in(points: Tensor, bboxes: Tensor) -> Tensor: + """Are points located in bboxes. + + Args: + points (Tensor): Points, shape: (m, 2). + bboxes (Tensor): Bounding boxes, shape: (n, 4). + + Return: + Tensor: Flags indicating if points are located in bboxes, + shape: (m, n). + """ + assert points.size(1) == 2 + assert bboxes.size(1) == 4 + return (points[:, 0].unsqueeze(1) > bboxes[:, 0].unsqueeze(0)) & \ + (points[:, 0].unsqueeze(1) < bboxes[:, 2].unsqueeze(0)) & \ + (points[:, 1].unsqueeze(1) > bboxes[:, 1].unsqueeze(0)) & \ + (points[:, 1].unsqueeze(1) < bboxes[:, 3].unsqueeze(0)) + + +def bboxes_area(bboxes: Tensor) -> Tensor: + """Compute the area of an array of bboxes. + + Args: + bboxes (Tensor): The coordinates ox bboxes. Shape: (m, 4) + + Returns: + Tensor: Area of the bboxes. Shape: (m, ) + """ + assert bboxes.size(1) == 4 + w = (bboxes[:, 2] - bboxes[:, 0]) + h = (bboxes[:, 3] - bboxes[:, 1]) + areas = w * h + return areas + + +@TASK_UTILS.register_module() +class CenterRegionAssigner(BaseAssigner): + """Assign pixels at the center region of a bbox as positive. + + Each proposals will be assigned with `-1`, `0`, or a positive integer + indicating the ground truth index. + - -1: negative samples + - semi-positive numbers: positive sample, index (0-based) of assigned gt + + Args: + pos_scale (float): Threshold within which pixels are + labelled as positive. + neg_scale (float): Threshold above which pixels are + labelled as positive. + min_pos_iof (float): Minimum iof of a pixel with a gt to be + labelled as positive. Default: 1e-2 + ignore_gt_scale (float): Threshold within which the pixels + are ignored when the gt is labelled as shadowed. Default: 0.5 + foreground_dominate (bool): If True, the bbox will be assigned as + positive when a gt's kernel region overlaps with another's shadowed + (ignored) region, otherwise it is set as ignored. Default to False. + iou_calculator (:obj:`ConfigDict` or dict): Config of overlaps + Calculator. + """ + + def __init__( + self, + pos_scale: float, + neg_scale: float, + min_pos_iof: float = 1e-2, + ignore_gt_scale: float = 0.5, + foreground_dominate: bool = False, + iou_calculator: ConfigType = dict(type='BboxOverlaps2D') + ) -> None: + self.pos_scale = pos_scale + self.neg_scale = neg_scale + self.min_pos_iof = min_pos_iof + self.ignore_gt_scale = ignore_gt_scale + self.foreground_dominate = foreground_dominate + self.iou_calculator = TASK_UTILS.build(iou_calculator) + + def get_gt_priorities(self, gt_bboxes: Tensor) -> Tensor: + """Get gt priorities according to their areas. + + Smaller gt has higher priority. + + Args: + gt_bboxes (Tensor): Ground truth boxes, shape (k, 4). + + Returns: + Tensor: The priority of gts so that gts with larger priority is + more likely to be assigned. Shape (k, ) + """ + gt_areas = bboxes_area(gt_bboxes) + # Rank all gt bbox areas. Smaller objects has larger priority + _, sort_idx = gt_areas.sort(descending=True) + sort_idx = sort_idx.argsort() + return sort_idx + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs) -> AssignResult: + """Assign gt to bboxes. + + This method assigns gts to every prior (proposal/anchor), each prior + will be assigned with -1, or a semi-positive number. -1 means + negative sample, semi-positive number is the index (0-based) of + assigned gt. + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + + Returns: + :obj:`AssignResult`: The assigned result. Note that shadowed_labels + of shape (N, 2) is also added as an `assign_result` attribute. + `shadowed_labels` is a tensor composed of N pairs of anchor_ind, + class_label], where N is the number of anchors that lie in the + outer region of a gt, anchor_ind is the shadowed anchor index + and class_label is the shadowed class label. + + Example: + >>> from mmengine.structures import InstanceData + >>> self = CenterRegionAssigner(0.2, 0.2) + >>> pred_instances.priors = torch.Tensor([[0, 0, 10, 10], + ... [10, 10, 20, 20]]) + >>> gt_instances = InstanceData() + >>> gt_instances.bboxes = torch.Tensor([[0, 0, 10, 10]]) + >>> gt_instances.labels = torch.Tensor([0]) + >>> assign_result = self.assign(pred_instances, gt_instances) + >>> expected_gt_inds = torch.LongTensor([1, 0]) + >>> assert torch.all(assign_result.gt_inds == expected_gt_inds) + """ + # There are in total 5 steps in the pixel assignment + # 1. Find core (the center region, say inner 0.2) + # and shadow (the relatively ourter part, say inner 0.2-0.5) + # regions of every gt. + # 2. Find all prior bboxes that lie in gt_core and gt_shadow regions + # 3. Assign prior bboxes in gt_core with a one-hot id of the gt in + # the image. + # 3.1. For overlapping objects, the prior bboxes in gt_core is + # assigned with the object with smallest area + # 4. Assign prior bboxes with class label according to its gt id. + # 4.1. Assign -1 to prior bboxes lying in shadowed gts + # 4.2. Assign positive prior boxes with the corresponding label + # 5. Find pixels lying in the shadow of an object and assign them with + # background label, but set the loss weight of its corresponding + # gt to zero. + + # TODO not extract bboxes in assign. + gt_bboxes = gt_instances.bboxes + priors = pred_instances.priors + gt_labels = gt_instances.labels + + assert priors.size(1) == 4, 'priors must have size of 4' + # 1. Find core positive and shadow region of every gt + gt_core = scale_boxes(gt_bboxes, self.pos_scale) + gt_shadow = scale_boxes(gt_bboxes, self.neg_scale) + + # 2. Find prior bboxes that lie in gt_core and gt_shadow regions + prior_centers = (priors[:, 2:4] + priors[:, 0:2]) / 2 + # The center points lie within the gt boxes + is_prior_in_gt = is_located_in(prior_centers, gt_bboxes) + # Only calculate prior and gt_core IoF. This enables small prior bboxes + # to match large gts + prior_and_gt_core_overlaps = self.iou_calculator( + priors, gt_core, mode='iof') + # The center point of effective priors should be within the gt box + is_prior_in_gt_core = is_prior_in_gt & ( + prior_and_gt_core_overlaps > self.min_pos_iof) # shape (n, k) + + is_prior_in_gt_shadow = ( + self.iou_calculator(priors, gt_shadow, mode='iof') > + self.min_pos_iof) + # Rule out center effective positive pixels + is_prior_in_gt_shadow &= (~is_prior_in_gt_core) + + num_gts, num_priors = gt_bboxes.size(0), priors.size(0) + if num_gts == 0 or num_priors == 0: + # If no gts exist, assign all pixels to negative + assigned_gt_ids = \ + is_prior_in_gt_core.new_zeros((num_priors,), + dtype=torch.long) + pixels_in_gt_shadow = assigned_gt_ids.new_empty((0, 2)) + else: + # Step 3: assign a one-hot gt id to each pixel, and smaller objects + # have high priority to assign the pixel. + sort_idx = self.get_gt_priorities(gt_bboxes) + assigned_gt_ids, pixels_in_gt_shadow = \ + self.assign_one_hot_gt_indices(is_prior_in_gt_core, + is_prior_in_gt_shadow, + gt_priority=sort_idx) + + if (gt_instances_ignore is not None + and gt_instances_ignore.bboxes.numel() > 0): + # No ground truth or boxes, return empty assignment + gt_bboxes_ignore = gt_instances_ignore.bboxes + gt_bboxes_ignore = scale_boxes( + gt_bboxes_ignore, scale=self.ignore_gt_scale) + is_prior_in_ignored_gts = is_located_in(prior_centers, + gt_bboxes_ignore) + is_prior_in_ignored_gts = is_prior_in_ignored_gts.any(dim=1) + assigned_gt_ids[is_prior_in_ignored_gts] = -1 + + # 4. Assign prior bboxes with class label according to its gt id. + # Default assigned label is the background (-1) + assigned_labels = assigned_gt_ids.new_full((num_priors, ), -1) + pos_inds = torch.nonzero(assigned_gt_ids > 0, as_tuple=False).squeeze() + if pos_inds.numel() > 0: + assigned_labels[pos_inds] = gt_labels[assigned_gt_ids[pos_inds] - + 1] + # 5. Find pixels lying in the shadow of an object + shadowed_pixel_labels = pixels_in_gt_shadow.clone() + if pixels_in_gt_shadow.numel() > 0: + pixel_idx, gt_idx =\ + pixels_in_gt_shadow[:, 0], pixels_in_gt_shadow[:, 1] + assert (assigned_gt_ids[pixel_idx] != gt_idx).all(), \ + 'Some pixels are dually assigned to ignore and gt!' + shadowed_pixel_labels[:, 1] = gt_labels[gt_idx - 1] + override = ( + assigned_labels[pixel_idx] == shadowed_pixel_labels[:, 1]) + if self.foreground_dominate: + # When a pixel is both positive and shadowed, set it as pos + shadowed_pixel_labels = shadowed_pixel_labels[~override] + else: + # When a pixel is both pos and shadowed, set it as shadowed + assigned_labels[pixel_idx[override]] = -1 + assigned_gt_ids[pixel_idx[override]] = 0 + + assign_result = AssignResult( + num_gts, assigned_gt_ids, None, labels=assigned_labels) + # Add shadowed_labels as assign_result property. Shape: (num_shadow, 2) + assign_result.set_extra_property('shadowed_labels', + shadowed_pixel_labels) + return assign_result + + def assign_one_hot_gt_indices( + self, + is_prior_in_gt_core: Tensor, + is_prior_in_gt_shadow: Tensor, + gt_priority: Optional[Tensor] = None) -> Tuple[Tensor, Tensor]: + """Assign only one gt index to each prior box. + + Gts with large gt_priority are more likely to be assigned. + + Args: + is_prior_in_gt_core (Tensor): Bool tensor indicating the prior + center is in the core area of a gt (e.g. 0-0.2). + Shape: (num_prior, num_gt). + is_prior_in_gt_shadow (Tensor): Bool tensor indicating the prior + center is in the shadowed area of a gt (e.g. 0.2-0.5). + Shape: (num_prior, num_gt). + gt_priority (Tensor): Priorities of gts. The gt with a higher + priority is more likely to be assigned to the bbox when the + bbox match with multiple gts. Shape: (num_gt, ). + + Returns: + tuple: Returns (assigned_gt_inds, shadowed_gt_inds). + + - assigned_gt_inds: The assigned gt index of each prior bbox \ + (i.e. index from 1 to num_gts). Shape: (num_prior, ). + - shadowed_gt_inds: shadowed gt indices. It is a tensor of \ + shape (num_ignore, 2) with first column being the shadowed prior \ + bbox indices and the second column the shadowed gt \ + indices (1-based). + """ + num_bboxes, num_gts = is_prior_in_gt_core.shape + + if gt_priority is None: + gt_priority = torch.arange( + num_gts, device=is_prior_in_gt_core.device) + assert gt_priority.size(0) == num_gts + # The bigger gt_priority, the more preferable to be assigned + # The assigned inds are by default 0 (background) + assigned_gt_inds = is_prior_in_gt_core.new_zeros((num_bboxes, ), + dtype=torch.long) + # Shadowed bboxes are assigned to be background. But the corresponding + # label is ignored during loss calculation, which is done through + # shadowed_gt_inds + shadowed_gt_inds = torch.nonzero(is_prior_in_gt_shadow, as_tuple=False) + if is_prior_in_gt_core.sum() == 0: # No gt match + shadowed_gt_inds[:, 1] += 1 # 1-based. For consistency issue + return assigned_gt_inds, shadowed_gt_inds + + # The priority of each prior box and gt pair. If one prior box is + # matched bo multiple gts. Only the pair with the highest priority + # is saved + pair_priority = is_prior_in_gt_core.new_full((num_bboxes, num_gts), + -1, + dtype=torch.long) + + # Each bbox could match with multiple gts. + # The following codes deal with this situation + # Matched bboxes (to any gt). Shape: (num_pos_anchor, ) + inds_of_match = torch.any(is_prior_in_gt_core, dim=1) + # The matched gt index of each positive bbox. Length >= num_pos_anchor + # , since one bbox could match multiple gts + matched_bbox_gt_inds = torch.nonzero( + is_prior_in_gt_core, as_tuple=False)[:, 1] + # Assign priority to each bbox-gt pair. + pair_priority[is_prior_in_gt_core] = gt_priority[matched_bbox_gt_inds] + _, argmax_priority = pair_priority[inds_of_match].max(dim=1) + assigned_gt_inds[inds_of_match] = argmax_priority + 1 # 1-based + # Zero-out the assigned anchor box to filter the shadowed gt indices + is_prior_in_gt_core[inds_of_match, argmax_priority] = 0 + # Concat the shadowed indices due to overlapping with that out side of + # effective scale. shape: (total_num_ignore, 2) + shadowed_gt_inds = torch.cat( + (shadowed_gt_inds, + torch.nonzero(is_prior_in_gt_core, as_tuple=False)), + dim=0) + # Change `is_prior_in_gt_core` back to keep arguments intact. + is_prior_in_gt_core[inds_of_match, argmax_priority] = 1 + # 1-based shadowed gt indices, to be consistent with `assigned_gt_inds` + if shadowed_gt_inds.numel() > 0: + shadowed_gt_inds[:, 1] += 1 + return assigned_gt_inds, shadowed_gt_inds diff --git a/mmdetection/mmdet/models/task_modules/assigners/dynamic_soft_label_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/dynamic_soft_label_assigner.py new file mode 100644 index 00000000..3fc7af39 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/dynamic_soft_label_assigner.py @@ -0,0 +1,227 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple + +import torch +import torch.nn.functional as F +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import BaseBoxes +from mmdet.utils import ConfigType +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + +INF = 100000000 +EPS = 1.0e-7 + + +def center_of_mass(masks: Tensor, eps: float = 1e-7) -> Tensor: + """Compute the masks center of mass. + + Args: + masks: Mask tensor, has shape (num_masks, H, W). + eps: a small number to avoid normalizer to be zero. + Defaults to 1e-7. + Returns: + Tensor: The masks center of mass. Has shape (num_masks, 2). + """ + n, h, w = masks.shape + grid_h = torch.arange(h, device=masks.device)[:, None] + grid_w = torch.arange(w, device=masks.device) + normalizer = masks.sum(dim=(1, 2)).float().clamp(min=eps) + center_y = (masks * grid_h).sum(dim=(1, 2)) / normalizer + center_x = (masks * grid_w).sum(dim=(1, 2)) / normalizer + center = torch.cat([center_x[:, None], center_y[:, None]], dim=1) + return center + + +@TASK_UTILS.register_module() +class DynamicSoftLabelAssigner(BaseAssigner): + """Computes matching between predictions and ground truth with dynamic soft + label assignment. + + Args: + soft_center_radius (float): Radius of the soft center prior. + Defaults to 3.0. + topk (int): Select top-k predictions to calculate dynamic k + best matches for each gt. Defaults to 13. + iou_weight (float): The scale factor of iou cost. Defaults to 3.0. + iou_calculator (ConfigType): Config of overlaps Calculator. + Defaults to dict(type='BboxOverlaps2D'). + """ + + def __init__( + self, + soft_center_radius: float = 3.0, + topk: int = 13, + iou_weight: float = 3.0, + iou_calculator: ConfigType = dict(type='BboxOverlaps2D') + ) -> None: + self.soft_center_radius = soft_center_radius + self.topk = topk + self.iou_weight = iou_weight + self.iou_calculator = TASK_UTILS.build(iou_calculator) + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs) -> AssignResult: + """Assign gt to priors. + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + Returns: + obj:`AssignResult`: The assigned result. + """ + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + num_gt = gt_bboxes.size(0) + + decoded_bboxes = pred_instances.bboxes + pred_scores = pred_instances.scores + priors = pred_instances.priors + num_bboxes = decoded_bboxes.size(0) + + # assign 0 by default + assigned_gt_inds = decoded_bboxes.new_full((num_bboxes, ), + 0, + dtype=torch.long) + if num_gt == 0 or num_bboxes == 0: + # No ground truth or boxes, return empty assignment + max_overlaps = decoded_bboxes.new_zeros((num_bboxes, )) + if num_gt == 0: + # No truth, assign everything to background + assigned_gt_inds[:] = 0 + assigned_labels = decoded_bboxes.new_full((num_bboxes, ), + -1, + dtype=torch.long) + return AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) + + prior_center = priors[:, :2] + if isinstance(gt_bboxes, BaseBoxes): + is_in_gts = gt_bboxes.find_inside_points(prior_center) + else: + # Tensor boxes will be treated as horizontal boxes by defaults + lt_ = prior_center[:, None] - gt_bboxes[:, :2] + rb_ = gt_bboxes[:, 2:] - prior_center[:, None] + + deltas = torch.cat([lt_, rb_], dim=-1) + is_in_gts = deltas.min(dim=-1).values > 0 + + valid_mask = is_in_gts.sum(dim=1) > 0 + + valid_decoded_bbox = decoded_bboxes[valid_mask] + valid_pred_scores = pred_scores[valid_mask] + num_valid = valid_decoded_bbox.size(0) + + if num_valid == 0: + # No ground truth or boxes, return empty assignment + max_overlaps = decoded_bboxes.new_zeros((num_bboxes, )) + assigned_labels = decoded_bboxes.new_full((num_bboxes, ), + -1, + dtype=torch.long) + return AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) + if hasattr(gt_instances, 'masks'): + gt_center = center_of_mass(gt_instances.masks, eps=EPS) + elif isinstance(gt_bboxes, BaseBoxes): + gt_center = gt_bboxes.centers + else: + # Tensor boxes will be treated as horizontal boxes by defaults + gt_center = (gt_bboxes[:, :2] + gt_bboxes[:, 2:]) / 2.0 + valid_prior = priors[valid_mask] + strides = valid_prior[:, 2] + distance = (valid_prior[:, None, :2] - gt_center[None, :, :] + ).pow(2).sum(-1).sqrt() / strides[:, None] + soft_center_prior = torch.pow(10, distance - self.soft_center_radius) + + pairwise_ious = self.iou_calculator(valid_decoded_bbox, gt_bboxes) + iou_cost = -torch.log(pairwise_ious + EPS) * self.iou_weight + + gt_onehot_label = ( + F.one_hot(gt_labels.to(torch.int64), + pred_scores.shape[-1]).float().unsqueeze(0).repeat( + num_valid, 1, 1)) + valid_pred_scores = valid_pred_scores.unsqueeze(1).repeat(1, num_gt, 1) + + soft_label = gt_onehot_label * pairwise_ious[..., None] + scale_factor = soft_label - valid_pred_scores.sigmoid() + soft_cls_cost = F.binary_cross_entropy_with_logits( + valid_pred_scores, soft_label, + reduction='none') * scale_factor.abs().pow(2.0) + soft_cls_cost = soft_cls_cost.sum(dim=-1) + + cost_matrix = soft_cls_cost + iou_cost + soft_center_prior + + matched_pred_ious, matched_gt_inds = self.dynamic_k_matching( + cost_matrix, pairwise_ious, num_gt, valid_mask) + + # convert to AssignResult format + assigned_gt_inds[valid_mask] = matched_gt_inds + 1 + assigned_labels = assigned_gt_inds.new_full((num_bboxes, ), -1) + assigned_labels[valid_mask] = gt_labels[matched_gt_inds].long() + max_overlaps = assigned_gt_inds.new_full((num_bboxes, ), + -INF, + dtype=torch.float32) + max_overlaps[valid_mask] = matched_pred_ious + return AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) + + def dynamic_k_matching(self, cost: Tensor, pairwise_ious: Tensor, + num_gt: int, + valid_mask: Tensor) -> Tuple[Tensor, Tensor]: + """Use IoU and matching cost to calculate the dynamic top-k positive + targets. Same as SimOTA. + + Args: + cost (Tensor): Cost matrix. + pairwise_ious (Tensor): Pairwise iou matrix. + num_gt (int): Number of gt. + valid_mask (Tensor): Mask for valid bboxes. + + Returns: + tuple: matched ious and gt indexes. + """ + matching_matrix = torch.zeros_like(cost, dtype=torch.uint8) + # select candidate topk ious for dynamic-k calculation + candidate_topk = min(self.topk, pairwise_ious.size(0)) + topk_ious, _ = torch.topk(pairwise_ious, candidate_topk, dim=0) + # calculate dynamic k for each gt + dynamic_ks = torch.clamp(topk_ious.sum(0).int(), min=1) + for gt_idx in range(num_gt): + _, pos_idx = torch.topk( + cost[:, gt_idx], k=dynamic_ks[gt_idx], largest=False) + matching_matrix[:, gt_idx][pos_idx] = 1 + + del topk_ious, dynamic_ks, pos_idx + + prior_match_gt_mask = matching_matrix.sum(1) > 1 + if prior_match_gt_mask.sum() > 0: + cost_min, cost_argmin = torch.min( + cost[prior_match_gt_mask, :], dim=1) + matching_matrix[prior_match_gt_mask, :] *= 0 + matching_matrix[prior_match_gt_mask, cost_argmin] = 1 + # get foreground mask inside box and center prior + fg_mask_inboxes = matching_matrix.sum(1) > 0 + valid_mask[valid_mask.clone()] = fg_mask_inboxes + + matched_gt_inds = matching_matrix[fg_mask_inboxes, :].argmax(1) + matched_pred_ious = (matching_matrix * + pairwise_ious).sum(1)[fg_mask_inboxes] + return matched_pred_ious, matched_gt_inds diff --git a/mmdetection/mmdet/models/task_modules/assigners/grid_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/grid_assigner.py new file mode 100644 index 00000000..d8935d2d --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/grid_assigner.py @@ -0,0 +1,177 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, Union + +import torch +from mmengine.structures import InstanceData + +from mmdet.registry import TASK_UTILS +from mmdet.utils import ConfigType +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + + +@TASK_UTILS.register_module() +class GridAssigner(BaseAssigner): + """Assign a corresponding gt bbox or background to each bbox. + + Each proposals will be assigned with `-1`, `0`, or a positive integer + indicating the ground truth index. + + - -1: don't care + - 0: negative sample, no assigned gt + - positive integer: positive sample, index (1-based) of assigned gt + + Args: + pos_iou_thr (float): IoU threshold for positive bboxes. + neg_iou_thr (float or tuple[float, float]): IoU threshold for negative + bboxes. + min_pos_iou (float): Minimum iou for a bbox to be considered as a + positive bbox. Positive samples can have smaller IoU than + pos_iou_thr due to the 4th step (assign max IoU sample to each gt). + Defaults to 0. + gt_max_assign_all (bool): Whether to assign all bboxes with the same + highest overlap with some gt to that gt. + iou_calculator (:obj:`ConfigDict` or dict): Config of overlaps + Calculator. + """ + + def __init__( + self, + pos_iou_thr: float, + neg_iou_thr: Union[float, Tuple[float, float]], + min_pos_iou: float = .0, + gt_max_assign_all: bool = True, + iou_calculator: ConfigType = dict(type='BboxOverlaps2D') + ) -> None: + self.pos_iou_thr = pos_iou_thr + self.neg_iou_thr = neg_iou_thr + self.min_pos_iou = min_pos_iou + self.gt_max_assign_all = gt_max_assign_all + self.iou_calculator = TASK_UTILS.build(iou_calculator) + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs) -> AssignResult: + """Assign gt to bboxes. The process is very much like the max iou + assigner, except that positive samples are constrained within the cell + that the gt boxes fell in. + + This method assign a gt bbox to every bbox (proposal/anchor), each bbox + will be assigned with -1, 0, or a positive number. -1 means don't care, + 0 means negative sample, positive number is the index (1-based) of + assigned gt. + The assignment is done in following steps, the order matters. + + 1. assign every bbox to -1 + 2. assign proposals whose iou with all gts <= neg_iou_thr to 0 + 3. for each bbox within a cell, if the iou with its nearest gt > + pos_iou_thr and the center of that gt falls inside the cell, + assign it to that bbox + 4. for each gt bbox, assign its nearest proposals within the cell the + gt bbox falls in to itself. + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + + Returns: + :obj:`AssignResult`: The assign result. + """ + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + + priors = pred_instances.priors + responsible_flags = pred_instances.responsible_flags + + num_gts, num_priors = gt_bboxes.size(0), priors.size(0) + + # compute iou between all gt and priors + overlaps = self.iou_calculator(gt_bboxes, priors) + + # 1. assign -1 by default + assigned_gt_inds = overlaps.new_full((num_priors, ), + -1, + dtype=torch.long) + + if num_gts == 0 or num_priors == 0: + # No ground truth or priors, return empty assignment + max_overlaps = overlaps.new_zeros((num_priors, )) + if num_gts == 0: + # No truth, assign everything to background + assigned_gt_inds[:] = 0 + assigned_labels = overlaps.new_full((num_priors, ), + -1, + dtype=torch.long) + return AssignResult( + num_gts, + assigned_gt_inds, + max_overlaps, + labels=assigned_labels) + + # 2. assign negative: below + # for each anchor, which gt best overlaps with it + # for each anchor, the max iou of all gts + # shape of max_overlaps == argmax_overlaps == num_priors + max_overlaps, argmax_overlaps = overlaps.max(dim=0) + + if isinstance(self.neg_iou_thr, float): + assigned_gt_inds[(max_overlaps >= 0) + & (max_overlaps <= self.neg_iou_thr)] = 0 + elif isinstance(self.neg_iou_thr, (tuple, list)): + assert len(self.neg_iou_thr) == 2 + assigned_gt_inds[(max_overlaps > self.neg_iou_thr[0]) + & (max_overlaps <= self.neg_iou_thr[1])] = 0 + + # 3. assign positive: falls into responsible cell and above + # positive IOU threshold, the order matters. + # the prior condition of comparison is to filter out all + # unrelated anchors, i.e. not responsible_flags + overlaps[:, ~responsible_flags.type(torch.bool)] = -1. + + # calculate max_overlaps again, but this time we only consider IOUs + # for anchors responsible for prediction + max_overlaps, argmax_overlaps = overlaps.max(dim=0) + + # for each gt, which anchor best overlaps with it + # for each gt, the max iou of all proposals + # shape of gt_max_overlaps == gt_argmax_overlaps == num_gts + gt_max_overlaps, gt_argmax_overlaps = overlaps.max(dim=1) + + pos_inds = (max_overlaps > self.pos_iou_thr) & responsible_flags.type( + torch.bool) + assigned_gt_inds[pos_inds] = argmax_overlaps[pos_inds] + 1 + + # 4. assign positive to max overlapped anchors within responsible cell + for i in range(num_gts): + if gt_max_overlaps[i] > self.min_pos_iou: + if self.gt_max_assign_all: + max_iou_inds = (overlaps[i, :] == gt_max_overlaps[i]) & \ + responsible_flags.type(torch.bool) + assigned_gt_inds[max_iou_inds] = i + 1 + elif responsible_flags[gt_argmax_overlaps[i]]: + assigned_gt_inds[gt_argmax_overlaps[i]] = i + 1 + + # assign labels of positive anchors + assigned_labels = assigned_gt_inds.new_full((num_priors, ), -1) + pos_inds = torch.nonzero( + assigned_gt_inds > 0, as_tuple=False).squeeze() + if pos_inds.numel() > 0: + assigned_labels[pos_inds] = gt_labels[assigned_gt_inds[pos_inds] - + 1] + + return AssignResult( + num_gts, assigned_gt_inds, max_overlaps, labels=assigned_labels) diff --git a/mmdetection/mmdet/models/task_modules/assigners/hungarian_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/hungarian_assigner.py new file mode 100644 index 00000000..a6745a36 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/hungarian_assigner.py @@ -0,0 +1,145 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Union + +import torch +from mmengine import ConfigDict +from mmengine.structures import InstanceData +from scipy.optimize import linear_sum_assignment +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + + +@TASK_UTILS.register_module() +class HungarianAssigner(BaseAssigner): + """Computes one-to-one matching between predictions and ground truth. + + This class computes an assignment between the targets and the predictions + based on the costs. The costs are weighted sum of some components. + For DETR the costs are weighted sum of classification cost, regression L1 + cost and regression iou cost. The targets don't include the no_object, so + generally there are more predictions than targets. After the one-to-one + matching, the un-matched are treated as backgrounds. Thus each query + prediction will be assigned with `0` or a positive integer indicating the + ground truth index: + + - 0: negative sample, no assigned gt + - positive integer: positive sample, index (1-based) of assigned gt + + Args: + match_costs (:obj:`ConfigDict` or dict or \ + List[Union[:obj:`ConfigDict`, dict]]): Match cost configs. + """ + + def __init__( + self, match_costs: Union[List[Union[dict, ConfigDict]], dict, + ConfigDict] + ) -> None: + + if isinstance(match_costs, dict): + match_costs = [match_costs] + elif isinstance(match_costs, list): + assert len(match_costs) > 0, \ + 'match_costs must not be a empty list.' + + self.match_costs = [ + TASK_UTILS.build(match_cost) for match_cost in match_costs + ] + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + **kwargs) -> AssignResult: + """Computes one-to-one matching based on the weighted costs. + + This method assign each query prediction to a ground truth or + background. The `assigned_gt_inds` with -1 means don't care, + 0 means negative sample, and positive number is the index (1-based) + of assigned gt. + The assignment is done in the following steps, the order matters. + + 1. assign every prediction to -1 + 2. compute the weighted costs + 3. do Hungarian matching on CPU based on the costs + 4. assign all to 0 (background) first, then for each matched pair + between predictions and gts, treat this prediction as foreground + and assign the corresponding gt index (plus 1) to it. + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. It may includes ``masks``, with shape + (n, h, w) or (n, l). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + ``labels``, with shape (k, ) and ``masks``, with shape + (k, h, w) or (k, l). + img_meta (dict): Image information. + + Returns: + :obj:`AssignResult`: The assigned result. + """ + assert isinstance(gt_instances.labels, Tensor) + num_gts, num_preds = len(gt_instances), len(pred_instances) + gt_labels = gt_instances.labels + device = gt_labels.device + + # 1. assign -1 by default + assigned_gt_inds = torch.full((num_preds, ), + -1, + dtype=torch.long, + device=device) + assigned_labels = torch.full((num_preds, ), + -1, + dtype=torch.long, + device=device) + + if num_gts == 0 or num_preds == 0: + # No ground truth or boxes, return empty assignment + if num_gts == 0: + # No ground truth, assign all to background + assigned_gt_inds[:] = 0 + return AssignResult( + num_gts=num_gts, + gt_inds=assigned_gt_inds, + max_overlaps=None, + labels=assigned_labels) + + # 2. compute weighted cost + cost_list = [] + for match_cost in self.match_costs: + cost = match_cost( + pred_instances=pred_instances, + gt_instances=gt_instances, + img_meta=img_meta) + cost_list.append(cost) + cost = torch.stack(cost_list).sum(dim=0) + + # 3. do Hungarian matching on CPU using linear_sum_assignment + cost = cost.detach().cpu() + if linear_sum_assignment is None: + raise ImportError('Please run "pip install scipy" ' + 'to install scipy first.') + + matched_row_inds, matched_col_inds = linear_sum_assignment(cost) + matched_row_inds = torch.from_numpy(matched_row_inds).to(device) + matched_col_inds = torch.from_numpy(matched_col_inds).to(device) + + # 4. assign backgrounds and foregrounds + # assign all indices to backgrounds first + assigned_gt_inds[:] = 0 + # assign foregrounds based on matching results + assigned_gt_inds[matched_row_inds] = matched_col_inds + 1 + assigned_labels[matched_row_inds] = gt_labels[matched_col_inds] + return AssignResult( + num_gts=num_gts, + gt_inds=assigned_gt_inds, + max_overlaps=None, + labels=assigned_labels) diff --git a/mmdetection/mmdet/models/task_modules/assigners/iou2d_calculator.py b/mmdetection/mmdet/models/task_modules/assigners/iou2d_calculator.py new file mode 100644 index 00000000..b6daa94f --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/iou2d_calculator.py @@ -0,0 +1,88 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import bbox_overlaps, get_box_tensor + + +def cast_tensor_type(x, scale=1., dtype=None): + if dtype == 'fp16': + # scale is for preventing overflows + x = (x / scale).half() + return x + + +@TASK_UTILS.register_module() +class BboxOverlaps2D: + """2D Overlaps (e.g. IoUs, GIoUs) Calculator.""" + + def __init__(self, scale=1., dtype=None): + self.scale = scale + self.dtype = dtype + + def __call__(self, bboxes1, bboxes2, mode='iou', is_aligned=False): + """Calculate IoU between 2D bboxes. + + Args: + bboxes1 (Tensor or :obj:`BaseBoxes`): bboxes have shape (m, 4) + in format, or shape (m, 5) in format. + bboxes2 (Tensor or :obj:`BaseBoxes`): bboxes have shape (m, 4) + in format, shape (m, 5) in format, or be empty. If ``is_aligned `` is ``True``, + then m and n must be equal. + mode (str): "iou" (intersection over union), "iof" (intersection + over foreground), or "giou" (generalized intersection over + union). + is_aligned (bool, optional): If True, then m and n must be equal. + Default False. + + Returns: + Tensor: shape (m, n) if ``is_aligned `` is False else shape (m,) + """ + bboxes1 = get_box_tensor(bboxes1) + bboxes2 = get_box_tensor(bboxes2) + assert bboxes1.size(-1) in [0, 4, 5] + assert bboxes2.size(-1) in [0, 4, 5] + if bboxes2.size(-1) == 5: + bboxes2 = bboxes2[..., :4] + if bboxes1.size(-1) == 5: + bboxes1 = bboxes1[..., :4] + + if self.dtype == 'fp16': + # change tensor type to save cpu and cuda memory and keep speed + bboxes1 = cast_tensor_type(bboxes1, self.scale, self.dtype) + bboxes2 = cast_tensor_type(bboxes2, self.scale, self.dtype) + overlaps = bbox_overlaps(bboxes1, bboxes2, mode, is_aligned) + if not overlaps.is_cuda and overlaps.dtype == torch.float16: + # resume cpu float32 + overlaps = overlaps.float() + return overlaps + + return bbox_overlaps(bboxes1, bboxes2, mode, is_aligned) + + def __repr__(self): + """str: a string describing the module""" + repr_str = self.__class__.__name__ + f'(' \ + f'scale={self.scale}, dtype={self.dtype})' + return repr_str + + +@TASK_UTILS.register_module() +class BboxOverlaps2D_GLIP(BboxOverlaps2D): + + def __call__(self, bboxes1, bboxes2, mode='iou', is_aligned=False): + TO_REMOVE = 1 + area1 = (bboxes1[:, 2] - bboxes1[:, 0] + TO_REMOVE) * ( + bboxes1[:, 3] - bboxes1[:, 1] + TO_REMOVE) + area2 = (bboxes2[:, 2] - bboxes2[:, 0] + TO_REMOVE) * ( + bboxes2[:, 3] - bboxes2[:, 1] + TO_REMOVE) + + lt = torch.max(bboxes1[:, None, :2], bboxes2[:, :2]) # [N,M,2] + rb = torch.min(bboxes1[:, None, 2:], bboxes2[:, 2:]) # [N,M,2] + + wh = (rb - lt + TO_REMOVE).clamp(min=0) # [N,M,2] + inter = wh[:, :, 0] * wh[:, :, 1] # [N,M] + + iou = inter / (area1[:, None] + area2 - inter) + return iou diff --git a/mmdetection/mmdet/models/task_modules/assigners/match_cost.py b/mmdetection/mmdet/models/task_modules/assigners/match_cost.py new file mode 100644 index 00000000..5fc62f01 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/match_cost.py @@ -0,0 +1,525 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import abstractmethod +from typing import Optional, Union + +import torch +import torch.nn.functional as F +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import bbox_overlaps, bbox_xyxy_to_cxcywh + + +class BaseMatchCost: + """Base match cost class. + + Args: + weight (Union[float, int]): Cost weight. Defaults to 1. + """ + + def __init__(self, weight: Union[float, int] = 1.) -> None: + self.weight = weight + + @abstractmethod + def __call__(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + img_meta (dict, optional): Image information. + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + pass + + +@TASK_UTILS.register_module() +class BBoxL1Cost(BaseMatchCost): + """BBoxL1Cost. + + Note: ``bboxes`` in ``InstanceData`` passed in is of format 'xyxy' + and its coordinates are unnormalized. + + Args: + box_format (str, optional): 'xyxy' for DETR, 'xywh' for Sparse_RCNN. + Defaults to 'xyxy'. + weight (Union[float, int]): Cost weight. Defaults to 1. + + Examples: + >>> from mmdet.models.task_modules.assigners. + ... match_costs.match_cost import BBoxL1Cost + >>> import torch + >>> self = BBoxL1Cost() + >>> bbox_pred = torch.rand(1, 4) + >>> gt_bboxes= torch.FloatTensor([[0, 0, 2, 4], [1, 2, 3, 4]]) + >>> factor = torch.tensor([10, 8, 10, 8]) + >>> self(bbox_pred, gt_bboxes, factor) + tensor([[1.6172, 1.6422]]) + """ + + def __init__(self, + box_format: str = 'xyxy', + weight: Union[float, int] = 1.) -> None: + super().__init__(weight=weight) + assert box_format in ['xyxy', 'xywh'] + self.box_format = box_format + + def __call__(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (:obj:`InstanceData`): ``bboxes`` inside is + predicted boxes with unnormalized coordinate + (x, y, x, y). + gt_instances (:obj:`InstanceData`): ``bboxes`` inside is gt + bboxes with unnormalized coordinate (x, y, x, y). + img_meta (Optional[dict]): Image information. Defaults to None. + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + pred_bboxes = pred_instances.bboxes + gt_bboxes = gt_instances.bboxes + + # convert box format + if self.box_format == 'xywh': + gt_bboxes = bbox_xyxy_to_cxcywh(gt_bboxes) + pred_bboxes = bbox_xyxy_to_cxcywh(pred_bboxes) + + # normalized + img_h, img_w = img_meta['img_shape'] + factor = gt_bboxes.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0) + gt_bboxes = gt_bboxes / factor + pred_bboxes = pred_bboxes / factor + + bbox_cost = torch.cdist(pred_bboxes, gt_bboxes, p=1) + return bbox_cost * self.weight + + +@TASK_UTILS.register_module() +class IoUCost(BaseMatchCost): + """IoUCost. + + Note: ``bboxes`` in ``InstanceData`` passed in is of format 'xyxy' + and its coordinates are unnormalized. + + Args: + iou_mode (str): iou mode such as 'iou', 'giou'. Defaults to 'giou'. + weight (Union[float, int]): Cost weight. Defaults to 1. + + Examples: + >>> from mmdet.models.task_modules.assigners. + ... match_costs.match_cost import IoUCost + >>> import torch + >>> self = IoUCost() + >>> bboxes = torch.FloatTensor([[1,1, 2, 2], [2, 2, 3, 4]]) + >>> gt_bboxes = torch.FloatTensor([[0, 0, 2, 4], [1, 2, 3, 4]]) + >>> self(bboxes, gt_bboxes) + tensor([[-0.1250, 0.1667], + [ 0.1667, -0.5000]]) + """ + + def __init__(self, iou_mode: str = 'giou', weight: Union[float, int] = 1.): + super().__init__(weight=weight) + self.iou_mode = iou_mode + + def __call__(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + **kwargs): + """Compute match cost. + + Args: + pred_instances (:obj:`InstanceData`): ``bboxes`` inside is + predicted boxes with unnormalized coordinate + (x, y, x, y). + gt_instances (:obj:`InstanceData`): ``bboxes`` inside is gt + bboxes with unnormalized coordinate (x, y, x, y). + img_meta (Optional[dict]): Image information. Defaults to None. + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + pred_bboxes = pred_instances.bboxes + gt_bboxes = gt_instances.bboxes + + # avoid fp16 overflow + if pred_bboxes.dtype == torch.float16: + fp16 = True + pred_bboxes = pred_bboxes.to(torch.float32) + else: + fp16 = False + + overlaps = bbox_overlaps( + pred_bboxes, gt_bboxes, mode=self.iou_mode, is_aligned=False) + + if fp16: + overlaps = overlaps.to(torch.float16) + + # The 1 is a constant that doesn't change the matching, so omitted. + iou_cost = -overlaps + return iou_cost * self.weight + + +@TASK_UTILS.register_module() +class ClassificationCost(BaseMatchCost): + """ClsSoftmaxCost. + + Args: + weight (Union[float, int]): Cost weight. Defaults to 1. + + Examples: + >>> from mmdet.models.task_modules.assigners. + ... match_costs.match_cost import ClassificationCost + >>> import torch + >>> self = ClassificationCost() + >>> cls_pred = torch.rand(4, 3) + >>> gt_labels = torch.tensor([0, 1, 2]) + >>> factor = torch.tensor([10, 8, 10, 8]) + >>> self(cls_pred, gt_labels) + tensor([[-0.3430, -0.3525, -0.3045], + [-0.3077, -0.2931, -0.3992], + [-0.3664, -0.3455, -0.2881], + [-0.3343, -0.2701, -0.3956]]) + """ + + def __init__(self, weight: Union[float, int] = 1) -> None: + super().__init__(weight=weight) + + def __call__(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (:obj:`InstanceData`): ``scores`` inside is + predicted classification logits, of shape + (num_queries, num_class). + gt_instances (:obj:`InstanceData`): ``labels`` inside should have + shape (num_gt, ). + img_meta (Optional[dict]): _description_. Defaults to None. + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + pred_scores = pred_instances.scores + gt_labels = gt_instances.labels + + pred_scores = pred_scores.softmax(-1) + cls_cost = -pred_scores[:, gt_labels] + + return cls_cost * self.weight + + +@TASK_UTILS.register_module() +class FocalLossCost(BaseMatchCost): + """FocalLossCost. + + Args: + alpha (Union[float, int]): focal_loss alpha. Defaults to 0.25. + gamma (Union[float, int]): focal_loss gamma. Defaults to 2. + eps (float): Defaults to 1e-12. + binary_input (bool): Whether the input is binary. Currently, + binary_input = True is for masks input, binary_input = False + is for label input. Defaults to False. + weight (Union[float, int]): Cost weight. Defaults to 1. + """ + + def __init__(self, + alpha: Union[float, int] = 0.25, + gamma: Union[float, int] = 2, + eps: float = 1e-12, + binary_input: bool = False, + weight: Union[float, int] = 1.) -> None: + super().__init__(weight=weight) + self.alpha = alpha + self.gamma = gamma + self.eps = eps + self.binary_input = binary_input + + def _focal_loss_cost(self, cls_pred: Tensor, gt_labels: Tensor) -> Tensor: + """ + Args: + cls_pred (Tensor): Predicted classification logits, shape + (num_queries, num_class). + gt_labels (Tensor): Label of `gt_bboxes`, shape (num_gt,). + + Returns: + torch.Tensor: cls_cost value with weight + """ + cls_pred = cls_pred.sigmoid() + neg_cost = -(1 - cls_pred + self.eps).log() * ( + 1 - self.alpha) * cls_pred.pow(self.gamma) + pos_cost = -(cls_pred + self.eps).log() * self.alpha * ( + 1 - cls_pred).pow(self.gamma) + + cls_cost = pos_cost[:, gt_labels] - neg_cost[:, gt_labels] + return cls_cost * self.weight + + def _mask_focal_loss_cost(self, cls_pred, gt_labels) -> Tensor: + """ + Args: + cls_pred (Tensor): Predicted classification logits. + in shape (num_queries, d1, ..., dn), dtype=torch.float32. + gt_labels (Tensor): Ground truth in shape (num_gt, d1, ..., dn), + dtype=torch.long. Labels should be binary. + + Returns: + Tensor: Focal cost matrix with weight in shape\ + (num_queries, num_gt). + """ + cls_pred = cls_pred.flatten(1) + gt_labels = gt_labels.flatten(1).float() + n = cls_pred.shape[1] + cls_pred = cls_pred.sigmoid() + neg_cost = -(1 - cls_pred + self.eps).log() * ( + 1 - self.alpha) * cls_pred.pow(self.gamma) + pos_cost = -(cls_pred + self.eps).log() * self.alpha * ( + 1 - cls_pred).pow(self.gamma) + + cls_cost = torch.einsum('nc,mc->nm', pos_cost, gt_labels) + \ + torch.einsum('nc,mc->nm', neg_cost, (1 - gt_labels)) + return cls_cost / n * self.weight + + def __call__(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (:obj:`InstanceData`): Predicted instances which + must contain ``scores`` or ``masks``. + gt_instances (:obj:`InstanceData`): Ground truth which must contain + ``labels`` or ``mask``. + img_meta (Optional[dict]): Image information. Defaults to None. + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + if self.binary_input: + pred_masks = pred_instances.masks + gt_masks = gt_instances.masks + return self._mask_focal_loss_cost(pred_masks, gt_masks) + else: + pred_scores = pred_instances.scores + gt_labels = gt_instances.labels + return self._focal_loss_cost(pred_scores, gt_labels) + + +@TASK_UTILS.register_module() +class BinaryFocalLossCost(FocalLossCost): + + def _focal_loss_cost(self, cls_pred: Tensor, gt_labels: Tensor) -> Tensor: + """ + Args: + cls_pred (Tensor): Predicted classification logits, shape + (num_queries, num_class). + gt_labels (Tensor): Label of `gt_bboxes`, shape (num_gt,). + + Returns: + torch.Tensor: cls_cost value with weight + """ + cls_pred = cls_pred.flatten(1) + gt_labels = gt_labels.flatten(1).float() + cls_pred = cls_pred.sigmoid() + neg_cost = -(1 - cls_pred + self.eps).log() * ( + 1 - self.alpha) * cls_pred.pow(self.gamma) + pos_cost = -(cls_pred + self.eps).log() * self.alpha * ( + 1 - cls_pred).pow(self.gamma) + + cls_cost = torch.einsum('nc,mc->nm', pos_cost, gt_labels) + \ + torch.einsum('nc,mc->nm', neg_cost, (1 - gt_labels)) + return cls_cost * self.weight + + def __call__(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (:obj:`InstanceData`): Predicted instances which + must contain ``scores`` or ``masks``. + gt_instances (:obj:`InstanceData`): Ground truth which must contain + ``labels`` or ``mask``. + img_meta (Optional[dict]): Image information. Defaults to None. + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + # gt_instances.text_token_mask is a repeated tensor of the same length + # of instances. Only gt_instances.text_token_mask[0] is useful + text_token_mask = torch.nonzero( + gt_instances.text_token_mask[0]).squeeze(-1) + pred_scores = pred_instances.scores[:, text_token_mask] + gt_labels = gt_instances.positive_maps[:, text_token_mask] + return self._focal_loss_cost(pred_scores, gt_labels) + + +@TASK_UTILS.register_module() +class DiceCost(BaseMatchCost): + """Cost of mask assignments based on dice losses. + + Args: + pred_act (bool): Whether to apply sigmoid to mask_pred. + Defaults to False. + eps (float): Defaults to 1e-3. + naive_dice (bool): If True, use the naive dice loss + in which the power of the number in the denominator is + the first power. If False, use the second power that + is adopted by K-Net and SOLO. Defaults to True. + weight (Union[float, int]): Cost weight. Defaults to 1. + """ + + def __init__(self, + pred_act: bool = False, + eps: float = 1e-3, + naive_dice: bool = True, + weight: Union[float, int] = 1.) -> None: + super().__init__(weight=weight) + self.pred_act = pred_act + self.eps = eps + self.naive_dice = naive_dice + + def _binary_mask_dice_loss(self, mask_preds: Tensor, + gt_masks: Tensor) -> Tensor: + """ + Args: + mask_preds (Tensor): Mask prediction in shape (num_queries, *). + gt_masks (Tensor): Ground truth in shape (num_gt, *) + store 0 or 1, 0 for negative class and 1 for + positive class. + + Returns: + Tensor: Dice cost matrix in shape (num_queries, num_gt). + """ + mask_preds = mask_preds.flatten(1) + gt_masks = gt_masks.flatten(1).float() + numerator = 2 * torch.einsum('nc,mc->nm', mask_preds, gt_masks) + if self.naive_dice: + denominator = mask_preds.sum(-1)[:, None] + \ + gt_masks.sum(-1)[None, :] + else: + denominator = mask_preds.pow(2).sum(1)[:, None] + \ + gt_masks.pow(2).sum(1)[None, :] + loss = 1 - (numerator + self.eps) / (denominator + self.eps) + return loss + + def __call__(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (:obj:`InstanceData`): Predicted instances which + must contain ``masks``. + gt_instances (:obj:`InstanceData`): Ground truth which must contain + ``mask``. + img_meta (Optional[dict]): Image information. Defaults to None. + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + pred_masks = pred_instances.masks + gt_masks = gt_instances.masks + + if self.pred_act: + pred_masks = pred_masks.sigmoid() + dice_cost = self._binary_mask_dice_loss(pred_masks, gt_masks) + return dice_cost * self.weight + + +@TASK_UTILS.register_module() +class CrossEntropyLossCost(BaseMatchCost): + """CrossEntropyLossCost. + + Args: + use_sigmoid (bool): Whether the prediction uses sigmoid + of softmax. Defaults to True. + weight (Union[float, int]): Cost weight. Defaults to 1. + """ + + def __init__(self, + use_sigmoid: bool = True, + weight: Union[float, int] = 1.) -> None: + super().__init__(weight=weight) + self.use_sigmoid = use_sigmoid + + def _binary_cross_entropy(self, cls_pred: Tensor, + gt_labels: Tensor) -> Tensor: + """ + Args: + cls_pred (Tensor): The prediction with shape (num_queries, 1, *) or + (num_queries, *). + gt_labels (Tensor): The learning label of prediction with + shape (num_gt, *). + + Returns: + Tensor: Cross entropy cost matrix in shape (num_queries, num_gt). + """ + cls_pred = cls_pred.flatten(1).float() + gt_labels = gt_labels.flatten(1).float() + n = cls_pred.shape[1] + pos = F.binary_cross_entropy_with_logits( + cls_pred, torch.ones_like(cls_pred), reduction='none') + neg = F.binary_cross_entropy_with_logits( + cls_pred, torch.zeros_like(cls_pred), reduction='none') + cls_cost = torch.einsum('nc,mc->nm', pos, gt_labels) + \ + torch.einsum('nc,mc->nm', neg, 1 - gt_labels) + cls_cost = cls_cost / n + + return cls_cost + + def __call__(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (:obj:`InstanceData`): Predicted instances which + must contain ``scores`` or ``masks``. + gt_instances (:obj:`InstanceData`): Ground truth which must contain + ``labels`` or ``masks``. + img_meta (Optional[dict]): Image information. Defaults to None. + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + pred_masks = pred_instances.masks + gt_masks = gt_instances.masks + if self.use_sigmoid: + cls_cost = self._binary_cross_entropy(pred_masks, gt_masks) + else: + raise NotImplementedError + + return cls_cost * self.weight diff --git a/mmdetection/mmdet/models/task_modules/assigners/max_iou_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/max_iou_assigner.py new file mode 100644 index 00000000..71da5442 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/max_iou_assigner.py @@ -0,0 +1,325 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import Optional, Union + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + + +def _perm_box(bboxes, + iou_calculator, + iou_thr=0.97, + perm_range=0.01, + counter=0, + max_iter=5): + """Compute the permuted bboxes. + + Args: + bboxes (Tensor): Shape (n, 4) for , "xyxy" format. + iou_calculator (obj): Overlaps Calculator. + iou_thr (float): The permuted bboxes should have IoU > iou_thr. + perm_range (float): The scale of permutation. + counter (int): Counter of permutation iteration. + max_iter (int): The max iterations of permutation. + Returns: + Tensor: The permuted bboxes. + """ + ori_bboxes = copy.deepcopy(bboxes) + is_valid = True + N = bboxes.size(0) + perm_factor = bboxes.new_empty(N, 4).uniform_(1 - perm_range, + 1 + perm_range) + bboxes *= perm_factor + new_wh = bboxes[:, 2:] - bboxes[:, :2] + if (new_wh <= 0).any(): + is_valid = False + iou = iou_calculator(ori_bboxes.unique(dim=0), bboxes) + if (iou < iou_thr).any(): + is_valid = False + if not is_valid and counter < max_iter: + return _perm_box( + ori_bboxes, + iou_calculator, + perm_range=max(perm_range - counter * 0.001, 1e-3), + counter=counter + 1) + return bboxes + + +def perm_repeat_bboxes(bboxes, iou_calculator=None, perm_repeat_cfg=None): + """Permute the repeated bboxes. + + Args: + bboxes (Tensor): Shape (n, 4) for , "xyxy" format. + iou_calculator (obj): Overlaps Calculator. + perm_repeat_cfg (Dict): Config of permutation. + Returns: + Tensor: Bboxes after permuted repeated bboxes. + """ + assert isinstance(bboxes, torch.Tensor) + if iou_calculator is None: + import torchvision + iou_calculator = torchvision.ops.box_iou + bboxes = copy.deepcopy(bboxes) + unique_bboxes = bboxes.unique(dim=0) + iou_thr = perm_repeat_cfg.get('iou_thr', 0.97) + perm_range = perm_repeat_cfg.get('perm_range', 0.01) + for box in unique_bboxes: + inds = (bboxes == box).sum(-1).float() == 4 + if inds.float().sum().item() == 1: + continue + bboxes[inds] = _perm_box( + bboxes[inds], + iou_calculator, + iou_thr=iou_thr, + perm_range=perm_range, + counter=0) + return bboxes + + +@TASK_UTILS.register_module() +class MaxIoUAssigner(BaseAssigner): + """Assign a corresponding gt bbox or background to each bbox. + + Each proposals will be assigned with `-1`, or a semi-positive integer + indicating the ground truth index. + + - -1: negative sample, no assigned gt + - semi-positive integer: positive sample, index (0-based) of assigned gt + + Args: + pos_iou_thr (float): IoU threshold for positive bboxes. + neg_iou_thr (float or tuple): IoU threshold for negative bboxes. + min_pos_iou (float): Minimum iou for a bbox to be considered as a + positive bbox. Positive samples can have smaller IoU than + pos_iou_thr due to the 4th step (assign max IoU sample to each gt). + `min_pos_iou` is set to avoid assigning bboxes that have extremely + small iou with GT as positive samples. It brings about 0.3 mAP + improvements in 1x schedule but does not affect the performance of + 3x schedule. More comparisons can be found in + `PR #7464 `_. + gt_max_assign_all (bool): Whether to assign all bboxes with the same + highest overlap with some gt to that gt. + ignore_iof_thr (float): IoF threshold for ignoring bboxes (if + `gt_bboxes_ignore` is specified). Negative values mean not + ignoring any bboxes. + ignore_wrt_candidates (bool): Whether to compute the iof between + `bboxes` and `gt_bboxes_ignore`, or the contrary. + match_low_quality (bool): Whether to allow low quality matches. This is + usually allowed for RPN and single stage detectors, but not allowed + in the second stage. Details are demonstrated in Step 4. + gpu_assign_thr (int): The upper bound of the number of GT for GPU + assign. When the number of gt is above this threshold, will assign + on CPU device. Negative values mean not assign on CPU. + iou_calculator (dict): Config of overlaps Calculator. + perm_repeat_gt_cfg (dict): Config of permute repeated gt bboxes. + """ + + def __init__(self, + pos_iou_thr: float, + neg_iou_thr: Union[float, tuple], + min_pos_iou: float = .0, + gt_max_assign_all: bool = True, + ignore_iof_thr: float = -1, + ignore_wrt_candidates: bool = True, + match_low_quality: bool = True, + gpu_assign_thr: float = -1, + iou_calculator: dict = dict(type='BboxOverlaps2D'), + perm_repeat_gt_cfg=None): + self.pos_iou_thr = pos_iou_thr + self.neg_iou_thr = neg_iou_thr + self.min_pos_iou = min_pos_iou + self.gt_max_assign_all = gt_max_assign_all + self.ignore_iof_thr = ignore_iof_thr + self.ignore_wrt_candidates = ignore_wrt_candidates + self.gpu_assign_thr = gpu_assign_thr + self.match_low_quality = match_low_quality + self.iou_calculator = TASK_UTILS.build(iou_calculator) + self.perm_repeat_gt_cfg = perm_repeat_gt_cfg + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs) -> AssignResult: + """Assign gt to bboxes. + + This method assign a gt bbox to every bbox (proposal/anchor), each bbox + will be assigned with -1, or a semi-positive number. -1 means negative + sample, semi-positive number is the index (0-based) of assigned gt. + The assignment is done in following steps, the order matters. + + 1. assign every bbox to the background + 2. assign proposals whose iou with all gts < neg_iou_thr to 0 + 3. for each bbox, if the iou with its nearest gt >= pos_iou_thr, + assign it to that bbox + 4. for each gt bbox, assign its nearest proposals (may be more than + one) to itself + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + + Returns: + :obj:`AssignResult`: The assign result. + + Example: + >>> from mmengine.structures import InstanceData + >>> self = MaxIoUAssigner(0.5, 0.5) + >>> pred_instances = InstanceData() + >>> pred_instances.priors = torch.Tensor([[0, 0, 10, 10], + ... [10, 10, 20, 20]]) + >>> gt_instances = InstanceData() + >>> gt_instances.bboxes = torch.Tensor([[0, 0, 10, 9]]) + >>> gt_instances.labels = torch.Tensor([0]) + >>> assign_result = self.assign(pred_instances, gt_instances) + >>> expected_gt_inds = torch.LongTensor([1, 0]) + >>> assert torch.all(assign_result.gt_inds == expected_gt_inds) + """ + gt_bboxes = gt_instances.bboxes + priors = pred_instances.priors + gt_labels = gt_instances.labels + if gt_instances_ignore is not None: + gt_bboxes_ignore = gt_instances_ignore.bboxes + else: + gt_bboxes_ignore = None + + assign_on_cpu = True if (self.gpu_assign_thr > 0) and ( + gt_bboxes.shape[0] > self.gpu_assign_thr) else False + # compute overlap and assign gt on CPU when number of GT is large + if assign_on_cpu: + device = priors.device + priors = priors.cpu() + gt_bboxes = gt_bboxes.cpu() + gt_labels = gt_labels.cpu() + if gt_bboxes_ignore is not None: + gt_bboxes_ignore = gt_bboxes_ignore.cpu() + + if self.perm_repeat_gt_cfg is not None and priors.numel() > 0: + gt_bboxes_unique = perm_repeat_bboxes(gt_bboxes, + self.iou_calculator, + self.perm_repeat_gt_cfg) + else: + gt_bboxes_unique = gt_bboxes + overlaps = self.iou_calculator(gt_bboxes_unique, priors) + + if (self.ignore_iof_thr > 0 and gt_bboxes_ignore is not None + and gt_bboxes_ignore.numel() > 0 and priors.numel() > 0): + if self.ignore_wrt_candidates: + ignore_overlaps = self.iou_calculator( + priors, gt_bboxes_ignore, mode='iof') + ignore_max_overlaps, _ = ignore_overlaps.max(dim=1) + else: + ignore_overlaps = self.iou_calculator( + gt_bboxes_ignore, priors, mode='iof') + ignore_max_overlaps, _ = ignore_overlaps.max(dim=0) + overlaps[:, ignore_max_overlaps > self.ignore_iof_thr] = -1 + + assign_result = self.assign_wrt_overlaps(overlaps, gt_labels) + if assign_on_cpu: + assign_result.gt_inds = assign_result.gt_inds.to(device) + assign_result.max_overlaps = assign_result.max_overlaps.to(device) + if assign_result.labels is not None: + assign_result.labels = assign_result.labels.to(device) + return assign_result + + def assign_wrt_overlaps(self, overlaps: Tensor, + gt_labels: Tensor) -> AssignResult: + """Assign w.r.t. the overlaps of priors with gts. + + Args: + overlaps (Tensor): Overlaps between k gt_bboxes and n bboxes, + shape(k, n). + gt_labels (Tensor): Labels of k gt_bboxes, shape (k, ). + + Returns: + :obj:`AssignResult`: The assign result. + """ + num_gts, num_bboxes = overlaps.size(0), overlaps.size(1) + + # 1. assign -1 by default + assigned_gt_inds = overlaps.new_full((num_bboxes, ), + -1, + dtype=torch.long) + + if num_gts == 0 or num_bboxes == 0: + # No ground truth or boxes, return empty assignment + max_overlaps = overlaps.new_zeros((num_bboxes, )) + assigned_labels = overlaps.new_full((num_bboxes, ), + -1, + dtype=torch.long) + if num_gts == 0: + # No truth, assign everything to background + assigned_gt_inds[:] = 0 + return AssignResult( + num_gts=num_gts, + gt_inds=assigned_gt_inds, + max_overlaps=max_overlaps, + labels=assigned_labels) + + # for each anchor, which gt best overlaps with it + # for each anchor, the max iou of all gts + max_overlaps, argmax_overlaps = overlaps.max(dim=0) + # for each gt, which anchor best overlaps with it + # for each gt, the max iou of all proposals + gt_max_overlaps, gt_argmax_overlaps = overlaps.max(dim=1) + + # 2. assign negative: below + # the negative inds are set to be 0 + if isinstance(self.neg_iou_thr, float): + assigned_gt_inds[(max_overlaps >= 0) + & (max_overlaps < self.neg_iou_thr)] = 0 + elif isinstance(self.neg_iou_thr, tuple): + assert len(self.neg_iou_thr) == 2 + assigned_gt_inds[(max_overlaps >= self.neg_iou_thr[0]) + & (max_overlaps < self.neg_iou_thr[1])] = 0 + + # 3. assign positive: above positive IoU threshold + pos_inds = max_overlaps >= self.pos_iou_thr + assigned_gt_inds[pos_inds] = argmax_overlaps[pos_inds] + 1 + + if self.match_low_quality: + # Low-quality matching will overwrite the assigned_gt_inds assigned + # in Step 3. Thus, the assigned gt might not be the best one for + # prediction. + # For example, if bbox A has 0.9 and 0.8 iou with GT bbox 1 & 2, + # bbox 1 will be assigned as the best target for bbox A in step 3. + # However, if GT bbox 2's gt_argmax_overlaps = A, bbox A's + # assigned_gt_inds will be overwritten to be bbox 2. + # This might be the reason that it is not used in ROI Heads. + for i in range(num_gts): + if gt_max_overlaps[i] >= self.min_pos_iou: + if self.gt_max_assign_all: + max_iou_inds = overlaps[i, :] == gt_max_overlaps[i] + assigned_gt_inds[max_iou_inds] = i + 1 + else: + assigned_gt_inds[gt_argmax_overlaps[i]] = i + 1 + + assigned_labels = assigned_gt_inds.new_full((num_bboxes, ), -1) + pos_inds = torch.nonzero( + assigned_gt_inds > 0, as_tuple=False).squeeze() + if pos_inds.numel() > 0: + assigned_labels[pos_inds] = gt_labels[assigned_gt_inds[pos_inds] - + 1] + + return AssignResult( + num_gts=num_gts, + gt_inds=assigned_gt_inds, + max_overlaps=max_overlaps, + labels=assigned_labels) diff --git a/mmdetection/mmdet/models/task_modules/assigners/multi_instance_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/multi_instance_assigner.py new file mode 100644 index 00000000..1ba32afe --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/multi_instance_assigner.py @@ -0,0 +1,140 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +from mmengine.structures import InstanceData + +from mmdet.registry import TASK_UTILS +from .assign_result import AssignResult +from .max_iou_assigner import MaxIoUAssigner + + +@TASK_UTILS.register_module() +class MultiInstanceAssigner(MaxIoUAssigner): + """Assign a corresponding gt bbox or background to each proposal bbox. If + we need to use a proposal box to generate multiple predict boxes, + `MultiInstanceAssigner` can assign multiple gt to each proposal box. + + Args: + num_instance (int): How many bboxes are predicted by each proposal box. + """ + + def __init__(self, num_instance: int = 2, **kwargs): + super().__init__(**kwargs) + self.num_instance = num_instance + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs) -> AssignResult: + """Assign gt to bboxes. + + This method assign gt bboxes to every bbox (proposal/anchor), each bbox + is assigned a set of gts, and the number of gts in this set is defined + by `self.num_instance`. + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + + Returns: + :obj:`AssignResult`: The assign result. + """ + gt_bboxes = gt_instances.bboxes + priors = pred_instances.priors + # Set the FG label to 1 and add ignored annotations + gt_labels = gt_instances.labels + 1 + if gt_instances_ignore is not None: + gt_bboxes_ignore = gt_instances_ignore.bboxes + if hasattr(gt_instances_ignore, 'labels'): + gt_labels_ignore = gt_instances_ignore.labels + else: + gt_labels_ignore = torch.ones_like(gt_bboxes_ignore)[:, 0] * -1 + else: + gt_bboxes_ignore = None + gt_labels_ignore = None + + assign_on_cpu = True if (self.gpu_assign_thr > 0) and ( + gt_bboxes.shape[0] > self.gpu_assign_thr) else False + # compute overlap and assign gt on CPU when number of GT is large + if assign_on_cpu: + device = priors.device + priors = priors.cpu() + gt_bboxes = gt_bboxes.cpu() + gt_labels = gt_labels.cpu() + if gt_bboxes_ignore is not None: + gt_bboxes_ignore = gt_bboxes_ignore.cpu() + gt_labels_ignore = gt_labels_ignore.cpu() + + if gt_bboxes_ignore is not None: + all_bboxes = torch.cat([gt_bboxes, gt_bboxes_ignore], dim=0) + all_labels = torch.cat([gt_labels, gt_labels_ignore], dim=0) + else: + all_bboxes = gt_bboxes + all_labels = gt_labels + all_priors = torch.cat([priors, all_bboxes], dim=0) + + overlaps_normal = self.iou_calculator( + all_priors, all_bboxes, mode='iou') + overlaps_ignore = self.iou_calculator( + all_priors, all_bboxes, mode='iof') + gt_ignore_mask = all_labels.eq(-1).repeat(all_priors.shape[0], 1) + overlaps_normal = overlaps_normal * ~gt_ignore_mask + overlaps_ignore = overlaps_ignore * gt_ignore_mask + + overlaps_normal, overlaps_normal_indices = overlaps_normal.sort( + descending=True, dim=1) + overlaps_ignore, overlaps_ignore_indices = overlaps_ignore.sort( + descending=True, dim=1) + + # select the roi with the higher score + max_overlaps_normal = overlaps_normal[:, :self.num_instance].flatten() + gt_assignment_normal = overlaps_normal_indices[:, :self. + num_instance].flatten() + max_overlaps_ignore = overlaps_ignore[:, :self.num_instance].flatten() + gt_assignment_ignore = overlaps_ignore_indices[:, :self. + num_instance].flatten() + + # ignore or not + ignore_assign_mask = (max_overlaps_normal < self.pos_iou_thr) * ( + max_overlaps_ignore > max_overlaps_normal) + overlaps = (max_overlaps_normal * ~ignore_assign_mask) + ( + max_overlaps_ignore * ignore_assign_mask) + gt_assignment = (gt_assignment_normal * ~ignore_assign_mask) + ( + gt_assignment_ignore * ignore_assign_mask) + + assigned_labels = all_labels[gt_assignment] + fg_mask = (overlaps >= self.pos_iou_thr) * (assigned_labels != -1) + bg_mask = (overlaps < self.neg_iou_thr) * (overlaps >= 0) + assigned_labels[fg_mask] = 1 + assigned_labels[bg_mask] = 0 + + overlaps = overlaps.reshape(-1, self.num_instance) + gt_assignment = gt_assignment.reshape(-1, self.num_instance) + assigned_labels = assigned_labels.reshape(-1, self.num_instance) + + assign_result = AssignResult( + num_gts=all_bboxes.size(0), + gt_inds=gt_assignment, + max_overlaps=overlaps, + labels=assigned_labels) + + if assign_on_cpu: + assign_result.gt_inds = assign_result.gt_inds.to(device) + assign_result.max_overlaps = assign_result.max_overlaps.to(device) + if assign_result.labels is not None: + assign_result.labels = assign_result.labels.to(device) + return assign_result diff --git a/mmdetection/mmdet/models/task_modules/assigners/point_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/point_assigner.py new file mode 100644 index 00000000..4da60a49 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/point_assigner.py @@ -0,0 +1,155 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +from mmengine.structures import InstanceData + +from mmdet.registry import TASK_UTILS +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + + +@TASK_UTILS.register_module() +class PointAssigner(BaseAssigner): + """Assign a corresponding gt bbox or background to each point. + + Each proposals will be assigned with `0`, or a positive integer + indicating the ground truth index. + + - 0: negative sample, no assigned gt + - positive integer: positive sample, index (1-based) of assigned gt + """ + + def __init__(self, scale: int = 4, pos_num: int = 3) -> None: + self.scale = scale + self.pos_num = pos_num + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs) -> AssignResult: + """Assign gt to points. + + This method assign a gt bbox to every points set, each points set + will be assigned with the background_label (-1), or a label number. + -1 is background, and semi-positive number is the index (0-based) of + assigned gt. + The assignment is done in following steps, the order matters. + + 1. assign every points to the background_label (-1) + 2. A point is assigned to some gt bbox if + (i) the point is within the k closest points to the gt bbox + (ii) the distance between this point and the gt is smaller than + other gt bboxes + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + + + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + Returns: + :obj:`AssignResult`: The assign result. + """ + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + # points to be assigned, shape(n, 3) while last + # dimension stands for (x, y, stride). + points = pred_instances.priors + + num_points = points.shape[0] + num_gts = gt_bboxes.shape[0] + + if num_gts == 0 or num_points == 0: + # If no truth assign everything to the background + assigned_gt_inds = points.new_full((num_points, ), + 0, + dtype=torch.long) + assigned_labels = points.new_full((num_points, ), + -1, + dtype=torch.long) + return AssignResult( + num_gts=num_gts, + gt_inds=assigned_gt_inds, + max_overlaps=None, + labels=assigned_labels) + + points_xy = points[:, :2] + points_stride = points[:, 2] + points_lvl = torch.log2( + points_stride).int() # [3...,4...,5...,6...,7...] + lvl_min, lvl_max = points_lvl.min(), points_lvl.max() + + # assign gt box + gt_bboxes_xy = (gt_bboxes[:, :2] + gt_bboxes[:, 2:]) / 2 + gt_bboxes_wh = (gt_bboxes[:, 2:] - gt_bboxes[:, :2]).clamp(min=1e-6) + scale = self.scale + gt_bboxes_lvl = ((torch.log2(gt_bboxes_wh[:, 0] / scale) + + torch.log2(gt_bboxes_wh[:, 1] / scale)) / 2).int() + gt_bboxes_lvl = torch.clamp(gt_bboxes_lvl, min=lvl_min, max=lvl_max) + + # stores the assigned gt index of each point + assigned_gt_inds = points.new_zeros((num_points, ), dtype=torch.long) + # stores the assigned gt dist (to this point) of each point + assigned_gt_dist = points.new_full((num_points, ), float('inf')) + points_range = torch.arange(points.shape[0]) + + for idx in range(num_gts): + gt_lvl = gt_bboxes_lvl[idx] + # get the index of points in this level + lvl_idx = gt_lvl == points_lvl + points_index = points_range[lvl_idx] + # get the points in this level + lvl_points = points_xy[lvl_idx, :] + # get the center point of gt + gt_point = gt_bboxes_xy[[idx], :] + # get width and height of gt + gt_wh = gt_bboxes_wh[[idx], :] + # compute the distance between gt center and + # all points in this level + points_gt_dist = ((lvl_points - gt_point) / gt_wh).norm(dim=1) + # find the nearest k points to gt center in this level + min_dist, min_dist_index = torch.topk( + points_gt_dist, self.pos_num, largest=False) + # the index of nearest k points to gt center in this level + min_dist_points_index = points_index[min_dist_index] + # The less_than_recorded_index stores the index + # of min_dist that is less then the assigned_gt_dist. Where + # assigned_gt_dist stores the dist from previous assigned gt + # (if exist) to each point. + less_than_recorded_index = min_dist < assigned_gt_dist[ + min_dist_points_index] + # The min_dist_points_index stores the index of points satisfy: + # (1) it is k nearest to current gt center in this level. + # (2) it is closer to current gt center than other gt center. + min_dist_points_index = min_dist_points_index[ + less_than_recorded_index] + # assign the result + assigned_gt_inds[min_dist_points_index] = idx + 1 + assigned_gt_dist[min_dist_points_index] = min_dist[ + less_than_recorded_index] + + assigned_labels = assigned_gt_inds.new_full((num_points, ), -1) + pos_inds = torch.nonzero( + assigned_gt_inds > 0, as_tuple=False).squeeze() + if pos_inds.numel() > 0: + assigned_labels[pos_inds] = gt_labels[assigned_gt_inds[pos_inds] - + 1] + + return AssignResult( + num_gts=num_gts, + gt_inds=assigned_gt_inds, + max_overlaps=None, + labels=assigned_labels) diff --git a/mmdetection/mmdet/models/task_modules/assigners/region_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/region_assigner.py new file mode 100644 index 00000000..df549143 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/region_assigner.py @@ -0,0 +1,239 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from ..prior_generators import anchor_inside_flags +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + + +def calc_region( + bbox: Tensor, + ratio: float, + stride: int, + featmap_size: Optional[Tuple[int, int]] = None) -> Tuple[Tensor]: + """Calculate region of the box defined by the ratio, the ratio is from the + center of the box to every edge.""" + # project bbox on the feature + f_bbox = bbox / stride + x1 = torch.round((1 - ratio) * f_bbox[0] + ratio * f_bbox[2]) + y1 = torch.round((1 - ratio) * f_bbox[1] + ratio * f_bbox[3]) + x2 = torch.round(ratio * f_bbox[0] + (1 - ratio) * f_bbox[2]) + y2 = torch.round(ratio * f_bbox[1] + (1 - ratio) * f_bbox[3]) + if featmap_size is not None: + x1 = x1.clamp(min=0, max=featmap_size[1]) + y1 = y1.clamp(min=0, max=featmap_size[0]) + x2 = x2.clamp(min=0, max=featmap_size[1]) + y2 = y2.clamp(min=0, max=featmap_size[0]) + return (x1, y1, x2, y2) + + +def anchor_ctr_inside_region_flags(anchors: Tensor, stride: int, + region: Tuple[Tensor]) -> Tensor: + """Get the flag indicate whether anchor centers are inside regions.""" + x1, y1, x2, y2 = region + f_anchors = anchors / stride + x = (f_anchors[:, 0] + f_anchors[:, 2]) * 0.5 + y = (f_anchors[:, 1] + f_anchors[:, 3]) * 0.5 + flags = (x >= x1) & (x <= x2) & (y >= y1) & (y <= y2) + return flags + + +@TASK_UTILS.register_module() +class RegionAssigner(BaseAssigner): + """Assign a corresponding gt bbox or background to each bbox. + + Each proposals will be assigned with `-1`, `0`, or a positive integer + indicating the ground truth index. + + - -1: don't care + - 0: negative sample, no assigned gt + - positive integer: positive sample, index (1-based) of assigned gt + + Args: + center_ratio (float): ratio of the region in the center of the bbox to + define positive sample. + ignore_ratio (float): ratio of the region to define ignore samples. + """ + + def __init__(self, + center_ratio: float = 0.2, + ignore_ratio: float = 0.5) -> None: + self.center_ratio = center_ratio + self.ignore_ratio = ignore_ratio + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: dict, + featmap_sizes: List[Tuple[int, int]], + num_level_anchors: List[int], + anchor_scale: int, + anchor_strides: List[int], + gt_instances_ignore: Optional[InstanceData] = None, + allowed_border: int = 0) -> AssignResult: + """Assign gt to anchors. + + This method assign a gt bbox to every bbox (proposal/anchor), each bbox + will be assigned with -1, 0, or a positive number. -1 means don't care, + 0 means negative sample, positive number is the index (1-based) of + assigned gt. + + The assignment is done in following steps, and the order matters. + + 1. Assign every anchor to 0 (negative) + 2. (For each gt_bboxes) Compute ignore flags based on ignore_region + then assign -1 to anchors w.r.t. ignore flags + 3. (For each gt_bboxes) Compute pos flags based on center_region then + assign gt_bboxes to anchors w.r.t. pos flags + 4. (For each gt_bboxes) Compute ignore flags based on adjacent anchor + level then assign -1 to anchors w.r.t. ignore flags + 5. Assign anchor outside of image to -1 + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + img_meta (dict): Meta info of image. + featmap_sizes (list[tuple[int, int]]): Feature map size each level. + num_level_anchors (list[int]): The number of anchors in each level. + anchor_scale (int): Scale of the anchor. + anchor_strides (list[int]): Stride of the anchor. + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + allowed_border (int, optional): The border to allow the valid + anchor. Defaults to 0. + + Returns: + :obj:`AssignResult`: The assign result. + """ + if gt_instances_ignore is not None: + raise NotImplementedError + + num_gts = len(gt_instances) + num_bboxes = len(pred_instances) + + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + flat_anchors = pred_instances.priors + flat_valid_flags = pred_instances.valid_flags + mlvl_anchors = torch.split(flat_anchors, num_level_anchors) + + if num_gts == 0 or num_bboxes == 0: + # No ground truth or boxes, return empty assignment + max_overlaps = gt_bboxes.new_zeros((num_bboxes, )) + assigned_gt_inds = gt_bboxes.new_zeros((num_bboxes, ), + dtype=torch.long) + assigned_labels = gt_bboxes.new_full((num_bboxes, ), + -1, + dtype=torch.long) + return AssignResult( + num_gts=num_gts, + gt_inds=assigned_gt_inds, + max_overlaps=max_overlaps, + labels=assigned_labels) + + num_lvls = len(mlvl_anchors) + r1 = (1 - self.center_ratio) / 2 + r2 = (1 - self.ignore_ratio) / 2 + + scale = torch.sqrt((gt_bboxes[:, 2] - gt_bboxes[:, 0]) * + (gt_bboxes[:, 3] - gt_bboxes[:, 1])) + min_anchor_size = scale.new_full( + (1, ), float(anchor_scale * anchor_strides[0])) + target_lvls = torch.floor( + torch.log2(scale) - torch.log2(min_anchor_size) + 0.5) + target_lvls = target_lvls.clamp(min=0, max=num_lvls - 1).long() + + # 1. assign 0 (negative) by default + mlvl_assigned_gt_inds = [] + mlvl_ignore_flags = [] + for lvl in range(num_lvls): + assigned_gt_inds = gt_bboxes.new_full((num_level_anchors[lvl], ), + 0, + dtype=torch.long) + ignore_flags = torch.zeros_like(assigned_gt_inds) + mlvl_assigned_gt_inds.append(assigned_gt_inds) + mlvl_ignore_flags.append(ignore_flags) + + for gt_id in range(num_gts): + lvl = target_lvls[gt_id].item() + featmap_size = featmap_sizes[lvl] + stride = anchor_strides[lvl] + anchors = mlvl_anchors[lvl] + gt_bbox = gt_bboxes[gt_id, :4] + + # Compute regions + ignore_region = calc_region(gt_bbox, r2, stride, featmap_size) + ctr_region = calc_region(gt_bbox, r1, stride, featmap_size) + + # 2. Assign -1 to ignore flags + ignore_flags = anchor_ctr_inside_region_flags( + anchors, stride, ignore_region) + mlvl_assigned_gt_inds[lvl][ignore_flags] = -1 + + # 3. Assign gt_bboxes to pos flags + pos_flags = anchor_ctr_inside_region_flags(anchors, stride, + ctr_region) + mlvl_assigned_gt_inds[lvl][pos_flags] = gt_id + 1 + + # 4. Assign -1 to ignore adjacent lvl + if lvl > 0: + d_lvl = lvl - 1 + d_anchors = mlvl_anchors[d_lvl] + d_featmap_size = featmap_sizes[d_lvl] + d_stride = anchor_strides[d_lvl] + d_ignore_region = calc_region(gt_bbox, r2, d_stride, + d_featmap_size) + ignore_flags = anchor_ctr_inside_region_flags( + d_anchors, d_stride, d_ignore_region) + mlvl_ignore_flags[d_lvl][ignore_flags] = 1 + if lvl < num_lvls - 1: + u_lvl = lvl + 1 + u_anchors = mlvl_anchors[u_lvl] + u_featmap_size = featmap_sizes[u_lvl] + u_stride = anchor_strides[u_lvl] + u_ignore_region = calc_region(gt_bbox, r2, u_stride, + u_featmap_size) + ignore_flags = anchor_ctr_inside_region_flags( + u_anchors, u_stride, u_ignore_region) + mlvl_ignore_flags[u_lvl][ignore_flags] = 1 + + # 4. (cont.) Assign -1 to ignore adjacent lvl + for lvl in range(num_lvls): + ignore_flags = mlvl_ignore_flags[lvl] + mlvl_assigned_gt_inds[lvl][ignore_flags == 1] = -1 + + # 5. Assign -1 to anchor outside of image + flat_assigned_gt_inds = torch.cat(mlvl_assigned_gt_inds) + assert (flat_assigned_gt_inds.shape[0] == flat_anchors.shape[0] == + flat_valid_flags.shape[0]) + inside_flags = anchor_inside_flags(flat_anchors, flat_valid_flags, + img_meta['img_shape'], + allowed_border) + outside_flags = ~inside_flags + flat_assigned_gt_inds[outside_flags] = -1 + + assigned_labels = torch.zeros_like(flat_assigned_gt_inds) + pos_flags = flat_assigned_gt_inds > 0 + assigned_labels[pos_flags] = gt_labels[flat_assigned_gt_inds[pos_flags] + - 1] + + return AssignResult( + num_gts=num_gts, + gt_inds=flat_assigned_gt_inds, + max_overlaps=None, + labels=assigned_labels) diff --git a/mmdetection/mmdet/models/task_modules/assigners/sim_ota_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/sim_ota_assigner.py new file mode 100644 index 00000000..d54a8b91 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/sim_ota_assigner.py @@ -0,0 +1,223 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple + +import torch +import torch.nn.functional as F +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.utils import ConfigType +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + +INF = 100000.0 +EPS = 1.0e-7 + + +@TASK_UTILS.register_module() +class SimOTAAssigner(BaseAssigner): + """Computes matching between predictions and ground truth. + + Args: + center_radius (float): Ground truth center size + to judge whether a prior is in center. Defaults to 2.5. + candidate_topk (int): The candidate top-k which used to + get top-k ious to calculate dynamic-k. Defaults to 10. + iou_weight (float): The scale factor for regression + iou cost. Defaults to 3.0. + cls_weight (float): The scale factor for classification + cost. Defaults to 1.0. + iou_calculator (ConfigType): Config of overlaps Calculator. + Defaults to dict(type='BboxOverlaps2D'). + """ + + def __init__(self, + center_radius: float = 2.5, + candidate_topk: int = 10, + iou_weight: float = 3.0, + cls_weight: float = 1.0, + iou_calculator: ConfigType = dict(type='BboxOverlaps2D')): + self.center_radius = center_radius + self.candidate_topk = candidate_topk + self.iou_weight = iou_weight + self.cls_weight = cls_weight + self.iou_calculator = TASK_UTILS.build(iou_calculator) + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs) -> AssignResult: + """Assign gt to priors using SimOTA. + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + Returns: + obj:`AssignResult`: The assigned result. + """ + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + num_gt = gt_bboxes.size(0) + + decoded_bboxes = pred_instances.bboxes + pred_scores = pred_instances.scores + priors = pred_instances.priors + num_bboxes = decoded_bboxes.size(0) + + # assign 0 by default + assigned_gt_inds = decoded_bboxes.new_full((num_bboxes, ), + 0, + dtype=torch.long) + if num_gt == 0 or num_bboxes == 0: + # No ground truth or boxes, return empty assignment + max_overlaps = decoded_bboxes.new_zeros((num_bboxes, )) + assigned_labels = decoded_bboxes.new_full((num_bboxes, ), + -1, + dtype=torch.long) + return AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) + + valid_mask, is_in_boxes_and_center = self.get_in_gt_and_in_center_info( + priors, gt_bboxes) + valid_decoded_bbox = decoded_bboxes[valid_mask] + valid_pred_scores = pred_scores[valid_mask] + num_valid = valid_decoded_bbox.size(0) + if num_valid == 0: + # No valid bboxes, return empty assignment + max_overlaps = decoded_bboxes.new_zeros((num_bboxes, )) + assigned_labels = decoded_bboxes.new_full((num_bboxes, ), + -1, + dtype=torch.long) + return AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) + + pairwise_ious = self.iou_calculator(valid_decoded_bbox, gt_bboxes) + iou_cost = -torch.log(pairwise_ious + EPS) + + gt_onehot_label = ( + F.one_hot(gt_labels.to(torch.int64), + pred_scores.shape[-1]).float().unsqueeze(0).repeat( + num_valid, 1, 1)) + + valid_pred_scores = valid_pred_scores.unsqueeze(1).repeat(1, num_gt, 1) + # disable AMP autocast and calculate BCE with FP32 to avoid overflow + with torch.cuda.amp.autocast(enabled=False): + cls_cost = ( + F.binary_cross_entropy( + valid_pred_scores.to(dtype=torch.float32), + gt_onehot_label, + reduction='none', + ).sum(-1).to(dtype=valid_pred_scores.dtype)) + + cost_matrix = ( + cls_cost * self.cls_weight + iou_cost * self.iou_weight + + (~is_in_boxes_and_center) * INF) + + matched_pred_ious, matched_gt_inds = \ + self.dynamic_k_matching( + cost_matrix, pairwise_ious, num_gt, valid_mask) + + # convert to AssignResult format + assigned_gt_inds[valid_mask] = matched_gt_inds + 1 + assigned_labels = assigned_gt_inds.new_full((num_bboxes, ), -1) + assigned_labels[valid_mask] = gt_labels[matched_gt_inds].long() + max_overlaps = assigned_gt_inds.new_full((num_bboxes, ), + -INF, + dtype=torch.float32) + max_overlaps[valid_mask] = matched_pred_ious + return AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) + + def get_in_gt_and_in_center_info( + self, priors: Tensor, gt_bboxes: Tensor) -> Tuple[Tensor, Tensor]: + """Get the information of which prior is in gt bboxes and gt center + priors.""" + num_gt = gt_bboxes.size(0) + + repeated_x = priors[:, 0].unsqueeze(1).repeat(1, num_gt) + repeated_y = priors[:, 1].unsqueeze(1).repeat(1, num_gt) + repeated_stride_x = priors[:, 2].unsqueeze(1).repeat(1, num_gt) + repeated_stride_y = priors[:, 3].unsqueeze(1).repeat(1, num_gt) + + # is prior centers in gt bboxes, shape: [n_prior, n_gt] + l_ = repeated_x - gt_bboxes[:, 0] + t_ = repeated_y - gt_bboxes[:, 1] + r_ = gt_bboxes[:, 2] - repeated_x + b_ = gt_bboxes[:, 3] - repeated_y + + deltas = torch.stack([l_, t_, r_, b_], dim=1) + is_in_gts = deltas.min(dim=1).values > 0 + is_in_gts_all = is_in_gts.sum(dim=1) > 0 + + # is prior centers in gt centers + gt_cxs = (gt_bboxes[:, 0] + gt_bboxes[:, 2]) / 2.0 + gt_cys = (gt_bboxes[:, 1] + gt_bboxes[:, 3]) / 2.0 + ct_box_l = gt_cxs - self.center_radius * repeated_stride_x + ct_box_t = gt_cys - self.center_radius * repeated_stride_y + ct_box_r = gt_cxs + self.center_radius * repeated_stride_x + ct_box_b = gt_cys + self.center_radius * repeated_stride_y + + cl_ = repeated_x - ct_box_l + ct_ = repeated_y - ct_box_t + cr_ = ct_box_r - repeated_x + cb_ = ct_box_b - repeated_y + + ct_deltas = torch.stack([cl_, ct_, cr_, cb_], dim=1) + is_in_cts = ct_deltas.min(dim=1).values > 0 + is_in_cts_all = is_in_cts.sum(dim=1) > 0 + + # in boxes or in centers, shape: [num_priors] + is_in_gts_or_centers = is_in_gts_all | is_in_cts_all + + # both in boxes and centers, shape: [num_fg, num_gt] + is_in_boxes_and_centers = ( + is_in_gts[is_in_gts_or_centers, :] + & is_in_cts[is_in_gts_or_centers, :]) + return is_in_gts_or_centers, is_in_boxes_and_centers + + def dynamic_k_matching(self, cost: Tensor, pairwise_ious: Tensor, + num_gt: int, + valid_mask: Tensor) -> Tuple[Tensor, Tensor]: + """Use IoU and matching cost to calculate the dynamic top-k positive + targets.""" + matching_matrix = torch.zeros_like(cost, dtype=torch.uint8) + # select candidate topk ious for dynamic-k calculation + candidate_topk = min(self.candidate_topk, pairwise_ious.size(0)) + topk_ious, _ = torch.topk(pairwise_ious, candidate_topk, dim=0) + # calculate dynamic k for each gt + dynamic_ks = torch.clamp(topk_ious.sum(0).int(), min=1) + for gt_idx in range(num_gt): + _, pos_idx = torch.topk( + cost[:, gt_idx], k=dynamic_ks[gt_idx], largest=False) + matching_matrix[:, gt_idx][pos_idx] = 1 + + del topk_ious, dynamic_ks, pos_idx + + prior_match_gt_mask = matching_matrix.sum(1) > 1 + if prior_match_gt_mask.sum() > 0: + cost_min, cost_argmin = torch.min( + cost[prior_match_gt_mask, :], dim=1) + matching_matrix[prior_match_gt_mask, :] *= 0 + matching_matrix[prior_match_gt_mask, cost_argmin] = 1 + # get foreground mask inside box and center prior + fg_mask_inboxes = matching_matrix.sum(1) > 0 + valid_mask[valid_mask.clone()] = fg_mask_inboxes + + matched_gt_inds = matching_matrix[fg_mask_inboxes, :].argmax(1) + matched_pred_ious = (matching_matrix * + pairwise_ious).sum(1)[fg_mask_inboxes] + return matched_pred_ious, matched_gt_inds diff --git a/mmdetection/mmdet/models/task_modules/assigners/task_aligned_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/task_aligned_assigner.py new file mode 100644 index 00000000..220ea848 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/task_aligned_assigner.py @@ -0,0 +1,158 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +from mmengine.structures import InstanceData + +from mmdet.registry import TASK_UTILS +from mmdet.utils import ConfigType +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + +INF = 100000000 + + +@TASK_UTILS.register_module() +class TaskAlignedAssigner(BaseAssigner): + """Task aligned assigner used in the paper: + `TOOD: Task-aligned One-stage Object Detection. + `_. + + Assign a corresponding gt bbox or background to each predicted bbox. + Each bbox will be assigned with `0` or a positive integer + indicating the ground truth index. + + - 0: negative sample, no assigned gt + - positive integer: positive sample, index (1-based) of assigned gt + + Args: + topk (int): number of bbox selected in each level + iou_calculator (:obj:`ConfigDict` or dict): Config dict for iou + calculator. Defaults to ``dict(type='BboxOverlaps2D')`` + """ + + def __init__(self, + topk: int, + iou_calculator: ConfigType = dict(type='BboxOverlaps2D')): + assert topk >= 1 + self.topk = topk + self.iou_calculator = TASK_UTILS.build(iou_calculator) + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + alpha: int = 1, + beta: int = 6) -> AssignResult: + """Assign gt to bboxes. + + The assignment is done in following steps + + 1. compute alignment metric between all bbox (bbox of all pyramid + levels) and gt + 2. select top-k bbox as candidates for each gt + 3. limit the positive sample's center in gt (because the anchor-free + detector only can predict positive distance) + + + Args: + pred_instances (:obj:`InstaceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors, points, or bboxes predicted by the model, + shape(n, 4). + gt_instances (:obj:`InstaceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + gt_instances_ignore (:obj:`InstaceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + alpha (int): Hyper-parameters related to alignment_metrics. + Defaults to 1. + beta (int): Hyper-parameters related to alignment_metrics. + Defaults to 6. + + Returns: + :obj:`TaskAlignedAssignResult`: The assign result. + """ + priors = pred_instances.priors + decode_bboxes = pred_instances.bboxes + pred_scores = pred_instances.scores + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + + priors = priors[:, :4] + num_gt, num_bboxes = gt_bboxes.size(0), priors.size(0) + # compute alignment metric between all bbox and gt + overlaps = self.iou_calculator(decode_bboxes, gt_bboxes).detach() + bbox_scores = pred_scores[:, gt_labels].detach() + # assign 0 by default + assigned_gt_inds = priors.new_full((num_bboxes, ), 0, dtype=torch.long) + assign_metrics = priors.new_zeros((num_bboxes, )) + + if num_gt == 0 or num_bboxes == 0: + # No ground truth or boxes, return empty assignment + max_overlaps = priors.new_zeros((num_bboxes, )) + if num_gt == 0: + # No gt boxes, assign everything to background + assigned_gt_inds[:] = 0 + assigned_labels = priors.new_full((num_bboxes, ), + -1, + dtype=torch.long) + assign_result = AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) + assign_result.assign_metrics = assign_metrics + return assign_result + + # select top-k bboxes as candidates for each gt + alignment_metrics = bbox_scores**alpha * overlaps**beta + topk = min(self.topk, alignment_metrics.size(0)) + _, candidate_idxs = alignment_metrics.topk(topk, dim=0, largest=True) + candidate_metrics = alignment_metrics[candidate_idxs, + torch.arange(num_gt)] + is_pos = candidate_metrics > 0 + + # limit the positive sample's center in gt + priors_cx = (priors[:, 0] + priors[:, 2]) / 2.0 + priors_cy = (priors[:, 1] + priors[:, 3]) / 2.0 + for gt_idx in range(num_gt): + candidate_idxs[:, gt_idx] += gt_idx * num_bboxes + ep_priors_cx = priors_cx.view(1, -1).expand( + num_gt, num_bboxes).contiguous().view(-1) + ep_priors_cy = priors_cy.view(1, -1).expand( + num_gt, num_bboxes).contiguous().view(-1) + candidate_idxs = candidate_idxs.view(-1) + + # calculate the left, top, right, bottom distance between positive + # bbox center and gt side + l_ = ep_priors_cx[candidate_idxs].view(-1, num_gt) - gt_bboxes[:, 0] + t_ = ep_priors_cy[candidate_idxs].view(-1, num_gt) - gt_bboxes[:, 1] + r_ = gt_bboxes[:, 2] - ep_priors_cx[candidate_idxs].view(-1, num_gt) + b_ = gt_bboxes[:, 3] - ep_priors_cy[candidate_idxs].view(-1, num_gt) + is_in_gts = torch.stack([l_, t_, r_, b_], dim=1).min(dim=1)[0] > 0.01 + is_pos = is_pos & is_in_gts + + # if an anchor box is assigned to multiple gts, + # the one with the highest iou will be selected. + overlaps_inf = torch.full_like(overlaps, + -INF).t().contiguous().view(-1) + index = candidate_idxs.view(-1)[is_pos.view(-1)] + overlaps_inf[index] = overlaps.t().contiguous().view(-1)[index] + overlaps_inf = overlaps_inf.view(num_gt, -1).t() + + max_overlaps, argmax_overlaps = overlaps_inf.max(dim=1) + assigned_gt_inds[ + max_overlaps != -INF] = argmax_overlaps[max_overlaps != -INF] + 1 + assign_metrics[max_overlaps != -INF] = alignment_metrics[ + max_overlaps != -INF, argmax_overlaps[max_overlaps != -INF]] + + assigned_labels = assigned_gt_inds.new_full((num_bboxes, ), -1) + pos_inds = torch.nonzero( + assigned_gt_inds > 0, as_tuple=False).squeeze() + if pos_inds.numel() > 0: + assigned_labels[pos_inds] = gt_labels[assigned_gt_inds[pos_inds] - + 1] + assign_result = AssignResult( + num_gt, assigned_gt_inds, max_overlaps, labels=assigned_labels) + assign_result.assign_metrics = assign_metrics + return assign_result diff --git a/mmdetection/mmdet/models/task_modules/assigners/topk_hungarian_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/topk_hungarian_assigner.py new file mode 100644 index 00000000..e48f092a --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/topk_hungarian_assigner.py @@ -0,0 +1,182 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmengine.structures import BaseDataElement +from scipy.optimize import linear_sum_assignment + +from mmdet.registry import TASK_UTILS +from .assign_result import AssignResult +from .task_aligned_assigner import TaskAlignedAssigner + + +@TASK_UTILS.register_module() +class TopkHungarianAssigner(TaskAlignedAssigner): + """Computes 1-to-k matching between ground truth and predictions. + + This class computes an assignment between the targets and the predictions + based on the costs. The costs are weighted sum of some components. + For DETR the costs are weighted sum of classification cost, regression L1 + cost and regression iou cost. The targets don't include the no_object, so + generally there are more predictions than targets. After the 1-to-k + gt-pred matching, the un-matched are treated as backgrounds. Thus each + query prediction will be assigned with `0` or a positive integer + indicating the ground truth index: + + - 0: negative sample, no assigned gt + - positive integer: positive sample, index (1-based) of assigned gt + + Args: + cls_cost (dict): Classification cost configuration. + reg_cost (dict): Regression L1 cost configuration. + iou_cost (dict): Regression iou cost configuration. + """ + + def __init__(self, + *args, + cls_cost=dict(type='FocalLossCost', weight=2.0), + reg_cost=dict(type='BBoxL1Cost', weight=5.0), + iou_cost=dict(type='IoUCost', iou_mode='giou', weight=2.0), + **kwargs): + super(TopkHungarianAssigner, self).__init__(*args, **kwargs) + + self.cls_cost = TASK_UTILS.build(cls_cost) + self.reg_cost = TASK_UTILS.build(reg_cost) + self.iou_cost = TASK_UTILS.build(iou_cost) + + def assign(self, + pred_scores, + decode_bboxes, + gt_bboxes, + gt_labels, + img_meta, + alpha=1, + beta=6, + **kwargs): + """Computes 1-to-k gt-pred matching based on the weighted costs. + + This method assign each query prediction to a ground truth or + background. The `assigned_gt_inds` with -1 means don't care, + 0 means negative sample, and positive number is the index (1-based) + of assigned gt. + The assignment is done in the following steps, the order matters. + + 1. Assign every prediction to -1. + 2. Compute the weighted costs, each cost has shape (num_pred, num_gt). + 3. Update topk to be min(topk, int(num_pred / num_gt)), then repeat + costs topk times to shape: (num_pred, num_gt * topk), so that each + gt will match topk predictions. + 3. Do Hungarian matching on CPU based on the costs. + 4. Assign all to 0 (background) first, then for each matched pair + between predictions and gts, treat this prediction as foreground + and assign the corresponding gt index (plus 1) to it. + 5. Calculate alignment metrics and overlaps of each matched pred-gt + pair. + + Args: + pred_scores (Tensor): Predicted normalized classification + scores for one image, has shape (num_dense_queries, + cls_out_channels). + decode_bboxes (Tensor): Predicted unnormalized bbox coordinates + for one image, has shape (num_dense_queries, 4) with the + last dimension arranged as (x1, y1, x2, y2). + gt_bboxes (Tensor): Unnormalized ground truth + bboxes for one image, has shape (num_gt, 4) with the + last dimension arranged as (x1, y1, x2, y2). + NOTE: num_gt is dynamic for each image. + gt_labels (Tensor): Ground truth classification + index for the image, has shape (num_gt,). + NOTE: num_gt is dynamic for each image. + img_meta (dict): Meta information for one image. + alpha (int): Hyper-parameters related to alignment_metrics. + Defaults to 1. + beta (int): Hyper-parameters related to alignment_metrics. + Defaults to 6. + + Returns: + :obj:`AssignResult`: The assigned result. + """ + pred_scores = pred_scores.detach() + decode_bboxes = decode_bboxes.detach() + temp_overlaps = self.iou_calculator(decode_bboxes, gt_bboxes).detach() + bbox_scores = pred_scores[:, gt_labels].detach() + alignment_metrics = bbox_scores**alpha * temp_overlaps**beta + + pred_instances = BaseDataElement() + gt_instances = BaseDataElement() + + pred_instances.bboxes = decode_bboxes + gt_instances.bboxes = gt_bboxes + + pred_instances.scores = pred_scores + gt_instances.labels = gt_labels + + reg_cost = self.reg_cost(pred_instances, gt_instances, img_meta) + iou_cost = self.iou_cost(pred_instances, gt_instances, img_meta) + cls_cost = self.cls_cost(pred_instances, gt_instances, img_meta) + all_cost = cls_cost + reg_cost + iou_cost + + num_gt, num_bboxes = gt_bboxes.size(0), pred_scores.size(0) + if num_gt > 0: + # assign 0 by default + assigned_gt_inds = pred_scores.new_full((num_bboxes, ), + 0, + dtype=torch.long) + select_cost = all_cost + + topk = min(self.topk, int(len(select_cost) / num_gt)) + + # Repeat the ground truth `topk` times to perform 1-to-k gt-pred + # matching. For example, if `num_pred` = 900, `num_gt` = 3, then + # there are only 3 gt-pred pairs in sum for 1-1 matching. + # However, for 1-k gt-pred matching, if `topk` = 4, then each + # gt is assigned 4 unique predictions, so there would be 12 + # gt-pred pairs in sum. + repeat_select_cost = select_cost[..., + None].repeat(1, 1, topk).view( + select_cost.size(0), -1) + # anchor index and gt index + matched_row_inds, matched_col_inds = linear_sum_assignment( + repeat_select_cost.detach().cpu().numpy()) + matched_row_inds = torch.from_numpy(matched_row_inds).to( + pred_scores.device) + matched_col_inds = torch.from_numpy(matched_col_inds).to( + pred_scores.device) + + match_gt_ids = matched_col_inds // topk + candidate_idxs = matched_row_inds + + assigned_labels = assigned_gt_inds.new_full((num_bboxes, ), -1) + + if candidate_idxs.numel() > 0: + assigned_labels[candidate_idxs] = gt_labels[match_gt_ids] + else: + assigned_labels = None + + assigned_gt_inds[candidate_idxs] = match_gt_ids + 1 + + overlaps = self.iou_calculator( + decode_bboxes[candidate_idxs], + gt_bboxes[match_gt_ids], + is_aligned=True).detach() + + temp_pos_alignment_metrics = alignment_metrics[candidate_idxs] + pos_alignment_metrics = torch.gather(temp_pos_alignment_metrics, 1, + match_gt_ids[:, + None]).view(-1) + assign_result = AssignResult( + num_gt, assigned_gt_inds, overlaps, labels=assigned_labels) + + assign_result.assign_metrics = pos_alignment_metrics + return assign_result + else: + + assigned_gt_inds = pred_scores.new_full((num_bboxes, ), + -1, + dtype=torch.long) + + assigned_labels = pred_scores.new_full((num_bboxes, ), + -1, + dtype=torch.long) + + assigned_gt_inds[:] = 0 + return AssignResult( + 0, assigned_gt_inds, None, labels=assigned_labels) diff --git a/mmdetection/mmdet/models/task_modules/assigners/uniform_assigner.py b/mmdetection/mmdet/models/task_modules/assigners/uniform_assigner.py new file mode 100644 index 00000000..9a83bfd0 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/assigners/uniform_assigner.py @@ -0,0 +1,173 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +from mmengine.structures import InstanceData + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import bbox_xyxy_to_cxcywh +from mmdet.utils import ConfigType +from .assign_result import AssignResult +from .base_assigner import BaseAssigner + + +@TASK_UTILS.register_module() +class UniformAssigner(BaseAssigner): + """Uniform Matching between the priors and gt boxes, which can achieve + balance in positive priors, and gt_bboxes_ignore was not considered for + now. + + Args: + pos_ignore_thr (float): the threshold to ignore positive priors + neg_ignore_thr (float): the threshold to ignore negative priors + match_times(int): Number of positive priors for each gt box. + Defaults to 4. + iou_calculator (:obj:`ConfigDict` or dict): Config dict for iou + calculator. Defaults to ``dict(type='BboxOverlaps2D')`` + """ + + def __init__(self, + pos_ignore_thr: float, + neg_ignore_thr: float, + match_times: int = 4, + iou_calculator: ConfigType = dict(type='BboxOverlaps2D')): + self.match_times = match_times + self.pos_ignore_thr = pos_ignore_thr + self.neg_ignore_thr = neg_ignore_thr + self.iou_calculator = TASK_UTILS.build(iou_calculator) + + def assign( + self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None + ) -> AssignResult: + """Assign gt to priors. + + The assignment is done in following steps + + 1. assign -1 by default + 2. compute the L1 cost between boxes. Note that we use priors and + predict boxes both + 3. compute the ignore indexes use gt_bboxes and predict boxes + 4. compute the ignore indexes of positive sample use priors and + predict boxes + + + Args: + pred_instances (:obj:`InstaceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be priors, points, or bboxes predicted by the model, + shape(n, 4). + gt_instances (:obj:`InstaceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + gt_instances_ignore (:obj:`InstaceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + + Returns: + :obj:`AssignResult`: The assign result. + """ + + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + priors = pred_instances.priors + bbox_pred = pred_instances.decoder_priors + + num_gts, num_bboxes = gt_bboxes.size(0), bbox_pred.size(0) + + # 1. assign -1 by default + assigned_gt_inds = bbox_pred.new_full((num_bboxes, ), + 0, + dtype=torch.long) + assigned_labels = bbox_pred.new_full((num_bboxes, ), + -1, + dtype=torch.long) + if num_gts == 0 or num_bboxes == 0: + # No ground truth or boxes, return empty assignment + if num_gts == 0: + # No ground truth, assign all to background + assigned_gt_inds[:] = 0 + assign_result = AssignResult( + num_gts, assigned_gt_inds, None, labels=assigned_labels) + assign_result.set_extra_property( + 'pos_idx', bbox_pred.new_empty(0, dtype=torch.bool)) + assign_result.set_extra_property('pos_predicted_boxes', + bbox_pred.new_empty((0, 4))) + assign_result.set_extra_property('target_boxes', + bbox_pred.new_empty((0, 4))) + return assign_result + + # 2. Compute the L1 cost between boxes + # Note that we use priors and predict boxes both + cost_bbox = torch.cdist( + bbox_xyxy_to_cxcywh(bbox_pred), + bbox_xyxy_to_cxcywh(gt_bboxes), + p=1) + cost_bbox_priors = torch.cdist( + bbox_xyxy_to_cxcywh(priors), bbox_xyxy_to_cxcywh(gt_bboxes), p=1) + + # We found that topk function has different results in cpu and + # cuda mode. In order to ensure consistency with the source code, + # we also use cpu mode. + # TODO: Check whether the performance of cpu and cuda are the same. + C = cost_bbox.cpu() + C1 = cost_bbox_priors.cpu() + + # self.match_times x n + index = torch.topk( + C, # c=b,n,x c[i]=n,x + k=self.match_times, + dim=0, + largest=False)[1] + + # self.match_times x n + index1 = torch.topk(C1, k=self.match_times, dim=0, largest=False)[1] + # (self.match_times*2) x n + indexes = torch.cat((index, index1), + dim=1).reshape(-1).to(bbox_pred.device) + + pred_overlaps = self.iou_calculator(bbox_pred, gt_bboxes) + anchor_overlaps = self.iou_calculator(priors, gt_bboxes) + pred_max_overlaps, _ = pred_overlaps.max(dim=1) + anchor_max_overlaps, _ = anchor_overlaps.max(dim=0) + + # 3. Compute the ignore indexes use gt_bboxes and predict boxes + ignore_idx = pred_max_overlaps > self.neg_ignore_thr + assigned_gt_inds[ignore_idx] = -1 + + # 4. Compute the ignore indexes of positive sample use priors + # and predict boxes + pos_gt_index = torch.arange( + 0, C1.size(1), + device=bbox_pred.device).repeat(self.match_times * 2) + pos_ious = anchor_overlaps[indexes, pos_gt_index] + pos_ignore_idx = pos_ious < self.pos_ignore_thr + + pos_gt_index_with_ignore = pos_gt_index + 1 + pos_gt_index_with_ignore[pos_ignore_idx] = -1 + assigned_gt_inds[indexes] = pos_gt_index_with_ignore + + if gt_labels is not None: + assigned_labels = assigned_gt_inds.new_full((num_bboxes, ), -1) + pos_inds = torch.nonzero( + assigned_gt_inds > 0, as_tuple=False).squeeze() + if pos_inds.numel() > 0: + assigned_labels[pos_inds] = gt_labels[ + assigned_gt_inds[pos_inds] - 1] + else: + assigned_labels = None + + assign_result = AssignResult( + num_gts, + assigned_gt_inds, + anchor_max_overlaps, + labels=assigned_labels) + assign_result.set_extra_property('pos_idx', ~pos_ignore_idx) + assign_result.set_extra_property('pos_predicted_boxes', + bbox_pred[indexes]) + assign_result.set_extra_property('target_boxes', + gt_bboxes[pos_gt_index]) + return assign_result diff --git a/mmdetection/mmdet/models/task_modules/builder.py b/mmdetection/mmdet/models/task_modules/builder.py new file mode 100644 index 00000000..6736049f --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/builder.py @@ -0,0 +1,62 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +from mmdet.registry import TASK_UTILS + +PRIOR_GENERATORS = TASK_UTILS +ANCHOR_GENERATORS = TASK_UTILS +BBOX_ASSIGNERS = TASK_UTILS +BBOX_SAMPLERS = TASK_UTILS +BBOX_CODERS = TASK_UTILS +MATCH_COSTS = TASK_UTILS +IOU_CALCULATORS = TASK_UTILS + + +def build_bbox_coder(cfg, **default_args): + """Builder of box coder.""" + warnings.warn('``build_sampler`` would be deprecated soon, please use ' + '``mmdet.registry.TASK_UTILS.build()`` ') + return TASK_UTILS.build(cfg, default_args=default_args) + + +def build_iou_calculator(cfg, default_args=None): + """Builder of IoU calculator.""" + warnings.warn( + '``build_iou_calculator`` would be deprecated soon, please use ' + '``mmdet.registry.TASK_UTILS.build()`` ') + return TASK_UTILS.build(cfg, default_args=default_args) + + +def build_match_cost(cfg, default_args=None): + """Builder of IoU calculator.""" + warnings.warn('``build_match_cost`` would be deprecated soon, please use ' + '``mmdet.registry.TASK_UTILS.build()`` ') + return TASK_UTILS.build(cfg, default_args=default_args) + + +def build_assigner(cfg, **default_args): + """Builder of box assigner.""" + warnings.warn('``build_assigner`` would be deprecated soon, please use ' + '``mmdet.registry.TASK_UTILS.build()`` ') + return TASK_UTILS.build(cfg, default_args=default_args) + + +def build_sampler(cfg, **default_args): + """Builder of box sampler.""" + warnings.warn('``build_sampler`` would be deprecated soon, please use ' + '``mmdet.registry.TASK_UTILS.build()`` ') + return TASK_UTILS.build(cfg, default_args=default_args) + + +def build_prior_generator(cfg, default_args=None): + warnings.warn( + '``build_prior_generator`` would be deprecated soon, please use ' + '``mmdet.registry.TASK_UTILS.build()`` ') + return TASK_UTILS.build(cfg, default_args=default_args) + + +def build_anchor_generator(cfg, default_args=None): + warnings.warn( + '``build_anchor_generator`` would be deprecated soon, please use ' + '``mmdet.registry.TASK_UTILS.build()`` ') + return TASK_UTILS.build(cfg, default_args=default_args) diff --git a/mmdetection/mmdet/models/task_modules/coders/__init__.py b/mmdetection/mmdet/models/task_modules/coders/__init__.py new file mode 100644 index 00000000..97c39821 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/coders/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_bbox_coder import BaseBBoxCoder +from .bucketing_bbox_coder import BucketingBBoxCoder +from .delta_xywh_bbox_coder import (DeltaXYWHBBoxCoder, + DeltaXYWHBBoxCoderForGLIP) +from .distance_point_bbox_coder import DistancePointBBoxCoder +from .legacy_delta_xywh_bbox_coder import LegacyDeltaXYWHBBoxCoder +from .pseudo_bbox_coder import PseudoBBoxCoder +from .tblr_bbox_coder import TBLRBBoxCoder +from .yolo_bbox_coder import YOLOBBoxCoder + +__all__ = [ + 'BaseBBoxCoder', 'PseudoBBoxCoder', 'DeltaXYWHBBoxCoder', + 'LegacyDeltaXYWHBBoxCoder', 'TBLRBBoxCoder', 'YOLOBBoxCoder', + 'BucketingBBoxCoder', 'DistancePointBBoxCoder', 'DeltaXYWHBBoxCoderForGLIP' +] diff --git a/mmdetection/mmdet/models/task_modules/coders/base_bbox_coder.py b/mmdetection/mmdet/models/task_modules/coders/base_bbox_coder.py new file mode 100644 index 00000000..806d2651 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/coders/base_bbox_coder.py @@ -0,0 +1,26 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod + + +class BaseBBoxCoder(metaclass=ABCMeta): + """Base bounding box coder. + + Args: + use_box_type (bool): Whether to warp decoded boxes with the + box type data structure. Defaults to False. + """ + + # The size of the last of dimension of the encoded tensor. + encode_size = 4 + + def __init__(self, use_box_type: bool = False, **kwargs): + self.use_box_type = use_box_type + + @abstractmethod + def encode(self, bboxes, gt_bboxes): + """Encode deltas between bboxes and ground truth boxes.""" + + @abstractmethod + def decode(self, bboxes, bboxes_pred): + """Decode the predicted bboxes according to prediction and base + boxes.""" diff --git a/mmdetection/mmdet/models/task_modules/coders/bucketing_bbox_coder.py b/mmdetection/mmdet/models/task_modules/coders/bucketing_bbox_coder.py new file mode 100644 index 00000000..4044e1cd --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/coders/bucketing_bbox_coder.py @@ -0,0 +1,366 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence, Tuple, Union + +import numpy as np +import torch +import torch.nn.functional as F +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import (BaseBoxes, HorizontalBoxes, bbox_rescale, + get_box_tensor) +from .base_bbox_coder import BaseBBoxCoder + + +@TASK_UTILS.register_module() +class BucketingBBoxCoder(BaseBBoxCoder): + """Bucketing BBox Coder for Side-Aware Boundary Localization (SABL). + + Boundary Localization with Bucketing and Bucketing Guided Rescoring + are implemented here. + + Please refer to https://arxiv.org/abs/1912.04260 for more details. + + Args: + num_buckets (int): Number of buckets. + scale_factor (int): Scale factor of proposals to generate buckets. + offset_topk (int): Topk buckets are used to generate + bucket fine regression targets. Defaults to 2. + offset_upperbound (float): Offset upperbound to generate + bucket fine regression targets. + To avoid too large offset displacements. Defaults to 1.0. + cls_ignore_neighbor (bool): Ignore second nearest bucket or Not. + Defaults to True. + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Defaults to True. + """ + + def __init__(self, + num_buckets: int, + scale_factor: int, + offset_topk: int = 2, + offset_upperbound: float = 1.0, + cls_ignore_neighbor: bool = True, + clip_border: bool = True, + **kwargs) -> None: + super().__init__(**kwargs) + self.num_buckets = num_buckets + self.scale_factor = scale_factor + self.offset_topk = offset_topk + self.offset_upperbound = offset_upperbound + self.cls_ignore_neighbor = cls_ignore_neighbor + self.clip_border = clip_border + + def encode(self, bboxes: Union[Tensor, BaseBoxes], + gt_bboxes: Union[Tensor, BaseBoxes]) -> Tuple[Tensor]: + """Get bucketing estimation and fine regression targets during + training. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): source boxes, + e.g., object proposals. + gt_bboxes (torch.Tensor or :obj:`BaseBoxes`): target of the + transformation, e.g., ground truth boxes. + + Returns: + encoded_bboxes(tuple[Tensor]): bucketing estimation + and fine regression targets and weights + """ + bboxes = get_box_tensor(bboxes) + gt_bboxes = get_box_tensor(gt_bboxes) + assert bboxes.size(0) == gt_bboxes.size(0) + assert bboxes.size(-1) == gt_bboxes.size(-1) == 4 + encoded_bboxes = bbox2bucket(bboxes, gt_bboxes, self.num_buckets, + self.scale_factor, self.offset_topk, + self.offset_upperbound, + self.cls_ignore_neighbor) + return encoded_bboxes + + def decode( + self, + bboxes: Union[Tensor, BaseBoxes], + pred_bboxes: Tensor, + max_shape: Optional[Tuple[int]] = None + ) -> Tuple[Union[Tensor, BaseBoxes], Tensor]: + """Apply transformation `pred_bboxes` to `boxes`. + Args: + boxes (torch.Tensor or :obj:`BaseBoxes`): Basic boxes. + pred_bboxes (torch.Tensor): Predictions for bucketing estimation + and fine regression + max_shape (tuple[int], optional): Maximum shape of boxes. + Defaults to None. + + Returns: + Union[torch.Tensor, :obj:`BaseBoxes`]: Decoded boxes. + """ + bboxes = get_box_tensor(bboxes) + assert len(pred_bboxes) == 2 + cls_preds, offset_preds = pred_bboxes + assert cls_preds.size(0) == bboxes.size(0) and offset_preds.size( + 0) == bboxes.size(0) + bboxes, loc_confidence = bucket2bbox(bboxes, cls_preds, offset_preds, + self.num_buckets, + self.scale_factor, max_shape, + self.clip_border) + if self.use_box_type: + bboxes = HorizontalBoxes(bboxes, clone=False) + return bboxes, loc_confidence + + +def generat_buckets(proposals: Tensor, + num_buckets: int, + scale_factor: float = 1.0) -> Tuple[Tensor]: + """Generate buckets w.r.t bucket number and scale factor of proposals. + + Args: + proposals (Tensor): Shape (n, 4) + num_buckets (int): Number of buckets. + scale_factor (float): Scale factor to rescale proposals. + + Returns: + tuple[Tensor]: (bucket_w, bucket_h, l_buckets, r_buckets, + t_buckets, d_buckets) + + - bucket_w: Width of buckets on x-axis. Shape (n, ). + - bucket_h: Height of buckets on y-axis. Shape (n, ). + - l_buckets: Left buckets. Shape (n, ceil(side_num/2)). + - r_buckets: Right buckets. Shape (n, ceil(side_num/2)). + - t_buckets: Top buckets. Shape (n, ceil(side_num/2)). + - d_buckets: Down buckets. Shape (n, ceil(side_num/2)). + """ + proposals = bbox_rescale(proposals, scale_factor) + + # number of buckets in each side + side_num = int(np.ceil(num_buckets / 2.0)) + pw = proposals[..., 2] - proposals[..., 0] + ph = proposals[..., 3] - proposals[..., 1] + px1 = proposals[..., 0] + py1 = proposals[..., 1] + px2 = proposals[..., 2] + py2 = proposals[..., 3] + + bucket_w = pw / num_buckets + bucket_h = ph / num_buckets + + # left buckets + l_buckets = px1[:, None] + (0.5 + torch.arange( + 0, side_num).to(proposals).float())[None, :] * bucket_w[:, None] + # right buckets + r_buckets = px2[:, None] - (0.5 + torch.arange( + 0, side_num).to(proposals).float())[None, :] * bucket_w[:, None] + # top buckets + t_buckets = py1[:, None] + (0.5 + torch.arange( + 0, side_num).to(proposals).float())[None, :] * bucket_h[:, None] + # down buckets + d_buckets = py2[:, None] - (0.5 + torch.arange( + 0, side_num).to(proposals).float())[None, :] * bucket_h[:, None] + return bucket_w, bucket_h, l_buckets, r_buckets, t_buckets, d_buckets + + +def bbox2bucket(proposals: Tensor, + gt: Tensor, + num_buckets: int, + scale_factor: float, + offset_topk: int = 2, + offset_upperbound: float = 1.0, + cls_ignore_neighbor: bool = True) -> Tuple[Tensor]: + """Generate buckets estimation and fine regression targets. + + Args: + proposals (Tensor): Shape (n, 4) + gt (Tensor): Shape (n, 4) + num_buckets (int): Number of buckets. + scale_factor (float): Scale factor to rescale proposals. + offset_topk (int): Topk buckets are used to generate + bucket fine regression targets. Defaults to 2. + offset_upperbound (float): Offset allowance to generate + bucket fine regression targets. + To avoid too large offset displacements. Defaults to 1.0. + cls_ignore_neighbor (bool): Ignore second nearest bucket or Not. + Defaults to True. + + Returns: + tuple[Tensor]: (offsets, offsets_weights, bucket_labels, cls_weights). + + - offsets: Fine regression targets. \ + Shape (n, num_buckets*2). + - offsets_weights: Fine regression weights. \ + Shape (n, num_buckets*2). + - bucket_labels: Bucketing estimation labels. \ + Shape (n, num_buckets*2). + - cls_weights: Bucketing estimation weights. \ + Shape (n, num_buckets*2). + """ + assert proposals.size() == gt.size() + + # generate buckets + proposals = proposals.float() + gt = gt.float() + (bucket_w, bucket_h, l_buckets, r_buckets, t_buckets, + d_buckets) = generat_buckets(proposals, num_buckets, scale_factor) + + gx1 = gt[..., 0] + gy1 = gt[..., 1] + gx2 = gt[..., 2] + gy2 = gt[..., 3] + + # generate offset targets and weights + # offsets from buckets to gts + l_offsets = (l_buckets - gx1[:, None]) / bucket_w[:, None] + r_offsets = (r_buckets - gx2[:, None]) / bucket_w[:, None] + t_offsets = (t_buckets - gy1[:, None]) / bucket_h[:, None] + d_offsets = (d_buckets - gy2[:, None]) / bucket_h[:, None] + + # select top-k nearest buckets + l_topk, l_label = l_offsets.abs().topk( + offset_topk, dim=1, largest=False, sorted=True) + r_topk, r_label = r_offsets.abs().topk( + offset_topk, dim=1, largest=False, sorted=True) + t_topk, t_label = t_offsets.abs().topk( + offset_topk, dim=1, largest=False, sorted=True) + d_topk, d_label = d_offsets.abs().topk( + offset_topk, dim=1, largest=False, sorted=True) + + offset_l_weights = l_offsets.new_zeros(l_offsets.size()) + offset_r_weights = r_offsets.new_zeros(r_offsets.size()) + offset_t_weights = t_offsets.new_zeros(t_offsets.size()) + offset_d_weights = d_offsets.new_zeros(d_offsets.size()) + inds = torch.arange(0, proposals.size(0)).to(proposals).long() + + # generate offset weights of top-k nearest buckets + for k in range(offset_topk): + if k >= 1: + offset_l_weights[inds, l_label[:, + k]] = (l_topk[:, k] < + offset_upperbound).float() + offset_r_weights[inds, r_label[:, + k]] = (r_topk[:, k] < + offset_upperbound).float() + offset_t_weights[inds, t_label[:, + k]] = (t_topk[:, k] < + offset_upperbound).float() + offset_d_weights[inds, d_label[:, + k]] = (d_topk[:, k] < + offset_upperbound).float() + else: + offset_l_weights[inds, l_label[:, k]] = 1.0 + offset_r_weights[inds, r_label[:, k]] = 1.0 + offset_t_weights[inds, t_label[:, k]] = 1.0 + offset_d_weights[inds, d_label[:, k]] = 1.0 + + offsets = torch.cat([l_offsets, r_offsets, t_offsets, d_offsets], dim=-1) + offsets_weights = torch.cat([ + offset_l_weights, offset_r_weights, offset_t_weights, offset_d_weights + ], + dim=-1) + + # generate bucket labels and weight + side_num = int(np.ceil(num_buckets / 2.0)) + labels = torch.stack( + [l_label[:, 0], r_label[:, 0], t_label[:, 0], d_label[:, 0]], dim=-1) + + batch_size = labels.size(0) + bucket_labels = F.one_hot(labels.view(-1), side_num).view(batch_size, + -1).float() + bucket_cls_l_weights = (l_offsets.abs() < 1).float() + bucket_cls_r_weights = (r_offsets.abs() < 1).float() + bucket_cls_t_weights = (t_offsets.abs() < 1).float() + bucket_cls_d_weights = (d_offsets.abs() < 1).float() + bucket_cls_weights = torch.cat([ + bucket_cls_l_weights, bucket_cls_r_weights, bucket_cls_t_weights, + bucket_cls_d_weights + ], + dim=-1) + # ignore second nearest buckets for cls if necessary + if cls_ignore_neighbor: + bucket_cls_weights = (~((bucket_cls_weights == 1) & + (bucket_labels == 0))).float() + else: + bucket_cls_weights[:] = 1.0 + return offsets, offsets_weights, bucket_labels, bucket_cls_weights + + +def bucket2bbox(proposals: Tensor, + cls_preds: Tensor, + offset_preds: Tensor, + num_buckets: int, + scale_factor: float = 1.0, + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None, + clip_border: bool = True) -> Tuple[Tensor]: + """Apply bucketing estimation (cls preds) and fine regression (offset + preds) to generate det bboxes. + + Args: + proposals (Tensor): Boxes to be transformed. Shape (n, 4) + cls_preds (Tensor): bucketing estimation. Shape (n, num_buckets*2). + offset_preds (Tensor): fine regression. Shape (n, num_buckets*2). + num_buckets (int): Number of buckets. + scale_factor (float): Scale factor to rescale proposals. + max_shape (tuple[int, int]): Maximum bounds for boxes. specifies (H, W) + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Defaults to True. + + Returns: + tuple[Tensor]: (bboxes, loc_confidence). + + - bboxes: predicted bboxes. Shape (n, 4) + - loc_confidence: localization confidence of predicted bboxes. + Shape (n,). + """ + + side_num = int(np.ceil(num_buckets / 2.0)) + cls_preds = cls_preds.view(-1, side_num) + offset_preds = offset_preds.view(-1, side_num) + + scores = F.softmax(cls_preds, dim=1) + score_topk, score_label = scores.topk(2, dim=1, largest=True, sorted=True) + + rescaled_proposals = bbox_rescale(proposals, scale_factor) + + pw = rescaled_proposals[..., 2] - rescaled_proposals[..., 0] + ph = rescaled_proposals[..., 3] - rescaled_proposals[..., 1] + px1 = rescaled_proposals[..., 0] + py1 = rescaled_proposals[..., 1] + px2 = rescaled_proposals[..., 2] + py2 = rescaled_proposals[..., 3] + + bucket_w = pw / num_buckets + bucket_h = ph / num_buckets + + score_inds_l = score_label[0::4, 0] + score_inds_r = score_label[1::4, 0] + score_inds_t = score_label[2::4, 0] + score_inds_d = score_label[3::4, 0] + l_buckets = px1 + (0.5 + score_inds_l.float()) * bucket_w + r_buckets = px2 - (0.5 + score_inds_r.float()) * bucket_w + t_buckets = py1 + (0.5 + score_inds_t.float()) * bucket_h + d_buckets = py2 - (0.5 + score_inds_d.float()) * bucket_h + + offsets = offset_preds.view(-1, 4, side_num) + inds = torch.arange(proposals.size(0)).to(proposals).long() + l_offsets = offsets[:, 0, :][inds, score_inds_l] + r_offsets = offsets[:, 1, :][inds, score_inds_r] + t_offsets = offsets[:, 2, :][inds, score_inds_t] + d_offsets = offsets[:, 3, :][inds, score_inds_d] + + x1 = l_buckets - l_offsets * bucket_w + x2 = r_buckets - r_offsets * bucket_w + y1 = t_buckets - t_offsets * bucket_h + y2 = d_buckets - d_offsets * bucket_h + + if clip_border and max_shape is not None: + x1 = x1.clamp(min=0, max=max_shape[1] - 1) + y1 = y1.clamp(min=0, max=max_shape[0] - 1) + x2 = x2.clamp(min=0, max=max_shape[1] - 1) + y2 = y2.clamp(min=0, max=max_shape[0] - 1) + bboxes = torch.cat([x1[:, None], y1[:, None], x2[:, None], y2[:, None]], + dim=-1) + + # bucketing guided rescoring + loc_confidence = score_topk[:, 0] + top2_neighbor_inds = (score_label[:, 0] - score_label[:, 1]).abs() == 1 + loc_confidence += score_topk[:, 1] * top2_neighbor_inds.float() + loc_confidence = loc_confidence.view(-1, 4).mean(dim=1) + + return bboxes, loc_confidence diff --git a/mmdetection/mmdet/models/task_modules/coders/delta_xywh_bbox_coder.py b/mmdetection/mmdet/models/task_modules/coders/delta_xywh_bbox_coder.py new file mode 100644 index 00000000..c2b60b5e --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/coders/delta_xywh_bbox_coder.py @@ -0,0 +1,579 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import Optional, Sequence, Union + +import numpy as np +import torch +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import BaseBoxes, HorizontalBoxes, get_box_tensor +from .base_bbox_coder import BaseBBoxCoder + + +@TASK_UTILS.register_module() +class DeltaXYWHBBoxCoder(BaseBBoxCoder): + """Delta XYWH BBox coder. + + Following the practice in `R-CNN `_, + this coder encodes bbox (x1, y1, x2, y2) into delta (dx, dy, dw, dh) and + decodes delta (dx, dy, dw, dh) back to original bbox (x1, y1, x2, y2). + + Args: + target_means (Sequence[float]): Denormalizing means of target for + delta coordinates + target_stds (Sequence[float]): Denormalizing standard deviation of + target for delta coordinates + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Defaults to True. + add_ctr_clamp (bool): Whether to add center clamp, when added, the + predicted box is clamped is its center is too far away from + the original anchor's center. Only used by YOLOF. Default False. + ctr_clamp (int): the maximum pixel shift to clamp. Only used by YOLOF. + Default 32. + """ + + def __init__(self, + target_means: Sequence[float] = (0., 0., 0., 0.), + target_stds: Sequence[float] = (1., 1., 1., 1.), + clip_border: bool = True, + add_ctr_clamp: bool = False, + ctr_clamp: int = 32, + **kwargs) -> None: + super().__init__(**kwargs) + self.means = target_means + self.stds = target_stds + self.clip_border = clip_border + self.add_ctr_clamp = add_ctr_clamp + self.ctr_clamp = ctr_clamp + + def encode(self, bboxes: Union[Tensor, BaseBoxes], + gt_bboxes: Union[Tensor, BaseBoxes]) -> Tensor: + """Get box regression transformation deltas that can be used to + transform the ``bboxes`` into the ``gt_bboxes``. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): Source boxes, + e.g., object proposals. + gt_bboxes (torch.Tensor or :obj:`BaseBoxes`): Target of the + transformation, e.g., ground-truth boxes. + + Returns: + torch.Tensor: Box transformation deltas + """ + bboxes = get_box_tensor(bboxes) + gt_bboxes = get_box_tensor(gt_bboxes) + assert bboxes.size(0) == gt_bboxes.size(0) + assert bboxes.size(-1) == gt_bboxes.size(-1) == 4 + encoded_bboxes = bbox2delta(bboxes, gt_bboxes, self.means, self.stds) + return encoded_bboxes + + def decode( + self, + bboxes: Union[Tensor, BaseBoxes], + pred_bboxes: Tensor, + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None, + wh_ratio_clip: Optional[float] = 16 / 1000 + ) -> Union[Tensor, BaseBoxes]: + """Apply transformation `pred_bboxes` to `boxes`. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): Basic boxes. Shape + (B, N, 4) or (N, 4) + pred_bboxes (Tensor): Encoded offsets with respect to each roi. + Has shape (B, N, num_classes * 4) or (B, N, 4) or + (N, num_classes * 4) or (N, 4). Note N = num_anchors * W * H + when rois is a grid of anchors.Offset encoding follows [1]_. + max_shape (Sequence[int] or torch.Tensor or Sequence[ + Sequence[int]],optional): Maximum bounds for boxes, specifies + (H, W, C) or (H, W). If bboxes shape is (B, N, 4), then + the max_shape should be a Sequence[Sequence[int]] + and the length of max_shape should also be B. + wh_ratio_clip (float, optional): The allowed ratio between + width and height. + + Returns: + Union[torch.Tensor, :obj:`BaseBoxes`]: Decoded boxes. + """ + bboxes = get_box_tensor(bboxes) + assert pred_bboxes.size(0) == bboxes.size(0) + if pred_bboxes.ndim == 3: + assert pred_bboxes.size(1) == bboxes.size(1) + + if pred_bboxes.ndim == 2 and not torch.onnx.is_in_onnx_export(): + # single image decode + decoded_bboxes = delta2bbox(bboxes, pred_bboxes, self.means, + self.stds, max_shape, wh_ratio_clip, + self.clip_border, self.add_ctr_clamp, + self.ctr_clamp) + else: + if pred_bboxes.ndim == 3 and not torch.onnx.is_in_onnx_export(): + warnings.warn( + 'DeprecationWarning: onnx_delta2bbox is deprecated ' + 'in the case of batch decoding and non-ONNX, ' + 'please use “delta2bbox” instead. In order to improve ' + 'the decoding speed, the batch function will no ' + 'longer be supported. ') + decoded_bboxes = onnx_delta2bbox(bboxes, pred_bboxes, self.means, + self.stds, max_shape, + wh_ratio_clip, self.clip_border, + self.add_ctr_clamp, + self.ctr_clamp) + + if self.use_box_type: + assert decoded_bboxes.size(-1) == 4, \ + ('Cannot warp decoded boxes with box type when decoded boxes' + 'have shape of (N, num_classes * 4)') + decoded_bboxes = HorizontalBoxes(decoded_bboxes) + return decoded_bboxes + + +@TASK_UTILS.register_module() +class DeltaXYWHBBoxCoderForGLIP(DeltaXYWHBBoxCoder): + """This is designed specifically for the GLIP algorithm. + + In order to completely match the official performance, we need to perform + special calculations in the encoding and decoding processes, such as + additional +1 and -1 calculations. However, this is not a user-friendly + design. + """ + + def encode(self, bboxes: Union[Tensor, BaseBoxes], + gt_bboxes: Union[Tensor, BaseBoxes]) -> Tensor: + """Get box regression transformation deltas that can be used to + transform the ``bboxes`` into the ``gt_bboxes``. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): Source boxes, + e.g., object proposals. + gt_bboxes (torch.Tensor or :obj:`BaseBoxes`): Target of the + transformation, e.g., ground-truth boxes. + + Returns: + torch.Tensor: Box transformation deltas + """ + bboxes = get_box_tensor(bboxes) + gt_bboxes = get_box_tensor(gt_bboxes) + assert bboxes.size(0) == gt_bboxes.size(0) + assert bboxes.size(-1) == gt_bboxes.size(-1) == 4 + encoded_bboxes = bbox2delta(bboxes, gt_bboxes, self.means, self.stds) + return encoded_bboxes + + def decode( + self, + bboxes: Union[Tensor, BaseBoxes], + pred_bboxes: Tensor, + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None, + wh_ratio_clip: Optional[float] = 16 / 1000 + ) -> Union[Tensor, BaseBoxes]: + """Apply transformation `pred_bboxes` to `boxes`. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): Basic boxes. Shape + (B, N, 4) or (N, 4) + pred_bboxes (Tensor): Encoded offsets with respect to each roi. + Has shape (B, N, num_classes * 4) or (B, N, 4) or + (N, num_classes * 4) or (N, 4). Note N = num_anchors * W * H + when rois is a grid of anchors.Offset encoding follows [1]_. + max_shape (Sequence[int] or torch.Tensor or Sequence[ + Sequence[int]],optional): Maximum bounds for boxes, specifies + (H, W, C) or (H, W). If bboxes shape is (B, N, 4), then + the max_shape should be a Sequence[Sequence[int]] + and the length of max_shape should also be B. + wh_ratio_clip (float, optional): The allowed ratio between + width and height. + + Returns: + Union[torch.Tensor, :obj:`BaseBoxes`]: Decoded boxes. + """ + bboxes = get_box_tensor(bboxes) + assert pred_bboxes.size(0) == bboxes.size(0) + if pred_bboxes.ndim == 3: + assert pred_bboxes.size(1) == bboxes.size(1) + + if pred_bboxes.ndim == 2 and not torch.onnx.is_in_onnx_export(): + # single image decode + decoded_bboxes = delta2bbox_glip(bboxes, pred_bboxes, self.means, + self.stds, max_shape, + wh_ratio_clip, self.clip_border, + self.add_ctr_clamp, + self.ctr_clamp) + else: + raise NotImplementedError() + + if self.use_box_type: + assert decoded_bboxes.size(-1) == 4, \ + ('Cannot warp decoded boxes with box type when decoded boxes' + 'have shape of (N, num_classes * 4)') + decoded_bboxes = HorizontalBoxes(decoded_bboxes) + return decoded_bboxes + + +def bbox2delta( + proposals: Tensor, + gt: Tensor, + means: Sequence[float] = (0., 0., 0., 0.), + stds: Sequence[float] = (1., 1., 1., 1.) +) -> Tensor: + """Compute deltas of proposals w.r.t. gt. + + We usually compute the deltas of x, y, w, h of proposals w.r.t ground + truth bboxes to get regression target. + This is the inverse function of :func:`delta2bbox`. + + Args: + proposals (Tensor): Boxes to be transformed, shape (N, ..., 4) + gt (Tensor): Gt bboxes to be used as base, shape (N, ..., 4) + means (Sequence[float]): Denormalizing means for delta coordinates + stds (Sequence[float]): Denormalizing standard deviation for delta + coordinates + + Returns: + Tensor: deltas with shape (N, 4), where columns represent dx, dy, + dw, dh. + """ + assert proposals.size() == gt.size() + + proposals = proposals.float() + gt = gt.float() + px = (proposals[..., 0] + proposals[..., 2]) * 0.5 + py = (proposals[..., 1] + proposals[..., 3]) * 0.5 + pw = proposals[..., 2] - proposals[..., 0] + ph = proposals[..., 3] - proposals[..., 1] + + gx = (gt[..., 0] + gt[..., 2]) * 0.5 + gy = (gt[..., 1] + gt[..., 3]) * 0.5 + gw = gt[..., 2] - gt[..., 0] + gh = gt[..., 3] - gt[..., 1] + + dx = (gx - px) / pw + dy = (gy - py) / ph + dw = torch.log(gw / pw) + dh = torch.log(gh / ph) + deltas = torch.stack([dx, dy, dw, dh], dim=-1) + + means = deltas.new_tensor(means).unsqueeze(0) + stds = deltas.new_tensor(stds).unsqueeze(0) + deltas = deltas.sub_(means).div_(stds) + + return deltas + + +def delta2bbox(rois: Tensor, + deltas: Tensor, + means: Sequence[float] = (0., 0., 0., 0.), + stds: Sequence[float] = (1., 1., 1., 1.), + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None, + wh_ratio_clip: float = 16 / 1000, + clip_border: bool = True, + add_ctr_clamp: bool = False, + ctr_clamp: int = 32) -> Tensor: + """Apply deltas to shift/scale base boxes. + + Typically the rois are anchor or proposed bounding boxes and the deltas are + network outputs used to shift/scale those boxes. + This is the inverse function of :func:`bbox2delta`. + + Args: + rois (Tensor): Boxes to be transformed. Has shape (N, 4). + deltas (Tensor): Encoded offsets relative to each roi. + Has shape (N, num_classes * 4) or (N, 4). Note + N = num_base_anchors * W * H, when rois is a grid of + anchors. Offset encoding follows [1]_. + means (Sequence[float]): Denormalizing means for delta coordinates. + Default (0., 0., 0., 0.). + stds (Sequence[float]): Denormalizing standard deviation for delta + coordinates. Default (1., 1., 1., 1.). + max_shape (tuple[int, int]): Maximum bounds for boxes, specifies + (H, W). Default None. + wh_ratio_clip (float): Maximum aspect ratio for boxes. Default + 16 / 1000. + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Default True. + add_ctr_clamp (bool): Whether to add center clamp. When set to True, + the center of the prediction bounding box will be clamped to + avoid being too far away from the center of the anchor. + Only used by YOLOF. Default False. + ctr_clamp (int): the maximum pixel shift to clamp. Only used by YOLOF. + Default 32. + + Returns: + Tensor: Boxes with shape (N, num_classes * 4) or (N, 4), where 4 + represent tl_x, tl_y, br_x, br_y. + + References: + .. [1] https://arxiv.org/abs/1311.2524 + + Example: + >>> rois = torch.Tensor([[ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 5., 5., 5., 5.]]) + >>> deltas = torch.Tensor([[ 0., 0., 0., 0.], + >>> [ 1., 1., 1., 1.], + >>> [ 0., 0., 2., -1.], + >>> [ 0.7, -1.9, -0.5, 0.3]]) + >>> delta2bbox(rois, deltas, max_shape=(32, 32, 3)) + tensor([[0.0000, 0.0000, 1.0000, 1.0000], + [0.1409, 0.1409, 2.8591, 2.8591], + [0.0000, 0.3161, 4.1945, 0.6839], + [5.0000, 5.0000, 5.0000, 5.0000]]) + """ + num_bboxes, num_classes = deltas.size(0), deltas.size(1) // 4 + if num_bboxes == 0: + return deltas + + deltas = deltas.reshape(-1, 4) + + means = deltas.new_tensor(means).view(1, -1) + stds = deltas.new_tensor(stds).view(1, -1) + denorm_deltas = deltas * stds + means + + dxy = denorm_deltas[:, :2] + dwh = denorm_deltas[:, 2:] + + # Compute width/height of each roi + rois_ = rois.repeat(1, num_classes).reshape(-1, 4) + pxy = ((rois_[:, :2] + rois_[:, 2:]) * 0.5) + pwh = (rois_[:, 2:] - rois_[:, :2]) + + dxy_wh = pwh * dxy + + max_ratio = np.abs(np.log(wh_ratio_clip)) + if add_ctr_clamp: + dxy_wh = torch.clamp(dxy_wh, max=ctr_clamp, min=-ctr_clamp) + dwh = torch.clamp(dwh, max=max_ratio) + else: + dwh = dwh.clamp(min=-max_ratio, max=max_ratio) + + gxy = pxy + dxy_wh + gwh = pwh * dwh.exp() + x1y1 = gxy - (gwh * 0.5) + x2y2 = gxy + (gwh * 0.5) + bboxes = torch.cat([x1y1, x2y2], dim=-1) + if clip_border and max_shape is not None: + bboxes[..., 0::2].clamp_(min=0, max=max_shape[1]) + bboxes[..., 1::2].clamp_(min=0, max=max_shape[0]) + bboxes = bboxes.reshape(num_bboxes, -1) + return bboxes + + +def onnx_delta2bbox(rois: Tensor, + deltas: Tensor, + means: Sequence[float] = (0., 0., 0., 0.), + stds: Sequence[float] = (1., 1., 1., 1.), + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None, + wh_ratio_clip: float = 16 / 1000, + clip_border: Optional[bool] = True, + add_ctr_clamp: bool = False, + ctr_clamp: int = 32) -> Tensor: + """Apply deltas to shift/scale base boxes. + + Typically the rois are anchor or proposed bounding boxes and the deltas are + network outputs used to shift/scale those boxes. + This is the inverse function of :func:`bbox2delta`. + + Args: + rois (Tensor): Boxes to be transformed. Has shape (N, 4) or (B, N, 4) + deltas (Tensor): Encoded offsets with respect to each roi. + Has shape (B, N, num_classes * 4) or (B, N, 4) or + (N, num_classes * 4) or (N, 4). Note N = num_anchors * W * H + when rois is a grid of anchors.Offset encoding follows [1]_. + means (Sequence[float]): Denormalizing means for delta coordinates. + Default (0., 0., 0., 0.). + stds (Sequence[float]): Denormalizing standard deviation for delta + coordinates. Default (1., 1., 1., 1.). + max_shape (Sequence[int] or torch.Tensor or Sequence[ + Sequence[int]],optional): Maximum bounds for boxes, specifies + (H, W, C) or (H, W). If rois shape is (B, N, 4), then + the max_shape should be a Sequence[Sequence[int]] + and the length of max_shape should also be B. Default None. + wh_ratio_clip (float): Maximum aspect ratio for boxes. + Default 16 / 1000. + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Default True. + add_ctr_clamp (bool): Whether to add center clamp, when added, the + predicted box is clamped is its center is too far away from + the original anchor's center. Only used by YOLOF. Default False. + ctr_clamp (int): the maximum pixel shift to clamp. Only used by YOLOF. + Default 32. + + Returns: + Tensor: Boxes with shape (B, N, num_classes * 4) or (B, N, 4) or + (N, num_classes * 4) or (N, 4), where 4 represent + tl_x, tl_y, br_x, br_y. + + References: + .. [1] https://arxiv.org/abs/1311.2524 + + Example: + >>> rois = torch.Tensor([[ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 5., 5., 5., 5.]]) + >>> deltas = torch.Tensor([[ 0., 0., 0., 0.], + >>> [ 1., 1., 1., 1.], + >>> [ 0., 0., 2., -1.], + >>> [ 0.7, -1.9, -0.5, 0.3]]) + >>> delta2bbox(rois, deltas, max_shape=(32, 32, 3)) + tensor([[0.0000, 0.0000, 1.0000, 1.0000], + [0.1409, 0.1409, 2.8591, 2.8591], + [0.0000, 0.3161, 4.1945, 0.6839], + [5.0000, 5.0000, 5.0000, 5.0000]]) + """ + means = deltas.new_tensor(means).view(1, + -1).repeat(1, + deltas.size(-1) // 4) + stds = deltas.new_tensor(stds).view(1, -1).repeat(1, deltas.size(-1) // 4) + denorm_deltas = deltas * stds + means + dx = denorm_deltas[..., 0::4] + dy = denorm_deltas[..., 1::4] + dw = denorm_deltas[..., 2::4] + dh = denorm_deltas[..., 3::4] + + x1, y1 = rois[..., 0], rois[..., 1] + x2, y2 = rois[..., 2], rois[..., 3] + # Compute center of each roi + px = ((x1 + x2) * 0.5).unsqueeze(-1).expand_as(dx) + py = ((y1 + y2) * 0.5).unsqueeze(-1).expand_as(dy) + # Compute width/height of each roi + pw = (x2 - x1).unsqueeze(-1).expand_as(dw) + ph = (y2 - y1).unsqueeze(-1).expand_as(dh) + + dx_width = pw * dx + dy_height = ph * dy + + max_ratio = np.abs(np.log(wh_ratio_clip)) + if add_ctr_clamp: + dx_width = torch.clamp(dx_width, max=ctr_clamp, min=-ctr_clamp) + dy_height = torch.clamp(dy_height, max=ctr_clamp, min=-ctr_clamp) + dw = torch.clamp(dw, max=max_ratio) + dh = torch.clamp(dh, max=max_ratio) + else: + dw = dw.clamp(min=-max_ratio, max=max_ratio) + dh = dh.clamp(min=-max_ratio, max=max_ratio) + # Use exp(network energy) to enlarge/shrink each roi + gw = pw * dw.exp() + gh = ph * dh.exp() + # Use network energy to shift the center of each roi + gx = px + dx_width + gy = py + dy_height + # Convert center-xy/width/height to top-left, bottom-right + x1 = gx - gw * 0.5 + y1 = gy - gh * 0.5 + x2 = gx + gw * 0.5 + y2 = gy + gh * 0.5 + + bboxes = torch.stack([x1, y1, x2, y2], dim=-1).view(deltas.size()) + + if clip_border and max_shape is not None: + # clip bboxes with dynamic `min` and `max` for onnx + if torch.onnx.is_in_onnx_export(): + from mmdet.core.export import dynamic_clip_for_onnx + x1, y1, x2, y2 = dynamic_clip_for_onnx(x1, y1, x2, y2, max_shape) + bboxes = torch.stack([x1, y1, x2, y2], dim=-1).view(deltas.size()) + return bboxes + if not isinstance(max_shape, torch.Tensor): + max_shape = x1.new_tensor(max_shape) + max_shape = max_shape[..., :2].type_as(x1) + if max_shape.ndim == 2: + assert bboxes.ndim == 3 + assert max_shape.size(0) == bboxes.size(0) + + min_xy = x1.new_tensor(0) + max_xy = torch.cat( + [max_shape] * (deltas.size(-1) // 2), + dim=-1).flip(-1).unsqueeze(-2) + bboxes = torch.where(bboxes < min_xy, min_xy, bboxes) + bboxes = torch.where(bboxes > max_xy, max_xy, bboxes) + + return bboxes + + +def delta2bbox_glip(rois: Tensor, + deltas: Tensor, + means: Sequence[float] = (0., 0., 0., 0.), + stds: Sequence[float] = (1., 1., 1., 1.), + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None, + wh_ratio_clip: float = 16 / 1000, + clip_border: bool = True, + add_ctr_clamp: bool = False, + ctr_clamp: int = 32) -> Tensor: + """Apply deltas to shift/scale base boxes. + + Typically the rois are anchor or proposed bounding boxes and the deltas are + network outputs used to shift/scale those boxes. + This is the inverse function of :func:`bbox2delta`. + + Args: + rois (Tensor): Boxes to be transformed. Has shape (N, 4). + deltas (Tensor): Encoded offsets relative to each roi. + Has shape (N, num_classes * 4) or (N, 4). Note + N = num_base_anchors * W * H, when rois is a grid of + anchors. Offset encoding follows [1]_. + means (Sequence[float]): Denormalizing means for delta coordinates. + Default (0., 0., 0., 0.). + stds (Sequence[float]): Denormalizing standard deviation for delta + coordinates. Default (1., 1., 1., 1.). + max_shape (tuple[int, int]): Maximum bounds for boxes, specifies + (H, W). Default None. + wh_ratio_clip (float): Maximum aspect ratio for boxes. Default + 16 / 1000. + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Default True. + add_ctr_clamp (bool): Whether to add center clamp. When set to True, + the center of the prediction bounding box will be clamped to + avoid being too far away from the center of the anchor. + Only used by YOLOF. Default False. + ctr_clamp (int): the maximum pixel shift to clamp. Only used by YOLOF. + Default 32. + + Returns: + Tensor: Boxes with shape (N, num_classes * 4) or (N, 4), where 4 + represent tl_x, tl_y, br_x, br_y. + """ + num_bboxes, num_classes = deltas.size(0), deltas.size(1) // 4 + if num_bboxes == 0: + return deltas + + deltas = deltas.reshape(-1, 4) + + means = deltas.new_tensor(means).view(1, -1) + stds = deltas.new_tensor(stds).view(1, -1) + denorm_deltas = deltas * stds + means + + dxy = denorm_deltas[:, :2] + dwh = denorm_deltas[:, 2:] + + # Compute width/height of each roi + rois_ = rois.repeat(1, num_classes).reshape(-1, 4) + pxy = ((rois_[:, :2] + rois_[:, 2:] - 1) * 0.5) # note + pwh = (rois_[:, 2:] - rois_[:, :2]) + + dxy_wh = pwh * dxy + + max_ratio = np.abs(np.log(wh_ratio_clip)) + if add_ctr_clamp: + dxy_wh = torch.clamp(dxy_wh, max=ctr_clamp, min=-ctr_clamp) + dwh = torch.clamp(dwh, max=max_ratio) + else: + dwh = dwh.clamp(min=-max_ratio, max=max_ratio) + + gxy = pxy + dxy_wh + gwh = pwh * dwh.exp() + + x1y1 = gxy - (gwh - 1) * 0.5 # Note + x2y2 = gxy + (gwh - 1) * 0.5 # Note + + bboxes = torch.cat([x1y1, x2y2], dim=-1) + + if clip_border and max_shape is not None: + bboxes[..., 0::2].clamp_(min=0, max=max_shape[1] - 1) # Note + bboxes[..., 1::2].clamp_(min=0, max=max_shape[0] - 1) # Note + bboxes = bboxes.reshape(num_bboxes, -1) + return bboxes diff --git a/mmdetection/mmdet/models/task_modules/coders/distance_point_bbox_coder.py b/mmdetection/mmdet/models/task_modules/coders/distance_point_bbox_coder.py new file mode 100644 index 00000000..ab26bf4b --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/coders/distance_point_bbox_coder.py @@ -0,0 +1,85 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence, Union + +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import (BaseBoxes, HorizontalBoxes, bbox2distance, + distance2bbox, get_box_tensor) +from .base_bbox_coder import BaseBBoxCoder + + +@TASK_UTILS.register_module() +class DistancePointBBoxCoder(BaseBBoxCoder): + """Distance Point BBox coder. + + This coder encodes gt bboxes (x1, y1, x2, y2) into (top, bottom, left, + right) and decode it back to the original. + + Args: + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Defaults to True. + """ + + def __init__(self, clip_border: Optional[bool] = True, **kwargs) -> None: + super().__init__(**kwargs) + self.clip_border = clip_border + + def encode(self, + points: Tensor, + gt_bboxes: Union[Tensor, BaseBoxes], + max_dis: Optional[float] = None, + eps: float = 0.1) -> Tensor: + """Encode bounding box to distances. + + Args: + points (Tensor): Shape (N, 2), The format is [x, y]. + gt_bboxes (Tensor or :obj:`BaseBoxes`): Shape (N, 4), The format + is "xyxy" + max_dis (float): Upper bound of the distance. Default None. + eps (float): a small value to ensure target < max_dis, instead <=. + Default 0.1. + + Returns: + Tensor: Box transformation deltas. The shape is (N, 4). + """ + gt_bboxes = get_box_tensor(gt_bboxes) + assert points.size(0) == gt_bboxes.size(0) + assert points.size(-1) == 2 + assert gt_bboxes.size(-1) == 4 + return bbox2distance(points, gt_bboxes, max_dis, eps) + + def decode( + self, + points: Tensor, + pred_bboxes: Tensor, + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None + ) -> Union[Tensor, BaseBoxes]: + """Decode distance prediction to bounding box. + + Args: + points (Tensor): Shape (B, N, 2) or (N, 2). + pred_bboxes (Tensor): Distance from the given point to 4 + boundaries (left, top, right, bottom). Shape (B, N, 4) + or (N, 4) + max_shape (Sequence[int] or torch.Tensor or Sequence[ + Sequence[int]],optional): Maximum bounds for boxes, specifies + (H, W, C) or (H, W). If priors shape is (B, N, 4), then + the max_shape should be a Sequence[Sequence[int]], + and the length of max_shape should also be B. + Default None. + Returns: + Union[Tensor, :obj:`BaseBoxes`]: Boxes with shape (N, 4) or + (B, N, 4) + """ + assert points.size(0) == pred_bboxes.size(0) + assert points.size(-1) == 2 + assert pred_bboxes.size(-1) == 4 + if self.clip_border is False: + max_shape = None + bboxes = distance2bbox(points, pred_bboxes, max_shape) + + if self.use_box_type: + bboxes = HorizontalBoxes(bboxes) + return bboxes diff --git a/mmdetection/mmdet/models/task_modules/coders/legacy_delta_xywh_bbox_coder.py b/mmdetection/mmdet/models/task_modules/coders/legacy_delta_xywh_bbox_coder.py new file mode 100644 index 00000000..9eb1bedb --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/coders/legacy_delta_xywh_bbox_coder.py @@ -0,0 +1,235 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence, Union + +import numpy as np +import torch +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import BaseBoxes, HorizontalBoxes, get_box_tensor +from .base_bbox_coder import BaseBBoxCoder + + +@TASK_UTILS.register_module() +class LegacyDeltaXYWHBBoxCoder(BaseBBoxCoder): + """Legacy Delta XYWH BBox coder used in MMDet V1.x. + + Following the practice in R-CNN [1]_, this coder encodes bbox (x1, y1, x2, + y2) into delta (dx, dy, dw, dh) and decodes delta (dx, dy, dw, dh) + back to original bbox (x1, y1, x2, y2). + + Note: + The main difference between :class`LegacyDeltaXYWHBBoxCoder` and + :class:`DeltaXYWHBBoxCoder` is whether ``+ 1`` is used during width and + height calculation. We suggest to only use this coder when testing with + MMDet V1.x models. + + References: + .. [1] https://arxiv.org/abs/1311.2524 + + Args: + target_means (Sequence[float]): denormalizing means of target for + delta coordinates + target_stds (Sequence[float]): denormalizing standard deviation of + target for delta coordinates + """ + + def __init__(self, + target_means: Sequence[float] = (0., 0., 0., 0.), + target_stds: Sequence[float] = (1., 1., 1., 1.), + **kwargs) -> None: + super().__init__(**kwargs) + self.means = target_means + self.stds = target_stds + + def encode(self, bboxes: Union[Tensor, BaseBoxes], + gt_bboxes: Union[Tensor, BaseBoxes]) -> Tensor: + """Get box regression transformation deltas that can be used to + transform the ``bboxes`` into the ``gt_bboxes``. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): source boxes, + e.g., object proposals. + gt_bboxes (torch.Tensor or :obj:`BaseBoxes`): target of the + transformation, e.g., ground-truth boxes. + + Returns: + torch.Tensor: Box transformation deltas + """ + bboxes = get_box_tensor(bboxes) + gt_bboxes = get_box_tensor(gt_bboxes) + assert bboxes.size(0) == gt_bboxes.size(0) + assert bboxes.size(-1) == gt_bboxes.size(-1) == 4 + encoded_bboxes = legacy_bbox2delta(bboxes, gt_bboxes, self.means, + self.stds) + return encoded_bboxes + + def decode( + self, + bboxes: Union[Tensor, BaseBoxes], + pred_bboxes: Tensor, + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None, + wh_ratio_clip: Optional[float] = 16 / 1000 + ) -> Union[Tensor, BaseBoxes]: + """Apply transformation `pred_bboxes` to `boxes`. + + Args: + boxes (torch.Tensor or :obj:`BaseBoxes`): Basic boxes. + pred_bboxes (torch.Tensor): Encoded boxes with shape + max_shape (tuple[int], optional): Maximum shape of boxes. + Defaults to None. + wh_ratio_clip (float, optional): The allowed ratio between + width and height. + + Returns: + Union[torch.Tensor, :obj:`BaseBoxes`]: Decoded boxes. + """ + bboxes = get_box_tensor(bboxes) + assert pred_bboxes.size(0) == bboxes.size(0) + decoded_bboxes = legacy_delta2bbox(bboxes, pred_bboxes, self.means, + self.stds, max_shape, wh_ratio_clip) + + if self.use_box_type: + assert decoded_bboxes.size(-1) == 4, \ + ('Cannot warp decoded boxes with box type when decoded boxes' + 'have shape of (N, num_classes * 4)') + decoded_bboxes = HorizontalBoxes(decoded_bboxes) + return decoded_bboxes + + +def legacy_bbox2delta( + proposals: Tensor, + gt: Tensor, + means: Sequence[float] = (0., 0., 0., 0.), + stds: Sequence[float] = (1., 1., 1., 1.) +) -> Tensor: + """Compute deltas of proposals w.r.t. gt in the MMDet V1.x manner. + + We usually compute the deltas of x, y, w, h of proposals w.r.t ground + truth bboxes to get regression target. + This is the inverse function of `delta2bbox()` + + Args: + proposals (Tensor): Boxes to be transformed, shape (N, ..., 4) + gt (Tensor): Gt bboxes to be used as base, shape (N, ..., 4) + means (Sequence[float]): Denormalizing means for delta coordinates + stds (Sequence[float]): Denormalizing standard deviation for delta + coordinates + + Returns: + Tensor: deltas with shape (N, 4), where columns represent dx, dy, + dw, dh. + """ + assert proposals.size() == gt.size() + + proposals = proposals.float() + gt = gt.float() + px = (proposals[..., 0] + proposals[..., 2]) * 0.5 + py = (proposals[..., 1] + proposals[..., 3]) * 0.5 + pw = proposals[..., 2] - proposals[..., 0] + 1.0 + ph = proposals[..., 3] - proposals[..., 1] + 1.0 + + gx = (gt[..., 0] + gt[..., 2]) * 0.5 + gy = (gt[..., 1] + gt[..., 3]) * 0.5 + gw = gt[..., 2] - gt[..., 0] + 1.0 + gh = gt[..., 3] - gt[..., 1] + 1.0 + + dx = (gx - px) / pw + dy = (gy - py) / ph + dw = torch.log(gw / pw) + dh = torch.log(gh / ph) + deltas = torch.stack([dx, dy, dw, dh], dim=-1) + + means = deltas.new_tensor(means).unsqueeze(0) + stds = deltas.new_tensor(stds).unsqueeze(0) + deltas = deltas.sub_(means).div_(stds) + + return deltas + + +def legacy_delta2bbox(rois: Tensor, + deltas: Tensor, + means: Sequence[float] = (0., 0., 0., 0.), + stds: Sequence[float] = (1., 1., 1., 1.), + max_shape: Optional[ + Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None, + wh_ratio_clip: float = 16 / 1000) -> Tensor: + """Apply deltas to shift/scale base boxes in the MMDet V1.x manner. + + Typically the rois are anchor or proposed bounding boxes and the deltas are + network outputs used to shift/scale those boxes. + This is the inverse function of `bbox2delta()` + + Args: + rois (Tensor): Boxes to be transformed. Has shape (N, 4) + deltas (Tensor): Encoded offsets with respect to each roi. + Has shape (N, 4 * num_classes). Note N = num_anchors * W * H when + rois is a grid of anchors. Offset encoding follows [1]_. + means (Sequence[float]): Denormalizing means for delta coordinates + stds (Sequence[float]): Denormalizing standard deviation for delta + coordinates + max_shape (tuple[int, int]): Maximum bounds for boxes. specifies (H, W) + wh_ratio_clip (float): Maximum aspect ratio for boxes. + + Returns: + Tensor: Boxes with shape (N, 4), where columns represent + tl_x, tl_y, br_x, br_y. + + References: + .. [1] https://arxiv.org/abs/1311.2524 + + Example: + >>> rois = torch.Tensor([[ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 5., 5., 5., 5.]]) + >>> deltas = torch.Tensor([[ 0., 0., 0., 0.], + >>> [ 1., 1., 1., 1.], + >>> [ 0., 0., 2., -1.], + >>> [ 0.7, -1.9, -0.5, 0.3]]) + >>> legacy_delta2bbox(rois, deltas, max_shape=(32, 32)) + tensor([[0.0000, 0.0000, 1.5000, 1.5000], + [0.0000, 0.0000, 5.2183, 5.2183], + [0.0000, 0.1321, 7.8891, 0.8679], + [5.3967, 2.4251, 6.0033, 3.7749]]) + """ + means = deltas.new_tensor(means).repeat(1, deltas.size(1) // 4) + stds = deltas.new_tensor(stds).repeat(1, deltas.size(1) // 4) + denorm_deltas = deltas * stds + means + dx = denorm_deltas[:, 0::4] + dy = denorm_deltas[:, 1::4] + dw = denorm_deltas[:, 2::4] + dh = denorm_deltas[:, 3::4] + max_ratio = np.abs(np.log(wh_ratio_clip)) + dw = dw.clamp(min=-max_ratio, max=max_ratio) + dh = dh.clamp(min=-max_ratio, max=max_ratio) + # Compute center of each roi + px = ((rois[:, 0] + rois[:, 2]) * 0.5).unsqueeze(1).expand_as(dx) + py = ((rois[:, 1] + rois[:, 3]) * 0.5).unsqueeze(1).expand_as(dy) + # Compute width/height of each roi + pw = (rois[:, 2] - rois[:, 0] + 1.0).unsqueeze(1).expand_as(dw) + ph = (rois[:, 3] - rois[:, 1] + 1.0).unsqueeze(1).expand_as(dh) + # Use exp(network energy) to enlarge/shrink each roi + gw = pw * dw.exp() + gh = ph * dh.exp() + # Use network energy to shift the center of each roi + gx = px + pw * dx + gy = py + ph * dy + # Convert center-xy/width/height to top-left, bottom-right + + # The true legacy box coder should +- 0.5 here. + # However, current implementation improves the performance when testing + # the models trained in MMDetection 1.X (~0.5 bbox AP, 0.2 mask AP) + x1 = gx - gw * 0.5 + y1 = gy - gh * 0.5 + x2 = gx + gw * 0.5 + y2 = gy + gh * 0.5 + if max_shape is not None: + x1 = x1.clamp(min=0, max=max_shape[1] - 1) + y1 = y1.clamp(min=0, max=max_shape[0] - 1) + x2 = x2.clamp(min=0, max=max_shape[1] - 1) + y2 = y2.clamp(min=0, max=max_shape[0] - 1) + bboxes = torch.stack([x1, y1, x2, y2], dim=-1).view_as(deltas) + return bboxes diff --git a/mmdetection/mmdet/models/task_modules/coders/pseudo_bbox_coder.py b/mmdetection/mmdet/models/task_modules/coders/pseudo_bbox_coder.py new file mode 100644 index 00000000..9ee74311 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/coders/pseudo_bbox_coder.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Union + +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import BaseBoxes, HorizontalBoxes, get_box_tensor +from .base_bbox_coder import BaseBBoxCoder + + +@TASK_UTILS.register_module() +class PseudoBBoxCoder(BaseBBoxCoder): + """Pseudo bounding box coder.""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def encode(self, bboxes: Tensor, gt_bboxes: Union[Tensor, + BaseBoxes]) -> Tensor: + """torch.Tensor: return the given ``bboxes``""" + gt_bboxes = get_box_tensor(gt_bboxes) + return gt_bboxes + + def decode(self, bboxes: Tensor, pred_bboxes: Union[Tensor, + BaseBoxes]) -> Tensor: + """torch.Tensor: return the given ``pred_bboxes``""" + if self.use_box_type: + pred_bboxes = HorizontalBoxes(pred_bboxes) + return pred_bboxes diff --git a/mmdetection/mmdet/models/task_modules/coders/tblr_bbox_coder.py b/mmdetection/mmdet/models/task_modules/coders/tblr_bbox_coder.py new file mode 100644 index 00000000..74b388f7 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/coders/tblr_bbox_coder.py @@ -0,0 +1,228 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence, Union + +import torch +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import BaseBoxes, HorizontalBoxes, get_box_tensor +from .base_bbox_coder import BaseBBoxCoder + + +@TASK_UTILS.register_module() +class TBLRBBoxCoder(BaseBBoxCoder): + """TBLR BBox coder. + + Following the practice in `FSAF `_, + this coder encodes gt bboxes (x1, y1, x2, y2) into (top, bottom, left, + right) and decode it back to the original. + + Args: + normalizer (list | float): Normalization factor to be + divided with when coding the coordinates. If it is a list, it should + have length of 4 indicating normalization factor in tblr dims. + Otherwise it is a unified float factor for all dims. Default: 4.0 + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Defaults to True. + """ + + def __init__(self, + normalizer: Union[Sequence[float], float] = 4.0, + clip_border: bool = True, + **kwargs) -> None: + super().__init__(**kwargs) + self.normalizer = normalizer + self.clip_border = clip_border + + def encode(self, bboxes: Union[Tensor, BaseBoxes], + gt_bboxes: Union[Tensor, BaseBoxes]) -> Tensor: + """Get box regression transformation deltas that can be used to + transform the ``bboxes`` into the ``gt_bboxes`` in the (top, left, + bottom, right) order. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): source boxes, + e.g., object proposals. + gt_bboxes (torch.Tensor or :obj:`BaseBoxes`): target of the + transformation, e.g., ground truth boxes. + + Returns: + torch.Tensor: Box transformation deltas + """ + bboxes = get_box_tensor(bboxes) + gt_bboxes = get_box_tensor(gt_bboxes) + assert bboxes.size(0) == gt_bboxes.size(0) + assert bboxes.size(-1) == gt_bboxes.size(-1) == 4 + encoded_bboxes = bboxes2tblr( + bboxes, gt_bboxes, normalizer=self.normalizer) + return encoded_bboxes + + def decode( + self, + bboxes: Union[Tensor, BaseBoxes], + pred_bboxes: Tensor, + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None + ) -> Union[Tensor, BaseBoxes]: + """Apply transformation `pred_bboxes` to `boxes`. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): Basic boxes.Shape + (B, N, 4) or (N, 4) + pred_bboxes (torch.Tensor): Encoded boxes with shape + (B, N, 4) or (N, 4) + max_shape (Sequence[int] or torch.Tensor or Sequence[ + Sequence[int]],optional): Maximum bounds for boxes, specifies + (H, W, C) or (H, W). If bboxes shape is (B, N, 4), then + the max_shape should be a Sequence[Sequence[int]] + and the length of max_shape should also be B. + + Returns: + Union[torch.Tensor, :obj:`BaseBoxes`]: Decoded boxes. + """ + bboxes = get_box_tensor(bboxes) + decoded_bboxes = tblr2bboxes( + bboxes, + pred_bboxes, + normalizer=self.normalizer, + max_shape=max_shape, + clip_border=self.clip_border) + + if self.use_box_type: + decoded_bboxes = HorizontalBoxes(decoded_bboxes) + return decoded_bboxes + + +def bboxes2tblr(priors: Tensor, + gts: Tensor, + normalizer: Union[Sequence[float], float] = 4.0, + normalize_by_wh: bool = True) -> Tensor: + """Encode ground truth boxes to tblr coordinate. + + It first convert the gt coordinate to tblr format, + (top, bottom, left, right), relative to prior box centers. + The tblr coordinate may be normalized by the side length of prior bboxes + if `normalize_by_wh` is specified as True, and it is then normalized by + the `normalizer` factor. + + Args: + priors (Tensor): Prior boxes in point form + Shape: (num_proposals,4). + gts (Tensor): Coords of ground truth for each prior in point-form + Shape: (num_proposals, 4). + normalizer (Sequence[float] | float): normalization parameter of + encoded boxes. If it is a list, it has to have length = 4. + Default: 4.0 + normalize_by_wh (bool): Whether to normalize tblr coordinate by the + side length (wh) of prior bboxes. + + Return: + encoded boxes (Tensor), Shape: (num_proposals, 4) + """ + + # dist b/t match center and prior's center + if not isinstance(normalizer, float): + normalizer = torch.tensor(normalizer, device=priors.device) + assert len(normalizer) == 4, 'Normalizer must have length = 4' + assert priors.size(0) == gts.size(0) + prior_centers = (priors[:, 0:2] + priors[:, 2:4]) / 2 + xmin, ymin, xmax, ymax = gts.split(1, dim=1) + top = prior_centers[:, 1].unsqueeze(1) - ymin + bottom = ymax - prior_centers[:, 1].unsqueeze(1) + left = prior_centers[:, 0].unsqueeze(1) - xmin + right = xmax - prior_centers[:, 0].unsqueeze(1) + loc = torch.cat((top, bottom, left, right), dim=1) + if normalize_by_wh: + # Normalize tblr by anchor width and height + wh = priors[:, 2:4] - priors[:, 0:2] + w, h = torch.split(wh, 1, dim=1) + loc[:, :2] /= h # tb is normalized by h + loc[:, 2:] /= w # lr is normalized by w + # Normalize tblr by the given normalization factor + return loc / normalizer + + +def tblr2bboxes(priors: Tensor, + tblr: Tensor, + normalizer: Union[Sequence[float], float] = 4.0, + normalize_by_wh: bool = True, + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None, + clip_border: bool = True) -> Tensor: + """Decode tblr outputs to prediction boxes. + + The process includes 3 steps: 1) De-normalize tblr coordinates by + multiplying it with `normalizer`; 2) De-normalize tblr coordinates by the + prior bbox width and height if `normalize_by_wh` is `True`; 3) Convert + tblr (top, bottom, left, right) pair relative to the center of priors back + to (xmin, ymin, xmax, ymax) coordinate. + + Args: + priors (Tensor): Prior boxes in point form (x0, y0, x1, y1) + Shape: (N,4) or (B, N, 4). + tblr (Tensor): Coords of network output in tblr form + Shape: (N, 4) or (B, N, 4). + normalizer (Sequence[float] | float): Normalization parameter of + encoded boxes. By list, it represents the normalization factors at + tblr dims. By float, it is the unified normalization factor at all + dims. Default: 4.0 + normalize_by_wh (bool): Whether the tblr coordinates have been + normalized by the side length (wh) of prior bboxes. + max_shape (Sequence[int] or torch.Tensor or Sequence[ + Sequence[int]],optional): Maximum bounds for boxes, specifies + (H, W, C) or (H, W). If priors shape is (B, N, 4), then + the max_shape should be a Sequence[Sequence[int]] + and the length of max_shape should also be B. + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Defaults to True. + + Return: + encoded boxes (Tensor): Boxes with shape (N, 4) or (B, N, 4) + """ + if not isinstance(normalizer, float): + normalizer = torch.tensor(normalizer, device=priors.device) + assert len(normalizer) == 4, 'Normalizer must have length = 4' + assert priors.size(0) == tblr.size(0) + if priors.ndim == 3: + assert priors.size(1) == tblr.size(1) + + loc_decode = tblr * normalizer + prior_centers = (priors[..., 0:2] + priors[..., 2:4]) / 2 + if normalize_by_wh: + wh = priors[..., 2:4] - priors[..., 0:2] + w, h = torch.split(wh, 1, dim=-1) + # Inplace operation with slice would failed for exporting to ONNX + th = h * loc_decode[..., :2] # tb + tw = w * loc_decode[..., 2:] # lr + loc_decode = torch.cat([th, tw], dim=-1) + # Cannot be exported using onnx when loc_decode.split(1, dim=-1) + top, bottom, left, right = loc_decode.split((1, 1, 1, 1), dim=-1) + xmin = prior_centers[..., 0].unsqueeze(-1) - left + xmax = prior_centers[..., 0].unsqueeze(-1) + right + ymin = prior_centers[..., 1].unsqueeze(-1) - top + ymax = prior_centers[..., 1].unsqueeze(-1) + bottom + + bboxes = torch.cat((xmin, ymin, xmax, ymax), dim=-1) + + if clip_border and max_shape is not None: + # clip bboxes with dynamic `min` and `max` for onnx + if torch.onnx.is_in_onnx_export(): + from mmdet.core.export import dynamic_clip_for_onnx + xmin, ymin, xmax, ymax = dynamic_clip_for_onnx( + xmin, ymin, xmax, ymax, max_shape) + bboxes = torch.cat([xmin, ymin, xmax, ymax], dim=-1) + return bboxes + if not isinstance(max_shape, torch.Tensor): + max_shape = priors.new_tensor(max_shape) + max_shape = max_shape[..., :2].type_as(priors) + if max_shape.ndim == 2: + assert bboxes.ndim == 3 + assert max_shape.size(0) == bboxes.size(0) + + min_xy = priors.new_tensor(0) + max_xy = torch.cat([max_shape, max_shape], + dim=-1).flip(-1).unsqueeze(-2) + bboxes = torch.where(bboxes < min_xy, min_xy, bboxes) + bboxes = torch.where(bboxes > max_xy, max_xy, bboxes) + + return bboxes diff --git a/mmdetection/mmdet/models/task_modules/coders/yolo_bbox_coder.py b/mmdetection/mmdet/models/task_modules/coders/yolo_bbox_coder.py new file mode 100644 index 00000000..2e1c7667 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/coders/yolo_bbox_coder.py @@ -0,0 +1,94 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Union + +import torch +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import BaseBoxes, HorizontalBoxes, get_box_tensor +from .base_bbox_coder import BaseBBoxCoder + + +@TASK_UTILS.register_module() +class YOLOBBoxCoder(BaseBBoxCoder): + """YOLO BBox coder. + + Following `YOLO `_, this coder divide + image into grids, and encode bbox (x1, y1, x2, y2) into (cx, cy, dw, dh). + cx, cy in [0., 1.], denotes relative center position w.r.t the center of + bboxes. dw, dh are the same as :obj:`DeltaXYWHBBoxCoder`. + + Args: + eps (float): Min value of cx, cy when encoding. + """ + + def __init__(self, eps: float = 1e-6, **kwargs): + super().__init__(**kwargs) + self.eps = eps + + def encode(self, bboxes: Union[Tensor, BaseBoxes], + gt_bboxes: Union[Tensor, BaseBoxes], + stride: Union[Tensor, int]) -> Tensor: + """Get box regression transformation deltas that can be used to + transform the ``bboxes`` into the ``gt_bboxes``. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): Source boxes, + e.g., anchors. + gt_bboxes (torch.Tensor or :obj:`BaseBoxes`): Target of the + transformation, e.g., ground-truth boxes. + stride (torch.Tensor | int): Stride of bboxes. + + Returns: + torch.Tensor: Box transformation deltas + """ + bboxes = get_box_tensor(bboxes) + gt_bboxes = get_box_tensor(gt_bboxes) + assert bboxes.size(0) == gt_bboxes.size(0) + assert bboxes.size(-1) == gt_bboxes.size(-1) == 4 + x_center_gt = (gt_bboxes[..., 0] + gt_bboxes[..., 2]) * 0.5 + y_center_gt = (gt_bboxes[..., 1] + gt_bboxes[..., 3]) * 0.5 + w_gt = gt_bboxes[..., 2] - gt_bboxes[..., 0] + h_gt = gt_bboxes[..., 3] - gt_bboxes[..., 1] + x_center = (bboxes[..., 0] + bboxes[..., 2]) * 0.5 + y_center = (bboxes[..., 1] + bboxes[..., 3]) * 0.5 + w = bboxes[..., 2] - bboxes[..., 0] + h = bboxes[..., 3] - bboxes[..., 1] + w_target = torch.log((w_gt / w).clamp(min=self.eps)) + h_target = torch.log((h_gt / h).clamp(min=self.eps)) + x_center_target = ((x_center_gt - x_center) / stride + 0.5).clamp( + self.eps, 1 - self.eps) + y_center_target = ((y_center_gt - y_center) / stride + 0.5).clamp( + self.eps, 1 - self.eps) + encoded_bboxes = torch.stack( + [x_center_target, y_center_target, w_target, h_target], dim=-1) + return encoded_bboxes + + def decode(self, bboxes: Union[Tensor, BaseBoxes], pred_bboxes: Tensor, + stride: Union[Tensor, int]) -> Union[Tensor, BaseBoxes]: + """Apply transformation `pred_bboxes` to `boxes`. + + Args: + boxes (torch.Tensor or :obj:`BaseBoxes`): Basic boxes, + e.g. anchors. + pred_bboxes (torch.Tensor): Encoded boxes with shape + stride (torch.Tensor | int): Strides of bboxes. + + Returns: + Union[torch.Tensor, :obj:`BaseBoxes`]: Decoded boxes. + """ + bboxes = get_box_tensor(bboxes) + assert pred_bboxes.size(-1) == bboxes.size(-1) == 4 + xy_centers = (bboxes[..., :2] + bboxes[..., 2:]) * 0.5 + ( + pred_bboxes[..., :2] - 0.5) * stride + whs = (bboxes[..., 2:] - + bboxes[..., :2]) * 0.5 * pred_bboxes[..., 2:].exp() + decoded_bboxes = torch.stack( + (xy_centers[..., 0] - whs[..., 0], xy_centers[..., 1] - + whs[..., 1], xy_centers[..., 0] + whs[..., 0], + xy_centers[..., 1] + whs[..., 1]), + dim=-1) + + if self.use_box_type: + decoded_bboxes = HorizontalBoxes(decoded_bboxes) + return decoded_bboxes diff --git a/mmdetection/mmdet/models/task_modules/prior_generators/__init__.py b/mmdetection/mmdet/models/task_modules/prior_generators/__init__.py new file mode 100644 index 00000000..7795e98c --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/prior_generators/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .anchor_generator import (AnchorGenerator, LegacyAnchorGenerator, + SSDAnchorGenerator, YOLOAnchorGenerator) +from .point_generator import MlvlPointGenerator, PointGenerator +from .utils import anchor_inside_flags, calc_region + +__all__ = [ + 'AnchorGenerator', 'LegacyAnchorGenerator', 'anchor_inside_flags', + 'PointGenerator', 'calc_region', 'YOLOAnchorGenerator', + 'MlvlPointGenerator', 'SSDAnchorGenerator' +] diff --git a/mmdetection/mmdet/models/task_modules/prior_generators/anchor_generator.py b/mmdetection/mmdet/models/task_modules/prior_generators/anchor_generator.py new file mode 100644 index 00000000..2757697c --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/prior_generators/anchor_generator.py @@ -0,0 +1,848 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from typing import List, Optional, Tuple, Union + +import numpy as np +import torch +from mmengine.utils import is_tuple_of +from torch import Tensor +from torch.nn.modules.utils import _pair + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import HorizontalBoxes + +DeviceType = Union[str, torch.device] + + +@TASK_UTILS.register_module() +class AnchorGenerator: + """Standard anchor generator for 2D anchor-based detectors. + + Args: + strides (list[int] | list[tuple[int, int]]): Strides of anchors + in multiple feature levels in order (w, h). + ratios (list[float]): The list of ratios between the height and width + of anchors in a single level. + scales (list[int], Optional): Anchor scales for anchors + in a single level. It cannot be set at the same time + if `octave_base_scale` and `scales_per_octave` are set. + base_sizes (list[int], Optional): The basic sizes + of anchors in multiple levels. + If None is given, strides will be used as base_sizes. + (If strides are non square, the shortest stride is taken.) + scale_major (bool): Whether to multiply scales first when generating + base anchors. If true, the anchors in the same row will have the + same scales. By default it is True in V2.0 + octave_base_scale (int, Optional): The base scale of octave. + scales_per_octave (int, Optional): Number of scales for each octave. + `octave_base_scale` and `scales_per_octave` are usually used in + retinanet and the `scales` should be None when they are set. + centers (list[tuple[float]], Optional): The centers of the anchor + relative to the feature grid center in multiple feature levels. + By default it is set to be None and not used. If a list of tuple of + float is given, they will be used to shift the centers of anchors. + center_offset (float): The offset of center in proportion to anchors' + width and height. By default it is 0 in V2.0. + use_box_type (bool): Whether to warp anchors with the box type data + structure. Defaults to False. + + Examples: + >>> from mmdet.models.task_modules. + ... prior_generators import AnchorGenerator + >>> self = AnchorGenerator([16], [1.], [1.], [9]) + >>> all_anchors = self.grid_priors([(2, 2)], device='cpu') + >>> print(all_anchors) + [tensor([[-4.5000, -4.5000, 4.5000, 4.5000], + [11.5000, -4.5000, 20.5000, 4.5000], + [-4.5000, 11.5000, 4.5000, 20.5000], + [11.5000, 11.5000, 20.5000, 20.5000]])] + >>> self = AnchorGenerator([16, 32], [1.], [1.], [9, 18]) + >>> all_anchors = self.grid_priors([(2, 2), (1, 1)], device='cpu') + >>> print(all_anchors) + [tensor([[-4.5000, -4.5000, 4.5000, 4.5000], + [11.5000, -4.5000, 20.5000, 4.5000], + [-4.5000, 11.5000, 4.5000, 20.5000], + [11.5000, 11.5000, 20.5000, 20.5000]]), \ + tensor([[-9., -9., 9., 9.]])] + """ + + def __init__(self, + strides: Union[List[int], List[Tuple[int, int]]], + ratios: List[float], + scales: Optional[List[int]] = None, + base_sizes: Optional[List[int]] = None, + scale_major: bool = True, + octave_base_scale: Optional[int] = None, + scales_per_octave: Optional[int] = None, + centers: Optional[List[Tuple[float, float]]] = None, + center_offset: float = 0., + use_box_type: bool = False) -> None: + # check center and center_offset + if center_offset != 0: + assert centers is None, 'center cannot be set when center_offset' \ + f'!=0, {centers} is given.' + if not (0 <= center_offset <= 1): + raise ValueError('center_offset should be in range [0, 1], ' + f'{center_offset} is given.') + if centers is not None: + assert len(centers) == len(strides), \ + 'The number of strides should be the same as centers, got ' \ + f'{strides} and {centers}' + + # calculate base sizes of anchors + self.strides = [_pair(stride) for stride in strides] + self.base_sizes = [min(stride) for stride in self.strides + ] if base_sizes is None else base_sizes + assert len(self.base_sizes) == len(self.strides), \ + 'The number of strides should be the same as base sizes, got ' \ + f'{self.strides} and {self.base_sizes}' + + # calculate scales of anchors + assert ((octave_base_scale is not None + and scales_per_octave is not None) ^ (scales is not None)), \ + 'scales and octave_base_scale with scales_per_octave cannot' \ + ' be set at the same time' + if scales is not None: + self.scales = torch.Tensor(scales) + elif octave_base_scale is not None and scales_per_octave is not None: + octave_scales = np.array( + [2**(i / scales_per_octave) for i in range(scales_per_octave)]) + scales = octave_scales * octave_base_scale + self.scales = torch.Tensor(scales) + else: + raise ValueError('Either scales or octave_base_scale with ' + 'scales_per_octave should be set') + + self.octave_base_scale = octave_base_scale + self.scales_per_octave = scales_per_octave + self.ratios = torch.Tensor(ratios) + self.scale_major = scale_major + self.centers = centers + self.center_offset = center_offset + self.base_anchors = self.gen_base_anchors() + self.use_box_type = use_box_type + + @property + def num_base_anchors(self) -> List[int]: + """list[int]: total number of base anchors in a feature grid""" + return self.num_base_priors + + @property + def num_base_priors(self) -> List[int]: + """list[int]: The number of priors (anchors) at a point + on the feature grid""" + return [base_anchors.size(0) for base_anchors in self.base_anchors] + + @property + def num_levels(self) -> int: + """int: number of feature levels that the generator will be applied""" + return len(self.strides) + + def gen_base_anchors(self) -> List[Tensor]: + """Generate base anchors. + + Returns: + list(torch.Tensor): Base anchors of a feature grid in multiple \ + feature levels. + """ + multi_level_base_anchors = [] + for i, base_size in enumerate(self.base_sizes): + center = None + if self.centers is not None: + center = self.centers[i] + multi_level_base_anchors.append( + self.gen_single_level_base_anchors( + base_size, + scales=self.scales, + ratios=self.ratios, + center=center)) + return multi_level_base_anchors + + def gen_single_level_base_anchors(self, + base_size: Union[int, float], + scales: Tensor, + ratios: Tensor, + center: Optional[Tuple[float]] = None) \ + -> Tensor: + """Generate base anchors of a single level. + + Args: + base_size (int | float): Basic size of an anchor. + scales (torch.Tensor): Scales of the anchor. + ratios (torch.Tensor): The ratio between the height + and width of anchors in a single level. + center (tuple[float], optional): The center of the base anchor + related to a single feature grid. Defaults to None. + + Returns: + torch.Tensor: Anchors in a single-level feature maps. + """ + w = base_size + h = base_size + if center is None: + x_center = self.center_offset * w + y_center = self.center_offset * h + else: + x_center, y_center = center + + h_ratios = torch.sqrt(ratios) + w_ratios = 1 / h_ratios + if self.scale_major: + ws = (w * w_ratios[:, None] * scales[None, :]).view(-1) + hs = (h * h_ratios[:, None] * scales[None, :]).view(-1) + else: + ws = (w * scales[:, None] * w_ratios[None, :]).view(-1) + hs = (h * scales[:, None] * h_ratios[None, :]).view(-1) + + # use float anchor and the anchor's center is aligned with the + # pixel center + base_anchors = [ + x_center - 0.5 * ws, y_center - 0.5 * hs, x_center + 0.5 * ws, + y_center + 0.5 * hs + ] + base_anchors = torch.stack(base_anchors, dim=-1) + + return base_anchors + + def _meshgrid(self, + x: Tensor, + y: Tensor, + row_major: bool = True) -> Tuple[Tensor]: + """Generate mesh grid of x and y. + + Args: + x (torch.Tensor): Grids of x dimension. + y (torch.Tensor): Grids of y dimension. + row_major (bool): Whether to return y grids first. + Defaults to True. + + Returns: + tuple[torch.Tensor]: The mesh grids of x and y. + """ + # use shape instead of len to keep tracing while exporting to onnx + xx = x.repeat(y.shape[0]) + yy = y.view(-1, 1).repeat(1, x.shape[0]).view(-1) + if row_major: + return xx, yy + else: + return yy, xx + + def grid_priors(self, + featmap_sizes: List[Tuple], + dtype: torch.dtype = torch.float32, + device: DeviceType = 'cuda') -> List[Tensor]: + """Generate grid anchors in multiple feature levels. + + Args: + featmap_sizes (list[tuple]): List of feature map sizes in + multiple feature levels. + dtype (:obj:`torch.dtype`): Dtype of priors. + Defaults to torch.float32. + device (str | torch.device): The device where the anchors + will be put on. + + Return: + list[torch.Tensor]: Anchors in multiple feature levels. \ + The sizes of each tensor should be [N, 4], where \ + N = width * height * num_base_anchors, width and height \ + are the sizes of the corresponding feature level, \ + num_base_anchors is the number of anchors for that level. + """ + assert self.num_levels == len(featmap_sizes) + multi_level_anchors = [] + for i in range(self.num_levels): + anchors = self.single_level_grid_priors( + featmap_sizes[i], level_idx=i, dtype=dtype, device=device) + multi_level_anchors.append(anchors) + return multi_level_anchors + + def single_level_grid_priors(self, + featmap_size: Tuple[int, int], + level_idx: int, + dtype: torch.dtype = torch.float32, + device: DeviceType = 'cuda') -> Tensor: + """Generate grid anchors of a single level. + + Note: + This function is usually called by method ``self.grid_priors``. + + Args: + featmap_size (tuple[int, int]): Size of the feature maps. + level_idx (int): The index of corresponding feature map level. + dtype (obj:`torch.dtype`): Date type of points.Defaults to + ``torch.float32``. + device (str | torch.device): The device the tensor will be put on. + Defaults to 'cuda'. + + Returns: + torch.Tensor: Anchors in the overall feature maps. + """ + + base_anchors = self.base_anchors[level_idx].to(device).to(dtype) + feat_h, feat_w = featmap_size + stride_w, stride_h = self.strides[level_idx] + # First create Range with the default dtype, than convert to + # target `dtype` for onnx exporting. + shift_x = torch.arange(0, feat_w, device=device).to(dtype) * stride_w + shift_y = torch.arange(0, feat_h, device=device).to(dtype) * stride_h + + shift_xx, shift_yy = self._meshgrid(shift_x, shift_y) + shifts = torch.stack([shift_xx, shift_yy, shift_xx, shift_yy], dim=-1) + # first feat_w elements correspond to the first row of shifts + # add A anchors (1, A, 4) to K shifts (K, 1, 4) to get + # shifted anchors (K, A, 4), reshape to (K*A, 4) + + all_anchors = base_anchors[None, :, :] + shifts[:, None, :] + all_anchors = all_anchors.view(-1, 4) + # first A rows correspond to A anchors of (0, 0) in feature map, + # then (0, 1), (0, 2), ... + if self.use_box_type: + all_anchors = HorizontalBoxes(all_anchors) + return all_anchors + + def sparse_priors(self, + prior_idxs: Tensor, + featmap_size: Tuple[int, int], + level_idx: int, + dtype: torch.dtype = torch.float32, + device: DeviceType = 'cuda') -> Tensor: + """Generate sparse anchors according to the ``prior_idxs``. + + Args: + prior_idxs (Tensor): The index of corresponding anchors + in the feature map. + featmap_size (tuple[int, int]): feature map size arrange as (h, w). + level_idx (int): The level index of corresponding feature + map. + dtype (obj:`torch.dtype`): Date type of points.Defaults to + ``torch.float32``. + device (str | torch.device): The device where the points is + located. + Returns: + Tensor: Anchor with shape (N, 4), N should be equal to + the length of ``prior_idxs``. + """ + + height, width = featmap_size + num_base_anchors = self.num_base_anchors[level_idx] + base_anchor_id = prior_idxs % num_base_anchors + x = (prior_idxs // + num_base_anchors) % width * self.strides[level_idx][0] + y = (prior_idxs // width // + num_base_anchors) % height * self.strides[level_idx][1] + priors = torch.stack([x, y, x, y], 1).to(dtype).to(device) + \ + self.base_anchors[level_idx][base_anchor_id, :].to(device) + + return priors + + def grid_anchors(self, + featmap_sizes: List[Tuple], + device: DeviceType = 'cuda') -> List[Tensor]: + """Generate grid anchors in multiple feature levels. + + Args: + featmap_sizes (list[tuple]): List of feature map sizes in + multiple feature levels. + device (str | torch.device): Device where the anchors will be + put on. + + Return: + list[torch.Tensor]: Anchors in multiple feature levels. \ + The sizes of each tensor should be [N, 4], where \ + N = width * height * num_base_anchors, width and height \ + are the sizes of the corresponding feature level, \ + num_base_anchors is the number of anchors for that level. + """ + warnings.warn('``grid_anchors`` would be deprecated soon. ' + 'Please use ``grid_priors`` ') + + assert self.num_levels == len(featmap_sizes) + multi_level_anchors = [] + for i in range(self.num_levels): + anchors = self.single_level_grid_anchors( + self.base_anchors[i].to(device), + featmap_sizes[i], + self.strides[i], + device=device) + multi_level_anchors.append(anchors) + return multi_level_anchors + + def single_level_grid_anchors(self, + base_anchors: Tensor, + featmap_size: Tuple[int, int], + stride: Tuple[int, int] = (16, 16), + device: DeviceType = 'cuda') -> Tensor: + """Generate grid anchors of a single level. + + Note: + This function is usually called by method ``self.grid_anchors``. + + Args: + base_anchors (torch.Tensor): The base anchors of a feature grid. + featmap_size (tuple[int]): Size of the feature maps. + stride (tuple[int, int]): Stride of the feature map in order + (w, h). Defaults to (16, 16). + device (str | torch.device): Device the tensor will be put on. + Defaults to 'cuda'. + + Returns: + torch.Tensor: Anchors in the overall feature maps. + """ + + warnings.warn( + '``single_level_grid_anchors`` would be deprecated soon. ' + 'Please use ``single_level_grid_priors`` ') + + # keep featmap_size as Tensor instead of int, so that we + # can convert to ONNX correctly + feat_h, feat_w = featmap_size + shift_x = torch.arange(0, feat_w, device=device) * stride[0] + shift_y = torch.arange(0, feat_h, device=device) * stride[1] + + shift_xx, shift_yy = self._meshgrid(shift_x, shift_y) + shifts = torch.stack([shift_xx, shift_yy, shift_xx, shift_yy], dim=-1) + shifts = shifts.type_as(base_anchors) + # first feat_w elements correspond to the first row of shifts + # add A anchors (1, A, 4) to K shifts (K, 1, 4) to get + # shifted anchors (K, A, 4), reshape to (K*A, 4) + + all_anchors = base_anchors[None, :, :] + shifts[:, None, :] + all_anchors = all_anchors.view(-1, 4) + # first A rows correspond to A anchors of (0, 0) in feature map, + # then (0, 1), (0, 2), ... + return all_anchors + + def valid_flags(self, + featmap_sizes: List[Tuple[int, int]], + pad_shape: Tuple, + device: DeviceType = 'cuda') -> List[Tensor]: + """Generate valid flags of anchors in multiple feature levels. + + Args: + featmap_sizes (list(tuple[int, int])): List of feature map sizes in + multiple feature levels. + pad_shape (tuple): The padded shape of the image. + device (str | torch.device): Device where the anchors will be + put on. + + Return: + list(torch.Tensor): Valid flags of anchors in multiple levels. + """ + assert self.num_levels == len(featmap_sizes) + multi_level_flags = [] + for i in range(self.num_levels): + anchor_stride = self.strides[i] + feat_h, feat_w = featmap_sizes[i] + h, w = pad_shape[:2] + valid_feat_h = min(int(np.ceil(h / anchor_stride[1])), feat_h) + valid_feat_w = min(int(np.ceil(w / anchor_stride[0])), feat_w) + flags = self.single_level_valid_flags((feat_h, feat_w), + (valid_feat_h, valid_feat_w), + self.num_base_anchors[i], + device=device) + multi_level_flags.append(flags) + return multi_level_flags + + def single_level_valid_flags(self, + featmap_size: Tuple[int, int], + valid_size: Tuple[int, int], + num_base_anchors: int, + device: DeviceType = 'cuda') -> Tensor: + """Generate the valid flags of anchor in a single feature map. + + Args: + featmap_size (tuple[int]): The size of feature maps, arrange + as (h, w). + valid_size (tuple[int]): The valid size of the feature maps. + num_base_anchors (int): The number of base anchors. + device (str | torch.device): Device where the flags will be put on. + Defaults to 'cuda'. + + Returns: + torch.Tensor: The valid flags of each anchor in a single level \ + feature map. + """ + feat_h, feat_w = featmap_size + valid_h, valid_w = valid_size + assert valid_h <= feat_h and valid_w <= feat_w + valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device) + valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device) + valid_x[:valid_w] = 1 + valid_y[:valid_h] = 1 + valid_xx, valid_yy = self._meshgrid(valid_x, valid_y) + valid = valid_xx & valid_yy + valid = valid[:, None].expand(valid.size(0), + num_base_anchors).contiguous().view(-1) + return valid + + def __repr__(self) -> str: + """str: a string that describes the module""" + indent_str = ' ' + repr_str = self.__class__.__name__ + '(\n' + repr_str += f'{indent_str}strides={self.strides},\n' + repr_str += f'{indent_str}ratios={self.ratios},\n' + repr_str += f'{indent_str}scales={self.scales},\n' + repr_str += f'{indent_str}base_sizes={self.base_sizes},\n' + repr_str += f'{indent_str}scale_major={self.scale_major},\n' + repr_str += f'{indent_str}octave_base_scale=' + repr_str += f'{self.octave_base_scale},\n' + repr_str += f'{indent_str}scales_per_octave=' + repr_str += f'{self.scales_per_octave},\n' + repr_str += f'{indent_str}num_levels={self.num_levels}\n' + repr_str += f'{indent_str}centers={self.centers},\n' + repr_str += f'{indent_str}center_offset={self.center_offset})' + return repr_str + + +@TASK_UTILS.register_module() +class SSDAnchorGenerator(AnchorGenerator): + """Anchor generator for SSD. + + Args: + strides (list[int] | list[tuple[int, int]]): Strides of anchors + in multiple feature levels. + ratios (list[float]): The list of ratios between the height and width + of anchors in a single level. + min_sizes (list[float]): The list of minimum anchor sizes on each + level. + max_sizes (list[float]): The list of maximum anchor sizes on each + level. + basesize_ratio_range (tuple(float)): Ratio range of anchors. Being + used when not setting min_sizes and max_sizes. + input_size (int): Size of feature map, 300 for SSD300, 512 for + SSD512. Being used when not setting min_sizes and max_sizes. + scale_major (bool): Whether to multiply scales first when generating + base anchors. If true, the anchors in the same row will have the + same scales. It is always set to be False in SSD. + use_box_type (bool): Whether to warp anchors with the box type data + structure. Defaults to False. + """ + + def __init__(self, + strides: Union[List[int], List[Tuple[int, int]]], + ratios: List[float], + min_sizes: Optional[List[float]] = None, + max_sizes: Optional[List[float]] = None, + basesize_ratio_range: Tuple[float] = (0.15, 0.9), + input_size: int = 300, + scale_major: bool = True, + use_box_type: bool = False) -> None: + assert len(strides) == len(ratios) + assert not (min_sizes is None) ^ (max_sizes is None) + self.strides = [_pair(stride) for stride in strides] + self.centers = [(stride[0] / 2., stride[1] / 2.) + for stride in self.strides] + + if min_sizes is None and max_sizes is None: + # use hard code to generate SSD anchors + self.input_size = input_size + assert is_tuple_of(basesize_ratio_range, float) + self.basesize_ratio_range = basesize_ratio_range + # calculate anchor ratios and sizes + min_ratio, max_ratio = basesize_ratio_range + min_ratio = int(min_ratio * 100) + max_ratio = int(max_ratio * 100) + step = int(np.floor(max_ratio - min_ratio) / (self.num_levels - 2)) + min_sizes = [] + max_sizes = [] + for ratio in range(int(min_ratio), int(max_ratio) + 1, step): + min_sizes.append(int(self.input_size * ratio / 100)) + max_sizes.append(int(self.input_size * (ratio + step) / 100)) + if self.input_size == 300: + if basesize_ratio_range[0] == 0.15: # SSD300 COCO + min_sizes.insert(0, int(self.input_size * 7 / 100)) + max_sizes.insert(0, int(self.input_size * 15 / 100)) + elif basesize_ratio_range[0] == 0.2: # SSD300 VOC + min_sizes.insert(0, int(self.input_size * 10 / 100)) + max_sizes.insert(0, int(self.input_size * 20 / 100)) + else: + raise ValueError( + 'basesize_ratio_range[0] should be either 0.15' + 'or 0.2 when input_size is 300, got ' + f'{basesize_ratio_range[0]}.') + elif self.input_size == 512: + if basesize_ratio_range[0] == 0.1: # SSD512 COCO + min_sizes.insert(0, int(self.input_size * 4 / 100)) + max_sizes.insert(0, int(self.input_size * 10 / 100)) + elif basesize_ratio_range[0] == 0.15: # SSD512 VOC + min_sizes.insert(0, int(self.input_size * 7 / 100)) + max_sizes.insert(0, int(self.input_size * 15 / 100)) + else: + raise ValueError( + 'When not setting min_sizes and max_sizes,' + 'basesize_ratio_range[0] should be either 0.1' + 'or 0.15 when input_size is 512, got' + f' {basesize_ratio_range[0]}.') + else: + raise ValueError( + 'Only support 300 or 512 in SSDAnchorGenerator when ' + 'not setting min_sizes and max_sizes, ' + f'got {self.input_size}.') + + assert len(min_sizes) == len(max_sizes) == len(strides) + + anchor_ratios = [] + anchor_scales = [] + for k in range(len(self.strides)): + scales = [1., np.sqrt(max_sizes[k] / min_sizes[k])] + anchor_ratio = [1.] + for r in ratios[k]: + anchor_ratio += [1 / r, r] # 4 or 6 ratio + anchor_ratios.append(torch.Tensor(anchor_ratio)) + anchor_scales.append(torch.Tensor(scales)) + + self.base_sizes = min_sizes + self.scales = anchor_scales + self.ratios = anchor_ratios + self.scale_major = scale_major + self.center_offset = 0 + self.base_anchors = self.gen_base_anchors() + self.use_box_type = use_box_type + + def gen_base_anchors(self) -> List[Tensor]: + """Generate base anchors. + + Returns: + list(torch.Tensor): Base anchors of a feature grid in multiple \ + feature levels. + """ + multi_level_base_anchors = [] + for i, base_size in enumerate(self.base_sizes): + base_anchors = self.gen_single_level_base_anchors( + base_size, + scales=self.scales[i], + ratios=self.ratios[i], + center=self.centers[i]) + indices = list(range(len(self.ratios[i]))) + indices.insert(1, len(indices)) + base_anchors = torch.index_select(base_anchors, 0, + torch.LongTensor(indices)) + multi_level_base_anchors.append(base_anchors) + return multi_level_base_anchors + + def __repr__(self) -> str: + """str: a string that describes the module""" + indent_str = ' ' + repr_str = self.__class__.__name__ + '(\n' + repr_str += f'{indent_str}strides={self.strides},\n' + repr_str += f'{indent_str}scales={self.scales},\n' + repr_str += f'{indent_str}scale_major={self.scale_major},\n' + repr_str += f'{indent_str}input_size={self.input_size},\n' + repr_str += f'{indent_str}scales={self.scales},\n' + repr_str += f'{indent_str}ratios={self.ratios},\n' + repr_str += f'{indent_str}num_levels={self.num_levels},\n' + repr_str += f'{indent_str}base_sizes={self.base_sizes},\n' + repr_str += f'{indent_str}basesize_ratio_range=' + repr_str += f'{self.basesize_ratio_range})' + return repr_str + + +@TASK_UTILS.register_module() +class LegacyAnchorGenerator(AnchorGenerator): + """Legacy anchor generator used in MMDetection V1.x. + + Note: + Difference to the V2.0 anchor generator: + + 1. The center offset of V1.x anchors are set to be 0.5 rather than 0. + 2. The width/height are minused by 1 when calculating the anchors' \ + centers and corners to meet the V1.x coordinate system. + 3. The anchors' corners are quantized. + + Args: + strides (list[int] | list[tuple[int]]): Strides of anchors + in multiple feature levels. + ratios (list[float]): The list of ratios between the height and width + of anchors in a single level. + scales (list[int] | None): Anchor scales for anchors in a single level. + It cannot be set at the same time if `octave_base_scale` and + `scales_per_octave` are set. + base_sizes (list[int]): The basic sizes of anchors in multiple levels. + If None is given, strides will be used to generate base_sizes. + scale_major (bool): Whether to multiply scales first when generating + base anchors. If true, the anchors in the same row will have the + same scales. By default it is True in V2.0 + octave_base_scale (int): The base scale of octave. + scales_per_octave (int): Number of scales for each octave. + `octave_base_scale` and `scales_per_octave` are usually used in + retinanet and the `scales` should be None when they are set. + centers (list[tuple[float, float]] | None): The centers of the anchor + relative to the feature grid center in multiple feature levels. + By default it is set to be None and not used. It a list of float + is given, this list will be used to shift the centers of anchors. + center_offset (float): The offset of center in proportion to anchors' + width and height. By default it is 0.5 in V2.0 but it should be 0.5 + in v1.x models. + use_box_type (bool): Whether to warp anchors with the box type data + structure. Defaults to False. + + Examples: + >>> from mmdet.models.task_modules. + ... prior_generators import LegacyAnchorGenerator + >>> self = LegacyAnchorGenerator( + >>> [16], [1.], [1.], [9], center_offset=0.5) + >>> all_anchors = self.grid_anchors(((2, 2),), device='cpu') + >>> print(all_anchors) + [tensor([[ 0., 0., 8., 8.], + [16., 0., 24., 8.], + [ 0., 16., 8., 24.], + [16., 16., 24., 24.]])] + """ + + def gen_single_level_base_anchors(self, + base_size: Union[int, float], + scales: Tensor, + ratios: Tensor, + center: Optional[Tuple[float]] = None) \ + -> Tensor: + """Generate base anchors of a single level. + + Note: + The width/height of anchors are minused by 1 when calculating \ + the centers and corners to meet the V1.x coordinate system. + + Args: + base_size (int | float): Basic size of an anchor. + scales (torch.Tensor): Scales of the anchor. + ratios (torch.Tensor): The ratio between the height. + and width of anchors in a single level. + center (tuple[float], optional): The center of the base anchor + related to a single feature grid. Defaults to None. + + Returns: + torch.Tensor: Anchors in a single-level feature map. + """ + w = base_size + h = base_size + if center is None: + x_center = self.center_offset * (w - 1) + y_center = self.center_offset * (h - 1) + else: + x_center, y_center = center + + h_ratios = torch.sqrt(ratios) + w_ratios = 1 / h_ratios + if self.scale_major: + ws = (w * w_ratios[:, None] * scales[None, :]).view(-1) + hs = (h * h_ratios[:, None] * scales[None, :]).view(-1) + else: + ws = (w * scales[:, None] * w_ratios[None, :]).view(-1) + hs = (h * scales[:, None] * h_ratios[None, :]).view(-1) + + # use float anchor and the anchor's center is aligned with the + # pixel center + base_anchors = [ + x_center - 0.5 * (ws - 1), y_center - 0.5 * (hs - 1), + x_center + 0.5 * (ws - 1), y_center + 0.5 * (hs - 1) + ] + base_anchors = torch.stack(base_anchors, dim=-1).round() + + return base_anchors + + +@TASK_UTILS.register_module() +class LegacySSDAnchorGenerator(SSDAnchorGenerator, LegacyAnchorGenerator): + """Legacy anchor generator used in MMDetection V1.x. + + The difference between `LegacySSDAnchorGenerator` and `SSDAnchorGenerator` + can be found in `LegacyAnchorGenerator`. + """ + + def __init__(self, + strides: Union[List[int], List[Tuple[int, int]]], + ratios: List[float], + basesize_ratio_range: Tuple[float], + input_size: int = 300, + scale_major: bool = True, + use_box_type: bool = False) -> None: + super(LegacySSDAnchorGenerator, self).__init__( + strides=strides, + ratios=ratios, + basesize_ratio_range=basesize_ratio_range, + input_size=input_size, + scale_major=scale_major, + use_box_type=use_box_type) + self.centers = [((stride - 1) / 2., (stride - 1) / 2.) + for stride in strides] + self.base_anchors = self.gen_base_anchors() + + +@TASK_UTILS.register_module() +class YOLOAnchorGenerator(AnchorGenerator): + """Anchor generator for YOLO. + + Args: + strides (list[int] | list[tuple[int, int]]): Strides of anchors + in multiple feature levels. + base_sizes (list[list[tuple[int, int]]]): The basic sizes + of anchors in multiple levels. + """ + + def __init__(self, + strides: Union[List[int], List[Tuple[int, int]]], + base_sizes: List[List[Tuple[int, int]]], + use_box_type: bool = False) -> None: + self.strides = [_pair(stride) for stride in strides] + self.centers = [(stride[0] / 2., stride[1] / 2.) + for stride in self.strides] + self.base_sizes = [] + num_anchor_per_level = len(base_sizes[0]) + for base_sizes_per_level in base_sizes: + assert num_anchor_per_level == len(base_sizes_per_level) + self.base_sizes.append( + [_pair(base_size) for base_size in base_sizes_per_level]) + self.base_anchors = self.gen_base_anchors() + self.use_box_type = use_box_type + + @property + def num_levels(self) -> int: + """int: number of feature levels that the generator will be applied""" + return len(self.base_sizes) + + def gen_base_anchors(self) -> List[Tensor]: + """Generate base anchors. + + Returns: + list(torch.Tensor): Base anchors of a feature grid in multiple \ + feature levels. + """ + multi_level_base_anchors = [] + for i, base_sizes_per_level in enumerate(self.base_sizes): + center = None + if self.centers is not None: + center = self.centers[i] + multi_level_base_anchors.append( + self.gen_single_level_base_anchors(base_sizes_per_level, + center)) + return multi_level_base_anchors + + def gen_single_level_base_anchors(self, + base_sizes_per_level: List[Tuple[int]], + center: Optional[Tuple[float]] = None) \ + -> Tensor: + """Generate base anchors of a single level. + + Args: + base_sizes_per_level (list[tuple[int]]): Basic sizes of + anchors. + center (tuple[float], optional): The center of the base anchor + related to a single feature grid. Defaults to None. + + Returns: + torch.Tensor: Anchors in a single-level feature maps. + """ + x_center, y_center = center + base_anchors = [] + for base_size in base_sizes_per_level: + w, h = base_size + + # use float anchor and the anchor's center is aligned with the + # pixel center + base_anchor = torch.Tensor([ + x_center - 0.5 * w, y_center - 0.5 * h, x_center + 0.5 * w, + y_center + 0.5 * h + ]) + base_anchors.append(base_anchor) + base_anchors = torch.stack(base_anchors, dim=0) + + return base_anchors diff --git a/mmdetection/mmdet/models/task_modules/prior_generators/point_generator.py b/mmdetection/mmdet/models/task_modules/prior_generators/point_generator.py new file mode 100644 index 00000000..c87ad656 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/prior_generators/point_generator.py @@ -0,0 +1,321 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple, Union + +import numpy as np +import torch +from torch import Tensor +from torch.nn.modules.utils import _pair + +from mmdet.registry import TASK_UTILS + +DeviceType = Union[str, torch.device] + + +@TASK_UTILS.register_module() +class PointGenerator: + + def _meshgrid(self, + x: Tensor, + y: Tensor, + row_major: bool = True) -> Tuple[Tensor, Tensor]: + """Generate mesh grid of x and y. + + Args: + x (torch.Tensor): Grids of x dimension. + y (torch.Tensor): Grids of y dimension. + row_major (bool): Whether to return y grids first. + Defaults to True. + + Returns: + tuple[torch.Tensor]: The mesh grids of x and y. + """ + xx = x.repeat(len(y)) + yy = y.view(-1, 1).repeat(1, len(x)).view(-1) + if row_major: + return xx, yy + else: + return yy, xx + + def grid_points(self, + featmap_size: Tuple[int, int], + stride=16, + device: DeviceType = 'cuda') -> Tensor: + """Generate grid points of a single level. + + Args: + featmap_size (tuple[int, int]): Size of the feature maps. + stride (int): The stride of corresponding feature map. + device (str | torch.device): The device the tensor will be put on. + Defaults to 'cuda'. + + Returns: + torch.Tensor: grid point in a feature map. + """ + feat_h, feat_w = featmap_size + shift_x = torch.arange(0., feat_w, device=device) * stride + shift_y = torch.arange(0., feat_h, device=device) * stride + shift_xx, shift_yy = self._meshgrid(shift_x, shift_y) + stride = shift_x.new_full((shift_xx.shape[0], ), stride) + shifts = torch.stack([shift_xx, shift_yy, stride], dim=-1) + all_points = shifts.to(device) + return all_points + + def valid_flags(self, + featmap_size: Tuple[int, int], + valid_size: Tuple[int, int], + device: DeviceType = 'cuda') -> Tensor: + """Generate valid flags of anchors in a feature map. + + Args: + featmap_sizes (list(tuple[int, int])): List of feature map sizes in + multiple feature levels. + valid_shape (tuple[int, int]): The valid shape of the image. + device (str | torch.device): Device where the anchors will be + put on. + + Return: + torch.Tensor: Valid flags of anchors in a level. + """ + feat_h, feat_w = featmap_size + valid_h, valid_w = valid_size + assert valid_h <= feat_h and valid_w <= feat_w + valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device) + valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device) + valid_x[:valid_w] = 1 + valid_y[:valid_h] = 1 + valid_xx, valid_yy = self._meshgrid(valid_x, valid_y) + valid = valid_xx & valid_yy + return valid + + +@TASK_UTILS.register_module() +class MlvlPointGenerator: + """Standard points generator for multi-level (Mlvl) feature maps in 2D + points-based detectors. + + Args: + strides (list[int] | list[tuple[int, int]]): Strides of anchors + in multiple feature levels in order (w, h). + offset (float): The offset of points, the value is normalized with + corresponding stride. Defaults to 0.5. + """ + + def __init__(self, + strides: Union[List[int], List[Tuple[int, int]]], + offset: float = 0.5) -> None: + self.strides = [_pair(stride) for stride in strides] + self.offset = offset + + @property + def num_levels(self) -> int: + """int: number of feature levels that the generator will be applied""" + return len(self.strides) + + @property + def num_base_priors(self) -> List[int]: + """list[int]: The number of priors (points) at a point + on the feature grid""" + return [1 for _ in range(len(self.strides))] + + def _meshgrid(self, + x: Tensor, + y: Tensor, + row_major: bool = True) -> Tuple[Tensor, Tensor]: + yy, xx = torch.meshgrid(y, x) + if row_major: + # warning .flatten() would cause error in ONNX exporting + # have to use reshape here + return xx.reshape(-1), yy.reshape(-1) + + else: + return yy.reshape(-1), xx.reshape(-1) + + def grid_priors(self, + featmap_sizes: List[Tuple], + dtype: torch.dtype = torch.float32, + device: DeviceType = 'cuda', + with_stride: bool = False) -> List[Tensor]: + """Generate grid points of multiple feature levels. + + Args: + featmap_sizes (list[tuple]): List of feature map sizes in + multiple feature levels, each size arrange as + as (h, w). + dtype (:obj:`dtype`): Dtype of priors. Defaults to torch.float32. + device (str | torch.device): The device where the anchors will be + put on. + with_stride (bool): Whether to concatenate the stride to + the last dimension of points. + + Return: + list[torch.Tensor]: Points of multiple feature levels. + The sizes of each tensor should be (N, 2) when with stride is + ``False``, where N = width * height, width and height + are the sizes of the corresponding feature level, + and the last dimension 2 represent (coord_x, coord_y), + otherwise the shape should be (N, 4), + and the last dimension 4 represent + (coord_x, coord_y, stride_w, stride_h). + """ + + assert self.num_levels == len(featmap_sizes) + multi_level_priors = [] + for i in range(self.num_levels): + priors = self.single_level_grid_priors( + featmap_sizes[i], + level_idx=i, + dtype=dtype, + device=device, + with_stride=with_stride) + multi_level_priors.append(priors) + return multi_level_priors + + def single_level_grid_priors(self, + featmap_size: Tuple[int], + level_idx: int, + dtype: torch.dtype = torch.float32, + device: DeviceType = 'cuda', + with_stride: bool = False) -> Tensor: + """Generate grid Points of a single level. + + Note: + This function is usually called by method ``self.grid_priors``. + + Args: + featmap_size (tuple[int]): Size of the feature maps, arrange as + (h, w). + level_idx (int): The index of corresponding feature map level. + dtype (:obj:`dtype`): Dtype of priors. Defaults to torch.float32. + device (str | torch.device): The device the tensor will be put on. + Defaults to 'cuda'. + with_stride (bool): Concatenate the stride to the last dimension + of points. + + Return: + Tensor: Points of single feature levels. + The shape of tensor should be (N, 2) when with stride is + ``False``, where N = width * height, width and height + are the sizes of the corresponding feature level, + and the last dimension 2 represent (coord_x, coord_y), + otherwise the shape should be (N, 4), + and the last dimension 4 represent + (coord_x, coord_y, stride_w, stride_h). + """ + feat_h, feat_w = featmap_size + stride_w, stride_h = self.strides[level_idx] + shift_x = (torch.arange(0, feat_w, device=device) + + self.offset) * stride_w + # keep featmap_size as Tensor instead of int, so that we + # can convert to ONNX correctly + shift_x = shift_x.to(dtype) + + shift_y = (torch.arange(0, feat_h, device=device) + + self.offset) * stride_h + # keep featmap_size as Tensor instead of int, so that we + # can convert to ONNX correctly + shift_y = shift_y.to(dtype) + shift_xx, shift_yy = self._meshgrid(shift_x, shift_y) + if not with_stride: + shifts = torch.stack([shift_xx, shift_yy], dim=-1) + else: + # use `shape[0]` instead of `len(shift_xx)` for ONNX export + stride_w = shift_xx.new_full((shift_xx.shape[0], ), + stride_w).to(dtype) + stride_h = shift_xx.new_full((shift_yy.shape[0], ), + stride_h).to(dtype) + shifts = torch.stack([shift_xx, shift_yy, stride_w, stride_h], + dim=-1) + all_points = shifts.to(device) + return all_points + + def valid_flags(self, + featmap_sizes: List[Tuple[int, int]], + pad_shape: Tuple[int], + device: DeviceType = 'cuda') -> List[Tensor]: + """Generate valid flags of points of multiple feature levels. + + Args: + featmap_sizes (list(tuple)): List of feature map sizes in + multiple feature levels, each size arrange as + as (h, w). + pad_shape (tuple(int)): The padded shape of the image, + arrange as (h, w). + device (str | torch.device): The device where the anchors will be + put on. + + Return: + list(torch.Tensor): Valid flags of points of multiple levels. + """ + assert self.num_levels == len(featmap_sizes) + multi_level_flags = [] + for i in range(self.num_levels): + point_stride = self.strides[i] + feat_h, feat_w = featmap_sizes[i] + h, w = pad_shape[:2] + valid_feat_h = min(int(np.ceil(h / point_stride[1])), feat_h) + valid_feat_w = min(int(np.ceil(w / point_stride[0])), feat_w) + flags = self.single_level_valid_flags((feat_h, feat_w), + (valid_feat_h, valid_feat_w), + device=device) + multi_level_flags.append(flags) + return multi_level_flags + + def single_level_valid_flags(self, + featmap_size: Tuple[int, int], + valid_size: Tuple[int, int], + device: DeviceType = 'cuda') -> Tensor: + """Generate the valid flags of points of a single feature map. + + Args: + featmap_size (tuple[int]): The size of feature maps, arrange as + as (h, w). + valid_size (tuple[int]): The valid size of the feature maps. + The size arrange as as (h, w). + device (str | torch.device): The device where the flags will be + put on. Defaults to 'cuda'. + + Returns: + torch.Tensor: The valid flags of each points in a single level \ + feature map. + """ + feat_h, feat_w = featmap_size + valid_h, valid_w = valid_size + assert valid_h <= feat_h and valid_w <= feat_w + valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device) + valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device) + valid_x[:valid_w] = 1 + valid_y[:valid_h] = 1 + valid_xx, valid_yy = self._meshgrid(valid_x, valid_y) + valid = valid_xx & valid_yy + return valid + + def sparse_priors(self, + prior_idxs: Tensor, + featmap_size: Tuple[int], + level_idx: int, + dtype: torch.dtype = torch.float32, + device: DeviceType = 'cuda') -> Tensor: + """Generate sparse points according to the ``prior_idxs``. + + Args: + prior_idxs (Tensor): The index of corresponding anchors + in the feature map. + featmap_size (tuple[int]): feature map size arrange as (w, h). + level_idx (int): The level index of corresponding feature + map. + dtype (obj:`torch.dtype`): Date type of points. Defaults to + ``torch.float32``. + device (str | torch.device): The device where the points is + located. + Returns: + Tensor: Anchor with shape (N, 2), N should be equal to + the length of ``prior_idxs``. And last dimension + 2 represent (coord_x, coord_y). + """ + height, width = featmap_size + x = (prior_idxs % width + self.offset) * self.strides[level_idx][0] + y = ((prior_idxs // width) % height + + self.offset) * self.strides[level_idx][1] + prioris = torch.stack([x, y], 1).to(dtype) + prioris = prioris.to(device) + return prioris diff --git a/mmdetection/mmdet/models/task_modules/prior_generators/utils.py b/mmdetection/mmdet/models/task_modules/prior_generators/utils.py new file mode 100644 index 00000000..3aa2dfd4 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/prior_generators/utils.py @@ -0,0 +1,70 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple + +import torch +from torch import Tensor + +from mmdet.structures.bbox import BaseBoxes + + +def anchor_inside_flags(flat_anchors: Tensor, + valid_flags: Tensor, + img_shape: Tuple[int], + allowed_border: int = 0) -> Tensor: + """Check whether the anchors are inside the border. + + Args: + flat_anchors (torch.Tensor): Flatten anchors, shape (n, 4). + valid_flags (torch.Tensor): An existing valid flags of anchors. + img_shape (tuple(int)): Shape of current image. + allowed_border (int): The border to allow the valid anchor. + Defaults to 0. + + Returns: + torch.Tensor: Flags indicating whether the anchors are inside a \ + valid range. + """ + img_h, img_w = img_shape[:2] + if allowed_border >= 0: + if isinstance(flat_anchors, BaseBoxes): + inside_flags = valid_flags & \ + flat_anchors.is_inside([img_h, img_w], + all_inside=True, + allowed_border=allowed_border) + else: + inside_flags = valid_flags & \ + (flat_anchors[:, 0] >= -allowed_border) & \ + (flat_anchors[:, 1] >= -allowed_border) & \ + (flat_anchors[:, 2] < img_w + allowed_border) & \ + (flat_anchors[:, 3] < img_h + allowed_border) + else: + inside_flags = valid_flags + return inside_flags + + +def calc_region(bbox: Tensor, + ratio: float, + featmap_size: Optional[Tuple] = None) -> Tuple[int]: + """Calculate a proportional bbox region. + + The bbox center are fixed and the new h' and w' is h * ratio and w * ratio. + + Args: + bbox (Tensor): Bboxes to calculate regions, shape (n, 4). + ratio (float): Ratio of the output region. + featmap_size (tuple, Optional): Feature map size in (height, width) + order used for clipping the boundary. Defaults to None. + + Returns: + tuple: x1, y1, x2, y2 + """ + x1 = torch.round((1 - ratio) * bbox[0] + ratio * bbox[2]).long() + y1 = torch.round((1 - ratio) * bbox[1] + ratio * bbox[3]).long() + x2 = torch.round(ratio * bbox[0] + (1 - ratio) * bbox[2]).long() + y2 = torch.round(ratio * bbox[1] + (1 - ratio) * bbox[3]).long() + if featmap_size is not None: + x1 = x1.clamp(min=0, max=featmap_size[1]) + y1 = y1.clamp(min=0, max=featmap_size[0]) + x2 = x2.clamp(min=0, max=featmap_size[1]) + y2 = y2.clamp(min=0, max=featmap_size[0]) + return (x1, y1, x2, y2) diff --git a/mmdetection/mmdet/models/task_modules/samplers/__init__.py b/mmdetection/mmdet/models/task_modules/samplers/__init__.py new file mode 100644 index 00000000..3782eb89 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/__init__.py @@ -0,0 +1,22 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_sampler import BaseSampler +from .combined_sampler import CombinedSampler +from .instance_balanced_pos_sampler import InstanceBalancedPosSampler +from .iou_balanced_neg_sampler import IoUBalancedNegSampler +from .mask_pseudo_sampler import MaskPseudoSampler +from .mask_sampling_result import MaskSamplingResult +from .multi_instance_random_sampler import MultiInsRandomSampler +from .multi_instance_sampling_result import MultiInstanceSamplingResult +from .ohem_sampler import OHEMSampler +from .pseudo_sampler import PseudoSampler +from .random_sampler import RandomSampler +from .sampling_result import SamplingResult +from .score_hlr_sampler import ScoreHLRSampler + +__all__ = [ + 'BaseSampler', 'PseudoSampler', 'RandomSampler', + 'InstanceBalancedPosSampler', 'IoUBalancedNegSampler', 'CombinedSampler', + 'OHEMSampler', 'SamplingResult', 'ScoreHLRSampler', 'MaskPseudoSampler', + 'MaskSamplingResult', 'MultiInstanceSamplingResult', + 'MultiInsRandomSampler' +] diff --git a/mmdetection/mmdet/models/task_modules/samplers/base_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/base_sampler.py new file mode 100644 index 00000000..be8a9a5e --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/base_sampler.py @@ -0,0 +1,136 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod + +import torch +from mmengine.structures import InstanceData + +from mmdet.structures.bbox import BaseBoxes, cat_boxes +from ..assigners import AssignResult +from .sampling_result import SamplingResult + + +class BaseSampler(metaclass=ABCMeta): + """Base class of samplers. + + Args: + num (int): Number of samples + pos_fraction (float): Fraction of positive samples + neg_pos_up (int): Upper bound number of negative and + positive samples. Defaults to -1. + add_gt_as_proposals (bool): Whether to add ground truth + boxes as proposals. Defaults to True. + """ + + def __init__(self, + num: int, + pos_fraction: float, + neg_pos_ub: int = -1, + add_gt_as_proposals: bool = True, + **kwargs) -> None: + self.num = num + self.pos_fraction = pos_fraction + self.neg_pos_ub = neg_pos_ub + self.add_gt_as_proposals = add_gt_as_proposals + self.pos_sampler = self + self.neg_sampler = self + + @abstractmethod + def _sample_pos(self, assign_result: AssignResult, num_expected: int, + **kwargs): + """Sample positive samples.""" + pass + + @abstractmethod + def _sample_neg(self, assign_result: AssignResult, num_expected: int, + **kwargs): + """Sample negative samples.""" + pass + + def sample(self, assign_result: AssignResult, pred_instances: InstanceData, + gt_instances: InstanceData, **kwargs) -> SamplingResult: + """Sample positive and negative bboxes. + + This is a simple implementation of bbox sampling given candidates, + assigning results and ground truth bboxes. + + Args: + assign_result (:obj:`AssignResult`): Assigning results. + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + + Returns: + :obj:`SamplingResult`: Sampling result. + + Example: + >>> from mmengine.structures import InstanceData + >>> from mmdet.models.task_modules.samplers import RandomSampler, + >>> from mmdet.models.task_modules.assigners import AssignResult + >>> from mmdet.models.task_modules.samplers. + ... sampling_result import ensure_rng, random_boxes + >>> rng = ensure_rng(None) + >>> assign_result = AssignResult.random(rng=rng) + >>> pred_instances = InstanceData() + >>> pred_instances.priors = random_boxes(assign_result.num_preds, + ... rng=rng) + >>> gt_instances = InstanceData() + >>> gt_instances.bboxes = random_boxes(assign_result.num_gts, + ... rng=rng) + >>> gt_instances.labels = torch.randint( + ... 0, 5, (assign_result.num_gts,), dtype=torch.long) + >>> self = RandomSampler(num=32, pos_fraction=0.5, neg_pos_ub=-1, + >>> add_gt_as_proposals=False) + >>> self = self.sample(assign_result, pred_instances, gt_instances) + """ + gt_bboxes = gt_instances.bboxes + priors = pred_instances.priors + gt_labels = gt_instances.labels + if len(priors.shape) < 2: + priors = priors[None, :] + + gt_flags = priors.new_zeros((priors.shape[0], ), dtype=torch.uint8) + if self.add_gt_as_proposals and len(gt_bboxes) > 0: + # When `gt_bboxes` and `priors` are all box type, convert + # `gt_bboxes` type to `priors` type. + if (isinstance(gt_bboxes, BaseBoxes) + and isinstance(priors, BaseBoxes)): + gt_bboxes_ = gt_bboxes.convert_to(type(priors)) + else: + gt_bboxes_ = gt_bboxes + priors = cat_boxes([gt_bboxes_, priors], dim=0) + assign_result.add_gt_(gt_labels) + gt_ones = priors.new_ones(gt_bboxes_.shape[0], dtype=torch.uint8) + gt_flags = torch.cat([gt_ones, gt_flags]) + + num_expected_pos = int(self.num * self.pos_fraction) + pos_inds = self.pos_sampler._sample_pos( + assign_result, num_expected_pos, bboxes=priors, **kwargs) + # We found that sampled indices have duplicated items occasionally. + # (may be a bug of PyTorch) + pos_inds = pos_inds.unique() + num_sampled_pos = pos_inds.numel() + num_expected_neg = self.num - num_sampled_pos + if self.neg_pos_ub >= 0: + _pos = max(1, num_sampled_pos) + neg_upper_bound = int(self.neg_pos_ub * _pos) + if num_expected_neg > neg_upper_bound: + num_expected_neg = neg_upper_bound + neg_inds = self.neg_sampler._sample_neg( + assign_result, num_expected_neg, bboxes=priors, **kwargs) + neg_inds = neg_inds.unique() + + sampling_result = SamplingResult( + pos_inds=pos_inds, + neg_inds=neg_inds, + priors=priors, + gt_bboxes=gt_bboxes, + assign_result=assign_result, + gt_flags=gt_flags) + return sampling_result diff --git a/mmdetection/mmdet/models/task_modules/samplers/combined_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/combined_sampler.py new file mode 100644 index 00000000..8e0560e3 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/combined_sampler.py @@ -0,0 +1,21 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.registry import TASK_UTILS +from .base_sampler import BaseSampler + + +@TASK_UTILS.register_module() +class CombinedSampler(BaseSampler): + """A sampler that combines positive sampler and negative sampler.""" + + def __init__(self, pos_sampler, neg_sampler, **kwargs): + super(CombinedSampler, self).__init__(**kwargs) + self.pos_sampler = TASK_UTILS.build(pos_sampler, default_args=kwargs) + self.neg_sampler = TASK_UTILS.build(neg_sampler, default_args=kwargs) + + def _sample_pos(self, **kwargs): + """Sample positive samples.""" + raise NotImplementedError + + def _sample_neg(self, **kwargs): + """Sample negative samples.""" + raise NotImplementedError diff --git a/mmdetection/mmdet/models/task_modules/samplers/instance_balanced_pos_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/instance_balanced_pos_sampler.py new file mode 100644 index 00000000..e48d8e91 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/instance_balanced_pos_sampler.py @@ -0,0 +1,56 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np +import torch + +from mmdet.registry import TASK_UTILS +from .random_sampler import RandomSampler + + +@TASK_UTILS.register_module() +class InstanceBalancedPosSampler(RandomSampler): + """Instance balanced sampler that samples equal number of positive samples + for each instance.""" + + def _sample_pos(self, assign_result, num_expected, **kwargs): + """Sample positive boxes. + + Args: + assign_result (:obj:`AssignResult`): The assigned results of boxes. + num_expected (int): The number of expected positive samples + + Returns: + Tensor or ndarray: sampled indices. + """ + pos_inds = torch.nonzero(assign_result.gt_inds > 0, as_tuple=False) + if pos_inds.numel() != 0: + pos_inds = pos_inds.squeeze(1) + if pos_inds.numel() <= num_expected: + return pos_inds + else: + unique_gt_inds = assign_result.gt_inds[pos_inds].unique() + num_gts = len(unique_gt_inds) + num_per_gt = int(round(num_expected / float(num_gts)) + 1) + sampled_inds = [] + for i in unique_gt_inds: + inds = torch.nonzero( + assign_result.gt_inds == i.item(), as_tuple=False) + if inds.numel() != 0: + inds = inds.squeeze(1) + else: + continue + if len(inds) > num_per_gt: + inds = self.random_choice(inds, num_per_gt) + sampled_inds.append(inds) + sampled_inds = torch.cat(sampled_inds) + if len(sampled_inds) < num_expected: + num_extra = num_expected - len(sampled_inds) + extra_inds = np.array( + list(set(pos_inds.cpu()) - set(sampled_inds.cpu()))) + if len(extra_inds) > num_extra: + extra_inds = self.random_choice(extra_inds, num_extra) + extra_inds = torch.from_numpy(extra_inds).to( + assign_result.gt_inds.device).long() + sampled_inds = torch.cat([sampled_inds, extra_inds]) + elif len(sampled_inds) > num_expected: + sampled_inds = self.random_choice(sampled_inds, num_expected) + return sampled_inds diff --git a/mmdetection/mmdet/models/task_modules/samplers/iou_balanced_neg_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/iou_balanced_neg_sampler.py new file mode 100644 index 00000000..dc1f4641 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/iou_balanced_neg_sampler.py @@ -0,0 +1,158 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np +import torch + +from mmdet.registry import TASK_UTILS +from .random_sampler import RandomSampler + + +@TASK_UTILS.register_module() +class IoUBalancedNegSampler(RandomSampler): + """IoU Balanced Sampling. + + arXiv: https://arxiv.org/pdf/1904.02701.pdf (CVPR 2019) + + Sampling proposals according to their IoU. `floor_fraction` of needed RoIs + are sampled from proposals whose IoU are lower than `floor_thr` randomly. + The others are sampled from proposals whose IoU are higher than + `floor_thr`. These proposals are sampled from some bins evenly, which are + split by `num_bins` via IoU evenly. + + Args: + num (int): number of proposals. + pos_fraction (float): fraction of positive proposals. + floor_thr (float): threshold (minimum) IoU for IoU balanced sampling, + set to -1 if all using IoU balanced sampling. + floor_fraction (float): sampling fraction of proposals under floor_thr. + num_bins (int): number of bins in IoU balanced sampling. + """ + + def __init__(self, + num, + pos_fraction, + floor_thr=-1, + floor_fraction=0, + num_bins=3, + **kwargs): + super(IoUBalancedNegSampler, self).__init__(num, pos_fraction, + **kwargs) + assert floor_thr >= 0 or floor_thr == -1 + assert 0 <= floor_fraction <= 1 + assert num_bins >= 1 + + self.floor_thr = floor_thr + self.floor_fraction = floor_fraction + self.num_bins = num_bins + + def sample_via_interval(self, max_overlaps, full_set, num_expected): + """Sample according to the iou interval. + + Args: + max_overlaps (torch.Tensor): IoU between bounding boxes and ground + truth boxes. + full_set (set(int)): A full set of indices of boxes。 + num_expected (int): Number of expected samples。 + + Returns: + np.ndarray: Indices of samples + """ + max_iou = max_overlaps.max() + iou_interval = (max_iou - self.floor_thr) / self.num_bins + per_num_expected = int(num_expected / self.num_bins) + + sampled_inds = [] + for i in range(self.num_bins): + start_iou = self.floor_thr + i * iou_interval + end_iou = self.floor_thr + (i + 1) * iou_interval + tmp_set = set( + np.where( + np.logical_and(max_overlaps >= start_iou, + max_overlaps < end_iou))[0]) + tmp_inds = list(tmp_set & full_set) + if len(tmp_inds) > per_num_expected: + tmp_sampled_set = self.random_choice(tmp_inds, + per_num_expected) + else: + tmp_sampled_set = np.array(tmp_inds, dtype=np.int64) + sampled_inds.append(tmp_sampled_set) + + sampled_inds = np.concatenate(sampled_inds) + if len(sampled_inds) < num_expected: + num_extra = num_expected - len(sampled_inds) + extra_inds = np.array(list(full_set - set(sampled_inds))) + if len(extra_inds) > num_extra: + extra_inds = self.random_choice(extra_inds, num_extra) + sampled_inds = np.concatenate([sampled_inds, extra_inds]) + + return sampled_inds + + def _sample_neg(self, assign_result, num_expected, **kwargs): + """Sample negative boxes. + + Args: + assign_result (:obj:`AssignResult`): The assigned results of boxes. + num_expected (int): The number of expected negative samples + + Returns: + Tensor or ndarray: sampled indices. + """ + neg_inds = torch.nonzero(assign_result.gt_inds == 0, as_tuple=False) + if neg_inds.numel() != 0: + neg_inds = neg_inds.squeeze(1) + if len(neg_inds) <= num_expected: + return neg_inds + else: + max_overlaps = assign_result.max_overlaps.cpu().numpy() + # balance sampling for negative samples + neg_set = set(neg_inds.cpu().numpy()) + + if self.floor_thr > 0: + floor_set = set( + np.where( + np.logical_and(max_overlaps >= 0, + max_overlaps < self.floor_thr))[0]) + iou_sampling_set = set( + np.where(max_overlaps >= self.floor_thr)[0]) + elif self.floor_thr == 0: + floor_set = set(np.where(max_overlaps == 0)[0]) + iou_sampling_set = set( + np.where(max_overlaps > self.floor_thr)[0]) + else: + floor_set = set() + iou_sampling_set = set( + np.where(max_overlaps > self.floor_thr)[0]) + # for sampling interval calculation + self.floor_thr = 0 + + floor_neg_inds = list(floor_set & neg_set) + iou_sampling_neg_inds = list(iou_sampling_set & neg_set) + num_expected_iou_sampling = int(num_expected * + (1 - self.floor_fraction)) + if len(iou_sampling_neg_inds) > num_expected_iou_sampling: + if self.num_bins >= 2: + iou_sampled_inds = self.sample_via_interval( + max_overlaps, set(iou_sampling_neg_inds), + num_expected_iou_sampling) + else: + iou_sampled_inds = self.random_choice( + iou_sampling_neg_inds, num_expected_iou_sampling) + else: + iou_sampled_inds = np.array( + iou_sampling_neg_inds, dtype=np.int64) + num_expected_floor = num_expected - len(iou_sampled_inds) + if len(floor_neg_inds) > num_expected_floor: + sampled_floor_inds = self.random_choice( + floor_neg_inds, num_expected_floor) + else: + sampled_floor_inds = np.array(floor_neg_inds, dtype=np.int64) + sampled_inds = np.concatenate( + (sampled_floor_inds, iou_sampled_inds)) + if len(sampled_inds) < num_expected: + num_extra = num_expected - len(sampled_inds) + extra_inds = np.array(list(neg_set - set(sampled_inds))) + if len(extra_inds) > num_extra: + extra_inds = self.random_choice(extra_inds, num_extra) + sampled_inds = np.concatenate((sampled_inds, extra_inds)) + sampled_inds = torch.from_numpy(sampled_inds).long().to( + assign_result.gt_inds.device) + return sampled_inds diff --git a/mmdetection/mmdet/models/task_modules/samplers/mask_pseudo_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/mask_pseudo_sampler.py new file mode 100644 index 00000000..307dd5d1 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/mask_pseudo_sampler.py @@ -0,0 +1,60 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""copy from +https://github.com/ZwwWayne/K-Net/blob/main/knet/det/mask_pseudo_sampler.py.""" + +import torch +from mmengine.structures import InstanceData + +from mmdet.registry import TASK_UTILS +from ..assigners import AssignResult +from .base_sampler import BaseSampler +from .mask_sampling_result import MaskSamplingResult + + +@TASK_UTILS.register_module() +class MaskPseudoSampler(BaseSampler): + """A pseudo sampler that does not do sampling actually.""" + + def __init__(self, **kwargs): + pass + + def _sample_pos(self, **kwargs): + """Sample positive samples.""" + raise NotImplementedError + + def _sample_neg(self, **kwargs): + """Sample negative samples.""" + raise NotImplementedError + + def sample(self, assign_result: AssignResult, pred_instances: InstanceData, + gt_instances: InstanceData, *args, **kwargs): + """Directly returns the positive and negative indices of samples. + + Args: + assign_result (:obj:`AssignResult`): Mask assigning results. + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``scores`` and ``masks`` predicted + by the model. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``labels`` and ``masks`` + attributes. + + Returns: + :obj:`SamplingResult`: sampler results + """ + pred_masks = pred_instances.masks + gt_masks = gt_instances.masks + pos_inds = torch.nonzero( + assign_result.gt_inds > 0, as_tuple=False).squeeze(-1).unique() + neg_inds = torch.nonzero( + assign_result.gt_inds == 0, as_tuple=False).squeeze(-1).unique() + gt_flags = pred_masks.new_zeros(pred_masks.shape[0], dtype=torch.uint8) + sampling_result = MaskSamplingResult( + pos_inds=pos_inds, + neg_inds=neg_inds, + masks=pred_masks, + gt_masks=gt_masks, + assign_result=assign_result, + gt_flags=gt_flags, + avg_factor_with_neg=False) + return sampling_result diff --git a/mmdetection/mmdet/models/task_modules/samplers/mask_sampling_result.py b/mmdetection/mmdet/models/task_modules/samplers/mask_sampling_result.py new file mode 100644 index 00000000..adaa62e8 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/mask_sampling_result.py @@ -0,0 +1,68 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""copy from +https://github.com/ZwwWayne/K-Net/blob/main/knet/det/mask_pseudo_sampler.py.""" + +import torch +from torch import Tensor + +from ..assigners import AssignResult +from .sampling_result import SamplingResult + + +class MaskSamplingResult(SamplingResult): + """Mask sampling result.""" + + def __init__(self, + pos_inds: Tensor, + neg_inds: Tensor, + masks: Tensor, + gt_masks: Tensor, + assign_result: AssignResult, + gt_flags: Tensor, + avg_factor_with_neg: bool = True) -> None: + self.pos_inds = pos_inds + self.neg_inds = neg_inds + self.num_pos = max(pos_inds.numel(), 1) + self.num_neg = max(neg_inds.numel(), 1) + self.avg_factor = self.num_pos + self.num_neg \ + if avg_factor_with_neg else self.num_pos + + self.pos_masks = masks[pos_inds] + self.neg_masks = masks[neg_inds] + self.pos_is_gt = gt_flags[pos_inds] + + self.num_gts = gt_masks.shape[0] + self.pos_assigned_gt_inds = assign_result.gt_inds[pos_inds] - 1 + + if gt_masks.numel() == 0: + # hack for index error case + assert self.pos_assigned_gt_inds.numel() == 0 + self.pos_gt_masks = torch.empty_like(gt_masks) + else: + self.pos_gt_masks = gt_masks[self.pos_assigned_gt_inds, :] + + @property + def masks(self) -> Tensor: + """torch.Tensor: concatenated positive and negative masks.""" + return torch.cat([self.pos_masks, self.neg_masks]) + + def __nice__(self) -> str: + data = self.info.copy() + data['pos_masks'] = data.pop('pos_masks').shape + data['neg_masks'] = data.pop('neg_masks').shape + parts = [f"'{k}': {v!r}" for k, v in sorted(data.items())] + body = ' ' + ',\n '.join(parts) + return '{\n' + body + '\n}' + + @property + def info(self) -> dict: + """Returns a dictionary of info about the object.""" + return { + 'pos_inds': self.pos_inds, + 'neg_inds': self.neg_inds, + 'pos_masks': self.pos_masks, + 'neg_masks': self.neg_masks, + 'pos_is_gt': self.pos_is_gt, + 'num_gts': self.num_gts, + 'pos_assigned_gt_inds': self.pos_assigned_gt_inds, + } diff --git a/mmdetection/mmdet/models/task_modules/samplers/multi_instance_random_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/multi_instance_random_sampler.py new file mode 100644 index 00000000..8b74054e --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/multi_instance_random_sampler.py @@ -0,0 +1,130 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Union + +import torch +from mmengine.structures import InstanceData +from numpy import ndarray +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from ..assigners import AssignResult +from .multi_instance_sampling_result import MultiInstanceSamplingResult +from .random_sampler import RandomSampler + + +@TASK_UTILS.register_module() +class MultiInsRandomSampler(RandomSampler): + """Random sampler for multi instance. + + Note: + Multi-instance means to predict multiple detection boxes with + one proposal box. `AssignResult` may assign multiple gt boxes + to each proposal box, in this case `RandomSampler` should be + replaced by `MultiInsRandomSampler` + """ + + def _sample_pos(self, assign_result: AssignResult, num_expected: int, + **kwargs) -> Union[Tensor, ndarray]: + """Randomly sample some positive samples. + + Args: + assign_result (:obj:`AssignResult`): Bbox assigning results. + num_expected (int): The number of expected positive samples + + Returns: + Tensor or ndarray: sampled indices. + """ + pos_inds = torch.nonzero( + assign_result.labels[:, 0] > 0, as_tuple=False) + if pos_inds.numel() != 0: + pos_inds = pos_inds.squeeze(1) + if pos_inds.numel() <= num_expected: + return pos_inds + else: + return self.random_choice(pos_inds, num_expected) + + def _sample_neg(self, assign_result: AssignResult, num_expected: int, + **kwargs) -> Union[Tensor, ndarray]: + """Randomly sample some negative samples. + + Args: + assign_result (:obj:`AssignResult`): Bbox assigning results. + num_expected (int): The number of expected positive samples + + Returns: + Tensor or ndarray: sampled indices. + """ + neg_inds = torch.nonzero( + assign_result.labels[:, 0] == 0, as_tuple=False) + if neg_inds.numel() != 0: + neg_inds = neg_inds.squeeze(1) + if len(neg_inds) <= num_expected: + return neg_inds + else: + return self.random_choice(neg_inds, num_expected) + + def sample(self, assign_result: AssignResult, pred_instances: InstanceData, + gt_instances: InstanceData, + **kwargs) -> MultiInstanceSamplingResult: + """Sample positive and negative bboxes. + + Args: + assign_result (:obj:`AssignResult`): Assigning results from + MultiInstanceAssigner. + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + + Returns: + :obj:`MultiInstanceSamplingResult`: Sampling result. + """ + + assert 'batch_gt_instances_ignore' in kwargs, \ + 'batch_gt_instances_ignore is necessary for MultiInsRandomSampler' + + gt_bboxes = gt_instances.bboxes + ignore_bboxes = kwargs['batch_gt_instances_ignore'].bboxes + gt_and_ignore_bboxes = torch.cat([gt_bboxes, ignore_bboxes], dim=0) + priors = pred_instances.priors + if len(priors.shape) < 2: + priors = priors[None, :] + priors = priors[:, :4] + + gt_flags = priors.new_zeros((priors.shape[0], ), dtype=torch.uint8) + priors = torch.cat([priors, gt_and_ignore_bboxes], dim=0) + gt_ones = priors.new_ones( + gt_and_ignore_bboxes.shape[0], dtype=torch.uint8) + gt_flags = torch.cat([gt_flags, gt_ones]) + + num_expected_pos = int(self.num * self.pos_fraction) + pos_inds = self.pos_sampler._sample_pos(assign_result, + num_expected_pos) + # We found that sampled indices have duplicated items occasionally. + # (may be a bug of PyTorch) + pos_inds = pos_inds.unique() + num_sampled_pos = pos_inds.numel() + num_expected_neg = self.num - num_sampled_pos + if self.neg_pos_ub >= 0: + _pos = max(1, num_sampled_pos) + neg_upper_bound = int(self.neg_pos_ub * _pos) + if num_expected_neg > neg_upper_bound: + num_expected_neg = neg_upper_bound + neg_inds = self.neg_sampler._sample_neg(assign_result, + num_expected_neg) + neg_inds = neg_inds.unique() + + sampling_result = MultiInstanceSamplingResult( + pos_inds=pos_inds, + neg_inds=neg_inds, + priors=priors, + gt_and_ignore_bboxes=gt_and_ignore_bboxes, + assign_result=assign_result, + gt_flags=gt_flags) + return sampling_result diff --git a/mmdetection/mmdet/models/task_modules/samplers/multi_instance_sampling_result.py b/mmdetection/mmdet/models/task_modules/samplers/multi_instance_sampling_result.py new file mode 100644 index 00000000..438a0aa9 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/multi_instance_sampling_result.py @@ -0,0 +1,56 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import Tensor + +from ..assigners import AssignResult +from .sampling_result import SamplingResult + + +class MultiInstanceSamplingResult(SamplingResult): + """Bbox sampling result. Further encapsulation of SamplingResult. Three + attributes neg_assigned_gt_inds, neg_gt_labels, and neg_gt_bboxes have been + added for SamplingResult. + + Args: + pos_inds (Tensor): Indices of positive samples. + neg_inds (Tensor): Indices of negative samples. + priors (Tensor): The priors can be anchors or points, + or the bboxes predicted by the previous stage. + gt_and_ignore_bboxes (Tensor): Ground truth and ignore bboxes. + assign_result (:obj:`AssignResult`): Assigning results. + gt_flags (Tensor): The Ground truth flags. + avg_factor_with_neg (bool): If True, ``avg_factor`` equal to + the number of total priors; Otherwise, it is the number of + positive priors. Defaults to True. + """ + + def __init__(self, + pos_inds: Tensor, + neg_inds: Tensor, + priors: Tensor, + gt_and_ignore_bboxes: Tensor, + assign_result: AssignResult, + gt_flags: Tensor, + avg_factor_with_neg: bool = True) -> None: + self.neg_assigned_gt_inds = assign_result.gt_inds[neg_inds] + self.neg_gt_labels = assign_result.labels[neg_inds] + + if gt_and_ignore_bboxes.numel() == 0: + self.neg_gt_bboxes = torch.empty_like(gt_and_ignore_bboxes).view( + -1, 4) + else: + if len(gt_and_ignore_bboxes.shape) < 2: + gt_and_ignore_bboxes = gt_and_ignore_bboxes.view(-1, 4) + self.neg_gt_bboxes = gt_and_ignore_bboxes[ + self.neg_assigned_gt_inds.long(), :] + + # To resist the minus 1 operation in `SamplingResult.init()`. + assign_result.gt_inds += 1 + super().__init__( + pos_inds=pos_inds, + neg_inds=neg_inds, + priors=priors, + gt_bboxes=gt_and_ignore_bboxes, + assign_result=assign_result, + gt_flags=gt_flags, + avg_factor_with_neg=avg_factor_with_neg) diff --git a/mmdetection/mmdet/models/task_modules/samplers/ohem_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/ohem_sampler.py new file mode 100644 index 00000000..f478a448 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/ohem_sampler.py @@ -0,0 +1,111 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import bbox2roi +from .base_sampler import BaseSampler + + +@TASK_UTILS.register_module() +class OHEMSampler(BaseSampler): + r"""Online Hard Example Mining Sampler described in `Training Region-based + Object Detectors with Online Hard Example Mining + `_. + """ + + def __init__(self, + num, + pos_fraction, + context, + neg_pos_ub=-1, + add_gt_as_proposals=True, + loss_key='loss_cls', + **kwargs): + super(OHEMSampler, self).__init__(num, pos_fraction, neg_pos_ub, + add_gt_as_proposals) + self.context = context + if not hasattr(self.context, 'num_stages'): + self.bbox_head = self.context.bbox_head + else: + self.bbox_head = self.context.bbox_head[self.context.current_stage] + + self.loss_key = loss_key + + def hard_mining(self, inds, num_expected, bboxes, labels, feats): + with torch.no_grad(): + rois = bbox2roi([bboxes]) + if not hasattr(self.context, 'num_stages'): + bbox_results = self.context._bbox_forward(feats, rois) + else: + bbox_results = self.context._bbox_forward( + self.context.current_stage, feats, rois) + cls_score = bbox_results['cls_score'] + loss = self.bbox_head.loss( + cls_score=cls_score, + bbox_pred=None, + rois=rois, + labels=labels, + label_weights=cls_score.new_ones(cls_score.size(0)), + bbox_targets=None, + bbox_weights=None, + reduction_override='none')[self.loss_key] + _, topk_loss_inds = loss.topk(num_expected) + return inds[topk_loss_inds] + + def _sample_pos(self, + assign_result, + num_expected, + bboxes=None, + feats=None, + **kwargs): + """Sample positive boxes. + + Args: + assign_result (:obj:`AssignResult`): Assigned results + num_expected (int): Number of expected positive samples + bboxes (torch.Tensor, optional): Boxes. Defaults to None. + feats (list[torch.Tensor], optional): Multi-level features. + Defaults to None. + + Returns: + torch.Tensor: Indices of positive samples + """ + # Sample some hard positive samples + pos_inds = torch.nonzero(assign_result.gt_inds > 0, as_tuple=False) + if pos_inds.numel() != 0: + pos_inds = pos_inds.squeeze(1) + if pos_inds.numel() <= num_expected: + return pos_inds + else: + return self.hard_mining(pos_inds, num_expected, bboxes[pos_inds], + assign_result.labels[pos_inds], feats) + + def _sample_neg(self, + assign_result, + num_expected, + bboxes=None, + feats=None, + **kwargs): + """Sample negative boxes. + + Args: + assign_result (:obj:`AssignResult`): Assigned results + num_expected (int): Number of expected negative samples + bboxes (torch.Tensor, optional): Boxes. Defaults to None. + feats (list[torch.Tensor], optional): Multi-level features. + Defaults to None. + + Returns: + torch.Tensor: Indices of negative samples + """ + # Sample some hard negative samples + neg_inds = torch.nonzero(assign_result.gt_inds == 0, as_tuple=False) + if neg_inds.numel() != 0: + neg_inds = neg_inds.squeeze(1) + if len(neg_inds) <= num_expected: + return neg_inds + else: + neg_labels = assign_result.labels.new_empty( + neg_inds.size(0)).fill_(self.bbox_head.num_classes) + return self.hard_mining(neg_inds, num_expected, bboxes[neg_inds], + neg_labels, feats) diff --git a/mmdetection/mmdet/models/task_modules/samplers/pseudo_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/pseudo_sampler.py new file mode 100644 index 00000000..a8186cc3 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/pseudo_sampler.py @@ -0,0 +1,60 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmengine.structures import InstanceData + +from mmdet.registry import TASK_UTILS +from ..assigners import AssignResult +from .base_sampler import BaseSampler +from .sampling_result import SamplingResult + + +@TASK_UTILS.register_module() +class PseudoSampler(BaseSampler): + """A pseudo sampler that does not do sampling actually.""" + + def __init__(self, **kwargs): + pass + + def _sample_pos(self, **kwargs): + """Sample positive samples.""" + raise NotImplementedError + + def _sample_neg(self, **kwargs): + """Sample negative samples.""" + raise NotImplementedError + + def sample(self, assign_result: AssignResult, pred_instances: InstanceData, + gt_instances: InstanceData, *args, **kwargs): + """Directly returns the positive and negative indices of samples. + + Args: + assign_result (:obj:`AssignResult`): Bbox assigning results. + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors, points, or bboxes predicted by the model, + shape(n, 4). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes`` and ``labels`` + attributes. + + Returns: + :obj:`SamplingResult`: sampler results + """ + gt_bboxes = gt_instances.bboxes + priors = pred_instances.priors + + pos_inds = torch.nonzero( + assign_result.gt_inds > 0, as_tuple=False).squeeze(-1).unique() + neg_inds = torch.nonzero( + assign_result.gt_inds == 0, as_tuple=False).squeeze(-1).unique() + + gt_flags = priors.new_zeros(priors.shape[0], dtype=torch.uint8) + sampling_result = SamplingResult( + pos_inds=pos_inds, + neg_inds=neg_inds, + priors=priors, + gt_bboxes=gt_bboxes, + assign_result=assign_result, + gt_flags=gt_flags, + avg_factor_with_neg=False) + return sampling_result diff --git a/mmdetection/mmdet/models/task_modules/samplers/random_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/random_sampler.py new file mode 100644 index 00000000..fa03665f --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/random_sampler.py @@ -0,0 +1,109 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Union + +import torch +from numpy import ndarray +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from ..assigners import AssignResult +from .base_sampler import BaseSampler + + +@TASK_UTILS.register_module() +class RandomSampler(BaseSampler): + """Random sampler. + + Args: + num (int): Number of samples + pos_fraction (float): Fraction of positive samples + neg_pos_up (int): Upper bound number of negative and + positive samples. Defaults to -1. + add_gt_as_proposals (bool): Whether to add ground truth + boxes as proposals. Defaults to True. + """ + + def __init__(self, + num: int, + pos_fraction: float, + neg_pos_ub: int = -1, + add_gt_as_proposals: bool = True, + **kwargs): + from .sampling_result import ensure_rng + super().__init__( + num=num, + pos_fraction=pos_fraction, + neg_pos_ub=neg_pos_ub, + add_gt_as_proposals=add_gt_as_proposals) + self.rng = ensure_rng(kwargs.get('rng', None)) + + def random_choice(self, gallery: Union[Tensor, ndarray, list], + num: int) -> Union[Tensor, ndarray]: + """Random select some elements from the gallery. + + If `gallery` is a Tensor, the returned indices will be a Tensor; + If `gallery` is a ndarray or list, the returned indices will be a + ndarray. + + Args: + gallery (Tensor | ndarray | list): indices pool. + num (int): expected sample num. + + Returns: + Tensor or ndarray: sampled indices. + """ + assert len(gallery) >= num + + is_tensor = isinstance(gallery, torch.Tensor) + if not is_tensor: + if torch.cuda.is_available(): + device = torch.cuda.current_device() + else: + device = 'cpu' + gallery = torch.tensor(gallery, dtype=torch.long, device=device) + # This is a temporary fix. We can revert the following code + # when PyTorch fixes the abnormal return of torch.randperm. + # See: https://github.com/open-mmlab/mmdetection/pull/5014 + perm = torch.randperm(gallery.numel())[:num].to(device=gallery.device) + rand_inds = gallery[perm] + if not is_tensor: + rand_inds = rand_inds.cpu().numpy() + return rand_inds + + def _sample_pos(self, assign_result: AssignResult, num_expected: int, + **kwargs) -> Union[Tensor, ndarray]: + """Randomly sample some positive samples. + + Args: + assign_result (:obj:`AssignResult`): Bbox assigning results. + num_expected (int): The number of expected positive samples + + Returns: + Tensor or ndarray: sampled indices. + """ + pos_inds = torch.nonzero(assign_result.gt_inds > 0, as_tuple=False) + if pos_inds.numel() != 0: + pos_inds = pos_inds.squeeze(1) + if pos_inds.numel() <= num_expected: + return pos_inds + else: + return self.random_choice(pos_inds, num_expected) + + def _sample_neg(self, assign_result: AssignResult, num_expected: int, + **kwargs) -> Union[Tensor, ndarray]: + """Randomly sample some negative samples. + + Args: + assign_result (:obj:`AssignResult`): Bbox assigning results. + num_expected (int): The number of expected positive samples + + Returns: + Tensor or ndarray: sampled indices. + """ + neg_inds = torch.nonzero(assign_result.gt_inds == 0, as_tuple=False) + if neg_inds.numel() != 0: + neg_inds = neg_inds.squeeze(1) + if len(neg_inds) <= num_expected: + return neg_inds + else: + return self.random_choice(neg_inds, num_expected) diff --git a/mmdetection/mmdet/models/task_modules/samplers/sampling_result.py b/mmdetection/mmdet/models/task_modules/samplers/sampling_result.py new file mode 100644 index 00000000..cb510ee6 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/sampling_result.py @@ -0,0 +1,240 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import numpy as np +import torch +from torch import Tensor + +from mmdet.structures.bbox import BaseBoxes, cat_boxes +from mmdet.utils import util_mixins +from mmdet.utils.util_random import ensure_rng +from ..assigners import AssignResult + + +def random_boxes(num=1, scale=1, rng=None): + """Simple version of ``kwimage.Boxes.random`` + + Returns: + Tensor: shape (n, 4) in x1, y1, x2, y2 format. + + References: + https://gitlab.kitware.com/computer-vision/kwimage/blob/master/kwimage/structs/boxes.py#L1390 + + Example: + >>> num = 3 + >>> scale = 512 + >>> rng = 0 + >>> boxes = random_boxes(num, scale, rng) + >>> print(boxes) + tensor([[280.9925, 278.9802, 308.6148, 366.1769], + [216.9113, 330.6978, 224.0446, 456.5878], + [405.3632, 196.3221, 493.3953, 270.7942]]) + """ + rng = ensure_rng(rng) + + tlbr = rng.rand(num, 4).astype(np.float32) + + tl_x = np.minimum(tlbr[:, 0], tlbr[:, 2]) + tl_y = np.minimum(tlbr[:, 1], tlbr[:, 3]) + br_x = np.maximum(tlbr[:, 0], tlbr[:, 2]) + br_y = np.maximum(tlbr[:, 1], tlbr[:, 3]) + + tlbr[:, 0] = tl_x * scale + tlbr[:, 1] = tl_y * scale + tlbr[:, 2] = br_x * scale + tlbr[:, 3] = br_y * scale + + boxes = torch.from_numpy(tlbr) + return boxes + + +class SamplingResult(util_mixins.NiceRepr): + """Bbox sampling result. + + Args: + pos_inds (Tensor): Indices of positive samples. + neg_inds (Tensor): Indices of negative samples. + priors (Tensor): The priors can be anchors or points, + or the bboxes predicted by the previous stage. + gt_bboxes (Tensor): Ground truth of bboxes. + assign_result (:obj:`AssignResult`): Assigning results. + gt_flags (Tensor): The Ground truth flags. + avg_factor_with_neg (bool): If True, ``avg_factor`` equal to + the number of total priors; Otherwise, it is the number of + positive priors. Defaults to True. + + Example: + >>> # xdoctest: +IGNORE_WANT + >>> from mmdet.models.task_modules.samplers.sampling_result import * # NOQA + >>> self = SamplingResult.random(rng=10) + >>> print(f'self = {self}') + self = + """ + + def __init__(self, + pos_inds: Tensor, + neg_inds: Tensor, + priors: Tensor, + gt_bboxes: Tensor, + assign_result: AssignResult, + gt_flags: Tensor, + avg_factor_with_neg: bool = True) -> None: + self.pos_inds = pos_inds + self.neg_inds = neg_inds + self.num_pos = max(pos_inds.numel(), 1) + self.num_neg = max(neg_inds.numel(), 1) + self.avg_factor_with_neg = avg_factor_with_neg + self.avg_factor = self.num_pos + self.num_neg \ + if avg_factor_with_neg else self.num_pos + self.pos_priors = priors[pos_inds] + self.neg_priors = priors[neg_inds] + self.pos_is_gt = gt_flags[pos_inds] + + self.num_gts = gt_bboxes.shape[0] + self.pos_assigned_gt_inds = assign_result.gt_inds[pos_inds] - 1 + self.pos_gt_labels = assign_result.labels[pos_inds] + box_dim = gt_bboxes.box_dim if isinstance(gt_bboxes, BaseBoxes) else 4 + if gt_bboxes.numel() == 0: + # hack for index error case + assert self.pos_assigned_gt_inds.numel() == 0 + self.pos_gt_bboxes = gt_bboxes.view(-1, box_dim) + else: + if len(gt_bboxes.shape) < 2: + gt_bboxes = gt_bboxes.view(-1, box_dim) + self.pos_gt_bboxes = gt_bboxes[self.pos_assigned_gt_inds.long()] + + @property + def priors(self): + """torch.Tensor: concatenated positive and negative priors""" + return cat_boxes([self.pos_priors, self.neg_priors]) + + @property + def bboxes(self): + """torch.Tensor: concatenated positive and negative boxes""" + warnings.warn('DeprecationWarning: bboxes is deprecated, ' + 'please use "priors" instead') + return self.priors + + @property + def pos_bboxes(self): + warnings.warn('DeprecationWarning: pos_bboxes is deprecated, ' + 'please use "pos_priors" instead') + return self.pos_priors + + @property + def neg_bboxes(self): + warnings.warn('DeprecationWarning: neg_bboxes is deprecated, ' + 'please use "neg_priors" instead') + return self.neg_priors + + def to(self, device): + """Change the device of the data inplace. + + Example: + >>> self = SamplingResult.random() + >>> print(f'self = {self.to(None)}') + >>> # xdoctest: +REQUIRES(--gpu) + >>> print(f'self = {self.to(0)}') + """ + _dict = self.__dict__ + for key, value in _dict.items(): + if isinstance(value, (torch.Tensor, BaseBoxes)): + _dict[key] = value.to(device) + return self + + def __nice__(self): + data = self.info.copy() + data['pos_priors'] = data.pop('pos_priors').shape + data['neg_priors'] = data.pop('neg_priors').shape + parts = [f"'{k}': {v!r}" for k, v in sorted(data.items())] + body = ' ' + ',\n '.join(parts) + return '{\n' + body + '\n}' + + @property + def info(self): + """Returns a dictionary of info about the object.""" + return { + 'pos_inds': self.pos_inds, + 'neg_inds': self.neg_inds, + 'pos_priors': self.pos_priors, + 'neg_priors': self.neg_priors, + 'pos_is_gt': self.pos_is_gt, + 'num_gts': self.num_gts, + 'pos_assigned_gt_inds': self.pos_assigned_gt_inds, + 'num_pos': self.num_pos, + 'num_neg': self.num_neg, + 'avg_factor': self.avg_factor + } + + @classmethod + def random(cls, rng=None, **kwargs): + """ + Args: + rng (None | int | numpy.random.RandomState): seed or state. + kwargs (keyword arguments): + - num_preds: Number of predicted boxes. + - num_gts: Number of true boxes. + - p_ignore (float): Probability of a predicted box assigned to + an ignored truth. + - p_assigned (float): probability of a predicted box not being + assigned. + + Returns: + :obj:`SamplingResult`: Randomly generated sampling result. + + Example: + >>> from mmdet.models.task_modules.samplers.sampling_result import * # NOQA + >>> self = SamplingResult.random() + >>> print(self.__dict__) + """ + from mmengine.structures import InstanceData + + from mmdet.models.task_modules.assigners import AssignResult + from mmdet.models.task_modules.samplers import RandomSampler + rng = ensure_rng(rng) + + # make probabilistic? + num = 32 + pos_fraction = 0.5 + neg_pos_ub = -1 + + assign_result = AssignResult.random(rng=rng, **kwargs) + + # Note we could just compute an assignment + priors = random_boxes(assign_result.num_preds, rng=rng) + gt_bboxes = random_boxes(assign_result.num_gts, rng=rng) + gt_labels = torch.randint( + 0, 5, (assign_result.num_gts, ), dtype=torch.long) + + pred_instances = InstanceData() + pred_instances.priors = priors + + gt_instances = InstanceData() + gt_instances.bboxes = gt_bboxes + gt_instances.labels = gt_labels + + add_gt_as_proposals = True + + sampler = RandomSampler( + num, + pos_fraction, + neg_pos_ub=neg_pos_ub, + add_gt_as_proposals=add_gt_as_proposals, + rng=rng) + self = sampler.sample( + assign_result=assign_result, + pred_instances=pred_instances, + gt_instances=gt_instances) + return self diff --git a/mmdetection/mmdet/models/task_modules/samplers/score_hlr_sampler.py b/mmdetection/mmdet/models/task_modules/samplers/score_hlr_sampler.py new file mode 100644 index 00000000..0227585b --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/samplers/score_hlr_sampler.py @@ -0,0 +1,290 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Union + +import torch +from mmcv.ops import nms_match +from mmengine.structures import InstanceData +from numpy import ndarray +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import bbox2roi +from ..assigners import AssignResult +from .base_sampler import BaseSampler +from .sampling_result import SamplingResult + + +@TASK_UTILS.register_module() +class ScoreHLRSampler(BaseSampler): + r"""Importance-based Sample Reweighting (ISR_N), described in `Prime Sample + Attention in Object Detection `_. + + Score hierarchical local rank (HLR) differentiates with RandomSampler in + negative part. It firstly computes Score-HLR in a two-step way, + then linearly maps score hlr to the loss weights. + + Args: + num (int): Total number of sampled RoIs. + pos_fraction (float): Fraction of positive samples. + context (:obj:`BaseRoIHead`): RoI head that the sampler belongs to. + neg_pos_ub (int): Upper bound of the ratio of num negative to num + positive, -1 means no upper bound. Defaults to -1. + add_gt_as_proposals (bool): Whether to add ground truth as proposals. + Defaults to True. + k (float): Power of the non-linear mapping. Defaults to 0.5 + bias (float): Shift of the non-linear mapping. Defaults to 0. + score_thr (float): Minimum score that a negative sample is to be + considered as valid bbox. Defaults to 0.05. + iou_thr (float): IoU threshold for NMS match. Defaults to 0.5. + """ + + def __init__(self, + num: int, + pos_fraction: float, + context, + neg_pos_ub: int = -1, + add_gt_as_proposals: bool = True, + k: float = 0.5, + bias: float = 0, + score_thr: float = 0.05, + iou_thr: float = 0.5, + **kwargs) -> None: + super().__init__( + num=num, + pos_fraction=pos_fraction, + neg_pos_ub=neg_pos_ub, + add_gt_as_proposals=add_gt_as_proposals) + self.k = k + self.bias = bias + self.score_thr = score_thr + self.iou_thr = iou_thr + self.context = context + # context of cascade detectors is a list, so distinguish them here. + if not hasattr(context, 'num_stages'): + self.bbox_roi_extractor = context.bbox_roi_extractor + self.bbox_head = context.bbox_head + self.with_shared_head = context.with_shared_head + if self.with_shared_head: + self.shared_head = context.shared_head + else: + self.bbox_roi_extractor = context.bbox_roi_extractor[ + context.current_stage] + self.bbox_head = context.bbox_head[context.current_stage] + + @staticmethod + def random_choice(gallery: Union[Tensor, ndarray, list], + num: int) -> Union[Tensor, ndarray]: + """Randomly select some elements from the gallery. + + If `gallery` is a Tensor, the returned indices will be a Tensor; + If `gallery` is a ndarray or list, the returned indices will be a + ndarray. + + Args: + gallery (Tensor or ndarray or list): indices pool. + num (int): expected sample num. + + Returns: + Tensor or ndarray: sampled indices. + """ + assert len(gallery) >= num + + is_tensor = isinstance(gallery, torch.Tensor) + if not is_tensor: + if torch.cuda.is_available(): + device = torch.cuda.current_device() + else: + device = 'cpu' + gallery = torch.tensor(gallery, dtype=torch.long, device=device) + perm = torch.randperm(gallery.numel(), device=gallery.device)[:num] + rand_inds = gallery[perm] + if not is_tensor: + rand_inds = rand_inds.cpu().numpy() + return rand_inds + + def _sample_pos(self, assign_result: AssignResult, num_expected: int, + **kwargs) -> Union[Tensor, ndarray]: + """Randomly sample some positive samples. + + Args: + assign_result (:obj:`AssignResult`): Bbox assigning results. + num_expected (int): The number of expected positive samples + + Returns: + Tensor or ndarray: sampled indices. + """ + pos_inds = torch.nonzero(assign_result.gt_inds > 0).flatten() + if pos_inds.numel() <= num_expected: + return pos_inds + else: + return self.random_choice(pos_inds, num_expected) + + def _sample_neg(self, assign_result: AssignResult, num_expected: int, + bboxes: Tensor, feats: Tensor, + **kwargs) -> Union[Tensor, ndarray]: + """Sample negative samples. + + Score-HLR sampler is done in the following steps: + 1. Take the maximum positive score prediction of each negative samples + as s_i. + 2. Filter out negative samples whose s_i <= score_thr, the left samples + are called valid samples. + 3. Use NMS-Match to divide valid samples into different groups, + samples in the same group will greatly overlap with each other + 4. Rank the matched samples in two-steps to get Score-HLR. + (1) In the same group, rank samples with their scores. + (2) In the same score rank across different groups, + rank samples with their scores again. + 5. Linearly map Score-HLR to the final label weights. + + Args: + assign_result (:obj:`AssignResult`): result of assigner. + num_expected (int): Expected number of samples. + bboxes (Tensor): bbox to be sampled. + feats (Tensor): Features come from FPN. + + Returns: + Tensor or ndarray: sampled indices. + """ + neg_inds = torch.nonzero(assign_result.gt_inds == 0).flatten() + num_neg = neg_inds.size(0) + if num_neg == 0: + return neg_inds, None + with torch.no_grad(): + neg_bboxes = bboxes[neg_inds] + neg_rois = bbox2roi([neg_bboxes]) + bbox_result = self.context._bbox_forward(feats, neg_rois) + cls_score, bbox_pred = bbox_result['cls_score'], bbox_result[ + 'bbox_pred'] + + ori_loss = self.bbox_head.loss( + cls_score=cls_score, + bbox_pred=None, + rois=None, + labels=neg_inds.new_full((num_neg, ), + self.bbox_head.num_classes), + label_weights=cls_score.new_ones(num_neg), + bbox_targets=None, + bbox_weights=None, + reduction_override='none')['loss_cls'] + + # filter out samples with the max score lower than score_thr + max_score, argmax_score = cls_score.softmax(-1)[:, :-1].max(-1) + valid_inds = (max_score > self.score_thr).nonzero().view(-1) + invalid_inds = (max_score <= self.score_thr).nonzero().view(-1) + num_valid = valid_inds.size(0) + num_invalid = invalid_inds.size(0) + + num_expected = min(num_neg, num_expected) + num_hlr = min(num_valid, num_expected) + num_rand = num_expected - num_hlr + if num_valid > 0: + valid_rois = neg_rois[valid_inds] + valid_max_score = max_score[valid_inds] + valid_argmax_score = argmax_score[valid_inds] + valid_bbox_pred = bbox_pred[valid_inds] + + # valid_bbox_pred shape: [num_valid, #num_classes, 4] + valid_bbox_pred = valid_bbox_pred.view( + valid_bbox_pred.size(0), -1, 4) + selected_bbox_pred = valid_bbox_pred[range(num_valid), + valid_argmax_score] + pred_bboxes = self.bbox_head.bbox_coder.decode( + valid_rois[:, 1:], selected_bbox_pred) + pred_bboxes_with_score = torch.cat( + [pred_bboxes, valid_max_score[:, None]], -1) + group = nms_match(pred_bboxes_with_score, self.iou_thr) + + # imp: importance + imp = cls_score.new_zeros(num_valid) + for g in group: + g_score = valid_max_score[g] + # g_score has already sorted + rank = g_score.new_tensor(range(g_score.size(0))) + imp[g] = num_valid - rank + g_score + _, imp_rank_inds = imp.sort(descending=True) + _, imp_rank = imp_rank_inds.sort() + hlr_inds = imp_rank_inds[:num_expected] + + if num_rand > 0: + rand_inds = torch.randperm(num_invalid)[:num_rand] + select_inds = torch.cat( + [valid_inds[hlr_inds], invalid_inds[rand_inds]]) + else: + select_inds = valid_inds[hlr_inds] + + neg_label_weights = cls_score.new_ones(num_expected) + + up_bound = max(num_expected, num_valid) + imp_weights = (up_bound - + imp_rank[hlr_inds].float()) / up_bound + neg_label_weights[:num_hlr] = imp_weights + neg_label_weights[num_hlr:] = imp_weights.min() + neg_label_weights = (self.bias + + (1 - self.bias) * neg_label_weights).pow( + self.k) + ori_selected_loss = ori_loss[select_inds] + new_loss = ori_selected_loss * neg_label_weights + norm_ratio = ori_selected_loss.sum() / new_loss.sum() + neg_label_weights *= norm_ratio + else: + neg_label_weights = cls_score.new_ones(num_expected) + select_inds = torch.randperm(num_neg)[:num_expected] + + return neg_inds[select_inds], neg_label_weights + + def sample(self, assign_result: AssignResult, pred_instances: InstanceData, + gt_instances: InstanceData, **kwargs) -> SamplingResult: + """Sample positive and negative bboxes. + + This is a simple implementation of bbox sampling given candidates, + assigning results and ground truth bboxes. + + Args: + assign_result (:obj:`AssignResult`): Assigning results. + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + + Returns: + :obj:`SamplingResult`: Sampling result. + """ + gt_bboxes = gt_instances.bboxes + priors = pred_instances.priors + gt_labels = gt_instances.labels + + gt_flags = priors.new_zeros((priors.shape[0], ), dtype=torch.uint8) + if self.add_gt_as_proposals and len(gt_bboxes) > 0: + priors = torch.cat([gt_bboxes, priors], dim=0) + assign_result.add_gt_(gt_labels) + gt_ones = priors.new_ones(gt_bboxes.shape[0], dtype=torch.uint8) + gt_flags = torch.cat([gt_ones, gt_flags]) + + num_expected_pos = int(self.num * self.pos_fraction) + pos_inds = self.pos_sampler._sample_pos( + assign_result, num_expected_pos, bboxes=priors, **kwargs) + num_sampled_pos = pos_inds.numel() + num_expected_neg = self.num - num_sampled_pos + if self.neg_pos_ub >= 0: + _pos = max(1, num_sampled_pos) + neg_upper_bound = int(self.neg_pos_ub * _pos) + if num_expected_neg > neg_upper_bound: + num_expected_neg = neg_upper_bound + neg_inds, neg_label_weights = self.neg_sampler._sample_neg( + assign_result, num_expected_neg, bboxes=priors, **kwargs) + + sampling_result = SamplingResult( + pos_inds=pos_inds, + neg_inds=neg_inds, + priors=priors, + gt_bboxes=gt_bboxes, + assign_result=assign_result, + gt_flags=gt_flags) + return sampling_result, neg_label_weights diff --git a/mmdetection/mmdet/models/task_modules/tracking/__init__.py b/mmdetection/mmdet/models/task_modules/tracking/__init__.py new file mode 100644 index 00000000..57a86d73 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/tracking/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .aflink import AppearanceFreeLink +from .camera_motion_compensation import CameraMotionCompensation +from .interpolation import InterpolateTracklets +from .kalman_filter import KalmanFilter +from .similarity import embed_similarity + +__all__ = [ + 'KalmanFilter', 'InterpolateTracklets', 'embed_similarity', + 'AppearanceFreeLink', 'CameraMotionCompensation' +] diff --git a/mmdetection/mmdet/models/task_modules/tracking/aflink.py b/mmdetection/mmdet/models/task_modules/tracking/aflink.py new file mode 100644 index 00000000..52461067 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/tracking/aflink.py @@ -0,0 +1,281 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections import defaultdict +from typing import Tuple + +import numpy as np +import torch +from mmengine.model import BaseModule +from mmengine.runner.checkpoint import load_checkpoint +from scipy.optimize import linear_sum_assignment +from torch import Tensor, nn + +from mmdet.registry import TASK_UTILS + +INFINITY = 1e5 + + +class TemporalBlock(BaseModule): + """The temporal block of AFLink model. + + Args: + in_channel (int): the dimension of the input channels. + out_channel (int): the dimension of the output channels. + """ + + def __init__(self, + in_channel: int, + out_channel: int, + kernel_size: tuple = (7, 1)): + super(TemporalBlock, self).__init__() + self.conv = nn.Conv2d(in_channel, out_channel, kernel_size, bias=False) + self.relu = nn.ReLU(inplace=True) + self.bnf = nn.BatchNorm1d(out_channel) + self.bnx = nn.BatchNorm1d(out_channel) + self.bny = nn.BatchNorm1d(out_channel) + + def bn(self, x: Tensor) -> Tensor: + x[:, :, :, 0] = self.bnf(x[:, :, :, 0]) + x[:, :, :, 1] = self.bnx(x[:, :, :, 1]) + x[:, :, :, 2] = self.bny(x[:, :, :, 2]) + return x + + def forward(self, x: Tensor) -> Tensor: + x = self.conv(x) + x = self.bn(x) + x = self.relu(x) + return x + + +class FusionBlock(BaseModule): + """The fusion block of AFLink model. + + Args: + in_channel (int): the dimension of the input channels. + out_channel (int): the dimension of the output channels. + """ + + def __init__(self, in_channel: int, out_channel: int): + super(FusionBlock, self).__init__() + self.conv = nn.Conv2d(in_channel, out_channel, (1, 3), bias=False) + self.bn = nn.BatchNorm2d(out_channel) + self.relu = nn.ReLU(inplace=True) + + def forward(self, x: Tensor) -> Tensor: + x = self.conv(x) + x = self.bn(x) + x = self.relu(x) + return x + + +class Classifier(BaseModule): + """The classifier of AFLink model. + + Args: + in_channel (int): the dimension of the input channels. + """ + + def __init__(self, in_channel: int, out_channel: int): + super(Classifier, self).__init__() + self.fc1 = nn.Linear(in_channel * 2, in_channel // 2) + self.relu = nn.ReLU(inplace=True) + self.fc2 = nn.Linear(in_channel // 2, out_channel) + + def forward(self, x1: Tensor, x2: Tensor) -> Tensor: + x = torch.cat((x1, x2), dim=1) + x = self.fc1(x) + x = self.relu(x) + x = self.fc2(x) + return x + + +class AFLinkModel(BaseModule): + """Appearance-Free Link Model.""" + + def __init__(self, + temporal_module_channels: list = [1, 32, 64, 128, 256], + fusion_module_channels: list = [256, 256], + classifier_channels: list = [256, 2]): + super(AFLinkModel, self).__init__() + self.TemporalModule_1 = nn.Sequential(*[ + TemporalBlock(temporal_module_channels[i], + temporal_module_channels[i + 1]) + for i in range(len(temporal_module_channels) - 1) + ]) + + self.TemporalModule_2 = nn.Sequential(*[ + TemporalBlock(temporal_module_channels[i], + temporal_module_channels[i + 1]) + for i in range(len(temporal_module_channels) - 1) + ]) + + self.FusionBlock_1 = FusionBlock(*fusion_module_channels) + self.FusionBlock_2 = FusionBlock(*fusion_module_channels) + + self.pooling = nn.AdaptiveAvgPool2d((1, 1)) + self.classifier = Classifier(*classifier_channels) + + def forward(self, x1: Tensor, x2: Tensor) -> Tensor: + assert not self.training, 'Only testing is supported for AFLink.' + x1 = x1[:, :, :, :3] + x2 = x2[:, :, :, :3] + x1 = self.TemporalModule_1(x1) # [B,1,30,3] -> [B,256,6,3] + x2 = self.TemporalModule_2(x2) + x1 = self.FusionBlock_1(x1) + x2 = self.FusionBlock_2(x2) + x1 = self.pooling(x1).squeeze(-1).squeeze(-1) + x2 = self.pooling(x2).squeeze(-1).squeeze(-1) + y = self.classifier(x1, x2) + y = torch.softmax(y, dim=1)[0, 1] + return y + + +@TASK_UTILS.register_module() +class AppearanceFreeLink(BaseModule): + """Appearance-Free Link method. + + This method is proposed in + "StrongSORT: Make DeepSORT Great Again" + `StrongSORT`_. + + Args: + checkpoint (str): Checkpoint path. + temporal_threshold (tuple, optional): The temporal constraint + for tracklets association. Defaults to (0, 30). + spatial_threshold (int, optional): The spatial constraint for + tracklets association. Defaults to 75. + confidence_threshold (float, optional): The minimum confidence + threshold for tracklets association. Defaults to 0.95. + """ + + def __init__(self, + checkpoint: str, + temporal_threshold: tuple = (0, 30), + spatial_threshold: int = 75, + confidence_threshold: float = 0.95): + super(AppearanceFreeLink, self).__init__() + self.temporal_threshold = temporal_threshold + self.spatial_threshold = spatial_threshold + self.confidence_threshold = confidence_threshold + + self.model = AFLinkModel() + if checkpoint: + load_checkpoint(self.model, checkpoint) + if torch.cuda.is_available(): + self.model.cuda() + self.model.eval() + + self.device = next(self.model.parameters()).device + self.fn_l2 = lambda x, y: np.sqrt(x**2 + y**2) + + def data_transform(self, + track1: np.ndarray, + track2: np.ndarray, + length: int = 30) -> Tuple[np.ndarray]: + """Data Transformation. This is used to standardize the length of + tracks to a unified length. Then perform min-max normalization to the + motion embeddings. + + Args: + track1 (ndarray): the first track with shape (N,C). + track2 (ndarray): the second track with shape (M,C). + length (int): the unified length of tracks. Defaults to 30. + + Returns: + Tuple[ndarray]: the transformed track1 and track2. + """ + # fill or cut track1 + length_1 = track1.shape[0] + track1 = track1[-length:] if length_1 >= length else \ + np.pad(track1, ((length - length_1, 0), (0, 0))) + + # fill or cut track1 + length_2 = track2.shape[0] + track2 = track2[:length] if length_2 >= length else \ + np.pad(track2, ((0, length - length_2), (0, 0))) + + # min-max normalization + min_ = np.concatenate((track1, track2), axis=0).min(axis=0) + max_ = np.concatenate((track1, track2), axis=0).max(axis=0) + subtractor = (max_ + min_) / 2 + divisor = (max_ - min_) / 2 + 1e-5 + track1 = (track1 - subtractor) / divisor + track2 = (track2 - subtractor) / divisor + + return track1, track2 + + def forward(self, pred_tracks: np.ndarray) -> np.ndarray: + """Forward function. + + pred_tracks (ndarray): With shape (N, 7). Each row denotes + (frame_id, track_id, x1, y1, x2, y2, score). + + Returns: + ndarray: The linked tracks with shape (N, 7). Each row denotes + (frame_id, track_id, x1, y1, x2, y2, score) + """ + # sort tracks by the frame id + pred_tracks = pred_tracks[np.argsort(pred_tracks[:, 0])] + + # gather tracks information + id2info = defaultdict(list) + for row in pred_tracks: + frame_id, track_id, x1, y1, x2, y2 = row[:6] + id2info[track_id].append([frame_id, x1, y1, x2 - x1, y2 - y1]) + id2info = {k: np.array(v) for k, v in id2info.items()} + num_track = len(id2info) + track_ids = np.array(list(id2info)) + cost_matrix = np.full((num_track, num_track), INFINITY) + + # compute the cost matrix + for i, id_i in enumerate(track_ids): + for j, id_j in enumerate(track_ids): + if id_i == id_j: + continue + info_i, info_j = id2info[id_i], id2info[id_j] + frame_i, box_i = info_i[-1][0], info_i[-1][1:3] + frame_j, box_j = info_j[0][0], info_j[0][1:3] + # temporal constraint + if not self.temporal_threshold[0] <= \ + frame_j - frame_i <= self.temporal_threshold[1]: + continue + # spatial constraint + if self.fn_l2(box_i[0] - box_j[0], box_i[1] - box_j[1]) \ + > self.spatial_threshold: + continue + # confidence constraint + track_i, track_j = self.data_transform(info_i, info_j) + + # numpy to torch + track_i = torch.tensor( + track_i, dtype=torch.float).to(self.device) + track_j = torch.tensor( + track_j, dtype=torch.float).to(self.device) + track_i = track_i.unsqueeze(0).unsqueeze(0) + track_j = track_j.unsqueeze(0).unsqueeze(0) + + confidence = self.model(track_i, + track_j).detach().cpu().numpy() + if confidence >= self.confidence_threshold: + cost_matrix[i, j] = 1 - confidence + + # linear assignment + indices = linear_sum_assignment(cost_matrix) + _id2id = dict() # the temporary assignment results + id2id = dict() # the final assignment results + for i, j in zip(indices[0], indices[1]): + if cost_matrix[i, j] < INFINITY: + _id2id[i] = j + for k, v in _id2id.items(): + if k in id2id: + id2id[v] = id2id[k] + else: + id2id[v] = k + + # link + for k, v in id2id.items(): + pred_tracks[pred_tracks[:, 1] == k, 1] = v + + # deduplicate + _, index = np.unique(pred_tracks[:, :2], return_index=True, axis=0) + + return pred_tracks[index] diff --git a/mmdetection/mmdet/models/task_modules/tracking/camera_motion_compensation.py b/mmdetection/mmdet/models/task_modules/tracking/camera_motion_compensation.py new file mode 100644 index 00000000..1a629849 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/tracking/camera_motion_compensation.py @@ -0,0 +1,104 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import cv2 +import numpy as np +import torch +from torch import Tensor + +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import bbox_cxcyah_to_xyxy, bbox_xyxy_to_cxcyah + + +@TASK_UTILS.register_module() +class CameraMotionCompensation: + """Camera motion compensation. + + Args: + warp_mode (str): Warp mode in opencv. + Defaults to 'cv2.MOTION_EUCLIDEAN'. + num_iters (int): Number of the iterations. Defaults to 50. + stop_eps (float): Terminate threshold. Defaults to 0.001. + """ + + def __init__(self, + warp_mode: str = 'cv2.MOTION_EUCLIDEAN', + num_iters: int = 50, + stop_eps: float = 0.001): + self.warp_mode = eval(warp_mode) + self.num_iters = num_iters + self.stop_eps = stop_eps + + def get_warp_matrix(self, img: np.ndarray, ref_img: np.ndarray) -> Tensor: + """Calculate warping matrix between two images.""" + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + ref_img = cv2.cvtColor(ref_img, cv2.COLOR_BGR2GRAY) + + warp_matrix = np.eye(2, 3, dtype=np.float32) + criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, + self.num_iters, self.stop_eps) + cc, warp_matrix = cv2.findTransformECC(img, ref_img, warp_matrix, + self.warp_mode, criteria, None, + 1) + warp_matrix = torch.from_numpy(warp_matrix) + return warp_matrix + + def warp_bboxes(self, bboxes: Tensor, warp_matrix: Tensor) -> Tensor: + """Warp bounding boxes according to the warping matrix.""" + tl, br = bboxes[:, :2], bboxes[:, 2:] + tl = torch.cat((tl, torch.ones(tl.shape[0], 1).to(bboxes.device)), + dim=1) + br = torch.cat((br, torch.ones(tl.shape[0], 1).to(bboxes.device)), + dim=1) + trans_tl = torch.mm(warp_matrix, tl.t()).t() + trans_br = torch.mm(warp_matrix, br.t()).t() + trans_bboxes = torch.cat((trans_tl, trans_br), dim=1) + return trans_bboxes.to(bboxes.device) + + def warp_means(self, means: np.ndarray, warp_matrix: Tensor) -> np.ndarray: + """Warp track.mean according to the warping matrix.""" + cxcyah = torch.from_numpy(means[:, :4]).float() + xyxy = bbox_cxcyah_to_xyxy(cxcyah) + warped_xyxy = self.warp_bboxes(xyxy, warp_matrix) + warped_cxcyah = bbox_xyxy_to_cxcyah(warped_xyxy).numpy() + means[:, :4] = warped_cxcyah + return means + + def track(self, img: Tensor, ref_img: Tensor, tracks: dict, + num_samples: int, frame_id: int, metainfo: dict) -> dict: + """Tracking forward.""" + img = img.squeeze(0).cpu().numpy().transpose((1, 2, 0)) + ref_img = ref_img.squeeze(0).cpu().numpy().transpose((1, 2, 0)) + warp_matrix = self.get_warp_matrix(img, ref_img) + + # rescale the warp_matrix due to the `resize` in pipeline + scale_factor_h, scale_factor_w = metainfo['scale_factor'] + warp_matrix[0, 2] = warp_matrix[0, 2] / scale_factor_w + warp_matrix[1, 2] = warp_matrix[1, 2] / scale_factor_h + + bboxes = [] + num_bboxes = [] + means = [] + for k, v in tracks.items(): + if int(v['frame_ids'][-1]) < frame_id - 1: + _num = 1 + else: + _num = min(num_samples, len(v.bboxes)) + num_bboxes.append(_num) + bboxes.extend(v.bboxes[-_num:]) + if len(v.mean) > 0: + means.append(v.mean) + bboxes = torch.cat(bboxes, dim=0) + warped_bboxes = self.warp_bboxes(bboxes, warp_matrix.to(bboxes.device)) + + warped_bboxes = torch.split(warped_bboxes, num_bboxes) + for b, (k, v) in zip(warped_bboxes, tracks.items()): + _num = b.shape[0] + b = torch.split(b, [1] * _num) + tracks[k].bboxes[-_num:] = b + + if means: + means = np.asarray(means) + warped_means = self.warp_means(means, warp_matrix) + for m, (k, v) in zip(warped_means, tracks.items()): + tracks[k].mean = m + + return tracks diff --git a/mmdetection/mmdet/models/task_modules/tracking/interpolation.py b/mmdetection/mmdet/models/task_modules/tracking/interpolation.py new file mode 100644 index 00000000..fb6a25af --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/tracking/interpolation.py @@ -0,0 +1,168 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np + +try: + from sklearn.gaussian_process import GaussianProcessRegressor as GPR + from sklearn.gaussian_process.kernels import RBF + HAS_SKIKIT_LEARN = True +except ImportError: + HAS_SKIKIT_LEARN = False + +from mmdet.registry import TASK_UTILS + + +@TASK_UTILS.register_module() +class InterpolateTracklets: + """Interpolate tracks to make tracks more complete. + + Args: + min_num_frames (int, optional): The minimum length of a track that will + be interpolated. Defaults to 5. + max_num_frames (int, optional): The maximum disconnected length in + a track. Defaults to 20. + use_gsi (bool, optional): Whether to use the GSI (Gaussian-smoothed + interpolation) method. Defaults to False. + smooth_tau (int, optional): smoothing parameter in GSI. Defaults to 10. + """ + + def __init__(self, + min_num_frames: int = 5, + max_num_frames: int = 20, + use_gsi: bool = False, + smooth_tau: int = 10): + if not HAS_SKIKIT_LEARN: + raise RuntimeError('sscikit-learn is not installed,\ + please install it by: pip install scikit-learn') + self.min_num_frames = min_num_frames + self.max_num_frames = max_num_frames + self.use_gsi = use_gsi + self.smooth_tau = smooth_tau + + def _interpolate_track(self, + track: np.ndarray, + track_id: int, + max_num_frames: int = 20) -> np.ndarray: + """Interpolate a track linearly to make the track more complete. + + This function is proposed in + "ByteTrack: Multi-Object Tracking by Associating Every Detection Box." + `ByteTrack`_. + + Args: + track (ndarray): With shape (N, 7). Each row denotes + (frame_id, track_id, x1, y1, x2, y2, score). + max_num_frames (int, optional): The maximum disconnected length in + the track. Defaults to 20. + + Returns: + ndarray: The interpolated track with shape (N, 7). Each row denotes + (frame_id, track_id, x1, y1, x2, y2, score) + """ + assert (track[:, 1] == track_id).all(), \ + 'The track id should not changed when interpolate a track.' + + frame_ids = track[:, 0] + interpolated_track = np.zeros((0, 7)) + # perform interpolation for the disconnected frames in the track. + for i in np.where(np.diff(frame_ids) > 1)[0]: + left_frame_id = frame_ids[i] + right_frame_id = frame_ids[i + 1] + num_disconnected_frames = int(right_frame_id - left_frame_id) + + if 1 < num_disconnected_frames < max_num_frames: + left_bbox = track[i, 2:6] + right_bbox = track[i + 1, 2:6] + + # perform interpolation for two adjacent tracklets. + for j in range(1, num_disconnected_frames): + cur_bbox = j / (num_disconnected_frames) * ( + right_bbox - left_bbox) + left_bbox + cur_result = np.ones((7, )) + cur_result[0] = j + left_frame_id + cur_result[1] = track_id + cur_result[2:6] = cur_bbox + + interpolated_track = np.concatenate( + (interpolated_track, cur_result[None]), axis=0) + + interpolated_track = np.concatenate((track, interpolated_track), + axis=0) + return interpolated_track + + def gaussian_smoothed_interpolation(self, + track: np.ndarray, + smooth_tau: int = 10) -> np.ndarray: + """Gaussian-Smoothed Interpolation. + + This function is proposed in + "StrongSORT: Make DeepSORT Great Again" + `StrongSORT`_. + + Args: + track (ndarray): With shape (N, 7). Each row denotes + (frame_id, track_id, x1, y1, x2, y2, score). + smooth_tau (int, optional): smoothing parameter in GSI. + Defaults to 10. + + Returns: + ndarray: The interpolated tracks with shape (N, 7). Each row + denotes (frame_id, track_id, x1, y1, x2, y2, score) + """ + len_scale = np.clip(smooth_tau * np.log(smooth_tau**3 / len(track)), + smooth_tau**-1, smooth_tau**2) + gpr = GPR(RBF(len_scale, 'fixed')) + t = track[:, 0].reshape(-1, 1) + x1 = track[:, 2].reshape(-1, 1) + y1 = track[:, 3].reshape(-1, 1) + x2 = track[:, 4].reshape(-1, 1) + y2 = track[:, 5].reshape(-1, 1) + gpr.fit(t, x1) + x1_gpr = gpr.predict(t) + gpr.fit(t, y1) + y1_gpr = gpr.predict(t) + gpr.fit(t, x2) + x2_gpr = gpr.predict(t) + gpr.fit(t, y2) + y2_gpr = gpr.predict(t) + gsi_track = [[ + t[i, 0], track[i, 1], x1_gpr[i], y1_gpr[i], x2_gpr[i], y2_gpr[i], + track[i, 6] + ] for i in range(len(t))] + return np.array(gsi_track) + + def forward(self, pred_tracks: np.ndarray) -> np.ndarray: + """Forward function. + + pred_tracks (ndarray): With shape (N, 7). Each row denotes + (frame_id, track_id, x1, y1, x2, y2, score). + + Returns: + ndarray: The interpolated tracks with shape (N, 7). Each row + denotes (frame_id, track_id, x1, y1, x2, y2, score). + """ + max_track_id = int(np.max(pred_tracks[:, 1])) + min_track_id = int(np.min(pred_tracks[:, 1])) + + # perform interpolation for each track + interpolated_tracks = [] + for track_id in range(min_track_id, max_track_id + 1): + inds = pred_tracks[:, 1] == track_id + track = pred_tracks[inds] + num_frames = len(track) + if num_frames <= 2: + continue + + if num_frames > self.min_num_frames: + interpolated_track = self._interpolate_track( + track, track_id, self.max_num_frames) + else: + interpolated_track = track + + if self.use_gsi: + interpolated_track = self.gaussian_smoothed_interpolation( + interpolated_track, self.smooth_tau) + + interpolated_tracks.append(interpolated_track) + + interpolated_tracks = np.concatenate(interpolated_tracks) + return interpolated_tracks[interpolated_tracks[:, 0].argsort()] diff --git a/mmdetection/mmdet/models/task_modules/tracking/kalman_filter.py b/mmdetection/mmdet/models/task_modules/tracking/kalman_filter.py new file mode 100644 index 00000000..a8ae1416 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/tracking/kalman_filter.py @@ -0,0 +1,267 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import numpy as np +import torch + +try: + import scipy.linalg + HAS_SCIPY = True +except ImportError: + HAS_SCIPY = False + +from mmdet.registry import TASK_UTILS + + +@TASK_UTILS.register_module() +class KalmanFilter: + """A simple Kalman filter for tracking bounding boxes in image space. + + The implementation is referred to https://github.com/nwojke/deep_sort. + + Args: + center_only (bool): If True, distance computation is done with + respect to the bounding box center position only. + Defaults to False. + use_nsa (bool): Whether to use the NSA (Noise Scale Adaptive) Kalman + Filter, which adaptively modulates the noise scale according to + the quality of detections. More details in + https://arxiv.org/abs/2202.11983. Defaults to False. + """ + chi2inv95 = { + 1: 3.8415, + 2: 5.9915, + 3: 7.8147, + 4: 9.4877, + 5: 11.070, + 6: 12.592, + 7: 14.067, + 8: 15.507, + 9: 16.919 + } + + def __init__(self, center_only: bool = False, use_nsa: bool = False): + if not HAS_SCIPY: + raise RuntimeError('sscikit-learn is not installed,\ + please install it by: pip install scikit-learn') + self.center_only = center_only + if self.center_only: + self.gating_threshold = self.chi2inv95[2] + else: + self.gating_threshold = self.chi2inv95[4] + + self.use_nsa = use_nsa + ndim, dt = 4, 1. + + # Create Kalman filter model matrices. + self._motion_mat = np.eye(2 * ndim, 2 * ndim) + for i in range(ndim): + self._motion_mat[i, ndim + i] = dt + self._update_mat = np.eye(ndim, 2 * ndim) + + # Motion and observation uncertainty are chosen relative to the current + # state estimate. These weights control the amount of uncertainty in + # the model. This is a bit hacky. + self._std_weight_position = 1. / 20 + self._std_weight_velocity = 1. / 160 + + def initiate(self, measurement: np.array) -> Tuple[np.array, np.array]: + """Create track from unassociated measurement. + + Args: + measurement (ndarray): Bounding box coordinates (x, y, a, h) with + center position (x, y), aspect ratio a, and height h. + + Returns: + (ndarray, ndarray): Returns the mean vector (8 dimensional) and + covariance matrix (8x8 dimensional) of the new track. + Unobserved velocities are initialized to 0 mean. + """ + mean_pos = measurement + mean_vel = np.zeros_like(mean_pos) + mean = np.r_[mean_pos, mean_vel] + + std = [ + 2 * self._std_weight_position * measurement[3], + 2 * self._std_weight_position * measurement[3], 1e-2, + 2 * self._std_weight_position * measurement[3], + 10 * self._std_weight_velocity * measurement[3], + 10 * self._std_weight_velocity * measurement[3], 1e-5, + 10 * self._std_weight_velocity * measurement[3] + ] + covariance = np.diag(np.square(std)) + return mean, covariance + + def predict(self, mean: np.array, + covariance: np.array) -> Tuple[np.array, np.array]: + """Run Kalman filter prediction step. + + Args: + mean (ndarray): The 8 dimensional mean vector of the object + state at the previous time step. + + covariance (ndarray): The 8x8 dimensional covariance matrix + of the object state at the previous time step. + + Returns: + (ndarray, ndarray): Returns the mean vector and covariance + matrix of the predicted state. Unobserved velocities are + initialized to 0 mean. + """ + std_pos = [ + self._std_weight_position * mean[3], + self._std_weight_position * mean[3], 1e-2, + self._std_weight_position * mean[3] + ] + std_vel = [ + self._std_weight_velocity * mean[3], + self._std_weight_velocity * mean[3], 1e-5, + self._std_weight_velocity * mean[3] + ] + motion_cov = np.diag(np.square(np.r_[std_pos, std_vel])) + + mean = np.dot(self._motion_mat, mean) + covariance = np.linalg.multi_dot( + (self._motion_mat, covariance, self._motion_mat.T)) + motion_cov + + return mean, covariance + + def project(self, + mean: np.array, + covariance: np.array, + bbox_score: float = 0.) -> Tuple[np.array, np.array]: + """Project state distribution to measurement space. + + Args: + mean (ndarray): The state's mean vector (8 dimensional array). + covariance (ndarray): The state's covariance matrix (8x8 + dimensional). + bbox_score (float): The confidence score of the bbox. + Defaults to 0. + + Returns: + (ndarray, ndarray): Returns the projected mean and covariance + matrix of the given state estimate. + """ + std = [ + self._std_weight_position * mean[3], + self._std_weight_position * mean[3], 1e-1, + self._std_weight_position * mean[3] + ] + + if self.use_nsa: + std = [(1 - bbox_score) * x for x in std] + + innovation_cov = np.diag(np.square(std)) + + mean = np.dot(self._update_mat, mean) + covariance = np.linalg.multi_dot( + (self._update_mat, covariance, self._update_mat.T)) + return mean, covariance + innovation_cov + + def update(self, + mean: np.array, + covariance: np.array, + measurement: np.array, + bbox_score: float = 0.) -> Tuple[np.array, np.array]: + """Run Kalman filter correction step. + + Args: + mean (ndarray): The predicted state's mean vector (8 dimensional). + covariance (ndarray): The state's covariance matrix (8x8 + dimensional). + measurement (ndarray): The 4 dimensional measurement vector + (x, y, a, h), where (x, y) is the center position, a the + aspect ratio, and h the height of the bounding box. + bbox_score (float): The confidence score of the bbox. + Defaults to 0. + + Returns: + (ndarray, ndarray): Returns the measurement-corrected state + distribution. + """ + projected_mean, projected_cov = \ + self.project(mean, covariance, bbox_score) + + chol_factor, lower = scipy.linalg.cho_factor( + projected_cov, lower=True, check_finite=False) + kalman_gain = scipy.linalg.cho_solve((chol_factor, lower), + np.dot(covariance, + self._update_mat.T).T, + check_finite=False).T + innovation = measurement - projected_mean + + new_mean = mean + np.dot(innovation, kalman_gain.T) + new_covariance = covariance - np.linalg.multi_dot( + (kalman_gain, projected_cov, kalman_gain.T)) + return new_mean, new_covariance + + def gating_distance(self, + mean: np.array, + covariance: np.array, + measurements: np.array, + only_position: bool = False) -> np.array: + """Compute gating distance between state distribution and measurements. + + A suitable distance threshold can be obtained from `chi2inv95`. If + `only_position` is False, the chi-square distribution has 4 degrees of + freedom, otherwise 2. + + Args: + mean (ndarray): Mean vector over the state distribution (8 + dimensional). + covariance (ndarray): Covariance of the state distribution (8x8 + dimensional). + measurements (ndarray): An Nx4 dimensional matrix of N + measurements, each in format (x, y, a, h) where (x, y) is the + bounding box center position, a the aspect ratio, and h the + height. + only_position (bool, optional): If True, distance computation is + done with respect to the bounding box center position only. + Defaults to False. + + Returns: + ndarray: Returns an array of length N, where the i-th element + contains the squared Mahalanobis distance between + (mean, covariance) and `measurements[i]`. + """ + mean, covariance = self.project(mean, covariance) + if only_position: + mean, covariance = mean[:2], covariance[:2, :2] + measurements = measurements[:, :2] + + cholesky_factor = np.linalg.cholesky(covariance) + d = measurements - mean + z = scipy.linalg.solve_triangular( + cholesky_factor, + d.T, + lower=True, + check_finite=False, + overwrite_b=True) + squared_maha = np.sum(z * z, axis=0) + return squared_maha + + def track(self, tracks: dict, + bboxes: torch.Tensor) -> Tuple[dict, np.array]: + """Track forward. + + Args: + tracks (dict[int:dict]): Track buffer. + bboxes (Tensor): Detected bounding boxes. + + Returns: + (dict[int:dict], ndarray): Updated tracks and bboxes. + """ + costs = [] + for id, track in tracks.items(): + track.mean, track.covariance = self.predict( + track.mean, track.covariance) + gating_distance = self.gating_distance(track.mean, + track.covariance, + bboxes.cpu().numpy(), + self.center_only) + costs.append(gating_distance) + + costs = np.stack(costs, 0) + costs[costs > self.gating_threshold] = np.nan + return tracks, costs diff --git a/mmdetection/mmdet/models/task_modules/tracking/similarity.py b/mmdetection/mmdet/models/task_modules/tracking/similarity.py new file mode 100644 index 00000000..730e43b8 --- /dev/null +++ b/mmdetection/mmdet/models/task_modules/tracking/similarity.py @@ -0,0 +1,34 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn.functional as F +from torch import Tensor + + +def embed_similarity(key_embeds: Tensor, + ref_embeds: Tensor, + method: str = 'dot_product', + temperature: int = -1) -> Tensor: + """Calculate feature similarity from embeddings. + + Args: + key_embeds (Tensor): Shape (N1, C). + ref_embeds (Tensor): Shape (N2, C). + method (str, optional): Method to calculate the similarity, + options are 'dot_product' and 'cosine'. Defaults to + 'dot_product'. + temperature (int, optional): Softmax temperature. Defaults to -1. + + Returns: + Tensor: Similarity matrix of shape (N1, N2). + """ + assert method in ['dot_product', 'cosine'] + + if method == 'cosine': + key_embeds = F.normalize(key_embeds, p=2, dim=1) + ref_embeds = F.normalize(ref_embeds, p=2, dim=1) + + similarity = torch.mm(key_embeds, ref_embeds.T) + + if temperature > 0: + similarity /= float(temperature) + return similarity diff --git a/mmdetection/mmdet/models/test_time_augs/__init__.py b/mmdetection/mmdet/models/test_time_augs/__init__.py new file mode 100644 index 00000000..f5e4926e --- /dev/null +++ b/mmdetection/mmdet/models/test_time_augs/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .det_tta import DetTTAModel +from .merge_augs import (merge_aug_bboxes, merge_aug_masks, + merge_aug_proposals, merge_aug_results, + merge_aug_scores) + +__all__ = [ + 'merge_aug_bboxes', 'merge_aug_masks', 'merge_aug_proposals', + 'merge_aug_scores', 'merge_aug_results', 'DetTTAModel' +] diff --git a/mmdetection/mmdet/models/test_time_augs/det_tta.py b/mmdetection/mmdet/models/test_time_augs/det_tta.py new file mode 100644 index 00000000..95f91db9 --- /dev/null +++ b/mmdetection/mmdet/models/test_time_augs/det_tta.py @@ -0,0 +1,144 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +from mmcv.ops import batched_nms +from mmengine.model import BaseTTAModel +from mmengine.registry import MODELS +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import bbox_flip + + +@MODELS.register_module() +class DetTTAModel(BaseTTAModel): + """Merge augmented detection results, only bboxes corresponding score under + flipping and multi-scale resizing can be processed now. + + Examples: + >>> tta_model = dict( + >>> type='DetTTAModel', + >>> tta_cfg=dict(nms=dict( + >>> type='nms', + >>> iou_threshold=0.5), + >>> max_per_img=100)) + >>> + >>> tta_pipeline = [ + >>> dict(type='LoadImageFromFile', + >>> backend_args=None), + >>> dict( + >>> type='TestTimeAug', + >>> transforms=[[ + >>> dict(type='Resize', + >>> scale=(1333, 800), + >>> keep_ratio=True), + >>> ], [ + >>> dict(type='RandomFlip', prob=1.), + >>> dict(type='RandomFlip', prob=0.) + >>> ], [ + >>> dict( + >>> type='PackDetInputs', + >>> meta_keys=('img_id', 'img_path', 'ori_shape', + >>> 'img_shape', 'scale_factor', 'flip', + >>> 'flip_direction')) + >>> ]])] + """ + + def __init__(self, tta_cfg=None, **kwargs): + super().__init__(**kwargs) + self.tta_cfg = tta_cfg + + def merge_aug_bboxes(self, aug_bboxes: List[Tensor], + aug_scores: List[Tensor], + img_metas: List[str]) -> Tuple[Tensor, Tensor]: + """Merge augmented detection bboxes and scores. + + Args: + aug_bboxes (list[Tensor]): shape (n, 4*#class) + aug_scores (list[Tensor] or None): shape (n, #class) + Returns: + tuple[Tensor]: ``bboxes`` with shape (n,4), where + 4 represent (tl_x, tl_y, br_x, br_y) + and ``scores`` with shape (n,). + """ + recovered_bboxes = [] + for bboxes, img_info in zip(aug_bboxes, img_metas): + ori_shape = img_info['ori_shape'] + flip = img_info['flip'] + flip_direction = img_info['flip_direction'] + if flip: + bboxes = bbox_flip( + bboxes=bboxes, + img_shape=ori_shape, + direction=flip_direction) + recovered_bboxes.append(bboxes) + bboxes = torch.cat(recovered_bboxes, dim=0) + if aug_scores is None: + return bboxes + else: + scores = torch.cat(aug_scores, dim=0) + return bboxes, scores + + def merge_preds(self, data_samples_list: List[List[DetDataSample]]): + """Merge batch predictions of enhanced data. + + Args: + data_samples_list (List[List[DetDataSample]]): List of predictions + of all enhanced data. The outer list indicates images, and the + inner list corresponds to the different views of one image. + Each element of the inner list is a ``DetDataSample``. + Returns: + List[DetDataSample]: Merged batch prediction. + """ + merged_data_samples = [] + for data_samples in data_samples_list: + merged_data_samples.append(self._merge_single_sample(data_samples)) + return merged_data_samples + + def _merge_single_sample( + self, data_samples: List[DetDataSample]) -> DetDataSample: + """Merge predictions which come form the different views of one image + to one prediction. + + Args: + data_samples (List[DetDataSample]): List of predictions + of enhanced data which come form one image. + Returns: + List[DetDataSample]: Merged prediction. + """ + aug_bboxes = [] + aug_scores = [] + aug_labels = [] + img_metas = [] + # TODO: support instance segmentation TTA + assert data_samples[0].pred_instances.get('masks', None) is None, \ + 'TTA of instance segmentation does not support now.' + for data_sample in data_samples: + aug_bboxes.append(data_sample.pred_instances.bboxes) + aug_scores.append(data_sample.pred_instances.scores) + aug_labels.append(data_sample.pred_instances.labels) + img_metas.append(data_sample.metainfo) + + merged_bboxes, merged_scores = self.merge_aug_bboxes( + aug_bboxes, aug_scores, img_metas) + merged_labels = torch.cat(aug_labels, dim=0) + + if merged_bboxes.numel() == 0: + return data_samples[0] + + det_bboxes, keep_idxs = batched_nms(merged_bboxes, merged_scores, + merged_labels, self.tta_cfg.nms) + + det_bboxes = det_bboxes[:self.tta_cfg.max_per_img] + det_labels = merged_labels[keep_idxs][:self.tta_cfg.max_per_img] + + results = InstanceData() + _det_bboxes = det_bboxes.clone() + results.bboxes = _det_bboxes[:, :-1] + results.scores = _det_bboxes[:, -1] + results.labels = det_labels + det_results = data_samples[0] + det_results.pred_instances = results + return det_results diff --git a/mmdetection/mmdet/models/test_time_augs/merge_augs.py b/mmdetection/mmdet/models/test_time_augs/merge_augs.py new file mode 100644 index 00000000..5935a861 --- /dev/null +++ b/mmdetection/mmdet/models/test_time_augs/merge_augs.py @@ -0,0 +1,219 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import warnings +from typing import List, Optional, Union + +import numpy as np +import torch +from mmcv.ops import nms +from mmengine.config import ConfigDict +from torch import Tensor + +from mmdet.structures.bbox import bbox_mapping_back + + +# TODO remove this, never be used in mmdet +def merge_aug_proposals(aug_proposals, img_metas, cfg): + """Merge augmented proposals (multiscale, flip, etc.) + + Args: + aug_proposals (list[Tensor]): proposals from different testing + schemes, shape (n, 5). Note that they are not rescaled to the + original image size. + + img_metas (list[dict]): list of image info dict where each dict has: + 'img_shape', 'scale_factor', 'flip', and may also contain + 'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'. + For details on the values of these keys see + `mmdet/datasets/pipelines/formatting.py:Collect`. + + cfg (dict): rpn test config. + + Returns: + Tensor: shape (n, 4), proposals corresponding to original image scale. + """ + + cfg = copy.deepcopy(cfg) + + # deprecate arguments warning + if 'nms' not in cfg or 'max_num' in cfg or 'nms_thr' in cfg: + warnings.warn( + 'In rpn_proposal or test_cfg, ' + 'nms_thr has been moved to a dict named nms as ' + 'iou_threshold, max_num has been renamed as max_per_img, ' + 'name of original arguments and the way to specify ' + 'iou_threshold of NMS will be deprecated.') + if 'nms' not in cfg: + cfg.nms = ConfigDict(dict(type='nms', iou_threshold=cfg.nms_thr)) + if 'max_num' in cfg: + if 'max_per_img' in cfg: + assert cfg.max_num == cfg.max_per_img, f'You set max_num and ' \ + f'max_per_img at the same time, but get {cfg.max_num} ' \ + f'and {cfg.max_per_img} respectively' \ + f'Please delete max_num which will be deprecated.' + else: + cfg.max_per_img = cfg.max_num + if 'nms_thr' in cfg: + assert cfg.nms.iou_threshold == cfg.nms_thr, f'You set ' \ + f'iou_threshold in nms and ' \ + f'nms_thr at the same time, but get ' \ + f'{cfg.nms.iou_threshold} and {cfg.nms_thr}' \ + f' respectively. Please delete the nms_thr ' \ + f'which will be deprecated.' + + recovered_proposals = [] + for proposals, img_info in zip(aug_proposals, img_metas): + img_shape = img_info['img_shape'] + scale_factor = img_info['scale_factor'] + flip = img_info['flip'] + flip_direction = img_info['flip_direction'] + _proposals = proposals.clone() + _proposals[:, :4] = bbox_mapping_back(_proposals[:, :4], img_shape, + scale_factor, flip, + flip_direction) + recovered_proposals.append(_proposals) + aug_proposals = torch.cat(recovered_proposals, dim=0) + merged_proposals, _ = nms(aug_proposals[:, :4].contiguous(), + aug_proposals[:, -1].contiguous(), + cfg.nms.iou_threshold) + scores = merged_proposals[:, 4] + _, order = scores.sort(0, descending=True) + num = min(cfg.max_per_img, merged_proposals.shape[0]) + order = order[:num] + merged_proposals = merged_proposals[order, :] + return merged_proposals + + +# TODO remove this, never be used in mmdet +def merge_aug_bboxes(aug_bboxes, aug_scores, img_metas, rcnn_test_cfg): + """Merge augmented detection bboxes and scores. + + Args: + aug_bboxes (list[Tensor]): shape (n, 4*#class) + aug_scores (list[Tensor] or None): shape (n, #class) + img_shapes (list[Tensor]): shape (3, ). + rcnn_test_cfg (dict): rcnn test config. + + Returns: + tuple: (bboxes, scores) + """ + recovered_bboxes = [] + for bboxes, img_info in zip(aug_bboxes, img_metas): + img_shape = img_info[0]['img_shape'] + scale_factor = img_info[0]['scale_factor'] + flip = img_info[0]['flip'] + flip_direction = img_info[0]['flip_direction'] + bboxes = bbox_mapping_back(bboxes, img_shape, scale_factor, flip, + flip_direction) + recovered_bboxes.append(bboxes) + bboxes = torch.stack(recovered_bboxes).mean(dim=0) + if aug_scores is None: + return bboxes + else: + scores = torch.stack(aug_scores).mean(dim=0) + return bboxes, scores + + +def merge_aug_results(aug_batch_results, aug_batch_img_metas): + """Merge augmented detection results, only bboxes corresponding score under + flipping and multi-scale resizing can be processed now. + + Args: + aug_batch_results (list[list[[obj:`InstanceData`]]): + Detection results of multiple images with + different augmentations. + The outer list indicate the augmentation . The inter + list indicate the batch dimension. + Each item usually contains the following keys. + + - scores (Tensor): Classification scores, in shape + (num_instance,) + - labels (Tensor): Labels of bboxes, in shape + (num_instances,). + - bboxes (Tensor): In shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + aug_batch_img_metas (list[list[dict]]): The outer list + indicates test-time augs (multiscale, flip, etc.) + and the inner list indicates + images in a batch. Each dict in the list contains + information of an image in the batch. + + Returns: + batch_results (list[obj:`InstanceData`]): Same with + the input `aug_results` except that all bboxes have + been mapped to the original scale. + """ + num_augs = len(aug_batch_results) + num_imgs = len(aug_batch_results[0]) + + batch_results = [] + aug_batch_results = copy.deepcopy(aug_batch_results) + for img_id in range(num_imgs): + aug_results = [] + for aug_id in range(num_augs): + img_metas = aug_batch_img_metas[aug_id][img_id] + results = aug_batch_results[aug_id][img_id] + + img_shape = img_metas['img_shape'] + scale_factor = img_metas['scale_factor'] + flip = img_metas['flip'] + flip_direction = img_metas['flip_direction'] + bboxes = bbox_mapping_back(results.bboxes, img_shape, scale_factor, + flip, flip_direction) + results.bboxes = bboxes + aug_results.append(results) + merged_aug_results = results.cat(aug_results) + batch_results.append(merged_aug_results) + + return batch_results + + +def merge_aug_scores(aug_scores): + """Merge augmented bbox scores.""" + if isinstance(aug_scores[0], torch.Tensor): + return torch.mean(torch.stack(aug_scores), dim=0) + else: + return np.mean(aug_scores, axis=0) + + +def merge_aug_masks(aug_masks: List[Tensor], + img_metas: dict, + weights: Optional[Union[list, Tensor]] = None) -> Tensor: + """Merge augmented mask prediction. + + Args: + aug_masks (list[Tensor]): each has shape + (n, c, h, w). + img_metas (dict): Image information. + weights (list or Tensor): Weight of each aug_masks, + the length should be n. + + Returns: + Tensor: has shape (n, c, h, w) + """ + recovered_masks = [] + for i, mask in enumerate(aug_masks): + if weights is not None: + assert len(weights) == len(aug_masks) + weight = weights[i] + else: + weight = 1 + flip = img_metas.get('flip', False) + if flip: + flip_direction = img_metas['flip_direction'] + if flip_direction == 'horizontal': + mask = mask[:, :, :, ::-1] + elif flip_direction == 'vertical': + mask = mask[:, :, ::-1, :] + elif flip_direction == 'diagonal': + mask = mask[:, :, :, ::-1] + mask = mask[:, :, ::-1, :] + else: + raise ValueError( + f"Invalid flipping direction '{flip_direction}'") + recovered_masks.append(mask[None, :] * weight) + + merged_masks = torch.cat(recovered_masks, 0).mean(dim=0) + if weights is not None: + merged_masks = merged_masks * len(weights) / sum(weights) + return merged_masks diff --git a/mmdetection/mmdet/models/trackers/__init__.py b/mmdetection/mmdet/models/trackers/__init__.py new file mode 100644 index 00000000..00284bb7 --- /dev/null +++ b/mmdetection/mmdet/models/trackers/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_tracker import BaseTracker +from .byte_tracker import ByteTracker +from .masktrack_rcnn_tracker import MaskTrackRCNNTracker +from .ocsort_tracker import OCSORTTracker +from .quasi_dense_tracker import QuasiDenseTracker +from .sort_tracker import SORTTracker +from .strongsort_tracker import StrongSORTTracker + +__all__ = [ + 'BaseTracker', 'ByteTracker', 'QuasiDenseTracker', 'SORTTracker', + 'StrongSORTTracker', 'OCSORTTracker', 'MaskTrackRCNNTracker' +] diff --git a/mmdetection/mmdet/models/trackers/base_tracker.py b/mmdetection/mmdet/models/trackers/base_tracker.py new file mode 100644 index 00000000..0cf18865 --- /dev/null +++ b/mmdetection/mmdet/models/trackers/base_tracker.py @@ -0,0 +1,240 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import List, Optional, Tuple + +import torch +import torch.nn.functional as F +from addict import Dict + + +class BaseTracker(metaclass=ABCMeta): + """Base tracker model. + + Args: + momentums (dict[str:float], optional): Momentums to update the buffers. + The `str` indicates the name of the buffer while the `float` + indicates the momentum. Defaults to None. + num_frames_retain (int, optional). If a track is disappeared more than + `num_frames_retain` frames, it will be deleted in the memo. + Defaults to 10. + """ + + def __init__(self, + momentums: Optional[dict] = None, + num_frames_retain: int = 10) -> None: + super().__init__() + if momentums is not None: + assert isinstance(momentums, dict), 'momentums must be a dict' + self.momentums = momentums + self.num_frames_retain = num_frames_retain + + self.reset() + + def reset(self) -> None: + """Reset the buffer of the tracker.""" + self.num_tracks = 0 + self.tracks = dict() + + @property + def empty(self) -> bool: + """Whether the buffer is empty or not.""" + return False if self.tracks else True + + @property + def ids(self) -> List[dict]: + """All ids in the tracker.""" + return list(self.tracks.keys()) + + @property + def with_reid(self) -> bool: + """bool: whether the framework has a reid model""" + return hasattr(self, 'reid') and self.reid is not None + + def update(self, **kwargs) -> None: + """Update the tracker. + + Args: + kwargs (dict[str: Tensor | int]): The `str` indicates the + name of the input variable. `ids` and `frame_ids` are + obligatory in the keys. + """ + memo_items = [k for k, v in kwargs.items() if v is not None] + rm_items = [k for k in kwargs.keys() if k not in memo_items] + for item in rm_items: + kwargs.pop(item) + if not hasattr(self, 'memo_items'): + self.memo_items = memo_items + else: + assert memo_items == self.memo_items + + assert 'ids' in memo_items + num_objs = len(kwargs['ids']) + id_indice = memo_items.index('ids') + assert 'frame_ids' in memo_items + frame_id = int(kwargs['frame_ids']) + if isinstance(kwargs['frame_ids'], int): + kwargs['frame_ids'] = torch.tensor([kwargs['frame_ids']] * + num_objs) + # cur_frame_id = int(kwargs['frame_ids'][0]) + for k, v in kwargs.items(): + if len(v) != num_objs: + raise ValueError('kwargs value must both equal') + + for obj in zip(*kwargs.values()): + id = int(obj[id_indice]) + if id in self.tracks: + self.update_track(id, obj) + else: + self.init_track(id, obj) + + self.pop_invalid_tracks(frame_id) + + def pop_invalid_tracks(self, frame_id: int) -> None: + """Pop out invalid tracks.""" + invalid_ids = [] + for k, v in self.tracks.items(): + if frame_id - v['frame_ids'][-1] >= self.num_frames_retain: + invalid_ids.append(k) + for invalid_id in invalid_ids: + self.tracks.pop(invalid_id) + + def update_track(self, id: int, obj: Tuple[torch.Tensor]): + """Update a track.""" + for k, v in zip(self.memo_items, obj): + v = v[None] + if self.momentums is not None and k in self.momentums: + m = self.momentums[k] + self.tracks[id][k] = (1 - m) * self.tracks[id][k] + m * v + else: + self.tracks[id][k].append(v) + + def init_track(self, id: int, obj: Tuple[torch.Tensor]): + """Initialize a track.""" + self.tracks[id] = Dict() + for k, v in zip(self.memo_items, obj): + v = v[None] + if self.momentums is not None and k in self.momentums: + self.tracks[id][k] = v + else: + self.tracks[id][k] = [v] + + @property + def memo(self) -> dict: + """Return all buffers in the tracker.""" + outs = Dict() + for k in self.memo_items: + outs[k] = [] + + for id, objs in self.tracks.items(): + for k, v in objs.items(): + if k not in outs: + continue + if self.momentums is not None and k in self.momentums: + v = v + else: + v = v[-1] + outs[k].append(v) + + for k, v in outs.items(): + outs[k] = torch.cat(v, dim=0) + return outs + + def get(self, + item: str, + ids: Optional[list] = None, + num_samples: Optional[int] = None, + behavior: Optional[str] = None) -> torch.Tensor: + """Get the buffer of a specific item. + + Args: + item (str): The demanded item. + ids (list[int], optional): The demanded ids. Defaults to None. + num_samples (int, optional): Number of samples to calculate the + results. Defaults to None. + behavior (str, optional): Behavior to calculate the results. + Options are `mean` | None. Defaults to None. + + Returns: + Tensor: The results of the demanded item. + """ + if ids is None: + ids = self.ids + + outs = [] + for id in ids: + out = self.tracks[id][item] + if isinstance(out, list): + if num_samples is not None: + out = out[-num_samples:] + out = torch.cat(out, dim=0) + if behavior == 'mean': + out = out.mean(dim=0, keepdim=True) + elif behavior is None: + out = out[None] + else: + raise NotImplementedError() + else: + out = out[-1] + outs.append(out) + return torch.cat(outs, dim=0) + + @abstractmethod + def track(self, *args, **kwargs): + """Tracking forward function.""" + pass + + def crop_imgs(self, + img: torch.Tensor, + meta_info: dict, + bboxes: torch.Tensor, + rescale: bool = False) -> torch.Tensor: + """Crop the images according to some bounding boxes. Typically for re- + identification sub-module. + + Args: + img (Tensor): of shape (T, C, H, W) encoding input image. + Typically these should be mean centered and std scaled. + meta_info (dict): image information dict where each dict + has: 'img_shape', 'scale_factor', 'flip', and may also contain + 'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'. + bboxes (Tensor): of shape (N, 4) or (N, 5). + rescale (bool, optional): If True, the bounding boxes should be + rescaled to fit the scale of the image. Defaults to False. + + Returns: + Tensor: Image tensor of shape (T, C, H, W). + """ + h, w = meta_info['img_shape'] + img = img[:, :, :h, :w] + if rescale: + factor_x, factor_y = meta_info['scale_factor'] + bboxes[:, :4] *= torch.tensor( + [factor_x, factor_y, factor_x, factor_y]).to(bboxes.device) + bboxes[:, 0] = torch.clamp(bboxes[:, 0], min=0, max=w - 1) + bboxes[:, 1] = torch.clamp(bboxes[:, 1], min=0, max=h - 1) + bboxes[:, 2] = torch.clamp(bboxes[:, 2], min=1, max=w) + bboxes[:, 3] = torch.clamp(bboxes[:, 3], min=1, max=h) + + crop_imgs = [] + for bbox in bboxes: + x1, y1, x2, y2 = map(int, bbox) + if x2 <= x1: + x2 = x1 + 1 + if y2 <= y1: + y2 = y1 + 1 + crop_img = img[:, :, y1:y2, x1:x2] + if self.reid.get('img_scale', False): + crop_img = F.interpolate( + crop_img, + size=self.reid['img_scale'], + mode='bilinear', + align_corners=False) + crop_imgs.append(crop_img) + + if len(crop_imgs) > 0: + return torch.cat(crop_imgs, dim=0) + elif self.reid.get('img_scale', False): + _h, _w = self.reid['img_scale'] + return img.new_zeros((0, 3, _h, _w)) + else: + return img.new_zeros((0, 3, h, w)) diff --git a/mmdetection/mmdet/models/trackers/byte_tracker.py b/mmdetection/mmdet/models/trackers/byte_tracker.py new file mode 100644 index 00000000..11f3adc5 --- /dev/null +++ b/mmdetection/mmdet/models/trackers/byte_tracker.py @@ -0,0 +1,334 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +try: + import lap +except ImportError: + lap = None +import numpy as np +import torch +from mmengine.structures import InstanceData + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import (bbox_cxcyah_to_xyxy, bbox_overlaps, + bbox_xyxy_to_cxcyah) +from .base_tracker import BaseTracker + + +@MODELS.register_module() +class ByteTracker(BaseTracker): + """Tracker for ByteTrack. + + Args: + motion (dict): Configuration of motion. Defaults to None. + obj_score_thrs (dict): Detection score threshold for matching objects. + - high (float): Threshold of the first matching. Defaults to 0.6. + - low (float): Threshold of the second matching. Defaults to 0.1. + init_track_thr (float): Detection score threshold for initializing a + new tracklet. Defaults to 0.7. + weight_iou_with_det_scores (bool): Whether using detection scores to + weight IOU which is used for matching. Defaults to True. + match_iou_thrs (dict): IOU distance threshold for matching between two + frames. + - high (float): Threshold of the first matching. Defaults to 0.1. + - low (float): Threshold of the second matching. Defaults to 0.5. + - tentative (float): Threshold of the matching for tentative + tracklets. Defaults to 0.3. + num_tentatives (int, optional): Number of continuous frames to confirm + a track. Defaults to 3. + """ + + def __init__(self, + motion: Optional[dict] = None, + obj_score_thrs: dict = dict(high=0.6, low=0.1), + init_track_thr: float = 0.7, + weight_iou_with_det_scores: bool = True, + match_iou_thrs: dict = dict(high=0.1, low=0.5, tentative=0.3), + num_tentatives: int = 3, + **kwargs): + super().__init__(**kwargs) + + if lap is None: + raise RuntimeError('lap is not installed,\ + please install it by: pip install lap') + if motion is not None: + self.motion = TASK_UTILS.build(motion) + + self.obj_score_thrs = obj_score_thrs + self.init_track_thr = init_track_thr + + self.weight_iou_with_det_scores = weight_iou_with_det_scores + self.match_iou_thrs = match_iou_thrs + + self.num_tentatives = num_tentatives + + @property + def confirmed_ids(self) -> List: + """Confirmed ids in the tracker.""" + ids = [id for id, track in self.tracks.items() if not track.tentative] + return ids + + @property + def unconfirmed_ids(self) -> List: + """Unconfirmed ids in the tracker.""" + ids = [id for id, track in self.tracks.items() if track.tentative] + return ids + + def init_track(self, id: int, obj: Tuple[torch.Tensor]) -> None: + """Initialize a track.""" + super().init_track(id, obj) + if self.tracks[id].frame_ids[-1] == 0: + self.tracks[id].tentative = False + else: + self.tracks[id].tentative = True + bbox = bbox_xyxy_to_cxcyah(self.tracks[id].bboxes[-1]) # size = (1, 4) + assert bbox.ndim == 2 and bbox.shape[0] == 1 + bbox = bbox.squeeze(0).cpu().numpy() + self.tracks[id].mean, self.tracks[id].covariance = self.kf.initiate( + bbox) + + def update_track(self, id: int, obj: Tuple[torch.Tensor]) -> None: + """Update a track.""" + super().update_track(id, obj) + if self.tracks[id].tentative: + if len(self.tracks[id]['bboxes']) >= self.num_tentatives: + self.tracks[id].tentative = False + bbox = bbox_xyxy_to_cxcyah(self.tracks[id].bboxes[-1]) # size = (1, 4) + assert bbox.ndim == 2 and bbox.shape[0] == 1 + bbox = bbox.squeeze(0).cpu().numpy() + track_label = self.tracks[id]['labels'][-1] + label_idx = self.memo_items.index('labels') + obj_label = obj[label_idx] + assert obj_label == track_label + self.tracks[id].mean, self.tracks[id].covariance = self.kf.update( + self.tracks[id].mean, self.tracks[id].covariance, bbox) + + def pop_invalid_tracks(self, frame_id: int) -> None: + """Pop out invalid tracks.""" + invalid_ids = [] + for k, v in self.tracks.items(): + # case1: disappeared frames >= self.num_frames_retrain + case1 = frame_id - v['frame_ids'][-1] >= self.num_frames_retain + # case2: tentative tracks but not matched in this frame + case2 = v.tentative and v['frame_ids'][-1] != frame_id + if case1 or case2: + invalid_ids.append(k) + for invalid_id in invalid_ids: + self.tracks.pop(invalid_id) + + def assign_ids( + self, + ids: List[int], + det_bboxes: torch.Tensor, + det_labels: torch.Tensor, + det_scores: torch.Tensor, + weight_iou_with_det_scores: Optional[bool] = False, + match_iou_thr: Optional[float] = 0.5 + ) -> Tuple[np.ndarray, np.ndarray]: + """Assign ids. + + Args: + ids (list[int]): Tracking ids. + det_bboxes (Tensor): of shape (N, 4) + det_labels (Tensor): of shape (N,) + det_scores (Tensor): of shape (N,) + weight_iou_with_det_scores (bool, optional): Whether using + detection scores to weight IOU which is used for matching. + Defaults to False. + match_iou_thr (float, optional): Matching threshold. + Defaults to 0.5. + + Returns: + tuple(np.ndarray, np.ndarray): The assigning ids. + """ + # get track_bboxes + track_bboxes = np.zeros((0, 4)) + for id in ids: + track_bboxes = np.concatenate( + (track_bboxes, self.tracks[id].mean[:4][None]), axis=0) + track_bboxes = torch.from_numpy(track_bboxes).to(det_bboxes) + track_bboxes = bbox_cxcyah_to_xyxy(track_bboxes) + + # compute distance + ious = bbox_overlaps(track_bboxes, det_bboxes) + if weight_iou_with_det_scores: + ious *= det_scores + # support multi-class association + track_labels = torch.tensor([ + self.tracks[id]['labels'][-1] for id in ids + ]).to(det_bboxes.device) + + cate_match = det_labels[None, :] == track_labels[:, None] + # to avoid det and track of different categories are matched + cate_cost = (1 - cate_match.int()) * 1e6 + + dists = (1 - ious + cate_cost).cpu().numpy() + + # bipartite match + if dists.size > 0: + cost, row, col = lap.lapjv( + dists, extend_cost=True, cost_limit=1 - match_iou_thr) + else: + row = np.zeros(len(ids)).astype(np.int32) - 1 + col = np.zeros(len(det_bboxes)).astype(np.int32) - 1 + return row, col + + def track(self, data_sample: DetDataSample, **kwargs) -> InstanceData: + """Tracking forward function. + + Args: + data_sample (:obj:`DetDataSample`): The data sample. + It includes information such as `pred_instances`. + + Returns: + :obj:`InstanceData`: Tracking results of the input images. + Each InstanceData usually contains ``bboxes``, ``labels``, + ``scores`` and ``instances_id``. + """ + metainfo = data_sample.metainfo + bboxes = data_sample.pred_instances.bboxes + labels = data_sample.pred_instances.labels + scores = data_sample.pred_instances.scores + + frame_id = metainfo.get('frame_id', -1) + if frame_id == 0: + self.reset() + if not hasattr(self, 'kf'): + self.kf = self.motion + + if self.empty or bboxes.size(0) == 0: + valid_inds = scores > self.init_track_thr + scores = scores[valid_inds] + bboxes = bboxes[valid_inds] + labels = labels[valid_inds] + num_new_tracks = bboxes.size(0) + ids = torch.arange(self.num_tracks, + self.num_tracks + num_new_tracks).to(labels) + self.num_tracks += num_new_tracks + + else: + # 0. init + ids = torch.full((bboxes.size(0), ), + -1, + dtype=labels.dtype, + device=labels.device) + + # get the detection bboxes for the first association + first_det_inds = scores > self.obj_score_thrs['high'] + first_det_bboxes = bboxes[first_det_inds] + first_det_labels = labels[first_det_inds] + first_det_scores = scores[first_det_inds] + first_det_ids = ids[first_det_inds] + + # get the detection bboxes for the second association + second_det_inds = (~first_det_inds) & ( + scores > self.obj_score_thrs['low']) + second_det_bboxes = bboxes[second_det_inds] + second_det_labels = labels[second_det_inds] + second_det_scores = scores[second_det_inds] + second_det_ids = ids[second_det_inds] + + # 1. use Kalman Filter to predict current location + for id in self.confirmed_ids: + # track is lost in previous frame + if self.tracks[id].frame_ids[-1] != frame_id - 1: + self.tracks[id].mean[7] = 0 + (self.tracks[id].mean, + self.tracks[id].covariance) = self.kf.predict( + self.tracks[id].mean, self.tracks[id].covariance) + + # 2. first match + first_match_track_inds, first_match_det_inds = self.assign_ids( + self.confirmed_ids, first_det_bboxes, first_det_labels, + first_det_scores, self.weight_iou_with_det_scores, + self.match_iou_thrs['high']) + # '-1' mean a detection box is not matched with tracklets in + # previous frame + valid = first_match_det_inds > -1 + first_det_ids[valid] = torch.tensor( + self.confirmed_ids)[first_match_det_inds[valid]].to(labels) + + first_match_det_bboxes = first_det_bboxes[valid] + first_match_det_labels = first_det_labels[valid] + first_match_det_scores = first_det_scores[valid] + first_match_det_ids = first_det_ids[valid] + assert (first_match_det_ids > -1).all() + + first_unmatch_det_bboxes = first_det_bboxes[~valid] + first_unmatch_det_labels = first_det_labels[~valid] + first_unmatch_det_scores = first_det_scores[~valid] + first_unmatch_det_ids = first_det_ids[~valid] + assert (first_unmatch_det_ids == -1).all() + + # 3. use unmatched detection bboxes from the first match to match + # the unconfirmed tracks + (tentative_match_track_inds, + tentative_match_det_inds) = self.assign_ids( + self.unconfirmed_ids, first_unmatch_det_bboxes, + first_unmatch_det_labels, first_unmatch_det_scores, + self.weight_iou_with_det_scores, + self.match_iou_thrs['tentative']) + valid = tentative_match_det_inds > -1 + first_unmatch_det_ids[valid] = torch.tensor(self.unconfirmed_ids)[ + tentative_match_det_inds[valid]].to(labels) + + # 4. second match for unmatched tracks from the first match + first_unmatch_track_ids = [] + for i, id in enumerate(self.confirmed_ids): + # tracklet is not matched in the first match + case_1 = first_match_track_inds[i] == -1 + # tracklet is not lost in the previous frame + case_2 = self.tracks[id].frame_ids[-1] == frame_id - 1 + if case_1 and case_2: + first_unmatch_track_ids.append(id) + + second_match_track_inds, second_match_det_inds = self.assign_ids( + first_unmatch_track_ids, second_det_bboxes, second_det_labels, + second_det_scores, False, self.match_iou_thrs['low']) + valid = second_match_det_inds > -1 + second_det_ids[valid] = torch.tensor(first_unmatch_track_ids)[ + second_match_det_inds[valid]].to(ids) + + # 5. gather all matched detection bboxes from step 2-4 + # we only keep matched detection bboxes in second match, which + # means the id != -1 + valid = second_det_ids > -1 + bboxes = torch.cat( + (first_match_det_bboxes, first_unmatch_det_bboxes), dim=0) + bboxes = torch.cat((bboxes, second_det_bboxes[valid]), dim=0) + + labels = torch.cat( + (first_match_det_labels, first_unmatch_det_labels), dim=0) + labels = torch.cat((labels, second_det_labels[valid]), dim=0) + + scores = torch.cat( + (first_match_det_scores, first_unmatch_det_scores), dim=0) + scores = torch.cat((scores, second_det_scores[valid]), dim=0) + + ids = torch.cat((first_match_det_ids, first_unmatch_det_ids), + dim=0) + ids = torch.cat((ids, second_det_ids[valid]), dim=0) + + # 6. assign new ids + new_track_inds = ids == -1 + ids[new_track_inds] = torch.arange( + self.num_tracks, + self.num_tracks + new_track_inds.sum()).to(labels) + self.num_tracks += new_track_inds.sum() + + self.update( + ids=ids, + bboxes=bboxes, + scores=scores, + labels=labels, + frame_ids=frame_id) + + # update pred_track_instances + pred_track_instances = InstanceData() + pred_track_instances.bboxes = bboxes + pred_track_instances.labels = labels + pred_track_instances.scores = scores + pred_track_instances.instances_id = ids + + return pred_track_instances diff --git a/mmdetection/mmdet/models/trackers/masktrack_rcnn_tracker.py b/mmdetection/mmdet/models/trackers/masktrack_rcnn_tracker.py new file mode 100644 index 00000000..cc167786 --- /dev/null +++ b/mmdetection/mmdet/models/trackers/masktrack_rcnn_tracker.py @@ -0,0 +1,189 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import bbox_overlaps +from .base_tracker import BaseTracker + + +@MODELS.register_module() +class MaskTrackRCNNTracker(BaseTracker): + """Tracker for MaskTrack R-CNN. + + Args: + match_weights (dict[str : float]): The Weighting factor when computing + the match score. It contains keys as follows: + + - det_score (float): The coefficient of `det_score` when computing + match score. + - iou (float): The coefficient of `ious` when computing match + score. + - det_label (float): The coefficient of `label_deltas` when + computing match score. + """ + + def __init__(self, + match_weights: dict = dict( + det_score=1.0, iou=2.0, det_label=10.0), + **kwargs): + super().__init__(**kwargs) + self.match_weights = match_weights + + def get_match_score(self, bboxes: Tensor, labels: Tensor, scores: Tensor, + prev_bboxes: Tensor, prev_labels: Tensor, + similarity_logits: Tensor) -> Tensor: + """Get the match score. + + Args: + bboxes (torch.Tensor): of shape (num_current_bboxes, 4) in + [tl_x, tl_y, br_x, br_y] format. Denoting the detection + bboxes of current frame. + labels (torch.Tensor): of shape (num_current_bboxes, ) + scores (torch.Tensor): of shape (num_current_bboxes, ) + prev_bboxes (torch.Tensor): of shape (num_previous_bboxes, 4) in + [tl_x, tl_y, br_x, br_y] format. Denoting the detection bboxes + of previous frame. + prev_labels (torch.Tensor): of shape (num_previous_bboxes, ) + similarity_logits (torch.Tensor): of shape (num_current_bboxes, + num_previous_bboxes + 1). Denoting the similarity logits from + track head. + + Returns: + torch.Tensor: The matching score of shape (num_current_bboxes, + num_previous_bboxes + 1) + """ + similarity_scores = similarity_logits.softmax(dim=1) + + ious = bbox_overlaps(bboxes, prev_bboxes) + iou_dummy = ious.new_zeros(ious.shape[0], 1) + ious = torch.cat((iou_dummy, ious), dim=1) + + label_deltas = (labels.view(-1, 1) == prev_labels).float() + label_deltas_dummy = label_deltas.new_ones(label_deltas.shape[0], 1) + label_deltas = torch.cat((label_deltas_dummy, label_deltas), dim=1) + + match_score = similarity_scores.log() + match_score += self.match_weights['det_score'] * \ + scores.view(-1, 1).log() + match_score += self.match_weights['iou'] * ious + match_score += self.match_weights['det_label'] * label_deltas + + return match_score + + def assign_ids(self, match_scores: Tensor): + num_prev_bboxes = match_scores.shape[1] - 1 + _, match_ids = match_scores.max(dim=1) + + ids = match_ids.new_zeros(match_ids.shape[0]) - 1 + best_match_scores = match_scores.new_zeros(num_prev_bboxes) - 1e6 + for idx, match_id in enumerate(match_ids): + if match_id == 0: + ids[idx] = self.num_tracks + self.num_tracks += 1 + else: + match_score = match_scores[idx, match_id] + # TODO: fix the bug where multiple candidate might match + # with the same previous object. + if match_score > best_match_scores[match_id - 1]: + ids[idx] = self.ids[match_id - 1] + best_match_scores[match_id - 1] = match_score + return ids, best_match_scores + + def track(self, + model: torch.nn.Module, + feats: List[torch.Tensor], + data_sample: DetDataSample, + rescale=True, + **kwargs) -> InstanceData: + """Tracking forward function. + + Args: + model (nn.Module): VIS model. + img (Tensor): of shape (T, C, H, W) encoding input image. + Typically these should be mean centered and std scaled. + The T denotes the number of key images and usually is 1 in + MaskTrackRCNN method. + feats (list[Tensor]): Multi level feature maps of `img`. + data_sample (:obj:`TrackDataSample`): The data sample. + It includes information such as `pred_det_instances`. + rescale (bool, optional): If True, the bounding boxes should be + rescaled to fit the original scale of the image. Defaults to + True. + + Returns: + :obj:`InstanceData`: Tracking results of the input images. + Each InstanceData usually contains ``bboxes``, ``labels``, + ``scores`` and ``instances_id``. + """ + metainfo = data_sample.metainfo + bboxes = data_sample.pred_instances.bboxes + masks = data_sample.pred_instances.masks + labels = data_sample.pred_instances.labels + scores = data_sample.pred_instances.scores + + frame_id = metainfo.get('frame_id', -1) + # create pred_track_instances + pred_track_instances = InstanceData() + + if bboxes.shape[0] == 0: + ids = torch.zeros_like(labels) + pred_track_instances = data_sample.pred_instances.clone() + pred_track_instances.instances_id = ids + return pred_track_instances + + rescaled_bboxes = bboxes.clone() + if rescale: + scale_factor = rescaled_bboxes.new_tensor( + metainfo['scale_factor']).repeat((1, 2)) + rescaled_bboxes = rescaled_bboxes * scale_factor + roi_feats, _ = model.track_head.extract_roi_feats( + feats, [rescaled_bboxes]) + + if self.empty: + num_new_tracks = bboxes.size(0) + ids = torch.arange( + self.num_tracks, + self.num_tracks + num_new_tracks, + dtype=torch.long) + self.num_tracks += num_new_tracks + else: + prev_bboxes = self.get('bboxes') + prev_labels = self.get('labels') + prev_roi_feats = self.get('roi_feats') + + similarity_logits = model.track_head.predict( + roi_feats, prev_roi_feats) + match_scores = self.get_match_score(bboxes, labels, scores, + prev_bboxes, prev_labels, + similarity_logits) + ids, _ = self.assign_ids(match_scores) + + valid_inds = ids > -1 + ids = ids[valid_inds] + bboxes = bboxes[valid_inds] + labels = labels[valid_inds] + scores = scores[valid_inds] + masks = masks[valid_inds] + roi_feats = roi_feats[valid_inds] + + self.update( + ids=ids, + bboxes=bboxes, + labels=labels, + scores=scores, + masks=masks, + roi_feats=roi_feats, + frame_ids=frame_id) + # update pred_track_instances + pred_track_instances.bboxes = bboxes + pred_track_instances.masks = masks + pred_track_instances.labels = labels + pred_track_instances.scores = scores + pred_track_instances.instances_id = ids + + return pred_track_instances diff --git a/mmdetection/mmdet/models/trackers/ocsort_tracker.py b/mmdetection/mmdet/models/trackers/ocsort_tracker.py new file mode 100644 index 00000000..4e09990c --- /dev/null +++ b/mmdetection/mmdet/models/trackers/ocsort_tracker.py @@ -0,0 +1,531 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +try: + import lap +except ImportError: + lap = None +import numpy as np +import torch +from addict import Dict +from mmengine.structures import InstanceData + +from mmdet.registry import MODELS +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import (bbox_cxcyah_to_xyxy, bbox_overlaps, + bbox_xyxy_to_cxcyah) +from .sort_tracker import SORTTracker + + +@MODELS.register_module() +class OCSORTTracker(SORTTracker): + """Tracker for OC-SORT. + + Args: + motion (dict): Configuration of motion. Defaults to None. + obj_score_thrs (float): Detection score threshold for matching objects. + Defaults to 0.3. + init_track_thr (float): Detection score threshold for initializing a + new tracklet. Defaults to 0.7. + weight_iou_with_det_scores (bool): Whether using detection scores to + weight IOU which is used for matching. Defaults to True. + match_iou_thr (float): IOU distance threshold for matching between two + frames. Defaults to 0.3. + num_tentatives (int, optional): Number of continuous frames to confirm + a track. Defaults to 3. + vel_consist_weight (float): Weight of the velocity consistency term in + association (OCM term in the paper). + vel_delta_t (int): The difference of time step for calculating of the + velocity direction of tracklets. + init_cfg (dict or list[dict], optional): Initialization config dict. + Defaults to None. + """ + + def __init__(self, + motion: Optional[dict] = None, + obj_score_thr: float = 0.3, + init_track_thr: float = 0.7, + weight_iou_with_det_scores: bool = True, + match_iou_thr: float = 0.3, + num_tentatives: int = 3, + vel_consist_weight: float = 0.2, + vel_delta_t: int = 3, + **kwargs): + if lap is None: + raise RuntimeError('lap is not installed,\ + please install it by: pip install lap') + super().__init__(motion=motion, **kwargs) + self.obj_score_thr = obj_score_thr + self.init_track_thr = init_track_thr + + self.weight_iou_with_det_scores = weight_iou_with_det_scores + self.match_iou_thr = match_iou_thr + self.vel_consist_weight = vel_consist_weight + self.vel_delta_t = vel_delta_t + + self.num_tentatives = num_tentatives + + @property + def unconfirmed_ids(self): + """Unconfirmed ids in the tracker.""" + ids = [id for id, track in self.tracks.items() if track.tentative] + return ids + + def init_track(self, id: int, obj: Tuple[torch.Tensor]): + """Initialize a track.""" + super().init_track(id, obj) + if self.tracks[id].frame_ids[-1] == 0: + self.tracks[id].tentative = False + else: + self.tracks[id].tentative = True + bbox = bbox_xyxy_to_cxcyah(self.tracks[id].bboxes[-1]) # size = (1, 4) + assert bbox.ndim == 2 and bbox.shape[0] == 1 + bbox = bbox.squeeze(0).cpu().numpy() + self.tracks[id].mean, self.tracks[id].covariance = self.kf.initiate( + bbox) + # track.obs maintains the history associated detections to this track + self.tracks[id].obs = [] + bbox_id = self.memo_items.index('bboxes') + self.tracks[id].obs.append(obj[bbox_id]) + # a placefolder to save mean/covariance before losing tracking it + # parameters to save: mean, covariance, measurement + self.tracks[id].tracked = True + self.tracks[id].saved_attr = Dict() + self.tracks[id].velocity = torch.tensor( + (-1, -1)).to(obj[bbox_id].device) # placeholder + + def update_track(self, id: int, obj: Tuple[torch.Tensor]): + """Update a track.""" + super().update_track(id, obj) + if self.tracks[id].tentative: + if len(self.tracks[id]['bboxes']) >= self.num_tentatives: + self.tracks[id].tentative = False + bbox = bbox_xyxy_to_cxcyah(self.tracks[id].bboxes[-1]) # size = (1, 4) + assert bbox.ndim == 2 and bbox.shape[0] == 1 + bbox = bbox.squeeze(0).cpu().numpy() + self.tracks[id].mean, self.tracks[id].covariance = self.kf.update( + self.tracks[id].mean, self.tracks[id].covariance, bbox) + self.tracks[id].tracked = True + bbox_id = self.memo_items.index('bboxes') + self.tracks[id].obs.append(obj[bbox_id]) + + bbox1 = self.k_step_observation(self.tracks[id]) + bbox2 = obj[bbox_id] + self.tracks[id].velocity = self.vel_direction(bbox1, bbox2).to( + obj[bbox_id].device) + + def vel_direction(self, bbox1: torch.Tensor, bbox2: torch.Tensor): + """Estimate the direction vector between two boxes.""" + if bbox1.sum() < 0 or bbox2.sum() < 0: + return torch.tensor((-1, -1)) + cx1, cy1 = (bbox1[0] + bbox1[2]) / 2.0, (bbox1[1] + bbox1[3]) / 2.0 + cx2, cy2 = (bbox2[0] + bbox2[2]) / 2.0, (bbox2[1] + bbox2[3]) / 2.0 + speed = torch.tensor([cy2 - cy1, cx2 - cx1]) + norm = torch.sqrt((speed[0])**2 + (speed[1])**2) + 1e-6 + return speed / norm + + def vel_direction_batch(self, bboxes1: torch.Tensor, + bboxes2: torch.Tensor): + """Estimate the direction vector given two batches of boxes.""" + cx1, cy1 = (bboxes1[:, 0] + bboxes1[:, 2]) / 2.0, (bboxes1[:, 1] + + bboxes1[:, 3]) / 2.0 + cx2, cy2 = (bboxes2[:, 0] + bboxes2[:, 2]) / 2.0, (bboxes2[:, 1] + + bboxes2[:, 3]) / 2.0 + speed_diff_y = cy2[None, :] - cy1[:, None] + speed_diff_x = cx2[None, :] - cx1[:, None] + speed = torch.cat((speed_diff_y[..., None], speed_diff_x[..., None]), + dim=-1) + norm = torch.sqrt((speed[:, :, 0])**2 + (speed[:, :, 1])**2) + 1e-6 + speed[:, :, 0] /= norm + speed[:, :, 1] /= norm + return speed + + def k_step_observation(self, track: Dict): + """return the observation k step away before.""" + obs_seqs = track.obs + num_obs = len(obs_seqs) + if num_obs == 0: + return torch.tensor((-1, -1, -1, -1)).to(track.obs[0].device) + elif num_obs > self.vel_delta_t: + if obs_seqs[num_obs - 1 - self.vel_delta_t] is not None: + return obs_seqs[num_obs - 1 - self.vel_delta_t] + else: + return self.last_obs(track) + else: + return self.last_obs(track) + + def ocm_assign_ids(self, + ids: List[int], + det_bboxes: torch.Tensor, + det_labels: torch.Tensor, + det_scores: torch.Tensor, + weight_iou_with_det_scores: Optional[bool] = False, + match_iou_thr: Optional[float] = 0.5): + """Apply Observation-Centric Momentum (OCM) to assign ids. + + OCM adds movement direction consistency into the association cost + matrix. This term requires no additional assumption but from the + same linear motion assumption as the canonical Kalman Filter in SORT. + + Args: + ids (list[int]): Tracking ids. + det_bboxes (Tensor): of shape (N, 4) + det_labels (Tensor): of shape (N,) + det_scores (Tensor): of shape (N,) + weight_iou_with_det_scores (bool, optional): Whether using + detection scores to weight IOU which is used for matching. + Defaults to False. + match_iou_thr (float, optional): Matching threshold. + Defaults to 0.5. + + Returns: + tuple(int): The assigning ids. + + OC-SORT uses velocity consistency besides IoU for association + """ + # get track_bboxes + track_bboxes = np.zeros((0, 4)) + for id in ids: + track_bboxes = np.concatenate( + (track_bboxes, self.tracks[id].mean[:4][None]), axis=0) + track_bboxes = torch.from_numpy(track_bboxes).to(det_bboxes) + track_bboxes = bbox_cxcyah_to_xyxy(track_bboxes) + + # compute distance + ious = bbox_overlaps(track_bboxes, det_bboxes) + if weight_iou_with_det_scores: + ious *= det_scores + + # support multi-class association + track_labels = torch.tensor([ + self.tracks[id]['labels'][-1] for id in ids + ]).to(det_bboxes.device) + cate_match = det_labels[None, :] == track_labels[:, None] + # to avoid det and track of different categories are matched + cate_cost = (1 - cate_match.int()) * 1e6 + + dists = (1 - ious + cate_cost).cpu().numpy() + + if len(ids) > 0 and len(det_bboxes) > 0: + track_velocities = torch.stack( + [self.tracks[id].velocity for id in ids]).to(det_bboxes.device) + k_step_observations = torch.stack([ + self.k_step_observation(self.tracks[id]) for id in ids + ]).to(det_bboxes.device) + # valid1: if the track has previous observations to estimate speed + # valid2: if the associated observation k steps ago is a detection + valid1 = track_velocities.sum(dim=1) != -2 + valid2 = k_step_observations.sum(dim=1) != -4 + valid = valid1 & valid2 + + vel_to_match = self.vel_direction_batch(k_step_observations, + det_bboxes) + track_velocities = track_velocities[:, None, :].repeat( + 1, det_bboxes.shape[0], 1) + + angle_cos = (vel_to_match * track_velocities).sum(dim=-1) + angle_cos = torch.clamp(angle_cos, min=-1, max=1) + angle = torch.acos(angle_cos) # [0, pi] + norm_angle = (angle - np.pi / 2.) / np.pi # [-0.5, 0.5] + valid_matrix = valid[:, None].int().repeat(1, det_bboxes.shape[0]) + # set non-valid entries 0 + valid_norm_angle = norm_angle * valid_matrix + + dists += valid_norm_angle.cpu().numpy() * self.vel_consist_weight + + # bipartite match + if dists.size > 0: + cost, row, col = lap.lapjv( + dists, extend_cost=True, cost_limit=1 - match_iou_thr) + else: + row = np.zeros(len(ids)).astype(np.int32) - 1 + col = np.zeros(len(det_bboxes)).astype(np.int32) - 1 + return row, col + + def last_obs(self, track: Dict): + """extract the last associated observation.""" + for bbox in track.obs[::-1]: + if bbox is not None: + return bbox + + def ocr_assign_ids(self, + track_obs: torch.Tensor, + last_track_labels: torch.Tensor, + det_bboxes: torch.Tensor, + det_labels: torch.Tensor, + det_scores: torch.Tensor, + weight_iou_with_det_scores: Optional[bool] = False, + match_iou_thr: Optional[float] = 0.5): + """association for Observation-Centric Recovery. + + As try to recover tracks from being lost whose estimated velocity is + out- to-date, we use IoU-only matching strategy. + + Args: + track_obs (Tensor): the list of historical associated + detections of tracks + det_bboxes (Tensor): of shape (N, 5), unmatched detections + det_labels (Tensor): of shape (N,) + det_scores (Tensor): of shape (N,) + weight_iou_with_det_scores (bool, optional): Whether using + detection scores to weight IOU which is used for matching. + Defaults to False. + match_iou_thr (float, optional): Matching threshold. + Defaults to 0.5. + + Returns: + tuple(int): The assigning ids. + """ + # compute distance + ious = bbox_overlaps(track_obs, det_bboxes) + if weight_iou_with_det_scores: + ious *= det_scores + + # support multi-class association + cate_match = det_labels[None, :] == last_track_labels[:, None] + # to avoid det and track of different categories are matched + cate_cost = (1 - cate_match.int()) * 1e6 + + dists = (1 - ious + cate_cost).cpu().numpy() + + # bipartite match + if dists.size > 0: + cost, row, col = lap.lapjv( + dists, extend_cost=True, cost_limit=1 - match_iou_thr) + else: + row = np.zeros(len(track_obs)).astype(np.int32) - 1 + col = np.zeros(len(det_bboxes)).astype(np.int32) - 1 + return row, col + + def online_smooth(self, track: Dict, obj: torch.Tensor): + """Once a track is recovered from being lost, online smooth its + parameters to fix the error accumulated during being lost. + + NOTE: you can use different virtual trajectory generation + strategies, we adopt the naive linear interpolation as default + """ + last_match_bbox = self.last_obs(track) + new_match_bbox = obj + unmatch_len = 0 + for bbox in track.obs[::-1]: + if bbox is None: + unmatch_len += 1 + else: + break + bbox_shift_per_step = (new_match_bbox - last_match_bbox) / ( + unmatch_len + 1) + track.mean = track.saved_attr.mean + track.covariance = track.saved_attr.covariance + for i in range(unmatch_len): + virtual_bbox = last_match_bbox + (i + 1) * bbox_shift_per_step + virtual_bbox = bbox_xyxy_to_cxcyah(virtual_bbox[None, :]) + virtual_bbox = virtual_bbox.squeeze(0).cpu().numpy() + track.mean, track.covariance = self.kf.update( + track.mean, track.covariance, virtual_bbox) + + def track(self, data_sample: DetDataSample, **kwargs) -> InstanceData: + """Tracking forward function. + NOTE: this implementation is slightly different from the original + OC-SORT implementation (https://github.com/noahcao/OC_SORT)that we + do association between detections and tentative/non-tentative tracks + independently while the original implementation combines them together. + + Args: + data_sample (:obj:`DetDataSample`): The data sample. + It includes information such as `pred_instances`. + + Returns: + :obj:`InstanceData`: Tracking results of the input images. + Each InstanceData usually contains ``bboxes``, ``labels``, + ``scores`` and ``instances_id``. + """ + metainfo = data_sample.metainfo + bboxes = data_sample.pred_instances.bboxes + labels = data_sample.pred_instances.labels + scores = data_sample.pred_instances.scores + frame_id = metainfo.get('frame_id', -1) + if frame_id == 0: + self.reset() + if not hasattr(self, 'kf'): + self.kf = self.motion + + if self.empty or bboxes.size(0) == 0: + valid_inds = scores > self.init_track_thr + scores = scores[valid_inds] + bboxes = bboxes[valid_inds] + labels = labels[valid_inds] + num_new_tracks = bboxes.size(0) + ids = torch.arange(self.num_tracks, + self.num_tracks + num_new_tracks).to(labels) + self.num_tracks += num_new_tracks + else: + # 0. init + ids = torch.full((bboxes.size(0), ), + -1, + dtype=labels.dtype, + device=labels.device) + + # get the detection bboxes for the first association + det_inds = scores > self.obj_score_thr + det_bboxes = bboxes[det_inds] + det_labels = labels[det_inds] + det_scores = scores[det_inds] + det_ids = ids[det_inds] + + # 1. predict by Kalman Filter + for id in self.confirmed_ids: + # track is lost in previous frame + if self.tracks[id].frame_ids[-1] != frame_id - 1: + self.tracks[id].mean[7] = 0 + if self.tracks[id].tracked: + self.tracks[id].saved_attr.mean = self.tracks[id].mean + self.tracks[id].saved_attr.covariance = self.tracks[ + id].covariance + (self.tracks[id].mean, + self.tracks[id].covariance) = self.kf.predict( + self.tracks[id].mean, self.tracks[id].covariance) + + # 2. match detections and tracks' predicted locations + match_track_inds, raw_match_det_inds = self.ocm_assign_ids( + self.confirmed_ids, det_bboxes, det_labels, det_scores, + self.weight_iou_with_det_scores, self.match_iou_thr) + # '-1' mean a detection box is not matched with tracklets in + # previous frame + valid = raw_match_det_inds > -1 + det_ids[valid] = torch.tensor( + self.confirmed_ids)[raw_match_det_inds[valid]].to(labels) + + match_det_bboxes = det_bboxes[valid] + match_det_labels = det_labels[valid] + match_det_scores = det_scores[valid] + match_det_ids = det_ids[valid] + assert (match_det_ids > -1).all() + + # unmatched tracks and detections + unmatch_det_bboxes = det_bboxes[~valid] + unmatch_det_labels = det_labels[~valid] + unmatch_det_scores = det_scores[~valid] + unmatch_det_ids = det_ids[~valid] + assert (unmatch_det_ids == -1).all() + + # 3. use unmatched detection bboxes from the first match to match + # the unconfirmed tracks + (tentative_match_track_inds, + tentative_match_det_inds) = self.ocm_assign_ids( + self.unconfirmed_ids, unmatch_det_bboxes, unmatch_det_labels, + unmatch_det_scores, self.weight_iou_with_det_scores, + self.match_iou_thr) + valid = tentative_match_det_inds > -1 + unmatch_det_ids[valid] = torch.tensor(self.unconfirmed_ids)[ + tentative_match_det_inds[valid]].to(labels) + + match_det_bboxes = torch.cat( + (match_det_bboxes, unmatch_det_bboxes[valid]), dim=0) + match_det_labels = torch.cat( + (match_det_labels, unmatch_det_labels[valid]), dim=0) + match_det_scores = torch.cat( + (match_det_scores, unmatch_det_scores[valid]), dim=0) + match_det_ids = torch.cat((match_det_ids, unmatch_det_ids[valid]), + dim=0) + assert (match_det_ids > -1).all() + + unmatch_det_bboxes = unmatch_det_bboxes[~valid] + unmatch_det_labels = unmatch_det_labels[~valid] + unmatch_det_scores = unmatch_det_scores[~valid] + unmatch_det_ids = unmatch_det_ids[~valid] + assert (unmatch_det_ids == -1).all() + + all_track_ids = [id for id, _ in self.tracks.items()] + unmatched_track_inds = torch.tensor( + [ind for ind in all_track_ids if ind not in match_det_ids]) + + if len(unmatched_track_inds) > 0: + # 4. still some tracks not associated yet, perform OCR + last_observations = [] + for id in unmatched_track_inds: + last_box = self.last_obs(self.tracks[id.item()]) + last_observations.append(last_box) + last_observations = torch.stack(last_observations) + last_track_labels = torch.tensor([ + self.tracks[id.item()]['labels'][-1] + for id in unmatched_track_inds + ]).to(det_bboxes.device) + + remain_det_ids = torch.full((unmatch_det_bboxes.size(0), ), + -1, + dtype=labels.dtype, + device=labels.device) + + _, ocr_match_det_inds = self.ocr_assign_ids( + last_observations, last_track_labels, unmatch_det_bboxes, + unmatch_det_labels, unmatch_det_scores, + self.weight_iou_with_det_scores, self.match_iou_thr) + + valid = ocr_match_det_inds > -1 + remain_det_ids[valid] = unmatched_track_inds.clone()[ + ocr_match_det_inds[valid]].to(labels) + + ocr_match_det_bboxes = unmatch_det_bboxes[valid] + ocr_match_det_labels = unmatch_det_labels[valid] + ocr_match_det_scores = unmatch_det_scores[valid] + ocr_match_det_ids = remain_det_ids[valid] + assert (ocr_match_det_ids > -1).all() + + ocr_unmatch_det_bboxes = unmatch_det_bboxes[~valid] + ocr_unmatch_det_labels = unmatch_det_labels[~valid] + ocr_unmatch_det_scores = unmatch_det_scores[~valid] + ocr_unmatch_det_ids = remain_det_ids[~valid] + assert (ocr_unmatch_det_ids == -1).all() + + unmatch_det_bboxes = ocr_unmatch_det_bboxes + unmatch_det_labels = ocr_unmatch_det_labels + unmatch_det_scores = ocr_unmatch_det_scores + unmatch_det_ids = ocr_unmatch_det_ids + match_det_bboxes = torch.cat( + (match_det_bboxes, ocr_match_det_bboxes), dim=0) + match_det_labels = torch.cat( + (match_det_labels, ocr_match_det_labels), dim=0) + match_det_scores = torch.cat( + (match_det_scores, ocr_match_det_scores), dim=0) + match_det_ids = torch.cat((match_det_ids, ocr_match_det_ids), + dim=0) + + # 5. summarize the track results + for i in range(len(match_det_ids)): + det_bbox = match_det_bboxes[i] + track_id = match_det_ids[i].item() + if not self.tracks[track_id].tracked: + # the track is lost before this step + self.online_smooth(self.tracks[track_id], det_bbox) + + for track_id in all_track_ids: + if track_id not in match_det_ids: + self.tracks[track_id].tracked = False + self.tracks[track_id].obs.append(None) + + bboxes = torch.cat((match_det_bboxes, unmatch_det_bboxes), dim=0) + labels = torch.cat((match_det_labels, unmatch_det_labels), dim=0) + scores = torch.cat((match_det_scores, unmatch_det_scores), dim=0) + ids = torch.cat((match_det_ids, unmatch_det_ids), dim=0) + # 6. assign new ids + new_track_inds = ids == -1 + + ids[new_track_inds] = torch.arange( + self.num_tracks, + self.num_tracks + new_track_inds.sum()).to(labels) + self.num_tracks += new_track_inds.sum() + + self.update( + ids=ids, + bboxes=bboxes, + labels=labels, + scores=scores, + frame_ids=frame_id) + + # update pred_track_instances + pred_track_instances = InstanceData() + pred_track_instances.bboxes = bboxes + pred_track_instances.labels = labels + pred_track_instances.scores = scores + pred_track_instances.instances_id = ids + return pred_track_instances diff --git a/mmdetection/mmdet/models/trackers/quasi_dense_tracker.py b/mmdetection/mmdet/models/trackers/quasi_dense_tracker.py new file mode 100644 index 00000000..c93c3c4c --- /dev/null +++ b/mmdetection/mmdet/models/trackers/quasi_dense_tracker.py @@ -0,0 +1,316 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +import torch.nn.functional as F +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS +from mmdet.structures import TrackDataSample +from mmdet.structures.bbox import bbox_overlaps +from .base_tracker import BaseTracker + + +@MODELS.register_module() +class QuasiDenseTracker(BaseTracker): + """Tracker for Quasi-Dense Tracking. + + Args: + init_score_thr (float): The cls_score threshold to + initialize a new tracklet. Defaults to 0.8. + obj_score_thr (float): The cls_score threshold to + update a tracked tracklet. Defaults to 0.5. + match_score_thr (float): The match threshold. Defaults to 0.5. + memo_tracklet_frames (int): The most frames in a tracklet memory. + Defaults to 10. + memo_backdrop_frames (int): The most frames in the backdrops. + Defaults to 1. + memo_momentum (float): The momentum value for embeds updating. + Defaults to 0.8. + nms_conf_thr (float): The nms threshold for confidence. + Defaults to 0.5. + nms_backdrop_iou_thr (float): The nms threshold for backdrop IoU. + Defaults to 0.3. + nms_class_iou_thr (float): The nms threshold for class IoU. + Defaults to 0.7. + with_cats (bool): Whether to track with the same category. + Defaults to True. + match_metric (str): The match metric. Defaults to 'bisoftmax'. + """ + + def __init__(self, + init_score_thr: float = 0.8, + obj_score_thr: float = 0.5, + match_score_thr: float = 0.5, + memo_tracklet_frames: int = 10, + memo_backdrop_frames: int = 1, + memo_momentum: float = 0.8, + nms_conf_thr: float = 0.5, + nms_backdrop_iou_thr: float = 0.3, + nms_class_iou_thr: float = 0.7, + with_cats: bool = True, + match_metric: str = 'bisoftmax', + **kwargs): + super().__init__(**kwargs) + assert 0 <= memo_momentum <= 1.0 + assert memo_tracklet_frames >= 0 + assert memo_backdrop_frames >= 0 + self.init_score_thr = init_score_thr + self.obj_score_thr = obj_score_thr + self.match_score_thr = match_score_thr + self.memo_tracklet_frames = memo_tracklet_frames + self.memo_backdrop_frames = memo_backdrop_frames + self.memo_momentum = memo_momentum + self.nms_conf_thr = nms_conf_thr + self.nms_backdrop_iou_thr = nms_backdrop_iou_thr + self.nms_class_iou_thr = nms_class_iou_thr + self.with_cats = with_cats + assert match_metric in ['bisoftmax', 'softmax', 'cosine'] + self.match_metric = match_metric + + self.num_tracks = 0 + self.tracks = dict() + self.backdrops = [] + + def reset(self): + """Reset the buffer of the tracker.""" + self.num_tracks = 0 + self.tracks = dict() + self.backdrops = [] + + def update(self, ids: Tensor, bboxes: Tensor, embeds: Tensor, + labels: Tensor, scores: Tensor, frame_id: int) -> None: + """Tracking forward function. + + Args: + ids (Tensor): of shape(N, ). + bboxes (Tensor): of shape (N, 5). + embeds (Tensor): of shape (N, 256). + labels (Tensor): of shape (N, ). + scores (Tensor): of shape (N, ). + frame_id (int): The id of current frame, 0-index. + """ + tracklet_inds = ids > -1 + + for id, bbox, embed, label, score in zip(ids[tracklet_inds], + bboxes[tracklet_inds], + embeds[tracklet_inds], + labels[tracklet_inds], + scores[tracklet_inds]): + id = int(id) + # update the tracked ones and initialize new tracks + if id in self.tracks.keys(): + velocity = (bbox - self.tracks[id]['bbox']) / ( + frame_id - self.tracks[id]['last_frame']) + self.tracks[id]['bbox'] = bbox + self.tracks[id]['embed'] = ( + 1 - self.memo_momentum + ) * self.tracks[id]['embed'] + self.memo_momentum * embed + self.tracks[id]['last_frame'] = frame_id + self.tracks[id]['label'] = label + self.tracks[id]['score'] = score + self.tracks[id]['velocity'] = ( + self.tracks[id]['velocity'] * self.tracks[id]['acc_frame'] + + velocity) / ( + self.tracks[id]['acc_frame'] + 1) + self.tracks[id]['acc_frame'] += 1 + else: + self.tracks[id] = dict( + bbox=bbox, + embed=embed, + label=label, + score=score, + last_frame=frame_id, + velocity=torch.zeros_like(bbox), + acc_frame=0) + # backdrop update according to IoU + backdrop_inds = torch.nonzero(ids == -1, as_tuple=False).squeeze(1) + ious = bbox_overlaps(bboxes[backdrop_inds], bboxes) + for i, ind in enumerate(backdrop_inds): + if (ious[i, :ind] > self.nms_backdrop_iou_thr).any(): + backdrop_inds[i] = -1 + backdrop_inds = backdrop_inds[backdrop_inds > -1] + # old backdrops would be removed at first + self.backdrops.insert( + 0, + dict( + bboxes=bboxes[backdrop_inds], + embeds=embeds[backdrop_inds], + labels=labels[backdrop_inds])) + + # pop memo + invalid_ids = [] + for k, v in self.tracks.items(): + if frame_id - v['last_frame'] >= self.memo_tracklet_frames: + invalid_ids.append(k) + for invalid_id in invalid_ids: + self.tracks.pop(invalid_id) + + if len(self.backdrops) > self.memo_backdrop_frames: + self.backdrops.pop() + + @property + def memo(self) -> Tuple[Tensor, ...]: + """Get tracks memory.""" + memo_embeds = [] + memo_ids = [] + memo_bboxes = [] + memo_labels = [] + # velocity of tracks + memo_vs = [] + # get tracks + for k, v in self.tracks.items(): + memo_bboxes.append(v['bbox'][None, :]) + memo_embeds.append(v['embed'][None, :]) + memo_ids.append(k) + memo_labels.append(v['label'].view(1, 1)) + memo_vs.append(v['velocity'][None, :]) + memo_ids = torch.tensor(memo_ids, dtype=torch.long).view(1, -1) + # get backdrops + for backdrop in self.backdrops: + backdrop_ids = torch.full((1, backdrop['embeds'].size(0)), + -1, + dtype=torch.long) + backdrop_vs = torch.zeros_like(backdrop['bboxes']) + memo_bboxes.append(backdrop['bboxes']) + memo_embeds.append(backdrop['embeds']) + memo_ids = torch.cat([memo_ids, backdrop_ids], dim=1) + memo_labels.append(backdrop['labels'][:, None]) + memo_vs.append(backdrop_vs) + + memo_bboxes = torch.cat(memo_bboxes, dim=0) + memo_embeds = torch.cat(memo_embeds, dim=0) + memo_labels = torch.cat(memo_labels, dim=0).squeeze(1) + memo_vs = torch.cat(memo_vs, dim=0) + return memo_bboxes, memo_labels, memo_embeds, memo_ids.squeeze( + 0), memo_vs + + def track(self, + model: torch.nn.Module, + img: torch.Tensor, + feats: List[torch.Tensor], + data_sample: TrackDataSample, + rescale=True, + **kwargs) -> InstanceData: + """Tracking forward function. + + Args: + model (nn.Module): MOT model. + img (Tensor): of shape (T, C, H, W) encoding input image. + Typically these should be mean centered and std scaled. + The T denotes the number of key images and usually is 1 in + QDTrack method. + feats (list[Tensor]): Multi level feature maps of `img`. + data_sample (:obj:`TrackDataSample`): The data sample. + It includes information such as `pred_instances`. + rescale (bool, optional): If True, the bounding boxes should be + rescaled to fit the original scale of the image. Defaults to + True. + + Returns: + :obj:`InstanceData`: Tracking results of the input images. + Each InstanceData usually contains ``bboxes``, ``labels``, + ``scores`` and ``instances_id``. + """ + metainfo = data_sample.metainfo + bboxes = data_sample.pred_instances.bboxes + labels = data_sample.pred_instances.labels + scores = data_sample.pred_instances.scores + + frame_id = metainfo.get('frame_id', -1) + # create pred_track_instances + pred_track_instances = InstanceData() + + # return zero bboxes if there is no track targets + if bboxes.shape[0] == 0: + ids = torch.zeros_like(labels) + pred_track_instances = data_sample.pred_instances.clone() + pred_track_instances.instances_id = ids + return pred_track_instances + + # get track feats + rescaled_bboxes = bboxes.clone() + if rescale: + scale_factor = rescaled_bboxes.new_tensor( + metainfo['scale_factor']).repeat((1, 2)) + rescaled_bboxes = rescaled_bboxes * scale_factor + track_feats = model.track_head.predict(feats, [rescaled_bboxes]) + # sort according to the object_score + _, inds = scores.sort(descending=True) + bboxes = bboxes[inds] + scores = scores[inds] + labels = labels[inds] + embeds = track_feats[inds, :] + + # duplicate removal for potential backdrops and cross classes + valids = bboxes.new_ones((bboxes.size(0))) + ious = bbox_overlaps(bboxes, bboxes) + for i in range(1, bboxes.size(0)): + thr = self.nms_backdrop_iou_thr if scores[ + i] < self.obj_score_thr else self.nms_class_iou_thr + if (ious[i, :i] > thr).any(): + valids[i] = 0 + valids = valids == 1 + bboxes = bboxes[valids] + scores = scores[valids] + labels = labels[valids] + embeds = embeds[valids, :] + + # init ids container + ids = torch.full((bboxes.size(0), ), -1, dtype=torch.long) + + # match if buffer is not empty + if bboxes.size(0) > 0 and not self.empty: + (memo_bboxes, memo_labels, memo_embeds, memo_ids, + memo_vs) = self.memo + + if self.match_metric == 'bisoftmax': + feats = torch.mm(embeds, memo_embeds.t()) + d2t_scores = feats.softmax(dim=1) + t2d_scores = feats.softmax(dim=0) + match_scores = (d2t_scores + t2d_scores) / 2 + elif self.match_metric == 'softmax': + feats = torch.mm(embeds, memo_embeds.t()) + match_scores = feats.softmax(dim=1) + elif self.match_metric == 'cosine': + match_scores = torch.mm( + F.normalize(embeds, p=2, dim=1), + F.normalize(memo_embeds, p=2, dim=1).t()) + else: + raise NotImplementedError + # track with the same category + if self.with_cats: + cat_same = labels.view(-1, 1) == memo_labels.view(1, -1) + match_scores *= cat_same.float().to(match_scores.device) + # track according to match_scores + for i in range(bboxes.size(0)): + conf, memo_ind = torch.max(match_scores[i, :], dim=0) + id = memo_ids[memo_ind] + if conf > self.match_score_thr: + if id > -1: + # keep bboxes with high object score + # and remove background bboxes + if scores[i] > self.obj_score_thr: + ids[i] = id + match_scores[:i, memo_ind] = 0 + match_scores[i + 1:, memo_ind] = 0 + else: + if conf > self.nms_conf_thr: + ids[i] = -2 + # initialize new tracks + new_inds = (ids == -1) & (scores > self.init_score_thr).cpu() + num_news = new_inds.sum() + ids[new_inds] = torch.arange( + self.num_tracks, self.num_tracks + num_news, dtype=torch.long) + self.num_tracks += num_news + + self.update(ids, bboxes, embeds, labels, scores, frame_id) + tracklet_inds = ids > -1 + # update pred_track_instances + pred_track_instances.bboxes = bboxes[tracklet_inds] + pred_track_instances.labels = labels[tracklet_inds] + pred_track_instances.scores = scores[tracklet_inds] + pred_track_instances.instances_id = ids[tracklet_inds] + + return pred_track_instances diff --git a/mmdetection/mmdet/models/trackers/sort_tracker.py b/mmdetection/mmdet/models/trackers/sort_tracker.py new file mode 100644 index 00000000..c4a4fed9 --- /dev/null +++ b/mmdetection/mmdet/models/trackers/sort_tracker.py @@ -0,0 +1,268 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import numpy as np +import torch +from mmengine.structures import InstanceData + +try: + import motmetrics + from motmetrics.lap import linear_sum_assignment +except ImportError: + motmetrics = None +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import bbox_overlaps, bbox_xyxy_to_cxcyah +from mmdet.utils import OptConfigType +from ..utils import imrenormalize +from .base_tracker import BaseTracker + + +@MODELS.register_module() +class SORTTracker(BaseTracker): + """Tracker for SORT/DeepSORT. + + Args: + obj_score_thr (float, optional): Threshold to filter the objects. + Defaults to 0.3. + motion (dict): Configuration of motion. Defaults to None. + reid (dict, optional): Configuration for the ReID model. + - num_samples (int, optional): Number of samples to calculate the + feature embeddings of a track. Default to 10. + - image_scale (tuple, optional): Input scale of the ReID model. + Default to (256, 128). + - img_norm_cfg (dict, optional): Configuration to normalize the + input. Default to None. + - match_score_thr (float, optional): Similarity threshold for the + matching process. Default to 2.0. + match_iou_thr (float, optional): Threshold of the IoU matching process. + Defaults to 0.7. + num_tentatives (int, optional): Number of continuous frames to confirm + a track. Defaults to 3. + """ + + def __init__(self, + motion: Optional[dict] = None, + obj_score_thr: float = 0.3, + reid: dict = dict( + num_samples=10, + img_scale=(256, 128), + img_norm_cfg=None, + match_score_thr=2.0), + match_iou_thr: float = 0.7, + num_tentatives: int = 3, + **kwargs): + if motmetrics is None: + raise RuntimeError('motmetrics is not installed,\ + please install it by: pip install motmetrics') + super().__init__(**kwargs) + if motion is not None: + self.motion = TASK_UTILS.build(motion) + assert self.motion is not None, 'SORT/Deep SORT need KalmanFilter' + self.obj_score_thr = obj_score_thr + self.reid = reid + self.match_iou_thr = match_iou_thr + self.num_tentatives = num_tentatives + + @property + def confirmed_ids(self) -> List: + """Confirmed ids in the tracker.""" + ids = [id for id, track in self.tracks.items() if not track.tentative] + return ids + + def init_track(self, id: int, obj: Tuple[Tensor]) -> None: + """Initialize a track.""" + super().init_track(id, obj) + self.tracks[id].tentative = True + bbox = bbox_xyxy_to_cxcyah(self.tracks[id].bboxes[-1]) # size = (1, 4) + assert bbox.ndim == 2 and bbox.shape[0] == 1 + bbox = bbox.squeeze(0).cpu().numpy() + self.tracks[id].mean, self.tracks[id].covariance = self.kf.initiate( + bbox) + + def update_track(self, id: int, obj: Tuple[Tensor]) -> None: + """Update a track.""" + super().update_track(id, obj) + if self.tracks[id].tentative: + if len(self.tracks[id]['bboxes']) >= self.num_tentatives: + self.tracks[id].tentative = False + bbox = bbox_xyxy_to_cxcyah(self.tracks[id].bboxes[-1]) # size = (1, 4) + assert bbox.ndim == 2 and bbox.shape[0] == 1 + bbox = bbox.squeeze(0).cpu().numpy() + self.tracks[id].mean, self.tracks[id].covariance = self.kf.update( + self.tracks[id].mean, self.tracks[id].covariance, bbox) + + def pop_invalid_tracks(self, frame_id: int) -> None: + """Pop out invalid tracks.""" + invalid_ids = [] + for k, v in self.tracks.items(): + # case1: disappeared frames >= self.num_frames_retrain + case1 = frame_id - v['frame_ids'][-1] >= self.num_frames_retain + # case2: tentative tracks but not matched in this frame + case2 = v.tentative and v['frame_ids'][-1] != frame_id + if case1 or case2: + invalid_ids.append(k) + for invalid_id in invalid_ids: + self.tracks.pop(invalid_id) + + def track(self, + model: torch.nn.Module, + img: Tensor, + data_sample: DetDataSample, + data_preprocessor: OptConfigType = None, + rescale: bool = False, + **kwargs) -> InstanceData: + """Tracking forward function. + + Args: + model (nn.Module): MOT model. + img (Tensor): of shape (T, C, H, W) encoding input image. + Typically these should be mean centered and std scaled. + The T denotes the number of key images and usually is 1 in + SORT method. + data_sample (:obj:`TrackDataSample`): The data sample. + It includes information such as `pred_det_instances`. + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`TrackDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + rescale (bool, optional): If True, the bounding boxes should be + rescaled to fit the original scale of the image. Defaults to + False. + + Returns: + :obj:`InstanceData`: Tracking results of the input images. + Each InstanceData usually contains ``bboxes``, ``labels``, + ``scores`` and ``instances_id``. + """ + metainfo = data_sample.metainfo + bboxes = data_sample.pred_instances.bboxes + labels = data_sample.pred_instances.labels + scores = data_sample.pred_instances.scores + + frame_id = metainfo.get('frame_id', -1) + if frame_id == 0: + self.reset() + if not hasattr(self, 'kf'): + self.kf = self.motion + + if self.with_reid: + if self.reid.get('img_norm_cfg', False): + img_norm_cfg = dict( + mean=data_preprocessor['mean'], + std=data_preprocessor['std'], + to_bgr=data_preprocessor['rgb_to_bgr']) + reid_img = imrenormalize(img, img_norm_cfg, + self.reid['img_norm_cfg']) + else: + reid_img = img.clone() + + valid_inds = scores > self.obj_score_thr + bboxes = bboxes[valid_inds] + labels = labels[valid_inds] + scores = scores[valid_inds] + + if self.empty or bboxes.size(0) == 0: + num_new_tracks = bboxes.size(0) + ids = torch.arange( + self.num_tracks, + self.num_tracks + num_new_tracks, + dtype=torch.long).to(bboxes.device) + self.num_tracks += num_new_tracks + if self.with_reid: + crops = self.crop_imgs(reid_img, metainfo, bboxes.clone(), + rescale) + if crops.size(0) > 0: + embeds = model.reid(crops, mode='tensor') + else: + embeds = crops.new_zeros((0, model.reid.head.out_channels)) + else: + ids = torch.full((bboxes.size(0), ), -1, + dtype=torch.long).to(bboxes.device) + + # motion + self.tracks, costs = self.motion.track(self.tracks, + bbox_xyxy_to_cxcyah(bboxes)) + + active_ids = self.confirmed_ids + if self.with_reid: + crops = self.crop_imgs(reid_img, metainfo, bboxes.clone(), + rescale) + embeds = model.reid(crops, mode='tensor') + + # reid + if len(active_ids) > 0: + track_embeds = self.get( + 'embeds', + active_ids, + self.reid.get('num_samples', None), + behavior='mean') + reid_dists = torch.cdist(track_embeds, embeds) + + # support multi-class association + track_labels = torch.tensor([ + self.tracks[id]['labels'][-1] for id in active_ids + ]).to(bboxes.device) + cate_match = labels[None, :] == track_labels[:, None] + cate_cost = (1 - cate_match.int()) * 1e6 + reid_dists = (reid_dists + cate_cost).cpu().numpy() + + valid_inds = [list(self.ids).index(_) for _ in active_ids] + reid_dists[~np.isfinite(costs[valid_inds, :])] = np.nan + + row, col = linear_sum_assignment(reid_dists) + for r, c in zip(row, col): + dist = reid_dists[r, c] + if not np.isfinite(dist): + continue + if dist <= self.reid['match_score_thr']: + ids[c] = active_ids[r] + + active_ids = [ + id for id in self.ids if id not in ids + and self.tracks[id].frame_ids[-1] == frame_id - 1 + ] + if len(active_ids) > 0: + active_dets = torch.nonzero(ids == -1).squeeze(1) + track_bboxes = self.get('bboxes', active_ids) + ious = bbox_overlaps(track_bboxes, bboxes[active_dets]) + + # support multi-class association + track_labels = torch.tensor([ + self.tracks[id]['labels'][-1] for id in active_ids + ]).to(bboxes.device) + cate_match = labels[None, active_dets] == track_labels[:, None] + cate_cost = (1 - cate_match.int()) * 1e6 + + dists = (1 - ious + cate_cost).cpu().numpy() + + row, col = linear_sum_assignment(dists) + for r, c in zip(row, col): + dist = dists[r, c] + if dist < 1 - self.match_iou_thr: + ids[active_dets[c]] = active_ids[r] + + new_track_inds = ids == -1 + ids[new_track_inds] = torch.arange( + self.num_tracks, + self.num_tracks + new_track_inds.sum(), + dtype=torch.long).to(bboxes.device) + self.num_tracks += new_track_inds.sum() + + self.update( + ids=ids, + bboxes=bboxes, + scores=scores, + labels=labels, + embeds=embeds if self.with_reid else None, + frame_ids=frame_id) + + # update pred_track_instances + pred_track_instances = InstanceData() + pred_track_instances.bboxes = bboxes + pred_track_instances.labels = labels + pred_track_instances.scores = scores + pred_track_instances.instances_id = ids + + return pred_track_instances diff --git a/mmdetection/mmdet/models/trackers/strongsort_tracker.py b/mmdetection/mmdet/models/trackers/strongsort_tracker.py new file mode 100644 index 00000000..9d707570 --- /dev/null +++ b/mmdetection/mmdet/models/trackers/strongsort_tracker.py @@ -0,0 +1,273 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple + +import numpy as np +import torch +from mmengine.structures import InstanceData + +try: + import motmetrics + from motmetrics.lap import linear_sum_assignment +except ImportError: + motmetrics = None +from torch import Tensor + +from mmdet.models.utils import imrenormalize +from mmdet.registry import MODELS +from mmdet.structures import TrackDataSample +from mmdet.structures.bbox import bbox_overlaps, bbox_xyxy_to_cxcyah +from mmdet.utils import OptConfigType +from .sort_tracker import SORTTracker + + +def cosine_distance(x: Tensor, y: Tensor) -> np.ndarray: + """compute the cosine distance. + + Args: + x (Tensor): embeddings with shape (N,C). + y (Tensor): embeddings with shape (M,C). + + Returns: + ndarray: cosine distance with shape (N,M). + """ + x = x.cpu().numpy() + y = y.cpu().numpy() + x = x / np.linalg.norm(x, axis=1, keepdims=True) + y = y / np.linalg.norm(y, axis=1, keepdims=True) + dists = 1. - np.dot(x, y.T) + return dists + + +@MODELS.register_module() +class StrongSORTTracker(SORTTracker): + """Tracker for StrongSORT. + + Args: + obj_score_thr (float, optional): Threshold to filter the objects. + Defaults to 0.6. + motion (dict): Configuration of motion. Defaults to None. + reid (dict, optional): Configuration for the ReID model. + - num_samples (int, optional): Number of samples to calculate the + feature embeddings of a track. Default to None. + - image_scale (tuple, optional): Input scale of the ReID model. + Default to (256, 128). + - img_norm_cfg (dict, optional): Configuration to normalize the + input. Default to None. + - match_score_thr (float, optional): Similarity threshold for the + matching process. Default to 0.3. + - motion_weight (float, optional): the weight of the motion cost. + Defaults to 0.02. + match_iou_thr (float, optional): Threshold of the IoU matching process. + Defaults to 0.7. + num_tentatives (int, optional): Number of continuous frames to confirm + a track. Defaults to 2. + """ + + def __init__(self, + motion: Optional[dict] = None, + obj_score_thr: float = 0.6, + reid: dict = dict( + num_samples=None, + img_scale=(256, 128), + img_norm_cfg=None, + match_score_thr=0.3, + motion_weight=0.02), + match_iou_thr: float = 0.7, + num_tentatives: int = 2, + **kwargs): + if motmetrics is None: + raise RuntimeError('motmetrics is not installed,\ + please install it by: pip install motmetrics') + super().__init__(motion, obj_score_thr, reid, match_iou_thr, + num_tentatives, **kwargs) + + def update_track(self, id: int, obj: Tuple[Tensor]) -> None: + """Update a track.""" + for k, v in zip(self.memo_items, obj): + v = v[None] + if self.momentums is not None and k in self.momentums: + m = self.momentums[k] + self.tracks[id][k] = (1 - m) * self.tracks[id][k] + m * v + else: + self.tracks[id][k].append(v) + + if self.tracks[id].tentative: + if len(self.tracks[id]['bboxes']) >= self.num_tentatives: + self.tracks[id].tentative = False + bbox = bbox_xyxy_to_cxcyah(self.tracks[id].bboxes[-1]) # size = (1, 4) + assert bbox.ndim == 2 and bbox.shape[0] == 1 + bbox = bbox.squeeze(0).cpu().numpy() + score = float(self.tracks[id].scores[-1].cpu()) + self.tracks[id].mean, self.tracks[id].covariance = self.kf.update( + self.tracks[id].mean, self.tracks[id].covariance, bbox, score) + + def track(self, + model: torch.nn.Module, + img: Tensor, + data_sample: TrackDataSample, + data_preprocessor: OptConfigType = None, + rescale: bool = False, + **kwargs) -> InstanceData: + """Tracking forward function. + + Args: + model (nn.Module): MOT model. + img (Tensor): of shape (T, C, H, W) encoding input image. + Typically these should be mean centered and std scaled. + The T denotes the number of key images and usually is 1 in + SORT method. + feats (list[Tensor]): Multi level feature maps of `img`. + data_sample (:obj:`TrackDataSample`): The data sample. + It includes information such as `pred_det_instances`. + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`TrackDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + rescale (bool, optional): If True, the bounding boxes should be + rescaled to fit the original scale of the image. Defaults to + False. + + Returns: + :obj:`InstanceData`: Tracking results of the input images. + Each InstanceData usually contains ``bboxes``, ``labels``, + ``scores`` and ``instances_id``. + """ + metainfo = data_sample.metainfo + bboxes = data_sample.pred_instances.bboxes + labels = data_sample.pred_instances.labels + scores = data_sample.pred_instances.scores + + frame_id = metainfo.get('frame_id', -1) + if frame_id == 0: + self.reset() + if not hasattr(self, 'kf'): + self.kf = self.motion + + if self.with_reid: + if self.reid.get('img_norm_cfg', False): + img_norm_cfg = dict( + mean=data_preprocessor.get('mean', [0, 0, 0]), + std=data_preprocessor.get('std', [1, 1, 1]), + to_bgr=data_preprocessor.get('rgb_to_bgr', False)) + reid_img = imrenormalize(img, img_norm_cfg, + self.reid['img_norm_cfg']) + else: + reid_img = img.clone() + + valid_inds = scores > self.obj_score_thr + bboxes = bboxes[valid_inds] + labels = labels[valid_inds] + scores = scores[valid_inds] + + if self.empty or bboxes.size(0) == 0: + num_new_tracks = bboxes.size(0) + ids = torch.arange( + self.num_tracks, + self.num_tracks + num_new_tracks, + dtype=torch.long).to(bboxes.device) + self.num_tracks += num_new_tracks + if self.with_reid: + crops = self.crop_imgs(reid_img, metainfo, bboxes.clone(), + rescale) + if crops.size(0) > 0: + embeds = model.reid(crops, mode='tensor') + else: + embeds = crops.new_zeros((0, model.reid.head.out_channels)) + else: + ids = torch.full((bboxes.size(0), ), -1, + dtype=torch.long).to(bboxes.device) + + # motion + if model.with_cmc: + num_samples = 1 + self.tracks = model.cmc.track(self.last_img, img, self.tracks, + num_samples, frame_id, metainfo) + + self.tracks, motion_dists = self.motion.track( + self.tracks, bbox_xyxy_to_cxcyah(bboxes)) + + active_ids = self.confirmed_ids + if self.with_reid: + crops = self.crop_imgs(reid_img, metainfo, bboxes.clone(), + rescale) + embeds = model.reid(crops, mode='tensor') + + # reid + if len(active_ids) > 0: + track_embeds = self.get( + 'embeds', + active_ids, + self.reid.get('num_samples', None), + behavior='mean') + reid_dists = cosine_distance(track_embeds, embeds) + valid_inds = [list(self.ids).index(_) for _ in active_ids] + reid_dists[~np.isfinite(motion_dists[ + valid_inds, :])] = np.nan + + weight_motion = self.reid.get('motion_weight') + match_dists = (1 - weight_motion) * reid_dists + \ + weight_motion * motion_dists[valid_inds] + + # support multi-class association + track_labels = torch.tensor([ + self.tracks[id]['labels'][-1] for id in active_ids + ]).to(bboxes.device) + cate_match = labels[None, :] == track_labels[:, None] + cate_cost = ((1 - cate_match.int()) * 1e6).cpu().numpy() + match_dists = match_dists + cate_cost + + row, col = linear_sum_assignment(match_dists) + for r, c in zip(row, col): + dist = match_dists[r, c] + if not np.isfinite(dist): + continue + if dist <= self.reid['match_score_thr']: + ids[c] = active_ids[r] + + active_ids = [ + id for id in self.ids if id not in ids + and self.tracks[id].frame_ids[-1] == frame_id - 1 + ] + if len(active_ids) > 0: + active_dets = torch.nonzero(ids == -1).squeeze(1) + track_bboxes = self.get('bboxes', active_ids) + ious = bbox_overlaps(track_bboxes, bboxes[active_dets]) + + # support multi-class association + track_labels = torch.tensor([ + self.tracks[id]['labels'][-1] for id in active_ids + ]).to(bboxes.device) + cate_match = labels[None, active_dets] == track_labels[:, None] + cate_cost = (1 - cate_match.int()) * 1e6 + + dists = (1 - ious + cate_cost).cpu().numpy() + + row, col = linear_sum_assignment(dists) + for r, c in zip(row, col): + dist = dists[r, c] + if dist < 1 - self.match_iou_thr: + ids[active_dets[c]] = active_ids[r] + + new_track_inds = ids == -1 + ids[new_track_inds] = torch.arange( + self.num_tracks, + self.num_tracks + new_track_inds.sum(), + dtype=torch.long).to(bboxes.device) + self.num_tracks += new_track_inds.sum() + + self.update( + ids=ids, + bboxes=bboxes, + scores=scores, + labels=labels, + embeds=embeds if self.with_reid else None, + frame_ids=frame_id) + self.last_img = img + + # update pred_track_instances + pred_track_instances = InstanceData() + pred_track_instances.bboxes = bboxes + pred_track_instances.labels = labels + pred_track_instances.scores = scores + pred_track_instances.instances_id = ids + + return pred_track_instances diff --git a/mmdetection/mmdet/models/tracking_heads/__init__.py b/mmdetection/mmdet/models/tracking_heads/__init__.py new file mode 100644 index 00000000..bd1f0561 --- /dev/null +++ b/mmdetection/mmdet/models/tracking_heads/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .mask2former_track_head import Mask2FormerTrackHead +from .quasi_dense_embed_head import QuasiDenseEmbedHead +from .quasi_dense_track_head import QuasiDenseTrackHead +from .roi_embed_head import RoIEmbedHead +from .roi_track_head import RoITrackHead + +__all__ = [ + 'QuasiDenseEmbedHead', 'QuasiDenseTrackHead', 'Mask2FormerTrackHead', + 'RoIEmbedHead', 'RoITrackHead' +] diff --git a/mmdetection/mmdet/models/tracking_heads/mask2former_track_head.py b/mmdetection/mmdet/models/tracking_heads/mask2former_track_head.py new file mode 100644 index 00000000..0877241b --- /dev/null +++ b/mmdetection/mmdet/models/tracking_heads/mask2former_track_head.py @@ -0,0 +1,729 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from collections import defaultdict +from typing import Dict, List, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Conv2d +from mmcv.ops import point_sample +from mmengine.model import ModuleList +from mmengine.model.weight_init import caffe2_xavier_init +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.dense_heads import AnchorFreeHead, MaskFormerHead +from mmdet.models.utils import get_uncertain_point_coords_with_randomness +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import TrackDataSample, TrackSampleList +from mmdet.structures.mask import mask2bbox +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptMultiConfig, reduce_mean) +from ..layers import Mask2FormerTransformerDecoder + + +@MODELS.register_module() +class Mask2FormerTrackHead(MaskFormerHead): + """Implements the Mask2Former head. + + See `Masked-attention Mask Transformer for Universal Image + Segmentation `_ for details. + + Args: + in_channels (list[int]): Number of channels in the input feature map. + feat_channels (int): Number of channels for features. + out_channels (int): Number of channels for output. + num_classes (int): Number of VIS classes. + num_queries (int): Number of query in Transformer decoder. + Defaults to 100. + num_transformer_feat_level (int): Number of feats levels. + Defaults to 3. + pixel_decoder (:obj:`ConfigDict` or dict): Config for pixel + decoder. + enforce_decoder_input_project (bool, optional): Whether to add + a layer to change the embed_dim of transformer encoder in + pixel decoder to the embed_dim of transformer decoder. + Defaults to False. + transformer_decoder (:obj:`ConfigDict` or dict): Config for + transformer decoder. + positional_encoding (:obj:`ConfigDict` or dict): Config for + transformer decoder position encoding. + Defaults to `SinePositionalEncoding3D`. + loss_cls (:obj:`ConfigDict` or dict): Config of the classification + loss. Defaults to `CrossEntropyLoss`. + loss_mask (:obj:`ConfigDict` or dict): Config of the mask loss. + Defaults to 'CrossEntropyLoss'. + loss_dice (:obj:`ConfigDict` or dict): Config of the dice loss. + Defaults to 'DiceLoss'. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config of + Mask2Former head. Defaults to None. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config of + Mask2Former head. Defaults to None. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict], optional): Initialization config dict. Defaults to None. + """ + + def __init__(self, + in_channels: List[int], + feat_channels: int, + out_channels: int, + num_classes: int, + num_frames: int = 2, + num_queries: int = 100, + num_transformer_feat_level: int = 3, + pixel_decoder: ConfigType = ..., + enforce_decoder_input_project: bool = False, + transformer_decoder: ConfigType = ..., + positional_encoding: ConfigType = dict( + num_feats=128, normalize=True), + loss_cls: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=2.0, + reduction='mean', + class_weight=[1.0] * 133 + [0.1]), + loss_mask: ConfigType = dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='mean', + loss_weight=5.0), + loss_dice: ConfigType = dict( + type='DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=5.0), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + super(AnchorFreeHead, self).__init__(init_cfg=init_cfg) + self.num_classes = num_classes + self.num_frames = num_frames + self.num_queries = num_queries + self.num_transformer_feat_level = num_transformer_feat_level + self.num_transformer_feat_level = num_transformer_feat_level + self.num_heads = transformer_decoder.layer_cfg.cross_attn_cfg.num_heads + self.num_transformer_decoder_layers = transformer_decoder.num_layers + assert pixel_decoder.encoder.layer_cfg. \ + self_attn_cfg.num_levels == num_transformer_feat_level + pixel_decoder_ = copy.deepcopy(pixel_decoder) + pixel_decoder_.update( + in_channels=in_channels, + feat_channels=feat_channels, + out_channels=out_channels) + self.pixel_decoder = MODELS.build(pixel_decoder_) + self.transformer_decoder = Mask2FormerTransformerDecoder( + **transformer_decoder) + self.decoder_embed_dims = self.transformer_decoder.embed_dims + + self.decoder_input_projs = ModuleList() + # from low resolution to high resolution + for _ in range(num_transformer_feat_level): + if (self.decoder_embed_dims != feat_channels + or enforce_decoder_input_project): + self.decoder_input_projs.append( + Conv2d( + feat_channels, self.decoder_embed_dims, kernel_size=1)) + else: + self.decoder_input_projs.append(nn.Identity()) + self.decoder_positional_encoding = MODELS.build(positional_encoding) + self.query_embed = nn.Embedding(self.num_queries, feat_channels) + self.query_feat = nn.Embedding(self.num_queries, feat_channels) + # from low resolution to high resolution + self.level_embed = nn.Embedding(self.num_transformer_feat_level, + feat_channels) + + self.cls_embed = nn.Linear(feat_channels, self.num_classes + 1) + self.mask_embed = nn.Sequential( + nn.Linear(feat_channels, feat_channels), nn.ReLU(inplace=True), + nn.Linear(feat_channels, feat_channels), nn.ReLU(inplace=True), + nn.Linear(feat_channels, out_channels)) + + self.test_cfg = test_cfg + self.train_cfg = train_cfg + if train_cfg: + self.assigner = TASK_UTILS.build(self.train_cfg.assigner) + self.sampler = TASK_UTILS.build( + # self.train_cfg.sampler, default_args=dict(context=self)) + self.train_cfg['sampler'], + default_args=dict(context=self)) + self.num_points = self.train_cfg.get('num_points', 12544) + self.oversample_ratio = self.train_cfg.get('oversample_ratio', 3.0) + self.importance_sample_ratio = self.train_cfg.get( + 'importance_sample_ratio', 0.75) + + self.class_weight = loss_cls.class_weight + self.loss_cls = MODELS.build(loss_cls) + self.loss_mask = MODELS.build(loss_mask) + self.loss_dice = MODELS.build(loss_dice) + + def init_weights(self) -> None: + for m in self.decoder_input_projs: + if isinstance(m, Conv2d): + caffe2_xavier_init(m, bias=0) + + self.pixel_decoder.init_weights() + + for p in self.transformer_decoder.parameters(): + if p.dim() > 1: + nn.init.xavier_normal_(p) + + def preprocess_gt(self, batch_gt_instances: InstanceList) -> InstanceList: + """Preprocess the ground truth for all images. + + It aims to reorganize the `gt`. For example, in the + `batch_data_sample.gt_instances.mask`, its shape is + `(all_num_gts, h, w)`, but we don't know each gt belongs to which `img` + (assume `num_frames` is 2). So, this func used to reshape the `gt_mask` + to `(num_gts_per_img, num_frames, h, w)`. In addition, we can't + guarantee that the number of instances in these two images is equal, + so `-1` refers to nonexistent instances. + + Args: + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``labels``, each is + ground truth labels of each bbox, with shape (num_gts, ) + and ``masks``, each is ground truth masks of each instances + of an image, shape (num_gts, h, w). + + Returns: + list[obj:`InstanceData`]: each contains the following keys + + - labels (Tensor): Ground truth class indices\ + for an image, with shape (n, ), n is the sum of\ + number of stuff type and number of instance in an image. + - masks (Tensor): Ground truth mask for a\ + image, with shape (n, t, h, w). + """ + final_batch_gt_instances = [] + batch_size = len(batch_gt_instances) // self.num_frames + for batch_idx in range(batch_size): + pair_gt_insatences = batch_gt_instances[batch_idx * + self.num_frames:batch_idx * + self.num_frames + + self.num_frames] + + assert len( + pair_gt_insatences + ) > 1, f'mask2former for vis need multi frames to train, \ + but you only use {len(pair_gt_insatences)} frames' + + _device = pair_gt_insatences[0].labels.device + + for gt_instances in pair_gt_insatences: + gt_instances.masks = gt_instances.masks.to_tensor( + dtype=torch.bool, device=_device) + all_ins_id = torch.cat([ + gt_instances.instances_ids + for gt_instances in pair_gt_insatences + ]) + all_ins_id = all_ins_id.unique().tolist() + map_ins_id = dict() + for i, ins_id in enumerate(all_ins_id): + map_ins_id[ins_id] = i + + num_instances = len(all_ins_id) + mask_shape = [ + num_instances, self.num_frames, + pair_gt_insatences[0].masks.shape[1], + pair_gt_insatences[0].masks.shape[2] + ] + gt_masks_per_video = torch.zeros( + mask_shape, dtype=torch.bool, device=_device) + gt_ids_per_video = torch.full((num_instances, self.num_frames), + -1, + dtype=torch.long, + device=_device) + gt_labels_per_video = torch.full((num_instances, ), + -1, + dtype=torch.long, + device=_device) + + for frame_id in range(self.num_frames): + cur_frame_gts = pair_gt_insatences[frame_id] + ins_ids = cur_frame_gts.instances_ids.tolist() + for i, id in enumerate(ins_ids): + gt_masks_per_video[map_ins_id[id], + frame_id, :, :] = cur_frame_gts.masks[i] + gt_ids_per_video[map_ins_id[id], + frame_id] = cur_frame_gts.instances_ids[i] + gt_labels_per_video[ + map_ins_id[id]] = cur_frame_gts.labels[i] + + tmp_instances = InstanceData( + labels=gt_labels_per_video, + masks=gt_masks_per_video.long(), + instances_id=gt_ids_per_video) + final_batch_gt_instances.append(tmp_instances) + + return final_batch_gt_instances + + def _get_targets_single(self, cls_score: Tensor, mask_pred: Tensor, + gt_instances: InstanceData, + img_meta: dict) -> Tuple[Tensor]: + """Compute classification and mask targets for one image. + + Args: + cls_score (Tensor): Mask score logits from a single decoder layer + for one image. Shape (num_queries, cls_out_channels). + mask_pred (Tensor): Mask logits for a single decoder layer for one + image. Shape (num_queries, num_frames, h, w). + gt_instances (:obj:`InstanceData`): It contains ``labels`` and + ``masks``. + img_meta (dict): Image informtation. + + Returns: + tuple[Tensor]: A tuple containing the following for one image. + + - labels (Tensor): Labels of each image. \ + shape (num_queries, ). + - label_weights (Tensor): Label weights of each image. \ + shape (num_queries, ). + - mask_targets (Tensor): Mask targets of each image. \ + shape (num_queries, num_frames, h, w). + - mask_weights (Tensor): Mask weights of each image. \ + shape (num_queries, ). + - pos_inds (Tensor): Sampled positive indices for each \ + image. + - neg_inds (Tensor): Sampled negative indices for each \ + image. + - sampling_result (:obj:`SamplingResult`): Sampling results. + """ + # (num_gts, ) + gt_labels = gt_instances.labels + # (num_gts, num_frames, h, w) + gt_masks = gt_instances.masks + # sample points + num_queries = cls_score.shape[0] + num_gts = gt_labels.shape[0] + + point_coords = torch.rand((1, self.num_points, 2), + device=cls_score.device) + + # shape (num_queries, num_points) + mask_points_pred = point_sample(mask_pred, + point_coords.repeat(num_queries, 1, + 1)).flatten(1) + # shape (num_gts, num_points) + gt_points_masks = point_sample(gt_masks.float(), + point_coords.repeat(num_gts, 1, + 1)).flatten(1) + + sampled_gt_instances = InstanceData( + labels=gt_labels, masks=gt_points_masks) + sampled_pred_instances = InstanceData( + scores=cls_score, masks=mask_points_pred) + # assign and sample + assign_result = self.assigner.assign( + pred_instances=sampled_pred_instances, + gt_instances=sampled_gt_instances, + img_meta=img_meta) + pred_instances = InstanceData(scores=cls_score, masks=mask_pred) + sampling_result = self.sampler.sample( + assign_result=assign_result, + pred_instances=pred_instances, + gt_instances=gt_instances) + pos_inds = sampling_result.pos_inds + neg_inds = sampling_result.neg_inds + + # label target + labels = gt_labels.new_full((self.num_queries, ), + self.num_classes, + dtype=torch.long) + labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds] + label_weights = gt_labels.new_ones((self.num_queries, )) + + # mask target + mask_targets = gt_masks[sampling_result.pos_assigned_gt_inds] + mask_weights = mask_pred.new_zeros((self.num_queries, )) + mask_weights[pos_inds] = 1.0 + + return (labels, label_weights, mask_targets, mask_weights, pos_inds, + neg_inds, sampling_result) + + def _loss_by_feat_single(self, cls_scores: Tensor, mask_preds: Tensor, + batch_gt_instances: List[InstanceData], + batch_img_metas: List[dict]) -> Tuple[Tensor]: + """Loss function for outputs from a single decoder layer. + + Args: + cls_scores (Tensor): Mask score logits from a single decoder layer + for all images. Shape (batch_size, num_queries, + cls_out_channels). Note `cls_out_channels` should include + background. + mask_preds (Tensor): Mask logits for a pixel decoder for all + images. Shape (batch_size, num_queries, num_frames,h, w). + batch_gt_instances (list[obj:`InstanceData`]): each contains + ``labels`` and ``masks``. + batch_img_metas (list[dict]): List of image meta information. + + Returns: + tuple[Tensor]: Loss components for outputs from a single \ + decoder layer. + """ + num_imgs = cls_scores.size(0) + cls_scores_list = [cls_scores[i] for i in range(num_imgs)] + mask_preds_list = [mask_preds[i] for i in range(num_imgs)] + (labels_list, label_weights_list, mask_targets_list, mask_weights_list, + avg_factor) = self.get_targets(cls_scores_list, mask_preds_list, + batch_gt_instances, batch_img_metas) + # shape (batch_size, num_queries) + labels = torch.stack(labels_list, dim=0) + # shape (batch_size, num_queries) + label_weights = torch.stack(label_weights_list, dim=0) + # shape (num_total_gts, num_frames, h, w) + mask_targets = torch.cat(mask_targets_list, dim=0) + # shape (batch_size, num_queries) + mask_weights = torch.stack(mask_weights_list, dim=0) + + # classfication loss + # shape (batch_size * num_queries, ) + cls_scores = cls_scores.flatten(0, 1) + labels = labels.flatten(0, 1) + label_weights = label_weights.flatten(0, 1) + + class_weight = cls_scores.new_tensor(self.class_weight) + loss_cls = self.loss_cls( + cls_scores, + labels, + label_weights, + avg_factor=class_weight[labels].sum()) + + num_total_masks = reduce_mean(cls_scores.new_tensor([avg_factor])) + num_total_masks = max(num_total_masks, 1) + + # extract positive ones + # shape (batch_size, num_queries, num_frames, h, w) + # -> (num_total_gts, num_frames, h, w) + mask_preds = mask_preds[mask_weights > 0] + + if mask_targets.shape[0] == 0: + # zero match + loss_dice = mask_preds.sum() + loss_mask = mask_preds.sum() + return loss_cls, loss_mask, loss_dice + + with torch.no_grad(): + points_coords = get_uncertain_point_coords_with_randomness( + mask_preds.flatten(0, 1).unsqueeze(1), None, self.num_points, + self.oversample_ratio, self.importance_sample_ratio) + # shape (num_total_gts * num_frames, h, w) -> + # (num_total_gts, num_points) + mask_point_targets = point_sample( + mask_targets.flatten(0, 1).unsqueeze(1).float(), + points_coords).squeeze(1) + # shape (num_total_gts * num_frames, num_points) + mask_point_preds = point_sample( + mask_preds.flatten(0, 1).unsqueeze(1), points_coords).squeeze(1) + + # dice loss + loss_dice = self.loss_dice( + mask_point_preds, mask_point_targets, avg_factor=num_total_masks) + + # mask loss + # shape (num_total_gts * num_frames, num_points) -> + # (num_total_gts * num_frames * num_points, ) + mask_point_preds = mask_point_preds.reshape(-1) + # shape (num_total_gts, num_points) -> (num_total_gts * num_points, ) + mask_point_targets = mask_point_targets.reshape(-1) + loss_mask = self.loss_mask( + mask_point_preds, + mask_point_targets, + avg_factor=num_total_masks * self.num_points / self.num_frames) + + return loss_cls, loss_mask, loss_dice + + def _forward_head( + self, decoder_out: Tensor, mask_feature: Tensor, + attn_mask_target_size: Tuple[int, + int]) -> Tuple[Tensor, Tensor, Tensor]: + """Forward for head part which is called after every decoder layer. + + Args: + decoder_out (Tensor): in shape (num_queries, batch_size, c). + mask_feature (Tensor): in shape (batch_size, t, c, h, w). + attn_mask_target_size (tuple[int, int]): target attention + mask size. + + Returns: + tuple: A tuple contain three elements. + + - cls_pred (Tensor): Classification scores in shape \ + (batch_size, num_queries, cls_out_channels). \ + Note `cls_out_channels` should include background. + - mask_pred (Tensor): Mask scores in shape \ + (batch_size, num_queries,h, w). + - attn_mask (Tensor): Attention mask in shape \ + (batch_size * num_heads, num_queries, h, w). + """ + decoder_out = self.transformer_decoder.post_norm(decoder_out) + cls_pred = self.cls_embed(decoder_out) + mask_embed = self.mask_embed(decoder_out) + + # shape (batch_size, num_queries, t, h, w) + mask_pred = torch.einsum('bqc,btchw->bqthw', mask_embed, mask_feature) + b, q, t, _, _ = mask_pred.shape + + attn_mask = F.interpolate( + mask_pred.flatten(0, 1), + attn_mask_target_size, + mode='bilinear', + align_corners=False).view(b, q, t, attn_mask_target_size[0], + attn_mask_target_size[1]) + + # shape (batch_size, num_queries, t, h, w) -> + # (batch_size, num_queries, t*h*w) -> + # (batch_size, num_head, num_queries, t*h*w) -> + # (batch_size*num_head, num_queries, t*h*w) + attn_mask = attn_mask.flatten(2).unsqueeze(1).repeat( + (1, self.num_heads, 1, 1)).flatten(0, 1) + attn_mask = attn_mask.sigmoid() < 0.5 + attn_mask = attn_mask.detach() + + return cls_pred, mask_pred, attn_mask + + def forward( + self, x: List[Tensor], data_samples: TrackDataSample + ) -> Tuple[List[Tensor], List[Tensor]]: + """Forward function. + + Args: + x (list[Tensor]): Multi scale Features from the + upstream network, each is a 4D-tensor. + data_samples (List[:obj:`TrackDataSample`]): The Data + Samples. It usually includes information such as `gt_instance`. + + Returns: + tuple[list[Tensor]]: A tuple contains two elements. + + - cls_pred_list (list[Tensor)]: Classification logits \ + for each decoder layer. Each is a 3D-tensor with shape \ + (batch_size, num_queries, cls_out_channels). \ + Note `cls_out_channels` should include background. + - mask_pred_list (list[Tensor]): Mask logits for each \ + decoder layer. Each with shape (batch_size, num_queries, \ + h, w). + """ + mask_features, multi_scale_memorys = self.pixel_decoder(x) + bt, c_m, h_m, w_m = mask_features.shape + batch_size = bt // self.num_frames if self.training else 1 + t = bt // batch_size + mask_features = mask_features.view(batch_size, t, c_m, h_m, w_m) + # multi_scale_memorys (from low resolution to high resolution) + decoder_inputs = [] + decoder_positional_encodings = [] + for i in range(self.num_transformer_feat_level): + decoder_input = self.decoder_input_projs[i](multi_scale_memorys[i]) + decoder_input = decoder_input.flatten(2) + level_embed = self.level_embed.weight[i][None, :, None] + decoder_input = decoder_input + level_embed + _, c, hw = decoder_input.shape + # shape (batch_size*t, c, h, w) -> + # (batch_size, t, c, hw) -> + # (batch_size, t*h*w, c) + decoder_input = decoder_input.view(batch_size, t, c, + hw).permute(0, 1, 3, + 2).flatten(1, 2) + # shape (batch_size, c, h, w) -> (h*w, batch_size, c) + mask = decoder_input.new_zeros( + (batch_size, t) + multi_scale_memorys[i].shape[-2:], + dtype=torch.bool) + decoder_positional_encoding = self.decoder_positional_encoding( + mask) + decoder_positional_encoding = decoder_positional_encoding.flatten( + 3).permute(0, 1, 3, 2).flatten(1, 2) + decoder_inputs.append(decoder_input) + decoder_positional_encodings.append(decoder_positional_encoding) + # shape (num_queries, c) -> (batch_size, num_queries, c) + query_feat = self.query_feat.weight.unsqueeze(0).repeat( + (batch_size, 1, 1)) + query_embed = self.query_embed.weight.unsqueeze(0).repeat( + (batch_size, 1, 1)) + + cls_pred_list = [] + mask_pred_list = [] + cls_pred, mask_pred, attn_mask = self._forward_head( + query_feat, mask_features, multi_scale_memorys[0].shape[-2:]) + cls_pred_list.append(cls_pred) + mask_pred_list.append(mask_pred) + + for i in range(self.num_transformer_decoder_layers): + level_idx = i % self.num_transformer_feat_level + # if a mask is all True(all background), then set it all False. + attn_mask[torch.where( + attn_mask.sum(-1) == attn_mask.shape[-1])] = False + + # cross_attn + self_attn + layer = self.transformer_decoder.layers[i] + query_feat = layer( + query=query_feat, + key=decoder_inputs[level_idx], + value=decoder_inputs[level_idx], + query_pos=query_embed, + key_pos=decoder_positional_encodings[level_idx], + cross_attn_mask=attn_mask, + query_key_padding_mask=None, + # here we do not apply masking on padded region + key_padding_mask=None) + cls_pred, mask_pred, attn_mask = self._forward_head( + query_feat, mask_features, multi_scale_memorys[ + (i + 1) % self.num_transformer_feat_level].shape[-2:]) + + cls_pred_list.append(cls_pred) + mask_pred_list.append(mask_pred) + + return cls_pred_list, mask_pred_list + + def loss( + self, + x: Tuple[Tensor], + data_samples: TrackSampleList, + ) -> Dict[str, Tensor]: + """Perform forward propagation and loss calculation of the track head + on the features of the upstream network. + + Args: + x (tuple[Tensor]): Multi-level features from the upstream + network, each is a 4D-tensor. + data_samples (List[:obj:`TrackDataSample`]): The Data + Samples. It usually includes information such as `gt_instance`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + batch_img_metas = [] + batch_gt_instances = [] + + for data_sample in data_samples: + video_img_metas = defaultdict(list) + for image_idx in range(len(data_sample)): + batch_gt_instances.append(data_sample[image_idx].gt_instances) + for key, value in data_sample[image_idx].metainfo.items(): + video_img_metas[key].append(value) + batch_img_metas.append(video_img_metas) + + # forward + all_cls_scores, all_mask_preds = self(x, data_samples) + + # preprocess ground truth + batch_gt_instances = self.preprocess_gt(batch_gt_instances) + # loss + losses = self.loss_by_feat(all_cls_scores, all_mask_preds, + batch_gt_instances, batch_img_metas) + + return losses + + def predict(self, + x: Tuple[Tensor], + data_samples: TrackDataSample, + rescale: bool = True) -> InstanceList: + """Test without augmentation. + + Args: + x (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + data_samples (List[:obj:`TrackDataSample`]): The Data + Samples. It usually includes information such as `gt_instance`. + rescale (bool, Optional): If False, then returned bboxes and masks + will fit the scale of img, otherwise, returned bboxes and masks + will fit the scale of original image shape. Defaults to True. + + Returns: + list[obj:`InstanceData`]: each contains the following keys + - labels (Tensor): Prediction class indices\ + for an image, with shape (n, ), n is the sum of\ + number of stuff type and number of instance in an image. + - masks (Tensor): Prediction mask for a\ + image, with shape (n, t, h, w). + """ + + batch_img_metas = [ + data_samples[img_idx].metainfo + for img_idx in range(len(data_samples)) + ] + all_cls_scores, all_mask_preds = self(x, data_samples) + mask_cls_results = all_cls_scores[-1] + mask_pred_results = all_mask_preds[-1] + + mask_cls_results = mask_cls_results[0] + # upsample masks + img_shape = batch_img_metas[0]['batch_input_shape'] + mask_pred_results = F.interpolate( + mask_pred_results[0], + size=(img_shape[0], img_shape[1]), + mode='bilinear', + align_corners=False) + + results = self.predict_by_feat(mask_cls_results, mask_pred_results, + batch_img_metas) + return results + + def predict_by_feat(self, + mask_cls_results: List[Tensor], + mask_pred_results: List[Tensor], + batch_img_metas: List[dict], + rescale: bool = True) -> InstanceList: + """Get top-10 predictions. + + Args: + mask_cls_results (Tensor): Mask classification logits,\ + shape (batch_size, num_queries, cls_out_channels). + Note `cls_out_channels` should include background. + mask_pred_results (Tensor): Mask logits, shape \ + (batch_size, num_queries, h, w). + batch_img_metas (list[dict]): List of image meta information. + rescale (bool, Optional): If False, then returned bboxes and masks + will fit the scale of img, otherwise, returned bboxes and masks + will fit the scale of original image shape. Defaults to True. + + Returns: + list[obj:`InstanceData`]: each contains the following keys + - labels (Tensor): Prediction class indices\ + for an image, with shape (n, ), n is the sum of\ + number of stuff type and number of instance in an image. + - masks (Tensor): Prediction mask for a\ + image, with shape (n, t, h, w). + """ + results = [] + if len(mask_cls_results) > 0: + scores = F.softmax(mask_cls_results, dim=-1)[:, :-1] + labels = torch.arange(self.num_classes).unsqueeze(0).repeat( + self.num_queries, 1).flatten(0, 1).to(scores.device) + # keep top-10 predictions + scores_per_image, topk_indices = scores.flatten(0, 1).topk( + 10, sorted=False) + labels_per_image = labels[topk_indices] + topk_indices = topk_indices // self.num_classes + mask_pred_results = mask_pred_results[topk_indices] + + img_shape = batch_img_metas[0]['img_shape'] + mask_pred_results = \ + mask_pred_results[:, :, :img_shape[0], :img_shape[1]] + if rescale: + # return result in original resolution + ori_height, ori_width = batch_img_metas[0]['ori_shape'][:2] + mask_pred_results = F.interpolate( + mask_pred_results, + size=(ori_height, ori_width), + mode='bilinear', + align_corners=False) + + masks = mask_pred_results > 0. + + # format top-10 predictions + for img_idx in range(len(batch_img_metas)): + pred_track_instances = InstanceData() + + pred_track_instances.masks = masks[:, img_idx] + pred_track_instances.bboxes = mask2bbox(masks[:, img_idx]) + pred_track_instances.labels = labels_per_image + pred_track_instances.scores = scores_per_image + pred_track_instances.instances_id = torch.arange(10) + + results.append(pred_track_instances) + + return results diff --git a/mmdetection/mmdet/models/tracking_heads/quasi_dense_embed_head.py b/mmdetection/mmdet/models/tracking_heads/quasi_dense_embed_head.py new file mode 100644 index 00000000..55e3c05b --- /dev/null +++ b/mmdetection/mmdet/models/tracking_heads/quasi_dense_embed_head.py @@ -0,0 +1,347 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch import Tensor +from torch.nn.modules.utils import _pair + +from mmdet.models.task_modules import SamplingResult +from mmdet.registry import MODELS +from ..task_modules.tracking import embed_similarity + + +@MODELS.register_module() +class QuasiDenseEmbedHead(BaseModule): + """The quasi-dense roi embed head. + + Args: + embed_channels (int): The input channel of embed features. + Defaults to 256. + softmax_temp (int): Softmax temperature. Defaults to -1. + loss_track (dict): The loss function for tracking. Defaults to + MultiPosCrossEntropyLoss. + loss_track_aux (dict): The auxiliary loss function for tracking. + Defaults to MarginL2Loss. + init_cfg (:obj:`ConfigDict` or dict or list[:obj:`ConfigDict` or \ + dict]): Initialization config dict. + """ + + def __init__(self, + num_convs: int = 0, + num_fcs: int = 0, + roi_feat_size: int = 7, + in_channels: int = 256, + conv_out_channels: int = 256, + with_avg_pool: bool = False, + fc_out_channels: int = 1024, + conv_cfg: Optional[dict] = None, + norm_cfg: Optional[dict] = None, + embed_channels: int = 256, + softmax_temp: int = -1, + loss_track: Optional[dict] = None, + loss_track_aux: dict = dict( + type='MarginL2Loss', + sample_ratio=3, + margin=0.3, + loss_weight=1.0, + hard_mining=True), + init_cfg: dict = dict( + type='Xavier', + layer='Linear', + distribution='uniform', + bias=0, + override=dict( + type='Normal', + name='fc_embed', + mean=0, + std=0.01, + bias=0))): + super(QuasiDenseEmbedHead, self).__init__(init_cfg=init_cfg) + self.num_convs = num_convs + self.num_fcs = num_fcs + self.roi_feat_size = _pair(roi_feat_size) + self.roi_feat_area = self.roi_feat_size[0] * self.roi_feat_size[1] + self.in_channels = in_channels + self.conv_out_channels = conv_out_channels + self.with_avg_pool = with_avg_pool + self.fc_out_channels = fc_out_channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + + if self.with_avg_pool: + self.avg_pool = nn.AvgPool2d(self.roi_feat_size) + # add convs and fcs + self.convs, self.fcs, self.last_layer_dim = self._add_conv_fc_branch( + self.num_convs, self.num_fcs, self.in_channels) + self.relu = nn.ReLU(inplace=True) + + if loss_track is None: + loss_track = dict( + type='MultiPosCrossEntropyLoss', loss_weight=0.25) + + self.fc_embed = nn.Linear(self.last_layer_dim, embed_channels) + self.softmax_temp = softmax_temp + self.loss_track = MODELS.build(loss_track) + if loss_track_aux is not None: + self.loss_track_aux = MODELS.build(loss_track_aux) + else: + self.loss_track_aux = None + + def _add_conv_fc_branch( + self, num_branch_convs: int, num_branch_fcs: int, + in_channels: int) -> Tuple[nn.ModuleList, nn.ModuleList, int]: + """Add shared or separable branch. convs -> avg pool (optional) -> fcs. + + Args: + num_branch_convs (int): The number of convoluational layers. + num_branch_fcs (int): The number of fully connection layers. + in_channels (int): The input channel of roi features. + + Returns: + Tuple[nn.ModuleList, nn.ModuleList, int]: The convs, fcs and the + last layer dimension. + """ + last_layer_dim = in_channels + # add branch specific conv layers + branch_convs = nn.ModuleList() + if num_branch_convs > 0: + for i in range(num_branch_convs): + conv_in_channels = ( + last_layer_dim if i == 0 else self.conv_out_channels) + branch_convs.append( + ConvModule( + conv_in_channels, + self.conv_out_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + last_layer_dim = self.conv_out_channels + + # add branch specific fc layers + branch_fcs = nn.ModuleList() + if num_branch_fcs > 0: + if not self.with_avg_pool: + last_layer_dim *= self.roi_feat_area + for i in range(num_branch_fcs): + fc_in_channels = ( + last_layer_dim if i == 0 else self.fc_out_channels) + branch_fcs.append( + nn.Linear(fc_in_channels, self.fc_out_channels)) + last_layer_dim = self.fc_out_channels + + return branch_convs, branch_fcs, last_layer_dim + + def forward(self, x: Tensor) -> Tensor: + """Forward function. + + Args: + x (Tensor): The input features from ROI head. + + Returns: + Tensor: The embedding feature map. + """ + + if self.num_convs > 0: + for conv in self.convs: + x = conv(x) + x = x.flatten(1) + if self.num_fcs > 0: + for fc in self.fcs: + x = self.relu(fc(x)) + x = self.fc_embed(x) + return x + + def get_targets( + self, gt_match_indices: List[Tensor], + key_sampling_results: List[SamplingResult], + ref_sampling_results: List[SamplingResult]) -> Tuple[List, List]: + """Calculate the track targets and track weights for all samples in a + batch according to the sampling_results. + + Args: + gt_match_indices (list(Tensor)): Mapping from gt_instance_ids to + ref_gt_instance_ids of the same tracklet in a pair of images. + key_sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + ref_sampling_results (List[obj:SamplingResult]): Assign results of + all reference images in a batch after sampling. + + Returns: + Tuple[list[Tensor]]: Association results. + Containing the following list of Tensors: + + - track_targets (list[Tensor]): The mapping instance ids from + all positive proposals in the key image to all proposals + in the reference image, each tensor in list has + shape (len(key_pos_bboxes), len(ref_bboxes)). + - track_weights (list[Tensor]): Loss weights for all positive + proposals in a batch, each tensor in list has + shape (len(key_pos_bboxes),). + """ + + track_targets = [] + track_weights = [] + for _gt_match_indices, key_res, ref_res in zip(gt_match_indices, + key_sampling_results, + ref_sampling_results): + targets = _gt_match_indices.new_zeros( + (key_res.pos_bboxes.size(0), ref_res.bboxes.size(0)), + dtype=torch.int) + _match_indices = _gt_match_indices[key_res.pos_assigned_gt_inds] + pos2pos = (_match_indices.view( + -1, 1) == ref_res.pos_assigned_gt_inds.view(1, -1)).int() + targets[:, :pos2pos.size(1)] = pos2pos + weights = (targets.sum(dim=1) > 0).float() + track_targets.append(targets) + track_weights.append(weights) + return track_targets, track_weights + + def match( + self, key_embeds: Tensor, ref_embeds: Tensor, + key_sampling_results: List[SamplingResult], + ref_sampling_results: List[SamplingResult] + ) -> Tuple[List[Tensor], List[Tensor]]: + """Calculate the dist matrixes for loss measurement. + + Args: + key_embeds (Tensor): Embeds of positive bboxes in sampling results + of key image. + ref_embeds (Tensor): Embeds of all bboxes in sampling results + of the reference image. + key_sampling_results (List[obj:SamplingResults]): Assign results of + all images in a batch after sampling. + ref_sampling_results (List[obj:SamplingResults]): Assign results of + all reference images in a batch after sampling. + + Returns: + Tuple[list[Tensor]]: Calculation results. + Containing the following list of Tensors: + + - dists (list[Tensor]): Dot-product dists between + key_embeds and ref_embeds, each tensor in list has + shape (len(key_pos_bboxes), len(ref_bboxes)). + - cos_dists (list[Tensor]): Cosine dists between + key_embeds and ref_embeds, each tensor in list has + shape (len(key_pos_bboxes), len(ref_bboxes)). + """ + + num_key_rois = [res.pos_bboxes.size(0) for res in key_sampling_results] + key_embeds = torch.split(key_embeds, num_key_rois) + num_ref_rois = [res.bboxes.size(0) for res in ref_sampling_results] + ref_embeds = torch.split(ref_embeds, num_ref_rois) + + dists, cos_dists = [], [] + for key_embed, ref_embed in zip(key_embeds, ref_embeds): + dist = embed_similarity( + key_embed, + ref_embed, + method='dot_product', + temperature=self.softmax_temp) + dists.append(dist) + if self.loss_track_aux is not None: + cos_dist = embed_similarity( + key_embed, ref_embed, method='cosine') + cos_dists.append(cos_dist) + else: + cos_dists.append(None) + return dists, cos_dists + + def loss(self, key_roi_feats: Tensor, ref_roi_feats: Tensor, + key_sampling_results: List[SamplingResult], + ref_sampling_results: List[SamplingResult], + gt_match_indices_list: List[Tensor]) -> dict: + """Calculate the track loss and the auxiliary track loss. + + Args: + key_roi_feats (Tensor): Embeds of positive bboxes in sampling + results of key image. + ref_roi_feats (Tensor): Embeds of all bboxes in sampling results + of the reference image. + key_sampling_results (List[obj:SamplingResults]): Assign results of + all images in a batch after sampling. + ref_sampling_results (List[obj:SamplingResults]): Assign results of + all reference images in a batch after sampling. + gt_match_indices_list (list(Tensor)): Mapping from gt_instances_ids + to ref_gt_instances_ids of the same tracklet in a pair of + images. + + Returns: + Dict [str: Tensor]: Calculation results. + Containing the following list of Tensors: + + - loss_track (Tensor): Results of loss_track function. + - loss_track_aux (Tensor): Results of loss_track_aux function. + """ + key_track_feats = self(key_roi_feats) + ref_track_feats = self(ref_roi_feats) + + losses = self.loss_by_feat(key_track_feats, ref_track_feats, + key_sampling_results, ref_sampling_results, + gt_match_indices_list) + return losses + + def loss_by_feat(self, key_track_feats: Tensor, ref_track_feats: Tensor, + key_sampling_results: List[SamplingResult], + ref_sampling_results: List[SamplingResult], + gt_match_indices_list: List[Tensor]) -> dict: + """Calculate the track loss and the auxiliary track loss. + + Args: + key_track_feats (Tensor): Embeds of positive bboxes in sampling + results of key image. + ref_track_feats (Tensor): Embeds of all bboxes in sampling results + of the reference image. + key_sampling_results (List[obj:SamplingResults]): Assign results of + all images in a batch after sampling. + ref_sampling_results (List[obj:SamplingResults]): Assign results of + all reference images in a batch after sampling. + gt_match_indices_list (list(Tensor)): Mapping from instances_ids + from key image to reference image of the same tracklet in a + pair of images. + + Returns: + Dict [str: Tensor]: Calculation results. + Containing the following list of Tensors: + + - loss_track (Tensor): Results of loss_track function. + - loss_track_aux (Tensor): Results of loss_track_aux function. + """ + dists, cos_dists = self.match(key_track_feats, ref_track_feats, + key_sampling_results, + ref_sampling_results) + targets, weights = self.get_targets(gt_match_indices_list, + key_sampling_results, + ref_sampling_results) + losses = dict() + + loss_track = 0. + loss_track_aux = 0. + for _dists, _cos_dists, _targets, _weights in zip( + dists, cos_dists, targets, weights): + loss_track += self.loss_track( + _dists, _targets, _weights, avg_factor=_weights.sum()) + if self.loss_track_aux is not None: + loss_track_aux += self.loss_track_aux(_cos_dists, _targets) + losses['loss_track'] = loss_track / len(dists) + + if self.loss_track_aux is not None: + losses['loss_track_aux'] = loss_track_aux / len(dists) + + return losses + + def predict(self, bbox_feats: Tensor) -> Tensor: + """Perform forward propagation of the tracking head and predict + tracking results on the features of the upstream network. + + Args: + bbox_feats: The extracted roi features. + + Returns: + Tensor: The extracted track features. + """ + track_feats = self(bbox_feats) + return track_feats diff --git a/mmdetection/mmdet/models/tracking_heads/quasi_dense_track_head.py b/mmdetection/mmdet/models/tracking_heads/quasi_dense_track_head.py new file mode 100644 index 00000000..bd078dac --- /dev/null +++ b/mmdetection/mmdet/models/tracking_heads/quasi_dense_track_head.py @@ -0,0 +1,178 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional + +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import TrackSampleList +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import InstanceList + + +@MODELS.register_module() +class QuasiDenseTrackHead(BaseModule): + """The quasi-dense track head.""" + + def __init__(self, + roi_extractor: Optional[dict] = None, + embed_head: Optional[dict] = None, + regress_head: Optional[dict] = None, + train_cfg: Optional[dict] = None, + test_cfg: Optional[dict] = None, + init_cfg: Optional[dict] = None, + **kwargs): + super().__init__(init_cfg=init_cfg) + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + if embed_head is not None: + self.init_embed_head(roi_extractor, embed_head) + + if regress_head is not None: + raise NotImplementedError('Regression head is not supported yet.') + + self.init_assigner_sampler() + + def init_embed_head(self, roi_extractor, embed_head) -> None: + """Initialize ``embed_head`` + + Args: + roi_extractor (dict, optional): Configuration of roi extractor. + Defaults to None. + embed_head (dict, optional): Configuration of embed head. Defaults + to None. + """ + self.roi_extractor = MODELS.build(roi_extractor) + self.embed_head = MODELS.build(embed_head) + + def init_assigner_sampler(self) -> None: + """Initialize assigner and sampler.""" + self.bbox_assigner = None + self.bbox_sampler = None + if self.train_cfg: + self.bbox_assigner = TASK_UTILS.build(self.train_cfg.assigner) + self.bbox_sampler = TASK_UTILS.build( + self.train_cfg.sampler, default_args=dict(context=self)) + + @property + def with_track(self) -> bool: + """bool: whether the multi-object tracker has an embed head""" + return hasattr(self, 'embed_head') and self.embed_head is not None + + def extract_roi_feats(self, feats: List[Tensor], + bboxes: List[Tensor]) -> Tensor: + """Extract roi features. + + Args: + feats (list[Tensor]): list of multi-level image features. + bboxes (list[Tensor]): list of bboxes in sampling result. + + Returns: + Tensor: The extracted roi features. + """ + rois = bbox2roi(bboxes) + bbox_feats = self.roi_extractor(feats[:self.roi_extractor.num_inputs], + rois) + return bbox_feats + + def loss(self, key_feats: List[Tensor], ref_feats: List[Tensor], + rpn_results_list: InstanceList, + ref_rpn_results_list: InstanceList, data_samples: TrackSampleList, + **kwargs) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + key_feats (list[Tensor]): list of multi-level image features. + ref_feats (list[Tensor]): list of multi-level ref_img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals of key img. + ref_rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals of ref img. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance`. + + Returns: + dict: A dictionary of loss components. + """ + assert self.with_track + num_imgs = len(data_samples) + batch_gt_instances = [] + ref_batch_gt_instances = [] + batch_gt_instances_ignore = [] + gt_match_indices_list = [] + for track_data_sample in data_samples: + key_data_sample = track_data_sample.get_key_frames()[0] + ref_data_sample = track_data_sample.get_ref_frames()[0] + batch_gt_instances.append(key_data_sample.gt_instances) + ref_batch_gt_instances.append(ref_data_sample.gt_instances) + if 'ignored_instances' in key_data_sample: + batch_gt_instances_ignore.append( + key_data_sample.ignored_instances) + else: + batch_gt_instances_ignore.append(None) + # get gt_match_indices + ins_ids = key_data_sample.gt_instances.instances_ids.tolist() + ref_ins_ids = ref_data_sample.gt_instances.instances_ids.tolist() + match_indices = Tensor([ + ref_ins_ids.index(i) if (i in ref_ins_ids and i > 0) else -1 + for i in ins_ids + ]).to(key_feats[0].device) + gt_match_indices_list.append(match_indices) + + key_sampling_results, ref_sampling_results = [], [] + for i in range(num_imgs): + rpn_results = rpn_results_list[i] + ref_rpn_results = ref_rpn_results_list[i] + # rename ref_rpn_results.bboxes to ref_rpn_results.priors + ref_rpn_results.priors = ref_rpn_results.pop('bboxes') + + assign_result = self.bbox_assigner.assign( + rpn_results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = self.bbox_sampler.sample( + assign_result, + rpn_results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in key_feats]) + key_sampling_results.append(sampling_result) + + ref_assign_result = self.bbox_assigner.assign( + ref_rpn_results, ref_batch_gt_instances[i], + batch_gt_instances_ignore[i]) + ref_sampling_result = self.bbox_sampler.sample( + ref_assign_result, + ref_rpn_results, + ref_batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in ref_feats]) + ref_sampling_results.append(ref_sampling_result) + + key_bboxes = [res.pos_bboxes for res in key_sampling_results] + key_roi_feats = self.extract_roi_feats(key_feats, key_bboxes) + ref_bboxes = [res.bboxes for res in ref_sampling_results] + ref_roi_feats = self.extract_roi_feats(ref_feats, ref_bboxes) + + loss_track = self.embed_head.loss(key_roi_feats, ref_roi_feats, + key_sampling_results, + ref_sampling_results, + gt_match_indices_list) + + return loss_track + + def predict(self, feats: List[Tensor], + rescaled_bboxes: List[Tensor]) -> Tensor: + """Perform forward propagation of the tracking head and predict + tracking results on the features of the upstream network. + + Args: + feats (list[Tensor]): Multi level feature maps of `img`. + rescaled_bboxes (list[Tensor]): list of rescaled bboxes in sampling + result. + + Returns: + Tensor: The extracted track features. + """ + bbox_feats = self.extract_roi_feats(feats, rescaled_bboxes) + track_feats = self.embed_head.predict(bbox_feats) + return track_feats diff --git a/mmdetection/mmdet/models/tracking_heads/roi_embed_head.py b/mmdetection/mmdet/models/tracking_heads/roi_embed_head.py new file mode 100644 index 00000000..e18b81fb --- /dev/null +++ b/mmdetection/mmdet/models/tracking_heads/roi_embed_head.py @@ -0,0 +1,391 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections import defaultdict +from typing import List, Optional, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch import Tensor +from torch.nn.modules.utils import _pair + +from mmdet.models.losses import accuracy +from mmdet.models.task_modules import SamplingResult +from mmdet.models.task_modules.tracking import embed_similarity +from mmdet.registry import MODELS + + +@MODELS.register_module() +class RoIEmbedHead(BaseModule): + """The roi embed head. + + This module is used in multi-object tracking methods, such as MaskTrack + R-CNN. + + Args: + num_convs (int): The number of convoluational layers to embed roi + features. Defaults to 0. + num_fcs (int): The number of fully connection layers to embed roi + features. Defaults to 0. + roi_feat_size (int|tuple(int)): The spatial size of roi features. + Defaults to 7. + in_channels (int): The input channel of roi features. Defaults to 256. + conv_out_channels (int): The output channel of roi features after + forwarding convoluational layers. Defaults to 256. + with_avg_pool (bool): Whether use average pooling before passing roi + features into fully connection layers. Defaults to False. + fc_out_channels (int): The output channel of roi features after + forwarding fully connection layers. Defaults to 1024. + conv_cfg (dict): Config dict for convolution layer. Defaults to None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. Defaults to None. + loss_match (dict): The loss function. Defaults to + dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) + init_cfg (dict): Configuration of initialization. Defaults to None. + """ + + def __init__(self, + num_convs: int = 0, + num_fcs: int = 0, + roi_feat_size: int = 7, + in_channels: int = 256, + conv_out_channels: int = 256, + with_avg_pool: bool = False, + fc_out_channels: int = 1024, + conv_cfg: Optional[dict] = None, + norm_cfg: Optional[dict] = None, + loss_match: dict = dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + init_cfg: Optional[dict] = None, + **kwargs): + super(RoIEmbedHead, self).__init__(init_cfg=init_cfg) + self.num_convs = num_convs + self.num_fcs = num_fcs + self.roi_feat_size = _pair(roi_feat_size) + self.roi_feat_area = self.roi_feat_size[0] * self.roi_feat_size[1] + self.in_channels = in_channels + self.conv_out_channels = conv_out_channels + self.with_avg_pool = with_avg_pool + self.fc_out_channels = fc_out_channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.loss_match = MODELS.build(loss_match) + self.fp16_enabled = False + + if self.with_avg_pool: + self.avg_pool = nn.AvgPool2d(self.roi_feat_size) + # add convs and fcs + self.convs, self.fcs, self.last_layer_dim = self._add_conv_fc_branch( + self.num_convs, self.num_fcs, self.in_channels) + self.relu = nn.ReLU(inplace=True) + + def _add_conv_fc_branch( + self, num_branch_convs: int, num_branch_fcs: int, + in_channels: int) -> Tuple[nn.ModuleList, nn.ModuleList, int]: + """Add shared or separable branch. + + convs -> avg pool (optional) -> fcs + """ + last_layer_dim = in_channels + # add branch specific conv layers + branch_convs = nn.ModuleList() + if num_branch_convs > 0: + for i in range(num_branch_convs): + conv_in_channels = ( + last_layer_dim if i == 0 else self.conv_out_channels) + branch_convs.append( + ConvModule( + conv_in_channels, + self.conv_out_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg)) + last_layer_dim = self.conv_out_channels + + # add branch specific fc layers + branch_fcs = nn.ModuleList() + if num_branch_fcs > 0: + if not self.with_avg_pool: + last_layer_dim *= self.roi_feat_area + for i in range(num_branch_fcs): + fc_in_channels = ( + last_layer_dim if i == 0 else self.fc_out_channels) + branch_fcs.append( + nn.Linear(fc_in_channels, self.fc_out_channels)) + last_layer_dim = self.fc_out_channels + + return branch_convs, branch_fcs, last_layer_dim + + @property + def custom_activation(self): + return getattr(self.loss_match, 'custom_activation', False) + + def extract_feat(self, x: Tensor, + num_x_per_img: List[int]) -> Tuple[Tensor]: + """Extract feature from the input `x`, and split the output to a list. + + Args: + x (Tensor): of shape [N, C, H, W]. N is the number of proposals. + num_x_per_img (list[int]): The `x` contains proposals of + multi-images. `num_x_per_img` denotes the number of proposals + for each image. + + Returns: + list[Tensor]: Each Tensor denotes the embed features belonging to + an image in a batch. + """ + if self.num_convs > 0: + for conv in self.convs: + x = conv(x) + + if self.num_fcs > 0: + if self.with_avg_pool: + x = self.avg_pool(x) + x = x.flatten(1) + for fc in self.fcs: + x = self.relu(fc(x)) + else: + x = x.flatten(1) + + x_split = torch.split(x, num_x_per_img, dim=0) + return x_split + + def forward( + self, x: Tensor, ref_x: Tensor, num_x_per_img: List[int], + num_x_per_ref_img: List[int] + ) -> Tuple[Tuple[Tensor], Tuple[Tensor]]: + """Computing the similarity scores between `x` and `ref_x`. + + Args: + x (Tensor): of shape [N, C, H, W]. N is the number of key frame + proposals. + ref_x (Tensor): of shape [M, C, H, W]. M is the number of reference + frame proposals. + num_x_per_img (list[int]): The `x` contains proposals of + multi-images. `num_x_per_img` denotes the number of proposals + for each key image. + num_x_per_ref_img (list[int]): The `ref_x` contains proposals of + multi-images. `num_x_per_ref_img` denotes the number of + proposals for each reference image. + + Returns: + tuple[tuple[Tensor], tuple[Tensor]]: Each tuple of tensor denotes + the embed features belonging to an image in a batch. + """ + x_split = self.extract_feat(x, num_x_per_img) + ref_x_split = self.extract_feat(ref_x, num_x_per_ref_img) + + return x_split, ref_x_split + + def get_targets(self, sampling_results: List[SamplingResult], + gt_instance_ids: List[Tensor], + ref_gt_instance_ids: List[Tensor]) -> Tuple[List, List]: + """Calculate the ground truth for all samples in a batch according to + the sampling_results. + + Args: + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + gt_instance_ids (list[Tensor]): The instance ids of gt_bboxes of + all images in a batch, each tensor has shape (num_gt, ). + ref_gt_instance_ids (list[Tensor]): The instance ids of gt_bboxes + of all reference images in a batch, each tensor has shape + (num_gt, ). + + Returns: + Tuple[list[Tensor]]: Ground truth for proposals in a batch. + Containing the following list of Tensors: + + - track_id_targets (list[Tensor]): The instance ids of + Gt_labels for all proposals in a batch, each tensor in list + has shape (num_proposals,). + - track_id_weights (list[Tensor]): Labels_weights for + all proposals in a batch, each tensor in list has + shape (num_proposals,). + """ + track_id_targets = [] + track_id_weights = [] + + for res, gt_instance_id, ref_gt_instance_id in zip( + sampling_results, gt_instance_ids, ref_gt_instance_ids): + pos_instance_ids = gt_instance_id[res.pos_assigned_gt_inds] + pos_match_id = gt_instance_id.new_zeros(len(pos_instance_ids)) + for i, id in enumerate(pos_instance_ids): + if id in ref_gt_instance_id: + pos_match_id[i] = ref_gt_instance_id.tolist().index(id) + 1 + + track_id_target = gt_instance_id.new_zeros( + len(res.bboxes), dtype=torch.int64) + track_id_target[:len(res.pos_bboxes)] = pos_match_id + track_id_weight = res.bboxes.new_zeros(len(res.bboxes)) + track_id_weight[:len(res.pos_bboxes)] = 1.0 + + track_id_targets.append(track_id_target) + track_id_weights.append(track_id_weight) + + return track_id_targets, track_id_weights + + def loss( + self, + bbox_feats: Tensor, + ref_bbox_feats: Tensor, + num_bbox_per_img: int, + num_bbox_per_ref_img: int, + sampling_results: List[SamplingResult], + gt_instance_ids: List[Tensor], + ref_gt_instance_ids: List[Tensor], + reduction_override: Optional[str] = None, + ) -> dict: + """Calculate the loss in a batch. + + Args: + bbox_feats (Tensor): of shape [N, C, H, W]. N is the number of + bboxes. + ref_bbox_feats (Tensor): of shape [M, C, H, W]. M is the number of + reference bboxes. + num_bbox_per_img (list[int]): The `bbox_feats` contains proposals + of multi-images. `num_bbox_per_img` denotes the number of + proposals for each key image. + num_bbox_per_ref_img (list[int]): The `ref_bbox_feats` contains + proposals of multi-images. `num_bbox_per_ref_img` denotes the + number of proposals for each reference image. + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + gt_instance_ids (list[Tensor]): The instance ids of gt_bboxes of + all images in a batch, each tensor has shape (num_gt, ). + ref_gt_instance_ids (list[Tensor]): The instance ids of gt_bboxes + of all reference images in a batch, each tensor has shape + (num_gt, ). + reduction_override (str, optional): The method used to reduce the + loss. Options are "none", "mean" and "sum". + + Returns: + dict[str, Tensor]: a dictionary of loss components. + """ + x_split, ref_x_split = self(bbox_feats, ref_bbox_feats, + num_bbox_per_img, num_bbox_per_ref_img) + + losses = self.loss_by_feat(x_split, ref_x_split, sampling_results, + gt_instance_ids, ref_gt_instance_ids, + reduction_override) + return losses + + def loss_by_feat(self, + x_split: Tuple[Tensor], + ref_x_split: Tuple[Tensor], + sampling_results: List[SamplingResult], + gt_instance_ids: List[Tensor], + ref_gt_instance_ids: List[Tensor], + reduction_override: Optional[str] = None) -> dict: + """Calculate losses. + + Args: + x_split (Tensor): The embed features belonging to key image. + ref_x_split (Tensor): The embed features belonging to ref image. + sampling_results (List[obj:SamplingResult]): Assign results of + all images in a batch after sampling. + gt_instance_ids (list[Tensor]): The instance ids of gt_bboxes of + all images in a batch, each tensor has shape (num_gt, ). + ref_gt_instance_ids (list[Tensor]): The instance ids of gt_bboxes + of all reference images in a batch, each tensor has shape + (num_gt, ). + reduction_override (str, optional): The method used to reduce the + loss. Options are "none", "mean" and "sum". + + Returns: + dict[str, Tensor]: a dictionary of loss components. + """ + track_id_targets, track_id_weights = self.get_targets( + sampling_results, gt_instance_ids, ref_gt_instance_ids) + assert isinstance(track_id_targets, list) + assert isinstance(track_id_weights, list) + assert len(track_id_weights) == len(track_id_targets) + + losses = defaultdict(list) + similarity_logits = [] + for one_x, one_ref_x in zip(x_split, ref_x_split): + similarity_logit = embed_similarity( + one_x, one_ref_x, method='dot_product') + dummy = similarity_logit.new_zeros(one_x.shape[0], 1) + similarity_logit = torch.cat((dummy, similarity_logit), dim=1) + similarity_logits.append(similarity_logit) + assert isinstance(similarity_logits, list) + assert len(similarity_logits) == len(track_id_targets) + + for similarity_logit, track_id_target, track_id_weight in zip( + similarity_logits, track_id_targets, track_id_weights): + avg_factor = max(torch.sum(track_id_target > 0).float().item(), 1.) + if similarity_logit.numel() > 0: + loss_match = self.loss_match( + similarity_logit, + track_id_target, + track_id_weight, + avg_factor=avg_factor, + reduction_override=reduction_override) + if isinstance(loss_match, dict): + for key, value in loss_match.items(): + losses[key].append(value) + else: + losses['loss_match'].append(loss_match) + + valid_index = track_id_weight > 0 + valid_similarity_logit = similarity_logit[valid_index] + valid_track_id_target = track_id_target[valid_index] + if self.custom_activation: + match_accuracy = self.loss_match.get_accuracy( + valid_similarity_logit, valid_track_id_target) + for key, value in match_accuracy.items(): + losses[key].append(value) + else: + losses['match_accuracy'].append( + accuracy(valid_similarity_logit, + valid_track_id_target)) + + for key, value in losses.items(): + losses[key] = sum(losses[key]) / len(similarity_logits) + return losses + + def predict(self, roi_feats: Tensor, + prev_roi_feats: Tensor) -> List[Tensor]: + """Perform forward propagation of the tracking head and predict + tracking results on the features of the upstream network. + + Args: + roi_feats (Tensor): Feature map of current images rois. + prev_roi_feats (Tensor): Feature map of previous images rois. + + Returns: + list[Tensor]: The predicted similarity_logits of each pair of key + image and reference image. + """ + x_split, ref_x_split = self(roi_feats, prev_roi_feats, + [roi_feats.shape[0]], + [prev_roi_feats.shape[0]]) + + similarity_logits = self.predict_by_feat(x_split, ref_x_split) + + return similarity_logits + + def predict_by_feat(self, x_split: Tuple[Tensor], + ref_x_split: Tuple[Tensor]) -> List[Tensor]: + """Get similarity_logits. + + Args: + x_split (Tensor): The embed features belonging to key image. + ref_x_split (Tensor): The embed features belonging to ref image. + + Returns: + list[Tensor]: The predicted similarity_logits of each pair of key + image and reference image. + """ + similarity_logits = [] + for one_x, one_ref_x in zip(x_split, ref_x_split): + similarity_logit = embed_similarity( + one_x, one_ref_x, method='dot_product') + dummy = similarity_logit.new_zeros(one_x.shape[0], 1) + similarity_logit = torch.cat((dummy, similarity_logit), dim=1) + similarity_logits.append(similarity_logit) + return similarity_logits diff --git a/mmdetection/mmdet/models/tracking_heads/roi_track_head.py b/mmdetection/mmdet/models/tracking_heads/roi_track_head.py new file mode 100644 index 00000000..c51c8100 --- /dev/null +++ b/mmdetection/mmdet/models/tracking_heads/roi_track_head.py @@ -0,0 +1,178 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta +from typing import List, Optional, Tuple + +from mmengine.model import BaseModule +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import TrackSampleList +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import InstanceList + + +@MODELS.register_module() +class RoITrackHead(BaseModule, metaclass=ABCMeta): + """The roi track head. + + This module is used in multi-object tracking methods, such as MaskTrack + R-CNN. + + Args: + roi_extractor (dict): Configuration of roi extractor. Defaults to None. + embed_head (dict): Configuration of embed head. Defaults to None. + train_cfg (dict): Configuration when training. Defaults to None. + test_cfg (dict): Configuration when testing. Defaults to None. + init_cfg (dict): Configuration of initialization. Defaults to None. + """ + + def __init__(self, + roi_extractor: Optional[dict] = None, + embed_head: Optional[dict] = None, + regress_head: Optional[dict] = None, + train_cfg: Optional[dict] = None, + test_cfg: Optional[dict] = None, + init_cfg: Optional[dict] = None, + *args, + **kwargs): + super().__init__(init_cfg=init_cfg) + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + if embed_head is not None: + self.init_embed_head(roi_extractor, embed_head) + + if regress_head is not None: + raise NotImplementedError('Regression head is not supported yet.') + + self.init_assigner_sampler() + + def init_embed_head(self, roi_extractor, embed_head) -> None: + """Initialize ``embed_head``""" + self.roi_extractor = MODELS.build(roi_extractor) + self.embed_head = MODELS.build(embed_head) + + def init_assigner_sampler(self) -> None: + """Initialize assigner and sampler.""" + self.bbox_assigner = None + self.bbox_sampler = None + if self.train_cfg: + self.bbox_assigner = TASK_UTILS.build(self.train_cfg.assigner) + self.bbox_sampler = TASK_UTILS.build( + self.train_cfg.sampler, default_args=dict(context=self)) + + @property + def with_track(self) -> bool: + """bool: whether the multi-object tracker has an embed head""" + return hasattr(self, 'embed_head') and self.embed_head is not None + + def extract_roi_feats( + self, feats: List[Tensor], + bboxes: List[Tensor]) -> Tuple[Tuple[Tensor], List[int]]: + """Extract roi features. + + Args: + feats (list[Tensor]): list of multi-level image features. + bboxes (list[Tensor]): list of bboxes in sampling result. + + Returns: + tuple[tuple[Tensor], list[int]]: The extracted roi features and + the number of bboxes in each image. + """ + rois = bbox2roi(bboxes) + bbox_feats = self.roi_extractor(feats[:self.roi_extractor.num_inputs], + rois) + num_bbox_per_img = [len(bbox) for bbox in bboxes] + return bbox_feats, num_bbox_per_img + + def loss(self, key_feats: List[Tensor], ref_feats: List[Tensor], + rpn_results_list: InstanceList, data_samples: TrackSampleList, + **kwargs) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + key_feats (list[Tensor]): list of multi-level image features. + ref_feats (list[Tensor]): list of multi-level ref_img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance`. + + Returns: + dict: A dictionary of loss components. + """ + assert self.with_track + batch_gt_instances = [] + ref_batch_gt_instances = [] + batch_gt_instances_ignore = [] + gt_instance_ids = [] + ref_gt_instance_ids = [] + for track_data_sample in data_samples: + key_data_sample = track_data_sample.get_key_frames()[0] + ref_data_sample = track_data_sample.get_ref_frames()[0] + batch_gt_instances.append(key_data_sample.gt_instances) + ref_batch_gt_instances.append(ref_data_sample.gt_instances) + if 'ignored_instances' in key_data_sample: + batch_gt_instances_ignore.append( + key_data_sample.ignored_instances) + else: + batch_gt_instances_ignore.append(None) + + gt_instance_ids.append(key_data_sample.gt_instances.instances_ids) + ref_gt_instance_ids.append( + ref_data_sample.gt_instances.instances_ids) + + losses = dict() + num_imgs = len(data_samples) + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + sampling_results = [] + for i in range(num_imgs): + rpn_results = rpn_results_list[i] + + assign_result = self.bbox_assigner.assign( + rpn_results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = self.bbox_sampler.sample( + assign_result, + rpn_results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in key_feats]) + sampling_results.append(sampling_result) + + bboxes = [res.bboxes for res in sampling_results] + bbox_feats, num_bbox_per_img = self.extract_roi_feats( + key_feats, bboxes) + + # batch_size is 1 + ref_gt_bboxes = [ + ref_batch_gt_instance.bboxes + for ref_batch_gt_instance in ref_batch_gt_instances + ] + ref_bbox_feats, num_bbox_per_ref_img = self.extract_roi_feats( + ref_feats, ref_gt_bboxes) + + loss_track = self.embed_head.loss(bbox_feats, ref_bbox_feats, + num_bbox_per_img, + num_bbox_per_ref_img, + sampling_results, gt_instance_ids, + ref_gt_instance_ids) + losses.update(loss_track) + + return losses + + def predict(self, roi_feats: Tensor, + prev_roi_feats: Tensor) -> List[Tensor]: + """Perform forward propagation of the tracking head and predict + tracking results on the features of the upstream network. + + Args: + roi_feats (Tensor): Feature map of current images rois. + prev_roi_feats (Tensor): Feature map of previous images rois. + + Returns: + list[Tensor]: The predicted similarity_logits of each pair of key + image and reference image. + """ + return self.embed_head.predict(roi_feats, prev_roi_feats)[0] diff --git a/mmdetection/mmdet/models/utils/__init__.py b/mmdetection/mmdet/models/utils/__init__.py new file mode 100644 index 00000000..a00d9a37 --- /dev/null +++ b/mmdetection/mmdet/models/utils/__init__.py @@ -0,0 +1,37 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .gaussian_target import (gather_feat, gaussian_radius, + gen_gaussian_target, get_local_maximum, + get_topk_from_heatmap, transpose_and_gather_feat) +from .image import imrenormalize +from .make_divisible import make_divisible +# Disable yapf because it conflicts with isort. +# yapf: disable +from .misc import (align_tensor, aligned_bilinear, center_of_mass, + empty_instances, filter_gt_instances, + filter_scores_and_topk, flip_tensor, generate_coordinate, + images_to_levels, interpolate_as, levels_to_images, + mask2ndarray, multi_apply, relative_coordinate_maps, + rename_loss_dict, reweight_loss_dict, + samplelist_boxtype2tensor, select_single_mlvl, + sigmoid_geometric_mean, unfold_wo_center, unmap, + unpack_gt_instances) +from .panoptic_gt_processing import preprocess_panoptic_gt +from .point_sample import (get_uncertain_point_coords_with_randomness, + get_uncertainty) +from .vlfuse_helper import BertEncoderLayer, VLFuse, permute_and_flatten +from .wbf import weighted_boxes_fusion + +__all__ = [ + 'gaussian_radius', 'gen_gaussian_target', 'make_divisible', + 'get_local_maximum', 'get_topk_from_heatmap', 'transpose_and_gather_feat', + 'interpolate_as', 'sigmoid_geometric_mean', 'gather_feat', + 'preprocess_panoptic_gt', 'get_uncertain_point_coords_with_randomness', + 'get_uncertainty', 'unpack_gt_instances', 'empty_instances', + 'center_of_mass', 'filter_scores_and_topk', 'flip_tensor', + 'generate_coordinate', 'levels_to_images', 'mask2ndarray', 'multi_apply', + 'select_single_mlvl', 'unmap', 'images_to_levels', + 'samplelist_boxtype2tensor', 'filter_gt_instances', 'rename_loss_dict', + 'reweight_loss_dict', 'relative_coordinate_maps', 'aligned_bilinear', + 'unfold_wo_center', 'imrenormalize', 'VLFuse', 'permute_and_flatten', + 'BertEncoderLayer', 'align_tensor', 'weighted_boxes_fusion' +] diff --git a/mmdetection/mmdet/models/utils/gaussian_target.py b/mmdetection/mmdet/models/utils/gaussian_target.py new file mode 100644 index 00000000..5bf4d558 --- /dev/null +++ b/mmdetection/mmdet/models/utils/gaussian_target.py @@ -0,0 +1,268 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from math import sqrt + +import torch +import torch.nn.functional as F + + +def gaussian2D(radius, sigma=1, dtype=torch.float32, device='cpu'): + """Generate 2D gaussian kernel. + + Args: + radius (int): Radius of gaussian kernel. + sigma (int): Sigma of gaussian function. Default: 1. + dtype (torch.dtype): Dtype of gaussian tensor. Default: torch.float32. + device (str): Device of gaussian tensor. Default: 'cpu'. + + Returns: + h (Tensor): Gaussian kernel with a + ``(2 * radius + 1) * (2 * radius + 1)`` shape. + """ + x = torch.arange( + -radius, radius + 1, dtype=dtype, device=device).view(1, -1) + y = torch.arange( + -radius, radius + 1, dtype=dtype, device=device).view(-1, 1) + + h = (-(x * x + y * y) / (2 * sigma * sigma)).exp() + + h[h < torch.finfo(h.dtype).eps * h.max()] = 0 + return h + + +def gen_gaussian_target(heatmap, center, radius, k=1): + """Generate 2D gaussian heatmap. + + Args: + heatmap (Tensor): Input heatmap, the gaussian kernel will cover on + it and maintain the max value. + center (list[int]): Coord of gaussian kernel's center. + radius (int): Radius of gaussian kernel. + k (int): Coefficient of gaussian kernel. Default: 1. + + Returns: + out_heatmap (Tensor): Updated heatmap covered by gaussian kernel. + """ + diameter = 2 * radius + 1 + gaussian_kernel = gaussian2D( + radius, sigma=diameter / 6, dtype=heatmap.dtype, device=heatmap.device) + + x, y = center + + height, width = heatmap.shape[:2] + + left, right = min(x, radius), min(width - x, radius + 1) + top, bottom = min(y, radius), min(height - y, radius + 1) + + masked_heatmap = heatmap[y - top:y + bottom, x - left:x + right] + masked_gaussian = gaussian_kernel[radius - top:radius + bottom, + radius - left:radius + right] + out_heatmap = heatmap + torch.max( + masked_heatmap, + masked_gaussian * k, + out=out_heatmap[y - top:y + bottom, x - left:x + right]) + + return out_heatmap + + +def gaussian_radius(det_size, min_overlap): + r"""Generate 2D gaussian radius. + + This function is modified from the `official github repo + `_. + + Given ``min_overlap``, radius could computed by a quadratic equation + according to Vieta's formulas. + + There are 3 cases for computing gaussian radius, details are following: + + - Explanation of figure: ``lt`` and ``br`` indicates the left-top and + bottom-right corner of ground truth box. ``x`` indicates the + generated corner at the limited position when ``radius=r``. + + - Case1: one corner is inside the gt box and the other is outside. + + .. code:: text + + |< width >| + + lt-+----------+ - + | | | ^ + +--x----------+--+ + | | | | + | | | | height + | | overlap | | + | | | | + | | | | v + +--+---------br--+ - + | | | + +----------+--x + + To ensure IoU of generated box and gt box is larger than ``min_overlap``: + + .. math:: + \cfrac{(w-r)*(h-r)}{w*h+(w+h)r-r^2} \ge {iou} \quad\Rightarrow\quad + {r^2-(w+h)r+\cfrac{1-iou}{1+iou}*w*h} \ge 0 \\ + {a} = 1,\quad{b} = {-(w+h)},\quad{c} = {\cfrac{1-iou}{1+iou}*w*h} + {r} \le \cfrac{-b-\sqrt{b^2-4*a*c}}{2*a} + + - Case2: both two corners are inside the gt box. + + .. code:: text + + |< width >| + + lt-+----------+ - + | | | ^ + +--x-------+ | + | | | | + | |overlap| | height + | | | | + | +-------x--+ + | | | v + +----------+-br - + + To ensure IoU of generated box and gt box is larger than ``min_overlap``: + + .. math:: + \cfrac{(w-2*r)*(h-2*r)}{w*h} \ge {iou} \quad\Rightarrow\quad + {4r^2-2(w+h)r+(1-iou)*w*h} \ge 0 \\ + {a} = 4,\quad {b} = {-2(w+h)},\quad {c} = {(1-iou)*w*h} + {r} \le \cfrac{-b-\sqrt{b^2-4*a*c}}{2*a} + + - Case3: both two corners are outside the gt box. + + .. code:: text + + |< width >| + + x--+----------------+ + | | | + +-lt-------------+ | - + | | | | ^ + | | | | + | | overlap | | height + | | | | + | | | | v + | +------------br--+ - + | | | + +----------------+--x + + To ensure IoU of generated box and gt box is larger than ``min_overlap``: + + .. math:: + \cfrac{w*h}{(w+2*r)*(h+2*r)} \ge {iou} \quad\Rightarrow\quad + {4*iou*r^2+2*iou*(w+h)r+(iou-1)*w*h} \le 0 \\ + {a} = {4*iou},\quad {b} = {2*iou*(w+h)},\quad {c} = {(iou-1)*w*h} \\ + {r} \le \cfrac{-b+\sqrt{b^2-4*a*c}}{2*a} + + Args: + det_size (list[int]): Shape of object. + min_overlap (float): Min IoU with ground truth for boxes generated by + keypoints inside the gaussian kernel. + + Returns: + radius (int): Radius of gaussian kernel. + """ + height, width = det_size + + a1 = 1 + b1 = (height + width) + c1 = width * height * (1 - min_overlap) / (1 + min_overlap) + sq1 = sqrt(b1**2 - 4 * a1 * c1) + r1 = (b1 - sq1) / (2 * a1) + + a2 = 4 + b2 = 2 * (height + width) + c2 = (1 - min_overlap) * width * height + sq2 = sqrt(b2**2 - 4 * a2 * c2) + r2 = (b2 - sq2) / (2 * a2) + + a3 = 4 * min_overlap + b3 = -2 * min_overlap * (height + width) + c3 = (min_overlap - 1) * width * height + sq3 = sqrt(b3**2 - 4 * a3 * c3) + r3 = (b3 + sq3) / (2 * a3) + return min(r1, r2, r3) + + +def get_local_maximum(heat, kernel=3): + """Extract local maximum pixel with given kernel. + + Args: + heat (Tensor): Target heatmap. + kernel (int): Kernel size of max pooling. Default: 3. + + Returns: + heat (Tensor): A heatmap where local maximum pixels maintain its + own value and other positions are 0. + """ + pad = (kernel - 1) // 2 + hmax = F.max_pool2d(heat, kernel, stride=1, padding=pad) + keep = (hmax == heat).float() + return heat * keep + + +def get_topk_from_heatmap(scores, k=20): + """Get top k positions from heatmap. + + Args: + scores (Tensor): Target heatmap with shape + [batch, num_classes, height, width]. + k (int): Target number. Default: 20. + + Returns: + tuple[torch.Tensor]: Scores, indexes, categories and coords of + topk keypoint. Containing following Tensors: + + - topk_scores (Tensor): Max scores of each topk keypoint. + - topk_inds (Tensor): Indexes of each topk keypoint. + - topk_clses (Tensor): Categories of each topk keypoint. + - topk_ys (Tensor): Y-coord of each topk keypoint. + - topk_xs (Tensor): X-coord of each topk keypoint. + """ + batch, _, height, width = scores.size() + topk_scores, topk_inds = torch.topk(scores.view(batch, -1), k) + topk_clses = topk_inds // (height * width) + topk_inds = topk_inds % (height * width) + topk_ys = topk_inds // width + topk_xs = (topk_inds % width).int().float() + return topk_scores, topk_inds, topk_clses, topk_ys, topk_xs + + +def gather_feat(feat, ind, mask=None): + """Gather feature according to index. + + Args: + feat (Tensor): Target feature map. + ind (Tensor): Target coord index. + mask (Tensor | None): Mask of feature map. Default: None. + + Returns: + feat (Tensor): Gathered feature. + """ + dim = feat.size(2) + ind = ind.unsqueeze(2).repeat(1, 1, dim) + feat = feat.gather(1, ind) + if mask is not None: + mask = mask.unsqueeze(2).expand_as(feat) + feat = feat[mask] + feat = feat.view(-1, dim) + return feat + + +def transpose_and_gather_feat(feat, ind): + """Transpose and gather feature according to index. + + Args: + feat (Tensor): Target feature map. + ind (Tensor): Target coord index. + + Returns: + feat (Tensor): Transposed and gathered feature. + """ + feat = feat.permute(0, 2, 3, 1).contiguous() + feat = feat.view(feat.size(0), -1, feat.size(3)) + feat = gather_feat(feat, ind) + return feat diff --git a/mmdetection/mmdet/models/utils/image.py b/mmdetection/mmdet/models/utils/image.py new file mode 100644 index 00000000..16b5787a --- /dev/null +++ b/mmdetection/mmdet/models/utils/image.py @@ -0,0 +1,52 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Union + +import mmcv +import numpy as np +import torch +from torch import Tensor + + +def imrenormalize(img: Union[Tensor, np.ndarray], img_norm_cfg: dict, + new_img_norm_cfg: dict) -> Union[Tensor, np.ndarray]: + """Re-normalize the image. + + Args: + img (Tensor | ndarray): Input image. If the input is a Tensor, the + shape is (1, C, H, W). If the input is a ndarray, the shape + is (H, W, C). + img_norm_cfg (dict): Original configuration for the normalization. + new_img_norm_cfg (dict): New configuration for the normalization. + + Returns: + Tensor | ndarray: Output image with the same type and shape of + the input. + """ + if isinstance(img, torch.Tensor): + assert img.ndim == 4 and img.shape[0] == 1 + new_img = img.squeeze(0).cpu().numpy().transpose(1, 2, 0) + new_img = _imrenormalize(new_img, img_norm_cfg, new_img_norm_cfg) + new_img = new_img.transpose(2, 0, 1)[None] + return torch.from_numpy(new_img).to(img) + else: + return _imrenormalize(img, img_norm_cfg, new_img_norm_cfg) + + +def _imrenormalize(img: Union[Tensor, np.ndarray], img_norm_cfg: dict, + new_img_norm_cfg: dict) -> Union[Tensor, np.ndarray]: + """Re-normalize the image.""" + img_norm_cfg = img_norm_cfg.copy() + new_img_norm_cfg = new_img_norm_cfg.copy() + for k, v in img_norm_cfg.items(): + if (k == 'mean' or k == 'std') and not isinstance(v, np.ndarray): + img_norm_cfg[k] = np.array(v, dtype=img.dtype) + # reverse cfg + if 'bgr_to_rgb' in img_norm_cfg: + img_norm_cfg['rgb_to_bgr'] = img_norm_cfg['bgr_to_rgb'] + img_norm_cfg.pop('bgr_to_rgb') + for k, v in new_img_norm_cfg.items(): + if (k == 'mean' or k == 'std') and not isinstance(v, np.ndarray): + new_img_norm_cfg[k] = np.array(v, dtype=img.dtype) + img = mmcv.imdenormalize(img, **img_norm_cfg) + img = mmcv.imnormalize(img, **new_img_norm_cfg) + return img diff --git a/mmdetection/mmdet/models/utils/make_divisible.py b/mmdetection/mmdet/models/utils/make_divisible.py new file mode 100644 index 00000000..ed42c2ee --- /dev/null +++ b/mmdetection/mmdet/models/utils/make_divisible.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +def make_divisible(value, divisor, min_value=None, min_ratio=0.9): + """Make divisible function. + + This function rounds the channel number to the nearest value that can be + divisible by the divisor. It is taken from the original tf repo. It ensures + that all layers have a channel number that is divisible by divisor. It can + be seen here: https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py # noqa + + Args: + value (int): The original channel number. + divisor (int): The divisor to fully divide the channel number. + min_value (int): The minimum value of the output channel. + Default: None, means that the minimum value equal to the divisor. + min_ratio (float): The minimum ratio of the rounded channel number to + the original channel number. Default: 0.9. + + Returns: + int: The modified output channel number. + """ + + if min_value is None: + min_value = divisor + new_value = max(min_value, int(value + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than (1-min_ratio). + if new_value < min_ratio * value: + new_value += divisor + return new_value diff --git a/mmdetection/mmdet/models/utils/misc.py b/mmdetection/mmdet/models/utils/misc.py new file mode 100644 index 00000000..2cf42915 --- /dev/null +++ b/mmdetection/mmdet/models/utils/misc.py @@ -0,0 +1,697 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from functools import partial +from typing import List, Optional, Sequence, Tuple, Union + +import numpy as np +import torch +from mmengine.structures import InstanceData +from mmengine.utils import digit_version +from six.moves import map, zip +from torch import Tensor +from torch.autograd import Function +from torch.nn import functional as F + +from mmdet.structures import SampleList +from mmdet.structures.bbox import BaseBoxes, get_box_type, stack_boxes +from mmdet.structures.mask import BitmapMasks, PolygonMasks +from mmdet.utils import OptInstanceList + + +class SigmoidGeometricMean(Function): + """Forward and backward function of geometric mean of two sigmoid + functions. + + This implementation with analytical gradient function substitutes + the autograd function of (x.sigmoid() * y.sigmoid()).sqrt(). The + original implementation incurs none during gradient backprapagation + if both x and y are very small values. + """ + + @staticmethod + def forward(ctx, x, y): + x_sigmoid = x.sigmoid() + y_sigmoid = y.sigmoid() + z = (x_sigmoid * y_sigmoid).sqrt() + ctx.save_for_backward(x_sigmoid, y_sigmoid, z) + return z + + @staticmethod + def backward(ctx, grad_output): + x_sigmoid, y_sigmoid, z = ctx.saved_tensors + grad_x = grad_output * z * (1 - x_sigmoid) / 2 + grad_y = grad_output * z * (1 - y_sigmoid) / 2 + return grad_x, grad_y + + +sigmoid_geometric_mean = SigmoidGeometricMean.apply + + +def interpolate_as(source, target, mode='bilinear', align_corners=False): + """Interpolate the `source` to the shape of the `target`. + + The `source` must be a Tensor, but the `target` can be a Tensor or a + np.ndarray with the shape (..., target_h, target_w). + + Args: + source (Tensor): A 3D/4D Tensor with the shape (N, H, W) or + (N, C, H, W). + target (Tensor | np.ndarray): The interpolation target with the shape + (..., target_h, target_w). + mode (str): Algorithm used for interpolation. The options are the + same as those in F.interpolate(). Default: ``'bilinear'``. + align_corners (bool): The same as the argument in F.interpolate(). + + Returns: + Tensor: The interpolated source Tensor. + """ + assert len(target.shape) >= 2 + + def _interpolate_as(source, target, mode='bilinear', align_corners=False): + """Interpolate the `source` (4D) to the shape of the `target`.""" + target_h, target_w = target.shape[-2:] + source_h, source_w = source.shape[-2:] + if target_h != source_h or target_w != source_w: + source = F.interpolate( + source, + size=(target_h, target_w), + mode=mode, + align_corners=align_corners) + return source + + if len(source.shape) == 3: + source = source[:, None, :, :] + source = _interpolate_as(source, target, mode, align_corners) + return source[:, 0, :, :] + else: + return _interpolate_as(source, target, mode, align_corners) + + +def unpack_gt_instances(batch_data_samples: SampleList) -> tuple: + """Unpack ``gt_instances``, ``gt_instances_ignore`` and ``img_metas`` based + on ``batch_data_samples`` + + Args: + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + tuple: + + - batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + - batch_gt_instances_ignore (list[:obj:`InstanceData`]): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + - batch_img_metas (list[dict]): Meta information of each image, + e.g., image size, scaling factor, etc. + """ + batch_gt_instances = [] + batch_gt_instances_ignore = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + if 'ignored_instances' in data_sample: + batch_gt_instances_ignore.append(data_sample.ignored_instances) + else: + batch_gt_instances_ignore.append(None) + + return batch_gt_instances, batch_gt_instances_ignore, batch_img_metas + + +def empty_instances(batch_img_metas: List[dict], + device: torch.device, + task_type: str, + instance_results: OptInstanceList = None, + mask_thr_binary: Union[int, float] = 0, + box_type: Union[str, type] = 'hbox', + use_box_type: bool = False, + num_classes: int = 80, + score_per_cls: bool = False) -> List[InstanceData]: + """Handle predicted instances when RoI is empty. + + Note: If ``instance_results`` is not None, it will be modified + in place internally, and then return ``instance_results`` + + Args: + batch_img_metas (list[dict]): List of image information. + device (torch.device): Device of tensor. + task_type (str): Expected returned task type. it currently + supports bbox and mask. + instance_results (list[:obj:`InstanceData`]): List of instance + results. + mask_thr_binary (int, float): mask binarization threshold. + Defaults to 0. + box_type (str or type): The empty box type. Defaults to `hbox`. + use_box_type (bool): Whether to warp boxes with the box type. + Defaults to False. + num_classes (int): num_classes of bbox_head. Defaults to 80. + score_per_cls (bool): Whether to generate classwise score for + the empty instance. ``score_per_cls`` will be True when the model + needs to produce raw results without nms. Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + """ + assert task_type in ('bbox', 'mask'), 'Only support bbox and mask,' \ + f' but got {task_type}' + + if instance_results is not None: + assert len(instance_results) == len(batch_img_metas) + + results_list = [] + for img_id in range(len(batch_img_metas)): + if instance_results is not None: + results = instance_results[img_id] + assert isinstance(results, InstanceData) + else: + results = InstanceData() + + if task_type == 'bbox': + _, box_type = get_box_type(box_type) + bboxes = torch.zeros(0, box_type.box_dim, device=device) + if use_box_type: + bboxes = box_type(bboxes, clone=False) + results.bboxes = bboxes + score_shape = (0, num_classes + 1) if score_per_cls else (0, ) + results.scores = torch.zeros(score_shape, device=device) + results.labels = torch.zeros((0, ), + device=device, + dtype=torch.long) + else: + # TODO: Handle the case where rescale is false + img_h, img_w = batch_img_metas[img_id]['ori_shape'][:2] + # the type of `im_mask` will be torch.bool or torch.uint8, + # where uint8 if for visualization and debugging. + im_mask = torch.zeros( + 0, + img_h, + img_w, + device=device, + dtype=torch.bool if mask_thr_binary >= 0 else torch.uint8) + results.masks = im_mask + results_list.append(results) + return results_list + + +def multi_apply(func, *args, **kwargs): + """Apply function to a list of arguments. + + Note: + This function applies the ``func`` to multiple inputs and + map the multiple outputs of the ``func`` into different + list. Each list contains the same type of outputs corresponding + to different inputs. + + Args: + func (Function): A function that will be applied to a list of + arguments + + Returns: + tuple(list): A tuple containing multiple list, each list contains \ + a kind of returned results by the function + """ + pfunc = partial(func, **kwargs) if kwargs else func + map_results = map(pfunc, *args) + return tuple(map(list, zip(*map_results))) + + +def unmap(data, count, inds, fill=0): + """Unmap a subset of item (data) back to the original set of items (of size + count)""" + if data.dim() == 1: + ret = data.new_full((count, ), fill) + ret[inds.type(torch.bool)] = data + else: + new_size = (count, ) + data.size()[1:] + ret = data.new_full(new_size, fill) + ret[inds.type(torch.bool), :] = data + return ret + + +def mask2ndarray(mask): + """Convert Mask to ndarray.. + + Args: + mask (:obj:`BitmapMasks` or :obj:`PolygonMasks` or + torch.Tensor or np.ndarray): The mask to be converted. + + Returns: + np.ndarray: Ndarray mask of shape (n, h, w) that has been converted + """ + if isinstance(mask, (BitmapMasks, PolygonMasks)): + mask = mask.to_ndarray() + elif isinstance(mask, torch.Tensor): + mask = mask.detach().cpu().numpy() + elif not isinstance(mask, np.ndarray): + raise TypeError(f'Unsupported {type(mask)} data type') + return mask + + +def flip_tensor(src_tensor, flip_direction): + """flip tensor base on flip_direction. + + Args: + src_tensor (Tensor): input feature map, shape (B, C, H, W). + flip_direction (str): The flipping direction. Options are + 'horizontal', 'vertical', 'diagonal'. + + Returns: + out_tensor (Tensor): Flipped tensor. + """ + assert src_tensor.ndim == 4 + valid_directions = ['horizontal', 'vertical', 'diagonal'] + assert flip_direction in valid_directions + if flip_direction == 'horizontal': + out_tensor = torch.flip(src_tensor, [3]) + elif flip_direction == 'vertical': + out_tensor = torch.flip(src_tensor, [2]) + else: + out_tensor = torch.flip(src_tensor, [2, 3]) + return out_tensor + + +def select_single_mlvl(mlvl_tensors, batch_id, detach=True): + """Extract a multi-scale single image tensor from a multi-scale batch + tensor based on batch index. + + Note: The default value of detach is True, because the proposal gradient + needs to be detached during the training of the two-stage model. E.g + Cascade Mask R-CNN. + + Args: + mlvl_tensors (list[Tensor]): Batch tensor for all scale levels, + each is a 4D-tensor. + batch_id (int): Batch index. + detach (bool): Whether detach gradient. Default True. + + Returns: + list[Tensor]: Multi-scale single image tensor. + """ + assert isinstance(mlvl_tensors, (list, tuple)) + num_levels = len(mlvl_tensors) + + if detach: + mlvl_tensor_list = [ + mlvl_tensors[i][batch_id].detach() for i in range(num_levels) + ] + else: + mlvl_tensor_list = [ + mlvl_tensors[i][batch_id] for i in range(num_levels) + ] + return mlvl_tensor_list + + +def filter_scores_and_topk(scores, score_thr, topk, results=None): + """Filter results using score threshold and topk candidates. + + Args: + scores (Tensor): The scores, shape (num_bboxes, K). + score_thr (float): The score filter threshold. + topk (int): The number of topk candidates. + results (dict or list or Tensor, Optional): The results to + which the filtering rule is to be applied. The shape + of each item is (num_bboxes, N). + + Returns: + tuple: Filtered results + + - scores (Tensor): The scores after being filtered, \ + shape (num_bboxes_filtered, ). + - labels (Tensor): The class labels, shape \ + (num_bboxes_filtered, ). + - anchor_idxs (Tensor): The anchor indexes, shape \ + (num_bboxes_filtered, ). + - filtered_results (dict or list or Tensor, Optional): \ + The filtered results. The shape of each item is \ + (num_bboxes_filtered, N). + """ + valid_mask = scores > score_thr + scores = scores[valid_mask] + valid_idxs = torch.nonzero(valid_mask) + + num_topk = min(topk, valid_idxs.size(0)) + # torch.sort is actually faster than .topk (at least on GPUs) + scores, idxs = scores.sort(descending=True) + scores = scores[:num_topk] + topk_idxs = valid_idxs[idxs[:num_topk]] + keep_idxs, labels = topk_idxs.unbind(dim=1) + + filtered_results = None + if results is not None: + if isinstance(results, dict): + filtered_results = {k: v[keep_idxs] for k, v in results.items()} + elif isinstance(results, list): + filtered_results = [result[keep_idxs] for result in results] + elif isinstance(results, torch.Tensor): + filtered_results = results[keep_idxs] + else: + raise NotImplementedError(f'Only supports dict or list or Tensor, ' + f'but get {type(results)}.') + return scores, labels, keep_idxs, filtered_results + + +def center_of_mass(mask, esp=1e-6): + """Calculate the centroid coordinates of the mask. + + Args: + mask (Tensor): The mask to be calculated, shape (h, w). + esp (float): Avoid dividing by zero. Default: 1e-6. + + Returns: + tuple[Tensor]: the coordinates of the center point of the mask. + + - center_h (Tensor): the center point of the height. + - center_w (Tensor): the center point of the width. + """ + h, w = mask.shape + grid_h = torch.arange(h, device=mask.device)[:, None] + grid_w = torch.arange(w, device=mask.device) + normalizer = mask.sum().float().clamp(min=esp) + center_h = (mask * grid_h).sum() / normalizer + center_w = (mask * grid_w).sum() / normalizer + return center_h, center_w + + +def generate_coordinate(featmap_sizes, device='cuda'): + """Generate the coordinate. + + Args: + featmap_sizes (tuple): The feature to be calculated, + of shape (N, C, W, H). + device (str): The device where the feature will be put on. + Returns: + coord_feat (Tensor): The coordinate feature, of shape (N, 2, W, H). + """ + + x_range = torch.linspace(-1, 1, featmap_sizes[-1], device=device) + y_range = torch.linspace(-1, 1, featmap_sizes[-2], device=device) + y, x = torch.meshgrid(y_range, x_range) + y = y.expand([featmap_sizes[0], 1, -1, -1]) + x = x.expand([featmap_sizes[0], 1, -1, -1]) + coord_feat = torch.cat([x, y], 1) + + return coord_feat + + +def levels_to_images(mlvl_tensor: List[torch.Tensor]) -> List[torch.Tensor]: + """Concat multi-level feature maps by image. + + [feature_level0, feature_level1...] -> [feature_image0, feature_image1...] + Convert the shape of each element in mlvl_tensor from (N, C, H, W) to + (N, H*W , C), then split the element to N elements with shape (H*W, C), and + concat elements in same image of all level along first dimension. + + Args: + mlvl_tensor (list[Tensor]): list of Tensor which collect from + corresponding level. Each element is of shape (N, C, H, W) + + Returns: + list[Tensor]: A list that contains N tensors and each tensor is + of shape (num_elements, C) + """ + batch_size = mlvl_tensor[0].size(0) + batch_list = [[] for _ in range(batch_size)] + channels = mlvl_tensor[0].size(1) + for t in mlvl_tensor: + t = t.permute(0, 2, 3, 1) + t = t.view(batch_size, -1, channels).contiguous() + for img in range(batch_size): + batch_list[img].append(t[img]) + return [torch.cat(item, 0) for item in batch_list] + + +def images_to_levels(target, num_levels): + """Convert targets by image to targets by feature level. + + [target_img0, target_img1] -> [target_level0, target_level1, ...] + """ + target = stack_boxes(target, 0) + level_targets = [] + start = 0 + for n in num_levels: + end = start + n + # level_targets.append(target[:, start:end].squeeze(0)) + level_targets.append(target[:, start:end]) + start = end + return level_targets + + +def samplelist_boxtype2tensor(batch_data_samples: SampleList) -> SampleList: + for data_samples in batch_data_samples: + if 'gt_instances' in data_samples: + bboxes = data_samples.gt_instances.get('bboxes', None) + if isinstance(bboxes, BaseBoxes): + data_samples.gt_instances.bboxes = bboxes.tensor + if 'pred_instances' in data_samples: + bboxes = data_samples.pred_instances.get('bboxes', None) + if isinstance(bboxes, BaseBoxes): + data_samples.pred_instances.bboxes = bboxes.tensor + if 'ignored_instances' in data_samples: + bboxes = data_samples.ignored_instances.get('bboxes', None) + if isinstance(bboxes, BaseBoxes): + data_samples.ignored_instances.bboxes = bboxes.tensor + + +_torch_version_div_indexing = ( + 'parrots' not in torch.__version__ + and digit_version(torch.__version__) >= digit_version('1.8')) + + +def floordiv(dividend, divisor, rounding_mode='trunc'): + if _torch_version_div_indexing: + return torch.div(dividend, divisor, rounding_mode=rounding_mode) + else: + return dividend // divisor + + +def _filter_gt_instances_by_score(batch_data_samples: SampleList, + score_thr: float) -> SampleList: + """Filter ground truth (GT) instances by score. + + Args: + batch_data_samples (SampleList): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + score_thr (float): The score filter threshold. + + Returns: + SampleList: The Data Samples filtered by score. + """ + for data_samples in batch_data_samples: + assert 'scores' in data_samples.gt_instances, \ + 'there does not exit scores in instances' + if data_samples.gt_instances.bboxes.shape[0] > 0: + data_samples.gt_instances = data_samples.gt_instances[ + data_samples.gt_instances.scores > score_thr] + return batch_data_samples + + +def _filter_gt_instances_by_size(batch_data_samples: SampleList, + wh_thr: tuple) -> SampleList: + """Filter ground truth (GT) instances by size. + + Args: + batch_data_samples (SampleList): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + wh_thr (tuple): Minimum width and height of bbox. + + Returns: + SampleList: The Data Samples filtered by score. + """ + for data_samples in batch_data_samples: + bboxes = data_samples.gt_instances.bboxes + if bboxes.shape[0] > 0: + w = bboxes[:, 2] - bboxes[:, 0] + h = bboxes[:, 3] - bboxes[:, 1] + data_samples.gt_instances = data_samples.gt_instances[ + (w > wh_thr[0]) & (h > wh_thr[1])] + return batch_data_samples + + +def filter_gt_instances(batch_data_samples: SampleList, + score_thr: float = None, + wh_thr: tuple = None): + """Filter ground truth (GT) instances by score and/or size. + + Args: + batch_data_samples (SampleList): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + score_thr (float): The score filter threshold. + wh_thr (tuple): Minimum width and height of bbox. + + Returns: + SampleList: The Data Samples filtered by score and/or size. + """ + + if score_thr is not None: + batch_data_samples = _filter_gt_instances_by_score( + batch_data_samples, score_thr) + if wh_thr is not None: + batch_data_samples = _filter_gt_instances_by_size( + batch_data_samples, wh_thr) + return batch_data_samples + + +def rename_loss_dict(prefix: str, losses: dict) -> dict: + """Rename the key names in loss dict by adding a prefix. + + Args: + prefix (str): The prefix for loss components. + losses (dict): A dictionary of loss components. + + Returns: + dict: A dictionary of loss components with prefix. + """ + return {prefix + k: v for k, v in losses.items()} + + +def reweight_loss_dict(losses: dict, weight: float) -> dict: + """Reweight losses in the dict by weight. + + Args: + losses (dict): A dictionary of loss components. + weight (float): Weight for loss components. + + Returns: + dict: A dictionary of weighted loss components. + """ + for name, loss in losses.items(): + if 'loss' in name: + if isinstance(loss, Sequence): + losses[name] = [item * weight for item in loss] + else: + losses[name] = loss * weight + return losses + + +def relative_coordinate_maps( + locations: Tensor, + centers: Tensor, + strides: Tensor, + size_of_interest: int, + feat_sizes: Tuple[int], +) -> Tensor: + """Generate the relative coordinate maps with feat_stride. + + Args: + locations (Tensor): The prior location of mask feature map. + It has shape (num_priors, 2). + centers (Tensor): The prior points of a object in + all feature pyramid. It has shape (num_pos, 2) + strides (Tensor): The prior strides of a object in + all feature pyramid. It has shape (num_pos, 1) + size_of_interest (int): The size of the region used in rel coord. + feat_sizes (Tuple[int]): The feature size H and W, which has 2 dims. + Returns: + rel_coord_feat (Tensor): The coordinate feature + of shape (num_pos, 2, H, W). + """ + + H, W = feat_sizes + rel_coordinates = centers.reshape(-1, 1, 2) - locations.reshape(1, -1, 2) + rel_coordinates = rel_coordinates.permute(0, 2, 1).float() + rel_coordinates = rel_coordinates / ( + strides[:, None, None] * size_of_interest) + return rel_coordinates.reshape(-1, 2, H, W) + + +def aligned_bilinear(tensor: Tensor, factor: int) -> Tensor: + """aligned bilinear, used in original implement in CondInst: + + https://github.com/aim-uofa/AdelaiDet/blob/\ + c0b2092ce72442b0f40972f7c6dda8bb52c46d16/adet/utils/comm.py#L23 + """ + + assert tensor.dim() == 4 + assert factor >= 1 + assert int(factor) == factor + + if factor == 1: + return tensor + + h, w = tensor.size()[2:] + tensor = F.pad(tensor, pad=(0, 1, 0, 1), mode='replicate') + oh = factor * h + 1 + ow = factor * w + 1 + tensor = F.interpolate( + tensor, size=(oh, ow), mode='bilinear', align_corners=True) + tensor = F.pad( + tensor, pad=(factor // 2, 0, factor // 2, 0), mode='replicate') + + return tensor[:, :, :oh - 1, :ow - 1] + + +def unfold_wo_center(x, kernel_size: int, dilation: int) -> Tensor: + """unfold_wo_center, used in original implement in BoxInst: + + https://github.com/aim-uofa/AdelaiDet/blob/\ + 4a3a1f7372c35b48ebf5f6adc59f135a0fa28d60/\ + adet/modeling/condinst/condinst.py#L53 + """ + assert x.dim() == 4 + assert kernel_size % 2 == 1 + + # using SAME padding + padding = (kernel_size + (dilation - 1) * (kernel_size - 1)) // 2 + unfolded_x = F.unfold( + x, kernel_size=kernel_size, padding=padding, dilation=dilation) + unfolded_x = unfolded_x.reshape( + x.size(0), x.size(1), -1, x.size(2), x.size(3)) + # remove the center pixels + size = kernel_size**2 + unfolded_x = torch.cat( + (unfolded_x[:, :, :size // 2], unfolded_x[:, :, size // 2 + 1:]), + dim=2) + + return unfolded_x + + +def padding_to(input_tensor: Tensor, max_len: int = 300) -> Tensor: + """Pad the first dimension of `input_tensor` to `max_len`. + + Args: + input_tensor (Tensor): The tensor to be padded, + max_len (int): Padding target size in the first dimension. + Default: 300 + https://github.com/jshilong/DDQ/blob/ddq_detr/projects/models/utils.py#L19 + Returns: + Tensor: The tensor padded with the first dimension size `max_len`. + """ + if max_len is None: + return input_tensor + num_padding = max_len - len(input_tensor) + if input_tensor.dim() > 1: + padding = input_tensor.new_zeros( + num_padding, *input_tensor.size()[1:], dtype=input_tensor.dtype) + else: + padding = input_tensor.new_zeros(num_padding, dtype=input_tensor.dtype) + output_tensor = torch.cat([input_tensor, padding], dim=0) + return output_tensor + + +def align_tensor(inputs: List[Tensor], + max_len: Optional[int] = None) -> Tensor: + """Pad each input to `max_len`, then stack them. If `max_len` is None, then + it is the max size of the first dimension of each input. + + https://github.com/jshilong/DDQ/blob/ddq_detr/projects/models/\ + utils.py#L12 + + Args: + inputs (list[Tensor]): The tensors to be padded, + Each input should have the same shape except the first dimension. + max_len (int): Padding target size in the first dimension. + Default: None + Returns: + Tensor: Stacked inputs after padding in the first dimension. + """ + if max_len is None: + max_len = max([len(item) for item in inputs]) + + return torch.stack([padding_to(item, max_len) for item in inputs]) diff --git a/mmdetection/mmdet/models/utils/panoptic_gt_processing.py b/mmdetection/mmdet/models/utils/panoptic_gt_processing.py new file mode 100644 index 00000000..7a3bc95f --- /dev/null +++ b/mmdetection/mmdet/models/utils/panoptic_gt_processing.py @@ -0,0 +1,70 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch +from torch import Tensor + + +def preprocess_panoptic_gt(gt_labels: Tensor, gt_masks: Tensor, + gt_semantic_seg: Tensor, num_things: int, + num_stuff: int) -> Tuple[Tensor, Tensor]: + """Preprocess the ground truth for a image. + + Args: + gt_labels (Tensor): Ground truth labels of each bbox, + with shape (num_gts, ). + gt_masks (BitmapMasks): Ground truth masks of each instances + of a image, shape (num_gts, h, w). + gt_semantic_seg (Tensor | None): Ground truth of semantic + segmentation with the shape (1, h, w). + [0, num_thing_class - 1] means things, + [num_thing_class, num_class-1] means stuff, + 255 means VOID. It's None when training instance segmentation. + + Returns: + tuple[Tensor, Tensor]: a tuple containing the following targets. + + - labels (Tensor): Ground truth class indices for a + image, with shape (n, ), n is the sum of number + of stuff type and number of instance in a image. + - masks (Tensor): Ground truth mask for a image, with + shape (n, h, w). Contains stuff and things when training + panoptic segmentation, and things only when training + instance segmentation. + """ + num_classes = num_things + num_stuff + things_masks = gt_masks.to_tensor( + dtype=torch.bool, device=gt_labels.device) + + if gt_semantic_seg is None: + masks = things_masks.long() + return gt_labels, masks + + things_labels = gt_labels + gt_semantic_seg = gt_semantic_seg.squeeze(0) + + semantic_labels = torch.unique( + gt_semantic_seg, + sorted=False, + return_inverse=False, + return_counts=False) + stuff_masks_list = [] + stuff_labels_list = [] + for label in semantic_labels: + if label < num_things or label >= num_classes: + continue + stuff_mask = gt_semantic_seg == label + stuff_masks_list.append(stuff_mask) + stuff_labels_list.append(label) + + if len(stuff_masks_list) > 0: + stuff_masks = torch.stack(stuff_masks_list, dim=0) + stuff_labels = torch.stack(stuff_labels_list, dim=0) + labels = torch.cat([things_labels, stuff_labels], dim=0) + masks = torch.cat([things_masks, stuff_masks], dim=0) + else: + labels = things_labels + masks = things_masks + + masks = masks.long() + return labels, masks diff --git a/mmdetection/mmdet/models/utils/point_sample.py b/mmdetection/mmdet/models/utils/point_sample.py new file mode 100644 index 00000000..1afc957f --- /dev/null +++ b/mmdetection/mmdet/models/utils/point_sample.py @@ -0,0 +1,88 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmcv.ops import point_sample +from torch import Tensor + + +def get_uncertainty(mask_preds: Tensor, labels: Tensor) -> Tensor: + """Estimate uncertainty based on pred logits. + + We estimate uncertainty as L1 distance between 0.0 and the logits + prediction in 'mask_preds' for the foreground class in `classes`. + + Args: + mask_preds (Tensor): mask predication logits, shape (num_rois, + num_classes, mask_height, mask_width). + + labels (Tensor): Either predicted or ground truth label for + each predicted mask, of length num_rois. + + Returns: + scores (Tensor): Uncertainty scores with the most uncertain + locations having the highest uncertainty score, + shape (num_rois, 1, mask_height, mask_width) + """ + if mask_preds.shape[1] == 1: + gt_class_logits = mask_preds.clone() + else: + inds = torch.arange(mask_preds.shape[0], device=mask_preds.device) + gt_class_logits = mask_preds[inds, labels].unsqueeze(1) + return -torch.abs(gt_class_logits) + + +def get_uncertain_point_coords_with_randomness( + mask_preds: Tensor, labels: Tensor, num_points: int, + oversample_ratio: float, importance_sample_ratio: float) -> Tensor: + """Get ``num_points`` most uncertain points with random points during + train. + + Sample points in [0, 1] x [0, 1] coordinate space based on their + uncertainty. The uncertainties are calculated for each point using + 'get_uncertainty()' function that takes point's logit prediction as + input. + + Args: + mask_preds (Tensor): A tensor of shape (num_rois, num_classes, + mask_height, mask_width) for class-specific or class-agnostic + prediction. + labels (Tensor): The ground truth class for each instance. + num_points (int): The number of points to sample. + oversample_ratio (float): Oversampling parameter. + importance_sample_ratio (float): Ratio of points that are sampled + via importnace sampling. + + Returns: + point_coords (Tensor): A tensor of shape (num_rois, num_points, 2) + that contains the coordinates sampled points. + """ + assert oversample_ratio >= 1 + assert 0 <= importance_sample_ratio <= 1 + batch_size = mask_preds.shape[0] + num_sampled = int(num_points * oversample_ratio) + point_coords = torch.rand( + batch_size, num_sampled, 2, device=mask_preds.device) + point_logits = point_sample(mask_preds, point_coords) + # It is crucial to calculate uncertainty based on the sampled + # prediction value for the points. Calculating uncertainties of the + # coarse predictions first and sampling them for points leads to + # incorrect results. To illustrate this: assume uncertainty func( + # logits)=-abs(logits), a sampled point between two coarse + # predictions with -1 and 1 logits has 0 logits, and therefore 0 + # uncertainty value. However, if we calculate uncertainties for the + # coarse predictions first, both will have -1 uncertainty, + # and sampled point will get -1 uncertainty. + point_uncertainties = get_uncertainty(point_logits, labels) + num_uncertain_points = int(importance_sample_ratio * num_points) + num_random_points = num_points - num_uncertain_points + idx = torch.topk( + point_uncertainties[:, 0, :], k=num_uncertain_points, dim=1)[1] + shift = num_sampled * torch.arange( + batch_size, dtype=torch.long, device=mask_preds.device) + idx += shift[:, None] + point_coords = point_coords.view(-1, 2)[idx.view(-1), :].view( + batch_size, num_uncertain_points, 2) + if num_random_points > 0: + rand_roi_coords = torch.rand( + batch_size, num_random_points, 2, device=mask_preds.device) + point_coords = torch.cat((point_coords, rand_roi_coords), dim=1) + return point_coords diff --git a/mmdetection/mmdet/models/utils/vlfuse_helper.py b/mmdetection/mmdet/models/utils/vlfuse_helper.py new file mode 100644 index 00000000..76b54de3 --- /dev/null +++ b/mmdetection/mmdet/models/utils/vlfuse_helper.py @@ -0,0 +1,773 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Modified from https://github.com/microsoft/GLIP/blob/main/maskrcnn_benchmark/utils/fuse_helper.py # noqa +# and https://github.com/microsoft/GLIP/blob/main/maskrcnn_benchmark/modeling/rpn/modeling_bert.py # noqa +import math +from typing import Dict, Optional, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as checkpoint +from mmcv.cnn.bricks import DropPath +from torch import Tensor + +try: + from transformers import BertConfig, BertPreTrainedModel + from transformers.modeling_utils import apply_chunking_to_forward + from transformers.models.bert.modeling_bert import \ + BertAttention as HFBertAttention + from transformers.models.bert.modeling_bert import \ + BertIntermediate as HFBertIntermediate + from transformers.models.bert.modeling_bert import \ + BertOutput as HFBertOutput +except ImportError: + BertConfig = None + BertPreTrainedModel = object + apply_chunking_to_forward = None + HFBertAttention = object + HFBertIntermediate = object + HFBertOutput = object + +MAX_CLAMP_VALUE = 50000 + + +def permute_and_flatten(layer: Tensor, N: int, A: int, C: int, H: int, + W: int) -> Tensor: + """Permute and then flatten a tensor, + + from size (N, A, C, H, W) to (N, H * W * A, C). + + Args: + layer (Tensor): Tensor of shape (N, C, H, W). + N (int): Batch size. + A (int): Number of attention heads. + C (int): Number of channels. + H (int): Height of feature map. + W (int): Width of feature map. + + Returns: + Tensor: A Tensor of shape (N, H * W * A, C). + """ + layer = layer.view(N, A, C, H, W) + layer = layer.permute(0, 3, 4, 1, 2) + layer = layer.reshape(N, -1, C) + return layer + + +def clamp_values(vector: Tensor) -> Tensor: + """Clamp the values of a vector to the range [-MAX_CLAMP_VALUE, + MAX_CLAMP_VALUE]. + + Args: + vector (Tensor): Tensor of shape (N, C, H, W). + + Returns: + Tensor: A Tensor of shape (N, C, H, W) with clamped values. + """ + vector = torch.clamp(vector, min=-MAX_CLAMP_VALUE, max=MAX_CLAMP_VALUE) + return vector + + +class BiMultiHeadAttention(nn.Module): + """Bidirectional fusion Multi-Head Attention layer. + + Args: + v_dim (int): The dimension of the vision input. + l_dim (int): The dimension of the language input. + embed_dim (int): The embedding dimension for the attention operation. + num_heads (int): The number of attention heads. + dropout (float, optional): The dropout probability. Defaults to 0.1. + """ + + def __init__(self, + v_dim: int, + l_dim: int, + embed_dim: int, + num_heads: int, + dropout: float = 0.1): + super(BiMultiHeadAttention, self).__init__() + + self.embed_dim = embed_dim + self.num_heads = num_heads + self.head_dim = embed_dim // num_heads + self.v_dim = v_dim + self.l_dim = l_dim + + assert ( + self.head_dim * self.num_heads == self.embed_dim + ), 'embed_dim must be divisible by num_heads ' \ + f'(got `embed_dim`: {self.embed_dim} ' \ + f'and `num_heads`: {self.num_heads}).' + self.scale = self.head_dim**(-0.5) + self.dropout = dropout + + self.v_proj = nn.Linear(self.v_dim, self.embed_dim) + self.l_proj = nn.Linear(self.l_dim, self.embed_dim) + self.values_v_proj = nn.Linear(self.v_dim, self.embed_dim) + self.values_l_proj = nn.Linear(self.l_dim, self.embed_dim) + + self.out_v_proj = nn.Linear(self.embed_dim, self.v_dim) + self.out_l_proj = nn.Linear(self.embed_dim, self.l_dim) + + self.stable_softmax_2d = False + self.clamp_min_for_underflow = True + self.clamp_max_for_overflow = True + + self._reset_parameters() + + def _shape(self, tensor: Tensor, seq_len: int, bsz: int): + return tensor.view(bsz, seq_len, self.num_heads, + self.head_dim).transpose(1, 2).contiguous() + + def _reset_parameters(self): + nn.init.xavier_uniform_(self.v_proj.weight) + self.v_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.l_proj.weight) + self.l_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.values_v_proj.weight) + self.values_v_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.values_l_proj.weight) + self.values_l_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.out_v_proj.weight) + self.out_v_proj.bias.data.fill_(0) + nn.init.xavier_uniform_(self.out_l_proj.weight) + self.out_l_proj.bias.data.fill_(0) + + def forward( + self, + vision: Tensor, + lang: Tensor, + attention_mask_v: Optional[Tensor] = None, + attention_mask_l: Optional[Tensor] = None, + ) -> Tuple[Tensor, Tensor]: + bsz, tgt_len, _ = vision.size() + + query_states = self.v_proj(vision) * self.scale + key_states = self._shape(self.l_proj(lang), -1, bsz) + value_v_states = self._shape(self.values_v_proj(vision), -1, bsz) + value_l_states = self._shape(self.values_l_proj(lang), -1, bsz) + + proj_shape = (bsz * self.num_heads, -1, self.head_dim) + query_states = self._shape(query_states, tgt_len, + bsz).view(*proj_shape) + key_states = key_states.view(*proj_shape) + value_v_states = value_v_states.view(*proj_shape) + value_l_states = value_l_states.view(*proj_shape) + + src_len = key_states.size(1) + attn_weights = torch.bmm(query_states, key_states.transpose(1, 2)) + + if attn_weights.size() != (bsz * self.num_heads, tgt_len, src_len): + raise ValueError( + f'Attention weights should be of ' + f'size {(bsz * self.num_heads, tgt_len, src_len)}, ' + f'but is {attn_weights.size()}') + + if self.stable_softmax_2d: + attn_weights = attn_weights - attn_weights.max() + + if self.clamp_min_for_underflow: + # Do not increase -50000, data type half has quite limited range + attn_weights = torch.clamp(attn_weights, min=-MAX_CLAMP_VALUE) + if self.clamp_max_for_overflow: + # Do not increase 50000, data type half has quite limited range + attn_weights = torch.clamp(attn_weights, max=MAX_CLAMP_VALUE) + + attn_weights_T = attn_weights.transpose(1, 2) + attn_weights_l = ( + attn_weights_T - + torch.max(attn_weights_T, dim=-1, keepdim=True)[0]) + if self.clamp_min_for_underflow: + # Do not increase -50000, data type half has quite limited range + attn_weights_l = torch.clamp(attn_weights_l, min=-MAX_CLAMP_VALUE) + if self.clamp_max_for_overflow: + # Do not increase 50000, data type half has quite limited range + attn_weights_l = torch.clamp(attn_weights_l, max=MAX_CLAMP_VALUE) + + if attention_mask_v is not None: + attention_mask_v = ( + attention_mask_v[:, None, + None, :].repeat(1, self.num_heads, 1, + 1).flatten(0, 1)) + attn_weights_l.masked_fill_(attention_mask_v, float('-inf')) + + attn_weights_l = attn_weights_l.softmax(dim=-1) + + if attention_mask_l is not None: + assert (attention_mask_l.dim() == 2) + attention_mask = attention_mask_l.unsqueeze(1).unsqueeze(1) + attention_mask = attention_mask.expand(bsz, 1, tgt_len, src_len) + attention_mask = attention_mask.masked_fill( + attention_mask == 0, -9e15) + + if attention_mask.size() != (bsz, 1, tgt_len, src_len): + raise ValueError('Attention mask should be of ' + f'size {(bsz, 1, tgt_len, src_len)}') + attn_weights = attn_weights.view(bsz, self.num_heads, tgt_len, + src_len) + attention_mask + attn_weights = attn_weights.view(bsz * self.num_heads, tgt_len, + src_len) + + attn_weights_v = nn.functional.softmax(attn_weights, dim=-1) + + attn_probs_v = F.dropout( + attn_weights_v, p=self.dropout, training=self.training) + attn_probs_l = F.dropout( + attn_weights_l, p=self.dropout, training=self.training) + + attn_output_v = torch.bmm(attn_probs_v, value_l_states) + attn_output_l = torch.bmm(attn_probs_l, value_v_states) + + if attn_output_v.size() != (bsz * self.num_heads, tgt_len, + self.head_dim): + raise ValueError( + '`attn_output_v` should be of ' + f'size {(bsz, self.num_heads, tgt_len, self.head_dim)}, ' + f'but is {attn_output_v.size()}') + + if attn_output_l.size() != (bsz * self.num_heads, src_len, + self.head_dim): + raise ValueError( + '`attn_output_l` should be of size ' + f'{(bsz, self.num_heads, src_len, self.head_dim)}, ' + f'but is {attn_output_l.size()}') + + attn_output_v = attn_output_v.view(bsz, self.num_heads, tgt_len, + self.head_dim) + attn_output_v = attn_output_v.transpose(1, 2) + attn_output_v = attn_output_v.reshape(bsz, tgt_len, self.embed_dim) + + attn_output_l = attn_output_l.view(bsz, self.num_heads, src_len, + self.head_dim) + attn_output_l = attn_output_l.transpose(1, 2) + attn_output_l = attn_output_l.reshape(bsz, src_len, self.embed_dim) + + attn_output_v = self.out_v_proj(attn_output_v) + attn_output_l = self.out_l_proj(attn_output_l) + + return attn_output_v, attn_output_l + + +class BiAttentionBlock(nn.Module): + """BiAttentionBlock Module: + + First, multi-level visual features are concat; Then the concat visual + feature and lang feature are fused by attention; Finally the newly visual + feature are split into multi levels. + + Args: + v_dim (int): The dimension of the visual features. + l_dim (int): The dimension of the language feature. + embed_dim (int): The embedding dimension for the attention operation. + num_heads (int): The number of attention heads. + dropout (float, optional): The dropout probability. Defaults to 0.1. + drop_path (float, optional): The drop path probability. + Defaults to 0.0. + init_values (float, optional): + The initial value for the scaling parameter. + Defaults to 1e-4. + """ + + def __init__(self, + v_dim: int, + l_dim: int, + embed_dim: int, + num_heads: int, + dropout: float = 0.1, + drop_path: float = .0, + init_values: float = 1e-4): + super().__init__() + + # pre layer norm + self.layer_norm_v = nn.LayerNorm(v_dim) + self.layer_norm_l = nn.LayerNorm(l_dim) + self.attn = BiMultiHeadAttention( + v_dim=v_dim, + l_dim=l_dim, + embed_dim=embed_dim, + num_heads=num_heads, + dropout=dropout) + + # add layer scale for training stability + self.drop_path = DropPath( + drop_path) if drop_path > 0. else nn.Identity() + self.gamma_v = nn.Parameter( + init_values * torch.ones(v_dim), requires_grad=True) + self.gamma_l = nn.Parameter( + init_values * torch.ones(l_dim), requires_grad=True) + + def forward(self, + vf0: Tensor, + vf1: Tensor, + vf2: Tensor, + vf3: Tensor, + vf4: Tensor, + lang_feature: Tensor, + attention_mask_l=None): + visual_features = [vf0, vf1, vf2, vf3, vf4] + size_per_level, visual_features_flatten = [], [] + for i, feat_per_level in enumerate(visual_features): + bs, c, h, w = feat_per_level.shape + size_per_level.append([h, w]) + feat = permute_and_flatten(feat_per_level, bs, -1, c, h, w) + visual_features_flatten.append(feat) + visual_features_flatten = torch.cat(visual_features_flatten, dim=1) + new_v, new_lang_feature = self.single_attention_call( + visual_features_flatten, + lang_feature, + attention_mask_l=attention_mask_l) + # [bs, N, C] -> [bs, C, N] + new_v = new_v.transpose(1, 2).contiguous() + + start = 0 + # fvfs is mean fusion_visual_features + fvfs = [] + for (h, w) in size_per_level: + new_v_per_level = new_v[:, :, + start:start + h * w].view(bs, -1, h, + w).contiguous() + fvfs.append(new_v_per_level) + start += h * w + + return fvfs[0], fvfs[1], fvfs[2], fvfs[3], fvfs[4], new_lang_feature + + def single_attention_call( + self, + visual: Tensor, + lang: Tensor, + attention_mask_v: Optional[Tensor] = None, + attention_mask_l: Optional[Tensor] = None, + ) -> Tuple[Tensor, Tensor]: + """Perform a single attention call between the visual and language + inputs. + + Args: + visual (Tensor): The visual input tensor. + lang (Tensor): The language input tensor. + attention_mask_v (Optional[Tensor]): + An optional attention mask tensor for the visual input. + attention_mask_l (Optional[Tensor]): + An optional attention mask tensor for the language input. + + Returns: + Tuple[Tensor, Tensor]: A tuple containing the updated + visual and language tensors after the attention call. + """ + visual = self.layer_norm_v(visual) + lang = self.layer_norm_l(lang) + delta_v, delta_l = self.attn( + visual, + lang, + attention_mask_v=attention_mask_v, + attention_mask_l=attention_mask_l) + # visual, lang = visual + delta_v, l + delta_l + visual = visual + self.drop_path(self.gamma_v * delta_v) + lang = lang + self.drop_path(self.gamma_l * delta_l) + return visual, lang + + +class SingleScaleBiAttentionBlock(BiAttentionBlock): + """This is a single-scale implementation of `BiAttentionBlock`. + + The only differenece between it and `BiAttentionBlock` is that the + `forward` function of `SingleScaleBiAttentionBlock` only accepts a single + flatten visual feature map, while the `forward` function in + `BiAttentionBlock` accepts multiple visual feature maps. + """ + + def forward(self, + visual_feature: Tensor, + lang_feature: Tensor, + attention_mask_v=None, + attention_mask_l=None): + """Single-scale forward pass. + + Args: + visual_feature (Tensor): The visual input tensor. Tensor of + shape (bs, patch_len, ch). + lang_feature (Tensor): The language input tensor. Tensor of + shape (bs, text_len, ch). + attention_mask_v (_type_, optional): Visual feature attention + mask. Defaults to None. + attention_mask_l (_type_, optional): Language feature attention + mask.Defaults to None. + """ + new_v, new_lang_feature = self.single_attention_call( + visual_feature, + lang_feature, + attention_mask_v=attention_mask_v, + attention_mask_l=attention_mask_l) + return new_v, new_lang_feature + + +class VLFuse(nn.Module): + """Early Fusion Module. + + Args: + v_dim (int): Dimension of visual features. + l_dim (int): Dimension of language features. + embed_dim (int): The embedding dimension for the attention operation. + num_heads (int): Number of attention heads. + dropout (float): Dropout probability. + drop_path (float): Drop path probability. + use_checkpoint (bool): Whether to use PyTorch's checkpoint function. + """ + + def __init__(self, + v_dim: int = 256, + l_dim: int = 768, + embed_dim: int = 2048, + num_heads: int = 8, + dropout: float = 0.1, + drop_path: float = 0.0, + use_checkpoint: bool = False): + super().__init__() + self.use_checkpoint = use_checkpoint + self.b_attn = BiAttentionBlock( + v_dim=v_dim, + l_dim=l_dim, + embed_dim=embed_dim, + num_heads=num_heads, + dropout=dropout, + drop_path=drop_path, + init_values=1.0 / 6.0) + + def forward(self, x: dict) -> dict: + """Forward pass of the VLFuse module.""" + visual_features = x['visual'] + language_dict_features = x['lang'] + + if self.use_checkpoint: + # vf is mean visual_features + # checkpoint does not allow complex data structures as input, + # such as list, so we must split them. + vf0, vf1, vf2, vf3, vf4, language_features = checkpoint.checkpoint( + self.b_attn, *visual_features, + language_dict_features['hidden'], + language_dict_features['masks']) + else: + vf0, vf1, vf2, vf3, vf4, language_features = self.b_attn( + *visual_features, language_dict_features['hidden'], + language_dict_features['masks']) + + language_dict_features['hidden'] = language_features + fused_language_dict_features = language_dict_features + + features_dict = { + 'visual': [vf0, vf1, vf2, vf3, vf4], + 'lang': fused_language_dict_features + } + + return features_dict + + +class BertEncoderLayer(BertPreTrainedModel): + """A modified version of the `BertLayer` class from the + `transformers.models.bert.modeling_bert` module. + + Args: + config (:class:`~transformers.BertConfig`): + The configuration object that + contains various parameters for the model. + clamp_min_for_underflow (bool, optional): + Whether to clamp the minimum value of the hidden states + to prevent underflow. Defaults to `False`. + clamp_max_for_overflow (bool, optional): + Whether to clamp the maximum value of the hidden states + to prevent overflow. Defaults to `False`. + """ + + def __init__(self, + config: BertConfig, + clamp_min_for_underflow: bool = False, + clamp_max_for_overflow: bool = False): + super().__init__(config) + self.config = config + self.chunk_size_feed_forward = config.chunk_size_feed_forward + self.seq_len_dim = 1 + + self.attention = BertAttention(config, clamp_min_for_underflow, + clamp_max_for_overflow) + self.intermediate = BertIntermediate(config) + self.output = BertOutput(config) + + def forward( + self, inputs: Dict[str, Dict[str, torch.Tensor]] + ) -> Dict[str, Dict[str, torch.Tensor]]: + """Applies the BertEncoderLayer to the input features.""" + language_dict_features = inputs['lang'] + hidden_states = language_dict_features['hidden'] + attention_mask = language_dict_features['masks'] + + device = hidden_states.device + input_shape = hidden_states.size()[:-1] + extended_attention_mask = self.get_extended_attention_mask( + attention_mask, input_shape, device) + + self_attention_outputs = self.attention( + hidden_states, + extended_attention_mask, + None, + output_attentions=False, + past_key_value=None) + attention_output = self_attention_outputs[0] + outputs = self_attention_outputs[1:] + layer_output = apply_chunking_to_forward(self.feed_forward_chunk, + self.chunk_size_feed_forward, + self.seq_len_dim, + attention_output) + outputs = (layer_output, ) + outputs + hidden_states = outputs[0] + + language_dict_features['hidden'] = hidden_states + + features_dict = { + 'visual': inputs['visual'], + 'lang': language_dict_features + } + + return features_dict + + def feed_forward_chunk(self, attention_output: Tensor) -> Tensor: + """Applies the intermediate and output layers of the BertEncoderLayer + to a chunk of the input sequence.""" + intermediate_output = self.intermediate(attention_output) + layer_output = self.output(intermediate_output, attention_output) + return layer_output + + +# The following code is the same as the Huggingface code, +# with the only difference being the additional clamp operation. +class BertSelfAttention(nn.Module): + """BERT self-attention layer from Huggingface transformers. + + Compared to the BertSelfAttention of Huggingface, only add the clamp. + + Args: + config (:class:`~transformers.BertConfig`): + The configuration object that + contains various parameters for the model. + clamp_min_for_underflow (bool, optional): + Whether to clamp the minimum value of the hidden states + to prevent underflow. Defaults to `False`. + clamp_max_for_overflow (bool, optional): + Whether to clamp the maximum value of the hidden states + to prevent overflow. Defaults to `False`. + """ + + def __init__(self, + config: BertConfig, + clamp_min_for_underflow: bool = False, + clamp_max_for_overflow: bool = False): + super().__init__() + if config.hidden_size % config.num_attention_heads != 0 and \ + not hasattr(config, 'embedding_size'): + raise ValueError(f'The hidden size ({config.hidden_size}) is ' + 'not a multiple of the number of attention ' + f'heads ({config.num_attention_heads})') + + self.num_attention_heads = config.num_attention_heads + self.attention_head_size = int(config.hidden_size / + config.num_attention_heads) + self.all_head_size = self.num_attention_heads * \ + self.attention_head_size + + self.query = nn.Linear(config.hidden_size, self.all_head_size) + self.key = nn.Linear(config.hidden_size, self.all_head_size) + self.value = nn.Linear(config.hidden_size, self.all_head_size) + + self.dropout = nn.Dropout(config.attention_probs_dropout_prob) + self.position_embedding_type = getattr(config, + 'position_embedding_type', + 'absolute') + if self.position_embedding_type == 'relative_key' or \ + self.position_embedding_type == 'relative_key_query': + self.max_position_embeddings = config.max_position_embeddings + self.distance_embedding = nn.Embedding( + 2 * config.max_position_embeddings - 1, + self.attention_head_size) + self.clamp_min_for_underflow = clamp_min_for_underflow + self.clamp_max_for_overflow = clamp_max_for_overflow + + self.is_decoder = config.is_decoder + + def transpose_for_scores(self, x: Tensor) -> Tensor: + """Transpose the dimensions of `x`.""" + new_x_shape = x.size()[:-1] + (self.num_attention_heads, + self.attention_head_size) + x = x.view(*new_x_shape) + return x.permute(0, 2, 1, 3) + + def forward( + self, + hidden_states: Tensor, + attention_mask: Optional[Tensor] = None, + head_mask: Optional[Tensor] = None, + encoder_hidden_states: Optional[Tensor] = None, + encoder_attention_mask: Optional[Tensor] = None, + past_key_value: Optional[Tuple[Tensor, Tensor]] = None, + output_attentions: bool = False, + ) -> Tuple[Tensor, ...]: + """Perform a forward pass through the BERT self-attention layer.""" + + mixed_query_layer = self.query(hidden_states) + + # If this is instantiated as a cross-attention module, the keys + # and values come from an encoder; the attention mask needs to be + # such that the encoder's padding tokens are not attended to. + is_cross_attention = encoder_hidden_states is not None + + if is_cross_attention and past_key_value is not None: + # reuse k,v, cross_attentions + key_layer = past_key_value[0] + value_layer = past_key_value[1] + attention_mask = encoder_attention_mask + elif is_cross_attention: + key_layer = self.transpose_for_scores( + self.key(encoder_hidden_states)) + value_layer = self.transpose_for_scores( + self.value(encoder_hidden_states)) + attention_mask = encoder_attention_mask + elif past_key_value is not None: + key_layer = self.transpose_for_scores(self.key(hidden_states)) + value_layer = self.transpose_for_scores(self.value(hidden_states)) + key_layer = torch.cat([past_key_value[0], key_layer], dim=2) + value_layer = torch.cat([past_key_value[1], value_layer], dim=2) + else: + key_layer = self.transpose_for_scores(self.key(hidden_states)) + value_layer = self.transpose_for_scores(self.value(hidden_states)) + + query_layer = self.transpose_for_scores(mixed_query_layer) + + if self.is_decoder: + past_key_value = (key_layer, value_layer) + + # Take the dot product between "query" and "key" + # to get the raw attention scores. + attention_scores = torch.matmul(query_layer, + key_layer.transpose(-1, -2)) + + if self.position_embedding_type == 'relative_key' or \ + self.position_embedding_type == 'relative_key_query': + seq_length = hidden_states.size()[1] + position_ids_l = torch.arange( + seq_length, dtype=torch.long, + device=hidden_states.device).view(-1, 1) + position_ids_r = torch.arange( + seq_length, dtype=torch.long, + device=hidden_states.device).view(1, -1) + distance = position_ids_l - position_ids_r + positional_embedding = self.distance_embedding( + distance + self.max_position_embeddings - 1) + positional_embedding = positional_embedding.to( + dtype=query_layer.dtype) # fp16 compatibility + + if self.position_embedding_type == 'relative_key': + relative_position_scores = torch.einsum( + 'bhld,lrd->bhlr', query_layer, positional_embedding) + attention_scores = attention_scores + relative_position_scores + elif self.position_embedding_type == 'relative_key_query': + relative_position_scores_query = torch.einsum( + 'bhld,lrd->bhlr', query_layer, positional_embedding) + relative_position_scores_key = torch.einsum( + 'bhrd,lrd->bhlr', key_layer, positional_embedding) + attention_scores = attention_scores + \ + relative_position_scores_query + \ + relative_position_scores_key + + attention_scores = attention_scores / math.sqrt( + self.attention_head_size) + + if self.clamp_min_for_underflow: + attention_scores = torch.clamp( + attention_scores, min=-MAX_CLAMP_VALUE + ) # Do not increase -50000, data type half has quite limited range + if self.clamp_max_for_overflow: + attention_scores = torch.clamp( + attention_scores, max=MAX_CLAMP_VALUE + ) # Do not increase 50000, data type half has quite limited range + + if attention_mask is not None: + # Apply the attention mask is + # (precomputed for all layers in BertModel forward() function) + attention_scores = attention_scores + attention_mask + + # Normalize the attention scores to probabilities. + attention_probs = nn.Softmax(dim=-1)(attention_scores) + + # This is actually dropping out entire tokens to attend to, which might + # seem a bit unusual, but is taken from the original Transformer paper. + attention_probs = self.dropout(attention_probs) + + # Mask heads if we want to + if head_mask is not None: + attention_probs = attention_probs * head_mask + + context_layer = torch.matmul(attention_probs, value_layer) + + context_layer = context_layer.permute(0, 2, 1, 3).contiguous() + new_context_layer_shape = context_layer.size()[:-2] + ( + self.all_head_size, ) + context_layer = context_layer.view(*new_context_layer_shape) + + outputs = (context_layer, + attention_probs) if output_attentions else (context_layer, ) + + if self.is_decoder: + outputs = outputs + (past_key_value, ) + return outputs + + +class BertAttention(HFBertAttention): + """BertAttention is made up of self-attention and intermediate+output. + + Compared to the BertAttention of Huggingface, only add the clamp. + + Args: + config (:class:`~transformers.BertConfig`): + The configuration object that + contains various parameters for the model. + clamp_min_for_underflow (bool, optional): + Whether to clamp the minimum value of the hidden states + to prevent underflow. Defaults to `False`. + clamp_max_for_overflow (bool, optional): + Whether to clamp the maximum value of the hidden states + to prevent overflow. Defaults to `False`. + """ + + def __init__(self, + config: BertConfig, + clamp_min_for_underflow: bool = False, + clamp_max_for_overflow: bool = False): + super().__init__(config) + self.self = BertSelfAttention(config, clamp_min_for_underflow, + clamp_max_for_overflow) + + +class BertIntermediate(HFBertIntermediate): + """Modified from transformers.models.bert.modeling_bert.BertIntermediate. + + Compared to the BertIntermediate of Huggingface, only add the clamp. + """ + + def forward(self, hidden_states: Tensor) -> Tensor: + hidden_states = self.dense(hidden_states) + hidden_states = clamp_values(hidden_states) + hidden_states = self.intermediate_act_fn(hidden_states) + hidden_states = clamp_values(hidden_states) + return hidden_states + + +class BertOutput(HFBertOutput): + """Modified from transformers.models.bert.modeling_bert.BertOutput. + + Compared to the BertOutput of Huggingface, only add the clamp. + """ + + def forward(self, hidden_states: Tensor, input_tensor: Tensor) -> Tensor: + hidden_states = self.dense(hidden_states) + hidden_states = self.dropout(hidden_states) + hidden_states = clamp_values(hidden_states) + hidden_states = self.LayerNorm(hidden_states + input_tensor) + hidden_states = clamp_values(hidden_states) + return hidden_states diff --git a/mmdetection/mmdet/models/utils/wbf.py b/mmdetection/mmdet/models/utils/wbf.py new file mode 100644 index 00000000..b26a2c66 --- /dev/null +++ b/mmdetection/mmdet/models/utils/wbf.py @@ -0,0 +1,250 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +import warnings +from typing import Tuple + +import numpy as np +import torch +from torch import Tensor + + +# References: https://github.com/ZFTurbo/Weighted-Boxes-Fusion +def weighted_boxes_fusion( + bboxes_list: list, + scores_list: list, + labels_list: list, + weights: list = None, + iou_thr: float = 0.55, + skip_box_thr: float = 0.0, + conf_type: str = 'avg', + allows_overflow: bool = False) -> Tuple[Tensor, Tensor, Tensor]: + """weighted boxes fusion is a method for + fusing predictions from different object detection models, which utilizes + confidence scores of all proposed bounding boxes to construct averaged + boxes. + + Args: + bboxes_list(list): list of boxes predictions from each model, + each box is 4 numbers. + scores_list(list): list of scores for each model + labels_list(list): list of labels for each model + weights: list of weights for each model. + Default: None, which means weight == 1 for each model + iou_thr: IoU value for boxes to be a match + skip_box_thr: exclude boxes with score lower than this variable. + conf_type: how to calculate confidence in weighted boxes. + 'avg': average value, + 'max': maximum value, + 'box_and_model_avg': box and model wise hybrid weighted average, + 'absent_model_aware_avg': weighted average that takes into + account the absent model. + allows_overflow: false if we want confidence score not exceed 1.0. + + Returns: + bboxes(Tensor): boxes coordinates (Order of boxes: x1, y1, x2, y2). + scores(Tensor): confidence scores + labels(Tensor): boxes labels + """ + + if weights is None: + weights = np.ones(len(bboxes_list)) + if len(weights) != len(bboxes_list): + print('Warning: incorrect number of weights {}. Must be: ' + '{}. Set weights equal to 1.'.format( + len(weights), len(bboxes_list))) + weights = np.ones(len(bboxes_list)) + weights = np.array(weights) + + if conf_type not in [ + 'avg', 'max', 'box_and_model_avg', 'absent_model_aware_avg' + ]: + print('Unknown conf_type: {}. Must be "avg", ' + '"max" or "box_and_model_avg", ' + 'or "absent_model_aware_avg"'.format(conf_type)) + exit() + + filtered_boxes = prefilter_boxes(bboxes_list, scores_list, labels_list, + weights, skip_box_thr) + if len(filtered_boxes) == 0: + return torch.Tensor(), torch.Tensor(), torch.Tensor() + + overall_boxes = [] + + for label in filtered_boxes: + boxes = filtered_boxes[label] + new_boxes = [] + weighted_boxes = np.empty((0, 8)) + + # Clusterize boxes + for j in range(0, len(boxes)): + index, best_iou = find_matching_box_fast(weighted_boxes, boxes[j], + iou_thr) + + if index != -1: + new_boxes[index].append(boxes[j]) + weighted_boxes[index] = get_weighted_box( + new_boxes[index], conf_type) + else: + new_boxes.append([boxes[j].copy()]) + weighted_boxes = np.vstack((weighted_boxes, boxes[j].copy())) + + # Rescale confidence based on number of models and boxes + for i in range(len(new_boxes)): + clustered_boxes = new_boxes[i] + if conf_type == 'box_and_model_avg': + clustered_boxes = np.array(clustered_boxes) + # weighted average for boxes + weighted_boxes[i, 1] = weighted_boxes[i, 1] * len( + clustered_boxes) / weighted_boxes[i, 2] + # identify unique model index by model index column + _, idx = np.unique(clustered_boxes[:, 3], return_index=True) + # rescale by unique model weights + weighted_boxes[i, 1] = weighted_boxes[i, 1] * clustered_boxes[ + idx, 2].sum() / weights.sum() + elif conf_type == 'absent_model_aware_avg': + clustered_boxes = np.array(clustered_boxes) + # get unique model index in the cluster + models = np.unique(clustered_boxes[:, 3]).astype(int) + # create a mask to get unused model weights + mask = np.ones(len(weights), dtype=bool) + mask[models] = False + # absent model aware weighted average + weighted_boxes[ + i, 1] = weighted_boxes[i, 1] * len(clustered_boxes) / ( + weighted_boxes[i, 2] + weights[mask].sum()) + elif conf_type == 'max': + weighted_boxes[i, 1] = weighted_boxes[i, 1] / weights.max() + elif not allows_overflow: + weighted_boxes[i, 1] = weighted_boxes[i, 1] * min( + len(weights), len(clustered_boxes)) / weights.sum() + else: + weighted_boxes[i, 1] = weighted_boxes[i, 1] * len( + clustered_boxes) / weights.sum() + overall_boxes.append(weighted_boxes) + overall_boxes = np.concatenate(overall_boxes, axis=0) + overall_boxes = overall_boxes[overall_boxes[:, 1].argsort()[::-1]] + + bboxes = torch.Tensor(overall_boxes[:, 4:]) + scores = torch.Tensor(overall_boxes[:, 1]) + labels = torch.Tensor(overall_boxes[:, 0]).int() + + return bboxes, scores, labels + + +def prefilter_boxes(boxes, scores, labels, weights, thr): + + new_boxes = dict() + + for t in range(len(boxes)): + + if len(boxes[t]) != len(scores[t]): + print('Error. Length of boxes arrays not equal to ' + 'length of scores array: {} != {}'.format( + len(boxes[t]), len(scores[t]))) + exit() + + if len(boxes[t]) != len(labels[t]): + print('Error. Length of boxes arrays not equal to ' + 'length of labels array: {} != {}'.format( + len(boxes[t]), len(labels[t]))) + exit() + + for j in range(len(boxes[t])): + score = scores[t][j] + if score < thr: + continue + label = int(labels[t][j]) + box_part = boxes[t][j] + x1 = float(box_part[0]) + y1 = float(box_part[1]) + x2 = float(box_part[2]) + y2 = float(box_part[3]) + + # Box data checks + if x2 < x1: + warnings.warn('X2 < X1 value in box. Swap them.') + x1, x2 = x2, x1 + if y2 < y1: + warnings.warn('Y2 < Y1 value in box. Swap them.') + y1, y2 = y2, y1 + if (x2 - x1) * (y2 - y1) == 0.0: + warnings.warn('Zero area box skipped: {}.'.format(box_part)) + continue + + # [label, score, weight, model index, x1, y1, x2, y2] + b = [ + int(label), + float(score) * weights[t], weights[t], t, x1, y1, x2, y2 + ] + + if label not in new_boxes: + new_boxes[label] = [] + new_boxes[label].append(b) + + # Sort each list in dict by score and transform it to numpy array + for k in new_boxes: + current_boxes = np.array(new_boxes[k]) + new_boxes[k] = current_boxes[current_boxes[:, 1].argsort()[::-1]] + + return new_boxes + + +def get_weighted_box(boxes, conf_type='avg'): + + box = np.zeros(8, dtype=np.float32) + conf = 0 + conf_list = [] + w = 0 + for b in boxes: + box[4:] += (b[1] * b[4:]) + conf += b[1] + conf_list.append(b[1]) + w += b[2] + box[0] = boxes[0][0] + if conf_type in ('avg', 'box_and_model_avg', 'absent_model_aware_avg'): + box[1] = conf / len(boxes) + elif conf_type == 'max': + box[1] = np.array(conf_list).max() + box[2] = w + box[3] = -1 + box[4:] /= conf + + return box + + +def find_matching_box_fast(boxes_list, new_box, match_iou): + + def bb_iou_array(boxes, new_box): + # bb intersection over union + xA = np.maximum(boxes[:, 0], new_box[0]) + yA = np.maximum(boxes[:, 1], new_box[1]) + xB = np.minimum(boxes[:, 2], new_box[2]) + yB = np.minimum(boxes[:, 3], new_box[3]) + + interArea = np.maximum(xB - xA, 0) * np.maximum(yB - yA, 0) + + # compute the area of both the prediction and ground-truth rectangles + boxAArea = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1]) + boxBArea = (new_box[2] - new_box[0]) * (new_box[3] - new_box[1]) + + iou = interArea / (boxAArea + boxBArea - interArea) + + return iou + + if boxes_list.shape[0] == 0: + return -1, match_iou + + boxes = boxes_list + + ious = bb_iou_array(boxes[:, 4:], new_box[4:]) + + ious[boxes[:, 0] != new_box[0]] = -1 + + best_idx = np.argmax(ious) + best_iou = ious[best_idx] + + if best_iou <= match_iou: + best_iou = match_iou + best_idx = -1 + + return best_idx, best_iou diff --git a/mmdetection/mmdet/models/vis/__init__.py b/mmdetection/mmdet/models/vis/__init__.py new file mode 100644 index 00000000..ab63a906 --- /dev/null +++ b/mmdetection/mmdet/models/vis/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .mask2former_vis import Mask2FormerVideo +from .masktrack_rcnn import MaskTrackRCNN + +__all__ = ['Mask2FormerVideo', 'MaskTrackRCNN'] diff --git a/mmdetection/mmdet/models/vis/mask2former_vis.py b/mmdetection/mmdet/models/vis/mask2former_vis.py new file mode 100644 index 00000000..6ab04296 --- /dev/null +++ b/mmdetection/mmdet/models/vis/mask2former_vis.py @@ -0,0 +1,120 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Union + +from torch import Tensor + +from mmdet.models.mot import BaseMOTModel +from mmdet.registry import MODELS +from mmdet.structures import TrackDataSample, TrackSampleList +from mmdet.utils import OptConfigType, OptMultiConfig + + +@MODELS.register_module() +class Mask2FormerVideo(BaseMOTModel): + r"""Implementation of `Masked-attention Mask + Transformer for Universal Image Segmentation + `_. + + Args: + backbone (dict): Configuration of backbone. Defaults to None. + track_head (dict): Configuration of track head. Defaults to None. + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`TrackDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + Defaults to None. + init_cfg (dict or list[dict]): Configuration of initialization. + Defaults to None. + """ + + def __init__(self, + backbone: Optional[dict] = None, + track_head: Optional[dict] = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super(BaseMOTModel, self).__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + + if backbone is not None: + self.backbone = MODELS.build(backbone) + + if track_head is not None: + self.track_head = MODELS.build(track_head) + + self.num_classes = self.track_head.num_classes + + def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict, + missing_keys, unexpected_keys, error_msgs): + """Overload in order to load mmdet pretrained ckpt.""" + for key in list(state_dict): + if key.startswith('panoptic_head'): + state_dict[key.replace('panoptic', + 'track')] = state_dict.pop(key) + + super()._load_from_state_dict(state_dict, prefix, local_metadata, + strict, missing_keys, unexpected_keys, + error_msgs) + + def loss(self, inputs: Tensor, data_samples: TrackSampleList, + **kwargs) -> Union[dict, tuple]: + """ + Args: + inputs (Tensor): Input images of shape (N, T, C, H, W). + These should usually be mean centered and std scaled. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + # shape (N * T, C, H, W) + img = inputs.flatten(0, 1) + + x = self.backbone(img) + losses = self.track_head.loss(x, data_samples) + + return losses + + def predict(self, + inputs: Tensor, + data_samples: TrackSampleList, + rescale: bool = True) -> TrackSampleList: + """Predict results from a batch of inputs and data samples with + postprocessing. + + Args: + inputs (Tensor): of shape (N, T, C, H, W) encoding + input images. The N denotes batch size. + The T denotes the number of frames in a video. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `video_data_samples`. + rescale (bool, Optional): If False, then returned bboxes and masks + will fit the scale of img, otherwise, returned bboxes and masks + will fit the scale of original image shape. Defaults to True. + + Returns: + TrackSampleList: Tracking results of the inputs. + """ + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + + assert len(data_samples) == 1, \ + 'Mask2former only support 1 batch size per gpu for now.' + + # [T, C, H, W] + img = inputs[0] + track_data_sample = data_samples[0] + feats = self.backbone(img) + pred_track_ins_list = self.track_head.predict(feats, track_data_sample, + rescale) + + det_data_samples_list = [] + for idx, pred_track_ins in enumerate(pred_track_ins_list): + img_data_sample = track_data_sample[idx] + img_data_sample.pred_track_instances = pred_track_ins + det_data_samples_list.append(img_data_sample) + + results = TrackDataSample() + results.video_data_samples = det_data_samples_list + return [results] diff --git a/mmdetection/mmdet/models/vis/masktrack_rcnn.py b/mmdetection/mmdet/models/vis/masktrack_rcnn.py new file mode 100644 index 00000000..9c28e7b8 --- /dev/null +++ b/mmdetection/mmdet/models/vis/masktrack_rcnn.py @@ -0,0 +1,181 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +from torch import Tensor + +from mmdet.models.mot import BaseMOTModel +from mmdet.registry import MODELS +from mmdet.structures import TrackSampleList +from mmdet.utils import OptConfigType, OptMultiConfig + + +@MODELS.register_module() +class MaskTrackRCNN(BaseMOTModel): + """Video Instance Segmentation. + + This video instance segmentor is the implementation of`MaskTrack R-CNN + `_. + + Args: + detector (dict): Configuration of detector. Defaults to None. + track_head (dict): Configuration of track head. Defaults to None. + tracker (dict): Configuration of tracker. Defaults to None. + data_preprocessor (dict or ConfigDict, optional): The pre-process + config of :class:`TrackDataPreprocessor`. it usually includes, + ``pad_size_divisor``, ``pad_value``, ``mean`` and ``std``. + init_cfg (dict or list[dict]): Configuration of initialization. + Defaults to None. + """ + + def __init__(self, + detector: Optional[dict] = None, + track_head: Optional[dict] = None, + tracker: Optional[dict] = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__(data_preprocessor, init_cfg) + + if detector is not None: + self.detector = MODELS.build(detector) + assert hasattr(self.detector, 'roi_head'), \ + 'MaskTrack R-CNN only supports two stage detectors.' + + if track_head is not None: + self.track_head = MODELS.build(track_head) + if tracker is not None: + self.tracker = MODELS.build(tracker) + + def loss(self, inputs: Tensor, data_samples: TrackSampleList, + **kwargs) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + inputs (Dict[str, Tensor]): of shape (N, T, C, H, W) encoding + input images. Typically these should be mean centered and std + scaled. The N denotes batch size. The T denotes the number of + frames. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance`. + + Returns: + dict: A dictionary of loss components. + """ + + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + assert inputs.size(1) == 2, \ + 'MaskTrackRCNN can only have 1 key frame and 1 reference frame.' + + # split the data_samples into two aspects: key frames and reference + # frames + ref_data_samples, key_data_samples = [], [] + key_frame_inds, ref_frame_inds = [], [] + + # set cat_id of gt_labels to 0 in RPN + for track_data_sample in data_samples: + key_data_sample = track_data_sample.get_key_frames()[0] + key_data_samples.append(key_data_sample) + ref_data_sample = track_data_sample.get_ref_frames()[0] + ref_data_samples.append(ref_data_sample) + key_frame_inds.append(track_data_sample.key_frames_inds[0]) + ref_frame_inds.append(track_data_sample.ref_frames_inds[0]) + + key_frame_inds = torch.tensor(key_frame_inds, dtype=torch.int64) + ref_frame_inds = torch.tensor(ref_frame_inds, dtype=torch.int64) + batch_inds = torch.arange(len(inputs)) + key_imgs = inputs[batch_inds, key_frame_inds].contiguous() + ref_imgs = inputs[batch_inds, ref_frame_inds].contiguous() + + x = self.detector.extract_feat(key_imgs) + ref_x = self.detector.extract_feat(ref_imgs) + + losses = dict() + + # RPN forward and loss + if self.detector.with_rpn: + proposal_cfg = self.detector.train_cfg.get( + 'rpn_proposal', self.detector.test_cfg.rpn) + + rpn_losses, rpn_results_list = self.detector.rpn_head. \ + loss_and_predict(x, + key_data_samples, + proposal_cfg=proposal_cfg, + **kwargs) + + # avoid get same name with roi_head loss + keys = rpn_losses.keys() + for key in keys: + if 'loss' in key and 'rpn' not in key: + rpn_losses[f'rpn_{key}'] = rpn_losses.pop(key) + losses.update(rpn_losses) + else: + # TODO: Not support currently, should have a check at Fast R-CNN + assert key_data_samples[0].get('proposals', None) is not None + # use pre-defined proposals in InstanceData for the second stage + # to extract ROI features. + rpn_results_list = [ + key_data_sample.proposals + for key_data_sample in key_data_samples + ] + + losses_detect = self.detector.roi_head.loss(x, rpn_results_list, + key_data_samples, **kwargs) + losses.update(losses_detect) + + losses_track = self.track_head.loss(x, ref_x, rpn_results_list, + data_samples, **kwargs) + losses.update(losses_track) + + return losses + + def predict(self, + inputs: Tensor, + data_samples: TrackSampleList, + rescale: bool = True, + **kwargs) -> TrackSampleList: + """Test without augmentation. + + Args: + inputs (Tensor): of shape (N, T, C, H, W) encoding + input images. The N denotes batch size. + The T denotes the number of frames in a video. + data_samples (list[:obj:`TrackDataSample`]): The batch + data samples. It usually includes information such + as `video_data_samples`. + rescale (bool, Optional): If False, then returned bboxes and masks + will fit the scale of img, otherwise, returned bboxes and masks + will fit the scale of original image shape. Defaults to True. + + Returns: + TrackSampleList: Tracking results of the inputs. + """ + assert inputs.dim() == 5, 'The img must be 5D Tensor (N, T, C, H, W).' + + assert len(data_samples) == 1, \ + 'MaskTrackRCNN only support 1 batch size per gpu for now.' + + track_data_sample = data_samples[0] + video_len = len(track_data_sample) + if track_data_sample[0].frame_id == 0: + self.tracker.reset() + + for frame_id in range(video_len): + img_data_sample = track_data_sample[frame_id] + single_img = inputs[:, frame_id].contiguous() + x = self.detector.extract_feat(single_img) + + rpn_results_list = self.detector.rpn_head.predict( + x, [img_data_sample]) + # det_results List[InstanceData] + det_results = self.detector.roi_head.predict( + x, rpn_results_list, [img_data_sample], rescale=rescale) + assert len(det_results) == 1, 'Batch inference is not supported.' + assert 'masks' in det_results[0], 'There are no mask results.' + + img_data_sample.pred_instances = det_results[0] + frame_pred_track_instances = self.tracker.track( + model=self, feats=x, data_sample=img_data_sample, **kwargs) + img_data_sample.pred_track_instances = frame_pred_track_instances + + return [track_data_sample] diff --git a/mmdetection/mmdet/registry.py b/mmdetection/mmdet/registry.py new file mode 100644 index 00000000..3a5b2b28 --- /dev/null +++ b/mmdetection/mmdet/registry.py @@ -0,0 +1,121 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""MMDetection provides 17 registry nodes to support using modules across +projects. Each node is a child of the root registry in MMEngine. + +More details can be found at +https://mmengine.readthedocs.io/en/latest/tutorials/registry.html. +""" + +from mmengine.registry import DATA_SAMPLERS as MMENGINE_DATA_SAMPLERS +from mmengine.registry import DATASETS as MMENGINE_DATASETS +from mmengine.registry import EVALUATOR as MMENGINE_EVALUATOR +from mmengine.registry import HOOKS as MMENGINE_HOOKS +from mmengine.registry import LOG_PROCESSORS as MMENGINE_LOG_PROCESSORS +from mmengine.registry import LOOPS as MMENGINE_LOOPS +from mmengine.registry import METRICS as MMENGINE_METRICS +from mmengine.registry import MODEL_WRAPPERS as MMENGINE_MODEL_WRAPPERS +from mmengine.registry import MODELS as MMENGINE_MODELS +from mmengine.registry import \ + OPTIM_WRAPPER_CONSTRUCTORS as MMENGINE_OPTIM_WRAPPER_CONSTRUCTORS +from mmengine.registry import OPTIM_WRAPPERS as MMENGINE_OPTIM_WRAPPERS +from mmengine.registry import OPTIMIZERS as MMENGINE_OPTIMIZERS +from mmengine.registry import PARAM_SCHEDULERS as MMENGINE_PARAM_SCHEDULERS +from mmengine.registry import \ + RUNNER_CONSTRUCTORS as MMENGINE_RUNNER_CONSTRUCTORS +from mmengine.registry import RUNNERS as MMENGINE_RUNNERS +from mmengine.registry import TASK_UTILS as MMENGINE_TASK_UTILS +from mmengine.registry import TRANSFORMS as MMENGINE_TRANSFORMS +from mmengine.registry import VISBACKENDS as MMENGINE_VISBACKENDS +from mmengine.registry import VISUALIZERS as MMENGINE_VISUALIZERS +from mmengine.registry import \ + WEIGHT_INITIALIZERS as MMENGINE_WEIGHT_INITIALIZERS +from mmengine.registry import Registry + +# manage all kinds of runners like `EpochBasedRunner` and `IterBasedRunner` +RUNNERS = Registry( + 'runner', parent=MMENGINE_RUNNERS, locations=['mmdet.engine.runner']) +# manage runner constructors that define how to initialize runners +RUNNER_CONSTRUCTORS = Registry( + 'runner constructor', + parent=MMENGINE_RUNNER_CONSTRUCTORS, + locations=['mmdet.engine.runner']) +# manage all kinds of loops like `EpochBasedTrainLoop` +LOOPS = Registry( + 'loop', parent=MMENGINE_LOOPS, locations=['mmdet.engine.runner']) +# manage all kinds of hooks like `CheckpointHook` +HOOKS = Registry( + 'hook', parent=MMENGINE_HOOKS, locations=['mmdet.engine.hooks']) + +# manage data-related modules +DATASETS = Registry( + 'dataset', parent=MMENGINE_DATASETS, locations=['mmdet.datasets']) +DATA_SAMPLERS = Registry( + 'data sampler', + parent=MMENGINE_DATA_SAMPLERS, + locations=['mmdet.datasets.samplers']) +TRANSFORMS = Registry( + 'transform', + parent=MMENGINE_TRANSFORMS, + locations=['mmdet.datasets.transforms']) + +# manage all kinds of modules inheriting `nn.Module` +MODELS = Registry('model', parent=MMENGINE_MODELS, locations=['mmdet.models']) +# manage all kinds of model wrappers like 'MMDistributedDataParallel' +MODEL_WRAPPERS = Registry( + 'model_wrapper', + parent=MMENGINE_MODEL_WRAPPERS, + locations=['mmdet.models']) +# manage all kinds of weight initialization modules like `Uniform` +WEIGHT_INITIALIZERS = Registry( + 'weight initializer', + parent=MMENGINE_WEIGHT_INITIALIZERS, + locations=['mmdet.models']) + +# manage all kinds of optimizers like `SGD` and `Adam` +OPTIMIZERS = Registry( + 'optimizer', + parent=MMENGINE_OPTIMIZERS, + locations=['mmdet.engine.optimizers']) +# manage optimizer wrapper +OPTIM_WRAPPERS = Registry( + 'optim_wrapper', + parent=MMENGINE_OPTIM_WRAPPERS, + locations=['mmdet.engine.optimizers']) +# manage constructors that customize the optimization hyperparameters. +OPTIM_WRAPPER_CONSTRUCTORS = Registry( + 'optimizer constructor', + parent=MMENGINE_OPTIM_WRAPPER_CONSTRUCTORS, + locations=['mmdet.engine.optimizers']) +# manage all kinds of parameter schedulers like `MultiStepLR` +PARAM_SCHEDULERS = Registry( + 'parameter scheduler', + parent=MMENGINE_PARAM_SCHEDULERS, + locations=['mmdet.engine.schedulers']) +# manage all kinds of metrics +METRICS = Registry( + 'metric', parent=MMENGINE_METRICS, locations=['mmdet.evaluation']) +# manage evaluator +EVALUATOR = Registry( + 'evaluator', parent=MMENGINE_EVALUATOR, locations=['mmdet.evaluation']) + +# manage task-specific modules like anchor generators and box coders +TASK_UTILS = Registry( + 'task util', parent=MMENGINE_TASK_UTILS, locations=['mmdet.models']) + +# manage visualizer +VISUALIZERS = Registry( + 'visualizer', + parent=MMENGINE_VISUALIZERS, + locations=['mmdet.visualization']) +# manage visualizer backend +VISBACKENDS = Registry( + 'vis_backend', + parent=MMENGINE_VISBACKENDS, + locations=['mmdet.visualization']) + +# manage logprocessor +LOG_PROCESSORS = Registry( + 'log_processor', + parent=MMENGINE_LOG_PROCESSORS, + # TODO: update the location when mmdet has its own log processor + locations=['mmdet.engine']) diff --git a/mmdetection/mmdet/structures/__init__.py b/mmdetection/mmdet/structures/__init__.py new file mode 100644 index 00000000..381c6a4f --- /dev/null +++ b/mmdetection/mmdet/structures/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .det_data_sample import DetDataSample, OptSampleList, SampleList +from .reid_data_sample import ReIDDataSample +from .track_data_sample import (OptTrackSampleList, TrackDataSample, + TrackSampleList) + +__all__ = [ + 'DetDataSample', 'SampleList', 'OptSampleList', 'TrackDataSample', + 'TrackSampleList', 'OptTrackSampleList', 'ReIDDataSample' +] diff --git a/mmdetection/mmdet/structures/bbox/__init__.py b/mmdetection/mmdet/structures/bbox/__init__.py new file mode 100644 index 00000000..4d531986 --- /dev/null +++ b/mmdetection/mmdet/structures/bbox/__init__.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_boxes import BaseBoxes +from .bbox_overlaps import bbox_overlaps +from .box_type import (autocast_box_type, convert_box_type, get_box_type, + register_box, register_box_converter) +from .horizontal_boxes import HorizontalBoxes +from .transforms import bbox_cxcyah_to_xyxy # noqa: E501 +from .transforms import (bbox2corner, bbox2distance, bbox2result, bbox2roi, + bbox_cxcywh_to_xyxy, bbox_flip, bbox_mapping, + bbox_mapping_back, bbox_project, bbox_rescale, + bbox_xyxy_to_cxcyah, bbox_xyxy_to_cxcywh, cat_boxes, + corner2bbox, distance2bbox, empty_box_as, + find_inside_bboxes, get_box_tensor, get_box_wh, + roi2bbox, scale_boxes, stack_boxes) + +__all__ = [ + 'bbox_overlaps', 'bbox_flip', 'bbox_mapping', 'bbox_mapping_back', + 'bbox2roi', 'roi2bbox', 'bbox2result', 'distance2bbox', 'bbox2distance', + 'bbox_rescale', 'bbox_cxcywh_to_xyxy', 'bbox_xyxy_to_cxcywh', + 'find_inside_bboxes', 'bbox2corner', 'corner2bbox', 'bbox_project', + 'BaseBoxes', 'convert_box_type', 'get_box_type', 'register_box', + 'register_box_converter', 'HorizontalBoxes', 'autocast_box_type', + 'cat_boxes', 'stack_boxes', 'scale_boxes', 'get_box_wh', 'get_box_tensor', + 'empty_box_as', 'bbox_xyxy_to_cxcyah', 'bbox_cxcyah_to_xyxy' +] diff --git a/mmdetection/mmdet/structures/bbox/base_boxes.py b/mmdetection/mmdet/structures/bbox/base_boxes.py new file mode 100644 index 00000000..0ed66766 --- /dev/null +++ b/mmdetection/mmdet/structures/bbox/base_boxes.py @@ -0,0 +1,549 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod, abstractproperty, abstractstaticmethod +from typing import List, Optional, Sequence, Tuple, Type, TypeVar, Union + +import numpy as np +import torch +from torch import BoolTensor, Tensor + +from mmdet.structures.mask.structures import BitmapMasks, PolygonMasks + +T = TypeVar('T') +DeviceType = Union[str, torch.device] +IndexType = Union[slice, int, list, torch.LongTensor, torch.cuda.LongTensor, + torch.BoolTensor, torch.cuda.BoolTensor, np.ndarray] +MaskType = Union[BitmapMasks, PolygonMasks] + + +class BaseBoxes(metaclass=ABCMeta): + """The base class for 2D box types. + + The functions of ``BaseBoxes`` lie in three fields: + + - Verify the boxes shape. + - Support tensor-like operations. + - Define abstract functions for 2D boxes. + + In ``__init__`` , ``BaseBoxes`` verifies the validity of the data shape + w.r.t ``box_dim``. The tensor with the dimension >= 2 and the length + of the last dimension being ``box_dim`` will be regarded as valid. + ``BaseBoxes`` will restore them at the field ``tensor``. It's necessary + to override ``box_dim`` in subclass to guarantee the data shape is + correct. + + There are many basic tensor-like functions implemented in ``BaseBoxes``. + In most cases, users can operate ``BaseBoxes`` instance like a normal + tensor. To protect the validity of data shape, All tensor-like functions + cannot modify the last dimension of ``self.tensor``. + + When creating a new box type, users need to inherit from ``BaseBoxes`` + and override abstract methods and specify the ``box_dim``. Then, register + the new box type by using the decorator ``register_box_type``. + + Args: + data (Tensor or np.ndarray or Sequence): The box data with shape + (..., box_dim). + dtype (torch.dtype, Optional): data type of boxes. Defaults to None. + device (str or torch.device, Optional): device of boxes. + Default to None. + clone (bool): Whether clone ``boxes`` or not. Defaults to True. + """ + + # Used to verify the last dimension length + # Should override it in subclass. + box_dim: int = 0 + + def __init__(self, + data: Union[Tensor, np.ndarray, Sequence], + dtype: Optional[torch.dtype] = None, + device: Optional[DeviceType] = None, + clone: bool = True) -> None: + if isinstance(data, (np.ndarray, Tensor, Sequence)): + data = torch.as_tensor(data) + else: + raise TypeError('boxes should be Tensor, ndarray, or Sequence, ', + f'but got {type(data)}') + + if device is not None or dtype is not None: + data = data.to(dtype=dtype, device=device) + # Clone the data to avoid potential bugs + if clone: + data = data.clone() + # handle the empty input like [] + if data.numel() == 0: + data = data.reshape((-1, self.box_dim)) + + assert data.dim() >= 2 and data.size(-1) == self.box_dim, \ + ('The boxes dimension must >= 2 and the length of the last ' + f'dimension must be {self.box_dim}, but got boxes with ' + f'shape {data.shape}.') + self.tensor = data + + def convert_to(self, dst_type: Union[str, type]) -> 'BaseBoxes': + """Convert self to another box type. + + Args: + dst_type (str or type): destination box type. + + Returns: + :obj:`BaseBoxes`: destination box type object . + """ + from .box_type import convert_box_type + return convert_box_type(self, dst_type=dst_type) + + def empty_boxes(self: T, + dtype: Optional[torch.dtype] = None, + device: Optional[DeviceType] = None) -> T: + """Create empty box. + + Args: + dtype (torch.dtype, Optional): data type of boxes. + device (str or torch.device, Optional): device of boxes. + + Returns: + T: empty boxes with shape of (0, box_dim). + """ + empty_box = self.tensor.new_zeros( + 0, self.box_dim, dtype=dtype, device=device) + return type(self)(empty_box, clone=False) + + def fake_boxes(self: T, + sizes: Tuple[int], + fill: float = 0, + dtype: Optional[torch.dtype] = None, + device: Optional[DeviceType] = None) -> T: + """Create fake boxes with specific sizes and fill values. + + Args: + sizes (Tuple[int]): The size of fake boxes. The last value must + be equal with ``self.box_dim``. + fill (float): filling value. Defaults to 0. + dtype (torch.dtype, Optional): data type of boxes. + device (str or torch.device, Optional): device of boxes. + + Returns: + T: Fake boxes with shape of ``sizes``. + """ + fake_boxes = self.tensor.new_full( + sizes, fill, dtype=dtype, device=device) + return type(self)(fake_boxes, clone=False) + + def __getitem__(self: T, index: IndexType) -> T: + """Rewrite getitem to protect the last dimension shape.""" + boxes = self.tensor + if isinstance(index, np.ndarray): + index = torch.as_tensor(index, device=self.device) + if isinstance(index, Tensor) and index.dtype == torch.bool: + assert index.dim() < boxes.dim() + elif isinstance(index, tuple): + assert len(index) < boxes.dim() + # `Ellipsis`(...) is commonly used in index like [None, ...]. + # When `Ellipsis` is in index, it must be the last item. + if Ellipsis in index: + assert index[-1] is Ellipsis + + boxes = boxes[index] + if boxes.dim() == 1: + boxes = boxes.reshape(1, -1) + return type(self)(boxes, clone=False) + + def __setitem__(self: T, index: IndexType, values: Union[Tensor, T]) -> T: + """Rewrite setitem to protect the last dimension shape.""" + assert type(values) is type(self), \ + 'The value to be set must be the same box type as self' + values = values.tensor + + if isinstance(index, np.ndarray): + index = torch.as_tensor(index, device=self.device) + if isinstance(index, Tensor) and index.dtype == torch.bool: + assert index.dim() < self.tensor.dim() + elif isinstance(index, tuple): + assert len(index) < self.tensor.dim() + # `Ellipsis`(...) is commonly used in index like [None, ...]. + # When `Ellipsis` is in index, it must be the last item. + if Ellipsis in index: + assert index[-1] is Ellipsis + + self.tensor[index] = values + + def __len__(self) -> int: + """Return the length of self.tensor first dimension.""" + return self.tensor.size(0) + + def __deepcopy__(self, memo): + """Only clone the ``self.tensor`` when applying deepcopy.""" + cls = self.__class__ + other = cls.__new__(cls) + memo[id(self)] = other + other.tensor = self.tensor.clone() + return other + + def __repr__(self) -> str: + """Return a strings that describes the object.""" + return self.__class__.__name__ + '(\n' + str(self.tensor) + ')' + + def new_tensor(self, *args, **kwargs) -> Tensor: + """Reload ``new_tensor`` from self.tensor.""" + return self.tensor.new_tensor(*args, **kwargs) + + def new_full(self, *args, **kwargs) -> Tensor: + """Reload ``new_full`` from self.tensor.""" + return self.tensor.new_full(*args, **kwargs) + + def new_empty(self, *args, **kwargs) -> Tensor: + """Reload ``new_empty`` from self.tensor.""" + return self.tensor.new_empty(*args, **kwargs) + + def new_ones(self, *args, **kwargs) -> Tensor: + """Reload ``new_ones`` from self.tensor.""" + return self.tensor.new_ones(*args, **kwargs) + + def new_zeros(self, *args, **kwargs) -> Tensor: + """Reload ``new_zeros`` from self.tensor.""" + return self.tensor.new_zeros(*args, **kwargs) + + def size(self, dim: Optional[int] = None) -> Union[int, torch.Size]: + """Reload new_zeros from self.tensor.""" + # self.tensor.size(dim) cannot work when dim=None. + return self.tensor.size() if dim is None else self.tensor.size(dim) + + def dim(self) -> int: + """Reload ``dim`` from self.tensor.""" + return self.tensor.dim() + + @property + def device(self) -> torch.device: + """Reload ``device`` from self.tensor.""" + return self.tensor.device + + @property + def dtype(self) -> torch.dtype: + """Reload ``dtype`` from self.tensor.""" + return self.tensor.dtype + + @property + def shape(self) -> torch.Size: + return self.tensor.shape + + def numel(self) -> int: + """Reload ``numel`` from self.tensor.""" + return self.tensor.numel() + + def numpy(self) -> np.ndarray: + """Reload ``numpy`` from self.tensor.""" + return self.tensor.numpy() + + def to(self: T, *args, **kwargs) -> T: + """Reload ``to`` from self.tensor.""" + return type(self)(self.tensor.to(*args, **kwargs), clone=False) + + def cpu(self: T) -> T: + """Reload ``cpu`` from self.tensor.""" + return type(self)(self.tensor.cpu(), clone=False) + + def cuda(self: T, *args, **kwargs) -> T: + """Reload ``cuda`` from self.tensor.""" + return type(self)(self.tensor.cuda(*args, **kwargs), clone=False) + + def clone(self: T) -> T: + """Reload ``clone`` from self.tensor.""" + return type(self)(self.tensor) + + def detach(self: T) -> T: + """Reload ``detach`` from self.tensor.""" + return type(self)(self.tensor.detach(), clone=False) + + def view(self: T, *shape: Tuple[int]) -> T: + """Reload ``view`` from self.tensor.""" + return type(self)(self.tensor.view(shape), clone=False) + + def reshape(self: T, *shape: Tuple[int]) -> T: + """Reload ``reshape`` from self.tensor.""" + return type(self)(self.tensor.reshape(shape), clone=False) + + def expand(self: T, *sizes: Tuple[int]) -> T: + """Reload ``expand`` from self.tensor.""" + return type(self)(self.tensor.expand(sizes), clone=False) + + def repeat(self: T, *sizes: Tuple[int]) -> T: + """Reload ``repeat`` from self.tensor.""" + return type(self)(self.tensor.repeat(sizes), clone=False) + + def transpose(self: T, dim0: int, dim1: int) -> T: + """Reload ``transpose`` from self.tensor.""" + ndim = self.tensor.dim() + assert dim0 != -1 and dim0 != ndim - 1 + assert dim1 != -1 and dim1 != ndim - 1 + return type(self)(self.tensor.transpose(dim0, dim1), clone=False) + + def permute(self: T, *dims: Tuple[int]) -> T: + """Reload ``permute`` from self.tensor.""" + assert dims[-1] == -1 or dims[-1] == self.tensor.dim() - 1 + return type(self)(self.tensor.permute(dims), clone=False) + + def split(self: T, + split_size_or_sections: Union[int, Sequence[int]], + dim: int = 0) -> List[T]: + """Reload ``split`` from self.tensor.""" + assert dim != -1 and dim != self.tensor.dim() - 1 + boxes_list = self.tensor.split(split_size_or_sections, dim=dim) + return [type(self)(boxes, clone=False) for boxes in boxes_list] + + def chunk(self: T, chunks: int, dim: int = 0) -> List[T]: + """Reload ``chunk`` from self.tensor.""" + assert dim != -1 and dim != self.tensor.dim() - 1 + boxes_list = self.tensor.chunk(chunks, dim=dim) + return [type(self)(boxes, clone=False) for boxes in boxes_list] + + def unbind(self: T, dim: int = 0) -> T: + """Reload ``unbind`` from self.tensor.""" + assert dim != -1 and dim != self.tensor.dim() - 1 + boxes_list = self.tensor.unbind(dim=dim) + return [type(self)(boxes, clone=False) for boxes in boxes_list] + + def flatten(self: T, start_dim: int = 0, end_dim: int = -2) -> T: + """Reload ``flatten`` from self.tensor.""" + assert end_dim != -1 and end_dim != self.tensor.dim() - 1 + return type(self)(self.tensor.flatten(start_dim, end_dim), clone=False) + + def squeeze(self: T, dim: Optional[int] = None) -> T: + """Reload ``squeeze`` from self.tensor.""" + boxes = self.tensor.squeeze() if dim is None else \ + self.tensor.squeeze(dim) + return type(self)(boxes, clone=False) + + def unsqueeze(self: T, dim: int) -> T: + """Reload ``unsqueeze`` from self.tensor.""" + assert dim != -1 and dim != self.tensor.dim() + return type(self)(self.tensor.unsqueeze(dim), clone=False) + + @classmethod + def cat(cls: Type[T], box_list: Sequence[T], dim: int = 0) -> T: + """Cancatenates a box instance list into one single box instance. + Similar to ``torch.cat``. + + Args: + box_list (Sequence[T]): A sequence of box instances. + dim (int): The dimension over which the box are concatenated. + Defaults to 0. + + Returns: + T: Concatenated box instance. + """ + assert isinstance(box_list, Sequence) + if len(box_list) == 0: + raise ValueError('box_list should not be a empty list.') + + assert dim != -1 and dim != box_list[0].dim() - 1 + assert all(isinstance(boxes, cls) for boxes in box_list) + + th_box_list = [boxes.tensor for boxes in box_list] + return cls(torch.cat(th_box_list, dim=dim), clone=False) + + @classmethod + def stack(cls: Type[T], box_list: Sequence[T], dim: int = 0) -> T: + """Concatenates a sequence of tensors along a new dimension. Similar to + ``torch.stack``. + + Args: + box_list (Sequence[T]): A sequence of box instances. + dim (int): Dimension to insert. Defaults to 0. + + Returns: + T: Concatenated box instance. + """ + assert isinstance(box_list, Sequence) + if len(box_list) == 0: + raise ValueError('box_list should not be a empty list.') + + assert dim != -1 and dim != box_list[0].dim() + assert all(isinstance(boxes, cls) for boxes in box_list) + + th_box_list = [boxes.tensor for boxes in box_list] + return cls(torch.stack(th_box_list, dim=dim), clone=False) + + @abstractproperty + def centers(self) -> Tensor: + """Return a tensor representing the centers of boxes.""" + pass + + @abstractproperty + def areas(self) -> Tensor: + """Return a tensor representing the areas of boxes.""" + pass + + @abstractproperty + def widths(self) -> Tensor: + """Return a tensor representing the widths of boxes.""" + pass + + @abstractproperty + def heights(self) -> Tensor: + """Return a tensor representing the heights of boxes.""" + pass + + @abstractmethod + def flip_(self, + img_shape: Tuple[int, int], + direction: str = 'horizontal') -> None: + """Flip boxes horizontally or vertically in-place. + + Args: + img_shape (Tuple[int, int]): A tuple of image height and width. + direction (str): Flip direction, options are "horizontal", + "vertical" and "diagonal". Defaults to "horizontal" + """ + pass + + @abstractmethod + def translate_(self, distances: Tuple[float, float]) -> None: + """Translate boxes in-place. + + Args: + distances (Tuple[float, float]): translate distances. The first + is horizontal distance and the second is vertical distance. + """ + pass + + @abstractmethod + def clip_(self, img_shape: Tuple[int, int]) -> None: + """Clip boxes according to the image shape in-place. + + Args: + img_shape (Tuple[int, int]): A tuple of image height and width. + """ + pass + + @abstractmethod + def rotate_(self, center: Tuple[float, float], angle: float) -> None: + """Rotate all boxes in-place. + + Args: + center (Tuple[float, float]): Rotation origin. + angle (float): Rotation angle represented in degrees. Positive + values mean clockwise rotation. + """ + pass + + @abstractmethod + def project_(self, homography_matrix: Union[Tensor, np.ndarray]) -> None: + """Geometric transformat boxes in-place. + + Args: + homography_matrix (Tensor or np.ndarray]): + Shape (3, 3) for geometric transformation. + """ + pass + + @abstractmethod + def rescale_(self, scale_factor: Tuple[float, float]) -> None: + """Rescale boxes w.r.t. rescale_factor in-place. + + Note: + Both ``rescale_`` and ``resize_`` will enlarge or shrink boxes + w.r.t ``scale_facotr``. The difference is that ``resize_`` only + changes the width and the height of boxes, but ``rescale_`` also + rescales the box centers simultaneously. + + Args: + scale_factor (Tuple[float, float]): factors for scaling boxes. + The length should be 2. + """ + pass + + @abstractmethod + def resize_(self, scale_factor: Tuple[float, float]) -> None: + """Resize the box width and height w.r.t scale_factor in-place. + + Note: + Both ``rescale_`` and ``resize_`` will enlarge or shrink boxes + w.r.t ``scale_facotr``. The difference is that ``resize_`` only + changes the width and the height of boxes, but ``rescale_`` also + rescales the box centers simultaneously. + + Args: + scale_factor (Tuple[float, float]): factors for scaling box + shapes. The length should be 2. + """ + pass + + @abstractmethod + def is_inside(self, + img_shape: Tuple[int, int], + all_inside: bool = False, + allowed_border: int = 0) -> BoolTensor: + """Find boxes inside the image. + + Args: + img_shape (Tuple[int, int]): A tuple of image height and width. + all_inside (bool): Whether the boxes are all inside the image or + part inside the image. Defaults to False. + allowed_border (int): Boxes that extend beyond the image shape + boundary by more than ``allowed_border`` are considered + "outside" Defaults to 0. + Returns: + BoolTensor: A BoolTensor indicating whether the box is inside + the image. Assuming the original boxes have shape (m, n, box_dim), + the output has shape (m, n). + """ + pass + + @abstractmethod + def find_inside_points(self, + points: Tensor, + is_aligned: bool = False) -> BoolTensor: + """Find inside box points. Boxes dimension must be 2. + + Args: + points (Tensor): Points coordinates. Has shape of (m, 2). + is_aligned (bool): Whether ``points`` has been aligned with boxes + or not. If True, the length of boxes and ``points`` should be + the same. Defaults to False. + + Returns: + BoolTensor: A BoolTensor indicating whether a point is inside + boxes. Assuming the boxes has shape of (n, box_dim), if + ``is_aligned`` is False. The index has shape of (m, n). If + ``is_aligned`` is True, m should be equal to n and the index has + shape of (m, ). + """ + pass + + @abstractstaticmethod + def overlaps(boxes1: 'BaseBoxes', + boxes2: 'BaseBoxes', + mode: str = 'iou', + is_aligned: bool = False, + eps: float = 1e-6) -> Tensor: + """Calculate overlap between two set of boxes with their types + converted to the present box type. + + Args: + boxes1 (:obj:`BaseBoxes`): BaseBoxes with shape of (m, box_dim) + or empty. + boxes2 (:obj:`BaseBoxes`): BaseBoxes with shape of (n, box_dim) + or empty. + mode (str): "iou" (intersection over union), "iof" (intersection + over foreground). Defaults to "iou". + is_aligned (bool): If True, then m and n must be equal. Defaults + to False. + eps (float): A value added to the denominator for numerical + stability. Defaults to 1e-6. + + Returns: + Tensor: shape (m, n) if ``is_aligned`` is False else shape (m,) + """ + pass + + @abstractstaticmethod + def from_instance_masks(masks: MaskType) -> 'BaseBoxes': + """Create boxes from instance masks. + + Args: + masks (:obj:`BitmapMasks` or :obj:`PolygonMasks`): BitmapMasks or + PolygonMasks instance with length of n. + + Returns: + :obj:`BaseBoxes`: Converted boxes with shape of (n, box_dim). + """ + pass diff --git a/mmdetection/mmdet/structures/bbox/bbox_overlaps.py b/mmdetection/mmdet/structures/bbox/bbox_overlaps.py new file mode 100644 index 00000000..8e3435d2 --- /dev/null +++ b/mmdetection/mmdet/structures/bbox/bbox_overlaps.py @@ -0,0 +1,199 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + + +def fp16_clamp(x, min=None, max=None): + if not x.is_cuda and x.dtype == torch.float16: + # clamp for cpu float16, tensor fp16 has no clamp implementation + return x.float().clamp(min, max).half() + + return x.clamp(min, max) + + +def bbox_overlaps(bboxes1, bboxes2, mode='iou', is_aligned=False, eps=1e-6): + """Calculate overlap between two set of bboxes. + + FP16 Contributed by https://github.com/open-mmlab/mmdetection/pull/4889 + Note: + Assume bboxes1 is M x 4, bboxes2 is N x 4, when mode is 'iou', + there are some new generated variable when calculating IOU + using bbox_overlaps function: + + 1) is_aligned is False + area1: M x 1 + area2: N x 1 + lt: M x N x 2 + rb: M x N x 2 + wh: M x N x 2 + overlap: M x N x 1 + union: M x N x 1 + ious: M x N x 1 + + Total memory: + S = (9 x N x M + N + M) * 4 Byte, + + When using FP16, we can reduce: + R = (9 x N x M + N + M) * 4 / 2 Byte + R large than (N + M) * 4 * 2 is always true when N and M >= 1. + Obviously, N + M <= N * M < 3 * N * M, when N >=2 and M >=2, + N + 1 < 3 * N, when N or M is 1. + + Given M = 40 (ground truth), N = 400000 (three anchor boxes + in per grid, FPN, R-CNNs), + R = 275 MB (one times) + + A special case (dense detection), M = 512 (ground truth), + R = 3516 MB = 3.43 GB + + When the batch size is B, reduce: + B x R + + Therefore, CUDA memory runs out frequently. + + Experiments on GeForce RTX 2080Ti (11019 MiB): + + | dtype | M | N | Use | Real | Ideal | + |:----:|:----:|:----:|:----:|:----:|:----:| + | FP32 | 512 | 400000 | 8020 MiB | -- | -- | + | FP16 | 512 | 400000 | 4504 MiB | 3516 MiB | 3516 MiB | + | FP32 | 40 | 400000 | 1540 MiB | -- | -- | + | FP16 | 40 | 400000 | 1264 MiB | 276MiB | 275 MiB | + + 2) is_aligned is True + area1: N x 1 + area2: N x 1 + lt: N x 2 + rb: N x 2 + wh: N x 2 + overlap: N x 1 + union: N x 1 + ious: N x 1 + + Total memory: + S = 11 x N * 4 Byte + + When using FP16, we can reduce: + R = 11 x N * 4 / 2 Byte + + So do the 'giou' (large than 'iou'). + + Time-wise, FP16 is generally faster than FP32. + + When gpu_assign_thr is not -1, it takes more time on cpu + but not reduce memory. + There, we can reduce half the memory and keep the speed. + + If ``is_aligned`` is ``False``, then calculate the overlaps between each + bbox of bboxes1 and bboxes2, otherwise the overlaps between each aligned + pair of bboxes1 and bboxes2. + + Args: + bboxes1 (Tensor): shape (B, m, 4) in format or empty. + bboxes2 (Tensor): shape (B, n, 4) in format or empty. + B indicates the batch dim, in shape (B1, B2, ..., Bn). + If ``is_aligned`` is ``True``, then m and n must be equal. + mode (str): "iou" (intersection over union), "iof" (intersection over + foreground) or "giou" (generalized intersection over union). + Default "iou". + is_aligned (bool, optional): If True, then m and n must be equal. + Default False. + eps (float, optional): A value added to the denominator for numerical + stability. Default 1e-6. + + Returns: + Tensor: shape (m, n) if ``is_aligned`` is False else shape (m,) + + Example: + >>> bboxes1 = torch.FloatTensor([ + >>> [0, 0, 10, 10], + >>> [10, 10, 20, 20], + >>> [32, 32, 38, 42], + >>> ]) + >>> bboxes2 = torch.FloatTensor([ + >>> [0, 0, 10, 20], + >>> [0, 10, 10, 19], + >>> [10, 10, 20, 20], + >>> ]) + >>> overlaps = bbox_overlaps(bboxes1, bboxes2) + >>> assert overlaps.shape == (3, 3) + >>> overlaps = bbox_overlaps(bboxes1, bboxes2, is_aligned=True) + >>> assert overlaps.shape == (3, ) + + Example: + >>> empty = torch.empty(0, 4) + >>> nonempty = torch.FloatTensor([[0, 0, 10, 9]]) + >>> assert tuple(bbox_overlaps(empty, nonempty).shape) == (0, 1) + >>> assert tuple(bbox_overlaps(nonempty, empty).shape) == (1, 0) + >>> assert tuple(bbox_overlaps(empty, empty).shape) == (0, 0) + """ + + assert mode in ['iou', 'iof', 'giou'], f'Unsupported mode {mode}' + # Either the boxes are empty or the length of boxes' last dimension is 4 + assert (bboxes1.size(-1) == 4 or bboxes1.size(0) == 0) + assert (bboxes2.size(-1) == 4 or bboxes2.size(0) == 0) + + # Batch dim must be the same + # Batch dim: (B1, B2, ... Bn) + assert bboxes1.shape[:-2] == bboxes2.shape[:-2] + batch_shape = bboxes1.shape[:-2] + + rows = bboxes1.size(-2) + cols = bboxes2.size(-2) + if is_aligned: + assert rows == cols + + if rows * cols == 0: + if is_aligned: + return bboxes1.new(batch_shape + (rows, )) + else: + return bboxes1.new(batch_shape + (rows, cols)) + + area1 = (bboxes1[..., 2] - bboxes1[..., 0]) * ( + bboxes1[..., 3] - bboxes1[..., 1]) + area2 = (bboxes2[..., 2] - bboxes2[..., 0]) * ( + bboxes2[..., 3] - bboxes2[..., 1]) + + if is_aligned: + lt = torch.max(bboxes1[..., :2], bboxes2[..., :2]) # [B, rows, 2] + rb = torch.min(bboxes1[..., 2:], bboxes2[..., 2:]) # [B, rows, 2] + + wh = fp16_clamp(rb - lt, min=0) + overlap = wh[..., 0] * wh[..., 1] + + if mode in ['iou', 'giou']: + union = area1 + area2 - overlap + else: + union = area1 + if mode == 'giou': + enclosed_lt = torch.min(bboxes1[..., :2], bboxes2[..., :2]) + enclosed_rb = torch.max(bboxes1[..., 2:], bboxes2[..., 2:]) + else: + lt = torch.max(bboxes1[..., :, None, :2], + bboxes2[..., None, :, :2]) # [B, rows, cols, 2] + rb = torch.min(bboxes1[..., :, None, 2:], + bboxes2[..., None, :, 2:]) # [B, rows, cols, 2] + + wh = fp16_clamp(rb - lt, min=0) + overlap = wh[..., 0] * wh[..., 1] + + if mode in ['iou', 'giou']: + union = area1[..., None] + area2[..., None, :] - overlap + else: + union = area1[..., None] + if mode == 'giou': + enclosed_lt = torch.min(bboxes1[..., :, None, :2], + bboxes2[..., None, :, :2]) + enclosed_rb = torch.max(bboxes1[..., :, None, 2:], + bboxes2[..., None, :, 2:]) + + eps = union.new_tensor([eps]) + union = torch.max(union, eps) + ious = overlap / union + if mode in ['iou', 'iof']: + return ious + # calculate gious + enclose_wh = fp16_clamp(enclosed_rb - enclosed_lt, min=0) + enclose_area = enclose_wh[..., 0] * enclose_wh[..., 1] + enclose_area = torch.max(enclose_area, eps) + gious = ious - (enclose_area - union) / enclose_area + return gious diff --git a/mmdetection/mmdet/structures/bbox/box_type.py b/mmdetection/mmdet/structures/bbox/box_type.py new file mode 100644 index 00000000..c7eb5494 --- /dev/null +++ b/mmdetection/mmdet/structures/bbox/box_type.py @@ -0,0 +1,296 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Callable, Optional, Tuple, Type, Union + +import numpy as np +import torch +from torch import Tensor + +from .base_boxes import BaseBoxes + +BoxType = Union[np.ndarray, Tensor, BaseBoxes] + +box_types: dict = {} +_box_type_to_name: dict = {} +box_converters: dict = {} + + +def _register_box(name: str, box_type: Type, force: bool = False) -> None: + """Register a box type. + + Args: + name (str): The name of box type. + box_type (type): Box mode class to be registered. + force (bool): Whether to override an existing class with the same + name. Defaults to False. + """ + assert issubclass(box_type, BaseBoxes) + name = name.lower() + + if not force and (name in box_types or box_type in _box_type_to_name): + raise KeyError(f'box type {name} has been registered') + elif name in box_types: + _box_type = box_types.pop(name) + _box_type_to_name.pop(_box_type) + elif box_type in _box_type_to_name: + _name = _box_type_to_name.pop(box_type) + box_types.pop(_name) + + box_types[name] = box_type + _box_type_to_name[box_type] = name + + +def register_box(name: str, + box_type: Type = None, + force: bool = False) -> Union[Type, Callable]: + """Register a box type. + + A record will be added to ``bbox_types``, whose key is the box type name + and value is the box type itself. Simultaneously, a reverse dictionary + ``_box_type_to_name`` will be updated. It can be used as a decorator or + a normal function. + + Args: + name (str): The name of box type. + bbox_type (type, Optional): Box type class to be registered. + Defaults to None. + force (bool): Whether to override the existing box type with the same + name. Defaults to False. + + Examples: + >>> from mmdet.structures.bbox import register_box + >>> from mmdet.structures.bbox import BaseBoxes + + >>> # as a decorator + >>> @register_box('hbox') + >>> class HorizontalBoxes(BaseBoxes): + >>> pass + + >>> # as a normal function + >>> class RotatedBoxes(BaseBoxes): + >>> pass + >>> register_box('rbox', RotatedBoxes) + """ + if not isinstance(force, bool): + raise TypeError(f'force must be a boolean, but got {type(force)}') + + # use it as a normal method: register_box(name, box_type=BoxCls) + if box_type is not None: + _register_box(name=name, box_type=box_type, force=force) + return box_type + + # use it as a decorator: @register_box(name) + def _register(cls): + _register_box(name=name, box_type=cls, force=force) + return cls + + return _register + + +def _register_box_converter(src_type: Union[str, type], + dst_type: Union[str, type], + converter: Callable, + force: bool = False) -> None: + """Register a box converter. + + Args: + src_type (str or type): source box type name or class. + dst_type (str or type): destination box type name or class. + converter (Callable): Convert function. + force (bool): Whether to override the existing box type with the same + name. Defaults to False. + """ + assert callable(converter) + src_type_name, _ = get_box_type(src_type) + dst_type_name, _ = get_box_type(dst_type) + + converter_name = src_type_name + '2' + dst_type_name + if not force and converter_name in box_converters: + raise KeyError(f'The box converter from {src_type_name} to ' + f'{dst_type_name} has been registered.') + + box_converters[converter_name] = converter + + +def register_box_converter(src_type: Union[str, type], + dst_type: Union[str, type], + converter: Optional[Callable] = None, + force: bool = False) -> Callable: + """Register a box converter. + + A record will be added to ``box_converter``, whose key is + '{src_type_name}2{dst_type_name}' and value is the convert function. + It can be used as a decorator or a normal function. + + Args: + src_type (str or type): source box type name or class. + dst_type (str or type): destination box type name or class. + converter (Callable): Convert function. Defaults to None. + force (bool): Whether to override the existing box type with the same + name. Defaults to False. + + Examples: + >>> from mmdet.structures.bbox import register_box_converter + >>> # as a decorator + >>> @register_box_converter('hbox', 'rbox') + >>> def converter_A(boxes): + >>> pass + + >>> # as a normal function + >>> def converter_B(boxes): + >>> pass + >>> register_box_converter('rbox', 'hbox', converter_B) + """ + if not isinstance(force, bool): + raise TypeError(f'force must be a boolean, but got {type(force)}') + + # use it as a normal method: + # register_box_converter(src_type, dst_type, converter=Func) + if converter is not None: + _register_box_converter( + src_type=src_type, + dst_type=dst_type, + converter=converter, + force=force) + return converter + + # use it as a decorator: @register_box_converter(name) + def _register(func): + _register_box_converter( + src_type=src_type, dst_type=dst_type, converter=func, force=force) + return func + + return _register + + +def get_box_type(box_type: Union[str, type]) -> Tuple[str, type]: + """get both box type name and class. + + Args: + box_type (str or type): Single box type name or class. + + Returns: + Tuple[str, type]: A tuple of box type name and class. + """ + if isinstance(box_type, str): + type_name = box_type.lower() + assert type_name in box_types, \ + f"Box type {type_name} hasn't been registered in box_types." + type_cls = box_types[type_name] + elif issubclass(box_type, BaseBoxes): + assert box_type in _box_type_to_name, \ + f"Box type {box_type} hasn't been registered in box_types." + type_name = _box_type_to_name[box_type] + type_cls = box_type + else: + raise KeyError('box_type must be a str or class inheriting from ' + f'BaseBoxes, but got {type(box_type)}.') + return type_name, type_cls + + +def convert_box_type(boxes: BoxType, + *, + src_type: Union[str, type] = None, + dst_type: Union[str, type] = None) -> BoxType: + """Convert boxes from source type to destination type. + + If ``boxes`` is a instance of BaseBoxes, the ``src_type`` will be set + as the type of ``boxes``. + + Args: + boxes (np.ndarray or Tensor or :obj:`BaseBoxes`): boxes need to + convert. + src_type (str or type, Optional): source box type. Defaults to None. + dst_type (str or type, Optional): destination box type. Defaults to + None. + + Returns: + Union[np.ndarray, Tensor, :obj:`BaseBoxes`]: Converted boxes. It's type + is consistent with the input's type. + """ + assert dst_type is not None + dst_type_name, dst_type_cls = get_box_type(dst_type) + + is_box_cls = False + is_numpy = False + if isinstance(boxes, BaseBoxes): + src_type_name, _ = get_box_type(type(boxes)) + is_box_cls = True + elif isinstance(boxes, (Tensor, np.ndarray)): + assert src_type is not None + src_type_name, _ = get_box_type(src_type) + if isinstance(boxes, np.ndarray): + is_numpy = True + else: + raise TypeError('boxes must be a instance of BaseBoxes, Tensor or ' + f'ndarray, but get {type(boxes)}.') + + if src_type_name == dst_type_name: + return boxes + + converter_name = src_type_name + '2' + dst_type_name + assert converter_name in box_converters, \ + "Convert function hasn't been registered in box_converters." + converter = box_converters[converter_name] + + if is_box_cls: + boxes = converter(boxes.tensor) + return dst_type_cls(boxes) + elif is_numpy: + boxes = converter(torch.from_numpy(boxes)) + return boxes.numpy() + else: + return converter(boxes) + + +def autocast_box_type(dst_box_type='hbox') -> Callable: + """A decorator which automatically casts results['gt_bboxes'] to the + destination box type. + + It commenly used in mmdet.datasets.transforms to make the transforms up- + compatible with the np.ndarray type of results['gt_bboxes']. + + The speed of processing of np.ndarray and BaseBoxes data are the same: + + - np.ndarray: 0.0509 img/s + - BaseBoxes: 0.0551 img/s + + Args: + dst_box_type (str): Destination box type. + """ + _, box_type_cls = get_box_type(dst_box_type) + + def decorator(func: Callable) -> Callable: + + def wrapper(self, results: dict, *args, **kwargs) -> dict: + if ('gt_bboxes' not in results + or isinstance(results['gt_bboxes'], BaseBoxes)): + return func(self, results) + elif isinstance(results['gt_bboxes'], np.ndarray): + results['gt_bboxes'] = box_type_cls( + results['gt_bboxes'], clone=False) + if 'mix_results' in results: + for res in results['mix_results']: + if isinstance(res['gt_bboxes'], np.ndarray): + res['gt_bboxes'] = box_type_cls( + res['gt_bboxes'], clone=False) + + _results = func(self, results, *args, **kwargs) + + # In some cases, the function will process gt_bboxes in-place + # Simultaneously convert inputting and outputting gt_bboxes + # back to np.ndarray + if isinstance(_results, dict) and 'gt_bboxes' in _results: + if isinstance(_results['gt_bboxes'], BaseBoxes): + _results['gt_bboxes'] = _results['gt_bboxes'].numpy() + if isinstance(results['gt_bboxes'], BaseBoxes): + results['gt_bboxes'] = results['gt_bboxes'].numpy() + return _results + else: + raise TypeError( + "auto_box_type requires results['gt_bboxes'] to " + 'be BaseBoxes or np.ndarray, but got ' + f"{type(results['gt_bboxes'])}") + + return wrapper + + return decorator diff --git a/mmdetection/mmdet/structures/bbox/horizontal_boxes.py b/mmdetection/mmdet/structures/bbox/horizontal_boxes.py new file mode 100644 index 00000000..b3a78518 --- /dev/null +++ b/mmdetection/mmdet/structures/bbox/horizontal_boxes.py @@ -0,0 +1,432 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, TypeVar, Union + +import cv2 +import numpy as np +import torch +from torch import BoolTensor, Tensor + +from mmdet.structures.mask.structures import BitmapMasks, PolygonMasks +from .base_boxes import BaseBoxes +from .bbox_overlaps import bbox_overlaps +from .box_type import register_box + +T = TypeVar('T') +DeviceType = Union[str, torch.device] +MaskType = Union[BitmapMasks, PolygonMasks] + + +@register_box(name='hbox') +class HorizontalBoxes(BaseBoxes): + """The horizontal box class used in MMDetection by default. + + The ``box_dim`` of ``HorizontalBoxes`` is 4, which means the length of + the last dimension of the data should be 4. Two modes of box data are + supported in ``HorizontalBoxes``: + + - 'xyxy': Each row of data indicates (x1, y1, x2, y2), which are the + coordinates of the left-top and right-bottom points. + - 'cxcywh': Each row of data indicates (x, y, w, h), where (x, y) are the + coordinates of the box centers and (w, h) are the width and height. + + ``HorizontalBoxes`` only restores 'xyxy' mode of data. If the the data is + in 'cxcywh' mode, users need to input ``in_mode='cxcywh'`` and The code + will convert the 'cxcywh' data to 'xyxy' automatically. + + Args: + data (Tensor or np.ndarray or Sequence): The box data with shape of + (..., 4). + dtype (torch.dtype, Optional): data type of boxes. Defaults to None. + device (str or torch.device, Optional): device of boxes. + Default to None. + clone (bool): Whether clone ``boxes`` or not. Defaults to True. + mode (str, Optional): the mode of boxes. If it is 'cxcywh', the + `data` will be converted to 'xyxy' mode. Defaults to None. + """ + + box_dim: int = 4 + + def __init__(self, + data: Union[Tensor, np.ndarray], + dtype: torch.dtype = None, + device: DeviceType = None, + clone: bool = True, + in_mode: Optional[str] = None) -> None: + super().__init__(data=data, dtype=dtype, device=device, clone=clone) + if isinstance(in_mode, str): + if in_mode not in ('xyxy', 'cxcywh'): + raise ValueError(f'Get invalid mode {in_mode}.') + if in_mode == 'cxcywh': + self.tensor = self.cxcywh_to_xyxy(self.tensor) + + @staticmethod + def cxcywh_to_xyxy(boxes: Tensor) -> Tensor: + """Convert box coordinates from (cx, cy, w, h) to (x1, y1, x2, y2). + + Args: + boxes (Tensor): cxcywh boxes tensor with shape of (..., 4). + + Returns: + Tensor: xyxy boxes tensor with shape of (..., 4). + """ + ctr, wh = boxes.split((2, 2), dim=-1) + return torch.cat([(ctr - wh / 2), (ctr + wh / 2)], dim=-1) + + @staticmethod + def xyxy_to_cxcywh(boxes: Tensor) -> Tensor: + """Convert box coordinates from (x1, y1, x2, y2) to (cx, cy, w, h). + + Args: + boxes (Tensor): xyxy boxes tensor with shape of (..., 4). + + Returns: + Tensor: cxcywh boxes tensor with shape of (..., 4). + """ + xy1, xy2 = boxes.split((2, 2), dim=-1) + return torch.cat([(xy2 + xy1) / 2, (xy2 - xy1)], dim=-1) + + @property + def cxcywh(self) -> Tensor: + """Return a tensor representing the cxcywh boxes.""" + return self.xyxy_to_cxcywh(self.tensor) + + @property + def centers(self) -> Tensor: + """Return a tensor representing the centers of boxes.""" + boxes = self.tensor + return (boxes[..., :2] + boxes[..., 2:]) / 2 + + @property + def areas(self) -> Tensor: + """Return a tensor representing the areas of boxes.""" + boxes = self.tensor + return (boxes[..., 2] - boxes[..., 0]) * ( + boxes[..., 3] - boxes[..., 1]) + + @property + def widths(self) -> Tensor: + """Return a tensor representing the widths of boxes.""" + boxes = self.tensor + return boxes[..., 2] - boxes[..., 0] + + @property + def heights(self) -> Tensor: + """Return a tensor representing the heights of boxes.""" + boxes = self.tensor + return boxes[..., 3] - boxes[..., 1] + + def flip_(self, + img_shape: Tuple[int, int], + direction: str = 'horizontal') -> None: + """Flip boxes horizontally or vertically in-place. + + Args: + img_shape (Tuple[int, int]): A tuple of image height and width. + direction (str): Flip direction, options are "horizontal", + "vertical" and "diagonal". Defaults to "horizontal" + """ + assert direction in ['horizontal', 'vertical', 'diagonal'] + flipped = self.tensor + boxes = flipped.clone() + if direction == 'horizontal': + flipped[..., 0] = img_shape[1] - boxes[..., 2] + flipped[..., 2] = img_shape[1] - boxes[..., 0] + elif direction == 'vertical': + flipped[..., 1] = img_shape[0] - boxes[..., 3] + flipped[..., 3] = img_shape[0] - boxes[..., 1] + else: + flipped[..., 0] = img_shape[1] - boxes[..., 2] + flipped[..., 1] = img_shape[0] - boxes[..., 3] + flipped[..., 2] = img_shape[1] - boxes[..., 0] + flipped[..., 3] = img_shape[0] - boxes[..., 1] + + def translate_(self, distances: Tuple[float, float]) -> None: + """Translate boxes in-place. + + Args: + distances (Tuple[float, float]): translate distances. The first + is horizontal distance and the second is vertical distance. + """ + boxes = self.tensor + assert len(distances) == 2 + self.tensor = boxes + boxes.new_tensor(distances).repeat(2) + + def clip_(self, img_shape: Tuple[int, int]) -> None: + """Clip boxes according to the image shape in-place. + + Args: + img_shape (Tuple[int, int]): A tuple of image height and width. + """ + boxes = self.tensor + boxes[..., 0::2] = boxes[..., 0::2].clamp(0, img_shape[1]) + boxes[..., 1::2] = boxes[..., 1::2].clamp(0, img_shape[0]) + + def rotate_(self, center: Tuple[float, float], angle: float) -> None: + """Rotate all boxes in-place. + + Args: + center (Tuple[float, float]): Rotation origin. + angle (float): Rotation angle represented in degrees. Positive + values mean clockwise rotation. + """ + boxes = self.tensor + rotation_matrix = boxes.new_tensor( + cv2.getRotationMatrix2D(center, -angle, 1)) + + corners = self.hbox2corner(boxes) + corners = torch.cat( + [corners, corners.new_ones(*corners.shape[:-1], 1)], dim=-1) + corners_T = torch.transpose(corners, -1, -2) + corners_T = torch.matmul(rotation_matrix, corners_T) + corners = torch.transpose(corners_T, -1, -2) + self.tensor = self.corner2hbox(corners) + + def project_(self, homography_matrix: Union[Tensor, np.ndarray]) -> None: + """Geometric transformat boxes in-place. + + Args: + homography_matrix (Tensor or np.ndarray]): + Shape (3, 3) for geometric transformation. + """ + boxes = self.tensor + if isinstance(homography_matrix, np.ndarray): + homography_matrix = boxes.new_tensor(homography_matrix) + corners = self.hbox2corner(boxes) + corners = torch.cat( + [corners, corners.new_ones(*corners.shape[:-1], 1)], dim=-1) + corners_T = torch.transpose(corners, -1, -2) + corners_T = torch.matmul(homography_matrix, corners_T) + corners = torch.transpose(corners_T, -1, -2) + # Convert to homogeneous coordinates by normalization + corners = corners[..., :2] / corners[..., 2:3] + self.tensor = self.corner2hbox(corners) + + @staticmethod + def hbox2corner(boxes: Tensor) -> Tensor: + """Convert box coordinates from (x1, y1, x2, y2) to corners ((x1, y1), + (x2, y1), (x1, y2), (x2, y2)). + + Args: + boxes (Tensor): Horizontal box tensor with shape of (..., 4). + + Returns: + Tensor: Corner tensor with shape of (..., 4, 2). + """ + x1, y1, x2, y2 = torch.split(boxes, 1, dim=-1) + corners = torch.cat([x1, y1, x2, y1, x1, y2, x2, y2], dim=-1) + return corners.reshape(*corners.shape[:-1], 4, 2) + + @staticmethod + def corner2hbox(corners: Tensor) -> Tensor: + """Convert box coordinates from corners ((x1, y1), (x2, y1), (x1, y2), + (x2, y2)) to (x1, y1, x2, y2). + + Args: + corners (Tensor): Corner tensor with shape of (..., 4, 2). + + Returns: + Tensor: Horizontal box tensor with shape of (..., 4). + """ + if corners.numel() == 0: + return corners.new_zeros((0, 4)) + min_xy = corners.min(dim=-2)[0] + max_xy = corners.max(dim=-2)[0] + return torch.cat([min_xy, max_xy], dim=-1) + + def rescale_(self, scale_factor: Tuple[float, float]) -> None: + """Rescale boxes w.r.t. rescale_factor in-place. + + Note: + Both ``rescale_`` and ``resize_`` will enlarge or shrink boxes + w.r.t ``scale_facotr``. The difference is that ``resize_`` only + changes the width and the height of boxes, but ``rescale_`` also + rescales the box centers simultaneously. + + Args: + scale_factor (Tuple[float, float]): factors for scaling boxes. + The length should be 2. + """ + boxes = self.tensor + assert len(scale_factor) == 2 + scale_factor = boxes.new_tensor(scale_factor).repeat(2) + self.tensor = boxes * scale_factor + + def resize_(self, scale_factor: Tuple[float, float]) -> None: + """Resize the box width and height w.r.t scale_factor in-place. + + Note: + Both ``rescale_`` and ``resize_`` will enlarge or shrink boxes + w.r.t ``scale_facotr``. The difference is that ``resize_`` only + changes the width and the height of boxes, but ``rescale_`` also + rescales the box centers simultaneously. + + Args: + scale_factor (Tuple[float, float]): factors for scaling box + shapes. The length should be 2. + """ + boxes = self.tensor + assert len(scale_factor) == 2 + ctrs = (boxes[..., 2:] + boxes[..., :2]) / 2 + wh = boxes[..., 2:] - boxes[..., :2] + scale_factor = boxes.new_tensor(scale_factor) + wh = wh * scale_factor + xy1 = ctrs - 0.5 * wh + xy2 = ctrs + 0.5 * wh + self.tensor = torch.cat([xy1, xy2], dim=-1) + + def is_inside(self, + img_shape: Tuple[int, int], + all_inside: bool = False, + allowed_border: int = 0) -> BoolTensor: + """Find boxes inside the image. + + Args: + img_shape (Tuple[int, int]): A tuple of image height and width. + all_inside (bool): Whether the boxes are all inside the image or + part inside the image. Defaults to False. + allowed_border (int): Boxes that extend beyond the image shape + boundary by more than ``allowed_border`` are considered + "outside" Defaults to 0. + Returns: + BoolTensor: A BoolTensor indicating whether the box is inside + the image. Assuming the original boxes have shape (m, n, 4), + the output has shape (m, n). + """ + img_h, img_w = img_shape + boxes = self.tensor + if all_inside: + return (boxes[:, 0] >= -allowed_border) & \ + (boxes[:, 1] >= -allowed_border) & \ + (boxes[:, 2] < img_w + allowed_border) & \ + (boxes[:, 3] < img_h + allowed_border) + else: + return (boxes[..., 0] < img_w + allowed_border) & \ + (boxes[..., 1] < img_h + allowed_border) & \ + (boxes[..., 2] > -allowed_border) & \ + (boxes[..., 3] > -allowed_border) + + def find_inside_points(self, + points: Tensor, + is_aligned: bool = False) -> BoolTensor: + """Find inside box points. Boxes dimension must be 2. + + Args: + points (Tensor): Points coordinates. Has shape of (m, 2). + is_aligned (bool): Whether ``points`` has been aligned with boxes + or not. If True, the length of boxes and ``points`` should be + the same. Defaults to False. + + Returns: + BoolTensor: A BoolTensor indicating whether a point is inside + boxes. Assuming the boxes has shape of (n, 4), if ``is_aligned`` + is False. The index has shape of (m, n). If ``is_aligned`` is + True, m should be equal to n and the index has shape of (m, ). + """ + boxes = self.tensor + assert boxes.dim() == 2, 'boxes dimension must be 2.' + + if not is_aligned: + boxes = boxes[None, :, :] + points = points[:, None, :] + else: + assert boxes.size(0) == points.size(0) + + x_min, y_min, x_max, y_max = boxes.unbind(dim=-1) + return (points[..., 0] >= x_min) & (points[..., 0] <= x_max) & \ + (points[..., 1] >= y_min) & (points[..., 1] <= y_max) + + def create_masks(self, img_shape: Tuple[int, int]) -> BitmapMasks: + """ + Args: + img_shape (Tuple[int, int]): A tuple of image height and width. + + Returns: + :obj:`BitmapMasks`: Converted masks + """ + img_h, img_w = img_shape + boxes = self.tensor + + xmin, ymin = boxes[:, 0:1], boxes[:, 1:2] + xmax, ymax = boxes[:, 2:3], boxes[:, 3:4] + gt_masks = np.zeros((len(boxes), img_h, img_w), dtype=np.uint8) + for i in range(len(boxes)): + gt_masks[i, + int(ymin[i]):int(ymax[i]), + int(xmin[i]):int(xmax[i])] = 1 + return BitmapMasks(gt_masks, img_h, img_w) + + @staticmethod + def overlaps(boxes1: BaseBoxes, + boxes2: BaseBoxes, + mode: str = 'iou', + is_aligned: bool = False, + eps: float = 1e-6) -> Tensor: + """Calculate overlap between two set of boxes with their types + converted to ``HorizontalBoxes``. + + Args: + boxes1 (:obj:`BaseBoxes`): BaseBoxes with shape of (m, box_dim) + or empty. + boxes2 (:obj:`BaseBoxes`): BaseBoxes with shape of (n, box_dim) + or empty. + mode (str): "iou" (intersection over union), "iof" (intersection + over foreground). Defaults to "iou". + is_aligned (bool): If True, then m and n must be equal. Defaults + to False. + eps (float): A value added to the denominator for numerical + stability. Defaults to 1e-6. + + Returns: + Tensor: shape (m, n) if ``is_aligned`` is False else shape (m,) + """ + boxes1 = boxes1.convert_to('hbox') + boxes2 = boxes2.convert_to('hbox') + return bbox_overlaps( + boxes1.tensor, + boxes2.tensor, + mode=mode, + is_aligned=is_aligned, + eps=eps) + + @staticmethod + def from_instance_masks(masks: MaskType) -> 'HorizontalBoxes': + """Create horizontal boxes from instance masks. + + Args: + masks (:obj:`BitmapMasks` or :obj:`PolygonMasks`): BitmapMasks or + PolygonMasks instance with length of n. + + Returns: + :obj:`HorizontalBoxes`: Converted boxes with shape of (n, 4). + """ + num_masks = len(masks) + boxes = np.zeros((num_masks, 4), dtype=np.float32) + if isinstance(masks, BitmapMasks): + x_any = masks.masks.any(axis=1) + y_any = masks.masks.any(axis=2) + for idx in range(num_masks): + x = np.where(x_any[idx, :])[0] + y = np.where(y_any[idx, :])[0] + if len(x) > 0 and len(y) > 0: + # use +1 for x_max and y_max so that the right and bottom + # boundary of instance masks are fully included by the box + boxes[idx, :] = np.array( + [x[0], y[0], x[-1] + 1, y[-1] + 1], dtype=np.float32) + elif isinstance(masks, PolygonMasks): + for idx, poly_per_obj in enumerate(masks.masks): + # simply use a number that is big enough for comparison with + # coordinates + xy_min = np.array([masks.width * 2, masks.height * 2], + dtype=np.float32) + xy_max = np.zeros(2, dtype=np.float32) + for p in poly_per_obj: + xy = np.array(p).reshape(-1, 2).astype(np.float32) + xy_min = np.minimum(xy_min, np.min(xy, axis=0)) + xy_max = np.maximum(xy_max, np.max(xy, axis=0)) + boxes[idx, :2] = xy_min + boxes[idx, 2:] = xy_max + else: + raise TypeError( + '`masks` must be `BitmapMasks` or `PolygonMasks`, ' + f'but got {type(masks)}.') + return HorizontalBoxes(boxes) diff --git a/mmdetection/mmdet/structures/bbox/transforms.py b/mmdetection/mmdet/structures/bbox/transforms.py new file mode 100644 index 00000000..287e6aa6 --- /dev/null +++ b/mmdetection/mmdet/structures/bbox/transforms.py @@ -0,0 +1,498 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Sequence, Tuple, Union + +import numpy as np +import torch +from torch import Tensor + +from mmdet.structures.bbox import BaseBoxes + + +def find_inside_bboxes(bboxes: Tensor, img_h: int, img_w: int) -> Tensor: + """Find bboxes as long as a part of bboxes is inside the image. + + Args: + bboxes (Tensor): Shape (N, 4). + img_h (int): Image height. + img_w (int): Image width. + + Returns: + Tensor: Index of the remaining bboxes. + """ + inside_inds = (bboxes[:, 0] < img_w) & (bboxes[:, 2] > 0) \ + & (bboxes[:, 1] < img_h) & (bboxes[:, 3] > 0) + return inside_inds + + +def bbox_flip(bboxes: Tensor, + img_shape: Tuple[int], + direction: str = 'horizontal') -> Tensor: + """Flip bboxes horizontally or vertically. + + Args: + bboxes (Tensor): Shape (..., 4*k) + img_shape (Tuple[int]): Image shape. + direction (str): Flip direction, options are "horizontal", "vertical", + "diagonal". Default: "horizontal" + + Returns: + Tensor: Flipped bboxes. + """ + assert bboxes.shape[-1] % 4 == 0 + assert direction in ['horizontal', 'vertical', 'diagonal'] + flipped = bboxes.clone() + if direction == 'horizontal': + flipped[..., 0::4] = img_shape[1] - bboxes[..., 2::4] + flipped[..., 2::4] = img_shape[1] - bboxes[..., 0::4] + elif direction == 'vertical': + flipped[..., 1::4] = img_shape[0] - bboxes[..., 3::4] + flipped[..., 3::4] = img_shape[0] - bboxes[..., 1::4] + else: + flipped[..., 0::4] = img_shape[1] - bboxes[..., 2::4] + flipped[..., 1::4] = img_shape[0] - bboxes[..., 3::4] + flipped[..., 2::4] = img_shape[1] - bboxes[..., 0::4] + flipped[..., 3::4] = img_shape[0] - bboxes[..., 1::4] + return flipped + + +def bbox_mapping(bboxes: Tensor, + img_shape: Tuple[int], + scale_factor: Union[float, Tuple[float]], + flip: bool, + flip_direction: str = 'horizontal') -> Tensor: + """Map bboxes from the original image scale to testing scale.""" + new_bboxes = bboxes * bboxes.new_tensor(scale_factor) + if flip: + new_bboxes = bbox_flip(new_bboxes, img_shape, flip_direction) + return new_bboxes + + +def bbox_mapping_back(bboxes: Tensor, + img_shape: Tuple[int], + scale_factor: Union[float, Tuple[float]], + flip: bool, + flip_direction: str = 'horizontal') -> Tensor: + """Map bboxes from testing scale to original image scale.""" + new_bboxes = bbox_flip(bboxes, img_shape, + flip_direction) if flip else bboxes + new_bboxes = new_bboxes.view(-1, 4) / new_bboxes.new_tensor(scale_factor) + return new_bboxes.view(bboxes.shape) + + +def bbox2roi(bbox_list: List[Union[Tensor, BaseBoxes]]) -> Tensor: + """Convert a list of bboxes to roi format. + + Args: + bbox_list (List[Union[Tensor, :obj:`BaseBoxes`]): a list of bboxes + corresponding to a batch of images. + + Returns: + Tensor: shape (n, box_dim + 1), where ``box_dim`` depends on the + different box types. For example, If the box type in ``bbox_list`` + is HorizontalBoxes, the output shape is (n, 5). Each row of data + indicates [batch_ind, x1, y1, x2, y2]. + """ + rois_list = [] + for img_id, bboxes in enumerate(bbox_list): + bboxes = get_box_tensor(bboxes) + img_inds = bboxes.new_full((bboxes.size(0), 1), img_id) + rois = torch.cat([img_inds, bboxes], dim=-1) + rois_list.append(rois) + rois = torch.cat(rois_list, 0) + return rois + + +def roi2bbox(rois: Tensor) -> List[Tensor]: + """Convert rois to bounding box format. + + Args: + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + List[Tensor]: Converted boxes of corresponding rois. + """ + bbox_list = [] + img_ids = torch.unique(rois[:, 0].cpu(), sorted=True) + for img_id in img_ids: + inds = (rois[:, 0] == img_id.item()) + bbox = rois[inds, 1:] + bbox_list.append(bbox) + return bbox_list + + +# TODO remove later +def bbox2result(bboxes: Union[Tensor, np.ndarray], labels: Union[Tensor, + np.ndarray], + num_classes: int) -> List[np.ndarray]: + """Convert detection results to a list of numpy arrays. + + Args: + bboxes (Tensor | np.ndarray): shape (n, 5) + labels (Tensor | np.ndarray): shape (n, ) + num_classes (int): class number, including background class + + Returns: + List(np.ndarray]): bbox results of each class + """ + if bboxes.shape[0] == 0: + return [np.zeros((0, 5), dtype=np.float32) for i in range(num_classes)] + else: + if isinstance(bboxes, torch.Tensor): + bboxes = bboxes.detach().cpu().numpy() + labels = labels.detach().cpu().numpy() + return [bboxes[labels == i, :] for i in range(num_classes)] + + +def distance2bbox( + points: Tensor, + distance: Tensor, + max_shape: Optional[Union[Sequence[int], Tensor, + Sequence[Sequence[int]]]] = None +) -> Tensor: + """Decode distance prediction to bounding box. + + Args: + points (Tensor): Shape (B, N, 2) or (N, 2). + distance (Tensor): Distance from the given point to 4 + boundaries (left, top, right, bottom). Shape (B, N, 4) or (N, 4) + max_shape (Union[Sequence[int], Tensor, Sequence[Sequence[int]]], + optional): Maximum bounds for boxes, specifies + (H, W, C) or (H, W). If priors shape is (B, N, 4), then + the max_shape should be a Sequence[Sequence[int]] + and the length of max_shape should also be B. + + Returns: + Tensor: Boxes with shape (N, 4) or (B, N, 4) + """ + + x1 = points[..., 0] - distance[..., 0] + y1 = points[..., 1] - distance[..., 1] + x2 = points[..., 0] + distance[..., 2] + y2 = points[..., 1] + distance[..., 3] + + bboxes = torch.stack([x1, y1, x2, y2], -1) + + if max_shape is not None: + if bboxes.dim() == 2 and not torch.onnx.is_in_onnx_export(): + # speed up + bboxes[:, 0::2].clamp_(min=0, max=max_shape[1]) + bboxes[:, 1::2].clamp_(min=0, max=max_shape[0]) + return bboxes + + # clip bboxes with dynamic `min` and `max` for onnx + if torch.onnx.is_in_onnx_export(): + # TODO: delete + from mmdet.core.export import dynamic_clip_for_onnx + x1, y1, x2, y2 = dynamic_clip_for_onnx(x1, y1, x2, y2, max_shape) + bboxes = torch.stack([x1, y1, x2, y2], dim=-1) + return bboxes + if not isinstance(max_shape, torch.Tensor): + max_shape = x1.new_tensor(max_shape) + max_shape = max_shape[..., :2].type_as(x1) + if max_shape.ndim == 2: + assert bboxes.ndim == 3 + assert max_shape.size(0) == bboxes.size(0) + + min_xy = x1.new_tensor(0) + max_xy = torch.cat([max_shape, max_shape], + dim=-1).flip(-1).unsqueeze(-2) + bboxes = torch.where(bboxes < min_xy, min_xy, bboxes) + bboxes = torch.where(bboxes > max_xy, max_xy, bboxes) + + return bboxes + + +def bbox2distance(points: Tensor, + bbox: Tensor, + max_dis: Optional[float] = None, + eps: float = 0.1) -> Tensor: + """Decode bounding box based on distances. + + Args: + points (Tensor): Shape (n, 2) or (b, n, 2), [x, y]. + bbox (Tensor): Shape (n, 4) or (b, n, 4), "xyxy" format + max_dis (float, optional): Upper bound of the distance. + eps (float): a small value to ensure target < max_dis, instead <= + + Returns: + Tensor: Decoded distances. + """ + left = points[..., 0] - bbox[..., 0] + top = points[..., 1] - bbox[..., 1] + right = bbox[..., 2] - points[..., 0] + bottom = bbox[..., 3] - points[..., 1] + if max_dis is not None: + left = left.clamp(min=0, max=max_dis - eps) + top = top.clamp(min=0, max=max_dis - eps) + right = right.clamp(min=0, max=max_dis - eps) + bottom = bottom.clamp(min=0, max=max_dis - eps) + return torch.stack([left, top, right, bottom], -1) + + +def bbox_rescale(bboxes: Tensor, scale_factor: float = 1.0) -> Tensor: + """Rescale bounding box w.r.t. scale_factor. + + Args: + bboxes (Tensor): Shape (n, 4) for bboxes or (n, 5) for rois + scale_factor (float): rescale factor + + Returns: + Tensor: Rescaled bboxes. + """ + if bboxes.size(1) == 5: + bboxes_ = bboxes[:, 1:] + inds_ = bboxes[:, 0] + else: + bboxes_ = bboxes + cx = (bboxes_[:, 0] + bboxes_[:, 2]) * 0.5 + cy = (bboxes_[:, 1] + bboxes_[:, 3]) * 0.5 + w = bboxes_[:, 2] - bboxes_[:, 0] + h = bboxes_[:, 3] - bboxes_[:, 1] + w = w * scale_factor + h = h * scale_factor + x1 = cx - 0.5 * w + x2 = cx + 0.5 * w + y1 = cy - 0.5 * h + y2 = cy + 0.5 * h + if bboxes.size(1) == 5: + rescaled_bboxes = torch.stack([inds_, x1, y1, x2, y2], dim=-1) + else: + rescaled_bboxes = torch.stack([x1, y1, x2, y2], dim=-1) + return rescaled_bboxes + + +def bbox_cxcywh_to_xyxy(bbox: Tensor) -> Tensor: + """Convert bbox coordinates from (cx, cy, w, h) to (x1, y1, x2, y2). + + Args: + bbox (Tensor): Shape (n, 4) for bboxes. + + Returns: + Tensor: Converted bboxes. + """ + cx, cy, w, h = bbox.split((1, 1, 1, 1), dim=-1) + bbox_new = [(cx - 0.5 * w), (cy - 0.5 * h), (cx + 0.5 * w), (cy + 0.5 * h)] + return torch.cat(bbox_new, dim=-1) + + +def bbox_xyxy_to_cxcywh(bbox: Tensor) -> Tensor: + """Convert bbox coordinates from (x1, y1, x2, y2) to (cx, cy, w, h). + + Args: + bbox (Tensor): Shape (n, 4) for bboxes. + + Returns: + Tensor: Converted bboxes. + """ + x1, y1, x2, y2 = bbox.split((1, 1, 1, 1), dim=-1) + bbox_new = [(x1 + x2) / 2, (y1 + y2) / 2, (x2 - x1), (y2 - y1)] + return torch.cat(bbox_new, dim=-1) + + +def bbox2corner(bboxes: torch.Tensor) -> torch.Tensor: + """Convert bbox coordinates from (x1, y1, x2, y2) to corners ((x1, y1), + (x2, y1), (x1, y2), (x2, y2)). + + Args: + bboxes (Tensor): Shape (n, 4) for bboxes. + Returns: + Tensor: Shape (n*4, 2) for corners. + """ + x1, y1, x2, y2 = torch.split(bboxes, 1, dim=1) + return torch.cat([x1, y1, x2, y1, x1, y2, x2, y2], dim=1).reshape(-1, 2) + + +def corner2bbox(corners: torch.Tensor) -> torch.Tensor: + """Convert bbox coordinates from corners ((x1, y1), (x2, y1), (x1, y2), + (x2, y2)) to (x1, y1, x2, y2). + + Args: + corners (Tensor): Shape (n*4, 2) for corners. + Returns: + Tensor: Shape (n, 4) for bboxes. + """ + corners = corners.reshape(-1, 4, 2) + min_xy = corners.min(dim=1)[0] + max_xy = corners.max(dim=1)[0] + return torch.cat([min_xy, max_xy], dim=1) + + +def bbox_project( + bboxes: Union[torch.Tensor, np.ndarray], + homography_matrix: Union[torch.Tensor, np.ndarray], + img_shape: Optional[Tuple[int, int]] = None +) -> Union[torch.Tensor, np.ndarray]: + """Geometric transformation for bbox. + + Args: + bboxes (Union[torch.Tensor, np.ndarray]): Shape (n, 4) for bboxes. + homography_matrix (Union[torch.Tensor, np.ndarray]): + Shape (3, 3) for geometric transformation. + img_shape (Tuple[int, int], optional): Image shape. Defaults to None. + Returns: + Union[torch.Tensor, np.ndarray]: Converted bboxes. + """ + bboxes_type = type(bboxes) + if bboxes_type is np.ndarray: + bboxes = torch.from_numpy(bboxes) + if isinstance(homography_matrix, np.ndarray): + homography_matrix = torch.from_numpy(homography_matrix) + corners = bbox2corner(bboxes) + corners = torch.cat( + [corners, corners.new_ones(corners.shape[0], 1)], dim=1) + corners = torch.matmul(homography_matrix, corners.t()).t() + # Convert to homogeneous coordinates by normalization + corners = corners[:, :2] / corners[:, 2:3] + bboxes = corner2bbox(corners) + if img_shape is not None: + bboxes[:, 0::2] = bboxes[:, 0::2].clamp(0, img_shape[1]) + bboxes[:, 1::2] = bboxes[:, 1::2].clamp(0, img_shape[0]) + if bboxes_type is np.ndarray: + bboxes = bboxes.numpy() + return bboxes + + +def cat_boxes(data_list: List[Union[Tensor, BaseBoxes]], + dim: int = 0) -> Union[Tensor, BaseBoxes]: + """Concatenate boxes with type of tensor or box type. + + Args: + data_list (List[Union[Tensor, :obj:`BaseBoxes`]]): A list of tensors + or box types need to be concatenated. + dim (int): The dimension over which the box are concatenated. + Defaults to 0. + + Returns: + Union[Tensor, :obj`BaseBoxes`]: Concatenated results. + """ + if data_list and isinstance(data_list[0], BaseBoxes): + return data_list[0].cat(data_list, dim=dim) + else: + return torch.cat(data_list, dim=dim) + + +def stack_boxes(data_list: List[Union[Tensor, BaseBoxes]], + dim: int = 0) -> Union[Tensor, BaseBoxes]: + """Stack boxes with type of tensor or box type. + + Args: + data_list (List[Union[Tensor, :obj:`BaseBoxes`]]): A list of tensors + or box types need to be stacked. + dim (int): The dimension over which the box are stacked. + Defaults to 0. + + Returns: + Union[Tensor, :obj`BaseBoxes`]: Stacked results. + """ + if data_list and isinstance(data_list[0], BaseBoxes): + return data_list[0].stack(data_list, dim=dim) + else: + return torch.stack(data_list, dim=dim) + + +def scale_boxes(boxes: Union[Tensor, BaseBoxes], + scale_factor: Tuple[float, float]) -> Union[Tensor, BaseBoxes]: + """Scale boxes with type of tensor or box type. + + Args: + boxes (Tensor or :obj:`BaseBoxes`): boxes need to be scaled. Its type + can be a tensor or a box type. + scale_factor (Tuple[float, float]): factors for scaling boxes. + The length should be 2. + + Returns: + Union[Tensor, :obj:`BaseBoxes`]: Scaled boxes. + """ + if isinstance(boxes, BaseBoxes): + boxes.rescale_(scale_factor) + return boxes + else: + # Tensor boxes will be treated as horizontal boxes + repeat_num = int(boxes.size(-1) / 2) + scale_factor = boxes.new_tensor(scale_factor).repeat((1, repeat_num)) + return boxes * scale_factor + + +def get_box_wh(boxes: Union[Tensor, BaseBoxes]) -> Tuple[Tensor, Tensor]: + """Get the width and height of boxes with type of tensor or box type. + + Args: + boxes (Tensor or :obj:`BaseBoxes`): boxes with type of tensor + or box type. + + Returns: + Tuple[Tensor, Tensor]: the width and height of boxes. + """ + if isinstance(boxes, BaseBoxes): + w = boxes.widths + h = boxes.heights + else: + # Tensor boxes will be treated as horizontal boxes by defaults + w = boxes[:, 2] - boxes[:, 0] + h = boxes[:, 3] - boxes[:, 1] + return w, h + + +def get_box_tensor(boxes: Union[Tensor, BaseBoxes]) -> Tensor: + """Get tensor data from box type boxes. + + Args: + boxes (Tensor or BaseBoxes): boxes with type of tensor or box type. + If its type is a tensor, the boxes will be directly returned. + If its type is a box type, the `boxes.tensor` will be returned. + + Returns: + Tensor: boxes tensor. + """ + if isinstance(boxes, BaseBoxes): + boxes = boxes.tensor + return boxes + + +def empty_box_as(boxes: Union[Tensor, BaseBoxes]) -> Union[Tensor, BaseBoxes]: + """Generate empty box according to input ``boxes` type and device. + + Args: + boxes (Tensor or :obj:`BaseBoxes`): boxes with type of tensor + or box type. + + Returns: + Union[Tensor, BaseBoxes]: Generated empty box. + """ + if isinstance(boxes, BaseBoxes): + return boxes.empty_boxes() + else: + # Tensor boxes will be treated as horizontal boxes by defaults + return boxes.new_zeros(0, 4) + + +def bbox_xyxy_to_cxcyah(bboxes: torch.Tensor) -> torch.Tensor: + """Convert bbox coordinates from (x1, y1, x2, y2) to (cx, cy, ratio, h). + + Args: + bbox (Tensor): Shape (n, 4) for bboxes. + + Returns: + Tensor: Converted bboxes. + """ + cx = (bboxes[:, 2] + bboxes[:, 0]) / 2 + cy = (bboxes[:, 3] + bboxes[:, 1]) / 2 + w = bboxes[:, 2] - bboxes[:, 0] + h = bboxes[:, 3] - bboxes[:, 1] + xyah = torch.stack([cx, cy, w / h, h], -1) + return xyah + + +def bbox_cxcyah_to_xyxy(bboxes: torch.Tensor) -> torch.Tensor: + """Convert bbox coordinates from (cx, cy, ratio, h) to (x1, y1, x2, y2). + + Args: + bbox (Tensor): Shape (n, 4) for bboxes. + Returns: + Tensor: Converted bboxes. + """ + cx, cy, ratio, h = bboxes.split((1, 1, 1, 1), dim=-1) + w = ratio * h + x1y1x2y2 = [cx - w / 2.0, cy - h / 2.0, cx + w / 2.0, cy + h / 2.0] + return torch.cat(x1y1x2y2, dim=-1) diff --git a/mmdetection/mmdet/structures/det_data_sample.py b/mmdetection/mmdet/structures/det_data_sample.py new file mode 100644 index 00000000..37dd7472 --- /dev/null +++ b/mmdetection/mmdet/structures/det_data_sample.py @@ -0,0 +1,237 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional + +from mmengine.structures import BaseDataElement, InstanceData, PixelData + + +class DetDataSample(BaseDataElement): + """A data structure interface of MMDetection. They are used as interfaces + between different components. + + The attributes in ``DetDataSample`` are divided into several parts: + + - ``proposals``(InstanceData): Region proposals used in two-stage + detectors. + - ``gt_instances``(InstanceData): Ground truth of instance annotations. + - ``pred_instances``(InstanceData): Instances of detection predictions. + - ``pred_track_instances``(InstanceData): Instances of tracking + predictions. + - ``ignored_instances``(InstanceData): Instances to be ignored during + training/testing. + - ``gt_panoptic_seg``(PixelData): Ground truth of panoptic + segmentation. + - ``pred_panoptic_seg``(PixelData): Prediction of panoptic + segmentation. + - ``gt_sem_seg``(PixelData): Ground truth of semantic segmentation. + - ``pred_sem_seg``(PixelData): Prediction of semantic segmentation. + + Examples: + >>> import torch + >>> import numpy as np + >>> from mmengine.structures import InstanceData + >>> from mmdet.structures import DetDataSample + + >>> data_sample = DetDataSample() + >>> img_meta = dict(img_shape=(800, 1196), + ... pad_shape=(800, 1216)) + >>> gt_instances = InstanceData(metainfo=img_meta) + >>> gt_instances.bboxes = torch.rand((5, 4)) + >>> gt_instances.labels = torch.rand((5,)) + >>> data_sample.gt_instances = gt_instances + >>> assert 'img_shape' in data_sample.gt_instances.metainfo_keys() + >>> len(data_sample.gt_instances) + 5 + >>> print(data_sample) + + ) at 0x7f21fb1b9880> + >>> pred_instances = InstanceData(metainfo=img_meta) + >>> pred_instances.bboxes = torch.rand((5, 4)) + >>> pred_instances.scores = torch.rand((5,)) + >>> data_sample = DetDataSample(pred_instances=pred_instances) + >>> assert 'pred_instances' in data_sample + + >>> pred_track_instances = InstanceData(metainfo=img_meta) + >>> pred_track_instances.bboxes = torch.rand((5, 4)) + >>> pred_track_instances.scores = torch.rand((5,)) + >>> data_sample = DetDataSample( + ... pred_track_instances=pred_track_instances) + >>> assert 'pred_track_instances' in data_sample + + >>> data_sample = DetDataSample() + >>> gt_instances_data = dict( + ... bboxes=torch.rand(2, 4), + ... labels=torch.rand(2), + ... masks=np.random.rand(2, 2, 2)) + >>> gt_instances = InstanceData(**gt_instances_data) + >>> data_sample.gt_instances = gt_instances + >>> assert 'gt_instances' in data_sample + >>> assert 'masks' in data_sample.gt_instances + + >>> data_sample = DetDataSample() + >>> gt_panoptic_seg_data = dict(panoptic_seg=torch.rand(2, 4)) + >>> gt_panoptic_seg = PixelData(**gt_panoptic_seg_data) + >>> data_sample.gt_panoptic_seg = gt_panoptic_seg + >>> print(data_sample) + + gt_panoptic_seg: + ) at 0x7f66c2bb7280> + >>> data_sample = DetDataSample() + >>> gt_segm_seg_data = dict(segm_seg=torch.rand(2, 2, 2)) + >>> gt_segm_seg = PixelData(**gt_segm_seg_data) + >>> data_sample.gt_segm_seg = gt_segm_seg + >>> assert 'gt_segm_seg' in data_sample + >>> assert 'segm_seg' in data_sample.gt_segm_seg + """ + + @property + def proposals(self) -> InstanceData: + return self._proposals + + @proposals.setter + def proposals(self, value: InstanceData): + self.set_field(value, '_proposals', dtype=InstanceData) + + @proposals.deleter + def proposals(self): + del self._proposals + + @property + def gt_instances(self) -> InstanceData: + return self._gt_instances + + @gt_instances.setter + def gt_instances(self, value: InstanceData): + self.set_field(value, '_gt_instances', dtype=InstanceData) + + @gt_instances.deleter + def gt_instances(self): + del self._gt_instances + + @property + def pred_instances(self) -> InstanceData: + return self._pred_instances + + @pred_instances.setter + def pred_instances(self, value: InstanceData): + self.set_field(value, '_pred_instances', dtype=InstanceData) + + @pred_instances.deleter + def pred_instances(self): + del self._pred_instances + + # directly add ``pred_track_instances`` in ``DetDataSample`` + # so that the ``TrackDataSample`` does not bother to access the + # instance-level information. + @property + def pred_track_instances(self) -> InstanceData: + return self._pred_track_instances + + @pred_track_instances.setter + def pred_track_instances(self, value: InstanceData): + self.set_field(value, '_pred_track_instances', dtype=InstanceData) + + @pred_track_instances.deleter + def pred_track_instances(self): + del self._pred_track_instances + + @property + def ignored_instances(self) -> InstanceData: + return self._ignored_instances + + @ignored_instances.setter + def ignored_instances(self, value: InstanceData): + self.set_field(value, '_ignored_instances', dtype=InstanceData) + + @ignored_instances.deleter + def ignored_instances(self): + del self._ignored_instances + + @property + def gt_panoptic_seg(self) -> PixelData: + return self._gt_panoptic_seg + + @gt_panoptic_seg.setter + def gt_panoptic_seg(self, value: PixelData): + self.set_field(value, '_gt_panoptic_seg', dtype=PixelData) + + @gt_panoptic_seg.deleter + def gt_panoptic_seg(self): + del self._gt_panoptic_seg + + @property + def pred_panoptic_seg(self) -> PixelData: + return self._pred_panoptic_seg + + @pred_panoptic_seg.setter + def pred_panoptic_seg(self, value: PixelData): + self.set_field(value, '_pred_panoptic_seg', dtype=PixelData) + + @pred_panoptic_seg.deleter + def pred_panoptic_seg(self): + del self._pred_panoptic_seg + + @property + def gt_sem_seg(self) -> PixelData: + return self._gt_sem_seg + + @gt_sem_seg.setter + def gt_sem_seg(self, value: PixelData): + self.set_field(value, '_gt_sem_seg', dtype=PixelData) + + @gt_sem_seg.deleter + def gt_sem_seg(self): + del self._gt_sem_seg + + @property + def pred_sem_seg(self) -> PixelData: + return self._pred_sem_seg + + @pred_sem_seg.setter + def pred_sem_seg(self, value: PixelData): + self.set_field(value, '_pred_sem_seg', dtype=PixelData) + + @pred_sem_seg.deleter + def pred_sem_seg(self): + del self._pred_sem_seg + + +SampleList = List[DetDataSample] +OptSampleList = Optional[SampleList] diff --git a/mmdetection/mmdet/structures/mask/__init__.py b/mmdetection/mmdet/structures/mask/__init__.py new file mode 100644 index 00000000..f7839470 --- /dev/null +++ b/mmdetection/mmdet/structures/mask/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .mask_target import mask_target +from .structures import (BaseInstanceMasks, BitmapMasks, PolygonMasks, + bitmap_to_polygon, polygon_to_bitmap) +from .utils import encode_mask_results, mask2bbox, split_combined_polys + +__all__ = [ + 'split_combined_polys', 'mask_target', 'BaseInstanceMasks', 'BitmapMasks', + 'PolygonMasks', 'encode_mask_results', 'mask2bbox', 'polygon_to_bitmap', + 'bitmap_to_polygon' +] diff --git a/mmdetection/mmdet/structures/mask/mask_target.py b/mmdetection/mmdet/structures/mask/mask_target.py new file mode 100644 index 00000000..b2fc5f18 --- /dev/null +++ b/mmdetection/mmdet/structures/mask/mask_target.py @@ -0,0 +1,127 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np +import torch +from torch.nn.modules.utils import _pair + + +def mask_target(pos_proposals_list, pos_assigned_gt_inds_list, gt_masks_list, + cfg): + """Compute mask target for positive proposals in multiple images. + + Args: + pos_proposals_list (list[Tensor]): Positive proposals in multiple + images, each has shape (num_pos, 4). + pos_assigned_gt_inds_list (list[Tensor]): Assigned GT indices for each + positive proposals, each has shape (num_pos,). + gt_masks_list (list[:obj:`BaseInstanceMasks`]): Ground truth masks of + each image. + cfg (dict): Config dict that specifies the mask size. + + Returns: + Tensor: Mask target of each image, has shape (num_pos, w, h). + + Example: + >>> from mmengine.config import Config + >>> import mmdet + >>> from mmdet.data_elements.mask import BitmapMasks + >>> from mmdet.data_elements.mask.mask_target import * + >>> H, W = 17, 18 + >>> cfg = Config({'mask_size': (13, 14)}) + >>> rng = np.random.RandomState(0) + >>> # Positive proposals (tl_x, tl_y, br_x, br_y) for each image + >>> pos_proposals_list = [ + >>> torch.Tensor([ + >>> [ 7.2425, 5.5929, 13.9414, 14.9541], + >>> [ 7.3241, 3.6170, 16.3850, 15.3102], + >>> ]), + >>> torch.Tensor([ + >>> [ 4.8448, 6.4010, 7.0314, 9.7681], + >>> [ 5.9790, 2.6989, 7.4416, 4.8580], + >>> [ 0.0000, 0.0000, 0.1398, 9.8232], + >>> ]), + >>> ] + >>> # Corresponding class index for each proposal for each image + >>> pos_assigned_gt_inds_list = [ + >>> torch.LongTensor([7, 0]), + >>> torch.LongTensor([5, 4, 1]), + >>> ] + >>> # Ground truth mask for each true object for each image + >>> gt_masks_list = [ + >>> BitmapMasks(rng.rand(8, H, W), height=H, width=W), + >>> BitmapMasks(rng.rand(6, H, W), height=H, width=W), + >>> ] + >>> mask_targets = mask_target( + >>> pos_proposals_list, pos_assigned_gt_inds_list, + >>> gt_masks_list, cfg) + >>> assert mask_targets.shape == (5,) + cfg['mask_size'] + """ + cfg_list = [cfg for _ in range(len(pos_proposals_list))] + mask_targets = map(mask_target_single, pos_proposals_list, + pos_assigned_gt_inds_list, gt_masks_list, cfg_list) + mask_targets = list(mask_targets) + if len(mask_targets) > 0: + mask_targets = torch.cat(mask_targets) + return mask_targets + + +def mask_target_single(pos_proposals, pos_assigned_gt_inds, gt_masks, cfg): + """Compute mask target for each positive proposal in the image. + + Args: + pos_proposals (Tensor): Positive proposals. + pos_assigned_gt_inds (Tensor): Assigned GT inds of positive proposals. + gt_masks (:obj:`BaseInstanceMasks`): GT masks in the format of Bitmap + or Polygon. + cfg (dict): Config dict that indicate the mask size. + + Returns: + Tensor: Mask target of each positive proposals in the image. + + Example: + >>> from mmengine.config import Config + >>> import mmdet + >>> from mmdet.data_elements.mask import BitmapMasks + >>> from mmdet.data_elements.mask.mask_target import * # NOQA + >>> H, W = 32, 32 + >>> cfg = Config({'mask_size': (7, 11)}) + >>> rng = np.random.RandomState(0) + >>> # Masks for each ground truth box (relative to the image) + >>> gt_masks_data = rng.rand(3, H, W) + >>> gt_masks = BitmapMasks(gt_masks_data, height=H, width=W) + >>> # Predicted positive boxes in one image + >>> pos_proposals = torch.FloatTensor([ + >>> [ 16.2, 5.5, 19.9, 20.9], + >>> [ 17.3, 13.6, 19.3, 19.3], + >>> [ 14.8, 16.4, 17.0, 23.7], + >>> [ 0.0, 0.0, 16.0, 16.0], + >>> [ 4.0, 0.0, 20.0, 16.0], + >>> ]) + >>> # For each predicted proposal, its assignment to a gt mask + >>> pos_assigned_gt_inds = torch.LongTensor([0, 1, 2, 1, 1]) + >>> mask_targets = mask_target_single( + >>> pos_proposals, pos_assigned_gt_inds, gt_masks, cfg) + >>> assert mask_targets.shape == (5,) + cfg['mask_size'] + """ + device = pos_proposals.device + mask_size = _pair(cfg.mask_size) + binarize = not cfg.get('soft_mask_target', False) + num_pos = pos_proposals.size(0) + if num_pos > 0: + proposals_np = pos_proposals.cpu().numpy() + maxh, maxw = gt_masks.height, gt_masks.width + proposals_np[:, [0, 2]] = np.clip(proposals_np[:, [0, 2]], 0, maxw) + proposals_np[:, [1, 3]] = np.clip(proposals_np[:, [1, 3]], 0, maxh) + pos_assigned_gt_inds = pos_assigned_gt_inds.cpu().numpy() + + mask_targets = gt_masks.crop_and_resize( + proposals_np, + mask_size, + device=device, + inds=pos_assigned_gt_inds, + binarize=binarize).to_ndarray() + + mask_targets = torch.from_numpy(mask_targets).float().to(device) + else: + mask_targets = pos_proposals.new_zeros((0, ) + mask_size) + + return mask_targets diff --git a/mmdetection/mmdet/structures/mask/structures.py b/mmdetection/mmdet/structures/mask/structures.py new file mode 100644 index 00000000..b4fdd275 --- /dev/null +++ b/mmdetection/mmdet/structures/mask/structures.py @@ -0,0 +1,1193 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import itertools +from abc import ABCMeta, abstractmethod +from typing import Sequence, Type, TypeVar + +import cv2 +import mmcv +import numpy as np +import pycocotools.mask as maskUtils +import shapely.geometry as geometry +import torch +from mmcv.ops.roi_align import roi_align + +T = TypeVar('T') + + +class BaseInstanceMasks(metaclass=ABCMeta): + """Base class for instance masks.""" + + @abstractmethod + def rescale(self, scale, interpolation='nearest'): + """Rescale masks as large as possible while keeping the aspect ratio. + For details can refer to `mmcv.imrescale`. + + Args: + scale (tuple[int]): The maximum size (h, w) of rescaled mask. + interpolation (str): Same as :func:`mmcv.imrescale`. + + Returns: + BaseInstanceMasks: The rescaled masks. + """ + + @abstractmethod + def resize(self, out_shape, interpolation='nearest'): + """Resize masks to the given out_shape. + + Args: + out_shape: Target (h, w) of resized mask. + interpolation (str): See :func:`mmcv.imresize`. + + Returns: + BaseInstanceMasks: The resized masks. + """ + + @abstractmethod + def flip(self, flip_direction='horizontal'): + """Flip masks alone the given direction. + + Args: + flip_direction (str): Either 'horizontal' or 'vertical'. + + Returns: + BaseInstanceMasks: The flipped masks. + """ + + @abstractmethod + def pad(self, out_shape, pad_val): + """Pad masks to the given size of (h, w). + + Args: + out_shape (tuple[int]): Target (h, w) of padded mask. + pad_val (int): The padded value. + + Returns: + BaseInstanceMasks: The padded masks. + """ + + @abstractmethod + def crop(self, bbox): + """Crop each mask by the given bbox. + + Args: + bbox (ndarray): Bbox in format [x1, y1, x2, y2], shape (4, ). + + Return: + BaseInstanceMasks: The cropped masks. + """ + + @abstractmethod + def crop_and_resize(self, + bboxes, + out_shape, + inds, + device, + interpolation='bilinear', + binarize=True): + """Crop and resize masks by the given bboxes. + + This function is mainly used in mask targets computation. + It firstly align mask to bboxes by assigned_inds, then crop mask by the + assigned bbox and resize to the size of (mask_h, mask_w) + + Args: + bboxes (Tensor): Bboxes in format [x1, y1, x2, y2], shape (N, 4) + out_shape (tuple[int]): Target (h, w) of resized mask + inds (ndarray): Indexes to assign masks to each bbox, + shape (N,) and values should be between [0, num_masks - 1]. + device (str): Device of bboxes + interpolation (str): See `mmcv.imresize` + binarize (bool): if True fractional values are rounded to 0 or 1 + after the resize operation. if False and unsupported an error + will be raised. Defaults to True. + + Return: + BaseInstanceMasks: the cropped and resized masks. + """ + + @abstractmethod + def expand(self, expanded_h, expanded_w, top, left): + """see :class:`Expand`.""" + + @property + @abstractmethod + def areas(self): + """ndarray: areas of each instance.""" + + @abstractmethod + def to_ndarray(self): + """Convert masks to the format of ndarray. + + Return: + ndarray: Converted masks in the format of ndarray. + """ + + @abstractmethod + def to_tensor(self, dtype, device): + """Convert masks to the format of Tensor. + + Args: + dtype (str): Dtype of converted mask. + device (torch.device): Device of converted masks. + + Returns: + Tensor: Converted masks in the format of Tensor. + """ + + @abstractmethod + def translate(self, + out_shape, + offset, + direction='horizontal', + border_value=0, + interpolation='bilinear'): + """Translate the masks. + + Args: + out_shape (tuple[int]): Shape for output mask, format (h, w). + offset (int | float): The offset for translate. + direction (str): The translate direction, either "horizontal" + or "vertical". + border_value (int | float): Border value. Default 0. + interpolation (str): Same as :func:`mmcv.imtranslate`. + + Returns: + Translated masks. + """ + + def shear(self, + out_shape, + magnitude, + direction='horizontal', + border_value=0, + interpolation='bilinear'): + """Shear the masks. + + Args: + out_shape (tuple[int]): Shape for output mask, format (h, w). + magnitude (int | float): The magnitude used for shear. + direction (str): The shear direction, either "horizontal" + or "vertical". + border_value (int | tuple[int]): Value used in case of a + constant border. Default 0. + interpolation (str): Same as in :func:`mmcv.imshear`. + + Returns: + ndarray: Sheared masks. + """ + + @abstractmethod + def rotate(self, out_shape, angle, center=None, scale=1.0, border_value=0): + """Rotate the masks. + + Args: + out_shape (tuple[int]): Shape for output mask, format (h, w). + angle (int | float): Rotation angle in degrees. Positive values + mean counter-clockwise rotation. + center (tuple[float], optional): Center point (w, h) of the + rotation in source image. If not specified, the center of + the image will be used. + scale (int | float): Isotropic scale factor. + border_value (int | float): Border value. Default 0 for masks. + + Returns: + Rotated masks. + """ + + def get_bboxes(self, dst_type='hbb'): + """Get the certain type boxes from masks. + + Please refer to ``mmdet.structures.bbox.box_type`` for more details of + the box type. + + Args: + dst_type: Destination box type. + + Returns: + :obj:`BaseBoxes`: Certain type boxes. + """ + from ..bbox import get_box_type + _, box_type_cls = get_box_type(dst_type) + return box_type_cls.from_instance_masks(self) + + @classmethod + @abstractmethod + def cat(cls: Type[T], masks: Sequence[T]) -> T: + """Concatenate a sequence of masks into one single mask instance. + + Args: + masks (Sequence[T]): A sequence of mask instances. + + Returns: + T: Concatenated mask instance. + """ + + +class BitmapMasks(BaseInstanceMasks): + """This class represents masks in the form of bitmaps. + + Args: + masks (ndarray): ndarray of masks in shape (N, H, W), where N is + the number of objects. + height (int): height of masks + width (int): width of masks + + Example: + >>> from mmdet.data_elements.mask.structures import * # NOQA + >>> num_masks, H, W = 3, 32, 32 + >>> rng = np.random.RandomState(0) + >>> masks = (rng.rand(num_masks, H, W) > 0.1).astype(np.int64) + >>> self = BitmapMasks(masks, height=H, width=W) + + >>> # demo crop_and_resize + >>> num_boxes = 5 + >>> bboxes = np.array([[0, 0, 30, 10.0]] * num_boxes) + >>> out_shape = (14, 14) + >>> inds = torch.randint(0, len(self), size=(num_boxes,)) + >>> device = 'cpu' + >>> interpolation = 'bilinear' + >>> new = self.crop_and_resize( + ... bboxes, out_shape, inds, device, interpolation) + >>> assert len(new) == num_boxes + >>> assert new.height, new.width == out_shape + """ + + def __init__(self, masks, height, width): + self.height = height + self.width = width + if len(masks) == 0: + self.masks = np.empty((0, self.height, self.width), dtype=np.uint8) + else: + assert isinstance(masks, (list, np.ndarray)) + if isinstance(masks, list): + assert isinstance(masks[0], np.ndarray) + assert masks[0].ndim == 2 # (H, W) + else: + assert masks.ndim == 3 # (N, H, W) + + self.masks = np.stack(masks).reshape(-1, height, width) + assert self.masks.shape[1] == self.height + assert self.masks.shape[2] == self.width + + def __getitem__(self, index): + """Index the BitmapMask. + + Args: + index (int | ndarray): Indices in the format of integer or ndarray. + + Returns: + :obj:`BitmapMasks`: Indexed bitmap masks. + """ + masks = self.masks[index].reshape(-1, self.height, self.width) + return BitmapMasks(masks, self.height, self.width) + + def __iter__(self): + return iter(self.masks) + + def __repr__(self): + s = self.__class__.__name__ + '(' + s += f'num_masks={len(self.masks)}, ' + s += f'height={self.height}, ' + s += f'width={self.width})' + return s + + def __len__(self): + """Number of masks.""" + return len(self.masks) + + def rescale(self, scale, interpolation='nearest'): + """See :func:`BaseInstanceMasks.rescale`.""" + if len(self.masks) == 0: + new_w, new_h = mmcv.rescale_size((self.width, self.height), scale) + rescaled_masks = np.empty((0, new_h, new_w), dtype=np.uint8) + else: + rescaled_masks = np.stack([ + mmcv.imrescale(mask, scale, interpolation=interpolation) + for mask in self.masks + ]) + height, width = rescaled_masks.shape[1:] + return BitmapMasks(rescaled_masks, height, width) + + def resize(self, out_shape, interpolation='nearest'): + """See :func:`BaseInstanceMasks.resize`.""" + if len(self.masks) == 0: + resized_masks = np.empty((0, *out_shape), dtype=np.uint8) + else: + resized_masks = np.stack([ + mmcv.imresize( + mask, out_shape[::-1], interpolation=interpolation) + for mask in self.masks + ]) + return BitmapMasks(resized_masks, *out_shape) + + def flip(self, flip_direction='horizontal'): + """See :func:`BaseInstanceMasks.flip`.""" + assert flip_direction in ('horizontal', 'vertical', 'diagonal') + + if len(self.masks) == 0: + flipped_masks = self.masks + else: + flipped_masks = np.stack([ + mmcv.imflip(mask, direction=flip_direction) + for mask in self.masks + ]) + return BitmapMasks(flipped_masks, self.height, self.width) + + def pad(self, out_shape, pad_val=0): + """See :func:`BaseInstanceMasks.pad`.""" + if len(self.masks) == 0: + padded_masks = np.empty((0, *out_shape), dtype=np.uint8) + else: + padded_masks = np.stack([ + mmcv.impad(mask, shape=out_shape, pad_val=pad_val) + for mask in self.masks + ]) + return BitmapMasks(padded_masks, *out_shape) + + def crop(self, bbox): + """See :func:`BaseInstanceMasks.crop`.""" + assert isinstance(bbox, np.ndarray) + assert bbox.ndim == 1 + + # clip the boundary + bbox = bbox.copy() + bbox[0::2] = np.clip(bbox[0::2], 0, self.width) + bbox[1::2] = np.clip(bbox[1::2], 0, self.height) + x1, y1, x2, y2 = bbox + w = np.maximum(x2 - x1, 1) + h = np.maximum(y2 - y1, 1) + + if len(self.masks) == 0: + cropped_masks = np.empty((0, h, w), dtype=np.uint8) + else: + cropped_masks = self.masks[:, y1:y1 + h, x1:x1 + w] + return BitmapMasks(cropped_masks, h, w) + + def crop_and_resize(self, + bboxes, + out_shape, + inds, + device='cpu', + interpolation='bilinear', + binarize=True): + """See :func:`BaseInstanceMasks.crop_and_resize`.""" + if len(self.masks) == 0: + empty_masks = np.empty((0, *out_shape), dtype=np.uint8) + return BitmapMasks(empty_masks, *out_shape) + + # convert bboxes to tensor + if isinstance(bboxes, np.ndarray): + bboxes = torch.from_numpy(bboxes).to(device=device) + if isinstance(inds, np.ndarray): + inds = torch.from_numpy(inds).to(device=device) + + num_bbox = bboxes.shape[0] + fake_inds = torch.arange( + num_bbox, device=device).to(dtype=bboxes.dtype)[:, None] + rois = torch.cat([fake_inds, bboxes], dim=1) # Nx5 + rois = rois.to(device=device) + if num_bbox > 0: + gt_masks_th = torch.from_numpy(self.masks).to(device).index_select( + 0, inds).to(dtype=rois.dtype) + targets = roi_align(gt_masks_th[:, None, :, :], rois, out_shape, + 1.0, 0, 'avg', True).squeeze(1) + if binarize: + resized_masks = (targets >= 0.5).cpu().numpy() + else: + resized_masks = targets.cpu().numpy() + else: + resized_masks = [] + return BitmapMasks(resized_masks, *out_shape) + + def expand(self, expanded_h, expanded_w, top, left): + """See :func:`BaseInstanceMasks.expand`.""" + if len(self.masks) == 0: + expanded_mask = np.empty((0, expanded_h, expanded_w), + dtype=np.uint8) + else: + expanded_mask = np.zeros((len(self), expanded_h, expanded_w), + dtype=np.uint8) + expanded_mask[:, top:top + self.height, + left:left + self.width] = self.masks + return BitmapMasks(expanded_mask, expanded_h, expanded_w) + + def translate(self, + out_shape, + offset, + direction='horizontal', + border_value=0, + interpolation='bilinear'): + """Translate the BitmapMasks. + + Args: + out_shape (tuple[int]): Shape for output mask, format (h, w). + offset (int | float): The offset for translate. + direction (str): The translate direction, either "horizontal" + or "vertical". + border_value (int | float): Border value. Default 0 for masks. + interpolation (str): Same as :func:`mmcv.imtranslate`. + + Returns: + BitmapMasks: Translated BitmapMasks. + + Example: + >>> from mmdet.data_elements.mask.structures import BitmapMasks + >>> self = BitmapMasks.random(dtype=np.uint8) + >>> out_shape = (32, 32) + >>> offset = 4 + >>> direction = 'horizontal' + >>> border_value = 0 + >>> interpolation = 'bilinear' + >>> # Note, There seem to be issues when: + >>> # * the mask dtype is not supported by cv2.AffineWarp + >>> new = self.translate(out_shape, offset, direction, + >>> border_value, interpolation) + >>> assert len(new) == len(self) + >>> assert new.height, new.width == out_shape + """ + if len(self.masks) == 0: + translated_masks = np.empty((0, *out_shape), dtype=np.uint8) + else: + masks = self.masks + if masks.shape[-2:] != out_shape: + empty_masks = np.zeros((masks.shape[0], *out_shape), + dtype=masks.dtype) + min_h = min(out_shape[0], masks.shape[1]) + min_w = min(out_shape[1], masks.shape[2]) + empty_masks[:, :min_h, :min_w] = masks[:, :min_h, :min_w] + masks = empty_masks + translated_masks = mmcv.imtranslate( + masks.transpose((1, 2, 0)), + offset, + direction, + border_value=border_value, + interpolation=interpolation) + if translated_masks.ndim == 2: + translated_masks = translated_masks[:, :, None] + translated_masks = translated_masks.transpose( + (2, 0, 1)).astype(self.masks.dtype) + return BitmapMasks(translated_masks, *out_shape) + + def shear(self, + out_shape, + magnitude, + direction='horizontal', + border_value=0, + interpolation='bilinear'): + """Shear the BitmapMasks. + + Args: + out_shape (tuple[int]): Shape for output mask, format (h, w). + magnitude (int | float): The magnitude used for shear. + direction (str): The shear direction, either "horizontal" + or "vertical". + border_value (int | tuple[int]): Value used in case of a + constant border. + interpolation (str): Same as in :func:`mmcv.imshear`. + + Returns: + BitmapMasks: The sheared masks. + """ + if len(self.masks) == 0: + sheared_masks = np.empty((0, *out_shape), dtype=np.uint8) + else: + sheared_masks = mmcv.imshear( + self.masks.transpose((1, 2, 0)), + magnitude, + direction, + border_value=border_value, + interpolation=interpolation) + if sheared_masks.ndim == 2: + sheared_masks = sheared_masks[:, :, None] + sheared_masks = sheared_masks.transpose( + (2, 0, 1)).astype(self.masks.dtype) + return BitmapMasks(sheared_masks, *out_shape) + + def rotate(self, + out_shape, + angle, + center=None, + scale=1.0, + border_value=0, + interpolation='bilinear'): + """Rotate the BitmapMasks. + + Args: + out_shape (tuple[int]): Shape for output mask, format (h, w). + angle (int | float): Rotation angle in degrees. Positive values + mean counter-clockwise rotation. + center (tuple[float], optional): Center point (w, h) of the + rotation in source image. If not specified, the center of + the image will be used. + scale (int | float): Isotropic scale factor. + border_value (int | float): Border value. Default 0 for masks. + interpolation (str): Same as in :func:`mmcv.imrotate`. + + Returns: + BitmapMasks: Rotated BitmapMasks. + """ + if len(self.masks) == 0: + rotated_masks = np.empty((0, *out_shape), dtype=self.masks.dtype) + else: + rotated_masks = mmcv.imrotate( + self.masks.transpose((1, 2, 0)), + angle, + center=center, + scale=scale, + border_value=border_value, + interpolation=interpolation) + if rotated_masks.ndim == 2: + # case when only one mask, (h, w) + rotated_masks = rotated_masks[:, :, None] # (h, w, 1) + rotated_masks = rotated_masks.transpose( + (2, 0, 1)).astype(self.masks.dtype) + return BitmapMasks(rotated_masks, *out_shape) + + @property + def areas(self): + """See :py:attr:`BaseInstanceMasks.areas`.""" + return self.masks.sum((1, 2)) + + def to_ndarray(self): + """See :func:`BaseInstanceMasks.to_ndarray`.""" + return self.masks + + def to_tensor(self, dtype, device): + """See :func:`BaseInstanceMasks.to_tensor`.""" + return torch.tensor(self.masks, dtype=dtype, device=device) + + @classmethod + def random(cls, + num_masks=3, + height=32, + width=32, + dtype=np.uint8, + rng=None): + """Generate random bitmap masks for demo / testing purposes. + + Example: + >>> from mmdet.data_elements.mask.structures import BitmapMasks + >>> self = BitmapMasks.random() + >>> print('self = {}'.format(self)) + self = BitmapMasks(num_masks=3, height=32, width=32) + """ + from mmdet.utils.util_random import ensure_rng + rng = ensure_rng(rng) + masks = (rng.rand(num_masks, height, width) > 0.1).astype(dtype) + self = cls(masks, height=height, width=width) + return self + + @classmethod + def cat(cls: Type[T], masks: Sequence[T]) -> T: + """Concatenate a sequence of masks into one single mask instance. + + Args: + masks (Sequence[BitmapMasks]): A sequence of mask instances. + + Returns: + BitmapMasks: Concatenated mask instance. + """ + assert isinstance(masks, Sequence) + if len(masks) == 0: + raise ValueError('masks should not be an empty list.') + assert all(isinstance(m, cls) for m in masks) + + mask_array = np.concatenate([m.masks for m in masks], axis=0) + return cls(mask_array, *mask_array.shape[1:]) + + +class PolygonMasks(BaseInstanceMasks): + """This class represents masks in the form of polygons. + + Polygons is a list of three levels. The first level of the list + corresponds to objects, the second level to the polys that compose the + object, the third level to the poly coordinates + + Args: + masks (list[list[ndarray]]): The first level of the list + corresponds to objects, the second level to the polys that + compose the object, the third level to the poly coordinates + height (int): height of masks + width (int): width of masks + + Example: + >>> from mmdet.data_elements.mask.structures import * # NOQA + >>> masks = [ + >>> [ np.array([0, 0, 10, 0, 10, 10., 0, 10, 0, 0]) ] + >>> ] + >>> height, width = 16, 16 + >>> self = PolygonMasks(masks, height, width) + + >>> # demo translate + >>> new = self.translate((16, 16), 4., direction='horizontal') + >>> assert np.all(new.masks[0][0][1::2] == masks[0][0][1::2]) + >>> assert np.all(new.masks[0][0][0::2] == masks[0][0][0::2] + 4) + + >>> # demo crop_and_resize + >>> num_boxes = 3 + >>> bboxes = np.array([[0, 0, 30, 10.0]] * num_boxes) + >>> out_shape = (16, 16) + >>> inds = torch.randint(0, len(self), size=(num_boxes,)) + >>> device = 'cpu' + >>> interpolation = 'bilinear' + >>> new = self.crop_and_resize( + ... bboxes, out_shape, inds, device, interpolation) + >>> assert len(new) == num_boxes + >>> assert new.height, new.width == out_shape + """ + + def __init__(self, masks, height, width): + assert isinstance(masks, list) + if len(masks) > 0: + assert isinstance(masks[0], list) + assert isinstance(masks[0][0], np.ndarray) + + self.height = height + self.width = width + self.masks = masks + + def __getitem__(self, index): + """Index the polygon masks. + + Args: + index (ndarray | List): The indices. + + Returns: + :obj:`PolygonMasks`: The indexed polygon masks. + """ + if isinstance(index, np.ndarray): + if index.dtype == bool: + index = np.where(index)[0].tolist() + else: + index = index.tolist() + if isinstance(index, list): + masks = [self.masks[i] for i in index] + else: + try: + masks = self.masks[index] + except Exception: + raise ValueError( + f'Unsupported input of type {type(index)} for indexing!') + if len(masks) and isinstance(masks[0], np.ndarray): + masks = [masks] # ensure a list of three levels + return PolygonMasks(masks, self.height, self.width) + + def __iter__(self): + return iter(self.masks) + + def __repr__(self): + s = self.__class__.__name__ + '(' + s += f'num_masks={len(self.masks)}, ' + s += f'height={self.height}, ' + s += f'width={self.width})' + return s + + def __len__(self): + """Number of masks.""" + return len(self.masks) + + def rescale(self, scale, interpolation=None): + """see :func:`BaseInstanceMasks.rescale`""" + new_w, new_h = mmcv.rescale_size((self.width, self.height), scale) + if len(self.masks) == 0: + rescaled_masks = PolygonMasks([], new_h, new_w) + else: + rescaled_masks = self.resize((new_h, new_w)) + return rescaled_masks + + def resize(self, out_shape, interpolation=None): + """see :func:`BaseInstanceMasks.resize`""" + if len(self.masks) == 0: + resized_masks = PolygonMasks([], *out_shape) + else: + h_scale = out_shape[0] / self.height + w_scale = out_shape[1] / self.width + resized_masks = [] + for poly_per_obj in self.masks: + resized_poly = [] + for p in poly_per_obj: + p = p.copy() + p[0::2] = p[0::2] * w_scale + p[1::2] = p[1::2] * h_scale + resized_poly.append(p) + resized_masks.append(resized_poly) + resized_masks = PolygonMasks(resized_masks, *out_shape) + return resized_masks + + def flip(self, flip_direction='horizontal'): + """see :func:`BaseInstanceMasks.flip`""" + assert flip_direction in ('horizontal', 'vertical', 'diagonal') + if len(self.masks) == 0: + flipped_masks = PolygonMasks([], self.height, self.width) + else: + flipped_masks = [] + for poly_per_obj in self.masks: + flipped_poly_per_obj = [] + for p in poly_per_obj: + p = p.copy() + if flip_direction == 'horizontal': + p[0::2] = self.width - p[0::2] + elif flip_direction == 'vertical': + p[1::2] = self.height - p[1::2] + else: + p[0::2] = self.width - p[0::2] + p[1::2] = self.height - p[1::2] + flipped_poly_per_obj.append(p) + flipped_masks.append(flipped_poly_per_obj) + flipped_masks = PolygonMasks(flipped_masks, self.height, + self.width) + return flipped_masks + + def crop(self, bbox): + """see :func:`BaseInstanceMasks.crop`""" + assert isinstance(bbox, np.ndarray) + assert bbox.ndim == 1 + + # clip the boundary + bbox = bbox.copy() + bbox[0::2] = np.clip(bbox[0::2], 0, self.width) + bbox[1::2] = np.clip(bbox[1::2], 0, self.height) + x1, y1, x2, y2 = bbox + w = np.maximum(x2 - x1, 1) + h = np.maximum(y2 - y1, 1) + + if len(self.masks) == 0: + cropped_masks = PolygonMasks([], h, w) + else: + # reference: https://github.com/facebookresearch/fvcore/blob/main/fvcore/transforms/transform.py # noqa + crop_box = geometry.box(x1, y1, x2, y2).buffer(0.0) + cropped_masks = [] + # suppress shapely warnings util it incorporates GEOS>=3.11.2 + # reference: https://github.com/shapely/shapely/issues/1345 + initial_settings = np.seterr() + np.seterr(invalid='ignore') + for poly_per_obj in self.masks: + cropped_poly_per_obj = [] + for p in poly_per_obj: + p = p.copy() + p = geometry.Polygon(p.reshape(-1, 2)).buffer(0.0) + # polygon must be valid to perform intersection. + if not p.is_valid: + continue + cropped = p.intersection(crop_box) + if cropped.is_empty: + continue + if isinstance(cropped, + geometry.collection.BaseMultipartGeometry): + cropped = cropped.geoms + else: + cropped = [cropped] + # one polygon may be cropped to multiple ones + for poly in cropped: + # ignore lines or points + if not isinstance( + poly, geometry.Polygon) or not poly.is_valid: + continue + coords = np.asarray(poly.exterior.coords) + # remove an extra identical vertex at the end + coords = coords[:-1] + coords[:, 0] -= x1 + coords[:, 1] -= y1 + cropped_poly_per_obj.append(coords.reshape(-1)) + # a dummy polygon to avoid misalignment between masks and boxes + if len(cropped_poly_per_obj) == 0: + cropped_poly_per_obj = [np.array([0, 0, 0, 0, 0, 0])] + cropped_masks.append(cropped_poly_per_obj) + np.seterr(**initial_settings) + cropped_masks = PolygonMasks(cropped_masks, h, w) + return cropped_masks + + def pad(self, out_shape, pad_val=0): + """padding has no effect on polygons`""" + return PolygonMasks(self.masks, *out_shape) + + def expand(self, *args, **kwargs): + """TODO: Add expand for polygon""" + raise NotImplementedError + + def crop_and_resize(self, + bboxes, + out_shape, + inds, + device='cpu', + interpolation='bilinear', + binarize=True): + """see :func:`BaseInstanceMasks.crop_and_resize`""" + out_h, out_w = out_shape + if len(self.masks) == 0: + return PolygonMasks([], out_h, out_w) + + if not binarize: + raise ValueError('Polygons are always binary, ' + 'setting binarize=False is unsupported') + + resized_masks = [] + for i in range(len(bboxes)): + mask = self.masks[inds[i]] + bbox = bboxes[i, :] + x1, y1, x2, y2 = bbox + w = np.maximum(x2 - x1, 1) + h = np.maximum(y2 - y1, 1) + h_scale = out_h / max(h, 0.1) # avoid too large scale + w_scale = out_w / max(w, 0.1) + + resized_mask = [] + for p in mask: + p = p.copy() + # crop + # pycocotools will clip the boundary + p[0::2] = p[0::2] - bbox[0] + p[1::2] = p[1::2] - bbox[1] + + # resize + p[0::2] = p[0::2] * w_scale + p[1::2] = p[1::2] * h_scale + resized_mask.append(p) + resized_masks.append(resized_mask) + return PolygonMasks(resized_masks, *out_shape) + + def translate(self, + out_shape, + offset, + direction='horizontal', + border_value=None, + interpolation=None): + """Translate the PolygonMasks. + + Example: + >>> self = PolygonMasks.random(dtype=np.int64) + >>> out_shape = (self.height, self.width) + >>> new = self.translate(out_shape, 4., direction='horizontal') + >>> assert np.all(new.masks[0][0][1::2] == self.masks[0][0][1::2]) + >>> assert np.all(new.masks[0][0][0::2] == self.masks[0][0][0::2] + 4) # noqa: E501 + """ + assert border_value is None or border_value == 0, \ + 'Here border_value is not '\ + f'used, and defaultly should be None or 0. got {border_value}.' + if len(self.masks) == 0: + translated_masks = PolygonMasks([], *out_shape) + else: + translated_masks = [] + for poly_per_obj in self.masks: + translated_poly_per_obj = [] + for p in poly_per_obj: + p = p.copy() + if direction == 'horizontal': + p[0::2] = np.clip(p[0::2] + offset, 0, out_shape[1]) + elif direction == 'vertical': + p[1::2] = np.clip(p[1::2] + offset, 0, out_shape[0]) + translated_poly_per_obj.append(p) + translated_masks.append(translated_poly_per_obj) + translated_masks = PolygonMasks(translated_masks, *out_shape) + return translated_masks + + def shear(self, + out_shape, + magnitude, + direction='horizontal', + border_value=0, + interpolation='bilinear'): + """See :func:`BaseInstanceMasks.shear`.""" + if len(self.masks) == 0: + sheared_masks = PolygonMasks([], *out_shape) + else: + sheared_masks = [] + if direction == 'horizontal': + shear_matrix = np.stack([[1, magnitude], + [0, 1]]).astype(np.float32) + elif direction == 'vertical': + shear_matrix = np.stack([[1, 0], [magnitude, + 1]]).astype(np.float32) + for poly_per_obj in self.masks: + sheared_poly = [] + for p in poly_per_obj: + p = np.stack([p[0::2], p[1::2]], axis=0) # [2, n] + new_coords = np.matmul(shear_matrix, p) # [2, n] + new_coords[0, :] = np.clip(new_coords[0, :], 0, + out_shape[1]) + new_coords[1, :] = np.clip(new_coords[1, :], 0, + out_shape[0]) + sheared_poly.append( + new_coords.transpose((1, 0)).reshape(-1)) + sheared_masks.append(sheared_poly) + sheared_masks = PolygonMasks(sheared_masks, *out_shape) + return sheared_masks + + def rotate(self, + out_shape, + angle, + center=None, + scale=1.0, + border_value=0, + interpolation='bilinear'): + """See :func:`BaseInstanceMasks.rotate`.""" + if len(self.masks) == 0: + rotated_masks = PolygonMasks([], *out_shape) + else: + rotated_masks = [] + rotate_matrix = cv2.getRotationMatrix2D(center, -angle, scale) + for poly_per_obj in self.masks: + rotated_poly = [] + for p in poly_per_obj: + p = p.copy() + coords = np.stack([p[0::2], p[1::2]], axis=1) # [n, 2] + # pad 1 to convert from format [x, y] to homogeneous + # coordinates format [x, y, 1] + coords = np.concatenate( + (coords, np.ones((coords.shape[0], 1), coords.dtype)), + axis=1) # [n, 3] + rotated_coords = np.matmul( + rotate_matrix[None, :, :], + coords[:, :, None])[..., 0] # [n, 2, 1] -> [n, 2] + rotated_coords[:, 0] = np.clip(rotated_coords[:, 0], 0, + out_shape[1]) + rotated_coords[:, 1] = np.clip(rotated_coords[:, 1], 0, + out_shape[0]) + rotated_poly.append(rotated_coords.reshape(-1)) + rotated_masks.append(rotated_poly) + rotated_masks = PolygonMasks(rotated_masks, *out_shape) + return rotated_masks + + def to_bitmap(self): + """convert polygon masks to bitmap masks.""" + bitmap_masks = self.to_ndarray() + return BitmapMasks(bitmap_masks, self.height, self.width) + + @property + def areas(self): + """Compute areas of masks. + + This func is modified from `detectron2 + `_. + The function only works with Polygons using the shoelace formula. + + Return: + ndarray: areas of each instance + """ # noqa: W501 + area = [] + for polygons_per_obj in self.masks: + area_per_obj = 0 + for p in polygons_per_obj: + area_per_obj += self._polygon_area(p[0::2], p[1::2]) + area.append(area_per_obj) + return np.asarray(area) + + def _polygon_area(self, x, y): + """Compute the area of a component of a polygon. + + Using the shoelace formula: + https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates + + Args: + x (ndarray): x coordinates of the component + y (ndarray): y coordinates of the component + + Return: + float: the are of the component + """ # noqa: 501 + return 0.5 * np.abs( + np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1))) + + def to_ndarray(self): + """Convert masks to the format of ndarray.""" + if len(self.masks) == 0: + return np.empty((0, self.height, self.width), dtype=np.uint8) + bitmap_masks = [] + for poly_per_obj in self.masks: + bitmap_masks.append( + polygon_to_bitmap(poly_per_obj, self.height, self.width)) + return np.stack(bitmap_masks) + + def to_tensor(self, dtype, device): + """See :func:`BaseInstanceMasks.to_tensor`.""" + if len(self.masks) == 0: + return torch.empty((0, self.height, self.width), + dtype=dtype, + device=device) + ndarray_masks = self.to_ndarray() + return torch.tensor(ndarray_masks, dtype=dtype, device=device) + + @classmethod + def random(cls, + num_masks=3, + height=32, + width=32, + n_verts=5, + dtype=np.float32, + rng=None): + """Generate random polygon masks for demo / testing purposes. + + Adapted from [1]_ + + References: + .. [1] https://gitlab.kitware.com/computer-vision/kwimage/-/blob/928cae35ca8/kwimage/structs/polygon.py#L379 # noqa: E501 + + Example: + >>> from mmdet.data_elements.mask.structures import PolygonMasks + >>> self = PolygonMasks.random() + >>> print('self = {}'.format(self)) + """ + from mmdet.utils.util_random import ensure_rng + rng = ensure_rng(rng) + + def _gen_polygon(n, irregularity, spikeyness): + """Creates the polygon by sampling points on a circle around the + centre. Random noise is added by varying the angular spacing + between sequential points, and by varying the radial distance of + each point from the centre. + + Based on original code by Mike Ounsworth + + Args: + n (int): number of vertices + irregularity (float): [0,1] indicating how much variance there + is in the angular spacing of vertices. [0,1] will map to + [0, 2pi/numberOfVerts] + spikeyness (float): [0,1] indicating how much variance there is + in each vertex from the circle of radius aveRadius. [0,1] + will map to [0, aveRadius] + + Returns: + a list of vertices, in CCW order. + """ + from scipy.stats import truncnorm + + # Generate around the unit circle + cx, cy = (0.0, 0.0) + radius = 1 + + tau = np.pi * 2 + + irregularity = np.clip(irregularity, 0, 1) * 2 * np.pi / n + spikeyness = np.clip(spikeyness, 1e-9, 1) + + # generate n angle steps + lower = (tau / n) - irregularity + upper = (tau / n) + irregularity + angle_steps = rng.uniform(lower, upper, n) + + # normalize the steps so that point 0 and point n+1 are the same + k = angle_steps.sum() / (2 * np.pi) + angles = (angle_steps / k).cumsum() + rng.uniform(0, tau) + + # Convert high and low values to be wrt the standard normal range + # https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.truncnorm.html + low = 0 + high = 2 * radius + mean = radius + std = spikeyness + a = (low - mean) / std + b = (high - mean) / std + tnorm = truncnorm(a=a, b=b, loc=mean, scale=std) + + # now generate the points + radii = tnorm.rvs(n, random_state=rng) + x_pts = cx + radii * np.cos(angles) + y_pts = cy + radii * np.sin(angles) + + points = np.hstack([x_pts[:, None], y_pts[:, None]]) + + # Scale to 0-1 space + points = points - points.min(axis=0) + points = points / points.max(axis=0) + + # Randomly place within 0-1 space + points = points * (rng.rand() * .8 + .2) + min_pt = points.min(axis=0) + max_pt = points.max(axis=0) + + high = (1 - max_pt) + low = (0 - min_pt) + offset = (rng.rand(2) * (high - low)) + low + points = points + offset + return points + + def _order_vertices(verts): + """ + References: + https://stackoverflow.com/questions/1709283/how-can-i-sort-a-coordinate-list-for-a-rectangle-counterclockwise + """ + mlat = verts.T[0].sum() / len(verts) + mlng = verts.T[1].sum() / len(verts) + + tau = np.pi * 2 + angle = (np.arctan2(mlat - verts.T[0], verts.T[1] - mlng) + + tau) % tau + sortx = angle.argsort() + verts = verts.take(sortx, axis=0) + return verts + + # Generate a random exterior for each requested mask + masks = [] + for _ in range(num_masks): + exterior = _order_vertices(_gen_polygon(n_verts, 0.9, 0.9)) + exterior = (exterior * [(width, height)]).astype(dtype) + masks.append([exterior.ravel()]) + + self = cls(masks, height, width) + return self + + @classmethod + def cat(cls: Type[T], masks: Sequence[T]) -> T: + """Concatenate a sequence of masks into one single mask instance. + + Args: + masks (Sequence[PolygonMasks]): A sequence of mask instances. + + Returns: + PolygonMasks: Concatenated mask instance. + """ + assert isinstance(masks, Sequence) + if len(masks) == 0: + raise ValueError('masks should not be an empty list.') + assert all(isinstance(m, cls) for m in masks) + + mask_list = list(itertools.chain(*[m.masks for m in masks])) + return cls(mask_list, masks[0].height, masks[0].width) + + +def polygon_to_bitmap(polygons, height, width): + """Convert masks from the form of polygons to bitmaps. + + Args: + polygons (list[ndarray]): masks in polygon representation + height (int): mask height + width (int): mask width + + Return: + ndarray: the converted masks in bitmap representation + """ + rles = maskUtils.frPyObjects(polygons, height, width) + rle = maskUtils.merge(rles) + bitmap_mask = maskUtils.decode(rle).astype(bool) + return bitmap_mask + + +def bitmap_to_polygon(bitmap): + """Convert masks from the form of bitmaps to polygons. + + Args: + bitmap (ndarray): masks in bitmap representation. + + Return: + list[ndarray]: the converted mask in polygon representation. + bool: whether the mask has holes. + """ + bitmap = np.ascontiguousarray(bitmap).astype(np.uint8) + # cv2.RETR_CCOMP: retrieves all of the contours and organizes them + # into a two-level hierarchy. At the top level, there are external + # boundaries of the components. At the second level, there are + # boundaries of the holes. If there is another contour inside a hole + # of a connected component, it is still put at the top level. + # cv2.CHAIN_APPROX_NONE: stores absolutely all the contour points. + outs = cv2.findContours(bitmap, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) + contours = outs[-2] + hierarchy = outs[-1] + if hierarchy is None: + return [], False + # hierarchy[i]: 4 elements, for the indexes of next, previous, + # parent, or nested contours. If there is no corresponding contour, + # it will be -1. + with_hole = (hierarchy.reshape(-1, 4)[:, 3] >= 0).any() + contours = [c.reshape(-1, 2) for c in contours] + return contours, with_hole diff --git a/mmdetection/mmdet/structures/mask/utils.py b/mmdetection/mmdet/structures/mask/utils.py new file mode 100644 index 00000000..6bd445e4 --- /dev/null +++ b/mmdetection/mmdet/structures/mask/utils.py @@ -0,0 +1,77 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np +import pycocotools.mask as mask_util +import torch +from mmengine.utils import slice_list + + +def split_combined_polys(polys, poly_lens, polys_per_mask): + """Split the combined 1-D polys into masks. + + A mask is represented as a list of polys, and a poly is represented as + a 1-D array. In dataset, all masks are concatenated into a single 1-D + tensor. Here we need to split the tensor into original representations. + + Args: + polys (list): a list (length = image num) of 1-D tensors + poly_lens (list): a list (length = image num) of poly length + polys_per_mask (list): a list (length = image num) of poly number + of each mask + + Returns: + list: a list (length = image num) of list (length = mask num) of \ + list (length = poly num) of numpy array. + """ + mask_polys_list = [] + for img_id in range(len(polys)): + polys_single = polys[img_id] + polys_lens_single = poly_lens[img_id].tolist() + polys_per_mask_single = polys_per_mask[img_id].tolist() + + split_polys = slice_list(polys_single, polys_lens_single) + mask_polys = slice_list(split_polys, polys_per_mask_single) + mask_polys_list.append(mask_polys) + return mask_polys_list + + +# TODO: move this function to more proper place +def encode_mask_results(mask_results): + """Encode bitmap mask to RLE code. + + Args: + mask_results (list): bitmap mask results. + + Returns: + list | tuple: RLE encoded mask. + """ + encoded_mask_results = [] + for mask in mask_results: + encoded_mask_results.append( + mask_util.encode( + np.array(mask[:, :, np.newaxis], order='F', + dtype='uint8'))[0]) # encoded with RLE + return encoded_mask_results + + +def mask2bbox(masks): + """Obtain tight bounding boxes of binary masks. + + Args: + masks (Tensor): Binary mask of shape (n, h, w). + + Returns: + Tensor: Bboxe with shape (n, 4) of \ + positive region in binary mask. + """ + N = masks.shape[0] + bboxes = masks.new_zeros((N, 4), dtype=torch.float32) + x_any = torch.any(masks, dim=1) + y_any = torch.any(masks, dim=2) + for i in range(N): + x = torch.where(x_any[i, :])[0] + y = torch.where(y_any[i, :])[0] + if len(x) > 0 and len(y) > 0: + bboxes[i, :] = bboxes.new_tensor( + [x[0], y[0], x[-1] + 1, y[-1] + 1]) + + return bboxes diff --git a/mmdetection/mmdet/structures/reid_data_sample.py b/mmdetection/mmdet/structures/reid_data_sample.py new file mode 100644 index 00000000..69958eec --- /dev/null +++ b/mmdetection/mmdet/structures/reid_data_sample.py @@ -0,0 +1,123 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from numbers import Number +from typing import Sequence, Union + +import mmengine +import numpy as np +import torch +from mmengine.structures import BaseDataElement, LabelData + + +def format_label(value: Union[torch.Tensor, np.ndarray, Sequence, int], + num_classes: int = None) -> LabelData: + """Convert label of various python types to :obj:`mmengine.LabelData`. + + Supported types are: :class:`numpy.ndarray`, :class:`torch.Tensor`, + :class:`Sequence`, :class:`int`. + + Args: + value (torch.Tensor | numpy.ndarray | Sequence | int): Label value. + num_classes (int, optional): The number of classes. If not None, set + it to the metainfo. Defaults to None. + + Returns: + :obj:`mmengine.LabelData`: The foramtted label data. + """ + + # Handle single number + if isinstance(value, (torch.Tensor, np.ndarray)) and value.ndim == 0: + value = int(value.item()) + + if isinstance(value, np.ndarray): + value = torch.from_numpy(value) + elif isinstance(value, Sequence) and not mmengine.utils.is_str(value): + value = torch.tensor(value) + elif isinstance(value, int): + value = torch.LongTensor([value]) + elif not isinstance(value, torch.Tensor): + raise TypeError(f'Type {type(value)} is not an available label type.') + + metainfo = {} + if num_classes is not None: + metainfo['num_classes'] = num_classes + if value.max() >= num_classes: + raise ValueError(f'The label data ({value}) should not ' + f'exceed num_classes ({num_classes}).') + label = LabelData(label=value, metainfo=metainfo) + return label + + +class ReIDDataSample(BaseDataElement): + """A data structure interface of ReID task. + + It's used as interfaces between different components. + + Meta field: + img_shape (Tuple): The shape of the corresponding input image. + Used for visualization. + ori_shape (Tuple): The original shape of the corresponding image. + Used for visualization. + num_classes (int): The number of all categories. + Used for label format conversion. + + Data field: + gt_label (LabelData): The ground truth label. + pred_label (LabelData): The predicted label. + scores (torch.Tensor): The outputs of model. + """ + + @property + def gt_label(self): + return self._gt_label + + @gt_label.setter + def gt_label(self, value: LabelData): + self.set_field(value, '_gt_label', dtype=LabelData) + + @gt_label.deleter + def gt_label(self): + del self._gt_label + + def set_gt_label( + self, value: Union[np.ndarray, torch.Tensor, Sequence[Number], Number] + ) -> 'ReIDDataSample': + """Set label of ``gt_label``.""" + label = format_label(value, self.get('num_classes')) + if 'gt_label' in self: # setting for the second time + self.gt_label.label = label.label + else: # setting for the first time + self.gt_label = label + return self + + def set_gt_score(self, value: torch.Tensor) -> 'ReIDDataSample': + """Set score of ``gt_label``.""" + assert isinstance(value, torch.Tensor), \ + f'The value should be a torch.Tensor but got {type(value)}.' + assert value.ndim == 1, \ + f'The dims of value should be 1, but got {value.ndim}.' + + if 'num_classes' in self: + assert value.size(0) == self.num_classes, \ + f"The length of value ({value.size(0)}) doesn't "\ + f'match the num_classes ({self.num_classes}).' + metainfo = {'num_classes': self.num_classes} + else: + metainfo = {'num_classes': value.size(0)} + + if 'gt_label' in self: # setting for the second time + self.gt_label.score = value + else: # setting for the first time + self.gt_label = LabelData(score=value, metainfo=metainfo) + return self + + @property + def pred_feature(self): + return self._pred_feature + + @pred_feature.setter + def pred_feature(self, value: torch.Tensor): + self.set_field(value, '_pred_feature', dtype=torch.Tensor) + + @pred_feature.deleter + def pred_feature(self): + del self._pred_feature diff --git a/mmdetection/mmdet/structures/track_data_sample.py b/mmdetection/mmdet/structures/track_data_sample.py new file mode 100644 index 00000000..d005a5a4 --- /dev/null +++ b/mmdetection/mmdet/structures/track_data_sample.py @@ -0,0 +1,273 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Sequence + +import numpy as np +import torch +from mmengine.structures import BaseDataElement + +from .det_data_sample import DetDataSample + + +class TrackDataSample(BaseDataElement): + """A data structure interface of tracking task in MMDetection. It is used + as interfaces between different components. + + This data structure can be viewd as a wrapper of multiple DetDataSample to + some extent. Specifically, it only contains a property: + ``video_data_samples`` which is a list of DetDataSample, each of which + corresponds to a single frame. If you want to get the property of a single + frame, you must first get the corresponding ``DetDataSample`` by indexing + and then get the property of the frame, such as ``gt_instances``, + ``pred_instances`` and so on. As for metainfo, it differs from + ``DetDataSample`` in that each value corresponds to the metainfo key is a + list where each element corresponds to information of a single frame. + + Examples: + >>> import torch + >>> from mmengine.structures import InstanceData + >>> from mmdet.structures import DetDataSample, TrackDataSample + >>> track_data_sample = TrackDataSample() + >>> # set the 1st frame + >>> frame1_data_sample = DetDataSample(metainfo=dict( + ... img_shape=(100, 100), frame_id=0)) + >>> frame1_gt_instances = InstanceData() + >>> frame1_gt_instances.bbox = torch.zeros([2, 4]) + >>> frame1_data_sample.gt_instances = frame1_gt_instances + >>> # set the 2nd frame + >>> frame2_data_sample = DetDataSample(metainfo=dict( + ... img_shape=(100, 100), frame_id=1)) + >>> frame2_gt_instances = InstanceData() + >>> frame2_gt_instances.bbox = torch.ones([3, 4]) + >>> frame2_data_sample.gt_instances = frame2_gt_instances + >>> track_data_sample.video_data_samples = [frame1_data_sample, + ... frame2_data_sample] + >>> # set metainfo for track_data_sample + >>> track_data_sample.set_metainfo(dict(key_frames_inds=[0])) + >>> track_data_sample.set_metainfo(dict(ref_frames_inds=[1])) + >>> print(track_data_sample) + + ) at 0x7f64bd223340>, + ) at 0x7f64bd1346d0>] + ) at 0x7f64bd2237f0> + >>> print(len(track_data_sample)) + 2 + >>> key_data_sample = track_data_sample.get_key_frames() + >>> print(key_data_sample[0].frame_id) + 0 + >>> ref_data_sample = track_data_sample.get_ref_frames() + >>> print(ref_data_sample[0].frame_id) + 1 + >>> frame1_data_sample = track_data_sample[0] + >>> print(frame1_data_sample.gt_instances.bbox) + tensor([[0., 0., 0., 0.], + [0., 0., 0., 0.]]) + >>> # Tensor-like methods + >>> cuda_track_data_sample = track_data_sample.to('cuda') + >>> cuda_track_data_sample = track_data_sample.cuda() + >>> cpu_track_data_sample = track_data_sample.cpu() + >>> cpu_track_data_sample = track_data_sample.to('cpu') + >>> fp16_instances = cuda_track_data_sample.to( + ... device=None, dtype=torch.float16, non_blocking=False, + ... copy=False, memory_format=torch.preserve_format) + """ + + @property + def video_data_samples(self) -> List[DetDataSample]: + return self._video_data_samples + + @video_data_samples.setter + def video_data_samples(self, value: List[DetDataSample]): + if isinstance(value, DetDataSample): + value = [value] + assert isinstance(value, list), 'video_data_samples must be a list' + assert isinstance( + value[0], DetDataSample + ), 'video_data_samples must be a list of DetDataSample, but got ' + f'{value[0]}' + self.set_field(value, '_video_data_samples', dtype=list) + + @video_data_samples.deleter + def video_data_samples(self): + del self._video_data_samples + + def __getitem__(self, index): + assert hasattr(self, + '_video_data_samples'), 'video_data_samples not set' + return self._video_data_samples[index] + + def get_key_frames(self): + assert hasattr(self, 'key_frames_inds'), \ + 'key_frames_inds not set' + assert isinstance(self.key_frames_inds, Sequence) + key_frames_info = [] + for index in self.key_frames_inds: + key_frames_info.append(self[index]) + return key_frames_info + + def get_ref_frames(self): + assert hasattr(self, 'ref_frames_inds'), \ + 'ref_frames_inds not set' + ref_frames_info = [] + assert isinstance(self.ref_frames_inds, Sequence) + for index in self.ref_frames_inds: + ref_frames_info.append(self[index]) + return ref_frames_info + + def __len__(self): + return len(self._video_data_samples) if hasattr( + self, '_video_data_samples') else 0 + + # TODO: add UT for this Tensor-like method + # Tensor-like methods + def to(self, *args, **kwargs) -> 'BaseDataElement': + """Apply same name function to all tensors in data_fields.""" + new_data = self.new() + for k, v_list in self.items(): + data_list = [] + for v in v_list: + if hasattr(v, 'to'): + v = v.to(*args, **kwargs) + data_list.append(v) + if len(data_list) > 0: + new_data.set_data({f'{k}': data_list}) + return new_data + + # Tensor-like methods + def cpu(self) -> 'BaseDataElement': + """Convert all tensors to CPU in data.""" + new_data = self.new() + for k, v_list in self.items(): + data_list = [] + for v in v_list: + if isinstance(v, (torch.Tensor, BaseDataElement)): + v = v.cpu() + data_list.append(v) + if len(data_list) > 0: + new_data.set_data({f'{k}': data_list}) + return new_data + + # Tensor-like methods + def cuda(self) -> 'BaseDataElement': + """Convert all tensors to GPU in data.""" + new_data = self.new() + for k, v_list in self.items(): + data_list = [] + for v in v_list: + if isinstance(v, (torch.Tensor, BaseDataElement)): + v = v.cuda() + data_list.append(v) + if len(data_list) > 0: + new_data.set_data({f'{k}': data_list}) + return new_data + + # Tensor-like methods + def npu(self) -> 'BaseDataElement': + """Convert all tensors to NPU in data.""" + new_data = self.new() + for k, v_list in self.items(): + data_list = [] + for v in v_list: + if isinstance(v, (torch.Tensor, BaseDataElement)): + v = v.npu() + data_list.append(v) + if len(data_list) > 0: + new_data.set_data({f'{k}': data_list}) + return new_data + + # Tensor-like methods + def detach(self) -> 'BaseDataElement': + """Detach all tensors in data.""" + new_data = self.new() + for k, v_list in self.items(): + data_list = [] + for v in v_list: + if isinstance(v, (torch.Tensor, BaseDataElement)): + v = v.detach() + data_list.append(v) + if len(data_list) > 0: + new_data.set_data({f'{k}': data_list}) + return new_data + + # Tensor-like methods + def numpy(self) -> 'BaseDataElement': + """Convert all tensors to np.ndarray in data.""" + new_data = self.new() + for k, v_list in self.items(): + data_list = [] + for v in v_list: + if isinstance(v, (torch.Tensor, BaseDataElement)): + v = v.detach().cpu().numpy() + data_list.append(v) + if len(data_list) > 0: + new_data.set_data({f'{k}': data_list}) + return new_data + + def to_tensor(self) -> 'BaseDataElement': + """Convert all np.ndarray to tensor in data.""" + new_data = self.new() + for k, v_list in self.items(): + data_list = [] + for v in v_list: + if isinstance(v, np.ndarray): + v = torch.from_numpy(v) + elif isinstance(v, BaseDataElement): + v = v.to_tensor() + data_list.append(v) + if len(data_list) > 0: + new_data.set_data({f'{k}': data_list}) + return new_data + + # Tensor-like methods + def clone(self) -> 'BaseDataElement': + """Deep copy the current data element. + + Returns: + BaseDataElement: The copy of current data element. + """ + clone_data = self.__class__() + clone_data.set_metainfo(dict(self.metainfo_items())) + + for k, v_list in self.items(): + clone_item_list = [] + for v in v_list: + clone_item_list.append(v.clone()) + clone_data.set_data({k: clone_item_list}) + return clone_data + + +TrackSampleList = List[TrackDataSample] +OptTrackSampleList = Optional[TrackSampleList] diff --git a/mmdetection/mmdet/testing/__init__.py b/mmdetection/mmdet/testing/__init__.py new file mode 100644 index 00000000..766fb471 --- /dev/null +++ b/mmdetection/mmdet/testing/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ._fast_stop_training_hook import FastStopTrainingHook # noqa: F401,F403 +from ._utils import (demo_mm_inputs, demo_mm_proposals, + demo_mm_sampling_results, demo_track_inputs, + get_detector_cfg, get_roi_head_cfg, random_boxes, + replace_to_ceph) + +__all__ = [ + 'demo_mm_inputs', 'get_detector_cfg', 'get_roi_head_cfg', + 'demo_mm_proposals', 'demo_mm_sampling_results', 'replace_to_ceph', + 'demo_track_inputs', 'VideoDataSampleFeeder', 'random_boxes' +] diff --git a/mmdetection/mmdet/testing/_fast_stop_training_hook.py b/mmdetection/mmdet/testing/_fast_stop_training_hook.py new file mode 100644 index 00000000..f8e3d114 --- /dev/null +++ b/mmdetection/mmdet/testing/_fast_stop_training_hook.py @@ -0,0 +1,27 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import Hook + +from mmdet.registry import HOOKS + + +@HOOKS.register_module() +class FastStopTrainingHook(Hook): + """Set runner's epoch information to the model.""" + + def __init__(self, by_epoch, save_ckpt=False, stop_iter_or_epoch=5): + self.by_epoch = by_epoch + self.save_ckpt = save_ckpt + self.stop_iter_or_epoch = stop_iter_or_epoch + + def after_train_iter(self, runner, batch_idx: int, data_batch: None, + outputs: None) -> None: + if self.save_ckpt and self.by_epoch: + # If it is epoch-based and want to save weights, + # we must run at least 1 epoch. + return + if runner.iter >= self.stop_iter_or_epoch: + raise RuntimeError('quick exit') + + def after_train_epoch(self, runner) -> None: + if runner.epoch >= self.stop_iter_or_epoch - 1: + raise RuntimeError('quick exit') diff --git a/mmdetection/mmdet/testing/_utils.py b/mmdetection/mmdet/testing/_utils.py new file mode 100644 index 00000000..c4d3a86d --- /dev/null +++ b/mmdetection/mmdet/testing/_utils.py @@ -0,0 +1,469 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from os.path import dirname, exists, join + +import numpy as np +import torch +from mmengine.config import Config +from mmengine.dataset import pseudo_collate +from mmengine.structures import InstanceData, PixelData + +from mmdet.utils.util_random import ensure_rng +from ..registry import TASK_UTILS +from ..structures import DetDataSample, TrackDataSample +from ..structures.bbox import HorizontalBoxes + + +def _get_config_directory(): + """Find the predefined detector config directory.""" + try: + # Assume we are running in the source mmdetection repo + repo_dpath = dirname(dirname(dirname(__file__))) + except NameError: + # For IPython development when this __file__ is not defined + import mmdet + repo_dpath = dirname(dirname(mmdet.__file__)) + config_dpath = join(repo_dpath, 'configs') + if not exists(config_dpath): + raise Exception('Cannot find config path') + return config_dpath + + +def _get_config_module(fname): + """Load a configuration as a python module.""" + config_dpath = _get_config_directory() + config_fpath = join(config_dpath, fname) + config_mod = Config.fromfile(config_fpath) + return config_mod + + +def get_detector_cfg(fname): + """Grab configs necessary to create a detector. + + These are deep copied to allow for safe modification of parameters without + influencing other tests. + """ + config = _get_config_module(fname) + model = copy.deepcopy(config.model) + return model + + +def get_roi_head_cfg(fname): + """Grab configs necessary to create a roi_head. + + These are deep copied to allow for safe modification of parameters without + influencing other tests. + """ + config = _get_config_module(fname) + model = copy.deepcopy(config.model) + + roi_head = model.roi_head + train_cfg = None if model.train_cfg is None else model.train_cfg.rcnn + test_cfg = None if model.test_cfg is None else model.test_cfg.rcnn + roi_head.update(dict(train_cfg=train_cfg, test_cfg=test_cfg)) + return roi_head + + +def _rand_bboxes(rng, num_boxes, w, h): + cx, cy, bw, bh = rng.rand(num_boxes, 4).T + + tl_x = ((cx * w) - (w * bw / 2)).clip(0, w) + tl_y = ((cy * h) - (h * bh / 2)).clip(0, h) + br_x = ((cx * w) + (w * bw / 2)).clip(0, w) + br_y = ((cy * h) + (h * bh / 2)).clip(0, h) + + bboxes = np.vstack([tl_x, tl_y, br_x, br_y]).T + return bboxes + + +def _rand_masks(rng, num_boxes, bboxes, img_w, img_h): + from mmdet.structures.mask import BitmapMasks + masks = np.zeros((num_boxes, img_h, img_w)) + for i, bbox in enumerate(bboxes): + bbox = bbox.astype(np.int32) + mask = (rng.rand(1, bbox[3] - bbox[1], bbox[2] - bbox[0]) > + 0.3).astype(np.int64) + masks[i:i + 1, bbox[1]:bbox[3], bbox[0]:bbox[2]] = mask + return BitmapMasks(masks, height=img_h, width=img_w) + + +def demo_mm_inputs(batch_size=2, + image_shapes=(3, 128, 128), + num_items=None, + num_classes=10, + sem_seg_output_strides=1, + with_mask=False, + with_semantic=False, + use_box_type=False, + device='cpu', + texts=None, + custom_entities=False): + """Create a superset of inputs needed to run test or train batches. + + Args: + batch_size (int): batch size. Defaults to 2. + image_shapes (List[tuple], Optional): image shape. + Defaults to (3, 128, 128) + num_items (None | List[int]): specifies the number + of boxes in each batch item. Default to None. + num_classes (int): number of different labels a + box might have. Defaults to 10. + with_mask (bool): Whether to return mask annotation. + Defaults to False. + with_semantic (bool): whether to return semantic. + Defaults to False. + device (str): Destination device type. Defaults to cpu. + """ + rng = np.random.RandomState(0) + + if isinstance(image_shapes, list): + assert len(image_shapes) == batch_size + else: + image_shapes = [image_shapes] * batch_size + + if isinstance(num_items, list): + assert len(num_items) == batch_size + + if texts is not None: + assert batch_size == len(texts) + + packed_inputs = [] + for idx in range(batch_size): + image_shape = image_shapes[idx] + c, h, w = image_shape + + image = rng.randint(0, 255, size=image_shape, dtype=np.uint8) + + mm_inputs = dict() + mm_inputs['inputs'] = torch.from_numpy(image).to(device) + + img_meta = { + 'img_id': idx, + 'img_shape': image_shape[1:], + 'ori_shape': image_shape[1:], + 'filename': '.png', + 'scale_factor': np.array([1.1, 1.2]), + 'flip': False, + 'flip_direction': None, + 'border': [1, 1, 1, 1] # Only used by CenterNet + } + + if texts: + img_meta['text'] = texts[idx] + img_meta['custom_entities'] = custom_entities + + data_sample = DetDataSample() + data_sample.set_metainfo(img_meta) + + # gt_instances + gt_instances = InstanceData() + if num_items is None: + num_boxes = rng.randint(1, 10) + else: + num_boxes = num_items[idx] + + bboxes = _rand_bboxes(rng, num_boxes, w, h) + labels = rng.randint(1, num_classes, size=num_boxes) + # TODO: remove this part when all model adapted with BaseBoxes + if use_box_type: + gt_instances.bboxes = HorizontalBoxes(bboxes, dtype=torch.float32) + else: + gt_instances.bboxes = torch.FloatTensor(bboxes) + gt_instances.labels = torch.LongTensor(labels) + + if with_mask: + masks = _rand_masks(rng, num_boxes, bboxes, w, h) + gt_instances.masks = masks + + # TODO: waiting for ci to be fixed + # masks = np.random.randint(0, 2, (len(bboxes), h, w), dtype=np.uint8) + # gt_instances.mask = BitmapMasks(masks, h, w) + + data_sample.gt_instances = gt_instances + + # ignore_instances + ignore_instances = InstanceData() + bboxes = _rand_bboxes(rng, num_boxes, w, h) + if use_box_type: + ignore_instances.bboxes = HorizontalBoxes( + bboxes, dtype=torch.float32) + else: + ignore_instances.bboxes = torch.FloatTensor(bboxes) + data_sample.ignored_instances = ignore_instances + + # gt_sem_seg + if with_semantic: + # assume gt_semantic_seg using scale 1/8 of the img + gt_semantic_seg = torch.from_numpy( + np.random.randint( + 0, + num_classes, (1, h // sem_seg_output_strides, + w // sem_seg_output_strides), + dtype=np.uint8)) + gt_sem_seg_data = dict(sem_seg=gt_semantic_seg) + data_sample.gt_sem_seg = PixelData(**gt_sem_seg_data) + + mm_inputs['data_samples'] = data_sample.to(device) + + # TODO: gt_ignore + + packed_inputs.append(mm_inputs) + data = pseudo_collate(packed_inputs) + return data + + +def demo_mm_proposals(image_shapes, num_proposals, device='cpu'): + """Create a list of fake porposals. + + Args: + image_shapes (list[tuple[int]]): Batch image shapes. + num_proposals (int): The number of fake proposals. + """ + rng = np.random.RandomState(0) + + results = [] + for img_shape in image_shapes: + result = InstanceData() + w, h = img_shape[1:] + proposals = _rand_bboxes(rng, num_proposals, w, h) + result.bboxes = torch.from_numpy(proposals).float() + result.scores = torch.from_numpy(rng.rand(num_proposals)).float() + result.labels = torch.zeros(num_proposals).long() + results.append(result.to(device)) + return results + + +def demo_mm_sampling_results(proposals_list, + batch_gt_instances, + batch_gt_instances_ignore=None, + assigner_cfg=None, + sampler_cfg=None, + feats=None): + """Create sample results that can be passed to BBoxHead.get_targets.""" + assert len(proposals_list) == len(batch_gt_instances) + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None for _ in batch_gt_instances] + else: + assert len(batch_gt_instances_ignore) == len(batch_gt_instances) + + default_assigner_cfg = dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + ignore_iof_thr=-1) + assigner_cfg = assigner_cfg if assigner_cfg is not None \ + else default_assigner_cfg + default_sampler_cfg = dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True) + sampler_cfg = sampler_cfg if sampler_cfg is not None \ + else default_sampler_cfg + bbox_assigner = TASK_UTILS.build(assigner_cfg) + bbox_sampler = TASK_UTILS.build(sampler_cfg) + + sampling_results = [] + for i in range(len(batch_gt_instances)): + if feats is not None: + feats = [lvl_feat[i][None] for lvl_feat in feats] + # rename proposals.bboxes to proposals.priors + proposals = proposals_list[i] + proposals.priors = proposals.pop('bboxes') + + assign_result = bbox_assigner.assign(proposals, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = bbox_sampler.sample( + assign_result, proposals, batch_gt_instances[i], feats=feats) + sampling_results.append(sampling_result) + + return sampling_results + + +def demo_track_inputs(batch_size=1, + num_frames=2, + key_frames_inds=None, + image_shapes=(3, 128, 128), + num_items=None, + num_classes=1, + with_mask=False, + with_semantic=False): + """Create a superset of inputs needed to run test or train batches. + + Args: + batch_size (int): batch size. Default to 1. + num_frames (int): The number of frames. + key_frames_inds (List): The indices of key frames. + image_shapes (List[tuple], Optional): image shape. + Default to (3, 128, 128) + num_items (None | List[int]): specifies the number + of boxes in each batch item. Default to None. + num_classes (int): number of different labels a + box might have. Default to 1. + with_mask (bool): Whether to return mask annotation. + Defaults to False. + with_semantic (bool): whether to return semantic. + Default to False. + """ + rng = np.random.RandomState(0) + + # Make sure the length of image_shapes is equal to ``batch_size`` + if isinstance(image_shapes, list): + assert len(image_shapes) == batch_size + else: + image_shapes = [image_shapes] * batch_size + + packed_inputs = [] + for idx in range(batch_size): + mm_inputs = dict(inputs=dict()) + _, h, w = image_shapes[idx] + + imgs = rng.randint( + 0, 255, size=(num_frames, *image_shapes[idx]), dtype=np.uint8) + mm_inputs['inputs'] = torch.from_numpy(imgs) + + img_meta = { + 'img_id': idx, + 'img_shape': image_shapes[idx][-2:], + 'ori_shape': image_shapes[idx][-2:], + 'filename': '.png', + 'scale_factor': np.array([1.1, 1.2]), + 'flip': False, + 'flip_direction': None, + 'is_video_data': True, + } + + video_data_samples = [] + for i in range(num_frames): + data_sample = DetDataSample() + img_meta['frame_id'] = i + data_sample.set_metainfo(img_meta) + + # gt_instances + gt_instances = InstanceData() + if num_items is None: + num_boxes = rng.randint(1, 10) + else: + num_boxes = num_items[idx] + + bboxes = _rand_bboxes(rng, num_boxes, w, h) + labels = rng.randint(0, num_classes, size=num_boxes) + instances_id = rng.randint(100, num_classes + 100, size=num_boxes) + gt_instances.bboxes = torch.FloatTensor(bboxes) + gt_instances.labels = torch.LongTensor(labels) + gt_instances.instances_ids = torch.LongTensor(instances_id) + + if with_mask: + masks = _rand_masks(rng, num_boxes, bboxes, w, h) + gt_instances.masks = masks + + data_sample.gt_instances = gt_instances + # ignore_instances + ignore_instances = InstanceData() + bboxes = _rand_bboxes(rng, num_boxes, w, h) + ignore_instances.bboxes = bboxes + data_sample.ignored_instances = ignore_instances + + video_data_samples.append(data_sample) + + track_data_sample = TrackDataSample() + track_data_sample.video_data_samples = video_data_samples + if key_frames_inds is not None: + assert isinstance( + key_frames_inds, + list) and len(key_frames_inds) < num_frames and max( + key_frames_inds) < num_frames + ref_frames_inds = [ + i for i in range(num_frames) if i not in key_frames_inds + ] + track_data_sample.set_metainfo( + dict(key_frames_inds=key_frames_inds)) + track_data_sample.set_metainfo( + dict(ref_frames_inds=ref_frames_inds)) + mm_inputs['data_samples'] = track_data_sample + + # TODO: gt_ignore + packed_inputs.append(mm_inputs) + data = pseudo_collate(packed_inputs) + return data + + +def random_boxes(num=1, scale=1, rng=None): + """Simple version of ``kwimage.Boxes.random`` + Returns: + Tensor: shape (n, 4) in x1, y1, x2, y2 format. + References: + https://gitlab.kitware.com/computer-vision/kwimage/blob/master/kwimage/structs/boxes.py#L1390 # noqa: E501 + Example: + >>> num = 3 + >>> scale = 512 + >>> rng = 0 + >>> boxes = random_boxes(num, scale, rng) + >>> print(boxes) + tensor([[280.9925, 278.9802, 308.6148, 366.1769], + [216.9113, 330.6978, 224.0446, 456.5878], + [405.3632, 196.3221, 493.3953, 270.7942]]) + """ + rng = ensure_rng(rng) + + tlbr = rng.rand(num, 4).astype(np.float32) + + tl_x = np.minimum(tlbr[:, 0], tlbr[:, 2]) + tl_y = np.minimum(tlbr[:, 1], tlbr[:, 3]) + br_x = np.maximum(tlbr[:, 0], tlbr[:, 2]) + br_y = np.maximum(tlbr[:, 1], tlbr[:, 3]) + + tlbr[:, 0] = tl_x * scale + tlbr[:, 1] = tl_y * scale + tlbr[:, 2] = br_x * scale + tlbr[:, 3] = br_y * scale + + boxes = torch.from_numpy(tlbr) + return boxes + + +# TODO: Support full ceph +def replace_to_ceph(cfg): + backend_args = dict( + backend='petrel', + path_mapping=dict({ + './data/': 's3://openmmlab/datasets/detection/', + 'data/': 's3://openmmlab/datasets/detection/' + })) + + # TODO: name is a reserved interface, which will be used later. + def _process_pipeline(dataset, name): + + def replace_img(pipeline): + if pipeline['type'] == 'LoadImageFromFile': + pipeline['backend_args'] = backend_args + + def replace_ann(pipeline): + if pipeline['type'] == 'LoadAnnotations' or pipeline[ + 'type'] == 'LoadPanopticAnnotations': + pipeline['backend_args'] = backend_args + + if 'pipeline' in dataset: + replace_img(dataset.pipeline[0]) + replace_ann(dataset.pipeline[1]) + if 'dataset' in dataset: + # dataset wrapper + replace_img(dataset.dataset.pipeline[0]) + replace_ann(dataset.dataset.pipeline[1]) + else: + # dataset wrapper + replace_img(dataset.dataset.pipeline[0]) + replace_ann(dataset.dataset.pipeline[1]) + + def _process_evaluator(evaluator, name): + if evaluator['type'] == 'CocoPanopticMetric': + evaluator['backend_args'] = backend_args + + # half ceph + _process_pipeline(cfg.train_dataloader.dataset, cfg.filename) + _process_pipeline(cfg.val_dataloader.dataset, cfg.filename) + _process_pipeline(cfg.test_dataloader.dataset, cfg.filename) + _process_evaluator(cfg.val_evaluator, cfg.filename) + _process_evaluator(cfg.test_evaluator, cfg.filename) diff --git a/mmdetection/mmdet/utils/__init__.py b/mmdetection/mmdet/utils/__init__.py new file mode 100644 index 00000000..449a890b --- /dev/null +++ b/mmdetection/mmdet/utils/__init__.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .collect_env import collect_env +from .compat_config import compat_cfg +from .dist_utils import (all_reduce_dict, allreduce_grads, reduce_mean, + sync_random_seed) +from .logger import get_caller_name, log_img_scale +from .memory import AvoidCUDAOOM, AvoidOOM +from .misc import (find_latest_checkpoint, get_test_pipeline_cfg, + update_data_root) +from .mot_error_visualize import imshow_mot_errors +from .replace_cfg_vals import replace_cfg_vals +from .setup_env import (register_all_modules, setup_cache_size_limit_of_dynamo, + setup_multi_processes) +from .split_batch import split_batch +from .typing_utils import (ConfigType, InstanceList, MultiConfig, + OptConfigType, OptInstanceList, OptMultiConfig, + OptPixelList, PixelList, RangeType) + +__all__ = [ + 'collect_env', 'find_latest_checkpoint', 'update_data_root', + 'setup_multi_processes', 'get_caller_name', 'log_img_scale', 'compat_cfg', + 'split_batch', 'register_all_modules', 'replace_cfg_vals', 'AvoidOOM', + 'AvoidCUDAOOM', 'all_reduce_dict', 'allreduce_grads', 'reduce_mean', + 'sync_random_seed', 'ConfigType', 'InstanceList', 'MultiConfig', + 'OptConfigType', 'OptInstanceList', 'OptMultiConfig', 'OptPixelList', + 'PixelList', 'RangeType', 'get_test_pipeline_cfg', + 'setup_cache_size_limit_of_dynamo', 'imshow_mot_errors' +] diff --git a/mmdetection/mmdet/utils/benchmark.py b/mmdetection/mmdet/utils/benchmark.py new file mode 100644 index 00000000..5419b2d1 --- /dev/null +++ b/mmdetection/mmdet/utils/benchmark.py @@ -0,0 +1,529 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import time +from functools import partial +from typing import List, Optional, Union + +import numpy as np +import torch +import torch.nn as nn +from mmcv.cnn import fuse_conv_bn +# TODO need update +# from mmcv.runner import wrap_fp16_model +from mmengine import MMLogger +from mmengine.config import Config +from mmengine.device import get_max_cuda_memory +from mmengine.dist import get_world_size +from mmengine.runner import Runner, load_checkpoint +from mmengine.utils.dl_utils import set_multi_processing +from torch.nn.parallel import DistributedDataParallel + +from mmdet.registry import DATASETS, MODELS + +try: + import psutil +except ImportError: + psutil = None + + +def custom_round(value: Union[int, float], + factor: Union[int, float], + precision: int = 2) -> float: + """Custom round function.""" + return round(value / factor, precision) + + +gb_round = partial(custom_round, factor=1024**3) + + +def print_log(msg: str, logger: Optional[MMLogger] = None) -> None: + """Print a log message.""" + if logger is None: + print(msg, flush=True) + else: + logger.info(msg) + + +def print_process_memory(p: psutil.Process, + logger: Optional[MMLogger] = None) -> None: + """print process memory info.""" + mem_used = gb_round(psutil.virtual_memory().used) + memory_full_info = p.memory_full_info() + uss_mem = gb_round(memory_full_info.uss) + if hasattr(memory_full_info, 'pss'): + pss_mem = gb_round(memory_full_info.pss) + + for children in p.children(): + child_mem_info = children.memory_full_info() + uss_mem += gb_round(child_mem_info.uss) + if hasattr(child_mem_info, 'pss'): + pss_mem += gb_round(child_mem_info.pss) + + process_count = 1 + len(p.children()) + + log_msg = f'(GB) mem_used: {mem_used:.2f} | uss: {uss_mem:.2f} | ' + if hasattr(memory_full_info, 'pss'): + log_msg += f'pss: {pss_mem:.2f} | ' + log_msg += f'total_proc: {process_count}' + print_log(log_msg, logger) + + +class BaseBenchmark: + """The benchmark base class. + + The ``run`` method is an external calling interface, and it will + call the ``run_once`` method ``repeat_num`` times for benchmarking. + Finally, call the ``average_multiple_runs`` method to further process + the results of multiple runs. + + Args: + max_iter (int): maximum iterations of benchmark. + log_interval (int): interval of logging. + num_warmup (int): Number of Warmup. + logger (MMLogger, optional): Formatted logger used to record messages. + """ + + def __init__(self, + max_iter: int, + log_interval: int, + num_warmup: int, + logger: Optional[MMLogger] = None): + self.max_iter = max_iter + self.log_interval = log_interval + self.num_warmup = num_warmup + self.logger = logger + + def run(self, repeat_num: int = 1) -> dict: + """benchmark entry method. + + Args: + repeat_num (int): Number of repeat benchmark. + Defaults to 1. + """ + assert repeat_num >= 1 + + results = [] + for _ in range(repeat_num): + results.append(self.run_once()) + + results = self.average_multiple_runs(results) + return results + + def run_once(self) -> dict: + """Executes the benchmark once.""" + raise NotImplementedError() + + def average_multiple_runs(self, results: List[dict]) -> dict: + """Average the results of multiple runs.""" + raise NotImplementedError() + + +class InferenceBenchmark(BaseBenchmark): + """The inference benchmark class. It will be statistical inference FPS, + CUDA memory and CPU memory information. + + Args: + cfg (mmengine.Config): config. + checkpoint (str): Accept local filepath, URL, ``torchvision://xxx``, + ``open-mmlab://xxx``. + distributed (bool): distributed testing flag. + is_fuse_conv_bn (bool): Whether to fuse conv and bn, this will + slightly increase the inference speed. + max_iter (int): maximum iterations of benchmark. Defaults to 2000. + log_interval (int): interval of logging. Defaults to 50. + num_warmup (int): Number of Warmup. Defaults to 5. + logger (MMLogger, optional): Formatted logger used to record messages. + """ + + def __init__(self, + cfg: Config, + checkpoint: str, + distributed: bool, + is_fuse_conv_bn: bool, + max_iter: int = 2000, + log_interval: int = 50, + num_warmup: int = 5, + logger: Optional[MMLogger] = None): + super().__init__(max_iter, log_interval, num_warmup, logger) + + assert get_world_size( + ) == 1, 'Inference benchmark does not allow distributed multi-GPU' + + self.cfg = copy.deepcopy(cfg) + self.distributed = distributed + + if psutil is None: + raise ImportError('psutil is not installed, please install it by: ' + 'pip install psutil') + + self._process = psutil.Process() + env_cfg = self.cfg.get('env_cfg') + if env_cfg.get('cudnn_benchmark'): + torch.backends.cudnn.benchmark = True + + mp_cfg: dict = env_cfg.get('mp_cfg', {}) + set_multi_processing(**mp_cfg, distributed=self.distributed) + + print_log('before build: ', self.logger) + print_process_memory(self._process, self.logger) + + self.model = self._init_model(checkpoint, is_fuse_conv_bn) + + # Because multiple processes will occupy additional CPU resources, + # FPS statistics will be more unstable when num_workers is not 0. + # It is reasonable to set num_workers to 0. + dataloader_cfg = cfg.test_dataloader + dataloader_cfg['num_workers'] = 0 + dataloader_cfg['batch_size'] = 1 + dataloader_cfg['persistent_workers'] = False + self.data_loader = Runner.build_dataloader(dataloader_cfg) + + print_log('after build: ', self.logger) + print_process_memory(self._process, self.logger) + + def _init_model(self, checkpoint: str, is_fuse_conv_bn: bool) -> nn.Module: + """Initialize the model.""" + model = MODELS.build(self.cfg.model) + # TODO need update + # fp16_cfg = self.cfg.get('fp16', None) + # if fp16_cfg is not None: + # wrap_fp16_model(model) + + load_checkpoint(model, checkpoint, map_location='cpu') + if is_fuse_conv_bn: + model = fuse_conv_bn(model) + + model = model.cuda() + + if self.distributed: + model = DistributedDataParallel( + model, + device_ids=[torch.cuda.current_device()], + broadcast_buffers=False, + find_unused_parameters=False) + + model.eval() + return model + + def run_once(self) -> dict: + """Executes the benchmark once.""" + pure_inf_time = 0 + fps = 0 + + for i, data in enumerate(self.data_loader): + + if (i + 1) % self.log_interval == 0: + print_log('==================================', self.logger) + + torch.cuda.synchronize() + start_time = time.perf_counter() + + with torch.no_grad(): + self.model.test_step(data) + + torch.cuda.synchronize() + elapsed = time.perf_counter() - start_time + + if i >= self.num_warmup: + pure_inf_time += elapsed + if (i + 1) % self.log_interval == 0: + fps = (i + 1 - self.num_warmup) / pure_inf_time + cuda_memory = get_max_cuda_memory() + + print_log( + f'Done image [{i + 1:<3}/{self.max_iter}], ' + f'fps: {fps:.1f} img/s, ' + f'times per image: {1000 / fps:.1f} ms/img, ' + f'cuda memory: {cuda_memory} MB', self.logger) + print_process_memory(self._process, self.logger) + + if (i + 1) == self.max_iter: + fps = (i + 1 - self.num_warmup) / pure_inf_time + break + + return {'fps': fps} + + def average_multiple_runs(self, results: List[dict]) -> dict: + """Average the results of multiple runs.""" + print_log('============== Done ==================', self.logger) + + fps_list_ = [round(result['fps'], 1) for result in results] + avg_fps_ = sum(fps_list_) / len(fps_list_) + outputs = {'avg_fps': avg_fps_, 'fps_list': fps_list_} + + if len(fps_list_) > 1: + times_pre_image_list_ = [ + round(1000 / result['fps'], 1) for result in results + ] + avg_times_pre_image_ = sum(times_pre_image_list_) / len( + times_pre_image_list_) + + print_log( + f'Overall fps: {fps_list_}[{avg_fps_:.1f}] img/s, ' + 'times per image: ' + f'{times_pre_image_list_}[{avg_times_pre_image_:.1f}] ' + 'ms/img', self.logger) + else: + print_log( + f'Overall fps: {fps_list_[0]:.1f} img/s, ' + f'times per image: {1000 / fps_list_[0]:.1f} ms/img', + self.logger) + + print_log(f'cuda memory: {get_max_cuda_memory()} MB', self.logger) + print_process_memory(self._process, self.logger) + + return outputs + + +class DataLoaderBenchmark(BaseBenchmark): + """The dataloader benchmark class. It will be statistical inference FPS and + CPU memory information. + + Args: + cfg (mmengine.Config): config. + distributed (bool): distributed testing flag. + dataset_type (str): benchmark data type, only supports ``train``, + ``val`` and ``test``. + max_iter (int): maximum iterations of benchmark. Defaults to 2000. + log_interval (int): interval of logging. Defaults to 50. + num_warmup (int): Number of Warmup. Defaults to 5. + logger (MMLogger, optional): Formatted logger used to record messages. + """ + + def __init__(self, + cfg: Config, + distributed: bool, + dataset_type: str, + max_iter: int = 2000, + log_interval: int = 50, + num_warmup: int = 5, + logger: Optional[MMLogger] = None): + super().__init__(max_iter, log_interval, num_warmup, logger) + + assert dataset_type in ['train', 'val', 'test'], \ + 'dataset_type only supports train,' \ + f' val and test, but got {dataset_type}' + assert get_world_size( + ) == 1, 'Dataloader benchmark does not allow distributed multi-GPU' + + self.cfg = copy.deepcopy(cfg) + self.distributed = distributed + + if psutil is None: + raise ImportError('psutil is not installed, please install it by: ' + 'pip install psutil') + self._process = psutil.Process() + + mp_cfg = self.cfg.get('env_cfg', {}).get('mp_cfg') + if mp_cfg is not None: + set_multi_processing(distributed=self.distributed, **mp_cfg) + else: + set_multi_processing(distributed=self.distributed) + + print_log('before build: ', self.logger) + print_process_memory(self._process, self.logger) + + if dataset_type == 'train': + self.data_loader = Runner.build_dataloader(cfg.train_dataloader) + elif dataset_type == 'test': + self.data_loader = Runner.build_dataloader(cfg.test_dataloader) + else: + self.data_loader = Runner.build_dataloader(cfg.val_dataloader) + + self.batch_size = self.data_loader.batch_size + self.num_workers = self.data_loader.num_workers + + print_log('after build: ', self.logger) + print_process_memory(self._process, self.logger) + + def run_once(self) -> dict: + """Executes the benchmark once.""" + pure_inf_time = 0 + fps = 0 + + # benchmark with 2000 image and take the average + start_time = time.perf_counter() + for i, data in enumerate(self.data_loader): + elapsed = time.perf_counter() - start_time + + if (i + 1) % self.log_interval == 0: + print_log('==================================', self.logger) + + if i >= self.num_warmup: + pure_inf_time += elapsed + if (i + 1) % self.log_interval == 0: + fps = (i + 1 - self.num_warmup) / pure_inf_time + + print_log( + f'Done batch [{i + 1:<3}/{self.max_iter}], ' + f'fps: {fps:.1f} batch/s, ' + f'times per batch: {1000 / fps:.1f} ms/batch, ' + f'batch size: {self.batch_size}, num_workers: ' + f'{self.num_workers}', self.logger) + print_process_memory(self._process, self.logger) + + if (i + 1) == self.max_iter: + fps = (i + 1 - self.num_warmup) / pure_inf_time + break + + start_time = time.perf_counter() + + return {'fps': fps} + + def average_multiple_runs(self, results: List[dict]) -> dict: + """Average the results of multiple runs.""" + print_log('============== Done ==================', self.logger) + + fps_list_ = [round(result['fps'], 1) for result in results] + avg_fps_ = sum(fps_list_) / len(fps_list_) + outputs = {'avg_fps': avg_fps_, 'fps_list': fps_list_} + + if len(fps_list_) > 1: + times_pre_image_list_ = [ + round(1000 / result['fps'], 1) for result in results + ] + avg_times_pre_image_ = sum(times_pre_image_list_) / len( + times_pre_image_list_) + + print_log( + f'Overall fps: {fps_list_}[{avg_fps_:.1f}] img/s, ' + 'times per batch: ' + f'{times_pre_image_list_}[{avg_times_pre_image_:.1f}] ' + f'ms/batch, batch size: {self.batch_size}, num_workers: ' + f'{self.num_workers}', self.logger) + else: + print_log( + f'Overall fps: {fps_list_[0]:.1f} batch/s, ' + f'times per batch: {1000 / fps_list_[0]:.1f} ms/batch, ' + f'batch size: {self.batch_size}, num_workers: ' + f'{self.num_workers}', self.logger) + + print_process_memory(self._process, self.logger) + + return outputs + + +class DatasetBenchmark(BaseBenchmark): + """The dataset benchmark class. It will be statistical inference FPS, FPS + pre transform and CPU memory information. + + Args: + cfg (mmengine.Config): config. + dataset_type (str): benchmark data type, only supports ``train``, + ``val`` and ``test``. + max_iter (int): maximum iterations of benchmark. Defaults to 2000. + log_interval (int): interval of logging. Defaults to 50. + num_warmup (int): Number of Warmup. Defaults to 5. + logger (MMLogger, optional): Formatted logger used to record messages. + """ + + def __init__(self, + cfg: Config, + dataset_type: str, + max_iter: int = 2000, + log_interval: int = 50, + num_warmup: int = 5, + logger: Optional[MMLogger] = None): + super().__init__(max_iter, log_interval, num_warmup, logger) + assert dataset_type in ['train', 'val', 'test'], \ + 'dataset_type only supports train,' \ + f' val and test, but got {dataset_type}' + assert get_world_size( + ) == 1, 'Dataset benchmark does not allow distributed multi-GPU' + self.cfg = copy.deepcopy(cfg) + + if dataset_type == 'train': + dataloader_cfg = copy.deepcopy(cfg.train_dataloader) + elif dataset_type == 'test': + dataloader_cfg = copy.deepcopy(cfg.test_dataloader) + else: + dataloader_cfg = copy.deepcopy(cfg.val_dataloader) + + dataset_cfg = dataloader_cfg.pop('dataset') + dataset = DATASETS.build(dataset_cfg) + if hasattr(dataset, 'full_init'): + dataset.full_init() + self.dataset = dataset + + def run_once(self) -> dict: + """Executes the benchmark once.""" + pure_inf_time = 0 + fps = 0 + + total_index = list(range(len(self.dataset))) + np.random.shuffle(total_index) + + start_time = time.perf_counter() + for i, idx in enumerate(total_index): + if (i + 1) % self.log_interval == 0: + print_log('==================================', self.logger) + + get_data_info_start_time = time.perf_counter() + data_info = self.dataset.get_data_info(idx) + get_data_info_elapsed = time.perf_counter( + ) - get_data_info_start_time + + if (i + 1) % self.log_interval == 0: + print_log(f'get_data_info - {get_data_info_elapsed * 1000} ms', + self.logger) + + for t in self.dataset.pipeline.transforms: + transform_start_time = time.perf_counter() + data_info = t(data_info) + transform_elapsed = time.perf_counter() - transform_start_time + + if (i + 1) % self.log_interval == 0: + print_log( + f'{t.__class__.__name__} - ' + f'{transform_elapsed * 1000} ms', self.logger) + + if data_info is None: + break + + elapsed = time.perf_counter() - start_time + + if i >= self.num_warmup: + pure_inf_time += elapsed + if (i + 1) % self.log_interval == 0: + fps = (i + 1 - self.num_warmup) / pure_inf_time + + print_log( + f'Done img [{i + 1:<3}/{self.max_iter}], ' + f'fps: {fps:.1f} img/s, ' + f'times per img: {1000 / fps:.1f} ms/img', self.logger) + + if (i + 1) == self.max_iter: + fps = (i + 1 - self.num_warmup) / pure_inf_time + break + + start_time = time.perf_counter() + + return {'fps': fps} + + def average_multiple_runs(self, results: List[dict]) -> dict: + """Average the results of multiple runs.""" + print_log('============== Done ==================', self.logger) + + fps_list_ = [round(result['fps'], 1) for result in results] + avg_fps_ = sum(fps_list_) / len(fps_list_) + outputs = {'avg_fps': avg_fps_, 'fps_list': fps_list_} + + if len(fps_list_) > 1: + times_pre_image_list_ = [ + round(1000 / result['fps'], 1) for result in results + ] + avg_times_pre_image_ = sum(times_pre_image_list_) / len( + times_pre_image_list_) + + print_log( + f'Overall fps: {fps_list_}[{avg_fps_:.1f}] img/s, ' + 'times per img: ' + f'{times_pre_image_list_}[{avg_times_pre_image_:.1f}] ' + 'ms/img', self.logger) + else: + print_log( + f'Overall fps: {fps_list_[0]:.1f} img/s, ' + f'times per img: {1000 / fps_list_[0]:.1f} ms/img', + self.logger) + + return outputs diff --git a/mmdetection/mmdet/utils/collect_env.py b/mmdetection/mmdet/utils/collect_env.py new file mode 100644 index 00000000..b0eed80f --- /dev/null +++ b/mmdetection/mmdet/utils/collect_env.py @@ -0,0 +1,17 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.utils import get_git_hash +from mmengine.utils.dl_utils import collect_env as collect_base_env + +import mmdet + + +def collect_env(): + """Collect the information of the running environments.""" + env_info = collect_base_env() + env_info['MMDetection'] = mmdet.__version__ + '+' + get_git_hash()[:7] + return env_info + + +if __name__ == '__main__': + for name, val in collect_env().items(): + print(f'{name}: {val}') diff --git a/mmdetection/mmdet/utils/compat_config.py b/mmdetection/mmdet/utils/compat_config.py new file mode 100644 index 00000000..133adb65 --- /dev/null +++ b/mmdetection/mmdet/utils/compat_config.py @@ -0,0 +1,139 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import warnings + +from mmengine.config import ConfigDict + + +def compat_cfg(cfg): + """This function would modify some filed to keep the compatibility of + config. + + For example, it will move some args which will be deprecated to the correct + fields. + """ + cfg = copy.deepcopy(cfg) + cfg = compat_imgs_per_gpu(cfg) + cfg = compat_loader_args(cfg) + cfg = compat_runner_args(cfg) + return cfg + + +def compat_runner_args(cfg): + if 'runner' not in cfg: + cfg.runner = ConfigDict({ + 'type': 'EpochBasedRunner', + 'max_epochs': cfg.total_epochs + }) + warnings.warn( + 'config is now expected to have a `runner` section, ' + 'please set `runner` in your config.', UserWarning) + else: + if 'total_epochs' in cfg: + assert cfg.total_epochs == cfg.runner.max_epochs + return cfg + + +def compat_imgs_per_gpu(cfg): + cfg = copy.deepcopy(cfg) + if 'imgs_per_gpu' in cfg.data: + warnings.warn('"imgs_per_gpu" is deprecated in MMDet V2.0. ' + 'Please use "samples_per_gpu" instead') + if 'samples_per_gpu' in cfg.data: + warnings.warn( + f'Got "imgs_per_gpu"={cfg.data.imgs_per_gpu} and ' + f'"samples_per_gpu"={cfg.data.samples_per_gpu}, "imgs_per_gpu"' + f'={cfg.data.imgs_per_gpu} is used in this experiments') + else: + warnings.warn('Automatically set "samples_per_gpu"="imgs_per_gpu"=' + f'{cfg.data.imgs_per_gpu} in this experiments') + cfg.data.samples_per_gpu = cfg.data.imgs_per_gpu + return cfg + + +def compat_loader_args(cfg): + """Deprecated sample_per_gpu in cfg.data.""" + + cfg = copy.deepcopy(cfg) + if 'train_dataloader' not in cfg.data: + cfg.data['train_dataloader'] = ConfigDict() + if 'val_dataloader' not in cfg.data: + cfg.data['val_dataloader'] = ConfigDict() + if 'test_dataloader' not in cfg.data: + cfg.data['test_dataloader'] = ConfigDict() + + # special process for train_dataloader + if 'samples_per_gpu' in cfg.data: + + samples_per_gpu = cfg.data.pop('samples_per_gpu') + assert 'samples_per_gpu' not in \ + cfg.data.train_dataloader, ('`samples_per_gpu` are set ' + 'in `data` field and ` ' + 'data.train_dataloader` ' + 'at the same time. ' + 'Please only set it in ' + '`data.train_dataloader`. ') + cfg.data.train_dataloader['samples_per_gpu'] = samples_per_gpu + + if 'persistent_workers' in cfg.data: + + persistent_workers = cfg.data.pop('persistent_workers') + assert 'persistent_workers' not in \ + cfg.data.train_dataloader, ('`persistent_workers` are set ' + 'in `data` field and ` ' + 'data.train_dataloader` ' + 'at the same time. ' + 'Please only set it in ' + '`data.train_dataloader`. ') + cfg.data.train_dataloader['persistent_workers'] = persistent_workers + + if 'workers_per_gpu' in cfg.data: + + workers_per_gpu = cfg.data.pop('workers_per_gpu') + cfg.data.train_dataloader['workers_per_gpu'] = workers_per_gpu + cfg.data.val_dataloader['workers_per_gpu'] = workers_per_gpu + cfg.data.test_dataloader['workers_per_gpu'] = workers_per_gpu + + # special process for val_dataloader + if 'samples_per_gpu' in cfg.data.val: + # keep default value of `sample_per_gpu` is 1 + assert 'samples_per_gpu' not in \ + cfg.data.val_dataloader, ('`samples_per_gpu` are set ' + 'in `data.val` field and ` ' + 'data.val_dataloader` at ' + 'the same time. ' + 'Please only set it in ' + '`data.val_dataloader`. ') + cfg.data.val_dataloader['samples_per_gpu'] = \ + cfg.data.val.pop('samples_per_gpu') + # special process for val_dataloader + + # in case the test dataset is concatenated + if isinstance(cfg.data.test, dict): + if 'samples_per_gpu' in cfg.data.test: + assert 'samples_per_gpu' not in \ + cfg.data.test_dataloader, ('`samples_per_gpu` are set ' + 'in `data.test` field and ` ' + 'data.test_dataloader` ' + 'at the same time. ' + 'Please only set it in ' + '`data.test_dataloader`. ') + + cfg.data.test_dataloader['samples_per_gpu'] = \ + cfg.data.test.pop('samples_per_gpu') + + elif isinstance(cfg.data.test, list): + for ds_cfg in cfg.data.test: + if 'samples_per_gpu' in ds_cfg: + assert 'samples_per_gpu' not in \ + cfg.data.test_dataloader, ('`samples_per_gpu` are set ' + 'in `data.test` field and ` ' + 'data.test_dataloader` at' + ' the same time. ' + 'Please only set it in ' + '`data.test_dataloader`. ') + samples_per_gpu = max( + [ds_cfg.pop('samples_per_gpu', 1) for ds_cfg in cfg.data.test]) + cfg.data.test_dataloader['samples_per_gpu'] = samples_per_gpu + + return cfg diff --git a/mmdetection/mmdet/utils/contextmanagers.py b/mmdetection/mmdet/utils/contextmanagers.py new file mode 100644 index 00000000..fa12bfca --- /dev/null +++ b/mmdetection/mmdet/utils/contextmanagers.py @@ -0,0 +1,122 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import asyncio +import contextlib +import logging +import os +import time +from typing import List + +import torch + +logger = logging.getLogger(__name__) + +DEBUG_COMPLETED_TIME = bool(os.environ.get('DEBUG_COMPLETED_TIME', False)) + + +@contextlib.asynccontextmanager +async def completed(trace_name='', + name='', + sleep_interval=0.05, + streams: List[torch.cuda.Stream] = None): + """Async context manager that waits for work to complete on given CUDA + streams.""" + if not torch.cuda.is_available(): + yield + return + + stream_before_context_switch = torch.cuda.current_stream() + if not streams: + streams = [stream_before_context_switch] + else: + streams = [s if s else stream_before_context_switch for s in streams] + + end_events = [ + torch.cuda.Event(enable_timing=DEBUG_COMPLETED_TIME) for _ in streams + ] + + if DEBUG_COMPLETED_TIME: + start = torch.cuda.Event(enable_timing=True) + stream_before_context_switch.record_event(start) + + cpu_start = time.monotonic() + logger.debug('%s %s starting, streams: %s', trace_name, name, streams) + grad_enabled_before = torch.is_grad_enabled() + try: + yield + finally: + current_stream = torch.cuda.current_stream() + assert current_stream == stream_before_context_switch + + if DEBUG_COMPLETED_TIME: + cpu_end = time.monotonic() + for i, stream in enumerate(streams): + event = end_events[i] + stream.record_event(event) + + grad_enabled_after = torch.is_grad_enabled() + + # observed change of torch.is_grad_enabled() during concurrent run of + # async_test_bboxes code + assert (grad_enabled_before == grad_enabled_after + ), 'Unexpected is_grad_enabled() value change' + + are_done = [e.query() for e in end_events] + logger.debug('%s %s completed: %s streams: %s', trace_name, name, + are_done, streams) + with torch.cuda.stream(stream_before_context_switch): + while not all(are_done): + await asyncio.sleep(sleep_interval) + are_done = [e.query() for e in end_events] + logger.debug( + '%s %s completed: %s streams: %s', + trace_name, + name, + are_done, + streams, + ) + + current_stream = torch.cuda.current_stream() + assert current_stream == stream_before_context_switch + + if DEBUG_COMPLETED_TIME: + cpu_time = (cpu_end - cpu_start) * 1000 + stream_times_ms = '' + for i, stream in enumerate(streams): + elapsed_time = start.elapsed_time(end_events[i]) + stream_times_ms += f' {stream} {elapsed_time:.2f} ms' + logger.info('%s %s %.2f ms %s', trace_name, name, cpu_time, + stream_times_ms) + + +@contextlib.asynccontextmanager +async def concurrent(streamqueue: asyncio.Queue, + trace_name='concurrent', + name='stream'): + """Run code concurrently in different streams. + + :param streamqueue: asyncio.Queue instance. + + Queue tasks define the pool of streams used for concurrent execution. + """ + if not torch.cuda.is_available(): + yield + return + + initial_stream = torch.cuda.current_stream() + + with torch.cuda.stream(initial_stream): + stream = await streamqueue.get() + assert isinstance(stream, torch.cuda.Stream) + + try: + with torch.cuda.stream(stream): + logger.debug('%s %s is starting, stream: %s', trace_name, name, + stream) + yield + current = torch.cuda.current_stream() + assert current == stream + logger.debug('%s %s has finished, stream: %s', trace_name, + name, stream) + finally: + streamqueue.task_done() + streamqueue.put_nowait(stream) diff --git a/mmdetection/mmdet/utils/dist_utils.py b/mmdetection/mmdet/utils/dist_utils.py new file mode 100644 index 00000000..2f2c8614 --- /dev/null +++ b/mmdetection/mmdet/utils/dist_utils.py @@ -0,0 +1,184 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import functools +import pickle +import warnings +from collections import OrderedDict + +import numpy as np +import torch +import torch.distributed as dist +from mmengine.dist import get_dist_info +from torch._utils import (_flatten_dense_tensors, _take_tensors, + _unflatten_dense_tensors) + + +def _allreduce_coalesced(tensors, world_size, bucket_size_mb=-1): + if bucket_size_mb > 0: + bucket_size_bytes = bucket_size_mb * 1024 * 1024 + buckets = _take_tensors(tensors, bucket_size_bytes) + else: + buckets = OrderedDict() + for tensor in tensors: + tp = tensor.type() + if tp not in buckets: + buckets[tp] = [] + buckets[tp].append(tensor) + buckets = buckets.values() + + for bucket in buckets: + flat_tensors = _flatten_dense_tensors(bucket) + dist.all_reduce(flat_tensors) + flat_tensors.div_(world_size) + for tensor, synced in zip( + bucket, _unflatten_dense_tensors(flat_tensors, bucket)): + tensor.copy_(synced) + + +def allreduce_grads(params, coalesce=True, bucket_size_mb=-1): + """Allreduce gradients. + + Args: + params (list[torch.Parameters]): List of parameters of a model + coalesce (bool, optional): Whether allreduce parameters as a whole. + Defaults to True. + bucket_size_mb (int, optional): Size of bucket, the unit is MB. + Defaults to -1. + """ + grads = [ + param.grad.data for param in params + if param.requires_grad and param.grad is not None + ] + world_size = dist.get_world_size() + if coalesce: + _allreduce_coalesced(grads, world_size, bucket_size_mb) + else: + for tensor in grads: + dist.all_reduce(tensor.div_(world_size)) + + +def reduce_mean(tensor): + """"Obtain the mean of tensor on different GPUs.""" + if not (dist.is_available() and dist.is_initialized()): + return tensor + tensor = tensor.clone() + dist.all_reduce(tensor.div_(dist.get_world_size()), op=dist.ReduceOp.SUM) + return tensor + + +def obj2tensor(pyobj, device='cuda'): + """Serialize picklable python object to tensor.""" + storage = torch.ByteStorage.from_buffer(pickle.dumps(pyobj)) + return torch.ByteTensor(storage).to(device=device) + + +def tensor2obj(tensor): + """Deserialize tensor to picklable python object.""" + return pickle.loads(tensor.cpu().numpy().tobytes()) + + +@functools.lru_cache() +def _get_global_gloo_group(): + """Return a process group based on gloo backend, containing all the ranks + The result is cached.""" + if dist.get_backend() == 'nccl': + return dist.new_group(backend='gloo') + else: + return dist.group.WORLD + + +def all_reduce_dict(py_dict, op='sum', group=None, to_float=True): + """Apply all reduce function for python dict object. + + The code is modified from https://github.com/Megvii- + BaseDetection/YOLOX/blob/main/yolox/utils/allreduce_norm.py. + + NOTE: make sure that py_dict in different ranks has the same keys and + the values should be in the same shape. Currently only supports + nccl backend. + + Args: + py_dict (dict): Dict to be applied all reduce op. + op (str): Operator, could be 'sum' or 'mean'. Default: 'sum' + group (:obj:`torch.distributed.group`, optional): Distributed group, + Default: None. + to_float (bool): Whether to convert all values of dict to float. + Default: True. + + Returns: + OrderedDict: reduced python dict object. + """ + warnings.warn( + 'group` is deprecated. Currently only supports NCCL backend.') + _, world_size = get_dist_info() + if world_size == 1: + return py_dict + + # all reduce logic across different devices. + py_key = list(py_dict.keys()) + if not isinstance(py_dict, OrderedDict): + py_key_tensor = obj2tensor(py_key) + dist.broadcast(py_key_tensor, src=0) + py_key = tensor2obj(py_key_tensor) + + tensor_shapes = [py_dict[k].shape for k in py_key] + tensor_numels = [py_dict[k].numel() for k in py_key] + + if to_float: + warnings.warn('Note: the "to_float" is True, you need to ' + 'ensure that the behavior is reasonable.') + flatten_tensor = torch.cat( + [py_dict[k].flatten().float() for k in py_key]) + else: + flatten_tensor = torch.cat([py_dict[k].flatten() for k in py_key]) + + dist.all_reduce(flatten_tensor, op=dist.ReduceOp.SUM) + if op == 'mean': + flatten_tensor /= world_size + + split_tensors = [ + x.reshape(shape) for x, shape in zip( + torch.split(flatten_tensor, tensor_numels), tensor_shapes) + ] + out_dict = {k: v for k, v in zip(py_key, split_tensors)} + if isinstance(py_dict, OrderedDict): + out_dict = OrderedDict(out_dict) + return out_dict + + +def sync_random_seed(seed=None, device='cuda'): + """Make sure different ranks share the same seed. + + All workers must call this function, otherwise it will deadlock. + This method is generally used in `DistributedSampler`, + because the seed should be identical across all processes + in the distributed group. + + In distributed sampling, different ranks should sample non-overlapped + data in the dataset. Therefore, this function is used to make sure that + each rank shuffles the data indices in the same order based + on the same seed. Then different ranks could use different indices + to select non-overlapped data from the same data list. + + Args: + seed (int, Optional): The seed. Default to None. + device (str): The device where the seed will be put on. + Default to 'cuda'. + + Returns: + int: Seed to be used. + """ + if seed is None: + seed = np.random.randint(2**31) + assert isinstance(seed, int) + + rank, world_size = get_dist_info() + + if world_size == 1: + return seed + + if rank == 0: + random_num = torch.tensor(seed, dtype=torch.int32, device=device) + else: + random_num = torch.tensor(0, dtype=torch.int32, device=device) + dist.broadcast(random_num, src=0) + return random_num.item() diff --git a/mmdetection/mmdet/utils/large_image.py b/mmdetection/mmdet/utils/large_image.py new file mode 100644 index 00000000..f1f07c2b --- /dev/null +++ b/mmdetection/mmdet/utils/large_image.py @@ -0,0 +1,104 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Sequence, Tuple + +import torch +from mmcv.ops import batched_nms +from mmengine.structures import InstanceData + +from mmdet.structures import DetDataSample, SampleList + + +def shift_rbboxes(bboxes: torch.Tensor, offset: Sequence[int]): + """Shift rotated bboxes with offset. + + Args: + bboxes (Tensor): The rotated bboxes need to be translated. + With shape (n, 5), which means (x, y, w, h, a). + offset (Sequence[int]): The translation offsets with shape of (2, ). + Returns: + Tensor: Shifted rotated bboxes. + """ + offset_tensor = bboxes.new_tensor(offset) + shifted_bboxes = bboxes.clone() + shifted_bboxes[:, 0:2] = shifted_bboxes[:, 0:2] + offset_tensor + return shifted_bboxes + + +def shift_predictions(det_data_samples: SampleList, + offsets: Sequence[Tuple[int, int]], + src_image_shape: Tuple[int, int]) -> SampleList: + """Shift predictions to the original image. + + Args: + det_data_samples (List[:obj:`DetDataSample`]): A list of patch results. + offsets (Sequence[Tuple[int, int]]): Positions of the left top points + of patches. + src_image_shape (Tuple[int, int]): A (height, width) tuple of the large + image's width and height. + Returns: + (List[:obj:`DetDataSample`]): shifted results. + """ + try: + from sahi.slicing import shift_bboxes, shift_masks + except ImportError: + raise ImportError('Please run "pip install -U sahi" ' + 'to install sahi first for large image inference.') + + assert len(det_data_samples) == len( + offsets), 'The `results` should has the ' 'same length with `offsets`.' + shifted_predictions = [] + for det_data_sample, offset in zip(det_data_samples, offsets): + pred_inst = det_data_sample.pred_instances.clone() + + # Check bbox type + if pred_inst.bboxes.size(-1) == 4: + # Horizontal bboxes + shifted_bboxes = shift_bboxes(pred_inst.bboxes, offset) + elif pred_inst.bboxes.size(-1) == 5: + # Rotated bboxes + shifted_bboxes = shift_rbboxes(pred_inst.bboxes, offset) + else: + raise NotImplementedError + + # shift bboxes and masks + pred_inst.bboxes = shifted_bboxes + if 'masks' in det_data_sample: + pred_inst.masks = shift_masks(pred_inst.masks, offset, + src_image_shape) + + shifted_predictions.append(pred_inst.clone()) + + shifted_predictions = InstanceData.cat(shifted_predictions) + + return shifted_predictions + + +def merge_results_by_nms(results: SampleList, offsets: Sequence[Tuple[int, + int]], + src_image_shape: Tuple[int, int], + nms_cfg: dict) -> DetDataSample: + """Merge patch results by nms. + + Args: + results (List[:obj:`DetDataSample`]): A list of patch results. + offsets (Sequence[Tuple[int, int]]): Positions of the left top points + of patches. + src_image_shape (Tuple[int, int]): A (height, width) tuple of the large + image's width and height. + nms_cfg (dict): it should specify nms type and other parameters + like `iou_threshold`. + Returns: + :obj:`DetDataSample`: merged results. + """ + shifted_instances = shift_predictions(results, offsets, src_image_shape) + + _, keeps = batched_nms( + boxes=shifted_instances.bboxes, + scores=shifted_instances.scores, + idxs=shifted_instances.labels, + nms_cfg=nms_cfg) + merged_instances = shifted_instances[keeps] + + merged_result = results[0].clone() + merged_result.pred_instances = merged_instances + return merged_result diff --git a/mmdetection/mmdet/utils/logger.py b/mmdetection/mmdet/utils/logger.py new file mode 100644 index 00000000..9fec08bb --- /dev/null +++ b/mmdetection/mmdet/utils/logger.py @@ -0,0 +1,49 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import inspect + +from mmengine.logging import print_log + + +def get_caller_name(): + """Get name of caller method.""" + # this_func_frame = inspect.stack()[0][0] # i.e., get_caller_name + # callee_frame = inspect.stack()[1][0] # e.g., log_img_scale + caller_frame = inspect.stack()[2][0] # e.g., caller of log_img_scale + caller_method = caller_frame.f_code.co_name + try: + caller_class = caller_frame.f_locals['self'].__class__.__name__ + return f'{caller_class}.{caller_method}' + except KeyError: # caller is a function + return caller_method + + +def log_img_scale(img_scale, shape_order='hw', skip_square=False): + """Log image size. + + Args: + img_scale (tuple): Image size to be logged. + shape_order (str, optional): The order of image shape. + 'hw' for (height, width) and 'wh' for (width, height). + Defaults to 'hw'. + skip_square (bool, optional): Whether to skip logging for square + img_scale. Defaults to False. + + Returns: + bool: Whether to have done logging. + """ + if shape_order == 'hw': + height, width = img_scale + elif shape_order == 'wh': + width, height = img_scale + else: + raise ValueError(f'Invalid shape_order {shape_order}.') + + if skip_square and (height == width): + return False + + caller = get_caller_name() + print_log( + f'image shape: height={height}, width={width} in {caller}', + logger='current') + + return True diff --git a/mmdetection/mmdet/utils/memory.py b/mmdetection/mmdet/utils/memory.py new file mode 100644 index 00000000..b6f9cbc7 --- /dev/null +++ b/mmdetection/mmdet/utils/memory.py @@ -0,0 +1,212 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from collections import abc +from contextlib import contextmanager +from functools import wraps + +import torch +from mmengine.logging import MMLogger + + +def cast_tensor_type(inputs, src_type=None, dst_type=None): + """Recursively convert Tensor in inputs from ``src_type`` to ``dst_type``. + + Args: + inputs: Inputs that to be casted. + src_type (torch.dtype | torch.device): Source type. + src_type (torch.dtype | torch.device): Destination type. + + Returns: + The same type with inputs, but all contained Tensors have been cast. + """ + assert dst_type is not None + if isinstance(inputs, torch.Tensor): + if isinstance(dst_type, torch.device): + # convert Tensor to dst_device + if hasattr(inputs, 'to') and \ + hasattr(inputs, 'device') and \ + (inputs.device == src_type or src_type is None): + return inputs.to(dst_type) + else: + return inputs + else: + # convert Tensor to dst_dtype + if hasattr(inputs, 'to') and \ + hasattr(inputs, 'dtype') and \ + (inputs.dtype == src_type or src_type is None): + return inputs.to(dst_type) + else: + return inputs + # we need to ensure that the type of inputs to be casted are the same + # as the argument `src_type`. + elif isinstance(inputs, abc.Mapping): + return type(inputs)({ + k: cast_tensor_type(v, src_type=src_type, dst_type=dst_type) + for k, v in inputs.items() + }) + elif isinstance(inputs, abc.Iterable): + return type(inputs)( + cast_tensor_type(item, src_type=src_type, dst_type=dst_type) + for item in inputs) + # TODO: Currently not supported + # elif isinstance(inputs, InstanceData): + # for key, value in inputs.items(): + # inputs[key] = cast_tensor_type( + # value, src_type=src_type, dst_type=dst_type) + # return inputs + else: + return inputs + + +@contextmanager +def _ignore_torch_cuda_oom(): + """A context which ignores CUDA OOM exception from pytorch. + + Code is modified from + # noqa: E501 + """ + try: + yield + except RuntimeError as e: + # NOTE: the string may change? + if 'CUDA out of memory. ' in str(e): + pass + else: + raise + + +class AvoidOOM: + """Try to convert inputs to FP16 and CPU if got a PyTorch's CUDA Out of + Memory error. It will do the following steps: + + 1. First retry after calling `torch.cuda.empty_cache()`. + 2. If that still fails, it will then retry by converting inputs + to FP16. + 3. If that still fails trying to convert inputs to CPUs. + In this case, it expects the function to dispatch to + CPU implementation. + + Args: + to_cpu (bool): Whether to convert outputs to CPU if get an OOM + error. This will slow down the code significantly. + Defaults to True. + test (bool): Skip `_ignore_torch_cuda_oom` operate that can use + lightweight data in unit test, only used in + test unit. Defaults to False. + + Examples: + >>> from mmdet.utils.memory import AvoidOOM + >>> AvoidCUDAOOM = AvoidOOM() + >>> output = AvoidOOM.retry_if_cuda_oom( + >>> some_torch_function)(input1, input2) + >>> # To use as a decorator + >>> # from mmdet.utils import AvoidCUDAOOM + >>> @AvoidCUDAOOM.retry_if_cuda_oom + >>> def function(*args, **kwargs): + >>> return None + ``` + + Note: + 1. The output may be on CPU even if inputs are on GPU. Processing + on CPU will slow down the code significantly. + 2. When converting inputs to CPU, it will only look at each argument + and check if it has `.device` and `.to` for conversion. Nested + structures of tensors are not supported. + 3. Since the function might be called more than once, it has to be + stateless. + """ + + def __init__(self, to_cpu=True, test=False): + self.to_cpu = to_cpu + self.test = test + + def retry_if_cuda_oom(self, func): + """Makes a function retry itself after encountering pytorch's CUDA OOM + error. + + The implementation logic is referred to + https://github.com/facebookresearch/detectron2/blob/main/detectron2/utils/memory.py + + Args: + func: a stateless callable that takes tensor-like objects + as arguments. + Returns: + func: a callable which retries `func` if OOM is encountered. + """ # noqa: W605 + + @wraps(func) + def wrapped(*args, **kwargs): + + # raw function + if not self.test: + with _ignore_torch_cuda_oom(): + return func(*args, **kwargs) + + # Clear cache and retry + torch.cuda.empty_cache() + with _ignore_torch_cuda_oom(): + return func(*args, **kwargs) + + # get the type and device of first tensor + dtype, device = None, None + values = args + tuple(kwargs.values()) + for value in values: + if isinstance(value, torch.Tensor): + dtype = value.dtype + device = value.device + break + if dtype is None or device is None: + raise ValueError('There is no tensor in the inputs, ' + 'cannot get dtype and device.') + + # Convert to FP16 + fp16_args = cast_tensor_type(args, dst_type=torch.half) + fp16_kwargs = cast_tensor_type(kwargs, dst_type=torch.half) + logger = MMLogger.get_current_instance() + logger.warning(f'Attempting to copy inputs of {str(func)} ' + 'to FP16 due to CUDA OOM') + + # get input tensor type, the output type will same as + # the first parameter type. + with _ignore_torch_cuda_oom(): + output = func(*fp16_args, **fp16_kwargs) + output = cast_tensor_type( + output, src_type=torch.half, dst_type=dtype) + if not self.test: + return output + logger.warning('Using FP16 still meet CUDA OOM') + + # Try on CPU. This will slow down the code significantly, + # therefore print a notice. + if self.to_cpu: + logger.warning(f'Attempting to copy inputs of {str(func)} ' + 'to CPU due to CUDA OOM') + cpu_device = torch.empty(0).device + cpu_args = cast_tensor_type(args, dst_type=cpu_device) + cpu_kwargs = cast_tensor_type(kwargs, dst_type=cpu_device) + + # convert outputs to GPU + with _ignore_torch_cuda_oom(): + logger.warning(f'Convert outputs to GPU (device={device})') + output = func(*cpu_args, **cpu_kwargs) + output = cast_tensor_type( + output, src_type=cpu_device, dst_type=device) + return output + + warnings.warn('Cannot convert output to GPU due to CUDA OOM, ' + 'the output is now on CPU, which might cause ' + 'errors if the output need to interact with GPU ' + 'data in subsequent operations') + logger.warning('Cannot convert output to GPU due to ' + 'CUDA OOM, the output is on CPU now.') + + return func(*cpu_args, **cpu_kwargs) + else: + # may still get CUDA OOM error + return func(*args, **kwargs) + + return wrapped + + +# To use AvoidOOM as a decorator +AvoidCUDAOOM = AvoidOOM() diff --git a/mmdetection/mmdet/utils/misc.py b/mmdetection/mmdet/utils/misc.py new file mode 100644 index 00000000..8dfb3944 --- /dev/null +++ b/mmdetection/mmdet/utils/misc.py @@ -0,0 +1,149 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import glob +import os +import os.path as osp +import urllib +import warnings +from typing import Union + +import torch +from mmengine.config import Config, ConfigDict +from mmengine.logging import print_log +from mmengine.utils import scandir + +IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', + '.tiff', '.webp') + + +def find_latest_checkpoint(path, suffix='pth'): + """Find the latest checkpoint from the working directory. + + Args: + path(str): The path to find checkpoints. + suffix(str): File extension. + Defaults to pth. + + Returns: + latest_path(str | None): File path of the latest checkpoint. + References: + .. [1] https://github.com/microsoft/SoftTeacher + /blob/main/ssod/utils/patch.py + """ + if not osp.exists(path): + warnings.warn('The path of checkpoints does not exist.') + return None + if osp.exists(osp.join(path, f'latest.{suffix}')): + return osp.join(path, f'latest.{suffix}') + + checkpoints = glob.glob(osp.join(path, f'*.{suffix}')) + if len(checkpoints) == 0: + warnings.warn('There are no checkpoints in the path.') + return None + latest = -1 + latest_path = None + for checkpoint in checkpoints: + count = int(osp.basename(checkpoint).split('_')[-1].split('.')[0]) + if count > latest: + latest = count + latest_path = checkpoint + return latest_path + + +def update_data_root(cfg, logger=None): + """Update data root according to env MMDET_DATASETS. + + If set env MMDET_DATASETS, update cfg.data_root according to + MMDET_DATASETS. Otherwise, using cfg.data_root as default. + + Args: + cfg (:obj:`Config`): The model config need to modify + logger (logging.Logger | str | None): the way to print msg + """ + assert isinstance(cfg, Config), \ + f'cfg got wrong type: {type(cfg)}, expected mmengine.Config' + + if 'MMDET_DATASETS' in os.environ: + dst_root = os.environ['MMDET_DATASETS'] + print_log(f'MMDET_DATASETS has been set to be {dst_root}.' + f'Using {dst_root} as data root.') + else: + return + + assert isinstance(cfg, Config), \ + f'cfg got wrong type: {type(cfg)}, expected mmengine.Config' + + def update(cfg, src_str, dst_str): + for k, v in cfg.items(): + if isinstance(v, ConfigDict): + update(cfg[k], src_str, dst_str) + if isinstance(v, str) and src_str in v: + cfg[k] = v.replace(src_str, dst_str) + + update(cfg.data, cfg.data_root, dst_root) + cfg.data_root = dst_root + + +def get_test_pipeline_cfg(cfg: Union[str, ConfigDict]) -> ConfigDict: + """Get the test dataset pipeline from entire config. + + Args: + cfg (str or :obj:`ConfigDict`): the entire config. Can be a config + file or a ``ConfigDict``. + + Returns: + :obj:`ConfigDict`: the config of test dataset. + """ + if isinstance(cfg, str): + cfg = Config.fromfile(cfg) + + def _get_test_pipeline_cfg(dataset_cfg): + if 'pipeline' in dataset_cfg: + return dataset_cfg.pipeline + # handle dataset wrapper + elif 'dataset' in dataset_cfg: + return _get_test_pipeline_cfg(dataset_cfg.dataset) + # handle dataset wrappers like ConcatDataset + elif 'datasets' in dataset_cfg: + return _get_test_pipeline_cfg(dataset_cfg.datasets[0]) + + raise RuntimeError('Cannot find `pipeline` in `test_dataloader`') + + return _get_test_pipeline_cfg(cfg.test_dataloader.dataset) + + +def get_file_list(source_root: str) -> [list, dict]: + """Get file list. + + Args: + source_root (str): image or video source path + + Return: + source_file_path_list (list): A list for all source file. + source_type (dict): Source type: file or url or dir. + """ + is_dir = os.path.isdir(source_root) + is_url = source_root.startswith(('http:/', 'https:/')) + is_file = os.path.splitext(source_root)[-1].lower() in IMG_EXTENSIONS + + source_file_path_list = [] + if is_dir: + # when input source is dir + for file in scandir(source_root, IMG_EXTENSIONS, recursive=True): + source_file_path_list.append(os.path.join(source_root, file)) + elif is_url: + # when input source is url + filename = os.path.basename( + urllib.parse.unquote(source_root).split('?')[0]) + file_save_path = os.path.join(os.getcwd(), filename) + print(f'Downloading source file to {file_save_path}') + torch.hub.download_url_to_file(source_root, file_save_path) + source_file_path_list = [file_save_path] + elif is_file: + # when input source is single image + source_file_path_list = [source_root] + else: + print('Cannot find image file.') + + source_type = dict(is_dir=is_dir, is_url=is_url, is_file=is_file) + + return source_file_path_list, source_type diff --git a/mmdetection/mmdet/utils/mot_error_visualize.py b/mmdetection/mmdet/utils/mot_error_visualize.py new file mode 100644 index 00000000..01bf8645 --- /dev/null +++ b/mmdetection/mmdet/utils/mot_error_visualize.py @@ -0,0 +1,273 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from typing import Union + +try: + import seaborn as sns +except ImportError: + sns = None +import cv2 +import matplotlib.pyplot as plt +import mmcv +import numpy as np +from matplotlib.patches import Rectangle +from mmengine.utils import mkdir_or_exist + + +def imshow_mot_errors(*args, backend: str = 'cv2', **kwargs): + """Show the wrong tracks on the input image. + + Args: + backend (str, optional): Backend of visualization. + Defaults to 'cv2'. + """ + if backend == 'cv2': + return _cv2_show_wrong_tracks(*args, **kwargs) + elif backend == 'plt': + return _plt_show_wrong_tracks(*args, **kwargs) + else: + raise NotImplementedError() + + +def _cv2_show_wrong_tracks(img: Union[str, np.ndarray], + bboxes: np.ndarray, + ids: np.ndarray, + error_types: np.ndarray, + thickness: int = 2, + font_scale: float = 0.4, + text_width: int = 10, + text_height: int = 15, + show: bool = False, + wait_time: int = 100, + out_file: str = None) -> np.ndarray: + """Show the wrong tracks with opencv. + + Args: + img (str or ndarray): The image to be displayed. + bboxes (ndarray): A ndarray of shape (k, 5). + ids (ndarray): A ndarray of shape (k, ). + error_types (ndarray): A ndarray of shape (k, ), where 0 denotes + false positives, 1 denotes false negative and 2 denotes ID switch. + thickness (int, optional): Thickness of lines. + Defaults to 2. + font_scale (float, optional): Font scale to draw id and score. + Defaults to 0.4. + text_width (int, optional): Width to draw id and score. + Defaults to 10. + text_height (int, optional): Height to draw id and score. + Defaults to 15. + show (bool, optional): Whether to show the image on the fly. + Defaults to False. + wait_time (int, optional): Value of waitKey param. + Defaults to 100. + out_file (str, optional): The filename to write the image. + Defaults to None. + + Returns: + ndarray: Visualized image. + """ + if sns is None: + raise ImportError('please run pip install seaborn') + assert bboxes.ndim == 2, \ + f' bboxes ndim should be 2, but its ndim is {bboxes.ndim}.' + assert ids.ndim == 1, \ + f' ids ndim should be 1, but its ndim is {ids.ndim}.' + assert error_types.ndim == 1, \ + f' error_types ndim should be 1, but its ndim is {error_types.ndim}.' + assert bboxes.shape[0] == ids.shape[0], \ + 'bboxes.shape[0] and ids.shape[0] should have the same length.' + assert bboxes.shape[1] == 5, \ + f' bboxes.shape[1] should be 5, but its {bboxes.shape[1]}.' + + bbox_colors = sns.color_palette() + # red, yellow, blue + bbox_colors = [bbox_colors[3], bbox_colors[1], bbox_colors[0]] + bbox_colors = [[int(255 * _c) for _c in bbox_color][::-1] + for bbox_color in bbox_colors] + + if isinstance(img, str): + img = mmcv.imread(img) + else: + assert img.ndim == 3 + + img_shape = img.shape + bboxes[:, 0::2] = np.clip(bboxes[:, 0::2], 0, img_shape[1]) + bboxes[:, 1::2] = np.clip(bboxes[:, 1::2], 0, img_shape[0]) + + for bbox, error_type, id in zip(bboxes, error_types, ids): + x1, y1, x2, y2 = bbox[:4].astype(np.int32) + score = float(bbox[-1]) + + # bbox + bbox_color = bbox_colors[error_type] + cv2.rectangle(img, (x1, y1), (x2, y2), bbox_color, thickness=thickness) + + # FN does not have id and score + if error_type == 1: + continue + + # score + text = '{:.02f}'.format(score) + width = (len(text) - 1) * text_width + img[y1:y1 + text_height, x1:x1 + width, :] = bbox_color + cv2.putText( + img, + text, (x1, y1 + text_height - 2), + cv2.FONT_HERSHEY_COMPLEX, + font_scale, + color=(0, 0, 0)) + + # id + text = str(id) + width = len(text) * text_width + img[y1 + text_height:y1 + text_height * 2, + x1:x1 + width, :] = bbox_color + cv2.putText( + img, + str(id), (x1, y1 + text_height * 2 - 2), + cv2.FONT_HERSHEY_COMPLEX, + font_scale, + color=(0, 0, 0)) + + if show: + mmcv.imshow(img, wait_time=wait_time) + if out_file is not None: + mmcv.imwrite(img, out_file) + + return img + + +def _plt_show_wrong_tracks(img: Union[str, np.ndarray], + bboxes: np.ndarray, + ids: np.ndarray, + error_types: np.ndarray, + thickness: float = 0.1, + font_scale: float = 3.0, + text_width: int = 8, + text_height: int = 13, + show: bool = False, + wait_time: int = 100, + out_file: str = None) -> np.ndarray: + """Show the wrong tracks with matplotlib. + + Args: + img (str or ndarray): The image to be displayed. + bboxes (ndarray): A ndarray of shape (k, 5). + ids (ndarray): A ndarray of shape (k, ). + error_types (ndarray): A ndarray of shape (k, ), where 0 denotes + false positives, 1 denotes false negative and 2 denotes ID switch. + thickness (float, optional): Thickness of lines. + Defaults to 0.1. + font_scale (float, optional): Font scale to draw id and score. + Defaults to 3.0. + text_width (int, optional): Width to draw id and score. + Defaults to 8. + text_height (int, optional): Height to draw id and score. + Defaults to 13. + show (bool, optional): Whether to show the image on the fly. + Defaults to False. + wait_time (int, optional): Value of waitKey param. + Defaults to 100. + out_file (str, optional): The filename to write the image. + Defaults to None. + + Returns: + ndarray: Original image. + """ + assert bboxes.ndim == 2, \ + f' bboxes ndim should be 2, but its ndim is {bboxes.ndim}.' + assert ids.ndim == 1, \ + f' ids ndim should be 1, but its ndim is {ids.ndim}.' + assert error_types.ndim == 1, \ + f' error_types ndim should be 1, but its ndim is {error_types.ndim}.' + assert bboxes.shape[0] == ids.shape[0], \ + 'bboxes.shape[0] and ids.shape[0] should have the same length.' + assert bboxes.shape[1] == 5, \ + f' bboxes.shape[1] should be 5, but its {bboxes.shape[1]}.' + + bbox_colors = sns.color_palette() + # red, yellow, blue + bbox_colors = [bbox_colors[3], bbox_colors[1], bbox_colors[0]] + + if isinstance(img, str): + img = plt.imread(img) + else: + assert img.ndim == 3 + img = mmcv.bgr2rgb(img) + + img_shape = img.shape + bboxes[:, 0::2] = np.clip(bboxes[:, 0::2], 0, img_shape[1]) + bboxes[:, 1::2] = np.clip(bboxes[:, 1::2], 0, img_shape[0]) + + plt.imshow(img) + plt.gca().set_axis_off() + plt.autoscale(False) + plt.subplots_adjust( + top=1, bottom=0, right=1, left=0, hspace=None, wspace=None) + plt.margins(0, 0) + plt.gca().xaxis.set_major_locator(plt.NullLocator()) + plt.gca().yaxis.set_major_locator(plt.NullLocator()) + plt.rcParams['figure.figsize'] = img_shape[1], img_shape[0] + + for bbox, error_type, id in zip(bboxes, error_types, ids): + x1, y1, x2, y2, score = bbox + w, h = int(x2 - x1), int(y2 - y1) + left_top = (int(x1), int(y1)) + + # bbox + plt.gca().add_patch( + Rectangle( + left_top, + w, + h, + thickness, + edgecolor=bbox_colors[error_type], + facecolor='none')) + + # FN does not have id and score + if error_type == 1: + continue + + # score + text = '{:.02f}'.format(score) + width = len(text) * text_width + plt.gca().add_patch( + Rectangle((left_top[0], left_top[1]), + width, + text_height, + thickness, + edgecolor=bbox_colors[error_type], + facecolor=bbox_colors[error_type])) + + plt.text( + left_top[0], + left_top[1] + text_height + 2, + text, + fontsize=font_scale) + + # id + text = str(id) + width = len(text) * text_width + plt.gca().add_patch( + Rectangle((left_top[0], left_top[1] + text_height + 1), + width, + text_height, + thickness, + edgecolor=bbox_colors[error_type], + facecolor=bbox_colors[error_type])) + plt.text( + left_top[0], + left_top[1] + 2 * (text_height + 1), + text, + fontsize=font_scale) + + if out_file is not None: + mkdir_or_exist(osp.abspath(osp.dirname(out_file))) + plt.savefig(out_file, dpi=300, bbox_inches='tight', pad_inches=0.0) + + if show: + plt.draw() + plt.pause(wait_time / 1000.) + + plt.clf() + return img diff --git a/mmdetection/mmdet/utils/profiling.py b/mmdetection/mmdet/utils/profiling.py new file mode 100644 index 00000000..2f53f456 --- /dev/null +++ b/mmdetection/mmdet/utils/profiling.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import contextlib +import sys +import time + +import torch + +if sys.version_info >= (3, 7): + + @contextlib.contextmanager + def profile_time(trace_name, + name, + enabled=True, + stream=None, + end_stream=None): + """Print time spent by CPU and GPU. + + Useful as a temporary context manager to find sweet spots of code + suitable for async implementation. + """ + if (not enabled) or not torch.cuda.is_available(): + yield + return + stream = stream if stream else torch.cuda.current_stream() + end_stream = end_stream if end_stream else stream + start = torch.cuda.Event(enable_timing=True) + end = torch.cuda.Event(enable_timing=True) + stream.record_event(start) + try: + cpu_start = time.monotonic() + yield + finally: + cpu_end = time.monotonic() + end_stream.record_event(end) + end.synchronize() + cpu_time = (cpu_end - cpu_start) * 1000 + gpu_time = start.elapsed_time(end) + msg = f'{trace_name} {name} cpu_time {cpu_time:.2f} ms ' + msg += f'gpu_time {gpu_time:.2f} ms stream {stream}' + print(msg, end_stream) diff --git a/mmdetection/mmdet/utils/replace_cfg_vals.py b/mmdetection/mmdet/utils/replace_cfg_vals.py new file mode 100644 index 00000000..a3331a36 --- /dev/null +++ b/mmdetection/mmdet/utils/replace_cfg_vals.py @@ -0,0 +1,70 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import re + +from mmengine.config import Config + + +def replace_cfg_vals(ori_cfg): + """Replace the string "${key}" with the corresponding value. + + Replace the "${key}" with the value of ori_cfg.key in the config. And + support replacing the chained ${key}. Such as, replace "${key0.key1}" + with the value of cfg.key0.key1. Code is modified from `vars.py + < https://github.com/microsoft/SoftTeacher/blob/main/ssod/utils/vars.py>`_ # noqa: E501 + + Args: + ori_cfg (mmengine.config.Config): + The origin config with "${key}" generated from a file. + + Returns: + updated_cfg [mmengine.config.Config]: + The config with "${key}" replaced by the corresponding value. + """ + + def get_value(cfg, key): + for k in key.split('.'): + cfg = cfg[k] + return cfg + + def replace_value(cfg): + if isinstance(cfg, dict): + return {key: replace_value(value) for key, value in cfg.items()} + elif isinstance(cfg, list): + return [replace_value(item) for item in cfg] + elif isinstance(cfg, tuple): + return tuple([replace_value(item) for item in cfg]) + elif isinstance(cfg, str): + # the format of string cfg may be: + # 1) "${key}", which will be replaced with cfg.key directly + # 2) "xxx${key}xxx" or "xxx${key1}xxx${key2}xxx", + # which will be replaced with the string of the cfg.key + keys = pattern_key.findall(cfg) + values = [get_value(ori_cfg, key[2:-1]) for key in keys] + if len(keys) == 1 and keys[0] == cfg: + # the format of string cfg is "${key}" + cfg = values[0] + else: + for key, value in zip(keys, values): + # the format of string cfg is + # "xxx${key}xxx" or "xxx${key1}xxx${key2}xxx" + assert not isinstance(value, (dict, list, tuple)), \ + f'for the format of string cfg is ' \ + f"'xxxxx${key}xxxxx' or 'xxx${key}xxx${key}xxx', " \ + f"the type of the value of '${key}' " \ + f'can not be dict, list, or tuple' \ + f'but you input {type(value)} in {cfg}' + cfg = cfg.replace(key, str(value)) + return cfg + else: + return cfg + + # the pattern of string "${key}" + pattern_key = re.compile(r'\$\{[a-zA-Z\d_.]*\}') + # the type of ori_cfg._cfg_dict is mmengine.config.ConfigDict + updated_cfg = Config( + replace_value(ori_cfg._cfg_dict), filename=ori_cfg.filename) + # replace the model with model_wrapper + if updated_cfg.get('model_wrapper', None) is not None: + updated_cfg.model = updated_cfg.model_wrapper + updated_cfg.pop('model_wrapper') + return updated_cfg diff --git a/mmdetection/mmdet/utils/setup_env.py b/mmdetection/mmdet/utils/setup_env.py new file mode 100644 index 00000000..a7b37845 --- /dev/null +++ b/mmdetection/mmdet/utils/setup_env.py @@ -0,0 +1,118 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import datetime +import logging +import os +import platform +import warnings + +import cv2 +import torch.multiprocessing as mp +from mmengine import DefaultScope +from mmengine.logging import print_log +from mmengine.utils import digit_version + + +def setup_cache_size_limit_of_dynamo(): + """Setup cache size limit of dynamo. + + Note: Due to the dynamic shape of the loss calculation and + post-processing parts in the object detection algorithm, these + functions must be compiled every time they are run. + Setting a large value for torch._dynamo.config.cache_size_limit + may result in repeated compilation, which can slow down training + and testing speed. Therefore, we need to set the default value of + cache_size_limit smaller. An empirical value is 4. + """ + + import torch + if digit_version(torch.__version__) >= digit_version('2.0.0'): + if 'DYNAMO_CACHE_SIZE_LIMIT' in os.environ: + import torch._dynamo + cache_size_limit = int(os.environ['DYNAMO_CACHE_SIZE_LIMIT']) + torch._dynamo.config.cache_size_limit = cache_size_limit + print_log( + f'torch._dynamo.config.cache_size_limit is force ' + f'set to {cache_size_limit}.', + logger='current', + level=logging.WARNING) + + +def setup_multi_processes(cfg): + """Setup multi-processing environment variables.""" + # set multi-process start method as `fork` to speed up the training + if platform.system() != 'Windows': + mp_start_method = cfg.get('mp_start_method', 'fork') + current_method = mp.get_start_method(allow_none=True) + if current_method is not None and current_method != mp_start_method: + warnings.warn( + f'Multi-processing start method `{mp_start_method}` is ' + f'different from the previous setting `{current_method}`.' + f'It will be force set to `{mp_start_method}`. You can change ' + f'this behavior by changing `mp_start_method` in your config.') + mp.set_start_method(mp_start_method, force=True) + + # disable opencv multithreading to avoid system being overloaded + opencv_num_threads = cfg.get('opencv_num_threads', 0) + cv2.setNumThreads(opencv_num_threads) + + # setup OMP threads + # This code is referred from https://github.com/pytorch/pytorch/blob/master/torch/distributed/run.py # noqa + workers_per_gpu = cfg.data.get('workers_per_gpu', 1) + if 'train_dataloader' in cfg.data: + workers_per_gpu = \ + max(cfg.data.train_dataloader.get('workers_per_gpu', 1), + workers_per_gpu) + + if 'OMP_NUM_THREADS' not in os.environ and workers_per_gpu > 1: + omp_num_threads = 1 + warnings.warn( + f'Setting OMP_NUM_THREADS environment variable for each process ' + f'to be {omp_num_threads} in default, to avoid your system being ' + f'overloaded, please further tune the variable for optimal ' + f'performance in your application as needed.') + os.environ['OMP_NUM_THREADS'] = str(omp_num_threads) + + # setup MKL threads + if 'MKL_NUM_THREADS' not in os.environ and workers_per_gpu > 1: + mkl_num_threads = 1 + warnings.warn( + f'Setting MKL_NUM_THREADS environment variable for each process ' + f'to be {mkl_num_threads} in default, to avoid your system being ' + f'overloaded, please further tune the variable for optimal ' + f'performance in your application as needed.') + os.environ['MKL_NUM_THREADS'] = str(mkl_num_threads) + + +def register_all_modules(init_default_scope: bool = True) -> None: + """Register all modules in mmdet into the registries. + + Args: + init_default_scope (bool): Whether initialize the mmdet default scope. + When `init_default_scope=True`, the global default scope will be + set to `mmdet`, and all registries will build modules from mmdet's + registry node. To understand more about the registry, please refer + to https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/registry.md + Defaults to True. + """ # noqa + import mmdet.datasets # noqa: F401,F403 + import mmdet.engine # noqa: F401,F403 + import mmdet.evaluation # noqa: F401,F403 + import mmdet.models # noqa: F401,F403 + import mmdet.visualization # noqa: F401,F403 + + if init_default_scope: + never_created = DefaultScope.get_current_instance() is None \ + or not DefaultScope.check_instance_created('mmdet') + if never_created: + DefaultScope.get_instance('mmdet', scope_name='mmdet') + return + current_scope = DefaultScope.get_current_instance() + if current_scope.scope_name != 'mmdet': + warnings.warn('The current default scope ' + f'"{current_scope.scope_name}" is not "mmdet", ' + '`register_all_modules` will force the current' + 'default scope to be "mmdet". If this is not ' + 'expected, please set `init_default_scope=False`.') + # avoid name conflict + new_instance_name = f'mmdet-{datetime.datetime.now()}' + DefaultScope.get_instance(new_instance_name, scope_name='mmdet') diff --git a/mmdetection/mmdet/utils/split_batch.py b/mmdetection/mmdet/utils/split_batch.py new file mode 100644 index 00000000..0276fb33 --- /dev/null +++ b/mmdetection/mmdet/utils/split_batch.py @@ -0,0 +1,45 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + + +def split_batch(img, img_metas, kwargs): + """Split data_batch by tags. + + Code is modified from + # noqa: E501 + + Args: + img (Tensor): of shape (N, C, H, W) encoding input images. + Typically these should be mean centered and std scaled. + img_metas (list[dict]): List of image info dict where each dict + has: 'img_shape', 'scale_factor', 'flip', and may also contain + 'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'. + For details on the values of these keys, see + :class:`mmdet.datasets.pipelines.Collect`. + kwargs (dict): Specific to concrete implementation. + + Returns: + data_groups (dict): a dict that data_batch splited by tags, + such as 'sup', 'unsup_teacher', and 'unsup_student'. + """ + + # only stack img in the batch + def fuse_list(obj_list, obj): + return torch.stack(obj_list) if isinstance(obj, + torch.Tensor) else obj_list + + # select data with tag from data_batch + def select_group(data_batch, current_tag): + group_flag = [tag == current_tag for tag in data_batch['tag']] + return { + k: fuse_list([vv for vv, gf in zip(v, group_flag) if gf], v) + for k, v in data_batch.items() + } + + kwargs.update({'img': img, 'img_metas': img_metas}) + kwargs.update({'tag': [meta['tag'] for meta in img_metas]}) + tags = list(set(kwargs['tag'])) + data_groups = {tag: select_group(kwargs, tag) for tag in tags} + for tag, group in data_groups.items(): + group.pop('tag') + return data_groups diff --git a/mmdetection/mmdet/utils/typing_utils.py b/mmdetection/mmdet/utils/typing_utils.py new file mode 100644 index 00000000..6caf6de5 --- /dev/null +++ b/mmdetection/mmdet/utils/typing_utils.py @@ -0,0 +1,22 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Collecting some commonly used type hint in mmdetection.""" +from typing import List, Optional, Sequence, Tuple, Union + +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData, PixelData + +# TODO: Need to avoid circular import with assigner and sampler +# Type hint of config data +ConfigType = Union[ConfigDict, dict] +OptConfigType = Optional[ConfigType] +# Type hint of one or more config data +MultiConfig = Union[ConfigType, List[ConfigType]] +OptMultiConfig = Optional[MultiConfig] + +InstanceList = List[InstanceData] +OptInstanceList = Optional[InstanceList] + +PixelList = List[PixelData] +OptPixelList = Optional[PixelList] + +RangeType = Sequence[Tuple[int, int]] diff --git a/mmdetection/mmdet/utils/util_mixins.py b/mmdetection/mmdet/utils/util_mixins.py new file mode 100644 index 00000000..b83b6617 --- /dev/null +++ b/mmdetection/mmdet/utils/util_mixins.py @@ -0,0 +1,105 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""This module defines the :class:`NiceRepr` mixin class, which defines a +``__repr__`` and ``__str__`` method that only depend on a custom ``__nice__`` +method, which you must define. This means you only have to overload one +function instead of two. Furthermore, if the object defines a ``__len__`` +method, then the ``__nice__`` method defaults to something sensible, otherwise +it is treated as abstract and raises ``NotImplementedError``. + +To use simply have your object inherit from :class:`NiceRepr` +(multi-inheritance should be ok). + +This code was copied from the ubelt library: https://github.com/Erotemic/ubelt + +Example: + >>> # Objects that define __nice__ have a default __str__ and __repr__ + >>> class Student(NiceRepr): + ... def __init__(self, name): + ... self.name = name + ... def __nice__(self): + ... return self.name + >>> s1 = Student('Alice') + >>> s2 = Student('Bob') + >>> print(f's1 = {s1}') + >>> print(f's2 = {s2}') + s1 = + s2 = + +Example: + >>> # Objects that define __len__ have a default __nice__ + >>> class Group(NiceRepr): + ... def __init__(self, data): + ... self.data = data + ... def __len__(self): + ... return len(self.data) + >>> g = Group([1, 2, 3]) + >>> print(f'g = {g}') + g = +""" +import warnings + + +class NiceRepr: + """Inherit from this class and define ``__nice__`` to "nicely" print your + objects. + + Defines ``__str__`` and ``__repr__`` in terms of ``__nice__`` function + Classes that inherit from :class:`NiceRepr` should redefine ``__nice__``. + If the inheriting class has a ``__len__``, method then the default + ``__nice__`` method will return its length. + + Example: + >>> class Foo(NiceRepr): + ... def __nice__(self): + ... return 'info' + >>> foo = Foo() + >>> assert str(foo) == '' + >>> assert repr(foo).startswith('>> class Bar(NiceRepr): + ... pass + >>> bar = Bar() + >>> import pytest + >>> with pytest.warns(None) as record: + >>> assert 'object at' in str(bar) + >>> assert 'object at' in repr(bar) + + Example: + >>> class Baz(NiceRepr): + ... def __len__(self): + ... return 5 + >>> baz = Baz() + >>> assert str(baz) == '' + """ + + def __nice__(self): + """str: a "nice" summary string describing this module""" + if hasattr(self, '__len__'): + # It is a common pattern for objects to use __len__ in __nice__ + # As a convenience we define a default __nice__ for these objects + return str(len(self)) + else: + # In all other cases force the subclass to overload __nice__ + raise NotImplementedError( + f'Define the __nice__ method for {self.__class__!r}') + + def __repr__(self): + """str: the string of the module""" + try: + nice = self.__nice__() + classname = self.__class__.__name__ + return f'<{classname}({nice}) at {hex(id(self))}>' + except NotImplementedError as ex: + warnings.warn(str(ex), category=RuntimeWarning) + return object.__repr__(self) + + def __str__(self): + """str: the string of the module""" + try: + classname = self.__class__.__name__ + nice = self.__nice__() + return f'<{classname}({nice})>' + except NotImplementedError as ex: + warnings.warn(str(ex), category=RuntimeWarning) + return object.__repr__(self) diff --git a/mmdetection/mmdet/utils/util_random.py b/mmdetection/mmdet/utils/util_random.py new file mode 100644 index 00000000..dc1ecb6c --- /dev/null +++ b/mmdetection/mmdet/utils/util_random.py @@ -0,0 +1,34 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Helpers for random number generators.""" +import numpy as np + + +def ensure_rng(rng=None): + """Coerces input into a random number generator. + + If the input is None, then a global random state is returned. + + If the input is a numeric value, then that is used as a seed to construct a + random state. Otherwise the input is returned as-is. + + Adapted from [1]_. + + Args: + rng (int | numpy.random.RandomState | None): + if None, then defaults to the global rng. Otherwise this can be an + integer or a RandomState class + Returns: + (numpy.random.RandomState) : rng - + a numpy random number generator + + References: + .. [1] https://gitlab.kitware.com/computer-vision/kwarray/blob/master/kwarray/util_random.py#L270 # noqa: E501 + """ + + if rng is None: + rng = np.random.mtrand._rand + elif isinstance(rng, int): + rng = np.random.RandomState(rng) + else: + rng = rng + return rng diff --git a/mmdetection/mmdet/version.py b/mmdetection/mmdet/version.py new file mode 100644 index 00000000..47989fc0 --- /dev/null +++ b/mmdetection/mmdet/version.py @@ -0,0 +1,27 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +__version__ = '3.3.0' +short_version = __version__ + + +def parse_version_info(version_str): + """Parse a version string into a tuple. + + Args: + version_str (str): The version string. + Returns: + tuple[int | str]: The version info, e.g., "1.3.0" is parsed into + (1, 3, 0), and "2.0.0rc1" is parsed into (2, 0, 0, 'rc1'). + """ + version_info = [] + for x in version_str.split('.'): + if x.isdigit(): + version_info.append(int(x)) + elif x.find('rc') != -1: + patch_version = x.split('rc') + version_info.append(int(patch_version[0])) + version_info.append(f'rc{patch_version[1]}') + return tuple(version_info) + + +version_info = parse_version_info(__version__) diff --git a/mmdetection/mmdet/visualization/__init__.py b/mmdetection/mmdet/visualization/__init__.py new file mode 100644 index 00000000..a7edaed9 --- /dev/null +++ b/mmdetection/mmdet/visualization/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .local_visualizer import DetLocalVisualizer, TrackLocalVisualizer +from .palette import get_palette, jitter_color, palette_val + +__all__ = [ + 'palette_val', 'get_palette', 'DetLocalVisualizer', 'jitter_color', + 'TrackLocalVisualizer' +] diff --git a/mmdetection/mmdet/visualization/local_visualizer.py b/mmdetection/mmdet/visualization/local_visualizer.py new file mode 100644 index 00000000..cc6521c5 --- /dev/null +++ b/mmdetection/mmdet/visualization/local_visualizer.py @@ -0,0 +1,699 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Tuple, Union + +import cv2 +import mmcv +import numpy as np + +try: + import seaborn as sns +except ImportError: + sns = None +import torch +from mmengine.dist import master_only +from mmengine.structures import InstanceData, PixelData +from mmengine.visualization import Visualizer + +from ..evaluation import INSTANCE_OFFSET +from ..registry import VISUALIZERS +from ..structures import DetDataSample +from ..structures.mask import BitmapMasks, PolygonMasks, bitmap_to_polygon +from .palette import _get_adaptive_scales, get_palette, jitter_color + + +@VISUALIZERS.register_module() +class DetLocalVisualizer(Visualizer): + """MMDetection Local Visualizer. + + Args: + name (str): Name of the instance. Defaults to 'visualizer'. + image (np.ndarray, optional): the origin image to draw. The format + should be RGB. Defaults to None. + vis_backends (list, optional): Visual backend config list. + Defaults to None. + save_dir (str, optional): Save file dir for all storage backends. + If it is None, the backend storage will not save any data. + bbox_color (str, tuple(int), optional): Color of bbox lines. + The tuple of color should be in BGR order. Defaults to None. + text_color (str, tuple(int), optional): Color of texts. + The tuple of color should be in BGR order. + Defaults to (200, 200, 200). + mask_color (str, tuple(int), optional): Color of masks. + The tuple of color should be in BGR order. + Defaults to None. + line_width (int, float): The linewidth of lines. + Defaults to 3. + alpha (int, float): The transparency of bboxes or mask. + Defaults to 0.8. + + Examples: + >>> import numpy as np + >>> import torch + >>> from mmengine.structures import InstanceData + >>> from mmdet.structures import DetDataSample + >>> from mmdet.visualization import DetLocalVisualizer + + >>> det_local_visualizer = DetLocalVisualizer() + >>> image = np.random.randint(0, 256, + ... size=(10, 12, 3)).astype('uint8') + >>> gt_instances = InstanceData() + >>> gt_instances.bboxes = torch.Tensor([[1, 2, 2, 5]]) + >>> gt_instances.labels = torch.randint(0, 2, (1,)) + >>> gt_det_data_sample = DetDataSample() + >>> gt_det_data_sample.gt_instances = gt_instances + >>> det_local_visualizer.add_datasample('image', image, + ... gt_det_data_sample) + >>> det_local_visualizer.add_datasample( + ... 'image', image, gt_det_data_sample, + ... out_file='out_file.jpg') + >>> det_local_visualizer.add_datasample( + ... 'image', image, gt_det_data_sample, + ... show=True) + >>> pred_instances = InstanceData() + >>> pred_instances.bboxes = torch.Tensor([[2, 4, 4, 8]]) + >>> pred_instances.labels = torch.randint(0, 2, (1,)) + >>> pred_det_data_sample = DetDataSample() + >>> pred_det_data_sample.pred_instances = pred_instances + >>> det_local_visualizer.add_datasample('image', image, + ... gt_det_data_sample, + ... pred_det_data_sample) + """ + + def __init__(self, + name: str = 'visualizer', + image: Optional[np.ndarray] = None, + vis_backends: Optional[Dict] = None, + save_dir: Optional[str] = None, + bbox_color: Optional[Union[str, Tuple[int]]] = None, + text_color: Optional[Union[str, + Tuple[int]]] = (200, 200, 200), + mask_color: Optional[Union[str, Tuple[int]]] = None, + line_width: Union[int, float] = 3, + alpha: float = 0.8) -> None: + super().__init__( + name=name, + image=image, + vis_backends=vis_backends, + save_dir=save_dir) + self.bbox_color = bbox_color + self.text_color = text_color + self.mask_color = mask_color + self.line_width = line_width + self.alpha = alpha + # Set default value. When calling + # `DetLocalVisualizer().dataset_meta=xxx`, + # it will override the default value. + self.dataset_meta = {} + + def _draw_instances(self, image: np.ndarray, instances: ['InstanceData'], + classes: Optional[List[str]], + palette: Optional[List[tuple]]) -> np.ndarray: + """Draw instances of GT or prediction. + + Args: + image (np.ndarray): The image to draw. + instances (:obj:`InstanceData`): Data structure for + instance-level annotations or predictions. + classes (List[str], optional): Category information. + palette (List[tuple], optional): Palette information + corresponding to the category. + + Returns: + np.ndarray: the drawn image which channel is RGB. + """ + self.set_image(image) + + if 'bboxes' in instances and instances.bboxes.sum() > 0: + bboxes = instances.bboxes + labels = instances.labels + + max_label = int(max(labels) if len(labels) > 0 else 0) + text_palette = get_palette(self.text_color, max_label + 1) + text_colors = [text_palette[label] for label in labels] + + bbox_color = palette if self.bbox_color is None \ + else self.bbox_color + bbox_palette = get_palette(bbox_color, max_label + 1) + colors = [bbox_palette[label] for label in labels] + self.draw_bboxes( + bboxes, + edge_colors=colors, + alpha=self.alpha, + line_widths=self.line_width) + + positions = bboxes[:, :2] + self.line_width + areas = (bboxes[:, 3] - bboxes[:, 1]) * ( + bboxes[:, 2] - bboxes[:, 0]) + scales = _get_adaptive_scales(areas) + + for i, (pos, label) in enumerate(zip(positions, labels)): + if 'label_names' in instances: + label_text = instances.label_names[i] + else: + label_text = classes[ + label] if classes is not None else f'class {label}' + if 'scores' in instances: + score = round(float(instances.scores[i]) * 100, 1) + label_text += f': {score}' + + self.draw_texts( + label_text, + pos, + colors=text_colors[i], + font_sizes=int(13 * scales[i]), + bboxes=[{ + 'facecolor': 'black', + 'alpha': 0.8, + 'pad': 0.7, + 'edgecolor': 'none' + }]) + + if 'masks' in instances: + labels = instances.labels + masks = instances.masks + if isinstance(masks, torch.Tensor): + masks = masks.numpy() + elif isinstance(masks, (PolygonMasks, BitmapMasks)): + masks = masks.to_ndarray() + + masks = masks.astype(bool) + + max_label = int(max(labels) if len(labels) > 0 else 0) + mask_color = palette if self.mask_color is None \ + else self.mask_color + mask_palette = get_palette(mask_color, max_label + 1) + colors = [jitter_color(mask_palette[label]) for label in labels] + text_palette = get_palette(self.text_color, max_label + 1) + text_colors = [text_palette[label] for label in labels] + + polygons = [] + for i, mask in enumerate(masks): + contours, _ = bitmap_to_polygon(mask) + polygons.extend(contours) + self.draw_polygons(polygons, edge_colors='w', alpha=self.alpha) + self.draw_binary_masks(masks, colors=colors, alphas=self.alpha) + + if len(labels) > 0 and \ + ('bboxes' not in instances or + instances.bboxes.sum() == 0): + # instances.bboxes.sum()==0 represent dummy bboxes. + # A typical example of SOLO does not exist bbox branch. + areas = [] + positions = [] + for mask in masks: + _, _, stats, centroids = cv2.connectedComponentsWithStats( + mask.astype(np.uint8), connectivity=8) + if stats.shape[0] > 1: + largest_id = np.argmax(stats[1:, -1]) + 1 + positions.append(centroids[largest_id]) + areas.append(stats[largest_id, -1]) + areas = np.stack(areas, axis=0) + scales = _get_adaptive_scales(areas) + + for i, (pos, label) in enumerate(zip(positions, labels)): + if 'label_names' in instances: + label_text = instances.label_names[i] + else: + label_text = classes[ + label] if classes is not None else f'class {label}' + if 'scores' in instances: + score = round(float(instances.scores[i]) * 100, 1) + label_text += f': {score}' + + self.draw_texts( + label_text, + pos, + colors=text_colors[i], + font_sizes=int(13 * scales[i]), + horizontal_alignments='center', + bboxes=[{ + 'facecolor': 'black', + 'alpha': 0.8, + 'pad': 0.7, + 'edgecolor': 'none' + }]) + return self.get_image() + + def _draw_panoptic_seg(self, image: np.ndarray, + panoptic_seg: ['PixelData'], + classes: Optional[List[str]], + palette: Optional[List]) -> np.ndarray: + """Draw panoptic seg of GT or prediction. + + Args: + image (np.ndarray): The image to draw. + panoptic_seg (:obj:`PixelData`): Data structure for + pixel-level annotations or predictions. + classes (List[str], optional): Category information. + + Returns: + np.ndarray: the drawn image which channel is RGB. + """ + # TODO: Is there a way to bypass? + num_classes = len(classes) + + panoptic_seg_data = panoptic_seg.sem_seg[0] + + ids = np.unique(panoptic_seg_data)[::-1] + + if 'label_names' in panoptic_seg: + # open set panoptic segmentation + classes = panoptic_seg.metainfo['label_names'] + ignore_index = panoptic_seg.metainfo.get('ignore_index', + len(classes)) + ids = ids[ids != ignore_index] + else: + # for VOID label + ids = ids[ids != num_classes] + + labels = np.array([id % INSTANCE_OFFSET for id in ids], dtype=np.int64) + segms = (panoptic_seg_data[None] == ids[:, None, None]) + + max_label = int(max(labels) if len(labels) > 0 else 0) + + mask_color = palette if self.mask_color is None \ + else self.mask_color + mask_palette = get_palette(mask_color, max_label + 1) + colors = [mask_palette[label] for label in labels] + + self.set_image(image) + + # draw segm + polygons = [] + for i, mask in enumerate(segms): + contours, _ = bitmap_to_polygon(mask) + polygons.extend(contours) + self.draw_polygons(polygons, edge_colors='w', alpha=self.alpha) + self.draw_binary_masks(segms, colors=colors, alphas=self.alpha) + + # draw label + areas = [] + positions = [] + for mask in segms: + _, _, stats, centroids = cv2.connectedComponentsWithStats( + mask.astype(np.uint8), connectivity=8) + max_id = np.argmax(stats[1:, -1]) + 1 + positions.append(centroids[max_id]) + areas.append(stats[max_id, -1]) + areas = np.stack(areas, axis=0) + scales = _get_adaptive_scales(areas) + + text_palette = get_palette(self.text_color, max_label + 1) + text_colors = [text_palette[label] for label in labels] + + for i, (pos, label) in enumerate(zip(positions, labels)): + label_text = classes[label] + + self.draw_texts( + label_text, + pos, + colors=text_colors[i], + font_sizes=int(13 * scales[i]), + bboxes=[{ + 'facecolor': 'black', + 'alpha': 0.8, + 'pad': 0.7, + 'edgecolor': 'none' + }], + horizontal_alignments='center') + return self.get_image() + + def _draw_sem_seg(self, image: np.ndarray, sem_seg: PixelData, + classes: Optional[List], + palette: Optional[List]) -> np.ndarray: + """Draw semantic seg of GT or prediction. + + Args: + image (np.ndarray): The image to draw. + sem_seg (:obj:`PixelData`): Data structure for pixel-level + annotations or predictions. + classes (list, optional): Input classes for result rendering, as + the prediction of segmentation model is a segment map with + label indices, `classes` is a list which includes items + responding to the label indices. If classes is not defined, + visualizer will take `cityscapes` classes by default. + Defaults to None. + palette (list, optional): Input palette for result rendering, which + is a list of color palette responding to the classes. + Defaults to None. + + Returns: + np.ndarray: the drawn image which channel is RGB. + """ + sem_seg_data = sem_seg.sem_seg + if isinstance(sem_seg_data, torch.Tensor): + sem_seg_data = sem_seg_data.numpy() + + # 0 ~ num_class, the value 0 means background + ids = np.unique(sem_seg_data) + ignore_index = sem_seg.metainfo.get('ignore_index', 255) + ids = ids[ids != ignore_index] + + if 'label_names' in sem_seg: + # open set semseg + label_names = sem_seg.metainfo['label_names'] + else: + label_names = classes + + labels = np.array(ids, dtype=np.int64) + colors = [palette[label] for label in labels] + + self.set_image(image) + + # draw semantic masks + for i, (label, color) in enumerate(zip(labels, colors)): + masks = sem_seg_data == label + self.draw_binary_masks(masks, colors=[color], alphas=self.alpha) + label_text = label_names[label] + _, _, stats, centroids = cv2.connectedComponentsWithStats( + masks[0].astype(np.uint8), connectivity=8) + if stats.shape[0] > 1: + largest_id = np.argmax(stats[1:, -1]) + 1 + centroids = centroids[largest_id] + + areas = stats[largest_id, -1] + scales = _get_adaptive_scales(areas) + + self.draw_texts( + label_text, + centroids, + colors=(255, 255, 255), + font_sizes=int(13 * scales), + horizontal_alignments='center', + bboxes=[{ + 'facecolor': 'black', + 'alpha': 0.8, + 'pad': 0.7, + 'edgecolor': 'none' + }]) + + return self.get_image() + + @master_only + def add_datasample( + self, + name: str, + image: np.ndarray, + data_sample: Optional['DetDataSample'] = None, + draw_gt: bool = True, + draw_pred: bool = True, + show: bool = False, + wait_time: float = 0, + # TODO: Supported in mmengine's Viusalizer. + out_file: Optional[str] = None, + pred_score_thr: float = 0.3, + step: int = 0) -> None: + """Draw datasample and save to all backends. + + - If GT and prediction are plotted at the same time, they are + displayed in a stitched image where the left image is the + ground truth and the right image is the prediction. + - If ``show`` is True, all storage backends are ignored, and + the images will be displayed in a local window. + - If ``out_file`` is specified, the drawn image will be + saved to ``out_file``. t is usually used when the display + is not available. + + Args: + name (str): The image identifier. + image (np.ndarray): The image to draw. + data_sample (:obj:`DetDataSample`, optional): A data + sample that contain annotations and predictions. + Defaults to None. + draw_gt (bool): Whether to draw GT DetDataSample. Default to True. + draw_pred (bool): Whether to draw Prediction DetDataSample. + Defaults to True. + show (bool): Whether to display the drawn image. Default to False. + wait_time (float): The interval of show (s). Defaults to 0. + out_file (str): Path to output file. Defaults to None. + pred_score_thr (float): The threshold to visualize the bboxes + and masks. Defaults to 0.3. + step (int): Global step value to record. Defaults to 0. + """ + image = image.clip(0, 255).astype(np.uint8) + classes = self.dataset_meta.get('classes', None) + palette = self.dataset_meta.get('palette', None) + + gt_img_data = None + pred_img_data = None + + if data_sample is not None: + data_sample = data_sample.cpu() + + if draw_gt and data_sample is not None: + gt_img_data = image + if 'gt_instances' in data_sample: + gt_img_data = self._draw_instances(image, + data_sample.gt_instances, + classes, palette) + if 'gt_sem_seg' in data_sample: + gt_img_data = self._draw_sem_seg(gt_img_data, + data_sample.gt_sem_seg, + classes, palette) + + if 'gt_panoptic_seg' in data_sample: + assert classes is not None, 'class information is ' \ + 'not provided when ' \ + 'visualizing panoptic ' \ + 'segmentation results.' + gt_img_data = self._draw_panoptic_seg( + gt_img_data, data_sample.gt_panoptic_seg, classes, palette) + + if draw_pred and data_sample is not None: + pred_img_data = image + if 'pred_instances' in data_sample: + pred_instances = data_sample.pred_instances + pred_instances = pred_instances[ + pred_instances.scores > pred_score_thr] + pred_img_data = self._draw_instances(image, pred_instances, + classes, palette) + + if 'pred_sem_seg' in data_sample: + pred_img_data = self._draw_sem_seg(pred_img_data, + data_sample.pred_sem_seg, + classes, palette) + + if 'pred_panoptic_seg' in data_sample: + assert classes is not None, 'class information is ' \ + 'not provided when ' \ + 'visualizing panoptic ' \ + 'segmentation results.' + pred_img_data = self._draw_panoptic_seg( + pred_img_data, data_sample.pred_panoptic_seg.numpy(), + classes, palette) + + if gt_img_data is not None and pred_img_data is not None: + drawn_img = np.concatenate((gt_img_data, pred_img_data), axis=1) + elif gt_img_data is not None: + drawn_img = gt_img_data + elif pred_img_data is not None: + drawn_img = pred_img_data + else: + # Display the original image directly if nothing is drawn. + drawn_img = image + + # It is convenient for users to obtain the drawn image. + # For example, the user wants to obtain the drawn image and + # save it as a video during video inference. + self.set_image(drawn_img) + + if show: + self.show(drawn_img, win_name=name, wait_time=wait_time) + + if out_file is not None: + mmcv.imwrite(drawn_img[..., ::-1], out_file) + else: + self.add_image(name, drawn_img, step) + + +def random_color(seed): + """Random a color according to the input seed.""" + if sns is None: + raise RuntimeError('motmetrics is not installed,\ + please install it by: pip install seaborn') + np.random.seed(seed) + colors = sns.color_palette() + color = colors[np.random.choice(range(len(colors)))] + color = tuple([int(255 * c) for c in color]) + return color + + +@VISUALIZERS.register_module() +class TrackLocalVisualizer(Visualizer): + """Tracking Local Visualizer for the MOT, VIS tasks. + + Args: + name (str): Name of the instance. Defaults to 'visualizer'. + image (np.ndarray, optional): the origin image to draw. The format + should be RGB. Defaults to None. + vis_backends (list, optional): Visual backend config list. + Defaults to None. + save_dir (str, optional): Save file dir for all storage backends. + If it is None, the backend storage will not save any data. + line_width (int, float): The linewidth of lines. + Defaults to 3. + alpha (int, float): The transparency of bboxes or mask. + Defaults to 0.8. + """ + + def __init__(self, + name: str = 'visualizer', + image: Optional[np.ndarray] = None, + vis_backends: Optional[Dict] = None, + save_dir: Optional[str] = None, + line_width: Union[int, float] = 3, + alpha: float = 0.8) -> None: + super().__init__(name, image, vis_backends, save_dir) + self.line_width = line_width + self.alpha = alpha + # Set default value. When calling + # `TrackLocalVisualizer().dataset_meta=xxx`, + # it will override the default value. + self.dataset_meta = {} + + def _draw_instances(self, image: np.ndarray, + instances: InstanceData) -> np.ndarray: + """Draw instances of GT or prediction. + + Args: + image (np.ndarray): The image to draw. + instances (:obj:`InstanceData`): Data structure for + instance-level annotations or predictions. + Returns: + np.ndarray: the drawn image which channel is RGB. + """ + self.set_image(image) + classes = self.dataset_meta.get('classes', None) + + # get colors and texts + # for the MOT and VIS tasks + colors = [random_color(_id) for _id in instances.instances_id] + categories = [ + classes[label] if classes is not None else f'cls{label}' + for label in instances.labels + ] + if 'scores' in instances: + texts = [ + f'{category_name}\n{instance_id} | {score:.2f}' + for category_name, instance_id, score in zip( + categories, instances.instances_id, instances.scores) + ] + else: + texts = [ + f'{category_name}\n{instance_id}' for category_name, + instance_id in zip(categories, instances.instances_id) + ] + + # draw bboxes and texts + if 'bboxes' in instances: + # draw bboxes + bboxes = instances.bboxes.clone() + self.draw_bboxes( + bboxes, + edge_colors=colors, + alpha=self.alpha, + line_widths=self.line_width) + # draw texts + if texts is not None: + positions = bboxes[:, :2] + self.line_width + areas = (bboxes[:, 3] - bboxes[:, 1]) * ( + bboxes[:, 2] - bboxes[:, 0]) + scales = _get_adaptive_scales(areas.cpu().numpy()) + for i, pos in enumerate(positions): + self.draw_texts( + texts[i], + pos, + colors='black', + font_sizes=int(13 * scales[i]), + bboxes=[{ + 'facecolor': [c / 255 for c in colors[i]], + 'alpha': 0.8, + 'pad': 0.7, + 'edgecolor': 'none' + }]) + + # draw masks + if 'masks' in instances: + masks = instances.masks + polygons = [] + for i, mask in enumerate(masks): + contours, _ = bitmap_to_polygon(mask) + polygons.extend(contours) + self.draw_polygons(polygons, edge_colors='w', alpha=self.alpha) + self.draw_binary_masks(masks, colors=colors, alphas=self.alpha) + + return self.get_image() + + @master_only + def add_datasample( + self, + name: str, + image: np.ndarray, + data_sample: DetDataSample = None, + draw_gt: bool = True, + draw_pred: bool = True, + show: bool = False, + wait_time: int = 0, + # TODO: Supported in mmengine's Viusalizer. + out_file: Optional[str] = None, + pred_score_thr: float = 0.3, + step: int = 0) -> None: + """Draw datasample and save to all backends. + + - If GT and prediction are plotted at the same time, they are + displayed in a stitched image where the left image is the + ground truth and the right image is the prediction. + - If ``show`` is True, all storage backends are ignored, and + the images will be displayed in a local window. + - If ``out_file`` is specified, the drawn image will be + saved to ``out_file``. t is usually used when the display + is not available. + Args: + name (str): The image identifier. + image (np.ndarray): The image to draw. + data_sample (OptTrackSampleList): A data + sample that contain annotations and predictions. + Defaults to None. + draw_gt (bool): Whether to draw GT TrackDataSample. + Default to True. + draw_pred (bool): Whether to draw Prediction TrackDataSample. + Defaults to True. + show (bool): Whether to display the drawn image. Default to False. + wait_time (int): The interval of show (s). Defaults to 0. + out_file (str): Path to output file. Defaults to None. + pred_score_thr (float): The threshold to visualize the bboxes + and masks. Defaults to 0.3. + step (int): Global step value to record. Defaults to 0. + """ + gt_img_data = None + pred_img_data = None + + if data_sample is not None: + data_sample = data_sample.cpu() + + if draw_gt and data_sample is not None: + assert 'gt_instances' in data_sample + gt_img_data = self._draw_instances(image, data_sample.gt_instances) + + if draw_pred and data_sample is not None: + assert 'pred_track_instances' in data_sample + pred_instances = data_sample.pred_track_instances + if 'scores' in pred_instances: + pred_instances = pred_instances[ + pred_instances.scores > pred_score_thr].cpu() + pred_img_data = self._draw_instances(image, pred_instances) + + if gt_img_data is not None and pred_img_data is not None: + drawn_img = np.concatenate((gt_img_data, pred_img_data), axis=1) + elif gt_img_data is not None: + drawn_img = gt_img_data + else: + drawn_img = pred_img_data + + if show: + self.show(drawn_img, win_name=name, wait_time=wait_time) + + if out_file is not None: + mmcv.imwrite(drawn_img[..., ::-1], out_file) + else: + self.add_image(name, drawn_img, step) diff --git a/mmdetection/mmdet/visualization/palette.py b/mmdetection/mmdet/visualization/palette.py new file mode 100644 index 00000000..3c402c08 --- /dev/null +++ b/mmdetection/mmdet/visualization/palette.py @@ -0,0 +1,108 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple, Union + +import mmcv +import numpy as np +from mmengine.utils import is_str + + +def palette_val(palette: List[tuple]) -> List[tuple]: + """Convert palette to matplotlib palette. + + Args: + palette (List[tuple]): A list of color tuples. + + Returns: + List[tuple[float]]: A list of RGB matplotlib color tuples. + """ + new_palette = [] + for color in palette: + color = [c / 255 for c in color] + new_palette.append(tuple(color)) + return new_palette + + +def get_palette(palette: Union[List[tuple], str, tuple], + num_classes: int) -> List[Tuple[int]]: + """Get palette from various inputs. + + Args: + palette (list[tuple] | str | tuple): palette inputs. + num_classes (int): the number of classes. + + Returns: + list[tuple[int]]: A list of color tuples. + """ + assert isinstance(num_classes, int) + + if isinstance(palette, list): + dataset_palette = palette + elif isinstance(palette, tuple): + dataset_palette = [palette] * num_classes + elif palette == 'random' or palette is None: + state = np.random.get_state() + # random color + np.random.seed(42) + palette = np.random.randint(0, 256, size=(num_classes, 3)) + np.random.set_state(state) + dataset_palette = [tuple(c) for c in palette] + elif palette == 'coco': + from mmdet.datasets import CocoDataset, CocoPanopticDataset + dataset_palette = CocoDataset.METAINFO['palette'] + if len(dataset_palette) < num_classes: + dataset_palette = CocoPanopticDataset.METAINFO['palette'] + elif palette == 'citys': + from mmdet.datasets import CityscapesDataset + dataset_palette = CityscapesDataset.METAINFO['palette'] + elif palette == 'voc': + from mmdet.datasets import VOCDataset + dataset_palette = VOCDataset.METAINFO['palette'] + elif is_str(palette): + dataset_palette = [mmcv.color_val(palette)[::-1]] * num_classes + else: + raise TypeError(f'Invalid type for palette: {type(palette)}') + + assert len(dataset_palette) >= num_classes, \ + 'The length of palette should not be less than `num_classes`.' + return dataset_palette + + +def _get_adaptive_scales(areas: np.ndarray, + min_area: int = 800, + max_area: int = 30000) -> np.ndarray: + """Get adaptive scales according to areas. + + The scale range is [0.5, 1.0]. When the area is less than + ``min_area``, the scale is 0.5 while the area is larger than + ``max_area``, the scale is 1.0. + + Args: + areas (ndarray): The areas of bboxes or masks with the + shape of (n, ). + min_area (int): Lower bound areas for adaptive scales. + Defaults to 800. + max_area (int): Upper bound areas for adaptive scales. + Defaults to 30000. + + Returns: + ndarray: The adaotive scales with the shape of (n, ). + """ + scales = 0.5 + (areas - min_area) // (max_area - min_area) + scales = np.clip(scales, 0.5, 1.0) + return scales + + +def jitter_color(color: tuple) -> tuple: + """Randomly jitter the given color in order to better distinguish instances + with the same class. + + Args: + color (tuple): The RGB color tuple. Each value is between [0, 255]. + + Returns: + tuple: The jittered color tuple. + """ + jitter = np.random.rand(3) + jitter = (jitter / np.linalg.norm(jitter) - 0.5) * 0.5 * 255 + color = np.clip(jitter + color, 0, 255).astype(np.uint8) + return tuple(color) diff --git a/mmdetection/model-index.yml b/mmdetection/model-index.yml new file mode 100644 index 00000000..d4b4392b --- /dev/null +++ b/mmdetection/model-index.yml @@ -0,0 +1,102 @@ +Import: + - configs/albu_example/metafile.yml + - configs/atss/metafile.yml + - configs/autoassign/metafile.yml + - configs/boxinst/metafile.yml + - configs/carafe/metafile.yml + - configs/cascade_rcnn/metafile.yml + - configs/cascade_rpn/metafile.yml + - configs/centernet/metafile.yml + - configs/centripetalnet/metafile.yml + - configs/condinst/metafile.yml + - configs/conditional_detr/metafile.yml + - configs/cornernet/metafile.yml + - configs/convnext/metafile.yml + - configs/crowddet/metafile.yml + - configs/dab_detr/metafile.yml + - configs/dcn/metafile.yml + - configs/dcnv2/metafile.yml + - configs/ddod/metafile.yml + - configs/deformable_detr/metafile.yml + - configs/detectors/metafile.yml + - configs/detr/metafile.yml + - configs/dino/metafile.yml + - configs/double_heads/metafile.yml + - configs/dyhead/metafile.yml + - configs/dynamic_rcnn/metafile.yml + - configs/efficientnet/metafile.yml + - configs/empirical_attention/metafile.yml + - configs/faster_rcnn/metafile.yml + - configs/fcos/metafile.yml + - configs/foveabox/metafile.yml + - configs/fpg/metafile.yml + - configs/free_anchor/metafile.yml + - configs/fsaf/metafile.yml + - configs/gcnet/metafile.yml + - configs/gfl/metafile.yml + - configs/ghm/metafile.yml + - configs/gn/metafile.yml + - configs/gn+ws/metafile.yml + - configs/grid_rcnn/metafile.yml + - configs/groie/metafile.yml + - configs/guided_anchoring/metafile.yml + - configs/hrnet/metafile.yml + - configs/htc/metafile.yml + - configs/instaboost/metafile.yml + - configs/lad/metafile.yml + - configs/ld/metafile.yml + - configs/libra_rcnn/metafile.yml + - configs/lvis/metafile.yml + - configs/mask2former/metafile.yml + - configs/mask_rcnn/metafile.yml + - configs/maskformer/metafile.yml + - configs/ms_rcnn/metafile.yml + - configs/nas_fcos/metafile.yml + - configs/nas_fpn/metafile.yml + - configs/openimages/metafile.yml + - configs/paa/metafile.yml + - configs/pafpn/metafile.yml + - configs/panoptic_fpn/metafile.yml + - configs/pvt/metafile.yml + - configs/pisa/metafile.yml + - configs/point_rend/metafile.yml + - configs/queryinst/metafile.yml + - configs/regnet/metafile.yml + - configs/reppoints/metafile.yml + - configs/res2net/metafile.yml + - configs/resnest/metafile.yml + - configs/resnet_strikes_back/metafile.yml + - configs/retinanet/metafile.yml + - configs/rpn/metafile.yml + - configs/rtmdet/metafile.yml + - configs/sabl/metafile.yml + - configs/scnet/metafile.yml + - configs/scratch/metafile.yml + - configs/seesaw_loss/metafile.yml + - configs/simple_copy_paste/metafile.yml + - configs/soft_teacher/metafile.yml + - configs/sparse_rcnn/metafile.yml + - configs/solo/metafile.yml + - configs/solov2/metafile.yml + - configs/ssd/metafile.yml + - configs/strong_baselines/metafile.yml + - configs/swin/metafile.yml + - configs/tridentnet/metafile.yml + - configs/tood/metafile.yml + - configs/vfnet/metafile.yml + - configs/yolact/metafile.yml + - configs/yolo/metafile.yml + - configs/yolof/metafile.yml + - configs/yolox/metafile.yml + - configs/bytetrack/metafile.yml + - configs/strongsort/metafile.yml + - configs/ocsort/metafile.yml + - configs/sort/metafile.yml + - configs/deepsort/metafile.yml + - configs/qdtrack/metafile.yml + - configs/mask2former_vis/metafile.yml + - configs/masktrack_rcnn/metafile.yml + - configs/glip/metafile.yml + - configs/ddq/metafile.yml + - configs/grounding_dino/metafile.yml + - configs/mm_grounding_dino/metafile.yml diff --git a/mmdetection/projects/AlignDETR/README.md b/mmdetection/projects/AlignDETR/README.md new file mode 100644 index 00000000..33690fe0 --- /dev/null +++ b/mmdetection/projects/AlignDETR/README.md @@ -0,0 +1,33 @@ +# AlignDETR + +> [Align-DETR: Improving DETR with Simple IoU-aware BCE loss](https://arxiv.org/abs/2304.07527) + + + +## Abstract + +DETR has set up a simple end-to-end pipeline for object detection by formulating this task as a set prediction problem, showing promising potential. However, despite the significant progress in improving DETR, this paper identifies a problem of misalignment in the output distribution, which prevents the best-regressed samples from being assigned with high confidence, hindering the model's accuracy. We propose a metric, recall of best-regressed samples, to quantitively evaluate the misalignment problem. Observing its importance, we propose a novel Align-DETR that incorporates a localization precision-aware classification loss in optimization. The proposed loss, IA-BCE, guides the training of DETR to build a strong correlation between classification score and localization precision. We also adopt the mixed-matching strategy, to facilitate DETR-based detectors with faster training convergence while keeping an end-to-end scheme. Moreover, to overcome the dramatic decrease in sample quality induced by the sparsity of queries, we introduce a prime sample weighting mechanism to suppress the interference of unimportant samples. Extensive experiments are conducted with very competitive results reported. In particular, it delivers a 46 (+3.8)% AP on the DAB-DETR baseline with the ResNet-50 backbone and reaches a new SOTA performance of 50.2% AP in the 1x setting on the COCO validation set when employing the strong baseline DINO. + +![image](https://github.com/open-mmlab/mmdetection/assets/33146359/5a4fa664-b4c6-487d-b6d8-22be9d59a2bc) + +## Results and Models + +| Backbone | Model | Lr schd | box AP | Config | Download | +| :------: | :---------: | :-----: | :----: | :------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | DINO-4scale | 12e | 50.5 | [config](./align_detr-4scale_r50_8xb2-12e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/align_detr/align_detr-4scale_r50_8xb2-12e_coco/align_detr-4scale_r50_8xb2-12e_coco_20230914_095734-61f921af.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/align_detr/align_detr-4scale_r50_8xb2-12e_coco/align_detr-4scale_r50_8xb2-12e_coco_20230914_095734.log.json) | +| R-50 | DINO-4scale | 24e | 51.4 | [config](./align_detr-4scale_r50_8xb2-24e_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/align_detr/align_detr-4scale_r50_8xb2-24e_coco/align_detr-4scale_r50_8xb2-24e_coco_20230919_152414-f4b6cf76.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/align_detr/align_detr-4scale_r50_8xb2-24e_coco/align_detr-4scale_r50_8xb2-24e_coco_20230919_152414.log.json) | + +## Citation + +We provide the config files for AlignDETR: [Align-DETR: Improving DETR with Simple IoU-aware BCE loss](https://arxiv.org/abs/2304.07527). + +```latex +@misc{cai2023aligndetr, + title={Align-DETR: Improving DETR with Simple IoU-aware BCE loss}, + author={Zhi Cai and Songtao Liu and Guodong Wang and Zheng Ge and Xiangyu Zhang and Di Huang}, + year={2023}, + eprint={2304.07527}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} +``` diff --git a/mmdetection/projects/AlignDETR/align_detr/__init__.py b/mmdetection/projects/AlignDETR/align_detr/__init__.py new file mode 100644 index 00000000..26a49b52 --- /dev/null +++ b/mmdetection/projects/AlignDETR/align_detr/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .align_detr_head import AlignDETRHead +from .mixed_hungarian_assigner import MixedHungarianAssigner + +__all__ = ['AlignDETRHead', 'MixedHungarianAssigner'] diff --git a/mmdetection/projects/AlignDETR/align_detr/align_detr_head.py b/mmdetection/projects/AlignDETR/align_detr/align_detr_head.py new file mode 100644 index 00000000..c06d1bd4 --- /dev/null +++ b/mmdetection/projects/AlignDETR/align_detr/align_detr_head.py @@ -0,0 +1,508 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Any, Dict, List, Tuple, Union + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.dense_heads import DINOHead +from mmdet.registry import MODELS +from mmdet.structures.bbox import (bbox_cxcywh_to_xyxy, bbox_overlaps, + bbox_xyxy_to_cxcywh) +from mmdet.utils import InstanceList +from .utils import KeysRecorder + + +@MODELS.register_module() +class AlignDETRHead(DINOHead): + r"""Head of the Align-DETR: Improving DETR with Simple IoU-aware BCE loss + + Code is modified from the `official github repo + `_. + + More details can be found in the `paper + `_ . + + Args: + all_layers_num_gt_repeat List[int]: Number to repeat gt for 1-to-k + matching between ground truth and predictions of each decoder + layer. Only used for matching queries, not for denoising queries. + Element count is `num_pred_layer`. If `as_two_stage` is True, then + the last element is for encoder output, and the others for + decoder layers. Otherwise, all elements are for decoder layers. + Defaults to a list of `1` for the last decoder layer and `2` for + the others. + alpha (float): Hyper-parameter of classification loss that controls + the proportion of each item to calculate `t`, the weighted + geometric average of the confident score and the IoU score, to + align classification and regression scores. Defaults to `0.25`. + gamma (float): Hyper-parameter of classification loss to do the hard + negative mining. Defaults to `2.0`. + tau (float): Hyper-parameter of classification and regression losses, + it is the temperature controlling the sharpness of the function + to calculate positive sample weight. Defaults to `1.5`. + """ + + def __init__(self, + *args, + all_layers_num_gt_repeat: List[int] = None, + alpha: float = 0.25, + gamma: float = 2.0, + tau: float = 1.5, + **kwargs) -> None: + self.all_layers_num_gt_repeat = all_layers_num_gt_repeat + self.alpha = alpha + self.gamma = gamma + self.tau = tau + self.weight_table = torch.zeros( + len(all_layers_num_gt_repeat), max(all_layers_num_gt_repeat)) + for layer_index, num_gt_repeat in enumerate(all_layers_num_gt_repeat): + self.weight_table[layer_index][:num_gt_repeat] = torch.exp( + -torch.arange(num_gt_repeat) / tau) + + super().__init__(*args, **kwargs) + assert len(self.all_layers_num_gt_repeat) == self.num_pred_layer + + def loss_by_feat(self, all_layers_cls_scores: Tensor, *args, + **kwargs) -> Any: + """Loss function. + AlignDETR: This method is based on `DINOHead.loss_by_feat`. + + Args: + all_layers_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, + num_queries_total, cls_out_channels), where + `num_queries_total` is the sum of `num_denoising_queries` + and `num_matching_queries`. + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + # Wrap `all_layers_cls_scores` with KeysRecorder to record its + # `__getitem__` keys and get decoder layer index. + all_layers_cls_scores = KeysRecorder(all_layers_cls_scores) + result = super(AlignDETRHead, + self).loss_by_feat(all_layers_cls_scores, *args, + **kwargs) + return result + + def loss_by_feat_single(self, cls_scores: Union[KeysRecorder, Tensor], + bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict]) -> Tuple[Tensor]: + """Loss function for outputs from a single decoder layer of a single + feature level. + AlignDETR: This method is based on `DINOHead.loss_by_feat_single`. + + Args: + cls_scores (Union[KeysRecorder, Tensor]): Box score logits from a + single decoder layer for all images, has shape (bs, + num_queries, cls_out_channels). + bbox_preds (Tensor): Sigmoid outputs from a single decoder layer + for all images, with normalized coordinate (cx, cy, w, h) and + shape (bs, num_queries, 4). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + + Returns: + Tuple[Tensor]: A tuple including `loss_cls`, `loss_box` and + `loss_iou`. + """ + # AlignDETR: Get layer_index. + if isinstance(cls_scores, KeysRecorder): + # Outputs are from decoder layer. Get layer_index from + # `__getitem__` keys history. + keys = [key for key in cls_scores.keys if isinstance(key, int)] + assert len(keys) == 1, \ + 'Failed to extract key from cls_scores.keys: {}'.format(keys) + layer_index = keys[0] + # Get dn_cls_scores tensor. + cls_scores = cls_scores.obj + else: + # Outputs are from encoder layer. + layer_index = self.num_pred_layer - 1 + + for img_meta in batch_img_metas: + img_meta['layer_index'] = layer_index + + results = super(AlignDETRHead, self).loss_by_feat_single( + cls_scores, + bbox_preds, + batch_gt_instances=batch_gt_instances, + batch_img_metas=batch_img_metas) + return results + + def get_targets(self, cls_scores_list: List[Tensor], + bbox_preds_list: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict]) -> tuple: + """Compute regression and classification targets for a batch image. + + Outputs from a single decoder layer of a single feature level are used. + AlignDETR: This method is based on `DETRHead.get_targets`. + + Args: + cls_scores_list (list[Tensor]): Box score logits from a single + decoder layer for each image, has shape [num_queries, + cls_out_channels]. + bbox_preds_list (list[Tensor]): Sigmoid outputs from a single + decoder layer for each image, with normalized coordinate + (cx, cy, w, h) and shape [num_queries, 4]. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + + Returns: + tuple: a tuple containing the following targets. + + - labels_list (list[Tensor]): Labels for all images. + - label_weights_list (list[Tensor]): Label weights for all images. + - bbox_targets_list (list[Tensor]): BBox targets for all images. + - bbox_weights_list (list[Tensor]): BBox weights for all images. + - num_total_pos (int): Number of positive samples in all images. + - num_total_neg (int): Number of negative samples in all images. + """ + results = super(AlignDETRHead, + self).get_targets(cls_scores_list, bbox_preds_list, + batch_gt_instances, batch_img_metas) + + # AlignDETR: `num_total_pos` for matching queries is the number of + # unique gt bboxes in the batch. Refer to AlignDETR official code: + # https://github.com/FelixCaae/AlignDETR/blob/8c2b1806026e1b33fe1c282577de1647e352d7f0/aligndetr/criterions/base_criterion.py#L195C15-L195C15 # noqa: E501 + num_total_pos = sum( + len(gt_instances) for gt_instances in batch_gt_instances) + + results = list(results) + results[-2] = num_total_pos + return tuple(results) + + def _get_targets_single(self, cls_score: Tensor, bbox_pred: Tensor, + gt_instances: InstanceData, + img_meta: dict) -> tuple: + """Compute regression and classification targets for one image. + + Outputs from a single decoder layer of a single feature level are used. + AlignDETR: This method is based on `DETRHead._get_targets_single`. + + Args: + cls_score (Tensor): Box score logits from a single decoder layer + for one image. Shape [num_queries, cls_out_channels]. + bbox_pred (Tensor): Sigmoid outputs from a single decoder layer + for one image, with normalized coordinate (cx, cy, w, h) and + shape [num_queries, 4]. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for one image. + layer_index (int): Decoder layer index for the outputs. Defaults + to `-1`. + + Returns: + tuple[Tensor]: a tuple containing the following for one image. + + - labels (Tensor): Labels of each image. + - label_weights (Tensor]): Label weights of each image. + - bbox_targets (Tensor): BBox targets of each image. + - bbox_weights (Tensor): BBox weights of each image. + - pos_inds (Tensor): Sampled positive indices for each image. + - neg_inds (Tensor): Sampled negative indices for each image. + """ + img_h, img_w = img_meta['img_shape'] + factor = bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0) + # convert bbox_pred from xywh, normalized to xyxy, unnormalized + bbox_pred = bbox_cxcywh_to_xyxy(bbox_pred) + bbox_pred = bbox_pred * factor + + pred_instances = InstanceData(scores=cls_score, bboxes=bbox_pred) + + # assigner and sampler + # AlignDETR: Get `k` of current layer. + layer_index = img_meta['layer_index'] + num_gt_repeat = self.all_layers_num_gt_repeat[layer_index] + assign_result = self.assigner.assign( + pred_instances=pred_instances, + gt_instances=gt_instances, + img_meta=img_meta, + k=num_gt_repeat) + + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + pos_inds = torch.nonzero( + assign_result.gt_inds > 0, as_tuple=False).squeeze(-1).unique() + neg_inds = torch.nonzero( + assign_result.gt_inds == 0, as_tuple=False).squeeze(-1).unique() + pos_assigned_gt_inds = assign_result.gt_inds[pos_inds] - 1 + pos_gt_bboxes = gt_bboxes[pos_assigned_gt_inds.long(), :] + + # AlignDETR: Get label targets, label weights, and bbox weights. + target_results = self._get_align_detr_targets_single( + cls_score, + bbox_pred, + gt_labels, + pos_gt_bboxes, + pos_inds, + pos_assigned_gt_inds, + layer_index, + is_matching_queries=True) + + label_targets, label_weights, bbox_weights = target_results + + # bbox targets + bbox_targets = torch.zeros_like(bbox_pred, dtype=gt_bboxes.dtype) + + # DETR regress the relative position of boxes (cxcywh) in the image. + # Thus the learning target should be normalized by the image size, also + # the box format should be converted from defaultly x1y1x2y2 to cxcywh. + pos_gt_bboxes_normalized = pos_gt_bboxes / factor + pos_gt_bboxes_targets = bbox_xyxy_to_cxcywh(pos_gt_bboxes_normalized) + bbox_targets[pos_inds] = pos_gt_bboxes_targets + return (label_targets, label_weights, bbox_targets, bbox_weights, + pos_inds, neg_inds) + + def _loss_dn_single(self, dn_cls_scores: KeysRecorder, + dn_bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + dn_meta: Dict[str, int]) -> Tuple[Tensor]: + """Denoising loss for outputs from a single decoder layer. + AlignDETR: This method is based on `DINOHead._loss_dn_single`. + + Args: + dn_cls_scores (KeysRecorder): Classification scores of a single + decoder layer in denoising part, has shape (bs, + num_denoising_queries, cls_out_channels). + dn_bbox_preds (Tensor): Regression outputs of a single decoder + layer in denoising part. Each is a 4D-tensor with normalized + coordinate format (cx, cy, w, h) and has shape + (bs, num_denoising_queries, 4). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + Tuple[Tensor]: A tuple including `loss_cls`, `loss_box` and + `loss_iou`. + """ + # AlignDETR: Get dn_cls_scores tensor. + dn_cls_scores = dn_cls_scores.obj + + # AlignDETR: Add layer outputs to meta info because they are not + # variables of method `_get_dn_targets_single`. + for image_index, img_meta in enumerate(batch_img_metas): + img_meta['dn_cls_score'] = dn_cls_scores[image_index] + img_meta['dn_bbox_pred'] = dn_bbox_preds[image_index] + + results = super()._loss_dn_single(dn_cls_scores, dn_bbox_preds, + batch_gt_instances, batch_img_metas, + dn_meta) + return results + + def _get_dn_targets_single(self, gt_instances: InstanceData, + img_meta: dict, dn_meta: Dict[str, + int]) -> tuple: + """Get targets in denoising part for one image. + AlignDETR: This method is based on + `DINOHead._get_dn_targets_single`. + and 1) Added passing `dn_cls_score`, `dn_bbox_pred` to this + method; 2) Modified the way to get targets. + Args: + dn_cls_score (Tensor): Box score logits from a single decoder + layer in denoising part for one image, has shape + [num_denoising_queries, cls_out_channels]. + dn_bbox_pred (Tensor): Sigmoid outputs from a single decoder + layer in denoising part for one image, with + normalized coordinate (cx, cy, w, h) and shape + [num_denoising_queries, 4]. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It should includes ``bboxes`` and ``labels`` + attributes. + img_meta (dict): Meta information for one image. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + + Returns: + tuple[Tensor]: a tuple containing the following for one image. + + - labels (Tensor): Labels of each image. + - label_weights (Tensor]): Label weights of each image. + - bbox_targets (Tensor): BBox targets of each image. + - bbox_weights (Tensor): BBox weights of each image. + - pos_inds (Tensor): Sampled positive indices for each image. + - neg_inds (Tensor): Sampled negative indices for each image. + """ + gt_bboxes = gt_instances.bboxes + gt_labels = gt_instances.labels + num_groups = dn_meta['num_denoising_groups'] + num_denoising_queries = dn_meta['num_denoising_queries'] + num_queries_each_group = int(num_denoising_queries / num_groups) + device = gt_bboxes.device + + if len(gt_labels) > 0: + t = torch.arange(len(gt_labels), dtype=torch.long, device=device) + t = t.unsqueeze(0).repeat(num_groups, 1) + pos_assigned_gt_inds = t.flatten() + pos_inds = torch.arange( + num_groups, dtype=torch.long, device=device) + pos_inds = pos_inds.unsqueeze(1) * num_queries_each_group + t + pos_inds = pos_inds.flatten() + else: + pos_inds = pos_assigned_gt_inds = \ + gt_bboxes.new_tensor([], dtype=torch.long) + + neg_inds = pos_inds + num_queries_each_group // 2 + + # AlignDETR: Get meta info and layer outputs. + img_h, img_w = img_meta['img_shape'] + dn_cls_score = img_meta['dn_cls_score'] + dn_bbox_pred = img_meta['dn_bbox_pred'] + factor = dn_bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0) + + # AlignDETR: Convert dn_bbox_pred from xywh, normalized to xyxy, + # unnormalized. + dn_bbox_pred = bbox_cxcywh_to_xyxy(dn_bbox_pred) + dn_bbox_pred = dn_bbox_pred * factor + + # AlignDETR: Get label targets, label weights, and bbox weights. + target_results = self._get_align_detr_targets_single( + dn_cls_score, dn_bbox_pred, gt_labels, + gt_bboxes.repeat([num_groups, 1]), pos_inds, pos_assigned_gt_inds) + + label_targets, label_weights, bbox_weights = target_results + + # bbox targets + bbox_targets = torch.zeros(num_denoising_queries, 4, device=device) + + # DETR regress the relative position of boxes (cxcywh) in the image. + # Thus the learning target should be normalized by the image size, also + # the box format should be converted from defaultly x1y1x2y2 to cxcywh. + gt_bboxes_normalized = gt_bboxes / factor + gt_bboxes_targets = bbox_xyxy_to_cxcywh(gt_bboxes_normalized) + bbox_targets[pos_inds] = gt_bboxes_targets.repeat([num_groups, 1]) + + return (label_targets, label_weights, bbox_targets, bbox_weights, + pos_inds, neg_inds) + + def _get_align_detr_targets_single(self, + cls_score: Tensor, + bbox_pred: Tensor, + gt_labels: Tensor, + pos_gt_bboxes: Tensor, + pos_inds: Tensor, + pos_assigned_gt_inds: Tensor, + layer_index: int = -1, + is_matching_queries: bool = False): + '''AlignDETR: Get label targets, label weights, and bbox weights based + on `t`, the weighted geometric average of the confident score and + the IoU score, to align classification and regression scores. + + Args: + cls_score (Tensor): Box score logits from the last encoder layer + or a single decoder layer for one image. Shape + [num_queries or num_denoising_queries, cls_out_channels]. + bbox_pred (Tensor): Sigmoid outputs from the last encoder layer + or a single decoder layer for one image, with unnormalized + coordinate (x, y, x, y) and shape + [num_queries or num_denoising_queries, 4]. + gt_labels (Tensor): Ground truth classification labels for one + image, has shape [num_gt]. + pos_gt_bboxes (Tensor): Positive ground truth bboxes for one + image, with unnormalized coordinate (x, y, x, y) and shape + [num_positive, 4]. + pos_inds (Tensor): Positive prediction box indices, has shape + [num_positive]. + pos_assigned_gt_inds Tensor: Positive ground truth box indices, + has shape [num_positive]. + layer_index (int): decoder layer index for the outputs. Defaults + to `-1`. + is_matching_queries (bool): The outputs are from matching + queries or denoising queries. Defaults to `False`. + + Returns: + tuple[Tensor]: a tuple containing the following for one image. + + - label_targets (Tensor): Labels of one image. Shape + [num_queries or num_denoising_queries, cls_out_channels]. + - label_weights (Tensor): Label weights of one image. Shape + [num_queries or num_denoising_queries, cls_out_channels]. + - bbox_weights (Tensor): BBox weights of one image. Shape + [num_queries or num_denoising_queries, 4]. + ''' + + # Classification loss + # = 1 * BCE(prob, t * rank_weights) for positive sample; + # = prob**gamma * BCE(prob, 0) for negative sample. + # That is, + # label_targets = 0 for negative sample; + # = t * rank_weights for positive sample. + # label_weights = pred**gamma for negative sample; + # = 1 for positive sample. + cls_prob = cls_score.sigmoid() + label_targets = torch.zeros_like( + cls_score, device=pos_gt_bboxes.device) + label_weights = cls_prob**self.gamma + + bbox_weights = torch.zeros_like(bbox_pred, dtype=pos_gt_bboxes.dtype) + + if len(pos_inds) == 0: + return label_targets, label_weights, bbox_weights + + pos_cls_score_inds = (pos_inds, gt_labels[pos_assigned_gt_inds]) + iou_scores = bbox_overlaps( + bbox_pred[pos_inds], pos_gt_bboxes, is_aligned=True) + + # t (Tensor): The weighted geometric average of the confident score + # and the IoU score, to align classification and regression scores. + # Shape [num_positive]. + t = ( + cls_prob[pos_cls_score_inds]**self.alpha * + iou_scores**(1 - self.alpha)) + t = torch.clamp(t, 0.01).detach() + + # Calculate rank_weights for matching queries. + if is_matching_queries: + # rank_weights (Tensor): Weights of each group of predictions + # assigned to the same positive gt bbox. Shape [num_positive]. + rank_weights = torch.zeros_like(t, dtype=self.weight_table.dtype) + + assert 0 <= layer_index < len(self.weight_table), layer_index + rank_to_weight = self.weight_table[layer_index].to( + rank_weights.device) + unique_gt_inds = torch.unique(pos_assigned_gt_inds) + + # For each positive gt bbox, get all predictions assigned to it, + # then calculate rank weights for this group of predictions. + for gt_index in unique_gt_inds: + pred_group_cond = pos_assigned_gt_inds == gt_index + # Weights are based on their rank sorted by t in the group. + pred_group = t[pred_group_cond] + indices = pred_group.sort(descending=True)[1] + group_weights = torch.zeros_like( + indices, dtype=self.weight_table.dtype) + group_weights[indices] = rank_to_weight[:len(indices)] + rank_weights[pred_group_cond] = group_weights + + t = t * rank_weights + pos_bbox_weights = rank_weights.unsqueeze(-1).repeat( + 1, bbox_pred.size(-1)) + bbox_weights[pos_inds] = pos_bbox_weights + else: + bbox_weights[pos_inds] = 1.0 + + label_targets[pos_cls_score_inds] = t + label_weights[pos_cls_score_inds] = 1.0 + + return label_targets, label_weights, bbox_weights diff --git a/mmdetection/projects/AlignDETR/align_detr/mixed_hungarian_assigner.py b/mmdetection/projects/AlignDETR/align_detr/mixed_hungarian_assigner.py new file mode 100644 index 00000000..cc31b5e6 --- /dev/null +++ b/mmdetection/projects/AlignDETR/align_detr/mixed_hungarian_assigner.py @@ -0,0 +1,162 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Union + +import torch +from mmengine import ConfigDict +from mmengine.structures import InstanceData +from scipy.optimize import linear_sum_assignment +from torch import Tensor + +from mmdet.models.task_modules import AssignResult, BaseAssigner +from mmdet.registry import TASK_UTILS + + +@TASK_UTILS.register_module() +class MixedHungarianAssigner(BaseAssigner): + """Computes 1-to-k matching between ground truth and predictions. + + This class computes an assignment between the targets and the predictions + based on the costs. The costs are weighted sum of some components. + For DETR the costs are weighted sum of classification cost, regression L1 + cost and regression iou cost. The targets don't include the no_object, so + generally there are more predictions than targets. After the 1-to-k + gt-pred matching, the un-matched are treated as backgrounds. Thus + each query prediction will be assigned with `0` or a positive integer + indicating the ground truth index: + + - 0: negative sample, no assigned gt + - positive integer: positive sample, index (1-based) of assigned gt + + Args: + match_costs (:obj:`ConfigDict` or dict or \ + List[Union[:obj:`ConfigDict`, dict]]): Match cost configs. + """ + + def __init__( + self, match_costs: Union[List[Union[dict, ConfigDict]], dict, + ConfigDict] + ) -> None: + + if isinstance(match_costs, dict): + match_costs = [match_costs] + elif isinstance(match_costs, list): + assert len(match_costs) > 0, \ + 'match_costs must not be a empty list.' + + self.match_costs = [ + TASK_UTILS.build(match_cost) for match_cost in match_costs + ] + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + img_meta: Optional[dict] = None, + k: int = 1, + **kwargs) -> AssignResult: + """Computes 1-to-k gt-pred matching based on the weighted costs. + + This method assign each query prediction to a ground truth or + background. The `assigned_gt_inds` with -1 means don't care, + 0 means negative sample, and positive number is the index (1-based) + of assigned gt. + The assignment is done in the following steps, the order matters. + + 1. Assign every prediction to -1. + 2. Compute the weighted costs, each cost has shape + (num_preds, num_gts). + 3. Update k according to num_preds and num_gts, then repeat + costs k times to shape: (num_preds, k * num_gts), so that each + gt will match k predictions. + 4. Do Hungarian matching on CPU based on the costs. + 5. Assign all to 0 (background) first, then for each matched pair + between predictions and gts, treat this prediction as foreground + and assign the corresponding gt index (plus 1) to it. + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. It may includes ``masks``, with shape + (n, h, w) or (n, l). + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + ``labels``, with shape (k, ) and ``masks``, with shape + (k, h, w) or (k, l). + img_meta (dict): Image information for one image. + + Returns: + :obj:`AssignResult`: The assigned result. + """ + assert isinstance(gt_instances.labels, Tensor) + num_gts, num_preds = len(gt_instances), len(pred_instances) + gt_labels = gt_instances.labels + device = gt_labels.device + + # 1. Assign -1 by default. + assigned_gt_inds = torch.full((num_preds, ), + -1, + dtype=torch.long, + device=device) + assigned_labels = torch.full((num_preds, ), + -1, + dtype=torch.long, + device=device) + + if num_gts == 0 or num_preds == 0: + # No ground truth or boxes, return empty assignment. + if num_gts == 0: + # No ground truth, assign all to background. + assigned_gt_inds[:] = 0 + return AssignResult( + num_gts=num_gts, + gt_inds=assigned_gt_inds, + max_overlaps=None, + labels=assigned_labels) + + # 2. Compute weighted costs. + cost_list = [] + for match_cost in self.match_costs: + cost = match_cost( + pred_instances=pred_instances, + gt_instances=gt_instances, + img_meta=img_meta) + cost_list.append(cost) + cost = torch.stack(cost_list).sum(dim=0) + + # 3. Update k according to num_preds and num_gts, then + # repeat the ground truth k times to perform 1-to-k gt-pred + # matching. For example, if num_preds = 900, num_gts = 3, then + # there are only 3 gt-pred pairs in sum for 1-1 matching. + # However, for 1-k gt-pred matching, if k = 4, then each + # gt is assigned 4 unique predictions, so there would be 12 + # gt-pred pairs in sum. + k = max(1, min(k, num_preds // num_gts)) + cost = cost.repeat(1, k) + + # 4. Do Hungarian matching on CPU using linear_sum_assignment. + cost = cost.detach().cpu() + if linear_sum_assignment is None: + raise ImportError('Please run "pip install scipy" ' + 'to install scipy first.') + + matched_row_inds, matched_col_inds = linear_sum_assignment(cost) + matched_row_inds = torch.from_numpy(matched_row_inds).to(device) + matched_col_inds = torch.from_numpy(matched_col_inds).to(device) + + matched_col_inds = matched_col_inds % num_gts + # 5. Assign backgrounds and foregrounds. + # Assign all indices to backgrounds first. + assigned_gt_inds[:] = 0 + # Assign foregrounds based on matching results. + assigned_gt_inds[matched_row_inds] = matched_col_inds + 1 + assigned_labels[matched_row_inds] = gt_labels[matched_col_inds] + assign_result = AssignResult( + num_gts=k * num_gts, + gt_inds=assigned_gt_inds, + max_overlaps=None, + labels=assigned_labels) + + return assign_result diff --git a/mmdetection/projects/AlignDETR/align_detr/utils.py b/mmdetection/projects/AlignDETR/align_detr/utils.py new file mode 100644 index 00000000..5a3c17ec --- /dev/null +++ b/mmdetection/projects/AlignDETR/align_detr/utils.py @@ -0,0 +1,34 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Any, List, Optional + + +class KeysRecorder: + """Wrap object to record its `__getitem__` keys in the history. + + Args: + obj (object): Any object that supports `__getitem__`. + keys (List): List of keys already recorded. Default to None. + """ + + def __init__(self, obj: Any, keys: Optional[List[Any]] = None) -> None: + self.obj = obj + + if keys is None: + keys = [] + self.keys = keys + + def __getitem__(self, key: Any) -> 'KeysRecorder': + """Wrap method `__getitem__` to record its keys. + + Args: + key: Key that is passed to the object. + + Returns: + result (KeysRecorder): KeysRecorder instance that wraps sub_obj. + """ + sub_obj = self.obj.__getitem__(key) + keys = self.keys.copy() + keys.append(key) + # Create a KeysRecorder instance from the sub_obj. + result = KeysRecorder(sub_obj, keys) + return result diff --git a/mmdetection/projects/AlignDETR/configs/align_detr-4scale_r50_8xb2-12e_coco.py b/mmdetection/projects/AlignDETR/configs/align_detr-4scale_r50_8xb2-12e_coco.py new file mode 100644 index 00000000..0fe06990 --- /dev/null +++ b/mmdetection/projects/AlignDETR/configs/align_detr-4scale_r50_8xb2-12e_coco.py @@ -0,0 +1,185 @@ +_base_ = [ + '../../../configs/_base_/datasets/coco_detection.py', + '../../../configs/_base_/default_runtime.py' +] +custom_imports = dict( + imports=['projects.AlignDETR.align_detr'], allow_failed_imports=False) + +model = dict( + type='DINO', + num_queries=900, # num_matching_queries + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(1, 2, 3), + # AlignDETR: Only freeze stem. + frozen_stages=0, + norm_cfg=dict(type='FrozenBN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[512, 1024, 2048], + kernel_size=1, + out_channels=256, + # AlignDETR: Add conv bias. + bias=True, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0))), # 0.1 for DeformDETR + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_heads=8, + dropout=0.0), # 0.1 for DeformDETR + cross_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0)), # 0.1 for DeformDETR + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, + normalize=True, + # AlignDETR: Set offset and temperature the same as DeformDETR. + offset=-0.5, # -0.5 for DeformDETR + temperature=10000), # 10000 for DeformDETR + bbox_head=dict( + type='AlignDETRHead', + # AlignDETR: First 6 elements of `all_layers_num_gt_repeat` are for + # decoder layers' outputs. The last element is for encoder layer. + all_layers_num_gt_repeat=[2, 2, 2, 2, 2, 1, 2], + alpha=0.25, + gamma=2.0, + tau=1.5, + num_classes=80, + sync_cls_avg_factor=True, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, + loss_weight=1.0), # 2.0 in DeformDETR + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( # TODO: Move to model.train_cfg ? + label_noise_scale=0.5, + box_noise_scale=1.0, # 0.4 for DN-DETR + group_cfg=dict(dynamic=True, num_groups=None, + num_dn_queries=100)), # TODO: half num_dn_queries + # training and testing settings + train_cfg=dict( + assigner=dict( + type='MixedHungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) # 100 for DeformDETR + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] +train_dataloader = dict( + dataset=dict( + # AlignDETR: Filter empty gt. + filter_cfg=dict(filter_empty_gt=True), + pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + type='AdamW', + lr=0.0001, # 0.0002 for DeformDETR + weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict( + custom_keys={'backbone': dict(lr_mult=0.1)}, + # AlignDETR: No norm decay. + norm_decay_mult=0.0) +) # custom_keys contains sampling_offsets and reference_points in DeformDETR # noqa + +# learning policy +max_epochs = 12 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.0001, + by_epoch=False, + begin=0, + end=2000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/projects/AlignDETR/configs/align_detr-4scale_r50_8xb2-24e_coco.py b/mmdetection/projects/AlignDETR/configs/align_detr-4scale_r50_8xb2-24e_coco.py new file mode 100644 index 00000000..f62114ce --- /dev/null +++ b/mmdetection/projects/AlignDETR/configs/align_detr-4scale_r50_8xb2-24e_coco.py @@ -0,0 +1,19 @@ +_base_ = './align_detr-4scale_r50_8xb2-12e_coco.py' +max_epochs = 24 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.0001, + by_epoch=False, + begin=0, + end=2000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[20], + gamma=0.1) +] diff --git a/mmdetection/projects/CO-DETR/README.md b/mmdetection/projects/CO-DETR/README.md new file mode 100644 index 00000000..787592ad --- /dev/null +++ b/mmdetection/projects/CO-DETR/README.md @@ -0,0 +1,32 @@ +# CO-DETR + +> [DETRs with Collaborative Hybrid Assignments Training](https://arxiv.org/abs/2211.12860) + + + +## Abstract + +In this paper, we provide the observation that too few queries assigned as positive samples in DETR with one-to-one set matching leads to sparse supervision on the encoder's output which considerably hurt the discriminative feature learning of the encoder and vice visa for attention learning in the decoder. To alleviate this, we present a novel collaborative hybrid assignments training scheme, namely Co-DETR, to learn more efficient and effective DETR-based detectors from versatile label assignment manners. This new training scheme can easily enhance the encoder's learning ability in end-to-end detectors by training the multiple parallel auxiliary heads supervised by one-to-many label assignments such as ATSS and Faster RCNN. In addition, we conduct extra customized positive queries by extracting the positive coordinates from these auxiliary heads to improve the training efficiency of positive samples in the decoder. In inference, these auxiliary heads are discarded and thus our method introduces no additional parameters and computational cost to the original detector while requiring no hand-crafted non-maximum suppression (NMS). We conduct extensive experiments to evaluate the effectiveness of the proposed approach on DETR variants, including DAB-DETR, Deformable-DETR, and DINO-Deformable-DETR. The state-of-the-art DINO-Deformable-DETR with Swin-L can be improved from 58.5% to 59.5% AP on COCO val. Surprisingly, incorporated with ViT-L backbone, we achieve 66.0% AP on COCO test-dev and 67.9% AP on LVIS val, outperforming previous methods by clear margins with much fewer model sizes. + +
    + +
    + +## Results and Models + +| Model | Backbone | Epochs | Aug | Dataset | box AP | Config | Download | +| :-------: | :------: | :----: | :--: | :---------------------------: | :----: | :--------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Co-DINO | R50 | 12 | LSJ | COCO | 52.0 | [config](configs/codino/co_dino_5scale_r50_lsj_8xb2_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_r50_lsj_8xb2_1x_coco/co_dino_5scale_r50_lsj_8xb2_1x_coco-69a72d67.pth)\\ [log](https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_r50_lsj_8xb2_1x_coco/co_dino_5scale_r50_lsj_8xb2_1x_coco_20230818_150457.json) | +| Co-DINO\* | R50 | 12 | DETR | COCO | 52.1 | [config](configs/codino/co_dino_5scale_r50_8xb2_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_r50_1x_coco-7481f903.pth) | +| Co-DINO\* | R50 | 36 | LSJ | COCO | 54.8 | [config](configs/codino/co_dino_5scale_r50_lsj_8xb2_3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_lsj_r50_3x_coco-fe5a6829.pth) | +| Co-DINO\* | Swin-L | 12 | DETR | COCO | 58.9 | [config](configs/codino/co_dino_5scale_swin_l_16xb1_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_swin_large_1x_coco-27c13da4.pth) | +| Co-DINO\* | Swin-L | 12 | LSJ | COCO | 59.3 | [config](configs/codino/co_dino_5scale_swin_l_lsj_16xb1_1x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_lsj_swin_large_1x_coco-3af73af2.pth) | +| Co-DINO\* | Swin-L | 36 | DETR | COCO | 60.0 | [config](configs/codino/co_dino_5scale_swin_l_16xb1_3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_swin_large_3x_coco-d7a6d8af.pth) | +| Co-DINO\* | Swin-L | 36 | LSJ | COCO | 60.7 | [config](configs/codino/co_dino_5scale_swin_l_lsj_16xb1_3x_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_lsj_swin_large_1x_coco-3af73af2.pth) | +| Co-DINO\* | Swin-L | 16 | DETR | Objects365 pre-trained + COCO | 64.1 | [config](configs/codino/co_dino_5scale_swin_l_16xb1_16e_o365tococo.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_swin_large_16e_o365tococo-614254c9.pth) | + +Note + +- Models labeled * are not trained by us, but from [CO-DETR](https://github.com/Sense-X/Co-DETR) official website. +- We find that the performance is unstable and may fluctuate by about 0.3 mAP. +- If you want to save GPU memory by enabling checkpointing, please use the `pip install fairscale` command. diff --git a/mmdetection/projects/CO-DETR/codetr/__init__.py b/mmdetection/projects/CO-DETR/codetr/__init__.py new file mode 100644 index 00000000..2ca4c02d --- /dev/null +++ b/mmdetection/projects/CO-DETR/codetr/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .co_atss_head import CoATSSHead +from .co_dino_head import CoDINOHead +from .co_roi_head import CoStandardRoIHead +from .codetr import CoDETR +from .transformer import (CoDinoTransformer, DetrTransformerDecoderLayer, + DetrTransformerEncoder, DinoTransformerDecoder) + +__all__ = [ + 'CoDETR', 'CoDinoTransformer', 'DinoTransformerDecoder', 'CoDINOHead', + 'CoATSSHead', 'CoStandardRoIHead', 'DetrTransformerEncoder', + 'DetrTransformerDecoderLayer' +] diff --git a/mmdetection/projects/CO-DETR/codetr/co_atss_head.py b/mmdetection/projects/CO-DETR/codetr/co_atss_head.py new file mode 100644 index 00000000..c6ae0180 --- /dev/null +++ b/mmdetection/projects/CO-DETR/codetr/co_atss_head.py @@ -0,0 +1,153 @@ +from typing import List + +import torch +from torch import Tensor + +from mmdet.models.dense_heads import ATSSHead +from mmdet.models.utils import images_to_levels, multi_apply +from mmdet.registry import MODELS +from mmdet.utils import InstanceList, OptInstanceList, reduce_mean + + +@MODELS.register_module() +class CoATSSHead(ATSSHead): + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + centernesses: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W) + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W) + centernesses (list[Tensor]): Centerness for each scale + level with shape (N, num_anchors * 1, H, W) + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], Optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + + (anchor_list, labels_list, label_weights_list, bbox_targets_list, + bbox_weights_list, avg_factor, ori_anchors, ori_labels, + ori_bbox_targets) = cls_reg_targets + + avg_factor = reduce_mean( + torch.tensor(avg_factor, dtype=torch.float, device=device)).item() + + losses_cls, losses_bbox, loss_centerness, \ + bbox_avg_factor = multi_apply( + self.loss_by_feat_single, + anchor_list, + cls_scores, + bbox_preds, + centernesses, + labels_list, + label_weights_list, + bbox_targets_list, + avg_factor=avg_factor) + + bbox_avg_factor = sum(bbox_avg_factor) + bbox_avg_factor = reduce_mean(bbox_avg_factor).clamp_(min=1).item() + losses_bbox = list(map(lambda x: x / bbox_avg_factor, losses_bbox)) + + # diff + pos_coords = (ori_anchors, ori_labels, ori_bbox_targets, 'atss') + return dict( + loss_cls=losses_cls, + loss_bbox=losses_bbox, + loss_centerness=loss_centerness, + pos_coords=pos_coords) + + def get_targets(self, + anchor_list: List[List[Tensor]], + valid_flag_list: List[List[Tensor]], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None, + unmap_outputs: bool = True) -> tuple: + """Get targets for ATSS head. + + This method is almost the same as `AnchorHead.get_targets()`. Besides + returning the targets as the parent method does, it also returns the + anchors as the first element of the returned tuple. + """ + num_imgs = len(batch_img_metas) + assert len(anchor_list) == len(valid_flag_list) == num_imgs + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + num_level_anchors_list = [num_level_anchors] * num_imgs + + # concat all level anchors and flags to a single tensor + for i in range(num_imgs): + assert len(anchor_list[i]) == len(valid_flag_list[i]) + anchor_list[i] = torch.cat(anchor_list[i]) + valid_flag_list[i] = torch.cat(valid_flag_list[i]) + + # compute targets for each image + if batch_gt_instances_ignore is None: + batch_gt_instances_ignore = [None] * num_imgs + (all_anchors, all_labels, all_label_weights, all_bbox_targets, + all_bbox_weights, pos_inds_list, neg_inds_list, + sampling_results_list) = multi_apply( + self._get_targets_single, + anchor_list, + valid_flag_list, + num_level_anchors_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore, + unmap_outputs=unmap_outputs) + # Get `avg_factor` of all images, which calculate in `SamplingResult`. + # When using sampling method, avg_factor is usually the sum of + # positive and negative priors. When using `PseudoSampler`, + # `avg_factor` is usually equal to the number of positive priors. + avg_factor = sum( + [results.avg_factor for results in sampling_results_list]) + # split targets to a list w.r.t. multiple levels + anchors_list = images_to_levels(all_anchors, num_level_anchors) + labels_list = images_to_levels(all_labels, num_level_anchors) + label_weights_list = images_to_levels(all_label_weights, + num_level_anchors) + bbox_targets_list = images_to_levels(all_bbox_targets, + num_level_anchors) + bbox_weights_list = images_to_levels(all_bbox_weights, + num_level_anchors) + + # diff + ori_anchors = all_anchors + ori_labels = all_labels + ori_bbox_targets = all_bbox_targets + return (anchors_list, labels_list, label_weights_list, + bbox_targets_list, bbox_weights_list, avg_factor, ori_anchors, + ori_labels, ori_bbox_targets) diff --git a/mmdetection/projects/CO-DETR/codetr/co_dino_head.py b/mmdetection/projects/CO-DETR/codetr/co_dino_head.py new file mode 100644 index 00000000..192acf97 --- /dev/null +++ b/mmdetection/projects/CO-DETR/codetr/co_dino_head.py @@ -0,0 +1,677 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import List + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import Linear +from mmcv.ops import batched_nms +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models import DINOHead +from mmdet.models.layers import CdnQueryGenerator +from mmdet.models.layers.transformer import inverse_sigmoid +from mmdet.models.utils import multi_apply +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import (bbox_cxcywh_to_xyxy, bbox_overlaps, + bbox_xyxy_to_cxcywh) +from mmdet.utils import InstanceList, reduce_mean + + +@MODELS.register_module() +class CoDINOHead(DINOHead): + + def __init__(self, + *args, + num_query=900, + transformer=None, + in_channels=2048, + max_pos_coords=300, + dn_cfg=None, + use_zero_padding=False, + positional_encoding=dict( + type='SinePositionalEncoding', + num_feats=128, + normalize=True), + **kwargs): + self.with_box_refine = True + self.mixed_selection = True + self.in_channels = in_channels + self.max_pos_coords = max_pos_coords + self.positional_encoding = positional_encoding + self.num_query = num_query + self.use_zero_padding = use_zero_padding + + if 'two_stage_num_proposals' in transformer: + assert transformer['two_stage_num_proposals'] == num_query, \ + 'two_stage_num_proposals must be equal to num_query for DINO' + else: + transformer['two_stage_num_proposals'] = num_query + transformer['as_two_stage'] = True + if self.mixed_selection: + transformer['mixed_selection'] = self.mixed_selection + self.transformer = transformer + self.act_cfg = transformer.get('act_cfg', + dict(type='ReLU', inplace=True)) + + super().__init__(*args, **kwargs) + + self.activate = MODELS.build(self.act_cfg) + self.positional_encoding = MODELS.build(self.positional_encoding) + self.init_denoising(dn_cfg) + + def _init_layers(self): + self.transformer = MODELS.build(self.transformer) + self.embed_dims = self.transformer.embed_dims + assert hasattr(self.positional_encoding, 'num_feats') + num_feats = self.positional_encoding.num_feats + assert num_feats * 2 == self.embed_dims, 'embed_dims should' \ + f' be exactly 2 times of num_feats. Found {self.embed_dims}' \ + f' and {num_feats}.' + """Initialize classification branch and regression branch of head.""" + fc_cls = Linear(self.embed_dims, self.cls_out_channels) + reg_branch = [] + for _ in range(self.num_reg_fcs): + reg_branch.append(Linear(self.embed_dims, self.embed_dims)) + reg_branch.append(nn.ReLU()) + reg_branch.append(Linear(self.embed_dims, 4)) + reg_branch = nn.Sequential(*reg_branch) + + def _get_clones(module, N): + return nn.ModuleList([copy.deepcopy(module) for i in range(N)]) + + # last reg_branch is used to generate proposal from + # encode feature map when as_two_stage is True. + num_pred = (self.transformer.decoder.num_layers + 1) if \ + self.as_two_stage else self.transformer.decoder.num_layers + + self.cls_branches = _get_clones(fc_cls, num_pred) + self.reg_branches = _get_clones(reg_branch, num_pred) + + self.downsample = nn.Sequential( + nn.Conv2d( + self.embed_dims, + self.embed_dims, + kernel_size=3, + stride=2, + padding=1), nn.GroupNorm(32, self.embed_dims)) + + def init_denoising(self, dn_cfg): + if dn_cfg is not None: + dn_cfg['num_classes'] = self.num_classes + dn_cfg['num_matching_queries'] = self.num_query + dn_cfg['embed_dims'] = self.embed_dims + self.dn_generator = CdnQueryGenerator(**dn_cfg) + + def forward(self, + mlvl_feats, + img_metas, + dn_label_query=None, + dn_bbox_query=None, + attn_mask=None): + batch_size = mlvl_feats[0].size(0) + input_img_h, input_img_w = img_metas[0]['batch_input_shape'] + img_masks = mlvl_feats[0].new_ones( + (batch_size, input_img_h, input_img_w)) + for img_id in range(batch_size): + img_h, img_w = img_metas[img_id]['img_shape'] + img_masks[img_id, :img_h, :img_w] = 0 + + mlvl_masks = [] + mlvl_positional_encodings = [] + for feat in mlvl_feats: + mlvl_masks.append( + F.interpolate(img_masks[None], + size=feat.shape[-2:]).to(torch.bool).squeeze(0)) + mlvl_positional_encodings.append( + self.positional_encoding(mlvl_masks[-1])) + + query_embeds = None + hs, inter_references, topk_score, topk_anchor, enc_outputs = \ + self.transformer( + mlvl_feats, + mlvl_masks, + query_embeds, + mlvl_positional_encodings, + dn_label_query, + dn_bbox_query, + attn_mask, + reg_branches=self.reg_branches if self.with_box_refine else None, # noqa:E501 + cls_branches=self.cls_branches if self.as_two_stage else None # noqa:E501 + ) + outs = [] + num_level = len(mlvl_feats) + start = 0 + for lvl in range(num_level): + bs, c, h, w = mlvl_feats[lvl].shape + end = start + h * w + feat = enc_outputs[start:end].permute(1, 2, 0).contiguous() + start = end + outs.append(feat.reshape(bs, c, h, w)) + outs.append(self.downsample(outs[-1])) + + hs = hs.permute(0, 2, 1, 3) + + if dn_label_query is not None and dn_label_query.size(1) == 0: + # NOTE: If there is no target in the image, the parameters of + # label_embedding won't be used in producing loss, which raises + # RuntimeError when using distributed mode. + hs[0] += self.dn_generator.label_embedding.weight[0, 0] * 0.0 + + outputs_classes = [] + outputs_coords = [] + + for lvl in range(hs.shape[0]): + reference = inter_references[lvl] + reference = inverse_sigmoid(reference, eps=1e-3) + outputs_class = self.cls_branches[lvl](hs[lvl]) + tmp = self.reg_branches[lvl](hs[lvl]) + if reference.shape[-1] == 4: + tmp += reference + else: + assert reference.shape[-1] == 2 + tmp[..., :2] += reference + outputs_coord = tmp.sigmoid() + outputs_classes.append(outputs_class) + outputs_coords.append(outputs_coord) + + outputs_classes = torch.stack(outputs_classes) + outputs_coords = torch.stack(outputs_coords) + + return outputs_classes, outputs_coords, topk_score, topk_anchor, outs + + def predict(self, + feats: List[Tensor], + batch_data_samples: SampleList, + rescale: bool = True) -> InstanceList: + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + outs = self.forward(feats, batch_img_metas) + + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas, rescale=rescale) + + return predictions + + def predict_by_feat(self, + all_cls_scores, + all_bbox_preds, + enc_cls_scores, + enc_bbox_preds, + enc_outputs, + batch_img_metas, + rescale=True): + + cls_scores = all_cls_scores[-1] + bbox_preds = all_bbox_preds[-1] + + result_list = [] + for img_id in range(len(batch_img_metas)): + cls_score = cls_scores[img_id] + bbox_pred = bbox_preds[img_id] + img_meta = batch_img_metas[img_id] + results = self._predict_by_feat_single(cls_score, bbox_pred, + img_meta, rescale) + result_list.append(results) + return result_list + + def _predict_by_feat_single(self, + cls_score: Tensor, + bbox_pred: Tensor, + img_meta: dict, + rescale: bool = True) -> InstanceData: + """Transform outputs from the last decoder layer into bbox predictions + for each image. + + Args: + cls_score (Tensor): Box score logits from the last decoder layer + for each image. Shape [num_queries, cls_out_channels]. + bbox_pred (Tensor): Sigmoid outputs from the last decoder layer + for each image, with coordinate format (cx, cy, w, h) and + shape [num_queries, 4]. + img_meta (dict): Image meta info. + rescale (bool): If True, return boxes in original image + space. Default True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert len(cls_score) == len(bbox_pred) # num_queries + max_per_img = self.test_cfg.get('max_per_img', self.num_query) + score_thr = self.test_cfg.get('score_thr', 0) + with_nms = self.test_cfg.get('nms', None) + + img_shape = img_meta['img_shape'] + # exclude background + if self.loss_cls.use_sigmoid: + cls_score = cls_score.sigmoid() + scores, indexes = cls_score.view(-1).topk(max_per_img) + det_labels = indexes % self.num_classes + bbox_index = indexes // self.num_classes + bbox_pred = bbox_pred[bbox_index] + else: + scores, det_labels = F.softmax(cls_score, dim=-1)[..., :-1].max(-1) + scores, bbox_index = scores.topk(max_per_img) + bbox_pred = bbox_pred[bbox_index] + det_labels = det_labels[bbox_index] + + if score_thr > 0: + valid_mask = scores > score_thr + scores = scores[valid_mask] + bbox_pred = bbox_pred[valid_mask] + det_labels = det_labels[valid_mask] + + det_bboxes = bbox_cxcywh_to_xyxy(bbox_pred) + det_bboxes[:, 0::2] = det_bboxes[:, 0::2] * img_shape[1] + det_bboxes[:, 1::2] = det_bboxes[:, 1::2] * img_shape[0] + det_bboxes[:, 0::2].clamp_(min=0, max=img_shape[1]) + det_bboxes[:, 1::2].clamp_(min=0, max=img_shape[0]) + if rescale: + assert img_meta.get('scale_factor') is not None + det_bboxes /= det_bboxes.new_tensor( + img_meta['scale_factor']).repeat((1, 2)) + + results = InstanceData() + results.bboxes = det_bboxes + results.scores = scores + results.labels = det_labels + + if with_nms and results.bboxes.numel() > 0: + det_bboxes, keep_idxs = batched_nms(results.bboxes, results.scores, + results.labels, + self.test_cfg.nms) + results = results[keep_idxs] + results.scores = det_bboxes[:, -1] + results = results[:max_per_img] + + return results + + def loss(self, x, batch_data_samples): + assert self.dn_generator is not None, '"dn_cfg" must be set' + + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + dn_label_query, dn_bbox_query, attn_mask, dn_meta = \ + self.dn_generator(batch_data_samples) + + outs = self(x, batch_img_metas, dn_label_query, dn_bbox_query, + attn_mask) + + loss_inputs = outs[:-1] + (batch_gt_instances, batch_img_metas, + dn_meta) + losses = self.loss_by_feat(*loss_inputs) + enc_outputs = outs[-1] + return losses, enc_outputs + + def forward_aux(self, mlvl_feats, img_metas, aux_targets, head_idx): + """Forward function. + + Args: + mlvl_feats (tuple[Tensor]): Features from the upstream + network, each is a 4D-tensor with shape + (N, C, H, W). + img_metas (list[dict]): List of image information. + + Returns: + all_cls_scores (Tensor): Outputs from the classification head, \ + shape [nb_dec, bs, num_query, cls_out_channels]. Note \ + cls_out_channels should includes background. + all_bbox_preds (Tensor): Sigmoid outputs from the regression \ + head with normalized coordinate format (cx, cy, w, h). \ + Shape [nb_dec, bs, num_query, 4]. + enc_outputs_class (Tensor): The score of each point on encode \ + feature map, has shape (N, h*w, num_class). Only when \ + as_two_stage is True it would be returned, otherwise \ + `None` would be returned. + enc_outputs_coord (Tensor): The proposal generate from the \ + encode feature map, has shape (N, h*w, 4). Only when \ + as_two_stage is True it would be returned, otherwise \ + `None` would be returned. + """ + aux_coords, aux_labels, aux_targets, aux_label_weights, \ + aux_bbox_weights, aux_feats, attn_masks = aux_targets + batch_size = mlvl_feats[0].size(0) + input_img_h, input_img_w = img_metas[0]['batch_input_shape'] + img_masks = mlvl_feats[0].new_ones( + (batch_size, input_img_h, input_img_w)) + for img_id in range(batch_size): + img_h, img_w = img_metas[img_id]['img_shape'] + img_masks[img_id, :img_h, :img_w] = 0 + + mlvl_masks = [] + mlvl_positional_encodings = [] + for feat in mlvl_feats: + mlvl_masks.append( + F.interpolate(img_masks[None], + size=feat.shape[-2:]).to(torch.bool).squeeze(0)) + mlvl_positional_encodings.append( + self.positional_encoding(mlvl_masks[-1])) + + query_embeds = None + hs, inter_references = self.transformer.forward_aux( + mlvl_feats, + mlvl_masks, + query_embeds, + mlvl_positional_encodings, + aux_coords, + pos_feats=aux_feats, + reg_branches=self.reg_branches if self.with_box_refine else None, + cls_branches=self.cls_branches if self.as_two_stage else None, + return_encoder_output=True, + attn_masks=attn_masks, + head_idx=head_idx) + + hs = hs.permute(0, 2, 1, 3) + outputs_classes = [] + outputs_coords = [] + + for lvl in range(hs.shape[0]): + reference = inter_references[lvl] + reference = inverse_sigmoid(reference, eps=1e-3) + outputs_class = self.cls_branches[lvl](hs[lvl]) + tmp = self.reg_branches[lvl](hs[lvl]) + if reference.shape[-1] == 4: + tmp += reference + else: + assert reference.shape[-1] == 2 + tmp[..., :2] += reference + outputs_coord = tmp.sigmoid() + outputs_classes.append(outputs_class) + outputs_coords.append(outputs_coord) + + outputs_classes = torch.stack(outputs_classes) + outputs_coords = torch.stack(outputs_coords) + + return outputs_classes, outputs_coords, None, None + + def loss_aux(self, + x, + pos_coords=None, + head_idx=0, + batch_data_samples=None): + batch_gt_instances = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + batch_gt_instances.append(data_sample.gt_instances) + + gt_bboxes = [b.bboxes for b in batch_gt_instances] + gt_labels = [b.labels for b in batch_gt_instances] + + aux_targets = self.get_aux_targets(pos_coords, batch_img_metas, x, + head_idx) + outs = self.forward_aux(x[:-1], batch_img_metas, aux_targets, head_idx) + outs = outs + aux_targets + if gt_labels is None: + loss_inputs = outs + (gt_bboxes, batch_img_metas) + else: + loss_inputs = outs + (gt_bboxes, gt_labels, batch_img_metas) + losses = self.loss_aux_by_feat(*loss_inputs) + return losses + + def get_aux_targets(self, pos_coords, img_metas, mlvl_feats, head_idx): + coords, labels, targets = pos_coords[:3] + head_name = pos_coords[-1] + bs, c = len(coords), mlvl_feats[0].shape[1] + max_num_coords = 0 + all_feats = [] + for i in range(bs): + label = labels[i] + feats = [ + feat[i].reshape(c, -1).transpose(1, 0) for feat in mlvl_feats + ] + feats = torch.cat(feats, dim=0) + bg_class_ind = self.num_classes + pos_inds = ((label >= 0) + & (label < bg_class_ind)).nonzero().squeeze(1) + max_num_coords = max(max_num_coords, len(pos_inds)) + all_feats.append(feats) + max_num_coords = min(self.max_pos_coords, max_num_coords) + max_num_coords = max(9, max_num_coords) + + if self.use_zero_padding: + attn_masks = [] + label_weights = coords[0].new_zeros([bs, max_num_coords]) + else: + attn_masks = None + label_weights = coords[0].new_ones([bs, max_num_coords]) + bbox_weights = coords[0].new_zeros([bs, max_num_coords, 4]) + + aux_coords, aux_labels, aux_targets, aux_feats = [], [], [], [] + + for i in range(bs): + coord, label, target = coords[i], labels[i], targets[i] + feats = all_feats[i] + if 'rcnn' in head_name: + feats = pos_coords[-2][i] + num_coords_per_point = 1 + else: + num_coords_per_point = coord.shape[0] // feats.shape[0] + feats = feats.unsqueeze(1).repeat(1, num_coords_per_point, 1) + feats = feats.reshape(feats.shape[0] * num_coords_per_point, + feats.shape[-1]) + img_meta = img_metas[i] + img_h, img_w = img_meta['img_shape'] + factor = coord.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0) + bg_class_ind = self.num_classes + pos_inds = ((label >= 0) + & (label < bg_class_ind)).nonzero().squeeze(1) + neg_inds = (label == bg_class_ind).nonzero().squeeze(1) + if pos_inds.shape[0] > max_num_coords: + indices = torch.randperm( + pos_inds.shape[0])[:max_num_coords].cuda() + pos_inds = pos_inds[indices] + + coord = bbox_xyxy_to_cxcywh(coord[pos_inds] / factor) + label = label[pos_inds] + target = bbox_xyxy_to_cxcywh(target[pos_inds] / factor) + feat = feats[pos_inds] + + if self.use_zero_padding: + label_weights[i][:len(label)] = 1 + bbox_weights[i][:len(label)] = 1 + attn_mask = torch.zeros([ + max_num_coords, + max_num_coords, + ]).bool().to(coord.device) + else: + bbox_weights[i][:len(label)] = 1 + + if coord.shape[0] < max_num_coords: + padding_shape = max_num_coords - coord.shape[0] + if self.use_zero_padding: + padding_coord = coord.new_zeros([padding_shape, 4]) + padding_label = label.new_ones([padding_shape + ]) * self.num_classes + padding_target = target.new_zeros([padding_shape, 4]) + padding_feat = feat.new_zeros([padding_shape, c]) + attn_mask[coord.shape[0]:, 0:coord.shape[0], ] = True + attn_mask[:, coord.shape[0]:, ] = True + else: + indices = torch.randperm( + neg_inds.shape[0])[:padding_shape].cuda() + neg_inds = neg_inds[indices] + padding_coord = bbox_xyxy_to_cxcywh(coords[i][neg_inds] / + factor) + padding_label = labels[i][neg_inds] + padding_target = bbox_xyxy_to_cxcywh(targets[i][neg_inds] / + factor) + padding_feat = feats[neg_inds] + coord = torch.cat((coord, padding_coord), dim=0) + label = torch.cat((label, padding_label), dim=0) + target = torch.cat((target, padding_target), dim=0) + feat = torch.cat((feat, padding_feat), dim=0) + if self.use_zero_padding: + attn_masks.append(attn_mask.unsqueeze(0)) + aux_coords.append(coord.unsqueeze(0)) + aux_labels.append(label.unsqueeze(0)) + aux_targets.append(target.unsqueeze(0)) + aux_feats.append(feat.unsqueeze(0)) + + if self.use_zero_padding: + attn_masks = torch.cat( + attn_masks, dim=0).unsqueeze(1).repeat(1, 8, 1, 1) + attn_masks = attn_masks.reshape(bs * 8, max_num_coords, + max_num_coords) + else: + attn_masks = None + + aux_coords = torch.cat(aux_coords, dim=0) + aux_labels = torch.cat(aux_labels, dim=0) + aux_targets = torch.cat(aux_targets, dim=0) + aux_feats = torch.cat(aux_feats, dim=0) + aux_label_weights = label_weights + aux_bbox_weights = bbox_weights + return (aux_coords, aux_labels, aux_targets, aux_label_weights, + aux_bbox_weights, aux_feats, attn_masks) + + def loss_aux_by_feat(self, + all_cls_scores, + all_bbox_preds, + enc_cls_scores, + enc_bbox_preds, + aux_coords, + aux_labels, + aux_targets, + aux_label_weights, + aux_bbox_weights, + aux_feats, + attn_masks, + gt_bboxes_list, + gt_labels_list, + img_metas, + gt_bboxes_ignore=None): + num_dec_layers = len(all_cls_scores) + all_labels = [aux_labels for _ in range(num_dec_layers)] + all_label_weights = [aux_label_weights for _ in range(num_dec_layers)] + all_bbox_targets = [aux_targets for _ in range(num_dec_layers)] + all_bbox_weights = [aux_bbox_weights for _ in range(num_dec_layers)] + img_metas_list = [img_metas for _ in range(num_dec_layers)] + all_gt_bboxes_ignore_list = [ + gt_bboxes_ignore for _ in range(num_dec_layers) + ] + + losses_cls, losses_bbox, losses_iou = multi_apply( + self._loss_aux_by_feat_single, all_cls_scores, all_bbox_preds, + all_labels, all_label_weights, all_bbox_targets, all_bbox_weights, + img_metas_list, all_gt_bboxes_ignore_list) + + loss_dict = dict() + # loss of proposal generated from encode feature map. + + # loss from the last decoder layer + loss_dict['loss_cls_aux'] = losses_cls[-1] + loss_dict['loss_bbox_aux'] = losses_bbox[-1] + loss_dict['loss_iou_aux'] = losses_iou[-1] + # loss from other decoder layers + num_dec_layer = 0 + for loss_cls_i, loss_bbox_i, loss_iou_i in zip(losses_cls[:-1], + losses_bbox[:-1], + losses_iou[:-1]): + loss_dict[f'd{num_dec_layer}.loss_cls_aux'] = loss_cls_i + loss_dict[f'd{num_dec_layer}.loss_bbox_aux'] = loss_bbox_i + loss_dict[f'd{num_dec_layer}.loss_iou_aux'] = loss_iou_i + num_dec_layer += 1 + return loss_dict + + def _loss_aux_by_feat_single(self, + cls_scores, + bbox_preds, + labels, + label_weights, + bbox_targets, + bbox_weights, + img_metas, + gt_bboxes_ignore_list=None): + num_imgs = cls_scores.size(0) + num_q = cls_scores.size(1) + + try: + labels = labels.reshape(num_imgs * num_q) + label_weights = label_weights.reshape(num_imgs * num_q) + bbox_targets = bbox_targets.reshape(num_imgs * num_q, 4) + bbox_weights = bbox_weights.reshape(num_imgs * num_q, 4) + except Exception: + return cls_scores.mean() * 0, cls_scores.mean( + ) * 0, cls_scores.mean() * 0 + + bg_class_ind = self.num_classes + num_total_pos = len( + ((labels >= 0) & (labels < bg_class_ind)).nonzero().squeeze(1)) + num_total_neg = num_imgs * num_q - num_total_pos + + # classification loss + cls_scores = cls_scores.reshape(-1, self.cls_out_channels) + # construct weighted avg_factor to match with the official DETR repo + cls_avg_factor = num_total_pos * 1.0 + \ + num_total_neg * self.bg_cls_weight + if self.sync_cls_avg_factor: + cls_avg_factor = reduce_mean( + cls_scores.new_tensor([cls_avg_factor])) + cls_avg_factor = max(cls_avg_factor, 1) + + bg_class_ind = self.num_classes + pos_inds = ((labels >= 0) + & (labels < bg_class_ind)).nonzero().squeeze(1) + scores = label_weights.new_zeros(labels.shape) + pos_bbox_targets = bbox_targets[pos_inds] + pos_decode_bbox_targets = bbox_cxcywh_to_xyxy(pos_bbox_targets) + pos_bbox_pred = bbox_preds.reshape(-1, 4)[pos_inds] + pos_decode_bbox_pred = bbox_cxcywh_to_xyxy(pos_bbox_pred) + scores[pos_inds] = bbox_overlaps( + pos_decode_bbox_pred.detach(), + pos_decode_bbox_targets, + is_aligned=True) + loss_cls = self.loss_cls( + cls_scores, (labels, scores), + weight=label_weights, + avg_factor=cls_avg_factor) + + # Compute the average number of gt boxes across all gpus, for + # normalization purposes + num_total_pos = loss_cls.new_tensor([num_total_pos]) + num_total_pos = torch.clamp(reduce_mean(num_total_pos), min=1).item() + + # construct factors used for rescale bboxes + factors = [] + for img_meta, bbox_pred in zip(img_metas, bbox_preds): + img_h, img_w = img_meta['img_shape'] + factor = bbox_pred.new_tensor([img_w, img_h, img_w, + img_h]).unsqueeze(0).repeat( + bbox_pred.size(0), 1) + factors.append(factor) + factors = torch.cat(factors, 0) + + # DETR regress the relative position of boxes (cxcywh) in the image, + # thus the learning target is normalized by the image size. So here + # we need to re-scale them for calculating IoU loss + bbox_preds = bbox_preds.reshape(-1, 4) + bboxes = bbox_cxcywh_to_xyxy(bbox_preds) * factors + bboxes_gt = bbox_cxcywh_to_xyxy(bbox_targets) * factors + + # regression IoU loss, defaultly GIoU loss + loss_iou = self.loss_iou( + bboxes, bboxes_gt, bbox_weights, avg_factor=num_total_pos) + + # regression L1 loss + loss_bbox = self.loss_bbox( + bbox_preds, bbox_targets, bbox_weights, avg_factor=num_total_pos) + return loss_cls, loss_bbox, loss_iou diff --git a/mmdetection/projects/CO-DETR/codetr/co_roi_head.py b/mmdetection/projects/CO-DETR/codetr/co_roi_head.py new file mode 100644 index 00000000..9aafb53b --- /dev/null +++ b/mmdetection/projects/CO-DETR/codetr/co_roi_head.py @@ -0,0 +1,108 @@ +from typing import List, Tuple + +import torch +from torch import Tensor + +from mmdet.models.roi_heads import StandardRoIHead +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.models.utils import unpack_gt_instances +from mmdet.registry import MODELS +from mmdet.structures import DetDataSample +from mmdet.structures.bbox import bbox2roi +from mmdet.utils import InstanceList + + +@MODELS.register_module() +class CoStandardRoIHead(StandardRoIHead): + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: List[DetDataSample]) -> dict: + max_proposal = 2000 + + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, _ = outputs + + # assign gts and sample proposals + num_imgs = len(batch_data_samples) + sampling_results = [] + for i in range(num_imgs): + # rename rpn_results.bboxes to rpn_results.priors + rpn_results = rpn_results_list[i] + rpn_results.priors = rpn_results.pop('bboxes') + + assign_result = self.bbox_assigner.assign( + rpn_results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + sampling_result = self.bbox_sampler.sample( + assign_result, + rpn_results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + sampling_results.append(sampling_result) + + losses = dict() + # bbox head forward and loss + if self.with_bbox: + bbox_results = self.bbox_loss(x, sampling_results) + losses.update(bbox_results['loss_bbox']) + + bbox_targets = bbox_results['bbox_targets'] + for res in sampling_results: + max_proposal = min(max_proposal, res.bboxes.shape[0]) + ori_coords = bbox2roi([res.bboxes for res in sampling_results]) + ori_proposals, ori_labels, \ + ori_bbox_targets, ori_bbox_feats = [], [], [], [] + for i in range(num_imgs): + idx = (ori_coords[:, 0] == i).nonzero().squeeze(1) + idx = idx[:max_proposal] + ori_proposal = ori_coords[idx][:, 1:].unsqueeze(0) + ori_label = bbox_targets[0][idx].unsqueeze(0) + ori_bbox_target = bbox_targets[2][idx].unsqueeze(0) + ori_bbox_feat = bbox_results['bbox_feats'].mean(-1).mean(-1) + ori_bbox_feat = ori_bbox_feat[idx].unsqueeze(0) + ori_proposals.append(ori_proposal) + ori_labels.append(ori_label) + ori_bbox_targets.append(ori_bbox_target) + ori_bbox_feats.append(ori_bbox_feat) + ori_coords = torch.cat(ori_proposals, dim=0) + ori_labels = torch.cat(ori_labels, dim=0) + ori_bbox_targets = torch.cat(ori_bbox_targets, dim=0) + ori_bbox_feats = torch.cat(ori_bbox_feats, dim=0) + pos_coords = (ori_coords, ori_labels, ori_bbox_targets, + ori_bbox_feats, 'rcnn') + losses.update(pos_coords=pos_coords) + + return losses + + def bbox_loss(self, x: Tuple[Tensor], + sampling_results: List[SamplingResult]) -> dict: + """Perform forward propagation and loss calculation of the bbox head on + the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + """ + rois = bbox2roi([res.priors for res in sampling_results]) + bbox_results = self._bbox_forward(x, rois) + + bbox_loss_and_target = self.bbox_head.loss_and_target( + cls_score=bbox_results['cls_score'], + bbox_pred=bbox_results['bbox_pred'], + rois=rois, + sampling_results=sampling_results, + rcnn_train_cfg=self.train_cfg) + + bbox_results.update(loss_bbox=bbox_loss_and_target['loss_bbox']) + # diff + bbox_results.update(bbox_targets=bbox_loss_and_target['bbox_targets']) + return bbox_results diff --git a/mmdetection/projects/CO-DETR/codetr/codetr.py b/mmdetection/projects/CO-DETR/codetr/codetr.py new file mode 100644 index 00000000..82826f64 --- /dev/null +++ b/mmdetection/projects/CO-DETR/codetr/codetr.py @@ -0,0 +1,320 @@ +import copy +from typing import Tuple, Union + +import torch +import torch.nn as nn +from torch import Tensor + +from mmdet.models.detectors.base import BaseDetector +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList, SampleList +from mmdet.utils import InstanceList, OptConfigType, OptMultiConfig + + +@MODELS.register_module() +class CoDETR(BaseDetector): + + def __init__( + self, + backbone, + neck=None, + query_head=None, # detr head + rpn_head=None, # two-stage rpn + roi_head=[None], # two-stage + bbox_head=[None], # one-stage + train_cfg=[None, None], + test_cfg=[None, None], + # Control whether to consider positive samples + # from the auxiliary head as additional positive queries. + with_pos_coord=True, + use_lsj=True, + eval_module='detr', + # Evaluate the Nth head. + eval_index=0, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super(CoDETR, self).__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + self.with_pos_coord = with_pos_coord + self.use_lsj = use_lsj + + assert eval_module in ['detr', 'one-stage', 'two-stage'] + self.eval_module = eval_module + + self.backbone = MODELS.build(backbone) + if neck is not None: + self.neck = MODELS.build(neck) + # Module index for evaluation + self.eval_index = eval_index + head_idx = 0 + if query_head is not None: + query_head.update(train_cfg=train_cfg[head_idx] if ( + train_cfg is not None and train_cfg[head_idx] is not None + ) else None) + query_head.update(test_cfg=test_cfg[head_idx]) + self.query_head = MODELS.build(query_head) + self.query_head.init_weights() + head_idx += 1 + + if rpn_head is not None: + rpn_train_cfg = train_cfg[head_idx].rpn if ( + train_cfg is not None + and train_cfg[head_idx] is not None) else None + rpn_head_ = rpn_head.copy() + rpn_head_.update( + train_cfg=rpn_train_cfg, test_cfg=test_cfg[head_idx].rpn) + self.rpn_head = MODELS.build(rpn_head_) + self.rpn_head.init_weights() + + self.roi_head = nn.ModuleList() + for i in range(len(roi_head)): + if roi_head[i]: + rcnn_train_cfg = train_cfg[i + head_idx].rcnn if ( + train_cfg + and train_cfg[i + head_idx] is not None) else None + roi_head[i].update(train_cfg=rcnn_train_cfg) + roi_head[i].update(test_cfg=test_cfg[i + head_idx].rcnn) + self.roi_head.append(MODELS.build(roi_head[i])) + self.roi_head[-1].init_weights() + + self.bbox_head = nn.ModuleList() + for i in range(len(bbox_head)): + if bbox_head[i]: + bbox_head[i].update( + train_cfg=train_cfg[i + head_idx + len(self.roi_head)] if ( + train_cfg and train_cfg[i + head_idx + + len(self.roi_head)] is not None + ) else None) + bbox_head[i].update(test_cfg=test_cfg[i + head_idx + + len(self.roi_head)]) + self.bbox_head.append(MODELS.build(bbox_head[i])) + self.bbox_head[-1].init_weights() + + self.head_idx = head_idx + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + @property + def with_rpn(self): + """bool: whether the detector has RPN""" + return hasattr(self, 'rpn_head') and self.rpn_head is not None + + @property + def with_query_head(self): + """bool: whether the detector has a RoI head""" + return hasattr(self, 'query_head') and self.query_head is not None + + @property + def with_roi_head(self): + """bool: whether the detector has a RoI head""" + return hasattr(self, 'roi_head') and self.roi_head is not None and len( + self.roi_head) > 0 + + @property + def with_shared_head(self): + """bool: whether the detector has a shared head in the RoI Head""" + return hasattr(self, 'roi_head') and self.roi_head[0].with_shared_head + + @property + def with_bbox(self): + """bool: whether the detector has a bbox head""" + return ((hasattr(self, 'roi_head') and self.roi_head is not None + and len(self.roi_head) > 0) + or (hasattr(self, 'bbox_head') and self.bbox_head is not None + and len(self.bbox_head) > 0)) + + def extract_feat(self, batch_inputs: Tensor) -> Tuple[Tensor]: + """Extract features. + + Args: + batch_inputs (Tensor): Image tensor, has shape (bs, dim, H, W). + + Returns: + tuple[Tensor]: Tuple of feature maps from neck. Each feature map + has shape (bs, dim, H, W). + """ + x = self.backbone(batch_inputs) + if self.with_neck: + x = self.neck(x) + return x + + def _forward(self, + batch_inputs: Tensor, + batch_data_samples: OptSampleList = None): + pass + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> Union[dict, list]: + batch_input_shape = batch_data_samples[0].batch_input_shape + if self.use_lsj: + for data_samples in batch_data_samples: + img_metas = data_samples.metainfo + input_img_h, input_img_w = batch_input_shape + img_metas['img_shape'] = [input_img_h, input_img_w] + + x = self.extract_feat(batch_inputs) + + losses = dict() + + def upd_loss(losses, idx, weight=1): + new_losses = dict() + for k, v in losses.items(): + new_k = '{}{}'.format(k, idx) + if isinstance(v, list) or isinstance(v, tuple): + new_losses[new_k] = [i * weight for i in v] + else: + new_losses[new_k] = v * weight + return new_losses + + # DETR encoder and decoder forward + if self.with_query_head: + bbox_losses, x = self.query_head.loss(x, batch_data_samples) + losses.update(bbox_losses) + + # RPN forward and loss + if self.with_rpn: + proposal_cfg = self.train_cfg[self.head_idx].get( + 'rpn_proposal', self.test_cfg[self.head_idx].rpn) + + rpn_data_samples = copy.deepcopy(batch_data_samples) + # set cat_id of gt_labels to 0 in RPN + for data_sample in rpn_data_samples: + data_sample.gt_instances.labels = \ + torch.zeros_like(data_sample.gt_instances.labels) + + rpn_losses, proposal_list = self.rpn_head.loss_and_predict( + x, rpn_data_samples, proposal_cfg=proposal_cfg) + + # avoid get same name with roi_head loss + keys = rpn_losses.keys() + for key in list(keys): + if 'loss' in key and 'rpn' not in key: + rpn_losses[f'rpn_{key}'] = rpn_losses.pop(key) + + losses.update(rpn_losses) + else: + assert batch_data_samples[0].get('proposals', None) is not None + # use pre-defined proposals in InstanceData for the second stage + # to extract ROI features. + proposal_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + + positive_coords = [] + for i in range(len(self.roi_head)): + roi_losses = self.roi_head[i].loss(x, proposal_list, + batch_data_samples) + if self.with_pos_coord: + positive_coords.append(roi_losses.pop('pos_coords')) + else: + if 'pos_coords' in roi_losses.keys(): + roi_losses.pop('pos_coords') + roi_losses = upd_loss(roi_losses, idx=i) + losses.update(roi_losses) + + for i in range(len(self.bbox_head)): + bbox_losses = self.bbox_head[i].loss(x, batch_data_samples) + if self.with_pos_coord: + pos_coords = bbox_losses.pop('pos_coords') + positive_coords.append(pos_coords) + else: + if 'pos_coords' in bbox_losses.keys(): + bbox_losses.pop('pos_coords') + bbox_losses = upd_loss(bbox_losses, idx=i + len(self.roi_head)) + losses.update(bbox_losses) + + if self.with_pos_coord and len(positive_coords) > 0: + for i in range(len(positive_coords)): + bbox_losses = self.query_head.loss_aux(x, positive_coords[i], + i, batch_data_samples) + bbox_losses = upd_loss(bbox_losses, idx=i) + losses.update(bbox_losses) + + return losses + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + batch_inputs (Tensor): Inputs, has shape (bs, dim, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to True. + + Returns: + list[:obj:`DetDataSample`]: Detection results of the input images. + Each DetDataSample usually contain 'pred_instances'. And the + `pred_instances` usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + assert self.eval_module in ['detr', 'one-stage', 'two-stage'] + + if self.use_lsj: + for data_samples in batch_data_samples: + img_metas = data_samples.metainfo + input_img_h, input_img_w = img_metas['batch_input_shape'] + img_metas['img_shape'] = [input_img_h, input_img_w] + + img_feats = self.extract_feat(batch_inputs) + if self.with_bbox and self.eval_module == 'one-stage': + results_list = self.predict_bbox_head( + img_feats, batch_data_samples, rescale=rescale) + elif self.with_roi_head and self.eval_module == 'two-stage': + results_list = self.predict_roi_head( + img_feats, batch_data_samples, rescale=rescale) + else: + results_list = self.predict_query_head( + img_feats, batch_data_samples, rescale=rescale) + + batch_data_samples = self.add_pred_to_datasample( + batch_data_samples, results_list) + return batch_data_samples + + def predict_query_head(self, + mlvl_feats: Tuple[Tensor], + batch_data_samples: SampleList, + rescale: bool = True) -> InstanceList: + return self.query_head.predict( + mlvl_feats, batch_data_samples=batch_data_samples, rescale=rescale) + + def predict_roi_head(self, + mlvl_feats: Tuple[Tensor], + batch_data_samples: SampleList, + rescale: bool = True) -> InstanceList: + assert self.with_bbox, 'Bbox head must be implemented.' + if self.with_query_head: + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + results = self.query_head.forward(mlvl_feats, batch_img_metas) + mlvl_feats = results[-1] + rpn_results_list = self.rpn_head.predict( + mlvl_feats, batch_data_samples, rescale=False) + return self.roi_head[self.eval_index].predict( + mlvl_feats, rpn_results_list, batch_data_samples, rescale=rescale) + + def predict_bbox_head(self, + mlvl_feats: Tuple[Tensor], + batch_data_samples: SampleList, + rescale: bool = True) -> InstanceList: + assert self.with_bbox, 'Bbox head must be implemented.' + if self.with_query_head: + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + results = self.query_head.forward(mlvl_feats, batch_img_metas) + mlvl_feats = results[-1] + return self.bbox_head[self.eval_index].predict( + mlvl_feats, batch_data_samples, rescale=rescale) diff --git a/mmdetection/projects/CO-DETR/codetr/transformer.py b/mmdetection/projects/CO-DETR/codetr/transformer.py new file mode 100644 index 00000000..009f94a8 --- /dev/null +++ b/mmdetection/projects/CO-DETR/codetr/transformer.py @@ -0,0 +1,1376 @@ +import math +import warnings + +import torch +import torch.nn as nn +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import (BaseTransformerLayer, + TransformerLayerSequence, + build_transformer_layer_sequence) +from mmcv.ops import MultiScaleDeformableAttention +from mmengine.model import BaseModule +from mmengine.model.weight_init import xavier_init +from torch.nn.init import normal_ + +from mmdet.models.layers.transformer import inverse_sigmoid +from mmdet.registry import MODELS + +try: + from fairscale.nn.checkpoint import checkpoint_wrapper +except Exception: + checkpoint_wrapper = None + +# In order to save the cost and effort of reproduction, +# I did not refactor it into the style of mmdet 3.x DETR. + + +class Transformer(BaseModule): + """Implements the DETR transformer. + + Following the official DETR implementation, this module copy-paste + from torch.nn.Transformer with modifications: + + * positional encodings are passed in MultiheadAttention + * extra LN at the end of encoder is removed + * decoder returns a stack of activations from all decoding layers + + See `paper: End-to-End Object Detection with Transformers + `_ for details. + + Args: + encoder (`mmcv.ConfigDict` | Dict): Config of + TransformerEncoder. Defaults to None. + decoder ((`mmcv.ConfigDict` | Dict)): Config of + TransformerDecoder. Defaults to None + init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization. + Defaults to None. + """ + + def __init__(self, encoder=None, decoder=None, init_cfg=None): + super(Transformer, self).__init__(init_cfg=init_cfg) + self.encoder = build_transformer_layer_sequence(encoder) + self.decoder = build_transformer_layer_sequence(decoder) + self.embed_dims = self.encoder.embed_dims + + def init_weights(self): + # follow the official DETR to init parameters + for m in self.modules(): + if hasattr(m, 'weight') and m.weight.dim() > 1: + xavier_init(m, distribution='uniform') + self._is_init = True + + def forward(self, x, mask, query_embed, pos_embed): + """Forward function for `Transformer`. + + Args: + x (Tensor): Input query with shape [bs, c, h, w] where + c = embed_dims. + mask (Tensor): The key_padding_mask used for encoder and decoder, + with shape [bs, h, w]. + query_embed (Tensor): The query embedding for decoder, with shape + [num_query, c]. + pos_embed (Tensor): The positional encoding for encoder and + decoder, with the same shape as `x`. + + Returns: + tuple[Tensor]: results of decoder containing the following tensor. + + - out_dec: Output from decoder. If return_intermediate_dec \ + is True output has shape [num_dec_layers, bs, + num_query, embed_dims], else has shape [1, bs, \ + num_query, embed_dims]. + - memory: Output results from encoder, with shape \ + [bs, embed_dims, h, w]. + """ + bs, c, h, w = x.shape + # use `view` instead of `flatten` for dynamically exporting to ONNX + x = x.view(bs, c, -1).permute(2, 0, 1) # [bs, c, h, w] -> [h*w, bs, c] + pos_embed = pos_embed.view(bs, c, -1).permute(2, 0, 1) + query_embed = query_embed.unsqueeze(1).repeat( + 1, bs, 1) # [num_query, dim] -> [num_query, bs, dim] + mask = mask.view(bs, -1) # [bs, h, w] -> [bs, h*w] + memory = self.encoder( + query=x, + key=None, + value=None, + query_pos=pos_embed, + query_key_padding_mask=mask) + target = torch.zeros_like(query_embed) + # out_dec: [num_layers, num_query, bs, dim] + out_dec = self.decoder( + query=target, + key=memory, + value=memory, + key_pos=pos_embed, + query_pos=query_embed, + key_padding_mask=mask) + out_dec = out_dec.transpose(1, 2) + memory = memory.permute(1, 2, 0).reshape(bs, c, h, w) + return out_dec, memory + + +@MODELS.register_module(force=True) +class DeformableDetrTransformerDecoder(TransformerLayerSequence): + """Implements the decoder in DETR transformer. + + Args: + return_intermediate (bool): Whether to return intermediate outputs. + coder_norm_cfg (dict): Config of last normalization layer. Default: + `LN`. + """ + + def __init__(self, *args, return_intermediate=False, **kwargs): + + super(DeformableDetrTransformerDecoder, self).__init__(*args, **kwargs) + self.return_intermediate = return_intermediate + + def forward(self, + query, + *args, + reference_points=None, + valid_ratios=None, + reg_branches=None, + **kwargs): + """Forward function for `TransformerDecoder`. + + Args: + query (Tensor): Input query with shape + `(num_query, bs, embed_dims)`. + reference_points (Tensor): The reference + points of offset. has shape + (bs, num_query, 4) when as_two_stage, + otherwise has shape ((bs, num_query, 2). + valid_ratios (Tensor): The radios of valid + points on the feature map, has shape + (bs, num_levels, 2) + reg_branch: (obj:`nn.ModuleList`): Used for + refining the regression results. Only would + be passed when with_box_refine is True, + otherwise would be passed a `None`. + + Returns: + Tensor: Results with shape [1, num_query, bs, embed_dims] when + return_intermediate is `False`, otherwise it has shape + [num_layers, num_query, bs, embed_dims]. + """ + output = query + intermediate = [] + intermediate_reference_points = [] + for lid, layer in enumerate(self.layers): + if reference_points.shape[-1] == 4: + reference_points_input = reference_points[:, :, None] * \ + torch.cat([valid_ratios, valid_ratios], -1)[:, None] + else: + assert reference_points.shape[-1] == 2 + reference_points_input = reference_points[:, :, None] * \ + valid_ratios[:, None] + output = layer( + output, + *args, + reference_points=reference_points_input, + **kwargs) + output = output.permute(1, 0, 2) + + if reg_branches is not None: + tmp = reg_branches[lid](output) + if reference_points.shape[-1] == 4: + new_reference_points = tmp + inverse_sigmoid( + reference_points) + new_reference_points = new_reference_points.sigmoid() + else: + assert reference_points.shape[-1] == 2 + new_reference_points = tmp + new_reference_points[..., :2] = tmp[ + ..., :2] + inverse_sigmoid(reference_points) + new_reference_points = new_reference_points.sigmoid() + reference_points = new_reference_points.detach() + + output = output.permute(1, 0, 2) + if self.return_intermediate: + intermediate.append(output) + intermediate_reference_points.append(reference_points) + + if self.return_intermediate: + return torch.stack(intermediate), torch.stack( + intermediate_reference_points) + + return output, reference_points + + +@MODELS.register_module(force=True) +class DeformableDetrTransformer(Transformer): + """Implements the DeformableDETR transformer. + + Args: + as_two_stage (bool): Generate query from encoder features. + Default: False. + num_feature_levels (int): Number of feature maps from FPN: + Default: 4. + two_stage_num_proposals (int): Number of proposals when set + `as_two_stage` as True. Default: 300. + """ + + def __init__(self, + as_two_stage=False, + num_feature_levels=4, + two_stage_num_proposals=300, + **kwargs): + super(DeformableDetrTransformer, self).__init__(**kwargs) + self.as_two_stage = as_two_stage + self.num_feature_levels = num_feature_levels + self.two_stage_num_proposals = two_stage_num_proposals + self.embed_dims = self.encoder.embed_dims + self.init_layers() + + def init_layers(self): + """Initialize layers of the DeformableDetrTransformer.""" + self.level_embeds = nn.Parameter( + torch.Tensor(self.num_feature_levels, self.embed_dims)) + + if self.as_two_stage: + self.enc_output = nn.Linear(self.embed_dims, self.embed_dims) + self.enc_output_norm = nn.LayerNorm(self.embed_dims) + self.pos_trans = nn.Linear(self.embed_dims * 2, + self.embed_dims * 2) + self.pos_trans_norm = nn.LayerNorm(self.embed_dims * 2) + else: + self.reference_points = nn.Linear(self.embed_dims, 2) + + def init_weights(self): + """Initialize the transformer weights.""" + for p in self.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + for m in self.modules(): + if isinstance(m, MultiScaleDeformableAttention): + m.init_weights() + if not self.as_two_stage: + xavier_init(self.reference_points, distribution='uniform', bias=0.) + normal_(self.level_embeds) + + def gen_encoder_output_proposals(self, memory, memory_padding_mask, + spatial_shapes): + """Generate proposals from encoded memory. + + Args: + memory (Tensor) : The output of encoder, + has shape (bs, num_key, embed_dim). num_key is + equal the number of points on feature map from + all level. + memory_padding_mask (Tensor): Padding mask for memory. + has shape (bs, num_key). + spatial_shapes (Tensor): The shape of all feature maps. + has shape (num_level, 2). + + Returns: + tuple: A tuple of feature map and bbox prediction. + + - output_memory (Tensor): The input of decoder, \ + has shape (bs, num_key, embed_dim). num_key is \ + equal the number of points on feature map from \ + all levels. + - output_proposals (Tensor): The normalized proposal \ + after a inverse sigmoid, has shape \ + (bs, num_keys, 4). + """ + + N, S, C = memory.shape + proposals = [] + _cur = 0 + for lvl, (H, W) in enumerate(spatial_shapes): + mask_flatten_ = memory_padding_mask[:, _cur:(_cur + H * W)].view( + N, H, W, 1) + valid_H = torch.sum(~mask_flatten_[:, :, 0, 0], 1) + valid_W = torch.sum(~mask_flatten_[:, 0, :, 0], 1) + + grid_y, grid_x = torch.meshgrid( + torch.linspace( + 0, H - 1, H, dtype=torch.float32, device=memory.device), + torch.linspace( + 0, W - 1, W, dtype=torch.float32, device=memory.device)) + grid = torch.cat([grid_x.unsqueeze(-1), grid_y.unsqueeze(-1)], -1) + + scale = torch.cat([valid_W.unsqueeze(-1), + valid_H.unsqueeze(-1)], 1).view(N, 1, 1, 2) + grid = (grid.unsqueeze(0).expand(N, -1, -1, -1) + 0.5) / scale + wh = torch.ones_like(grid) * 0.05 * (2.0**lvl) + proposal = torch.cat((grid, wh), -1).view(N, -1, 4) + proposals.append(proposal) + _cur += (H * W) + output_proposals = torch.cat(proposals, 1) + output_proposals_valid = ((output_proposals > 0.01) & + (output_proposals < 0.99)).all( + -1, keepdim=True) + output_proposals = torch.log(output_proposals / (1 - output_proposals)) + output_proposals = output_proposals.masked_fill( + memory_padding_mask.unsqueeze(-1), float('inf')) + output_proposals = output_proposals.masked_fill( + ~output_proposals_valid, float('inf')) + + output_memory = memory + output_memory = output_memory.masked_fill( + memory_padding_mask.unsqueeze(-1), float(0)) + output_memory = output_memory.masked_fill(~output_proposals_valid, + float(0)) + output_memory = self.enc_output_norm(self.enc_output(output_memory)) + return output_memory, output_proposals + + @staticmethod + def get_reference_points(spatial_shapes, valid_ratios, device): + """Get the reference points used in decoder. + + Args: + spatial_shapes (Tensor): The shape of all + feature maps, has shape (num_level, 2). + valid_ratios (Tensor): The radios of valid + points on the feature map, has shape + (bs, num_levels, 2) + device (obj:`device`): The device where + reference_points should be. + + Returns: + Tensor: reference points used in decoder, has \ + shape (bs, num_keys, num_levels, 2). + """ + reference_points_list = [] + for lvl, (H, W) in enumerate(spatial_shapes): + ref_y, ref_x = torch.meshgrid( + torch.linspace( + 0.5, H - 0.5, H, dtype=torch.float32, device=device), + torch.linspace( + 0.5, W - 0.5, W, dtype=torch.float32, device=device)) + ref_y = ref_y.reshape(-1)[None] / ( + valid_ratios[:, None, lvl, 1] * H) + ref_x = ref_x.reshape(-1)[None] / ( + valid_ratios[:, None, lvl, 0] * W) + ref = torch.stack((ref_x, ref_y), -1) + reference_points_list.append(ref) + reference_points = torch.cat(reference_points_list, 1) + reference_points = reference_points[:, :, None] * valid_ratios[:, None] + return reference_points + + def get_valid_ratio(self, mask): + """Get the valid radios of feature maps of all level.""" + _, H, W = mask.shape + valid_H = torch.sum(~mask[:, :, 0], 1) + valid_W = torch.sum(~mask[:, 0, :], 1) + valid_ratio_h = valid_H.float() / H + valid_ratio_w = valid_W.float() / W + valid_ratio = torch.stack([valid_ratio_w, valid_ratio_h], -1) + return valid_ratio + + def get_proposal_pos_embed(self, + proposals, + num_pos_feats=128, + temperature=10000): + """Get the position embedding of proposal.""" + scale = 2 * math.pi + dim_t = torch.arange( + num_pos_feats, dtype=torch.float32, device=proposals.device) + dim_t = temperature**(2 * (dim_t // 2) / num_pos_feats) + # N, L, 4 + proposals = proposals.sigmoid() * scale + # N, L, 4, 128 + pos = proposals[:, :, :, None] / dim_t + # N, L, 4, 64, 2 + pos = torch.stack((pos[:, :, :, 0::2].sin(), pos[:, :, :, 1::2].cos()), + dim=4).flatten(2) + return pos + + def forward(self, + mlvl_feats, + mlvl_masks, + query_embed, + mlvl_pos_embeds, + reg_branches=None, + cls_branches=None, + **kwargs): + """Forward function for `Transformer`. + + Args: + mlvl_feats (list(Tensor)): Input queries from + different level. Each element has shape + [bs, embed_dims, h, w]. + mlvl_masks (list(Tensor)): The key_padding_mask from + different level used for encoder and decoder, + each element has shape [bs, h, w]. + query_embed (Tensor): The query embedding for decoder, + with shape [num_query, c]. + mlvl_pos_embeds (list(Tensor)): The positional encoding + of feats from different level, has the shape + [bs, embed_dims, h, w]. + reg_branches (obj:`nn.ModuleList`): Regression heads for + feature maps from each decoder layer. Only would + be passed when + `with_box_refine` is True. Default to None. + cls_branches (obj:`nn.ModuleList`): Classification heads + for feature maps from each decoder layer. Only would + be passed when `as_two_stage` + is True. Default to None. + + + Returns: + tuple[Tensor]: results of decoder containing the following tensor. + + - inter_states: Outputs from decoder. If + return_intermediate_dec is True output has shape \ + (num_dec_layers, bs, num_query, embed_dims), else has \ + shape (1, bs, num_query, embed_dims). + - init_reference_out: The initial value of reference \ + points, has shape (bs, num_queries, 4). + - inter_references_out: The internal value of reference \ + points in decoder, has shape \ + (num_dec_layers, bs,num_query, embed_dims) + - enc_outputs_class: The classification score of \ + proposals generated from \ + encoder's feature maps, has shape \ + (batch, h*w, num_classes). \ + Only would be returned when `as_two_stage` is True, \ + otherwise None. + - enc_outputs_coord_unact: The regression results \ + generated from encoder's feature maps., has shape \ + (batch, h*w, 4). Only would \ + be returned when `as_two_stage` is True, \ + otherwise None. + """ + assert self.as_two_stage or query_embed is not None + + feat_flatten = [] + mask_flatten = [] + lvl_pos_embed_flatten = [] + spatial_shapes = [] + for lvl, (feat, mask, pos_embed) in enumerate( + zip(mlvl_feats, mlvl_masks, mlvl_pos_embeds)): + bs, c, h, w = feat.shape + spatial_shape = (h, w) + spatial_shapes.append(spatial_shape) + feat = feat.flatten(2).transpose(1, 2) + mask = mask.flatten(1) + pos_embed = pos_embed.flatten(2).transpose(1, 2) + lvl_pos_embed = pos_embed + self.level_embeds[lvl].view(1, 1, -1) + lvl_pos_embed_flatten.append(lvl_pos_embed) + feat_flatten.append(feat) + mask_flatten.append(mask) + feat_flatten = torch.cat(feat_flatten, 1) + mask_flatten = torch.cat(mask_flatten, 1) + lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1) + spatial_shapes = torch.as_tensor( + spatial_shapes, dtype=torch.long, device=feat_flatten.device) + level_start_index = torch.cat((spatial_shapes.new_zeros( + (1, )), spatial_shapes.prod(1).cumsum(0)[:-1])) + valid_ratios = torch.stack( + [self.get_valid_ratio(m) for m in mlvl_masks], 1) + + reference_points = \ + self.get_reference_points(spatial_shapes, + valid_ratios, + device=feat.device) + + feat_flatten = feat_flatten.permute(1, 0, 2) # (H*W, bs, embed_dims) + lvl_pos_embed_flatten = lvl_pos_embed_flatten.permute( + 1, 0, 2) # (H*W, bs, embed_dims) + memory = self.encoder( + query=feat_flatten, + key=None, + value=None, + query_pos=lvl_pos_embed_flatten, + query_key_padding_mask=mask_flatten, + spatial_shapes=spatial_shapes, + reference_points=reference_points, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + **kwargs) + + memory = memory.permute(1, 0, 2) + bs, _, c = memory.shape + if self.as_two_stage: + output_memory, output_proposals = \ + self.gen_encoder_output_proposals( + memory, mask_flatten, spatial_shapes) + enc_outputs_class = cls_branches[self.decoder.num_layers]( + output_memory) + enc_outputs_coord_unact = \ + reg_branches[ + self.decoder.num_layers](output_memory) + output_proposals + + topk = self.two_stage_num_proposals + # We only use the first channel in enc_outputs_class as foreground, + # the other (num_classes - 1) channels are actually not used. + # Its targets are set to be 0s, which indicates the first + # class (foreground) because we use [0, num_classes - 1] to + # indicate class labels, background class is indicated by + # num_classes (similar convention in RPN). + # See https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/dense_heads/deformable_detr_head.py#L241 # noqa + # This follows the official implementation of Deformable DETR. + topk_proposals = torch.topk( + enc_outputs_class[..., 0], topk, dim=1)[1] + topk_coords_unact = torch.gather( + enc_outputs_coord_unact, 1, + topk_proposals.unsqueeze(-1).repeat(1, 1, 4)) + topk_coords_unact = topk_coords_unact.detach() + reference_points = topk_coords_unact.sigmoid() + init_reference_out = reference_points + pos_trans_out = self.pos_trans_norm( + self.pos_trans(self.get_proposal_pos_embed(topk_coords_unact))) + query_pos, query = torch.split(pos_trans_out, c, dim=2) + else: + query_pos, query = torch.split(query_embed, c, dim=1) + query_pos = query_pos.unsqueeze(0).expand(bs, -1, -1) + query = query.unsqueeze(0).expand(bs, -1, -1) + reference_points = self.reference_points(query_pos).sigmoid() + init_reference_out = reference_points + + # decoder + query = query.permute(1, 0, 2) + memory = memory.permute(1, 0, 2) + query_pos = query_pos.permute(1, 0, 2) + inter_states, inter_references = self.decoder( + query=query, + key=None, + value=memory, + query_pos=query_pos, + key_padding_mask=mask_flatten, + reference_points=reference_points, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reg_branches=reg_branches, + **kwargs) + + inter_references_out = inter_references + if self.as_two_stage: + return inter_states, init_reference_out,\ + inter_references_out, enc_outputs_class,\ + enc_outputs_coord_unact + return inter_states, init_reference_out, \ + inter_references_out, None, None + + +@MODELS.register_module() +class CoDeformableDetrTransformerDecoder(TransformerLayerSequence): + """Implements the decoder in DETR transformer. + + Args: + return_intermediate (bool): Whether to return intermediate outputs. + coder_norm_cfg (dict): Config of last normalization layer. Default: + `LN`. + """ + + def __init__(self, + *args, + return_intermediate=False, + look_forward_twice=False, + **kwargs): + + super(CoDeformableDetrTransformerDecoder, + self).__init__(*args, **kwargs) + self.return_intermediate = return_intermediate + self.look_forward_twice = look_forward_twice + + def forward(self, + query, + *args, + reference_points=None, + valid_ratios=None, + reg_branches=None, + **kwargs): + """Forward function for `TransformerDecoder`. + + Args: + query (Tensor): Input query with shape + `(num_query, bs, embed_dims)`. + reference_points (Tensor): The reference + points of offset. has shape + (bs, num_query, 4) when as_two_stage, + otherwise has shape ((bs, num_query, 2). + valid_ratios (Tensor): The radios of valid + points on the feature map, has shape + (bs, num_levels, 2) + reg_branch: (obj:`nn.ModuleList`): Used for + refining the regression results. Only would + be passed when with_box_refine is True, + otherwise would be passed a `None`. + + Returns: + Tensor: Results with shape [1, num_query, bs, embed_dims] when + return_intermediate is `False`, otherwise it has shape + [num_layers, num_query, bs, embed_dims]. + """ + output = query + intermediate = [] + intermediate_reference_points = [] + for lid, layer in enumerate(self.layers): + if reference_points.shape[-1] == 4: + reference_points_input = reference_points[:, :, None] * \ + torch.cat([valid_ratios, valid_ratios], -1)[:, None] + else: + assert reference_points.shape[-1] == 2 + reference_points_input = reference_points[:, :, None] * \ + valid_ratios[:, None] + output = layer( + output, + *args, + reference_points=reference_points_input, + **kwargs) + output = output.permute(1, 0, 2) + + if reg_branches is not None: + tmp = reg_branches[lid](output) + if reference_points.shape[-1] == 4: + new_reference_points = tmp + inverse_sigmoid( + reference_points) + new_reference_points = new_reference_points.sigmoid() + else: + assert reference_points.shape[-1] == 2 + new_reference_points = tmp + new_reference_points[..., :2] = tmp[ + ..., :2] + inverse_sigmoid(reference_points) + new_reference_points = new_reference_points.sigmoid() + reference_points = new_reference_points.detach() + + output = output.permute(1, 0, 2) + if self.return_intermediate: + intermediate.append(output) + intermediate_reference_points.append( + new_reference_points if self. + look_forward_twice else reference_points) + if self.return_intermediate: + return torch.stack(intermediate), torch.stack( + intermediate_reference_points) + + return output, reference_points + + +@MODELS.register_module() +class CoDeformableDetrTransformer(DeformableDetrTransformer): + + def __init__(self, + mixed_selection=True, + with_pos_coord=True, + with_coord_feat=True, + num_co_heads=1, + **kwargs): + self.mixed_selection = mixed_selection + self.with_pos_coord = with_pos_coord + self.with_coord_feat = with_coord_feat + self.num_co_heads = num_co_heads + super(CoDeformableDetrTransformer, self).__init__(**kwargs) + self._init_layers() + + def _init_layers(self): + """Initialize layers of the CoDeformableDetrTransformer.""" + if self.with_pos_coord: + if self.num_co_heads > 0: + # bug: this code should be 'self.head_pos_embed = + # nn.Embedding(self.num_co_heads, self.embed_dims)', + # we keep this bug for reproducing our results with ResNet-50. + # You can fix this bug when reproducing results with + # swin transformer. + self.head_pos_embed = nn.Embedding(self.num_co_heads, 1, 1, + self.embed_dims) + self.aux_pos_trans = nn.ModuleList() + self.aux_pos_trans_norm = nn.ModuleList() + self.pos_feats_trans = nn.ModuleList() + self.pos_feats_norm = nn.ModuleList() + for i in range(self.num_co_heads): + self.aux_pos_trans.append( + nn.Linear(self.embed_dims * 2, self.embed_dims * 2)) + self.aux_pos_trans_norm.append( + nn.LayerNorm(self.embed_dims * 2)) + if self.with_coord_feat: + self.pos_feats_trans.append( + nn.Linear(self.embed_dims, self.embed_dims)) + self.pos_feats_norm.append( + nn.LayerNorm(self.embed_dims)) + + def get_proposal_pos_embed(self, + proposals, + num_pos_feats=128, + temperature=10000): + """Get the position embedding of proposal.""" + num_pos_feats = self.embed_dims // 2 + scale = 2 * math.pi + dim_t = torch.arange( + num_pos_feats, dtype=torch.float32, device=proposals.device) + dim_t = temperature**(2 * (dim_t // 2) / num_pos_feats) + # N, L, 4 + proposals = proposals.sigmoid() * scale + # N, L, 4, 128 + pos = proposals[:, :, :, None] / dim_t + # N, L, 4, 64, 2 + pos = torch.stack((pos[:, :, :, 0::2].sin(), pos[:, :, :, 1::2].cos()), + dim=4).flatten(2) + return pos + + def forward(self, + mlvl_feats, + mlvl_masks, + query_embed, + mlvl_pos_embeds, + reg_branches=None, + cls_branches=None, + return_encoder_output=False, + attn_masks=None, + **kwargs): + """Forward function for `Transformer`. + + Args: + mlvl_feats (list(Tensor)): Input queries from + different level. Each element has shape + [bs, embed_dims, h, w]. + mlvl_masks (list(Tensor)): The key_padding_mask from + different level used for encoder and decoder, + each element has shape [bs, h, w]. + query_embed (Tensor): The query embedding for decoder, + with shape [num_query, c]. + mlvl_pos_embeds (list(Tensor)): The positional encoding + of feats from different level, has the shape + [bs, embed_dims, h, w]. + reg_branches (obj:`nn.ModuleList`): Regression heads for + feature maps from each decoder layer. Only would + be passed when + `with_box_refine` is True. Default to None. + cls_branches (obj:`nn.ModuleList`): Classification heads + for feature maps from each decoder layer. Only would + be passed when `as_two_stage` + is True. Default to None. + + + Returns: + tuple[Tensor]: results of decoder containing the following tensor. + + - inter_states: Outputs from decoder. If + return_intermediate_dec is True output has shape \ + (num_dec_layers, bs, num_query, embed_dims), else has \ + shape (1, bs, num_query, embed_dims). + - init_reference_out: The initial value of reference \ + points, has shape (bs, num_queries, 4). + - inter_references_out: The internal value of reference \ + points in decoder, has shape \ + (num_dec_layers, bs,num_query, embed_dims) + - enc_outputs_class: The classification score of \ + proposals generated from \ + encoder's feature maps, has shape \ + (batch, h*w, num_classes). \ + Only would be returned when `as_two_stage` is True, \ + otherwise None. + - enc_outputs_coord_unact: The regression results \ + generated from encoder's feature maps., has shape \ + (batch, h*w, 4). Only would \ + be returned when `as_two_stage` is True, \ + otherwise None. + """ + assert self.as_two_stage or query_embed is not None + + feat_flatten = [] + mask_flatten = [] + lvl_pos_embed_flatten = [] + spatial_shapes = [] + for lvl, (feat, mask, pos_embed) in enumerate( + zip(mlvl_feats, mlvl_masks, mlvl_pos_embeds)): + bs, c, h, w = feat.shape + spatial_shape = (h, w) + spatial_shapes.append(spatial_shape) + feat = feat.flatten(2).transpose(1, 2) + mask = mask.flatten(1) + pos_embed = pos_embed.flatten(2).transpose(1, 2) + lvl_pos_embed = pos_embed + self.level_embeds[lvl].view(1, 1, -1) + lvl_pos_embed_flatten.append(lvl_pos_embed) + feat_flatten.append(feat) + mask_flatten.append(mask) + feat_flatten = torch.cat(feat_flatten, 1) + mask_flatten = torch.cat(mask_flatten, 1) + lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1) + spatial_shapes = torch.as_tensor( + spatial_shapes, dtype=torch.long, device=feat_flatten.device) + level_start_index = torch.cat((spatial_shapes.new_zeros( + (1, )), spatial_shapes.prod(1).cumsum(0)[:-1])) + valid_ratios = torch.stack( + [self.get_valid_ratio(m) for m in mlvl_masks], 1) + + reference_points = \ + self.get_reference_points(spatial_shapes, + valid_ratios, + device=feat.device) + + feat_flatten = feat_flatten.permute(1, 0, 2) # (H*W, bs, embed_dims) + lvl_pos_embed_flatten = lvl_pos_embed_flatten.permute( + 1, 0, 2) # (H*W, bs, embed_dims) + memory = self.encoder( + query=feat_flatten, + key=None, + value=None, + query_pos=lvl_pos_embed_flatten, + query_key_padding_mask=mask_flatten, + spatial_shapes=spatial_shapes, + reference_points=reference_points, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + **kwargs) + + memory = memory.permute(1, 0, 2) + bs, _, c = memory.shape + if self.as_two_stage: + output_memory, output_proposals = \ + self.gen_encoder_output_proposals( + memory, mask_flatten, spatial_shapes) + enc_outputs_class = cls_branches[self.decoder.num_layers]( + output_memory) + enc_outputs_coord_unact = \ + reg_branches[ + self.decoder.num_layers](output_memory) + output_proposals + + topk = self.two_stage_num_proposals + topk = query_embed.shape[0] + topk_proposals = torch.topk( + enc_outputs_class[..., 0], topk, dim=1)[1] + topk_coords_unact = torch.gather( + enc_outputs_coord_unact, 1, + topk_proposals.unsqueeze(-1).repeat(1, 1, 4)) + topk_coords_unact = topk_coords_unact.detach() + reference_points = topk_coords_unact.sigmoid() + init_reference_out = reference_points + pos_trans_out = self.pos_trans_norm( + self.pos_trans(self.get_proposal_pos_embed(topk_coords_unact))) + + if not self.mixed_selection: + query_pos, query = torch.split(pos_trans_out, c, dim=2) + else: + # query_embed here is the content embed for deformable DETR + query = query_embed.unsqueeze(0).expand(bs, -1, -1) + query_pos, _ = torch.split(pos_trans_out, c, dim=2) + else: + query_pos, query = torch.split(query_embed, c, dim=1) + query_pos = query_pos.unsqueeze(0).expand(bs, -1, -1) + query = query.unsqueeze(0).expand(bs, -1, -1) + reference_points = self.reference_points(query_pos).sigmoid() + init_reference_out = reference_points + + # decoder + query = query.permute(1, 0, 2) + memory = memory.permute(1, 0, 2) + query_pos = query_pos.permute(1, 0, 2) + inter_states, inter_references = self.decoder( + query=query, + key=None, + value=memory, + query_pos=query_pos, + key_padding_mask=mask_flatten, + reference_points=reference_points, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reg_branches=reg_branches, + attn_masks=attn_masks, + **kwargs) + + inter_references_out = inter_references + if self.as_two_stage: + if return_encoder_output: + return inter_states, init_reference_out,\ + inter_references_out, enc_outputs_class,\ + enc_outputs_coord_unact, memory + return inter_states, init_reference_out,\ + inter_references_out, enc_outputs_class,\ + enc_outputs_coord_unact + if return_encoder_output: + return inter_states, init_reference_out, \ + inter_references_out, None, None, memory + return inter_states, init_reference_out, \ + inter_references_out, None, None + + def forward_aux(self, + mlvl_feats, + mlvl_masks, + query_embed, + mlvl_pos_embeds, + pos_anchors, + pos_feats=None, + reg_branches=None, + cls_branches=None, + return_encoder_output=False, + attn_masks=None, + head_idx=0, + **kwargs): + feat_flatten = [] + mask_flatten = [] + spatial_shapes = [] + for lvl, (feat, mask, pos_embed) in enumerate( + zip(mlvl_feats, mlvl_masks, mlvl_pos_embeds)): + bs, c, h, w = feat.shape + spatial_shape = (h, w) + spatial_shapes.append(spatial_shape) + feat = feat.flatten(2).transpose(1, 2) + mask = mask.flatten(1) + feat_flatten.append(feat) + mask_flatten.append(mask) + feat_flatten = torch.cat(feat_flatten, 1) + mask_flatten = torch.cat(mask_flatten, 1) + spatial_shapes = torch.as_tensor( + spatial_shapes, dtype=torch.long, device=feat_flatten.device) + level_start_index = torch.cat((spatial_shapes.new_zeros( + (1, )), spatial_shapes.prod(1).cumsum(0)[:-1])) + valid_ratios = torch.stack( + [self.get_valid_ratio(m) for m in mlvl_masks], 1) + + feat_flatten = feat_flatten.permute(1, 0, 2) # (H*W, bs, embed_dims) + + memory = feat_flatten + memory = memory.permute(1, 0, 2) + bs, _, c = memory.shape + + topk_coords_unact = inverse_sigmoid(pos_anchors) + reference_points = pos_anchors + init_reference_out = reference_points + if self.num_co_heads > 0: + pos_trans_out = self.aux_pos_trans_norm[head_idx]( + self.aux_pos_trans[head_idx]( + self.get_proposal_pos_embed(topk_coords_unact))) + query_pos, query = torch.split(pos_trans_out, c, dim=2) + if self.with_coord_feat: + query = query + self.pos_feats_norm[head_idx]( + self.pos_feats_trans[head_idx](pos_feats)) + query_pos = query_pos + self.head_pos_embed.weight[head_idx] + + # decoder + query = query.permute(1, 0, 2) + memory = memory.permute(1, 0, 2) + query_pos = query_pos.permute(1, 0, 2) + inter_states, inter_references = self.decoder( + query=query, + key=None, + value=memory, + query_pos=query_pos, + key_padding_mask=mask_flatten, + reference_points=reference_points, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reg_branches=reg_branches, + attn_masks=attn_masks, + **kwargs) + + inter_references_out = inter_references + return inter_states, init_reference_out, \ + inter_references_out + + +def build_MLP(input_dim, hidden_dim, output_dim, num_layers): + assert num_layers > 1, \ + f'num_layers should be greater than 1 but got {num_layers}' + h = [hidden_dim] * (num_layers - 1) + layers = list() + for n, k in zip([input_dim] + h[:-1], h): + layers.extend((nn.Linear(n, k), nn.ReLU())) + # Note that the relu func of MLP in original DETR repo is set + # 'inplace=False', however the ReLU cfg of FFN in mmdet is set + # 'inplace=True' by default. + layers.append(nn.Linear(hidden_dim, output_dim)) + return nn.Sequential(*layers) + + +@MODELS.register_module() +class DinoTransformerDecoder(DeformableDetrTransformerDecoder): + + def __init__(self, *args, **kwargs): + super(DinoTransformerDecoder, self).__init__(*args, **kwargs) + self._init_layers() + + def _init_layers(self): + self.ref_point_head = build_MLP(self.embed_dims * 2, self.embed_dims, + self.embed_dims, 2) + self.norm = nn.LayerNorm(self.embed_dims) + + @staticmethod + def gen_sineembed_for_position(pos_tensor, pos_feat): + # n_query, bs, _ = pos_tensor.size() + # sineembed_tensor = torch.zeros(n_query, bs, 256) + scale = 2 * math.pi + dim_t = torch.arange( + pos_feat, dtype=torch.float32, device=pos_tensor.device) + dim_t = 10000**(2 * (dim_t // 2) / pos_feat) + x_embed = pos_tensor[:, :, 0] * scale + y_embed = pos_tensor[:, :, 1] * scale + pos_x = x_embed[:, :, None] / dim_t + pos_y = y_embed[:, :, None] / dim_t + pos_x = torch.stack((pos_x[:, :, 0::2].sin(), pos_x[:, :, 1::2].cos()), + dim=3).flatten(2) + pos_y = torch.stack((pos_y[:, :, 0::2].sin(), pos_y[:, :, 1::2].cos()), + dim=3).flatten(2) + if pos_tensor.size(-1) == 2: + pos = torch.cat((pos_y, pos_x), dim=2) + elif pos_tensor.size(-1) == 4: + w_embed = pos_tensor[:, :, 2] * scale + pos_w = w_embed[:, :, None] / dim_t + pos_w = torch.stack( + (pos_w[:, :, 0::2].sin(), pos_w[:, :, 1::2].cos()), + dim=3).flatten(2) + + h_embed = pos_tensor[:, :, 3] * scale + pos_h = h_embed[:, :, None] / dim_t + pos_h = torch.stack( + (pos_h[:, :, 0::2].sin(), pos_h[:, :, 1::2].cos()), + dim=3).flatten(2) + + pos = torch.cat((pos_y, pos_x, pos_w, pos_h), dim=2) + else: + raise ValueError('Unknown pos_tensor shape(-1):{}'.format( + pos_tensor.size(-1))) + return pos + + def forward(self, + query, + *args, + reference_points=None, + valid_ratios=None, + reg_branches=None, + **kwargs): + output = query + intermediate = [] + intermediate_reference_points = [reference_points] + for lid, layer in enumerate(self.layers): + if reference_points.shape[-1] == 4: + reference_points_input = \ + reference_points[:, :, None] * torch.cat( + [valid_ratios, valid_ratios], -1)[:, None] + else: + assert reference_points.shape[-1] == 2 + reference_points_input = \ + reference_points[:, :, None] * valid_ratios[:, None] + + query_sine_embed = self.gen_sineembed_for_position( + reference_points_input[:, :, 0, :], self.embed_dims // 2) + query_pos = self.ref_point_head(query_sine_embed) + + query_pos = query_pos.permute(1, 0, 2) + output = layer( + output, + *args, + query_pos=query_pos, + reference_points=reference_points_input, + **kwargs) + output = output.permute(1, 0, 2) + + if reg_branches is not None: + tmp = reg_branches[lid](output) + assert reference_points.shape[-1] == 4 + new_reference_points = tmp + inverse_sigmoid( + reference_points, eps=1e-3) + new_reference_points = new_reference_points.sigmoid() + reference_points = new_reference_points.detach() + + output = output.permute(1, 0, 2) + if self.return_intermediate: + intermediate.append(self.norm(output)) + intermediate_reference_points.append(new_reference_points) + # NOTE this is for the "Look Forward Twice" module, + # in the DeformDETR, reference_points was appended. + + if self.return_intermediate: + return torch.stack(intermediate), torch.stack( + intermediate_reference_points) + + return output, reference_points + + +@MODELS.register_module() +class CoDinoTransformer(CoDeformableDetrTransformer): + + def __init__(self, *args, **kwargs): + super(CoDinoTransformer, self).__init__(*args, **kwargs) + + def init_layers(self): + """Initialize layers of the DinoTransformer.""" + self.level_embeds = nn.Parameter( + torch.Tensor(self.num_feature_levels, self.embed_dims)) + self.enc_output = nn.Linear(self.embed_dims, self.embed_dims) + self.enc_output_norm = nn.LayerNorm(self.embed_dims) + self.query_embed = nn.Embedding(self.two_stage_num_proposals, + self.embed_dims) + + def _init_layers(self): + if self.with_pos_coord: + if self.num_co_heads > 0: + self.aux_pos_trans = nn.ModuleList() + self.aux_pos_trans_norm = nn.ModuleList() + self.pos_feats_trans = nn.ModuleList() + self.pos_feats_norm = nn.ModuleList() + for i in range(self.num_co_heads): + self.aux_pos_trans.append( + nn.Linear(self.embed_dims * 2, self.embed_dims)) + self.aux_pos_trans_norm.append( + nn.LayerNorm(self.embed_dims)) + if self.with_coord_feat: + self.pos_feats_trans.append( + nn.Linear(self.embed_dims, self.embed_dims)) + self.pos_feats_norm.append( + nn.LayerNorm(self.embed_dims)) + + def init_weights(self): + super().init_weights() + nn.init.normal_(self.query_embed.weight.data) + + def forward(self, + mlvl_feats, + mlvl_masks, + query_embed, + mlvl_pos_embeds, + dn_label_query, + dn_bbox_query, + attn_mask, + reg_branches=None, + cls_branches=None, + **kwargs): + assert self.as_two_stage and query_embed is None, \ + 'as_two_stage must be True for DINO' + + feat_flatten = [] + mask_flatten = [] + lvl_pos_embed_flatten = [] + spatial_shapes = [] + for lvl, (feat, mask, pos_embed) in enumerate( + zip(mlvl_feats, mlvl_masks, mlvl_pos_embeds)): + bs, c, h, w = feat.shape + spatial_shape = (h, w) + spatial_shapes.append(spatial_shape) + feat = feat.flatten(2).transpose(1, 2) + mask = mask.flatten(1) + pos_embed = pos_embed.flatten(2).transpose(1, 2) + lvl_pos_embed = pos_embed + self.level_embeds[lvl].view(1, 1, -1) + lvl_pos_embed_flatten.append(lvl_pos_embed) + feat_flatten.append(feat) + mask_flatten.append(mask) + feat_flatten = torch.cat(feat_flatten, 1) + mask_flatten = torch.cat(mask_flatten, 1) + lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1) + spatial_shapes = torch.as_tensor( + spatial_shapes, dtype=torch.long, device=feat_flatten.device) + level_start_index = torch.cat((spatial_shapes.new_zeros( + (1, )), spatial_shapes.prod(1).cumsum(0)[:-1])) + valid_ratios = torch.stack( + [self.get_valid_ratio(m) for m in mlvl_masks], 1) + + reference_points = self.get_reference_points( + spatial_shapes, valid_ratios, device=feat.device) + + feat_flatten = feat_flatten.permute(1, 0, 2) # (H*W, bs, embed_dims) + lvl_pos_embed_flatten = lvl_pos_embed_flatten.permute( + 1, 0, 2) # (H*W, bs, embed_dims) + memory = self.encoder( + query=feat_flatten, + key=None, + value=None, + query_pos=lvl_pos_embed_flatten, + query_key_padding_mask=mask_flatten, + spatial_shapes=spatial_shapes, + reference_points=reference_points, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + **kwargs) + memory = memory.permute(1, 0, 2) + bs, _, c = memory.shape + + output_memory, output_proposals = self.gen_encoder_output_proposals( + memory, mask_flatten, spatial_shapes) + enc_outputs_class = cls_branches[self.decoder.num_layers]( + output_memory) + enc_outputs_coord_unact = reg_branches[self.decoder.num_layers]( + output_memory) + output_proposals + cls_out_features = cls_branches[self.decoder.num_layers].out_features + topk = self.two_stage_num_proposals + # NOTE In DeformDETR, enc_outputs_class[..., 0] is used for topk + topk_indices = torch.topk(enc_outputs_class.max(-1)[0], topk, dim=1)[1] + + topk_score = torch.gather( + enc_outputs_class, 1, + topk_indices.unsqueeze(-1).repeat(1, 1, cls_out_features)) + topk_coords_unact = torch.gather( + enc_outputs_coord_unact, 1, + topk_indices.unsqueeze(-1).repeat(1, 1, 4)) + topk_anchor = topk_coords_unact.sigmoid() + topk_coords_unact = topk_coords_unact.detach() + + query = self.query_embed.weight[:, None, :].repeat(1, bs, + 1).transpose(0, 1) + # NOTE the query_embed here is not spatial query as in DETR. + # It is actually content query, which is named tgt in other + # DETR-like models + if dn_label_query is not None: + query = torch.cat([dn_label_query, query], dim=1) + if dn_bbox_query is not None: + reference_points = torch.cat([dn_bbox_query, topk_coords_unact], + dim=1) + else: + reference_points = topk_coords_unact + reference_points = reference_points.sigmoid() + # decoder + query = query.permute(1, 0, 2) + memory = memory.permute(1, 0, 2) + inter_states, inter_references = self.decoder( + query=query, + key=None, + value=memory, + attn_masks=attn_mask, + key_padding_mask=mask_flatten, + reference_points=reference_points, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reg_branches=reg_branches, + **kwargs) + + inter_references_out = inter_references + + return inter_states, inter_references_out, \ + topk_score, topk_anchor, memory + + def forward_aux(self, + mlvl_feats, + mlvl_masks, + query_embed, + mlvl_pos_embeds, + pos_anchors, + pos_feats=None, + reg_branches=None, + cls_branches=None, + return_encoder_output=False, + attn_masks=None, + head_idx=0, + **kwargs): + feat_flatten = [] + mask_flatten = [] + spatial_shapes = [] + for lvl, (feat, mask, pos_embed) in enumerate( + zip(mlvl_feats, mlvl_masks, mlvl_pos_embeds)): + bs, c, h, w = feat.shape + spatial_shape = (h, w) + spatial_shapes.append(spatial_shape) + feat = feat.flatten(2).transpose(1, 2) + mask = mask.flatten(1) + feat_flatten.append(feat) + mask_flatten.append(mask) + feat_flatten = torch.cat(feat_flatten, 1) + mask_flatten = torch.cat(mask_flatten, 1) + spatial_shapes = torch.as_tensor( + spatial_shapes, dtype=torch.long, device=feat_flatten.device) + level_start_index = torch.cat((spatial_shapes.new_zeros( + (1, )), spatial_shapes.prod(1).cumsum(0)[:-1])) + valid_ratios = torch.stack( + [self.get_valid_ratio(m) for m in mlvl_masks], 1) + + feat_flatten = feat_flatten.permute(1, 0, 2) # (H*W, bs, embed_dims) + + memory = feat_flatten + memory = memory.permute(1, 0, 2) + bs, _, c = memory.shape + + topk_coords_unact = inverse_sigmoid(pos_anchors) + reference_points = pos_anchors + if self.num_co_heads > 0: + pos_trans_out = self.aux_pos_trans_norm[head_idx]( + self.aux_pos_trans[head_idx]( + self.get_proposal_pos_embed(topk_coords_unact))) + query = pos_trans_out + if self.with_coord_feat: + query = query + self.pos_feats_norm[head_idx]( + self.pos_feats_trans[head_idx](pos_feats)) + + # decoder + query = query.permute(1, 0, 2) + memory = memory.permute(1, 0, 2) + inter_states, inter_references = self.decoder( + query=query, + key=None, + value=memory, + attn_masks=None, + key_padding_mask=mask_flatten, + reference_points=reference_points, + spatial_shapes=spatial_shapes, + level_start_index=level_start_index, + valid_ratios=valid_ratios, + reg_branches=reg_branches, + **kwargs) + + inter_references_out = inter_references + + return inter_states, inter_references_out + + +@MODELS.register_module() +class DetrTransformerEncoder(TransformerLayerSequence): + """TransformerEncoder of DETR. + + Args: + post_norm_cfg (dict): Config of last normalization layer. Default: + `LN`. Only used when `self.pre_norm` is `True` + """ + + def __init__(self, + *args, + post_norm_cfg=dict(type='LN'), + with_cp=-1, + **kwargs): + super(DetrTransformerEncoder, self).__init__(*args, **kwargs) + if post_norm_cfg is not None: + self.post_norm = build_norm_layer( + post_norm_cfg, self.embed_dims)[1] if self.pre_norm else None + else: + assert not self.pre_norm, f'Use prenorm in ' \ + f'{self.__class__.__name__},' \ + f'Please specify post_norm_cfg' + self.post_norm = None + self.with_cp = with_cp + if self.with_cp > 0: + if checkpoint_wrapper is None: + warnings.warn('If you want to reduce GPU memory usage, \ + please install fairscale by executing the \ + following command: pip install fairscale.') + return + for i in range(self.with_cp): + self.layers[i] = checkpoint_wrapper(self.layers[i]) + + +@MODELS.register_module() +class DetrTransformerDecoderLayer(BaseTransformerLayer): + """Implements decoder layer in DETR transformer. + + Args: + attn_cfgs (list[`mmcv.ConfigDict`] | list[dict] | dict )): + Configs for self_attention or cross_attention, the order + should be consistent with it in `operation_order`. If it is + a dict, it would be expand to the number of attention in + `operation_order`. + feedforward_channels (int): The hidden dimension for FFNs. + ffn_dropout (float): Probability of an element to be zeroed + in ffn. Default 0.0. + operation_order (tuple[str]): The execution order of operation + in transformer. Such as ('self_attn', 'norm', 'ffn', 'norm'). + Default:None + act_cfg (dict): The activation config for FFNs. Default: `LN` + norm_cfg (dict): Config dict for normalization layer. + Default: `LN`. + ffn_num_fcs (int): The number of fully-connected layers in FFNs. + Default:2. + """ + + def __init__(self, + attn_cfgs, + feedforward_channels, + ffn_dropout=0.0, + operation_order=None, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN'), + ffn_num_fcs=2, + **kwargs): + super(DetrTransformerDecoderLayer, self).__init__( + attn_cfgs=attn_cfgs, + feedforward_channels=feedforward_channels, + ffn_dropout=ffn_dropout, + operation_order=operation_order, + act_cfg=act_cfg, + norm_cfg=norm_cfg, + ffn_num_fcs=ffn_num_fcs, + **kwargs) + assert len(operation_order) == 6 + assert set(operation_order) == set( + ['self_attn', 'norm', 'cross_attn', 'ffn']) diff --git a/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_r50_8xb2_1x_coco.py b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_r50_8xb2_1x_coco.py new file mode 100644 index 00000000..1a413043 --- /dev/null +++ b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_r50_8xb2_1x_coco.py @@ -0,0 +1,68 @@ +_base_ = './co_dino_5scale_r50_lsj_8xb2_1x_coco.py' + +model = dict( + use_lsj=False, data_preprocessor=dict(pad_mask=False, batch_augments=None)) + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + dataset=dict( + _delete_=True, + type=_base_.dataset_type, + data_root=_base_.data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + filter_cfg=dict(filter_empty_gt=False, min_size=32), + pipeline=train_pipeline, + backend_args=_base_.backend_args)) + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='Resize', scale=(1333, 800), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_r50_lsj_8xb2_1x_coco.py b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_r50_lsj_8xb2_1x_coco.py new file mode 100644 index 00000000..876b90f8 --- /dev/null +++ b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_r50_lsj_8xb2_1x_coco.py @@ -0,0 +1,359 @@ +_base_ = 'mmdet::common/ssj_scp_270k_coco-instance.py' + +custom_imports = dict( + imports=['projects.CO-DETR.codetr'], allow_failed_imports=False) + +# model settings +num_dec_layer = 6 +loss_lambda = 2.0 +num_classes = 80 + +image_size = (1024, 1024) +batch_augments = [ + dict(type='BatchFixedSizePad', size=image_size, pad_mask=True) +] +model = dict( + type='CoDETR', + # If using the lsj augmentation, + # it is recommended to set it to True. + use_lsj=True, + # detr: 52.1 + # one-stage: 49.4 + # two-stage: 47.9 + eval_module='detr', # in ['detr', 'one-stage', 'two-stage'] + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_mask=True, + batch_augments=batch_augments), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[256, 512, 1024, 2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=5), + query_head=dict( + type='CoDINOHead', + num_query=900, + num_classes=num_classes, + in_channels=2048, + as_two_stage=True, + dn_cfg=dict( + label_noise_scale=0.5, + box_noise_scale=1.0, + group_cfg=dict(dynamic=True, num_groups=None, num_dn_queries=100)), + transformer=dict( + type='CoDinoTransformer', + with_coord_feat=False, + num_co_heads=2, # ATSS Aux Head + Faster RCNN Aux Head + num_feature_levels=5, + encoder=dict( + type='DetrTransformerEncoder', + num_layers=6, + # number of layers that use checkpoint. + # The maximum value for the setting is num_layers. + # FairScale must be installed for it to work. + with_cp=4, + transformerlayers=dict( + type='BaseTransformerLayer', + attn_cfgs=dict( + type='MultiScaleDeformableAttention', + embed_dims=256, + num_levels=5, + dropout=0.0), + feedforward_channels=2048, + ffn_dropout=0.0, + operation_order=('self_attn', 'norm', 'ffn', 'norm'))), + decoder=dict( + type='DinoTransformerDecoder', + num_layers=6, + return_intermediate=True, + transformerlayers=dict( + type='DetrTransformerDecoderLayer', + attn_cfgs=[ + dict( + type='MultiheadAttention', + embed_dims=256, + num_heads=8, + dropout=0.0), + dict( + type='MultiScaleDeformableAttention', + embed_dims=256, + num_levels=5, + dropout=0.0), + ], + feedforward_channels=2048, + ffn_dropout=0.0, + operation_order=('self_attn', 'norm', 'cross_attn', 'norm', + 'ffn', 'norm')))), + positional_encoding=dict( + type='SinePositionalEncoding', + num_feats=128, + temperature=20, + normalize=True), + loss_cls=dict( # Different from the DINO + type='QualityFocalLoss', + use_sigmoid=True, + beta=2.0, + loss_weight=1.0), + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + rpn_head=dict( + type='RPNHead', + in_channels=256, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[0.5, 1.0, 2.0], + strides=[4, 8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0 * num_dec_layer * loss_lambda), + loss_bbox=dict( + type='L1Loss', loss_weight=1.0 * num_dec_layer * loss_lambda)), + roi_head=[ + dict( + type='CoStandardRoIHead', + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', output_size=7, sampling_ratio=0), + out_channels=256, + featmap_strides=[4, 8, 16, 32, 64], + finest_scale=56), + bbox_head=dict( + type='Shared2FCBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=False, + reg_decoded_bbox=True, + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0 * num_dec_layer * loss_lambda), + loss_bbox=dict( + type='GIoULoss', + loss_weight=10.0 * num_dec_layer * loss_lambda))) + ], + bbox_head=[ + dict( + type='CoATSSHead', + num_classes=num_classes, + in_channels=256, + stacked_convs=1, + feat_channels=256, + anchor_generator=dict( + type='AnchorGenerator', + ratios=[1.0], + octave_base_scale=8, + scales_per_octave=1, + strides=[4, 8, 16, 32, 64, 128]), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[0.1, 0.1, 0.2, 0.2]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0 * num_dec_layer * loss_lambda), + loss_bbox=dict( + type='GIoULoss', + loss_weight=2.0 * num_dec_layer * loss_lambda), + loss_centerness=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=1.0 * num_dec_layer * loss_lambda)), + ], + # model training and testing settings + train_cfg=[ + dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=-1, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=4000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0.5, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + pos_weight=-1, + debug=False)), + dict( + assigner=dict(type='ATSSAssigner', topk=9), + allowed_border=-1, + pos_weight=-1, + debug=False) + ], + test_cfg=[ + # Deferent from the DINO, we use the NMS. + dict( + max_per_img=300, + # NMS can improve the mAP by 0.2. + nms=dict(type='soft_nms', iou_threshold=0.8)), + dict( + rpn=dict( + nms_pre=1000, + max_per_img=1000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=dict( + score_thr=0.0, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=100)), + dict( + # atss bbox head: + nms_pre=1000, + min_bbox_size=0, + score_thr=0.0, + nms=dict(type='nms', iou_threshold=0.6), + max_per_img=100), + # soft-nms is also supported for rcnn testing + # e.g., nms=dict(type='soft_nms', iou_threshold=0.5, min_score=0.05) + ]) + +# LSJ + CopyPaste +load_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=image_size, pad_val=dict(img=(114, 114, 114))), +] + +train_pipeline = [ + dict(type='CopyPaste', max_num_pasted=100), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + pipeline=train_pipeline, + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), pipeline=load_pipeline))) + +# follow ViTDet +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=image_size, keep_ratio=True), # diff + dict(type='Pad', size=image_size, pad_val=dict(img=(114, 114, 114))), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=2e-4, weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.1)})) + +val_evaluator = dict(metric='bbox') +test_evaluator = val_evaluator + +max_epochs = 12 +train_cfg = dict( + _delete_=True, + type='EpochBasedTrainLoop', + max_epochs=max_epochs, + val_interval=1) + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + +default_hooks = dict( + checkpoint=dict(by_epoch=True, interval=1, max_keep_ckpts=3)) +log_processor = dict(by_epoch=True) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_r50_lsj_8xb2_3x_coco.py b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_r50_lsj_8xb2_3x_coco.py new file mode 100644 index 00000000..9a9fc34f --- /dev/null +++ b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_r50_lsj_8xb2_3x_coco.py @@ -0,0 +1,4 @@ +_base_ = ['co_dino_5scale_r50_lsj_8xb2_1x_coco.py'] + +param_scheduler = [dict(milestones=[30])] +train_cfg = dict(max_epochs=36) diff --git a/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_16xb1_16e_o365tococo.py b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_16xb1_16e_o365tococo.py new file mode 100644 index 00000000..77821c38 --- /dev/null +++ b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_16xb1_16e_o365tococo.py @@ -0,0 +1,115 @@ +_base_ = ['co_dino_5scale_r50_8xb2_1x_coco.py'] + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa +load_from = 'https://download.openmmlab.com/mmdetection/v3.0/codetr/co_dino_5scale_swin_large_16e_o365tococo-614254c9.pth' # noqa + +# model settings +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=True, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[192, 384, 768, 1536]), + query_head=dict( + dn_cfg=dict(box_noise_scale=0.4, group_cfg=dict(num_dn_queries=500)), + transformer=dict(encoder=dict(with_cp=6)))) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 2048), (512, 2048), (544, 2048), (576, 2048), + (608, 2048), (640, 2048), (672, 2048), (704, 2048), + (736, 2048), (768, 2048), (800, 2048), (832, 2048), + (864, 2048), (896, 2048), (928, 2048), (960, 2048), + (992, 2048), (1024, 2048), (1056, 2048), + (1088, 2048), (1120, 2048), (1152, 2048), + (1184, 2048), (1216, 2048), (1248, 2048), + (1280, 2048), (1312, 2048), (1344, 2048), + (1376, 2048), (1408, 2048), (1440, 2048), + (1472, 2048), (1504, 2048), (1536, 2048)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 2048), (512, 2048), (544, 2048), (576, 2048), + (608, 2048), (640, 2048), (672, 2048), (704, 2048), + (736, 2048), (768, 2048), (800, 2048), (832, 2048), + (864, 2048), (896, 2048), (928, 2048), (960, 2048), + (992, 2048), (1024, 2048), (1056, 2048), + (1088, 2048), (1120, 2048), (1152, 2048), + (1184, 2048), (1216, 2048), (1248, 2048), + (1280, 2048), (1312, 2048), (1344, 2048), + (1376, 2048), (1408, 2048), (1440, 2048), + (1472, 2048), (1504, 2048), (1536, 2048)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + batch_size=1, num_workers=1, dataset=dict(pipeline=train_pipeline)) + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1280), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +optim_wrapper = dict(optimizer=dict(lr=1e-4)) + +max_epochs = 16 +train_cfg = dict(max_epochs=max_epochs) + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[8], + gamma=0.1) +] diff --git a/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_16xb1_1x_coco.py b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_16xb1_1x_coco.py new file mode 100644 index 00000000..d4a87346 --- /dev/null +++ b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_16xb1_1x_coco.py @@ -0,0 +1,31 @@ +_base_ = ['co_dino_5scale_r50_8xb2_1x_coco.py'] + +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa + +# model settings +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[192, 384, 768, 1536]), + query_head=dict(transformer=dict(encoder=dict(with_cp=6)))) + +train_dataloader = dict(batch_size=1, num_workers=1) diff --git a/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_16xb1_3x_coco.py b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_16xb1_3x_coco.py new file mode 100644 index 00000000..c2fce29b --- /dev/null +++ b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_16xb1_3x_coco.py @@ -0,0 +1,6 @@ +_base_ = ['co_dino_5scale_swin_l_16xb1_1x_coco.py'] +# model settings +model = dict(backbone=dict(drop_path_rate=0.6)) + +param_scheduler = [dict(milestones=[30])] +train_cfg = dict(max_epochs=36) diff --git a/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_lsj_16xb1_1x_coco.py b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_lsj_16xb1_1x_coco.py new file mode 100644 index 00000000..4a9b3688 --- /dev/null +++ b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_lsj_16xb1_1x_coco.py @@ -0,0 +1,72 @@ +_base_ = ['co_dino_5scale_r50_lsj_8xb2_1x_coco.py'] + +image_size = (1280, 1280) +batch_augments = [ + dict(type='BatchFixedSizePad', size=image_size, pad_mask=True) +] +pretrained = 'https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22k.pth' # noqa + +# model settings +model = dict( + data_preprocessor=dict(batch_augments=batch_augments), + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + # Please only add indices that would be used + # in FPN, otherwise some parameter will not be used + with_cp=False, + convert_weights=True, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + neck=dict(in_channels=[192, 384, 768, 1536]), + query_head=dict(transformer=dict(encoder=dict(with_cp=6)))) + +load_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='Pad', size=image_size, pad_val=dict(img=(114, 114, 114))), +] + +train_dataloader = dict( + batch_size=1, + num_workers=1, + dataset=dict(dataset=dict(pipeline=load_pipeline))) + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=image_size, keep_ratio=True), + dict(type='Pad', size=image_size, pad_val=dict(img=(114, 114, 114))), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_lsj_16xb1_3x_coco.py b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_lsj_16xb1_3x_coco.py new file mode 100644 index 00000000..bf9cd4f4 --- /dev/null +++ b/mmdetection/projects/CO-DETR/configs/codino/co_dino_5scale_swin_l_lsj_16xb1_3x_coco.py @@ -0,0 +1,7 @@ +_base_ = ['co_dino_5scale_swin_l_lsj_16xb1_1x_coco.py'] + +model = dict(backbone=dict(drop_path_rate=0.5)) + +param_scheduler = [dict(type='MultiStepLR', milestones=[30])] + +train_cfg = dict(max_epochs=36) diff --git a/mmdetection/projects/ConvNeXt-V2/README.md b/mmdetection/projects/ConvNeXt-V2/README.md new file mode 100644 index 00000000..7a9f56cd --- /dev/null +++ b/mmdetection/projects/ConvNeXt-V2/README.md @@ -0,0 +1,37 @@ +# ConvNeXt-V2 + +> [ConvNeXt V2: Co-designing and Scaling ConvNets with Masked Autoencoders](http://arxiv.org/abs/2301.00808) + +## Abstract + +Driven by improved architectures and better representation learning frameworks, the field of visual recognition has enjoyed rapid modernization and performance boost in the early 2020s. For example, modern ConvNets, represented by ConvNeXt \[52\], have demonstrated strong performance in various scenarios. While these models were originally designed for supervised learning with ImageNet labels, they can also potentially benefit from self-supervised learning techniques such as masked autoencoders (MAE) . However, we found that simply combining these two approaches leads to subpar performance. In this paper, we propose a fully convolutional masked autoencoder framework and a new Global Response Normalization (GRN) layer that can be added to the ConvNeXt architecture to enhance inter-channel feature competition. This co-design of self-supervised learning techniques and architectural improvement results in a new model family called ConvNeXt V2, which significantly improves the performance of pure ConvNets on various recognition benchmarks, including ImageNet classification, COCO detection, and ADE20K segmentation. We also provide pre-trained ConvNeXt V2 models of various sizes, ranging from an efficient 3.7Mparameter Atto model with 76.7% top-1 accuracy on Im-ageNet, to a 650M Huge model that achieves a state-of-theart 88.9% accuracy using only public training data. + +
    + +
    + +## Results and models + +| Method | Backbone | Pretrain | Lr schd | Augmentation | Mem (GB) | box AP | mask AP | Config | Download | +| :--------: | :-----------: | :------: | :-----: | :----------: | :------: | :----: | :-----: | :----------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Mask R-CNN | ConvNeXt-V2-B | FCMAE | 3x | LSJ | 22.5 | 52.9 | 46.4 | [config](./mask-rcnn_convnext-v2-b_fpn_lsj-3x-fcmae_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/convnextv2/mask-rcnn_convnext-v2-b_fpn_lsj-3x-fcmae_coco/mask-rcnn_convnext-v2-b_fpn_lsj-3x-fcmae_coco_20230113_110947-757ee2dd.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/convnextv2/mask-rcnn_convnext-v2-b_fpn_lsj-3x-fcmae_coco/mask-rcnn_convnext-v2-b_fpn_lsj-3x-fcmae_coco_20230113_110947.log.json) | + +**Note**: + +- This is a pre-release version of ConvNeXt-V2 object detection. The official finetuning setting of ConvNeXt-V2 has not been released yet. +- ConvNeXt backbone needs to install [MMPretrain](https://github.com/open-mmlab/mmpretrain/) first, which has abundant backbones for downstream tasks. + +```shell +pip install mmpretrain +``` + +## Citation + +```bibtex +@article{Woo2023ConvNeXtV2, + title={ConvNeXt V2: Co-designing and Scaling ConvNets with Masked Autoencoders}, + author={Sanghyun Woo, Shoubhik Debnath, Ronghang Hu, Xinlei Chen, Zhuang Liu, In So Kweon and Saining Xie}, + year={2023}, + journal={arXiv preprint arXiv:2301.00808}, +} +``` diff --git a/mmdetection/projects/ConvNeXt-V2/configs/mask-rcnn_convnext-v2-b_fpn_lsj-3x-fcmae_coco.py b/mmdetection/projects/ConvNeXt-V2/configs/mask-rcnn_convnext-v2-b_fpn_lsj-3x-fcmae_coco.py new file mode 100644 index 00000000..59e89550 --- /dev/null +++ b/mmdetection/projects/ConvNeXt-V2/configs/mask-rcnn_convnext-v2-b_fpn_lsj-3x-fcmae_coco.py @@ -0,0 +1,92 @@ +_base_ = [ + 'mmdet::_base_/models/mask-rcnn_r50_fpn.py', + 'mmdet::_base_/datasets/coco_instance.py', + 'mmdet::_base_/schedules/schedule_1x.py', + 'mmdet::_base_/default_runtime.py' +] + +# please install the mmpretrain +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict( + imports=['mmpretrain.models'], allow_failed_imports=False) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext-v2/convnext-v2-base_3rdparty-fcmae_in1k_20230104-8a798eaf.pth' # noqa +image_size = (1024, 1024) + +model = dict( + backbone=dict( + _delete_=True, + type='mmpretrain.ConvNeXt', + arch='base', + out_indices=[0, 1, 2, 3], + # TODO: verify stochastic depth rate {0.1, 0.2, 0.3, 0.4} + drop_path_rate=0.4, + layer_scale_init_value=0., # disable layer scale when using GRN + gap_before_final_norm=False, + use_grn=True, # V2 uses GRN + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + neck=dict(in_channels=[128, 256, 512, 1024]), + test_cfg=dict( + rpn=dict(nms=dict(type='nms')), # TODO: does RPN use soft_nms? + rcnn=dict(nms=dict(type='soft_nms')))) + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + batch_size=4, # total_batch_size 32 = 8 GPUS x 4 images + num_workers=8, + dataset=dict(pipeline=train_pipeline)) + +max_epochs = 36 +train_cfg = dict(max_epochs=max_epochs) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[27, 33], + gamma=0.1) +] + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + constructor='LearningRateDecayOptimizerConstructor', + paramwise_cfg={ + 'decay_rate': 0.95, + 'decay_type': 'layer_wise', # TODO: sweep layer-wise lr decay? + 'num_layers': 12 + }, + optimizer=dict( + _delete_=True, + type='AdamW', + lr=0.0001, + betas=(0.9, 0.999), + weight_decay=0.05, + )) + +default_hooks = dict(checkpoint=dict(max_keep_ckpts=1)) diff --git a/mmdetection/projects/Detic/README.md b/mmdetection/projects/Detic/README.md new file mode 100644 index 00000000..98cd705b --- /dev/null +++ b/mmdetection/projects/Detic/README.md @@ -0,0 +1,156 @@ +# Note: This project has been deprecated, please use [Detic_new](../Detic_new). + +# Detecting Twenty-thousand Classes using Image-level Supervision + +## Description + +**Detic**: A **Det**ector with **i**mage **c**lasses that can use image-level labels to easily train detectors. + +

    + +> [**Detecting Twenty-thousand Classes using Image-level Supervision**](http://arxiv.org/abs/2201.02605), +> Xingyi Zhou, Rohit Girdhar, Armand Joulin, Philipp Krähenbühl, Ishan Misra, +> *ECCV 2022 ([arXiv 2201.02605](http://arxiv.org/abs/2201.02605))* + +## Usage + + + +## Installation + +Detic requires to install CLIP. + +```shell +pip install git+https://github.com/openai/CLIP.git +``` + +### Demo + +#### Inference with existing dataset vocabulary embeddings + +First, go to the Detic project folder. + +```shell +cd projects/Detic +``` + +Then, download the pre-computed CLIP embeddings from [dataset metainfo](https://github.com/facebookresearch/Detic/tree/main/datasets/metadata) to the `datasets/metadata` folder. +The CLIP embeddings will be loaded to the zero-shot classifier during inference. +For example, you can download LVIS's class name embeddings with the following command: + +```shell +wget -P datasets/metadata https://raw.githubusercontent.com/facebookresearch/Detic/main/datasets/metadata/lvis_v1_clip_a%2Bcname.npy +``` + +You can run demo like this: + +```shell +python demo.py \ + ${IMAGE_PATH} \ + ${CONFIG_PATH} \ + ${MODEL_PATH} \ + --show \ + --score-thr 0.5 \ + --dataset lvis +``` + +![image](https://user-images.githubusercontent.com/12907710/213624759-f0a2ba0c-0f5c-4424-a350-5ba5349e5842.png) + +### Inference with custom vocabularies + +- Detic can detects any class given class names by using CLIP. + +You can detect custom classes with `--class-name` command: + +``` +python demo.py \ + ${IMAGE_PATH} \ + ${CONFIG_PATH} \ + ${MODEL_PATH} \ + --show \ + --score-thr 0.3 \ + --class-name headphone webcam paper coffe +``` + +![image](https://user-images.githubusercontent.com/12907710/213624637-e9e8a313-9821-4782-a18a-4408c876852b.png) + +Note that `headphone`, `paper` and `coffe` (typo intended) are not LVIS classes. Despite the misspelled class name, Detic can produce a reasonable detection for `coffe`. + +## Results + +Here we only provide the Detic Swin-B model for the open vocabulary demo. Multi-dataset training and open-vocabulary testing will be supported in the future. + +To find more variants, please visit the [official model zoo](https://github.com/facebookresearch/Detic/blob/main/docs/MODEL_ZOO.md). + +| Backbone | Training data | Config | Download | +| :------: | :------------------------: | :-------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Swin-B | ImageNet-21K & LVIS & COCO | [config](./configs/detic_centernet2_swin-b_fpn_4x_lvis-coco-in21k.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_swin-b_fpn_4x_lvis-coco-in21k/detic_centernet2_swin-b_fpn_4x_lvis-coco-in21k_20230120-0d301978.pth) | + +## Citation + +If you find Detic is useful in your research or applications, please consider giving a star 🌟 to the [official repository](https://github.com/facebookresearch/Detic) and citing Detic by the following BibTeX entry. + +```BibTeX +@inproceedings{zhou2022detecting, + title={Detecting Twenty-thousand Classes using Image-level Supervision}, + author={Zhou, Xingyi and Girdhar, Rohit and Joulin, Armand and Kr{\"a}henb{\"u}hl, Philipp and Misra, Ishan}, + booktitle={ECCV}, + year={2022} +} + +``` + +## Checklist + + + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + + + - [x] Basic docstrings & proper citation + + + + - [x] Test-time correctness + + + + - [x] A full README + + + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + + + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + + + - [ ] Unit tests + + + + - [ ] Code polishing + + + + - [ ] Metafile.yml + + + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + + + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/mmdetection/projects/Detic/configs/detic_centernet2_swin-b_fpn_4x_lvis-coco-in21k.py b/mmdetection/projects/Detic/configs/detic_centernet2_swin-b_fpn_4x_lvis-coco-in21k.py new file mode 100644 index 00000000..d554c40e --- /dev/null +++ b/mmdetection/projects/Detic/configs/detic_centernet2_swin-b_fpn_4x_lvis-coco-in21k.py @@ -0,0 +1,298 @@ +_base_ = 'mmdet::common/lsj-200e_coco-detection.py' + +custom_imports = dict( + imports=['projects.Detic.detic'], allow_failed_imports=False) + +image_size = (1024, 1024) +batch_augments = [dict(type='BatchFixedSizePad', size=image_size)] + +cls_layer = dict( + type='ZeroShotClassifier', + zs_weight_path='rand', + zs_weight_dim=512, + use_bias=0.0, + norm_weight=True, + norm_temperature=50.0) +reg_layer = [ + dict(type='Linear', in_features=1024, out_features=1024), + dict(type='ReLU', inplace=True), + dict(type='Linear', in_features=1024, out_features=4) +] + +num_classes = 22047 + +model = dict( + type='CascadeRCNN', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32, + batch_augments=batch_augments), + backbone=dict( + type='SwinTransformer', + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024], + out_channels=256, + start_level=0, + add_extra_convs='on_output', + num_outs=5, + init_cfg=dict(type='Caffe2Xavier', layer='Conv2d'), + relu_before_extra_convs=True), + rpn_head=dict( + type='CenterNetRPNHead', + num_classes=1, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + conv_bias=True, + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True), + loss_cls=dict( + type='GaussianFocalLoss', + pos_weight=0.25, + neg_weight=0.75, + loss_weight=1.0), + loss_bbox=dict(type='GIoULoss', loss_weight=2.0), + ), + roi_head=dict( + type='DeticRoIHead', + num_stages=3, + stage_loss_weights=[1, 0.5, 0.25], + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', + output_size=7, + sampling_ratio=0, + use_torchvision=True), + out_channels=256, + featmap_strides=[8, 16, 32], + # approximately equal to + # canonical_box_size=224, canonical_level=4 in D2 + finest_scale=112), + bbox_head=[ + dict( + type='DeticBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + cls_predictor_cfg=cls_layer, + reg_predictor_cfg=reg_layer, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='DeticBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + cls_predictor_cfg=cls_layer, + reg_predictor_cfg=reg_layer, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, + loss_weight=1.0)), + dict( + type='DeticBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + cls_predictor_cfg=cls_layer, + reg_predictor_cfg=reg_layer, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)) + ], + mask_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[8, 16, 32], + # approximately equal to + # canonical_box_size=224, canonical_level=4 in D2 + finest_scale=112), + mask_head=dict( + type='FCNMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + class_agnostic=True, + num_classes=num_classes, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + nms_pre=2000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.7), + min_bbox_size=0), + rcnn=[ + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.8, + neg_iou_thr=0.8, + min_pos_iou=0.8, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + score_thr=0.0001, + nms_pre=1000, + max_per_img=256, + nms=dict(type='nms', iou_threshold=0.9), + min_bbox_size=0), + rcnn=dict( + score_thr=0.02, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=300, + mask_thr_binary=0.5))) + +backend = 'pillow' +test_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args, + imdecode_backend=backend), + dict(type='Resize', scale=(1333, 800), keep_ratio=True, backend=backend), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict(batch_size=8, num_workers=4) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='AmpOptimWrapper', + optimizer=dict( + type='SGD', lr=0.01 * 4, momentum=0.9, weight_decay=0.00004), + paramwise_cfg=dict(norm_decay_mult=0.)) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.00025, + by_epoch=False, + begin=0, + end=4000), + dict( + type='MultiStepLR', + begin=0, + end=25, + by_epoch=True, + milestones=[22, 24], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/mmdetection/projects/Detic/demo.py b/mmdetection/projects/Detic/demo.py new file mode 100644 index 00000000..d5c80c9a --- /dev/null +++ b/mmdetection/projects/Detic/demo.py @@ -0,0 +1,142 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import urllib +from argparse import ArgumentParser + +import mmcv +import torch +from mmengine.logging import print_log +from mmengine.utils import ProgressBar, scandir + +from mmdet.apis import inference_detector, init_detector +from mmdet.registry import VISUALIZERS +from mmdet.utils import register_all_modules + +IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', + '.tiff', '.webp') + + +def get_file_list(source_root: str) -> [list, dict]: + """Get file list. + + Args: + source_root (str): image or video source path + + Return: + source_file_path_list (list): A list for all source file. + source_type (dict): Source type: file or url or dir. + """ + is_dir = os.path.isdir(source_root) + is_url = source_root.startswith(('http:/', 'https:/')) + is_file = os.path.splitext(source_root)[-1].lower() in IMG_EXTENSIONS + + source_file_path_list = [] + if is_dir: + # when input source is dir + for file in scandir(source_root, IMG_EXTENSIONS, recursive=True): + source_file_path_list.append(os.path.join(source_root, file)) + elif is_url: + # when input source is url + filename = os.path.basename( + urllib.parse.unquote(source_root).split('?')[0]) + file_save_path = os.path.join(os.getcwd(), filename) + print(f'Downloading source file to {file_save_path}') + torch.hub.download_url_to_file(source_root, file_save_path) + source_file_path_list = [file_save_path] + elif is_file: + # when input source is single image + source_file_path_list = [source_root] + else: + print('Cannot find image file.') + + source_type = dict(is_dir=is_dir, is_url=is_url, is_file=is_file) + + return source_file_path_list, source_type + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument( + 'img', help='Image path, include image file, dir and URL.') + parser.add_argument('config', help='Config file') + parser.add_argument('checkpoint', help='Checkpoint file') + parser.add_argument( + '--out-dir', default='./output', help='Path to output file') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + parser.add_argument( + '--show', action='store_true', help='Show the detection results') + parser.add_argument( + '--score-thr', type=float, default=0.3, help='Bbox score threshold') + parser.add_argument( + '--dataset', type=str, help='dataset name to load the text embedding') + parser.add_argument( + '--class-name', nargs='+', type=str, help='custom class names') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + + # register all modules in mmdet into the registries + register_all_modules() + + # build the model from a config file and a checkpoint file + model = init_detector(args.config, args.checkpoint, device=args.device) + + if not os.path.exists(args.out_dir) and not args.show: + os.mkdir(args.out_dir) + + # init visualizer + visualizer = VISUALIZERS.build(model.cfg.visualizer) + visualizer.dataset_meta = model.dataset_meta + + # get file list + files, source_type = get_file_list(args.img) + from detic.utils import (get_class_names, get_text_embeddings, + reset_cls_layer_weight) + + # class name embeddings + if args.class_name: + dataset_classes = args.class_name + elif args.dataset: + dataset_classes = get_class_names(args.dataset) + embedding = get_text_embeddings( + dataset=args.dataset, custom_vocabulary=args.class_name) + visualizer.dataset_meta['classes'] = dataset_classes + reset_cls_layer_weight(model, embedding) + + # start detector inference + progress_bar = ProgressBar(len(files)) + for file in files: + result = inference_detector(model, file) + + img = mmcv.imread(file) + img = mmcv.imconvert(img, 'bgr', 'rgb') + + if source_type['is_dir']: + filename = os.path.relpath(file, args.img).replace('/', '_') + else: + filename = os.path.basename(file) + out_file = None if args.show else os.path.join(args.out_dir, filename) + + progress_bar.update() + + visualizer.add_datasample( + filename, + img, + data_sample=result, + draw_gt=False, + show=args.show, + wait_time=0, + out_file=out_file, + pred_score_thr=args.score_thr) + + if not args.show: + print_log( + f'\nResults have been saved at {os.path.abspath(args.out_dir)}') + + +if __name__ == '__main__': + main() diff --git a/mmdetection/projects/Detic/detic/__init__.py b/mmdetection/projects/Detic/detic/__init__.py new file mode 100644 index 00000000..d0ad0702 --- /dev/null +++ b/mmdetection/projects/Detic/detic/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .centernet_rpn_head import CenterNetRPNHead +from .detic_bbox_head import DeticBBoxHead +from .detic_roi_head import DeticRoIHead +from .zero_shot_classifier import ZeroShotClassifier + +__all__ = [ + 'CenterNetRPNHead', 'DeticBBoxHead', 'DeticRoIHead', 'ZeroShotClassifier' +] diff --git a/mmdetection/projects/Detic/detic/centernet_rpn_head.py b/mmdetection/projects/Detic/detic/centernet_rpn_head.py new file mode 100644 index 00000000..765d6dfb --- /dev/null +++ b/mmdetection/projects/Detic/detic/centernet_rpn_head.py @@ -0,0 +1,196 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import List, Sequence, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import Scale +from mmengine import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.dense_heads import CenterNetUpdateHead +from mmdet.models.utils import multi_apply +from mmdet.registry import MODELS + +INF = 1000000000 +RangeType = Sequence[Tuple[int, int]] + + +@MODELS.register_module(force=True) # avoid bug +class CenterNetRPNHead(CenterNetUpdateHead): + """CenterNetUpdateHead is an improved version of CenterNet in CenterNet2. + + Paper link ``_. + """ + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self._init_reg_convs() + self._init_predictor() + + def _init_predictor(self) -> None: + """Initialize predictor layers of the head.""" + self.conv_cls = nn.Conv2d( + self.feat_channels, self.num_classes, 3, padding=1) + self.conv_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1) + + def forward(self, x: Tuple[Tensor]) -> Tuple[List[Tensor], List[Tensor]]: + """Forward features from the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + + Returns: + tuple: A tuple of each level outputs. + + - cls_scores (list[Tensor]): Box scores for each scale level, \ + each is a 4D-tensor, the channel number is num_classes. + - bbox_preds (list[Tensor]): Box energies / deltas for each \ + scale level, each is a 4D-tensor, the channel number is 4. + """ + res = multi_apply(self.forward_single, x, self.scales, self.strides) + return res + + def forward_single(self, x: Tensor, scale: Scale, + stride: int) -> Tuple[Tensor, Tensor]: + """Forward features of a single scale level. + + Args: + x (Tensor): FPN feature maps of the specified stride. + scale (:obj:`mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + stride (int): The corresponding stride for feature maps. + + Returns: + tuple: scores for each class, bbox predictions of + input feature maps. + """ + for m in self.reg_convs: + x = m(x) + cls_score = self.conv_cls(x) + bbox_pred = self.conv_reg(x) + # scale the bbox_pred of different level + # float to avoid overflow when enabling FP16 + bbox_pred = scale(bbox_pred).float() + # bbox_pred needed for gradient computation has been modified + # by F.relu(bbox_pred) when run with PyTorch 1.10. So replace + # F.relu(bbox_pred) with bbox_pred.clamp(min=0) + bbox_pred = bbox_pred.clamp(min=0) + if not self.training: + bbox_pred *= stride + return cls_score, bbox_pred # score aligned, box larger + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image, each item has shape + (num_priors * 1, H, W). + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid. In all + anchor-based methods, it has shape (num_priors, 4). In + all anchor-free methods, it has shape (num_priors, 2) + when `with_stride=True`, otherwise it still has shape + (num_priors, 4). + img_meta (dict): Image meta info. + cfg (mmengine.Config): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bbox_preds = [] + mlvl_valid_priors = [] + mlvl_scores = [] + mlvl_labels = [] + + for level_idx, (cls_score, bbox_pred, score_factor, priors) in \ + enumerate(zip(cls_score_list, bbox_pred_list, + score_factor_list, mlvl_priors)): + + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + + dim = self.bbox_coder.encode_size + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, dim) + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + heatmap = cls_score.sigmoid() + score_thr = cfg.get('score_thr', 0) + + candidate_inds = heatmap > score_thr # 0.05 + pre_nms_top_n = candidate_inds.sum() # N + pre_nms_top_n = pre_nms_top_n.clamp(max=nms_pre) # N + + heatmap = heatmap[candidate_inds] # n + + candidate_nonzeros = candidate_inds.nonzero() # n + box_loc = candidate_nonzeros[:, 0] # n + labels = candidate_nonzeros[:, 1] # n + + bbox_pred = bbox_pred[box_loc] # n x 4 + per_grids = priors[box_loc] # n x 2 + + if candidate_inds.sum().item() > pre_nms_top_n.item(): + heatmap, top_k_indices = \ + heatmap.topk(pre_nms_top_n, sorted=False) + labels = labels[top_k_indices] + bbox_pred = bbox_pred[top_k_indices] + per_grids = per_grids[top_k_indices] + + bboxes = self.bbox_coder.decode(per_grids, bbox_pred) + # avoid invalid boxes in RoI heads + bboxes[:, 2] = torch.max(bboxes[:, 2], bboxes[:, 0] + 0.01) + bboxes[:, 3] = torch.max(bboxes[:, 3], bboxes[:, 1] + 0.01) + + mlvl_bbox_preds.append(bboxes) + mlvl_valid_priors.append(priors) + mlvl_scores.append(torch.sqrt(heatmap)) + mlvl_labels.append(labels) + + results = InstanceData() + results.bboxes = torch.cat(mlvl_bbox_preds) + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + + return self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) diff --git a/mmdetection/projects/Detic/detic/detic_bbox_head.py b/mmdetection/projects/Detic/detic/detic_bbox_head.py new file mode 100644 index 00000000..9408cbe0 --- /dev/null +++ b/mmdetection/projects/Detic/detic/detic_bbox_head.py @@ -0,0 +1,112 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Union + +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.layers import multiclass_nms +from mmdet.models.roi_heads.bbox_heads import Shared2FCBBoxHead +from mmdet.models.utils import empty_instances +from mmdet.registry import MODELS +from mmdet.structures.bbox import get_box_tensor, scale_boxes + + +@MODELS.register_module(force=True) # avoid bug +class DeticBBoxHead(Shared2FCBBoxHead): + + def __init__(self, + *args, + init_cfg: Optional[Union[dict, ConfigDict]] = None, + **kwargs) -> None: + super().__init__(*args, init_cfg=init_cfg, **kwargs) + # reconstruct fc_cls and fc_reg since input channels are changed + assert self.with_cls + cls_channels = self.num_classes + cls_predictor_cfg_ = self.cls_predictor_cfg.copy() + cls_predictor_cfg_.update( + in_features=self.cls_last_dim, out_features=cls_channels) + self.fc_cls = MODELS.build(cls_predictor_cfg_) + + def _predict_by_feat_single( + self, + roi: Tensor, + cls_score: Tensor, + bbox_pred: Tensor, + img_meta: dict, + rescale: bool = False, + rcnn_test_cfg: Optional[ConfigDict] = None) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + roi (Tensor): Boxes to be transformed. Has shape (num_boxes, 5). + last dimension 5 arrange as (batch_index, x1, y1, x2, y2). + cls_score (Tensor): Box scores, has shape + (num_boxes, num_classes + 1). + bbox_pred (Tensor): Box energies / deltas. + has shape (num_boxes, num_classes * 4). + img_meta (dict): image information. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of Bbox Head. + Defaults to None + + Returns: + :obj:`InstanceData`: Detection results of each image\ + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + results = InstanceData() + if roi.shape[0] == 0: + return empty_instances([img_meta], + roi.device, + task_type='bbox', + instance_results=[results], + box_type=self.predict_box_type, + use_box_type=False, + num_classes=self.num_classes, + score_per_cls=rcnn_test_cfg is None)[0] + scores = cls_score + img_shape = img_meta['img_shape'] + num_rois = roi.size(0) + + num_classes = 1 if self.reg_class_agnostic else self.num_classes + roi = roi.repeat_interleave(num_classes, dim=0) + bbox_pred = bbox_pred.view(-1, self.bbox_coder.encode_size) + bboxes = self.bbox_coder.decode( + roi[..., 1:], bbox_pred, max_shape=img_shape) + + if rescale and bboxes.size(0) > 0: + assert img_meta.get('scale_factor') is not None + scale_factor = [1 / s for s in img_meta['scale_factor']] + bboxes = scale_boxes(bboxes, scale_factor) + + # Get the inside tensor when `bboxes` is a box type + bboxes = get_box_tensor(bboxes) + box_dim = bboxes.size(-1) + bboxes = bboxes.view(num_rois, -1) + + if rcnn_test_cfg is None: + # This means that it is aug test. + # It needs to return the raw results without nms. + results.bboxes = bboxes + results.scores = scores + else: + det_bboxes, det_labels = multiclass_nms( + bboxes, + scores, + rcnn_test_cfg.score_thr, + rcnn_test_cfg.nms, + rcnn_test_cfg.max_per_img, + box_dim=box_dim) + results.bboxes = det_bboxes[:, :-1] + results.scores = det_bboxes[:, -1] + results.labels = det_labels + return results diff --git a/mmdetection/projects/Detic/detic/detic_roi_head.py b/mmdetection/projects/Detic/detic/detic_roi_head.py new file mode 100644 index 00000000..a09c11c6 --- /dev/null +++ b/mmdetection/projects/Detic/detic/detic_roi_head.py @@ -0,0 +1,326 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Sequence, Tuple + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.roi_heads import CascadeRoIHead +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.models.test_time_augs import merge_aug_masks +from mmdet.models.utils.misc import empty_instances +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi, get_box_tensor +from mmdet.utils import ConfigType, InstanceList, MultiConfig + + +@MODELS.register_module(force=True) # avoid bug +class DeticRoIHead(CascadeRoIHead): + + def init_mask_head(self, mask_roi_extractor: MultiConfig, + mask_head: MultiConfig) -> None: + """Initialize mask head and mask roi extractor. + + Args: + mask_head (dict): Config of mask in mask head. + mask_roi_extractor (:obj:`ConfigDict`, dict or list): + Config of mask roi extractor. + """ + self.mask_head = MODELS.build(mask_head) + + if mask_roi_extractor is not None: + self.share_roi_extractor = False + self.mask_roi_extractor = MODELS.build(mask_roi_extractor) + else: + self.share_roi_extractor = True + self.mask_roi_extractor = self.bbox_roi_extractor + + def _refine_roi(self, x: Tuple[Tensor], rois: Tensor, + batch_img_metas: List[dict], + num_proposals_per_img: Sequence[int], **kwargs) -> tuple: + """Multi-stage refinement of RoI. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): shape (n, 5), [batch_ind, x1, y1, x2, y2] + batch_img_metas (list[dict]): List of image information. + num_proposals_per_img (sequence[int]): number of proposals + in each image. + + Returns: + tuple: + + - rois (Tensor): Refined RoI. + - cls_scores (list[Tensor]): Average predicted + cls score per image. + - bbox_preds (list[Tensor]): Bbox branch predictions + for the last stage of per image. + """ + # "ms" in variable names means multi-stage + ms_scores = [] + for stage in range(self.num_stages): + bbox_results = self._bbox_forward( + stage=stage, x=x, rois=rois, **kwargs) + + # split batch bbox prediction back to each image + cls_scores = bbox_results['cls_score'].sigmoid() + bbox_preds = bbox_results['bbox_pred'] + + rois = rois.split(num_proposals_per_img, 0) + cls_scores = cls_scores.split(num_proposals_per_img, 0) + ms_scores.append(cls_scores) + bbox_preds = bbox_preds.split(num_proposals_per_img, 0) + + if stage < self.num_stages - 1: + bbox_head = self.bbox_head[stage] + refine_rois_list = [] + for i in range(len(batch_img_metas)): + if rois[i].shape[0] > 0: + bbox_label = cls_scores[i][:, :-1].argmax(dim=1) + # Refactor `bbox_head.regress_by_class` to only accept + # box tensor without img_idx concatenated. + refined_bboxes = bbox_head.regress_by_class( + rois[i][:, 1:], bbox_label, bbox_preds[i], + batch_img_metas[i]) + refined_bboxes = get_box_tensor(refined_bboxes) + refined_rois = torch.cat( + [rois[i][:, [0]], refined_bboxes], dim=1) + refine_rois_list.append(refined_rois) + rois = torch.cat(refine_rois_list) + # ms_scores aligned + # average scores of each image by stages + cls_scores = [ + sum([score[i] for score in ms_scores]) / float(len(ms_scores)) + for i in range(len(batch_img_metas)) + ] # aligned + return rois, cls_scores, bbox_preds + + def _bbox_forward(self, stage: int, x: Tuple[Tensor], + rois: Tensor) -> dict: + """Box head forward function used in both training and testing. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict[str, Tensor]: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + """ + bbox_roi_extractor = self.bbox_roi_extractor[stage] + bbox_head = self.bbox_head[stage] + bbox_feats = bbox_roi_extractor(x[:bbox_roi_extractor.num_inputs], + rois) + # do not support caffe_c4 model anymore + cls_score, bbox_pred = bbox_head(bbox_feats) + + bbox_results = dict( + cls_score=cls_score, bbox_pred=bbox_pred, bbox_feats=bbox_feats) + return bbox_results + + def predict_bbox(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + rpn_results_list: InstanceList, + rcnn_test_cfg: ConfigType, + rescale: bool = False, + **kwargs) -> InstanceList: + """Perform forward propagation of the bbox head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of R-CNN. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + proposals = [res.bboxes for res in rpn_results_list] + proposal_scores = [res.scores for res in rpn_results_list] + num_proposals_per_img = tuple(len(p) for p in proposals) + rois = bbox2roi(proposals) + + if rois.shape[0] == 0: + return empty_instances( + batch_img_metas, + rois.device, + task_type='bbox', + box_type=self.bbox_head[-1].predict_box_type, + num_classes=self.bbox_head[-1].num_classes, + score_per_cls=rcnn_test_cfg is None) + # rois aligned + rois, cls_scores, bbox_preds = self._refine_roi( + x=x, + rois=rois, + batch_img_metas=batch_img_metas, + num_proposals_per_img=num_proposals_per_img, + **kwargs) + + # score reweighting in centernet2 + cls_scores = [(s * ps[:, None])**0.5 + for s, ps in zip(cls_scores, proposal_scores)] + cls_scores = [ + s * (s == s[:, :-1].max(dim=1)[0][:, None]).float() + for s in cls_scores + ] + + # fast_rcnn_inference + results_list = self.bbox_head[-1].predict_by_feat( + rois=rois, + cls_scores=cls_scores, + bbox_preds=bbox_preds, + batch_img_metas=batch_img_metas, + rescale=rescale, + rcnn_test_cfg=rcnn_test_cfg) + return results_list + + def _mask_forward(self, x: Tuple[Tensor], rois: Tensor) -> dict: + """Mask head forward function used in both training and testing. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): Tuple of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + """ + mask_feats = self.mask_roi_extractor( + x[:self.mask_roi_extractor.num_inputs], rois) + # do not support caffe_c4 model anymore + mask_preds = self.mask_head(mask_feats) + + mask_results = dict(mask_preds=mask_preds) + return mask_results + + def mask_loss(self, x, sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList) -> dict: + """Run forward function and calculate loss for mask head in training. + + Args: + x (tuple[Tensor]): Tuple of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + - `loss_mask` (dict): A dictionary of mask loss components. + """ + pos_rois = bbox2roi([res.pos_priors for res in sampling_results]) + mask_results = self._mask_forward(x, pos_rois) + + mask_loss_and_target = self.mask_head.loss_and_target( + mask_preds=mask_results['mask_preds'], + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=self.train_cfg[-1]) + mask_results.update(mask_loss_and_target) + + return mask_results + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components + """ + raise NotImplementedError + + def predict_mask(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + results_list: List[InstanceData], + rescale: bool = False) -> List[InstanceData]: + """Perform forward propagation of the mask head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + bboxes = [res.bboxes for res in results_list] + mask_rois = bbox2roi(bboxes) + if mask_rois.shape[0] == 0: + results_list = empty_instances( + batch_img_metas, + mask_rois.device, + task_type='mask', + instance_results=results_list, + mask_thr_binary=self.test_cfg.mask_thr_binary) + return results_list + + num_mask_rois_per_img = [len(res) for res in results_list] + aug_masks = [] + mask_results = self._mask_forward(x, mask_rois) + mask_preds = mask_results['mask_preds'] + # split batch mask prediction back to each image + mask_preds = mask_preds.split(num_mask_rois_per_img, 0) + aug_masks.append([m.sigmoid().detach() for m in mask_preds]) + + merged_masks = [] + for i in range(len(batch_img_metas)): + aug_mask = [mask[i] for mask in aug_masks] + merged_mask = merge_aug_masks(aug_mask, batch_img_metas[i]) + merged_masks.append(merged_mask) + results_list = self.mask_head.predict_by_feat( + mask_preds=merged_masks, + results_list=results_list, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=self.test_cfg, + rescale=rescale, + activate_map=True) + return results_list diff --git a/mmdetection/projects/Detic/detic/text_encoder.py b/mmdetection/projects/Detic/detic/text_encoder.py new file mode 100644 index 00000000..f0024efa --- /dev/null +++ b/mmdetection/projects/Detic/detic/text_encoder.py @@ -0,0 +1,50 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Union + +import torch +import torch.nn as nn + + +class CLIPTextEncoder(nn.Module): + + def __init__(self, model_name='ViT-B/32'): + super().__init__() + import clip + from clip.simple_tokenizer import SimpleTokenizer + self.tokenizer = SimpleTokenizer() + pretrained_model, _ = clip.load(model_name, device='cpu') + self.clip = pretrained_model + + @property + def device(self): + return self.clip.device + + @property + def dtype(self): + return self.clip.dtype + + def tokenize(self, + texts: Union[str, List[str]], + context_length: int = 77) -> torch.LongTensor: + if isinstance(texts, str): + texts = [texts] + + sot_token = self.tokenizer.encoder['<|startoftext|>'] + eot_token = self.tokenizer.encoder['<|endoftext|>'] + all_tokens = [[sot_token] + self.tokenizer.encode(text) + [eot_token] + for text in texts] + result = torch.zeros(len(all_tokens), context_length, dtype=torch.long) + + for i, tokens in enumerate(all_tokens): + if len(tokens) > context_length: + st = torch.randint(len(tokens) - context_length + 1, + (1, ))[0].item() + tokens = tokens[st:st + context_length] + result[i, :len(tokens)] = torch.tensor(tokens) + + return result + + def forward(self, text): + text = self.tokenize(text) + text_features = self.clip.encode_text(text) + return text_features diff --git a/mmdetection/projects/Detic/detic/utils.py b/mmdetection/projects/Detic/detic/utils.py new file mode 100644 index 00000000..56d4fd42 --- /dev/null +++ b/mmdetection/projects/Detic/detic/utils.py @@ -0,0 +1,78 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np +import torch +import torch.nn.functional as F +from mmengine.logging import print_log + +from .text_encoder import CLIPTextEncoder + +# download from +# https://github.com/facebookresearch/Detic/tree/main/datasets/metadata +DATASET_EMBEDDINGS = { + 'lvis': 'datasets/metadata/lvis_v1_clip_a+cname.npy', + 'objects365': 'datasets/metadata/o365_clip_a+cnamefix.npy', + 'openimages': 'datasets/metadata/oid_clip_a+cname.npy', + 'coco': 'datasets/metadata/coco_clip_a+cname.npy', +} + + +def get_text_embeddings(dataset=None, + custom_vocabulary=None, + prompt_prefix='a '): + assert (dataset is None) ^ (custom_vocabulary is None), \ + 'Either `dataset` or `custom_vocabulary` should be specified.' + if dataset: + if dataset in DATASET_EMBEDDINGS: + return DATASET_EMBEDDINGS[dataset] + else: + custom_vocabulary = get_class_names(dataset) + + text_encoder = CLIPTextEncoder() + text_encoder.eval() + texts = [prompt_prefix + x for x in custom_vocabulary] + print_log( + f'Computing text embeddings for {len(custom_vocabulary)} classes.') + embeddings = text_encoder(texts).detach().permute(1, 0).contiguous().cpu() + return embeddings + + +def get_class_names(dataset): + if dataset == 'coco': + from mmdet.datasets import CocoDataset + class_names = CocoDataset.METAINFO['classes'] + elif dataset == 'cityscapes': + from mmdet.datasets import CityscapesDataset + class_names = CityscapesDataset.METAINFO['classes'] + elif dataset == 'voc': + from mmdet.datasets import VOCDataset + class_names = VOCDataset.METAINFO['classes'] + elif dataset == 'openimages': + from mmdet.datasets import OpenImagesDataset + class_names = OpenImagesDataset.METAINFO['classes'] + elif dataset == 'lvis': + from mmdet.datasets import LVISV1Dataset + class_names = LVISV1Dataset.METAINFO['classes'] + else: + raise TypeError(f'Invalid type for dataset name: {type(dataset)}') + return class_names + + +def reset_cls_layer_weight(model, weight): + if type(weight) == str: + print_log(f'Resetting cls_layer_weight from file: {weight}') + zs_weight = torch.tensor( + np.load(weight), + dtype=torch.float32).permute(1, 0).contiguous() # D x C + else: + zs_weight = weight + zs_weight = torch.cat( + [zs_weight, zs_weight.new_zeros( + (zs_weight.shape[0], 1))], dim=1) # D x (C + 1) + zs_weight = F.normalize(zs_weight, p=2, dim=0) + zs_weight = zs_weight.to('cuda') + num_classes = zs_weight.shape[-1] + + for bbox_head in model.roi_head.bbox_head: + bbox_head.num_classes = num_classes + del bbox_head.fc_cls.zs_weight + bbox_head.fc_cls.zs_weight = zs_weight diff --git a/mmdetection/projects/Detic/detic/zero_shot_classifier.py b/mmdetection/projects/Detic/detic/zero_shot_classifier.py new file mode 100644 index 00000000..35c9e492 --- /dev/null +++ b/mmdetection/projects/Detic/detic/zero_shot_classifier.py @@ -0,0 +1,73 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +import numpy as np +import torch +from torch import nn +from torch.nn import functional as F + +from mmdet.registry import MODELS + + +@MODELS.register_module(force=True) # avoid bug +class ZeroShotClassifier(nn.Module): + + def __init__( + self, + in_features: int, + out_features: int, # num_classes + zs_weight_path: str, + zs_weight_dim: int = 512, + use_bias: float = 0.0, + norm_weight: bool = True, + norm_temperature: float = 50.0, + ): + super().__init__() + num_classes = out_features + self.norm_weight = norm_weight + self.norm_temperature = norm_temperature + + self.use_bias = use_bias < 0 + if self.use_bias: + self.cls_bias = nn.Parameter(torch.ones(1) * use_bias) + + self.linear = nn.Linear(in_features, zs_weight_dim) + + if zs_weight_path == 'rand': + zs_weight = torch.randn((zs_weight_dim, num_classes)) + nn.init.normal_(zs_weight, std=0.01) + else: + zs_weight = torch.tensor( + np.load(zs_weight_path), + dtype=torch.float32).permute(1, 0).contiguous() # D x C + zs_weight = torch.cat( + [zs_weight, zs_weight.new_zeros( + (zs_weight_dim, 1))], dim=1) # D x (C + 1) + + if self.norm_weight: + zs_weight = F.normalize(zs_weight, p=2, dim=0) + + if zs_weight_path == 'rand': + self.zs_weight = nn.Parameter(zs_weight) + else: + self.register_buffer('zs_weight', zs_weight) + + assert self.zs_weight.shape[1] == num_classes + 1, self.zs_weight.shape + + def forward(self, x, classifier=None): + ''' + Inputs: + x: B x D' + classifier_info: (C', C' x D) + ''' + x = self.linear(x) + if classifier is not None: + zs_weight = classifier.permute(1, 0).contiguous() # D x C' + zs_weight = F.normalize(zs_weight, p=2, dim=0) \ + if self.norm_weight else zs_weight + else: + zs_weight = self.zs_weight + if self.norm_weight: + x = self.norm_temperature * F.normalize(x, p=2, dim=1) + x = torch.mm(x, zs_weight) + if self.use_bias: + x = x + self.cls_bias + return x diff --git a/mmdetection/projects/Detic_new/README.md b/mmdetection/projects/Detic_new/README.md new file mode 100644 index 00000000..3c7714c3 --- /dev/null +++ b/mmdetection/projects/Detic_new/README.md @@ -0,0 +1,248 @@ +# Detecting Twenty-thousand Classes using Image-level Supervision + +## Description + +**Detic**: A **Det**ector with **i**mage **c**lasses that can use image-level labels to easily train detectors. + +

    + +> [**Detecting Twenty-thousand Classes using Image-level Supervision**](http://arxiv.org/abs/2201.02605), +> Xingyi Zhou, Rohit Girdhar, Armand Joulin, Philipp Krähenbühl, Ishan Misra, +> *ECCV 2022 ([arXiv 2201.02605](http://arxiv.org/abs/2201.02605))* + +## Usage + + + +## Installation + +Detic requires to install CLIP. + +```shell +pip install git+https://github.com/openai/CLIP.git +``` + +## Prepare Datasets + +It is recommended to download and extract the dataset somewhere outside the project directory and symlink the dataset root to `$MMDETECTION/data` as below. If your folder structure is different, you may need to change the corresponding paths in config files. + +### LVIS + +LVIS dataset is adopted as box-labeled data, [LVIS](https://www.lvisdataset.org/) is available from official website or mirror. You need to generate `lvis_v1_train_norare.json` according to the [official prepare datasets](https://github.com/facebookresearch/Detic/blob/main/datasets/README.md#coco-and-lvis) for open-vocabulary LVIS, which removes the labels of 337 rare-class from training. You can also download [lvis_v1_train_norare.json](https://download.openmmlab.com/mmdetection/v3.0/detic/data/lvis/annotations/lvis_v1_train_norare.json) from our backup. The directory should be like this. + +```shell +mmdetection +├── data +│ ├── lvis +│ │ ├── annotations +│ │ | ├── lvis_v1_train.json +│ │ | ├── lvis_v1_val.json +│ │ | ├── lvis_v1_train_norare.json +│ │ ├── train2017 +│ │ ├── val2017 +``` + +### ImageNet-LVIS + +ImageNet-LVIS is adopted as image-labeled data. You can download [ImageNet-21K](https://www.image-net.org/download.php) dataset from the official website. Then you need to unzip the overlapping classes of LVIS and convert them into LVIS annotation format according to the [official prepare datasets](https://github.com/facebookresearch/Detic/blob/main/datasets/README.md#imagenet-21k). The directory should be like this. + +```shell +mmdetection +├── data +│ ├── imagenet +│ │ ├── annotations +│ │ | ├── imagenet_lvis_image_info.json +│ │ ├── ImageNet-21K +│ │ | ├── n00007846 +│ │ | ├── n01318894 +│ │ | ├── ... +``` + +### Metadata + +`data/metadata/` is the preprocessed meta-data (included in the repo). Please follow the [official instruction](https://github.com/facebookresearch/Detic/blob/main/datasets/README.md#metadata) to pre-process the LVIS dataset. You will generate `lvis_v1_train_cat_info.json` for Federated loss, which contains the frequency of each category of training set of LVIS. In addition, `lvis_v1_clip_a+cname.npy` is the pre-computed CLIP embeddings for each category of LVIS. You can also choose to directly download [lvis_v1_train_cat_info](https://download.openmmlab.com/mmdetection/v3.0/detic/data/metadata/lvis_v1_train_cat_info.json) and [lvis_v1_clip_a+cname.npy](https://download.openmmlab.com/mmdetection/v3.0/detic/data/metadata/lvis_v1_clip_a%2Bcname.npy) form our backup. The directory should be like this. + +```shell +mmdetection +├── data +│ ├── metadata +│ │ ├── lvis_v1_train_cat_info.json +│ │ ├── lvis_v1_clip_a+cname.npy +``` + +## Demo + +Here we provide the Detic model for the open vocabulary demo. This model is trained on combined LVIS-COCO and ImageNet-21K for better demo purposes. LVIS models do not detect persons well due to its federated annotation protocol. LVIS+COCO models give better visual results. + +| Backbone | Training data | Config | Download | +| :------: | :----------------------------: | :-------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Swin-B | LVIS & COCO & ImageNet-21K | [config](./configs/detic_centernet2_swin-b_fpn_4x_lvis_coco_in21k.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_swin-b_fpn_4x_lvis-coco-in21k/detic_centernet2_swin-b_fpn_4x_lvis-coco-in21k_20230120-0d301978.pth) | + +You can also download other models from [official model zoo](https://github.com/facebookresearch/Detic/blob/main/docs/MODEL_ZOO.md), and convert the format by run + +```shell +python tools/model_converters/detic_to_mmdet.py --src /path/to/detic_weight.pth --dst /path/to/mmdet_weight.pth +``` + +### Inference with existing dataset vocabulary + +You can detect classes of existing dataset with `--texts` command: + +```shell +python demo/image_demo.py \ + ${IMAGE_PATH} \ + ${CONFIG_PATH} \ + ${MODEL_PATH} \ + --texts lvis \ + --pred-score-thr 0.5 \ + --palette 'random' +``` + +![image](https://user-images.githubusercontent.com/12907710/213624759-f0a2ba0c-0f5c-4424-a350-5ba5349e5842.png) + +### Inference with custom vocabularies + +Detic can detects any class given class names by using CLIP. You can detect customized classes with `--texts` command: + +```shell +python demo/image_demo.py \ + ${IMAGE_PATH} \ + ${CONFIG_PATH} \ + ${MODEL_PATH} \ + --texts 'headphone . webcam . paper . coffe.' \ + --pred-score-thr 0.3 \ + --palette 'random' +``` + +![image](https://user-images.githubusercontent.com/12907710/213624637-e9e8a313-9821-4782-a18a-4408c876852b.png) + +Note that `headphone`, `paper` and `coffe` (typo intended) are not LVIS classes. Despite the misspelled class name, Detic can produce a reasonable detection for `coffe`. + +## Models and Results + +### Training + +There are two stages in the whole training process. The first stage is to train a model using images with box labels as the baseline. The second stage is to finetune from the baseline model and leverage image-labeled data. + +#### First stage + +To train the baseline with box-supervised, run + +```shell +bash ./tools/dist_train.sh projects/Detic_new/detic_centernet2_r50_fpn_4x_lvis_boxsup.py 8 +``` + +| Model (Config) | mask mAP | mask mAP(official) | mask mAP_rare | mask mAP_rare(officical) | +| :---------------------------------------------------------------------------------------------: | :------: | :----------------: | :-----------: | :----------------------: | +| [detic_centernet2_r50_fpn_4x_lvis_boxsup](./configs/detic_centernet2_r50_fpn_4x_lvis_boxsup.py) | 31.6 | 31.5 | 26.6 | 25.6 | + +#### Second stage + +The second stage uses both object detection and image classification datasets. + +##### Multi-Datasets Config + +We provide improved dataset_wrapper `ConcatDataset` to concatenate multiple datasets, all datasets could have different annotation types and different pipelines (e.g., image_size). You can also obtain the index of `dataset_source` for each sample through ` get_dataset_source` . We provide sampler `MultiDataSampler` to custom the ratios of different datasets. Beside, we provide batch_sampler `MultiDataAspectRatioBatchSampler` to enable different datasets to have different batchsizes. The config of multiple datasets is as follows: + +```python +dataset_det = dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='LVISV1Dataset', + data_root='data/lvis/', + ann_file='annotations/lvis_v1_train.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline_det, + backend_args=backend_args)) + +dataset_cls = dict( + type='ImageNetLVISV1Dataset', + data_root='data/imagenet', + ann_file='annotations/imagenet_lvis_image_info.json', + data_prefix=dict(img='ImageNet-LVIS/'), + pipeline=train_pipeline_cls, + backend_args=backend_args) + +train_dataloader = dict( + batch_size=[8, 32], + num_workers=2, + persistent_workers=True, + sampler=dict( + type='MultiDataSampler', + dataset_ratio=[1, 4]), + batch_sampler=dict( + type='MultiDataAspectRatioBatchSampler', + num_datasets=2), + dataset=dict( + type='ConcatDataset', + datasets=[dataset_det, dataset_cls])) +``` + +###### Note: + +- If the one of the multiple datasets is `ConcatDataset` , it is still considered as a dataset for `num_datasets` in `MultiDataAspectRatioBatchSampler`. + +To finetune the baseline model with image-labeled data, run: + +```shell +bash ./tools/dist_train.sh projects/Detic_new/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis.py 8 +``` + +| Model (Config) | mask mAP | mask mAP(official) | mask mAP_rare | mask mAP_rare(officical) | +| :-----------------------------------------------------------------------------------------------------: | :------: | :----------------: | :-----------: | :----------------------: | +| [detic_centernet2_r50_fpn_4x_lvis_in21k-lvis](./configs/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis.py) | 32.9 | 33.2 | 30.9 | 29.7 | + +#### Standard LVIS Results + +| Model (Config) | mask mAP | mask mAP(official) | mask mAP_rare | mask mAP_rare(officical) | Download | +| :-----------------------------------------------------------------------------------------------------------: | :------: | :----------------: | :-----------: | :----------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [detic_centernet2_r50_fpn_4x_lvis_boxsup](./configs/detic_centernet2_r50_fpn_4x_lvis_boxsup.py) | 31.6 | 31.5 | 26.6 | 25.6 | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_r50_fpn_4x_lvis_boxsup/detic_centernet2_r50_fpn_4x_lvis_boxsup_20230911_233514-54116677.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_r50_fpn_4x_lvis_boxsup/detic_centernet2_r50_fpn_4x_lvis_boxsup_20230911_233514.log.json) | +| [detic_centernet2_r50_fpn_4x_lvis_in21k-lvis](./configs/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis.py) | 32.9 | 33.2 | 30.9 | 29.7 | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis_20230912_040619-9e7a3258.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis_20230912_040619.log.json) | +| [detic_centernet2_swin-b_fpn_4x_lvis_boxsup](./configs/detic_centernet2_swin-b_fpn_4x_lvis_boxsup.py) | 40.7 | 40.7 | 38.0 | 35.9 | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_swin-b_fpn_4x_lvis_boxsup/detic_centernet2_swin-b_fpn_4x_lvis_boxsup_20230825_061737-328e85f9.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_swin-b_fpn_4x_lvis_boxsup/detic_centernet2_swin-b_fpn_4x_lvis_boxsup_20230825_061737.log.json) | +| [detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis](./configs/detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis.py) | 41.7 | 41.7 | 41.7 | 41.7 | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis/detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis_20230926_235410-0c152391.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis/detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis_20230926_235410.log.json) | + +#### Open-vocabulary LVIS Results + +| Model (Config) | mask mAP | mask mAP(official) | mask mAP_rare | mask mAP_rare(officical) | Download | +| :---------------------------------------------------------------------------------------------------------------: | :------: | :----------------: | :-----------: | :----------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [detic_centernet2_r50_fpn_4x_lvis-base_boxsup](./configs/detic_centernet2_r50_fpn_4x_lvis-base_boxsup.py) | 30.4 | 30.2 | 16.2 | 16.4 | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_r50_fpn_4x_lvis-base_boxsup/detic_centernet2_r50_fpn_4x_lvis-base_boxsup_20230921_180638-c1685ee2.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_r50_fpn_4x_lvis-base_boxsup/detic_centernet2_r50_fpn_4x_lvis-base_boxsup_20230921_180638.log.json) | +| [detic_centernet2_r50_fpn_4x_lvis-base_in21k-lvis](./configs/detic_centernet2_r50_fpn_4x_lvis-base_in21k-lvis.py) | 32.6 | 32.4 | 27.4 | 24.9 | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_r50_fpn_4x_lvis-base_in21k-lvis/detic_centernet2_r50_fpn_4x_lvis-base_in21k-lvis_20230925_014315-2d2cc8b7.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_r50_fpn_4x_lvis-base_in21k-lvis/detic_centernet2_r50_fpn_4x_lvis-base_in21k-lvis_20230925_014315.log.json) | + +### Testing + +#### Test Command + +To evaluate a model with a trained model, run + +```shell +python ./tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} +``` + +#### Open-vocabulary LVIS Results + +The models are converted from the official model zoo. + +| Model (Config) | mask mAP | mask mAP_novel | Download | +| :---------------------------------------------------------------------------------------------------------------------: | :------: | :------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [detic_centernet2_swin-b_fpn_4x_lvis-base_boxsup](./configs/detic_centernet2_swin-b_fpn_4x_lvis-base_boxsup.py) | 38.4 | 21.9 | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_swin-b_fpn_4x_lvis-base_boxsup/detic_centernet2_swin-b_fpn_4x_lvis-base_boxsup-481281c8.pth) | +| [detic_centernet2_swin-b_fpn_4x_lvis-base_in21k-lvis](./configs/detic_centernet2_swin-b_fpn_4x_lvis-base_in21k-lvis.py) | 40.7 | 34.0 | [model](https://download.openmmlab.com/mmdetection/v3.0/detic/detic_centernet2_swin-b_fpn_4x_lvis-base_in21k-lvis/detic_centernet2_swin-b_fpn_4x_lvis-base_in21k-lvis-ec91245d.pth) | + +###### Note: + +- The open-vocabulary LVIS setup is LVIS without rare class annotations in training, termed `lvisbase`. We evaluate rare classes as novel classes in testing. +- ` in21k-lvis` denotes that the model use the overlap classes between ImageNet-21K and LVIS as image-labeled data. + +## Citation + +If you find Detic is useful in your research or applications, please consider giving a star 🌟 to the [official repository](https://github.com/facebookresearch/Detic) and citing Detic by the following BibTeX entry. + +```BibTeX +@inproceedings{zhou2022detecting, + title={Detecting Twenty-thousand Classes using Image-level Supervision}, + author={Zhou, Xingyi and Girdhar, Rohit and Joulin, Armand and Kr{\"a}henb{\"u}hl, Philipp and Misra, Ishan}, + booktitle={ECCV}, + year={2022} +} +``` diff --git a/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis-base_boxsup.py b/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis-base_boxsup.py new file mode 100644 index 00000000..8ca57b77 --- /dev/null +++ b/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis-base_boxsup.py @@ -0,0 +1,9 @@ +_base_ = './detic_centernet2_r50_fpn_4x_lvis_boxsup.py' + +# 'lvis_v1_train_norare.json' is the annotations of lvis_v1 +# removing the labels of 337 rare-class +train_dataloader = dict( + dataset=dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict(ann_file='annotations/lvis_v1_train_norare.json'))) diff --git a/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis-base_in21k-lvis.py b/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis-base_in21k-lvis.py new file mode 100644 index 00000000..034acb6e --- /dev/null +++ b/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis-base_in21k-lvis.py @@ -0,0 +1,93 @@ +_base_ = './detic_centernet2_r50_fpn_4x_lvis_boxsup.py' +dataset_type = ['LVISV1Dataset', 'ImageNetLVISV1Dataset'] +image_size_det = (640, 640) +image_size_cls = (320, 320) + +# backend = 'pillow' +backend_args = None + +train_pipeline_det = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size_det, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size_det, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_pipeline_cls = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=False, with_label=True), + dict( + type='RandomResize', + scale=image_size_cls, + ratio_range=(0.5, 1.5), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size_cls, + recompute_bbox=False, + bbox_clip_border=False, + allow_negative_crop=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +# 'lvis_v1_train_norare.json' is the annotations of lvis_v1 +# removing the labels of 337 rare-class +dataset_det = dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='LVISV1Dataset', + data_root='data/lvis/', + ann_file='annotations/lvis_v1_train_norare.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline_det, + backend_args=backend_args)) + +dataset_cls = dict( + type='ImageNetLVISV1Dataset', + data_root='data/imagenet', + ann_file='annotations/imagenet_lvis_image_info.json', + data_prefix=dict(img='ImageNet-LVIS/'), + pipeline=train_pipeline_cls, + backend_args=backend_args) + +train_dataloader = dict( + _delete_=True, + batch_size=[8, 32], + num_workers=2, + persistent_workers=True, + sampler=dict(type='MultiDataSampler', dataset_ratio=[1, 4]), + batch_sampler=dict( + type='MultiDataAspectRatioBatchSampler', num_datasets=2), + dataset=dict(type='ConcatDataset', datasets=[dataset_det, dataset_cls])) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='CosineAnnealingLR', + begin=0, + by_epoch=False, + T_max=90000, + ) +] + +load_from = './first_stage/detic_centernet2_r50_fpn_4x_lvis-base_boxsup.pth' + +find_unused_parameters = True diff --git a/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis_boxsup.py b/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis_boxsup.py new file mode 100644 index 00000000..a11be374 --- /dev/null +++ b/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis_boxsup.py @@ -0,0 +1,410 @@ +_base_ = 'mmdet::_base_/default_runtime.py' +dataset_type = 'LVISV1Dataset' +custom_imports = dict( + imports=['projects.Detic_new.detic'], allow_failed_imports=False) + +num_classes = 1203 +lvis_cat_frequency_info = 'data/metadata/lvis_v1_train_cat_info.json' + +# 'data/metadata/lvis_v1_clip_a+cname.npy' is pre-computed +# CLIP embeddings for each category +cls_layer = dict( + type='ZeroShotClassifier', + zs_weight_path='data/metadata/lvis_v1_clip_a+cname.npy', + zs_weight_dim=512, + use_bias=0.0, + norm_weight=True, + norm_temperature=50.0) +reg_layer = [ + dict(type='Linear', in_features=1024, out_features=1024), + dict(type='ReLU', inplace=True), + dict(type='Linear', in_features=1024, out_features=4) +] + +model = dict( + type='Detic', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(1, 2, 3), + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + init_cfg=dict( + type='Pretrained', + checkpoint='https://miil-public-eu.oss-eu-central-1.aliyuncs.com/' + 'model-zoo/ImageNet_21K_P/models/resnet50_miil_21k.pth')), + neck=dict( + type='FPN', + in_channels=[512, 1024, 2048], + out_channels=256, + start_level=0, + add_extra_convs='on_output', + num_outs=5, + init_cfg=dict(type='Caffe2Xavier', layer='Conv2d'), + relu_before_extra_convs=True), + rpn_head=dict( + type='CenterNetRPNHead', + num_classes=1, + in_channels=256, + stacked_convs=4, + feat_channels=256, + strides=[8, 16, 32, 64, 128], + conv_bias=True, + norm_cfg=dict(type='GN', num_groups=32, requires_grad=True), + loss_cls=dict( + type='HeatmapFocalLoss', + alpha=0.25, + beta=4.0, + gamma=2.0, + pos_weight=0.5, + neg_weight=0.5, + loss_weight=1.0, + ignore_high_fp=0.85, + ), + loss_bbox=dict(type='GIoULoss', eps=1e-6, loss_weight=1.0), + ), + roi_head=dict( + type='DeticRoIHead', + num_stages=3, + stage_loss_weights=[1.0, 1.0, 1.0], + bbox_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', + output_size=7, + sampling_ratio=0, + use_torchvision=True), + out_channels=256, + featmap_strides=[8, 16, 32], + # approximately equal to + # canonical_box_size=224, canonical_level=4 in D2 + finest_scale=112), + bbox_head=[ + dict( + type='DeticBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + cls_predictor_cfg=cls_layer, + reg_predictor_cfg=reg_layer, + use_fed_loss=True, + cat_freq_path=lvis_cat_frequency_info, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.1, 0.1, 0.2, 0.2]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=0.1, + loss_weight=1.0)), + dict( + type='DeticBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + cls_predictor_cfg=cls_layer, + reg_predictor_cfg=reg_layer, + use_fed_loss=True, + cat_freq_path=lvis_cat_frequency_info, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.05, 0.05, 0.1, 0.1]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=0.1, + loss_weight=1.0)), + dict( + type='DeticBBoxHead', + in_channels=256, + fc_out_channels=1024, + roi_feat_size=7, + num_classes=num_classes, + cls_predictor_cfg=cls_layer, + reg_predictor_cfg=reg_layer, + use_fed_loss=True, + cat_freq_path=lvis_cat_frequency_info, + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[0., 0., 0., 0.], + target_stds=[0.033, 0.033, 0.067, 0.067]), + reg_class_agnostic=True, + loss_cls=dict( + type='CrossEntropyLoss', use_sigmoid=True, + loss_weight=1.0), + loss_bbox=dict(type='SmoothL1Loss', beta=0.1, loss_weight=1.0)) + ], + mask_roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0), + out_channels=256, + featmap_strides=[8, 16, 32], + # approximately equal to + # canonical_box_size=224, canonical_level=4 in D2 + finest_scale=112), + mask_head=dict( + type='FCNMaskHead', + num_convs=4, + in_channels=256, + conv_out_channels=256, + class_agnostic=True, + num_classes=num_classes, + loss_mask=dict( + type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))), + # model training and testing settings + train_cfg=dict( + rpn=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.3, + min_pos_iou=0.3, + match_low_quality=True, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=256, + pos_fraction=0.5, + neg_pos_ub=-1, + add_gt_as_proposals=False), + allowed_border=0, + pos_weight=-1, + debug=False), + rpn_proposal=dict( + score_thr=0.0001, + nms_pre=4000, + max_per_img=2000, + nms=dict(type='nms', iou_threshold=0.9), + min_bbox_size=0), + rcnn=[ + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.6, + neg_iou_thr=0.6, + min_pos_iou=0.6, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=True), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.7, + neg_iou_thr=0.7, + min_pos_iou=0.7, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=False), + mask_size=28, + pos_weight=-1, + debug=False), + dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.8, + neg_iou_thr=0.8, + min_pos_iou=0.8, + match_low_quality=False, + ignore_iof_thr=-1), + sampler=dict( + type='RandomSampler', + num=512, + pos_fraction=0.25, + neg_pos_ub=-1, + add_gt_as_proposals=False), + mask_size=28, + pos_weight=-1, + debug=False) + ]), + test_cfg=dict( + rpn=dict( + score_thr=0.0001, + nms_pre=1000, + max_per_img=256, + nms=dict(type='nms', iou_threshold=0.9), + min_bbox_size=0), + rcnn=dict( + score_thr=0.02, + nms=dict(type='nms', iou_threshold=0.5), + max_per_img=300, + mask_thr_binary=0.5))) + +# backend = 'pillow' +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=(640, 640), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(640, 640), + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=backend_args, + imdecode_backend=backend_args), + dict( + type='Resize', + scale=(1333, 800), + keep_ratio=True, + backend=backend_args), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor', 'text', 'custom_entities')) +] + +val_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=backend_args, + imdecode_backend=backend_args), + dict( + type='Resize', + scale=(1333, 800), + keep_ratio=True, + backend=backend_args), + dict( + type='LoadAnnotations', + with_bbox=True, + with_mask=True, + poly2mask=False), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=8, + num_workers=2, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + batch_sampler=dict(type='AspectRatioBatchSampler'), + dataset=dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='LVISV1Dataset', + data_root='data/lvis/', + ann_file='annotations/lvis_v1_train_norare.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline, + backend_args=backend_args))) + +val_dataloader = dict( + batch_size=8, + num_workers=2, + persistent_workers=True, + drop_last=False, + pin_memory=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type='LVISV1Dataset', + data_root='data/lvis/', + ann_file='annotations/lvis_v1_val.json', + data_prefix=dict(img=''), + pipeline=val_pipeline, + return_classes=False)) + +test_dataloader = dict( + batch_size=8, + num_workers=2, + persistent_workers=True, + drop_last=False, + pin_memory=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type='LVISV1Dataset', + data_root='data/lvis/', + ann_file='annotations/lvis_v1_val.json', + data_prefix=dict(img=''), + pipeline=test_pipeline, + return_classes=True)) + +val_evaluator = dict( + type='LVISMetric', + ann_file='data/lvis/annotations/lvis_v1_val.json', + metric=['bbox', 'segm']) +test_evaluator = val_evaluator + +# training schedule for 90k with batch_size of 64 +# with total batch_size of 16, 90k iters is equivalent to '1x' (12 epochs) +# with total batch_size of 64, 90k iters is equivalent to '4x' +max_iter = 90000 +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iter, val_interval=90000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.0001), + paramwise_cfg=dict(norm_decay_mult=0.), + clip_grad=dict(max_norm=1.0, norm_type=2)) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.0001, + by_epoch=False, + begin=0, + end=10000), + dict( + type='CosineAnnealingLR', + begin=0, + by_epoch=False, + T_max=max_iter, + ) +] + +# only keep latest 5 checkpoints +default_hooks = dict( + checkpoint=dict(by_epoch=False, interval=30000, max_keep_ckpts=5), + logger=dict(type='LoggerHook', interval=50)) diff --git a/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis.py b/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis.py new file mode 100644 index 00000000..ce97ed6d --- /dev/null +++ b/mmdetection/projects/Detic_new/configs/detic_centernet2_r50_fpn_4x_lvis_in21k-lvis.py @@ -0,0 +1,91 @@ +_base_ = './detic_centernet2_r50_fpn_4x_lvis_boxsup.py' +dataset_type = ['LVISV1Dataset', 'ImageNetLVISV1Dataset'] +image_size_det = (640, 640) +image_size_cls = (320, 320) + +# backend = 'pillow' +backend_args = None + +train_pipeline_det = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size_det, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size_det, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_pipeline_cls = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=False, with_label=True), + dict( + type='RandomResize', + scale=image_size_cls, + ratio_range=(0.5, 1.5), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size_cls, + recompute_bbox=False, + bbox_clip_border=False, + allow_negative_crop=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +dataset_det = dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='LVISV1Dataset', + data_root='data/lvis/', + ann_file='annotations/lvis_v1_train.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline_det, + backend_args=backend_args)) + +dataset_cls = dict( + type='ImageNetLVISV1Dataset', + data_root='data/imagenet', + ann_file='annotations/imagenet_lvis_image_info.json', + data_prefix=dict(img='ImageNet-LVIS/'), + pipeline=train_pipeline_cls, + backend_args=backend_args) + +train_dataloader = dict( + _delete_=True, + batch_size=[8, 32], + num_workers=2, + persistent_workers=True, + sampler=dict(type='MultiDataSampler', dataset_ratio=[1, 4]), + batch_sampler=dict( + type='MultiDataAspectRatioBatchSampler', num_datasets=2), + dataset=dict(type='ConcatDataset', datasets=[dataset_det, dataset_cls])) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='CosineAnnealingLR', + begin=0, + by_epoch=False, + T_max=90000, + ) +] + +load_from = './first_stage/detic_centernet2_r50_fpn_4x_lvis_boxsup.pth' + +find_unused_parameters = True diff --git a/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis-base_boxsup.py b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis-base_boxsup.py new file mode 100644 index 00000000..efedd111 --- /dev/null +++ b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis-base_boxsup.py @@ -0,0 +1,9 @@ +_base_ = './detic_centernet2_swin-b_fpn_4x_lvis_boxsup.py' + +# 'lvis_v1_train_norare.json' is the annotations of lvis_v1 +# removing the labels of 337 rare-class +train_dataloader = dict( + dataset=dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict(ann_file='annotations/lvis_v1_train_norare.json'))) diff --git a/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis-base_in21k-lvis.py b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis-base_in21k-lvis.py new file mode 100644 index 00000000..1df70970 --- /dev/null +++ b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis-base_in21k-lvis.py @@ -0,0 +1,118 @@ +_base_ = './detic_centernet2_r50_fpn_4x_lvis_in21k-lvis.py' + +image_size_det = (896, 896) +image_size_cls = (448, 448) + +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False), + neck=dict(in_channels=[256, 512, 1024])) + +backend_args = None +train_pipeline_det = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size_det, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size_det, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_pipeline_cls = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=False, with_label=True), + dict( + type='RandomResize', + scale=image_size_cls, + ratio_range=(0.5, 1.5), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size_cls, + recompute_bbox=False, + bbox_clip_border=False, + allow_negative_crop=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +# 'lvis_v1_train_norare.json' is the annotations of lvis_v1 +# removing the labels of 337 rare-class +dataset_det = dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='LVISV1Dataset', + data_root='data/lvis/', + ann_file='annotations/lvis_v1_train_norare.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline_det, + backend_args=backend_args)) + +dataset_cls = dict( + type='ImageNetLVISV1Dataset', + data_root='data/imagenet', + ann_file='annotations/imagenet_lvis_image_info.json', + data_prefix=dict(img='ImageNet-LVIS/'), + pipeline=train_pipeline_cls, + backend_args=backend_args) + +train_dataloader = dict( + _delete_=True, + batch_size=[4, 16], + num_workers=2, + persistent_workers=True, + sampler=dict(type='MultiDataSampler', dataset_ratio=[1, 4]), + batch_sampler=dict( + type='MultiDataAspectRatioBatchSampler', num_datasets=2), + dataset=dict(type='ConcatDataset', datasets=[dataset_det, dataset_cls])) + +# training schedule for 180k +max_iter = 180000 +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iter, val_interval=180000) + +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001)) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='CosineAnnealingLR', + begin=0, + by_epoch=False, + T_max=max_iter, + ) +] + +load_from = './first_stage/detic_centernet2_swin-b_fpn_4x_lvis-base_boxsup.pth' +find_unused_parameters = True diff --git a/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis_boxsup.py b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis_boxsup.py new file mode 100644 index 00000000..ce04a815 --- /dev/null +++ b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis_boxsup.py @@ -0,0 +1,78 @@ +_base_ = './detic_centernet2_r50_fpn_4x_lvis_boxsup.py' + +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False, + convert_weights=True, + init_cfg=dict( + type='Pretrained', + checkpoint='https://github.com/SwinTransformer/storage/releases/' + 'download/v1.0.0/swin_base_patch4_window7_224_22k.pth')), + neck=dict(in_channels=[256, 512, 1024])) + +# backend = 'pillow' +backend_args = None + +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=(896, 896), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(896, 896), + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_dataloader = dict( + dataset=dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict(pipeline=train_pipeline))) + +# training schedule for 180k +max_iter = 180000 +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iter, val_interval=180000) + +# Enable automatic-mixed-precision training with AmpOptimWrapper. +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001)) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=0.0001, + by_epoch=False, + begin=0, + end=10000), + dict( + type='CosineAnnealingLR', + begin=0, + by_epoch=False, + T_max=max_iter, + ) +] diff --git a/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis_coco_in21k.py b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis_coco_in21k.py new file mode 100644 index 00000000..a9ab2c69 --- /dev/null +++ b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis_coco_in21k.py @@ -0,0 +1,2 @@ +# not support training, only for testing +_base_ = './detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis.py' diff --git a/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis.py b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis.py new file mode 100644 index 00000000..de358ac3 --- /dev/null +++ b/mmdetection/projects/Detic_new/configs/detic_centernet2_swin-b_fpn_4x_lvis_in21k-lvis.py @@ -0,0 +1,116 @@ +_base_ = './detic_centernet2_r50_fpn_4x_lvis_in21k-lvis.py' + +image_size_det = (896, 896) +image_size_cls = (448, 448) + +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(1, 2, 3), + with_cp=False), + neck=dict(in_channels=[256, 512, 1024])) + +backend_args = None +train_pipeline_det = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True, with_mask=True), + dict( + type='RandomResize', + scale=image_size_det, + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size_det, + recompute_bbox=True, + allow_negative_crop=True), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1e-2, 1e-2)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +train_pipeline_cls = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=False, with_label=True), + dict( + type='RandomResize', + scale=image_size_cls, + ratio_range=(0.5, 1.5), + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=image_size_cls, + recompute_bbox=False, + bbox_clip_border=False, + allow_negative_crop=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] + +dataset_det = dict( + type='ClassBalancedDataset', + oversample_thr=1e-3, + dataset=dict( + type='LVISV1Dataset', + data_root='data/lvis/', + ann_file='annotations/lvis_v1_train.json', + data_prefix=dict(img=''), + filter_cfg=dict(filter_empty_gt=True, min_size=32), + pipeline=train_pipeline_det, + backend_args=backend_args)) + +dataset_cls = dict( + type='ImageNetLVISV1Dataset', + data_root='data/imagenet', + ann_file='annotations/imagenet_lvis_image_info.json', + data_prefix=dict(img='ImageNet-LVIS/'), + pipeline=train_pipeline_cls, + backend_args=backend_args) + +train_dataloader = dict( + _delete_=True, + batch_size=[4, 16], + num_workers=2, + persistent_workers=True, + sampler=dict(type='MultiDataSampler', dataset_ratio=[1, 4]), + batch_sampler=dict( + type='MultiDataAspectRatioBatchSampler', num_datasets=2), + dataset=dict(type='ConcatDataset', datasets=[dataset_det, dataset_cls])) + +# training schedule for 180k +max_iter = 180000 +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iter, val_interval=180000) + +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001)) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='CosineAnnealingLR', + begin=0, + by_epoch=False, + T_max=max_iter, + ) +] + +load_from = './first_stage/detic_centernet2_swin-b_fpn_4x_lvis_boxsup.pth' +find_unused_parameters = True diff --git a/mmdetection/projects/Detic_new/detic/__init__.py b/mmdetection/projects/Detic_new/detic/__init__.py new file mode 100644 index 00000000..e4b0d7bb --- /dev/null +++ b/mmdetection/projects/Detic_new/detic/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .centernet_rpn_head import CenterNetRPNHead +from .detic import Detic +from .detic_bbox_head import DeticBBoxHead +from .detic_roi_head import DeticRoIHead +from .heatmap_focal_loss import HeatmapFocalLoss +from .imagenet_lvis import ImageNetLVISV1Dataset +from .zero_shot_classifier import ZeroShotClassifier + +__all__ = [ + 'CenterNetRPNHead', 'Detic', 'DeticBBoxHead', 'DeticRoIHead', + 'ZeroShotClassifier', 'HeatmapFocalLoss', 'ImageNetLVISV1Dataset' +] diff --git a/mmdetection/projects/Detic_new/detic/centernet_rpn_head.py b/mmdetection/projects/Detic_new/detic/centernet_rpn_head.py new file mode 100644 index 00000000..62987282 --- /dev/null +++ b/mmdetection/projects/Detic_new/detic/centernet_rpn_head.py @@ -0,0 +1,573 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import Dict, List, Optional, Sequence, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import Scale +from mmengine import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.dense_heads import CenterNetUpdateHead +from mmdet.models.utils import unpack_gt_instances +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2distance +from mmdet.utils import (ConfigType, InstanceList, OptConfigType, + OptInstanceList, reduce_mean) +from .iou_loss import IOULoss + +# from .heatmap_focal_loss import binary_heatmap_focal_loss_jit +INF = 1000000000 +RangeType = Sequence[Tuple[int, int]] + + +@MODELS.register_module() +class CenterNetRPNHead(CenterNetUpdateHead): + """CenterNetUpdateHead is an improved version of CenterNet in CenterNet2. + + Paper link ``_. + Args: + num_classes (int): Number of categories excluding the background + category. + in_channels (int): Number of channel in the input feature map. + regress_ranges (Sequence[Tuple[int, int]]): Regress range of multiple + level points. + hm_min_radius (int): Heatmap target minimum radius of cls branch. + Defaults to 4. + hm_min_overlap (float): Heatmap target minimum overlap of cls branch. + Defaults to 0.8. + more_pos_thresh (float): The filtering threshold when the cls branch + adds more positive samples. Defaults to 0.2. + more_pos_topk (int): The maximum number of additional positive samples + added to each gt. Defaults to 9. + soft_weight_on_reg (bool): Whether to use the soft target of the + cls branch as the soft weight of the bbox branch. + Defaults to False. + loss_cls (:obj:`ConfigDict` or dict): Config of cls loss. Defaults to + dict(type='GaussianFocalLoss', loss_weight=1.0) + loss_bbox (:obj:`ConfigDict` or dict): Config of bbox loss. Defaults to + dict(type='GIoULoss', loss_weight=2.0). + norm_cfg (:obj:`ConfigDict` or dict, optional): dictionary to construct + and config norm layer. Defaults to + ``norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)``. + train_cfg (:obj:`ConfigDict` or dict, optional): Training config. + Unused in CenterNet. Reserved for compatibility with + SingleStageDetector. + test_cfg (:obj:`ConfigDict` or dict, optional): Testing config + of CenterNet. + """ + + def __init__(self, + num_classes: int, + in_channels: int, + regress_ranges: RangeType = ((0, 80), (64, 160), (128, 320), + (256, 640), (512, INF)), + hm_min_radius: int = 4, + hm_min_overlap: float = 0.8, + more_pos: bool = False, + more_pos_thresh: float = 0.2, + more_pos_topk: int = 9, + soft_weight_on_reg: bool = False, + not_clamp_box: bool = False, + loss_cls: ConfigType = dict( + type='HeatmapFocalLoss', + alpha=0.25, + beta=4.0, + gamma=2.0, + pos_weight=1.0, + neg_weight=1.0, + sigmoid_clamp=1e-4, + ignore_high_fp=-1.0, + loss_weight=1.0, + ), + loss_bbox: ConfigType = dict( + type='GIoULoss', loss_weight=2.0), + norm_cfg: OptConfigType = dict( + type='GN', num_groups=32, requires_grad=True), + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + **kwargs) -> None: + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + # loss_bbox=loss_bbox, + loss_cls=loss_cls, + norm_cfg=norm_cfg, + train_cfg=train_cfg, + test_cfg=test_cfg, + **kwargs) + self.soft_weight_on_reg = soft_weight_on_reg + self.hm_min_radius = hm_min_radius + self.more_pos_thresh = more_pos_thresh + self.more_pos_topk = more_pos_topk + self.more_pos = more_pos + self.not_clamp_box = not_clamp_box + self.delta = (1 - hm_min_overlap) / (1 + hm_min_overlap) + self.loss_bbox = IOULoss('giou') + + # GaussianFocalLoss must be sigmoid mode + self.use_sigmoid_cls = True + self.cls_out_channels = num_classes + + self.regress_ranges = regress_ranges + self.scales = nn.ModuleList([Scale(1.0) for _ in self.strides]) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self._init_reg_convs() + self._init_predictor() + + def forward_single(self, x: Tensor, scale: Scale, + stride: int) -> Tuple[Tensor, Tensor]: + """Forward features of a single scale level. + + Args: + x (Tensor): FPN feature maps of the specified stride. + scale (:obj:`mmcv.cnn.Scale`): Learnable scale module to resize + the bbox prediction. + stride (int): The corresponding stride for feature maps. + + Returns: + tuple: scores for each class, bbox predictions of + input feature maps. + """ + for m in self.reg_convs: + x = m(x) + cls_score = self.conv_cls(x) + bbox_pred = self.conv_reg(x) + # scale the bbox_pred of different level + # float to avoid overflow when enabling FP16 + bbox_pred = scale(bbox_pred).float() + # bbox_pred needed for gradient computation has been modified + # by F.relu(bbox_pred) when run with PyTorch 1.10. So replace + # F.relu(bbox_pred) with bbox_pred.clamp(min=0) + bbox_pred = bbox_pred.clamp(min=0) + return cls_score, bbox_pred # score aligned, box larger + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level, + each is a 4D-tensor, the channel number is num_classes. + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level, each is a 4D-tensor, the channel number is 4. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + + num_imgs = cls_scores[0].size(0) + assert len(cls_scores) == len(bbox_preds) + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + all_level_points = self.prior_generator.grid_priors( + featmap_sizes, + dtype=bbox_preds[0].dtype, + device=bbox_preds[0].device) + + # 1 flatten outputs + flatten_cls_scores = [ + cls_score.permute(0, 2, 3, 1).reshape(-1, self.cls_out_channels) + for cls_score in cls_scores + ] + flatten_bbox_preds = [ + bbox_pred.permute(0, 2, 3, 1).reshape(-1, 4) + for bbox_pred in bbox_preds + ] + flatten_cls_scores = torch.cat(flatten_cls_scores) + flatten_bbox_preds = torch.cat(flatten_bbox_preds) + + # repeat points to align with bbox_preds + flatten_points = torch.cat( + [points.repeat(num_imgs, 1) for points in all_level_points]) + + assert (torch.isfinite(flatten_bbox_preds).all().item()) + + # 2 calc reg and cls branch targets + cls_targets, bbox_targets = self.get_targets(all_level_points, + batch_gt_instances) + + # 3 pos index for cls branch + featmap_sizes = flatten_points.new_tensor(featmap_sizes) + + if self.more_pos: + pos_inds, cls_labels = self.add_cls_pos_inds( + flatten_points, flatten_bbox_preds, featmap_sizes, + batch_gt_instances) + else: + pos_inds = self._get_label_inds(batch_gt_instances, + batch_img_metas, featmap_sizes) + + # 4 calc cls loss + if pos_inds is None: + # num_gts=0 + num_pos_cls = bbox_preds[0].new_tensor(0, dtype=torch.float) + else: + num_pos_cls = bbox_preds[0].new_tensor( + len(pos_inds), dtype=torch.float) + num_pos_cls = max(reduce_mean(num_pos_cls), 1.0) + + cat_agn_cls_targets = cls_targets.max(dim=1)[0] # M + + cls_pos_loss, cls_neg_loss = self.loss_cls( + flatten_cls_scores.squeeze(1), cat_agn_cls_targets, pos_inds, + num_pos_cls) + + # 5 calc reg loss + pos_bbox_inds = torch.nonzero( + bbox_targets.max(dim=1)[0] >= 0).squeeze(1) + pos_bbox_preds = flatten_bbox_preds[pos_bbox_inds] + pos_bbox_targets = bbox_targets[pos_bbox_inds] + + bbox_weight_map = cls_targets.max(dim=1)[0] + bbox_weight_map = bbox_weight_map[pos_bbox_inds] + bbox_weight_map = bbox_weight_map if self.soft_weight_on_reg \ + else torch.ones_like(bbox_weight_map) + + num_pos_bbox = max(reduce_mean(bbox_weight_map.sum()), 1.0) + + if len(pos_bbox_inds) > 0: + bbox_loss = self.loss_bbox( + pos_bbox_preds, + pos_bbox_targets, + bbox_weight_map, + reduction='sum') / num_pos_bbox + else: + bbox_loss = flatten_bbox_preds.sum() * 0 + + return dict( + loss_bbox=bbox_loss, + loss_cls_pos=cls_pos_loss, + loss_cls_neg=cls_neg_loss) + + def loss_and_predict( + self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + proposal_cfg: Optional[ConfigDict] = None + ) -> Tuple[dict, InstanceList]: + """Perform forward propagation of the head, then calculate loss and + predictions from the features and data samples. + + Args: + x (tuple[Tensor]): Features from FPN. + batch_data_samples (list[:obj:`DetDataSample`]): Each item contains + the meta information of each image and corresponding + annotations. + proposal_cfg (ConfigDict, optional): Test / postprocessing + configuration, if None, test_cfg would be used. + Defaults to None. + + Returns: + tuple: the return value is a tuple contains: + + - losses: (dict[str, Tensor]): A dictionary of loss components. + - predictions (list[:obj:`InstanceData`]): Detection + results of each image after the post process. + """ + outputs = unpack_gt_instances(batch_data_samples) + (batch_gt_instances, batch_gt_instances_ignore, + batch_img_metas) = outputs + + outs = self(x) + + loss_inputs = outs + (batch_gt_instances, batch_img_metas, + batch_gt_instances_ignore) + losses = self.loss_by_feat(*loss_inputs) + predictions = self.predict_by_feat( + *outs, batch_img_metas=batch_img_metas, cfg=proposal_cfg) + return losses, predictions + + def _predict_by_feat_single(self, + cls_score_list: List[Tensor], + bbox_pred_list: List[Tensor], + score_factor_list: List[Tensor], + mlvl_priors: List[Tensor], + img_meta: dict, + cfg: ConfigDict, + rescale: bool = False, + with_nms: bool = True) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + cls_score_list (list[Tensor]): Box scores from all scale + levels of a single image, each item has shape + (num_priors * num_classes, H, W). + bbox_pred_list (list[Tensor]): Box energies / deltas from + all scale levels of a single image, each item has shape + (num_priors * 4, H, W). + score_factor_list (list[Tensor]): Score factor from all scale + levels of a single image, each item has shape + (num_priors * 1, H, W). + mlvl_priors (list[Tensor]): Each element in the list is + the priors of a single level in feature pyramid. In all + anchor-based methods, it has shape (num_priors, 4). In + all anchor-free methods, it has shape (num_priors, 2) + when `with_stride=True`, otherwise it still has shape + (num_priors, 4). + img_meta (dict): Image meta info. + cfg (mmengine.Config): Test / postprocessing configuration, + if None, test_cfg would be used. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + with_nms (bool): If True, do nms before return boxes. + Defaults to True. + + Returns: + :obj:`InstanceData`: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + nms_pre = cfg.get('nms_pre', -1) + + mlvl_bbox_preds = [] + mlvl_valid_priors = [] + mlvl_scores = [] + mlvl_labels = [] + + for level_idx, (cls_score, bbox_pred, score_factor, priors) in \ + enumerate(zip(cls_score_list, bbox_pred_list, + score_factor_list, mlvl_priors)): + + assert cls_score.size()[-2:] == bbox_pred.size()[-2:] + + bbox_pred = bbox_pred * self.strides[level_idx] + + dim = self.bbox_coder.encode_size + bbox_pred = bbox_pred.permute(1, 2, 0).reshape(-1, dim) + cls_score = cls_score.permute(1, 2, + 0).reshape(-1, self.cls_out_channels) + heatmap = cls_score.sigmoid() + score_thr = cfg.get('score_thr', 0) + + candidate_inds = heatmap > score_thr # 0.05 + pre_nms_top_n = candidate_inds.sum() # N + pre_nms_top_n = pre_nms_top_n.clamp(max=nms_pre) # N + + heatmap = heatmap[candidate_inds] # n + + candidate_nonzeros = candidate_inds.nonzero() # n + box_loc = candidate_nonzeros[:, 0] # n + labels = candidate_nonzeros[:, 1] # n + + bbox_pred = bbox_pred[box_loc] # n x 4 + per_grids = priors[box_loc] # n x 2 + + if candidate_inds.sum().item() > pre_nms_top_n.item(): + heatmap, top_k_indices = \ + heatmap.topk(pre_nms_top_n, sorted=False) + labels = labels[top_k_indices] + bbox_pred = bbox_pred[top_k_indices] + per_grids = per_grids[top_k_indices] + + bboxes = torch.stack([ + per_grids[:, 0] - bbox_pred[:, 0], + per_grids[:, 1] - bbox_pred[:, 1], + per_grids[:, 0] + bbox_pred[:, 2], + per_grids[:, 1] + bbox_pred[:, 3], + ], + dim=1) # n x 4 + + # avoid invalid boxes in RoI heads + bboxes[:, 2] = torch.max(bboxes[:, 2], bboxes[:, 0] + 0.01) + bboxes[:, 3] = torch.max(bboxes[:, 3], bboxes[:, 1] + 0.01) + + # bboxes = self.bbox_coder.decode(per_grids, bbox_pred) + # # avoid invalid boxes in RoI heads + # bboxes[:, 2] = torch.max(bboxes[:, 2], bboxes[:, 0] + 0.01) + # bboxes[:, 3] = torch.max(bboxes[:, 3], bboxes[:, 1] + 0.01) + + mlvl_bbox_preds.append(bboxes) + mlvl_valid_priors.append(priors) + mlvl_scores.append(torch.sqrt(heatmap)) + mlvl_labels.append(labels) + + results = InstanceData() + results.bboxes = torch.cat(mlvl_bbox_preds) + results.scores = torch.cat(mlvl_scores) + results.labels = torch.cat(mlvl_labels) + + return self._bbox_post_process( + results=results, + cfg=cfg, + rescale=rescale, + with_nms=with_nms, + img_meta=img_meta) + + def _get_label_inds(self, batch_gt_instances, batch_img_metas, + shapes_per_level): + ''' + Inputs: + batch_gt_instances: [n_i], sum n_i = N + shapes_per_level: L x 2 [(h_l, w_l)]_L + Returns: + pos_inds: N' + labels: N' + ''' + pos_inds = [] + L = len(self.strides) + B = len(batch_gt_instances) + shapes_per_level = shapes_per_level.long() + loc_per_level = (shapes_per_level[:, 0] * + shapes_per_level[:, 1]).long() # L + level_bases = [] + s = 0 + for i in range(L): + level_bases.append(s) + s = s + B * loc_per_level[i] + level_bases = shapes_per_level.new_tensor(level_bases).long() # L + strides_default = shapes_per_level.new_tensor( + self.strides).float() # L + for im_i in range(B): + targets_per_im = batch_gt_instances[im_i] + if hasattr(targets_per_im, 'bboxes'): + bboxes = targets_per_im.bboxes # n x 4 + else: + bboxes = targets_per_im.labels.new_tensor( + [], dtype=torch.float).reshape(-1, 4) + n = bboxes.shape[0] + centers = ((bboxes[:, [0, 1]] + bboxes[:, [2, 3]]) / 2) # n x 2 + centers = centers.view(n, 1, 2).expand(n, L, 2).contiguous() + if self.not_clamp_box: + h, w = batch_img_metas[im_i]._image_size + centers[:, :, 0].clamp_(min=0).clamp_(max=w - 1) + centers[:, :, 1].clamp_(min=0).clamp_(max=h - 1) + strides = strides_default.view(1, L, 1).expand(n, L, 2) + centers_inds = (centers / strides).long() # n x L x 2 + Ws = shapes_per_level[:, 1].view(1, L).expand(n, L) + pos_ind = level_bases.view(1, L).expand(n, L) \ + + im_i * loc_per_level.view(1, L).expand(n, L) \ + + centers_inds[:, :, 1] * Ws + centers_inds[:, :, 0] # n x L + is_cared_in_the_level = self.assign_fpn_level(bboxes) + pos_ind = pos_ind[is_cared_in_the_level].view(-1) + + pos_inds.append(pos_ind) # n' + pos_inds = torch.cat(pos_inds, dim=0).long() + return pos_inds # N, N + + def assign_fpn_level(self, boxes): + ''' + Inputs: + boxes: n x 4 + size_ranges: L x 2 + Return: + is_cared_in_the_level: n x L + ''' + size_ranges = boxes.new_tensor(self.regress_ranges).view( + len(self.regress_ranges), 2) # L x 2 + crit = ((boxes[:, 2:] - boxes[:, :2])**2).sum(dim=1)**0.5 / 2 # n + n, L = crit.shape[0], size_ranges.shape[0] + crit = crit.view(n, 1).expand(n, L) + size_ranges_expand = size_ranges.view(1, L, 2).expand(n, L, 2) + is_cared_in_the_level = (crit >= size_ranges_expand[:, :, 0]) & \ + (crit <= size_ranges_expand[:, :, 1]) + return is_cared_in_the_level + + def _get_targets_single(self, gt_instances: InstanceData, points: Tensor, + regress_ranges: Tensor, + strides: Tensor) -> Tuple[Tensor, Tensor]: + """Compute classification and bbox targets for a single image.""" + num_points = points.size(0) + num_gts = len(gt_instances) + gt_labels = gt_instances.labels + + if not hasattr(gt_instances, 'bboxes'): + gt_bboxes = gt_labels.new_tensor([], dtype=torch.float) + else: + gt_bboxes = gt_instances.bboxes + + if not hasattr(gt_instances, 'bboxes') or num_gts == 0: + return gt_labels.new_full((num_points, + self.num_classes), + self.num_classes, + dtype=torch.float), \ + gt_bboxes.new_full((num_points, 4), -1) + + # Calculate the regression tblr target corresponding to all points + points = points[:, None].expand(num_points, num_gts, 2) + gt_bboxes = gt_bboxes[None].expand(num_points, num_gts, 4) + strides = strides[:, None, None].expand(num_points, num_gts, 2) + + bbox_target = bbox2distance(points, gt_bboxes) # M x N x 4 + + # condition1: inside a gt bbox + inside_gt_bbox_mask = bbox_target.min(dim=2)[0] > 0 # M x N + + # condition2: Calculate the nearest points from + # the upper, lower, left and right ranges from + # the center of the gt bbox + centers = ((gt_bboxes[..., [0, 1]] + gt_bboxes[..., [2, 3]]) / 2) + centers_discret = ((centers / strides).int() * strides).float() + \ + strides / 2 + + centers_discret_dist = points - centers_discret + dist_x = centers_discret_dist[..., 0].abs() + dist_y = centers_discret_dist[..., 1].abs() + inside_gt_center3x3_mask = (dist_x <= strides[..., 0]) & \ + (dist_y <= strides[..., 0]) + + # condition3: limit the regression range for each location + bbox_target_wh = bbox_target[..., :2] + bbox_target[..., 2:] + crit = (bbox_target_wh**2).sum(dim=2)**0.5 / 2 + inside_fpn_level_mask = (crit >= regress_ranges[:, [0]]) & \ + (crit <= regress_ranges[:, [1]]) + bbox_target_mask = inside_gt_bbox_mask & \ + inside_gt_center3x3_mask & \ + inside_fpn_level_mask + + # Calculate the distance weight map + gt_center_peak_mask = ((centers_discret_dist**2).sum(dim=2) == 0) + weighted_dist = ((points - centers)**2).sum(dim=2) # M x N + weighted_dist[gt_center_peak_mask] = 0 + + areas = (gt_bboxes[..., 2] - gt_bboxes[..., 0]) * ( + gt_bboxes[..., 3] - gt_bboxes[..., 1]) + radius = self.delta**2 * 2 * areas + radius = torch.clamp(radius, min=self.hm_min_radius**2) + weighted_dist = weighted_dist / radius + + # Calculate bbox_target + bbox_weighted_dist = weighted_dist.clone() + bbox_weighted_dist[bbox_target_mask == 0] = INF * 1.0 + min_dist, min_inds = bbox_weighted_dist.min(dim=1) + bbox_target = bbox_target[range(len(bbox_target)), + min_inds] # M x N x 4 --> M x 4 + bbox_target[min_dist == INF] = -INF + + # Convert to feature map scale + bbox_target /= strides[:, 0, :].repeat(1, 2) + + # Calculate cls_target + cls_target = self._create_heatmaps_from_dist(weighted_dist, gt_labels) + + return cls_target, bbox_target diff --git a/mmdetection/projects/Detic_new/detic/detic.py b/mmdetection/projects/Detic_new/detic/detic.py new file mode 100644 index 00000000..7028690a --- /dev/null +++ b/mmdetection/projects/Detic_new/detic/detic.py @@ -0,0 +1,274 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from typing import List, Union + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.logging import print_log +from torch import Tensor + +from mmdet.datasets import LVISV1Dataset +from mmdet.models.detectors.cascade_rcnn import CascadeRCNN +from mmdet.registry import MODELS +from mmdet.structures import SampleList + + +class CLIPTextEncoder(nn.Module): + + def __init__(self, model_name='ViT-B/32'): + super().__init__() + import clip + from clip.simple_tokenizer import SimpleTokenizer + self.tokenizer = SimpleTokenizer() + pretrained_model, _ = clip.load(model_name, device='cpu') + self.clip = pretrained_model + + @property + def device(self): + return self.clip.device + + @property + def dtype(self): + return self.clip.dtype + + def tokenize(self, + texts: Union[str, List[str]], + context_length: int = 77) -> torch.LongTensor: + if isinstance(texts, str): + texts = [texts] + + sot_token = self.tokenizer.encoder['<|startoftext|>'] + eot_token = self.tokenizer.encoder['<|endoftext|>'] + all_tokens = [[sot_token] + self.tokenizer.encode(text) + [eot_token] + for text in texts] + result = torch.zeros(len(all_tokens), context_length, dtype=torch.long) + + for i, tokens in enumerate(all_tokens): + if len(tokens) > context_length: + st = torch.randint(len(tokens) - context_length + 1, + (1, ))[0].item() + tokens = tokens[st:st + context_length] + result[i, :len(tokens)] = torch.tensor(tokens) + + return result + + def forward(self, text): + text = self.tokenize(text) + text_features = self.clip.encode_text(text) + return text_features + + +def get_class_weight(original_caption, prompt_prefix='a '): + if isinstance(original_caption, str): + if original_caption == 'coco': + from mmdet.datasets import CocoDataset + class_names = CocoDataset.METAINFO['classes'] + elif original_caption == 'cityscapes': + from mmdet.datasets import CityscapesDataset + class_names = CityscapesDataset.METAINFO['classes'] + elif original_caption == 'voc': + from mmdet.datasets import VOCDataset + class_names = VOCDataset.METAINFO['classes'] + elif original_caption == 'openimages': + from mmdet.datasets import OpenImagesDataset + class_names = OpenImagesDataset.METAINFO['classes'] + elif original_caption == 'lvis': + from mmdet.datasets import LVISV1Dataset + class_names = LVISV1Dataset.METAINFO['classes'] + else: + if not original_caption.endswith('.'): + original_caption = original_caption + ' . ' + original_caption = original_caption.split(' . ') + class_names = list(filter(lambda x: len(x) > 0, original_caption)) + + # for test.py + else: + class_names = list(original_caption) + + text_encoder = CLIPTextEncoder() + text_encoder.eval() + texts = [prompt_prefix + x for x in class_names] + print_log(f'Computing text embeddings for {len(class_names)} classes.') + embeddings = text_encoder(texts).detach().permute(1, 0).contiguous().cpu() + return class_names, embeddings + + +def reset_cls_layer_weight(roi_head, weight): + if type(weight) == str: + print_log(f'Resetting cls_layer_weight from file: {weight}') + zs_weight = torch.tensor( + np.load(weight), + dtype=torch.float32).permute(1, 0).contiguous() # D x C + else: + zs_weight = weight + zs_weight = torch.cat( + [zs_weight, zs_weight.new_zeros( + (zs_weight.shape[0], 1))], dim=1) # D x (C + 1) + zs_weight = F.normalize(zs_weight, p=2, dim=0) + zs_weight = zs_weight.to('cuda') + num_classes = zs_weight.shape[-1] + + for bbox_head in roi_head.bbox_head: + bbox_head.num_classes = num_classes + del bbox_head.fc_cls.zs_weight + bbox_head.fc_cls.zs_weight = zs_weight + + +@MODELS.register_module() +class Detic(CascadeRCNN): + + def __init__(self, + with_image_labels: bool = False, + sync_caption_batch: bool = False, + fp16: bool = False, + roi_head_name: str = '', + cap_batch_ratio: int = 4, + with_caption: bool = False, + dynamic_classifier: bool = False, + **kwargs) -> None: + super().__init__(**kwargs) + + self._entities = LVISV1Dataset.METAINFO['classes'] + self._text_prompts = None + # Turn on co-training with classification data + self.with_image_labels = with_image_labels + # Caption losses + self.with_caption = with_caption + # synchronize across GPUs to enlarge # "classes" + self.sync_caption_batch = sync_caption_batch + # Ratio between detection data and caption data + self.cap_batch_ratio = cap_batch_ratio + self.fp16 = fp16 + self.roi_head_name = roi_head_name + # dynamic class sampling when training with 21K classes, + # Federated loss is enabled when DYNAMIC_CLASSIFIER is on + self.dynamic_classifier = dynamic_classifier + self.return_proposal = False + if self.dynamic_classifier: + self.freq_weight = kwargs.pop('freq_weight') + self.num_classes = kwargs.pop('num_classes') + self.num_sample_cats = kwargs.pop('num_sample_cats') + + def loss(self, batch_inputs: Tensor, + batch_data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + batch_inputs (Tensor): Input images of shape (N, C, H, W). + These should usually be mean centered and std scaled. + batch_data_samples (List[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components + """ + + x = self.extract_feat(batch_inputs) + losses = dict() + + # RPN forward and loss + if self.with_rpn: + proposal_cfg = self.train_cfg.get('rpn_proposal', + self.test_cfg.rpn) + rpn_data_samples = copy.deepcopy(batch_data_samples) + # set cat_id of gt_labels to 0 in RPN + for data_sample in rpn_data_samples: + data_sample.gt_instances.labels = \ + torch.zeros_like(data_sample.gt_instances.labels) + + rpn_losses, rpn_results_list = self.rpn_head.loss_and_predict( + x, rpn_data_samples, proposal_cfg=proposal_cfg) + + # avoid get same name with roi_head loss + keys = rpn_losses.keys() + for key in list(keys): + if 'loss' in key and 'rpn' not in key: + rpn_losses[f'rpn_{key}'] = rpn_losses.pop(key) + losses.update(rpn_losses) + # if not hasattr(batch_data_samples[0].gt_instances, 'bboxes'): + # losses.update({k: v * 0 for k, v in rpn_losses.items()}) + # else: + # losses.update(rpn_losses) + else: + assert batch_data_samples[0].get('proposals', None) is not None + # use pre-defined proposals in InstanceData for the second stage + # to extract ROI features. + rpn_results_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + + roi_losses = self.roi_head.loss(x, rpn_results_list, + batch_data_samples) + + losses.update(roi_losses) + + return losses + + def predict(self, + batch_inputs: Tensor, + batch_data_samples: SampleList, + rescale: bool = True) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + batch_inputs (Tensor): Inputs with shape (N, C, H, W). + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool): Whether to rescale the results. + Defaults to True. + + Returns: + list[:obj:`DetDataSample`]: Return the detection results of the + input images. The returns value is DetDataSample, + which usually contain 'pred_instances'. And the + ``pred_instances`` usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + # For single image inference + if 'custom_entities' in batch_data_samples[0]: + text_prompts = batch_data_samples[0].text + if text_prompts != self._text_prompts: + self._text_prompts = text_prompts + class_names, zs_weight = get_class_weight(text_prompts) + self._entities = class_names + reset_cls_layer_weight(self.roi_head, zs_weight) + + assert self.with_bbox, 'Bbox head must be implemented.' + + x = self.extract_feat(batch_inputs) + + # If there are no pre-defined proposals, use RPN to get proposals + if batch_data_samples[0].get('proposals', None) is None: + rpn_results_list = self.rpn_head.predict( + x, batch_data_samples, rescale=False) + else: + rpn_results_list = [ + data_sample.proposals for data_sample in batch_data_samples + ] + + results_list = self.roi_head.predict( + x, rpn_results_list, batch_data_samples, rescale=rescale) + + for data_sample, pred_instances in zip(batch_data_samples, + results_list): + if len(pred_instances) > 0: + label_names = [] + for labels in pred_instances.labels: + label_names.append(self._entities[labels]) + # for visualization + pred_instances.label_names = label_names + data_sample.pred_instances = pred_instances + + return batch_data_samples diff --git a/mmdetection/projects/Detic_new/detic/detic_bbox_head.py b/mmdetection/projects/Detic_new/detic/detic_bbox_head.py new file mode 100644 index 00000000..8779494b --- /dev/null +++ b/mmdetection/projects/Detic_new/detic/detic_bbox_head.py @@ -0,0 +1,434 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +from typing import List, Optional + +import torch +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor +from torch.nn import functional as F + +from mmdet.models.layers import multiclass_nms +from mmdet.models.losses import accuracy +from mmdet.models.roi_heads.bbox_heads import Shared2FCBBoxHead +from mmdet.models.utils import empty_instances +from mmdet.registry import MODELS +from mmdet.structures.bbox import get_box_tensor, scale_boxes +from mmdet.utils import ConfigType, InstanceList + + +def load_class_freq(path='datasets/metadata/lvis_v1_train_cat_info.json', + freq_weight=0.5): + cat_info = json.load(open(path, 'r')) + cat_info = torch.tensor( + [c['image_count'] for c in sorted(cat_info, key=lambda x: x['id'])]) + freq_weight = cat_info.float()**freq_weight + return freq_weight + + +def get_fed_loss_inds(labels, num_sample_cats, C, weight=None): + + appeared = torch.unique(labels) # C' + prob = appeared.new_ones(C + 1).float() + prob[-1] = 0 + if len(appeared) < num_sample_cats: + if weight is not None: + prob[:C] = weight.float().clone() + prob[appeared] = 0 + more_appeared = torch.multinomial( + prob, num_sample_cats - len(appeared), replacement=False) + appeared = torch.cat([appeared, more_appeared]) + return appeared + + +@MODELS.register_module() +class DeticBBoxHead(Shared2FCBBoxHead): + + def __init__(self, + image_loss_weight: float = 0.1, + use_fed_loss: bool = False, + cat_freq_path: str = '', + fed_loss_freq_weight: float = 0.5, + fed_loss_num_cat: int = 50, + cls_predictor_cfg: ConfigType = dict( + type='ZeroShotClassifier'), + *args, + **kwargs) -> None: + super().__init__(*args, **kwargs) + # reconstruct fc_cls and fc_reg since input channels are changed + assert self.with_cls + + self.cls_predictor_cfg = cls_predictor_cfg + cls_channels = self.num_classes + self.cls_predictor_cfg.update( + in_features=self.cls_last_dim, out_features=cls_channels) + self.fc_cls = MODELS.build(self.cls_predictor_cfg) + + self.init_cfg += [ + dict(type='Caffe2Xavier', override=dict(name='reg_fcs')) + ] + + self.image_loss_weight = image_loss_weight + self.use_fed_loss = use_fed_loss + self.cat_freq_path = cat_freq_path + self.fed_loss_freq_weight = fed_loss_freq_weight + self.fed_loss_num_cat = fed_loss_num_cat + + if self.use_fed_loss: + freq_weight = load_class_freq(cat_freq_path, fed_loss_freq_weight) + self.register_buffer('freq_weight', freq_weight) + else: + self.freq_weight = None + + def _predict_by_feat_single( + self, + roi: Tensor, + cls_score: Tensor, + bbox_pred: Tensor, + img_meta: dict, + rescale: bool = False, + rcnn_test_cfg: Optional[ConfigDict] = None) -> InstanceData: + """Transform a single image's features extracted from the head into + bbox results. + + Args: + roi (Tensor): Boxes to be transformed. Has shape (num_boxes, 5). + last dimension 5 arrange as (batch_index, x1, y1, x2, y2). + cls_score (Tensor): Box scores, has shape + (num_boxes, num_classes + 1). + bbox_pred (Tensor): Box energies / deltas. + has shape (num_boxes, num_classes * 4). + img_meta (dict): image information. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of Bbox Head. + Defaults to None + + Returns: + :obj:`InstanceData`: Detection results of each image\ + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + results = InstanceData() + if roi.shape[0] == 0: + return empty_instances([img_meta], + roi.device, + task_type='bbox', + instance_results=[results], + box_type=self.predict_box_type, + use_box_type=False, + num_classes=self.num_classes, + score_per_cls=rcnn_test_cfg is None)[0] + scores = cls_score + img_shape = img_meta['img_shape'] + num_rois = roi.size(0) + + num_classes = 1 if self.reg_class_agnostic else self.num_classes + roi = roi.repeat_interleave(num_classes, dim=0) + bbox_pred = bbox_pred.view(-1, self.bbox_coder.encode_size) + bboxes = self.bbox_coder.decode( + roi[..., 1:], bbox_pred, max_shape=img_shape) + + if rescale and bboxes.size(0) > 0: + assert img_meta.get('scale_factor') is not None + scale_factor = [1 / s for s in img_meta['scale_factor']] + bboxes = scale_boxes(bboxes, scale_factor) + + # Get the inside tensor when `bboxes` is a box type + bboxes = get_box_tensor(bboxes) + box_dim = bboxes.size(-1) + bboxes = bboxes.view(num_rois, -1) + + if rcnn_test_cfg is None: + # This means that it is aug test. + # It needs to return the raw results without nms. + results.bboxes = bboxes + results.scores = scores + else: + det_bboxes, det_labels = multiclass_nms( + bboxes, + scores, + rcnn_test_cfg.score_thr, + rcnn_test_cfg.nms, + rcnn_test_cfg.max_per_img, + box_dim=box_dim) + results.bboxes = det_bboxes[:, :-1] + results.scores = det_bboxes[:, -1] + results.labels = det_labels + return results + + def loss(self, + cls_score: Tensor, + bbox_pred: Tensor, + rois: Tensor, + labels: Tensor, + label_weights: Tensor, + bbox_targets: Tensor, + bbox_weights: Tensor, + reduction_override: Optional[str] = None) -> dict: + """Calculate the loss based on the network predictions and targets. + + Args: + cls_score (Tensor): Classification prediction + results of all class, has shape + (batch_size * num_proposals_single_image, num_classes) + bbox_pred (Tensor): Regression prediction results, + has shape + (batch_size * num_proposals_single_image, 4), the last + dimension 4 represents [tl_x, tl_y, br_x, br_y]. + rois (Tensor): RoIs with the shape + (batch_size * num_proposals_single_image, 5) where the first + column indicates batch id of each RoI. + labels (Tensor): Gt_labels for all proposals in a batch, has + shape (batch_size * num_proposals_single_image, ). + label_weights (Tensor): Labels_weights for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, ). + bbox_targets (Tensor): Regression target for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, 4), + the last dimension 4 represents [tl_x, tl_y, br_x, br_y]. + bbox_weights (Tensor): Regression weights for all proposals in a + batch, has shape (batch_size * num_proposals_single_image, 4). + reduction_override (str, optional): The reduction + method used to override the original reduction + method of the loss. Options are "none", + "mean" and "sum". Defaults to None, + + Returns: + dict: A dictionary of loss. + """ + + losses = dict() + + if cls_score is not None: + + if cls_score.numel() > 0: + loss_cls_ = self.sigmoid_cross_entropy_loss(cls_score, labels) + if isinstance(loss_cls_, dict): + losses.update(loss_cls_) + else: + losses['loss_cls'] = loss_cls_ + if self.custom_activation: + acc_ = self.loss_cls.get_accuracy(cls_score, labels) + losses.update(acc_) + else: + losses['acc'] = accuracy(cls_score, labels) + if bbox_pred is not None: + bg_class_ind = self.num_classes + # 0~self.num_classes-1 are FG, self.num_classes is BG + pos_inds = (labels >= 0) & (labels < bg_class_ind) + # do not perform bounding box regression for BG anymore. + if pos_inds.any(): + if self.reg_decoded_bbox: + # When the regression loss (e.g. `IouLoss`, + # `GIouLoss`, `DIouLoss`) is applied directly on + # the decoded bounding boxes, it decodes the + # already encoded coordinates to absolute format. + bbox_pred = self.bbox_coder.decode(rois[:, 1:], bbox_pred) + bbox_pred = get_box_tensor(bbox_pred) + if self.reg_class_agnostic: + pos_bbox_pred = bbox_pred.view( + bbox_pred.size(0), -1)[pos_inds.type(torch.bool)] + else: + pos_bbox_pred = bbox_pred.view( + bbox_pred.size(0), self.num_classes, + -1)[pos_inds.type(torch.bool), + labels[pos_inds.type(torch.bool)]] + + losses['loss_bbox'] = self.loss_bbox( + pos_bbox_pred, + bbox_targets[pos_inds.type(torch.bool)], + bbox_weights[pos_inds.type(torch.bool)], + avg_factor=bbox_targets.size(0), + reduction_override=reduction_override) + else: + losses['loss_bbox'] = bbox_pred[pos_inds].sum() + return losses + + def sigmoid_cross_entropy_loss(self, cls_score, labels): + if cls_score.numel() == 0: + return cls_score.new_zeros( + [1])[0] # This is more robust than .sum() * 0. + B = cls_score.shape[0] + C = cls_score.shape[1] - 1 + + target = cls_score.new_zeros(B, C + 1) + target[range(len(labels)), labels] = 1 # B x (C + 1) + target = target[:, :C] # B x C + + weight = 1 + if self.use_fed_loss and (self.freq_weight is not None): # fedloss + appeared = get_fed_loss_inds( + labels, + num_sample_cats=self.fed_loss_num_cat, + C=C, + weight=self.freq_weight) + appeared_mask = appeared.new_zeros(C + 1) + appeared_mask[appeared] = 1 # C + 1 + appeared_mask = appeared_mask[:C] + fed_w = appeared_mask.view(1, C).expand(B, C) + weight = weight * fed_w.float() + # if self.ignore_zero_cats and (self.freq_weight is not None): + # w = (self.freq_weight.view(-1) > 1e-4).float() + # weight = weight * w.view(1, C).expand(B, C) + # # import pdb; pdb.set_trace() + + cls_loss = F.binary_cross_entropy_with_logits( + cls_score[:, :-1], target, reduction='none') # B x C + loss = torch.sum(cls_loss * weight) / B + return loss + + def image_label_losses(self, cls_score, sampling_results, image_labels): + ''' + Inputs: + cls_score: N x (C + 1) + image_labels B x 1 + ''' + num_inst_per_image = [ + len(pred_instances) for pred_instances in sampling_results + ] + cls_score = cls_score.split( + num_inst_per_image, dim=0) # B x n x (C + 1) + B = len(cls_score) + loss = cls_score[0].new_zeros([1])[0] + for (score, labels, pred_instances) in zip(cls_score, image_labels, + sampling_results): + if score.shape[0] == 0: + loss += score.new_zeros([1])[0] + continue + # find out max-size idx + bboxes = pred_instances.bboxes + areas = (bboxes[:, 2] - bboxes[:, 0]) * ( + bboxes[:, 3] - bboxes[:, 1]) + idx = areas[:-1].argmax().item() if len(areas) > 1 else 0 + + for label in labels: + target = score.new_zeros(score.shape[1]) + target[label] = 1 + loss_i = F.binary_cross_entropy_with_logits( + score[idx], target, reduction='sum') + loss += loss_i / len(labels) + loss = loss / B + + return loss * self.image_loss_weight + + def refine_bboxes(self, bbox_results: dict, + batch_img_metas: List[dict]) -> InstanceList: + """Refine bboxes during training. + + Args: + bbox_results (dict): Usually is a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `rois` (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + - `bbox_targets` (tuple): Ground truth for proposals in a + single image. Containing the following list of Tensors: + (labels, label_weights, bbox_targets, bbox_weights) + batch_img_metas (List[dict]): List of image information. + + Returns: + list[:obj:`InstanceData`]: Refined bboxes of each image. + + Example: + >>> # xdoctest: +REQUIRES(module:kwarray) + >>> import numpy as np + >>> from mmdet.models.task_modules.samplers. + ... sampling_result import random_boxes + >>> from mmdet.models.task_modules.samplers import SamplingResult + >>> self = BBoxHead(reg_class_agnostic=True) + >>> n_roi = 2 + >>> n_img = 4 + >>> scale = 512 + >>> rng = np.random.RandomState(0) + ... batch_img_metas = [{'img_shape': (scale, scale)} + >>> for _ in range(n_img)] + >>> sampling_results = [SamplingResult.random(rng=10) + ... for _ in range(n_img)] + >>> # Create rois in the expected format + >>> roi_boxes = random_boxes(n_roi, scale=scale, rng=rng) + >>> img_ids = torch.randint(0, n_img, (n_roi,)) + >>> img_ids = img_ids.float() + >>> rois = torch.cat([img_ids[:, None], roi_boxes], dim=1) + >>> # Create other args + >>> labels = torch.randint(0, 81, (scale,)).long() + >>> bbox_preds = random_boxes(n_roi, scale=scale, rng=rng) + >>> cls_score = torch.randn((scale, 81)) + ... # For each image, pretend random positive boxes are gts + >>> bbox_targets = (labels, None, None, None) + ... bbox_results = dict(rois=rois, bbox_pred=bbox_preds, + ... cls_score=cls_score, + ... bbox_targets=bbox_targets) + >>> bboxes_list = self.refine_bboxes(sampling_results, + ... bbox_results, + ... batch_img_metas) + >>> print(bboxes_list) + """ + # bbox_targets is a tuple + cls_scores = bbox_results['cls_score'] + rois = bbox_results['rois'] + bbox_preds = bbox_results['bbox_pred'] + if self.custom_activation: + # TODO: Create a SeasawBBoxHead to simplified logic in BBoxHead + cls_scores = self.loss_cls.get_activation(cls_scores) + if cls_scores.numel() == 0: + return None + if cls_scores.shape[-1] == self.num_classes + 1: + # remove background class + cls_scores = cls_scores[:, :-1] + elif cls_scores.shape[-1] != self.num_classes: + raise ValueError('The last dim of `cls_scores` should equal to ' + '`num_classes` or `num_classes + 1`,' + f'but got {cls_scores.shape[-1]}.') + + img_ids = rois[:, 0].long().unique(sorted=True) + assert img_ids.numel() <= len(batch_img_metas) + + results_list = [] + for i in range(len(batch_img_metas)): + inds = torch.nonzero( + rois[:, 0] == i, as_tuple=False).squeeze(dim=1) + + bboxes_ = rois[inds, 1:] + bbox_pred_ = bbox_preds[inds] + img_meta_ = batch_img_metas[i] + + bboxes = self.regress(bboxes_, bbox_pred_, img_meta_) + + # don't filter gt bboxes like D2 + results = InstanceData(bboxes=bboxes) + results_list.append(results) + + return results_list + + def regress(self, priors: Tensor, bbox_pred: Tensor, + img_meta: dict) -> Tensor: + """Regress the bbox for the predicted class. Used in Cascade R-CNN. + + Args: + priors (Tensor): Priors from `rpn_head` or last stage + `bbox_head`, has shape (num_proposals, 4). + label (Tensor): Only used when `self.reg_class_agnostic` + is False, has shape (num_proposals, ). + bbox_pred (Tensor): Regression prediction of + current stage `bbox_head`. When `self.reg_class_agnostic` + is False, it has shape (n, num_classes * 4), otherwise + it has shape (n, 4). + img_meta (dict): Image meta info. + + Returns: + Tensor: Regressed bboxes, the same shape as input rois. + """ + reg_dim = self.bbox_coder.encode_size + assert bbox_pred.size()[1] == reg_dim + + max_shape = img_meta['img_shape'] + regressed_bboxes = self.bbox_coder.decode( + priors, bbox_pred, max_shape=max_shape) + return regressed_bboxes diff --git a/mmdetection/projects/Detic_new/detic/detic_roi_head.py b/mmdetection/projects/Detic_new/detic/detic_roi_head.py new file mode 100644 index 00000000..35785cda --- /dev/null +++ b/mmdetection/projects/Detic_new/detic/detic_roi_head.py @@ -0,0 +1,440 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Sequence, Tuple + +import torch +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.models.roi_heads import CascadeRoIHead +from mmdet.models.task_modules.samplers import SamplingResult +from mmdet.models.test_time_augs import merge_aug_masks +from mmdet.models.utils import empty_instances, unpack_gt_instances +from mmdet.registry import MODELS +from mmdet.structures import SampleList +from mmdet.structures.bbox import bbox2roi, get_box_tensor +from mmdet.utils import ConfigType, InstanceList, MultiConfig + + +@MODELS.register_module() +class DeticRoIHead(CascadeRoIHead): + + def __init__( + self, + *, + mult_proposal_score: bool = False, + with_image_labels: bool = False, + add_image_box: bool = False, + image_box_size: float = 1.0, + ws_num_props: int = 128, + add_feature_to_prop: bool = False, + mask_weight: float = 1.0, + one_class_per_proposal: bool = False, + **kwargs, + ): + super().__init__(**kwargs) + self.mult_proposal_score = mult_proposal_score + self.with_image_labels = with_image_labels + self.add_image_box = add_image_box + self.image_box_size = image_box_size + self.ws_num_props = ws_num_props + self.add_feature_to_prop = add_feature_to_prop + self.mask_weight = mask_weight + self.one_class_per_proposal = one_class_per_proposal + + def init_mask_head(self, mask_roi_extractor: MultiConfig, + mask_head: MultiConfig) -> None: + """Initialize mask head and mask roi extractor. + + Args: + mask_head (dict): Config of mask in mask head. + mask_roi_extractor (:obj:`ConfigDict`, dict or list): + Config of mask roi extractor. + """ + self.mask_head = MODELS.build(mask_head) + + if mask_roi_extractor is not None: + self.share_roi_extractor = False + self.mask_roi_extractor = MODELS.build(mask_roi_extractor) + else: + self.share_roi_extractor = True + self.mask_roi_extractor = self.bbox_roi_extractor + + def _refine_roi(self, x: Tuple[Tensor], rois: Tensor, + batch_img_metas: List[dict], + num_proposals_per_img: Sequence[int], **kwargs) -> tuple: + """Multi-stage refinement of RoI. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rois (Tensor): shape (n, 5), [batch_ind, x1, y1, x2, y2] + batch_img_metas (list[dict]): List of image information. + num_proposals_per_img (sequence[int]): number of proposals + in each image. + + Returns: + tuple: + + - rois (Tensor): Refined RoI. + - cls_scores (list[Tensor]): Average predicted + cls score per image. + - bbox_preds (list[Tensor]): Bbox branch predictions + for the last stage of per image. + """ + # "ms" in variable names means multi-stage + ms_scores = [] + for stage in range(self.num_stages): + bbox_results = self._bbox_forward( + stage=stage, x=x, rois=rois, **kwargs) + + # split batch bbox prediction back to each image + cls_scores = bbox_results['cls_score'].sigmoid() + bbox_preds = bbox_results['bbox_pred'] + + rois = rois.split(num_proposals_per_img, 0) + cls_scores = cls_scores.split(num_proposals_per_img, 0) + ms_scores.append(cls_scores) + bbox_preds = bbox_preds.split(num_proposals_per_img, 0) + + if stage < self.num_stages - 1: + bbox_head = self.bbox_head[stage] + refine_rois_list = [] + for i in range(len(batch_img_metas)): + if rois[i].shape[0] > 0: + bbox_label = cls_scores[i][:, :-1].argmax(dim=1) + # Refactor `bbox_head.regress_by_class` to only accept + # box tensor without img_idx concatenated. + refined_bboxes = bbox_head.regress_by_class( + rois[i][:, 1:], bbox_label, bbox_preds[i], + batch_img_metas[i]) + refined_bboxes = get_box_tensor(refined_bboxes) + refined_rois = torch.cat( + [rois[i][:, [0]], refined_bboxes], dim=1) + refine_rois_list.append(refined_rois) + rois = torch.cat(refine_rois_list) + # ms_scores aligned + # average scores of each image by stages + cls_scores = [ + sum([score[i] for score in ms_scores]) / float(len(ms_scores)) + for i in range(len(batch_img_metas)) + ] # aligned + return rois, cls_scores, bbox_preds + + def predict_bbox(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + rpn_results_list: InstanceList, + rcnn_test_cfg: ConfigType, + rescale: bool = False, + **kwargs) -> InstanceList: + """Perform forward propagation of the bbox head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + rcnn_test_cfg (obj:`ConfigDict`): `test_cfg` of R-CNN. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + """ + proposals = [res.bboxes for res in rpn_results_list] + proposal_scores = [res.scores for res in rpn_results_list] + num_proposals_per_img = tuple(len(p) for p in proposals) + rois = bbox2roi(proposals) + + if rois.shape[0] == 0: + return empty_instances( + batch_img_metas, + rois.device, + task_type='bbox', + box_type=self.bbox_head[-1].predict_box_type, + num_classes=self.bbox_head[-1].num_classes, + score_per_cls=rcnn_test_cfg is None) + # rois aligned + rois, cls_scores, bbox_preds = self._refine_roi( + x=x, + rois=rois, + batch_img_metas=batch_img_metas, + num_proposals_per_img=num_proposals_per_img, + **kwargs) + + # score reweighting in centernet2 + cls_scores = [(s * ps[:, None])**0.5 + for s, ps in zip(cls_scores, proposal_scores)] + # # for demo + # cls_scores = [ + # s * (s == s[:, :-1].max(dim=1)[0][:, None]).float() + # for s in cls_scores + # ] + + # fast_rcnn_inference + results_list = self.bbox_head[-1].predict_by_feat( + rois=rois, + cls_scores=cls_scores, + bbox_preds=bbox_preds, + batch_img_metas=batch_img_metas, + rescale=rescale, + rcnn_test_cfg=rcnn_test_cfg) + return results_list + + def _mask_forward(self, x: Tuple[Tensor], rois: Tensor) -> dict: + """Mask head forward function used in both training and testing. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): Tuple of multi-level img features. + rois (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + """ + mask_feats = self.mask_roi_extractor( + x[:self.mask_roi_extractor.num_inputs], rois) + # do not support caffe_c4 model anymore + mask_preds = self.mask_head(mask_feats) + + mask_results = dict(mask_preds=mask_preds) + return mask_results + + def mask_loss(self, x, sampling_results: List[SamplingResult], + batch_gt_instances: InstanceList) -> dict: + """Run forward function and calculate loss for mask head in training. + + Args: + x (tuple[Tensor]): Tuple of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes``, ``labels``, and + ``masks`` attributes. + + Returns: + dict: Usually returns a dictionary with keys: + + - `mask_preds` (Tensor): Mask prediction. + - `loss_mask` (dict): A dictionary of mask loss components. + """ + pos_rois = bbox2roi([res.pos_priors for res in sampling_results]) + mask_results = self._mask_forward(x, pos_rois) + + mask_loss_and_target = self.mask_head.loss_and_target( + mask_preds=mask_results['mask_preds'], + sampling_results=sampling_results, + batch_gt_instances=batch_gt_instances, + rcnn_train_cfg=self.train_cfg[-1]) + mask_results.update(mask_loss_and_target) + + return mask_results + + def loss(self, x: Tuple[Tensor], rpn_results_list: InstanceList, + batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + roi on the features of the upstream network. + + Args: + x (tuple[Tensor]): List of multi-level img features. + rpn_results_list (list[:obj:`InstanceData`]): List of region + proposals. + batch_data_samples (list[:obj:`DetDataSample`]): The batch + data samples. It usually includes information such + as `gt_instance` or `gt_panoptic_seg` or `gt_sem_seg`. + + Returns: + dict[str, Tensor]: A dictionary of loss components + """ + assert len(rpn_results_list) == len(batch_data_samples) + outputs = unpack_gt_instances(batch_data_samples) + batch_gt_instances, batch_gt_instances_ignore, batch_img_metas \ + = outputs + + num_imgs = len(batch_data_samples) + image_labels = [x.gt_instances.labels for x in batch_data_samples] + losses = dict() + results_list = rpn_results_list + + for stage in range(self.num_stages): + self.current_stage = stage + stage_loss_weight = self.stage_loss_weights[stage] + if hasattr(batch_gt_instances[0], 'bboxes'): + # assign gts and sample proposals + sampling_results = [] + if self.with_bbox or self.with_mask: + bbox_assigner = self.bbox_assigner[stage] + bbox_sampler = self.bbox_sampler[stage] + + for i in range(num_imgs): + results = results_list[i] + # rename rpn_results.bboxes to rpn_results.priors + results.priors = results.pop('bboxes') + + assign_result = bbox_assigner.assign( + results, batch_gt_instances[i], + batch_gt_instances_ignore[i]) + + sampling_result = bbox_sampler.sample( + assign_result, + results, + batch_gt_instances[i], + feats=[lvl_feat[i][None] for lvl_feat in x]) + + sampling_results.append(sampling_result) + + # bbox head forward and loss + bbox_results = self.bbox_loss(stage, x, sampling_results) + + for name, value in bbox_results['loss_bbox'].items(): + losses[f's{stage}.{name}'] = ( + value * stage_loss_weight if 'loss' in name else value) + losses[f's{stage}.image_loss'] = x[0].new_zeros([1])[0] + + # mask head forward and loss + # D2 only forward stage.0 + if self.with_mask and stage == 0: + mask_results = self.mask_loss(x, sampling_results, + batch_gt_instances) + for name, value in mask_results['loss_mask'].items(): + losses[name] = ( + value * + stage_loss_weight if 'loss' in name else value) + + else: + # get ws_num_props pred_instances for each image + sampling_results = [ + pred_instances[:self.ws_num_props] + for pred_instances in results_list + ] + for i, pred_instances in enumerate(sampling_results): + pred_instances.bboxes = pred_instances.bboxes.detach() + bbox_results = self.image_loss(stage, x, sampling_results, + image_labels) + losses[f's{stage}.image_loss'] = bbox_results['image_loss'] + + for name in ['loss_cls', 'loss_bbox']: + losses[f's{stage}.{name}'] = x[0].new_zeros([1])[0] + if stage == 0: + losses['loss_mask'] = x[0].new_zeros([1])[0] + + # refine bboxes + if stage < self.num_stages - 1: + bbox_head = self.bbox_head[stage] + with torch.no_grad(): + results_list = bbox_head.refine_bboxes( + bbox_results, batch_img_metas) + # Empty proposal + if results_list is None: + break + + return losses + + def image_loss(self, stage: int, x: Tuple[Tensor], + sampling_results: List[SamplingResult], + image_labels) -> dict: + """Run forward function and calculate loss for box head in training. + + Args: + stage (int): The current stage in Cascade RoI Head. + x (tuple[Tensor]): List of multi-level img features. + sampling_results (list["obj:`SamplingResult`]): Sampling results. + + Returns: + dict: Usually returns a dictionary with keys: + + - `cls_score` (Tensor): Classification scores. + - `bbox_pred` (Tensor): Box energies / deltas. + - `bbox_feats` (Tensor): Extract bbox RoI features. + - `loss_bbox` (dict): A dictionary of bbox loss components. + - `rois` (Tensor): RoIs with the shape (n, 5) where the first + column indicates batch id of each RoI. + - `bbox_targets` (tuple): Ground truth for proposals in a + single image. Containing the following list of Tensors: + (labels, label_weights, bbox_targets, bbox_weights) + """ + bbox_head = self.bbox_head[stage] + rois = bbox2roi([res.bboxes for res in sampling_results]) + bbox_results = self._bbox_forward(stage, x, rois) + bbox_results.update(rois=rois) + + image_loss = bbox_head.image_label_losses( + cls_score=bbox_results['cls_score'], + sampling_results=sampling_results, + image_labels=image_labels) + bbox_results.update(dict(image_loss=image_loss)) + + return bbox_results + + def predict_mask(self, + x: Tuple[Tensor], + batch_img_metas: List[dict], + results_list: List[InstanceData], + rescale: bool = False) -> List[InstanceData]: + """Perform forward propagation of the mask head and predict detection + results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Feature maps of all scale level. + batch_img_metas (list[dict]): List of image information. + results_list (list[:obj:`InstanceData`]): Detection results of + each image. + rescale (bool): If True, return boxes in original image space. + Defaults to False. + + Returns: + list[:obj:`InstanceData`]: Detection results of each image + after the post process. + Each item usually contains following keys. + + - scores (Tensor): Classification scores, has a shape + (num_instance, ) + - labels (Tensor): Labels of bboxes, has a shape + (num_instances, ). + - bboxes (Tensor): Has a shape (num_instances, 4), + the last dimension 4 arrange as (x1, y1, x2, y2). + - masks (Tensor): Has a shape (num_instances, H, W). + """ + bboxes = [res.bboxes for res in results_list] + mask_rois = bbox2roi(bboxes) + if mask_rois.shape[0] == 0: + results_list = empty_instances( + batch_img_metas, + mask_rois.device, + task_type='mask', + instance_results=results_list, + mask_thr_binary=self.test_cfg.mask_thr_binary) + return results_list + + num_mask_rois_per_img = [len(res) for res in results_list] + aug_masks = [] + mask_results = self._mask_forward(x, mask_rois) + mask_preds = mask_results['mask_preds'] + # split batch mask prediction back to each image + mask_preds = mask_preds.split(num_mask_rois_per_img, 0) + aug_masks.append([m.sigmoid().detach() for m in mask_preds]) + + merged_masks = [] + for i in range(len(batch_img_metas)): + aug_mask = [mask[i] for mask in aug_masks] + merged_mask = merge_aug_masks(aug_mask, batch_img_metas[i]) + merged_masks.append(merged_mask) + results_list = self.mask_head.predict_by_feat( + mask_preds=merged_masks, + results_list=results_list, + batch_img_metas=batch_img_metas, + rcnn_test_cfg=self.test_cfg, + rescale=rescale, + activate_map=True) + return results_list diff --git a/mmdetection/projects/Detic_new/detic/heatmap_focal_loss.py b/mmdetection/projects/Detic_new/detic/heatmap_focal_loss.py new file mode 100644 index 00000000..021a5b22 --- /dev/null +++ b/mmdetection/projects/Detic_new/detic/heatmap_focal_loss.py @@ -0,0 +1,131 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Union + +import torch +import torch.nn as nn +from torch import Tensor + +from mmdet.registry import MODELS + + +# support class-agnostic heatmap_focal_loss +def heatmap_focal_loss_with_pos_inds( + pred: Tensor, + targets: Tensor, + pos_inds: Tensor, + alpha: float = 2.0, + beta: float = 4.0, + gamma: float = 4.0, + sigmoid_clamp: float = 1e-4, + ignore_high_fp: float = -1.0, + pos_weight: float = 1.0, + neg_weight: float = 1.0, + avg_factor: Optional[Union[int, float]] = None) -> Tensor: + + pred = torch.clamp( + pred.sigmoid_(), min=sigmoid_clamp, max=1 - sigmoid_clamp) + + neg_weights = torch.pow(1 - targets, beta) + + pos_pred = pred[pos_inds] + pos_loss = torch.log(pos_pred) * torch.pow(1 - pos_pred, gamma) + neg_loss = torch.log(1 - pred) * torch.pow(pred, gamma) * neg_weights + if ignore_high_fp > 0: + not_high_fp = (pred < ignore_high_fp).float() + neg_loss = not_high_fp * neg_loss + + pos_loss = -pos_loss.sum() + neg_loss = -neg_loss.sum() + if alpha >= 0: + pos_loss = alpha * pos_loss + neg_loss = (1 - alpha) * neg_loss + + pos_loss = pos_weight * pos_loss / avg_factor + neg_loss = neg_weight * neg_loss / avg_factor + + return pos_loss, neg_loss + + +@MODELS.register_module() +class HeatmapFocalLoss(nn.Module): + """GaussianFocalLoss is a variant of focal loss. + + More details can be found in the `paper + `_ + Code is modified from `kp_utils.py + `_ # noqa: E501 + Please notice that the target in GaussianFocalLoss is a gaussian heatmap, + not 0/1 binary target. + + Args: + alpha (float): Power of prediction. + gamma (float): Power of target for negative samples. + reduction (str): Options are "none", "mean" and "sum". + loss_weight (float): Loss weight of current loss. + pos_weight(float): Positive sample loss weight. Defaults to 1.0. + neg_weight(float): Negative sample loss weight. Defaults to 1.0. + """ + + def __init__( + self, + alpha: float = 2.0, + beta: float = 4.0, + gamma: float = 4.0, + sigmoid_clamp: float = 1e-4, + ignore_high_fp: float = -1.0, + loss_weight: float = 1.0, + pos_weight: float = 1.0, + neg_weight: float = 1.0, + ) -> None: + super().__init__() + self.alpha = alpha + self.beta = beta + self.gamma = gamma + self.sigmoid_clamp = sigmoid_clamp + self.ignore_high_fp = ignore_high_fp + self.loss_weight = loss_weight + self.pos_weight = pos_weight + self.neg_weight = neg_weight + + def forward(self, + pred: Tensor, + target: Tensor, + pos_inds: Optional[Tensor] = None, + avg_factor: Optional[Union[int, float]] = None) -> Tensor: + """Forward function. + + If you want to manually determine which positions are + positive samples, you can set the pos_index and pos_label + parameter. Currently, only the CenterNet update version uses + the parameter. + + Args: + pred (torch.Tensor): The prediction. The shape is (N, num_classes). + target (torch.Tensor): The learning target of the prediction + in gaussian distribution. The shape is (N, num_classes). + pos_inds (torch.Tensor): The positive sample index. + Defaults to None. + pos_labels (torch.Tensor): The label corresponding to the positive + sample index. Defaults to None. + weight (torch.Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, float, optional): Average factor that is used to + average the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + """ + + pos_loss, neg_loss = heatmap_focal_loss_with_pos_inds( + pred, + target, + pos_inds, + alpha=self.alpha, + beta=self.beta, + gamma=self.gamma, + sigmoid_clamp=self.sigmoid_clamp, + ignore_high_fp=self.ignore_high_fp, + pos_weight=self.pos_weight, + neg_weight=self.neg_weight, + avg_factor=avg_factor) + return pos_loss, neg_loss diff --git a/mmdetection/projects/Detic_new/detic/imagenet_lvis.py b/mmdetection/projects/Detic_new/detic/imagenet_lvis.py new file mode 100644 index 00000000..3375a086 --- /dev/null +++ b/mmdetection/projects/Detic_new/detic/imagenet_lvis.py @@ -0,0 +1,395 @@ +# Copyright (c) OpenMMLab. All rights reserved.METAINFO +import copy +import os.path as osp +import pickle +import warnings +from typing import List, Union + +from mmengine.fileio import get_local_path + +from mmdet.datasets import LVISV1Dataset +from mmdet.registry import DATASETS + + +@DATASETS.register_module() +class ImageNetLVISV1Dataset(LVISV1Dataset): + """LVIS v1 dataset for detection.""" + + METAINFO = { + 'classes': + ('aerosol_can', 'air_conditioner', 'airplane', 'alarm_clock', + 'alcohol', 'alligator', 'almond', 'ambulance', 'amplifier', 'anklet', + 'antenna', 'apple', 'applesauce', 'apricot', 'apron', 'aquarium', + 'arctic_(type_of_shoe)', 'armband', 'armchair', 'armoire', 'armor', + 'artichoke', 'trash_can', 'ashtray', 'asparagus', 'atomizer', + 'avocado', 'award', 'awning', 'ax', 'baboon', 'baby_buggy', + 'basketball_backboard', 'backpack', 'handbag', 'suitcase', 'bagel', + 'bagpipe', 'baguet', 'bait', 'ball', 'ballet_skirt', 'balloon', + 'bamboo', 'banana', 'Band_Aid', 'bandage', 'bandanna', 'banjo', + 'banner', 'barbell', 'barge', 'barrel', 'barrette', 'barrow', + 'baseball_base', 'baseball', 'baseball_bat', 'baseball_cap', + 'baseball_glove', 'basket', 'basketball', 'bass_horn', 'bat_(animal)', + 'bath_mat', 'bath_towel', 'bathrobe', 'bathtub', 'batter_(food)', + 'battery', 'beachball', 'bead', 'bean_curd', 'beanbag', 'beanie', + 'bear', 'bed', 'bedpan', 'bedspread', 'cow', 'beef_(food)', 'beeper', + 'beer_bottle', 'beer_can', 'beetle', 'bell', 'bell_pepper', 'belt', + 'belt_buckle', 'bench', 'beret', 'bib', 'Bible', 'bicycle', 'visor', + 'billboard', 'binder', 'binoculars', 'bird', 'birdfeeder', 'birdbath', + 'birdcage', 'birdhouse', 'birthday_cake', 'birthday_card', + 'pirate_flag', 'black_sheep', 'blackberry', 'blackboard', 'blanket', + 'blazer', 'blender', 'blimp', 'blinker', 'blouse', 'blueberry', + 'gameboard', 'boat', 'bob', 'bobbin', 'bobby_pin', 'boiled_egg', + 'bolo_tie', 'deadbolt', 'bolt', 'bonnet', 'book', 'bookcase', + 'booklet', 'bookmark', 'boom_microphone', 'boot', 'bottle', + 'bottle_opener', 'bouquet', 'bow_(weapon)', + 'bow_(decorative_ribbons)', 'bow-tie', 'bowl', 'pipe_bowl', + 'bowler_hat', 'bowling_ball', 'box', 'boxing_glove', 'suspenders', + 'bracelet', 'brass_plaque', 'brassiere', 'bread-bin', 'bread', + 'breechcloth', 'bridal_gown', 'briefcase', 'broccoli', 'broach', + 'broom', 'brownie', 'brussels_sprouts', 'bubble_gum', 'bucket', + 'horse_buggy', 'bull', 'bulldog', 'bulldozer', 'bullet_train', + 'bulletin_board', 'bulletproof_vest', 'bullhorn', 'bun', 'bunk_bed', + 'buoy', 'burrito', 'bus_(vehicle)', 'business_card', 'butter', + 'butterfly', 'button', 'cab_(taxi)', 'cabana', 'cabin_car', 'cabinet', + 'locker', 'cake', 'calculator', 'calendar', 'calf', 'camcorder', + 'camel', 'camera', 'camera_lens', 'camper_(vehicle)', 'can', + 'can_opener', 'candle', 'candle_holder', 'candy_bar', 'candy_cane', + 'walking_cane', 'canister', 'canoe', 'cantaloup', 'canteen', + 'cap_(headwear)', 'bottle_cap', 'cape', 'cappuccino', + 'car_(automobile)', 'railcar_(part_of_a_train)', 'elevator_car', + 'car_battery', 'identity_card', 'card', 'cardigan', 'cargo_ship', + 'carnation', 'horse_carriage', 'carrot', 'tote_bag', 'cart', 'carton', + 'cash_register', 'casserole', 'cassette', 'cast', 'cat', + 'cauliflower', 'cayenne_(spice)', 'CD_player', 'celery', + 'cellular_telephone', 'chain_mail', 'chair', 'chaise_longue', + 'chalice', 'chandelier', 'chap', 'checkbook', 'checkerboard', + 'cherry', 'chessboard', 'chicken_(animal)', 'chickpea', + 'chili_(vegetable)', 'chime', 'chinaware', 'crisp_(potato_chip)', + 'poker_chip', 'chocolate_bar', 'chocolate_cake', 'chocolate_milk', + 'chocolate_mousse', 'choker', 'chopping_board', 'chopstick', + 'Christmas_tree', 'slide', 'cider', 'cigar_box', 'cigarette', + 'cigarette_case', 'cistern', 'clarinet', 'clasp', 'cleansing_agent', + 'cleat_(for_securing_rope)', 'clementine', 'clip', 'clipboard', + 'clippers_(for_plants)', 'cloak', 'clock', 'clock_tower', + 'clothes_hamper', 'clothespin', 'clutch_bag', 'coaster', 'coat', + 'coat_hanger', 'coatrack', 'cock', 'cockroach', 'cocoa_(beverage)', + 'coconut', 'coffee_maker', 'coffee_table', 'coffeepot', 'coil', + 'coin', 'colander', 'coleslaw', 'coloring_material', + 'combination_lock', 'pacifier', 'comic_book', 'compass', + 'computer_keyboard', 'condiment', 'cone', 'control', + 'convertible_(automobile)', 'sofa_bed', 'cooker', 'cookie', + 'cooking_utensil', 'cooler_(for_food)', 'cork_(bottle_plug)', + 'corkboard', 'corkscrew', 'edible_corn', 'cornbread', 'cornet', + 'cornice', 'cornmeal', 'corset', 'costume', 'cougar', 'coverall', + 'cowbell', 'cowboy_hat', 'crab_(animal)', 'crabmeat', 'cracker', + 'crape', 'crate', 'crayon', 'cream_pitcher', 'crescent_roll', 'crib', + 'crock_pot', 'crossbar', 'crouton', 'crow', 'crowbar', 'crown', + 'crucifix', 'cruise_ship', 'police_cruiser', 'crumb', 'crutch', + 'cub_(animal)', 'cube', 'cucumber', 'cufflink', 'cup', 'trophy_cup', + 'cupboard', 'cupcake', 'hair_curler', 'curling_iron', 'curtain', + 'cushion', 'cylinder', 'cymbal', 'dagger', 'dalmatian', 'dartboard', + 'date_(fruit)', 'deck_chair', 'deer', 'dental_floss', 'desk', + 'detergent', 'diaper', 'diary', 'die', 'dinghy', 'dining_table', + 'tux', 'dish', 'dish_antenna', 'dishrag', 'dishtowel', 'dishwasher', + 'dishwasher_detergent', 'dispenser', 'diving_board', 'Dixie_cup', + 'dog', 'dog_collar', 'doll', 'dollar', 'dollhouse', 'dolphin', + 'domestic_ass', 'doorknob', 'doormat', 'doughnut', 'dove', + 'dragonfly', 'drawer', 'underdrawers', 'dress', 'dress_hat', + 'dress_suit', 'dresser', 'drill', 'drone', 'dropper', + 'drum_(musical_instrument)', 'drumstick', 'duck', 'duckling', + 'duct_tape', 'duffel_bag', 'dumbbell', 'dumpster', 'dustpan', 'eagle', + 'earphone', 'earplug', 'earring', 'easel', 'eclair', 'eel', 'egg', + 'egg_roll', 'egg_yolk', 'eggbeater', 'eggplant', 'electric_chair', + 'refrigerator', 'elephant', 'elk', 'envelope', 'eraser', 'escargot', + 'eyepatch', 'falcon', 'fan', 'faucet', 'fedora', 'ferret', + 'Ferris_wheel', 'ferry', 'fig_(fruit)', 'fighter_jet', 'figurine', + 'file_cabinet', 'file_(tool)', 'fire_alarm', 'fire_engine', + 'fire_extinguisher', 'fire_hose', 'fireplace', 'fireplug', + 'first-aid_kit', 'fish', 'fish_(food)', 'fishbowl', 'fishing_rod', + 'flag', 'flagpole', 'flamingo', 'flannel', 'flap', 'flash', + 'flashlight', 'fleece', 'flip-flop_(sandal)', 'flipper_(footwear)', + 'flower_arrangement', 'flute_glass', 'foal', 'folding_chair', + 'food_processor', 'football_(American)', 'football_helmet', + 'footstool', 'fork', 'forklift', 'freight_car', 'French_toast', + 'freshener', 'frisbee', 'frog', 'fruit_juice', 'frying_pan', 'fudge', + 'funnel', 'futon', 'gag', 'garbage', 'garbage_truck', 'garden_hose', + 'gargle', 'gargoyle', 'garlic', 'gasmask', 'gazelle', 'gelatin', + 'gemstone', 'generator', 'giant_panda', 'gift_wrap', 'ginger', + 'giraffe', 'cincture', 'glass_(drink_container)', 'globe', 'glove', + 'goat', 'goggles', 'goldfish', 'golf_club', 'golfcart', + 'gondola_(boat)', 'goose', 'gorilla', 'gourd', 'grape', 'grater', + 'gravestone', 'gravy_boat', 'green_bean', 'green_onion', 'griddle', + 'grill', 'grits', 'grizzly', 'grocery_bag', 'guitar', 'gull', 'gun', + 'hairbrush', 'hairnet', 'hairpin', 'halter_top', 'ham', 'hamburger', + 'hammer', 'hammock', 'hamper', 'hamster', 'hair_dryer', 'hand_glass', + 'hand_towel', 'handcart', 'handcuff', 'handkerchief', 'handle', + 'handsaw', 'hardback_book', 'harmonium', 'hat', 'hatbox', 'veil', + 'headband', 'headboard', 'headlight', 'headscarf', 'headset', + 'headstall_(for_horses)', 'heart', 'heater', 'helicopter', 'helmet', + 'heron', 'highchair', 'hinge', 'hippopotamus', 'hockey_stick', 'hog', + 'home_plate_(baseball)', 'honey', 'fume_hood', 'hook', 'hookah', + 'hornet', 'horse', 'hose', 'hot-air_balloon', 'hotplate', 'hot_sauce', + 'hourglass', 'houseboat', 'hummingbird', 'hummus', 'polar_bear', + 'icecream', 'popsicle', 'ice_maker', 'ice_pack', 'ice_skate', + 'igniter', 'inhaler', 'iPod', 'iron_(for_clothing)', 'ironing_board', + 'jacket', 'jam', 'jar', 'jean', 'jeep', 'jelly_bean', 'jersey', + 'jet_plane', 'jewel', 'jewelry', 'joystick', 'jumpsuit', 'kayak', + 'keg', 'kennel', 'kettle', 'key', 'keycard', 'kilt', 'kimono', + 'kitchen_sink', 'kitchen_table', 'kite', 'kitten', 'kiwi_fruit', + 'knee_pad', 'knife', 'knitting_needle', 'knob', 'knocker_(on_a_door)', + 'koala', 'lab_coat', 'ladder', 'ladle', 'ladybug', 'lamb_(animal)', + 'lamb-chop', 'lamp', 'lamppost', 'lampshade', 'lantern', 'lanyard', + 'laptop_computer', 'lasagna', 'latch', 'lawn_mower', 'leather', + 'legging_(clothing)', 'Lego', 'legume', 'lemon', 'lemonade', + 'lettuce', 'license_plate', 'life_buoy', 'life_jacket', 'lightbulb', + 'lightning_rod', 'lime', 'limousine', 'lion', 'lip_balm', 'liquor', + 'lizard', 'log', 'lollipop', 'speaker_(stereo_equipment)', 'loveseat', + 'machine_gun', 'magazine', 'magnet', 'mail_slot', 'mailbox_(at_home)', + 'mallard', 'mallet', 'mammoth', 'manatee', 'mandarin_orange', + 'manger', 'manhole', 'map', 'marker', 'martini', 'mascot', + 'mashed_potato', 'masher', 'mask', 'mast', 'mat_(gym_equipment)', + 'matchbox', 'mattress', 'measuring_cup', 'measuring_stick', + 'meatball', 'medicine', 'melon', 'microphone', 'microscope', + 'microwave_oven', 'milestone', 'milk', 'milk_can', 'milkshake', + 'minivan', 'mint_candy', 'mirror', 'mitten', 'mixer_(kitchen_tool)', + 'money', 'monitor_(computer_equipment) computer_monitor', 'monkey', + 'motor', 'motor_scooter', 'motor_vehicle', 'motorcycle', + 'mound_(baseball)', 'mouse_(computer_equipment)', 'mousepad', + 'muffin', 'mug', 'mushroom', 'music_stool', 'musical_instrument', + 'nailfile', 'napkin', 'neckerchief', 'necklace', 'necktie', 'needle', + 'nest', 'newspaper', 'newsstand', 'nightshirt', + 'nosebag_(for_animals)', 'noseband_(for_animals)', 'notebook', + 'notepad', 'nut', 'nutcracker', 'oar', 'octopus_(food)', + 'octopus_(animal)', 'oil_lamp', 'olive_oil', 'omelet', 'onion', + 'orange_(fruit)', 'orange_juice', 'ostrich', 'ottoman', 'oven', + 'overalls_(clothing)', 'owl', 'packet', 'inkpad', 'pad', 'paddle', + 'padlock', 'paintbrush', 'painting', 'pajamas', 'palette', + 'pan_(for_cooking)', 'pan_(metal_container)', 'pancake', 'pantyhose', + 'papaya', 'paper_plate', 'paper_towel', 'paperback_book', + 'paperweight', 'parachute', 'parakeet', 'parasail_(sports)', + 'parasol', 'parchment', 'parka', 'parking_meter', 'parrot', + 'passenger_car_(part_of_a_train)', 'passenger_ship', 'passport', + 'pastry', 'patty_(food)', 'pea_(food)', 'peach', 'peanut_butter', + 'pear', 'peeler_(tool_for_fruit_and_vegetables)', 'wooden_leg', + 'pegboard', 'pelican', 'pen', 'pencil', 'pencil_box', + 'pencil_sharpener', 'pendulum', 'penguin', 'pennant', 'penny_(coin)', + 'pepper', 'pepper_mill', 'perfume', 'persimmon', 'person', 'pet', + 'pew_(church_bench)', 'phonebook', 'phonograph_record', 'piano', + 'pickle', 'pickup_truck', 'pie', 'pigeon', 'piggy_bank', 'pillow', + 'pin_(non_jewelry)', 'pineapple', 'pinecone', 'ping-pong_ball', + 'pinwheel', 'tobacco_pipe', 'pipe', 'pistol', 'pita_(bread)', + 'pitcher_(vessel_for_liquid)', 'pitchfork', 'pizza', 'place_mat', + 'plate', 'platter', 'playpen', 'pliers', 'plow_(farm_equipment)', + 'plume', 'pocket_watch', 'pocketknife', 'poker_(fire_stirring_tool)', + 'pole', 'polo_shirt', 'poncho', 'pony', 'pool_table', 'pop_(soda)', + 'postbox_(public)', 'postcard', 'poster', 'pot', 'flowerpot', + 'potato', 'potholder', 'pottery', 'pouch', 'power_shovel', 'prawn', + 'pretzel', 'printer', 'projectile_(weapon)', 'projector', 'propeller', + 'prune', 'pudding', 'puffer_(fish)', 'puffin', 'pug-dog', 'pumpkin', + 'puncher', 'puppet', 'puppy', 'quesadilla', 'quiche', 'quilt', + 'rabbit', 'race_car', 'racket', 'radar', 'radiator', 'radio_receiver', + 'radish', 'raft', 'rag_doll', 'raincoat', 'ram_(animal)', 'raspberry', + 'rat', 'razorblade', 'reamer_(juicer)', 'rearview_mirror', 'receipt', + 'recliner', 'record_player', 'reflector', 'remote_control', + 'rhinoceros', 'rib_(food)', 'rifle', 'ring', 'river_boat', 'road_map', + 'robe', 'rocking_chair', 'rodent', 'roller_skate', 'Rollerblade', + 'rolling_pin', 'root_beer', 'router_(computer_equipment)', + 'rubber_band', 'runner_(carpet)', 'plastic_bag', + 'saddle_(on_an_animal)', 'saddle_blanket', 'saddlebag', 'safety_pin', + 'sail', 'salad', 'salad_plate', 'salami', 'salmon_(fish)', + 'salmon_(food)', 'salsa', 'saltshaker', 'sandal_(type_of_shoe)', + 'sandwich', 'satchel', 'saucepan', 'saucer', 'sausage', 'sawhorse', + 'saxophone', 'scale_(measuring_instrument)', 'scarecrow', 'scarf', + 'school_bus', 'scissors', 'scoreboard', 'scraper', 'screwdriver', + 'scrubbing_brush', 'sculpture', 'seabird', 'seahorse', 'seaplane', + 'seashell', 'sewing_machine', 'shaker', 'shampoo', 'shark', + 'sharpener', 'Sharpie', 'shaver_(electric)', 'shaving_cream', 'shawl', + 'shears', 'sheep', 'shepherd_dog', 'sherbert', 'shield', 'shirt', + 'shoe', 'shopping_bag', 'shopping_cart', 'short_pants', 'shot_glass', + 'shoulder_bag', 'shovel', 'shower_head', 'shower_cap', + 'shower_curtain', 'shredder_(for_paper)', 'signboard', 'silo', 'sink', + 'skateboard', 'skewer', 'ski', 'ski_boot', 'ski_parka', 'ski_pole', + 'skirt', 'skullcap', 'sled', 'sleeping_bag', 'sling_(bandage)', + 'slipper_(footwear)', 'smoothie', 'snake', 'snowboard', 'snowman', + 'snowmobile', 'soap', 'soccer_ball', 'sock', 'sofa', 'softball', + 'solar_array', 'sombrero', 'soup', 'soup_bowl', 'soupspoon', + 'sour_cream', 'soya_milk', 'space_shuttle', 'sparkler_(fireworks)', + 'spatula', 'spear', 'spectacles', 'spice_rack', 'spider', 'crawfish', + 'sponge', 'spoon', 'sportswear', 'spotlight', 'squid_(food)', + 'squirrel', 'stagecoach', 'stapler_(stapling_machine)', 'starfish', + 'statue_(sculpture)', 'steak_(food)', 'steak_knife', 'steering_wheel', + 'stepladder', 'step_stool', 'stereo_(sound_system)', 'stew', + 'stirrer', 'stirrup', 'stool', 'stop_sign', 'brake_light', 'stove', + 'strainer', 'strap', 'straw_(for_drinking)', 'strawberry', + 'street_sign', 'streetlight', 'string_cheese', 'stylus', 'subwoofer', + 'sugar_bowl', 'sugarcane_(plant)', 'suit_(clothing)', 'sunflower', + 'sunglasses', 'sunhat', 'surfboard', 'sushi', 'mop', 'sweat_pants', + 'sweatband', 'sweater', 'sweatshirt', 'sweet_potato', 'swimsuit', + 'sword', 'syringe', 'Tabasco_sauce', 'table-tennis_table', 'table', + 'table_lamp', 'tablecloth', 'tachometer', 'taco', 'tag', 'taillight', + 'tambourine', 'army_tank', 'tank_(storage_vessel)', + 'tank_top_(clothing)', 'tape_(sticky_cloth_or_paper)', 'tape_measure', + 'tapestry', 'tarp', 'tartan', 'tassel', 'tea_bag', 'teacup', + 'teakettle', 'teapot', 'teddy_bear', 'telephone', 'telephone_booth', + 'telephone_pole', 'telephoto_lens', 'television_camera', + 'television_set', 'tennis_ball', 'tennis_racket', 'tequila', + 'thermometer', 'thermos_bottle', 'thermostat', 'thimble', 'thread', + 'thumbtack', 'tiara', 'tiger', 'tights_(clothing)', 'timer', + 'tinfoil', 'tinsel', 'tissue_paper', 'toast_(food)', 'toaster', + 'toaster_oven', 'toilet', 'toilet_tissue', 'tomato', 'tongs', + 'toolbox', 'toothbrush', 'toothpaste', 'toothpick', 'cover', + 'tortilla', 'tow_truck', 'towel', 'towel_rack', 'toy', + 'tractor_(farm_equipment)', 'traffic_light', 'dirt_bike', + 'trailer_truck', 'train_(railroad_vehicle)', 'trampoline', 'tray', + 'trench_coat', 'triangle_(musical_instrument)', 'tricycle', 'tripod', + 'trousers', 'truck', 'truffle_(chocolate)', 'trunk', 'vat', 'turban', + 'turkey_(food)', 'turnip', 'turtle', 'turtleneck_(clothing)', + 'typewriter', 'umbrella', 'underwear', 'unicycle', 'urinal', 'urn', + 'vacuum_cleaner', 'vase', 'vending_machine', 'vent', 'vest', + 'videotape', 'vinegar', 'violin', 'vodka', 'volleyball', 'vulture', + 'waffle', 'waffle_iron', 'wagon', 'wagon_wheel', 'walking_stick', + 'wall_clock', 'wall_socket', 'wallet', 'walrus', 'wardrobe', + 'washbasin', 'automatic_washer', 'watch', 'water_bottle', + 'water_cooler', 'water_faucet', 'water_heater', 'water_jug', + 'water_gun', 'water_scooter', 'water_ski', 'water_tower', + 'watering_can', 'watermelon', 'weathervane', 'webcam', 'wedding_cake', + 'wedding_ring', 'wet_suit', 'wheel', 'wheelchair', 'whipped_cream', + 'whistle', 'wig', 'wind_chime', 'windmill', 'window_box_(for_plants)', + 'windshield_wiper', 'windsock', 'wine_bottle', 'wine_bucket', + 'wineglass', 'blinder_(for_horses)', 'wok', 'wolf', 'wooden_spoon', + 'wreath', 'wrench', 'wristband', 'wristlet', 'yacht', 'yogurt', + 'yoke_(animal_equipment)', 'zebra', 'zucchini'), + 'palette': + None + } + + def get_data_info(self, idx: int) -> dict: + """Get annotation by index and automatically call ``full_init`` if the + dataset has not been fully initialized. + + Args: + idx (int): The index of data. + + Returns: + dict: The idx-th annotation of the dataset. + """ + if self.serialize_data: + start_addr = 0 if idx == 0 else self.data_address[idx - 1].item() + end_addr = self.data_address[idx].item() + bytes = memoryview( + self.data_bytes[start_addr:end_addr]) # type: ignore + data_info = pickle.loads(bytes) # type: ignore + else: + data_info = copy.deepcopy(self.data_list[idx]) + + # Some codebase needs `sample_idx` of data information. Here we convert + # the idx to a positive number and save it in data information. + if idx >= 0: + data_info['sample_idx'] = idx + else: + data_info['sample_idx'] = len(self) + idx + + return data_info + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ # noqa: E501 + try: + import lvis + if getattr(lvis, '__version__', '0') >= '10.5.3': + warnings.warn( + 'mmlvis is deprecated, please install official lvis-api by "pip install git+https://github.com/lvis-dataset/lvis-api.git"', # noqa: E501 + UserWarning) + from lvis import LVIS + except ImportError: + raise ImportError( + 'Package lvis is not installed. Please run "pip install git+https://github.com/lvis-dataset/lvis-api.git".' # noqa: E501 + ) + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + self.lvis = LVIS(local_path) + self.cat_ids = self.lvis.get_cat_ids() + self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)} + self.cat_img_map = copy.deepcopy(self.lvis.cat_img_map) + img_ids = self.lvis.get_img_ids() + data_list = [] + total_ann_ids = [] + for img_id in img_ids: + raw_img_info = self.lvis.load_imgs([img_id])[0] + raw_img_info['img_id'] = img_id + + ann_ids = self.lvis.get_ann_ids(img_ids=[img_id]) + total_ann_ids.extend(ann_ids) + parsed_data_info = self.parse_data_info( + {'raw_img_info': raw_img_info}) + data_list.append(parsed_data_info) + if self.ANN_ID_UNIQUE: + assert len(set(total_ann_ids)) == len( + total_ann_ids + ), f"Annotation ids in '{self.ann_file}' are not unique!" + + del self.lvis + # print(data_list) + return data_list + + def parse_data_info(self, raw_data_info: dict) -> Union[dict, List[dict]]: + """Parse raw annotation to target format. + + Args: + raw_data_info (dict): Raw data information load from ``ann_file`` + + Returns: + Union[dict, List[dict]]: Parsed annotation. + """ + img_info = raw_data_info['raw_img_info'] + + data_info = {} + + # TODO: need to change data_prefix['img'] to data_prefix['img_path'] + img_path = osp.join(self.data_prefix['img'], img_info['file_name']) + if self.data_prefix.get('seg', None): + seg_map_path = osp.join( + self.data_prefix['seg'], + img_info['file_name'].rsplit('.', 1)[0] + self.seg_map_suffix) + else: + seg_map_path = None + data_info['img_path'] = img_path + data_info['img_id'] = img_info['img_id'] + data_info['seg_map_path'] = seg_map_path + data_info['height'] = img_info['height'] + data_info['width'] = img_info['width'] + + if self.return_classes: + data_info['text'] = self.metainfo['classes'] + data_info['custom_entities'] = True + + instances = [] + image_labels = [ + self.cat2label[x] for x in img_info['pos_category_ids'] + ] + for image_label in image_labels: + instance = {} + instance['bbox_label'] = image_label + instances.append(instance) + data_info['instances'] = instances + + return data_info + + def get_cat_ids(self, idx: int) -> List[int]: + """Get COCO category ids by index. + + Args: + idx (int): Index of data. + + Returns: + List[int]: All categories in the image of specified index. + """ + data_info = self.get_data_info(idx) + image_labels = [] + for instance in data_info['instances']: + image_labels.append(instance['bbox_label']) + + return image_labels diff --git a/mmdetection/projects/Detic_new/detic/iou_loss.py b/mmdetection/projects/Detic_new/detic/iou_loss.py new file mode 100644 index 00000000..349545cf --- /dev/null +++ b/mmdetection/projects/Detic_new/detic/iou_loss.py @@ -0,0 +1,125 @@ +import torch +from torch import nn + + +# support calculate IOULoss with box_pred +class IOULoss(nn.Module): + + def __init__(self, loc_loss_type='iou'): + super(IOULoss, self).__init__() + self.loc_loss_type = loc_loss_type + + def forward(self, pred, target, weight=None, reduction='sum'): + pred_left = pred[:, 0] + pred_top = pred[:, 1] + pred_right = pred[:, 2] + pred_bottom = pred[:, 3] + + target_left = target[:, 0] + target_top = target[:, 1] + target_right = target[:, 2] + target_bottom = target[:, 3] + + target_aera = (target_left + target_right) * ( + target_top + target_bottom) + pred_aera = (pred_left + pred_right) * (pred_top + pred_bottom) + + w_intersect = torch.min(pred_left, target_left) + torch.min( + pred_right, target_right) + h_intersect = torch.min(pred_bottom, target_bottom) + torch.min( + pred_top, target_top) + + g_w_intersect = torch.max(pred_left, target_left) + torch.max( + pred_right, target_right) + g_h_intersect = torch.max(pred_bottom, target_bottom) + torch.max( + pred_top, target_top) + ac_uion = g_w_intersect * g_h_intersect + + area_intersect = w_intersect * h_intersect + area_union = target_aera + pred_aera - area_intersect + + ious = (area_intersect + 1.0) / (area_union + 1.0) + gious = ious - (ac_uion - area_union) / ac_uion + if self.loc_loss_type == 'iou': + losses = -torch.log(ious) + elif self.loc_loss_type == 'linear_iou': + losses = 1 - ious + elif self.loc_loss_type == 'giou': + losses = 1 - gious + else: + raise NotImplementedError + + if weight is not None: + losses = losses * weight + else: + losses = losses + + if reduction == 'sum': + return losses.sum() + elif reduction == 'batch': + return losses.sum(dim=[1]) + elif reduction == 'none': + return losses + else: + raise NotImplementedError + + +def giou_loss( + boxes1: torch.Tensor, + boxes2: torch.Tensor, + reduction: str = 'none', + eps: float = 1e-7, +) -> torch.Tensor: + """Generalized Intersection over Union Loss (Hamid Rezatofighi et. + + al) + https://arxiv.org/abs/1902.09630 + Gradient-friendly IoU loss with an additional penalty that is + non-zero when the boxes do not overlap and scales with the size + of their smallest enclosing box. This loss is symmetric, so the + boxes1 and boxes2 arguments are interchangeable. + Args: + boxes1, boxes2 (Tensor): box locations in XYXY format, shape + (N, 4) or (4,). + reduction: 'none' | 'mean' | 'sum' + 'none': No reduction will be applied to the output. + 'mean': The output will be averaged. + 'sum': The output will be summed. + eps (float): small number to prevent division by zero + """ + + x1, y1, x2, y2 = boxes1.unbind(dim=-1) + x1g, y1g, x2g, y2g = boxes2.unbind(dim=-1) + + assert (x2 >= x1).all(), 'bad box: x1 larger than x2' + assert (y2 >= y1).all(), 'bad box: y1 larger than y2' + + # Intersection keypoints + xkis1 = torch.max(x1, x1g) + ykis1 = torch.max(y1, y1g) + xkis2 = torch.min(x2, x2g) + ykis2 = torch.min(y2, y2g) + + intsctk = torch.zeros_like(x1) + mask = (ykis2 > ykis1) & (xkis2 > xkis1) + intsctk[mask] = (xkis2[mask] - xkis1[mask]) * (ykis2[mask] - ykis1[mask]) + unionk = (x2 - x1) * (y2 - y1) + (x2g - x1g) * (y2g - y1g) - intsctk + iouk = intsctk / (unionk + eps) + + # smallest enclosing box + xc1 = torch.min(x1, x1g) + yc1 = torch.min(y1, y1g) + xc2 = torch.max(x2, x2g) + yc2 = torch.max(y2, y2g) + + area_c = (xc2 - xc1) * (yc2 - yc1) + miouk = iouk - ((area_c - unionk) / (area_c + eps)) + + loss = 1 - miouk + + if reduction == 'mean': + loss = loss.mean() + elif reduction == 'sum': + loss = loss.sum() + + return loss diff --git a/mmdetection/projects/Detic_new/detic/zero_shot_classifier.py b/mmdetection/projects/Detic_new/detic/zero_shot_classifier.py new file mode 100644 index 00000000..cb9946d5 --- /dev/null +++ b/mmdetection/projects/Detic_new/detic/zero_shot_classifier.py @@ -0,0 +1,73 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +import numpy as np +import torch +from torch import nn +from torch.nn import functional as F + +from mmdet.registry import MODELS + + +@MODELS.register_module() +class ZeroShotClassifier(nn.Module): + + def __init__( + self, + in_features: int, + out_features: int, # num_classes + zs_weight_path: str, + zs_weight_dim: int = 512, + use_bias: float = 0.0, + norm_weight: bool = True, + norm_temperature: float = 50.0, + ): + super().__init__() + num_classes = out_features + self.norm_weight = norm_weight + self.norm_temperature = norm_temperature + + self.use_bias = use_bias < 0 + if self.use_bias: + self.cls_bias = nn.Parameter(torch.ones(1) * use_bias) + + self.linear = nn.Linear(in_features, zs_weight_dim) + + if zs_weight_path == 'rand': + zs_weight = torch.randn((zs_weight_dim, num_classes)) + nn.init.normal_(zs_weight, std=0.01) + else: + zs_weight = torch.tensor( + np.load(zs_weight_path), + dtype=torch.float32).permute(1, 0).contiguous() # D x C + zs_weight = torch.cat( + [zs_weight, zs_weight.new_zeros( + (zs_weight_dim, 1))], dim=1) # D x (C + 1) + + if self.norm_weight: + zs_weight = F.normalize(zs_weight, p=2, dim=0) + + if zs_weight_path == 'rand': + self.zs_weight = nn.Parameter(zs_weight) + else: + self.register_buffer('zs_weight', zs_weight) + + assert self.zs_weight.shape[1] == num_classes + 1, self.zs_weight.shape + + def forward(self, x, classifier=None): + ''' + Inputs: + x: B x D' + classifier_info: (C', C' x D) + ''' + x = self.linear(x) + if classifier is not None: + zs_weight = classifier.permute(1, 0).contiguous() # D x C' + zs_weight = F.normalize(zs_weight, p=2, dim=0) \ + if self.norm_weight else zs_weight + else: + zs_weight = self.zs_weight + if self.norm_weight: + x = self.norm_temperature * F.normalize(x, p=2, dim=1) + x = torch.mm(x, zs_weight) + if self.use_bias: + x = x + self.cls_bias + return x diff --git a/mmdetection/projects/DiffusionDet/README.md b/mmdetection/projects/DiffusionDet/README.md new file mode 100644 index 00000000..5542d9a5 --- /dev/null +++ b/mmdetection/projects/DiffusionDet/README.md @@ -0,0 +1,172 @@ +## Description + +This is an implementation of [DiffusionDet](https://github.com/ShoufaChen/DiffusionDet) based on [MMDetection](https://github.com/open-mmlab/mmdetection/tree/main), [MMCV](https://github.com/open-mmlab/mmcv), and [MMEngine](https://github.com/open-mmlab/mmengine). + +
    + +
    + +## Usage + + + +### Comparison of results + +1. Download the [DiffusionDet released model](https://github.com/ShoufaChen/DiffusionDet#models). + +2. Convert model from DiffusionDet version to MMDetection version. We give a [sample script](model_converters/diffusiondet_resnet_to_mmdet.py) + to convert `DiffusionDet-resnet50` model. Users can download the corresponding models from [here](https://github.com/ShoufaChen/DiffusionDet/releases/download/v0.1/diffdet_coco_res50.pth). + + ```shell + python projects/DiffusionDet/model_converters/diffusiondet_resnet_to_mmdet.py ${DiffusionDet ckpt path} ${MMDetectron ckpt path} + ``` + +3. Testing the model in MMDetection. + + ```shell + python tools/test.py projects/DiffusionDet/configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py ${CHECKPOINT_PATH} + ``` + +**Note:** During inference time, DiffusionDet will randomly generate noisy boxes, +which may affect the AP results. If users want to get the same result every inference time, setting seed is a good way. +We give a table to compare the inference results on `ResNet50-500-proposals` between DiffusionDet and MMDetection. + +| Config | Step | AP | +| :---------------------------------------------------------------------------------------------------------------------: | :--: | :-------: | +| [DiffusionDet](https://github.com/ShoufaChen/DiffusionDet/blob/main/configs/diffdet.coco.res50.yaml) (released results) | 1 | 45.5 | +| [DiffusionDet](https://github.com/ShoufaChen/DiffusionDet/blob/main/configs/diffdet.coco.res50.yaml) (seed=0) | 1 | 45.66 | +| [MMDetection](configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py) (seed=0) | 1 | 45.7 | +| [MMDetection](configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py) (random seed) | 1 | 45.6~45.8 | +| [DiffusionDet](https://github.com/ShoufaChen/DiffusionDet/blob/main/configs/diffdet.coco.res50.yaml) (released results) | 4 | 46.1 | +| [DiffusionDet](https://github.com/ShoufaChen/DiffusionDet/blob/main/configs/diffdet.coco.res50.yaml) (seed=0) | 4 | 46.38 | +| [MMDetection](configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py) (seed=0) | 4 | 46.4 | +| [MMDetection](configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py) (random seed) | 4 | 46.2~46.4 | + +- `seed=0` means hard set seed before generating random boxes. + ```python + # hard set seed=0 before generating random boxes + seed = 0 + random.seed(seed) + torch.manual_seed(seed) + # torch.cuda.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + ... + noise_bboxes_raw = torch.randn( + (self.num_proposals, 4), + device=device) + ... + ``` +- `random seed` means do not hard set seed before generating random boxes. + +### Training commands + +In MMDetection's root directory, run the following command to train the model: + +```bash +python tools/train.py projects/DiffusionDet/configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py +``` + +For multi-gpu training, run: + +```bash +python -m torch.distributed.launch --nnodes=1 --node_rank=0 --nproc_per_node=${NUM_GPUS} --master_port=29506 --master_addr="127.0.0.1" tools/train.py projects/DiffusionDet/configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py +``` + +### Testing commands + +In MMDetection's root directory, run the following command to test the model: + +```bash +# for 1 step inference +# test command +python tools/test.py projects/DiffusionDet/configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py ${CHECKPOINT_PATH} + +# for 4 steps inference + +# test command +python tools/test.py projects/DiffusionDet/configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py ${CHECKPOINT_PATH} --cfg-options model.bbox_head.sampling_timesteps=4 +``` + +**Note:** There is no difference between 1 step or 4 steps (or other multi-step) during training. Users can set different steps during inference through `--cfg-options model.bbox_head.sampling_timesteps=${STEPS}`, but larger `sampling_timesteps` will affect the inference time. + +## Results + +Here we provide the baseline version of DiffusionDet with ResNet50 backbone. + +To find more variants, please visit the [official model zoo](https://github.com/ShoufaChen/DiffusionDet#models). + +| Backbone | Style | Lr schd | AP (Step=1) | AP (Step=4) | Config | Download | +| :------: | :-----: | :-----: | :---------: | :---------: | :----------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| R-50 | PyTorch | 450k | 44.5 | 46.2 | [config](./configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py) | [model](https://download.openmmlab.com/mmdetection/v3.0/diffusiondet/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco_20230215_090925-7d6ed504.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/diffusiondet/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco_20230215_090925.log.json) | + +## License + +DiffusionDet is under the [CC-BY-NC 4.0 license](https://github.com/ShoufaChen/DiffusionDet/blob/main/LICENSE). Users should be careful about adopting these features in any commercial matters. + +## Citation + +If you find DiffusionDet is useful in your research or applications, please consider giving a star 🌟 to the [official repository](https://github.com/ShoufaChen/DiffusionDet) and citing DiffusionDet by the following BibTeX entry. + +```BibTeX +@article{chen2022diffusiondet, + title={DiffusionDet: Diffusion Model for Object Detection}, + author={Chen, Shoufa and Sun, Peize and Song, Yibing and Luo, Ping}, + journal={arXiv preprint arXiv:2211.09788}, + year={2022} +} +``` + +## Checklist + + + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + + + - [x] Basic docstrings & proper citation + + + + - [x] Test-time correctness + + + + - [x] A full README + + + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + + + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + + + - [ ] Unit tests + + + + - [ ] Code polishing + + + + - [ ] Metafile.yml + + + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + + + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/mmdetection/projects/DiffusionDet/configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py b/mmdetection/projects/DiffusionDet/configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py new file mode 100644 index 00000000..187cdc39 --- /dev/null +++ b/mmdetection/projects/DiffusionDet/configs/diffusiondet_r50_fpn_500-proposals_1-step_crop-ms-480-800-450k_coco.py @@ -0,0 +1,185 @@ +_base_ = [ + 'mmdet::_base_/datasets/coco_detection.py', + 'mmdet::_base_/schedules/schedule_1x.py', + 'mmdet::_base_/default_runtime.py' +] + +custom_imports = dict( + imports=['projects.DiffusionDet.diffusiondet'], allow_failed_imports=False) + +# model settings +model = dict( + type='DiffusionDet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=32), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=4), + bbox_head=dict( + type='DynamicDiffusionDetHead', + num_classes=80, + feat_channels=256, + num_proposals=500, + num_heads=6, + deep_supervision=True, + prior_prob=0.01, + snr_scale=2.0, + sampling_timesteps=1, + ddim_sampling_eta=1.0, + single_head=dict( + type='SingleDiffusionDetHead', + num_cls_convs=1, + num_reg_convs=3, + dim_feedforward=2048, + num_heads=8, + dropout=0.0, + act_cfg=dict(type='ReLU', inplace=True), + dynamic_conv=dict(dynamic_dim=64, dynamic_num=2)), + roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + # criterion + criterion=dict( + type='DiffusionDetCriterion', + num_classes=80, + assigner=dict( + type='DiffusionDetMatcher', + match_costs=[ + dict( + type='FocalLossCost', + alpha=0.25, + gamma=2.0, + weight=2.0, + eps=1e-8), + dict(type='BBoxL1Cost', weight=5.0, box_format='xyxy'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ], + center_radius=2.5, + candidate_topk=5), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + alpha=0.25, + gamma=2.0, + reduction='sum', + loss_weight=2.0), + loss_bbox=dict(type='L1Loss', reduction='sum', loss_weight=5.0), + loss_giou=dict(type='GIoULoss', reduction='sum', + loss_weight=2.0))), + test_cfg=dict( + use_nms=True, + score_thr=0.5, + min_bbox_size=0, + nms=dict(type='nms', iou_threshold=0.5), + )) + +backend = 'pillow' +train_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args, + imdecode_backend=backend), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[[ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True, + backend=backend), + ], + [ + dict( + type='RandomChoiceResize', + scales=[(400, 1333), (500, 1333), (600, 1333)], + keep_ratio=True, + backend=backend), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), + (576, 1333), (608, 1333), (640, 1333), + (672, 1333), (704, 1333), (736, 1333), + (768, 1333), (800, 1333)], + keep_ratio=True, + backend=backend) + ]]), + dict(type='PackDetInputs') +] + +test_pipeline = [ + dict( + type='LoadImageFromFile', + backend_args=_base_.backend_args, + imdecode_backend=backend), + dict(type='Resize', scale=(1333, 800), keep_ratio=True, backend=backend), + # If you don't have a gt annotation, delete the pipeline + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] +train_dataloader = dict( + sampler=dict(type='InfiniteSampler'), + dataset=dict( + filter_cfg=dict(filter_empty_gt=False, min_size=1e-5), + pipeline=train_pipeline)) + +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + _delete_=True, type='AdamW', lr=0.000025, weight_decay=0.0001), + clip_grad=dict(max_norm=1.0, norm_type=2)) +train_cfg = dict( + _delete_=True, + type='IterBasedTrainLoop', + max_iters=450000, + val_interval=75000) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.01, by_epoch=False, begin=0, end=1000), + dict( + type='MultiStepLR', + begin=0, + end=450000, + by_epoch=False, + milestones=[350000, 420000], + gamma=0.1) +] + +default_hooks = dict( + checkpoint=dict(by_epoch=False, interval=75000, max_keep_ckpts=3)) +log_processor = dict(by_epoch=False) diff --git a/mmdetection/projects/DiffusionDet/diffusiondet/__init__.py b/mmdetection/projects/DiffusionDet/diffusiondet/__init__.py new file mode 100644 index 00000000..35d60322 --- /dev/null +++ b/mmdetection/projects/DiffusionDet/diffusiondet/__init__.py @@ -0,0 +1,10 @@ +from .diffusiondet import DiffusionDet +from .head import (DynamicConv, DynamicDiffusionDetHead, + SingleDiffusionDetHead, SinusoidalPositionEmbeddings) +from .loss import DiffusionDetCriterion, DiffusionDetMatcher + +__all__ = [ + 'DiffusionDet', 'DynamicDiffusionDetHead', 'SingleDiffusionDetHead', + 'SinusoidalPositionEmbeddings', 'DynamicConv', 'DiffusionDetCriterion', + 'DiffusionDetMatcher' +] diff --git a/mmdetection/projects/DiffusionDet/diffusiondet/diffusiondet.py b/mmdetection/projects/DiffusionDet/diffusiondet/diffusiondet.py new file mode 100644 index 00000000..5a46ddf7 --- /dev/null +++ b/mmdetection/projects/DiffusionDet/diffusiondet/diffusiondet.py @@ -0,0 +1,26 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.models import SingleStageDetector +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig + + +@MODELS.register_module() +class DiffusionDet(SingleStageDetector): + """Implementation of `DiffusionDet <>`_""" + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/projects/DiffusionDet/diffusiondet/head.py b/mmdetection/projects/DiffusionDet/diffusiondet/head.py new file mode 100644 index 00000000..794c9c9f --- /dev/null +++ b/mmdetection/projects/DiffusionDet/diffusiondet/head.py @@ -0,0 +1,1034 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +# Modified from https://github.com/ShoufaChen/DiffusionDet/blob/main/diffusiondet/detector.py # noqa +# Modified from https://github.com/ShoufaChen/DiffusionDet/blob/main/diffusiondet/head.py # noqa + +# This work is licensed under the CC-BY-NC 4.0 License. +# Users should be careful about adopting these features in any commercial matters. # noqa +# For more details, please refer to https://github.com/ShoufaChen/DiffusionDet/blob/main/LICENSE # noqa + +import copy +import math +import random +import warnings +from typing import Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import build_activation_layer +from mmcv.ops import batched_nms +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures import SampleList +from mmdet.structures.bbox import (bbox2roi, bbox_cxcywh_to_xyxy, + bbox_xyxy_to_cxcywh, get_box_wh, + scale_boxes) +from mmdet.utils import InstanceList + +_DEFAULT_SCALE_CLAMP = math.log(100000.0 / 16) + + +def cosine_beta_schedule(timesteps, s=0.008): + """Cosine schedule as proposed in + https://openreview.net/forum?id=-NEXDKk8gZ.""" + steps = timesteps + 1 + x = torch.linspace(0, timesteps, steps, dtype=torch.float64) + alphas_cumprod = torch.cos( + ((x / timesteps) + s) / (1 + s) * math.pi * 0.5)**2 + alphas_cumprod = alphas_cumprod / alphas_cumprod[0] + betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1]) + return torch.clip(betas, 0, 0.999) + + +def extract(a, t, x_shape): + """extract the appropriate t index for a batch of indices.""" + batch_size = t.shape[0] + out = a.gather(-1, t) + return out.reshape(batch_size, *((1, ) * (len(x_shape) - 1))) + + +class SinusoidalPositionEmbeddings(nn.Module): + + def __init__(self, dim): + super().__init__() + self.dim = dim + + def forward(self, time): + device = time.device + half_dim = self.dim // 2 + embeddings = math.log(10000) / (half_dim - 1) + embeddings = torch.exp( + torch.arange(half_dim, device=device) * -embeddings) + embeddings = time[:, None] * embeddings[None, :] + embeddings = torch.cat((embeddings.sin(), embeddings.cos()), dim=-1) + return embeddings + + +@MODELS.register_module() +class DynamicDiffusionDetHead(nn.Module): + + def __init__(self, + num_classes=80, + feat_channels=256, + num_proposals=500, + num_heads=6, + prior_prob=0.01, + snr_scale=2.0, + timesteps=1000, + sampling_timesteps=1, + self_condition=False, + box_renewal=True, + use_ensemble=True, + deep_supervision=True, + ddim_sampling_eta=1.0, + criterion=dict( + type='DiffusionDetCriterion', + num_classes=80, + assigner=dict( + type='DiffusionDetMatcher', + match_costs=[ + dict( + type='FocalLossCost', + alpha=2.0, + gamma=0.25, + weight=2.0), + dict( + type='BBoxL1Cost', + weight=5.0, + box_format='xyxy'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ], + center_radius=2.5, + candidate_topk=5), + ), + single_head=dict( + type='DiffusionDetHead', + num_cls_convs=1, + num_reg_convs=3, + dim_feedforward=2048, + num_heads=8, + dropout=0.0, + act_cfg=dict(type='ReLU'), + dynamic_conv=dict(dynamic_dim=64, dynamic_num=2)), + roi_extractor=dict( + type='SingleRoIExtractor', + roi_layer=dict( + type='RoIAlign', output_size=7, sampling_ratio=2), + out_channels=256, + featmap_strides=[4, 8, 16, 32]), + test_cfg=None, + **kwargs) -> None: + super().__init__() + self.roi_extractor = MODELS.build(roi_extractor) + + self.num_classes = num_classes + self.num_classes = num_classes + self.feat_channels = feat_channels + self.num_proposals = num_proposals + self.num_heads = num_heads + # Build Diffusion + assert isinstance(timesteps, int), 'The type of `timesteps` should ' \ + f'be int but got {type(timesteps)}' + assert sampling_timesteps <= timesteps + self.timesteps = timesteps + self.sampling_timesteps = sampling_timesteps + self.snr_scale = snr_scale + + self.ddim_sampling = self.sampling_timesteps < self.timesteps + self.ddim_sampling_eta = ddim_sampling_eta + self.self_condition = self_condition + self.box_renewal = box_renewal + self.use_ensemble = use_ensemble + + self._build_diffusion() + + # Build assigner + assert criterion.get('assigner', None) is not None + assigner = TASK_UTILS.build(criterion.get('assigner')) + # Init parameters. + self.use_focal_loss = assigner.use_focal_loss + self.use_fed_loss = assigner.use_fed_loss + + # build criterion + criterion.update(deep_supervision=deep_supervision) + self.criterion = TASK_UTILS.build(criterion) + + # Build Dynamic Head. + single_head_ = single_head.copy() + single_head_num_classes = single_head_.get('num_classes', None) + if single_head_num_classes is None: + single_head_.update(num_classes=num_classes) + else: + if single_head_num_classes != num_classes: + warnings.warn( + 'The `num_classes` of `DynamicDiffusionDetHead` and ' + '`SingleDiffusionDetHead` should be same, changing ' + f'`single_head.num_classes` to {num_classes}') + single_head_.update(num_classes=num_classes) + + single_head_feat_channels = single_head_.get('feat_channels', None) + if single_head_feat_channels is None: + single_head_.update(feat_channels=feat_channels) + else: + if single_head_feat_channels != feat_channels: + warnings.warn( + 'The `feat_channels` of `DynamicDiffusionDetHead` and ' + '`SingleDiffusionDetHead` should be same, changing ' + f'`single_head.feat_channels` to {feat_channels}') + single_head_.update(feat_channels=feat_channels) + + default_pooler_resolution = roi_extractor['roi_layer'].get( + 'output_size') + assert default_pooler_resolution is not None + single_head_pooler_resolution = single_head_.get('pooler_resolution') + if single_head_pooler_resolution is None: + single_head_.update(pooler_resolution=default_pooler_resolution) + else: + if single_head_pooler_resolution != default_pooler_resolution: + warnings.warn( + 'The `pooler_resolution` of `DynamicDiffusionDetHead` ' + 'and `SingleDiffusionDetHead` should be same, changing ' + f'`single_head.pooler_resolution` to {num_classes}') + single_head_.update( + pooler_resolution=default_pooler_resolution) + + single_head_.update( + use_focal_loss=self.use_focal_loss, use_fed_loss=self.use_fed_loss) + single_head_module = MODELS.build(single_head_) + + self.num_heads = num_heads + self.head_series = nn.ModuleList( + [copy.deepcopy(single_head_module) for _ in range(num_heads)]) + + self.deep_supervision = deep_supervision + + # Gaussian random feature embedding layer for time + time_dim = feat_channels * 4 + self.time_mlp = nn.Sequential( + SinusoidalPositionEmbeddings(feat_channels), + nn.Linear(feat_channels, time_dim), nn.GELU(), + nn.Linear(time_dim, time_dim)) + + self.prior_prob = prior_prob + self.test_cfg = test_cfg + self.use_nms = self.test_cfg.get('use_nms', True) + self._init_weights() + + def _init_weights(self): + # init all parameters. + bias_value = -math.log((1 - self.prior_prob) / self.prior_prob) + for p in self.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + + # initialize the bias for focal loss and fed loss. + if self.use_focal_loss or self.use_fed_loss: + if p.shape[-1] == self.num_classes or \ + p.shape[-1] == self.num_classes + 1: + nn.init.constant_(p, bias_value) + + def _build_diffusion(self): + betas = cosine_beta_schedule(self.timesteps) + alphas = 1. - betas + alphas_cumprod = torch.cumprod(alphas, dim=0) + alphas_cumprod_prev = F.pad(alphas_cumprod[:-1], (1, 0), value=1.) + + self.register_buffer('betas', betas) + self.register_buffer('alphas_cumprod', alphas_cumprod) + self.register_buffer('alphas_cumprod_prev', alphas_cumprod_prev) + + # calculations for diffusion q(x_t | x_{t-1}) and others + self.register_buffer('sqrt_alphas_cumprod', torch.sqrt(alphas_cumprod)) + self.register_buffer('sqrt_one_minus_alphas_cumprod', + torch.sqrt(1. - alphas_cumprod)) + self.register_buffer('log_one_minus_alphas_cumprod', + torch.log(1. - alphas_cumprod)) + self.register_buffer('sqrt_recip_alphas_cumprod', + torch.sqrt(1. / alphas_cumprod)) + self.register_buffer('sqrt_recipm1_alphas_cumprod', + torch.sqrt(1. / alphas_cumprod - 1)) + + # calculations for posterior q(x_{t-1} | x_t, x_0) + # equal to 1. / (1. / (1. - alpha_cumprod_tm1) + alpha_t / beta_t) + posterior_variance = betas * (1. - alphas_cumprod_prev) / ( + 1. - alphas_cumprod) + self.register_buffer('posterior_variance', posterior_variance) + + # log calculation clipped because the posterior variance is 0 at + # the beginning of the diffusion chain + self.register_buffer('posterior_log_variance_clipped', + torch.log(posterior_variance.clamp(min=1e-20))) + self.register_buffer( + 'posterior_mean_coef1', + betas * torch.sqrt(alphas_cumprod_prev) / (1. - alphas_cumprod)) + self.register_buffer('posterior_mean_coef2', + (1. - alphas_cumprod_prev) * torch.sqrt(alphas) / + (1. - alphas_cumprod)) + + def forward(self, features, init_bboxes, init_t, init_features=None): + time = self.time_mlp(init_t, ) + + inter_class_logits = [] + inter_pred_bboxes = [] + + bs = len(features[0]) + bboxes = init_bboxes + + if init_features is not None: + init_features = init_features[None].repeat(1, bs, 1) + proposal_features = init_features.clone() + else: + proposal_features = None + + for head_idx, single_head in enumerate(self.head_series): + class_logits, pred_bboxes, proposal_features = single_head( + features, bboxes, proposal_features, self.roi_extractor, time) + if self.deep_supervision: + inter_class_logits.append(class_logits) + inter_pred_bboxes.append(pred_bboxes) + bboxes = pred_bboxes.detach() + + if self.deep_supervision: + return torch.stack(inter_class_logits), torch.stack( + inter_pred_bboxes) + else: + return class_logits[None, ...], pred_bboxes[None, ...] + + def loss(self, x: Tuple[Tensor], batch_data_samples: SampleList) -> dict: + """Perform forward propagation and loss calculation of the detection + head on the features of the upstream network. + + Args: + x (tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + + Returns: + dict: A dictionary of loss components. + """ + prepare_outputs = self.prepare_training_targets(batch_data_samples) + (batch_gt_instances, batch_pred_instances, batch_gt_instances_ignore, + batch_img_metas) = prepare_outputs + + batch_diff_bboxes = torch.stack([ + pred_instances.diff_bboxes_abs + for pred_instances in batch_pred_instances + ]) + batch_time = torch.stack( + [pred_instances.time for pred_instances in batch_pred_instances]) + + pred_logits, pred_bboxes = self(x, batch_diff_bboxes, batch_time) + + output = { + 'pred_logits': pred_logits[-1], + 'pred_boxes': pred_bboxes[-1] + } + if self.deep_supervision: + output['aux_outputs'] = [{ + 'pred_logits': a, + 'pred_boxes': b + } for a, b in zip(pred_logits[:-1], pred_bboxes[:-1])] + + losses = self.criterion(output, batch_gt_instances, batch_img_metas) + return losses + + def prepare_training_targets(self, batch_data_samples): + # hard-setting seed to keep results same (if necessary) + # random.seed(0) + # torch.manual_seed(0) + # torch.cuda.manual_seed_all(0) + # torch.backends.cudnn.deterministic = True + # torch.backends.cudnn.benchmark = False + + batch_gt_instances = [] + batch_pred_instances = [] + batch_gt_instances_ignore = [] + batch_img_metas = [] + for data_sample in batch_data_samples: + img_meta = data_sample.metainfo + gt_instances = data_sample.gt_instances + + gt_bboxes = gt_instances.bboxes + h, w = img_meta['img_shape'] + image_size = gt_bboxes.new_tensor([w, h, w, h]) + + norm_gt_bboxes = gt_bboxes / image_size + norm_gt_bboxes_cxcywh = bbox_xyxy_to_cxcywh(norm_gt_bboxes) + pred_instances = self.prepare_diffusion(norm_gt_bboxes_cxcywh, + image_size) + + gt_instances.set_metainfo(dict(image_size=image_size)) + gt_instances.norm_bboxes_cxcywh = norm_gt_bboxes_cxcywh + + batch_gt_instances.append(gt_instances) + batch_pred_instances.append(pred_instances) + batch_img_metas.append(data_sample.metainfo) + if 'ignored_instances' in data_sample: + batch_gt_instances_ignore.append(data_sample.ignored_instances) + else: + batch_gt_instances_ignore.append(None) + return (batch_gt_instances, batch_pred_instances, + batch_gt_instances_ignore, batch_img_metas) + + def prepare_diffusion(self, gt_boxes, image_size): + device = gt_boxes.device + time = torch.randint( + 0, self.timesteps, (1, ), dtype=torch.long, device=device) + noise = torch.randn(self.num_proposals, 4, device=device) + + num_gt = gt_boxes.shape[0] + if num_gt < self.num_proposals: + # 3 * sigma = 1/2 --> sigma: 1/6 + box_placeholder = torch.randn( + self.num_proposals - num_gt, 4, device=device) / 6. + 0.5 + box_placeholder[:, 2:] = torch.clip( + box_placeholder[:, 2:], min=1e-4) + x_start = torch.cat((gt_boxes, box_placeholder), dim=0) + else: + select_mask = [True] * self.num_proposals + \ + [False] * (num_gt - self.num_proposals) + random.shuffle(select_mask) + x_start = gt_boxes[select_mask] + + x_start = (x_start * 2. - 1.) * self.snr_scale + + # noise sample + x = self.q_sample(x_start=x_start, time=time, noise=noise) + + x = torch.clamp(x, min=-1 * self.snr_scale, max=self.snr_scale) + x = ((x / self.snr_scale) + 1) / 2. + + diff_bboxes = bbox_cxcywh_to_xyxy(x) + # convert to abs bboxes + diff_bboxes_abs = diff_bboxes * image_size + + metainfo = dict(time=time.squeeze(-1)) + pred_instances = InstanceData(metainfo=metainfo) + pred_instances.diff_bboxes = diff_bboxes + pred_instances.diff_bboxes_abs = diff_bboxes_abs + pred_instances.noise = noise + return pred_instances + + # forward diffusion + def q_sample(self, x_start, time, noise=None): + if noise is None: + noise = torch.randn_like(x_start) + + x_start_shape = x_start.shape + + sqrt_alphas_cumprod_t = extract(self.sqrt_alphas_cumprod, time, + x_start_shape) + sqrt_one_minus_alphas_cumprod_t = extract( + self.sqrt_one_minus_alphas_cumprod, time, x_start_shape) + + return sqrt_alphas_cumprod_t * x_start + \ + sqrt_one_minus_alphas_cumprod_t * noise + + def predict(self, + x: Tuple[Tensor], + batch_data_samples: SampleList, + rescale: bool = False) -> InstanceList: + """Perform forward propagation of the detection head and predict + detection results on the features of the upstream network. + + Args: + x (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + batch_data_samples (List[:obj:`DetDataSample`]): The Data + Samples. It usually includes information such as + `gt_instance`, `gt_panoptic_seg` and `gt_sem_seg`. + rescale (bool, optional): Whether to rescale the results. + Defaults to False. + + Returns: + list[obj:`InstanceData`]: Detection results of each image + after the post process. + """ + # hard-setting seed to keep results same (if necessary) + # seed = 0 + # random.seed(seed) + # torch.manual_seed(seed) + # torch.cuda.manual_seed_all(seed) + + device = x[-1].device + + batch_img_metas = [ + data_samples.metainfo for data_samples in batch_data_samples + ] + + (time_pairs, batch_noise_bboxes, batch_noise_bboxes_raw, + batch_image_size) = self.prepare_testing_targets( + batch_img_metas, device) + + predictions = self.predict_by_feat( + x, + time_pairs=time_pairs, + batch_noise_bboxes=batch_noise_bboxes, + batch_noise_bboxes_raw=batch_noise_bboxes_raw, + batch_image_size=batch_image_size, + device=device, + batch_img_metas=batch_img_metas) + return predictions + + def predict_by_feat(self, + x, + time_pairs, + batch_noise_bboxes, + batch_noise_bboxes_raw, + batch_image_size, + device, + batch_img_metas=None, + cfg=None, + rescale=True): + + batch_size = len(batch_img_metas) + + cfg = self.test_cfg if cfg is None else cfg + cfg = copy.deepcopy(cfg) + + ensemble_score, ensemble_label, ensemble_coord = [], [], [] + for time, time_next in time_pairs: + batch_time = torch.full((batch_size, ), + time, + device=device, + dtype=torch.long) + # self_condition = x_start if self.self_condition else None + pred_logits, pred_bboxes = self(x, batch_noise_bboxes, batch_time) + + x_start = pred_bboxes[-1] + + x_start = x_start / batch_image_size[:, None, :] + x_start = bbox_xyxy_to_cxcywh(x_start) + x_start = (x_start * 2 - 1.) * self.snr_scale + x_start = torch.clamp( + x_start, min=-1 * self.snr_scale, max=self.snr_scale) + pred_noise = self.predict_noise_from_start(batch_noise_bboxes_raw, + batch_time, x_start) + pred_noise_list, x_start_list = [], [] + noise_bboxes_list, num_remain_list = [], [] + if self.box_renewal: # filter + score_thr = cfg.get('score_thr', 0) + for img_id in range(batch_size): + score_per_image = pred_logits[-1][img_id] + + score_per_image = torch.sigmoid(score_per_image) + value, _ = torch.max(score_per_image, -1, keepdim=False) + keep_idx = value > score_thr + + num_remain_list.append(torch.sum(keep_idx)) + pred_noise_list.append(pred_noise[img_id, keep_idx, :]) + x_start_list.append(x_start[img_id, keep_idx, :]) + noise_bboxes_list.append(batch_noise_bboxes[img_id, + keep_idx, :]) + if time_next < 0: + # Not same as original DiffusionDet + if self.use_ensemble and self.sampling_timesteps > 1: + box_pred_per_image, scores_per_image, labels_per_image = \ + self.inference( + box_cls=pred_logits[-1], + box_pred=pred_bboxes[-1], + cfg=cfg, + device=device) + ensemble_score.append(scores_per_image) + ensemble_label.append(labels_per_image) + ensemble_coord.append(box_pred_per_image) + continue + + alpha = self.alphas_cumprod[time] + alpha_next = self.alphas_cumprod[time_next] + + sigma = self.ddim_sampling_eta * ((1 - alpha / alpha_next) * + (1 - alpha_next) / + (1 - alpha)).sqrt() + c = (1 - alpha_next - sigma**2).sqrt() + + batch_noise_bboxes_list = [] + batch_noise_bboxes_raw_list = [] + for idx in range(batch_size): + pred_noise = pred_noise_list[idx] + x_start = x_start_list[idx] + noise_bboxes = noise_bboxes_list[idx] + num_remain = num_remain_list[idx] + noise = torch.randn_like(noise_bboxes) + + noise_bboxes = x_start * alpha_next.sqrt() + \ + c * pred_noise + sigma * noise + + if self.box_renewal: # filter + # replenish with randn boxes + if num_remain < self.num_proposals: + noise_bboxes = torch.cat( + (noise_bboxes, + torch.randn( + self.num_proposals - num_remain, + 4, + device=device)), + dim=0) + else: + select_mask = [True] * self.num_proposals + \ + [False] * (num_remain - + self.num_proposals) + random.shuffle(select_mask) + noise_bboxes = noise_bboxes[select_mask] + + # raw noise boxes + batch_noise_bboxes_raw_list.append(noise_bboxes) + # resize to xyxy + noise_bboxes = torch.clamp( + noise_bboxes, + min=-1 * self.snr_scale, + max=self.snr_scale) + noise_bboxes = ((noise_bboxes / self.snr_scale) + 1) / 2 + noise_bboxes = bbox_cxcywh_to_xyxy(noise_bboxes) + noise_bboxes = noise_bboxes * batch_image_size[idx] + + batch_noise_bboxes_list.append(noise_bboxes) + batch_noise_bboxes = torch.stack(batch_noise_bboxes_list) + batch_noise_bboxes_raw = torch.stack(batch_noise_bboxes_raw_list) + if self.use_ensemble and self.sampling_timesteps > 1: + box_pred_per_image, scores_per_image, labels_per_image = \ + self.inference( + box_cls=pred_logits[-1], + box_pred=pred_bboxes[-1], + cfg=cfg, + device=device) + ensemble_score.append(scores_per_image) + ensemble_label.append(labels_per_image) + ensemble_coord.append(box_pred_per_image) + if self.use_ensemble and self.sampling_timesteps > 1: + steps = len(ensemble_score) + results_list = [] + for idx in range(batch_size): + ensemble_score_per_img = [ + ensemble_score[i][idx] for i in range(steps) + ] + ensemble_label_per_img = [ + ensemble_label[i][idx] for i in range(steps) + ] + ensemble_coord_per_img = [ + ensemble_coord[i][idx] for i in range(steps) + ] + + scores_per_image = torch.cat(ensemble_score_per_img, dim=0) + labels_per_image = torch.cat(ensemble_label_per_img, dim=0) + box_pred_per_image = torch.cat(ensemble_coord_per_img, dim=0) + + if self.use_nms: + det_bboxes, keep_idxs = batched_nms( + box_pred_per_image, scores_per_image, labels_per_image, + cfg.nms) + box_pred_per_image = box_pred_per_image[keep_idxs] + labels_per_image = labels_per_image[keep_idxs] + scores_per_image = det_bboxes[:, -1] + results = InstanceData() + results.bboxes = box_pred_per_image + results.scores = scores_per_image + results.labels = labels_per_image + results_list.append(results) + else: + box_cls = pred_logits[-1] + box_pred = pred_bboxes[-1] + results_list = self.inference(box_cls, box_pred, cfg, device) + if rescale: + results_list = self.do_results_post_process( + results_list, cfg, batch_img_metas=batch_img_metas) + return results_list + + @staticmethod + def do_results_post_process(results_list, cfg, batch_img_metas=None): + processed_results = [] + for results, img_meta in zip(results_list, batch_img_metas): + assert img_meta.get('scale_factor') is not None + scale_factor = [1 / s for s in img_meta['scale_factor']] + results.bboxes = scale_boxes(results.bboxes, scale_factor) + # clip w, h + h, w = img_meta['ori_shape'] + results.bboxes[:, 0::2] = results.bboxes[:, 0::2].clamp( + min=0, max=w) + results.bboxes[:, 1::2] = results.bboxes[:, 1::2].clamp( + min=0, max=h) + + # filter small size bboxes + if cfg.get('min_bbox_size', 0) >= 0: + w, h = get_box_wh(results.bboxes) + valid_mask = (w > cfg.min_bbox_size) & (h > cfg.min_bbox_size) + if not valid_mask.all(): + results = results[valid_mask] + processed_results.append(results) + + return processed_results + + def prepare_testing_targets(self, batch_img_metas, device): + # [-1, 0, 1, 2, ..., T-1] when sampling_timesteps == timesteps + times = torch.linspace( + -1, self.timesteps - 1, steps=self.sampling_timesteps + 1) + times = list(reversed(times.int().tolist())) + # [(T-1, T-2), (T-2, T-3), ..., (1, 0), (0, -1)] + time_pairs = list(zip(times[:-1], times[1:])) + + noise_bboxes_list = [] + noise_bboxes_raw_list = [] + image_size_list = [] + for img_meta in batch_img_metas: + h, w = img_meta['img_shape'] + image_size = torch.tensor([w, h, w, h], + dtype=torch.float32, + device=device) + noise_bboxes_raw = torch.randn((self.num_proposals, 4), + device=device) + noise_bboxes = torch.clamp( + noise_bboxes_raw, min=-1 * self.snr_scale, max=self.snr_scale) + noise_bboxes = ((noise_bboxes / self.snr_scale) + 1) / 2 + noise_bboxes = bbox_cxcywh_to_xyxy(noise_bboxes) + noise_bboxes = noise_bboxes * image_size + + noise_bboxes_raw_list.append(noise_bboxes_raw) + noise_bboxes_list.append(noise_bboxes) + image_size_list.append(image_size[None]) + batch_noise_bboxes = torch.stack(noise_bboxes_list) + batch_image_size = torch.cat(image_size_list) + batch_noise_bboxes_raw = torch.stack(noise_bboxes_raw_list) + return (time_pairs, batch_noise_bboxes, batch_noise_bboxes_raw, + batch_image_size) + + def predict_noise_from_start(self, x_t, t, x0): + results = (extract( + self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t - x0) / \ + extract(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) + return results + + def inference(self, box_cls, box_pred, cfg, device): + """ + Args: + box_cls (Tensor): tensor of shape (batch_size, num_proposals, K). + The tensor predicts the classification probability for + each proposal. + box_pred (Tensor): tensors of shape (batch_size, num_proposals, 4). + The tensor predicts 4-vector (x,y,w,h) box + regression values for every proposal + + Returns: + results (List[Instances]): a list of #images elements. + """ + results = [] + + if self.use_focal_loss or self.use_fed_loss: + scores = torch.sigmoid(box_cls) + labels = torch.arange( + self.num_classes, + device=device).unsqueeze(0).repeat(self.num_proposals, + 1).flatten(0, 1) + box_pred_list = [] + scores_list = [] + labels_list = [] + for i, (scores_per_image, + box_pred_per_image) in enumerate(zip(scores, box_pred)): + + scores_per_image, topk_indices = scores_per_image.flatten( + 0, 1).topk( + self.num_proposals, sorted=False) + labels_per_image = labels[topk_indices] + box_pred_per_image = box_pred_per_image.view(-1, 1, 4).repeat( + 1, self.num_classes, 1).view(-1, 4) + box_pred_per_image = box_pred_per_image[topk_indices] + + if self.use_ensemble and self.sampling_timesteps > 1: + box_pred_list.append(box_pred_per_image) + scores_list.append(scores_per_image) + labels_list.append(labels_per_image) + continue + + if self.use_nms: + det_bboxes, keep_idxs = batched_nms( + box_pred_per_image, scores_per_image, labels_per_image, + cfg.nms) + box_pred_per_image = box_pred_per_image[keep_idxs] + labels_per_image = labels_per_image[keep_idxs] + # some nms would reweight the score, such as softnms + scores_per_image = det_bboxes[:, -1] + result = InstanceData() + result.bboxes = box_pred_per_image + result.scores = scores_per_image + result.labels = labels_per_image + results.append(result) + + else: + # For each box we assign the best class or the second + # best if the best on is `no_object`. + scores, labels = F.softmax(box_cls, dim=-1)[:, :, :-1].max(-1) + + for i, (scores_per_image, labels_per_image, + box_pred_per_image) in enumerate( + zip(scores, labels, box_pred)): + if self.use_ensemble and self.sampling_timesteps > 1: + return box_pred_per_image, scores_per_image, \ + labels_per_image + + if self.use_nms: + det_bboxes, keep_idxs = batched_nms( + box_pred_per_image, scores_per_image, labels_per_image, + cfg.nms) + box_pred_per_image = box_pred_per_image[keep_idxs] + labels_per_image = labels_per_image[keep_idxs] + # some nms would reweight the score, such as softnms + scores_per_image = det_bboxes[:, -1] + + result = InstanceData() + result.bboxes = box_pred_per_image + result.scores = scores_per_image + result.labels = labels_per_image + results.append(result) + if self.use_ensemble and self.sampling_timesteps > 1: + return box_pred_list, scores_list, labels_list + else: + return results + + +@MODELS.register_module() +class SingleDiffusionDetHead(nn.Module): + + def __init__( + self, + num_classes=80, + feat_channels=256, + dim_feedforward=2048, + num_cls_convs=1, + num_reg_convs=3, + num_heads=8, + dropout=0.0, + pooler_resolution=7, + scale_clamp=_DEFAULT_SCALE_CLAMP, + bbox_weights=(2.0, 2.0, 1.0, 1.0), + use_focal_loss=True, + use_fed_loss=False, + act_cfg=dict(type='ReLU', inplace=True), + dynamic_conv=dict(dynamic_dim=64, dynamic_num=2) + ) -> None: + super().__init__() + self.feat_channels = feat_channels + + # Dynamic + self.self_attn = nn.MultiheadAttention( + feat_channels, num_heads, dropout=dropout) + self.inst_interact = DynamicConv( + feat_channels=feat_channels, + pooler_resolution=pooler_resolution, + dynamic_dim=dynamic_conv['dynamic_dim'], + dynamic_num=dynamic_conv['dynamic_num']) + + self.linear1 = nn.Linear(feat_channels, dim_feedforward) + self.dropout = nn.Dropout(dropout) + self.linear2 = nn.Linear(dim_feedforward, feat_channels) + + self.norm1 = nn.LayerNorm(feat_channels) + self.norm2 = nn.LayerNorm(feat_channels) + self.norm3 = nn.LayerNorm(feat_channels) + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + self.dropout3 = nn.Dropout(dropout) + + self.activation = build_activation_layer(act_cfg) + + # block time mlp + self.block_time_mlp = nn.Sequential( + nn.SiLU(), nn.Linear(feat_channels * 4, feat_channels * 2)) + + # cls. + cls_module = list() + for _ in range(num_cls_convs): + cls_module.append(nn.Linear(feat_channels, feat_channels, False)) + cls_module.append(nn.LayerNorm(feat_channels)) + cls_module.append(nn.ReLU(inplace=True)) + self.cls_module = nn.ModuleList(cls_module) + + # reg. + reg_module = list() + for _ in range(num_reg_convs): + reg_module.append(nn.Linear(feat_channels, feat_channels, False)) + reg_module.append(nn.LayerNorm(feat_channels)) + reg_module.append(nn.ReLU(inplace=True)) + self.reg_module = nn.ModuleList(reg_module) + + # pred. + self.use_focal_loss = use_focal_loss + self.use_fed_loss = use_fed_loss + if self.use_focal_loss or self.use_fed_loss: + self.class_logits = nn.Linear(feat_channels, num_classes) + else: + self.class_logits = nn.Linear(feat_channels, num_classes + 1) + self.bboxes_delta = nn.Linear(feat_channels, 4) + self.scale_clamp = scale_clamp + self.bbox_weights = bbox_weights + + def forward(self, features, bboxes, pro_features, pooler, time_emb): + """ + :param bboxes: (N, num_boxes, 4) + :param pro_features: (N, num_boxes, feat_channels) + """ + + N, num_boxes = bboxes.shape[:2] + + # roi_feature. + proposal_boxes = list() + for b in range(N): + proposal_boxes.append(bboxes[b]) + rois = bbox2roi(proposal_boxes) + + roi_features = pooler(features, rois) + + if pro_features is None: + pro_features = roi_features.view(N, num_boxes, self.feat_channels, + -1).mean(-1) + + roi_features = roi_features.view(N * num_boxes, self.feat_channels, + -1).permute(2, 0, 1) + + # self_att. + pro_features = pro_features.view(N, num_boxes, + self.feat_channels).permute(1, 0, 2) + pro_features2 = self.self_attn( + pro_features, pro_features, value=pro_features)[0] + pro_features = pro_features + self.dropout1(pro_features2) + pro_features = self.norm1(pro_features) + + # inst_interact. + pro_features = pro_features.view( + num_boxes, N, + self.feat_channels).permute(1, 0, + 2).reshape(1, N * num_boxes, + self.feat_channels) + pro_features2 = self.inst_interact(pro_features, roi_features) + pro_features = pro_features + self.dropout2(pro_features2) + obj_features = self.norm2(pro_features) + + # obj_feature. + obj_features2 = self.linear2( + self.dropout(self.activation(self.linear1(obj_features)))) + obj_features = obj_features + self.dropout3(obj_features2) + obj_features = self.norm3(obj_features) + + fc_feature = obj_features.transpose(0, 1).reshape(N * num_boxes, -1) + + scale_shift = self.block_time_mlp(time_emb) + scale_shift = torch.repeat_interleave(scale_shift, num_boxes, dim=0) + scale, shift = scale_shift.chunk(2, dim=1) + fc_feature = fc_feature * (scale + 1) + shift + + cls_feature = fc_feature.clone() + reg_feature = fc_feature.clone() + for cls_layer in self.cls_module: + cls_feature = cls_layer(cls_feature) + for reg_layer in self.reg_module: + reg_feature = reg_layer(reg_feature) + class_logits = self.class_logits(cls_feature) + bboxes_deltas = self.bboxes_delta(reg_feature) + pred_bboxes = self.apply_deltas(bboxes_deltas, bboxes.view(-1, 4)) + + return (class_logits.view(N, num_boxes, + -1), pred_bboxes.view(N, num_boxes, + -1), obj_features) + + def apply_deltas(self, deltas, boxes): + """Apply transformation `deltas` (dx, dy, dw, dh) to `boxes`. + + Args: + deltas (Tensor): transformation deltas of shape (N, k*4), + where k >= 1. deltas[i] represents k potentially + different class-specific box transformations for + the single box boxes[i]. + boxes (Tensor): boxes to transform, of shape (N, 4) + """ + boxes = boxes.to(deltas.dtype) + + widths = boxes[:, 2] - boxes[:, 0] + heights = boxes[:, 3] - boxes[:, 1] + ctr_x = boxes[:, 0] + 0.5 * widths + ctr_y = boxes[:, 1] + 0.5 * heights + + wx, wy, ww, wh = self.bbox_weights + dx = deltas[:, 0::4] / wx + dy = deltas[:, 1::4] / wy + dw = deltas[:, 2::4] / ww + dh = deltas[:, 3::4] / wh + + # Prevent sending too large values into torch.exp() + dw = torch.clamp(dw, max=self.scale_clamp) + dh = torch.clamp(dh, max=self.scale_clamp) + + pred_ctr_x = dx * widths[:, None] + ctr_x[:, None] + pred_ctr_y = dy * heights[:, None] + ctr_y[:, None] + pred_w = torch.exp(dw) * widths[:, None] + pred_h = torch.exp(dh) * heights[:, None] + + pred_boxes = torch.zeros_like(deltas) + pred_boxes[:, 0::4] = pred_ctr_x - 0.5 * pred_w # x1 + pred_boxes[:, 1::4] = pred_ctr_y - 0.5 * pred_h # y1 + pred_boxes[:, 2::4] = pred_ctr_x + 0.5 * pred_w # x2 + pred_boxes[:, 3::4] = pred_ctr_y + 0.5 * pred_h # y2 + + return pred_boxes + + +class DynamicConv(nn.Module): + + def __init__(self, + feat_channels: int, + dynamic_dim: int = 64, + dynamic_num: int = 2, + pooler_resolution: int = 7) -> None: + super().__init__() + + self.feat_channels = feat_channels + self.dynamic_dim = dynamic_dim + self.dynamic_num = dynamic_num + self.num_params = self.feat_channels * self.dynamic_dim + self.dynamic_layer = nn.Linear(self.feat_channels, + self.dynamic_num * self.num_params) + + self.norm1 = nn.LayerNorm(self.dynamic_dim) + self.norm2 = nn.LayerNorm(self.feat_channels) + + self.activation = nn.ReLU(inplace=True) + + num_output = self.feat_channels * pooler_resolution**2 + self.out_layer = nn.Linear(num_output, self.feat_channels) + self.norm3 = nn.LayerNorm(self.feat_channels) + + def forward(self, pro_features: Tensor, roi_features: Tensor) -> Tensor: + """Forward function. + + Args: + pro_features: (1, N * num_boxes, self.feat_channels) + roi_features: (49, N * num_boxes, self.feat_channels) + + Returns: + """ + features = roi_features.permute(1, 0, 2) + parameters = self.dynamic_layer(pro_features).permute(1, 0, 2) + + param1 = parameters[:, :, :self.num_params].view( + -1, self.feat_channels, self.dynamic_dim) + param2 = parameters[:, :, + self.num_params:].view(-1, self.dynamic_dim, + self.feat_channels) + + features = torch.bmm(features, param1) + features = self.norm1(features) + features = self.activation(features) + + features = torch.bmm(features, param2) + features = self.norm2(features) + features = self.activation(features) + + features = features.flatten(1) + features = self.out_layer(features) + features = self.norm3(features) + features = self.activation(features) + + return features diff --git a/mmdetection/projects/DiffusionDet/diffusiondet/loss.py b/mmdetection/projects/DiffusionDet/diffusiondet/loss.py new file mode 100644 index 00000000..3d532f1f --- /dev/null +++ b/mmdetection/projects/DiffusionDet/diffusiondet/loss.py @@ -0,0 +1,341 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +# Modified from https://github.com/ShoufaChen/DiffusionDet/blob/main/diffusiondet/loss.py # noqa + +# This work is licensed under the CC-BY-NC 4.0 License. +# Users should be careful about adopting these features in any commercial matters. # noqa +# For more details, please refer to https://github.com/ShoufaChen/DiffusionDet/blob/main/LICENSE # noqa + +from typing import List, Tuple, Union + +import torch +import torch.nn as nn +from mmengine.config import ConfigDict +from mmengine.structures import InstanceData +from torch import Tensor + +from mmdet.registry import MODELS, TASK_UTILS +from mmdet.structures.bbox import bbox_cxcywh_to_xyxy, bbox_xyxy_to_cxcywh +from mmdet.utils import ConfigType + + +@TASK_UTILS.register_module() +class DiffusionDetCriterion(nn.Module): + + def __init__( + self, + num_classes, + assigner: Union[ConfigDict, nn.Module], + deep_supervision=True, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + alpha=0.25, + gamma=2.0, + reduction='sum', + loss_weight=2.0), + loss_bbox=dict(type='L1Loss', reduction='sum', loss_weight=5.0), + loss_giou=dict(type='GIoULoss', reduction='sum', loss_weight=2.0), + ): + + super().__init__() + self.num_classes = num_classes + + if isinstance(assigner, nn.Module): + self.assigner = assigner + else: + self.assigner = TASK_UTILS.build(assigner) + + self.deep_supervision = deep_supervision + + self.loss_cls = MODELS.build(loss_cls) + self.loss_bbox = MODELS.build(loss_bbox) + self.loss_giou = MODELS.build(loss_giou) + + def forward(self, outputs, batch_gt_instances, batch_img_metas): + batch_indices = self.assigner(outputs, batch_gt_instances, + batch_img_metas) + # Compute all the requested losses + loss_cls = self.loss_classification(outputs, batch_gt_instances, + batch_indices) + loss_bbox, loss_giou = self.loss_boxes(outputs, batch_gt_instances, + batch_indices) + + losses = dict( + loss_cls=loss_cls, loss_bbox=loss_bbox, loss_giou=loss_giou) + + if self.deep_supervision: + assert 'aux_outputs' in outputs + for i, aux_outputs in enumerate(outputs['aux_outputs']): + batch_indices = self.assigner(aux_outputs, batch_gt_instances, + batch_img_metas) + loss_cls = self.loss_classification(aux_outputs, + batch_gt_instances, + batch_indices) + loss_bbox, loss_giou = self.loss_boxes(aux_outputs, + batch_gt_instances, + batch_indices) + tmp_losses = dict( + loss_cls=loss_cls, + loss_bbox=loss_bbox, + loss_giou=loss_giou) + for name, value in tmp_losses.items(): + losses[f's.{i}.{name}'] = value + return losses + + def loss_classification(self, outputs, batch_gt_instances, indices): + assert 'pred_logits' in outputs + src_logits = outputs['pred_logits'] + target_classes_list = [ + gt.labels[J] for gt, (_, J) in zip(batch_gt_instances, indices) + ] + target_classes = torch.full( + src_logits.shape[:2], + self.num_classes, + dtype=torch.int64, + device=src_logits.device) + for idx in range(len(batch_gt_instances)): + target_classes[idx, indices[idx][0]] = target_classes_list[idx] + + src_logits = src_logits.flatten(0, 1) + target_classes = target_classes.flatten(0, 1) + # comp focal loss. + num_instances = max(torch.cat(target_classes_list).shape[0], 1) + loss_cls = self.loss_cls( + src_logits, + target_classes, + ) / num_instances + return loss_cls + + def loss_boxes(self, outputs, batch_gt_instances, indices): + assert 'pred_boxes' in outputs + pred_boxes = outputs['pred_boxes'] + + target_bboxes_norm_list = [ + gt.norm_bboxes_cxcywh[J] + for gt, (_, J) in zip(batch_gt_instances, indices) + ] + target_bboxes_list = [ + gt.bboxes[J] for gt, (_, J) in zip(batch_gt_instances, indices) + ] + + pred_bboxes_list = [] + pred_bboxes_norm_list = [] + for idx in range(len(batch_gt_instances)): + pred_bboxes_list.append(pred_boxes[idx, indices[idx][0]]) + image_size = batch_gt_instances[idx].image_size + pred_bboxes_norm_list.append(pred_boxes[idx, indices[idx][0]] / + image_size) + + pred_boxes_cat = torch.cat(pred_bboxes_list) + pred_boxes_norm_cat = torch.cat(pred_bboxes_norm_list) + target_bboxes_cat = torch.cat(target_bboxes_list) + target_bboxes_norm_cat = torch.cat(target_bboxes_norm_list) + + if len(pred_boxes_cat) > 0: + num_instances = pred_boxes_cat.shape[0] + + loss_bbox = self.loss_bbox( + pred_boxes_norm_cat, + bbox_cxcywh_to_xyxy(target_bboxes_norm_cat)) / num_instances + loss_giou = self.loss_giou(pred_boxes_cat, + target_bboxes_cat) / num_instances + else: + loss_bbox = pred_boxes.sum() * 0 + loss_giou = pred_boxes.sum() * 0 + return loss_bbox, loss_giou + + +@TASK_UTILS.register_module() +class DiffusionDetMatcher(nn.Module): + """This class computes an assignment between the targets and the + predictions of the network For efficiency reasons, the targets don't + include the no_object. + + Because of this, in general, there are more predictions than targets. In + this case, we do a 1-to-k (dynamic) matching of the best predictions, while + the others are un-matched (and thus treated as non-objects). + """ + + def __init__(self, + match_costs: Union[List[Union[dict, ConfigDict]], dict, + ConfigDict], + center_radius: float = 2.5, + candidate_topk: int = 5, + iou_calculator: ConfigType = dict(type='BboxOverlaps2D'), + **kwargs): + super().__init__() + + self.center_radius = center_radius + self.candidate_topk = candidate_topk + + if isinstance(match_costs, dict): + match_costs = [match_costs] + elif isinstance(match_costs, list): + assert len(match_costs) > 0, \ + 'match_costs must not be a empty list.' + self.use_focal_loss = False + self.use_fed_loss = False + for _match_cost in match_costs: + if _match_cost.get('type') == 'FocalLossCost': + self.use_focal_loss = True + if _match_cost.get('type') == 'FedLoss': + self.use_fed_loss = True + raise NotImplementedError + + self.match_costs = [ + TASK_UTILS.build(match_cost) for match_cost in match_costs + ] + self.iou_calculator = TASK_UTILS.build(iou_calculator) + + def forward(self, outputs, batch_gt_instances, batch_img_metas): + assert 'pred_logits' in outputs and 'pred_boxes' in outputs + + pred_logits = outputs['pred_logits'] + pred_bboxes = outputs['pred_boxes'] + batch_size = len(batch_gt_instances) + + assert batch_size == pred_logits.shape[0] == pred_bboxes.shape[0] + batch_indices = [] + for i in range(batch_size): + pred_instances = InstanceData() + pred_instances.bboxes = pred_bboxes[i, ...] + pred_instances.scores = pred_logits[i, ...] + gt_instances = batch_gt_instances[i] + img_meta = batch_img_metas[i] + indices = self.single_assigner(pred_instances, gt_instances, + img_meta) + batch_indices.append(indices) + return batch_indices + + def single_assigner(self, pred_instances, gt_instances, img_meta): + with torch.no_grad(): + gt_bboxes = gt_instances.bboxes + pred_bboxes = pred_instances.bboxes + num_gt = gt_bboxes.size(0) + + if num_gt == 0: # empty object in key frame + valid_mask = pred_bboxes.new_zeros((pred_bboxes.shape[0], ), + dtype=torch.bool) + matched_gt_inds = pred_bboxes.new_zeros((gt_bboxes.shape[0], ), + dtype=torch.long) + return valid_mask, matched_gt_inds + + valid_mask, is_in_boxes_and_center = \ + self.get_in_gt_and_in_center_info( + bbox_xyxy_to_cxcywh(pred_bboxes), + bbox_xyxy_to_cxcywh(gt_bboxes) + ) + + cost_list = [] + for match_cost in self.match_costs: + cost = match_cost( + pred_instances=pred_instances, + gt_instances=gt_instances, + img_meta=img_meta) + cost_list.append(cost) + + pairwise_ious = self.iou_calculator(pred_bboxes, gt_bboxes) + + cost_list.append((~is_in_boxes_and_center) * 100.0) + cost_matrix = torch.stack(cost_list).sum(0) + cost_matrix[~valid_mask] = cost_matrix[~valid_mask] + 10000.0 + + fg_mask_inboxes, matched_gt_inds = \ + self.dynamic_k_matching( + cost_matrix, pairwise_ious, num_gt) + return fg_mask_inboxes, matched_gt_inds + + def get_in_gt_and_in_center_info( + self, pred_bboxes: Tensor, + gt_bboxes: Tensor) -> Tuple[Tensor, Tensor]: + """Get the information of which prior is in gt bboxes and gt center + priors.""" + xy_target_gts = bbox_cxcywh_to_xyxy(gt_bboxes) # (x1, y1, x2, y2) + + pred_bboxes_center_x = pred_bboxes[:, 0].unsqueeze(1) + pred_bboxes_center_y = pred_bboxes[:, 1].unsqueeze(1) + + # whether the center of each anchor is inside a gt box + b_l = pred_bboxes_center_x > xy_target_gts[:, 0].unsqueeze(0) + b_r = pred_bboxes_center_x < xy_target_gts[:, 2].unsqueeze(0) + b_t = pred_bboxes_center_y > xy_target_gts[:, 1].unsqueeze(0) + b_b = pred_bboxes_center_y < xy_target_gts[:, 3].unsqueeze(0) + # (b_l.long()+b_r.long()+b_t.long()+b_b.long())==4 [300,num_gt] , + is_in_boxes = ((b_l.long() + b_r.long() + b_t.long() + + b_b.long()) == 4) + is_in_boxes_all = is_in_boxes.sum(1) > 0 # [num_query] + # in fixed center + center_radius = 2.5 + # Modified to self-adapted sampling --- the center size depends + # on the size of the gt boxes + # https://github.com/dulucas/UVO_Challenge/blob/main/Track1/detection/mmdet/core/bbox/assigners/rpn_sim_ota_assigner.py#L212 # noqa + b_l = pred_bboxes_center_x > ( + gt_bboxes[:, 0] - + (center_radius * + (xy_target_gts[:, 2] - xy_target_gts[:, 0]))).unsqueeze(0) + b_r = pred_bboxes_center_x < ( + gt_bboxes[:, 0] + + (center_radius * + (xy_target_gts[:, 2] - xy_target_gts[:, 0]))).unsqueeze(0) + b_t = pred_bboxes_center_y > ( + gt_bboxes[:, 1] - + (center_radius * + (xy_target_gts[:, 3] - xy_target_gts[:, 1]))).unsqueeze(0) + b_b = pred_bboxes_center_y < ( + gt_bboxes[:, 1] + + (center_radius * + (xy_target_gts[:, 3] - xy_target_gts[:, 1]))).unsqueeze(0) + + is_in_centers = ((b_l.long() + b_r.long() + b_t.long() + + b_b.long()) == 4) + is_in_centers_all = is_in_centers.sum(1) > 0 + + is_in_boxes_anchor = is_in_boxes_all | is_in_centers_all + is_in_boxes_and_center = (is_in_boxes & is_in_centers) + + return is_in_boxes_anchor, is_in_boxes_and_center + + def dynamic_k_matching(self, cost: Tensor, pairwise_ious: Tensor, + num_gt: int) -> Tuple[Tensor, Tensor]: + """Use IoU and matching cost to calculate the dynamic top-k positive + targets.""" + matching_matrix = torch.zeros_like(cost) + # select candidate topk ious for dynamic-k calculation + candidate_topk = min(self.candidate_topk, pairwise_ious.size(0)) + topk_ious, _ = torch.topk(pairwise_ious, candidate_topk, dim=0) + # calculate dynamic k for each gt + dynamic_ks = torch.clamp(topk_ious.sum(0).int(), min=1) + for gt_idx in range(num_gt): + _, pos_idx = torch.topk( + cost[:, gt_idx], k=dynamic_ks[gt_idx], largest=False) + matching_matrix[:, gt_idx][pos_idx] = 1 + + del topk_ious, dynamic_ks, pos_idx + + prior_match_gt_mask = matching_matrix.sum(1) > 1 + if prior_match_gt_mask.sum() > 0: + _, cost_argmin = torch.min(cost[prior_match_gt_mask, :], dim=1) + matching_matrix[prior_match_gt_mask, :] *= 0 + matching_matrix[prior_match_gt_mask, cost_argmin] = 1 + + while (matching_matrix.sum(0) == 0).any(): + matched_query_id = matching_matrix.sum(1) > 0 + cost[matched_query_id] += 100000.0 + unmatch_id = torch.nonzero( + matching_matrix.sum(0) == 0, as_tuple=False).squeeze(1) + for gt_idx in unmatch_id: + pos_idx = torch.argmin(cost[:, gt_idx]) + matching_matrix[:, gt_idx][pos_idx] = 1.0 + if (matching_matrix.sum(1) > 1).sum() > 0: + _, cost_argmin = torch.min(cost[prior_match_gt_mask], dim=1) + matching_matrix[prior_match_gt_mask] *= 0 + matching_matrix[prior_match_gt_mask, cost_argmin, ] = 1 + + assert not (matching_matrix.sum(0) == 0).any() + # get foreground mask inside box and center prior + fg_mask_inboxes = matching_matrix.sum(1) > 0 + matched_gt_inds = matching_matrix[fg_mask_inboxes, :].argmax(1) + + return fg_mask_inboxes, matched_gt_inds diff --git a/mmdetection/projects/DiffusionDet/model_converters/diffusiondet_resnet_to_mmdet.py b/mmdetection/projects/DiffusionDet/model_converters/diffusiondet_resnet_to_mmdet.py new file mode 100644 index 00000000..101abd83 --- /dev/null +++ b/mmdetection/projects/DiffusionDet/model_converters/diffusiondet_resnet_to_mmdet.py @@ -0,0 +1,88 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +from collections import OrderedDict + +import numpy as np +import torch +from mmengine.fileio import load + + +def convert(src, dst): + if src.endswith('pth'): + src_model = torch.load(src) + else: + src_model = load(src) + + dst_state_dict = OrderedDict() + for k, v in src_model['model'].items(): + key_name_split = k.split('.') + if 'backbone.fpn_lateral' in k: + lateral_id = int(key_name_split[-2][-1]) + name = f'neck.lateral_convs.{lateral_id - 2}.' \ + f'conv.{key_name_split[-1]}' + elif 'backbone.fpn_output' in k: + lateral_id = int(key_name_split[-2][-1]) + name = f'neck.fpn_convs.{lateral_id - 2}.conv.' \ + f'{key_name_split[-1]}' + elif 'backbone.bottom_up.stem.conv1.norm.' in k: + name = f'backbone.bn1.{key_name_split[-1]}' + elif 'backbone.bottom_up.stem.conv1.' in k: + name = f'backbone.conv1.{key_name_split[-1]}' + elif 'backbone.bottom_up.res' in k: + # weight_type = key_name_split[-1] + res_id = int(key_name_split[2][-1]) - 1 + # deal with short cut + if 'shortcut' in key_name_split[4]: + if 'shortcut' == key_name_split[-2]: + name = f'backbone.layer{res_id}.' \ + f'{key_name_split[3]}.downsample.0.' \ + f'{key_name_split[-1]}' + elif 'shortcut' == key_name_split[-3]: + name = f'backbone.layer{res_id}.' \ + f'{key_name_split[3]}.downsample.1.' \ + f'{key_name_split[-1]}' + else: + print(f'Unvalid key {k}') + # deal with conv + elif 'conv' in key_name_split[-2]: + conv_id = int(key_name_split[-2][-1]) + name = f'backbone.layer{res_id}.{key_name_split[3]}' \ + f'.conv{conv_id}.{key_name_split[-1]}' + # deal with BN + elif key_name_split[-2] == 'norm': + conv_id = int(key_name_split[-3][-1]) + name = f'backbone.layer{res_id}.{key_name_split[3]}.' \ + f'bn{conv_id}.{key_name_split[-1]}' + else: + print(f'{k} is invalid') + + elif key_name_split[0] == 'head': + # d2: head.xxx -> mmdet: bbox_head.xxx + name = f'bbox_{k}' + else: + # some base parameters such as beta will not convert + print(f'{k} is not converted!!') + continue + + if not isinstance(v, np.ndarray) and not isinstance(v, torch.Tensor): + raise ValueError( + 'Unsupported type found in checkpoint! {}: {}'.format( + k, type(v))) + if not isinstance(v, torch.Tensor): + dst_state_dict[name] = torch.from_numpy(v) + else: + dst_state_dict[name] = v + mmdet_model = dict(state_dict=dst_state_dict, meta=dict()) + torch.save(mmdet_model, dst) + + +def main(): + parser = argparse.ArgumentParser(description='Convert model keys') + parser.add_argument('src', help='src detectron model path') + parser.add_argument('dst', help='save path') + args = parser.parse_args() + convert(args.src, args.dst) + + +if __name__ == '__main__': + main() diff --git a/mmdetection/projects/EfficientDet/README.md b/mmdetection/projects/EfficientDet/README.md new file mode 100644 index 00000000..36f4ed40 --- /dev/null +++ b/mmdetection/projects/EfficientDet/README.md @@ -0,0 +1,154 @@ +# EfficientDet + +> [**EfficientDet: Scalable and Efficient Object Detection**](https://arxiv.org/pdf/1911.09070.pdf), +> Mingxing Tan, Ruoming Pang, Quoc V. Le, +> *CVPR 2020* + +## Abstract + +This is an implementation of [EfficientDet](https://github.com/google/automl) based on [MMDetection](https://github.com/open-mmlab/mmdetection/tree/main), [MMCV](https://github.com/open-mmlab/mmcv), and [MMEngine](https://github.com/open-mmlab/mmengine). +
    +EfficientDet a new family of object detectors, which consistently achieve much better efficiency than prior art across a wide +spectrum of resource constraints. +In particular, with single model and single-scale, EfficientDet-D7 achieves stateof-the-art 55.1 AP on COCO test-dev with 77M parameters and 410B FLOP. +
    +BiFPN is a simple yet highly effective weighted bi-directional feature pyramid network, which introduces learnable weights to learn the importance of different input features, while repeatedly applying topdown and bottom-up multi-scale feature fusion. +
    +In contrast to other feature pyramid network, such as FPN, FPN + PAN, NAS-FPN, BiFPN achieves the best accuracy with fewer parameters and FLOPs. + +
    + +
    + +## Usage + +## Official TensorFlow Model + +This project also supports [official tensorflow model](https://github.com/google/automl), it uses 90 categories and yxyx box encoding in training. If you want to use the original model weight to get official results, please refer to the following steps. + +### Model conversion + +Firstly, download EfficientDet [weights](https://github.com/google/automl/tree/master/efficientdet) and unzip, please use the following command + +```bash +tar -xzvf {EFFICIENTDET_WEIGHT} +``` + +Then, install tensorflow, please use the following command + +```bash +pip install tensorflow-gpu==2.6.0 +``` + +Lastly, convert weights from tensorflow to pytorch, please use the following command + +```bash +python projects/EfficientDet/convert_tf_to_pt.py --backbone {BACKBONE_NAME} --tensorflow_weight {TENSORFLOW_WEIGHT_PATH} --out_weight {OUT_PATH} +``` + +### Testing commands + +In MMDetection's root directory, run the following command to test the model: + +```bash +python tools/test.py projects/EfficientDet/configs/tensorflow/efficientdet_effb0_bifpn_8xb16-crop512-300e_coco_tf.py ${CHECKPOINT_PATH} +``` + +## Reproduce Model + +For convenience, we recommend the current implementation version, it uses 80 categories and xyxy encoding in training. On this basis, a higher result was finally achieved. + +### Training commands + +In MMDetection's root directory, run the following command to train the model: + +```bash +python tools/train.py projects/EfficientDet/configs/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco.py +``` + +### Testing commands + +In MMDetection's root directory, run the following command to test the model: + +```bash +python tools/test.py projects/EfficientDet/configs/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco.py ${CHECKPOINT_PATH} +``` + +## Results + +Based on mmdetection, this project aligns the accuracy of the [official model](https://github.com/google/automl). + +| Method | Backbone | Pretrained Model | Training set | Test set | Epoch | Val Box AP | Official AP | Download | +| :------------------------------------------------------------------------------------------------------------------: | :-------------: | :--------------: | :------------: | :----------: | :---: | :--------: | :---------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [efficientdet-d0\*](projects/EfficientDet/configs/tensorflow/efficientdet_effb0_bifpn_8xb16-crop512-300e_coco_tf.py) | efficientnet-b0 | ImageNet | COCO2017 Train | COCO2017 Val | 300 | 34.4 | 34.3 | | +| [efficientdet-d3](projects/EfficientDet/configs/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco.py) | efficientnet-b3 | ImageNet | COCO2017 Train | COCO2017 Val | 300 | 47.2 | 46.8 | [model](https://download.openmmlab.com/mmdetection/v3.0/efficientdet/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco_20230223_122457-e6f7a833.pth) \| [log](https://download.openmmlab.com/mmdetection/v3.0/efficientdet/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco_20230223_122457.log.json) | + +**Note**: +\*means use [official tensorflow model](https://github.com/google/automl) weights to test. + +## Citation + +```BibTeX +@inproceedings{tan2020efficientdet, + title={Efficientdet: Scalable and efficient object detection}, + author={Tan, Mingxing and Pang, Ruoming and Le, Quoc V}, + booktitle={Proceedings of the IEEE/CVF conference on computer vision and pattern recognition}, + pages={10781--10790}, + year={2020} +} +``` + +## Checklist + + + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + + + - [x] Basic docstrings & proper citation + + + + - [x] Test-time correctness + + + + - [x] A full README + + + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + + + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + + + - [ ] Unit tests + + + + - [ ] Code polishing + + + + - [ ] Metafile.yml + + + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + + + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/mmdetection/projects/EfficientDet/configs/efficientdet_effb0_bifpn_8xb16-crop512-300e_coco.py b/mmdetection/projects/EfficientDet/configs/efficientdet_effb0_bifpn_8xb16-crop512-300e_coco.py new file mode 100644 index 00000000..c7a3b309 --- /dev/null +++ b/mmdetection/projects/EfficientDet/configs/efficientdet_effb0_bifpn_8xb16-crop512-300e_coco.py @@ -0,0 +1,171 @@ +_base_ = [ + 'mmdet::_base_/datasets/coco_detection.py', + 'mmdet::_base_/schedules/schedule_1x.py', + 'mmdet::_base_/default_runtime.py' +] +custom_imports = dict( + imports=['projects.EfficientDet.efficientdet'], allow_failed_imports=False) + +image_size = 512 +batch_augments = [ + dict(type='BatchFixedSizePad', size=(image_size, image_size)) +] +dataset_type = 'CocoDataset' +evalute_type = 'CocoMetric' +norm_cfg = dict(type='SyncBN', requires_grad=True, eps=1e-3, momentum=0.01) +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b0_3rdparty_8xb32-aa-advprop_in1k_20220119-26434485.pth' # noqa +model = dict( + type='EfficientDet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=image_size, + batch_augments=batch_augments), + backbone=dict( + type='EfficientNet', + arch='b0', + drop_path_rate=0.2, + out_indices=(3, 4, 5), + frozen_stages=0, + conv_cfg=dict(type='Conv2dSamePadding'), + norm_cfg=norm_cfg, + norm_eval=False, + init_cfg=dict( + type='Pretrained', prefix='backbone', checkpoint=checkpoint)), + neck=dict( + type='BiFPN', + num_stages=3, + in_channels=[40, 112, 320], + out_channels=64, + start_level=0, + norm_cfg=norm_cfg), + bbox_head=dict( + type='EfficientDetSepBNHead', + num_classes=80, + num_ins=5, + in_channels=64, + feat_channels=64, + stacked_convs=3, + norm_cfg=norm_cfg, + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[1.0, 0.5, 2.0], + strides=[8, 16, 32, 64, 128], + center_offset=0.5), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=1.5, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='HuberLoss', beta=0.1, loss_weight=50)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0, + ignore_iof_thr=-1), + sampler=dict( + type='PseudoSampler'), # Focal loss should use PseudoSampler + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict( + type='soft_nms', + iou_threshold=0.3, + sigma=0.5, + min_score=1e-3, + method='gaussian'), + max_per_img=100)) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(image_size, image_size), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(image_size, image_size)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(image_size, image_size), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=16, + num_workers=8, + dataset=dict(type=dataset_type, pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(type=dataset_type, pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type=evalute_type) +test_evaluator = val_evaluator + +optim_wrapper = dict( + optimizer=dict(lr=0.16, weight_decay=4e-5), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True), + clip_grad=dict(max_norm=10, norm_type=2)) + +# learning policy +max_epochs = 300 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=917), + dict( + type='CosineAnnealingLR', + eta_min=0.0, + begin=1, + T_max=299, + end=300, + by_epoch=True, + convert_to_iter_based=True) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +vis_backends = [ + dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend') +] +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') + +default_hooks = dict(checkpoint=dict(type='CheckpointHook', interval=15)) +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49) +] +# cudnn_benchmark=True can accelerate fix-size training +env_cfg = dict(cudnn_benchmark=True) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (16 samples per GPU) +auto_scale_lr = dict(base_batch_size=128) diff --git a/mmdetection/projects/EfficientDet/configs/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco-90cls.py b/mmdetection/projects/EfficientDet/configs/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco-90cls.py new file mode 100644 index 00000000..fe82a5e1 --- /dev/null +++ b/mmdetection/projects/EfficientDet/configs/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco-90cls.py @@ -0,0 +1,171 @@ +_base_ = [ + 'mmdet::_base_/datasets/coco_detection.py', + 'mmdet::_base_/schedules/schedule_1x.py', + 'mmdet::_base_/default_runtime.py' +] +custom_imports = dict( + imports=['projects.EfficientDet.efficientdet'], allow_failed_imports=False) + +image_size = 896 +batch_augments = [ + dict(type='BatchFixedSizePad', size=(image_size, image_size)) +] +dataset_type = 'Coco90Dataset' +evalute_type = 'Coco90Metric' +norm_cfg = dict(type='SyncBN', requires_grad=True, eps=1e-3, momentum=0.01) +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b3_3rdparty_8xb32-aa-advprop_in1k_20220119-53b41118.pth' # noqa +model = dict( + type='EfficientDet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=image_size, + batch_augments=batch_augments), + backbone=dict( + type='EfficientNet', + arch='b3', + drop_path_rate=0.3, + out_indices=(3, 4, 5), + frozen_stages=0, + conv_cfg=dict(type='Conv2dSamePadding'), + norm_cfg=norm_cfg, + norm_eval=False, + init_cfg=dict( + type='Pretrained', prefix='backbone', checkpoint=checkpoint)), + neck=dict( + type='BiFPN', + num_stages=6, + in_channels=[48, 136, 384], + out_channels=160, + start_level=0, + norm_cfg=norm_cfg), + bbox_head=dict( + type='EfficientDetSepBNHead', + num_classes=90, + num_ins=5, + in_channels=160, + feat_channels=160, + stacked_convs=4, + norm_cfg=norm_cfg, + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[1.0, 0.5, 2.0], + strides=[8, 16, 32, 64, 128], + center_offset=0.5), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=1.5, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='HuberLoss', beta=0.1, loss_weight=50)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0, + ignore_iof_thr=-1), + sampler=dict( + type='PseudoSampler'), # Focal loss should use PseudoSampler + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict( + type='soft_nms', + iou_threshold=0.3, + sigma=0.5, + min_score=1e-3, + method='gaussian'), + max_per_img=100)) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(image_size, image_size), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(image_size, image_size)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(image_size, image_size), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=16, + num_workers=8, + dataset=dict(type=dataset_type, pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(type=dataset_type, pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type=evalute_type) +test_evaluator = val_evaluator + +optim_wrapper = dict( + optimizer=dict(lr=0.16, weight_decay=4e-5), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True), + clip_grad=dict(max_norm=10, norm_type=2)) + +# learning policy +max_epochs = 300 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=917), + dict( + type='CosineAnnealingLR', + eta_min=0.0, + begin=1, + T_max=299, + end=300, + by_epoch=True, + convert_to_iter_based=True) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +vis_backends = [ + dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend') +] +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') + +default_hooks = dict(checkpoint=dict(type='CheckpointHook', interval=15)) +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49) +] +# cudnn_benchmark=True can accelerate fix-size training +env_cfg = dict(cudnn_benchmark=True) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (16 samples per GPU) +auto_scale_lr = dict(base_batch_size=128) diff --git a/mmdetection/projects/EfficientDet/configs/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco.py b/mmdetection/projects/EfficientDet/configs/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco.py new file mode 100644 index 00000000..2079e2ac --- /dev/null +++ b/mmdetection/projects/EfficientDet/configs/efficientdet_effb3_bifpn_8xb16-crop896-300e_coco.py @@ -0,0 +1,171 @@ +_base_ = [ + 'mmdet::_base_/datasets/coco_detection.py', + 'mmdet::_base_/schedules/schedule_1x.py', + 'mmdet::_base_/default_runtime.py' +] +custom_imports = dict( + imports=['projects.EfficientDet.efficientdet'], allow_failed_imports=False) + +image_size = 896 +batch_augments = [ + dict(type='BatchFixedSizePad', size=(image_size, image_size)) +] +dataset_type = 'CocoDataset' +evalute_type = 'CocoMetric' +norm_cfg = dict(type='SyncBN', requires_grad=True, eps=1e-3, momentum=0.01) +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b3_3rdparty_8xb32-aa-advprop_in1k_20220119-53b41118.pth' # noqa +model = dict( + type='EfficientDet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=image_size, + batch_augments=batch_augments), + backbone=dict( + type='EfficientNet', + arch='b3', + drop_path_rate=0.3, + out_indices=(3, 4, 5), + frozen_stages=0, + conv_cfg=dict(type='Conv2dSamePadding'), + norm_cfg=norm_cfg, + norm_eval=False, + init_cfg=dict( + type='Pretrained', prefix='backbone', checkpoint=checkpoint)), + neck=dict( + type='BiFPN', + num_stages=6, + in_channels=[48, 136, 384], + out_channels=160, + start_level=0, + norm_cfg=norm_cfg), + bbox_head=dict( + type='EfficientDetSepBNHead', + num_classes=80, + num_ins=5, + in_channels=160, + feat_channels=160, + stacked_convs=4, + norm_cfg=norm_cfg, + anchor_generator=dict( + type='AnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[1.0, 0.5, 2.0], + strides=[8, 16, 32, 64, 128], + center_offset=0.5), + bbox_coder=dict( + type='DeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=1.5, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='HuberLoss', beta=0.1, loss_weight=50)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='MaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0, + ignore_iof_thr=-1), + sampler=dict( + type='PseudoSampler'), # Focal loss should use PseudoSampler + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict( + type='soft_nms', + iou_threshold=0.3, + sigma=0.5, + min_score=1e-3, + method='gaussian'), + max_per_img=100)) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(image_size, image_size), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(image_size, image_size)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(image_size, image_size), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=16, + num_workers=8, + dataset=dict(type=dataset_type, pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(type=dataset_type, pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type=evalute_type) +test_evaluator = val_evaluator + +optim_wrapper = dict( + optimizer=dict(lr=0.16, weight_decay=4e-5), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True), + clip_grad=dict(max_norm=10, norm_type=2)) + +# learning policy +max_epochs = 300 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=917), + dict( + type='CosineAnnealingLR', + eta_min=0.0, + begin=1, + T_max=299, + end=300, + by_epoch=True, + convert_to_iter_based=True) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +vis_backends = [ + dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend') +] +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') + +default_hooks = dict(checkpoint=dict(type='CheckpointHook', interval=15)) +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49) +] +# cudnn_benchmark=True can accelerate fix-size training +env_cfg = dict(cudnn_benchmark=True) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (16 samples per GPU) +auto_scale_lr = dict(base_batch_size=128) diff --git a/mmdetection/projects/EfficientDet/configs/tensorflow/efficientdet_effb0_bifpn_8xb16-crop512-300e_coco_tf.py b/mmdetection/projects/EfficientDet/configs/tensorflow/efficientdet_effb0_bifpn_8xb16-crop512-300e_coco_tf.py new file mode 100644 index 00000000..bf3d3fc1 --- /dev/null +++ b/mmdetection/projects/EfficientDet/configs/tensorflow/efficientdet_effb0_bifpn_8xb16-crop512-300e_coco_tf.py @@ -0,0 +1,171 @@ +_base_ = [ + 'mmdet::_base_/datasets/coco_detection.py', + 'mmdet::_base_/schedules/schedule_1x.py', + 'mmdet::_base_/default_runtime.py' +] +custom_imports = dict( + imports=['projects.EfficientDet.efficientdet'], allow_failed_imports=False) + +image_size = 512 +batch_augments = [ + dict(type='BatchFixedSizePad', size=(image_size, image_size)) +] +dataset_type = 'Coco90Dataset' +evalute_type = 'Coco90Metric' +norm_cfg = dict(type='SyncBN', requires_grad=True, eps=1e-3, momentum=0.01) +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b0_3rdparty_8xb32-aa-advprop_in1k_20220119-26434485.pth' # noqa +model = dict( + type='EfficientDet', + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=image_size, + batch_augments=batch_augments), + backbone=dict( + type='EfficientNet', + arch='b0', + drop_path_rate=0.2, + out_indices=(3, 4, 5), + frozen_stages=0, + conv_cfg=dict(type='Conv2dSamePadding'), + norm_cfg=norm_cfg, + norm_eval=False, + init_cfg=dict( + type='Pretrained', prefix='backbone', checkpoint=checkpoint)), + neck=dict( + type='BiFPN', + num_stages=3, + in_channels=[40, 112, 320], + out_channels=64, + start_level=0, + norm_cfg=norm_cfg), + bbox_head=dict( + type='EfficientDetSepBNHead', + num_classes=90, + num_ins=5, + in_channels=64, + feat_channels=64, + stacked_convs=3, + norm_cfg=norm_cfg, + anchor_generator=dict( + type='YXYXAnchorGenerator', + octave_base_scale=4, + scales_per_octave=3, + ratios=[1.0, 0.5, 2.0], + strides=[8, 16, 32, 64, 128], + center_offset=0.5), + bbox_coder=dict( + type='YXYXDeltaXYWHBBoxCoder', + target_means=[.0, .0, .0, .0], + target_stds=[1.0, 1.0, 1.0, 1.0]), + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=1.5, + alpha=0.25, + loss_weight=1.0), + loss_bbox=dict(type='HuberLoss', beta=0.1, loss_weight=50)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='TransMaxIoUAssigner', + pos_iou_thr=0.5, + neg_iou_thr=0.5, + min_pos_iou=0, + ignore_iof_thr=-1), + sampler=dict( + type='PseudoSampler'), # Focal loss should use PseudoSampler + allowed_border=-1, + pos_weight=-1, + debug=False), + test_cfg=dict( + nms_pre=1000, + min_bbox_size=0, + score_thr=0.05, + nms=dict( + type='soft_nms', + iou_threshold=0.3, + sigma=0.5, + min_score=1e-3, + method='gaussian'), + max_per_img=100)) + +# dataset settings +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='RandomResize', + scale=(image_size, image_size), + ratio_range=(0.1, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(image_size, image_size)), + dict(type='RandomFlip', prob=0.5), + dict(type='PackDetInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}), + dict(type='Resize', scale=(image_size, image_size), keep_ratio=True), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=16, + num_workers=8, + dataset=dict(type=dataset_type, pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(type=dataset_type, pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type=evalute_type) +test_evaluator = val_evaluator + +optim_wrapper = dict( + optimizer=dict(lr=0.16, weight_decay=4e-5), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True), + clip_grad=dict(max_norm=10, norm_type=2)) + +# learning policy +max_epochs = 300 +param_scheduler = [ + dict(type='LinearLR', start_factor=0.1, by_epoch=False, begin=0, end=917), + dict( + type='CosineAnnealingLR', + eta_min=0.0, + begin=1, + T_max=299, + end=300, + by_epoch=True, + convert_to_iter_based=True) +] +train_cfg = dict(max_epochs=max_epochs, val_interval=1) + +vis_backends = [ + dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend') +] +visualizer = dict( + type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer') + +default_hooks = dict(checkpoint=dict(type='CheckpointHook', interval=15)) +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49) +] +# cudnn_benchmark=True can accelerate fix-size training +env_cfg = dict(cudnn_benchmark=True) + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (16 samples per GPU) +auto_scale_lr = dict(base_batch_size=128) diff --git a/mmdetection/projects/EfficientDet/convert_tf_to_pt.py b/mmdetection/projects/EfficientDet/convert_tf_to_pt.py new file mode 100644 index 00000000..f3b127f2 --- /dev/null +++ b/mmdetection/projects/EfficientDet/convert_tf_to_pt.py @@ -0,0 +1,626 @@ +import argparse + +import numpy as np +import torch +from tensorflow.python.training import py_checkpoint_reader + +torch.set_printoptions(precision=20) + + +def tf2pth(v): + if v.ndim == 4: + return np.ascontiguousarray(v.transpose(3, 2, 0, 1)) + elif v.ndim == 2: + return np.ascontiguousarray(v.transpose()) + return v + + +def convert_key(model_name, bifpn_repeats, weights): + + p6_w1 = [ + torch.tensor([-1e4, -1e4], dtype=torch.float64) + for _ in range(bifpn_repeats) + ] + p5_w1 = [ + torch.tensor([-1e4, -1e4], dtype=torch.float64) + for _ in range(bifpn_repeats) + ] + p4_w1 = [ + torch.tensor([-1e4, -1e4], dtype=torch.float64) + for _ in range(bifpn_repeats) + ] + p3_w1 = [ + torch.tensor([-1e4, -1e4], dtype=torch.float64) + for _ in range(bifpn_repeats) + ] + p4_w2 = [ + torch.tensor([-1e4, -1e4, -1e4], dtype=torch.float64) + for _ in range(bifpn_repeats) + ] + p5_w2 = [ + torch.tensor([-1e4, -1e4, -1e4], dtype=torch.float64) + for _ in range(bifpn_repeats) + ] + p6_w2 = [ + torch.tensor([-1e4, -1e4, -1e4], dtype=torch.float64) + for _ in range(bifpn_repeats) + ] + p7_w2 = [ + torch.tensor([-1e4, -1e4], dtype=torch.float64) + for _ in range(bifpn_repeats) + ] + idx2key = { + 0: '1.0', + 1: '2.0', + 2: '2.1', + 3: '3.0', + 4: '3.1', + 5: '4.0', + 6: '4.1', + 7: '4.2', + 8: '4.3', + 9: '4.4', + 10: '4.5', + 11: '5.0', + 12: '5.1', + 13: '5.2', + 14: '5.3', + 15: '5.4' + } + m = dict() + for k, v in weights.items(): + + if 'Exponential' in k or 'global_step' in k: + continue + + seg = k.split('/') + if len(seg) == 1: + continue + if seg[2] == 'depthwise_conv2d': + v = v.transpose(1, 0) + + if seg[0] == model_name: + if seg[1] == 'stem': + prefix = 'backbone.layers.0' + mapping = { + 'conv2d/kernel': 'conv.weight', + 'tpu_batch_normalization/beta': 'bn.bias', + 'tpu_batch_normalization/gamma': 'bn.weight', + 'tpu_batch_normalization/moving_mean': 'bn.running_mean', + 'tpu_batch_normalization/moving_variance': + 'bn.running_var', + } + suffix = mapping['/'.join(seg[2:])] + m[prefix + '.' + suffix] = v + + elif seg[1].startswith('blocks_'): + idx = int(seg[1][7:]) + prefix = '.'.join(['backbone', 'layers', idx2key[idx]]) + base_mapping = { + 'depthwise_conv2d/depthwise_kernel': + 'depthwise_conv.conv.weight', + 'se/conv2d/kernel': 'se.conv1.conv.weight', + 'se/conv2d/bias': 'se.conv1.conv.bias', + 'se/conv2d_1/kernel': 'se.conv2.conv.weight', + 'se/conv2d_1/bias': 'se.conv2.conv.bias' + } + if idx == 0: + mapping = { + 'conv2d/kernel': + 'linear_conv.conv.weight', + 'tpu_batch_normalization/beta': + 'depthwise_conv.bn.bias', + 'tpu_batch_normalization/gamma': + 'depthwise_conv.bn.weight', + 'tpu_batch_normalization/moving_mean': + 'depthwise_conv.bn.running_mean', + 'tpu_batch_normalization/moving_variance': + 'depthwise_conv.bn.running_var', + 'tpu_batch_normalization_1/beta': + 'linear_conv.bn.bias', + 'tpu_batch_normalization_1/gamma': + 'linear_conv.bn.weight', + 'tpu_batch_normalization_1/moving_mean': + 'linear_conv.bn.running_mean', + 'tpu_batch_normalization_1/moving_variance': + 'linear_conv.bn.running_var', + } + else: + mapping = { + 'depthwise_conv2d/depthwise_kernel': + 'depthwise_conv.conv.weight', + 'conv2d/kernel': + 'expand_conv.conv.weight', + 'conv2d_1/kernel': + 'linear_conv.conv.weight', + 'tpu_batch_normalization/beta': + 'expand_conv.bn.bias', + 'tpu_batch_normalization/gamma': + 'expand_conv.bn.weight', + 'tpu_batch_normalization/moving_mean': + 'expand_conv.bn.running_mean', + 'tpu_batch_normalization/moving_variance': + 'expand_conv.bn.running_var', + 'tpu_batch_normalization_1/beta': + 'depthwise_conv.bn.bias', + 'tpu_batch_normalization_1/gamma': + 'depthwise_conv.bn.weight', + 'tpu_batch_normalization_1/moving_mean': + 'depthwise_conv.bn.running_mean', + 'tpu_batch_normalization_1/moving_variance': + 'depthwise_conv.bn.running_var', + 'tpu_batch_normalization_2/beta': + 'linear_conv.bn.bias', + 'tpu_batch_normalization_2/gamma': + 'linear_conv.bn.weight', + 'tpu_batch_normalization_2/moving_mean': + 'linear_conv.bn.running_mean', + 'tpu_batch_normalization_2/moving_variance': + 'linear_conv.bn.running_var', + } + mapping.update(base_mapping) + suffix = mapping['/'.join(seg[2:])] + m[prefix + '.' + suffix] = v + elif seg[0] == 'resample_p6': + prefix = 'neck.bifpn.0.p5_to_p6.0' + mapping = { + 'conv2d/kernel': 'down_conv.weight', + 'conv2d/bias': 'down_conv.bias', + 'bn/beta': 'bn.bias', + 'bn/gamma': 'bn.weight', + 'bn/moving_mean': 'bn.running_mean', + 'bn/moving_variance': 'bn.running_var', + } + suffix = mapping['/'.join(seg[1:])] + m[prefix + '.' + suffix] = v + elif seg[0] == 'fpn_cells': + fpn_idx = int(seg[1][5:]) + prefix = '.'.join(['neck', 'bifpn', str(fpn_idx)]) + fnode_id = int(seg[2][5]) + if fnode_id == 0: + mapping = { + 'op_after_combine5/conv/depthwise_kernel': + 'conv6_up.depthwise_conv.weight', + 'op_after_combine5/conv/pointwise_kernel': + 'conv6_up.pointwise_conv.weight', + 'op_after_combine5/conv/bias': + 'conv6_up.pointwise_conv.bias', + 'op_after_combine5/bn/beta': + 'conv6_up.bn.bias', + 'op_after_combine5/bn/gamma': + 'conv6_up.bn.weight', + 'op_after_combine5/bn/moving_mean': + 'conv6_up.bn.running_mean', + 'op_after_combine5/bn/moving_variance': + 'conv6_up.bn.running_var', + } + if seg[3] != 'WSM' and seg[3] != 'WSM_1': + suffix = mapping['/'.join(seg[3:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif seg[3] == 'WSM': + p6_w1[fpn_idx][0] = v + elif seg[3] == 'WSM_1': + p6_w1[fpn_idx][1] = v + if torch.min(p6_w1[fpn_idx]) > -1e4: + m[prefix + '.p6_w1'] = p6_w1[fpn_idx] + elif fnode_id == 1: + base_mapping = { + 'op_after_combine6/conv/depthwise_kernel': + 'conv5_up.depthwise_conv.weight', + 'op_after_combine6/conv/pointwise_kernel': + 'conv5_up.pointwise_conv.weight', + 'op_after_combine6/conv/bias': + 'conv5_up.pointwise_conv.bias', + 'op_after_combine6/bn/beta': + 'conv5_up.bn.bias', + 'op_after_combine6/bn/gamma': + 'conv5_up.bn.weight', + 'op_after_combine6/bn/moving_mean': + 'conv5_up.bn.running_mean', + 'op_after_combine6/bn/moving_variance': + 'conv5_up.bn.running_var', + } + if fpn_idx == 0: + mapping = { + 'resample_0_2_6/conv2d/kernel': + 'p5_down_channel.down_conv.weight', + 'resample_0_2_6/conv2d/bias': + 'p5_down_channel.down_conv.bias', + 'resample_0_2_6/bn/beta': + 'p5_down_channel.bn.bias', + 'resample_0_2_6/bn/gamma': + 'p5_down_channel.bn.weight', + 'resample_0_2_6/bn/moving_mean': + 'p5_down_channel.bn.running_mean', + 'resample_0_2_6/bn/moving_variance': + 'p5_down_channel.bn.running_var', + } + base_mapping.update(mapping) + if seg[3] != 'WSM' and seg[3] != 'WSM_1': + suffix = base_mapping['/'.join(seg[3:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif seg[3] == 'WSM': + p5_w1[fpn_idx][0] = v + elif seg[3] == 'WSM_1': + p5_w1[fpn_idx][1] = v + if torch.min(p5_w1[fpn_idx]) > -1e4: + m[prefix + '.p5_w1'] = p5_w1[fpn_idx] + elif fnode_id == 2: + base_mapping = { + 'op_after_combine7/conv/depthwise_kernel': + 'conv4_up.depthwise_conv.weight', + 'op_after_combine7/conv/pointwise_kernel': + 'conv4_up.pointwise_conv.weight', + 'op_after_combine7/conv/bias': + 'conv4_up.pointwise_conv.bias', + 'op_after_combine7/bn/beta': + 'conv4_up.bn.bias', + 'op_after_combine7/bn/gamma': + 'conv4_up.bn.weight', + 'op_after_combine7/bn/moving_mean': + 'conv4_up.bn.running_mean', + 'op_after_combine7/bn/moving_variance': + 'conv4_up.bn.running_var', + } + if fpn_idx == 0: + mapping = { + 'resample_0_1_7/conv2d/kernel': + 'p4_down_channel.down_conv.weight', + 'resample_0_1_7/conv2d/bias': + 'p4_down_channel.down_conv.bias', + 'resample_0_1_7/bn/beta': + 'p4_down_channel.bn.bias', + 'resample_0_1_7/bn/gamma': + 'p4_down_channel.bn.weight', + 'resample_0_1_7/bn/moving_mean': + 'p4_down_channel.bn.running_mean', + 'resample_0_1_7/bn/moving_variance': + 'p4_down_channel.bn.running_var', + } + base_mapping.update(mapping) + if seg[3] != 'WSM' and seg[3] != 'WSM_1': + suffix = base_mapping['/'.join(seg[3:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif seg[3] == 'WSM': + p4_w1[fpn_idx][0] = v + elif seg[3] == 'WSM_1': + p4_w1[fpn_idx][1] = v + if torch.min(p4_w1[fpn_idx]) > -1e4: + m[prefix + '.p4_w1'] = p4_w1[fpn_idx] + elif fnode_id == 3: + + base_mapping = { + 'op_after_combine8/conv/depthwise_kernel': + 'conv3_up.depthwise_conv.weight', + 'op_after_combine8/conv/pointwise_kernel': + 'conv3_up.pointwise_conv.weight', + 'op_after_combine8/conv/bias': + 'conv3_up.pointwise_conv.bias', + 'op_after_combine8/bn/beta': + 'conv3_up.bn.bias', + 'op_after_combine8/bn/gamma': + 'conv3_up.bn.weight', + 'op_after_combine8/bn/moving_mean': + 'conv3_up.bn.running_mean', + 'op_after_combine8/bn/moving_variance': + 'conv3_up.bn.running_var', + } + if fpn_idx == 0: + mapping = { + 'resample_0_0_8/conv2d/kernel': + 'p3_down_channel.down_conv.weight', + 'resample_0_0_8/conv2d/bias': + 'p3_down_channel.down_conv.bias', + 'resample_0_0_8/bn/beta': + 'p3_down_channel.bn.bias', + 'resample_0_0_8/bn/gamma': + 'p3_down_channel.bn.weight', + 'resample_0_0_8/bn/moving_mean': + 'p3_down_channel.bn.running_mean', + 'resample_0_0_8/bn/moving_variance': + 'p3_down_channel.bn.running_var', + } + base_mapping.update(mapping) + if seg[3] != 'WSM' and seg[3] != 'WSM_1': + suffix = base_mapping['/'.join(seg[3:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif seg[3] == 'WSM': + p3_w1[fpn_idx][0] = v + elif seg[3] == 'WSM_1': + p3_w1[fpn_idx][1] = v + if torch.min(p3_w1[fpn_idx]) > -1e4: + m[prefix + '.p3_w1'] = p3_w1[fpn_idx] + elif fnode_id == 4: + base_mapping = { + 'op_after_combine9/conv/depthwise_kernel': + 'conv4_down.depthwise_conv.weight', + 'op_after_combine9/conv/pointwise_kernel': + 'conv4_down.pointwise_conv.weight', + 'op_after_combine9/conv/bias': + 'conv4_down.pointwise_conv.bias', + 'op_after_combine9/bn/beta': + 'conv4_down.bn.bias', + 'op_after_combine9/bn/gamma': + 'conv4_down.bn.weight', + 'op_after_combine9/bn/moving_mean': + 'conv4_down.bn.running_mean', + 'op_after_combine9/bn/moving_variance': + 'conv4_down.bn.running_var', + } + if fpn_idx == 0: + mapping = { + 'resample_0_1_9/conv2d/kernel': + 'p4_level_connection.down_conv.weight', + 'resample_0_1_9/conv2d/bias': + 'p4_level_connection.down_conv.bias', + 'resample_0_1_9/bn/beta': + 'p4_level_connection.bn.bias', + 'resample_0_1_9/bn/gamma': + 'p4_level_connection.bn.weight', + 'resample_0_1_9/bn/moving_mean': + 'p4_level_connection.bn.running_mean', + 'resample_0_1_9/bn/moving_variance': + 'p4_level_connection.bn.running_var', + } + base_mapping.update(mapping) + if seg[3] != 'WSM' and seg[3] != 'WSM_1' and seg[3] != 'WSM_2': + suffix = base_mapping['/'.join(seg[3:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif seg[3] == 'WSM': + p4_w2[fpn_idx][0] = v + elif seg[3] == 'WSM_1': + p4_w2[fpn_idx][1] = v + elif seg[3] == 'WSM_2': + p4_w2[fpn_idx][2] = v + if torch.min(p4_w2[fpn_idx]) > -1e4: + m[prefix + '.p4_w2'] = p4_w2[fpn_idx] + elif fnode_id == 5: + base_mapping = { + 'op_after_combine10/conv/depthwise_kernel': + 'conv5_down.depthwise_conv.weight', + 'op_after_combine10/conv/pointwise_kernel': + 'conv5_down.pointwise_conv.weight', + 'op_after_combine10/conv/bias': + 'conv5_down.pointwise_conv.bias', + 'op_after_combine10/bn/beta': + 'conv5_down.bn.bias', + 'op_after_combine10/bn/gamma': + 'conv5_down.bn.weight', + 'op_after_combine10/bn/moving_mean': + 'conv5_down.bn.running_mean', + 'op_after_combine10/bn/moving_variance': + 'conv5_down.bn.running_var', + } + if fpn_idx == 0: + mapping = { + 'resample_0_2_10/conv2d/kernel': + 'p5_level_connection.down_conv.weight', + 'resample_0_2_10/conv2d/bias': + 'p5_level_connection.down_conv.bias', + 'resample_0_2_10/bn/beta': + 'p5_level_connection.bn.bias', + 'resample_0_2_10/bn/gamma': + 'p5_level_connection.bn.weight', + 'resample_0_2_10/bn/moving_mean': + 'p5_level_connection.bn.running_mean', + 'resample_0_2_10/bn/moving_variance': + 'p5_level_connection.bn.running_var', + } + base_mapping.update(mapping) + if seg[3] != 'WSM' and seg[3] != 'WSM_1' and seg[3] != 'WSM_2': + suffix = base_mapping['/'.join(seg[3:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif seg[3] == 'WSM': + p5_w2[fpn_idx][0] = v + elif seg[3] == 'WSM_1': + p5_w2[fpn_idx][1] = v + elif seg[3] == 'WSM_2': + p5_w2[fpn_idx][2] = v + if torch.min(p5_w2[fpn_idx]) > -1e4: + m[prefix + '.p5_w2'] = p5_w2[fpn_idx] + elif fnode_id == 6: + base_mapping = { + 'op_after_combine11/conv/depthwise_kernel': + 'conv6_down.depthwise_conv.weight', + 'op_after_combine11/conv/pointwise_kernel': + 'conv6_down.pointwise_conv.weight', + 'op_after_combine11/conv/bias': + 'conv6_down.pointwise_conv.bias', + 'op_after_combine11/bn/beta': + 'conv6_down.bn.bias', + 'op_after_combine11/bn/gamma': + 'conv6_down.bn.weight', + 'op_after_combine11/bn/moving_mean': + 'conv6_down.bn.running_mean', + 'op_after_combine11/bn/moving_variance': + 'conv6_down.bn.running_var', + } + if seg[3] != 'WSM' and seg[3] != 'WSM_1' and seg[3] != 'WSM_2': + suffix = base_mapping['/'.join(seg[3:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif seg[3] == 'WSM': + p6_w2[fpn_idx][0] = v + elif seg[3] == 'WSM_1': + p6_w2[fpn_idx][1] = v + elif seg[3] == 'WSM_2': + p6_w2[fpn_idx][2] = v + if torch.min(p6_w2[fpn_idx]) > -1e4: + m[prefix + '.p6_w2'] = p6_w2[fpn_idx] + elif fnode_id == 7: + base_mapping = { + 'op_after_combine12/conv/depthwise_kernel': + 'conv7_down.depthwise_conv.weight', + 'op_after_combine12/conv/pointwise_kernel': + 'conv7_down.pointwise_conv.weight', + 'op_after_combine12/conv/bias': + 'conv7_down.pointwise_conv.bias', + 'op_after_combine12/bn/beta': + 'conv7_down.bn.bias', + 'op_after_combine12/bn/gamma': + 'conv7_down.bn.weight', + 'op_after_combine12/bn/moving_mean': + 'conv7_down.bn.running_mean', + 'op_after_combine12/bn/moving_variance': + 'conv7_down.bn.running_var', + } + if seg[3] != 'WSM' and seg[3] != 'WSM_1' and seg[3] != 'WSM_2': + suffix = base_mapping['/'.join(seg[3:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif seg[3] == 'WSM': + p7_w2[fpn_idx][0] = v + elif seg[3] == 'WSM_1': + p7_w2[fpn_idx][1] = v + if torch.min(p7_w2[fpn_idx]) > -1e4: + m[prefix + '.p7_w2'] = p7_w2[fpn_idx] + elif seg[0] == 'box_net': + if 'box-predict' in seg[1]: + prefix = '.'.join(['bbox_head', 'reg_header']) + base_mapping = { + 'depthwise_kernel': 'depthwise_conv.weight', + 'pointwise_kernel': 'pointwise_conv.weight', + 'bias': 'pointwise_conv.bias' + } + suffix = base_mapping['/'.join(seg[2:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif 'bn' in seg[1]: + bbox_conv_idx = int(seg[1][4]) + bbox_bn_idx = int(seg[1][9]) - 3 + prefix = '.'.join([ + 'bbox_head', 'reg_bn_list', + str(bbox_conv_idx), + str(bbox_bn_idx) + ]) + base_mapping = { + 'beta': 'bias', + 'gamma': 'weight', + 'moving_mean': 'running_mean', + 'moving_variance': 'running_var' + } + suffix = base_mapping['/'.join(seg[2:])] + m[prefix + '.' + suffix] = v + else: + bbox_conv_idx = int(seg[1][4]) + prefix = '.'.join( + ['bbox_head', 'reg_conv_list', + str(bbox_conv_idx)]) + base_mapping = { + 'depthwise_kernel': 'depthwise_conv.weight', + 'pointwise_kernel': 'pointwise_conv.weight', + 'bias': 'pointwise_conv.bias' + } + suffix = base_mapping['/'.join(seg[2:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif seg[0] == 'class_net': + if 'class-predict' in seg[1]: + prefix = '.'.join(['bbox_head', 'cls_header']) + base_mapping = { + 'depthwise_kernel': 'depthwise_conv.weight', + 'pointwise_kernel': 'pointwise_conv.weight', + 'bias': 'pointwise_conv.bias' + } + suffix = base_mapping['/'.join(seg[2:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + elif 'bn' in seg[1]: + cls_conv_idx = int(seg[1][6]) + cls_bn_idx = int(seg[1][11]) - 3 + prefix = '.'.join([ + 'bbox_head', 'cls_bn_list', + str(cls_conv_idx), + str(cls_bn_idx) + ]) + base_mapping = { + 'beta': 'bias', + 'gamma': 'weight', + 'moving_mean': 'running_mean', + 'moving_variance': 'running_var' + } + suffix = base_mapping['/'.join(seg[2:])] + m[prefix + '.' + suffix] = v + else: + cls_conv_idx = int(seg[1][6]) + prefix = '.'.join( + ['bbox_head', 'cls_conv_list', + str(cls_conv_idx)]) + base_mapping = { + 'depthwise_kernel': 'depthwise_conv.weight', + 'pointwise_kernel': 'pointwise_conv.weight', + 'bias': 'pointwise_conv.bias' + } + suffix = base_mapping['/'.join(seg[2:])] + if 'depthwise_conv' in suffix: + v = v.transpose(1, 0) + m[prefix + '.' + suffix] = v + return m + + +def parse_args(): + parser = argparse.ArgumentParser( + description='convert efficientdet weight from tensorflow to pytorch') + parser.add_argument( + '--backbone', + type=str, + help='efficientnet model name, like efficientnet-b0') + parser.add_argument( + '--tensorflow_weight', + type=str, + help='efficientdet tensorflow weight name, like efficientdet-d0/model') + parser.add_argument( + '--out_weight', + type=str, + help='efficientdet pytorch weight name like demo.pth') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + model_name = args.backbone + ori_weight_name = args.tensorflow_weight + out_name = args.out_weight + + repeat_map = { + 0: 3, + 1: 4, + 2: 5, + 3: 6, + 4: 7, + 5: 7, + 6: 8, + 7: 8, + } + + reader = py_checkpoint_reader.NewCheckpointReader(ori_weight_name) + weights = { + n: torch.as_tensor(tf2pth(reader.get_tensor(n))) + for (n, _) in reader.get_variable_to_shape_map().items() + } + bifpn_repeats = repeat_map[int(model_name[14])] + out = convert_key(model_name, bifpn_repeats, weights) + result = {'state_dict': out} + torch.save(result, out_name) + + +if __name__ == '__main__': + main() diff --git a/mmdetection/projects/EfficientDet/efficientdet/__init__.py b/mmdetection/projects/EfficientDet/efficientdet/__init__.py new file mode 100644 index 00000000..b6c66bcc --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/__init__.py @@ -0,0 +1,16 @@ +from .bifpn import BiFPN +from .efficientdet import EfficientDet +from .efficientdet_head import EfficientDetSepBNHead +from .huber_loss import HuberLoss +from .tensorflow.anchor_generator import YXYXAnchorGenerator +from .tensorflow.coco_90class import Coco90Dataset +from .tensorflow.coco_90metric import Coco90Metric +from .tensorflow.trans_max_iou_assigner import TransMaxIoUAssigner +from .tensorflow.yxyx_bbox_coder import YXYXDeltaXYWHBBoxCoder +from .utils import Conv2dSamePadding + +__all__ = [ + 'EfficientDet', 'BiFPN', 'HuberLoss', 'EfficientDetSepBNHead', + 'Conv2dSamePadding', 'Coco90Dataset', 'Coco90Metric', + 'YXYXAnchorGenerator', 'TransMaxIoUAssigner', 'YXYXDeltaXYWHBBoxCoder' +] diff --git a/mmdetection/projects/EfficientDet/efficientdet/bifpn.py b/mmdetection/projects/EfficientDet/efficientdet/bifpn.py new file mode 100644 index 00000000..56356c3c --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/bifpn.py @@ -0,0 +1,306 @@ +from typing import List + +import torch +import torch.nn as nn +from mmcv.cnn.bricks import Swish +from mmengine.model import BaseModule + +from mmdet.registry import MODELS +from mmdet.utils import MultiConfig, OptConfigType +from .utils import DepthWiseConvBlock, DownChannelBlock, MaxPool2dSamePadding + + +class BiFPNStage(nn.Module): + """ + in_channels: List[int], input dim for P3, P4, P5 + out_channels: int, output dim for P2 - P7 + first_time: int, whether is the first bifpnstage + conv_bn_act_pattern: bool, whether use conv_bn_act_pattern + norm_cfg: (:obj:`ConfigDict` or dict, optional): Config dict for + normalization layer. + epsilon: float, hyperparameter in fusion features + """ + + def __init__(self, + in_channels: List[int], + out_channels: int, + first_time: bool = False, + apply_bn_for_resampling: bool = True, + conv_bn_act_pattern: bool = False, + norm_cfg: OptConfigType = dict( + type='BN', momentum=1e-2, eps=1e-3), + epsilon: float = 1e-4) -> None: + super().__init__() + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + self.first_time = first_time + self.apply_bn_for_resampling = apply_bn_for_resampling + self.conv_bn_act_pattern = conv_bn_act_pattern + self.norm_cfg = norm_cfg + self.epsilon = epsilon + + if self.first_time: + self.p5_down_channel = DownChannelBlock( + self.in_channels[-1], + self.out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.p4_down_channel = DownChannelBlock( + self.in_channels[-2], + self.out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.p3_down_channel = DownChannelBlock( + self.in_channels[-3], + self.out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.p5_to_p6 = nn.Sequential( + DownChannelBlock( + self.in_channels[-1], + self.out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg), MaxPool2dSamePadding(3, 2)) + self.p6_to_p7 = MaxPool2dSamePadding(3, 2) + self.p4_level_connection = DownChannelBlock( + self.in_channels[-2], + self.out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.p5_level_connection = DownChannelBlock( + self.in_channels[-1], + self.out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + + self.p6_upsample = nn.Upsample(scale_factor=2, mode='nearest') + self.p5_upsample = nn.Upsample(scale_factor=2, mode='nearest') + self.p4_upsample = nn.Upsample(scale_factor=2, mode='nearest') + self.p3_upsample = nn.Upsample(scale_factor=2, mode='nearest') + + # bottom to up: feature map down_sample module + self.p4_down_sample = MaxPool2dSamePadding(3, 2) + self.p5_down_sample = MaxPool2dSamePadding(3, 2) + self.p6_down_sample = MaxPool2dSamePadding(3, 2) + self.p7_down_sample = MaxPool2dSamePadding(3, 2) + + # Fuse Conv Layers + self.conv6_up = DepthWiseConvBlock( + out_channels, + out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.conv5_up = DepthWiseConvBlock( + out_channels, + out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.conv4_up = DepthWiseConvBlock( + out_channels, + out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.conv3_up = DepthWiseConvBlock( + out_channels, + out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.conv4_down = DepthWiseConvBlock( + out_channels, + out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.conv5_down = DepthWiseConvBlock( + out_channels, + out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.conv6_down = DepthWiseConvBlock( + out_channels, + out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + self.conv7_down = DepthWiseConvBlock( + out_channels, + out_channels, + apply_norm=self.apply_bn_for_resampling, + conv_bn_act_pattern=self.conv_bn_act_pattern, + norm_cfg=norm_cfg) + # weights + self.p6_w1 = nn.Parameter( + torch.ones(2, dtype=torch.float32), requires_grad=True) + self.p6_w1_relu = nn.ReLU() + self.p5_w1 = nn.Parameter( + torch.ones(2, dtype=torch.float32), requires_grad=True) + self.p5_w1_relu = nn.ReLU() + self.p4_w1 = nn.Parameter( + torch.ones(2, dtype=torch.float32), requires_grad=True) + self.p4_w1_relu = nn.ReLU() + self.p3_w1 = nn.Parameter( + torch.ones(2, dtype=torch.float32), requires_grad=True) + self.p3_w1_relu = nn.ReLU() + + self.p4_w2 = nn.Parameter( + torch.ones(3, dtype=torch.float32), requires_grad=True) + self.p4_w2_relu = nn.ReLU() + self.p5_w2 = nn.Parameter( + torch.ones(3, dtype=torch.float32), requires_grad=True) + self.p5_w2_relu = nn.ReLU() + self.p6_w2 = nn.Parameter( + torch.ones(3, dtype=torch.float32), requires_grad=True) + self.p6_w2_relu = nn.ReLU() + self.p7_w2 = nn.Parameter( + torch.ones(2, dtype=torch.float32), requires_grad=True) + self.p7_w2_relu = nn.ReLU() + + self.swish = Swish() + + def combine(self, x): + if not self.conv_bn_act_pattern: + x = self.swish(x) + + return x + + def forward(self, x): + if self.first_time: + p3, p4, p5 = x + # build feature map P6 + p6_in = self.p5_to_p6(p5) + # build feature map P7 + p7_in = self.p6_to_p7(p6_in) + + p3_in = self.p3_down_channel(p3) + p4_in = self.p4_down_channel(p4) + p5_in = self.p5_down_channel(p5) + + else: + p3_in, p4_in, p5_in, p6_in, p7_in = x + + # Weights for P6_0 and P7_0 to P6_1 + p6_w1 = self.p6_w1_relu(self.p6_w1) + weight = p6_w1 / (torch.sum(p6_w1, dim=0) + self.epsilon) + # Connections for P6_0 and P7_0 to P6_1 respectively + p6_up = self.conv6_up( + self.combine(weight[0] * p6_in + + weight[1] * self.p6_upsample(p7_in))) + + # Weights for P5_0 and P6_1 to P5_1 + p5_w1 = self.p5_w1_relu(self.p5_w1) + weight = p5_w1 / (torch.sum(p5_w1, dim=0) + self.epsilon) + # Connections for P5_0 and P6_1 to P5_1 respectively + p5_up = self.conv5_up( + self.combine(weight[0] * p5_in + + weight[1] * self.p5_upsample(p6_up))) + + # Weights for P4_0 and P5_1 to P4_1 + p4_w1 = self.p4_w1_relu(self.p4_w1) + weight = p4_w1 / (torch.sum(p4_w1, dim=0) + self.epsilon) + # Connections for P4_0 and P5_1 to P4_1 respectively + p4_up = self.conv4_up( + self.combine(weight[0] * p4_in + + weight[1] * self.p4_upsample(p5_up))) + + # Weights for P3_0 and P4_1 to P3_2 + p3_w1 = self.p3_w1_relu(self.p3_w1) + weight = p3_w1 / (torch.sum(p3_w1, dim=0) + self.epsilon) + # Connections for P3_0 and P4_1 to P3_2 respectively + p3_out = self.conv3_up( + self.combine(weight[0] * p3_in + + weight[1] * self.p3_upsample(p4_up))) + + if self.first_time: + p4_in = self.p4_level_connection(p4) + p5_in = self.p5_level_connection(p5) + + # Weights for P4_0, P4_1 and P3_2 to P4_2 + p4_w2 = self.p4_w2_relu(self.p4_w2) + weight = p4_w2 / (torch.sum(p4_w2, dim=0) + self.epsilon) + # Connections for P4_0, P4_1 and P3_2 to P4_2 respectively + p4_out = self.conv4_down( + self.combine(weight[0] * p4_in + weight[1] * p4_up + + weight[2] * self.p4_down_sample(p3_out))) + + # Weights for P5_0, P5_1 and P4_2 to P5_2 + p5_w2 = self.p5_w2_relu(self.p5_w2) + weight = p5_w2 / (torch.sum(p5_w2, dim=0) + self.epsilon) + # Connections for P5_0, P5_1 and P4_2 to P5_2 respectively + p5_out = self.conv5_down( + self.combine(weight[0] * p5_in + weight[1] * p5_up + + weight[2] * self.p5_down_sample(p4_out))) + + # Weights for P6_0, P6_1 and P5_2 to P6_2 + p6_w2 = self.p6_w2_relu(self.p6_w2) + weight = p6_w2 / (torch.sum(p6_w2, dim=0) + self.epsilon) + # Connections for P6_0, P6_1 and P5_2 to P6_2 respectively + p6_out = self.conv6_down( + self.combine(weight[0] * p6_in + weight[1] * p6_up + + weight[2] * self.p6_down_sample(p5_out))) + + # Weights for P7_0 and P6_2 to P7_2 + p7_w2 = self.p7_w2_relu(self.p7_w2) + weight = p7_w2 / (torch.sum(p7_w2, dim=0) + self.epsilon) + # Connections for P7_0 and P6_2 to P7_2 + p7_out = self.conv7_down( + self.combine(weight[0] * p7_in + + weight[1] * self.p7_down_sample(p6_out))) + return p3_out, p4_out, p5_out, p6_out, p7_out + + +@MODELS.register_module() +class BiFPN(BaseModule): + """ + num_stages: int, bifpn number of repeats + in_channels: List[int], input dim for P3, P4, P5 + out_channels: int, output dim for P2 - P7 + start_level: int, Index of input features in backbone + epsilon: float, hyperparameter in fusion features + apply_bn_for_resampling: bool, whether use bn after resampling + conv_bn_act_pattern: bool, whether use conv_bn_act_pattern + norm_cfg: (:obj:`ConfigDict` or dict, optional): Config dict for + normalization layer. + init_cfg: MultiConfig: init method + """ + + def __init__(self, + num_stages: int, + in_channels: List[int], + out_channels: int, + start_level: int = 0, + epsilon: float = 1e-4, + apply_bn_for_resampling: bool = True, + conv_bn_act_pattern: bool = False, + norm_cfg: OptConfigType = dict( + type='BN', momentum=1e-2, eps=1e-3), + init_cfg: MultiConfig = None) -> None: + super().__init__(init_cfg=init_cfg) + self.start_level = start_level + self.bifpn = nn.Sequential(*[ + BiFPNStage( + in_channels=in_channels, + out_channels=out_channels, + first_time=True if _ == 0 else False, + apply_bn_for_resampling=apply_bn_for_resampling, + conv_bn_act_pattern=conv_bn_act_pattern, + norm_cfg=norm_cfg, + epsilon=epsilon) for _ in range(num_stages) + ]) + + def forward(self, x): + x = x[self.start_level:] + x = self.bifpn(x) + + return x diff --git a/mmdetection/projects/EfficientDet/efficientdet/efficientdet.py b/mmdetection/projects/EfficientDet/efficientdet/efficientdet.py new file mode 100644 index 00000000..84e1778f --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/efficientdet.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmdet.models.detectors.single_stage import SingleStageDetector +from mmdet.registry import MODELS +from mmdet.utils import ConfigType, OptConfigType, OptMultiConfig + + +@MODELS.register_module() +class EfficientDet(SingleStageDetector): + + def __init__(self, + backbone: ConfigType, + neck: ConfigType, + bbox_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None) -> None: + super().__init__( + backbone=backbone, + neck=neck, + bbox_head=bbox_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + init_cfg=init_cfg) diff --git a/mmdetection/projects/EfficientDet/efficientdet/efficientdet_head.py b/mmdetection/projects/EfficientDet/efficientdet/efficientdet_head.py new file mode 100644 index 00000000..ae3efbe2 --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/efficientdet_head.py @@ -0,0 +1,261 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn.bricks import Swish, build_norm_layer +from mmengine.model import bias_init_with_prob +from torch import Tensor + +from mmdet.models.dense_heads.anchor_head import AnchorHead +from mmdet.models.utils import images_to_levels, multi_apply +from mmdet.registry import MODELS +from mmdet.structures.bbox import cat_boxes, get_box_tensor +from mmdet.utils import (InstanceList, OptConfigType, OptInstanceList, + OptMultiConfig, reduce_mean) +from .utils import DepthWiseConvBlock + + +@MODELS.register_module() +class EfficientDetSepBNHead(AnchorHead): + """EfficientDetHead with separate BN. + + num_classes (int): Number of categories num_ins (int): Number of the input + feature map. in_channels (int): Number of channels in the input feature + map. feat_channels (int): Number of hidden channels. stacked_convs (int): + Number of repetitions of conv norm_cfg (dict): Config dict for + normalization layer. anchor_generator (dict): Config dict for anchor + generator bbox_coder (dict): Config of bounding box coder. loss_cls (dict): + Config of classification loss. loss_bbox (dict): Config of localization + loss. train_cfg (dict): Training config of anchor head. test_cfg (dict): + Testing config of anchor head. init_cfg (dict or list[dict], optional): + Initialization config dict. + """ + + def __init__(self, + num_classes: int, + num_ins: int, + in_channels: int, + feat_channels: int, + stacked_convs: int = 3, + norm_cfg: OptConfigType = dict( + type='BN', momentum=1e-2, eps=1e-3), + init_cfg: OptMultiConfig = None, + **kwargs) -> None: + self.num_ins = num_ins + self.stacked_convs = stacked_convs + self.norm_cfg = norm_cfg + super().__init__( + num_classes=num_classes, + in_channels=in_channels, + feat_channels=feat_channels, + init_cfg=init_cfg, + **kwargs) + + def _init_layers(self) -> None: + """Initialize layers of the head.""" + self.reg_conv_list = nn.ModuleList() + self.cls_conv_list = nn.ModuleList() + for i in range(self.stacked_convs): + channels = self.in_channels if i == 0 else self.feat_channels + self.reg_conv_list.append( + DepthWiseConvBlock( + channels, self.feat_channels, apply_norm=False)) + self.cls_conv_list.append( + DepthWiseConvBlock( + channels, self.feat_channels, apply_norm=False)) + + self.reg_bn_list = nn.ModuleList([ + nn.ModuleList([ + build_norm_layer( + self.norm_cfg, num_features=self.feat_channels)[1] + for j in range(self.num_ins) + ]) for i in range(self.stacked_convs) + ]) + + self.cls_bn_list = nn.ModuleList([ + nn.ModuleList([ + build_norm_layer( + self.norm_cfg, num_features=self.feat_channels)[1] + for j in range(self.num_ins) + ]) for i in range(self.stacked_convs) + ]) + + self.cls_header = DepthWiseConvBlock( + self.in_channels, + self.num_base_priors * self.cls_out_channels, + apply_norm=False) + self.reg_header = DepthWiseConvBlock( + self.in_channels, self.num_base_priors * 4, apply_norm=False) + self.swish = Swish() + + def init_weights(self) -> None: + """Initialize weights of the head.""" + for m in self.reg_conv_list: + nn.init.constant_(m.pointwise_conv.bias, 0.0) + for m in self.cls_conv_list: + nn.init.constant_(m.pointwise_conv.bias, 0.0) + bias_cls = bias_init_with_prob(0.01) + nn.init.constant_(self.cls_header.pointwise_conv.bias, bias_cls) + nn.init.constant_(self.reg_header.pointwise_conv.bias, 0.0) + + def forward_single_bbox(self, feat: Tensor, level_id: int, + i: int) -> Tensor: + conv_op = self.reg_conv_list[i] + bn = self.reg_bn_list[i][level_id] + + feat = conv_op(feat) + feat = bn(feat) + feat = self.swish(feat) + + return feat + + def forward_single_cls(self, feat: Tensor, level_id: int, + i: int) -> Tensor: + conv_op = self.cls_conv_list[i] + bn = self.cls_bn_list[i][level_id] + + feat = conv_op(feat) + feat = bn(feat) + feat = self.swish(feat) + + return feat + + def forward(self, feats: Tuple[Tensor]) -> tuple: + cls_scores = [] + bbox_preds = [] + for level_id in range(self.num_ins): + feat = feats[level_id] + for i in range(self.stacked_convs): + feat = self.forward_single_bbox(feat, level_id, i) + bbox_pred = self.reg_header(feat) + bbox_preds.append(bbox_pred) + for level_id in range(self.num_ins): + feat = feats[level_id] + for i in range(self.stacked_convs): + feat = self.forward_single_cls(feat, level_id, i) + cls_score = self.cls_header(feat) + cls_scores.append(cls_score) + + return cls_scores, bbox_preds + + def loss_by_feat( + self, + cls_scores: List[Tensor], + bbox_preds: List[Tensor], + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (list[Tensor]): Box scores for each scale level + has shape (N, num_anchors * num_classes, H, W). + bbox_preds (list[Tensor]): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict: A dictionary of loss components. + """ + featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores] + assert len(featmap_sizes) == self.prior_generator.num_levels + + device = cls_scores[0].device + + anchor_list, valid_flag_list = self.get_anchors( + featmap_sizes, batch_img_metas, device=device) + cls_reg_targets = self.get_targets( + anchor_list, + valid_flag_list, + batch_gt_instances, + batch_img_metas, + batch_gt_instances_ignore=batch_gt_instances_ignore) + (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list, + avg_factor) = cls_reg_targets + + # anchor number of multi levels + num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]] + # concat all level anchors and flags to a single tensor + concat_anchor_list = [] + for i in range(len(anchor_list)): + concat_anchor_list.append(cat_boxes(anchor_list[i])) + all_anchor_list = images_to_levels(concat_anchor_list, + num_level_anchors) + + avg_factor = reduce_mean( + torch.tensor(avg_factor, dtype=torch.float, device=device)).item() + avg_factor = max(avg_factor, 1.0) + losses_cls, losses_bbox = multi_apply( + self.loss_by_feat_single, + cls_scores, + bbox_preds, + all_anchor_list, + labels_list, + label_weights_list, + bbox_targets_list, + bbox_weights_list, + avg_factor=avg_factor) + return dict(loss_cls=losses_cls, loss_bbox=losses_bbox) + + def loss_by_feat_single(self, cls_score: Tensor, bbox_pred: Tensor, + anchors: Tensor, labels: Tensor, + label_weights: Tensor, bbox_targets: Tensor, + bbox_weights: Tensor, avg_factor: int) -> tuple: + """Calculate the loss of a single scale level based on the features + extracted by the detection head. + + Args: + cls_score (Tensor): Box scores for each scale level + Has shape (N, num_anchors * num_classes, H, W). + bbox_pred (Tensor): Box energies / deltas for each scale + level with shape (N, num_anchors * 4, H, W). + anchors (Tensor): Box reference for each scale level with shape + (N, num_total_anchors, 4). + labels (Tensor): Labels of each anchors with shape + (N, num_total_anchors). + label_weights (Tensor): Label weights of each anchor with shape + (N, num_total_anchors) + bbox_targets (Tensor): BBox regression targets of each anchor + weight shape (N, num_total_anchors, 4). + bbox_weights (Tensor): BBox regression loss weights of each anchor + with shape (N, num_total_anchors, 4). + avg_factor (int): Average factor that is used to average the loss. + + Returns: + tuple: loss components. + """ + + # classification loss + labels = labels.reshape(-1) + label_weights = label_weights.reshape(-1) + cls_score = cls_score.permute(0, 2, 3, + 1).reshape(-1, self.cls_out_channels) + loss_cls = self.loss_cls( + cls_score, labels, label_weights, avg_factor=avg_factor) + # regression loss + target_dim = bbox_targets.size(-1) + bbox_targets = bbox_targets.reshape(-1, target_dim) + bbox_weights = bbox_weights.reshape(-1, target_dim) + bbox_pred = bbox_pred.permute(0, 2, 3, + 1).reshape(-1, + self.bbox_coder.encode_size) + if self.reg_decoded_bbox: + # When the regression loss (e.g. `IouLoss`, `GIouLoss`) + # is applied directly on the decoded bounding boxes, it + # decodes the already encoded coordinates to absolute format. + anchors = anchors.reshape(-1, anchors.size(-1)) + bbox_pred = self.bbox_coder.decode(anchors, bbox_pred) + bbox_pred = get_box_tensor(bbox_pred) + loss_bbox = self.loss_bbox( + bbox_pred, bbox_targets, bbox_weights, avg_factor=avg_factor * 4) + return loss_cls, loss_bbox diff --git a/mmdetection/projects/EfficientDet/efficientdet/huber_loss.py b/mmdetection/projects/EfficientDet/efficientdet/huber_loss.py new file mode 100644 index 00000000..091963fa --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/huber_loss.py @@ -0,0 +1,91 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +import torch.nn as nn +from torch import Tensor + +from mmdet.models.losses.utils import weighted_loss +from mmdet.registry import MODELS + + +@weighted_loss +def huber_loss(pred: Tensor, target: Tensor, beta: float = 1.0) -> Tensor: + """Huber loss. + + Args: + pred (Tensor): The prediction. + target (Tensor): The learning target of the prediction. + beta (float, optional): The threshold in the piecewise function. + Defaults to 1.0. + + Returns: + Tensor: Calculated loss + """ + assert beta > 0 + if target.numel() == 0: + return pred.sum() * 0 + + assert pred.size() == target.size() + diff = torch.abs(pred - target) + loss = torch.where(diff < beta, 0.5 * diff * diff, + beta * diff - 0.5 * beta * beta) + return loss + + +@MODELS.register_module() +class HuberLoss(nn.Module): + """Huber loss. + + Args: + beta (float, optional): The threshold in the piecewise function. + Defaults to 1.0. + reduction (str, optional): The method to reduce the loss. + Options are "none", "mean" and "sum". Defaults to "mean". + loss_weight (float, optional): The weight of loss. + """ + + def __init__(self, + beta: float = 1.0, + reduction: str = 'mean', + loss_weight: float = 1.0) -> None: + super().__init__() + self.beta = beta + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + avg_factor: Optional[int] = None, + reduction_override: Optional[str] = None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): The prediction. + target (Tensor): The learning target of the prediction. + weight (Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Defaults to None. + + Returns: + Tensor: Calculated loss + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss_bbox = self.loss_weight * huber_loss( + pred, + target, + weight, + beta=self.beta, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss_bbox diff --git a/mmdetection/projects/EfficientDet/efficientdet/tensorflow/anchor_generator.py b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/anchor_generator.py new file mode 100644 index 00000000..51936a34 --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/anchor_generator.py @@ -0,0 +1,109 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, Union + +import torch +from torch import Tensor + +from mmdet.models.task_modules.prior_generators.anchor_generator import \ + AnchorGenerator +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import HorizontalBoxes + +DeviceType = Union[str, torch.device] + + +@TASK_UTILS.register_module() +class YXYXAnchorGenerator(AnchorGenerator): + + def gen_single_level_base_anchors(self, + base_size: Union[int, float], + scales: Tensor, + ratios: Tensor, + center: Optional[Tuple[float]] = None) \ + -> Tensor: + """Generate base anchors of a single level. + + Args: + base_size (int | float): Basic size of an anchor. + scales (torch.Tensor): Scales of the anchor. + ratios (torch.Tensor): The ratio between the height + and width of anchors in a single level. + center (tuple[float], optional): The center of the base anchor + related to a single feature grid. Defaults to None. + + Returns: + torch.Tensor: Anchors in a single-level feature maps. + """ + + w = base_size + h = base_size + if center is None: + x_center = self.center_offset * w + y_center = self.center_offset * h + else: + x_center, y_center = center + + h_ratios = torch.sqrt(ratios) + w_ratios = 1 / h_ratios + if self.scale_major: + ws = (w * scales[:, None] * w_ratios[None, :]).view(-1) + hs = (h * scales[:, None] * h_ratios[None, :]).view(-1) + else: + ws = (w * scales[:, None] * w_ratios[None, :]).view(-1) + hs = (h * scales[:, None] * h_ratios[None, :]).view(-1) + + # use float anchor and the anchor's center is aligned with the + # pixel center + base_anchors = [ + y_center - 0.5 * hs, + x_center - 0.5 * ws, + y_center + 0.5 * hs, + x_center + 0.5 * ws, + ] + base_anchors = torch.stack(base_anchors, dim=-1) + + return base_anchors + + def single_level_grid_priors(self, + featmap_size: Tuple[int, int], + level_idx: int, + dtype: torch.dtype = torch.float32, + device: DeviceType = 'cuda') -> Tensor: + """Generate grid anchors of a single level. + + Note: + This function is usually called by method ``self.grid_priors``. + + Args: + featmap_size (tuple[int, int]): Size of the feature maps. + level_idx (int): The index of corresponding feature map level. + dtype (obj:`torch.dtype`): Date type of points.Defaults to + ``torch.float32``. + device (str | torch.device): The device the tensor will be put on. + Defaults to 'cuda'. + + Returns: + torch.Tensor: Anchors in the overall feature maps. + """ + base_anchors = self.base_anchors[level_idx].to(device).to(dtype) + feat_h, feat_w = featmap_size + stride_w, stride_h = self.strides[level_idx] + # First create Range with the default dtype, than convert to + # target `dtype` for onnx exporting. + shift_x = torch.arange(0, feat_w, device=device).to(dtype) * stride_w + shift_y = torch.arange(0, feat_h, device=device).to(dtype) * stride_h + + shift_xx, shift_yy = self._meshgrid(shift_x, shift_y) + shifts = torch.stack([shift_yy, shift_xx, shift_yy, shift_xx], dim=-1) + # first feat_w elements correspond to the first row of shifts + # add A anchors (1, A, 4) to K shifts (K, 1, 4) to get + # shifted anchors (K, A, 4), reshape to (K*A, 4) + + all_anchors = base_anchors[None, :, :] + shifts[:, None, :] + all_anchors = all_anchors.view(-1, 4) + # first A rows correspond to A anchors of (0, 0) in feature map, + # then (0, 1), (0, 2), ... + if self.use_box_type: + all_anchors = HorizontalBoxes(all_anchors) + + return all_anchors diff --git a/mmdetection/projects/EfficientDet/efficientdet/tensorflow/api_wrappers/__init__.py b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/api_wrappers/__init__.py new file mode 100644 index 00000000..a27afc46 --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/api_wrappers/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .coco_api import COCO, COCOeval, COCOPanoptic + +__all__ = ['COCO', 'COCOeval', 'COCOPanoptic'] diff --git a/mmdetection/projects/EfficientDet/efficientdet/tensorflow/api_wrappers/coco_api.py b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/api_wrappers/coco_api.py new file mode 100644 index 00000000..142f27d7 --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/api_wrappers/coco_api.py @@ -0,0 +1,145 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# This file add snake case alias for coco api + +import warnings +from collections import defaultdict +from typing import List, Optional, Union + +import pycocotools +from pycocotools.coco import COCO as _COCO +from pycocotools.cocoeval import COCOeval as _COCOeval + + +class COCO(_COCO): + """This class is almost the same as official pycocotools package. + + It implements some snake case function aliases. So that the COCO class has + the same interface as LVIS class. + """ + + def __init__(self, annotation_file=None): + if getattr(pycocotools, '__version__', '0') >= '12.0.2': + warnings.warn( + 'mmpycocotools is deprecated. Please install official pycocotools by "pip install pycocotools"', # noqa: E501 + UserWarning) + super().__init__(annotation_file=annotation_file) + self.img_ann_map = self.imgToAnns + self.cat_img_map = self.catToImgs + + def get_ann_ids(self, img_ids=[], cat_ids=[], area_rng=[], iscrowd=None): + return self.getAnnIds(img_ids, cat_ids, area_rng, iscrowd) + + def get_cat_ids(self, cat_names=[], sup_names=[], cat_ids=[]): + cat_ids_coco = self.getCatIds(cat_names, sup_names, cat_ids) + if None in cat_names: + index = [i for i, v in enumerate(cat_names) if v is not None] + cat_ids = list(range(len(cat_names))) + for i in range(len(index)): + cat_ids[index[i]] = cat_ids_coco[i] + return cat_ids + else: + return cat_ids_coco + + def get_img_ids(self, img_ids=[], cat_ids=[]): + return self.getImgIds(img_ids, cat_ids) + + def load_anns(self, ids): + return self.loadAnns(ids) + + def load_cats(self, ids): + return self.loadCats(ids) + + def load_imgs(self, ids): + return self.loadImgs(ids) + + +# just for the ease of import +COCOeval = _COCOeval + + +class COCOPanoptic(COCO): + """This wrapper is for loading the panoptic style annotation file. + + The format is shown in the CocoPanopticDataset class. + + Args: + annotation_file (str, optional): Path of annotation file. + Defaults to None. + """ + + def __init__(self, annotation_file: Optional[str] = None) -> None: + super(COCOPanoptic, self).__init__(annotation_file) + + def createIndex(self) -> None: + """Create index.""" + # create index + print('creating index...') + # anns stores 'segment_id -> annotation' + anns, cats, imgs = {}, {}, {} + img_to_anns, cat_to_imgs = defaultdict(list), defaultdict(list) + if 'annotations' in self.dataset: + for ann in self.dataset['annotations']: + for seg_ann in ann['segments_info']: + # to match with instance.json + seg_ann['image_id'] = ann['image_id'] + img_to_anns[ann['image_id']].append(seg_ann) + # segment_id is not unique in coco dataset orz... + # annotations from different images but + # may have same segment_id + if seg_ann['id'] in anns.keys(): + anns[seg_ann['id']].append(seg_ann) + else: + anns[seg_ann['id']] = [seg_ann] + + # filter out annotations from other images + img_to_anns_ = defaultdict(list) + for k, v in img_to_anns.items(): + img_to_anns_[k] = [x for x in v if x['image_id'] == k] + img_to_anns = img_to_anns_ + + if 'images' in self.dataset: + for img_info in self.dataset['images']: + img_info['segm_file'] = img_info['file_name'].replace( + 'jpg', 'png') + imgs[img_info['id']] = img_info + + if 'categories' in self.dataset: + for cat in self.dataset['categories']: + cats[cat['id']] = cat + + if 'annotations' in self.dataset and 'categories' in self.dataset: + for ann in self.dataset['annotations']: + for seg_ann in ann['segments_info']: + cat_to_imgs[seg_ann['category_id']].append(ann['image_id']) + + print('index created!') + + self.anns = anns + self.imgToAnns = img_to_anns + self.catToImgs = cat_to_imgs + self.imgs = imgs + self.cats = cats + + def load_anns(self, + ids: Union[List[int], int] = []) -> Optional[List[dict]]: + """Load anns with the specified ids. + + ``self.anns`` is a list of annotation lists instead of a + list of annotations. + + Args: + ids (Union[List[int], int]): Integer ids specifying anns. + + Returns: + anns (List[dict], optional): Loaded ann objects. + """ + anns = [] + + if hasattr(ids, '__iter__') and hasattr(ids, '__len__'): + # self.anns is a list of annotation lists instead of + # a list of annotations + for id in ids: + anns += self.anns[id] + return anns + elif type(ids) == int: + return self.anns[ids] diff --git a/mmdetection/projects/EfficientDet/efficientdet/tensorflow/coco_90class.py b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/coco_90class.py new file mode 100644 index 00000000..d2996ccb --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/coco_90class.py @@ -0,0 +1,198 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +from typing import List, Union + +from mmengine.fileio import get_local_path + +from mmdet.datasets.base_det_dataset import BaseDetDataset +from mmdet.registry import DATASETS +from .api_wrappers import COCO + + +@DATASETS.register_module() +class Coco90Dataset(BaseDetDataset): + """Dataset for COCO.""" + + METAINFO = { + 'classes': + ('person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'traffic light', 'fire hydrant', None, 'stop sign', + 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', + 'cow', 'elephant', 'bear', 'zebra', 'giraffe', None, 'backpack', + 'umbrella', None, None, 'handbag', 'tie', 'suitcase', 'frisbee', + 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', + 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', + 'bottle', None, 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', + 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', + 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', + 'bed', None, 'dining table', None, None, 'toilet', None, 'tv', + 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', + 'oven', 'toaster', 'sink', 'refrigerator', None, 'book', 'clock', + 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'), + # palette is a list of color tuples, which is used for visualization. + 'palette': + [(220, 20, 60), (119, 11, 32), (0, 0, 142), (0, 0, 230), (106, 0, 228), + (0, 60, 100), (0, 80, 100), (0, 0, 70), (0, 0, 192), (250, 170, 30), + (100, 170, 30), None, (220, 220, 0), (175, 116, 175), (250, 0, 30), + (165, 42, 42), (255, 77, 255), (0, 226, 252), (182, 182, 255), + (0, 82, 0), (120, 166, 157), (110, 76, 0), (174, 57, 255), + (199, 100, 0), (72, 0, 118), None, + (255, 179, 240), (0, 125, 92), None, None, (209, 0, 151), + (188, 208, 182), (0, 220, 176), (255, 99, 164), (92, 0, 73), + (133, 129, 255), (78, 180, 255), (0, 228, 0), (174, 255, 243), + (45, 89, 255), (134, 134, 103), (145, 148, 174), (255, 208, 186), + (197, 226, 255), None, (171, 134, 1), (109, 63, 54), (207, 138, 255), + (151, 0, 95), (9, 80, 61), (84, 105, 51), (74, 65, 105), + (166, 196, 102), (208, 195, 210), (255, 109, 65), (0, 143, 149), + (179, 0, 194), (209, 99, 106), (5, 121, 0), (227, 255, 205), + (147, 186, 208), (153, 69, 1), (3, 95, 161), (163, 255, 0), + (119, 0, 170), None, (0, 182, 199), None, None, (0, 165, 120), None, + (183, 130, 88), (95, 32, 0), (130, 114, 135), (110, 129, 133), + (166, 74, 118), (219, 142, 185), (79, 210, 114), (178, 90, 62), + (65, 70, 15), (127, 167, 115), (59, 105, 106), None, (142, 108, 45), + (196, 172, 0), (95, 54, 80), (128, 76, 255), (201, 57, 1), + (246, 0, 122), (191, 162, 208)] + } + COCOAPI = COCO + # ann_id is unique in coco dataset. + ANN_ID_UNIQUE = True + + def load_data_list(self) -> List[dict]: + """Load annotations from an annotation file named as ``self.ann_file`` + + Returns: + List[dict]: A list of annotation. + """ # noqa: E501 + with get_local_path( + self.ann_file, backend_args=self.backend_args) as local_path: + self.coco = self.COCOAPI(local_path) + # The order of returned `cat_ids` will not + # change with the order of the `classes` + self.cat_ids = self.coco.get_cat_ids( + cat_names=self.metainfo['classes']) + self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)} + self.cat_img_map = copy.deepcopy(self.coco.cat_img_map) + + img_ids = self.coco.get_img_ids() + data_list = [] + total_ann_ids = [] + for img_id in img_ids: + raw_img_info = self.coco.load_imgs([img_id])[0] + raw_img_info['img_id'] = img_id + + ann_ids = self.coco.get_ann_ids(img_ids=[img_id]) + raw_ann_info = self.coco.load_anns(ann_ids) + total_ann_ids.extend(ann_ids) + + parsed_data_info = self.parse_data_info({ + 'raw_ann_info': + raw_ann_info, + 'raw_img_info': + raw_img_info + }) + data_list.append(parsed_data_info) + if self.ANN_ID_UNIQUE: + assert len(set(total_ann_ids)) == len( + total_ann_ids + ), f"Annotation ids in '{self.ann_file}' are not unique!" + + del self.coco + + return data_list + + def parse_data_info(self, raw_data_info: dict) -> Union[dict, List[dict]]: + """Parse raw annotation to target format. + + Args: + raw_data_info (dict): Raw data information load from ``ann_file`` + + Returns: + Union[dict, List[dict]]: Parsed annotation. + """ + img_info = raw_data_info['raw_img_info'] + ann_info = raw_data_info['raw_ann_info'] + + data_info = {} + + # TODO: need to change data_prefix['img'] to data_prefix['img_path'] + img_path = osp.join(self.data_prefix['img'], img_info['file_name']) + if self.data_prefix.get('seg', None): + seg_map_path = osp.join( + self.data_prefix['seg'], + img_info['file_name'].rsplit('.', 1)[0] + self.seg_map_suffix) + else: + seg_map_path = None + data_info['img_path'] = img_path + data_info['img_id'] = img_info['img_id'] + data_info['seg_map_path'] = seg_map_path + data_info['height'] = img_info['height'] + data_info['width'] = img_info['width'] + + instances = [] + for i, ann in enumerate(ann_info): + instance = {} + + if ann.get('ignore', False): + continue + x1, y1, w, h = ann['bbox'] + inter_w = max(0, min(x1 + w, img_info['width']) - max(x1, 0)) + inter_h = max(0, min(y1 + h, img_info['height']) - max(y1, 0)) + if inter_w * inter_h == 0: + continue + if ann['area'] <= 0 or w < 1 or h < 1: + continue + if ann['category_id'] not in self.cat_ids: + continue + bbox = [x1, y1, x1 + w, y1 + h] + + if ann.get('iscrowd', False): + instance['ignore_flag'] = 1 + else: + instance['ignore_flag'] = 0 + instance['bbox'] = bbox + instance['bbox_label'] = self.cat2label[ann['category_id']] + + if ann.get('segmentation', None): + instance['mask'] = ann['segmentation'] + + instances.append(instance) + data_info['instances'] = instances + return data_info + + def filter_data(self) -> List[dict]: + """Filter annotations according to filter_cfg. + + Returns: + List[dict]: Filtered results. + """ + if self.test_mode: + return self.data_list + + if self.filter_cfg is None: + return self.data_list + + filter_empty_gt = self.filter_cfg.get('filter_empty_gt', False) + min_size = self.filter_cfg.get('min_size', 0) + + # obtain images that contain annotation + ids_with_ann = set(data_info['img_id'] for data_info in self.data_list) + # obtain images that contain annotations of the required categories + ids_in_cat = set() + for i, class_id in enumerate(self.cat_ids): + ids_in_cat |= set(self.cat_img_map[class_id]) + # merge the image id sets of the two conditions and use the merged set + # to filter out images if self.filter_empty_gt=True + ids_in_cat &= ids_with_ann + + valid_data_infos = [] + for i, data_info in enumerate(self.data_list): + img_id = data_info['img_id'] + width = data_info['width'] + height = data_info['height'] + if filter_empty_gt and img_id not in ids_in_cat: + continue + if min(width, height) >= min_size: + valid_data_infos.append(data_info) + + return valid_data_infos diff --git a/mmdetection/projects/EfficientDet/efficientdet/tensorflow/coco_90metric.py b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/coco_90metric.py new file mode 100644 index 00000000..eed65224 --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/coco_90metric.py @@ -0,0 +1,540 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import datetime +import itertools +import os.path as osp +import tempfile +from collections import OrderedDict +from typing import Dict, List, Optional, Sequence, Union + +import numpy as np +from mmengine.evaluator import BaseMetric +from mmengine.fileio import dump, get_local_path, load +from mmengine.logging import MMLogger +from terminaltables import AsciiTable + +from mmdet.evaluation.functional import eval_recalls +from mmdet.registry import METRICS +from mmdet.structures.mask import encode_mask_results +from .api_wrappers import COCO, COCOeval + + +@METRICS.register_module() +class Coco90Metric(BaseMetric): + """COCO evaluation metric. + + Evaluate AR, AP, and mAP for detection tasks including proposal/box + detection and instance segmentation. Please refer to + https://cocodataset.org/#detection-eval for more details. + + Args: + ann_file (str, optional): Path to the coco format annotation file. + If not specified, ground truth annotations from the dataset will + be converted to coco format. Defaults to None. + metric (str | List[str]): Metrics to be evaluated. Valid metrics + include 'bbox', 'segm', 'proposal', and 'proposal_fast'. + Defaults to 'bbox'. + classwise (bool): Whether to evaluate the metric class-wise. + Defaults to False. + proposal_nums (Sequence[int]): Numbers of proposals to be evaluated. + Defaults to (100, 300, 1000). + iou_thrs (float | List[float], optional): IoU threshold to compute AP + and AR. If not specified, IoUs from 0.5 to 0.95 will be used. + Defaults to None. + metric_items (List[str], optional): Metric result names to be + recorded in the evaluation result. Defaults to None. + format_only (bool): Format the output results without perform + evaluation. It is useful when you want to format the result + to a specific format and submit it to the test server. + Defaults to False. + outfile_prefix (str, optional): The prefix of json files. It includes + the file path and the prefix of filename, e.g., "a/b/prefix". + If not specified, a temp file will be created. Defaults to None. + backend_args (dict, optional): Arguments to instantiate the + corresponding backend. Defaults to None. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + """ + default_prefix: Optional[str] = 'coco' + + def __init__(self, + ann_file: Optional[str] = None, + metric: Union[str, List[str]] = 'bbox', + classwise: bool = False, + proposal_nums: Sequence[int] = (100, 300, 1000), + iou_thrs: Optional[Union[float, Sequence[float]]] = None, + metric_items: Optional[Sequence[str]] = None, + format_only: bool = False, + outfile_prefix: Optional[str] = None, + backend_args: dict = None, + collect_device: str = 'cpu', + prefix: Optional[str] = None) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + # coco evaluation metrics + self.metrics = metric if isinstance(metric, list) else [metric] + allowed_metrics = ['bbox', 'segm', 'proposal', 'proposal_fast'] + for metric in self.metrics: + if metric not in allowed_metrics: + raise KeyError( + "metric should be one of 'bbox', 'segm', 'proposal', " + f"'proposal_fast', but got {metric}.") + + # do class wise evaluation, default False + self.classwise = classwise + + # proposal_nums used to compute recall or precision. + self.proposal_nums = list(proposal_nums) + + # iou_thrs used to compute recall or precision. + if iou_thrs is None: + iou_thrs = np.linspace( + .5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True) + self.iou_thrs = iou_thrs + self.metric_items = metric_items + self.format_only = format_only + if self.format_only: + assert outfile_prefix is not None, 'outfile_prefix must be not' + 'None when format_only is True, otherwise the result files will' + 'be saved to a temp directory which will be cleaned up at the end.' + + self.outfile_prefix = outfile_prefix + + self.backend_args = backend_args + + # if ann_file is not specified, + # initialize coco api with the converted dataset + if ann_file is not None: + with get_local_path( + ann_file, backend_args=self.backend_args) as local_path: + self._coco_api = COCO(local_path) + else: + self._coco_api = None + + # handle dataset lazy init + self.cat_ids = None + self.img_ids = None + + def fast_eval_recall(self, + results: List[dict], + proposal_nums: Sequence[int], + iou_thrs: Sequence[float], + logger: Optional[MMLogger] = None) -> np.ndarray: + """Evaluate proposal recall with COCO's fast_eval_recall. + + Args: + results (List[dict]): Results of the dataset. + proposal_nums (Sequence[int]): Proposal numbers used for + evaluation. + iou_thrs (Sequence[float]): IoU thresholds used for evaluation. + logger (MMLogger, optional): Logger used for logging the recall + summary. + Returns: + np.ndarray: Averaged recall results. + """ + gt_bboxes = [] + pred_bboxes = [result['bboxes'] for result in results] + for i in range(len(self.img_ids)): + ann_ids = self._coco_api.get_ann_ids(img_ids=self.img_ids[i]) + ann_info = self._coco_api.load_anns(ann_ids) + if len(ann_info) == 0: + gt_bboxes.append(np.zeros((0, 4))) + continue + bboxes = [] + for ann in ann_info: + if ann.get('ignore', False) or ann['iscrowd']: + continue + x1, y1, w, h = ann['bbox'] + bboxes.append([x1, y1, x1 + w, y1 + h]) + bboxes = np.array(bboxes, dtype=np.float32) + if bboxes.shape[0] == 0: + bboxes = np.zeros((0, 4)) + gt_bboxes.append(bboxes) + + recalls = eval_recalls( + gt_bboxes, pred_bboxes, proposal_nums, iou_thrs, logger=logger) + ar = recalls.mean(axis=1) + return ar + + def xyxy2xywh(self, bbox: np.ndarray) -> list: + """Convert ``xyxy`` style bounding boxes to ``xywh`` style for COCO + evaluation. + + Args: + bbox (numpy.ndarray): The bounding boxes, shape (4, ), in + ``xyxy`` order. + + Returns: + list[float]: The converted bounding boxes, in ``xywh`` order. + """ + + _bbox: List = bbox.tolist() + return [ + _bbox[0], + _bbox[1], + _bbox[2] - _bbox[0], + _bbox[3] - _bbox[1], + ] + + def results2json(self, results: Sequence[dict], + outfile_prefix: str) -> dict: + """Dump the detection results to a COCO style json file. + + There are 3 types of results: proposals, bbox predictions, mask + predictions, and they have different data types. This method will + automatically recognize the type, and dump them to json files. + + Args: + results (Sequence[dict]): Testing results of the + dataset. + outfile_prefix (str): The filename prefix of the json files. If the + prefix is "somepath/xxx", the json files will be named + "somepath/xxx.bbox.json", "somepath/xxx.segm.json", + "somepath/xxx.proposal.json". + + Returns: + dict: Possible keys are "bbox", "segm", "proposal", and + values are corresponding filenames. + """ + bbox_json_results = [] + segm_json_results = [] if 'masks' in results[0] else None + for idx, result in enumerate(results): + image_id = result.get('img_id', idx) + labels = result['labels'] + bboxes = result['bboxes'] + scores = result['scores'] + # bbox results + for i, label in enumerate(labels): + data = dict() + data['image_id'] = image_id + data['bbox'] = self.xyxy2xywh(bboxes[i]) + data['score'] = float(scores[i]) + data['category_id'] = self.cat_ids[label] + bbox_json_results.append(data) + + if segm_json_results is None: + continue + + # segm results + masks = result['masks'] + mask_scores = result.get('mask_scores', scores) + for i, label in enumerate(labels): + data = dict() + data['image_id'] = image_id + data['bbox'] = self.xyxy2xywh(bboxes[i]) + data['score'] = float(mask_scores[i]) + data['category_id'] = self.cat_ids[label] + if isinstance(masks[i]['counts'], bytes): + masks[i]['counts'] = masks[i]['counts'].decode() + data['segmentation'] = masks[i] + segm_json_results.append(data) + + result_files = dict() + result_files['bbox'] = f'{outfile_prefix}.bbox.json' + result_files['proposal'] = f'{outfile_prefix}.bbox.json' + dump(bbox_json_results, result_files['bbox']) + + if segm_json_results is not None: + result_files['segm'] = f'{outfile_prefix}.segm.json' + dump(segm_json_results, result_files['segm']) + + return result_files + + def gt_to_coco_json(self, gt_dicts: Sequence[dict], + outfile_prefix: str) -> str: + """Convert ground truth to coco format json file. + + Args: + gt_dicts (Sequence[dict]): Ground truth of the dataset. + outfile_prefix (str): The filename prefix of the json files. If the + prefix is "somepath/xxx", the json file will be named + "somepath/xxx.gt.json". + Returns: + str: The filename of the json file. + """ + categories = [ + dict(id=id, name=name) + for id, name in enumerate(self.dataset_meta['classes']) + ] + image_infos = [] + annotations = [] + + for idx, gt_dict in enumerate(gt_dicts): + img_id = gt_dict.get('img_id', idx) + image_info = dict( + id=img_id, + width=gt_dict['width'], + height=gt_dict['height'], + file_name='') + image_infos.append(image_info) + for ann in gt_dict['anns']: + label = ann['bbox_label'] + bbox = ann['bbox'] + coco_bbox = [ + bbox[0], + bbox[1], + bbox[2] - bbox[0], + bbox[3] - bbox[1], + ] + + annotation = dict( + id=len(annotations) + + 1, # coco api requires id starts with 1 + image_id=img_id, + bbox=coco_bbox, + iscrowd=ann.get('ignore_flag', 0), + category_id=int(label), + area=coco_bbox[2] * coco_bbox[3]) + if ann.get('mask', None): + mask = ann['mask'] + # area = mask_util.area(mask) + if isinstance(mask, dict) and isinstance( + mask['counts'], bytes): + mask['counts'] = mask['counts'].decode() + annotation['segmentation'] = mask + # annotation['area'] = float(area) + annotations.append(annotation) + + info = dict( + date_created=str(datetime.datetime.now()), + description='Coco json file converted by mmdet CocoMetric.') + coco_json = dict( + info=info, + images=image_infos, + categories=categories, + licenses=None, + ) + if len(annotations) > 0: + coco_json['annotations'] = annotations + converted_json_path = f'{outfile_prefix}.gt.json' + dump(coco_json, converted_json_path) + return converted_json_path + + # TODO: data_batch is no longer needed, consider adjusting the + # parameter position + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data samples and predictions. The processed + results should be stored in ``self.results``, which will be used to + compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of data samples that + contain annotations and predictions. + """ + for data_sample in data_samples: + result = dict() + pred = data_sample['pred_instances'] + result['img_id'] = data_sample['img_id'] + result['bboxes'] = pred['bboxes'].cpu().numpy() + result['scores'] = pred['scores'].cpu().numpy() + result['labels'] = pred['labels'].cpu().numpy() + # encode mask to RLE + if 'masks' in pred: + result['masks'] = encode_mask_results( + pred['masks'].detach().cpu().numpy()) + # some detectors use different scores for bbox and mask + if 'mask_scores' in pred: + result['mask_scores'] = pred['mask_scores'].cpu().numpy() + + # parse gt + gt = dict() + gt['width'] = data_sample['ori_shape'][1] + gt['height'] = data_sample['ori_shape'][0] + gt['img_id'] = data_sample['img_id'] + if self._coco_api is None: + # TODO: Need to refactor to support LoadAnnotations + assert 'instances' in data_sample, \ + 'ground truth is required for evaluation when ' \ + '`ann_file` is not provided' + gt['anns'] = data_sample['instances'] + # add converted result to the results list + self.results.append((gt, result)) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. + """ + logger: MMLogger = MMLogger.get_current_instance() + + # split gt and prediction list + gts, preds = zip(*results) + + tmp_dir = None + if self.outfile_prefix is None: + tmp_dir = tempfile.TemporaryDirectory() + outfile_prefix = osp.join(tmp_dir.name, 'results') + else: + outfile_prefix = self.outfile_prefix + + if self._coco_api is None: + # use converted gt json file to initialize coco api + logger.info('Converting ground truth to coco format...') + coco_json_path = self.gt_to_coco_json( + gt_dicts=gts, outfile_prefix=outfile_prefix) + self._coco_api = COCO(coco_json_path) + + # handle lazy init + if self.cat_ids is None: + self.cat_ids = self._coco_api.get_cat_ids( + cat_names=self.dataset_meta['classes']) + if self.img_ids is None: + self.img_ids = self._coco_api.get_img_ids() + + # convert predictions to coco format and dump to json file + result_files = self.results2json(preds, outfile_prefix) + + eval_results = OrderedDict() + if self.format_only: + logger.info('results are saved in ' + f'{osp.dirname(outfile_prefix)}') + return eval_results + + for metric in self.metrics: + logger.info(f'Evaluating {metric}...') + + # TODO: May refactor fast_eval_recall to an independent metric? + # fast eval recall + if metric == 'proposal_fast': + ar = self.fast_eval_recall( + preds, self.proposal_nums, self.iou_thrs, logger=logger) + log_msg = [] + for i, num in enumerate(self.proposal_nums): + eval_results[f'AR@{num}'] = ar[i] + log_msg.append(f'\nAR@{num}\t{ar[i]:.4f}') + log_msg = ''.join(log_msg) + logger.info(log_msg) + continue + + # evaluate proposal, bbox and segm + iou_type = 'bbox' if metric == 'proposal' else metric + if metric not in result_files: + raise KeyError(f'{metric} is not in results') + try: + predictions = load(result_files[metric]) + if iou_type == 'segm': + # Refer to https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/coco.py#L331 # noqa + # When evaluating mask AP, if the results contain bbox, + # cocoapi will use the box area instead of the mask area + # for calculating the instance area. Though the overall AP + # is not affected, this leads to different + # small/medium/large mask AP results. + for x in predictions: + x.pop('bbox') + coco_dt = self._coco_api.loadRes(predictions) + + except IndexError: + logger.error( + 'The testing results of the whole dataset is empty.') + break + + coco_eval = COCOeval(self._coco_api, coco_dt, iou_type) + + coco_eval.params.catIds = self.cat_ids + coco_eval.params.imgIds = self.img_ids + coco_eval.params.maxDets = list(self.proposal_nums) + coco_eval.params.iouThrs = self.iou_thrs + + # mapping of cocoEval.stats + coco_metric_names = { + 'mAP': 0, + 'mAP_50': 1, + 'mAP_75': 2, + 'mAP_s': 3, + 'mAP_m': 4, + 'mAP_l': 5, + 'AR@100': 6, + 'AR@300': 7, + 'AR@1000': 8, + 'AR_s@1000': 9, + 'AR_m@1000': 10, + 'AR_l@1000': 11 + } + metric_items = self.metric_items + if metric_items is not None: + for metric_item in metric_items: + if metric_item not in coco_metric_names: + raise KeyError( + f'metric item "{metric_item}" is not supported') + + if metric == 'proposal': + coco_eval.params.useCats = 0 + coco_eval.evaluate() + coco_eval.accumulate() + coco_eval.summarize() + if metric_items is None: + metric_items = [ + 'AR@100', 'AR@300', 'AR@1000', 'AR_s@1000', + 'AR_m@1000', 'AR_l@1000' + ] + + for item in metric_items: + val = float( + f'{coco_eval.stats[coco_metric_names[item]]:.3f}') + eval_results[item] = val + else: + coco_eval.evaluate() + coco_eval.accumulate() + coco_eval.summarize() + if self.classwise: # Compute per-category AP + # Compute per-category AP + # from https://github.com/facebookresearch/detectron2/ + precisions = coco_eval.eval['precision'] + # precision: (iou, recall, cls, area range, max dets) + assert len(self.cat_ids) == precisions.shape[2] + + results_per_category = [] + for idx, cat_id in enumerate(self.cat_ids): + # area range index 0: all area ranges + # max dets index -1: typically 100 per image + nm = self._coco_api.loadCats(cat_id)[0] + precision = precisions[:, :, idx, 0, -1] + precision = precision[precision > -1] + if precision.size: + ap = np.mean(precision) + else: + ap = float('nan') + results_per_category.append( + (f'{nm["name"]}', f'{round(ap, 3)}')) + eval_results[f'{nm["name"]}_precision'] = round(ap, 3) + + num_columns = min(6, len(results_per_category) * 2) + results_flatten = list( + itertools.chain(*results_per_category)) + headers = ['category', 'AP'] * (num_columns // 2) + results_2d = itertools.zip_longest(*[ + results_flatten[i::num_columns] + for i in range(num_columns) + ]) + table_data = [headers] + table_data += [result for result in results_2d] + table = AsciiTable(table_data) + logger.info('\n' + table.table) + + if metric_items is None: + metric_items = [ + 'mAP', 'mAP_50', 'mAP_75', 'mAP_s', 'mAP_m', 'mAP_l' + ] + + for metric_item in metric_items: + key = f'{metric}_{metric_item}' + val = coco_eval.stats[coco_metric_names[metric_item]] + eval_results[key] = float(f'{round(val, 3)}') + + ap = coco_eval.stats[:6] + logger.info(f'{metric}_mAP_copypaste: {ap[0]:.3f} ' + f'{ap[1]:.3f} {ap[2]:.3f} {ap[3]:.3f} ' + f'{ap[4]:.3f} {ap[5]:.3f}') + + if tmp_dir is not None: + tmp_dir.cleanup() + return eval_results diff --git a/mmdetection/projects/EfficientDet/efficientdet/tensorflow/trans_max_iou_assigner.py b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/trans_max_iou_assigner.py new file mode 100644 index 00000000..10fc45b5 --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/trans_max_iou_assigner.py @@ -0,0 +1,110 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch +from mmengine.structures import InstanceData + +from mmdet.models.task_modules.assigners.assign_result import AssignResult +from mmdet.models.task_modules.assigners.max_iou_assigner import MaxIoUAssigner +from mmdet.registry import TASK_UTILS + + +@TASK_UTILS.register_module() +class TransMaxIoUAssigner(MaxIoUAssigner): + + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs) -> AssignResult: + """Assign gt to bboxes. + + This method assign a gt bbox to every bbox (proposal/anchor), each bbox + will be assigned with -1, or a semi-positive number. -1 means negative + sample, semi-positive number is the index (0-based) of assigned gt. + The assignment is done in following steps, the order matters. + + 1. assign every bbox to the background + 2. assign proposals whose iou with all gts < neg_iou_thr to 0 + 3. for each bbox, if the iou with its nearest gt >= pos_iou_thr, + assign it to that bbox + 4. for each gt bbox, assign its nearest proposals (may be more than + one) to itself + + Args: + pred_instances (:obj:`InstanceData`): Instances of model + predictions. It includes ``priors``, and the priors can + be anchors or points, or the bboxes predicted by the + previous stage, has shape (n, 4). The bboxes predicted by + the current model or stage will be named ``bboxes``, + ``labels``, and ``scores``, the same as the ``InstanceData`` + in other places. + gt_instances (:obj:`InstanceData`): Ground truth of instance + annotations. It usually includes ``bboxes``, with shape (k, 4), + and ``labels``, with shape (k, ). + gt_instances_ignore (:obj:`InstanceData`, optional): Instances + to be ignored during training. It includes ``bboxes`` + attribute data that is ignored during training and testing. + Defaults to None. + + Returns: + :obj:`AssignResult`: The assign result. + + Example: + >>> from mmengine.structures import InstanceData + >>> self = MaxIoUAssigner(0.5, 0.5) + >>> pred_instances = InstanceData() + >>> pred_instances.priors = torch.Tensor([[0, 0, 10, 10], + ... [10, 10, 20, 20]]) + >>> gt_instances = InstanceData() + >>> gt_instances.bboxes = torch.Tensor([[0, 0, 10, 9]]) + >>> gt_instances.labels = torch.Tensor([0]) + >>> assign_result = self.assign(pred_instances, gt_instances) + >>> expected_gt_inds = torch.LongTensor([1, 0]) + >>> assert torch.all(assign_result.gt_inds == expected_gt_inds) + """ + gt_bboxes = gt_instances.bboxes + priors = pred_instances.priors + gt_labels = gt_instances.labels + if gt_instances_ignore is not None: + gt_bboxes_ignore = gt_instances_ignore.bboxes + else: + gt_bboxes_ignore = None + + assign_on_cpu = True if (self.gpu_assign_thr > 0) and ( + gt_bboxes.shape[0] > self.gpu_assign_thr) else False + # compute overlap and assign gt on CPU when number of GT is large + if assign_on_cpu: + device = priors.device + priors = priors.cpu() + gt_bboxes = gt_bboxes.cpu() + gt_labels = gt_labels.cpu() + if gt_bboxes_ignore is not None: + gt_bboxes_ignore = gt_bboxes_ignore.cpu() + + trans_priors = torch.cat([ + priors[..., 1].view(-1, 1), priors[..., 0].view(-1, 1), + priors[..., 3].view(-1, 1), priors[..., 2].view(-1, 1) + ], + dim=-1) + overlaps = self.iou_calculator(gt_bboxes, trans_priors) + + if (self.ignore_iof_thr > 0 and gt_bboxes_ignore is not None + and gt_bboxes_ignore.numel() > 0 and trans_priors.numel() > 0): + if self.ignore_wrt_candidates: + ignore_overlaps = self.iou_calculator( + trans_priors, gt_bboxes_ignore, mode='iof') + ignore_max_overlaps, _ = ignore_overlaps.max(dim=1) + else: + ignore_overlaps = self.iou_calculator( + gt_bboxes_ignore, trans_priors, mode='iof') + ignore_max_overlaps, _ = ignore_overlaps.max(dim=0) + overlaps[:, ignore_max_overlaps > self.ignore_iof_thr] = -1 + + assign_result = self.assign_wrt_overlaps(overlaps, gt_labels) + if assign_on_cpu: + assign_result.gt_inds = assign_result.gt_inds.to(device) + assign_result.max_overlaps = assign_result.max_overlaps.to(device) + if assign_result.labels is not None: + assign_result.labels = assign_result.labels.to(device) + return assign_result diff --git a/mmdetection/projects/EfficientDet/efficientdet/tensorflow/yxyx_bbox_coder.py b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/yxyx_bbox_coder.py new file mode 100644 index 00000000..63e23300 --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/tensorflow/yxyx_bbox_coder.py @@ -0,0 +1,369 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import numpy as np +import torch + +from mmdet.models.task_modules.coders.delta_xywh_bbox_coder import \ + DeltaXYWHBBoxCoder +from mmdet.registry import TASK_UTILS +from mmdet.structures.bbox import HorizontalBoxes, get_box_tensor + + +@TASK_UTILS.register_module() +class YXYXDeltaXYWHBBoxCoder(DeltaXYWHBBoxCoder): + + def encode(self, bboxes, gt_bboxes): + """Get box regression transformation deltas that can be used to + transform the ``bboxes`` into the ``gt_bboxes``. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): Source boxes, + e.g., object proposals. + gt_bboxes (torch.Tensor or :obj:`BaseBoxes`): Target of the + transformation, e.g., ground-truth boxes. + + Returns: + torch.Tensor: Box transformation deltas + """ + bboxes = get_box_tensor(bboxes) + gt_bboxes = get_box_tensor(gt_bboxes) + assert bboxes.size(0) == gt_bboxes.size(0) + assert bboxes.size(-1) == gt_bboxes.size(-1) == 4 + encoded_bboxes = YXbbox2delta(bboxes, gt_bboxes, self.means, self.stds) + return encoded_bboxes + + def decode(self, + bboxes, + pred_bboxes, + max_shape=None, + wh_ratio_clip=16 / 1000): + """Apply transformation `pred_bboxes` to `boxes`. + + Args: + bboxes (torch.Tensor or :obj:`BaseBoxes`): Basic boxes. Shape + (B, N, 4) or (N, 4) + pred_bboxes (Tensor): Encoded offsets with respect to each roi. + Has shape (B, N, num_classes * 4) or (B, N, 4) or + (N, num_classes * 4) or (N, 4). Note N = num_anchors * W * H + when rois is a grid of anchors.Offset encoding follows [1]_. + max_shape (Sequence[int] or torch.Tensor or Sequence[ + Sequence[int]],optional): Maximum bounds for boxes, specifies + (H, W, C) or (H, W). If bboxes shape is (B, N, 4), then + the max_shape should be a Sequence[Sequence[int]] + and the length of max_shape should also be B. + wh_ratio_clip (float, optional): The allowed ratio between + width and height. + + Returns: + Union[torch.Tensor, :obj:`BaseBoxes`]: Decoded boxes. + """ + bboxes = get_box_tensor(bboxes) + assert pred_bboxes.size(0) == bboxes.size(0) + if pred_bboxes.ndim == 3: + assert pred_bboxes.size(1) == bboxes.size(1) + + if pred_bboxes.ndim == 2 and not torch.onnx.is_in_onnx_export(): + # single image decode + decoded_bboxes = YXdelta2bbox(bboxes, pred_bboxes, self.means, + self.stds, max_shape, wh_ratio_clip, + self.clip_border, self.add_ctr_clamp, + self.ctr_clamp) + else: + if pred_bboxes.ndim == 3 and not torch.onnx.is_in_onnx_export(): + warnings.warn( + 'DeprecationWarning: onnx_delta2bbox is deprecated ' + 'in the case of batch decoding and non-ONNX, ' + 'please use “delta2bbox” instead. In order to improve ' + 'the decoding speed, the batch function will no ' + 'longer be supported. ') + decoded_bboxes = YXonnx_delta2bbox(bboxes, pred_bboxes, self.means, + self.stds, max_shape, + wh_ratio_clip, self.clip_border, + self.add_ctr_clamp, + self.ctr_clamp) + + if self.use_box_type: + assert decoded_bboxes.size(-1) == 4, \ + ('Cannot warp decoded boxes with box type when decoded boxes' + 'have shape of (N, num_classes * 4)') + decoded_bboxes = HorizontalBoxes(decoded_bboxes) + return decoded_bboxes + + +def YXdelta2bbox(rois, + deltas, + means=(0., 0., 0., 0.), + stds=(1., 1., 1., 1.), + max_shape=None, + hw_ratio_clip=1000 / 16, + clip_border=True, + add_ctr_clamp=False, + ctr_clamp=32): + """Apply deltas to shift/scale base boxes. + + Typically the rois are anchor or proposed bounding boxes and the deltas are + network outputs used to shift/scale those boxes. + This is the inverse function of :func:`bbox2delta`. + + Args: + rois (Tensor): Boxes to be transformed. Has shape (N, 4). + deltas (Tensor): Encoded offsets relative to each roi. + Has shape (N, num_classes * 4) or (N, 4). Note + N = num_base_anchors * W * H, when rois is a grid of + anchors. Offset encoding follows [1]_. + means (Sequence[float]): Denormalizing means for delta coordinates. + Default (0., 0., 0., 0.). + stds (Sequence[float]): Denormalizing standard deviation for delta + coordinates. Default (1., 1., 1., 1.). + max_shape (tuple[int, int]): Maximum bounds for boxes, specifies + (H, W). Default None. + wh_ratio_clip (float): Maximum aspect ratio for boxes. Default + 16 / 1000. + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Default True. + add_ctr_clamp (bool): Whether to add center clamp. When set to True, + the center of the prediction bounding box will be clamped to + avoid being too far away from the center of the anchor. + Only used by YOLOF. Default False. + ctr_clamp (int): the maximum pixel shift to clamp. Only used by YOLOF. + Default 32. + + Returns: + Tensor: Boxes with shape (N, num_classes * 4) or (N, 4), where 4 + represent tl_x, tl_y, br_x, br_y. + + References: + .. [1] https://arxiv.org/abs/1311.2524 + + Example: + >>> rois = torch.Tensor([[ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 5., 5., 5., 5.]]) + >>> deltas = torch.Tensor([[ 0., 0., 0., 0.], + >>> [ 1., 1., 1., 1.], + >>> [ 0., 0., 2., -1.], + >>> [ 0.7, -1.9, -0.5, 0.3]]) + >>> delta2bbox(rois, deltas, max_shape=(32, 32, 3)) + tensor([[0.0000, 0.0000, 1.0000, 1.0000], + [0.1409, 0.1409, 2.8591, 2.8591], + [0.0000, 0.3161, 4.1945, 0.6839], + [5.0000, 5.0000, 5.0000, 5.0000]]) + """ + num_bboxes, num_classes = deltas.size(0), deltas.size(1) // 4 + if num_bboxes == 0: + return deltas + + deltas = deltas.reshape(-1, 4) + + means = deltas.new_tensor(means).view(1, -1) + stds = deltas.new_tensor(stds).view(1, -1) + denorm_deltas = deltas * stds + means + + dyx = denorm_deltas[:, :2] + dhw = denorm_deltas[:, 2:] + + # Compute width/height of each roi + rois_ = rois.repeat(1, num_classes).reshape(-1, 4) + pyx = ((rois_[:, :2] + rois_[:, 2:]) * 0.5) + phw = (rois_[:, 2:] - rois_[:, :2]) + + dyx_hw = phw * dyx + + max_ratio = np.abs(np.log(hw_ratio_clip)) + if add_ctr_clamp: + dyx_hw = torch.clamp(dyx_hw, max=ctr_clamp, min=-ctr_clamp) + dhw = torch.clamp(dhw, max=max_ratio) + else: + dhw = dhw.clamp(min=-max_ratio, max=max_ratio) + + gyx = pyx + dyx_hw + ghw = phw * dhw.exp() + y1x1 = gyx - (ghw * 0.5) + y2x2 = gyx + (ghw * 0.5) + ymin, xmin = y1x1[:, 0].reshape(-1, 1), y1x1[:, 1].reshape(-1, 1) + ymax, xmax = y2x2[:, 0].reshape(-1, 1), y2x2[:, 1].reshape(-1, 1) + bboxes = torch.cat([xmin, ymin, xmax, ymax], dim=-1) + if clip_border and max_shape is not None: + bboxes[..., 0::2].clamp_(min=0, max=max_shape[1]) + bboxes[..., 1::2].clamp_(min=0, max=max_shape[0]) + bboxes = bboxes.reshape(num_bboxes, -1) + return bboxes + + +def YXbbox2delta(proposals, gt, means=(0., 0., 0., 0.), stds=(1., 1., 1., 1.)): + """Compute deltas of proposals w.r.t. gt. + + We usually compute the deltas of x, y, w, h of proposals w.r.t ground + truth bboxes to get regression target. + This is the inverse function of :func:`delta2bbox`. + + Args: + proposals (Tensor): Boxes to be transformed, shape (N, ..., 4) + gt (Tensor): Gt bboxes to be used as base, shape (N, ..., 4) + means (Sequence[float]): Denormalizing means for delta coordinates + stds (Sequence[float]): Denormalizing standard deviation for delta + coordinates + + Returns: + Tensor: deltas with shape (N, 4), where columns represent dx, dy, + dw, dh. + """ + assert proposals.size() == gt.size() + + proposals = proposals.float() + gt = gt.float() + py = (proposals[..., 0] + proposals[..., 2]) * 0.5 + px = (proposals[..., 1] + proposals[..., 3]) * 0.5 + ph = proposals[..., 2] - proposals[..., 0] + pw = proposals[..., 3] - proposals[..., 1] + + gx = (gt[..., 0] + gt[..., 2]) * 0.5 + gy = (gt[..., 1] + gt[..., 3]) * 0.5 + gw = gt[..., 2] - gt[..., 0] + gh = gt[..., 3] - gt[..., 1] + + dx = (gx - px) / pw + dy = (gy - py) / ph + dw = torch.log(gw / pw) + dh = torch.log(gh / ph) + deltas = torch.stack([dy, dx, dh, dw], dim=-1) + + means = deltas.new_tensor(means).unsqueeze(0) + stds = deltas.new_tensor(stds).unsqueeze(0) + deltas = deltas.sub_(means).div_(stds) + + return deltas + + +def YXonnx_delta2bbox(rois, + deltas, + means=(0., 0., 0., 0.), + stds=(1., 1., 1., 1.), + max_shape=None, + wh_ratio_clip=16 / 1000, + clip_border=True, + add_ctr_clamp=False, + ctr_clamp=32): + """Apply deltas to shift/scale base boxes. + + Typically the rois are anchor or proposed bounding boxes and the deltas are + network outputs used to shift/scale those boxes. + This is the inverse function of :func:`bbox2delta`. + + Args: + rois (Tensor): Boxes to be transformed. Has shape (N, 4) or (B, N, 4) + deltas (Tensor): Encoded offsets with respect to each roi. + Has shape (B, N, num_classes * 4) or (B, N, 4) or + (N, num_classes * 4) or (N, 4). Note N = num_anchors * W * H + when rois is a grid of anchors.Offset encoding follows [1]_. + means (Sequence[float]): Denormalizing means for delta coordinates. + Default (0., 0., 0., 0.). + stds (Sequence[float]): Denormalizing standard deviation for delta + coordinates. Default (1., 1., 1., 1.). + max_shape (Sequence[int] or torch.Tensor or Sequence[ + Sequence[int]],optional): Maximum bounds for boxes, specifies + (H, W, C) or (H, W). If rois shape is (B, N, 4), then + the max_shape should be a Sequence[Sequence[int]] + and the length of max_shape should also be B. Default None. + wh_ratio_clip (float): Maximum aspect ratio for boxes. + Default 16 / 1000. + clip_border (bool, optional): Whether clip the objects outside the + border of the image. Default True. + add_ctr_clamp (bool): Whether to add center clamp, when added, the + predicted box is clamped is its center is too far away from + the original anchor's center. Only used by YOLOF. Default False. + ctr_clamp (int): the maximum pixel shift to clamp. Only used by YOLOF. + Default 32. + + Returns: + Tensor: Boxes with shape (B, N, num_classes * 4) or (B, N, 4) or + (N, num_classes * 4) or (N, 4), where 4 represent + tl_x, tl_y, br_x, br_y. + + References: + .. [1] https://arxiv.org/abs/1311.2524 + + Example: + >>> rois = torch.Tensor([[ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 0., 0., 1., 1.], + >>> [ 5., 5., 5., 5.]]) + >>> deltas = torch.Tensor([[ 0., 0., 0., 0.], + >>> [ 1., 1., 1., 1.], + >>> [ 0., 0., 2., -1.], + >>> [ 0.7, -1.9, -0.5, 0.3]]) + >>> delta2bbox(rois, deltas, max_shape=(32, 32, 3)) + tensor([[0.0000, 0.0000, 1.0000, 1.0000], + [0.1409, 0.1409, 2.8591, 2.8591], + [0.0000, 0.3161, 4.1945, 0.6839], + [5.0000, 5.0000, 5.0000, 5.0000]]) + """ + means = deltas.new_tensor(means).view(1, + -1).repeat(1, + deltas.size(-1) // 4) + stds = deltas.new_tensor(stds).view(1, -1).repeat(1, deltas.size(-1) // 4) + denorm_deltas = deltas * stds + means + dy = denorm_deltas[..., 0::4] + dx = denorm_deltas[..., 1::4] + dh = denorm_deltas[..., 2::4] + dw = denorm_deltas[..., 3::4] + + y1, x1 = rois[..., 0], rois[..., 1] + y2, x2 = rois[..., 2], rois[..., 3] + # Compute center of each roi + px = ((x1 + x2) * 0.5).unsqueeze(-1).expand_as(dx) + py = ((y1 + y2) * 0.5).unsqueeze(-1).expand_as(dy) + # Compute width/height of each roi + pw = (x2 - x1).unsqueeze(-1).expand_as(dw) + ph = (y2 - y1).unsqueeze(-1).expand_as(dh) + + dx_width = pw * dx + dy_height = ph * dy + + max_ratio = np.abs(np.log(wh_ratio_clip)) + if add_ctr_clamp: + dx_width = torch.clamp(dx_width, max=ctr_clamp, min=-ctr_clamp) + dy_height = torch.clamp(dy_height, max=ctr_clamp, min=-ctr_clamp) + dw = torch.clamp(dw, max=max_ratio) + dh = torch.clamp(dh, max=max_ratio) + else: + dw = dw.clamp(min=-max_ratio, max=max_ratio) + dh = dh.clamp(min=-max_ratio, max=max_ratio) + # Use exp(network energy) to enlarge/shrink each roi + gw = pw * dw.exp() + gh = ph * dh.exp() + # Use network energy to shift the center of each roi + gx = px + dx_width + gy = py + dy_height + # Convert center-xy/width/height to top-left, bottom-right + x1 = gx - gw * 0.5 + y1 = gy - gh * 0.5 + x2 = gx + gw * 0.5 + y2 = gy + gh * 0.5 + + bboxes = torch.stack([x1, y1, x2, y2], dim=-1).view(deltas.size()) + + if clip_border and max_shape is not None: + # clip bboxes with dynamic `min` and `max` for onnx + if torch.onnx.is_in_onnx_export(): + from mmdet.core.export import dynamic_clip_for_onnx + x1, y1, x2, y2 = dynamic_clip_for_onnx(x1, y1, x2, y2, max_shape) + bboxes = torch.stack([x1, y1, x2, y2], dim=-1).view(deltas.size()) + return bboxes + if not isinstance(max_shape, torch.Tensor): + max_shape = x1.new_tensor(max_shape) + max_shape = max_shape[..., :2].type_as(x1) + if max_shape.ndim == 2: + assert bboxes.ndim == 3 + assert max_shape.size(0) == bboxes.size(0) + + min_xy = x1.new_tensor(0) + max_xy = torch.cat( + [max_shape] * (deltas.size(-1) // 2), + dim=-1).flip(-1).unsqueeze(-2) + bboxes = torch.where(bboxes < min_xy, min_xy, bboxes) + bboxes = torch.where(bboxes > max_xy, max_xy, bboxes) + + return bboxes diff --git a/mmdetection/projects/EfficientDet/efficientdet/utils.py b/mmdetection/projects/EfficientDet/efficientdet/utils.py new file mode 100644 index 00000000..9c30a01f --- /dev/null +++ b/mmdetection/projects/EfficientDet/efficientdet/utils.py @@ -0,0 +1,154 @@ +import math +from typing import Tuple, Union + +import torch +import torch.nn as nn +from mmcv.cnn.bricks import Swish, build_norm_layer +from torch.nn import functional as F +from torch.nn.init import _calculate_fan_in_and_fan_out, trunc_normal_ + +from mmdet.registry import MODELS +from mmdet.utils import OptConfigType + + +def variance_scaling_trunc(tensor, gain=1.): + fan_in, _ = _calculate_fan_in_and_fan_out(tensor) + gain /= max(1.0, fan_in) + std = math.sqrt(gain) / .87962566103423978 + return trunc_normal_(tensor, 0., std) + + +@MODELS.register_module() +class Conv2dSamePadding(nn.Conv2d): + + def __init__(self, + in_channels: int, + out_channels: int, + kernel_size: Union[int, Tuple[int, int]], + stride: Union[int, Tuple[int, int]] = 1, + padding: Union[int, Tuple[int, int]] = 0, + dilation: Union[int, Tuple[int, int]] = 1, + groups: int = 1, + bias: bool = True): + super().__init__(in_channels, out_channels, kernel_size, stride, 0, + dilation, groups, bias) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + img_h, img_w = x.size()[-2:] + kernel_h, kernel_w = self.weight.size()[-2:] + extra_w = (math.ceil(img_w / self.stride[1]) - + 1) * self.stride[1] - img_w + kernel_w + extra_h = (math.ceil(img_h / self.stride[0]) - + 1) * self.stride[0] - img_h + kernel_h + + left = extra_w // 2 + right = extra_w - left + top = extra_h // 2 + bottom = extra_h - top + x = F.pad(x, [left, right, top, bottom]) + return F.conv2d(x, self.weight, self.bias, self.stride, self.padding, + self.dilation, self.groups) + + +class MaxPool2dSamePadding(nn.Module): + + def __init__(self, + kernel_size: Union[int, Tuple[int, int]] = 3, + stride: Union[int, Tuple[int, int]] = 2, + **kwargs): + super().__init__() + self.pool = nn.MaxPool2d(kernel_size, stride, **kwargs) + self.stride = self.pool.stride + self.kernel_size = self.pool.kernel_size + + if isinstance(self.stride, int): + self.stride = [self.stride] * 2 + if isinstance(self.kernel_size, int): + self.kernel_size = [self.kernel_size] * 2 + + def forward(self, x): + h, w = x.shape[-2:] + + extra_h = (math.ceil(w / self.stride[1]) - + 1) * self.stride[1] - w + self.kernel_size[1] + extra_v = (math.ceil(h / self.stride[0]) - + 1) * self.stride[0] - h + self.kernel_size[0] + + left = extra_h // 2 + right = extra_h - left + top = extra_v // 2 + bottom = extra_v - top + + x = F.pad(x, [left, right, top, bottom]) + x = self.pool(x) + + return x + + +class DepthWiseConvBlock(nn.Module): + + def __init__( + self, + in_channels: int, + out_channels: int, + apply_norm: bool = True, + conv_bn_act_pattern: bool = False, + norm_cfg: OptConfigType = dict(type='BN', momentum=1e-2, eps=1e-3) + ) -> None: + super(DepthWiseConvBlock, self).__init__() + self.depthwise_conv = Conv2dSamePadding( + in_channels, + in_channels, + kernel_size=3, + stride=1, + groups=in_channels, + bias=False) + self.pointwise_conv = Conv2dSamePadding( + in_channels, out_channels, kernel_size=1, stride=1) + + self.apply_norm = apply_norm + if self.apply_norm: + self.bn = build_norm_layer(norm_cfg, num_features=out_channels)[1] + + self.apply_activation = conv_bn_act_pattern + if self.apply_activation: + self.swish = Swish() + + def forward(self, x): + x = self.depthwise_conv(x) + x = self.pointwise_conv(x) + if self.apply_norm: + x = self.bn(x) + if self.apply_activation: + x = self.swish(x) + + return x + + +class DownChannelBlock(nn.Module): + + def __init__( + self, + in_channels: int, + out_channels: int, + apply_norm: bool = True, + conv_bn_act_pattern: bool = False, + norm_cfg: OptConfigType = dict(type='BN', momentum=1e-2, eps=1e-3) + ) -> None: + super(DownChannelBlock, self).__init__() + self.down_conv = Conv2dSamePadding(in_channels, out_channels, 1) + self.apply_norm = apply_norm + if self.apply_norm: + self.bn = build_norm_layer(norm_cfg, num_features=out_channels)[1] + self.apply_activation = conv_bn_act_pattern + if self.apply_activation: + self.swish = Swish() + + def forward(self, x): + x = self.down_conv(x) + if self.apply_norm: + x = self.bn(x) + if self.apply_activation: + x = self.swish(x) + + return x diff --git a/mmdetection/projects/HDINO/README.md b/mmdetection/projects/HDINO/README.md new file mode 100644 index 00000000..078ca429 --- /dev/null +++ b/mmdetection/projects/HDINO/README.md @@ -0,0 +1,35 @@ +# H-DETR + +> [DETRs with Hybrid Matching](https://arxiv.org/abs/2207.13080) + + + +## Abstract + +One-to-one set matching is a key design for DETR to establish its end-to-end capability, so that object detection does not require a hand-crafted NMS (non-maximum suppression) to remove duplicate detections. This end-to-end signature is important for the versatility of DETR, and it has been generalized to broader vision tasks. However, we note that there are few queries assigned as positive samples and the one-to-one set matching significantly reduces the training efficacy of positive samples. We propose a simple yet effective method based on a hybrid matching scheme that combines the original one-to-one matching branch with an auxiliary one-to-many matching branch during training. Our hybrid strategy has been shown to significantly improve accuracy. In inference, only the original one-to-one match branch is used, thus maintaining the end-to-end merit and the same inference efficiency of DETR. The method is named H-DETR, and it shows that a wide range of representative DETR methods can be consistently improved across a wide range of visual tasks, including DeformableDETR, PETRv2, PETR, and TransTrack, among others. + +
    + +
    + +## Results and Models + +| Backbone | Model | Lr schd | box AP | Config | Download | +| :------: | :-----------: | :-----: | :----: | :--------------------------------------------: | :------: | +| R-50 | H-DINO-4scale | 12e | 48.0 | [config](./h-dino-4scale_r50_8xb2-12e_coco.py) | | + +### NOTE + +1. We are based on `DINO` rather than `Deformable DETR` to support the `Hybrid Matching` algorithm. +2. We found that directly applying Hybrid Matching to the DINO algorithm results in a significant decrease in performance. If you have any other insights or suggestions, please feel free to comment or submit a pull request (PR). + +## Citation + +```latex +@article{jia2022detrs, + title={DETRs with Hybrid Matching}, + author={Jia, Ding and Yuan, Yuhui and He, Haodi and Wu, Xiaopei and Yu, Haojun and Lin, Weihong and Sun, Lei and Zhang, Chao and Hu, Han}, + journal={arXiv preprint arXiv:2207.13080}, + year={2022} +} +``` diff --git a/mmdetection/projects/HDINO/__init__.py b/mmdetection/projects/HDINO/__init__.py new file mode 100644 index 00000000..f8c3478b --- /dev/null +++ b/mmdetection/projects/HDINO/__init__.py @@ -0,0 +1,4 @@ +from .h_dino import HDINO +from .h_dino_head import HybridDINOHead + +__all__ = ['HDINO', 'HybridDINOHead'] diff --git a/mmdetection/projects/HDINO/h-dino-4scale_r50_8xb2-12e_coco.py b/mmdetection/projects/HDINO/h-dino-4scale_r50_8xb2-12e_coco.py new file mode 100644 index 00000000..7b16b48d --- /dev/null +++ b/mmdetection/projects/HDINO/h-dino-4scale_r50_8xb2-12e_coco.py @@ -0,0 +1,168 @@ +_base_ = [ + '../../configs/_base_/datasets/coco_detection.py', + '../../configs/_base_/default_runtime.py' +] + +custom_imports = dict(imports=['projects.HDINO'], allow_failed_imports=False) + +model = dict( + type='HDINO', + num_queries=1800, # num_total_queries: 900+900 + with_box_refine=True, + as_two_stage=True, + data_preprocessor=dict( + type='DetDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_size_divisor=1), + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(1, 2, 3), + frozen_stages=1, + norm_cfg=dict(type='BN', requires_grad=False), + norm_eval=True, + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + neck=dict( + type='ChannelMapper', + in_channels=[512, 1024, 2048], + kernel_size=1, + out_channels=256, + act_cfg=None, + norm_cfg=dict(type='GN', num_groups=32), + num_outs=4), + encoder=dict( + num_layers=6, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0))), # 0.1 for DeformDETR + decoder=dict( + num_layers=6, + return_intermediate=True, + layer_cfg=dict( + self_attn_cfg=dict(embed_dims=256, num_heads=8, + dropout=0.0), # 0.1 for DeformDETR + cross_attn_cfg=dict(embed_dims=256, num_levels=4, + dropout=0.0), # 0.1 for DeformDETR + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, # 1024 for DeformDETR + ffn_drop=0.0)), # 0.1 for DeformDETR + post_norm_cfg=None), + positional_encoding=dict( + num_feats=128, + normalize=True, + offset=0.0, # -0.5 for DeformDETR + temperature=20), # 10000 for DeformDETR + bbox_head=dict( + type='HybridDINOHead', + num_classes=80, + sync_cls_avg_factor=True, + num_query_one2one=900, + k_one2many=2, + loss_cls=dict( + type='FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + loss_weight=1.0), # 2.0 in DeformDETR + loss_bbox=dict(type='L1Loss', loss_weight=5.0), + loss_iou=dict(type='GIoULoss', loss_weight=2.0)), + dn_cfg=dict( + label_noise_scale=0.5, + box_noise_scale=1.0, # 0.4 for DN-DETR + group_cfg=dict(dynamic=True, num_groups=None, num_dn_queries=100)), + # training and testing settings + train_cfg=dict( + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='FocalLossCost', weight=2.0), + dict(type='BBoxL1Cost', weight=5.0, box_format='xywh'), + dict(type='IoUCost', iou_mode='giou', weight=2.0) + ])), + test_cfg=dict(max_per_img=300)) # 100 for DeformDETR + +# train_pipeline, NOTE the img_scale and the Pad's size_divisor is different +# from the default setting in mmdet. +train_pipeline = [ + dict(type='LoadImageFromFile', backend_args=_base_.backend_args), + dict(type='LoadAnnotations', with_bbox=True), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomChoice', + transforms=[ + [ + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ], + [ + dict( + type='RandomChoiceResize', + # The radio of all image in train dataset < 7 + # follow the original implement + scales=[(400, 4200), (500, 4200), (600, 4200)], + keep_ratio=True), + dict( + type='RandomCrop', + crop_type='absolute_range', + crop_size=(384, 600), + allow_negative_crop=True), + dict( + type='RandomChoiceResize', + scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333), + (608, 1333), (640, 1333), (672, 1333), (704, 1333), + (736, 1333), (768, 1333), (800, 1333)], + keep_ratio=True) + ] + ]), + dict(type='PackDetInputs') +] +train_dataloader = dict( + dataset=dict( + filter_cfg=dict(filter_empty_gt=False), pipeline=train_pipeline)) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + type='AdamW', + lr=0.0001, # 0.0002 for DeformDETR + weight_decay=0.0001), + clip_grad=dict(max_norm=0.1, norm_type=2), + paramwise_cfg=dict(custom_keys={'backbone': dict(lr_mult=0.1)}) +) # custom_keys contains sampling_offsets and reference_points in DeformDETR # noqa + +# learning policy +max_epochs = 12 +train_cfg = dict( + type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=1) + +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +param_scheduler = [ + dict( + type='MultiStepLR', + begin=0, + end=max_epochs, + by_epoch=True, + milestones=[11], + gamma=0.1) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (2 samples per GPU) +auto_scale_lr = dict(base_batch_size=16) diff --git a/mmdetection/projects/HDINO/h_dino.py b/mmdetection/projects/HDINO/h_dino.py new file mode 100644 index 00000000..3f9d116d --- /dev/null +++ b/mmdetection/projects/HDINO/h_dino.py @@ -0,0 +1,149 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple + +import torch +from torch import Tensor, nn +from torch.nn.init import normal_ + +from mmdet.models.detectors import DINO, DeformableDETR +from mmdet.models.detectors.deformable_detr import \ + MultiScaleDeformableAttention +from mmdet.registry import MODELS +from mmdet.structures import OptSampleList +from mmdet.utils import OptConfigType + + +@MODELS.register_module() +class HDINO(DINO): + + def __init__(self, + *args, + bbox_head: OptConfigType = None, + **kwargs) -> None: + self.method = 0 + self.num_query_one2one = bbox_head['num_query_one2one'] + super(HDINO, self).__init__(*args, bbox_head=bbox_head, **kwargs) + + def _init_layers(self) -> None: + super(HDINO, self)._init_layers() + self.query_embedding = None + if self.method == 1: + self.query_map = nn.Linear(self.embed_dims, self.embed_dims) + else: + self.pos_trans_fc = nn.Linear(self.embed_dims * 2, self.embed_dims) + self.pos_trans_norm = nn.LayerNorm(self.embed_dims) + + def init_weights(self) -> None: + super(DeformableDETR, self).init_weights() + """Initialize weights for Transformer and other components.""" + for coder in self.encoder, self.decoder: + for p in coder.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + for m in self.modules(): + if isinstance(m, MultiScaleDeformableAttention): + m.init_weights() + nn.init.xavier_uniform_(self.memory_trans_fc.weight) + normal_(self.level_embed) + + if self.method == 1: + nn.init.xavier_uniform_(self.query_map.weight) + else: + nn.init.xavier_uniform_(self.pos_trans_fc.weight) + + def pre_decoder( + self, + memory: Tensor, + memory_mask: Tensor, + spatial_shapes: Tensor, + batch_data_samples: OptSampleList = None, + ) -> Tuple[dict, dict]: + + bs, _, c = memory.shape + cls_out_features = self.bbox_head.cls_branches[ + self.decoder.num_layers].out_features + + output_memory, output_proposals = self.gen_encoder_output_proposals( + memory, memory_mask, spatial_shapes) + enc_outputs_class = self.bbox_head.cls_branches[ + self.decoder.num_layers]( + output_memory) + enc_outputs_coord_unact = self.bbox_head.reg_branches[ + self.decoder.num_layers](output_memory) + output_proposals + + # NOTE The DINO selects top-k proposals according to scores of + # multi-class classification, while DeformDETR, where the input + # is `enc_outputs_class[..., 0]` selects according to scores of + # binary classification. + topk_indices = torch.topk( + enc_outputs_class.max(-1)[0], k=self.num_queries, dim=1)[1] + topk_score = torch.gather( + enc_outputs_class, 1, + topk_indices.unsqueeze(-1).repeat(1, 1, cls_out_features)) + topk_coords_unact = torch.gather( + enc_outputs_coord_unact, 1, + topk_indices.unsqueeze(-1).repeat(1, 1, 4)) + topk_coords = topk_coords_unact.sigmoid() + topk_coords_unact = topk_coords_unact.detach() + + # We only made changes here. + # ------------------------------------- + if self.method == 1: + map_memory = self.query_map(memory.detach()) + query = torch.gather( + map_memory, 1, + topk_indices.unsqueeze(-1).repeat(1, 1, self.embed_dims)) + else: + pos_trans_out = self.pos_trans_fc( + self.get_proposal_pos_embed(topk_coords_unact)) + query = self.pos_trans_norm(pos_trans_out) + # ------------------------------------- + + if self.training: + dn_label_query, dn_bbox_query, dn_mask, dn_meta = \ + self.dn_query_generator(batch_data_samples) + query = torch.cat([dn_label_query, query], dim=1) + reference_points = torch.cat([dn_bbox_query, topk_coords_unact], + dim=1) + else: + reference_points = topk_coords_unact + dn_mask, dn_meta = None, None + reference_points = reference_points.sigmoid() + + decoder_inputs_dict = dict( + query=query, + memory=memory, + reference_points=reference_points, + dn_mask=dn_mask) + # NOTE DINO calculates encoder losses on scores and coordinates + # of selected top-k encoder queries, while DeformDETR is of all + # encoder queries. + head_inputs_dict = dict( + enc_outputs_class=topk_score, + enc_outputs_coord=topk_coords, + dn_meta=dn_meta) if self.training else dict() + + # We only made changes here. + # ------------------------------------- + if self.training: + # train: num_denoising_queries + num_query_one2one + # + num_query_one2many + dn_mask = decoder_inputs_dict['dn_mask'] + num_denoising_queries = head_inputs_dict['dn_meta'][ + 'num_denoising_queries'] + num_query_one2one = num_denoising_queries + self.num_query_one2one + # dn_mask[num_query_one2one:, :num_query_one2one] = True + dn_mask[num_denoising_queries:num_query_one2one, + num_query_one2one:] = True + decoder_inputs_dict['dn_mask'] = dn_mask + else: + # test: num_query_one2one + # + num_query_one2many + query = decoder_inputs_dict['query'] + reference_points = decoder_inputs_dict['reference_points'] + num_query_one2many = self.num_queries - self.num_query_one2one + decoder_inputs_dict['query'] = query[:num_query_one2many] + decoder_inputs_dict[ + 'reference_points'] = reference_points[:num_query_one2many] + # ------------------------------------- + return decoder_inputs_dict, head_inputs_dict diff --git a/mmdetection/projects/HDINO/h_dino_head.py b/mmdetection/projects/HDINO/h_dino_head.py new file mode 100644 index 00000000..aa1d0867 --- /dev/null +++ b/mmdetection/projects/HDINO/h_dino_head.py @@ -0,0 +1,112 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List + +from torch import Tensor + +from mmdet.models.dense_heads.dino_head import DINOHead +from mmdet.models.utils import multi_apply +from mmdet.registry import MODELS +from mmdet.utils import InstanceList, OptInstanceList + + +@MODELS.register_module() +class HybridDINOHead(DINOHead): + """Head of the Hybrid Matching.""" + + def __init__(self, + *args, + num_query_one2one: int = 900, + k_one2many: int = 2, + **kwargs) -> None: + self.num_query_one2one = num_query_one2one + self.k_one2many = k_one2many + super().__init__(*args, **kwargs) + + def loss_by_feat( + self, + all_layers_cls_scores: Tensor, + all_layers_bbox_preds: Tensor, + enc_cls_scores: Tensor, + enc_bbox_preds: Tensor, + batch_gt_instances: InstanceList, + batch_img_metas: List[dict], + dn_meta: Dict[str, int], + batch_gt_instances_ignore: OptInstanceList = None + ) -> Dict[str, Tensor]: + """Loss function. + + Args: + all_layers_cls_scores (Tensor): Classification scores of all + decoder layers, has shape (num_decoder_layers, bs, + num_queries_total, cls_out_channels), where + `num_queries_total` is the sum of `num_denoising_queries` + and `num_matching_queries`. + all_layers_bbox_preds (Tensor): Regression outputs of all decoder + layers. Each is a 4D-tensor with normalized coordinate format + (cx, cy, w, h) and has shape (num_decoder_layers, bs, + num_queries_total, 4). + enc_cls_scores (Tensor): The score of each point on encode + feature map, has shape (bs, num_feat_points, cls_out_channels). + enc_bbox_preds (Tensor): The proposal generate from the encode + feature map, has shape (bs, num_feat_points, 4) with the last + dimension arranged as (cx, cy, w, h). + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + dn_meta (Dict[str, int]): The dictionary saves information about + group collation, including 'num_denoising_queries' and + 'num_denoising_groups'. It will be used for split outputs of + denoising and matching parts and loss calculation. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + # train: num_denoising_queries + num_query_one2one + # + num_query_one2many + num_query_one2one = dn_meta[ + 'num_denoising_queries'] + self.num_query_one2one + outputs_classes_one2one = \ + all_layers_cls_scores[:, :, 0: num_query_one2one, :] + outputs_coords_one2one = \ + all_layers_bbox_preds[:, :, 0: num_query_one2one, :] + # hybrid-matching part + outputs_classes_one2many = \ + all_layers_cls_scores[:, :, num_query_one2one:, :] + outputs_coords_one2many = \ + all_layers_bbox_preds[:, :, num_query_one2one:, :] + + loss_dict = super(HybridDINOHead, self).loss_by_feat( + outputs_classes_one2one, outputs_coords_one2one, enc_cls_scores, + enc_bbox_preds, batch_gt_instances, batch_img_metas, dn_meta, + batch_gt_instances_ignore) + + o2m_batch_gt_instances = [] + for gt_instance in batch_gt_instances: + bboxes = gt_instance.bboxes.repeat(self.k_one2many, 1) + labels = gt_instance.labels.repeat(self.k_one2many) + new_gt_instance = gt_instance.new(bboxes=bboxes, labels=labels) + o2m_batch_gt_instances.append(new_gt_instance) + + losses_cls_o2m, losses_bbox_o2m, losses_iou_o2m = multi_apply( + self.loss_by_feat_single, + outputs_classes_one2many, + outputs_coords_one2many, + batch_gt_instances=o2m_batch_gt_instances, + batch_img_metas=batch_img_metas) + + loss_dict['loss_cls_o2m'] = losses_cls_o2m[-1] + loss_dict['loss_bbox_o2m'] = losses_bbox_o2m[-1] + loss_dict['loss_iou_o2m'] = losses_iou_o2m[-1] + for num_dec_layer, (loss_cls_i, loss_bbox_i, loss_iou_i) in \ + enumerate(zip(losses_cls_o2m[:-1], losses_bbox_o2m[:-1], + losses_iou_o2m[:-1])): + loss_dict[f'd{num_dec_layer}.loss_cls_o2m'] = loss_cls_i + loss_dict[f'd{num_dec_layer}.loss_bbox_o2m'] = loss_bbox_i + loss_dict[f'd{num_dec_layer}.loss_iou_o2m'] = loss_iou_i + return loss_dict diff --git a/mmdetection/projects/LabelStudio/backend_template/_wsgi.py b/mmdetection/projects/LabelStudio/backend_template/_wsgi.py new file mode 100644 index 00000000..1f8fb68c --- /dev/null +++ b/mmdetection/projects/LabelStudio/backend_template/_wsgi.py @@ -0,0 +1,145 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import json +import logging +import logging.config +import os + +logging.config.dictConfig({ + 'version': 1, + 'formatters': { + 'standard': { + 'format': + '[%(asctime)s] [%(levelname)s] [%(name)s::%(funcName)s::%(lineno)d] %(message)s' # noqa E501 + } + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'level': 'DEBUG', + 'stream': 'ext://sys.stdout', + 'formatter': 'standard' + } + }, + 'root': { + 'level': 'ERROR', + 'handlers': ['console'], + 'propagate': True + } +}) + +_DEFAULT_CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'config.json') + + +def get_kwargs_from_config(config_path=_DEFAULT_CONFIG_PATH): + if not os.path.exists(config_path): + return dict() + with open(config_path) as f: + config = json.load(f) + assert isinstance(config, dict) + return config + + +if __name__ == '__main__': + + from label_studio_ml.api import init_app + + from projects.LabelStudio.backend_template.mmdetection import MMDetection + + parser = argparse.ArgumentParser(description='Label studio') + parser.add_argument( + '-p', + '--port', + dest='port', + type=int, + default=9090, + help='Server port') + parser.add_argument( + '--host', dest='host', type=str, default='0.0.0.0', help='Server host') + parser.add_argument( + '--kwargs', + '--with', + dest='kwargs', + metavar='KEY=VAL', + nargs='+', + type=lambda kv: kv.split('='), + help='Additional LabelStudioMLBase model initialization kwargs') + parser.add_argument( + '-d', + '--debug', + dest='debug', + action='store_true', + help='Switch debug mode') + parser.add_argument( + '--log-level', + dest='log_level', + choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], + default=None, + help='Logging level') + parser.add_argument( + '--model-dir', + dest='model_dir', + default=os.path.dirname(__file__), + help='Directory models are store', + ) + parser.add_argument( + '--check', + dest='check', + action='store_true', + help='Validate model instance before launching server') + + args = parser.parse_args() + + # setup logging level + if args.log_level: + logging.root.setLevel(args.log_level) + + def isfloat(value): + try: + float(value) + return True + except ValueError: + return False + + def parse_kwargs(): + param = dict() + for k, v in args.kwargs: + if v.isdigit(): + param[k] = int(v) + elif v == 'True' or v == 'true': + param[k] = True + elif v == 'False' or v == 'False': + param[k] = False + elif isfloat(v): + param[k] = float(v) + else: + param[k] = v + return param + + kwargs = get_kwargs_from_config() + + if args.kwargs: + kwargs.update(parse_kwargs()) + + if args.check: + print('Check "' + MMDetection.__name__ + '" instance creation..') + model = MMDetection(**kwargs) + + app = init_app( + model_class=MMDetection, + model_dir=os.environ.get('MODEL_DIR', args.model_dir), + redis_queue=os.environ.get('RQ_QUEUE_NAME', 'default'), + redis_host=os.environ.get('REDIS_HOST', 'localhost'), + redis_port=os.environ.get('REDIS_PORT', 6379), + **kwargs) + + app.run(host=args.host, port=args.port, debug=args.debug) + +else: + # for uWSGI use + app = init_app( + model_class=MMDetection, + model_dir=os.environ.get('MODEL_DIR', os.path.dirname(__file__)), + redis_queue=os.environ.get('RQ_QUEUE_NAME', 'default'), + redis_host=os.environ.get('REDIS_HOST', 'localhost'), + redis_port=os.environ.get('REDIS_PORT', 6379)) diff --git a/mmdetection/projects/LabelStudio/backend_template/mmdetection.py b/mmdetection/projects/LabelStudio/backend_template/mmdetection.py new file mode 100644 index 00000000..f25e80e8 --- /dev/null +++ b/mmdetection/projects/LabelStudio/backend_template/mmdetection.py @@ -0,0 +1,148 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import io +import json +import logging +import os +from urllib.parse import urlparse + +import boto3 +from botocore.exceptions import ClientError +from label_studio_ml.model import LabelStudioMLBase +from label_studio_ml.utils import (DATA_UNDEFINED_NAME, get_image_size, + get_single_tag_keys) +from label_studio_tools.core.utils.io import get_data_dir + +from mmdet.apis import inference_detector, init_detector + +logger = logging.getLogger(__name__) + + +class MMDetection(LabelStudioMLBase): + """Object detector based on https://github.com/open-mmlab/mmdetection.""" + + def __init__(self, + config_file=None, + checkpoint_file=None, + image_dir=None, + labels_file=None, + score_threshold=0.5, + device='cpu', + **kwargs): + + super(MMDetection, self).__init__(**kwargs) + config_file = config_file or os.environ['config_file'] + checkpoint_file = checkpoint_file or os.environ['checkpoint_file'] + self.config_file = config_file + self.checkpoint_file = checkpoint_file + self.labels_file = labels_file + # default Label Studio image upload folder + upload_dir = os.path.join(get_data_dir(), 'media', 'upload') + self.image_dir = image_dir or upload_dir + logger.debug( + f'{self.__class__.__name__} reads images from {self.image_dir}') + if self.labels_file and os.path.exists(self.labels_file): + self.label_map = json_load(self.labels_file) + else: + self.label_map = {} + + self.from_name, self.to_name, self.value, self.labels_in_config = get_single_tag_keys( # noqa E501 + self.parsed_label_config, 'RectangleLabels', 'Image') + schema = list(self.parsed_label_config.values())[0] + self.labels_in_config = set(self.labels_in_config) + + # Collect label maps from `predicted_values="airplane,car"` attribute in